[Scummvm-git-logs] scummvm master -> ef64a5c814c37c81fa60e2b5994ec785772efd48

mduggan noreply at scummvm.org
Sat Dec 14 01:05:46 UTC 2024


This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
ef64a5c814 DGDS: Implement HoC train mini game


Commit: ef64a5c814c37c81fa60e2b5994ec785772efd48
    https://github.com/scummvm/scummvm/commit/ef64a5c814c37c81fa60e2b5994ec785772efd48
Author: Matthew Duggan (mgithub at guarana.org)
Date: 2024-12-14T12:05:30+11:00

Commit Message:
DGDS: Implement HoC train mini game

Changed paths:
    engines/dgds/dgds.cpp
    engines/dgds/globals.h
    engines/dgds/image.cpp
    engines/dgds/image.h
    engines/dgds/menu.cpp
    engines/dgds/minigames/china_train.cpp
    engines/dgds/minigames/china_train.h
    engines/dgds/scene.cpp
    engines/dgds/scene.h


diff --git a/engines/dgds/dgds.cpp b/engines/dgds/dgds.cpp
index af8dc46f44f..ade2606fc88 100644
--- a/engines/dgds/dgds.cpp
+++ b/engines/dgds/dgds.cpp
@@ -260,8 +260,10 @@ void DgdsEngine::setMouseCursor(int num) {
 	if (!_icons || num >= _icons->loadedFrameCount())
 		return;
 
-	if ((int)num == _currentCursor)
+	if (num == _currentCursor) {
+		CursorMan.showMouse(true);
 		return;
+	}
 
 	const Common::Array<MouseCursor> &cursors = _gdsScene->getCursorList();
 
@@ -588,9 +590,13 @@ Common::Error DgdsEngine::run() {
 			} else if (ev.type == Common::EVENT_KEYDOWN) {
 				if (_dragonArcade)
 					_dragonArcade->onKeyDown(ev.kbd);
+				if (_chinaTrain)
+					_chinaTrain->onKeyDown(ev.kbd);
 			} else if (ev.type == Common::EVENT_KEYUP) {
 				if (_dragonArcade)
 					_dragonArcade->onKeyUp(ev.kbd);
+				if (_chinaTrain)
+					_chinaTrain->onKeyUp(ev.kbd);
 			}
 		}
 
diff --git a/engines/dgds/globals.h b/engines/dgds/globals.h
index e901e8f0550..443e4e70916 100644
--- a/engines/dgds/globals.h
+++ b/engines/dgds/globals.h
@@ -164,6 +164,9 @@ public:
 	int16 getNativeGameState() const { return _nativeGameState; }
 	void setNativeGameState(int16 state) { _nativeGameState = state; }
 
+	int16 getTrainState() const { return _trainState; }
+	void setTrainState(int16 state) { _trainState = state; }
+
 	int16 getIntroState() const { return _introState; }
 	void setIntroState(int16 state) { _introState = state; }
 
diff --git a/engines/dgds/image.cpp b/engines/dgds/image.cpp
index 80efa3712e6..1773b0e8919 100644
--- a/engines/dgds/image.cpp
+++ b/engines/dgds/image.cpp
@@ -351,6 +351,11 @@ void Image::drawScrollBitmap(int16 x, int16 y, int16 width, int16 height, int16
 	}
 }
 
+int16 Image::getFrameFromMatrix(int16 x, int16 y) {
+	assert(x >= 0 && y >= 0 && x < _matrixX && y < _matrixY);
+	return _tileMatrix[_matrixY * x + y];
+}
+
 
 void Image::loadBitmap4(Graphics::ManagedSurface *surf, uint32 toffset, Common::SeekableReadStream *stream, bool highByte, uint16 tw, uint16 th) {
 	assert(th != 0);
diff --git a/engines/dgds/image.h b/engines/dgds/image.h
index 3cab5979d7a..c25c2ce4f9c 100644
--- a/engines/dgds/image.h
+++ b/engines/dgds/image.h
@@ -64,6 +64,8 @@ public:
 
 	const Common::Array<Common::SharedPtr<Graphics::ManagedSurface>> &getFrames() const { return _frames; }
 
+	int16 getFrameFromMatrix(int16 x, int16 y);
+
 	int16 width(uint frameno) const;
 	int16 height(uint frameno) const;
 
diff --git a/engines/dgds/menu.cpp b/engines/dgds/menu.cpp
index 1ea592d7c83..7f296f29a63 100644
--- a/engines/dgds/menu.cpp
+++ b/engines/dgds/menu.cpp
@@ -36,6 +36,7 @@
 #include "dgds/request.h"
 #include "dgds/scene.h"
 #include "dgds/sound.h"
+#include "dgds/minigames/china_train.h"
 
 namespace Dgds {
 
@@ -120,12 +121,12 @@ enum MenuButtonIds {
 	kMenuRestartYes = 163,
 	kMenuRestartNo = 164,
 
-	// For Dragon arcade
-	kMenuDragonReplayArcadeYes = 139,
-	kMenuDragonReplayArcadeNo = 140,
+	// For Dragon/HOC arcade
+	kMenuReplayArcadeYes = 139,
+	kMenuReplayArcadeNo = 140,
 
-	kMenuDragonFrustratedArcadeWin = 147,
-	kMenuDragonFrustratedArcadeKeepTrying = 148,
+	kMenuFrustratedArcadeWin = 147,
+	kMenuFrustratedArcadeKeepTrying = 148,
 
 	kMenuGameOverQuit = 169,
 	kMenuGameOverRestart = 168,
@@ -419,7 +420,7 @@ void Menu::handleClick(const Common::Point &mouse) {
 	//case kMenuCalibratePlay:
 	//case kMenuCalibratePlayHoC:
 	//case kMenuMouseCalibrationPlay:
-		_curMenu = kMenuNone;
+		hideMenu();
 		CursorMan.showMouse(false);
 		break;
 	case kMenuMainControls:
@@ -544,21 +545,29 @@ void Menu::handleClick(const Common::Point &mouse) {
 		drawMenu(_curMenu);
 		break;
 	}
-	case kMenuDragonReplayArcadeYes:
-	case kMenuDragonReplayArcadeNo:
-		if (engine->getGameId() == GID_DRAGON && _curMenu == kMenuReplayArcade) {
+	case kMenuReplayArcadeYes:
+	case kMenuReplayArcadeNo:
+		if (_curMenu != kMenuReplayArcade)
+			break;
+		if (engine->getGameId() == GID_DRAGON) {
 			DragonGlobals *dragonGlobals = static_cast<DragonGlobals *>(engine->getGameGlobals());
-			dragonGlobals->setArcadeState(clickedMenuItem == kMenuDragonReplayArcadeYes ? 20 : 10);
-			_curMenu = kMenuNone;
+			dragonGlobals->setArcadeState(clickedMenuItem == kMenuReplayArcadeYes ? 20 : 10);
+		} else if (engine->getGameId() == GID_HOC) {
+			engine->getChinaTrain()->setMenuResult(clickedMenuItem == kMenuReplayArcadeYes);
 		}
+		hideMenu();
 		break;
-	case kMenuDragonFrustratedArcadeWin:
-	case kMenuDragonFrustratedArcadeKeepTrying:
-		if (engine->getGameId() == GID_DRAGON && _curMenu == kMenuArcadeFrustrated) {
+	case kMenuFrustratedArcadeWin:
+	case kMenuFrustratedArcadeKeepTrying:
+		if (_curMenu != kMenuArcadeFrustrated)
+			break;
+		if (engine->getGameId() == GID_DRAGON) {
 			DragonGlobals *dragonGlobals = static_cast<DragonGlobals *>(engine->getGameGlobals());
-			dragonGlobals->setArcadeState(clickedMenuItem == kMenuDragonFrustratedArcadeWin ? 6 : 20);
-			_curMenu = kMenuNone;
+			dragonGlobals->setArcadeState(clickedMenuItem == kMenuFrustratedArcadeWin ? 6 : 20);
+		} else if (engine->getGameId() == GID_HOC) {
+			engine->getChinaTrain()->setMenuResult(clickedMenuItem == kMenuFrustratedArcadeKeepTrying);
 		}
+		hideMenu();
 		break;
 	case kMenuTankTrainSkipArcade:
 		hideMenu();
@@ -568,12 +577,14 @@ void Menu::handleClick(const Common::Point &mouse) {
 			engine->changeScene(106);	// skip train mini-game
 		break;
 	case kMenuTankTrainPlayArcade:
-		// TODO
-		if (currentScene == 73)
-			warning("Play tank mini-game");
-		else if (currentScene == 84)
-			warning("Play train mini-game");
-		drawMenu(_curMenu);
+		if (currentScene == 73) {
+			// Tank game - not implemented.
+			warning("TODO: Play tank mini-game");
+			drawMenu(_curMenu);
+		} else if (currentScene == 84) {
+			// Play train game - open the "save before arcade" menu.
+			drawMenu(kMenuSaveBeforeArcade);
+		}
 		break;
 	default:
 		debug(1, "Clicked ID %d", clickedMenuItem);
diff --git a/engines/dgds/minigames/china_train.cpp b/engines/dgds/minigames/china_train.cpp
index 446bc0949c3..7489c2f90a6 100644
--- a/engines/dgds/minigames/china_train.cpp
+++ b/engines/dgds/minigames/china_train.cpp
@@ -22,22 +22,1528 @@
 #include "common/system.h"
 
 #include "dgds/dgds.h"
+#include "dgds/drawing.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 {
 
-ChinaTrain::ChinaTrain() {
+/* Not used anywhere.
+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 &currentTunnel) {
+	// 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 &currentTunnel = 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 ChinaTrain::init() {
+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 ChinaTrain::tick() {
-	g_system->displayMessageOnOSD(Common::U32String("Train minigame not implemented yet!"));
-	DgdsEngine::getInstance()->setMenuToTrigger(kMenuSkipArcade);
+void TrainPlayers::freeAnims() {
+	_lucky._allData.clear();
+	_tong._allData.clear();
+	_lucky._currentActionData = nullptr;
+	_lucky._data = nullptr;
+	_tong._currentActionData = nullptr;
+	_tong._data = nullptr;
 }
 
-void ChinaTrain::end() {
+
+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(-1);
+
+	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, "FEROICTY");
+	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);
+			} 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
diff --git a/engines/dgds/minigames/china_train.h b/engines/dgds/minigames/china_train.h
index fa783ee9004..d9505c150fd 100644
--- a/engines/dgds/minigames/china_train.h
+++ b/engines/dgds/minigames/china_train.h
@@ -24,18 +24,229 @@
 
 namespace Dgds {
 
+enum PlayerType {
+	kPlayerLucky,
+	kPlayerTong,
+};
+
+enum PlayerAction {
+	// The list of actions is not directly referenced in the game, but is in
+	// the exe just after the "Heart of China Train Game" copyright
+	kActionStandRight = 0,
+	kActionWalkRight = 1,
+	kActionWalkLeft = 2,
+	kActionJumpRight = 3,
+	kActionJumpLeft = 4,
+	kActionDuckRight = 5,
+	kActionFallRight = 6,
+	kActionFallLeft = 7,
+	kActionStagger = 8,
+	kActionDeathScene = 9,
+	kActionClub = 10,
+	kActionClubHit = 11,
+	kActionSwing = 12,
+	kActionSwingHit = 13,
+	kActionStab = 14,
+	kActionStabHit = 15,
+	kActionBlock = 16,
+	kActionHeroicJump = 17,
+	kActionBlockUp = 18,
+	kActionFree6 = 19,
+	kActionStandLeft = 20,
+	kActionDuckLeft = 21,
+	kActionFree7 = 22,
+	kActionFree8 = 23,
+	kActionERROR = 24,
+};
+
+enum PlayerIntent {
+	kIntentDuck = 0,
+	kIntentRest = 1,
+	kIntentPursue = 2,
+	kIntentAttack = 3,
+	kIntentRetreat = 4,
+	kIntentQ = 5,
+	kIntentInvalid = 255,
+};
+
+class TrainPlayers;
+
+struct PlayerData {
+	int16 _frame;			 // field 0
+	ImageFlipMode _flipMode; // field 1
+	int16 _xstep;			 // field 2
+	int16 _ystep;			 // field 3
+	int16 _xoff;			 // field 4
+	int16 _yoff;			 // field 5
+	int16 _val6;			 // field 6
+};
+
+struct TunnelData {
+	int32 _start;
+	int32 _end;
+};
+
+class TrainPlayer {
+public:
+	friend class TrainPlayers;
+	TrainPlayer(PlayerType type);
+
+	bool isBlocking() const { return _action == kActionBlock || _action == kActionBlockUp; }
+	bool isDucking() const { return _action == kActionDuckRight || _action == kActionDuckLeft; }
+	bool isFalling() const { return _action == kActionFallRight || _action == kActionFallLeft; }
+	bool isJumping() const { return _action == kActionJumpRight || _action == kActionJumpRight; }
+	bool isStaggering() const { return _action == kActionStagger; }
+	bool isStanding() const { return _action == kActionStandRight || _action == kActionStandLeft; }
+	bool isWalking() const { return _action == kActionWalkLeft || _action == kActionWalkRight; }
+
+	bool isTong() const { return _type == kPlayerTong; }
+	bool isLucky() const { return _type == kPlayerLucky; }
+
+	void checkLives();
+	void doAttack(TrainPlayer &other);
+	void doBlock();
+	void doClub(int16 damage);
+	void doPursue(const TrainPlayer &other);
+	void doRun();
+	void doJump();
+	void setAction(PlayerAction state, bool flag);
+	bool inRange(const TrainPlayer &other);
+	void hit(int16 damage);
+	void startStagger(const TrainPlayer &other);
+	void readStuff(const Common::String &path);
+	void computerDucks();
+	void checkDuck(const TunnelData &currentTunnel);
+	void doProcess();
+
+	PlayerType _type;
+	int16 _xpos;
+	int16 _ypos;
+	PlayerAction _action;
+	int16 _fatigue;
+	int16 _hitpoints;
+	PlayerIntent _intent;
+	int16 _ferocity;
+	int16 _val7;
+	Common::Array<Common::Array<PlayerData>> _allData;
+	Common::Array<PlayerData> *_currentActionData;
+	PlayerData *_data;
+
+private:
+	TrainPlayer &chooseEnemy();
+	PlayerData *endOfCurrentAction();
+	PlayerData *startOfCurrentAction();
+
+	static int16 _blockSoundFlag;
+};
+
+class TrainPlayers {
+public:
+	TrainPlayers();
+
+	void initPlayers();
+	void checkLives();
+	void readAnims();
+	void freeAnims();
+	void doScroll(int16 jumpOffset);
+
+	TrainPlayer _lucky;
+	TrainPlayer _tong;
+};
+
+
 /** Train fight mini-game for Heart of China */
 class ChinaTrain {
 public:
 	ChinaTrain();
 
-	void init();
-	void tick();
-	void end();
+	void init(); 	// aka arcadeInit
+	int16 tick(); 	// aka arcadeLoop
+	void end();  	// aka aracdeReset
+
+	//int16 currentCar() const { return _currentCar; }
+	void checkTongFall(int16 xpos, int16 car);
+	TrainPlayers &getPlayers() { return _players; }
+	const TunnelData &getCurrentTunnel() const { return _currentTunnel; }
+	void leaveArcade();
+	bool checkGap(int16 xpos, int16 offset);
+
+	void onKeyDown(Common::KeyState kbd);
+	void onKeyUp(Common::KeyState kbd);
+	void setMenuResult(bool yes);
+
+	int16 _jumpOffset;
 
 private:
-	// TODO add private members
+	void arcadeFadeout();
+	void getUserInput();
+	void getNpcInput();
+	void processInput();
+	void handleVariables();
+	void drawFrame();
+	int16 trainArcade();
+	void trainRestart();
+	void lost();
+	void makeNewTunnel();
+	//void setupArcade(); // just sets fill=1 and _clipWin.
+	void initScoreWindow();
+	void drawBorder();
+	void shadeLabel(int16 x, int16 y, int16 w, int16 h, const char *label);
+	void shadeBox(Graphics::ManagedSurface &buf, byte backCol, byte foreCol, byte fill, int16 x, int16 y, int16 w, int16 h);
+
+	void fixBorder();
+	void drawCommandButtons();
+	void drawBmps();
+	void drawTunnel();
+	void drawMountains(int16 num);
+	void drawBlock(int16 x1, int16 x2, int16 param_3, int16 y1, int16 y2, int16 param_6, int16 param_7, int16 param_8);
+	void drawButtons(int16 *buttons);
+	void shadePressButton(int16 x, int16 y);
+	void shadeButton(int16 x, int16 y);
+	void drawScoreWindow();
+	void drawScore();
+	void drawSnow();
+	void drawTrain();
+	void drawActors();
+	void drawCar(int16 xoff, int16 frame, int16 yoff);
+	void fixUpTunnel();
+	bool calcBounce(int16 car);
+	void checkRegions(TrainPlayer &player);
+	void processOrders(TrainPlayer &player, TrainPlayer &enemy);
+	int16 readButtons();
+	void cabooseLost();
+
+	int16 _arcadeCount;
+	int16 _arcadeFlag;
+	bool _arcadeInitFlag;
+	int16 _arcadeDrawFlag;
 
+	int16 _failCounter;
+	int16 _lastMaskedArcadeFlag;
+	int16 _int3036;
+	int16 _tongAttackCounter;
+	int16 _tongInjuredCounter;
+	int16 _tongRestTarget;
+	int16 _lastTongHP;
+	int16 _lastTongFatigue;
+	TrainPlayers _players;
+	int16 _currentCar;
+	int32 _trackPos;
+	int32 _frameCnt;
+	int16 _xOffset;
+	int16 _tunnelNum;
+	TunnelData _currentTunnel;
+	int16 _cabooseTrail;
+	int16 _lastBtn;
+	const Common::Rect _clipWin;
+	//Common::SharedPtr<Image> _simChars;
+	Common::SharedPtr<Image> _rectShape;
+	Common::SharedPtr<Image> _luckyMaps;
+	Common::SharedPtr<Image> _test;
+	int16 _pressedCommandButton;
+	bool _leftButtonDown;
+	bool _rightButtonDown;
+	int16 _lastKeycode;
+	bool _playedTunnelSFX;
 };
 
 } // end namespace Dgds
diff --git a/engines/dgds/scene.cpp b/engines/dgds/scene.cpp
index 0150fed0df7..83bdeb56bdd 100644
--- a/engines/dgds/scene.cpp
+++ b/engines/dgds/scene.cpp
@@ -290,7 +290,7 @@ bool Scene::readConditionList(Common::SeekableReadStream *s, Common::Array<Scene
 	for (uint16 i = 0; i < num; i++) {
 		uint16 cnum = s->readUint16LE();
 		SceneCondition cond = static_cast<SceneCondition>(s->readUint16LE());
-		uint16 val = s->readUint16LE();
+		int16 val = s->readSint16LE();
 		list.push_back(SceneConditions(cnum, cond, val));
 	}
 	return !s->err();
@@ -788,7 +788,6 @@ bool Scene::runChinaOp(const SceneOp &op) {
 		break;
 	case kSceneOpChinaTankTick:
 		engine->getChinaTank()->tick();
-		//engine->setMenuToTrigger(kMenuSkipArcade);
 		break;
 	case kSceneOpShellGameTick:
 		engine->getShellGame()->shellGameTick();
diff --git a/engines/dgds/scene.h b/engines/dgds/scene.h
index 5b7ccd89113..75d7412af17 100644
--- a/engines/dgds/scene.h
+++ b/engines/dgds/scene.h
@@ -51,17 +51,17 @@ enum SceneCondition {
 
 class SceneConditions {
 public:
-	SceneConditions(uint16 num, SceneCondition cond, uint16 val) : _num(num), _flags(cond), _val(val) {}
+	SceneConditions(uint16 num, SceneCondition cond, int16 val) : _num(num), _flags(cond), _val(val) {}
 	Common::String dump(const Common::String &indent) const;
 
 	uint16 getNum() const { return _num; }
 	SceneCondition getCond() const { return _flags; }
-	uint16 getVal() const { return _val; }
+	int16 getVal() const { return _val; }
 
 private:
 	uint16 _num;
 	SceneCondition _flags; /* eg, see usage in FUN_1f1a_2106 */
-	uint16 _val;
+	int16 _val;
 };
 
 class HotArea {




More information about the Scummvm-git-logs mailing list