Implement global loot system Fixes #619

This should allow us to emulate lives global tables

The options available to filter tables are min_level, max_level, race,
rare, raid, race, class, bodytype, and zone.

race, class, bodytype, and zone are a pipe | separated list of IDs
This commit is contained in:
Michael Cook (mackal) 2018-02-10 22:15:21 -05:00
parent 0b97db9fd2
commit c5e4bb08f4
23 changed files with 372 additions and 27 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,98 @@
#include "global_loot_manager.h"
#include "npc.h"
#include "client.h"
std::vector<int> 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<int> 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;
}

View File

@ -0,0 +1,61 @@
#ifndef GLOBAL_LOOT_MANAGER_H
#define GLOBAL_LOOT_MANAGER_H
#include <vector>
#include <string>
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<GlobalLoot::Rule> 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<GlobalLootEntry> m_entries;
public:
std::vector<int> 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 */

View File

@ -26,6 +26,7 @@
#include "mob.h"
#include "npc.h"
#include "zonedb.h"
#include "global_loot_manager.h"
#include <iostream>
#include <stdlib.h>
@ -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);
}
}

View File

@ -266,6 +266,7 @@ Mob::Mob(const char* in_name,
IsFullHP = (cur_hp == max_hp);
qglobal = 0;
spawned = false;
rare_spawn = false;
InitializeBuffSlots();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<int, item_tick_struct> tick_items;
inline std::vector<int> 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

View File

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

View File

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

View File

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