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