Browse Source

Additional tradeskill/craft crash fixes, item db rework, source in new items db

First stage of work in issue #340
Image 3 years ago
parent
commit
5e623860d4

+ 3 - 0
EQ2/source/WorldServer/Commands/Commands.cpp

@@ -9937,6 +9937,9 @@ void Commands::Command_Test(Client* client, EQ2_16BitString* command_parms) {
 				safe_delete(packet2);
 			}
 		}
+		else if (atoi(sep->arg[0]) == 28 && sep->IsNumber(1)) {
+			World::newValue = atoi(sep->arg[1]);
+		}
 	}
 	else {
 			PacketStruct* packet2 = configReader.getStruct("WS_ExamineSpellInfo", client->GetVersion());

+ 53 - 6
EQ2/source/WorldServer/Items/Items.cpp

@@ -1488,8 +1488,8 @@ bool Item::CheckFlag(int32 flag){
 	int32 value = 0;
 	int32 flag_val = generic_info.item_flags;
 	while(flag_val>0){
-		if (flag_val >= FLAGS_32768) //change this
-			value = FLAGS_32768;
+		if (flag_val >= CURSED) //change this
+			value = CURSED;
 		else if (flag_val >= NO_TRANSMUTE) //change this
 			value = NO_TRANSMUTE;
 		else if (flag_val >= LORE_EQUIP) //change this
@@ -1581,7 +1581,7 @@ void Item::SetSlots(int32 slots){
 		AddSlot(EQ2_TEXTURES_SLOT);
 }
 
-void Item::AddStat(int8 type, int16 subtype, float value, char* name){
+void Item::AddStat(int8 type, int16 subtype, float value, int8 level, char* name){
 	char item_stat_combined_string[8] = {0};
 	if(name && strlen(name) > 0 && type != 1){
 		ItemStatString* stat = new ItemStatString;
@@ -1596,18 +1596,21 @@ void Item::AddStat(int8 type, int16 subtype, float value, char* name){
 		stat->stat_type = type;
 		stat->stat_subtype = subtype;
 		stat->value = value;
+		stat->level = level;
 		snprintf(item_stat_combined_string, 7, "%u%02u", type, subtype);
 		stat->stat_type_combined = atoi(item_stat_combined_string);
 		AddStat(stat);
 	}
 }
-void Item::AddSet(int32 item_id, int32 item_crc, int16 item_icon, int32 item_stack_size, int32 item_list_color){
+void Item::AddSet(int32 item_id, int32 item_crc, int16 item_icon, int32 item_stack_size, int32 item_list_color, std::string name, int8 language){
 	ItemSet* set = new ItemSet;
 	set->item_id = item_id;
 	set->item_icon = item_icon;
 	set->item_crc = item_crc;
 	set->item_stack_size = item_stack_size;
 	set->item_list_color = item_list_color;
+	set->name = string(name);
+	set->language = language;
 	
 	AddSet(set);
 }
@@ -1702,6 +1705,9 @@ void Item::serialize(PacketStruct* packet, bool show_name, Player* player, int16
 				if ((client->GetVersion() >= 63119) || client->GetVersion() == 61331){  //KA
 					tmp_subtype = world.GetItemStatKAValue(stat->stat_subtype);
 				}
+				else if(client->GetVersion() >= 60085 ) {
+					tmp_subtype = world.GetItemStatAOMValue(stat->stat_subtype);
+				}
 				else if (client->GetVersion() >= 57107){ //TOV
 					tmp_subtype = world.GetItemStatTOVValue(stat->stat_subtype);
 				}
@@ -1734,6 +1740,9 @@ void Item::serialize(PacketStruct* packet, bool show_name, Player* player, int16
 				if ((client->GetVersion() >= 63119) || client->GetVersion() == 61331){  //KA
 					tmp_subtype = world.GetItemStatKAValue(stat->stat_subtype);
 				}
+				else if(client->GetVersion() >= 60085 ) {
+					tmp_subtype = world.GetItemStatAOMValue(stat->stat_subtype);
+				}
 				else if (client->GetVersion() >= 57107){ //TOV
 					tmp_subtype = world.GetItemStatTOVValue(stat->stat_subtype);
 				}
@@ -2345,14 +2354,14 @@ void Item::serialize(PacketStruct* packet, bool show_name, Player* player, int16
 	if(generic_info.offers_quest_id > 0){
 		Quest* quest = master_quest_list.GetQuest(generic_info.offers_quest_id, false);
 		if(quest){
-			packet->setSubstructDataByName("footer", "offers_quest", quest->GetName());
+			packet->setSubstructDataByName("footer", "offers_quest", strlen(generic_info.offers_quest_name) ? generic_info.offers_quest_name : quest->GetName());
 			packet->setSubstructDataByName("footer", "quest_color", player->GetArrowColor(quest->GetQuestLevel()));
 		}
 	}
 	if(generic_info.part_of_quest_id > 0){
 		Quest* quest = master_quest_list.GetQuest(generic_info.part_of_quest_id, false);
 		if(quest){
-			packet->setSubstructDataByName("footer", "part_of_quest", quest->GetName());
+			packet->setSubstructDataByName("footer", "part_of_quest", strlen(generic_info.required_by_quest_name) ? generic_info.required_by_quest_name : quest->GetName());
 			packet->setSubstructDataByName("footer", "quest_color", player->GetArrowColor(quest->GetQuestLevel()));
 		}
 	}
@@ -3602,6 +3611,44 @@ Item* PlayerItemList::GetItemFromID(int32 id, int8 count, bool include_bank, boo
 	return closest;
 }
 
+sint32 PlayerItemList::GetAllStackCountItemFromID(int32 id, int8 count, bool include_bank, bool lock){
+	sint32 stack_count = 0;
+	//first check for an exact count match
+	map<sint32, map<int8, map<int16, Item*>> >::iterator itr;
+	map<int16, Item*>::iterator slot_itr;
+	if(lock)
+		MPlayerItems.readlock(__FUNCTION__, __LINE__);
+	for(itr = items.begin(); itr != items.end(); itr++){
+		if(include_bank || (!include_bank && itr->first >= 0)){
+			for(int8 i=0;i<MAX_EQUIPMENT;i++)
+			{
+				for(slot_itr=itr->second[i].begin();slot_itr!=itr->second[i].end(); slot_itr++){
+					if(slot_itr->second && slot_itr->second->details.item_id == id && (count == 0 || slot_itr->second->details.count == count)){
+						stack_count += slot_itr->second->details.count;
+					}
+				}
+			}
+		}
+	}
+
+	//couldn't find an exact match, look for closest
+	Item* closest = 0;
+	for(itr = items.begin(); itr != items.end(); itr++){
+		if(include_bank || (!include_bank && itr->first >= 0)){
+			for(int8 i=0;i<MAX_EQUIPMENT;i++)
+			{
+				for(slot_itr=itr->second[i].begin();slot_itr!=itr->second[i].end(); slot_itr++){
+					if(slot_itr->second && slot_itr->second->details.item_id == id && slot_itr->second->details.count > count && (closest == 0 || slot_itr->second->details.count < closest->details.count))
+						stack_count += slot_itr->second->details.count;
+				}
+			}
+		}
+	}
+	if(lock)
+		MPlayerItems.releasereadlock(__FUNCTION__, __LINE__);
+	return stack_count;
+}
+
 Item* PlayerItemList::GetItemFromUniqueID(int32 id, bool include_bank, bool lock){
 	map<sint32, map<int8, map<int16, Item*>> >::iterator itr;
 	map<int16, Item*>::iterator slot_itr;

+ 17 - 5
EQ2/source/WorldServer/Items/Items.h

@@ -143,7 +143,7 @@ extern MasterItemList master_item_list;
 #define STACK_LORE		4096  
 #define LORE_EQUIP		8192  
 #define NO_TRANSMUTE	16384
-#define FLAGS_32768		32768
+#define CURSED			32768
 
 #define ORNATE			1
 #define HEIRLOOM		2
@@ -680,6 +680,7 @@ struct ItemStat{
 	sint16					stat_subtype;
 	int16					stat_type_combined;
 	float					value;
+	int8					level;
 };
 struct ItemSet{
 	int32					item_id;
@@ -687,7 +688,8 @@ struct ItemSet{
 	int16					item_icon;
 	int16					item_stack_size;
 	int32					item_list_color;
-	
+	std::string				name;
+	int8					language;
 };
 struct Classifications{
 	int32					classification_id;  //classifications MJ
@@ -749,6 +751,14 @@ public:
 		int8					usable;
 		int8					harvest;
 		int8					body_drop;
+		int8					pvp_description;
+		int8					merc_only;
+		int8					mount_only;
+		int32					set_id;
+		int8					collectable_unk;
+		char					offers_quest_name[255];
+		char					required_by_quest_name[255];
+		int8					transmuted_material;
 	};
 	struct Armor_Info {
 		int16					mitigation_low;
@@ -836,7 +846,8 @@ public:
 		int16					item_icon;
 		int32					item_stack_size;
 		int32					item_list_color;
-		
+		int32					soe_item_id_unsigned;
+		int32					soe_item_crc_unsigned;
 	};
 	struct Thrown_Info{
 		sint32					range;
@@ -923,8 +934,8 @@ public:
 	void DeleteItemSets();
 	void AddSet(ItemSet* in_set);
 	void AddStatString(ItemStatString* in_stat);
-	void AddStat(int8 type, int16 subtype, float value, char* name = 0);
-	void AddSet(int32 item_id, int32 item_crc, int16 item_icon, int32 item_stack_size, int32 item_list_color);
+	void AddStat(int8 type, int16 subtype, float value, int8 level, char* name = 0);
+	void AddSet(int32 item_id, int32 item_crc, int16 item_icon, int32 item_stack_size, int32 item_list_color, std::string name, int8 language);
 	void SetWeaponType(int8 type);
 	int8 GetWeaponType();
 	bool HasSlot(int8 slot, int8 slot2 = 255);
@@ -1026,6 +1037,7 @@ public:
 	bool  MoveItem(sint32 to_bag_id, int16 from_index, sint8 to, int8 appearance_type, int8 charges);
 	Item* GetItemFromUniqueID(int32 item_id, bool include_bank = false, bool lock = true);
 	Item* GetItemFromID(int32 item_id, int8 count = 0, bool include_bank = false, bool lock = true);
+	sint32 GetAllStackCountItemFromID(int32 item_id, int8 count = 0, bool include_bank = false, bool lock = true);
 	bool  AssignItemToFreeSlot(Item* item);
 	int16 GetNumberOfFreeSlots();
 	int16 GetNumberOfItems();

+ 84 - 10
EQ2/source/WorldServer/Items/ItemsDB.cpp

@@ -199,8 +199,8 @@ void WorldDatabase::LoadDataFromRow(DatabaseResult* result, Item* item)
 	if (result->GetInt8Str("no_transmute") == 1)
 		item->generic_info.item_flags += NO_TRANSMUTE;
 
-	if (result->GetInt8Str("flags_32768") == 1)
-		item->generic_info.item_flags += FLAGS_32768;
+	if (result->GetInt8Str("CURSED_flags_32768") == 1)
+		item->generic_info.item_flags += CURSED;
 
 	if (result->GetInt8Str("ornate") == 1)
 		item->generic_info.item_flags2 += ORNATE;
@@ -285,6 +285,29 @@ void WorldDatabase::LoadDataFromRow(DatabaseResult* result, Item* item)
 	item->generic_info.body_drop				= result->GetInt8Str("body_drop");
 
 	item->no_buy_back							= (result->GetInt8Str("no_buy_back") == 1);
+
+	item->generic_info.pvp_description			= result->GetInt8Str("bPvpDesc");
+
+	item->generic_info.merc_only				= (result->GetInt8Str("merc_only") == 1);
+	item->generic_info.mount_only				= (result->GetInt8Str("mount_only") == 1);
+	
+	item->generic_info.set_id					= result->GetInt32Str("set_id");
+	
+	item->generic_info.collectable_unk			= result->GetInt8Str("collectable_unk");
+
+	const char* offerQuestName = result->GetFieldValueStr("offers_quest_name");
+	if(offerQuestName)
+		strncpy(item->generic_info.offers_quest_name,offerQuestName,255);
+	else
+		strcpy(item->generic_info.offers_quest_name,"");
+
+	const char* requiredQuestName = result->GetFieldValueStr("required_by_quest_name");
+	if(requiredQuestName)
+		strncpy(item->generic_info.required_by_quest_name,requiredQuestName,255);
+	else
+		strcpy(item->generic_info.required_by_quest_name,"");
+		
+	item->generic_info.transmuted_material		= result->GetInt8Str("transmuted_material");
 }
 
 int32 WorldDatabase::LoadSkillItems()
@@ -302,6 +325,9 @@ int32 WorldDatabase::LoadSkillItems()
 			id = atoul(row[0]);
 			Item* item = master_item_list.GetItem(id);
 
+			if(!row[1] || !row[2])
+				continue;
+
 			if(item)
 			{
 				LogWrite(ITEM__DEBUG, 5, "Items", "\tLoading Skill for item_id %u", id);
@@ -470,18 +496,20 @@ int32 WorldDatabase::LoadItemsets()
 	int32 total = 0;
 	int32 id = 0;
 
-	if (database_new.Select(&result, "SELECT id, itemset_item_id, item_id, item_icon,item_stack_size,item_list_color,language_type FROM item_details_itemset"))
+	//if (database_new.Select(&result, "SELECT id, itemset_item_id, item_id, item_icon,item_stack_size,item_list_color,language_type FROM item_details_itemset"))
+	if (database_new.Select(&result, "select crate.item_id, crateitem.reward_item_id, crateitem.icon, crateitem.stack_size, crateitem.name_color, crateitem.name, crateitem.language_type from item_details_reward_crate crate, item_details_reward_crate_item crateitem where crateitem.crate_item_id = crate.item_id"))
 	{
 		while (result.Next())
 		{
-			id = result.GetInt32Str("itemset_item_id");
+			id = result.GetInt32(0);
 			Item* item = master_item_list.GetItem(id);
 
 			if (item)
 			{
 				item->SetItemType(ITEM_TYPE_ITEMCRATE);
 				//int32 item_id = result.GetInt32Str("item_id");
-				item->AddSet(result.GetInt32Str("item_id"),0, result.GetInt16Str("item_icon"), result.GetInt16Str("item_stack_size"), result.GetInt32Str("item_list_color"));
+				const char* setName = result.GetString(5);
+				item->AddSet(result.GetInt32(1),0, result.GetInt16(2), result.GetInt16(3), result.GetInt32(4), setName ? string(setName) : string(""), result.GetInt8(6));
 				
 
 				total++;
@@ -687,7 +715,7 @@ int32 WorldDatabase::LoadRangeWeapons()
 {
 	Query query;
 	MYSQL_ROW row;
-	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, damage_low1, damage_high1, damage_low2, damage_high2, damage_low3, damage_high3, delay, damage_rating, range_low, range_high, damage_type FROM item_details_range");
+	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, dmg_low, dmg_high, dmg_mastery_low, dmg_mastery_high, dmg_base_low, dmg_base_high, delay, damage_rating, range_low, range_high, damage_type FROM item_details_range");
 	int32 total = 0;
 	int32 id = 0;
 
@@ -761,7 +789,7 @@ int32 WorldDatabase::LoadWeapons()
 {
 	Query query;
 	MYSQL_ROW row;
-	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, wield_style, damage_low1, damage_high1, damage_low2, damage_high2, damage_low3, damage_high3, delay, damage_rating, damage_type FROM  item_details_weapon");
+	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, wield_style, dmg_low, dmg_high, dmg_mastery_low, dmg_mastery_high, dmg_base_low, dmg_base_high, delay, damage_rating, damage_type FROM  item_details_weapon");
 	int32 total = 0;
 	int32 id = 0;
 
@@ -930,7 +958,7 @@ int32 WorldDatabase::LoadItemStats()
 {
 	Query query;
 	MYSQL_ROW row;
-	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, type, subtype, value, text FROM item_stats ORDER BY item_id asc");
+	MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT item_id, type, subtype, iValue, fValue, sValue, level FROM item_mod_stats ORDER BY stats_order asc");
 	int32 id = 0;
 	Item* item = 0;
 	int32 total = 0;
@@ -948,8 +976,15 @@ int32 WorldDatabase::LoadItemStats()
 			if(item)
 			{
 				LogWrite(ITEM__DEBUG, 5, "Items", "\tItem Stats for item_id %u", id);
-				LogWrite(ITEM__DEBUG, 5, "Items", "\ttype: %i, sub: %i, val: %.2f, name: %s", atoi(row[1]), atoi(row[2]), atof(row[3]), row[4]);
-				item->AddStat(atoi(row[1]), atoi(row[2]), atof(row[3]), row[4]);
+
+				float fValue = 0.0f;
+				if(row[3])
+					fValue = atof(row[3]);
+				else if(row[4])
+					fValue = atof(row[4]);
+
+				//LogWrite(ITEM__DEBUG, 5, "Items", "\ttype: %i, sub: %i, val: %.2f, name: %s", atoi(row[1]), atoi(row[2]), atof(row[3]), row[4]);
+				item->AddStat(atoi(row[1]), atoi(row[2]), fValue, atoul(row[6]), row[5]);
 				total++;
 			}
 			else
@@ -959,6 +994,42 @@ int32 WorldDatabase::LoadItemStats()
 	return total;
 }
 
+int32 WorldDatabase::LoadItemModStrings()
+{
+	DatabaseResult result;
+
+	int32 id = 0;
+	Item* item = 0;
+	int32 total = 0;
+
+	if( !database_new.Select(&result, "SELECT * FROM item_mod_strings") ) {
+		LogWrite(ITEM__ERROR, 0, "Items", "Cannot load WorldDatabase::LoadItemModStrings in %s, line: %i", __FUNCTION__, __LINE__);
+		return 0;
+	}
+	else {
+		while( result.Next() )
+		{
+			int32 item_id = result.GetInt32Str("item_id");
+			if(id != item_id)
+			{
+				item = master_item_list.GetItem(item_id);
+				id = item_id;
+			}
+			
+			const char* modName = result.GetFieldValueStr("mod");
+			if(item && modName)
+			{
+				Item::ItemStatString* stat_ = new Item::ItemStatString;
+				stat_->stat_string.data = string(modName);
+				stat_->stat_string.size = stat_->stat_string.data.length();
+				item->AddStatString(stat_);
+			}
+			total++;
+		}
+	}
+	return total;
+}
+
 void WorldDatabase::ReloadItemList() 
 {
 	LogWrite(ITEM__DEBUG, 0, "Items", "Unloading Item List...");
@@ -1020,6 +1091,9 @@ void WorldDatabase::LoadItemList()
 	LogWrite(ITEM__DEBUG, 0, "Items", "Loading Item Stats...");
 	LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Item Stats", LoadItemStats());
 
+	LogWrite(ITEM__DEBUG, 0, "Items", "Loading Item Stats Mods (Strings)...");
+	LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Item Stats", LoadItemModStrings());
+
 	LogWrite(ITEM__DEBUG, 0, "Items", "Loading Item Effects...");
 	LogWrite(ITEM__DEBUG, 0, "Items", "\tLoaded %u Item Effects", LoadItemEffects());
 

+ 27 - 11
EQ2/source/WorldServer/Tradeskills/Tradeskills.cpp

@@ -296,6 +296,16 @@ void TradeskillMgr::StopCrafting(Client* client, bool lock) {
 	int8 i = 0;
 	int8 qty = 0;
 
+	Recipe* playerRecipe = client->GetPlayer()->GetRecipeList()->GetRecipe(recipe->GetID());
+	
+	if(!playerRecipe)
+	{
+		LogWrite(TRADESKILL__ERROR, 0, "Tradeskills", "%s: TradeskillMgr::StopCrafting Error finding player recipe in their recipe book for recipe id %u", client->GetPlayer()->GetName(), recipe->GetID());
+		client->Message(CHANNEL_COLOR_RED, "%s: StopCrafting Error finding player recipe in their recipe book for recipe id %u!", client->GetPlayer()->GetName(), recipe->GetID());
+		if (lock)
+			m_tradeskills.releasewritelock(__FUNCTION__, __LINE__);
+		return;
+	}
 	// cycle through the list of used items and remove them
 	for (itr = tradeskill->usedComponents.begin(); itr != tradeskill->usedComponents.end(); itr++, i++) {
 		// get the quantity to remove, first item in the vectore is always the primary, last is always the fuel
@@ -313,21 +323,27 @@ void TradeskillMgr::StopCrafting(Client* client, bool lock) {
 			qty = recipe->GetFuelComponentQuantity();
 
 		// Get the item in the players inventory and remove or reduce the quantity
-		item = client->GetPlayer()->item_list.GetItemFromID(*itr);
-		if (item->details.count <= qty)
+		int32 itmid = *itr;
+		item = client->GetPlayer()->item_list.GetItemFromID(itmid);
+		if (item && item->details.count <= qty)
 			client->GetPlayer()->item_list.RemoveItem(item);
-		else {
+		else if(item) {
 			item->details.count -= qty;
 			item->save_needed = true;
 		}
+		else
+		{
+			LogWrite(TRADESKILL__ERROR, 0, "Tradeskills", "%s: TradeskillMgr::StopCrafting Error finding item %u to remove quantity for recipe id %u", client->GetPlayer()->GetName(), itmid, recipe->GetID());
+			client->Message(CHANNEL_COLOR_RED, "%s: StopCrafting Error finding item %u to remove quantity for recipe id %u!", client->GetPlayer()->GetName(), itmid, recipe->GetID());
+		}
 	}
 
 	item = 0;
 	qty = recipe->GetFuelComponentQuantity();	
 	item_id = recipe->components[5][0];
 	if (progress >= 400 && progress < 600) {
-		if (client->GetPlayer()->GetRecipeList()->GetRecipe(recipe->GetID())->GetHighestStage() < 1) {
-			client->GetPlayer()->GetRecipeList()->GetRecipe(recipe->GetID())->SetHighestStage(1);
+		if (playerRecipe->GetHighestStage() < 1) {
+			playerRecipe->SetHighestStage(1);
 			database.UpdatePlayerRecipe(client->GetPlayer(), recipe->GetID(), 1);
 		}
 		if (recipe->products.count(1) > 0) {
@@ -336,8 +352,8 @@ void TradeskillMgr::StopCrafting(Client* client, bool lock) {
 		}
 	}
 	else if ((dur < 200 && progress >= 600) || (dur >= 200 && progress >= 600 && progress < 800)) {
-		if (client->GetPlayer()->GetRecipeList()->GetRecipe(recipe->GetID())->GetHighestStage() < 2) {
-			client->GetPlayer()->GetRecipeList()->GetRecipe(recipe->GetID())->SetHighestStage(2);
+		if (playerRecipe->GetHighestStage() < 2) {
+			playerRecipe->SetHighestStage(2);
 			database.UpdatePlayerRecipe(client->GetPlayer(), recipe->GetID(), 2);
 		}
 		if (recipe->products.count(2) > 0) {
@@ -346,8 +362,8 @@ void TradeskillMgr::StopCrafting(Client* client, bool lock) {
 		}
 	}
 	else if ((dur >= 200 && dur < 800 && progress >= 800) || (dur >= 800 && progress >= 800 && progress < 1000)) {
-		if (client->GetPlayer()->GetRecipeList()->GetRecipe(recipe->GetID())->GetHighestStage() < 3) {
-			client->GetPlayer()->GetRecipeList()->GetRecipe(recipe->GetID())->SetHighestStage(3);
+		if (playerRecipe->GetHighestStage() < 3) {
+			playerRecipe->SetHighestStage(3);
 			database.UpdatePlayerRecipe(client->GetPlayer(), recipe->GetID(), 3);
 		}
 		if (recipe->products.count(3) > 0) {
@@ -356,8 +372,8 @@ void TradeskillMgr::StopCrafting(Client* client, bool lock) {
 		}
 	}
 	else if (dur >= 800 && progress >= 1000) {
-		if (client->GetPlayer()->GetRecipeList()->GetRecipe(recipe->GetID())->GetHighestStage() < 4) {
-			client->GetPlayer()->GetRecipeList()->GetRecipe(recipe->GetID())->SetHighestStage(4);
+		if (playerRecipe->GetHighestStage() < 4) {
+			playerRecipe->SetHighestStage(4);
 			database.UpdatePlayerRecipe(client->GetPlayer(), recipe->GetID(), 4);
 		}
 		if (recipe->products.count(4) > 0) {

+ 21 - 1
EQ2/source/WorldServer/Tradeskills/TradeskillsPackets.cpp

@@ -40,8 +40,18 @@ void ClientPacketFunctions::SendCreateFromRecipe(Client* client, int32 recipeID)
 	else
 		client->GetPlayer()->SetCurrentRecipe(recipeID);
 
+	Recipe* playerRecipe = client->GetPlayer()->GetRecipeList()->GetRecipe(recipeID);
+	
 	// Get the recipe
 	Recipe* recipe = master_recipe_list.GetRecipe(recipeID);
+
+	if(!playerRecipe)
+	{
+		LogWrite(TRADESKILL__ERROR, 0, "Tradeskills", "%s: ClientPacketFunctions::SendCreateFromRecipe Error finding player recipe %s in their recipe book for recipe id %u", client->GetPlayer()->GetName(), client->GetPlayer()->GetName(), recipe ? recipe->GetID() : 0);
+		client->Message(CHANNEL_COLOR_RED, "You do not have %s (%u) in your recipe book.", recipe ? recipe->GetName() : "Unknown", recipe ? recipe->GetID() : 0);
+		return;
+	}
+
 	if (!recipe) {
 		LogWrite(TRADESKILL__ERROR, 0, "Recipes", "Error loading recipe (%u) in ClientPacketFunctions::SendCreateFromRecipe()", recipeID);
 		return;
@@ -279,7 +289,17 @@ void ClientPacketFunctions::SendItemCreationUI(Client* client, Recipe* recipe) {
 
 	// Highest stage the player has been able to complete
 	// TODO: store this for the player, for now use 0 (none known)
-	packet->setDataByName("progress_levels_known", client->GetPlayer()->GetRecipeList()->GetRecipe(recipe->GetID())->GetHighestStage());
+	Recipe* playerRecipe = client->GetPlayer()->GetRecipeList()->GetRecipe(recipe->GetID());
+	
+	if(!playerRecipe)
+	{
+		LogWrite(TRADESKILL__ERROR, 0, "Tradeskills", "%s: ClientPacketFunctions::SendItemCreationUI Error finding player recipe in their recipe book for recipe id %u", client->GetPlayer()->GetName(), recipe->GetID());
+		client->Message(CHANNEL_COLOR_RED, "%s: SendItemCreationUI Error finding player recipe in their recipe book for recipe id %u!", client->GetPlayer()->GetName(), recipe->GetID());
+		safe_delete(packet);
+		return;
+	}
+
+	packet->setDataByName("progress_levels_known", playerRecipe ? playerRecipe->GetHighestStage() : 0);
 
 	packet->setArrayLengthByName("num_process", 4);
 	for (int8 i = 0; i < 4; i++) {

+ 52 - 0
EQ2/source/WorldServer/World.cpp

@@ -2100,6 +2100,58 @@ int32 World::LoadItemBlueStats() {
 	return count;
 }
 
+sint16 World::newValue = 27;
+
+sint16 World::GetItemStatAOMValue(sint16 subtype) {
+	sint16 tmp_subtype = subtype;
+	// this is ugly for now cause I didn't want to map it all out, see a better way later but a lot of these are just slightly shifted
+	if(subtype > 39)
+	{
+		// 88 needs to be something else (crit mitigation)
+		// 19 needs to be something else (ability reuse speed) which is 62
+		if(subtype == 21) // ITEM_STAT_MAXATTPERC
+			tmp_subtype = 20; 
+		else if(subtype == 41) // flurry
+			tmp_subtype = 39;
+		else if(subtype == 47) // flurry
+			tmp_subtype = 41;
+		else if(subtype == 49) // flurry
+			tmp_subtype = 42;
+		else if(subtype == 51) // ITEM_STAT_EXTRASHIELDBLOCKCHANCE
+			tmp_subtype = 44;
+
+			//tmp_subtype = 43 is bountiful harvest
+		else if(subtype == 54 && subtype <= 57) // ITEM_STAT_MELEECRITCHANCE
+			tmp_subtype = subtype - 7;
+		else if(subtype == 59) // ITEM_STAT_POTENCY
+			tmp_subtype = 51;
+		else if(subtype >= 61 && subtype <= 85) // ITEM_STAT_RANGEDWEAPONRANGE
+			tmp_subtype = subtype - 9; //  
+		else if(subtype >= 86 && subtype <= 101) // ITEM_STAT_WEAPONDAMAGEBONUSMELEEONLY
+			tmp_subtype = subtype - 8; //  
+		else if(subtype == 102) // ITEM_STAT_SPELLWEAPONDAMAGEBONUS
+			tmp_subtype = 77; //  
+		else if(subtype >= 103 && subtype <= 110)
+			tmp_subtype = subtype - 9;
+		else if(subtype == 122) // ITEM_STAT_ABILITYDOUBLECASTCHANCE
+			tmp_subtype = 40; //  
+		else if(subtype == 124) // ITEM_STAT_STATUSEARNED
+			tmp_subtype = 27; //  
+		else
+			tmp_subtype += 1;
+		
+		// 80 serves as ranged weapon range increase, but so does 58?
+	}
+	else if((subtype > 18 && subtype < 28) || subtype > 30) // max mana was 18
+		tmp_subtype = subtype - 1;
+	else if(subtype == 5)
+		tmp_subtype = 46;
+	else if(subtype == 4)
+		tmp_subtype = 45;
+	
+	LogWrite(PLAYER__DEBUG, 0, "Player", "Convert type: %i -> %i", subtype, tmp_subtype);
+	return tmp_subtype;
+}
 sint16 World::GetItemStatTOVValue(sint16 subtype) {
 	return (tov_itemstat_conversion[subtype] - 600);
 }

+ 2 - 0
EQ2/source/WorldServer/World.h

@@ -581,6 +581,7 @@ public:
 	void CheckLottoPlayers();
 	void PopulateTOVStatMap();
 	int32 LoadItemBlueStats();
+	sint16 GetItemStatAOMValue(sint16 subtype);
 	sint16 GetItemStatTOVValue(sint16 subtype);
 	sint16 GetItemStatDOVValue(sint16 subtype);
 	sint16 GetItemStatCOEValue(sint16 subtype);
@@ -631,6 +632,7 @@ public:
 	
 	void LoadMaps(std::string zoneFile);
 	Map* GetMap(std::string zoneFile, int32 client_version);
+	static sint16 newValue;
 private:
 	int32 suppressed_warning = 0;
 	map<string, int32> reloading_subsystems;

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

@@ -261,6 +261,7 @@ public:
 	void	ReloadItemList();
 	void	LoadItemList();
 	int32	LoadItemStats();
+	int32	LoadItemModStrings();
 	int32	LoadItemAppearances();
 	int32	LoadItemLevelOverride();
 	int32	LoadItemEffects();

+ 2 - 3
EQ2/source/common/DatabaseResult.h

@@ -42,14 +42,13 @@ public:
 
 	unsigned int GetNumRows() {return result == NULL ? 0 : (unsigned int)mysql_num_rows(result);}
 	
+	const char * GetFieldValue(unsigned int index);
+	const char * GetFieldValueStr(const char *field_name);
 private:
 	MYSQL_RES *result;
 	MYSQL_ROW row;
 	char **field_names;
 	unsigned int num_fields;
-
-	const char * GetFieldValue(unsigned int index);
-	const char * GetFieldValueStr(const char *field_name);
 };
 
 #endif