From ca3a9dcc8764909e163a860e4a472404620480e2 Mon Sep 17 00:00:00 2001 From: Matthew Stewart Date: Wed, 23 May 2018 19:56:15 -0400 Subject: STARTREK: Finish implementing first room Doors now work, and the system of "walking, then performing an action afterward" is implemented. --- engines/startrek/action.h | 22 +++- engines/startrek/awaymission.cpp | 98 +++++++++------ engines/startrek/awaymission.h | 7 +- engines/startrek/object.h | 11 +- engines/startrek/room.cpp | 49 +++++--- engines/startrek/room.h | 17 ++- engines/startrek/rooms/demon0.cpp | 34 +++++- engines/startrek/rooms/function_map.h | 33 ++++-- engines/startrek/startrek.cpp | 217 ++++++++++++++++++++++++++++++++-- engines/startrek/startrek.h | 18 ++- 10 files changed, 410 insertions(+), 96 deletions(-) diff --git a/engines/startrek/action.h b/engines/startrek/action.h index 68d5823000..7be09eb671 100644 --- a/engines/startrek/action.h +++ b/engines/startrek/action.h @@ -35,8 +35,8 @@ enum Acton { ACTION_TOUCHED_WARP = 6, ACTION_TOUCHED_HOTSPOT = 7, // Doors? (Or just hotspots activated by Kirk moving there?) - ACTION_FINISHED_BEAMING_IN = 10, - ACTION_FINISHED_ENTERING_ROOM = 12, + ACTION_FINISHED_ANIMATION = 10, + ACTION_FINISHED_WALKING = 12, ACTION_OPTIONS = 13 // Not really an action, but selectable from action menu }; @@ -46,6 +46,7 @@ struct Action { byte b2; byte b3; + Action() {} Action(byte _type, byte _b1, byte _b2, byte _b3) : type(_type), b1(_b1), @@ -60,6 +61,23 @@ struct Action { bool operator==(const Action &a) const { return type == a.type && b1 == a.b1 && b2 == a.b2 && b3 == a.b3; } + + uint32 getBitmask() const { + uint32 ret = 0; + if (type != 0xff) + ret |= (0xff << 24); + if (b1 != 0xff) + ret |= (0xff << 16); + if (b2 != 0xff) + ret |= (0xff << 8); + if (b3 != 0xff) + ret |= (0xff << 0); + return ret; + } + + uint32 toUint32() const { + return (type << 24) | (b1 << 16) | (b2 << 8) | (b3 << 0); + } }; #endif diff --git a/engines/startrek/awaymission.cpp b/engines/startrek/awaymission.cpp index 5db0271da1..5bd134df8f 100644 --- a/engines/startrek/awaymission.cpp +++ b/engines/startrek/awaymission.cpp @@ -128,8 +128,8 @@ void StarTrekEngine::initAwayCrewPositions(int warpEntryIndex) { actorWalkToPosition(i, anim, srcX, srcY, destX, destY); } - _kirkActor->walkingIntoRoom = 1; - _kirkActor->field66 = 0xff; + _kirkActor->triggerActionWhenAnimFinished = true; + _kirkActor->finishedAnimActionParam = 0xff; _awayMission.transitioningIntoRoom = 1; _warpHotspotsActive = false; break; @@ -140,8 +140,8 @@ void StarTrekEngine::initAwayCrewPositions(int warpEntryIndex) { Common::Point warpPos = _room->getBeamInPosition(i); loadActorAnimWithRoomScaling(i, animFilename, warpPos.x, warpPos.y); } - _kirkActor->walkingIntoRoom = 1; - _kirkActor->field66 = 0xff; + _kirkActor->triggerActionWhenAnimFinished = true; + _kirkActor->finishedAnimActionParam = 0xff; _awayMission.transitioningIntoRoom = 1; playSoundEffectIndex(0x09); _warpHotspotsActive = false; @@ -179,26 +179,33 @@ void StarTrekEngine::handleAwayMissionEvents() { break; switch (_awayMission.activeAction) { - case ACTION_WALK: - if (_awayMission.field1c == 0) { - _kirkActor->sprite.drawMode = 1; // Hide these objects for function call below? - _spockActor->sprite.drawMode = 1; - _mccoyActor->sprite.drawMode = 1; - _redshirtActor->sprite.drawMode = 1; - - // findActorClickedOn(); - // ... - - _kirkActor->sprite.drawMode = 0; - _spockActor->sprite.drawMode = 0; - _mccoyActor->sprite.drawMode = 0; - _redshirtActor->sprite.drawMode = 0; - - Common::String animFilename = getCrewmanAnimFilename(0, "walk"); + case ACTION_WALK: { + if (_awayMission.field1c != 0) + break; + _kirkActor->sprite.drawMode = 1; // Hide these objects for function call below? + _spockActor->sprite.drawMode = 1; + _mccoyActor->sprite.drawMode = 1; + _redshirtActor->sprite.drawMode = 1; + + int clickedObject = findObjectAt(_gfx->getMousePos()); + + _kirkActor->sprite.drawMode = 0; + _spockActor->sprite.drawMode = 0; + _mccoyActor->sprite.drawMode = 0; + _redshirtActor->sprite.drawMode = 0; + + if (walkActiveObjectToHotspot()) + break; + + if (clickedObject > OBJECT_KIRK && clickedObject < ITEMS_START) + addAction(ACTION_WALK, clickedObject, 0, 0); + else { + Common::String animFilename = getCrewmanAnimFilename(OBJECT_KIRK, "walk"); Common::Point mousePos = _gfx->getMousePos(); - actorWalkToPosition(0, animFilename, _kirkActor->pos.x, _kirkActor->pos.y, mousePos.x, mousePos.y); + actorWalkToPosition(OBJECT_KIRK, animFilename, _kirkActor->pos.x, _kirkActor->pos.y, mousePos.x, mousePos.y); } break; + } case ACTION_USE: { if (_awayMission.activeObject == OBJECT_REDSHIRT && (_awayMission.redshirtDead || (_awayMission.field24 & 8))) { @@ -246,7 +253,7 @@ void StarTrekEngine::handleAwayMissionEvents() { || (activeIsCrewman && passiveIsItem) || (activeIsItem && passiveIsItem)) { if (_awayMission.passiveObject == OBJECT_ICOMM) { - if (sub_2330c()) + if (walkActiveObjectToHotspot()) break; addAction(Action(ACTION_USE, OBJECT_ICOMM, 0, 0)); _sound->playVoc("commun30"); @@ -264,7 +271,7 @@ void StarTrekEngine::handleAwayMissionEvents() { } checkAddAction: - if (!sub_2330c()) + if (!walkActiveObjectToHotspot()) { if (clickedObject != -2) addAction(Action(_awayMission.activeAction, _awayMission.activeObject, _awayMission.passiveObject, 0)); @@ -291,13 +298,13 @@ checkShowInventory: _awayMission.activeObject = clickedObject; - if (sub_2330c()) + if (walkActiveObjectToHotspot()) break; if (clickedObject != -2) addAction(Action(_awayMission.activeAction, _awayMission.activeObject, 0, 0)); - if (_awayMission.activeAction == ACTION_LOOK && !(_awayMission.field24 & 1)) + if (_awayMission.activeAction == ACTION_LOOK && !(_awayMission.field24 & (1 << OBJECT_KIRK))) showInventoryIcons(false); } break; @@ -316,16 +323,15 @@ checkShowInventory: playSoundEffectIndex(0x07); _awayMission.activeAction = showActionMenu(); if (_awayMission.activeAction == ACTION_USE) { - //int16 clickedObject = selectObjectForGetAction(); // TODO - int16 clickedObject = 0; + int16 clickedObject = selectObjectForUseAction(); if (clickedObject == -1) break; else _awayMission.activeObject = clickedObject; } if (_awayMission.activeAction == ACTION_USE - && _awayMission.activeObject == OBJECT_ICOMM && (_awayMission.field24 & 1) == 0) { // TODO - if (!sub_2330c()) { + && _awayMission.activeObject == OBJECT_ICOMM && (_awayMission.field24 & (1 << OBJECT_KIRK)) == 0) { + if (!walkActiveObjectToHotspot()) { addAction(Action(_awayMission.activeAction, _awayMission.activeObject, 0, 0)); _sound->playVoc("communic"); _awayMission.activeAction = ACTION_WALK; @@ -389,13 +395,17 @@ void StarTrekEngine::addAction(const Action &action) { void StarTrekEngine::handleAwayMissionAction() { Action action = _actionQueue.pop(); - if ((action.type == ACTION_FINISHED_BEAMING_IN || action.type == ACTION_FINISHED_ENTERING_ROOM) && action.b1 == 0xff) { + if ((action.type == ACTION_FINISHED_ANIMATION || action.type == ACTION_FINISHED_WALKING) && action.b1 == 0xff) { _awayMission.transitioningIntoRoom = 0; _warpHotspotsActive = true; return; } - else if (action.type == ACTION_FINISHED_ENTERING_ROOM && action.b1 >= 0xe0) { // TODO - return; + else if (action.type == ACTION_FINISHED_WALKING && action.b1 >= 0xe0) { + // Finished walking to a position; perform the action that was input back when + // they started walking over there. + int index = action.b1 - 0xe0; + addAction(_actionOnWalkCompletion[index]); + _actionOnWalkCompletionInUse[index] = false; } if (_room->handleAction(action)) @@ -403,10 +413,14 @@ void StarTrekEngine::handleAwayMissionAction() { // Action not defined for the room, check for default behaviour - switch (action.type) { // TODO: everything + switch (action.type) { - case ACTION_WALK: // TODO - warning("Unhandled walk action: %d %d %d", action.b1, action.b2, action.b3); + case ACTION_WALK: + if (!_room->handleActionWithBitmask(action)) { + Common::String animFilename = getCrewmanAnimFilename(OBJECT_KIRK, "walk"); + Common::Point mousePos = _gfx->getMousePos(); + actorWalkToPosition(OBJECT_KIRK, animFilename, _kirkActor->pos.x, _kirkActor->pos.y, mousePos.x, mousePos.y); + } break; case ACTION_USE: // TODO @@ -446,8 +460,7 @@ void StarTrekEngine::handleAwayMissionAction() { break; case ACTION_TOUCHED_WARP: - // if (!sub_203e1(action.type)) // Probably calls RDF code - { + if (!_room->handleActionWithBitmask(action)) { byte warpIndex = action.b1; int16 roomIndex = _room->readRdfWord(RDF_WARP_ROOM_INDICES + warpIndex * 2); unloadRoom(); @@ -538,4 +551,15 @@ bool StarTrekEngine::isPositionSolid(int16 x, int16 y) { return _mapFile->readByte() & (0x80 >> (x % 8)); } +void StarTrekEngine::loadRoomIndex(int roomIndex, int spawnIndex) { + unloadRoom(); + _sound->loadMusicFile("ground"); + + loadRoom(_missionName, roomIndex); + initAwayCrewPositions(spawnIndex % 6); + + // WORKAROUND: original game calls "retrieveStackVars" to return execution directly to + // the top of "runAwayMission". That can't really be done here. But does it matter? +} + } diff --git a/engines/startrek/awaymission.h b/engines/startrek/awaymission.h index b89e9cf0fe..34cdb4ba25 100644 --- a/engines/startrek/awaymission.h +++ b/engines/startrek/awaymission.h @@ -29,7 +29,7 @@ struct AwayMission { int16 mouseY; byte field1c; byte field1d; - byte transitioningIntoRoom; // Set while beaming in or walking into a room + byte transitioningIntoRoom; // Set while beaming in or walking into a room. Disables control? bool redshirtDead; byte activeAction; byte activeObject; // The item that is going to be used on something @@ -39,8 +39,11 @@ struct AwayMission { // any "default" code for the event, if any. bool rdfStillDoDefaultAction; + // Bits 0-3 correspond to the crewmen? A bit is set if they're not selectable with + // the use action? byte field24; - int8 field25[4]; + + int8 field25[4]; // Sets an object's direction after they finish walking somewhere? // Demon Word: nonzero if a rude response was given to the prelate. int16 field29; diff --git a/engines/startrek/object.h b/engines/startrek/object.h index 2cfbbd2611..888289249d 100644 --- a/engines/startrek/object.h +++ b/engines/startrek/object.h @@ -56,6 +56,13 @@ const int ITEMS_END = ITEMS_START + NUM_ITEMS; // See items.h const int NUM_OBJECTS = ITEMS_END; +enum Directions { + DIR_N = 0, + DIR_S = 1, + DIR_E = 2, + DIR_W = 3 +}; + // Some object indices are reserved (see items.h for item objects) enum Objects { OBJECT_KIRK = 0, @@ -80,8 +87,8 @@ struct Actor { Common::Point pos; uint16 field60; uint16 field62; - uint16 walkingIntoRoom; // Walking or beaming into a room? - uint16 field66; + bool triggerActionWhenAnimFinished; + uint16 finishedAnimActionParam; char animationString2[8]; uint16 field70; uint16 field72; diff --git a/engines/startrek/room.cpp b/engines/startrek/room.cpp index 555a72126b..17d66d8cf7 100644 --- a/engines/startrek/room.cpp +++ b/engines/startrek/room.cpp @@ -83,6 +83,23 @@ bool Room::handleAction(const Action &action) { return false; } +bool Room::handleActionWithBitmask(const Action &action) { + RoomAction *roomActionPtr = _roomActionList; + int n = _numRoomActions; + + while (n-- > 0) { + uint32 bitmask = action.getBitmask(); + if ((action.toUint32() & bitmask) == (roomActionPtr->action.toUint32() & bitmask)) { + _vm->_awayMission.rdfStillDoDefaultAction = false; + (this->*(roomActionPtr->funcPtr))(); + if (!_vm->_awayMission.rdfStillDoDefaultAction) + return true; + } + roomActionPtr++; + } + return false; +} + Common::Point Room::getBeamInPosition(int crewmanIndex) { int base = 0xaa + crewmanIndex * 4; return Common::Point(readRdfWord(base), readRdfWord(base + 2)); @@ -91,7 +108,7 @@ Common::Point Room::getBeamInPosition(int crewmanIndex) { // Interface for room-specific code -void Room::loadActorAnim(int actorIndex, Common::String anim, int16 x, int16 y, uint16 field66) { +void Room::loadActorAnim(int actorIndex, Common::String anim, int16 x, int16 y, uint16 finishedAnimActionParam) { Actor *actor = &_vm->_actorList[actorIndex]; if (x == -1 || y == -1) { @@ -104,9 +121,9 @@ void Room::loadActorAnim(int actorIndex, Common::String anim, int16 x, int16 y, else _vm->loadActorAnim(actorIndex, anim, x, y, 256); - if (field66 != 0) { - actor->walkingIntoRoom = 1; - actor->field66 = field66; + if (finishedAnimActionParam != 0) { + actor->triggerActionWhenAnimFinished = true; + actor->finishedAnimActionParam = finishedAnimActionParam; } } @@ -125,8 +142,8 @@ void Room::loadActorStandAnim(int actorIndex) { /** * This is exactly the same as "loadActorAnim", but the game calls it at different times? */ -void Room::loadActorAnim2(int actorIndex, Common::String anim, int16 x, int16 y, uint16 field66) { - loadActorAnim(actorIndex, anim, x, y, field66); +void Room::loadActorAnim2(int actorIndex, Common::String anim, int16 x, int16 y, uint16 finishedAnimActionParam) { + loadActorAnim(actorIndex, anim, x, y, finishedAnimActionParam); } // TODO: replace "rdfOffset" with a pointer, so we no longer read from RDF files? (This @@ -168,17 +185,15 @@ void Room::loadRoomIndex(int roomIndex, int spawnIndex) { if (_vm->_awayMission.field24 != 0) return; - _vm->unloadRoom(); - _vm->_sound->loadMusicFile("ground"); - - _vm->loadRoom(_vm->_missionName, roomIndex); - _vm->initAwayCrewPositions(spawnIndex % 6); + _vm->loadRoomIndex(roomIndex, spawnIndex); - // TODO: "retrieveStackVars" call returns program counter directly to beginning of - // away mission loop. How to handle this? + // This room has now been deleted, don't do anything else here. + // FIXME: this could a bit dangerous since this is generally called from room-specific + // code, which isn't guaranteed to do nothing afterward. Original game would + // manipulate the stack to jump directly back to the start of "runAwayMission"... } -void Room::walkCrewman(int actorIndex, int16 destX, int16 destY, uint16 field66) { +void Room::walkCrewman(int actorIndex, int16 destX, int16 destY, uint16 finishedAnimActionParam) { if (!(actorIndex >= OBJECT_KIRK && actorIndex < OBJECT_REDSHIRT)) error("Tried to walk a non PC"); @@ -186,9 +201,9 @@ void Room::walkCrewman(int actorIndex, int16 destX, int16 destY, uint16 field66) Common::String anim = _vm->getCrewmanAnimFilename(actorIndex, "walk"); bool success = _vm->actorWalkToPosition(actorIndex, anim, actor->pos.x, actor->pos.y, destX, destY); - if (success && field66 != 0) { - actor->walkingIntoRoom = 1; - actor->field66 = field66; + if (success && finishedAnimActionParam != 0) { + actor->triggerActionWhenAnimFinished = true; + actor->finishedAnimActionParam = finishedAnimActionParam; } } diff --git a/engines/startrek/room.h b/engines/startrek/room.h index cb00815cff..220c7333ec 100644 --- a/engines/startrek/room.h +++ b/engines/startrek/room.h @@ -69,6 +69,9 @@ public: bool actionHasCode(const Action &action); bool handleAction(const Action &action); + // Same as above, but if any byte in the action is -1 (0xff), it matches any value. + bool handleActionWithBitmask(const Action &action); + uint16 getFirstHotspot() { return readRdfWord(0x12); } uint16 getHotspotEnd() { return readRdfWord(0x14); } @@ -94,10 +97,10 @@ private: void loadActorAnim(int actorIndex, Common::String anim, int16 x, int16 y, uint16 field66); // Cmd 0x00 void loadActorStandAnim(int actorIndex); // Cmd 0x01 void loadActorAnim2(int actorIndex, Common::String anim, int16 x, int16 y, uint16 field66);// Cmd 0x02 - int showRoomSpecificText(const char **textAddr); // Cmd 0x03 + int showRoomSpecificText(const char **textAddr); // Cmd 0x03 void giveItem(int item); // Cmd 0x04 void loadRoomIndex(int roomIndex, int spawnIndex); // Cmd 0x06 - void walkCrewman(int actorIndex, int16 destX, int16 destY, uint16 field66); // Cmd 0x08 + void walkCrewman(int actorIndex, int16 destX, int16 destY, uint16 finishedAnimActionParam);// Cmd 0x08 void playSoundEffectIndex(int soundEffect); // Cmd 0x0f void playMidiMusicTracks(int startTrack, int loopTrack); // Cmd 0x10 void playVoc(Common::String filename); // Cmd 0x15 @@ -111,8 +114,12 @@ public: void demon0Tick100(); void demon0Tick140(); void demon0TouchedWarp0(); + void demon0WalkToBottomDoor(); void demon0TouchedHotspot1(); + void demon0ReachedBottomDoor(); + void demon0WalkToTopDoor(); void demon0TouchedHotspot0(); + void demon0ReachedTopDoor(); void demon0TalkToPrelate(); void demon0LookAtPrelate(); void demon0UsePhaserOnSnow(); @@ -134,9 +141,9 @@ public: void demon0TalkToMcCoy(); void demon0TalkToSpock(); void demon0AskPrelateAboutSightings(); - void useSTricorderAnywhere(); - void useMTricorderAnywhere(); - void useMTricorderOnPrelate(); + void demon0UseSTricorderAnywhere(); + void demon0UseMTricorderAnywhere(); + void demon0UseMTricorderOnPrelate(); void demon0BadResponse(); }; diff --git a/engines/startrek/rooms/demon0.cpp b/engines/startrek/rooms/demon0.cpp index f687eaaaa3..41a5bbf802 100644 --- a/engines/startrek/rooms/demon0.cpp +++ b/engines/startrek/rooms/demon0.cpp @@ -72,6 +72,13 @@ void Room::demon0TouchedWarp0() { _vm->_awayMission.rdfStillDoDefaultAction = true; } +void Room::demon0WalkToBottomDoor() { + _vm->_awayMission.transitioningIntoRoom = 1; + _rdfData[0xcd] = 1; // FIXME + _vm->_awayMission.field25[OBJECT_KIRK] = DIR_E; + walkCrewman(OBJECT_KIRK, 243, 158, 1); +} + void Room::demon0TouchedHotspot1() { if (_rdfData[0xcd] != 1) // FIXME: not portable to mac/amiga? return; @@ -79,13 +86,32 @@ void Room::demon0TouchedHotspot1() { playSoundEffectIndex(0x05); } +void Room::demon0ReachedBottomDoor() { + if (++_rdfData[0xca] != 2) // FIXME + return; + loadRoomIndex(5, 0); +} + +void Room::demon0WalkToTopDoor() { + _vm->_awayMission.transitioningIntoRoom = 1; + _rdfData[0xcc] = 1; // FIXME + _vm->_awayMission.field25[OBJECT_KIRK] = DIR_E; + walkCrewman(OBJECT_KIRK, 157, 134, 2); +} + void Room::demon0TouchedHotspot0() { - if (_rdfData[0xcd] != 1) // FIXME: not portable to mac/amiga? + if (_rdfData[0xcc] != 1) // FIXME: not portable to mac/amiga? return; loadActorAnim2(10, "s0r0d2", 158, 130, 2); playSoundEffectIndex(0x05); } +void Room::demon0ReachedTopDoor() { + if (++_rdfData[0xcb] != 2) // FIXME + return; + loadRoomIndex(6, 3); +} + void Room::demon0TalkToPrelate() { const char *options1[] = { "Capt. Kirk", @@ -471,7 +497,7 @@ void Room::demon0AskPrelateAboutSightings() { _vm->_awayMission.field36 = 1; } -void Room::useSTricorderAnywhere() { +void Room::demon0UseSTricorderAnywhere() { loadActorAnim2(OBJECT_SPOCK, "sscans", -1, -1, 0); playSoundEffectIndex(0x04); @@ -484,7 +510,7 @@ void Room::useSTricorderAnywhere() { showRoomSpecificText(text); } -void Room::useMTricorderAnywhere() { +void Room::demon0UseMTricorderAnywhere() { loadActorAnim2(OBJECT_MCCOY, "mscans", -1, -1, 0); playSoundEffectIndex(0x04); @@ -496,7 +522,7 @@ void Room::useMTricorderAnywhere() { showRoomSpecificText(text); } -void Room::useMTricorderOnPrelate() { +void Room::demon0UseMTricorderOnPrelate() { loadActorAnim2(OBJECT_MCCOY, "mscans", -1, -1, 0); playSoundEffectIndex(0x04); diff --git a/engines/startrek/rooms/function_map.h b/engines/startrek/rooms/function_map.h index 6b5dfd0307..ae7e8eb252 100644 --- a/engines/startrek/rooms/function_map.h +++ b/engines/startrek/rooms/function_map.h @@ -37,10 +37,17 @@ RoomAction demon0ActionList[] = { { Action(ACTION_TOUCHED_WARP, 0, 0, 0), &Room::demon0TouchedWarp0 }, - // TODO: Walk events; entered room event; beamed in event - + { Action(ACTION_WALK, 0x27, 0, 0), &Room::demon0WalkToBottomDoor }, + { Action(ACTION_WALK, 9, 0, 0), &Room::demon0WalkToBottomDoor }, { Action(ACTION_TOUCHED_HOTSPOT, 1, 0, 0), &Room::demon0TouchedHotspot1 }, + { Action(ACTION_FINISHED_WALKING, 1, 0, 0), &Room::demon0ReachedBottomDoor }, + { Action(ACTION_FINISHED_ANIMATION, 1, 0, 0), &Room::demon0ReachedBottomDoor }, + + { Action(ACTION_WALK, 0x26, 0, 0), &Room::demon0WalkToTopDoor }, + { Action(ACTION_WALK, 10, 0, 0), &Room::demon0WalkToTopDoor }, { Action(ACTION_TOUCHED_HOTSPOT, 0, 0, 0), &Room::demon0TouchedHotspot0 }, + { Action(ACTION_FINISHED_WALKING, 2, 0, 0), &Room::demon0ReachedTopDoor }, + { Action(ACTION_FINISHED_ANIMATION, 2, 0, 0), &Room::demon0ReachedTopDoor }, { Action(ACTION_TALK, 8, 0, 0), &Room::demon0TalkToPrelate }, { Action(ACTION_LOOK, 8, 0, 0), &Room::demon0LookAtPrelate }, @@ -59,23 +66,23 @@ RoomAction demon0ActionList[] = { { Action(ACTION_LOOK, 0xff, 0, 0), &Room::demon0LookAnywhere }, { Action(ACTION_LOOK, 0x24, 0, 0), &Room::demon0LookAtBushes }, - { Action(ACTION_LOOK, 0, 0, 0), &Room::demon0LookAtKirk }, - { Action(ACTION_LOOK, 2, 0, 0), &Room::demon0LookAtMcCoy }, - { Action(ACTION_LOOK, 3, 0, 0), &Room::demon0LookAtRedShirt }, - { Action(ACTION_LOOK, 1, 0, 0), &Room::demon0LookAtSpock }, + { Action(ACTION_LOOK, OBJECT_KIRK, 0, 0), &Room::demon0LookAtKirk }, + { Action(ACTION_LOOK, OBJECT_MCCOY, 0, 0), &Room::demon0LookAtMcCoy }, + { Action(ACTION_LOOK, OBJECT_REDSHIRT, 0, 0), &Room::demon0LookAtRedShirt }, + { Action(ACTION_LOOK, OBJECT_SPOCK, 0, 0), &Room::demon0LookAtSpock }, { Action(ACTION_LOOK, 9, 0, 0), &Room::demon0LookAtShelter }, // Door 1 { Action(ACTION_LOOK, 10, 0, 0), &Room::demon0LookAtShelter }, // Door 2 { Action(ACTION_LOOK, 0x20, 0, 0), &Room::demon0LookAtShelter }, // Shelter itself - { Action(ACTION_TALK, 0, 0, 0), &Room::demon0TalkToKirk }, - { Action(ACTION_TALK, 3, 0, 0), &Room::demon0TalkToRedshirt }, - { Action(ACTION_TALK, 2, 0, 0), &Room::demon0TalkToMcCoy }, - { Action(ACTION_TALK, 1, 0, 0), &Room::demon0TalkToSpock }, + { Action(ACTION_TALK, OBJECT_KIRK, 0, 0), &Room::demon0TalkToKirk }, + { Action(ACTION_TALK, OBJECT_REDSHIRT, 0, 0), &Room::demon0TalkToRedshirt }, + { Action(ACTION_TALK, OBJECT_MCCOY, 0, 0), &Room::demon0TalkToMcCoy }, + { Action(ACTION_TALK, OBJECT_SPOCK, 0, 0), &Room::demon0TalkToSpock }, - { Action(ACTION_USE, OBJECT_ISTRICOR, 0xff, 0), &Room::useSTricorderAnywhere }, - { Action(ACTION_USE, OBJECT_IMTRICOR, 0xff, 0), &Room::useMTricorderAnywhere }, - { Action(ACTION_USE, OBJECT_IMTRICOR, 8, 0), &Room::useMTricorderOnPrelate }, + { Action(ACTION_USE, OBJECT_ISTRICOR, -1, 0), &Room::demon0UseSTricorderAnywhere }, + { Action(ACTION_USE, OBJECT_IMTRICOR, -1, 0), &Room::demon0UseMTricorderAnywhere }, + { Action(ACTION_USE, OBJECT_IMTRICOR, 8, 0), &Room::demon0UseMTricorderOnPrelate }, }; } diff --git a/engines/startrek/startrek.cpp b/engines/startrek/startrek.cpp index 0c1b6d8ade..b4777a9240 100644 --- a/engines/startrek/startrek.cpp +++ b/engines/startrek/startrek.cpp @@ -57,6 +57,8 @@ StarTrekEngine::StarTrekEngine(OSystem *syst, const StarTrekGameDescription *gam _sound = nullptr; _macResFork = nullptr; + memset(_actionOnWalkCompletionInUse, 0, sizeof(_actionOnWalkCompletionInUse)); + _clockTicks = 0; _musicEnabled = true; @@ -368,8 +370,8 @@ int StarTrekEngine::loadActorAnim(int actorIndex, const Common::String &animName drawActorToScreen(actor, animName, x, y, scale, true); } - actor->walkingIntoRoom = 0; - actor->field66 = 0; + actor->triggerActionWhenAnimFinished = false; + actor->finishedAnimActionParam = 0; return actorIndex; } @@ -383,7 +385,7 @@ bool StarTrekEngine::actorWalkToPosition(int actorIndex, const Common::String &a Actor *actor = &_actorList[actorIndex]; - actor->walkingIntoRoom = 0; + actor->triggerActionWhenAnimFinished = false; if (isPositionSolid(destX, destY)) return false; @@ -400,7 +402,7 @@ bool StarTrekEngine::actorWalkToPosition(int actorIndex, const Common::String &a actor->dest.x = destX; actor->dest.y = destY; actor->field92 = 0; - actor->walkingIntoRoom = 0; + actor->triggerActionWhenAnimFinished = false; actor->iwDestPosition = -1; actor->iwSrcPosition = -1; @@ -450,8 +452,8 @@ void StarTrekEngine::updateActorAnimations() { if (actor->animFrame != nextAnimFrame) { if (nextAnimFrame == actor->numAnimFrames - 1) { actor->field62++; - if (actor->walkingIntoRoom != 0) { - addAction(Action(ACTION_FINISHED_BEAMING_IN, actor->field66, 0, 0)); + if (actor->triggerActionWhenAnimFinished) { + addAction(Action(ACTION_FINISHED_ANIMATION, actor->finishedAnimActionParam, 0, 0)); } } } @@ -520,9 +522,9 @@ void StarTrekEngine::updateActorAnimations() { } else { // actor->field90 == 0 if (actor->iwSrcPosition == -1) { - if (actor->walkingIntoRoom != 0) { - actor->walkingIntoRoom = 0; - addAction(Action(ACTION_FINISHED_ENTERING_ROOM, actor->field66 & 0xff, 0, 0)); + if (actor->triggerActionWhenAnimFinished) { + actor->triggerActionWhenAnimFinished = false; + addAction(Action(ACTION_FINISHED_WALKING, actor->finishedAnimActionParam & 0xff, 0, 0)); } actor->sprite.bitmap.reset(); @@ -831,7 +833,7 @@ int StarTrekEngine::findObjectAt(int x, int y) { error("findObject: Clicked on an unknown sprite"); } - // word_4b418 = 0; + _objectHasWalkPosition = false; int actionBit = 1 << (_awayMission.activeAction - 1); int offset = _room->getFirstHotspot(); @@ -840,9 +842,9 @@ int StarTrekEngine::findObjectAt(int x, int y) { if (word & 0x8000) { if ((word & actionBit) && isPointInPolygon((int16 *)(_room->_rdfData + offset + 6), x, y)) { int actorIndex = _room->readRdfWord(offset + 6); - // word_4b418 = 1; - // word_4a792 = _room->readRdfWord(offset + 2); - // word_4a796 = _room->readRdfWord(offset + 4); // TODO + _objectHasWalkPosition = true; + _objectWalkPosition.x = _room->readRdfWord(offset + 2); + _objectWalkPosition.y = _room->readRdfWord(offset + 4); return actorIndex; } @@ -962,6 +964,112 @@ SharedPtr StarTrekEngine::loadAnimationFrame(const Common::String &filen return bitmapToReturn; } + +/** + * Called when the "get" action is first selected. Returns a selected object. + * This behaves like other menus in that it holds game execution, but no actual menu pops + * up; it just waits for the player to select something on the screen. + */ +int StarTrekEngine::selectObjectForUseAction() { + while (true) { + if (!(_awayMission.field24 & (1 << OBJECT_KIRK))) + showInventoryIcons(false); + + TrekEvent event; + + while (true) { + if (!getNextEvent(&event)) + continue; + + if (event.type == TREKEVENT_TICK) { + updateMouseBitmap(); + _gfx->drawAllSprites(); + _sound->checkLoopMusic(); + } + else if (event.type == TREKEVENT_LBUTTONDOWN) { + removeNextEvent(); + break; + } + else if (event.type == TREKEVENT_MOUSEMOVE) { + } + else if (event.type == TREKEVENT_RBUTTONDOWN) { + // Allow this to be processed by main away mission loop + break; + } + else if (event.type == TREKEVENT_KEYDOWN) { + if (event.kbd.keycode == Common::KEYCODE_ESCAPE + || event.kbd.keycode == Common::KEYCODE_w + || event.kbd.keycode == Common::KEYCODE_t + || event.kbd.keycode == Common::KEYCODE_u + || event.kbd.keycode == Common::KEYCODE_g + || event.kbd.keycode == Common::KEYCODE_l + || event.kbd.keycode == Common::KEYCODE_SPACE + || event.kbd.keycode == Common::KEYCODE_F2) { + // Allow these buttons to be processed by main away mission loop + break; + } + else if (event.kbd.keycode == Common::KEYCODE_i) { + removeNextEvent(); + break; + } + else if (event.kbd.keycode == Common::KEYCODE_RETURN || event.kbd.keycode == Common::KEYCODE_KP_ENTER || event.kbd.keycode == Common::KEYCODE_F1) { + // Simulate left-click + removeNextEvent(); + event.type = TREKEVENT_LBUTTONDOWN; + break; + } + } + + removeNextEvent(); + } + + if (event.type == TREKEVENT_KEYDOWN && event.kbd.keycode == Common::KEYCODE_i) { + hideInventoryIcons(); + int clickedObject = showInventoryMenu(50, 50, true); + if (clickedObject == -1) + continue; + return clickedObject; + } + else if (event.type == TREKEVENT_LBUTTONDOWN) { + int clickedObject = findObjectAt(_gfx->getMousePos()); + hideInventoryIcons(); + + if (clickedObject == -1) + continue; + else if (isObjectUnusable(clickedObject, ACTION_USE)) + continue; + else if (clickedObject == OBJECT_INVENTORY_ICON) { + clickedObject = showInventoryMenu(50, 50, false); + if (clickedObject == -1) + continue; + else + return clickedObject; + } + else if (clickedObject <= OBJECT_REDSHIRT) + return clickedObject; + else if (isObjectUnusable(OBJECT_KIRK, ACTION_USE)) + continue; + else if (_room->actionHasCode(Action(ACTION_USE, OBJECT_KIRK, clickedObject, 0)) + || _room->actionHasCode(Action(ACTION_GET, clickedObject, 0, 0)) + || _room->actionHasCode(Action(ACTION_WALK, clickedObject, 0, 0))) { + _awayMission.activeObject = OBJECT_KIRK; + _awayMission.passiveObject = clickedObject; + _awayMission.activeAction = ACTION_USE; + clickedObject = OBJECT_KIRK; + if (!walkActiveObjectToHotspot()) + addAction(Action(_awayMission.activeAction, _awayMission.activeObject, _awayMission.passiveObject, 0)); + return clickedObject; + } + else + continue; + } + else { + hideInventoryIcons(); + return -1; + } + } +} + Common::String StarTrekEngine::getCrewmanAnimFilename(int actorIndex, const Common::String &basename) { const char *crewmanChars = "ksmr"; assert(actorIndex >= 0 && actorIndex < 4); @@ -1013,6 +1121,72 @@ void StarTrekEngine::updateMouseBitmap() { chooseMouseBitmapForAction(action, withRedOutline); } +/** + * Checks whether to walk a crewman to a hotspot (the last one obtained from + * a "findObjectAt" call). + */ +bool StarTrekEngine::walkActiveObjectToHotspot() { + if (!_objectHasWalkPosition) + return false; + + int objectIndex; + if (_awayMission.activeAction != ACTION_USE) + objectIndex = OBJECT_KIRK; + else if (_awayMission.activeObject >= OBJECT_KIRK && _awayMission.activeObject <= OBJECT_REDSHIRT) + objectIndex = _awayMission.activeObject; + else if (_awayMission.activeObject >= ITEMS_START && _awayMission.activeObject <= ITEMS_END) { // FIXME: "<= ITEMS_END" doesn't make sense? + if (_awayMission.activeObject == OBJECT_ISTRICOR) + objectIndex = OBJECT_SPOCK; + else if (_awayMission.activeObject == OBJECT_IMTRICOR) + objectIndex = OBJECT_MCCOY; + else + objectIndex = OBJECT_KIRK; + } + else // This is the original error message... + error("Jay didn't think about pmcheck"); + + byte finishedAnimActionParam = false; + bool walk = false; + + if (_awayMission.activeAction == ACTION_WALK) + walk = true; + else { + // If this action has code defined for it in this room, buffer the action to be + // done after the object finished walking there. + Action action(_awayMission.activeAction, _awayMission.activeObject, 0, 0); + if (_awayMission.activeAction == ACTION_USE) + action.b2 = _awayMission.passiveObject; + + if (_room->actionHasCode(action)) { + for (int i = 0; i < MAX_BUFFERED_WALK_ACTIONS; i++) { + if (!_actionOnWalkCompletionInUse[i]) { + finishedAnimActionParam = i + 0xe0; + _actionOnWalkCompletionInUse[i] = true; + _actionOnWalkCompletion[i] = action; + walk = true; + break; + } + } + } + } + + if (walk) { + Actor *actor = &_actorList[objectIndex]; + Common::String anim = getCrewmanAnimFilename(objectIndex, "walk"); + actorWalkToPosition(objectIndex, anim, actor->pos.x, actor->pos.y, _objectWalkPosition.x, _objectWalkPosition.y); + if (finishedAnimActionParam != 0) { + actor->triggerActionWhenAnimFinished = true; + actor->finishedAnimActionParam = finishedAnimActionParam; + } + _objectHasWalkPosition = false; + return true; + } + else { + _objectHasWalkPosition = false; + return false; + } +} + void StarTrekEngine::showInventoryIcons(bool showItem) { const char *crewmanFilenames[] = { "ikirk", @@ -1057,6 +1231,23 @@ void StarTrekEngine::showInventoryIcons(bool showItem) { _inventoryIconSprite.setBitmap(_gfx->loadBitmap("inv00")); } +/** + * Return true if an object is unselectable with use? + */ +bool StarTrekEngine::isObjectUnusable(int object, int action) { + if (action == ACTION_LOOK) + return false; + if (object == OBJECT_REDSHIRT && _awayMission.redshirtDead) + return true; + if (object >= OBJECT_KIRK && object <= OBJECT_REDSHIRT && (_awayMission.field24 & (1 << object))) + return true; + if (object == OBJECT_IMTRICOR && (_awayMission.field24 & (1 << OBJECT_MCCOY))) + return true; + if (object == OBJECT_ISTRICOR && (_awayMission.field24 & (1 << OBJECT_SPOCK))) + return true; + return false; +} + void StarTrekEngine::hideInventoryIcons() { // Clear these sprites from the screen if (_itemIconSprite.drawMode == 2) diff --git a/engines/startrek/startrek.h b/engines/startrek/startrek.h index 217a06652b..b408b393b0 100644 --- a/engines/startrek/startrek.h +++ b/engines/startrek/startrek.h @@ -65,6 +65,8 @@ const int MAX_MENUBUTTONS = 32; const int TEXTBOX_WIDTH = 26; const int MAX_TEXTBOX_LINES = 12; +const int MAX_BUFFERED_WALK_ACTIONS = 32; + enum StarTrekGameType { GType_ST25 = 1, @@ -190,12 +192,14 @@ public: int loadActorAnimWithRoomScaling(int actorIndex, const Common::String &animName, int16 x, int16 y); uint16 getActorScaleAtPosition(int16 y); void addAction(const Action &action); + void addAction(byte b1, byte b2, byte b3, byte b4) { addAction(Action(b1, b2, b3, b4)); } bool checkItemInteractionExists(int action, int activeItem, int passiveItem, int16 arg6); void handleAwayMissionAction(); bool isPointInPolygon(int16 *data, int16 x, int16 y); void checkTouchedLoadingZone(int16 x, int16 y); bool isPositionSolid(int16 x, int16 y); + void loadRoomIndex(int roomIndex, int spawnIndex); public: SharedPtr getRoom(); @@ -231,9 +235,12 @@ public: int findObjectAt(int x, int y); int findObjectAt(Common::Point p) { return findObjectAt(p.x, p.y); } SharedPtr loadAnimationFrame(const Common::String &filename, Fixed16 scale); + + int selectObjectForUseAction(); Common::String getCrewmanAnimFilename(int actorIndex, const Common::String &basename); void updateMouseBitmap(); - bool sub_2330c() { return false; } // TODO + bool walkActiveObjectToHotspot(); + bool isObjectUnusable(int objectIndex, int action); void showInventoryIcons(bool showItem); void hideInventoryIcons(); int showInventoryMenu(int x, int y, bool restoreMouse); @@ -384,6 +391,15 @@ public: Sprite _inventoryIconSprite; Sprite _itemIconSprite; + // Certain hotspots store a position value where objects must walk to before + // interacting with them. After calling "findObjectAt", these values are updated. + bool _objectHasWalkPosition; + Common::Point _objectWalkPosition; + + // Actions to perform after a crewman finishes walking to a position. + Action _actionOnWalkCompletion[MAX_BUFFERED_WALK_ACTIONS]; + bool _actionOnWalkCompletionInUse[MAX_BUFFERED_WALK_ACTIONS]; + // _clockTicks is based on DOS interrupt 1A, AH=0; read system clock counter. // Updates 18.206 times every second. uint32 _clockTicks; -- cgit v1.2.3