mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-13 18:51:29 +00:00
[Items] Overhaul Item Hand-in System (#4593)
* [Items] Overhaul Item Hand-in System * Edge case lua fix * Merge fix * I'm going to be amazed if this works first try * Update linux-build.sh * Update linux-build.sh * Update linux-build.sh * Update linux-build.sh * Update linux-build.sh * Update linux-build.sh * Update linux-build.sh * Update linux-build.sh * Add protections against scripts that hand back items themselves * Remove EVENT_ITEM_ScriptStopReturn * test * Update npc_handins.cpp * Add Items:AlwaysReturnHandins * Update spdat.cpp * Bypass update prompt on CI
This commit is contained in:
parent
d1d6db3a09
commit
6fb919a16f
@ -192,7 +192,7 @@ bool DatabaseUpdate::UpdateManifest(
|
|||||||
LogInfo("{}", Strings::Repeat("-", BREAK_LENGTH));
|
LogInfo("{}", Strings::Repeat("-", BREAK_LENGTH));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (force_interactive) {
|
if (force_interactive && !std::getenv("FORCE_INTERACTIVE")) {
|
||||||
LogInfo("{}", Strings::Repeat("-", BREAK_LENGTH));
|
LogInfo("{}", Strings::Repeat("-", BREAK_LENGTH));
|
||||||
LogInfo("Some migrations require user input. Running interactively");
|
LogInfo("Some migrations require user input. Running interactively");
|
||||||
LogInfo("This is usually due to a major change that could cause data loss");
|
LogInfo("This is usually due to a major change that could cause data loss");
|
||||||
|
|||||||
@ -6491,8 +6491,19 @@ ALTER TABLE `merchantlist_temp`
|
|||||||
MODIFY COLUMN `slot` int UNSIGNED NOT NULL DEFAULT 0 AFTER `npcid`;
|
MODIFY COLUMN `slot` int UNSIGNED NOT NULL DEFAULT 0 AFTER `npcid`;
|
||||||
)",
|
)",
|
||||||
.content_schema_update = false
|
.content_schema_update = false
|
||||||
|
},
|
||||||
|
ManifestEntry{
|
||||||
|
.version = 9300,
|
||||||
|
.description = "2024_10_15_npc_types_multiquest_enabled.sql",
|
||||||
|
.check = "SHOW COLUMNS FROM `npc_types` LIKE 'multiquest_enabled'",
|
||||||
|
.condition = "empty",
|
||||||
|
.match = "",
|
||||||
|
.sql = R"(
|
||||||
|
ALTER TABLE `npc_types`
|
||||||
|
ADD COLUMN `multiquest_enabled` tinyint(1) UNSIGNED NOT NULL DEFAULT 0 AFTER `is_parcel_merchant`;
|
||||||
|
)",
|
||||||
|
.content_schema_update = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- template; copy/paste this when you need to create a new entry
|
// -- template; copy/paste this when you need to create a new entry
|
||||||
// ManifestEntry{
|
// ManifestEntry{
|
||||||
// .version = 9228,
|
// .version = 9228,
|
||||||
|
|||||||
@ -105,6 +105,8 @@ EQEmuLogSys *EQEmuLogSys::LoadLogSettingsDefaults()
|
|||||||
log_settings[Logs::QuestErrors].log_to_console = static_cast<uint8>(Logs::General);
|
log_settings[Logs::QuestErrors].log_to_console = static_cast<uint8>(Logs::General);
|
||||||
log_settings[Logs::EqTime].log_to_console = static_cast<uint8>(Logs::General);
|
log_settings[Logs::EqTime].log_to_console = static_cast<uint8>(Logs::General);
|
||||||
log_settings[Logs::EqTime].log_to_gmsay = static_cast<uint8>(Logs::General);
|
log_settings[Logs::EqTime].log_to_gmsay = static_cast<uint8>(Logs::General);
|
||||||
|
log_settings[Logs::NpcHandin].log_to_console = static_cast<uint8>(Logs::General);
|
||||||
|
log_settings[Logs::NpcHandin].log_to_gmsay = static_cast<uint8>(Logs::General);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RFC 5424
|
* RFC 5424
|
||||||
|
|||||||
@ -148,6 +148,7 @@ namespace Logs {
|
|||||||
BotSettings,
|
BotSettings,
|
||||||
BotSpellChecks,
|
BotSpellChecks,
|
||||||
BotSpellTypeChecks,
|
BotSpellTypeChecks,
|
||||||
|
NpcHandin,
|
||||||
MaxCategoryID /* Don't Remove this */
|
MaxCategoryID /* Don't Remove this */
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -254,7 +255,8 @@ namespace Logs {
|
|||||||
"KSM", // Kernel Samepage Merging
|
"KSM", // Kernel Samepage Merging
|
||||||
"Bot Settings",
|
"Bot Settings",
|
||||||
"Bot Spell Checks",
|
"Bot Spell Checks",
|
||||||
"Bot Spell Type Checks"
|
"Bot Spell Type Checks",
|
||||||
|
"NpcHandin"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -904,6 +904,16 @@
|
|||||||
OutF(LogSys, Logs::Detail, Logs::BotSpellTypeChecks, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
OutF(LogSys, Logs::Detail, Logs::BotSpellTypeChecks, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
|
#define LogNpcHandin(message, ...) do {\
|
||||||
|
if (LogSys.IsLogEnabled(Logs::General, Logs::NpcHandin))\
|
||||||
|
OutF(LogSys, Logs::General, Logs::NpcHandin, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define LogNpcHandinDetail(message, ...) do {\
|
||||||
|
if (LogSys.IsLogEnabled(Logs::Detail, Logs::NpcHandin))\
|
||||||
|
OutF(LogSys, Logs::Detail, Logs::NpcHandin, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
||||||
|
} while (0)
|
||||||
|
|
||||||
#define Log(debug_level, log_category, message, ...) do {\
|
#define Log(debug_level, log_category, message, ...) do {\
|
||||||
if (LogSys.IsLogEnabled(debug_level, log_category))\
|
if (LogSys.IsLogEnabled(debug_level, log_category))\
|
||||||
LogSys.Out(debug_level, log_category, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
LogSys.Out(debug_level, log_category, __FILE__, __func__, __LINE__, message, ##__VA_ARGS__);\
|
||||||
|
|||||||
@ -714,6 +714,18 @@ std::string PlayerEventDiscordFormatter::FormatNPCHandinEvent(
|
|||||||
h.charges > 1 ? fmt::format(" Charges: {}", h.charges) : "",
|
h.charges > 1 ? fmt::format(" Charges: {}", h.charges) : "",
|
||||||
h.attuned ? " (Attuned)" : ""
|
h.attuned ? " (Attuned)" : ""
|
||||||
);
|
);
|
||||||
|
|
||||||
|
for (int i = 0; i < h.augment_ids.size(); i++) {
|
||||||
|
if (!Strings::EqualFold(h.augment_names[i], "None")) {
|
||||||
|
const uint8 slot_id = (i + 1);
|
||||||
|
handin_items_info += fmt::format(
|
||||||
|
"Augment {}: {} ({})\n",
|
||||||
|
slot_id,
|
||||||
|
h.augment_names[i],
|
||||||
|
h.augment_ids[i]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -727,6 +739,18 @@ std::string PlayerEventDiscordFormatter::FormatNPCHandinEvent(
|
|||||||
r.charges > 1 ? fmt::format(" Charges: {}", r.charges) : "",
|
r.charges > 1 ? fmt::format(" Charges: {}", r.charges) : "",
|
||||||
r.attuned ? " (Attuned)" : ""
|
r.attuned ? " (Attuned)" : ""
|
||||||
);
|
);
|
||||||
|
|
||||||
|
for (int i = 0; i < r.augment_ids.size(); i++) {
|
||||||
|
if (!Strings::EqualFold(r.augment_names[i], "None")) {
|
||||||
|
const uint8 slot_id = (i + 1);
|
||||||
|
return_items_info += fmt::format(
|
||||||
|
"Augment {}: {} ({})\n",
|
||||||
|
slot_id,
|
||||||
|
r.augment_names[i],
|
||||||
|
r.augment_ids[i]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -220,6 +220,34 @@ bool EQ::ItemData::IsType1HWeapon() const
|
|||||||
return ((ItemType == item::ItemType1HBlunt) || (ItemType == item::ItemType1HSlash) || (ItemType == item::ItemType1HPiercing) || (ItemType == item::ItemTypeMartial));
|
return ((ItemType == item::ItemType1HBlunt) || (ItemType == item::ItemType1HSlash) || (ItemType == item::ItemType1HPiercing) || (ItemType == item::ItemTypeMartial));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool EQ::ItemData::IsPetUsable() const
|
||||||
|
{
|
||||||
|
if (ItemClass == item::ItemClassBag) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if it's a misc item and has slots, it's wearable
|
||||||
|
// this item type is conflated with many other item types
|
||||||
|
if (ItemClass == item::ItemTypeMisc && Slots != 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (ItemType) {
|
||||||
|
case item::ItemType1HBlunt:
|
||||||
|
case item::ItemType1HSlash:
|
||||||
|
case item::ItemType1HPiercing:
|
||||||
|
case item::ItemType2HBlunt:
|
||||||
|
case item::ItemType2HSlash:
|
||||||
|
case item::ItemTypeMartial:
|
||||||
|
case item::ItemTypeShield:
|
||||||
|
case item::ItemTypeArmor:
|
||||||
|
case item::ItemTypeJewelry:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool EQ::ItemData::IsType2HWeapon() const
|
bool EQ::ItemData::IsType2HWeapon() const
|
||||||
{
|
{
|
||||||
return ((ItemType == item::ItemType2HBlunt) || (ItemType == item::ItemType2HSlash) || (ItemType == item::ItemType2HPiercing));
|
return ((ItemType == item::ItemType2HBlunt) || (ItemType == item::ItemType2HSlash) || (ItemType == item::ItemType2HPiercing));
|
||||||
|
|||||||
@ -550,6 +550,7 @@ namespace EQ
|
|||||||
bool IsType1HWeapon() const;
|
bool IsType1HWeapon() const;
|
||||||
bool IsType2HWeapon() const;
|
bool IsType2HWeapon() const;
|
||||||
bool IsTypeShield() const;
|
bool IsTypeShield() const;
|
||||||
|
bool IsPetUsable() const;
|
||||||
bool IsQuestItem() const;
|
bool IsQuestItem() const;
|
||||||
|
|
||||||
static bool CheckLoreConflict(const ItemData* l_item, const ItemData* r_item);
|
static bool CheckLoreConflict(const ItemData* l_item, const ItemData* r_item);
|
||||||
|
|||||||
@ -1785,6 +1785,18 @@ std::vector<uint32> EQ::ItemInstance::GetAugmentIDs() const
|
|||||||
return augments;
|
return augments;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> EQ::ItemInstance::GetAugmentNames() const
|
||||||
|
{
|
||||||
|
std::vector<std::string> augment_names;
|
||||||
|
|
||||||
|
for (uint8 slot_id = invaug::SOCKET_BEGIN; slot_id <= invaug::SOCKET_END; slot_id++) {
|
||||||
|
const auto augment = GetAugment(slot_id);
|
||||||
|
augment_names.push_back(augment ? augment->GetItem()->Name : "None");
|
||||||
|
}
|
||||||
|
|
||||||
|
return augment_names;
|
||||||
|
}
|
||||||
|
|
||||||
int EQ::ItemInstance::GetItemRegen(bool augments) const
|
int EQ::ItemInstance::GetItemRegen(bool augments) const
|
||||||
{
|
{
|
||||||
int stat = 0;
|
int stat = 0;
|
||||||
|
|||||||
@ -305,6 +305,7 @@ namespace EQ
|
|||||||
int GetItemSkillsStat(EQ::skills::SkillType skill, bool augments = false) const;
|
int GetItemSkillsStat(EQ::skills::SkillType skill, bool augments = false) const;
|
||||||
uint32 GetItemGuildFavor() const;
|
uint32 GetItemGuildFavor() const;
|
||||||
std::vector<uint32> GetAugmentIDs() const;
|
std::vector<uint32> GetAugmentIDs() const;
|
||||||
|
std::vector<std::string> GetAugmentNames() const;
|
||||||
static void AddGUIDToMap(uint64 existing_serial_number);
|
static void AddGUIDToMap(uint64 existing_serial_number);
|
||||||
static void ClearGUIDMap();
|
static void ClearGUIDMap();
|
||||||
|
|
||||||
|
|||||||
@ -123,7 +123,7 @@ public:
|
|||||||
int8_t legtexture;
|
int8_t legtexture;
|
||||||
int8_t feettexture;
|
int8_t feettexture;
|
||||||
int8_t light;
|
int8_t light;
|
||||||
int8_t walkspeed;
|
float walkspeed;
|
||||||
int32_t peqid;
|
int32_t peqid;
|
||||||
int8_t unique_;
|
int8_t unique_;
|
||||||
int8_t fixed;
|
int8_t fixed;
|
||||||
@ -148,6 +148,7 @@ public:
|
|||||||
int32_t faction_amount;
|
int32_t faction_amount;
|
||||||
uint8_t keeps_sold_items;
|
uint8_t keeps_sold_items;
|
||||||
uint8_t is_parcel_merchant;
|
uint8_t is_parcel_merchant;
|
||||||
|
uint8_t multiquest_enabled;
|
||||||
};
|
};
|
||||||
|
|
||||||
static std::string PrimaryKey()
|
static std::string PrimaryKey()
|
||||||
@ -287,6 +288,7 @@ public:
|
|||||||
"faction_amount",
|
"faction_amount",
|
||||||
"keeps_sold_items",
|
"keeps_sold_items",
|
||||||
"is_parcel_merchant",
|
"is_parcel_merchant",
|
||||||
|
"multiquest_enabled",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -422,6 +424,7 @@ public:
|
|||||||
"faction_amount",
|
"faction_amount",
|
||||||
"keeps_sold_items",
|
"keeps_sold_items",
|
||||||
"is_parcel_merchant",
|
"is_parcel_merchant",
|
||||||
|
"multiquest_enabled",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -591,6 +594,7 @@ public:
|
|||||||
e.faction_amount = 0;
|
e.faction_amount = 0;
|
||||||
e.keeps_sold_items = 1;
|
e.keeps_sold_items = 1;
|
||||||
e.is_parcel_merchant = 0;
|
e.is_parcel_merchant = 0;
|
||||||
|
e.multiquest_enabled = 0;
|
||||||
|
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
@ -731,7 +735,7 @@ public:
|
|||||||
e.legtexture = row[101] ? static_cast<int8_t>(atoi(row[101])) : 0;
|
e.legtexture = row[101] ? static_cast<int8_t>(atoi(row[101])) : 0;
|
||||||
e.feettexture = row[102] ? static_cast<int8_t>(atoi(row[102])) : 0;
|
e.feettexture = row[102] ? static_cast<int8_t>(atoi(row[102])) : 0;
|
||||||
e.light = row[103] ? static_cast<int8_t>(atoi(row[103])) : 0;
|
e.light = row[103] ? static_cast<int8_t>(atoi(row[103])) : 0;
|
||||||
e.walkspeed = row[104] ? static_cast<int8_t>(atoi(row[104])) : 0;
|
e.walkspeed = row[104] ? strtof(row[104], nullptr) : 0;
|
||||||
e.peqid = row[105] ? static_cast<int32_t>(atoi(row[105])) : 0;
|
e.peqid = row[105] ? static_cast<int32_t>(atoi(row[105])) : 0;
|
||||||
e.unique_ = row[106] ? static_cast<int8_t>(atoi(row[106])) : 0;
|
e.unique_ = row[106] ? static_cast<int8_t>(atoi(row[106])) : 0;
|
||||||
e.fixed = row[107] ? static_cast<int8_t>(atoi(row[107])) : 0;
|
e.fixed = row[107] ? static_cast<int8_t>(atoi(row[107])) : 0;
|
||||||
@ -756,6 +760,7 @@ public:
|
|||||||
e.faction_amount = row[126] ? static_cast<int32_t>(atoi(row[126])) : 0;
|
e.faction_amount = row[126] ? static_cast<int32_t>(atoi(row[126])) : 0;
|
||||||
e.keeps_sold_items = row[127] ? static_cast<uint8_t>(strtoul(row[127], nullptr, 10)) : 1;
|
e.keeps_sold_items = row[127] ? static_cast<uint8_t>(strtoul(row[127], nullptr, 10)) : 1;
|
||||||
e.is_parcel_merchant = row[128] ? static_cast<uint8_t>(strtoul(row[128], nullptr, 10)) : 0;
|
e.is_parcel_merchant = row[128] ? static_cast<uint8_t>(strtoul(row[128], nullptr, 10)) : 0;
|
||||||
|
e.multiquest_enabled = row[129] ? static_cast<uint8_t>(strtoul(row[129], nullptr, 10)) : 0;
|
||||||
|
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
@ -917,6 +922,7 @@ public:
|
|||||||
v.push_back(columns[126] + " = " + std::to_string(e.faction_amount));
|
v.push_back(columns[126] + " = " + std::to_string(e.faction_amount));
|
||||||
v.push_back(columns[127] + " = " + std::to_string(e.keeps_sold_items));
|
v.push_back(columns[127] + " = " + std::to_string(e.keeps_sold_items));
|
||||||
v.push_back(columns[128] + " = " + std::to_string(e.is_parcel_merchant));
|
v.push_back(columns[128] + " = " + std::to_string(e.is_parcel_merchant));
|
||||||
|
v.push_back(columns[129] + " = " + std::to_string(e.multiquest_enabled));
|
||||||
|
|
||||||
auto results = db.QueryDatabase(
|
auto results = db.QueryDatabase(
|
||||||
fmt::format(
|
fmt::format(
|
||||||
@ -1067,6 +1073,7 @@ public:
|
|||||||
v.push_back(std::to_string(e.faction_amount));
|
v.push_back(std::to_string(e.faction_amount));
|
||||||
v.push_back(std::to_string(e.keeps_sold_items));
|
v.push_back(std::to_string(e.keeps_sold_items));
|
||||||
v.push_back(std::to_string(e.is_parcel_merchant));
|
v.push_back(std::to_string(e.is_parcel_merchant));
|
||||||
|
v.push_back(std::to_string(e.multiquest_enabled));
|
||||||
|
|
||||||
auto results = db.QueryDatabase(
|
auto results = db.QueryDatabase(
|
||||||
fmt::format(
|
fmt::format(
|
||||||
@ -1225,6 +1232,7 @@ public:
|
|||||||
v.push_back(std::to_string(e.faction_amount));
|
v.push_back(std::to_string(e.faction_amount));
|
||||||
v.push_back(std::to_string(e.keeps_sold_items));
|
v.push_back(std::to_string(e.keeps_sold_items));
|
||||||
v.push_back(std::to_string(e.is_parcel_merchant));
|
v.push_back(std::to_string(e.is_parcel_merchant));
|
||||||
|
v.push_back(std::to_string(e.multiquest_enabled));
|
||||||
|
|
||||||
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
|
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
|
||||||
}
|
}
|
||||||
@ -1362,7 +1370,7 @@ public:
|
|||||||
e.legtexture = row[101] ? static_cast<int8_t>(atoi(row[101])) : 0;
|
e.legtexture = row[101] ? static_cast<int8_t>(atoi(row[101])) : 0;
|
||||||
e.feettexture = row[102] ? static_cast<int8_t>(atoi(row[102])) : 0;
|
e.feettexture = row[102] ? static_cast<int8_t>(atoi(row[102])) : 0;
|
||||||
e.light = row[103] ? static_cast<int8_t>(atoi(row[103])) : 0;
|
e.light = row[103] ? static_cast<int8_t>(atoi(row[103])) : 0;
|
||||||
e.walkspeed = row[104] ? static_cast<int8_t>(atoi(row[104])) : 0;
|
e.walkspeed = row[104] ? strtof(row[104], nullptr) : 0;
|
||||||
e.peqid = row[105] ? static_cast<int32_t>(atoi(row[105])) : 0;
|
e.peqid = row[105] ? static_cast<int32_t>(atoi(row[105])) : 0;
|
||||||
e.unique_ = row[106] ? static_cast<int8_t>(atoi(row[106])) : 0;
|
e.unique_ = row[106] ? static_cast<int8_t>(atoi(row[106])) : 0;
|
||||||
e.fixed = row[107] ? static_cast<int8_t>(atoi(row[107])) : 0;
|
e.fixed = row[107] ? static_cast<int8_t>(atoi(row[107])) : 0;
|
||||||
@ -1387,6 +1395,7 @@ public:
|
|||||||
e.faction_amount = row[126] ? static_cast<int32_t>(atoi(row[126])) : 0;
|
e.faction_amount = row[126] ? static_cast<int32_t>(atoi(row[126])) : 0;
|
||||||
e.keeps_sold_items = row[127] ? static_cast<uint8_t>(strtoul(row[127], nullptr, 10)) : 1;
|
e.keeps_sold_items = row[127] ? static_cast<uint8_t>(strtoul(row[127], nullptr, 10)) : 1;
|
||||||
e.is_parcel_merchant = row[128] ? static_cast<uint8_t>(strtoul(row[128], nullptr, 10)) : 0;
|
e.is_parcel_merchant = row[128] ? static_cast<uint8_t>(strtoul(row[128], nullptr, 10)) : 0;
|
||||||
|
e.multiquest_enabled = row[129] ? static_cast<uint8_t>(strtoul(row[129], nullptr, 10)) : 0;
|
||||||
|
|
||||||
all_entries.push_back(e);
|
all_entries.push_back(e);
|
||||||
}
|
}
|
||||||
@ -1515,7 +1524,7 @@ public:
|
|||||||
e.legtexture = row[101] ? static_cast<int8_t>(atoi(row[101])) : 0;
|
e.legtexture = row[101] ? static_cast<int8_t>(atoi(row[101])) : 0;
|
||||||
e.feettexture = row[102] ? static_cast<int8_t>(atoi(row[102])) : 0;
|
e.feettexture = row[102] ? static_cast<int8_t>(atoi(row[102])) : 0;
|
||||||
e.light = row[103] ? static_cast<int8_t>(atoi(row[103])) : 0;
|
e.light = row[103] ? static_cast<int8_t>(atoi(row[103])) : 0;
|
||||||
e.walkspeed = row[104] ? static_cast<int8_t>(atoi(row[104])) : 0;
|
e.walkspeed = row[104] ? strtof(row[104], nullptr) : 0;
|
||||||
e.peqid = row[105] ? static_cast<int32_t>(atoi(row[105])) : 0;
|
e.peqid = row[105] ? static_cast<int32_t>(atoi(row[105])) : 0;
|
||||||
e.unique_ = row[106] ? static_cast<int8_t>(atoi(row[106])) : 0;
|
e.unique_ = row[106] ? static_cast<int8_t>(atoi(row[106])) : 0;
|
||||||
e.fixed = row[107] ? static_cast<int8_t>(atoi(row[107])) : 0;
|
e.fixed = row[107] ? static_cast<int8_t>(atoi(row[107])) : 0;
|
||||||
@ -1540,6 +1549,7 @@ public:
|
|||||||
e.faction_amount = row[126] ? static_cast<int32_t>(atoi(row[126])) : 0;
|
e.faction_amount = row[126] ? static_cast<int32_t>(atoi(row[126])) : 0;
|
||||||
e.keeps_sold_items = row[127] ? static_cast<uint8_t>(strtoul(row[127], nullptr, 10)) : 1;
|
e.keeps_sold_items = row[127] ? static_cast<uint8_t>(strtoul(row[127], nullptr, 10)) : 1;
|
||||||
e.is_parcel_merchant = row[128] ? static_cast<uint8_t>(strtoul(row[128], nullptr, 10)) : 0;
|
e.is_parcel_merchant = row[128] ? static_cast<uint8_t>(strtoul(row[128], nullptr, 10)) : 0;
|
||||||
|
e.multiquest_enabled = row[129] ? static_cast<uint8_t>(strtoul(row[129], nullptr, 10)) : 0;
|
||||||
|
|
||||||
all_entries.push_back(e);
|
all_entries.push_back(e);
|
||||||
}
|
}
|
||||||
@ -1743,6 +1753,7 @@ public:
|
|||||||
v.push_back(std::to_string(e.faction_amount));
|
v.push_back(std::to_string(e.faction_amount));
|
||||||
v.push_back(std::to_string(e.keeps_sold_items));
|
v.push_back(std::to_string(e.keeps_sold_items));
|
||||||
v.push_back(std::to_string(e.is_parcel_merchant));
|
v.push_back(std::to_string(e.is_parcel_merchant));
|
||||||
|
v.push_back(std::to_string(e.multiquest_enabled));
|
||||||
|
|
||||||
auto results = db.QueryDatabase(
|
auto results = db.QueryDatabase(
|
||||||
fmt::format(
|
fmt::format(
|
||||||
@ -1894,6 +1905,7 @@ public:
|
|||||||
v.push_back(std::to_string(e.faction_amount));
|
v.push_back(std::to_string(e.faction_amount));
|
||||||
v.push_back(std::to_string(e.keeps_sold_items));
|
v.push_back(std::to_string(e.keeps_sold_items));
|
||||||
v.push_back(std::to_string(e.is_parcel_merchant));
|
v.push_back(std::to_string(e.is_parcel_merchant));
|
||||||
|
v.push_back(std::to_string(e.multiquest_enabled));
|
||||||
|
|
||||||
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
|
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -283,10 +283,9 @@ RULE_CATEGORY(Pets)
|
|||||||
RULE_REAL(Pets, AttackCommandRange, 150, "Range at which a pet will respond to attack commands")
|
RULE_REAL(Pets, AttackCommandRange, 150, "Range at which a pet will respond to attack commands")
|
||||||
RULE_BOOL(Pets, UnTargetableSwarmPet, false, "Setting whether swarm pets should be targetable")
|
RULE_BOOL(Pets, UnTargetableSwarmPet, false, "Setting whether swarm pets should be targetable")
|
||||||
RULE_REAL(Pets, PetPowerLevelCap, 10, "Maximum number of levels a player pet can go up with pet power")
|
RULE_REAL(Pets, PetPowerLevelCap, 10, "Maximum number of levels a player pet can go up with pet power")
|
||||||
RULE_BOOL(Pets, CanTakeNoDrop, false, "Setting whether anyone can give no-drop items to pets")
|
|
||||||
RULE_BOOL(Pets, CanTakeQuestItems, true, "Setting whether anyone can give quest items to pets")
|
|
||||||
RULE_BOOL(Pets, LivelikeBreakCharmOnInvis, true, "Default: true will break charm on any type of invis (hide/ivu/iva/etc) false will only break if the pet can not see you (ex. you have an undead pet and cast IVU")
|
RULE_BOOL(Pets, LivelikeBreakCharmOnInvis, true, "Default: true will break charm on any type of invis (hide/ivu/iva/etc) false will only break if the pet can not see you (ex. you have an undead pet and cast IVU")
|
||||||
RULE_BOOL(Pets, ClientPetsUseOwnerNameInLastName, true, "Disable this to keep client pet's last names from being owner_name's pet")
|
RULE_BOOL(Pets, ClientPetsUseOwnerNameInLastName, true, "Disable this to keep client pet's last names from being owner_name's pet")
|
||||||
|
RULE_BOOL(Pets, CanTakeNoDrop, false, "Setting whether anyone can give no-drop items to pets")
|
||||||
RULE_INT(Pets, PetTauntRange, 150, "Range at which a pet will taunt targets.")
|
RULE_INT(Pets, PetTauntRange, 150, "Range at which a pet will taunt targets.")
|
||||||
RULE_CATEGORY_END()
|
RULE_CATEGORY_END()
|
||||||
|
|
||||||
@ -666,8 +665,6 @@ RULE_BOOL(NPC, EnableNPCQuestJournal, false, "Setting whether the NPC Quest Jour
|
|||||||
RULE_INT(NPC, LastFightingDelayMovingMin, 10000, "Minimum time before mob goes home after all aggro loss (milliseconds)")
|
RULE_INT(NPC, LastFightingDelayMovingMin, 10000, "Minimum time before mob goes home after all aggro loss (milliseconds)")
|
||||||
RULE_INT(NPC, LastFightingDelayMovingMax, 20000, "Maximum time before mob goes home after all aggro loss (milliseconds)")
|
RULE_INT(NPC, LastFightingDelayMovingMax, 20000, "Maximum time before mob goes home after all aggro loss (milliseconds)")
|
||||||
RULE_BOOL(NPC, SmartLastFightingDelayMoving, true, "When true, mobs that started going home previously will do so again immediately if still on FD hate list")
|
RULE_BOOL(NPC, SmartLastFightingDelayMoving, true, "When true, mobs that started going home previously will do so again immediately if still on FD hate list")
|
||||||
RULE_BOOL(NPC, ReturnNonQuestNoDropItems, false, "Returns NO DROP items on NPC that don't have an EVENT_TRADE sub in their script")
|
|
||||||
RULE_BOOL(NPC, ReturnQuestItemsFromNonQuestNPCs, false, "Returns Quest items traded to NPCs that are not flagged as a Quest NPC")
|
|
||||||
RULE_INT(NPC, StartEnrageValue, 9, " Percentage HP that an NPC will begin to enrage")
|
RULE_INT(NPC, StartEnrageValue, 9, " Percentage HP that an NPC will begin to enrage")
|
||||||
RULE_BOOL(NPC, LiveLikeEnrage, false, "If set to true then only player controlled pets will enrage")
|
RULE_BOOL(NPC, LiveLikeEnrage, false, "If set to true then only player controlled pets will enrage")
|
||||||
RULE_BOOL(NPC, EnableMeritBasedFaction, false, "If set to true, faction will be given in the same way as experience (solo/group/raid)")
|
RULE_BOOL(NPC, EnableMeritBasedFaction, false, "If set to true, faction will be given in the same way as experience (solo/group/raid)")
|
||||||
@ -1144,6 +1141,7 @@ RULE_BOOL(Items, DisablePotionBelt, false, "Enable this to disable Potion Belt I
|
|||||||
RULE_BOOL(Items, DisableSpellFocusEffects, false, "Enable this to disable Spell Focus Effects on Items")
|
RULE_BOOL(Items, DisableSpellFocusEffects, false, "Enable this to disable Spell Focus Effects on Items")
|
||||||
RULE_BOOL(Items, SummonItemAllowInvisibleAugments, false, "Enable this to allow augments to be put in invisible augment slots of items in Client::SummonItem")
|
RULE_BOOL(Items, SummonItemAllowInvisibleAugments, false, "Enable this to allow augments to be put in invisible augment slots of items in Client::SummonItem")
|
||||||
RULE_BOOL(Items, AugmentItemAllowInvisibleAugments, false, "Enable this to allow augments to be put in invisible augment slots by players")
|
RULE_BOOL(Items, AugmentItemAllowInvisibleAugments, false, "Enable this to allow augments to be put in invisible augment slots by players")
|
||||||
|
RULE_BOOL(Items, AlwaysReturnHandins, true, "Enable this to always return handins to the player")
|
||||||
RULE_CATEGORY_END()
|
RULE_CATEGORY_END()
|
||||||
|
|
||||||
RULE_CATEGORY(Parcel)
|
RULE_CATEGORY(Parcel)
|
||||||
|
|||||||
@ -2949,3 +2949,55 @@ bool IsHateSpell(uint16 spell_id) {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool IsDisciplineTome(const EQ::ItemData* item)
|
||||||
|
{
|
||||||
|
if (!item->IsClassCommon() || item->ItemType != EQ::item::ItemTypeSpell) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Need a way to determine the difference between a spell and a tome
|
||||||
|
//so they cant turn in a spell and get it as a discipline
|
||||||
|
//this is kinda a hack:
|
||||||
|
|
||||||
|
const std::string item_name = item->Name;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!Strings::BeginsWith(item_name, "Tome of ") &&
|
||||||
|
!Strings::BeginsWith(item_name, "Skill: ")
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//we know for sure none of the int casters get disciplines
|
||||||
|
uint32 class_bit = 0;
|
||||||
|
class_bit |= 1 << (Class::Wizard - 1);
|
||||||
|
class_bit |= 1 << (Class::Enchanter - 1);
|
||||||
|
class_bit |= 1 << (Class::Magician - 1);
|
||||||
|
class_bit |= 1 << (Class::Necromancer - 1);
|
||||||
|
if (item->Classes & class_bit) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& spell_id = static_cast<uint32>(item->Scroll.Effect);
|
||||||
|
if (!IsValidSpell(spell_id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsDiscipline(spell_id)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &spell = spells[spell_id];
|
||||||
|
if (
|
||||||
|
spell.classes[Class::Wizard - 1] != 255 &&
|
||||||
|
spell.classes[Class::Enchanter - 1] != 255 &&
|
||||||
|
spell.classes[Class::Magician - 1] != 255 &&
|
||||||
|
spell.classes[Class::Necromancer - 1] != 255
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|||||||
@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
#include "classes.h"
|
#include "classes.h"
|
||||||
#include "skills.h"
|
#include "skills.h"
|
||||||
|
#include "item_data.h"
|
||||||
|
|
||||||
#define SPELL_UNKNOWN 0xFFFF
|
#define SPELL_UNKNOWN 0xFFFF
|
||||||
#define POISON_PROC 0xFFFE
|
#define POISON_PROC 0xFFFE
|
||||||
@ -1913,5 +1914,6 @@ bool IsResistanceOnlySpell(uint16 spell_id);
|
|||||||
bool IsDamageShieldOnlySpell(uint16 spell_id);
|
bool IsDamageShieldOnlySpell(uint16 spell_id);
|
||||||
bool IsDamageShieldAndResistSpell(uint16 spell_id);
|
bool IsDamageShieldAndResistSpell(uint16 spell_id);
|
||||||
bool IsHateSpell(uint16 spell_id);
|
bool IsHateSpell(uint16 spell_id);
|
||||||
|
bool IsDisciplineTome(const EQ::ItemData* item);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -42,7 +42,7 @@
|
|||||||
* Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
|
* Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#define CURRENT_BINARY_DATABASE_VERSION 9299
|
#define CURRENT_BINARY_DATABASE_VERSION 9300
|
||||||
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9054
|
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9054
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -5,6 +5,9 @@ set -ex
|
|||||||
sudo chown eqemu:eqemu /drone/src/ * -R
|
sudo chown eqemu:eqemu /drone/src/ * -R
|
||||||
sudo chown eqemu:eqemu /home/eqemu/.ccache/ * -R
|
sudo chown eqemu:eqemu /home/eqemu/.ccache/ * -R
|
||||||
|
|
||||||
|
chmod +x ./utils/scripts/build/source-db-build.sh
|
||||||
|
utils/scripts/build/source-db-build.sh &
|
||||||
|
|
||||||
git submodule init && git submodule update
|
git submodule init && git submodule update
|
||||||
|
|
||||||
perl utils/scripts/build/tag-version.pl
|
perl utils/scripts/build/tag-version.pl
|
||||||
@ -19,13 +22,39 @@ mkdir -p build && cd build && \
|
|||||||
-DCMAKE_CXX_FLAGS_RELWITHDEBINFO:STRING="-O1 -g -Wno-everything" \
|
-DCMAKE_CXX_FLAGS_RELWITHDEBINFO:STRING="-O1 -g -Wno-everything" \
|
||||||
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
|
||||||
-G 'Unix Makefiles' \
|
-G 'Unix Makefiles' \
|
||||||
.. && make -j$((`nproc`-4))
|
.. && make -j$((`nproc`-12))
|
||||||
|
|
||||||
curl https://raw.githubusercontent.com/Akkadius/eqemu-install-v2/master/eqemu_config.json --output eqemu_config.json
|
curl https://raw.githubusercontent.com/Akkadius/eqemu-install-v2/master/eqemu_config.json --output eqemu_config.json
|
||||||
./bin/tests
|
./bin/tests
|
||||||
|
|
||||||
ldd ./bin/zone
|
ldd ./bin/zone
|
||||||
|
|
||||||
|
echo "Waiting for MariaDB to be ready..."
|
||||||
|
while ! mysqladmin ping -uroot -peqemu -hlocalhost --silent; do
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "# Cloning quests repository"
|
||||||
|
git -C ./quests pull 2> /dev/null || git clone https://github.com/ProjectEQ/projecteqquests.git quests
|
||||||
|
|
||||||
|
# remove this eventually
|
||||||
|
cd ./quests && git checkout akkadius/item-handin-overhaul && cd ..
|
||||||
|
|
||||||
|
mkdir maps
|
||||||
|
mkdir logs
|
||||||
|
|
||||||
|
ln -s ./quests/lua_modules ./lua_modules
|
||||||
|
ln -s ./quests/plugins ./plugins
|
||||||
|
|
||||||
|
echo "# Running world database updates"
|
||||||
|
FORCE_INTERACTIVE=1 ./bin/world database:updates --skip-backup --force
|
||||||
|
|
||||||
|
echo "# Running shared_memory"
|
||||||
|
./bin/shared_memory
|
||||||
|
|
||||||
|
echo "# Running NPC hand-in tests"
|
||||||
|
./bin/zone tests:npc-handins
|
||||||
|
|
||||||
# shellcheck disable=SC2164
|
# shellcheck disable=SC2164
|
||||||
cd /drone/src/
|
cd /drone/src/
|
||||||
|
|
||||||
|
|||||||
90
utils/scripts/build/source-db-build.sh
Normal file
90
utils/scripts/build/source-db-build.sh
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
# Variables
|
||||||
|
ROOT_PASSWORD="eqemu"
|
||||||
|
MARIADB_CONFIG="/etc/mysql/mariadb.conf.d/50-server.cnf"
|
||||||
|
|
||||||
|
# Update and install MariaDB
|
||||||
|
echo "Installing MariaDB..."
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y mariadb-server mariadb-client
|
||||||
|
|
||||||
|
# Ensure MariaDB is stopped before configuration
|
||||||
|
echo "Stopping MariaDB service..."
|
||||||
|
sudo systemctl stop mariadb
|
||||||
|
|
||||||
|
# Initialize the data directory (in case it's not already initialized)
|
||||||
|
echo "Initializing MariaDB data directory..."
|
||||||
|
sudo mysqld --initialize --user=mysql --datadir=/var/lib/mysql
|
||||||
|
|
||||||
|
# Start MariaDB in safe mode
|
||||||
|
echo "Starting MariaDB in safe mode..."
|
||||||
|
sudo mysqld_safe --skip-grant-tables --skip-networking &
|
||||||
|
sleep 5
|
||||||
|
|
||||||
|
# Reset root password and configure authentication
|
||||||
|
echo "Resetting root password and configuring authentication..."
|
||||||
|
mariadb <<EOF
|
||||||
|
FLUSH PRIVILEGES;
|
||||||
|
ALTER USER 'root'@'localhost' IDENTIFIED VIA mysql_native_password USING PASSWORD('$ROOT_PASSWORD');
|
||||||
|
GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' IDENTIFIED BY '$ROOT_PASSWORD' WITH GRANT OPTION;
|
||||||
|
FLUSH PRIVILEGES;
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Stop MariaDB safe mode
|
||||||
|
echo "Stopping MariaDB safe mode..."
|
||||||
|
sudo killall mysqld
|
||||||
|
|
||||||
|
# Configure MariaDB to allow remote connections (optional)
|
||||||
|
echo "Configuring MariaDB to allow remote connections..."
|
||||||
|
sudo sed -i "s/^bind-address.*/bind-address = 0.0.0.0/" $MARIADB_CONFIG
|
||||||
|
|
||||||
|
# Restart MariaDB service
|
||||||
|
echo "Restarting MariaDB service..."
|
||||||
|
sudo systemctl restart mariadb
|
||||||
|
|
||||||
|
# Test connection
|
||||||
|
echo "Testing MariaDB connection..."
|
||||||
|
mysql -u root -p"$ROOT_PASSWORD" -e "SELECT VERSION();"
|
||||||
|
|
||||||
|
# Display completion message
|
||||||
|
echo "MariaDB setup completed!"
|
||||||
|
echo "Root password: $ROOT_PASSWORD"
|
||||||
|
|
||||||
|
# Set Database Credentials
|
||||||
|
DB_USER="root"
|
||||||
|
DB_PASS="eqemu"
|
||||||
|
DB_HOST="localhost"
|
||||||
|
DB_NAME="peq"
|
||||||
|
SQL_DIR="/tmp/db/peq-dump"
|
||||||
|
|
||||||
|
# Download the latest database dump
|
||||||
|
echo "Downloading the latest PEQ database dump..."
|
||||||
|
curl -s http://db.projecteq.net/api/v1/dump/latest -o /tmp/db.zip
|
||||||
|
|
||||||
|
# Unzip the database dump
|
||||||
|
echo "Extracting the database dump..."
|
||||||
|
unzip -o /tmp/db.zip -d /tmp/db/
|
||||||
|
|
||||||
|
# Ensure MariaDB is running
|
||||||
|
echo "Ensuring MariaDB is running..."
|
||||||
|
sudo systemctl start mariadb
|
||||||
|
|
||||||
|
# Wait for MariaDB to be ready
|
||||||
|
echo "Waiting for MariaDB to be ready..."
|
||||||
|
while ! mysqladmin ping -u${DB_USER} -p${DB_PASS} -h${DB_HOST} --silent; do
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
# Create the peq database
|
||||||
|
echo "Creating the '${DB_NAME}' database..."
|
||||||
|
mysql -u${DB_USER} -p${DB_PASS} -h${DB_HOST} -e "DROP DATABASE IF EXISTS ${DB_NAME}; CREATE DATABASE ${DB_NAME};"
|
||||||
|
|
||||||
|
# Parallelize the import process
|
||||||
|
echo "Importing tables in parallel..."
|
||||||
|
ls /tmp/db/peq-dump/create_tables_*.sql | xargs -P 4 -I {} sh -c "mysql -u${DB_USER} -p${DB_PASS} -h${DB_HOST} ${DB_NAME} < {}"
|
||||||
|
|
||||||
|
# Clean up temporary files
|
||||||
|
echo "Cleaning up temporary files..."
|
||||||
|
rm -rf /tmp/db/
|
||||||
|
rm -rf ${COMBINED_DIR}
|
||||||
|
|
||||||
|
echo "Database import complete!"
|
||||||
@ -2515,6 +2515,17 @@ bool NPC::Death(Mob* killer_mob, int64 damage, uint16 spell, EQ::skills::SkillTy
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (IsMultiQuestEnabled()) {
|
||||||
|
for (auto &i: m_hand_in.items) {
|
||||||
|
if (i.is_multiquest_item && i.item->GetItem()->NoDrop != 0) {
|
||||||
|
auto lde = LootdropEntriesRepository::NewNpcEntity();
|
||||||
|
lde.equip_item = 0;
|
||||||
|
lde.item_charges = i.item->GetCharges();
|
||||||
|
AddLootDrop(i.item->GetItem(), lde, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (killer_mob && killer_mob->IsOfClientBot() && IsValidSpell(spell) && damage > 0) {
|
if (killer_mob && killer_mob->IsOfClientBot() && IsValidSpell(spell) && damage > 0) {
|
||||||
char val1[20] = { 0 };
|
char val1[20] = { 0 };
|
||||||
|
|
||||||
|
|||||||
514
zone/cli/npc_handins.cpp
Normal file
514
zone/cli/npc_handins.cpp
Normal file
@ -0,0 +1,514 @@
|
|||||||
|
#include "../../common/http/httplib.h"
|
||||||
|
#include "../../common/eqemu_logsys.h"
|
||||||
|
#include "../../common/platform.h"
|
||||||
|
#include "../zone.h"
|
||||||
|
#include "../client.h"
|
||||||
|
#include "../../common/net/eqstream.h"
|
||||||
|
|
||||||
|
extern Zone *zone;
|
||||||
|
|
||||||
|
void ZoneCLI::NpcHandins(int argc, char **argv, argh::parser &cmd, std::string &description)
|
||||||
|
{
|
||||||
|
if (cmd[{"-h", "--help"}]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32 break_length = 50;
|
||||||
|
int failed_count = 0;
|
||||||
|
|
||||||
|
RegisterExecutablePlatform(EQEmuExePlatform::ExePlatformZoneSidecar);
|
||||||
|
|
||||||
|
LogInfo("{}", Strings::Repeat("-", break_length));
|
||||||
|
LogInfo("Booting test zone for NPC handins");
|
||||||
|
LogInfo("{}", Strings::Repeat("-", break_length));
|
||||||
|
|
||||||
|
Zone::Bootup(ZoneID("qrg"), 0, false);
|
||||||
|
zone->StopShutdownTimer();
|
||||||
|
|
||||||
|
entity_list.Process();
|
||||||
|
entity_list.MobProcess();
|
||||||
|
|
||||||
|
LogInfo("{}", Strings::Repeat("-", break_length));
|
||||||
|
LogInfo("> Done booting test zone");
|
||||||
|
LogInfo("{}", Strings::Repeat("-", break_length));
|
||||||
|
|
||||||
|
Client *c = new Client();
|
||||||
|
auto npc_type = content_db.LoadNPCTypesData(754008);
|
||||||
|
if (npc_type) {
|
||||||
|
auto npc = new NPC(
|
||||||
|
npc_type,
|
||||||
|
nullptr,
|
||||||
|
glm::vec4(0, 0, 0, 0),
|
||||||
|
GravityBehavior::Water
|
||||||
|
);
|
||||||
|
|
||||||
|
entity_list.AddNPC(npc);
|
||||||
|
|
||||||
|
LogInfo("> Spawned NPC [{}]", npc->GetCleanName());
|
||||||
|
LogInfo("> Spawned client [{}]", c->GetCleanName());
|
||||||
|
|
||||||
|
struct HandinEntry {
|
||||||
|
std::string item_id = "0";
|
||||||
|
uint32 count = 0;
|
||||||
|
const EQ::ItemInstance *item = nullptr;
|
||||||
|
bool is_multiquest_item = false; // state
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HandinMoney {
|
||||||
|
uint32 platinum = 0;
|
||||||
|
uint32 gold = 0;
|
||||||
|
uint32 silver = 0;
|
||||||
|
uint32 copper = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Handin {
|
||||||
|
std::vector<HandinEntry> items = {}; // items can be removed from this set as successful handins are made
|
||||||
|
HandinMoney money = {}; // money can be removed from this set as successful handins are made
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TestCase {
|
||||||
|
std::string description = "";
|
||||||
|
Handin hand_in;
|
||||||
|
Handin required;
|
||||||
|
Handin returned;
|
||||||
|
bool handin_check_result;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<TestCase> test_cases = {
|
||||||
|
TestCase{
|
||||||
|
.description = "Test basic cloth-cap hand-in",
|
||||||
|
.hand_in = {
|
||||||
|
.items = {
|
||||||
|
HandinEntry{.item_id = "1001", .count = 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.required = {
|
||||||
|
.items = {
|
||||||
|
HandinEntry{.item_id = "1001", .count = 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.returned = {},
|
||||||
|
.handin_check_result = true,
|
||||||
|
},
|
||||||
|
TestCase{
|
||||||
|
.description = "Test basic cloth-cap hand-in failure",
|
||||||
|
.hand_in = {
|
||||||
|
.items = {
|
||||||
|
HandinEntry{.item_id = "9997", .count = 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.required = {
|
||||||
|
.items = {
|
||||||
|
HandinEntry{.item_id = "1001", .count = 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.returned = {
|
||||||
|
.items = {
|
||||||
|
HandinEntry{.item_id = "9997", .count = 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.handin_check_result = false,
|
||||||
|
},
|
||||||
|
TestCase{
|
||||||
|
.description = "Test basic cloth-cap hand-in failure from handing in too many",
|
||||||
|
.hand_in = {
|
||||||
|
.items = {
|
||||||
|
HandinEntry{.item_id = "9997", .count = 1},
|
||||||
|
HandinEntry{.item_id = "9997", .count = 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.required = {
|
||||||
|
.items = {
|
||||||
|
HandinEntry{.item_id = "1001", .count = 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.returned = {
|
||||||
|
.items = {
|
||||||
|
HandinEntry{.item_id = "9997", .count = 1},
|
||||||
|
HandinEntry{.item_id = "9997", .count = 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.handin_check_result = false,
|
||||||
|
},
|
||||||
|
TestCase{
|
||||||
|
.description = "Test handing in money",
|
||||||
|
.hand_in = {
|
||||||
|
.items = {},
|
||||||
|
.money = {.platinum = 1},
|
||||||
|
},
|
||||||
|
.required = {
|
||||||
|
.items = {},
|
||||||
|
.money = {.platinum = 1},
|
||||||
|
},
|
||||||
|
.returned = {},
|
||||||
|
.handin_check_result = true,
|
||||||
|
},
|
||||||
|
TestCase{
|
||||||
|
.description = "Test handing in money, but not enough",
|
||||||
|
.hand_in = {
|
||||||
|
.items = {},
|
||||||
|
.money = {.platinum = 1},
|
||||||
|
},
|
||||||
|
.required = {
|
||||||
|
.items = {},
|
||||||
|
.money = {.platinum = 100},
|
||||||
|
},
|
||||||
|
.returned = {},
|
||||||
|
.handin_check_result = false,
|
||||||
|
},
|
||||||
|
TestCase{
|
||||||
|
.description = "Test handing in money, but not enough of any type",
|
||||||
|
.hand_in = {
|
||||||
|
.items = {},
|
||||||
|
.money = {.platinum = 1, .gold = 1, .silver = 1, .copper = 1},
|
||||||
|
},
|
||||||
|
.required = {
|
||||||
|
.items = {},
|
||||||
|
.money = {.platinum = 100, .gold = 100, .silver = 100, .copper = 100},
|
||||||
|
},
|
||||||
|
.returned = {},
|
||||||
|
.handin_check_result = false,
|
||||||
|
},
|
||||||
|
TestCase{
|
||||||
|
.description = "Test handing in money of all types",
|
||||||
|
.hand_in = {
|
||||||
|
.items = {},
|
||||||
|
.money = {.platinum = 1, .gold = 1, .silver = 1, .copper = 1},
|
||||||
|
},
|
||||||
|
.required = {
|
||||||
|
.items = {},
|
||||||
|
.money = {.platinum = 1, .gold = 1, .silver = 1, .copper = 1},
|
||||||
|
},
|
||||||
|
.returned = {},
|
||||||
|
.handin_check_result = true,
|
||||||
|
},
|
||||||
|
TestCase{
|
||||||
|
.description = "Test handing in platinum with items with success",
|
||||||
|
.hand_in = {
|
||||||
|
.items = {
|
||||||
|
HandinEntry{.item_id = "1001", .count = 1},
|
||||||
|
},
|
||||||
|
.money = {.platinum = 1},
|
||||||
|
},
|
||||||
|
.required = {
|
||||||
|
.items = {
|
||||||
|
HandinEntry{.item_id = "1001", .count = 1},
|
||||||
|
},
|
||||||
|
.money = {.platinum = 1},
|
||||||
|
},
|
||||||
|
.returned = {},
|
||||||
|
.handin_check_result = true,
|
||||||
|
},
|
||||||
|
TestCase{
|
||||||
|
.description = "Test handing in platinum with items with failure",
|
||||||
|
.hand_in = {
|
||||||
|
.items = {
|
||||||
|
HandinEntry{.item_id = "1001", .count = 1},
|
||||||
|
},
|
||||||
|
.money = {.platinum = 1},
|
||||||
|
},
|
||||||
|
.required = {
|
||||||
|
.items = {
|
||||||
|
HandinEntry{.item_id = "1001", .count = 1},
|
||||||
|
},
|
||||||
|
.money = {.platinum = 100},
|
||||||
|
},
|
||||||
|
.returned = {
|
||||||
|
.items = {
|
||||||
|
HandinEntry{.item_id = "1001", .count = 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.handin_check_result = false,
|
||||||
|
},
|
||||||
|
TestCase{
|
||||||
|
.description = "Test returning money and items",
|
||||||
|
.hand_in = {
|
||||||
|
.items = {
|
||||||
|
HandinEntry{.item_id = "1007", .count = 1},
|
||||||
|
},
|
||||||
|
.money = {
|
||||||
|
.platinum = 1,
|
||||||
|
.gold = 666,
|
||||||
|
.silver = 234,
|
||||||
|
.copper = 444,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.required = {
|
||||||
|
.items = {
|
||||||
|
HandinEntry{.item_id = "1001", .count = 1},
|
||||||
|
},
|
||||||
|
.money = {.platinum = 100},
|
||||||
|
},
|
||||||
|
.returned = {
|
||||||
|
.items = {
|
||||||
|
HandinEntry{.item_id = "1007", .count = 1},
|
||||||
|
},
|
||||||
|
.money = {
|
||||||
|
.platinum = 1,
|
||||||
|
.gold = 666,
|
||||||
|
.silver = 234,
|
||||||
|
.copper = 444,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.handin_check_result = false,
|
||||||
|
},
|
||||||
|
TestCase{
|
||||||
|
.description = "Test returning money",
|
||||||
|
.hand_in = {
|
||||||
|
.items = {},
|
||||||
|
.money = {
|
||||||
|
.platinum = 1,
|
||||||
|
.gold = 666,
|
||||||
|
.silver = 234,
|
||||||
|
.copper = 444,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.required = {
|
||||||
|
.items = {},
|
||||||
|
.money = {.platinum = 100},
|
||||||
|
},
|
||||||
|
.returned = {
|
||||||
|
.items = {
|
||||||
|
},
|
||||||
|
.money = {
|
||||||
|
.platinum = 1,
|
||||||
|
.gold = 666,
|
||||||
|
.silver = 234,
|
||||||
|
.copper = 444,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.handin_check_result = false,
|
||||||
|
},
|
||||||
|
TestCase{
|
||||||
|
.description = "Test handing in many items of the same required item",
|
||||||
|
.hand_in = {
|
||||||
|
.items = {
|
||||||
|
HandinEntry{.item_id = "1007", .count = 1},
|
||||||
|
HandinEntry{.item_id = "1007", .count = 1},
|
||||||
|
HandinEntry{.item_id = "1007", .count = 1},
|
||||||
|
HandinEntry{.item_id = "1007", .count = 1},
|
||||||
|
},
|
||||||
|
.money = {},
|
||||||
|
},
|
||||||
|
.required = {
|
||||||
|
.items = {
|
||||||
|
HandinEntry{.item_id = "1007", .count = 1},
|
||||||
|
},
|
||||||
|
.money = {},
|
||||||
|
},
|
||||||
|
.returned = {
|
||||||
|
.items = {
|
||||||
|
HandinEntry{.item_id = "1007", .count = 1},
|
||||||
|
HandinEntry{.item_id = "1007", .count = 1},
|
||||||
|
HandinEntry{.item_id = "1007", .count = 1},
|
||||||
|
},
|
||||||
|
.money = {
|
||||||
|
.platinum = 1,
|
||||||
|
.gold = 666,
|
||||||
|
.silver = 234,
|
||||||
|
.copper = 444,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.handin_check_result = true,
|
||||||
|
},
|
||||||
|
TestCase{
|
||||||
|
.description = "Test handing in item of a stack",
|
||||||
|
.hand_in = {
|
||||||
|
.items = {
|
||||||
|
HandinEntry{.item_id = "13005", .count = 20},
|
||||||
|
},
|
||||||
|
.money = {},
|
||||||
|
},
|
||||||
|
.required = {
|
||||||
|
.items = {
|
||||||
|
HandinEntry{.item_id = "13005", .count = 20},
|
||||||
|
},
|
||||||
|
.money = {},
|
||||||
|
},
|
||||||
|
.returned = {
|
||||||
|
.items = {},
|
||||||
|
.money = {},
|
||||||
|
},
|
||||||
|
.handin_check_result = true,
|
||||||
|
},
|
||||||
|
TestCase{
|
||||||
|
.description = "Test handing in item of a stack but not enough",
|
||||||
|
.hand_in = {
|
||||||
|
.items = {
|
||||||
|
HandinEntry{.item_id = "13005", .count = 10},
|
||||||
|
},
|
||||||
|
.money = {},
|
||||||
|
},
|
||||||
|
.required = {
|
||||||
|
.items = {
|
||||||
|
HandinEntry{.item_id = "13005", .count = 20},
|
||||||
|
},
|
||||||
|
.money = {},
|
||||||
|
},
|
||||||
|
.returned = {
|
||||||
|
.items = {
|
||||||
|
HandinEntry{.item_id = "13005", .count = 10},
|
||||||
|
},
|
||||||
|
.money = {},
|
||||||
|
},
|
||||||
|
.handin_check_result = false,
|
||||||
|
},
|
||||||
|
TestCase{
|
||||||
|
.description = "Test handing in 4 non-stacking helmets when 4 are required",
|
||||||
|
.hand_in = {
|
||||||
|
.items = {
|
||||||
|
HandinEntry{.item_id = "29062", .count = 1},
|
||||||
|
HandinEntry{.item_id = "29062", .count = 1},
|
||||||
|
HandinEntry{.item_id = "29062", .count = 1},
|
||||||
|
HandinEntry{.item_id = "29062", .count = 1},
|
||||||
|
},
|
||||||
|
.money = {},
|
||||||
|
},
|
||||||
|
.required = {
|
||||||
|
.items = {
|
||||||
|
HandinEntry{.item_id = "29062", .count = 4},
|
||||||
|
},
|
||||||
|
.money = {},
|
||||||
|
},
|
||||||
|
.returned = {
|
||||||
|
.items = {
|
||||||
|
},
|
||||||
|
.money = {},
|
||||||
|
},
|
||||||
|
.handin_check_result = true,
|
||||||
|
},
|
||||||
|
TestCase{
|
||||||
|
.description = "Test handing in Soulfire that has 5 charges and have it count as 1 item",
|
||||||
|
.hand_in = {
|
||||||
|
.items = {
|
||||||
|
HandinEntry{.item_id = "5504", .count = 1},
|
||||||
|
},
|
||||||
|
.money = {},
|
||||||
|
},
|
||||||
|
.required = {
|
||||||
|
.items = {
|
||||||
|
HandinEntry{.item_id = "5504", .count = 1},
|
||||||
|
},
|
||||||
|
.money = {},
|
||||||
|
},
|
||||||
|
.returned = {
|
||||||
|
.items = {
|
||||||
|
},
|
||||||
|
.money = {},
|
||||||
|
},
|
||||||
|
.handin_check_result = true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
std::map<std::string, uint32> hand_ins;
|
||||||
|
std::map<std::string, uint32> required;
|
||||||
|
std::vector<EQ::ItemInstance *> items;
|
||||||
|
|
||||||
|
// turn this on to see debugging output
|
||||||
|
LogSys.log_settings[Logs::NpcHandin].log_to_console = std::getenv("DEBUG") ? 3 : 0;
|
||||||
|
|
||||||
|
LogInfo("{}", Strings::Repeat("-", break_length));
|
||||||
|
|
||||||
|
for (auto &test_case: test_cases) {
|
||||||
|
hand_ins.clear();
|
||||||
|
required.clear();
|
||||||
|
items.clear();
|
||||||
|
|
||||||
|
for (auto &hand_in: test_case.hand_in.items) {
|
||||||
|
auto item_id = Strings::ToInt(hand_in.item_id);
|
||||||
|
EQ::ItemInstance *inst = database.CreateItem(item_id);
|
||||||
|
if (inst->IsStackable()) {
|
||||||
|
inst->SetCharges(hand_in.count);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inst->GetItem()->MaxCharges > 0) {
|
||||||
|
inst->SetCharges(inst->GetItem()->MaxCharges);
|
||||||
|
}
|
||||||
|
|
||||||
|
hand_ins[hand_in.item_id] = inst->GetCharges();
|
||||||
|
items.push_back(inst);
|
||||||
|
}
|
||||||
|
|
||||||
|
// money
|
||||||
|
if (test_case.hand_in.money.platinum > 0) {
|
||||||
|
hand_ins["platinum"] = test_case.hand_in.money.platinum;
|
||||||
|
}
|
||||||
|
if (test_case.hand_in.money.gold > 0) {
|
||||||
|
hand_ins["gold"] = test_case.hand_in.money.gold;
|
||||||
|
}
|
||||||
|
if (test_case.hand_in.money.silver > 0) {
|
||||||
|
hand_ins["silver"] = test_case.hand_in.money.silver;
|
||||||
|
}
|
||||||
|
if (test_case.hand_in.money.copper > 0) {
|
||||||
|
hand_ins["copper"] = test_case.hand_in.money.copper;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &req: test_case.required.items) {
|
||||||
|
required[req.item_id] = req.count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// money
|
||||||
|
if (test_case.required.money.platinum > 0) {
|
||||||
|
required["platinum"] = test_case.required.money.platinum;
|
||||||
|
}
|
||||||
|
if (test_case.required.money.gold > 0) {
|
||||||
|
required["gold"] = test_case.required.money.gold;
|
||||||
|
}
|
||||||
|
if (test_case.required.money.silver > 0) {
|
||||||
|
required["silver"] = test_case.required.money.silver;
|
||||||
|
}
|
||||||
|
if (test_case.required.money.copper > 0) {
|
||||||
|
required["copper"] = test_case.required.money.copper;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = npc->CheckHandin(c, hand_ins, required, items);
|
||||||
|
if (result != test_case.handin_check_result) {
|
||||||
|
failed_count++;
|
||||||
|
LogError("FAIL [{}]", test_case.description);
|
||||||
|
// print out the hand-ins
|
||||||
|
LogError("Hand-ins >");
|
||||||
|
for (auto &hand_in: hand_ins) {
|
||||||
|
LogError(" > Item [{}] count [{}]", hand_in.first, hand_in.second);
|
||||||
|
}
|
||||||
|
LogError("Required >");
|
||||||
|
for (auto &req: required) {
|
||||||
|
LogError(" > Item [{}] count [{}]", req.first, req.second);
|
||||||
|
}
|
||||||
|
LogError("Expected [{}] got [{}]", test_case.handin_check_result, result);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LogInfo("PASS [{}]", test_case.description);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto returned = npc->ReturnHandinItems(c);
|
||||||
|
|
||||||
|
// assert that returned items are expected
|
||||||
|
for (auto &item: test_case.returned.items) {
|
||||||
|
auto found = false;
|
||||||
|
for (auto &ret: returned.items) {
|
||||||
|
if (ret.item_id == item.item_id) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found) {
|
||||||
|
LogError("Returned item [{}] not expected", item.item_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
npc->ResetHandin();
|
||||||
|
|
||||||
|
if (LogSys.log_settings[Logs::NpcHandin].log_to_console > 0) {
|
||||||
|
std::cout << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (failed_count > 0) {
|
||||||
|
LogError("Failed [{}] tests", failed_count);
|
||||||
|
std::exit(1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LogInfo("All tests passed");
|
||||||
|
}
|
||||||
|
}
|
||||||
585
zone/client.cpp
585
zone/client.cpp
@ -89,6 +89,314 @@ extern PetitionList petition_list;
|
|||||||
|
|
||||||
void UpdateWindowTitle(char* iNewTitle);
|
void UpdateWindowTitle(char* iNewTitle);
|
||||||
|
|
||||||
|
// client constructor purely for testing / mocking
|
||||||
|
Client::Client() : Mob(
|
||||||
|
"No name", // in_name
|
||||||
|
"", // in_lastname
|
||||||
|
0, // in_cur_hp
|
||||||
|
0, // in_max_hp
|
||||||
|
Gender::Male, // in_gender
|
||||||
|
Race::Doug, // in_race
|
||||||
|
Class::None, // in_class
|
||||||
|
BodyType::Humanoid, // in_bodytype
|
||||||
|
Deity::Unknown, // in_deity
|
||||||
|
0, // in_level
|
||||||
|
0, // in_npctype_id
|
||||||
|
0.0f, // in_size
|
||||||
|
0.7f, // in_runspeed
|
||||||
|
glm::vec4(), // position
|
||||||
|
0, // in_light
|
||||||
|
0xFF, // in_texture
|
||||||
|
0xFF, // in_helmtexture
|
||||||
|
0, // in_ac
|
||||||
|
0, // in_atk
|
||||||
|
0, // in_str
|
||||||
|
0, // in_sta
|
||||||
|
0, // in_dex
|
||||||
|
0, // in_agi
|
||||||
|
0, // in_int
|
||||||
|
0, // in_wis
|
||||||
|
0, // in_cha
|
||||||
|
0, // in_haircolor
|
||||||
|
0, // in_beardcolor
|
||||||
|
0, // in_eyecolor1
|
||||||
|
0, // in_eyecolor2
|
||||||
|
0, // in_hairstyle
|
||||||
|
0, // in_luclinface
|
||||||
|
0, // in_beard
|
||||||
|
0, // in_drakkin_heritage
|
||||||
|
0, // in_drakkin_tattoo
|
||||||
|
0, // in_drakkin_details
|
||||||
|
EQ::TintProfile(), // in_armor_tint
|
||||||
|
0xff, // in_aa_title
|
||||||
|
0, // in_see_invis
|
||||||
|
0, // in_see_invis_undead
|
||||||
|
0, // in_see_hide
|
||||||
|
0, // in_see_improved_hide
|
||||||
|
0, // in_hp_regen
|
||||||
|
0, // in_mana_regen
|
||||||
|
0, // in_qglobal
|
||||||
|
0, // in_maxlevel
|
||||||
|
0, // in_scalerate
|
||||||
|
0, // in_armtexture
|
||||||
|
0, // in_bracertexture
|
||||||
|
0, // in_handtexture
|
||||||
|
0, // in_legtexture
|
||||||
|
0, // in_feettexture
|
||||||
|
0, // in_usemodel
|
||||||
|
false, // in_always_aggros_foes
|
||||||
|
0, // in_heroic_strikethrough
|
||||||
|
false // in_keeps_sold_items
|
||||||
|
),
|
||||||
|
hpupdate_timer(2000),
|
||||||
|
camp_timer(29000),
|
||||||
|
process_timer(100),
|
||||||
|
consume_food_timer(CONSUMPTION_TIMER),
|
||||||
|
zoneinpacket_timer(1000),
|
||||||
|
linkdead_timer(RuleI(Zone, ClientLinkdeadMS)),
|
||||||
|
dead_timer(2000),
|
||||||
|
global_channel_timer(1000),
|
||||||
|
fishing_timer(8000),
|
||||||
|
endupkeep_timer(1000),
|
||||||
|
autosave_timer(RuleI(Character, AutosaveIntervalS) * 1000),
|
||||||
|
m_client_npc_aggro_scan_timer(RuleI(Aggro, ClientAggroCheckIdleInterval)),
|
||||||
|
m_client_bulk_npc_pos_update_timer(60 * 1000),
|
||||||
|
tribute_timer(Tribute_duration),
|
||||||
|
proximity_timer(ClientProximity_interval),
|
||||||
|
TaskPeriodic_Timer(RuleI(TaskSystem, PeriodicCheckTimer) * 1000),
|
||||||
|
charm_update_timer(6000),
|
||||||
|
rest_timer(1),
|
||||||
|
pick_lock_timer(1000),
|
||||||
|
charm_class_attacks_timer(3000),
|
||||||
|
charm_cast_timer(3500),
|
||||||
|
qglobal_purge_timer(30000),
|
||||||
|
TrackingTimer(2000),
|
||||||
|
RespawnFromHoverTimer(0),
|
||||||
|
merc_timer(RuleI(Mercs, UpkeepIntervalMS)),
|
||||||
|
ItemQuestTimer(500),
|
||||||
|
anon_toggle_timer(250),
|
||||||
|
afk_toggle_timer(250),
|
||||||
|
helm_toggle_timer(250),
|
||||||
|
aggro_meter_timer(AGGRO_METER_UPDATE_MS),
|
||||||
|
m_Proximity(FLT_MAX, FLT_MAX, FLT_MAX), //arbitrary large number
|
||||||
|
m_ZoneSummonLocation(-2.0f, -2.0f, -2.0f, -2.0f),
|
||||||
|
m_AutoAttackPosition(0.0f, 0.0f, 0.0f, 0.0f),
|
||||||
|
m_AutoAttackTargetLocation(0.0f, 0.0f, 0.0f),
|
||||||
|
last_region_type(RegionTypeUnsupported),
|
||||||
|
m_dirtyautohaters(false),
|
||||||
|
m_position_update_timer(10000),
|
||||||
|
consent_throttle_timer(2000),
|
||||||
|
tmSitting(0),
|
||||||
|
parcel_timer(RuleI(Parcel, ParcelDeliveryDelay)),
|
||||||
|
lazy_load_bank_check_timer(1000),
|
||||||
|
bandolier_throttle_timer(0)
|
||||||
|
{
|
||||||
|
eqs = nullptr;
|
||||||
|
for (auto client_filter = FilterNone; client_filter < _FilterCount; client_filter = eqFilterType(client_filter + 1)) {
|
||||||
|
SetFilter(client_filter, FilterShow);
|
||||||
|
}
|
||||||
|
|
||||||
|
cheat_manager.SetClient(this);
|
||||||
|
mMovementManager->AddClient(this);
|
||||||
|
character_id = 0;
|
||||||
|
conn_state = NoPacketsReceived;
|
||||||
|
client_data_loaded = false;
|
||||||
|
berserk = false;
|
||||||
|
dead = false;
|
||||||
|
client_state = CLIENT_CONNECTING;
|
||||||
|
SetTrader(false);
|
||||||
|
Haste = 0;
|
||||||
|
SetCustomerID(0);
|
||||||
|
SetTraderID(0);
|
||||||
|
TrackingID = 0;
|
||||||
|
WID = 0;
|
||||||
|
account_id = 0;
|
||||||
|
admin = AccountStatus::Player;
|
||||||
|
lsaccountid = 0;
|
||||||
|
guild_id = GUILD_NONE;
|
||||||
|
guildrank = 0;
|
||||||
|
guild_tribute_opt_in = 0;
|
||||||
|
SetGuildListDirty(false);
|
||||||
|
GuildBanker = false;
|
||||||
|
memset(lskey, 0, sizeof(lskey));
|
||||||
|
strcpy(account_name, "");
|
||||||
|
tellsoff = false;
|
||||||
|
last_reported_mana = 0;
|
||||||
|
last_reported_endurance = 0;
|
||||||
|
last_reported_endurance_percent = 0;
|
||||||
|
last_reported_mana_percent = 0;
|
||||||
|
gm_hide_me = false;
|
||||||
|
AFK = false;
|
||||||
|
LFG = false;
|
||||||
|
LFGFromLevel = 0;
|
||||||
|
LFGToLevel = 0;
|
||||||
|
LFGMatchFilter = false;
|
||||||
|
LFGComments[0] = '\0';
|
||||||
|
LFP = false;
|
||||||
|
gmspeed = 0;
|
||||||
|
gminvul = false;
|
||||||
|
playeraction = 0;
|
||||||
|
SetTarget(0);
|
||||||
|
auto_attack = false;
|
||||||
|
auto_fire = false;
|
||||||
|
runmode = false;
|
||||||
|
linkdead_timer.Disable();
|
||||||
|
zonesummon_id = 0;
|
||||||
|
zonesummon_ignorerestrictions = 0;
|
||||||
|
bZoning = false;
|
||||||
|
m_lock_save_position = false;
|
||||||
|
zone_mode = ZoneUnsolicited;
|
||||||
|
casting_spell_id = 0;
|
||||||
|
npcflag = false;
|
||||||
|
npclevel = 0;
|
||||||
|
fishing_timer.Disable();
|
||||||
|
dead_timer.Disable();
|
||||||
|
camp_timer.Disable();
|
||||||
|
autosave_timer.Disable();
|
||||||
|
GetMercTimer()->Disable();
|
||||||
|
instalog = false;
|
||||||
|
m_pp.autosplit = false;
|
||||||
|
// initialise haste variable
|
||||||
|
m_tradeskill_object = nullptr;
|
||||||
|
delaytimer = false;
|
||||||
|
PendingRezzXP = -1;
|
||||||
|
PendingRezzDBID = 0;
|
||||||
|
PendingRezzSpellID = 0;
|
||||||
|
numclients++;
|
||||||
|
// emuerror;
|
||||||
|
UpdateWindowTitle(nullptr);
|
||||||
|
horseId = 0;
|
||||||
|
tgb = false;
|
||||||
|
tribute_master_id = 0xFFFFFFFF;
|
||||||
|
tribute_timer.Disable();
|
||||||
|
task_state = nullptr;
|
||||||
|
TotalSecondsPlayed = 0;
|
||||||
|
keyring.clear();
|
||||||
|
bind_sight_target = nullptr;
|
||||||
|
p_raid_instance = nullptr;
|
||||||
|
mercid = 0;
|
||||||
|
mercSlot = 0;
|
||||||
|
InitializeMercInfo();
|
||||||
|
SetMerc(0);
|
||||||
|
if (RuleI(World, PVPMinLevel) > 0 && level >= RuleI(World, PVPMinLevel) && m_pp.pvp == 0) SetPVP(true, false);
|
||||||
|
dynamiczone_removal_timer.Disable();
|
||||||
|
|
||||||
|
//for good measure:
|
||||||
|
memset(&m_pp, 0, sizeof(m_pp));
|
||||||
|
memset(&m_epp, 0, sizeof(m_epp));
|
||||||
|
PendingTranslocate = false;
|
||||||
|
PendingSacrifice = false;
|
||||||
|
sacrifice_caster_id = 0;
|
||||||
|
controlling_boat_id = 0;
|
||||||
|
controlled_mob_id = 0;
|
||||||
|
qGlobals = nullptr;
|
||||||
|
|
||||||
|
if (!RuleB(Character, PerCharacterQglobalMaxLevel) && !RuleB(Character, PerCharacterBucketMaxLevel)) {
|
||||||
|
SetClientMaxLevel(0);
|
||||||
|
} else if (RuleB(Character, PerCharacterQglobalMaxLevel)) {
|
||||||
|
SetClientMaxLevel(GetCharMaxLevelFromQGlobal());
|
||||||
|
} else if (RuleB(Character, PerCharacterBucketMaxLevel)) {
|
||||||
|
SetClientMaxLevel(GetCharMaxLevelFromBucket());
|
||||||
|
}
|
||||||
|
|
||||||
|
KarmaUpdateTimer = new Timer(RuleI(Chat, KarmaUpdateIntervalMS));
|
||||||
|
GlobalChatLimiterTimer = new Timer(RuleI(Chat, IntervalDurationMS));
|
||||||
|
AttemptedMessages = 0;
|
||||||
|
TotalKarma = 0;
|
||||||
|
m_ClientVersion = EQ::versions::ClientVersion::Unknown;
|
||||||
|
m_ClientVersionBit = 0;
|
||||||
|
AggroCount = 0;
|
||||||
|
ooc_regen = false;
|
||||||
|
AreaHPRegen = 1.0f;
|
||||||
|
AreaManaRegen = 1.0f;
|
||||||
|
AreaEndRegen = 1.0f;
|
||||||
|
XPRate = 100;
|
||||||
|
current_endurance = 0;
|
||||||
|
|
||||||
|
CanUseReport = true;
|
||||||
|
aa_los_them_mob = nullptr;
|
||||||
|
los_status = false;
|
||||||
|
los_status_facing = false;
|
||||||
|
HideCorpseMode = HideCorpseNone;
|
||||||
|
PendingGuildInvitation = false;
|
||||||
|
|
||||||
|
InitializeBuffSlots();
|
||||||
|
|
||||||
|
adventure_request_timer = nullptr;
|
||||||
|
adventure_create_timer = nullptr;
|
||||||
|
adventure_leave_timer = nullptr;
|
||||||
|
adventure_door_timer = nullptr;
|
||||||
|
adv_requested_data = nullptr;
|
||||||
|
adventure_stats_timer = nullptr;
|
||||||
|
adventure_leaderboard_timer = nullptr;
|
||||||
|
adv_data = nullptr;
|
||||||
|
adv_requested_theme = LDoNTheme::Unused;
|
||||||
|
adv_requested_id = 0;
|
||||||
|
adv_requested_member_count = 0;
|
||||||
|
|
||||||
|
for(int i = 0; i < XTARGET_HARDCAP; ++i)
|
||||||
|
{
|
||||||
|
XTargets[i].Type = Auto;
|
||||||
|
XTargets[i].ID = 0;
|
||||||
|
XTargets[i].Name[0] = 0;
|
||||||
|
XTargets[i].dirty = false;
|
||||||
|
}
|
||||||
|
MaxXTargets = 5;
|
||||||
|
XTargetAutoAddHaters = true;
|
||||||
|
m_autohatermgr.SetOwner(this, nullptr, nullptr);
|
||||||
|
m_activeautohatermgr = &m_autohatermgr;
|
||||||
|
|
||||||
|
initial_respawn_selection = 0;
|
||||||
|
alternate_currency_loaded = false;
|
||||||
|
|
||||||
|
interrogateinv_flag = false;
|
||||||
|
|
||||||
|
trapid = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < InnateSkillMax; ++i)
|
||||||
|
m_pp.InnateSkills[i] = InnateDisabled;
|
||||||
|
|
||||||
|
temp_pvp = false;
|
||||||
|
|
||||||
|
moving = false;
|
||||||
|
|
||||||
|
environment_damage_modifier = 0;
|
||||||
|
invulnerable_environment_damage = false;
|
||||||
|
|
||||||
|
// rate limiter
|
||||||
|
m_list_task_timers_rate_limit.Start(1000);
|
||||||
|
|
||||||
|
// gm
|
||||||
|
SetDisplayMobInfoWindow(true);
|
||||||
|
SetDevToolsEnabled(true);
|
||||||
|
|
||||||
|
bot_owner_options[booDeathMarquee] = false;
|
||||||
|
bot_owner_options[booStatsUpdate] = false;
|
||||||
|
bot_owner_options[booSpawnMessageSay] = false;
|
||||||
|
bot_owner_options[booSpawnMessageTell] = true;
|
||||||
|
bot_owner_options[booSpawnMessageClassSpecific] = true;
|
||||||
|
bot_owner_options[booAutoDefend] = RuleB(Bots, AllowOwnerOptionAutoDefend);
|
||||||
|
bot_owner_options[booBuffCounter] = false;
|
||||||
|
bot_owner_options[booMonkWuMessage] = false;
|
||||||
|
|
||||||
|
m_parcel_platinum = 0;
|
||||||
|
m_parcel_gold = 0;
|
||||||
|
m_parcel_silver = 0;
|
||||||
|
m_parcel_copper = 0;
|
||||||
|
m_parcel_count = 0;
|
||||||
|
m_parcel_enabled = true;
|
||||||
|
m_parcel_merchant_engaged = false;
|
||||||
|
m_parcels.clear();
|
||||||
|
|
||||||
|
m_buyer_id = 0;
|
||||||
|
|
||||||
|
SetBotPulling(false);
|
||||||
|
SetBotPrecombat(false);
|
||||||
|
|
||||||
|
AI_Init();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
Client::Client(EQStreamInterface *ieqs) : Mob(
|
Client::Client(EQStreamInterface *ieqs) : Mob(
|
||||||
"No name", // in_name
|
"No name", // in_name
|
||||||
"", // in_lastname
|
"", // in_lastname
|
||||||
@ -504,9 +812,11 @@ Client::~Client() {
|
|||||||
zone->RemoveAuth(GetName(), lskey);
|
zone->RemoveAuth(GetName(), lskey);
|
||||||
|
|
||||||
//let the stream factory know were done with this stream
|
//let the stream factory know were done with this stream
|
||||||
eqs->Close();
|
if (eqs) {
|
||||||
eqs->ReleaseFromUse();
|
eqs->Close();
|
||||||
safe_delete(eqs);
|
eqs->ReleaseFromUse();
|
||||||
|
safe_delete(eqs);
|
||||||
|
}
|
||||||
|
|
||||||
UninitializeBuffSlots();
|
UninitializeBuffSlots();
|
||||||
}
|
}
|
||||||
@ -2488,6 +2798,7 @@ void Client::AddMoneyToPP(uint64 copper, bool update_client){
|
|||||||
|
|
||||||
/* Add Amount of Platinum */
|
/* Add Amount of Platinum */
|
||||||
temporary_copper_two = temporary_copper / 1000;
|
temporary_copper_two = temporary_copper / 1000;
|
||||||
|
m_external_handin_money_returned.platinum = temporary_copper_two;
|
||||||
int32 new_value = m_pp.platinum + temporary_copper_two;
|
int32 new_value = m_pp.platinum + temporary_copper_two;
|
||||||
|
|
||||||
if (new_value < 0) {
|
if (new_value < 0) {
|
||||||
@ -2501,6 +2812,7 @@ void Client::AddMoneyToPP(uint64 copper, bool update_client){
|
|||||||
/* Add Amount of Gold */
|
/* Add Amount of Gold */
|
||||||
temporary_copper_two = temporary_copper / 100;
|
temporary_copper_two = temporary_copper / 100;
|
||||||
new_value = m_pp.gold + temporary_copper_two;
|
new_value = m_pp.gold + temporary_copper_two;
|
||||||
|
m_external_handin_money_returned.gold = temporary_copper_two;
|
||||||
|
|
||||||
if (new_value < 0) {
|
if (new_value < 0) {
|
||||||
m_pp.gold = 0;
|
m_pp.gold = 0;
|
||||||
@ -2513,6 +2825,7 @@ void Client::AddMoneyToPP(uint64 copper, bool update_client){
|
|||||||
/* Add Amount of Silver */
|
/* Add Amount of Silver */
|
||||||
temporary_copper_two = temporary_copper / 10;
|
temporary_copper_two = temporary_copper / 10;
|
||||||
new_value = m_pp.silver + temporary_copper_two;
|
new_value = m_pp.silver + temporary_copper_two;
|
||||||
|
m_external_handin_money_returned.silver = temporary_copper_two;
|
||||||
|
|
||||||
if (new_value < 0) {
|
if (new_value < 0) {
|
||||||
m_pp.silver = 0;
|
m_pp.silver = 0;
|
||||||
@ -2525,6 +2838,7 @@ void Client::AddMoneyToPP(uint64 copper, bool update_client){
|
|||||||
/* Add Amount of Copper */
|
/* Add Amount of Copper */
|
||||||
temporary_copper_two = temporary_copper;
|
temporary_copper_two = temporary_copper;
|
||||||
new_value = m_pp.copper + temporary_copper_two;
|
new_value = m_pp.copper + temporary_copper_two;
|
||||||
|
m_external_handin_money_returned.copper = temporary_copper_two;
|
||||||
|
|
||||||
if (new_value < 0) {
|
if (new_value < 0) {
|
||||||
m_pp.copper = 0;
|
m_pp.copper = 0;
|
||||||
@ -2541,23 +2855,12 @@ void Client::AddMoneyToPP(uint64 copper, bool update_client){
|
|||||||
|
|
||||||
SaveCurrency();
|
SaveCurrency();
|
||||||
|
|
||||||
|
m_external_handin_money_returned.return_source = "AddMoneyToPP";
|
||||||
|
|
||||||
LogDebug("Client::AddMoneyToPP() [{}] should have: plat:[{}] gold:[{}] silver:[{}] copper:[{}]", GetName(), m_pp.platinum, m_pp.gold, m_pp.silver, m_pp.copper);
|
LogDebug("Client::AddMoneyToPP() [{}] should have: plat:[{}] gold:[{}] silver:[{}] copper:[{}]", GetName(), m_pp.platinum, m_pp.gold, m_pp.silver, m_pp.copper);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::EVENT_ITEM_ScriptStopReturn(){
|
|
||||||
/* Set a timestamp in an entity variable for plugin check_handin.pl in return_items
|
|
||||||
This will stopgap players from items being returned if global_npc.pl has a catch all return_items
|
|
||||||
*/
|
|
||||||
struct timeval read_time;
|
|
||||||
char buffer[50];
|
|
||||||
gettimeofday(&read_time, 0);
|
|
||||||
sprintf(buffer, "%li.%li \n", read_time.tv_sec, read_time.tv_usec);
|
|
||||||
SetEntityVariable("Stop_Return", buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Client::AddMoneyToPP(uint32 copper, uint32 silver, uint32 gold, uint32 platinum, bool update_client){
|
void Client::AddMoneyToPP(uint32 copper, uint32 silver, uint32 gold, uint32 platinum, bool update_client){
|
||||||
EVENT_ITEM_ScriptStopReturn();
|
|
||||||
|
|
||||||
int32 new_value = m_pp.platinum + platinum;
|
int32 new_value = m_pp.platinum + platinum;
|
||||||
if (new_value >= 0 && new_value > m_pp.platinum) {
|
if (new_value >= 0 && new_value > m_pp.platinum) {
|
||||||
m_pp.platinum += platinum;
|
m_pp.platinum += platinum;
|
||||||
@ -2585,6 +2888,14 @@ void Client::AddMoneyToPP(uint32 copper, uint32 silver, uint32 gold, uint32 plat
|
|||||||
RecalcWeight();
|
RecalcWeight();
|
||||||
SaveCurrency();
|
SaveCurrency();
|
||||||
|
|
||||||
|
m_external_handin_money_returned = ExternalHandinMoneyReturned{
|
||||||
|
.copper = copper,
|
||||||
|
.silver = silver,
|
||||||
|
.gold = gold,
|
||||||
|
.platinum = platinum,
|
||||||
|
.return_source = "AddMoneyToPP"
|
||||||
|
};
|
||||||
|
|
||||||
#if (EQDEBUG>=5)
|
#if (EQDEBUG>=5)
|
||||||
LogDebug("Client::AddMoneyToPP() [{}] should have: plat:[{}] gold:[{}] silver:[{}] copper:[{}]",
|
LogDebug("Client::AddMoneyToPP() [{}] should have: plat:[{}] gold:[{}] silver:[{}] copper:[{}]",
|
||||||
GetName(), m_pp.platinum, m_pp.gold, m_pp.silver, m_pp.copper);
|
GetName(), m_pp.platinum, m_pp.gold, m_pp.silver, m_pp.copper);
|
||||||
@ -12373,248 +12684,6 @@ void Client::PlayerTradeEventLog(Trade *t, Trade *t2)
|
|||||||
RecordPlayerEventLogWithClient(trader2, PlayerEvent::TRADE, e);
|
RecordPlayerEventLogWithClient(trader2, PlayerEvent::TRADE, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Client::NPCHandinEventLog(Trade* t, NPC* n)
|
|
||||||
{
|
|
||||||
Client* c = t->GetOwner()->CastToClient();
|
|
||||||
|
|
||||||
std::vector<PlayerEvent::HandinEntry> hi = {};
|
|
||||||
std::vector<PlayerEvent::HandinEntry> ri = {};
|
|
||||||
PlayerEvent::HandinMoney hm{};
|
|
||||||
PlayerEvent::HandinMoney rm{};
|
|
||||||
|
|
||||||
if (
|
|
||||||
c->EntityVariableExists("HANDIN_ITEMS") &&
|
|
||||||
c->EntityVariableExists("HANDIN_MONEY") &&
|
|
||||||
c->EntityVariableExists("RETURN_ITEMS") &&
|
|
||||||
c->EntityVariableExists("RETURN_MONEY")
|
|
||||||
) {
|
|
||||||
const std::string& handin_items = c->GetEntityVariable("HANDIN_ITEMS");
|
|
||||||
const std::string& return_items = c->GetEntityVariable("RETURN_ITEMS");
|
|
||||||
const std::string& handin_money = c->GetEntityVariable("HANDIN_MONEY");
|
|
||||||
const std::string& return_money = c->GetEntityVariable("RETURN_MONEY");
|
|
||||||
|
|
||||||
// Handin Items
|
|
||||||
if (!handin_items.empty()) {
|
|
||||||
if (Strings::Contains(handin_items, ",")) {
|
|
||||||
const auto handin_data = Strings::Split(handin_items, ",");
|
|
||||||
for (const auto& h : handin_data) {
|
|
||||||
const auto item_data = Strings::Split(h, "|");
|
|
||||||
if (
|
|
||||||
item_data.size() == 3 &&
|
|
||||||
Strings::IsNumber(item_data[0]) &&
|
|
||||||
Strings::IsNumber(item_data[1]) &&
|
|
||||||
Strings::IsNumber(item_data[2])
|
|
||||||
) {
|
|
||||||
const uint32 item_id = Strings::ToUnsignedInt(item_data[0]);
|
|
||||||
if (item_id != 0) {
|
|
||||||
const auto* item = database.GetItem(item_id);
|
|
||||||
|
|
||||||
if (item) {
|
|
||||||
hi.emplace_back(
|
|
||||||
PlayerEvent::HandinEntry{
|
|
||||||
.item_id = item_id,
|
|
||||||
.item_name = item->Name,
|
|
||||||
.charges = static_cast<uint16>(Strings::ToUnsignedInt(item_data[1])),
|
|
||||||
.attuned = Strings::ToInt(item_data[2]) ? true : false
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (Strings::Contains(handin_items, "|")) {
|
|
||||||
const auto item_data = Strings::Split(handin_items, "|");
|
|
||||||
if (
|
|
||||||
item_data.size() == 3 &&
|
|
||||||
Strings::IsNumber(item_data[0]) &&
|
|
||||||
Strings::IsNumber(item_data[1]) &&
|
|
||||||
Strings::IsNumber(item_data[2])
|
|
||||||
) {
|
|
||||||
const uint32 item_id = Strings::ToUnsignedInt(item_data[0]);
|
|
||||||
const auto* item = database.GetItem(item_id);
|
|
||||||
|
|
||||||
if (item) {
|
|
||||||
hi.emplace_back(
|
|
||||||
PlayerEvent::HandinEntry{
|
|
||||||
.item_id = item_id,
|
|
||||||
.item_name = item->Name,
|
|
||||||
.charges = static_cast<uint16>(Strings::ToUnsignedInt(item_data[1])),
|
|
||||||
.attuned = Strings::ToInt(item_data[2]) ? true : false
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handin Money
|
|
||||||
if (!handin_money.empty()) {
|
|
||||||
const auto hms = Strings::Split(handin_money, "|");
|
|
||||||
|
|
||||||
hm.copper = Strings::ToUnsignedInt(hms[0]);
|
|
||||||
hm.silver = Strings::ToUnsignedInt(hms[1]);
|
|
||||||
hm.gold = Strings::ToUnsignedInt(hms[2]);
|
|
||||||
hm.platinum = Strings::ToUnsignedInt(hms[3]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return Items
|
|
||||||
if (!return_items.empty()) {
|
|
||||||
if (Strings::Contains(return_items, ",")) {
|
|
||||||
const auto return_data = Strings::Split(return_items, ",");
|
|
||||||
for (const auto& r : return_data) {
|
|
||||||
const auto item_data = Strings::Split(r, "|");
|
|
||||||
if (
|
|
||||||
item_data.size() == 3 &&
|
|
||||||
Strings::IsNumber(item_data[0]) &&
|
|
||||||
Strings::IsNumber(item_data[1]) &&
|
|
||||||
Strings::IsNumber(item_data[2])
|
|
||||||
) {
|
|
||||||
const uint32 item_id = Strings::ToUnsignedInt(item_data[0]);
|
|
||||||
const auto* item = database.GetItem(item_id);
|
|
||||||
|
|
||||||
if (item) {
|
|
||||||
ri.emplace_back(
|
|
||||||
PlayerEvent::HandinEntry{
|
|
||||||
.item_id = item_id,
|
|
||||||
.item_name = item->Name,
|
|
||||||
.charges = static_cast<uint16>(Strings::ToUnsignedInt(item_data[1])),
|
|
||||||
.attuned = Strings::ToInt(item_data[2]) ? true : false
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (Strings::Contains(return_items, "|")) {
|
|
||||||
const auto item_data = Strings::Split(return_items, "|");
|
|
||||||
if (
|
|
||||||
item_data.size() == 3 &&
|
|
||||||
Strings::IsNumber(item_data[0]) &&
|
|
||||||
Strings::IsNumber(item_data[1]) &&
|
|
||||||
Strings::IsNumber(item_data[2])
|
|
||||||
) {
|
|
||||||
const uint32 item_id = Strings::ToUnsignedInt(item_data[0]);
|
|
||||||
const auto* item = database.GetItem(item_id);
|
|
||||||
|
|
||||||
if (item) {
|
|
||||||
ri.emplace_back(
|
|
||||||
PlayerEvent::HandinEntry{
|
|
||||||
.item_id = item_id,
|
|
||||||
.item_name = item->Name,
|
|
||||||
.charges = static_cast<uint16>(Strings::ToUnsignedInt(item_data[1])),
|
|
||||||
.attuned = Strings::ToInt(item_data[2]) ? true : false
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return Money
|
|
||||||
if (!return_money.empty()) {
|
|
||||||
const auto rms = Strings::Split(return_money, "|");
|
|
||||||
rm.copper = static_cast<uint32>(Strings::ToUnsignedInt(rms[0]));
|
|
||||||
rm.silver = static_cast<uint32>(Strings::ToUnsignedInt(rms[1]));
|
|
||||||
rm.gold = static_cast<uint32>(Strings::ToUnsignedInt(rms[2]));
|
|
||||||
rm.platinum = static_cast<uint32>(Strings::ToUnsignedInt(rms[3]));
|
|
||||||
}
|
|
||||||
|
|
||||||
c->DeleteEntityVariable("HANDIN_ITEMS");
|
|
||||||
c->DeleteEntityVariable("HANDIN_MONEY");
|
|
||||||
c->DeleteEntityVariable("RETURN_ITEMS");
|
|
||||||
c->DeleteEntityVariable("RETURN_MONEY");
|
|
||||||
|
|
||||||
const bool handed_in_money = hm.platinum > 0 || hm.gold > 0 || hm.silver > 0 || hm.copper > 0;
|
|
||||||
|
|
||||||
const bool event_has_data_to_record = (
|
|
||||||
!hi.empty() || handed_in_money
|
|
||||||
);
|
|
||||||
|
|
||||||
if (player_event_logs.IsEventEnabled(PlayerEvent::NPC_HANDIN) && event_has_data_to_record) {
|
|
||||||
auto e = PlayerEvent::HandinEvent{
|
|
||||||
.npc_id = n->GetNPCTypeID(),
|
|
||||||
.npc_name = n->GetCleanName(),
|
|
||||||
.handin_items = hi,
|
|
||||||
.handin_money = hm,
|
|
||||||
.return_items = ri,
|
|
||||||
.return_money = rm,
|
|
||||||
.is_quest_handin = true
|
|
||||||
};
|
|
||||||
|
|
||||||
RecordPlayerEventLogWithClient(c, PlayerEvent::NPC_HANDIN, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8 item_count = 0;
|
|
||||||
|
|
||||||
hm.platinum = t->pp;
|
|
||||||
hm.gold = t->gp;
|
|
||||||
hm.silver = t->sp;
|
|
||||||
hm.copper = t->cp;
|
|
||||||
|
|
||||||
for (uint16 i = EQ::invslot::TRADE_BEGIN; i <= EQ::invslot::TRADE_NPC_END; i++) {
|
|
||||||
if (c->GetInv().GetItem(i)) {
|
|
||||||
item_count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hi.reserve(item_count);
|
|
||||||
|
|
||||||
if (item_count > 0) {
|
|
||||||
for (uint16 i = EQ::invslot::TRADE_BEGIN; i <= EQ::invslot::TRADE_NPC_END; i++) {
|
|
||||||
const EQ::ItemInstance* inst = c->GetInv().GetItem(i);
|
|
||||||
if (inst) {
|
|
||||||
hi.emplace_back(
|
|
||||||
PlayerEvent::HandinEntry{
|
|
||||||
.item_id = inst->GetItem()->ID,
|
|
||||||
.item_name = inst->GetItem()->Name,
|
|
||||||
.charges = static_cast<uint16>(inst->GetCharges()),
|
|
||||||
.attuned = inst->IsAttuned()
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (inst->IsClassBag()) {
|
|
||||||
for (uint8 j = EQ::invbag::SLOT_BEGIN; j <= EQ::invbag::SLOT_END; j++) {
|
|
||||||
inst = c->GetInv().GetItem(i, j);
|
|
||||||
if (inst) {
|
|
||||||
hi.emplace_back(
|
|
||||||
PlayerEvent::HandinEntry{
|
|
||||||
.item_id = inst->GetItem()->ID,
|
|
||||||
.item_name = inst->GetItem()->Name,
|
|
||||||
.charges = static_cast<uint16>(inst->GetCharges()),
|
|
||||||
.attuned = inst->IsAttuned()
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const bool handed_in_money = hm.platinum > 0 || hm.gold > 0 || hm.silver > 0 || hm.copper > 0;
|
|
||||||
|
|
||||||
ri = hi;
|
|
||||||
rm = hm;
|
|
||||||
|
|
||||||
const bool event_has_data_to_record = !hi.empty() || handed_in_money;
|
|
||||||
|
|
||||||
if (player_event_logs.IsEventEnabled(PlayerEvent::NPC_HANDIN) && event_has_data_to_record) {
|
|
||||||
auto e = PlayerEvent::HandinEvent{
|
|
||||||
.npc_id = n->GetNPCTypeID(),
|
|
||||||
.npc_name = n->GetCleanName(),
|
|
||||||
.handin_items = hi,
|
|
||||||
.handin_money = hm,
|
|
||||||
.return_items = ri,
|
|
||||||
.return_money = rm,
|
|
||||||
.is_quest_handin = false
|
|
||||||
};
|
|
||||||
|
|
||||||
RecordPlayerEventLogWithClient(c, PlayerEvent::NPC_HANDIN, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Client::ShowSpells(Client* c, ShowSpellType show_spell_type)
|
void Client::ShowSpells(Client* c, ShowSpellType show_spell_type)
|
||||||
{
|
{
|
||||||
std::string spell_string;
|
std::string spell_string;
|
||||||
|
|||||||
@ -255,6 +255,7 @@ public:
|
|||||||
#include "client_packet.h"
|
#include "client_packet.h"
|
||||||
|
|
||||||
Client(EQStreamInterface * ieqs);
|
Client(EQStreamInterface * ieqs);
|
||||||
|
Client(); // mocking / testing
|
||||||
~Client();
|
~Client();
|
||||||
|
|
||||||
void ReconnectUCS();
|
void ReconnectUCS();
|
||||||
@ -1101,7 +1102,6 @@ public:
|
|||||||
|
|
||||||
// Item methods
|
// Item methods
|
||||||
void UseAugmentContainer(int container_slot);
|
void UseAugmentContainer(int container_slot);
|
||||||
void EVENT_ITEM_ScriptStopReturn();
|
|
||||||
uint32 NukeItem(uint32 itemnum, uint8 where_to_check =
|
uint32 NukeItem(uint32 itemnum, uint8 where_to_check =
|
||||||
(invWhereWorn | invWherePersonal | invWhereBank | invWhereSharedBank | invWhereTrading | invWhereCursor));
|
(invWhereWorn | invWherePersonal | invWhereBank | invWhereSharedBank | invWhereTrading | invWhereCursor));
|
||||||
void SetTint(int16 slot_id, uint32 color);
|
void SetTint(int16 slot_id, uint32 color);
|
||||||
@ -1868,6 +1868,24 @@ public:
|
|||||||
uint32 GetBandolierItemID(uint8 bandolier_slot, uint8 slot_id);
|
uint32 GetBandolierItemID(uint8 bandolier_slot, uint8 slot_id);
|
||||||
std::string GetBandolierItemName(uint8 bandolier_slot, uint8 slot_id);
|
std::string GetBandolierItemName(uint8 bandolier_slot, uint8 slot_id);
|
||||||
|
|
||||||
|
// External handin tracking
|
||||||
|
// this is used to prevent things like quest::givecash and AddMoneyToPP
|
||||||
|
// from double giving money back to players in scripts when return_items
|
||||||
|
// also gives money back to players
|
||||||
|
struct ExternalHandinMoneyReturned {
|
||||||
|
uint64 copper;
|
||||||
|
uint64 silver;
|
||||||
|
uint64 gold;
|
||||||
|
uint64 platinum;
|
||||||
|
std::string return_source;
|
||||||
|
};
|
||||||
|
private:
|
||||||
|
ExternalHandinMoneyReturned m_external_handin_money_returned = {};
|
||||||
|
std::vector<uint32_t> m_external_handin_items_returned = {};
|
||||||
|
public:
|
||||||
|
ExternalHandinMoneyReturned GetExternalHandinMoneyReturned() { return m_external_handin_money_returned; }
|
||||||
|
std::vector<uint32_t> GetExternalHandinItemsReturned() { return m_external_handin_items_returned; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
friend class Mob;
|
friend class Mob;
|
||||||
void CalcEdibleBonuses(StatBonuses* newbon);
|
void CalcEdibleBonuses(StatBonuses* newbon);
|
||||||
@ -2317,7 +2335,6 @@ private:
|
|||||||
bool CanTradeFVNoDropItem();
|
bool CanTradeFVNoDropItem();
|
||||||
void SendMobPositions();
|
void SendMobPositions();
|
||||||
void PlayerTradeEventLog(Trade *t, Trade *t2);
|
void PlayerTradeEventLog(Trade *t, Trade *t2);
|
||||||
void NPCHandinEventLog(Trade* t, NPC* n);
|
|
||||||
|
|
||||||
// full and partial mail key cache
|
// full and partial mail key cache
|
||||||
std::string m_mail_key_full;
|
std::string m_mail_key_full;
|
||||||
|
|||||||
@ -502,9 +502,6 @@ void Client::AddEXP(ExpSource exp_source, uint64 in_add_exp, uint8 conlevel, boo
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
EVENT_ITEM_ScriptStopReturn();
|
|
||||||
|
|
||||||
uint64 exp = 0;
|
uint64 exp = 0;
|
||||||
uint64 aaexp = 0;
|
uint64 aaexp = 0;
|
||||||
|
|
||||||
|
|||||||
@ -181,10 +181,6 @@ bool Client::CheckLoreConflict(const EQ::ItemData* item)
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool Client::SummonItem(uint32 item_id, int16 charges, uint32 aug1, uint32 aug2, uint32 aug3, uint32 aug4, uint32 aug5, uint32 aug6, bool attuned, uint16 to_slot, uint32 ornament_icon, uint32 ornament_idfile, uint32 ornament_hero_model) {
|
bool Client::SummonItem(uint32 item_id, int16 charges, uint32 aug1, uint32 aug2, uint32 aug3, uint32 aug4, uint32 aug5, uint32 aug6, bool attuned, uint16 to_slot, uint32 ornament_icon, uint32 ornament_idfile, uint32 ornament_hero_model) {
|
||||||
EVENT_ITEM_ScriptStopReturn();
|
|
||||||
|
|
||||||
// TODO: update calling methods and script apis to handle a failure return
|
|
||||||
|
|
||||||
const EQ::ItemData* item = database.GetItem(item_id);
|
const EQ::ItemData* item = database.GetItem(item_id);
|
||||||
|
|
||||||
// make sure the item exists
|
// make sure the item exists
|
||||||
@ -658,6 +654,8 @@ bool Client::SummonItem(uint32 item_id, int16 charges, uint32 aug1, uint32 aug2,
|
|||||||
PutItemInInventory(to_slot, *inst, true);
|
PutItemInInventory(to_slot, *inst, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_external_handin_items_returned.emplace_back(inst->GetItem()->ID);
|
||||||
|
|
||||||
safe_delete(inst);
|
safe_delete(inst);
|
||||||
|
|
||||||
// discover item and any augments
|
// discover item and any augments
|
||||||
@ -3181,8 +3179,13 @@ uint32 Client::GetEquipmentColor(uint8 material_slot) const
|
|||||||
// Send an item packet (including all subitems of the item)
|
// Send an item packet (including all subitems of the item)
|
||||||
void Client::SendItemPacket(int16 slot_id, const EQ::ItemInstance* inst, ItemPacketType packet_type)
|
void Client::SendItemPacket(int16 slot_id, const EQ::ItemInstance* inst, ItemPacketType packet_type)
|
||||||
{
|
{
|
||||||
if (!inst)
|
if (!inst) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!eqs) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (packet_type != ItemPacketMerchant) {
|
if (packet_type != ItemPacketMerchant) {
|
||||||
if (slot_id <= EQ::invslot::POSSESSIONS_END && slot_id >= EQ::invslot::POSSESSIONS_BEGIN) {
|
if (slot_id <= EQ::invslot::POSSESSIONS_END && slot_id >= EQ::invslot::POSSESSIONS_BEGIN) {
|
||||||
|
|||||||
@ -433,6 +433,12 @@ void Lua_ItemInst::SetEvolveProgression(float amount)
|
|||||||
self->SetEvolveProgression(amount);
|
self->SetEvolveProgression(amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int Lua_ItemInst::GetSerialNumber()
|
||||||
|
{
|
||||||
|
Lua_Safe_Call_Int();
|
||||||
|
return self->GetSerialNumber();
|
||||||
|
}
|
||||||
|
|
||||||
luabind::scope lua_register_iteminst() {
|
luabind::scope lua_register_iteminst() {
|
||||||
return luabind::class_<Lua_ItemInst>("ItemInst")
|
return luabind::class_<Lua_ItemInst>("ItemInst")
|
||||||
.def(luabind::constructor<>())
|
.def(luabind::constructor<>())
|
||||||
@ -475,6 +481,7 @@ luabind::scope lua_register_iteminst() {
|
|||||||
.def("GetItemScriptID", (uint32(Lua_ItemInst::*)(void))&Lua_ItemInst::GetItemScriptID)
|
.def("GetItemScriptID", (uint32(Lua_ItemInst::*)(void))&Lua_ItemInst::GetItemScriptID)
|
||||||
.def("GetMaxEvolveLvl", (int(Lua_ItemInst::*)(void))&Lua_ItemInst::GetMaxEvolveLvl)
|
.def("GetMaxEvolveLvl", (int(Lua_ItemInst::*)(void))&Lua_ItemInst::GetMaxEvolveLvl)
|
||||||
.def("GetName", (std::string(Lua_ItemInst::*)(void))&Lua_ItemInst::GetName)
|
.def("GetName", (std::string(Lua_ItemInst::*)(void))&Lua_ItemInst::GetName)
|
||||||
|
.def("GetSerialNumber", (int(Lua_ItemInst::*)(void))&Lua_ItemInst::GetSerialNumber)
|
||||||
.def("GetPrice", (uint32(Lua_ItemInst::*)(void))&Lua_ItemInst::GetPrice)
|
.def("GetPrice", (uint32(Lua_ItemInst::*)(void))&Lua_ItemInst::GetPrice)
|
||||||
.def("GetTaskDeliveredCount", &Lua_ItemInst::GetTaskDeliveredCount)
|
.def("GetTaskDeliveredCount", &Lua_ItemInst::GetTaskDeliveredCount)
|
||||||
.def("GetTotalItemCount", (uint8(Lua_ItemInst::*)(void))&Lua_ItemInst::GetTotalItemCount)
|
.def("GetTotalItemCount", (uint8(Lua_ItemInst::*)(void))&Lua_ItemInst::GetTotalItemCount)
|
||||||
|
|||||||
@ -86,6 +86,7 @@ public:
|
|||||||
int GetTaskDeliveredCount();
|
int GetTaskDeliveredCount();
|
||||||
int RemoveTaskDeliveredItems();
|
int RemoveTaskDeliveredItems();
|
||||||
std::string GetName();
|
std::string GetName();
|
||||||
|
int GetSerialNumber();
|
||||||
void ItemSay(const char* text);
|
void ItemSay(const char* text);
|
||||||
void ItemSay(const char* text, uint8 language_id);
|
void ItemSay(const char* text, uint8 language_id);
|
||||||
luabind::object GetAugmentIDs(lua_State* L);
|
luabind::object GetAugmentIDs(lua_State* L);
|
||||||
|
|||||||
@ -7,6 +7,8 @@
|
|||||||
#include "npc.h"
|
#include "npc.h"
|
||||||
#include "lua_npc.h"
|
#include "lua_npc.h"
|
||||||
#include "lua_client.h"
|
#include "lua_client.h"
|
||||||
|
#include "lua_item.h"
|
||||||
|
#include "lua_iteminst.h"
|
||||||
|
|
||||||
struct Lua_NPC_Loot_List {
|
struct Lua_NPC_Loot_List {
|
||||||
std::vector<uint32> entries;
|
std::vector<uint32> entries;
|
||||||
@ -837,6 +839,99 @@ void Lua_NPC::DescribeSpecialAbilities(Lua_Client c)
|
|||||||
self->DescribeSpecialAbilities(c);
|
self->DescribeSpecialAbilities(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Lua_NPC::IsMultiQuestEnabled()
|
||||||
|
{
|
||||||
|
Lua_Safe_Call_Bool();
|
||||||
|
return self->IsMultiQuestEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Lua_NPC::MultiQuestEnable()
|
||||||
|
{
|
||||||
|
Lua_Safe_Call_Void();
|
||||||
|
self->MultiQuestEnable();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Lua_NPC::LuaCheckHandin(
|
||||||
|
Lua_Client c,
|
||||||
|
luabind::adl::object handin_table,
|
||||||
|
luabind::adl::object required_table,
|
||||||
|
luabind::adl::object items_table
|
||||||
|
)
|
||||||
|
{
|
||||||
|
Lua_Safe_Call_Bool();
|
||||||
|
|
||||||
|
if (
|
||||||
|
luabind::type(handin_table) != LUA_TTABLE ||
|
||||||
|
luabind::type(required_table) != LUA_TTABLE ||
|
||||||
|
luabind::type(items_table) != LUA_TTABLE
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<std::string, uint32> handin_map;
|
||||||
|
std::map<std::string, uint32> required_map;
|
||||||
|
std::vector<EQ::ItemInstance *> items;
|
||||||
|
|
||||||
|
for (luabind::iterator i(handin_table), end; i != end; i++) {
|
||||||
|
std::string key;
|
||||||
|
if (luabind::type(i.key()) == LUA_TSTRING) {
|
||||||
|
key = luabind::object_cast<std::string>(i.key());
|
||||||
|
}
|
||||||
|
else if (luabind::type(i.key()) == LUA_TNUMBER) {
|
||||||
|
key = fmt::format("{}", luabind::object_cast<int>(i.key()));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LogError("Handin key type [{}] not supported", luabind::type(i.key()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!key.empty()) {
|
||||||
|
handin_map[key] = luabind::object_cast<uint32>(handin_table[i.key()]);
|
||||||
|
LogNpcHandinDetail("Handin key [{}] value [{}]", key, handin_map[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (luabind::iterator i(required_table), end; i != end; i++) {
|
||||||
|
std::string key;
|
||||||
|
if (luabind::type(i.key()) == LUA_TSTRING) {
|
||||||
|
key = luabind::object_cast<std::string>(i.key());
|
||||||
|
}
|
||||||
|
else if (luabind::type(i.key()) == LUA_TNUMBER) {
|
||||||
|
key = fmt::format("{}", luabind::object_cast<int>(i.key()));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
LogError("Required key type [{}] not supported", luabind::type(i.key()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!key.empty()) {
|
||||||
|
required_map[key] = luabind::object_cast<uint32>(required_table[i.key()]);
|
||||||
|
LogNpcHandinDetail("Required key [{}] value [{}]", key, required_map[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (luabind::iterator i(items_table), end; i != end; i++) {
|
||||||
|
auto item = luabind::object_cast<Lua_ItemInst>(items_table[i.key()]);
|
||||||
|
|
||||||
|
if (item && item.GetItem()) {
|
||||||
|
LogNpcHandinDetail(
|
||||||
|
"Item instance [{}] ({}) UUID ({}) added to handin list",
|
||||||
|
item.GetName(),
|
||||||
|
item.GetID(),
|
||||||
|
item.GetSerialNumber()
|
||||||
|
);
|
||||||
|
|
||||||
|
items.emplace_back(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return self->CheckHandin(c, handin_map, required_map, items);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Lua_NPC::ReturnHandinItems(Lua_Client c)
|
||||||
|
{
|
||||||
|
Lua_Safe_Call_Void();
|
||||||
|
self->ReturnHandinItems(c);
|
||||||
|
}
|
||||||
|
|
||||||
luabind::scope lua_register_npc() {
|
luabind::scope lua_register_npc() {
|
||||||
return luabind::class_<Lua_NPC, Lua_Mob>("NPC")
|
return luabind::class_<Lua_NPC, Lua_Mob>("NPC")
|
||||||
.def(luabind::constructor<>())
|
.def(luabind::constructor<>())
|
||||||
@ -859,6 +954,7 @@ luabind::scope lua_register_npc() {
|
|||||||
.def("AssignWaypoints", (void(Lua_NPC::*)(int))&Lua_NPC::AssignWaypoints)
|
.def("AssignWaypoints", (void(Lua_NPC::*)(int))&Lua_NPC::AssignWaypoints)
|
||||||
.def("CalculateNewWaypoint", (void(Lua_NPC::*)(void))&Lua_NPC::CalculateNewWaypoint)
|
.def("CalculateNewWaypoint", (void(Lua_NPC::*)(void))&Lua_NPC::CalculateNewWaypoint)
|
||||||
.def("ChangeLastName", (void(Lua_NPC::*)(std::string))&Lua_NPC::ChangeLastName)
|
.def("ChangeLastName", (void(Lua_NPC::*)(std::string))&Lua_NPC::ChangeLastName)
|
||||||
|
.def("CheckHandin", (bool(Lua_NPC::*)(Lua_Client,luabind::adl::object,luabind::adl::object,luabind::adl::object))&Lua_NPC::LuaCheckHandin)
|
||||||
.def("CheckNPCFactionAlly", (int(Lua_NPC::*)(int))&Lua_NPC::CheckNPCFactionAlly)
|
.def("CheckNPCFactionAlly", (int(Lua_NPC::*)(int))&Lua_NPC::CheckNPCFactionAlly)
|
||||||
.def("ClearItemList", (void(Lua_NPC::*)(void))&Lua_NPC::ClearLootItems)
|
.def("ClearItemList", (void(Lua_NPC::*)(void))&Lua_NPC::ClearLootItems)
|
||||||
.def("ClearLastName", (void(Lua_NPC::*)(void))&Lua_NPC::ClearLastName)
|
.def("ClearLastName", (void(Lua_NPC::*)(void))&Lua_NPC::ClearLastName)
|
||||||
@ -932,6 +1028,7 @@ luabind::scope lua_register_npc() {
|
|||||||
.def("IsLDoNLocked", (bool(Lua_NPC::*)(void))&Lua_NPC::IsLDoNLocked)
|
.def("IsLDoNLocked", (bool(Lua_NPC::*)(void))&Lua_NPC::IsLDoNLocked)
|
||||||
.def("IsLDoNTrapped", (bool(Lua_NPC::*)(void))&Lua_NPC::IsLDoNTrapped)
|
.def("IsLDoNTrapped", (bool(Lua_NPC::*)(void))&Lua_NPC::IsLDoNTrapped)
|
||||||
.def("IsLDoNTrapDetected", (bool(Lua_NPC::*)(void))&Lua_NPC::IsLDoNTrapDetected)
|
.def("IsLDoNTrapDetected", (bool(Lua_NPC::*)(void))&Lua_NPC::IsLDoNTrapDetected)
|
||||||
|
.def("IsMultiQuestEnabled", (bool(Lua_NPC::*)(void))&Lua_NPC::IsMultiQuestEnabled)
|
||||||
.def("IsOnHatelist", (bool(Lua_NPC::*)(Lua_Mob))&Lua_NPC::IsOnHatelist)
|
.def("IsOnHatelist", (bool(Lua_NPC::*)(Lua_Mob))&Lua_NPC::IsOnHatelist)
|
||||||
.def("IsRaidTarget", (bool(Lua_NPC::*)(void))&Lua_NPC::IsRaidTarget)
|
.def("IsRaidTarget", (bool(Lua_NPC::*)(void))&Lua_NPC::IsRaidTarget)
|
||||||
.def("IsRareSpawn", (bool(Lua_NPC::*)(void))&Lua_NPC::IsRareSpawn)
|
.def("IsRareSpawn", (bool(Lua_NPC::*)(void))&Lua_NPC::IsRareSpawn)
|
||||||
@ -941,6 +1038,7 @@ luabind::scope lua_register_npc() {
|
|||||||
.def("MerchantOpenShop", (void(Lua_NPC::*)(void))&Lua_NPC::MerchantOpenShop)
|
.def("MerchantOpenShop", (void(Lua_NPC::*)(void))&Lua_NPC::MerchantOpenShop)
|
||||||
.def("ModifyNPCStat", (void(Lua_NPC::*)(std::string,std::string))&Lua_NPC::ModifyNPCStat)
|
.def("ModifyNPCStat", (void(Lua_NPC::*)(std::string,std::string))&Lua_NPC::ModifyNPCStat)
|
||||||
.def("MoveTo", (void(Lua_NPC::*)(float,float,float,float,bool))&Lua_NPC::MoveTo)
|
.def("MoveTo", (void(Lua_NPC::*)(float,float,float,float,bool))&Lua_NPC::MoveTo)
|
||||||
|
.def("MultiQuestEnable", &Lua_NPC::MultiQuestEnable)
|
||||||
.def("NextGuardPosition", (void(Lua_NPC::*)(void))&Lua_NPC::NextGuardPosition)
|
.def("NextGuardPosition", (void(Lua_NPC::*)(void))&Lua_NPC::NextGuardPosition)
|
||||||
.def("PauseWandering", (void(Lua_NPC::*)(int))&Lua_NPC::PauseWandering)
|
.def("PauseWandering", (void(Lua_NPC::*)(int))&Lua_NPC::PauseWandering)
|
||||||
.def("PickPocket", (void(Lua_NPC::*)(Lua_Client))&Lua_NPC::PickPocket)
|
.def("PickPocket", (void(Lua_NPC::*)(Lua_Client))&Lua_NPC::PickPocket)
|
||||||
@ -953,6 +1051,7 @@ luabind::scope lua_register_npc() {
|
|||||||
.def("RemoveItem", (void(Lua_NPC::*)(int,int))&Lua_NPC::RemoveItem)
|
.def("RemoveItem", (void(Lua_NPC::*)(int,int))&Lua_NPC::RemoveItem)
|
||||||
.def("RemoveItem", (void(Lua_NPC::*)(int,int,int))&Lua_NPC::RemoveItem)
|
.def("RemoveItem", (void(Lua_NPC::*)(int,int,int))&Lua_NPC::RemoveItem)
|
||||||
.def("ResumeWandering", (void(Lua_NPC::*)(void))&Lua_NPC::ResumeWandering)
|
.def("ResumeWandering", (void(Lua_NPC::*)(void))&Lua_NPC::ResumeWandering)
|
||||||
|
.def("ReturnHandinItems", (void(Lua_NPC::*)(Lua_Client))&Lua_NPC::ReturnHandinItems)
|
||||||
.def("SaveGuardSpot", (void(Lua_NPC::*)(void))&Lua_NPC::SaveGuardSpot)
|
.def("SaveGuardSpot", (void(Lua_NPC::*)(void))&Lua_NPC::SaveGuardSpot)
|
||||||
.def("SaveGuardSpot", (void(Lua_NPC::*)(bool))&Lua_NPC::SaveGuardSpot)
|
.def("SaveGuardSpot", (void(Lua_NPC::*)(bool))&Lua_NPC::SaveGuardSpot)
|
||||||
.def("SaveGuardSpot", (void(Lua_NPC::*)(float,float,float,float))&Lua_NPC::SaveGuardSpot)
|
.def("SaveGuardSpot", (void(Lua_NPC::*)(float,float,float,float))&Lua_NPC::SaveGuardSpot)
|
||||||
|
|||||||
@ -9,6 +9,7 @@ class Lua_Mob;
|
|||||||
class Lua_NPC;
|
class Lua_NPC;
|
||||||
class Lua_Client;
|
class Lua_Client;
|
||||||
struct Lua_NPC_Loot_List;
|
struct Lua_NPC_Loot_List;
|
||||||
|
class Lua_Inventory;
|
||||||
|
|
||||||
namespace luabind {
|
namespace luabind {
|
||||||
struct scope;
|
struct scope;
|
||||||
@ -186,6 +187,15 @@ public:
|
|||||||
void SetNPCAggro(bool in_npc_aggro);
|
void SetNPCAggro(bool in_npc_aggro);
|
||||||
uint32 GetNPCSpellsEffectsID();
|
uint32 GetNPCSpellsEffectsID();
|
||||||
void DescribeSpecialAbilities(Lua_Client c);
|
void DescribeSpecialAbilities(Lua_Client c);
|
||||||
|
bool IsMultiQuestEnabled();
|
||||||
|
void MultiQuestEnable();
|
||||||
|
bool LuaCheckHandin(
|
||||||
|
Lua_Client c,
|
||||||
|
luabind::adl::object handin_table,
|
||||||
|
luabind::adl::object required_table,
|
||||||
|
luabind::adl::object items_table
|
||||||
|
);
|
||||||
|
void ReturnHandinItems(Lua_Client c);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -56,6 +56,11 @@ void handle_npc_event_trade(
|
|||||||
uint32 extra_data,
|
uint32 extra_data,
|
||||||
std::vector<std::any> *extra_pointers
|
std::vector<std::any> *extra_pointers
|
||||||
) {
|
) {
|
||||||
|
Lua_NPC l_npc(reinterpret_cast<NPC*>(npc));
|
||||||
|
luabind::adl::object l_npc_o = luabind::adl::object(L, l_npc);
|
||||||
|
l_npc_o.push(L);
|
||||||
|
lua_setfield(L, -2, "self");
|
||||||
|
|
||||||
Lua_Client l_client(reinterpret_cast<Client *>(init));
|
Lua_Client l_client(reinterpret_cast<Client *>(init));
|
||||||
luabind::adl::object l_client_o = luabind::adl::object(L, l_client);
|
luabind::adl::object l_client_o = luabind::adl::object(L, l_client);
|
||||||
l_client_o.push(L);
|
l_client_o.push(L);
|
||||||
@ -102,6 +107,10 @@ void handle_npc_event_trade(
|
|||||||
lua_pushinteger(L, money_value);
|
lua_pushinteger(L, money_value);
|
||||||
lua_setfield(L, -2, "copper");
|
lua_setfield(L, -2, "copper");
|
||||||
|
|
||||||
|
// set a reference to the NPC inside the trade object as well for plugins to process
|
||||||
|
l_npc_o.push(L);
|
||||||
|
lua_setfield(L, -2, "self");
|
||||||
|
|
||||||
// set a reference to the client inside of the trade object as well for plugins to process
|
// set a reference to the client inside of the trade object as well for plugins to process
|
||||||
l_client_o.push(L);
|
l_client_o.push(L);
|
||||||
lua_setfield(L, -2, "other");
|
lua_setfield(L, -2, "other");
|
||||||
|
|||||||
@ -124,6 +124,7 @@ void CatchSignal(int sig_num);
|
|||||||
|
|
||||||
extern void MapOpcodes();
|
extern void MapOpcodes();
|
||||||
|
|
||||||
|
bool CheckForCompatibleQuestPlugins();
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
RegisterExecutablePlatform(ExePlatformZone);
|
RegisterExecutablePlatform(ExePlatformZone);
|
||||||
@ -298,7 +299,7 @@ int main(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// command handler
|
// command handler
|
||||||
if (ZoneCLI::RanConsoleCommand(argc, argv) && !ZoneCLI::RanSidecarCommand(argc, argv)) {
|
if (ZoneCLI::RanConsoleCommand(argc, argv) && !(ZoneCLI::RanSidecarCommand(argc, argv) || ZoneCLI::RanTestCommand(argc, argv))) {
|
||||||
LogSys.EnableConsoleLogging();
|
LogSys.EnableConsoleLogging();
|
||||||
ZoneCLI::CommandHandler(argc, argv);
|
ZoneCLI::CommandHandler(argc, argv);
|
||||||
}
|
}
|
||||||
@ -369,6 +370,11 @@ int main(int argc, char **argv)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!CheckForCompatibleQuestPlugins()) {
|
||||||
|
LogError("Incompatible quest plugins detected, please update your plugins to the latest version");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
// load these here for now until spells and items can be truly repointed to "content_db"
|
// load these here for now until spells and items can be truly repointed to "content_db"
|
||||||
database.SetSharedItemsCount(content_db.GetItemsCount());
|
database.SetSharedItemsCount(content_db.GetItemsCount());
|
||||||
database.SetSharedSpellsCount(content_db.GetSpellsCount());
|
database.SetSharedSpellsCount(content_db.GetSpellsCount());
|
||||||
@ -481,7 +487,8 @@ int main(int argc, char **argv)
|
|||||||
worldserver.SetScheduler(&event_scheduler);
|
worldserver.SetScheduler(&event_scheduler);
|
||||||
|
|
||||||
// sidecar command handler
|
// sidecar command handler
|
||||||
if (ZoneCLI::RanConsoleCommand(argc, argv) && ZoneCLI::RanSidecarCommand(argc, argv)) {
|
if (ZoneCLI::RanConsoleCommand(argc, argv)
|
||||||
|
&& (ZoneCLI::RanSidecarCommand(argc, argv) || ZoneCLI::RanTestCommand(argc, argv))) {
|
||||||
ZoneCLI::CommandHandler(argc, argv);
|
ZoneCLI::CommandHandler(argc, argv);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -712,3 +719,43 @@ void UpdateWindowTitle(char *iNewTitle)
|
|||||||
SetConsoleTitle(tmp);
|
SetConsoleTitle(tmp);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CheckForCompatibleQuestPlugins()
|
||||||
|
{
|
||||||
|
const std::vector<std::string>& directories = { "lua_modules", "plugins" };
|
||||||
|
|
||||||
|
bool lua_found = false;
|
||||||
|
bool perl_found = false;
|
||||||
|
|
||||||
|
for (const auto& directory : directories) {
|
||||||
|
for (const auto& file : fs::directory_iterator(path.GetServerPath() + "/" + directory)) {
|
||||||
|
if (file.is_regular_file()) {
|
||||||
|
auto f = file.path().string();
|
||||||
|
if (File::Exists(f)) {
|
||||||
|
auto r = File::GetContents(std::filesystem::path{ f }.string());
|
||||||
|
if (Strings::Contains(r.contents, "CheckHandin")) {
|
||||||
|
if (Strings::EqualFold(directory, "lua_modules")) {
|
||||||
|
lua_found = true;
|
||||||
|
} else if (Strings::EqualFold(directory, "plugins")) {
|
||||||
|
perl_found = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lua_found && perl_found) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!lua_found) {
|
||||||
|
LogError("Failed to find CheckHandin in lua_modules");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!perl_found) {
|
||||||
|
LogError("Failed to find CheckHandin in plugins");
|
||||||
|
}
|
||||||
|
|
||||||
|
return lua_found && perl_found;
|
||||||
|
}
|
||||||
|
|||||||
27
zone/mob.cpp
27
zone/mob.cpp
@ -8724,7 +8724,8 @@ bool Mob::CheckLosCheat(Mob* other) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Mob::CheckLosCheatExempt(Mob* other) {
|
bool Mob::CheckLosCheatExempt(Mob* other)
|
||||||
|
{
|
||||||
if (RuleB(Map, EnableLoSCheatExemptions)) {
|
if (RuleB(Map, EnableLoSCheatExemptions)) {
|
||||||
/* This is an exmaple of how to configure exemptions for LoS checks.
|
/* This is an exmaple of how to configure exemptions for LoS checks.
|
||||||
glm::vec4 exempt_check_who;
|
glm::vec4 exempt_check_who;
|
||||||
@ -8747,3 +8748,27 @@ bool Mob::CheckLosCheatExempt(Mob* other) {
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Mob::IsGuildmaster() const {
|
||||||
|
switch (GetClass()) {
|
||||||
|
case Class::WarriorGM:
|
||||||
|
case Class::ClericGM:
|
||||||
|
case Class::PaladinGM:
|
||||||
|
case Class::RangerGM:
|
||||||
|
case Class::ShadowKnightGM:
|
||||||
|
case Class::DruidGM:
|
||||||
|
case Class::MonkGM:
|
||||||
|
case Class::BardGM:
|
||||||
|
case Class::RogueGM:
|
||||||
|
case Class::ShamanGM:
|
||||||
|
case Class::NecromancerGM:
|
||||||
|
case Class::WizardGM:
|
||||||
|
case Class::MagicianGM:
|
||||||
|
case Class::EnchanterGM:
|
||||||
|
case Class::BeastlordGM:
|
||||||
|
case Class::BerserkerGM:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1504,6 +1504,7 @@ public:
|
|||||||
void CheckScanCloseMobsMovingTimer();
|
void CheckScanCloseMobsMovingTimer();
|
||||||
|
|
||||||
void ClearDataBucketCache();
|
void ClearDataBucketCache();
|
||||||
|
bool IsGuildmaster() const;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void CommonDamage(Mob* other, int64 &damage, const uint16 spell_id, const EQ::skills::SkillType attack_skill, bool &avoidable, const int8 buffslot, const bool iBuffTic, eSpecialAttacks specal = eSpecialAttacks::None);
|
void CommonDamage(Mob* other, int64 &damage, const uint16 spell_id, const EQ::skills::SkillType attack_skill, bool &avoidable, const int8 buffslot, const bool iBuffTic, eSpecialAttacks specal = eSpecialAttacks::None);
|
||||||
|
|||||||
618
zone/npc.cpp
618
zone/npc.cpp
@ -49,6 +49,7 @@
|
|||||||
|
|
||||||
#include "bot.h"
|
#include "bot.h"
|
||||||
#include "../common/skill_caps.h"
|
#include "../common/skill_caps.h"
|
||||||
|
#include "../common/events/player_event_logs.h"
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
@ -226,6 +227,7 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi
|
|||||||
ATK = npc_type_data->ATK;
|
ATK = npc_type_data->ATK;
|
||||||
heroic_strikethrough = npc_type_data->heroic_strikethrough;
|
heroic_strikethrough = npc_type_data->heroic_strikethrough;
|
||||||
keeps_sold_items = npc_type_data->keeps_sold_items;
|
keeps_sold_items = npc_type_data->keeps_sold_items;
|
||||||
|
m_multiquest_enabled = npc_type_data->multiquest_enabled;
|
||||||
|
|
||||||
// used for when switch back to charm
|
// used for when switch back to charm
|
||||||
default_ac = npc_type_data->AC;
|
default_ac = npc_type_data->AC;
|
||||||
@ -4263,3 +4265,619 @@ bool NPC::FacesTarget()
|
|||||||
return std::find(v.begin(), v.end(), std::to_string(GetBaseRace())) == v.end();
|
return std::find(v.begin(), v.end(), std::to_string(GetBaseRace())) == v.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool NPC::CanPetTakeItem(const EQ::ItemInstance *inst)
|
||||||
|
{
|
||||||
|
if (!inst) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsPetOwnerClient()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool can_take_nodrop = RuleB(Pets, CanTakeNoDrop) || inst->GetItem()->NoDrop != 0;
|
||||||
|
const bool is_charmed_with_attuned = IsCharmed() && inst->IsAttuned();
|
||||||
|
|
||||||
|
auto o = GetOwner() && GetOwner()->IsClient() ? GetOwner()->CastToClient() : nullptr;
|
||||||
|
|
||||||
|
struct Check {
|
||||||
|
bool condition;
|
||||||
|
std::string message;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Check checks[] = {
|
||||||
|
{inst->IsAttuned(), "I cannot equip attuned items, master."},
|
||||||
|
{!can_take_nodrop || is_charmed_with_attuned, "I cannot equip no-drop items, master."},
|
||||||
|
{inst->GetItem()->IsQuestItem(), "I cannot equip quest items, master."},
|
||||||
|
{!inst->GetItem()->IsPetUsable(), "I cannot equip that item, master."}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Iterate over checks and return false if any condition is true
|
||||||
|
for (const auto &c : checks) {
|
||||||
|
if (c.condition) {
|
||||||
|
if (o) {
|
||||||
|
o->Message(Chat::PetResponse, c.message.c_str());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NPC::IsGuildmasterForClient(Client *c) {
|
||||||
|
std::map<uint8, uint8> guildmaster_map = {
|
||||||
|
{ Class::Warrior, Class::WarriorGM },
|
||||||
|
{ Class::Cleric, Class::ClericGM },
|
||||||
|
{ Class::Paladin, Class::PaladinGM },
|
||||||
|
{ Class::Ranger, Class::RangerGM },
|
||||||
|
{ Class::ShadowKnight, Class::ShadowKnightGM },
|
||||||
|
{ Class::Druid, Class::DruidGM },
|
||||||
|
{ Class::Monk, Class::MonkGM },
|
||||||
|
{ Class::Bard, Class::BardGM },
|
||||||
|
{ Class::Rogue, Class::RogueGM },
|
||||||
|
{ Class::Shaman, Class::ShamanGM },
|
||||||
|
{ Class::Necromancer, Class::NecromancerGM },
|
||||||
|
{ Class::Wizard, Class::WizardGM },
|
||||||
|
{ Class::Magician, Class::MagicianGM },
|
||||||
|
{ Class::Enchanter, Class::EnchanterGM },
|
||||||
|
{ Class::Beastlord, Class::BeastlordGM },
|
||||||
|
{ Class::Berserker, Class::BerserkerGM },
|
||||||
|
};
|
||||||
|
|
||||||
|
if (guildmaster_map.find(c->GetClass()) != guildmaster_map.end()) {
|
||||||
|
if (guildmaster_map[c->GetClass()] == GetClass()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NPC::CheckHandin(
|
||||||
|
Client *c,
|
||||||
|
std::map<std::string, uint32> handin,
|
||||||
|
std::map<std::string, uint32> required,
|
||||||
|
std::vector<EQ::ItemInstance *> items
|
||||||
|
)
|
||||||
|
{
|
||||||
|
auto h = Handin{};
|
||||||
|
auto r = Handin{};
|
||||||
|
|
||||||
|
std::string log_handin_prefix = fmt::format("[{}] -> [{}]", c->GetCleanName(), GetCleanName());
|
||||||
|
|
||||||
|
// if the npc is a multi-quest npc, we want to re-use our previously set hand-in bucket
|
||||||
|
if (!m_handin_started && IsMultiQuestEnabled()) {
|
||||||
|
h = m_hand_in;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<const std::map<std::string, uint32>&, Handin&>> datasets = {};
|
||||||
|
|
||||||
|
// if we've already started the hand-in process, we don't want to re-process the hand-in data
|
||||||
|
// we continue to use the originally set hand-in bucket and decrement from it with each successive hand-in
|
||||||
|
if (m_handin_started) {
|
||||||
|
h = m_hand_in;
|
||||||
|
} else {
|
||||||
|
datasets.emplace_back(handin, h);
|
||||||
|
}
|
||||||
|
datasets.emplace_back(required, r);
|
||||||
|
|
||||||
|
const std::string set_hand_in = "Hand-in";
|
||||||
|
const std::string set_required = "Required";
|
||||||
|
for (const auto &[data_map, current_handin]: datasets) {
|
||||||
|
std::string current_dataset = ¤t_handin == &h ? set_hand_in : set_required;
|
||||||
|
for (const auto &[key, value]: data_map) {
|
||||||
|
LogNpcHandinDetail("Processing [{}] key [{}] value [{}]", current_dataset, key, value);
|
||||||
|
|
||||||
|
// Handle items
|
||||||
|
if (Strings::IsNumber(key)) {
|
||||||
|
if (const auto *exists = database.GetItem(Strings::ToUnsignedInt(key));
|
||||||
|
exists && current_dataset == set_required) {
|
||||||
|
current_handin.items.emplace_back(HandinEntry{.item_id = key, .count = value});
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle money and any other key-value pairs
|
||||||
|
if (key == "platinum") { current_handin.money.platinum = value; }
|
||||||
|
else if (key == "gold") { current_handin.money.gold = value; }
|
||||||
|
else if (key == "silver") { current_handin.money.silver = value; }
|
||||||
|
else if (key == "copper") { current_handin.money.copper = value; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pull hand-in items from the item instances
|
||||||
|
if (!m_handin_started) {
|
||||||
|
for (const auto &i: items) {
|
||||||
|
if (!i) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
h.items.emplace_back(
|
||||||
|
HandinEntry{
|
||||||
|
.item_id = std::to_string(i->GetItem()->ID),
|
||||||
|
.count = std::max(static_cast<uint16>(i->IsStackable() ? i->GetCharges() : 1), static_cast<uint16>(1)),
|
||||||
|
.item = i->Clone(),
|
||||||
|
.is_multiquest_item = false
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// compare hand-in to required, the item_id can be in any slot
|
||||||
|
bool requirement_met = true;
|
||||||
|
|
||||||
|
// money
|
||||||
|
bool money_met = h.money.platinum == r.money.platinum
|
||||||
|
&& h.money.gold == r.money.gold
|
||||||
|
&& h.money.silver == r.money.silver
|
||||||
|
&& h.money.copper == r.money.copper;
|
||||||
|
|
||||||
|
// if we started the hand-in process, we want to use the hand-in items from the member variable hand-in bucket
|
||||||
|
auto &handin_items = !m_handin_started ? h.items : m_hand_in.items;
|
||||||
|
|
||||||
|
for (auto &h_item: h.items) {
|
||||||
|
LogNpcHandinDetail(
|
||||||
|
"{} Hand-in item [{}] ({}) count [{}] is_multiquest_item [{}]",
|
||||||
|
log_handin_prefix,
|
||||||
|
h_item.item->GetItem()->Name,
|
||||||
|
h_item.item_id,
|
||||||
|
h_item.count,
|
||||||
|
h_item.is_multiquest_item
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove items from the hand-in bucket that were used to fulfill the requirement
|
||||||
|
std::vector<HandinEntry> items_to_remove;
|
||||||
|
|
||||||
|
// check if the hand-in items fulfill the requirement
|
||||||
|
bool items_met = true;
|
||||||
|
if (!handin_items.empty() && !r.items.empty()) {
|
||||||
|
std::vector<HandinEntry> before_handin_state = handin_items;
|
||||||
|
for (const auto &r_item : r.items) {
|
||||||
|
uint32 remaining_requirement = r_item.count;
|
||||||
|
bool fulfilled = false;
|
||||||
|
|
||||||
|
// Process the hand-in items using a standard for loop
|
||||||
|
for (size_t i = 0; i < handin_items.size() && remaining_requirement > 0; ++i) {
|
||||||
|
auto &h_item = handin_items[i];
|
||||||
|
|
||||||
|
// Check if the item IDs match (normalize if necessary)
|
||||||
|
bool id_match = (h_item.item_id == r_item.item_id);
|
||||||
|
|
||||||
|
if (id_match) {
|
||||||
|
uint32 used_count = std::min(remaining_requirement, h_item.count);
|
||||||
|
h_item.count -= used_count;
|
||||||
|
remaining_requirement -= used_count;
|
||||||
|
|
||||||
|
LogNpcHandinDetail(
|
||||||
|
"{} >>>> Using item [{}] ({}) count [{}] to fulfill [{}], remaining requirement [{}]",
|
||||||
|
log_handin_prefix,
|
||||||
|
h_item.item->GetItem()->Name,
|
||||||
|
h_item.item_id,
|
||||||
|
used_count,
|
||||||
|
r_item.item_id,
|
||||||
|
remaining_requirement
|
||||||
|
);
|
||||||
|
|
||||||
|
// If the item is fully consumed, mark it for removal
|
||||||
|
if (h_item.count == 0) {
|
||||||
|
items_to_remove.push_back(h_item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we cannot fulfill the requirement, mark as not met
|
||||||
|
if (remaining_requirement > 0) {
|
||||||
|
LogNpcHandinDetail(
|
||||||
|
"{} >>>> Failed to fulfill requirement for [{}], remaining [{}]",
|
||||||
|
log_handin_prefix,
|
||||||
|
r_item.item_id,
|
||||||
|
remaining_requirement
|
||||||
|
);
|
||||||
|
items_met = false;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
fulfilled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset the hand-in items to the state prior to processing the hand-in
|
||||||
|
// if we failed to fulfill the requirement
|
||||||
|
if (!items_met) {
|
||||||
|
handin_items = before_handin_state;
|
||||||
|
items_to_remove.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (h.items.empty() && r.items.empty()) { // no items required, money only
|
||||||
|
items_met = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
items_met = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
requirement_met = money_met && items_met;
|
||||||
|
|
||||||
|
// multi-quest
|
||||||
|
if (IsMultiQuestEnabled()) {
|
||||||
|
for (auto &h_item: h.items) {
|
||||||
|
for (const auto &r_item: r.items) {
|
||||||
|
if (h_item.item_id == r_item.item_id && h_item.count == r_item.count) {
|
||||||
|
h_item.is_multiquest_item = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// in-case we trigger CheckHand-in multiple times, only set these once
|
||||||
|
if (!m_handin_started) {
|
||||||
|
m_handin_started = true;
|
||||||
|
m_hand_in = h;
|
||||||
|
// save original items for logging
|
||||||
|
m_hand_in.original_items = m_hand_in.items;
|
||||||
|
m_hand_in.original_money = m_hand_in.money;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if npc is guildmaster
|
||||||
|
if (IsGuildmaster()) {
|
||||||
|
for (const auto &remove_item : items_to_remove) {
|
||||||
|
if (!remove_item.item) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsDisciplineTome(remove_item.item->GetItem())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsGuildmasterForClient(c)) {
|
||||||
|
c->TrainDiscipline(remove_item.item->GetID());
|
||||||
|
m_hand_in.items.erase(
|
||||||
|
std::remove_if(
|
||||||
|
m_hand_in.items.begin(),
|
||||||
|
m_hand_in.items.end(),
|
||||||
|
[&](const HandinEntry &i) {
|
||||||
|
bool removed = i.item == remove_item.item;
|
||||||
|
if (removed) {
|
||||||
|
LogNpcHandin(
|
||||||
|
"{} Hand-in success, removing discipline tome [{}] from hand-in bucket",
|
||||||
|
log_handin_prefix,
|
||||||
|
i.item_id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
),
|
||||||
|
m_hand_in.items.end()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Say("You are not a member of my guild. I will not train you!");
|
||||||
|
requirement_met = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// print current hand-in bucket
|
||||||
|
LogNpcHandin(
|
||||||
|
"{} > Before processing hand-in | requirement_met [{}] item_count [{}] platinum [{}] gold [{}] silver [{}] copper [{}]",
|
||||||
|
log_handin_prefix,
|
||||||
|
requirement_met,
|
||||||
|
h.items.size(),
|
||||||
|
h.money.platinum,
|
||||||
|
h.money.gold,
|
||||||
|
h.money.silver,
|
||||||
|
h.money.copper
|
||||||
|
);
|
||||||
|
|
||||||
|
LogNpcHandin(
|
||||||
|
"{} >> Handed Items | Item(s) ({}) platinum [{}] gold [{}] silver [{}] copper [{}]",
|
||||||
|
log_handin_prefix,
|
||||||
|
h.items.size(),
|
||||||
|
h.money.platinum,
|
||||||
|
h.money.gold,
|
||||||
|
h.money.silver,
|
||||||
|
h.money.copper
|
||||||
|
);
|
||||||
|
|
||||||
|
int item_count = 1;
|
||||||
|
for (const auto &i: h.items) {
|
||||||
|
LogNpcHandin(
|
||||||
|
"{} >>> item{} [{}] ({}) count [{}]",
|
||||||
|
log_handin_prefix,
|
||||||
|
item_count,
|
||||||
|
i.item->GetItem()->Name,
|
||||||
|
i.item_id,
|
||||||
|
i.count
|
||||||
|
);
|
||||||
|
item_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogNpcHandin(
|
||||||
|
"{} >> Required Items | Item(s) ({}) platinum [{}] gold [{}] silver [{}] copper [{}]",
|
||||||
|
log_handin_prefix,
|
||||||
|
r.items.size(),
|
||||||
|
r.money.platinum,
|
||||||
|
r.money.gold,
|
||||||
|
r.money.silver,
|
||||||
|
r.money.copper
|
||||||
|
);
|
||||||
|
|
||||||
|
item_count = 1;
|
||||||
|
for (const auto &i: r.items) {
|
||||||
|
auto item = database.GetItem(Strings::ToUnsignedInt(i.item_id));
|
||||||
|
|
||||||
|
LogNpcHandin(
|
||||||
|
"{} >>> item{} [{}] ({}) count [{}]",
|
||||||
|
log_handin_prefix,
|
||||||
|
item_count,
|
||||||
|
item ? item->Name : "Unknown",
|
||||||
|
i.item_id,
|
||||||
|
i.count
|
||||||
|
);
|
||||||
|
|
||||||
|
item_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requirement_met) {
|
||||||
|
std::vector<std::string> log_entries = {};
|
||||||
|
for (const auto &remove_item: items_to_remove) {
|
||||||
|
m_hand_in.items.erase(
|
||||||
|
std::remove_if(
|
||||||
|
m_hand_in.items.begin(),
|
||||||
|
m_hand_in.items.end(),
|
||||||
|
[&](const HandinEntry &i) {
|
||||||
|
bool removed = (remove_item.item == i.item);
|
||||||
|
if (removed) {
|
||||||
|
log_entries.emplace_back(
|
||||||
|
fmt::format(
|
||||||
|
"{} >>> Hand-in success | Removing from hand-in bucket | item [{}] ({}) count [{}]",
|
||||||
|
log_handin_prefix,
|
||||||
|
i.item->GetItem()->Name,
|
||||||
|
i.item_id,
|
||||||
|
i.count
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
),
|
||||||
|
m_hand_in.items.end()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// log successful hand-in items
|
||||||
|
if (!log_entries.empty()) {
|
||||||
|
for (const auto& log : log_entries) {
|
||||||
|
LogNpcHandin("{}", log);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// decrement successful hand-in money from current hand-in bucket
|
||||||
|
if (h.money.platinum > 0 || h.money.gold > 0 || h.money.silver > 0 || h.money.copper > 0) {
|
||||||
|
LogNpcHandin(
|
||||||
|
"{} Hand-in success, removing money p [{}] g [{}] s [{}] c [{}]",
|
||||||
|
log_handin_prefix,
|
||||||
|
h.money.platinum,
|
||||||
|
h.money.gold,
|
||||||
|
h.money.silver,
|
||||||
|
h.money.copper
|
||||||
|
);
|
||||||
|
m_hand_in.money.platinum -= h.money.platinum;
|
||||||
|
m_hand_in.money.gold -= h.money.gold;
|
||||||
|
m_hand_in.money.silver -= h.money.silver;
|
||||||
|
m_hand_in.money.copper -= h.money.copper;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogNpcHandin(
|
||||||
|
"{} > End of hand-in | requirement_met [{}] item_count [{}] platinum [{}] gold [{}] silver [{}] copper [{}]",
|
||||||
|
log_handin_prefix,
|
||||||
|
requirement_met,
|
||||||
|
m_hand_in.items.size(),
|
||||||
|
m_hand_in.money.platinum,
|
||||||
|
m_hand_in.money.gold,
|
||||||
|
m_hand_in.money.silver,
|
||||||
|
m_hand_in.money.copper
|
||||||
|
);
|
||||||
|
for (const auto &i: m_hand_in.items) {
|
||||||
|
LogNpcHandin(
|
||||||
|
"{} Hand-in success, item [{}] ({}) count [{}]",
|
||||||
|
log_handin_prefix,
|
||||||
|
i.item->GetItem()->Name,
|
||||||
|
i.item_id,
|
||||||
|
i.count
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return requirement_met;
|
||||||
|
}
|
||||||
|
|
||||||
|
NPC::Handin NPC::ReturnHandinItems(Client *c)
|
||||||
|
{
|
||||||
|
// player event
|
||||||
|
std::vector<PlayerEvent::HandinEntry> handin_items;
|
||||||
|
PlayerEvent::HandinMoney handin_money{};
|
||||||
|
std::vector<PlayerEvent::HandinEntry> return_items;
|
||||||
|
PlayerEvent::HandinMoney return_money{};
|
||||||
|
for (const auto& i : m_hand_in.original_items) {
|
||||||
|
if (i.item && i.item->GetItem()) {
|
||||||
|
handin_items.emplace_back(
|
||||||
|
PlayerEvent::HandinEntry{
|
||||||
|
.item_id = i.item->GetID(),
|
||||||
|
.item_name = i.item->GetItem()->Name,
|
||||||
|
.augment_ids = i.item->GetAugmentIDs(),
|
||||||
|
.augment_names = i.item->GetAugmentNames(),
|
||||||
|
.charges = std::max(static_cast<uint16>(i.item->GetCharges()), static_cast<uint16>(1))
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto returned = m_hand_in;
|
||||||
|
|
||||||
|
// check if any money was handed in
|
||||||
|
if (m_hand_in.original_money.platinum > 0 ||
|
||||||
|
m_hand_in.original_money.gold > 0 ||
|
||||||
|
m_hand_in.original_money.silver > 0 ||
|
||||||
|
m_hand_in.original_money.copper > 0
|
||||||
|
) {
|
||||||
|
handin_money.copper = m_hand_in.original_money.copper;
|
||||||
|
handin_money.silver = m_hand_in.original_money.silver;
|
||||||
|
handin_money.gold = m_hand_in.original_money.gold;
|
||||||
|
handin_money.platinum = m_hand_in.original_money.platinum;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if scripts have their own implementation of returning items instead of
|
||||||
|
// going through return_items, this guards against returning items twice (duplicate items)
|
||||||
|
bool external_returned_items = c->GetExternalHandinItemsReturned().size() > 0;
|
||||||
|
bool returned_items_already = false;
|
||||||
|
for (auto &handin_item: m_hand_in.items) {
|
||||||
|
for (auto &i: c->GetExternalHandinItemsReturned()) {
|
||||||
|
auto item = database.GetItem(i);
|
||||||
|
if (item && std::to_string(item->ID) == handin_item.item_id) {
|
||||||
|
LogNpcHandin(" -- External quest methods already returned item [{}] ({})", item->Name, item->ID);
|
||||||
|
returned_items_already = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (returned_items_already) {
|
||||||
|
LogNpcHandin("External quest methods returned items, not returning items to player via ReturnHandinItems");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool returned_handin = false;
|
||||||
|
m_hand_in.items.erase(
|
||||||
|
std::remove_if(
|
||||||
|
m_hand_in.items.begin(),
|
||||||
|
m_hand_in.items.end(),
|
||||||
|
[&](HandinEntry &i) {
|
||||||
|
if (i.item && i.item->GetItem() && !i.is_multiquest_item && !returned_items_already) {
|
||||||
|
return_items.emplace_back(
|
||||||
|
PlayerEvent::HandinEntry{
|
||||||
|
.item_id = i.item->GetID(),
|
||||||
|
.item_name = i.item->GetItem()->Name,
|
||||||
|
.augment_ids = i.item->GetAugmentIDs(),
|
||||||
|
.augment_names = i.item->GetAugmentNames(),
|
||||||
|
.charges = std::max(static_cast<uint16>(i.item->GetCharges()), static_cast<uint16>(1))
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// If the item is stackable and the new charges don't match the original count
|
||||||
|
// set the charges to the original count
|
||||||
|
if (i.item->IsStackable() && i.item->GetCharges() != i.count) {
|
||||||
|
i.item->SetCharges(i.count);
|
||||||
|
}
|
||||||
|
|
||||||
|
c->PushItemOnCursor(*i.item, true);
|
||||||
|
LogNpcHandin("Hand-in failed, returning item [{}]", i.item->GetItem()->Name);
|
||||||
|
|
||||||
|
returned_handin = true;
|
||||||
|
return true; // Mark this item for removal
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
),
|
||||||
|
m_hand_in.items.end()
|
||||||
|
);
|
||||||
|
|
||||||
|
// check if any money was handed in via external quest methods
|
||||||
|
auto em = c->GetExternalHandinMoneyReturned();
|
||||||
|
|
||||||
|
bool money_returned_via_external_quest_methods =
|
||||||
|
em.copper > 0 ||
|
||||||
|
em.silver > 0 ||
|
||||||
|
em.gold > 0 ||
|
||||||
|
em.platinum > 0;
|
||||||
|
|
||||||
|
// check if any money was handed in
|
||||||
|
bool money_handed = m_hand_in.money.platinum > 0 ||
|
||||||
|
m_hand_in.money.gold > 0 ||
|
||||||
|
m_hand_in.money.silver > 0 ||
|
||||||
|
m_hand_in.money.copper > 0;
|
||||||
|
if (money_handed && !money_returned_via_external_quest_methods) {
|
||||||
|
c->AddMoneyToPP(
|
||||||
|
m_hand_in.money.copper,
|
||||||
|
m_hand_in.money.silver,
|
||||||
|
m_hand_in.money.gold,
|
||||||
|
m_hand_in.money.platinum,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
returned_handin = true;
|
||||||
|
LogNpcHandin(
|
||||||
|
"Hand-in failed, returning money p [{}] g [{}] s [{}] c [{}]",
|
||||||
|
m_hand_in.money.platinum,
|
||||||
|
m_hand_in.money.gold,
|
||||||
|
m_hand_in.money.silver,
|
||||||
|
m_hand_in.money.copper
|
||||||
|
);
|
||||||
|
|
||||||
|
// player event
|
||||||
|
return_money.copper = m_hand_in.money.copper;
|
||||||
|
return_money.silver = m_hand_in.money.silver;
|
||||||
|
return_money.gold = m_hand_in.money.gold;
|
||||||
|
return_money.platinum = m_hand_in.money.platinum;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (money_returned_via_external_quest_methods) {
|
||||||
|
LogNpcHandin(
|
||||||
|
"Money handed in was returned via external quest methods, not returning money to player via ReturnHandinItems | handed-in p [{}] g [{}] s [{}] c [{}] returned-external p [{}] g [{}] s [{}] c [{}] source [{}]",
|
||||||
|
m_hand_in.money.platinum,
|
||||||
|
m_hand_in.money.gold,
|
||||||
|
m_hand_in.money.silver,
|
||||||
|
m_hand_in.money.copper,
|
||||||
|
em.platinum,
|
||||||
|
em.gold,
|
||||||
|
em.silver,
|
||||||
|
em.copper,
|
||||||
|
em.return_source
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_has_processed_handin_return = returned_handin;
|
||||||
|
|
||||||
|
if (returned_handin) {
|
||||||
|
Say(
|
||||||
|
fmt::format(
|
||||||
|
"I have no need for this {}, you can have it back.",
|
||||||
|
c->GetCleanName()
|
||||||
|
).c_str()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool handed_in_money = (
|
||||||
|
handin_money.platinum > 0 ||
|
||||||
|
handin_money.gold > 0 ||
|
||||||
|
handin_money.silver > 0 ||
|
||||||
|
handin_money.copper > 0
|
||||||
|
);
|
||||||
|
const bool event_has_data_to_record = !handin_items.empty() || handed_in_money;
|
||||||
|
|
||||||
|
if (player_event_logs.IsEventEnabled(PlayerEvent::NPC_HANDIN) && event_has_data_to_record) {
|
||||||
|
auto e = PlayerEvent::HandinEvent{
|
||||||
|
.npc_id = GetNPCTypeID(),
|
||||||
|
.npc_name = GetCleanName(),
|
||||||
|
.handin_items = handin_items,
|
||||||
|
.handin_money = handin_money,
|
||||||
|
.return_items = return_items,
|
||||||
|
.return_money = return_money,
|
||||||
|
.is_quest_handin = parse->HasQuestSub(GetNPCTypeID(), EVENT_TRADE)
|
||||||
|
};
|
||||||
|
|
||||||
|
RecordPlayerEventLogWithClient(c, PlayerEvent::NPC_HANDIN, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return returned;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NPC::ResetHandin()
|
||||||
|
{
|
||||||
|
m_has_processed_handin_return = false;
|
||||||
|
m_handin_started = false;
|
||||||
|
if (!IsMultiQuestEnabled()) {
|
||||||
|
for (auto &i: m_hand_in.original_items) {
|
||||||
|
safe_delete(i.item);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_hand_in = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
49
zone/npc.h
49
zone/npc.h
@ -559,6 +559,46 @@ public:
|
|||||||
bool CanPathTo(float x, float y, float z);
|
bool CanPathTo(float x, float y, float z);
|
||||||
|
|
||||||
void DoNpcToNpcAggroScan();
|
void DoNpcToNpcAggroScan();
|
||||||
|
|
||||||
|
// hand-ins
|
||||||
|
bool CanPetTakeItem(const EQ::ItemInstance *inst);
|
||||||
|
|
||||||
|
struct HandinEntry {
|
||||||
|
std::string item_id = "0";
|
||||||
|
uint32 count = 0;
|
||||||
|
EQ::ItemInstance *item = nullptr;
|
||||||
|
bool is_multiquest_item = false; // state
|
||||||
|
};
|
||||||
|
|
||||||
|
struct HandinMoney {
|
||||||
|
uint32 platinum = 0;
|
||||||
|
uint32 gold = 0;
|
||||||
|
uint32 silver = 0;
|
||||||
|
uint32 copper = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Handin {
|
||||||
|
std::vector<HandinEntry> original_items = {}; // this is what the player originally handed in, never modified
|
||||||
|
std::vector<HandinEntry> items = {}; // items can be removed from this set as successful handins are made
|
||||||
|
HandinMoney original_money = {}; // this is what the player originally handed in, never modified
|
||||||
|
HandinMoney money = {}; // money can be removed from this set as successful handins are made
|
||||||
|
};
|
||||||
|
|
||||||
|
// NPC Hand-in
|
||||||
|
bool IsMultiQuestEnabled() { return m_multiquest_enabled; }
|
||||||
|
void MultiQuestEnable() { m_multiquest_enabled = true; }
|
||||||
|
bool IsGuildmasterForClient(Client *c);
|
||||||
|
bool CheckHandin(
|
||||||
|
Client *c,
|
||||||
|
std::map<std::string, uint32> handin,
|
||||||
|
std::map<std::string, uint32> required,
|
||||||
|
std::vector<EQ::ItemInstance *> items
|
||||||
|
);
|
||||||
|
Handin ReturnHandinItems(Client *c);
|
||||||
|
void ResetHandin();
|
||||||
|
bool HasProcessedHandinReturn() { return m_has_processed_handin_return; }
|
||||||
|
bool HandinStarted() { return m_handin_started; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
void HandleRoambox();
|
void HandleRoambox();
|
||||||
@ -700,6 +740,15 @@ protected:
|
|||||||
bool raid_target;
|
bool raid_target;
|
||||||
bool ignore_despawn; //NPCs with this set to 1 will ignore the despawn value in spawngroup
|
bool ignore_despawn; //NPCs with this set to 1 will ignore the despawn value in spawngroup
|
||||||
|
|
||||||
|
// NPC Hand-in
|
||||||
|
bool m_multiquest_enabled = false;
|
||||||
|
bool m_handin_started = false;
|
||||||
|
bool m_has_processed_handin_return = false;
|
||||||
|
|
||||||
|
// this is the working handin data from the player
|
||||||
|
// items can be decremented from this as each successful
|
||||||
|
// check is ran in scripts, the remainder is what is returned
|
||||||
|
Handin m_hand_in = {};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
uint32 m_loottable_id;
|
uint32 m_loottable_id;
|
||||||
|
|||||||
@ -796,6 +796,85 @@ void Perl_NPC_DescribeSpecialAbilities(NPC* self, Client* c)
|
|||||||
self->DescribeSpecialAbilities(c);
|
self->DescribeSpecialAbilities(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Perl_NPC_IsMultiQuestEnabled(NPC* self)
|
||||||
|
{
|
||||||
|
return self->IsMultiQuestEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Perl_NPC_MultiQuestEnable(NPC* self)
|
||||||
|
{
|
||||||
|
self->MultiQuestEnable();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Perl_NPC_CheckHandin(
|
||||||
|
NPC* self,
|
||||||
|
Client* c,
|
||||||
|
perl::reference handin_ref,
|
||||||
|
perl::reference required_ref,
|
||||||
|
perl::array items_ref
|
||||||
|
)
|
||||||
|
{
|
||||||
|
perl::hash handin = handin_ref;
|
||||||
|
perl::hash required = required_ref;
|
||||||
|
|
||||||
|
std::map<std::string, uint32> handin_map;
|
||||||
|
std::map<std::string, uint32> required_map;
|
||||||
|
std::vector<EQ::ItemInstance *> items;
|
||||||
|
|
||||||
|
for (auto e: handin) {
|
||||||
|
if (!e.first) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Strings::EqualFold(e.first, "0")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogNpcHandinDetail("Handin key [{}] value [{}]", e.first, handin.at(e.first).c_str());
|
||||||
|
|
||||||
|
const uint32 count = static_cast<uint32>(handin.at(e.first));
|
||||||
|
handin_map[e.first] = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto e: required) {
|
||||||
|
if (!e.first) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Strings::EqualFold(e.first, "0")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
LogNpcHandinDetail("Required key [{}] value [{}]", e.first, required.at(e.first).c_str());
|
||||||
|
|
||||||
|
const uint32 count = static_cast<uint32>(required.at(e.first));
|
||||||
|
required_map[e.first] = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto e : items_ref) {
|
||||||
|
EQ::ItemInstance* i = static_cast<EQ::ItemInstance*>(e);
|
||||||
|
if (!i) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
items.emplace_back(i);
|
||||||
|
|
||||||
|
LogNpcHandinDetail(
|
||||||
|
"Item instance [{}] ({}) UUID ({}) added to handin list",
|
||||||
|
i->GetItem()->Name,
|
||||||
|
i->GetItem()->ID,
|
||||||
|
i->GetSerialNumber()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return self->CheckHandin(c, handin_map, required_map, items);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Perl_NPC_ReturnHandinItems(NPC *self, Client* c)
|
||||||
|
{
|
||||||
|
self->ReturnHandinItems(c);
|
||||||
|
}
|
||||||
|
|
||||||
void perl_register_npc()
|
void perl_register_npc()
|
||||||
{
|
{
|
||||||
perl::interpreter perl(PERL_GET_THX);
|
perl::interpreter perl(PERL_GET_THX);
|
||||||
@ -827,6 +906,7 @@ void perl_register_npc()
|
|||||||
package.add("CalculateNewWaypoint", &Perl_NPC_CalculateNewWaypoint);
|
package.add("CalculateNewWaypoint", &Perl_NPC_CalculateNewWaypoint);
|
||||||
package.add("ChangeLastName", &Perl_NPC_ChangeLastName);
|
package.add("ChangeLastName", &Perl_NPC_ChangeLastName);
|
||||||
package.add("CheckNPCFactionAlly", &Perl_NPC_CheckNPCFactionAlly);
|
package.add("CheckNPCFactionAlly", &Perl_NPC_CheckNPCFactionAlly);
|
||||||
|
package.add("CheckHandin", &Perl_NPC_CheckHandin);
|
||||||
package.add("ClearItemList", &Perl_NPC_ClearLootItems);
|
package.add("ClearItemList", &Perl_NPC_ClearLootItems);
|
||||||
package.add("ClearLastName", &Perl_NPC_ClearLastName);
|
package.add("ClearLastName", &Perl_NPC_ClearLastName);
|
||||||
package.add("CountItem", &Perl_NPC_CountItem);
|
package.add("CountItem", &Perl_NPC_CountItem);
|
||||||
@ -893,6 +973,7 @@ void perl_register_npc()
|
|||||||
package.add("IsLDoNLocked", &Perl_NPC_IsLDoNLocked);
|
package.add("IsLDoNLocked", &Perl_NPC_IsLDoNLocked);
|
||||||
package.add("IsLDoNTrapped", &Perl_NPC_IsLDoNTrapped);
|
package.add("IsLDoNTrapped", &Perl_NPC_IsLDoNTrapped);
|
||||||
package.add("IsLDoNTrapDetected", &Perl_NPC_IsLDoNTrapDetected);
|
package.add("IsLDoNTrapDetected", &Perl_NPC_IsLDoNTrapDetected);
|
||||||
|
package.add("IsMultiQuestEnabled", &Perl_NPC_IsMultiQuestEnabled);
|
||||||
package.add("IsOnHatelist", &Perl_NPC_IsOnHatelist);
|
package.add("IsOnHatelist", &Perl_NPC_IsOnHatelist);
|
||||||
package.add("IsRaidTarget", &Perl_NPC_IsRaidTarget);
|
package.add("IsRaidTarget", &Perl_NPC_IsRaidTarget);
|
||||||
package.add("IsRareSpawn", &Perl_NPC_IsRareSpawn);
|
package.add("IsRareSpawn", &Perl_NPC_IsRareSpawn);
|
||||||
@ -904,6 +985,7 @@ void perl_register_npc()
|
|||||||
package.add("MoveTo", (void(*)(NPC*, float, float, float))&Perl_NPC_MoveTo);
|
package.add("MoveTo", (void(*)(NPC*, float, float, float))&Perl_NPC_MoveTo);
|
||||||
package.add("MoveTo", (void(*)(NPC*, float, float, float, float))&Perl_NPC_MoveTo);
|
package.add("MoveTo", (void(*)(NPC*, float, float, float, float))&Perl_NPC_MoveTo);
|
||||||
package.add("MoveTo", (void(*)(NPC*, float, float, float, float, bool))&Perl_NPC_MoveTo);
|
package.add("MoveTo", (void(*)(NPC*, float, float, float, float, bool))&Perl_NPC_MoveTo);
|
||||||
|
package.add("MultiQuestEnable", &Perl_NPC_MultiQuestEnable);
|
||||||
package.add("NextGuardPosition", &Perl_NPC_NextGuardPosition);
|
package.add("NextGuardPosition", &Perl_NPC_NextGuardPosition);
|
||||||
package.add("PauseWandering", &Perl_NPC_PauseWandering);
|
package.add("PauseWandering", &Perl_NPC_PauseWandering);
|
||||||
package.add("PickPocket", &Perl_NPC_PickPocket);
|
package.add("PickPocket", &Perl_NPC_PickPocket);
|
||||||
@ -920,6 +1002,7 @@ void perl_register_npc()
|
|||||||
package.add("RemoveMeleeProc", &Perl_NPC_RemoveMeleeProc);
|
package.add("RemoveMeleeProc", &Perl_NPC_RemoveMeleeProc);
|
||||||
package.add("RemoveRangedProc", &Perl_NPC_RemoveRangedProc);
|
package.add("RemoveRangedProc", &Perl_NPC_RemoveRangedProc);
|
||||||
package.add("ResumeWandering", &Perl_NPC_ResumeWandering);
|
package.add("ResumeWandering", &Perl_NPC_ResumeWandering);
|
||||||
|
package.add("ReturnHandinItems", &Perl_NPC_ReturnHandinItems);
|
||||||
package.add("SaveGuardSpot", (void(*)(NPC*))&Perl_NPC_SaveGuardSpot);
|
package.add("SaveGuardSpot", (void(*)(NPC*))&Perl_NPC_SaveGuardSpot);
|
||||||
package.add("SaveGuardSpot", (void(*)(NPC*, bool))&Perl_NPC_SaveGuardSpot);
|
package.add("SaveGuardSpot", (void(*)(NPC*, bool))&Perl_NPC_SaveGuardSpot);
|
||||||
package.add("SaveGuardSpot", (void(*)(NPC*, float, float, float, float))&Perl_NPC_SaveGuardSpot);
|
package.add("SaveGuardSpot", (void(*)(NPC*, float, float, float, float))&Perl_NPC_SaveGuardSpot);
|
||||||
|
|||||||
@ -1223,50 +1223,7 @@ bool QuestManager::isdisctome(uint32 item_id) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!item->IsClassCommon() || item->ItemType != EQ::item::ItemTypeSpell) {
|
return IsDisciplineTome(item);
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Need a way to determine the difference between a spell and a tome
|
|
||||||
//so they cant turn in a spell and get it as a discipline
|
|
||||||
//this is kinda a hack:
|
|
||||||
|
|
||||||
const std::string item_name = item->Name;
|
|
||||||
|
|
||||||
if (
|
|
||||||
!Strings::BeginsWith(item_name, "Tome of ") &&
|
|
||||||
!Strings::BeginsWith(item_name, "Skill: ")
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//we know for sure none of the int casters get disciplines
|
|
||||||
uint32 class_bit = 0;
|
|
||||||
class_bit |= 1 << (Class::Wizard - 1);
|
|
||||||
class_bit |= 1 << (Class::Enchanter - 1);
|
|
||||||
class_bit |= 1 << (Class::Magician - 1);
|
|
||||||
class_bit |= 1 << (Class::Necromancer - 1);
|
|
||||||
if (item->Classes & class_bit) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto& spell_id = static_cast<uint32>(item->Scroll.Effect);
|
|
||||||
if (!IsValidSpell(spell_id)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
//we know for sure none of the int casters get disciplines
|
|
||||||
const auto& spell = spells[spell_id];
|
|
||||||
if(
|
|
||||||
spell.classes[Class::Wizard - 1] != 255 &&
|
|
||||||
spell.classes[Class::Enchanter - 1] != 255 &&
|
|
||||||
spell.classes[Class::Magician - 1] != 255 &&
|
|
||||||
spell.classes[Class::Necromancer - 1] != 255
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string QuestManager::getracename(uint16 race_id) {
|
std::string QuestManager::getracename(uint16 race_id) {
|
||||||
|
|||||||
249
zone/trading.cpp
249
zone/trading.cpp
@ -320,7 +320,11 @@ void Client::ResetTrade() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, std::list<void*>* event_details) {
|
void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, std::list<void*>* event_details) {
|
||||||
if(tradingWith && tradingWith->IsClient()) {
|
if (!tradingWith) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tradingWith->IsClient()) {
|
||||||
Client * other = tradingWith->CastToClient();
|
Client * other = tradingWith->CastToClient();
|
||||||
PlayerLogTrade_Struct * qs_audit = nullptr;
|
PlayerLogTrade_Struct * qs_audit = nullptr;
|
||||||
bool qs_log = false;
|
bool qs_log = false;
|
||||||
@ -366,7 +370,7 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st
|
|||||||
inst->GetItem()->NoDrop != 0 ||
|
inst->GetItem()->NoDrop != 0 ||
|
||||||
CanTradeFVNoDropItem() ||
|
CanTradeFVNoDropItem() ||
|
||||||
other == this
|
other == this
|
||||||
) {
|
) {
|
||||||
int16 free_slot = other->GetInv().FindFreeSlotForTradeItem(inst);
|
int16 free_slot = other->GetInv().FindFreeSlotForTradeItem(inst);
|
||||||
|
|
||||||
if (free_slot != INVALID_INDEX) {
|
if (free_slot != INVALID_INDEX) {
|
||||||
@ -481,8 +485,12 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st
|
|||||||
LogTrading("Transferring partial stack [{}] ([{}]) in slot [{}] to [{}]", inst->GetItem()->Name, inst->GetItem()->ID, trade_slot, other->GetName());
|
LogTrading("Transferring partial stack [{}] ([{}]) in slot [{}] to [{}]", inst->GetItem()->Name, inst->GetItem()->ID, trade_slot, other->GetName());
|
||||||
|
|
||||||
if (other->PutItemInInventory(partial_slot, *partial_inst, true)) {
|
if (other->PutItemInInventory(partial_slot, *partial_inst, true)) {
|
||||||
LogTrading("Partial stack [{}] ([{}]) successfully transferred, deleting [{}] charges from trade slot",
|
LogTrading(
|
||||||
inst->GetItem()->Name, inst->GetItem()->ID, (old_charges - inst->GetCharges()));
|
"Partial stack [{}] ([{}]) successfully transferred, deleting [{}] charges from trade slot",
|
||||||
|
inst->GetItem()->Name,
|
||||||
|
inst->GetItem()->ID,
|
||||||
|
(old_charges - inst->GetCharges())
|
||||||
|
);
|
||||||
inst->TransferOwnership(database, other->CharacterID());
|
inst->TransferOwnership(database, other->CharacterID());
|
||||||
if (qs_log) {
|
if (qs_log) {
|
||||||
auto detail = new PlayerLogTradeItemsEntry_Struct;
|
auto detail = new PlayerLogTradeItemsEntry_Struct;
|
||||||
@ -509,7 +517,7 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
LogTrading("Transfer of partial stack [{}] ([{}]) to [{}] failed, returning [{}] charges to trade slot",
|
LogTrading("Transfer of partial stack [{}] ([{}]) to [{}] failed, returning [{}] charges to trade slot",
|
||||||
inst->GetItem()->Name, inst->GetItem()->ID, other->GetName(), (old_charges - inst->GetCharges()));
|
inst->GetItem()->Name, inst->GetItem()->ID, other->GetName(), (old_charges - inst->GetCharges()));
|
||||||
|
|
||||||
inst->SetCharges(old_charges);
|
inst->SetCharges(old_charges);
|
||||||
partial_inst->SetCharges(partial_charges);
|
partial_inst->SetCharges(partial_charges);
|
||||||
@ -666,8 +674,7 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st
|
|||||||
//Do not reset the trade here, done by the caller.
|
//Do not reset the trade here, done by the caller.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if(tradingWith && tradingWith->IsNPC()) {
|
else if(tradingWith->IsNPC()) {
|
||||||
NPCHandinEventLog(trade, tradingWith->CastToNPC());
|
|
||||||
|
|
||||||
QSPlayerLogHandin_Struct* qs_audit = nullptr;
|
QSPlayerLogHandin_Struct* qs_audit = nullptr;
|
||||||
bool qs_log = false;
|
bool qs_log = false;
|
||||||
@ -744,7 +751,6 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st
|
|||||||
|
|
||||||
bool quest_npc = false;
|
bool quest_npc = false;
|
||||||
if (parse->HasQuestSub(tradingWith->GetNPCTypeID(), EVENT_TRADE)) {
|
if (parse->HasQuestSub(tradingWith->GetNPCTypeID(), EVENT_TRADE)) {
|
||||||
// This is a quest NPC
|
|
||||||
quest_npc = true;
|
quest_npc = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -760,34 +766,14 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st
|
|||||||
|
|
||||||
if (RuleB(TaskSystem, EnableTaskSystem)) {
|
if (RuleB(TaskSystem, EnableTaskSystem)) {
|
||||||
if (UpdateTasksOnDeliver(items, *trade, tradingWith->CastToNPC())) {
|
if (UpdateTasksOnDeliver(items, *trade, tradingWith->CastToNPC())) {
|
||||||
if (!tradingWith->IsMoving())
|
if (!tradingWith->IsMoving()) {
|
||||||
tradingWith->FaceTarget(this);
|
tradingWith->FaceTarget(this);
|
||||||
|
|
||||||
EVENT_ITEM_ScriptStopReturn();
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Regardless of quest or non-quest NPC - No in combat trade completion
|
|
||||||
// is allowed.
|
|
||||||
if (tradingWith->CheckAggro(this))
|
|
||||||
{
|
|
||||||
for (EQ::ItemInstance* inst : items) {
|
|
||||||
if (!inst || !inst->GetItem()) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tradingWith->SayString(TRADE_BACK, GetCleanName());
|
|
||||||
PushItemOnCursor(*inst, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
items.clear();
|
|
||||||
}
|
}
|
||||||
// Only enforce trade rules if the NPC doesn't have an EVENT_TRADE
|
|
||||||
// subroutine. That overrides all.
|
if (!quest_npc) {
|
||||||
else if (!quest_npc)
|
for (auto &inst: items) {
|
||||||
{
|
|
||||||
for (EQ::ItemInstance* inst : items) {
|
|
||||||
if (!inst || !inst->GetItem()) {
|
if (!inst || !inst->GetItem()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -801,128 +787,121 @@ void Client::FinishTrade(Mob* tradingWith, bool finalizer, void* event_entry, st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const EQ::ItemData* item = inst->GetItem();
|
auto with = tradingWith->CastToNPC();
|
||||||
const bool is_pet = _CLIENTPET(tradingWith) && tradingWith->GetPetType()<=petOther;
|
const EQ::ItemData *item = inst->GetItem();
|
||||||
const bool is_quest_npc = tradingWith->CastToNPC()->IsQuestNPC();
|
|
||||||
const bool restrict_quest_items_to_quest_npc = RuleB(NPC, ReturnQuestItemsFromNonQuestNPCs);
|
if (with->IsPetOwnerClient() && with->CanPetTakeItem(inst)) {
|
||||||
const bool pets_can_take_quest_items = RuleB(Pets, CanTakeQuestItems);
|
|
||||||
const bool is_pet_and_can_have_nodrop_items = (RuleB(Pets, CanTakeNoDrop) && is_pet);
|
|
||||||
const bool is_pet_and_can_have_quest_items = (pets_can_take_quest_items && is_pet);
|
|
||||||
// if it was not a NO DROP or Attuned item (or if a GM is trading), let the NPC have it
|
|
||||||
if (GetGM() ||
|
|
||||||
(!restrict_quest_items_to_quest_npc || (is_quest_npc && item->IsQuestItem()) || !item->IsQuestItem()) && // If rule is enabled, return any quest items given to non-quest NPCs
|
|
||||||
(((item->NoDrop != 0 && !inst->IsAttuned()) || is_pet_and_can_have_nodrop_items) &&
|
|
||||||
((!item->IsQuestItem() || is_pet_and_can_have_quest_items || !is_pet)))) {
|
|
||||||
// pets need to look inside bags and try to equip items found there
|
// pets need to look inside bags and try to equip items found there
|
||||||
if (item->IsClassBag() && item->BagSlots > 0) {
|
if (item->IsClassBag() && item->BagSlots > 0) {
|
||||||
for (int16 bslot = EQ::invbag::SLOT_BEGIN; bslot < item->BagSlots; bslot++) {
|
// if an item inside the bag can't be given to the pet, keep the bag
|
||||||
|
bool keep_bag = false;
|
||||||
|
int item_count = 0;
|
||||||
|
for (int16 bslot = EQ::invbag::SLOT_BEGIN; bslot < item->BagSlots; bslot++) {
|
||||||
const EQ::ItemInstance *baginst = inst->GetItem(bslot);
|
const EQ::ItemInstance *baginst = inst->GetItem(bslot);
|
||||||
if (baginst) {
|
if (baginst && baginst->GetItem() && with->CanPetTakeItem(baginst)) {
|
||||||
const EQ::ItemData *bagitem = baginst->GetItem();
|
// add item to pet's inventory
|
||||||
if (bagitem && (GetGM() ||
|
auto lde = LootdropEntriesRepository::NewNpcEntity();
|
||||||
(!restrict_quest_items_to_quest_npc ||
|
lde.equip_item = 1;
|
||||||
(is_quest_npc && bagitem->IsQuestItem()) || !bagitem->IsQuestItem()) &&
|
lde.item_charges = static_cast<int8>(baginst->GetCharges());
|
||||||
// If rule is enabled, return any quest items given to non-quest NPCs (inside bags)
|
with->AddLootDrop(baginst->GetItem(), lde, true);
|
||||||
(bagitem->NoDrop != 0 && !baginst->IsAttuned()) &&
|
inst->DeleteItem(bslot);
|
||||||
((is_pet && (!bagitem->IsQuestItem() || pets_can_take_quest_items) ||
|
item_count++;
|
||||||
!is_pet)))) {
|
}
|
||||||
|
else {
|
||||||
if (GetGM()) {
|
keep_bag = true;
|
||||||
const std::string& item_link = database.CreateItemLink(bagitem->ID);
|
|
||||||
Message(
|
|
||||||
Chat::White,
|
|
||||||
fmt::format(
|
|
||||||
"Your GM flag allows you to give {} to {}.",
|
|
||||||
item_link,
|
|
||||||
GetTargetDescription(tradingWith)
|
|
||||||
).c_str()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto lde = LootdropEntriesRepository::NewNpcEntity();
|
|
||||||
lde.equip_item = 1;
|
|
||||||
lde.item_charges = static_cast<int8>(baginst->GetCharges());
|
|
||||||
|
|
||||||
tradingWith->CastToNPC()->AddLootDrop(
|
|
||||||
bagitem,
|
|
||||||
lde,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
// Return quest items being traded to non-quest NPC when the rule is true
|
|
||||||
} else if (restrict_quest_items_to_quest_npc && (!is_quest_npc && bagitem->IsQuestItem())) {
|
|
||||||
tradingWith->SayString(TRADE_BACK, GetCleanName());
|
|
||||||
PushItemOnCursor(*baginst, true);
|
|
||||||
Message(Chat::Red, "You can only trade quest items to quest NPCs.");
|
|
||||||
// Return quest items being traded to player pet when not allowed
|
|
||||||
} else if (is_pet && bagitem->IsQuestItem() && !pets_can_take_quest_items) {
|
|
||||||
tradingWith->SayString(TRADE_BACK, GetCleanName());
|
|
||||||
PushItemOnCursor(*baginst, true);
|
|
||||||
Message(Chat::Red, "You cannot trade quest items with your pet.");
|
|
||||||
} else if (RuleB(NPC, ReturnNonQuestNoDropItems)) {
|
|
||||||
tradingWith->SayString(TRADE_BACK, GetCleanName());
|
|
||||||
PushItemOnCursor(*baginst, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
|
// add item to pet's inventory
|
||||||
|
if (!keep_bag || item_count == 0) {
|
||||||
|
auto lde = LootdropEntriesRepository::NewNpcEntity();
|
||||||
|
lde.equip_item = 1;
|
||||||
|
lde.item_charges = static_cast<int8>(inst->GetCharges());
|
||||||
|
with->AddLootDrop(item, lde, true);
|
||||||
|
inst = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// add item to pet's inventory
|
||||||
auto lde = LootdropEntriesRepository::NewNpcEntity();
|
auto lde = LootdropEntriesRepository::NewNpcEntity();
|
||||||
lde.equip_item = 1;
|
lde.equip_item = 1;
|
||||||
lde.item_charges = static_cast<int8>(inst->GetCharges());
|
lde.item_charges = static_cast<int8>(inst->GetCharges());
|
||||||
|
with->AddLootDrop(item, lde, true);
|
||||||
tradingWith->CastToNPC()->AddLootDrop(
|
inst = nullptr;
|
||||||
item,
|
|
||||||
lde,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Return quest items being traded to non-quest NPC when the rule is true
|
|
||||||
else if (restrict_quest_items_to_quest_npc && (!is_quest_npc && item->IsQuestItem())) {
|
|
||||||
tradingWith->SayString(TRADE_BACK, GetCleanName());
|
|
||||||
PushItemOnCursor(*inst, true);
|
|
||||||
Message(Chat::Red, "You can only trade quest items to quest NPCs.");
|
|
||||||
}
|
|
||||||
// Return quest items being traded to player pet when not allowed
|
|
||||||
else if (is_pet && item->IsQuestItem()) {
|
|
||||||
tradingWith->SayString(TRADE_BACK, GetCleanName());
|
|
||||||
PushItemOnCursor(*inst, true);
|
|
||||||
Message(Chat::Red, "You cannot trade quest items with your pet.");
|
|
||||||
}
|
|
||||||
// Return NO DROP and Attuned items being handed into a non-quest NPC if the rule is true
|
|
||||||
else if (RuleB(NPC, ReturnNonQuestNoDropItems)) {
|
|
||||||
tradingWith->SayString(TRADE_BACK, GetCleanName());
|
|
||||||
PushItemOnCursor(*inst, true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
char temp1[100] = { 0 };
|
std::string currencies[] = {"copper", "silver", "gold", "platinum"};
|
||||||
char temp2[100] = { 0 };
|
int32 amounts[] = {trade->cp, trade->sp, trade->gp, trade->pp};
|
||||||
snprintf(temp1, 100, "copper.%d", tradingWith->GetNPCTypeID());
|
|
||||||
snprintf(temp2, 100, "%u", trade->cp);
|
|
||||||
parse->AddVar(temp1, temp2);
|
|
||||||
snprintf(temp1, 100, "silver.%d", tradingWith->GetNPCTypeID());
|
|
||||||
snprintf(temp2, 100, "%u", trade->sp);
|
|
||||||
parse->AddVar(temp1, temp2);
|
|
||||||
snprintf(temp1, 100, "gold.%d", tradingWith->GetNPCTypeID());
|
|
||||||
snprintf(temp2, 100, "%u", trade->gp);
|
|
||||||
parse->AddVar(temp1, temp2);
|
|
||||||
snprintf(temp1, 100, "platinum.%d", tradingWith->GetNPCTypeID());
|
|
||||||
snprintf(temp2, 100, "%u", trade->pp);
|
|
||||||
parse->AddVar(temp1, temp2);
|
|
||||||
|
|
||||||
if(tradingWith->GetAppearance() != eaDead) {
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
parse->AddVar(
|
||||||
|
fmt::format("{}.{}", currencies[i], tradingWith->GetNPCTypeID()),
|
||||||
|
fmt::format("{}", amounts[i])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tradingWith->GetAppearance() != eaDead) {
|
||||||
tradingWith->FaceTarget(this);
|
tradingWith->FaceTarget(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parse->HasQuestSub(tradingWith->GetNPCTypeID(), EVENT_TRADE)) {
|
// we cast to any to pass through the quest event system
|
||||||
std::vector<std::any> item_list(items.begin(), items.end());
|
std::vector<std::any> item_list(items.begin(), items.end());
|
||||||
parse->EventNPC(EVENT_TRADE, tradingWith->CastToNPC(), this, "", 0, &item_list);
|
for (EQ::ItemInstance *inst: items) {
|
||||||
|
if (!inst || !inst->GetItem()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
item_list.emplace_back(inst);
|
||||||
}
|
}
|
||||||
|
|
||||||
for(int i = 0; i < 4; ++i) {
|
m_external_handin_money_returned = {};
|
||||||
if(insts[i]) {
|
m_external_handin_items_returned = {};
|
||||||
safe_delete(insts[i]);
|
bool has_aggro = tradingWith->CheckAggro(this);
|
||||||
|
if (parse->HasQuestSub(tradingWith->GetNPCTypeID(), EVENT_TRADE) && !has_aggro) {
|
||||||
|
parse->EventNPC(EVENT_TRADE, tradingWith->CastToNPC(), this, "", 0, &item_list);
|
||||||
|
LogNpcHandinDetail("EVENT_TRADE triggered for NPC [{}]", tradingWith->GetNPCTypeID());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto handin_npc = tradingWith->CastToNPC();
|
||||||
|
|
||||||
|
// this is a catch-all return for items that weren't consumed by the EVENT_TRADE subroutine
|
||||||
|
// it's possible we have a quest NPC that doesn't have an EVENT_TRADE subroutine
|
||||||
|
// we can't double fire the ReturnHandinItems() event, so we need to check if it's already been processed from EVENT_TRADE
|
||||||
|
if (!handin_npc->HasProcessedHandinReturn()) {
|
||||||
|
if (!handin_npc->HandinStarted()) {
|
||||||
|
LogNpcHandinDetail("EVENT_TRADE did not process handin, calling ReturnHandinItems() for NPC [{}]", tradingWith->GetNPCTypeID());
|
||||||
|
std::map<std::string, uint32> handin = {
|
||||||
|
{"copper", trade->cp},
|
||||||
|
{"silver", trade->sp},
|
||||||
|
{"gold", trade->gp},
|
||||||
|
{"platinum", trade->pp}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (EQ::ItemInstance *inst: items) {
|
||||||
|
if (!inst || !inst->GetItem()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string item_id = fmt::format("{}", inst->GetItem()->ID);
|
||||||
|
handin[item_id] += inst->GetCharges();
|
||||||
|
}
|
||||||
|
|
||||||
|
handin_npc->CheckHandin(this, handin, {}, items);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (RuleB(Items, AlwaysReturnHandins)) {
|
||||||
|
handin_npc->ReturnHandinItems(this);
|
||||||
|
LogNpcHandin("ReturnHandinItems called for NPC [{}]", handin_npc->GetNPCTypeID());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handin_npc->ResetHandin();
|
||||||
|
|
||||||
|
for (auto &inst: insts) {
|
||||||
|
if (inst) {
|
||||||
|
safe_delete(inst);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,6 +12,11 @@ bool ZoneCLI::RanSidecarCommand(int argc, char **argv)
|
|||||||
return argc > 1 && (strstr(argv[1], "sidecar:") != nullptr);
|
return argc > 1 && (strstr(argv[1], "sidecar:") != nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ZoneCLI::RanTestCommand(int argc, char **argv)
|
||||||
|
{
|
||||||
|
return argc > 1 && (strstr(argv[1], "tests:") != nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
void ZoneCLI::CommandHandler(int argc, char **argv)
|
void ZoneCLI::CommandHandler(int argc, char **argv)
|
||||||
{
|
{
|
||||||
if (argc == 1) { return; }
|
if (argc == 1) { return; }
|
||||||
@ -25,8 +30,10 @@ void ZoneCLI::CommandHandler(int argc, char **argv)
|
|||||||
|
|
||||||
// Register commands
|
// Register commands
|
||||||
function_map["sidecar:serve-http"] = &ZoneCLI::SidecarServeHttp;
|
function_map["sidecar:serve-http"] = &ZoneCLI::SidecarServeHttp;
|
||||||
|
function_map["tests:npc-handins"] = &ZoneCLI::NpcHandins;
|
||||||
|
|
||||||
EQEmuCommand::HandleMenu(function_map, cmd, argc, argv);
|
EQEmuCommand::HandleMenu(function_map, cmd, argc, argv);
|
||||||
}
|
}
|
||||||
|
|
||||||
#include "cli/sidecar_serve_http.cpp"
|
#include "cli/sidecar_serve_http.cpp"
|
||||||
|
#include "cli/npc_handins.cpp"
|
||||||
|
|||||||
@ -9,6 +9,8 @@ public:
|
|||||||
static void SidecarServeHttp(int argc, char **argv, argh::parser &cmd, std::string &description);
|
static void SidecarServeHttp(int argc, char **argv, argh::parser &cmd, std::string &description);
|
||||||
static bool RanConsoleCommand(int argc, char **argv);
|
static bool RanConsoleCommand(int argc, char **argv);
|
||||||
static bool RanSidecarCommand(int argc, char **argv);
|
static bool RanSidecarCommand(int argc, char **argv);
|
||||||
|
static bool RanTestCommand(int argc, char **argv);
|
||||||
|
static void NpcHandins(int argc, char **argv, argh::parser &cmd, std::string &description);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1905,6 +1905,7 @@ const NPCType *ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load
|
|||||||
t->heroic_strikethrough = n.heroic_strikethrough;
|
t->heroic_strikethrough = n.heroic_strikethrough;
|
||||||
t->faction_amount = n.faction_amount;
|
t->faction_amount = n.faction_amount;
|
||||||
t->keeps_sold_items = n.keeps_sold_items;
|
t->keeps_sold_items = n.keeps_sold_items;
|
||||||
|
t->multiquest_enabled = n.multiquest_enabled != 0;
|
||||||
|
|
||||||
// If NPC with duplicate NPC id already in table,
|
// If NPC with duplicate NPC id already in table,
|
||||||
// free item we attempted to add.
|
// free item we attempted to add.
|
||||||
|
|||||||
@ -156,6 +156,7 @@ struct NPCType
|
|||||||
bool keeps_sold_items;
|
bool keeps_sold_items;
|
||||||
bool is_parcel_merchant;
|
bool is_parcel_merchant;
|
||||||
uint8 greed;
|
uint8 greed;
|
||||||
|
bool multiquest_enabled;
|
||||||
};
|
};
|
||||||
|
|
||||||
#pragma pack()
|
#pragma pack()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user