Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Core/ObjectMgr): Implement display probabilities. #19068

Merged
merged 19 commits into from
Jun 16, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions data/sql/updates/pending_db_world/display-probabilities.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
DROP TABLE IF EXISTS `creature_template_model`;
CREATE TABLE `creature_template_model`(
`CreatureID` int(10) unsigned NOT NULL,
`Idx` int(10) unsigned NOT NULL DEFAULT '0',
`CreatureDisplayID` int(10) unsigned NOT NULL,
`DisplayScale` float NOT NULL DEFAULT '1',
`Probability` float NOT NULL DEFAULT '0',
`VerifiedBuild` smallint(5) unsigned NOT NULL,
heyitsbench marked this conversation as resolved.
Show resolved Hide resolved
PRIMARY KEY (`CreatureID`,`Idx`)
) ENGINE=InnoDB CHARSET=utf8mb4;

INSERT IGNORE INTO `creature_template_model` (`CreatureID`,`Idx`,`CreatureDisplayID`,`DisplayScale`,`Probability`,`VerifiedBuild`) SELECT `entry`,0,`modelid1`,`scale`,1,`VerifiedBuild` FROM `creature_template` WHERE `modelid1`!=0;
INSERT IGNORE INTO `creature_template_model` (`CreatureID`,`Idx`,`CreatureDisplayID`,`DisplayScale`,`Probability`,`VerifiedBuild`) SELECT `entry`,1,`modelid2`,`scale`,1,`VerifiedBuild` FROM `creature_template` WHERE `modelid2`!=0;
INSERT IGNORE INTO `creature_template_model` (`CreatureID`,`Idx`,`CreatureDisplayID`,`DisplayScale`,`Probability`,`VerifiedBuild`) SELECT `entry`,2,`modelid3`,`scale`,1,`VerifiedBuild` FROM `creature_template` WHERE `modelid3`!=0;
INSERT IGNORE INTO `creature_template_model` (`CreatureID`,`Idx`,`CreatureDisplayID`,`DisplayScale`,`Probability`,`VerifiedBuild`) SELECT `entry`,3,`modelid4`,`scale`,1,`VerifiedBuild` FROM `creature_template` WHERE `modelid4`!=0;

UPDATE `creature_template` SET `scale`=1;

ALTER TABLE `creature_template`
DROP `modelid1`,
DROP `modelid2`,
DROP `modelid3`,
DROP `modelid4`;
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ void WorldDatabaseConnection::DoPrepareStatements()
PrepareStatement(WORLD_SEL_WAYPOINT_SCRIPT_ID_BY_GUID, "SELECT id FROM waypoint_scripts WHERE guid = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_DEL_CREATURE, "DELETE FROM creature WHERE guid = ?", CONNECTION_ASYNC);
PrepareStatement(WORLD_SEL_COMMANDS, "SELECT name, security, help FROM command", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_CREATURE_TEMPLATE, "SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, modelid1, modelid2, modelid3, modelid4, name, subname, IconName, gossip_menu_id, minlevel, maxlevel, exp, faction, npcflag, speed_walk, speed_run, speed_swim, speed_flight, detection_range, scale, `rank`, dmgschool, DamageModifier, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, dynamicflags, family, trainer_type, trainer_spell, trainer_class, trainer_race, type, type_flags, lootid, pickpocketloot, skinloot, PetSpellDataId, VehicleId, mingold, maxgold, AIName, MovementType, ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, ctm.Chase, ctm.Random, ctm.InteractionPauseTimer, HoverHeight, HealthModifier, ManaModifier, ArmorModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, mechanic_immune_mask, spell_school_immune_mask, flags_extra, ScriptName FROM creature_template ct LEFT JOIN creature_template_movement ctm ON ct.entry = ctm.CreatureId WHERE entry = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_CREATURE_TEMPLATE, "SELECT entry, difficulty_entry_1, difficulty_entry_2, difficulty_entry_3, KillCredit1, KillCredit2, name, subname, IconName, gossip_menu_id, minlevel, maxlevel, exp, faction, npcflag, speed_walk, speed_run, speed_swim, speed_flight, detection_range, scale, `rank`, dmgschool, DamageModifier, BaseAttackTime, RangeAttackTime, BaseVariance, RangeVariance, unit_class, unit_flags, unit_flags2, dynamicflags, family, trainer_type, trainer_spell, trainer_class, trainer_race, type, type_flags, lootid, pickpocketloot, skinloot, PetSpellDataId, VehicleId, mingold, maxgold, AIName, MovementType, ctm.Ground, ctm.Swim, ctm.Flight, ctm.Rooted, ctm.Chase, ctm.Random, ctm.InteractionPauseTimer, HoverHeight, HealthModifier, ManaModifier, ArmorModifier, ExperienceModifier, RacialLeader, movementId, RegenHealth, mechanic_immune_mask, spell_school_immune_mask, flags_extra, ScriptName FROM creature_template ct LEFT JOIN creature_template_movement ctm ON ct.entry = ctm.CreatureId WHERE entry = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_WAYPOINT_SCRIPT_BY_ID, "SELECT guid, delay, command, datalong, datalong2, dataint, x, y, z, o FROM waypoint_scripts WHERE id = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_ITEM_TEMPLATE_BY_NAME, "SELECT entry FROM item_template WHERE name = ?", CONNECTION_SYNCH);
PrepareStatement(WORLD_SEL_CREATURE_BY_ID, "SELECT guid FROM creature WHERE id1 = ? OR id2 = ? OR id3 = ?", CONNECTION_SYNCH);
Expand Down
8 changes: 4 additions & 4 deletions src/server/game/AI/SmartScripts/SmartScript.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -424,10 +424,10 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
{
if (CreatureTemplate const* ci = sObjectMgr->GetCreatureTemplate(e.action.morphOrMount.creature))
{
uint32 displayId = ObjectMgr::ChooseDisplayId(ci);
target->ToCreature()->SetDisplayId(displayId);
CreatureModel const* model = ObjectMgr::ChooseDisplayId(ci);
target->ToCreature()->SetDisplayId(model->CreatureDisplayID, model->DisplayScale);
LOG_DEBUG("scripts.ai", "SmartScript::ProcessAction:: SMART_ACTION_MORPH_TO_ENTRY_OR_MODEL: Creature entry {}, GuidLow {} set displayid to {}",
target->GetEntry(), target->GetGUID().ToString(), displayId);
target->GetEntry(), target->GetGUID().ToString(), model->CreatureDisplayID);
}
}
//if no param1, then use value from param2 (modelId)
Expand Down Expand Up @@ -1316,7 +1316,7 @@ void SmartScript::ProcessAction(SmartScriptHolder& e, Unit* unit, uint32 var0, u
if (e.action.morphOrMount.creature > 0)
{
if (CreatureTemplate const* cInfo = sObjectMgr->GetCreatureTemplate(e.action.morphOrMount.creature))
target->ToUnit()->Mount(ObjectMgr::ChooseDisplayId(cInfo));
target->ToUnit()->Mount(ObjectMgr::ChooseDisplayId(cInfo)->CreatureDisplayID);
}
else
target->ToUnit()->Mount(e.action.morphOrMount.model);
Expand Down
164 changes: 114 additions & 50 deletions src/server/game/Entities/Creature/Creature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,51 +116,104 @@ VendorItem const* VendorItemData::FindItemCostPair(uint32 item_id, uint32 extend
return nullptr;
}

uint32 CreatureTemplate::GetRandomValidModelId() const
CreatureModel const CreatureModel::DefaultInvisibleModel(11686, 1.0f, 1.0f);
CreatureModel const CreatureModel::DefaultVisibleModel(17519, 1.0f, 1.0f);

CreatureModel const* CreatureTemplate::GetModelByIdx(uint32 idx) const
{
return idx < Models.size() ? &Models[idx] : nullptr;
}

CreatureModel const* CreatureTemplate::GetRandomValidModel() const
{
if (!Models.size())
return nullptr;

// If only one element, ignore the Probability (even if 0)
if (Models.size() == 1)
return &Models[0];

auto selectedItr = Acore::Containers::SelectRandomWeightedContainerElement(Models, [](CreatureModel const& model)
{
return model.Probability;
});

return &(*selectedItr);
}

CreatureModel const* CreatureTemplate::GetFirstValidModel() const
{
for (CreatureModel const& model : Models)
if (model.CreatureDisplayID)
return &model;

return nullptr;
}

CreatureModel const* CreatureTemplate::GetModelWithDisplayId(uint32 displayId) const
{
uint8 c = 0;
uint32 modelIDs[4];
for (CreatureModel const& model : Models)
if (displayId == model.CreatureDisplayID)
return &model;

return nullptr;
}

if (Modelid1) modelIDs[c++] = Modelid1;
if (Modelid2) modelIDs[c++] = Modelid2;
if (Modelid3) modelIDs[c++] = Modelid3;
if (Modelid4) modelIDs[c++] = Modelid4;
CreatureModel const* CreatureTemplate::GetFirstInvisibleModel() const
{
for (CreatureModel const& model : Models)
if (CreatureModelInfo const* modelInfo = sObjectMgr->GetCreatureModelInfo(model.CreatureDisplayID))
if (modelInfo && modelInfo->is_trigger)
return &model;

return ((c > 0) ? modelIDs[urand(0, c - 1)] : 0);
return &CreatureModel::DefaultInvisibleModel;
}

uint32 CreatureTemplate::GetFirstValidModelId() const
CreatureModel const* CreatureTemplate::GetFirstVisibleModel() const
{
if (Modelid1) return Modelid1;
if (Modelid2) return Modelid2;
if (Modelid3) return Modelid3;
if (Modelid4) return Modelid4;
return 0;
for (CreatureModel const& model : Models)
if (CreatureModelInfo const* modelInfo = sObjectMgr->GetCreatureModelInfo(model.CreatureDisplayID))
if (modelInfo && !modelInfo->is_trigger)
return &model;

return &CreatureModel::DefaultVisibleModel;
}

void CreatureTemplate::InitializeQueryData()
{
queryData.Initialize(SMSG_CREATURE_QUERY_RESPONSE, 1);

queryData << uint32(Entry); // creature entry
queryData << uint32(Entry); // creature entry
queryData << Name;
queryData << uint8(0) << uint8(0) << uint8(0); // name2, name3, name4, always empty
queryData << uint8(0) << uint8(0) << uint8(0); // name2, name3, name4, always empty
queryData << SubName;
queryData << IconName; // "Directions" for guard, string for Icons 2.3.0
queryData << uint32(type_flags); // flags
queryData << uint32(type); // CreatureType.dbc
queryData << uint32(family); // CreatureFamily.dbc
queryData << uint32(rank); // Creature Rank (elite, boss, etc)
queryData << uint32(KillCredit[0]); // new in 3.1, kill credit
queryData << uint32(KillCredit[1]); // new in 3.1, kill credit
queryData << uint32(Modelid1); // Modelid1
queryData << uint32(Modelid2); // Modelid2
queryData << uint32(Modelid3); // Modelid3
queryData << uint32(Modelid4); // Modelid4
queryData << float(ModHealth); // dmg/hp modifier
queryData << float(ModMana); // dmg/mana modifier
queryData << IconName; // "Directions" for guard, string for Icons 2.3.0
queryData << uint32(type_flags); // flags
queryData << uint32(type); // CreatureType.dbc
queryData << uint32(family); // CreatureFamily.dbc
queryData << uint32(rank); // Creature Rank (elite, boss, etc)
queryData << uint32(KillCredit[0]); // new in 3.1, kill credit
queryData << uint32(KillCredit[1]); // new in 3.1, kill credit
if (GetModelByIdx(0))
queryData << uint32(GetModelByIdx(0)->CreatureDisplayID); // Modelid1
else
queryData << uint32(0); // Modelid1
if (GetModelByIdx(1))
queryData << uint32(GetModelByIdx(1)->CreatureDisplayID); // Modelid2
else
queryData << uint32(0); // Modelid2
if (GetModelByIdx(2))
queryData << uint32(GetModelByIdx(2)->CreatureDisplayID); // Modelid3
else
queryData << uint32(0); // Modelid3
if (GetModelByIdx(3))
queryData << uint32(GetModelByIdx(3)->CreatureDisplayID); // Modelid4
else
queryData << uint32(0); // Modelid4
queryData << float(ModHealth); // dmg/hp modifier
queryData << float(ModMana); // dmg/mana modifier
queryData << uint8(RacialLeader);
queryData << uint32(movementId); // CreatureMovementInfo.dbc
queryData << uint32(movementId); // CreatureMovementInfo.dbc
}

bool AssistDelayEvent::Execute(uint64 /*e_time*/, uint32 /*p_time*/)
Expand Down Expand Up @@ -423,21 +476,22 @@ bool Creature::InitEntry(uint32 Entry, const CreatureData* data)
SetByteValue(UNIT_FIELD_BYTES_0, 1, uint8(cinfo->unit_class));

// Cancel load if no model defined
if (!(cinfo->GetFirstValidModelId()))
if (!(cinfo->GetFirstValidModel()))
{
LOG_ERROR("sql.sql", "Creature (Entry: {}) has no model defined in table `creature_template`, can't load. ", Entry);
LOG_ERROR("sql.sql", "Creature (Entry: {}) has no model defined in table `creature_template_model`, can't load. ", Entry);
return false;
}

uint32 displayID = ObjectMgr::ChooseDisplayId(GetCreatureTemplate(), data);
if (!sObjectMgr->GetCreatureModelRandomGender(&displayID)) // Cancel load if no model defined
CreatureModel model = *ObjectMgr::ChooseDisplayId(cinfo, data);
CreatureModelInfo const* mInfo = sObjectMgr->GetCreatureModelRandomGender(&model, cinfo);
if (!mInfo) // Cancel load if no model defined
{
LOG_ERROR("sql.sql", "Creature (Entry: {}) has no model defined in table `creature_template`, can't load. ", Entry);
LOG_ERROR("sql.sql", "Creature (Entry: {}) has no model {} defined in table `creature_template_model`, can't load. ", Entry, model.CreatureDisplayID);
return false;
}

SetDisplayId(displayID);
SetNativeDisplayId(displayID);
SetDisplayId(model.CreatureDisplayID, model.DisplayScale);
SetNativeDisplayId(model.CreatureDisplayID);

// Load creature equipment
if (!data)
Expand Down Expand Up @@ -1121,11 +1175,12 @@ bool Creature::Create(ObjectGuid::LowType guidlow, Map* map, uint32 phaseMask, u
break;
}

uint32 displayID = GetNativeDisplayId();
if (sObjectMgr->GetCreatureModelRandomGender(&displayID) && !IsTotem()) // Cancel load if no model defined or if totem
CreatureModel display(GetNativeDisplayId(), GetNativeObjectScale(), 1.0f);
CreatureModelInfo const* minfo = sObjectMgr->GetCreatureModelRandomGender(&display, cinfo);
if (minfo && !IsTotem()) // Cancel load if no model defined or if totem
{
SetDisplayId(displayID);
SetNativeDisplayId(displayID);
SetDisplayId(display.CreatureDisplayID, display.DisplayScale);
SetNativeDisplayId(display.CreatureDisplayID);
}

LoadCreaturesAddon();
Expand Down Expand Up @@ -1360,9 +1415,9 @@ void Creature::SaveToDB(uint32 mapid, uint8 spawnMask, uint32 phaseMask)
CreatureTemplate const* cinfo = GetCreatureTemplate();
if (cinfo)
{
if (displayId == cinfo->Modelid1 || displayId == cinfo->Modelid2 ||
displayId == cinfo->Modelid3 || displayId == cinfo->Modelid4)
displayId = 0;
for (CreatureModel model : cinfo->Models)
if (displayId && displayId == model.CreatureDisplayID)
displayId = 0;

if (npcflag == cinfo->npcflag)
npcflag = 0;
Expand Down Expand Up @@ -2031,11 +2086,12 @@ void Creature::Respawn(bool force)
// Do not override transform auras
if (GetAuraEffectsByType(SPELL_AURA_TRANSFORM).empty())
{
uint32 displayID = GetNativeDisplayId();
if (sObjectMgr->GetCreatureModelRandomGender(&displayID)) // Cancel load if no model defined
CreatureModel display(GetNativeDisplayId(), GetNativeObjectScale(), 1.0f);
CreatureModelInfo const* minfo = sObjectMgr->GetCreatureModelRandomGender(&display, GetCreatureTemplate());
if (minfo) // Cancel load if no model defined
{
SetDisplayId(displayID);
SetNativeDisplayId(displayID);
SetDisplayId(display.CreatureDisplayID, display.DisplayScale);
SetNativeDisplayId(display.CreatureDisplayID);
}
}

Expand Down Expand Up @@ -3414,9 +3470,9 @@ void Creature::SetObjectScale(float scale)
SetFloatValue(UNIT_FIELD_COMBATREACH, combatReach * scale);
}

void Creature::SetDisplayId(uint32 modelId)
void Creature::SetDisplayId(uint32 modelId, float displayScale /*= 1.f*/)
{
Unit::SetDisplayId(modelId);
Unit::SetDisplayId(modelId, displayScale);

float combatReach = DEFAULT_WORLD_OBJECT_SIZE;

Expand All @@ -3430,9 +3486,17 @@ void Creature::SetDisplayId(uint32 modelId)
if (IsPet())
combatReach = DEFAULT_COMBAT_REACH;

SetObjectScale(displayScale);

SetFloatValue(UNIT_FIELD_COMBATREACH, combatReach * GetObjectScale());
}

void Creature::SetDisplayFromModel(uint32 modelIdx)
{
if (CreatureModel const* model = GetCreatureTemplate()->GetModelByIdx(modelIdx))
SetDisplayId(model->CreatureDisplayID, model->DisplayScale);
}

void Creature::SetTarget(ObjectGuid guid)
{
if (!_focusSpell)
Expand Down
3 changes: 2 additions & 1 deletion src/server/game/Entities/Creature/Creature.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ class Creature : public Unit, public GridObject<Creature>, public MovableMapObje

float GetNativeObjectScale() const override;
void SetObjectScale(float scale) override;
void SetDisplayId(uint32 modelId) override;
void SetDisplayId(uint32 displayId, float displayScale = 1.f) override;
void SetDisplayFromModel(uint32 modelIdx);

void DisappearAndDie();

Expand Down
30 changes: 24 additions & 6 deletions src/server/game/Entities/Creature/CreatureData.h
Original file line number Diff line number Diff line change
Expand Up @@ -171,16 +171,29 @@ struct CreatureMovementData
std::string ToString() const;
};

struct CreatureModel
{
static CreatureModel const DefaultInvisibleModel;
static CreatureModel const DefaultVisibleModel;

CreatureModel() :
CreatureDisplayID(0), DisplayScale(0.0f), Probability(0.0f) { }

CreatureModel(uint32 creatureDisplayID, float displayScale, float probability) :
CreatureDisplayID(creatureDisplayID), DisplayScale(displayScale), Probability(probability) { }

uint32 CreatureDisplayID;
float DisplayScale;
float Probability;
};

// from `creature_template` table
struct CreatureTemplate
{
uint32 Entry;
uint32 DifficultyEntry[MAX_DIFFICULTY - 1];
uint32 KillCredit[MAX_KILL_CREDIT];
uint32 Modelid1;
uint32 Modelid2;
uint32 Modelid3;
uint32 Modelid4;
std::vector<CreatureModel> Models;
std::string Name;
std::string SubName;
std::string IconName;
Expand Down Expand Up @@ -239,8 +252,12 @@ struct CreatureTemplate
uint32 flags_extra;
uint32 ScriptID;
WorldPacket queryData; // pussywizard
[[nodiscard]] uint32 GetRandomValidModelId() const;
[[nodiscard]] uint32 GetFirstValidModelId() const;
CreatureModel const* GetModelByIdx(uint32 idx) const;
CreatureModel const* GetRandomValidModel() const;
CreatureModel const* GetFirstValidModel() const;
CreatureModel const* GetModelWithDisplayId(uint32 displayId) const;
CreatureModel const* GetFirstInvisibleModel() const;
CreatureModel const* GetFirstVisibleModel() const;

// helpers
[[nodiscard]] SkillType GetRequiredLootSkill() const
Expand Down Expand Up @@ -389,6 +406,7 @@ struct CreatureModelInfo
float combat_reach;
uint8 gender;
uint32 modelid_other_gender;
float is_trigger;
heyitsbench marked this conversation as resolved.
Show resolved Hide resolved
};

// Benchmarked: Faster than std::map (insert/find)
Expand Down
4 changes: 2 additions & 2 deletions src/server/game/Entities/Pet/Pet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2418,9 +2418,9 @@ void Pet::SynchronizeLevelWithOwner()
}
}

void Pet::SetDisplayId(uint32 modelId)
void Pet::SetDisplayId(uint32 modelId, float displayScale /*= 1.f*/)
{
Guardian::SetDisplayId(modelId);
Guardian::SetDisplayId(modelId, displayScale);

if (!isControlled())
return;
Expand Down
2 changes: 1 addition & 1 deletion src/server/game/Entities/Pet/Pet.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class Pet : public Guardian
void RemoveFromWorld() override;

float GetNativeObjectScale() const override;
void SetDisplayId(uint32 modelId) override;
void SetDisplayId(uint32 modelId, float displayScale = 1.f) override;

PetType getPetType() const { return m_petType; }
void setPetType(PetType type) { m_petType = type; }
Expand Down
Loading
Loading