Initial commit
This commit is contained in:
269
engines/ultima/ultima1/widgets/dungeon_monster.cpp
Normal file
269
engines/ultima/ultima1/widgets/dungeon_monster.cpp
Normal file
@@ -0,0 +1,269 @@
|
||||
/* 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/ultima1/widgets/dungeon_monster.h"
|
||||
#include "ultima/ultima1/maps/map.h"
|
||||
#include "ultima/ultima1/maps/map_dungeon.h"
|
||||
#include "ultima/ultima1/maps/map_tile.h"
|
||||
#include "ultima/ultima1/core/resources.h"
|
||||
#include "ultima/ultima1/game.h"
|
||||
#include "ultima/shared/core/utils.h"
|
||||
#include "ultima/shared/early/ultima_early.h"
|
||||
|
||||
namespace Ultima {
|
||||
namespace Ultima1 {
|
||||
namespace Widgets {
|
||||
|
||||
DungeonMonster::DungeonMonster(Ultima1Game *game, Maps::MapBase *map, DungeonWidgetId monsterId,
|
||||
int hitPoints, const Point &pt) :
|
||||
DungeonWidget(game, map, monsterId, pt), Shared::Maps::DungeonCreature(game, map, hitPoints) {
|
||||
_name = getGame()->_res->DUNGEON_MONSTER_NAMES[_widgetId];
|
||||
}
|
||||
|
||||
DungeonMonster::DungeonMonster(Ultima1Game *game, Maps::MapBase *map) :
|
||||
DungeonWidget(game, map), Shared::Maps::DungeonCreature(game, map) {
|
||||
}
|
||||
|
||||
void DungeonMonster::synchronize(Common::Serializer &s) {
|
||||
DungeonWidget::synchronize(s);
|
||||
Creature::synchronize(s);
|
||||
s.syncAsUint16LE(_widgetId);
|
||||
|
||||
if (s.isLoading())
|
||||
_name = getGame()->_res->DUNGEON_MONSTER_NAMES[_widgetId];
|
||||
}
|
||||
|
||||
bool DungeonMonster::isBlockingView() const {
|
||||
return _widgetId != MONSTER_INVISIBLE_SEEKER && _widgetId != MONSTER_MIMIC
|
||||
&& _widgetId != MONSTER_GELATINOUS_CUBE;
|
||||
}
|
||||
|
||||
void DungeonMonster::draw(Shared::DungeonSurface &s, uint distance) {
|
||||
if (distance < 5) {
|
||||
if (_widgetId == MONSTER_GELATINOUS_CUBE) {
|
||||
s.drawWall(distance);
|
||||
s.drawLeftEdge(distance);
|
||||
s.drawRightEdge(distance);
|
||||
} else {
|
||||
DungeonWidget::draw(s, distance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DungeonMonster::update(bool isPreUpdate) {
|
||||
assert(isPreUpdate);
|
||||
Point playerPos = _map->_playerWidget->_position;
|
||||
Point delta = playerPos - _position;
|
||||
int distance = ABS(delta.x) + ABS(delta.y);
|
||||
|
||||
if (distance == 1) {
|
||||
attackParty();
|
||||
} else if (distance < 8) {
|
||||
movement();
|
||||
}
|
||||
}
|
||||
|
||||
void DungeonMonster::movement() {
|
||||
if (attackDistance())
|
||||
// Dungeon monsters don't move if they're already in attack range
|
||||
return;
|
||||
|
||||
Point playerPos = _map->_playerWidget->_position;
|
||||
Point diff = playerPos - _position;
|
||||
|
||||
if (diff.x != 0 && canMoveTo(Point(_position.x + SGN(diff.x), _position.y)))
|
||||
_position.x += SGN(diff.x);
|
||||
else if (diff.y != 0 && canMoveTo(Point(_position.x, _position.y + SGN(diff.y))))
|
||||
_position.y += SGN(diff.y);
|
||||
}
|
||||
|
||||
Shared::Maps::MapWidget::CanMove DungeonMonster::canMoveTo(const Point &destPos) {
|
||||
Shared::Maps::MapWidget::CanMove result = MapWidget::canMoveTo(destPos);
|
||||
if (result != UNSET)
|
||||
return result;
|
||||
|
||||
return DungeonMonster::canMoveTo(_map, this, destPos);
|
||||
}
|
||||
|
||||
Shared::Maps::MapWidget::CanMove DungeonMonster::canMoveTo(Shared::Maps::MapBase *map, MapWidget *widget, const Point &destPos) {
|
||||
// Get the details of the position
|
||||
Shared::Maps::MapTile currTile, destTile;
|
||||
|
||||
map->getTileAt(map->getPosition(), &currTile);
|
||||
map->getTileAt(destPos, &destTile);
|
||||
|
||||
// Can't move onto certain dungeon tile types
|
||||
if (destTile._isWall || destTile._isSecretDoor || destTile._isBeams)
|
||||
return NO;
|
||||
|
||||
// Can't move to directly adjoining doorway cells (they'd be in parallel to each other, not connected)
|
||||
if (destTile._isDoor && currTile._isDoor)
|
||||
return NO;
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
void DungeonMonster::attackParty() {
|
||||
Ultima1Game *game = static_cast<Ultima1Game *>(_game);
|
||||
Point playerPos = _map->_playerWidget->_position;
|
||||
//Point delta = playerPos - _position;
|
||||
Shared::Character &c = *_game->_party;
|
||||
uint threshold, damage;
|
||||
bool isHit = true;
|
||||
|
||||
// Get tile details for both the player and the attacking creature
|
||||
Maps::U1MapTile playerTile,creatureTile;
|
||||
_map->getTileAt(playerPos, &playerTile);
|
||||
_map->getTileAt(_position, &creatureTile);
|
||||
|
||||
if (playerTile._isBeams || (creatureTile._isDoor && (playerTile._isDoor || playerTile._isWall || playerTile._isSecretDoor)))
|
||||
return;
|
||||
|
||||
// Write attack line
|
||||
addInfoMsg(Common::String::format(game->_res->ATTACKED_BY, _name.c_str()));
|
||||
_game->playFX(3);
|
||||
|
||||
threshold = (c._stamina / 2) + (c._equippedArmour * 8) + 56;
|
||||
|
||||
if (_game->getRandomNumber(1, 255) > threshold) {
|
||||
threshold = _game->getRandomNumber(1, 255);
|
||||
damage = (_widgetId * _widgetId) + _map->getLevel();
|
||||
if (damage > 255) {
|
||||
damage = _game->getRandomNumber(_widgetId + 1, 255);
|
||||
}
|
||||
|
||||
if (_widgetId == MONSTER_GELATINOUS_CUBE && c.isArmourEquipped()) {
|
||||
addInfoMsg(game->_res->ARMOR_DESTROYED);
|
||||
c._armour[c._equippedArmour]->decrQuantity();
|
||||
c.removeArmour();
|
||||
isHit = false;
|
||||
} else if (_widgetId == MONSTER_GREMLIN) {
|
||||
addInfoMsg(game->_res->GREMLIN_STOLE);
|
||||
c._food /= 2;
|
||||
isHit = false;
|
||||
} else if (_widgetId == MONSTER_MIND_WHIPPER && threshold < 128) {
|
||||
addInfoMsg(game->_res->MENTAL_ATTACK);
|
||||
c._intelligence = (c._intelligence / 2) + 5;
|
||||
isHit = false;
|
||||
} else if (_widgetId == MONSTER_THIEF) {
|
||||
// Thief will steal the first spare weapon player has that isn't equipped
|
||||
for (int weaponNum = 1; weaponNum < (int)c._weapons.size(); ++weaponNum) {
|
||||
if (weaponNum != c._equippedWeapon && !c._weapons[weaponNum]->empty()) {
|
||||
// TODO: May need to worry about word wrapping long line
|
||||
addInfoMsg(Common::String::format(game->_res->THIEF_STOLE,
|
||||
|
||||
Shared::isVowel(c._weapons[weaponNum]->_longName.firstChar()) ? game->_res->AN : game->_res->A
|
||||
));
|
||||
c._weapons[weaponNum]->decrQuantity();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isHit) {
|
||||
addInfoMsg(Common::String::format("%s %2d %s", game->_res->HIT, damage, game->_res->DAMAGE));
|
||||
c._hitPoints -= damage;
|
||||
}
|
||||
} else {
|
||||
addInfoMsg(game->_res->MISSED);
|
||||
}
|
||||
}
|
||||
|
||||
void DungeonMonster::attackMonster(uint effectNum, uint agility, uint damage) {
|
||||
Ultima1Game *game = static_cast<Ultima1Game *>(_game);
|
||||
Maps::MapDungeon *map = static_cast<Maps::MapDungeon *>(_map);
|
||||
Point currPos = map->getPosition();
|
||||
Maps::U1MapTile playerTile, monsTile;
|
||||
map->getTileAt(currPos, &playerTile);
|
||||
map->getTileAt(_position, &monsTile);
|
||||
|
||||
bool flag = true;
|
||||
if (!playerTile._isDoor) {
|
||||
if (!monsTile._isHallway && !monsTile._isLadderUp && !monsTile._isLadderDown)
|
||||
flag = false;
|
||||
}
|
||||
|
||||
if (game->getRandomNumber(1, 100) <= agility && !playerTile._isWall && !playerTile._isSecretDoor
|
||||
&& !playerTile._isBeams && flag) {
|
||||
// Play effect and add hit message
|
||||
game->playFX(effectNum);
|
||||
if (damage != ITS_OVER_9000)
|
||||
addInfoMsg(Common::String::format("%s ", game->_res->HIT), false);
|
||||
|
||||
if ((int)damage < _hitPoints) {
|
||||
addInfoMsg(Common::String::format("%u %s!", damage, game->_res->DAMAGE));
|
||||
_hitPoints -= damage;
|
||||
} else {
|
||||
addInfoMsg(Common::String::format("%s %s", _name.c_str(),
|
||||
damage == ITS_OVER_9000 ? game->_res->DESTROYED : game->_res->KILLED));
|
||||
monsterDead();
|
||||
|
||||
// Give some treasure
|
||||
uint amount = game->getRandomNumber(2, map->getLevel() * 3 + (uint)_widgetId + 10);
|
||||
addInfoMsg(game->_res->THOU_DOST_FIND);
|
||||
game->giveTreasure(amount, 0);
|
||||
|
||||
// Give experience
|
||||
Shared::Character &c = *game->_party;
|
||||
uint experience = game->getRandomNumber(2, map->getLevel() * map->getLevel() + 10);
|
||||
c._experience += experience;
|
||||
map->_dungeonExitHitPoints = MIN(map->_dungeonExitHitPoints + experience * 2, 9999U);
|
||||
|
||||
// Delete the monster and create a new one
|
||||
map->removeWidget(this);
|
||||
map->spawnMonster();
|
||||
}
|
||||
} else {
|
||||
// Attack missed
|
||||
addInfoMsg(game->_res->MISSED);
|
||||
}
|
||||
}
|
||||
|
||||
void DungeonMonster::monsterDead() {
|
||||
int index;
|
||||
switch (_widgetId) {
|
||||
case MONSTER_BALRON:
|
||||
index = 8;
|
||||
break;
|
||||
case MONSTER_CARRION_CREEPER:
|
||||
index = 4;
|
||||
break;
|
||||
case MONSTER_LICH:
|
||||
index = 6;
|
||||
break;
|
||||
case MONSTER_GELATINOUS_CUBE:
|
||||
index = 2;
|
||||
break;
|
||||
default:
|
||||
index = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (index) {
|
||||
// Mark monster-based quests as complete if in progress
|
||||
Ultima1Game *game = static_cast<Ultima1Game *>(_game);
|
||||
game->_quests[8 - index].complete();
|
||||
}
|
||||
}
|
||||
|
||||
} // End of namespace Widgets
|
||||
} // End of namespace Ultima1
|
||||
} // End of namespace Ultima
|
||||
Reference in New Issue
Block a user