1559 lines
42 KiB
C++
1559 lines
42 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 "common/system.h"
|
|
|
|
#include "dgds/dgds.h"
|
|
#include "dgds/globals.h"
|
|
#include "dgds/game_palettes.h"
|
|
#include "dgds/image.h"
|
|
#include "dgds/includes.h"
|
|
#include "dgds/font.h"
|
|
#include "dgds/scene.h"
|
|
#include "dgds/sound.h"
|
|
#include "dgds/resource.h"
|
|
#include "dgds/minigames/china_train.h"
|
|
|
|
namespace Dgds {
|
|
|
|
/*
|
|
* Not used anywhere, but these strings are in the EXE and conveniently
|
|
* tell us what the PlayerAction enum values are.
|
|
*
|
|
static const char *ACTIONS[] = {
|
|
"Stand Right",
|
|
"Walk Right",
|
|
"Walk Left",
|
|
"Jump Right",
|
|
"Jump Left",
|
|
"Duck Right",
|
|
"Fall Right",
|
|
"Fall Left",
|
|
"Stagger",
|
|
"Death Scene",
|
|
"Club",
|
|
"Club Hit",
|
|
"Swing",
|
|
"Swing Hit",
|
|
"Stab",
|
|
"Stab Hit",
|
|
"Block",
|
|
"Heroic Jump",
|
|
"Block Up",
|
|
"Free6",
|
|
"Stand Left",
|
|
"Duck Left",
|
|
"Free7",
|
|
"Free8",
|
|
"##> ERROR <##",
|
|
};
|
|
*/
|
|
|
|
static const int16 COMMAND_BUTTONS[][6] = {
|
|
// x y w h id str (always 0)
|
|
{ 0xD5, 0x96, 0xC, 0xA, 0x20, 0x0 }, // DUCK
|
|
{ 0xE8, 0x96, 0xC, 0xA, 0x1E, 0x0 }, // ATTACK
|
|
{ 0xD5, 0xA7, 0xC, 0xA, 0x1F, 0x0 }, // REST
|
|
{ 0xE8, 0xA7, 0xC, 0xA, 0x13, 0x0 }, // RETREAT
|
|
};
|
|
|
|
static const int16 TRAIN[] = { 3, 2, 2, 2, 1, -1 };
|
|
|
|
int16 TrainPlayer::_blockSoundFlag = 0;
|
|
|
|
TrainPlayer::TrainPlayer(PlayerType type) : _type(type), _xpos(0), _ypos(0), _action(kActionStandRight),
|
|
_fatigue(0), _hitpoints(0), _intent(kIntentInvalid), _ferocity(0), _val7(0), _currentActionData(nullptr), _data(nullptr)
|
|
{}
|
|
|
|
void TrainPlayer::doPursue(const TrainPlayer &other) {
|
|
if (_xpos < other._xpos)
|
|
setAction(kActionWalkRight, false);
|
|
else if (other._xpos < _xpos)
|
|
setAction(kActionWalkLeft, false);
|
|
else
|
|
_intent = kIntentRest;
|
|
}
|
|
|
|
void TrainPlayer::doRun() {
|
|
if (_val7 < _xpos) {
|
|
// FIXME: Rechecking this doesn't make sense, but that's what the original does..
|
|
if (_val7 < _xpos)
|
|
setAction(kActionWalkLeft, false);
|
|
else
|
|
_intent = kIntentRest;
|
|
} else {
|
|
setAction(kActionWalkRight, false);
|
|
}
|
|
}
|
|
|
|
void TrainPlayer::setAction(PlayerAction state, bool flag) {
|
|
_action = state;
|
|
assert((uint)state < _allData.size());
|
|
_currentActionData = _allData.data() + (uint)state;
|
|
|
|
if (flag)
|
|
_data = startOfCurrentAction();
|
|
}
|
|
|
|
bool TrainPlayer::inRange(const TrainPlayer &other) {
|
|
return abs(_xpos - other._xpos) < 30;
|
|
}
|
|
|
|
void TrainPlayer::hit(int16 damage) {
|
|
DgdsEngine *engine = DgdsEngine::getInstance();
|
|
int16 difficulty = engine->getDifficulty();
|
|
if (isTong())
|
|
damage = damage << (2 - difficulty);
|
|
_hitpoints -= damage;
|
|
engine->_soundPlayer->playSFX(0x88);
|
|
}
|
|
|
|
void TrainPlayer::startStagger(const TrainPlayer &other) {
|
|
ChinaTrain *game = DgdsEngine::getInstance()->getChinaTrain();
|
|
if (_data->_flipMode == kImageFlipNone) {
|
|
_xpos = other._xpos - 30;
|
|
setAction(kActionStagger, true);
|
|
if (isTong())
|
|
game->checkTongFall(_xpos, 4);
|
|
} else {
|
|
_xpos = other._xpos + 30;
|
|
setAction(kActionStagger, true);
|
|
if (isTong())
|
|
game->checkTongFall(_xpos, 1);
|
|
}
|
|
}
|
|
|
|
uint16 _effective(int32 ferocity, int32 fatigue) {
|
|
fatigue = MIN((int32)100, fatigue);
|
|
ferocity = MIN((int32)6, ferocity);
|
|
|
|
if (ferocity * 10000 <= fatigue * 600)
|
|
return 0;
|
|
else
|
|
// max of 6 * 10000
|
|
return (uint16)(ferocity * 10000 - fatigue * 600);
|
|
}
|
|
|
|
int16 _getFatigue(int16 val) {
|
|
DgdsEngine *engine = DgdsEngine::getInstance();
|
|
int16 difficulty = engine->getDifficulty();
|
|
return (difficulty == 2 ? val : val / 2);
|
|
}
|
|
|
|
void TrainPlayer::checkDuck(const TunnelData ¤tTunnel) {
|
|
// Check if the player should be ducking to avoid the end of the tunnel
|
|
int32 distanceToStart = (currentTunnel._start + 2) * 8 - _xpos;
|
|
int32 distanceToEnd = (currentTunnel._end + 6) * 8 - _xpos;
|
|
|
|
if (!isDucking() && 0 < _xpos && _xpos < 320
|
|
&& (currentTunnel._start < 26 || currentTunnel._end < 39)
|
|
&& ((distanceToStart < 16 && distanceToStart >= 0) || (distanceToEnd < 16 && distanceToEnd >= 0)
|
|
)
|
|
) {
|
|
if (isTong() && !isJumping()) {
|
|
// Tong always ducks..
|
|
setAction(kActionDuckRight, true);
|
|
} else {
|
|
// Lucky falls
|
|
setAction(kActionFallLeft, true);
|
|
DgdsEngine::getInstance()->_soundPlayer->playSFX(131);
|
|
}
|
|
}
|
|
}
|
|
|
|
void TrainPlayer::doAttack(TrainPlayer &other) {
|
|
PlayerAction newAction;
|
|
if (!isStanding() || !other.isStanding() || other.isFalling()) {
|
|
if (_data->_flipMode == kImageFlipNone)
|
|
newAction = kActionStandRight;
|
|
else
|
|
newAction = kActionStandLeft;
|
|
} else {
|
|
if (other._data->_flipMode != _data->_flipMode) {
|
|
if (_xpos < other._xpos)
|
|
_xpos = other._xpos - 26;
|
|
else
|
|
_xpos = other._xpos + 26;
|
|
}
|
|
|
|
Common::RandomSource &random = DgdsEngine::getInstance()->getRandom();
|
|
uint16 peffective = _effective(_ferocity, _fatigue);
|
|
uint16 randVal1 = random.getRandomNumber(UINT16_MAX);
|
|
if (randVal1 < peffective && !other.isStaggering() && !other.isDucking()
|
|
&& !other.isJumping() && !other.isFalling() && other._data->_flipMode != _data->_flipMode) {
|
|
randVal1 = random.getRandomNumber(UINT16_MAX);
|
|
int16 chance = (_fatigue >> 4) + 4;
|
|
PlayerAction response;
|
|
PlayerAction randActions[16];
|
|
PlayerAction randResponse[16];
|
|
|
|
uint16 p2effective = _effective(other._ferocity, _getFatigue(other._fatigue));
|
|
uint16 p1effective = _effective(_ferocity, _getFatigue(_fatigue));
|
|
|
|
if (p2effective - (p1effective >> 2) < randVal1)
|
|
response = kActionBlock;
|
|
else
|
|
response = kActionStabHit;
|
|
|
|
for (int i = 0; i < chance && i < 16; i++) {
|
|
randActions[i] = kActionStab;
|
|
randResponse[i] = response;
|
|
}
|
|
|
|
if (response != kActionBlock)
|
|
response = kActionSwingHit;
|
|
|
|
for (int i = chance; i < chance * 2 && i < 16; i++) {
|
|
randActions[i] = kActionSwing;
|
|
randResponse[i] = response;
|
|
}
|
|
|
|
if (response == kActionBlock)
|
|
response = kActionBlockUp;
|
|
else
|
|
response = kActionClubHit;
|
|
|
|
for (int i = chance * 2; i < 16; i++) {
|
|
randActions[i] = kActionClub;
|
|
randResponse[i] = response;
|
|
}
|
|
|
|
randVal1 = random.getRandomNumber(15);
|
|
setAction(randActions[randVal1], true);
|
|
other.setAction(randResponse[randVal1], true);
|
|
if (response != kActionClubHit)
|
|
return;
|
|
|
|
DgdsEngine::getInstance()->getChinaTrain()->getPlayers().checkLives();
|
|
return;
|
|
}
|
|
|
|
if (_data->_flipMode == kImageFlipNone)
|
|
newAction = kActionStandRight;
|
|
else
|
|
newAction = kActionStandLeft;
|
|
}
|
|
setAction(newAction, true);
|
|
}
|
|
|
|
void TrainPlayer::doClub(int16 damage) {
|
|
if (_data->_val6 != 0) {
|
|
TrainPlayer &enemy = chooseEnemy();
|
|
if (inRange(enemy)) {
|
|
if (!enemy.isBlocking())
|
|
enemy.hit(damage);
|
|
}
|
|
}
|
|
|
|
if (_data == endOfCurrentAction()) {
|
|
if (_data->_flipMode == kImageFlipNone)
|
|
setAction(kActionStandRight, true);
|
|
else
|
|
setAction(kActionStandLeft, true);
|
|
}
|
|
}
|
|
|
|
void TrainPlayer::doJump() {
|
|
TrainPlayer &enemy = chooseEnemy();
|
|
DgdsEngine *engine = DgdsEngine::getInstance();
|
|
|
|
if (_data->_val6)
|
|
engine->_soundPlayer->playSFX(0x87);
|
|
|
|
|
|
if (inRange(enemy)) {
|
|
if (_data->_flipMode == kImageFlipNone)
|
|
enemy._xpos = _xpos + 0x1e;
|
|
else
|
|
enemy._xpos = _xpos - 0x1e;
|
|
|
|
if (engine->getChinaTrain()->checkGap(enemy._xpos, 2)) {
|
|
if (enemy._xpos < 0xa0)
|
|
enemy._xpos = _xpos + 0x1e;
|
|
else
|
|
enemy._xpos = _xpos - 0x1e;
|
|
}
|
|
}
|
|
|
|
if (_data == startOfCurrentAction()) {
|
|
if (_data->_flipMode == kImageFlipNone)
|
|
setAction(kActionStandRight, true);
|
|
else
|
|
setAction(kActionStandLeft, true);
|
|
|
|
if (isLucky()) {
|
|
if (_data->_flipMode == kImageFlipNone && 0xdc < _xpos) {
|
|
setAction(kActionWalkRight, true);
|
|
engine->getChinaTrain()->_jumpOffset++;
|
|
_xpos -= 0x10;
|
|
enemy._xpos -= 0x10;
|
|
} else if (_data->_flipMode != kImageFlipNone && (0x5d >= _xpos)) {
|
|
setAction(kActionWalkLeft, true);
|
|
engine->getChinaTrain()->_jumpOffset--;
|
|
_xpos += 0x10;
|
|
enemy._xpos += 0x10;
|
|
}
|
|
}
|
|
}
|
|
_fatigue++;
|
|
|
|
}
|
|
|
|
void TrainPlayer::doBlock() {
|
|
TrainPlayer &enemy = chooseEnemy();
|
|
if (_data->_val6 == 0) {
|
|
_blockSoundFlag = 0;
|
|
} else {
|
|
if (_blockSoundFlag == 0) {
|
|
DgdsEngine *engine = DgdsEngine::getInstance();
|
|
uint16 randval = engine->getRandom().getRandomNumber(UINT16_MAX);
|
|
//_blockSoundFlag = randval; // bug in original? it assigns this twice
|
|
_blockSoundFlag = -(randval >> 0xf);
|
|
|
|
engine->_soundPlayer->playSFX(-(randval >> 0xf) + 0x80);
|
|
}
|
|
// TODO: What is going on here??
|
|
//enemy._data++;
|
|
//assert(enemy._data->_frame != -1);
|
|
enemy._data++;
|
|
if (enemy._data->_frame == -1)
|
|
enemy._data = enemy.startOfCurrentAction();
|
|
}
|
|
|
|
if (_data == endOfCurrentAction()) {
|
|
_fatigue++;
|
|
if (_data->_flipMode == kImageFlipNone) {
|
|
enemy.setAction(kActionStandLeft, true);
|
|
setAction(kActionStandRight, true);
|
|
} else {
|
|
setAction(kActionStandLeft, true);
|
|
enemy.setAction(kActionStandRight, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
TrainPlayer &TrainPlayer::chooseEnemy() {
|
|
ChinaTrain *game = DgdsEngine::getInstance()->getChinaTrain();
|
|
if (isTong())
|
|
return game->getPlayers()._lucky;
|
|
else
|
|
return game->getPlayers()._tong;
|
|
}
|
|
|
|
|
|
void TrainPlayer::readStuff(const Common::String &resname) {
|
|
ResourceManager *resMan = DgdsEngine::getInstance()->getResourceManager();
|
|
Common::SeekableReadStream *stream = resMan->getResource(resname);
|
|
if (!stream)
|
|
error("Couldn't open train animation %s", resname.c_str());
|
|
|
|
_allData.clear();
|
|
|
|
uint16 nitems = stream->readUint16LE();
|
|
debug(10, "Reading %d items from %s", nitems, resname.c_str());
|
|
|
|
uint16 gotitems = 0;
|
|
Common::Array<PlayerData> currentArray;
|
|
while (gotitems < nitems && !stream->eos()) {
|
|
PlayerData data;
|
|
data._frame = stream->readSint16LE();
|
|
data._flipMode = static_cast<ImageFlipMode>(stream->readUint16LE());
|
|
data._xstep = stream->readSint16LE();
|
|
data._ystep = stream->readSint16LE();
|
|
data._xoff = stream->readSint16LE();
|
|
data._yoff = stream->readSint16LE();
|
|
data._val6 = stream->readSint16LE();
|
|
|
|
//
|
|
// Frame -1 means end of current list. Add it to the list as a sentinal
|
|
// and manually reset to the start.
|
|
//
|
|
// The original uses a lopped linked list so the next frame is always
|
|
// avaialble.
|
|
//
|
|
currentArray.push_back(data);
|
|
if (data._frame == -1) {
|
|
_allData.push_back(currentArray);
|
|
currentArray.clear();
|
|
} else {
|
|
gotitems++;
|
|
}
|
|
}
|
|
|
|
debug(10, "Got %d/%d items from %s", gotitems, nitems, resname.c_str());
|
|
|
|
delete stream;
|
|
}
|
|
|
|
void TrainPlayer::computerDucks() {
|
|
PlayerAction newState;
|
|
ChinaTrain *game = DgdsEngine::getInstance()->getChinaTrain();
|
|
const TunnelData ¤tTunnel = game->getCurrentTunnel();
|
|
int16 xd1 = (currentTunnel._start + 10) * 8 - _xpos;
|
|
int16 xd2 = currentTunnel._start * 8 - _xpos;
|
|
if (((xd1 < 0x130 && 0 < xd1) || (xd2 < 0x80 && -0x60 < xd2)) &&
|
|
(isStanding() || isWalking() || isDucking())) {
|
|
if (_data->_flipMode == kImageFlipNone)
|
|
newState = kActionDuckRight;
|
|
else
|
|
newState = kActionDuckLeft;
|
|
} else {
|
|
if (!isDucking())
|
|
return;
|
|
|
|
if (chooseEnemy()._xpos < _xpos)
|
|
newState = kActionStandLeft;
|
|
else
|
|
newState = kActionStandRight;
|
|
}
|
|
setAction(newState, true);
|
|
}
|
|
|
|
PlayerData *TrainPlayer::endOfCurrentAction() {
|
|
// return the last real item in the current action
|
|
assert(_currentActionData);
|
|
int offset = (int)_currentActionData->size() - 2;
|
|
assert(offset >= 0);
|
|
return _currentActionData->data() + offset;
|
|
}
|
|
|
|
PlayerData *TrainPlayer::startOfCurrentAction() {
|
|
assert(_currentActionData);
|
|
return _currentActionData->data();
|
|
}
|
|
|
|
void TrainPlayer::doProcess() {
|
|
ChinaTrain *game = DgdsEngine::getInstance()->getChinaTrain();
|
|
assert(_data);
|
|
_xpos += _data->_xstep;
|
|
_ypos += _data->_ystep;
|
|
|
|
_data++;
|
|
if (_data->_frame == -1) // hit the end of the list.
|
|
_data = startOfCurrentAction();
|
|
|
|
assert(_data->_frame != -1);
|
|
|
|
switch (_action) {
|
|
case kActionStandRight:
|
|
case kActionStandLeft:
|
|
_ypos = 95;
|
|
if (_fatigue != 0 && _data == startOfCurrentAction())
|
|
_fatigue--;
|
|
|
|
break;
|
|
case kActionWalkRight:
|
|
_ypos = 95;
|
|
setAction(kActionStandRight, 0);
|
|
if (_data == startOfCurrentAction())
|
|
_fatigue++;
|
|
|
|
break;
|
|
case kActionWalkLeft:
|
|
_ypos = 95;
|
|
setAction(kActionStandLeft, 0);
|
|
if (_data == startOfCurrentAction())
|
|
_fatigue++;
|
|
|
|
break;
|
|
case kActionJumpRight:
|
|
case kActionJumpLeft:
|
|
doJump();
|
|
break;
|
|
case kActionDuckRight:
|
|
case kActionDuckLeft:
|
|
if (_fatigue != 0 && _data == startOfCurrentAction())
|
|
_fatigue--;
|
|
|
|
break;
|
|
case kActionFallRight:
|
|
case kActionFallLeft:
|
|
if (0xa0 < _ypos) {
|
|
game->leaveArcade();
|
|
}
|
|
_hitpoints = 0;
|
|
break;
|
|
case kActionStagger:
|
|
if (_data == startOfCurrentAction()) {
|
|
if (_data->_flipMode == kImageFlipNone)
|
|
setAction(kActionStandRight, true);
|
|
else
|
|
setAction(kActionStandLeft, true);
|
|
}
|
|
break;
|
|
case kActionDeathScene:
|
|
if (isLucky()) {
|
|
if (_data->_val6) {
|
|
_data++;
|
|
if (_data->_frame == -1)
|
|
_data = endOfCurrentAction();
|
|
}
|
|
} else if (_data->_val6) {
|
|
game->leaveArcade();
|
|
}
|
|
break;
|
|
case kActionClub:
|
|
doClub(5);
|
|
_fatigue += 2;
|
|
break;
|
|
case kActionClubHit:
|
|
case kActionSwingHit:
|
|
case kActionStabHit:
|
|
if (_data == startOfCurrentAction())
|
|
startStagger(chooseEnemy());
|
|
break;
|
|
case kActionSwing:
|
|
doClub(3);
|
|
_fatigue++;
|
|
break;
|
|
case kActionStab:
|
|
doClub(1);
|
|
_fatigue++;
|
|
break;
|
|
case kActionBlock:
|
|
case kActionBlockUp:
|
|
doBlock();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (100 < _fatigue)
|
|
_fatigue = 100;
|
|
}
|
|
|
|
|
|
//////////////////////////
|
|
|
|
TrainPlayers::TrainPlayers() : _lucky(kPlayerLucky), _tong(kPlayerTong) {
|
|
}
|
|
|
|
void TrainPlayers::initPlayers() {
|
|
_lucky._xpos = 160;
|
|
_lucky._ypos = 95;
|
|
_lucky.setAction(kActionStandLeft, true);
|
|
_lucky._hitpoints = 100;
|
|
_lucky._fatigue = 0;
|
|
_lucky._ferocity = 0;
|
|
_lucky._intent = kIntentRest;
|
|
_tong._xpos = 110;
|
|
_tong._ypos = 95;
|
|
_tong.setAction(kActionStandRight, true);
|
|
_tong._hitpoints = 100;
|
|
_tong._fatigue = 0;
|
|
_tong._ferocity = 1;
|
|
_tong._intent = kIntentAttack;
|
|
}
|
|
|
|
void TrainPlayers::checkLives() {
|
|
if (_lucky._hitpoints < 3) {
|
|
_tong.setAction(kActionDeathScene, true);
|
|
_lucky.setAction(kActionDeathScene, true);
|
|
} else if (_tong._hitpoints < 4) {
|
|
_tong._intent = kIntentQ;
|
|
}
|
|
}
|
|
|
|
void TrainPlayers::readAnims() {
|
|
_lucky.readStuff("lucky.anm");
|
|
_tong.readStuff("tong.anm");
|
|
}
|
|
|
|
void TrainPlayers::freeAnims() {
|
|
_lucky._allData.clear();
|
|
_tong._allData.clear();
|
|
_lucky._currentActionData = nullptr;
|
|
_lucky._data = nullptr;
|
|
_tong._currentActionData = nullptr;
|
|
_tong._data = nullptr;
|
|
}
|
|
|
|
|
|
void TrainPlayers::doScroll(int16 jumpOffset) {
|
|
if (!jumpOffset)
|
|
return;
|
|
|
|
int16 offset = (jumpOffset < 0) ? 16 : -16;
|
|
_lucky._xpos += offset;
|
|
_tong._xpos += offset;
|
|
_lucky._val7 += offset;
|
|
}
|
|
|
|
//////////////////////////
|
|
|
|
|
|
ChinaTrain::ChinaTrain() : _arcadeCount(0), _arcadeFlag(0), _arcadeInitFlag(false),
|
|
_arcadeDrawFlag(0), _failCounter(0), _lastMaskedArcadeFlag(0), _int3036(0),
|
|
_tongAttackCounter(0), _tongInjuredCounter(0), _tongRestTarget(0), _lastTongHP(0),
|
|
_lastTongFatigue(0), _currentCar(0), _trackPos(0), _frameCnt(0), _xOffset(0), _tunnelNum(0),
|
|
_cabooseTrail(0), _lastBtn(0), _pressedCommandButton(-1), _leftButtonDown(false),
|
|
_rightButtonDown(false), _lastKeycode(0), _playedTunnelSFX(false), _jumpOffset(0),
|
|
_clipWin(Common::Point(8, 0), 304, 130) {
|
|
}
|
|
|
|
void ChinaTrain::init() { // aka arcadeInit
|
|
if (_arcadeInitFlag)
|
|
return;
|
|
|
|
//_reshow_before_ads = _arc_refresh;
|
|
DgdsEngine *engine = DgdsEngine::getInstance();
|
|
|
|
engine->disableKeymapper();
|
|
engine->getBackgroundBuffer().fillRect(Common::Rect(SCREEN_WIDTH, SCREEN_HEIGHT), 0);
|
|
engine->getStoredAreaBuffer().fillRect(Common::Rect(SCREEN_WIDTH, SCREEN_HEIGHT), 0);
|
|
|
|
_rectShape.reset(new Image(engine->getResourceManager(), engine->getDecompressor()));
|
|
_rectShape->loadBitmap("bkgnd.bmp");
|
|
|
|
engine->getGamePals()->loadPalette("hc.pal");
|
|
|
|
_test.reset(new Image(engine->getResourceManager(), engine->getDecompressor()));
|
|
_test->loadBitmap("train.bmp");
|
|
_luckyMaps.reset(new Image(engine->getResourceManager(), engine->getDecompressor()));
|
|
_luckyMaps->loadBitmap("lucky.bmp");
|
|
|
|
_players.readAnims();
|
|
trainRestart();
|
|
// engine->getFontMan()->getFont(font6);
|
|
// _fgcolor = 0xe;
|
|
|
|
engine->_soundPlayer->playSFX(125);
|
|
_arcadeInitFlag = true;
|
|
_arcadeCount = 0;
|
|
_arcadeDrawFlag = 1;
|
|
|
|
_leftButtonDown = false;
|
|
_rightButtonDown = false;
|
|
_lastKeycode = false;
|
|
_playedTunnelSFX = false;
|
|
_pressedCommandButton = -1;
|
|
_lastBtn = 0;
|
|
|
|
// setupArcade(); // this just sets clipping window which we already set.
|
|
initScoreWindow();
|
|
|
|
// the background buffer is now set up, copy it to the composition buffer
|
|
engine->_compositionBuffer.blitFrom(engine->getBackgroundBuffer());
|
|
|
|
drawFrame();
|
|
|
|
// ensure mouse cursor is on
|
|
engine->setMouseCursor(kDgdsMouseGameDefault);
|
|
|
|
engine->setMenuToTrigger(kMenuSkipArcade);
|
|
|
|
// Copy to screen now so the menu is drawn over the game background.
|
|
g_system->copyRectToScreen(engine->_compositionBuffer.getPixels(), SCREEN_WIDTH, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
|
|
}
|
|
|
|
int16 ChinaTrain::tick() { // aka arcadeLoop
|
|
if (!trainArcade())
|
|
return 0;
|
|
|
|
HocGlobals *globals = static_cast<HocGlobals *>(DgdsEngine::getInstance()->getGameGlobals());
|
|
int16 trainState = globals->getTrainState();
|
|
|
|
if (trainState == 3 || trainState == 4) {
|
|
arcadeFadeout();
|
|
end();
|
|
_arcadeFlag = 0;
|
|
return 0;
|
|
} else {
|
|
getUserInput();
|
|
getNpcInput();
|
|
processInput();
|
|
handleVariables();
|
|
drawFrame();
|
|
|
|
// Original has delay here to reduce the arcade speed to 15 FPS.
|
|
//DgdsEngine::getInstance()->setSkipNextFrame();
|
|
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
void ChinaTrain::arcadeFadeout() {
|
|
DgdsEngine *engine = DgdsEngine::getInstance();
|
|
for (int i = 63; i > 0; i--) {
|
|
engine->getGamePals()->setFade(0, 255, 0, i * 4);
|
|
g_system->updateScreen();
|
|
g_system->delayMillis(5);
|
|
}
|
|
Common::Rect screen(SCREEN_WIDTH, SCREEN_HEIGHT);
|
|
engine->_compositionBuffer.fillRect(screen, 0);
|
|
engine->getBackgroundBuffer().fillRect(screen, 0);
|
|
g_system->lockScreen()->fillRect(screen, 0);
|
|
g_system->unlockScreen();
|
|
engine->getGamePals()->setFade(0, 255, 0, 255);
|
|
}
|
|
|
|
int16 ChinaTrain::trainArcade() {
|
|
HocGlobals *globals = static_cast<HocGlobals *>(DgdsEngine::getInstance()->getGameGlobals());
|
|
int16 trainState = globals->getTrainState();
|
|
if (trainState == 0) {
|
|
globals->setTrainState(1);
|
|
if (_int3036 != 0)
|
|
_arcadeFlag |= 0xa5;
|
|
else
|
|
_arcadeFlag |= 0x25;
|
|
|
|
_failCounter = 0;
|
|
_lastMaskedArcadeFlag = 0;
|
|
}
|
|
if (_lastMaskedArcadeFlag == 0)
|
|
_lastMaskedArcadeFlag = _arcadeFlag & 0x380;
|
|
|
|
if (_lastMaskedArcadeFlag == 0) {
|
|
return 1;
|
|
} else {
|
|
if (!(_arcadeFlag & _lastMaskedArcadeFlag)) {
|
|
if (_arcadeFlag & 0x400 || _lastMaskedArcadeFlag == 0x80) {
|
|
globals->setTrainState(1);
|
|
trainRestart();
|
|
_lastMaskedArcadeFlag = 0;
|
|
return 1;
|
|
}
|
|
if (!(_lastMaskedArcadeFlag & 0x200))
|
|
globals->setTrainState(3);
|
|
else
|
|
globals->setTrainState(4);
|
|
|
|
_lastMaskedArcadeFlag = 0;
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void ChinaTrain::setMenuResult(bool yes) {
|
|
if (_arcadeInitFlag && _arcadeFlag)
|
|
_arcadeFlag = (yes ? 0x400 : 0);
|
|
}
|
|
|
|
void ChinaTrain::end() { // aka arcadeReset
|
|
if (!_arcadeInitFlag)
|
|
return;
|
|
|
|
//_reshow_before_ads = _game_reshow;
|
|
//_matrix.clear();
|
|
_rectShape.reset();
|
|
_luckyMaps.reset();
|
|
_test.reset();
|
|
|
|
_players.freeAnims();
|
|
DgdsEngine *engine = DgdsEngine::getInstance();
|
|
engine->_soundPlayer->stopSfxByNum(125);
|
|
|
|
engine->enableKeymapper();
|
|
_arcadeInitFlag = false;
|
|
_arcadeDrawFlag = 0;
|
|
_arcadeFlag = 0;
|
|
}
|
|
|
|
void ChinaTrain::leaveArcade() {
|
|
HocGlobals *globals = static_cast<HocGlobals *>(DgdsEngine::getInstance()->getGameGlobals());
|
|
assert(globals);
|
|
|
|
int16 trainState = globals->getTrainState();
|
|
if (trainState != 4)
|
|
lost();
|
|
}
|
|
|
|
void ChinaTrain::trainRestart() {
|
|
_currentCar = 2;
|
|
_trackPos = 0;
|
|
_xOffset = 0;
|
|
_tunnelNum = 0;
|
|
makeNewTunnel();
|
|
_players.initPlayers();
|
|
}
|
|
|
|
void ChinaTrain::makeNewTunnel() {
|
|
Common::RandomSource &random = DgdsEngine::getInstance()->getRandom();
|
|
uint16 randval = random.getRandomNumber(UINT16_MAX);
|
|
_currentTunnel._start = (randval >> 9) + 350;
|
|
randval = random.getRandomNumber(UINT16_MAX);
|
|
_currentTunnel._end = _currentTunnel._start + (randval >> 10) + 100;
|
|
}
|
|
|
|
void ChinaTrain::lost() {
|
|
_failCounter++;
|
|
|
|
DgdsEngine *engine = DgdsEngine::getInstance();
|
|
|
|
if (_failCounter < 5) {
|
|
engine->setMenuToTrigger(kMenuReplayArcade);
|
|
_arcadeFlag |= 0x100;
|
|
} else {
|
|
engine->setMenuToTrigger(kMenuArcadeFrustrated);
|
|
_arcadeFlag |= 0x200;
|
|
}
|
|
|
|
_lastMaskedArcadeFlag = _arcadeFlag & 0x300;
|
|
}
|
|
|
|
void ChinaTrain::checkTongFall(int16 xpos, int16 car) {
|
|
if (checkGap(xpos, -2) && _currentCar == car)
|
|
leaveArcade();
|
|
}
|
|
|
|
bool ChinaTrain::checkGap(int16 xpos, int16 offset) {
|
|
int16 xmin = (94 - offset) - _jumpOffset;
|
|
int16 xmax = (offset + 220) - _jumpOffset;
|
|
return ((xmin + offset * 2 - 48 < xpos) && xpos < xmin)
|
|
|| (xmax < xpos && xpos < xmax - offset * 2 + 48)
|
|
|| (_currentCar == 4 && xpos < xmin)
|
|
|| (_currentCar == 1 && xmax < xpos);
|
|
}
|
|
|
|
void ChinaTrain::initScoreWindow() {
|
|
Graphics::ManagedSurface &buf = DgdsEngine::getInstance()->getBackgroundBuffer();
|
|
drawBorder();
|
|
buf.fillRect(Common::Rect(Common::Point(8, 143), 304, 39), 126);
|
|
shadeBox(buf, 23, 20, 0, 0xac, 0x91, 0x82, 0x24);
|
|
shadeBox(buf, 17, 6, 0, 0xad, 0x92, 0x80, 0x22);
|
|
shadeLabel(0xb1, 0x97, 0x1e, 9, "DUCK");
|
|
shadeLabel(0xb1, 0xa8, 0x1e, 9, "REST");
|
|
shadeLabel(0xfa, 0x97, 0x2d, 9, "ATTACK");
|
|
shadeLabel(0xfa, 0xa8, 0x2d, 9, "RETREAT");
|
|
shadeLabel(0x2a, 0x99, 0x1e, 9, "LIFE");
|
|
shadeLabel(0x67, 0x99, 0x2d, 9, "FATIGUE");
|
|
shadeLabel(0x42, 0xac, 0x31, 9, "FEROCITY");
|
|
buf.fillRect(Common::Rect(Common::Point(0, 195), 320, 5), 0);
|
|
}
|
|
|
|
void ChinaTrain::drawBorder() {
|
|
//
|
|
// This is not exactly how the original does it, but should work.
|
|
// The original uses indexes into the tiling matrix for the image.
|
|
// We just reference the tiles we want directly.
|
|
//
|
|
// TL is 370, 372, 20
|
|
// TR is 340 693, 703
|
|
// BR is 340, 350, 360
|
|
// BL is 0, 10, 20
|
|
//
|
|
// LEFT/RIGHT border is 371 (or 704?)
|
|
// TOP/BOTTOM border is 87
|
|
// RIGHT MIDDLE EDGE (between game and control panel) is 77
|
|
// LEFT MIDDLE EDGE (between game and control panel) is 30
|
|
// MIDDLE LINE (between game and control panel) is 40
|
|
//
|
|
|
|
Graphics::ManagedSurface &buf = DgdsEngine::getInstance()->getBackgroundBuffer();
|
|
const Common::Rect screen(SCREEN_WIDTH, SCREEN_HEIGHT);
|
|
|
|
for (int16 i = 1; i < 39; i++)
|
|
_rectShape->drawBitmap(87, i * 8, 0, screen, buf);
|
|
|
|
// top-left
|
|
_rectShape->drawBitmap(370, 0, 0, screen, buf);
|
|
_rectShape->drawBitmap(372, 8, 0, screen, buf);
|
|
_rectShape->drawBitmap(20, 16, 0, screen, buf);
|
|
|
|
// top-right
|
|
_rectShape->drawBitmap(340, 0x128, 0, screen, buf);
|
|
_rectShape->drawBitmap(693, 0x130, 0, screen, buf);
|
|
_rectShape->drawBitmap(703, 0x138, 0, screen, buf);
|
|
|
|
// bottom-left
|
|
_rectShape->drawBitmap(0, 0, 0xb6, screen, buf);
|
|
_rectShape->drawBitmap(10, 8, 0xb6, screen, buf);
|
|
_rectShape->drawBitmap(20, 16, 0xb6, screen, buf);
|
|
|
|
// bottom-right
|
|
_rectShape->drawBitmap(340, 0x128, 0xb6, screen, buf);
|
|
_rectShape->drawBitmap(350, 0x130, 0xb6, screen, buf);
|
|
_rectShape->drawBitmap(360, 0x138, 0xb6, screen, buf);
|
|
|
|
// Mid corners (between game and score area)
|
|
_rectShape->drawBitmap(30, 0, 0x82, screen, buf);
|
|
_rectShape->drawBitmap(40, 8, 0x82, screen, buf);
|
|
_rectShape->drawBitmap(40, 0x130, 0x82, screen, buf);
|
|
_rectShape->drawBitmap(77, 0x138, 0x82, screen, buf);
|
|
|
|
for (int16 i = 2; i < 0x26; i++)
|
|
_rectShape->drawBitmap(40, i * 8, 0x82, screen, buf);
|
|
|
|
for (int16 i = 3; i < 0x25; i++)
|
|
_rectShape->drawBitmap(87, i * 8, 0xb6, screen, buf);
|
|
|
|
// Complete left/right sides
|
|
for (int16 i = 1; i < 10; i++) {
|
|
// Left side
|
|
_rectShape->drawBitmap(371, 0, i * 13, screen, buf);
|
|
// Right side
|
|
_rectShape->drawBitmap(371, 0x138, i * 13, screen, buf);
|
|
}
|
|
|
|
for (int16 i = 11; i < 14; i++) {
|
|
_rectShape->drawBitmap(371, 0, i * 13, screen, buf);
|
|
_rectShape->drawBitmap(371, 0x138, i * 13, screen, buf);
|
|
}
|
|
}
|
|
|
|
void ChinaTrain::drawBlock(int16 x1, int16 x2, int16 param_3, int16 y1, int16 y2, int16 stride, int16 xoffset, int16 param_8) {
|
|
Graphics::ManagedSurface &compBuf = DgdsEngine::getInstance()->_compositionBuffer;
|
|
for (; y1 < y2; y1++) {
|
|
for (int16 xx = x1; xx < x2; xx = xx + 1) {
|
|
if (xx <= 0 || xx >= 39)
|
|
continue;
|
|
int16 matrixX = param_8 + ((param_3 + xx) - x1 + xoffset) % stride;
|
|
int16 matrixY = y1;
|
|
int16 frameno = _rectShape->getFrameFromMatrix(matrixX, matrixY);
|
|
_rectShape->drawBitmap(frameno, xx * 8, y1 * 13, _clipWin, compBuf);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ChinaTrain::drawMountains(int16 param) {
|
|
static int16 lastXOffsetOdd = 0;
|
|
static int16 numEvenXOffsets = 0;
|
|
|
|
int16 x2;
|
|
if (_currentTunnel._start < 40 && _currentTunnel._start >= 0)
|
|
x2 = _currentTunnel._start;
|
|
else
|
|
x2 = 39;
|
|
|
|
// Top row - mountains
|
|
drawBlock(param, x2, param - 1, 1, 5, 40, 0, 0);
|
|
|
|
// Mid row - moves at 1/2 speed xOffset
|
|
if (_xOffset % 2 != lastXOffsetOdd) {
|
|
lastXOffsetOdd = _xOffset % 2;
|
|
if (lastXOffsetOdd == 0) {
|
|
numEvenXOffsets++;
|
|
if (numEvenXOffsets > 39)
|
|
numEvenXOffsets = 0;
|
|
}
|
|
}
|
|
|
|
Graphics::ManagedSurface &compBuf = DgdsEngine::getInstance()->_compositionBuffer;
|
|
for (int16 xx = param; xx < 39; xx = xx + 1) {
|
|
//int16 frameno = 5 * 40 + ((xx + numEvenXOffsets) % 40);
|
|
int16 frameno = _rectShape->getFrameFromMatrix((xx + numEvenXOffsets) % 40, 5);
|
|
_rectShape->drawBitmap(frameno, xx * 8, 0x41, _clipWin, compBuf);
|
|
}
|
|
|
|
// 3rd row - moves at speed of xOffset
|
|
drawBlock(param, x2, param - 1, 6, 7, 40, _xOffset, 0);
|
|
// Bottom row - moves at 2x speed of xOffset
|
|
drawBlock(param, x2, param - 1, 7, 10, 40, _xOffset * 2, 0);
|
|
}
|
|
|
|
void ChinaTrain::drawTunnel() {
|
|
int16 x2;
|
|
if (_currentTunnel._start + 13 < 40)
|
|
x2 = _currentTunnel._start + 13;
|
|
else
|
|
x2 = 39;
|
|
|
|
drawBlock(_currentTunnel._start, x2, 0, 1, 10, 13, 0, 0x29);
|
|
|
|
if (x2 < 39 && _currentTunnel._end >= 0) {
|
|
if (_currentTunnel._end < 40)
|
|
x2 = _currentTunnel._end;
|
|
else
|
|
x2 = 39;
|
|
|
|
drawBlock(_currentTunnel._start + 13, x2, 0, 1, 10, 12, 0, 0x43);
|
|
}
|
|
|
|
if (_currentTunnel._end < 39) {
|
|
drawBlock(_currentTunnel._end, _currentTunnel._end + 13, 0, 1, 10, 13, 0, 0x36);
|
|
}
|
|
}
|
|
|
|
int16 ChinaTrain::readButtons() {
|
|
const Common::Point pt = DgdsEngine::getInstance()->getLastMouse();
|
|
_pressedCommandButton = -1;
|
|
int16 retVal = 0;
|
|
for (int btn = 0; btn < ARRAYSIZE(COMMAND_BUTTONS); btn++) {
|
|
const int16 *btnData = COMMAND_BUTTONS[btn];
|
|
|
|
if (btnData[0] < pt.x && pt.x < btnData[0] + btnData[2]
|
|
&& btnData[1] < pt.y && pt.y < btnData[1] + btnData[3]) {
|
|
retVal = btnData[4];
|
|
_pressedCommandButton = btn;
|
|
}
|
|
}
|
|
return retVal;
|
|
}
|
|
|
|
void ChinaTrain::getUserInput() {
|
|
/*
|
|
TODO: original gets joystick values here.
|
|
djoy(0);
|
|
joyButton(0);
|
|
joyButton(1);
|
|
*/
|
|
_rightButtonDown = DgdsEngine::getInstance()->getScene()->isRButtonDown();
|
|
_leftButtonDown = DgdsEngine::getInstance()->getScene()->isLButtonDown();
|
|
|
|
uint16 btn = _lastKeycode;
|
|
if (btn == 0 && _leftButtonDown) {
|
|
btn = readButtons();
|
|
}
|
|
|
|
if (btn == _lastBtn) {
|
|
btn = 0;
|
|
} else {
|
|
_lastBtn = btn;
|
|
}
|
|
|
|
switch (btn) {
|
|
case 0x1e: // ATTACK (A)
|
|
if (_players._lucky._intent == kIntentAttack || _players._lucky._intent == kIntentPursue) {
|
|
_players._lucky._ferocity++;
|
|
if (6 < _players._lucky._ferocity) {
|
|
_players._lucky._ferocity = 6;
|
|
}
|
|
} else {
|
|
_players._lucky._ferocity = 1;
|
|
_players._lucky._intent = kIntentAttack;
|
|
}
|
|
break;
|
|
case 0x10: // (Q)
|
|
_players._tong._intent = kIntentQ;
|
|
break;
|
|
case 0x13: // RETREAT (R)
|
|
if (!_players._lucky.isJumping()) {
|
|
_players._lucky._intent = kIntentRetreat;
|
|
int16 newVal7 = _players._lucky._xpos;
|
|
if (_players._tong._xpos < newVal7)
|
|
newVal7 = newVal7 + 0x90;
|
|
else
|
|
newVal7 = _players._lucky._xpos - 0x90;
|
|
|
|
_players._lucky._val7 = newVal7;
|
|
|
|
if (_currentCar == 1) {
|
|
if (_players._lucky._xpos < 0xad)
|
|
_players._lucky._val7 = 0xac;
|
|
else
|
|
_players._lucky._val7 = _players._lucky._xpos;
|
|
|
|
} else if (_currentCar == 4 && (_players._lucky._xpos < _players._tong._xpos)) {
|
|
_players._lucky._val7 = 0x8e;
|
|
}
|
|
|
|
if (checkGap(_players._lucky._val7, 0) != 0) {
|
|
_players._lucky._val7 = 0xa0;
|
|
}
|
|
}
|
|
break;
|
|
case 0x1f: // REST (S)
|
|
_players._lucky._intent = kIntentRest;
|
|
break;
|
|
case 0x20: // DUCK (D)
|
|
_players._lucky._intent = kIntentDuck;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
PlayerAction newAction;
|
|
if (!_rightButtonDown || _players._lucky.isJumping() || _players._lucky.isFalling()) {
|
|
if (!_players._lucky.isDucking())
|
|
return;
|
|
|
|
// Stand up if there is no longer a signal to duck.
|
|
if (_players._tong._xpos < _players._lucky._xpos)
|
|
newAction = kActionStandLeft;
|
|
else
|
|
newAction = kActionStandRight;
|
|
} else {
|
|
if (_players._lucky._data->_flipMode == kImageFlipNone)
|
|
newAction = kActionDuckRight;
|
|
else
|
|
newAction = kActionDuckLeft;
|
|
}
|
|
_players._lucky.setAction(newAction, true);
|
|
}
|
|
|
|
void ChinaTrain::getNpcInput() {
|
|
_players._tong.computerDucks();
|
|
if (DgdsEngine::getInstance()->getDifficulty() == 0)
|
|
_players._lucky.computerDucks();
|
|
|
|
if (_players._tong.isStanding()) {
|
|
if ( _players._lucky.isFalling() && !_players._tong.isDucking()) {
|
|
_players._tong._intent = kIntentRest;
|
|
if (_players._tong._xpos < _players._lucky._xpos)
|
|
_players._tong.setAction(kActionStandRight, true);
|
|
else
|
|
_players._tong.setAction(kActionStandLeft, true);
|
|
}
|
|
if (_players._tong._intent == kIntentAttack || _players._tong._intent == kIntentPursue) {
|
|
_tongAttackCounter++;
|
|
} else {
|
|
_tongAttackCounter = 0;
|
|
}
|
|
|
|
if (20 < _tongAttackCounter) {
|
|
_tongAttackCounter = 0;
|
|
if (_players._tong.inRange(_players._lucky)) {
|
|
_players._tong._ferocity++;
|
|
if (_players._tong._ferocity > 6)
|
|
_players._tong._ferocity = 6;
|
|
}
|
|
}
|
|
|
|
if (_players._tong._hitpoints == _lastTongHP) {
|
|
_tongInjuredCounter--;
|
|
if (_tongInjuredCounter < 0)
|
|
_tongInjuredCounter = 0;
|
|
} else {
|
|
_tongInjuredCounter++;
|
|
}
|
|
|
|
if (5 < _tongInjuredCounter && _currentCar != 4) {
|
|
_players._tong._intent = kIntentRetreat;
|
|
_players._tong._ferocity = 1;
|
|
_tongInjuredCounter = 0;
|
|
}
|
|
|
|
if (_players._tong._intent == kIntentRest && _players._tong._fatigue == _lastTongFatigue) {
|
|
_tongRestTarget = _players._tong._fatigue - 20;
|
|
if (_tongRestTarget < 0)
|
|
_tongRestTarget = 0;
|
|
}
|
|
|
|
if (_players._tong._intent == kIntentRest && _players._tong._fatigue == _tongRestTarget) {
|
|
_players._tong._intent = kIntentAttack;
|
|
}
|
|
|
|
if (0x4b < _players._tong._fatigue) {
|
|
_players._tong._ferocity--;
|
|
if (_players._tong._ferocity < 1)
|
|
_players._tong._ferocity = 1;
|
|
}
|
|
|
|
_lastTongHP = _players._tong._hitpoints;
|
|
_lastTongFatigue = _players._tong._fatigue;
|
|
}
|
|
}
|
|
|
|
void ChinaTrain::handleVariables() {
|
|
_frameCnt++;
|
|
_trackPos++;
|
|
_xOffset = _trackPos % 40;
|
|
|
|
_currentTunnel._start -= 2;
|
|
_currentTunnel._end -= 2;
|
|
|
|
// TODO: Simplify this logic
|
|
if (_jumpOffset < 1 || (_jumpOffset += 16, _jumpOffset < 0xb1)) {
|
|
if (_jumpOffset < 0 && (_jumpOffset -= 16, _jumpOffset < -0xb0)) {
|
|
_jumpOffset = 0;
|
|
_currentCar++;
|
|
}
|
|
} else {
|
|
_jumpOffset = 0;
|
|
_currentCar--;
|
|
}
|
|
|
|
if (_cabooseTrail) {
|
|
_cabooseTrail += 2;
|
|
if (_cabooseTrail > 0x40)
|
|
cabooseLost();
|
|
}
|
|
|
|
if (_currentTunnel._start < 0x32 && _playedTunnelSFX == 0) {
|
|
DgdsEngine::getInstance()->_soundPlayer->playSFX(0x85);
|
|
_playedTunnelSFX = 1;
|
|
}
|
|
|
|
if (_currentTunnel._end < -12) {
|
|
makeNewTunnel();
|
|
_playedTunnelSFX = 0;
|
|
}
|
|
}
|
|
|
|
void ChinaTrain::cabooseLost() {
|
|
HocGlobals *globals = static_cast<HocGlobals *>(DgdsEngine::getInstance()->getGameGlobals());
|
|
assert(globals);
|
|
globals->setTrainState(4);
|
|
leaveArcade();
|
|
}
|
|
|
|
|
|
void ChinaTrain::fixUpTunnel() {
|
|
Graphics::ManagedSurface &compBuf = DgdsEngine::getInstance()->_compositionBuffer;
|
|
if (_currentTunnel._start < 0x25 && -12 < _currentTunnel._start)
|
|
_test->drawBitmap(4, _currentTunnel._start * 8 + 0x18, 0x5e, _clipWin, compBuf, kImageFlipNone);
|
|
|
|
if (_currentTunnel._end < 0x1e)
|
|
_test->drawBitmap(4, _currentTunnel._end * 8 + 0x47, 0x5e, _clipWin, compBuf, kImageFlipH);
|
|
}
|
|
|
|
void ChinaTrain::drawFrame() {
|
|
//setupArcade();
|
|
if (2 < _currentTunnel._start)
|
|
drawMountains(1);
|
|
|
|
if (_currentTunnel._end < 0x1a)
|
|
drawMountains(_currentTunnel._end + 13);
|
|
|
|
if (_currentTunnel._start < 40)
|
|
drawTunnel();
|
|
|
|
fixBorder();
|
|
drawScoreWindow();
|
|
drawCommandButtons();
|
|
drawBmps();
|
|
//fullScreen(); // set clip window to full screen
|
|
}
|
|
|
|
void ChinaTrain::shadeBox(Graphics::ManagedSurface &buf, byte tlCol, byte brCol, byte fill, int16 x, int16 y, int16 w, int16 h) {
|
|
// Button background
|
|
buf.fillRect(Common::Rect(Common::Point(x + 1, y + 1), w - 2, h - 2), fill);
|
|
// Button top/right color
|
|
buf.vLine(x + w - 1, y, y + h - 1, tlCol);
|
|
buf.hLine(x, y, x + w - 1, tlCol);
|
|
// Button bottom/left color
|
|
buf.vLine(x, y + 1, y + h - 1, brCol);
|
|
buf.hLine(x, y + h - 1, x + w - 1, brCol);
|
|
}
|
|
|
|
void ChinaTrain::shadeLabel(int16 x, int16 y, int16 w, int16 h, const char *label) {
|
|
Graphics::ManagedSurface &buf = DgdsEngine::getInstance()->getBackgroundBuffer();
|
|
shadeBox(buf, 0x10, 0x14, 0x16, x, y, w, h);
|
|
const DgdsFont *font = DgdsEngine::getInstance()->getFontMan()->getFont(FontManager::kGameFont);
|
|
int16 len = font->getStringWidth(label);
|
|
font->drawString(&buf, label, x + (w / 2) - (len / 2), y + 2, len, 0x1a);
|
|
}
|
|
|
|
void ChinaTrain::drawCommandButtons() {
|
|
// The original has code to draw strings in the buttons, but in practice that was deleted
|
|
// so we can just do the simpler thing.
|
|
for (int i = 0; i < ARRAYSIZE(COMMAND_BUTTONS); i++) {
|
|
const int16 *btn = COMMAND_BUTTONS[i];
|
|
if (_pressedCommandButton == i)
|
|
shadePressButton(btn[0], btn[1]);
|
|
else
|
|
shadeButton(btn[0], btn[1]);
|
|
}
|
|
_pressedCommandButton = -1;
|
|
}
|
|
|
|
void ChinaTrain::shadePressButton(int16 x, int16 y) {
|
|
Graphics::ManagedSurface &compBuf = DgdsEngine::getInstance()->_compositionBuffer;
|
|
shadeBox(compBuf, 16, 20, 0, x, y, 12, 10);
|
|
shadeBox(compBuf, 20, 17, 0, x + 1, y + 1, 10, 8);
|
|
shadeBox(compBuf, 6, 16, 24, x + 2, y + 2, 8, 6);
|
|
}
|
|
|
|
void ChinaTrain::shadeButton(int16 x, int16 y) {
|
|
Graphics::ManagedSurface &compBuf = DgdsEngine::getInstance()->_compositionBuffer;
|
|
shadeBox(compBuf, 16, 20, 0, x, y, 12, 10);
|
|
shadeBox(compBuf, 17, 6, 23, x + 1, y + 1, 10, 8);
|
|
}
|
|
|
|
void ChinaTrain::drawScoreWindow() {
|
|
Graphics::ManagedSurface &compBuf = DgdsEngine::getInstance()->_compositionBuffer;
|
|
shadeBox(compBuf, 17, 6, 16, 0x18, 0x90, 0x41, 8);
|
|
shadeBox(compBuf, 6, 17, 13, 0x1b, 0x92, 0x3c, 4);
|
|
shadeBox(compBuf, 17, 6, 16, 0x5d, 0x90, 0x41, 8);
|
|
shadeBox(compBuf, 6, 17, 13, 0x60, 0x92, 0x3c, 4);
|
|
shadeBox(compBuf, 17, 6, 16, 0x3a, 0xa3, 0x41, 8);
|
|
shadeBox(compBuf, 6, 17, 13, 0x3d, 0xa5, 0x3c, 4);
|
|
drawScore();
|
|
}
|
|
|
|
void ChinaTrain::drawScore() {
|
|
Graphics::ManagedSurface &compBuf = DgdsEngine::getInstance()->_compositionBuffer;
|
|
|
|
if (1 < _players._lucky._hitpoints) {
|
|
int16 width = _players._lucky._hitpoints / 2;
|
|
if (25 < width) {
|
|
width += 8;
|
|
}
|
|
|
|
compBuf.fillRect(Common::Rect(Common::Point(0x1c, 0x93), width, 2), 10);
|
|
}
|
|
|
|
if (1 < _players._lucky._fatigue) {
|
|
int16 width = _players._lucky._fatigue / 2;
|
|
if (30 < width)
|
|
width += 8;
|
|
|
|
if (58 < width)
|
|
width = 58;
|
|
|
|
compBuf.fillRect(Common::Rect(Common::Point(0x61, 0x93), width, 2), 12);
|
|
}
|
|
|
|
if (0 < _players._lucky._ferocity) {
|
|
int16 width = _players._lucky._ferocity * 10;
|
|
if (58 < width)
|
|
width = 58;
|
|
|
|
compBuf.fillRect(Common::Rect(Common::Point(0x3e, 0xa6), width, 2), 0xe);
|
|
}
|
|
}
|
|
|
|
bool ChinaTrain::calcBounce(int16 car) {
|
|
return (_xOffset % 10 < car * 2 + 5 && car * 2 < _xOffset % 10);
|
|
}
|
|
|
|
void ChinaTrain::drawBmps() {
|
|
drawSnow();
|
|
drawTrain();
|
|
drawActors();
|
|
fixUpTunnel();
|
|
}
|
|
|
|
void ChinaTrain::drawActors() {
|
|
Graphics::ManagedSurface &compBuf = DgdsEngine::getInstance()->_compositionBuffer;
|
|
int16 bounce = calcBounce(_currentCar);
|
|
|
|
const PlayerData *luckyData = _players._lucky._data;
|
|
_luckyMaps->drawBitmap(luckyData->_frame, _players._lucky._xpos + luckyData->_xoff,
|
|
_players._lucky._ypos + bounce + luckyData->_yoff, _clipWin, compBuf, luckyData->_flipMode);
|
|
|
|
if (_players._tong._xpos < 0x5e)
|
|
bounce = calcBounce(_currentCar + 1);
|
|
else if (_players._tong._xpos >= 0xdd)
|
|
bounce = calcBounce(_currentCar - 1);
|
|
|
|
|
|
const PlayerData *tongData = _players._tong._data;
|
|
_luckyMaps->drawBitmap(tongData->_frame, _players._tong._xpos + tongData->_xoff,
|
|
_players._tong._ypos + bounce + tongData->_yoff, _clipWin, compBuf, tongData->_flipMode);
|
|
|
|
_players._lucky.checkDuck(_currentTunnel);
|
|
_players._tong.checkDuck(_currentTunnel);
|
|
}
|
|
|
|
void ChinaTrain::drawTrain() {
|
|
int16 xoff = -268;
|
|
for (int carNum = _currentCar + 2; carNum > -1; carNum--) {
|
|
int16 yoff = calcBounce(carNum);
|
|
if (carNum < 5 && TRAIN[carNum] != -1)
|
|
drawCar(xoff, TRAIN[carNum], yoff);
|
|
|
|
xoff += 176;
|
|
}
|
|
}
|
|
|
|
void ChinaTrain::drawCar(int16 xoff, int16 frame, int16 yoff) {
|
|
Graphics::ManagedSurface &compBuf = DgdsEngine::getInstance()->_compositionBuffer;
|
|
int16 cabooseX = 0;
|
|
if (_cabooseTrail) {
|
|
if (TRAIN[_currentCar] == 1 || frame != 1) {
|
|
if (TRAIN[_currentCar] == 1 && frame != 1)
|
|
cabooseX = _cabooseTrail;
|
|
} else {
|
|
cabooseX = -_cabooseTrail;
|
|
}
|
|
}
|
|
|
|
if (frame == 1)
|
|
yoff -= 5;
|
|
|
|
_test->drawBitmap(frame, xoff - _jumpOffset + cabooseX, yoff + 117, _clipWin, compBuf);
|
|
}
|
|
|
|
void ChinaTrain::drawSnow() {
|
|
Graphics::ManagedSurface &compBuf = DgdsEngine::getInstance()->_compositionBuffer;
|
|
if (_currentTunnel._start < 39 && _currentTunnel._start > 0)
|
|
_test->drawBitmap(0, _currentTunnel._start * 8 - 32, 84, _clipWin, compBuf, kImageFlipNone);
|
|
|
|
if (_currentTunnel._end < 29)
|
|
_test->drawBitmap(0, _currentTunnel._end * 8 + 0x68, 84, _clipWin, compBuf, kImageFlipH);
|
|
}
|
|
|
|
void ChinaTrain::fixBorder() {
|
|
Graphics::ManagedSurface &compBuf = DgdsEngine::getInstance()->_compositionBuffer;
|
|
const Common::Rect screenRect(SCREEN_WIDTH, SCREEN_HEIGHT);
|
|
_rectShape->drawBitmap(371, 0, 65, screenRect, compBuf);
|
|
_rectShape->drawBitmap(371, 312, 65, screenRect, compBuf);
|
|
}
|
|
|
|
void ChinaTrain::processInput() {
|
|
processOrders(_players._tong, _players._lucky);
|
|
processOrders(_players._lucky, _players._tong);
|
|
checkRegions(_players._lucky);
|
|
_players._lucky.doProcess();
|
|
checkRegions(_players._tong);
|
|
_players._tong.doProcess();
|
|
_players.doScroll(_jumpOffset);
|
|
}
|
|
|
|
void ChinaTrain::checkRegions(TrainPlayer &player) {
|
|
DgdsEngine *engine = DgdsEngine::getInstance();
|
|
|
|
if (checkGap(player._xpos, 0)) {
|
|
int16 nextX = player._xpos + _jumpOffset;
|
|
int16 mode;
|
|
if (nextX < 68)
|
|
mode = 0;
|
|
else if (nextX < 160)
|
|
mode = 1;
|
|
else if (nextX < 244)
|
|
mode = 2;
|
|
else
|
|
mode = 3;
|
|
|
|
if (player._action == kActionWalkRight) {
|
|
if (mode == 0 || (mode == 2 && _cabooseTrail == 0)) {
|
|
player.setAction(kActionJumpRight, true);
|
|
engine->_soundPlayer->playSFX(134);
|
|
}
|
|
//
|
|
// The original also has this code, but it can never execute because
|
|
// mode == 0 would have taken the above branch.
|
|
// There's also no code to handle kActionHeroicJump in doProcess,
|
|
// so it was probably vestigal.
|
|
//
|
|
// } else if (mode == 0 && _cabooseTrail != 0) {
|
|
// player.setAction(kActionHeroicJump, true);
|
|
// engine->_soundPlayer->playSFX(134);
|
|
// }
|
|
} else if (player._action == kActionWalkLeft) {
|
|
if ((mode == 1 || mode == 3) && (_players._tong._intent != 5 || _currentCar != 3 || &player == &_players._tong)) {
|
|
player.setAction(kActionJumpLeft, true);
|
|
engine->_soundPlayer->playSFX(134);
|
|
player._xpos = 95;
|
|
} else {
|
|
player.setAction(kActionStandLeft, true);
|
|
}
|
|
} else if (player._action == kActionStagger) {
|
|
if (&player == &_players._tong || engine->getDifficulty() == 0) {
|
|
if (mode == 1) {
|
|
player.setAction(kActionJumpLeft, true);
|
|
engine->_soundPlayer->playSFX(134);
|
|
player._xpos = 95;
|
|
} else if (_currentCar == 1) {
|
|
player.setAction(kActionFallRight, true);
|
|
engine->_soundPlayer->playSFX(131);
|
|
} else {
|
|
player.setAction(kActionJumpRight, true);
|
|
engine->_soundPlayer->playSFX(134);
|
|
}
|
|
} else if (mode == 1) {
|
|
player.setAction(kActionFallLeft, true);
|
|
engine->_soundPlayer->playSFX(131);
|
|
} else {
|
|
player.setAction(kActionFallRight, true);
|
|
engine->_soundPlayer->playSFX(131);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ChinaTrain::processOrders(TrainPlayer &player, TrainPlayer &enemy) {
|
|
if (!player.isStanding())
|
|
return;
|
|
|
|
switch (player._intent) {
|
|
case kIntentDuck:
|
|
if (_players._lucky._data->_flipMode == kImageFlipNone)
|
|
_players._lucky.setAction(kActionDuckRight, true);
|
|
else
|
|
_players._lucky.setAction(kActionDuckLeft, true);
|
|
|
|
break;
|
|
case kIntentRest:
|
|
if (player._xpos < enemy._xpos)
|
|
player.setAction(kActionStandRight, true);
|
|
else
|
|
player.setAction(kActionStandLeft, true);
|
|
|
|
player._ferocity = 1;
|
|
if (player._fatigue)
|
|
player._fatigue--;
|
|
|
|
break;
|
|
case kIntentPursue:
|
|
if (!player.inRange(enemy)) {
|
|
if (enemy._intent == kIntentPursue && enemy.isJumping()) {
|
|
if (player._data->_flipMode == kImageFlipNone)
|
|
player.setAction(kActionStandRight, true);
|
|
else
|
|
player.setAction(kActionStandLeft, true);
|
|
} else {
|
|
player.doPursue(enemy);
|
|
}
|
|
} else {
|
|
player._intent = kIntentAttack;
|
|
}
|
|
break;
|
|
case kIntentAttack:
|
|
if (!player.inRange(enemy))
|
|
player._intent = kIntentPursue;
|
|
else
|
|
player.doAttack(enemy);
|
|
break;
|
|
case kIntentRetreat:
|
|
if (abs(player._xpos - player._val7) < 5)
|
|
player._intent = kIntentRest;
|
|
else
|
|
player.doRun();
|
|
break;
|
|
case kIntentQ:
|
|
if ((160 - _jumpOffset) - (4 - _currentCar) * 160 < player._xpos) {
|
|
player.setAction(kActionWalkLeft, false);
|
|
} else {
|
|
player.setAction(kActionStandRight, true);
|
|
if (_cabooseTrail == 0)
|
|
_cabooseTrail = 1;
|
|
else if (_currentCar != 4)
|
|
_players._tong._xpos--;
|
|
}
|
|
break;
|
|
default:
|
|
error("Unexpected player intent value %d", (int)player._intent);
|
|
}
|
|
}
|
|
|
|
|
|
void ChinaTrain::onKeyDown(Common::KeyState kbd) {
|
|
switch (kbd.keycode) {
|
|
case Common::KEYCODE_a: _lastKeycode = 0x1e; break;
|
|
case Common::KEYCODE_q: _lastKeycode = 0x10; break;
|
|
case Common::KEYCODE_r: _lastKeycode = 0x13; break;
|
|
case Common::KEYCODE_s: _lastKeycode = 0x1f; break;
|
|
case Common::KEYCODE_d: _lastKeycode = 0x20; break;
|
|
default: break;
|
|
}
|
|
}
|
|
|
|
|
|
void ChinaTrain::onKeyUp(Common::KeyState kbd) {
|
|
byte code_to_clear = 0;
|
|
switch (kbd.keycode) {
|
|
case Common::KEYCODE_a: code_to_clear = 0x1e; break;
|
|
case Common::KEYCODE_q: code_to_clear = 0x10; break;
|
|
case Common::KEYCODE_r: code_to_clear = 0x13; break;
|
|
case Common::KEYCODE_s: code_to_clear = 0x1f; break;
|
|
case Common::KEYCODE_d: code_to_clear = 0x20; break;
|
|
default: break;
|
|
}
|
|
if (_lastKeycode == code_to_clear)
|
|
_lastKeycode = 0;
|
|
}
|
|
|
|
|
|
|
|
} // end namespace Dgds
|