Fix #448 - Scroll/Recipe scribing fixes

* Spells now have level and class checks
  * Recipes now have class checks (already had level checks)
  * Recipes now fallback to the item recipebook info to populate players recipes from a recipe book if the recipe book is not properly configured by book name
Fix #396 - Implemented subspawn tables so we can update specific types of spawns, in this case collectors when we remove the red book
Fix #205 - ground spawns now can have random heading assigned or forced heading
alter table spawn_ground add column randomize_heading tinyint(3) unsigned default 1;
randomize_heading = 0 uses base heading in the spawn location
randomize_heading = 1 means all spawns will have a random heading 0-360 degrees
Fix #118 - Knowledge sorting / spell book updates now correctly work.
  * Down/Across patterns now supported (zig-zag was default)
Fixed a crash when a spawn spell is interrupted and then the spawn dies to a melee attack could cause memory corruption removing spell timer pointers
Startup opcode warnings are now grouped by versioning to allow easier troubleshooting, DoF uses 546 and AoM 60085, thus most opcodes warnings are not relevant to supported cases
Fixed a regex crash on /findspawn
This commit is contained in:
Emagi 2022-08-13 10:40:04 -04:00
parent 742f91c50f
commit 60928d2e0c
19 changed files with 278 additions and 73 deletions

View file

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

View file

@ -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)

View file

@ -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<Recipe*>* 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<Recipe*>::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");

View file

@ -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();

View file

@ -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(){

View file

@ -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

View file

@ -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<SpellBookEntry*> 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<SpellBookEntry*>::iterator itr;
SpellBookEntry* spell = 0;
int i = 0;
map<string, SpellBookEntry*> tmpSpells;
vector<SpellBookEntry*> 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<SpellBookEntry*>::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<int32, Quest*>::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;
}
}
}

View file

@ -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);

View file

@ -22,9 +22,12 @@
#include "../../common/types.h"
#include "../../common/Mutex.h"
#include "../classes.h"
#include <string.h>
#include <map>
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;}

View file

@ -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);

View file

@ -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);
}
}
}

View file

@ -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()))

View file

@ -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)++;
}
}
}

View file

@ -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);

View file

@ -196,14 +196,40 @@ int main(int argc, char** argv) {
EQOpcodeVersions = database.GetVersions();
map<int16,int16>::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<string, uint16> 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");

View file

@ -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<int32, Spawn*>::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<int32>::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<int32, Spawn*>::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<int32,bool>::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<int32, Spawn*>::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__);
}

View file

@ -257,6 +257,11 @@ struct ZoneInfoSlideStruct {
vector<ZoneInfoSlideStructTransitionInfo*> 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<Entity*> 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<Spawn*> pending_spawn_list_add;
/* Specialized Lists to update specific scenarios */
std::map<int32, Spawn*> subspawn_list[SUBSPAWN_TYPES::MAX_SUBSPAWN_TYPE];
/* Vectors */
vector<RevivePoint*>* revive_points;
vector<int32> transport_spawns;

View file

@ -41,7 +41,7 @@ using namespace std;
OpcodeManager::OpcodeManager() {
loaded = false;
}
bool OpcodeManager::LoadOpcodesMap(map<string, uint16>* eq, OpcodeSetStrategy *s){
bool OpcodeManager::LoadOpcodesMap(map<string, uint16>* 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<string, uint16>* 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<string, uint16>* eq) {
bool RegularOpcodeManager::LoadOpcodes(map<string, uint16>* eq, std::string* missingOpcodes) {
NormalMemStrategy s;
s.it = this;
MOpcodes.lock();
@ -170,7 +180,7 @@ bool RegularOpcodeManager::LoadOpcodes(map<string, uint16>* 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<string, uint16>* eq) {
bool NullOpcodeManager::LoadOpcodes(map<string, uint16>* eq, std::string* missingOpcodes) {
return(true);
}
@ -292,7 +302,7 @@ bool EmptyOpcodeManager::LoadOpcodes(const char *filename) {
return(true);
}
bool EmptyOpcodeManager::LoadOpcodes(map<string, uint16>* eq) {
bool EmptyOpcodeManager::LoadOpcodes(map<string, uint16>* eq, std::string* missingOpcodes) {
return(true);
}

View file

@ -34,7 +34,7 @@ public:
virtual bool Mutable() { return(false); }
virtual bool LoadOpcodes(const char *filename) = 0;
virtual bool LoadOpcodes(map<string, uint16>* eq) = 0;
virtual bool LoadOpcodes(map<string, uint16>* 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<string, uint16>* eq, OpcodeSetStrategy *s);
static bool LoadOpcodesMap(map<string, uint16>* 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<string, uint16>* eq);
virtual bool LoadOpcodes(map<string, uint16>* 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<string, uint16>* eq);
virtual bool LoadOpcodes(map<string, uint16>* 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<string, uint16>* eq);
virtual bool LoadOpcodes(map<string, uint16>* 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<string, uint16>* eq);
virtual bool LoadOpcodes(map<string, uint16>* eq, std::string* missingOpcodes = nullptr);
virtual bool ReloadOpcodes(const char *filename);
virtual uint16 EmuToEQ(const EmuOpcode emu_op);