diff --git a/DB/updates/spawn_ground_randomheading_aug_11_2022.sql b/DB/updates/spawn_ground_randomheading_aug_11_2022.sql new file mode 100644 index 000000000..8290e9441 --- /dev/null +++ b/DB/updates/spawn_ground_randomheading_aug_11_2022.sql @@ -0,0 +1 @@ +alter table spawn_ground add column randomize_heading tinyint(3) unsigned default 1; \ No newline at end of file diff --git a/EQ2/source/WorldServer/Combat.cpp b/EQ2/source/WorldServer/Combat.cpp index f3d197dc1..b66486126 100644 --- a/EQ2/source/WorldServer/Combat.cpp +++ b/EQ2/source/WorldServer/Combat.cpp @@ -1060,7 +1060,7 @@ bool Entity::DamageSpawn(Entity* victim, int8 type, int8 damage_type, int32 low_ } if (victim->GetHP() <= 0) - KillSpawn(victim, damage_type, blow_type); + KillSpawn(victim, type, damage_type, blow_type); else { victim->CheckProcs(PROC_TYPE_DEFENSIVE, this); if (spell_name) @@ -1257,7 +1257,7 @@ bool Entity::CheckInterruptSpell(Entity* attacker) { return false; } -void Entity::KillSpawn(Spawn* dead, int8 damage_type, int16 kill_blow_type) { +void Entity::KillSpawn(Spawn* dead, int8 type, int8 damage_type, int16 kill_blow_type) { if(!dead) return; @@ -1303,7 +1303,7 @@ void Entity::KillSpawn(Spawn* dead, int8 damage_type, int16 kill_blow_type) { dead->ClearRunningLocations(); dead->CalculateRunningLocation(true); - GetZone()->KillSpawn(true, dead, this, true, damage_type, kill_blow_type); + GetZone()->KillSpawn(true, dead, this, true, type, damage_type, kill_blow_type); } void Entity::HandleDeathExperienceDebt(Spawn* killer) diff --git a/EQ2/source/WorldServer/Commands/Commands.cpp b/EQ2/source/WorldServer/Commands/Commands.cpp index 68c761254..e5343cb62 100644 --- a/EQ2/source/WorldServer/Commands/Commands.cpp +++ b/EQ2/source/WorldServer/Commands/Commands.cpp @@ -2187,7 +2187,10 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie Spell* spell = master_spell_list.GetSpell(item->skill_info->spell_id, item->skill_info->spell_tier); int8 old_slot = 0; if (spell) { - + if(!spell->ScribeAllowed(client->GetPlayer())) { + client->SimpleMessage(CHANNEL_COLOR_RED, "You do not meet one of the requirements to scribe, due to class or level."); + break; + } int16 tier_up = player->GetTierUp(spell->GetSpellTier()); if (rule_manager.GetGlobalRule(R_Spells, RequirePreviousTierScribe)->GetInt8() && !player->HasSpell(spell->GetSpellID(), tier_up, false, true)) client->SimpleMessage(CHANNEL_COLOR_RED, "You have not scribed the required previous version of this ability."); @@ -2205,7 +2208,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie // force purge client cache and display updated spell for hover over EQ2Packet* app = spell->SerializeSpell(client, false, false); client->QueuePacket(app); - } + } } else LogWrite(COMMAND__ERROR, 0, "Command", "Unknown spell ID: %u and tier: %u", item->skill_info->spell_id, item->skill_info->spell_tier); @@ -2218,6 +2221,10 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie client->Message(CHANNEL_NARRATIVE, "Your tradeskill level is not high enough to scribe this book."); safe_delete(recipe_book); } + else if(recipe_book && !recipe_book->CanUseRecipeByClass(item, client->GetPlayer()->GetTradeskillClass())) { + client->Message(CHANNEL_NARRATIVE, "Your tradeskill class cannot use this recipe."); + safe_delete(recipe_book); + } else if (recipe_book && !(client->GetPlayer()->GetRecipeBookList()->HasRecipeBook(item->details.item_id))) { LogWrite(TRADESKILL__DEBUG, 0, "Recipe", "Valid recipe book that the player doesn't have"); // Add recipe book to the players list @@ -2226,43 +2233,34 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie // Get a list of all recipes this book contains vector* book_recipes = master_recipe_list.GetRecipes(recipe_book->GetBookName()); LogWrite(TRADESKILL__DEBUG, 0, "Recipe", "%i recipes found for %s book", book_recipes->size(), recipe_book->GetBookName()); - + + int16 i = 0; // Create the packet to send to update the players recipe list PacketStruct* packet = 0; if (client->GetRecipeListSent()) { packet = configReader.getStruct("WS_RecipeList", client->GetVersion()); - - if (packet) + if(packet && book_recipes->size() < 1 && item->recipebook_info) { + packet->setArrayLengthByName("num_recipes", item->recipebook_info->recipes.size()); + for(int32 r = 0; r < item->recipebook_info->recipes.size(); r++){ + Recipe* recipe = master_recipe_list.GetRecipeByName(item->recipebook_info->recipes.at(r).c_str()); + if(recipe) { + Recipe* player_recipe = new Recipe(recipe); + client->AddRecipeToPlayer(recipe, packet, &i); + } + } + } + else if (packet) packet->setArrayLengthByName("num_recipes", book_recipes->size()); } // loop through the list vector::iterator itr; - int16 i = 0; for (itr = book_recipes->begin(); itr != book_recipes->end(); itr++) { // check to see if the player already has this recipe some how if (!client->GetPlayer()->GetRecipeList()->GetRecipe((*itr)->GetID())) { // Player doesn't already have this recipe so lets add it Recipe* recipe = new Recipe(master_recipe_list.GetRecipe((*itr)->GetID())); - client->GetPlayer()->GetRecipeList()->AddRecipe(recipe); - database.SavePlayerRecipe(client->GetPlayer(), recipe->GetID()); - client->Message(CHANNEL_NARRATIVE, "Recipe: \"%s\" put in recipe book.", recipe->GetName()); - - if (packet && client->GetRecipeListSent()) { - packet->setArrayDataByName("id", recipe->GetID(), i); - packet->setArrayDataByName("tier", recipe->GetTier(), i); - packet->setArrayDataByName("level", recipe->GetLevel(), i); - packet->setArrayDataByName("icon", recipe->GetIcon(), i); - packet->setArrayDataByName("classes", recipe->GetClasses(), i); - packet->setArrayDataByName("skill", recipe->GetSkill(), i); - packet->setArrayDataByName("technique", recipe->GetTechnique(), i); - packet->setArrayDataByName("knowledge", recipe->GetKnowledge(), i); - packet->setArrayDataByName("unknown2", recipe->GetUnknown2(), i); - packet->setArrayDataByName("recipe_name", recipe->GetName(), i); - packet->setArrayDataByName("recipe_book", recipe->GetBook(), i); - packet->setArrayDataByName("unknown3", recipe->GetUnknown3(), i); - i++; - } + client->AddRecipeToPlayer(recipe, packet, &i); } } LogWrite(TRADESKILL__DEBUG, 0, "Recipe", "Done adding recipes"); diff --git a/EQ2/source/WorldServer/Entity.h b/EQ2/source/WorldServer/Entity.h index e2beb409d..71625f313 100644 --- a/EQ2/source/WorldServer/Entity.h +++ b/EQ2/source/WorldServer/Entity.h @@ -1379,7 +1379,7 @@ public: void AddHate(Entity* attacker, sint32 hate); bool CheckInterruptSpell(Entity* attacker); bool CheckFizzleSpell(LuaSpell* spell); - void KillSpawn(Spawn* dead, int8 damage_type = 0, int16 kill_blow_type = 0); + void KillSpawn(Spawn* dead, int8 type = 0, int8 damage_type = 0, int16 kill_blow_type = 0); void HandleDeathExperienceDebt(Spawn* killer); void SetAttackDelay(bool primary = false, bool ranged = false); float CalculateAttackSpeedMod(); diff --git a/EQ2/source/WorldServer/GroundSpawn.cpp b/EQ2/source/WorldServer/GroundSpawn.cpp index e283ca852..826a0b335 100644 --- a/EQ2/source/WorldServer/GroundSpawn.cpp +++ b/EQ2/source/WorldServer/GroundSpawn.cpp @@ -37,6 +37,7 @@ GroundSpawn::GroundSpawn(){ groundspawn_id = 0; MHarvest.SetName("GroundSpawn::MHarvest"); MHarvestUse.SetName("GroundSpawn::MHarvestUse"); + randomize_heading = true; // we by default randomize heading of groundspawns DB overrides } GroundSpawn::~GroundSpawn(){ diff --git a/EQ2/source/WorldServer/GroundSpawn.h b/EQ2/source/WorldServer/GroundSpawn.h index 360d65364..f8306862d 100644 --- a/EQ2/source/WorldServer/GroundSpawn.h +++ b/EQ2/source/WorldServer/GroundSpawn.h @@ -52,6 +52,7 @@ public: new_spawn->SetOmittedByDBFlag(IsOmittedByDBFlag()); new_spawn->SetLootTier(GetLootTier()); new_spawn->SetLootDropType(GetLootDropType()); + new_spawn->SetRandomizeHeading(GetRandomizeHeading()); return new_spawn; } bool IsGroundSpawn(){ return true; } @@ -69,6 +70,9 @@ public: string GetHarvestSpellType(); string GetHarvestSpellName(); void HandleUse(Client* client, string type); + + void SetRandomizeHeading(bool val) { randomize_heading = val; } + bool GetRandomizeHeading() { return randomize_heading; } private: int8 number_harvests; int8 num_attempts_per_harvest; @@ -76,6 +80,7 @@ private: string collection_skill; Mutex MHarvest; Mutex MHarvestUse; + bool randomize_heading; }; #endif diff --git a/EQ2/source/WorldServer/Player.cpp b/EQ2/source/WorldServer/Player.cpp index 07899a0d5..defddc521 100644 --- a/EQ2/source/WorldServer/Player.cpp +++ b/EQ2/source/WorldServer/Player.cpp @@ -2555,40 +2555,46 @@ void Player::ResortSpellBook(int32 sort_by, int32 order, int32 pattern, int32 ma //pattern : 0 - zigzag, 1 - down, 2 - across MSpellsBook.lock(); + std::vector sort_spells(spells); + if (!maxlvl_only) { switch (sort_by) { case 0: if (!order) - stable_sort(spells.begin(), spells.end(), SortSpellEntryByName); + stable_sort(sort_spells.begin(), sort_spells.end(), SortSpellEntryByName); else - stable_sort(spells.begin(), spells.end(), SortSpellEntryByNameReverse); + stable_sort(sort_spells.begin(), sort_spells.end(), SortSpellEntryByNameReverse); break; case 1: if (!order) - stable_sort(spells.begin(), spells.end(), SortSpellEntryByLevel); + stable_sort(sort_spells.begin(), sort_spells.end(), SortSpellEntryByLevel); else - stable_sort(spells.begin(), spells.end(), SortSpellEntryByLevelReverse); + stable_sort(sort_spells.begin(), sort_spells.end(), SortSpellEntryByLevelReverse); break; case 2: if (!order) - stable_sort(spells.begin(), spells.end(), SortSpellEntryByCategory); + stable_sort(sort_spells.begin(), sort_spells.end(), SortSpellEntryByCategory); else - stable_sort(spells.begin(), spells.end(), SortSpellEntryByCategoryReverse); + stable_sort(sort_spells.begin(), sort_spells.end(), SortSpellEntryByCategoryReverse); break; } } vector::iterator itr; SpellBookEntry* spell = 0; - int i = 0; map tmpSpells; vector resultSpells; - for (itr = spells.begin(); itr != spells.end(); itr++) { + + int32 i = 0; + int8 page_book_count = 0; + int32 last_start_point = 0; + + for (itr = sort_spells.begin(); itr != sort_spells.end(); itr++) { spell = *itr; - if (spell->type != book_type || spell->slot == -1) + if (spell->type != book_type) continue; if (maxlvl_only) @@ -2627,9 +2633,12 @@ void Player::ResortSpellBook(int32 sort_by, int32 order, int32 pattern, int32 ma resultSpells.push_back(spell); } spell->slot = i; - i++; + + Spell* tmpspell = 0; + tmpspell = master_spell_list.GetSpell(spell->spell_id, spell->tier); + GetSpellBookSlotSort(pattern, &i, &page_book_count, &last_start_point); } // end for loop for setting slots - + if (maxlvl_only) { switch (sort_by) @@ -2655,13 +2664,15 @@ void Player::ResortSpellBook(int32 sort_by, int32 order, int32 pattern, int32 ma } i = 0; + page_book_count = 0; + last_start_point = 0; vector::iterator tmpItr; for (tmpItr = resultSpells.begin(); tmpItr != resultSpells.end(); tmpItr++) { ((SpellBookEntry*)*tmpItr)->slot = i; - i++; + GetSpellBookSlotSort(pattern, &i, &page_book_count, &last_start_point); } } - + MSpellsBook.unlock(); } @@ -3162,8 +3173,8 @@ EQ2Packet* Player::GetSpellBookUpdatePacket(int16 version) { } spell_xor_packet = new uchar[count * total_bytes]; memset(spell_xor_packet, 0, count * total_bytes); - spell_count = count; } + spell_count = count; MSpellsBook.lock(); for (int32 i = 0; i < spells.size(); i++) { spell_entry = (SpellBookEntry*)spells[i]; @@ -4936,7 +4947,10 @@ void Player::RemoveQuest(int32 id, bool delete_quest){ if(delete_quest){ safe_delete(player_quests[id]); } - player_quests.erase(id); + map::iterator itr = player_quests.find(id); + if(itr != player_quests.end()) { + player_quests.erase(itr); + } MPlayerQuests.releasewritelock(__FUNCTION__, __LINE__); SendQuestRequiredSpawns(id); } @@ -5021,7 +5035,9 @@ int16 Player::GetTaskGroupStep(int32 quest_id){ MPlayerQuests.readlock(__FUNCTION__, __LINE__); if(player_quests.count(quest_id) > 0){ quest = player_quests[quest_id]; - step = quest->GetTaskGroupStep(); + if(quest) { + step = quest->GetTaskGroupStep(); + } } MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); return step; @@ -5045,7 +5061,9 @@ int16 Player::GetQuestStep(int32 quest_id){ MPlayerQuests.readlock(__FUNCTION__, __LINE__); if(player_quests.count(quest_id) > 0){ quest = player_quests[quest_id]; - step = quest->GetQuestStep(); + if(quest) { + step = quest->GetQuestStep(); + } } MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__); return step; @@ -7263,4 +7281,49 @@ int Player::GetPVPAlignment(){ return alignment; } return -1; //error +} + +void Player::GetSpellBookSlotSort(int32 pattern, int32* i, int8* page_book_count, int32* last_start_point) { + switch(pattern) { + case 1: { // down + *i = (*i) + 2; + (*page_book_count)++; + if(*page_book_count > 3) { + if(((*i) % 2) == 0) { + (*i) = (*last_start_point) + 1; + } + else { + (*last_start_point) = (*last_start_point) + 8; + (*i) = (*last_start_point); + } + (*page_book_count) = 0; + } + break; + } + case 2: { // across + (*page_book_count)++; + switch(*page_book_count) { + case 1: + case 3: { + (*i)++; + break; + } + case 2: { + (*i) = (*i) + 7; + break; + } + case 4: { + (*last_start_point) = (*last_start_point) + 2; + (*i) = (*last_start_point); + (*page_book_count) = 0; + break; + } + } + break; + } + default: { // zig-zag + (*i)++; + break; + } + } } \ No newline at end of file diff --git a/EQ2/source/WorldServer/Player.h b/EQ2/source/WorldServer/Player.h index 0ec732f48..d0cb9b7f1 100644 --- a/EQ2/source/WorldServer/Player.h +++ b/EQ2/source/WorldServer/Player.h @@ -849,7 +849,7 @@ public: } void RemoveSpellBookEntry(int32 spell_id, bool remove_passives_from_list = true); void ResortSpellBook(int32 sort_by, int32 order, int32 pattern, int32 maxlvl_only, int32 book_type); - + void GetSpellBookSlotSort(int32 pattern, int32* i, int8* page_book_count, int32* last_start_point); static bool SortSpellEntryByName(SpellBookEntry* s1, SpellBookEntry* s2); static bool SortSpellEntryByCategory(SpellBookEntry* s1, SpellBookEntry* s2); static bool SortSpellEntryByLevel(SpellBookEntry* s1, SpellBookEntry* s2); diff --git a/EQ2/source/WorldServer/Recipes/Recipe.h b/EQ2/source/WorldServer/Recipes/Recipe.h index 64c08ed04..8ccf47fa7 100644 --- a/EQ2/source/WorldServer/Recipes/Recipe.h +++ b/EQ2/source/WorldServer/Recipes/Recipe.h @@ -22,9 +22,12 @@ #include "../../common/types.h" #include "../../common/Mutex.h" +#include "../classes.h" + #include #include +class Item; using namespace std; struct RecipeProducts { @@ -85,6 +88,16 @@ public: int32 GetTechnique() {return technique;} int32 GetKnowledge() {return knowledge;} int32 GetClasses() {return classes;} + //class_id = classes.GetTSBaseClass(spawn->GetTradeskillClass()) bit-match on class ids 1-13 + //secondary_class_id = classes.GetSecondaryTSBaseClass(spawn->GetTradeskillClass()) bit-match on class ids 1-13 + //tertiary_class_id = spawn->GetTradeskillClass() (direct match) + bool CanUseRecipeByClass(Item* item, int8 class_id) { + /* any can use bit combination of 1+2 + adornments = 1 + artisan = 2 + */ + return item->generic_info.tradeskill_classes < 4 || (1 << class_id) & item->generic_info.tradeskill_classes; + } int32 GetUnknown2() {return unknown2;} int32 GetUnknown3() {return unknown3;} int32 GetUnknown4() {return unknown4;} diff --git a/EQ2/source/WorldServer/Spawn.cpp b/EQ2/source/WorldServer/Spawn.cpp index 552f0656e..fa182ed1f 100644 --- a/EQ2/source/WorldServer/Spawn.cpp +++ b/EQ2/source/WorldServer/Spawn.cpp @@ -2084,8 +2084,6 @@ void Spawn::InitializePosPacketData(Player* player, PacketStruct* packet, bool b include_heading = false; else if (IsSign() && ((Sign*)this)->GetIncludeHeading() == false) include_heading = false; - else if (IsGroundSpawn()) - include_heading = false; if (include_heading){ packet->setDataByName("pos_heading1", appearance.pos.Dir1); diff --git a/EQ2/source/WorldServer/SpellProcess.cpp b/EQ2/source/WorldServer/SpellProcess.cpp index 3ae6d964b..3d2af9b61 100644 --- a/EQ2/source/WorldServer/SpellProcess.cpp +++ b/EQ2/source/WorldServer/SpellProcess.cpp @@ -1998,7 +1998,7 @@ void SpellProcess::RemoveSpellTimersFromSpawn(Spawn* spawn, bool remove_all, boo while(itr.Next()){ interrupt = itr->value; if(interrupt && interrupt->interrupted == spawn){ - interrupt_list.Remove(interrupt); + interrupt_list.Remove(interrupt, true); } } } diff --git a/EQ2/source/WorldServer/WorldDatabase.cpp b/EQ2/source/WorldServer/WorldDatabase.cpp index e14573ef5..aa7bd1321 100644 --- a/EQ2/source/WorldServer/WorldDatabase.cpp +++ b/EQ2/source/WorldServer/WorldDatabase.cpp @@ -6699,7 +6699,7 @@ bool WorldDatabase::LoadGroundSpawn(ZoneServer* zone, int32 spawn_id) { int32 id = 0; DatabaseResult result; - database_new.Select(&result, "SELECT sg.spawn_id, s.name, s.race, s.model_type, s.command_primary, s.command_secondary, s.targetable, s.size, s.show_name, s.visual_state, s.attackable, s.show_level, s.show_command_icon, s.display_hand_icon, s.faction_id, s.collision_radius, sg.number_harvests, sg.num_attempts_per_harvest, sg.groundspawn_id, sg.collection_skill, s.size_offset, s.disable_sounds, s.aaxp_rewards, s.loot_tier, s.loot_drop_type\n" + database_new.Select(&result, "SELECT sg.spawn_id, s.name, s.race, s.model_type, s.command_primary, s.command_secondary, s.targetable, s.size, s.show_name, s.visual_state, s.attackable, s.show_level, s.show_command_icon, s.display_hand_icon, s.faction_id, s.collision_radius, sg.number_harvests, sg.num_attempts_per_harvest, sg.groundspawn_id, sg.collection_skill, s.size_offset, s.disable_sounds, s.aaxp_rewards, s.loot_tier, s.loot_drop_type, sg.randomize_heading\n" "FROM spawn s\n" "INNER JOIN spawn_ground sg\n" "ON sg.spawn_id = s.id\n" @@ -6746,6 +6746,8 @@ bool WorldDatabase::LoadGroundSpawn(ZoneServer* zone, int32 spawn_id) { spawn->SetLootDropType(result.GetInt32(24)); + spawn->SetRandomizeHeading(result.GetInt32(25)); + zone->AddGroundSpawn(id, spawn); if (!zone->GetGroundSpawnEntries(spawn->GetGroundSpawnEntryID())) diff --git a/EQ2/source/WorldServer/client.cpp b/EQ2/source/WorldServer/client.cpp index 5ea83b529..d8c79ff33 100755 --- a/EQ2/source/WorldServer/client.cpp +++ b/EQ2/source/WorldServer/client.cpp @@ -9738,6 +9738,7 @@ void Client::AcceptCollectionRewards(Collection* collection, int32 selectable_it HandInCollections(); + GetPlayer()->GetZone()->SendSubSpawnUpdates(SUBSPAWN_TYPES::COLLECTOR); } void Client::SendRecipeList() { @@ -11224,3 +11225,30 @@ void Client::UpdateCharacterRewardData(QuestRewardData* data) { data->is_temporary, data->is_collection, data->has_displayed, data->tmp_coin, data->tmp_status, database.getSafeEscapeString(data->description.c_str()).c_str(), GetCharacterID(), data->db_index, data->quest_id); } } + +void Client::AddRecipeToPlayer(Recipe* recipe, PacketStruct* packet, int16* i) { + if(recipe == nullptr) + return; + + GetPlayer()->GetRecipeList()->AddRecipe(recipe); + database.SavePlayerRecipe(GetPlayer(), recipe->GetID()); + Message(CHANNEL_NARRATIVE, "Recipe: \"%s\" put in recipe book.", recipe->GetName()); + + if (packet && GetRecipeListSent()) { + packet->setArrayDataByName("id", recipe->GetID(), *i); + packet->setArrayDataByName("tier", recipe->GetTier(), *i); + packet->setArrayDataByName("level", recipe->GetLevel(), *i); + packet->setArrayDataByName("icon", recipe->GetIcon(), *i); + packet->setArrayDataByName("classes", recipe->GetClasses(), *i); + packet->setArrayDataByName("skill", recipe->GetSkill(), *i); + packet->setArrayDataByName("technique", recipe->GetTechnique(), *i); + packet->setArrayDataByName("knowledge", recipe->GetKnowledge(), *i); + packet->setArrayDataByName("unknown2", recipe->GetUnknown2(), *i); + packet->setArrayDataByName("recipe_name", recipe->GetName(), *i); + packet->setArrayDataByName("recipe_book", recipe->GetBook(), *i); + packet->setArrayDataByName("unknown3", recipe->GetUnknown3(), *i); + if(i) { + (*i)++; + } + } +} \ No newline at end of file diff --git a/EQ2/source/WorldServer/client.h b/EQ2/source/WorldServer/client.h index 16c09325d..c1950cc6c 100644 --- a/EQ2/source/WorldServer/client.h +++ b/EQ2/source/WorldServer/client.h @@ -561,6 +561,9 @@ public: void SaveQuestRewardData(bool force_refresh = false); void UpdateCharacterRewardData(QuestRewardData* data); void SetQuestUpdateState(bool val) { quest_updates = val; } + + void AddRecipeToPlayer(Recipe* recipe, PacketStruct* packet, int16* i); + private: void SavePlayerImages(); void SkillChanged(Skill* skill, int16 previous_value, int16 new_value); diff --git a/EQ2/source/WorldServer/net.cpp b/EQ2/source/WorldServer/net.cpp index 0f89d6905..7636c5b3c 100644 --- a/EQ2/source/WorldServer/net.cpp +++ b/EQ2/source/WorldServer/net.cpp @@ -196,14 +196,40 @@ int main(int argc, char** argv) { EQOpcodeVersions = database.GetVersions(); map::iterator version_itr; int16 version1 = 0; + int16 prevVersion = 0; + std::string prevString = std::string(""); + std::string builtString = std::string(""); for (version_itr = EQOpcodeVersions.begin(); version_itr != EQOpcodeVersions.end(); version_itr++) { version1 = version_itr->first; EQOpcodeManager[version1] = new RegularOpcodeManager(); map eq = database.GetOpcodes(version1); - if(!EQOpcodeManager[version1]->LoadOpcodes(&eq)) { + std::string missingOpcodesList = std::string(""); + if(!EQOpcodeManager[version1]->LoadOpcodes(&eq, &missingOpcodesList)) { LogWrite(INIT__ERROR, 0, "Init", "Loading opcodes failed. Make sure you have sourced the opcodes.sql file!"); return false; } + + if(version1 == 0) // we don't need to display version 0 + continue; + + if(prevString.size() > 0) { + if(prevString == missingOpcodesList) { + builtString += ", " + std::to_string(version1); + } + else { + LogWrite(OPCODE__WARNING, 1, "Opcode", "Opcodes %s.", builtString.c_str()); + builtString = std::string(""); + prevString = std::string(""); + } + } + if(prevString.size() < 1) { + prevString = std::string(missingOpcodesList); + builtString = std::string(missingOpcodesList + " are missing from the opcodes table for version(s) " + std::to_string(version1)); + } + } + + if(builtString.size() > 0) { + LogWrite(OPCODE__WARNING, 1, "Opcode", "Opcodes %s.", builtString.c_str()); } LogWrite(WORLD__DEBUG, 1, "World", "-Loading structs..."); @@ -956,7 +982,7 @@ void NetConnection::WelcomeHeader() #ifdef _WIN32 SetConsoleTextAttribute(console, FOREGROUND_YELLOW_BOLD); #endif - printf("\n\nCopyright (C) 2007-2021 EQ2Emulator. https://www.eq2emu.com \n\n"); + printf("\n\nCopyright (C) 2007-2022 EQ2Emulator. https://www.eq2emu.com \n\n"); printf("EQ2Emulator is free software: you can redistribute it and/or modify\n"); printf("it under the terms of the GNU General Public License as published by\n"); printf("the Free Software Foundation, either version 3 of the License, or\n"); diff --git a/EQ2/source/WorldServer/zoneserver.cpp b/EQ2/source/WorldServer/zoneserver.cpp index 6197ae51c..842ecc148 100644 --- a/EQ2/source/WorldServer/zoneserver.cpp +++ b/EQ2/source/WorldServer/zoneserver.cpp @@ -178,6 +178,7 @@ ZoneServer::ZoneServer(const char* name, bool incoming_client) { ZoneServer::~ZoneServer() { zoneShuttingDown = true; //ensure other threads shut down too //allow other threads to properly shut down + LogWrite(ZONE__INFO, 0, "Zone", "Initiating zone shutdown of '%s'", zone_name); while (spawnthread_active || initial_spawn_threads_active > 0){ if (spawnthread_active) LogWrite(ZONE__DEBUG, 7, "Zone", "Zone shutdown waiting on spawn thread"); @@ -185,7 +186,6 @@ ZoneServer::~ZoneServer() { LogWrite(ZONE__DEBUG, 7, "Zone", "Zone shutdown waiting on initial spawn thread"); Sleep(10); } - LogWrite(ZONE__INFO, 0, "Zone", "Initiating zone shutdown of '%s'", zone_name); changed_spawns.clear(true); transport_spawns.clear(); safe_delete(tradeskillMgr); @@ -1273,6 +1273,13 @@ void ZoneServer::DeleteSpawns(bool delete_all) { if(sitr != spawn_list.end()) { spawn_list.erase(sitr); } + + if(spawn->IsCollector()) { + std::map::iterator subitr = subspawn_list[SUBSPAWN_TYPES::COLLECTOR].find(spawn->GetID()); + if(subitr != subspawn_list[SUBSPAWN_TYPES::COLLECTOR].end()) { + subspawn_list[SUBSPAWN_TYPES::COLLECTOR].erase(subitr); + } + } MSpawnList.releasewritelock(__FUNCTION__, __LINE__); safe_delete(spawn); @@ -1625,8 +1632,10 @@ bool ZoneServer::SpawnProcess(){ if (pending_spawn_list_remove.size() > 0) { MSpawnList.writelock(__FUNCTION__, __LINE__); vector::iterator itr2; - for (itr2 = pending_spawn_list_remove.begin(); itr2 != pending_spawn_list_remove.end(); itr2++) + for (itr2 = pending_spawn_list_remove.begin(); itr2 != pending_spawn_list_remove.end(); itr2++) { spawn_list.erase(*itr2); + subspawn_list[SUBSPAWN_TYPES::COLLECTOR].erase(*itr2); + } pending_spawn_list_remove.clear(); MSpawnList.releasewritelock(__FUNCTION__, __LINE__); @@ -1645,6 +1654,10 @@ bool ZoneServer::SpawnProcess(){ Spawn* spawn = *itr2; if (spawn) spawn_list[spawn->GetID()] = spawn; + + if(spawn->IsCollector()) { + subspawn_list[SUBSPAWN_TYPES::COLLECTOR].insert(make_pair(spawn->GetID(),spawn)); + } } pending_spawn_list_add.clear(); @@ -3090,6 +3103,15 @@ GroundSpawn* ZoneServer::AddGroundSpawn(SpawnLocation* spawnlocation, SpawnEntry spawn->SetSpawnEntryID(spawnentry->spawn_entry_id); spawn->SetRespawnTime(spawnentry->respawn); spawn->SetExpireTime(spawnentry->expire_time); + + if(spawn->GetRandomizeHeading()) { + float rand_heading = MakeRandomFloat(0.0f, 360.0f); + spawn->SetHeading(rand_heading); + } + else { + spawn->SetHeading(spawnlocation->heading); + } + if (spawnentry->expire_time > 0) AddSpawnExpireTimer(spawn, spawnentry->expire_time, spawnentry->expire_offset); @@ -4482,8 +4504,12 @@ void ZoneServer::Despawn(Spawn* spawn, int32 timer){ AddDeadSpawn(spawn, timer); } -void ZoneServer::KillSpawn(bool spawnListLocked, Spawn* dead, Spawn* killer, bool send_packet, int8 damage_type, int16 kill_blow_type) +void ZoneServer::KillSpawn(bool spawnListLocked, Spawn* dead, Spawn* killer, bool send_packet, int8 type, int8 damage_type, int16 kill_blow_type) { + bool isSpell = (type == DAMAGE_PACKET_TYPE_SIPHON_SPELL || type == DAMAGE_PACKET_TYPE_SIPHON_SPELL2 || + type == DAMAGE_PACKET_TYPE_SPELL_DAMAGE || type == DAMAGE_PACKET_TYPE_SPELL_CRIT_DMG || + type == DAMAGE_PACKET_TYPE_SPELL_DAMAGE2 || type == DAMAGE_PACKET_TYPE_SPELL_DAMAGE3); + MDeadSpawns.readlock(__FUNCTION__, __LINE__); if(!dead || this->dead_spawns.count(dead->GetID()) > 0) { MDeadSpawns.releasereadlock(__FUNCTION__, __LINE__); @@ -4524,7 +4550,7 @@ void ZoneServer::KillSpawn(bool spawnListLocked, Spawn* dead, Spawn* killer, boo if (dead->Alive()) return; - RemoveSpellTimersFromSpawn(dead, true, !dead->IsPlayer()); + RemoveSpellTimersFromSpawn(dead, true, !dead->IsPlayer(), true, !isSpell); if(dead->IsPlayer()) { @@ -4670,7 +4696,7 @@ void ZoneServer::KillSpawn(bool spawnListLocked, Spawn* dead, Spawn* killer, boo // Remove the support functions for the dead spawn - RemoveSpawnSupportFunctions(dead); + RemoveSpawnSupportFunctions(dead, !isSpell); // Erase the expire timer if it has one if (spawn_expire_timers.count(dead->GetID()) > 0) @@ -6294,13 +6320,21 @@ void ZoneServer::FindSpawn(Client* client, char* regSearchStr) client->SimpleMessage(CHANNEL_COLOR_RED, "Try/Catch ZoneServer::FindSpawn(Client*, char* regSearchStr) failure."); return; } + std::regex re; + try { + re = std::regex(resString, std::regex_constants::icase); + } + catch(...) { + client->SimpleMessage(CHANNEL_COLOR_RED, "Invalid regex for FindSpawn."); + return; + } + client->Message(CHANNEL_NARRATIVE, "RegEx Search Spawn List: %s", regSearchStr); client->Message(CHANNEL_NARRATIVE, "Database ID | Spawn Name | X , Y , Z"); client->Message(CHANNEL_NARRATIVE, "========================"); map::iterator itr; MSpawnList.readlock(__FUNCTION__, __LINE__); int32 spawnsFound = 0; - std::regex re(resString, std::regex_constants::icase); for (itr = spawn_list.begin(); itr != spawn_list.end(); itr++) { Spawn* spawn = itr->second; if (!spawn || !spawn->GetName()) @@ -8202,8 +8236,10 @@ void ZoneServer::ProcessSpawnRemovals() MPendingSpawnRemoval.writelock(__FUNCTION__, __LINE__); if (m_pendingSpawnRemove.size() > 0) { map::iterator itr2; - for (itr2 = m_pendingSpawnRemove.begin(); itr2 != m_pendingSpawnRemove.end(); itr2++) + for (itr2 = m_pendingSpawnRemove.begin(); itr2 != m_pendingSpawnRemove.end(); itr2++) { spawn_list.erase(itr2->first); + subspawn_list[SUBSPAWN_TYPES::COLLECTOR].erase(itr2->first); + } m_pendingSpawnRemove.clear(); } @@ -8325,3 +8361,14 @@ void ZoneServer::RemoveClientsFromZone(ZoneServer* zone) { } MClientList.releasereadlock(__FUNCTION__, __LINE__); } + +void ZoneServer::SendSubSpawnUpdates(SUBSPAWN_TYPES subtype) { + std::map::iterator subitr; + MSpawnList.readlock(__FUNCTION__, __LINE__); + for(subitr = subspawn_list[subtype].begin(); subitr != subspawn_list[subtype].end(); subitr++) { + subitr->second->changed = true; + subitr->second->info_changed = true; + AddChangedSpawn(subitr->second); + } + MSpawnList.releasereadlock(__FUNCTION__, __LINE__); +} \ No newline at end of file diff --git a/EQ2/source/WorldServer/zoneserver.h b/EQ2/source/WorldServer/zoneserver.h index 9559f1ab7..3b2db5bb1 100644 --- a/EQ2/source/WorldServer/zoneserver.h +++ b/EQ2/source/WorldServer/zoneserver.h @@ -257,6 +257,11 @@ struct ZoneInfoSlideStruct { vector slide_transition_info; }; +enum SUBSPAWN_TYPES { + COLLECTOR = 0, + MAX_SUBSPAWN_TYPE = 20 +}; + // need to attempt to clean this up and add xml comments, remove unused code, find a logical way to sort the functions maybe by get/set/process/add etc... class ZoneServer { public: @@ -334,7 +339,7 @@ public: vector GetPlayers(); - void KillSpawn(bool spawnListLocked, Spawn* dead, Spawn* killer, bool send_packet = true, int8 damage_type = 0, int16 kill_blow_type = 0); + void KillSpawn(bool spawnListLocked, Spawn* dead, Spawn* killer, bool send_packet = true, int8 type = 0, int8 damage_type = 0, int16 kill_blow_type = 0); void SendDamagePacket(Spawn* attacker, Spawn* victim, int8 type1, int8 type2, int8 damage_type, int16 damage, const char* spell_name); void SendHealPacket(Spawn* caster, Spawn* target, int16 type, int32 heal_amt, const char* spell_name); @@ -690,6 +695,8 @@ public: void StopSpawnScriptTimer(Spawn* spawn, std::string functionName); Client* RemoveZoneServerFromClient(ZoneServer* zone); + + void SendSubSpawnUpdates(SUBSPAWN_TYPES subtype); private: #ifndef WIN32 pthread_t ZoneThread; @@ -835,6 +842,9 @@ private: /* Lists */ list pending_spawn_list_add; + /* Specialized Lists to update specific scenarios */ + std::map subspawn_list[SUBSPAWN_TYPES::MAX_SUBSPAWN_TYPE]; + /* Vectors */ vector* revive_points; vector transport_spawns; diff --git a/EQ2/source/common/opcodemgr.cpp b/EQ2/source/common/opcodemgr.cpp index 8696ba1bd..9c690c821 100644 --- a/EQ2/source/common/opcodemgr.cpp +++ b/EQ2/source/common/opcodemgr.cpp @@ -41,7 +41,7 @@ using namespace std; OpcodeManager::OpcodeManager() { loaded = false; } -bool OpcodeManager::LoadOpcodesMap(map* eq, OpcodeSetStrategy *s){ +bool OpcodeManager::LoadOpcodesMap(map* eq, OpcodeSetStrategy *s, std::string* missingOpcodes){ //do the mapping and store them in the shared memory array bool ret = true; EmuOpcode emu_op; @@ -59,7 +59,17 @@ bool OpcodeManager::LoadOpcodesMap(map* eq, OpcodeSetStrategy *s //find the opcode in the file res = eq->find(op_name); if(res == eq->end()) { - LogWrite(OPCODE__WARNING, 1, "Opcode", "Opcode %s is missing from the opcodes table.", op_name); + if(missingOpcodes) { + if(missingOpcodes->size() < 1) { + missingOpcodes->append(op_name); + } + else { + missingOpcodes->append(", " + std::string(op_name)); + } + } + else { + LogWrite(OPCODE__WARNING, 1, "Opcode", "Opcode %s is missing from the opcodes table.", op_name); + } s->Set(emu_op, 0xFFFF); continue; //continue to give them a list of all missing opcodes } @@ -156,7 +166,7 @@ RegularOpcodeManager::~RegularOpcodeManager() { safe_delete_array(eq_to_emu); } -bool RegularOpcodeManager::LoadOpcodes(map* eq) { +bool RegularOpcodeManager::LoadOpcodes(map* eq, std::string* missingOpcodes) { NormalMemStrategy s; s.it = this; MOpcodes.lock(); @@ -170,7 +180,7 @@ bool RegularOpcodeManager::LoadOpcodes(map* eq) { //dont need to set eq_to_emu cause every element should get a value memset(eq_to_emu, 0, sizeof(EmuOpcode)*MAX_EQ_OPCODE); - bool ret = LoadOpcodesMap(eq, &s); + bool ret = LoadOpcodesMap(eq, &s, missingOpcodes); MOpcodes.unlock(); return ret; } @@ -263,7 +273,7 @@ NullOpcodeManager::NullOpcodeManager() : MutableOpcodeManager() { } -bool NullOpcodeManager::LoadOpcodes(map* eq) { +bool NullOpcodeManager::LoadOpcodes(map* eq, std::string* missingOpcodes) { return(true); } @@ -292,7 +302,7 @@ bool EmptyOpcodeManager::LoadOpcodes(const char *filename) { return(true); } -bool EmptyOpcodeManager::LoadOpcodes(map* eq) { +bool EmptyOpcodeManager::LoadOpcodes(map* eq, std::string* missingOpcodes) { return(true); } diff --git a/EQ2/source/common/opcodemgr.h b/EQ2/source/common/opcodemgr.h index 47c3eaf93..fe487f0e5 100644 --- a/EQ2/source/common/opcodemgr.h +++ b/EQ2/source/common/opcodemgr.h @@ -34,7 +34,7 @@ public: virtual bool Mutable() { return(false); } virtual bool LoadOpcodes(const char *filename) = 0; - virtual bool LoadOpcodes(map* eq) = 0; + virtual bool LoadOpcodes(map* eq, std::string* missingOpcodes = nullptr) = 0; virtual bool ReloadOpcodes(const char *filename) = 0; virtual uint16 EmuToEQ(const EmuOpcode emu_op) = 0; @@ -57,7 +57,7 @@ protected: //in a shared manager, this dosent protect others static bool LoadOpcodesFile(const char *filename, OpcodeSetStrategy *s); - static bool LoadOpcodesMap(map* eq, OpcodeSetStrategy *s); + static bool LoadOpcodesMap(map* eq, OpcodeSetStrategy *s, std::string* missingOpcodes = nullptr); }; class MutableOpcodeManager : public OpcodeManager { @@ -74,7 +74,7 @@ public: virtual ~SharedOpcodeManager() {} virtual bool LoadOpcodes(const char *filename); - virtual bool LoadOpcodes(map* eq); + virtual bool LoadOpcodes(map* eq, std::string* missingOpcodes = nullptr); virtual bool ReloadOpcodes(const char *filename); virtual uint16 EmuToEQ(const EmuOpcode emu_op); @@ -97,7 +97,7 @@ public: virtual bool Editable() { return(true); } virtual bool LoadOpcodes(const char *filename); - virtual bool LoadOpcodes(map* eq); + virtual bool LoadOpcodes(map* eq, std::string* missingOpcodes = nullptr); virtual bool ReloadOpcodes(const char *filename); virtual uint16 EmuToEQ(const EmuOpcode emu_op); @@ -126,7 +126,7 @@ public: NullOpcodeManager(); virtual bool LoadOpcodes(const char *filename); - virtual bool LoadOpcodes(map* eq); + virtual bool LoadOpcodes(map* eq, std::string* missingOpcodes = nullptr); virtual bool ReloadOpcodes(const char *filename); virtual uint16 EmuToEQ(const EmuOpcode emu_op); @@ -144,7 +144,7 @@ public: EmptyOpcodeManager(); virtual bool LoadOpcodes(const char *filename); - virtual bool LoadOpcodes(map* eq); + virtual bool LoadOpcodes(map* eq, std::string* missingOpcodes = nullptr); virtual bool ReloadOpcodes(const char *filename); virtual uint16 EmuToEQ(const EmuOpcode emu_op);