aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShauren <shauren.trinity@gmail.com>2015-05-17 18:06:28 +0200
committerShauren <shauren.trinity@gmail.com>2015-05-17 18:06:28 +0200
commit92bfa759facd47c1e375876c1df4604091811d16 (patch)
tree731f58769764f0f6852e62de2ae3c1adb05fa61f
parent051e3b631db74886479c58485fad46b9a326a966 (diff)
Core/Garrisons: Initial work on garrison followers
-rw-r--r--sql/updates/hotfixes/2015_05_17_00_hotfixes.sql205
-rw-r--r--src/server/game/DataStores/DB2Stores.cpp10
-rw-r--r--src/server/game/DataStores/DB2Stores.h4
-rw-r--r--src/server/game/DataStores/DB2Structure.h54
-rw-r--r--src/server/game/DataStores/DB2fmt.h8
-rw-r--r--src/server/game/Garrison/Garrison.cpp31
-rw-r--r--src/server/game/Garrison/Garrison.h30
-rw-r--r--src/server/game/Garrison/GarrisonMgr.cpp133
-rw-r--r--src/server/game/Garrison/GarrisonMgr.h13
-rw-r--r--src/server/game/Server/Packets/GarrisonPackets.cpp12
-rw-r--r--src/server/game/Server/Packets/GarrisonPackets.h19
-rw-r--r--src/server/game/Server/Protocol/Opcodes.cpp2
-rw-r--r--src/server/game/Spells/Spell.h1
-rw-r--r--src/server/game/Spells/SpellEffects.cpp14
-rw-r--r--src/server/shared/Database/Implementation/HotfixDatabase.cpp19
-rw-r--r--src/server/shared/Database/Implementation/HotfixDatabase.h11
16 files changed, 553 insertions, 13 deletions
diff --git a/sql/updates/hotfixes/2015_05_17_00_hotfixes.sql b/sql/updates/hotfixes/2015_05_17_00_hotfixes.sql
new file mode 100644
index 00000000000..29d18a31703
--- /dev/null
+++ b/sql/updates/hotfixes/2015_05_17_00_hotfixes.sql
@@ -0,0 +1,205 @@
+--
+-- Table structure for table `garr_ability`
+--
+
+DROP TABLE IF EXISTS `garr_ability`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `garr_ability` (
+ `ID` int(10) unsigned NOT NULL DEFAULT '0',
+ `Flags` int(10) unsigned NOT NULL DEFAULT '0',
+ `Name` text,
+ `Description` text,
+ `IconFileDataID` int(10) unsigned NOT NULL DEFAULT '0',
+ `OtherFactionGarrAbilityID` int(10) unsigned NOT NULL DEFAULT '0',
+ `GarrAbilityCategoryID` int(10) unsigned NOT NULL DEFAULT '0',
+ `VerifiedBuild` smallint(6) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`ID`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `garr_ability`
+--
+
+LOCK TABLES `garr_ability` WRITE;
+/*!40000 ALTER TABLE `garr_ability` DISABLE KEYS */;
+/*!40000 ALTER TABLE `garr_ability` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `garr_ability_locale`
+--
+
+DROP TABLE IF EXISTS `garr_ability_locale`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `garr_ability_locale` (
+ `ID` int(10) unsigned NOT NULL DEFAULT '0',
+ `locale` varchar(4) NOT NULL,
+ `Name_lang` text,
+ `Description_lang` text,
+ `VerifiedBuild` smallint(6) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`ID`,`locale`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `garr_ability_locale`
+--
+
+LOCK TABLES `garr_ability_locale` WRITE;
+/*!40000 ALTER TABLE `garr_ability_locale` DISABLE KEYS */;
+/*!40000 ALTER TABLE `garr_ability_locale` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `garr_class_spec`
+--
+
+DROP TABLE IF EXISTS `garr_class_spec`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `garr_class_spec` (
+ `ID` int(10) unsigned NOT NULL DEFAULT '0',
+ `NameMale` text,
+ `NameFemale` text,
+ `NameGenderless` text,
+ `ClassAtlasID` int(10) unsigned NOT NULL DEFAULT '0',
+ `GarrFollItemSetID` int(10) unsigned NOT NULL DEFAULT '0',
+ `VerifiedBuild` smallint(6) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`ID`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `garr_class_spec`
+--
+
+LOCK TABLES `garr_class_spec` WRITE;
+/*!40000 ALTER TABLE `garr_class_spec` DISABLE KEYS */;
+/*!40000 ALTER TABLE `garr_class_spec` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `garr_class_spec_locale`
+--
+
+DROP TABLE IF EXISTS `garr_class_spec_locale`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `garr_class_spec_locale` (
+ `ID` int(10) unsigned NOT NULL DEFAULT '0',
+ `locale` varchar(4) NOT NULL,
+ `NameMale_lang` text,
+ `NameFemale_lang` text,
+ `NameGenderless_lang` text,
+ `VerifiedBuild` smallint(6) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`ID`,`locale`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `garr_class_spec_locale`
+--
+
+LOCK TABLES `garr_class_spec_locale` WRITE;
+/*!40000 ALTER TABLE `garr_class_spec_locale` DISABLE KEYS */;
+/*!40000 ALTER TABLE `garr_class_spec_locale` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `garr_follower`
+--
+
+DROP TABLE IF EXISTS `garr_follower`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `garr_follower` (
+ `ID` int(10) unsigned NOT NULL DEFAULT '0',
+ `HordeCreatureID` int(10) unsigned NOT NULL DEFAULT '0',
+ `AllianceCreatureID` int(10) unsigned NOT NULL DEFAULT '0',
+ `HordeUiAnimRaceInfoID` int(10) unsigned NOT NULL DEFAULT '0',
+ `AllianceUiAnimRaceInfoID` int(10) unsigned NOT NULL DEFAULT '0',
+ `Quality` int(10) unsigned NOT NULL DEFAULT '0',
+ `HordeGarrClassSpecID` int(10) unsigned NOT NULL DEFAULT '0',
+ `AllianceGarrClassSpecID` int(10) unsigned NOT NULL DEFAULT '0',
+ `HordeGarrFollItemSetID` int(10) unsigned NOT NULL DEFAULT '0',
+ `AllianceGarrFollItemSetID` int(10) unsigned NOT NULL DEFAULT '0',
+ `Level` int(10) unsigned NOT NULL DEFAULT '0',
+ `ItemLevelWeapon` int(10) unsigned NOT NULL DEFAULT '0',
+ `ItemLevelArmor` int(10) unsigned NOT NULL DEFAULT '0',
+ `Unknown1` int(10) unsigned NOT NULL DEFAULT '0',
+ `Flags` int(10) unsigned NOT NULL DEFAULT '0',
+ `HordeSourceText` text,
+ `AllianceSourceText` text,
+ `Unknown2` int(11) NOT NULL DEFAULT '0',
+ `Unknown3` int(11) NOT NULL DEFAULT '0',
+ `HordePortraitIconID` int(10) unsigned NOT NULL DEFAULT '0',
+ `AlliancePortraitIconID` int(10) unsigned NOT NULL DEFAULT '0',
+ `VerifiedBuild` smallint(6) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`ID`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `garr_follower`
+--
+
+LOCK TABLES `garr_follower` WRITE;
+/*!40000 ALTER TABLE `garr_follower` DISABLE KEYS */;
+/*!40000 ALTER TABLE `garr_follower` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `garr_follower_locale`
+--
+
+DROP TABLE IF EXISTS `garr_follower_locale`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `garr_follower_locale` (
+ `ID` int(10) unsigned NOT NULL DEFAULT '0',
+ `locale` varchar(4) NOT NULL,
+ `HordeSourceText_lang` text,
+ `AllianceSourceText_lang` text,
+ `VerifiedBuild` smallint(6) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`ID`,`locale`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `garr_follower_locale`
+--
+
+LOCK TABLES `garr_follower_locale` WRITE;
+/*!40000 ALTER TABLE `garr_follower_locale` DISABLE KEYS */;
+/*!40000 ALTER TABLE `garr_follower_locale` ENABLE KEYS */;
+UNLOCK TABLES;
+
+--
+-- Table structure for table `garr_follower_x_ability`
+--
+
+DROP TABLE IF EXISTS `garr_follower_x_ability`;
+/*!40101 SET @saved_cs_client = @@character_set_client */;
+/*!40101 SET character_set_client = utf8 */;
+CREATE TABLE `garr_follower_x_ability` (
+ `ID` int(10) unsigned NOT NULL DEFAULT '0',
+ `GarrFollowerID` int(10) unsigned NOT NULL DEFAULT '0',
+ `GarrAbilityID` int(10) unsigned NOT NULL DEFAULT '0',
+ `FactionIndex` int(10) unsigned NOT NULL DEFAULT '0',
+ `VerifiedBuild` smallint(6) NOT NULL DEFAULT '0',
+ PRIMARY KEY (`ID`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+/*!40101 SET character_set_client = @saved_cs_client */;
+
+--
+-- Dumping data for table `garr_follower_x_ability`
+--
+
+LOCK TABLES `garr_follower_x_ability` WRITE;
+/*!40000 ALTER TABLE `garr_follower_x_ability` DISABLE KEYS */;
+/*!40000 ALTER TABLE `garr_follower_x_ability` ENABLE KEYS */;
+UNLOCK TABLES;
+
diff --git a/src/server/game/DataStores/DB2Stores.cpp b/src/server/game/DataStores/DB2Stores.cpp
index 35978810001..01c65315d35 100644
--- a/src/server/game/DataStores/DB2Stores.cpp
+++ b/src/server/game/DataStores/DB2Stores.cpp
@@ -29,8 +29,12 @@ DB2Storage<BroadcastTextEntry> sBroadcastTextStore("BroadcastText.d
DB2Storage<CurrencyTypesEntry> sCurrencyTypesStore("CurrencyTypes.db2", CurrencyTypesFormat, HOTFIX_SEL_CURRENCY_TYPES);
DB2Storage<CurvePointEntry> sCurvePointStore("CurvePoint.db2", CurvePointFormat, HOTFIX_SEL_CURVE_POINT);
DB2Storage<GameObjectsEntry> sGameObjectsStore("GameObjects.db2", GameObjectsFormat, HOTFIX_SEL_GAMEOBJECTS);
+DB2Storage<GarrAbilityEntry> sGarrAbilityStore("GarrAbility.db2", GarrAbilityFormat, HOTFIX_SEL_GARR_ABILITY);
DB2Storage<GarrBuildingEntry> sGarrBuildingStore("GarrBuilding.db2", GarrBuildingFormat, HOTFIX_SEL_GARR_BUILDING);
DB2Storage<GarrBuildingPlotInstEntry> sGarrBuildingPlotInstStore("GarrBuildingPlotInst.db2", GarrBuildingPlotInstFormat, HOTFIX_SEL_GARR_BUILDING_PLOT_INST);
+DB2Storage<GarrClassSpecEntry> sGarrClassSpecStore("GarrClassSpec.db2", GarrClassSpecFormat, HOTFIX_SEL_GARR_CLASS_SPEC);
+DB2Storage<GarrFollowerEntry> sGarrFollowerStore("GarrFollower.db2", GarrFollowerFormat, HOTFIX_SEL_GARR_FOLLOWER);
+DB2Storage<GarrFollowerXAbilityEntry> sGarrFollowerXAbilityStore("GarrFollowerXAbility.db2", GarrFollowerXAbilityFormat, HOTFIX_SEL_GARR_FOLLOWER_X_ABILITY);
DB2Storage<GarrPlotBuildingEntry> sGarrPlotBuildingStore("GarrPlotBuilding.db2", GarrPlotBuildingFormat, HOTFIX_SEL_GARR_PLOT_BUILDING);
DB2Storage<GarrPlotEntry> sGarrPlotStore("GarrPlot.db2", GarrPlotFormat, HOTFIX_SEL_GARR_PLOT);
DB2Storage<GarrPlotInstanceEntry> sGarrPlotInstanceStore("GarrPlotInstance.db2", GarrPlotInstanceFormat, HOTFIX_SEL_GARR_PLOT_INSTANCE);
@@ -141,8 +145,12 @@ void DB2Manager::LoadStores(std::string const& dataPath)
LOAD_DB2(sCurrencyTypesStore);
LOAD_DB2(sCurvePointStore);
LOAD_DB2(sGameObjectsStore);
- LOAD_DB2(sGarrBuildingPlotInstStore);
+ LOAD_DB2(sGarrAbilityStore);
LOAD_DB2(sGarrBuildingStore);
+ LOAD_DB2(sGarrBuildingPlotInstStore);
+ LOAD_DB2(sGarrClassSpecStore);
+ LOAD_DB2(sGarrFollowerStore);
+ LOAD_DB2(sGarrFollowerXAbilityStore);
LOAD_DB2(sGarrPlotBuildingStore);
LOAD_DB2(sGarrPlotInstanceStore);
LOAD_DB2(sGarrPlotStore);
diff --git a/src/server/game/DataStores/DB2Stores.h b/src/server/game/DataStores/DB2Stores.h
index 720cd01531c..a79b3d1c1fa 100644
--- a/src/server/game/DataStores/DB2Stores.h
+++ b/src/server/game/DataStores/DB2Stores.h
@@ -25,8 +25,12 @@
extern DB2Storage<BroadcastTextEntry> sBroadcastTextStore;
extern DB2Storage<CurrencyTypesEntry> sCurrencyTypesStore;
extern DB2Storage<GameObjectsEntry> sGameObjectsStore;
+extern DB2Storage<GarrAbilityEntry> sGarrAbilityStore;
extern DB2Storage<GarrBuildingEntry> sGarrBuildingStore;
extern DB2Storage<GarrBuildingPlotInstEntry> sGarrBuildingPlotInstStore;
+extern DB2Storage<GarrClassSpecEntry> sGarrClassSpecStore;
+extern DB2Storage<GarrFollowerEntry> sGarrFollowerStore;
+extern DB2Storage<GarrFollowerXAbilityEntry> sGarrFollowerXAbilityStore;
extern DB2Storage<GarrPlotBuildingEntry> sGarrPlotBuildingStore;
extern DB2Storage<GarrPlotEntry> sGarrPlotStore;
extern DB2Storage<GarrPlotInstanceEntry> sGarrPlotInstanceStore;
diff --git a/src/server/game/DataStores/DB2Structure.h b/src/server/game/DataStores/DB2Structure.h
index cad9ca32e69..80c79a94b96 100644
--- a/src/server/game/DataStores/DB2Structure.h
+++ b/src/server/game/DataStores/DB2Structure.h
@@ -100,6 +100,17 @@ struct GameObjectsEntry
LocalizedString* Name; // 23
};
+struct GarrAbilityEntry
+{
+ uint32 ID; // 0
+ uint32 Flags; // 1
+ LocalizedString* Name; // 2
+ LocalizedString* Description; // 3
+ uint32 IconFileDataID; // 4
+ uint32 OtherFactionGarrAbilityID; // 5
+ uint32 GarrAbilityCategoryID; // 6
+};
+
struct GarrBuildingEntry
{
uint32 ID; // 0
@@ -137,6 +148,49 @@ struct GarrBuildingPlotInstEntry
DBCPosition2D LandmarkOffset; // 4-5
};
+struct GarrClassSpecEntry
+{
+ uint32 ID; // 0
+ LocalizedString* NameMale; // 1
+ LocalizedString* NameFemale; // 2
+ LocalizedString* NameGenderless; // 3
+ uint32 ClassAtlasID; // 4 UiTextureAtlasMember.db2 ref
+ uint32 GarrFollItemSetID; // 5
+};
+
+struct GarrFollowerEntry
+{
+ uint32 ID; // 0
+ uint32 HordeCreatureID; // 1
+ uint32 AllianceCreatureID; // 2
+ uint32 HordeUiAnimRaceInfoID; // 3
+ uint32 AllianceUiAnimRaceInfoID; // 4
+ uint32 Quality; // 5
+ uint32 HordeGarrClassSpecID; // 6
+ uint32 AllianceGarrClassSpecID; // 7
+ uint32 HordeGarrFollItemSetID; // 8
+ uint32 AllianceGarrFollItemSetID; // 9
+ uint32 Level; // 10
+ uint32 ItemLevelWeapon; // 11
+ uint32 ItemLevelArmor; // 12
+ uint32 Unknown1; // 13
+ uint32 Flags; // 14
+ LocalizedString* HordeSourceText; // 15
+ LocalizedString* AllianceSourceText; // 16
+ int32 Unknown2; // 17
+ int32 Unknown3; // 18
+ uint32 HordePortraitIconID; // 19
+ uint32 AlliancePortraitIconID; // 20
+};
+
+struct GarrFollowerXAbilityEntry
+{
+ uint32 ID; // 0
+ uint32 GarrFollowerID; // 1
+ uint32 GarrAbilityID; // 2
+ uint32 FactionIndex; // 3
+};
+
struct GarrPlotEntry
{
uint32 ID; // 0
diff --git a/src/server/game/DataStores/DB2fmt.h b/src/server/game/DataStores/DB2fmt.h
index 19622dcc3d2..7c915b37326 100644
--- a/src/server/game/DataStores/DB2fmt.h
+++ b/src/server/game/DataStores/DB2fmt.h
@@ -24,10 +24,14 @@ char const BroadcastTextFormat[] = "nissiiiiiiiii";
char const CurrencyTypesFormat[] = "nisssiiiiiis";
char const CurvePointFormat[] = "niiff";
char const GameObjectsFormat[] = "niiffffffffiiiiiiiiiiiis";
+char const GarrAbilityFormat[] = "nissiii";
char const GarrBuildingFormat[] = "niiiiissssiiiiiiiiiiiiii";
-char const GarrPlotFormat[] = "niiisiiii";
-char const GarrPlotBuildingFormat[] = "nii";
char const GarrBuildingPlotInstFormat[] = "niiiff";
+char const GarrClassSpecFormat[] = "nsssii";
+char const GarrFollowerFormat[] = "niiiiiiiiiiiiiissiiii";
+char const GarrFollowerXAbilityFormat[] = "niii";
+char const GarrPlotBuildingFormat[] = "nii";
+char const GarrPlotFormat[] = "niiisiiii";
char const GarrPlotInstanceFormat[] = "nis";
char const GarrSiteLevelFormat[] = "niiiiffiiii";
char const GarrSiteLevelPlotInstFormat[] = "niiffi";
diff --git a/src/server/game/Garrison/Garrison.cpp b/src/server/game/Garrison/Garrison.cpp
index 35c07e1590c..3087de37472 100644
--- a/src/server/game/Garrison/Garrison.cpp
+++ b/src/server/game/Garrison/Garrison.cpp
@@ -344,6 +344,34 @@ void Garrison::CancelBuildingConstruction(uint32 garrPlotInstanceId)
_owner->SendDirectMessage(buildingRemoved.Write());
}
+void Garrison::AddFollower(uint32 garrFollowerId)
+{
+ WorldPackets::Garrison::GarrisonAddFollowerResult addFollowerResult;
+ GarrFollowerEntry const* followerEntry = sGarrFollowerStore.LookupEntry(garrFollowerId);
+ if (_followers.count(garrFollowerId) || !followerEntry)
+ {
+ addFollowerResult.Result = GARRISON_GENERIC_UNKNOWN_ERROR;
+ _owner->SendDirectMessage(addFollowerResult.Write());
+ return;
+ }
+
+ Follower& follower = _followers[garrFollowerId];
+ follower.PacketInfo.DbID = sGarrisonMgr.GenerateFollowerDbId();
+ follower.PacketInfo.GarrFollowerID = garrFollowerId;
+ follower.PacketInfo.Quality = followerEntry->Quality; // TODO: handle magic upgrades
+ follower.PacketInfo.FollowerLevel = followerEntry->Level;
+ follower.PacketInfo.ItemLevelWeapon = followerEntry->ItemLevelWeapon;
+ follower.PacketInfo.ItemLevelArmor = followerEntry->ItemLevelArmor;
+ follower.PacketInfo.Xp = 0;
+ follower.PacketInfo.CurrentBuildingID = 0;
+ follower.PacketInfo.CurrentMissionID = 0;
+ follower.PacketInfo.AbilityID = sGarrisonMgr.RollFollowerAbilities(followerEntry, follower.PacketInfo.Quality, GetFaction(), true);
+ follower.PacketInfo.FollowerStatus = 0;
+
+ addFollowerResult.Follower = follower.PacketInfo;
+ _owner->SendDirectMessage(addFollowerResult.Write());
+}
+
void Garrison::SendInfo()
{
WorldPackets::Garrison::GetGarrisonInfoResult garrisonInfo;
@@ -359,6 +387,9 @@ void Garrison::SendInfo()
garrisonInfo.Buildings.push_back(plot.BuildingInfo.PacketInfo.get_ptr());
}
+ for (auto const& p : _followers)
+ garrisonInfo.Followers.push_back(&p.second.PacketInfo);
+
_owner->SendDirectMessage(garrisonInfo.Write());
}
diff --git a/src/server/game/Garrison/Garrison.h b/src/server/game/Garrison/Garrison.h
index 1b12ac62263..348998b276c 100644
--- a/src/server/game/Garrison/Garrison.h
+++ b/src/server/game/Garrison/Garrison.h
@@ -32,6 +32,20 @@ enum GarrisonBuildingFlags
GARRISON_BUILDING_FLAG_NEEDS_PLAN = 0x1
};
+enum GarrisonFollowerFlags
+{
+ GARRISON_FOLLOWER_FLAG_UNIQUE = 0x1
+};
+
+enum GarrisonAbilityFlags
+{
+ GARRISON_ABILITY_FLAG_TRAIT = 0x01,
+ GARRISON_ABILITY_CANNOT_ROLL = 0x02,
+ GARRISON_ABILITY_HORDE_ONLY = 0x04,
+ GARRISON_ABILITY_ALLIANCE_ONLY = 0x08,
+ GARRISON_ABILITY_FLAG_CANNOT_REMOVE = 0x10
+};
+
enum GarrisonError
{
GARRISON_SUCCESS = 0,
@@ -44,7 +58,9 @@ enum GarrisonError
GARRISON_ERROR_BLUEPRINT_NOT_KNOWN = 22,
GARRISON_ERROR_BUILDING_EXISTS = 24,
GARRISON_ERROR_NOT_ENOUGH_CURRENCY = 46,
- GARRISON_ERROR_NOT_ENOUGH_GOLD = 47
+ GARRISON_ERROR_NOT_ENOUGH_GOLD = 47,
+
+ GARRISON_GENERIC_UNKNOWN_ERROR = 255 // custom value for packets whose handlers only check if error != 0
};
enum GarrisonFollowerStatus
@@ -81,6 +97,11 @@ public:
Building BuildingInfo;
};
+ struct Follower
+ {
+ WorldPackets::Garrison::GarrisonFollower PacketInfo;
+ };
+
explicit Garrison(Player* owner);
bool LoadFromDB(PreparedQueryResult garrison, PreparedQueryResult blueprints, PreparedQueryResult buildings);
@@ -93,15 +114,21 @@ public:
void Leave() const;
GarrisonFactionIndex GetFaction() const;
+
+ // Plots
std::vector<Plot*> GetPlots();
Plot* GetPlot(uint32 garrPlotInstanceId);
Plot const* GetPlot(uint32 garrPlotInstanceId) const;
+ // Buildings
void LearnBlueprint(uint32 garrBuildingId);
void UnlearnBlueprint(uint32 garrBuildingId);
void PlaceBuilding(uint32 garrPlotInstanceId, uint32 garrBuildingId);
void CancelBuildingConstruction(uint32 garrPlotInstanceId);
+ // Followers
+ void AddFollower(uint32 garrFollowerId);
+
void SendInfo();
void SendRemoteInfo() const;
void SendBlueprintAndSpecializationData();
@@ -120,6 +147,7 @@ private:
std::unordered_map<uint32 /*garrPlotInstanceId*/, Plot> _plots;
std::unordered_set<uint32 /*garrBuildingId*/> _knownBuildings;
+ std::unordered_map<uint64 /*dbId*/, Follower> _followers;
};
#endif // Garrison_h__
diff --git a/src/server/game/Garrison/GarrisonMgr.cpp b/src/server/game/Garrison/GarrisonMgr.cpp
index f81baa6ddf1..bb2d44d7141 100644
--- a/src/server/game/Garrison/GarrisonMgr.cpp
+++ b/src/server/game/Garrison/GarrisonMgr.cpp
@@ -16,7 +16,10 @@
*/
#include "GarrisonMgr.h"
+#include "Containers.h"
+#include "Garrison.h"
#include "ObjectDefines.h"
+#include "World.h"
void GarrisonMgr::Initialize()
{
@@ -35,6 +38,23 @@ void GarrisonMgr::Initialize()
for (GarrBuildingEntry const* building : sGarrBuildingStore)
_garrisonBuildingsByType[building->Type].push_back(building);
+
+ for (GarrFollowerXAbilityEntry const* followerAbility : sGarrFollowerXAbilityStore)
+ {
+ if (GarrAbilityEntry const* ability = sGarrAbilityStore.LookupEntry(followerAbility->GarrAbilityID))
+ {
+ if (!(ability->Flags & GARRISON_ABILITY_CANNOT_ROLL) && ability->Flags & GARRISON_ABILITY_FLAG_TRAIT)
+ _garrisonFollowerRandomTraits.insert(ability);
+
+ if (followerAbility->FactionIndex < 2)
+ {
+ if (ability->Flags & GARRISON_ABILITY_FLAG_TRAIT)
+ _garrisonFollowerAbilities[followerAbility->FactionIndex][followerAbility->GarrFollowerID].Traits.insert(ability);
+ else
+ _garrisonFollowerAbilities[followerAbility->FactionIndex][followerAbility->GarrFollowerID].Counters.insert(ability);
+ }
+ }
+ }
}
GarrSiteLevelEntry const* GarrisonMgr::GetGarrSiteLevelEntry(uint32 garrSiteId, uint32 level) const
@@ -96,3 +116,116 @@ GarrBuildingEntry const* GarrisonMgr::GetPreviousLevelBuilding(uint32 buildingTy
return nullptr;
}
+
+uint64 GarrisonMgr::GenerateFollowerDbId()
+{
+ if (_followerDbIdGenerator >= std::numeric_limits<uint64>::max())
+ {
+ TC_LOG_ERROR("misc", "Garrison follower db id oberflow! Can't continue, shutting down server. ");
+ World::StopNow(ERROR_EXIT_CODE);
+ }
+
+ return _followerDbIdGenerator++;
+}
+
+uint32 const AbilitiesForQuality[][2] =
+{
+ // Counters, Traits
+ { 0, 0 },
+ { 1, 0 },
+ { 1, 1 }, // Uncommon
+ { 1, 2 }, // Rare
+ { 2, 3 }, // Epic
+ { 2, 3 } // Legendary
+};
+
+std::list<uint32> GarrisonMgr::RollFollowerAbilities(GarrFollowerEntry const* follower, uint32 quality, uint32 faction, bool initial) const
+{
+ ASSERT(faction < 2);
+
+ std::list<uint32> result;
+ int32 slots[2] = { AbilitiesForQuality[quality][0], AbilitiesForQuality[quality][1] };
+
+ GarrAbilities const* abilities = nullptr;
+ auto itr = _garrisonFollowerAbilities[faction].find(follower->ID);
+ if (itr != _garrisonFollowerAbilities[faction].end())
+ abilities = &itr->second;
+
+ std::list<uint32> abilityList, forcedAbilities, traitList, forcedTraits;
+ if (abilities)
+ {
+ for (GarrAbilityEntry const* ability : abilities->Counters)
+ {
+ if (ability->Flags & GARRISON_ABILITY_HORDE_ONLY && faction != GARRISON_FACTION_INDEX_HORDE)
+ continue;
+ else if (ability->Flags & GARRISON_ABILITY_ALLIANCE_ONLY && faction != GARRISON_FACTION_INDEX_ALLIANCE)
+ continue;
+
+ if (ability->Flags & GARRISON_ABILITY_FLAG_CANNOT_REMOVE)
+ forcedAbilities.push_back(ability->ID);
+ else
+ abilityList.push_back(ability->ID);
+ }
+
+ for (GarrAbilityEntry const* ability : abilities->Traits)
+ {
+ if (ability->Flags & GARRISON_ABILITY_HORDE_ONLY && faction != GARRISON_FACTION_INDEX_HORDE)
+ continue;
+ else if (ability->Flags & GARRISON_ABILITY_ALLIANCE_ONLY && faction != GARRISON_FACTION_INDEX_ALLIANCE)
+ continue;
+
+ if (ability->Flags & GARRISON_ABILITY_FLAG_CANNOT_REMOVE)
+ forcedTraits.push_back(ability->ID);
+ else
+ traitList.push_back(ability->ID);
+ }
+ }
+
+ Trinity::Containers::RandomResizeList(abilityList, std::max<int32>(0, slots[0] - forcedAbilities.size()));
+ Trinity::Containers::RandomResizeList(traitList, std::max<int32>(0, slots[1] - forcedTraits.size()));
+
+ // Add counters specified in GarrFollowerXAbility.db2 before generic classspec ones on follower creation
+ if (initial)
+ {
+ forcedAbilities.splice(forcedAbilities.end(), abilityList);
+ forcedTraits.splice(forcedTraits.end(), traitList);
+ }
+
+ if (slots[0] > forcedAbilities.size() + abilityList.size())
+ {
+ std::list<uint32> classSpecAbilities; // = GetDefaultClassSpecAbilities(follower, faction)
+
+ abilityList.splice(abilityList.end(), classSpecAbilities);
+ abilityList.sort();
+ abilityList.unique();
+
+ Trinity::Containers::RandomResizeList(abilityList, std::max<int32>(0, slots[0] - forcedAbilities.size()));
+ }
+
+ if (slots[1] > forcedTraits.size() + traitList.size())
+ {
+ std::list<uint32> genericTraits;
+ for (GarrAbilityEntry const* ability : _garrisonFollowerRandomTraits)
+ {
+ if (ability->Flags & GARRISON_ABILITY_HORDE_ONLY && faction != GARRISON_FACTION_INDEX_HORDE)
+ continue;
+ else if (ability->Flags & GARRISON_ABILITY_ALLIANCE_ONLY && faction != GARRISON_FACTION_INDEX_ALLIANCE)
+ continue;
+
+ genericTraits.push_back(ability->ID);
+ }
+
+ traitList.splice(traitList.end(), genericTraits);
+ traitList.sort();
+ traitList.unique();
+
+ Trinity::Containers::RandomResizeList(traitList, std::max<int32>(0, slots[1] - forcedTraits.size()));
+ }
+
+ result.splice(result.end(), forcedAbilities);
+ result.splice(result.end(), abilityList);
+ result.splice(result.end(), forcedTraits);
+ result.splice(result.end(), traitList);
+
+ return result;
+}
diff --git a/src/server/game/Garrison/GarrisonMgr.h b/src/server/game/Garrison/GarrisonMgr.h
index 89fd90f25c3..f037c0867a0 100644
--- a/src/server/game/Garrison/GarrisonMgr.h
+++ b/src/server/game/Garrison/GarrisonMgr.h
@@ -18,9 +18,14 @@
#ifndef GarrisonMgr_h__
#define GarrisonMgr_h__
+#include "DB2Stores.h"
#include <unordered_set>
-#include "DB2Stores.h"
+struct GarrAbilities
+{
+ std::unordered_set<GarrAbilityEntry const*> Counters;
+ std::unordered_set<GarrAbilityEntry const*> Traits;
+};
class GarrisonMgr
{
@@ -39,6 +44,8 @@ public:
bool IsPlotMatchingBuilding(uint32 garrPlotId, uint32 garrBuildingId) const;
uint32 GetGarrBuildingPlotInst(uint32 garrBuildingId, uint32 garrSiteLevelPlotInstId) const;
GarrBuildingEntry const* GetPreviousLevelBuilding(uint32 buildingType, uint32 currentLevel) const;
+ uint64 GenerateFollowerDbId();
+ std::list<uint32> RollFollowerAbilities(GarrFollowerEntry const* follower, uint32 quality, uint32 faction, bool initial) const;
private:
std::unordered_map<uint32 /*garrSiteId*/, std::vector<GarrSiteLevelPlotInstEntry const*>> _garrisonPlotInstBySiteLevel;
@@ -46,6 +53,10 @@ private:
std::unordered_map<uint32 /*garrPlotId*/, std::unordered_set<uint32/*garrBuildingId*/>> _garrisonBuildingsByPlot;
std::unordered_map<uint64 /*garrBuildingId | garrSiteLevelPlotInstId << 32*/, uint32 /*garrBuildingPlotInstId*/> _garrisonBuildingPlotInstances;
std::unordered_map<uint32 /*buildingType*/, std::vector<GarrBuildingEntry const*>> _garrisonBuildingsByType;
+ std::unordered_map<uint32 /*garrFollowerId*/, GarrAbilities> _garrisonFollowerAbilities[2];
+ std::unordered_set<GarrAbilityEntry const*> _garrisonFollowerRandomTraits;
+
+ uint64 _followerDbIdGenerator;
};
#define sGarrisonMgr GarrisonMgr::Instance()
diff --git a/src/server/game/Server/Packets/GarrisonPackets.cpp b/src/server/game/Server/Packets/GarrisonPackets.cpp
index 8dff6ff86d4..456149bbdbb 100644
--- a/src/server/game/Server/Packets/GarrisonPackets.cpp
+++ b/src/server/game/Server/Packets/GarrisonPackets.cpp
@@ -60,8 +60,8 @@ ByteBuffer& operator<<(ByteBuffer& data, WorldPackets::Garrison::GarrisonFollowe
data << uint32(follower.CurrentMissionID);
data << uint32(follower.AbilityID.size());
data << uint32(follower.FollowerStatus);
- if (!follower.AbilityID.empty())
- data.append(follower.AbilityID.data(), follower.AbilityID.size());
+ for (uint32 abilityId : follower.AbilityID)
+ data << uint32(abilityId);
return data;
}
@@ -237,3 +237,11 @@ WorldPacket const* WorldPackets::Garrison::GarrisonPlotRemoved::Write()
return &_worldPacket;
}
+
+WorldPacket const* WorldPackets::Garrison::GarrisonAddFollowerResult::Write()
+{
+ _worldPacket << uint32(Result);
+ _worldPacket << Follower;
+
+ return &_worldPacket;
+}
diff --git a/src/server/game/Server/Packets/GarrisonPackets.h b/src/server/game/Server/Packets/GarrisonPackets.h
index 288074284b5..5fc386ab72b 100644
--- a/src/server/game/Server/Packets/GarrisonPackets.h
+++ b/src/server/game/Server/Packets/GarrisonPackets.h
@@ -74,7 +74,7 @@ namespace WorldPackets
uint32 Xp = 0;
uint32 CurrentBuildingID = 0;
uint32 CurrentMissionID = 0;
- std::vector<uint32> AbilityID;
+ std::list<uint32> AbilityID;
uint32 FollowerStatus;
};
@@ -102,9 +102,9 @@ namespace WorldPackets
uint32 FactionIndex = 0;
uint32 NumFollowerActivationsRemaining = 0;
std::vector<GarrisonPlotInfo*> Plots;
- std::vector<GarrisonBuildingInfo*> Buildings;
- std::vector<GarrisonFollower*> Followers;
- std::vector<GarrisonMission*> Missions;
+ std::vector<GarrisonBuildingInfo const*> Buildings;
+ std::vector<GarrisonFollower const*> Followers;
+ std::vector<GarrisonMission const*> Missions;
std::vector<int32> ArchivedMissions;
};
@@ -267,6 +267,17 @@ namespace WorldPackets
uint32 GarrPlotInstanceID = 0;
};
+
+ class GarrisonAddFollowerResult final : public ServerPacket
+ {
+ public:
+ GarrisonAddFollowerResult() : ServerPacket(SMSG_GARRISON_ADD_FOLLOWER_RESULT, 8 + 4 + 4 + 4 + 4 + 4 + 4 + 4 + 4 + 4 + 5 * 4 + 4) { }
+
+ WorldPacket const* Write() override;
+
+ GarrisonFollower Follower;
+ uint32 Result = 0;
+ };
}
}
diff --git a/src/server/game/Server/Protocol/Opcodes.cpp b/src/server/game/Server/Protocol/Opcodes.cpp
index 07632e48b40..8c559fe80c7 100644
--- a/src/server/game/Server/Protocol/Opcodes.cpp
+++ b/src/server/game/Server/Protocol/Opcodes.cpp
@@ -1109,7 +1109,7 @@ void OpcodeTable::Initialize()
DEFINE_SERVER_OPCODE_HANDLER(SMSG_GAME_SPEED_SET, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_GAME_TIME_SET, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_GAME_TIME_UPDATE, STATUS_UNHANDLED, CONNECTION_TYPE_REALM);
- DEFINE_SERVER_OPCODE_HANDLER(SMSG_GARRISON_ADD_FOLLOWER_RESULT, STATUS_UNHANDLED, CONNECTION_TYPE_INSTANCE);
+ DEFINE_SERVER_OPCODE_HANDLER(SMSG_GARRISON_ADD_FOLLOWER_RESULT, STATUS_NEVER, CONNECTION_TYPE_INSTANCE);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_GARRISON_ADD_MISSION_RESULT, STATUS_UNHANDLED, CONNECTION_TYPE_INSTANCE);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_GARRISON_ASSIGN_FOLLOWER_TO_BUILDING_RESULT, STATUS_UNHANDLED, CONNECTION_TYPE_INSTANCE);
DEFINE_SERVER_OPCODE_HANDLER(SMSG_GARRISON_BUILDING_ACTIVATED, STATUS_UNHANDLED, CONNECTION_TYPE_INSTANCE);
diff --git a/src/server/game/Spells/Spell.h b/src/server/game/Spells/Spell.h
index 7af7644d924..3ca8550b3ce 100644
--- a/src/server/game/Spells/Spell.h
+++ b/src/server/game/Spells/Spell.h
@@ -407,6 +407,7 @@ class Spell
void EffectDestroyItem(SpellEffIndex effIndex);
void EffectLearnGarrisonBuilding(SpellEffIndex effIndex);
void EffectCreateGarrison(SpellEffIndex effIndex);
+ void EffectAddGarrisonFollower(SpellEffIndex effIndex);
typedef std::set<Aura*> UsedSpellMods;
diff --git a/src/server/game/Spells/SpellEffects.cpp b/src/server/game/Spells/SpellEffects.cpp
index 40a8c853bb0..9e4f0608a38 100644
--- a/src/server/game/Spells/SpellEffects.cpp
+++ b/src/server/game/Spells/SpellEffects.cpp
@@ -294,7 +294,7 @@ pEffect SpellEffects[TOTAL_SPELL_EFFECTS]=
&Spell::EffectNULL, //217 SPELL_EFFECT_UPGRADE_GARRISON
&Spell::EffectNULL, //218 SPELL_EFFECT_218
&Spell::EffectNULL, //219 SPELL_EFFECT_219
- &Spell::EffectNULL, //220 SPELL_EFFECT_ADD_GARRISON_FOLLOWER
+ &Spell::EffectAddGarrisonFollower, //220 SPELL_EFFECT_ADD_GARRISON_FOLLOWER
&Spell::EffectNULL, //221 SPELL_EFFECT_221
&Spell::EffectNULL, //222 SPELL_EFFECT_CREATE_HEIRLOOM_ITEM
&Spell::EffectNULL, //223 SPELL_EFFECT_CHANGE_ITEM_BONUSES
@@ -5826,3 +5826,15 @@ void Spell::EffectCreateGarrison(SpellEffIndex effIndex)
unitTarget->ToPlayer()->CreateGarrison(GetEffect(effIndex)->MiscValue);
}
+
+void Spell::EffectAddGarrisonFollower(SpellEffIndex effIndex)
+{
+ if (effectHandleMode != SPELL_EFFECT_HANDLE_HIT_TARGET)
+ return;
+
+ if (!unitTarget || unitTarget->GetTypeId() != TYPEID_PLAYER)
+ return;
+
+ if (Garrison* garrison = unitTarget->ToPlayer()->GetGarrison())
+ garrison->AddFollower(GetEffect(effIndex)->MiscValue);
+}
diff --git a/src/server/shared/Database/Implementation/HotfixDatabase.cpp b/src/server/shared/Database/Implementation/HotfixDatabase.cpp
index 1afb118a06e..a7043b1d5ff 100644
--- a/src/server/shared/Database/Implementation/HotfixDatabase.cpp
+++ b/src/server/shared/Database/Implementation/HotfixDatabase.cpp
@@ -52,6 +52,11 @@ void HotfixDatabaseConnection::DoPrepareStatements()
"PhaseUseFlags, PhaseID, PhaseGroupID, Type, Data0, Data1, Data2, Data3, Data4, Data5, Data6, Data7, Name FROM gameobjects ORDER BY ID DESC", CONNECTION_SYNCH);
PREPARE_LOCALE_STMT(HOTFIX_SEL_GAMEOBJECTS, "SELECT ID, Name_lang FROM gameobjects_locale WHERE locale = ?", CONNECTION_SYNCH);
+ // GarrAbility.db2
+ PrepareStatement(HOTFIX_SEL_GARR_ABILITY, "SELECT ID, Flags, Name, Description, IconFileDataID, OtherFactionGarrAbilityID, "
+ "GarrAbilityCategoryID FROM garr_ability ORDER BY ID DESC", CONNECTION_SYNCH);
+ PREPARE_LOCALE_STMT(HOTFIX_SEL_GARR_ABILITY, "SELECT ID, Name_lang, Description_lang FROM garr_ability_locale WHERE locale = ?", CONNECTION_SYNCH);
+
// GarrBuilding.db2
PrepareStatement(HOTFIX_SEL_GARR_BUILDING, "SELECT ID, HordeGameObjectID, AllianceGameObjectID, Unknown, Type, Level, NameAlliance, NameHorde, Description, "
"Tooltip, BuildDuration, CostCurrencyID, CostCurrencyAmount, HordeTexPrefixKitID, AllianceTexPrefixKitID, IconFileDataID, BonusAmount, Flags, "
@@ -64,6 +69,20 @@ void HotfixDatabaseConnection::DoPrepareStatements()
PrepareStatement(HOTFIX_SEL_GARR_BUILDING_PLOT_INST, "SELECT ID, GarrBuildingID, UiTextureAtlasMemberID, GarrSiteLevelPlotInstID, "
"LandmarkOffsetX, LandmarkOffsetY FROM garr_building_plot_inst ORDER BY ID DESC", CONNECTION_SYNCH);
+ // GarrClassSpec.db2
+ PrepareStatement(HOTFIX_SEL_GARR_CLASS_SPEC, "SELECT ID, NameMale, NameFemale, NameGenderless, ClassAtlasID, GarrFollItemSetID "
+ "FROM garr_class_spec ORDER BY ID DESC", CONNECTION_SYNCH);
+ PREPARE_LOCALE_STMT(HOTFIX_SEL_GARR_CLASS_SPEC, "SELECT ID, NameMale_lang, NameFemale_lang, NameGenderless_lang FROM garr_class_spec_locale WHERE locale = ?", CONNECTION_SYNCH);
+
+ // GarrFollower.db2
+ PrepareStatement(HOTFIX_SEL_GARR_FOLLOWER, "SELECT ID, HordeCreatureID, AllianceCreatureID, HordeUiAnimRaceInfoID, AllianceUiAnimRaceInfoID, Quality, "
+ "HordeGarrClassSpecID, AllianceGarrClassSpecID, HordeGarrFollItemSetID, AllianceGarrFollItemSetID, Level, ItemLevelWeapon, ItemLevelArmor, Unknown1, Flags, "
+ "HordeSourceText, AllianceSourceText, Unknown2, Unknown3, HordePortraitIconID, AlliancePortraitIconID FROM garr_follower ORDER BY ID DESC", CONNECTION_SYNCH);
+ PREPARE_LOCALE_STMT(HOTFIX_SEL_GARR_FOLLOWER, "SELECT ID, HordeSourceText_lang, AllianceSourceText_lang FROM garr_follower_locale WHERE locale = ?", CONNECTION_SYNCH);
+
+ // GarrFollowerXAbility.db2
+ PrepareStatement(HOTFIX_SEL_GARR_FOLLOWER_X_ABILITY, "SELECT ID, GarrFollowerID, GarrAbilityID, FactionIndex FROM garr_follower_x_ability ORDER BY ID DESC", CONNECTION_SYNCH);
+
// GarrPlot.db2
PrepareStatement(HOTFIX_SEL_GARR_PLOT, "SELECT ID, GarrPlotUICategoryID, PlotType, Flags, Name, MinCount, MaxCount, "
"AllianceConstructionGameObjectID, HordeConstructionGameObjectID FROM garr_plot ORDER BY ID DESC", CONNECTION_SYNCH);
diff --git a/src/server/shared/Database/Implementation/HotfixDatabase.h b/src/server/shared/Database/Implementation/HotfixDatabase.h
index 5d5f00dbf9e..4adf555dc2f 100644
--- a/src/server/shared/Database/Implementation/HotfixDatabase.h
+++ b/src/server/shared/Database/Implementation/HotfixDatabase.h
@@ -57,11 +57,22 @@ enum HotfixDatabaseStatements
HOTFIX_SEL_GAMEOBJECTS,
HOTFIX_SEL_GAMEOBJECTS_LOCALE,
+ HOTFIX_SEL_GARR_ABILITY,
+ HOTFIX_SEL_GARR_ABILITY_LOCALE,
+
HOTFIX_SEL_GARR_BUILDING,
HOTFIX_SEL_GARR_BUILDING_LOCALE,
HOTFIX_SEL_GARR_BUILDING_PLOT_INST,
+ HOTFIX_SEL_GARR_CLASS_SPEC,
+ HOTFIX_SEL_GARR_CLASS_SPEC_LOCALE,
+
+ HOTFIX_SEL_GARR_FOLLOWER,
+ HOTFIX_SEL_GARR_FOLLOWER_LOCALE,
+
+ HOTFIX_SEL_GARR_FOLLOWER_X_ABILITY,
+
HOTFIX_SEL_GARR_PLOT,
HOTFIX_SEL_GARR_PLOT_LOCALE,