/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "bladerunner/script/ai_script.h" #include "bladerunner/vector.h" #include "bladerunner/actor.h" namespace BladeRunner { enum kMaggieStates { kMaggieStateIdle = 0, kMaggieStateWalking = 1, kMaggieStateJumping = 2, kMaggieStateHappyA = 3, kMaggieStateHappyB = 4, kMaggieStateLyingDown = 5, kMaggieStateLyingIdle = 6, kMaggieStateStandingUp = 7, kMaggieStateGoingToSleep = 8, kMaggieStateSleeping = 9, kMaggieStateWakingUp = 10, kMaggieStateBombIdle = 11, kMaggieStateBombWalk = 12, kMaggieStateBombJumping = 13, kMaggieStateExploding = 14, kMaggieStateDeadExploded = 15, kMaggieStateDead = 16 }; AIScriptMaggie::AIScriptMaggie(BladeRunnerEngine *vm) : AIScriptBase(vm) { _varTimesToLoopWhenHappyB = 0; _varTimesToBarkWhenHappyA = 0; _varMaggieSoundPan = 0; _varMaggieClickResponse = 0; var_45F408 = 0; } void AIScriptMaggie::Initialize() { _animationState = kMaggieStateIdle; _animationFrame = 0; _animationStateNext = 0; _animationNext = 0; _varTimesToLoopWhenHappyB = 0; _varTimesToBarkWhenHappyA = 0; _varMaggieSoundPan = 0; _varMaggieClickResponse = 0; var_45F408 = 0; // only assigned to 0. Never checked. Unused. Actor_Set_Goal_Number(kActorMaggie, kGoalMaggieMA02Default); } bool AIScriptMaggie::Update() { if (Actor_Query_Which_Set_In(kActorMaggie) == kSetMA02_MA04 && Global_Variable_Query(kVariableChapter) == 4 ) { Actor_Put_In_Set(kActorMaggie, kSetFreeSlotG); Actor_Set_At_Waypoint(kActorMaggie, 39, 0); } switch (Actor_Query_Goal_Number(kActorMaggie)) { case kGoalMaggieKP05WillExplode: Actor_Set_Goal_Number(kActorMaggie, kGoalMaggieKP05Explode); break; case kGoalMaggieKP05WalkToMcCoy: if (Actor_Query_Inch_Distance_From_Actor(kActorMcCoy, kActorMaggie) < 60) { Actor_Set_Goal_Number(kActorMaggie, kGoalMaggieKP05Explode); } break; } if (Global_Variable_Query(kVariableChapter) == 5) { if (Actor_Query_Goal_Number(kActorMaggie) < kGoalMaggieAct5Default) { Actor_Set_Goal_Number(kActorMaggie, kGoalMaggieAct5Default); } return true; } return false; } void AIScriptMaggie::TimerExpired(int timer) { if (timer == kActorTimerAIScriptCustomTask0) { switch (Actor_Query_Goal_Number(kActorMaggie)) { case kGoalMaggieMA02Wait: AI_Countdown_Timer_Reset(kActorMaggie, kActorTimerAIScriptCustomTask0); if (Random_Query(0, 4)) { AI_Movement_Track_Flush(kActorMaggie); AI_Movement_Track_Append(kActorMaggie, randomWaypointMA02(), 0); AI_Movement_Track_Repeat(kActorMaggie); } else { if (_vm->_cutContent) { // In the Restored Content Mode: // With a Random Chance Maggie will either: // walk to another (random) waypoint and then sleep (kGoalMaggieMA02GoingToSleep) // or sleep here (again kGoalMaggieMA02GoingToSleep) // or lie down awake (and maybe sleep or get up again) if (Random_Query(0, 3) == 0) { Actor_Set_Goal_Number(kActorMaggie, kGoalMaggieMA02GoingToSleep); } else { // At the end of the lying down animation, we will check for kGoalMaggieMA02Wait goal // and act accordingly. See: AIScriptMaggie::UpdateAnimation() Actor_Change_Animation_Mode(kActorMaggie, 54); // Go to lying (awake) pose } } else { // ORIGINAL: By setting the animation mode explicitly here, and not setting the goal, // Maggie will lie awake and stay in that pose idling. She will not do anything else // until McCoy leaves the room. // NEW: At the end of the lying down animation, we will check for kGoalMaggieMA02Wait goal // and act accordingly. See: AIScriptMaggie::UpdateAnimation() Actor_Change_Animation_Mode(kActorMaggie, 54); // Go to lying (awake) pose } } break; // return true case kGoalMaggieMA02SitDownToSleep: // Untriggered in the original. // Will put Maggie to sleep, if already lying down. AI_Countdown_Timer_Reset(kActorMaggie, kActorTimerAIScriptCustomTask0); // Changing animation mode to 55 (sleeping) is effective only if state is at kMaggieStateLyingIdle. Actor_Change_Animation_Mode(kActorMaggie, 55); // Go to sleeping pose break; // return true case kGoalMaggieMA02GoingToSleep: // New case (end of lying idle - time to sleep) AI_Countdown_Timer_Reset(kActorMaggie, kActorTimerAIScriptCustomTask0); // At the end of the lying down animation, we will check the goal // and act accordingly. See: AIScriptMaggie::UpdateAnimation() Actor_Change_Animation_Mode(kActorMaggie, 54); // Go to lying (awake) pose break; case kGoalMaggieMA02SitDownToGetUp: // New case (end of lying idle - time to get up) AI_Countdown_Timer_Reset(kActorMaggie, kActorTimerAIScriptCustomTask0); // At the end of the stand up animation, we will check the goal // and act accordingly. See: AIScriptMaggie::UpdateAnimation() Actor_Change_Animation_Mode(kActorMaggie, kAnimationModeIdle); // Go to stand up pose break; case kGoalMaggieMA02Sleeping: // New case (end of sleeping session) AI_Countdown_Timer_Reset(kActorMaggie, kActorTimerAIScriptCustomTask0); // At the end of the wake-up animation, we will check the goal // and act accordingly (set new goal, restart timer) Actor_Change_Animation_Mode(kActorMaggie, 54); // Go to lying (awake) pose break; } } return; //false } void AIScriptMaggie::CompletedMovementTrack() { // Note, CompletedMovementTrack() is triggered *after* the delay at the last waypoint of the track has expired switch (Actor_Query_Goal_Number(kActorMaggie)) { case kGoalMaggieMA02WalkToEntrance: Actor_Set_Goal_Number(kActorMaggie, kGoalMaggieMA02WalkToMcCoy); break; // return true case kGoalMaggieMA02Wait: Actor_Face_Actor(kActorMaggie, kActorMcCoy, true); AI_Countdown_Timer_Reset(kActorMaggie, kActorTimerAIScriptCustomTask0); AI_Countdown_Timer_Start(kActorMaggie, kActorTimerAIScriptCustomTask0, Random_Query(1, 5)); break; // return true case kGoalMaggieMA02GoingToSleep: // (In the original engine) when Maggie is moving to a random waypoint // --due to having her goal changed to kGoalMaggieMA02GoingToSleep-- // the delay when reaching that waypoint is too long (486 seconds). // So Maggie would stand at the target waypoint, waiting for the delay to pass // without turning to face McCoy or sitting down. // However, this goal is UNTRIGGERED in the original engine, so the bug was not visible. // We changed the behavior to have no delay when reaching this waypoint, // and start a timer with a random delay from 5 seconds to 486 seconds for Maggie to by lying awake. Actor_Face_Actor(kActorMaggie, kActorMcCoy, true); Actor_Change_Animation_Mode(kActorMaggie, 54); // Go to lying (awake) pose #if !BLADERUNNER_ORIGINAL_BUGS AI_Countdown_Timer_Reset(kActorMaggie, kActorTimerAIScriptCustomTask0); AI_Countdown_Timer_Start(kActorMaggie, kActorTimerAIScriptCustomTask0, Random_Query(5, 486)); #endif // !BLADERUNNER_ORIGINAL_BUGS break; // return true case kGoalMaggieKP05WalkToMcCoy: Actor_Set_Goal_Number(kActorMaggie, kGoalMaggieKP05WillExplode); break; // return true case kGoalMaggieMA02Default: //fall through default: Actor_Set_Goal_Number(kActorMaggie, kGoalMaggieMA02Wait); break; // return true } } void AIScriptMaggie::ReceivedClue(int clueId, int fromActorId) { } void AIScriptMaggie::ClickedByPlayer() { if (!Game_Flag_Query(kFlagMcCoyIsHelpingReplicants) && Global_Variable_Query(kVariableChapter) == 5 ) { if (Actor_Query_Goal_Number(kActorMaggie) == kGoalMaggieKP05WalkToMcCoy) { Actor_Set_Targetable(kActorMaggie, true); AI_Movement_Track_Flush(kActorMaggie); Actor_Face_Actor(kActorMcCoy, kActorMaggie, true); Actor_Says(kActorMcCoy, 2400, kAnimationModeFeeding); } return; // true } if (_animationState == kMaggieStateDead) { return; // false } #if !BLADERUNNER_ORIGINAL_BUGS if (Actor_Query_Goal_Number(kActorMaggie) == kGoalMaggieMA02WalkToMcCoy || Actor_Query_Goal_Number(kActorMaggie) == kGoalMaggieMA02GetFed) { // Don't do anything if Maggie's goal is already kGoalMaggieMA02WalkToMcCoy or kGoalMaggieMA02GetFed // This is for the small time-gap when Maggie has to wake up and get up, // during which the player/McCoy still has control return; // false; } #endif // !BLADERUNNER_ORIGINAL_BUGS Actor_Face_Actor(kActorMcCoy, kActorMaggie, true); float mccoy_x, mccoy_y, mccoy_z; Actor_Query_XYZ(kActorMcCoy, &mccoy_x, &mccoy_y, &mccoy_z); if (distanceToActor(kActorMaggie, mccoy_x, mccoy_y, mccoy_z) > 60.0f) { if (_vm->_cutContent && Random_Query(0, 1)) { Actor_Says(kActorMcCoy, 2395, 18); } else { Actor_Says(kActorMcCoy, 2430, 18); } Actor_Set_Goal_Number(kActorMaggie, kGoalMaggieMA02WalkToMcCoy); return; // true } // 1: McCoy told Maggie to sit down, 2: McCoy asks Maggie if hungry, 3: McCoy sits down in front of Maggie _varMaggieClickResponse = 0; int randMcCoyCueToMaggie = Random_Query(0, 4); if (_vm->_cutContent) { // enhance somewhat the probability of getting a cut cue and interaction if (Actor_Query_Goal_Number(kActorMaggie) == kGoalMaggieMA02SitDownToSleep || Actor_Query_Goal_Number(kActorMaggie) == kGoalMaggieMA02SitDownToGetUp) { int randFavorOfCutCue = Random_Query(1, 10); if (randFavorOfCutCue < 4) { randMcCoyCueToMaggie = 2; } else if (randFavorOfCutCue > 6) { randMcCoyCueToMaggie = 4; } // otherwise keep original randMcCoyCueToMaggie (0 to 4) } else if (_animationState == kMaggieStateIdle) { int randFavorOfCutCue = Random_Query(1, 5); if (randFavorOfCutCue < 2) { randMcCoyCueToMaggie = 3; } // otherwise keep original randMcCoyCueToMaggie (0 to 4) } } switch (randMcCoyCueToMaggie) { case 0: // Good doggy. Actor_Says(kActorMcCoy, 2435, 13); break; case 1: // Who's the best dog...? Actor_Says(kActorMcCoy, 2440, 18); break; case 2: if (_vm->_cutContent) { if (Actor_Query_Goal_Number(kActorMaggie) == kGoalMaggieMA02Sleeping || Actor_Query_Goal_Number(kActorMaggie) == kGoalMaggieMA02SitDownToSleep || Actor_Query_Goal_Number(kActorMaggie) == kGoalMaggieMA02SitDownToGetUp) { // Hey, Maggie (if sitting or sleeping) Actor_Says(kActorMcCoy, 2395, 18); } } break; case 3: if (_vm->_cutContent) { // Down Maggie, get down! That's my girl. (if standing still) if (_animationState == kMaggieStateIdle) { Actor_Start_Speech_Sample(kActorMcCoy, 2415); _varMaggieClickResponse = 1; } } break; case 4: if (_vm->_cutContent) { // Are you hungry Mags? (if lying awake) if (Actor_Query_Goal_Number(kActorMaggie) == kGoalMaggieMA02SitDownToSleep || Actor_Query_Goal_Number(kActorMaggie) == kGoalMaggieMA02SitDownToGetUp) { // Set a var to trigger appropriate response Actor_Says(kActorMcCoy, 2425, 18); _varMaggieClickResponse = 2; } } break; default: break; } switch (Actor_Query_Goal_Number(kActorMaggie)) { case kGoalMaggieMA02Wait: Actor_Face_Actor(kActorMaggie, kActorMcCoy, true); if (_animationState == kMaggieStateIdle && _varMaggieClickResponse == 1) { // (Cut Content; _varMaggieClickResponse only gets values > 0 in Cut Content) AI_Countdown_Timer_Reset(kActorMaggie, kActorTimerAIScriptCustomTask0); Delay(500); Actor_Change_Animation_Mode(kActorMaggie, 54); // Go to lying (awake) pose } else { if (Random_Query(0, 1)) { Actor_Change_Animation_Mode(kActorMaggie, 57); // HappyB - Not Barking - Tail Wagging if (_vm->_cutContent && Random_Query(1, 5) < 4) { Player_Loses_Control(); Actor_Change_Animation_Mode(kActorMcCoy, 85); // McCoy sits down in front of Maggie _varMaggieClickResponse = 3; } } else { Actor_Change_Animation_Mode(kActorMaggie, 56); // HappyA - Barking } AI_Countdown_Timer_Reset(kActorMaggie, kActorTimerAIScriptCustomTask0); if (_varMaggieClickResponse == 3) { AI_Countdown_Timer_Start(kActorMaggie, kActorTimerAIScriptCustomTask0, Random_Query(6, 9)); } else { AI_Countdown_Timer_Start(kActorMaggie, kActorTimerAIScriptCustomTask0, Random_Query(3, 9)); } } break; // return true case kGoalMaggieMA02SitDownToSleep: // fall through case kGoalMaggieMA02SitDownToGetUp: // Maggie just stands up Actor_Change_Animation_Mode(kActorMaggie, kAnimationModeIdle); if (_varMaggieClickResponse == 2) { // Maggie makes "Need" sounds // (Cut Content; _varMaggieClickResponse only gets values > 0 in Cut Content) _varMaggieSoundPan = _vm->_actors[kActorMaggie]->soundPan(75); Sound_Play(Random_Query(kSfxDOGNEED1, kSfxDOGNEED2), 50, _varMaggieSoundPan, _varMaggieSoundPan, 50); _varMaggieClickResponse = 0; } break; // return true case kGoalMaggieMA02Sleeping: // Go to lying awake pose from sleeping (onclick) Actor_Change_Animation_Mode(kActorMaggie, 54); break; // return true default: // This effects basically in restarting the custom timer for Maggie Actor_Set_Goal_Number(kActorMaggie, kGoalMaggieMA02Wait); break; // return true } } void AIScriptMaggie::EnteredSet(int setId) { } void AIScriptMaggie::OtherAgentEnteredThisSet(int otherActorId) { // this is executed *after* the scene's script (eg. SceneScriptMA02::PlayerWalkedIn()) if (_vm->_cutContent && otherActorId == kActorMcCoy && Actor_Query_Which_Set_In(kActorMaggie) == kSetMA02_MA04 && Global_Variable_Query(kVariableChapter) < 4 && Actor_Query_Goal_Number(kActorMaggie) == kGoalMaggieMA02Default) { Actor_Face_Actor(kActorMaggie, kActorMcCoy, true); Actor_Set_Goal_Number(kActorMaggie, kGoalMaggieMA02Wait); } } void AIScriptMaggie::OtherAgentExitedThisSet(int otherActorId) { if (otherActorId == kActorMcCoy && Actor_Query_Which_Set_In(kActorMaggie) == kSetMA02_MA04 && Global_Variable_Query(kVariableChapter) < 4 ) { AI_Movement_Track_Flush(kActorMaggie); Actor_Set_Goal_Number(kActorMaggie, kGoalMaggieMA02Default); } } void AIScriptMaggie::OtherAgentEnteredCombatMode(int otherActorId, int combatMode) { } void AIScriptMaggie::ShotAtAndMissed() { } bool AIScriptMaggie::ShotAtAndHit() { AI_Movement_Track_Flush(kActorMaggie); Actor_Set_Goal_Number(kActorMaggie, kGoalMaggieKP05WillExplode); Actor_Set_Targetable(kActorMaggie, false); return false; } void AIScriptMaggie::Retired(int byActorId) { } int AIScriptMaggie::GetFriendlinessModifierIfGetsClue(int otherActorId, int clueId) { return 0; } bool AIScriptMaggie::GoalChanged(int currentGoalNumber, int newGoalNumber) { if (currentGoalNumber == kGoalMaggieDead) { return true; } switch (newGoalNumber) { case kGoalMaggieMA02Default: Actor_Put_In_Set(kActorMaggie, kSetMA02_MA04); Actor_Set_At_Waypoint(kActorMaggie, 265, 780); return true; case kGoalMaggieMA02WalkToEntrance: Actor_Put_In_Set(kActorMaggie, kSetMA02_MA04); Actor_Set_At_Waypoint(kActorMaggie, randomWaypointMA02(), 512); AI_Movement_Track_Flush(kActorMaggie); AI_Movement_Track_Append(kActorMaggie, 264, 0); AI_Movement_Track_Repeat(kActorMaggie); return true; case kGoalMaggieMA02GetFed: #if BLADERUNNER_ORIGINAL_BUGS Player_Loses_Control(); AI_Movement_Track_Flush(kActorMaggie); Loop_Actor_Walk_To_Actor(kActorMaggie, kActorMcCoy, 48, false, false); Actor_Face_Actor(kActorMcCoy, kActorMaggie, true); Actor_Face_Actor(kActorMaggie, kActorMcCoy, false); Actor_Says(kActorMcCoy, 2400, kAnimationModeFeeding); Actor_Set_Goal_Number(kActorMaggie, kGoalMaggieMA02Wait); Player_Gains_Control(); #else if (_animationState != kMaggieStateSleeping && _animationState != kMaggieStateWakingUp && _animationState != kMaggieStateStandingUp && _animationState != kMaggieStateLyingDown && _animationState != kMaggieStateLyingIdle) { // For specific animationStates we have to ignore this goal change, // and go through animation chain until Maggie is standing up // at which point (end of animation of standing up), the goal will change again to kGoalMaggieMA02GetFed // to trigger this case Player_Loses_Control(); AI_Countdown_Timer_Reset(kActorMaggie, kActorTimerAIScriptCustomTask0); AI_Movement_Track_Flush(kActorMaggie); // Allows McCoy to perform both animated turns (first towards the BAR-MAIN and then towards Maggie) // when Maggie is already too close // original bug: When Maggie is close McCoy would alternate between // - turning to Maggie and throw food at her // - only performing the turn toward the BAR-MAIN and "throw" food to wrong direction if (Actor_Query_Inch_Distance_From_Actor(kActorMaggie, kActorMcCoy) <= 85) { Delay(500); } Loop_Actor_Walk_To_Actor(kActorMaggie, kActorMcCoy, 48, false, false); Actor_Face_Actor(kActorMcCoy, kActorMaggie, true); Actor_Face_Actor(kActorMaggie, kActorMcCoy, true); Actor_Says(kActorMcCoy, 2400, kAnimationModeFeeding); Actor_Set_Goal_Number(kActorMaggie, kGoalMaggieMA02Wait); Player_Gains_Control(); } #endif // BLADERUNNER_ORIGINAL_BUGS return true; case kGoalMaggieMA02WalkToMcCoy: AI_Countdown_Timer_Reset(kActorMaggie, kActorTimerAIScriptCustomTask0); #if BLADERUNNER_ORIGINAL_BUGS AI_Movement_Track_Flush(kActorMaggie); Loop_Actor_Walk_To_Actor(kActorMaggie, kActorMcCoy, 30, false, false); Actor_Face_Actor(kActorMaggie, kActorMcCoy, true); Actor_Change_Animation_Mode(kActorMaggie, 56); Actor_Set_Goal_Number(kActorMaggie, kGoalMaggieMA02Wait); #else if (_animationState != kMaggieStateSleeping && _animationState != kMaggieStateWakingUp && _animationState != kMaggieStateStandingUp && _animationState != kMaggieStateLyingDown && _animationState != kMaggieStateLyingIdle) { // For specific animationStates we have to ignore this goal change, // and go through animation chain until Maggie is standing up // at which point (end of animation of standing up), the goal will change again to kGoalMaggieMA02WalkToMcCoy // to trigger this case AI_Movement_Track_Flush(kActorMaggie); // When an actor other than McCoy does a loopWalk, their walk is non interruptible // (we set it to false explicitly here, but this is also taken care of inside the Actor::loopWalk()) // This means McCoy loses control until the other Actor reaches the target waypoint // Also Loop_Actor_Walk_To_Actor() is blocking. The commands following it will be executed // after Maggie completes her loopWalk ie. reaches her target (McCoy). Loop_Actor_Walk_To_Actor(kActorMaggie, kActorMcCoy, 30, false, false); Actor_Face_Actor(kActorMaggie, kActorMcCoy, true); Actor_Face_Actor(kActorMcCoy, kActorMaggie, true); Actor_Change_Animation_Mode(kActorMaggie, 56); Actor_Set_Goal_Number(kActorMaggie, kGoalMaggieMA02Wait); } #endif return true; case kGoalMaggieMA02Wait: AI_Countdown_Timer_Reset(kActorMaggie, kActorTimerAIScriptCustomTask0); AI_Countdown_Timer_Start(kActorMaggie, kActorTimerAIScriptCustomTask0, Random_Query(3, 9)); return true; case kGoalMaggieMA02GoingToSleep: // Maggie will (randomly) either: // - sit and go to sleep at her current position, // - or go to a random waypoint and then sit and go to sleep after a while. // // Note: Original checks for <= 0, but that is basically same as checking for == 0 here. if (Random_Query(0, 2) == 0) { Actor_Face_Actor(kActorMaggie, kActorMcCoy, true); Actor_Change_Animation_Mode(kActorMaggie, 54); // Go to lying (awake) pose // At the end of the animation, if the mode change is not ignored, // the goal will be set to kGoalMaggieMA02SitDownToSleep // (specifically for the case of kGoalMaggieMA02GoingToSleep being the current goal) // and the state to kMaggieStateLyingIdle } else { AI_Movement_Track_Flush(kActorMaggie); if (Actor_Query_Which_Set_In(kActorMaggie) == kSetMA02_MA04) { #if BLADERUNNER_ORIGINAL_BUGS // Maggie stays for 486 seconds at the target waypoint (8.1 minutes) // This is a bug since CompletedMovementTrack() won't trigger until after this huge delay. AI_Movement_Track_Append(kActorMaggie, randomWaypointMA02(), 486); #else AI_Movement_Track_Append(kActorMaggie, randomWaypointMA02(), 0); #endif // BLADERUNNER_ORIGINAL_BUGS } AI_Movement_Track_Repeat(kActorMaggie); } return true; case kGoalMaggieMA02SitDownToSleep: // fall through case kGoalMaggieMA02SitDownToGetUp: Actor_Change_Animation_Mode(kActorMaggie, 54); // Go to lying (awake) pose // By setting _animationState and frame explicitly here, // the lying-down-idling pose is enforced, and the transition animation (from standing to sitting) will not play _animationState = kMaggieStateLyingIdle; _animationFrame = 0; AI_Countdown_Timer_Reset(kActorMaggie, kActorTimerAIScriptCustomTask0); AI_Countdown_Timer_Start(kActorMaggie, kActorTimerAIScriptCustomTask0, Random_Query(2, 9)); return true; case kGoalMaggieMA02Sleeping: // When setting Maggie's *goal* to "sleeping", we expect it to be enforced // However, Actor_Change_Animation_Mode is not enforceable and could be ignored. // The goal change here is *NOT* done in order to play the animation. // It is to set the animation State, and by explicitly setting it, it overrides playing the animation transition. // Actor_Change_Animation_Mode() is called to store the _animationMode on Maggie's actor object (see: Actor::changeAnimationMode()) Actor_Change_Animation_Mode(kActorMaggie, 55); // Go to sleeping pose _animationState = kMaggieStateSleeping; #if BLADERUNNER_ORIGINAL_BUGS _animationFrame = 0; #else // We actually need the final frame here to avoid Maggie glitching here _animationFrame = Slice_Animation_Query_Number_Of_Frames(kModelAnimationMaggieToggleSleepingWakeUp) - 1; AI_Countdown_Timer_Reset(kActorMaggie, kActorTimerAIScriptCustomTask0); // Sleep for 20 - 390 seconds, unless McCoy wakes Maggie up. AI_Countdown_Timer_Start(kActorMaggie, kActorTimerAIScriptCustomTask0, Random_Query(20, 390)); #endif // BLADERUNNER_ORIGINAL_BUGS return true; case kGoalMaggieAct5Default: Actor_Set_Goal_Number(kActorMaggie, kGoalMaggieAct5Start); break; case kGoalMaggieKP05Wait: AI_Movement_Track_Flush(kActorMaggie); Game_Flag_Set(kFlagMaggieHasBomb); Actor_Put_In_Set(kActorMaggie, kSetKP05_KP06); Actor_Set_At_XYZ(kActorMaggie, -672.0, 0.0, -428.0, 653); Actor_Change_Animation_Mode(kActorMaggie, kAnimationModeIdle); break; case kGoalMaggieKP05McCoyEntered: Scene_Exits_Disable(); Loop_Actor_Walk_To_XYZ(kActorMaggie, -734.0, 0.0, -432.0, 0, false, false, false); Actor_Face_Actor(kActorMaggie, kActorMcCoy, true); Actor_Change_Animation_Mode(kActorMaggie, 56); Actor_Face_Actor(kActorMcCoy, kActorMaggie, true); Actor_Says(kActorMcCoy, 2225, kAnimationModeTalk); Actor_Set_Goal_Number(kActorMaggie, kGoalMaggieKP05WalkToMcCoy); break; case kGoalMaggieKP05WalkToMcCoy: if (Actor_Query_Is_In_Current_Set(kActorSteele)) { Actor_Says(kActorSteele, 3270, 59); } AI_Movement_Track_Flush(kActorMaggie); AI_Movement_Track_Append(kActorMaggie, 540, 0); AI_Movement_Track_Repeat(kActorMaggie); break; case kGoalMaggieKP05Explode: AI_Movement_Track_Flush(kActorMaggie); Actor_Face_Actor(kActorMcCoy, kActorMaggie, true); #if BLADERUNNER_ORIGINAL_BUGS Sound_Play(kSfxDOGEXPL1, 50, 0, 0, 100); #else _varMaggieSoundPan = _vm->_actors[kActorMaggie]->soundPan(75); Sound_Play(kSfxDOGEXPL1, 50, _varMaggieSoundPan, _varMaggieSoundPan, 100); #endif // BLADERUNNER_ORIGINAL_BUGS Actor_Set_Goal_Number(kActorMaggie, kGoalMaggieDead); Actor_Change_Animation_Mode(kActorMaggie, 51); if (Actor_Query_Inch_Distance_From_Actor(kActorMcCoy, kActorMaggie) < 144) { Player_Loses_Control(); Actor_Change_Animation_Mode(kActorMcCoy, kAnimationModeDie); Actor_Retired_Here(kActorMcCoy, 6, 6, true, -1); } else { Delay(3000); Scene_Exits_Disable(); Actor_Says(kActorMcCoy, 2235, 12); if (Actor_Query_Is_In_Current_Set(kActorSteele)) { Actor_Says(kActorSteele, 1530, 58); Actor_Set_Goal_Number(kActorSteele, kGoalSteeleKP05Leave); } Delay(2000); Actor_Says(kActorMcCoy, 2390, 13); if (Actor_Query_Goal_Number(kActorSadik) == 411) { Actor_Set_Goal_Number(kActorSadik, 412); } else { // there is no way how Maggie can explode and Sadik's goal is not 411 Actor_Set_Goal_Number(kActorClovis, kGoalClovisKP06TalkToMcCoy); } } break; } return false; } bool AIScriptMaggie::UpdateAnimation(int *animation, int *frame) { switch (_animationState) { case kMaggieStateDead: *animation = kModelAnimationMaggieLyingDead; _animationFrame = 0; break; case kMaggieStateDeadExploded: *animation = kModelAnimationMaggieExploding; _animationFrame = Slice_Animation_Query_Number_Of_Frames(kModelAnimationMaggieExploding) - 1; break; case kMaggieStateExploding: *animation = kModelAnimationMaggieExploding; ++_animationFrame; if (_animationFrame >= Slice_Animation_Query_Number_Of_Frames(kModelAnimationMaggieExploding) - 1) { _animationState = kMaggieStateDeadExploded; _animationFrame = Slice_Animation_Query_Number_Of_Frames(*animation) - 1; Actor_Put_In_Set(kActorMaggie, kSetFreeSlotI); Actor_Set_At_Waypoint(kActorMaggie, 41, 0); } break; case kMaggieStateBombJumping: *animation = kModelAnimationMaggieStandingOnTwoFeetTrapped; ++_animationFrame; if (_animationFrame >= Slice_Animation_Query_Number_Of_Frames(kModelAnimationMaggieStandingOnTwoFeetTrapped)) { _animationState = kMaggieStateBombIdle; _animationFrame = 0; *animation = kModelAnimationMaggieStandingIdleTrapped; Actor_Set_Goal_Number(kActorMaggie, kGoalMaggieKP05WillExplode); } break; case kMaggieStateBombWalk: *animation = kModelAnimationMaggieWalkingTrapped; ++_animationFrame; if (_animationFrame >= Slice_Animation_Query_Number_Of_Frames(kModelAnimationMaggieWalkingTrapped)) { _animationFrame = 0; } break; case kMaggieStateBombIdle: *animation = kModelAnimationMaggieStandingIdleTrapped; ++_animationFrame; if (_animationFrame >= Slice_Animation_Query_Number_Of_Frames(kModelAnimationMaggieStandingIdleTrapped)) { _animationFrame = 0; } break; case kMaggieStateWakingUp: *animation = kModelAnimationMaggieToggleSleepingWakeUp; --_animationFrame; if (_animationFrame > 0) { break; } // At the end of the wake-up animation _animationState = kMaggieStateLyingIdle; _animationFrame = 0; *animation = kModelAnimationMaggieLyingIdleTailWagging; switch (Actor_Query_Goal_Number(kActorMaggie)) { case kGoalMaggieMA02GetFed: _animationState = kMaggieStateStandingUp; _animationFrame = 0; *animation = kModelAnimationMaggieLyingStandingUp; break; case kGoalMaggieMA02WalkToMcCoy: #if BLADERUNNER_ORIGINAL_BUGS Actor_Set_Goal_Number(kActorMaggie, kGoalMaggieMA02SitDownToSleep); Actor_Set_Goal_Number(kActorMaggie, kGoalMaggieMA02WalkToMcCoy); #else // Stand up, similar to GetFed case above _animationState = kMaggieStateLyingIdle; _animationFrame = 0; *animation = kModelAnimationMaggieLyingStandingUp; #endif break; case kGoalMaggieMA02Sleeping: // New behavior: // Decide randomly whether Maggie will: // - Stand up or // - Go back to sleep (which is the default case too) // Both animations will happen after a small delay period, // which is set via timer, restarted in the GoalChanged() code if (Random_Query(0, 1)) { // sleep after a while Actor_Set_Goal_Number(kActorMaggie, kGoalMaggieMA02SitDownToSleep); } else { // get up after a while Actor_Set_Goal_Number(kActorMaggie, kGoalMaggieMA02SitDownToGetUp); } break; default: Actor_Set_Goal_Number(kActorMaggie, kGoalMaggieMA02SitDownToSleep); break; } break; case kMaggieStateSleeping: *animation = kModelAnimationMaggieToggleSleepingWakeUp; _animationFrame = Slice_Animation_Query_Number_Of_Frames(kModelAnimationMaggieToggleSleepingWakeUp) - 1; #if !BLADERUNNER_ORIGINAL_BUGS switch (Actor_Query_Goal_Number(kActorMaggie)) { case kGoalMaggieMA02GetFed: // fall through case kGoalMaggieMA02WalkToMcCoy: // _animationFrame and model do not change here, just the state _animationState = kMaggieStateWakingUp; break; default: break; } #endif // !BLADERUNNER_ORIGINAL_BUGS break; case kMaggieStateGoingToSleep: *animation = kModelAnimationMaggieToggleSleepingWakeUp; ++_animationFrame; if (_animationFrame >= Slice_Animation_Query_Number_Of_Frames(kModelAnimationMaggieToggleSleepingWakeUp) - 1) { _animationState = kMaggieStateSleeping; Actor_Set_Goal_Number(kActorMaggie, kGoalMaggieMA02Sleeping); } break; case kMaggieStateStandingUp: *animation = kModelAnimationMaggieLyingStandingUp; ++_animationFrame; if (_animationFrame >= Slice_Animation_Query_Number_Of_Frames(kModelAnimationMaggieLyingStandingUp)) { *animation = kModelAnimationMaggieStandingIdle; _animationState = kMaggieStateIdle; _animationFrame = 0; switch (Actor_Query_Goal_Number(kActorMaggie)) { case kGoalMaggieMA02SitDownToGetUp: // new // fall through case kGoalMaggieMA02SitDownToSleep: Actor_Set_Goal_Number(kActorMaggie, kGoalMaggieMA02Wait); break; case kGoalMaggieMA02WalkToMcCoy: // kGoalMaggieMA02Intermediate12 is a dummy goal, used only // to trigger a GoalChanged case, when we set back to kGoalMaggieMA02WalkToMcCoy below Actor_Set_Goal_Number(kActorMaggie, kGoalMaggieMA02Intermediate12); Actor_Set_Goal_Number(kActorMaggie, kGoalMaggieMA02WalkToMcCoy); break; #if !BLADERUNNER_ORIGINAL_BUGS case kGoalMaggieMA02GetFed: // kGoalMaggieMA02Intermediate12 is a dummy goal, used only // to trigger a GoalChanged case, when we set back to kGoalMaggieMA02GetFed below Actor_Set_Goal_Number(kActorMaggie, kGoalMaggieMA02Intermediate12); Actor_Set_Goal_Number(kActorMaggie, kGoalMaggieMA02GetFed); break; #endif // !BLADERUNNER_ORIGINAL_BUGS default: break; } } break; case kMaggieStateLyingIdle: *animation = kModelAnimationMaggieLyingIdleTailWagging; ++_animationFrame; if (_animationFrame >= Slice_Animation_Query_Number_Of_Frames(kModelAnimationMaggieLyingIdleTailWagging)) { _animationFrame = 0; #if !BLADERUNNER_ORIGINAL_BUGS switch (Actor_Query_Goal_Number(kActorMaggie)) { case kGoalMaggieMA02GetFed: // fall through case kGoalMaggieMA02WalkToMcCoy: _animationState = kMaggieStateStandingUp; *animation = kModelAnimationMaggieLyingStandingUp; break; default: break; } #endif // !BLADERUNNER_ORIGINAL_BUGS } break; case kMaggieStateLyingDown: *animation = kModelAnimationMaggieLyingDown; ++_animationFrame; if (_animationFrame >= Slice_Animation_Query_Number_Of_Frames(kModelAnimationMaggieLyingDown)) { _animationState = kMaggieStateLyingIdle; _animationFrame = 0; *animation = kModelAnimationMaggieLyingIdleTailWagging; switch (Actor_Query_Goal_Number(kActorMaggie)) { case kGoalMaggieMA02GoingToSleep: // sleep after a while Actor_Set_Goal_Number(kActorMaggie, kGoalMaggieMA02SitDownToSleep); break; #if !BLADERUNNER_ORIGINAL_BUGS case kGoalMaggieMA02Wait: // This case was missing from the original engine // but is required in order to set Maggie's goal properly, // so that when she get's up she will go through the proper standing up animation. // This could be guarded with a _cutContent clause check, // since this fix restores untriggered cases, // but since it's fixing a missing animation glitch, // it will be part of vanilla mode, as well. if (Random_Query(0, 1) && _varMaggieClickResponse != 1) { // sleep after a while Actor_Set_Goal_Number(kActorMaggie, kGoalMaggieMA02SitDownToSleep); } else { // get up after a while Actor_Set_Goal_Number(kActorMaggie, kGoalMaggieMA02SitDownToGetUp); if (_varMaggieClickResponse == 1) { _varMaggieClickResponse = 0; } } break; case kGoalMaggieMA02GetFed: // fall through case kGoalMaggieMA02WalkToMcCoy: _animationState = kMaggieStateStandingUp; *animation = kModelAnimationMaggieLyingStandingUp; break; #endif // !BLADERUNNER_ORIGINAL_BUGS default: break; } } break; case kMaggieStateHappyB: // Not actually barking in this case, but tail wagging (with breathing sounds) *animation = kModelAnimationMaggieBarking; ++_animationFrame; if (_animationFrame >= Slice_Animation_Query_Number_Of_Frames(kModelAnimationMaggieBarking)) { _animationFrame = 0; --_varTimesToLoopWhenHappyB; if (_varTimesToLoopWhenHappyB <= 0) { Actor_Change_Animation_Mode(kActorMaggie, kAnimationModeIdle); *animation = kModelAnimationMaggieStandingIdle; if (_varMaggieClickResponse == 3) { // (Cut Content; _varMaggieClickResponse only gets values > 0 in Cut Content) Actor_Start_Speech_Sample(kActorMcCoy, 2705); Actor_Change_Animation_Mode(kActorMcCoy, 29); // Get up Player_Gains_Control(); _varMaggieClickResponse = 0; } } } break; case kMaggieStateHappyA: // Barking in this case *animation = kModelAnimationMaggieBarkingOrHeadUp; if (_animationFrame == 1) { // one of kSfxDOGBARK1, kSfxDOGBARK3 #if BLADERUNNER_ORIGINAL_BUGS Sound_Play(Random_Query(kSfxDOGBARK1, kSfxDOGBARK3), 50, 0, 0, 50); #else _varMaggieSoundPan = _vm->_actors[kActorMaggie]->soundPan(75); Sound_Play(Random_Query(kSfxDOGBARK1, kSfxDOGBARK3), 50, _varMaggieSoundPan, _varMaggieSoundPan, 50); #endif // BLADERUNNER_ORIGINAL_BUGS } ++_animationFrame; if (_animationFrame >= Slice_Animation_Query_Number_Of_Frames(*animation)) { --_varTimesToBarkWhenHappyA; if (_varTimesToBarkWhenHappyA <= 0) { Actor_Change_Animation_Mode(kActorMaggie, kAnimationModeIdle); *animation = kModelAnimationMaggieStandingIdle; _animationState = kMaggieStateIdle; } _animationFrame = 0; } break; case kMaggieStateJumping: *animation = kModelAnimationMaggieStandingOnTwoFeet; ++_animationFrame; if (_animationFrame >= Slice_Animation_Query_Number_Of_Frames(kModelAnimationMaggieStandingOnTwoFeet)) { Actor_Change_Animation_Mode(kActorMaggie, kAnimationModeIdle); *animation = kModelAnimationMaggieStandingIdle; _animationState = kMaggieStateIdle; _animationFrame = 0; } break; case kMaggieStateWalking: *animation = kModelAnimationMaggieWalking; ++_animationFrame; if (_animationFrame >= Slice_Animation_Query_Number_Of_Frames(kModelAnimationMaggieWalking)) { _animationFrame = 0; } break; case kMaggieStateIdle: *animation = kModelAnimationMaggieStandingIdle; ++_animationFrame; if (_animationFrame >= Slice_Animation_Query_Number_Of_Frames(kModelAnimationMaggieStandingIdle)) { _animationFrame = 0; } break; default: debugC(6, kDebugAnimation, "AIScriptMaggie::UpdateAnimation() - Current _animationState (%d) is not supported", _animationState); break; } *frame = _animationFrame; return true; } bool AIScriptMaggie::ChangeAnimationMode(int mode) { switch (mode) { case kAnimationModeIdle: if (Game_Flag_Query(kFlagMaggieHasBomb)) { _animationState = kMaggieStateBombIdle; _animationFrame = 0; } else { switch (_animationState) { case kMaggieStateGoingToSleep: // fall through case kMaggieStateSleeping: _animationState = kMaggieStateWakingUp; break; case kMaggieStateLyingIdle: _animationState = kMaggieStateStandingUp; _animationFrame = 0; break; case kMaggieStateLyingDown: _animationState = kMaggieStateStandingUp; _animationFrame = 0; break; case kMaggieStateJumping: // fall through case kMaggieStateStandingUp: // fall through case kMaggieStateWakingUp: break; default: _animationState = kMaggieStateIdle; _animationFrame = 0; break; } } break; case kAnimationModeWalk: if (Game_Flag_Query(kFlagMaggieHasBomb)) { _animationState = kMaggieStateBombWalk; _animationFrame = 0; } else { _animationState = kMaggieStateWalking; _animationFrame = 0; } break; case 51: _animationState = kMaggieStateExploding; _animationFrame = 0; #if BLADERUNNER_ORIGINAL_BUGS Sound_Play(kSfxDOGHURT1, 50, 0, 0, 50); #else _varMaggieSoundPan = _vm->_actors[kActorMaggie]->soundPan(75); Sound_Play(kSfxDOGHURT1, 50, _varMaggieSoundPan, _varMaggieSoundPan, 50); #endif // BLADERUNNER_ORIGINAL_BUGS break; case kAnimationModeFeeding: if (Game_Flag_Query(kFlagMaggieHasBomb)) { _animationState = kMaggieStateBombJumping; _animationFrame = 0; } else { _animationState = kMaggieStateJumping; _animationFrame = 0; } break; case 54: if (_animationState <= kMaggieStateSleeping) { if (_animationState > kMaggieStateIdle) { if (_animationState == kMaggieStateSleeping) { _animationState = kMaggieStateWakingUp; #if BLADERUNNER_ORIGINAL_BUGS // Don't start from frame 0 here, since the animation has to play backwards, // and being on kMaggieStateSleeping state means we are already at the proper (end) frame _animationFrame = 0; #endif // BLADERUNNER_ORIGINAL_BUGS } } else { _animationState = kMaggieStateLyingDown; _animationFrame = 0; } } // otherwise, the request for animation 54 (ie. assume lying awake pose) is ignored break; case 55: if (_animationState == kMaggieStateLyingIdle) { _animationState = kMaggieStateGoingToSleep; _animationFrame = 0; } // otherwise, the request for animation 55 (ie. assume sleeping pose) is ignored break; case 56: if (_animationState != kMaggieStateHappyA) { _animationFrame = 0; _animationState = kMaggieStateHappyA; } _varTimesToBarkWhenHappyA = Random_Query(2, 6); break; case 57: if (_animationState != kMaggieStateHappyB) { _animationFrame = 0; _animationState = kMaggieStateHappyB; } if (_varMaggieClickResponse == 3) { // (Cut Content; _varMaggieClickResponse only gets values > 0 in Cut Content) // Allow for time for McCoy to sit down _varTimesToLoopWhenHappyB = Random_Query(4, 6); } else { _varTimesToLoopWhenHappyB = Random_Query(2, 6); } _varMaggieSoundPan = _vm->_actors[kActorMaggie]->soundPan(75); if (_vm->_cutContent) { Sound_Play(Random_Query(kSfxDOGTAIL1, kSfxDOGTAIL2), 50, _varMaggieSoundPan, _varMaggieSoundPan, 50); } else { #if BLADERUNNER_ORIGINAL_BUGS Sound_Play(kSfxDOGTAIL1, 50, 0, 0, 50); #else Sound_Play(kSfxDOGTAIL1, 50, _varMaggieSoundPan, _varMaggieSoundPan, 50); #endif // BLADERUNNER_ORIGINAL_BUGS } break; case 88: _animationState = kMaggieStateDead; _animationFrame = 0; break; default: debugC(6, kDebugAnimation, "AIScriptMaggie::ChangeAnimationMode(%d) - Target mode is not supported", mode); break; } return true; } void AIScriptMaggie::QueryAnimationState(int *animationState, int *animationFrame, int *animationStateNext, int *animationNext) { *animationState = _animationState; *animationFrame = _animationFrame; *animationStateNext = _animationStateNext; *animationNext = _animationNext; } void AIScriptMaggie::SetAnimationState(int animationState, int animationFrame, int animationStateNext, int animationNext) { _animationState = animationState; _animationFrame = animationFrame; _animationStateNext = animationStateNext; _animationNext = animationNext; } bool AIScriptMaggie::ReachedMovementTrackWaypoint(int waypointId) { return true; } void AIScriptMaggie::FledCombat() { } int AIScriptMaggie::randomWaypointMA02() { switch (Random_Query(0, 3)) { case 0: // near counter and apartment door (eg. when greeting McCoy) return 264; case 1: // bottom right of screen return 265; case 2: // near bedroom door (left) return 266; default: // in front of windows (near counter) return 267; } } float AIScriptMaggie::distanceToActor(int actorId, float x, float y, float z) { float actorX, actorY, actorZ; Actor_Query_XYZ(actorId, &actorX, &actorY, &actorZ); return sqrt(static_cast((z - actorZ) * (z - actorZ) + (y - actorY) * (y - actorY) + (x - actorX) * (x - actorX))); } } // End of namespace BladeRunner