Browse Source

- Fix #560 - DoF Trade Support! Works cross client to AoM, restricted to 6 slots when trading with DoF. WorldStructs.xml updated!
- /info command updated with /info your_trade|their_trade slot_num for DoF trading
- Rule R_Player, CoinWeightPerHundred -> R_Player, CoinWeightPerStone, defaulted to 40 which is the DoF client equivalent (eg 100/40 = 2.5 weight)
- Concentration is now capped on zone/reload of the player spells
- Fix crash related to trade (entity pointers were loosely handled), bot or player otherwise could decouple from the zone and leave a dead ptr.
- LUA Functions added: GetCharacterFlag(Spawn, val) and ToggleCharacterFlag(Spawn, val) -- val is defined by the Character Flags in Player.h with #define CF_ or CF2_

Emagi 2 weeks ago
parent
commit
f9ddc49fcd

+ 23 - 1
EQ2/source/WorldServer/Commands/Commands.cpp

@@ -2094,6 +2094,7 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 			break;
 								}
 		case COMMAND_INFO:{
+			bool check_self_trade = true;
 			if(sep && sep->arg[1][0] && sep->IsNumber(1)){
 				if(strcmp(sep->arg[0], "inventory") == 0){
 					int32 item_index = atol(sep->arg[1]);
@@ -2268,6 +2269,26 @@ void Commands::Process(int32 index, EQ2_16BitString* command_parms, Client* clie
 					else
 						LogWrite(COMMAND__ERROR, 0, "Command", "/info effect: Unknown Spell ID: %u", spell_id);
 				}
+				else if(sep->IsNumber(1) && ((strncasecmp("your_trade", sep->arg[0], 10) == 0) ||
+						(strncasecmp("their_trade", sep->arg[0], 11) == 0)))
+				{
+					if(strncasecmp("t", sep->arg[0], 1) == 0) { // their_trade not self trade
+						check_self_trade = false;
+					}
+					
+					int8 slot_id = atoul(sep->arg[1]);
+					if(client->GetPlayer()->trade) {
+						Entity* traderToCheck = client->GetPlayer();
+						if(!check_self_trade) {
+							traderToCheck = client->GetPlayer()->trade->GetTradee(client->GetPlayer());
+						}
+						Item* tradeItem = client->GetPlayer()->trade->GetTraderSlot(traderToCheck, slot_id);
+						if(tradeItem != nullptr) {
+							EQ2Packet* app = tradeItem->serialize(client->GetVersion(), true, client->GetPlayer(), true, 0, 0, client->GetVersion() > 546 ? true : false);
+							client->QueuePacket(app);
+						}
+					}
+				}
 			}
 			else if (sep && strcmp(sep->arg[0], "overflow") == 0) {
 				Item* item = player->item_list.GetOverflowItem();
@@ -6713,7 +6734,6 @@ void Commands::Command_InspectPlayer(Client* client, Seperator* sep)
 */ 
 void Commands::Command_Inventory(Client* client, Seperator* sep, EQ2_RemoteCommandString* command)
 {
-
 	PrintSep(sep, "Command_Inventory"); // temp to figure out the params
 
 	Player* player = client->GetPlayer();
@@ -9677,6 +9697,8 @@ void Commands::Command_TradeAddItem(Client* client, Seperator* sep)
 				client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You can't trade NO-TRADE items.");
 			else if (result == 3)
 				client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You can't trade HEIRLOOM items.");
+			else if (result == 254)
+				client->Message(CHANNEL_COLOR_YELLOW, "You are trading with an older client with a %u trade slot restriction...", client->GetPlayer()->trade->MaxSlots());
 			else if (result == 255)
 				client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Unknown error trying to add the item to the trade...");
 		}

+ 18 - 4
EQ2/source/WorldServer/Entity.cpp

@@ -1644,11 +1644,17 @@ void Entity::CalculateApplyWeight() {
 		int32 coin_gold = GetInfoStruct()->get_coin_gold();
 		int32 coin_plat = GetInfoStruct()->get_coin_plat();
 		
-		float weight_per_hundred = rule_manager.GetGlobalRule(R_Player, CoinWeightPerHundred)->GetFloat();
-		if(weight_per_hundred < 0.0f) {
-			weight_per_hundred = 0.0f;
+		float weight_per_stone = rule_manager.GetGlobalRule(R_Player, CoinWeightPerStone)->GetFloat();
+		if(weight_per_stone < 0.0f) {
+			weight_per_stone = 0.0f;
 		}
-		int32 total_weight = (int32)((double)coin_copper / weight_per_hundred) + (double)((double)coin_silver / weight_per_hundred) + (double)((double)coin_gold / weight_per_hundred) + (double)((double)coin_plat / weight_per_hundred);
+		
+		double weight_copper = ((double)coin_copper / weight_per_stone);
+		double weight_silver = ((double)coin_silver / weight_per_stone);
+		double weight_gold = ((double)coin_gold / weight_per_stone);
+		double weight_platinum = ((double)coin_plat / weight_per_stone);
+		int32 total_weight = (int32)(weight_copper + weight_silver + weight_gold + weight_platinum);
+		LogWrite(PLAYER__DEBUG, 0, "Debug", "Coin Weight Calculated to: %u.  Weight_Copper: %f, Weight_Silver: %f, Weight_Gold: %f, Weight_Platinum: %f", total_weight, weight_copper, weight_silver, weight_gold, weight_platinum);
 		
 		total_weight += (int32)((double)inv_weight / 10.0);
 		
@@ -3931,3 +3937,11 @@ void Entity::SendControlEffectDetailsToClient(Client* client) {
 	}
 	client->Message(CHANNEL_COLOR_YELLOW, "-------------------------------");
 }
+
+void Entity::TerminateTrade() {
+	Trade* tmpTradePtr = trade;
+	if (tmpTradePtr) {
+		tmpTradePtr->CancelTrade(this);
+		safe_delete(tmpTradePtr);
+	}
+}

+ 4 - 0
EQ2/source/WorldServer/Entity.h

@@ -24,6 +24,7 @@
 #include "Skills.h"
 #include "MutexList.h"
 #include "MutexVector.h"
+#include "Trade.h"
 #include <set>
 #include <mutex>
 #include <vector>
@@ -1027,6 +1028,7 @@ struct InfoStruct{
 			spell_effects[i].spell = nullptr;
 		}
 	}
+	
 	// maintained via their own mutex
 	SpellEffects	spell_effects[45];
 	MaintainedEffects maintained_effects[30];
@@ -2009,6 +2011,8 @@ public:
 			}
 		}
 	}
+	
+	void TerminateTrade();
 	// when PacketStruct is fixed for C++17 this should become a shared_mutex and handle read/write lock
 	std::mutex		MEquipment;
 	std::mutex		MStats;

+ 44 - 2
EQ2/source/WorldServer/LuaFunctions.cpp

@@ -1758,7 +1758,6 @@ int EQ2Emu_lua_AddHate(lua_State* state) {
 int EQ2Emu_lua_Zone(lua_State* state) {
 	if (!lua_interface)
 		return 0;
-	LuaSpell* spell = lua_interface->GetCurrentSpell(state);
 	ZoneServer* zone = lua_interface->GetZone(state);
 	Spawn* player = lua_interface->GetSpawn(state, 2);
 	Client* client = 0;
@@ -14018,4 +14017,47 @@ int EQ2Emu_lua_SendHearCast(lua_State* state) {
 		}
 	}
 	return 0;
-}
+}
+
+int EQ2Emu_lua_GetCharacterFlag(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+
+	Spawn* player = lua_interface->GetSpawn(state);
+	sint32 flag_id = lua_interface->GetSInt32Value(state, 2);
+	lua_interface->ResetFunctionStack(state);
+
+	if (!player) {
+		lua_interface->LogError("%s: LUA HasRecipeBook command error, Spawn is not valid", lua_interface->GetScriptName(state));
+		return 0;
+	}
+	if (!player->IsPlayer()) {
+		lua_interface->LogError("%s: LUA HasRecipeBook command error, Spawn is not a player", lua_interface->GetScriptName(state));
+		return 0;
+	}
+
+	bool ret = ((Player*)player)->get_character_flag(flag_id);
+	lua_interface->SetBooleanValue(state, ret);
+	return 1;
+}
+
+int EQ2Emu_lua_ToggleCharacterFlag(lua_State* state) {
+	if (!lua_interface)
+		return 0;
+
+	Spawn* player = lua_interface->GetSpawn(state);
+	sint32 flag_id = lua_interface->GetSInt32Value(state, 2);
+	lua_interface->ResetFunctionStack(state);
+
+	if (!player) {
+		lua_interface->LogError("%s: LUA HasRecipeBook command error, Spawn is not valid", lua_interface->GetScriptName(state));
+		return 0;
+	}
+	if (!player->IsPlayer()) {
+		lua_interface->LogError("%s: LUA HasRecipeBook command error, Spawn is not a player", lua_interface->GetScriptName(state));
+		return 0;
+	}
+
+	((Player*)player)->toggle_character_flag(flag_id);
+	return 0;
+}

+ 3 - 0
EQ2/source/WorldServer/LuaFunctions.h

@@ -651,4 +651,7 @@ int EQ2Emu_lua_RemoveWidgetFromSpawnMap(lua_State* state);
 int EQ2Emu_lua_RemoveWidgetFromZoneMap(lua_State* state);
 
 int EQ2Emu_lua_SendHearCast(lua_State* state);
+
+int EQ2Emu_lua_GetCharacterFlag(lua_State* state);
+int EQ2Emu_lua_ToggleCharacterFlag(lua_State* state);
 #endif

+ 8 - 0
EQ2/source/WorldServer/LuaInterface.cpp

@@ -288,6 +288,11 @@ bool LuaInterface::LoadLuaSpell(const char* name) {
 
 		MSpells.lock();
 		if (spells.count(lua_script) > 0) {
+			
+			SetLuaUserDataStale(spells[lua_script]);
+			MSpellDelete.lock();
+			RemoveCurrentSpell(spells[lua_script]->state, false);
+			MSpellDelete.unlock();
 			lua_close(spells[lua_script]->state);
 			safe_delete(spells[lua_script]);
 		}
@@ -1558,6 +1563,9 @@ void LuaInterface::RegisterFunctions(lua_State* state) {
 	lua_register(state, "RemoveWidgetFromZoneMap", EQ2Emu_lua_RemoveWidgetFromZoneMap);
 	
 	lua_register(state, "SendHearCast", EQ2Emu_lua_SendHearCast);
+	
+	lua_register(state, "GetCharacterFlag", EQ2Emu_lua_GetCharacterFlag);
+	lua_register(state, "ToggleCharacterFlag", EQ2Emu_lua_ToggleCharacterFlag);
 }
 
 void LuaInterface::LogError(const char* error, ...)  {

+ 1 - 1
EQ2/source/WorldServer/Rules/Rules.cpp

@@ -224,7 +224,7 @@ void RuleManager::Init()
 	RULE_INIT(R_Player, BaseWeight, "50"); // base weight per class, added to max weight with the strength multiplier
 	RULE_INIT(R_Player, WeightPercentImpact, "0.01"); // overweight in stone speed impact (.01 = 1% per 1 stone)
 	RULE_INIT(R_Player, WeightPercentCap, "0.95"); // cap total impact for being overweight (.95 = 95%)
-	RULE_INIT(R_Player, CoinWeightPerHundred, "100.0"); // coin weight per stone, 100.0 = 100 coins per 1 stone
+	RULE_INIT(R_Player, CoinWeightPerStone, "40.0"); // coin weight per stone, 40.0 = 40 coins per 1 stone (per DoF client hover over)
 	RULE_INIT(R_Player, WeightInflictsSpeed, "1"); // whether weight will inflict speed, 1 = on, 0 = off
 	RULE_INIT(R_Player, LevelMasterySkillMultiplier, "5"); // multiplier for adventure level / recommended level when applying mastery damage to determine if they are in mastery range
 	

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

@@ -84,7 +84,7 @@ enum RuleType {
 	BaseWeight,
 	WeightPercentImpact,
 	WeightPercentCap,
-	CoinWeightPerHundred,
+	CoinWeightPerStone,
 	WeightInflictsSpeed,
 	LevelMasterySkillMultiplier,
 

+ 35 - 1
EQ2/source/WorldServer/Trade.cpp

@@ -13,6 +13,19 @@ Trade::Trade(Entity* trader1, Entity* trader2) {
 	this->trader1 = trader1;
 	this->trader2 = trader2;
 
+	trade_max_slots = 12;
+	
+	if(trader1->IsPlayer()) {
+		if(((Player*)trader1)->GetClient() && ((Player*)trader1)->GetClient()->GetVersion() <= 547) {
+			trade_max_slots = 6;
+		}
+	}
+	
+	if(trader2->IsPlayer()) {
+		if(((Player*)trader2)->GetClient() && ((Player*)trader2)->GetClient()->GetVersion() <= 547) {
+			trade_max_slots = 6;
+		}
+	}
 	trader1_accepted = false;
 	trader2_accepted = false;
 
@@ -32,10 +45,13 @@ int8 Trade::AddItemToTrade(Entity* character, Item* item, int8 quantity, int8 sl
 	if (slot == 255)
 		slot = GetNextFreeSlot(character);
 
-	if (slot < 0 || slot > 11) {
+	if (slot < 0) {
 		LogWrite(PLAYER__ERROR, 0, "Trade", "Player (%s) tried to add an item to an invalid trade slot (%u)", character->GetName(), slot);
 		return 255;
 	}
+	else if(slot >= trade_max_slots) {
+		return 254;
+	}
 
 	Entity* other = GetTradee(character);
 	int8 result = CheckItem(character, item, other);
@@ -261,6 +277,22 @@ void Trade::Trader2ItemAdd(Item* item, int8 quantity, int8 slot) {
 	trader2_items[slot].quantity = quantity;
 }
 
+Item* Trade::GetTraderSlot(Entity* trader, int8 slot) {
+	if(trader == GetTrader1()) {
+		map<int8, TradeItemInfo>::iterator itr = trader1_items.find(slot);
+		if(itr != trader1_items.end()) {
+			return itr->second.item;
+		}
+	}
+	else if(trader == GetTrader2()) {
+		map<int8, TradeItemInfo>::iterator itr = trader2_items.find(slot);
+		if(itr != trader2_items.end()) {
+			return itr->second.item;
+		}
+	}
+	return nullptr;
+}
+
 void Trade::CompleteTrade() {
 	map<int8, TradeItemInfo>::iterator itr;
 	vector<Item*> trader1_item_pass;
@@ -404,6 +436,7 @@ void Trade::SendTradePacket() {
 				packet->setArrayDataByName("your_item_unknown2", 1, i);
 				packet->setArrayDataByName("your_item_slot", itr->first, i);
 				packet->setArrayDataByName("your_item_id", itr->second.item->details.item_id, i);
+				packet->setArrayDataByName("your_item_name", itr->second.item->name.c_str(), i);
 				packet->setArrayDataByName("your_item_quantity", itr->second.quantity, i);
 				packet->setArrayDataByName("your_item_icon", itr->second.item->details.icon, i);
 				packet->setArrayDataByName("your_item_background", 0, i); // No clue on this value yet
@@ -430,6 +463,7 @@ void Trade::SendTradePacket() {
 				packet->setArrayDataByName("their_item_unknown2", 1, i);
 				packet->setArrayDataByName("their_item_slot", itr->first, i);
 				packet->setArrayDataByName("their_item_id", itr->second.item->details.item_id, i);
+				packet->setArrayDataByName("their_item_name", itr->second.item->name.c_str(), i);
 				packet->setArrayDataByName("their_item_quantity", itr->second.quantity, i);
 				packet->setArrayDataByName("their_item_icon", itr->second.item->details.icon, i);
 				packet->setArrayDataByName("their_item_background", 0, i); // No clue on this value yet

+ 6 - 0
EQ2/source/WorldServer/Trade.h

@@ -28,6 +28,11 @@ public:
 
 	int8 CheckItem(Entity* trader, Item* item, Entity* other);
 
+	Item* GetTraderSlot(Entity* trader, int8 slot);
+	Entity* GetTrader1() { return trader1; }
+	Entity* GetTrader2() { return trader2; }
+	
+	int8 MaxSlots() { return trade_max_slots; }
 private:
 
 
@@ -48,4 +53,5 @@ private:
 	map<int8, TradeItemInfo> trader2_items;
 	int64 trader2_coins;
 	bool trader2_accepted;
+	int32 trade_max_slots;
 };

+ 2 - 0
EQ2/source/WorldServer/WorldDatabase.cpp

@@ -8120,6 +8120,8 @@ void WorldDatabase::LoadCharacterSpellEffects(int32 char_id, Client* client, int
 
 		if ( db_spell_type == DB_TYPE_MAINTAINEDEFFECTS )
 		{
+			// restore concentration on zoning/reloading characters maintained effects
+			spellProcess->AddConcentration(lua_spell);
 			if (num_triggers > 0)
 				ClientPacketFunctions::SendMaintainedExamineUpdate(client, slot_pos, num_triggers, 0);
 			if (damage_remaining > 0)

+ 5 - 0
EQ2/source/WorldServer/zoneserver.cpp

@@ -6551,6 +6551,11 @@ void ZoneServer::RemoveSpawnSupportFunctions(Spawn* spawn, bool lock_spell_proce
 	if(!spawn)
 		return;	
 	
+	// remove entity* in trade class
+	if(spawn->IsEntity()) {
+		((Entity*)spawn)->TerminateTrade();
+	}
+	
 	if(spawn->IsPlayer() && spawn->GetZone())
 		spawn->GetZone()->RemovePlayerPassenger(((Player*)spawn)->GetCharacterID());
 	if(spawn->IsEntity())

+ 37 - 1
server/WorldStructs.xml

@@ -19265,7 +19265,43 @@ to zero and treated like placeholders." />
 <Data ElementName="update_value" Type="int32" />
 <Data ElementName="update_type" Type="int64" />
 </Struct>
-<Struct Name="WS_PlayerTrade" ClientVersion="1" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqUpdateStoreCmd"> <!-- Figured out in a 1208 client -->
+<Struct Name="WS_PlayerTrade" ClientVersion="1" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqUpdatePlayerTradeCmd">
+<Data ElementName="spawn_id" Type="int32" />
+<Data ElementName="type" Type="int16" />
+<!-- top half -->
+<Data ElementName="your_item_count" Type="int8" />
+<Data ElementName="your_item_array" Type="Array" ArraySizeVariable="your_item_count">
+  <Data ElementName="your_item_name" Type="EQ2_8Bit_String" />
+  <Data ElementName="your_item_slot" Type ="int8" />
+  <Data ElementName="your_item_unknown3" Type ="int8" Size="3" />
+  <Data ElementName="your_item_quantity" Type="int16" />
+  <Data ElementName="your_item_icon" Type="int16" />
+  <Data ElementName="your_item_unknown4" Type ="int8" Size="4" />
+  <Data ElementName="your_item_background" Type="int8" />
+  <Data ElementName="your_item_unknown4" Type ="int8" Size="7" />
+</Data>
+<Data ElementName="your_copper" Type="int32" />
+<Data ElementName="your_silver" Type="int32" />
+<Data ElementName="your_gold" Type="int32" />
+<Data ElementName="your_plat" Type="int32" />
+<!-- lower half -->
+<Data ElementName="their_item_count" Type="int8" />
+<Data ElementName="their_item_array" Type="Array" ArraySizeVariable="their_item_count">
+  <Data ElementName="their_item_name" Type="EQ2_8Bit_String" />
+  <Data ElementName="their_item_slot" Type="int8" />
+  <Data ElementName="their_item_unknown3" Type ="int8" Size="3" />
+  <Data ElementName="their_item_quantity" Type="int16" />
+  <Data ElementName="their_item_icon" Type="int16" />
+  <Data ElementName="their_item_unknown4" Type ="int8" Size="4" />
+  <Data ElementName="their_item_background" Type="int8" />
+  <Data ElementName="their_item_unknown4" Type ="int8" Size="7" />   
+</Data>
+<Data ElementName="their_copper" Type="int32" />
+<Data ElementName="their_silver" Type="int32" />
+<Data ElementName="their_gold" Type="int32" />
+<Data ElementName="their_plat" Type="int32" />
+</Struct>
+<Struct Name="WS_PlayerTrade" ClientVersion="1208" OpcodeName="OP_ClientCmdMsg" OpcodeType="OP_EqUpdateStoreCmd"> <!-- Figured out in a 1208 client -->
 <Data ElementName="spawn_id" Type="int32" />
 <Data ElementName="type" Type="int16" />
 <!-- top half -->