scummvm/engines/lure/fights.cpp
2021-12-26 18:48:43 +01:00

619 lines
17 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 "lure/fights.h"
#include "lure/luredefs.h"
#include "lure/game.h"
#include "lure/lure.h"
#include "lure/res.h"
#include "lure/room.h"
#include "lure/sound.h"
namespace Lure {
// Three records containing initial states for player, pig, and Skorl
const FighterRecord initialFighterList[3] = {
{0x23C, 0x440, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0},
{0, 0x441, 0x1092, 0, 3, 0, 0, 0, 0xB94, 8, 0xA34, 0x8D4, 0xD06, 0,
0, 0, 0, 0, 0xDDC, PLAYER_ID},
{0, 0x446, 0x1092, 0, 3, 0, 0, 0, 0xB94, 8, 0xA34, 0x8D4, 0xD06, 0,
0, 0, 0, 0, 0xDDC, PLAYER_ID}
};
FightsManager *int_fights = nullptr;
FightsManager::FightsManager() : _rnd(LureEngine::getReference().rnd()) {
int_fights = this;
_fightData = nullptr;
_mouseFlags = 0;
_keyDown = KS_UP;
reset();
}
FightsManager::~FightsManager() {
// Release the fight data
delete _fightData;
}
FightsManager &FightsManager::getReference() {
return *int_fights;
}
FighterRecord &FightsManager::getDetails(uint16 hotspotId) {
if (hotspotId == PLAYER_ID) return _fighterList[0];
else if (hotspotId == PIG_ID) return _fighterList[1];
else if (hotspotId == SKORL_FIGHTER_ID) return _fighterList[2];
error("Unknown NPC %d attempted to fight", hotspotId);
}
// Sets up the data for the pig fight in the cave
void FightsManager::setupPigFight() {
Resources &res = Resources::getReference();
Hotspot *player = res.getActiveHotspot(PLAYER_ID);
player->setSkipFlag(false);
player->resource()->colorOffset = 16;
player->setTickProc(PLAYER_FIGHT_TICK_PROC_ID);
player->setSize(48, 53);
player->setAnimationIndex(PLAYER_FIGHT_ANIM_INDEX);
player->resource()->width = 48;
player->resource()->height = 53;
player->setOccupied(false);
player->setPosition(262, 94);
FighterRecord &rec = getDetails(PLAYER_ID);
rec.fwhits = 0;
rec.fwtrue_x = 262;
rec.fwtrue_y = 53;
rec.fwseq_ad = FIGHT_PLAYER_INIT;
rec.fwenemy_ad = PIG_ID;
}
void FightsManager::setupSkorlFight() {
Resources &res = Resources::getReference();
Hotspot *player = res.getActiveHotspot(PLAYER_ID);
FighterRecord &rec = getDetails(PLAYER_ID);
setupPigFight();
rec.fwenemy_ad = SKORL_FIGHTER_ID;
rec.fwweapon = 0x445;
rec.fwtrue_x = 282;
rec.fwtrue_y = 136;
player->setPosition(282, 136);
player->resource()->colorOffset = 96;
}
bool FightsManager::isFighting() {
FighterRecord &rec = getDetails(PLAYER_ID);
return rec.fwhits == 0;
}
void FightsManager::fightLoop() {
LureEngine &engine = LureEngine::getReference();
Resources &res = Resources::getReference();
Game &game = Game::getReference();
Room &room = Room::getReference();
FighterRecord &playerFight = getDetails(PLAYER_ID);
uint32 timerVal = g_system->getMillis();
// Loop for the duration of the battle
while (!engine.shouldQuit() && (playerFight.fwhits != GENERAL_MAGIC_ID)) {
checkEvents();
if (g_system->getMillis() > timerVal + GAME_FRAME_DELAY) {
timerVal = g_system->getMillis();
game.tick();
room.update();
res.delayList().tick();
}
Screen::getReference().update();
g_system->delayMillis(10);
}
}
void FightsManager::saveToStream(Common::WriteStream *stream) {
for (int fighterCtr = 0; fighterCtr < 3; ++fighterCtr) {
FighterRecord &rec = _fighterList[fighterCtr];
stream->writeUint16LE(rec.fwseq_no);
stream->writeUint16LE(rec.fwseq_ad);
stream->writeUint16LE(rec.fwdist);
stream->writeUint16LE(rec.fwwalk_roll);
stream->writeUint16LE(rec.fwmove_number);
stream->writeUint16LE(rec.fwhits);
}
}
void FightsManager::loadFromStream(Common::ReadStream *stream) {
reset();
for (int fighterCtr = 0; fighterCtr < 3; ++fighterCtr) {
FighterRecord &rec = _fighterList[fighterCtr];
rec.fwseq_no = stream->readUint16LE();
rec.fwseq_ad = stream->readUint16LE();
rec.fwdist = stream->readUint16LE();
rec.fwwalk_roll = stream->readUint16LE();
rec.fwmove_number = stream->readUint16LE();
rec.fwhits = stream->readUint16LE();
}
}
void FightsManager::reset() {
_fighterList[0] = initialFighterList[0];
_fighterList[1] = initialFighterList[1];
_fighterList[2] = initialFighterList[2];
}
const CursorType moveList[] = {CURSOR_LEFT_ARROW, CURSOR_FIGHT_UPPER,
CURSOR_FIGHT_MIDDLE, CURSOR_FIGHT_LOWER, CURSOR_RIGHT_ARROW};
struct KeyMapping {
Common::KeyCode keycode;
uint8 moveNumber;
};
const KeyMapping keyList[] = {
{Common::KEYCODE_LEFT, 10}, {Common::KEYCODE_RIGHT, 14},
{Common::KEYCODE_KP7, 11}, {Common::KEYCODE_KP4, 12}, {Common::KEYCODE_KP1, 13},
{Common::KEYCODE_KP9, 6}, {Common::KEYCODE_KP6, 7}, {Common::KEYCODE_KP3, 8},
{Common::KEYCODE_INVALID, 0}};
void FightsManager::checkEvents() {
LureEngine &engine = LureEngine::getReference();
Events &events = Events::getReference();
Mouse &mouse = Mouse::getReference();
FighterRecord &rec = getDetails(PLAYER_ID);
Hotspot *player = Resources::getReference().getActiveHotspot(PLAYER_ID);
int moveNumber = 0;
while ((moveNumber == 0) && events.pollEvent()) {
if (events.type() == Common::EVENT_KEYDOWN) {
switch (events.event().kbd.keycode) {
case Common::KEYCODE_ESCAPE:
engine.quitGame();
return;
default:
// Scan through the mapping list for a move for the keypress
const KeyMapping *keyPtr = &keyList[0];
while ((keyPtr->keycode != Common::KEYCODE_INVALID) &&
(keyPtr->keycode != events.event().kbd.keycode))
++keyPtr;
if (keyPtr->keycode != Common::KEYCODE_INVALID) {
moveNumber = keyPtr->moveNumber;
_keyDown = KS_KEYDOWN_1;
}
}
} else if (events.type() == Common::EVENT_KEYUP) {
_keyDown = KS_UP;
} else if (events.type() == Common::EVENT_MOUSEMOVE) {
Common::Point mPos = events.event().mouse;
if (mPos.x < rec.fwtrue_x - 12)
mouse.setCursorNum(CURSOR_LEFT_ARROW);
else if (mPos.x > rec.fwtrue_x + player->width())
mouse.setCursorNum(CURSOR_RIGHT_ARROW);
else if (mPos.y < player->y() + 4)
mouse.setCursorNum(CURSOR_FIGHT_UPPER);
else if (mPos.y < player->y() + 38)
mouse.setCursorNum(CURSOR_FIGHT_MIDDLE);
else
mouse.setCursorNum(CURSOR_FIGHT_LOWER);
} else if ((events.type() == Common::EVENT_LBUTTONDOWN) ||
(events.type() == Common::EVENT_RBUTTONDOWN) ||
(events.type() == Common::EVENT_LBUTTONUP) ||
(events.type() == Common::EVENT_RBUTTONUP)) {
_mouseFlags = 0;
if (events.type() == Common::EVENT_LBUTTONDOWN) ++_mouseFlags;
if (events.type() == Common::EVENT_RBUTTONDOWN) _mouseFlags += 2;
}
}
if (_keyDown == KS_KEYDOWN_2) return;
// Get the correct base index for the move
while ((moveNumber < 5) && (moveList[moveNumber] != mouse.getCursorNum()))
++moveNumber;
if (moveNumber < 5) {
if (_mouseFlags == 1)
// Left mouse
moveNumber += 10;
else if (_mouseFlags == 2)
// Right mouse
moveNumber += 5;
}
rec.fwmove_number = moveNumber;
if (_keyDown == KS_KEYDOWN_1)
_keyDown = KS_KEYDOWN_2;
if (rec.fwmove_number >= 5)
debugC(ERROR_INTERMEDIATE, kLureDebugFights,
"Player fight move number=%d", rec.fwmove_number);
}
void FightsManager::fighterAnimHandler(Hotspot &h) {
FighterRecord &fighter = getDetails(h.hotspotId());
FighterRecord &opponent = getDetails(fighter.fwenemy_ad);
FighterRecord &player = getDetails(PLAYER_ID);
fetchFighterDistance(fighter, opponent);
if (fighter.fwseq_ad) {
fightHandler(h, fighter.fwseq_ad);
return;
}
uint16 seqNum = 0;
if (fighter.fwdist != FIGHT_DISTANCE) {
seqNum = getFighterMove(fighter, fighter.fwnot_near);
} else {
uint16 offset = (fighter.fwhits * fighter.fwdef_len) + fighter.fwdefend_adds + 4;
// Scan for the given sequence
uint16 v = getWord(offset);
while ((v != 0) && (v != player.fwseq_no)) {
offset += 4;
v = getWord(offset);
}
if (v == 0) {
// No sequence match found
seqNum = getFighterMove(fighter, fighter.fwattack_table);
} else {
v = getWord(offset + 2);
seqNum = getFighterMove(fighter, fighter.fwdefend_table);
if (seqNum == 0)
seqNum = getFighterMove(fighter, fighter.fwattack_table);
else if (seqNum == 0xff)
seqNum = v;
}
}
// Set the sequence and pass onto the fight handler
fighter.fwseq_no = seqNum;
fighter.fwseq_ad = getWord(FIGHT_TBL_1 + (seqNum << 1));
}
void FightsManager::playerAnimHandler(Hotspot &h) {
FighterRecord &fighter = getDetails(h.hotspotId());
fightHandler(h, fighter.fwseq_ad);
}
void FightsManager::fightHandler(Hotspot &h, uint16 moveOffset) {
Resources &res = Resources::getReference();
FighterRecord &fighter = getDetails(h.hotspotId());
FighterRecord &opponent = getDetails(fighter.fwenemy_ad);
uint16 v1, v2;
bool breakFlag = false;
while (!breakFlag) {
if (moveOffset == 0) {
// Player is doing nothing, so check the move number
moveOffset = getWord(FIGHT_PLAYER_MOVE_TABLE + (fighter.fwmove_number << 1));
debugC(ERROR_DETAILED, kLureDebugFights,
"Hotspot %xh fight move=%d, new offset=%xh",
h.hotspotId(), fighter.fwmove_number, moveOffset);
if (moveOffset == 0)
return;
fighter.fwseq_no = fighter.fwmove_number;
fighter.fwseq_ad = moveOffset;
}
uint16 moveValue = getWord(moveOffset);
debugC(ERROR_DETAILED, kLureDebugFights,
"Hotspot %xh script offset=%xh value=%xh",
h.hotspotId(), moveOffset, moveValue);
moveOffset += sizeof(uint16);
if ((moveValue & 0x8000) == 0) {
// Set frame to specified number
h.setFrameNumber(moveValue);
// Set the new fighter position
int16 newX, newY;
newX = h.x() + (int16)getWord(moveOffset);
if (newX < 32) newX = 32;
if (newX > 240) newX = 240;
newY = h.y() + (int16)getWord(moveOffset + 2);
h.setPosition(newX, newY);
if (fighter.fwweapon != 0) {
Hotspot *weaponHotspot = res.getActiveHotspot(fighter.fwweapon);
assert(weaponHotspot);
uint16 newFrameNumber = getWord(moveOffset + 4);
int16 xChange = (int16)getWord(moveOffset + 6);
int16 yChange = (int16)getWord(moveOffset + 8);
weaponHotspot->setFrameNumber(newFrameNumber);
weaponHotspot->setPosition(h.x() + xChange, h.y() + yChange);
}
moveOffset += 5 * sizeof(uint16);
fighter.fwseq_ad = moveOffset;
return;
}
switch (moveValue) {
case 0xFFFA:
// Walk left
if ((fighter.fwmove_number == 5) || (fighter.fwmove_number == 10)) {
if (h.x() < 32) {
breakFlag = true;
} else {
h.setPosition(h.x() - 4, h.y());
fighter.fwtrue_x = h.x();
if (fetchFighterDistance(fighter, opponent) < FIGHT_DISTANCE) {
h.setPosition(h.x() + 4, h.y());
fighter.fwtrue_x += 4;
breakFlag = true;
} else {
removeWeapon(fighter.fwweapon);
fighter.fwtrue_x = h.x();
fighter.fwtrue_y = h.y();
uint16 frameNum = (fighter.fwwalk_roll == 7) ? 0 :
fighter.fwwalk_roll + 1;
fighter.fwwalk_roll = frameNum;
fighter.fwseq_ad = moveOffset;
h.setFrameNumber(frameNum);
return;
}
}
} else {
// Signal to start a new action
moveOffset = 0;
}
break;
case 0xFFF9:
// Walk right
if ((fighter.fwmove_number == 9) || (fighter.fwmove_number == 14)) {
if (h.x() >= 240) {
breakFlag = true;
} else {
removeWeapon(fighter.fwweapon);
h.setPosition(h.x() + 4, h.y());
fighter.fwtrue_x = h.x();
fighter.fwtrue_y = h.y();
fighter.fwwalk_roll = (fighter.fwwalk_roll == 0) ? 7 :
fighter.fwwalk_roll - 1;
fighter.fwseq_ad = moveOffset;
h.setFrameNumber(fighter.fwwalk_roll);
return;
}
} else {
// Signal to start a new action
moveOffset = 0;
}
break;
case 0xFFEB:
// Enemy right
removeWeapon(fighter.fwweapon);
h.setPosition(h.x() + 4, h.y());
fighter.fwtrue_x = h.x();
if (fetchFighterDistance(fighter, opponent) < FIGHT_DISTANCE) {
h.setPosition(h.x() - 4, h.y());
fighter.fwtrue_x -= 4;
fighter.fwseq_ad = 0;
h.setFrameNumber(8);
} else {
h.setFrameNumber(getWord(moveOffset));
moveOffset += sizeof(uint16);
fighter.fwseq_ad = moveOffset;
}
return;
case 0xFFFB:
// End of sequence
breakFlag = true;
break;
case 0xFFF8:
// Set fight address
moveOffset = getWord(moveOffset);
break;
case 0xFFFF:
case 0xFFFE:
if (moveValue == 0xffff)
// Set the animation record
h.setAnimation(getWord(moveOffset));
else
// New set animation record
h.setAnimation(getWord(fighter.fwheader_list + (getWord(moveOffset) << 1)));
h.setFrameNumber(0);
moveOffset += sizeof(uint16);
break;
case 0xFFF7:
// On hold
if (getWord(moveOffset) == fighter.fwmove_number)
moveOffset = getWord(moveOffset + 2);
else
moveOffset += 2 * sizeof(uint16);
break;
case 0xFFF6:
// Not hold
if (getWord(moveOffset) == fighter.fwmove_number)
moveOffset += 2 * sizeof(uint16);
else
moveOffset = getWord(moveOffset + 2);
break;
case 0xFFF4:
// End sequence
fighter.fwseq_no = 0;
break;
case 0xFFF2:
// Set defend
fighter.fwblocking = getWord(moveOffset);
moveOffset += sizeof(uint16);
break;
case 0xFFF1:
// If blocking
v1 = getWord(moveOffset);
v2 = getWord(moveOffset + 2);
moveOffset += 2 * sizeof(uint16);
if (v1 == opponent.fwblocking) {
Sound.addSound(42);
moveOffset = v2;
}
break;
case 0xFFF0:
// Check hit
v1 = getWord(moveOffset);
moveOffset += sizeof(uint16);
if (fighter.fwdist <= FIGHT_DISTANCE) {
if (h.hotspotId() == PLAYER_ID) {
// Player hits opponent
Sound.addSound(52);
if (opponent.fwhits != 5) {
opponent.fwseq_ad = v1;
if (++opponent.fwhit_value != opponent.fwhit_rate) {
opponent.fwhit_value = 0;
if (++opponent.fwhits == 5)
opponent.fwseq_ad = opponent.fwdie_seq;
}
}
} else {
// Opponent hit player
Sound.addSound(37);
opponent.fwseq_ad = v1;
if (++opponent.fwhits == 10) {
// Player has been killed
fighter.fwhits = 10;
opponent.fwseq_ad = FIGHT_PLAYER_DIES;
Sound.addSound(36);
}
}
}
break;
case 0xFFEF:
// Save co-ordinates
fighter.fwtrue_x = h.x();
fighter.fwtrue_y = h.y();
break;
case 0xFFEE:
// Restore co-ordinates
h.setPosition(fighter.fwtrue_x, fighter.fwtrue_y);
break;
case 0xFFED:
// End of game
getDetails(PLAYER_ID).fwhits = GENERAL_MAGIC_ID;
Game::getReference().setState(GS_RESTORE_RESTART);
return;
case 0xFFEC:
// Enemy has been killed
enemyKilled();
break;
case 0xFFEA:
// Fight sound
Sound.addSound(getWord(moveOffset) & 0xff);
moveOffset += sizeof(uint16);
break;
default:
error("Unknown fight command %xh", moveValue);
}
}
fighter.fwseq_no = 0;
fighter.fwseq_ad = 0;
if (h.hotspotId() == PLAYER_ID)
_mouseFlags = 0;
}
uint16 FightsManager::fetchFighterDistance(FighterRecord &f1, FighterRecord &f2) {
f1.fwdist = ABS(f1.fwtrue_x - f2.fwtrue_x) & 0xFFF8;
return f1.fwdist;
}
void FightsManager::enemyKilled() {
Resources &res = Resources::getReference();
Hotspot *playerHotspot = res.getActiveHotspot(PLAYER_ID);
FighterRecord &playerRec = getDetails(PLAYER_ID);
playerHotspot->setTickProc(PLAYER_TICK_PROC_ID);
playerRec.fwhits = GENERAL_MAGIC_ID;
playerHotspot->resource()->colorOffset = 128;
playerHotspot->setSize(32, 48);
playerHotspot->resource()->width = 32;
playerHotspot->resource()->height = 48;
playerHotspot->setAnimationIndex(PLAYER_ANIM_INDEX);
playerHotspot->setPosition(playerHotspot->x(), playerHotspot->y() + 5);
playerHotspot->setDirection(LEFT);
if (playerHotspot->roomNumber() == 6) {
Dialog::show(0xc9f);
HotspotData *axeHotspot = res.getHotspot(0x2738);
axeHotspot->roomNumber = PLAYER_ID;
axeHotspot->flags |= HOTSPOTFLAG_FOUND;
// Prevent the weapon animation being drawn
axeHotspot = res.getHotspot(0x440);
axeHotspot->layer = 0;
}
}
// Makes sure the given weapon is off-screen
void FightsManager::removeWeapon(uint16 weaponId) {
Hotspot *weaponHotspot = Resources::getReference().getActiveHotspot(weaponId);
weaponHotspot->setPosition(-32, -32);
}
uint16 FightsManager::getFighterMove(FighterRecord &rec, uint16 baseOffset) {
int actionIndex = _rnd.getRandomNumber(31);
return getByte(baseOffset + (rec.fwhits << 5) + actionIndex);
}
} // End of namespace Lure