/* 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 .
*
*/
#include "gamos/console.h"
#include "gamos/detection.h"
#include "gamos/gamos.h"
#include "graphics/cursorman.h"
#include "graphics/framelimiter.h"
#include "graphics/paletteman.h"
#include "common/config-manager.h"
#include "common/debug-channels.h"
#include "common/endian.h"
#include "common/events.h"
#include "common/keyboard.h"
#include "common/rect.h"
#include "common/scummsys.h"
#include "common/system.h"
#include "common/util.h"
#include "engines/util.h"
#include "audio/mididrv.h"
#include "audio/midiplayer.h"
namespace Gamos {
GamosEngine::GamosEngine(OSystem *syst, const GamosGameDescription *gameDesc) : Engine(syst),
_gameDescription(gameDesc),
_messageProc(this),
_vm(this, callbackVMCallDispatcher),
_txtInputVMAccess(_vm) {}
GamosEngine::~GamosEngine() {
freeImages();
freeSequences();
delete _screen;
}
void GamosEngine::freeImages() {
for (Image *img : _images)
delete img;
_images.clear();
}
void GamosEngine::freeSequences() {
for (ImageSeq *seq : _imgSeq)
delete seq;
_imgSeq.clear();
}
Common::Error GamosEngine::run() {
// Set the engine's debugger console
setDebugger(new Console());
// If a savegame was selected from the launcher, load it
int saveSlot = ConfMan.getInt("save_slot");
if (saveSlot != -1)
(void)loadGameState(saveSlot);
CursorMan.setDefaultArrowCursor();
CursorMan.showMouse(true);
if (getGameLanguage() == Common::RU_RUS)
setCP1251();
else
setCP1252();
_engineVersion = getEngineVersion() & 0xFF;
_arch.setVersion(_engineVersion);
init(getRunFile());
Common::Event e;
while (!shouldQuit()) {
Common::Point prevMousePos = _messageProc._mouseReportedPos;
while (_system->getEventManager()->pollEvent(e)) {
_messageProc.processMessage(e);
}
uint32 curTime = _system->getMillis();
if (curTime >= _lastTimeStamp + _delayTime) {
_lastTimeStamp = curTime;
if (_messageProc._inputFlags & 2) {
}
uint8 result = 2;
while (result == 2) {
result = update({}, _messageProc._mouseReportedPos, _messageProc._mouseActPos, _messageProc._act2, _messageProc._act1, _messageProc._rawKeyCode, true);
}
if (!result)
break;
_messageProc._act2 = ACT_NONE;
_messageProc._act1 = ACT_NONE;
_messageProc._rawKeyCode = ACT_NONE;
} else {
if (prevMousePos != _messageProc._mouseReportedPos)
_system->updateScreen();
_system->delayMillis(1);
}
//if (_delayTime)
}
stopSounds();
stopMidi();
stopCDAudio();
_enableMovie = true;
_enableMidi = true;
_enableSounds = true;
_enableInput = true;
_isResLoadingProcess = true;
writeStateFile();
return Common::kNoError;
}
bool GamosEngine::loader2() {
int32 skipsz = _arch.readSint32LE();
_arch.skip(skipsz);
if (_arch.readByte() != 7)
return false;
RawData data;
if (!_arch.readCompressedData(&data))
return false;
int32 p1 = 0;
int32 p2 = 0;
int32 pid = 0;
byte resType = 0;
int32 resSize = 0;
Common::MemoryReadStream dataStream(data.data(), data.size());
while (!dataStream.eos()) {
byte curByte = dataStream.readByte();
if (curByte == 0) {
break;
} else if (curByte == 0x80) {
p1 = 0;
p2 = 0;
pid = dataStream.readSint32LE();
} else if (curByte == 1) {
p1 = dataStream.readSint32LE();
} else if (curByte == 2) {
p2 = dataStream.readSint32LE();
} else if (curByte == 7) {
/*int32 needsz = dataStream.readSint32LE(); // check free mem ? */
dataStream.skip(4);
} else if (curByte == 0x40) {
resSize = 4;
resType = 0x40;
if (!loadResHandler(resType, pid, p1, p2, 0, data.data() + dataStream.pos(), resSize))
return false;
dataStream.skip(resSize);
} else if (curByte == 0x41 || curByte == 0x42) {
resSize = dataStream.readSint32LE();
resType = curByte;
if (!loadResHandler(resType, pid, p1, p2, 0, data.data() + dataStream.pos(), resSize))
return false;
_loadedDataSize += (resSize + 3) & (~3);
dataStream.skip(resSize);
} else if (curByte == 0x43) {
resSize = 0x10;
resType = 0x43;
if (!loadResHandler(resType, pid, p1, p2, 0, data.data() + dataStream.pos(), resSize))
return false;
_loadedDataSize += (resSize + 3) & (~3);
dataStream.skip(resSize);
} else if (curByte == 0xff) {
//warning("0xFF %d %d %d", pid, p1, p2);
if (!reuseLastResource(resType, pid, p1, p2, 0))
return false;
} else {
warning("loader2 want %x", curByte);
return false;
}
}
return true;
}
bool GamosEngine::loadModule(uint id) {
_keySeq.clear();
if ((!_isResLoadingProcess && !writeStateFile()) ||
!_arch.seekDir(1))
return false;
_currentModuleID = id;
const byte targetDir = 2 + id;
//DAT_004126e4 = 1;
_currentGameScreen = -1;
_readingBkgMainId = -1;
_countReadedBkg = 0;
//DAT_004126ec = 0;
//INT_004126e8 = 0;
_xorSeq[0].clear();
_xorSeq[1].clear();
_xorSeq[2].clear();
stopMidi();
stopCDAudio();
stopSounds();
/* Complete me */
bool prefixLoaded = false;
byte prevByte = 0;
bool doLoad = true;
int32 p1 = 0;
int32 p2 = 0;
int32 p3 = 0;
int32 pid = 0;
while (doLoad) {
byte curByte = _arch.readByte();
switch (curByte) {
case 0:
if (prefixLoaded) {
doLoad = false;
break;
}
prefixLoaded = true;
if (!_arch.seekDir(targetDir))
return false;
break;
case CONFTP_P1:
p1 = _arch.readPackedInt();
break;
case CONFTP_P2:
p2 = _arch.readPackedInt();
break;
case CONFTP_P3:
p3 = _arch.readPackedInt();
break;
case 4: {
_resReadOffset = _arch.pos();
bool isResource = true;
if (prevByte == RESTP_GAMECONF) {
RawData data;
if (!_arch.readCompressedData(&data))
return false;
if (_isResLoadingProcess && !_isSaveLoadingProcess)
readData2(data);
if (!_isSaveLoadingProcess) {
_scrollY = 0;
_scrollX = 0;
_scrollTrackObj = -1;
_scrollSpeed = 16;
_scrollCutoff = 80;
_scrollSpeedReduce = -1;
_scrollBorderB = 0;
_scrollBorderU = 0;
_scrollBorderR = 0;
_scrollBorderL = 0;
}
isResource = false; /* do not loadResHandler */
} else if (prevByte == RESTP_GAMECONF2) {
if (!initMainDatas())
return false;
isResource = false; /* do not loadResHandler */
} else if (prevByte == RESTP_DATACONF) {
RawData data;
if (!_arch.readCompressedData(&data))
return false;
if (pid == id)
readElementsConfig(data);
isResource = false; /* do not loadResHandler */
} else if (prevByte == RESTP_BKG) {
/* free elements ? */
_readingBkgOffset = _arch.pos();
_countReadedBkg++;
}
RawData data;
if (isResource) {
if (!_arch.readCompressedData(&data))
return false;
if (!loadResHandler(prevByte, pid, p1, p2, p3, data))
return false;
}
uint32 datasz = (data.size() + 3) & (~3);
switch (prevByte) {
case RESTP_DATACONF:
case RESTP_BKG:
case RESTP_INITACT:
case RESTP_ACT_INFO:
case RESTP_SPR_INFO:
case RESTP_UNKNOWN_50:
break;
case RESTP_SPR_SEQIMGDATA:
//warning("t %x sz %x sum %x", prevByte, data.size(), _loadedDataSize);
if (_onlyScanImage)
_loadedDataSize += 0x10;
else
_loadedDataSize += datasz;
break;
default:
//warning("t %x sz %x sum %x", prevByte, data.size(), _loadedDataSize);
_loadedDataSize += datasz;
break;
}
break;
}
case 5: {
byte t = _arch.readByte();
if (t == 0 || (t & 0xec) != 0xec)
return false;
byte sz = (t & 3) + 1;
int32 movieSize = 0;
for (uint i = 0; i < sz; ++i)
movieSize |= _arch.readByte() << (i * 8);
if (prevByte == 0x14)
_movieOffsets[pid] = _arch.pos();
_arch.skip(movieSize);
break;
}
case 6:
if (!loader2())
return false;
break;
case 0xFF:
if (!reuseLastResource(prevByte, pid, p1, p2, 0))
return false;
break;
default:
p1 = 0;
p2 = 0;
p3 = 0;
pid = 0;
prevByte = curByte & CONFTP_RESMASK;
if ((curByte & CONFTP_IDFLG) == 0)
pid = _arch.readPackedInt();
break;
}
}
//FUN_00404a28();
if (_isSaveLoadingProcess)
return true;
// Reverse Here
setCursor(0, false);
if (!loadStateFile())
return false;
int bkg = _readingBkgMainId;
if (bkg == -1)
bkg = 0;
if (!switchToGameScreen(bkg, false))
return false;
return true;
}
bool GamosEngine::loadResHandler(uint tp, uint pid, uint p1, uint p2, uint p3, const byte *data, size_t dataSize) {
if (tp == RESTP_VMSTATE) {
Common::MemoryReadStream dataStream(data, dataSize, DisposeAfterUse::NO);
_addrBlk12 = _loadedDataSize;
_addrFPS = _loadedDataSize + 1;
_addrKeyDown = _loadedDataSize + 2;
_addrKeyCode = _loadedDataSize + 3;
_addrCurrentFrame = _loadedDataSize + 4;
_vm.memory().setU8(_addrBlk12, dataStream.readByte());
dataStream.skip(1);
_vm.memory().setU8(_addrFPS, _fps);
_vm.memory().setU8(_addrKeyDown, dataStream.readByte());
_vm.memory().setU8(_addrKeyCode, dataStream.readByte());
_vm.memory().setU32(_addrCurrentFrame, dataStream.readUint32LE());
setFPS(_fps);
} else if (tp == RESTP_VMDATA) {
_vm.writeMemory(_loadedDataSize, data, dataSize);
} else if (tp == RESTP_BKG) {
loadBackground(pid, data, dataSize);
} else if (tp == RESTP_INITACT) {
if (!_isSaveLoadingProcess) {
for (int i = 0; i < _states.size(); i++)
_states.at(i) = ObjState(0xfe, 0, 0xf);
_ignoreSoundActions = true;
Actions acts;
acts.parse(data, dataSize);
doActions(acts, true);
if (_needReload)
warning("needs reload from loadResHandler, CANT HAPPEN!");
_ignoreSoundActions = false;
storeToGameScreen(pid);
}
} else if (tp == RESTP_ACT_INFO) {
if (dataSize != 4)
return false;
_objectActions[pid].actType = data[0];
_objectActions[pid].mask = data[1];
_objectActions[pid].priority = data[2];
_objectActions[pid].storageSize = data[3] + 1;
} else if (tp == RESTP_ACT_ONCREATE) {
_vm.writeMemory(_loadedDataSize, data, dataSize);
_objectActions[pid].onCreateAddress = _loadedDataSize + p3;
//warning("RESTP_ACT_ONCREATE %x pid %d sz %x", _loadedDataSize, pid, dataSize);
} else if (tp == RESTP_ACT_ONDELETE) {
_vm.writeMemory(_loadedDataSize, data, dataSize);
_objectActions[pid].onDeleteAddress = _loadedDataSize + p3;
//warning("RESTP_ACT_ONDELETE %x pid %d sz %x", _loadedDataSize, pid, dataSize);
} else if (tp == RESTP_ACT_COUNT) {
if (dataSize % 4 != 0 || dataSize < 4)
return false;
_objectActions[pid].actions.resize(dataSize / 4);
} else if (tp == RESTP_ACT_DATA) {
Actions &scr = _objectActions[pid].actions[p1];
scr.parse(data, dataSize);
} else if (tp == RESTP_ACT_COND) {
_vm.writeMemory(_loadedDataSize, data, dataSize);
_objectActions[pid].actions[p1].conditionAddress = _loadedDataSize + p3;
//warning("RESTP_ACT_COND %x pid %d p1 %d sz %x", _loadedDataSize, pid, p1, dataSize);
} else if (tp == RESTP_ACT_FUNC) {
_vm.writeMemory(_loadedDataSize, data, dataSize);
_objectActions[pid].actions[p1].functionAddress = _loadedDataSize + p3;
//warning("RESTP_ACT_FUNC %x pid %d p1 %d sz %x", _loadedDataSize, pid, p1, dataSize);
} else if (tp == RESTP_UNK_MASKS) {
//warning("Data 38 size %zu", dataSize);
_thing2[pid].masks.assign(data, data + dataSize);
} else if (tp == RESTP_UNK_OIDS) {
if (data[0] == 0)
_thing2[pid].oids.clear();
else
_thing2[pid].oids.assign(data + 1, data + 1 + data[0]);
} else if (tp == RESTP_UNK_ACTST) {
_thing2[pid].actsT.assign(data, data + dataSize);
} else if (tp == RESTP_SPR_INFO) {
return loadSpriteInfo(pid, data, dataSize);
} else if (tp == RESTP_SPR_SEQLEN) {
return loadSpriteSeqLength(pid, data, dataSize);
} else if (tp == RESTP_SPR_SEQIMGINFO) {
return loadSpriteSeqImageInfo(pid, p1, data, dataSize);
} else if (tp == RESTP_SPR_SEQIMGDATA) {
return loadSpriteSeqImageData(pid, p1, p2, data, dataSize);
} else if (tp == RESTP_UNKNOWN_50) {
/* just ignore it? */
} else if (tp == RESTP_SFX_SAMPLE) {
uint32 datSz = getU32(data) & (~3);
_soundSamples[pid].assign(data + 4, data + 4 + datSz);
//warning("sound size %d", dataSize);
} else if (tp == RESTP_MIDI_TRACK) {
return loadMidiTrack(pid, data, dataSize);
//warning("midi size %d", dataSize);
} else if (tp == RESTP_SUB_ACT) {
_subtitleActions[pid].parse(data, dataSize);
} else if (tp == RESTP_SUB_PLACE) {
Common::MemoryReadStream dataStream(data, dataSize, DisposeAfterUse::NO);
const int count = dataSize / 8;
_subtitlePoints[pid].resize(count);
for (int i = 0; i < count; i++) {
SubtitlePoint &d = _subtitlePoints[pid][i];
d.x = dataStream.readSint16LE();
d.y = dataStream.readSint16LE();
d.sprId = dataStream.readUint16LE();
dataStream.skip(2);
}
} else if (tp == RESTP_XORSEQ0) {
loadXorSeq(data, dataSize, 0);
} else if (tp == RESTP_XORSEQ1) {
loadXorSeq(data, dataSize, 1);
} else if (tp == RESTP_XORSEQ2) {
loadXorSeq(data, dataSize, 2);
} else {
warning("Unk Res %x at %x sz %zx", tp, _loadedDataSize, dataSize);
}
return true;
}
bool GamosEngine::loadResHandler(uint tp, uint pid, uint p1, uint p2, uint p3, const RawData &data) {
return loadResHandler(tp, pid, p1, p2, p3, data.data(), data.size());
}
bool GamosEngine::reuseLastResource(uint tp, uint pid, uint p1, uint p2, uint p3) {
if (tp == RESTP_SPR_SEQIMGDATA) {
_sprites[pid].sequences[p1]->operator[](p2).image = _images.back();
} else if (tp == RESTP_SPR_SEQIMGINFO) {
_sprites[pid].sequences[p1] = _imgSeq.back();
} else {
error("Reuse of resource not implemented: resource type %x, id %d %d %d %d", tp, pid, p1, p2, p3);
}
return true;
}
bool GamosEngine::initMainDatas() {
RawData rawdata;
if (!_arch.readCompressedData(&rawdata))
return false;
Common::MemoryReadStream dataStream(rawdata.data(), rawdata.size(), DisposeAfterUse::NO);
_magic = dataStream.readUint32LE();
if (_magic != getEngineVersion()) {
error("InitMainData: Invalid engine version! get %x expecting %x", _magic, getEngineVersion());
return false;
}
/* skip count of pages 1kb size */
dataStream.skip(4); // 4
if (_engineVersion >= 0x12) {
/* skip read buffer size */
dataStream.skip(4); // 8
}
_width = dataStream.readUint32LE(); // c
_height = dataStream.readUint32LE(); // 10
_gridCellW = dataStream.readSint32LE(); // 14
_gridCellH = dataStream.readSint32LE(); // 18
_movieCount = dataStream.readUint32LE(); // 1c
dataStream.skip(3); // skip unknown unused 20
_fps = dataStream.readByte(); // 23
dataStream.skip(1); // skip unknown unused 24
_drawCursor = dataStream.readByte(); // 25
if (_engineVersion >= 0x12) {
_fadeEffectID = dataStream.readByte(); // 26
_playIntro = dataStream.readByte(); // 27
} else {
_playIntro = dataStream.readByte();
_fadeEffectID = dataStream.readByte();
}
if (_engineVersion >= 0x12) {
_introPos.x = dataStream.readSint32LE(); // 28
_introPos.y = dataStream.readSint32LE(); // 2c
_introSize.x = dataStream.readSint32LE(); // 30
_introSize.y = dataStream.readSint32LE(); // 34
int64 pos = dataStream.pos();
_string1 = dataStream.readString(0, 64); // 38
dataStream.seek(pos + 64);
} else {
_string1 = "";
}
_winCaption = dataStream.readString(0, 32); // 78
if (!_screen) {
initGraphics(_width, _height);
_screen = new Graphics::Screen();
}
_movieOffsets.clear();
_movieOffsets.resize(_movieCount, 0);
_objects.clear();
return true;
}
bool GamosEngine::init(const Common::String &moduleName) {
_isSaveLoadingProcess = false;
if (!_arch.open(Common::Path(moduleName)))
return false;
if (!loadInitModule())
return false;
_savedSndVolume = !ConfMan.hasKey("sfx_volume") ? 255 : ConfMan.getInt("sfx_volume");
_savedMidiVolume = !ConfMan.hasKey("music_volume") ? 255 : ConfMan.getInt("music_volume");
_sndVolumeTarget = _savedSndVolume;
_midiVolumeTarget = _savedMidiVolume;
playVideo("intro", _introPos, _introSize);
if (!playIntro())
return false;
return true;
}
bool GamosEngine::loadInitModule() {
rndSeed(_system->getMillis());
_curObjIndex = -1;
_curObject = nullptr;
_curAction = nullptr;
_xorSeq[2].clear();
_xorSeq[1].clear();
_xorSeq[0].clear();
_isMoviePlay = 0;
_txtInputActive = false;
//_updateMouse = false;
_isResLoadingProcess = true;
_savedSndVolume = 0;
_savedMidiVolume = 0;
_sndVolumeTarget = 0;
_midiVolumeTarget = 0;
//_mouseInWindow = false;
return loadModule(0);
}
void GamosEngine::setFPS(uint fps) {
_delayTime = 0;
if (fps)
_delayTime = 1000 / fps;
}
void GamosEngine::readElementsConfig(const RawData &data) {
Common::MemoryReadStream dataStream(data.data(), data.size(), DisposeAfterUse::NO);
freeImages();
freeSequences();
uint32 bkgnum1 = dataStream.readUint32LE(); // 0
uint32 bkgnum2 = dataStream.readUint32LE(); // 4
_statesWidth = dataStream.readUint32LE(); // 8
_statesHeight = dataStream.readUint32LE(); // c
_bkgSize.x = dataStream.readUint32LE(); // 10
_bkgSize.y = dataStream.readUint32LE(); // 14
/* bkgbufferSize */ dataStream.readUint32LE(); // 18
uint32 actsCount = dataStream.readUint32LE(); // 1c
uint32 unk1Count = dataStream.readUint32LE(); // 20
uint32 imageCount = dataStream.readUint32LE(); // 24
uint32 soundCount = dataStream.readUint32LE(); // 28
uint32 midiCount = dataStream.readUint32LE(); // 2c
uint32 dat6xCount = dataStream.readUint32LE(); // 30
_statesShift = 2;
for (int i = 2; i < 9; i++) {
if (_statesWidth <= (1 << i)) {
_statesShift = i;
break;
}
}
_states.clear();
_states.resize(_statesWidth, _statesHeight);
_statesCount = _statesHeight * _statesWidth;
_pathRight = _statesWidth - 1;
_pathBottom = _statesHeight - 1;
_pathMap.clear();
_pathMap.resize(_statesWidth, _statesHeight);
_gameScreens.clear();
_gameScreens.resize(bkgnum1 * bkgnum2);
_sprites.clear();
_sprites.resize(imageCount);
for (uint i = 0; i < imageCount; i++)
_sprites[i].index = i;
_midiTracks.clear();
_midiTracks.resize(midiCount);
_soundSamples.clear();
_soundSamples.resize(soundCount);
_thing2.clear();
_thing2.resize(unk1Count);
_objectActions.clear();
_objectActions.resize(actsCount);
_subtitleActions.clear();
_subtitlePoints.clear();
_subtitleActions.resize(dat6xCount);
_subtitlePoints.resize(dat6xCount);
_loadedDataSize = 0;
_vm.clearMemory();
}
void GamosEngine::loadXorSeq(const byte *data, size_t dataSize, int id) {
Common::MemoryReadStream dataStream(data, dataSize);
Common::Array &seq = _xorSeq[id];
uint32 num = dataStream.readUint32LE();
seq.resize(num);
for (uint i = 0; i < num; ++i) {
seq[i].pos = dataStream.readUint32LE();
seq[i].len = dataStream.readUint32LE();
}
}
bool GamosEngine::loadSpriteInfo(int32 id, const byte *data, size_t dataSize) {
if (dataSize < 4)
return false;
if (dataSize % 4)
warning("dataSize > 4");
_sprites[id].field_0 = data[0];
_sprites[id].flags = data[1];
_sprites[id].lastChar = data[2];
_sprites[id].frameCount = data[3];
_onlyScanImage = data[1] & 0x80;
return true;
}
bool GamosEngine::loadSpriteSeqLength(int32 id, const byte *data, size_t dataSize) {
if (*(const uint32 *)data != 0)
error("41 not null!!!");
if (dataSize % 4)
warning("loadRes41 datasize > 4");
_sprites[id].sequences.resize(dataSize / 4);
return true;
}
bool GamosEngine::loadSpriteSeqImageInfo(int32 id, int32 p1, const byte *data, size_t dataSize) {
//warning("loadRes42 pid %d p %d sz %x",id, p1, dataSize);
if (_sprites[id].sequences.size() == 0)
_sprites[id].sequences.resize(1);
int32 count = dataSize / 8;
_imgSeq.push_back(new ImageSeq(count));
_sprites[id].sequences[p1] = _imgSeq.back();
Common::MemoryReadStream strm(data, dataSize);
for (int i = 0; i < count; ++i) {
int32 dataz = strm.readSint32LE();
if (dataz != 0) {
error("42 nut null");
}
ImagePos &imgpos = _sprites[id].sequences[p1]->operator[](i);
imgpos.xoffset = strm.readSint16LE();
imgpos.yoffset = strm.readSint16LE();
}
return true;
}
bool GamosEngine::loadSpriteSeqImageData(int32 id, int32 p1, int32 p2, const byte *data, size_t dataSize) {
_images.push_back(new Image());
_sprites[id].sequences[p1]->operator[](p2).image = _images.back();
Image *img = _sprites[id].sequences[p1]->operator[](p2).image;
Common::MemoryReadStream s(data, dataSize);
img->surface.pitch = img->surface.w = s.readSint16LE();
img->surface.h = s.readSint16LE();
img->loaded = false;
img->offset = -1;
uint32 token = s.readUint32LE();
/* token 'Disk' */
if (token == 0x4469736b) {
img->offset = s.readSint32LE();
img->cSize = s.readSint32LE();
} else {
if (_sprites[id].flags & 0x80) {
if (_arch._lastReadDecompressedSize) {
img->offset = _arch._lastReadDataOffset;
img->cSize = _arch._lastReadSize;
} else {
img->offset = _arch._lastReadDataOffset;
img->cSize = 0;
}
} else {
img->loaded = true;
img->rawData.assign(data + 4, data + dataSize);
img->surface.setPixels(img->rawData.data());
img->surface.format = Graphics::PixelFormat::createFormatCLUT8();
}
}
return true;
}
bool GamosEngine::loadMidiTrack(int32 id, const byte *data, size_t dataSize) {
_midiTracks[id].assign(data, data + dataSize);
return true;
}
bool GamosEngine::loadBackground(int32 id, const byte *data, size_t dataSize) {
GameScreen &bimg = _gameScreens[id];
bimg.loaded = true;
bimg.offset = _readingBkgOffset;
bimg._savedStates.clear();
bimg._savedObjects.clear();
bimg.palette = nullptr;
bimg._bkgImageData.assign(data, data + dataSize);
Common::MemoryReadStream strm(data, dataSize);
if (_readingBkgMainId == -1 && (strm.readUint32LE() & 0x80000000))
_readingBkgMainId = id;
//warning("res 18 id %d 4: %x", id, strm.readUint32LE());
strm.seek(8);
bimg._bkgImage.pitch = bimg._bkgImage.w = strm.readUint32LE();
bimg._bkgImage.h = strm.readUint32LE();
uint32 imgsize = strm.readUint32LE();
//warning("res 18 id %d 14: %x", id, strm.readUint32LE());
bimg._bkgImage.setPixels(bimg._bkgImageData.data() + 0x18);
bimg._bkgImage.format = Graphics::PixelFormat::createFormatCLUT8();
bimg.palette = bimg._bkgImageData.data() + 0x18 + imgsize;
return true;
}
bool GamosEngine::playIntro() {
if (_movieCount != 0 && _playIntro == 1)
return playMovie(0);
return true;
}
bool GamosEngine::moviePlayerPlay(int id) {
bool res = _moviePlayer.playMovie(&_arch, _movieOffsets[id], this);
return res;
}
bool GamosEngine::playMovie(uint32 id) {
if (_enableMovie) {
_isMoviePlay++;
bool res = moviePlayerPlay(id);
_isMoviePlay--;
return res;
}
return true;
}
void GamosEngine::stopMidi() {
_musicPlayer.stopMusic();
_midiStarted = false;
}
void GamosEngine::stopCDAudio() {
//warning("Not implemented stopMCI");
_cdAudioTrack = -1;
}
void GamosEngine::stopSounds() {
_mixer->stopAll();
}
void GamosEngine::setErrMessage(const Common::String &msg) {
if (_errSet)
return;
_errMessage = msg;
_errSet = true;
}
void GamosEngine::updateScreen(bool checkers, const Common::Rect &rect) {
if (_width == 0 || _height == 0)
return;
if (!checkers || shouldQuit()) {
_screen->addDirtyRect(rect);
return;
}
/* checkers update */
static const Common::Point checkerCoords[16] = {
{0, 0}, {16, 32}, {48, 16}, {16, 48},
{0, 32}, {32, 48}, {16, 16}, {48, 0},
{32, 32}, {0, 48}, {32, 16}, {16, 0},
{48, 32}, {32, 0}, {0, 16}, {48, 48}
};
/* 0.4sec */
const int16 maxDelay = (400 / 16) - 1;
_screen->clearDirtyRects();
for (int16 p = 0; p < 16; p++) {
uint32 val = _system->getMillis();
const Common::Point point = checkerCoords[p];
for (uint32 x = point.x; x < _width; x += 64) {
for (uint32 y = point.y; y < _height; y += 64) {
_screen->addDirtyRect(Common::Rect(x, y, x + 16, y + 16));
}
}
_screen->update();
while (_system->getMillis() - val < maxDelay) {
_system->delayMillis(1);
if (eventsSkip()) {
_screen->addDirtyRect(rect);
_screen->update();
return;
} else {
_system->updateScreen();
}
}
}
}
void GamosEngine::flushDirtyRects(bool apply) {
if (apply) {
for (const Common::Rect &r : _dirtyRects) {
updateScreen(false, r);
}
}
_dirtyRects.clear();
_screen->update();
_inputMouseActId = 0xff;
_inputMouseActType = ACT_NONE;
PTR_00417388 = nullptr;
rndSeed(_system->getMillis());
_inputActObj = nullptr;
cycleNextInputObj(nullptr);
}
bool GamosEngine::usePalette(const byte *pal, int num, int fade, bool winColors) {
static const byte winColorMap[20][3] = {
/* r g b */
{ 0x00, 0x00, 0x00 },
{ 0x80, 0x00, 0x00 },
{ 0x00, 0x80, 0x00 },
{ 0x80, 0x80, 0x00 },
{ 0x00, 0x00, 0x80 },
{ 0x80, 0x00, 0x80 },
{ 0x00, 0x80, 0x80 },
{ 0xc0, 0xc0, 0xc0 },
{ 0xc0, 0xdc, 0xc0 },
{ 0xa6, 0xca, 0xf0 },
{ 0xff, 0xfb, 0xf0 },
{ 0xa0, 0xa0, 0xa4 },
{ 0x80, 0x80, 0x80 },
{ 0xff, 0x00, 0x00 },
{ 0x00, 0xff, 0x00 },
{ 0xff, 0xff, 0x00 },
{ 0x00, 0x00, 0xff },
{ 0xff, 0x00, 0xff },
{ 0x00, 0xff, 0xff },
{ 0xff, 0xff, 0xff }
};
if (!pal)
return false;
if (_width != 0 && _height != 0) {
if (fade == 0 || shouldQuit()) {
uint16 color = _screen->getPalette().findBestColor(0, 0, 0);
_screen->fillRect(_screen->getBounds(), color);
_screen->update();
} else {
uint16 color = 0;
if (fade == 2)
color = _screen->getPalette().findBestColor(0x80, 0x80, 0x80);
else if (fade == 3)
color = _screen->getPalette().findBestColor(0xc0, 0xc0, 0xc0);
else if (fade == 4)
color = _screen->getPalette().findBestColor(0xff, 0xff, 0xff);
else
color = _screen->getPalette().findBestColor(0, 0, 0);
/* 0.4sec */
const int16 maxDelay = (400 / 8) - 1;
for (int j = 0 ; j < 8; j++) {
uint32 val = _system->getMillis();
for (int i = j; i < _screen->w; i += 8)
_screen->drawLine(i, 0, i, _screen->h - 1, color);
for (int i = j; i < _screen->h; i += 8)
_screen->drawLine(0, i, _screen->w - 1, i, color);
_screen->update();
while (_system->getMillis() - val < maxDelay) {
_system->delayMillis(1);
if (eventsSkip()) {
j = 8;
color = _screen->getPalette().findBestColor(0, 0, 0);
_screen->fillRect(_screen->getBounds(), color);
_screen->update();
break;
} else {
_system->updateScreen();
}
}
}
}
}
Graphics::Palette newPal(256);
newPal.set(pal, 0, num);
if (winColors) {
newPal.set(winColorMap[0], 0, 10);
newPal.set(winColorMap[10], 246, 10);
}
newPal.resize(num, true);
_screen->setPalette(newPal);
return true;
}
bool GamosEngine::setPaletteCurrentGS() {
_currentFade = _fadeEffectID;
int curGS = _currentGameScreen;
if (curGS == -1)
curGS = 0;
if (!usePalette(_gameScreens[curGS].palette, 256, _currentFade, true))
return false;
addDirtyRect(Common::Rect(_bkgSize.x, _bkgSize.y));
return true;
}
void GamosEngine::readData2(const RawData &data) {
Common::MemoryReadStream dataStream(data.data(), data.size());
//warning("Game data size %d", data.size());
if (_engineVersion == 0x18) {
_stateExt = dataStream.readString(0, 4); // FIX ME
dataStream.seek(4);
_messageProc._inputFlags = dataStream.readByte(); //4
dataStream.seek(8);
_svModuleId = dataStream.readSint32LE(); // 8
_svGameScreen = dataStream.readSint32LE(); // c
_d2_fld10 = dataStream.readUint32LE(); // x10
_enableSounds = dataStream.readByte() != 0 ? true : false; // x14
_enableMidi = dataStream.readByte() != 0 ? true : false; //x15
_enableInput = dataStream.readByte() != 0 ? true : false; // x16
_enableMovie = dataStream.readByte() != 0 ? true : false; // x17
_enableCDAudio = dataStream.readByte() != 0 ? true : false; // x18
_cdAudioTrack = dataStream.readSByte(); // x19
dataStream.seek(0x1c);
_scrollX = dataStream.readSint32LE(); // x1c
_scrollY = dataStream.readSint32LE(); // x20
_scrollTrackObj = dataStream.readSint16LE(); // x24
_scrollSpeed = dataStream.readSint16LE(); // x26
_scrollCutoff = dataStream.readSint16LE(); // x28
_scrollSpeedReduce = dataStream.readSint16LE(); // x2a
_scrollBorderL = dataStream.readByte(); // x2c
_scrollBorderR = dataStream.readByte(); // x2d
_scrollBorderU = dataStream.readByte(); // x2e
_scrollBorderB = dataStream.readByte(); // x2f
_sndChannels = dataStream.readByte(); // x30
_sndVolume = dataStream.readByte(); // x34
_midiVolume = dataStream.readByte(); // x1a
_svFps = dataStream.readByte(); // x1b
_svFrame = dataStream.readSint32LE(); // x1c
_midiTrack = dataStream.readUint32LE(); //0x38
_mouseCursorImgId = dataStream.readSint32LE(); //0x3c
//0x40
for (int i = 0; i < 12; i++) {
_messageProc._keyCodes[i] = dataStream.readByte();
}
} else if (_engineVersion >= 0x12 && _engineVersion <= 0x16) {
_stateExt = dataStream.readString(0, 4); // FIX ME
dataStream.seek(4);
_messageProc._inputFlags = dataStream.readByte(); //4
dataStream.seek(8);
_svModuleId = dataStream.readSint32LE();
_svGameScreen = dataStream.readSint32LE();
_d2_fld10 = dataStream.readUint32LE();
_enableSounds = dataStream.readByte() != 0 ? true : false; // x14
_enableMidi = dataStream.readByte() != 0 ? true : false; //x15
_enableInput = dataStream.readByte() != 0 ? true : false; // x16
_enableMovie = dataStream.readByte() != 0 ? true : false; // x17
_enableCDAudio = false;
_cdAudioTrack = -1;
_scrollX = 0;
_scrollY = 0;
_scrollTrackObj = -1;
_scrollSpeed = 16;
_scrollCutoff = 80;
_scrollSpeedReduce = -1;
_scrollBorderL = 0;
_scrollBorderR = 0;
_scrollBorderU = 0;
_scrollBorderB = 0;
_sndChannels = dataStream.readByte(); // x18
_sndVolume = dataStream.readByte(); // x19
_midiVolume = dataStream.readByte(); // x1a
_svFps = dataStream.readByte(); // x1b
_svFrame = dataStream.readSint32LE(); // x1c
_midiTrack = dataStream.readUint32LE(); // x20
_mouseCursorImgId = dataStream.readSint32LE(); // x24
//0x28
for (int i = 0; i < 12; i++) {
_messageProc._keyCodes[i] = dataStream.readByte();
}
} else if (_engineVersion == 0xb) {
_stateExt = dataStream.readString(0, 4); // FIX ME
dataStream.seek(4);
_messageProc._inputFlags = dataStream.readByte(); //4
dataStream.seek(8);
_svModuleId = dataStream.readSint32LE(); // 8
_svGameScreen = dataStream.readSint32LE(); // c
_d2_fld10 = dataStream.readUint32LE(); // 10
_enableSounds = dataStream.readByte() != 0 ? true : false; // x14
_enableMidi = dataStream.readByte() != 0 ? true : false; //x15
_enableInput = dataStream.readByte() != 0 ? true : false; // x16
_enableMovie = dataStream.readByte() != 0 ? true : false; // x17
_enableCDAudio = false;
_cdAudioTrack = -1;
_scrollX = 0;
_scrollY = 0;
_scrollTrackObj = -1;
_scrollSpeed = 16;
_scrollCutoff = 80;
_scrollSpeedReduce = -1;
_scrollBorderL = 0;
_scrollBorderR = 0;
_scrollBorderU = 0;
_scrollBorderB = 0;
_sndChannels = dataStream.readByte(); // x18
_sndVolume = dataStream.readByte(); // x19
_midiVolume = dataStream.readByte(); // x1a
_svFps = dataStream.readByte(); // x1b
_svFrame = dataStream.readSint32LE(); // x1c
_midiTrack = dataStream.readUint32LE(); // x20
_mouseCursorImgId = dataStream.readSint32LE(); // x24
//0x28
_messageProc._keyCodes[0] = KeyCodes::WIN_UP;
_messageProc._keyCodes[1] = KeyCodes::WIN_PRIOR;
_messageProc._keyCodes[2] = KeyCodes::WIN_RIGHT;
_messageProc._keyCodes[3] = KeyCodes::WIN_NEXT;
_messageProc._keyCodes[4] = KeyCodes::WIN_DOWN;
_messageProc._keyCodes[5] = KeyCodes::WIN_END;
_messageProc._keyCodes[6] = KeyCodes::WIN_LEFT;
_messageProc._keyCodes[7] = KeyCodes::WIN_HOME;
_messageProc._keyCodes[8] = KeyCodes::WIN_SPACE;
_messageProc._keyCodes[9] = KeyCodes::WIN_RETURN;
_messageProc._keyCodes[10] = KeyCodes::WIN_TAB;
_messageProc._keyCodes[11] = KeyCodes::WIN_INVALID;
_messageProc._keyCodes[12] = KeyCodes::WIN_INVALID;
}
}
bool GamosEngine::playMidi(Common::Array *buffer) {
_musicPlayer.stopMusic();
_midiStarted = _musicPlayer.playMusic(buffer);
return _midiStarted;
}
bool GamosEngine::playSound(uint id) {
Audio::SeekableAudioStream *stream = Audio::makeRawStream(_soundSamples[id].data(), _soundSamples[id].size(), 11025, Audio::FLAG_UNSIGNED, DisposeAfterUse::NO);
_mixer->playStream(Audio::Mixer::kPlainSoundType, nullptr, stream, -1, _sndVolume);
return true;
}
int GamosEngine::stepVolume(int volume, int target) {
int d = target - volume;
if (d == 0)
return 0;
int step = 255 / _fps;
if (d < 0) {
step = -step;
if (step < d)
step = d;
} else {
if (step > d)
step = d;
}
return step;
}
void GamosEngine::changeVolume() {
const int sndStep = stepVolume(_sndVolume, _sndVolumeTarget);
if (sndStep) {
_sndVolume += sndStep;
_mixer->setVolumeForSoundType(Audio::Mixer::kPlainSoundType, _sndVolume);
}
const int midiStep = stepVolume(_midiVolume, _midiVolumeTarget);
if (midiStep) {
_midiVolume += midiStep;
_musicPlayer.setVolume(_midiVolume);
}
}
uint8 GamosEngine::update(Common::Point screenSize, Common::Point mouseMove, Common::Point actPos, uint8 act2, uint8 act1, uint16 keyCode, bool mouseInWindow) {
_needReload = false;
_vm._interrupt = false;
if (_enableInput == 0) {
act1 = ACT_NONE;
act2 = ACT_NONE;
_pressedKeyCode = ACT_NONE;
}
_pressedKeyCode = keyCode;
if (_pressedKeyCode != 0 && _pressedKeyCode != ACT_NONE) {
if (_keySeq.size() >= 32)
_keySeq = _keySeq.substr(_keySeq.size() - 31);
_keySeq += _pressedKeyCode;
}
processInput(mouseMove, actPos, act2, act1);
changeVolume();
if (!updateVMInputFrameStates())
return 0;
bool loop = false;
if (!_txtInputActive)
loop = updateObjects();
else
loop = onTxtInputUpdate(act2);
if (_needReload)
return 2; // rerun update after loadModule
while (loop) {
if (!PTR_00417388) {
if (updateMouseCursor(mouseMove) && scrollAndDraw())
return 1;
else
return 0;
}
_pressedKeyCode = ACT_NONE;
if (!updateVMInputFrameStates())
return 0;
if (!_txtInputActive)
loop = updateObjects();
else
loop = onTxtInputUpdate(act2);
if (_needReload)
return 2; // rerun update after loadModule
}
return 0;
}
int32 GamosEngine::doActions(const Actions &a, bool absolute) {
Common::Array ARR_00412208(512);
if (!absolute) {
_curObjectStartCell = _curObject->cell;
} else {
_curObject = nullptr;
_curObjIndex = -1;
_curAction = nullptr;
_curObjectStartCell = Common::Point();
_curObjectT = 1;
_preprocDataId = 0;
_curObjStorage = nullptr;
}
_curObjectCurrentCell = _curObjectStartCell;
int32 spos = -1;
int32 sbuf[6];
if (a.flags & Actions::HAS_CONDITION) {
if (a.conditionAddress != -1) {
if (!doScript(a.conditionAddress))
return 0;
if (_needReload)
return 0;
}
}
if (a.flags & Actions::HAS_ACT2) {
bool fastSkipAll = false;
for (const ActTypeEntry &ate : a.act_2) {
if (ate.t == 4) {
spos++;
if (spos == 0) {
sbuf[0] = 0;
sbuf[1] = 0;
} else {
int32 p = sbuf[spos * 2 - 1];
sbuf[spos * 2 + 1] = p;
sbuf[spos * 2] = p;
}
} else {
spos = -1;
}
int32 ps = spos * 2 + 1;
for (int i = 0; i < ate.entries.size(); i++) {
/* use copy of entrie because it will be modified */
ActEntry e = ate.entries[i];
preprocessData(_preprocDataId, &e);
ObjState fb;
if (!absolute) {
Common::Point xy;
xy.x = (e.x + _curObjectCurrentCell.x + _statesWidth) % _statesWidth;
xy.y = (e.y + _curObjectCurrentCell.y + _statesHeight) % _statesHeight;
fb = _states.at(xy);
} else {
fb = _states.at(e.x, e.y);
}
int cval = 0;
int fnc = e.t;
if ((e.flags & 1) == 0) {
if (e.actid == fb.actid && (fb.t & e.t)) {
cval = 2;
}
} else if (fb.actid != 0xfe &&
(_thing2[e.actid].masks[(fb.actid) >> 3] & (1 << (fb.actid & 7))) != 0) {
if (!_thing2[e.actid].actsT.empty()) {
e.t = _thing2[e.actid].actsT[fb.actid] >> 4;
preprocessData(fnc + 8, &e);
}
if (fb.t & e.t) {
cval = 2;
}
}
if ((e.flags & 2) == cval) {
if ((e.flags & 0xc) == 0) {
break;
}
if ((e.flags & 0xc) == 4)
return 0;
if ((e.flags & 0xc) == 8) {
fastSkipAll = true;
break;
}
ARR_00412208[ sbuf[ps] ] = Common::Point(e.x, e.y);
sbuf[ps]++;
} else if ((ate.entries.size() - i) == 1 && spos > -1 && sbuf[spos * 2] == sbuf[ps]) {
return 0;
}
}
if (fastSkipAll)
break;
}
}
_curObjectActProcessed = false;
if (a.flags & Actions::HAS_ACT4) {
ActEntry e = a.act_4;
preprocessData(_preprocDataId, &e);
preprocessDataB1(e.t, &e);
rnd();
e.flags = a.act_4.flags;
processActionCurObject(e);
if (_needReload)
return 0;
}
_gfxObjectCreated = false;
if (a.flags & Actions::HAS_FUNCTION) {
uint32 fldsv;
if (_curObject)
fldsv = _curObject->priority;
if (a.functionAddress != -1)
doScript(a.functionAddress);
if (_needReload)
return 0;
if (!_gfxObjectCreated && !_curObjectActProcessed && _curObject && _curObject->priority != fldsv && _curObject->curObjectId != -1)
addDirtRectOnObject(&_objects[_curObject->curObjectId]);
}
if (!_gfxObjectCreated && _curObjectActProcessed)
updateLinkedGfxObject(_curObject);
int32 retval = 0;
if (a.flags & Actions::HAS_ACT10) {
int ivar5 = -1;
for (const ActTypeEntry &ate : a.act_10) {
switch (ate.t) {
case 0: {
uint16 rndval = rndRange16(a.num_act_10e);
for (ActEntry e : a.act_10end[rndval]) {
retval += processData(e, absolute);
if (_needReload)
return 0;
}
}
break;
case 1: {
int32 num = rndRange16(ate.entries.size());
for (int i = 0; i < ate.entries.size(); i++) {
if (num != 0) {
ActEntry e = ate.entries[i];
retval += processData(e, absolute);
if (_needReload)
return 0;
}
num--;
}
}
break;
case 2: {
int32 num = rndRange16(ate.entries.size());
ActEntry e = ate.entries[num];
retval += processData(e, absolute);
if (_needReload)
return 0;
}
break;
case 3: {
for (int i = 0; i < ate.entries.size(); i++) {
uint16 doproc = rndRange16(2);
if (doproc != 0) {
ActEntry e = ate.entries[i];
retval += processData(e, absolute);
if (_needReload)
return 0;
}
}
}
break;
default: {
ivar5++;
/* Seems it's has a error in original
think it's must be:
min + rnd(max-min) */
uint32 lb = rnd() >> 0x10;
uint32 idx = ((sbuf[ivar5 * 2 + 1] - sbuf[ivar5 * 2]) * lb + sbuf[ivar5 * 2]) >> 0x10;
Common::Point point = ARR_00412208[ idx ];
for (ActEntry e : ate.entries) {
if (Common::Point(e.x, e.y) == point) {
retval += processData(e, absolute);
if (_needReload)
return 0;
break;
}
}
}
break;
}
}
}
return retval + 1;
}
uint32 GamosEngine::getU32(const void *ptr) {
const uint8 *p = (const uint8 *)ptr;
return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
}
void GamosEngine::preprocessData(int id, ActEntry *e) {
switch (id) {
default:
case 0:
break;
case 1:
case 10: {
static const uint8 lookup[16] = {0, 2, 4, 6, 8, 10, 12, 14, 1, 3, 5, 7, 9, 11, 13, 15};
int8 tmp = e->y;
e->y = e->x;
e->x = -tmp;
e->t = lookup[ e->t ];
}
break;
case 2:
case 12: {
static const uint8 lookup[16] = {0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15};
e->y = -e->y;
e->x = -e->x;
e->t = lookup[ e->t ];
}
break;
case 3:
case 16: {
static const uint8 lookup[16] = {0, 8, 1, 9, 2, 10, 3, 11, 4, 12, 5, 13, 6, 14, 7, 15};
int8 tmp = e->x;
e->x = e->y;
e->y = -tmp;
e->t = lookup[ e->t ];
}
break;
case 4: {
static const uint8 lookup[16] = {0, 1, 8, 9, 4, 5, 12, 13, 2, 3, 10, 11, 6, 7, 14, 15};
e->x = -e->x;
e->t = lookup[ e->t ];
}
break;
case 5: {
static const uint8 lookup[16] = {0, 2, 1, 3, 8, 10, 9, 11, 4, 6, 5, 7, 12, 14, 13, 15};
int8 tmp = e->x;
e->x = -e->y;
e->y = -tmp;
e->t = lookup[ e->t ];
}
break;
case 6: {
static const uint8 lookup[16] = {0, 4, 2, 6, 1, 5, 3, 7, 8, 12, 10, 14, 9, 13, 11, 15};
e->y = -e->y;
e->t = lookup[ e->t ];
}
break;
case 7: {
static const uint8 lookup[16] = {0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15};
uint8 tmp = e->x;
e->x = e->y;
e->y = tmp;
e->t = lookup[ e->t ];
}
break;
}
}
void GamosEngine::preprocessDataB1(int id, ActEntry *e) {
switch (id) {
default:
case 0:
break;
case 1:
case 2:
case 4:
case 8:
//e->t = e->t;
break;
case 3: {
static const uint8 lookup[2] = {1, 2};
e->t = lookup[rndRange16(2)];
}
break;
case 5: {
static const uint8 lookup[2] = {1, 4};
e->t = lookup[rndRange16(2)];
}
break;
case 6: {
static const uint8 lookup[2] = {2, 4};
e->t = lookup[rndRange16(2)];
}
break;
case 7: {
static const uint8 lookup[3] = {1, 2, 4};
e->t = lookup[rndRange16(3)];
}
break;
case 9: {
static const uint8 lookup[2] = {1, 8};
e->t = lookup[rndRange16(2)];
}
break;
case 0xa: {
static const uint8 lookup[2] = {2, 8};
e->t = lookup[rndRange16(2)];
}
break;
case 0xb: {
static const uint8 lookup[3] = {1, 2, 8};
e->t = lookup[rndRange16(3)];
}
break;
case 0xc: {
static const uint8 lookup[2] = {4, 8};
e->t = lookup[rndRange16(2)];
}
break;
case 0xd: {
static const uint8 lookup[3] = {1, 4, 8};
e->t = lookup[rndRange16(3)];
}
break;
case 0xe: {
static const uint8 lookup[3] = {2, 4, 8};
e->t = lookup[rndRange16(3)];
}
break;
case 0xf: {
static const uint8 lookup[4] = {1, 2, 4, 8};
e->t = lookup[rndRange16(4)];
}
break;
}
}
int GamosEngine::processData(ActEntry e, bool absolute) {
preprocessData(_preprocDataId, &e);
if (!absolute) {
createActiveObject(e, Common::Point(
(e.x + _curObjectCurrentCell.x + _statesWidth) % _statesWidth,
(e.y + _curObjectCurrentCell.y + _statesHeight) % _statesHeight) );
if (_needReload)
return 0;
return e.x == 0 && e.y == 0;
} else {
createActiveObject(e, Common::Point(e.x, e.y) );
return 0;
}
}
void GamosEngine::processActionCurObject(ActEntry e) {
if (e.x != 0 || e.y != 0) {
_curObjectCurrentCell.x = (e.x + _curObjectCurrentCell.x + _statesWidth) % _statesWidth;
_curObjectCurrentCell.y = (e.y + _curObjectCurrentCell.y + _statesHeight) % _statesHeight;
ObjState st = _curObject->state;
_states.at(_curObjectStartCell) = ObjState(st.actid, 0, st.t);
removeObjectAtCoords(_curObjectCurrentCell, false);
_curObject->cell = _curObjectCurrentCell;
ObjState rthing = _states.at(_curObjectCurrentCell);
_curObject->state = ObjState(rthing.actid, st.flags, rthing.t);
_states.at(_curObjectCurrentCell) = ObjState(_curObject->actID, 0, _curObject->t);
_curObjectActProcessed = true;
}
if (e.t != _curObjectT) {
_curObjectT = e.t;
_curObject->t = e.t;
ObjState &stref = _states.at(_curObjectCurrentCell);
stref.flags = 0;
stref.t = _curObjectT;
_curObjectActProcessed = true;
}
}
void GamosEngine::createActiveObject(ActEntry e, Common::Point cell) {
ObjState &stref = _states.at(cell);
uint8 oid = e.actid;
if ((e.flags & 1) == 0) {
if (oid == 0xfe) {
removeObjectAtCoords(cell, true);
if (_needReload)
return;
stref = e;
return;
}
} else {
Unknown1 &unk1 = _thing2[ oid ];
uint8 index = rndRange16(unk1.oids.size());
oid = unk1.oids[ index ];
if (!unk1.actsT.empty()) {
byte id1 = e.t;
e.t = unk1.actsT[ oid ] >> 4;
preprocessData(8 + id1, &e);
}
}
preprocessDataB1(e.t, &e);
e.flags = 0;
rnd(); // needed?
Object *obj = nullptr;
int index = 0;
byte *odat = nullptr;
ObjectAction &act = _objectActions[oid];
if (act.actType == 0) {
removeObjectAtCoords(cell, true);
if (_needReload)
return;
obj = nullptr;
index = -1;
odat = nullptr;
} else {
removeObjectAtCoords(cell, false);
if (_needReload)
return;
obj = getFreeObject();
obj->flags = Object::FLAG_VALID | Object::FLAG_HASACTION;
obj->t = e.t;
obj->actID = oid;
obj->inputFlag = 0;
obj->priority = act.priority;
obj->cell = cell;
obj->tgtObjectId = -1;
obj->curObjectId = -1;
obj->state = stref;
if (_curObject && obj->index > _curObject->index)
obj->state.flags |= 1;
// if (storageSize < 5) {
// obj->pImg = nullptr;
// odat = &obj->pImg;
// } else {
// odat = malloc(storageSize);
// obj->pImg = (Sprite *)odat;
// obj->flags |= 8;
// }
obj->storage.clear();
obj->storage.resize(act.storageSize, 0);
odat = obj->storage.data();
index = obj->index;
if (act.actType == 3 && _inputActObj == nullptr)
_inputActObj = obj;
}
stref = ObjState(oid, e.flags, e.t);
executeScript(act.onCreateAddress, &act, obj, index, odat, cell, e.t);
}
void GamosEngine::removeObjectByIDMarkDirty(int32 id) {
if (id != -1)
removeObjectMarkDirty(&_objects[id]);
}
void GamosEngine::removeObjectAtCoords(Common::Point cell, bool deleteGfxObj) {
ObjState &stref = _states.at(cell);
uint8 actid = stref.actid;
if (actid == 0xfe)
return;
ObjectAction &act = _objectActions[actid];
Object *povar4 = nullptr;
bool multidel = false;
for (uint i = 0; i < _objects.size(); i++) {
Object &obj = _objects[i];
if (obj.flags & Object::FLAG_VALID) {
if (obj.flags & Object::FLAG_HASACTION) {
if (obj.cell == cell) {
removeObjectByIDMarkDirty(obj.curObjectId);
if (obj.curObjectId != obj.tgtObjectId)
removeObjectByIDMarkDirty(obj.tgtObjectId);
/* if (obj.flags & Object::FLAG_STORAGE)
obj.storage.clear(); */
removeSubtitles(&obj);
removeObject(&obj);
cycleNextInputObj(&obj);
povar4 = &obj;
if (!deleteGfxObj || multidel)
break;
multidel = true;
}
} else {
if (deleteGfxObj && obj.cell == cell &&
obj.actObjIndex == -1 && (obj.flags & Object::FLAG_FREECOORDS) == 0) {
removeObjectMarkDirty(&obj);
if (multidel)
break;
multidel = true;
}
}
}
}
if (povar4)
stref = ObjState(povar4->state.actid, 0, povar4->state.t);
executeScript(act.onDeleteAddress, &act, nullptr, -1, nullptr, cell, stref.t);
}
Object *GamosEngine::getFreeObject() {
Object *obj = nullptr;
for (uint i = 0; i < _objects.size(); i++) {
Object &rObj = _objects[i];
if ((rObj.flags & Object::FLAG_VALID) == 0) {
obj = &rObj;
break;
}
}
if (!obj) {
_objects.emplace_back();
obj = &_objects.back();
obj->index = _objects.size() - 1;
}
obj->flags = Object::FLAG_VALID;
obj->priority = 0;
obj->cell.x = 0;
obj->cell.y = 0;
obj->sprId = -1;
obj->seqId = -1;
obj->frame = -1;
obj->frameMax = -1;
obj->position.x = 0;
obj->position.y = 0;
obj->actObjIndex = -1;
obj->actID = 0;
obj->t = 0;
obj->state = ObjState();
obj->inputFlag = 0;
obj->tgtObjectId = -1;
obj->curObjectId = -1;
obj->pImg = nullptr;
obj->storage.clear(); // clear it so gfx object will not have data
return obj;
}
void GamosEngine::removeObject(Object *obj) {
obj->flags = 0;
/*
In original engine here is free for continoues unused objects in the end
*/
}
void GamosEngine::removeObjectMarkDirty(Object *obj) {
if (obj->flags & Object::FLAG_GRAPHIC)
addDirtRectOnObject(obj);
removeObject(obj);
}
void GamosEngine::executeScript(int32 scriptAddr, ObjectAction *act, Object *pobj, int32 index, byte *storage, Common::Point cell, uint8 t) {
if (scriptAddr == -1)
return;
const uint8 sv1 = _curObjectT;
byte * const sv2 = _curObjStorage;
const Common::Point sv4 = _curObjectStartCell;
const Common::Point sv6 = _curObjectCurrentCell;
const int32 sv7 = _curObjIndex;
Object * const sv8 = _curObject;
ObjectAction * const sv9 = _curAction;
_curObjectT = t;
_curObjStorage = storage;
_curObjectStartCell = cell;
_curObjectCurrentCell = cell;
_curObjIndex = index;
_curObject = pobj;
_curAction = act;
doScript(scriptAddr);
_curObjectT = sv1;
_curObjStorage = sv2;
_curObjectStartCell = sv4;
_curObjectCurrentCell = sv6;
_curObjIndex = sv7;
_curObject = sv8;
_curAction = sv9;
}
bool GamosEngine::updateObjects() {
if (_objects.empty())
return true;
Object *pobj = _firstUpdateObject;
if (!pobj)
pobj = &(_objects.front());
for (int32 objIdx = pobj->index; objIdx < _objects.size(); objIdx++) {
pobj = &_objects[objIdx];
if (pobj->isActionObject()) {
if (!PTR_00417388 || (PTR_00417388[ pobj->actID >> 3 ] & (1 << (pobj->actID & 7)))) {
if (pobj->state.flags & 1) {
pobj->state.flags &= ~1;
} else {
if ((pobj->flags & Object::FLAG_TRANSITION) == 0) {
if (pobj->curObjectId != -1 && updateGfxFrames(&_objects[pobj->curObjectId], false, true)) {
pobj->curObjectId = pobj->tgtObjectId;
if (pobj->tgtObjectId != -1) {
Object &o = _objects[pobj->tgtObjectId];
o.flags |= Object::FLAG_GRAPHIC;
o.cell = pobj->cell;
updateGfxObjectPosition(&o);
addDirtRectOnObject(&o);
}
}
} else {
if (updateGfxFrames(&_objects[pobj->curObjectId], true, pobj->curObjectId != pobj->tgtObjectId)) {
pobj->curObjectId = pobj->tgtObjectId;
if (pobj->tgtObjectId != -1) {
Object &o = _objects[pobj->tgtObjectId];
o.flags |= Object::FLAG_GRAPHIC;
o.cell = pobj->cell;
updateGfxObjectPosition(&o);
addDirtRectOnObject(&o);
}
pobj->flags &= ~Object::FLAG_TRANSITION;
} else {
if (pobj == _firstUpdateObject) {
goto exit;
}
goto continue_to_next_object;
}
}
_curObject = pobj;
_curObjIndex = pobj->index;
_curAction = &_objectActions[pobj->actID];
_curObjStorage = pobj->storage.data();
_pathInMove = false;
for (Actions &scr : _curAction->actions) {
_curObjectT = _curObject->t;
int ivr8 = 0;
if (_curObjectT == 2)
ivr8 = 1;
else if (_curObjectT == 4)
ivr8 = 2;
else if (_curObjectT == 8)
ivr8 = 3;
bool tmp = false;
for (int i = 0; i < 8; i++) {
if (_curAction->mask & (1 << i)) {
int fncid = ((i & 3) + ivr8) & 3;
if (i > 3)
fncid += 4;
DAT_004173ec = fncid;
_restartUpdateObject = false;
_preprocDataId = fncid;
int32 res = doActions(scr, false);
if (_needReload)
return false;
if (res == 1) {
if (_restartUpdateObject) {
tmp = true;
break;
}
if (_firstUpdateObject) {
_firstUpdateObject = nullptr;
goto exit;
}
goto continue_to_next_object;
} else if (res != 0) {
if (_firstUpdateObject) {
_firstUpdateObject = nullptr;
goto exit;
}
cycleNextInputObj(pobj);
goto continue_to_next_object;
}
}
}
if (scr.flags & 0x80) {
if (tmp) {
_firstUpdateObject = pobj;
goto exit;
}
if (_firstUpdateObject) {
_firstUpdateObject = nullptr;
goto exit;
}
break;
}
}
}
}
} else {
if (!PTR_00417388 && pobj->isGraphicObject() && pobj->actObjIndex == -1)
updateGfxFrames(pobj, false, true);
}
continue_to_next_object:
;
}
exit:
_curObject = nullptr;
_curObjIndex = -1;
return true;
}
bool GamosEngine::updateGfxFrames(Object *obj, bool p2, bool p1) {
if (obj->frameMax < 2) {
if (p2 || (obj->flags & Object::FLAG_DIRTRECT)) {
addDirtRectOnObject(obj);
if (p1)
removeObject(obj);
return true;
}
} else {
addDirtRectOnObject(obj);
obj->frame++;
if (obj->frame == obj->frameMax) {
obj->frame = 0;
obj->pImg = &_sprites[obj->sprId].sequences[obj->seqId]->operator[](obj->frame);
if (p2 || (obj->flags & Object::FLAG_DIRTRECT)) {
addDirtRectOnObject(obj);
if (p1)
removeObject(obj);
return true;
}
} else {
obj->pImg = &_sprites[obj->sprId].sequences[obj->seqId]->operator[](obj->frame);
}
if ((obj->flags & Object::FLAG_FREECOORDS) == 0)
updateGfxObjectPosition(obj);
addDirtRectOnObject(obj);
}
return false;
}
void GamosEngine::updateGfxObjectPosition(Object *gfxObj) {
ImagePos *imgPos = gfxObj->pImg;
Image *img = imgPos->image;
int32 x = gfxObj->cell.x * _gridCellW;
int32 y = gfxObj->cell.y * _gridCellH;
if (gfxObj->actObjIndex != -1) {
Object *o = &_objects[ gfxObj->actObjIndex ];
if (o->flags & Object::FLAG_TRANSITION) {
int t = gfxObj->frame + 1;
x += (o->cell.x - gfxObj->cell.x) * _gridCellW * t / gfxObj->frameMax;
y += (o->cell.y - gfxObj->cell.y) * _gridCellH * t / gfxObj->frameMax;
}
}
if (gfxObj->flags & Object::FLAG_FLIPH)
gfxObj->position.x = x - (img->surface.w - _gridCellW - imgPos->xoffset);
else
gfxObj->position.x = x - imgPos->xoffset;
if (gfxObj->flags & Object::FLAG_FLIPV)
gfxObj->position.y = y - (img->surface.h - _gridCellH - imgPos->yoffset);
else
gfxObj->position.y = y - imgPos->yoffset;
}
void GamosEngine::addDirtRectOnObject(Object *obj) {
ImagePos *imgPos = obj->pImg;
Common::Rect rect;
rect.left = obj->position.x;
rect.top = obj->position.y;
if (obj->flags & Object::FLAG_FREECOORDS) {
rect.left -= imgPos->xoffset;
rect.top -= imgPos->yoffset;
}
rect.setWidth(imgPos->image->surface.w);
rect.setHeight(imgPos->image->surface.h);
addDirtyRect(rect);
}
void GamosEngine::addDirtyRect(const Common::Rect &rect) {
if (_dirtyRects.empty()) {
_dirtyRects.push_back(rect);
return;
}
bool intersects = 0;
for (int i = 0; i < _dirtyRects.size(); i++) {
Common::Rect &r = _dirtyRects[i];
if (!rect.intersects(r))
continue;
intersects = true;
r.extend(rect);
break;
}
if (!intersects) {
_dirtyRects.push_back(rect);
return;
}
rerunCheck:
for (int i = _dirtyRects.size() - 2; i > 0; i--) {
for (int j = _dirtyRects.size() - 1; j > i; j--) {
Common::Rect &r1 = _dirtyRects[i];
Common::Rect &r2 = _dirtyRects[j];
if (!r1.intersects(r2))
continue;
r1.extend(r2);
_dirtyRects.remove_at(j);
goto rerunCheck;
}
}
}
void GamosEngine::doDraw() {
if (_dirtyRects.empty())
return;
int32 bkg = _currentGameScreen;
if (bkg == -1)
bkg = 0;
Common::Array