Browse Source

Item/Merchant/Scribing/Inventory Updates

Fix #231 - item_description function which returns a string added to ItemScripts.  Can allow setting of specialized red text, eg "You already know this language.".  Completes 231 with the other ItemScripts support (item_difficulty) and scribing support
which includes a new rule:
RULE_INIT(R_Spells, RequirePreviousTierScribe, "0"); // requires step up apprentice -> apprentice (handcrafted?) -> journeyman (handcrafted?) -> adept -> expert -> master

Fix #323 - check in main equipment (0) slots for bags only which addresses the appearance overriding bags issue.
Image 3 years ago
parent
commit
82ccc26642

+ 5 - 1
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);

+ 13 - 5
EQ2/source/WorldServer/Items/Items.cpp

@@ -31,6 +31,7 @@
 #include <algorithm>
 #include <sstream>
 #include <boost/algorithm/string.hpp>
+#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 {							

+ 68 - 0
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)

+ 2 - 0
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);

+ 21 - 4
EQ2/source/WorldServer/Player.cpp

@@ -1828,8 +1828,8 @@ vector<EQ2Packet*>	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<SpellBookEntry*>* 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<SpellBookEntry*>::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;
 		}

+ 2 - 1
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<Item*>* 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);

+ 1 - 0
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");

+ 1 - 0
EQ2/source/WorldServer/Rules/Rules.h

@@ -182,6 +182,7 @@ enum RuleType {
 	EnableCrossZoneTargetBuffs,
 	PlayerSpellSaveStateWaitInterval,
 	PlayerSpellSaveStateCap,
+	RequirePreviousTierScribe,
 
 	/* ZONE TIMERS */
 	RegenTimer,

+ 6 - 11
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)

+ 78 - 9
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<Client*>::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<Client*>::iterator itr;
+
+	MLuaQueueStateCmd.lock();
+
+	if(lua_queued_state_commands.size() > 0)
+	{
+		std::map<int32, int32>::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<int32, std::map<std::string,float>>::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<std::string,float>::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();
 }

+ 9 - 0
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<int32> m_pendingSpawnRemove;
 	Mutex MPendingSpawnRemoval;
+
+	std::map<int32, int32> lua_queued_state_commands;
+	std::map<int32, std::map<std::string, float>> lua_spawn_update_command;
+	std::mutex MLuaQueueStateCmd;
 public:
 	Spawn*				GetSpawn(int32 id);