diff --git a/EQ2/source/WorldServer/Commands/Commands.cpp b/EQ2/source/WorldServer/Commands/Commands.cpp index 35456d967..4d44aca88 100644 --- a/EQ2/source/WorldServer/Commands/Commands.cpp +++ b/EQ2/source/WorldServer/Commands/Commands.cpp @@ -1759,7 +1759,11 @@ 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 (!player->HasSpell(spell->GetSpellID(), spell->GetSpellTier(), true)) { + + 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."); + else if (!player->HasSpell(spell->GetSpellID(), spell->GetSpellTier(), true)) { old_slot = player->GetSpellSlot(spell->GetSpellID()); player->RemoveSpellBookEntry(spell->GetSpellID()); player->AddSpellBookEntry(spell->GetSpellID(), spell->GetSpellTier(), old_slot, spell->GetSpellData()->spell_book_type, spell->GetSpellData()->linked_timer, true); diff --git a/EQ2/source/WorldServer/Items/Items.cpp b/EQ2/source/WorldServer/Items/Items.cpp index 484405749..7edb223a8 100644 --- a/EQ2/source/WorldServer/Items/Items.cpp +++ b/EQ2/source/WorldServer/Items/Items.cpp @@ -31,6 +31,7 @@ #include #include #include +#include "../Rules/Rules.h" extern World world; extern MasterSpellList master_spell_list; @@ -38,6 +39,7 @@ extern MasterQuestList master_quest_list; extern MasterRecipeList master_recipe_list; extern ConfigReader configReader; extern LuaInterface* lua_interface; +extern RuleManager rule_manager; MasterItemList::MasterItemList(){ AddMappedItemStat(ITEM_STAT_ADORNING, std::string("adorning")); @@ -2184,16 +2186,22 @@ void Item::serialize(PacketStruct* packet, bool show_name, Player* player, int16 packet->setSubstructDataByName("header_info", "footer_type", 0); spell->SetPacketInformation(packet, player->GetZone()->GetClientBySpawn(player)); - if (player->HasSpell(skill_info->spell_id, skill_info->spell_tier)) + if (player->HasSpell(skill_info->spell_id, skill_info->spell_tier, true)) packet->setDataByName("scribed", 1); - else + if (packet->GetVersion() >= 927){ if (player->HasSpell(skill_info->spell_id, skill_info->spell_tier, true)) - packet->setAddToPacketByName("better_version", 1);// need to confirm + packet->setAddToPacketByName("scribed_better_version", 1);// need to confirm } else - packet->setAddToPacketByName("better_version", 0); //if not scribed - packet->setDataByName("require_previous", 0); + packet->setAddToPacketByName("scribed_better_version", 0); //if not scribed + + // either don't require previous tier or check that we have the lower tier spells potentially + int32 tier_up = player->GetTierUp(skill_info->spell_tier); + if (!rule_manager.GetGlobalRule(R_Spells, RequirePreviousTierScribe)->GetInt8() || player->HasSpell(skill_info->spell_id, tier_up, false, true)) + packet->setDataByName("require_previous", 1, 0); + // membership required + //packet->setDataByName("unknown_1188_2_MJ", 1, 1); } else { diff --git a/EQ2/source/WorldServer/LuaInterface.cpp b/EQ2/source/WorldServer/LuaInterface.cpp index affcb0073..1ff0a2ff8 100644 --- a/EQ2/source/WorldServer/LuaInterface.cpp +++ b/EQ2/source/WorldServer/LuaInterface.cpp @@ -629,6 +629,33 @@ void LuaInterface::RemoveSpawnScript(const char* name) { MSpawnScripts.releasewritelock(__FUNCTION__, __LINE__); } +bool LuaInterface::CallItemScript(lua_State* state, int8 num_parameters, std::string* returnValue) { + if(shutting_down) + return false; + if(!state || lua_pcall(state, num_parameters, 1, 0) != 0){ + if (state){ + const char* err = lua_tostring(state, -1); + LogError("%s: %s", GetScriptName(state), err); + lua_pop(state, 1); + } + return false; + } + + std::string result = std::string(""); + + if(lua_isstring(state, -1)){ + size_t size = 0; + const char* str = lua_tolstring(state, -1, &size); + if(str) + result = string(str); + } + + if(returnValue) + *returnValue = std::string(result); + + return true; +} + bool LuaInterface::CallItemScript(lua_State* state, int8 num_parameters, sint64* returnValue) { if(shutting_down) return false; @@ -2088,6 +2115,47 @@ bool LuaInterface::RunItemScript(string script_name, const char* function_name, return false; } +bool LuaInterface::RunItemScriptWithReturnString(string script_name, const char* function_name, Item* item, Spawn* spawn, std::string* returnValue) { + if(!item) + return false; + lua_State* state = GetItemScript(script_name.c_str(), true, true); + if(state){ + Mutex* mutex = GetItemScriptMutex(script_name.c_str()); + if(mutex) + mutex->readlock(__FUNCTION__, __LINE__); + else{ + LogError("Error getting lock for '%s'", script_name.c_str()); + UseItemScript(script_name.c_str(), state, false); + return false; + } + lua_getglobal(state, function_name); + if (!lua_isfunction(state, lua_gettop(state))){ + lua_pop(state, 1); + mutex->releasereadlock(__FUNCTION__); + UseItemScript(script_name.c_str(), state, false); + return false; + } + SetItemValue(state, item); + int8 num_parms = 1; + if(spawn){ + SetSpawnValue(state, spawn); + num_parms++; + } + if(!CallItemScript(state, num_parms, returnValue)){ + if(mutex) + mutex->releasereadlock(__FUNCTION__, __LINE__); + UseItemScript(script_name.c_str(), state, false); + return false; + } + if(mutex) + mutex->releasereadlock(__FUNCTION__, __LINE__); + UseItemScript(script_name.c_str(), state, false); + return true; + } + else + return false; +} + bool LuaInterface::RunSpawnScript(string script_name, const char* function_name, Spawn* npc, Spawn* spawn, const char* message, bool is_door_open) { if(!npc || spawn_scripts_reloading) diff --git a/EQ2/source/WorldServer/LuaInterface.h b/EQ2/source/WorldServer/LuaInterface.h index 3a8a35dc6..a2c35dc45 100644 --- a/EQ2/source/WorldServer/LuaInterface.h +++ b/EQ2/source/WorldServer/LuaInterface.h @@ -251,6 +251,8 @@ public: void RemoveSpawnScript(const char* name); bool RunItemScript(string script_name, const char* function_name, Item* item, Spawn* spawn = 0, sint64* returnValue = 0); + bool RunItemScriptWithReturnString(string script_name, const char* function_name, Item* item, Spawn* spawn = 0, std::string* returnValue = 0); + bool CallItemScript(lua_State* state, int8 num_parameters, std::string* returnValue = 0); bool CallItemScript(lua_State* state, int8 num_parameters, sint64* returnValue = 0); bool RunSpawnScript(string script_name, const char* function_name, Spawn* npc, Spawn* spawn = 0, const char* message = 0, bool is_door_open = false); bool CallSpawnScript(lua_State* state, int8 num_parameters); diff --git a/EQ2/source/WorldServer/Player.cpp b/EQ2/source/WorldServer/Player.cpp index bff45bbae..aad89e447 100644 --- a/EQ2/source/WorldServer/Player.cpp +++ b/EQ2/source/WorldServer/Player.cpp @@ -1828,8 +1828,8 @@ vector Player::UnequipItem(int16 index, sint32 bag_id, int8 slot, in bag_id = 0; } - if (item_list.items.count(bag_id) > 0 && item_list.items[bag_id][appearance_type].count(slot) > 0) - to_item = item_list.items[bag_id][appearance_type][slot]; + if (item_list.items.count(bag_id) > 0 && item_list.items[bag_id][BASE_EQUIPMENT].count(slot) > 0) + to_item = item_list.items[bag_id][BASE_EQUIPMENT][slot]; if (to_item && equipList->CanItemBeEquippedInSlot(to_item, ConvertSlotFromClient(item->details.slot_id, version))) { equipList->RemoveItem(index); if(item->details.appearance_type) @@ -2829,14 +2829,31 @@ vector* Player::GetSpellsSaveNeeded(){ return ret; } -bool Player::HasSpell(int32 spell_id, int8 tier, bool include_higher_tiers){ +int16 Player::GetTierUp(int16 tier) +{ + switch(tier) + { + case 0: + break; + case 7: + case 9: + tier -= 2; + break; + default: + tier -= 1; + break; + } + + return tier; +} +bool Player::HasSpell(int32 spell_id, int8 tier, bool include_higher_tiers, bool include_possible_scribe){ bool ret = false; vector::iterator itr; MSpellsBook.lock(); SpellBookEntry* spell = 0; for(itr = spells.begin(); itr != spells.end(); itr++){ spell = *itr; - if(spell->spell_id == spell_id && (tier == 255 || spell->tier == tier || (include_higher_tiers && spell->tier > tier))){ + if(spell->spell_id == spell_id && (tier == 255 || spell->tier == tier || (include_higher_tiers && spell->tier > tier) || (include_possible_scribe && tier <= spell->tier))){ ret = true; break; } diff --git a/EQ2/source/WorldServer/Player.h b/EQ2/source/WorldServer/Player.h index 2141643e8..ec3103486 100644 --- a/EQ2/source/WorldServer/Player.h +++ b/EQ2/source/WorldServer/Player.h @@ -682,7 +682,8 @@ public: void RemovePendingLootItem(int32 id, int32 item_id); void RemovePendingLootItems(int32 id); void AddPendingLootItems(int32 id, vector* items); - bool HasSpell(int32 spell_id, int8 tier = 255, bool include_higher_tiers = false); + int16 GetTierUp(int16 tier); + bool HasSpell(int32 spell_id, int8 tier = 255, bool include_higher_tiers = false, bool include_possible_scribe = false); bool HasRecipeBook(int32 recipe_id); void AddPlayerStatistic(int32 stat_id, sint32 stat_value, int32 stat_date); void UpdatePlayerStatistic(int32 stat_id, sint32 stat_value, bool overwrite = false); diff --git a/EQ2/source/WorldServer/Rules/Rules.cpp b/EQ2/source/WorldServer/Rules/Rules.cpp index a10e295a2..232aa6f6c 100644 --- a/EQ2/source/WorldServer/Rules/Rules.cpp +++ b/EQ2/source/WorldServer/Rules/Rules.cpp @@ -333,6 +333,7 @@ void RuleManager::Init() RULE_INIT(R_Spells, EnableCrossZoneTargetBuffs, "0"); // enables/disables allowing cross zone target buffs RULE_INIT(R_Spells, PlayerSpellSaveStateWaitInterval, "100"); // time in milliseconds we wait before performing a save when the spell save trigger is activated, allows additional actions to take place until the cap is hit RULE_INIT(R_Spells, PlayerSpellSaveStateCap, "1000"); // sets a maximum wait time before we queue a spell state save to the DB, given a lot can go on in a short period with players especially in combat, maybe good to have this at a higher interval. + RULE_INIT(R_Spells, RequirePreviousTierScribe, "0"); // requires step up apprentice -> apprentice (handcrafted?) -> journeyman (handcrafted?) -> adept -> expert -> master RULE_INIT(R_Expansion, GlobalExpansionFlag, "0"); RULE_INIT(R_Expansion, GlobalHolidayFlag, "0"); diff --git a/EQ2/source/WorldServer/Rules/Rules.h b/EQ2/source/WorldServer/Rules/Rules.h index 25b272755..95de61549 100644 --- a/EQ2/source/WorldServer/Rules/Rules.h +++ b/EQ2/source/WorldServer/Rules/Rules.h @@ -182,6 +182,7 @@ enum RuleType { EnableCrossZoneTargetBuffs, PlayerSpellSaveStateWaitInterval, PlayerSpellSaveStateCap, + RequirePreviousTierScribe, /* ZONE TIMERS */ RegenTimer, diff --git a/EQ2/source/WorldServer/client.cpp b/EQ2/source/WorldServer/client.cpp index 4bc376c68..32732c8e4 100644 --- a/EQ2/source/WorldServer/client.cpp +++ b/EQ2/source/WorldServer/client.cpp @@ -7009,10 +7009,7 @@ void Client::SendBuyMerchantList(bool sell) { sint64 overrideValue = 0; if (item->GetItemScript() && lua_interface && lua_interface->RunItemScript(item->GetItemScript(), "item_difficulty", item, player, &overrideValue)) - { item_difficulty = (sint8)overrideValue; - printf("Override difficulty: %i\n",item_difficulty); - } item_difficulty -= 6; if (item_difficulty < 0) @@ -7022,8 +7019,12 @@ void Client::SendBuyMerchantList(bool sell) { packet->setArrayDataByName("quantity", ItemInfo.quantity, i); packet->setArrayDataByName("unknown5", 255, i); packet->setArrayDataByName("stack_size2", item->stack_count, i); - if (GetVersion() <= 1096) - packet->setArrayDataByName("description", item->description.c_str(), i); + + std::string overrideValueStr; + // classic client isn't properly tracking this field, DoF we don't have it identified yet, but no field to cause any issues (can add later if identified) + if (GetVersion() >= 546 && item->GetItemScript() && lua_interface && lua_interface->RunItemScriptWithReturnString(item->GetItemScript(), "item_description", item, player, &overrideValueStr)) + packet->setArrayDataByName("description", overrideValueStr.c_str(), i); + // If no price set in the merchant_inventory table then use the old method if (ItemInfo.price_item_id == 0 && ItemInfo.price_item2_id == 0 && ItemInfo.price_coins == 0 && ItemInfo.price_status == 0 && ItemInfo.price_stationcash == 0) { sell_price = (int32)(item->sell_price * multiplier); @@ -7191,10 +7192,7 @@ void Client::SendSellMerchantList(bool sell) { sint64 overrideValue = 0; if (item->GetItemScript() && lua_interface && lua_interface->RunItemScript(item->GetItemScript(), "item_difficulty", item, player, &overrideValue)) - { item_difficulty = (sint8)overrideValue; - printf("Override difficulty: %i\n",item_difficulty); - } item_difficulty -= 6; if (item_difficulty < 0) @@ -7271,10 +7269,7 @@ void Client::SendBuyBackList(bool sell) { sint64 overrideValue = 0; if (master_item->GetItemScript() && lua_interface && lua_interface->RunItemScript(master_item->GetItemScript(), "item_difficulty", master_item, player, &overrideValue)) - { item_difficulty = (sint8)overrideValue; - printf("Override difficulty: %i\n",item_difficulty); - } item_difficulty -= 6; if (item_difficulty < 0) diff --git a/EQ2/source/WorldServer/zoneserver.cpp b/EQ2/source/WorldServer/zoneserver.cpp index 64ed743f9..f84562d36 100644 --- a/EQ2/source/WorldServer/zoneserver.cpp +++ b/EQ2/source/WorldServer/zoneserver.cpp @@ -261,6 +261,9 @@ void ZoneServer::Init() spawn_update.Start(rule_manager.GetGlobalRule(R_Zone, SpawnUpdateTimer)->GetInt16()); LogWrite(ZONE__DEBUG, 0, "Zone", "SpawnUpdateTimer: %ims", rule_manager.GetGlobalRule(R_Zone, SpawnUpdateTimer)->GetInt16()); + queue_updates.Start(rule_manager.GetGlobalRule(R_Zone, SpawnUpdateTimer)->GetInt16()); + LogWrite(ZONE__DEBUG, 0, "Zone", "QueueUpdateTimer(inherits SpawnUpdateTimer): %ims", rule_manager.GetGlobalRule(R_Zone, SpawnUpdateTimer)->GetInt16()); + spawn_delete_timer = rule_manager.GetGlobalRule(R_Zone, SpawnDeleteTimer)->GetInt32(); LogWrite(ZONE__DEBUG, 0, "Zone", "SpawnDeleteTimer: %ums", spawn_delete_timer); @@ -1596,6 +1599,8 @@ bool ZoneServer::SpawnProcess(){ movementMgr->Process(); MSpawnList.releasereadlock(__FUNCTION__, __LINE__); + if(queue_updates.Check()) + ProcessQueuedStateCommands(); // Do other loops for spawns // tracking, client loop with spawn loop for each client that is tracking, change to a spawn_range_map loop instead of using the main spawn list? //if (tracking_timer.Check()) @@ -5459,16 +5464,8 @@ void ZoneServer::SendUpdateDefaultCommand(Spawn* spawn, const char* command, flo return; } - Client* client = 0; - PacketStruct* packet = 0; - vector::iterator client_itr; + QueueDefaultCommand(spawn->GetID(), std::string(command), distance); - MClientList.readlock(__FUNCTION__, __LINE__); - for (client_itr = clients.begin(); client_itr != clients.end(); client_itr++) { - client = *client_itr; - client->SendDefaultCommand(spawn, command, distance); - } - if (strlen(command)>0) spawn->SetPrimaryCommand(command, command, distance); MClientList.releasereadlock(__FUNCTION__, __LINE__); @@ -7896,4 +7893,76 @@ void ZoneServer::AddSpawnToGroup(Spawn* spawn, int32 group_id) } groupList->Add(spawn->GetID()); spawn->SetSpawnGroupID(group_id); +} + +void ZoneServer::QueueStateCommandToClients(int32 spawn_id, int32 state) +{ + if(spawn_id < 1) + return; + + MLuaQueueStateCmd.lock(); + lua_queued_state_commands.insert(make_pair(spawn_id, state)); + MLuaQueueStateCmd.unlock(); +} + +void ZoneServer::QueueDefaultCommand(int32 spawn_id, std::string command, float distance) +{ + if(spawn_id < 1) + return; + + MLuaQueueStateCmd.lock(); + lua_spawn_update_command[spawn_id].insert(make_pair(command,distance)); + MLuaQueueStateCmd.unlock(); +} + +void ZoneServer::ProcessQueuedStateCommands() // in a client list lock only +{ + vector::iterator itr; + + MLuaQueueStateCmd.lock(); + + if(lua_queued_state_commands.size() > 0) + { + std::map::iterator statecmds; + for(statecmds = lua_queued_state_commands.begin(); statecmds != lua_queued_state_commands.end(); statecmds++) + { + Spawn* spawn = GetSpawnByID(statecmds->first, false); + if(!spawn) + continue; + + MClientList.readlock(__FUNCTION__, __LINE__); + for (itr = clients.begin(); itr != clients.end(); itr++) { + Client* client = *itr; + if (client && client->GetPlayer()->WasSentSpawn(spawn->GetID()) && !client->GetPlayer()->WasSpawnRemoved(spawn)) + ClientPacketFunctions::SendStateCommand(client, client->GetPlayer()->GetIDWithPlayerSpawn(spawn), statecmds->second); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); + } + lua_queued_state_commands.clear(); + } + + if(lua_spawn_update_command.size() > 0) + { + std::map>::iterator updatecmds; + for(updatecmds = lua_spawn_update_command.begin(); updatecmds != lua_spawn_update_command.end(); updatecmds++) + { + Spawn* spawn = GetSpawnByID(updatecmds->first, false); + if(!spawn) + continue; + + std::map::iterator innermap; + for(innermap = lua_spawn_update_command[updatecmds->first].begin(); innermap != lua_spawn_update_command[updatecmds->first].end(); innermap++) + { + MClientList.readlock(__FUNCTION__, __LINE__); + for (itr = clients.begin(); itr != clients.end(); itr++) { + Client* client = *itr; + if (client && client->GetPlayer()->WasSentSpawn(spawn->GetID()) && !client->GetPlayer()->WasSpawnRemoved(spawn)) + client->SendDefaultCommand(spawn, innermap->first.c_str(), innermap->second); + } + MClientList.releasereadlock(__FUNCTION__, __LINE__); + } + } + lua_spawn_update_command.clear(); + } + MLuaQueueStateCmd.unlock(); } \ No newline at end of file diff --git a/EQ2/source/WorldServer/zoneserver.h b/EQ2/source/WorldServer/zoneserver.h index 2cfa14e19..dc5103326 100644 --- a/EQ2/source/WorldServer/zoneserver.h +++ b/EQ2/source/WorldServer/zoneserver.h @@ -669,6 +669,10 @@ public: bool SendRemoveSpawn(Client* client, Spawn* spawn, PacketStruct* packet = 0, bool delete_spawn = false); void AddSpawnToGroup(Spawn* spawn, int32 group_id); + + void QueueStateCommandToClients(int32 spawn_id, int32 state); + void QueueDefaultCommand(int32 spawn_id, std::string command, float distance); + void ProcessQueuedStateCommands(); private: #ifndef WIN32 pthread_t ZoneThread; @@ -843,6 +847,7 @@ private: Timer tracking_timer; Timer weatherTimer; Timer widget_timer; + Timer queue_updates; /* Enums */ Instance_Type InstanceType; @@ -947,6 +952,10 @@ private: vector m_pendingSpawnRemove; Mutex MPendingSpawnRemoval; + + std::map lua_queued_state_commands; + std::map> lua_spawn_update_command; + std::mutex MLuaQueueStateCmd; public: Spawn* GetSpawn(int32 id);