- Fix #453 PlayFlavor re-design

PlayFlavorID(NPC, type, id, index, Player, language)
Set Player to 'nil' to send to all clients, specifying a Player will make it send ONLY to that player
New command /reload voiceovers added
- versioning updated to 0.9.4-aquarii
- Fix #454 Support for SendShowBook to have language, items field 'book_language' added
- Some debug log cleanup
This commit is contained in:
Emagi 2022-07-24 22:19:45 -04:00
parent 17eb8e0d23
commit 0e00165195
22 changed files with 338 additions and 18 deletions

View file

@ -0,0 +1 @@
alter table items add column book_language tinyint(3) unsigned default 0;

View file

@ -0,0 +1,13 @@
insert into commands set type=1,command='reload',subcommand='voiceovers',required_status=100,handler=532;
CREATE TABLE `voiceovers` (
`type_id` tinyint(3) unsigned NOT NULL default 0,
`id` int(10) unsigned NOT NULL default 0,
`indexed` smallint(5) unsigned NOT NULL default 0,
`mp3_string` text not null default '',
`text_string` text not null default '',
`emote_string` text not null default '',
`key1` int(10) unsigned NOT NULL default 0,
`key2` int(10) unsigned NOT NULL default 0,
`garbled` tinyint(3) unsigned NOT NULL default 0,
`garble_link_id` tinyint(3) unsigned NOT NULL default 0
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

View file

@ -756,7 +756,7 @@ void Commands::Command_Bot_Help(Client* client, Seperator* sep) {
details += "18\tSarnak\n";
details += "19\tVampire\n";
details += "20\tAerakyn\n";
client->SendShowBook(client->GetPlayer(), title, 1, details);
client->SendShowBook(client->GetPlayer(), title, 0, 1, details);
return;
}
else if (strncasecmp("class", sep->arg[0], 5) == 0) {
@ -811,7 +811,7 @@ void Commands::Command_Bot_Help(Client* client, Seperator* sep) {
details3 += "43\tSHAPER\n";
details3 += "44\tCHANNELER\n";
client->SendShowBook(client->GetPlayer(), title, 3, details, details2, details3);
client->SendShowBook(client->GetPlayer(), title, 0, 3, details, details2, details3);
return;
}
}

View file

@ -1833,6 +1833,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload rules");
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload transporters");
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload startabilities");
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/reload voiceovers");
break;
}
case COMMAND_RELOADSTRUCTS: {
@ -1946,6 +1947,13 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!");
break;
}
case COMMAND_RELOAD_VOICEOVERS: {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Reloading Voiceovers...");
world.PurgeVoiceOvers();
world.LoadVoiceOvers();
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Done!");
break;
}
case COMMAND_READ: {
if (sep && sep->arg[1][0] && sep->IsNumber(1)) {
if (strcmp(sep->arg[0], "read") == 0) {
@ -1953,7 +1961,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
Item* item = client->GetPlayer()->item_list.GetItemFromIndex(item_index);
if (item) {
Spawn* spawn = cmdTarget;
client->SendShowBook(client->GetPlayer(), item->name, item->book_pages);
client->SendShowBook(client->GetPlayer(), item->name, item->book_language, item->book_pages);
break;
}
}
@ -3867,7 +3875,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
client->GetCurrentZone()->SendAllSpawnsForVisChange(client, false);
client->Message(CHANNEL_COLOR_RED, "Adding spawn group tag \"%s\" with tag icon %u.", (value == 1) ? "on" : "off", tag_icon);
}
else if(strncasecmp(sep->arg[1], "race", 5) == 0){
else if(strncasecmp(sep->arg[1], "race", 4 == 0)){
if(!value) {
client->SimpleMessage(CHANNEL_COLOR_RED, "Need to supply a valid race id.");
break;
@ -4742,7 +4750,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
}
string title = string(spawn->GetName()) + "(" + to_string(spawn->GetDatabaseID()) + ")";
client->SendShowBook(client->GetPlayer(), title, 4, details, details2, details3, details4);
client->SendShowBook(client->GetPlayer(), title, 0, 4, details, details2, details3, details4);
}
else {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Syntax: /spawn details (radius)");

View file

@ -932,6 +932,8 @@ private:
#define COMMAND_CANCEL_EFFECT 530
#define COMMAND_CUREPLAYER 531
#define COMMAND_RELOAD_VOICEOVERS 532
#define GET_AA_XML 750
#define ADD_AA 751

View file

@ -897,6 +897,7 @@ Item::Item(){
no_sale = false;
created = std::time(nullptr);
effect_type = NO_EFFECT_TYPE;
book_language = 0;
}
Item::Item(Item* in_item){
@ -917,6 +918,7 @@ Item::Item(Item* in_item){
created = in_item->created;
grouped_char_ids.insert(in_item->grouped_char_ids.begin(), in_item->grouped_char_ids.end());
effect_type = in_item->effect_type;
book_language = in_item->book_language;
}
Item::~Item(){
@ -1140,6 +1142,7 @@ void Item::SetItem(Item* old_item){
slot_data = old_item->slot_data;
spell_id = old_item->spell_id;
spell_tier = old_item->spell_tier;
book_language = old_item->book_language;
}
bool Item::CheckArchetypeAdvSubclass(int8 adventure_class, map<int8, int16>* adv_class_levels) {

View file

@ -951,6 +951,7 @@ public:
ItemEffectType effect_type;
bool crafted;
bool tinkered;
int8 book_language;
void AddEffect(string effect, int8 percentage, int8 subbulletflag);
void AddBookPage(int8 page, string page_text,int8 valign, int8 halign);

View file

@ -224,6 +224,7 @@ void WorldDatabase::LoadDataFromRow(DatabaseResult* result, Item* item)
item->crafted = result->GetInt8Str("crafted");
item->tinkered = result->GetInt8Str("tinkered");
item->book_language = result->GetInt8Str("book_language");
}

View file

@ -188,6 +188,33 @@ int EQ2Emu_lua_PlayFlavor(lua_State* state) {
return 0;
}
int EQ2Emu_lua_PlayFlavorID(lua_State* state) {
if (!lua_interface)
return 0;
Spawn* spawn = lua_interface->GetSpawn(state);
int8 type = lua_interface->GetInt8Value(state, 2);
int32 id = lua_interface->GetInt32Value(state, 3);
int16 index = lua_interface->GetInt16Value(state, 4);
Spawn* player = lua_interface->GetSpawn(state, 5);
int8 language = lua_interface->GetInt8Value(state, 6);
lua_interface->ResetFunctionStack(state);
if (spawn) {
Client* client = 0;
if (player && player->IsPlayer())
client = spawn->GetZone()->GetClientBySpawn(player);
if (client) {
VoiceOverStruct non_garble, garble;
bool garble_success = false;
bool success = world.FindVoiceOver(type, id, index, &non_garble, &garble_success, &garble);
client->SendPlayFlavor(spawn, language, &non_garble, &garble, success, garble_success);
}
else
spawn->GetZone()->PlayFlavorID(spawn, type, id, index, language);
}
return 0;
}
int EQ2Emu_lua_PlaySound(lua_State* state) {
if (!lua_interface)
return 0;

View file

@ -194,6 +194,7 @@ int EQ2Emu_lua_GetCharacterID(lua_State* state);
int EQ2Emu_lua_MovementLoopAdd(lua_State* state);
int EQ2Emu_lua_GetCurrentZoneSafeLocation(lua_State* state);
int EQ2Emu_lua_PlayFlavor(lua_State* state);
int EQ2Emu_lua_PlayFlavorID(lua_State* state);
int EQ2Emu_lua_PlaySound(lua_State* state);
int EQ2Emu_lua_PlayVoice(lua_State* state);
int EQ2Emu_lua_PlayAnimation(lua_State* state);

View file

@ -1057,6 +1057,7 @@ void LuaInterface::RegisterFunctions(lua_State* state) {
lua_register(state, "GetSpawnByGroupID", EQ2Emu_lua_GetSpawnByGroupID);
lua_register(state, "GetSpawnByLocationID", EQ2Emu_lua_GetSpawnByLocationID);
lua_register(state, "PlayFlavor", EQ2Emu_lua_PlayFlavor);
lua_register(state, "PlayFlavorID", EQ2Emu_lua_PlayFlavorID);
lua_register(state, "PlaySound", EQ2Emu_lua_PlaySound);
lua_register(state, "PlayVoice", EQ2Emu_lua_PlayVoice);
lua_register(state, "PlayAnimation", EQ2Emu_lua_PlayAnimation);

View file

@ -132,6 +132,7 @@ World::~World(){
tov_itemstat_conversion.clear();
PurgeStartingLists();
PurgeVoiceOvers();
map<std::string, RegionMapRange*>::iterator itr3;
for (itr3 = region_maps.begin(); itr3 != region_maps.end(); itr3++)
@ -170,6 +171,7 @@ void World::init(){
LogWrite(WORLD__DEBUG, 1, "World", "-Load `visual states` complete!");
LoadStartingLists();
LoadVoiceOvers();
LogWrite(WORLD__DEBUG, 1, "World", "-Setting system parameters...");
Variable* var = variables.FindVariable("gametime");
@ -2411,7 +2413,18 @@ void World::PurgeStartingLists()
safe_delete(tmpMap);
}
starting_spells.clear();
for(int type=0;type<3;type++) {
multimap<int32, multimap<int16, VoiceOverStruct>*>::iterator vos_itr;
for (vos_itr = voiceover_map[type].begin(); vos_itr != voiceover_map[type].end(); vos_itr++)
{
multimap<int16, VoiceOverStruct>* tmpMap = vos_itr->second;
safe_delete(tmpMap);
}
voiceover_map[type].clear();
}
MStartingLists.releasewritelock();
}
@ -2611,3 +2624,146 @@ void World::SendTimeUpdate()
{
zone_list.SendTimeUpdate();
}
void World::LoadVoiceOvers()
{
LogWrite(WORLD__DEBUG, 1, "World", "-Loading `voiceovers`...");
database.LoadVoiceOvers(this);
}
void World::PurgeVoiceOvers()
{
MVoiceOvers.writelock();
for(int type=0;type<MAX_VOICEOVER_TYPE+1;type++) {
multimap<int32, multimap<int16, VoiceOverStruct>*>::iterator vos_itr;
for (vos_itr = voiceover_map[type].begin(); vos_itr != voiceover_map[type].end(); vos_itr++)
{
multimap<int16, VoiceOverStruct>* tmpMap = vos_itr->second;
safe_delete(tmpMap);
}
voiceover_map[type].clear();
}
MVoiceOvers.releasewritelock();
}
bool World::FindVoiceOver(int8 type, int32 id, int16 index, VoiceOverStruct* struct_, bool* find_garbled, VoiceOverStruct* garble_struct_) {
// if we complete both requirements, based on struct_ and garble_struct_ being passed when required by ptr not being null
bool succeed = false;
if(type > MAX_VOICEOVER_TYPE) {
LogWrite(WORLD__ERROR, 0, "World", "Voice over %u out of range, max voiceover type is %u...", type, MAX_VOICEOVER_TYPE);
return succeed;
}
MVoiceOvers.readlock();
multimap<int32, multimap<int16, VoiceOverStruct>*>::iterator itr = voiceover_map[type].find(id);
if(itr != voiceover_map[type].end()) {
std::pair<VOMapIterator, VOMapIterator> result = itr->second->equal_range(index);
int count = std::distance(result.first, result.second);
bool tries_attempt = true; // abort out the while loop
bool non_garble_found = false;
int rand = 0; // use to randomize the voiceover selection
int pos = 0;
int tries = 0;
bool has_ungarbled = false;
bool has_garbled = false;
int8 garble_link_id = 0; // used to match ungarbled to garbled when the link id is set in the DB
while(tries_attempt) {
pos = 0;
rand = MakeRandomInt(0, count);
if ( tries == 3 || non_garble_found || (find_garbled && (*find_garbled)))
rand = 0; // override, too many tries, or we otherwise found one garbled/ungarbled lets try to link it
for (VOMapIterator it = result.first; it != result.second; it++) {
if(!it->second.is_garbled) {
has_ungarbled = true;
}
else {
has_garbled = true;
}
pos++;
// if there is only 1 entry in the voiceover list we aren't going to bother skipping
if(count > 1 && pos < rand) {
continue;
}
if(!it->second.is_garbled && (garble_link_id == 0 || it->second.garble_link_id == garble_link_id)) {
garble_link_id = it->second.garble_link_id;
non_garble_found = true;
if(struct_) {
CopyVoiceOver(struct_, &it->second);
}
if(!find_garbled || ((*find_garbled))) {
if(find_garbled)
*find_garbled = true;
tries_attempt = false;
succeed = true;
break;
}
}
else if(find_garbled && !(*find_garbled) && it->second.is_garbled && (garble_link_id == 0 || it->second.garble_link_id == garble_link_id)) {
*find_garbled = true;
garble_link_id = it->second.garble_link_id;
if(garble_struct_) {
CopyVoiceOver(garble_struct_, &it->second);
if(!struct_ || non_garble_found) {
tries_attempt = false;
succeed = true;
break;
}
}
}
}
tries++;
if(!tries_attempt || (tries > 0 && !has_ungarbled && (!find_garbled || *find_garbled == true || !has_garbled)) || tries > 3)
break;
}
}
MVoiceOvers.releasereadlock();
return succeed;
}
void World::AddVoiceOver(int8 type, int32 id, int16 index, VoiceOverStruct* struct_) {
if(type > MAX_VOICEOVER_TYPE) {
LogWrite(WORLD__ERROR, 0, "World", "Voice over %u out of range, max voiceover type is %u...", type, MAX_VOICEOVER_TYPE);
return;
}
VoiceOverStruct tmpStruct;
tmpStruct.mp3_string = std::string(struct_->mp3_string);
tmpStruct.text_string = std::string(struct_->text_string);
tmpStruct.emote_string = std::string(struct_->emote_string);
tmpStruct.key1 = struct_->key1;
tmpStruct.key2 = struct_->key2;
tmpStruct.is_garbled = struct_->is_garbled;
MVoiceOvers.writelock();
if(!voiceover_map[type].count(id))
{
multimap<int16, VoiceOverStruct>* vo_struct = new multimap<int16, VoiceOverStruct>();
vo_struct->insert(make_pair(index, tmpStruct));
voiceover_map[type].insert(make_pair(id, vo_struct));
}
else
{
multimap<int32, multimap<int16, VoiceOverStruct>*>::const_iterator itr = voiceover_map[type].find(id);
itr->second->insert(make_pair(index, tmpStruct));
}
MVoiceOvers.releasewritelock();
}
void World::CopyVoiceOver(VoiceOverStruct* struct1, VoiceOverStruct* struct2) {
if(!struct1 || !struct2)
return;
struct1->mp3_string = std::string(struct2->mp3_string);
struct1->text_string = std::string(struct2->text_string);
struct1->emote_string = std::string(struct2->emote_string);
struct1->key1 = struct2->key1;
struct1->key2 = struct2->key2;
struct1->is_garbled = struct2->is_garbled;
struct1->garble_link_id = struct2->garble_link_id;
}

View file

@ -398,6 +398,17 @@ struct StartingSpell
int32 knowledge_slot;
};
#define MAX_VOICEOVER_TYPE 2
struct VoiceOverStruct{
string mp3_string;
string text_string;
string emote_string;
int32 key1;
int32 key2;
bool is_garbled;
int8 garble_link_id;
};
class ZoneList {
public:
ZoneList();
@ -642,10 +653,17 @@ public:
// just in case we roll over a time as to not send bad times to clients (days before hours, hours before minutes as examples)
Mutex MWorldTime;
void LoadVoiceOvers();
void PurgeVoiceOvers();
typedef std::multimap<int16, VoiceOverStruct>::iterator VOMapIterator;
bool FindVoiceOver(int8 type, int32 id, int16 index, VoiceOverStruct* struct_ = nullptr, bool* find_garbled = nullptr, VoiceOverStruct* garble_struct_ = nullptr);
void AddVoiceOver(int8 type, int32 id, int16 index, VoiceOverStruct* struct_);
void CopyVoiceOver(VoiceOverStruct* struct1, VoiceOverStruct* struct2);
Mutex MVoiceOvers;
static sint64 newValue;
private:
multimap<int32, multimap<int16, VoiceOverStruct>*> voiceover_map[3];
int32 suppressed_warning = 0;
map<string, int32> reloading_subsystems;
//void RemovePlayerFromGroup(PlayerGroup* group, GroupMemberInfo* info, bool erase = true);

View file

@ -7416,6 +7416,42 @@ void WorldDatabase::LoadStartingSkills(World* world)
}
void WorldDatabase::LoadVoiceOvers(World* world)
{
int32 total = 0;
Query query;
MYSQL_ROW row;
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT type_id, id, indexed, mp3_string, text_string, emote_string, key1, key2, garbled, garble_link_id FROM voiceovers");
if (result)
{
if (mysql_num_rows(result) > 0)
{
Skill* skill = 0;
while (result && (row = mysql_fetch_row(result)))
{
VoiceOverStruct vos;
vos.mp3_string = std::string(row[3]);
vos.text_string = std::string(row[4]);
vos.emote_string = std::string(row[5]);
vos.key1 = atoul(row[6]);
vos.key2 = atoul(row[7]);
vos.is_garbled = atoul(row[8]);
vos.garble_link_id = atoul(row[9]);
int8 type = atoul(row[0]);
int32 id = atoul(row[1]);
int16 index = atoul(row[2]);
world->AddVoiceOver(type, id, index, &vos);
total++;
}
}
}
LogWrite(WORLD__DEBUG, 3, "World", "--Loaded %u Voiceover(s)", total);
}
void WorldDatabase::LoadStartingSpells(World* world)
{
world->MStartingLists.writelock();

View file

@ -604,6 +604,7 @@ public:
void LoadStartingSkills(World* world);
void LoadStartingSpells(World* world);
void LoadVoiceOvers(World* world);
int32 CreateSpiritShard(const char* name, int32 level, int8 race, int8 gender, int8 adventure_class,
int16 model_type, int16 soga_model_type, int16 hair_type, int16 hair_face_type, int16 wing_type,

View file

@ -64,7 +64,6 @@ std::string RegionMapV1::TestFile(std::string testFile)
string tmpScript("RegionScripts/");
tmpScript.append(mZoneNameLower);
tmpScript.append("/" + tmpStr + ".lua");
printf("File to test : %s\n",tmpScript.c_str());
std::ifstream f(tmpScript.c_str());
return f.good() ? tmpScript : string("");
}

View file

@ -10530,7 +10530,7 @@ void Client::SendFlightAutoMount(int32 path_id, int16 mount_id, int8 mount_red_c
((Entity*)GetPlayer())->SetMount(mount_id, mount_red_color, mount_green_color, mount_blue_color);
}
void Client::SendShowBook(Spawn* sender, string title, int8 num_pages, ...)
void Client::SendShowBook(Spawn* sender, string title, int8 language, int8 num_pages, ...)
{
if (!sender)
{
@ -10547,7 +10547,9 @@ void Client::SendShowBook(Spawn* sender, string title, int8 num_pages, ...)
packet->setDataByName("book_title", title.c_str());
packet->setDataByName("book_type", "simple");
packet->setDataByName("unknown2", 1);
if(language > 0 && !GetPlayer()->HasLanguage(language))
packet->setDataByName("language", language);
if (GetVersion() > 546)
packet->setDataByName("unknown5", 1, 4);
@ -10596,7 +10598,7 @@ void Client::SendShowBook(Spawn* sender, string title, int8 num_pages, ...)
safe_delete(packet);
}
void Client::SendShowBook(Spawn* sender, string title, vector<Item::BookPage*> pages)
void Client::SendShowBook(Spawn* sender, string title, int8 language, vector<Item::BookPage*> pages)
{
if (!sender)
{
@ -10613,6 +10615,9 @@ void Client::SendShowBook(Spawn* sender, string title, vector<Item::BookPage*> p
packet->setDataByName("book_title", title.c_str());
packet->setDataByName("book_type", "simple");
packet->setDataByName("unknown2", 1);
if(language > 0 && !GetPlayer()->HasLanguage(language))
packet->setDataByName("language", language);
if (GetVersion() > 546)
packet->setDataByName("unknown5", 1, 4);
@ -11018,4 +11023,22 @@ bool Client::UseItem(Item* item, Spawn* target) {
}
}
return false;
}
void Client::SendPlayFlavor(Spawn* spawn, int8 language, VoiceOverStruct* non_garble,
VoiceOverStruct* garble, bool success, bool garble_success) {
VoiceOverStruct* resStruct = nullptr;
if(language == 0 || GetPlayer()->HasLanguage(language)) {
if(success) {
resStruct = non_garble;
}
}
else if(garble_success) {
resStruct = garble;
}
if(resStruct) {
GetPlayer()->GetZone()->PlayFlavor(this, spawn, resStruct->mp3_string.c_str(), resStruct->text_string.c_str(), resStruct->emote_string.c_str(), resStruct->key1, resStruct->key2, language);
}
}

View file

@ -32,6 +32,7 @@ using namespace std;
#define CLIENT_TIMEOUT 60000
struct TransportDestination;
struct ConversationOption;
struct VoiceOverStruct;
#define MAIL_SEND_RESULT_SUCCESS 0
#define MAIL_SEND_RESULT_UNKNOWN_PLAYER 1
@ -446,8 +447,8 @@ public:
void SendFlightAutoMount(int32 path_id, int16 mount_id = 0, int8 mount_red_color = 0xFF, int8 mount_green_color = 0xFF, int8 mount_blue_color=0xFF);
void SendShowBook(Spawn* sender, string title, int8 num_pages, ...);
void SendShowBook(Spawn* sender, string title, vector<Item::BookPage*> pages);
void SendShowBook(Spawn* sender, string title, int8 language, int8 num_pages, ...);
void SendShowBook(Spawn* sender, string title, int8 language, vector<Item::BookPage*> pages);
void SetTemporaryTransportID(int32 id) { temporary_transport_id = id; }
int32 GetTemporaryTransportID() { return temporary_transport_id; }
@ -540,6 +541,8 @@ public:
}
bool UseItem(Item* item, Spawn* target = nullptr);
void SendPlayFlavor(Spawn* spawn, int8 language, VoiceOverStruct* non_garble, VoiceOverStruct* garble, bool success = false, bool garble_success = false);
private:
void SavePlayerImages();
void SkillChanged(Skill* skill, int16 previous_value, int16 new_value);

View file

@ -3654,6 +3654,29 @@ void ZoneServer::PlayFlavor(Spawn* spawn, const char* mp3, const char* text, con
MClientList.releasereadlock(__FUNCTION__, __LINE__);
}
void ZoneServer::PlayFlavorID(Spawn* spawn, int8 type, int32 id, int16 index, int8 language){
if(!spawn)
return;
Client* client = 0;
vector<Client*>::iterator client_itr;
VoiceOverStruct non_garble, garble;
bool garble_success = false;
bool success = world.FindVoiceOver(type, id, index, &non_garble, &garble_success, &garble);
VoiceOverStruct* resStruct = nullptr;
MClientList.readlock(__FUNCTION__, __LINE__);
for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) {
client = *client_itr;
if(!client || !client->IsReadyForUpdates() || !client->GetPlayer()->WasSentSpawn(spawn->GetID()) || client->GetPlayer()->GetDistance(spawn) > 30)
continue;
client->SendPlayFlavor(spawn, language, &non_garble, &garble, success, garble_success);
}
MClientList.releasereadlock(__FUNCTION__, __LINE__);
}
void ZoneServer::PlayVoice(Spawn* spawn, const char* mp3, int32 key1, int32 key2){
if(!spawn || !mp3)
return;

View file

@ -450,6 +450,7 @@ public:
void PlayFlavor(Client* client, Spawn* spawn, const char* mp3, const char* text, const char* emote, int32 key1, int32 key2, int8 language);
void PlayVoice(Client* client, Spawn* spawn, const char* mp3, int32 key1, int32 key2);
void PlayFlavor(Spawn* spawn, const char* mp3, const char* text, const char* emote, int32 key1, int32 key2, int8 language);
void PlayFlavorID(Spawn* spawn, int8 type, int32 id, int16 index, int8 language);
void PlayVoice(Spawn* spawn, const char* mp3, int32 key1, int32 key2);
void SendThreatPacket(Spawn* caster, Spawn* target, int32 threat_amt, const char* spell_name);
void KillSpawnByDistance(Spawn* spawn, float max_distance, bool include_players = false, bool send_packet = false);

View file

@ -38,11 +38,11 @@
#endif
#if defined(LOGIN)
#define CURRENT_VERSION "0.9.4-geminorum"
#define CURRENT_VERSION "0.9.4-aquarii"
#elif defined(WORLD)
#define CURRENT_VERSION "0.9.4-geminorum"
#define CURRENT_VERSION "0.9.4-aquarii"
#else
#define CURRENT_VERSION "0.9.4-geminorum"
#define CURRENT_VERSION "0.9.4-aquarii"
#endif
#define COMPILE_DATE __DATE__

View file

@ -9876,7 +9876,8 @@ to zero and treated like placeholders." />
<Struct Name="WS_EqShowBook" ClientVersion="546" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqShowBookCmd" >
<Data ElementName="spawn_id" Type="int32" Size="1" />
<Data ElementName="book_title" Type="EQ2_16Bit_String" Size="1" />
<Data ElementName="unknown" Type="int16" Size="1" />
<Data ElementName="language" Type="int8" Size="1" />
<Data ElementName="unknown1" Type="int8" Size="1" />
<Data ElementName="book_type" Type="EQ2_16Bit_String" Size="1" />
<Data ElementName="unknown2" Type="int16" Size="1" />
<Data ElementName="num_pages" Type="int8" Size="1" />
@ -9891,7 +9892,8 @@ to zero and treated like placeholders." />
<Struct Name="WS_EqShowBook" ClientVersion="1096" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqShowBookCmd" >
<Data ElementName="spawn_id" Type="int32" Size="1" />
<Data ElementName="book_title" Type="EQ2_16Bit_String" Size="1" />
<Data ElementName="unknown" Type="int16" Size="1" />
<Data ElementName="language" Type="int8" Size="1" />
<Data ElementName="unknown1" Type="int8" Size="1" />
<Data ElementName="book_type" Type="EQ2_16Bit_String" Size="1" />
<Data ElementName="unknown2" Type="int8" Size="1" />
<Data ElementName="unknown3" Type="int16" Size="1" />