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/ultima4/map/dungeon.h"
24 #include "ultima/ultima4/map/annotation.h"
25 #include "ultima/ultima4/game/context.h"
26 #include "ultima/ultima4/game/game.h"
27 #include "ultima/ultima4/game/item.h"
28 #include "ultima/ultima4/map/location.h"
29 #include "ultima/ultima4/map/mapmgr.h"
30 #include "ultima/ultima4/map/tilemap.h"
31 #include "ultima/ultima4/game/player.h"
32 #include "ultima/ultima4/gfx/screen.h"
33 #include "ultima/ultima4/views/stats.h"
34 #include "ultima/ultima4/map/tileset.h"
35 #include "ultima/ultima4/core/utils.h"
36
37 namespace Ultima {
38 namespace Ultima4 {
39
isDungeon(Map * punknown)40 bool isDungeon(Map *punknown) {
41 Dungeon *pd;
42 if ((pd = dynamic_cast<Dungeon *>(punknown)) != nullptr)
43 return true;
44 else
45 return false;
46 }
47
48 /*-------------------------------------------------------------------*/
49
load(Common::SeekableReadStream & s)50 void DngRoom::load(Common::SeekableReadStream &s) {
51 int i;
52
53 s.read(_creatureTiles, 16);
54 for (i = 0; i < 16; ++i)
55 _creatureStart[i].x = s.readByte();
56 for (i = 0; i < 16; ++i)
57 _creatureStart[i].y = s.readByte();
58
59 #define READ_DIR(DIR, XY) \
60 for (i = 0; i < 8; ++i) \
61 _partyStart[i][DIR].XY = s.readByte()
62
63 READ_DIR(DIR_NORTH, x);
64 READ_DIR(DIR_NORTH, y);
65 READ_DIR(DIR_EAST, x);
66 READ_DIR(DIR_EAST, y);
67 READ_DIR(DIR_SOUTH, x);
68 READ_DIR(DIR_SOUTH, y);
69 READ_DIR(DIR_WEST, x);
70 READ_DIR(DIR_WEST, y);
71
72 #undef READ_DIR
73 }
74
hythlothFix7()75 void DngRoom::hythlothFix7() {
76 int i;
77
78 // Update party start positions when entering from the east
79 const byte X1[8] = { 0x8, 0x8, 0x9, 0x9, 0x9, 0xA, 0xA, 0xA },
80 Y1[8] = { 0x3, 0x2, 0x3, 0x2, 0x1, 0x3, 0x2, 0x1 };
81
82 for (i = 0; i < 8; ++i)
83 _partyStart[i]._eastStart.x = X1[i];
84 for (i = 0; i < 8; ++i)
85 _partyStart[i]._eastStart.y = Y1[i];
86
87 // Update party start positions when entering from the south
88 const byte X2[8] = { 0x3, 0x2, 0x3, 0x2, 0x1, 0x3, 0x2, 0x1 },
89 Y2[8] = { 0x8, 0x8, 0x9, 0x9, 0x9, 0xA, 0xA, 0xA };
90
91 for (i = 0; i < 8; ++i)
92 _partyStart[i]._southStart.x = X2[i];
93 for (i = 0; i < 8; ++i)
94 _partyStart[i]._southStart.y = Y2[i];
95 }
96
hythlothFix9()97 void DngRoom::hythlothFix9() {
98 int i;
99
100 // Update the starting position of monsters 7, 8, and 9
101 const byte X1[3] = { 0x4, 0x6, 0x5 },
102 Y1[3] = { 0x5, 0x5, 0x6 };
103
104 for (i = 0; i < 3; ++i)
105 _creatureStart[i + 7].x = X1[i];
106 for (i = 0; i < 3; ++i)
107 _creatureStart[i + 7].y = Y1[i];
108
109 // Update party start positions when entering from the west
110 const byte X2[8] = { 0x2, 0x2, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0 },
111 Y2[8] = { 0x9, 0x8, 0x9, 0x8, 0x7, 0x9, 0x8, 0x7 };
112
113 for (i = 0; i < 8; ++i)
114 _partyStart[i]._westStart.x = X2[i];
115 for (i = 0; i < 8; ++i)
116 _partyStart[i]._westStart.y = Y2[i];
117
118 // Update the map data, moving the chest to the center of the room,
119 // and removing the walls at the lower-left corner thereby creating
120 // a connection to room 8
121 const Coords tile[6] = {
122 Coords(5, 5, 0x3C), // Chest
123 Coords(0, 7, 0x16), // Floor
124 Coords(1, 7, 0x16),
125 Coords(0, 8, 0x16),
126 Coords(1, 8, 0x16),
127 Coords(0, 9, 0x16)
128 };
129
130 for (i = 0; i < 6; ++i) {
131 const int index = (tile[i].y * CON_WIDTH) + tile[i].x;
132 _mapData[index] = g_tileMaps->get("base")->translate(tile[i].z);
133 }
134 }
135
136 /*-------------------------------------------------------------------*/
137
Dungeon()138 Dungeon::Dungeon() : _nRooms(0), _rooms(nullptr),
139 _roomMaps(nullptr), _currentRoom(0) {
140 Common::fill(&_partyStartX[0], &_partyStartX[8], 0);
141 Common::fill(&_partyStartY[0], &_partyStartY[8], 0);
142 }
143
getName()144 Common::String Dungeon::getName() {
145 return _name;
146 }
147
tokenForTile(MapTile tile)148 DungeonToken Dungeon::tokenForTile(MapTile tile) {
149 const static Common::String tileNames[] = {
150 "brick_floor", "up_ladder", "down_ladder", "up_down_ladder", "chest",
151 "unimpl_ceiling_hole", "unimpl_floor_hole", "magic_orb",
152 "ceiling_hole", "fountain",
153 "brick_floor", "dungeon_altar", "dungeon_door", "dungeon_room",
154 "secret_door", "brick_wall", ""
155 };
156
157 const static Common::String fieldNames[] = { "poison_field", "energy_field", "fire_field", "sleep_field", "" };
158
159 int i;
160 Tile *t = _tileSet->get(tile.getId());
161
162 for (i = 0; !tileNames[i].empty(); i++) {
163 if (t->getName() == tileNames[i])
164 return DungeonToken(i << 4);
165 }
166
167 for (i = 0; !fieldNames[i].empty(); i++) {
168 if (t->getName() == fieldNames[i])
169 return DUNGEON_FIELD;
170 }
171
172 return (DungeonToken)0;
173 }
174
currentToken()175 DungeonToken Dungeon::currentToken() {
176 return tokenAt(g_context->_location->_coords);
177 }
178
currentSubToken()179 byte Dungeon::currentSubToken() {
180 return subTokenAt(g_context->_location->_coords);
181 }
182
tokenAt(MapCoords coords)183 DungeonToken Dungeon::tokenAt(MapCoords coords) {
184 return tokenForTile(*getTileFromData(coords));
185 }
186
subTokenAt(MapCoords coords)187 byte Dungeon::subTokenAt(MapCoords coords) {
188 int index = coords.x + (coords.y * _width) + (_width * _height * coords.z);
189 return _dataSubTokens[index];
190 }
191
dungeonSearch(void)192 void dungeonSearch(void) {
193 Dungeon *dungeon = dynamic_cast<Dungeon *>(g_context->_location->_map);
194 assert(dungeon);
195
196 DungeonToken token = dungeon->currentToken();
197 Annotation::List a = dungeon->_annotations->allAt(g_context->_location->_coords);
198 const ItemLocation *item;
199 if (a.size() > 0)
200 token = DUNGEON_CORRIDOR;
201
202 g_screen->screenMessage("Search...\n");
203
204 switch (token) {
205 case DUNGEON_MAGIC_ORB: /* magic orb */
206 g_screen->screenMessage("You find a Magical Ball...\nWho touches? ");
207 dungeonTouchOrb();
208 break;
209
210 case DUNGEON_FOUNTAIN: /* fountains */
211 dungeonDrinkFountain();
212 break;
213
214 default: {
215 /* see if there is an item at the current location (stones on altars, etc.) */
216 item = g_items->itemAtLocation(dungeon, g_context->_location->_coords);
217 if (item) {
218 if (item->_isItemInInventory && (g_items->*(item->_isItemInInventory))(item->_data)) {
219 g_screen->screenMessage("Nothing Here!\n");
220 } else {
221 if (item->_name)
222 g_screen->screenMessage("You find...\n%s!\n", item->_name);
223 (g_items->*(item->_putItemInInventory))(item->_data);
224 }
225 } else {
226 g_screen->screenMessage("\nYou find Nothing!\n");
227 }
228 }
229
230 break;
231 }
232 }
233
dungeonDrinkFountain()234 void dungeonDrinkFountain() {
235 g_screen->screenMessage("You find a Fountain.\nWho drinks? ");
236 int player = gameGetPlayer(false, false);
237 if (player == -1)
238 return;
239
240 Dungeon *dungeon = dynamic_cast<Dungeon *>(g_context->_location->_map);
241 assert(dungeon);
242 FountainType type = (FountainType) dungeon->currentSubToken();
243
244 switch (type) {
245 /* plain fountain */
246 case FOUNTAIN_NORMAL:
247 g_screen->screenMessage("\nHmmm--No Effect!\n");
248 break;
249
250 /* healing fountain */
251 case FOUNTAIN_HEALING:
252 if (g_context->_party->member(player)->heal(HT_FULLHEAL))
253 g_screen->screenMessage("\nAhh-Refreshing!\n");
254 else
255 g_screen->screenMessage("\nHmmm--No Effect!\n");
256 break;
257
258 /* acid fountain */
259 case FOUNTAIN_ACID:
260 g_context->_party->member(player)->applyDamage(100); /* 100 damage to drinker */
261 g_screen->screenMessage("\nBleck--Nasty!\n");
262 break;
263
264 /* cure fountain */
265 case FOUNTAIN_CURE:
266 if (g_context->_party->member(player)->heal(HT_CURE))
267 g_screen->screenMessage("\nHmmm--Delicious!\n");
268 else
269 g_screen->screenMessage("\nHmmm--No Effect!\n");
270 break;
271
272 /* poison fountain */
273 case FOUNTAIN_POISON:
274 if (g_context->_party->member(player)->getStatus() != STAT_POISONED) {
275 soundPlay(SOUND_POISON_DAMAGE);
276 g_context->_party->member(player)->applyEffect(EFFECT_POISON);
277 g_context->_party->member(player)->applyDamage(100); /* 100 damage to drinker also */
278 g_screen->screenMessage("\nArgh-Choke-Gasp!\n");
279 } else {
280 g_screen->screenMessage("\nHmm--No Effect!\n");
281 }
282 break;
283
284 default:
285 error("Invalid call to dungeonDrinkFountain: no fountain at current location");
286 }
287 }
288
dungeonTouchOrb()289 void dungeonTouchOrb() {
290 g_screen->screenMessage("You find a Magical Ball...\nWho touches? ");
291 int player = gameGetPlayer(false, false);
292 if (player == -1)
293 return;
294
295 int stats = 0;
296 int damage = 0;
297
298 /* Get current position and find a replacement tile for it */
299 Tile *orb_tile = g_context->_location->_map->_tileSet->getByName("magic_orb");
300 MapTile replacementTile(g_context->_location->getReplacementTile(g_context->_location->_coords, orb_tile));
301
302 switch (g_context->_location->_map->_id) {
303 case MAP_DECEIT:
304 stats = STATSBONUS_INT;
305 break;
306 case MAP_DESPISE:
307 stats = STATSBONUS_DEX;
308 break;
309 case MAP_DESTARD:
310 stats = STATSBONUS_STR;
311 break;
312 case MAP_WRONG:
313 stats = STATSBONUS_INT | STATSBONUS_DEX;
314 break;
315 case MAP_COVETOUS:
316 stats = STATSBONUS_DEX | STATSBONUS_STR;
317 break;
318 case MAP_SHAME:
319 stats = STATSBONUS_INT | STATSBONUS_STR;
320 break;
321 case MAP_HYTHLOTH:
322 stats = STATSBONUS_INT | STATSBONUS_DEX | STATSBONUS_STR;
323 break;
324 default:
325 break;
326 }
327
328 /* give stats bonuses */
329 if (stats & STATSBONUS_STR) {
330 g_screen->screenMessage("Strength + 5\n");
331 AdjustValueMax(g_ultima->_saveGame->_players[player]._str, 5, 50);
332 damage += 200;
333 }
334 if (stats & STATSBONUS_DEX) {
335 g_screen->screenMessage("Dexterity + 5\n");
336 AdjustValueMax(g_ultima->_saveGame->_players[player]._dex, 5, 50);
337 damage += 200;
338 }
339 if (stats & STATSBONUS_INT) {
340 g_screen->screenMessage("Intelligence + 5\n");
341 AdjustValueMax(g_ultima->_saveGame->_players[player]._intel, 5, 50);
342 damage += 200;
343 }
344
345 /* deal damage to the party member who touched the orb */
346 g_context->_party->member(player)->applyDamage(damage);
347 /* remove the orb from the map */
348 g_context->_location->_map->_annotations->add(g_context->_location->_coords, replacementTile);
349 }
350
dungeonHandleTrap(TrapType trap)351 bool dungeonHandleTrap(TrapType trap) {
352 Dungeon *dungeon = dynamic_cast<Dungeon *>(g_context->_location->_map);
353 assert(dungeon);
354
355 switch ((TrapType)dungeon->currentSubToken()) {
356 case TRAP_WINDS:
357 g_screen->screenMessage("\nWinds!\n");
358 g_context->_party->quenchTorch();
359 break;
360 case TRAP_FALLING_ROCK:
361 // Treat falling rocks and pits like bomb traps
362 // XXX: That's a little harsh.
363 g_screen->screenMessage("\nFalling Rocks!\n");
364 g_context->_party->applyEffect(EFFECT_LAVA);
365 break;
366 case TRAP_PIT:
367 g_screen->screenMessage("\nPit!\n");
368 g_context->_party->applyEffect(EFFECT_LAVA);
369 break;
370 default:
371 break;
372 }
373
374 return true;
375 }
376
ladderUpAt(MapCoords coords)377 bool Dungeon::ladderUpAt(MapCoords coords) {
378 Annotation::List a = _annotations->allAt(coords);
379
380 if (tokenAt(coords) == DUNGEON_LADDER_UP ||
381 tokenAt(coords) == DUNGEON_LADDER_UPDOWN)
382 return true;
383
384 if (a.size() > 0) {
385 Annotation::List::iterator i;
386 for (i = a.begin(); i != a.end(); i++) {
387 if (i->getTile() == _tileSet->getByName("up_ladder")->getId())
388 return true;
389 }
390 }
391 return false;
392 }
393
ladderDownAt(MapCoords coords)394 bool Dungeon::ladderDownAt(MapCoords coords) {
395 Annotation::List a = _annotations->allAt(coords);
396
397 if (tokenAt(coords) == DUNGEON_LADDER_DOWN ||
398 tokenAt(coords) == DUNGEON_LADDER_UPDOWN)
399 return true;
400
401 if (a.size() > 0) {
402 Annotation::List::iterator i;
403 for (i = a.begin(); i != a.end(); i++) {
404 if (i->getTile() == _tileSet->getByName("down_ladder")->getId())
405 return true;
406 }
407 }
408 return false;
409 }
410
validTeleportLocation(MapCoords coords)411 bool Dungeon::validTeleportLocation(MapCoords coords) {
412 MapTile *tile = tileAt(coords, WITH_OBJECTS);
413 return tokenForTile(*tile) == DUNGEON_CORRIDOR;
414 }
415
416 } // End of namespace Ultima4
417 } // End of namespace Ultima
418