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/misc/pent_include.h"
24 #include "ultima/ultima8/world/world.h"
25 #include "ultima/ultima8/world/map.h"
26 #include "ultima/ultima8/world/current_map.h"
27 #include "ultima/ultima8/filesys/flex_file.h"
28 #include "ultima/ultima8/filesys/raw_archive.h"
29 #include "ultima/ultima8/world/item_factory.h"
30 #include "ultima/ultima8/world/actors/main_actor.h"
31 #include "ultima/ultima8/world/loop_script.h"
32 #include "ultima/ultima8/usecode/uc_list.h"
33 #include "ultima/ultima8/misc/direction_util.h"
34 #include "ultima/ultima8/games/game_data.h"
35 #include "ultima/ultima8/kernel/kernel.h"
36 #include "ultima/ultima8/kernel/object_manager.h"
37 #include "ultima/ultima8/world/camera_process.h" // for resetting the camera
38 #include "ultima/ultima8/gumps/gump.h" // For CloseItemDependents notification
39 #include "ultima/ultima8/world/get_object.h"
40 #include "ultima/ultima8/world/target_reticle_process.h"
41 #include "ultima/ultima8/audio/audio_process.h"
42 #include "ultima/ultima8/world/snap_process.h"
43 #include "ultima/ultima8/graphics/main_shape_archive.h"
44
45 namespace Ultima {
46 namespace Ultima8 {
47
48 //#define DUMP_ITEMS
49
50 World *World::_world = nullptr;
51
World()52 World::World() : _currentMap(nullptr), _alertActive(false), _difficulty(3),
53 _controlledNPCNum(1), _vargasShield(5000) {
54 debugN(MM_INFO, "Creating World...\n");
55
56 _world = this;
57 }
58
59
~World()60 World::~World() {
61 debugN(MM_INFO, "Destroying World...\n");
62 clear();
63
64 _world = nullptr;
65 }
66
67
clear()68 void World::clear() {
69 unsigned int i;
70
71 for (i = 0; i < _maps.size(); ++i) {
72 delete _maps[i];
73 }
74 _maps.clear();
75
76 while (!_ethereal.empty())
77 _ethereal.pop_front();
78
79 if (_currentMap)
80 delete _currentMap;
81 _currentMap = nullptr;
82
83 _alertActive = false;
84 _controlledNPCNum = 1;
85 _vargasShield = 5000;
86 }
87
reset()88 void World::reset() {
89 debugN(MM_INFO, "Resetting World...\n");
90
91 clear();
92
93 initMaps();
94 }
95
initMaps()96 void World::initMaps() {
97 // Q: How do we determine which Maps to create? Only create those
98 // with non-zero size in fixed.dat?
99
100 _maps.resize(256);
101 for (unsigned int i = 0; i < 256; ++i) {
102 _maps[i] = new Map(i);
103 }
104
105 _currentMap = new CurrentMap();
106 }
107
switchMap(uint32 newmap)108 bool World::switchMap(uint32 newmap) {
109 assert(_currentMap);
110
111 if (_currentMap->getNum() == newmap)
112 return true;
113
114 if (newmap >= _maps.size() || _maps[newmap] == nullptr)
115 return false; // no such map
116
117 // Map switching procedure:
118
119 // stop all sound effects (except speech, such as Guardian barks)
120 // notify all gumps of a map change
121 // delete any ethereal objects
122 // write back CurrentMap to the old map, which
123 // deletes all disposable items
124 // deletes the EggHatcher
125 // resets all eggs
126 // swap out fixed items in old map
127 // kill all processes (except those of type 1 or of item 0)
128 // load fixed items in new map
129 // load new map into CurrentMap, which also
130 // assigns objIDs to fixed items
131 // assigns objIDs to nonfixed items
132 // creates an EggHatcher and notifies it of all eggs
133 // sets up all NPCs in the new map
134 // update camera if needed
135
136 AudioProcess *ap = AudioProcess::get_instance();
137 if (ap) ap->stopAllExceptSpeech();
138
139 // Notify all the gumps of the mapchange
140 Ultima8Engine *gui = Ultima8Engine::get_instance();
141 if (gui) {
142 Gump *desktop = gui->getDesktopGump();
143 if (desktop) desktop->CloseItemDependents();
144 }
145
146 // get rid of any remaining _ethereal items
147 while (!_ethereal.empty()) {
148 uint16 eth = _ethereal.front();
149 _ethereal.pop_front();
150 Item *i = getItem(eth);
151 if (i) i->destroy();
152 }
153
154 uint32 oldmap = _currentMap->getNum();
155 if (oldmap != 0) {
156 perr << "Unloading map " << oldmap << Std::endl;
157
158 assert(oldmap < _maps.size() && _maps[oldmap] != nullptr);
159
160 _currentMap->writeback();
161
162 perr << "Unloading Fixed items from map " << oldmap << Std::endl;
163
164 _maps[oldmap]->unloadFixed();
165 }
166
167 // Kill any processes that need killing (those with type != 1 && item != 0)
168 Kernel::get_instance()->killProcessesNotOfType(0, 1, true);
169
170 pout << "Loading Fixed items in map " << newmap << Std::endl;
171 Common::SeekableReadStream *items = GameData::get_instance()->getFixed()
172 ->get_datasource(newmap);
173 _maps[newmap]->loadFixed(items);
174 delete items;
175
176 _currentMap->loadMap(_maps[newmap]);
177
178 // update camera if needed (u8 only)
179 // TODO: This may not even be needed, but do it just in case the
180 // camera was looking at something else during teleport.
181 if (GAME_IS_U8) {
182 CameraProcess *camera = CameraProcess::GetCameraProcess();
183 if (camera && camera->getItemNum() != 1) {
184 CameraProcess::SetCameraProcess(new CameraProcess(1));
185 }
186 CameraProcess::SetEarthquake(0);
187 }
188
189 return true;
190 }
191
loadNonFixed(Common::SeekableReadStream * rs)192 void World::loadNonFixed(Common::SeekableReadStream *rs) {
193 FlexFile *f = new FlexFile(rs);
194
195 pout << "Loading NonFixed items" << Std::endl;
196
197 for (unsigned int i = 0; i < f->getCount(); ++i) {
198
199 // items in this map?
200 if (f->getSize(i) > 0) {
201 assert(_maps.size() > i);
202 assert(_maps[i] != nullptr);
203
204 Common::SeekableReadStream *items = f->getDataSource(i);
205
206 _maps[i]->loadNonFixed(items);
207
208 delete items;
209
210 }
211 }
212
213 delete f;
214 }
215
loadItemCachNPCData(Common::SeekableReadStream * itemcach,Common::SeekableReadStream * npcdata)216 void World::loadItemCachNPCData(Common::SeekableReadStream *itemcach, Common::SeekableReadStream *npcdata) {
217 FlexFile *itemcachflex = new FlexFile(itemcach);
218 FlexFile *npcdataflex = new FlexFile(npcdata);
219
220 Common::SeekableReadStream *itemds = itemcachflex->getDataSource(0);
221 Common::SeekableReadStream *npcds = npcdataflex->getDataSource(0);
222
223 delete itemcachflex;
224 delete npcdataflex;
225
226 pout << "Loading NPCs" << Std::endl;
227
228 for (uint32 i = 1; i < 256; ++i) { // Get rid of constants?
229 // These are ALL unsigned on disk
230 itemds->seek(0x00000 + i * 2);
231 int32 x = static_cast<int32>(itemds->readUint16LE());
232 itemds->seek(0x04800 + i * 2);
233 int32 y = static_cast<int32>(itemds->readUint16LE());
234 itemds->seek(0x09000 + i * 1);
235 int32 z = static_cast<int32>(itemds->readByte());
236
237 itemds->seek(0x0B400 + i * 2);
238 uint32 shape = itemds->readUint16LE();
239 itemds->seek(0x0FC00 + i * 1);
240 uint32 frame = itemds->readByte();
241 itemds->seek(0x12000 + i * 2);
242 uint16 flags = itemds->readUint16LE();
243 itemds->seek(0x16800 + i * 2);
244 uint16 quality = itemds->readUint16LE();
245 itemds->seek(0x1B000 + i * 1);
246 uint16 npcnum = static_cast<uint8>(itemds->readByte());
247 itemds->seek(0x1D400 + i * 1);
248 uint16 mapnum = static_cast<uint8>(itemds->readByte());
249 itemds->seek(0x1F800 + i * 2);
250 //uint16 next;
251 (void)itemds->readUint16LE();
252
253 // half the frame number is stored in npcdata.dat
254 npcds->seek(7 + i * 0x31);
255 frame += npcds->readByte() << 8;
256
257 if (shape == 0) {
258 // U8's itemcach has a lot of garbage in it.
259 // Ignore it.
260 continue;
261 }
262
263 #ifdef DUMP_ITEMS
264 pout << shape << "," << frame << ":\t(" << x << "," << y << "," << z << "),\t" << Std::hex << flags << Std::dec << ", " << quality << ", " << npcnum << ", " << mapnum << ", " << next << Std::endl;
265 #endif
266
267 Actor *actor = ItemFactory::createActor(shape, frame, quality,
268 flags | Item::FLG_IN_NPC_LIST,
269 npcnum, mapnum,
270 Item::EXT_PERMANENT_NPC, false);
271 if (!actor) {
272 #ifdef DUMP_ITEMS
273 pout << "Couldn't create actor" << Std::endl;
274 #endif
275 continue;
276 }
277 ObjectManager::get_instance()->assignActorObjId(actor, i);
278
279 actor->setLocation(x, y, z);
280
281 // read npcdata:
282 npcds->seek(i * 0x31);
283 actor->setStr(npcds->readByte()); // 0x00: strength
284 actor->setDex(npcds->readByte()); // 0x01: dexterity
285 actor->setInt(npcds->readByte()); // 0x02: intelligence
286 actor->setHP(npcds->readByte()); // 0x03: hitpoints
287 actor->setDir(Direction_FromUsecodeDir(npcds->readByte())); // 0x04: direction
288 uint16 la = npcds->readUint16LE(); // 0x05,0x06: last anim
289 actor->setLastAnim(static_cast<Animation::Sequence>(la));
290 npcds->skip(1); // 0x07: high byte of framenum
291 npcds->skip(1); // 0x08: current anim frame
292 npcds->skip(1); // 0x09: start Z of current fall
293 npcds->skip(1); // 0x0A: unknown, always zero
294 uint8 align = npcds->readByte(); // 0x0B: alignments
295 actor->setAlignment(align & 0x0F);
296 actor->setEnemyAlignment(align & 0xF0);
297 actor->setUnkByte(npcds->readByte()); // 0x0C: unknown;
298 // 0x0C is almost always zero, except for
299 // the avatar (0xC0) and
300 // Malchir, Vardion, Gorgrond, Beren (0xE0)
301 npcds->skip(14); // 0x0D-0x1A: unknown, always zero
302 actor->clearActorFlag(0xFF);
303 actor->setActorFlag(npcds->readByte()); // 0x1B: flags
304 npcds->skip(1); // 0x1C: unknown, always zero
305 npcds->skip(16); // 0x1D-0x2C: equipment
306 int16 mana = static_cast<int16>(npcds->readUint16LE()); // 0x2D,0x2E: mana
307 actor->setMana(mana);
308 actor->clearActorFlag(0xFFFF00);
309 uint32 flags2F = npcds->readByte(); // 0x2F: flags
310 actor->setActorFlag(flags2F << 8);
311 uint32 flags30 = npcds->readByte(); // 0x30: flags
312 actor->setActorFlag(flags30 << 16);
313 }
314
315 delete itemds;
316 delete npcds;
317 }
318
319
worldStats() const320 void World::worldStats() const {
321 unsigned int i, mapcount = 0;
322
323 for (i = 0; i < _maps.size(); i++) {
324 if (_maps[i] != nullptr && !_maps[i]->isEmpty())
325 mapcount++;
326 }
327
328 g_debugger->debugPrintf("World memory stats:\n");
329 g_debugger->debugPrintf("Maps : %u/256\n", mapcount);
330
331 const Actor *av = getMainActor();
332 g_debugger->debugPrintf("Avatar pos.: ");
333 if (av) {
334 g_debugger->debugPrintf("map %d, (", av->getMapNum());
335 int32 x, y, z;
336 av->getLocation(x, y, z);
337 g_debugger->debugPrintf("%d,%d,%d)\n", x, y, z);
338 } else {
339 g_debugger->debugPrintf("missing (null)\n");
340 }
341 }
342
save(Common::WriteStream * ws)343 void World::save(Common::WriteStream *ws) {
344 ws->writeUint32LE(_currentMap->getNum());
345
346 ws->writeUint16LE(_currentMap->_eggHatcher);
347
348 if (GAME_IS_CRUSADER) {
349 ws->writeByte(_alertActive ? 1 : 0);
350 ws->writeByte(_difficulty);
351 ws->writeUint16LE(_controlledNPCNum);
352 ws->writeUint32LE(_vargasShield);
353 }
354
355 uint16 es = static_cast<uint16>(_ethereal.size());
356 ws->writeUint32LE(es);
357
358 // empty stack and refill it again
359 uint16 *e = new uint16[es];
360 Std::list<ObjId>::const_iterator it = _ethereal.begin();
361 unsigned int i;
362 for (i = 0; i < es; ++i) {
363 e[es - i] = *it;
364 ++it;
365 }
366
367 for (i = 0; i < es; ++i) {
368 ws->writeUint16LE(e[i]);
369 }
370 delete[] e;
371 }
372
373 // load items
load(Common::ReadStream * rs,uint32 version)374 bool World::load(Common::ReadStream *rs, uint32 version) {
375 uint16 curmapnum = rs->readUint32LE();
376 _currentMap->setMap(_maps[curmapnum]);
377
378 _currentMap->_eggHatcher = rs->readUint16LE();
379
380 if (GAME_IS_CRUSADER) {
381 _alertActive = (rs->readByte() != 0);
382 _difficulty = rs->readByte();
383 _controlledNPCNum = rs->readUint16LE();
384 _vargasShield = rs->readUint32LE();
385 }
386
387 uint32 etherealcount = rs->readUint32LE();
388 for (unsigned int i = 0; i < etherealcount; ++i) {
389 _ethereal.push_front(rs->readUint16LE());
390 }
391
392 return true;
393 }
394
saveMaps(Common::WriteStream * ws)395 void World::saveMaps(Common::WriteStream *ws) {
396 ws->writeUint32LE(static_cast<uint32>(_maps.size()));
397 for (unsigned int i = 0; i < _maps.size(); ++i) {
398 _maps[i]->save(ws);
399 }
400 }
401
loadMaps(Common::ReadStream * rs,uint32 version)402 bool World::loadMaps(Common::ReadStream *rs, uint32 version) {
403 uint32 mapcount = rs->readUint32LE();
404
405 // Integrity check
406 if (mapcount > _maps.size()) {
407 warning("Invalid mapcount in save: %d. Corrupt save?", mapcount);
408 return false;
409 }
410
411 // Map objects have already been created by reset()
412 for (unsigned int i = 0; i < mapcount; ++i) {
413 bool res = _maps[i]->load(rs, version);
414 if (!res) return false;
415 }
416
417 return true;
418 }
419
setAlertActive(bool active)420 void World::setAlertActive(bool active)
421 {
422 assert(GAME_IS_CRUSADER);
423 _alertActive = active;
424
425 if (GAME_IS_REMORSE) {
426 setAlertActiveRemorse(active);
427 } else {
428 setAlertActiveRegret(active);
429 }
430 }
431
setAlertActiveRemorse(bool active)432 void World::setAlertActiveRemorse(bool active)
433 {
434 // Replicate the behavior of the original game.
435 LOOPSCRIPT(script,
436 LS_OR(
437 LS_OR(
438 LS_OR(
439 LS_OR(LS_SHAPE_EQUAL(0x49), LS_SHAPE_EQUAL(0x21)),
440 LS_SHAPE_EQUAL(0x174)),
441 LS_SHAPE_EQUAL(0x271)),
442 LS_SHAPE_EQUAL(0x477))
443 );
444
445 UCList itemlist(2);
446 _world->getCurrentMap()->areaSearch(&itemlist, script, sizeof(script),
447 nullptr, 0xffff, false);
448 for (uint32 i = 0; i < itemlist.getSize(); i++) {
449 uint16 itemid = itemlist.getuint16(i);
450 Item *item = getItem(itemid);
451 assert(item);
452 int frame = item->getFrame();
453 if (_alertActive) {
454 if (item->getShape() == 0x477) {
455 if (frame < 2)
456 item->setFrame(frame + 2);
457 } else if (frame == 0) {
458 item->setFrame(1);
459 }
460 } else {
461 if (item->getShape() == 0x477) {
462 if (frame > 1)
463 item->setFrame(frame - 2);
464 } else if (frame == 1) {
465 item->setFrame(0);
466 }
467 }
468 }
469 }
470
setAlertActiveRegret(bool active)471 void World::setAlertActiveRegret(bool active)
472 {
473 setAlertActiveRemorse(active);
474
475 LOOPSCRIPT(offscript, LS_OR(LS_SHAPE_EQUAL(0x660), LS_SHAPE_EQUAL(0x661)));
476 LOOPSCRIPT(onscript, LS_OR(LS_SHAPE_EQUAL(0x662), LS_SHAPE_EQUAL(0x663)));
477
478 const uint8 *script = active ? onscript : offscript;
479 // note: size should be the same, but just to be explicit.
480 int scriptlen = active ? sizeof(onscript) : sizeof(offscript);
481
482 UCList itemlist(2);
483 _world->getCurrentMap()->areaSearch(&itemlist, script, scriptlen,
484 nullptr, 0xffff, false);
485 for (uint32 i = 0; i < itemlist.getSize(); i++) {
486 uint16 itemid = itemlist.getuint16(i);
487 Item *item = getItem(itemid);
488 assert(item);
489 switch (item->getShape()) {
490 case 0x660:
491 item->setShape(0x663);
492 break;
493 case 0x661:
494 item->setShape(0x662);
495 break;
496 case 0x662:
497 item->setShape(0x661);
498 break;
499 case 0x663:
500 item->setShape(0x660);
501 break;
502 default:
503 warning("unexpected shape %d returned from search", item->getShape());
504 break;
505 }
506 item->setFrame(0);
507 }
508 }
509
setGameDifficulty(uint8 difficulty)510 void World::setGameDifficulty(uint8 difficulty) {
511 _difficulty = difficulty;
512 if (GAME_IS_REMORSE) {
513 // HACK: Set ammo data for BA-40 in higher 2 difficulty levels
514 // This would be better handled in the ini file somehow?
515 const ShapeInfo *si = GameData::get_instance()->getMainShapes()->getShapeInfo(0x32E);
516 if (si && si->_weaponInfo) {
517 WeaponInfo *wi = si->_weaponInfo;
518 wi->_clipSize = 20;
519 if (difficulty > 1) {
520 wi->_ammoShape = 0x33D;
521 wi->_ammoType = 1;
522 } else {
523 wi->_ammoShape = 0;
524 wi->_ammoType = 0;
525 }
526 }
527 }
528 }
529
530
setControlledNPCNum(uint16 num)531 void World::setControlledNPCNum(uint16 num) {
532 uint16 oldnpc = _controlledNPCNum;
533 _controlledNPCNum = num;
534 Actor *previous = getActor(oldnpc);
535 if (previous && !previous->isDead() && previous->isInCombat()) {
536 previous->clearInCombat();
537 }
538
539 Actor *controlled = getActor(num);
540 if (controlled) {
541 if (num != 1) {
542 Kernel::get_instance()->killProcesses(num, Kernel::PROC_TYPE_ALL, true);
543 if (controlled->isInCombat())
544 controlled->clearInCombat();
545 }
546 int32 x, y, z;
547 controlled->getCentre(x, y, z);
548 CameraProcess::SetCameraProcess(new CameraProcess(x, y, z));
549 }
550
551 TargetReticleProcess *t = TargetReticleProcess::get_instance();
552 if (t) {
553 t->avatarMoved();
554 }
555 }
556
557
I_getAlertActive(const uint8 *,unsigned int)558 uint32 World::I_getAlertActive(const uint8 * /*args*/,
559 unsigned int /*argsize*/) {
560 return get_instance()->_world->isAlertActive() ? 1 : 0;
561 }
562
I_setAlertActive(const uint8 *,unsigned int)563 uint32 World::I_setAlertActive(const uint8 * /*args*/,
564 unsigned int /*argsize*/) {
565 get_instance()->_world->setAlertActive(true);
566 return 0;
567 }
568
I_clrAlertActive(const uint8 *,unsigned int)569 uint32 World::I_clrAlertActive(const uint8 * /*args*/,
570 unsigned int /*argsize*/) {
571 get_instance()->_world->setAlertActive(false);
572 return 0;
573 }
574
I_gameDifficulty(const uint8 *,unsigned int)575 uint32 World::I_gameDifficulty(const uint8 * /*args*/,
576 unsigned int /*argsize*/) {
577 return get_instance()->_world->getGameDifficulty();
578 }
579
I_getControlledNPCNum(const uint8 *,unsigned int)580 uint32 World::I_getControlledNPCNum(const uint8 * /*args*/,
581 unsigned int /*argsize*/) {
582 return get_instance()->_world->getControlledNPCNum();
583 }
584
I_setControlledNPCNum(const uint8 * args,unsigned int)585 uint32 World::I_setControlledNPCNum(const uint8 *args,
586 unsigned int /*argsize*/) {
587 ARG_UINT16(num);
588 get_instance()->_world->setControlledNPCNum(num);
589 return 0;
590 }
591
I_resetVargasShield(const uint8 *,unsigned int)592 uint32 World::I_resetVargasShield(const uint8 * /*args*/,
593 unsigned int /*argsize*/) {
594 get_instance()->setVargasShield(500);
595 return 0;
596 }
597
598 } // End of namespace Ultima8
599 } // End of namespace Ultima
600