Skip to content

Commit

Permalink
[12524] Add generic DoDisplayText function and use additional data of…
Browse files Browse the repository at this point in the history
… dbscripts table

Signed-off-by: Schmoozerd <schmoozerd@cmangos.net>
  • Loading branch information
xfurry authored and Schmoozerd committed Jun 3, 2013
1 parent b32291d commit d2fd4ba
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 84 deletions.
2 changes: 0 additions & 2 deletions doc/script_commands.txt
Expand Up @@ -150,8 +150,6 @@ Where "A -> B" means that the command is executed from A with B as target.
-- --------------------------

0 SCRIPT_COMMAND_TALK resultingSource = WorldObject, resultingTarget = Unit/none
* datalong (see enum ChatType for supported CHAT_TYPE_'s)
* datalong2 = language
* dataint = text entry from db_script_string -table. dataint2-dataint4 optionally, for random selection of text

1 SCRIPT_COMMAND_EMOTE resultingSource = Unit, resultingTarget = Unit/none
Expand Down
2 changes: 1 addition & 1 deletion src/game/CreatureEventAIMgr.cpp
Expand Up @@ -38,7 +38,7 @@ void CreatureEventAIMgr::LoadCreatureEventAI_Texts(bool check_entry_use)
m_CreatureEventAI_TextMap.clear();

// Load EventAI Text
sObjectMgr.LoadMangosStrings(WorldDatabase, "creature_ai_texts", MIN_CREATURE_AI_TEXT_STRING_ID, MAX_CREATURE_AI_TEXT_STRING_ID);
sObjectMgr.LoadMangosStrings(WorldDatabase, "creature_ai_texts", MIN_CREATURE_AI_TEXT_STRING_ID, MAX_CREATURE_AI_TEXT_STRING_ID, false);

// Gather Additional data from EventAI Texts
QueryResult* result = WorldDatabase.Query("SELECT entry, sound, type, language, emote FROM creature_ai_texts");
Expand Down
168 changes: 152 additions & 16 deletions src/game/ObjectMgr.cpp
Expand Up @@ -7419,7 +7419,27 @@ void ObjectMgr::LoadGameObjectForQuests()
sLog.outString(">> Loaded %u GameObjects for quests", count);
}

bool ObjectMgr::LoadMangosStrings(DatabaseType& db, char const* table, int32 min_value, int32 max_value)
inline void _DoStringError(int32 entry, char const* text, ...)
{
MANGOS_ASSERT(text);

char buf[256];
va_list ap;
va_start(ap, text);
vsnprintf(buf, 256, text, ap);
va_end(ap);

if (entry <= MAX_CREATURE_AI_TEXT_STRING_ID) // script library error
sLog.outErrorScriptLib("%s", entry, buf);
else if (entry <= MIN_CREATURE_AI_TEXT_STRING_ID) // eventAI error
sLog.outErrorEventAI("%s", entry, buf);
else if (entry < MIN_DB_SCRIPT_STRING_ID) // mangos string error
sLog.outError("%s");
else // if (entry > MIN_DB_SCRIPT_STRING_ID) // DB script text error
sLog.outErrorDb("DB-SCRIPTS: %s", entry, buf);
}

bool ObjectMgr::LoadMangosStrings(DatabaseType& db, char const* table, int32 min_value, int32 max_value, bool extra_content)
{
int32 start_value = min_value;
int32 end_value = max_value;
Expand Down Expand Up @@ -7455,7 +7475,10 @@ bool ObjectMgr::LoadMangosStrings(DatabaseType& db, char const* table, int32 min
++itr;
}

QueryResult* result = db.PQuery("SELECT entry,content_default,content_loc1,content_loc2,content_loc3,content_loc4,content_loc5,content_loc6,content_loc7,content_loc8 FROM %s", table);
sLog.outString("Loading texts from %s%s", table, extra_content ? ", with additional data" : "");

QueryResult* result = db.PQuery("SELECT entry,content_default,content_loc1,content_loc2,content_loc3,content_loc4,content_loc5,content_loc6,content_loc7,content_loc8 %s FROM %s",
extra_content ? ",sound,type,language,emote" : "", table);

if (!result)
{
Expand Down Expand Up @@ -7484,20 +7507,20 @@ bool ObjectMgr::LoadMangosStrings(DatabaseType& db, char const* table, int32 min

if (entry == 0)
{
sLog.outErrorDb("Table `%s` contain reserved entry 0, ignored.", table);
_DoStringError(start_value, "Table `%s` contain reserved entry 0, ignored.", table);
continue;
}
else if (entry < start_value || entry >= end_value)
{
sLog.outErrorDb("Table `%s` contain entry %i out of allowed range (%d - %d), ignored.", table, entry, min_value, max_value);
_DoStringError(start_value, "Table `%s` contain entry %i out of allowed range (%d - %d), ignored.", table, entry, min_value, max_value);
continue;
}

MangosStringLocale& data = mMangosStringLocaleMap[entry];

if (!data.Content.empty())
{
sLog.outErrorDb("Table `%s` contain data for already loaded entry %i (from another table?), ignored.", table, entry);
_DoStringError(entry, "Table `%s` contain data for already loaded entry %i (from another table?), ignored.", table, entry);
continue;
}

Expand All @@ -7523,6 +7546,39 @@ bool ObjectMgr::LoadMangosStrings(DatabaseType& db, char const* table, int32 min
}
}
}

// Load additional string content if necessary
if (extra_content)
{
data.SoundId = fields[10].GetUInt32();
data.Type = fields[11].GetUInt32();
data.Language = fields[12].GetUInt32();
data.Emote = fields[13].GetUInt32();

if (data.SoundId && !sSoundEntriesStore.LookupEntry(data.SoundId))
{
_DoStringError(entry, "Entry %i in table `%s` has soundId %u but sound does not exist.", entry, table, data.SoundId);
data.SoundId = 0;
}

if (!GetLanguageDescByID(data.Language))
{
_DoStringError(entry, "Entry %i in table `%s` using Language %u but Language does not exist.", entry, table, data.Language);
data.Language = LANG_UNIVERSAL;
}

if (data.Type > CHAT_TYPE_ZONE_YELL)
{
_DoStringError(entry, "Entry %i in table `%s` has Type %u but this Chat Type does not exist.", entry, table, data.Type);
data.Type = CHAT_TYPE_SAY;
}

if (data.Emote && !sEmotesStore.LookupEntry(data.Emote))
{
_DoStringError(entry, "Entry %i in table `%s` has Emote %u but emote does not exist.", entry, table, data.Emote);
data.Emote = EMOTE_ONESHOT_NONE;
}
}
}
while (result->NextRow());

Expand All @@ -7532,7 +7588,7 @@ bool ObjectMgr::LoadMangosStrings(DatabaseType& db, char const* table, int32 min
if (min_value == MIN_MANGOS_STRING_ID)
sLog.outString(">> Loaded %u MaNGOS strings from table %s", count, table);
else
sLog.outString(">> Loaded %u string templates from %s", count, table);
sLog.outString(">> Loaded %u %s templates from %s", count, extra_content ? "text" : "string", table);

return true;
}
Expand All @@ -7549,14 +7605,8 @@ const char* ObjectMgr::GetMangosString(int32 entry, int locale_idx) const
return msl->Content[0].c_str();
}

if (entry > MIN_DB_SCRIPT_STRING_ID)
sLog.outErrorDb("Entry %i not found in `db_script_string` table.", entry);
else if (entry > 0)
sLog.outErrorDb("Entry %i not found in `mangos_string` table.", entry);
else if (entry > MAX_CREATURE_AI_TEXT_STRING_ID)
sLog.outErrorEventAI("Entry %i not found in `creature_ai_texts` table.", entry);
else
sLog.outErrorScriptLib("String entry %i not found in Database.", entry);
_DoStringError(entry, "Entry %i not found but requested", entry);

return "<error>";
}

Expand Down Expand Up @@ -9521,7 +9571,7 @@ void ObjectMgr::GetNpcTextLocaleStrings0(uint32 entry, int32 loc_idx, std::strin
}

// Functions for scripting access
bool LoadMangosStrings(DatabaseType& db, char const* table, int32 start_value, int32 end_value)
bool LoadMangosStrings(DatabaseType& db, char const* table, int32 start_value, int32 end_value, bool extra_content)
{
// MAX_DB_SCRIPT_STRING_ID is max allowed negative value for scripts (scrpts can use only more deep negative values
// start/end reversed for negative values
Expand All @@ -9531,7 +9581,7 @@ bool LoadMangosStrings(DatabaseType& db, char const* table, int32 start_value, i
return false;
}

return sObjectMgr.LoadMangosStrings(db, table, start_value, end_value);
return sObjectMgr.LoadMangosStrings(db, table, start_value, end_value, extra_content);
}

void ObjectMgr::LoadCreatureTemplateSpells()
Expand Down Expand Up @@ -9569,6 +9619,11 @@ Quest const* GetQuestTemplateStore(uint32 entry)
return sObjectMgr.GetQuestTemplate(entry);
}

MangosStringLocale const* GetMangosStringData(int32 entry)
{
return sObjectMgr.GetMangosStringLocale(entry);
}

bool FindCreatureData::operator()(CreatureDataPair const& dataPair)
{
// skip wrong entry ids
Expand Down Expand Up @@ -9668,3 +9723,84 @@ GameObjectDataPair const* FindGOData::GetResult() const

return i_anyData;
}

bool DoDisplayText(WorldObject* source, int32 entry, Unit const* target /*=NULL*/)
{
MangosStringLocale const* data = sObjectMgr.GetMangosStringLocale(entry);

if (!data)
{
_DoStringError(entry, "DoScriptText with source %s could not find text entry %i.", source->GetGuidStr().c_str(), entry);
return false;
}

if (data->SoundId)
{
if (data->Type == CHAT_TYPE_ZONE_YELL)
source->GetMap()->PlayDirectSoundToMap(data->SoundId, source->GetZoneId());
else if (data->Type == CHAT_TYPE_WHISPER || data->Type == CHAT_TYPE_BOSS_WHISPER)
{
// An error will be displayed for the text
if (target && target->GetTypeId() == TYPEID_PLAYER)
source->PlayDirectSound(data->SoundId, (Player const*)target);
}
else
source->PlayDirectSound(data->SoundId);
}

if (data->Emote)
{
if (source->GetTypeId() == TYPEID_UNIT || source->GetTypeId() == TYPEID_PLAYER)
{
((Unit*)source)->HandleEmote(data->Emote);
}
else
{
_DoStringError(entry, "DoDisplayText entry %i tried to process emote for invalid source %s", entry, source->GetGuidStr().c_str());
return false;
}
}

switch (data->Type)
{
case CHAT_TYPE_SAY:
source->MonsterSay(entry, data->Language, target);
break;
case CHAT_TYPE_YELL:
source->MonsterYell(entry, data->Language, target);
break;
case CHAT_TYPE_TEXT_EMOTE:
source->MonsterTextEmote(entry, target);
break;
case CHAT_TYPE_BOSS_EMOTE:
source->MonsterTextEmote(entry, target, true);
break;
case CHAT_TYPE_WHISPER:
{
if (target && target->GetTypeId() == TYPEID_PLAYER)
source->MonsterWhisper(entry, target);
else
{
_DoStringError(entry, "DoDisplayText entry %i cannot whisper without target unit (TYPEID_PLAYER).");
return false;
}
break;
}
case CHAT_TYPE_BOSS_WHISPER:
{
if (target && target->GetTypeId() == TYPEID_PLAYER)
source->MonsterWhisper(entry, target, true);
else
{
_DoStringError(entry, "DoDisplayText entry %i cannot whisper without target unit (TYPEID_PLAYER).");
return false;
}
break;
}
case CHAT_TYPE_ZONE_YELL:
source->MonsterYellToZone(entry, data->Language, target);
break;
}

return true;
}
16 changes: 13 additions & 3 deletions src/game/ObjectMgr.h
Expand Up @@ -128,7 +128,13 @@ static_assert(MAX_DB_SCRIPT_STRING_ID < ACE_INT32_MAX, "Must scope with int32 ra

struct MangosStringLocale
{
MangosStringLocale() : SoundId(0), Type(0), Language(0), Emote(0) { }

std::vector<std::string> Content; // 0 -> default, i -> i-1 locale index
uint32 SoundId;
uint8 Type;
uint32 Language;
uint32 Emote;
};

typedef UNORDERED_MAP<uint32, CreatureData> CreatureDataMap;
Expand Down Expand Up @@ -681,8 +687,8 @@ class ObjectMgr
void LoadCreatureQuestRelations();
void LoadCreatureInvolvedRelations();

bool LoadMangosStrings(DatabaseType& db, char const* table, int32 min_value, int32 max_value);
bool LoadMangosStrings() { return LoadMangosStrings(WorldDatabase, "mangos_string", MIN_MANGOS_STRING_ID, MAX_MANGOS_STRING_ID); }
bool LoadMangosStrings(DatabaseType& db, char const* table, int32 min_value, int32 max_value, bool extra_content);
bool LoadMangosStrings() { return LoadMangosStrings(WorldDatabase, "mangos_string", MIN_MANGOS_STRING_ID, MAX_MANGOS_STRING_ID, false); }
void LoadCreatureLocales();
void LoadCreatureTemplates();
void LoadCreatures();
Expand Down Expand Up @@ -1241,9 +1247,13 @@ class ObjectMgr

#define sObjectMgr MaNGOS::Singleton<ObjectMgr>::Instance()

/// generic text function
MANGOS_DLL_SPEC bool DoDisplayText(WorldObject* source, int32 entry, Unit const* target = NULL);

// scripting access functions
MANGOS_DLL_SPEC bool LoadMangosStrings(DatabaseType& db, char const* table, int32 start_value = MAX_CREATURE_AI_TEXT_STRING_ID, int32 end_value = std::numeric_limits<int32>::min());
MANGOS_DLL_SPEC bool LoadMangosStrings(DatabaseType& db, char const* table, int32 start_value = MAX_CREATURE_AI_TEXT_STRING_ID, int32 end_value = std::numeric_limits<int32>::min(), bool extra_content = false);
MANGOS_DLL_SPEC CreatureInfo const* GetCreatureTemplateStore(uint32 entry);
MANGOS_DLL_SPEC Quest const* GetQuestTemplateStore(uint32 entry);
MANGOS_DLL_SPEC MangosStringLocale const* GetMangosStringData(int32 entry);

#endif
52 changes: 3 additions & 49 deletions src/game/ScriptMgr.cpp
Expand Up @@ -253,18 +253,6 @@ void ScriptMgr::LoadScripts(ScriptMapMapName& scripts, const char* tablename)
{
case SCRIPT_COMMAND_TALK: // 0
{
if (tmp.talk.chatType > CHAT_TYPE_ZONE_YELL)
{
sLog.outErrorDb("Table `%s` has invalid CHAT_TYPE_ (datalong = %u) in SCRIPT_COMMAND_TALK for script id %u", tablename, tmp.talk.chatType, tmp.id);
continue;
}

if (!GetLanguageDescByID(tmp.talk.language))
{
sLog.outErrorDb("Table `%s` has datalong2 = %u in SCRIPT_COMMAND_TALK for script id %u, but this language does not exist.", tablename, tmp.talk.language, tmp.id);
continue;
}

if (tmp.textId[0] == 0)
{
sLog.outErrorDb("Table `%s` has invalid talk text id (dataint = %i) in SCRIPT_COMMAND_TALK for script id %u", tablename, tmp.textId[0], tmp.id);
Expand Down Expand Up @@ -829,7 +817,7 @@ void ScriptMgr::LoadCreatureDeathScripts()

void ScriptMgr::LoadDbScriptStrings()
{
sObjectMgr.LoadMangosStrings(WorldDatabase, "db_script_string", MIN_DB_SCRIPT_STRING_ID, MAX_DB_SCRIPT_STRING_ID);
sObjectMgr.LoadMangosStrings(WorldDatabase, "db_script_string", MIN_DB_SCRIPT_STRING_ID, MAX_DB_SCRIPT_STRING_ID, true);

std::set<int32> ids;

Expand Down Expand Up @@ -1129,42 +1117,8 @@ bool ScriptAction::HandleScriptStep()
textId = m_script->textId[urand(0, i - 1)];
}

switch (m_script->talk.chatType)
{
case CHAT_TYPE_SAY:
pSource->MonsterSay(textId, m_script->talk.language, unitTarget);
break;
case CHAT_TYPE_YELL:
pSource->MonsterYell(textId, m_script->talk.language, unitTarget);
break;
case CHAT_TYPE_TEXT_EMOTE:
pSource->MonsterTextEmote(textId, unitTarget);
break;
case CHAT_TYPE_BOSS_EMOTE:
pSource->MonsterTextEmote(textId, unitTarget, true);
break;
case CHAT_TYPE_WHISPER:
if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER)
{
sLog.outErrorDb(" DB-SCRIPTS: Process table `%s` id %u, command %u attempt to whisper (%u) to %s, skipping.", m_table, m_script->id, m_script->command, m_script->talk.chatType, unitTarget ? unitTarget->GetGuidStr().c_str() : "<no target>");
break;
}
pSource->MonsterWhisper(textId, unitTarget);
break;
case CHAT_TYPE_BOSS_WHISPER:
if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER)
{
sLog.outErrorDb(" DB-SCRIPTS: Process table `%s` id %u, command %u attempt to whisper (%u) to %s, skipping.", m_table, m_script->id, m_script->command, m_script->talk.chatType, unitTarget ? unitTarget->GetGuidStr().c_str() : "<no target>");
break;
}
pSource->MonsterWhisper(textId, unitTarget, true);
break;
case CHAT_TYPE_ZONE_YELL:
pSource->MonsterYellToZone(textId, m_script->talk.language, unitTarget);
break;
default:
break; // must be already checked at load
}
if (!DoDisplayText(pSource, textId, unitTarget))
sLog.outErrorDb(" DB-SCRIPTS: Process table `%s` id %u, could not display text %i properly", m_table, m_script->id, textId);
break;
}
case SCRIPT_COMMAND_EMOTE: // 1
Expand Down

5 comments on commit d2fd4ba

@cala
Copy link
Contributor

@cala cala commented on d2fd4ba Jun 3, 2013

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't wait these are backported to classic. I'll now more easily track why MonsterTextEmote does not work with AIEvent or DBScript. Thanks :)

@Schmoozerd
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is MonsterTextEmote supported by the classic client?
After all these all are just high-level changes...

So if it didn't work before on classic, it won't now.

@cala
Copy link
Contributor

@cala cala commented on d2fd4ba Jun 3, 2013

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is. If core calls MonsterTextEmote by providing a string as an argument, the client displays it correctly. If MonsterTextEmote is provided a textID, the client raises an error. I did not yet track precisely where the error occurs: when the core try to call the string from db? When building the packet in MonsterChatBuilder?

@Schmoozerd
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes - i will look into it when i finished with tbc (anyhow it is time for classic backporting)

@Schmoozerd
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see two differences:

  1. especially .npc textemote is called without target, possible that the textemotes simply don't support target in classic which causes the packet to fail

  2. The direction version uses this->GetName() and target->GetName - where the other version uses GetNameForLocaleIdx

But I would suggest to try with NULL target for some testings, then only with Player targets, maybe this is it already :)

Call Stack notes: http://paste2.org/9GnK6dzD

Please sign in to comment.