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/ultima8.h"
24 #include "ultima/ultima8/usecode/usecode.h"
25 #include "ultima/ultima8/games/game_data.h"
26 #include "ultima/ultima8/usecode/uc_machine.h"
27 #include "ultima/ultima8/usecode/uc_list.h"
28 #include "ultima/ultima8/world/world.h"
29 #include "ultima/ultima8/kernel/kernel.h"
30 #include "ultima/ultima8/kernel/delay_process.h"
31 #include "ultima/ultima8/world/get_object.h"
32 #include "ultima/ultima8/graphics/main_shape_archive.h"
33 #include "ultima/ultima8/graphics/gump_shape_archive.h"
34 #include "ultima/ultima8/graphics/shape.h"
35 #include "ultima/ultima8/world/item_factory.h"
36 #include "ultima/ultima8/world/current_map.h"
37 #include "ultima/ultima8/world/fire_type.h"
38 #include "ultima/ultima8/misc/direction_util.h"
39 #include "ultima/ultima8/gumps/bark_gump.h"
40 #include "ultima/ultima8/gumps/ask_gump.h"
41 #include "ultima/ultima8/world/actors/actor_bark_notify_process.h"
42 #include "ultima/ultima8/gumps/paperdoll_gump.h"
43 #include "ultima/ultima8/gumps/game_map_gump.h"
44 #include "ultima/ultima8/world/world_point.h"
45 #include "ultima/ultima8/world/gravity_process.h"
46 #include "ultima/ultima8/world/loop_script.h"
47 #include "ultima/ultima8/world/camera_process.h"
48 #include "ultima/ultima8/world/sprite_process.h"
49 #include "ultima/ultima8/gumps/slider_gump.h"
50 #include "ultima/ultima8/usecode/uc_process.h"
51 #include "ultima/ultima8/world/destroy_item_process.h"
52 #include "ultima/ultima8/world/target_reticle_process.h"
53 #include "ultima/ultima8/world/snap_process.h"
54 #include "ultima/ultima8/world/super_sprite_process.h"
55 #include "ultima/ultima8/audio/audio_process.h"
56 #include "ultima/ultima8/world/actors/main_actor.h"
57 #include "ultima/ultima8/world/missile_tracker.h"
58 #include "ultima/ultima8/world/crosshair_process.h"
59 #include "ultima/ultima8/world/actors/anim_action.h"
60
61 namespace Ultima {
62 namespace Ultima8 {
63
64 // Shape for camera snap eggs. Same in Remorse and Regret.
65 static const uint32 SNAP_EGG_SHAPE = 0x4fe;
66
67 // Shape for small spark where a bullet hits. Same in Remorse and Regret.
68 static const uint32 BULLET_SPLASH_SHAPE = 0x1d9;
69
DEFINE_RUNTIME_CLASSTYPE_CODE(Item)70 DEFINE_RUNTIME_CLASSTYPE_CODE(Item)
71
72 Item::Item()
73 : _shape(0), _frame(0), _x(0), _y(0), _z(0),
74 _flags(0), _quality(0), _npcNum(0), _mapNum(0),
75 _extendedFlags(0), _parent(0),
76 _cachedShape(nullptr), _cachedShapeInfo(nullptr),
77 _gump(0), _gravityPid(0), _lastSetup(0),
78 _ix(0), _iy(0), _iz(0), _damagePoints(1) {
79 }
80
81
~Item()82 Item::~Item() {
83 }
84
dumpInfo() const85 void Item::dumpInfo() const {
86 pout << "Item " << getObjId() << " (class "
87 << GetClassType()._className << ", shape "
88 << getShape();
89
90 const char *ucname = GameData::get_instance()->getMainUsecode()->get_class_name(_shape);
91 if (ucname != nullptr) {
92 pout << " (uc:" << ucname << ")";
93 }
94
95 pout << ", " << getFrame() << ", (";
96
97 if (_parent) {
98 int32 gx, gy;
99 getGumpLocation(gx, gy);
100 pout << gx << "," << gy;
101 } else {
102 pout << _x << "," << _y << "," << _z;
103 }
104
105 pout << ") q:" << getQuality()
106 << ", m:" << getMapNum() << ", n:" << getNpcNum()
107 << ", f:0x" << ConsoleStream::hex << getFlags() << ", ef:0x"
108 << getExtFlags();
109
110 const ShapeInfo *info = getShapeInfo();
111 if (info) {
112 pout << " shapeinfo f:" << info->_flags << ", fam:"
113 << info->_family << ", et:" << info->_equipType;
114 }
115
116 pout << ")" << ConsoleStream::dec << Std::endl;
117 }
118
getParentAsContainer() const119 Container *Item::getParentAsContainer() const {
120 // No parent, no container
121 if (!_parent || getObject(_parent) == nullptr)
122 return nullptr;
123
124 Container *p = getContainer(_parent);
125
126 if (!p) {
127 perr << "Item " << getObjId() << " _parent (" << _parent << ") is an invalid Container ObjID" << Std::endl;
128 CANT_HAPPEN();
129 }
130
131 return p;
132 }
133
getTopItem()134 Item *Item::getTopItem() {
135 Container *parentItem = getParentAsContainer();
136
137 if (!parentItem) return this;
138
139 while (parentItem->getParentAsContainer()) {
140 parentItem = parentItem->getParentAsContainer();
141 }
142
143 return parentItem;
144 }
145
setLocation(int32 X,int32 Y,int32 Z)146 void Item::setLocation(int32 X, int32 Y, int32 Z) {
147 _x = X;
148 _y = Y;
149 _z = Z;
150 }
151
move(const Point3 & pt)152 void Item::move(const Point3 &pt) {
153 move(pt.x, pt.y, pt.z);
154 }
155
move(int32 X,int32 Y,int32 Z)156 void Item::move(int32 X, int32 Y, int32 Z) {
157 bool no_lerping = false;
158 CurrentMap *map = World::get_instance()->getCurrentMap();
159 int mapChunkSize = map->getChunkSize();
160
161 if (getObjId() == 1 && Z < 0) {
162 perr.Print("Warning: moving avatar below Z=0. (%d,%d,%d)\n", X, Y, Z);
163 }
164
165 // TODO: In Crusader, if we are moving the avatar the game also checks whether
166 // there is an active "shield zap" sprite that needs moving at the same time.
167
168 // It's currently in the ethereal void, remove it from there
169 if (_flags & FLG_ETHEREAL) {
170
171 // Remove us from the ethereal void
172 World::get_instance()->etherealRemove(_objId);
173 }
174
175 // Remove from container (if contained or equiped)
176 if (_flags & (FLG_CONTAINED | FLG_EQUIPPED)) {
177 if (_parent) {
178 // If we are flagged as Ethereal, we are already removed
179 if (!(_flags & FLG_ETHEREAL)) {
180 Container *p = getParentAsContainer();
181 if (p) p->removeItem(this);
182 }
183 } else
184 perr << "Item " << getObjId() << " FLG_CONTAINED or FLG_EQUIPPED set but item has no _parent" << Std::endl;
185
186 // Clear our owner.
187 _parent = 0;
188
189 // No lerping when going from a container to somewhere else
190 no_lerping = true;
191 }
192 // Item needs to be removed if in the map, and it is moving to a
193 // different chunk
194 else if ((_extendedFlags & EXT_INCURMAP) &&
195 ((_x / mapChunkSize != X / mapChunkSize) ||
196 (_y / mapChunkSize != Y / mapChunkSize))) {
197
198 // Remove us from the map
199 map->removeItem(this);
200 }
201
202 // Unset all the various _flags that no longer apply
203 _flags &= ~(FLG_CONTAINED | FLG_EQUIPPED | FLG_ETHEREAL);
204
205 // Set the location
206 _x = X;
207 _y = Y;
208 _z = Z;
209
210 // Add it back to the map if needed
211 if (!(_extendedFlags & EXT_INCURMAP)) {
212 // Disposable fast only items get put at the end
213 // While normal items get put at start
214 if (_flags & (FLG_DISPOSABLE | FLG_FAST_ONLY))
215 map->addItemToEnd(this);
216 else
217 map->addItem(this);
218 }
219
220 // Call just moved
221 callUsecodeEvent_justMoved();
222
223 // Are we moving somewhere fast
224 bool dest_fast = map->isChunkFast(X / mapChunkSize, Y / mapChunkSize);
225
226 // No lerping for this move
227 if (no_lerping) _extendedFlags |= EXT_LERP_NOPREV;
228
229 // If the destination is not in the fast area, and we are in
230 // the fast area, we need to call leaveFastArea, as long as we aren't
231 // being followed by the camera. We also disable lerping in such a case
232 if (!dest_fast && (_flags & Item::FLG_FASTAREA)) {
233 _extendedFlags |= EXT_LERP_NOPREV;
234 if (_extendedFlags & EXT_CAMERA)
235 CameraProcess::GetCameraProcess()->itemMoved();
236 else
237 leaveFastArea();
238
239 return; //we are done
240 }
241 // Or if the dest is fast, and we are not, we need to call enterFastArea
242 else if (dest_fast && !(_flags & Item::FLG_FASTAREA)) {
243 _extendedFlags |= EXT_LERP_NOPREV;
244 enterFastArea();
245 }
246
247 // If we are being followed, notify the camera that we moved
248 // Note that we don't need to
249 if (_extendedFlags & EXT_CAMERA)
250 CameraProcess::GetCameraProcess()->itemMoved();
251
252 if (_extendedFlags & EXT_TARGET)
253 TargetReticleProcess::get_instance()->itemMoved(this);
254 }
255
moveToContainer(Container * container,bool checkwghtvol)256 bool Item::moveToContainer(Container *container, bool checkwghtvol) {
257 // Null container, report an error message
258 if (!container) {
259 perr << "NULL container passed to Item::moveToContainer" << Std::endl;
260 return false;
261 }
262
263 // Already there, do nothing, but only if not ethereal
264 bool ethereal_same = false;
265 if (container->getObjId() == _parent) {
266 // If we are ethereal we'd like to know if we are just being moved back
267 if (_flags & FLG_ETHEREAL) ethereal_same = true;
268 else return true;
269 }
270
271 // Eh, can't do it
272 if (!container->CanAddItem(this, checkwghtvol)) return false;
273
274 // It's currently in the ethereal void
275 if (_flags & FLG_ETHEREAL) {
276
277 // Remove us from the ethereal void
278 World::get_instance()->etherealRemove(_objId);
279 }
280
281 // Remove from container (if contained or equiped)
282 if (_flags & (FLG_CONTAINED | FLG_EQUIPPED)) {
283 if (_parent) {
284 // If we are flagged as Ethereal, we are already removed
285 if (!(_flags & FLG_ETHEREAL)) {
286 Container *p = getParentAsContainer();
287 if (p) p->removeItem(this);
288 }
289 } else
290 perr << "Item " << getObjId() << " FLG_CONTAINED or FLG_EQUIPPED set but item has no _parent" << Std::endl;
291
292 // Clear our owner.
293 _parent = 0;
294 }
295 // Item needs to be removed if it in the map
296 else if (_extendedFlags & EXT_INCURMAP) {
297
298 // Remove us from the map
299 World::get_instance()->getCurrentMap()->removeItem(this);
300 }
301
302 // Unset all the various _flags that no longer apply
303 _flags &= ~(FLG_CONTAINED | FLG_EQUIPPED | FLG_ETHEREAL);
304
305 // Set location to 0,0,0 if we aren't an ethereal item moving back
306 if (!ethereal_same) _x = _y = 0;
307 _z = 0;
308
309 // Add the item, don't bother with checking weight or vol since we already
310 // know if it will fit or not
311 container->addItem(this, false);
312
313 // Set our owner.
314 _parent = container->getObjId();
315
316 // Set us contained
317 _flags |= FLG_CONTAINED;
318
319 // If moving to avatar, mark as OWNED
320 Item *p = this;
321 while (p->getParentAsContainer())
322 p = p->getParentAsContainer();
323 // In Avatar's inventory?
324 if (p->getObjId() == 1)
325 setFlagRecursively(FLG_OWNED);
326
327 // No lerping when moving to a container
328 _extendedFlags |= EXT_LERP_NOPREV;
329
330 // Call just moved
331 callUsecodeEvent_justMoved();
332
333 // For a container, it's fast if it's got a _gump open
334 bool dest_fast = (container->_flags & FLG_GUMP_OPEN) != 0;
335
336 // If the destination is not in the fast area, and we are in
337 // the fast area, we need to call leaveFastArea
338 if (!dest_fast && (_flags & Item::FLG_FASTAREA))
339 leaveFastArea();
340 // Or if the dest is fast, and we are not, we need to call enterFastArea
341 else if (dest_fast && !(_flags & Item::FLG_FASTAREA))
342 enterFastArea();
343
344 // Done
345 return true;
346 }
347
moveToEtherealVoid()348 void Item::moveToEtherealVoid() {
349 // It's already Ethereal
350 if (_flags & FLG_ETHEREAL) return;
351
352 // Add it to the ethereal void
353 World::get_instance()->etherealPush(_objId);
354
355 // It's owned by something removed it from the something, but keep _flags
356 if (_flags & (FLG_CONTAINED | FLG_EQUIPPED)) {
357
358 if (_parent) {
359 Container *p = getParentAsContainer();
360 if (p) p->removeItem(this);
361 } else
362 perr << "Item " << getObjId() << " FLG_CONTAINED or FLG_EQUIPPED set but item has no _parent" << Std::endl;
363 } else if (_extendedFlags & EXT_INCURMAP) {
364 World::get_instance()->getCurrentMap()->removeItem(this);
365 }
366
367 // Set the ETHEREAL Flag
368 _flags |= FLG_ETHEREAL;
369 }
370
returnFromEtherealVoid()371 void Item::returnFromEtherealVoid() {
372 // It's not Ethereal
373 if (!(_flags & FLG_ETHEREAL)) return;
374
375 // Ok, we can do 2 things here
376 // If an item has the contained or Equipped _flags set, we return it to it's owner
377 if (_flags & (FLG_CONTAINED | FLG_EQUIPPED)) {
378 Container *p = getParentAsContainer();
379 if (!p) {
380 perr << "Item " << getObjId() << " FLG_CONTAINED or FLG_EQUIPPED set but item has no valid _parent" << Std::endl;
381 CANT_HAPPEN();
382 }
383 moveToContainer(p);
384 }
385 // or we return it to the world
386 else {
387 move(_x, _y, _z);
388 }
389
390 }
391
movedByPlayer()392 void Item::movedByPlayer() {
393 // owned-by-avatar items can't be stolen
394 if (_flags & FLG_OWNED) return;
395
396 // Otherwise, player is stealing.
397 // See if anybody is around to notice.
398 Item *avatar = getItem(1);
399 UCList itemlist(2);
400 LOOPSCRIPT(script, LS_TOKEN_TRUE);
401 CurrentMap *currentmap = World::get_instance()->getCurrentMap();
402 currentmap->areaSearch(&itemlist, script, sizeof(script), avatar, 640, 0);
403
404 for (unsigned int i = 0; i < itemlist.getSize(); ++i) {
405 Actor *actor = getActor(itemlist.getuint16(i));
406 if (actor && !actor->isDead())
407 actor->callUsecodeEvent_AvatarStoleSomething(getObjId());
408 }
409 }
410
getZ() const411 int32 Item::getZ() const {
412 return _z;
413 }
414
getLocationAbsolute(int32 & X,int32 & Y,int32 & Z) const415 void Item::getLocationAbsolute(int32 &X, int32 &Y, int32 &Z) const {
416 if (_parent) {
417 Item *p = getParentAsContainer();
418
419 if (p) {
420 p->getLocationAbsolute(X, Y, Z);
421 return;
422 }
423 }
424
425 X = _x;
426 Y = _y;
427 Z = _z;
428 }
429
getGumpLocation(int32 & X,int32 & Y) const430 void Item::getGumpLocation(int32 &X, int32 &Y) const {
431 if (!_parent) return;
432
433 X = _y & 0xFF;
434 Y = (_y >> 8) & 0xFF;
435 }
436
setGumpLocation(int32 X,int32 Y)437 void Item::setGumpLocation(int32 X, int32 Y) {
438 if (!_parent) return;
439
440 _y = (X & 0xFF) + ((Y & 0xFF) << 8);
441 }
442
randomGumpLocation()443 void Item::randomGumpLocation() {
444 if (!_parent) return;
445
446 // This sets the coordinates to (255,255) and lets the ContainerGump
447 // randomize the position when it is next opened.
448 _y = 0xFFFF;
449 }
450
getCentre(int32 & X,int32 & Y,int32 & Z) const451 void Item::getCentre(int32 &X, int32 &Y, int32 &Z) const {
452 // constants!
453 const ShapeInfo *shapeinfo = getShapeInfo();
454 if (_flags & FLG_FLIPPED) {
455 X = _x - shapeinfo->_y * 16;
456 Y = _y - shapeinfo->_x * 16;
457 } else {
458 X = _x - shapeinfo->_x * 16;
459 Y = _y - shapeinfo->_y * 16;
460 }
461
462 Z = _z + shapeinfo->_z * 4;
463 }
464
getWorldBox() const465 Box Item::getWorldBox() const {
466 int32 xd, yd, zd;
467 getFootpadWorld(xd, yd, zd);
468 return Box(_x, _y, _z, xd, yd, zd);
469 }
470
setShape(uint32 shape)471 void Item::setShape(uint32 shape) {
472 _cachedShape = nullptr;
473
474 if (GAME_IS_CRUSADER && _shape && shape != _shape) {
475 // In Crusader, here we need to check if the shape
476 // changed from targetable to not-targetable, or vice-versa
477 const ShapeInfo *oldinfo = getShapeInfo();
478 _shape = shape;
479 _cachedShapeInfo = nullptr;
480 const ShapeInfo *newinfo = getShapeInfo();
481
482 if (!hasFlags(FLG_BROKEN) && oldinfo && newinfo) {
483 if (oldinfo->is_targetable() && !newinfo->is_targetable()) {
484 World::get_instance()->getCurrentMap()->removeTargetItem(this);
485 }
486 else if (!oldinfo->is_targetable() && newinfo->is_targetable()) {
487 World::get_instance()->getCurrentMap()->addTargetItem(this);
488 }
489 }
490 } else {
491 _shape = shape;
492 _cachedShapeInfo = nullptr;
493 }
494 }
495
overlaps(const Item & item2) const496 bool Item::overlaps(const Item &item2) const {
497 int32 x1a, y1a, z1a, x1b, y1b, z1b;
498 int32 x2a, y2a, z2a, x2b, y2b, z2b;
499 getLocation(x1b, y1b, z1a);
500 item2.getLocation(x2b, y2b, z2a);
501
502 int32 xd, yd, zd;
503 getFootpadWorld(xd, yd, zd);
504 x1a = x1b - xd;
505 y1a = y1b - yd;
506 z1b = z1a + zd;
507
508 item2.getFootpadWorld(xd, yd, zd);
509 x2a = x2b - xd;
510 y2a = y2b - yd;
511 z2b = z2a + zd;
512
513 if (x1b <= x2a || x2b <= x1a) return false;
514 if (y1b <= y2a || y2b <= y1a) return false;
515 if (z1b <= z2a || z2b <= z1a) return false;
516 return true;
517 }
518
overlapsxy(const Item & item2) const519 bool Item::overlapsxy(const Item &item2) const {
520 int32 x1a, y1a, z1a, x1b, y1b;
521 int32 x2a, y2a, z2a, x2b, y2b;
522 getLocation(x1b, y1b, z1a);
523 item2.getLocation(x2b, y2b, z2a);
524
525 int32 xd, yd, zd;
526 getFootpadWorld(xd, yd, zd);
527 x1a = x1b - xd;
528 y1a = y1b - yd;
529
530 item2.getFootpadWorld(xd, yd, zd);
531 x2a = x2b - xd;
532 y2a = y2b - yd;
533
534 if (x1b <= x2a || x2b <= x1a) return false;
535 if (y1b <= y2a || y2b <= y1a) return false;
536 return true;
537 }
538
isOn(const Item & item2) const539 bool Item::isOn(const Item &item2) const {
540 int32 x1a, y1a, z1a, x1b, y1b;
541 int32 x2a, y2a, z2a, x2b, y2b, z2b;
542 getLocation(x1b, y1b, z1a);
543 item2.getLocation(x2b, y2b, z2a);
544
545 int32 xd, yd, zd;
546 getFootpadWorld(xd, yd, zd);
547 x1a = x1b - xd;
548 y1a = y1b - yd;
549
550 item2.getFootpadWorld(xd, yd, zd);
551 x2a = x2b - xd;
552 y2a = y2b - yd;
553 z2b = z2a + zd;
554
555 if (x1b <= x2a || x2b <= x1a) return false;
556 if (y1b <= y2a || y2b <= y1a) return false;
557 if (z2b == z1a) return true;
558 return false;
559 }
560
isCompletelyOn(const Item & item2) const561 bool Item::isCompletelyOn(const Item &item2) const {
562 if (hasFlags(FLG_CONTAINED) || item2.hasFlags(FLG_CONTAINED))
563 return false;
564
565 int32 x1a, y1a, z1a, x1b, y1b;
566 int32 x2a, y2a, z2a, x2b, y2b, z2b;
567 getLocation(x1b, y1b, z1a);
568 item2.getLocation(x2b, y2b, z2a);
569
570 int32 xd, yd, zd;
571 getFootpadWorld(xd, yd, zd);
572 x1a = x1b - xd;
573 y1a = y1b - yd;
574
575 item2.getFootpadWorld(xd, yd, zd);
576 x2a = x2b - xd;
577 y2a = y2b - yd;
578 z2b = z2a + zd;
579
580 return ((x1b <= x2b && x2a <= x1a) &&
581 (y1b <= y2b && y2a <= y1a) &&
582 (z2b == z1a));
583 }
584
isCentreOn(const Item & item2) const585 bool Item::isCentreOn(const Item &item2) const {
586 int32 x1c, y1c, z1c;
587 int32 x2a, y2a, z2a, x2b, y2b, z2b;
588 item2.getLocation(x2b, y2b, z2a);
589
590 getCentre(x1c, y1c, z1c);
591
592 int32 xd, yd, zd;
593 item2.getFootpadWorld(xd, yd, zd);
594 x2a = x2b - xd;
595 y2a = y2b - yd;
596 z2b = z2a + zd;
597
598 if (x1c <= x2a || x2b <= x1c) return false;
599 if (y1c <= y2a || y2b <= y1c) return false;
600 if (z2b == getZ()) return true;
601 return false;
602 }
603
isOnScreen() const604 bool Item::isOnScreen() const {
605 GameMapGump *game_map = Ultima8Engine::get_instance()->getGameMapGump();
606
607 if (!game_map)
608 return false;
609
610 Rect game_map_dims;
611 int32 screenx = -1;
612 int32 screeny = -1;
613 game_map->GetLocationOfItem(_objId, screenx, screeny);
614 game_map->GetDims(game_map_dims);
615 int32 xd, yd, zd;
616 getFootpadWorld(xd, yd, zd);
617
618 if (game_map_dims.contains(screenx, screeny) &&
619 game_map_dims.contains(screenx + xd, screeny + yd)) {
620 return true;
621 }
622
623 return false;
624 }
625
isPartlyOnScreen() const626 bool Item::isPartlyOnScreen() const {
627 GameMapGump *game_map = Ultima8Engine::get_instance()->getGameMapGump();
628
629 if (!game_map)
630 return false;
631
632 Rect game_map_dims;
633 int32 screenx = -1;
634 int32 screeny = -1;
635 game_map->GetLocationOfItem(_objId, screenx, screeny);
636 game_map->GetDims(game_map_dims);
637 int32 xd, yd, zd;
638 getFootpadWorld(xd, yd, zd);
639
640 if (game_map_dims.contains(screenx, screeny) ||
641 game_map_dims.contains(screenx + xd, screeny + yd)) {
642 return true;
643 }
644
645 return false;
646 }
647
canExistAt(int32 x,int32 y,int32 z,bool needsupport) const648 bool Item::canExistAt(int32 x, int32 y, int32 z, bool needsupport) const {
649 CurrentMap *cm = World::get_instance()->getCurrentMap();
650 const Item *support;
651 bool valid = cm->isValidPosition(x, y, z, getShape(), getObjId(),
652 &support, 0);
653 return valid && (!needsupport || support);
654 }
655
getDirToItemCentre(const Item & item2) const656 Direction Item::getDirToItemCentre(const Item &item2) const {
657 int32 xv, yv, zv;
658 getCentre(xv, yv, zv);
659
660 int32 i2x, i2y, i2z;
661 item2.getCentre(i2x, i2y, i2z);
662
663 return Direction_GetWorldDir(i2y - yv, i2x - xv, dirmode_8dirs);
664 }
665
getDirToItemCentre(const Point3 & pt) const666 Direction Item::getDirToItemCentre(const Point3 &pt) const {
667 int32 xv, yv, zv;
668 getCentre(xv, yv, zv);
669
670 return Direction_GetWorldDir(pt.y - yv, pt.x - xv, dirmode_8dirs);
671 }
672
673
getRange(const Item & item2,bool checkz) const674 int Item::getRange(const Item &item2, bool checkz) const {
675 int32 thisX, thisY, thisZ;
676 int32 otherX, otherY, otherZ;
677 int32 thisXd, thisYd, thisZd;
678 int32 otherXd, otherYd, otherZd;
679 int32 thisXmin, thisYmin, thisZmax;
680 int32 otherXmin, otherYmin, otherZmax;
681
682 getLocationAbsolute(thisX, thisY, thisZ);
683 item2.getLocationAbsolute(otherX, otherY, otherZ);
684 getFootpadWorld(thisXd, thisYd, thisZd);
685 item2.getFootpadWorld(otherXd, otherYd, otherZd);
686
687 thisXmin = thisX - thisXd;
688 thisYmin = thisY - thisYd;
689 thisZmax = thisZ + thisZd;
690
691 otherXmin = otherX - otherXd;
692 otherYmin = otherY - otherYd;
693 otherZmax = otherZ + otherZd;
694
695 int32 range = 0;
696
697 if (thisXmin - otherX > range)
698 range = thisYmin - otherY;
699 if (otherXmin - thisX > range)
700 range = thisXmin - otherX;
701 if (thisYmin - otherY > range)
702 range = otherXmin - thisX;
703 if (otherYmin - thisY > range)
704 range = otherYmin - thisY;
705 if (checkz && thisZ - otherZmax > range)
706 range = thisZ - otherZmax;
707 if (checkz && otherZ - thisZmax > range)
708 range = otherZ - thisZmax;
709
710 return range;
711 }
712
getRangeIfVisible(const Item & item2) const713 int Item::getRangeIfVisible(const Item &item2) const {
714 World *world = World::get_instance();
715 CurrentMap *map = world->getCurrentMap();
716 int32 start[3];
717 int32 end[3];
718 int32 dims[3] = {1, 1, 1};
719 Std::list<CurrentMap::SweepItem> hitItems;
720 getCentre(start[0], start[1], start[2]);
721 item2.getCentre(end[0], end[1], end[2]);
722
723 int xdiff = abs(start[0] - end[0]);
724 int ydiff = abs(start[1] - end[1]);
725 int zdiff = abs(start[2] - end[2]);
726
727 map->sweepTest(start, end, dims, getShapeInfo()->_flags, _objId, true, &hitItems);
728
729 if (hitItems.size() > 0) {
730 for (Std::list<CurrentMap::SweepItem>::const_iterator it = hitItems.begin();
731 it != hitItems.end();
732 it++) {
733 int objId = it->_item;
734 if (it->_blocking && objId != _objId && objId != item2.getObjId()) {
735 //int out[3];
736 //it->GetInterpolatedCoords(out, start, end);
737 //warning("found blocking item %d at %d %d %d.", objId, out[0], out[1], out[2]);
738 return 0;
739 }
740 }
741 }
742
743 int distance = MAX(MAX(xdiff, ydiff), zdiff);
744 return distance;
745 }
746
getShapeInfoFromGameInstance() const747 const ShapeInfo *Item::getShapeInfoFromGameInstance() const {
748 return GameData::get_instance()->getMainShapes()->getShapeInfo(_shape);
749 }
750
getShapeObject() const751 const Shape *Item::getShapeObject() const {
752 if (!_cachedShape) _cachedShape = GameData::get_instance()->getMainShapes()->getShape(_shape);
753 return _cachedShape;
754 }
755
getFamily() const756 uint16 Item::getFamily() const {
757 const ShapeInfo *info = getShapeInfo();
758 if (!info)
759 return 0;
760 return static_cast<uint16>(info->_family);
761 }
762
getWeight() const763 uint32 Item::getWeight() const {
764 uint32 weight = getShapeInfo()->_weight;
765
766 switch (getShapeInfo()->_family) {
767 case ShapeInfo::SF_QUANTITY:
768 return ((getQuality() * weight) + 9) / 10;
769 case ShapeInfo::SF_REAGENT:
770 return getQuality() * weight;
771 default:
772 return weight * 10;
773 }
774 }
775
getTotalWeight() const776 uint32 Item::getTotalWeight() const {
777 return getWeight();
778 }
779
getVolume() const780 uint32 Item::getVolume() const {
781 // invisible items (trap markers and such) don't take up volume
782 if (hasFlags(FLG_INVISIBLE))
783 return 0;
784
785
786 uint32 volume = getShapeInfo()->_volume;
787
788 switch (getShapeInfo()->_family) {
789 case ShapeInfo::SF_QUANTITY:
790 return ((getQuality() * volume) + 99) / 100;
791 case ShapeInfo::SF_REAGENT:
792 return ((getQuality() * volume) + 9) / 10;
793 case ShapeInfo::SF_CONTAINER:
794 return (volume == 0) ? 1 : volume;
795 default:
796 return volume;
797 }
798 }
799
checkLoopScript(const uint8 * script,uint32 scriptsize) const800 bool Item::checkLoopScript(const uint8 *script, uint32 scriptsize) const {
801 // if really necessary this could be made static to prevent news/deletes
802 DynamicUCStack stack(0x40); // 64bytes should be plenty of room
803
804 unsigned int i = 0;
805
806 uint16 ui16a, ui16b;
807
808 stack.push2(1); // default to true if script is empty
809
810 while (i < scriptsize) {
811 switch (script[i]) {
812 case LS_TOKEN_FALSE: // false
813 stack.push2(0);
814 break;
815
816 case LS_TOKEN_TRUE: // true
817 stack.push2(1);
818 break;
819
820 case LS_TOKEN_END: // end
821 ui16a = stack.pop2();
822 return (ui16a != 0);
823
824 case LS_TOKEN_INT: // int
825 //! check for i out of bounds
826 ui16a = script[i + 1] + (script[i + 2] << 8);
827 stack.push2(ui16a);
828 i += 2;
829 break;
830
831 case LS_TOKEN_AND: // and
832 ui16a = stack.pop2();
833 ui16b = stack.pop2();
834 if (ui16a != 0 && ui16b != 0)
835 stack.push2(1);
836 else
837 stack.push2(0);
838 break;
839
840 case LS_TOKEN_OR: // or
841 ui16a = stack.pop2();
842 ui16b = stack.pop2();
843 if (ui16a != 0 || ui16b != 0)
844 stack.push2(1);
845 else
846 stack.push2(0);
847 break;
848
849 case LS_TOKEN_NOT: // not
850 ui16a = stack.pop2();
851 if (ui16a != 0)
852 stack.push2(0);
853 else
854 stack.push2(1);
855 break;
856
857 case LS_TOKEN_STATUS: // item status
858 stack.push2(getFlags());
859 break;
860
861 case LS_TOKEN_Q: // item _quality
862 stack.push2(getQuality());
863 break;
864
865 case LS_TOKEN_NPCNUM: // npc num
866 stack.push2(getNpcNum());
867 break;
868
869 case LS_TOKEN_EQUAL: // equal
870 ui16a = stack.pop2();
871 ui16b = stack.pop2();
872 if (ui16a == ui16b)
873 stack.push2(1);
874 else
875 stack.push2(0);
876 break;
877
878 case LS_TOKEN_GREATER: // greater than
879 ui16a = stack.pop2();
880 ui16b = stack.pop2();
881 if (ui16b > ui16a)
882 stack.push2(1);
883 else
884 stack.push2(0);
885 break;
886
887 case LS_TOKEN_LESS: // less than
888 ui16a = stack.pop2();
889 ui16b = stack.pop2();
890 if (ui16b < ui16a)
891 stack.push2(1);
892 else
893 stack.push2(0);
894 break;
895
896 case LS_TOKEN_GEQUAL: // greater or equal
897 ui16a = stack.pop2();
898 ui16b = stack.pop2();
899 if (ui16b >= ui16a)
900 stack.push2(1);
901 else
902 stack.push2(0);
903 break;
904
905 case LS_TOKEN_LEQUAL: // smaller or equal
906 ui16a = stack.pop2();
907 ui16b = stack.pop2();
908 if (ui16b <= ui16a)
909 stack.push2(1);
910 else
911 stack.push2(0);
912 break;
913
914 case LS_TOKEN_FAMILY: // item family
915 stack.push2(getFamily());
916 break;
917
918 case LS_TOKEN_SHAPE: // item _shape
919 stack.push2(static_cast<uint16>(getShape()));
920 break;
921
922 case 'A':
923 case 'B':
924 case 'C':
925 case 'D':
926 case 'E':
927 case 'F':
928 case 'G':
929 case 'H':
930 case 'I':
931 case 'J':
932 case 'K':
933 case 'L':
934 case 'M':
935 case 'N':
936 case 'O':
937 case 'P':
938 case 'Q':
939 case 'R':
940 case 'S':
941 case 'T':
942 case 'U':
943 case 'V':
944 case 'W':
945 case 'X':
946 case 'Y':
947 case 'Z': {
948 bool match = false;
949 int count = script[i] - '@';
950 for (int j = 0; j < count; j++) {
951 //! check for i out of bounds
952 if (getShape() == static_cast<uint32>(script[i + 1] + (script[i + 2] << 8)))
953 match = true;
954 i += 2;
955 }
956 if (match)
957 stack.push2(1);
958 else
959 stack.push2(0);
960 }
961 break;
962
963 case LS_TOKEN_FRAME: // item _frame
964 stack.push2(static_cast<uint16>(getFrame()));
965 break;
966
967 case 'a':
968 case 'b':
969 case 'c':
970 case 'd':
971 case 'e':
972 case 'f':
973 case 'g':
974 case 'h':
975 case 'i':
976 case 'j':
977 case 'k':
978 case 'l':
979 case 'm':
980 case 'n':
981 case 'o':
982 case 'p':
983 case 'q':
984 case 'r':
985 case 's':
986 case 't':
987 case 'u':
988 case 'v':
989 case 'w':
990 case 'x':
991 case 'y':
992 case 'z': {
993 bool match = false;
994 int count = script[i] - '`';
995 for (int j = 0; j < count; j++) {
996 //! check for i out of bounds
997 if (getFrame() == static_cast<uint32>(script[i + 1] + (script[i + 2] << 8)))
998 match = true;
999 i += 2;
1000 }
1001 if (match)
1002 stack.push2(1);
1003 else
1004 stack.push2(0);
1005 }
1006 break;
1007
1008 default:
1009 perr.Print("Unknown loopscript opcode %02X\n", script[i]);
1010 }
1011
1012 i++;
1013 }
1014 perr.Print("Didn't encounter $ in loopscript\n");
1015 return false;
1016 }
1017
1018
collideMove(int32 dx,int32 dy,int32 dz,bool teleport,bool force,ObjId * hititem,uint8 * dirs)1019 int32 Item::collideMove(int32 dx, int32 dy, int32 dz, bool teleport, bool force, ObjId *hititem, uint8 *dirs) {
1020 Ultima8Engine *guiapp = Ultima8Engine::get_instance();
1021 World *world = World::get_instance();
1022 CurrentMap *map = world->getCurrentMap();
1023
1024 if (hititem) *hititem = 0;
1025 if (dirs) *dirs = 0;
1026
1027 int32 end[3] = { dx, dy, dz };
1028
1029 int32 start[3];
1030 if (_parent) {
1031 // If we are moving from a container, only check the destination
1032 start[0] = end[0];
1033 start[1] = end[1];
1034 start[2] = end[2];
1035 } else {
1036 // Otherwise check from where we are to where we want to go
1037 getLocation(start[0], start[1], start[2]);
1038 }
1039
1040 int32 dims[3];
1041 getFootpadWorld(dims[0], dims[1], dims[2]);
1042
1043 // Do the sweep test
1044 Std::list<CurrentMap::SweepItem> collisions;
1045 Std::list<CurrentMap::SweepItem>::iterator it;
1046 map->sweepTest(start, end, dims, getShapeInfo()->_flags, _objId, false, &collisions);
1047
1048 // Ok, now to work out what to do
1049
1050
1051 // the force of the hit, used for the gotHit/hit usecode calls
1052 int deltax = ABS(start[0] - end[0]) / 4;
1053 int deltay = ABS(start[1] - end[1]) / 4;
1054 int deltaz = ABS(start[2] - end[2]);
1055 int maxdirdelta = deltay;
1056 if (deltay > maxdirdelta) maxdirdelta = deltay;
1057 if (deltaz > maxdirdelta) maxdirdelta = deltaz;
1058 int hitforce = (deltax + deltay + deltaz + maxdirdelta) / 2;
1059
1060 // if we are contained, we always teleport
1061 if (teleport || _parent) {
1062 // If teleporting and not force, work out if we can reach the end
1063 if (!force) {
1064 for (it = collisions.begin(); it != collisions.end(); it++) {
1065 // Uh oh, we hit something, can't move
1066 if (it->_endTime == 0x4000 && !it->_touching && it->_blocking) {
1067 if (hititem) *hititem = it->_item;
1068 if (dirs) *dirs = it->_dirs;
1069 return 0;
1070 }
1071 }
1072 }
1073
1074 // Trigger all the required events
1075 bool we_were_released = false;
1076 for (it = collisions.begin(); it != collisions.end(); it++) {
1077 Item *item = getItem(it->_item);
1078 if (!item)
1079 continue; // shouldn't happen..
1080
1081 // Hitting us at the start and end, don't do anything
1082 if (!_parent && it->_hitTime == 0x0000 &&
1083 it->_endTime == 0x4000) {
1084 continue;
1085 }
1086 // Hitting us at the end (call hit on us, got hit on them)
1087 else if (it->_endTime == 0x4000) {
1088 if (_objId == 1 && guiapp->isShowTouchingItems())
1089 item->setExtFlag(Item::EXT_HIGHLIGHT);
1090
1091 item->callUsecodeEvent_gotHit(_objId, hitforce);
1092 callUsecodeEvent_hit(item->getObjId(), hitforce);
1093 }
1094 // Hitting us at the start (call release on us and them)
1095 else if (!_parent && it->_hitTime == 0x0000) {
1096 if (_objId == 1) item->clearExtFlag(Item::EXT_HIGHLIGHT);
1097 we_were_released = true;
1098 item->callUsecodeEvent_release();
1099 }
1100 }
1101
1102 // Call release on us
1103 if (we_were_released) callUsecodeEvent_release();
1104
1105 // Move US!
1106 move(end[0], end[1], end[2]);
1107
1108 // We reached the end
1109 return 0x4000;
1110 } else {
1111 int32 hit = 0x4000;
1112
1113 // If not force, work out if we can reach the end
1114 // if not, need to do 'stuff'
1115 // We don't care about items hitting us at the start
1116 if (!force) {
1117 for (it = collisions.begin(); it != collisions.end(); it++) {
1118 if (it->_blocking && !it->_touching) {
1119 if (hititem) *hititem = it->_item;
1120 if (dirs) *dirs = it->_dirs;
1121 hit = it->_hitTime;
1122 break;
1123 }
1124 }
1125 if (hit < 0) hit = 0;
1126
1127 if (hit != 0x4000) {
1128 #if 0
1129 pout << " Hit time: " << hit << Std::endl;
1130 pout << " Start: " << start[0] << ", " << start[1] << ", " << start[2] << Std::endl;
1131 pout << " End: " << end[0] << ", " << end[1] << ", " << end[2] << Std::endl;
1132 #endif
1133 it->GetInterpolatedCoords(end, start, end);
1134 #if 0
1135 pout << "Collision: " << end[0] << ", " << end[1] << ", " << end[2] << Std::endl;
1136 #endif
1137 }
1138 }
1139
1140 // Trigger all the required events
1141 bool we_were_released = false;
1142 for (it = collisions.begin(); it != collisions.end(); it++) {
1143 Item *item = getItem(it->_item);
1144 if (!item) continue;
1145
1146 // Did we go past the endpoint of the move?
1147 if (it->_hitTime > hit) break;
1148
1149 uint16 proc_gothit = 0, proc_rel = 0;
1150
1151 // If hitting at start, we should have already
1152 // called gotHit and hit. In Crusader we call
1153 // hit for every hitting frame to make sickbays
1154 // and teleporters work.
1155 bool call_hit = it->_hitTime >= 0 || GAME_IS_CRUSADER;
1156 if ((!it->_touching || it->_touchingFloor) && call_hit) {
1157 if (_objId == 1 && guiapp->isShowTouchingItems())
1158 item->setExtFlag(Item::EXT_HIGHLIGHT);
1159
1160 proc_gothit = item->callUsecodeEvent_gotHit(_objId, hitforce);
1161 callUsecodeEvent_hit(item->getObjId(), hitforce);
1162 }
1163
1164 // If not hitting at end, we will need to call release
1165 if (it->_endTime < hit) {
1166 if (_objId == 1) item->clearExtFlag(Item::EXT_HIGHLIGHT);
1167 we_were_released = true;
1168 proc_rel = item->callUsecodeEvent_release();
1169 }
1170
1171 // We want to make sure that release is called AFTER gotHit.
1172 if (proc_rel && proc_gothit) {
1173 Process *p = Kernel::get_instance()->getProcess(proc_rel);
1174 p->waitFor(proc_gothit);
1175 }
1176 }
1177
1178 // Call release on us
1179 if (we_were_released) callUsecodeEvent_release();
1180
1181 // Move US!
1182 move(end[0], end[1], end[2]);
1183
1184 return hit;
1185 }
1186
1187 return 0;
1188 }
1189
fireWeapon(int32 x,int32 y,int32 z,Direction dir,int firetype,bool findtarget)1190 uint16 Item::fireWeapon(int32 x, int32 y, int32 z, Direction dir, int firetype, bool findtarget) {
1191 int32 ix, iy, iz;
1192 getLocation(ix, iy, iz);
1193
1194 if (!GAME_IS_CRUSADER)
1195 return 0;
1196
1197 ix += x;
1198 iy += y;
1199 iz += z;
1200
1201 CurrentMap *currentmap = World::get_instance()->getCurrentMap();
1202 const FireType *firetypedat = GameData::get_instance()->getFireType(firetype);
1203
1204 if (!firetypedat)
1205 return 0;
1206
1207 int damage = firetypedat->getRandomDamage();
1208
1209 const Item *blocker = nullptr;
1210 // CHECKME: the original doesn't exclude the source like this,
1211 // but it seems obvious we have to or NPCs shoot themselves?
1212 bool isvalid = currentmap->isValidPosition(ix, iy, iz, BULLET_SPLASH_SHAPE, _objId, nullptr, nullptr, &blocker);
1213
1214 if (!isvalid && blocker) {
1215 Item *block = getItem(blocker->getObjId());
1216 Point3 blockpt;
1217 block->getLocation(blockpt);
1218 Direction damagedir = Direction_GetWorldDir(blockpt.y - iy, blockpt.x - ix, dirmode_8dirs);
1219 block->receiveHit(getObjId(), damagedir, damage, firetype);
1220 if (firetypedat->getRange() != 0) {
1221 int splashdamage = firetypedat->getRandomDamage();
1222 firetypedat->applySplashDamageAround(blockpt, splashdamage, 1, block, this);
1223 }
1224 if (firetypedat->getNearSprite())
1225 firetypedat->makeBulletSplashShapeAndPlaySound(ix, iy, iz);
1226 return 0;
1227 }
1228
1229 int spriteframe = 0; // fire types 1, 2, 7, 8, 0xb, 0xd
1230 switch (firetype) {
1231 case 3:
1232 case 9:
1233 case 10:
1234 spriteframe = dir + 0x11;
1235 break;
1236 case 5:
1237 spriteframe = dir + 1;
1238 break;
1239 case 6:
1240 spriteframe = 0x46;
1241 break;
1242 case 0xe:
1243 spriteframe = 0x47 + getRandom() % 5;
1244 break;
1245 case 0xf:
1246 case 0x12: // No Regret only
1247 case 0x13: // No Regret only
1248 spriteframe = 0x4c;
1249 break;
1250 case 0x10: // No Regret only
1251 spriteframe = dir + 0x50;
1252 break;
1253 case 0x11: // No Regret only
1254 spriteframe = dir * 6 + 0x78;
1255 break;
1256 case 0x14: // No Regret only
1257 spriteframe = dir * 3 + 0xdc;
1258 break;
1259 case 0x15: // No Regret only
1260 spriteframe = dir + 100;
1261 break;
1262 case 0x16: // No Regret only
1263 spriteframe = dir + 0x11;
1264 break;
1265 default:
1266 break;
1267 }
1268
1269 // HACK: this should be fixed to use inheritence so the behavior
1270 // is clean for both Item and Actor.
1271 DirectionMode dirmode = dirmode_8dirs;
1272 const Actor *thisactor = dynamic_cast<Actor *>(this);
1273 if (thisactor) {
1274 // TODO: Get damage for active inventory item if not a weapon?
1275 dirmode = thisactor->animDirMode(thisactor->getLastAnim());
1276 }
1277
1278 Item *target = nullptr;
1279 if (findtarget) {
1280 if (this != getControlledActor()) {
1281 target = getControlledActor();
1282 } else {
1283 target = currentmap->findBestTargetItem(ix, iy, iz - z, dir, dirmode);
1284 }
1285 }
1286
1287 int32 tx = -1;
1288 int32 ty = 0;
1289 int32 tz = 0;
1290 if (target) {
1291 target->getCentre(tx, ty, tz);
1292 tz = target->getTargetZRelativeToAttackerZ(getZ());
1293 }
1294
1295 // TODO: check if we need the equivalent of FUN_1130_0299 here..
1296 // maybe not? It seems to reset the target reticle etc which we
1297 // handle differently.
1298
1299 int numshots = firetypedat->getNumShots();
1300 uint16 spriteprocpid = 0;
1301 for (int i = 0; i < numshots; i++) {
1302 SuperSpriteProcess *ssp;
1303 CrosshairProcess *chp = CrosshairProcess::get_instance();
1304 assert(chp);
1305 Item *crosshair = getItem(chp->getItemNum());
1306 int32 ssx, ssy, ssz;
1307 if (tx != -1) {
1308 // Shoot toward the target
1309 ssx = tx;
1310 ssy = ty;
1311 ssz = tz;
1312 findtarget = true;
1313 } else if (this == getControlledActor() && crosshair) {
1314 // Shoot toward the crosshair
1315 crosshair->getLocation(ssx, ssy, ssz);
1316 ssz = iz;
1317 } else {
1318 // Just send the projectile off into the distance
1319 ssx = ix + Direction_XFactor(dir) * 0x500;
1320 ssy = iy + Direction_YFactor(dir) * 0x500;
1321 ssz = iz;
1322 }
1323
1324 uint16 targetid = (target ? target->getObjId() : 0);
1325 ssp = new SuperSpriteProcess(BULLET_SPLASH_SHAPE, spriteframe,
1326 ix, iy, iz, ssx, ssy, ssz, firetype,
1327 damage, _objId, targetid, findtarget);
1328 Kernel::get_instance()->addProcess(ssp);
1329 spriteprocpid = ssp->getPid();
1330 }
1331 return spriteprocpid;
1332 }
1333
fireDistance(const Item * other,Direction dir,int16 xoff,int16 yoff,int16 zoff) const1334 uint16 Item::fireDistance(const Item *other, Direction dir, int16 xoff, int16 yoff, int16 zoff) const {
1335 if (!other)
1336 return 0;
1337
1338 //
1339 // We pick what animation the actor would do to fire, then
1340 // pick the frame(s) where they fire in that anim.
1341 //
1342 // Then, check if the target can be hit using the attackx/attacky/attackz offsets.
1343 // The offsets are checked in priority order:
1344 // * First fire frame in anim
1345 // * Second fire frame
1346 // * if there are no fire frames, use the parameter offsets
1347 //
1348 int16 xoff2 = 0;
1349 int16 yoff2 = 0;
1350 int16 zoff2 = 0;
1351 bool other_offsets = false;
1352 const Actor *a = dynamic_cast<const Actor *>(this);
1353 if (a) {
1354 Animation::Sequence anim;
1355 bool kneeling = a->isKneeling();
1356 bool smallwpn = true;
1357 const MainActor *ma = dynamic_cast<const MainActor *>(this);
1358 const Item *wpn = getItem(a->getActiveWeapon());
1359 if (wpn && wpn->getShapeInfo()->_weaponInfo) {
1360 smallwpn = wpn->getShapeInfo()->_weaponInfo->_small;
1361 }
1362
1363 if (kneeling) {
1364 if (smallwpn)
1365 anim = Animation::kneelAndFireSmallWeapon;
1366 else
1367 anim = Animation::kneelAndFireLargeWeapon;
1368 } else {
1369 if (smallwpn || !ma)
1370 anim = Animation::fireSmallWeapon;
1371 else
1372 anim = Animation::fireLargeWeapon;
1373 }
1374
1375 bool first_offsets = false;
1376 const AnimAction *action = GameData::get_instance()->getMainShapes()->getAnim(_shape, static_cast<int32>(anim));
1377 unsigned int nframes = action ? action->getSize() : 0;
1378 for (unsigned int i = 0; i < nframes; i++) {
1379 const AnimFrame &frame = action->getFrame(dir, i);
1380 if (frame.is_cruattack()) {
1381 if (!first_offsets) {
1382 xoff = frame.cru_attackx();
1383 yoff = frame.cru_attacky();
1384 zoff = frame.cru_attackz();
1385 first_offsets = true;
1386 } else {
1387 xoff2 = frame.cru_attackx();
1388 yoff2 = frame.cru_attacky();
1389 zoff2 = frame.cru_attackz();
1390 other_offsets = true;
1391 break;
1392 }
1393 }
1394 }
1395 }
1396
1397 int32 x, y, z;
1398 getLocation(x, y, z);
1399
1400 int32 ox, oy, oz;
1401 other->getLocation(ox, oy, oz);
1402
1403 int32 dist = 0;
1404
1405 CurrentMap *cm = World::get_instance()->getCurrentMap();
1406 if (!cm)
1407 return 0;
1408
1409 for (int i = 0; i < (other_offsets ? 2 : 1) && dist == 0; i++) {
1410 int32 cx = x + (i == 0 ? xoff : xoff2);
1411 int32 cy = y + (i == 0 ? yoff : yoff2);
1412 int32 cz = z + (i == 0 ? zoff : zoff2);
1413 const Item *blocker = nullptr;
1414 bool valid = cm->isValidPosition(cx, cy, cz, BULLET_SPLASH_SHAPE,
1415 getObjId(), nullptr, nullptr, &blocker);
1416 if (!valid) {
1417 if (blocker->getObjId() == other->getObjId())
1418 dist = MAX(abs(_x - ox), abs(_y - oy));
1419 } else {
1420 int32 ocx, ocy, ocz;
1421 other->getCentre(ocx, ocy, ocz);
1422 ocz = other->getTargetZRelativeToAttackerZ(getZ());
1423 const int32 start[3] = {cx, cy, cz};
1424 const int32 end[3] = {ocx, ocy, ocz};
1425 const int32 dims[3] = {2, 2, 2};
1426
1427 Std::list<CurrentMap::SweepItem> collisions;
1428 Std::list<CurrentMap::SweepItem>::iterator it;
1429 cm->sweepTest(start, end, dims, ShapeInfo::SI_SOLID,
1430 _objId, true, &collisions);
1431 for (it = collisions.begin(); it != collisions.end(); it++) {
1432 if (it->_item == getObjId())
1433 continue;
1434 if (it->_touching)
1435 continue;
1436 if (it->_item != other->getObjId())
1437 break;
1438 int32 out[3];
1439 it->GetInterpolatedCoords(out, start, end);
1440 dist = MAX(abs(_x - out[0]), abs(_y - out[1]));
1441 break;
1442 }
1443 }
1444 }
1445
1446 if (!dist)
1447 return 0;
1448 return dist < 32 ? 1 : dist / 32;
1449 }
1450
getTargetZRelativeToAttackerZ(int32 otherz) const1451 int32 Item::getTargetZRelativeToAttackerZ(int32 otherz) const {
1452 int32 tsx, tsy, tsz;
1453 getFootpadData(tsx, tsy, tsz);
1454
1455 int32 tz = getZ() + tsz * 8;
1456
1457 if (tsz < 3) {
1458 if (tsz)
1459 tz -= 8;
1460 } else {
1461 int32 targetz = tz;
1462 tz -= 16;
1463 if (otherz - targetz < -0x2f) {
1464 tz += 8;
1465 } else if (otherz - targetz > 0x2f) {
1466 if (tsz == 6) {
1467 tz -= 16;
1468 } else if (tsz >= 7) {
1469 tz -= 24;
1470 }
1471 }
1472 }
1473 return tz;
1474 }
1475
1476
countNearby(uint32 shape,uint16 range)1477 unsigned int Item::countNearby(uint32 shape, uint16 range) {
1478 CurrentMap *currentmap = World::get_instance()->getCurrentMap();
1479 UCList itemlist(2);
1480 LOOPSCRIPT(script, LS_SHAPE_EQUAL(shape));
1481 currentmap->areaSearch(&itemlist, script, sizeof(script),
1482 this, range, false);
1483 return itemlist.getSize();
1484 }
1485
1486
callUsecodeEvent(uint32 event,const uint8 * args,int argsize)1487 uint32 Item::callUsecodeEvent(uint32 event, const uint8 *args, int argsize) {
1488 uint32 class_id = _shape;
1489
1490 // Non-monster NPCs use _objId/_npcNum + 1024 (2048 in crusader)
1491 // Note: in the original, a non-monster NPC is specified with
1492 // the FAST_ONLY flag. However, this causes some summoned monster which
1493 // do not receive the FAST_ONLY flag to behave strangely. (Confirmed that
1494 // happens in the original as well.) -wjp 20050128
1495 if (_objId < 256 && (_extendedFlags & EXT_PERMANENT_NPC)) {
1496 if (GAME_IS_U8)
1497 class_id = _objId + 1024;
1498 else
1499 class_id = _objId + 2048;
1500 }
1501
1502 // CHECKME: to make Pentagram behave as much like the original as possible,
1503 // don't call any usecode if the original would call the wrong class
1504 if (GAME_IS_U8 && _objId < 256 && !(_extendedFlags & EXT_PERMANENT_NPC) &&
1505 !(_flags & FLG_FAST_ONLY))
1506 return 0;
1507
1508 // UnkEggs have class quality + 0x47F in U8, +0x900 in Crusader
1509 if (getFamily() == ShapeInfo::SF_UNKEGG)
1510 class_id = _quality + (GAME_IS_U8 ? 0x47F : 0x900);
1511
1512 Usecode *u = GameData::get_instance()->getMainUsecode();
1513 uint32 offset = u->get_class_event(class_id, event);
1514 if (!offset) return 0; // event not found
1515
1516 debug(10, "Item: %d (shape %d) calling usecode event %d @ %04X:%04X",
1517 _objId, _shape, event, class_id, offset);
1518
1519 return callUsecode(static_cast<uint16>(class_id),
1520 static_cast<uint16>(offset),
1521 args, argsize);
1522 }
1523
callUsecodeEvent_look()1524 uint32 Item::callUsecodeEvent_look() { // event 0
1525 return callUsecodeEvent(0); // CONSTANT
1526 }
1527
callUsecodeEvent_use()1528 uint32 Item::callUsecodeEvent_use() { // event 1
1529 return callUsecodeEvent(1); // CONSTANT
1530 }
1531
callUsecodeEvent_anim()1532 uint32 Item::callUsecodeEvent_anim() { // event 2
1533 return callUsecodeEvent(2); // CONSTANT
1534 }
1535
callUsecodeEvent_cachein()1536 uint32 Item::callUsecodeEvent_cachein() { // event 4
1537 return callUsecodeEvent(4); // CONSTANT
1538 }
1539
callUsecodeEvent_hit(uint16 hitter,int16 hitforce)1540 uint32 Item::callUsecodeEvent_hit(uint16 hitter, int16 hitforce) { // event 5
1541 DynamicUCStack arg_stack(4);
1542 arg_stack.push2(hitforce);
1543 arg_stack.push2(hitter);
1544 return callUsecodeEvent(5, arg_stack.access(), 4); // CONSTANT 5
1545 }
1546
callUsecodeEvent_gotHit(uint16 hitter,int16 hitforce)1547 uint32 Item::callUsecodeEvent_gotHit(uint16 hitter, int16 hitforce) { // event 6
1548 DynamicUCStack arg_stack(4);
1549 arg_stack.push2(hitforce);
1550 arg_stack.push2(hitter);
1551 return callUsecodeEvent(6, arg_stack.access(), 4); // CONSTANT 6
1552 }
1553
callUsecodeEvent_hatch()1554 uint32 Item::callUsecodeEvent_hatch() { // event 7
1555 return callUsecodeEvent(7); // CONSTANT
1556 }
1557
callUsecodeEvent_schedule(uint32 time)1558 uint32 Item::callUsecodeEvent_schedule(uint32 time) { // event 8
1559 DynamicUCStack arg_stack(4);
1560 arg_stack.push4(time);
1561 return callUsecodeEvent(8, arg_stack.access(), 4); // CONSTANT 8
1562 }
1563
callUsecodeEvent_release()1564 uint32 Item::callUsecodeEvent_release() { // event 9
1565 return callUsecodeEvent(9); // CONSTANT
1566 }
1567
callUsecodeEvent_equip()1568 uint32 Item::callUsecodeEvent_equip() { // event A
1569 return callUsecodeEvent(0xA); // CONSTANT
1570 }
1571
callUsecodeEvent_equipWithParam(ObjId param)1572 uint32 Item::callUsecodeEvent_equipWithParam(ObjId param) { // event A
1573 DynamicUCStack arg_stack(2);
1574 arg_stack.push2(param);
1575 return callUsecodeEvent(0xA, arg_stack.access(), 2);
1576 }
1577
callUsecodeEvent_unequip()1578 uint32 Item::callUsecodeEvent_unequip() { // event B
1579 return callUsecodeEvent(0xB); // CONSTANT
1580 }
1581
callUsecodeEvent_unequipWithParam(ObjId param)1582 uint32 Item::callUsecodeEvent_unequipWithParam(ObjId param) { // event B
1583 DynamicUCStack arg_stack(2);
1584 arg_stack.push2(param);
1585 return callUsecodeEvent(0xB, arg_stack.access(), 2);
1586 }
1587
callUsecodeEvent_combine()1588 uint32 Item::callUsecodeEvent_combine() { // event C
1589 return callUsecodeEvent(0xC); // CONSTANT
1590 }
1591
callUsecodeEvent_calledFromAnim()1592 uint32 Item::callUsecodeEvent_calledFromAnim() { // event E
1593 return callUsecodeEvent(0xE); // CONSTANT
1594 }
1595
callUsecodeEvent_enterFastArea()1596 uint32 Item::callUsecodeEvent_enterFastArea() { // event F
1597 return callUsecodeEvent(0xF); // CONSTANT
1598 }
1599
callUsecodeEvent_leaveFastArea()1600 uint32 Item::callUsecodeEvent_leaveFastArea() { // event 10
1601 return callUsecodeEvent(0x10); // CONSTANT
1602 }
1603
callUsecodeEvent_cast(uint16 unk)1604 uint32 Item::callUsecodeEvent_cast(uint16 unk) { // event 11
1605 DynamicUCStack arg_stack(2);
1606 arg_stack.push2(unk);
1607 return callUsecodeEvent(0x11, arg_stack.access(), 2); // CONSTANT 0x11
1608 }
1609
callUsecodeEvent_justMoved()1610 uint32 Item::callUsecodeEvent_justMoved() { // event 12
1611 return callUsecodeEvent(0x12); // CONSTANT
1612 }
1613
callUsecodeEvent_AvatarStoleSomething(uint16 unk)1614 uint32 Item::callUsecodeEvent_AvatarStoleSomething(uint16 unk) { // event 13
1615 DynamicUCStack arg_stack(2);
1616 arg_stack.push2(unk);
1617 return callUsecodeEvent(0x13, arg_stack.access(), 2); // CONSTANT 0x13
1618 }
1619
callUsecodeEvent_guardianBark(int16 unk)1620 uint32 Item::callUsecodeEvent_guardianBark(int16 unk) { // event 15
1621 DynamicUCStack arg_stack(2);
1622 arg_stack.push2(unk);
1623 return callUsecodeEvent(0x15, arg_stack.access(), 2); // CONSTANT 0x15
1624 }
1625
callUsecodeEvent_unhatch()1626 uint32 Item::callUsecodeEvent_unhatch() { // event 15
1627 return callUsecodeEvent(0x15); // CONSTANT
1628 }
1629
use()1630 uint32 Item::use() {
1631 Actor *actor = dynamic_cast<Actor *>(this);
1632 if (actor) {
1633 if (actor->isDead()) {
1634 if (GAME_IS_U8) {
1635 // dead actor, so open/close the dead-body-gump
1636 if (hasFlags(FLG_GUMP_OPEN)) {
1637 closeGump();
1638 } else {
1639 openGump(12); // CONSTANT!!
1640 }
1641 }
1642 return 0;
1643 }
1644 }
1645
1646 return callUsecodeEvent_use();
1647 }
1648
destroy(bool delnow)1649 void Item::destroy(bool delnow) {
1650 if (_flags & FLG_ETHEREAL) {
1651 // Remove us from the ether
1652 World::get_instance()->etherealRemove(_objId);
1653 } else if (_parent) {
1654 // we're in a container, so remove self from _parent
1655 //!! need to make sure this works for equipped items too...
1656 Container *p = getParentAsContainer();
1657 if (p) p->removeItem(this);
1658 } else if (_extendedFlags & EXT_INCURMAP) {
1659 // remove self from CurrentMap
1660 World::get_instance()->getCurrentMap()->removeItemFromList(this, _x, _y);
1661 }
1662
1663 if (GAME_IS_CRUSADER) {
1664 // Ensure sounds for this object are stopped
1665 AudioProcess *audio = AudioProcess::get_instance();
1666 if (audio)
1667 audio->stopSFX(-1, _objId);
1668 if (_shape == SNAP_EGG_SHAPE) {
1669 SnapProcess *snap = SnapProcess::get_instance();
1670 if (snap)
1671 snap->removeEgg(this);
1672 }
1673 }
1674
1675 if (_extendedFlags & Item::EXT_CAMERA)
1676 CameraProcess::SetCameraProcess(0);
1677
1678 // Do we want to delete now or not?
1679 if (!delnow) {
1680 Process *dap = new DestroyItemProcess(this);
1681 Kernel::get_instance()->addProcess(dap);
1682 return;
1683 }
1684
1685 clearObjId();
1686 delete this; // delete self.
1687 }
1688
1689 //
1690 // Item::setupLerp()
1691 //
1692 // Desc: Setup the lerped info for this frame
1693 //
setupLerp(int32 gametick)1694 void Item::setupLerp(int32 gametick) {
1695 // Don't need to set us up
1696 if (_lastSetup && gametick == _lastSetup)
1697 return;
1698
1699 // Are we lerping or are we not? Default we lerp.
1700 bool no_lerp = false;
1701
1702 // No lerping this _frame if Shape Changed, or No lerp is set,
1703 // or no last setup, or last setup more than 1 tick ago
1704 if ((_lastSetup == 0) || (_lNext._shape != _shape) ||
1705 (_extendedFlags & EXT_LERP_NOPREV) ||
1706 (gametick - _lastSetup) > 1 || _flags & FLG_CONTAINED)
1707 no_lerp = true;
1708
1709 // Update the time we were just setup
1710 _lastSetup = gametick;
1711
1712 // Clear the flag
1713 _extendedFlags &= ~EXT_LERP_NOPREV;
1714
1715 // Animate it, if needed.
1716 //
1717 // We use (tick % speed == 0) here. To be completely faithful to the original
1718 // game it should be (tick % speed == tick % _objId). That is how the game
1719 // does it, but it also causes animation frame mismatches on multi-shape
1720 // objects. This is easily noticable on the waterfall West of Tenebrae,
1721 // which appears to tear slightly even on the original.
1722 //
1723 // In the original it was likely done to spread CPU time over different frames,
1724 // but it's a little bit nicer to do it correctly now that we can afford to..
1725 //
1726 const ShapeInfo *info = getShapeInfo();
1727 if (info->_animType &&
1728 ((gametick % info->_animSpeed) == 0))
1729 animateItem();
1730
1731 // Setup the prev values for lerping
1732 if (!no_lerp) _lPrev = _lNext;
1733
1734 // Setup next
1735 if (_flags & FLG_CONTAINED) {
1736 _lNext._x = _ix = _y & 0xFF;
1737 _lNext._y = _iy = (_y >> 8) & 0xFF;
1738 _lNext._z = _iz = 0;
1739 } else {
1740 _lNext._x = _ix = _x;
1741 _lNext._y = _iy = _y;
1742 _lNext._z = _iz = _z;
1743 }
1744 _lNext._shape = _shape;
1745 _lNext._frame = _frame;
1746
1747 // Setup prev values for not lerping
1748 if (no_lerp) _lPrev = _lNext;
1749 }
1750
1751 // Animate the item
animateItem()1752 void Item::animateItem() {
1753 const ShapeInfo *info = getShapeInfo();
1754
1755 if (!info->_animType)
1756 return;
1757
1758 uint32 anim_data = info->_animData;
1759 const Shape *shp = getShapeObject();
1760
1761 switch (info->_animType) {
1762 case 2:
1763 // Randomly change frame
1764 if ((getRandom() & 1) && shp)
1765 _frame = getRandom() % shp->frameCount();
1766 break;
1767
1768 case 1:
1769 case 3:
1770 // animdata 0 = always increment
1771 // animdata 1 = 50 % chance of changing
1772 // animdata 2+ = loop in frame blocks of size animdata
1773 if (anim_data == 0 || (anim_data == 1 && (getRandom() & 1))) {
1774 _frame++;
1775 if (shp && _frame >= shp->frameCount())
1776 _frame = 0;
1777 } else if (anim_data > 1) {
1778 _frame++;
1779 uint32 num = (_frame - 1) / anim_data;
1780 if (_frame == ((num + 1) * anim_data))
1781 _frame = num * anim_data;
1782 }
1783 break;
1784
1785 case 4:
1786 // Randomly start animating, with chance of 1/(animdata + 2)
1787 // once animating, go through all frames.
1788 if (_frame || getRandom() % (anim_data + 2)) {
1789 _frame++;
1790 if (shp && _frame >= shp->frameCount())
1791 _frame = 0;
1792 }
1793 break;
1794
1795 case 5:
1796 // Just call the usecode
1797 callUsecodeEvent_anim();
1798 break;
1799
1800 case 6:
1801 // animdata 0 = stick on frame 0, else loop from 1 to count
1802 // animdata 1 = same as 0, but with 50% chance of change
1803 // animdata 2+ = same, but loop in frame blocks of size animdata
1804 if (anim_data == 0 || (anim_data == 1 && (getRandom() & 1))) {
1805 if (!_frame)
1806 break;
1807 _frame++;
1808 if (shp && _frame >= shp->frameCount())
1809 _frame = 1;
1810 } else if (anim_data > 1) {
1811 if (!(_frame % anim_data))
1812 break;
1813 _frame++;
1814 uint32 num = (_frame - 1) / anim_data;
1815 if (_frame == ((num + 1) * anim_data))
1816 _frame = num * anim_data + 1;
1817 }
1818 break;
1819
1820 default:
1821 pout << "type " << info->_animType << " data " << anim_data << Std::endl;
1822 break;
1823 }
1824 }
1825
1826
1827 // Called when an item has entered the fast area
enterFastArea()1828 uint32 Item::enterFastArea() {
1829 uint16 retval = 0;
1830 //!! HACK to get rid of endless SFX loops
1831 if (_shape == 0x2c8 && GAME_IS_U8)
1832 return 0;
1833
1834 const ShapeInfo *si = getShapeInfo();
1835
1836 // Call usecode
1837 if (!(_flags & FLG_FASTAREA)) {
1838 Actor *actor = dynamic_cast<Actor *>(this);
1839 // Crusader special-cases a few shapes even when they are dead.
1840 bool call_even_if_dead = (_shape == 0x576 || _shape == 0x596 ||
1841 _shape == 0x59c || _shape == 0x58f) && GAME_IS_CRUSADER;
1842 if (actor && actor->isDead() && !call_even_if_dead) {
1843 // dead actor, don't call the usecode
1844 } else {
1845 if (actor && _objId != 1 && GAME_IS_CRUSADER) {
1846 uint16 lastactivity = actor->getLastActivityNo();
1847 actor->clearLastActivityNo();
1848 actor->clearInCombat();
1849 actor->setToStartOfAnim(Animation::stand);
1850 actor->clearActorFlag(Actor::ACT_WEAPONREADY);
1851 actor->setActivity(lastactivity);
1852 }
1853
1854 // TODO: For eggs, Crusader also resets the NPC info if a
1855 // certain global is set. For now just skip that.
1856
1857 //
1858 // NOTE: Original games only call usecode for actors or NOISY
1859 // types. We call for all types to fix an usecode bug in
1860 // Crusader: No Remorse. See note about it below.
1861 //
1862 // if (actor || si->_flags & ShapeInfo::SI_NOISY)
1863 retval = callUsecodeEvent_enterFastArea();
1864 }
1865 }
1866
1867 if (!hasFlags(FLG_BROKEN) && GAME_IS_CRUSADER) {
1868 if (si->is_targetable()) {
1869 World::get_instance()->getCurrentMap()->addTargetItem(this);
1870 }
1871 if (_shape == SNAP_EGG_SHAPE) {
1872 SnapProcess *snap = SnapProcess::get_instance();
1873 if (snap)
1874 snap->addEgg(this);
1875 }
1876 }
1877
1878 // We're fast!
1879 _flags |= FLG_FASTAREA;
1880
1881 //
1882 // WORKAROUND: Crusader: No Remorse usecode has one place (REB_EGG, after
1883 // randomly creating 0x34D REB_COUP (rebel couple) at the bar) where this
1884 // is called with an "implies" but the enterFastArea event code never
1885 // returns. Every other instance this is "spawn"ed.
1886 //
1887 // In the original this bug is never triggered as enterFastArea intrinsic
1888 // does not call usecode event unless the shape is SI_NOISY (REB_COUP is
1889 // not). The result is the rebel couple do not start moving until you move
1890 // near them and trigger their event, then enterFastArea function is
1891 // spawned directly.
1892 //
1893 // Work around both problems by always calling event above and return 0.
1894 //
1895 if (_shape == 0x34D && GAME_IS_REMORSE)
1896 return 0;
1897
1898 return retval;
1899 }
1900
1901 // Called when an item is leaving the fast area
leaveFastArea()1902 void Item::leaveFastArea() {
1903 if (_objId == 1) {
1904 debug(6, "avatar leaving fast area");
1905 }
1906
1907 // Call usecode
1908 if ((!(_flags & FLG_FAST_ONLY) || getShapeInfo()->is_noisy()) &&
1909 (_flags & FLG_FASTAREA))
1910 callUsecodeEvent_leaveFastArea();
1911
1912 // If we have a gump open, close it (unless we're in a container)
1913 if (!_parent && (_flags & FLG_GUMP_OPEN)) {
1914 Gump *g = Ultima8Engine::get_instance()->getGump(_gump);
1915 if (g) g->Close();
1916 }
1917
1918 // Unset the flag
1919 _flags &= ~FLG_FASTAREA;
1920
1921 if (!hasFlags(FLG_BROKEN) && GAME_IS_CRUSADER) {
1922 World::get_instance()->getCurrentMap()->removeTargetItem(this);
1923 if (_shape == SNAP_EGG_SHAPE) {
1924 SnapProcess *snap = SnapProcess::get_instance();
1925 if (snap)
1926 snap->removeEgg(this);
1927 }
1928 }
1929
1930 // CHECKME: what do we need to do exactly?
1931 // currently, destroy object
1932
1933 // Kill us if we are fast only, unless we're in a container
1934 if ((_flags & FLG_FAST_ONLY) && !getParent()) {
1935 // destroy contents if container
1936 Container *c = dynamic_cast<Container *>(this);
1937 if (c) c->destroyContents();
1938
1939 destroy();
1940 // NB: destroy() creates an DestroyItemProcess to actually
1941 // delete the item in this case.
1942 }
1943 // If we have a gravity process, move us to the ground
1944 else if (_gravityPid) {
1945 Process *p = Kernel::get_instance()->getProcess(_gravityPid);
1946 if (p) {
1947 p->terminateDeferred();
1948 _gravityPid = 0;
1949 collideMove(_x, _y, 0, true, false);
1950 }
1951 }
1952 }
1953
openGump(uint32 gumpshape)1954 uint16 Item::openGump(uint32 gumpshape) {
1955 if (_flags & FLG_GUMP_OPEN) return 0;
1956 assert(_gump == 0);
1957 const Shape *shapeP = GameData::get_instance()->getGumps()->getShape(gumpshape);
1958
1959 ContainerGump *cgump;
1960
1961 if (getObjId() != 1) { //!! constant
1962 cgump = new ContainerGump(shapeP, 0, _objId, Gump::FLAG_ITEM_DEPENDENT |
1963 Gump::FLAG_DRAGGABLE);
1964 } else {
1965 cgump = new PaperdollGump(shapeP, 0, _objId, Gump::FLAG_ITEM_DEPENDENT |
1966 Gump::FLAG_DRAGGABLE);
1967 }
1968 //!!TODO: clean up the way this is set
1969 //!! having the itemarea associated with the shapeP through the
1970 //!! GumpShapeFlex maybe
1971 cgump->setItemArea(GameData::get_instance()->
1972 getGumps()->getGumpItemArea(gumpshape));
1973 cgump->InitGump(0);
1974 _flags |= FLG_GUMP_OPEN;
1975 _gump = cgump->getObjId();
1976
1977 return _gump;
1978 }
1979
closeGump()1980 void Item::closeGump() {
1981 if (!(_flags & FLG_GUMP_OPEN)) return;
1982
1983 Gump *g = Ultima8Engine::get_instance()->getGump(_gump);
1984 if (g)
1985 g->Close();
1986
1987 // can we already clear gump here, or do we need to wait for the gump
1988 // to really close??
1989 clearGump();
1990 }
1991
1992
clearGump()1993 void Item::clearGump() {
1994 _gump = 0;
1995 _flags &= ~FLG_GUMP_OPEN;
1996 }
1997
ascend(int delta)1998 int32 Item::ascend(int delta) {
1999 // pout << "Ascend: _objId=" << getObjId() << ", delta=" << delta << Std::endl;
2000
2001 if (delta == 0) return 0x4000;
2002
2003 // * gather all items on top of this item (recursively?)
2004 // * etherealize all those items to get them out of the way
2005 // * move self up/down
2006 // * attempt to rematerialize the items up/down
2007 // (going through etherealness to avoid having to sort the supported items)
2008
2009 UCList uclist(2);
2010 LOOPSCRIPT(script, LS_TOKEN_TRUE); // we want all items
2011 World *world = World::get_instance();
2012 world->getCurrentMap()->surfaceSearch(&uclist, script, sizeof(script),
2013 this, true, false, false);
2014 for (uint32 i = 0; i < uclist.getSize(); i++) {
2015 Item *item = getItem(uclist.getuint16(i));
2016 if (!item) continue;
2017 if (item->getShapeInfo()->is_fixed()) continue;
2018
2019 item->moveToEtherealVoid();
2020 }
2021
2022 // move self
2023 int32 xv, yv, zv;
2024 getLocation(xv, yv, zv);
2025 int dist = collideMove(xv, yv, zv + delta, false, false);
2026 delta = (delta * dist) / 0x4000;
2027
2028 // pout << "Ascend: dist=" << dist << Std::endl;
2029
2030 // move other items
2031 for (uint32 i = 0; i < uclist.getSize(); i++) {
2032 Item *item = getItem(uclist.getuint16(i));
2033 if (!item) continue;
2034 if (item->getShapeInfo()->is_fixed()) continue;
2035
2036 item->getLocation(_ix, _iy, _iz);
2037
2038 if (item->canExistAt(_ix, _iy, _iz + delta)) {
2039 item->move(_ix, _iy, _iz + delta); // automatically un-etherealizes item
2040 } else {
2041 // uh oh...
2042 // CHECKME: what do we do here?
2043 item->move(_ix, _iy, _iz);
2044 if (delta < 0) item->fall();
2045 }
2046 }
2047
2048 return dist;
2049 }
2050
ensureGravityProcess()2051 GravityProcess *Item::ensureGravityProcess() {
2052 GravityProcess *p;
2053 if (_gravityPid) {
2054 p = dynamic_cast<GravityProcess *>(
2055 Kernel::get_instance()->getProcess(_gravityPid));
2056 } else {
2057 p = new GravityProcess(this, 0);
2058 Kernel::get_instance()->addProcess(p);
2059 p->init();
2060 }
2061 assert(p);
2062 return p;
2063 }
2064
fall()2065 void Item::fall() {
2066 const ShapeInfo *info = getShapeInfo();
2067 bool hanging = GAME_IS_U8 && (_flags & FLG_HANGING);
2068
2069 if (hanging || info->is_fixed() || info->_weight == 0) {
2070 // can't fall
2071 return;
2072 }
2073
2074 int gravity = GAME_IS_CRUSADER ? 2 : 4; //!! constants
2075
2076 hurl(0, 0, 0, gravity);
2077 }
2078
grab()2079 void Item::grab() {
2080 // CHECKME: is the fall/release timing correct?
2081
2082 UCList uclist(2);
2083 LOOPSCRIPT(script, LS_TOKEN_TRUE); // we want all items
2084 World *world = World::get_instance();
2085 world->getCurrentMap()->surfaceSearch(&uclist, script, sizeof(script),
2086 this, true, false, true);
2087
2088 for (uint32 i = 0; i < uclist.getSize(); i++) {
2089 Item *item = getItem(uclist.getuint16(i));
2090 if (!item) continue;
2091 item->fall();
2092 }
2093
2094 uclist.free();
2095
2096 world->getCurrentMap()->surfaceSearch(&uclist, script, sizeof(script),
2097 this, false, true, false);
2098
2099 for (uint32 i = 0; i < uclist.getSize(); i++) {
2100 Item *item = getItem(uclist.getuint16(i));
2101 if (!item) continue;
2102 item->callUsecodeEvent_release();
2103 }
2104
2105 }
2106
2107
hurl(int xs,int ys,int zs,int grav)2108 void Item::hurl(int xs, int ys, int zs, int grav) {
2109 if (_parent) {
2110 // Should be removed from the container first??
2111 // This will break otherwise as location is 0,0,0
2112 warning("Ignoring hurl for contained item %d.", _objId);
2113 return;
2114 }
2115 // crusader sleeps existing gravity at first
2116 bool do_sleep = GAME_IS_CRUSADER && (_gravityPid == 0);
2117 GravityProcess *p = ensureGravityProcess();
2118 p->setGravity(grav);
2119 p->move(xs, ys, zs);
2120 if (do_sleep) {
2121 Process *delayProc = new DelayProcess(0x14);
2122 ProcId pid = Kernel::get_instance()->addProcess(delayProc);
2123 p->waitFor(pid);
2124 }
2125 }
2126
2127
explode(int explosion_type,bool destroy_item,bool cause_damage)2128 void Item::explode(int explosion_type, bool destroy_item, bool cause_damage) {
2129 Process *p;
2130 int damage_divisor = 1;
2131
2132 if (GAME_IS_CRUSADER) {
2133
2134 damage_divisor = explosion_type + 1;
2135 if (damage_divisor == 1)
2136 damage_divisor = 3;
2137 else if (damage_divisor == 3)
2138 damage_divisor = 1;
2139
2140 setFlag(FLG_BROKEN);
2141 // TODO: original game puts them at cx/cy/cz, but that looks wrong..
2142 int32 cx, cy, cz;
2143 getCentre(cx, cy, cz);
2144 static const int expshapes[] = {0x31C, 0x31F, 0x326, 0x320, 0x321, 0x324, 0x323, 0x325};
2145 int rnd = getRandom();
2146 int spriteno;
2147 // NOTE: The game does some weird 32-bit stuff to decide what
2148 // shapenum to use. Just simplified to a random.
2149 switch (explosion_type) {
2150 case 0:
2151 spriteno = expshapes[rnd % 2];
2152 break;
2153 case 1:
2154 spriteno = expshapes[2 + rnd % 3];
2155 break;
2156 case 2:
2157 default:
2158 spriteno = expshapes[5 + rnd % 3];
2159 break;
2160 }
2161 p = new SpriteProcess(spriteno, 0, 39, 1, 1, //!! constants
2162 _x, _y, cz);
2163 } else {
2164 p = new SpriteProcess(578, 20, 34, 1, 1, //!! constants
2165 _x, _y, _z);
2166 }
2167 Kernel::get_instance()->addProcess(p);
2168
2169 AudioProcess *audioproc = AudioProcess::get_instance();
2170 if (audioproc) {
2171 int sfx;
2172 if (GAME_IS_CRUSADER) {
2173 sfx = (getRandom() % 2) ? 28 : 108;
2174 audioproc->stopSFX(-1, _objId);
2175 } else {
2176 sfx = (getRandom() % 2) ? 31 : 158;
2177 }
2178 audioproc->playSFX(sfx, 0x60, 0, 0);
2179 }
2180
2181 int32 xv, yv, zv;
2182 getLocation(xv, yv, zv);
2183
2184 if (destroy_item) {
2185 destroy(); // delete self
2186 // WARNING: we are deleted at this point
2187 }
2188
2189 if (!cause_damage)
2190 return;
2191
2192 if (GAME_IS_U8) {
2193 UCList itemlist(2);
2194 LOOPSCRIPT(script, LS_TOKEN_TRUE); // we want all items
2195 CurrentMap *currentmap = World::get_instance()->getCurrentMap();
2196 currentmap->areaSearch(&itemlist, script, sizeof(script), 0,
2197 160, false, xv, yv); //! CHECKME: 128?
2198
2199 for (unsigned int i = 0; i < itemlist.getSize(); ++i) {
2200 Item *item = getItem(itemlist.getuint16(i));
2201 if (!item) continue;
2202 if (getRange(*item, true) > 160) continue; // check vertical distance
2203
2204 item->getLocation(xv, yv, zv);
2205 Direction dir = Direction_GetWorldDir(xv - xv, yv - yv, dirmode_8dirs); //!! CHECKME
2206 item->receiveHit(0, dir, 6 + (getRandom() % 6),
2207 WeaponInfo::DMG_BLUNT | WeaponInfo::DMG_FIRE);
2208 }
2209 } else {
2210 Point3 pt;
2211 getLocation(pt);
2212 // Note: same FireType number used in both Remorse and Regret
2213 const FireType *firetypedat = GameData::get_instance()->getFireType(4);
2214 if (firetypedat) {
2215 int damage = firetypedat->getRandomDamage() / damage_divisor;
2216 firetypedat->applySplashDamageAround(pt, damage, damage_divisor, this, this);
2217 } else {
2218 warning("couldn't explode properly - no firetype 4 data");
2219 }
2220 }
2221 }
2222
getDamageType() const2223 uint16 Item::getDamageType() const {
2224 const ShapeInfo *si = getShapeInfo();
2225 if (si->_weaponInfo) {
2226 return si->_weaponInfo->_damageType;
2227 }
2228
2229 return 0;
2230 }
2231
receiveHit(uint16 other,Direction dir,int damage,uint16 type)2232 void Item::receiveHit(uint16 other, Direction dir, int damage, uint16 type) {
2233 if (GAME_IS_U8)
2234 receiveHitU8(other, dir, damage, type);
2235 else
2236 receiveHitCru(other, dir, damage, type);
2237 }
2238
receiveHitU8(uint16 other,Direction dir,int damage,uint16 type)2239 void Item::receiveHitU8(uint16 other, Direction dir, int damage, uint16 type) {
2240 // first, check if the item has a 'gotHit' usecode event
2241 if (callUsecodeEvent_gotHit(other, 0)) //!! TODO: what should the 0 be??
2242 return;
2243
2244 // explosive?
2245 if (getShapeInfo()->is_u8_explode()) {
2246 explode(0, true); // warning: deletes this
2247 return;
2248 }
2249
2250 // breakable?
2251 if (getFamily() == ShapeInfo::SF_BREAKABLE) {
2252 // CHECKME: anything else?
2253 destroy();
2254 return;
2255 }
2256
2257 if (getShapeInfo()->is_fixed() || getShapeInfo()->_weight == 0) {
2258 // can't move
2259 return;
2260 }
2261
2262 // nothing special, so just hurl the item
2263 // TODO: hurl item in direction, with speed depending on damage
2264 hurl(-16 * Direction_XFactor(dir), -16 * Direction_YFactor(dir), 16, 4); //!! constants
2265 }
2266
2267
receiveHitCru(uint16 other,Direction dir,int damage,uint16 type)2268 void Item::receiveHitCru(uint16 other, Direction dir, int damage, uint16 type) {
2269 damage = scaleReceivedDamageCru(damage, type);
2270 const ShapeInfo *shapeInfo = getShapeInfo();
2271 if (!shapeInfo)
2272 return;
2273 const DamageInfo *damageInfo = shapeInfo->_damageInfo;
2274
2275 // TODO: work out how this flag is decided.
2276 uint8 shouldCallUsecode = 1;
2277
2278 if (shouldCallUsecode)
2279 callUsecodeEvent_gotHit(0x4000, (type << 8) | (damage & 0xff));
2280
2281 if (damageInfo) {
2282 bool wasbroken = damageInfo->applyToItem(this, damage);
2283 if (wasbroken) {
2284 Kernel::get_instance()->killProcesses(_objId, Kernel::PROC_TYPE_ALL, true);
2285 }
2286 }
2287
2288 // Fixed items or 0 weight etems can't move.
2289 // Only damage types 3 and 4 move items in Crusader.
2290 if (shapeInfo->is_fixed() || shapeInfo->_weight == 0 || (type != 3 && type != 4)) {
2291 return;
2292 }
2293
2294 assert((int)dir >= 0 && (int)dir < 16);
2295
2296 static const int hurl_x_factor[] = { 0, +1, +2, +2, +2, +2, +2, +1, 0, -1, -2, -2, -2, -2, -2, -1 };
2297 static const int hurl_y_factor[] = { -2, -2, -2, -1, 0, +1, +2, +2, +2, +2, +2, +1, 0, -1, -2, -2 };
2298
2299 int xhurl = 10 + getRandom() % 15;
2300 int yhurl = 10 + getRandom() % 15;
2301 hurl(-xhurl * hurl_x_factor[(int)dir], -yhurl * hurl_y_factor[(int)dir], 0, 2); //!! constants
2302 }
2303
2304
canDrag()2305 bool Item::canDrag() {
2306 const ShapeInfo *si = getShapeInfo();
2307 if (si->is_fixed()) return false;
2308 if (si->_weight == 0) return false;
2309
2310 Actor *actor = dynamic_cast<Actor *>(this);
2311 if (actor) {
2312 // living actors can't be moved
2313 if (!actor->isDead()) return false;
2314 }
2315
2316 // CHECKME: might need more checks here (weight?)
2317
2318 return true;
2319 }
2320
getThrowRange()2321 int Item::getThrowRange() {
2322 if (!canDrag()) return 0;
2323
2324 Actor *avatar = getMainActor();
2325
2326 int range = 64 - getTotalWeight() + avatar->getStr();
2327 if (range < 1) range = 1;
2328 range = (range * range) / 2;
2329
2330 return range;
2331 }
2332
checkLineOfSightCollisions(const Std::list<CurrentMap::SweepItem> & collisions,bool usingAlternatePos,ObjId item,ObjId other)2333 static bool checkLineOfSightCollisions(
2334 const Std::list<CurrentMap::SweepItem> &collisions,
2335 bool usingAlternatePos, ObjId item, ObjId other) {
2336 Std::list<CurrentMap::SweepItem>::const_iterator it;
2337 int32 other_hit_time = 0x4000;
2338 int32 blocked_time = 0x4000;
2339 for (it = collisions.begin(); it != collisions.end(); it++) {
2340 // ignore self and other
2341 if (it->_item == item) continue;
2342 if (it->_item == other && !usingAlternatePos) {
2343 other_hit_time = it->_hitTime;
2344 continue;
2345 }
2346
2347 // only touching?
2348 if (it->_touching) continue;
2349
2350 // hit something
2351 if (it->_blocking && it->_hitTime < blocked_time) {
2352 blocked_time = it->_hitTime;
2353 }
2354 }
2355
2356 // 'other' must be the first item that is hit.
2357 return (blocked_time >= other_hit_time);
2358 }
2359
canReach(Item * other,int range,int32 otherX,int32 otherY,int32 otherZ)2360 bool Item::canReach(Item *other, int range,
2361 int32 otherX, int32 otherY, int32 otherZ) {
2362 // get location and dimensions of self and other (or their root containers)
2363 int32 thisX, thisY, thisZ;
2364 int32 thisXd, thisYd, thisZd;
2365 int32 otherXd, otherYd, otherZd;
2366 int32 thisXmin, thisYmin;
2367 int32 otherXmin, otherYmin;
2368
2369 bool usingAlternatePos = (otherX != 0);
2370
2371 getLocationAbsolute(thisX, thisY, thisZ);
2372 other = other->getTopItem();
2373 if (otherX == 0)
2374 other->getLocationAbsolute(otherX, otherY, otherZ);
2375
2376 getFootpadWorld(thisXd, thisYd, thisZd);
2377 other->getFootpadWorld(otherXd, otherYd, otherZd);
2378
2379 thisXmin = thisX - thisXd;
2380 thisYmin = thisY - thisYd;
2381
2382 otherXmin = otherX - otherXd;
2383 otherYmin = otherY - otherYd;
2384
2385 // if items are further away than range in any direction, return false
2386 if (thisXmin - otherX > range) return false;
2387 if (otherXmin - thisX > range) return false;
2388 if (thisYmin - otherY > range) return false;
2389 if (otherYmin - thisY > range) return false;
2390
2391
2392 // if not, do line of sight between origins of items
2393 int32 start[3];
2394 int32 end[3];
2395 int32 dims[3] = { 2, 2, 2 };
2396
2397 start[0] = thisX;
2398 start[1] = thisY;
2399 start[2] = thisZ;
2400 end[0] = otherX;
2401 end[1] = otherY;
2402 end[2] = otherZ;
2403 if (otherZ > thisZ && otherZ < thisZ + thisZd)
2404 start[2] = end[2]; // bottom of other between bottom and top of this
2405
2406 Std::list<CurrentMap::SweepItem> collisions;
2407 Std::list<CurrentMap::SweepItem>::iterator it;
2408 World *world = World::get_instance();
2409 CurrentMap *map = world->getCurrentMap();
2410 map->sweepTest(start, end, dims, ShapeInfo::SI_SOLID,
2411 _objId, false, &collisions);
2412 if (checkLineOfSightCollisions(collisions, usingAlternatePos,
2413 getObjId(), other->getObjId()))
2414 return true;
2415
2416 // if that fails, try line of sight between centers
2417 start[0] = thisX - thisXd / 2; // xy center of this
2418 start[1] = thisY - thisYd / 2;
2419 start[2] = thisZ;
2420 if (thisZd > 16)
2421 start[2] += thisZd - 8; // eye height
2422
2423 end[0] = otherX - otherXd / 2; // xyz center of other
2424 end[1] = otherY - otherYd / 2;
2425 end[2] = otherZ + otherZd / 2;
2426
2427 collisions.clear();
2428 map->sweepTest(start, end, dims, ShapeInfo::SI_SOLID,
2429 _objId, false, &collisions);
2430 if (checkLineOfSightCollisions(collisions, usingAlternatePos,
2431 getObjId(), other->getObjId()))
2432 return true;
2433
2434 // if that fails, try line of sight between eye level and top of 2nd
2435 end[2] = otherZ + otherZd;
2436
2437 collisions.clear();
2438 map->sweepTest(start, end, dims, ShapeInfo::SI_SOLID,
2439 _objId, false, &collisions);
2440 return checkLineOfSightCollisions(collisions, usingAlternatePos,
2441 getObjId(), other->getObjId());
2442 }
2443
2444
2445 /**
2446 * A helper function to check if both frames are in the
2447 * same range (inclusive) for the merge check below */
bothInRange(uint32 x,uint32 y,uint32 lo,uint32 hi)2448 static inline bool bothInRange(uint32 x, uint32 y, uint32 lo, uint32 hi) {
2449 return (x >= lo && x <= hi && y >= lo && y <= hi);
2450 }
2451
canMergeWith(Item * other)2452 bool Item::canMergeWith(Item *other) {
2453 // can't merge with self
2454 if (other->getObjId() == getObjId()) return false;
2455
2456 if (other->getShape() != getShape()) return false;
2457
2458 int family = getFamily();
2459 if (family == ShapeInfo::SF_QUANTITY) return true;
2460
2461 if (family != ShapeInfo::SF_REAGENT) return false;
2462
2463 uint32 frame1 = getFrame();
2464 uint32 frame2 = other->getFrame();
2465 if (frame1 == frame2) return true;
2466
2467 // special cases:
2468 // necromancy reagents (shape 395)
2469 // blood: frame 0-5
2470 // bone: frame 6-7
2471 // wood: frame 8
2472 // dirt: frame 9
2473 // ex.hood: frame 10-12
2474 // blackmoor: frame 14-15
2475 // dead man's elbow: frame 16-20
2476 // sorcery reagents (shape 398)
2477 // volcanic ash: frame 0-1
2478 // pumice: frame 2-5
2479 // obsidian: 6-9
2480 // iron: 10-13
2481 // brimstone: 14-17
2482 // daemon bones: 18-20
2483 // ether reagents (shape 399)
2484 // only one frame per type, no special case needed
2485 // Note: necromancy reagents are special-cased to be plural in their look()
2486 // function, but the sorcery ones aren't, but the original game is the same.
2487 //
2488 if (GAME_IS_U8) {
2489 if (getShape() == 395) {
2490 if (bothInRange(frame1, frame2, 0, 5))
2491 return true;
2492 if (bothInRange(frame1, frame2, 6, 7))
2493 return true;
2494 if (bothInRange(frame1, frame2, 10, 12))
2495 return true;
2496 if (bothInRange(frame1, frame2, 14, 15))
2497 return true;
2498 if (bothInRange(frame1, frame2, 16, 20))
2499 return true;
2500 }
2501 if (getShape() == 398) {
2502 if (bothInRange(frame1, frame2, 0, 1))
2503 return true;
2504 if (bothInRange(frame1, frame2, 2, 5))
2505 return true;
2506 if (bothInRange(frame1, frame2, 6, 9))
2507 return true;
2508 if (bothInRange(frame1, frame2, 10, 13))
2509 return true;
2510 if (bothInRange(frame1, frame2, 14, 17))
2511 return true;
2512 if (bothInRange(frame1, frame2, 18, 20))
2513 return true;
2514 }
2515 }
2516 return false;
2517 }
2518
isRobotCru() const2519 bool Item::isRobotCru() const {
2520 uint32 shape = getShape();
2521 return (shape == 0x4c8 || shape == 0x338 || shape == 0x45d ||
2522 shape == 0x2cb || shape == 0x4e6 || shape == 899 ||
2523 shape == 0x385);
2524 }
2525
scaleReceivedDamageCru(int damage,uint16 type) const2526 int Item::scaleReceivedDamageCru(int damage, uint16 type) const {
2527 uint8 difficulty = World::get_instance()->getGameDifficulty();
2528 const Actor *actor = dynamic_cast<const Actor *>(this);
2529 //
2530 // For difficulty 1 and 2, we scale damage to others *up* and damage
2531 // to avatar *down*.
2532 //
2533 if (!actor || (this != getMainActor() && this != getControlledActor())) {
2534 if (difficulty == 1) {
2535 damage *= 5;
2536 } else if (difficulty == 2) {
2537 damage *= 3;
2538 }
2539 } else {
2540 if (difficulty == 1) {
2541 damage /= 5;
2542 } else if (difficulty == 2) {
2543 damage /= 3;
2544 }
2545 }
2546
2547 if (isRobotCru() && (type == 1 || type == 2 || type == 0xb || type == 0xd)) {
2548 damage /= 3;
2549 }
2550
2551 damage = CLIP(damage, 1, 0xfa);
2552 return damage;
2553 }
2554
2555
saveData(Common::WriteStream * ws)2556 void Item::saveData(Common::WriteStream *ws) {
2557 Object::saveData(ws);
2558 ws->writeUint16LE(static_cast<uint16>(_extendedFlags));
2559 ws->writeUint16LE(_flags);
2560 ws->writeUint16LE(static_cast<uint16>(_shape));
2561 ws->writeUint16LE(static_cast<uint16>(_frame));
2562 ws->writeUint16LE(static_cast<uint16>(_x));
2563 ws->writeUint16LE(static_cast<uint16>(_y));
2564 ws->writeUint16LE(static_cast<uint16>(_z));
2565 ws->writeUint16LE(_quality);
2566 ws->writeUint16LE(_npcNum);
2567 ws->writeUint16LE(_mapNum);
2568 if (getObjId() != 0xFFFF) {
2569 // these only make sense in currently loaded items
2570 ws->writeUint16LE(_gump);
2571 ws->writeUint16LE(_gravityPid);
2572 }
2573 if ((_flags & FLG_ETHEREAL) && (_flags & (FLG_CONTAINED | FLG_EQUIPPED)))
2574 ws->writeUint16LE(_parent);
2575 }
2576
loadData(Common::ReadStream * rs,uint32 version)2577 bool Item::loadData(Common::ReadStream *rs, uint32 version) {
2578 if (!Object::loadData(rs, version)) return false;
2579
2580 _extendedFlags = rs->readUint16LE();
2581 _flags = rs->readUint16LE();
2582 _shape = rs->readUint16LE();
2583 _frame = rs->readUint16LE();
2584 _x = rs->readUint16LE();
2585 _y = rs->readUint16LE();
2586 _z = rs->readUint16LE();
2587
2588 _quality = rs->readUint16LE();
2589 _npcNum = rs->readUint16LE();
2590 _mapNum = rs->readUint16LE();
2591 if (getObjId() != 0xFFFF) {
2592 _gump = rs->readUint16LE();
2593 _gravityPid = rs->readUint16LE();
2594 } else {
2595 _gump = _gravityPid = 0;
2596 }
2597
2598 if ((_flags & FLG_ETHEREAL) && (_flags & (FLG_CONTAINED | FLG_EQUIPPED)))
2599 _parent = rs->readUint16LE();
2600 else
2601 _parent = 0;
2602
2603 //!! hackish...
2604 if (_extendedFlags & EXT_INCURMAP) {
2605 World::get_instance()->getCurrentMap()->addItem(this);
2606 }
2607
2608 return true;
2609 }
2610
2611
I_touch(const uint8 * args,unsigned int)2612 uint32 Item::I_touch(const uint8 *args, unsigned int /*argsize*/) {
2613 ARG_NULL32(); // ARG_ITEM_FROM_PTR(item);
2614
2615 // Guess: this is used to make sure an item is painted in the original.
2616 // Our renderer is different, making this intrinsic unnecessary.
2617
2618 return 0;
2619 }
2620
I_getX(const uint8 * args,unsigned int)2621 uint32 Item::I_getX(const uint8 *args, unsigned int /*argsize*/) {
2622 ARG_ITEM_FROM_PTR(item);
2623 if (!item) return 0;
2624
2625 int32 x, y, z;
2626 item->getLocationAbsolute(x, y, z);
2627 if (GAME_IS_CRUSADER)
2628 return x / 2;
2629 else
2630 return x;
2631 }
2632
I_getY(const uint8 * args,unsigned int)2633 uint32 Item::I_getY(const uint8 *args, unsigned int /*argsize*/) {
2634 ARG_ITEM_FROM_PTR(item);
2635 if (!item) return 0;
2636
2637 int32 x, y, z;
2638 item->getLocationAbsolute(x, y, z);
2639 if (GAME_IS_CRUSADER)
2640 return y / 2;
2641 else
2642 return y;
2643 }
2644
I_getZ(const uint8 * args,unsigned int)2645 uint32 Item::I_getZ(const uint8 *args, unsigned int /*argsize*/) {
2646 ARG_ITEM_FROM_PTR(item);
2647 if (!item) return 0;
2648
2649 int32 x, y, z;
2650 item->getLocationAbsolute(x, y, z);
2651 return z;
2652 }
2653
I_getCX(const uint8 * args,unsigned int)2654 uint32 Item::I_getCX(const uint8 *args, unsigned int /*argsize*/) {
2655 ARG_ITEM_FROM_PTR(item);
2656 if (!item) return 0;
2657
2658 int32 x, y, z;
2659 item->getLocationAbsolute(x, y, z);
2660
2661 int mul = 16;
2662 if (GAME_IS_CRUSADER) {
2663 x /= 2;
2664 mul /= 2;
2665 }
2666
2667 if (item->_flags & FLG_FLIPPED)
2668 return x - item->getShapeInfo()->_y * mul;
2669 else
2670 return x - item->getShapeInfo()->_x * mul;
2671 }
2672
I_getCY(const uint8 * args,unsigned int)2673 uint32 Item::I_getCY(const uint8 *args, unsigned int /*argsize*/) {
2674 ARG_ITEM_FROM_PTR(item);
2675 if (!item) return 0;
2676
2677 int32 x, y, z;
2678 item->getLocationAbsolute(x, y, z);
2679
2680 int mul = 16;
2681 if (GAME_IS_CRUSADER) {
2682 y /= 2;
2683 mul /= 2;
2684 }
2685
2686 if (item->_flags & FLG_FLIPPED)
2687 return y - item->getShapeInfo()->_x * mul;
2688 else
2689 return y - item->getShapeInfo()->_y * mul;
2690 }
2691
I_getCZ(const uint8 * args,unsigned int)2692 uint32 Item::I_getCZ(const uint8 *args, unsigned int /*argsize*/) {
2693 ARG_ITEM_FROM_PTR(item);
2694 if (!item) return 0;
2695
2696 int32 x, y, z;
2697 item->getLocationAbsolute(x, y, z);
2698
2699 return z + item->getShapeInfo()->_z * 4;
2700 }
2701
I_getPoint(const uint8 * args,unsigned int)2702 uint32 Item::I_getPoint(const uint8 *args, unsigned int /*argsize*/) {
2703 ARG_ITEM_FROM_PTR(item);
2704 ARG_UC_PTR(ptr);
2705 if (!item) return 0;
2706
2707 int32 x, y, z;
2708 item->getLocationAbsolute(x, y, z);
2709
2710 if (GAME_IS_CRUSADER) {
2711 x /= 2;
2712 y /= 2;
2713 }
2714
2715 WorldPoint point;
2716 point.setX(x);
2717 point.setY(y);
2718 point.setZ(z);
2719
2720 UCMachine::get_instance()->assignPointer(ptr, point._buf, 5);
2721
2722 return 0;
2723 }
2724
I_getShape(const uint8 * args,unsigned int)2725 uint32 Item::I_getShape(const uint8 *args, unsigned int /*argsize*/) {
2726 ARG_ITEM_FROM_PTR(item);
2727 if (!item) return 0;
2728
2729 return item->getShape();
2730 }
2731
I_setShape(const uint8 * args,unsigned int)2732 uint32 Item::I_setShape(const uint8 *args, unsigned int /*argsize*/) {
2733 ARG_ITEM_FROM_PTR(item);
2734 ARG_UINT16(shape);
2735 if (!item) return 0;
2736
2737 #if 0
2738 debug(6, "Item::setShape: objid %04X shape (%d -> %d)",
2739 item->getObjId(), item->getShape(), shape);
2740 #endif
2741
2742 item->setShape(shape);
2743 return 0;
2744 }
2745
I_getFrame(const uint8 * args,unsigned int)2746 uint32 Item::I_getFrame(const uint8 *args, unsigned int /*argsize*/) {
2747 ARG_ITEM_FROM_PTR(item);
2748 if (!item) return 0;
2749
2750 return item->getFrame();
2751 }
2752
I_setFrame(const uint8 * args,unsigned int)2753 uint32 Item::I_setFrame(const uint8 *args, unsigned int /*argsize*/) {
2754 ARG_ITEM_FROM_PTR(item);
2755 ARG_UINT16(frame);
2756 if (!item) return 0;
2757
2758 item->setFrame(frame);
2759 return 0;
2760 }
2761
I_getQuality(const uint8 * args,unsigned int)2762 uint32 Item::I_getQuality(const uint8 *args, unsigned int /*argsize*/) {
2763 ARG_ITEM_FROM_PTR(item);
2764 if (!item) return 0;
2765
2766 if (item->getFamily() == ShapeInfo::SF_QUALITY)
2767 return item->getQuality();
2768 else
2769 return 0;
2770 }
2771
I_getUnkEggType(const uint8 * args,unsigned int)2772 uint32 Item::I_getUnkEggType(const uint8 *args, unsigned int /*argsize*/) {
2773 ARG_ITEM_FROM_PTR(item);
2774 if (!item) return 0;
2775
2776 if (item->getFamily() == ShapeInfo::SF_UNKEGG) {
2777 if (GAME_IS_U8) {
2778 return item->getQuality();
2779 } else {
2780 return item->getQuality() & 0xFF;
2781 }
2782 } else {
2783 return 0;
2784 }
2785 }
2786
I_setUnkEggType(const uint8 * args,unsigned int)2787 uint32 Item::I_setUnkEggType(const uint8 *args, unsigned int /*argsize*/) {
2788 ARG_ITEM_FROM_PTR(item);
2789 ARG_UINT16(val);
2790 if (!item) return 0;
2791
2792 if (item->getFamily() == ShapeInfo::SF_UNKEGG) {
2793 item->setQuality(val);
2794 }
2795 return 0;
2796 }
2797
I_getQuantity(const uint8 * args,unsigned int)2798 uint32 Item::I_getQuantity(const uint8 *args, unsigned int /*argsize*/) {
2799 ARG_ITEM_FROM_PTR(item);
2800 if (!item) return 0;
2801
2802 if (item->getFamily() == ShapeInfo::SF_QUANTITY ||
2803 item->getFamily() == ShapeInfo::SF_REAGENT)
2804 return item->getQuality();
2805 else
2806 return 0;
2807 }
2808
I_getContainer(const uint8 * args,unsigned int)2809 uint32 Item::I_getContainer(const uint8 *args, unsigned int /*argsize*/) {
2810 ARG_ITEM_FROM_PTR(item);
2811 if (!item) return 0;
2812
2813 //! What do we do if item has no _parent?
2814 //! What do we do with equipped items?
2815
2816 if (item->getParent())
2817 return item->getParent();
2818 else
2819 return 0;
2820 }
2821
I_getRootContainer(const uint8 * args,unsigned int)2822 uint32 Item::I_getRootContainer(const uint8 *args, unsigned int /*argsize*/) {
2823 ARG_ITEM_FROM_PTR(item);
2824 if (!item) return 0;
2825
2826 Container *_parent = item->getParentAsContainer();
2827
2828 //! What do we do if item has no _parent?
2829 //! What do we do with equipped items?
2830
2831 if (!_parent) return 0;
2832
2833 while (_parent->getParentAsContainer()) {
2834 _parent = _parent->getParentAsContainer();
2835 }
2836
2837 return _parent->getObjId();
2838 }
2839
I_getQ(const uint8 * args,unsigned int)2840 uint32 Item::I_getQ(const uint8 *args, unsigned int /*argsize*/) {
2841 ARG_ITEM_FROM_PTR(item);
2842 if (!item) return 0;
2843
2844 return item->getQuality();
2845 }
2846
I_getQLo(const uint8 * args,unsigned int)2847 uint32 Item::I_getQLo(const uint8 *args, unsigned int /*argsize*/) {
2848 ARG_ITEM_FROM_PTR(item);
2849 if (!item) return 0;
2850
2851 return item->getQuality() & 0xFF;
2852 }
2853
I_getQHi(const uint8 * args,unsigned int)2854 uint32 Item::I_getQHi(const uint8 *args, unsigned int /*argsize*/) {
2855 ARG_ITEM_FROM_PTR(item);
2856 if (!item) return 0;
2857
2858 return (item->getQuality() >> 8) & 0xFF;
2859 }
2860
I_setQ(const uint8 * args,unsigned int)2861 uint32 Item::I_setQ(const uint8 *args, unsigned int /*argsize*/) {
2862 ARG_ITEM_FROM_PTR(item);
2863 ARG_UINT16(q);
2864 if (!item) return 0;
2865
2866 item->setQuality(q);
2867 return 0;
2868 }
2869
I_setQLo(const uint8 * args,unsigned int)2870 uint32 Item::I_setQLo(const uint8 *args, unsigned int /*argsize*/) {
2871 ARG_ITEM_FROM_PTR(item);
2872 ARG_UINT16(q);
2873 if (!item) return 0;
2874
2875 uint16 iq = item->getQuality() & 0xFF00;
2876
2877 item->setQuality(iq | (q & 0xFF));
2878 return 0;
2879 }
2880
I_setQHi(const uint8 * args,unsigned int)2881 uint32 Item::I_setQHi(const uint8 *args, unsigned int /*argsize*/) {
2882 ARG_ITEM_FROM_PTR(item);
2883 ARG_UINT16(q);
2884 if (!item) return 0;
2885
2886 uint16 iq = item->getQuality() & 0x00FF;
2887
2888 item->setQuality(iq | ((q << 8) & 0xFF00));
2889 return 0;
2890 }
2891
I_setQuality(const uint8 * args,unsigned int)2892 uint32 Item::I_setQuality(const uint8 *args, unsigned int /*argsize*/) {
2893 ARG_ITEM_FROM_PTR(item);
2894 ARG_UINT16(q);
2895 if (!item) return 0;
2896
2897 if (item->getFamily() != ShapeInfo::SF_GENERIC)
2898 item->setQuality(q);
2899
2900 return 0;
2901 }
2902
I_setQuantity(const uint8 * args,unsigned int)2903 uint32 Item::I_setQuantity(const uint8 *args, unsigned int /*argsize*/) {
2904 ARG_ITEM_FROM_PTR(item);
2905 ARG_UINT16(q);
2906 if (!item) return 0;
2907
2908 if (item->getFamily() == ShapeInfo::SF_QUANTITY ||
2909 item->getFamily() == ShapeInfo::SF_REAGENT)
2910 item->setQuality(q);
2911
2912 return 0;
2913 }
2914
I_setQAndCombine(const uint8 * args,unsigned int)2915 uint32 Item::I_setQAndCombine(const uint8 *args, unsigned int /*argsize*/) {
2916 ARG_ITEM_FROM_PTR(item);
2917 ARG_UINT16(q);
2918 if (!item) return 0;
2919
2920 item->setQuality(q);
2921 item->callUsecodeEvent_combine();
2922
2923 return 0;
2924 }
2925
I_getFamily(const uint8 * args,unsigned int)2926 uint32 Item::I_getFamily(const uint8 *args, unsigned int /*argsize*/) {
2927 ARG_ITEM_FROM_PTR(item);
2928 if (!item) return 0;
2929
2930 return item->getFamily();
2931 }
2932
I_getTypeFlag(const uint8 * args,unsigned int)2933 uint32 Item::I_getTypeFlag(const uint8 *args, unsigned int /*argsize*/) {
2934 ARG_ITEM_FROM_PTR(item);
2935 ARG_UINT16(typeflag);
2936 if (!item) return 0;
2937
2938 const ShapeInfo *info = item->getShapeInfo();
2939
2940 if (GAME_IS_U8 && typeflag >= 64)
2941 perr << "Invalid TypeFlag greater than 63 requested (" << typeflag << ") by Usecode" << Std::endl;
2942 if (GAME_IS_CRUSADER && typeflag >= 72)
2943 perr << "Invalid TypeFlag greater than 72 requested (" << typeflag << ") by Usecode" << Std::endl;
2944
2945 if (info->getTypeFlag(typeflag))
2946 return 1;
2947 else
2948 return 0;
2949 }
2950
I_getStatus(const uint8 * args,unsigned int)2951 uint32 Item::I_getStatus(const uint8 *args, unsigned int /*argsize*/) {
2952 ARG_ITEM_FROM_PTR(item);
2953 if (!item) return 0;
2954
2955 return item->getFlags();
2956 }
2957
I_orStatus(const uint8 * args,unsigned int argsize)2958 uint32 Item::I_orStatus(const uint8 *args, unsigned int argsize) {
2959 ARG_ITEM_FROM_PTR(item);
2960 ARG_UINT16(mask);
2961 if (!item) return 0;
2962
2963 item->setFlag(mask);
2964 return 0;
2965 }
2966
I_andStatus(const uint8 * args,unsigned int argsize)2967 uint32 Item::I_andStatus(const uint8 *args, unsigned int argsize) {
2968 ARG_ITEM_FROM_PTR(item);
2969 ARG_UINT16(mask);
2970 if (!item) return 0;
2971
2972 item->_flags &= mask;
2973 return 0;
2974 }
2975
I_ascend(const uint8 * args,unsigned int)2976 uint32 Item::I_ascend(const uint8 *args, unsigned int /*argsize*/) {
2977 ARG_ITEM_FROM_PTR(item);
2978 ARG_SINT16(delta);
2979 if (!item) return 0;
2980
2981 int dist = item->ascend(delta);
2982
2983 if (dist == 0x4000)
2984 return 1;
2985 else
2986 return 0;
2987 }
2988
I_getWeight(const uint8 * args,unsigned int)2989 uint32 Item::I_getWeight(const uint8 *args, unsigned int /*argsize*/) {
2990 ARG_ITEM_FROM_PTR(item);
2991 if (!item) return 0;
2992
2993 return item->getWeight();
2994 }
2995
I_getWeightIncludingContents(const uint8 * args,unsigned int)2996 uint32 Item::I_getWeightIncludingContents(const uint8 *args,
2997 unsigned int /*argsize*/) {
2998 ARG_ITEM_FROM_PTR(item);
2999 if (!item) return 0;
3000
3001 return item->getTotalWeight();
3002 }
3003
I_bark(const uint8 * args,unsigned int)3004 uint32 Item::I_bark(const uint8 *args, unsigned int /*argsize*/) {
3005 ARG_ITEM_FROM_PTR(item);
3006 ARG_STRING(str);
3007 if (id_item == 666)
3008 item = getItem(1);
3009
3010 if (!item) {
3011 // Hack! Items should always be valid?
3012 warning("skipping bark of '%s' because item invalid.", str.c_str());
3013 return 0;
3014 }
3015
3016 uint32 shapenum = item->getShape();
3017 if (id_item == 666)
3018 shapenum = 666; // Hack for guardian barks
3019 Gump *gump = new BarkGump(item->getObjId(), str, shapenum);
3020
3021 if (item->getObjId() < 256) { // CONSTANT!
3022 GumpNotifyProcess *notifyproc;
3023 notifyproc = new ActorBarkNotifyProcess(item->getObjId());
3024 Kernel::get_instance()->addProcess(notifyproc);
3025 gump->SetNotifyProcess(notifyproc);
3026 }
3027
3028 gump->InitGump(0);
3029
3030 return gump->GetNotifyProcess()->getPid();
3031 }
3032
I_look(const uint8 * args,unsigned int)3033 uint32 Item::I_look(const uint8 *args, unsigned int /*argsize*/) {
3034 ARG_ITEM_FROM_PTR(item);
3035 if (!item) return 0;
3036
3037 return item->callUsecodeEvent_look();
3038 }
3039
I_use(const uint8 * args,unsigned int)3040 uint32 Item::I_use(const uint8 *args, unsigned int /*argsize*/) {
3041 ARG_ITEM_FROM_PTR(item);
3042 if (!item) return 0;
3043
3044 return item->callUsecodeEvent_use();
3045 }
3046
I_gotHit(const uint8 * args,unsigned int)3047 uint32 Item::I_gotHit(const uint8 *args, unsigned int /*argsize*/) {
3048 ARG_ITEM_FROM_PTR(item);
3049 ARG_UINT16(hitter);
3050 ARG_SINT16(force);
3051 if (!item) return 0;
3052
3053 return item->callUsecodeEvent_gotHit(hitter, force);
3054 }
3055
I_equip(const uint8 * args,unsigned int argsize)3056 uint32 Item::I_equip(const uint8 *args, unsigned int argsize) {
3057 ARG_ITEM_FROM_PTR(item);
3058 if (!item) return 0;
3059
3060 // Note: The U8 version (no param) is never actually called in the usecode.
3061 assert(argsize > 4);
3062
3063 ARG_UINT16(val);
3064 return item->callUsecodeEvent_equipWithParam(val);
3065 }
3066
I_unequip(const uint8 * args,unsigned int argsize)3067 uint32 Item::I_unequip(const uint8 *args, unsigned int argsize) {
3068 ARG_ITEM_FROM_PTR(item);
3069 if (!item) return 0;
3070
3071 // Note: The U8 version (no param) is never actually called in the usecode.
3072 assert(argsize > 4);
3073
3074 ARG_UINT16(val)
3075 return item->callUsecodeEvent_unequipWithParam(val);
3076 }
3077
I_enterFastArea(const uint8 * args,unsigned int)3078 uint32 Item::I_enterFastArea(const uint8 *args, unsigned int /*argsize*/) {
3079 ARG_ITEM_FROM_PTR(item);
3080 if (!item) return 0;
3081
3082 return item->enterFastArea();
3083 }
3084
I_cast(const uint8 * args,unsigned int)3085 uint32 Item::I_cast(const uint8 *args, unsigned int /*argsize*/) {
3086 ARG_ITEM_FROM_PTR(item);
3087 if (!item) return 0;
3088 ARG_UINT16(arg);
3089
3090 return item->callUsecodeEvent_cast(arg);
3091 }
3092
I_avatarStoleSomething(const uint8 * args,unsigned int)3093 uint32 Item::I_avatarStoleSomething(const uint8 *args, unsigned int /*argsize*/) {
3094 ARG_ITEM_FROM_PTR(item);
3095 if (!item) return 0;
3096
3097 // Abort if npc && dead to match original game behavior
3098 Actor *actor = dynamic_cast<Actor *>(item);
3099 if (actor && actor->isDead())
3100 return 0;
3101
3102 ARG_UINT16(arg);
3103
3104 return item->callUsecodeEvent_AvatarStoleSomething(arg);
3105 }
3106
I_ask(const uint8 * args,unsigned int)3107 uint32 Item::I_ask(const uint8 *args, unsigned int /*argsize*/) {
3108 ARG_NULL32(); // ARG_ITEM_FROM_PTR(item); // currently unused.
3109 ARG_LIST(answers);
3110
3111 if (!answers) return 0;
3112
3113 // Use AskGump
3114 Gump *_gump = new AskGump(1, answers);
3115 _gump->InitGump(0);
3116 return _gump->GetNotifyProcess()->getPid();
3117 }
3118
I_legalCreateAtPoint(const uint8 * args,unsigned int)3119 uint32 Item::I_legalCreateAtPoint(const uint8 *args, unsigned int /*argsize*/) {
3120 ARG_UC_PTR(itemptr); // need to store the item id at *itemptr
3121 ARG_UINT16(shape);
3122 ARG_UINT16(frame);
3123 ARG_WORLDPOINT(point);
3124
3125 int32 x = point.getX();
3126 int32 y = point.getY();
3127 int32 z = point.getZ();
3128
3129 if (GAME_IS_CRUSADER) {
3130 x *= 2;
3131 y *= 2;
3132 }
3133
3134 // check if item can exist
3135 CurrentMap *cm = World::get_instance()->getCurrentMap();
3136 bool valid = cm->isValidPosition(x, y, z, shape, 0, 0, 0);
3137 if (!valid)
3138 return 0;
3139
3140 Item *newitem = ItemFactory::createItem(shape, frame, 0, 0, 0, 0, 0, true);
3141 if (!newitem) {
3142 perr << "I_legalCreateAtPoint failed to create item (" << shape
3143 << "," << frame << ")." << Std::endl;
3144 return 0;
3145 }
3146 uint16 objID = newitem->getObjId();
3147 newitem->move(x, y, z);
3148
3149 uint8 buf[2];
3150 buf[0] = static_cast<uint8>(objID);
3151 buf[1] = static_cast<uint8>(objID >> 8);
3152 UCMachine::get_instance()->assignPointer(itemptr, buf, 2);
3153
3154 return 1;
3155 }
3156
I_legalCreateAtCoords(const uint8 * args,unsigned int)3157 uint32 Item::I_legalCreateAtCoords(const uint8 *args, unsigned int /*argsize*/) {
3158 ARG_UC_PTR(itemptr); // need to store the item id at *itemptr
3159 ARG_UINT16(shape);
3160 ARG_UINT16(frame);
3161 ARG_UINT16(x);
3162 ARG_UINT16(y);
3163 ARG_UINT8(z);
3164
3165 if (GAME_IS_CRUSADER) {
3166 x *= 2;
3167 y *= 2;
3168 }
3169
3170 // check if item can exist
3171 CurrentMap *cm = World::get_instance()->getCurrentMap();
3172 bool valid = cm->isValidPosition(x, y, z, shape, 0, 0, 0);
3173 if (!valid)
3174 return 0;
3175
3176 // if yes, create it
3177 Item *newitem = ItemFactory::createItem(shape, frame, 0, 0, 0, 0, 0, true);
3178 if (!newitem) {
3179 perr << "I_legalCreateAtCoords failed to create item (" << shape
3180 << "," << frame << ")." << Std::endl;
3181 return 0;
3182 }
3183 uint16 objID = newitem->getObjId();
3184 newitem->move(x, y, z);
3185
3186 uint8 buf[2];
3187 buf[0] = static_cast<uint8>(objID);
3188 buf[1] = static_cast<uint8>(objID >> 8);
3189 UCMachine::get_instance()->assignPointer(itemptr, buf, 2);
3190
3191 return 1;
3192 }
3193
I_legalCreateInCont(const uint8 * args,unsigned int)3194 uint32 Item::I_legalCreateInCont(const uint8 *args, unsigned int /*argsize*/) {
3195 ARG_UC_PTR(itemptr); // need to store the item id at *itemptr
3196 ARG_UINT16(shape);
3197 ARG_UINT16(frame);
3198 ARG_CONTAINER_FROM_ID(container);
3199 ARG_UINT16(unknown); // ?
3200
3201 uint8 buf[2];
3202 buf[0] = 0;
3203 buf[1] = 0;
3204 UCMachine::get_instance()->assignPointer(itemptr, buf, 2);
3205
3206 // Create an item and try to add it to the given container.
3207 // If it fits, return id; otherwise return 0.
3208
3209 Item *newitem = ItemFactory::createItem(shape, frame, 0, 0, 0, 0, 0, true);
3210 if (!newitem) {
3211 perr << "I_legalCreateInCont failed to create item (" << shape
3212 << "," << frame << ")." << Std::endl;
3213 return 0;
3214 }
3215
3216 // also need to check weight, volume maybe??
3217 if (newitem->moveToContainer(container)) {
3218 uint16 objID = newitem->getObjId();
3219
3220 buf[0] = static_cast<uint8>(objID);
3221 buf[1] = static_cast<uint8>(objID >> 8);
3222 UCMachine::get_instance()->assignPointer(itemptr, buf, 2);
3223
3224 return 1;
3225 } else {
3226 perr << "I_legalCreateInCont failed to add item to container ("
3227 << container->getObjId() << ")" << Std::endl;
3228 // failed to add; clean up
3229 newitem->destroy();
3230
3231 return 0;
3232 }
3233 }
3234
I_destroy(const uint8 * args,unsigned int)3235 uint32 Item::I_destroy(const uint8 *args, unsigned int /*argsize*/) {
3236 ARG_ITEM_FROM_PTR(item);
3237 if (!item || item->getObjId() == 1) return 0;
3238
3239 item->destroy();
3240
3241 return 0;
3242 }
3243
I_getFootpadData(const uint8 * args,unsigned int)3244 uint32 Item::I_getFootpadData(const uint8 *args, unsigned int /*argsize*/) {
3245 ARG_ITEM_FROM_PTR(item);
3246 ARG_UC_PTR(xptr);
3247 ARG_UC_PTR(yptr);
3248 ARG_UC_PTR(zptr);
3249 if (!item) return 0;
3250
3251 // TODO: Data is packed differently in Crusader - check that this still works.
3252
3253 uint8 buf[2];
3254 int32 x, y, z;
3255 item->getFootpadData(x, y, z);
3256
3257 buf[0] = static_cast<uint8>(x);
3258 buf[1] = static_cast<uint8>(x >> 8);
3259 UCMachine::get_instance()->assignPointer(xptr, buf, 2);
3260
3261 buf[0] = static_cast<uint8>(y);
3262 buf[1] = static_cast<uint8>(y >> 8);
3263 UCMachine::get_instance()->assignPointer(yptr, buf, 2);
3264
3265 buf[0] = static_cast<uint8>(z);
3266 buf[1] = static_cast<uint8>(z >> 8);
3267 UCMachine::get_instance()->assignPointer(zptr, buf, 2);
3268
3269 return 0;
3270 }
3271
I_overlaps(const uint8 * args,unsigned int)3272 uint32 Item::I_overlaps(const uint8 *args, unsigned int /*argsize*/) {
3273 ARG_ITEM_FROM_PTR(item);
3274 ARG_ITEM_FROM_ID(item2);
3275 if (!item) return 0;
3276 if (!item2) return 0;
3277
3278 if (item->overlaps(*item2))
3279 return 1;
3280 else
3281 return 0;
3282 }
3283
I_overlapsXY(const uint8 * args,unsigned int)3284 uint32 Item::I_overlapsXY(const uint8 *args, unsigned int /*argsize*/) {
3285 ARG_ITEM_FROM_PTR(item);
3286 ARG_ITEM_FROM_ID(item2);
3287 if (!item) return 0;
3288 if (!item2) return 0;
3289
3290 if (item->overlapsxy(*item2))
3291 return 1;
3292 else
3293 return 0;
3294 }
3295
I_isOn(const uint8 * args,unsigned int)3296 uint32 Item::I_isOn(const uint8 *args, unsigned int /*argsize*/) {
3297 ARG_ITEM_FROM_PTR(item);
3298 ARG_ITEM_FROM_ID(item2);
3299 if (!item) return 0;
3300 if (!item2) return 0;
3301
3302 if (item->isOn(*item2))
3303 return 1;
3304 else
3305 return 0;
3306 }
3307
I_isCompletelyOn(const uint8 * args,unsigned int)3308 uint32 Item::I_isCompletelyOn(const uint8 *args, unsigned int /*argsize*/) {
3309 ARG_ITEM_FROM_PTR(item);
3310 ARG_ITEM_FROM_ID(item2);
3311 if (!item) return 0;
3312 if (!item2) return 0;
3313
3314 if (item->isCompletelyOn(*item2))
3315 return 1;
3316 else
3317 return 0;
3318 }
3319
I_isCentreOn(const uint8 * args,unsigned int)3320 uint32 Item::I_isCentreOn(const uint8 *args, unsigned int /*argsize*/) {
3321 ARG_ITEM_FROM_PTR(item);
3322 ARG_ITEM_FROM_ID(item2);
3323 if (!item) return 0;
3324 if (!item2) return 0;
3325
3326 if (item->isCentreOn(*item2))
3327 return 1;
3328 else
3329 return 0;
3330 }
3331
I_isInNpc(const uint8 * args,unsigned int)3332 uint32 Item::I_isInNpc(const uint8 *args, unsigned int /*argsize*/) {
3333 ARG_ITEM_FROM_PTR(item);
3334 if (!item)
3335 return 0;
3336
3337 const Container *container = item->getParentAsContainer();
3338 while (container) {
3339 const Actor *actor = dynamic_cast<const Actor *>(container);
3340 if (actor)
3341 return 1;
3342 container = container->getParentAsContainer();
3343 }
3344 return 0;
3345 }
3346
I_getFamilyOfType(const uint8 * args,unsigned int)3347 uint32 Item::I_getFamilyOfType(const uint8 *args, unsigned int /*argsize*/) {
3348 ARG_UINT16(shape);
3349
3350 return GameData::get_instance()->getMainShapes()->
3351 getShapeInfo(shape)->_family;
3352 }
3353
I_push(const uint8 * args,unsigned int)3354 uint32 Item::I_push(const uint8 *args, unsigned int /*argsize*/) {
3355 ARG_ITEM_FROM_PTR(item);
3356 if (!item)
3357 return 0;
3358
3359 #if 0
3360 perr << "Pushing item to ethereal void: id: " << item->getObjId() << " shp: " << item->getShape() << "," << item->getFrame() << Std::endl;
3361 #endif
3362
3363 item->moveToEtherealVoid();
3364
3365 return 0;
3366 }
3367
I_create(const uint8 * args,unsigned int)3368 uint32 Item::I_create(const uint8 *args, unsigned int /*argsize*/) {
3369 ARG_UC_PTR(itemptr); // need to store the item id at *itemptr (????)
3370 ARG_UINT16(shape);
3371 ARG_UINT16(frame);
3372
3373 Item *newitem = ItemFactory::createItem(shape, frame, 0, 0, 0, 0, 0, true);
3374 if (!newitem) {
3375 perr << "I_create failed to create item (" << shape
3376 << "," << frame << ")." << Std::endl;
3377 return 0;
3378 }
3379 uint16 objID = newitem->getObjId();
3380
3381 #if 0
3382 debug(6, "Item::create: objid %04X shape (%d, %d)",
3383 objID, shape, frame);
3384 #endif
3385
3386 newitem->moveToEtherealVoid();
3387
3388 uint8 buf[2];
3389 buf[0] = static_cast<uint8>(objID);
3390 buf[1] = static_cast<uint8>(objID >> 8);
3391 UCMachine::get_instance()->assignPointer(itemptr, buf, 2);
3392
3393 return 1;
3394 }
3395
I_pop(const uint8 * args,unsigned int)3396 uint32 Item::I_pop(const uint8 *args, unsigned int /*argsize*/) {
3397 ARG_NULL32(); // ARG_ITEM_FROM_PTR(item); // unused
3398
3399 World *w = World::get_instance();
3400
3401 if (w->etherealEmpty())
3402 return 0; // no items left on stack
3403
3404 uint16 _objId = w->etherealPeek();
3405 Item *item = getItem(_objId);
3406 if (!item) {
3407 w->etherealRemove(_objId);
3408 return 0; // top item was invalid
3409 }
3410
3411 item->returnFromEtherealVoid();
3412
3413 #if 0
3414 perr << "Popping item to original location: " << item->getShape() << "," << item->getFrame() << Std::endl;
3415 #endif
3416
3417 //! Anything else?
3418
3419 return _objId;
3420 }
3421
I_popToCoords(const uint8 * args,unsigned int)3422 uint32 Item::I_popToCoords(const uint8 *args, unsigned int /*argsize*/) {
3423 ARG_NULL32(); // ARG_ITEM_FROM_PTR(item); // unused
3424 ARG_UINT16(x);
3425 ARG_UINT16(y);
3426 ARG_UINT8(z);
3427
3428 World *w = World::get_instance();
3429
3430 if (w->etherealEmpty())
3431 return 0; // no items left on stack
3432
3433 uint16 objId = w->etherealPeek();
3434 Item *item = getItem(objId);
3435 if (!item) {
3436 w->etherealRemove(objId);
3437 return 0; // top item was invalid
3438 }
3439
3440 if (GAME_IS_CRUSADER) {
3441 x *= 2;
3442 y *= 2;
3443 // HACK!! DEATHFL::ordinal20 has a hack to add 1 to z for the death
3444 // animation (falling into acid), but then our animation tracker
3445 // detects a fall and stops animating. Fight hacks with hacks..
3446 if (item->_shape == 1408 && z > 0)
3447 z -= 1;
3448 }
3449
3450 item->move(x, y, z);
3451
3452 #if 0
3453 perr << "Popping item into map: " << item->getShape() << "," << item->getFrame() << " at (" << x << "," << y << "," << z << ")" << Std::endl;
3454 #endif
3455
3456 //! Anything else?
3457
3458 return objId;
3459 }
3460
I_popToContainer(const uint8 * args,unsigned int)3461 uint32 Item::I_popToContainer(const uint8 *args, unsigned int /*argsize*/) {
3462 ARG_NULL32(); // ARG_ITEM_FROM_PTR(item); // unused
3463 ARG_CONTAINER_FROM_ID(container);
3464
3465 if (!container) {
3466 perr << "Trying to pop item to invalid container (" << id_container << ")." << Std::endl;
3467 return 0;
3468 }
3469
3470 World *w = World::get_instance();
3471
3472 if (w->etherealEmpty())
3473 return 0; // no items left on stack
3474
3475 uint16 _objId = w->etherealPeek();
3476 Item *item = getItem(_objId);
3477 if (!item) {
3478 w->etherealRemove(_objId);
3479 return 0; // top item was invalid
3480 }
3481
3482 item->moveToContainer(container);
3483
3484 //! Anything else?
3485
3486 return _objId;
3487 }
3488
I_popToEnd(const uint8 * args,unsigned int)3489 uint32 Item::I_popToEnd(const uint8 *args, unsigned int /*argsize*/) {
3490 ARG_NULL32(); // ARG_ITEM_FROM_PTR(item); // unused
3491 ARG_CONTAINER_FROM_ID(container);
3492
3493 if (!container) {
3494 perr << "Trying to pop item to invalid container (" << id_container << ")." << Std::endl;
3495 return 0;
3496 }
3497
3498 World *w = World::get_instance();
3499
3500 if (w->etherealEmpty())
3501 return 0; // no items left on stack
3502
3503 uint16 _objId = w->etherealPeek();
3504 Item *item = getItem(_objId);
3505 if (!item) {
3506 w->etherealRemove(_objId);
3507 return 0; // top item was invalid
3508 }
3509
3510 item->moveToContainer(container);
3511
3512 //! Anything else?
3513
3514 //! This should probably be different from I_popToContainer, but
3515 //! how exactly?
3516
3517 return _objId;
3518 }
3519
I_move(const uint8 * args,unsigned int)3520 uint32 Item::I_move(const uint8 *args, unsigned int /*argsize*/) {
3521 ARG_ITEM_FROM_PTR(item);
3522 ARG_UINT16(x);
3523 ARG_UINT16(y);
3524 ARG_UINT8(z);
3525 if (!item)
3526 return 0;
3527
3528 //! What should this do to ethereal items?
3529 if (GAME_IS_CRUSADER) {
3530 x *= 2;
3531 y *= 2;
3532 }
3533
3534 #if 0
3535 perr << "Moving item: " << item->getShape() << "," << item->getFrame() << " to (" << x << "," << y << "," << z << ")" << Std::endl;
3536 #endif
3537
3538 item->move(x, y, z);
3539 return 0;
3540 }
3541
I_legalMoveToPoint(const uint8 * args,unsigned int argsize)3542 uint32 Item::I_legalMoveToPoint(const uint8 *args, unsigned int argsize) {
3543 ARG_ITEM_FROM_PTR(item);
3544 ARG_WORLDPOINT(point);
3545 ARG_UINT16(move_if_blocked); // 0/1
3546 ARG_UINT16(unknown2); // always 0
3547
3548 int32 x = point.getX();
3549 int32 y = point.getY();
3550 int32 z = point.getZ();
3551
3552 if (GAME_IS_CRUSADER) {
3553 x *= 2;
3554 y *= 2;
3555 }
3556
3557 if (!item)
3558 return 0;
3559
3560 //
3561 // Return true when there are no blockers.
3562 // If there are blockers, only move if move_if_blocked is set.
3563 //
3564 int retval = 1;
3565 Std::list<CurrentMap::SweepItem> collisions;
3566 int32 start[3], end[3], dims[3];
3567 end[0] = x;
3568 end[1] = y;
3569 end[2] = z;
3570 item->getLocation(start[0], start[1], start[2]);
3571 item->getFootpadWorld(dims[0], dims[1], dims[2]);
3572 CurrentMap *map = World::get_instance()->getCurrentMap();
3573 map->sweepTest(start, end, dims, item->getShapeInfo()->_flags,
3574 item->getObjId(), true, &collisions);
3575 for (Std::list<CurrentMap::SweepItem>::iterator it = collisions.begin();
3576 it != collisions.end(); it++) {
3577 if (it->_blocking && !it->_touching && it->_endTime > 0) {
3578 if (!move_if_blocked)
3579 return 0;
3580 retval = 0;
3581 break;
3582 }
3583 }
3584
3585 item->collideMove(x, y, z, false, false);
3586 return retval;
3587 }
3588
I_legalMoveToContainer(const uint8 * args,unsigned int)3589 uint32 Item::I_legalMoveToContainer(const uint8 *args, unsigned int /*argsize*/) {
3590 ARG_ITEM_FROM_PTR(item);
3591 ARG_CONTAINER_FROM_PTR(container);
3592 ARG_UINT16(unknown); // always 0
3593 if (!item || !container) return 0; // shouldn't happen?
3594
3595 // try to move item to container checking weight and volume
3596 return item->moveToContainer(container, true);
3597 }
3598
I_getEtherealTop(const uint8 *,unsigned int)3599 uint32 Item::I_getEtherealTop(const uint8 * /*args*/, unsigned int /*argsize*/) {
3600 World *w = World::get_instance();
3601 if (w->etherealEmpty()) return 0; // no items left on stack
3602 return w->etherealPeek();
3603 }
3604
3605
3606 //!!! is this correct?
I_getMapArray(const uint8 * args,unsigned int)3607 uint32 Item::I_getMapArray(const uint8 *args, unsigned int /*argsize*/) {
3608 ARG_ITEM_FROM_PTR(item);
3609 if (!item) return 0;
3610
3611 return item->getMapNum();
3612 }
3613
3614 //!!! is this correct?
I_setMapArray(const uint8 * args,unsigned int)3615 uint32 Item::I_setMapArray(const uint8 *args, unsigned int /*argsize*/) {
3616 ARG_ITEM_FROM_PTR(item);
3617 ARG_UINT16(mapNum);
3618 if (!item) return 0;
3619
3620 item->setMapNum(mapNum);
3621 return 0;
3622 }
3623
I_getNpcNum(const uint8 * args,unsigned int)3624 uint32 Item::I_getNpcNum(const uint8 *args, unsigned int /*argsize*/) {
3625 ARG_ITEM_FROM_PTR(item);
3626 if (!item) return 0;
3627
3628 return item->getNpcNum();
3629 }
3630
I_setNpcNum(const uint8 * args,unsigned int)3631 uint32 Item::I_setNpcNum(const uint8 *args, unsigned int /*argsize*/) {
3632 ARG_ITEM_FROM_PTR(item);
3633 ARG_UINT16(npcNum);
3634 if (!item) return 0;
3635
3636 item->setNpcNum(npcNum);
3637 return 0;
3638 }
3639
I_getDirToCoords(const uint8 * args,unsigned int)3640 uint32 Item::I_getDirToCoords(const uint8 *args, unsigned int /*argsize*/) {
3641 ARG_ITEM_FROM_PTR(item);
3642 ARG_UINT16(x);
3643 ARG_UINT16(y);
3644 if (!item) return 0;
3645
3646 if (GAME_IS_CRUSADER) {
3647 x *= 2;
3648 y *= 2;
3649 }
3650
3651 int32 ix, iy, iz;
3652 item->getLocationAbsolute(ix, iy, iz);
3653
3654 return Direction_ToUsecodeDir(Direction_GetWorldDir(y - iy, x - ix, dirmode_8dirs));
3655 }
3656
I_getDirFromCoords(const uint8 * args,unsigned int)3657 uint32 Item::I_getDirFromCoords(const uint8 *args, unsigned int /*argsize*/) {
3658 ARG_ITEM_FROM_PTR(item);
3659 ARG_UINT16(x);
3660 ARG_UINT16(y);
3661 if (!item) return 0;
3662
3663 if (GAME_IS_CRUSADER) {
3664 x *= 2;
3665 y *= 2;
3666 }
3667
3668 int32 ix, iy, iz;
3669 item->getLocationAbsolute(ix, iy, iz);
3670
3671 return Direction_ToUsecodeDir(Direction_GetWorldDir(iy - y, ix - x, dirmode_8dirs));
3672 }
3673
I_getDirToItem(const uint8 * args,unsigned int)3674 uint32 Item::I_getDirToItem(const uint8 *args, unsigned int /*argsize*/) {
3675 ARG_ITEM_FROM_PTR(item);
3676 ARG_ITEM_FROM_ID(item2);
3677 if (!item) return 0;
3678 if (!item2) return 0;
3679
3680 int32 ix, iy, iz;
3681 item->getLocationAbsolute(ix, iy, iz);
3682
3683 int32 i2x, i2y, i2z;
3684 item2->getLocationAbsolute(i2x, i2y, i2z);
3685
3686 return Direction_ToUsecodeDir(Direction_GetWorldDir(i2y - iy, i2x - ix, dirmode_8dirs));
3687 }
3688
I_getDirFromItem(const uint8 * args,unsigned int)3689 uint32 Item::I_getDirFromItem(const uint8 *args, unsigned int /*argsize*/) {
3690 ARG_ITEM_FROM_PTR(item);
3691 ARG_ITEM_FROM_ID(item2);
3692 if (!item) return 0;
3693 if (!item2) return 0;
3694
3695 int32 ix, iy, iz;
3696 item->getLocationAbsolute(ix, iy, iz);
3697
3698 int32 i2x, i2y, i2z;
3699 item2->getLocationAbsolute(i2x, i2y, i2z);
3700
3701 return Direction_ToUsecodeDir(Direction_Invert(Direction_GetWorldDir(i2y - iy, i2x - ix, dirmode_8dirs)));
3702 }
3703
I_getDirFromTo16(const uint8 * args,unsigned int)3704 uint32 Item::I_getDirFromTo16(const uint8 *args, unsigned int /*argsize*/) {
3705 ARG_UINT16(x1);
3706 ARG_UINT16(y1);
3707 ARG_UINT16(x2);
3708 ARG_UINT16(y2);
3709
3710 if (x1 == x2 && y1 == y2)
3711 return 16;
3712
3713 return Direction_ToUsecodeDir(Direction_GetWorldDir(y2 - y1, x2 - x1, dirmode_16dirs));
3714 }
3715
I_getClosestDirectionInRange(const uint8 * args,unsigned int)3716 uint32 Item::I_getClosestDirectionInRange(const uint8 *args, unsigned int /*argsize*/) {
3717 ARG_UINT16(x1);
3718 ARG_UINT16(y1);
3719 ARG_UINT16(x2);
3720 ARG_UINT16(y2);
3721 ARG_UINT16(ndirs);
3722 ARG_UINT16(mind);
3723 ARG_UINT16(maxd);
3724
3725 Direction mindir = Direction_FromUsecodeDir(mind);
3726 Direction maxdir = Direction_FromUsecodeDir(maxd);
3727 DirectionMode mode = (ndirs == 16 ? dirmode_16dirs : dirmode_8dirs);
3728 Direction result = Direction_GetWorldDirInRange(y2 - y1, x2 - x1, mode, mindir, maxdir);
3729 return Direction_ToUsecodeDir(result);
3730 }
3731
I_hurl(const uint8 * args,unsigned int)3732 uint32 Item::I_hurl(const uint8 *args, unsigned int /*argsize*/) {
3733 ARG_ITEM_FROM_PTR(item);
3734 ARG_SINT16(xs);
3735 ARG_SINT16(ys);
3736 ARG_SINT16(zs);
3737 ARG_SINT16(grav);
3738 if (!item) return 0;
3739
3740 item->hurl(xs, ys, zs, grav);
3741
3742 return item->_gravityPid;
3743 }
3744
I_shoot(const uint8 * args,unsigned int)3745 uint32 Item::I_shoot(const uint8 *args, unsigned int /*argsize*/) {
3746 ARG_ITEM_FROM_PTR(item);
3747 ARG_WORLDPOINT(point);
3748 ARG_UINT16(speed); // either 0x20 (fish) or 0x40 (death disk, dart)
3749 ARG_UINT16(gravity); // either 2 (fish) or 1 (death disk, dart)
3750 if (!item) return 0;
3751
3752 MissileTracker tracker(item, point.getX(), point.getY(), point.getZ(),
3753 speed, gravity);
3754 tracker.launchItem();
3755
3756 return 0;
3757 }
3758
I_fall(const uint8 * args,unsigned int)3759 uint32 Item::I_fall(const uint8 *args, unsigned int /*argsize*/) {
3760 ARG_ITEM_FROM_PTR(item);
3761 if (!item) return 0;
3762
3763 item->fall();
3764
3765 return 0;
3766 }
3767
I_grab(const uint8 * args,unsigned int)3768 uint32 Item::I_grab(const uint8 *args, unsigned int /*argsize*/) {
3769 ARG_ITEM_FROM_PTR(item);
3770 if (!item) return 0;
3771
3772 item->grab();
3773
3774 return 0;
3775 }
3776
I_getSliderInput(const uint8 * args,unsigned int)3777 uint32 Item::I_getSliderInput(const uint8 *args, unsigned int /*argsize*/) {
3778 ARG_NULL32(); // ARG_ITEM_FROM_PTR(item);
3779 ARG_SINT16(minval);
3780 ARG_SINT16(maxval);
3781 ARG_SINT16(step);
3782
3783 UCProcess *current = dynamic_cast<UCProcess *>(Kernel::get_instance()->getRunningProcess());
3784 assert(current);
3785
3786 // pout << "SliderGump: min=" << minval << ", max=" << maxval << ", step=" << step << Std::endl;
3787
3788 SliderGump *_gump = new SliderGump(100, 100, minval, maxval, minval, step);
3789 _gump->InitGump(0); // modal _gump
3790 _gump->setUsecodeNotify(current);
3791
3792 current->suspend();
3793
3794 return 0;
3795 }
3796
I_openGump(const uint8 * args,unsigned int)3797 uint32 Item::I_openGump(const uint8 *args, unsigned int /*argsize*/) {
3798 ARG_ITEM_FROM_PTR(item);
3799 ARG_UINT16(gumpshape);
3800 if (!item) return 0;
3801
3802 item->openGump(gumpshape);
3803 return 0;
3804 }
3805
I_closeGump(const uint8 * args,unsigned int)3806 uint32 Item::I_closeGump(const uint8 *args, unsigned int /*argsize*/) {
3807 ARG_ITEM_FROM_PTR(item);
3808 if (!item) return 0;
3809
3810 item->closeGump();
3811 return 0;
3812 }
3813
I_guardianBark(const uint8 * args,unsigned int)3814 uint32 Item::I_guardianBark(const uint8 *args, unsigned int /*argsize*/) {
3815 ARG_ITEM_FROM_PTR(item);
3816 ARG_UINT16(num);
3817 if (!item) return 0;
3818
3819 assert(GAME_IS_U8);
3820
3821 return item->callUsecodeEvent_guardianBark(num);
3822 }
3823
I_getSurfaceWeight(const uint8 * args,unsigned int)3824 uint32 Item::I_getSurfaceWeight(const uint8 *args, unsigned int /*argsize*/) {
3825 ARG_ITEM_FROM_PTR(item);
3826 if (!item) return 0;
3827
3828 UCList uclist(2);
3829 LOOPSCRIPT(script, LS_TOKEN_TRUE);
3830 World *world = World::get_instance();
3831 world->getCurrentMap()->surfaceSearch(&uclist, script, sizeof(script),
3832 item, true, false, true);
3833
3834 uint32 weight = 0;
3835 for (uint32 i = 0; i < uclist.getSize(); i++) {
3836 Item *other = getItem(uclist.getuint16(i));
3837 if (!other) continue;
3838 weight += other->getTotalWeight();
3839 }
3840
3841 return weight;
3842 }
3843
I_isExplosive(const uint8 * args,unsigned int)3844 uint32 Item::I_isExplosive(const uint8 *args, unsigned int /*argsize*/) {
3845 ARG_ITEM_FROM_PTR(item);
3846 assert(GAME_IS_U8); // explode bit has different meaning in Cru.
3847 if (!item) return 0;
3848 return item->getShapeInfo()->is_u8_explode() ? 1 : 0;
3849 }
3850
I_receiveHit(const uint8 * args,unsigned int)3851 uint32 Item::I_receiveHit(const uint8 *args, unsigned int /*argsize*/) {
3852 ARG_ITEM_FROM_PTR(item);
3853 ARG_UINT16(other);
3854 ARG_SINT16(dir);
3855 ARG_SINT16(damage); // force of the hit
3856 ARG_UINT16(type); // hit type
3857 if (!item) return 0;
3858
3859 item->receiveHit(other, Direction_FromUsecodeDir(dir), damage, type);
3860
3861 return 0;
3862 }
3863
I_explode(const uint8 * args,unsigned int argsize)3864 uint32 Item::I_explode(const uint8 *args, unsigned int argsize) {
3865 ARG_ITEM_FROM_PTR(item);
3866 if (!item) return 0;
3867
3868 int exptype = 0;
3869 bool destroy_item = true;
3870 if (argsize > 4) {
3871 ARG_UINT16(etype)
3872 ARG_UINT16(destroy)
3873 exptype = etype;
3874 destroy_item = (destroy != 0);
3875 }
3876
3877 item->explode(exptype, destroy_item);
3878 return 0;
3879 }
3880
I_igniteChaos(const uint8 * args,unsigned int)3881 uint32 Item::I_igniteChaos(const uint8 *args, unsigned int /*argsize*/) {
3882 ARG_UINT16(x);
3883 ARG_UINT16(y);
3884 ARG_NULL8(); // z, unused
3885
3886 assert(GAME_IS_U8);
3887
3888 UCList itemlist(2);
3889 LOOPSCRIPT(script, LS_SHAPE_EQUAL(592)); // all oilflasks (CONSTANT!)
3890 CurrentMap *currentmap = World::get_instance()->getCurrentMap();
3891 currentmap->areaSearch(&itemlist, script, sizeof(script), 0,
3892 160, false, x, y); //! CHECKME: 160?
3893
3894 for (unsigned int i = 0; i < itemlist.getSize(); ++i) {
3895 Item *item = getItem(itemlist.getuint16(i));
3896 if (!item) continue;
3897 item->use();
3898 }
3899
3900 return 0;
3901 }
3902
I_canReach(const uint8 * args,unsigned int)3903 uint32 Item::I_canReach(const uint8 *args, unsigned int /*argsize*/) {
3904 ARG_ITEM_FROM_PTR(item);
3905 ARG_ITEM_FROM_ID(other);
3906 ARG_SINT16(range);
3907
3908 // TODO: add cheat to make this always return 1
3909
3910 if (item && other && item->canReach(other, range))
3911 return 1;
3912 else
3913 return 0;
3914 }
3915
3916
I_getRange(const uint8 * args,unsigned int)3917 uint32 Item::I_getRange(const uint8 *args, unsigned int /*argsize*/) {
3918 ARG_ITEM_FROM_PTR(item);
3919 ARG_ITEM_FROM_ID(other);
3920 if (!item) return 0;
3921 if (!other) return 0;
3922
3923 assert(GAME_IS_U8);
3924
3925 return item->getRange(*other);
3926 }
3927
I_getRangeIfVisible(const uint8 * args,unsigned int)3928 uint32 Item::I_getRangeIfVisible(const uint8 *args, unsigned int /*argsize*/) {
3929 // TODO: This is not exactly the same as the implementation in Cruasder,
3930 // but it should work?
3931 ARG_ITEM_FROM_PTR(item);
3932 ARG_ITEM_FROM_ID(other);
3933
3934 if (!item || !other)
3935 return 0;
3936
3937 // Somewhat arbitrary maths in here to replicate Crusader behavior.
3938 int range = item->getRangeIfVisible(*other) / 32;
3939 if ((range & 0xf) != 0)
3940 range++;
3941
3942 if (range <= 48) {
3943 return range;
3944 }
3945 return 0;
3946 }
3947
I_isCrusTypeNPC(const uint8 * args,unsigned int)3948 uint32 Item::I_isCrusTypeNPC(const uint8 *args, unsigned int /*argsize*/) {
3949 ARG_UINT16(sh);
3950
3951 if (sh == 0x7FE) return 1;
3952
3953 const ShapeInfo *info;
3954 info = GameData::get_instance()->getMainShapes()->getShapeInfo(sh);
3955 if (!info) return 0;
3956
3957 if (info->_flags & ShapeInfo::SI_CRU_NPC)
3958 return 1;
3959 else
3960 return 0;
3961 }
3962
I_setBroken(const uint8 * args,unsigned int)3963 uint32 Item::I_setBroken(const uint8 *args, unsigned int /*argsize*/) {
3964 ARG_ITEM_FROM_PTR(item);
3965 if (!item)
3966 return 0;
3967
3968 World::get_instance()->getCurrentMap()->removeTargetItem(item);
3969 item->setFlag(FLG_BROKEN);
3970 return 0;
3971 }
3972
I_inFastArea(const uint8 * args,unsigned int)3973 uint32 Item::I_inFastArea(const uint8 *args, unsigned int /*argsize*/) {
3974 ARG_ITEM_FROM_PTR(item);
3975
3976 if (!item)
3977 return 0;
3978
3979 return item->hasFlags(FLG_FASTAREA);
3980 }
3981
I_isPartlyOnScreen(const uint8 * args,unsigned int)3982 uint32 Item::I_isPartlyOnScreen(const uint8 *args, unsigned int /*argsize*/) {
3983 ARG_ITEM_FROM_PTR(item);
3984 if (!item) return 0;
3985
3986 return item->isPartlyOnScreen();
3987 }
3988
I_fireWeapon(const uint8 * args,unsigned int)3989 uint32 Item::I_fireWeapon(const uint8 *args, unsigned int /*argsize*/) {
3990 ARG_ITEM_FROM_PTR(item);
3991 ARG_SINT16(x);
3992 ARG_SINT16(y);
3993 ARG_SINT16(z);
3994 ARG_UINT16(dir);
3995 ARG_UINT16(firetype);
3996 ARG_UINT16(findtarget);
3997
3998 if (!item) return 0;
3999
4000 return item->fireWeapon(x * 2, y * 2, z, Direction_FromUsecodeDir(dir), firetype, findtarget != 0);
4001 }
4002
I_fireDistance(const uint8 * args,unsigned int)4003 uint32 Item::I_fireDistance(const uint8 *args, unsigned int /*argsize*/) {
4004 ARG_ITEM_FROM_PTR(item);
4005 ARG_UINT16(other);
4006 ARG_SINT16(dir);
4007 ARG_SINT16(xoff);
4008 ARG_SINT16(yoff);
4009 ARG_SINT16(zoff);
4010
4011 Item *otheritem = getItem(other);
4012
4013 if (!item || !otheritem) return 0;
4014
4015 return item->fireDistance(otheritem, Direction_FromUsecodeDir(dir), xoff * 2, yoff * 2, zoff);
4016 }
4017
4018 } // End of namespace Ultima8
4019 } // End of namespace Ultima
4020