/* 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 2 * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * $URL$ * $Id$ * */ #include "lastexpress/game/entities.h" // Data #include "lastexpress/data/scene.h" #include "lastexpress/data/sequence.h" // Entities #include "lastexpress/entities/entity.h" #include "lastexpress/entities/abbot.h" #include "lastexpress/entities/alexei.h" #include "lastexpress/entities/alouan.h" #include "lastexpress/entities/anna.h" #include "lastexpress/entities/august.h" #include "lastexpress/entities/boutarel.h" #include "lastexpress/entities/chapters.h" #include "lastexpress/entities/cooks.h" #include "lastexpress/entities/coudert.h" #include "lastexpress/entities/entity39.h" #include "lastexpress/entities/francois.h" #include "lastexpress/entities/gendarmes.h" #include "lastexpress/entities/hadija.h" #include "lastexpress/entities/ivo.h" #include "lastexpress/entities/kahina.h" #include "lastexpress/entities/kronos.h" #include "lastexpress/entities/mahmud.h" #include "lastexpress/entities/max.h" #include "lastexpress/entities/mertens.h" #include "lastexpress/entities/milos.h" #include "lastexpress/entities/mmeboutarel.h" #include "lastexpress/entities/pascale.h" #include "lastexpress/entities/rebecca.h" #include "lastexpress/entities/salko.h" #include "lastexpress/entities/servers0.h" #include "lastexpress/entities/servers1.h" #include "lastexpress/entities/sophie.h" #include "lastexpress/entities/tables.h" #include "lastexpress/entities/tatiana.h" #include "lastexpress/entities/train.h" #include "lastexpress/entities/vassili.h" #include "lastexpress/entities/verges.h" #include "lastexpress/entities/vesna.h" #include "lastexpress/entities/yasmin.h" // Game #include "lastexpress/game/logic.h" #include "lastexpress/game/savepoint.h" #include "lastexpress/game/scenes.h" #include "lastexpress/game/sound.h" #include "lastexpress/game/state.h" #include "lastexpress/graphics.h" #include "lastexpress/helpers.h" #include "lastexpress/lastexpress.h" #include "lastexpress/resource.h" namespace LastExpress { #define STORE_VALUE(data) ((uint)1 << (uint)data) static const EntityPosition objectsPosition[8] = {kPosition_8200, kPosition_7500, kPosition_6470, kPosition_5790, kPosition_4840, kPosition_4070, kPosition_3050, kPosition_2740}; static const EntityPosition entityPositions[41] = { kPositionNone, kPosition_851, kPosition_1430, kPosition_2110, kPositionNone, kPosition_2410, kPosition_2980, kPosition_3450, kPosition_3760, kPosition_4100, kPosition_4680, kPosition_5140, kPosition_5440, kPosition_5810, kPosition_6410, kPosition_6850, kPosition_7160, kPosition_7510, kPosition_8514, kPositionNone, kPositionNone, kPositionNone, kPosition_2086, kPosition_2690, kPositionNone, kPosition_3110, kPosition_3390, kPosition_3890, kPosition_4460, kPosition_4770, kPosition_5090, kPosition_5610, kPosition_6160, kPosition_6460, kPosition_6800, kPosition_7320, kPosition_7870, kPosition_8160, kPosition_8500, kPosition_9020, kPosition_9269}; #define ADD_ENTITY(class) \ _entities.push_back(new class(engine)); #define COMPUTE_SEQUENCE_NAME(sequenceTo, sequenceFrom) { \ sequenceTo = sequenceFrom; \ for (int seqIdx = 0; seqIdx < 7; seqIdx++) \ sequenceTo.deleteLastChar(); \ if (isInsideTrainCar(entityIndex, kCarGreenSleeping) || isInsideTrainCar(entityIndex, kCarGreenSleeping)) { \ if (data->car < getData(kEntityPlayer)->car || (data->car == getData(kEntityPlayer)->car && data->entityPosition < getData(kEntityPlayer)->entityPosition)) \ sequenceTo += "R.SEQ"; \ else \ sequenceTo += "F.SEQ"; \ } else { \ sequenceTo += ".SEQ"; \ } \ } #define TRY_LOAD_SEQUENCE(sequence, name, name1, name2) { \ if (data->car == getData(kEntityPlayer)->car) \ sequence = loadSequence1(name1, field30); \ if (sequence) { \ name = name1; \ } else { \ if (name2 != "") \ sequence = loadSequence1(name2, field30); \ name = (sequence ? name2 : ""); \ } \ } ////////////////////////////////////////////////////////////////////////// // Entities ////////////////////////////////////////////////////////////////////////// Entities::Entities(LastExpressEngine *engine) : _engine(engine) { _header = new EntityData(); _entities.push_back(NULL); // Header ADD_ENTITY(Anna); ADD_ENTITY(August); ADD_ENTITY(Mertens); ADD_ENTITY(Coudert); ADD_ENTITY(Pascale); ADD_ENTITY(Servers0); ADD_ENTITY(Servers1); ADD_ENTITY(Cooks); ADD_ENTITY(Verges); ADD_ENTITY(Tatiana); ADD_ENTITY(Vassili); ADD_ENTITY(Alexei); ADD_ENTITY(Abbot); ADD_ENTITY(Milos); ADD_ENTITY(Vesna); ADD_ENTITY(Ivo); ADD_ENTITY(Salko); ADD_ENTITY(Kronos); ADD_ENTITY(Kahina); ADD_ENTITY(Francois); ADD_ENTITY(MmeBoutarel); ADD_ENTITY(Boutarel); ADD_ENTITY(Rebecca); ADD_ENTITY(Sophie); ADD_ENTITY(Mahmud); ADD_ENTITY(Yasmin); ADD_ENTITY(Hadija); ADD_ENTITY(Alouan); ADD_ENTITY(Gendarmes); ADD_ENTITY(Max); ADD_ENTITY(Chapters); ADD_ENTITY(Train); // Special case for tables _entities.push_back(new Tables(engine, kEntityTables0)); _entities.push_back(new Tables(engine, kEntityTables1)); _entities.push_back(new Tables(engine, kEntityTables2)); _entities.push_back(new Tables(engine, kEntityTables3)); _entities.push_back(new Tables(engine, kEntityTables4)); _entities.push_back(new Tables(engine, kEntityTables5)); ADD_ENTITY(Entity39); // Init compartments & positions memset(&_compartments, 0, sizeof(_compartments)); memset(&_compartments1, 0, sizeof(_compartments1)); memset(&_positions, 0, sizeof(_positions)); } Entities::~Entities() { SAFE_DELETE(_header); for (int i = 0; i < (int)_entities.size(); i++) SAFE_DELETE(_entities[i]); _entities.clear(); // Zero passed pointers _engine = NULL; } ////////////////////////////////////////////////////////////////////////// // Accessors ////////////////////////////////////////////////////////////////////////// Entity *Entities::get(EntityIndex entity) { assert((uint)entity < _entities.size()); if (entity == kEntityPlayer) error("Cannot get entity for index == 0!"); return _entities[entity]; } EntityData::EntityCallData *Entities::getData(EntityIndex entity) const { assert((uint)entity < _entities.size()); if (entity == kEntityPlayer) return _header->getCallData(); return _entities[entity]->getData(); } int Entities::getPosition(CarIndex car, Position position) const { int index = 100 * car + position; if (car > 10) error("Entities::getPosition: trying to access an invalid car (was: %d, valid:0-9)", car); if (position > 100) error("Entities::getPosition: trying to access an invalid position (was: %d, valid:0-100)", position); return _positions[index]; } int Entities::getCompartments(int index) const { if (index >= _compartmentsCount) error("Entities::getCompartments: trying to access an invalid compartment (was: %d, valid:0-15)", index); return _compartments[index]; } int Entities::getCompartments1(int index) const { if (index >= _compartmentsCount) error("Entities::getCompartments: trying to access an invalid compartment (was: %d, valid:0-15)", index); return _compartments1[index]; } ////////////////////////////////////////////////////////////////////////// // Savegame ////////////////////////////////////////////////////////////////////////// void Entities::saveLoadWithSerializer(Common::Serializer &s) { _header->saveLoadWithSerializer(s); for (uint i = 1; i < _entities.size(); i++) _entities[i]->saveLoadWithSerializer(s); } void Entities::savePositions(Common::Serializer &s) { for (uint i = 0; i < (uint)_positionsCount; i++) s.syncAsUint32LE(_positions[i]); } void Entities::saveCompartments(Common::Serializer &s) { for (uint i = 0; i < (uint)_compartmentsCount; i++) s.syncAsUint32LE(_compartments[i]); for (uint i = 0; i < (uint)_compartmentsCount; i++) s.syncAsUint32LE(_compartments1[i]); } ////////////////////////////////////////////////////////////////////////// // Setup ////////////////////////////////////////////////////////////////////////// void Entities::setup(bool isFirstChapter, EntityIndex entityIndex) { setupChapter(isFirstChapter ? kChapter1 : kChapterAll); bool flag_4 = false; if (!isFirstChapter) { getFlags()->flag_4 = false; if (entityIndex) { getSavePoints()->call(kEntityPlayer, entityIndex, kActionNone); flag_4 = getFlags()->flag_4; } } getFlags()->flag_4 = flag_4; if (!getFlags()->flag_4) getScenes()->loadScene(getState()->scene); } void Entities::setupChapter(ChapterIndex chapter) { if (chapter) { // Reset current call, inventory item & draw sequences for (uint i = 1; i < _entities.size(); i++) { getData((EntityIndex)i)->currentCall = 0; getData((EntityIndex)i)->inventoryItem = kItemNone; clearSequences((EntityIndex)i); } // Init compartments & positions memset(&_compartments, 0, sizeof(_compartments)); memset(&_compartments1, 0, sizeof(_compartments1)); memset(&_positions, 0, sizeof(_positions)); getSound()->resetQueue(SoundManager::kSoundType13); } // we skip the header when doing entity setup for (uint i = 1; i < _entities.size(); i++) { // Special case of chapters (prevents infinite loop as we will be called from Chapters functions when changing chapters) if (i == kEntityChapters && chapter >= 2) continue; _entities[i]->setup(chapter); } } void Entities::reset() { // Reset header delete _header; _header = new EntityData(); for (uint i = 1; i < _entities.size(); i++) resetSequences((EntityIndex)i); getScenes()->resetDoorsAndClock(); } ////////////////////////////////////////////////////////////////////////// // State & Sequences ////////////////////////////////////////////////////////////////////////// EntityIndex Entities::canInteractWith(const Common::Point &point) const { if (!getFlags()->isGameRunning) return kEntityPlayer; EntityIndex index = kEntityPlayer; int location = 10000; // Check if there is an entity we can interact with for (uint i = 0; i < _entities.size(); i++) { // Skip entities with no current frame if (!getData((EntityIndex)i)->frame) continue; FrameInfo *info = getData((EntityIndex)i)->frame->getInfo(); // Check the hotspot if (info->hotspot.contains(point)) { // If closer to us, update with its values if (location > info->location) { location = info->location; index = (EntityIndex)i; } } } // Check if we found an entity if (!index) return kEntityPlayer; // Check that there is an item to interact with if (!getData(index)->inventoryItem) return kEntityPlayer; return index; } void Entities::resetState(EntityIndex entityIndex) { getData(entityIndex)->currentCall = 0; getData(entityIndex)->inventoryItem = kItemNone; if (getSound()->isBuffered(entityIndex)) getSound()->removeFromQueue(entityIndex); clearSequences(entityIndex); if (entityIndex == kEntity39) entityIndex = kEntityPlayer; if (entityIndex > kEntityChapters) return; // reset compartments and positions for this entity for (int i = 0; i < _positionsCount; i++) _positions[i] &= ~STORE_VALUE(entityIndex); for (int i = 0; i < _compartmentsCount; i++) { _compartments[i] &= ~STORE_VALUE(entityIndex); _compartments1[i] &= ~STORE_VALUE(entityIndex); } getLogic()->updateCursor(); } void Entities::updateFields() const { if (!getFlags()->isGameRunning) return; for (int i = 0; i < (int)_entities.size(); i++) { if (!getSavePoints()->getCallback((EntityIndex)i)) continue; EntityData::EntityCallData *data = getData((EntityIndex)i); int positionDelta = data->field_4A3 * 10; switch (data->direction) { default: break; case kDirectionUp: if (data->entityPosition >= 10000 - positionDelta) data->entityPosition = (EntityPosition)(data->entityPosition + positionDelta); break; case kDirectionDown: if (data->entityPosition > positionDelta) data->entityPosition = (EntityPosition)(data->entityPosition - positionDelta); break; case kDirectionLeft: data->currentFrame++; break; case kDirectionRight: data->field_4A1 += 9; break; case kDirectionSwitch: if (data->directionSwitch == kDirectionRight) data->field_4A1 += 9; break; } } } void Entities::updateFrame(EntityIndex entityIndex) const { Sequence *sequence = NULL; int16 *currentFrame = NULL; bool found = false; if (getData(entityIndex)->direction == kDirectionSwitch) { sequence = getData(entityIndex)->sequence2; currentFrame = &getData(entityIndex)->currentFrame2; } else { sequence = getData(entityIndex)->sequence; currentFrame = &getData(entityIndex)->currentFrame; } if (!sequence) return; // Save current values int16 oldFrame = *currentFrame; int16 field_4A1 = getData(entityIndex)->field_4A1; do { // Check we do not get past the end if (*currentFrame >= (int)sequence->count() - 1) break; // Get the proper frame FrameInfo *info = sequence->getFrameInfo((uint16)*currentFrame); if (info->field_33 & 8) { found = true; } else { if (info->soundAction == 35) found = true; getData(entityIndex)->field_4A1 += info->field_30; // Progress to the next frame ++*currentFrame; } } while (!found); // Restore old values if (!found) { *currentFrame = oldFrame; getData(entityIndex)->field_4A1 = field_4A1; } } void Entities::updateSequences() const { if (!getFlags()->isGameRunning) return; // Update the train clock & doors getScenes()->updateDoorsAndClock(); ////////////////////////////////////////////////////////////////////////// // First pass: Drawing ////////////////////////////////////////////////////////////////////////// for (uint i = 1; i < _entities.size(); i++) { EntityIndex entityIndex = (EntityIndex)i; if (!getSavePoints()->getCallback(entityIndex)) continue; EntityData::EntityCallData *data = getData(entityIndex); if (data->frame) { getScenes()->removeFromQueue(data->frame); SAFE_DELETE(data->frame); } if (data->frame1) { getScenes()->removeFromQueue(data->frame1); SAFE_DELETE(data->frame1); } if (data->direction == kDirectionSwitch) { // Clear sequence 2 if (data->sequence) SAFE_DELETE(data->sequence); // Replace by sequence 3 if available if (data->sequence2) { data->sequence = data->sequence2; data->sequenceName = data->sequenceName2; data->sequence2 = NULL; data->sequenceName2 = ""; } data->direction = data->directionSwitch; data->currentFrame = -1; data->field_49B = 0; } // Draw sequences drawSequences(entityIndex, data->direction, false); } ////////////////////////////////////////////////////////////////////////// // Second pass: Load sequences for next pass ////////////////////////////////////////////////////////////////////////// for (uint i = 1; i < _entities.size(); i++) { EntityIndex entityIndex = (EntityIndex)i; if (!getSavePoints()->getCallback(entityIndex)) continue; EntityData::EntityCallData *data = getData(entityIndex); byte field30 = (data->direction == kDirectionLeft ? entityIndex + 35 : 15); if (data->sequenceName != "" && !data->sequence) { data->sequence = loadSequence1(data->sequenceName, field30); // If sequence 2 was loaded correctly, remove the copied name // otherwise, compute new name if (data->sequence) { data->sequenceNameCopy = ""; } else { Common::String sequenceName; // Left and down directions if (data->direction == kDirectionLeft || data->direction == kDirectionRight) { COMPUTE_SEQUENCE_NAME(sequenceName, data->sequenceName); // Try loading the sequence data->sequence = loadSequence1(sequenceName, field30); } // Update sequence names data->sequenceNameCopy = (data->sequence ? "" : data->sequenceName); data->sequenceName = (data->sequence ? sequenceName : ""); } } // Update sequence 3 if (data->sequenceName2 != "" && !data->sequence2) { if (data->car == getData(kEntityPlayer)->car) data->sequence2 = loadSequence1(data->sequenceName2, field30); if (!data->sequence2) { Common::String sequenceName; // Left and down directions if (data->directionSwitch == kDirectionLeft || data->directionSwitch == kDirectionRight) { COMPUTE_SEQUENCE_NAME(sequenceName, data->sequenceName2); // Try loading the sequence data->sequence2 = loadSequence1(sequenceName, field30); } // Update sequence names data->sequenceName2 = (data->sequence2 ? sequenceName : ""); } } } } void Entities::resetSequences(EntityIndex entityIndex) const { // Reset direction if (getData(entityIndex)->direction == kDirectionSwitch) { getData(entityIndex)->direction = getData(entityIndex)->directionSwitch; getData(entityIndex)->field_49B = 0; getData(entityIndex)->currentFrame = -1; } SAFE_DELETE(getData(entityIndex)->frame); SAFE_DELETE(getData(entityIndex)->frame1); SAFE_DELETE(getData(entityIndex)->sequence); SAFE_DELETE(getData(entityIndex)->sequence2); SAFE_DELETE(getData(entityIndex)->sequence3); getData(entityIndex)->field_4A9 = false; getData(entityIndex)->field_4AA = false; strcpy((char*)&getData(entityIndex)->sequenceNameCopy, ""); strcpy((char*)&getData(entityIndex)->sequenceName, ""); strcpy((char*)&getData(entityIndex)->sequenceName2, ""); // Original engine resets flag to decompress data on the fly (we don't need to do that) } ////////////////////////////////////////////////////////////////////////// // Callbacks ////////////////////////////////////////////////////////////////////////// void Entities::updateCallbacks() { if (!getFlags()->isGameRunning) return; getFlags()->flag_entities_0 = false; if (getFlags()->flag_entities_1) { executeCallbacks(); getFlags()->flag_entities_0 = true; } else { getFlags()->flag_entities_1 = true; executeCallbacks(); getFlags()->flag_entities_1 = false; } } void Entities::executeCallbacks() { for (uint i = 1; i < _entities.size(); i++) { if (getFlags()->flag_entities_0) break; if (getSavePoints()->getCallback((EntityIndex)i)) processEntity((EntityIndex)i); } if (getFlags()->flag_entities_0) return; bool processed = true; do { processed = true; for (int i = 1; i < (int)_entities.size(); i++) { if (getFlags()->flag_entities_0) break; if (getSavePoints()->getCallback((EntityIndex)i)) { if (getData((EntityIndex)i)->doProcessEntity) { processed = false; processEntity((EntityIndex)i); } } } } while (!processed); } ////////////////////////////////////////////////////////////////////////// // Processing ////////////////////////////////////////////////////////////////////////// #define INCREMENT_DIRECTION_COUNTER() { \ data->doProcessEntity = false; \ if (data->direction == kDirectionRight || (data->direction == kDirectionSwitch && data->directionSwitch == kDirectionRight)) \ ++data->field_4A1; \ } void Entities::processEntity(EntityIndex entityIndex) { EntityData::EntityCallData *data = getData(entityIndex); bool keepPreviousFrame = false; data->doProcessEntity = false; if (getData(kEntityPlayer)->car != data->car && data->direction != kDirectionRight && data->direction != kDirectionSwitch) { if (data->position) { updatePositionExit(entityIndex, data->car2, data->position); data->car2 = kCarNone; data->position = 0; } getScenes()->removeAndRedraw(&data->frame, false); getScenes()->removeAndRedraw(&data->frame1, false); INCREMENT_DIRECTION_COUNTER(); return; } if (data->frame1) { getScenes()->removeAndRedraw(&data->frame1, false); if (data->frame && data->frame->getInfo()->subType != kFrameType3) { data->frame->getInfo()->subType = kFrameTypeNone; getScenes()->setFlagDrawSequences(); } } SAFE_DELETE(data->sequence3); if (!data->frame || !data->direction) { if (!data->sequence) label_nosequence: drawSequences(entityIndex, data->direction, true); data->doProcessEntity = false; computeCurrentFrame(entityIndex); if (getFlags()->flag_entities_0 || data->doProcessEntity) return; if (data->sequence && data->currentFrame != -1 && data->currentFrame <= (int16)(data->sequence->count() - 1)) { processFrame(entityIndex, false, true); if (!getFlags()->flag_entities_0 && !data->doProcessEntity) { INCREMENT_DIRECTION_COUNTER(); return; } } else { if (data->direction == kDirectionRight && data->field_4A1 > 100) { getSavePoints()->push(kEntityPlayer, entityIndex, kActionExitCompartment); getSavePoints()->process(); if (getFlags()->flag_entities_0 || data->doProcessEntity) return; } if (data->position) { updatePositionExit(entityIndex, data->car2, data->position); data->car2 = kCarNone; data->position = 0; } INCREMENT_DIRECTION_COUNTER(); } return; } if (!data->sequence) goto label_nosequence; if (data->frame->getInfo()->field_30 > data->field_49B + 1 || (data->direction == kDirectionLeft && data->sequence->count() == 1)) { ++data->field_49B; INCREMENT_DIRECTION_COUNTER(); return; } if (data->frame->getInfo()->field_30 > data->field_49B && !data->frame->getInfo()->keepPreviousFrame) { ++data->field_49B; INCREMENT_DIRECTION_COUNTER(); return; } if (data->frame->getInfo()->keepPreviousFrame == 1) keepPreviousFrame = true; // Increment current frame ++data->currentFrame; if (data->currentFrame > (int16)(data->sequence->count() - 1) || (data->field_4A9 && checkSequenceFromPosition(entityIndex))) { if (data->direction == kDirectionLeft) { data->currentFrame = 0; } else { keepPreviousFrame = true; drawNextSequence(entityIndex); if (getFlags()->flag_entities_0 || data->doProcessEntity) return; if (!data->sequence2) { updateEntityPosition(entityIndex); data->doProcessEntity = false; return; } copySequenceData(entityIndex); } } processFrame(entityIndex, keepPreviousFrame, false); if (!getFlags()->flag_entities_0 && !data->doProcessEntity) INCREMENT_DIRECTION_COUNTER(); } void Entities::computeCurrentFrame(EntityIndex entityIndex) const { EntityData::EntityCallData *data = getData(entityIndex); int16 originalCurrentFrame = data->currentFrame; if (!data->sequence) { data->currentFrame = -1; return; } switch (data->direction) { default: break; case kDirectionNone: case kDirectionSwitch: data->currentFrame = -1; break; case kDirectionUp: case kDirectionDown: { Scene *scene = getScenes()->get(getState()->scene); if (scene->position > 40) break; switch (scene->position) { default: case 4: case 19: case 20: case 21: case 24: break; case 1: case 18: case 22: case 40: data->currentFrame = getCurrentFrame(entityIndex, data->sequence, kPositionNone, false); break; case 2: case 3: case 5: case 6: case 7: case 8: case 9: case 10: case 11: case 12: case 13: case 14: case 15: case 16: case 17: if (data->field_4A9) { if (getData(kEntityPlayer)->entityPosition >= data->entityPosition) { data->currentFrame = -1; } else { data->currentFrame = getCurrentFrame(entityIndex, data->sequence, getEntityPositionFromCurrentPosition(), true); if (data->currentFrame != -1 && originalCurrentFrame == data->currentFrame) if (data->currentFrame < (int)(data->sequence->count() - 2)) data->currentFrame += 2; } } else { data->currentFrame = getCurrentFrame(entityIndex, data->sequence, kPositionNone, false); } break; case 23: case 25: case 26: case 27: case 28: case 29: case 30: case 31: case 32: case 33: case 34: case 35: case 36: case 37: case 38: case 39: if (data->field_4A9) { if (getData(kEntityPlayer)->entityPosition <= data->entityPosition) { data->currentFrame = -1; } else { data->currentFrame = getCurrentFrame(entityIndex, data->sequence, getEntityPositionFromCurrentPosition(), true); if (data->currentFrame != -1 && originalCurrentFrame == data->currentFrame) if (data->currentFrame < (int)(data->sequence->count() - 2)) data->currentFrame += 2; } } else { data->currentFrame = getCurrentFrame(entityIndex, data->sequence, kPositionNone, false); } break; } } break; case kDirectionLeft: if (data->currentFrame == -1 || data->currentFrame >= (int32)data->sequence->count()) { data->currentFrame = 0; data->field_49B = 0; } break; case kDirectionRight: bool found = false; bool flag = false; uint16 frameIndex = 0; byte field30 = 0; int16 currentFrameCopy = (!data->currentFrame && !data->field_4A1) ? -1 : data->currentFrame; // Process frames do { if (frameIndex >= data->sequence->count()) break; FrameInfo *info = data->sequence->getFrameInfo(frameIndex); if (field30 + info->field_30 >= data->field_4A1) { found = true; break; } if (field30 > data->field_4A1 - 10) { if (info->soundAction) getSound()->playSoundEvent(entityIndex, info->soundAction, (field30 <= data->field_4A1 - info->field_31) ? 0 : (byte)(field30 + info->field_31 - data->field_4A1)); } field30 += info->field_30; if (info->field_33 & 4) flag = true; if (info->field_33 & 2) { flag = false; getSavePoints()->push(kEntityPlayer, entityIndex, kAction10); getSavePoints()->process(); if (getFlags()->flag_entities_0 || data->doProcessEntity) return; } if (info->field_33 & 16) { getSavePoints()->push(kEntityPlayer, entityIndex, kAction4); getSavePoints()->process(); if (getFlags()->flag_entities_0 || data->doProcessEntity) return; } frameIndex++; } while (!found); if (found) { if (flag) { bool found2 = false; do { if (frameIndex >= data->sequence->count()) break; FrameInfo *info = data->sequence->getFrameInfo(frameIndex); if (info->field_33 & 2) { found2 = true; getSavePoints()->push(kEntityPlayer, entityIndex, kAction10); getSavePoints()->process(); if (getFlags()->flag_entities_0 || data->doProcessEntity) return; } else { data->field_4A1 += info->field_30; byte soundAction = data->sequence->getFrameInfo((uint16)data->currentFrame)->soundAction; if (soundAction) getSound()->playSoundEvent(entityIndex, soundAction); ++frameIndex; } } while (!found2); if (found2) { data->currentFrame = frameIndex; data->field_49B = 0; byte soundAction = data->sequence->getFrameInfo((uint16)data->currentFrame)->soundAction; byte field31 = data->sequence->getFrameInfo((uint16)data->currentFrame)->field_31; if (soundAction && data->currentFrame != currentFrameCopy) getSound()->playSoundEvent(entityIndex, soundAction, field31); } else { data->currentFrame = (int16)(data->sequence->count() - 1); data->field_49B = data->sequence->getFrameInfo((uint16)data->currentFrame)->field_30; } } else { data->currentFrame = frameIndex; data->field_49B = data->field_4A1 - field30; byte soundAction = data->sequence->getFrameInfo((uint16)data->currentFrame)->soundAction; byte field31 = data->sequence->getFrameInfo((uint16)data->currentFrame)->field_31; if (soundAction && data->currentFrame != currentFrameCopy) getSound()->playSoundEvent(entityIndex, soundAction, field31 <= data->field_49B ? 0 : (byte)(field31 - data->field_49B)); } } else { data->currentFrame = (int16)(data->sequence->count() - 1); data->field_49B = data->sequence->getFrameInfo((uint16)data->currentFrame)->field_30; getSavePoints()->push(kEntityPlayer, entityIndex, kActionExitCompartment); getSavePoints()->process(); } break; } } int16 Entities::getCurrentFrame(EntityIndex entity, Sequence *sequence, EntityPosition position, bool doProcessing) const { EntityData::EntityCallData *data = getData(entity); EntityPosition firstFramePosition = sequence->getFrameInfo(0)->entityPosition; EntityPosition lastFramePosition = sequence->getFrameInfo(sequence->count() - 1)->entityPosition; bool isGoingForward = (firstFramePosition < lastFramePosition); if (!doProcessing) { if (!isGoingForward) { if (data->field_4A3 + firstFramePosition < data->entityPosition || lastFramePosition - data->field_4A3 > data->entityPosition) return -1; } else { if (firstFramePosition - data->field_4A3 > data->entityPosition || lastFramePosition + data->field_4A3 < data->entityPosition) return -1; } } if (sequence->count() == 0) return 0; // Search for the correct frame // TODO: looks slightly like some sort of binary search uint16 frame = 0; uint16 numFrames = sequence->count() - 1; for (;;) { uint16 currentFrame = (frame + numFrames) / 2; if (position + sequence->getFrameInfo(currentFrame)->entityPosition <= data->entityPosition) { if (!isGoingForward) numFrames = (frame + numFrames) / 2; else frame = (frame + numFrames) / 2; } else { if (isGoingForward) numFrames = (frame + numFrames) / 2; else frame = (frame + numFrames) / 2; } if (numFrames - frame == 1) { uint16 lastFramePos = ABS(position - (sequence->getFrameInfo(numFrames)->entityPosition + data->entityPosition)); uint16 framePosition = ABS(position - (sequence->getFrameInfo(frame)->entityPosition + data->entityPosition)); return (framePosition > lastFramePos) ? numFrames : frame; } if (numFrames <= frame) return currentFrame; } } void Entities::processFrame(EntityIndex entityIndex, bool keepPreviousFrame, bool dontPlaySound) { EntityData::EntityCallData *data = getData(entityIndex); // Set frame to be drawn again if (data->frame && keepPreviousFrame) { if (data->frame->getInfo()->subType != kFrameType3) data->frame->getInfo()->subType = kFrameType2; getScenes()->setFlagDrawSequences(); } // Remove old frame from queue if (data->frame && !keepPreviousFrame) getScenes()->removeFromQueue(data->frame); // Stop if nothing else to draw if (data->currentFrame < 0) return; if (data->currentFrame > (int)data->sequence->count()) return; // Get new frame info FrameInfo *info = data->sequence->getFrameInfo((uint16)data->currentFrame); if (data->frame && data->frame->getInfo()->subType != kFrameType3) if (!info->field_2E || keepPreviousFrame) getScenes()->setCoordinates(data->frame); // Update position if (info->entityPosition) { data->entityPosition = info->entityPosition; if (data->field_4A9) data->entityPosition = (EntityPosition)(data->entityPosition + getEntityPositionFromCurrentPosition()); } info->location = entityIndex + ABS(getData(entityIndex)->entityPosition - getData(kEntityPlayer)->entityPosition); if (info->subType != kFrameType3) { info->subType = kFrameType1; if (!keepPreviousFrame) info->subType = kFrameTypeNone; } if (info->field_33 & 1) getSavePoints()->push(kEntityPlayer, entityIndex, kActionExcuseMeCath); if (info->field_33 & 2) { getSavePoints()->push(kEntityPlayer, entityIndex, kAction10); getSavePoints()->process(); if (getFlags()->flag_entities_0 || data->doProcessEntity) return; } if (info->field_33 & 16) { getSavePoints()->push(kEntityPlayer, entityIndex, kAction4); getSavePoints()->process(); if (getFlags()->flag_entities_0 || data->doProcessEntity) return; } if (data->position) { updatePositionExit(entityIndex, data->car2, data->position); data->car2 = kCarNone; data->position = 0; } if (info->position) { data->car2 = data->car; data->position = info->position; updatePositionEnter(entityIndex, data->car2, data->position); if (getFlags()->flag_entities_0 || data->doProcessEntity) return; } if (info->soundAction && !dontPlaySound) getSound()->playSoundEvent(entityIndex, info->soundAction, info->field_31); // Add the new frame to the queue SequenceFrame *frame = new SequenceFrame(data->sequence, (uint16)data->currentFrame); getScenes()->addToQueue(frame); // Keep previous frame if needed and store the new frame if (keepPreviousFrame) { SAFE_DELETE(data->frame1); data->frame1 = data->frame; } else { SAFE_DELETE(data->frame); } data->frame = frame; if (!dontPlaySound) data->field_49B = keepPreviousFrame ? 0 : 1; } void Entities::drawNextSequence(EntityIndex entityIndex) const { EntityData::EntityCallData *data = getData(entityIndex); if (data->direction == kDirectionRight) { getSavePoints()->push(kEntityPlayer, entityIndex, kActionExitCompartment); getSavePoints()->process(); if (getFlags()->flag_entities_0 || data->doProcessEntity) return; } if (!isDirectionUpOrDown(entityIndex)) return; if (data->sequence2) return; if (!getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingAtDoors)) return; if (getData(kEntityPlayer)->car != data->car) return; if (!data->field_4A9 || isWalkingOppositeToPlayer(entityIndex)) { if (!data->field_4A9 && isWalkingOppositeToPlayer(entityIndex)) { data->entityPosition = kPosition_2088; if (data->direction != kDirectionUp) data->entityPosition = kPosition_8512; drawSequences(entityIndex, data->direction, true); } } else { data->entityPosition = kPosition_8514; if (data->direction != kDirectionUp) data->entityPosition = kPosition_2086; drawSequences(entityIndex, data->direction, true); } } void Entities::updateEntityPosition(EntityIndex entityIndex) const { EntityData::EntityCallData *data = getData(entityIndex); getScenes()->removeAndRedraw(&data->frame, false); SAFE_DELETE(data->frame1); data->field_49B = 0; if (isDirectionUpOrDown(entityIndex) && (getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingUp) || getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingDown)) && data->car == getData(kEntityPlayer)->car) { if (isWalkingOppositeToPlayer(entityIndex)) { data->entityPosition = getData(kEntityPlayer)->entityPosition; } else if (data->field_4A9) { data->entityPosition = (data->direction == kDirectionUp) ? kPosition_8514 : kPosition_2086; } else { if (isPlayerPosition(kCarGreenSleeping, 1) || isPlayerPosition(kCarGreenSleeping, 40) || isPlayerPosition(kCarRedSleeping, 1) || isPlayerPosition(kCarRedSleeping, 40)) { data->entityPosition = (data->direction == kDirectionUp) ? kPosition_2588 : kPosition_8012; } else { data->entityPosition = (data->direction == kDirectionUp) ? kPosition_9271 : kPosition_849; } } } SAFE_DELETE(data->sequence); data->sequenceName = ""; data->field_4A9 = false; if (data->directionSwitch) data->direction = data->directionSwitch; } void Entities::copySequenceData(EntityIndex entityIndex) const { EntityData::EntityCallData *data = getData(entityIndex); if (data->sequence) data->sequence3 = data->sequence; data->sequence = data->sequence2; data->sequenceName = data->sequenceName2; data->field_4A9 = data->field_4AA; if (data->directionSwitch) data->direction = data->directionSwitch; // Clear sequence 3 data->sequence2 = NULL; data->sequenceName2 = ""; data->field_4AA = false; data->directionSwitch = kDirectionNone; if (data->field_4A9) { computeCurrentFrame(entityIndex); if (data->currentFrame == -1) data->currentFrame = 0; } else { data->currentFrame = data->currentFrame2; data->currentFrame2 = 0; if (data->currentFrame == -1) data->currentFrame = 0; } } ////////////////////////////////////////////////////////////////////////// // Drawing ////////////////////////////////////////////////////////////////////////// void Entities::drawSequenceLeft(EntityIndex index, const char *sequence) const { drawSequence(index, sequence, kDirectionLeft); } void Entities::drawSequenceRight(EntityIndex index, const char *sequence) const { drawSequence(index, sequence, kDirectionRight); } void Entities::clearSequences(EntityIndex entityIndex) const { debugC(8, kLastExpressDebugLogic, "Clear sequences for entity %s", ENTITY_NAME(entityIndex)); EntityData::EntityCallData *data = getData(entityIndex); getScenes()->removeAndRedraw(&data->frame, false); getScenes()->removeAndRedraw(&data->frame1, false); if (data->sequence2) { SAFE_DELETE(data->sequence2); data->sequenceName2 = ""; data->field_4AA = false; data->directionSwitch = kDirectionNone; } if (data->sequence) { SAFE_DELETE(data->sequence); data->sequenceName = ""; data->field_4A9 = false; data->currentFrame = -1; } data->sequenceNamePrefix = ""; data->direction = kDirectionNone; data->doProcessEntity = true; } void Entities::drawSequence(EntityIndex index, const char *sequence, EntityDirection direction) const { debugC(8, kLastExpressDebugLogic, "Drawing sequence %s for entity %s with direction %s", sequence, ENTITY_NAME(index), DIRECTION_NAME(direction)); // Copy sequence name getData(index)->sequenceNamePrefix = sequence; getData(index)->sequenceNamePrefix.toUppercase(); getData(index)->sequenceNamePrefix += "-"; // Reset fields getData(index)->field_49B = 0; getData(index)->currentFrame = 0; getData(index)->field_4A1 = 0; drawSequences(index, direction, true); } void Entities::drawSequences(EntityIndex entityIndex, EntityDirection direction, bool loadSequence) const { EntityData::EntityCallData *data = getData(entityIndex); // Compute value for loading sequence depending on direction byte field30 = (direction == kDirectionLeft ? entityIndex + 35 : 15); data->doProcessEntity = true; bool field4A9 = data->field_4A9; // First case: different car and not going right: cleanup and return if (data->car != getData(kEntityPlayer)->car && direction != kDirectionRight) { clearEntitySequenceData(data, direction); return; } data->directionSwitch = kDirectionNone; // Process sequence names Common::String sequenceName; Common::String sequenceName1; Common::String sequenceName2; Common::String sequenceName3; getSequenceName(entityIndex, direction, sequenceName1, sequenceName2); // No sequence 1: cleanup and return if (sequenceName1 == "") { clearEntitySequenceData(data, direction); return; } if (sequenceName1 == data->sequenceNameCopy) { data->direction = direction; return; } if (direction == kDirectionLeft || direction == kDirectionRight) { COMPUTE_SEQUENCE_NAME(sequenceName, sequenceName1); if (sequenceName3 != "") COMPUTE_SEQUENCE_NAME(sequenceName3, sequenceName2); } if (!data->frame) { data->direction = direction; if (sequenceName1 == data->sequenceName) { if (sequenceName2 == "") return; loadSequence2(entityIndex, sequenceName2, sequenceName3, field30, loadSequence); return; } SAFE_DELETE(data->sequence); if (sequenceName1 != data->sequenceName2) { if (loadSequence) { if (data->car == getData(kEntityPlayer)->car) data->sequence = loadSequence1(sequenceName1, field30); if (data->sequence) { data->sequenceName = sequenceName1; data->sequenceNameCopy = ""; } else { if (sequenceName != "") data->sequence = loadSequence1(sequenceName, field30); data->sequenceName = (data->sequence ? sequenceName : ""); data->sequenceNameCopy = (data->sequence ? "" : sequenceName1); } } else { data->sequenceName = sequenceName1; } if (sequenceName2 != "") { loadSequence2(entityIndex, sequenceName2, sequenceName3, field30, loadSequence); return; } if (!data->sequence2) { if (sequenceName2 == "") return; loadSequence2(entityIndex, sequenceName2, sequenceName3, field30, loadSequence); return; } SAFE_DELETE(data->sequence2); } else { data->sequence = data->sequence2; data->sequenceName = data->sequenceName2; data->sequence2 = NULL; } data->sequenceName2 = ""; if (sequenceName2 == "") return; loadSequence2(entityIndex, sequenceName2, sequenceName3, field30, loadSequence); return; } if (data->sequenceName != sequenceName1) { if (data->sequenceName2 != sequenceName1) { SAFE_DELETE(data->sequence2); TRY_LOAD_SEQUENCE(data->sequence2, data->sequenceName2, sequenceName1, sequenceName); } data->field_4AA = data->field_4A9; if ((direction != kDirectionUp && direction != kDirectionDown) || data->field_4AA || !data->sequence2) { data->currentFrame2 = 0; } else { data->currentFrame2 = getCurrentFrame(entityIndex, data->sequence2, kPositionNone, false); if (data->currentFrame2 == -1) { clearSequences(entityIndex); return; } } data->field_4A9 = field4A9; data->field_49B = data->frame->getInfo()->field_30; data->currentFrame = (int16)(data->sequence->count() - 1); data->direction = kDirectionSwitch; data->directionSwitch = direction; } else { SAFE_DELETE(data->sequence2); data->sequence2 = loadSequence1(data->sequence->getName(), data->sequence->getField30()); data->sequenceName2 = data->sequenceName; data->field_4AA = data->field_4A9; data->field_49B = data->frame->getInfo()->field_30; data->currentFrame = (int16)(data->sequence->count() - 1); data->direction = kDirectionSwitch; data->directionSwitch = direction; if ((direction != kDirectionUp && direction != kDirectionDown) || data->field_4AA || !data->sequence2) { data->currentFrame2 = 0; } else { data->currentFrame2 = getCurrentFrame(entityIndex, data->sequence2, kPositionNone, false); if (data->currentFrame2 == -1) clearSequences(entityIndex); } } } void Entities::loadSequence2(EntityIndex entityIndex, Common::String sequenceName, Common::String sequenceName2, byte field30, bool reloadSequence) const { EntityData::EntityCallData *data = getData(entityIndex); if (data->sequenceName2 == sequenceName) return; if (data->sequence2) SAFE_DELETE(data->sequence2); if (reloadSequence) { TRY_LOAD_SEQUENCE(data->sequence2, data->sequenceName2, sequenceName, sequenceName2); } else { data->sequenceName2 = sequenceName; } } void Entities::getSequenceName(EntityIndex index, EntityDirection direction, Common::String &sequence1, Common::String &sequence2) const { EntityData::EntityCallData *data = getData(index); Position position = getScenes()->get(getState()->scene)->position; // reset fields data->field_4A9 = false; data->field_4AA = false; switch (direction) { default: break; case kDirectionUp: switch (position) { default: break; case 1: if (data->entityPosition < kPosition_2587) sequence1 = Common::String::format("%02d%01d-01u.seq", index, data->clothes); break; case 2: case 3: case 5: case 6: case 7: case 8: case 9: case 10: case 11: case 12: case 13: case 14: case 15: case 16: case 17: if (data->entityPosition >= kPosition_9270) break; if (data->entityPosition >= kPosition_8513) { sequence1 = Common::String::format("%02d%01d-%02deu.seq", index, data->clothes, position); } else { sequence1 = Common::String::format("%02d%01d-03u.seq", index, data->clothes); sequence2 = Common::String::format("%02d%01d-%02deu.seq", index, data->clothes, position); data->field_4A9 = true; } break; case 18: if (data->entityPosition < kPosition_9270) sequence1 = Common::String::format("%02d%01d-18u.seq", index, data->clothes); break; case 22: if (getData(kEntityPlayer)->entityPosition > data->entityPosition) sequence1 = Common::String::format("%02d%01d-22u.seq", index, data->clothes); break; case 23: case 25: case 26: case 27: case 28: case 29: case 30: case 31: case 32: case 33: case 34: case 35: case 36: case 37: case 38: case 39: if (getData(kEntityPlayer)->entityPosition <= data->entityPosition) break; if (data->entityPosition >= kPosition_2087) { sequence1 = Common::String::format("%02d%01d-38u.seq", index, data->clothes); data->field_4A9 = true; } else { sequence1 = Common::String::format("%02d%01d-%02deu.seq", index, data->clothes, position); sequence2 = Common::String::format("%02d%01d-38u.seq", index, data->clothes); data->field_4AA = true; } break; case 40: if (getData(kEntityPlayer)->entityPosition > data->entityPosition) sequence1 = Common::String::format("%02d%01d-40u.seq", index, data->clothes); break; } break; case kDirectionDown: switch (position) { default: break; case 1: if (getData(kEntityPlayer)->entityPosition < data->entityPosition) sequence1 = Common::String::format("%02d%01d-01d.seq", index, data->clothes); break; case 2: case 3: case 5: case 6: case 7: case 8: case 9: case 10: case 11: case 12: case 13: case 14: case 15: case 16: case 17: if (getData(kEntityPlayer)->entityPosition >= data->entityPosition) break; if (data->entityPosition <= kPosition_8513) { sequence1 = Common::String::format("%02d%01d-03d.seq", index, data->clothes); data->field_4A9 = true; } else { sequence1 = Common::String::format("%02d%01d-%02ded.seq", index, data->clothes, position); sequence2 = Common::String::format("%02d%01d-03d.seq", index, data->clothes); data->field_4AA = true; } break; case 18: if (getData(kEntityPlayer)->entityPosition < data->entityPosition) sequence1 = Common::String::format("%02d%01d-18d.seq", index, data->clothes); break; case 22: if (data->entityPosition > kPosition_850) sequence1 = Common::String::format("%02d%01d-22d.seq", index, data->clothes); break; case 23: case 25: case 26: case 27: case 28: case 29: case 30: case 31: case 32: case 33: case 34: case 35: case 36: case 37: case 38: case 39: if (data->entityPosition <= kPosition_850) break; if (data->entityPosition <= kPosition_2087) { sequence1 = Common::String::format("%02d%01d-%02ded.seq", index, data->clothes, position); } else { sequence1 = Common::String::format("%02d%01d-38d.seq", index, data->clothes); sequence2 = Common::String::format("%02d%01d-%02ded.seq", index, data->clothes, position); data->field_4A9 = true; } break; case 40: if (getData(kEntityPlayer)->entityPosition > kPosition_8013) sequence1 = Common::String::format("%02d%01d-40d.seq", index, data->clothes); break; } break; // First part of sequence is already set case kDirectionLeft: case kDirectionRight: sequence1 = Common::String::format("%s%02d.seq", data->sequenceNamePrefix.c_str(), position); break; } } ////////////////////////////////////////////////////////////////////////// /// Compartments ////////////////////////////////////////////////////////////////////////// void Entities::enterCompartment(EntityIndex entity, ObjectIndex compartment, bool useCompartment1) { if (entity > kEntityChapters) return; switch (compartment) { default: // Return here so we do not update the compartments return; case kObjectCompartment1: updatePositionsEnter(entity, kCarGreenSleeping, 41, 51, 17, 38); break; case kObjectCompartment2: updatePositionsEnter(entity, kCarGreenSleeping, 42, 52, 15, 36); break; case kObjectCompartment3: updatePositionsEnter(entity, kCarGreenSleeping, 43, 53, 13, 34); break; case kObjectCompartment4: updatePositionsEnter(entity, kCarGreenSleeping, 44, 54, 11, 32); break; case kObjectCompartment5: updatePositionsEnter(entity, kCarGreenSleeping, 45, 55, 9, 30); break; case kObjectCompartment6: updatePositionsEnter(entity, kCarGreenSleeping, 46, 56, 7, 28); break; case kObjectCompartment7: updatePositionsEnter(entity, kCarGreenSleeping, 47, 57, 5, 26); break; case kObjectCompartment8: updatePositionsEnter(entity, kCarGreenSleeping, 48, 58, 3, 25); break; case kObjectCompartmentA: updatePositionsEnter(entity, kCarRedSleeping, 41, 51, 17, 38); break; case kObjectCompartmentB: updatePositionsEnter(entity, kCarRedSleeping, 42, 52, 15, 36); break; case kObjectCompartmentC: updatePositionsEnter(entity, kCarRedSleeping, 43, 53, 13, 34); break; case kObjectCompartmentD: updatePositionsEnter(entity, kCarRedSleeping, 44, 54, 11, 32); break; case kObjectCompartmentE: updatePositionsEnter(entity, kCarRedSleeping, 45, 55, 9, 30); break; case kObjectCompartmentF: updatePositionsEnter(entity, kCarRedSleeping, 46, 56, 7, 28); break; case kObjectCompartmentG: updatePositionsEnter(entity, kCarRedSleeping, 47, 57, 5, 26); break; case kObjectCompartmentH: updatePositionsEnter(entity, kCarRedSleeping, 48, 58, 3, 25); break; } // Update compartments int index = (compartment < 32 ? compartment - 1 : compartment - 24); if (index >= 16) error("Entities::exitCompartment: invalid compartment index!"); if (useCompartment1) _compartments1[index] |= STORE_VALUE(entity); else _compartments[index] |= STORE_VALUE(entity); } void Entities::exitCompartment(EntityIndex entity, ObjectIndex compartment, bool useCompartment1) { if (entity > kEntityChapters) return; // TODO factorize in one line switch (compartment) { default: // Return here so we do not update the compartments return; case kObjectCompartment1: updatePositionsExit(entity, kCarGreenSleeping, 41, 51); break; case kObjectCompartment2: updatePositionsExit(entity, kCarGreenSleeping, 42, 52); break; case kObjectCompartment3: updatePositionsExit(entity, kCarGreenSleeping, 43, 53); break; case kObjectCompartment4: updatePositionsExit(entity, kCarGreenSleeping, 44, 54); break; case kObjectCompartment5: updatePositionsExit(entity, kCarGreenSleeping, 45, 55); break; case kObjectCompartment6: updatePositionsExit(entity, kCarGreenSleeping, 46, 56); break; case kObjectCompartment7: updatePositionsExit(entity, kCarGreenSleeping, 47, 57); break; case kObjectCompartment8: updatePositionsExit(entity, kCarGreenSleeping, 48, 58); break; case kObjectCompartmentA: updatePositionsExit(entity, kCarRedSleeping, 41, 51); break; case kObjectCompartmentB: updatePositionsExit(entity, kCarRedSleeping, 42, 52); break; case kObjectCompartmentC: updatePositionsExit(entity, kCarRedSleeping, 43, 53); break; case kObjectCompartmentD: updatePositionsExit(entity, kCarRedSleeping, 44, 54); break; case kObjectCompartmentE: updatePositionsExit(entity, kCarRedSleeping, 45, 55); break; case kObjectCompartmentF: updatePositionsExit(entity, kCarRedSleeping, 46, 56); break; case kObjectCompartmentG: updatePositionsExit(entity, kCarRedSleeping, 47, 57); break; case kObjectCompartmentH: updatePositionsExit(entity, kCarRedSleeping, 48, 58); break; } // Update compartments int index = (compartment < 32 ? compartment - 1 : compartment - 24); if (index >= 16) error("Entities::exitCompartment: invalid compartment index!"); if (useCompartment1) _compartments1[index] &= ~STORE_VALUE(entity); else _compartments[index] &= ~STORE_VALUE(entity); } void Entities::updatePositionEnter(EntityIndex entity, CarIndex car, Position position) { if (entity == kEntity39) entity = kEntityPlayer; if (entity > kEntityChapters) return; _positions[100 * car + position] |= STORE_VALUE(entity); if (isPlayerPosition(car, position) || (car == kCarRestaurant && position == 57 && isPlayerPosition(kCarRestaurant, 50))) { getSound()->excuseMe(entity); getScenes()->loadScene(getScenes()->processIndex(getState()->scene)); getSound()->playSound(kEntityPlayer, "CAT1127A"); } else { getLogic()->updateCursor(); } } void Entities::updatePositionExit(EntityIndex entity, CarIndex car, Position position) { if (entity == kEntity39) entity = kEntityPlayer; if (entity > kEntityChapters) return; _positions[100 * car + position] &= ~STORE_VALUE(entity); getLogic()->updateCursor(); } void Entities::updatePositionsEnter(EntityIndex entity, CarIndex car, Position position1, Position position2, Position position3, Position position4) { if (entity == kEntity39) entity = kEntityPlayer; if (entity > kEntityChapters) return; _positions[100 * car + position1] |= STORE_VALUE(entity); _positions[100 * car + position2] |= STORE_VALUE(entity); // FIXME: also checking two DWORD values that do not seem to updated anywhere... if (isPlayerPosition(car, position1) || isPlayerPosition(car, position2) || isPlayerPosition(car, position3) || isPlayerPosition(car, position4)) { getSound()->excuseMe(entity); getScenes()->loadScene(getScenes()->processIndex(getState()->scene)); getSound()->playSound(kEntityPlayer, "CAT1127A"); } else { getLogic()->updateCursor(); } } void Entities::updatePositionsExit(EntityIndex entity, CarIndex car, Position position1, Position position2) { if (entity == kEntity39) entity = kEntityPlayer; if (entity > kEntityChapters) return; _positions[100 * car + position1] &= ~STORE_VALUE(entity); _positions[100 * car + position2] &= ~STORE_VALUE(entity); getLogic()->updateCursor(); } void Entities::loadSceneFromEntityPosition(CarIndex car, EntityPosition entityPosition, bool alternate) const { // Determine position Position position = (alternate ? 1 : 40); do { if (entityPosition > entityPositions[position]) { if (alternate) break; // For default value, we ignore position 24 if (position != 24) break; } alternate ? ++position : --position; } while (alternate ? position <= 18 : position >= 22); // For position outside bounds, use minimal value if ((alternate && position > 18) || (alternate && position < 22)) { getScenes()->loadSceneFromPosition(car, alternate ? 18 : 22); return; } // Load scene from position switch (position) { default: getScenes()->loadSceneFromPosition(car, (Position)(position + (alternate ? - 1 : 1))); break; // Alternate case 1: if (alternate) getScenes()->loadSceneFromPosition(car, 1); break; case 5: if (alternate) getScenes()->loadSceneFromPosition(car, 3); break; // Default case 23: if (!alternate) getScenes()->loadSceneFromPosition(car, 25); break; case 40: if (!alternate) getScenes()->loadSceneFromPosition(car, 40); break; } } ////////////////////////////////////////////////////////////////////////// // Checks ////////////////////////////////////////////////////////////////////////// bool Entities::hasValidFrame(EntityIndex entity) const { return (getData(entity)->frame && (getData(entity)->frame->getInfo()->subType != kFrameType3)); } bool Entities::compare(EntityIndex entity1, EntityIndex entity2) const { EntityData::EntityCallData *data1 = getData(entity1); EntityData::EntityCallData *data2 = getData(entity2); if (data2->car != data1->car || data1->car < kCarGreenSleeping || data1->car > kCarRedSleeping) return false; EntityPosition position1 = (data1->entityPosition >= data2->entityPosition) ? data1->entityPosition : data2->entityPosition; EntityPosition position2 = (data1->entityPosition >= data2->entityPosition) ? data2->entityPosition : data1->entityPosition; // Compute position int index1 = 7; do { if (objectsPosition[index1] >= position2) break; --index1; } while (index1 > -1); int index2 = 0; do { if (objectsPosition[index2] <= position2) break; ++index2; } while (index2 < 8); if (index1 > -1 && index2 < 8 && index2 <= index1) { while (index2 <= index1) { if (getCompartments(index2 + (data1->car == kCarGreenSleeping ? 0 : 8))) return true; if (getCompartments1(index2 + (data1->car == kCarGreenSleeping ? 0 : 8))) return true; ++index2; } } for (EntityIndex entity = kEntityAnna; entity <= kEntity39; entity = (EntityIndex)(entity + 1)) { if (entity1 == entity || entity2 == entity) continue; if (!isDirectionUpOrDown(entity)) continue; if (data1->car == getEntityData(entity)->car && getEntityData(entity)->entityPosition > position2 && getEntityData(entity)->entityPosition < position1) return true; } return false; } bool Entities::updateEntity(EntityIndex entity, CarIndex car, EntityPosition position) const { EntityData::EntityCallData *data = getData(entity); EntityDirection direction = kDirectionNone; int delta = 0; bool flag1 = false; bool flag2 = false; bool flag3 = false; if (position == kPosition_2000 && getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingUp) && !isPlayerPosition(kCarGreenSleeping, 1) && !isPlayerPosition(kCarRedSleeping, 2)) position = kPosition_1500; if (data->direction != kDirectionUp && data->direction != kDirectionDown) data->field_497 = 0; if (data->field_497) { data->field_497--; if (data->field_497 == 128) data->field_497 = 0; if ((data->field_497 & 127) != 8) { data->field_49B = 0; return false; } flag1 = true; if (data->field_497 & 128) flag2 = true; } if (data->car != car) goto label_process_entity; // Calculate delta delta = ABS(data->entityPosition - position); if (delta < 100 || (position > kPosition_850 && position < kPosition_9270 && delta < 300)) flag3 = true; if (!flag3) { if ((getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingUp) && data->direction == kDirectionUp) || (getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingDown) && data->direction == kDirectionDown)) { if (!checkPosition(position) && isDistanceBetweenEntities(entity, kEntityPlayer, 250)) flag3 = true; } if (!flag3) goto label_process_entity; } if (getEntities()->hasValidFrame(entity) && getEntities()->isWalkingOppositeToPlayer(entity) && !getEntities()->checkPosition(position)) { flag3 = false; position = (EntityPosition)(getData(kEntityPlayer)->entityPosition + 250 * (data->direction == kDirectionUp ? 1 : -1)); } if (!flag3) { label_process_entity: // Calculate direction if (data->car < car) direction = kDirectionUp; else if (data->car > car) direction = kDirectionDown; else // same car direction = (data->entityPosition < position) ? kDirectionUp : kDirectionDown; if (data->direction == direction) { if (!flag1) { if (checkDistanceFromPosition(entity, kPosition_1500, 750) && entity != kEntityFrancois) { if (data->entity != kEntityPlayer) { if (data->direction != kDirectionUp || (position <= kPosition_2000 && data->car == car)) { if (data->direction == kDirectionDown && (position < kPosition_1500 || data->car != car)) { if (data->entityPosition > kPosition_1500 && (data->car == kCarGreenSleeping || data->car == kCarRedSleeping)) { data->entity = (data->car == kCarGreenSleeping) ? kEntityMertens : kEntityCoudert; getSavePoints()->push(entity, data->entity, kAction11); } } } else { if (data->entityPosition < kPosition_1500 && (data->car == kCarGreenSleeping || data->car == kCarRedSleeping)) { data->entity = (data->car == kCarGreenSleeping) ? kEntityMertens : kEntityCoudert; getSavePoints()->push(entity, data->entity, kAction11, 1); } } } } else if (data->entity) { getSavePoints()->push(entity, data->entity, kAction16); data->entity = kEntityPlayer; } if (hasValidFrame(entity)) { if (!data->field_4A9) return false; int compartmentIndex = 0; if (data->car == kCarGreenSleeping) compartmentIndex = 0; else if (data->car == kCarRedSleeping) compartmentIndex = 8; for (int i = 0; i < 8; i++) { if (getCompartments(compartmentIndex) || getCompartments1(compartmentIndex)) { if (checkDistanceFromPosition(entity, objectsPosition[i], 750)) { if (checkPosition(objectsPosition[i])) { if ((data->direction == kDirectionUp && data->entityPosition < objectsPosition[i] && (data->car != car || position > objectsPosition[i])) || (data->direction == kDirectionDown && data->entityPosition > objectsPosition[i] && (data->car != car || position < objectsPosition[i]))) { getSound()->excuseMe(entity, (EntityIndex)(State::getPowerOfTwo((uint32)(getCompartments(compartmentIndex) ? getCompartments(compartmentIndex) : getCompartments1(compartmentIndex))))); data->field_497 = 144; break; } } } } compartmentIndex++; } for (EntityIndex entityIndex = kEntityAnna; entityIndex <= kEntity39; entityIndex = (EntityIndex)(entityIndex + 1)) { if (getSavePoints()->getCallback(entityIndex) && hasValidFrame(entityIndex) && entityIndex != entity && isDistanceBetweenEntities(entity, entityIndex, 750) && isDirectionUpOrDown(entityIndex) && (entity != kEntityRebecca || entityIndex != kEntitySophie) && (entity != kEntitySophie || entityIndex != kEntityRebecca) && (entity != kEntityIvo || entityIndex != kEntitySalko) && (entity != kEntitySalko || entityIndex != kEntityIvo) && (entity != kEntityMilos || entityIndex != kEntityVesna) && (entity != kEntityVesna || entityIndex != kEntityMilos)) { EntityData::EntityCallData *data2 = getData(entityIndex); if (data->direction != data2->direction) { if ((data->direction != kDirectionUp || data2->entityPosition <= data->entityPosition) && (data->direction != kDirectionDown || data2->entityPosition >= data->entityPosition)) continue; data->field_49B = 0; data2->field_49B = 0; data->field_497 = 16; data2->field_497 = 16; getSound()->excuseMe(entity, entityIndex); getSound()->excuseMe(entityIndex, entity); if (entityIndex > entity) ++data2->field_497; break; } if (ABS(data2->entityPosition - getData(kEntityPlayer)->entityPosition) < ABS(data->entityPosition - getData(kEntityPlayer)->entityPosition)) { if (!isWalkingOppositeToPlayer(entity)) { if (direction == kDirectionUp) { if (data->entityPosition < kPosition_9500) data->entityPosition = (EntityPosition)(data->entityPosition + 500); } else { if (data->entityPosition > kPosition_500) data->entityPosition = (EntityPosition)(data->entityPosition - 500); } drawSequences(entity, direction, true); return false; } data->field_49B = 0; break; } } } return false; } if (data->direction == kDirectionUp) { if (data->entityPosition + data->field_4A3 < 10000) data->entityPosition = (EntityPosition)(data->entityPosition + data->field_4A3); } else { if (data->entityPosition > data->field_4A3) data->entityPosition = (EntityPosition)(data->entityPosition - data->field_4A3); } if (data->entityPosition <= kPosition_9270 || data->direction != kDirectionUp) { if (data->entityPosition < kPosition_850 && data->direction == kDirectionDown) { if (changeCar(data, entity, car, position, false, kPosition_9269, kCarKronos)) return true; } } else { if (changeCar(data, entity, car, position, true, kPosition_851, kCarGreenSleeping)) return true; } if (getData(kEntityPlayer)->car == data->car && data->location == kLocationOutsideCompartment) { if (data->direction == kDirectionUp) { if (getData(kEntityPlayer)->entityPosition > data->entityPosition && getData(kEntityPlayer)->entityPosition - data->entityPosition >= 500 && data->field_4A3 + 500 > getData(kEntityPlayer)->entityPosition - data->entityPosition) { if (getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingUp) || getScenes()->checkCurrentPosition(false)) { getSavePoints()->push(kEntityPlayer, entity, kActionExcuseMe); if (getScenes()->checkCurrentPosition(false)) getScenes()->loadSceneFromObject((ObjectIndex)getScenes()->get(getState()->scene)->param1, true); } else if (getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingDown)) { getSavePoints()->push(kEntityPlayer, entity, kActionExcuseMeCath); } } } else { if (getData(kEntityPlayer)->entityPosition < data->entityPosition && data->entityPosition - getData(kEntityPlayer)->entityPosition >= 500 && data->field_4A3 + 500 > data->entityPosition - getData(kEntityPlayer)->entityPosition) { if (getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingUp)) { getSavePoints()->push(kEntityPlayer, entity, kActionExcuseMeCath); } else if (getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingDown) || getScenes()->checkCurrentPosition(false)){ getSavePoints()->push(kEntityPlayer, entity, kActionExcuseMe); if (getScenes()->checkCurrentPosition(false)) getScenes()->loadSceneFromObject((ObjectIndex)getScenes()->get(getState()->scene)->param1); } } } return false; } } } else if (!flag1) { drawSequences(entity, direction, true); return false; } ////////////////////////////////////////////////////////////////////////// // Adjust positions // Direction Up if (direction == kDirectionUp) { if (data->entityPosition < (flag2 ? kPosition_8800 : kPosition_9250)) data->entityPosition = (EntityPosition)(data->entityPosition + (flag2 ? kPosition_1200 : kPosition_750)); if (data->car == car && data->entityPosition >= position) { data->entityPosition = position; data->direction = kDirectionNone; data->entity = kEntityPlayer; return true; } drawSequences(entity, direction, true); return false; } // Direction Down if (data->entityPosition > (flag2 ? kPosition_1200 : kPosition_750)) data->entityPosition = (EntityPosition)(data->entityPosition - (flag2 ? kPosition_1200 : kPosition_750)); if (data->car == car && data->entityPosition <= position) { data->entityPosition = position; data->direction = kDirectionNone; data->entity = kEntityPlayer; return true; } drawSequences(entity, direction, true); return false; } data->entityPosition = position; if (data->direction == kDirectionUp || data->direction == kDirectionDown) data->direction = kDirectionNone; data->entity = kEntityPlayer; return true; } bool Entities::changeCar(EntityData::EntityCallData *data, EntityIndex entity, CarIndex car, EntityPosition position, bool increment, EntityPosition newPosition, CarIndex newCar) const { if (getData(kEntityPlayer)->car == data->car) { getSound()->playSoundEvent(entity, 36); getSound()->playSoundEvent(entity, 37, 30); } data->car = (CarIndex)(increment ? data->car + 1 : data->car - 1); data->entityPosition = newPosition; if (data->car == newCar) { if (isInGreenCarEntrance(kEntityPlayer)) { getSound()->playSoundEvent(kEntityPlayer, 14); getSound()->excuseMe(entity, kEntityPlayer, SoundManager::kFlagDefault); getScenes()->loadSceneFromPosition(kCarGreenSleeping, 1); getSound()->playSound(kEntityPlayer, "CAT1127A"); getSound()->playSoundEvent(kEntityPlayer, 15); } } if ((increment ? data->car > car : data->car < car) || (data->car == car && (increment ? data->entityPosition >= position : data->entityPosition <= position))) { data->car = car; data->entityPosition = position; data->direction = kDirectionNone; data->entity = kEntityPlayer; return true; } if (data->car == newCar) { if (isInKronosCarEntrance(kEntityPlayer)) { getSound()->playSoundEvent(kEntityPlayer, 14); getSound()->excuseMe(entity, kEntityPlayer, SoundManager::kFlagDefault); getScenes()->loadSceneFromPosition(kCarGreenSleeping, 62); getSound()->playSound(kEntityPlayer, "CAT1127A"); getSound()->playSoundEvent(kEntityPlayer, 15); } } if (data->car == getData(kEntityPlayer)->car) { getSound()->playSoundEvent(entity, 36); getSound()->playSoundEvent(entity, 37, 30); } return false; } ////////////////////////////////////////////////////////////////////////// // CHECKS ////////////////////////////////////////////////////////////////////////// bool Entities::isInsideCompartment(EntityIndex entity, CarIndex car, EntityPosition position) const { return (getData(entity)->entityPosition == position && getData(entity)->location == kLocationInsideCompartment && getData(entity)->car == car); } bool Entities::checkFields2(ObjectIndex object) const { EntityPosition position = kPositionNone; CarIndex car = kCarNone; switch (object) { default: return false; case kObjectCompartment1: case kObjectCompartment2: case kObjectCompartment3: case kObjectCompartment4: case kObjectCompartment5: case kObjectCompartment6: case kObjectCompartment7: case kObjectCompartment8: position = objectsPosition[object - 1]; car = kCarGreenSleeping; if (isInsideCompartment(kEntityPlayer, car, position)) return false; break; case kObjectHandleBathroom: case kObjectHandleInsideBathroom: case kObjectKitchen: case kObject20: case kObject21: case kObject22: position = objectsPosition[object-17]; car = kCarGreenSleeping; break; case kObjectCompartmentA: case kObjectCompartmentB: case kObjectCompartmentC: case kObjectCompartmentD: case kObjectCompartmentE: case kObjectCompartmentF: case kObjectCompartmentG: case kObjectCompartmentH: position = objectsPosition[object-32]; car = kCarRedSleeping; if (isInsideCompartment(kEntityPlayer, car, position)) return false; break; case kObject48: case kObject49: case kObject50: case kObject51: case kObject52: case kObject53: position = objectsPosition[object-48]; car = kCarRedSleeping; break; } uint index = 1; while (!isInsideCompartment((EntityIndex)index, car, position) || index == kEntityVassili) { index++; if (index >= 40) return false; } return true; } bool Entities::isInsideCompartments(EntityIndex entity) const { return (getData(entity)->car == kCarGreenSleeping || getData(entity)->car == kCarRedSleeping) && getData(entity)->location == kLocationInsideCompartment; } bool Entities::isPlayerPosition(CarIndex car, Position position) const { return getData(kEntityPlayer)->car == car && getScenes()->get(getState()->scene)->position == position; } bool Entities::isInsideTrainCar(EntityIndex entity, CarIndex car) const { return getData(entity)->car == car && getData(entity)->location <= kLocationInsideCompartment; } bool Entities::isInGreenCarEntrance(EntityIndex entity) const { return isInsideTrainCar(entity, kCarGreenSleeping) && getData(entity)->entityPosition < kPosition_850; } bool Entities::isPlayerInCar(CarIndex car) const { return isInsideTrainCar(kEntityPlayer, car) && getData(kEntityPlayer)->location && !isInGreenCarEntrance(kEntityPlayer); } bool Entities::isDirectionUpOrDown(EntityIndex entity) const { return getData(entity)->direction == kDirectionUp || getData(entity)->direction == kDirectionDown; } bool Entities::isDistanceBetweenEntities(EntityIndex entity1, EntityIndex entity2, uint distance) const { return getData(entity1)->car == getData(entity2)->car && (uint)ABS(getData(entity1)->entityPosition - getData(entity2)->entityPosition) <= distance && (getData(entity1)->location != kLocationOutsideTrain || getData(entity2)->location != kLocationOutsideTrain); } bool Entities::checkFields10(EntityIndex entity) const { return getData(entity)->location <= kLocationOutsideTrain; } bool Entities::isSomebodyInsideRestaurantOrSalon() const { for (uint i = 1; i < _entities.size(); i++) { EntityIndex index = (EntityIndex)i; if (getData(index)->location == kLocationOutsideCompartment && (isInSalon(index) || isInRestaurant(index))) return false; } return true; } bool Entities::isInSalon(EntityIndex entity) const { return isInsideTrainCar(entity, kCarRestaurant) && getData(entity)->entityPosition >= kPosition_1540 && getData(entity)->entityPosition <= kPosition_3650; } bool Entities::isInRestaurant(EntityIndex entity) const { return isInsideTrainCar(entity, kCarRestaurant) && getData(entity)->entityPosition >= kPosition_3650 && getData(entity)->entityPosition <= kPosition_5800; } bool Entities::isInKronosSalon(EntityIndex entity) const { return isInsideTrainCar(entity, kCarKronos) && getData(entity)->entityPosition >= kPosition_5500 && getData(entity)->entityPosition <= kPosition_7500; } bool Entities::isOutsideAlexeiWindow() const { return (getData(kEntityPlayer)->entityPosition == kPosition_7500 || getData(kEntityPlayer)->entityPosition == kPosition_8200) && getData(kEntityPlayer)->location == kLocationOutsideTrain && getData(kEntityPlayer)->car == kCarGreenSleeping; } bool Entities::isOutsideAnnaWindow() const { return (getData(kEntityPlayer)->entityPosition == kPosition_4070 || getData(kEntityPlayer)->entityPosition == kPosition_4840) && getData(kEntityPlayer)->location == kLocationOutsideTrain && getData(kEntityPlayer)->car == kCarRedSleeping; } bool Entities::isInKitchen(EntityIndex entity) const { return isInsideTrainCar(entity, kCarRestaurant) && getData(entity)->entityPosition > kPosition_5800; } bool Entities::isNobodyInCompartment(CarIndex car, EntityPosition position) const { for (uint i = 1; i < _entities.size(); i++) { if (isInsideCompartment((EntityIndex)i, car, position)) return false; } return true; } bool Entities::checkFields19(EntityIndex entity, CarIndex car, EntityPosition position) const { if (getData(entity)->car != car || getData(entity)->location != kLocationInsideCompartment) return false; EntityPosition entityPosition = getData(entity)->entityPosition; // Test values if (position == kPosition_4455) { if (entityPosition == kPosition_4070 || entityPosition == kPosition_4455 || entityPosition == kPosition_4840) return true; return false; } if (position == kPosition_6130) { if (entityPosition == kPosition_5790 || entityPosition == kPosition_6130 || entityPosition == kPosition_6470) return true; return false; } if (position != kPosition_7850 || (entityPosition != kPosition_7500 && entityPosition != kPosition_7850 && entityPosition != kPosition_8200)) return false; return true; } bool Entities::isInBaggageCarEntrance(EntityIndex entity) const { return isInsideTrainCar(entity, kCarBaggage) && getData(entity)->entityPosition >= kPosition_4500 && getData(entity)->entityPosition <= kPosition_5500; } bool Entities::isInBaggageCar(EntityIndex entity) const { return isInsideTrainCar(entity, kCarBaggage) && getData(entity)->entityPosition < kPosition_4500; } bool Entities::isInKronosSanctum(EntityIndex entity) const { return isInsideTrainCar(entity, kCarKronos) && getData(entity)->entityPosition >= kPosition_3500 && getData(entity)->entityPosition <= kPosition_5500; } bool Entities::isInKronosCarEntrance(EntityIndex entity) const { return isInsideTrainCar(entity, kCarKronos) && getData(entity)->entityPosition > kPosition_7900; } bool Entities::checkDistanceFromPosition(EntityIndex entity, EntityPosition position, int distance) const { return distance >= ABS(getData(entity)->entityPosition - position); } bool Entities::isWalkingOppositeToPlayer(EntityIndex entity) const { if (getData(entity)->direction == kDirectionUp && getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingDown)) return true; return (getData(entity)->direction == kDirectionDown && getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingUp)); } bool Entities::isFemale(EntityIndex entity) { return (entity == kEntityAnna || entity == kEntityTatiana || entity == kEntityVesna || entity == kEntityKahina || entity == kEntityMmeBoutarel || entity == kEntityRebecca || entity == kEntitySophie || entity == kEntityYasmin || entity == kEntityHadija || entity == kEntityAlouan); } bool Entities::isMarried(EntityIndex entity) { return (entity != kEntityTatiana && entity != kEntityRebecca && entity != kEntitySophie); } bool Entities::checkPosition(EntityPosition position) const { Position position1 = 0; Position position2 = 0; switch (position) { default: return true; case kPosition_1500: position1 = 1; position2 = 23; break; case kPosition_2740: position1 = 3; position2 = 25; break; case kPosition_3050: position1 = 5; position2 = 26; break; case kPosition_4070: position1 = 7; position2 = 28; break; case kPosition_4840: position1 = 9; position2 = 30; break; case kPosition_5790: position1 = 11; position2 = 32; break; case kPosition_6470: position1 = 13; position2 = 34; break; case kPosition_7500: position1 = 15; position2 = 36; break; case kPosition_8200: position1 = 17; position2 = 38; break; } if (getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingUp) && entityPositions[position1] >= getEntityData(kEntityPlayer)->entityPosition) return true; else return (getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingDown) && entityPositions[position2] <= getEntityData(kEntityPlayer)->entityPosition); } bool Entities::checkSequenceFromPosition(EntityIndex entity) const { FrameInfo *info = getEntityData(entity)->sequence->getFrameInfo((uint16)getEntityData(entity)->currentFrame); if (getEntityData(entity)->direction == kDirectionUp) return (getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingUp) && info->entityPosition + getEntityPositionFromCurrentPosition() > kPosition_8513); if (getEntityData(entity)->direction == kDirectionDown) return (getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingDown) && info->entityPosition + getEntityPositionFromCurrentPosition() < kPosition_2087); return false; } EntityPosition Entities::getEntityPositionFromCurrentPosition() const { // Get the scene position first Position position = getScenes()->get(getState()->scene)->position; if (getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingUp)) return (EntityPosition)(entityPositions[position] - kPosition_1430); if (getScenes()->checkPosition(kSceneNone, SceneManager::kCheckPositionLookingDown)) return (EntityPosition)(entityPositions[position] - kPosition_9020); return kPositionNone; } void Entities::clearEntitySequenceData(EntityData::EntityCallData *data, EntityDirection direction) const { getScenes()->removeAndRedraw(&data->frame, false); getScenes()->removeAndRedraw(&data->frame1, false); SAFE_DELETE(data->sequence); SAFE_DELETE(data->sequence2); data->sequenceName = ""; data->sequenceName2 = ""; data->field_4A9 = false; data->field_4AA = false; data->directionSwitch = kDirectionNone; data->currentFrame = -1; data->currentFrame2 = 0; data->direction = direction; } } // End of namespace LastExpress