/* 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