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:
parent
742f91c50f
commit
60928d2e0c
19 changed files with 278 additions and 73 deletions
1
DB/updates/spawn_ground_randomheading_aug_11_2022.sql
Normal file
1
DB/updates/spawn_ground_randomheading_aug_11_2022.sql
Normal file
|
@ -0,0 +1 @@
|
|||
alter table spawn_ground add column randomize_heading tinyint(3) unsigned default 1;
|
|
@ -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)
|
||||
|
|
|
@ -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.");
|
||||
|
@ -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
|
||||
|
@ -2227,42 +2234,33 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
|
|||
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");
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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(){
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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,7 +2633,10 @@ 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)
|
||||
|
@ -2655,10 +2664,12 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,8 +5035,10 @@ int16 Player::GetTaskGroupStep(int32 quest_id){
|
|||
MPlayerQuests.readlock(__FUNCTION__, __LINE__);
|
||||
if(player_quests.count(quest_id) > 0){
|
||||
quest = player_quests[quest_id];
|
||||
if(quest) {
|
||||
step = quest->GetTaskGroupStep();
|
||||
}
|
||||
}
|
||||
MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__);
|
||||
return step;
|
||||
}
|
||||
|
@ -5045,8 +5061,10 @@ int16 Player::GetQuestStep(int32 quest_id){
|
|||
MPlayerQuests.readlock(__FUNCTION__, __LINE__);
|
||||
if(player_quests.count(quest_id) > 0){
|
||||
quest = player_quests[quest_id];
|
||||
if(quest) {
|
||||
step = quest->GetQuestStep();
|
||||
}
|
||||
}
|
||||
MPlayerQuests.releasereadlock(__FUNCTION__, __LINE__);
|
||||
return step;
|
||||
}
|
||||
|
@ -7264,3 +7282,48 @@ int Player::GetPVPAlignment(){
|
|||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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;}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()))
|
||||
|
|
|
@ -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)++;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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__);
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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()) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue