832 lines
24 KiB
C++
832 lines
24 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "ultima/ultima4/ultima4.h"
|
|
#include "ultima/ultima4/controllers/camp_controller.h"
|
|
#include "ultima/ultima4/controllers/combat_controller.h"
|
|
#include "ultima/ultima4/controllers/intro_controller.h"
|
|
#include "ultima/ultima4/controllers/read_choice_controller.h"
|
|
#include "ultima/ultima4/controllers/read_dir_controller.h"
|
|
#include "ultima/ultima4/controllers/read_int_controller.h"
|
|
#include "ultima/ultima4/controllers/read_player_controller.h"
|
|
#include "ultima/ultima4/controllers/read_string_controller.h"
|
|
#include "ultima/ultima4/conversation/conversation.h"
|
|
#include "ultima/ultima4/core/config.h"
|
|
#include "ultima/ultima4/core/debugger.h"
|
|
#include "ultima/ultima4/core/settings.h"
|
|
#include "ultima/ultima4/core/utils.h"
|
|
#include "ultima/ultima4/events/event_handler.h"
|
|
#include "ultima/ultima4/filesys/savegame.h"
|
|
#include "ultima/ultima4/game/game.h"
|
|
#include "ultima/ultima4/game/armor.h"
|
|
#include "ultima/ultima4/game/context.h"
|
|
#include "ultima/ultima4/game/death.h"
|
|
#include "ultima/ultima4/game/item.h"
|
|
#include "ultima/ultima4/views/menu.h"
|
|
#include "ultima/ultima4/game/creature.h"
|
|
#include "ultima/ultima4/game/moongate.h"
|
|
#include "ultima/ultima4/game/names.h"
|
|
#include "ultima/ultima4/game/person.h"
|
|
#include "ultima/ultima4/game/player.h"
|
|
#include "ultima/ultima4/game/portal.h"
|
|
#include "ultima/ultima4/game/spell.h"
|
|
#include "ultima/ultima4/views/stats.h"
|
|
#include "ultima/ultima4/game/script.h"
|
|
#include "ultima/ultima4/game/weapon.h"
|
|
#include "ultima/ultima4/gfx/imagemgr.h"
|
|
#include "ultima/ultima4/gfx/screen.h"
|
|
#include "ultima/ultima4/map/city.h"
|
|
#include "ultima/ultima4/map/annotation.h"
|
|
#include "ultima/ultima4/map/dungeon.h"
|
|
#include "ultima/ultima4/map/direction.h"
|
|
#include "ultima/ultima4/map/location.h"
|
|
#include "ultima/ultima4/map/mapmgr.h"
|
|
#include "ultima/ultima4/map/movement.h"
|
|
#include "ultima/ultima4/map/shrine.h"
|
|
#include "ultima/ultima4/map/tilemap.h"
|
|
#include "ultima/ultima4/map/tileset.h"
|
|
#include "ultima/ultima4/sound/music.h"
|
|
#include "ultima/ultima4/sound/sound.h"
|
|
#include "ultima/ultima4/views/dungeonview.h"
|
|
#include "ultima/ultima4/metaengine.h"
|
|
#include "common/savefile.h"
|
|
#include "common/system.h"
|
|
|
|
namespace Ultima {
|
|
namespace Ultima4 {
|
|
|
|
/*-----------------*/
|
|
/* Functions BEGIN */
|
|
|
|
/* main game functions */
|
|
void gameAdvanceLevel(PartyMember *player);
|
|
void gameInnHandler();
|
|
void gameLostEighth(Virtue virtue);
|
|
void gamePartyStarving();
|
|
|
|
void mixReagentsSuper();
|
|
|
|
/* action functions */
|
|
void wearArmor(int player = -1);
|
|
|
|
/* Functions END */
|
|
/*---------------*/
|
|
|
|
void gameSetViewMode(ViewMode newMode) {
|
|
g_context->_location->_viewMode = newMode;
|
|
}
|
|
|
|
void gameUpdateScreen() {
|
|
switch (g_context->_location->_viewMode) {
|
|
case VIEW_NORMAL:
|
|
g_screen->screenUpdate(&g_game->_mapArea, true, false);
|
|
break;
|
|
case VIEW_GEM:
|
|
g_screen->screenGemUpdate();
|
|
break;
|
|
case VIEW_RUNE:
|
|
g_screen->screenUpdate(&g_game->_mapArea, false, false);
|
|
break;
|
|
case VIEW_DUNGEON:
|
|
g_screen->screenUpdate(&g_game->_mapArea, true, false);
|
|
break;
|
|
case VIEW_DEAD:
|
|
g_screen->screenUpdate(&g_game->_mapArea, true, true);
|
|
break;
|
|
case VIEW_CODEX: /* the screen updates will be handled elsewhere */
|
|
break;
|
|
case VIEW_MIXTURES: /* still testing */
|
|
break;
|
|
default:
|
|
error("invalid view mode: %d", g_context->_location->_viewMode);
|
|
}
|
|
}
|
|
|
|
void gameSpellEffect(int spell, int player, Sound sound) {
|
|
int time;
|
|
Spell::SpecialEffects effect = Spell::SFX_INVERT;
|
|
|
|
if (player >= 0)
|
|
g_context->_stats->highlightPlayer(player);
|
|
|
|
time = settings._spellEffectSpeed * 800 / settings._gameCyclesPerSecond;
|
|
soundPlay(sound, false, time);
|
|
|
|
///The following effect multipliers are not accurate
|
|
switch (spell) {
|
|
case 'g': /* gate */
|
|
case 'r': /* resurrection */
|
|
break;
|
|
case 't': /* tremor */
|
|
effect = Spell::SFX_TREMOR;
|
|
break;
|
|
default:
|
|
/* default spell effect */
|
|
break;
|
|
}
|
|
|
|
switch (effect) {
|
|
case Spell::SFX_TREMOR:
|
|
case Spell::SFX_INVERT:
|
|
gameUpdateScreen();
|
|
g_game->_mapArea.highlight(0, 0, VIEWPORT_W * TILE_WIDTH, VIEWPORT_H * TILE_HEIGHT);
|
|
g_screen->update();
|
|
EventHandler::sleep(time);
|
|
g_game->_mapArea.unhighlight();
|
|
g_screen->update();
|
|
|
|
if (effect == Spell::SFX_TREMOR) {
|
|
gameUpdateScreen();
|
|
g_screen->update();
|
|
soundPlay(SOUND_RUMBLE, false);
|
|
g_screen->screenShake(8);
|
|
}
|
|
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
Common::String gameGetInput(int maxlen) {
|
|
g_screen->screenEnableCursor();
|
|
g_screen->screenShowCursor();
|
|
#ifdef IOS_ULTIMA4
|
|
U4IOS::IOSConversationHelper helper;
|
|
helper.beginConversation(U4IOS::UIKeyboardTypeDefault);
|
|
#endif
|
|
|
|
return ReadStringController::get(maxlen, TEXT_AREA_X + g_context->_col, TEXT_AREA_Y + g_context->_line);
|
|
}
|
|
|
|
int gameGetPlayer(bool canBeDisabled, bool canBeActivePlayer) {
|
|
int player;
|
|
if (g_ultima->_saveGame->_members <= 1) {
|
|
player = 0;
|
|
} else {
|
|
if (canBeActivePlayer && (g_context->_party->getActivePlayer() >= 0)) {
|
|
player = g_context->_party->getActivePlayer();
|
|
} else {
|
|
ReadPlayerController readPlayerController;
|
|
eventHandler->pushController(&readPlayerController);
|
|
player = readPlayerController.waitFor();
|
|
}
|
|
|
|
if (player == -1) {
|
|
g_screen->screenMessage("None\n");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
g_context->_col--;// display the selected character name, in place of the number
|
|
if ((player >= 0) && (player < 8)) {
|
|
g_screen->screenMessage("%s\n", g_ultima->_saveGame->_players[player]._name); //Write player's name after prompt
|
|
}
|
|
|
|
if (!canBeDisabled && g_context->_party->member(player)->isDisabled()) {
|
|
g_screen->screenMessage("%cDisabled!%c\n", FG_GREY, FG_WHITE);
|
|
return -1;
|
|
}
|
|
|
|
assertMsg(player < g_context->_party->size(), "player %d, but only %d members\n", player, g_context->_party->size());
|
|
return player;
|
|
}
|
|
|
|
Direction gameGetDirection() {
|
|
ReadDirController dirController;
|
|
|
|
g_screen->screenMessage("Dir?");
|
|
#ifdef IOS_ULTIMA4
|
|
U4IOS::IOSDirectionHelper directionPopup;
|
|
#endif
|
|
|
|
eventHandler->pushController(&dirController);
|
|
Direction dir = dirController.waitFor();
|
|
|
|
g_screen->screenMessage("\b\b\b\b");
|
|
|
|
if (dir == DIR_NONE) {
|
|
g_screen->screenMessage(" \n");
|
|
return dir;
|
|
} else {
|
|
g_screen->screenMessage("%s\n", getDirectionName(dir));
|
|
return dir;
|
|
}
|
|
}
|
|
|
|
bool fireAt(const Coords &coords, bool originAvatar) {
|
|
bool validObject = false;
|
|
bool hitsAvatar = false;
|
|
bool objectHit = false;
|
|
|
|
Object *obj = nullptr;
|
|
|
|
|
|
MapTile tile(g_context->_location->_map->_tileSet->getByName("miss_flash")->getId());
|
|
GameController::flashTile(coords, tile, 1);
|
|
|
|
obj = g_context->_location->_map->objectAt(coords);
|
|
Creature *m = dynamic_cast<Creature *>(obj);
|
|
|
|
if (obj && obj->getType() == Object::CREATURE && m && m->isAttackable())
|
|
validObject = true;
|
|
/* See if it's an object to be destroyed (the avatar cannot destroy the balloon) */
|
|
else if (obj &&
|
|
(obj->getType() == Object::UNKNOWN) &&
|
|
!(obj->getTile().getTileType()->isBalloon() && originAvatar))
|
|
validObject = true;
|
|
|
|
/* Does the cannon hit the avatar? */
|
|
if (coords == g_context->_location->_coords) {
|
|
validObject = true;
|
|
hitsAvatar = true;
|
|
}
|
|
|
|
if (validObject) {
|
|
/* always displays as a 'hit' though the object may not be destroyed */
|
|
|
|
/* Is is a pirate ship firing at US? */
|
|
if (hitsAvatar) {
|
|
GameController::flashTile(coords, "hit_flash", 4);
|
|
|
|
if (g_context->_transportContext == TRANSPORT_SHIP)
|
|
gameDamageShip(-1, 10);
|
|
else gameDamageParty(10, 25); /* party gets hurt between 10-25 damage */
|
|
}
|
|
/* inanimate objects get destroyed instantly, while creatures get a chance */
|
|
else if (obj->getType() == Object::UNKNOWN) {
|
|
GameController::flashTile(coords, "hit_flash", 4);
|
|
g_context->_location->_map->removeObject(obj);
|
|
}
|
|
|
|
/* only the avatar can hurt other creatures with cannon fire */
|
|
else if (originAvatar) {
|
|
GameController::flashTile(coords, "hit_flash", 4);
|
|
if (xu4_random(4) == 0) /* reverse-engineered from u4dos */
|
|
g_context->_location->_map->removeObject(obj);
|
|
}
|
|
|
|
objectHit = true;
|
|
}
|
|
|
|
return objectHit;
|
|
}
|
|
|
|
bool gamePeerCity(int city, void *data) {
|
|
Map *peerMap;
|
|
|
|
peerMap = mapMgr->get((MapId)(city + 1));
|
|
|
|
if (peerMap != nullptr) {
|
|
g_game->setMap(peerMap, 1, nullptr);
|
|
g_context->_location->_viewMode = VIEW_GEM;
|
|
g_game->_paused = true;
|
|
g_game->_pausedTimer = 0;
|
|
|
|
g_screen->screenDisableCursor();
|
|
#ifdef IOS_ULTIMA4
|
|
U4IOS::IOSConversationChoiceHelper continueHelper;
|
|
continueHelper.updateChoices(" ");
|
|
continueHelper.fullSizeChoicePanel();
|
|
#endif
|
|
ReadChoiceController::get("\015 \033");
|
|
|
|
g_game->exitToParentMap();
|
|
g_screen->screenEnableCursor();
|
|
g_game->_paused = false;
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void peer(bool useGem) {
|
|
if (useGem) {
|
|
if (g_ultima->_saveGame->_gems <= 0) {
|
|
g_screen->screenMessage("%cPeer at What?%c\n", FG_GREY, FG_WHITE);
|
|
return;
|
|
}
|
|
|
|
g_ultima->_saveGame->_gems--;
|
|
g_screen->screenMessage("Peer at a Gem!\n");
|
|
}
|
|
|
|
g_game->_paused = true;
|
|
g_game->_pausedTimer = 0;
|
|
g_screen->screenDisableCursor();
|
|
|
|
g_context->_location->_viewMode = VIEW_GEM;
|
|
#ifdef IOS_ULTIMA4
|
|
U4IOS::IOSConversationChoiceHelper continueHelper;
|
|
continueHelper.updateChoices(" ");
|
|
continueHelper.fullSizeChoicePanel();
|
|
#endif
|
|
ReadChoiceController::get("\015 \033");
|
|
|
|
g_screen->screenEnableCursor();
|
|
g_context->_location->_viewMode = VIEW_NORMAL;
|
|
g_game->_paused = false;
|
|
}
|
|
|
|
void gameCheckHullIntegrity() {
|
|
int i;
|
|
|
|
bool killAll = false;
|
|
/* see if the ship has sunk */
|
|
if ((g_context->_transportContext == TRANSPORT_SHIP) && g_ultima->_saveGame->_shipHull <= 0) {
|
|
g_screen->screenMessage("\nThy ship sinks!\n\n");
|
|
killAll = true;
|
|
}
|
|
|
|
|
|
if (!g_debugger->_collisionOverride && g_context->_transportContext == TRANSPORT_FOOT &&
|
|
g_context->_location->_map->tileTypeAt(g_context->_location->_coords, WITHOUT_OBJECTS)->isSailable() &&
|
|
!g_context->_location->_map->tileTypeAt(g_context->_location->_coords, WITH_GROUND_OBJECTS)->isShip() &&
|
|
!g_context->_location->_map->getValidMoves(g_context->_location->_coords, g_context->_party->getTransport())) {
|
|
g_screen->screenMessage("\nTrapped at sea without thy ship, thou dost drown!\n\n");
|
|
killAll = true;
|
|
}
|
|
|
|
if (killAll) {
|
|
for (i = 0; i < g_context->_party->size(); i++) {
|
|
g_context->_party->member(i)->setHp(0);
|
|
g_context->_party->member(i)->setStatus(STAT_DEAD);
|
|
}
|
|
|
|
g_screen->update();
|
|
g_death->start(5);
|
|
}
|
|
}
|
|
|
|
void gameFixupObjects(Map *map) {
|
|
int i;
|
|
Object *obj;
|
|
|
|
/* add stuff from the monster table to the map */
|
|
for (i = 0; i < MONSTERTABLE_SIZE; i++) {
|
|
SaveGameMonsterRecord *monster = &map->_monsterTable[i];
|
|
if (monster->_prevTile != 0) {
|
|
Coords coords(monster->_x, monster->_y);
|
|
|
|
// tile values stored in monsters.sav hardcoded to index into base tilemap
|
|
MapTile tile = g_tileMaps->get("base")->translate(monster->_tile),
|
|
oldTile = g_tileMaps->get("base")->translate(monster->_prevTile);
|
|
|
|
if (i < MONSTERTABLE_CREATURES_SIZE) {
|
|
const Creature *creature = creatureMgr->getByTile(tile);
|
|
/* make sure we really have a creature */
|
|
if (creature)
|
|
obj = map->addCreature(creature, coords);
|
|
else {
|
|
warning("A non-creature object was found in the creature section of the monster table. (Tile: %s)\n", tile.getTileType()->getName().c_str());
|
|
obj = map->addObject(tile, oldTile, coords);
|
|
}
|
|
} else {
|
|
obj = map->addObject(tile, oldTile, coords);
|
|
}
|
|
|
|
/* set the map for our object */
|
|
obj->setMap(map);
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32 gameTimeSinceLastCommand() {
|
|
return (g_system->getMillis() - g_context->_lastCommandTime) / 1000;
|
|
}
|
|
|
|
void gameCreatureAttack(Creature *m) {
|
|
Object *under;
|
|
const Tile *ground;
|
|
|
|
g_screen->screenMessage("\nAttacked by %s\n", m->getName().c_str());
|
|
|
|
/// TODO: CHEST: Make a user option to not make chests change battlefield
|
|
/// map (2 of 2)
|
|
ground = g_context->_location->_map->tileTypeAt(g_context->_location->_coords, WITH_GROUND_OBJECTS);
|
|
if (!ground->isChest()) {
|
|
ground = g_context->_location->_map->tileTypeAt(g_context->_location->_coords, WITHOUT_OBJECTS);
|
|
if ((under = g_context->_location->_map->objectAt(g_context->_location->_coords)) &&
|
|
under->getTile().getTileType()->isShip())
|
|
ground = under->getTile().getTileType();
|
|
}
|
|
|
|
CombatController *cc = new CombatController(CombatMap::mapForTile(ground, g_context->_party->getTransport().getTileType(), m));
|
|
cc->init(m);
|
|
cc->begin();
|
|
}
|
|
|
|
bool creatureRangeAttack(const Coords &coords, Creature *m) {
|
|
// int attackdelay = MAX_BATTLE_SPEED - settings.battleSpeed;
|
|
|
|
// Figure out what the ranged attack should look like
|
|
MapTile tile(g_context->_location->_map->_tileSet->getByName((m && !m->getWorldrangedtile().empty()) ?
|
|
m->getWorldrangedtile() :
|
|
"hit_flash")->getId());
|
|
|
|
GameController::flashTile(coords, tile, 1);
|
|
|
|
// See if the attack hits the avatar
|
|
Object *obj = g_context->_location->_map->objectAt(coords);
|
|
m = dynamic_cast<Creature *>(obj);
|
|
|
|
// Does the attack hit the avatar?
|
|
if (coords == g_context->_location->_coords) {
|
|
/* always displays as a 'hit' */
|
|
GameController::flashTile(coords, tile, 3);
|
|
|
|
/* FIXME: check actual damage from u4dos -- values here are guessed */
|
|
if (g_context->_transportContext == TRANSPORT_SHIP)
|
|
gameDamageShip(-1, 10);
|
|
else
|
|
gameDamageParty(10, 25);
|
|
|
|
return true;
|
|
}
|
|
// Destroy objects that were hit
|
|
else if (obj) {
|
|
if ((obj->getType() == Object::CREATURE && m && m->isAttackable()) ||
|
|
obj->getType() == Object::UNKNOWN) {
|
|
|
|
GameController::flashTile(coords, tile, 3);
|
|
g_context->_location->_map->removeObject(obj);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Std::vector<Coords> gameGetDirectionalActionPath(int dirmask, int validDirections, const Coords &origin, int minDistance, int maxDistance, bool (*blockedPredicate)(const Tile *tile), bool includeBlocked) {
|
|
Std::vector<Coords> path;
|
|
Direction dirx = DIR_NONE,
|
|
diry = DIR_NONE;
|
|
|
|
/* Figure out which direction the action is going */
|
|
if (DIR_IN_MASK(DIR_WEST, dirmask))
|
|
dirx = DIR_WEST;
|
|
else if (DIR_IN_MASK(DIR_EAST, dirmask))
|
|
dirx = DIR_EAST;
|
|
if (DIR_IN_MASK(DIR_NORTH, dirmask))
|
|
diry = DIR_NORTH;
|
|
else if (DIR_IN_MASK(DIR_SOUTH, dirmask))
|
|
diry = DIR_SOUTH;
|
|
|
|
/*
|
|
* try every tile in the given direction, up to the given range.
|
|
* Stop when the range is exceeded, or the action is blocked.
|
|
*/
|
|
|
|
MapCoords t_c(origin);
|
|
if ((dirx <= 0 || DIR_IN_MASK(dirx, validDirections)) &&
|
|
(diry <= 0 || DIR_IN_MASK(diry, validDirections))) {
|
|
for (int distance = 0; distance <= maxDistance;
|
|
distance++, t_c.move(dirx, g_context->_location->_map), t_c.move(diry, g_context->_location->_map)) {
|
|
|
|
if (distance >= minDistance) {
|
|
/* make sure our action isn't taking us off the map */
|
|
if (MAP_IS_OOB(g_context->_location->_map, t_c))
|
|
break;
|
|
|
|
const Tile *tile = g_context->_location->_map->tileTypeAt(t_c, WITH_GROUND_OBJECTS);
|
|
|
|
/* should we see if the action is blocked before trying it? */
|
|
if (!includeBlocked && blockedPredicate &&
|
|
!(*(blockedPredicate))(tile))
|
|
break;
|
|
|
|
path.push_back(t_c);
|
|
|
|
/* see if the action was blocked only if it did not succeed */
|
|
if (includeBlocked && blockedPredicate &&
|
|
!(*(blockedPredicate))(tile))
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
void gameDamageParty(int minDamage, int maxDamage) {
|
|
int i;
|
|
int damage;
|
|
int lastdmged = -1;
|
|
|
|
for (i = 0; i < g_context->_party->size(); i++) {
|
|
if (xu4_random(2) == 0) {
|
|
damage = ((minDamage >= 0) && (minDamage < maxDamage)) ?
|
|
xu4_random((maxDamage + 1) - minDamage) + minDamage :
|
|
maxDamage;
|
|
g_context->_party->member(i)->applyDamage(damage);
|
|
g_context->_stats->highlightPlayer(i);
|
|
lastdmged = i;
|
|
EventHandler::sleep(50);
|
|
}
|
|
}
|
|
|
|
g_screen->screenShake(1);
|
|
|
|
// Un-highlight the last player
|
|
if (lastdmged != -1) g_context->_stats->highlightPlayer(lastdmged);
|
|
}
|
|
|
|
void gameDamageShip(int minDamage, int maxDamage) {
|
|
int damage;
|
|
|
|
if (g_context->_transportContext == TRANSPORT_SHIP) {
|
|
damage = ((minDamage >= 0) && (minDamage < maxDamage)) ?
|
|
xu4_random((maxDamage + 1) - minDamage) + minDamage :
|
|
maxDamage;
|
|
|
|
g_screen->screenShake(1);
|
|
|
|
g_context->_party->damageShip(damage);
|
|
gameCheckHullIntegrity();
|
|
}
|
|
}
|
|
|
|
void gameSetActivePlayer(int player) {
|
|
if (player == -1) {
|
|
g_context->_party->setActivePlayer(-1);
|
|
g_screen->screenMessage("Set Active Player: None!\n");
|
|
} else if (player < g_context->_party->size()) {
|
|
g_screen->screenMessage("Set Active Player: %s!\n", g_context->_party->member(player)->getName().c_str());
|
|
if (g_context->_party->member(player)->isDisabled())
|
|
g_screen->screenMessage("Disabled!\n");
|
|
else
|
|
g_context->_party->setActivePlayer(player);
|
|
}
|
|
}
|
|
|
|
bool gameSpawnCreature(const Creature *m) {
|
|
int t, i;
|
|
const Creature *creature;
|
|
MapCoords coords = g_context->_location->_coords;
|
|
|
|
if (g_context->_location->_context & CTX_DUNGEON) {
|
|
/* FIXME: for some reason dungeon monsters aren't spawning correctly */
|
|
|
|
bool found = false;
|
|
MapCoords new_coords;
|
|
|
|
for (i = 0; i < 0x20; i++) {
|
|
new_coords = MapCoords(xu4_random(g_context->_location->_map->_width), xu4_random(g_context->_location->_map->_height), coords.z);
|
|
const Tile *tile = g_context->_location->_map->tileTypeAt(new_coords, WITH_OBJECTS);
|
|
if (tile->isCreatureWalkable()) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
return false;
|
|
|
|
coords = new_coords;
|
|
} else {
|
|
int dx = 0,
|
|
dy = 0;
|
|
bool ok = false;
|
|
int tries = 0;
|
|
static const int MAX_TRIES = 10;
|
|
|
|
while (!ok && (tries < MAX_TRIES)) {
|
|
dx = 7;
|
|
dy = xu4_random(7);
|
|
|
|
if (xu4_random(2))
|
|
dx = -dx;
|
|
if (xu4_random(2))
|
|
dy = -dy;
|
|
if (xu4_random(2)) {
|
|
t = dx;
|
|
dx = dy;
|
|
dy = t;
|
|
}
|
|
|
|
/* make sure we can spawn the creature there */
|
|
if (m) {
|
|
MapCoords new_coords = coords;
|
|
new_coords.move(dx, dy, g_context->_location->_map);
|
|
|
|
const Tile *tile = g_context->_location->_map->tileTypeAt(new_coords, WITHOUT_OBJECTS);
|
|
if ((m->sails() && tile->isSailable()) ||
|
|
(m->swims() && tile->isSwimable()) ||
|
|
(m->walks() && tile->isCreatureWalkable()) ||
|
|
(m->flies() && tile->isFlyable()))
|
|
ok = true;
|
|
else
|
|
tries++;
|
|
} else {
|
|
ok = true;
|
|
}
|
|
}
|
|
|
|
if (ok)
|
|
coords.move(dx, dy, g_context->_location->_map);
|
|
}
|
|
|
|
/* can't spawn creatures on top of the player */
|
|
if (coords == g_context->_location->_coords)
|
|
return false;
|
|
|
|
/* figure out what creature to spawn */
|
|
if (m)
|
|
creature = m;
|
|
else if (g_context->_location->_context & CTX_DUNGEON)
|
|
creature = creatureMgr->randomForDungeon(g_context->_location->_coords.z);
|
|
else
|
|
creature = creatureMgr->randomForTile(g_context->_location->_map->tileTypeAt(coords, WITHOUT_OBJECTS));
|
|
|
|
if (creature)
|
|
g_context->_location->_map->addCreature(creature, coords);
|
|
return true;
|
|
}
|
|
|
|
void gameDestroyAllCreatures(void) {
|
|
int i;
|
|
|
|
gameSpellEffect('t', -1, SOUND_MAGIC); /* same effect as tremor */
|
|
|
|
if (g_context->_location->_context & CTX_COMBAT) {
|
|
// Destroy all creatures in combat
|
|
for (i = 0; i < AREA_CREATURES; i++) {
|
|
CombatMap *cm = getCombatMap();
|
|
CreatureVector creatures = cm->getCreatures();
|
|
for (const auto *obj : creatures) {
|
|
if (obj->getId() != LORDBRITISH_ID)
|
|
cm->removeObject(obj);
|
|
}
|
|
}
|
|
} else {
|
|
// Destroy all creatures on the map
|
|
ObjectDeque::iterator current;
|
|
Map *map = g_context->_location->_map;
|
|
|
|
for (current = map->_objects.begin(); current != map->_objects.end();) {
|
|
Creature *m = dynamic_cast<Creature *>(*current);
|
|
|
|
if (m) {
|
|
// The skull does not destroy Lord British
|
|
if (m->getId() != LORDBRITISH_ID)
|
|
current = map->removeObject(current);
|
|
else
|
|
current++;
|
|
} else {
|
|
current++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Alert the guards! Really, the only one left should be LB himself :)
|
|
g_context->_location->_map->alertGuards();
|
|
}
|
|
|
|
// Colors assigned to reagents based on my best reading of them
|
|
// from the book of wisdom. Maybe we could use BOLD to distinguish
|
|
// the two grey and the two red reagents.
|
|
const int colors[] = {
|
|
FG_YELLOW, FG_GREY, FG_BLUE, FG_WHITE, FG_RED, FG_GREY, FG_GREEN, FG_RED
|
|
};
|
|
|
|
void showMixturesSuper(int page = 0) {
|
|
g_screen->screenTextColor(FG_WHITE);
|
|
for (int i = 0; i < 13; i++) {
|
|
|
|
const Spell *s = g_spells->getSpell(i + 13 * page);
|
|
int line = i + 8;
|
|
g_screen->screenTextAt(2, line, "%s", s->_name);
|
|
|
|
g_screen->screenTextAt(6, line, "%s", Common::String::format("%3d", g_ultima->_saveGame->_mixtures[i + 13 * page]).c_str());
|
|
|
|
g_screen->screenShowChar(32, 9, line);
|
|
int comp = s->_components;
|
|
for (int j = 0; j < 8; j++) {
|
|
g_screen->screenTextColor(colors[j]);
|
|
g_screen->screenShowChar(comp & (1 << j) ? CHARSET_BULLET : ' ', 10 + j, line);
|
|
}
|
|
g_screen->screenTextColor(FG_WHITE);
|
|
|
|
g_screen->screenTextAt(19, line, "%s", Common::String::format("%2d", s->_mp).c_str());
|
|
}
|
|
}
|
|
|
|
void mixReagentsSuper() {
|
|
g_screen->screenMessage("Mix reagents\n");
|
|
|
|
static int page = 0;
|
|
|
|
struct ReagentShop {
|
|
const char *name;
|
|
int price[6];
|
|
};
|
|
ReagentShop shops[] = {
|
|
{ "BuccDen", {6, 7, 9, 9, 9, 1} },
|
|
{ "Moonglo", {2, 5, 6, 3, 6, 9} },
|
|
{ "Paws", {3, 4, 2, 8, 6, 7} },
|
|
{ "SkaraBr", {2, 4, 9, 6, 4, 8} },
|
|
};
|
|
const int shopcount = sizeof(shops) / sizeof(shops[0]);
|
|
|
|
int oldlocation = g_context->_location->_viewMode;
|
|
g_context->_location->_viewMode = VIEW_MIXTURES;
|
|
g_screen->screenUpdate(&g_game->_mapArea, true, true);
|
|
|
|
g_screen->screenTextAt(16, 2, "%s", "<-Shops");
|
|
|
|
g_context->_stats->setView(StatsView(STATS_REAGENTS));
|
|
g_screen->screenTextColor(FG_PURPLE);
|
|
g_screen->screenTextAt(2, 7, "%s", "SPELL # Reagents MP");
|
|
|
|
for (int i = 0; i < shopcount; i++) {
|
|
int line = i + 1;
|
|
ReagentShop *s = &shops[i];
|
|
g_screen->screenTextColor(FG_WHITE);
|
|
g_screen->screenTextAt(2, line, "%s", s->name);
|
|
for (int j = 0; j < 6; j++) {
|
|
g_screen->screenTextColor(colors[j]);
|
|
g_screen->screenShowChar('0' + s->price[j], 10 + j, line);
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < 8; i++) {
|
|
g_screen->screenTextColor(colors[i]);
|
|
g_screen->screenShowChar('A' + i, 10 + i, 6);
|
|
}
|
|
|
|
bool done = false;
|
|
while (!done) {
|
|
showMixturesSuper(page);
|
|
g_screen->screenMessage("For Spell: ");
|
|
|
|
int spell = ReadChoiceController::get("abcdefghijklmnopqrstuvwxyz \033\n\r");
|
|
if (spell < 'a' || spell > 'z') {
|
|
g_screen->screenMessage("\nDone.\n");
|
|
done = true;
|
|
} else {
|
|
spell -= 'a';
|
|
const Spell *s = g_spells->getSpell(spell);
|
|
g_screen->screenMessage("%s\n", s->_name);
|
|
page = (spell >= 13);
|
|
showMixturesSuper(page);
|
|
|
|
// how many can we mix?
|
|
int mixQty = 99 - g_ultima->_saveGame->_mixtures[spell];
|
|
int ingQty = 99;
|
|
int comp = s->_components;
|
|
for (int i = 0; i < 8; i++) {
|
|
if (comp & 1 << i) {
|
|
int reagentQty = g_ultima->_saveGame->_reagents[i];
|
|
if (reagentQty < ingQty)
|
|
ingQty = reagentQty;
|
|
}
|
|
}
|
|
g_screen->screenMessage("You can make %d.\n", (mixQty > ingQty) ? ingQty : mixQty);
|
|
g_screen->screenMessage("How many? ");
|
|
|
|
int howmany = ReadIntController::get(2, TEXT_AREA_X + g_context->_col, TEXT_AREA_Y + g_context->_line);
|
|
|
|
if (howmany == 0) {
|
|
g_screen->screenMessage("\nNone mixed!\n");
|
|
} else if (howmany > mixQty) {
|
|
g_screen->screenMessage("\n%cYou cannot mix that much more of that spell!%c\n", FG_GREY, FG_WHITE);
|
|
} else if (howmany > ingQty) {
|
|
g_screen->screenMessage("\n%cYou don't have enough reagents to mix %d spells!%c\n", FG_GREY, howmany, FG_WHITE);
|
|
} else {
|
|
g_ultima->_saveGame->_mixtures[spell] += howmany;
|
|
for (int i = 0; i < 8; i++) {
|
|
if (comp & 1 << i) {
|
|
g_ultima->_saveGame->_reagents[i] -= howmany;
|
|
}
|
|
}
|
|
g_screen->screenMessage("\nSuccess!\n\n");
|
|
}
|
|
}
|
|
g_context->_stats->setView(StatsView(STATS_REAGENTS));
|
|
}
|
|
|
|
g_context->_location->_viewMode = oldlocation;
|
|
return;
|
|
}
|
|
|
|
} // End of namespace Ultima4
|
|
} // End of namespace Ultima
|