diff --git a/changelog.txt b/changelog.txt index a7e75de62..a700ded31 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,15 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 02/10/2018 == +mackal: Add Global Loot system + +This will allow us to implement global loot similarly to how it works on live +This system reuses our current loottable tables which the global_loot table references. +The limits for the rules to govern if a table should be rolled are min level, max level, rare, +raid, race, class, bodytype, and zone + +race, class, bodytype, and zone are a pipe | separated list of IDs. + == 01/31/2018 == Uleat: Re-work of Bot::AI_Process(). Overall behavior is much improved. - Removed a 'ton' of unneeded packet updates diff --git a/common/version.h b/common/version.h index a79766ec3..928592a4a 100644 --- a/common/version.h +++ b/common/version.h @@ -30,7 +30,7 @@ Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt */ -#define CURRENT_BINARY_DATABASE_VERSION 9118 +#define CURRENT_BINARY_DATABASE_VERSION 9119 #ifdef BOTS #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9018 #else diff --git a/utils/sql/db_update_manifest.txt b/utils/sql/db_update_manifest.txt index 8463d979f..188db2162 100644 --- a/utils/sql/db_update_manifest.txt +++ b/utils/sql/db_update_manifest.txt @@ -372,6 +372,7 @@ 9116|2017_12_16_GroundSpawn_Respawn_Timer.sql|SHOW COLUMNS FROM `ground_spawns` WHERE Field = 'respawn_timer' AND Type = 'int(11) unsigned'|empty| 9117|2018_02_01_NPC_Spells_Min_Max_HP.sql|SHOW COLUMNS FROM `npc_spells_entries` LIKE 'min_hp'|empty| 9118|2018_02_04_Charm_Stats.sql|SHOW COLUMNS FROM `npc_types` LIKE 'charm_ac'|empty| +9119|2018_02_10_GlobalLoot.sql|SHOW TABLES LIKE 'global_loot'|empty| # Upgrade conditions: # This won't be needed after this system is implemented, but it is used database that are not diff --git a/utils/sql/git/required/2018_02_10_GlobalLoot.sql b/utils/sql/git/required/2018_02_10_GlobalLoot.sql new file mode 100644 index 000000000..573ab8f71 --- /dev/null +++ b/utils/sql/git/required/2018_02_10_GlobalLoot.sql @@ -0,0 +1,19 @@ +ALTER TABLE `npc_types` ADD `skip_global_loot` TINYINT DEFAULT '0'; +ALTER TABLE `npc_types` ADD `rare_spawn` TINYINT DEFAULT '0'; + +CREATE TABLE global_loot ( + id INT NOT NULL AUTO_INCREMENT, + description varchar(255), + loottable_id INT NOT NULL, + enabled TINYINT NOT NULL DEFAULT 1, + min_level INT NOT NULL DEFAULT 0, + max_level INT NOT NULL DEFAULT 0, + rare TINYINT NULL, + raid TINYINT NULL, + race MEDIUMTEXT NULL, + class MEDIUMTEXT NULL, + bodytype MEDIUMTEXT NULL, + zone MEDIUMTEXT NULL, + PRIMARY KEY (id) +); + diff --git a/zone/CMakeLists.txt b/zone/CMakeLists.txt index 0171c8eb9..49644cfcf 100644 --- a/zone/CMakeLists.txt +++ b/zone/CMakeLists.txt @@ -68,6 +68,7 @@ SET(zone_sources exp.cpp fearpath.cpp forage.cpp + global_loot_manager.cpp groups.cpp guild.cpp guild_mgr.cpp @@ -158,6 +159,7 @@ SET(zone_headers errmsg.h event_codes.h forage.h + global_loot_manager.h groups.h guild_mgr.h hate_list.h diff --git a/zone/command.cpp b/zone/command.cpp index 5b45f3a69..7a4366a4f 100644 --- a/zone/command.cpp +++ b/zone/command.cpp @@ -358,9 +358,11 @@ int command_init(void) command_add("showbonusstats", "[item|spell|all] Shows bonus stats for target from items or spells. Shows both by default.", 50, command_showbonusstats) || command_add("showbuffs", "- List buffs active on your target or you if no target", 50, command_showbuffs) || command_add("shownumhits", "Shows buffs numhits for yourself.", 0, command_shownumhits) || + command_add("shownpcgloballoot", "Show GlobalLoot entires on this npc", 50, command_shownpcgloballoot) || command_add("showskills", "- Show the values of your or your player target's skills", 50, command_showskills) || command_add("showspellslist", "Shows spell list of targeted NPC", 100, command_showspellslist) || command_add("showstats", "- Show details about you or your target", 50, command_showstats) || + command_add("showzonegloballoot", "Show GlobalLoot entires on this zone", 50, command_showzonegloballoot) || command_add("shutdown", "- Shut this zone process down", 150, command_shutdown) || command_add("size", "[size] - Change size of you or your target", 50, command_size) || command_add("spawn", "[name] [race] [level] [material] [hp] [gender] [class] [priweapon] [secweapon] [merchantid] - Spawn an NPC", 10, command_spawn) || @@ -2458,7 +2460,9 @@ void command_npctypespawn(Client *c, const Seperator *sep) if (npc && sep->IsNumber(2)) npc->SetNPCFactionID(atoi(sep->arg[2])); - npc->AddLootTable(); + npc->AddLootTable(); + if (npc->DropsGlobalLoot()) + npc->CheckGlobalLootTables(); entity_list.AddNPC(npc); } else @@ -3857,6 +3861,12 @@ void command_showstats(Client *c, const Seperator *sep) c->ShowStats(c); } +void command_showzonegloballoot(Client *c, const Seperator *sep) +{ + c->Message(0, "GlobalLoot for %s (%d:%d)", zone->GetShortName(), zone->GetZoneID(), zone->GetInstanceVersion()); + zone->ShowZoneGlobalLoot(c); +} + void command_mystats(Client *c, const Seperator *sep) { if (c->GetTarget() && c->GetPet()) { @@ -10441,6 +10451,20 @@ void command_shownumhits(Client *c, const Seperator *sep) return; } +void command_shownpcgloballoot(Client *c, const Seperator *sep) +{ + auto tar = c->GetTarget(); + + if (!tar || !tar->IsNPC()) { + c->Message(0, "You must target an NPC to use this command."); + return; + } + + auto npc = tar->CastToNPC(); + c->Message(0, "GlobalLoot for %s (%d)", npc->GetName(), npc->GetNPCTypeID()); + zone->ShowNPCGlobalLoot(c, npc); +} + void command_tune(Client *c, const Seperator *sep) { //Work in progress - Kayen diff --git a/zone/command.h b/zone/command.h index 8f6257949..b8d688a54 100644 --- a/zone/command.h +++ b/zone/command.h @@ -267,10 +267,12 @@ void command_setxp(Client *c, const Seperator *sep); void command_showbonusstats(Client *c, const Seperator *sep); void command_showbuffs(Client *c, const Seperator *sep); void command_shownumhits(Client *c, const Seperator *sep); +void command_shownpcgloballoot(Client *c, const Seperator *sep); void command_showpetspell(Client *c, const Seperator *sep); void command_showskills(Client *c, const Seperator *sep); void command_showspellslist(Client *c, const Seperator *sep); void command_showstats(Client *c, const Seperator *sep); +void command_showzonegloballoot(Client *c, const Seperator *sep); void command_shutdown(Client *c, const Seperator *sep); void command_size(Client *c, const Seperator *sep); void command_spawn(Client *c, const Seperator *sep); diff --git a/zone/forage.cpp b/zone/forage.cpp index be93d39ae..4dc3bc04f 100644 --- a/zone/forage.cpp +++ b/zone/forage.cpp @@ -277,20 +277,23 @@ void Client::GoFish() food_id = database.GetZoneFishing(m_pp.zone_id, fishing_skill, npc_id, npc_chance); //check for add NPC - if(npc_chance > 0 && npc_id) { - if(npc_chance < zone->random.Int(0, 99)) { - const NPCType* tmp = database.LoadNPCTypesData(npc_id); - if(tmp != nullptr) { - auto positionNPC = GetPosition(); - positionNPC.x = positionNPC.x + 3; - auto npc = new NPC(tmp, nullptr, positionNPC, FlyMode3); - npc->AddLootTable(); + if (npc_chance > 0 && npc_id) { + if (npc_chance < zone->random.Int(0, 99)) { + const NPCType *tmp = database.LoadNPCTypesData(npc_id); + if (tmp != nullptr) { + auto positionNPC = GetPosition(); + positionNPC.x = positionNPC.x + 3; + auto npc = new NPC(tmp, nullptr, positionNPC, FlyMode3); + npc->AddLootTable(); + if (npc->DropsGlobalLoot()) + npc->CheckGlobalLootTables(); - npc->AddToHateList(this, 1, 0, false); // no help yelling + npc->AddToHateList(this, 1, 0, false); // no help yelling - entity_list.AddNPC(npc); + entity_list.AddNPC(npc); - Message(MT_Emote, "You fish up a little more than you bargained for..."); + Message(MT_Emote, + "You fish up a little more than you bargained for..."); } } } diff --git a/zone/global_loot_manager.cpp b/zone/global_loot_manager.cpp new file mode 100644 index 000000000..8e990c0ac --- /dev/null +++ b/zone/global_loot_manager.cpp @@ -0,0 +1,98 @@ +#include "global_loot_manager.h" +#include "npc.h" +#include "client.h" + +std::vector GlobalLootManager::GetGlobalLootTables(NPC *mob) const +{ + // we may be able to add a cache here if performance is an issue, but for now + // just return NRVO'd vector + // The cache would have to be keyed by NPCType and level (for NPCs with Max Level set) + std::vector tables; + + for (auto &e : m_entries) { + if (e.PassesRules(mob)) { + tables.push_back(e.GetLootTableID()); + } + } + + return tables; +} + +void GlobalLootManager::ShowZoneGlobalLoot(Client *to) const +{ + for (auto &e : m_entries) + to->Message(0, " %s : %d table %d", e.GetDescription().c_str(), e.GetID(), e.GetLootTableID()); +} + +void GlobalLootManager::ShowNPCGlobalLoot(Client *to, NPC *who) const +{ + for (auto &e : m_entries) { + if (e.PassesRules(who)) + to->Message(0, " %s : %d table %d", e.GetDescription().c_str(), e.GetID(), e.GetLootTableID()); + } +} + +bool GlobalLootEntry::PassesRules(NPC *mob) const +{ + bool bRace = false; + bool bPassesRace = false; + bool bBodyType = false; + bool bPassesBodyType = false; + bool bClass = false; + bool bPassesClass = false; + + for (auto &r : m_rules) { + switch (r.type) { + case GlobalLoot::RuleTypes::LevelMin: + if (mob->GetLevel() < r.value) + return false; + break; + case GlobalLoot::RuleTypes::LevelMax: + if (mob->GetLevel() > r.value) + return false; + break; + case GlobalLoot::RuleTypes::Raid: // value == 0 must not be raid, value != 0 must be raid + if (mob->IsRaidTarget() && !r.value) + return false; + if (!mob->IsRaidTarget() && r.value) + return false; + break; + case GlobalLoot::RuleTypes::Rare: + if (mob->IsRareSpawn() && !r.value) + return false; + if (!mob->IsRareSpawn() && r.value) + return false; + break; + case GlobalLoot::RuleTypes::Race: // can have multiple races per rule set + bRace = true; // we must pass race + if (mob->GetRace() == r.value) + bPassesRace = true; + break; + case GlobalLoot::RuleTypes::Class: // can have multiple classes per rule set + bClass = true; // we must pass class + if (mob->GetClass() == r.value) + bPassesClass = true; + break; + case GlobalLoot::RuleTypes::BodyType: // can have multiple bodytypes per rule set + bBodyType = true; // we must pass BodyType + if (mob->GetBodyType() == r.value) + bPassesBodyType = true; + break; + default: + break; + } + } + + if (bRace && !bPassesRace) + return false; + + if (bClass && !bPassesClass) + return false; + + if (bBodyType && !bPassesBodyType) + return false; + + // we abort as early as possible if we fail a rule, so if we get here, we passed + return true; +} + diff --git a/zone/global_loot_manager.h b/zone/global_loot_manager.h new file mode 100644 index 000000000..fec5a7215 --- /dev/null +++ b/zone/global_loot_manager.h @@ -0,0 +1,61 @@ +#ifndef GLOBAL_LOOT_MANAGER_H +#define GLOBAL_LOOT_MANAGER_H + +#include +#include + +class NPC; +class Client; + +namespace GlobalLoot { + +enum class RuleTypes { + LevelMin = 0, + LevelMax = 1, + Race = 2, + Class = 3, + BodyType = 4, + Rare = 5, + Raid = 6, + Max +}; + +struct Rule { + RuleTypes type; + int value; + Rule(RuleTypes t, int v) : type(t), value(v) { } +}; + +}; + +class GlobalLootEntry { + int m_id; + int m_loottable_id; + std::string m_description; + std::vector m_rules; +public: + GlobalLootEntry(int id, int loottable, std::string des) + : m_id(id), m_loottable_id(loottable), m_description(std::move(des)) + { } + bool PassesRules(NPC *mob) const; + inline int GetLootTableID() const { return m_loottable_id; } + inline int GetID() const { return m_id; } + inline const std::string &GetDescription() const { return m_description; } + inline void SetLootTableID(int in) { m_loottable_id = in; } + inline void SetID(int in) { m_id = in; } + inline void SetDescription(const std::string &in) { m_description = in; } + inline void AddRule(GlobalLoot::RuleTypes rule, int value) { m_rules.emplace_back(rule, value); } +}; + +class GlobalLootManager { + std::vector m_entries; + +public: + std::vector GetGlobalLootTables(NPC *mob) const; + inline void Clear() { m_entries.clear(); } + inline void AddEntry(GlobalLootEntry &in) { m_entries.push_back(in); } + void ShowZoneGlobalLoot(Client *to) const; + void ShowNPCGlobalLoot(Client *to, NPC *who) const; +}; + +#endif /* !GLOBAL_LOOT_MANAGER_H */ diff --git a/zone/loottables.cpp b/zone/loottables.cpp index d9fa86257..961d66781 100644 --- a/zone/loottables.cpp +++ b/zone/loottables.cpp @@ -26,6 +26,7 @@ #include "mob.h" #include "npc.h" #include "zonedb.h" +#include "global_loot_manager.h" #include #include @@ -37,10 +38,14 @@ // Queries the loottable: adds item & coin to the npc void ZoneDatabase::AddLootTableToNPC(NPC* npc,uint32 loottable_id, ItemList* itemlist, uint32* copper, uint32* silver, uint32* gold, uint32* plat) { const LootTable_Struct* lts = nullptr; - *copper = 0; - *silver = 0; - *gold = 0; - *plat = 0; + // global loot passes nullptr for these + bool bGlobal = copper == nullptr && silver == nullptr && gold == nullptr && plat == nullptr; + if (!bGlobal) { + *copper = 0; + *silver = 0; + *gold = 0; + *plat = 0; + } lts = database.GetLootTable(loottable_id); if (!lts) @@ -55,17 +60,19 @@ void ZoneDatabase::AddLootTableToNPC(NPC* npc,uint32 loottable_id, ItemList* ite } uint32 cash = 0; - if(max_cash > 0 && lts->avgcoin > 0 && EQEmu::ValueWithin(lts->avgcoin, min_cash, max_cash)) { - float upper_chance = (float)(lts->avgcoin - min_cash) / (float)(max_cash - min_cash); - float avg_cash_roll = (float)zone->random.Real(0.0, 1.0); + if (!bGlobal) { + if(max_cash > 0 && lts->avgcoin > 0 && EQEmu::ValueWithin(lts->avgcoin, min_cash, max_cash)) { + float upper_chance = (float)(lts->avgcoin - min_cash) / (float)(max_cash - min_cash); + float avg_cash_roll = (float)zone->random.Real(0.0, 1.0); - if(avg_cash_roll < upper_chance) { - cash = zone->random.Int(lts->avgcoin, max_cash); + if(avg_cash_roll < upper_chance) { + cash = zone->random.Int(lts->avgcoin, max_cash); + } else { + cash = zone->random.Int(min_cash, lts->avgcoin); + } } else { - cash = zone->random.Int(min_cash, lts->avgcoin); + cash = zone->random.Int(min_cash, max_cash); } - } else { - cash = zone->random.Int(min_cash, max_cash); } if(cash != 0) { @@ -80,6 +87,7 @@ void ZoneDatabase::AddLootTableToNPC(NPC* npc,uint32 loottable_id, ItemList* ite *copper = cash; } + uint32 global_loot_multiplier = RuleI(Zone, GlobalLootMultiplier); // Do items @@ -444,3 +452,76 @@ void NPC::AddLootTable(uint32 ldid) { database.AddLootTableToNPC(this,ldid, &itemlist, &copper, &silver, &gold, &platinum); } } + +void NPC::CheckGlobalLootTables() +{ + auto tables = zone->GetGlobalLootTables(this); + + for (auto &id : tables) + database.AddLootTableToNPC(this, id, &itemlist, nullptr, nullptr, nullptr, nullptr); +} + +void ZoneDatabase::LoadGlobalLoot() +{ + auto query = StringFormat("SELECT id, loottable_id, description, min_level, max_level, rare, raid, race, " + "class, bodytype, zone FROM global_loot WHERE enabled = 1"); + + auto results = QueryDatabase(query); + if (!results.Success() || results.RowCount() == 0) + return; + + // we might need this, lets not keep doing it in a loop + auto zoneid = std::to_string(zone->GetZoneID()); + for (auto row = results.begin(); row != results.end(); ++row) { + // checking zone limits + if (row[10]) { + auto zones = SplitString(row[10], '|'); + + auto it = std::find(zones.begin(), zones.end(), zoneid); + if (it == zones.end()) // not in here, skip + continue; + } + + GlobalLootEntry e(atoi(row[0]), atoi(row[1]), row[2] ? row[2] : ""); + + auto min_level = atoi(row[3]); + if (min_level) + e.AddRule(GlobalLoot::RuleTypes::LevelMin, min_level); + + auto max_level = atoi(row[4]); + if (max_level) + e.AddRule(GlobalLoot::RuleTypes::LevelMax, max_level); + + // null is not used + if (row[5]) + e.AddRule(GlobalLoot::RuleTypes::Rare, atoi(row[5])); + + // null is not used + if (row[6]) + e.AddRule(GlobalLoot::RuleTypes::Raid, atoi(row[6])); + + if (row[7]) { + auto races = SplitString(row[7], '|'); + + for (auto &r : races) + e.AddRule(GlobalLoot::RuleTypes::Race, std::stoi(r)); + } + + if (row[8]) { + auto classes = SplitString(row[8], '|'); + + for (auto &c : classes) + e.AddRule(GlobalLoot::RuleTypes::Class, std::stoi(c)); + } + + if (row[9]) { + auto bodytypes = SplitString(row[9], '|'); + + for (auto &b : bodytypes) + e.AddRule(GlobalLoot::RuleTypes::Class, std::stoi(b)); + } + + zone->AddGlobalLootEntry(e); + } +} + diff --git a/zone/mob.cpp b/zone/mob.cpp index 9efdda70c..0c0e20d81 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -266,6 +266,7 @@ Mob::Mob(const char* in_name, IsFullHP = (cur_hp == max_hp); qglobal = 0; spawned = false; + rare_spawn = false; InitializeBuffSlots(); diff --git a/zone/mob.h b/zone/mob.h index f973dd022..c223a45b4 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -689,6 +689,8 @@ public: void SetFollowDistance(uint32 dist) { follow_dist = dist; } uint32 GetFollowID() const { return follow; } uint32 GetFollowDistance() const { return follow_dist; } + inline bool IsRareSpawn() const { return rare_spawn; } + inline void SetRareSpawn(bool in) { rare_spawn = in; } virtual void Message(uint32 type, const char* message, ...) { } virtual void Message_StringID(uint32 type, uint32 string_id, uint32 distance = 0) { } @@ -1225,6 +1227,7 @@ protected: uint32 follow; uint32 follow_dist; bool no_target_hotkey; + bool rare_spawn; uint32 m_PlayerState; uint32 GetPlayerState() { return m_PlayerState; } diff --git a/zone/npc.cpp b/zone/npc.cpp index de494bb05..8f070f7a9 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -245,6 +245,8 @@ NPC::NPC(const NPCType* d, Spawn2* in_respawn, const glm::vec4& position, int if roambox_delay = 1000; p_depop = false; loottable_id = d->loottable_id; + skip_global_loot = d->skip_global_loot; + rare_spawn = d->rare_spawn; no_target_hotkey = d->no_target_hotkey; @@ -938,6 +940,7 @@ bool NPC::SpawnZoneController(){ npc_type->d_melee_texture2 = 0; npc_type->merchanttype = 0; npc_type->bodytype = 11; + npc_type->skip_global_loot = true; if (RuleB(Zone, EnableZoneControllerGlobals)) { npc_type->qglobal = true; diff --git a/zone/npc.h b/zone/npc.h index 0ff7f3b03..2e264ce24 100644 --- a/zone/npc.h +++ b/zone/npc.h @@ -185,6 +185,7 @@ public: void AddItem(uint32 itemid, uint16 charges, bool equipitem = true, uint32 aug1 = 0, uint32 aug2 = 0, uint32 aug3 = 0, uint32 aug4 = 0, uint32 aug5 = 0, uint32 aug6 = 0); void AddLootTable(); void AddLootTable(uint32 ldid); + void CheckGlobalLootTables(); void DescribeAggro(Client *towho, Mob *mob, bool verbose); void RemoveItem(uint32 item_id, uint16 quantity = 0, uint16 slot = 0); void CheckMinMaxLevel(Mob *them); @@ -197,6 +198,7 @@ public: uint32 CountLoot(); inline uint32 GetLoottableID() const { return loottable_id; } virtual void UpdateEquipmentLight(); + inline bool DropsGlobalLoot() const { return !skip_global_loot; } inline uint32 GetCopper() const { return copper; } inline uint32 GetSilver() const { return silver; } @@ -562,6 +564,7 @@ protected: private: uint32 loottable_id; + bool skip_global_loot; bool p_depop; }; diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index c77e96979..6c0bdfe0a 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -210,6 +210,8 @@ Mob* QuestManager::spawn2(int npc_type, int grid, int unused, const glm::vec4& p { auto npc = new NPC(tmp, nullptr, position, FlyMode3); npc->AddLootTable(); + if (npc->DropsGlobalLoot()) + npc->CheckGlobalLootTables(); entity_list.AddNPC(npc,true,true); if(grid > 0) { @@ -232,6 +234,8 @@ Mob* QuestManager::unique_spawn(int npc_type, int grid, int unused, const glm::v { auto npc = new NPC(tmp, nullptr, position, FlyMode3); npc->AddLootTable(); + if (npc->DropsGlobalLoot()) + npc->CheckGlobalLootTables(); entity_list.AddNPC(npc,true,true); if(grid > 0) { @@ -308,6 +312,8 @@ Mob* QuestManager::spawn_from_spawn2(uint32 spawn2_id) found_spawn->SetNPCPointer(npc); npc->AddLootTable(); + if (npc->DropsGlobalLoot()) + npc->CheckGlobalLootTables(); npc->SetSp2(found_spawn->SpawnGroupID()); entity_list.AddNPC(npc); entity_list.LimitAddNPC(npc); @@ -1656,6 +1662,8 @@ void QuestManager::respawn(int npcTypeID, int grid) { { owner = new NPC(npcType, nullptr, owner->GetPosition(), FlyMode3); owner->CastToNPC()->AddLootTable(); + if (owner->CastToNPC()->DropsGlobalLoot()) + owner->CastToNPC()->CheckGlobalLootTables(); entity_list.AddNPC(owner->CastToNPC(),true,true); if(grid > 0) owner->CastToNPC()->AssignWaypoints(grid); diff --git a/zone/spawn2.cpp b/zone/spawn2.cpp index 654c5e836..bfc474721 100644 --- a/zone/spawn2.cpp +++ b/zone/spawn2.cpp @@ -236,6 +236,8 @@ bool Spawn2::Process() { npcthis = npc; npc->AddLootTable(); + if (npc->DropsGlobalLoot()) + npc->CheckGlobalLootTables(); npc->SetSp2(spawngroup_id_); npc->SaveGuardPointAnim(anim); npc->SetAppearance((EmuAppearance)anim); diff --git a/zone/trap.cpp b/zone/trap.cpp index d5bcfbeb8..91e11c012 100644 --- a/zone/trap.cpp +++ b/zone/trap.cpp @@ -168,6 +168,8 @@ void Trap::Trigger(Mob* trigger) auto spawnPosition = randomOffset + glm::vec4(m_Position, 0.0f); auto new_npc = new NPC(tmp, nullptr, spawnPosition, FlyMode3); new_npc->AddLootTable(); + if (new_npc->DropsGlobalLoot()) + new_npc->CheckGlobalLootTables(); entity_list.AddNPC(new_npc); new_npc->AddToHateList(trigger,1); } @@ -191,6 +193,8 @@ void Trap::Trigger(Mob* trigger) auto spawnPosition = randomOffset + glm::vec4(m_Position, 0.0f); auto new_npc = new NPC(tmp, nullptr, spawnPosition, FlyMode3); new_npc->AddLootTable(); + if (new_npc->DropsGlobalLoot()) + new_npc->CheckGlobalLootTables(); entity_list.AddNPC(new_npc); new_npc->AddToHateList(trigger,1); } @@ -555,4 +559,4 @@ void Trap::UpdateTrap(bool respawn, bool repopnow) { database.SetTrapData(this, repopnow); } -} \ No newline at end of file +} diff --git a/zone/zone.cpp b/zone/zone.cpp index 8045e0fbf..fce562c94 100644 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -968,6 +968,8 @@ bool Zone::Init(bool iStaticZone) { LoadAlternateAdvancement(); + database.LoadGlobalLoot(); + //Load merchant data zone->GetMerchantDataForZoneLoad(); @@ -2229,6 +2231,8 @@ void Zone::DoAdventureActions() { NPC* npc = new NPC(tmp, nullptr, glm::vec4(ds->assa_x, ds->assa_y, ds->assa_z, ds->assa_h), FlyMode3); npc->AddLootTable(); + if (npc->DropsGlobalLoot()) + npc->CheckGlobalLootTables(); entity_list.AddNPC(npc); npc->Shout("Rarrrgh!"); did_adventure_actions = true; diff --git a/zone/zone.h b/zone/zone.h index 3a5125eb4..158d414a4 100644 --- a/zone/zone.h +++ b/zone/zone.h @@ -28,6 +28,7 @@ #include "spawn2.h" #include "spawngroup.h" #include "aa_ability.h" +#include "global_loot_manager.h" struct ZonePoint { @@ -269,6 +270,11 @@ public: void UpdateHotzone(); std::unordered_map tick_items; + inline std::vector GetGlobalLootTables(NPC *mob) const { return m_global_loot.GetGlobalLootTables(mob); } + inline void AddGlobalLootEntry(GlobalLootEntry &in) { return m_global_loot.AddEntry(in); } + inline void ShowZoneGlobalLoot(Client *to) { m_global_loot.ShowZoneGlobalLoot(to); } + inline void ShowNPCGlobalLoot(Client *to, NPC *who) { m_global_loot.ShowNPCGlobalLoot(to, who); } + // random object that provides random values for the zone EQEmu::Random random; @@ -347,6 +353,8 @@ private: QGlobalCache *qGlobals; Timer hotzone_timer; + + GlobalLootManager m_global_loot; }; #endif diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index 75ca99ac8..01add9944 100644 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -1969,7 +1969,9 @@ const NPCType* ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load "npc_types.charm_attack_delay, " "npc_types.charm_accuracy_rating, " "npc_types.charm_avoidance_rating, " - "npc_types.charm_atk " + "npc_types.charm_atk, " + "npc_types.skip_global_loot, " + "npc_types.rare_spawn " "FROM npc_types %s", where_condition.c_str() ); @@ -2156,6 +2158,9 @@ const NPCType* ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load temp_npctype_data->charm_avoidance_rating = atoi(row[105]); temp_npctype_data->charm_atk = atoi(row[106]); + temp_npctype_data->skip_global_loot = atoi(row[107]) != 0; + temp_npctype_data->rare_spawn = atoi(row[108]) != 0; + // If NPC with duplicate NPC id already in table, // free item we attempted to add. if (zone->npctable.find(temp_npctype_data->npc_id) != zone->npctable.end()) { diff --git a/zone/zonedb.h b/zone/zonedb.h index ec1a1d663..6f65bae91 100644 --- a/zone/zonedb.h +++ b/zone/zonedb.h @@ -424,6 +424,7 @@ public: uint32 GetMaxNPCSpellsID(); uint32 GetMaxNPCSpellsEffectsID(); bool GetAuraEntry(uint16 spell_id, AuraRecord &record); + void LoadGlobalLoot(); DBnpcspells_Struct* GetNPCSpells(uint32 iDBSpellsID); DBnpcspellseffects_Struct* GetNPCSpellsEffects(uint32 iDBSpellsEffectsID); diff --git a/zone/zonedump.h b/zone/zonedump.h index 636978475..66c7ec01d 100644 --- a/zone/zonedump.h +++ b/zone/zonedump.h @@ -141,6 +141,8 @@ struct NPCType bool ignore_despawn; bool show_name; // should default on bool untargetable; + bool skip_global_loot; + bool rare_spawn; }; namespace player_lootitem {