diff --git a/doc/script_commands.txt b/doc/script_commands.txt index 38efec869f..78de46d88b 100644 --- a/doc/script_commands.txt +++ b/doc/script_commands.txt @@ -184,6 +184,8 @@ Defining a buddy could be done in several way: * datalong3 = enum ForcedMovement * data_flags & SCRIPT_FLAG_COMMAND_ADDITIONAL: teleport unit to position * x/y/z/o + * dataint = 1 to init move to respawn position, 2 to init creature group member each in their respawn position. + no other data is needed. So ex: command 3 ... dataint=1 and creature should head to its respawn pos 4 SCRIPT_COMMAND_FLAG_SET source = any * datalong = field_id @@ -397,3 +399,15 @@ Defining a buddy could be done in several way: 50 SCRIPT_COMMAND_ZONE_PULSE Pulses zone for combat and attacks closest enemy +51 SCRIPT_COMMAND_SPAWN_GROUP Set of commands for creature spawn group + * datalong = command + only formation command(staring from 100) supported right now + - 100: switch formation shape. + datalong1 should have shape value + Random(0), Queue(1), Side bu side(2), Geese(3), Fanned out behind(4), Fanned out in front(5), Circle leader(6) + - 101: Set formation spread + Spread is set in x field as we need a float for it + - 102: Set formation options + Set flag 0x1 for keep compact option (after a member death) + Set flag 0x2 for disabling pathfinding (not impemented yet) + diff --git a/sql/base/mangos.sql b/sql/base/mangos.sql index db2dc4232c..207f3d015e 100644 --- a/sql/base/mangos.sql +++ b/sql/base/mangos.sql @@ -23,7 +23,7 @@ DROP TABLE IF EXISTS `db_version`; CREATE TABLE `db_version` ( `version` varchar(120) DEFAULT NULL, `creature_ai_version` varchar(120) DEFAULT NULL, - `required_s2438_01_mangos_spawn_groups` bit(1) DEFAULT NULL + `required_s2439_01_mangos_groups_formation` bit(1) DEFAULT NULL ) ENGINE=MyISAM DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='Used DB version notes'; -- diff --git a/sql/updates/mangos/s2439_01_mangos_groups_formation.sql b/sql/updates/mangos/s2439_01_mangos_groups_formation.sql new file mode 100644 index 0000000000..5312f6a2ef --- /dev/null +++ b/sql/updates/mangos/s2439_01_mangos_groups_formation.sql @@ -0,0 +1,30 @@ +ALTER TABLE db_version CHANGE COLUMN required_s2438_01_mangos_spawn_groups required_s2439_01_mangos_groups_formation bit; + +ALTER TABLE `spawn_group_spawn` ADD COLUMN `SlotId` tinyint(4) NOT NULL DEFAULT -1 COMMENT '0 is the leader, -1 not part of the formation' AFTER `Guid`; + +DROP TABLE IF EXISTS `spawn_group_formation`; +CREATE TABLE `spawn_group_formation` ( + `SpawnGroupID` int(11) NOT NULL COMMENT 'Spawn group id', + `FormationType` tinyint(11) NOT NULL DEFAULT 0 COMMENT 'Formation shape 0..6', + `FormationSpread` float(11, 0) NOT NULL DEFAULT 0 COMMENT 'Distance between formation members', + `FormationOptions` int(11) NOT NULL DEFAULT 0 COMMENT 'Keep formation compact (bit 1)', + `MovementID` int(11) NOT NULL DEFAULT 0 COMMENT 'Id from waypoint_path path', + `MovementType` tinyint(11) NOT NULL COMMENT 'Same as creature table', + `Comment` varchar(255) NULL DEFAULT NULL, + PRIMARY KEY (`SpawnGroupID`) +); + +DROP TABLE IF EXISTS `waypoint_path`; +CREATE TABLE `waypoint_path` ( + `entry` mediumint(8) UNSIGNED NOT NULL COMMENT 'Creature entry', + `pathId` int(10) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Path ID for entry', + `point` mediumint(8) UNSIGNED NOT NULL DEFAULT 0, + `position_x` float NOT NULL DEFAULT 0, + `position_y` float NOT NULL DEFAULT 0, + `position_z` float NOT NULL DEFAULT 0, + `orientation` float NOT NULL DEFAULT 0, + `waittime` int(10) UNSIGNED NOT NULL DEFAULT 0, + `script_id` mediumint(8) UNSIGNED NOT NULL DEFAULT 0, + `comment` text NULL DEFAULT NULL, + PRIMARY KEY (`entry`, `pathId`, `point`) +); diff --git a/src/game/AI/PlayerAI/PlayerAI.cpp b/src/game/AI/PlayerAI/PlayerAI.cpp index 8a5dc9ed83..53b09db115 100644 --- a/src/game/AI/PlayerAI/PlayerAI.cpp +++ b/src/game/AI/PlayerAI/PlayerAI.cpp @@ -102,7 +102,12 @@ void PlayerAI::ExecuteSpells() void PlayerAI::JustGotCharmed(Unit* charmer) { - m_player->GetMotionMaster()->MoveFollow(charmer, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE, true); + if (charmer->GetFormationSlot()) + { + charmer->GetFormationSlot()->GetFormationData()->Add(m_player); + } + else + m_player->GetMotionMaster()->MoveFollow(charmer, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE, true); AttackClosestEnemy(); } @@ -110,7 +115,14 @@ void PlayerAI::EnterEvadeMode() { m_player->CombatStopWithPets(true); if (Unit* charmer = m_player->GetCharmer()) - m_player->GetMotionMaster()->MoveFollow(charmer, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE, true); + { + if (charmer->GetFormationSlot()) + { + charmer->GetFormationSlot()->GetFormationData()->Add(m_player); + } + else + m_player->GetMotionMaster()->MoveFollow(charmer, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE, true); + } } void PlayerAI::AttackClosestEnemy() diff --git a/src/game/Chat/Chat.cpp b/src/game/Chat/Chat.cpp index 959e508e01..a2b2c90cea 100644 --- a/src/game/Chat/Chat.cpp +++ b/src/game/Chat/Chat.cpp @@ -487,6 +487,28 @@ ChatCommand* ChatHandler::getCommandTable() { nullptr, 0, false, nullptr, "", nullptr } }; + static ChatCommand npcFormationCommandTable[] = + { + { "info", SEC_GAMEMASTER, false, &ChatHandler::HandleNpcFormationInfoCommand, "", nullptr }, + { "reset", SEC_GAMEMASTER, false, &ChatHandler::HandleNpcFormationResetCommand, "", nullptr }, + { "switch", SEC_GAMEMASTER, false, &ChatHandler::HandleNpcFormationSwitchCommand, "", nullptr }, + { nullptr, 0, false, nullptr, "", nullptr } + }; + +// static ChatCommand npcGroupBehaviorCommandTable[] = +// { +// { "show", SEC_GAMEMASTER, false, &ChatHandler::HandleNpcGroupBehaviorShowCommand, "", nullptr }, +// { "set", SEC_GAMEMASTER, false, &ChatHandler::HandleNpcGroupBehaviorSetCommand, "", nullptr }, +// { nullptr, 0, false, nullptr, "", nullptr } +// }; + + static ChatCommand npcGroupCommandTable[] = + { + { "info", SEC_GAMEMASTER, false, &ChatHandler::HandleNpcGroupInfoCommand, "", nullptr }, + //{ "behavior", SEC_GAMEMASTER, false, nullptr, "", npcGroupBehaviorCommandTable }, + { nullptr, 0, false, nullptr, "", nullptr } + }; + static ChatCommand npcCommandTable[] = { { "add", SEC_GAMEMASTER, false, &ChatHandler::HandleNpcAddCommand, "", nullptr }, @@ -518,6 +540,8 @@ ChatCommand* ChatHandler::getCommandTable() { "showloot", SEC_GAMEMASTER, false, &ChatHandler::HandleNpcShowLootCommand, "", nullptr }, { "tempspawn", SEC_GAMEMASTER, false, &ChatHandler::HandleNpcTempSpawn, "", nullptr }, { "evade", SEC_GAMEMASTER, false, &ChatHandler::HandleNpcEvade, "", nullptr }, + { "formation", SEC_GAMEMASTER, false, nullptr, "", npcFormationCommandTable }, + { "group", SEC_GAMEMASTER, false, nullptr, "", npcGroupCommandTable }, //{ TODO: fix or remove this commands { "addweapon", SEC_ADMINISTRATOR, false, &ChatHandler::HandleNpcAddWeaponCommand, "", nullptr }, diff --git a/src/game/Chat/Chat.h b/src/game/Chat/Chat.h index 84eacced8b..739b6b4f1c 100644 --- a/src/game/Chat/Chat.h +++ b/src/game/Chat/Chat.h @@ -482,6 +482,12 @@ class ChatHandler bool HandleNpcYellCommand(char* args); bool HandleNpcTempSpawn(char* args); bool HandleNpcEvade(char* args); + bool HandleNpcGroupInfoCommand(char* args); + //bool HandleNpcGroupBehaviorShowCommand(char* args); + //bool HandleNpcGroupBehaviorSetCommand(char* args); + bool HandleNpcFormationInfoCommand(char* args); + bool HandleNpcFormationResetCommand(char* args); + bool HandleNpcFormationSwitchCommand(char* args); // TODO: NpcCommands that needs to be fixed : bool HandleNpcAddWeaponCommand(char* args); diff --git a/src/game/Chat/Level2.cpp b/src/game/Chat/Level2.cpp index 197fe0c32a..e65f937d78 100644 --- a/src/game/Chat/Level2.cpp +++ b/src/game/Chat/Level2.cpp @@ -2317,6 +2317,165 @@ bool ChatHandler::HandleNpcShowLootCommand(char* /*args*/) return true; } +// show detailed information of creature formation if exist +bool ChatHandler::HandleNpcGroupInfoCommand(char* /*args*/) +{ + Creature* creature = getSelectedCreature(); + + if (!creature) + { + SendSysMessage(LANG_SELECT_CREATURE); + SetSentErrorMessage(true); + return false; + } + + auto gData = creature->GetCreatureGroup(); + if (!gData) + { + SendSysMessage("Creature is not in group"); + return true; + } + + PSendSysMessage("Group id[%u]", gData->GetGroupId()); + PSendSysMessage("Group name: %s", gData->GetGroupEntry().Name.c_str()); + + Unit* master = nullptr; + if (gData->GetFormationData()) + master = gData->GetFormationData()->GetMaster(); + + if (master) + { + if (master == creature) + SendSysMessage("Creature is formation leader"); + else if (creature->GetFormationSlot()) + SendSysMessage("Creature is in formation slot"); + else + SendSysMessage("Error: unable to retrieve the slot of the creature."); + } + + PSendSysMessage("%s", gData->to_string().c_str()); + return true; +} + +// show detailed information of group behavior +// bool ChatHandler::HandleNpcGroupBehaviorShowCommand(char* /*args*/) +// { +// Creature* creature = getSelectedCreature(); +// +// if (!creature) +// { +// SendSysMessage(LANG_SELECT_CREATURE); +// SetSentErrorMessage(true); +// return false; +// } +// +// +// return true; +// } + +// set behavior of targeted group +// bool ChatHandler::HandleNpcGroupBehaviorSetCommand(char* args) +// { +// Creature* creature = getSelectedCreature(); +// +// if (!creature) +// { +// SendSysMessage(LANG_SELECT_CREATURE); +// SetSentErrorMessage(true); +// return false; +// } +// +// return true; +// } + +// reset creature formation template +bool ChatHandler::HandleNpcFormationInfoCommand(char* /*args*/) +{ + Creature* creature = getSelectedCreature(); + + if (!creature) + { + SendSysMessage(LANG_SELECT_CREATURE); + SetSentErrorMessage(true); + return false; + } + + auto currSlot = creature->GetFormationSlot(); + if (!currSlot) + { + SendSysMessage("Creature is not in formation"); + return true; + } + + PSendSysMessage("%s", currSlot->GetFormationData()->to_string().c_str()); + return true; +} + +// reset creature formation template +bool ChatHandler::HandleNpcFormationResetCommand(char* /*args*/) +{ + Creature* creature = getSelectedCreature(); + + if (!creature) + { + SendSysMessage(LANG_SELECT_CREATURE); + SetSentErrorMessage(true); + return false; + } + + auto currSlot = creature->GetFormationSlot(); + if (!currSlot) + { + SendSysMessage("Creature is not in formation"); + return true; + } + + currSlot->GetFormationData()->Reset(); + + // need implementation + SendSysMessage("Formation is reset to default!"); + return true; +} + +// change creature formation template +bool ChatHandler::HandleNpcFormationSwitchCommand(char* args) +{ + Creature* creature = getSelectedCreature(); + + if (!creature) + { + SendSysMessage(LANG_SELECT_CREATURE); + SetSentErrorMessage(true); + return false; + } + + auto currSlot = creature->GetFormationSlot(); + if (!currSlot) + { + SendSysMessage("Creature is not in formation"); + return true; + } + + uint32 formationId; + if (!ExtractUInt32(&args, formationId)) + { + PSendSysMessage("Please provide a valid formation id!\n.npc formation switch #formationId"); + return false; + } + + if (formationId >= static_cast(SpawnGroupFormationType::SPAWN_GROUP_FORMATION_TYPE_COUNT)) + { + PSendSysMessage("Formation shape id should be in 0..%u range!", static_cast(SpawnGroupFormationType::SPAWN_GROUP_FORMATION_TYPE_COUNT) - 1); + return true; + } + + if (!currSlot->GetFormationData()->SwitchFormation(static_cast(formationId))) + PSendSysMessage("Failed to switch formation template!"); + else + SendSysMessage("Formation shape changed."); + return true; +} + // TODO: NpcCommands that need to be fixed : bool ChatHandler::HandleNpcNameCommand(char* /*args*/) diff --git a/src/game/DBScripts/ScriptMgr.cpp b/src/game/DBScripts/ScriptMgr.cpp index ee91060bf8..436c5a2cb2 100644 --- a/src/game/DBScripts/ScriptMgr.cpp +++ b/src/game/DBScripts/ScriptMgr.cpp @@ -33,6 +33,7 @@ #include "Mails/Mail.h" #include "AI/ScriptDevAI/ScriptDevAIMgr.h" #include "Maps/InstanceData.h" +#include "Entities/Object.h" ScriptMapMapName sQuestEndScripts; ScriptMapMapName sQuestStartScripts; @@ -784,6 +785,65 @@ void ScriptMgr::LoadScripts(ScriptMapMapName& scripts, const char* tablename) break; case SCRIPT_COMMAND_ZONE_PULSE: // 50 break; + + case SCRIPT_COMMAND_SPAWN_GROUP: // 51 + { + switch (tmp.formationData.command) + { +// case 2: // SetFormation +// break; +// case 3: // Add buddy to formation +// { +// if (!tmp.buddyEntry) +// { +// sLog.outErrorDb("Table `%s` has no buddy entry defined in SCRIPT_COMMAND_FORMATION for script id %u", tablename, tmp.id); +// continue; +// } +// break; +// } +// +// case 4: // remove buddy to formation +// { +// if (!tmp.buddyEntry) +// { +// sLog.outErrorDb("Table `%s` has no buddy entry defined in SCRIPT_COMMAND_FORMATION for script id %u", tablename, tmp.id); +// continue; +// } +// break; +// } + + case 100: // switch formation shape + { + if (tmp.formationData.data1 >= SpawnGroupFormationType::SPAWN_GROUP_FORMATION_TYPE_COUNT) + { + sLog.outErrorDb("Table `%s` uses invalid formation shape id(%u) for script id %u.", tablename, tmp.formationData.data1, tmp.id); + continue; + } + break; + } + + case 101: // change formation spread + { + if (tmp.x < 0.5f || tmp.x > 15.0f) + { + sLog.outErrorDb("Table `%s` uses invalid formation spread(%f) should be in 0.5 .. 15 range for script id %u.", tablename, tmp.x, tmp.id); + continue; + } + break; + } + + case 102: // change formation options + { + break; + } + + default: + sLog.outErrorDb("Table `%s` unknown formation command %u, skipping.", tablename, tmp.formationData.command); + continue; + } + + break; + } default: { sLog.outErrorDb("Table `%s` unknown command %u, skipping.", tablename, tmp.command); @@ -1541,6 +1601,22 @@ bool ScriptAction::ExecuteDbscriptCommand(WorldObject* pSource, WorldObject* pTa if (LogIfNotUnit(pSource)) break; + Creature* creature = static_cast(pSource); + + if (m_script->textId[0]) + { + if (m_script->textId[0] == 1 || m_script->textId[0] == 2 && !creature->GetCreatureGroup()) + { + Position const& respPos = creature->GetRespawnPosition(); + creature->GetMotionMaster()->MovePoint(0, respPos, ForcedMovement(m_script->moveTo.forcedMovement), 0.f, true); + } + else if (m_script->textId[0] == 2) + { + creature->GetCreatureGroup()->MoveHome(); + } + break; + } + // Just turn around if ((m_script->x == 0.0f && m_script->y == 0.0f && m_script->z == 0.0f) || // Check point-to-point distance, hence revert effect of bounding radius @@ -2618,8 +2694,164 @@ bool ScriptAction::ExecuteDbscriptCommand(WorldObject* pSource, WorldObject* pTa creature->AI()->AttackClosestEnemy(); break; } + case SCRIPT_COMMAND_SPAWN_GROUP: // 60 + { + if (LogIfNotCreature(pTarget)) + return false; + + Creature* leader = static_cast(pTarget); + + CreatureGroup* leaderGroup = leader->GetCreatureGroup(); + FormationSlotDataSPtr leaderSlot = leader->GetFormationSlot(); + FormationData* leaderFormation = nullptr; + if (leaderSlot) + leaderFormation = leaderSlot->GetFormationData(); + + switch (m_script->formationData.command) + { +// case 2: // set formation +// { +// if (LogIfNotCreature(pSource)) +// return false; +// +// CreatureGroup* targetGroup = nullptr; +// if (!m_script->formationData.data1) +// { +// if (!leaderGroup) +// { +// sLog.outErrorDb(" DB-SCRIPTS: Process table `%s` script id %u, command %u and subcommand formation create(2) failed. Target group(%u) not found!", +// m_table, m_script->id, m_script->command, m_script->formationData.data1); +// break; +// } +// leaderGroup->SetFormationData(nullptr); +// break; +// } +// else +// { +// auto sgData = leader->GetMap()->GetSpawnManager().GetSpawnGroup(m_script->formationData.data1); +// if (!sgData || !sgData->GetCreatureGroup()) +// { +// sLog.outErrorDb(" DB-SCRIPTS: Process table `%s` script id %u, command %u and subcommand formation create(2) failed. Target group(%u) not found!", +// m_table, m_script->id, m_script->command, m_script->formationData.data1); +// break; +// } +// targetGroup = sgData->GetCreatureGroup(); +// } +// +// if (targetGroup->GetFormationData()) +// { +// // +// break; +// } +// +// FormationEntrySPtr fEntry = std::make_shared(); +// fEntry->GroupId = targetGroup->GetGroupId(); +// fEntry->Type = static_cast(m_script->textId[0]); +// fEntry->Spread = m_script->x; +// fEntry->Options = m_script->textId[1]; +// fEntry->MovementType = m_script->textId[2]; // todo need to check that data!!! +// fEntry->MovementID = m_script->textId[3]; +// fEntry->Comment = "Dynamically created formation!"; +// +// targetGroup->SetFormationData(fEntry); +// break; +// } +// case 3: // add creature to the formation +// { +// if (LogIfNotCreature(pSource)) +// return false; +// +// if (!leaderFormation) +// { +// sLog.outErrorDb(" DB-SCRIPTS: Process table `%s` id %u, command %u failed. %s is not in formation!", +// m_table, m_script->id, m_script->command, leader->GetGuidStr().c_str()); +// break; +// } +// leaderFormation->Add(static_cast(pSource)); +// break; +// } +// case 4: // remove creature from the formation +// { +// if (LogIfNotCreature(pSource)) +// return false; +// +// if (!leaderFormation) +// { +// sLog.outErrorDb(" DB-SCRIPTS: Process table `%s` id %u, command %u failed. %s is not in formation!", +// m_table, m_script->id, m_script->command, leader->GetGuidStr().c_str()); +// break; +// } +// leaderFormation->Remove(static_cast(pSource)); +// break; +// } + case 100: // switch formation shape + { + if (!leaderFormation) + { + sLog.outErrorDb(" DB-SCRIPTS: Process table `%s` id %u, command %u failed. %s is not in formation!", + m_table, m_script->id, m_script->command, leader->GetGuidStr().c_str()); + break; + } + + if (m_script->formationData.data1 < static_cast(SpawnGroupFormationType::SPAWN_GROUP_FORMATION_TYPE_COUNT)) + { + if (!leaderFormation->SwitchFormation(static_cast(m_script->formationData.data1))) + sLog.outErrorDb(" DB-SCRIPTS: Process table `%s` id %u, command %u subcommand changeShape(%u) failed.", + m_table, m_script->id, m_script->command, m_script->formationData.command); + } + else + { + sLog.outErrorDb(" DB-SCRIPTS: Process table `%s` id %u, command %u subcommand switchShape(%u) failed, wrong shape id(%u).", + m_table, m_script->id, m_script->command, m_script->formationData.command, m_script->formationData.data1); + return true; + } + break; + } + case 101: // set formation spread + { + if (!leaderFormation) + { + sLog.outErrorDb(" DB-SCRIPTS: Process table `%s` id %u, command %u failed. %s is not in formation!", + m_table, m_script->id, m_script->command, leader->GetGuidStr().c_str()); + break; + } + + if (m_script->x <= 15) + { + leaderFormation->SetSpread(m_script->x); + } + else + { + sLog.outErrorDb(" DB-SCRIPTS: Process table `%s` id %u, command %u subcommand changeSpread(%u) failed, wrong shape id(%f).", + m_table, m_script->id, m_script->command, m_script->formationData.command, m_script->x); + return true; + } + break; + } + case 102: // set formation options + { + if (!leaderFormation) + { + sLog.outErrorDb(" DB-SCRIPTS: Process table `%s` id %u, command %u failed. %s is not in formation!", + m_table, m_script->id, m_script->command, leader->GetGuidStr().c_str()); + break; + } + + leaderFormation->SetOptions(m_script->formationData.data1); + break; + } + + default: + sLog.outErrorDb(" DB-SCRIPTS: Process table `%s` id %u, command %u failed. Invalid value for formation command!", + m_table, m_script->id, m_script->command, m_script->formationData.command); + break; + } + + break; + } default: - sLog.outErrorDb(" DB-SCRIPTS: Process table `%s` id %u, command %u unknown command used.", m_table, m_script->id, m_script->command); + sLog.outErrorDb(" DB-SCRIPTS: Process table `%s` id %u, command %u unknown command used.", + m_table, m_script->id, m_script->command); break; } diff --git a/src/game/DBScripts/ScriptMgr.h b/src/game/DBScripts/ScriptMgr.h index f4e60755ed..80da30db73 100644 --- a/src/game/DBScripts/ScriptMgr.h +++ b/src/game/DBScripts/ScriptMgr.h @@ -131,7 +131,8 @@ enum ScriptCommand // resSource, resTar // datalong=UnitFlags // datalong2:0x00=add, 0x01=remove, 0x02=toggle SCRIPT_COMMAND_SET_DATA_64 = 49, // datalong = set data param 1, datalong2 = set data param 2 - SCRIPT_COMMAND_ZONE_PULSE = 50, // + SCRIPT_COMMAND_ZONE_PULSE = 50, // + SCRIPT_COMMAND_SPAWN_GROUP = 51, // dalalong = command }; #define MAX_TEXT_ID 4 // used for SCRIPT_COMMAND_TALK, SCRIPT_COMMAND_EMOTE, SCRIPT_COMMAND_CAST_SPELL, SCRIPT_COMMAND_TERMINATE_SCRIPT @@ -446,6 +447,13 @@ struct ScriptInfo uint32 empty2; // datalong2 } logKill; + struct // SCRIPT_COMMAND_SPAWN_GROUP (60) + { + uint32 command; // datalong + uint32 data1; // datalong2 + uint32 data2; // datalong3 + } formationData; + struct { uint32 data[3]; diff --git a/src/game/Entities/Creature.cpp b/src/game/Entities/Creature.cpp index f13c96f4b4..5e5cb62883 100644 --- a/src/game/Entities/Creature.cpp +++ b/src/game/Entities/Creature.cpp @@ -163,6 +163,10 @@ Creature::~Creature() void Creature::CleanupsBeforeDelete() { + + if (GetCreatureGroup() && GetCreatureGroup()->GetFormationData()) + GetCreatureGroup()->GetFormationData()->OnDelete(this); + Unit::CleanupsBeforeDelete(); m_vendorItemCounts.clear(); } @@ -1541,13 +1545,17 @@ bool Creature::GetSpellCooldown(uint32 spellId, uint32& cooldown) const void Creature::SetCreatureGroup(CreatureGroup* group) { m_creatureGroup = group; - group->AddObject(GetDbGuid(), GetEntry()); + group->AddObject(GetDbGuid(), GetEntry()); } void Creature::ClearCreatureGroup() { if (m_creatureGroup) + { + if (m_creatureGroup->GetFormationData()) + m_creatureGroup->GetFormationData()->Remove(this); m_creatureGroup->RemoveObject(this); + } m_creatureGroup = nullptr; } @@ -1678,7 +1686,12 @@ bool Creature::LoadFromDB(uint32 dbGuid, Map* map, uint32 newGuid, uint32 forced GetMap()->GetCreatureLinkingHolder()->DoCreatureLinkingEvent(LINKING_EVENT_RESPAWN, this); if (GetCreatureGroup()) + { + auto fData = m_creatureGroup->GetFormationData(); + if (fData) + fData->SetFormationSlot(this); GetCreatureGroup()->TriggerLinkingEvent(CREATURE_GROUP_EVENT_RESPAWN, this); + } // check if it is rabbit day if (IsAlive() && sWorld.getConfig(CONFIG_UINT32_RABBIT_DAY)) diff --git a/src/game/Entities/Unit.cpp b/src/game/Entities/Unit.cpp index 0bfb78b58f..f0a271bf3e 100644 --- a/src/game/Entities/Unit.cpp +++ b/src/game/Entities/Unit.cpp @@ -1153,11 +1153,15 @@ void Unit::Kill(Unit* killer, Unit* victim, DamageEffectType damagetype, SpellEn /* * Actions for the victim */ - if (victim->GetTypeId() == TYPEID_PLAYER) // Killed player + if (victim->IsPlayer()) // Killed player { - Player* playerVictim = (Player*)victim; - + Player* playerVictim = static_cast(victim); + if (victim->GetFormationSlot()) + { + // simply remove player from the formation on death event + victim->GetFormationSlot()->GetFormationData()->Remove(playerVictim); + } // remember victim PvP death for corpse type and corpse reclaim delay // at original death (not at SpiritOfRedemtionTalent timeout) @@ -1405,6 +1409,13 @@ void Unit::JustKilledCreature(Unit* killer, Creature* victim, Player* responsibl if (victim->IsLinkingEventTrigger()) victim->GetMap()->GetCreatureLinkingHolder()->DoCreatureLinkingEvent(LINKING_EVENT_DIE, victim); + if (victim->GetCreatureGroup()) + { + auto fData = victim->GetCreatureGroup()->GetFormationData(); + if (fData) + fData->OnDeath(victim); + } + // Dungeon specific stuff if (victim->GetInstanceId()) { @@ -12161,6 +12172,11 @@ void Unit::Uncharm(Unit* charmed, uint32 spellId) charmed->GetMotionMaster()->UnMarkFollowMovegens(); } + if (GetFormationSlot()) + { + GetFormationSlot()->GetFormationData()->Remove(charmed); + } + // Update possessed's client control status after altering flags if (const Player* controllingClientPlayer = charmed->GetClientControlling()) controllingClientPlayer->UpdateClientControl(charmed, true); diff --git a/src/game/Entities/Unit.h b/src/game/Entities/Unit.h index 53cb4012cd..a67efc8272 100644 --- a/src/game/Entities/Unit.h +++ b/src/game/Entities/Unit.h @@ -45,6 +45,7 @@ #include "Timer.h" #include "AI/BaseAI/UnitAI.h" #include "Spells/SpellDefines.h" +#include "Maps/SpawnGroupDefines.h" #include #include @@ -2409,6 +2410,10 @@ class Unit : public WorldObject virtual bool CanCheckForHelp() const { return true; } virtual std::vector GetCharmSpells() const { return {}; } + + FormationSlotDataSPtr GetFormationSlot() { return m_formationSlot; } + void SetFormationSlot(FormationSlotDataSPtr fSlot) { m_formationSlot = fSlot; } + protected: bool MeetsSelectAttackingRequirement(Unit* target, SpellEntry const* spellInfo, uint32 selectFlags, SelectAttackingTargetParams params) const; @@ -2529,6 +2534,8 @@ class Unit : public WorldObject ObjectGuid const& GetCritterGuid() const { return m_critterGuid; } void SetCritterGuid(ObjectGuid critterGuid) { m_critterGuid = critterGuid; } + FormationSlotDataSPtr m_formationSlot; + private: void CleanupDeletedAuras(); void UpdateSplineMovement(uint32 t_diff); diff --git a/src/game/Globals/ObjectMgr.cpp b/src/game/Globals/ObjectMgr.cpp index 41c864c52a..273d1a28ed 100644 --- a/src/game/Globals/ObjectMgr.cpp +++ b/src/game/Globals/ObjectMgr.cpp @@ -1112,11 +1112,70 @@ void ObjectMgr::LoadSpawnGroups() entry.Flags = fields[5].GetUInt32(); entry.Active = false; entry.EnabledByDefault = true; + entry.formationEntry = nullptr; newContainer->spawnGroupMap.emplace(entry.Id, entry); } while (result->NextRow()); } - result.reset(WorldDatabase.Query("SELECT Id, Guid FROM spawn_group_spawn")); + result.reset(WorldDatabase.Query("SELECT SpawnGroupID, FormationType, FormationSpread, FormationOptions, MovementID, MovementType, Comment FROM spawn_group_formation")); + if (result) + { + do + { + Field* fields = result->Fetch(); + + FormationEntrySPtr fEntry = std::make_shared(); + fEntry->GroupId = fields[0].GetUInt32(); + uint32 fType = fields[1].GetUInt32(); + fEntry->Spread = fields[2].GetFloat(); + fEntry->Options = fields[3].GetUInt32(); + fEntry->MovementID = fields[4].GetUInt32(); + fEntry->MovementType = fields[5].GetUInt32(); + fEntry->Comment = fields[6].GetCppString(); + + auto itr = newContainer->spawnGroupMap.find(fEntry->GroupId); + if (itr == newContainer->spawnGroupMap.end()) + { + sLog.outErrorDb("LoadSpawnGroups: Invalid group Id:%u found in `spawn_group_formation`. Skipping.", fEntry->GroupId); + continue; + } + + if (fType >= static_cast(SPAWN_GROUP_FORMATION_TYPE_COUNT)) + { + sLog.outErrorDb("LoadSpawnGroups: Invalid formation type in `spawn_group_formation` ID:%u. Skipping.", fEntry->GroupId); + continue; + } + + if (fEntry->MovementType >= static_cast(MAX_DB_MOTION_TYPE)) + { + sLog.outErrorDb("LoadSpawnGroups: Invalid movement type in `spawn_group_formation` ID:%u. Skipping.", fEntry->GroupId); + continue; + } + + //todo this check have to be done after loading movement_table (load movement before spawn_group) +// if (fEntry->MovementID) +// { +// auto path = sWaypointMgr.GetPathFromOrigin(fEntry->MovementID, 0, 0, WaypointPathOrigin::PATH_FROM_MOVEMENT_TEMPLATE); +// if (!path) +// { +// sLog.outErrorDb("LoadSpawnGroups: No path ID(%u) found for formation ID(%u) in `movement_template` ID:%u. Ignoring movement.", fEntry->MovementID, fEntry->GroupId); +// fEntry->MovementID = 0; +// } +// } + + fEntry->Type = static_cast(fType); + + if (fEntry->Spread > 15.0f || fEntry->Spread < 0.5f) + { + sLog.outErrorDb("LoadSpawnGroups: Invalid spread value (%5.2f) should be between (0.5..15) in formation ID:%u . Skipping.", fEntry->Spread, fEntry->GroupId); + continue; + } + + itr->second.formationEntry = std::move(fEntry); + } while (result->NextRow()); + } + + result.reset(WorldDatabase.Query("SELECT Id, Guid, SlotId FROM spawn_group_spawn")); if (result) { do @@ -1126,6 +1185,7 @@ void ObjectMgr::LoadSpawnGroups() SpawnGroupDbGuids guid; guid.Id = fields[0].GetUInt32(); guid.DbGuid = fields[1].GetUInt32(); + guid.SlotId = fields[2].GetInt32(); if (newContainer->spawnGroupMap.find(guid.Id) == newContainer->spawnGroupMap.end()) { @@ -1152,8 +1212,36 @@ void ObjectMgr::LoadSpawnGroups() } } - group.DbGuids.push_back(guid); + group.DbGuids.push_back(guid); } while (result->NextRow()); + + // check and fix correctness of slot id indexation + for (auto& sg : newContainer->spawnGroupMap) + { + if (sg.second.formationEntry == nullptr) + continue; + + auto& guidMap = sg.second.DbGuids; + + uint32 slotIndex = 0; + bool dbError = false; + std::sort(guidMap.begin(), guidMap.end(), [](SpawnGroupDbGuids const& a, SpawnGroupDbGuids const& b) { return a.SlotId < b.SlotId; }); + for (auto& gInfo : guidMap) + { + if (gInfo.SlotId < 0) + continue; + + if (gInfo.SlotId != slotIndex) + { + gInfo.SlotId = slotIndex; + dbError = true; + } + ++slotIndex; + } + + if (dbError) + sLog.outErrorDb("LoadSpawnGroups: Invalid index for slot id in `spawn_group_spawn` for group ID:%u. Using default.", sg.second.Id); + } } result.reset(WorldDatabase.Query("SELECT Id, Entry, MinCount, MaxCount, Chance FROM spawn_group_entry")); @@ -1302,7 +1390,7 @@ void ObjectMgr::LoadSpawnGroups() } m_spawnGroupEntries = newContainer; - sLog.outString(">> Loaded %u spawn_group definitions", count); + sLog.outString(">> Loaded %u spawn_group definitions", uint32(m_spawnGroupEntries->spawnGroupMap.size())); sLog.outString(); } diff --git a/src/game/Maps/SpawnGroup.cpp b/src/game/Maps/SpawnGroup.cpp index 2510546ccc..ed144da6d3 100644 --- a/src/game/Maps/SpawnGroup.cpp +++ b/src/game/Maps/SpawnGroup.cpp @@ -24,6 +24,7 @@ #include "Maps/SpawnGroupDefines.h" #include "Maps/MapPersistentStateMgr.h" #include "Globals/ObjectMgr.h" +#include "MotionGenerators/TargetedMovementGenerator.h" SpawnGroup::SpawnGroup(SpawnGroupEntry const& entry, Map& map, uint32 typeId) : m_entry(entry), m_map(map), m_objectTypeId(typeId), m_enabled(m_entry.EnabledByDefault) { @@ -75,7 +76,7 @@ uint32 SpawnGroup::GetEligibleEntry(std::map& existingEntries, s { if (existingEntries[explicitly->Entry] > 0) { - if (roll < explicitly->Chance) + if (roll < static_cast(explicitly->Chance)) return explicitly->Entry; roll -= int32(explicitly->Chance); @@ -203,8 +204,29 @@ void SpawnGroup::Spawn(bool force) } } +std::string SpawnGroup::to_string() const +{ + std::stringstream result; + for (auto obj : m_objects) + { + std::string cData = ""; + auto creature = m_map.GetCreature(obj.first); + std::string guidStr = "Not found!"; + if (creature) + guidStr = creature->GetGuidStr(); + + result << "[" << obj.first << ", " << obj.second << "] " << guidStr << "\n"; + } + return result.str(); +} + + CreatureGroup::CreatureGroup(SpawnGroupEntry const& entry, Map& map) : SpawnGroup(entry, map, uint32(TYPEID_UNIT)) { + if (entry.formationEntry) + m_formationData = std::make_shared(this, entry.formationEntry); + else + m_formationData = nullptr; } void CreatureGroup::RemoveObject(WorldObject* wo) @@ -263,6 +285,38 @@ void CreatureGroup::TriggerLinkingEvent(uint32 event, Unit* target) } } +void CreatureGroup::SetFormationData(FormationEntrySPtr fEntry) +{ + if (fEntry) + { + m_formationData = std::make_shared(this, fEntry); + m_formationData->Reset(); + } + else + { + m_formationData->Disband(); + m_formationData = nullptr; + } +} + +void CreatureGroup::Update() +{ + SpawnGroup::Update(); +} + +void CreatureGroup::MoveHome() +{ + for (auto objItr : m_objects) + { + auto creature = m_map.GetCreature(objItr.first); + if (creature) + { + auto respPos = creature->GetRespawnPosition(); + creature->GetMotionMaster()->MovePoint(0, respPos, FORCED_MOVEMENT_RUN, 0.f, true); + } + } +} + void CreatureGroup::ClearRespawnTimes() { time_t now = time(nullptr); @@ -280,3 +334,947 @@ void GameObjectGroup::RemoveObject(WorldObject* wo) GameObjectData const* data = sObjectMgr.GetGOData(wo->GetDbGuid()); m_map.GetPersistentState()->RemoveGameobjectFromGrid(wo->GetDbGuid(), data); } + +//////////////////// +// Formation code // +//////////////////// + +FormationData::FormationData(CreatureGroup* gData, FormationEntrySPtr fEntry) : + m_groupData(gData), m_fEntry(fEntry), m_mirrorState(false), m_lastWP(0), m_wpPathId(0) +{ + for (auto const& sData : m_groupData->GetGroupEntry().DbGuids) + { + m_slotsMap.emplace(sData.SlotId, new FormationSlotData(sData.SlotId, sData.DbGuid, m_groupData)); + + // save real master guid + if (sData.SlotId == 0) + m_realMasterDBGuid = sData.DbGuid; + } + + m_masterMotionType = static_cast(fEntry->MovementType); + + // provided slot id should be ordered with no gap! + m_slotGuid = m_slotsMap.size(); + + // set current value from their defaults + m_currentFormationShape = m_fEntry->Type; + m_currentSpread = m_fEntry->Spread; + m_currentOptions = m_fEntry->Options; +} + +FormationData::~FormationData() +{ + //sLog.outDebug("Deleting formation (%u)!!!!!", m_groupData->GetGroupEntry().Id); +} + +bool FormationData::SetFollowersMaster() +{ + Unit* master = GetMaster(); + if (!master) + { + return false; + } + + for (auto slotItr : m_slotsMap) + { + auto& currentSlot = slotItr.second; + + if (currentSlot->IsFormationMaster()) + continue; + + auto follower = currentSlot->GetOwner(); + + if (follower && follower->IsAlive()) + { + bool setMgen = false; + if (follower->GetMotionMaster()->GetCurrentMovementGeneratorType() != FORMATION_MOTION_TYPE) + setMgen = true; + else + { + auto mgen = static_cast(follower->GetMotionMaster()->GetCurrent()); + if (mgen->GetCurrentTarget() != master) + setMgen = true; + } + + if (setMgen) + { + follower->GetMotionMaster()->Clear(false, true); + follower->GetMotionMaster()->MoveInFormation(currentSlot, true); + currentSlot->GetRecomputePosition() = true; + } + } + } + + //sLog.outDebug("FormationData::SetFollowersMaste> called for groupId(%u)", m_groupData->GetGroupEntry().Id); + + return false; +} + +bool FormationData::SwitchFormation(SpawnGroupFormationType newShape) +{ + if (m_currentFormationShape == newShape) + return false; + + m_currentFormationShape = newShape; + + FixSlotsPositions(); + return true; +} + +void FormationData::SetOptions(uint32 options) +{ + if (HaveOption(SPAWN_GROUP_FORMATION_OPTION_KEEP_CONPACT, m_currentOptions) != HaveOption(SPAWN_GROUP_FORMATION_OPTION_KEEP_CONPACT, options)) + { + FixSlotsPositions(); + } + + m_currentOptions = options; +} + +// remove all creatures from formation data +void FormationData::Disband() +{ + ClearMoveGen(); + for (auto& slotItr : m_slotsMap) + { + auto& slot = slotItr.second; + + Unit* slotUnit = slot->GetOwner(); + if (slotUnit) + slotUnit->SetFormationSlot(nullptr); + } + + m_slotsMap.clear(); +} + +// remove all movegen (maybe we should remove only move in formation one) +void FormationData::ClearMoveGen() +{ + for (auto& slotItr : m_slotsMap) + { + auto& slot = slotItr.second; + + Unit* slotUnit = slot->GetOwner(); + if (slotUnit && slotUnit->IsAlive()) + { + if (slot->IsFormationMaster()) + { + m_lastWP = slotUnit->GetMotionMaster()->getLastReachedWaypoint(); + m_wpPathId = slotUnit->GetMotionMaster()->GetPathId(); + } + slotUnit->GetMotionMaster()->Initialize(); + } + } +} + +Unit* FormationData::GetMaster() +{ + if (m_slotsMap.begin() != m_slotsMap.end() && m_slotsMap.begin()->first == 0) + return m_slotsMap.begin()->second->GetOwner(); + return nullptr; +} + +void FormationData::SetMasterMovement() +{ + auto newMaster = m_slotsMap.at(0)->GetOwner(); + + if (!newMaster) + return; + + newMaster->GetMotionMaster()->Clear(true, true); + if (m_masterMotionType == WAYPOINT_MOTION_TYPE) + { + newMaster->GetMotionMaster()->MoveWaypoint(m_wpPathId, 4, 0, m_fEntry->MovementID); + newMaster->GetMotionMaster()->SetNextWaypoint(m_lastWP + 1); + m_wpPathId = 0; + m_lastWP = 0; + } +// else if (m_masterMotionType == MasterMotionType::FORMATION_TYPE_MASTER_LINEAR_WP) +// { +// newMaster->GetMotionMaster()->MoveLinearWP(m_wpPathId, 0, 0, 0, m_realMasterGuid, m_lastWP); +// m_wpPathId = 0; +// m_lastWP = 0; +// } + else if (m_masterMotionType == RANDOM_MOTION_TYPE) + { + newMaster->GetMotionMaster()->MoveRandomAroundPoint(m_spawnPos.x, m_spawnPos.y, m_spawnPos.z, m_spawnPos.radius); + } + else + { + newMaster->GetMotionMaster()->MoveIdle(); + } +} + +FormationSlotDataSPtr FormationData::GetFirstEmptySlot() +{ + for (auto slot : m_slotsMap) + { + if (slot.second->IsRemovable()) + continue; + + if (!slot.second->GetOwner()) + return slot.second; + } + return nullptr; +} + +FormationSlotDataSPtr FormationData::GetFirstAliveSlot() +{ + for (auto slot : m_slotsMap) + { + if (slot.second->IsRemovable()) + continue; + + if (slot.second->GetOwner() && slot.second->GetOwner()->IsAlive()) + return slot.second; + } + return nullptr; +} + +bool FormationData::TrySetNewMaster(Unit* masterCandidat /*= nullptr*/) +{ + auto masterSlotItr = m_slotsMap.find(0); + if (masterSlotItr == m_slotsMap.end()) + return false; + + auto& masterSlot = masterSlotItr->second; + + FormationSlotDataSPtr aliveSlot = nullptr; + + if (masterCandidat && masterCandidat->IsAlive()) + { + auto candidateSlot = masterCandidat->GetFormationSlot(); + + // candidate have to be in this group + if (candidateSlot && candidateSlot->GetCreatureGroup()->GetGroupId() == m_groupData->GetGroupId()) + aliveSlot = candidateSlot; + } + else + { + // Get first alive slot + aliveSlot = GetFirstAliveSlot(); + } + + if (aliveSlot) + { + SwitchSlotOwner(masterSlot, aliveSlot); + FixSlotsPositions(); + SetMasterMovement(); + SetFollowersMaster(); + return true; + } + + return false; +} + +void FormationData::Reset() +{ + m_mirrorState = false; + + SwitchFormation(m_fEntry->Type); + + FormationSlotMap::iterator slotItr = m_slotsMap.end(); + for (FormationSlotMap::iterator itr = m_slotsMap.begin(); itr != m_slotsMap.end();) + { + auto slot = itr->second; + if (slot->IsRemovable()) + { + if (slot->GetOwner()) + { + slot->GetOwner()->GetMotionMaster()->Initialize(); + slot->GetOwner()->SetFormationSlot(nullptr); + } + itr = m_slotsMap.erase(itr); + } + else + { + if (!slot->GetOwner() || slot->GetOwner()->GetDbGuid() != slot->GetRealOwnerGuid()) + { + auto creature = m_groupData->GetMap().GetCreature(slot->GetRealOwnerGuid()); + if (creature) + { + if (creature->GetFormationSlot()) + { + if (creature->GetFormationSlot() != slot) + { + SwitchSlotOwner(slot, creature->GetFormationSlot()); + } + } + + creature->SetFormationSlot(slot); + slot->SetOwner(creature); + } + else + { + slot->SetOwner(nullptr); + } + } + ++itr; + } + } + + auto masterSlotItr = m_slotsMap.find(0); + if (masterSlotItr != m_slotsMap.end()) + { + TrySetNewMaster(masterSlotItr->second->GetOwner()); + } +} + +void FormationData::OnDeath(Creature* creature) +{ + auto slot = creature->GetFormationSlot(); + if(!slot) + return; + //sLog.outString("Deleting %s from formation(%u)", creature->GetGuidStr().c_str(), m_groupData->GetGroupEntry().Id); + + bool formationMaster = false; + if (slot->IsFormationMaster()) + { + m_lastWP = creature->GetMotionMaster()->getLastReachedWaypoint(); + m_wpPathId = creature->GetMotionMaster()->GetPathId(); + + formationMaster = true; + } + slot->SetOwner(nullptr); + creature->SetFormationSlot(nullptr); + + if (formationMaster) + TrySetNewMaster(); + else if(HaveOption(SPAWN_GROUP_FORMATION_OPTION_KEEP_CONPACT)) + FixSlotsPositions(); +} + +void FormationData::OnDelete(Creature* creature) +{ + // we can handle it like a death event + OnDeath(creature); +} + +// get formation slot id for provided dbGuid, return -1 if not found +int32 FormationData::GetDefaultSlotId(uint32 dbGuid) +{ + for (auto const& entry : m_groupData->GetGroupEntry().DbGuids) + if (entry.DbGuid == dbGuid) + return entry.SlotId; + return -1; +} + +FormationSlotDataSPtr FormationData::GetDefaultSlot(uint32 dbGuid, SpawnGroupFormationSlotType slotType /*= SPAWN_GROUP_FORMATION_SLOT_TYPE_STATIC*/) +{ + FormationSlotDataSPtr result = nullptr; + switch (slotType) + { + case SPAWN_GROUP_FORMATION_SLOT_TYPE_STATIC: + { + auto slotId = GetDefaultSlotId(dbGuid); + if (slotId < 0) + result = nullptr; + else + result = m_slotsMap[slotId]; + break; + } + case SPAWN_GROUP_FORMATION_SLOT_TYPE_SCRIPT: + case SPAWN_GROUP_FORMATION_SLOT_TYPE_PLAYER: + for (auto slotItr : m_slotsMap) + { + if (slotItr.second->GetSlotType() != slotType) + continue; + + if (slotItr.second->GetRealOwnerGuid() == dbGuid) + { + result = slotItr.second; + break; + } + } + break; + default: + break; + } + + return result; +} + +void FormationData::SwitchSlotOwner(FormationSlotDataSPtr slotA, FormationSlotDataSPtr slotB) +{ + Unit* aUnit = slotA->GetOwner(); + Unit* bUnit = slotB->GetOwner(); + + slotA->SetOwner(bUnit); + if (aUnit) + aUnit->SetFormationSlot(slotB); + + slotB->SetOwner(aUnit); + if (bUnit) + bUnit->SetFormationSlot(slotA); +} + +bool FormationData::FreeSlot(FormationSlotDataSPtr slot) +{ + if (!slot->GetOwner()) + return true; + + auto newSlot = GetDefaultSlot(slot->GetOwner()->GetDbGuid()); + + if (!newSlot || newSlot == slot) + { + newSlot = GetFirstEmptySlot(); + if (!newSlot) + { + sLog.outError("FormationData::MoveSlotOwner> Unable to find free place in formation groupID: %u for %s", + m_groupData->GetGroupId(), slot->GetOwner()->GetGuidStr().c_str()); + return false; + } + } + + SwitchSlotOwner(slot, newSlot); + return true; +} + +bool FormationData::AddInFormationSlot(Unit* newUnit, SpawnGroupFormationSlotType slotType /*= SPAWN_GROUP_FORMATION_SLOT_TYPE_STATIC*/) +{ + if (!newUnit || !newUnit->IsAlive()) + { + sLog.outError("FormationData::AddInFormationSlot> Invalid call detected! (unit is nullptr or not alive)"); + return false; + } + + Unit* oldUnit = nullptr; + + // TODO:: its normal to not have default slot for dynamically added creature/player to the formation we should add one + auto slot = GetDefaultSlot(newUnit->GetDbGuid(), slotType); + if (!slot) + { + sLog.outError("FormationData::AddInFormationSlot> Unable to find default slot for %s , is it part of the formation? Aborting...", newUnit->GetGuidStr().c_str()); + return false; + } + + if (!FreeSlot(slot)) + { + sLog.outError("FormationData::AddInFormationSlot> Unable to free occupied slot by %s for %s", slot->GetOwner()->GetGuidStr().c_str(), newUnit->GetGuidStr().c_str()); + return false; + } + + slot->SetOwner(newUnit); + newUnit->SetFormationSlot(slot); + + //sLog.outString("Slot(%u) filled by %s in formation(%u)", slot->GetSlotId(), newUnit->GetGuidStr().c_str(), m_groupData->GetGroupEntry().Id); + return true; +} + +void FormationData::Compact(bool set /*= true*/) +{ + if (set) + m_currentOptions = m_currentOptions | static_cast(SPAWN_GROUP_FORMATION_OPTION_KEEP_CONPACT); + else + m_currentOptions = m_currentOptions & ~(static_cast(SPAWN_GROUP_FORMATION_OPTION_KEEP_CONPACT)); + + FixSlotsPositions(); +} + +void FormationData::Add(Creature* creature) +{ + if (creature->GetFormationSlot()) + return; + + if (creature->IsTemporarySummon()) + { + sLog.outError("FormationData::Add> unable to add temporary summon creature %s to formation %u", creature->GetGuidStr().c_str(), m_groupData->GetGroupId()); + return; + } + + if (!creature->IsAlive()) + { + sLog.outError("FormationData::Add> Cannot add dead creature %s to formation %u", creature->GetGuidStr().c_str(), m_groupData->GetGroupId()); + return; + } + + SpawnGroupFormationSlotType slotType = SPAWN_GROUP_FORMATION_SLOT_TYPE_SCRIPT; + + // be sure that no slot already exist before creating a new one + auto defaultSlot = GetDefaultSlot(creature->GetDbGuid(), slotType); + if (!defaultSlot) + { + uint32 slotId = m_slotGuid++; + auto result = m_slotsMap.emplace(slotId, new FormationSlotData(slotId, creature->GetDbGuid(), m_groupData, slotType)); + if (!result.second) + { + sLog.outError("FormationData::Add> Failled to add creature %s to formation %u", creature->GetGuidStr().c_str(), m_groupData->GetGroupId()); + return; + } + } + + SetFormationSlot(creature, slotType); +} + +void FormationData::Add(Player* player) +{ + if (player->GetFormationSlot()) + return; + + uint32 slotId = m_slotGuid++; + if (slotId == 0) + { + // this should have at least one master + sLog.outError("FormationData::Add(Player* player)> Unable to add %s to formation %u as master", player->GetGuidStr().c_str(), m_groupData->GetGroupId()); + m_slotGuid = 0; + return; + } + + // be sure that no slot already exist before creating a new one + auto defaultSlot = GetDefaultSlot(player->GetDbGuid(), SPAWN_GROUP_FORMATION_SLOT_TYPE_PLAYER); + if (!defaultSlot) + { + auto result = m_slotsMap.emplace(slotId, new FormationSlotData(slotId, player->GetDbGuid(), m_groupData, SPAWN_GROUP_FORMATION_SLOT_TYPE_PLAYER)); + if (!result.second) + { + sLog.outError("FormationData::Add(Player* player)> Failled to add %s to formation %u", player->GetGuidStr().c_str(), m_groupData->GetGroupId()); + return; + } + + defaultSlot = result.first->second; + } + player->SetFormationSlot(defaultSlot); + player->GetFormationSlot()->SetOwner(player); + FixSlotsPositions(); + SetFollowersMaster(); +} + +void FormationData::Remove(Unit* unit) +{ + if (unit->IsPlayer()) + { + Remove(static_cast(unit)); + } + else if (unit->IsCreature()) + Remove(static_cast(unit)); +} + +void FormationData::Remove(Creature* creature) +{ + auto slot = creature->GetFormationSlot(); + if (!slot) + return; + + // check if slot is still owner one + if (slot->GetRealOwnerGuid() != creature->GetDbGuid()) + { + FormationSlotMap::iterator slotItr = m_slotsMap.end(); + for (FormationSlotMap::iterator itr = m_slotsMap.begin(); itr != m_slotsMap.end(); ++itr) + { + if (itr->second->GetRealOwnerGuid() == creature->GetDbGuid()) + { + slotItr = itr; + break; + } + } + + if (slotItr == m_slotsMap.end()) + { + // slot can be just freed like the creature was dead + OnDeath(creature); + } + else + { + SwitchSlotOwner(slot, slotItr->second); + slot = slotItr->second; + } + } + + // free the slot + OnDeath(creature); + + creature->GetMotionMaster()->Initialize(); + + if (slot->IsRemovable()) + m_slotsMap.erase(slot->GetSlotId()); + if (HaveOption(SPAWN_GROUP_FORMATION_OPTION_KEEP_CONPACT)) + FixSlotsPositions(); +} + +void FormationData::Remove(Player* player) +{ + auto slot = player->GetFormationSlot(); + if (!slot) + return; + + if (slot->GetRealOwnerGuid() != player->GetDbGuid()) + { + FormationSlotMap::iterator slotItr = m_slotsMap.end(); + for (FormationSlotMap::iterator itr = m_slotsMap.begin(); itr != m_slotsMap.end(); ++itr) + { + if (itr->second->GetSlotType() == SPAWN_GROUP_FORMATION_SLOT_TYPE_PLAYER) + continue; + + if (itr->second->GetRealOwnerGuid() == player->GetDbGuid()) + { + slotItr = itr; + break; + } + } + + if (slotItr == m_slotsMap.end()) + { + // slot can be just freed + slot = nullptr; + } + else + { + SwitchSlotOwner(slot, slotItr->second); + slot = slotItr->second; + } + } + + if (!slot) + { + sLog.outError("FormationData::Remove(Player* player)> Failled to remove player %s from formation %u", player->GetGuidStr().c_str(), m_groupData->GetGroupId()); + return; + } + + if (slot->IsRemovable()) + { + // remove the slot + m_slotsMap.erase(slot->GetSlotId()); + } + else + slot->SetOwner(nullptr); + + player->SetFormationSlot(nullptr); + player->GetMotionMaster()->Initialize(); + + if (HaveOption(SPAWN_GROUP_FORMATION_OPTION_KEEP_CONPACT)) + FixSlotsPositions(); +} + +FormationSlotDataSPtr FormationData::SetFormationSlot(Creature* creature, SpawnGroupFormationSlotType slotType /*= SPAWN_GROUP_FORMATION_SLOT_TYPE_STATIC*/) +{ + if (!creature->IsAlive()) + return nullptr; + + auto const& gEntry = m_groupData->GetGroupEntry(); + + if (m_fEntry == nullptr) + return nullptr; + + uint32 dbGuid = creature->GetDbGuid(); + + // check if existing slot + if (auto currentSlot = creature->GetFormationSlot()) + { + // no more work to do + return std::move(currentSlot); + } + + // add it in the corresponding slot + if (!AddInFormationSlot(creature, slotType)) + return nullptr; + + // set the creature as active to avoid some problem + //creature->SetActiveObjectState(true); // maybe not needed? + + auto slot = creature->GetFormationSlot(); + if (!m_realMasterDBGuid) + { + if (slot->GetSlotId() == 0) + { + m_realMasterDBGuid = dbGuid; + creature->GetRespawnCoord(m_spawnPos.x, m_spawnPos.y, m_spawnPos.z, nullptr, &m_spawnPos.radius); + + switch (creature->GetDefaultMovementType()) + { + case RANDOM_MOTION_TYPE: + m_masterMotionType = RANDOM_MOTION_TYPE; + break; + case WAYPOINT_MOTION_TYPE: + m_masterMotionType = WAYPOINT_MOTION_TYPE; + break; + //case LINEAR_WP_MOTION_TYPE: + // m_masterMotionType = MasterMotionType::FORMATION_TYPE_MASTER_LINEAR_WP; + // break; + default: + sLog.outError("FormationData::FillSlot> Master have not recognized default movement type for formation! Forced to random."); + m_masterMotionType = RANDOM_MOTION_TYPE; + break; + } + } + } + + if (GetMaster()) + { + if (slot->GetSlotId() == 0) + { + // spawned/respawned master + SetMasterMovement(); + + // reset formation shape as it will restart from node 0 in respawn case + SwitchFormation(m_fEntry->Type); + } + FixSlotsPositions(); + SetFollowersMaster(); + } + return slot; +} + +std::string FormationData::to_string() const +{ + std::stringstream result; + + static const std::string FormationType[] = { + "[0]Random", + "[1]Single file", + "[2]Side by side", + "[3]Like a geese", + "[4]Fanned out behind", + "[5]Fanned out in front", + "[6]Circle the leader" + }; + + std::string fType = FormationType[static_cast(m_currentFormationShape)]; + std::string fMoveType = GetMoveTypeStr(m_masterMotionType); + std::string fOptions = (HaveOption(SPAWN_GROUP_FORMATION_OPTION_KEEP_CONPACT)) ? ", keepCompact" : "no options"; + result << "Formation group id: " << m_fEntry->GroupId << "\n"; + result << "Shape: " << fType << "\n"; + result << "Spread: " << m_currentSpread << "\n"; + result << "MovementType: " << fMoveType << "\n"; + result << "MovementId: " << m_fEntry->MovementID << "\n"; + result << "Options: " << fOptions << "\n"; + result << "Comment: " << m_fEntry->Comment << "\n"; + + for (auto slot : m_slotsMap) + { + std::string guidStr = ""; + if (slot.second->GetOwner()) + guidStr = slot.second->GetOwner()->GetGuidStr(); + else + guidStr = "empty slot"; + + result << "[" << slot.first << "] " << guidStr << "\n"; + } + + return result.str(); +} + +void FormationData::FixSlotsPositions() +{ + float defaultDist = m_currentSpread; + auto& slots = m_slotsMap; + float totalMembers = 0; + bool onlyAlive = HaveOption(SPAWN_GROUP_FORMATION_OPTION_KEEP_CONPACT); + + if (onlyAlive) + { + for (auto& slotItr : slots) + { + auto& slot = slotItr.second; + + if (slot->GetOwner() && !slot->GetOwner()->IsAlive()) + continue; + + ++totalMembers; + } + } + else + { + totalMembers = m_slotsMap.size(); + } + + if (totalMembers <= 1) + return; + + // only take account of the followers + --totalMembers; + + switch (m_currentFormationShape) + { + // random formation + case SPAWN_GROUP_FORMATION_TYPE_RANDOM: + { + break; + } + + // single file formation + case SPAWN_GROUP_FORMATION_TYPE_SINGLE_FILE: + { + uint32 membCount = 1; + for (auto& slotItr : slots) + { + auto& slot = slotItr.second; + + if (slot->GetOwner() && slot->GetOwner() == GetMaster()) + { + slot->SetAngle(0); + slot->SetDistance(0); + continue; + } + + if (onlyAlive && (!slot->GetOwner() || !slot->GetOwner()->IsAlive())) + continue; + + slot->SetAngle(M_PI_F); + slot->SetDistance(defaultDist * membCount); + slot->GetRecomputePosition() = true; + ++membCount; + } + break; + } + + // side by side formation + case SPAWN_GROUP_FORMATION_TYPE_SIDE_BY_SIDE: + { + uint32 membCount = 1; + for (auto& slotItr : slots) + { + auto& slot = slotItr.second; + + if (slot->GetOwner() && slot->GetOwner() == GetMaster()) + { + slot->SetAngle(0); + slot->SetDistance(0); + continue; + } + + if (onlyAlive && (!slot->GetOwner() || !slot->GetOwner()->IsAlive())) + continue; + + if ((membCount & 1) == 0) + slot->SetAngle((M_PI_F / 2.0f) + M_PI_F); + else + slot->SetAngle(M_PI_F / 2.0f); + slot->SetDistance(defaultDist * (((membCount - 1) / 2) + 1)); + slot->GetRecomputePosition() = true; + ++membCount; + } + break; + } + + // like a geese formation + case SPAWN_GROUP_FORMATION_TYPE_LIKE_GEESE: + { + uint32 membCount = 1; + for (auto& slotItr : slots) + { + auto& slot = slotItr.second; + + if (slot->GetOwner() && slot->GetOwner() == GetMaster()) + { + slot->SetAngle(0); + slot->SetDistance(0); + continue; + } + + if (onlyAlive && (!slot->GetOwner() || !slot->GetOwner()->IsAlive())) + continue; + + if ((membCount & 1) == 0) + slot->SetAngle(M_PI_F + (M_PI_F / 4.0f)); + else + slot->SetAngle(M_PI_F - (M_PI_F / 4.0f)); + slot->SetDistance(defaultDist* (((membCount - 1) / 2) + 1)); + slot->GetRecomputePosition() = true; + ++membCount; + } + break; + } + + // fanned behind formation + case SPAWN_GROUP_FORMATION_TYPE_FANNED_OUT_BEHIND: + { + uint32 membCount = 1; + for (auto& slotItr : slots) + { + auto& slot = slotItr.second; + + if (slot->GetOwner() && slot->GetOwner() == GetMaster()) + { + slot->SetAngle(0); + slot->SetDistance(0); + continue; + } + + if (onlyAlive && (!slot->GetOwner() || !slot->GetOwner()->IsAlive())) + continue; + + slot->SetAngle((M_PI_F / 2.0f) + (M_PI_F / totalMembers) * (membCount - 1)); + slot->SetDistance(defaultDist); + slot->GetRecomputePosition() = true; + ++membCount; + } + break; + } + + // fanned in front formation + case SPAWN_GROUP_FORMATION_TYPE_FANNED_OUT_IN_FRONT: + { + uint32 membCount = 1; + for (auto& slotItr : slots) + { + auto& slot = slotItr.second; + + if (slot->GetOwner() && slot->GetOwner() == GetMaster()) + { + slot->SetAngle(0); + slot->SetDistance(0); + continue; + } + + if (onlyAlive && (!slot->GetOwner() || !slot->GetOwner()->IsAlive())) + continue; + + slot->SetAngle(M_PI_F + (M_PI_F / 2.0f) + (M_PI_F / totalMembers) * (membCount - 1)); + if (slot->GetAngle() > M_PI_F * 2.0f) + slot->SetAngle(slot->GetAngle() - M_PI_F * 2.0f); + slot->SetDistance(defaultDist); + slot->GetRecomputePosition() = true; + ++membCount; + } + break; + } + + // circle formation + case SPAWN_GROUP_FORMATION_TYPE_CIRCLE_THE_LEADER: + { + uint32 membCount = 1; + for (auto& slotItr : slots) + { + auto& slot = slotItr.second; + + if (slot->GetOwner() && slot->GetOwner() == GetMaster()) + { + slot->SetAngle(0); + slot->SetDistance(0); + continue; + } + + if (onlyAlive && (!slot->GetOwner() || !slot->GetOwner()->IsAlive())) + continue; + + slot->SetAngle(((M_PI_F * 2.0f) / totalMembers) * (membCount - 1)); + slot->SetDistance(defaultDist); + slot->GetRecomputePosition() = true; + ++membCount; + } + break; + } + default: + break; + } +} + +Unit* FormationSlotData::GetMaster() +{ + return GetFormationData()->GetMaster(); +} + +bool FormationSlotData::IsFormationMaster() +{ + return GetMaster() == m_owner; +} + +float FormationSlotData::GetAngle() +{ + return m_angle; +} + +float FormationSlotData::GetDistance() +{ + return m_distance; +} diff --git a/src/game/Maps/SpawnGroup.h b/src/game/Maps/SpawnGroup.h index 14ada47b94..df9476e8ae 100644 --- a/src/game/Maps/SpawnGroup.h +++ b/src/game/Maps/SpawnGroup.h @@ -21,7 +21,11 @@ #include "Platform/Define.h" #include "Entities/ObjectGuid.h" +#include "SpawnGroupDefines.h" +#include "MotionGenerators/MotionMaster.h" #include +#include +#include class WorldObject; class Creature; @@ -29,6 +33,7 @@ class GameObject; struct SpawnGroupEntry; class Map; class Unit; +class CreatureGroup; class SpawnGroup { @@ -40,8 +45,14 @@ class SpawnGroup virtual void Update(); uint32 GetEligibleEntry(std::map& existingEntries, std::map& minEntries); virtual void Spawn(bool force); + std::string to_string() const; uint32 GetObjectTypeId() const { return m_objectTypeId; } void SetEnabled(bool enabled) { m_enabled = enabled; } + SpawnGroupEntry const& GetGroupEntry() const { return m_entry; } + uint32 GetGroupId() const { return m_entry.Id; } + + virtual CreatureGroup* GetCreatureGroup() { return nullptr; } + protected: SpawnGroupEntry const& m_entry; Map& m_map; @@ -55,11 +66,28 @@ class CreatureGroup : public SpawnGroup public: CreatureGroup(SpawnGroupEntry const& entry, Map& map); void RemoveObject(WorldObject* wo) override; - void TriggerLinkingEvent(uint32 event, Unit* target); + void SetFormationData(FormationEntrySPtr fEntry); + FormationData* GetFormationData() { return m_formationData.get(); } + FormationEntrySPtr GetFormationEntry() const { return m_entry.formationEntry; } + + virtual void Update() override; + + virtual CreatureGroup* GetCreatureGroup() override { return this; } + + Map& GetMap() { return m_map; } + + void MoveHome(); + private: void ClearRespawnTimes(); + FormationDataSPtr m_formationData; +}; + +struct RespawnPosistion +{ + float x, y, z, radius; }; class GameObjectGroup : public SpawnGroup @@ -69,4 +97,114 @@ class GameObjectGroup : public SpawnGroup void RemoveObject(WorldObject* wo) override; }; +class FormationSlotData +{ + public: + FormationSlotData(uint32 slotId, uint32 _ownerDBGuid, CreatureGroup* creatureGrp, SpawnGroupFormationSlotType type = SpawnGroupFormationSlotType::SPAWN_GROUP_FORMATION_SLOT_TYPE_STATIC) + : m_slotId(slotId), m_realOwnerGuid(_ownerDBGuid), m_creatureGroup(creatureGrp), m_slotType(type), m_owner(nullptr), + m_angle(0), m_distance(1), m_recomputePosition(true) {} + + uint32 GetSlotId() const { return m_slotId; } + Unit* GetMaster(); + bool IsFormationMaster(); + + float GetAngle(); + float GetDistance(); + + void SetAngle(float angle) { m_angle = angle; } + void SetDistance(float distance) { m_distance = distance; } + + bool& GetRecomputePosition() { return m_recomputePosition; } + + CreatureGroup* GetCreatureGroup() { return m_creatureGroup; } + FormationData* GetFormationData() { return m_creatureGroup->GetFormationData(); } + void SetOwner(Unit* owner) { m_owner = owner; } + Unit* GetOwner() { return m_owner; } + uint32 GetRealOwnerGuid() const { return m_realOwnerGuid; } + SpawnGroupFormationSlotType GetSlotType() const { return m_slotType; } + bool IsRemovable() const + { + switch (m_slotType) + { + case SPAWN_GROUP_FORMATION_SLOT_TYPE_STATIC: + return false; + case SPAWN_GROUP_FORMATION_SLOT_TYPE_SCRIPT: + case SPAWN_GROUP_FORMATION_SLOT_TYPE_PLAYER: + return true; + default: + break; + } + return false; + } + + private: + uint32 m_slotId; + uint32 m_realOwnerGuid; + CreatureGroup* m_creatureGroup; + Unit* m_owner; + float m_angle; + float m_distance; + bool m_recomputePosition; + SpawnGroupFormationSlotType m_slotType; +}; + +class FormationData +{ + public: + FormationData(CreatureGroup* gData, FormationEntrySPtr fEntry); + FormationData() = delete; + ~FormationData(); + + Unit* GetMaster(); + void Add(Creature* creature); + void Add(Player* player); + void Remove(Creature* creature); + void Remove(Player* player); + void Remove(Unit* unit); + void Reset(); + void Disband(); + void OnDeath(Creature* creature); + void OnDelete(Creature* creature); + void Compact(bool set = true); + bool SwitchFormation(SpawnGroupFormationType newShape); + void SetSpread(float spread) { m_currentSpread = spread; FixSlotsPositions(); } + void SetOptions(uint32 options); + + FormationSlotDataSPtr SetFormationSlot(Creature* creature, SpawnGroupFormationSlotType slotType = SPAWN_GROUP_FORMATION_SLOT_TYPE_STATIC); + std::string to_string() const; + + private: + void SetMasterMovement(); + bool TrySetNewMaster(Unit* masterCandidat = nullptr); + FormationSlotDataSPtr GetFirstEmptySlot(); + FormationSlotDataSPtr GetFirstAliveSlot(); + int32 GetDefaultSlotId(uint32 dbGuid); + FormationSlotDataSPtr GetDefaultSlot(uint32 dbGuid, SpawnGroupFormationSlotType slotType = SPAWN_GROUP_FORMATION_SLOT_TYPE_STATIC); + void FixSlotsPositions(); + void SwitchSlotOwner(FormationSlotDataSPtr slotA, FormationSlotDataSPtr slotB); + bool SetFollowersMaster(); + void ClearMoveGen(); + bool FreeSlot(FormationSlotDataSPtr slot); + bool AddInFormationSlot(Unit* newUnit, SpawnGroupFormationSlotType slotType = SPAWN_GROUP_FORMATION_SLOT_TYPE_STATIC); + bool HaveOption(SpawGroupFormationOptions const& option, uint32 const& options) const { return (static_cast(options) & option) != 0; } + bool HaveOption(SpawGroupFormationOptions const& option) const { return (static_cast(m_currentOptions) & option) != 0; } + + CreatureGroup* m_groupData; + FormationEntrySPtr m_fEntry; + SpawnGroupFormationType m_currentFormationShape; + FormationSlotMap m_slotsMap; + MovementGeneratorType m_masterMotionType; + RespawnPosistion m_spawnPos; + + bool m_mirrorState; + + uint32 m_realMasterDBGuid; + uint32 m_slotGuid; + uint32 m_lastWP; + uint32 m_wpPathId; + uint32 m_currentOptions; + + float m_currentSpread; +}; + #endif \ No newline at end of file diff --git a/src/game/Maps/SpawnGroupDefines.h b/src/game/Maps/SpawnGroupDefines.h index f9d1da3e69..d7ba17ef7c 100644 --- a/src/game/Maps/SpawnGroupDefines.h +++ b/src/game/Maps/SpawnGroupDefines.h @@ -23,6 +23,16 @@ #include #include #include +#include + +struct FormationEntry; +class FormationData; +class FormationSlotData; + +typedef std::shared_ptr FormationSlotDataSPtr; +typedef std::shared_ptr FormationDataSPtr; +typedef std::shared_ptr FormationEntrySPtr; +typedef std::map FormationSlotMap; struct SpawnGroupRandomEntry { @@ -40,6 +50,7 @@ struct SpawnGroupDbGuids { uint32 Id; uint32 DbGuid; + int32 SlotId; }; enum SpawnGroupType @@ -78,6 +89,53 @@ struct SpawnGroupEntry std::vector EquallyChanced; std::vector ExplicitlyChanced; std::vector LinkedGroups; + + // may be nullptr + FormationEntrySPtr formationEntry; + + int32 GetFormationSlotId(uint32 dbGuid) const + { + auto const& itr = std::find_if(DbGuids.begin(), DbGuids.end(), [dbGuid](SpawnGroupDbGuids const& x) { return x.DbGuid == dbGuid; }); + return itr != DbGuids.end() ? (*itr).SlotId : -1; + } +}; + +// Formation defines +enum SpawnGroupFormationType : uint32 +{ + SPAWN_GROUP_FORMATION_TYPE_RANDOM = 0, + SPAWN_GROUP_FORMATION_TYPE_SINGLE_FILE = 1, + SPAWN_GROUP_FORMATION_TYPE_SIDE_BY_SIDE = 2, + SPAWN_GROUP_FORMATION_TYPE_LIKE_GEESE = 3, + SPAWN_GROUP_FORMATION_TYPE_FANNED_OUT_BEHIND = 4, + SPAWN_GROUP_FORMATION_TYPE_FANNED_OUT_IN_FRONT = 5, + SPAWN_GROUP_FORMATION_TYPE_CIRCLE_THE_LEADER = 6, + + SPAWN_GROUP_FORMATION_TYPE_COUNT = 7 +}; + +enum SpawnGroupFormationSlotType : uint32 +{ + SPAWN_GROUP_FORMATION_SLOT_TYPE_STATIC = 0, + SPAWN_GROUP_FORMATION_SLOT_TYPE_SCRIPT = 1, + SPAWN_GROUP_FORMATION_SLOT_TYPE_PLAYER = 2, +}; + +enum SpawGroupFormationOptions : uint32 +{ + SPAWN_GROUP_FORMATION_OPTION_NONE = 0x00, + SPAWN_GROUP_FORMATION_OPTION_KEEP_CONPACT = 0x01 +}; + +struct FormationEntry +{ + uint32 GroupId; + SpawnGroupFormationType Type; + uint32 MovementID; + uint32 MovementType; + float Spread; + uint32 Options; + std::string Comment; }; struct SpawnGroupEntryContainer diff --git a/src/game/MotionGenerators/MotionMaster.cpp b/src/game/MotionGenerators/MotionMaster.cpp index 71c7302a30..23d0f64924 100644 --- a/src/game/MotionGenerators/MotionMaster.cpp +++ b/src/game/MotionGenerators/MotionMaster.cpp @@ -73,7 +73,7 @@ void MotionMaster::Initialize() push(movement == nullptr ? &si_idleMovement : movement); top()->Initialize(*m_owner); if (top()->GetMovementGeneratorType() == WAYPOINT_MOTION_TYPE) - (static_cast*>(top()))->InitializeWaypointPath(*((Creature*)(m_owner)), m_currentPathId, PATH_NO_PATH, 0, 0); + (static_cast*>(top()))->InitializeWaypointPath(*((Creature*)(m_owner)), m_currentPathId, PATH_NO_PATH, 0); } else push(&si_idleMovement); @@ -346,6 +346,27 @@ void MotionMaster::MoveFollow(Unit* target, float dist, float angle, bool asMain Mutate(new FollowMovementGenerator(*target, dist, angle, asMain, (m_owner->IsPlayer() && !m_owner->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED)), alwaysBoost)); } +void MotionMaster::MoveInFormation(FormationSlotDataSPtr& sData, bool asMain /*= false*/) +{ + if (m_owner->hasUnitState(UNIT_STAT_LOST_CONTROL)) + return; + + if (asMain) + Clear(false, true); + else + Clear(!empty()); // avoid resetting if we are already empty + + auto master = sData->GetMaster(); + // ignore movement request if target not exist + if (!sData->GetOwner() || !master) + return; + + DEBUG_FILTER_LOG(LOG_FILTER_AI_AND_MOVEGENSS, "%s is in formation with %s", m_owner->GetGuidStr().c_str(), master->GetGuidStr().c_str()); + sLog.outString("%s is in formation with %s", m_owner->GetGuidStr().c_str(), master->GetGuidStr().c_str()); + + Mutate(new FormationMovementGenerator(sData, asMain)); +} + void MotionMaster::MoveStay(float x, float y, float z, float o, bool asMain) { if (m_owner->hasUnitState(UNIT_STAT_LOST_CONTROL)) @@ -423,7 +444,7 @@ void MotionMaster::MoveFleeing(Unit* source, uint32 time) Mutate(new FleeingMovementGenerator(*source)); } -void MotionMaster::MoveWaypoint(uint32 pathId /*=0*/, uint32 source /*=0==PATH_NO_PATH*/, uint32 initialDelay /*=0*/, uint32 overwriteEntry /*=0*/, ForcedMovement forcedMovement /*=FORCED_MOVEMENT_NONE*/, ObjectGuid guid/* = ObjectGuid()*/) +void MotionMaster::MoveWaypoint(uint32 pathId /*= 0*/, uint32 source /*= 0*/, uint32 initialDelay /*= 0*/, uint32 overwriteEntry /*= 0*/, ForcedMovement forcedMovement /*= FORCED_MOVEMENT_NONE*/, ObjectGuid guid /*= ObjectGuid()*/) { if (m_owner->GetTypeId() == TYPEID_UNIT) { diff --git a/src/game/MotionGenerators/MotionMaster.h b/src/game/MotionGenerators/MotionMaster.h index 3e29427990..a4a780260c 100644 --- a/src/game/MotionGenerators/MotionMaster.h +++ b/src/game/MotionGenerators/MotionMaster.h @@ -23,6 +23,7 @@ #include "Globals/SharedDefines.h" #include "MotionGenerators/WaypointManager.h" #include "Entities/ObjectGuid.h" +#include "Maps/SpawnGroupDefines.h" #include #include @@ -74,8 +75,39 @@ enum MovementGeneratorType EXTERNAL_WAYPOINT_MOVE = 17, // Only used in UnitAI::MovementInform when a waypoint is reached. The pathId >= 0 is added as additonal value EXTERNAL_WAYPOINT_MOVE_START = 18, // Only used in UnitAI::MovementInform when a waypoint is started. The pathId >= 0 is added as additional value EXTERNAL_WAYPOINT_FINISHED_LAST = 19, // Only used in UnitAI::MovementInform when the waittime of the last wp is finished The pathId >= 0 is added as additional value + + FORMATION_MOTION_TYPE = 20, // TargetedMovementGenerator.h }; +static const char* GetMoveTypeStr(MovementGeneratorType mType) +{ + switch (mType) + { + case IDLE_MOTION_TYPE: return "IDLE_MOTION_TYPE"; + case RANDOM_MOTION_TYPE: return "RANDOM_MOTION_TYPE"; + case WAYPOINT_MOTION_TYPE: return "WAYPOINT_MOTION_TYPE"; + case PATH_MOTION_TYPE: return "PATH_MOTION_TYPE"; + case DISTRACT_MOTION_TYPE: return "DISTRACT_MOTION_TYPE"; + case STAY_MOTION_TYPE: return "STAY_MOTION_TYPE"; + case FOLLOW_MOTION_TYPE: return "FOLLOW_MOTION_TYPE"; + case CHASE_MOTION_TYPE: return "CHASE_MOTION_TYPE"; + case RETREAT_MOTION_TYPE: return "RETREAT_MOTION_TYPE"; + case TIMED_FLEEING_MOTION_TYPE: return "TIMED_FLEEING_MOTION_TYPE"; + case POINT_MOTION_TYPE: return "POINT_MOTION_TYPE"; + case HOME_MOTION_TYPE: return "HOME_MOTION_TYPE"; + case FLEEING_MOTION_TYPE: return "FLEEING_MOTION_TYPE"; + case CONFUSED_MOTION_TYPE: return "CONFUSED_MOTION_TYPE"; + case EFFECT_MOTION_TYPE: return "EFFECT_MOTION_TYPE"; + case TAXI_MOTION_TYPE: return "TAXI_MOTION_TYPE"; + case TIMED_RANDOM_MOTION_TYPE: return "TIMED_RANDOM_MOTION_TYPE"; + case EXTERNAL_WAYPOINT_MOVE: return "EXTERNAL_WAYPOINT_MOVE"; + case EXTERNAL_WAYPOINT_MOVE_START: return "EXTERNAL_WAYPOINT_MOVE_START"; + case EXTERNAL_WAYPOINT_FINISHED_LAST: return "EXTERNAL_WAYPOINT_FINISHED_LAST"; + case FORMATION_MOTION_TYPE: return "FORMATION_MOTION_TYPE"; + default: return "UKNOWN_MOTION_TYPE"; + } +} + enum MMCleanFlag { MMCF_NONE = 0, @@ -134,6 +166,7 @@ class MotionMaster : private std::stack void MoveRandomAroundPoint(float x, float y, float z, float radius, float verticalZ = 0.0f, uint32 timer = 0); void MoveTargetedHome(bool runHome = true); void MoveFollow(Unit* target, float dist, float angle, bool asMain = false, bool alwaysBoost = false); + void MoveInFormation(FormationSlotDataSPtr& sData, bool asMain = false); void MoveStay(float x, float y, float z, float o = 0, bool asMain = false); void MoveChase(Unit* target, float dist = 0.0f, float angle = 0.0f, bool moveFurther = false, bool walk = false, bool combat = true, bool delayed = false); void DistanceYourself(float dist); diff --git a/src/game/MotionGenerators/TargetedMovementGenerator.cpp b/src/game/MotionGenerators/TargetedMovementGenerator.cpp index 3ae82b6aec..ae6b182735 100644 --- a/src/game/MotionGenerators/TargetedMovementGenerator.cpp +++ b/src/game/MotionGenerators/TargetedMovementGenerator.cpp @@ -1133,6 +1133,275 @@ void FollowMovementGenerator::HandleFinalizedMovement(Unit& owner) _reachTarget(owner); } +FormationMovementGenerator::~FormationMovementGenerator() +{ + //sLog.outError("Removing formation movegen for %s", m_slot->GetOwner()->GetGuidStr().c_str()); +} + +bool FormationMovementGenerator::Update(Unit& unit, const uint32& diff) +{ + Unit* master = m_slot->GetMaster(); + if (!i_target.isValid() || !master) + return false; + + // handle master change + if (i_target.getTarget() != master) + SetNewTarget(*master); + + return TargetedMovementGeneratorMedium::Update(unit, diff); +} + +float FormationMovementGenerator::BuildPath(Unit& owner, PointsArray& path) +{ + float speed = -1.0f; + auto const& masterSpline = i_target->movespline; + + float slotAngle = m_slot->GetAngle(); + float slotDist = m_slot->GetDistance(); + float angle = i_target->GetOrientation(); + bool isOnGround = !owner.IsFlying() && !owner.IsInWater() && !owner.HasHoverAura(); + + if (masterSpline->Finalized()) + { + Vector3 masterPos(i_target->GetPositionX(), i_target->GetPositionY(), i_target->GetPositionZ()); + bool done = false; + angle += slotAngle; + Position bestPos(masterPos.x, masterPos.y, masterPos.z, 0); + i_target->MovePositionToFirstCollision(bestPos, slotDist, angle); + Vector3 nextPos(bestPos.x, bestPos.y, bestPos.z); + + float lenght = (masterPos - nextPos).magnitude(); + if (lenght > 0.1f) + { + path.emplace_back(owner.GetPositionX(), owner.GetPositionY(), owner.GetPositionZ()); + path.push_back(nextPos); + speed = owner.GetSpeed(MOVE_WALK); + } + } + else + { + // guessed values from sniff and ingame check + float distAhead = (masterSpline->Speed() / 3.0f) * 4.0f; + float distGood = distAhead - (distAhead / 5); + + float slaveTravelDistance = 0; + Vector3 ownerPos(owner.GetPositionX(), owner.GetPositionY(), owner.GetPositionZ()); + path.emplace_back(ownerPos); + + int32 masterTravelTime = 0; + Vector3 masterPrevPoint(i_target->GetPositionX(), i_target->GetPositionY(), i_target->GetPositionZ()); + + float pathLen = 0; + for (int32 pathIdx = masterSpline->GetRawPathIndex() + 1; pathIdx <= masterSpline->_Spline().last(); ++pathIdx) + { + float pathFactor = 1.0f; + Vector3 nextMasterDest = masterSpline->_Spline().getPoint(pathIdx); + + float tDist = (masterPrevPoint - nextMasterDest).length(); + if (tDist > distAhead) + { + // path is too long we have to reduce its size a bit + float missingDist = distAhead - slaveTravelDistance; + pathFactor = 1.0f / (tDist / missingDist); + nextMasterDest = masterPrevPoint.lerp(nextMasterDest, pathFactor); + } + + // we have to compute new angle between two intermediate points + Vector3 direction = nextMasterDest - masterPrevPoint; + angle = atan2(direction.y, direction.x) + slotAngle; + + // get best possible point near the slot position + Position bestPos(nextMasterDest.x, nextMasterDest.y, nextMasterDest.z, 0); + i_target->MovePositionToFirstCollision(bestPos, slotDist, angle); + Vector3 nextPos(bestPos.x, bestPos.y, bestPos.z); + + // compute travel time and slave dist + Vector3& prevSlavePos = path[path.size() - 1]; + pathLen = (prevSlavePos - nextPos).length(); + if (pathLen > 0.5f) + { + // compute lenght + slaveTravelDistance += pathLen; + + // compute time (adjust with previously computed factor) + int32 nextPointTime = masterSpline->ComputeTimeToIndex(pathIdx) - masterTravelTime; + masterTravelTime = masterTravelTime + (nextPointTime * pathFactor); + + // time and lenght are added properly we can then push the next position for the follower + path.push_back(nextPos); + } + + // distance to travel is good enough the last yard will be handled in next update + if (slaveTravelDistance > distGood) + break; + + masterPrevPoint = nextMasterDest; + } + + // compute slave speed + if (slaveTravelDistance > 0.1f) + { + // Speed computation + float masterSpeed = masterSpline->Speed(); + + // define some factor that will influence slave speed to smooth its movement + static const float speedFactor = 2.0f; + + // compute the slave factor of speed that will be added/removed from master speed + speed = ((slaveTravelDistance / (masterTravelTime / 1000.0f)) - masterSpeed) / speedFactor; + speed = masterSpeed + speed; + + // clamp the speed to some limit + speed = std::max(0.5f, speed); + speed = std::min(masterSpeed * 2, speed); + } + } + return speed; +} + +// return true if follower is far from master and heading to it +bool FormationMovementGenerator::HandleMasterDistanceCheck(Unit& owner, const uint32& time_diff) +{ + Unit* master = m_slot->GetMaster(); + if (!m_headingToMaster || i_recheckDistance.Passed()) + { + float distToMaster = owner.GetDistance(master); + if (distToMaster > 200) + { + Position const& mPos = master->GetPosition(); + owner.NearTeleportTo(mPos.x, mPos.y, mPos.z, mPos.o); + + m_slot->GetRecomputePosition() = true; + //sLog.outString("BIG TELEPORT TO MASTER!!"); + return true; + } + else if (distToMaster > 20) + { + Position const& mPos = master->GetPosition(); + _addUnitStateMove(owner); + + Movement::MoveSplineInit init(owner); + init.MoveTo(mPos.x, mPos.y, mPos.z, true, true); + init.SetFacing(mPos.o); + + init.SetWalk(false); + //init.SetVelocity(0); + + i_recheckDistance.Reset(init.Launch() / 2); + m_headingToMaster = true; + //sLog.outString("MOVE BACK FOLLOWER TO MASTER!!"); + return true; + } + else + { + m_headingToMaster = false; + } + } + else + { + i_recheckDistance.Update(time_diff); + return true; + } + return false; +} + +void FormationMovementGenerator::HandleTargetedMovement(Unit& owner, const uint32& time_diff) +{ + if (HandleMasterDistanceCheck(owner, time_diff)) + return; + + // Detect target movement and relocation + const bool targetMovingLast = m_targetMoving; + // If moving in any direction (not count jumping in place) + m_targetMoving = i_target->m_movementInfo.HasMovementFlag(MovementFlags(movementFlagsMask & ~(MOVEFLAG_FALLING | MOVEFLAG_FALLINGFAR))); + bool targetRelocation = false; + bool targetOrientation = false; + bool needToRePos = false; + + if (m_targetMoving && (!targetMovingLast || owner.movespline->Finalized())) // Movement just started or master is moving and not owner: force update + targetRelocation = true; + else // Periodic dist poll: fast when moving, slow when stationary + { + i_recheckDistance.Update(time_diff); + + if (i_recheckDistance.Passed()) + { + i_recheckDistance.Reset(1000); + + G3D::Vector3 currentTargetPos; + + i_target->GetPosition(currentTargetPos.x, currentTargetPos.y, currentTargetPos.z); + + targetRelocation = (currentTargetPos != i_lastTargetPos); + targetOrientation = (!targetRelocation && !m_targetMoving && !m_targetFaced); + i_lastTargetPos = currentTargetPos; + } + } + + if (m_slot->GetRecomputePosition()) + { + if (i_target->movespline->Finalized() || !targetRelocation) + needToRePos = true; + + m_slot->GetRecomputePosition() = false; + } + + // Decide whether it's suitable time to update position or orientation + if (targetRelocation || needToRePos) + { + _setLocation(owner, needToRePos); + } +} + +void FormationMovementGenerator::HandleFinalizedMovement(Unit& owner) +{ + + if (!i_target->movespline->Finalized()) + return; + + // signal that we have finalized the movement + //m_slot->OnMovementStop(); + + i_targetReached = true; +} + +void FormationMovementGenerator::_setLocation(Unit& owner, bool needRepos) +{ + PointsArray newPath; + float speed = -1.f; + speed = BuildPath(owner, newPath); + + if (speed < 1.0f) + return; + + _addUnitStateMove(owner); + + Movement::MoveSplineInit init(owner); + init.MovebyPath(newPath); + init.SetFacing(i_target->GetOrientation()); + + if (needRepos) + init.SetWalk(true); + else + { + init.SetWalk(EnableWalking()); + init.SetVelocity(speed); + } + + int32 nextUpdate = init.Launch() - FORMATION_FOLLOWERS_CHECK_OVERRIDE; + if (nextUpdate < 0) + nextUpdate = 0; + + i_recheckDistance.Reset(nextUpdate); + + // signal that we have started to move + //m_slot->OnMovementStart(); + + i_targetReached = false; + i_speedChanged = false; + m_targetFaced = false; +} + //-----------------------------------------------// template bool TargetedMovementGeneratorMedium::Update(Unit&, const uint32&); template bool TargetedMovementGeneratorMedium::Update(Unit&, const uint32&); diff --git a/src/game/MotionGenerators/TargetedMovementGenerator.h b/src/game/MotionGenerators/TargetedMovementGenerator.h index 0ddeff1fd4..e42ada8f12 100644 --- a/src/game/MotionGenerators/TargetedMovementGenerator.h +++ b/src/game/MotionGenerators/TargetedMovementGenerator.h @@ -223,7 +223,7 @@ class FollowMovementGenerator : public TargetedMovementGeneratorMediumGetMaster(), sData->GetDistance(), sData->GetDistance(), main, false, false), + m_slot(sData), m_headingToMaster(false) + { + } + ~FormationMovementGenerator(); + + MovementGeneratorType GetMovementGeneratorType() const override { return FORMATION_MOTION_TYPE; } + + virtual bool Update(Unit&, const uint32&) override; + + protected: + void HandleTargetedMovement(Unit& owner, const uint32& time_diff) override; + void HandleFinalizedMovement(Unit& owner) override; + + private: + virtual void _setLocation(Unit& owner, bool catchup) override; + float BuildPath(Unit& owner, PointsArray& path); + bool HandleMasterDistanceCheck(Unit& owner, const uint32& time_diff); + FormationSlotDataSPtr m_slot; + bool m_headingToMaster; +}; + #endif diff --git a/src/game/MotionGenerators/WaypointManager.cpp b/src/game/MotionGenerators/WaypointManager.cpp index 0f6ae03e89..737d34a7d5 100644 --- a/src/game/MotionGenerators/WaypointManager.cpp +++ b/src/game/MotionGenerators/WaypointManager.cpp @@ -31,8 +31,8 @@ char const* waypointOriginTables[] = "", "creature_movement", "creature_movement_template", - "DND", - "creature_movement_reference", + "DND",// Todo:: delete this DND? + "movement_template", }; char const* waypointKeyColumn[] = @@ -382,6 +382,96 @@ void WaypointManager::Load() sLog.outString(); } + // ///////////////////////////////////////////////////// + // waypoint_path + // ///////////////////////////////////////////////////// + + result = WorldDatabase.Query("SELECT entry, COUNT(point) FROM waypoint_path GROUP BY entry"); + + if (!result) + { + BarGoLink bar(1); + bar.step(); + sLog.outString(">> Loaded 0 path templates. DB table `waypoint_path` is empty."); + sLog.outString(); + } + else + { + total_nodes = 0; + total_behaviors = 0; + total_paths = (uint32)result->GetRowCount(); + + do // Count expected amount of nodes + { + Field* fields = result->Fetch(); + + // uint32 entry = fields[0].GetUInt32(); + uint32 count = fields[1].GetUInt32(); + + total_nodes += count; + } while (result->NextRow()); + delete result; + + // 0 1 2 3 4 5 6 7 + result = WorldDatabase.Query("SELECT entry, pathId, point, position_x, position_y, position_z, orientation, waittime, script_id FROM waypoint_path"); + + BarGoLink bar(result->GetRowCount()); + std::set blacklistWaypoints; + + do + { + bar.step(); + Field* fields = result->Fetch(); + + uint32 entry = fields[0].GetUInt32(); + uint32 pathId = fields[1].GetUInt32(); + uint32 point = fields[2].GetUInt32(); + + if (point == 0) + { + blacklistWaypoints.insert((entry << 8) + pathId); + sLog.outErrorDb("Table `waypoint_path` has invalid point 0 for entry %u in path %u. Skipping.`", entry, pathId); + } + + WaypointPath& path = m_pathMovementTemplateMap[(entry << 8) + pathId]; + WaypointNode& node = path[point]; + + node.x = fields[3].GetFloat(); + node.y = fields[4].GetFloat(); + node.z = fields[5].GetFloat(); + node.orientation = fields[6].GetFloat(); + node.delay = fields[7].GetUInt32(); + node.script_id = fields[8].GetUInt32(); + + // prevent using invalid coordinates + if (!MaNGOS::IsValidMapCoord(node.x, node.y, node.z, node.orientation)) + { + sLog.outErrorDb("Table waypoint_path for entry %u (point %u) are using invalid coordinates position_x: %f, position_y: %f)", + entry, point, node.x, node.y); + + MaNGOS::NormalizeMapCoord(node.x); + MaNGOS::NormalizeMapCoord(node.y); + + sLog.outErrorDb("Table waypoint_path for entry %u (point %u) are auto corrected to normalized position_x=%f, position_y=%f", + entry, point, node.x, node.y); + + WorldDatabase.PExecute("UPDATE waypoint_path SET position_x = '%f', position_y = '%f' WHERE entry = %u AND point = %u AND pathId = %u", node.x, node.y, entry, point, pathId); + } + + if (node.script_id) + CheckDbscript(node, entry, point, movementScriptSet, "waypoint_path"); + } while (result->NextRow()); + + delete result; + + // sanitize waypoints + for (uint32 itr : blacklistWaypoints) + m_pathTemplateMap.erase(itr); + + sLog.outString(">> Loaded %u path templates with %u nodes and %u behaviors from waypoint movement templates", total_paths, total_nodes, total_behaviors); + sLog.outString(); + } + if (!movementScriptSet.empty()) { for (uint32 itr : movementScriptSet) diff --git a/src/game/MotionGenerators/WaypointManager.h b/src/game/MotionGenerators/WaypointManager.h index e469429382..b841208b2c 100644 --- a/src/game/MotionGenerators/WaypointManager.h +++ b/src/game/MotionGenerators/WaypointManager.h @@ -23,10 +23,11 @@ enum WaypointPathOrigin { - PATH_NO_PATH = 0, - PATH_FROM_GUID = 1, - PATH_FROM_ENTRY = 2, - PATH_FROM_EXTERNAL = 3, + PATH_NO_PATH = 0, + PATH_FROM_GUID = 1, + PATH_FROM_ENTRY = 2, + PATH_FROM_EXTERNAL = 3, + PATH_FROM_MOVEMENT_TEMPLATE = 4, }; struct WaypointNode @@ -75,6 +76,14 @@ class WaypointManager path = GetPathTemplate(entry); if (path && wpOrigin) *wpOrigin = PATH_FROM_ENTRY; + + if (!path) + { + // check movement_template + path = GetPathMovementTemplate(entry); + if (path) + *wpOrigin = PATH_FROM_MOVEMENT_TEMPLATE; + } } return path; @@ -104,6 +113,12 @@ class WaypointManager key = (entry << 8) + pathId; wpMap = &m_externalPathTemplateMap; break; + case PATH_FROM_MOVEMENT_TEMPLATE: + if (pathId >= 0xFF || pathId < 0) + return nullptr; + key = (entry << 8) + pathId; + wpMap = &m_pathMovementTemplateMap; + break; case PATH_NO_PATH: default: return nullptr; @@ -157,16 +172,23 @@ class WaypointManager return itr != m_pathTemplateMap.end() ? &itr->second : nullptr; } + WaypointPath* GetPathMovementTemplate(uint32 entry) + { + WaypointPathMap::iterator itr = m_pathMovementTemplateMap.find((entry << 8) /*+ pathId*/); + return itr != m_pathMovementTemplateMap.end() ? &itr->second : nullptr; + } + typedef std::unordered_map WaypointPathMap; WaypointPathMap* getMapForPathType(WaypointPathOrigin origin) { switch (origin) { - case PATH_NO_PATH: return nullptr; - case PATH_FROM_GUID: return &m_pathMap; - case PATH_FROM_ENTRY: return &m_pathTemplateMap; - case PATH_FROM_EXTERNAL: return &m_externalPathTemplateMap; + case PATH_NO_PATH : return nullptr; + case PATH_FROM_GUID : return &m_pathMap; + case PATH_FROM_ENTRY : return &m_pathTemplateMap; + case PATH_FROM_EXTERNAL : return &m_externalPathTemplateMap; + case PATH_FROM_MOVEMENT_TEMPLATE : return &m_pathMovementTemplateMap; default: return nullptr; } } @@ -174,6 +196,8 @@ class WaypointManager WaypointPathMap m_pathMap; WaypointPathMap m_pathTemplateMap; WaypointPathMap m_externalPathTemplateMap; + WaypointPathMap m_pathMovementTemplateMap; + std::string m_externalTable; }; diff --git a/src/game/MotionGenerators/WaypointMovementGenerator.cpp b/src/game/MotionGenerators/WaypointMovementGenerator.cpp index c6ee9c15c3..7d18e43a85 100644 --- a/src/game/MotionGenerators/WaypointMovementGenerator.cpp +++ b/src/game/MotionGenerators/WaypointMovementGenerator.cpp @@ -39,6 +39,12 @@ void WaypointMovementGenerator::LoadPath(Creature& creature, int32 pat if (!overwriteEntry) overwriteEntry = creature.GetEntry(); + //sLog.outErrorScriptLib("LoadPath: Assign creature guid(%u) and entry(%u) path %i", creature.GetDbGuid(), overwriteEntry, pathId); + + // enforce movement_template table for creature group + if (creature.GetCreatureGroup()) + wpOrigin = WaypointPathOrigin::PATH_FROM_MOVEMENT_TEMPLATE; + if (wpOrigin == PATH_NO_PATH && pathId == 0) i_path = sWaypointMgr.GetDefaultPath(overwriteEntry, creature.GetDbGuid(), &m_PathOrigin); else @@ -87,7 +93,7 @@ void WaypointMovementGenerator::Initialize(Creature& creature) creature.clearUnitState(UNIT_STAT_WAYPOINT_PAUSED); } -void WaypointMovementGenerator::InitializeWaypointPath(Creature& u, int32 pathId, WaypointPathOrigin wpSource, uint32 initialDelay, uint32 overwriteEntry) +void WaypointMovementGenerator::InitializeWaypointPath(Creature& u, int32 pathId, WaypointPathOrigin wpSource, uint32 initialDelay, uint32 overwriteEntry/* = 0*/) { LoadPath(u, pathId, wpSource, overwriteEntry); i_nextMoveTime.Reset(initialDelay); @@ -563,6 +569,9 @@ bool WaypointMovementGenerator::SetNextWaypoint(uint32 pointId) if (!i_path || i_path->empty()) return false; + // point id should not be bigger than the size of the path -1 + pointId = uint32(pointId % i_path->size()); + WaypointPath::const_iterator currPoint = i_path->find(pointId); if (currPoint == i_path->end()) return false; @@ -575,5 +584,12 @@ bool WaypointMovementGenerator::SetNextWaypoint(uint32 pointId) // Set the point i_currentNode = pointId; m_currentWaypointNode = currPoint; + + // set last reached point accordingly to avoid going back to point 0 if the + // movegen is interrupted before reaching next point + if (pointId > 0) + m_lastReachedWaypoint = pointId - 1; + else + m_lastReachedWaypoint = i_path->rbegin()->first; return true; } diff --git a/src/game/MotionGenerators/WaypointMovementGenerator.h b/src/game/MotionGenerators/WaypointMovementGenerator.h index 3f0c1d3a62..d043b3a057 100644 --- a/src/game/MotionGenerators/WaypointMovementGenerator.h +++ b/src/game/MotionGenerators/WaypointMovementGenerator.h @@ -81,7 +81,7 @@ class WaypointMovementGenerator void Finalize(Creature&); void Reset(Creature& creature); bool Update(Creature& creature, const uint32& diff); - void InitializeWaypointPath(Creature& u, int32 pathId, WaypointPathOrigin wpSource, uint32 initialDelay, uint32 overwriteEntry); + void InitializeWaypointPath(Creature& u, int32 pathId, WaypointPathOrigin wpSource, uint32 initialDelay, uint32 overwriteEntry = 0); MovementGeneratorType GetMovementGeneratorType() const { return WAYPOINT_MOTION_TYPE; } diff --git a/src/game/Movement/MoveSpline.h b/src/game/Movement/MoveSpline.h index 70831209ed..59c3fecece 100644 --- a/src/game/Movement/MoveSpline.h +++ b/src/game/Movement/MoveSpline.h @@ -130,8 +130,10 @@ namespace Movement const Vector3 FinalDestination() const; const Vector3 CurrentDestination() const { return Initialized() ? spline.getPoint(point_Idx + 1) : Vector3();} int32 currentPathIdx() const; + int32 GetRawPathIndex() const { return point_Idx; } uint32 Duration() const { return spline.length();} + int32 ComputeTimeToIndex(uint32 idx) const { return spline.length(idx) - time_passed; } float Speed() const { return speed; } diff --git a/src/shared/revision_sql.h b/src/shared/revision_sql.h index 838c1ec19e..80e58bedba 100644 --- a/src/shared/revision_sql.h +++ b/src/shared/revision_sql.h @@ -3,5 +3,5 @@ #define REVISION_DB_REALMD "required_s2433_01_realmd_anticheat" #define REVISION_DB_LOGS "required_s2433_01_logs_anticheat" #define REVISION_DB_CHARACTERS "required_s2429_01_characters_raf" - #define REVISION_DB_MANGOS "required_s2438_01_mangos_spawn_groups" + #define REVISION_DB_MANGOS "required_s2439_01_mangos_groups_formation" #endif // __REVISION_SQL_H__