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.
This commit is contained in:
Image 2021-03-21 08:18:38 -04:00
parent 017a9b80ee
commit 82ccc26642
11 changed files with 206 additions and 31 deletions

View file

@ -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);

View file

@ -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 {

View file

@ -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)

View file

@ -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);

View file

@ -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;
}

View file

@ -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);

View file

@ -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");

View file

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

View file

@ -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)

View file

@ -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();
}

View file

@ -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);