[Feature] Enable spawn attribute for NPCTintID (#4871)

* Add scripting for NPCTintIndex

* AddNPCTintID

Add NPCTintID to spawn logic

* Update base_npc_types_repository.h

* Correct version.h
This commit is contained in:
Mitch Freeman 2025-05-14 22:26:35 -03:00 committed by GitHub
parent 1221e88d92
commit 20da490bda
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 106 additions and 34 deletions

View File

@ -7087,6 +7087,18 @@ CREATE INDEX `idx_expire_at` ON `instance_list` (`expire_at`);
)", )",
.content_schema_update = false .content_schema_update = false
}, },
ManifestEntry{
.version = 9322,
.description = "2025_04_24_add_npc_tint_id.sql",
.check = "SHOW COLUMNS FROM `npc_types` LIKE 'npc_tint_id'",
.condition = "empty",
.match = "",
.sql = R"(
ALTER TABLE `npc_types`
ADD COLUMN `npc_tint_id` SMALLINT UNSIGNED NULL DEFAULT '0' AFTER `multiquest_enabled`;
)",
.content_schema_update = true
}
// -- template; copy/paste this when you need to create a new entry // -- template; copy/paste this when you need to create a new entry
// ManifestEntry{ // ManifestEntry{
// .version = 9228, // .version = 9228,

View File

@ -325,6 +325,7 @@ union
bool trader; bool trader;
bool buyer; bool buyer;
bool untargetable; bool untargetable;
uint32 npc_tint_id;
}; };
struct PlayerState_Struct { struct PlayerState_Struct {

View File

@ -4839,13 +4839,13 @@ namespace RoF2
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->petOwnerId); VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->petOwnerId);
VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // FindBits MQ2 name VARSTRUCT_ENCODE_TYPE(uint8, Buffer, 0); // FindBits MQ2 name
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->PlayerState); VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->PlayerState);
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // NpcTintIndex VARSTRUCT_ENCODE_TYPE(uint32, Buffer, emu->npc_tint_id); // NpcTintIndex
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // PrimaryTintIndex VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // PrimaryTintIndex
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // SecondaryTintIndex VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0); // SecondaryTintIndex
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0xffffffff); // These do something with OP_WeaponEquip1 VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0xffffffff); // These do something with OP_WeaponEquip1
VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0xffffffff); // ^ VARSTRUCT_ENCODE_TYPE(uint32, Buffer, 0xffffffff); // ^
if ((emu->NPC == 0) || (emu->race <= Race::Gnome) || (emu->race == Race::Iksar) || if ((emu->NPC == 0) || (emu->race <= Race::Gnome) || (emu->race == Race::Iksar) ||
(emu->race == Race::VahShir) || (emu->race == Race::Froglok2) || (emu->race == Race::Drakkin) (emu->race == Race::VahShir) || (emu->race == Race::Froglok2) || (emu->race == Race::Drakkin)

View File

@ -149,6 +149,7 @@ public:
uint8_t keeps_sold_items; uint8_t keeps_sold_items;
uint8_t is_parcel_merchant; uint8_t is_parcel_merchant;
uint8_t multiquest_enabled; uint8_t multiquest_enabled;
uint16_t npc_tint_id;
}; };
static std::string PrimaryKey() static std::string PrimaryKey()
@ -289,6 +290,7 @@ public:
"keeps_sold_items", "keeps_sold_items",
"is_parcel_merchant", "is_parcel_merchant",
"multiquest_enabled", "multiquest_enabled",
"npc_tint_id",
}; };
} }
@ -425,6 +427,7 @@ public:
"keeps_sold_items", "keeps_sold_items",
"is_parcel_merchant", "is_parcel_merchant",
"multiquest_enabled", "multiquest_enabled",
"npc_tint_id",
}; };
} }
@ -595,6 +598,7 @@ public:
e.keeps_sold_items = 1; e.keeps_sold_items = 1;
e.is_parcel_merchant = 0; e.is_parcel_merchant = 0;
e.multiquest_enabled = 0; e.multiquest_enabled = 0;
e.npc_tint_id = 0;
return e; return e;
} }
@ -761,6 +765,7 @@ public:
e.keeps_sold_items = row[127] ? static_cast<uint8_t>(strtoul(row[127], nullptr, 10)) : 1; e.keeps_sold_items = row[127] ? static_cast<uint8_t>(strtoul(row[127], nullptr, 10)) : 1;
e.is_parcel_merchant = row[128] ? static_cast<uint8_t>(strtoul(row[128], nullptr, 10)) : 0; e.is_parcel_merchant = row[128] ? static_cast<uint8_t>(strtoul(row[128], nullptr, 10)) : 0;
e.multiquest_enabled = row[129] ? static_cast<uint8_t>(strtoul(row[129], nullptr, 10)) : 0; e.multiquest_enabled = row[129] ? static_cast<uint8_t>(strtoul(row[129], nullptr, 10)) : 0;
e.npc_tint_id = row[130] ? static_cast<uint16_t>(strtoul(row[130], nullptr, 10)) : 0;
return e; return e;
} }
@ -923,6 +928,7 @@ public:
v.push_back(columns[127] + " = " + std::to_string(e.keeps_sold_items)); v.push_back(columns[127] + " = " + std::to_string(e.keeps_sold_items));
v.push_back(columns[128] + " = " + std::to_string(e.is_parcel_merchant)); v.push_back(columns[128] + " = " + std::to_string(e.is_parcel_merchant));
v.push_back(columns[129] + " = " + std::to_string(e.multiquest_enabled)); v.push_back(columns[129] + " = " + std::to_string(e.multiquest_enabled));
v.push_back(columns[130] + " = " + std::to_string(e.npc_tint_id));
auto results = db.QueryDatabase( auto results = db.QueryDatabase(
fmt::format( fmt::format(
@ -1074,6 +1080,7 @@ public:
v.push_back(std::to_string(e.keeps_sold_items)); v.push_back(std::to_string(e.keeps_sold_items));
v.push_back(std::to_string(e.is_parcel_merchant)); v.push_back(std::to_string(e.is_parcel_merchant));
v.push_back(std::to_string(e.multiquest_enabled)); v.push_back(std::to_string(e.multiquest_enabled));
v.push_back(std::to_string(e.npc_tint_id));
auto results = db.QueryDatabase( auto results = db.QueryDatabase(
fmt::format( fmt::format(
@ -1233,6 +1240,7 @@ public:
v.push_back(std::to_string(e.keeps_sold_items)); v.push_back(std::to_string(e.keeps_sold_items));
v.push_back(std::to_string(e.is_parcel_merchant)); v.push_back(std::to_string(e.is_parcel_merchant));
v.push_back(std::to_string(e.multiquest_enabled)); v.push_back(std::to_string(e.multiquest_enabled));
v.push_back(std::to_string(e.npc_tint_id));
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
} }
@ -1396,6 +1404,7 @@ public:
e.keeps_sold_items = row[127] ? static_cast<uint8_t>(strtoul(row[127], nullptr, 10)) : 1; e.keeps_sold_items = row[127] ? static_cast<uint8_t>(strtoul(row[127], nullptr, 10)) : 1;
e.is_parcel_merchant = row[128] ? static_cast<uint8_t>(strtoul(row[128], nullptr, 10)) : 0; e.is_parcel_merchant = row[128] ? static_cast<uint8_t>(strtoul(row[128], nullptr, 10)) : 0;
e.multiquest_enabled = row[129] ? static_cast<uint8_t>(strtoul(row[129], nullptr, 10)) : 0; e.multiquest_enabled = row[129] ? static_cast<uint8_t>(strtoul(row[129], nullptr, 10)) : 0;
e.npc_tint_id = row[130] ? static_cast<uint16_t>(strtoul(row[130], nullptr, 10)) : 0;
all_entries.push_back(e); all_entries.push_back(e);
} }
@ -1550,6 +1559,7 @@ public:
e.keeps_sold_items = row[127] ? static_cast<uint8_t>(strtoul(row[127], nullptr, 10)) : 1; e.keeps_sold_items = row[127] ? static_cast<uint8_t>(strtoul(row[127], nullptr, 10)) : 1;
e.is_parcel_merchant = row[128] ? static_cast<uint8_t>(strtoul(row[128], nullptr, 10)) : 0; e.is_parcel_merchant = row[128] ? static_cast<uint8_t>(strtoul(row[128], nullptr, 10)) : 0;
e.multiquest_enabled = row[129] ? static_cast<uint8_t>(strtoul(row[129], nullptr, 10)) : 0; e.multiquest_enabled = row[129] ? static_cast<uint8_t>(strtoul(row[129], nullptr, 10)) : 0;
e.npc_tint_id = row[130] ? static_cast<uint16_t>(strtoul(row[130], nullptr, 10)) : 0;
all_entries.push_back(e); all_entries.push_back(e);
} }
@ -1754,6 +1764,7 @@ public:
v.push_back(std::to_string(e.keeps_sold_items)); v.push_back(std::to_string(e.keeps_sold_items));
v.push_back(std::to_string(e.is_parcel_merchant)); v.push_back(std::to_string(e.is_parcel_merchant));
v.push_back(std::to_string(e.multiquest_enabled)); v.push_back(std::to_string(e.multiquest_enabled));
v.push_back(std::to_string(e.npc_tint_id));
auto results = db.QueryDatabase( auto results = db.QueryDatabase(
fmt::format( fmt::format(
@ -1906,6 +1917,7 @@ public:
v.push_back(std::to_string(e.keeps_sold_items)); v.push_back(std::to_string(e.keeps_sold_items));
v.push_back(std::to_string(e.is_parcel_merchant)); v.push_back(std::to_string(e.is_parcel_merchant));
v.push_back(std::to_string(e.multiquest_enabled)); v.push_back(std::to_string(e.multiquest_enabled));
v.push_back(std::to_string(e.npc_tint_id));
insert_chunks.push_back("(" + Strings::Implode(",", v) + ")"); insert_chunks.push_back("(" + Strings::Implode(",", v) + ")");
} }

View File

@ -42,7 +42,7 @@
* Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt * Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
*/ */
#define CURRENT_BINARY_DATABASE_VERSION 9321 #define CURRENT_BINARY_DATABASE_VERSION 9322
#define CURRENT_BINARY_BOTS_DATABASE_VERSION 9054 #define CURRENT_BINARY_BOTS_DATABASE_VERSION 9054
#endif #endif

View File

@ -1693,6 +1693,24 @@ void command_npcedit(Client *c, const Seperator *sep)
c->Message(Chat::White, "Usage: #npcedit set_grid [Grid ID] - Sets an NPC's Grid ID"); c->Message(Chat::White, "Usage: #npcedit set_grid [Grid ID] - Sets an NPC's Grid ID");
return; return;
} }
} else if (!strcasecmp(sep->arg[1], "npc_tint_id")) {
if (sep->IsNumber(2)) {
const uint32 npc_tint_id = (Strings::ToUnsignedInt(sep->arg[2]));
n.npc_tint_id = npc_tint_id;
d = fmt::format(
"Set NPCTintID {} for {}",
npc_tint_id,
npc_id_string
);
} else {
c->Message(
Chat::White,
"Usage: #npcedit npc_tint_id [id] - Sets an NPC's NPCTintID [0 - 78 for RoF2]"
);
return;
}
} else { } else {
SendNPCEditSubCommands(c); SendNPCEditSubCommands(c);
return; return;

View File

@ -945,6 +945,12 @@ bool Lua_NPC::IsResumedFromZoneSuspend()
return self->IsResumedFromZoneSuspend(); return self->IsResumedFromZoneSuspend();
} }
void Lua_NPC::SetNPCTintIndex(uint32 id)
{
Lua_Safe_Call_Void();
self->SendAppearancePacket(AppearanceType::NPCTintIndex, id);
}
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<>())
@ -1091,6 +1097,7 @@ luabind::scope lua_register_npc() {
.def("SetLDoNTrapType", (void(Lua_NPC::*)(uint8))&Lua_NPC::SetLDoNTrapType) .def("SetLDoNTrapType", (void(Lua_NPC::*)(uint8))&Lua_NPC::SetLDoNTrapType)
.def("SetNPCAggro", (void(Lua_NPC::*)(bool))&Lua_NPC::SetNPCAggro) .def("SetNPCAggro", (void(Lua_NPC::*)(bool))&Lua_NPC::SetNPCAggro)
.def("SetNPCFactionID", (void(Lua_NPC::*)(int))&Lua_NPC::SetNPCFactionID) .def("SetNPCFactionID", (void(Lua_NPC::*)(int))&Lua_NPC::SetNPCFactionID)
.def("SetNPCTintIndex", &Lua_NPC::SetNPCTintIndex)
.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)
.def("SetPrimSkill", (void(Lua_NPC::*)(int))&Lua_NPC::SetPrimSkill) .def("SetPrimSkill", (void(Lua_NPC::*)(int))&Lua_NPC::SetPrimSkill)

View File

@ -199,6 +199,8 @@ public:
void ReturnHandinItems(Lua_Client c); void ReturnHandinItems(Lua_Client c);
Lua_Spawn GetSpawn(lua_State* L); Lua_Spawn GetSpawn(lua_State* L);
bool IsResumedFromZoneSuspend(); bool IsResumedFromZoneSuspend();
void SetNPCTintIndex(uint32 id);
}; };
#endif #endif

View File

@ -101,7 +101,8 @@ Mob::Mob(
bool in_always_aggro, bool in_always_aggro,
int32 in_heroic_strikethrough, int32 in_heroic_strikethrough,
bool in_keeps_sold_items, bool in_keeps_sold_items,
int64 in_hp_regen_per_second int64 in_hp_regen_per_second,
uint32 npc_tint_id
) : ) :
attack_timer(2000), attack_timer(2000),
attack_dw_timer(2000), attack_dw_timer(2000),
@ -289,6 +290,7 @@ Mob::Mob(
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; keeps_sold_items = in_keeps_sold_items;
m_npc_tint_id = npc_tint_id;
InitializeBuffSlots(); InitializeBuffSlots();
@ -1285,23 +1287,24 @@ void Mob::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho)
strn0cpy(ns->spawn.lastName, lastname, sizeof(ns->spawn.lastName)); strn0cpy(ns->spawn.lastName, lastname, sizeof(ns->spawn.lastName));
} }
ns->spawn.heading = FloatToEQ12(m_Position.w); ns->spawn.heading = FloatToEQ12(m_Position.w);
ns->spawn.x = FloatToEQ19(m_Position.x);//((int32)x_pos)<<3; ns->spawn.x = FloatToEQ19(m_Position.x); //((int32)x_pos)<<3;
ns->spawn.y = FloatToEQ19(m_Position.y);//((int32)y_pos)<<3; ns->spawn.y = FloatToEQ19(m_Position.y); //((int32)y_pos)<<3;
ns->spawn.z = FloatToEQ19(m_Position.z);//((int32)z_pos)<<3; ns->spawn.z = FloatToEQ19(m_Position.z); //((int32)z_pos)<<3;
ns->spawn.spawnId = GetID(); ns->spawn.spawnId = GetID();
ns->spawn.curHp = static_cast<uint8>(GetHPRatio()); ns->spawn.curHp = static_cast<uint8>(GetHPRatio());
ns->spawn.max_hp = 100; //this field needs a better name ns->spawn.max_hp = 100; // this field needs a better name
ns->spawn.race = (use_model) ? use_model : race; ns->spawn.race = (use_model) ? use_model : race;
ns->spawn.runspeed = runspeed; ns->spawn.runspeed = runspeed;
ns->spawn.walkspeed = walkspeed; ns->spawn.walkspeed = walkspeed;
ns->spawn.class_ = class_; ns->spawn.class_ = class_;
ns->spawn.gender = gender; ns->spawn.gender = gender;
ns->spawn.level = level; ns->spawn.level = level;
ns->spawn.PlayerState = GetPlayerState(); ns->spawn.PlayerState = GetPlayerState();
ns->spawn.deity = deity; ns->spawn.deity = deity;
ns->spawn.animation = 0; ns->spawn.animation = 0;
ns->spawn.findable = findable?1:0; ns->spawn.findable = findable ? 1 : 0;
ns->spawn.npc_tint_id = GetNpcTintId();
UpdateActiveLight(); UpdateActiveLight();
ns->spawn.light = m_Light.Type[EQ::lightsource::LightActive]; ns->spawn.light = m_Light.Type[EQ::lightsource::LightActive];

View File

@ -192,7 +192,8 @@ public:
bool in_always_aggros_foes, bool in_always_aggros_foes,
int32 in_heroic_strikethrough, int32 in_heroic_strikethrough,
bool keeps_sold_items, bool keeps_sold_items,
int64 in_hp_regen_per_second = 0 int64 in_hp_regen_per_second = 0,
uint32 npc_tint_id = 0
); );
virtual ~Mob(); virtual ~Mob();
@ -1066,6 +1067,7 @@ public:
void SendWearChangeAndLighting(int8 last_texture); void SendWearChangeAndLighting(int8 last_texture);
inline uint8 GetActiveLightType() { return m_Light.Type[EQ::lightsource::LightActive]; } inline uint8 GetActiveLightType() { return m_Light.Type[EQ::lightsource::LightActive]; }
bool UpdateActiveLight(); // returns true if change, false if no change bool UpdateActiveLight(); // returns true if change, false if no change
uint32 GetNpcTintId() { return m_npc_tint_id; }
EQ::LightSourceProfile* GetLightProfile() { return &m_Light; } EQ::LightSourceProfile* GetLightProfile() { return &m_Light; }
@ -1597,6 +1599,7 @@ protected:
bool rare_spawn; bool rare_spawn;
int32 heroic_strikethrough; int32 heroic_strikethrough;
bool keeps_sold_items; bool keeps_sold_items;
uint32 m_npc_tint_id;
uint32 m_PlayerState; uint32 m_PlayerState;
uint32 GetPlayerState() { return m_PlayerState; } uint32 GetPlayerState() { return m_PlayerState; }

View File

@ -128,7 +128,8 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi
npc_type_data->always_aggro, npc_type_data->always_aggro,
npc_type_data->heroic_strikethrough, npc_type_data->heroic_strikethrough,
npc_type_data->keeps_sold_items, npc_type_data->keeps_sold_items,
npc_type_data->hp_regen_per_second npc_type_data->hp_regen_per_second,
npc_type_data->m_npc_tint_id
), ),
attacked_timer(CombatEventTimer_expire), attacked_timer(CombatEventTimer_expire),
swarm_timer(100), swarm_timer(100),
@ -451,6 +452,7 @@ NPC::NPC(const NPCType *npc_type_data, Spawn2 *in_respawn, const glm::vec4 &posi
raid_target = npc_type_data->raid_target; raid_target = npc_type_data->raid_target;
ignore_despawn = npc_type_data->ignore_despawn; ignore_despawn = npc_type_data->ignore_despawn;
m_targetable = !npc_type_data->untargetable; m_targetable = !npc_type_data->untargetable;
m_npc_tint_id = npc_type_data->m_npc_tint_id;
npc_scale_manager->ScaleNPC(this); npc_scale_manager->ScaleNPC(this);
@ -1256,10 +1258,11 @@ uint32 ZoneDatabase::CreateNewNPCCommand(
e.Avoidance = n->GetAvoidanceRating(); e.Avoidance = n->GetAvoidanceRating();
e.heroic_strikethrough = n->GetHeroicStrikethrough(); e.heroic_strikethrough = n->GetHeroicStrikethrough();
e.see_hide = n->SeeHide(); e.see_hide = n->SeeHide();
e.see_improved_hide = n->SeeImprovedHide(); e.see_improved_hide = n->SeeImprovedHide();
e.see_invis = n->SeeInvisible(); e.see_invis = n->SeeInvisible();
e.see_invis_undead = n->SeeInvisibleUndead(); e.see_invis_undead = n->SeeInvisibleUndead();
e.npc_tint_id = n->GetNpcTintId();
e = NpcTypesRepository::InsertOne(*this, e); e = NpcTypesRepository::InsertOne(*this, e);
@ -1399,6 +1402,7 @@ uint32 ZoneDatabase::UpdateNPCTypeAppearance(Client* c, NPC* n)
e.loottable_id = n->GetLoottableID(); e.loottable_id = n->GetLoottableID();
e.merchant_id = n->MerchantType; e.merchant_id = n->MerchantType;
e.face = n->GetLuclinFace(); e.face = n->GetLuclinFace();
e.npc_tint_id = n->GetNpcTintId();
const int updated = NpcTypesRepository::UpdateOne(*this, e); const int updated = NpcTypesRepository::UpdateOne(*this, e);
@ -1539,6 +1543,7 @@ uint32 ZoneDatabase::AddNPCTypes(
e.runspeed = n->GetRunspeed(); e.runspeed = n->GetRunspeed();
e.prim_melee_type = static_cast<uint8_t>(EQ::skills::SkillHandtoHand); e.prim_melee_type = static_cast<uint8_t>(EQ::skills::SkillHandtoHand);
e.sec_melee_type = static_cast<uint8_t>(EQ::skills::SkillHandtoHand); e.sec_melee_type = static_cast<uint8_t>(EQ::skills::SkillHandtoHand);
e.npc_tint_id = n->GetNpcTintId();
e = NpcTypesRepository::InsertOne(*this, e); e = NpcTypesRepository::InsertOne(*this, e);
@ -2169,9 +2174,10 @@ void NPC::FillSpawnStruct(NewSpawn_Struct* ns, Mob* ForWho)
PetOnSpawn(ns); PetOnSpawn(ns);
ns->spawn.is_npc = 1; ns->spawn.is_npc = 1;
UpdateActiveLight(); UpdateActiveLight();
ns->spawn.light = GetActiveLightType(); ns->spawn.light = GetActiveLightType();
ns->spawn.show_name = NPCTypedata->show_name; ns->spawn.show_name = NPCTypedata->show_name;
ns->spawn.trader = false; ns->spawn.trader = false;
ns->spawn.npc_tint_id = GetNpcTintId();
} }
void NPC::PetOnSpawn(NewSpawn_Struct* ns) void NPC::PetOnSpawn(NewSpawn_Struct* ns)

View File

@ -885,6 +885,11 @@ Spawn2* Perl_NPC_GetSpawn(NPC* self)
return self->GetSpawn(); return self->GetSpawn();
} }
void Perl_NPC_SetNPCTintIndex(NPC* self, uint32 id)
{
return self->SendAppearancePacket(AppearanceType::NPCTintIndex, id);
}
void perl_register_npc() void perl_register_npc()
{ {
perl::interpreter perl(PERL_GET_THX); perl::interpreter perl(PERL_GET_THX);
@ -1034,6 +1039,7 @@ void perl_register_npc()
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);
package.add("SetNPCTintIndex", &Perl_NPC_SetNPCTintIndex);
package.add("SetPetSpellID", &Perl_NPC_SetPetSpellID); package.add("SetPetSpellID", &Perl_NPC_SetPetSpellID);
package.add("SetPlatinum", &Perl_NPC_SetPlatinum); package.add("SetPlatinum", &Perl_NPC_SetPlatinum);
package.add("SetPrimSkill", &Perl_NPC_SetPrimSkill); package.add("SetPrimSkill", &Perl_NPC_SetPrimSkill);

View File

@ -1732,6 +1732,7 @@ const NPCType *ZoneDatabase::LoadNPCTypesData(uint32 npc_type_id, bool bulk_load
t->attack_count = n.attack_count; t->attack_count = n.attack_count;
t->is_parcel_merchant = n.is_parcel_merchant ? true : false; t->is_parcel_merchant = n.is_parcel_merchant ? true : false;
t->greed = n.greed; t->greed = n.greed;
t->m_npc_tint_id = n.npc_tint_id;
if (!n.special_abilities.empty()) { if (!n.special_abilities.empty()) {
strn0cpy(t->special_abilities, n.special_abilities.c_str(), 512); strn0cpy(t->special_abilities, n.special_abilities.c_str(), 512);

View File

@ -157,6 +157,7 @@ struct NPCType
bool is_parcel_merchant; bool is_parcel_merchant;
uint8 greed; uint8 greed;
bool multiquest_enabled; bool multiquest_enabled;
uint32 m_npc_tint_id;
}; };
#pragma pack() #pragma pack()