[Feature] Add "Keeps Sold Items" Flag to NPCs (#2671)

# Perl
- Add `$npc->GetKeepsSoldItems()`.
- Add `$npc->SetKeepsSoldItems(keeps_sold_items)`.

# Lua
- Add `npc:GetKeepsSoldItems()`.
- Add `npc:SetKeepsSoldItems(keeps_sold_items)`.

# Notes
- Allows operators to keep specific NPCs from keeping items sold to them.
- Keeps NPCs from being cluttered with stuff like Cloth Caps, Bone Chips, etc.
This commit is contained in:
Alex King 2022-12-25 16:36:20 -05:00 committed by GitHub
parent 2ed73199bf
commit a590ea1d52
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 696 additions and 415 deletions

View File

@ -16,6 +16,7 @@
#include "../../strings.h" #include "../../strings.h"
#include <ctime> #include <ctime>
class BaseNpcTypesRepository { class BaseNpcTypesRepository {
public: public:
struct NpcTypes { struct NpcTypes {
@ -145,6 +146,7 @@ public:
int32_t exp_mod; int32_t exp_mod;
int32_t heroic_strikethrough; int32_t heroic_strikethrough;
int32_t faction_amount; int32_t faction_amount;
uint8_t keeps_sold_items;
}; };
static std::string PrimaryKey() static std::string PrimaryKey()
@ -281,6 +283,7 @@ public:
"exp_mod", "exp_mod",
"heroic_strikethrough", "heroic_strikethrough",
"faction_amount", "faction_amount",
"keeps_sold_items",
}; };
} }
@ -413,6 +416,7 @@ public:
"exp_mod", "exp_mod",
"heroic_strikethrough", "heroic_strikethrough",
"faction_amount", "faction_amount",
"keeps_sold_items",
}; };
} }
@ -579,6 +583,7 @@ public:
e.exp_mod = 100; e.exp_mod = 100;
e.heroic_strikethrough = 0; e.heroic_strikethrough = 0;
e.faction_amount = 0; e.faction_amount = 0;
e.keeps_sold_items = 0;
return e; return e;
} }
@ -604,8 +609,9 @@ public:
{ {
auto results = db.QueryDatabase( auto results = db.QueryDatabase(
fmt::format( fmt::format(
"{} WHERE id = {} LIMIT 1", "{} WHERE {} = {} LIMIT 1",
BaseSelect(), BaseSelect(),
PrimaryKey(),
npc_types_id npc_types_id
) )
); );
@ -740,6 +746,7 @@ public:
e.exp_mod = static_cast<int32_t>(atoi(row[123])); e.exp_mod = static_cast<int32_t>(atoi(row[123]));
e.heroic_strikethrough = static_cast<int32_t>(atoi(row[124])); e.heroic_strikethrough = static_cast<int32_t>(atoi(row[124]));
e.faction_amount = static_cast<int32_t>(atoi(row[125])); e.faction_amount = static_cast<int32_t>(atoi(row[125]));
e.keeps_sold_items = static_cast<uint8_t>(strtoul(row[126], nullptr, 10));
return e; return e;
} }
@ -898,6 +905,7 @@ public:
v.push_back(columns[123] + " = " + std::to_string(e.exp_mod)); v.push_back(columns[123] + " = " + std::to_string(e.exp_mod));
v.push_back(columns[124] + " = " + std::to_string(e.heroic_strikethrough)); v.push_back(columns[124] + " = " + std::to_string(e.heroic_strikethrough));
v.push_back(columns[125] + " = " + std::to_string(e.faction_amount)); v.push_back(columns[125] + " = " + std::to_string(e.faction_amount));
v.push_back(columns[126] + " = " + std::to_string(e.keeps_sold_items));
auto results = db.QueryDatabase( auto results = db.QueryDatabase(
fmt::format( fmt::format(
@ -1045,6 +1053,7 @@ public:
v.push_back(std::to_string(e.exp_mod)); v.push_back(std::to_string(e.exp_mod));
v.push_back(std::to_string(e.heroic_strikethrough)); v.push_back(std::to_string(e.heroic_strikethrough));
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));
auto results = db.QueryDatabase( auto results = db.QueryDatabase(
fmt::format( fmt::format(
@ -1200,6 +1209,7 @@ public:
v.push_back(std::to_string(e.exp_mod)); v.push_back(std::to_string(e.exp_mod));
v.push_back(std::to_string(e.heroic_strikethrough)); v.push_back(std::to_string(e.heroic_strikethrough));
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));
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
} }
@ -1359,6 +1369,7 @@ public:
e.exp_mod = static_cast<int32_t>(atoi(row[123])); e.exp_mod = static_cast<int32_t>(atoi(row[123]));
e.heroic_strikethrough = static_cast<int32_t>(atoi(row[124])); e.heroic_strikethrough = static_cast<int32_t>(atoi(row[124]));
e.faction_amount = static_cast<int32_t>(atoi(row[125])); e.faction_amount = static_cast<int32_t>(atoi(row[125]));
e.keeps_sold_items = static_cast<uint8_t>(strtoul(row[126], nullptr, 10));
all_entries.push_back(e); all_entries.push_back(e);
} }
@ -1509,6 +1520,7 @@ public:
e.exp_mod = static_cast<int32_t>(atoi(row[123])); e.exp_mod = static_cast<int32_t>(atoi(row[123]));
e.heroic_strikethrough = static_cast<int32_t>(atoi(row[124])); e.heroic_strikethrough = static_cast<int32_t>(atoi(row[124]));
e.faction_amount = static_cast<int32_t>(atoi(row[125])); e.faction_amount = static_cast<int32_t>(atoi(row[125]));
e.keeps_sold_items = static_cast<uint8_t>(strtoul(row[126], nullptr, 10));
all_entries.push_back(e); all_entries.push_back(e);
} }

View File

@ -34,7 +34,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 9212 #define CURRENT_BINARY_DATABASE_VERSION 9213
#ifdef BOTS #ifdef BOTS
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9035 #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9035

View File

@ -466,6 +466,7 @@
9210|2022_10_11_fix_misty_pok_stone.sql|select * from doors where id = 2040 and `name` = 'POKRVPORT500' and client_version_mask = 4294967232|empty| 9210|2022_10_11_fix_misty_pok_stone.sql|select * from doors where id = 2040 and `name` = 'POKRVPORT500' and client_version_mask = 4294967232|empty|
9211|2022_10_14_fix_neriak_pok_stone.sql|select * from doors where id = 2057 and `name` = 'POKNRKPORT500' and client_version_mask = 4294967232|empty| 9211|2022_10_14_fix_neriak_pok_stone.sql|select * from doors where id = 2057 and `name` = 'POKNRKPORT500' and client_version_mask = 4294967232|empty|
9212|2022_10_14_fix_misty_pok_stone.sql|select * from doors where id = 2040 and `name` = 'POKRVPORT500' and dest_zone = 'misty'|empty| 9212|2022_10_14_fix_misty_pok_stone.sql|select * from doors where id = 2040 and `name` = 'POKRVPORT500' and dest_zone = 'misty'|empty|
9213|2022_12_24_npc_keeps_sold_items.sql|SHOW COLUMNS FROM `npc_types` LIKE 'keeps_sold_items'|empty|
# Upgrade conditions: # Upgrade conditions:
# This won't be needed after this system is implemented, but it is used database that are not # This won't be needed after this system is implemented, but it is used database that are not

View File

@ -0,0 +1,2 @@
ALTER TABLE `npc_types`
ADD COLUMN `keeps_sold_items` tinyint(1) UNSIGNED NOT NULL DEFAULT 1 AFTER `faction_amount`;

View File

@ -52,14 +52,66 @@ extern Zone* zone;
// if lifetime is 0 this is a permanent beacon.. not sure if that'll be // if lifetime is 0 this is a permanent beacon.. not sure if that'll be
// useful for anything // useful for anything
Beacon::Beacon(const glm::vec4 &in_pos, int lifetime) Beacon::Beacon(const glm::vec4 &in_pos, int lifetime) : Mob(
:Mob nullptr, // in_name
( nullptr, // in_lastname
nullptr, nullptr, 0, 0, 0, INVISIBLE_MAN, 0, BT_NoTarget, 0, 0, 0, 0, 0, in_pos, 0, 0, 0, 0, // in_cur_hp
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, EQ::TintProfile(), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false 0, // in_max_hp
MALE, // in_gender
INVISIBLE_MAN, // in_race
0, // in_class
BT_NoTarget, // in_bodytype
0, // in_deity
0, // in_level
0, // in_npctype_id
0.0f, // in_size
0.0f, // in_runspeed
in_pos, // position
0, // in_light
0, // in_texture
0, // 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
0, // 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
), ),
remove_timer(lifetime), remove_timer(lifetime),
spell_timer(0) spell_timer(0)
{ {
remove_timer.Disable(); remove_timer.Disable();
spell_timer.Disable(); spell_timer.Disable();

View File

@ -82,63 +82,64 @@ char entirecommand[255];
void UpdateWindowTitle(char* iNewTitle); void UpdateWindowTitle(char* iNewTitle);
Client::Client(EQStreamInterface* ieqs) Client::Client(EQStreamInterface *ieqs) : Mob(
: Mob("No name", // name "No name", // in_name
"", // lastname "", // in_lastname
0, // cur_hp 0, // in_cur_hp
0, // max_hp 0, // in_max_hp
0, // gender 0, // in_gender
0, // race 0, // in_race
0, // class 0, // in_class
BT_Humanoid, // bodytype BT_Humanoid, // in_bodytype
0, // deity 0, // in_deity
0, // level 0, // in_level
0, // npctypeid 0, // in_npctype_id
0, // size 0.0f, // in_size
0.7, // runspeed 0.7f, // in_runspeed
glm::vec4(), glm::vec4(), // position
0, // light - verified for client innate_light value 0, // in_light
0xFF, // texture 0xFF, // in_texture
0xFF, // helmtexture 0xFF, // in_helmtexture
0, // ac 0, // in_ac
0, // atk 0, // in_atk
0, // str 0, // in_str
0, // sta 0, // in_sta
0, // dex 0, // in_dex
0, // agi 0, // in_agi
0, // int 0, // in_int
0, // wis 0, // in_wis
0, // cha 0, // in_cha
0, // Luclin Hair Colour 0, // in_haircolor
0, // Luclin Beard Color 0, // in_beardcolor
0, // Luclin Eye1 0, // in_eyecolor1
0, // Luclin Eye2 0, // in_eyecolor2
0, // Luclin Hair Style 0, // in_hairstyle
0, // Luclin Face 0, // in_luclinface
0, // Luclin Beard 0, // in_beard
0, // Drakkin Heritage 0, // in_drakkin_heritage
0, // Drakkin Tattoo 0, // in_drakkin_tattoo
0, // Drakkin Details 0, // in_drakkin_details
EQ::TintProfile(), // Armor Tint EQ::TintProfile(), // in_armor_tint
0xff, // AA Title 0xff, // in_aa_title
0, // see_invis 0, // in_see_invis
0, // see_invis_undead 0, // in_see_invis_undead
0, 0, // in_see_hide
0, 0, // in_see_improved_hide
0, 0, // in_hp_regen
0, 0, // in_mana_regen
0, // qglobal 0, // in_qglobal
0, // maxlevel 0, // in_maxlevel
0, // scalerate 0, // in_scalerate
0, 0, // in_armtexture
0, 0, // in_bracertexture
0, 0, // in_handtexture
0, 0, // in_legtexture
0, 0, // in_feettexture
0, 0, // in_usemodel
0, false, // in_always_aggros_foes
false 0, // in_heroic_strikethrough
), false // in_keeps_sold_items
),
hpupdate_timer(2000), hpupdate_timer(2000),
camp_timer(29000), camp_timer(29000),
process_timer(100), process_timer(100),

View File

@ -13403,32 +13403,50 @@ void Client::Handle_OP_ShopPlayerSell(const EQApplicationPacket *app)
int charges = mp->quantity; int charges = mp->quantity;
int freeslot = 0; if (vendor->GetKeepsSoldItems()) {
if ((freeslot = zone->SaveTempItem(vendor->CastToNPC()->MerchantType, vendor->GetNPCTypeID(), itemid, charges, true)) > 0) { int freeslot = 0;
EQ::ItemInstance* inst2 = inst->Clone(); if (
(freeslot = zone->SaveTempItem(
vendor->CastToNPC()->MerchantType,
vendor->GetNPCTypeID(),
itemid,
charges,
true
)
) > 0) {
EQ::ItemInstance *inst2 = inst->Clone();
while (true) {
if (!inst2) {
break;
}
uint32 price = (
item->Price *
RuleR(Merchant, SellCostMod) *
item->SellRate
);
if (RuleB(Merchant, UsePriceMod)) {
price *= Client::CalcPriceMod(vendor, false);
}
inst2->SetPrice(price);
inst2->SetMerchantSlot(freeslot);
uint32 merchant_quantity = zone->GetTempMerchantQuantity(vendor->GetNPCTypeID(), freeslot);
if (inst2->IsStackable()) {
inst2->SetCharges(merchant_quantity);
}
inst2->SetMerchantCount(merchant_quantity);
SendItemPacket(freeslot - 1, inst2, ItemPacketMerchant);
safe_delete(inst2);
while (true) {
if (inst2 == nullptr)
break; break;
if (RuleB(Merchant, UsePriceMod)) {
inst2->SetPrice(item->Price*(RuleR(Merchant, SellCostMod))*item->SellRate*Client::CalcPriceMod(vendor, false));
} }
else
inst2->SetPrice(item->Price*(RuleR(Merchant, SellCostMod))*item->SellRate);
inst2->SetMerchantSlot(freeslot);
uint32 MerchantQuantity = zone->GetTempMerchantQuantity(vendor->GetNPCTypeID(), freeslot);
if (inst2->IsStackable()) {
inst2->SetCharges(MerchantQuantity);
}
inst2->SetMerchantCount(MerchantQuantity);
SendItemPacket(freeslot - 1, inst2, ItemPacketMerchant);
safe_delete(inst2);
break;
} }
} }

View File

@ -30,6 +30,7 @@ Child of the Mob class.
#define strcasecmp _stricmp #define strcasecmp _stricmp
#endif #endif
#include "../common/data_verification.h"
#include "../common/global_define.h" #include "../common/global_define.h"
#include "../common/eqemu_logsys.h" #include "../common/eqemu_logsys.h"
#include "../common/rulesys.h" #include "../common/rulesys.h"
@ -148,19 +149,75 @@ Corpse* Corpse::LoadCharacterCorpseEntity(uint32 in_dbid, uint32 in_charid, std:
return pc; return pc;
} }
Corpse::Corpse(NPC* in_npc, ItemList* in_itemlist, uint32 in_npctypeid, const NPCType** in_npctypedata, uint32 in_decaytime) Corpse::Corpse(
// vesuvias - appearence fix NPC *in_npc,
: Mob("Unnamed_Corpse","",0,0,in_npc->GetGender(),in_npc->GetRace(),in_npc->GetClass(),BT_Humanoid,//bodytype added ItemList *in_itemlist,
in_npc->GetDeity(),in_npc->GetLevel(),in_npc->GetNPCTypeID(),in_npc->GetSize(),0, uint32 in_npctypeid,
in_npc->GetPosition(), in_npc->GetInnateLightType(), in_npc->GetTexture(),in_npc->GetHelmTexture(), const NPCType **in_npctypedata,
0,0,0,0,0,0,0,0,0, uint32 in_decaytime
0,0,0,0,0,0,0,0,0,0,EQ::TintProfile(),0xff,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, ) : Mob(
(*in_npctypedata)->use_model, false), "Unnamed_Corpse", // in_name
corpse_decay_timer(in_decaytime), "", // in_lastname
corpse_rez_timer(0), 0, // in_cur_hp
corpse_delay_timer(RuleI(NPC, CorpseUnlockTimer)), 0, // in_max_hp
corpse_graveyard_timer(0), in_npc->GetGender(), // in_gender
loot_cooldown_timer(10) in_npc->GetRace(), // in_race
in_npc->GetClass(), // in_class
BT_Humanoid, // in_bodytype
in_npc->GetDeity(), // in_deity
in_npc->GetLevel(), // in_level
in_npc->GetNPCTypeID(), // in_npctype_id
in_npc->GetSize(), // in_size
0.0f, // in_runspeed
in_npc->GetPosition(), // position
in_npc->GetInnateLightType(), // in_light
in_npc->GetTexture(), // in_texture
in_npc->GetHelmTexture(), // 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
(*in_npctypedata)->use_model, // in_usemodel
false, // in_always_aggros_foes
0, // in_heroic_strikethrough
false // in_keeps_sold_items
),
corpse_decay_timer(in_decaytime),
corpse_rez_timer(0),
corpse_delay_timer(RuleI(NPC, CorpseUnlockTimer)),
corpse_graveyard_timer(0),
loot_cooldown_timer(10)
{ {
corpse_graveyard_timer.Disable(); corpse_graveyard_timer.Disable();
@ -184,23 +241,31 @@ Corpse::Corpse(NPC* in_npc, ItemList* in_itemlist, uint32 in_npctypeid, const NP
strcpy(name, in_npc->GetName()); strcpy(name, in_npc->GetName());
for(int count = 0; count < 100; count++) { for(int count = 0; count < 100; count++) {
if ((level >= npcCorpseDecayTimes[count].minlvl) && (level <= npcCorpseDecayTimes[count].maxlvl)) { if (
EQ::ValueWithin(
level,
npcCorpseDecayTimes[count].minlvl,
npcCorpseDecayTimes[count].maxlvl
)
) {
corpse_decay_timer.SetTimer(npcCorpseDecayTimes[count].seconds*1000); corpse_decay_timer.SetTimer(npcCorpseDecayTimes[count].seconds*1000);
break; break;
} }
} }
if(IsEmpty()) {
if (IsEmpty()) {
corpse_decay_timer.SetTimer(RuleI(NPC,EmptyNPCCorpseDecayTimeMS)+1000); corpse_decay_timer.SetTimer(RuleI(NPC,EmptyNPCCorpseDecayTimeMS)+1000);
} }
if(in_npc->HasPrivateCorpse()) { if (in_npc->HasPrivateCorpse()) {
corpse_delay_timer.SetTimer(corpse_decay_timer.GetRemainingTime() + 1000); corpse_delay_timer.SetTimer(corpse_decay_timer.GetRemainingTime() + 1000);
} }
for (int i = 0; i < MAX_LOOTERS; i++){ for (int i = 0; i < MAX_LOOTERS; i++){
allowed_looters[i] = 0; allowed_looters[i] = 0;
} }
rez_experience = 0; rez_experience = 0;
UpdateEquipmentLight(); UpdateEquipmentLight();
@ -210,107 +275,105 @@ Corpse::Corpse(NPC* in_npc, ItemList* in_itemlist, uint32 in_npctypeid, const NP
} }
Corpse::Corpse(Client* client, int32 in_rezexp) : Mob ( Corpse::Corpse(Client* client, int32 in_rezexp) : Mob (
"Unnamed_Corpse", // const char* in_name, "Unnamed_Corpse", // in_name
"", // const char* in_lastname, "", // in_lastname
0, // int32 in_cur_hp, 0, // in_cur_hp
0, // int32 in_max_hp, 0, // in_max_hp
client->GetGender(), // uint8 in_gender, client->GetGender(), // in_gender
client->GetRace(), // uint16 in_race, client->GetRace(), // in_race
client->GetClass(), // uint8 in_class, client->GetClass(), // in_class
BT_Humanoid, // bodyType in_bodytype, BT_Humanoid, // in_bodytype
client->GetDeity(), // uint8 in_deity, client->GetDeity(), // in_deity
client->GetLevel(), // uint8 in_level, client->GetLevel(), // in_level
0, // uint32 in_npctype_id, 0, // in_npctype_id
client->GetSize(), // float in_size, client->GetSize(), // in_size
0, // float in_runspeed, 0, // in_runspeed
client->GetPosition(), client->GetPosition(), // position
client->GetInnateLightType(), // uint8 in_light, - verified for client innate_light value client->GetInnateLightType(), // in_light
client->GetTexture(), // uint8 in_texture, client->GetTexture(), // in_texture
client->GetHelmTexture(), // uint8 in_helmtexture, client->GetHelmTexture(), // in_helmtexture
0, // uint16 in_ac, 0, // in_ac
0, // uint16 in_atk, 0, // in_atk
0, // uint16 in_str, 0, // in_str
0, // uint16 in_sta, 0, // in_sta
0, // uint16 in_dex, 0, // in_dex
0, // uint16 in_agi, 0, // in_agi
0, // uint16 in_int, 0, // in_int
0, // uint16 in_wis, 0, // in_wis
0, // uint16 in_cha, 0, // in_cha
client->GetPP().haircolor, // uint8 in_haircolor, client->GetPP().haircolor, // in_haircolor
client->GetPP().beardcolor, // uint8 in_beardcolor, client->GetPP().beardcolor, // in_beardcolor
client->GetPP().eyecolor1, // uint8 in_eyecolor1, // the eyecolors always seem to be the same, maybe left and right eye? client->GetPP().eyecolor1, // in_eyecolor1
client->GetPP().eyecolor2, // uint8 in_eyecolor2, client->GetPP().eyecolor2, // in_eyecolor2
client->GetPP().hairstyle, // uint8 in_hairstyle, client->GetPP().hairstyle, // in_hairstyle
client->GetPP().face, // uint8 in_luclinface, client->GetPP().face, // in_luclinface
client->GetPP().beard, // uint8 in_beard, client->GetPP().beard, // in_beard
client->GetPP().drakkin_heritage, // uint32 in_drakkin_heritage, client->GetPP().drakkin_heritage, // in_drakkin_heritage
client->GetPP().drakkin_tattoo, // uint32 in_drakkin_tattoo, client->GetPP().drakkin_tattoo, // in_drakkin_tattoo
client->GetPP().drakkin_details, // uint32 in_drakkin_details, client->GetPP().drakkin_details, // in_drakkin_details
EQ::TintProfile(), // uint32 in_armor_tint[_MaterialCount], EQ::TintProfile(), // in_armor_tint
0xff, // uint8 in_aa_title, 0xff, // in_aa_title
0, // uint8 in_see_invis, // see through invis 0, // in_see_invis
0, // uint8 in_see_invis_undead, // see through invis vs. undead 0, // in_see_invis_undead
0, // uint8 in_see_hide, 0, // in_see_hide
0, // uint8 in_see_improved_hide, 0, // in_see_improved_hide
0, // int32 in_hp_regen, 0, // in_hp_regen
0, // int32 in_mana_regen, 0, // in_mana_regen
0, // uint8 in_qglobal, 0, // in_qglobal
0, // uint8 in_maxlevel, 0, // in_maxlevel
0, // uint32 in_scalerate 0, // in_scalerate
0, // uint8 in_armtexture, 0, // in_armtexture
0, // uint8 in_bracertexture, 0, // in_bracertexture
0, // uint8 in_handtexture, 0, // in_handtexture
0, // uint8 in_legtexture, 0, // in_legtexture
0, // uint8 in_feettexture, 0, // in_feettexture
0, // uint8 in_usemodel, 0, // in_usemodel
0, // bool in_always_aggro, false, // in_always_aggro
0 // Int32 in_heroic_strikethrough 0, // in_heroic_strikethrough
), false // in_keeps_sold_items
),
corpse_decay_timer(RuleI(Character, CorpseDecayTimeMS)), corpse_decay_timer(RuleI(Character, CorpseDecayTimeMS)),
corpse_rez_timer(RuleI(Character, CorpseResTimeMS)), corpse_rez_timer(RuleI(Character, CorpseResTimeMS)),
corpse_delay_timer(RuleI(NPC, CorpseUnlockTimer)), corpse_delay_timer(RuleI(NPC, CorpseUnlockTimer)),
corpse_graveyard_timer(RuleI(Zone, GraveyardTimeMS)), corpse_graveyard_timer(RuleI(Zone, GraveyardTimeMS)),
loot_cooldown_timer(10) loot_cooldown_timer(10)
{ {
int i;
PlayerProfile_Struct *pp = &client->GetPP(); PlayerProfile_Struct *pp = &client->GetPP();
EQ::ItemInstance *item = nullptr; EQ::ItemInstance *item = nullptr;
/* Check if Zone has Graveyard First */ if (!zone->HasGraveyard()) {
if(!zone->HasGraveyard()) {
corpse_graveyard_timer.Disable(); corpse_graveyard_timer.Disable();
} }
for (i = 0; i < MAX_LOOTERS; i++){ for (int i = 0; i < MAX_LOOTERS; i++){
allowed_looters[i] = 0; allowed_looters[i] = 0;
} }
if (client->AutoConsentGroupEnabled()) { if (client->AutoConsentGroupEnabled()) {
Group* grp = client->GetGroup(); auto* g = client->GetGroup();
consented_group_id = grp ? grp->GetID() : 0; consented_group_id = g ? g->GetID() : 0;
} }
if (client->AutoConsentRaidEnabled()) { if (client->AutoConsentRaidEnabled()) {
Raid* raid = client->GetRaid(); auto* r = client->GetRaid();
consented_raid_id = raid ? raid->GetID() : 0; consented_raid_id = r ? r->GetID() : 0;
} }
consented_guild_id = client->AutoConsentGuildEnabled() ? client->GuildID() : 0; consented_guild_id = client->AutoConsentGuildEnabled() ? client->GuildID() : 0;
is_corpse_changed = true; is_corpse_changed = true;
rez_experience = in_rezexp; rez_experience = in_rezexp;
can_corpse_be_rezzed = true; can_corpse_be_rezzed = true;
is_player_corpse = true; is_player_corpse = true;
is_locked = false; is_locked = false;
being_looted_by = 0xFFFFFFFF; being_looted_by = 0xFFFFFFFF;
char_id = client->CharacterID(); char_id = client->CharacterID();
corpse_db_id = 0; corpse_db_id = 0;
player_corpse_depop = false; player_corpse_depop = false;
copper = 0; copper = 0;
silver = 0; silver = 0;
gold = 0; gold = 0;
platinum = 0; platinum = 0;
strcpy(corpse_name, pp->name); strcpy(corpse_name, pp->name);
strcpy(name, pp->name); strcpy(name, pp->name);
@ -321,17 +384,22 @@ Corpse::Corpse(Client* client, int32 in_rezexp) : Mob (
SetPlayerKillItemID(0); SetPlayerKillItemID(0);
/* Check Rule to see if we can leave corpses */ /* Check Rule to see if we can leave corpses */
if(!RuleB(Character, LeaveNakedCorpses) || if (
!RuleB(Character, LeaveNakedCorpses) ||
RuleB(Character, LeaveCorpses) && RuleB(Character, LeaveCorpses) &&
GetLevel() >= RuleI(Character, DeathItemLossLevel)) { GetLevel() >= RuleI(Character, DeathItemLossLevel)
) {
// cash // cash
// Let's not move the cash when 'RespawnFromHover = true' && 'client->GetClientVersion() < EQClientSoF' since the client doesn't. // Let's not move the cash when 'RespawnFromHover = true' && 'client->GetClientVersion() < EQClientSoF' since the client doesn't.
// (change to first client that supports 'death hover' mode, if not SoF.) // (change to first client that supports 'death hover' mode, if not SoF.)
if (!RuleB(Character, RespawnFromHover) || client->ClientVersion() < EQ::versions::ClientVersion::SoF) { if (
!RuleB(Character, RespawnFromHover) ||
client->ClientVersion() < EQ::versions::ClientVersion::SoF
) {
SetCash(pp->copper, pp->silver, pp->gold, pp->platinum); SetCash(pp->copper, pp->silver, pp->gold, pp->platinum);
pp->copper = 0; pp->copper = 0;
pp->silver = 0; pp->silver = 0;
pp->gold = 0; pp->gold = 0;
pp->platinum = 0; pp->platinum = 0;
} }
@ -346,12 +414,21 @@ Corpse::Corpse(Client* client, int32 in_rezexp) : Mob (
// ..then regress and process invslot::EQUIPMENT_BEGIN through invslot::EQUIPMENT_END... // ..then regress and process invslot::EQUIPMENT_BEGIN through invslot::EQUIPMENT_END...
// without additional work to database loading of player corpses, this order is not // without additional work to database loading of player corpses, this order is not
// currently preserved and a re-work of this processing loop is not warranted. // currently preserved and a re-work of this processing loop is not warranted.
for (i = EQ::invslot::POSSESSIONS_BEGIN; i <= EQ::invslot::POSSESSIONS_END; ++i) { for (int i = EQ::invslot::POSSESSIONS_BEGIN; i <= EQ::invslot::POSSESSIONS_END; ++i) {
item = client->GetInv().GetItem(i); item = client->GetInv().GetItem(i);
if (item == nullptr) { continue; } if (!item) {
continue;
}
if(!client->IsBecomeNPC() || (client->IsBecomeNPC() && !item->GetItem()->NoRent)) if (
!client->IsBecomeNPC() ||
(
client->IsBecomeNPC() &&
!item->GetItem()->NoRent
)
) {
MoveItemToCorpse(client, item, i, removed_list); MoveItemToCorpse(client, item, i, removed_list);
}
} }
database.TransactionBegin(); database.TransactionBegin();
@ -393,7 +470,7 @@ Corpse::Corpse(Client* client, int32 in_rezexp) : Mob (
UpdateActiveLight(); UpdateActiveLight();
return; return;
} //end "not leaving naked corpses" }
UpdateEquipmentLight(); UpdateEquipmentLight();
UpdateActiveLight(); UpdateActiveLight();
@ -450,73 +527,75 @@ void Corpse::MoveItemToCorpse(Client *client, EQ::ItemInstance *inst, int16 equi
} }
// To be called from LoadFromDBData // To be called from LoadFromDBData
Corpse::Corpse(uint32 in_dbid, uint32 in_charid, const char* in_charname, ItemList* in_itemlist, uint32 in_copper, uint32 in_silver, uint32 in_gold, uint32 in_plat, const glm::vec4& position, float in_size, uint8 in_gender, uint16 in_race, uint8 in_class, uint8 in_deity, uint8 in_level, uint8 in_texture, uint8 in_helmtexture,uint32 in_rezexp, bool wasAtGraveyard) Corpse::Corpse(uint32 in_dbid, uint32 in_charid, const char* in_charname, ItemList* in_itemlist, uint32 in_copper, uint32 in_silver, uint32 in_gold, uint32 in_plat, const glm::vec4& position, float in_size, uint8 in_gender, uint16 in_race, uint8 in_class, uint8 in_deity, uint8 in_level, uint8 in_texture, uint8 in_helmtexture,uint32 in_rezexp, bool wasAtGraveyard) : Mob(
: Mob("Unnamed_Corpse", "Unnamed_Corpse", // in_name
"", "", // in_lastname
0, 0, // in_cur_hp
0, 0, // in_max_hp
in_gender, in_gender, // in_gender
in_race, in_race, // in_race
in_class, in_class, // in_class
BT_Humanoid, BT_Humanoid, // in_bodytype
in_deity, in_deity, // in_deity
in_level, in_level, // in_level
0, 0, // in_npctype_id
in_size, in_size, // in_size
0, 0.0f, // in_runspeed
position, position, // position
0, // verified for client innate_light value 0, // in_light
in_texture, in_texture, // in_texture
in_helmtexture, in_helmtexture, // in_helmtexture
0, 0, // in_ac
0, 0, // in_atk
0, 0, // in_str
0, 0, // in_sta
0, 0, // in_dex
0, 0, // in_agi
0, 0, // in_int
0, 0, // in_wis
0, 0, // in_cha
0, 0, // in_haircolor
0, 0, // in_beardcolor
0, 0, // in_eyecolor1
0, 0, // in_eyecolor2
0, 0, // in_hairstyle
0, 0, // in_luclinface
0, 0, // in_beard
0, 0, // in_drakkin_heritage
0, 0, // in_drakkin_tattoo
0, 0, // in_drakkin_details
EQ::TintProfile(), EQ::TintProfile(), // in_armor_tint
0xff, 0xFF, // in_aa_title
0, 0, // in_see_invis
0, 0, // in_see_invis_undead
0, 0, // in_see_hide
0, 0, // in_see_improved_hide
0, 0, // in_hp_regen
0, 0, // in_mana_regen
0, 0, // in_qglobal
0, 0, // in_maxlevel
0, 0, // in_scalerate
0, 0, // in_armtexture
0, 0, // in_bracertexture
0, 0, // in_handtexture
0, 0, // in_legtexture
0, 0, // in_feettexture
0, 0, // in_usemodel
0, false, // in_always_aggros_foes
false), 0, // in_heroic_strikethrough
false // in_keeps_sold_items
),
corpse_decay_timer(RuleI(Character, CorpseDecayTimeMS)), corpse_decay_timer(RuleI(Character, CorpseDecayTimeMS)),
corpse_rez_timer(RuleI(Character, CorpseResTimeMS)), corpse_rez_timer(RuleI(Character, CorpseResTimeMS)),
corpse_delay_timer(RuleI(NPC, CorpseUnlockTimer)), corpse_delay_timer(RuleI(NPC, CorpseUnlockTimer)),
corpse_graveyard_timer(RuleI(Zone, GraveyardTimeMS)), corpse_graveyard_timer(RuleI(Zone, GraveyardTimeMS)),
loot_cooldown_timer(10) loot_cooldown_timer(10)
{ {
LoadPlayerCorpseDecayTime(in_dbid); LoadPlayerCorpseDecayTime(in_dbid);
if (!zone->HasGraveyard() || wasAtGraveyard) if (!zone->HasGraveyard() || wasAtGraveyard) {
corpse_graveyard_timer.Disable(); corpse_graveyard_timer.Disable();
}
is_corpse_changed = false; is_corpse_changed = false;
is_player_corpse = true; is_player_corpse = true;
@ -541,6 +620,7 @@ false),
for (int i = 0; i < MAX_LOOTERS; i++){ for (int i = 0; i < MAX_LOOTERS; i++){
allowed_looters[i] = 0; allowed_looters[i] = 0;
} }
SetPlayerKillItemID(0); SetPlayerKillItemID(0);
UpdateEquipmentLight(); UpdateEquipmentLight();

View File

@ -32,13 +32,64 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
class Zone; class Zone;
Encounter::Encounter(const char* enc_name) Encounter::Encounter(const char *enc_name) : Mob(
:Mob nullptr, // in_name
( nullptr, // in_lastname
nullptr, nullptr, 0, 0, 0, INVISIBLE_MAN, 0, BT_NoTarget, 0, 0, 0, 0, 0, glm::vec4(0,0,0,0), 0, 0, 0, 0, // in_cur_hp
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, EQ::TintProfile(), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false 0, // in_max_hp
) MALE, // in_gender
{ INVISIBLE_MAN, // in_race
0, // in_class
BT_NoTarget, // in_bodytype
0, // in_deity
0, // in_level
0, // in_npcype_id
0, // in_size
0, // in_runspeed
glm::vec4(0, 0, 0, 0), // position
0, // in_light
0, // in_texture
0, // 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
0, // 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
) {
encounter_name[0] = 0; encounter_name[0] = 0;
strn0cpy(encounter_name, enc_name, 64); strn0cpy(encounter_name, enc_name, 64);
remove_me = false; remove_me = false;

View File

@ -8,7 +8,7 @@ void command_modifynpcstat(Client *c, const Seperator *sep)
ListModifyNPCStatMap(c); ListModifyNPCStatMap(c);
return; return;
} }
if (!c->GetTarget() || !c->GetTarget()->IsNPC()) { if (!c->GetTarget() || !c->GetTarget()->IsNPC()) {
c->Message(Chat::White, "You must target an NPC to use this command."); c->Message(Chat::White, "You must target an NPC to use this command.");
return; return;
@ -76,6 +76,7 @@ std::map<std::string, std::string> GetModifyNPCStatMap()
{ "hp_regen_per_second", "HP Regen Per Second" }, { "hp_regen_per_second", "HP Regen Per Second" },
{ "int", "Intelligence" }, { "int", "Intelligence" },
{ "_int", "Intelligence" }, { "_int", "Intelligence" },
{ "keeps_sold_items", "Keeps Sold Items" },
{ "level", "Level" }, { "level", "Level" },
{ "loottable_id", "Loottable ID" }, { "loottable_id", "Loottable ID" },
{ "mana_regen", "Mana Regen" }, { "mana_regen", "Mana Regen" },

View File

@ -1491,6 +1491,22 @@ void command_npcedit(Client *c, const Seperator *sep)
); );
return; return;
} }
} else if (!strcasecmp(sep->arg[1], "keeps_sold_items")) {
if (sep->IsNumber(2)) {
auto keeps_sold_items = static_cast<uint8_t>(std::stoul(sep->arg[2]));
n.keeps_sold_items = keeps_sold_items;
d = fmt::format(
"{} will {} Keep Sold Items.",
npc_id_string,
keeps_sold_items ? "now" : "no longer"
);
} else {
c->Message(
Chat::White,
"Usage: #npcedit keeps_sold_items [Flag] - Sets an NPC's Keeps Sold Items Flag [0 = False, 1 = True]"
);
return;
}
} else if (!strcasecmp(sep->arg[1], "setanimation")) { } else if (!strcasecmp(sep->arg[1], "setanimation")) {
if (sep->IsNumber(2)) { if (sep->IsNumber(2)) {
auto animation_id = std::stoul(sep->arg[2]); auto animation_id = std::stoul(sep->arg[2]);
@ -1760,6 +1776,10 @@ void SendNPCEditSubCommands(Client *c)
Chat::White, Chat::White,
"Usage: #npcedit exp_mod [Modifier] - Sets an NPC's Experience Modifier [50 = 50%, 100 = 100%, 200 = 200%]" "Usage: #npcedit exp_mod [Modifier] - Sets an NPC's Experience Modifier [50 = 50%, 100 = 100%, 200 = 200%]"
); );
c->Message(
Chat::White,
"Usage: #npcedit keeps_sold_items [Flag] - Sets an NPC's Keeps Sold Items Flag [0 = False, 1 = True]"
);
c->Message( c->Message(
Chat::White, Chat::White,
"Usage: #npcedit setanimation [Animation ID] - Sets an NPC's Animation on Spawn (Stored in spawn2 table)" "Usage: #npcedit setanimation [Animation ID] - Sets an NPC's Animation on Spawn (Stored in spawn2 table)"

View File

@ -691,6 +691,16 @@ void Lua_NPC::SendPayload(int payload_id, std::string payload_value) {
self->SendPayload(payload_id, payload_value); self->SendPayload(payload_id, payload_value);
} }
bool Lua_NPC::GetKeepsSoldItems() {
Lua_Safe_Call_Bool();
return self->GetKeepsSoldItems();
}
void Lua_NPC::SetKeepsSoldItems(bool keeps_sold_items) {
Lua_Safe_Call_Void();
self->SetKeepsSoldItems(keeps_sold_items);
}
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<>())
@ -736,6 +746,7 @@ luabind::scope lua_register_npc() {
.def("GetGuardPointZ", (float(Lua_NPC::*)(void))&Lua_NPC::GetGuardPointZ) .def("GetGuardPointZ", (float(Lua_NPC::*)(void))&Lua_NPC::GetGuardPointZ)
.def("GetHealScale", (float(Lua_NPC::*)(void))&Lua_NPC::GetHealScale) .def("GetHealScale", (float(Lua_NPC::*)(void))&Lua_NPC::GetHealScale)
.def("GetItemIDBySlot", (uint32(Lua_NPC::*)(uint16))&Lua_NPC::GetItemIDBySlot) .def("GetItemIDBySlot", (uint32(Lua_NPC::*)(uint16))&Lua_NPC::GetItemIDBySlot)
.def("GetKeepsSoldItems", (bool(Lua_NPC::*)(void))&Lua_NPC::GetKeepsSoldItems)
.def("GetLootList", (Lua_NPC_Loot_List(Lua_NPC::*)(lua_State* L))&Lua_NPC::GetLootList) .def("GetLootList", (Lua_NPC_Loot_List(Lua_NPC::*)(lua_State* L))&Lua_NPC::GetLootList)
.def("GetLoottableID", (int(Lua_NPC::*)(void))&Lua_NPC::GetLoottableID) .def("GetLoottableID", (int(Lua_NPC::*)(void))&Lua_NPC::GetLoottableID)
.def("GetMaxDMG", (uint32(Lua_NPC::*)(void))&Lua_NPC::GetMaxDMG) .def("GetMaxDMG", (uint32(Lua_NPC::*)(void))&Lua_NPC::GetMaxDMG)
@ -805,6 +816,7 @@ luabind::scope lua_register_npc() {
.def("SetFollowID", (void(Lua_NPC::*)(int))&Lua_NPC::SetFollowID) .def("SetFollowID", (void(Lua_NPC::*)(int))&Lua_NPC::SetFollowID)
.def("SetGold", (void(Lua_NPC::*)(uint32))&Lua_NPC::SetGold) .def("SetGold", (void(Lua_NPC::*)(uint32))&Lua_NPC::SetGold)
.def("SetGrid", (void(Lua_NPC::*)(int))&Lua_NPC::SetGrid) .def("SetGrid", (void(Lua_NPC::*)(int))&Lua_NPC::SetGrid)
.def("SetKeepsSoldItems", (void(Lua_NPC::*)(bool))&Lua_NPC::SetKeepsSoldItems)
.def("SetNPCFactionID", (void(Lua_NPC::*)(int))&Lua_NPC::SetNPCFactionID) .def("SetNPCFactionID", (void(Lua_NPC::*)(int))&Lua_NPC::SetNPCFactionID)
.def("SetPetSpellID", (void(Lua_NPC::*)(int))&Lua_NPC::SetPetSpellID) .def("SetPetSpellID", (void(Lua_NPC::*)(int))&Lua_NPC::SetPetSpellID)
.def("SetPlatinum", (void(Lua_NPC::*)(uint32))&Lua_NPC::SetPlatinum) .def("SetPlatinum", (void(Lua_NPC::*)(uint32))&Lua_NPC::SetPlatinum)

View File

@ -159,6 +159,8 @@ public:
float GetNPCStat(std::string stat); float GetNPCStat(std::string stat);
void SendPayload(int payload_id); void SendPayload(int payload_id);
void SendPayload(int payload_id, std::string payload_value); void SendPayload(int payload_id, std::string payload_value);
bool GetKeepsSoldItems();
void SetKeepsSoldItems(bool keeps_sold_items);
}; };
#endif #endif

View File

@ -98,6 +98,7 @@ Mob::Mob(
uint16 in_usemodel, uint16 in_usemodel,
bool in_always_aggro, bool in_always_aggro,
int32 in_heroic_strikethrough, int32 in_heroic_strikethrough,
bool in_keeps_sold_items,
int64 in_hp_regen_per_second int64 in_hp_regen_per_second
) : ) :
attack_timer(2000), attack_timer(2000),
@ -252,39 +253,40 @@ Mob::Mob(
aa_title = 0xFF; aa_title = 0xFF;
} }
AC = in_ac; AC = in_ac;
ATK = in_atk; ATK = in_atk;
STR = in_str; STR = in_str;
STA = in_sta; STA = in_sta;
DEX = in_dex; DEX = in_dex;
AGI = in_agi; AGI = in_agi;
INT = in_int; INT = in_int;
WIS = in_wis; WIS = in_wis;
CHA = in_cha; CHA = in_cha;
MR = CR = FR = DR = PR = Corrup = PhR = 0; MR = CR = FR = DR = PR = Corrup = PhR = 0;
ExtraHaste = 0; ExtraHaste = 0;
bEnraged = false; bEnraged = false;
current_mana = 0; current_mana = 0;
max_mana = 0; max_mana = 0;
hp_regen = in_hp_regen; hp_regen = in_hp_regen;
hp_regen_per_second = in_hp_regen_per_second; hp_regen_per_second = in_hp_regen_per_second;
mana_regen = in_mana_regen; mana_regen = in_mana_regen;
ooc_regen = RuleI(NPC, OOCRegen); //default Out of Combat Regen ooc_regen = RuleI(NPC, OOCRegen); //default Out of Combat Regen
maxlevel = in_maxlevel; maxlevel = in_maxlevel;
scalerate = in_scalerate; scalerate = in_scalerate;
invisible = 0; invisible = 0;
invisible_undead = 0; invisible_undead = 0;
invisible_animals = 0; invisible_animals = 0;
sneaking = false; sneaking = false;
hidden = false; hidden = false;
improved_hidden = false; improved_hidden = false;
invulnerable = false; invulnerable = false;
IsFullHP = (current_hp == max_hp); IsFullHP = (current_hp == max_hp);
qglobal = 0; qglobal = 0;
spawned = false; spawned = false;
rare_spawn = false; rare_spawn = false;
always_aggro = in_always_aggro; always_aggro = in_always_aggro;
heroic_strikethrough = in_heroic_strikethrough; heroic_strikethrough = in_heroic_strikethrough;
keeps_sold_items = in_keeps_sold_items;
InitializeBuffSlots(); InitializeBuffSlots();

View File

@ -165,6 +165,7 @@ public:
uint16 in_usemodel, uint16 in_usemodel,
bool in_always_aggros_foes, bool in_always_aggros_foes,
int32 in_heroic_strikethrough, int32 in_heroic_strikethrough,
bool keeps_sold_items,
int64 in_hp_regen_per_second = 0 int64 in_hp_regen_per_second = 0
); );
virtual ~Mob(); virtual ~Mob();
@ -653,6 +654,8 @@ public:
bool IsControllableBoat() const; bool IsControllableBoat() const;
inline const bool AlwaysAggro() const { return always_aggro; } inline const bool AlwaysAggro() const { return always_aggro; }
inline int32 GetHeroicStrikethrough() const { return heroic_strikethrough; } inline int32 GetHeroicStrikethrough() const { return heroic_strikethrough; }
inline const bool GetKeepsSoldItems() const { return keeps_sold_items; }
inline void SetKeepsSoldItems(bool in_keeps_sold_items) { keeps_sold_items = in_keeps_sold_items; }
void CopyHateList(Mob* to); void CopyHateList(Mob* to);
@ -1531,6 +1534,7 @@ protected:
bool no_target_hotkey; bool no_target_hotkey;
bool rare_spawn; bool rare_spawn;
int32 heroic_strikethrough; int32 heroic_strikethrough;
bool keeps_sold_items;
uint32 m_PlayerState; uint32 m_PlayerState;
uint32 GetPlayerState() { return m_PlayerState; } uint32 GetPlayerState() { return m_PlayerState; }

View File

@ -121,7 +121,8 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi
npc_type_data->use_model, npc_type_data->use_model,
npc_type_data->always_aggro, npc_type_data->always_aggro,
npc_type_data->hp_regen_per_second, npc_type_data->hp_regen_per_second,
npc_type_data->heroic_strikethrough npc_type_data->heroic_strikethrough,
npc_type_data->keeps_sold_items
), ),
attacked_timer(CombatEventTimer_expire), attacked_timer(CombatEventTimer_expire),
swarm_timer(100), swarm_timer(100),
@ -210,12 +211,13 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi
LevelScale(); LevelScale();
} }
base_damage = round((max_dmg - min_dmg) / 1.9); base_damage = round((max_dmg - min_dmg) / 1.9);
min_damage = min_dmg - round(base_damage / 10.0); min_damage = min_dmg - round(base_damage / 10.0);
accuracy_rating = npc_type_data->accuracy_rating; accuracy_rating = npc_type_data->accuracy_rating;
avoidance_rating = npc_type_data->avoidance_rating; avoidance_rating = npc_type_data->avoidance_rating;
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;
// 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;
@ -2672,6 +2674,10 @@ void NPC::ModifyNPCStat(std::string stat, std::string value)
heroic_strikethrough = atoi(value.c_str()); heroic_strikethrough = atoi(value.c_str());
return; return;
} }
else if (stat_lower == "keeps_sold_items") {
SetKeepsSoldItems(Strings::ToBool(value));
return;
}
} }
float NPC::GetNPCStat(std::string stat) float NPC::GetNPCStat(std::string stat)
@ -2813,6 +2819,9 @@ float NPC::GetNPCStat(std::string stat)
else if (stat_lower == "heroic_strikethrough") { else if (stat_lower == "heroic_strikethrough") {
return heroic_strikethrough; return heroic_strikethrough;
} }
else if (stat_lower == "keeps_sold_items") {
return keeps_sold_items;
}
//default values //default values
else if (stat_lower == "default_ac") { else if (stat_lower == "default_ac") {
return default_ac; return default_ac;

View File

@ -680,6 +680,16 @@ void Perl_NPC_SendPayload(NPC* self, int payload_id, std::string payload_value)
self->SendPayload(payload_id, payload_value); self->SendPayload(payload_id, payload_value);
} }
bool Perl_NPC_GetKeepsSoldItems(NPC* self)
{
return self->GetKeepsSoldItems();
}
void Perl_NPC_SetKeepsSoldItems(NPC* self, bool keeps_sold_items)
{
self->SetKeepsSoldItems(keeps_sold_items);
}
void perl_register_npc() void perl_register_npc()
{ {
perl::interpreter perl(PERL_GET_THX); perl::interpreter perl(PERL_GET_THX);
@ -731,6 +741,7 @@ void perl_register_npc()
package.add("GetGuardPointZ", &Perl_NPC_GetGuardPointZ); package.add("GetGuardPointZ", &Perl_NPC_GetGuardPointZ);
package.add("GetHealScale", &Perl_NPC_GetHealScale); package.add("GetHealScale", &Perl_NPC_GetHealScale);
package.add("GetItemIDBySlot", &Perl_NPC_GetItemIDBySlot); package.add("GetItemIDBySlot", &Perl_NPC_GetItemIDBySlot);
package.add("GetKeepsSoldItems", &Perl_NPC_GetKeepsSoldItems);
package.add("GetLootList", &Perl_NPC_GetLootList); package.add("GetLootList", &Perl_NPC_GetLootList);
package.add("GetLoottableID", &Perl_NPC_GetLoottableID); package.add("GetLoottableID", &Perl_NPC_GetLoottableID);
package.add("GetMaxDMG", &Perl_NPC_GetMaxDMG); package.add("GetMaxDMG", &Perl_NPC_GetMaxDMG);
@ -799,6 +810,7 @@ void perl_register_npc()
package.add("SendPayload", (void(*)(NPC*, int))&Perl_NPC_SendPayload); package.add("SendPayload", (void(*)(NPC*, int))&Perl_NPC_SendPayload);
package.add("SendPayload", (void(*)(NPC*, int, std::string))&Perl_NPC_SendPayload); package.add("SendPayload", (void(*)(NPC*, int, std::string))&Perl_NPC_SendPayload);
package.add("SetCopper", &Perl_NPC_SetCopper); package.add("SetCopper", &Perl_NPC_SetCopper);
package.add("SetKeepsSoldItems", &Perl_NPC_SetKeepsSoldItems);
package.add("SetGold", &Perl_NPC_SetGold); package.add("SetGold", &Perl_NPC_SetGold);
package.add("SetGrid", &Perl_NPC_SetGrid); package.add("SetGrid", &Perl_NPC_SetGrid);
package.add("SetNPCFactionID", &Perl_NPC_SetNPCFactionID); package.add("SetNPCFactionID", &Perl_NPC_SetNPCFactionID);

View File

@ -2134,6 +2134,7 @@ const NPCType *ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load
t->hp_regen_per_second = n.hp_regen_per_second; t->hp_regen_per_second = n.hp_regen_per_second;
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;
// 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.

View File

@ -34,124 +34,125 @@ spawn2 mediumblob, npcs mediumblob, npc_loot mediumblob, gmspawntype mediumblob,
struct NPCType struct NPCType
{ {
char name[64]; char name[64];
char lastname[64]; char lastname[64];
int64 current_hp; int64 current_hp;
int64 max_hp; int64 max_hp;
float size; float size;
float runspeed; float runspeed;
uint8 gender; uint8 gender;
uint16 race; uint16 race;
uint8 class_; uint8 class_;
uint8 bodytype; // added for targettype support uint8 bodytype; // added for targettype support
uint32 deity; //not loaded from DB uint32 deity; //not loaded from DB
uint8 level; uint8 level;
uint32 npc_id; uint32 npc_id;
uint8 texture; uint8 texture;
uint8 helmtexture; uint8 helmtexture;
uint32 herosforgemodel; uint32 herosforgemodel;
uint32 loottable_id; uint32 loottable_id;
uint32 npc_spells_id; uint32 npc_spells_id;
uint32 npc_spells_effects_id; uint32 npc_spells_effects_id;
int32 npc_faction_id; int32 npc_faction_id;
int32 faction_amount; // faction association magnitude, will use primary faction int32 faction_amount; // faction association magnitude, will use primary faction
uint32 merchanttype; uint32 merchanttype;
uint32 alt_currency_type; uint32 alt_currency_type;
uint32 adventure_template; uint32 adventure_template;
uint32 trap_template; uint32 trap_template;
uint8 light; uint8 light;
uint32 AC; uint32 AC;
uint64 Mana; //not loaded from DB uint64 Mana; //not loaded from DB
uint32 ATK; //not loaded from DB uint32 ATK; //not loaded from DB
uint32 STR; uint32 STR;
uint32 STA; uint32 STA;
uint32 DEX; uint32 DEX;
uint32 AGI; uint32 AGI;
uint32 INT; uint32 INT;
uint32 WIS; uint32 WIS;
uint32 CHA; uint32 CHA;
int32 MR; int32 MR;
int32 FR; int32 FR;
int32 CR; int32 CR;
int32 PR; int32 PR;
int32 DR; int32 DR;
int32 Corrup; int32 Corrup;
int32 PhR; int32 PhR;
uint8 haircolor; uint8 haircolor;
uint8 beardcolor; uint8 beardcolor;
uint8 eyecolor1; // the eyecolors always seem to be the same, maybe left and right eye? uint8 eyecolor1; // the eyecolors always seem to be the same, maybe left and right eye?
uint8 eyecolor2; uint8 eyecolor2;
uint8 hairstyle; uint8 hairstyle;
uint8 luclinface; // uint8 luclinface; //
uint8 beard; // uint8 beard; //
uint32 drakkin_heritage; uint32 drakkin_heritage;
uint32 drakkin_tattoo; uint32 drakkin_tattoo;
uint32 drakkin_details; uint32 drakkin_details;
EQ::TintProfile armor_tint; EQ::TintProfile armor_tint;
uint32 min_dmg; uint32 min_dmg;
uint32 max_dmg; uint32 max_dmg;
uint32 charm_ac; uint32 charm_ac;
uint32 charm_min_dmg; uint32 charm_min_dmg;
uint32 charm_max_dmg; uint32 charm_max_dmg;
int charm_attack_delay; int charm_attack_delay;
int charm_accuracy_rating; int charm_accuracy_rating;
int charm_avoidance_rating; int charm_avoidance_rating;
int charm_atk; int charm_atk;
int16 attack_count; int16 attack_count;
char special_abilities[512]; char special_abilities[512];
uint16 d_melee_texture1; uint16 d_melee_texture1;
uint16 d_melee_texture2; uint16 d_melee_texture2;
char ammo_idfile[30]; char ammo_idfile[30];
uint8 prim_melee_type; uint8 prim_melee_type;
uint8 sec_melee_type; uint8 sec_melee_type;
uint8 ranged_type; uint8 ranged_type;
int64 hp_regen; int64 hp_regen;
int64 hp_regen_per_second; int64 hp_regen_per_second;
int64 mana_regen; int64 mana_regen;
int32 aggroradius; // added for AI improvement - neotokyo int32 aggroradius; // added for AI improvement - neotokyo
int32 assistradius; // assist radius, defaults to aggroradis if not set int32 assistradius; // assist radius, defaults to aggroradis if not set
uint16 see_invis; // See Invis flag added uint16 see_invis; // See Invis flag added
uint16 see_invis_undead; // See Invis vs. Undead flag added uint16 see_invis_undead; // See Invis vs. Undead flag added
bool see_hide; bool see_hide;
bool see_improved_hide; bool see_improved_hide;
bool qglobal; bool qglobal;
bool npc_aggro; bool npc_aggro;
uint8 spawn_limit; //only this many may be in zone at a time (0=no limit) uint8 spawn_limit; //only this many may be in zone at a time (0=no limit)
uint8 mount_color; //only used by horse class uint8 mount_color; //only used by horse class
float attack_speed; //%+- on attack delay of the mob. float attack_speed; //%+- on attack delay of the mob.
int attack_delay; //delay between attacks in ms int attack_delay; //delay between attacks in ms
int accuracy_rating; // flat bonus before mods int accuracy_rating; // flat bonus before mods
int avoidance_rating; // flat bonus before mods int avoidance_rating; // flat bonus before mods
bool findable; //can be found with find command bool findable; //can be found with find command
bool trackable; bool trackable;
int16 slow_mitigation; int16 slow_mitigation;
uint8 maxlevel; uint8 maxlevel;
uint32 scalerate; uint32 scalerate;
bool private_corpse; bool private_corpse;
bool unique_spawn_by_name; bool unique_spawn_by_name;
bool underwater; bool underwater;
uint32 emoteid; uint32 emoteid;
float spellscale; float spellscale;
float healscale; float healscale;
bool no_target_hotkey; bool no_target_hotkey;
bool raid_target; bool raid_target;
uint8 armtexture; uint8 armtexture;
uint8 bracertexture; uint8 bracertexture;
uint8 handtexture; uint8 handtexture;
uint8 legtexture; uint8 legtexture;
uint8 feettexture; uint8 feettexture;
bool ignore_despawn; bool ignore_despawn;
bool show_name; // should default on bool show_name; // should default on
bool untargetable; bool untargetable;
bool skip_global_loot; bool skip_global_loot;
bool rare_spawn; bool rare_spawn;
bool skip_auto_scale; // just so it doesn't mess up bots or mercs, probably should add to DB too just in case bool skip_auto_scale; // just so it doesn't mess up bots or mercs, probably should add to DB too just in case
int8 stuck_behavior; int8 stuck_behavior;
uint16 use_model; uint16 use_model;
int8 flymode; int8 flymode;
bool always_aggro; bool always_aggro;
int exp_mod; int exp_mod;
int heroic_strikethrough; int heroic_strikethrough;
bool keeps_sold_items;
}; };
namespace player_lootitem { namespace player_lootitem {