Files
GTASource/game/text/messages.cpp
expvintl 419f2e4752 init
2025-02-23 17:40:52 +08:00

2847 lines
90 KiB
C++

/////////////////////////////////////////////////////////////////////////////////
//
// FILE : messages.cpp
// PURPOSE : Messages handling. Keeps track of messages that are to be displayed.
// AUTHOR : Obbe, GraemeW, DerekP (13/12/99)
//
/////////////////////////////////////////////////////////////////////////////////
// C headers
#include <stdio.h>
// fw headers
#include "fwsys/timer.h"
#include "fwmaths/angle.h"
// game headers
#include "messages.h"
#include "audio/frontendaudioentity.h"
#include "camera/viewports/ViewportManager.h"
#include "camera/CamInterface.h"
#include "camera/debug/DebugDirector.h"
#include "camera/Caminterface.h"
#include "camera/viewports/ViewportManager.h"
#include "Control/GameLogic.h"
#include "control/replay/replay.h"
#include "core/game.h"
#include "cutscene\CutSceneManagerNew.h"
#include "debug\Debug.h"
#include "frontend\BusySpinner.h"
#include "frontend\PauseMenu.h"
#include "frontend/WarningScreen.h"
#include "frontend\NewHud.h"
#include "Frontend/Scaleform/ScaleFormStore.h"
#include "frontend/VideoEditor/VideoEditorInterface.h"
#include "frontend/ui_channel.h"
#include "game\Clock.h"
#include "peds/Ped.h"
#include "Stats\StatsMgr.h"
#include "renderer/DrawLists/DrawListMgr.h"
#include "SaveLoad/savegame_autoload.h"
#include "SaveLoad/savegame_messages.h"
#include "scene/world/GameWorld.h"
#include "script/script_channel.h"
#include "script/script_hud.h"
#include "system/controlMgr.h"
#include "Text\Text.h"
#include "Text\TextFormat.h"
#include "Text\TextConversion.h"
// Parser headers
#include "parser/manager.h"
#include "Text\dialogueCharacters_parser.h"
#include "data/aes_init.h"
AES_INIT_C;
TEXT_OPTIMISATIONS()
CQueuedMessage CMessages::BriefMessages[MAX_NUM_BRIEF_MESSAGES];
CPreviousMessages CMessages::PreviousDialogueMessages;
CPreviousMessages CMessages::PreviousMissionMessages;
CPreviousMessages CMessages::PreviousHelpTextMessages;
bool CMessages::bTitleActive = false;
u32 CMessages::m_PreviousSubtitleTimer = 0;
char CHelpMessage::m_HelpMessageText[MAX_HELP_TEXT_SLOTS][MAX_CHARS_IN_MESSAGE];
bool CHelpMessage::m_bHelpMessagesFading[MAX_HELP_TEXT_SLOTS];
bool CHelpMessage::m_bTriggerSound[MAX_HELP_TEXT_SLOTS];
CHelpMessage::eTEXT_STATE CHelpMessage::m_iNewText[MAX_HELP_TEXT_SLOTS];
eTEXT_STYLE CHelpMessage::m_iStyle[MAX_HELP_TEXT_SLOTS];
eTEXT_ARROW_ORIENTATION CHelpMessage::m_iArrowDirection[MAX_HELP_TEXT_SLOTS];
s32 CHelpMessage::m_iFloatingTextOffset[MAX_HELP_TEXT_SLOTS];
Color32 CHelpMessage::m_iColour[MAX_HELP_TEXT_SLOTS];
Vector2 CHelpMessage::m_vScreenPosition[MAX_HELP_TEXT_SLOTS];
s32 CHelpMessage::m_OverrideDuration[MAX_HELP_TEXT_SLOTS];
// Dialogue Character Data
#define DEFAULT_CHARACTER_TXD "CHAR_DEFAULT"
#define DIALOGUE_CHARACTERS_META_FILE "common:/data/ui/dialogueCharacters"
SDialogueCharacters CMessages::ms_dialogueCharacters;
// ******************************************************************************************************
// ** SDialogueCharacters
// ******************************************************************************************************
// Gets the character txd associated with the specified voice name hash
const char* SDialogueCharacters::GetCharacterTXD(u32 iVoiceNameHash, u32 iMissionNameHash/*=0*/)
{
if(iVoiceNameHash != 0)
{
for(int i = 0; i < m_characterInfo.GetCount(); ++i)
{
u32 charMissionHash = m_characterInfo[i].m_MissionNameHash;
if ( m_characterInfo[i].m_VoiceNameHash == iVoiceNameHash &&
( charMissionHash == 0 || charMissionHash == iMissionNameHash) )
{
return m_characterInfo[i].m_txd.GetCStr();
}
}
}
return DEFAULT_CHARACTER_TXD;
}
// ******************************************************************************************************
// ** CNumberWithinMessage
// ******************************************************************************************************
void CNumberWithinMessage::Set(s32 IntegerToDisplay)
{
m_Integer = IntegerToDisplay;
m_NumberOfDecimalPlaces = DISPLAY_INTEGER;
}
void CNumberWithinMessage::Set(float FloatToDisplay, s32 NumberOfDecimalPlaces)
{
m_Float = FloatToDisplay;
m_NumberOfDecimalPlaces = NumberOfDecimalPlaces;
}
// Returns length of new string
s32 CNumberWithinMessage::FillString(char *pStringToFill, s32 MaxLengthOfString) const
{
switch (m_NumberOfDecimalPlaces)
{
case NO_NUMBER_TO_DISPLAY :
Assertf(0, "CNumberWithinMessage::FillString - text contains a ~1~ but there is no number to display");
safecpy(pStringToFill, "", MaxLengthOfString);
break;
case DISPLAY_INTEGER : // Treat as an integer
formatf(pStringToFill, MaxLengthOfString, "%d", m_Integer);
break;
default : // Treat as a float
Assertf(m_NumberOfDecimalPlaces >= 0, "CNumberWithinMessage::FillString - number of decimal places (%d) for float (%f) is less than 0", m_NumberOfDecimalPlaces, m_Float);
Assertf(m_NumberOfDecimalPlaces < 5, "CNumberWithinMessage::FillString - trying to display a floating point number (%f) to more than 5 decimal places (%d)", m_Float, m_NumberOfDecimalPlaces);
formatf(pStringToFill, MaxLengthOfString, "%.*f", m_NumberOfDecimalPlaces, m_Float);
break;
}
return istrlen(pStringToFill);
}
#if !__FINAL
void CNumberWithinMessage::DebugPrintContents()
{
switch (m_NumberOfDecimalPlaces)
{
case NO_NUMBER_TO_DISPLAY :
break;
case DISPLAY_INTEGER : // Treat as an integer
scriptDisplayf("Integer Value = %d", m_Integer);
break;
default : // Treat as a float
Assertf(m_NumberOfDecimalPlaces >= 0, "CNumberWithinMessage::DebugPrintContents - number of decimal places (%d) for float (%f) is less than 0", m_NumberOfDecimalPlaces, m_Float);
Assertf(m_NumberOfDecimalPlaces < 5, "CNumberWithinMessage::DebugPrintContents - trying to display a floating point number (%f) to more than 5 decimal places (%d)", m_Float, m_NumberOfDecimalPlaces);
scriptDisplayf("Float Value = %.*f", m_NumberOfDecimalPlaces, m_Float);
break;
}
}
#endif // !__FINAL
bool CNumberWithinMessage::operator==(const CNumberWithinMessage& otherNumber) const
{
// Should I be comparing m_Colour as well?
if (m_NumberOfDecimalPlaces == otherNumber.m_NumberOfDecimalPlaces)
{
switch (m_NumberOfDecimalPlaces)
{
case NO_NUMBER_TO_DISPLAY :
return true;
break;
case DISPLAY_INTEGER :
if (m_Integer == otherNumber.m_Integer)
{
return true;
}
break;
default :
if (m_Float == otherNumber.m_Float)
{
return true;
}
break;
}
}
return false;
}
// ******************************************************************************************************
// ** CSubStringWithinMessage
// ******************************************************************************************************
void CSubStringWithinMessage::SetTextLabel(const char *pTextLabel)
{
m_pText = TheText.Get(pTextLabel);
m_BlockContainingThisText = TheText.GetBlockContainingLastReturnedString();
}
void CSubStringWithinMessage::SetTextLabelHashKey(u32 HashKeyOfTextLabel)
{
char StringContainingHashKey[24];
formatf(StringContainingHashKey, 24, "%u", HashKeyOfTextLabel);
m_pText = TheText.Get(HashKeyOfTextLabel, StringContainingHashKey);
m_BlockContainingThisText = TheText.GetBlockContainingLastReturnedString();
}
void CSubStringWithinMessage::SetCharPointer(const char *pLocalizedString)
{
m_pText = pLocalizedString;
m_BlockContainingThisText = CHAR_POINTER;
}
void CSubStringWithinMessage::SetLiteralString(const char *pLiteralString, eLiteralStringType literalStringType, bool bUseDebugPortionOfSingleFrameArray)
{
uLiteralString.m_LiteralStringIndex = static_cast<s16>(CScriptHud::ScriptLiteralStrings.SetLiteralString(pLiteralString, literalStringType, bUseDebugPortionOfSingleFrameArray));
uLiteralString.m_LiteralStringType = static_cast<u16>(literalStringType);
m_BlockContainingThisText = LITERAL_STRING;
}
void CSubStringWithinMessage::SetPersistentLiteralStringOccursInSubtitles(bool bOccursInSubtitles)
{
if (Verifyf(m_BlockContainingThisText == LITERAL_STRING, "CSubStringWithinMessage::SetPersistentLiteralStringOccursInSubtitles - expected this string to be a literal string"))
{
if (Verifyf(uLiteralString.m_LiteralStringType == LITERAL_STRING_TYPE_PERSISTENT, "CSubStringWithinMessage::SetPersistentLiteralStringOccursInSubtitles - Expected literal strings in subtitles to always be persistent"))
{
CScriptHud::ScriptLiteralStrings.SetPersistentLiteralOccursInSubtitles(uLiteralString.m_LiteralStringIndex, bOccursInSubtitles);
}
}
}
void CSubStringWithinMessage::Clear(bool ClearPersistentLiterals, bool bClearingFromPreviousBriefs)
{
if (m_BlockContainingThisText == LITERAL_STRING)
{
if (uLiteralString.m_LiteralStringType == LITERAL_STRING_TYPE_PERSISTENT)
{
if (ClearPersistentLiterals)
{
CScriptHud::ScriptLiteralStrings.ClearPersistentString(uLiteralString.m_LiteralStringIndex, bClearingFromPreviousBriefs);
}
}
uLiteralString.m_LiteralStringIndex = -1;
}
else
{
m_pText = NULL;
}
m_BlockContainingThisText = NO_STRING_TO_DISPLAY;
m_Colour = HUD_COLOUR_INVALID;
}
bool CSubStringWithinMessage::UsesTextFromThisBlock(s32 TextBlockIndex)
{
if (m_BlockContainingThisText == TextBlockIndex)
{
return true;
}
return false;
}
u32 CSubStringWithinMessage::GetStringCharacterCount()
{
if (m_BlockContainingThisText == NO_STRING_TO_DISPLAY)
{
return 0;
}
return CTextConversion::GetCharacterCount(GetString());
}
const char *CSubStringWithinMessage::GetString() const
{
switch (m_BlockContainingThisText)
{
case NO_STRING_TO_DISPLAY :
return NULL;
case LITERAL_STRING :
if ( (uLiteralString.m_LiteralStringType == LITERAL_STRING_TYPE_SINGLE_FRAME) && !CSystem::IsThisThreadId(SYS_THREAD_RENDER))
{ // If we're trying to access a single frame literal outside the Render Thread then we need to access the write buffer of the double buffer
return CScriptHud::ScriptLiteralStrings.GetSingleFrameLiteralStringFromWriteBuffer(uLiteralString.m_LiteralStringIndex);
}
else
{
return CScriptHud::ScriptLiteralStrings.GetLiteralString(uLiteralString.m_LiteralStringIndex, (CSubStringWithinMessage::eLiteralStringType) uLiteralString.m_LiteralStringType);
}
break;
default:
return m_pText;
break;
}
}
void CSubStringWithinMessage::DetermineWhichPreviousBriefArrayToUse(bool &bAddToPreviousBriefs, bool &bAddToDialogueBriefs)
{
switch (m_BlockContainingThisText)
{
case GTA5_GLOBAL_TEXT_SLOT : // main text block
case LITERAL_STRING : // not sure what to do for this - going to try leaving the two flags as they are
case NO_STRING_TO_DISPLAY : // not sure what to do for this - going to try leaving the two flags as they are
case MISSION_TEXT_SLOT :
case CREDITS_TEXT_SLOT :
case MINIGAME_TEXT_SLOT :
case ODDJOB_TEXT_SLOT :
case DLC_TEXT_SLOT0 :
case DLC_TEXT_SLOT1 :
case DLC_TEXT_SLOT2 :
break;
case MISSION_DIALOGUE_TEXT_SLOT :
case AMBIENT_DIALOGUE_TEXT_SLOT :
case DLC_MISSION_DIALOGUE_TEXT_SLOT :
case DLC_AMBIENT_DIALOGUE_TEXT_SLOT :
case DLC_MISSION_DIALOGUE_TEXT_SLOT2 :
bAddToDialogueBriefs = true;
break;
default :
bAddToPreviousBriefs = false;
bAddToDialogueBriefs = false;
return;
}
}
void CSubStringWithinMessage::SetLiteralStringOccursInPreviousBriefs()
{
if (m_BlockContainingThisText == LITERAL_STRING)
{
if (Verifyf(uLiteralString.m_LiteralStringType == LITERAL_STRING_TYPE_PERSISTENT, "CSubStringWithinMessage::SetLiteralStringOccursInPreviousBriefs - Expected literal strings in subtitles to always be persistent"))
{
CScriptHud::ScriptLiteralStrings.SetPersistentLiteralOccursInPreviousBriefs(uLiteralString.m_LiteralStringIndex, true);
}
}
}
bool CSubStringWithinMessage::operator==(const CSubStringWithinMessage& otherSubString) const
{
// Should I be comparing m_Colour as well?
// Should I actually be calling CTextConversion::charStrcmp to check that the characters
// in both strings match?
if (m_BlockContainingThisText == otherSubString.m_BlockContainingThisText)
{
switch (m_BlockContainingThisText)
{
case NO_STRING_TO_DISPLAY :
return true;
break;
case LITERAL_STRING :
if (uLiteralString.m_LiteralStringIndex == otherSubString.uLiteralString.m_LiteralStringIndex)
{
if (uLiteralString.m_LiteralStringType == otherSubString.uLiteralString.m_LiteralStringType)
{
return true;
}
}
break;
case CHAR_POINTER :
return m_pText == otherSubString.m_pText; // this MAY want to compare strcmps if they're pointing at different values... but none of the other fields do
default :
if (m_pText == otherSubString.m_pText)
{
return true;
}
break;
}
}
return false;
}
// ******************************************************************************************************
// ** CSubtitleMessage
// ******************************************************************************************************
void CSubtitleMessage::Set(char const *pText, s32 TextBlock,
CNumberWithinMessage *pArrayOfNumbers, u32 SizeOfNumberArray,
CSubStringWithinMessage *pArrayOfSubStrings, u32 SizeOfSubStringArray,
bool bUsesUnderscore, bool bHelpText, u32 iVoiceNameHash/*=0*/, u32 iMissionNameHash/*=0*/)
{
Clear(false, false);
if (pArrayOfSubStrings)
{
if (Verifyf(SizeOfSubStringArray <= MAX_SUBSTRINGS_IN_SUBTITLES, "CSubtitleMessage::Set - new subtitle has more than %d substrings", MAX_SUBSTRINGS_IN_SUBTITLES))
{
for (u32 string_loop = 0; string_loop < SizeOfSubStringArray; string_loop++)
{
m_SubstringsWithinMessage[string_loop] = pArrayOfSubStrings[string_loop];
}
}
}
if (pArrayOfNumbers)
{
if (Verifyf(SizeOfNumberArray <= MAX_NUMBERS_IN_SUBTITLES, "CSubtitleMessage::Set - new subtitle has more than %d numbers", MAX_NUMBERS_IN_SUBTITLES))
{
for (u32 number_loop = 0; number_loop < SizeOfNumberArray; number_loop++)
{
m_NumbersWithinMessage[number_loop] = pArrayOfNumbers[number_loop];
}
}
}
m_pMessage = pText;
m_TextBlockContainingThisMessage = TextBlock;
m_bUseUnderscore = bUsesUnderscore;
m_bIsHelpText = bHelpText;
m_iVoiceNameHash = iVoiceNameHash;
m_iMissionNameHash = iMissionNameHash;
}
void CSubtitleMessage::Clear(bool ClearPersistentLiterals, bool bClearingFromPreviousBriefs)
{
m_pMessage = NULL;
m_TextBlockContainingThisMessage = -1;
u32 loop = 0;
for (loop = 0; loop < MAX_NUMBERS_IN_SUBTITLES; loop++)
{
m_NumbersWithinMessage[loop].Clear();
}
for (loop = 0; loop < MAX_SUBSTRINGS_IN_SUBTITLES; loop++)
{
m_SubstringsWithinMessage[loop].Clear(ClearPersistentLiterals, bClearingFromPreviousBriefs);
}
m_bUseUnderscore = false;
m_bIsHelpText = false;
}
bool CSubtitleMessage::UsesTextFromThisBlock(s32 TextBlockIndex)
{
if (m_TextBlockContainingThisMessage == TextBlockIndex)
{
return true;
}
for (u32 loop = 0; loop < MAX_SUBSTRINGS_IN_SUBTITLES; loop++)
{
if (m_SubstringsWithinMessage[loop].UsesTextFromThisBlock(TextBlockIndex))
{
return true;
}
}
return false;
}
void CSubtitleMessage::FillInString(char *pStringToFillIn, u32 MaxLengthOfString)
{
CMessages::InsertNumbersAndSubStringsIntoString(m_pMessage,
m_NumbersWithinMessage, MAX_NUMBERS_IN_SUBTITLES,
m_SubstringsWithinMessage, MAX_SUBSTRINGS_IN_SUBTITLES,
pStringToFillIn, MaxLengthOfString);
#if __WIN32PC
CMessages::InsertPlayerControlKeysInString(pStringToFillIn);
#endif // __WIN32PC
}
bool CSubtitleMessage::DoesMainTextMatch(CSubtitleMessage &OtherSubtitle)
{
if ( strcmp( m_pMessage, OtherSubtitle.m_pMessage ) == 0 )
{
return true;
}
return false;
}
bool CSubtitleMessage::Matches(CSubtitleMessage &OtherSubtitle)
{
if (m_pMessage != OtherSubtitle.m_pMessage)
{
return false;
}
if (m_TextBlockContainingThisMessage != OtherSubtitle.m_TextBlockContainingThisMessage)
{
return false;
}
for (u32 number_loop = 0; number_loop < MAX_NUMBERS_IN_SUBTITLES; number_loop++)
{
if (m_NumbersWithinMessage[number_loop] != OtherSubtitle.m_NumbersWithinMessage[number_loop])
{
return false;
}
}
for (u32 sub_string_loop = 0; sub_string_loop < MAX_SUBSTRINGS_IN_SUBTITLES; sub_string_loop++)
{
if (m_SubstringsWithinMessage[sub_string_loop] != OtherSubtitle.m_SubstringsWithinMessage[sub_string_loop])
{
return false;
}
}
return true;
}
void CSubtitleMessage::DetermineWhichPreviousBriefArrayToUse(bool &bAddToPreviousBriefs, bool &bAddToDialogueBriefs, bool &bAddToHelpTextBriefs)
{
bAddToPreviousBriefs = true;
bAddToDialogueBriefs = false;
if (m_bIsHelpText)
{
bAddToHelpTextBriefs = true;
return;
}
switch (m_TextBlockContainingThisMessage)
{
case GTA5_GLOBAL_TEXT_SLOT : // main text block
case MISSION_TEXT_SLOT :
case CREDITS_TEXT_SLOT :
case MINIGAME_TEXT_SLOT :
case ODDJOB_TEXT_SLOT :
case DLC_TEXT_SLOT0 :
case DLC_TEXT_SLOT1 :
case DLC_TEXT_SLOT2 :
break;
case PHONE_TEXT_SLOT:
case MISSION_DIALOGUE_TEXT_SLOT :
case AMBIENT_DIALOGUE_TEXT_SLOT :
case DLC_MISSION_DIALOGUE_TEXT_SLOT :
case DLC_AMBIENT_DIALOGUE_TEXT_SLOT :
case DLC_MISSION_DIALOGUE_TEXT_SLOT2 :
bAddToDialogueBriefs = true;
break;
default :
bAddToPreviousBriefs = false;
bAddToDialogueBriefs = false;
return;
}
for (u32 SubStringLoop = 0; SubStringLoop < MAX_SUBSTRINGS_IN_SUBTITLES; SubStringLoop++)
{
m_SubstringsWithinMessage[SubStringLoop].DetermineWhichPreviousBriefArrayToUse(bAddToPreviousBriefs, bAddToDialogueBriefs);
if (!bAddToPreviousBriefs)
{
bAddToDialogueBriefs = false;
return;
}
}
}
void CSubtitleMessage::SetAllLiteralStringsOccurInPreviousBriefs()
{
for (u32 sub_string_loop = 0; sub_string_loop < MAX_SUBSTRINGS_IN_SUBTITLES; sub_string_loop++)
{
m_SubstringsWithinMessage[sub_string_loop].SetLiteralStringOccursInPreviousBriefs();
}
}
// ******************************************************************************************************
// ** CQueuedMessage
// ******************************************************************************************************
void CQueuedMessage::Set(char const *pText, s32 TextBlock,
u32 Duration, bool bAddToPrevBriefs, ePreviousBriefOverride OverrideOfPreviousBriefList,
CNumberWithinMessage *pArrayOfNumbers, u32 SizeOfNumberArray,
CSubStringWithinMessage *pArrayOfSubStrings, u32 SizeOfSubStringArray,
bool bUsesUnderscore, u32 iVoiceNameHash/*=0*/, u32 iMissionNameHash/*=0*/)
{
m_MessageDetails.Set(pText, TextBlock,
pArrayOfNumbers, SizeOfNumberArray,
pArrayOfSubStrings, SizeOfSubStringArray,
bUsesUnderscore, false, iVoiceNameHash, iMissionNameHash);
m_Duration = Duration;
m_WhenStarted = fwTimer::GetTimeInMilliseconds();
m_bAddToPreviousBriefs = bAddToPrevBriefs;
m_OverrideBriefList = OverrideOfPreviousBriefList;
m_bNewlyAddedToTheHeadOfTheQueue = false;
// If main text begins with ~z~ then it won't be displayed if subtitles are switched off
// (There could be a ~x~ before the ~z~)
// Should I bother testing if pShortText begins with ~z~?
if (CMessages::IsThisTextSwitchedOffByFrontendSubtitleSetting(pText))
{
m_bDisplayEvenIfSubtitlesAreSwitchedOff = false;
}
else
{
m_bDisplayEvenIfSubtitlesAreSwitchedOff = true;
}
}
void CQueuedMessage::Clear(bool ClearPersistentLiterals, bool bClearingFromPreviousBriefs)
{
m_MessageDetails.Clear(ClearPersistentLiterals, bClearingFromPreviousBriefs);
m_Duration = 0;
m_WhenStarted = 0;
m_bAddToPreviousBriefs = true;
m_OverrideBriefList = PREVIOUS_BRIEF_NO_OVERRIDE;
m_bNewlyAddedToTheHeadOfTheQueue = false;
m_bDisplayEvenIfSubtitlesAreSwitchedOff = false;
}
bool CQueuedMessage::HasDisplayTimeExpired()
{
if (fwTimer::GetTimeInMilliseconds() > (m_WhenStarted + m_Duration) )
{
return true;
}
return false;
}
void CQueuedMessage::AddToPreviousBriefsIfRequired()
{
if (m_bNewlyAddedToTheHeadOfTheQueue)
{
m_bNewlyAddedToTheHeadOfTheQueue = false;
if (!IsEmpty())
{
if (m_bAddToPreviousBriefs)
{
CMessages::AddToPreviousBriefArray(m_MessageDetails, m_OverrideBriefList);
}
}
}
}
// ******************************************************************************************************
// ** CMessages
// ******************************************************************************************************
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CMessages::Init
// PURPOSE: initialises the class
/////////////////////////////////////////////////////////////////////////////////////
void CMessages::Init()
{
const s32 MAX_NUM_PREVIOUS_BRIEF_ITEMS = 20;
PreviousHelpTextMessages.Init(MAX_NUM_PREVIOUS_BRIEF_ITEMS);
PreviousDialogueMessages.Init(MAX_NUM_PREVIOUS_BRIEF_ITEMS);
PreviousMissionMessages.Init(MAX_NUM_PREVIOUS_BRIEF_ITEMS);
InitDialogueMeta();
}
#if __BANK
void CMessages::InitWidgets()
{
bkBank *pBank = BANKMGR.FindBank(UI_DEBUG_BANK_NAME);
if (pBank)
{
pBank->AddButton("Reload Dialogue Meta File", &CMessages::InitDialogueMeta);
}
}
#endif
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CMessages::Init
// PURPOSE: initialises dialogue character data
/////////////////////////////////////////////////////////////////////////////////////
void CMessages::InitDialogueMeta()
{
// Init Dialogue Character Info
PARSER.LoadObject(DIALOGUE_CHARACTERS_META_FILE, "meta", ms_dialogueCharacters);
uiDebugf1("Dialogue Character Metadata Loaded");
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CMessages::Init
// PURPOSE: initialises the class at game startup
/////////////////////////////////////////////////////////////////////////////////////
void CMessages::Init(unsigned initMode)
{
if(initMode == INIT_SESSION)
{
s32 C;
SetMissionTitleActive(false);
for (C = 0; C < MAX_NUM_BRIEF_MESSAGES; C++)
{
BriefMessages[C].Clear(false, false);
}
ClearAllMessagesDisplayedByGame();
CHelpMessage::Init();
CSubtitleText::Init();
}
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CMessages::Shutdown
// PURPOSE: shuts down the class at game shutdown
/////////////////////////////////////////////////////////////////////////////////////
void CMessages::Shutdown(unsigned shutdownMode)
{
if(shutdownMode == SHUTDOWN_CORE)
{
TheText.Unload();
PreviousHelpTextMessages.Shutdown();
PreviousDialogueMessages.Shutdown();
PreviousMissionMessages.Shutdown();
CLoadingText::Shutdown();
}
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CMessages::ClearAllBriefMessages
// PURPOSE: Wipes the brief messages array
/////////////////////////////////////////////////////////////////////////////////////
void CMessages::ClearAllBriefMessages()
{
PreviousHelpTextMessages.ClearAllPreviousBriefs();
PreviousDialogueMessages.ClearAllPreviousBriefs();
PreviousMissionMessages.ClearAllPreviousBriefs();
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CMessages::ClearAllMessagesDisplayedByGame
// PURPOSE: This will be called when the language changes and will make sure no
// messages are displayed on screen
/////////////////////////////////////////////////////////////////////////////////////
void CMessages::ClearAllMessagesDisplayedByGame()
{
CHelpMessage::ClearAll();
ClearMessages();
ClearAllBriefMessages();
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CMessages::ClearAllDisplayedMessagesThatBelongToThisTextBlock
// PURPOSE: clears all messages that belong to the text block passed in, but leaves
// the rest intact
/////////////////////////////////////////////////////////////////////////////////////
void CMessages::ClearAllDisplayedMessagesThatBelongToThisTextBlock(s32 TextBlock, bool bClearPreviousBriefs) // , bool bIgnoreMissionTitle)
{
Assertf(TextBlock >= 0, "ClearAllDisplayedMessagesThatBelongToThisTextBlock - TextBlock index should be >= 0");
// Brief Messages
for (s32 loop = 0; loop < MAX_NUM_BRIEF_MESSAGES; loop++)
{
if (BriefMessages[loop].UsesTextFromThisBlock(TextBlock))
{
BriefMessages[loop].Clear(true, false);
}
}
// Brief Messages - shift up any remaining messages to fill the gaps
s32 CopyToIndex = 0;
while (CopyToIndex < MAX_NUM_BRIEF_MESSAGES)
{
if (BriefMessages[CopyToIndex].IsEmpty())
{
s32 CopyFromIndex = (CopyToIndex + 1);
while ( (CopyFromIndex < MAX_NUM_BRIEF_MESSAGES) && (BriefMessages[CopyFromIndex].IsEmpty()) )
{
CopyFromIndex++;
}
if (CopyFromIndex < MAX_NUM_BRIEF_MESSAGES)
{
BriefMessages[CopyToIndex] = BriefMessages[CopyFromIndex];
if (CopyToIndex == 0)
{
BriefMessages[0].SetWhenStarted(fwTimer::GetTimeInMilliseconds());
BriefMessages[0].SetNewlyAddedToTheHeadOfTheQueue(true);
}
BriefMessages[CopyFromIndex].Clear(false, false);
}
}
CopyToIndex++;
}
// Previous Briefs
if (bClearPreviousBriefs)
{
ClearAllPreviousBriefsThatBelongToThisTextBlock(TextBlock);
} // end of if (bClearPreviousBriefs)
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CMessages::ClearMessages
// PURPOSE: Clears all the messages (the brief ones anyway)
/////////////////////////////////////////////////////////////////////////////////////
void CMessages::ClearMessages()
{
for (s32 C = 0; C < MAX_NUM_BRIEF_MESSAGES; C++)
{
BriefMessages[C].Clear(true, false);
}
}
// Return false if copy fails to complete (because destination string isn't long enough)
bool CMessages::CopyGxtStringIntoStringAtIndex(char *pDestString, u32 &DestIndex, u32 MaxDestStringLength, const char *pSourceGxtString, u32 SourceStringLength)
{
if (pSourceGxtString == NULL)
{
return true;
}
if (SourceStringLength == 0)
{
SourceStringLength = CTextConversion::GetByteCount(pSourceGxtString);
}
u32 CopyLoop = 0;
while (CopyLoop < SourceStringLength)
{
if (DestIndex < MaxDestStringLength)
{
pDestString[DestIndex++] = pSourceGxtString[CopyLoop++];
}
else
{
#if __ASSERT
Assertf(0, "CMessages::CopyGxtStringIntoStringAtIndex - destination string has length %d. It's not long enough. Source string begins %s", MaxDestStringLength, pSourceGxtString );
#endif // __ASSERT
return false;
}
}
return true;
}
// Return false if copy fails to complete (because destination string isn't long enough, or source string is too long to store in temporary char array)
bool CMessages::CopyStringIntoStringAtIndex(char *pDestString, u32 &DestIndex, u32 MaxDestStringLength, const char *pSourceString, u32 SourceStringLength)
{
if (pSourceString == NULL)
{
return true;
}
if (SourceStringLength == 0)
{
SourceStringLength = ustrlen(pSourceString);
}
return CopyGxtStringIntoStringAtIndex(pDestString, DestIndex, MaxDestStringLength, pSourceString, SourceStringLength);
}
void CMessages::CopyColouredGxtStringIntoStringAtIndex(char *pDestString, u32 &DestIndex, u32 MaxDestStringLength, eHUD_COLOURS ColourOfSourceString, const char *pSourceGxtString, u32 SourceStringLength)
{
bool bResult = false;
const u32 MaxLengthOfColourString = 16;
char ColourString[MaxLengthOfColourString];
if (HUD_COLOUR_INVALID != ColourOfSourceString)
{
formatf(ColourString, MaxLengthOfColourString, "~HC_%d~", ColourOfSourceString);
bResult = CopyStringIntoStringAtIndex(pDestString, DestIndex, MaxDestStringLength, ColourString);
Assertf(bResult, "CMessages::CopyColouredGxtStringIntoStringAtIndex - failed to insert colour for substring/number into main string");
}
bResult = CopyGxtStringIntoStringAtIndex(pDestString, DestIndex, MaxDestStringLength, pSourceGxtString, SourceStringLength);
Assertf(bResult, "CMessages::CopyColouredGxtStringIntoStringAtIndex - failed to insert substring/number into main string");
// Maybe it's more flexible to leave it up to the line of text to reset the colour in case it should be reset to something other than COLOUR_STANDARD
// if (HUD_COLOUR_INVALID != ColourOfSourceString)
// { // Now write ~s~ after the substring/number to reset the colour for the rest of the line of text
// formatf(ColourString, MaxLengthOfColourString, "~%c~", FONT_CHAR_TOKEN_COLOUR_STANDARD);
// bResult = CopyStringIntoStringAtIndex(pDestString, DestIndex, MaxDestStringLength, ColourString);
// Assertf(bResult, "CMessages::CopyColouredGxtStringIntoStringAtIndex - failed to insert ~s~ for substring/number into main string");
// }
}
eTokenFormat CMessages::CheckForInsertionToken(const char *pOriginalString, const u32 CharacterIndex, u32 &IndexInteger)
{
eTokenFormat Format = TOKEN_FORMAT_NONE;
if (pOriginalString[CharacterIndex] == FONT_CHAR_TOKEN_DELIMITER)
{
char CharactersToCheck[4];
bool bReachedEndOfOriginalString = false;
for (u32 loop = 0; loop < 4; loop++)
{
if (bReachedEndOfOriginalString || (pOriginalString[CharacterIndex + 1 + loop] == rage::TEXT_CHAR_TERMINATOR) )
{
bReachedEndOfOriginalString = true;
CharactersToCheck[loop] = rage::TEXT_CHAR_TERMINATOR;
}
else
{
CharactersToCheck[loop] = pOriginalString[CharacterIndex + 1 + loop];
}
}
if (CharactersToCheck[1] == FONT_CHAR_TOKEN_DELIMITER)
{ // Check if it's ~a~ or ~1~. Could still be something else like ~n~
if (CharactersToCheck[0] == FONT_CHAR_TOKEN_NUMBER)
{
Format = TOKEN_FORMAT_NUMBER_ORIGINAL;
}
else if (CharactersToCheck[0] == FONT_CHAR_TOKEN_SUBSTRING)
{
Format = TOKEN_FORMAT_SUBSTRING_ORIGINAL;
}
}
else if (CharactersToCheck[1] == FONT_CHAR_TOKEN_UNDERLINE)
{ // Check if it's ~a_.~ or ~1_.~ Could still be something else
if (CharactersToCheck[3] == FONT_CHAR_TOKEN_DELIMITER)
{
if (CharactersToCheck[0] == FONT_CHAR_TOKEN_NUMBER)
{
Format = TOKEN_FORMAT_NUMBER_INDEXED;
}
else if (CharactersToCheck[0] == FONT_CHAR_TOKEN_SUBSTRING)
{
Format = TOKEN_FORMAT_SUBSTRING_INDEXED;
}
if (Format != TOKEN_FORMAT_NONE)
{
if (Verifyf( (CharactersToCheck[2] >= '0') && (CharactersToCheck[2] <= '9'),
"CMessages::CheckForInsertionToken - expected an integer after _ inside ~a_~ or ~1_~ but character is %c", CharactersToCheck[2]))
{
IndexInteger = CharactersToCheck[2] - '0';
}
}
}
}
}
return Format;
}
u32 CMessages::GetArrayIndexAccordingToFormat(const eTokenFormat FormatUsedForThisPosition, const u32 IndexInteger, eTokenFormat &MethodToUseForInserting, const u32 NumberOfItemsAlreadyInserted, const char * ASSERT_ONLY(pOriginalStringForAssertMessage))
{
if (MethodToUseForInserting == TOKEN_FORMAT_NONE)
{ // If this is the first element of this type (number or substring) to be inserted into the main string
// then use the method (original or indexed) that has just been read from the first token
MethodToUseForInserting = FormatUsedForThisPosition;
}
else
{ // Otherwise check that the current token is using the same method (original or indexed)
// as the first token for this type (number or substring)
if (FormatUsedForThisPosition != MethodToUseForInserting)
{
#if __ASSERT
Displayf("CMessages::GetArrayIndexAccordingToFormat - Main string is %s", pOriginalStringForAssertMessage );
Assertf(0, "CMessages::GetArrayIndexAccordingToFormat - Main string uses a combination of ~a~ and ~a_.~ or a combination of ~1~ and ~1_.~. See TTY for details");
#endif // __ASSERT
return 0;
}
}
switch (MethodToUseForInserting)
{
case TOKEN_FORMAT_NONE :
Assertf(0, "CMessages::GetArrayIndexAccordingToFormat - didn't expect MethodToUseForInserting to be TOKEN_FORMAT_NONE at this stage");
break;
case TOKEN_FORMAT_NUMBER_ORIGINAL :
case TOKEN_FORMAT_SUBSTRING_ORIGINAL :
break;
case TOKEN_FORMAT_NUMBER_INDEXED :
case TOKEN_FORMAT_SUBSTRING_INDEXED :
return IndexInteger; // Return the index that has been read from the token
break;
}
return NumberOfItemsAlreadyInserted; // For original method, the index will count up from 0
}
void CMessages::InsertNumberIntoString(char *pNewString, u32 &NewCharLoop, const u32 MaxSizeOfNewString, const CNumberWithinMessage *pArrayOfNumbers, const u32 SizeOfNumberArray, const u32 ArrayElementToUse, const char * ASSERT_ONLY(pOriginalStringForAssertMessage))
{
eHUD_COLOURS ColourForThisTextComponent = HUD_COLOUR_INVALID;
const s32 MaxLengthOfNumberString = 10;
char NumberAsAsciiString[MaxLengthOfNumberString];
u32 NumberStringLength = 0;
if (pArrayOfNumbers && (ArrayElementToUse < SizeOfNumberArray))
{
NumberStringLength = pArrayOfNumbers[ArrayElementToUse].FillString(NumberAsAsciiString, MaxLengthOfNumberString);
ColourForThisTextComponent = pArrayOfNumbers[ArrayElementToUse].GetColour();
}
else
{
safecpy(NumberAsAsciiString, "", MaxLengthOfNumberString);
}
Assertf(NumberStringLength != 0, "The number of numbers doesn't match the number of ~1~ in \"%s\"",
pOriginalStringForAssertMessage );
CopyColouredGxtStringIntoStringAtIndex(pNewString, NewCharLoop, MaxSizeOfNewString, ColourForThisTextComponent, NumberAsAsciiString, NumberStringLength);
}
void CMessages::InsertSubStringIntoString(char *pNewString, u32 &NewCharLoop, const u32 MaxSizeOfNewString, const CSubStringWithinMessage *pArrayOfSubStrings, const u32 SizeOfSubStringArray, const u32 ArrayElementToUse, const char * ASSERT_ONLY(pOriginalStringForAssertMessage))
{
eHUD_COLOURS ColourForThisTextComponent = HUD_COLOUR_INVALID;
const char *pSubStringToInsert = NULL;
if (pArrayOfSubStrings && (ArrayElementToUse < SizeOfSubStringArray))
{
pSubStringToInsert = pArrayOfSubStrings[ArrayElementToUse].GetString();
ColourForThisTextComponent = pArrayOfSubStrings[ArrayElementToUse].GetColour();
}
#if __ASSERT
if (pSubStringToInsert == NULL)
{
Displayf("Main string is %s", pOriginalStringForAssertMessage );
for (s32 debug_loop = 0; debug_loop < SizeOfSubStringArray; debug_loop++)
{
const char *pGxtSubString = pArrayOfSubStrings[debug_loop].GetString();
if (pGxtSubString)
{
Displayf("SubString %d is %s", debug_loop, pGxtSubString );
}
else
{
Displayf("SubString %d is empty", debug_loop);
}
}
Assertf(0, "CMessages::InsertSubStringIntoString - The number of substrings doesn't match the number of ~a~ in the main string. See TTY for details");
}
#endif // __ASSERT
CopyColouredGxtStringIntoStringAtIndex(pNewString, NewCharLoop, MaxSizeOfNewString, ColourForThisTextComponent, pSubStringToInsert);
}
void CMessages::InsertNumbersAndSubStringsIntoString(const char *pOriginalString,
const CNumberWithinMessage *pArrayOfNumbers, u32 SizeOfNumberArray,
const CSubStringWithinMessage *pArrayOfSubStrings, u32 SizeOfSubStringArray,
char *pNewString, u32 MaxSizeOfNewString)
{
if (pNewString == NULL)
{
Assertf(0, "CMessages::InsertNumbersAndSubStringsIntoString - Destination string is NULL");
return;
}
if (pOriginalString == NULL)
{
pNewString[0] = rage::TEXT_CHAR_TERMINATOR;
return;
}
if (pArrayOfNumbers == NULL)
{
SizeOfNumberArray = 0;
}
if (pArrayOfSubStrings == NULL)
{
SizeOfSubStringArray = 0;
}
u32 NumberOfNumbersInserted = 0;
u32 NumberOfSubStringsInserted = 0;
eTokenFormat MethodToUseForInsertingNumbers = TOKEN_FORMAT_NONE;
eTokenFormat MethodToUseForInsertingSubStrings = TOKEN_FORMAT_NONE;
u32 CharLoop = 0;
u32 NewCharLoop = 0;
while ( (pOriginalString[CharLoop] != rage::TEXT_CHAR_TERMINATOR) && (NewCharLoop < MaxSizeOfNewString) )
{
bool bNumberOrStringInserted = false;
u32 IndexInteger = 0;
eTokenFormat FormatUsedForThisPosition = CheckForInsertionToken(pOriginalString, CharLoop, IndexInteger);
if (FormatUsedForThisPosition != TOKEN_FORMAT_NONE)
{
switch (FormatUsedForThisPosition)
{
case TOKEN_FORMAT_NONE :
break;
case TOKEN_FORMAT_NUMBER_ORIGINAL :
case TOKEN_FORMAT_NUMBER_INDEXED :
IndexInteger = GetArrayIndexAccordingToFormat(FormatUsedForThisPosition, IndexInteger, MethodToUseForInsertingNumbers, NumberOfNumbersInserted, pOriginalString);
InsertNumberIntoString(pNewString, NewCharLoop, MaxSizeOfNewString, pArrayOfNumbers, SizeOfNumberArray, IndexInteger, pOriginalString);
NumberOfNumbersInserted++;
break;
case TOKEN_FORMAT_SUBSTRING_ORIGINAL :
case TOKEN_FORMAT_SUBSTRING_INDEXED :
IndexInteger = GetArrayIndexAccordingToFormat(FormatUsedForThisPosition, IndexInteger, MethodToUseForInsertingSubStrings, NumberOfSubStringsInserted, pOriginalString);
InsertSubStringIntoString(pNewString, NewCharLoop, MaxSizeOfNewString, pArrayOfSubStrings, SizeOfSubStringArray, IndexInteger, pOriginalString);
NumberOfSubStringsInserted++;
break;
}
switch (FormatUsedForThisPosition)
{
case TOKEN_FORMAT_NONE :
break;
case TOKEN_FORMAT_NUMBER_ORIGINAL :
case TOKEN_FORMAT_SUBSTRING_ORIGINAL :
bNumberOrStringInserted = true;
CharLoop += 3;
break;
case TOKEN_FORMAT_NUMBER_INDEXED :
case TOKEN_FORMAT_SUBSTRING_INDEXED :
bNumberOrStringInserted = true;
CharLoop += 5;
break;
}
}
if (!bNumberOrStringInserted)
{ // Just copy the current character from the OriginalString to the NewString
if (NewCharLoop < MaxSizeOfNewString)
{
pNewString[NewCharLoop++] = pOriginalString[CharLoop++];
}
#if __ASSERT
else
{
Assertf(0, "CMessages::InsertNumbersAndSubStringsIntoString - NewString has length %d. It's not long enough. Original string is %s", MaxSizeOfNewString, pOriginalString );
}
#endif // __ASSERT
}
}
if (NewCharLoop >= MaxSizeOfNewString)
{
#if __ASSERT
Assertf(0, "CMessages::InsertNumbersAndSubStringsIntoString - string is too long. The maximum length is %d. Original string is %s", MaxSizeOfNewString, pOriginalString );
#endif // __ASSERT
NewCharLoop = MaxSizeOfNewString-1;
u32 NumberOfTildes = 0;
for (u32 char_loop = 0; char_loop < NewCharLoop; char_loop++)
{
if (pNewString[char_loop] == FONT_CHAR_TOKEN_DELIMITER)
{
NumberOfTildes++;
}
}
if ((NumberOfTildes % 2) == 1)
{ // There are an odd number of tildes so add one as the second last character.
// This is so that CFont::GetTokenType doesn't have to scan forward until it finds a closing tilde.
// It seems to already handle an unrecognised token.
pNewString[NewCharLoop-1] = FONT_CHAR_TOKEN_DELIMITER;
}
}
pNewString[NewCharLoop] = rage::TEXT_CHAR_TERMINATOR;
// This is what differentiates LITERAL_STRING_TYPE_FOR_IMMEDIATE_USE from LITERAL_STRING_TYPE_SINGLE_FRAME
// After creating your immediate-use literals, you should call InsertNumbersAndSubStringsIntoString() to construct your larger string.
// As soon as the larger string has been constructed, we assume that it is safe to clear all the immediate-use literals on this thread (update or render).
CScriptHud::ScriptLiteralStrings.ClearArrayOfImmediateUseLiteralStrings(false);
}
void CMessages::GetExpectedNumberOfTextComponents(char *pGxtString, s32 &ReturnNumberOfNumbers, s32 &ReturnNumberOfSubstrings)
{
ReturnNumberOfNumbers = 0;
ReturnNumberOfSubstrings = 0;
if (pGxtString == NULL)
{
return;
}
u32 CharLoop = 0;
while ( (pGxtString[CharLoop] != rage::TEXT_CHAR_TERMINATOR) )
{
u32 IndexInteger = 0;
eTokenFormat FormatUsedForThisPosition = CheckForInsertionToken(pGxtString, CharLoop, IndexInteger);
switch (FormatUsedForThisPosition)
{
case TOKEN_FORMAT_NONE :
CharLoop++;
break;
case TOKEN_FORMAT_NUMBER_ORIGINAL :
ReturnNumberOfNumbers++;
CharLoop += 3;
break;
case TOKEN_FORMAT_SUBSTRING_ORIGINAL :
ReturnNumberOfSubstrings++;
CharLoop += 3;
break;
case TOKEN_FORMAT_NUMBER_INDEXED :
ReturnNumberOfNumbers++;
CharLoop += 5;
break;
case TOKEN_FORMAT_SUBSTRING_INDEXED :
ReturnNumberOfSubstrings++;
CharLoop += 5;
break;
}
}
}
#if __WIN32PC
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CMessages::InsertPlayerControlKeysInString
// PURPOSE: Insert player control key descriptions into text
/////////////////////////////////////////////////////////////////////////////////////
void CMessages::InsertPlayerControlKeysInString(char* UNUSED_PARAM(pBigString))
{
}
#endif // #if __WIN32PC
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CMessages::Update
// PURPOSE: updates any messages we may have
/////////////////////////////////////////////////////////////////////////////////////
void CMessages::Update(void)
{
s32 iCopyIndex;
if (!BriefMessages[0].IsEmpty())
{ // May have run out
if (BriefMessages[0].HasDisplayTimeExpired())
{
BriefMessages[0].Clear(true, false);
// Copy the new ones in
iCopyIndex = 0;
while (iCopyIndex < (MAX_NUM_BRIEF_MESSAGES-1) && !BriefMessages[iCopyIndex+1].IsEmpty())
{
BriefMessages[iCopyIndex] = BriefMessages[iCopyIndex+1];
iCopyIndex++;
}
BriefMessages[iCopyIndex].Clear(false, false);
// Set the start time of new message. (If there is one)
BriefMessages[0].SetWhenStarted(fwTimer::GetTimeInMilliseconds());
if (!BriefMessages[0].IsEmpty())
{ // Add the message to the previous brief array when it is first displayed
BriefMessages[0].SetNewlyAddedToTheHeadOfTheQueue(true);
}
}
}
BriefMessages[0].AddToPreviousBriefsIfRequired();
if (CPauseMenu::IsActive() && CWarningScreen::IsActive())
return;
#if __BANK
if (!CHudTools::bDisplayHud)
return;
#endif
//
// update the subtitles:
//
CRGBA colour;
char StringToDisplay[MAX_CHARS_IN_EXTENDED_MESSAGE];
// Update subtitles depending on when the script wants subtitles displayed
BriefMessages[0].FillInString(StringToDisplay, NELEM(StringToDisplay));
bool bDisplaySubtitles = true;
// if hud is hidden then dont display subtitles when pausemenu is active
if (CNewHud::IsFullHudHidden())
{
if ( CPauseMenu::IsActive()
#if GTA_REPLAY
|| CVideoEditorInterface::IsActive()
#endif
)
{
bDisplaySubtitles = false;
}
}
if ( (StringToDisplay[0] != 0 && bDisplaySubtitles)
&& (CPauseMenu::GetMenuPreference(PREF_SUBTITLES) || BriefMessages[0].GetDisplayEvenIfSubtitlesAreSwitchedOff() ) )
{
// set value:
CSubtitleText::SetMessageText(StringToDisplay);
}
else
{
CSubtitleText::ClearMessage();
}
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CMessages::ClearThisPrint
// PURPOSE: clear this message if its the one passed in
/////////////////////////////////////////////////////////////////////////////////////
void CMessages::ClearThisPrint(CSubtitleMessage &Message, bool bCompareMainStringsOnly)
{
bool bAStringHasBeenCleared = true;
char FirstString[MAX_CHARS_IN_EXTENDED_MESSAGE];
char SecondString[MAX_CHARS_IN_EXTENDED_MESSAGE];
if (!bCompareMainStringsOnly)
{
Message.FillInString(FirstString, NELEM(FirstString));
}
while (bAStringHasBeenCleared)
{
bAStringHasBeenCleared = false;
u32 Index = 0;
while (!bAStringHasBeenCleared && (Index < MAX_NUM_BRIEF_MESSAGES) && !BriefMessages[Index].IsEmpty())
{
bool bStringsAreEqual = false;
if (bCompareMainStringsOnly)
{
if (BriefMessages[Index].DoesMainTextMatch(Message))
{
bStringsAreEqual = true;
}
}
else
{
BriefMessages[Index].FillInString(SecondString, NELEM(SecondString));
if ( strcmp(FirstString, SecondString) == 0 )
{
bStringsAreEqual = true;
}
}
if (bStringsAreEqual)
{
BriefMessages[Index].Clear(true, false);
// Copy the new ones in
u32 CopyIndex = Index;
while (CopyIndex < (MAX_NUM_BRIEF_MESSAGES-1) && !BriefMessages[CopyIndex+1].IsEmpty())
{
BriefMessages[CopyIndex] = BriefMessages[CopyIndex+1];
CopyIndex++;
}
BriefMessages[CopyIndex].Clear(false, false);
if ( (Index == 0) && (!BriefMessages[0].IsEmpty()) )
{ // If there's a new message to display
BriefMessages[0].SetWhenStarted(fwTimer::GetTimeInMilliseconds());
BriefMessages[0].SetNewlyAddedToTheHeadOfTheQueue(true);
}
bAStringHasBeenCleared = true;
}
else
{
Index++;
}
}
}
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CMessages::IsThisMessageEmpty
// PURPOSE: returns whether this string is "empty" (pText is NULL, StringLength == 0, or pText contains only "~z~")
/////////////////////////////////////////////////////////////////////////////////////
bool CMessages::IsThisMessageEmpty(char const* pText)
{
bool bMessageEmpty = (pText == NULL);
if(pText)
{
s32 StringLength = CTextConversion::GetByteCount(pText);
if(StringLength == 0 ||
(StringLength == 3 && pText[0] == FONT_CHAR_TOKEN_DELIMITER && pText[1] == FONT_CHAR_TOKEN_DIALOGUE && pText[2] == FONT_CHAR_TOKEN_DELIMITER))
{
bMessageEmpty = true;
}
}
return bMessageEmpty;
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CMessages::DoesTextBeginWithThisControlCharacter
// PURPOSE: returns whether this string of text begins with the token passed in
/////////////////////////////////////////////////////////////////////////////////////
bool CMessages::DoesTextBeginWithThisControlCharacter(char const *pText, s32 TokenToSearchFor, s32 TokenToIgnore)
{
enum eSearchStringState
{
EXPECTING_OPENING_DELIMITER,
EXPECTING_CONTROL_CHARACTER,
EXPECTING_CLOSING_DELIMITER_AFTER_FINDING_TOKEN_TO_SEARCH_FOR,
EXPECTING_CLOSING_DELIMITER_AFTER_FINDING_TOKEN_TO_IGNORE
};
if (pText == NULL)
{
return true;
}
s32 StringLength = CTextConversion::GetByteCount(pText);
bool bContinueChecking = true;
s32 CharLoop = 0;
eSearchStringState State = EXPECTING_OPENING_DELIMITER;
while ( (CharLoop < StringLength) && bContinueChecking)
{
switch (State)
{
case EXPECTING_OPENING_DELIMITER :
if (pText[CharLoop] == FONT_CHAR_TOKEN_DELIMITER)
{
State = EXPECTING_CONTROL_CHARACTER;
}
else
{ // String does not begin with a control character so return false
bContinueChecking = false;
}
break;
case EXPECTING_CONTROL_CHARACTER :
if (pText[CharLoop] == TokenToSearchFor)
{ // Found ~TokenToSearchFor so now expect to find a closing delimiter
State = EXPECTING_CLOSING_DELIMITER_AFTER_FINDING_TOKEN_TO_SEARCH_FOR;
}
else if (pText[CharLoop] == TokenToIgnore)
{ // Found ~TokenToIgnore. Simon said this could happen so ignore it and continue looking for ~TokenToSearchFor~
State = EXPECTING_CLOSING_DELIMITER_AFTER_FINDING_TOKEN_TO_IGNORE;
}
else
{ // String does not begin with ~TokenToSearchFor~ or ~TokenToIgnore~ so return false
bContinueChecking = false;
}
break;
case EXPECTING_CLOSING_DELIMITER_AFTER_FINDING_TOKEN_TO_SEARCH_FOR :
if (pText[CharLoop] == FONT_CHAR_TOKEN_DELIMITER)
{ // Found a complete ~TokenToSearchFor~ so return true
return true;
}
else
{ // String does not begin with ~TokenToSearchFor~ so return false
bContinueChecking = false;
}
break;
case EXPECTING_CLOSING_DELIMITER_AFTER_FINDING_TOKEN_TO_IGNORE :
if (pText[CharLoop] == FONT_CHAR_TOKEN_DELIMITER)
{ // Found a complete ~TokenToIgnore~ so continue looking for ~TokenToSearchFor~
State = EXPECTING_OPENING_DELIMITER;
}
else
{ // String does not begin with ~TokenToIgnore~ so give up
bContinueChecking = false;
}
break;
}
CharLoop++;
}
// String does not begin with ~TokenToSearchFor~ so return false
return false;
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CMessages::ShouldThisMessageBeExcludedFromThePreviousBriefs
// PURPOSE: returns whether the text should be added to the brief or not
/////////////////////////////////////////////////////////////////////////////////////
bool CMessages::ShouldThisMessageBeExcludedFromThePreviousBriefs(char const *pText)
{
return (IsThisMessageEmpty(pText) || DoesTextBeginWithThisControlCharacter(pText, FONT_CHAR_TOKEN_EXCLUDE_FROM_PREVIOUS_BRIEFS, FONT_CHAR_TOKEN_DIALOGUE));
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CMessages::IsThisTextSwitchedOffByFrontendSubtitleSetting
// PURPOSE: If text begins with ~z~ then it won't be displayed if Subtitles are
// switched off
/////////////////////////////////////////////////////////////////////////////////////
bool CMessages::IsThisTextSwitchedOffByFrontendSubtitleSetting(char const *pText)
{ // If text begins with ~z~ then it won't be displayed if Subtitles are switched off
return (DoesTextBeginWithThisControlCharacter(pText, FONT_CHAR_TOKEN_DIALOGUE, FONT_CHAR_TOKEN_EXCLUDE_FROM_PREVIOUS_BRIEFS));
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CMessages::AddMessage
// PURPOSE: Adds one message to the Q to be displayed
/////////////////////////////////////////////////////////////////////////////////////
void CMessages::AddMessage(char const* pText, s32 TextBlock,
u32 Duration, bool bJumpQ, bool bAddToPrevBriefs, ePreviousBriefOverride PreviousBriefOverride,
CNumberWithinMessage *pArrayOfNumbers, u32 SizeOfNumberArray,
CSubStringWithinMessage *pArrayOfSubStrings, u32 SizeOfSubStringArray,
bool bUsesUnderscore, u32 iVoiceNameHash/*=0*/, u32 iMissionNameHash/*=0*/)
{
#if __ASSERT
char TestString[MAX_CHARS_IN_EXTENDED_MESSAGE];
InsertNumbersAndSubStringsIntoString(pText,
pArrayOfNumbers, SizeOfNumberArray,
pArrayOfSubStrings, SizeOfSubStringArray,
TestString, NELEM(TestString) );
#if __WIN32PC
InsertPlayerControlKeysInString(&TestString[0]);
#endif // __WIN32PC
u32 StringLength = CTextConversion::GetByteCount(TestString);
Assertf(StringLength < MAX_CHARS_IN_EXTENDED_MESSAGE, "CMessages::AddMessage - string is too long");
#endif // __ASSERT
if (ShouldThisMessageBeExcludedFromThePreviousBriefs(pText))
{
bAddToPrevBriefs = false;
PreviousBriefOverride = PREVIOUS_BRIEF_NO_OVERRIDE;
}
s32 Index = 0;
if (!bJumpQ)
{ // Find the next empty one.
while (Index < (MAX_NUM_BRIEF_MESSAGES) && !BriefMessages[Index].IsEmpty())
{
Index++;
}
}
if (Index < MAX_NUM_BRIEF_MESSAGES)
{ // Add this one
BriefMessages[Index].Clear(true, false);
BriefMessages[Index].Set(pText, TextBlock,
Duration, bAddToPrevBriefs, PreviousBriefOverride,
pArrayOfNumbers, SizeOfNumberArray,
pArrayOfSubStrings, SizeOfSubStringArray,
bUsesUnderscore, iVoiceNameHash, iMissionNameHash);
if (Index == 0)
{ // Add the new message to the previous brief array when it is first displayed
BriefMessages[0].SetNewlyAddedToTheHeadOfTheQueue(true);
}
}
}
// ******************************************************************************************************
// ** CPreviousHelpMessages
// ******************************************************************************************************
/*
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CPreviousHelpMessages::Init
// PURPOSE:
/////////////////////////////////////////////////////////////////////////////////////
void CPreviousHelpMessages::Init(s32 MaxSizeOfArray)
{
Assertf(m_pArrayOfPreviousMessages == NULL, "CPreviousMessages::Init - array of previous messages has already been allocated");
m_MaxNumberOfPreviousMessages = MaxSizeOfArray;
m_pArrayOfPreviousMessages = rage_new CHelpMessageText[m_MaxNumberOfPreviousMessages];
Assert(m_pArrayOfPreviousMessages);
ClearAllPreviousBriefs();
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CPreviousHelpMessages::Shutdown
// PURPOSE:
/////////////////////////////////////////////////////////////////////////////////////
void CPreviousHelpMessages::Shutdown()
{
if (m_pArrayOfPreviousMessages)
{
delete[] m_pArrayOfPreviousMessages;
m_pArrayOfPreviousMessages = NULL;
}
m_MaxNumberOfPreviousMessages = 0;
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CPreviousHelpMessages::ClearAllPreviousBriefs
// PURPOSE:
/////////////////////////////////////////////////////////////////////////////////////
void CPreviousHelpMessages::ClearAllPreviousBriefs()
{
Assertf(m_pArrayOfPreviousMessages, "CPreviousHelpMessages::ClearAllPreviousBriefs - array of previous messages has not been allocated");
if (!m_pArrayOfPreviousMessages)
return;
for (s32 loop = 0; loop < m_MaxNumberOfPreviousMessages; loop++)
{
m_pArrayOfPreviousMessages[loop].Clear();
}
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CPreviousHelpMessages::AddBrief
// PURPOSE: adds message
/////////////////////////////////////////////////////////////////////////////////////
void CPreviousHelpMessages::AddBrief(char *pMessage)
{
Assertf(m_pArrayOfPreviousMessages, "CPreviousHelpMessages::AddBrief - array of previous messages has not been allocated");
if (!m_pArrayOfPreviousMessages)
return;
// find our current number of previous brief messages:
s32 NumberOfPreviousBriefs = m_MaxNumberOfPreviousMessages-1;
while ( (NumberOfPreviousBriefs > 0) && (m_pArrayOfPreviousMessages[NumberOfPreviousBriefs].cHelpMessage[0] != '\0') )
{
NumberOfPreviousBriefs--;
}
if (NumberOfPreviousBriefs > 0) // we have some previous messages, so lets readjust the list so we can add the new one at the top
{
s32 copy_loop = (NumberOfPreviousBriefs - 1);
if (NumberOfPreviousBriefs == m_MaxNumberOfPreviousMessages)
{ // array of previous briefs is full - need to clear any literal strings for the final one (about to drop off the end of the array)
m_pArrayOfPreviousMessages[copy_loop].Clear();
copy_loop -= 1;
}
for (; copy_loop >= 0; copy_loop--)
{
m_pArrayOfPreviousMessages[copy_loop + 1] = m_pArrayOfPreviousMessages[copy_loop];
}
}
CTextConversion::charStrcpy(m_pArrayOfPreviousMessages[0].cHelpMessage, pMessage, MAX_CHARS_IN_MESSAGE);
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CPreviousHelpMessages::FillStringWithPreviousMessage
// PURPOSE: copies a previous brief message into a string
/////////////////////////////////////////////////////////////////////////////////////
bool CPreviousHelpMessages::FillStringWithPreviousMessage(s32 IndexOfString, char *pGxtStringToFill, u32 MaxLengthOfString)
{
Assertf(m_pArrayOfPreviousMessages, "CPreviousMessages::FillStringWithPreviousMessage - array of previous messages has not been allocated");
if (!m_pArrayOfPreviousMessages)
return false;
Assertf(IndexOfString >= 0, "CPreviousMessages::FillStringWithPreviousMessage - array index is negative");
Assertf(IndexOfString < m_MaxNumberOfPreviousMessages, "CPreviousMessages::FillStringWithPreviousMessage - array index is too large");
if ( (IndexOfString < 0) || (IndexOfString >= m_MaxNumberOfPreviousMessages) )
{
return false;
}
CTextConversion::charStrcpy(pGxtStringToFill, GetMessageText(IndexOfString), MaxLengthOfString);
return true;
}
*/
// ******************************************************************************************************
// ** CPreviousMessages
// ******************************************************************************************************
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CPreviousMessages::Init
// PURPOSE: Inits previous brief
/////////////////////////////////////////////////////////////////////////////////////
void CPreviousMessages::Init(s32 MaxSizeOfArray)
{
Assertf(m_pArrayOfPreviousMessages == NULL, "CPreviousMessages::Init - array of previous messages has already been allocated");
m_MaxNumberOfPreviousMessages = MaxSizeOfArray;
m_pArrayOfPreviousMessages = rage_new CSubtitleMessage[m_MaxNumberOfPreviousMessages];
Assert(m_pArrayOfPreviousMessages);
for (s32 loop = 0; loop < m_MaxNumberOfPreviousMessages; loop++)
{
m_pArrayOfPreviousMessages[loop].Clear(false, false);
}
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CPreviousMessages::Shutdown
// PURPOSE: shutdown of the brief messages
/////////////////////////////////////////////////////////////////////////////////////
void CPreviousMessages::Shutdown()
{
if (m_pArrayOfPreviousMessages)
{
delete[] m_pArrayOfPreviousMessages;
m_pArrayOfPreviousMessages = NULL;
}
m_MaxNumberOfPreviousMessages = 0;
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CPreviousMessages::ClearAllPreviousBriefs
// PURPOSE: clear the contents of all previous briefs
/////////////////////////////////////////////////////////////////////////////////////
void CPreviousMessages::ClearAllPreviousBriefs()
{
Assertf(m_pArrayOfPreviousMessages, "CPreviousMessages::ClearAllPreviousBriefs - array of previous messages has not been allocated");
if (!m_pArrayOfPreviousMessages)
return;
for (s32 loop = 0; loop < m_MaxNumberOfPreviousMessages; loop++)
{
m_pArrayOfPreviousMessages[loop].Clear(true, true);
}
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CPreviousMessages::ClearAllPreviousBriefsBelongingToThisTextBlock
// PURPOSE: clear the contents of all previous briefs if it matches the text block
/////////////////////////////////////////////////////////////////////////////////////
void CPreviousMessages::ClearAllPreviousBriefsBelongingToThisTextBlock(s32 TextBlock)
{
Assertf(m_pArrayOfPreviousMessages, "CPreviousMessages::ClearAllPreviousBriefsBelongingToThisTextBlock - array of previous messages has not been allocated");
if (!m_pArrayOfPreviousMessages)
return;
for (s32 loop = 0; loop < m_MaxNumberOfPreviousMessages; loop++)
{
if (m_pArrayOfPreviousMessages[loop].UsesTextFromThisBlock(TextBlock))
{
m_pArrayOfPreviousMessages[loop].Clear(true, true);
}
}
// Previous Briefs - shift up any remaining messages to fill the gaps
s32 CopyToIndex = 0;
s32 CopyFromIndex = 0;
while (CopyToIndex < m_MaxNumberOfPreviousMessages)
{
if (m_pArrayOfPreviousMessages[CopyToIndex].IsEmpty())
{
CopyFromIndex = (CopyToIndex + 1);
while ( (CopyFromIndex < m_MaxNumberOfPreviousMessages) && (m_pArrayOfPreviousMessages[CopyFromIndex].IsEmpty()) )
{
CopyFromIndex++;
}
if (CopyFromIndex < m_MaxNumberOfPreviousMessages)
{
m_pArrayOfPreviousMessages[CopyToIndex] = m_pArrayOfPreviousMessages[CopyFromIndex];
m_pArrayOfPreviousMessages[CopyFromIndex].Clear(false, false);
}
}
CopyToIndex++;
}
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CPreviousMessages::AddBrief
// PURPOSE: adds message to the brief
/////////////////////////////////////////////////////////////////////////////////////
void CPreviousMessages::AddBrief(CSubtitleMessage &SubtitleMessage)
{
Assertf(m_pArrayOfPreviousMessages, "CPreviousMessages::AddBrief - array of previous messages has not been allocated");
if (!m_pArrayOfPreviousMessages)
return;
// find our current number of previous brief messages:
s32 NumberOfPreviousBriefs = 0;
while ( (NumberOfPreviousBriefs < m_MaxNumberOfPreviousMessages) && !m_pArrayOfPreviousMessages[NumberOfPreviousBriefs].IsEmpty() )
{
if (m_pArrayOfPreviousMessages[NumberOfPreviousBriefs].Matches(SubtitleMessage))
{
return;
}
NumberOfPreviousBriefs++;
}
if (NumberOfPreviousBriefs > 0) // we have some previous messages, so lets readjust the list so we can add the new one at the top
{
s32 copy_loop = (NumberOfPreviousBriefs - 1);
if (NumberOfPreviousBriefs == m_MaxNumberOfPreviousMessages)
{ // array of previous briefs is full - need to clear any literal strings for the final one (about to drop off the end of the array)
m_pArrayOfPreviousMessages[copy_loop].Clear(true, true);
copy_loop -= 1;
}
for (; copy_loop >= 0; copy_loop--)
{
m_pArrayOfPreviousMessages[copy_loop + 1] = m_pArrayOfPreviousMessages[copy_loop];
}
}
m_pArrayOfPreviousMessages[0] = SubtitleMessage;
m_pArrayOfPreviousMessages[0].SetAllLiteralStringsOccurInPreviousBriefs();
}
bool CPreviousMessages::IsPreviousMessageEmpty(s32 IndexOfString)
{
Assertf(m_pArrayOfPreviousMessages, "CPreviousMessages::IsPreviousMessageEmpty - array of previous messages has not been allocated");
if (!m_pArrayOfPreviousMessages)
return true;
Assertf(IndexOfString >= 0, "CPreviousMessages::IsPreviousMessageEmpty - array index is negative");
Assertf(IndexOfString < m_MaxNumberOfPreviousMessages, "CPreviousMessages::IsPreviousMessageEmpty - array index is too large");
if ( (IndexOfString < 0) || (IndexOfString >= m_MaxNumberOfPreviousMessages) )
{
return true;
}
if(!m_pArrayOfPreviousMessages[IndexOfString].IsEmpty())
{
return false;
}
return true;
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CPreviousMessages::FillStringWithPreviousMessage
// PURPOSE: copies a previous brief message into a string
/////////////////////////////////////////////////////////////////////////////////////
bool CPreviousMessages::FillStringWithPreviousMessage(s32 IndexOfString, char *pGxtStringToFill, u32 MaxLengthOfString)
{
Assertf(m_pArrayOfPreviousMessages, "CPreviousMessages::FillStringWithPreviousMessage - array of previous messages has not been allocated");
if (!m_pArrayOfPreviousMessages)
return false;
Assertf(IndexOfString >= 0, "CPreviousMessages::FillStringWithPreviousMessage - array index is negative");
Assertf(IndexOfString < m_MaxNumberOfPreviousMessages, "CPreviousMessages::FillStringWithPreviousMessage - array index is too large");
if ( (IndexOfString < 0) || (IndexOfString >= m_MaxNumberOfPreviousMessages) )
{
return false;
}
if(!m_pArrayOfPreviousMessages[IndexOfString].IsEmpty())
{
m_pArrayOfPreviousMessages[IndexOfString].FillInString(pGxtStringToFill, MaxLengthOfString);
return true;
}
return false;
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CPreviousMessages::GetPreviousMessageCharacterTXD
// PURPOSE: returns the previous message's character txd
/////////////////////////////////////////////////////////////////////////////////////
const char* CPreviousMessages::GetPreviousMessageCharacterTXD(s32 IndexOfMsg)
{
Assertf(m_pArrayOfPreviousMessages, "CPreviousMessages::GetPreviousMessageCharacterTXD - array of previous messages has not been allocated");
if (!m_pArrayOfPreviousMessages)
return DEFAULT_CHARACTER_TXD;
Assertf(IndexOfMsg >= 0, "CPreviousMessages::GetPreviousMessageCharacterTXD - array index is negative");
Assertf(IndexOfMsg < m_MaxNumberOfPreviousMessages, "CPreviousMessages::GetPreviousMessageCharacterTXD - array index is too large");
if ( (IndexOfMsg < 0) || (IndexOfMsg >= m_MaxNumberOfPreviousMessages) )
{
return DEFAULT_CHARACTER_TXD;
}
if(!m_pArrayOfPreviousMessages[IndexOfMsg].IsEmpty())
{
u32 iVoiceNameHash = m_pArrayOfPreviousMessages[IndexOfMsg].GetVoiceNameHash();
u32 iMissionNameHash = m_pArrayOfPreviousMessages[IndexOfMsg].GetMissionNameHash();
return CMessages::ms_dialogueCharacters.GetCharacterTXD(iVoiceNameHash, iMissionNameHash);
}
return DEFAULT_CHARACTER_TXD;
}
/////////////////////////////////////////////////////////////////////////////////
// FUNCTION : CMessages::AddToPreviousBriefArray
// PURPOSE : Adds a message as the first previous message. Any messages already stored
// are moved back one space to accommodate the new one. Only MAX_NUM_PREVIOUS_BRIEFS
// previous messages are remembered
/////////////////////////////////////////////////////////////////////////////////
void CMessages::AddToPreviousBriefArray(CSubtitleMessage &SubtitleMessage, ePreviousBriefOverride PreviousBriefOverride)
{
bool bAddToPreviousBriefs = false;
bool bAddToDialogueBriefs = false;
bool bAddToHelpTextBriefs = false;
SubtitleMessage.DetermineWhichPreviousBriefArrayToUse(bAddToPreviousBriefs, bAddToDialogueBriefs, bAddToHelpTextBriefs);
// If bAddToPreviousBriefs is false at this stage then I think it's safer to leave it as false
// even if PreviousBriefOverride has a "force" value
switch (PreviousBriefOverride)
{
case PREVIOUS_BRIEF_NO_OVERRIDE :
break;
case PREVIOUS_BRIEF_FORCE_DIALOGUE :
bAddToDialogueBriefs = true;
break;
case PREVIOUS_BRIEF_FORCE_GOD_TEXT :
bAddToDialogueBriefs = false;
break;
}
if (bAddToPreviousBriefs)
{
if (bAddToDialogueBriefs)
{
PreviousDialogueMessages.AddBrief(SubtitleMessage);
}
else if (bAddToHelpTextBriefs)
{
PreviousHelpTextMessages.AddBrief(SubtitleMessage);
}
else
{
PreviousMissionMessages.AddBrief(SubtitleMessage);
}
}
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CMessages::ClearAllPreviousBriefsThatBelongToThisTextBlock
// PURPOSE: clears any brief that belongs to the passed text block
/////////////////////////////////////////////////////////////////////////////////////
void CMessages::ClearAllPreviousBriefsThatBelongToThisTextBlock(s32 TextBlock)
{
PreviousDialogueMessages.ClearAllPreviousBriefsBelongingToThisTextBlock(TextBlock);
PreviousMissionMessages.ClearAllPreviousBriefsBelongingToThisTextBlock(TextBlock);
PreviousHelpTextMessages.ClearAllPreviousBriefsBelongingToThisTextBlock(TextBlock);
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CMessages::GetMaxNumberOfPreviousMessages
// PURPOSE: returns the number of previous messages in use
/////////////////////////////////////////////////////////////////////////////////////
s32 CMessages::GetMaxNumberOfPreviousMessages(ePreviousMessageTypes iMessageType)
{
switch (iMessageType)
{
case PREV_MESSAGE_TYPE_DIALOG:
return PreviousDialogueMessages.GetMaxNumberOfMessages();
case PREV_MESSAGE_TYPE_MISSION:
return PreviousMissionMessages.GetMaxNumberOfMessages();
case PREV_MESSAGE_TYPE_HELP:
return PreviousHelpTextMessages.GetMaxNumberOfMessages();
}
return 0;
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CMessages::FillStringWithPreviousMessage
// PURPOSE: fills the passed string with a brief message
/////////////////////////////////////////////////////////////////////////////////////
bool CMessages::FillStringWithPreviousMessage(ePreviousMessageTypes iMessageType, s32 IndexOfString, char *pGxtStringToFill, u32 MaxLengthOfStringToFill)
{
switch (iMessageType)
{
case PREV_MESSAGE_TYPE_DIALOG:
return PreviousDialogueMessages.FillStringWithPreviousMessage(IndexOfString, pGxtStringToFill, MaxLengthOfStringToFill);
case PREV_MESSAGE_TYPE_MISSION:
return PreviousMissionMessages.FillStringWithPreviousMessage(IndexOfString, pGxtStringToFill, MaxLengthOfStringToFill);
case PREV_MESSAGE_TYPE_HELP:
return PreviousHelpTextMessages.FillStringWithPreviousMessage(IndexOfString, pGxtStringToFill, MaxLengthOfStringToFill);
}
return false;
}
bool CMessages::IsPreviousMessageEmpty(ePreviousMessageTypes iMessageType, s32 IndexOfString)
{
switch (iMessageType)
{
case PREV_MESSAGE_TYPE_DIALOG:
return PreviousDialogueMessages.IsPreviousMessageEmpty(IndexOfString);
case PREV_MESSAGE_TYPE_MISSION:
return PreviousMissionMessages.IsPreviousMessageEmpty(IndexOfString);
case PREV_MESSAGE_TYPE_HELP:
return PreviousHelpTextMessages.IsPreviousMessageEmpty(IndexOfString);
}
return true;
}
const char* CMessages::GetPreviousMessageCharacterTXD(s32 IndexOfMsg)
{
return PreviousDialogueMessages.GetPreviousMessageCharacterTXD(IndexOfMsg);
}
// ******************************************************************************************************
// ** CHelpMessage
// ******************************************************************************************************
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CHelpMessage::Init
// PURPOSE: initialises some variables
/////////////////////////////////////////////////////////////////////////////////////
void CHelpMessage::Init()
{
for (s32 i = 0; i < MAX_HELP_TEXT_SLOTS; i++)
{
m_HelpMessageText[i][0] = 0;
m_bHelpMessagesFading[i] = false;
m_iNewText[i] = HELP_TEXT_STATE_NONE;
m_vScreenPosition[i].x = -9999.0f;
m_vScreenPosition[i].y = -9999.0f;
m_iArrowDirection[i] = HELP_TEXT_ARROW_FORCE_RESET;
m_iFloatingTextOffset[i] = 0;
m_iStyle[i] = HELP_TEXT_STYLE_NORMAL;
m_iColour[i] = CRGBA(0,0,0,214);
m_bTriggerSound[i] = false;
m_OverrideDuration[i] = -1;
}
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CHelpMessage::SetMessageText
// PURPOSE: sets the content of the help message text
/////////////////////////////////////////////////////////////////////////////////////
void CHelpMessage::SetMessageText(s32 iId, const char *pHelpMessage,
const CNumberWithinMessage *pArrayOfNumbers, u32 SizeOfNumberArray,
const CSubStringWithinMessage *pArrayOfSubStrings, u32 SizeOfSubStringArray,
bool bPlaySound, bool bDisplayForever, s32 OverrideDuration, bool WIN32PC_ONLY(bIME))
{
if (CSystem::IsThisThreadId(SYS_THREAD_RENDER))
{
AssertMsg(0, "CHelpMessage::SetMessageText only allowed to be called on UPDATE thread");
return;
}
#if RSG_PC
if(ioKeyboard::ImeIsInProgress() && !bIME)
return;
#endif // RSG_PC
if (uiVerifyf(iId < MAX_HELP_TEXT_SLOTS, "CHelpMessage: Invalid Help Message Slot Passed: %d", iId))
{
if (!pHelpMessage)
{
if (DoesMessageTextExist(iId))
{
Clear(iId, true);
}
}
else
{
char NewString[MAX_CHARS_IN_EXTENDED_MESSAGE];
CMessages::InsertNumbersAndSubStringsIntoString(pHelpMessage,
pArrayOfNumbers, SizeOfNumberArray,
pArrayOfSubStrings, SizeOfSubStringArray,
NewString, NELEM(NewString));
#if __WIN32PC
CMessages::InsertPlayerControlKeysInString(&NewString[0]);
#endif // #if __WIN32PC
#if __ASSERT
s32 StringLength = CTextConversion::GetByteCount(NewString);
Assertf(StringLength < MAX_CHARS_IN_MESSAGE, "CHelpMessage::SetMessageText - string is too long");
#endif
if ( (CNewHud::GetHelpTextWaitingForActionScriptResponse(iId)) || ( strcmp(NewString, m_HelpMessageText[iId]) != 0 ) )
{
safecpy( m_HelpMessageText[iId], NewString);
m_iNewText[iId] = HELP_TEXT_STATE_NEW;
m_bHelpMessagesFading[iId] = false;
m_vScreenPosition[iId].x = -9999.0f;
m_vScreenPosition[iId].y = -9999.0f;
m_iArrowDirection[iId] = HELP_TEXT_ARROW_FORCE_RESET;
m_iFloatingTextOffset[iId] = 0;
m_iStyle[iId] = HELP_TEXT_STYLE_NORMAL;
m_iColour[iId] = CRGBA(0,0,0,214);
m_bTriggerSound[iId] = bPlaySound;
m_OverrideDuration[iId] = OverrideDuration;
// will ignore any CLEAR_HELP_TEXT callbacks that may come in from a previous CLEAR as we have set the help text again
CNewHud::SetHelpTextWaitingForActionScriptResponse(iId, false);
}
else
{
/*#if __ASSERT
//
// simple check to catch any instances where help text is set 2 frames in a row.
// this is a waste
//
static u32 iPreviousFrameHelpSet = 0;
if (iPreviousFrameHelpSet == GRCDEVICE.GetFrameCounter()-1)
{
uiAssertf(0, "Help text shouldn't be called every frame - use the FOREVER param and CLEAR instead");
}
iPreviousFrameHelpSet = GRCDEVICE.GetFrameCounter();
#endif // __ASSERT*/
m_iNewText[iId] = HELP_TEXT_STATE_SAME;
m_bTriggerSound[iId] = false;
}
if (bDisplayForever)
{
m_iNewText[iId] = HELP_TEXT_STATE_FOREVER;
}
}
}
}
void CHelpMessage::SetMessageTextAndAddToBrief(s32 iId, const char *pHelpMessageLabel,
CNumberWithinMessage *pArrayOfNumbers, u32 SizeOfNumberArray,
CSubStringWithinMessage *pArrayOfSubStrings, u32 SizeOfSubStringArray,
bool bPlaySound, bool bDisplayForever, s32 OverrideDuration)
{
char *pHelpMessage = TheText.Get(pHelpMessageLabel);
s32 MainTextBlock = TheText.GetBlockContainingLastReturnedString();
CHelpMessage::SetMessageText(iId, pHelpMessage,
pArrayOfNumbers, SizeOfNumberArray,
pArrayOfSubStrings, SizeOfSubStringArray,
bPlaySound, bDisplayForever, OverrideDuration);
CSubtitleMessage PreviousBrief;
PreviousBrief.Set(pHelpMessage, MainTextBlock,
pArrayOfNumbers, SizeOfNumberArray,
pArrayOfSubStrings, SizeOfSubStringArray,
false, true);
CMessages::AddToPreviousBriefArray(PreviousBrief, CScriptHud::GetNextMessagePreviousBriefOverride());
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CHelpMessage::SetScreenPosition
// PURPOSE: sets the position of the help text on the screen
/////////////////////////////////////////////////////////////////////////////////////
void CHelpMessage::SetScreenPosition(s32 iId, Vector2 vScreenPos)
{
if (uiVerifyf(iId < MAX_HELP_TEXT_SLOTS, "CHelpMessage: Invalid Help Message Slot Passed: %d", iId))
{
m_vScreenPosition[iId] = vScreenPos;
if (m_vScreenPosition[iId].x > 1.0f-OFFSET_FROM_EDGE_OF_SCREEN)
m_vScreenPosition[iId].x = 1.0f-OFFSET_FROM_EDGE_OF_SCREEN;
if (m_vScreenPosition[iId].x < OFFSET_FROM_EDGE_OF_SCREEN)
m_vScreenPosition[iId].x = OFFSET_FROM_EDGE_OF_SCREEN;
if (m_vScreenPosition[iId].y > 1.0f-OFFSET_FROM_EDGE_OF_SCREEN)
m_vScreenPosition[iId].y = 1.0f-OFFSET_FROM_EDGE_OF_SCREEN;
if (m_vScreenPosition[iId].y < OFFSET_FROM_EDGE_OF_SCREEN)
m_vScreenPosition[iId].y = OFFSET_FROM_EDGE_OF_SCREEN;
}
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CHelpMessage::SetWorldPosition
// PURPOSE: sets the position of the help text in the world and onto the screen
/////////////////////////////////////////////////////////////////////////////////////
void CHelpMessage::SetWorldPosition(s32 iId, Vector3::Vector3Param vWorldPositon)
{
if (uiVerifyf(iId < MAX_HELP_TEXT_SLOTS, "CHelpMessage: Invalid Help Message Slot Passed: %d", iId))
{
Vector3 vWorldPositionV(vWorldPositon);
eTEXT_ARROW_ORIENTATION iArrowDir;
SetScreenPosition(iId, CHudTools::GetHudPosFromWorldPos(vWorldPositionV, &iArrowDir));
}
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CHelpMessage::SetWorldPositionFromEntity
// PURPOSE: sets the position of the help text in the world from an entity pos and
// to a screen pos
/////////////////////////////////////////////////////////////////////////////////////
void CHelpMessage::SetWorldPositionFromEntity(s32 iId, CPhysical* pEntity, Vector2 vOffset)
{
if (pEntity)
{
if (pEntity->GetIsTypePed())
{
SetWorldPosition(iId, VEC3V_TO_VECTOR3(pEntity->GetTransform().GetPosition()));
}
else if (pEntity->GetIsTypeVehicle())
{
CPed *pDriverPed = static_cast<CVehicle*>(pEntity)->GetDriver();
if (pDriverPed)
{
SetWorldPosition(iId, VEC3V_TO_VECTOR3(pDriverPed->GetTransform().GetPosition()));
}
else
{
SetWorldPosition(iId, VEC3V_TO_VECTOR3(pEntity->GetTransform().GetPosition()));
}
}
else if (pEntity->GetIsTypeObject())
{
SetWorldPosition(iId, VEC3V_TO_VECTOR3(pEntity->GetTransform().GetPosition()));
}
// apply the offset and pass it to be the screen pos
Vector2 vScreenPosWithOffset = (GetScreenPosition(iId) + vOffset);
SetScreenPosition(iId, vScreenPosWithOffset);
}
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CHelpMessage::SetStyle
// PURPOSE: sets the style of this help text
/////////////////////////////////////////////////////////////////////////////////////
void CHelpMessage::SetStyle(s32 iId, s32 iNewStyle, s32 iNewArrowDirection, s32 iFloatingTextOffset, s32 iHudColour, s32 iAlpha)
{
if (uiVerifyf(iId < MAX_HELP_TEXT_SLOTS, "CHelpMessage: Invalid Help Message Slot Passed: %d", iId))
{
m_iStyle[iId] = (eTEXT_STYLE)iNewStyle;
m_iArrowDirection[iId] = (eTEXT_ARROW_ORIENTATION)iNewArrowDirection;
m_iFloatingTextOffset[iId] = iFloatingTextOffset;
if (iAlpha >= 0 && iAlpha <= 255)
{
m_iColour[iId] = CHudColour::GetRGB((eHUD_COLOURS)iHudColour, (u8)iAlpha);
}
else
{
m_iColour[iId] = CHudColour::GetRGBA((eHUD_COLOURS)iHudColour);
}
}
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CHelpMessage::Clear
// PURPOSE: flags to clear the help message
/////////////////////////////////////////////////////////////////////////////////////
void CHelpMessage::Clear(s32 iId, bool bClearNow)
{
if (CSystem::IsThisThreadId(SYS_THREAD_RENDER))
{
AssertMsg(0, "CHelpMessage::Clear only allowed to be called on UPDATE thread");
return;
}
if (uiVerifyf(iId < MAX_HELP_TEXT_SLOTS, "CHelpMessage: Invalid Help Message Slot Passed: %d", iId))
{
if (m_HelpMessageText[iId][0] != 0)
{
m_bTriggerSound[iId] = false;
m_bHelpMessagesFading[iId] = !bClearNow;
CNewHud::ClearHelpText(iId, bClearNow);
}
}
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CHelpMessage::ClearAll
// PURPOSE: clears all help messages
/////////////////////////////////////////////////////////////////////////////////////
void CHelpMessage::ClearAll()
{
for (s32 i = 0; i < MAX_HELP_TEXT_SLOTS; i++)
{
Clear(i, true);
}
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CHelpMessage::ClearMessage
// PURPOSE: clears the actual help message content
/////////////////////////////////////////////////////////////////////////////////////
void CHelpMessage::ClearMessage(s32 iId)
{
if (uiVerifyf(iId < MAX_HELP_TEXT_SLOTS, "CHelpMessage: Invalid Help Message Slot Passed: %d", iId))
{
m_HelpMessageText[iId][0] = 0;
m_bHelpMessagesFading[iId] = false;
m_iNewText[iId] = HELP_TEXT_STATE_NONE;
}
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CHelpMessage::GetMessageText
// PURPOSE: returns the content of the help message text
/////////////////////////////////////////////////////////////////////////////////////
char *CHelpMessage::GetMessageText(s32 iId)
{
if (CSystem::IsThisThreadId(SYS_THREAD_RENDER))
{
AssertMsg(0, "CHelpMessage::GetMessageText only allowed to be called on UPDATE thread");
return NULL;
}
if (uiVerifyf(iId < MAX_HELP_TEXT_SLOTS, "CHelpMessage: Invalid Help Message Slot Passed: %d", iId))
{
return &m_HelpMessageText[iId][0];
}
else
{
return NULL;
}
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CHelpMessage::DoesMessageTextExist
// PURPOSE: returns whether a help message is currently set or not
/////////////////////////////////////////////////////////////////////////////////////
bool CHelpMessage::DoesMessageTextExist(s32 iId)
{
if (!CSystem::IsThisThreadId(SYS_THREAD_UPDATE))
{
AssertMsg(0, "CHelpMessage::GetMessageText only allowed to be called on UPDATE thread");
return false;
}
if (uiVerifyf(iId < MAX_HELP_TEXT_SLOTS, "CHelpMessage: Invalid Help Message Slot Passed: %d", iId))
{
return (m_HelpMessageText[iId][0] != 0);
}
else
{
return false;
}
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CHelpMessage::IsMessageFadingOut
// PURPOSE: returns whether a help message is currently fading out
/////////////////////////////////////////////////////////////////////////////////////
bool CHelpMessage::IsMessageFadingOut(s32 iId)
{
if (!CSystem::IsThisThreadId(SYS_THREAD_UPDATE))
{
AssertMsg(0, "CHelpMessage::GetMessageText only allowed to be called on UPDATE thread");
return false;
}
if (uiVerifyf(iId < MAX_HELP_TEXT_SLOTS, "CHelpMessage: Invalid Help Message Slot Passed: %d", iId))
{
return (m_HelpMessageText[iId][0] != 0) && m_bHelpMessagesFading[iId];
}
else
{
return false;
}
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CHelpMessage::IsHelpTextNew
// PURPOSE: returns whether the help text is new
/////////////////////////////////////////////////////////////////////////////////////
bool CHelpMessage::IsHelpTextNew(s32 iId)
{
if (uiVerifyf(iId < MAX_HELP_TEXT_SLOTS, "CHelpMessage: Invalid Help Message Slot Passed: %d", iId))
{
return (m_iNewText[iId] == HELP_TEXT_STATE_NEW);
}
else
{
return false;
}
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CHelpMessage::IsHelpTextSame
// PURPOSE: returns whether the help text is the same as the previous frame
/////////////////////////////////////////////////////////////////////////////////////
bool CHelpMessage::IsHelpTextSame(s32 iId)
{
if (uiVerifyf(iId < MAX_HELP_TEXT_SLOTS, "CHelpMessage: Invalid Help Message Slot Passed: %d", iId))
{
return (m_iNewText[iId] == HELP_TEXT_STATE_SAME);
}
else
{
return false;
}
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CHelpMessage::IsHelpTextForever
// PURPOSE: returns whether the help text is to display forever
/////////////////////////////////////////////////////////////////////////////////////
bool CHelpMessage::IsHelpTextForever(s32 iId)
{
if (uiVerifyf(iId < MAX_HELP_TEXT_SLOTS, "CHelpMessage: Invalid Help Message Slot Passed: %d", iId))
{
return (m_iNewText[iId] == HELP_TEXT_STATE_FOREVER);
}
else
{
return false;
}
}
bool CHelpMessage::WasHelpTextForever(s32 iId)
{
if (uiVerifyf(iId < MAX_HELP_TEXT_SLOTS, "CHelpMessage: Invalid Help Message Slot Passed: %d", iId))
{
return (m_iNewText[iId] == HELP_TEXT_STATE_FOREVER_IDLE);
}
else
{
return false;
}
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CHelpMessage::SetHelpMessageAsProcessed
// PURPOSE: flags the help message as processed - called after the scaleform movie
// has been updated with the content
/////////////////////////////////////////////////////////////////////////////////////
void CHelpMessage::SetHelpMessageAsProcessed(s32 iId)
{
if (uiVerifyf(iId < MAX_HELP_TEXT_SLOTS, "CHelpMessage: Invalid Help Message Slot Passed: %d", iId))
{
if(IsHelpTextForever(iId))
{
m_iNewText[iId] = HELP_TEXT_STATE_FOREVER_IDLE;
}
else
{
m_iNewText[iId] = HELP_TEXT_STATE_IDLE;
}
}
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CHelpMessage::GetDirectionHelpText
// PURPOSE: returns the direction of the help text
/////////////////////////////////////////////////////////////////////////////////////
eTEXT_ARROW_ORIENTATION CHelpMessage::GetDirectionHelpText(s32 iId, Vector2 vNewPosition)
{
eTEXT_ARROW_ORIENTATION iDirectionOffScreen = HELP_TEXT_ARROW_NORMAL;
if (uiVerifyf(iId < MAX_HELP_TEXT_SLOTS, "CHelpMessage: Invalid Help Message Slot Passed: %d", iId))
{
// work out whether the new position of the help text is offscreen
if (vNewPosition.x >= 1.0f-OFFSET_FROM_EDGE_OF_SCREEN)
iDirectionOffScreen = HELP_TEXT_ARROW_EAST;
else if (vNewPosition.x <= OFFSET_FROM_EDGE_OF_SCREEN)
iDirectionOffScreen = HELP_TEXT_ARROW_WEST;
else if (vNewPosition.y >= 1.0f-OFFSET_FROM_EDGE_OF_SCREEN)
iDirectionOffScreen = HELP_TEXT_ARROW_SOUTH;
else if (vNewPosition.y <= OFFSET_FROM_EDGE_OF_SCREEN)
iDirectionOffScreen = HELP_TEXT_ARROW_NORTH;
}
return iDirectionOffScreen;
}
// ******************************************************************************************************
// ** CSubtitleText
// ******************************************************************************************************
char CSubtitleText::m_SubtitleMessageText[MAX_CHARS_IN_EXTENDED_MESSAGE];
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CSubtitleText::Init
// PURPOSE: initialises some variables
/////////////////////////////////////////////////////////////////////////////////////
void CSubtitleText::Init()
{
m_SubtitleMessageText[0] = 0;
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CSubtitleText::SetMessageText
// PURPOSE: stores the new text and sets it up in Scaleform if its different
/////////////////////////////////////////////////////////////////////////////////////
void CSubtitleText::SetMessageText(char *pText)
{
if ( strcmp(m_SubtitleMessageText, pText) != 0 )
{
safecpy(m_SubtitleMessageText, pText);
CNewHud::SetSubtitleText(pText);
}
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CSubtitleText::ClearMessage
// PURPOSE: clears the text
/////////////////////////////////////////////////////////////////////////////////////
void CSubtitleText::ClearMessage()
{
if (m_SubtitleMessageText[0] != 0)
{
m_SubtitleMessageText[0] = 0;
CNewHud::ClearSubtitleText();
}
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CSubtitleText::GetMessageText
// PURPOSE: returns the content of the subtitle text
/////////////////////////////////////////////////////////////////////////////////////
char *CSubtitleText::GetMessageText()
{
if (CSystem::IsThisThreadId(SYS_THREAD_RENDER))
{
AssertMsg(0, "CSubtitleText::GetMessageText only allowed to be called on UPDATE thread");
return NULL;
}
return &m_SubtitleMessageText[0];
}
// ******************************************************************************************************
// ** CSavingGameMessage
// ******************************************************************************************************
char CSavingGameMessage::m_cMessageText[MAX_CHARS_IN_MESSAGE];
CSavingGameMessage::eICON_STYLE CSavingGameMessage::m_iIconStyle;
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CSavingGameMessage::Init
// PURPOSE: initialises some variables
/////////////////////////////////////////////////////////////////////////////////////
void CSavingGameMessage::Init()
{
Clear();
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CSavingGameMessage::SetMessageText
// PURPOSE: stores the new text
/////////////////////////////////////////////////////////////////////////////////////
void CSavingGameMessage::SetMessageText(const char *pText, eICON_STYLE iconStyle)
{
safecpy(m_cMessageText, pText);
m_iIconStyle = iconStyle;
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CSavingGameMessage::GetMessageText
// PURPOSE: returns the content of the subtitle text
/////////////////////////////////////////////////////////////////////////////////////
char *CSavingGameMessage::GetMessageText()
{
if (CSystem::IsThisThreadId(SYS_THREAD_RENDER))
{
AssertMsg(0, "CSavingGameMessage::GetMessageText only allowed to be called on UPDATE thread");
return NULL;
}
return &m_cMessageText[0];
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CSavingGameMessage::GetIconStyle
// PURPOSE: returns the icon style as an integer
/////////////////////////////////////////////////////////////////////////////////////
s32 CSavingGameMessage::GetIconStyle()
{
if (CSystem::IsThisThreadId(SYS_THREAD_RENDER))
{
AssertMsg(0, "CSavingGameMessage::GetMessageText only allowed to be called on UPDATE thread");
return 0;
}
return s32( m_iIconStyle == SAVEGAME_ICON_STYLE_SAVING_NO_MESSAGE ? SAVEGAME_ICON_STYLE_SAVING : m_iIconStyle);
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CSavingGameMessage::IsSavingMessageActive
// PURPOSE: returns whether the saving message is active or not
/////////////////////////////////////////////////////////////////////////////////////
bool CSavingGameMessage::IsSavingMessageActive()
{
return (m_cMessageText[0] != 0) || m_iIconStyle == SAVEGAME_ICON_STYLE_SAVING_NO_MESSAGE;
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CSavingGameMessage::Clear
// PURPOSE: clears the message
/////////////////////////////////////////////////////////////////////////////////////
void CSavingGameMessage::Clear()
{
m_cMessageText[0] = 0;
m_iIconStyle = SAVEGAME_ICON_STYLE_NONE;
}
// ******************************************************************************************************
// ** CLoadingText
// ******************************************************************************************************
char CLoadingText::m_cText[MAX_CHARS_IN_MESSAGE];
bool CLoadingText::bActive = false;
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CLoadingText::Init
// PURPOSE: initialises some variables
/////////////////////////////////////////////////////////////////////////////////////
void CLoadingText::Init()
{
bActive = false;
Clear();
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CLoadingText::Shutdown
// PURPOSE: clears and shuts down the scaleform movie for the Loading Text
/////////////////////////////////////////////////////////////////////////////////////
void CLoadingText::Shutdown()
{
Clear();
SetActive(false);
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CLoadingText::SetActive
// PURPOSE: sets loading text on/off
/////////////////////////////////////////////////////////////////////////////////////
void CLoadingText::SetActive(bool bSet)
{
if (bSet)
{
if (!bActive)
{
bActive = true;
Clear();
}
}
else
{
if (bActive)
{
bActive = false;
}
}
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CLoadingText::SetText
// PURPOSE: stores the new text
/////////////////////////////////////////////////////////////////////////////////////
void CLoadingText::SetText(const char *pText)
{
safecpy(m_cText, pText);
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CLoadingText::GetText
// PURPOSE: returns the content of the loading text
/////////////////////////////////////////////////////////////////////////////////////
char *CLoadingText::GetText()
{
return &m_cText[0];
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CLoadingText::IsActive
// PURPOSE: returns whether the loading text is active or not
/////////////////////////////////////////////////////////////////////////////////////
bool CLoadingText::IsActive()
{
return (m_cText[0] != 0);
}
/////////////////////////////////////////////////////////////////////////////////////
// NAME: CLoadingText::Clear
// PURPOSE: clears the message
/////////////////////////////////////////////////////////////////////////////////////
void CLoadingText::Clear()
{
m_cText[0] = 0;
}
//
// name: CLoadingText::UpdateAndRenderLoadingText
// description: update and render the loading text
void CLoadingText::UpdateAndRenderLoadingText(float fCurrentTimer)
{
if (!bActive)
return;
#define FADEOUT_WITHOUT_LOADING_TEXT (4.0f)
CMessages::ClearMessages();
if(CWarningScreen::IsActive())
{
if (TheText.IsBasicTextLoaded()) // only render text if the text file is loaded
{
if(CControlMgr::GetMainFrontendControl(false).GetFrontendAccept().IsPressed())
{
CWarningScreen::Remove();
}
}
}
else
{
if ( (fCurrentTimer > FADEOUT_WITHOUT_LOADING_TEXT) || CSavingMessage::ShouldDisplaySavingSpinner() )
{
if (CBusySpinner::CanRender() && CBusySpinner::HasBodyText()) // if we have text then we need to display the real busy spinner now
{
CBusySpinner::Render();
}
else
{
// if we have no text, lets just render same way as we have done (this limits the change of using the busy spinner here to just a few places)
Vector2 vSize(0.0073125f, 0.013f);
const Vector2 vPos = CHudTools::CalculateHudPosition(Vector2(0,0), vSize, 'R', 'B');
CPauseMenu::RenderAnimatedSpinner(vSize, vPos, true, false, (CSavingMessage::ShouldDisplaySavingSpinner() REPLAY_ONLY(|| CReplayMgr::IsSaving()))?true:false);
}
}
}
}
// eof