diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000000..939f8c9ed27 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "src/modules/Bots"] + path = src/modules/Bots + url = https://github.com/celguar/mangosbot-bots.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 561b11c6124..77ee95df05f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -327,6 +327,21 @@ if(NOT BUILD_GAME_SERVER AND BUILD_PLAYERBOT) message(STATUS "BUILD_PLAYERBOT forced to OFF due to BUILD_GAME_SERVER is not set") endif() +if(NOT BUILD_GAME_SERVER AND BUILD_IKE3_BOTS) + set(BUILD_IKE3_BOTS OFF) + message(STATUS "BUILD_PLAYERBOTS forced to OFF due to BUILD_GAME_SERVER is not set") +endif() + +if(BUILD_IKE3_BOTS) + set(BUILD_PLAYERBOT OFF) + message(STATUS "CMaNGOS bots DISABLED because Ike3 bots enabled") +endif() + +if(BUILD_PLAYERBOT) + set(BUILD_IKE3_BOTS OFF) + message(STATUS "Ike3 bots DISABLED because CMaNGOS bots enabled") +endif() + if(PCH) if(${CMAKE_VERSION} VERSION_LESS "3.16") message("PCH is not supported by your CMake version") diff --git a/cmake/options.cmake b/cmake/options.cmake index 7455747ccad..d84b6434b2e 100644 --- a/cmake/options.cmake +++ b/cmake/options.cmake @@ -7,6 +7,7 @@ option(BUILD_LOGIN_SERVER "Build login server" ON) option(BUILD_EXTRACTORS "Build map/dbc/vmap/mmap extractors" OFF) option(BUILD_SCRIPTDEV "Build ScriptDev. (OFF Speedup build)" ON) option(BUILD_PLAYERBOT "Build Playerbot mod" OFF) +option(BUILD_IKE3_BOTS "Build ike3 Playerbots" OFF) option(BUILD_AHBOT "Build Auction House Bot mod" OFF) option(BUILD_METRICS "Build Metrics, generate data for Grafana" OFF) option(BUILD_RECASTDEMOMOD "Build map/vmap/mmap viewer" OFF) @@ -34,6 +35,7 @@ message(STATUS BUILD_EXTRACTORS Build map/dbc/vmap/mmap extractor BUILD_SCRIPTDEV Build scriptdev. (Disable it to speedup build in dev mode by not including scripts) BUILD_PLAYERBOT Build Playerbot mod + BUILD_IKE3_BOTS Build Ike3 Playerbot mod BUILD_AHBOT Build Auction House Bot mod BUILD_METRICS Build Metrics, generate data for Grafana BUILD_RECASTDEMOMOD Build map/vmap/mmap viewer diff --git a/cmake/showoptions.cmake b/cmake/showoptions.cmake index 14f7dadc6af..91dab474ac1 100644 --- a/cmake/showoptions.cmake +++ b/cmake/showoptions.cmake @@ -67,6 +67,12 @@ else() message(STATUS "Build Playerbot : No (default)") endif() +if(BUILD_IKE3_BOTS) + message(STATUS "Build ike3 Playerbots : Yes") +else() + message(STATUS "Build ike3 Playerbots : No (default)") +endif() + if(BUILD_EXTRACTORS) message(STATUS "Build extractors : Yes") else() diff --git a/contrib/mmap/src/MapBuilder.cpp b/contrib/mmap/src/MapBuilder.cpp index 6f3b367e451..1c2c496a2fe 100644 --- a/contrib/mmap/src/MapBuilder.cpp +++ b/contrib/mmap/src/MapBuilder.cpp @@ -34,6 +34,36 @@ using namespace VMAP; +void rcModAlmostUnwalkableTriangles(rcContext* ctx, const float walkableSlopeAngle, + const float* verts, int /*nv*/, + const int* tris, int nt, + unsigned char* areas) +{ + rcIgnoreUnused(ctx); + + const float walkableThr = cosf(walkableSlopeAngle / 180.0f * RC_PI); + + float norm[3]; + + for (int i = 0; i < nt; ++i) + { + if (areas[i] & RC_WALKABLE_AREA) + { + const int* tri = &tris[i * 3]; + + float e0[3], e1[3]; + rcVsub(e0, &verts[tri[1] * 3], &verts[tri[0] * 3]); + rcVsub(e1, &verts[tri[2] * 3], &verts[tri[0] * 3]); + rcVcross(norm, e0, e1); + rcVnormalize(norm); + + // Check if the face is walkable. + if (norm[1] <= walkableThr) + areas[i] = NAV_AREA_GROUND_STEEP; //Slopes between 50 and 60. Walkable for mobs, unwalkable for players. + } + } +} + void from_json(const json& j, rcConfig& config) { config.tileSize = MMAP::VERTEX_PER_TILE; @@ -1147,8 +1177,13 @@ namespace MMAP } // mark all walkable tiles, both liquids and solids - rcClearUnwalkableTriangles(m_rcContext, tileCfg.walkableSlopeAngle, tVerts, tVertCount, tTris, tTriCount, tTriFlags); - rcRasterizeTriangles(m_rcContext, tVerts, tVertCount, tTris, tTriFlags, tTriCount, *tile.solid, tileCfg.walkableClimb); + + unsigned char* triFlags = new unsigned char[tTriCount]; + memset(triFlags, NAV_AREA_GROUND, tTriCount * sizeof(unsigned char)); + rcClearUnwalkableTriangles(m_rcContext, tileCfg.walkableSlopeAngle, tVerts, tVertCount, tTris, tTriCount, triFlags); + rcModAlmostUnwalkableTriangles(m_rcContext, 50.0f, tVerts, tVertCount, tTris, tTriCount, triFlags); + rcRasterizeTriangles(m_rcContext, tVerts, tVertCount, tTris, triFlags, tTriCount, *tile.solid, tileCfg.walkableClimb); + delete[] triFlags; rcFilterLowHangingWalkableObstacles(m_rcContext, tileCfg.walkableClimb, *tile.solid); rcFilterLedgeSpans(m_rcContext, tileCfg.walkableHeight, tileCfg.walkableClimb, *tile.solid); @@ -1371,7 +1406,7 @@ namespace MMAP {"minRegionArea", 30}, {"walkableClimb", 4}, {"walkableHeight", 3}, - {"walkableRadius", 2}, + {"walkableRadius", 1}, {"walkableSlopeAngle", 60.0f}, {"liquidFlagMergeThreshold", 0.0f}, }; diff --git a/contrib/recastdemomod/Include/RecastDemoSharedDefines.h b/contrib/recastdemomod/Include/RecastDemoSharedDefines.h index 6ff1e50c37e..3b6d3533708 100644 --- a/contrib/recastdemomod/Include/RecastDemoSharedDefines.h +++ b/contrib/recastdemomod/Include/RecastDemoSharedDefines.h @@ -17,15 +17,16 @@ enum SamplePolyAreas SAMPLE_POLYAREA_DOOR, SAMPLE_POLYAREA_GRASS, SAMPLE_POLYAREA_JUMP, + SAMPLE_POLYAREA_SLOPE, }; enum SamplePolyFlags { SAMPLE_POLYFLAGS_WALK = 0x01, // Ability to walk (ground, grass, road) - SAMPLE_POLYFLAGS_SWIM = 0x02, // Ability to swim (water). - SAMPLE_POLYFLAGS_DOOR = 0x04, // Ability to move through doors. - SAMPLE_POLYFLAGS_JUMP = 0x08, // Ability to jump. - SAMPLE_POLYFLAGS_DISABLED = 0x10, // Disabled polygon - SAMPLE_POLYFLAGS_ALL = 0xffff // All abilities. + SAMPLE_POLYFLAGS_SLOPES = 0x02, // Ability to swim (water). + SAMPLE_POLYFLAGS_SWIM = 0x04, // Ability to move through doors. + SAMPLE_POLYFLAGS_MAGMA_SLIME = 0x08, // Ability to jump. + SAMPLE_POLYFLAGS_DISABLED = 0x20, // Disabled polygon + SAMPLE_POLYFLAGS_ALL = 0xffff // All abilities. }; enum SamplePartitionType diff --git a/contrib/recastdemomod/Include/SampleInterfaces.h b/contrib/recastdemomod/Include/SampleInterfaces.h index 5d6413382cf..d8a629c64dc 100644 --- a/contrib/recastdemomod/Include/SampleInterfaces.h +++ b/contrib/recastdemomod/Include/SampleInterfaces.h @@ -46,14 +46,21 @@ class BuildContext : public rcContext std::string m_mMapsFolder; std::string m_vMapsFolder; + unsigned int mapID; + unsigned int tileX; + unsigned int tileY; + public: - BuildContext(const char* dataDir); + BuildContext(const char* dataDir, unsigned int mapId, unsigned int tileX, unsigned int tileY); virtual ~BuildContext(); const char* getMapFolder() { return m_mapsFolder.c_str(); } const char* getMMapFolder() { return m_mMapsFolder.c_str(); } const char* getVMapFolder() { return m_vMapsFolder.c_str(); } const char* getDataDir() { return m_dataDir; } + unsigned int getMapId() { return mapID; } + unsigned int getTileX() { return tileX; } + unsigned int getTileY() { return tileY; } /// Dumps the log to stdout. void dumpLog(const char* format, ...); diff --git a/contrib/recastdemomod/Source/CMaNGOS_Map.cpp b/contrib/recastdemomod/Source/CMaNGOS_Map.cpp index 26b1b86f297..89abe649164 100644 --- a/contrib/recastdemomod/Source/CMaNGOS_Map.cpp +++ b/contrib/recastdemomod/Source/CMaNGOS_Map.cpp @@ -266,11 +266,58 @@ void CMaNGOS_Map::Init() ScanFoldersForMaps(); if (m_mapID < 0) return; + + if (m_ctx->getMapId() != m_mapID && m_ctx->getTileX() && m_ctx->getTileY()) + m_mapID = m_ctx->getMapId(); + scanFoldersForTiles(); m_MapInfos = new MapInfos(); m_MapInfos->Init(m_mapID, m_ctx); m_navMesh = m_MapInfos->GetNavMesh(); m_navQuery = m_MapInfos->GetNavMeshQuery(); + + if (m_ctx->getTileX() && m_ctx->getTileY()) + { + if (LoadTileData(m_ctx->getTileX(), m_ctx->getTileY())) + { + //m_showLevel = SHOW_LEVEL_NONE; + } + // if first chosen tile, add neighbor + if (!m_MapInfos->IsEmpty()) + { + unsigned int x, y; + x = m_ctx->getTileX(); + y = m_ctx->getTileY(); + m_NeighborTiles.clear(); + if (x != y || x != 64) + { + for (int i = 0; i < 3; ++i) + { + for (int j = 0; j < 3; ++j) + { + uint32 tx = x - 1 + i; + uint32 ty = y - 1 + j; + + // skip current already loaded tile + if (tx == x && ty == y) + continue; + + // add only available tile + uint32 pxy = VMAP::StaticMapTree::packTileID(tx, ty); + if (m_TilesFound.find(pxy) != m_TilesFound.end()) + m_NeighborTiles.insert(pxy); + } + } + } + } + + if (m_MapInfos->IsEmpty()) + m_TileButtonStr = "Click to choose a tile"; + else if (!m_NeighborTiles.empty()) + m_TileButtonStr = "Add neighbor tile"; + else + m_TileButtonStr = "No neighbor tile to add"; + } } void CMaNGOS_Map::ClearAllGeoms() @@ -299,7 +346,7 @@ void CMaNGOS_Map::resetCommonSettings() m_agentHeight = m_bigBaseUnit ? 3.0f : 6.0f; m_agentRadius = m_bigBaseUnit ? 1.0f : 2.0f; m_agentMaxClimb = m_bigBaseUnit ? 2.0f : 4.0f; - m_agentMaxSlope = 50.0f; + m_agentMaxSlope = 60.0f; m_regionMinSize = 60; m_regionMergeSize = 50; m_partitionType = SAMPLE_PARTITION_WATERSHED; diff --git a/contrib/recastdemomod/Source/MMapData.cpp b/contrib/recastdemomod/Source/MMapData.cpp index 08644a65a28..d7258972dbf 100644 --- a/contrib/recastdemomod/Source/MMapData.cpp +++ b/contrib/recastdemomod/Source/MMapData.cpp @@ -497,22 +497,17 @@ void MMapData::BuildNavMeshOfTile(unsigned int tx, unsigned int ty, MeshObjects // Update poly flags from areas. for (int i = 0; i < m_pmesh->npolys; ++i) { - if (m_pmesh->areas[i] == RC_WALKABLE_AREA) - m_pmesh->areas[i] = SAMPLE_POLYAREA_GROUND; - - if (m_pmesh->areas[i] == SAMPLE_POLYAREA_GROUND || - m_pmesh->areas[i] == SAMPLE_POLYAREA_GRASS || - m_pmesh->areas[i] == SAMPLE_POLYAREA_ROAD) + if (m_pmesh->areas[i] == 11) { m_pmesh->flags[i] = SAMPLE_POLYFLAGS_WALK; } - else if (m_pmesh->areas[i] == SAMPLE_POLYAREA_WATER) + else if (m_pmesh->areas[i] == 9 || m_pmesh->areas[i] == 8) { m_pmesh->flags[i] = SAMPLE_POLYFLAGS_SWIM; } - else if (m_pmesh->areas[i] == SAMPLE_POLYAREA_DOOR) + else if (m_pmesh->areas[i] == 10) { - m_pmesh->flags[i] = SAMPLE_POLYFLAGS_WALK | SAMPLE_POLYFLAGS_DOOR; + m_pmesh->flags[i] = SAMPLE_POLYFLAGS_SLOPES; } } diff --git a/contrib/recastdemomod/Source/NavMeshTesterTool.cpp b/contrib/recastdemomod/Source/NavMeshTesterTool.cpp index 378a86d2f0f..95ceba251e4 100644 --- a/contrib/recastdemomod/Source/NavMeshTesterTool.cpp +++ b/contrib/recastdemomod/Source/NavMeshTesterTool.cpp @@ -263,6 +263,7 @@ void NavMeshTesterTool::init(CMaNGOS_Map* sample) m_filter.setAreaCost(SAMPLE_POLYAREA_DOOR, 1.0f); m_filter.setAreaCost(SAMPLE_POLYAREA_GRASS, 2.0f); m_filter.setAreaCost(SAMPLE_POLYAREA_JUMP, 1.5f); + m_filter.setAreaCost(SAMPLE_POLYAREA_SLOPE, 5.0f); } m_neighbourhoodRadius = sample->getAgentRadius() * 20.0f; @@ -424,14 +425,9 @@ void NavMeshTesterTool::handleMenu() m_filter.setIncludeFlags(m_filter.getIncludeFlags() ^ SAMPLE_POLYFLAGS_SWIM); recalc(); } - if (imguiCheck("Door", (m_filter.getIncludeFlags() & SAMPLE_POLYFLAGS_DOOR) != 0)) + if (imguiCheck("Slopes", (m_filter.getIncludeFlags() & SAMPLE_POLYFLAGS_SLOPES) != 0)) { - m_filter.setIncludeFlags(m_filter.getIncludeFlags() ^ SAMPLE_POLYFLAGS_DOOR); - recalc(); - } - if (imguiCheck("Jump", (m_filter.getIncludeFlags() & SAMPLE_POLYFLAGS_JUMP) != 0)) - { - m_filter.setIncludeFlags(m_filter.getIncludeFlags() ^ SAMPLE_POLYFLAGS_JUMP); + m_filter.setIncludeFlags(m_filter.getIncludeFlags() ^ SAMPLE_POLYFLAGS_SLOPES); recalc(); } imguiUnindent(); @@ -450,14 +446,9 @@ void NavMeshTesterTool::handleMenu() m_filter.setExcludeFlags(m_filter.getExcludeFlags() ^ SAMPLE_POLYFLAGS_SWIM); recalc(); } - if (imguiCheck("Door", (m_filter.getExcludeFlags() & SAMPLE_POLYFLAGS_DOOR) != 0)) - { - m_filter.setExcludeFlags(m_filter.getExcludeFlags() ^ SAMPLE_POLYFLAGS_DOOR); - recalc(); - } - if (imguiCheck("Jump", (m_filter.getExcludeFlags() & SAMPLE_POLYFLAGS_JUMP) != 0)) + if (imguiCheck("Slopes", (m_filter.getExcludeFlags() & SAMPLE_POLYFLAGS_SLOPES) != 0)) { - m_filter.setExcludeFlags(m_filter.getExcludeFlags() ^ SAMPLE_POLYFLAGS_JUMP); + m_filter.setExcludeFlags(m_filter.getExcludeFlags() ^ SAMPLE_POLYFLAGS_SLOPES); recalc(); } imguiUnindent(); diff --git a/contrib/recastdemomod/Source/SampleInterfaces.cpp b/contrib/recastdemomod/Source/SampleInterfaces.cpp index de8e3745cd9..769e35736ab 100644 --- a/contrib/recastdemomod/Source/SampleInterfaces.cpp +++ b/contrib/recastdemomod/Source/SampleInterfaces.cpp @@ -16,11 +16,14 @@ //////////////////////////////////////////////////////////////////////////////////////////////////// -BuildContext::BuildContext(const char* dataDir) : +BuildContext::BuildContext(const char* dataDir, unsigned int mapid, unsigned int tilex, unsigned int tiley) : m_messageCount(0), m_textPoolSize(0) { m_dataDir = dataDir; + mapID = mapid; + tileX = tilex; + tileY = tiley; m_mapsFolder = dataDir; m_vMapsFolder = dataDir; m_mMapsFolder = dataDir; diff --git a/contrib/recastdemomod/Source/main.cpp b/contrib/recastdemomod/Source/main.cpp index 24dc0f89200..b874b073f3c 100644 --- a/contrib/recastdemomod/Source/main.cpp +++ b/contrib/recastdemomod/Source/main.cpp @@ -28,6 +28,7 @@ #include "RecastDebugDraw.h" #include "Filelist.h" #include +#include //#include "Sample_SoloMesh.h" //#include "Sample_TileMesh.h" @@ -71,6 +72,39 @@ bool handleArgs(int argc, char** argv, char*& dataDir) return true; } +uint32 check_int_input(const char* arg) +{ + std::istringstream ss(arg); + unsigned int x; + if (!(ss >> x)) + return 0; // std::cerr << "Invalid number: " << arg << '\n'; + else if (!ss.eof()) + return 0; // std::cerr << "Trailing characters after number: " << arg << '\n'; + return x; +} + +bool handleArgs(int argc, char** argv, char* argName, uint32& result) +{ + uint32 param = 0; + + for (int i = 1; i < argc; ++i) + { + if (strcmp(argv[i], argName) == 0) + { + param = check_int_input(argv[++i]); + if (!param) + { + printUsage(); + return false; + } + + result = param; + } + } + + return true; +} + int main(int argc, char** argv) { // Init SDL @@ -81,10 +115,17 @@ int main(int argc, char** argv) } char* dataDir = nullptr; + uint32 mapId = 0; + uint32 tileX = 0; + uint32 tileY = 0; if (!handleArgs(argc, argv, dataDir)) return -1; + handleArgs(argc, argv, "-map", mapId); + handleArgs(argc, argv, "-tilex", tileX); + handleArgs(argc, argv, "-tiley", tileY); + // Center window char env[] = "SDL_VIDEO_CENTERED=1"; putenv(env); @@ -164,7 +205,7 @@ int main(int argc, char** argv) //InputGeom* geom = 0; CMaNGOS_Map* sample = new CMaNGOS_Map(); - BuildContext ctx(dataDir); + BuildContext ctx(dataDir, mapId, tileX, tileY); sample->setContext(&ctx); sample->Init(); if (sample->GetMapId() < 0) diff --git a/sql/base/characters.sql b/sql/base/characters.sql index 01763c6366e..38638d8b9df 100644 --- a/sql/base/characters.sql +++ b/sql/base/characters.sql @@ -1144,6 +1144,29 @@ LOCK TABLES `creature_respawn` WRITE; /*!40000 ALTER TABLE `creature_respawn` ENABLE KEYS */; UNLOCK TABLES; +-- +-- Table structure for table `custom_solocraft_character_stats` +-- + +DROP TABLE IF EXISTS `custom_solocraft_character_stats`; +CREATE TABLE `custom_solocraft_character_stats` ( + `GUID` int(11) unsigned NOT NULL, + `Difficulty` float NOT NULL, + `GroupSize` int(11) NOT NULL, + `SpellPower` int(10) unsigned NOT NULL DEFAULT '0', + `Stats` float NOT NULL DEFAULT '100', + PRIMARY KEY (`GUID`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin; + +-- +-- Dumping data for table `custom_solocraft_character_stats` +-- + +LOCK TABLES `custom_solocraft_character_stats` WRITE; +/*!40000 ALTER TABLE `custom_solocraft_character_stats` DISABLE KEYS */; +/*!40000 ALTER TABLE `custom_solocraft_character_stats` ENABLE KEYS */; +UNLOCK TABLES; + -- -- Table structure for table `event_group_chosen` -- diff --git a/sql/base/mangos.sql b/sql/base/mangos.sql index 00d6b72aad5..024aba55623 100644 --- a/sql/base/mangos.sql +++ b/sql/base/mangos.sql @@ -625,6 +625,8 @@ INSERT INTO `command` VALUES ('banlist account',3,'Syntax: .banlist account [$Name]\r\nSearches the banlist for a account name pattern or show full list account bans.'), ('banlist character',3,'Syntax: .banlist character $Name\r\nSearches the banlist for a character name pattern. Pattern required.'), ('banlist ip',3,'Syntax: .banlist ip [$Ip]\r\nSearches the banlist for a IP pattern or show full list of IP bans.'), +('bot add',0,'Syntax: .bot add [$botname]\r\nAdd bot to world.'), +('bot self',0,'Syntax: .bot self\r\nEnable or disable playerbot AI on current logged player. Client side only.'), ('cast',3,'Syntax: .cast #spellid [triggered]\r\n Cast #spellid to selected target. If no target selected cast to self. If \'trigered\' or part provided then spell casted with triggered flag.'), ('cast back',3,'Syntax: .cast back #spellid [triggered]\r\n Selected target will cast #spellid to your character. If \'trigered\' or part provided then spell casted with triggered flag.'), ('cast dist',3,'Syntax: .cast dist #spellid [#dist [triggered]]\r\n You will cast spell to pint at distance #dist. If \'trigered\' or part provided then spell casted with triggered flag. Not all spells can be casted as area spells.'), diff --git a/sql/create/db_create_mysql.sql b/sql/create/db_create_mysql.sql index b910046aced..26c14a5c079 100644 --- a/sql/create/db_create_mysql.sql +++ b/sql/create/db_create_mysql.sql @@ -6,6 +6,8 @@ CREATE DATABASE `wotlkcharacters` DEFAULT CHARACTER SET utf8 COLLATE utf8_genera CREATE DATABASE `wotlkrealmd` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; +CREATE DATABASE `wotlkplayerbots` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; + CREATE USER IF NOT EXISTS 'mangos'@'localhost' IDENTIFIED BY 'mangos'; GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, LOCK TABLES, CREATE TEMPORARY TABLES ON `wotlkmangos`.* TO 'mangos'@'localhost'; @@ -15,3 +17,5 @@ GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, LOCK TABLES, CREATE T GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, LOCK TABLES, CREATE TEMPORARY TABLES ON `wotlkcharacters`.* TO 'mangos'@'localhost'; GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, LOCK TABLES, CREATE TEMPORARY TABLES ON `wotlkrealmd`.* TO 'mangos'@'localhost'; + +GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, LOCK TABLES, CREATE TEMPORARY TABLES ON `wotlkplayerbots`.* TO 'mangos'@'localhost'; diff --git a/sql/custom/transmog/transmog_characters.sql b/sql/custom/transmog/transmog_characters.sql new file mode 100644 index 00000000000..5be6eabb6c0 --- /dev/null +++ b/sql/custom/transmog/transmog_characters.sql @@ -0,0 +1,26 @@ +-- Dumping structure for table tc_c.custom_transmogrification +CREATE TABLE IF NOT EXISTS `custom_transmogrification` ( + `GUID` int(10) unsigned NOT NULL COMMENT 'Item guidLow', + `FakeEntry` int(10) unsigned NOT NULL COMMENT 'Item entry', + `Owner` int(10) unsigned NOT NULL COMMENT 'Player guidLow', + PRIMARY KEY (`GUID`), + KEY `Owner` (`Owner`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='6_2'; + +-- Data exporting was unselected. + + +-- Dumping structure for table tc_c.custom_transmogrification_sets +CREATE TABLE IF NOT EXISTS `custom_transmogrification_sets` ( + `Owner` int(10) unsigned NOT NULL COMMENT 'Player guidlow', + `PresetID` tinyint(3) unsigned NOT NULL COMMENT 'Preset identifier', + `SetName` text COMMENT 'SetName', + `SetData` text COMMENT 'Slot1 Entry1 Slot2 Entry2', + PRIMARY KEY (`Owner`,`PresetID`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='6_1'; + +CREATE TABLE IF NOT EXISTS `custom_unlocked_appearances` ( + `account_id` int(10) unsigned NOT NULL, + `item_template_id` mediumint(8) unsigned NOT NULL DEFAULT '0', + PRIMARY KEY (`account_id`, `item_template_id`) +) ENGINE = InnoDB DEFAULT CHARSET = utf8; \ No newline at end of file diff --git a/sql/custom/transmog/transmog_world.sql b/sql/custom/transmog/transmog_world.sql new file mode 100644 index 00000000000..df9fe24d0fd --- /dev/null +++ b/sql/custom/transmog/transmog_world.sql @@ -0,0 +1,57 @@ +SET +@Entry = 190010, +@Name = "Magister Stellaria"; +DELETE FROM `creature_template` WHERE `entry` = 190010; + +INSERT INTO `creature_template` (`entry`, `modelid1`, `modelid2`, `name`, `subname`, `IconName`, `GossipMenuId`, `minlevel`, `maxlevel`, `Expansion`, `faction`, `NpcFlags`, `scale`, `rank`, `DamageSchool`, `MeleeBaseAttackTime`, `RangedBaseAttackTime`, `unitClass`, `unitFlags`, `CreatureType`, `CreatureTypeFlags`, `lootid`, `PickpocketLootId`, `SkinningLootId`, `AIName`, `MovementType`, `RacialLeader`, `RegenerateStats`, `MechanicImmuneMask`, `ExtraFlags`, `ScriptName`) VALUES +(@Entry, 2240, 0, @Name, "Transmogrifier", NULL, 0, 80, 80, 2, 35, 1, 1, 0, 0, 2000, 0, 1, 0, 7, 138936390, 0, 0, 0, '', 0, 0, 1, 0, 0, 'npc_transmogrifier'); + +SET @TEXT_ID := 601083; +DELETE FROM `npc_text` WHERE `ID` BETWEEN @TEXT_ID AND @TEXT_ID+5; +INSERT INTO `npc_text` (`ID`, `text0_0`) VALUES +(@TEXT_ID, 'Transmogrification allows you to change how your items look like without changing the stats of the items.\r\nItems used in transmogrification are no longer refundable, tradeable and are bound to you.\r\n\r\nNot everything can be transmogrified with eachother.\r\nRestrictions include but are not limited to:\r\nOnly armor and weapons can be transmogrified\r\nGuns, bows and crossbows can be transmogrified with eachother\r\nFishing poles can not be transmogrified\r\nYou must be able to equip both items used in the process.\r\n\r\nTransmogrifications stay on your items as long as you own them.\r\nIf you try to put the item in guild bank or mail it to someone else, the transmogrification is stripped.\r\n\r\nYou can also remove transmogrifications for free at the transmogrifier.'), +(@TEXT_ID+1, 'You can save your own transmogrification sets.\r\n\r\nTo save, first you must transmogrify your equipped items.\r\nThen when you go to the set management menu and go to save set menu,\r\nall items you have transmogrified are displayed so you see what you are saving.\r\nIf you think the set is fine, you can click to save the set and name it as you wish.\r\n\r\nTo use a set you can click the saved set in the set management menu and then select use set.\r\nIf the set has a transmogrification for an item that is already transmogrified, the old transmogrification is lost.\r\nNote that same transmogrification restrictions apply when trying to use a set as in normal transmogrification.\r\n\r\nTo delete a set you can go to the set\'s menu and select delete set.'), +(@TEXT_ID+2, 'Select desired look for this slot.'), +(@TEXT_ID+3, 'Check price and click Confirm to change item look!'), +(@TEXT_ID+4, 'This item is already transmogrified.\r\nClick Remove to restore original look!'), +(@TEXT_ID+5, 'This item is already transmogrified.\r\nClick Remove to restore original look,\r\n\or select a different one!'); + +SET @STRING_ENTRY := 11100; +DELETE FROM `mangos_string` WHERE `entry` BETWEEN @STRING_ENTRY+0 AND @STRING_ENTRY+29; +INSERT INTO `mangos_string` (`entry`, `content_default`) VALUES +(@STRING_ENTRY+0, 'Item successfully transmogrified.'), +(@STRING_ENTRY+1, 'Equipment slot is empty.'), +(@STRING_ENTRY+2, 'Invalid source item selected.'), +(@STRING_ENTRY+3, 'Source item does not exist.'), +(@STRING_ENTRY+4, 'Destination item does not exist.'), +(@STRING_ENTRY+5, 'Selected items are invalid.'), +(@STRING_ENTRY+6, 'You don''t have enough money.'), +(@STRING_ENTRY+7, 'You don''t have enough tokens.'), +(@STRING_ENTRY+8, 'Destination item already has this transmogrification.'), +(@STRING_ENTRY+9, 'All your transmogrifications were removed.'), +(@STRING_ENTRY+10, 'Item transmogrifications were removed.'), +(@STRING_ENTRY+11, 'No transmogrification found.'), +(@STRING_ENTRY+12, 'No transmogrification found on this item.'), +(@STRING_ENTRY+13, 'Invalid name inserted.'), +(@STRING_ENTRY+14, 'Showing transmogrifieded items, relog to update the current area.'), +(@STRING_ENTRY+15, 'Hiding transmogrifieded items, relog to update the current area.'), +(@STRING_ENTRY+16, 'The selected Item is not suitable for transmogrification.'), +(@STRING_ENTRY+17, 'The selected Item cannot be used for transmogrification of the target player.'), +(@STRING_ENTRY+18, 'You have no suitable items in your inventory.'), +(@STRING_ENTRY+19, 'Remove: '), +(@STRING_ENTRY+20, '< [Back]'), +(@STRING_ENTRY+21, '< [Main Menu]'), +(@STRING_ENTRY+22, 'Before: '), +(@STRING_ENTRY+23, 'After:'), +(@STRING_ENTRY+24, 'Cost is '), +(@STRING_ENTRY+25, 'Confirm'), +(@STRING_ENTRY+26, 'Your item: '), +(@STRING_ENTRY+27, 'Transmog: '), +(@STRING_ENTRY+28, 'Possible transmogrifications:'), +(@STRING_ENTRY+29, 'Options:'); + +DELETE FROM `command` WHERE `name` IN ('transmog', 'transmog add', 'transmog add set'); +INSERT INTO `command` (`name`, `security`, `help`) VALUES +('transmog', 0, 'Syntax: .transmog \nAllows seeing transmogrified items and the transmogrifier NPC.'), +('transmog add', 1, 'Syntax: .transmog add $player $item\nAdds an item to a player\'s appearance collection.'), +('transmog add set', 1, 'Syntax: .transmog add set $player $itemSet\nAdds items of an ItemSet to a player\'s appearance collection.'); \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ba37dd1c3e8..52ca754527e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -29,3 +29,7 @@ endif() if(BUILD_LOGIN_SERVER) add_subdirectory(realmd) endif() + +if(BUILD_IKE3_BOTS) +add_subdirectory(modules) +endif() diff --git a/src/game/AI/ScriptDevAI/scripts/custom/Transmogrification.cpp b/src/game/AI/ScriptDevAI/scripts/custom/Transmogrification.cpp new file mode 100644 index 00000000000..4aa5cbea5b5 --- /dev/null +++ b/src/game/AI/ScriptDevAI/scripts/custom/Transmogrification.cpp @@ -0,0 +1,837 @@ +#include "Transmogrification.h" + +Transmogrification* Transmogrification::instance() +{ + static Transmogrification instance; + return &instance; +} + +#ifdef PRESETS +void Transmogrification::PresetTransmog(Player* player, Item* itemTransmogrified, uint32 fakeEntry, uint8 slot) +{ + if (!EnableSets) + return; + if (!player || !itemTransmogrified) + return; + if (slot >= EQUIPMENT_SLOT_END) + return; + if (!CanTransmogrifyItemWithItem(player, itemTransmogrified->GetProto(), sObjectMgr.GetItemPrototype(fakeEntry))) + return; + + // [AZTH] Custom + if (GetFakeEntry(itemTransmogrified->GetObjectGuid())) + DeleteFakeEntry(player, slot, itemTransmogrified); + + SetFakeEntry(player, fakeEntry, itemTransmogrified); // newEntry + itemTransmogrified->SetOwnerGuid(player->GetObjectGuid()); +} + +void Transmogrification::LoadPlayerSets(ObjectGuid pGUID) +{ + for (presetData::iterator it = presetById[pGUID].begin(); it != presetById[pGUID].end(); ++it) + it->second.clear(); + + presetById[pGUID].clear(); + + presetByName[pGUID].clear(); + + QueryResult* result = CharacterDatabase.PQuery("SELECT `PresetID`, `SetName`, `SetData` FROM `custom_transmogrification_sets` WHERE Owner = %u", pGUID.GetCounter()); + if (result) + { + do + { + uint8 PresetID = (*result)[0].GetUInt8(); + std::string SetName = (*result)[1].GetString(); + std::istringstream SetData((*result)[2].GetString()); + while (SetData.good()) + { + uint32 slot; + uint32 entry; + SetData >> slot >> entry; + if (SetData.fail()) + break; + if (slot >= EQUIPMENT_SLOT_END) + { + sLog.outError("Item entry (FakeEntry: %u, player: %s, slot: %u, presetId: %u) has invalid slot, ignoring.", entry, std::to_string(pGUID).c_str(), slot, uint32(PresetID)); + continue; + } + if (sObjectMgr.GetItemPrototype(entry)) + presetById[pGUID][PresetID][slot] = entry; // Transmogrification::Preset(presetName, fakeEntry); + //else + //sLog->outError(LOG_FILTER_SQL, "Item entry (FakeEntry: %u, playerGUID: %u, slot: %u, presetId: %u) does not exist, ignoring.", entry, GUID_LOPART(pGUID), uint32(slot), uint32(PresetID)); + } + + if (!presetById[pGUID][PresetID].empty()) + { + presetByName[pGUID][PresetID] = SetName; + // load all presets anyways + //if (presetByName[pGUID].size() >= GetMaxSets()) + // break; + } + else // should be deleted on startup, so this never runs (shouldnt..) + { + presetById[pGUID].erase(PresetID); + CharacterDatabase.PExecute("DELETE FROM `custom_transmogrification_sets` WHERE Owner = %u AND PresetID = %u", pGUID.GetCounter(), PresetID); + } + } while (result->NextRow()); + } +} + +bool Transmogrification::GetEnableSets() const +{ + return EnableSets; +} +uint8 Transmogrification::GetMaxSets() const +{ + return MaxSets; +} +float Transmogrification::GetSetCostModifier() const +{ + return SetCostModifier; +} +int32 Transmogrification::GetSetCopperCost() const +{ + return SetCopperCost; +} + +void Transmogrification::UnloadPlayerSets(ObjectGuid pGUID) +{ + for (presetData::iterator it = presetById[pGUID].begin(); it != presetById[pGUID].end(); ++it) + it->second.clear(); + presetById[pGUID].clear(); + + presetByName[pGUID].clear(); +} +#endif + +const char* Transmogrification::GetSlotName(uint8 slot, WorldSession* /*session*/) const +{ + switch (slot) + { + case EQUIPMENT_SLOT_HEAD: return "Head";// session->GetAcoreString(LANG_SLOT_NAME_HEAD); + case EQUIPMENT_SLOT_SHOULDERS: return "Shoulders";// session->GetAcoreString(LANG_SLOT_NAME_SHOULDERS); + case EQUIPMENT_SLOT_BODY: return "Shirt";// session->GetAcoreString(LANG_SLOT_NAME_BODY); + case EQUIPMENT_SLOT_CHEST: return "Chest";// session->GetAcoreString(LANG_SLOT_NAME_CHEST); + case EQUIPMENT_SLOT_WAIST: return "Waist";// session->GetAcoreString(LANG_SLOT_NAME_WAIST); + case EQUIPMENT_SLOT_LEGS: return "Legs";// session->GetAcoreString(LANG_SLOT_NAME_LEGS); + case EQUIPMENT_SLOT_FEET: return "Feet";// session->GetAcoreString(LANG_SLOT_NAME_FEET); + case EQUIPMENT_SLOT_WRISTS: return "Wrists";// session->GetAcoreString(LANG_SLOT_NAME_WRISTS); + case EQUIPMENT_SLOT_HANDS: return "Hands";// session->GetAcoreString(LANG_SLOT_NAME_HANDS); + case EQUIPMENT_SLOT_BACK: return "Back";// session->GetAcoreString(LANG_SLOT_NAME_BACK); + case EQUIPMENT_SLOT_MAINHAND: return "Main hand";// session->GetAcoreString(LANG_SLOT_NAME_MAINHAND); + case EQUIPMENT_SLOT_OFFHAND: return "Off hand";// session->GetAcoreString(LANG_SLOT_NAME_OFFHAND); + case EQUIPMENT_SLOT_RANGED: return "Ranged";// session->GetAcoreString(LANG_SLOT_NAME_RANGED); + case EQUIPMENT_SLOT_TABARD: return "Tabard";// session->GetAcoreString(LANG_SLOT_NAME_TABARD); + default: return nullptr; + } +} + +std::string Transmogrification::GetItemIcon(uint32 entry, uint32 width, uint32 height, int x, int y) const +{ + std::ostringstream ss; + ss << "|TInterface"; + const ItemPrototype* temp = sObjectMgr.GetItemPrototype(entry); + const ItemDisplayInfoEntry* dispInfo = nullptr; + if (temp) + { + GameObjectDisplayInfoEntry const* info = sGameObjectDisplayInfoStore.LookupEntry(temp->DisplayInfoID); + dispInfo = sItemStorage.LookupEntry(temp->DisplayInfoID); + if (dispInfo) + //ss << "/ICONS/" << dispInfo->inventoryIcon; + ss << "/ICONS/" << dispInfo->ID; + } + if (!dispInfo) + ss << "/InventoryItems/WoWUnknownItem01"; + ss << ":" << width << ":" << height << ":" << x << ":" << y << "|t"; + return ss.str(); +} + +std::string Transmogrification::GetSlotIcon(uint8 slot, uint32 width, uint32 height, int x, int y) const +{ + std::ostringstream ss; + ss << "|TInterface/PaperDoll/"; + switch (slot) + { + case EQUIPMENT_SLOT_HEAD: ss << "UI-PaperDoll-Slot-Head"; break; + case EQUIPMENT_SLOT_SHOULDERS: ss << "UI-PaperDoll-Slot-Shoulder"; break; + case EQUIPMENT_SLOT_BODY: ss << "UI-PaperDoll-Slot-Shirt"; break; + case EQUIPMENT_SLOT_CHEST: ss << "UI-PaperDoll-Slot-Chest"; break; + case EQUIPMENT_SLOT_WAIST: ss << "UI-PaperDoll-Slot-Waist"; break; + case EQUIPMENT_SLOT_LEGS: ss << "UI-PaperDoll-Slot-Legs"; break; + case EQUIPMENT_SLOT_FEET: ss << "UI-PaperDoll-Slot-Feet"; break; + case EQUIPMENT_SLOT_WRISTS: ss << "UI-PaperDoll-Slot-Wrists"; break; + case EQUIPMENT_SLOT_HANDS: ss << "UI-PaperDoll-Slot-Hands"; break; + case EQUIPMENT_SLOT_BACK: ss << "UI-PaperDoll-Slot-Chest"; break; + case EQUIPMENT_SLOT_MAINHAND: ss << "UI-PaperDoll-Slot-MainHand"; break; + case EQUIPMENT_SLOT_OFFHAND: ss << "UI-PaperDoll-Slot-SecondaryHand"; break; + case EQUIPMENT_SLOT_RANGED: ss << "UI-PaperDoll-Slot-Ranged"; break; + case EQUIPMENT_SLOT_TABARD: ss << "UI-PaperDoll-Slot-Tabard"; break; + default: ss << "UI-Backpack-EmptySlot"; + } + ss << ":" << width << ":" << height << ":" << x << ":" << y << "|t"; + return ss.str(); +} + +std::string Transmogrification::GetItemLink(Item* item, WorldSession* session) const +{ + int loc_idx = session->GetSessionDbLocaleIndex(); + const ItemPrototype* temp = item->GetProto(); + std::string name = temp->Name1; + //if (ItemLocale const* il = sObjectMgr.GetItemLocale(temp->ItemId)) + //ObjectMgr::GetLocaleString(il->Name, loc_idx, name); + + /*if (int32 itemRandPropId = item->GetItemRandomPropertyId()) + { + std::array const* suffix = nullptr; + if (itemRandPropId < 0) + { + if (const ItemRandomPropertiesEntry* itemRandEntry = sItemRandomPropertiesStore.LookupEntry(-itemRandPropId)) + suffix = &itemRandEntry->Name; + } + else + { + if (const ItemRandomPropertiesEntry* itemRandEntry = sItemRandomPropertiesStore.LookupEntry(itemRandPropId)) + suffix = &itemRandEntry->Name; + } + if (suffix) + { + std::string_view test((*suffix)[(name != temp->Name1) ? loc_idx : DEFAULT_LOCALE]); + if (!test.empty()) + { + name += ' '; + name += test; + } + } + }*/ + + std::ostringstream oss; + oss << "|c" << std::hex << ItemQualityColors[temp->Quality] << std::dec << + "|Hitem:" << temp->ItemId << ":" << + item->GetEnchantmentId(PERM_ENCHANTMENT_SLOT) << ":" << + item->GetEnchantmentId(PROP_ENCHANTMENT_SLOT_0) << ":" << + item->GetEnchantmentId(PROP_ENCHANTMENT_SLOT_1) << ":" << + item->GetEnchantmentId(PROP_ENCHANTMENT_SLOT_2) << ":" << + item->GetEnchantmentId(PROP_ENCHANTMENT_SLOT_3) << ":" << + item->GetItemRandomPropertyId() << ":" << item->GetItemSuffixFactor() << ":" << + item->GetOwner()->GetLevel() << "|h[" << name << "]|h|r"; + + return oss.str(); +} + +std::string Transmogrification::GetItemLink(uint32 entry, WorldSession* session) const +{ + const ItemPrototype* temp = sObjectMgr.GetItemPrototype(entry); + int loc_idx = session->GetSessionDbLocaleIndex(); + std::string name = temp->Name1; + std::ostringstream oss; + oss << "|c" << std::hex << ItemQualityColors[temp->Quality] << std::dec << + "|Hitem:" << entry << ":0:0:0:0:0:0:0:0:0|h[" << name << "]|h|r"; + + return oss.str(); +} + +uint32 Transmogrification::GetFakeEntry(const ObjectGuid itemGUID) const +{ + const auto itr = dataMap.find(itemGUID); + if (itr == dataMap.end()) return 0; + const auto itr2 = entryMap.find(itr->second); + if (itr2 == entryMap.end()) return 0; + const auto itr3 = itr2->second.find(itemGUID); + if (itr3 == itr2->second.end()) return 0; + return itr3->second; +} + +void Transmogrification::UpdateItem(Player* player, Item* item) const +{ + if (item->IsEquipped()) + player->SetVisibleItemSlot(item->GetSlot(), item); +} + +void Transmogrification::DeleteFakeEntry(Player* player, uint8 /*slot*/, Item* itemTransmogrified) +{ + //if (!GetFakeEntry(item)) + // return false; + DeleteFakeFromDB(itemTransmogrified->GetObjectGuid()); + UpdateItem(player, itemTransmogrified); +} + +void Transmogrification::SetFakeEntry(Player* player, uint32 newEntry, Item* itemTransmogrified) +{ + const ObjectGuid itemGUID = itemTransmogrified->GetObjectGuid(); + entryMap[player->GetObjectGuid()][itemGUID] = newEntry; + dataMap[itemGUID] = player->GetObjectGuid(); + CharacterDatabase.PExecute("REPLACE INTO custom_transmogrification (GUID, FakeEntry, Owner) VALUES (%u, %u, %u)", itemGUID.GetCounter(), newEntry, player->GetObjectGuid()); + UpdateItem(player, itemTransmogrified); +} + +TransmogAcoreStrings Transmogrification::Transmogrify(Player* player, ObjectGuid TransmogrifierGUID, uint8 slot, /*uint32 newEntry, */bool no_cost) +{ + if (slot >= EQUIPMENT_SLOT_END) + { + sLog.outError("Transmogrify wrong slot: %u", slot); + return LANG_ERR_TRANSMOG_INVALID_SLOT; + } + + Item* itemTransmogrifier = nullptr; + // guid of the transmogrifier item, if it's not 0 + if (TransmogrifierGUID) + { + itemTransmogrifier = player->GetItemByGuid(TransmogrifierGUID); + if (!itemTransmogrifier) + { + return LANG_ERR_TRANSMOG_MISSING_SRC_ITEM; + } + } + + // transmogrified item + Item* itemTransmogrified = player->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + if (!itemTransmogrified) + { + return LANG_ERR_TRANSMOG_MISSING_DEST_ITEM; + } + + if (!itemTransmogrifier) // reset look newEntry + { + // Custom + DeleteFakeEntry(player, slot, itemTransmogrified); + } + else + { + if (!CanTransmogrifyItemWithItem(player, itemTransmogrified->GetProto(), itemTransmogrifier->GetProto())) + return LANG_ERR_TRANSMOG_INVALID_ITEMS; + + if (sTransmogrification->GetFakeEntry(itemTransmogrified->GetObjectGuid()) == itemTransmogrifier->GetEntry()) + return LANG_ERR_TRANSMOG_SAME_DISPLAYID; + + if (!no_cost) + { + if (RequireToken) + { + if (player->HasItemCount(TokenEntry, TokenAmount)) + player->DestroyItemCount(TokenEntry, TokenAmount, true); + else + return LANG_ERR_TRANSMOG_NOT_ENOUGH_TOKENS; + } + + int32 cost = GetSpecialPrice(itemTransmogrified->GetProto()); + cost *= ScaledCostModifier; + cost += CopperCost; + + if (cost) // 0 cost if reverting look + { + if (player->GetMoney() < cost) + return LANG_ERR_TRANSMOG_NOT_ENOUGH_MONEY; + player->ModifyMoney(-cost); + } + } + + // Custom + SetFakeEntry(player, itemTransmogrifier->GetEntry(), itemTransmogrified); // newEntry + itemTransmogrified->SetOwnerGuid(player->GetObjectGuid()); + + if (itemTransmogrifier->GetProto()->Bonding == BIND_WHEN_EQUIPPED || itemTransmogrifier->GetProto()->Bonding == BIND_WHEN_USE) + itemTransmogrifier->SetBinding(true); + + itemTransmogrifier->SetOwnerGuid(player->GetObjectGuid()); + } + + return LANG_ERR_TRANSMOG_OK; +} + +bool Transmogrification::CanTransmogrifyItemWithItem(Player* player, ItemPrototype const* target, ItemPrototype const* source) const +{ + if (!target || !source) + return false; + + if (source->ItemId == target->ItemId) + return false; + + if (source->DisplayInfoID == target->DisplayInfoID) + return false; + + if (source->Class != target->Class) + return false; + + if (source->InventoryType == INVTYPE_BAG || + source->InventoryType == INVTYPE_RELIC || + source->InventoryType == INVTYPE_FINGER || + source->InventoryType == INVTYPE_TRINKET || + source->InventoryType == INVTYPE_AMMO || + source->InventoryType == INVTYPE_QUIVER) + return false; + + if (target->InventoryType == INVTYPE_BAG || + target->InventoryType == INVTYPE_RELIC || + target->InventoryType == INVTYPE_FINGER || + target->InventoryType == INVTYPE_TRINKET || + target->InventoryType == INVTYPE_AMMO || + target->InventoryType == INVTYPE_QUIVER) + return false; + + if (!SuitableForTransmogrification(player, target) || !SuitableForTransmogrification(player, source)) + return false; + + if (IsRangedWeapon(source->Class, source->SubClass) != IsRangedWeapon(target->Class, target->SubClass)) + return false; + + if (source->InventoryType != target->InventoryType) + { + if (source->Class == ITEM_CLASS_WEAPON && !(IsRangedWeapon(target->Class, target->SubClass) || + // [AZTH] Yehonal: fixed weapon check + (target->InventoryType == INVTYPE_WEAPON || target->InventoryType == INVTYPE_2HWEAPON || target->InventoryType == INVTYPE_WEAPONMAINHAND || target->InventoryType == INVTYPE_WEAPONOFFHAND) + && (source->InventoryType == INVTYPE_WEAPON || source->InventoryType == INVTYPE_2HWEAPON || source->InventoryType == INVTYPE_WEAPONMAINHAND || source->InventoryType == INVTYPE_WEAPONOFFHAND) + )) + return false; + if (source->Class == ITEM_CLASS_ARMOR && + !((source->InventoryType == INVTYPE_CHEST || source->InventoryType == INVTYPE_ROBE) && + (target->InventoryType == INVTYPE_CHEST || target->InventoryType == INVTYPE_ROBE))) + return false; + } + + return true; +} + +bool Transmogrification::SuitableForTransmogrification(Player* player, ItemPrototype const* proto) +{ + if (!player || !proto) + return false; + + if (proto->Class != ITEM_CLASS_ARMOR && + proto->Class != ITEM_CLASS_WEAPON) + return false; + + // Don't allow fishing poles + if (proto->Class == ITEM_CLASS_WEAPON && proto->SubClass == ITEM_SUBCLASS_WEAPON_FISHING_POLE) + return false; + + // Check skill requirements + if (proto->RequiredSkill != 0) + { + if (player->GetSkillValue(static_cast(proto->RequiredSkill)) == 0) + return false; + + if (player->GetSkillValue(static_cast(proto->RequiredSkill)) < proto->RequiredSkillRank) + return false; + } + + // Check spell requirements + if (proto->RequiredSpell != 0 && !player->HasSpell(proto->RequiredSpell)) + return false; + + // Check level requirements + if (player->GetLevel() < proto->RequiredLevel) + return false; + + return true; +} + +uint32 Transmogrification::GetSpecialPrice(ItemPrototype const* proto) const +{ + uint32 cost = proto->SellPrice < 10000 ? 10000 : proto->SellPrice; + return cost; +} + +std::string Transmogrification::FormatPrice(uint32 copper) const +{ + std::ostringstream out; + if (!copper) + { + out << "0"; + return out.str(); + } + + uint32 gold = uint32(copper / 10000); + copper -= (gold * 10000); + uint32 silver = uint32(copper / 100); + copper -= (silver * 100); + + bool space = false; + if (gold > 0) + { + out << gold << "g"; + space = true; + } + + if (silver > 0 && gold < 50) + { + if (space) out << " "; + out << silver << "s"; + space = true; + } + + if (copper > 0 && gold < 10) + { + if (space) out << " "; + out << copper << "c"; + } + + return out.str(); +} + +bool Transmogrification::IsRangedWeapon(uint32 Class, uint32 SubClass) const +{ + return Class == ITEM_CLASS_WEAPON && ( + SubClass == ITEM_SUBCLASS_WEAPON_BOW || + SubClass == ITEM_SUBCLASS_WEAPON_GUN || + SubClass == ITEM_SUBCLASS_WEAPON_CROSSBOW); +} + +bool Transmogrification::IsAllowed(uint32 entry) const +{ + return Allowed.find(entry) != Allowed.end(); +} + +bool Transmogrification::IsNotAllowed(uint32 entry) const +{ + return NotAllowed.find(entry) != NotAllowed.end(); +} + +bool Transmogrification::IsAllowedQuality(uint32 quality) const +{ + switch (quality) + { + case ITEM_QUALITY_POOR: return AllowPoor; + case ITEM_QUALITY_NORMAL: return AllowCommon; + case ITEM_QUALITY_UNCOMMON: return AllowUncommon; + case ITEM_QUALITY_RARE: return AllowRare; + case ITEM_QUALITY_EPIC: return AllowEpic; + case ITEM_QUALITY_LEGENDARY: return AllowLegendary; + case ITEM_QUALITY_ARTIFACT: return AllowArtifact; + default: return false; + } +} + +void Transmogrification::LoadConfig(bool reload) +{ +#ifdef PRESETS + EnableSetInfo = true; + SetNpcText = 601084; + + EnableSets = true; + MaxSets = 10; + SetCostModifier = 3.0f; + SetCopperCost = 0; + + if (MaxSets > MAX_OPTIONS) + MaxSets = MAX_OPTIONS; + + if (reload) // dont store presets for nothing + { + sWorld.ExecuteForAllSessions([this](WorldSession& worldSession) + { + if (Player* player = worldSession.GetPlayer()) + { + // skipping session check + UnloadPlayerSets(player->GetObjectGuid()); + if (GetEnableSets()) + LoadPlayerSets(player->GetObjectGuid()); + } + }); + } +#endif + + EnableTransmogInfo = true; + TransmogNpcText = 601083; + TransmogNpcSelectLookText = 601085; + TransmogNpcConfirmText = 601086; + TransmogNpcAlreadyText = 601087; + TransmogNpcAlreadyAltText = 601088; + + std::istringstream issAllowed(""); + std::istringstream issNotAllowed(""); + while (issAllowed.good()) + { + uint32 entry; + issAllowed >> entry; + if (issAllowed.fail()) + break; + Allowed.insert(entry); + } + while (issNotAllowed.good()) + { + uint32 entry; + issNotAllowed >> entry; + if (issNotAllowed.fail()) + break; + NotAllowed.insert(entry); + } + + ScaledCostModifier = 1.0f; + CopperCost = 0; + + RequireToken = false; + TokenEntry = 49426; + TokenAmount = 1; + + AllowPoor = true; + AllowCommon = true; + AllowUncommon = true; + AllowRare = true; + AllowEpic = true; + AllowLegendary = true; + AllowArtifact = true; + + AllowMixedArmorTypes = false; + AllowMixedWeaponTypes = false; + AllowFishingPoles = false; + + IgnoreReqRace = false; + IgnoreReqClass = false; + IgnoreReqSkill = false; + IgnoreReqSpell = false; + IgnoreReqLevel = false; + IgnoreReqEvent = false; + IgnoreReqStats = false; + + if (!sObjectMgr.GetItemPrototype(TokenEntry)) + { + //sLog->outError(LOG_FILTER_SERVER_LOADING, "Transmogrification.TokenEntry (%u) does not exist. Using default.", TokenEntry); + TokenEntry = 49426; + } +} + +void Transmogrification::DeleteFakeFromDB(const ObjectGuid itemLowGuid) +{ + const ObjectGuid itemGUID = itemLowGuid; + + if (dataMap.find(itemGUID) != dataMap.end()) + { + if (entryMap.find(dataMap[itemGUID]) != entryMap.end()) + entryMap[dataMap[itemGUID]].erase(itemGUID); + dataMap.erase(itemGUID); + } + + CharacterDatabase.PExecute("DELETE FROM custom_transmogrification WHERE GUID = %u", itemLowGuid.GetCounter()); +} + +void Transmogrification::CleanUp(Player* pPlayer) +{ + QueryResult* result = CharacterDatabase.PQuery("SELECT GUID, FakeEntry FROM custom_transmogrification WHERE Owner = %u", pPlayer->GetObjectGuid()); + if (result) + { + do + { + const auto itemGUID = ObjectGuid(HIGHGUID_ITEM, (*result)[0].GetUInt32()); + if (!pPlayer->GetItemByGuid(itemGUID)) + CharacterDatabase.PExecute("DELETE FROM custom_transmogrification WHERE GUID = %u", itemGUID.GetCounter()); + } while (result->NextRow()); + } +} + +void Transmogrification::BuildTransmogMap(Player* pPlayer) +{ + const ObjectGuid playerGUID = pPlayer->GetObjectGuid(); + entryMap.erase(playerGUID); + QueryResult* result = CharacterDatabase.PQuery("SELECT GUID, FakeEntry FROM custom_transmogrification WHERE Owner = %u", pPlayer->GetObjectGuid()); + if (result) + { + do + { + ObjectGuid itemGUID = ObjectGuid(HIGHGUID_ITEM, (*result)[0].GetUInt32()); + const uint32 fakeEntry = (*result)[1].GetUInt32(); + if (sObjectMgr.GetItemPrototype(fakeEntry)) + { + dataMap[itemGUID] = playerGUID; + entryMap[playerGUID][itemGUID] = fakeEntry; + } + } while (result->NextRow()); + } +} + +bool Transmogrification::Refresh(Player* pPlayer, Item* pEquippedItem) +{ + //if (!pPlayer || !pEquippedItem) + // return false; + + //if (pPlayer->AI()) + // return false; + // + bool ok = false; + //const uint8 slot = pEquippedItem->GetSlot(); + //// We need the equipped item's GUID to compared with the ones saved in the DB for each player + //const ObjectGuid EquippedItemGUID = pEquippedItem->GetObjectGuid(); + + //// If we find an equipped item that has a Transmogrifier then we need to reapply it + //if (const uint32 Transmogrifier = GetFakeEntry(EquippedItemGUID)) + //{ + // // We need to search for the Transmogrifier in all the bags and bank + // if (Item* pItemTransmogrifier = pPlayer->GetItemByEntry(Transmogrifier)) + // { + // const uint16 VisibleBase = PLAYER_VISIBLE_ITEM_1_0 + slot * MAX_VISIBLE_ITEM_OFFSET; + // if (pPlayer->GetUInt32Value(VisibleBase + 0) != pItemTransmogrifier->GetEntry()) + // { + // pPlayer->SetVisibleItemSlot(slot, pItemTransmogrifier); + // ok = true; + // } + // } + // else + // { + // // If we couldn't find the Transmogrifier then we need to delete it from the DB + // DeleteFakeEntry(pPlayer, slot, pEquippedItem); + // const uint16 VisibleBase = PLAYER_VISIBLE_ITEM_1_0 + slot * MAX_VISIBLE_ITEM_OFFSET; + // if (pPlayer->GetUInt32Value(VisibleBase + 0) != pEquippedItem->GetEntry()) + // pPlayer->SetVisibleItemSlot(slot, pEquippedItem); + // } + //} + + return ok; +} + +bool Transmogrification::RevertAll(Player* pPlayer) +{ + if (!pPlayer) + return false; + + bool ok = false; + //if (!pPlayer->RevertTransmogSlots.empty()) + //{ + // for (const auto slot : pPlayer->RevertTransmogSlots) + // { + // // If we find an equipped item then let's check if it's transmogrified + // if (Item* pEquippedItem = pPlayer->GetItemByPos(INVENTORY_SLOT_BAG_0, slot)) + // { + // const uint16 VisibleBase = PLAYER_VISIBLE_ITEM_1_0 + slot * MAX_VISIBLE_ITEM_OFFSET; + // if (pPlayer->GetUInt32Value(VisibleBase + 0) != pEquippedItem->GetEntry()) + // { + // pPlayer->SetVisibleItemSlot(slot, pEquippedItem); + // ok = true; + // } + // } + // } + // pPlayer->RevertTransmogSlots.clear(); + //} + //else + //{ + // // Check all the equipped items to see if they have a Transmogrifier + // for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; ++slot) + // { + // // If we find an equipped item then let's check if it's transmogrified + // if (Item* pEquippedItem = pPlayer->GetItemByPos(INVENTORY_SLOT_BAG_0, slot)) + // { + // const uint16 VisibleBase = PLAYER_VISIBLE_ITEM_1_0 + slot * MAX_VISIBLE_ITEM_OFFSET; + // if (pPlayer->GetUInt32Value(VisibleBase + 0) != pEquippedItem->GetEntry()) + // { + // pPlayer->SetVisibleItemSlot(slot, pEquippedItem); + // ok = true; + // } + // } + // } + //} + + return ok; +} + +bool Transmogrification::ApplyAll(Player* pPlayer) +{ + //if (!pPlayer) + // return false; + + bool ok = false; + //// Check all the equipped items to see if they have a Transmogrifier + //for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; ++slot) + //{ + // // If we find an equipped item then let's check if it's transmogrified + // if (Item* pEquippedItem = pPlayer->GetItemByPos(INVENTORY_SLOT_BAG_0, slot)) + // { + // // We need the equipped item's GUID to compare with the ones saved in the DB for each player + // const ObjectGuid EquippedItemGUID = pEquippedItem->GetObjectGuid(); + + // // If we find an equipped item that has a Transmogrifier then we need to reapply it + // if (const uint32 Transmogrifier = GetFakeEntry(EquippedItemGUID)) + // { + // // We need to search for the Transmogrifier in all the bags and bank + // if (Item* pItemTransmogrifier = pPlayer->GetItemByEntry(Transmogrifier)) + // { + // const uint16 VisibleBase = PLAYER_VISIBLE_ITEM_1_0 + slot * MAX_VISIBLE_ITEM_OFFSET; + // if (pPlayer->GetUInt32Value(VisibleBase + 0) != pItemTransmogrifier->GetEntry()) + // { + // pPlayer->SetVisibleItemSlot(slot, pItemTransmogrifier); + // ok = true; + // } + // } + // else + // { + // // If we couldn't find the Transmogrifier then we need to delete it from the DB + // DeleteFakeEntry(pPlayer, slot, pEquippedItem); + // const uint16 VisibleBase = PLAYER_VISIBLE_ITEM_1_0 + slot * MAX_VISIBLE_ITEM_OFFSET; + // if (pPlayer->GetUInt32Value(VisibleBase + 0) != pEquippedItem->GetEntry()) + // pPlayer->SetVisibleItemSlot(slot, pEquippedItem); + // } + // } + // } + //} + + return ok; +} + +void Transmogrification::OnLogout(Player* player) +{ + if (!player) + return; + + const ObjectGuid pGUID = player->GetObjectGuid(); + for (transmog2Data::const_iterator it = entryMap[pGUID].begin(); it != entryMap[pGUID].end(); ++it) + dataMap.erase(it->first); + entryMap.erase(pGUID); +} + +bool Transmogrification::GetEnableTransmogInfo() const +{ + return EnableTransmogInfo; +} +uint32 Transmogrification::GetTransmogNpcText() const +{ + return TransmogNpcText; +} +uint32 Transmogrification::GetNpcSelectLookText() const +{ + return TransmogNpcSelectLookText; +} +uint32 Transmogrification::GetSetNpcConfirmText() const +{ + return TransmogNpcConfirmText; +} +uint32 Transmogrification::GetSetNpcAlreadyText() const +{ + return TransmogNpcAlreadyText; +} +uint32 Transmogrification::GetSetNpcAlreadyAltText() const +{ + return TransmogNpcAlreadyAltText; +} +bool Transmogrification::GetEnableSetInfo() const +{ + return EnableSetInfo; +} +uint32 Transmogrification::GetSetNpcText() const +{ + return SetNpcText; +} +float Transmogrification::GetScaledCostModifier() const +{ + return ScaledCostModifier; +} +int32 Transmogrification::GetCopperCost() const +{ + return CopperCost; +} +bool Transmogrification::GetRequireToken() const +{ + return RequireToken; +} +uint32 Transmogrification::GetTokenEntry() const +{ + return TokenEntry; +} +uint32 Transmogrification::GetTokenAmount() const +{ + return TokenAmount; +} +bool Transmogrification::GetAllowMixedArmorTypes() const +{ + return AllowMixedArmorTypes; +}; +bool Transmogrification::GetAllowMixedWeaponTypes() const +{ + return AllowMixedWeaponTypes; +}; \ No newline at end of file diff --git a/src/game/AI/ScriptDevAI/scripts/custom/Transmogrification.h b/src/game/AI/ScriptDevAI/scripts/custom/Transmogrification.h new file mode 100644 index 00000000000..1b9f76c0f10 --- /dev/null +++ b/src/game/AI/ScriptDevAI/scripts/custom/Transmogrification.h @@ -0,0 +1,189 @@ +#ifndef DEF_TRANSMOGRIFICATION_H +#define DEF_TRANSMOGRIFICATION_H + +#include "World.h" + +#include + +#define PRESETS // comment this line to disable preset feature totally +#define MAX_OPTIONS 25 // do not alter + +class Item; +class Player; +class WorldSession; +struct ItemPrototype; + +enum TransmogAcoreStrings // Language.h might have same entries, appears when executing SQL, change if needed +{ + LANG_ERR_TRANSMOG_OK = 11100, // change this + LANG_ERR_TRANSMOG_INVALID_SLOT, + LANG_ERR_TRANSMOG_INVALID_SRC_ENTRY, + LANG_ERR_TRANSMOG_MISSING_SRC_ITEM, + LANG_ERR_TRANSMOG_MISSING_DEST_ITEM, + LANG_ERR_TRANSMOG_INVALID_ITEMS, + LANG_ERR_TRANSMOG_NOT_ENOUGH_MONEY, + LANG_ERR_TRANSMOG_NOT_ENOUGH_TOKENS, + LANG_ERR_TRANSMOG_SAME_DISPLAYID, + + LANG_ERR_UNTRANSMOG_OK, + LANG_ERR_UNTRANSMOG_SINGLE_OK, + LANG_ERR_UNTRANSMOG_NO_TRANSMOGS, + LANG_ERR_UNTRANSMOG_SINGLE_NO_TRANSMOGS, + + +#ifdef PRESETS + LANG_PRESET_ERR_INVALID_NAME, +#endif + LANG_CMD_TRANSMOG_SHOW, + LANG_CMD_TRANSMOG_HIDE, + LANG_CMD_TRANSMOG_ADD_UNSUITABLE, + LANG_CMD_TRANSMOG_ADD_FORBIDDEN, + LANG_MENU_NO_SUITABLE_ITEMS, + LANG_MENU_REMOVE, + LANG_MENU_BACK, + LANG_MENU_MAIN_MENU, + LANG_MENU_BEFORE, + LANG_MENU_AFTER, + LANG_MENU_COST_IS, + LANG_MENU_CONFIRM, + LANG_MENU_YOUR_ITEM, + LANG_MENU_TRANSMOG, + LANG_MENU_POSSIBLE_LOOK, + LANG_MENU_OPTIONS, +}; + +class Transmogrification +{ +public: + static Transmogrification* instance(); + + typedef std::unordered_map transmog2Data; + typedef std::unordered_map transmogMap; + transmogMap entryMap; // entryMap[pGUID][iGUID] = entry + + typedef std::unordered_map transmogData; + transmogData dataMap; // dataMap[iGUID] = pGUID + +#ifdef PRESETS + bool EnableSetInfo; + uint32 SetNpcText; + + typedef std::map slotMap; + typedef std::map presetData; + typedef std::unordered_map presetDataMap; + presetDataMap presetById; // presetById[pGUID][presetID][slot] = entry + typedef std::map presetIdMap; + typedef std::unordered_map presetNameMap; + presetNameMap presetByName; // presetByName[pGUID][presetID] = presetName + + void PresetTransmog(Player* player, Item* itemTransmogrified, uint32 fakeEntry, uint8 slot); + + bool EnableSets; + uint8 MaxSets; + float SetCostModifier; + int32 SetCopperCost; + + bool GetEnableSets() const; + uint8 GetMaxSets() const; + float GetSetCostModifier() const; + int32 GetSetCopperCost() const; + + void LoadPlayerSets(ObjectGuid pGUID); + void UnloadPlayerSets(ObjectGuid pGUID); +#endif + + bool EnableTransmogInfo; + uint32 TransmogNpcText; + uint32 TransmogNpcSelectLookText; + uint32 TransmogNpcConfirmText; + uint32 TransmogNpcAlreadyText; + uint32 TransmogNpcAlreadyAltText; + + // Use IsAllowed() and IsNotAllowed() + // these are thread unsafe, but assumed to be static data so it should be safe + std::set Allowed; + std::set NotAllowed; + + float ScaledCostModifier; + int32 CopperCost; + + bool RequireToken; + uint32 TokenEntry; + uint32 TokenAmount; + + bool AllowPoor; + bool AllowCommon; + bool AllowUncommon; + bool AllowRare; + bool AllowEpic; + bool AllowLegendary; + bool AllowArtifact; + bool AllowHeirloom; + + bool AllowMixedArmorTypes; + bool AllowMixedWeaponTypes; + bool AllowFishingPoles; + + bool IgnoreReqRace; + bool IgnoreReqClass; + bool IgnoreReqSkill; + bool IgnoreReqSpell; + bool IgnoreReqLevel; + bool IgnoreReqEvent; + bool IgnoreReqStats; + + bool IsAllowed(uint32 entry) const; + bool IsNotAllowed(uint32 entry) const; + bool IsAllowedQuality(uint32 quality) const; + bool IsRangedWeapon(uint32 Class, uint32 SubClass) const; + + void LoadConfig(bool reload); // thread unsafe + + std::string GetItemIcon(uint32 entry, uint32 width, uint32 height, int x, int y) const; + std::string GetSlotIcon(uint8 slot, uint32 width, uint32 height, int x, int y) const; + const char* GetSlotName(uint8 slot, WorldSession* session) const; + std::string GetItemLink(Item* item, WorldSession* session) const; + std::string GetItemLink(uint32 entry, WorldSession* session) const; + uint32 GetFakeEntry(ObjectGuid itemGUID) const; + void UpdateItem(Player* player, Item* item) const; + void DeleteFakeEntry(Player* player, uint8 slot, Item* itemTransmogrified); + void SetFakeEntry(Player* player, uint32 newEntry, Item* itemTransmogrified); + + TransmogAcoreStrings Transmogrify(Player* player, ObjectGuid itemTransmogrifier, uint8 slot, /*uint32 newEntry, */bool no_cost = false); + bool CanTransmogrifyItemWithItem(Player* player, ItemPrototype const* target, ItemPrototype const* source) const; + static bool SuitableForTransmogrification(Player* player, ItemPrototype const* proto); + // bool CanBeTransmogrified(Item const* item); + // bool CanTransmogrify(Item const* item); + uint32 GetSpecialPrice(ItemPrototype const* proto) const; + std::string FormatPrice(uint32 copper) const; + + void DeleteFakeFromDB(ObjectGuid itemLowGuid); + static void CleanUp(Player* pPlayer); + void BuildTransmogMap(Player* pPlayer); + bool Refresh(Player* pPlayer, Item* pEquippedItem); + static bool RevertAll(Player* pPlayer); + bool ApplyAll(Player* pPlayer); + void OnLogout(Player* player); + float GetScaledCostModifier() const; + int32 GetCopperCost() const; + + bool GetRequireToken() const; + uint32 GetTokenEntry() const; + uint32 GetTokenAmount() const; + + bool GetAllowMixedArmorTypes() const; + bool GetAllowMixedWeaponTypes() const; + + // Config + bool GetEnableTransmogInfo() const; + uint32 GetTransmogNpcText() const; + bool GetEnableSetInfo() const; + uint32 GetSetNpcText() const; + uint32 GetNpcSelectLookText() const; + uint32 GetSetNpcConfirmText() const; + uint32 GetSetNpcAlreadyText() const; + uint32 GetSetNpcAlreadyAltText() const; +}; +#define sTransmogrification Transmogrification::instance() + +#endif diff --git a/src/game/AI/ScriptDevAI/scripts/custom/transmog_scripts.cpp b/src/game/AI/ScriptDevAI/scripts/custom/transmog_scripts.cpp new file mode 100644 index 00000000000..c568d45f4cc --- /dev/null +++ b/src/game/AI/ScriptDevAI/scripts/custom/transmog_scripts.cpp @@ -0,0 +1,730 @@ +/* +5.0 +Transmogrification 3.3.5a - Gossip menu +By Rochet2 + +ScriptName for NPC: +Creature_Transmogrify + +TODO: +Make DB saving even better (Deleting)? What about coding? + +Fix the cost formula +-- Too much data handling, use default costs + +Are the qualities right? +Blizzard might have changed the quality requirements. +(TC handles it with stat checks) + +Cant transmogrify rediculus items // Foereaper: would be fun to stab people with a fish +-- Cant think of any good way to handle this easily, could rip flagged items from cata DB +*/ + +#include "Bag.h" +#include "Transmogrification.h" +#define sTransmogrifier sTransmogrification +#define GTS session->GetAcoreString // dropped translation support, no one using? + +void ShowTransmogItems(Player* player, Creature* creature, uint8 slot) // Only checks bags while can use an item from anywhere in inventory +{ + WorldSession* session = player->GetSession(); + Item* oldItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + if (oldItem) + { + uint32 limit = 0; + uint32 price = sTransmogrifier->GetSpecialPrice(oldItem->GetProto()); + price *= sTransmogrifier->GetScaledCostModifier(); + price += sTransmogrifier->GetCopperCost(); + std::ostringstream ss; + ss << std::endl; + if (sTransmogrifier->GetRequireToken()) + ss << std::endl << std::endl << sTransmogrifier->GetTokenAmount() << " x " << sTransmogrifier->GetItemLink(sTransmogrifier->GetTokenEntry(), session); + + for (uint8 i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i) + { + if (limit > MAX_OPTIONS) + break; + Item* newItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, i); + if (!newItem) + continue; + if (!sTransmogrifier->CanTransmogrifyItemWithItem(player, oldItem->GetProto(), newItem->GetProto())) + continue; + if (sTransmogrifier->GetFakeEntry(oldItem->GetObjectGuid()) == newItem->GetEntry()) + continue; + ++limit; + player->ADD_GOSSIP_ITEM(GOSSIP_ICON_MONEY_BAG, ss.str(), price, false); + } + + for (uint8 i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i) + { + const auto bag = dynamic_cast(player->GetItemByPos(INVENTORY_SLOT_BAG_0, i)); + if (!bag) + continue; + for (uint32 j = 0; j < bag->GetBagSize(); ++j) + { + if (limit > MAX_OPTIONS) + break; + Item* newItem = player->GetItemByPos(i, j); + if (!newItem) + continue; + if (!sTransmogrifier->CanTransmogrifyItemWithItem(player, oldItem->GetProto(), newItem->GetProto())) + continue; + if (sTransmogrifier->GetFakeEntry(oldItem->GetObjectGuid()) == newItem->GetEntry()) + continue; + ++limit; + player->ADD_GOSSIP_ITEM_EXTENDED(GOSSIP_ICON_MONEY_BAG, sTransmogrifier->GetItemIcon(newItem->GetEntry(), 30, 30, -18, 0) + sTransmogrifier->GetItemLink(newItem, session), slot, newItem->GetObjectGuid().GetCounter(), "Using this item for transmogrify will bind it to you and make it non-refundable and non-tradeable.\nDo you wish to continue?\n\n" + sTransmogrifier->GetItemIcon(newItem->GetEntry(), 40, 40, -15, -10) + sTransmogrifier->GetItemLink(newItem, session) + ss.str(), price, false); + } + } + } + + player->ADD_GOSSIP_ITEM_EXTENDED(GOSSIP_ICON_MONEY_BAG, "Remove transmogrification", EQUIPMENT_SLOT_END + 3, slot, "Remove transmogrification from the slot?", 0, false); + player->ADD_GOSSIP_ITEM(GOSSIP_ICON_MONEY_BAG, "Update menu", EQUIPMENT_SLOT_END, slot); + player->ADD_GOSSIP_ITEM(GOSSIP_ICON_MONEY_BAG, "Back...", EQUIPMENT_SLOT_END + 1, 0); + player->SEND_GOSSIP_MENU(DEFAULT_GOSSIP_MESSAGE, creature->GetObjectGuid()); +} + +bool OnGossipHello(Player* player, Creature* creature) +{ + WorldSession* session = player->GetSession(); + if (sTransmogrifier->GetEnableTransmogInfo()) + player->ADD_GOSSIP_ITEM(GOSSIP_ICON_MONEY_BAG, "How does transmogrification work?", EQUIPMENT_SLOT_END + 9, 0); + for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; ++slot) + { + if (const char* slotName = sTransmogrifier->GetSlotName(slot, session)) + { + Item* newItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, slot); + uint32 entry = newItem ? sTransmogrifier->GetFakeEntry(newItem->GetObjectGuid()) : 0; + std::string icon = entry ? sTransmogrifier->GetItemIcon(entry, 30, 30, -18, 0) : sTransmogrifier->GetSlotIcon(slot, 30, 30, -18, 0); + player->ADD_GOSSIP_ITEM(GOSSIP_ICON_MONEY_BAG, slotName, EQUIPMENT_SLOT_END, 0); + } + } +#ifdef PRESETS + //if (sTransmogrifier->GetEnableSets()) + // player->ADD_GOSSIP_ITEM(GOSSIP_ICON_MONEY_BAG, "Manage sets", EQUIPMENT_SLOT_END + 4, 0); +#endif + //player->ADD_GOSSIP_ITEM(GOSSIP_ICON_MONEY_BAG, "Remove all transmogrifications", EQUIPMENT_SLOT_END + 2, 0, "Remove transmogrifications from all equipped items?", 0, false); + player->ADD_GOSSIP_ITEM(GOSSIP_ICON_MONEY_BAG, "Update menu", EQUIPMENT_SLOT_END + 1, 0); + player->SEND_GOSSIP_MENU(DEFAULT_GOSSIP_MESSAGE, creature->GetObjectGuid()); + return true; +} + +bool OnGossipSelect(Player* player, Creature* creature, uint32 sender, uint32 action) +{ + player->GetPlayerMenu()->ClearMenus(); + WorldSession* session = player->GetSession(); + switch (sender) + { + case EQUIPMENT_SLOT_END: // Show items you can use + ShowTransmogItems(player, creature, action); + break; + case EQUIPMENT_SLOT_END + 1: // Main menu + OnGossipHello(player, creature); + break; + case EQUIPMENT_SLOT_END + 2: // Remove Transmogrifications + { + bool removed = false; + auto trans = CharacterDatabase.BeginTransaction(); + for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; ++slot) + { + if (Item* newItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, slot)) + { + if (!sTransmogrifier->GetFakeEntry(newItem->GetObjectGuid())) + continue; + sTransmogrifier->DeleteFakeEntry(player, slot, newItem); + removed = true; + } + } + if (removed) + { + session->SendAreaTriggerMessage("%s", LANG_ERR_UNTRANSMOG_OK); + CharacterDatabase.CommitTransaction(); + } + else + session->SendNotification(LANG_ERR_UNTRANSMOG_NO_TRANSMOGS); + OnGossipHello(player, creature); + } break; + case EQUIPMENT_SLOT_END + 3: // Remove Transmogrification from single item + { + if (Item* newItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, action)) + { + if (sTransmogrifier->GetFakeEntry(newItem->GetObjectGuid())) + { + sTransmogrifier->DeleteFakeEntry(player, action, newItem); + session->SendAreaTriggerMessage("%s", LANG_ERR_UNTRANSMOG_OK); + } + else + session->SendNotification(LANG_ERR_UNTRANSMOG_NO_TRANSMOGS); + } + OnGossipSelect(player, creature, EQUIPMENT_SLOT_END, action); + } break; +#ifdef PRESETS + case EQUIPMENT_SLOT_END + 4: // Presets menu + { + if (!sTransmogrifier->GetEnableSets()) + { + OnGossipHello(player, creature); + return true; + } + if (sTransmogrifier->GetEnableSetInfo()) + player->ADD_GOSSIP_ITEM(GOSSIP_ICON_MONEY_BAG, "How do sets work?", EQUIPMENT_SLOT_END + 10, 0); + for (Transmogrification::presetIdMap::const_iterator it = sTransmogrifier->presetByName[player->GetObjectGuid()].begin(); it != sTransmogrifier->presetByName[player->GetObjectGuid()].end(); ++it) + player->ADD_GOSSIP_ITEM(GOSSIP_ICON_MONEY_BAG, "zz?" /*it->second*/, EQUIPMENT_SLOT_END + 6, it->first); + + if (sTransmogrifier->presetByName[player->GetObjectGuid()].size() < sTransmogrifier->GetMaxSets()) + player->ADD_GOSSIP_ITEM(GOSSIP_ICON_MONEY_BAG, "Save set", EQUIPMENT_SLOT_END + 8, 0); + player->ADD_GOSSIP_ITEM(GOSSIP_ICON_MONEY_BAG, "Back...", EQUIPMENT_SLOT_END + 1, 0); + player->SEND_GOSSIP_MENU(DEFAULT_GOSSIP_MESSAGE, creature->GetObjectGuid()); + } break; + case EQUIPMENT_SLOT_END + 5: // Use preset + { + if (!sTransmogrifier->GetEnableSets()) + { + OnGossipHello(player, creature); + return true; + } + // action = presetID + for (Transmogrification::slotMap::const_iterator it = sTransmogrifier->presetById[player->GetObjectGuid()][action].begin(); it != sTransmogrifier->presetById[player->GetObjectGuid()][action].end(); ++it) + { + if (Item* item = player->GetItemByPos(INVENTORY_SLOT_BAG_0, it->first)) + sTransmogrifier->PresetTransmog(player, item, it->second, it->first); + } + OnGossipSelect(player, creature, EQUIPMENT_SLOT_END + 6, action); + } break; + case EQUIPMENT_SLOT_END + 6: // view preset + { + if (!sTransmogrifier->GetEnableSets()) + { + OnGossipHello(player, creature); + return true; + } + // action = presetID + for (Transmogrification::slotMap::const_iterator it = sTransmogrifier->presetById[player->GetObjectGuid()][action].begin(); it != sTransmogrifier->presetById[player->GetObjectGuid()][action].end(); ++it) + player->ADD_GOSSIP_ITEM(GOSSIP_ICON_MONEY_BAG, "zzz"/*sTransmogrifier->GetItemIcon(it->second, 30, 30, -18, 0) + sTransmogrifier->GetItemLink(it->second, session)*/, sender, action); + + player->ADD_GOSSIP_ITEM_EXTENDED(GOSSIP_ICON_MONEY_BAG, "Use this set", EQUIPMENT_SLOT_END + 5, action, "Using this set for transmogrify will bind transmogrified items to you and make them non-refundable and non-tradeable.\nDo you wish to continue?\n\n" + sTransmogrifier->presetByName[player->GetObjectGuid()][action], 0, false); + player->ADD_GOSSIP_ITEM_EXTENDED(GOSSIP_ICON_MONEY_BAG, "Delete set", EQUIPMENT_SLOT_END + 7, action, "Are you sure you want to delete " + sTransmogrifier->presetByName[player->GetObjectGuid()][action] + "?", 0, false); + player->ADD_GOSSIP_ITEM(GOSSIP_ICON_MONEY_BAG, "Back...", EQUIPMENT_SLOT_END + 4, 0); + player->SEND_GOSSIP_MENU(DEFAULT_GOSSIP_MESSAGE, creature->GetObjectGuid()); + } break; + case EQUIPMENT_SLOT_END + 7: // Delete preset + { + if (!sTransmogrifier->GetEnableSets()) + { + OnGossipHello(player, creature); + return true; + } + // action = presetID + CharacterDatabase.PExecute("DELETE FROM `custom_transmogrification_sets` WHERE Owner = %u AND PresetID = %u", player->GetObjectGuid(), action); + sTransmogrifier->presetById[player->GetObjectGuid()][action].clear(); + sTransmogrifier->presetById[player->GetObjectGuid()].erase(action); + sTransmogrifier->presetByName[player->GetObjectGuid()].erase(action); + + OnGossipSelect(player, creature, EQUIPMENT_SLOT_END + 4, 0); + } break; + case EQUIPMENT_SLOT_END + 8: // Save preset + { + if (!sTransmogrifier->GetEnableSets() || sTransmogrifier->presetByName[player->GetObjectGuid()].size() >= sTransmogrifier->GetMaxSets()) + { + OnGossipHello(player, creature); + return true; + } + uint32 cost = 0; + bool canSave = false; + for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; ++slot) + { + if (!sTransmogrifier->GetSlotName(slot, session)) + continue; + if (Item* newItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, slot)) + { + uint32 entry = sTransmogrifier->GetFakeEntry(newItem->GetObjectGuid()); + if (!entry) + continue; + const ItemPrototype* temp = sObjectMgr.GetItemPrototype(entry); + if (!temp) + continue; + if (!sTransmogrifier->SuitableForTransmogrification(player, temp)) // no need to check? + continue; + cost += sTransmogrifier->GetSpecialPrice(temp); + canSave = true; + player->ADD_GOSSIP_ITEM(GOSSIP_ICON_MONEY_BAG, "zzzu"/*sTransmogrifier->GetItemIcon(entry, 30, 30, -18, 0) + sTransmogrifier->GetItemLink(entry, session)*/, EQUIPMENT_SLOT_END + 8, 0); + } + } + if (canSave) + player->ADD_GOSSIP_ITEM_EXTENDED(GOSSIP_ICON_MONEY_BAG, "Save set", 0, 0, "Insert set name", cost * sTransmogrifier->GetSetCostModifier() + sTransmogrifier->GetSetCopperCost(), true); + player->ADD_GOSSIP_ITEM(GOSSIP_ICON_MONEY_BAG, "Update menu", sender, action); + player->ADD_GOSSIP_ITEM(GOSSIP_ICON_MONEY_BAG, "Back...", EQUIPMENT_SLOT_END + 4, 0); + player->SEND_GOSSIP_MENU(DEFAULT_GOSSIP_MESSAGE, creature->GetObjectGuid()); + } break; + case EQUIPMENT_SLOT_END + 10: // Set info + { + player->ADD_GOSSIP_ITEM(GOSSIP_ICON_MONEY_BAG, "Back...", EQUIPMENT_SLOT_END + 4, 0); + player->SEND_GOSSIP_MENU(sTransmogrifier->GetSetNpcText(), creature->GetObjectGuid()); + } break; +#endif + case EQUIPMENT_SLOT_END + 9: // Transmog info + { + player->ADD_GOSSIP_ITEM(GOSSIP_ICON_MONEY_BAG, "Back...", EQUIPMENT_SLOT_END + 1, 0); + player->SEND_GOSSIP_MENU(sTransmogrifier->GetTransmogNpcText(), creature->GetObjectGuid()); + } break; + default: // Transmogrify + { + if (!sender && !action) + { + OnGossipHello(player, creature); + return true; + } + // sender = slot, action = display + TransmogAcoreStrings res = sTransmogrifier->Transmogrify(player, ObjectGuid(HIGHGUID_ITEM, action), sender); + if (res == LANG_ERR_TRANSMOG_OK) + session->SendAreaTriggerMessage("%s", LANG_ERR_TRANSMOG_OK); + else + session->SendNotification(res); + // OnGossipSelect(player, creature, EQUIPMENT_SLOT_END, sender); + // ShowTransmogItems(player, creature, sender); + player->CLOSE_GOSSIP_MENU(); + } break; + } + return true; +} + +#ifdef PRESETS +bool OnGossipSelectCode(Player* player, Creature* creature, uint32 sender, uint32 action, const char* code) +{ + player->GetPlayerMenu()->ClearMenus(); + if (sender || action) + return true; // should never happen + if (!sTransmogrifier->GetEnableSets()) + { + OnGossipHello(player, creature); + return true; + } + std::string name(code); + if (name.find('"') != std::string::npos || name.find('\\') != std::string::npos) + player->GetSession()->SendNotification(LANG_PRESET_ERR_INVALID_NAME); + else + { + for (uint8 presetID = 0; presetID < sTransmogrifier->GetMaxSets(); ++presetID) // should never reach over max + { + if (sTransmogrifier->presetByName[player->GetObjectGuid()].find(presetID) != sTransmogrifier->presetByName[player->GetObjectGuid()].end()) + continue; // Just remember never to use presetByName[pGUID][presetID] when finding etc! + + int32 cost = 0; + std::map items; + for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; ++slot) + { + if (!sTransmogrifier->GetSlotName(slot, player->GetSession())) + continue; + if (Item* newItem = player->GetItemByPos(INVENTORY_SLOT_BAG_0, slot)) + { + uint32 entry = sTransmogrifier->GetFakeEntry(newItem->GetObjectGuid()); + if (!entry) + continue; + const ItemPrototype* temp = sObjectMgr.GetItemPrototype(entry); + if (!temp) + continue; + if (!sTransmogrifier->SuitableForTransmogrification(player, temp)) + continue; + cost += sTransmogrifier->GetSpecialPrice(temp); + items[slot] = entry; + } + } + if (items.empty()) + break; // no transmogrified items were found to be saved + cost *= sTransmogrifier->GetSetCostModifier(); + cost += sTransmogrifier->GetSetCopperCost(); + if (player->GetMoney() < cost) + { + player->GetSession()->SendNotification(LANG_ERR_TRANSMOG_NOT_ENOUGH_MONEY); + break; + } + + std::ostringstream ss; + for (std::map::iterator it = items.begin(); it != items.end(); ++it) + { + ss << uint32(it->first) << ' ' << it->second << ' '; + sTransmogrifier->presetById[player->GetObjectGuid()][presetID][it->first] = it->second; + } + sTransmogrifier->presetByName[player->GetObjectGuid()][presetID] = name; // Make sure code doesnt mess up SQL! + CharacterDatabase.PExecute("REPLACE INTO `custom_transmogrification_sets` (`Owner`, `PresetID`, `SetName`, `SetData`) VALUES (%u, %u, \"%s\", \"%s\")", player->GetObjectGuid(), uint32(presetID), name.c_str(), ss.str().c_str()); + if (cost) + player->ModifyMoney(-cost); + break; + } + } + //OnGossipSelect(player, creature, EQUIPMENT_SLOT_END+4, 0); + player->CLOSE_GOSSIP_MENU(); // Wait for SetMoney to get fixed, issue #10053 + return true; +} +#endif + +std::map > Items; +bool GossipHello_TransmogNPC(Player* pPlayer, Creature* pUnit) +{ + pPlayer->GetPlayerMenu()->ClearMenus(); + + if (sTransmogrifier->GetEnableTransmogInfo()) + pPlayer->ADD_GOSSIP_ITEM(GOSSIP_ICON_MONEY_BAG, "How does transmogrification work?", EQUIPMENT_SLOT_END + 9, 0); + + // Only show the menu option for items that you have equipped + for (uint8 Slot = EQUIPMENT_SLOT_START; Slot < EQUIPMENT_SLOT_END; Slot++) + { + // No point in checking for tabard, shirt, necklace, rings or trinkets + if (Slot == EQUIPMENT_SLOT_NECK || Slot == EQUIPMENT_SLOT_FINGER1 || Slot == EQUIPMENT_SLOT_FINGER2 || Slot == EQUIPMENT_SLOT_TRINKET1 || Slot == EQUIPMENT_SLOT_TRINKET2 || Slot == EQUIPMENT_SLOT_TABARD || Slot == EQUIPMENT_SLOT_BODY) + continue; + + // Only show the menu option for transmogrifiers that you have per slot + if (Item* pEquippedItem = pPlayer->GetItemByPos(INVENTORY_SLOT_BAG_0, Slot)) + { + // Only show the menu option if there is a transmogrifying option available + bool has_option = false; + + if (!has_option) + { + // Check in main inventory + for (uint8 i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; ++i) + { + if (Item* pTransmogrifier = pPlayer->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + { + if (!sTransmogrifier->CanTransmogrifyItemWithItem(pPlayer, pEquippedItem->GetProto(), pTransmogrifier->GetProto())) + continue; + + has_option = true; + break; + } + } + } + if (!has_option) + { + // Check in the other bags + for (uint8 i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; ++i) + { + if (const auto pBag = dynamic_cast(pPlayer->GetItemByPos(INVENTORY_SLOT_BAG_0, i))) + { + for (uint32 j = 0; j < pBag->GetBagSize(); ++j) + { + if (Item* pTransmogrifier = pBag->GetItemByPos(static_cast(j))) + { + if (!sTransmogrifier->CanTransmogrifyItemWithItem(pPlayer, pEquippedItem->GetProto(), pTransmogrifier->GetProto())) + continue; + + has_option = true; + break; + } + } + } + + // If we find a suitable mog in the first bag then there's no point in checking the rest + if (has_option) + break; + } + } + if (!has_option) + { + // Check in the bank's main inventory + for (uint8 i = BANK_SLOT_ITEM_START; i < BANK_SLOT_ITEM_END; ++i) + { + if (Item* pTransmogrifier = pPlayer->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + { + if (!sTransmogrifier->CanTransmogrifyItemWithItem(pPlayer, pEquippedItem->GetProto(), pTransmogrifier->GetProto())) + continue; + + has_option = true; + break; + } + } + } + if (!has_option) + { + // Check in the bank's other bags + for (uint8 i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; ++i) + { + if (const auto pBag = dynamic_cast(pPlayer->GetItemByPos(INVENTORY_SLOT_BAG_0, i))) + { + for (uint32 j = 0; j < pBag->GetBagSize(); ++j) + { + if (Item* pTransmogrifier = pBag->GetItemByPos(static_cast(j))) + { + if (!sTransmogrifier->CanTransmogrifyItemWithItem(pPlayer, pEquippedItem->GetProto(), pTransmogrifier->GetProto())) + continue; + + has_option = true; + break; + } + } + } + + if (has_option) + break; + } + } + + if (has_option) + { + std::string SlotName = sTransmogrifier->GetSlotName(Slot, pPlayer->GetSession()); + if (SlotName.length() > 0) + pPlayer->ADD_GOSSIP_ITEM(GOSSIP_ICON_TABARD, SlotName.c_str(), EQUIPMENT_SLOT_END, Slot); + } + } + } + + // Remove all transmogrifiers + pPlayer->ADD_GOSSIP_ITEM(GOSSIP_ICON_BATTLE, "Remove all transmogrifications.", EQUIPMENT_SLOT_END + 2, 0); + pPlayer->SEND_GOSSIP_MENU(DEFAULT_GOSSIP_MESSAGE, pUnit->GetObjectGuid()); + + return true; +} + +bool GossipSelect_TransmogNPC(Player* pPlayer, Creature* pUnit, const uint32 sender, const uint32 aSlot) +{ + pPlayer->GetPlayerMenu()->ClearMenus(); + // Display the available transmofrifiers inside the options + if (sender == EQUIPMENT_SLOT_END) + { + if (Item* pEquippedItem = pPlayer->GetItemByPos(INVENTORY_SLOT_BAG_0, static_cast(aSlot))) + { + const ObjectGuid EquippedItemGUID = pEquippedItem->GetObjectGuid(); + if (ItemPrototype const* equippedProto = pEquippedItem->GetProto()) + { + std::string yourItem; + const std::string equip_name = equippedProto->Name1; + yourItem = pPlayer->GetSession()->GetMangosString(LANG_MENU_YOUR_ITEM) + equip_name; + pPlayer->ADD_GOSSIP_ITEM(GOSSIP_ICON_VENDOR, yourItem.c_str(), sender, aSlot); + } + + const uint64 PlayerGUID = pPlayer->GetObjectGuid(); + Items[PlayerGUID].clear(); + for (uint8 i = INVENTORY_SLOT_ITEM_START; i < INVENTORY_SLOT_ITEM_END; i++) + if (Item* pItemTransmogrifier = pPlayer->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + if (sTransmogrifier->CanTransmogrifyItemWithItem(pPlayer, pEquippedItem->GetProto(), pItemTransmogrifier->GetProto()) && sTransmogrification->GetFakeEntry(pEquippedItem->GetObjectGuid()) != pItemTransmogrifier->GetEntry()) + if (Items[PlayerGUID].find(pItemTransmogrifier->GetProto()->DisplayInfoID) == Items[PlayerGUID].end()) + { + Items[PlayerGUID][pItemTransmogrifier->GetProto()->DisplayInfoID] = pItemTransmogrifier; + pPlayer->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_1, pItemTransmogrifier->GetProto()->Name1, aSlot + 100, pItemTransmogrifier->GetProto()->DisplayInfoID); + } + + for (uint8 i = INVENTORY_SLOT_BAG_START; i < INVENTORY_SLOT_BAG_END; i++) + if (const auto pBag = dynamic_cast(pPlayer->GetItemByPos(INVENTORY_SLOT_BAG_0, i))) + for (uint32 j = 0; j < pBag->GetBagSize(); j++) + if (Item* pItemTransmogrifier = pBag->GetItemByPos(static_cast(j))) + if (sTransmogrifier->CanTransmogrifyItemWithItem(pPlayer, pEquippedItem->GetProto(), pItemTransmogrifier->GetProto()) && sTransmogrification->GetFakeEntry(pEquippedItem->GetObjectGuid()) != pItemTransmogrifier->GetEntry()) + if (Items[PlayerGUID].find(pItemTransmogrifier->GetProto()->DisplayInfoID) == Items[PlayerGUID].end()) + { + Items[PlayerGUID][pItemTransmogrifier->GetProto()->DisplayInfoID] = pItemTransmogrifier; + pPlayer->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_1, pItemTransmogrifier->GetProto()->Name1, aSlot + 100, pItemTransmogrifier->GetProto()->DisplayInfoID); + } + + for (uint8 i = BANK_SLOT_ITEM_START; i < BANK_SLOT_ITEM_END; i++) + if (Item* pItemTransmogrifier = pPlayer->GetItemByPos(INVENTORY_SLOT_BAG_0, i)) + if (sTransmogrifier->CanTransmogrifyItemWithItem(pPlayer, pEquippedItem->GetProto(), pItemTransmogrifier->GetProto()) && sTransmogrification->GetFakeEntry(pEquippedItem->GetObjectGuid()) != pItemTransmogrifier->GetEntry()) + if (Items[PlayerGUID].find(pItemTransmogrifier->GetProto()->DisplayInfoID) == Items[PlayerGUID].end()) + { + Items[PlayerGUID][pItemTransmogrifier->GetProto()->DisplayInfoID] = pItemTransmogrifier; + pPlayer->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_1, pItemTransmogrifier->GetProto()->Name1, aSlot + 100, pItemTransmogrifier->GetProto()->DisplayInfoID); + } + + for (uint8 i = BANK_SLOT_BAG_START; i < BANK_SLOT_BAG_END; i++) + if (const auto pBag = dynamic_cast(pPlayer->GetItemByPos(INVENTORY_SLOT_BAG_0, i))) + for (uint32 j = 0; j < pBag->GetBagSize(); j++) + if (Item* pItemTransmogrifier = pBag->GetItemByPos(static_cast(j))) + if (sTransmogrifier->CanTransmogrifyItemWithItem(pPlayer, pEquippedItem->GetProto(), pItemTransmogrifier->GetProto()) && sTransmogrification->GetFakeEntry(pEquippedItem->GetObjectGuid()) != pItemTransmogrifier->GetEntry()) + if (Items[PlayerGUID].find(pItemTransmogrifier->GetProto()->DisplayInfoID) == Items[PlayerGUID].end()) + { + Items[PlayerGUID][pItemTransmogrifier->GetProto()->DisplayInfoID] = pItemTransmogrifier; + pPlayer->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_1, pItemTransmogrifier->GetProto()->Name1, aSlot + 100, pItemTransmogrifier->GetProto()->DisplayInfoID); + } + + // Remove the transmogrifier on the current item + bool hasTransmogOptions = !Items[PlayerGUID].empty(); + bool hasTransmog = false; + if (const uint32 FakeEntry = sTransmogrification->GetFakeEntry(EquippedItemGUID)) + { + hasTransmog = true; + if (ItemPrototype const* pItem = sObjectMgr.GetItemPrototype(FakeEntry)) + { + const std::string item_name = pItem->Name1; + const std::string illusion = pPlayer->GetSession()->GetMangosString(LANG_MENU_REMOVE) + item_name; + //pPlayer->ADD_GOSSIP_ITEM(GOSSIP_ICON_BATTLE, pPlayer->GetSession()->GetMangosString(LANG_MENU_OPTIONS), sender, aSlot); + pPlayer->ADD_GOSSIP_ITEM(GOSSIP_ICON_BATTLE, illusion.c_str(), EQUIPMENT_SLOT_END + 3, aSlot); + } + } + else + { + if (Items[PlayerGUID].empty()) + { + pPlayer->GetSession()->SendAreaTriggerMessage(pPlayer->GetSession()->GetMangosString(LANG_MENU_NO_SUITABLE_ITEMS)); + GossipHello_TransmogNPC(pPlayer, pUnit); + return true; + } + } + + pPlayer->ADD_GOSSIP_ITEM(GOSSIP_ICON_TALK, pPlayer->GetSession()->GetMangosString(LANG_MENU_BACK), EQUIPMENT_SLOT_END + 1, 0); + if (hasTransmog && !hasTransmogOptions) + pPlayer->SEND_GOSSIP_MENU(sTransmogrifier->GetSetNpcAlreadyText(), pUnit->GetObjectGuid()); + else if (hasTransmog && hasTransmogOptions) + pPlayer->SEND_GOSSIP_MENU(sTransmogrifier->GetSetNpcAlreadyAltText(), pUnit->GetObjectGuid()); + else + pPlayer->SEND_GOSSIP_MENU(sTransmogrifier->GetNpcSelectLookText(), pUnit->GetObjectGuid()); + } + else + GossipHello_TransmogNPC(pPlayer, pUnit); + } + // Go back to main menu + else if (sender == EQUIPMENT_SLOT_END + 1) + GossipHello_TransmogNPC(pPlayer, pUnit); + // Remove all transmogrifications + else if (sender == EQUIPMENT_SLOT_END + 2) + { + bool removed = false; + for (uint8 Slot = EQUIPMENT_SLOT_START; Slot < EQUIPMENT_SLOT_END; ++Slot) + { + if (Item* pEquippedItem = pPlayer->GetItemByPos(INVENTORY_SLOT_BAG_0, Slot)) + { + ObjectGuid EquippedItemGUID = pEquippedItem->GetObjectGuid(); + if (!sTransmogrification->GetFakeEntry(EquippedItemGUID)) + continue; + + sTransmogrification->DeleteFakeEntry(pPlayer, Slot, pEquippedItem); + removed = true; + } + } + if (removed) + { + pPlayer->GetSession()->SendAreaTriggerMessage("%s", pPlayer->GetSession()->GetMangosString(LANG_ERR_UNTRANSMOG_OK)); + + uint32 selfVisualSpell = 24085; + uint32 npcToPlayerSpell = 14867; + pUnit->CastSpell(pPlayer, npcToPlayerSpell, TRIGGERED_OLD_TRIGGERED); + pPlayer->CastSpell(pPlayer, selfVisualSpell, TRIGGERED_OLD_TRIGGERED); + } + else + pPlayer->GetSession()->SendNotification(LANG_ERR_UNTRANSMOG_NO_TRANSMOGS); + + GossipHello_TransmogNPC(pPlayer, pUnit); + } + // Remove transmogrification on a single equipped item + else if (sender == EQUIPMENT_SLOT_END + 3) + { + bool removed = false; + if (Item* pEquippedItem = pPlayer->GetItemByPos(INVENTORY_SLOT_BAG_0, static_cast(aSlot))) + { + const ObjectGuid EquippedItemGUID = pEquippedItem->GetObjectGuid(); + if (sTransmogrification->GetFakeEntry(EquippedItemGUID)) + { + sTransmogrification->DeleteFakeEntry(pPlayer, static_cast(aSlot), pEquippedItem); + removed = true; + } + } + const std::string SlotName = sTransmogrifier->GetSlotName(static_cast(aSlot), pPlayer->GetSession()); + if (removed) + { + pPlayer->GetSession()->SendAreaTriggerMessage("%s (%s)", pPlayer->GetSession()->GetMangosString(LANG_ERR_UNTRANSMOG_SINGLE_OK), SlotName.c_str()); + + uint32 selfVisualSpell = 24085; + uint32 npcToPlayerSpell = 14867; + pUnit->CastSpell(pPlayer, npcToPlayerSpell, TRIGGERED_OLD_TRIGGERED); + pPlayer->CastSpell(pPlayer, selfVisualSpell, TRIGGERED_OLD_TRIGGERED); + } + else + pPlayer->GetSession()->SendAreaTriggerMessage("%s (%s)", pPlayer->GetSession()->GetMangosString(LANG_ERR_UNTRANSMOG_SINGLE_NO_TRANSMOGS), SlotName.c_str()); + + GossipHello_TransmogNPC(pPlayer, pUnit); + } + // Update all transmogrifications + else if (sender == EQUIPMENT_SLOT_END + 4) + { + sTransmogrification->ApplyAll(pPlayer); + pPlayer->GetSession()->SendAreaTriggerMessage("Your appearance was refreshed"); + GossipHello_TransmogNPC(pPlayer, pUnit); + } + // Info about transmogrification + else if (sender == EQUIPMENT_SLOT_END + 9) + { + pPlayer->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, pPlayer->GetSession()->GetMangosString(LANG_MENU_BACK), EQUIPMENT_SLOT_END + 1, 0); + pPlayer->SEND_GOSSIP_MENU(sTransmogrifier->GetTransmogNpcText(), pUnit->GetObjectGuid()); + } + // Check cost + else if (sender >= 100) + { + // The cost is the vendor sell price of the transmogrifier or 1 Gold + const uint64 PlayerGUID = pPlayer->GetObjectGuid(); + if (Item* pItemTransmogrifier = Items[PlayerGUID][aSlot]) + { + if (ItemPrototype const* protoItemTransmogrifier = pItemTransmogrifier->GetProto()) + { + // Display transaction + Item* pItemTransmogrified = pPlayer->GetItemByPos(INVENTORY_SLOT_BAG_0, static_cast(sender - 100)); + if (!pItemTransmogrified) + { + GossipHello_TransmogNPC(pPlayer, pUnit); + return true; + } + if (ItemPrototype const* protoItemTransmogrified = pItemTransmogrified->GetProto()) + { + std::string nameItemTransmogrified = protoItemTransmogrified->Name1; + std::string before = pPlayer->GetSession()->GetMangosString(LANG_MENU_BEFORE) + nameItemTransmogrified; + pPlayer->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, before.c_str(), sender, aSlot); + } + + // get cost + uint32 cost = sTransmogrifier->GetSpecialPrice(pItemTransmogrified->GetProto()); + cost *= sTransmogrifier->GetScaledCostModifier(); + cost += sTransmogrifier->GetCopperCost(); + + std::string nameItemTransmogrifier = protoItemTransmogrifier->Name1; + std::string after = pPlayer->GetSession()->GetMangosString(LANG_MENU_AFTER) + nameItemTransmogrifier; + pPlayer->ADD_GOSSIP_ITEM(GOSSIP_ICON_CHAT, after.c_str(), sender, aSlot); + + // Display cost + std::string s_cost; + s_cost = pPlayer->GetSession()->GetMangosString(LANG_MENU_COST_IS) + sTransmogrifier->FormatPrice(cost); + pPlayer->ADD_GOSSIP_ITEM(GOSSIP_ICON_MONEY_BAG, s_cost.c_str(), sender, aSlot); + + // Only show confirmation button if player has enough money + if (pPlayer->GetMoney() > cost) + pPlayer->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_1, pPlayer->GetSession()->GetMangosString(LANG_MENU_CONFIRM), sender - 100, aSlot); + else + pPlayer->ADD_GOSSIP_ITEM(GOSSIP_ICON_INTERACT_1, pPlayer->GetSession()->GetMangosString(LANG_ERR_TRANSMOG_NOT_ENOUGH_MONEY), sender, aSlot); + + pPlayer->ADD_GOSSIP_ITEM(GOSSIP_ICON_TALK, pPlayer->GetSession()->GetMangosString(LANG_MENU_BACK), EQUIPMENT_SLOT_END, pItemTransmogrified->GetSlot()); + pPlayer->ADD_GOSSIP_ITEM(GOSSIP_ICON_TALK, pPlayer->GetSession()->GetMangosString(LANG_MENU_MAIN_MENU), EQUIPMENT_SLOT_END + 1, 0); + pPlayer->SEND_GOSSIP_MENU(sTransmogrifier->GetSetNpcConfirmText(), pUnit->GetObjectGuid()); + } + } + } + else + { + const uint64 PlayerGUID = pPlayer->GetObjectGuid(); + if (!Items[PlayerGUID][aSlot]) + return false; + + // sender = slot, action = display + TransmogAcoreStrings res = sTransmogrifier->Transmogrify(pPlayer, Items[PlayerGUID][aSlot]->GetObjectGuid(), static_cast(sender)); + if (res == LANG_ERR_TRANSMOG_OK) + { + uint32 selfVisualSpell = 24085; + uint32 npcToPlayerSpell = 14867; + pUnit->CastSpell(pPlayer, npcToPlayerSpell, TRIGGERED_OLD_TRIGGERED); + pPlayer->CastSpell(pPlayer, selfVisualSpell, TRIGGERED_OLD_TRIGGERED); + //pPlayer->GetSession()->SendAreaTriggerMessage("Successfully casted an illusion on your %s", sTransmogrifier->GetSlotName(static_cast(sender), pPlayer->GetSession())); + pPlayer->GetSession()->SendAreaTriggerMessage("%s (%s)", pPlayer->GetSession()->GetMangosString(LANG_ERR_TRANSMOG_OK), sTransmogrifier->GetSlotName(static_cast(sender), pPlayer->GetSession())); + GossipHello_TransmogNPC(pPlayer, pUnit); + } + else + { + pPlayer->GetSession()->SendNotification(res); + GossipHello_TransmogNPC(pPlayer, pUnit); + } + Items[PlayerGUID].clear(); + GossipHello_TransmogNPC(pPlayer, pUnit); + } + return true; +} + +void AddSC_Transmog() +{ + auto newscript = new Script; + newscript->Name = "npc_transmogrifier"; + newscript->pGossipHello = &GossipHello_TransmogNPC; + newscript->pGossipSelect = &GossipSelect_TransmogNPC; + newscript->RegisterSelf(false); +} \ No newline at end of file diff --git a/src/game/AI/ScriptDevAI/scripts/northrend/world_northrend.cpp b/src/game/AI/ScriptDevAI/scripts/northrend/world_northrend.cpp index aaf92a4386d..4ba5799a112 100644 --- a/src/game/AI/ScriptDevAI/scripts/northrend/world_northrend.cpp +++ b/src/game/AI/ScriptDevAI/scripts/northrend/world_northrend.cpp @@ -51,4 +51,4 @@ void AddSC_world_northrend() pNewScript->Name = "world_map_northrend"; pNewScript->GetInstanceData = &GetNewInstanceScript; pNewScript->RegisterSelf(); -} \ No newline at end of file +} diff --git a/src/game/AI/ScriptDevAI/system/ScriptLoader.cpp b/src/game/AI/ScriptDevAI/system/ScriptLoader.cpp index 1583bd3e084..449d101bb22 100644 --- a/src/game/AI/ScriptDevAI/system/ScriptLoader.cpp +++ b/src/game/AI/ScriptDevAI/system/ScriptLoader.cpp @@ -8,6 +8,7 @@ extern void AddSC_battleground(); // custom +extern void AddSC_Transmog(); // examples extern void AddSC_example_creature(); @@ -509,6 +510,7 @@ void AddScripts() AddSC_battleground(); // custom + AddSC_Transmog(); // examples AddSC_example_creature(); diff --git a/src/game/Achievements/AchievementMgr.cpp b/src/game/Achievements/AchievementMgr.cpp index 60802741ffb..0bbf81dd5ad 100644 --- a/src/game/Achievements/AchievementMgr.cpp +++ b/src/game/Achievements/AchievementMgr.cpp @@ -2310,6 +2310,7 @@ void AchievementMgr::CompletedAchievement(AchievementEntry const* achievement) return; SendAchievementEarned(achievement); + GetPlayer()->CreateWowarmoryFeed(1, achievement->ID, 0, 0); CompletedAchievementData& ca = m_completedAchievements[achievement->ID]; ca.date = time(nullptr); ca.changed = true; diff --git a/src/game/BattleGround/BattleGround.cpp b/src/game/BattleGround/BattleGround.cpp index cf1d4da5c4b..8252012e7c6 100644 --- a/src/game/BattleGround/BattleGround.cpp +++ b/src/game/BattleGround/BattleGround.cpp @@ -300,7 +300,10 @@ BattleGround::~BattleGround() // unload map // map can be null at bg destruction if (m_bgMap) + { m_bgMap->SetUnload(); + m_bgMap->SetBG(nullptr); + } // remove from bg free slot queue this->RemoveFromBgFreeSlotQueue(); @@ -1911,6 +1914,21 @@ void BattleGround::OnObjectDBLoad(GameObject* obj) } } +/** + Function returns a gameobject guid from event map + + @param event1 + @param event2 +*/ +uint32 BattleGround::GetSingleGameObjectGuid(uint8 event1, uint8 event2) +{ + auto itr = m_eventObjects[MAKE_PAIR32(event1, event2)].gameobjects.begin(); + if (itr != m_eventObjects[MAKE_PAIR32(event1, event2)].gameobjects.end()) + return *itr; + + return ObjectGuid(); +} + /** Function that checks if event handles doors diff --git a/src/game/BattleGround/BattleGround.h b/src/game/BattleGround/BattleGround.h index 0bee52fe3b5..6181d2dd7e6 100644 --- a/src/game/BattleGround/BattleGround.h +++ b/src/game/BattleGround/BattleGround.h @@ -612,6 +612,9 @@ class BattleGround // Get creature guid from event uint32 GetSingleCreatureGuid(uint8 /*event1*/, uint8 /*event2*/); + // Get gameobject guid from event + uint32 GetSingleGameObjectGuid(uint8 /*event1*/, uint8 /*event2*/); + // Handle door events void OpenDoorEvent(uint8 /*event1*/, uint8 event2 = 0); bool IsDoorEvent(uint8 /*event1*/, uint8 /*event2*/) const; diff --git a/src/game/BattleGround/BattleGroundAB.h b/src/game/BattleGround/BattleGroundAB.h index 7d65e50b8e8..b000379b3a8 100644 --- a/src/game/BattleGround/BattleGroundAB.h +++ b/src/game/BattleGround/BattleGroundAB.h @@ -152,8 +152,8 @@ enum ABGraveyards AB_GRAVEYARD_ALLIANCE = 898, AB_GRAVEYARD_HORDE = 899, - AB_GRAVEYARD_ALLIANCE_BASE = 890, - AB_GRAVEYARD_HORDE_BASE = 889, + AB_GRAVEYARD_ALLIANCE_BASE = 890, + AB_GRAVEYARD_HORDE_BASE = 889, BG_AB_ZONE_MAIN = 3358, }; diff --git a/src/game/BattleGround/BattleGroundAV.cpp b/src/game/BattleGround/BattleGroundAV.cpp index c197b1a8aa7..f5e47252ae1 100644 --- a/src/game/BattleGround/BattleGroundAV.cpp +++ b/src/game/BattleGround/BattleGroundAV.cpp @@ -176,7 +176,7 @@ void BattleGroundAV::HandleQuestComplete(uint32 questid, Player* player) if (m_teamQuestStatus[teamIdx][0] == 500 || m_teamQuestStatus[teamIdx][0] == 1000 || m_teamQuestStatus[teamIdx][0] == 1500) // 25,50,75 turn ins { DEBUG_LOG("BattleGroundAV: Quest %i completed starting with unit upgrading..", questid); - for (uint8 i = BG_AV_NODE_GY_DUN_BALDAR; i < BG_AV_NODE_GY_FROSTWOLF_KEEP; ++i) + for (uint8 i = BG_AV_NODES_FIRSTAID_STATION; i < BG_AV_NODES_FROSTWOLF_HUT; ++i) if (m_nodes[i].owner == teamIdx && m_nodes[i].state == POINT_CONTROLLED) PopulateNode(AVNodeIds(i)); } @@ -519,8 +519,8 @@ void BattleGroundAV::ProcessPlayerDestroyedPoint(AVNodeIds node) PvpTeamIndex otherTeamIdx = GetOtherTeamIndex(ownerTeamIdx); Team ownerTeam = GetTeamIdByTeamIndex(ownerTeamIdx); - bool isTower = m_nodes[node].graveyardId == 0; - uint32 newState = 0; + bool isTower = !m_nodes[node].graveyardId; + uint32 newState = ownerTeam == ALLIANCE ? avNodeWorldStates[node].worldStateAlly : avNodeWorldStates[node].worldStateHorde; // despawn banner DestroyNode(node); @@ -529,7 +529,7 @@ void BattleGroundAV::ProcessPlayerDestroyedPoint(AVNodeIds node) if (isTower) { - uint8 tmp = node - BG_AV_NODE_DUNBALDAR_SOUTH; + uint8 tmp = node - BG_AV_NODES_DUNBALDAR_SOUTH; // despawn marshal (one of those guys protecting the boss) SpawnEvent(BG_AV_MARSHAL_A_SOUTH + tmp, 0, false); @@ -620,7 +620,7 @@ bool BattleGroundAV::CanPlayerDoMineQuest(uint32 goEntry, Team team) void BattleGroundAV::PopulateNode(AVNodeIds node) { PvpTeamIndex teamIdx = m_nodes[node].owner; - bool isTower = m_nodes[node].graveyardId; + bool isTower = !m_nodes[node].graveyardId; if (!isTower && teamIdx != TEAM_INDEX_NEUTRAL) { @@ -680,7 +680,7 @@ void BattleGroundAV::ProcessPlayerDefendsPoint(Player* player, AVNodeIds node) return; // ToDo: improve this part; The middle graveyard should be handled directly as an assault - if (m_nodes[node].totalOwner == TEAM_INDEX_NEUTRAL && node == BG_AV_NODE_GY_SNOWFALL) // initial snowfall capture + if (m_nodes[node].totalOwner == TEAM_INDEX_NEUTRAL && node == BG_AV_NODES_SNOWFALL_GRAVE) // initial snowfall capture { ProcessPlayerAssaultsPoint(player, node); return; @@ -694,7 +694,7 @@ void BattleGroundAV::ProcessPlayerDefendsPoint(Player* player, AVNodeIds node) return; } - bool isTower = m_nodes[node].graveyardId; + bool isTower = !m_nodes[node].graveyardId; uint32 newState = teamIdx == TEAM_INDEX_ALLIANCE ? avNodeWorldStates[node].worldStateAlly : avNodeWorldStates[node].worldStateHorde; @@ -731,7 +731,7 @@ void BattleGroundAV::ProcessPlayerAssaultsPoint(Player* player, AVNodeIds node) if (m_nodes[node].owner == teamIdx || teamIdx == m_nodes[node].totalOwner) return; - bool isTower = m_nodes[node].graveyardId; + bool isTower = !m_nodes[node].graveyardId; uint32 newState = teamIdx == TEAM_INDEX_ALLIANCE ? avNodeWorldStates[node].worldStateAllyGrey : avNodeWorldStates[node].worldStateHordeGrey; uint32 scoreType = isTower ? SCORE_TOWERS_ASSAULTED : SCORE_GRAVEYARDS_ASSAULTED; @@ -786,21 +786,21 @@ int32 BattleGroundAV::GetNodeMessageId(AVNodeIds node) const { switch (node) { - case BG_AV_NODE_GY_DUN_BALDAR: return LANG_BG_AV_NODE_GRAVE_STORM_AID; - case BG_AV_NODE_GY_STORMPIKE: return LANG_BG_AV_NODE_GRAVE_STORMPIKE; - case BG_AV_NODE_GY_STONEHEARTH: return LANG_BG_AV_NODE_GRAVE_STONE; - case BG_AV_NODE_GY_SNOWFALL: return LANG_BG_AV_NODE_GRAVE_SNOW; - case BG_AV_NODE_GY_ICEBLOOD: return LANG_BG_AV_NODE_GRAVE_ICE; - case BG_AV_NODE_GY_FROSTWOLF: return LANG_BG_AV_NODE_GRAVE_FROST; - case BG_AV_NODE_GY_FROSTWOLF_KEEP: return LANG_BG_AV_NODE_GRAVE_FROST_HUT; - case BG_AV_NODE_DUNBALDAR_SOUTH: return LANG_BG_AV_NODE_TOWER_DUN_S; - case BG_AV_NODE_DUNBALDAR_NORTH: return LANG_BG_AV_NODE_TOWER_DUN_N; - case BG_AV_NODE_ICEWING_BUNKER: return LANG_BG_AV_NODE_TOWER_ICEWING; - case BG_AV_NODE_STONEHEART_BUNKER: return LANG_BG_AV_NODE_TOWER_STONE; - case BG_AV_NODE_ICEBLOOD_TOWER: return LANG_BG_AV_NODE_TOWER_ICE; - case BG_AV_NODE_TOWER_POINT: return LANG_BG_AV_NODE_TOWER_POINT; - case BG_AV_NODE_FROSTWOLF_EAST: return LANG_BG_AV_NODE_TOWER_FROST_E; - case BG_AV_NODE_FROSTWOLF_WEST: return LANG_BG_AV_NODE_TOWER_FROST_W; + case BG_AV_NODES_FIRSTAID_STATION: return LANG_BG_AV_NODE_GRAVE_STORM_AID; + case BG_AV_NODES_DUNBALDAR_SOUTH: return LANG_BG_AV_NODE_TOWER_DUN_S; + case BG_AV_NODES_DUNBALDAR_NORTH: return LANG_BG_AV_NODE_TOWER_DUN_N; + case BG_AV_NODES_STORMPIKE_GRAVE: return LANG_BG_AV_NODE_GRAVE_STORMPIKE; + case BG_AV_NODES_ICEWING_BUNKER: return LANG_BG_AV_NODE_TOWER_ICEWING; + case BG_AV_NODES_STONEHEART_GRAVE: return LANG_BG_AV_NODE_GRAVE_STONE; + case BG_AV_NODES_STONEHEART_BUNKER: return LANG_BG_AV_NODE_TOWER_STONE; + case BG_AV_NODES_SNOWFALL_GRAVE: return LANG_BG_AV_NODE_GRAVE_SNOW; + case BG_AV_NODES_ICEBLOOD_TOWER: return LANG_BG_AV_NODE_TOWER_ICE; + case BG_AV_NODES_ICEBLOOD_GRAVE: return LANG_BG_AV_NODE_GRAVE_ICE; + case BG_AV_NODES_TOWER_POINT: return LANG_BG_AV_NODE_TOWER_POINT; + case BG_AV_NODES_FROSTWOLF_GRAVE: return LANG_BG_AV_NODE_GRAVE_FROST; + case BG_AV_NODES_FROSTWOLF_ETOWER: return LANG_BG_AV_NODE_TOWER_FROST_E; + case BG_AV_NODES_FROSTWOLF_WTOWER: return LANG_BG_AV_NODE_TOWER_FROST_W; + case BG_AV_NODES_FROSTWOLF_HUT: return LANG_BG_AV_NODE_GRAVE_FROST_HUT; } return 0; @@ -921,6 +921,7 @@ void BattleGroundAV::Reset() m_enemyTowersDestroyed[i] = 0; m_homeTowersControlled[i] = BG_AV_MAX_TOWERS_PER_TEAM; + m_activeEvents[BG_AV_NODE_CAPTAIN_DEAD_A + i] = BG_EVENT_NONE; } // initialize mine variables and active events @@ -940,8 +941,8 @@ void BattleGroundAV::Reset() m_activeEvents[BG_AV_BOSS_A] = 0; m_activeEvents[BG_AV_BOSS_H] = 0; - for (uint8 i = 0; i < BG_AV_MAX_NODES; ++i) // towers - m_activeEvents[BG_AV_MARSHAL_A_SOUTH + i - BG_AV_NODE_DUNBALDAR_SOUTH] = 0; + for (uint8 i = BG_AV_NODES_DUNBALDAR_SOUTH; i < BG_AV_MAX_NODES; ++i) // towers + m_activeEvents[BG_AV_MARSHAL_A_SOUTH + i - BG_AV_NODES_DUNBALDAR_SOUTH] = 0; // initialize all nodes for (uint8 i = 0; i < BG_AV_MAX_NODES; ++i) diff --git a/src/game/BattleGround/BattleGroundAV.h b/src/game/BattleGround/BattleGroundAV.h index 5d7f5291122..5f9de572e02 100644 --- a/src/game/BattleGround/BattleGroundAV.h +++ b/src/game/BattleGround/BattleGroundAV.h @@ -240,25 +240,25 @@ enum AVKillCredits enum AVNodeIds { - BG_AV_NODE_GY_DUN_BALDAR = 0, - BG_AV_NODE_GY_STORMPIKE = 1, - BG_AV_NODE_GY_STONEHEARTH = 2, + BG_AV_NODES_FIRSTAID_STATION = 0, + BG_AV_NODES_STORMPIKE_GRAVE = 1, + BG_AV_NODES_STONEHEART_GRAVE = 2, - BG_AV_NODE_GY_SNOWFALL = 3, + BG_AV_NODES_SNOWFALL_GRAVE = 3, - BG_AV_NODE_GY_ICEBLOOD = 4, - BG_AV_NODE_GY_FROSTWOLF = 5, - BG_AV_NODE_GY_FROSTWOLF_KEEP = 6, + BG_AV_NODES_ICEBLOOD_GRAVE = 4, + BG_AV_NODES_FROSTWOLF_GRAVE = 5, + BG_AV_NODES_FROSTWOLF_HUT = 6, - BG_AV_NODE_DUNBALDAR_SOUTH = 7, - BG_AV_NODE_DUNBALDAR_NORTH = 8, - BG_AV_NODE_ICEWING_BUNKER = 9, - BG_AV_NODE_STONEHEART_BUNKER = 10, + BG_AV_NODES_DUNBALDAR_SOUTH = 7, + BG_AV_NODES_DUNBALDAR_NORTH = 8, + BG_AV_NODES_ICEWING_BUNKER = 9, + BG_AV_NODES_STONEHEART_BUNKER = 10, - BG_AV_NODE_ICEBLOOD_TOWER = 11, - BG_AV_NODE_TOWER_POINT = 12, - BG_AV_NODE_FROSTWOLF_EAST = 13, - BG_AV_NODE_FROSTWOLF_WEST = 14, + BG_AV_NODES_ICEBLOOD_TOWER = 11, + BG_AV_NODES_TOWER_POINT = 12, + BG_AV_NODES_FROSTWOLF_ETOWER = 13, + BG_AV_NODES_FROSTWOLF_WTOWER = 14, }; // for nodeevents we will use event1=node @@ -350,10 +350,10 @@ enum AVWorldStates BG_AV_STATE_SCORE_SHOW_A = 3134, BG_AV_STATE_GY_SNOWFALL_N = 1966, - BG_AV_STATE_GY_SNOWFALL_A = 1343, - BG_AV_STATE_GY_SNOWFALL_A_GREY = 1341, - BG_AV_STATE_GY_SNOWFALL_H = 1344, - BG_AV_STATE_GY_SNOWFALL_H_GREY = 1342, + BG_AV_STATE_GY_SNOWFALL_A = 1341, + BG_AV_STATE_GY_SNOWFALL_A_GREY = 1343, + BG_AV_STATE_GY_SNOWFALL_H = 1342, + BG_AV_STATE_GY_SNOWFALL_H_GREY = 1344, // mine world states BG_AV_STATE_IRONDEEP_MINE_A = 1358, diff --git a/src/game/BattleGround/BattleGroundEY.cpp b/src/game/BattleGround/BattleGroundEY.cpp index cad58c3e237..604af6d3fc1 100644 --- a/src/game/BattleGround/BattleGroundEY.cpp +++ b/src/game/BattleGround/BattleGroundEY.cpp @@ -63,7 +63,7 @@ void BattleGroundEY::Update(uint32 diff) if (m_flagState == EY_FLAG_STATE_WAIT_RESPAWN) RespawnFlagAtCenter(true); - else + else if (m_flagState == EY_FLAG_STATE_ON_GROUND) RespawnDroppedFlag(); } else @@ -296,7 +296,8 @@ bool BattleGroundEY::HandleEvent(uint32 eventId, Object* source, Object* target) { if (eyTowerEvents[i][j].eventEntry == eventId) { - ProcessCaptureEvent(go, i, eyTowerEvents[i][j].team, eyTowerEvents[i][j].worldState, eyTowerEvents[i][j].message); + if (eyTowerEvents[i][j].team != m_towerOwner[i]) + ProcessCaptureEvent(go, i, eyTowerEvents[i][j].team, eyTowerEvents[i][j].worldState, eyTowerEvents[i][j].message); // no need to iterate other events or towers return false; @@ -507,6 +508,9 @@ void BattleGroundEY::RespawnFlagAtCenter(bool wasCaptured) // Method that respawns dropped flag; called if nobody picks the dropped flag after 10 seconds void BattleGroundEY::RespawnDroppedFlag() { + if (m_flagState != EY_FLAG_STATE_ON_GROUND) + return; + RespawnFlagAtCenter(false); if (GameObject* flag = GetBgMap()->GetGameObject(m_droppedFlagGuid)) diff --git a/src/game/BattleGround/BattleGroundHandler.cpp b/src/game/BattleGround/BattleGroundHandler.cpp index abe8a8c3e97..75df644b0a8 100644 --- a/src/game/BattleGround/BattleGroundHandler.cpp +++ b/src/game/BattleGround/BattleGroundHandler.cpp @@ -448,6 +448,14 @@ void WorldSession::HandleBattlefieldPortOpcode(WorldPacket& recv_data) return; } + // if player is not removed from queue by the time BG has ended + if (action == 1 && bg->GetStatus() == STATUS_WAIT_LEAVE) + { + sLog.outError("Battleground: Player %s (%u) tried to enter already finished battleground (%u)! Do not port him to battleground!", + _player->GetName(), _player->GetGUIDLow(), bg->GetTypeId()); + action = 0; + } + // expected bracket entry PvPDifficultyEntry const* bracketEntry = GetBattlegroundBracketByLevel(bg->GetMapId(), _player->GetLevel()); if (!bracketEntry) diff --git a/src/game/BattleGround/BattleGroundIC.cpp b/src/game/BattleGround/BattleGroundIC.cpp index 556770addb7..801fe26ec78 100644 --- a/src/game/BattleGround/BattleGroundIC.cpp +++ b/src/game/BattleGround/BattleGroundIC.cpp @@ -553,6 +553,9 @@ void BattleGroundIC::HandleCreatureCreate(Creature* creature) void BattleGroundIC::HandleGameObjectCreate(GameObject* go) { + // add all objects in GO store + m_goEntryGuidStore[go->GetEntry()] = go->GetObjectGuid(); + switch (go->GetEntry()) { case BG_IC_GO_SEAFORIUM_BOMBS_A: diff --git a/src/game/BattleGround/BattleGroundMgr.cpp b/src/game/BattleGround/BattleGroundMgr.cpp index 5394e5d7eef..c8a2990ed66 100644 --- a/src/game/BattleGround/BattleGroundMgr.cpp +++ b/src/game/BattleGround/BattleGroundMgr.cpp @@ -1178,7 +1178,12 @@ bool BgQueueInviteEvent::Execute(uint64 /*e_time*/, uint32 /*p_time*/) if (!plr) return; - BattleGround* bg = sBattleGroundMgr.GetBattleGround(m_bgInstanceGuid, m_bgTypeId); + BattleGround* bg = nullptr; + if (m_bgTypeId < MAX_BATTLEGROUND_TYPE_ID) + bg = sBattleGroundMgr.GetBattleGround(m_bgInstanceGuid, m_bgTypeId); + else + sLog.outError("BgQueueInviteEvent: wrong event data, bgTypeId = %u, instanceGuid = %u", m_bgTypeId, m_bgInstanceGuid); + // if battleground ended and its instance deleted - do nothing if (!bg) return; @@ -1225,7 +1230,12 @@ bool BgQueueRemoveEvent::Execute(uint64 /*e_time*/, uint32 /*p_time*/) // player logged off (we should do nothing, he is correctly removed from queue in another procedure) return; - BattleGround* bg = sBattleGroundMgr.GetBattleGround(m_bgInstanceGuid, m_bgTypeId); + BattleGround* bg = nullptr; + if (m_bgTypeId < MAX_BATTLEGROUND_TYPE_ID) + bg = sBattleGroundMgr.GetBattleGround(m_bgInstanceGuid, m_bgTypeId); + else + sLog.outError("BgQueueRemoveEvent: wrong event data, bgTypeId = %u, instanceGuid = %u", m_bgTypeId, m_bgInstanceGuid); + // battleground can be deleted already when we are removing queue info // bg pointer can be nullptr! so use it carefully! @@ -1929,6 +1939,13 @@ void BattleGroundMgr::CreateInitialBattleGrounds() bool isArena = (bl->type == TYPE_ARENA); uint32 minPlayersPerTeam = fields[1].GetUInt32(); uint32 maxPlayersPerTeam = fields[2].GetUInt32(); + + uint32 minLevel = bl->minLevel; + uint32 maxLevel = bl->maxLevel; + + // Random BG should be for 80 level + if (bgTypeId == BATTLEGROUND_RB) + minLevel = maxLevel = 80; // check values from DB if (maxPlayersPerTeam == 0) diff --git a/src/game/BattleGround/BattleGroundSA.cpp b/src/game/BattleGround/BattleGroundSA.cpp index fc88837503b..74de822b329 100644 --- a/src/game/BattleGround/BattleGroundSA.cpp +++ b/src/game/BattleGround/BattleGroundSA.cpp @@ -404,6 +404,8 @@ void BattleGroundSA::HandleCreatureCreate(Creature* creature) void BattleGroundSA::HandleGameObjectCreate(GameObject* go) { + m_goEntryGuidStore[go->GetEntry()] = go->GetObjectGuid(); + switch (go->GetEntry()) { case BG_SA_GO_TRANSPORT_SHIP_HORDE_1: @@ -428,19 +430,6 @@ void BattleGroundSA::HandleGameObjectCreate(GameObject* go) case BG_SA_GO_TITAN_RELIC_HORDE: m_relicGuid[TEAM_INDEX_HORDE] = go->GetObjectGuid(); break; - case BG_SA_GO_GY_FLAG_ALLIANCE_EAST: - case BG_SA_GO_GY_FLAG_ALLIANCE_WEST: - case BG_SA_GO_GY_FLAG_ALLIANCE_SOUTH: - case BG_SA_GO_GY_FLAG_HORDE_EAST: - case BG_SA_GO_GY_FLAG_HORDE_WEST: - case BG_SA_GO_GY_FLAG_HORDE_SOUTH: - case BG_SA_GO_SIGIL_YELLOW_MOON: - case BG_SA_GO_SIGIL_GREEN_MOON: - case BG_SA_GO_SIGIL_BLUE_MOON: - case BG_SA_GO_SIGIL_RED_MOON: - case BG_SA_GO_SIGIL_PURPLE_MOON: - m_goEntryGuidStore[go->GetEntry()] = go->GetObjectGuid(); - break; } } diff --git a/src/game/CMakeLists.txt b/src/game/CMakeLists.txt index 1dbeb9e36c8..db8af6be9fe 100644 --- a/src/game/CMakeLists.txt +++ b/src/game/CMakeLists.txt @@ -51,6 +51,17 @@ if(NOT BUILD_PLAYERBOT) endforeach() endif() +if(NOT BUILD_IKE3_BOTS) + # exclude Ike3 Playerbots folder + set (EXCLUDE_DIR "Bots/") + foreach (TMP_PATH ${LIBRARY_SRCS}) + string (FIND ${TMP_PATH} ${EXCLUDE_DIR} EXCLUDE_DIR_FOUND) + if (NOT ${EXCLUDE_DIR_FOUND} EQUAL -1) + list(REMOVE_ITEM LIBRARY_SRCS ${TMP_PATH}) + endif () + endforeach() +endif() + set(PCH_BASE_FILENAME "pchdef") # exclude pchdef files set (EXCLUDE_FILE "${PCH_BASE_FILENAME}") @@ -84,6 +95,7 @@ target_link_libraries(${LIBRARY_NAME} ) # include additionals headers +if(NOT BUILD_IKE3_BOTS) set(ADDITIONAL_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/vmap @@ -93,6 +105,16 @@ set(ADDITIONAL_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/PlayerBot ${CMAKE_BINARY_DIR} ) +else() +set(ADDITIONAL_INCLUDE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/vmap + ${CMAKE_CURRENT_SOURCE_DIR}/AuctionHouseBot + ${CMAKE_CURRENT_SOURCE_DIR}/BattleGround + ${CMAKE_CURRENT_SOURCE_DIR}/OutdoorPvP + ${CMAKE_BINARY_DIR} +) +endif() target_include_directories(${LIBRARY_NAME} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR} @@ -100,6 +122,68 @@ target_include_directories(${LIBRARY_NAME} PRIVATE ${Boost_INCLUDE_DIRS} ) +if(BUILD_IKE3_BOTS) + include_directories( + ${CMAKE_SOURCE_DIR}/src/modules/Bots/playerbot + ${CMAKE_SOURCE_DIR}/src/modules/Bots/ahbot + ${CMAKE_SOURCE_DIR}/src/game + ${CMAKE_SOURCE_DIR}/src/game/AI + ${CMAKE_SOURCE_DIR}/src/game/Accounts + ${CMAKE_SOURCE_DIR}/src/game/Addons + ${CMAKE_SOURCE_DIR}/src/game/Arena + ${CMAKE_SOURCE_DIR}/src/game/AuctionHouse + ${CMAKE_SOURCE_DIR}/src/game/BattleGround + ${CMAKE_SOURCE_DIR}/src/game/Chat + ${CMAKE_SOURCE_DIR}/src/game/ChatCommands + ${CMAKE_SOURCE_DIR}/src/game/Combat + ${CMAKE_SOURCE_DIR}/src/game/DBScripts + ${CMAKE_SOURCE_DIR}/src/game/Entities + ${CMAKE_SOURCE_DIR}/src/game/GMTickets + ${CMAKE_SOURCE_DIR}/src/game/GameEvents + ${CMAKE_SOURCE_DIR}/src/game/Globals + ${CMAKE_SOURCE_DIR}/src/game/Grids + ${CMAKE_SOURCE_DIR}/src/game/Groups + ${CMAKE_SOURCE_DIR}/src/game/Guilds + ${CMAKE_SOURCE_DIR}/src/game/LFG + ${CMAKE_SOURCE_DIR}/src/game/Loot + ${CMAKE_SOURCE_DIR}/src/game/Mails + ${CMAKE_SOURCE_DIR}/src/game/Maps + ${CMAKE_SOURCE_DIR}/src/game/MotionGenerators + ${CMAKE_SOURCE_DIR}/src/game/Movement + ${CMAKE_SOURCE_DIR}/src/game/Object + ${CMAKE_SOURCE_DIR}/src/game/OutdoorPvP + ${CMAKE_SOURCE_DIR}/src/game/Pools + ${CMAKE_SOURCE_DIR}/src/game/Quests + ${CMAKE_SOURCE_DIR}/src/game/References + ${CMAKE_SOURCE_DIR}/src/game/Reputation + ${CMAKE_SOURCE_DIR}/src/game/Server + ${CMAKE_SOURCE_DIR}/src/game/Server + ${CMAKE_SOURCE_DIR}/src/game/Skills + ${CMAKE_SOURCE_DIR}/src/game/Social + ${CMAKE_SOURCE_DIR}/src/game/Spells + ${CMAKE_SOURCE_DIR}/src/game/Tools + ${CMAKE_SOURCE_DIR}/src/game/Trade + ${CMAKE_SOURCE_DIR}/src/game/VoiceChat + ${CMAKE_SOURCE_DIR}/src/game/Warden + ${CMAKE_SOURCE_DIR}/src/game/Weather + ${CMAKE_SOURCE_DIR}/src/game/World + ${CMAKE_SOURCE_DIR}/src/game/WorldHandlers + ${CMAKE_SOURCE_DIR}/src/game/movement + ${CMAKE_SOURCE_DIR}/src/game/vmap + ${CMAKE_SOURCE_DIR}/src/shared + ${CMAKE_SOURCE_DIR}/src/shared/Auth + ${CMAKE_SOURCE_DIR}/src/shared/Config + ${CMAKE_SOURCE_DIR}/src/shared/Common + ${CMAKE_SOURCE_DIR}/src/shared/Database + ${CMAKE_SOURCE_DIR}/src/shared/DataStores + ${CMAKE_SOURCE_DIR}/src/shared/Utilities + ${CMAKE_SOURCE_DIR}/src/shared/Log + ${CMAKE_SOURCE_DIR}/src/shared/Threading + ) + target_link_libraries(${LIBRARY_NAME} PUBLIC Bots) + add_dependencies(${LIBRARY_NAME} Bots) +endif() + if(UNIX) # Both systems don't have libdl and don't need them if (NOT (CMAKE_SYSTEM_NAME STREQUAL "FreeBSD" OR CMAKE_SYSTEM_NAME STREQUAL "NetBSD")) @@ -127,6 +211,11 @@ if (BUILD_PLAYERBOT) add_definitions(-DBUILD_PLAYERBOT) endif() +# Define Ike3 Bots ENABLE_PLAYERBOTS if need +if (BUILD_IKE3_BOTS) + add_definitions(-DENABLE_PLAYERBOTS) +endif() + if (MSVC) set_target_properties(${LIBRARY_NAME} PROPERTIES PROJECT_LABEL "Game") endif() diff --git a/src/game/Chat/Chat.cpp b/src/game/Chat/Chat.cpp index 2de66237503..ebb8a4047ab 100644 --- a/src/game/Chat/Chat.cpp +++ b/src/game/Chat/Chat.cpp @@ -34,6 +34,13 @@ #include "Pools/PoolManager.h" #include "GameEvents/GameEventMgr.h" +#ifdef ENABLE_PLAYERBOTS +#include "AhBot.h" +#include "playerbot.h" +#include "PlayerbotAIConfig.h" +#include "GuildTaskMgr.h" +#endif + // Supported shift-links (client generated and server side) // |color|Hachievement:achievement_id:player_guid_hex:completed_0_1:mm:dd:yy_from_2000:criteriaMask1:criteriaMask2:criteriaMask3:criteriaMask4|h[name]|h|r // - client, item icon shift click, not used in server currently @@ -71,6 +78,7 @@ ChatCommand* ChatHandler::getCommandTable() { "addon", SEC_ADMINISTRATOR, true, &ChatHandler::HandleAccountSetAddonCommand, "", nullptr }, { "gmlevel", SEC_CONSOLE, true, &ChatHandler::HandleAccountSetGmLevelCommand, "", nullptr }, { "password", SEC_CONSOLE, true, &ChatHandler::HandleAccountSetPasswordCommand, "", nullptr }, + { "edition", SEC_ADMINISTRATOR, true, &ChatHandler::HandleAccountSetEditionCommand, "", nullptr }, { nullptr, 0, false, nullptr, "", nullptr } }; @@ -403,6 +411,7 @@ ChatCommand* ChatHandler::getCommandTable() { "all_mypettalents", SEC_ADMINISTRATOR, false, &ChatHandler::HandleLearnAllMyPetTalentsCommand, "", nullptr }, { "all_myspells", SEC_ADMINISTRATOR, false, &ChatHandler::HandleLearnAllMySpellsCommand, "", nullptr }, { "all_mytalents", SEC_ADMINISTRATOR, false, &ChatHandler::HandleLearnAllMyTalentsCommand, "", nullptr }, + { "all_mylevel", SEC_ADMINISTRATOR, false, &ChatHandler::HandleLearnAllMyLevelCommand, "", nullptr }, { "all_recipes", SEC_GAMEMASTER, false, &ChatHandler::HandleLearnAllRecipesCommand, "", nullptr }, { "", SEC_ADMINISTRATOR, false, &ChatHandler::HandleLearnCommand, "", nullptr }, { nullptr, 0, false, nullptr, "", nullptr } @@ -475,6 +484,7 @@ ChatCommand* ChatHandler::getCommandTable() { "stats", SEC_GAMEMASTER, false, &ChatHandler::HandleMmapStatsCommand, "", nullptr }, { "testarea", SEC_GAMEMASTER, false, &ChatHandler::HandleMmapTestArea, "", nullptr }, { "testheight", SEC_GAMEMASTER, false, &ChatHandler::HandleMmapTestHeight, "", nullptr }, + { "demoapp", SEC_GAMEMASTER, false, &ChatHandler::HandleMmapDemoApp, "", nullptr }, { "", SEC_ADMINISTRATOR, false, &ChatHandler::HandleMmap, "", nullptr }, { nullptr, 0, false, nullptr, "", nullptr } }; @@ -974,6 +984,15 @@ ChatCommand* ChatHandler::getCommandTable() { "auction", SEC_ADMINISTRATOR, false, nullptr, "", auctionCommandTable }, #ifdef BUILD_AHBOT { "ahbot", SEC_ADMINISTRATOR, true, nullptr, "", ahbotCommandTable }, +#endif +#ifdef ENABLE_PLAYERBOTS +#ifndef BUILD_AHBOT + { "ahbot", SEC_GAMEMASTER, true, &ChatHandler::HandleAhBotCommand, "", NULL }, +#endif + { "rndbot", SEC_GAMEMASTER, true, &ChatHandler::HandleRandomPlayerbotCommand, "", NULL }, + { "bot", SEC_PLAYER, false, &ChatHandler::HandlePlayerbotCommand, "", NULL }, + { "gtask", SEC_GAMEMASTER, true, &ChatHandler::HandleGuildTaskCommand, "", NULL }, + { "pmon", SEC_GAMEMASTER, true, &ChatHandler::HandlePerfMonCommand, "" }, #endif { "cast", SEC_ADMINISTRATOR, false, nullptr, "", castCommandTable }, { "character", SEC_GAMEMASTER, true, nullptr, "", characterCommandTable}, diff --git a/src/game/Chat/Chat.h b/src/game/Chat/Chat.h index 4751258027f..4bfdd11c5dc 100644 --- a/src/game/Chat/Chat.h +++ b/src/game/Chat/Chat.h @@ -104,6 +104,11 @@ class ChatHandler bool HasSentErrorMessage() const { return sentErrorMessage;} +#ifdef ENABLE_PLAYERBOTS + WorldSession* GetSession() { return m_session; } + bool HandlePerfMonCommand(char* args); +#endif + /** * \brief Prepare SMSG_GM_MESSAGECHAT/SMSG_MESSAGECHAT * @@ -207,6 +212,7 @@ class ChatHandler bool HandleAccountSetAddonCommand(char* args); bool HandleAccountSetGmLevelCommand(char* args); bool HandleAccountSetPasswordCommand(char* args); + bool HandleAccountSetEditionCommand(char* args); #ifdef BUILD_AHBOT bool HandleAHBotRebuildCommand(char* args); @@ -402,6 +408,7 @@ class ChatHandler bool HandleLearnAllMyPetTalentsCommand(char* args); bool HandleLearnAllMySpellsCommand(char* args); bool HandleLearnAllMyTalentsCommand(char* args); + bool HandleLearnAllMyLevelCommand(char* args); bool HandleListAreaTriggerCommand(char* args); bool HandleListAurasCommand(char* args); @@ -792,6 +799,12 @@ class ChatHandler #ifdef BUILD_PLAYERBOT bool HandlePlayerbotCommand(char* args); #endif +#ifdef ENABLE_PLAYERBOTS + bool HandlePlayerbotCommand(char* args); + bool HandleRandomPlayerbotCommand(char* args); + bool HandleAhBotCommand(char* args); + bool HandleGuildTaskCommand(char* args); +#endif bool HandleArenaFlushPointsCommand(char* args); bool HandleArenaSeasonRewardsCommand(char* args); @@ -805,6 +818,7 @@ class ChatHandler bool HandleMmap(char* args); bool HandleMmapTestArea(char* args); bool HandleMmapTestHeight(char* args); + bool HandleMmapDemoApp(char* args); bool HandleLinkAddCommand(char* args); bool HandleLinkRemoveCommand(char* args); diff --git a/src/game/Chat/ChatHandler.cpp b/src/game/Chat/ChatHandler.cpp index af121681eac..3d73288e1d5 100644 --- a/src/game/Chat/ChatHandler.cpp +++ b/src/game/Chat/ChatHandler.cpp @@ -37,6 +37,11 @@ #include "GMTickets/GMTicketMgr.h" #include "Anticheat/Anticheat.hpp" +#ifdef ENABLE_PLAYERBOTS +#include "playerbot.h" +#include "RandomPlayerbotMgr.h" +#endif + bool WorldSession::CheckChatMessage(std::string& msg, bool addon/* = false*/) { #ifdef BUILD_PLAYERBOT @@ -192,6 +197,19 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recv_data) if (!CheckChatMessage(msg)) return; +#ifdef ENABLE_PLAYERBOTS + if (GetSecurity() > SEC_PLAYER && GetPlayer()->IsGameMaster()) + sRandomPlayerbotMgr.HandleCommand(type, msg, *_player, "", TEAM_BOTH_ALLOWED, lang); + else + sRandomPlayerbotMgr.HandleCommand(type, msg, *_player, "", GetPlayer()->GetTeam(), lang); + + // apply to own bots + if (_player->GetPlayerbotMgr()) + { + _player->GetPlayerbotMgr()->HandleCommand(type, msg, lang); + } +#endif + if (type == CHAT_MSG_SAY) { GetPlayer()->Say(msg, lang); @@ -277,7 +295,17 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recv_data) return; } } + +#ifdef ENABLE_PLAYERBOTS + if (player->GetPlayerbotAI()) + { + player->GetPlayerbotAI()->HandleCommand(type, msg, *GetPlayer(), lang); + GetPlayer()->m_speakTime = 0; + GetPlayer()->m_speakCount = 0; + } + if (msg.find("BOT\t") != 0) //These are spoofed SendAddonMessage with channel "WHISPER". +#endif GetPlayer()->Whisper(msg, lang, player->GetObjectGuid()); if (lang != LANG_ADDON && !m_anticheat->IsSilenced()) @@ -316,6 +344,19 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recv_data) if ((type == CHAT_MSG_PARTY_LEADER) && !group->IsLeader(_player->GetObjectGuid())) return; +#ifdef ENABLE_PLAYERBOTS + for (GroupReference* itr = group->GetFirstMember(); itr != NULL; itr = itr->next()) + { + Player* player = itr->getSource(); + if (player && player->GetPlayerbotAI()) + { + player->GetPlayerbotAI()->HandleCommand(type, msg, *GetPlayer(), lang); + GetPlayer()->m_speakTime = 0; + GetPlayer()->m_speakCount = 0; + } + } +#endif + WorldPacket data; ChatHandler::BuildChatPacket(data, ChatMsg(type), msg.c_str(), Language(lang), _player->GetChatTag(), _player->GetObjectGuid(), _player->GetName()); group->BroadcastPacket(data, false, group->GetMemberGroup(GetPlayer()->GetObjectGuid())); @@ -344,6 +385,20 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recv_data) if (Guild* guild = sGuildMgr.GetGuildById(GetPlayer()->GetGuildId())) guild->BroadcastToGuild(this, msg, lang == LANG_ADDON ? LANG_ADDON : LANG_UNIVERSAL); +#ifdef ENABLE_PLAYERBOTS + PlayerbotMgr *mgr = GetPlayer()->GetPlayerbotMgr(); + if (mgr && GetPlayer()->GetGuildId()) + { + for (PlayerBotMap::const_iterator it = mgr->GetPlayerBotsBegin(); it != mgr->GetPlayerBotsEnd(); ++it) + { + Player* const bot = it->second; + if (bot->GetGuildId() == GetPlayer()->GetGuildId()) + bot->GetPlayerbotAI()->HandleCommand(type, msg, *GetPlayer(), lang); + } + } + sRandomPlayerbotMgr.HandleCommand(type, msg, *_player, "", GetPlayer()->GetTeam(), lang); +#endif + break; } case CHAT_MSG_OFFICER: @@ -401,6 +456,19 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recv_data) if (!group || !group->IsRaidGroup()) return; +#ifdef ENABLE_PLAYERBOTS + for (GroupReference* itr = group->GetFirstMember(); itr != NULL; itr = itr->next()) + { + Player* player = itr->getSource(); + if (player && player->GetPlayerbotAI()) + { + player->GetPlayerbotAI()->HandleCommand(type, msg, *GetPlayer(), lang); + GetPlayer()->m_speakTime = 0; + GetPlayer()->m_speakCount = 0; + } + } +#endif + WorldPacket data; ChatHandler::BuildChatPacket(data, CHAT_MSG_RAID, msg.c_str(), Language(lang), _player->GetChatTag(), _player->GetObjectGuid(), _player->GetName()); group->BroadcastPacket(data, false); @@ -432,6 +500,19 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recv_data) if (!group || !group->IsRaidGroup() || !group->IsLeader(_player->GetObjectGuid())) return; +#ifdef ENABLE_PLAYERBOTS + for (GroupReference* itr = group->GetFirstMember(); itr != NULL; itr = itr->next()) + { + Player* player = itr->getSource(); + if (player && player->GetPlayerbotAI()) + { + player->GetPlayerbotAI()->HandleCommand(type, msg, *GetPlayer(), lang); + GetPlayer()->m_speakTime = 0; + GetPlayer()->m_speakCount = 0; + } + } +#endif + WorldPacket data; ChatHandler::BuildChatPacket(data, CHAT_MSG_RAID_LEADER, msg.c_str(), Language(lang), _player->GetChatTag(), _player->GetObjectGuid(), _player->GetName()); group->BroadcastPacket(data, false); @@ -462,6 +543,19 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recv_data) else if (sWorld.getConfig(CONFIG_BOOL_CHAT_RESTRICTED_RAID_WARNINGS)) return; +#ifdef ENABLE_PLAYERBOTS + for (GroupReference* itr = group->GetFirstMember(); itr != NULL; itr = itr->next()) + { + Player* player = itr->getSource(); + if (player && player->GetPlayerbotAI()) + { + player->GetPlayerbotAI()->HandleCommand(type, msg, *GetPlayer(), lang); + GetPlayer()->m_speakTime = 0; + GetPlayer()->m_speakCount = 0; + } + } +#endif + WorldPacket data; ChatHandler::BuildChatPacket(data, CHAT_MSG_RAID_WARNING, msg.c_str(), Language(lang), _player->GetChatTag(), _player->GetObjectGuid(), _player->GetName()); group->BroadcastPacket(data, true); @@ -537,6 +631,20 @@ void WorldSession::HandleMessagechatOpcode(WorldPacket& recv_data) if (lang != LANG_ADDON && (chn->HasFlag(Channel::ChannelFlags::CHANNEL_FLAG_GENERAL) || chn->IsStatic())) m_anticheat->Channel(msg); + +#ifdef ENABLE_PLAYERBOTS + // if GM apply to all random bots + if (GetSecurity() > SEC_PLAYER && GetPlayer()->IsGameMaster()) + sRandomPlayerbotMgr.HandleCommand(type, msg, *_player, "", TEAM_BOTH_ALLOWED, lang); + else + sRandomPlayerbotMgr.HandleCommand(type, msg, *_player, "", GetPlayer()->GetTeam(), lang); + + // apply to own bots + if (_player->GetPlayerbotMgr() && chn->GetFlags() & 0x18) + { + _player->GetPlayerbotMgr()->HandleCommand(type, msg, lang); + } +#endif } } } break; diff --git a/src/game/Chat/Level3.cpp b/src/game/Chat/Level3.cpp index 63e3301589c..c5a72091078 100644 --- a/src/game/Chat/Level3.cpp +++ b/src/game/Chat/Level3.cpp @@ -62,6 +62,8 @@ #endif #include "Server/PacketLog.h" +#include + #ifdef BUILD_AHBOT #include "AuctionHouseBot/AuctionHouseBot.h" @@ -1234,6 +1236,58 @@ bool ChatHandler::HandleAccountSetPasswordCommand(char* args) return false; } +// Set collector's edition +bool ::ChatHandler::HandleAccountSetEditionCommand(char* args) +{ + char* accountStr = ExtractOptNotLastArg(&args); + + std::string targetAccountName; + Player* targetPlayer = nullptr; + uint32 targetAccountId = ExtractAccountId(&accountStr, &targetAccountName, &targetPlayer); + if (!targetAccountId) + return false; + + bool value; + if (!ExtractOnOff(&args, value)) + { + SendSysMessage(LANG_USE_BOL); + SetSentErrorMessage(true); + return false; + } + + if (value) + { + if (targetPlayer && targetPlayer->GetSession()->HasAccountFlag(ACCOUNT_FLAG_COLLECTOR_CLASSIC | ACCOUNT_FLAG_COLLECTOR_TBC | ACCOUNT_FLAG_COLLECTOR_WRATH)) + { + SendSysMessage("Target account already has Collector's Edition enabled"); + return false; + } + if (targetPlayer) + targetPlayer->GetSession()->AddAccountFlag(ACCOUNT_FLAG_COLLECTOR_CLASSIC | ACCOUNT_FLAG_COLLECTOR_TBC | ACCOUNT_FLAG_COLLECTOR_WRATH); + + LoginDatabase.PExecute("UPDATE account SET flags = flags | 0x%x WHERE id = %u", targetAccountId, ACCOUNT_FLAG_COLLECTOR_CLASSIC | ACCOUNT_FLAG_COLLECTOR_TBC | ACCOUNT_FLAG_COLLECTOR_WRATH); + SendSysMessage("Target account Collector's Edition enabled"); + return true; + } + else + { + if (targetPlayer && !targetPlayer->GetSession()->HasAccountFlag(ACCOUNT_FLAG_COLLECTOR_CLASSIC | ACCOUNT_FLAG_COLLECTOR_TBC | ACCOUNT_FLAG_COLLECTOR_WRATH)) + { + SendSysMessage("Target account does not have Collector's Edition enabled"); + return false; + } + if (targetPlayer) + targetPlayer->GetSession()->AddAccountFlag(ACCOUNT_FLAG_COLLECTOR_CLASSIC | ACCOUNT_FLAG_COLLECTOR_TBC | ACCOUNT_FLAG_COLLECTOR_WRATH); + + LoginDatabase.PExecute("UPDATE account SET flags = flags & ~0x%x WHERE id = %u", targetAccountId, ACCOUNT_FLAG_COLLECTOR_CLASSIC | ACCOUNT_FLAG_COLLECTOR_TBC | ACCOUNT_FLAG_COLLECTOR_WRATH); + SendSysMessage("Target account Collector's Edition disabled"); + return true; + } + + //PSendSysMessage(LANG_COMMAND_FLYMODE_STATUS, GetNameLink(target).c_str(), args); + return true; +} + void ChatHandler::ShowAchievementCriteriaListHelper(AchievementCriteriaEntry const* criEntry, AchievementEntry const* achEntry, LocaleConstant loc, Player* target /*= nullptr*/) { std::ostringstream ss; @@ -1917,6 +1971,15 @@ bool ChatHandler::HandleLearnAllMyTalentsCommand(char* /*args*/) return true; } +bool ChatHandler::HandleLearnAllMyLevelCommand(char* /*args*/) +{ + Player* player = m_session->GetPlayer(); + player->learnClassLevelSpells(); + + SendSysMessage(LANG_COMMAND_LEARN_CLASS_SPELLS); + return true; +} + bool ChatHandler::HandleLearnAllMyPetTalentsCommand(char* /*args*/) { Player* player = m_session->GetPlayer(); @@ -7485,6 +7548,44 @@ bool ChatHandler::HandleMmapTestHeight(char* args) return true; } +bool ChatHandler::HandleMmapDemoApp(char* args) +{ +#ifdef _WIN32 + Player* player = m_session->GetPlayer(); + + FILE* fin = fopen("RecastDemoMod.exe", "r"); + if (!fin) + { + PSendSysMessage("No RecastDemoMod.exe found!"); + return false; + } + + GridPair p = MaNGOS::ComputeGridPair(player->GetPositionX(), player->GetPositionY()); + + int gx = 63 - p.x_coord; + int gy = 63 - p.y_coord; + + std::string cmdline = "RecastDemoMod.exe -d " + sWorld.GetDataPath() + " -map " + std::to_string(player->GetMapId()) + " -tilex " + std::to_string(gx) + " -tiley " + std::to_string(gy); + LPTSTR szCmdline = _tcsdup(TEXT(cmdline.c_str())); + STARTUPINFO info = { sizeof(info) }; + PROCESS_INFORMATION processInfo; + if (CreateProcess("RecastDemoMod.exe", szCmdline, NULL, NULL, TRUE, CREATE_NEW_PROCESS_GROUP, NULL, NULL, &info, &processInfo)) + { + // don't wait for it to finish. + //::WaitForSingleObject(processInfo.hProcess, INFINITE); + // free up resources... + CloseHandle(processInfo.hProcess); + CloseHandle(processInfo.hThread); + } + + PSendSysMessage("Running Recast Demo App at current tile"); + return true; +#else + PSendSysMessage("Command is Windows only"); + return false; +#endif +} + bool ChatHandler::HandleServerResetAllRaidCommand(char* /*args*/) { PSendSysMessage("Global raid instances reset, all players in raid instances will be teleported to homebind!"); diff --git a/src/game/Entities/Camera.cpp b/src/game/Entities/Camera.cpp index a74ccab0d09..4636d360afc 100644 --- a/src/game/Entities/Camera.cpp +++ b/src/game/Entities/Camera.cpp @@ -143,10 +143,15 @@ template void Camera::UpdateVisibilityOf(Corpse*, UpdateData&, WorldObjectSet&); template void Camera::UpdateVisibilityOf(GameObject*, UpdateData&, WorldObjectSet&); template void Camera::UpdateVisibilityOf(DynamicObject*, UpdateData&, WorldObjectSet&); -void Camera::UpdateVisibilityForOwner(bool addToWorld) +void Camera::UpdateVisibilityForOwner(bool addToWorld, bool onlyUpdate) { +#ifdef ENABLE_PLAYERBOTS + if (!m_owner.isRealPlayer()) + return; +#endif + MaNGOS::VisibleNotifier notifier(*this); - Cell::VisitAllObjects(m_source, notifier, addToWorld ? MAX_VISIBILITY_DISTANCE : m_source->GetVisibilityData().GetVisibilityDistance(), false); + Cell::VisitAllObjects(m_source, notifier, (addToWorld || onlyUpdate) ? MAX_VISIBILITY_DISTANCE : m_source->GetVisibilityData().GetVisibilityDistance(), onlyUpdate); notifier.Notify(); } diff --git a/src/game/Entities/Camera.h b/src/game/Entities/Camera.h index 38846c0d9fe..04db198ea77 100644 --- a/src/game/Entities/Camera.h +++ b/src/game/Entities/Camera.h @@ -55,7 +55,7 @@ class Camera // updates visibility of worldobjects around viewpoint for camera's owner void UpdateVisibilityForOwner() { UpdateVisibilityForOwner(false); } - void UpdateVisibilityForOwner(bool addToWorld); + void UpdateVisibilityForOwner(bool addToWorld, bool onlyUpdate = false); private: // called when viewpoint changes visibility state diff --git a/src/game/Entities/CharacterHandler.cpp b/src/game/Entities/CharacterHandler.cpp index fcf30b237b7..5b20bb5a4fc 100644 --- a/src/game/Entities/CharacterHandler.cpp +++ b/src/game/Entities/CharacterHandler.cpp @@ -40,11 +40,18 @@ #include "Calendar/Calendar.h" #include "AI/ScriptDevAI/ScriptDevAIMgr.h" #include "Anticheat/Anticheat.hpp" +#include "Mail.h" +#include "AI/ScriptDevAI/scripts/custom/Transmogrification.h" #ifdef BUILD_PLAYERBOT #include "PlayerBot/Base/PlayerbotMgr.h" #endif +#ifdef ENABLE_PLAYERBOTS +#include "playerbot.h" +#include "PlayerbotAIConfig.h" +#endif + // config option SkipCinematics supported values enum CinematicsSkipMode { @@ -66,6 +73,101 @@ class LoginQueryHolder : public SqlQueryHolder bool Initialize(); }; +#ifdef ENABLE_PLAYERBOTS + +class PlayerbotLoginQueryHolder : public LoginQueryHolder +{ +private: + uint32 masterAccountId; + PlayerbotHolder* playerbotHolder; + +public: + PlayerbotLoginQueryHolder(PlayerbotHolder* playerbotHolder, uint32 masterAccount, uint32 accountId, uint32 guid) + : LoginQueryHolder(accountId, ObjectGuid(HIGHGUID_PLAYER, guid)), masterAccountId(masterAccount), playerbotHolder(playerbotHolder) { } + +public: + uint32 GetMasterAccountId() const { return masterAccountId; } + PlayerbotHolder* GetPlayerbotHolder() { return playerbotHolder; } +}; + +void PlayerbotHolder::AddPlayerBot(uint32 playerGuid, uint32 masterAccount) +{ + // has bot already been added? + ObjectGuid guid = ObjectGuid(HIGHGUID_PLAYER, playerGuid); + Player* bot = sObjectMgr.GetPlayer(guid); + + if (bot && bot->IsInWorld()) + return; + + uint32 accountId = sObjectMgr.GetPlayerAccountIdByGUID(guid); + if (accountId == 0) + return; + + PlayerbotLoginQueryHolder *holder = new PlayerbotLoginQueryHolder(this, masterAccount, accountId, playerGuid); + if (!holder->Initialize()) + { + delete holder; // delete all unprocessed queries + return; + } + + CharacterDatabase.DelayQueryHolder(this, &PlayerbotHolder::HandlePlayerBotLoginCallback, holder); +} + +void PlayerbotHolder::HandlePlayerBotLoginCallback(QueryResult * dummy, SqlQueryHolder * holder) +{ + if (!holder) + return; + + PlayerbotLoginQueryHolder* lqh = (PlayerbotLoginQueryHolder*)holder; + uint32 masterAccount = lqh->GetMasterAccountId(); + + WorldSession* masterSession = masterAccount ? sWorld.FindSession(masterAccount) : NULL; + uint32 botAccountId = lqh->GetAccountId(); + WorldSession *botSession = new WorldSession(botAccountId, NULL, SEC_PLAYER, MAX_EXPANSION, 0, LOCALE_enUS, "", 0, 0, false); + botSession->SetNoAnticheat(); + + // has bot already been added? + if (sObjectMgr.GetPlayer(lqh->GetGuid())) + return; + + uint32 guid = lqh->GetGuid().GetRawValue(); + botSession->HandlePlayerLogin(lqh); // will delete lqh + + Player* bot = botSession->GetPlayer(); + if (!bot) + { + sLog.outError("Error logging in bot %d, please try to reset all random bots", guid); + return; + } + PlayerbotMgr *mgr = bot->GetPlayerbotMgr(); + bot->SetPlayerbotMgr(NULL); + delete mgr; + sRandomPlayerbotMgr.OnPlayerLogin(bot); + + bool allowed = false; + if (botAccountId == masterAccount) + allowed = true; + else if (masterSession && sPlayerbotAIConfig.allowGuildBots && bot->GetGuildId() == masterSession->GetPlayer()->GetGuildId()) + allowed = true; + else if (sPlayerbotAIConfig.IsInRandomAccountList(botAccountId)) + allowed = true; + + if (allowed) + { + OnBotLogin(bot); + return; + } + + if (masterSession) + { + ChatHandler ch(masterSession); + ch.PSendSysMessage("You are not allowed to control bot %s", bot->GetName()); + } + LogoutPlayerBot(bot->GetObjectGuid()); + sLog.outError("Attempt to add not allowed bot %s, please try to reset all random bots", bot->GetName()); +} +#endif + bool LoginQueryHolder::Initialize() { SetSize(MAX_PLAYER_LOGIN_QUERY); @@ -135,8 +237,25 @@ class CharacterHandler { if (!holder) return; - if (WorldSession* session = sWorld.FindSession(((LoginQueryHolder*)holder)->GetAccountId())) - session->HandlePlayerLogin((LoginQueryHolder*)holder); + WorldSession* session = sWorld.FindSession(((LoginQueryHolder*)holder)->GetAccountId()); + if (!session) + { + delete holder; + return; + } +#ifdef ENABLE_PLAYERBOTS + ObjectGuid guid = ((LoginQueryHolder*)holder)->GetGuid(); +#endif + session->HandlePlayerLogin((LoginQueryHolder*)holder); +#ifdef ENABLE_PLAYERBOTS + Player* player = sObjectMgr.GetPlayer(guid, true); + if (player) + { + player->SetPlayerbotMgr(new PlayerbotMgr(player)); + player->GetPlayerbotMgr()->OnPlayerLogin(player); + sRandomPlayerbotMgr.OnPlayerLogin(player); + } +#endif } #ifdef BUILD_PLAYERBOT // This callback is different from the normal HandlePlayerLoginCallback in that it @@ -604,6 +723,34 @@ void WorldSession::HandlePlayerLoginOpcode(WorldPacket& recv_data) return; } +#ifdef ENABLE_PLAYERBOTS + if (pCurrChar && pCurrChar->GetPlayerbotAI()) + { + WorldSession* botSession = pCurrChar->GetSession(); + SetPlayer(pCurrChar, playerGuid); + _player->SetSession(this); + _logoutTime = time(0); + + m_sessionDbcLocale = botSession->m_sessionDbcLocale; + m_sessionDbLocaleIndex = botSession->m_sessionDbLocaleIndex; + + PlayerbotMgr* mgr = _player->GetPlayerbotMgr(); + if (!mgr || mgr->GetMaster() != _player) + { + _player->SetPlayerbotMgr(NULL); + delete mgr; + _player->SetPlayerbotMgr(new PlayerbotMgr(_player)); + _player->GetPlayerbotMgr()->OnPlayerLogin(_player); + + if (sRandomPlayerbotMgr.GetPlayerBot(playerGuid)) + + sRandomPlayerbotMgr.MovePlayerBot(playerGuid, _player->GetPlayerbotMgr()); + else + _player->GetPlayerbotMgr()->OnBotLogin(_player); + } + } +#endif + if (_player) { // player is reconnecting @@ -688,12 +835,13 @@ void WorldSession::HandlePlayerLogin(LoginQueryHolder* holder) // "GetAccountId()==db stored account id" checked in LoadFromDB (prevent login not own character using cheating tools) if (!pCurrChar->LoadFromDB(playerGuid, holder)) { + std::string pCurrName = "unknown"; KickPlayer(); // disconnect client, player no set to session and it will not deleted or saved at kick // also deletes player delete holder; // delete all unprocessed queries m_playerLoading = false; - sLog.outError("HandlePlayerLogin> LoadFromDB failed to load %s, AccountId = %u", pCurrChar->GetGuidStr().c_str(), GetAccountId()); + sLog.outError("HandlePlayerLogin> LoadFromDB failed to load %s, AccountId = %u", pCurrName.c_str(), GetAccountId()); WorldPacket data(SMSG_CHARACTER_LOGIN_FAILED, 1); data << (uint8)CHAR_LOGIN_NO_CHARACTER; @@ -918,6 +1066,169 @@ void WorldSession::HandlePlayerLogin(LoginQueryHolder* holder) if (pCurrChar->HasAtLoginFlag(AT_LOGIN_FIRST)) pCurrChar->RemoveAtLoginFlag(AT_LOGIN_FIRST); + // add collector to all accounts if enabled + if (sWorld.getConfig(CONFIG_BOOL_COLLECTORS_EDITION) && !(HasAccountFlag(ACCOUNT_FLAG_COLLECTOR_CLASSIC) && HasAccountFlag(ACCOUNT_FLAG_COLLECTOR_TBC) && HasAccountFlag(ACCOUNT_FLAG_COLLECTOR_WRATH))) + { + AddAccountFlag(ACCOUNT_FLAG_COLLECTOR_CLASSIC | ACCOUNT_FLAG_COLLECTOR_TBC | ACCOUNT_FLAG_COLLECTOR_WRATH); + LoginDatabase.PExecute("UPDATE account SET flags = flags | 0x%x WHERE id = %u", GetAccountId(), ACCOUNT_FLAG_COLLECTOR_CLASSIC | ACCOUNT_FLAG_COLLECTOR_TBC | ACCOUNT_FLAG_COLLECTOR_WRATH); + } + + // create collector's edition reward (tbc) + if (HasAccountFlag(ACCOUNT_FLAG_COLLECTOR_TBC) && !(pCurrChar->HasItemCount(25535, 1, true) || pCurrChar->HasSpell(32298))) + { + bool hasPetReward = false; + // check if already has in mail + for (PlayerMails::iterator itr = _player->GetMailBegin(); itr != _player->GetMailEnd(); ++itr) + { + // skip deleted mails + if ((*itr)->state == MAIL_STATE_DELETED) + continue; + + uint8 item_count = uint8((*itr)->items.size()); + for (uint8 i = 0; i < item_count; ++i) + { + Item* item = _player->GetMItem((*itr)->items[i].item_guid); + if (item->GetEntry() == 25535) + { + hasPetReward = true; + break; + } + } + } + + if (!hasPetReward) + { + ostringstream body; + body << "Hello, " << pCurrChar->GetName() << ",\n\n"; + body << "Welcome to the World of Warcraft!\n\n"; + body << "As special thanks for purchasing the World of Warcraft: The Burning Crusade Collector's Edition we send you a gift: a little companion to join you on your quest for adventure and glory.\n\n"; + body << "Thanks again, and enjoy your stay in the World of Warcraft!"; + + MailDraft draft; + draft.SetSubjectAndBody("Collector's Edition Gift", body.str()); + + Item* gift = Item::CreateItem(25535, 1, nullptr); + gift->SaveToDB(); + draft.AddItem(gift); + + MailSender sender(MAIL_NORMAL, (uint32)0, MAIL_STATIONERY_GM); + draft.SendMailTo(MailReceiver(pCurrChar, pCurrChar->GetObjectGuid()), sender); + } + } + + // create collector's edition reward (tbc) + if (HasAccountFlag(ACCOUNT_FLAG_COLLECTOR_WRATH) && !(pCurrChar->HasItemCount(39286, 1, true) || pCurrChar->HasSpell(52615))) + { + bool hasPetReward = false; + // check if already has in mail + for (PlayerMails::iterator itr = _player->GetMailBegin(); itr != _player->GetMailEnd(); ++itr) + { + // skip deleted mails + if ((*itr)->state == MAIL_STATE_DELETED) + continue; + + uint8 item_count = uint8((*itr)->items.size()); + for (uint8 i = 0; i < item_count; ++i) + { + Item* item = _player->GetMItem((*itr)->items[i].item_guid); + if (item->GetEntry() == 39286) + { + hasPetReward = true; + break; + } + } + } + + if (!hasPetReward) + { + ostringstream body; + body << "Hello, " << pCurrChar->GetName() << ",\n\n"; + body << "Welcome to the World of Warcraft!\n\n"; + body << "As special thanks for purchasing the World of Warcraft: Wrath of the Lich King Collector's Edition we send you a gift: a little companion to join you on your quest for adventure and glory.\n\n"; + body << "Thanks again, and enjoy your stay in the World of Warcraft!"; + + MailDraft draft; + draft.SetSubjectAndBody("Collector's Edition Gift", body.str()); + + Item* gift = Item::CreateItem(39286, 1, nullptr); + gift->SaveToDB(); + draft.AddItem(gift); + + MailSender sender(MAIL_NORMAL, (uint32)0, MAIL_STATIONERY_GM); + draft.SendMailTo(MailReceiver(pCurrChar, pCurrChar->GetObjectGuid()), sender); + } + } + + // create collector's edition reward (vanilla) + if (HasAccountFlag(ACCOUNT_FLAG_COLLECTOR_CLASSIC)) + { + uint32 itemid = 0; + uint32 questid = 0; + switch (pCurrChar->getRace()) + { + case RACE_HUMAN: + itemid = 14646; + questid = 5805; + break; + case RACE_ORC: + case RACE_TROLL: + itemid = 14649; + questid = 5843; + break; + case RACE_DWARF: + case RACE_GNOME: + itemid = 14647; + questid = 5843; + break; + case RACE_NIGHTELF: + itemid = 14648; + questid = 5842; + break; + case RACE_UNDEAD: + itemid = 14651; + questid = 5847; + break; + case RACE_TAUREN: + itemid = 14650; + questid = 5844; + break; + } + + if (itemid && questid) + { + if (!pCurrChar->HasQuest(questid) && !pCurrChar->HasItemCount(itemid, 1, true) && !pCurrChar->GetQuestRewardStatus(questid)) + { + ItemPrototype const* pProto = ObjectMgr::GetItemPrototype(itemid); + if (pProto) + { + uint32 noSpaceForCount = 0; + ItemPosCountVec dest; + uint8 msg = pCurrChar->CanStoreNewItem(NULL_BAG, NULL_SLOT, dest, itemid, 1, &noSpaceForCount); + if (msg != EQUIP_ERR_OK) + { + ostringstream body; + body << "Hello, " << pCurrChar->GetName() << ",\n\n"; + body << "Welcome to the World of Warcraft!\n\n"; + body << "As special thanks for purchasing the World of Warcraft Collector's Edition we send you a gift: a little companion to join you on your quest for adventure and glory.\n\n"; + body << "Thanks again, and enjoy your stay in the World of Warcraft!"; + + MailDraft draft; + draft.SetSubjectAndBody("Collector's Edition Gift", body.str()); + + Item* gift = Item::CreateItem(itemid, 1, nullptr); + gift->SaveToDB(); + draft.AddItem(gift); + + MailSender sender(MAIL_NORMAL, (uint32)0, MAIL_STATIONERY_GM); + draft.SendMailTo(MailReceiver(pCurrChar, pCurrChar->GetObjectGuid()), sender); + } + else + Item* item = pCurrChar->StoreNewItem(dest, itemid, true); + } + } + } + } + // show time before shutdown if shutdown planned. if (sWorld.IsShutdowning()) sWorld.ShutdownMsg(true, pCurrChar); @@ -936,14 +1247,63 @@ void WorldSession::HandlePlayerLogin(LoginQueryHolder* holder) pCurrChar->CastSpell(pCurrChar, invisibleAuraInfo, TRIGGERED_OLD_TRIGGERED); } + std::string IP_str = GetRemoteAddress(); sLog.outChar("Account: %d (IP: %s) Login Character:[%s] (guid: %u)", - GetAccountId(), GetRemoteAddress().c_str(), pCurrChar->GetName(), pCurrChar->GetGUIDLow()); + GetAccountId(), IP_str.c_str(), pCurrChar->GetName(), pCurrChar->GetGUIDLow()); if (!pCurrChar->IsStandState() && !pCurrChar->IsStunned()) pCurrChar->SetStandState(UNIT_STAND_STATE_STAND); m_playerLoading = false; +//Start Solocraft Functions + bool SoloCraftEnable = sWorld.getConfig(CONFIG_BOOL_SOLOCRAFT_ENABLED); + bool SoloCraftAnnounceModule = sWorld.getConfig(CONFIG_BOOL_SOLOCRAFT_ANNOUNCE); + + if (SoloCraftEnable) + { + if (SoloCraftAnnounceModule) + { + ChatHandler(pCurrChar->GetSession()).SendSysMessage("This server is running |cff4CFF00SPP SoloCraft Custom |rmodule."); + } + } +//End Solocraft Functions + + ObjectGuid playerGUID = _player->GetObjectGuid(); + sTransmogrification->entryMap.erase(playerGUID); + QueryResult* result = CharacterDatabase.PQuery("SELECT GUID, FakeEntry FROM custom_transmogrification WHERE Owner = %u", _player->GetObjectGuid()); + if (result) + { + do + { + const ObjectGuid itemGUID = ObjectGuid(HIGHGUID_ITEM, (result[0][0].GetUInt32())); + uint32 fakeEntry = (*result)[1].GetUInt32(); + if (sObjectMgr.GetItemPrototype(fakeEntry)) + { + sTransmogrification->dataMap[itemGUID] = playerGUID; + sTransmogrification->entryMap[playerGUID][itemGUID] = fakeEntry; + } + else + { + //sLog->outError(LOG_FILTER_SQL, "Item entry (Entry: %u, itemGUID: %u, playerGUID: %u) does not exist, ignoring.", fakeEntry, GUID_LOPART(itemGUID), player->GetObjectGuidLow()); + // CharacterDatabase.PExecute("DELETE FROM custom_transmogrification WHERE FakeEntry = %u", fakeEntry); + } + } while (result->NextRow()); + + for (uint8 slot = EQUIPMENT_SLOT_START; slot < EQUIPMENT_SLOT_END; ++slot) + { + if (Item* item = _player->GetItemByPos(INVENTORY_SLOT_BAG_0, slot)) + _player->SetVisibleItemSlot(slot, item); + } + + delete result; + } + +#ifdef PRESETS + if (sTransmogrification->GetEnableSets()) + sTransmogrification->LoadPlayerSets(playerGUID); +#endif + // Handle Login-Achievements (should be handled after loading) pCurrChar->GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_ON_LOGIN, 1); diff --git a/src/game/Entities/GameObject.cpp b/src/game/Entities/GameObject.cpp index 6cb567615a3..eb229f840ae 100644 --- a/src/game/Entities/GameObject.cpp +++ b/src/game/Entities/GameObject.cpp @@ -296,9 +296,12 @@ bool GameObject::Create(uint32 dbGuid, uint32 guidlow, uint32 name_id, Map* map, if (InstanceData* iData = map->GetInstanceData()) iData->OnObjectCreate(this); - // Check if GameObject is Large - if (GetGOInfo()->IsLargeGameObject()) + // set large visibility, skip if map already has same or better visibility (e.g. BG) + if (GetGOInfo()->IsLargeGameObject() && GetVisibilityData().GetVisibilityDistance() < VISIBILITY_DISTANCE_LARGE) GetVisibilityData().SetVisibilityDistanceOverride(VisibilityDistanceType::Large); + // set maximum visibility for some object types + if (GetGOInfo()->IsInfiniteGameObject() && !GetVisibilityData().IsVisibilityOverridden() && GetVisibilityData().GetVisibilityDistance() < MAX_VISIBILITY_DISTANCE) + GetVisibilityData().SetVisibilityDistanceOverride(VisibilityDistanceType::Infinite); return true; } diff --git a/src/game/Entities/GameObject.h b/src/game/Entities/GameObject.h index 23eb90935a9..4059cc5dce6 100644 --- a/src/game/Entities/GameObject.h +++ b/src/game/Entities/GameObject.h @@ -586,6 +586,17 @@ struct GameObjectInfo } } + bool IsInfiniteGameObject() const + { + switch (type) + { + case GAMEOBJECT_TYPE_DOOR: return true; + case GAMEOBJECT_TYPE_FLAGSTAND: return true; + case GAMEOBJECT_TYPE_FLAGDROP: return true; + default: return false; + } + } + bool IsServerOnly() const { switch (type) diff --git a/src/game/Entities/Item.cpp b/src/game/Entities/Item.cpp index 3bca00af7dd..c23d7beaf33 100644 --- a/src/game/Entities/Item.cpp +++ b/src/game/Entities/Item.cpp @@ -970,7 +970,7 @@ void Item::SetEnchantment(EnchantmentSlot slot, uint32 id, uint32 duration, uint return; Player* owner = GetOwner(); - if (slot < MAX_INSPECTED_ENCHANTMENT_SLOT) + if (slot < MAX_INSPECTED_ENCHANTMENT_SLOT && owner && owner->IsInWorld()) { if (uint32 oldEnchant = GetEnchantmentId(slot)) owner->SendEnchantmentLog(ObjectGuid(), GetEntry(), oldEnchant); @@ -1113,7 +1113,8 @@ void Item::SendUpdateSockets() for (uint32 i = SOCK_ENCHANTMENT_SLOT; i <= BONUS_ENCHANTMENT_SLOT; ++i) data << uint32(GetEnchantmentId(EnchantmentSlot(i))); - GetOwner()->SendDirectMessage(data); + if (GetOwner() && GetOwner()->IsInWorld()) + GetOwner()->SendDirectMessage(data); } // Though the client has the information in the item's data field, diff --git a/src/game/Entities/MiscHandler.cpp b/src/game/Entities/MiscHandler.cpp index 9d2fff91dfb..d9b31e5d862 100644 --- a/src/game/Entities/MiscHandler.cpp +++ b/src/game/Entities/MiscHandler.cpp @@ -158,7 +158,7 @@ void WorldSession::HandleWhoOpcode(WorldPacket& recv_data) { Player* pl = itr->second; - if (security == SEC_PLAYER) + if (security == SEC_PLAYER || !pl->IsGameMaster()) { // player can see member of other team only if CONFIG_BOOL_ALLOW_TWO_SIDE_WHO_LIST if (pl->GetTeam() != team && !allowTwoSideWhoList) diff --git a/src/game/Entities/Object.cpp b/src/game/Entities/Object.cpp index c2b00b8eec6..a8d1d5ce920 100644 --- a/src/game/Entities/Object.cpp +++ b/src/game/Entities/Object.cpp @@ -2618,6 +2618,9 @@ struct WorldObjectChangeAccumulator // send self fields changes in another way, otherwise // with new camera system when player's camera too far from player, camera wouldn't receive packets and changes from player if (i_object.isType(TYPEMASK_PLAYER)) +#ifdef ENABLE_PLAYERBOTS + if(((Player*)&i_object)->isRealPlayer()) +#endif i_object.BuildUpdateDataForPlayer((Player*)&i_object, i_updateDatas); } @@ -2626,7 +2629,11 @@ struct WorldObjectChangeAccumulator for (auto& iter : m) { Player* owner = iter.getSource()->GetOwner(); +#ifdef ENABLE_PLAYERBOTS + if (owner != &i_object && owner->isRealPlayer() && owner->HasAtClient(&i_object)) +#else if (owner != &i_object && owner->HasAtClient(&i_object)) +#endif i_object.BuildUpdateDataForPlayer(owner, i_updateDatas); } } diff --git a/src/game/Entities/ObjectVisibility.cpp b/src/game/Entities/ObjectVisibility.cpp index ef139d5d16c..f904e868837 100644 --- a/src/game/Entities/ObjectVisibility.cpp +++ b/src/game/Entities/ObjectVisibility.cpp @@ -44,8 +44,6 @@ VisibilityData::VisibilityData(WorldObject* owner) : m_visibilityDistanceOverrid void VisibilityData::SetVisibilityDistanceOverride(VisibilityDistanceType type) { MANGOS_ASSERT(type < VisibilityDistanceType::Max); - if (m_owner->GetTypeId() == TYPEID_PLAYER) - return; m_visibilityDistanceOverride = VisibilityDistances[AsUnderlyingType(type)]; } @@ -55,7 +53,7 @@ float VisibilityData::GetVisibilityDistance() const if (IsVisibilityOverridden()) return m_visibilityDistanceOverride; else - return m_owner->GetMap()->GetVisibilityDistance(); + return m_owner->GetMap() ? m_owner->GetMap()->GetVisibilityDistance() : DEFAULT_VISIBILITY_DISTANCE; } float VisibilityData::GetVisibilityDistanceFor(WorldObject* obj) const diff --git a/src/game/Entities/Pet.cpp b/src/game/Entities/Pet.cpp index c6457792d10..842d94508ef 100644 --- a/src/game/Entities/Pet.cpp +++ b/src/game/Entities/Pet.cpp @@ -245,7 +245,7 @@ bool Pet::LoadPetFromDB(Player* owner, Position const& spawnPos, uint32 petentry } // DK Permanent ghoul spell - if (spellInfo->HasAttribute(SPELL_ATTR_EX7_RECAST_ON_RESUMMON) && current && !forced) // must cast through spell in order to trigger correct ghoul CD + if (spellInfo && spellInfo->HasAttribute(SPELL_ATTR_EX7_RECAST_ON_RESUMMON) && current && !forced) // must cast through spell in order to trigger correct ghoul CD { Position pos = Pet::GetPetSpawnPosition(owner); owner->CastSpell(pos.x, pos.y, pos.z, summon_spell_id, TRIGGERED_IGNORE_GCD | TRIGGERED_IGNORE_COOLDOWNS); @@ -879,7 +879,7 @@ void Pet::Unsummon(PetSaveMode mode, Unit* owner /*= nullptr*/) } if (isControlled()) - if (owner->IsPlayer()) + if (owner && owner->IsPlayer()) static_cast(owner)->RemoveControllable(this); AddObjectToRemoveList(); @@ -2288,6 +2288,9 @@ void Pet::CastPetAuras(bool current) Unit* owner = GetOwner(); + if (!owner) + return; + for (PetAuraSet::const_iterator itr = owner->m_petAuras.begin(); itr != owner->m_petAuras.end();) { PetAura const* pa = *itr; diff --git a/src/game/Entities/PetitionsHandler.cpp b/src/game/Entities/PetitionsHandler.cpp index b364fe769d9..11edf3b2d6e 100644 --- a/src/game/Entities/PetitionsHandler.cpp +++ b/src/game/Entities/PetitionsHandler.cpp @@ -534,7 +534,11 @@ void WorldSession::HandlePetitionSignOpcode(WorldPacket& recv_data) // not allow sign another player from already sign player account result = CharacterDatabase.PQuery("SELECT playerguid FROM petition_sign WHERE player_account = '%u' AND petitionguid = '%u'", GetAccountId(), petitionLowGuid); +#ifdef ENABLE_PLAYERBOTS + if (result && !_player->GetPlayerbotAI()) +#else if (result) +#endif { delete result; WorldPacket data(SMSG_PETITION_SIGN_RESULTS, (8 + 8 + 4)); diff --git a/src/game/Entities/Player.cpp b/src/game/Entities/Player.cpp index c77142dfcd2..c061aa3e2b7 100644 --- a/src/game/Entities/Player.cpp +++ b/src/game/Entities/Player.cpp @@ -69,6 +69,7 @@ #include "World/WorldStateDefines.h" #include "World/WorldState.h" #include "Anticheat/Anticheat.hpp" +#include "AI/ScriptDevAI/scripts/custom/Transmogrification.h" #ifdef BUILD_PLAYERBOT #include "PlayerBot/Base/PlayerbotAI.h" @@ -76,6 +77,11 @@ #include "Config/Config.h" #endif +#ifdef ENABLE_PLAYERBOTS +#include "playerbot.h" +#include "PlayerbotAIConfig.h" +#endif + #include #define ZONE_UPDATE_INTERVAL (1*IN_MILLISECONDS) @@ -502,6 +508,10 @@ Player::Player(WorldSession* session): Unit(), m_taxiTracker(*this), m_mover(thi #ifdef BUILD_PLAYERBOT m_playerbotAI = 0; m_playerbotMgr = 0; +#endif +#ifdef ENABLE_PLAYERBOTS + m_playerbotAI = 0; + m_playerbotMgr = 0; #endif m_speakTime = 0; m_speakCount = 0; @@ -696,6 +706,11 @@ Player::Player(WorldSession* session): Unit(), m_taxiTracker(*this), m_mover(thi m_isDebuggingAreaTriggers = false; m_fishingSteps = 0; + +#ifdef ENABLE_PLAYERBOTS + m_playerbotAI = NULL; + m_playerbotMgr = NULL; +#endif } Player::~Player() @@ -744,6 +759,21 @@ Player::~Player() m_playerbotMgr = 0; } #endif + +#ifdef ENABLE_PLAYERBOTS + if (m_playerbotAI) { + { + delete m_playerbotAI; + } + m_playerbotAI = 0; + } + if (m_playerbotMgr) { + { + delete m_playerbotMgr; + } + m_playerbotMgr = 0; + } +#endif } void Player::CleanupsBeforeDelete() @@ -1633,7 +1663,10 @@ void Player::Update(const uint32 diff) { if (diff >= m_DetectInvTimer) { - HandleStealthedUnitsDetection(); +#ifdef ENABLE_PLAYERBOTS + if(isRealPlayer()) +#endif + HandleStealthedUnitsDetection(); m_DetectInvTimer = GetMap()->IsBattleGroundOrArena() ? 500 : 2000; } else @@ -1701,6 +1734,18 @@ void Player::Update(const uint32 diff) if (IsHasDelayedTeleport() && !m_semaphoreTeleport_Near) TeleportTo(m_teleport_dest, m_teleport_options); + // increase visibility of taxi flying characters for others + if (IsTaxiFlying() && sWorld.getConfig(CONFIG_BOOL_FAR_VISIBLE_TAXI)) + { + if (!GetVisibilityData().IsVisibilityOverridden()) + GetVisibilityData().SetVisibilityDistanceOverride(VisibilityDistanceType::Gigantic); + } + else + { + if (GetVisibilityData().IsVisibilityOverridden()) + GetVisibilityData().SetVisibilityDistanceOverride(VisibilityDistanceType::Normal); + } + #ifdef BUILD_PLAYERBOT if (m_playerbotAI) m_playerbotAI->UpdateAI(diff); @@ -1709,6 +1754,20 @@ void Player::Update(const uint32 diff) #endif } +#ifdef ENABLE_PLAYERBOTS +void Player::UpdateAI(const uint32 diff, bool minimal) +{ + if (m_playerbotAI) + { + m_playerbotAI->UpdateAI(diff, minimal); + } + if (m_playerbotMgr) + { + m_playerbotMgr->UpdateAI(diff); + } +} +#endif + void Player::Heartbeat() { Unit::Heartbeat(); @@ -4664,6 +4723,7 @@ void Player::DeleteFromDB(ObjectGuid playerguid, uint32 accountId, bool updateRe CharacterDatabase.PExecute("DELETE FROM character_equipmentsets WHERE guid = '%u'", lowguid); CharacterDatabase.PExecute("DELETE FROM guild_eventlog WHERE PlayerGuid1 = '%u' OR PlayerGuid2 = '%u'", lowguid, lowguid); CharacterDatabase.PExecute("DELETE FROM guild_bank_eventlog WHERE PlayerGuid = '%u'", lowguid); + CharacterDatabase.PExecute("DELETE FROM character_armory_feed WHERE guid = '%u'", lowguid); CharacterDatabase.CommitTransaction(); break; } @@ -5208,7 +5268,10 @@ uint32 Player::DurabilityRepair(uint16 pos, bool cost, float discountMod, bool g return TotalCost; } else + { ModifyMoney(-int32(costs)); + TotalCost += costs; + } } } @@ -11369,6 +11432,18 @@ Item* Player::StoreItem(ItemPosCountVec const& dest, Item* pItem, bool update) lastItem = _StoreItem(pos, pItem, count, true, update); } + /* World of Warcraft Armory */ + if (lastItem) + { + ItemPrototype const* pProto = lastItem->GetProto(); + if (pProto && pProto->Quality > 2 && pProto->Flags != 2048 && (pProto->Class == ITEM_CLASS_WEAPON || pProto->Class == ITEM_CLASS_ARMOR)) + { + if (lastItem->GetOwner()) + lastItem->GetOwner()->CreateWowarmoryFeed(2, pProto->ItemId, lastItem->GetGUIDLow(), pProto->Quality); + } + } + /* World of Warcraft Armory */ + GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_OWN_ITEM, entry); return lastItem; } @@ -11485,6 +11560,16 @@ Item* Player::EquipNewItem(uint16 pos, uint32 item, bool update) { ItemAddedQuestCheck(item, 1); GetAchievementMgr().UpdateAchievementCriteria(ACHIEVEMENT_CRITERIA_TYPE_RECEIVE_EPIC_ITEM, item, 1); + + /* World of Warcraft Armory */ + ItemPrototype const* pProto = pItem->GetProto(); + if (pProto && pProto->Quality > 2 && pProto->Flags != 2048 && (pProto->Class == ITEM_CLASS_WEAPON || pProto->Class == ITEM_CLASS_ARMOR)) + { + if (pItem->GetOwner()) + pItem->GetOwner()->CreateWowarmoryFeed(2, item, pItem->GetGUIDLow(), pProto->Quality); + } + /* World of Warcraft Armory */ + return EquipItem(pos, pItem, update); } @@ -11625,6 +11710,9 @@ void Player::SetVisibleItemSlot(uint8 slot, Item* pItem) SetUInt32Value(PLAYER_VISIBLE_ITEM_1_ENTRYID + (slot * 2), pItem->GetEntry()); SetUInt16Value(PLAYER_VISIBLE_ITEM_1_ENCHANTMENT + (slot * 2), 0, pItem->GetEnchantmentId(PERM_ENCHANTMENT_SLOT)); SetUInt16Value(PLAYER_VISIBLE_ITEM_1_ENCHANTMENT + (slot * 2), 1, pItem->GetEnchantmentId(TEMP_ENCHANTMENT_SLOT)); + + if (uint32 entry = sTransmogrification->GetFakeEntry(pItem->GetObjectGuid())) + SetUInt32Value(PLAYER_VISIBLE_ITEM_1_ENTRYID + (slot * 2), entry); } else { @@ -11767,6 +11855,7 @@ void Player::MoveItemFromInventory(uint8 bag, uint8 slot, bool update) it->RemoveFromWorld(); it->DestroyForPlayer(this); } + sTransmogrification->DeleteFakeFromDB(it->GetObjectGuid()); } } @@ -14329,7 +14418,12 @@ void Player::AddQuest(Quest const* pQuest, Object* questGiver) questStatusData.uState = QUEST_CHANGED; // quest accept scripts +#ifdef ENABLE_PLAYERBOTS + // quest accept scripts + if (questGiver && this != questGiver) +#else if (questGiver) +#endif { switch (questGiver->GetTypeId()) { @@ -14577,15 +14671,19 @@ void Player::RewardQuest(Quest const* pQuest, uint32 reward, Object* questGiver, { switch (questGiver->GetTypeId()) { - case TYPEID_UNIT: - handled = sScriptDevAIMgr.OnQuestRewarded(this, (Creature*)questGiver, pQuest); - break; - case TYPEID_GAMEOBJECT: - handled = sScriptDevAIMgr.OnQuestRewarded(this, (GameObject*)questGiver, pQuest); - break; + case TYPEID_UNIT: + handled = sScriptDevAIMgr.OnQuestRewarded(this, (Creature*)questGiver, pQuest); + break; + case TYPEID_GAMEOBJECT: + handled = sScriptDevAIMgr.OnQuestRewarded(this, (GameObject*)questGiver, pQuest); + break; } +#ifdef ENABLE_PLAYERBOTS + if (this != questGiver && !handled && pQuest->GetQuestCompleteScript() != 0) +#else if (!handled && pQuest->GetQuestCompleteScript() != 0) +#endif GetMap()->ScriptsStart(SCRIPT_TYPE_QUEST_END, pQuest->GetQuestCompleteScript(), questGiver, this, Map::SCRIPT_EXEC_PARAM_UNIQUE_BY_SOURCE); } @@ -16275,6 +16373,9 @@ bool Player::LoadFromDB(ObjectGuid guid, SqlQueryHolder* holder) return false; } + // Cleanup old Wowarmory feeds + InitWowarmoryFeeds(); + // overwrite possible wrong/corrupted guid SetGuidValue(OBJECT_FIELD_GUID, guid); @@ -18353,11 +18454,103 @@ void Player::SaveToDB() if (m_session->isLogingOut() || !sWorld.getConfig(CONFIG_BOOL_STATS_SAVE_ONLY_ON_LOGOUT)) _SaveStats(); + /* World of Warcraft Armory */ + // Place this code AFTER CharacterDatabase.CommitTransaction(); to avoid some character saving errors. + // Wowarmory feeds + if (!m_wowarmory_feeds.empty()) + { + std::ostringstream sWowarmory; + if (m_wowarmory_feeds.size() > 20) + { + uint32 numPerTime = 20; + uint32 counter = 1; + for (WowarmoryFeeds::iterator iter = m_wowarmory_feeds.begin(); iter < m_wowarmory_feeds.end(); ++iter) + { + // guid type data date counter difficulty item_guid item_quality + sWowarmory << "(" << (*iter).guid << ", " << (*iter).type << ", " << (*iter).data << ", " << uint64((*iter).date) << ", " << (*iter).counter << ", " << uint32((*iter).difficulty) << ", " << uint32((*iter).item_guid) << ", " << uint32((*iter).item_quality) << ")"; + if (iter != m_wowarmory_feeds.end() - 1 && counter < numPerTime) + sWowarmory << ","; + + if (counter >= numPerTime || iter == m_wowarmory_feeds.end() - 1) + { + std::ostringstream sWowarmoryPartial; + sWowarmoryPartial << "INSERT IGNORE INTO character_armory_feed (guid,type,data,date,counter,difficulty,item_guid,item_quality) VALUES "; + sWowarmoryPartial << sWowarmory.str().c_str(); + CharacterDatabase.PExecute(sWowarmoryPartial.str().c_str()); + sWowarmory.str(""); + sWowarmory.clear(); + counter = 1; + } + + ++counter; + } + } + else + { + sWowarmory << "INSERT IGNORE INTO character_armory_feed (guid,type,data,date,counter,difficulty,item_guid,item_quality) VALUES "; + for (WowarmoryFeeds::iterator iter = m_wowarmory_feeds.begin(); iter < m_wowarmory_feeds.end(); ++iter) + { + // guid type data date counter difficulty item_guid item_quality + sWowarmory << "(" << (*iter).guid << ", " << (*iter).type << ", " << (*iter).data << ", " << uint64((*iter).date) << ", " << (*iter).counter << ", " << uint32((*iter).difficulty) << ", " << uint32((*iter).item_guid) << ", " << uint32((*iter).item_quality) << ")"; + if (iter != m_wowarmory_feeds.end() - 1) + sWowarmory << ","; + } + CharacterDatabase.PExecute(sWowarmory.str().c_str()); + } + // Clear old saved feeds from storage - they are not required for server core. + InitWowarmoryFeeds(); + } + /* World of Warcraft Armory */ + // save pet (hunter pet level and experience and all type pets health/mana except priest pet). if (Pet* pet = GetPet()) pet->SavePetToDB(PET_SAVE_AS_CURRENT, this); } +void Player::InitWowarmoryFeeds() +{ + // Clear feeds + m_wowarmory_feeds.clear(); +} + +void Player::CreateWowarmoryFeed(uint32 type, uint32 data, uint32 item_guid, uint32 item_quality) +{ + if (GetGUIDLow() == 0) + { + sLog.outError("[Wowarmory]: player is not initialized, unable to create log entry!"); + return; + } + + /* + 1 - TYPE_ACHIEVEMENT_FEED + 2 - TYPE_ITEM_FEED + 3 - TYPE_BOSS_FEED + */ + + if (type <= 0 || type > 3) + { + sLog.outError("[Wowarmory]: unknown feed type: %d, ignore.", type); + return; + } + + if (data == 0) + { + sLog.outError("[Wowarmory]: empty data (GUID: %u), ignore.", GetGUIDLow()); + return; + } + + WowarmoryFeedEntry feed; + feed.guid = GetGUIDLow(); + feed.type = type; + feed.data = data; + feed.difficulty = type == 3 ? GetMap()->GetDifficulty() : 0; + feed.item_guid = item_guid; + feed.item_quality = item_quality; + feed.counter = 0; + feed.date = time(NULL); + m_wowarmory_feeds.push_back(feed); +} + // fast save function for item/money cheating preventing - save only inventory and money state void Player::SaveInventoryAndGoldToDB() { @@ -18559,11 +18752,18 @@ void Player::_SaveInventory() m_items[i]->FSetState(ITEM_NEW); } +#ifdef ENABLE_PLAYERBOTS + if (!GetPlayerbotAI()) // hackfix for crash during save + { +#endif // update enchantment durations for (EnchantDurationList::const_iterator itr = m_enchantDuration.begin(); itr != m_enchantDuration.end(); ++itr) { itr->item->SetEnchantmentDuration(itr->slot, itr->leftduration); } +#ifdef ENABLE_PLAYERBOTS + } +#endif // if no changes if (m_itemUpdateQueue.empty()) return; @@ -18936,8 +19136,14 @@ void Player::_SaveStats() stmt = CharacterDatabase.CreateStatement(insertStats, "INSERT INTO character_stats (guid, maxhealth, maxpower1, maxpower2, maxpower3, maxpower4, maxpower5, maxpower6, maxpower7, " "strength, agility, stamina, intellect, spirit, armor, resHoly, resFire, resNature, resFrost, resShadow, resArcane, " - "blockPct, dodgePct, parryPct, critPct, rangedCritPct, spellCritPct, attackPower, rangedAttackPower, spellPower) " - "VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + "blockPct, dodgePct, parryPct, critPct, rangedCritPct, spellCritPct, attackPower, rangedAttackPower, spellPower, " + "holyCritPct, fireCritPct, natureCritPct, frostCritPct, shadowCritPct, arcaneCritPct, " + "attackPowerMod, rangedAttackPowerMod, holyDamage, fireDamage, natureDamage, frostDamage, shadowDamage, arcaneDamage, healBonus, " + "defenseRating, dodgeRating, parryRating, blockRating, resilience, " + "meleeHitRating, rangedHitRating, spellHitRating, meleeCritRating, rangedCritRating, spellCritRating, meleeHasteRating, rangedHasteRating, spellHasteRating, " + "expertise, expertiseRating, " + "mainHandDamageMin, mainHandDamageMax, mainHandSpeed, offHandDamageMin, offHandDamageMax, offHandSpeed, rangedDamageMin, rangedDamageMax, rangedSpeed, manaRegen, manaInterrupt, pvpRank) " + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); stmt.addUInt32(GetGUIDLow()); stmt.addUInt32(GetMaxHealth()); @@ -18958,6 +19164,74 @@ void Player::_SaveStats() stmt.addUInt32(GetUInt32Value(UNIT_FIELD_RANGED_ATTACK_POWER)); stmt.addUInt32(GetBaseSpellPowerBonus()); + // new stats + // spell crits + for (int i = SPELL_SCHOOL_HOLY; i < MAX_SPELL_SCHOOL; ++i) + stmt.addFloat(m_modSpellCritChance[i]); + + // attack power mods + stmt.addUInt32(GetUInt32Value(UNIT_FIELD_ATTACK_POWER_MODS)); + stmt.addUInt32(GetUInt32Value(UNIT_FIELD_RANGED_ATTACK_POWER_MODS)); + + // spell damage + for (int i = SPELL_SCHOOL_HOLY; i < MAX_SPELL_SCHOOL; ++i) + stmt.addInt32(SpellBaseDamageBonusDone(SpellSchoolMask(1 << i))); + + // healing bonus + stmt.addInt32(SpellBaseHealingBonusDone(SPELL_SCHOOL_MASK_ALL)); + + // defense rating + stmt.addInt32(GetUInt32Value(PLAYER_FIELD_COMBAT_RATING_1 + CR_DEFENSE_SKILL)); + + // dodge bonus + stmt.addInt32(GetUInt32Value(PLAYER_FIELD_COMBAT_RATING_1 + CR_DODGE)); + + // parry Rating + stmt.addInt32(GetUInt32Value(PLAYER_FIELD_COMBAT_RATING_1 + CR_PARRY)); + + // block rating + stmt.addInt32(GetUInt32Value(PLAYER_FIELD_COMBAT_RATING_1 + CR_BLOCK)); + + // resilience + stmt.addInt32(GetUInt32Value(PLAYER_FIELD_COMBAT_RATING_1 + CR_CRIT_TAKEN_MELEE)); + + // ratings + stmt.addInt32(GetUInt32Value(PLAYER_FIELD_COMBAT_RATING_1 + CR_HIT_MELEE)); + stmt.addInt32(GetUInt32Value(PLAYER_FIELD_COMBAT_RATING_1 + CR_HIT_RANGED)); + stmt.addInt32(GetUInt32Value(PLAYER_FIELD_COMBAT_RATING_1 + CR_HIT_SPELL)); + stmt.addInt32(GetUInt32Value(PLAYER_FIELD_COMBAT_RATING_1 + CR_CRIT_MELEE)); + stmt.addInt32(GetUInt32Value(PLAYER_FIELD_COMBAT_RATING_1 + CR_CRIT_RANGED)); + stmt.addInt32(GetUInt32Value(PLAYER_FIELD_COMBAT_RATING_1 + CR_CRIT_SPELL)); + + stmt.addInt32(GetUInt32Value(PLAYER_FIELD_COMBAT_RATING_1 + CR_HASTE_MELEE)); + stmt.addInt32(GetUInt32Value(PLAYER_FIELD_COMBAT_RATING_1 + CR_HASTE_RANGED)); + stmt.addInt32(GetUInt32Value(PLAYER_FIELD_COMBAT_RATING_1 + CR_HASTE_SPELL)); + stmt.addInt32(GetUInt32Value(PLAYER_EXPERTISE)); + stmt.addInt32(GetUInt32Value(PLAYER_FIELD_COMBAT_RATING_1 + CR_EXPERTISE)); + + // weapon damage + // main hand + stmt.addFloat(GetFloatValue(UNIT_FIELD_MINDAMAGE)); + stmt.addFloat(GetFloatValue(UNIT_FIELD_MAXDAMAGE)); + stmt.addFloat(GetAPMultiplier(BASE_ATTACK, false)); + + // off hand + stmt.addFloat(GetFloatValue(UNIT_FIELD_MINOFFHANDDAMAGE)); + stmt.addFloat(GetFloatValue(UNIT_FIELD_MAXOFFHANDDAMAGE)); + stmt.addFloat(GetAPMultiplier(OFF_ATTACK, false)); + + // ranged + stmt.addFloat(GetFloatValue(UNIT_FIELD_MINRANGEDDAMAGE)); + stmt.addFloat(GetFloatValue(UNIT_FIELD_MAXRANGEDDAMAGE)); + stmt.addFloat(GetAPMultiplier(OFF_ATTACK, false)); + + // mana regen + stmt.addFloat(0); + stmt.addFloat(0); + + // pvp rank + stmt.addInt32(GetHighestPvPRankIndex()); + stmt.Execute(); } @@ -20027,9 +20301,19 @@ bool Player::ActivateTaxiPathTo(std::vector const& nodes, Creature* npc // prevent stealth flight // RemoveAurasWithInterruptFlags(AURA_INTERRUPT_FLAG_TALK); - GetSession()->SendActivateTaxiReply(ERR_TAXIOK); - - GetMotionMaster()->MoveTaxi(); + if (sWorld.getConfig(CONFIG_BOOL_INSTANT_TAXI)) + { + TaxiNodesEntry const* lastnode = sTaxiNodesStore.LookupEntry(nodes[nodes.size() - 1]); + m_taxiTracker.Clear(true); + TeleportTo(lastnode->map_id, lastnode->x, lastnode->y, lastnode->z, GetOrientation()); + return false; + } + else + { + GetSession()->SendActivateTaxiReply(ERR_TAXIOK); + + GetMotionMaster()->MoveTaxi(); + } return true; } @@ -22919,17 +23203,6 @@ void Player::RestoreBaseRune(uint8 index) return false; }; - for (auto itr = auras.begin(); itr != auras.end();) - { - if (criteria(*itr)) - itr = auras.erase(itr); - else - ++itr; - } - - if (!auras.empty()) - return; - ConvertRune(index, GetBaseRune(index)); if (removeList.empty()) @@ -22941,6 +23214,8 @@ void Player::RestoreBaseRune(uint8 index) uint8 itr = 0; for (; itr < MAX_RUNES; ++itr) { + RemoveRuneConvertAura(itr, storedAura); + if (m_runes->runes[itr].ConvertAuras.find(storedAura) != m_runes->runes[itr].ConvertAuras.end()) break; } @@ -23164,6 +23439,152 @@ void Player::learnSpellHighRank(uint32 spellid) sSpellMgr.doForHighRanks(spellid, worker); } +void Player::learnClassLevelSpells(bool includeHighLevelQuestRewards) +{ + ChrClassesEntry const* clsEntry = sChrClassesStore.LookupEntry(getClass()); + if (!clsEntry) + return; + uint32 family = clsEntry->spellfamily; + + // special cases which aren't sourced from trainers and normally require quests to obtain - added here for convenience + ObjectMgr::QuestMap const& qTemplates = sObjectMgr.GetQuestTemplates(); + for (const auto& qTemplate : qTemplates) + { + Quest const* quest = qTemplate.second; + if (!quest) + continue; + + // only class quests player could do + if (quest->GetRequiredClasses() == 0 || !SatisfyQuestClass(quest, false) || !SatisfyQuestRace(quest, false) || !SatisfyQuestLevel(quest, false)) + continue; + + // custom filter for scripting purposes + if (!includeHighLevelQuestRewards && quest->GetMinLevel() >= 60) + continue; + + learnQuestRewardedSpells(quest); + } + + // learn trainer spells + for (uint32 id = 0; id < sCreatureStorage.GetMaxEntry(); ++id) + { + CreatureInfo const* co = sCreatureStorage.LookupEntry(id); + if (!co) + continue; + + if (co->TrainerType != TRAINER_TYPE_CLASS) + continue; + + if (co->TrainerType == TRAINER_TYPE_CLASS && co->TrainerClass != getClass()) + continue; + + uint32 trainerId = co->TrainerTemplateId; + if (!trainerId) + trainerId = co->Entry; + + TrainerSpellData const* trainer_spells = sObjectMgr.GetNpcTrainerTemplateSpells(trainerId); + if (!trainer_spells) + trainer_spells = sObjectMgr.GetNpcTrainerSpells(trainerId); + + if (!trainer_spells) + continue; + + for (TrainerSpellMap::const_iterator itr = trainer_spells->spellList.begin(); itr != trainer_spells->spellList.end(); ++itr) + { + TrainerSpell const* tSpell = &itr->second; + + if (!tSpell) + continue; + + // skip wrong class/race skills + if (!IsSpellFitByClassAndRace(tSpell->learnedSpell)) + continue; + + uint32 reqLevel = 0; + IsSpellFitByClassAndRace(tSpell->learnedSpell, &reqLevel); + + if (tSpell->conditionId && !sObjectMgr.IsConditionSatisfied(tSpell->conditionId, this, GetMap(), this, CONDITION_FROM_TRAINER)) + continue; + + // skip spells with first rank learned as talent (and all talents then also) + uint32 first_rank = sSpellMgr.GetFirstSpellInChain(tSpell->learnedSpell); + reqLevel = tSpell->isProvidedReqLevel ? tSpell->reqLevel : std::max(reqLevel, tSpell->reqLevel); + bool isValidTalent = GetTalentSpellCost(first_rank) && HasSpell(first_rank) && reqLevel <= GetLevel(); + + TrainerSpellState state = GetTrainerSpellState(tSpell, reqLevel); + if (state != TRAINER_SPELL_GREEN && !isValidTalent) + continue; + + SpellEntry const* proto = sSpellTemplate.LookupEntry(tSpell->learnedSpell); + if (!proto) + continue; + + // fix activate state for non-stackable low rank (and find next spell for !active case) + if (uint32 nextId = sSpellMgr.GetSpellBookSuccessorSpellId(proto->Id)) + { + if (HasSpell(nextId)) + { + // high rank already known so this must !active + continue; + } + } + + // skip other spell families (minus a few exceptions) + if (proto->SpellFamilyName != family) + { + SkillLineAbilityMapBounds bounds = sSpellMgr.GetSkillLineAbilityMapBoundsBySpellId(tSpell->learnedSpell); + if (bounds.first == bounds.second) + continue; + + SkillLineAbilityEntry const* skillInfo = bounds.first->second; + if (!skillInfo) + continue; + + switch (skillInfo->skillId) + { + case SKILL_SUBTLETY: + //case SKILL_POISONS: + case SKILL_BEAST_MASTERY: + case SKILL_SURVIVAL: + case SKILL_DEFENSE: + case SKILL_DUAL_WIELD: + case SKILL_FERAL_COMBAT: + case SKILL_PROTECTION: + //case SKILL_BEAST_TRAINING: + case SKILL_PLATE_MAIL: + case SKILL_DEMONOLOGY: + case SKILL_ENHANCEMENT: + case SKILL_MAIL: + case SKILL_HOLY2: + case SKILL_LOCKPICKING: + break; + default: + continue; + } + } + + // skip broken spells + if (!SpellMgr::IsSpellValid(proto, this, false)) + continue; + + if (tSpell->learnedSpell) + { + bool learned = false; + for (int j = 0; j < 3; ++j) + { + if (proto->Effect[j] == SPELL_EFFECT_LEARN_SPELL) + { + uint32 learnedSpell = proto->EffectTriggerSpell[j]; + learnSpell(learnedSpell, false); + learned = true; + } + } + if (!learned) learnSpell(tSpell->learnedSpell, false); + } + } + } +} + void Player::_LoadSkills(QueryResult* result) { // 0 1 2 diff --git a/src/game/Entities/Player.h b/src/game/Entities/Player.h index 6e349cb308f..7ffa55e176a 100644 --- a/src/game/Entities/Player.h +++ b/src/game/Entities/Player.h @@ -65,6 +65,11 @@ struct FactionTemplateEntry; #include "PlayerBot/Base/PlayerbotAI.h" #endif +#ifdef ENABLE_PLAYERBOTS +class PlayerbotAI; +class PlayerbotMgr; +#endif + struct AreaTrigger; typedef std::deque PlayerMails; @@ -1060,6 +1065,22 @@ class TradeData ObjectGuid m_items[TRADE_SLOT_COUNT]; // traded itmes from m_player side including non-traded slot }; +/* World of Warcraft Armory */ +struct WowarmoryFeedEntry +{ + uint32 guid; // Player GUID + time_t date; // Log date + uint32 type; // TYPE_ACHIEVEMENT_FEED, TYPE_ITEM_FEED, TYPE_BOSS_FEED + uint32 data; // TYPE_ITEM_FEED: item_entry, TYPE_BOSS_FEED: creature_entry + uint32 item_guid; // Can be 0 + uint32 item_quality; // Can be 0 + uint8 difficulty; // Can be 0 + int counter; // Can be 0 +}; + +typedef std::vector WowarmoryFeeds; +/* World of Warcraft Armory */ + class Player : public Unit { friend class WorldSession; @@ -1101,6 +1122,10 @@ class Player : public Unit void Update(const uint32 diff) override; void Heartbeat() override; +#ifdef ENABLE_PLAYERBOTS + void UpdateAI(const uint32 diff, bool minimal = false); +#endif + static bool BuildEnumData(QueryResult* result, WorldPacket& p_data); void SendInitialPacketsBeforeAddToMap(); @@ -1581,6 +1606,10 @@ class Player : public Unit bool LoadFromDB(ObjectGuid guid, SqlQueryHolder* holder); +#ifdef ENABLE_PLAYERBOTS + bool MinimalLoadFromDB(QueryResult *result, uint32 guid); +#endif + static uint32 GetZoneIdFromDB(ObjectGuid guid); static uint32 GetLevelFromDB(ObjectGuid guid); static bool LoadPositionFromDB(ObjectGuid guid, uint32& mapid, float& x, float& y, float& z, float& o, bool& in_flight); @@ -1715,6 +1744,7 @@ class Player : public Unit void learnQuestRewardedSpells(); void learnQuestRewardedSpells(Quest const* quest); void learnSpellHighRank(uint32 spellid); + void learnClassLevelSpells(bool includeHighLevelQuestRewards = false); uint32 GetFreeTalentPoints() const { return GetUInt32Value(PLAYER_CHARACTER_POINTS1); } void SetFreeTalentPoints(uint32 points) { SetUInt32Value(PLAYER_CHARACTER_POINTS1, points); } @@ -2407,6 +2437,12 @@ class Player : public Unit void SendCinematicStart(uint32 CinematicSequenceId); void SendMovieStart(uint32 MovieId) const; + /* World of Warcraft Armory */ + void CreateWowarmoryFeed(uint32 type, uint32 data, uint32 item_guid, uint32 item_quality); + void InitWowarmoryFeeds(); + WowarmoryFeeds m_wowarmory_feeds; + /* World of Warcraft Armory */ + /*********************************************************/ /*** INSTANCE SYSTEM ***/ /*********************************************************/ @@ -2513,6 +2549,17 @@ class Player : public Unit bool IsInDuel() const { return duel && duel->startTime != 0; } #endif +#ifdef ENABLE_PLAYERBOTS + //EquipmentSets& GetEquipmentSets() { return m_EquipmentSets; } + void SetPlayerbotAI(PlayerbotAI* ai) { assert(!m_playerbotAI && !m_playerbotMgr); m_playerbotAI = ai; } + PlayerbotAI* GetPlayerbotAI() { return m_playerbotAI; } + void SetPlayerbotMgr(PlayerbotMgr* mgr) { assert(!m_playerbotAI && !m_playerbotMgr); m_playerbotMgr = mgr; } + PlayerbotMgr* GetPlayerbotMgr() { return m_playerbotMgr; } + void SetBotDeathTimer() { m_deathTimer = 0; } + bool isRealPlayer() { return m_session->GetRemoteAddress() != "disconnected/bot"; } + //PlayerTalentMap& GetTalentMap(uint8 spec) { return m_talents[spec]; } +#endif + // function used for raise ally spell bool IsGhouled() const { return m_isGhouled; } void SetGhouled(bool enable) { m_isGhouled = enable; } @@ -2867,6 +2914,11 @@ class Player : public Unit PlayerbotMgr* m_playerbotMgr; #endif +#ifdef ENABLE_PLAYERBOTS + PlayerbotAI* m_playerbotAI; + PlayerbotMgr* m_playerbotMgr; +#endif + // Homebind coordinates uint32 m_homebindMapId; uint16 m_homebindAreaId; diff --git a/src/game/Entities/Unit.cpp b/src/game/Entities/Unit.cpp index e7f40931c5a..71adb101026 100644 --- a/src/game/Entities/Unit.cpp +++ b/src/game/Entities/Unit.cpp @@ -56,6 +56,12 @@ #include "Metric/Metric.h" #endif +// Playerbots +#ifdef ENABLE_PLAYERBOTS +#include "playerbot.h" +#include "GuildTaskMgr.h" +#endif + #include #include #include @@ -1408,6 +1414,12 @@ void Unit::JustKilledCreature(Unit* killer, Creature* victim, Player* responsibl if (BattleGround* bg = responsiblePlayer->GetBattleGround()) bg->HandleKillUnit(victim, responsiblePlayer); +#ifdef ENABLE_PLAYERBOTS + // Guild Task check + if(responsiblePlayer) + sGuildTaskMgr.CheckKillTask(responsiblePlayer, victim); +#endif + // Notify the outdoor pvp script if (OutdoorPvP* outdoorPvP = sOutdoorPvPMgr.GetScript(responsiblePlayer ? responsiblePlayer->GetCachedZoneId() : victim->GetZoneId())) { @@ -1440,6 +1452,7 @@ void Unit::JustKilledCreature(Unit* killer, Creature* victim, Player* responsibl if (map->IsRaidOrHeroicDungeon() && victim->GetCreatureInfo()->ExtraFlags & CREATURE_EXTRA_FLAG_INSTANCE_BIND) { static_cast(map)->PermBindAllPlayers(creditedPlayer); + creditedPlayer->CreateWowarmoryFeed(3, victim->GetCreatureInfo()->Entry, 0, 0); } static_cast(map)->GetPersistanceState()->UpdateEncounterState(ENCOUNTER_CREDIT_KILL_CREATURE, victim->GetEntry()); } @@ -6439,6 +6452,7 @@ void Unit::RemoveNotOwnTrackedTargetAuras(uint32 newPhase) void Unit::RemoveSpellAuraHolder(SpellAuraHolder* holder, AuraRemoveMode mode) { + MANGOS_ASSERT(holder); MANGOS_ASSERT(!holder->IsDeleted()); // Statue unsummoned at holder remove @@ -6523,7 +6537,8 @@ void Unit::RemoveAura(Aura* Aur, AuraRemoveMode mode) // remove from list before mods removing (prevent cyclic calls, mods added before including to aura list - use reverse order) if (Aur->GetModifier()->m_auraname < TOTAL_AURAS) { - m_modAuras[Aur->GetModifier()->m_auraname].remove(Aur); + if (std::find(m_modAuras[Aur->GetModifier()->m_auraname].begin(), m_modAuras[Aur->GetModifier()->m_auraname].end(), Aur) != m_modAuras[Aur->GetModifier()->m_auraname].end()) + m_modAuras[Aur->GetModifier()->m_auraname].remove(Aur); } // Set remove mode @@ -8705,7 +8720,7 @@ uint32 Unit::SpellDamageBonusTaken(Unit* caster, SpellSchoolMask schoolMask, Spe AuraList const& mOwnerTaken = GetAurasByType(SPELL_AURA_MOD_DAMAGE_FROM_CASTER); for (auto i : mOwnerTaken) { - if (i->GetCasterGuid() == caster->GetObjectGuid() && i->isAffectedOnSpell(spellProto)) + if (caster && i->GetCasterGuid() == caster->GetObjectGuid() && i->isAffectedOnSpell(spellProto)) TakenTotalMod *= (i->GetModifier()->m_amount + 100.0f) / 100.0f; } diff --git a/src/game/Entities/Unit.h b/src/game/Entities/Unit.h index 82907b03075..167d40e644a 100644 --- a/src/game/Entities/Unit.h +++ b/src/game/Entities/Unit.h @@ -1410,6 +1410,7 @@ class Unit : public WorldObject void SetMaxHealth(uint32 val); void SetHealthPercent(float percent); int32 ModifyHealth(int32 dVal); + void SetFullHealth() { SetHealth(GetMaxHealth()); } float OCTRegenHPPerSpirit() const; float OCTRegenMPPerSpirit() const; diff --git a/src/game/Grids/GridNotifiers.cpp b/src/game/Grids/GridNotifiers.cpp index 6ec1f1db5ef..4f95e2fe2f8 100644 --- a/src/game/Grids/GridNotifiers.cpp +++ b/src/game/Grids/GridNotifiers.cpp @@ -41,6 +41,10 @@ void VisibleChangesNotifier::Visit(CameraMapType& m) void VisibleNotifier::Notify() { Player& player = *i_camera.GetOwner(); +#ifdef ENABLE_PLAYERBOTS + if (!player.isRealPlayer()) + return; +#endif // at this moment i_clientGUIDs have guids that not iterate at grid level checks // but exist one case when this possible and object not out of range: transports if (GenericTransport* transport = player.GetTransport()) diff --git a/src/game/Groups/Group.h b/src/game/Groups/Group.h index 26a71116a05..03073bdddbd 100644 --- a/src/game/Groups/Group.h +++ b/src/game/Groups/Group.h @@ -297,6 +297,10 @@ class Group BoundInstancesMap& GetBoundInstances(Difficulty difficulty) { return m_boundInstances[difficulty]; } LFGData& GetLfgData() { return m_lfgData; } + +#ifdef ENABLE_PLAYERBOTS + ObjectGuid GetTargetIcon(int index) { return m_targetIcons[index]; } +#endif protected: bool _addMember(ObjectGuid guid, const char* name, bool isAssistant = false); diff --git a/src/game/Maps/Map.cpp b/src/game/Maps/Map.cpp index 60bc48d4c51..56fa2307aca 100644 --- a/src/game/Maps/Map.cpp +++ b/src/game/Maps/Map.cpp @@ -48,6 +48,11 @@ #include "Metric/Metric.h" #endif +#ifdef ENABLE_PLAYERBOTS +#include "playerbot.h" +#include "PlayerbotAIConfig.h" +#endif + Map::~Map() { UnloadAll(true); @@ -208,7 +213,7 @@ Map::Map(uint32 id, time_t expiry, uint32 InstanceId, uint8 SpawnMode) m_activeNonPlayersIter(m_activeNonPlayers.end()), m_onEventNotifiedIter(m_onEventNotifiedObjects.end()), i_gridExpiry(expiry), m_TerrainData(sTerrainMgr.LoadTerrain(id)), i_data(nullptr), i_script_id(0), m_transportsIterator(m_transports.begin()), m_defaultLight(GetDefaultMapLight(id)), m_spawnManager(*this), - m_variableManager(this) + m_variableManager(this), m_activeAreasTimer(0), hasRealPlayers(false) { m_weatherSystem = new WeatherSystem(this); } @@ -488,6 +493,242 @@ bool Map::Add(Player* player) player->GetViewPoint().Event_AddedToWorld(&(*grid)(cell.CellX(), cell.CellY())); UpdateObjectVisibility(player, cell, p); +//Start Solocraft Functions + if (sWorld.getConfig(CONFIG_BOOL_SOLOCRAFT_ENABLED)) + { + //Get Config Values + //Balancing + SoloCraftDebuffEnable = sWorld.getConfig(CONFIG_BOOL_SOLOCRAFT_DEBUFF_ENABLE); + SoloCraftSpellMult = sWorld.getConfig(CONFIG_FLOAT_SOLOCRAFT_SPELLPOWER_MULT); + SoloCraftStatsMult = sWorld.getConfig(CONFIG_FLOAT_SOLOCRAFT_STATS_MULT); + //Level Thresholds + SolocraftLevelDiff = sWorld.getConfig(CONFIG_UINT32_SOLOCRAFT_MAX_LEVEL_DIFF); + //Default Value + SolocraftDungeonLevel = sWorld.getConfig(CONFIG_UINT32_DUNGEON_LEVEL); + //Dungeon Level + dungeons = + { + //Classic Instances + {33, sWorld.getConfig(CONFIG_UINT32_SHADOWFANGKEEP_LEVEL) }, + {34, sWorld.getConfig(CONFIG_UINT32_STOCKADES_LEVEL) }, + {36, sWorld.getConfig(CONFIG_UINT32_DEADMINES_LEVEL) }, + {43, sWorld.getConfig(CONFIG_UINT32_WAILINGCAVERNS_LEVEL) }, + {47, sWorld.getConfig(CONFIG_UINT32_RAZORFENKRAULINSTANCE_LEVEL) }, + {48, sWorld.getConfig(CONFIG_UINT32_BLACKFATHOM_LEVEL) }, + {70, sWorld.getConfig(CONFIG_UINT32_ULDAMAN_LEVEL) }, + {90, sWorld.getConfig(CONFIG_UINT32_GNOMERAGONINSTANCE_LEVEL) }, + {109, sWorld.getConfig(CONFIG_UINT32_SUNKENTEMPLE_LEVEL) }, + {129, sWorld.getConfig(CONFIG_UINT32_RAZORFENDOWNS_LEVEL) }, + {189, sWorld.getConfig(CONFIG_UINT32_MONASTERYINSTANCES_LEVEL) }, // Scarlet Monastery + {209, sWorld.getConfig(CONFIG_UINT32_TANARISINSTANCE_LEVEL) }, // Zul'Farrak + {229, sWorld.getConfig(CONFIG_UINT32_BLACKROCKSPIRE_LEVEL) }, + {230, sWorld.getConfig(CONFIG_UINT32_BLACKROCKDEPTHS_LEVEL) }, + {249, sWorld.getConfig(CONFIG_UINT32_ONYXIALAIRINSTANCE_LEVEL) }, + {289, sWorld.getConfig(CONFIG_UINT32_SCHOOLOFNECROMANCY_LEVEL) }, // Scholomance + {309, sWorld.getConfig(CONFIG_UINT32_ZULGURUB_LEVEL) }, + {329, sWorld.getConfig(CONFIG_UINT32_STRATHOLME_LEVEL) }, + {349, sWorld.getConfig(CONFIG_UINT32_MAURADON_LEVEL) }, + {389, sWorld.getConfig(CONFIG_UINT32_ORGRIMMARINSTANCE_LEVEL) }, // Ragefire Chasm + {409, sWorld.getConfig(CONFIG_UINT32_MOLTENCORE_LEVEL) }, + {429, sWorld.getConfig(CONFIG_UINT32_DIREMAUL_LEVEL) }, + {469, sWorld.getConfig(CONFIG_UINT32_BLACKWINGLAIR_LEVEL) }, + {509, sWorld.getConfig(CONFIG_UINT32_AHNQIRAJ_LEVEL) }, // Ruins of Ahn'Qiraj + {531, sWorld.getConfig(CONFIG_UINT32_AHNQIRAJTEMPLE_LEVEL) }, + //TBC Instances + {269, sWorld.getConfig(CONFIG_UINT32_CAVERNSOFTIME_LEVEL) }, // The Black Morass + {532, sWorld.getConfig(CONFIG_UINT32_KARAZAHN_LEVEL) }, + {534, sWorld.getConfig(CONFIG_UINT32_HYJALPAST_LEVEL) }, // The Battle for Mount Hyjal - Hyjal Summit + {540, sWorld.getConfig(CONFIG_UINT32_HELLFIREMILITARY_LEVEL) }, // The Shattered Halls + {542, sWorld.getConfig(CONFIG_UINT32_HELLFIREDEMON_LEVEL) }, // The Blood Furnace + {543, sWorld.getConfig(CONFIG_UINT32_HELLFIRERAMPART_LEVEL) }, + {544, sWorld.getConfig(CONFIG_UINT32_HELLFIRERAID_LEVEL) }, // Magtheridon's Lair + {545, sWorld.getConfig(CONFIG_UINT32_COILFANGPUMPING_LEVEL) }, // The Steamvault + {546, sWorld.getConfig(CONFIG_UINT32_COILFANGMARSH_LEVEL) }, // The Underbog + {547, sWorld.getConfig(CONFIG_UINT32_COILFANGDRAENEI_LEVEL) }, // The Slavepens + {548, sWorld.getConfig(CONFIG_UINT32_COILFANGRAID_LEVEL) }, // Serpentshrine Cavern + {550, sWorld.getConfig(CONFIG_UINT32_TEMPESTKEEPRAID_LEVEL) }, // The Eye + {552, sWorld.getConfig(CONFIG_UINT32_TEMPESTKEEPARCANE_LEVEL) }, // The Arcatraz + {553, sWorld.getConfig(CONFIG_UINT32_TEMPESTKEEPATRIUM_LEVEL) }, // The Botanica + {554, sWorld.getConfig(CONFIG_UINT32_TEMPESTKEEPFACTORY_LEVEL) }, // The Mechanar + {555, sWorld.getConfig(CONFIG_UINT32_AUCHINDOUNSHADOW_LEVEL) }, // Shadow Labyrinth + {556, sWorld.getConfig(CONFIG_UINT32_AUCHINDOUNDEMON_LEVEL) }, // Sethekk Halls + {557, sWorld.getConfig(CONFIG_UINT32_AUCHINDOUNETHEREAL_LEVEL) }, // Mana-Tombs + {558, sWorld.getConfig(CONFIG_UINT32_AUCHINDOUNDRAENEI_LEVEL) }, // Auchenai Crypts + {560, sWorld.getConfig(CONFIG_UINT32_HILLSBRADPAST_LEVEL) }, // Old Hillsbrad Foothills + {564, sWorld.getConfig(CONFIG_UINT32_BLACKTEMPLE_LEVEL) }, + {565, sWorld.getConfig(CONFIG_UINT32_GRUULSLAIR_LEVEL) }, + {568, sWorld.getConfig(CONFIG_UINT32_ZULAMAN_LEVEL) }, + {580, sWorld.getConfig(CONFIG_UINT32_SUNWELLPLATEAU_LEVEL) }, + {585, sWorld.getConfig(CONFIG_UINT32_SUNWELL5MANFIX_LEVEL) }, // Magister's Terrace + //WotLK Instances + {533, sWorld.getConfig(CONFIG_UINT32_STRATHOLMERAID_LEVEL) }, // Naxxramas + {574, sWorld.getConfig(CONFIG_UINT32_VALGARDE70_LEVEL) }, // Utgarde Keep + {575, sWorld.getConfig(CONFIG_UINT32_UTGARDEPINNACLE_LEVEL) }, + {576, sWorld.getConfig(CONFIG_UINT32_NEXUS70_LEVEL) }, // The Nexus + {578, sWorld.getConfig(CONFIG_UINT32_NEXUS80_LEVEL) }, // The Occulus + {595, sWorld.getConfig(CONFIG_UINT32_STRATHOLMECOT_LEVEL) }, // The Culling of Stratholme + {599, sWorld.getConfig(CONFIG_UINT32_ULDUAR70_LEVEL) }, // Halls of Stone + {600, sWorld.getConfig(CONFIG_UINT32_DRAKTHERONKEEP_LEVEL) }, // Drak'Tharon Keep + {601, sWorld.getConfig(CONFIG_UINT32_AZJOL_UPPERCITY_LEVEL) }, // Azjol-Nerub + {602, sWorld.getConfig(CONFIG_UINT32_ULDUAR80_LEVEL) }, // Halls of Lighting + {603, sWorld.getConfig(CONFIG_UINT32_ULDUARRAID_LEVEL) }, // Ulduar + {604, sWorld.getConfig(CONFIG_UINT32_GUNDRAK_LEVEL) }, + {608, sWorld.getConfig(CONFIG_UINT32_DALARANPRISON_LEVEL) }, // Violet Hold + {615, sWorld.getConfig(CONFIG_UINT32_CHAMBEROFASPECTSBLACK_LEVEL) }, // The Obsidian Sanctum + {616, sWorld.getConfig(CONFIG_UINT32_NEXUSRAID_LEVEL) }, // The Eye of Eternity + {619, sWorld.getConfig(CONFIG_UINT32_AZJOL_LOWERCITY_LEVEL) }, // Ahn'kahet: The Old Kingdom + {631, sWorld.getConfig(CONFIG_UINT32_ICECROWNCITADEL_LEVEL) }, // Icecrown Citadel + {632, sWorld.getConfig(CONFIG_UINT32_ICECROWNCITADEL5MAN_LEVEL) }, // The Forge of Souls + {649, sWorld.getConfig(CONFIG_UINT32_ARGENTTOURNAMENTRAID_LEVEL) }, // Trial of the Crusader + {650, sWorld.getConfig(CONFIG_UINT32_ARGENTTOURNAMENTDUNGEON_LEVEL) }, // Trial of the Champion + {658, sWorld.getConfig(CONFIG_UINT32_QUARRYOFTEARS_LEVEL) }, // Pit of Saron + {668, sWorld.getConfig(CONFIG_UINT32_HALLSOFREFLECTION_LEVEL) }, // Halls of Reflection + {724, sWorld.getConfig(CONFIG_UINT32_CHAMBEROFASPECTSRED_LEVEL) }, // The Ruby Sanctum + }; + // Dungeon Difficulty + // Catch alls + D5 = sWorld.getConfig(CONFIG_FLOAT_DUNGEON_DIFF); + D10 = sWorld.getConfig(CONFIG_FLOAT_HEROIC_DIFF); + D25 = sWorld.getConfig(CONFIG_FLOAT_RAID25_DIFF); + D40 = sWorld.getConfig(CONFIG_FLOAT_RAID40_DIFF); + diff_Multiplier = + { + // WOW Classic Instances + {33, sWorld.getConfig(CONFIG_FLOAT_SHADOWFANGKEEP_DIFF) }, + {34, sWorld.getConfig(CONFIG_FLOAT_STOCKADES_DIFF) }, + {36, sWorld.getConfig(CONFIG_FLOAT_DEADMINES_DIFF) }, + {43, sWorld.getConfig(CONFIG_FLOAT_WAILINGCAVERNS_DIFF) }, + {47, sWorld.getConfig(CONFIG_FLOAT_RAZORFENKRAULINSTANCE_DIFF) }, + {48, sWorld.getConfig(CONFIG_FLOAT_BLACKFATHOM_DIFF) }, + {70, sWorld.getConfig(CONFIG_FLOAT_ULDAMAN_DIFF) }, + {90, sWorld.getConfig(CONFIG_FLOAT_GNOMERAGONINSTANCE_DIFF) }, + {109, sWorld.getConfig(CONFIG_FLOAT_SUNKENTEMPLE_DIFF) }, + {129, sWorld.getConfig(CONFIG_FLOAT_RAZORFENDOWNS_DIFF) }, + {189, sWorld.getConfig(CONFIG_FLOAT_MONASTERYINSTANCES_DIFF) }, // Scarlet + {209, sWorld.getConfig(CONFIG_FLOAT_TANARISINSTANCE_DIFF) }, // Zul'Farrak + {229, sWorld.getConfig(CONFIG_FLOAT_BLACKROCKSPIRE_DIFF) }, + {230, sWorld.getConfig(CONFIG_FLOAT_BLACKROCKDEPTHS_DIFF) }, + {249, sWorld.getConfig(CONFIG_FLOAT_ONYXIALAIRINSTANCE_DIFF) }, + {289, sWorld.getConfig(CONFIG_FLOAT_SCHOOLOFNECROMANCY_DIFF) }, // Scholo + {309, sWorld.getConfig(CONFIG_FLOAT_ZULGURUB_DIFF) }, + {329, sWorld.getConfig(CONFIG_FLOAT_STRATHOLME_DIFF) }, + {349, sWorld.getConfig(CONFIG_FLOAT_MAURADON_DIFF) }, + {389, sWorld.getConfig(CONFIG_FLOAT_ORGRIMMARINSTANCE_DIFF) }, // Ragefire + {409, sWorld.getConfig(CONFIG_FLOAT_MOLTENCORE_DIFF) }, + {429, sWorld.getConfig(CONFIG_FLOAT_DIREMAUL_DIFF) }, + {469, sWorld.getConfig(CONFIG_FLOAT_BLACKWINGLAIR_DIFF) }, + {509, sWorld.getConfig(CONFIG_FLOAT_AHNQIRAJ_DIFF) }, + {531, sWorld.getConfig(CONFIG_FLOAT_AHNQIRAJTEMPLE_DIFF) }, + // BC Instances + {269, sWorld.getConfig(CONFIG_FLOAT_CAVERNSOFTIME_DIFF) }, // Black Morass + {532, sWorld.getConfig(CONFIG_FLOAT_KARAZAHN_DIFF) }, + {534, sWorld.getConfig(CONFIG_FLOAT_HYJALPAST_DIFF) }, // Mount Hyjal + {540, sWorld.getConfig(CONFIG_FLOAT_HELLFIREMILITARY_DIFF) }, // The Shattered Halls + {542, sWorld.getConfig(CONFIG_FLOAT_HELLFIREDEMON_DIFF) }, // The Blood Furnace + {543, sWorld.getConfig(CONFIG_FLOAT_HELLFIRERAMPART_DIFF) }, + {544, sWorld.getConfig(CONFIG_FLOAT_HELLFIRERAID_DIFF) }, // Magtheridon's Lair + {545, sWorld.getConfig(CONFIG_FLOAT_COILFANGPUMPING_DIFF) }, // The Steamvault + {546, sWorld.getConfig(CONFIG_FLOAT_COILFANGMARSH_DIFF) }, // The Underbog + {547, sWorld.getConfig(CONFIG_FLOAT_COILFANGDRAENEI_DIFF) }, // The Slavepens + {548, sWorld.getConfig(CONFIG_FLOAT_COILFANGRAID_DIFF) }, // Serpentshrine Cavern + {550, sWorld.getConfig(CONFIG_FLOAT_TEMPESTKEEPRAID_DIFF) }, // The Eye + {552, sWorld.getConfig(CONFIG_FLOAT_TEMPESTKEEPARCANE_DIFF) }, // The Arcatraz + {553, sWorld.getConfig(CONFIG_FLOAT_TEMPESTKEEPATRIUM_DIFF) }, // The Botanica + {554, sWorld.getConfig(CONFIG_FLOAT_TEMPESTKEEPFACTORY_DIFF) }, // The Mechanar + {555, sWorld.getConfig(CONFIG_FLOAT_AUCHINDOUNSHADOW_DIFF) }, // Shadow Labyrinth + {556, sWorld.getConfig(CONFIG_FLOAT_AUCHINDOUNDEMON_DIFF) }, // Sethekk Halls + {557, sWorld.getConfig(CONFIG_FLOAT_AUCHINDOUNETHEREAL_DIFF) }, // Mana-Tombs + {558, sWorld.getConfig(CONFIG_FLOAT_AUCHINDOUNDRAENEI_DIFF) }, // Auchenai Crypts + {560, sWorld.getConfig(CONFIG_FLOAT_HILLSBRADPAST_DIFF) }, // Old Hillsbrad Foothills + {564, sWorld.getConfig(CONFIG_FLOAT_BLACKTEMPLE_DIFF) }, + {565, sWorld.getConfig(CONFIG_FLOAT_GRUULSLAIR_DIFF) }, + {568, sWorld.getConfig(CONFIG_FLOAT_ZULAMAN_DIFF) }, + {580, sWorld.getConfig(CONFIG_FLOAT_SUNWELLPLATEAU_DIFF) }, + {585, sWorld.getConfig(CONFIG_FLOAT_SUNWELL5MANFIX_DIFF) }, // Magister's Terrace + // WOTLK Instances + {533, sWorld.getConfig(CONFIG_FLOAT_STRATHOLMERAID_DIFF) }, // Nax 10 + {574, sWorld.getConfig(CONFIG_FLOAT_VALGARDE70_DIFF) }, // Utgarde Keep + {575, sWorld.getConfig(CONFIG_FLOAT_UTGARDEPINNACLE_DIFF) }, + {576, sWorld.getConfig(CONFIG_FLOAT_NEXUS70_DIFF) }, // The Nexus + {578, sWorld.getConfig(CONFIG_FLOAT_NEXUS80_DIFF) }, // The Occulus + {595, sWorld.getConfig(CONFIG_FLOAT_STRATHOLMECOT_DIFF) }, // The Culling of Stratholme + {599, sWorld.getConfig(CONFIG_FLOAT_ULDUAR70_DIFF) }, // Halls of Stone + {600, sWorld.getConfig(CONFIG_FLOAT_DRAKTHERONKEEP_DIFF) }, // Drak'Tharon Keep + {601, sWorld.getConfig(CONFIG_FLOAT_AZJOL_UPPERCITY_DIFF) }, // Azjol-Nerub + {602, sWorld.getConfig(CONFIG_FLOAT_ULDUAR80_DIFF) }, // Halls of Lighting + {603, sWorld.getConfig(CONFIG_FLOAT_ULDUARRAID_DIFF) }, // Ulduar 10 + {604, sWorld.getConfig(CONFIG_FLOAT_GUNDRAK_DIFF) }, + {608, sWorld.getConfig(CONFIG_FLOAT_DALARANPRISON_DIFF) }, // Violet Hold + {615, sWorld.getConfig(CONFIG_FLOAT_CHAMBEROFASPECTSBLACK_DIFF) }, // The Obsidian Sanctum 10 + {616, sWorld.getConfig(CONFIG_FLOAT_NEXUSRAID_DIFF) }, // The Eye of Eternity 10 + {619, sWorld.getConfig(CONFIG_FLOAT_AZJOL_LOWERCITY_DIFF) }, // Ahn'kahet: The Old Kingdom + {631, sWorld.getConfig(CONFIG_FLOAT_ICECROWNCITADEL_DIFF) }, // Icecrown Citadel 10 + {632, sWorld.getConfig(CONFIG_FLOAT_ICECROWNCITADEL5MAN_DIFF) }, // The Forge of Souls + {649, sWorld.getConfig(CONFIG_FLOAT_ARGENTTOURNAMENTRAID_DIFF) }, // Trial of the Crusader 10 + {650, sWorld.getConfig(CONFIG_FLOAT_ARGENTTOURNAMENTDUNGEON_DIFF) }, // Trial of the Champion + {658, sWorld.getConfig(CONFIG_FLOAT_QUARRYOFTEARS_DIFF) }, // Pit of Saron + {668, sWorld.getConfig(CONFIG_FLOAT_HALLSOFREFLECTION_DIFF) }, // Halls of Reflection + {724, sWorld.getConfig(CONFIG_FLOAT_CHAMBEROFASPECTSRED_DIFF) }, // The Ruby Sanctum 10 + }; + // Heroics + diff_Multiplier_Heroics = + { + // BC Instances Heroics + {269, sWorld.getConfig(CONFIG_FLOAT_HEROIC_CAVERNSOFTIME_DIFF) }, // Black Morass H + {540, sWorld.getConfig(CONFIG_FLOAT_HEROIC_HELLFIREMILITARY_DIFF) }, // The Shattered Halls H + {542, sWorld.getConfig(CONFIG_FLOAT_HEROIC_HELLFIREDEMON_DIFF) }, // The Blood Furnace H + {543, sWorld.getConfig(CONFIG_FLOAT_HEROIC_HELLFIRERAMPART_DIFF) }, // Heroic + {545, sWorld.getConfig(CONFIG_FLOAT_HEROIC_COILFANGPUMPING_DIFF) }, // The Steamvault + {546, sWorld.getConfig(CONFIG_FLOAT_HEROIC_COILFANGMARSH_DIFF) }, // The Underbog + {547, sWorld.getConfig(CONFIG_FLOAT_HEROIC_COILFANGDRAENEI_DIFF) }, // The Slavepens H + {552, sWorld.getConfig(CONFIG_FLOAT_HEROIC_TEMPESTKEEPARCANE_DIFF) }, // The Arcatraz H + {553, sWorld.getConfig(CONFIG_FLOAT_HEROIC_TEMPESTKEEPATRIUM_DIFF) }, // The Botanica H + {554, sWorld.getConfig(CONFIG_FLOAT_HEROIC_TEMPESTKEEPFACTORY_DIFF) }, // The Mechanar H + {555, sWorld.getConfig(CONFIG_FLOAT_HEROIC_AUCHINDOUNSHADOW_DIFF) }, // Shadow Labyrinth H + {556, sWorld.getConfig(CONFIG_FLOAT_HEROIC_AUCHINDOUNDEMON_DIFF) }, // Sethekk Halls H + {557, sWorld.getConfig(CONFIG_FLOAT_HEROIC_AUCHINDOUNETHEREAL_DIFF) }, // Mana-Tombs H + {558, sWorld.getConfig(CONFIG_FLOAT_HEROIC_AUCHINDOUNDRAENEI_DIFF) }, // Auchenai Crypts H + {560, sWorld.getConfig(CONFIG_FLOAT_HEROIC_HILLSBRADPAST_DIFF) }, // Old Hillsbrad Foothills H + {568, sWorld.getConfig(CONFIG_FLOAT_HEROIC_ZULAMAN_DIFF) }, // Zul'Aman H + {585, sWorld.getConfig(CONFIG_FLOAT_HEROIC_SUNWELL5MANFIX_DIFF) }, // Magister's Terrace H + // WOTLK Instances Heroics + {533, sWorld.getConfig(CONFIG_FLOAT_HEROIC_STRATHOLMERAID_DIFF) }, // Naxxramas 25 + {574, sWorld.getConfig(CONFIG_FLOAT_HEROIC_VALGARDE70_DIFF) }, // Utgarde Keep H + {575, sWorld.getConfig(CONFIG_FLOAT_HEROIC_UTGARDEPINNACLE_DIFF) }, // Utgarde Pinnacle H + {576, sWorld.getConfig(CONFIG_FLOAT_HEROIC_NEXUS70_DIFF) }, // The Nexus H + {578, sWorld.getConfig(CONFIG_FLOAT_HEROIC_NEXUS80_DIFF) }, // The Occulus H + {595, sWorld.getConfig(CONFIG_FLOAT_HEROIC_STRATHOLMECOT_DIFF) }, // The Culling of Stratholme H + {599, sWorld.getConfig(CONFIG_FLOAT_HEROIC_ULDUAR70_DIFF) }, // Halls of Stone H + {600, sWorld.getConfig(CONFIG_FLOAT_HEROIC_DRAKTHERONKEEP_DIFF) }, // Drak'Tharon Keep H + {601, sWorld.getConfig(CONFIG_FLOAT_HEROIC_AZJOL_UPPERCITY_DIFF) }, // Azjol-Nerub H + {602, sWorld.getConfig(CONFIG_FLOAT_HEROIC_ULDUAR80_DIFF) }, // Halls of Lighting H + {603, sWorld.getConfig(CONFIG_FLOAT_HEROIC_ULDUARRAID_DIFF) }, // Ulduar 25 + {604, sWorld.getConfig(CONFIG_FLOAT_HEROIC_GUNDRAK_DIFF) }, // Gundrak H + {608, sWorld.getConfig(CONFIG_FLOAT_HEROIC_DALARANPRISON_DIFF) }, // Violet Hold H + {615, sWorld.getConfig(CONFIG_FLOAT_HEROIC_CHAMBEROFASPECTSBLACK_DIFF) }, // The Obsidian Sanctum 25 + {616, sWorld.getConfig(CONFIG_FLOAT_HEROIC_NEXUSRAID_DIFF) }, // The Eye of Eternity 25 + {619, sWorld.getConfig(CONFIG_FLOAT_HEROIC_AZJOL_LOWERCITY_DIFF) }, // Ahn'kahet: The Old Kingdom H + {631, sWorld.getConfig(CONFIG_FLOAT_HEROIC_ICECROWNCITADEL_DIFF) }, // Icecrown Citadel 25 + {632, sWorld.getConfig(CONFIG_FLOAT_HEROIC_ICECROWNCITADEL5MAN_DIFF) }, // The Forge of Souls + {649, sWorld.getConfig(CONFIG_FLOAT_HEROIC_ARGENTTOURNAMENTRAID_DIFF) }, // Trial of the Crusader 25 + {650, sWorld.getConfig(CONFIG_FLOAT_HEROIC_ARGENTTOURNAMENTDUNGEON_DIFF) }, // Trial of the Champion H + {658, sWorld.getConfig(CONFIG_FLOAT_HEROIC_QUARRYOFTEARS_DIFF) }, // Pit of Saron H + {668, sWorld.getConfig(CONFIG_FLOAT_HEROIC_HALLSOFREFLECTION_DIFF) }, // Halls of Reflection H + {724, sWorld.getConfig(CONFIG_FLOAT_HEROIC_CHAMBEROFASPECTSRED_DIFF) }, // The Ruby Sanctum 25 + }; + + //Unique Raids beyond the heroic and normal versions of themselves + D649H10 = sWorld.getConfig(CONFIG_FLOAT_HEROIC_ARGENTTOURNAMENTRAID10_DIFF); + D649H25 = sWorld.getConfig(CONFIG_FLOAT_HEROIC_ARGENTTOURNAMENTRAID25_DIFF); //Trial of the Crusader 25 Heroic + + Map* map = player->GetMap(); + float difficulty = CalculateDifficulty(map, player); + int dunLevel = CalculateDungeonLevel(map, player); + int numInGroup = GetNumInGroup(player); + ApplyBuffs(player, map, difficulty, dunLevel, numInGroup); + } +//End Solocraft Functions + if (IsRaid()) player->RemoveAllGroupBuffsFromCaster(ObjectGuid()); @@ -781,20 +1022,129 @@ void Map::Update(const uint32& t_diff) #endif } + // active areas timer + m_activeAreasTimer += t_diff; + if (m_activeAreasTimer >= 10000) + { + m_activeAreasTimer = 0; + m_activeAreas.clear(); + m_activeZones.clear(); + } + + if (!m_activeAreasTimer && IsContinent() && HasRealPlayers()) + { + for (m_mapRefIter = m_mapRefManager.begin(); m_mapRefIter != m_mapRefManager.end(); ++m_mapRefIter) + { + Player* plr = m_mapRefIter->getSource(); + if (plr && plr->IsInWorld()) + { + if (plr->GetPlayerbotAI() && !plr->GetPlayerbotAI()->IsRealPlayer()) + continue; + + if (plr->isAFK()) + continue; + + if (!plr->isGMVisible()) + continue; + + if (find(m_activeZones.begin(), m_activeZones.end(), plr->GetZoneId()) == m_activeZones.end()) + m_activeZones.push_back(plr->GetZoneId()); + + ContinentArea activeArea = sMapMgr.GetContinentInstanceId(GetId(), plr->GetPositionX(), plr->GetPositionY()); + // check active area + if (activeArea != MAP_NO_AREA) + { + if (!HasActiveAreas(activeArea)) + m_activeAreas.push_back(activeArea); + } + } + } + } + + bool hasPlayers = false; + uint32 activeChars = 0; + uint32 avgDiff = sWorld.GetAverageDiff(); + bool updateAI = urand(0, (HasRealPlayers() ? avgDiff : (avgDiff * 3))) < 10; /// update players at tick for (m_mapRefIter = m_mapRefManager.begin(); m_mapRefIter != m_mapRefManager.end(); ++m_mapRefIter) { Player* plr = m_mapRefIter->getSource(); if (plr && plr->IsInWorld()) + { + bool isInActiveArea = false; + if (!plr->GetPlayerbotAI() || plr->GetPlayerbotAI()->IsRealPlayer()) + { + isInActiveArea = true; + + hasPlayers = true; + + } + else if (HasRealPlayers()) + { + ContinentArea activeArea = MAP_NO_AREA; + if (IsContinent()) + activeArea = sMapMgr.GetContinentInstanceId(GetId(), plr->GetPositionX(), plr->GetPositionY()); + + isInActiveArea = IsContinent() ? (activeArea == MAP_NO_AREA ? false : HasActiveAreas(activeArea)) : HasRealPlayers(); + + /*if (isInActiveArea) + { + if (avgDiff > 200 && IsContinent()) + { + if (find(m_activeZones.begin(), m_activeZones.end(), plr->GetZoneId()) == m_activeZones.end()) + isInActiveArea = false; + } + }*/ + } + if (plr->GetPlayerbotAI() && plr->GetPlayerbotAI()->HasRealPlayerMaster()) + isInActiveArea = true; + if (plr->InBattleGroundQueue() || plr->InBattleGround()) + isInActiveArea = true; + + if (isInActiveArea) + activeChars++; + plr->Update(t_diff); + plr->UpdateAI(t_diff, !(isInActiveArea || updateAI || plr->IsInCombat())); + } + } + + hasRealPlayers = hasPlayers; + + if (IsContinent() && HasRealPlayers() && HasActiveAreas() && !m_activeAreasTimer) + { + sLog.outBasic("Map %u: Active Areas:Zones - %u:%u", GetId(), m_activeAreas.size(), m_activeZones.size()); + sLog.outBasic("Map %u: Active Areas Chars - %u of %u", GetId(), activeChars, m_mapRefManager.getSize()); } for (m_mapRefIter = m_mapRefManager.begin(); m_mapRefIter != m_mapRefManager.end(); ++m_mapRefIter) { Player* player = m_mapRefIter->getSource(); - if (!player->IsInWorld() || !player->IsPositionValid()) + if (!player || !player->IsInWorld() || !player->IsPositionValid()) continue; +#ifdef ENABLE_PLAYERBOTS + if (!player->isRealPlayer()) //For non-players only load the grid. + { + CellPair center = MaNGOS::ComputeCellPair(player->GetPositionX(), player->GetPositionY()).normalize(); + uint32 cell_id = (center.y_coord * TOTAL_NUMBER_OF_CELLS_PER_MAP) + center.x_coord; + + if (!isCellMarked(cell_id)) + { + Cell cell(center); + const uint32 x = cell.GridX(); + const uint32 y = cell.GridY(); + if (!cell.NoCreate() || loaded(GridPair(x, y))) + EnsureGridLoaded(player->GetCurrentCell()); + } + continue; + } +#endif + + // update objects beyond visibility distance + if (!player->GetPlayerbotAI() && !player->isAFK()) + player->GetCamera().UpdateVisibilityForOwner(false, true); + VisitNearbyCellsOf(player, grid_object_update, world_object_update); // If player is using far sight, visit that object too @@ -803,6 +1153,7 @@ void Map::Update(const uint32& t_diff) } // non-player active objects + bool updateObj = urand(0, (HasRealPlayers() ? avgDiff : (avgDiff * 3))) < 10; if (!m_activeNonPlayers.empty()) { for (m_activeNonPlayersIter = m_activeNonPlayers.begin(); m_activeNonPlayersIter != m_activeNonPlayers.end();) @@ -817,6 +1168,27 @@ void Map::Update(const uint32& t_diff) if (!obj->IsInWorld() || !obj->IsPositionValid()) continue; + // skip objects if world is laggy + if (IsContinent() && avgDiff > 100) + { + bool isInActiveArea = false; + + ContinentArea activeArea = MAP_NO_AREA; + if (IsContinent()) + activeArea = sMapMgr.GetContinentInstanceId(GetId(), obj->GetPositionX(), obj->GetPositionY()); + + isInActiveArea = IsContinent() ? (activeArea == MAP_NO_AREA ? false : HasActiveAreas(activeArea)) : HasRealPlayers(); + + if (isInActiveArea && IsContinent()) + { + if (avgDiff > 150 && find(m_activeZones.begin(), m_activeZones.end(), obj->GetZoneId()) == m_activeZones.end()) + isInActiveArea = false; + } + + if (!isInActiveArea && !updateObj) + continue; + } + objToUpdate.insert(obj); // lets update mobs/objects in ALL visible cells around player! @@ -932,7 +1304,12 @@ void Map::Remove(Player* player, bool remove) SendRemoveTransports(player); UpdateObjectVisibility(player, cell, p); +#ifdef ENABLE_PLAYERBOTS + if (!player->GetPlayerbotAI()) + player->ResetMap(); +#else player->ResetMap(); +#endif if (remove) DeleteFromWorld(player); } @@ -966,7 +1343,7 @@ void Map::Remove(T* obj, bool remove) UpdateObjectVisibility(obj, cell, p); // i think will be better to call this function while object still in grid, this changes nothing but logically is better(as for me) RemoveFromGrid(obj, grid, cell); - m_objRemoveList.insert(obj->GetObjectGuid()); + //m_objRemoveList.insert(obj->GetObjectGuid()); obj->ResetMap(); if (remove) @@ -998,20 +1375,26 @@ void Map::PlayerRelocation(Player* player, float x, float y, float z, float orie DEBUG_FILTER_LOG(LOG_FILTER_PLAYER_MOVES, "Player %s relocation grid[%u,%u]cell[%u,%u]->grid[%u,%u]cell[%u,%u]", player->GetName(), old_cell.GridX(), old_cell.GridY(), old_cell.CellX(), old_cell.CellY(), new_cell.GridX(), new_cell.GridY(), new_cell.CellX(), new_cell.CellY()); NGridType* oldGrid = getNGrid(old_cell.GridX(), old_cell.GridY()); - RemoveFromGrid(player, oldGrid, old_cell); - if (!old_cell.DiffGrid(new_cell)) - AddToGrid(player, oldGrid, new_cell); - else - EnsureGridLoadedAtEnter(new_cell, player); + if (oldGrid) + { + RemoveFromGrid(player, oldGrid, old_cell); + if (!old_cell.DiffGrid(new_cell)) + AddToGrid(player, oldGrid, new_cell); + else + EnsureGridLoadedAtEnter(new_cell, player); + } NGridType* newGrid = getNGrid(new_cell.GridX(), new_cell.GridY()); - player->GetViewPoint().Event_GridChanged(&(*newGrid)(new_cell.CellX(), new_cell.CellY())); + if (newGrid) + { + player->GetViewPoint().Event_GridChanged(&(*newGrid)(new_cell.CellX(), new_cell.CellY())); + } } player->OnRelocated(); NGridType* newGrid = getNGrid(new_cell.GridX(), new_cell.GridY()); - if (!same_cell && newGrid->GetGridState() != GRID_STATE_ACTIVE) + if (newGrid && !same_cell && newGrid->GetGridState() != GRID_STATE_ACTIVE) { ResetGridExpiry(*newGrid, 0.1f); newGrid->SetGridState(GRID_STATE_ACTIVE); @@ -1265,7 +1648,10 @@ void Map::UpdateObjectVisibility(WorldObject* obj, Cell cell, const CellPair& ce cell.Visit(cellpair, player_notifier, *this, *obj, obj->GetVisibilityData().GetVisibilityDistance()); for (auto guid : notifier.GetUnvisitedGuids()) if (Player* player = GetPlayer(guid)) - player->UpdateVisibilityOf(player->GetCamera().GetBody(), obj); +#ifdef ENABLE_PLAYERBOTS + if (player->isRealPlayer()) +#endif + player->UpdateVisibilityOf(player->GetCamera().GetBody(), obj); } void Map::SendInitSelf(Player* player) const @@ -1386,7 +1772,7 @@ void Map::AddObjectToRemoveList(WorldObject* obj) MANGOS_ASSERT(obj->GetMapId() == GetId() && obj->GetInstanceId() == GetInstanceId()); obj->CleanupsBeforeDelete(); // remove or simplify at least cross referenced links - + //std::unique_lock lock(i_objectsToRemove_lock); i_objectsToRemove.insert(obj); // DEBUG_LOG("Object (GUID: %u TypeId: %u ) added to removing list.",obj->GetGUIDLow(),obj->GetTypeId()); } @@ -1396,6 +1782,7 @@ void Map::RemoveAllObjectsInRemoveList() if (i_objectsToRemove.empty()) return; + std::unique_lock lock(i_objectsToRemove_lock); // DEBUG_LOG("Object remover 1 check."); while (!i_objectsToRemove.empty()) { @@ -1980,6 +2367,7 @@ BattleGroundMap::BattleGroundMap(uint32 id, time_t expiry, uint32 InstanceId, ui BattleGroundMap::~BattleGroundMap() { + UnloadAll(true); } void BattleGroundMap::Initialize(bool) @@ -1989,6 +2377,9 @@ void BattleGroundMap::Initialize(bool) void BattleGroundMap::Update(const uint32& diff) { + if (!GetBG()) + return; + Map::Update(diff); GetBG()->Update(diff); @@ -2303,6 +2694,7 @@ void Map::SendObjectUpdates() while (!i_objectsToClientUpdate.empty()) { Object* obj = *i_objectsToClientUpdate.begin(); + i_objectsToClientUpdate.erase(i_objectsToClientUpdate.begin()); obj->BuildUpdateData(update_players); } @@ -3000,3 +3392,230 @@ void Map::SetZoneOverrideLight(uint32 zoneId, uint32 areaId, uint32 lightId, uin } } } + +//Start Solocraft Subfunctions +//Set the instance difficulty +int Map::CalculateDifficulty(Map* map, Player* /*player*/) +{ + //float difficulty = 0.0;//changed from 1.0 + if (map) + { + if (map->IsBattleGroundOrArena()) + return 0; + + //WOTLK 25 Man raids + if (map->Is25ManRaid()) + { + if (map->IsHeroic() && map->GetId() == 649) { + return D649H25; //Heroic Grand Trial of the Crusader + } + else if (diff_Multiplier_Heroics.find(map->GetId()) == diff_Multiplier_Heroics.end()) { + return D25; //map not found returns the catch all value + } + else + return diff_Multiplier_Heroics[map->GetId()]; //return the specific dungeon's level + } + if (map->IsHeroic()) + { + //WOTLK 10 Man Heroic + if (map->GetId() == 649) { + return D649H10; + } + else if (diff_Multiplier_Heroics.find(map->GetId()) == diff_Multiplier_Heroics.end()) { + return D10; //map not found returns the catch all value + } + else + return diff_Multiplier_Heroics[map->GetId()]; //return the specific dungeon's level + } + if (diff_Multiplier.find(map->GetId()) == diff_Multiplier.end()) { + //Catch Alls ----------------------5 Dungeons and 40 Raids + if (map->IsDungeon()) { + return D5; + } + else if (map->IsRaid()) { + return D40; + } + } + else + return diff_Multiplier[map->GetId()]; //return the specific dungeon's level + } + return 0; //return 0 +} + +//Set the Dungeon Level +int Map::CalculateDungeonLevel(Map* map, Player* /*player*/) +{ + if (dungeons.find(map->GetId()) == dungeons.end()) + { + return SolocraftDungeonLevel; //map not found returns the catch all value + } + else + return dungeons[map->GetId()]; //return the specific dungeon's level +} + +//Get the group's size +int Map::GetNumInGroup(Player* player) +{ + int numInGroup = 1; + Group* group = player->GetGroup(); + if (group) { + Group::MemberSlotList const& groupMembers = group->GetMemberSlots(); + numInGroup = groupMembers.size(); + } + return numInGroup; +} + +void Map::ApplyBuffs(Player* player, Map* map, float difficulty, int dunLevel, int numInGroup) +{ + int SpellPowerBonus = 0; + //Check whether to buff the player or check to debuff back to normal + if (difficulty != 0) + { + std::ostringstream ss; + if (player->GetLevel() <= dunLevel + SolocraftLevelDiff) //If a player is too high level for dungeon don't buff but if in a group will count towards the group offset balancing. + { + //Get Current members total difficulty offset and if it exceeds the difficulty offset of the dungeon then debuff new group members coming in until all members leave and re-enter. This happens when a player already inside dungeon invite others to the group but the player already has the full difficulty offset. + float GroupDifficulty = GetGroupDifficulty(player); + //Check to either debuff or buff player entering dungeon. Debuff must be enabled in Config + if (GroupDifficulty >= difficulty && SoloCraftDebuffEnable == 1) + { + //Current dungeon offset exceeded - Debuff player + difficulty = (-abs(difficulty)) + (difficulty / numInGroup); + difficulty = roundf(difficulty * 100) / 100; //Float variables suck + + //sLog->outError("%u: would have this difficulty: %f", player->GetGUID(), tempDiff); + } + else + { + //Current Dungeon offset not exceeded - Buff player + //Group difficulty adjustment + difficulty = difficulty / numInGroup; + difficulty = roundf(difficulty * 100) / 100; //Float variables suck - two decimal rounding + } + + //Check Database for a current dungeon entry + QueryResult* result = CharacterDatabase.PQuery("SELECT `GUID`, `Difficulty`, `GroupSize`, `SpellPower`, `Stats` FROM `custom_solocraft_character_stats` WHERE GUID = %u", player->GetGUIDLow()); + + //Modify Player Stats + for (int32 i = STAT_STRENGTH; i < MAX_STATS; ++i) //STATS defined/enum in SharedDefines.h + { + if (result) + { + player->HandleStatModifier(UnitMods(UNIT_MOD_STAT_START + i), TOTAL_VALUE, (*result)[1].GetFloat() * (*result)[4].GetFloat(), false); + } + // Buff the player + player->HandleStatModifier(UnitMods(UNIT_MOD_STAT_START + i), TOTAL_VALUE, difficulty * SoloCraftStatsMult, true); //Unitmods enum UNIT_MOD_STAT_START defined in Unit.h line 391 + } + // Set player health + player->SetFullHealth();//defined in Unit.h line 1524 + //Spellcaster Stat modify + if (player->GetPowerType() == POWER_MANA) + { + // Buff the player's mana + player->SetPower(POWER_MANA, player->GetMaxPower(POWER_MANA)); + + //Check for Dungeon to Dungeon Transfer and remove old Spellpower buff + if (result) + { + // remove spellpower bonus + player->ApplySpellPowerBonus((*result)[3].GetUInt32() * (*result)[4].GetFloat(), false); + } + + //Buff Spellpower + if (difficulty > 0) //Debuffed characters do not get spellpower + { + SpellPowerBonus = static_cast((player->GetLevel() * SoloCraftSpellMult) * difficulty);//Yes, I pulled this calc out of my butt. + player->ApplySpellPowerBonus(SpellPowerBonus, true); + //sLog->outError("%u: spellpower Bonus applied: %i", player->GetGUID(), SpellPowerBonus); + } + } + //Announcements + if (difficulty > 0) + { + // Announce to player - Buff + ss << "|cffFF0000[SoloCraft] |cffFF8000" << player->GetName() << " entered %s - Difficulty Offset: %0.2f. Spellpower Bonus: %i"; + ChatHandler(player->GetSession()).PSendSysMessage(ss.str().c_str(), map->GetMapName(), difficulty, SpellPowerBonus); + } + else + { + // Announce to player - Debuff + ss << "|cffFF0000[SoloCraft] |cffFF8000" << player->GetName() << " entered %s - |cffFF0000BE ADVISED - You have been debuffed by offset: %0.2f. |cffFF8000 A group member already inside has the dungeon's full buff offset. No Spellpower buff will be applied to spell casters. ALL group members must exit the dungeon and re-enter to receive a balanced offset."; + ChatHandler(player->GetSession()).PSendSysMessage(ss.str().c_str(), map->GetMapName(), difficulty); + } + // Save Player Dungeon Offsets to Database + CharacterDatabase.PExecute("REPLACE INTO custom_solocraft_character_stats (GUID, Difficulty, GroupSize, SpellPower, Stats) VALUES (%u, %f, %u, %i, %f)", player->GetGUIDLow(), difficulty, numInGroup, SpellPowerBonus, SoloCraftStatsMult); + } + else + { + // Announce to player - Over Max Level Threshold + ss << "|cffFF0000[SoloCraft] |cffFF8000" << player->GetName() << " entered %s - |cffFF0000You have not been buffed. |cffFF8000 Your level is higher than the max level (%i) threshold for this dungeon."; + ChatHandler(player->GetSession()).PSendSysMessage(ss.str().c_str(), map->GetMapName(), dunLevel + SolocraftLevelDiff); + ClearBuffs(player, map); //Check to revert player back to normal + } + + } + else + { + ClearBuffs(player, map); //Check to revert player back to normal - Moving this here fixed logout and login while in instance buff and debuff issues + } +} + +// Get the current group members GUIDS and return the total sum of the difficulty offset by all group members currently in the dungeon +float Map::GetGroupDifficulty(Player* player) +{ + float GroupDifficulty = 0.0; + Group* group = player->GetGroup(); + if (group) + { + Group::MemberSlotList const& groupMembers = group->GetMemberSlots(); + for (Group::member_citerator itr = groupMembers.begin(); itr != groupMembers.end(); ++itr) + { + //Exclude player from the tally because the player is the one entering the dungeon + if (itr->guid != player->GetGUIDLow()) + { + //Database query to find difficulty for each group member that is currently in an instance + QueryResult* result = CharacterDatabase.PQuery("SELECT `GUID`, `Difficulty`, `GroupSize` FROM `custom_solocraft_character_stats` WHERE GUID = %u", itr->guid); + if (result) + { + //Test for debuffs already give to other members - They cannot be used to determine the total offset because negative numbers will skew the total difficulty offset + if ((*result)[1].GetFloat() > 0) + { + GroupDifficulty = GroupDifficulty + (*result)[1].GetFloat(); + //sLog->outError("%u : Group member GUID in instance: %u", player->GetGUID(), itr->guid); + } + } + } + } + } + return GroupDifficulty; +} + +void Map::ClearBuffs(Player* player, Map* map) +{ + //Database query to get offset from the last instance player exited + QueryResult* result = CharacterDatabase.PQuery("SELECT `GUID`, `Difficulty`, `GroupSize`, `SpellPower`, `Stats` FROM `custom_solocraft_character_stats` WHERE GUID = %u", player->GetGUIDLow()); + if (result) + { + float difficulty = (*result)[1].GetFloat(); + int SpellPowerBonus = (*result)[3].GetUInt32(); + float StatsMultPct = (*result)[4].GetFloat(); + //sLog->outError("Map difficulty: %f", difficulty); + // Inform the player + std::ostringstream ss; + ss << "|cffFF0000[SoloCraft] |cffFF8000" << player->GetName() << " exited to %s - Reverting Difficulty Offset: %0.2f. Spellpower Bonus Removed: %i"; + ChatHandler(player->GetSession()).PSendSysMessage(ss.str().c_str(), map->GetMapName(), difficulty, SpellPowerBonus); + // Clear the buffs + for (int32 i = STAT_STRENGTH; i < MAX_STATS; ++i) + { + player->HandleStatModifier(UnitMods(UNIT_MOD_STAT_START + i), TOTAL_VALUE, difficulty * StatsMultPct, false); + } + if (player->GetPowerType() == POWER_MANA && difficulty > 0) + { + // remove spellpower bonus + player->ApplySpellPowerBonus(SpellPowerBonus, false); + } + //Remove database entry as the player is no longer in an instance + CharacterDatabase.PExecute("DELETE FROM custom_solocraft_character_stats WHERE GUID = %u", player->GetGUIDLow()); + } +} +//End Solocraft Subfunctions diff --git a/src/game/Maps/Map.h b/src/game/Maps/Map.h index eb6bb560648..f63361138e9 100644 --- a/src/game/Maps/Map.h +++ b/src/game/Maps/Map.h @@ -63,6 +63,48 @@ class GenericTransport; namespace MaNGOS { struct ObjectUpdater; } class Transport; +enum ContinentArea +{ + MAP_NO_AREA = 0, + + MAP0_TOP_NORTH = 1, + MAP0_MIDDLE_NORTH = 2, + MAP0_IRONFORGE_AREA = 3, + MAP0_MIDDLE = 4, // Burning stepps, Redridge monts, Blasted lands + MAP0_STORMWIND_AREA = 5, // Stormwind, Elwynn forest, Redridge Mts + MAP0_SOUTH = 6, // Southern phase of the continent + + MAP1_TELDRASSIL = 11, // Teldrassil + MAP1_NORTH = 12, // Stonetalon, Ashenvale, Darkshore, Felwood, Moonglade, Winterspring, Azshara, Desolace + MAP1_DUROTAR = 13, // Durotar + MAP1_UPPER_MIDDLE = 14, // Mulgore, Barrens, Dustwallow Marsh + MAP1_LOWER_MIDDLE = 15, // Feralas, 1K needles + MAP1_VALLEY = 16, // Orc and Troll starting area + MAP1_ORGRIMMAR = 17, // Orgrimmar (on its own) + MAP1_SOUTH = 18, // Silithus, Un'goro and Tanaris + MAP1_GMISLAND = 19, // GM island + + MAP530_NORTH = 21, // Blade Edge, Netherstorm + MAP530_MIDDLE = 22, // Zangarmarsh, Hellfire Peninsula + MAP530_SHATTRATH = 23, // Shattrath, part of Terrokar Forest + MAP530_SOUTH = 24, // Nagrand, Shadowmoon Valley, southern Terrokar Forrest + MAP530_DRAENEI = 25, // Draenei zones + MAP530_BLOODELF = 26, // Blood elf zones + + MAP571_DALARAN = 31, // Dalaran, part of Crystalsong Forest + MAP571_NORTH = 32, // Icecrown, Storm Peaks + MAP571_SOUTH = 33, // Borean Tundra, Sholazar Basin, Wintergrasp, Dragonblight, Grizzly Hills, Howling Fjord, rest of Crystalsong Forest + + MAP0_FIRST = 1, + MAP0_LAST = 6, + MAP1_FIRST = 11, + MAP1_LAST = 19, + MAP530_FIRST = 21, + MAP530_LAST = 26, + MAP571_FIRST = 31, + MAP571_LAST = 33, +}; + // GCC have alternative #pragma pack(N) syntax and old gcc version not support pack(push,N), also any gcc version not support it at some platform #if defined( __GNUC__ ) #pragma pack(1) @@ -226,6 +268,7 @@ class Map : public GridRefManager bool Instanceable() const { return i_mapEntry && i_mapEntry->Instanceable(); } bool IsDungeon() const { return i_mapEntry && i_mapEntry->IsDungeon(); } bool IsRaid() const { return i_mapEntry && i_mapEntry->IsRaid(); } + bool Is25ManRaid() const { return IsRaid() && i_spawnMode & RAID_DIFFICULTY_MASK_25MAN; } bool IsNonRaidDungeon() const { return i_mapEntry && i_mapEntry->IsNonRaidDungeon(); } bool IsRaidOrHeroicDungeon() const { return IsRaid() || GetDifficulty() > DUNGEON_DIFFICULTY_NORMAL; } bool IsHeroic() const { return IsRaid() ? i_spawnMode >= RAID_DIFFICULTY_10MAN_HEROIC : i_spawnMode >= DUNGEON_DIFFICULTY_HEROIC; } @@ -251,6 +294,10 @@ class Map : public GridRefManager uint32 GetPlayersCountExceptGMs() const; bool ActiveObjectsNearGrid(uint32 x, uint32 y) const; +#ifdef ENABLE_PLAYERBOTS + bool HasRealPlayers() { return hasRealPlayers; } +#endif + /// Send a Packet to all players on a map void SendToPlayers(WorldPacket const& data) const; /// Send a Packet to all players in a zone. Return false if no player found @@ -414,6 +461,34 @@ class Map : public GridRefManager void AwardLFGRewards(uint32 dungeonId); + bool HasActiveAreas(ContinentArea areaId = MAP_NO_AREA) { if (areaId == MAP_NO_AREA) { return !m_activeAreas.empty(); } else { return !(find(m_activeAreas.begin(), m_activeAreas.end(), areaId) == m_activeAreas.end()); } } + bool HasActiveZone(uint32 zoneId) { return !(find(m_activeZones.begin(), m_activeZones.end(), zoneId) == m_activeZones.end()); } + + //Start Solocraft Functions + bool SoloCraftDebuffEnable = 1; + float SoloCraftSpellMult = 1.0; + float SoloCraftStatsMult = 100.0; + uint32 SolocraftLevelDiff = 1; + std::map _unitDifficulty; + std::unordered_map dungeons; + std::unordered_map diff_Multiplier; + std::unordered_map diff_Multiplier_Heroics; + uint32 SolocraftDungeonLevel = 1; + float D5 = 1.0; + float D10 = 1.0; + float D25 = 1.0; + float D40 = 1.0; + float D649H10 = 1.0; + float D649H25 = 1.0; + + int CalculateDifficulty(Map* map, Player* /*player*/); + int CalculateDungeonLevel(Map* map, Player* /*player*/); + int GetNumInGroup(Player* player); + void ApplyBuffs(Player* player, Map* map, float difficulty, int dunLevel, int numInGroup); + float GetGroupDifficulty(Player* player); + void ClearBuffs(Player* player, Map* map); +//End Solocraft Functions + private: void LoadMapAndVMap(int gx, int gy); @@ -487,6 +562,7 @@ class Map : public GridRefManager std::bitset marked_cells; + mutable std::mutex i_objectsToRemove_lock; WorldObjectSet i_objectsToRemove; typedef std::multimap ScriptScheduleMap; @@ -539,6 +615,11 @@ class Map : public GridRefManager TimePoint m_dynamicDifficultyCooldown; std::map, uint32> m_tileNumberPerTile; + + std::vector m_activeAreas; + std::vector m_activeZones; + uint32 m_activeAreasTimer; + bool hasRealPlayers; }; class WorldMap : public Map diff --git a/src/game/Maps/MapManager.cpp b/src/game/Maps/MapManager.cpp index 7cedf66a1b5..3efecc17338 100644 --- a/src/game/Maps/MapManager.cpp +++ b/src/game/Maps/MapManager.cpp @@ -398,6 +398,342 @@ BattleGroundMap* MapManager::CreateBattleGroundMap(uint32 id, uint32 InstanceId, return map; } +bool IsNorthTo(float x, float y, float const* limits, int count /* last case is limits[2*count - 1] */) +{ + int insideCount = 0; + for (int i = 0; i < count - 1; ++i) + { + if ((limits[2 * i + 1] < y && y < limits[2 * i + 3]) || (limits[2 * i + 1] > y && y > limits[2 * i + 3])) + { + float threshold = limits[2 * i] + (limits[2 * i + 2] - limits[2 * i]) * (y - limits[2 * i + 1]) / (limits[2 * i + 3] - limits[2 * i + 1]); + if (x > threshold) + ++insideCount; + } + } + return insideCount % 2 == 1; +} + +ContinentArea MapManager::GetContinentInstanceId(uint32 mapId, float x, float y, bool* transitionArea) +{ + if (transitionArea) + *transitionArea = false; + + // Y = horizontal axis on wow ... + switch (mapId) + { + case 0: + { + static float const topNorthSouthLimit[] = { + 2032.048340f, -6927.750000f, + 1634.863403f, -6157.505371f, + 1109.519775f, -5181.036133f, + 1315.204712f, -4096.020508f, + 1073.089233f, -3372.571533f, + 825.833191f, -3125.778809f, + 657.343994f, -2314.813232f, + 424.736145f, -1888.283691f, + 744.395813f, -1647.935425f, + 1424.160645f, -654.948181f, + 1447.065308f, -169.751358f, + 1208.715454f, 189.748703f, + 1596.240356f, 998.616699f, + 1577.923706f, 1293.419922f, + 1458.520264f, 1727.373291f, + 1591.916138f, 3728.139404f + }; + static float const ironforgeAreaSouthLimit[] = { + -7491.33f, 3093.74f, + -7472.04f, -391.88f, + -6366.68f, -730.10f, + -6063.96f, -1411.76f, + -6087.62f, -2190.21f, + -6349.54f, -2533.66f, + -6308.63f, -3049.32f, + -6107.82f, -3345.30f, + -6008.49f, -3590.52f, + -5989.37f, -4312.29f, + -5806.26f, -5864.11f + }; + static float const stormwindAreaNorthLimit[] = { + -8004.25f, 3714.11f, + -8075.00f, -179.00f, + -8638.00f, 169.00f, + -9044.00f, 35.00f, + -9068.00f, -125.00f, + -9094.00f, -147.00f, + -9206.00f, -290.00f, + -9097.00f, -510.00f, + -8739.00f, -501.00f, + -8725.50f, -1618.45f, + -9810.40f, -1698.41f, + -10049.60f, -1740.40f, + -10670.61f, -1692.51f, + -10908.48f, -1563.87f, + -13006.40f, -1622.80f, + -12863.23f, -4798.42f + }; + static float const stormwindAreaSouthLimit[] = { + -8725.337891f, 3535.624023f, + -9525.699219f, 910.132568f, + -9796.953125f, 839.069580f, + -9946.341797f, 743.102844f, + -10287.361328f, 760.076477f, + -10083.828125f, 380.389893f, + -10148.072266f, 80.056450f, + -10014.583984f, -161.638519f, + -9978.146484f, -361.638031f, + -9877.489258f, -563.304871f, + -9980.967773f, -1128.510498f, + -9991.717773f, -1428.793213f, + -9887.579102f, -1618.514038f, + -10169.600586f, -1801.582031f, + -9966.274414f, -2227.197754f, + -9861.309570f, -2989.841064f, + -9944.026367f, -3205.886963f, + -9610.209961f, -3648.369385f, + -7949.329590f, -4081.389404f, + -7910.859375f, -5855.578125f + }; + if (IsNorthTo(x, y, topNorthSouthLimit, sizeof(topNorthSouthLimit) / (2 * sizeof(float)))) + return MAP0_TOP_NORTH; + if (x > -2521) + return MAP0_MIDDLE_NORTH; + if (IsNorthTo(x, y, ironforgeAreaSouthLimit, sizeof(ironforgeAreaSouthLimit) / (2 * sizeof(float)))) + return MAP0_IRONFORGE_AREA; + if (IsNorthTo(x, y, stormwindAreaNorthLimit, sizeof(stormwindAreaNorthLimit) / (2 * sizeof(float)))) + return MAP0_MIDDLE; + if (IsNorthTo(x, y, stormwindAreaSouthLimit, sizeof(stormwindAreaNorthLimit) / (2 * sizeof(float)))) + return MAP0_STORMWIND_AREA; + return MAP0_SOUTH; + } + case 1: + { + static float const teldrassilSouthLimit[] = { + 7916.0f, 3475.0f, + 7916.0f, 1000.0f, + 8283.0f, -501.0f, + 8804.0f, -1098.0f + }; + static float const northMiddleLimit[] = { + -2280.00f, 4054.00f, + -2401.00f, 2365.00f, + -2432.00f, 1338.00f, + -2286.00f, 769.00f, + -2137.00f, 662.00f, + -2044.54f, 489.86f, + -1808.52f, 436.39f, + -1754.85f, 504.55f, + -1094.55f, 651.75f, + -747.46f, 647.73f, + -685.55f, 408.43f, + -311.38f, 114.43f, + -358.40f, -587.42f, + -377.92f, -748.70f, + -512.57f, -919.49f, + -280.65f, -1008.87f, + -81.29f, -930.89f, + 284.31f, -1105.39f, + 568.86f, -892.28f, + 1211.09f, -1135.55f, + 879.60f, -2110.18f, + 788.96f, -2276.02f, + 899.68f, -2625.56f, + 1281.54f, -2689.42f, + 1521.82f, -3047.85f, + 1424.22f, -3365.69f, + 1694.11f, -3615.20f, + 2373.78f, -4019.96f, + 2388.13f, -5124.35f, + 2193.79f, -5484.38f, + 1703.57f, -5510.53f, + 1497.59f, -6376.56f, + 1368.00f, -8530.00f + }; + static float const durotarSouthLimit[] = { + 2755.00f, -3766.00f, + 2225.00f, -3596.00f, + 1762.00f, -3746.00f, + 1564.00f, -3943.00f, + 1184.00f, -3915.00f, + 737.00f, -3782.00f, + -75.00f, -3742.00f, + -263.00f, -3836.00f, + -173.00f, -4064.00f, + -81.00f, -4091.00f, + -49.00f, -4089.00f, + -16.00f, -4187.00f, + -5.00f, -4192.00f, + -14.00f, -4551.00f, + -397.00f, -4601.00f, + -522.00f, -4583.00f, + -668.00f, -4539.00f, + -790.00f, -4502.00f, + -1176.00f, -4213.00f, + -1387.00f, -4674.00f, + -2243.00f, -6046.00f + }; + static float const valleyoftrialsSouthLimit[] = { + -324.00f, -3869.00f, + -774.00f, -3992.00f, + -965.00f, -4290.00f, + -932.00f, -4349.00f, + -828.00f, -4414.00f, + -661.00f, -4541.00f, + -521.00f, -4582.00f + }; + static float const middleToSouthLimit[] = { + -2402.01f, 4255.70f, + -2475.933105f, 3199.568359f, // Desolace + -2344.124023f, 1756.164307f, + -2826.438965f, 403.824738f, // Mulgore + -3472.819580f, 182.522476f, // Feralas + -4365.006836f, -1602.575439f, // the Barrens + -4515.219727f, -1681.356079f, + -4543.093750f, -1882.869385f, // Thousand Needles + -4824.16f, -2310.11f, + -5102.913574f, -2647.062744f, + -5248.286621f, -3034.536377f, + -5246.920898f, -3339.139893f, + -5459.449707f, -4920.155273f, // Tanaris + -5437.00f, -5863.00f + }; + + static float const orgrimmarSouthLimit[] = { + 2132.5076f, -3912.2478f, + 1944.4298f, -3855.2583f, + 1735.6906f, -3834.2417f, + 1654.3671f, -3380.9902f, + 1593.9861f, -3975.5413f, + 1439.2548f, -4249.6923f, + 1436.3106f, -4007.8950f, + 1393.3199f, -4196.0625f, + 1445.2428f, -4373.9052f, + 1407.2349f, -4429.4145f, + 1464.7142f, -4545.2875f, + 1584.1331f, -4596.8764f, + 1716.8065f, -4601.1323f, + 1875.8312f, -4788.7187f, + 1979.7647f, -4883.4585f, + 2219.1562f, -4854.3330f + }; + + static float const feralasThousandNeedlesSouthLimit[] = { + -6495.4995f, -4711.981f, + -6674.9995f, -4515.0019f, + -6769.5717f, -4122.4272f, + -6838.2651f, -3874.2792f, + -6851.1314f, -3659.1179f, + -6624.6845f, -3063.3843f, + -6416.9067f, -2570.1301f, + -5959.8466f, -2287.2634f, + -5947.9135f, -1866.5028f, + -5947.9135f, -820.4881f, + -5876.7114f, -3.5138f, + -5876.7114f, 917.6407f, + -6099.3603f, 1153.2884f, + -6021.8989f, 1638.1809f, + -6091.6176f, 2335.8892f, + -6744.9946f, 2393.4855f, + -6973.8608f, 3077.0281f, + -7068.7241f, 4376.2304f, + -7142.1211f, 4808.4331f + }; + + if (IsNorthTo(x, y, teldrassilSouthLimit, sizeof(teldrassilSouthLimit) / (2 * sizeof(float)))) + return MAP1_TELDRASSIL; + if (IsNorthTo(x, y, northMiddleLimit, sizeof(northMiddleLimit) / (2 * sizeof(float)))) + return MAP1_NORTH; + if (IsNorthTo(x, y, orgrimmarSouthLimit, sizeof(orgrimmarSouthLimit) / (2 * sizeof(float)))) + return MAP1_ORGRIMMAR; + if (IsNorthTo(x, y, durotarSouthLimit, sizeof(durotarSouthLimit) / (2 * sizeof(float)))) + return MAP1_DUROTAR; + if (IsNorthTo(x, y, valleyoftrialsSouthLimit, sizeof(valleyoftrialsSouthLimit) / (2 * sizeof(float)))) + return MAP1_VALLEY; + if (IsNorthTo(x, y, middleToSouthLimit, sizeof(middleToSouthLimit) / (2 * sizeof(float)))) + return MAP1_UPPER_MIDDLE; + if (IsNorthTo(x, y, feralasThousandNeedlesSouthLimit, sizeof(feralasThousandNeedlesSouthLimit) / (2 * sizeof(float)))) + return MAP1_LOWER_MIDDLE; + if (y > 15000.0f) + return MAP1_GMISLAND; + else + return MAP1_SOUTH; + } + case 530: + { + static float const ShattrathAreaSouthLimit[] = { + -2493.8823f, 5761.6894f, + -2593.7438f, 4768.7978f, + -1831.5280f, 3383.5705f + }; + static float const HellfireZangarSouthLimit[] = { + + -531.47265f, 8697.5830f, + -514.56945f, 7291.2763f, + -404.92804f, 6976.7958f, + -593.56475f, 6646.0634f, + -856.75695f, 6318.5507f, + -1166.2729f, 5799.7817f, + -1007.9321f, 4761.1352f, + -1831.5280f, 3383.5705f, + -2135.1586f, 2335.4426f, + -2179.3974f, 896.0285f, + }; + static float const BladeEdgeNetherSouthLimit[] = { + 2074.6831f, 8216.6113f, + 1248.3884f, 7472.7592f, + 1118.4877f, 6972.6821f, + 1212.2004f, 6106.2861f, + 1175.4729f, 5633.375f, + 1543.8314f, 3961.8886f, + }; + if (x > 5800) + return MAP530_BLOODELF; + if (y < -8600) + return MAP530_DRAENEI; + if (IsNorthTo(x, y, BladeEdgeNetherSouthLimit, sizeof(BladeEdgeNetherSouthLimit) / (2 * sizeof(float)))) + return MAP530_NORTH; + if (IsNorthTo(x, y, HellfireZangarSouthLimit, sizeof(HellfireZangarSouthLimit) / (2 * sizeof(float)))) + return MAP530_MIDDLE; + if (IsNorthTo(x, y, ShattrathAreaSouthLimit, sizeof(ShattrathAreaSouthLimit) / (2 * sizeof(float)))) + return MAP530_SHATTRATH; + return MAP530_SOUTH; + } + case 571: + { + static float const DalaranAreaSouthLimit[] = { + 5606.0f, 1458.0f, + 5100.0f, 775.0f, + 5756.0f, -659.0f + }; + static float const IcecrownStormpeaksSouthLimit[] = { + 7300.0f, 5331.0f, + 6889.0f, 4697.0f, + 6797.0f, 4193.0f, + 6250.0f, 3358.0f, + 5575.0f, 2502.0f, + 5177.0f, 1945.0f, + 5606.0f, 1458.0f, + 6200.0f, 1262.0f, + 6350.0f, 612.0f, + 6000.0f, 362.0f, + 5800.0f, -636.0f, + 6233.0f, -1746.0f, + 6616.0f, -1938.0f, + 6933.0f, -2555.0f, + 7033.0f, -3669.0f, + 7333.0f, -4115.0f, + 7975.0f, -4657.0f + }; + if (IsNorthTo(x, y, IcecrownStormpeaksSouthLimit, sizeof(IcecrownStormpeaksSouthLimit) / (2 * sizeof(float)))) + return MAP571_NORTH; + if (IsNorthTo(x, y, DalaranAreaSouthLimit, sizeof(DalaranAreaSouthLimit) / (2 * sizeof(float)))) + return MAP571_DALARAN; + return MAP571_SOUTH; + } + } + return MAP_NO_AREA; +} + void MapManager::DoForAllMapsWithMapId(uint32 mapId, std::function worker) { MapMapType::const_iterator start = i_maps.lower_bound(MapID(mapId, 0)); diff --git a/src/game/Maps/MapManager.h b/src/game/Maps/MapManager.h index ab71f2783b4..f2e5506b964 100644 --- a/src/game/Maps/MapManager.h +++ b/src/game/Maps/MapManager.h @@ -64,6 +64,7 @@ class MapManager : public MaNGOS::Singleton MapMapType; void CreateContinents(); + ContinentArea GetContinentInstanceId(uint32 mapId, float x, float y, bool* transitionArea = nullptr); Map* CreateMap(uint32, const WorldObject* obj); Map* CreateBgMap(uint32 mapid, BattleGround* bg); Map* FindMap(uint32 mapid, uint32 instanceId = 0) const; diff --git a/src/game/MotionGenerators/MotionMaster.cpp b/src/game/MotionGenerators/MotionMaster.cpp index e0273bcf4ce..08f94075fed 100644 --- a/src/game/MotionGenerators/MotionMaster.cpp +++ b/src/game/MotionGenerators/MotionMaster.cpp @@ -419,12 +419,12 @@ void MotionMaster::MovePointTOL(uint32 id, float x, float y, float z, bool takeO Mutate(new PointTOLMovementGenerator(id, x, y, z, takeOff, forcedMovement)); } -void MotionMaster::MovePath(std::vector& path, ForcedMovement forcedMovement, bool flying) +void MotionMaster::MovePath(std::vector& path, ForcedMovement forcedMovement, bool flying, bool cyclic) { - return MovePath(path, 0, forcedMovement, flying); + return MovePath(path, 0, forcedMovement, flying, cyclic); } -void MotionMaster::MovePath(std::vector& path, float o, ForcedMovement forcedMovement, bool flying) +void MotionMaster::MovePath(std::vector& path, float o, ForcedMovement forcedMovement, bool flying, bool cyclic) { if (path.empty()) return; @@ -437,7 +437,7 @@ void MotionMaster::MovePath(std::vector& path, float o, ForcedMove else DEBUG_FILTER_LOG(LOG_FILTER_AI_AND_MOVEGENSS, "%s follows a pre-calculated path to X: %f Y: %f Z: %f", m_owner->GetGuidStr().c_str(), x, y, z); - Mutate(new FixedPathMovementGenerator(path, o, forcedMovement, flying)); + Mutate(new FixedPathMovementGenerator(path, o, forcedMovement, flying, 0.0f, 0, cyclic)); } void MotionMaster::MovePath(int32 pathId, WaypointPathOrigin wpOrigin /*= PATH_NO_PATH*/, ForcedMovement forcedMovement, bool flying, float speed, bool cyclic, ObjectGuid guid/* = ObjectGuid()*/) diff --git a/src/game/MotionGenerators/MotionMaster.h b/src/game/MotionGenerators/MotionMaster.h index 0211c9e766b..a8cfd934df2 100644 --- a/src/game/MotionGenerators/MotionMaster.h +++ b/src/game/MotionGenerators/MotionMaster.h @@ -175,8 +175,8 @@ class MotionMaster : private std::stack void MovePoint(uint32 id, Position const& position, ForcedMovement forcedMovement = FORCED_MOVEMENT_NONE, float speed = 0.f, bool generatePath = true, ObjectGuid guid = ObjectGuid(), uint32 relayId = 0); void MovePoint(uint32 id, float x, float y, float z, ForcedMovement forcedMovement = FORCED_MOVEMENT_NONE, bool generatePath = true); void MovePointTOL(uint32 id, float x, float y, float z, bool takeOff, ForcedMovement forcedMovement = FORCED_MOVEMENT_NONE); - void MovePath(std::vector& path, ForcedMovement forcedMovement = FORCED_MOVEMENT_NONE, bool flying = false); - void MovePath(std::vector& path, float o, ForcedMovement forcedMovement = FORCED_MOVEMENT_NONE, bool flying = false); + void MovePath(std::vector& path, ForcedMovement forcedMovement = FORCED_MOVEMENT_NONE, bool flying = false, bool cyclic = true); + void MovePath(std::vector& path, float o, ForcedMovement forcedMovement = FORCED_MOVEMENT_NONE, bool flying = false, bool cyclic = true); // MovePath can not change speed or flying mid path due to how it works - if you wish to do that, split it into two paths void MovePath(int32 pathId = 0, WaypointPathOrigin wpOrigin = PATH_NO_PATH, ForcedMovement forcedMovement = FORCED_MOVEMENT_NONE, bool flying = false, float speed = 0.f, bool cyclic = false, ObjectGuid guid = ObjectGuid()); void MoveRetreat(float x, float y, float z, float o, uint32 delay); diff --git a/src/game/MotionGenerators/MovementHandler.cpp b/src/game/MotionGenerators/MovementHandler.cpp index 8825ea6f1ba..9a973bfde2b 100644 --- a/src/game/MotionGenerators/MovementHandler.cpp +++ b/src/game/MotionGenerators/MovementHandler.cpp @@ -289,9 +289,22 @@ void WorldSession::HandleMoveTeleportAckOpcode(WorldPacket& recv_data) WorldLocation const& dest = plMover->GetTeleportDest(); + // send MSG_MOVE_TELEPORT to observers around old position + SendTeleportToObservers(dest.coord_x, dest.coord_y, dest.coord_z, dest.orientation); + plMover->SetDelayedZoneUpdate(false, 0); plMover->SetPosition(dest.coord_x, dest.coord_y, dest.coord_z, dest.orientation, true); + plMover->m_movementInfo.ChangePosition(dest.coord_x, dest.coord_y, dest.coord_z, dest.orientation); + +#ifdef ENABLE_PLAYERBOTS + // interrupt moving for bot if any + if (plMover->GetPlayerbotAI() && !plMover->GetMotionMaster()->empty()) + { + if (MovementGenerator* movgen = plMover->GetMotionMaster()->top()) + movgen->Interrupt(*plMover); + } +#endif plMover->SetFallInformation(0, dest.coord_z); @@ -304,15 +317,26 @@ void WorldSession::HandleMoveTeleportAckOpcode(WorldPacket& recv_data) uint32 newzone, newarea; plMover->GetZoneAndAreaId(newzone, newarea); - plMover->UpdateZone(newzone, newarea); // new zone if (old_zone != newzone) + plMover->UpdateZone(newzone, newarea); + + // honorless target + if (plMover->pvpInfo.inPvPEnforcedArea) + plMover->CastSpell(plMover, 2479, TRIGGERED_OLD_TRIGGERED); + +#ifdef ENABLE_PLAYERBOTS + // reset moving for bot if any + if (plMover->GetPlayerbotAI() && !plMover->GetMotionMaster()->empty()) { - // honorless target - if (plMover->pvpInfo.inPvPEnforcedArea) - plMover->CastSpell(plMover, 2479, TRIGGERED_OLD_TRIGGERED); + if (MovementGenerator* movgen = plMover->GetMotionMaster()->top()) + movgen->Reset(*plMover); } +#endif + + // send MSG_MOVE_TELEPORT to observers around new position + SendTeleportToObservers(dest.coord_x, dest.coord_y, dest.coord_z, dest.orientation); m_anticheat->Teleport({ dest.coord_x, dest.coord_y, dest.coord_z, dest.orientation }); @@ -563,6 +587,17 @@ void WorldSession::SendKnockBack(Unit* who, float angle, float horizontalSpeed, m_anticheat->KnockBack(horizontalSpeed, -verticalSpeed, vcos, vsin); } +void WorldSession::SendTeleportToObservers(float x, float y, float z, float orientation) +{ + WorldPacket data(MSG_MOVE_TELEPORT, 64); + data << _player->GetPackGUID(); + // copy moveinfo to change position to where player is teleporting + MovementInfo moveInfo = _player->m_movementInfo; + moveInfo.ChangePosition(x, y, z, orientation); + data << moveInfo; + _player->SendMessageToSetExcept(data, _player); +} + void WorldSession::HandleMoveFlagChangeOpcode(WorldPacket& recv_data) { DEBUG_LOG("%s", recv_data.GetOpcodeName()); diff --git a/src/game/MotionGenerators/PathFinder.cpp b/src/game/MotionGenerators/PathFinder.cpp index 76fb46f3fec..60f9c4e4f17 100644 --- a/src/game/MotionGenerators/PathFinder.cpp +++ b/src/game/MotionGenerators/PathFinder.cpp @@ -32,6 +32,28 @@ #include ////////////////// PathFinder ////////////////// +PathFinder::PathFinder() : + m_polyLength(0), m_type(PATHFIND_BLANK), + m_useStraightPath(false), m_forceDestination(false), m_straightLine(false), m_pointPathLimit(MAX_POINT_PATH_LENGTH), // TODO: Fix legitimate long paths + m_sourceUnit(nullptr), m_navMesh(nullptr), m_navMeshQuery(nullptr), m_cachedPoints(m_pointPathLimit* VERTEX_SIZE), m_pathPolyRefs(m_pointPathLimit), m_smoothPathPolyRefs(m_pointPathLimit), m_defaultMapId(0) +{ + //MMAP::MMapManager* mmap = MMAP::MMapFactory::createOrGetMMapManager(); + //m_defaultNavMeshQuery = mmap->GetNavMeshQuery(m_sourceUnit->GetMapId(), m_sourceUnit->GetInstanceId()); + + //createFilter(); +} + +PathFinder::PathFinder(uint32 mapId, uint32 instanceId) : + m_polyLength(0), m_type(PATHFIND_BLANK), + m_useStraightPath(false), m_forceDestination(false), m_straightLine(false), m_pointPathLimit(MAX_POINT_PATH_LENGTH), // TODO: Fix legitimate long paths + m_sourceUnit(nullptr), m_navMesh(nullptr), m_navMeshQuery(nullptr), m_cachedPoints(m_pointPathLimit* VERTEX_SIZE), m_pathPolyRefs(m_pointPathLimit), m_smoothPathPolyRefs(m_pointPathLimit), m_defaultMapId(mapId) +{ + MMAP::MMapManager* mmap = MMAP::MMapFactory::createOrGetMMapManager(); + m_defaultNavMeshQuery = mmap->GetNavMeshQuery(mapId, instanceId); + + createFilter(); +} + PathFinder::PathFinder(const Unit* owner, bool ignoreNormalization) : m_polyLength(0), m_type(PATHFIND_BLANK), m_useStraightPath(false), m_forceDestination(false), m_straightLine(false), m_pointPathLimit(MAX_POINT_PATH_LENGTH), // TODO: Fix legitimate long paths @@ -50,12 +72,13 @@ PathFinder::PathFinder(const Unit* owner, bool ignoreNormalization) : PathFinder::~PathFinder() { - DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ PathFinder::~PathInfo() for %u \n", m_sourceUnit->GetGUIDLow()); + //if (m_sourceUnit) + // DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ PathFinder::~PathInfo() for %u \n", m_sourceUnit->GetGUIDLow()); } void PathFinder::SetCurrentNavMesh() { - if (MMAP::MMapFactory::IsPathfindingEnabled(m_sourceUnit->GetMapId(), m_sourceUnit)) + if (m_sourceUnit && MMAP::MMapFactory::IsPathfindingEnabled(m_sourceUnit->GetMapId(), m_sourceUnit)) { MMAP::MMapManager* mmap = MMAP::MMapFactory::createOrGetMMapManager(); if (GenericTransport* transport = m_sourceUnit->GetTransport()) @@ -68,6 +91,15 @@ void PathFinder::SetCurrentNavMesh() m_navMeshQuery = m_defaultNavMeshQuery; } + if (m_navMeshQuery) + m_navMesh = m_navMeshQuery->getAttachedNavMesh(); + } + else if (!m_sourceUnit && MMAP::MMapFactory::IsPathfindingEnabled(m_defaultMapId, nullptr)) + { + MMAP::MMapManager* mmap = MMAP::MMapFactory::createOrGetMMapManager(); + + m_navMeshQuery = m_defaultNavMeshQuery; + if (m_navMeshQuery) m_navMesh = m_navMeshQuery->getAttachedNavMesh(); } @@ -92,13 +124,14 @@ bool PathFinder::calculate(Vector3 const& start, Vector3 const& dest, bool force return false; #ifdef BUILD_METRICS - metric::duration meas("pathfinder.calculate", { - { "entry", std::to_string(m_sourceUnit->GetEntry()) }, - { "guid", std::to_string(m_sourceUnit->GetGUIDLow()) }, - { "unit_type", std::to_string(m_sourceUnit->GetGUIDHigh()) }, - { "map_id", std::to_string(m_sourceUnit->GetMapId()) }, - { "instance_id", std::to_string(m_sourceUnit->GetInstanceId()) } - }, 1000); + if (m_sourceUnit) + metric::duration meas("pathfinder.calculate", { + { "entry", std::to_string(m_sourceUnit->GetEntry()) }, + { "guid", std::to_string(m_sourceUnit->GetGUIDLow()) }, + { "unit_type", std::to_string(m_sourceUnit->GetGUIDHigh()) }, + { "map_id", std::to_string(m_sourceUnit->GetMapId()) }, + { "instance_id", std::to_string(m_sourceUnit->GetInstanceId()) } + }, 1000); #endif //if (GenericTransport* transport = m_sourceUnit->GetTransport()) @@ -113,11 +146,12 @@ bool PathFinder::calculate(Vector3 const& start, Vector3 const& dest, bool force SetCurrentNavMesh(); - DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ PathFinder::calculate() for %u \n", m_sourceUnit->GetGUIDLow()); + if (m_sourceUnit) + DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ PathFinder::calculate() for %u \n", m_sourceUnit->GetGUIDLow()); // make sure navMesh works - we can run on map w/o mmap // check if the start and end point have a .mmtile loaded (can we pass via not loaded tile on the way?) - if (!m_navMesh || !m_navMeshQuery || m_sourceUnit->hasUnitState(UNIT_STAT_IGNORE_PATHFINDING) || + if (!m_navMesh || !m_navMeshQuery || (m_sourceUnit && m_sourceUnit->hasUnitState(UNIT_STAT_IGNORE_PATHFINDING)) || !HaveTile(start) || !HaveTile(dest)) { BuildShortcut(); @@ -131,6 +165,128 @@ bool PathFinder::calculate(Vector3 const& start, Vector3 const& dest, bool force return true; } +void PathFinder::setArea(uint32 mapId, float x, float y, float z, uint32 area, float range) +{ + if (!MaNGOS::IsValidMapCoord(x, y, z)) + return; + + MMAP::MMapManager* mmap = MMAP::MMapFactory::createOrGetMMapManager(); + + dtNavMeshQuery const* query = mmap->GetNavMeshQuery(mapId, 0); + dtNavMesh const* cnavMesh = mmap->GetNavMesh(mapId, 0); + dtNavMesh* navMesh = const_cast (cnavMesh); + dtQueryFilter m_filter; + + uint16 includeFlags = 0; + uint16 excludeFlags = 0; + + + includeFlags |= (NAV_GROUND | NAV_WATER); + excludeFlags |= (NAV_MAGMA_SLIME | NAV_GROUND_STEEP); + + + m_filter.setIncludeFlags(includeFlags); + m_filter.setExcludeFlags(excludeFlags); + + dtPolyRef polyRef = INVALID_POLYREF; + + + float point[VERTEX_SIZE] = { y, z, x }; + float extents[VERTEX_SIZE] = { 5.0f, 5.0f, 5.0f }; // bounds of poly search area + float closestPoint[VERTEX_SIZE] = { 0.0f, 0.0f, 0.0f }; + //unsigned int dtResult = INVALID_POLYREF; + //m_navMeshQuery->getNodePool(); + + dtStatus dtResult = query->findNearestPoly(point, extents, &m_filter, &polyRef, closestPoint); + static const int MAX_POLYS = 2560; + dtPolyRef m_polys[MAX_POLYS]; + dtPolyRef m_parent[MAX_POLYS]; + int m_npolys; + + if (dtResult == DT_FAILURE || polyRef == INVALID_POLYREF) + return; + + query->findPolysAroundCircle(polyRef, closestPoint, range, &m_filter, m_polys, m_parent, 0, &m_npolys, MAX_POLYS); + + if (dtResult == DT_FAILURE || polyRef == INVALID_POLYREF) + return; + + for (int i = 0; i < m_npolys; i++) + { + unsigned char curArea; + dtStatus status = navMesh->getPolyArea(m_polys[i], &curArea); + + if (curArea != 8 && curArea < area) + dtStatus status = navMesh->setPolyArea(m_polys[i], area); + } +} + +uint32 PathFinder::getArea(uint32 mapId, float x, float y, float z) +{ + if (!MaNGOS::IsValidMapCoord(x, y, z)) + return 99; + + MMAP::MMapManager* mmap = MMAP::MMapFactory::createOrGetMMapManager(); + + dtNavMeshQuery const* query = mmap->GetNavMeshQuery(mapId, 0); + dtNavMesh const* cnavMesh = mmap->GetNavMesh(mapId, 0); + dtNavMesh* navMesh = const_cast (cnavMesh); + dtQueryFilter m_filter; + dtPolyRef polyRef = INVALID_POLYREF; + + + float point[VERTEX_SIZE] = { y, z, x }; + float extents[VERTEX_SIZE] = { 5.0f, 5.0f, 5.0f }; // bounds of poly search area + float closestPoint[VERTEX_SIZE] = { 0.0f, 0.0f, 0.0f }; + + dtStatus dtResult = query->findNearestPoly(point, extents, &m_filter, &polyRef, closestPoint); + + if (dtResult == DT_FAILURE || polyRef == INVALID_POLYREF) + return 99; + + unsigned char area; + + dtStatus status = navMesh->getPolyArea(polyRef, &area); + + return area; +} + +unsigned short PathFinder::getFlags(uint32 mapId, float x, float y, float z) +{ + if (!MaNGOS::IsValidMapCoord(x, y, z)) + return 0; + + MMAP::MMapManager* mmap = MMAP::MMapFactory::createOrGetMMapManager(); + + dtNavMeshQuery const* query = mmap->GetNavMeshQuery(mapId, 0); + dtNavMesh const* cnavMesh = mmap->GetNavMesh(mapId, 0); + dtNavMesh* navMesh = const_cast (cnavMesh); + dtQueryFilter m_filter; + dtPolyRef polyRef = INVALID_POLYREF; + + + float point[VERTEX_SIZE] = { y, z, x }; + float extents[VERTEX_SIZE] = { 5.0f, 5.0f, 5.0f }; // bounds of poly search area + float closestPoint[VERTEX_SIZE] = { 0.0f, 0.0f, 0.0f }; + + dtStatus dtResult = query->findNearestPoly(point, extents, &m_filter, &polyRef, closestPoint); + + if (dtResult == DT_FAILURE || polyRef == INVALID_POLYREF) + return 0; + + unsigned short flags; + + dtStatus status = navMesh->getPolyFlags(polyRef, &flags); + + return flags; +} + + +void PathFinder::setAreaCost(uint32 area, float cost) +{ + m_filter.setAreaCost(area, cost); +} + dtPolyRef PathFinder::getPathPolyByPosition(const dtPolyRef* polyPath, uint32 polyPathSize, const float* point, float* distance) const { if (!polyPath || !polyPathSize) @@ -202,7 +358,7 @@ dtPolyRef PathFinder::getPolyByLocation(const float* point, float* distance) void PathFinder::BuildPolyPath(const Vector3& startPos, const Vector3& endPos) { // *** getting start/end poly logic *** - if (m_sourceUnit->GetMap()->IsDungeon()) + if (m_sourceUnit && m_sourceUnit->GetMap()->IsDungeon()) { float distance = sqrt((endPos.x - startPos.x) * (endPos.x - startPos.x) + (endPos.y - startPos.y) * (endPos.y - startPos.y) + (endPos.z - startPos.z) * (endPos.z - startPos.z)); if (distance > 300.f) @@ -215,8 +371,8 @@ void PathFinder::BuildPolyPath(const Vector3& startPos, const Vector3& endPos) m_pathPolyRefs.resize(m_pointPathLimit); } float distToStartPoly, distToEndPoly; - float startPoint[VERTEX_SIZE] = {startPos.y, startPos.z, startPos.x}; - float endPoint[VERTEX_SIZE] = {endPos.y, endPos.z, endPos.x}; + float startPoint[VERTEX_SIZE] = { startPos.y, startPos.z, startPos.x }; + float endPoint[VERTEX_SIZE] = { endPos.y, endPos.z, endPos.x }; dtPolyRef startPoly = getPolyByLocation(startPoint, &distToStartPoly); dtPolyRef endPoly = getPolyByLocation(endPoint, &distToEndPoly); @@ -232,13 +388,15 @@ void PathFinder::BuildPolyPath(const Vector3& startPos, const Vector3& endPos) BuildShortcut(); // Check for swimming or flying shortcut - if ((startPoly == INVALID_POLYREF && m_sourceUnit->GetTerrain()->IsSwimmable(startPos.x, startPos.y, startPos.z)) || - (endPoly == INVALID_POLYREF && m_sourceUnit->GetTerrain()->IsSwimmable(endPos.x, endPos.y, endPos.z))) + if (m_sourceUnit && ((startPoly == INVALID_POLYREF && m_sourceUnit->GetTerrain()->IsSwimmable(startPos.x, startPos.y, startPos.z)) || + (endPoly == INVALID_POLYREF && m_sourceUnit->GetTerrain()->IsSwimmable(endPos.x, endPos.y, endPos.z)))) m_type = m_sourceUnit->CanSwim() ? PathType(PATHFIND_NORMAL | PATHFIND_NOT_USING_PATH) : PATHFIND_NOPATH; - else + else if (m_sourceUnit) { if (m_sourceUnit->GetTypeId() != TYPEID_PLAYER) m_type = m_sourceUnit->CanFly() ? PathType(PATHFIND_NORMAL | PATHFIND_NOT_USING_PATH) : PATHFIND_NOPATH; + else if (((Player*)m_sourceUnit)->GetPlayerbotAI()) //Allow bots to use flying pathfinding. + m_type = m_sourceUnit->CanFly() ? PathType(PATHFIND_NORMAL | PATHFIND_NOT_USING_PATH) : PATHFIND_NOPATH; else m_type = PATHFIND_NOPATH; } @@ -254,7 +412,7 @@ void PathFinder::BuildPolyPath(const Vector3& startPos, const Vector3& endPos) bool buildShotrcut = false; Vector3 p = (distToStartPoly > 7.0f) ? startPos : endPos; - if (m_sourceUnit->GetTerrain()->IsUnderWater(p.x, p.y, p.z)) + if (m_sourceUnit && m_sourceUnit->GetTerrain()->IsUnderWater(p.x, p.y, p.z)) { DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ BuildPolyPath :: underWater case\n"); if (m_sourceUnit->CanSwim()) @@ -263,7 +421,7 @@ void PathFinder::BuildPolyPath(const Vector3& startPos, const Vector3& endPos) else { DEBUG_FILTER_LOG(LOG_FILTER_PATHFINDING, "++ BuildPolyPath :: flying case\n"); - if (m_sourceUnit->CanFly()) + if (m_sourceUnit && m_sourceUnit->CanFly()) buildShotrcut = true; } @@ -316,10 +474,11 @@ void PathFinder::BuildPolyPath(const Vector3& startPos, const Vector3& endPos) // here to catch few bugs if (m_pathPolyRefs[pathStartIndex] == INVALID_POLYREF) { - sLog.outError("Invalid poly ref in BuildPolyPath. polyLength: %u, pathStartIndex: %u," - " startPos: %s, endPos: %s, mapId: %u", - m_polyLength, pathStartIndex, startPos.toString().c_str(), endPos.toString().c_str(), - m_sourceUnit->GetMapId()); + if (m_sourceUnit) + sLog.outError("Invalid poly ref in BuildPolyPath. polyLength: %u, pathStartIndex: %u," + " startPos: %s, endPos: %s, mapId: %u", + m_polyLength, pathStartIndex, startPos.toString().c_str(), endPos.toString().c_str(), + m_sourceUnit->GetMapId()); break; } @@ -456,30 +615,30 @@ void PathFinder::BuildPolyPath(const Vector3& startPos, const Vector3& endPos) if (!m_straightLine) { dtResult = m_navMeshQuery->findPath( - startPoly, // start polygon - endPoly, // end polygon - startPoint, // start position - endPoint, // end position - &m_filter, // polygon search filter - m_pathPolyRefs.data(), // [out] path - (int*)&m_polyLength, - m_pointPathLimit); // max number of polygons in output path + startPoly, // start polygon + endPoly, // end polygon + startPoint, // start position + endPoint, // end position + &m_filter, // polygon search filter + m_pathPolyRefs.data(), // [out] path + (int*)&m_polyLength, + m_pointPathLimit / 2); // max number of polygons in output path } else { float hit = 0.0f; - float hitNormal[3] = {0.0f, 0.0f, 0.0f}; + float hitNormal[3] = { 0.0f, 0.0f, 0.0f }; dtResult = m_navMeshQuery->raycast( - startPoly, - startPoint, - endPoint, - &m_filter, - &hit, - hitNormal, - m_pathPolyRefs.data(), - (int*)&m_polyLength, - m_pointPathLimit); + startPoly, + startPoint, + endPoint, + &m_filter, + &hit, + hitNormal, + m_pathPolyRefs.data(), + (int*)&m_polyLength, + m_pointPathLimit / 2); // raycast() sets hit to FLT_MAX if there is a ray between start and end if (hit != FLT_MAX) @@ -528,7 +687,8 @@ void PathFinder::BuildPolyPath(const Vector3& startPos, const Vector3& endPos) if (!m_polyLength || dtStatusFailed(dtResult)) { // only happens if we passed bad data to findPath(), or navmesh is messed up - sLog.outError("%u's Path Build failed: 0 length path", m_sourceUnit->GetGUIDLow()); + if (m_sourceUnit) + sLog.outError("%u's Path Build failed: 0 length path", m_sourceUnit->GetGUIDLow()); BuildShortcut(); m_type = PATHFIND_NOPATH; return; @@ -716,7 +876,7 @@ void PathFinder::BuildPointPath(const float* startPoint, const float* endPoint) // force the given destination, if needed if (m_forceDestination && - (!(m_type & PATHFIND_NORMAL) || !inRange(getEndPosition(), getActualEndPosition(), 1.0f, 1.0f))) + (!(m_type & PATHFIND_NORMAL) || !inRange(getEndPosition(), getActualEndPosition(), 1.0f, 1.0f))) { // we may want to keep partial subpath if (dist3DSqr(getActualEndPosition(), getEndPosition()) < 0.3f * dist3DSqr(getStartPosition(), getEndPosition())) @@ -738,10 +898,12 @@ void PathFinder::BuildPointPath(const float* startPoint, const float* endPoint) void PathFinder::NormalizePath() { - if (!sWorld.getConfig(CONFIG_BOOL_PATH_FIND_NORMALIZE_Z) || m_ignoreNormalization) + if (!sWorld.getConfig(CONFIG_BOOL_PATH_FIND_NORMALIZE_Z) || m_ignoreNormalization || !m_sourceUnit) return; - GenericTransport* transport = m_sourceUnit->GetTransport(); + GenericTransport* transport; + if (m_sourceUnit) + transport = m_sourceUnit->GetTransport(); for (auto& m_pathPoint : m_pathPoints) { @@ -776,7 +938,25 @@ void PathFinder::createFilter() uint16 includeFlags = 0; uint16 excludeFlags = 0; - if (m_sourceUnit->GetTypeId() == TYPEID_UNIT) + if (!m_sourceUnit || m_sourceUnit->GetTypeId() == TYPEID_PLAYER) + { + // perfect support not possible, just stay 'safe' + if (!m_sourceUnit || ((Player*)m_sourceUnit)->GetPlayerbotAI()) //Blank or bot-navigation + { + includeFlags |= (NAV_GROUND | NAV_WATER); + excludeFlags |= (NAV_MAGMA_SLIME | NAV_GROUND_STEEP); + + m_filter.setAreaCost(9, 20.0f); //Water + m_filter.setAreaCost(12, 5.0f); //Mob proximity + m_filter.setAreaCost(13, 20.0f); //Mob agro + } + else + { + includeFlags |= (NAV_GROUND | NAV_WATER | NAV_GROUND_STEEP); + excludeFlags |= (NAV_MAGMA_SLIME); + } + } + else if (m_sourceUnit->GetTypeId() == TYPEID_UNIT) { Creature* creature = (Creature*)m_sourceUnit; if (creature->CanWalk()) @@ -786,11 +966,6 @@ void PathFinder::createFilter() if (creature->CanSwim()) includeFlags |= (NAV_WATER | NAV_MAGMA_SLIME); // swim } - else if (m_sourceUnit->GetTypeId() == TYPEID_PLAYER) - { - // perfect support not possible, just stay 'safe' - includeFlags |= (NAV_GROUND | NAV_WATER); - } m_filter.setIncludeFlags(includeFlags); m_filter.setExcludeFlags(excludeFlags); @@ -802,12 +977,12 @@ void PathFinder::updateFilter() { // allow creatures to cheat and use different movement types if they are moved // forcefully into terrain they can't normally move in - if (m_sourceUnit->IsInWater() || m_sourceUnit->IsUnderwater()) + if (m_sourceUnit && (m_sourceUnit->IsInWater() || m_sourceUnit->IsUnderwater())) { uint16 includedFlags = m_filter.getIncludeFlags(); includedFlags |= getNavTerrain(m_sourceUnit->GetPositionX(), - m_sourceUnit->GetPositionY(), - m_sourceUnit->GetPositionZ()); + m_sourceUnit->GetPositionY(), + m_sourceUnit->GetPositionZ()); m_filter.setIncludeFlags(includedFlags); } @@ -816,7 +991,7 @@ void PathFinder::updateFilter() NavTerrainFlag PathFinder::getNavTerrain(float x, float y, float z) const { GridMapLiquidData data; - if (m_sourceUnit->GetTerrain()->getLiquidStatus(x, y, z, MAP_ALL_LIQUIDS, &data) == LIQUID_MAP_NO_WATER) + if (m_sourceUnit && m_sourceUnit->GetTerrain()->getLiquidStatus(x, y, z, MAP_ALL_LIQUIDS, &data) == LIQUID_MAP_NO_WATER) return NAV_GROUND; switch (data.type_flags) @@ -834,11 +1009,11 @@ NavTerrainFlag PathFinder::getNavTerrain(float x, float y, float z) const bool PathFinder::HaveTile(const Vector3& p) const { - if (m_sourceUnit->GetTransport()) + if (m_sourceUnit && m_sourceUnit->GetTransport()) return true; int tx = -1, ty = -1; - float point[VERTEX_SIZE] = {p.y, p.z, p.x}; + float point[VERTEX_SIZE] = { p.y, p.z, p.x }; m_navMesh->calcTileLoc(point, &tx, &ty); @@ -900,8 +1075,8 @@ uint32 PathFinder::fixupCorridor(dtPolyRef* path, uint32 npath, uint32 maxPath, } bool PathFinder::getSteerTarget(const float* startPos, const float* endPos, - float minTargetDist, const dtPolyRef* path, uint32 pathSize, - float* steerPos, unsigned char& steerPosFlag, dtPolyRef& steerPosRef) const + float minTargetDist, const dtPolyRef* path, uint32 pathSize, + float* steerPos, unsigned char& steerPosFlag, dtPolyRef& steerPosRef) const { // Find steer target. static const uint32 MAX_STEER_POINTS = 3; @@ -910,7 +1085,7 @@ bool PathFinder::getSteerTarget(const float* startPos, const float* endPos, dtPolyRef steerPathPolys[MAX_STEER_POINTS]; uint32 nsteerPath = 0; dtStatus dtResult = m_navMeshQuery->findStraightPath(startPos, endPos, path, pathSize, - steerPath, steerPathFlags, steerPathPolys, (int*)&nsteerPath, MAX_STEER_POINTS); + steerPath, steerPathFlags, steerPathPolys, (int*)&nsteerPath, MAX_STEER_POINTS); if (!nsteerPath || dtStatusFailed(dtResult)) return false; @@ -920,7 +1095,7 @@ bool PathFinder::getSteerTarget(const float* startPos, const float* endPos, { // Stop at Off-Mesh link or when point is further than slop away. if ((steerPathFlags[ns] & DT_STRAIGHTPATH_OFFMESH_CONNECTION) || - !inRangeYZX(&steerPath[ns * VERTEX_SIZE], startPos, minTargetDist, 1000.0f)) + !inRangeYZX(&steerPath[ns * VERTEX_SIZE], startPos, minTargetDist, 1000.0f)) break; ++ns; } @@ -937,8 +1112,8 @@ bool PathFinder::getSteerTarget(const float* startPos, const float* endPos, } dtStatus PathFinder::findSmoothPath(const float* startPos, const float* endPos, - const dtPolyRef* polyPath, uint32 polyPathSize, - float* smoothPath, int* smoothPathSize, uint32 maxSmoothPathSize) + const dtPolyRef* polyPath, uint32 polyPathSize, + float* smoothPath, int* smoothPathSize, uint32 maxSmoothPathSize) { *smoothPathSize = 0; uint32 nsmoothPath = 0; @@ -1058,7 +1233,7 @@ dtStatus PathFinder::findSmoothPath(const float* startPos, const float* endPos, *smoothPathSize = nsmoothPath; // this is most likely a loop - return nsmoothPath < m_pointPathLimit ? DT_SUCCESS : DT_FAILURE; + return nsmoothPath <= m_pointPathLimit ? DT_SUCCESS : DT_FAILURE; } void PathFinder::ComputePathToRandomPoint(Vector3 const& startPoint, float maxRange) diff --git a/src/game/MotionGenerators/PathFinder.h b/src/game/MotionGenerators/PathFinder.h index a5e12c3b1d3..f6ad1bf9444 100644 --- a/src/game/MotionGenerators/PathFinder.h +++ b/src/game/MotionGenerators/PathFinder.h @@ -16,6 +16,10 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#ifndef IKE_PATHFINDER +#define IKE_PATHFINDER +#endif + #ifndef MANGOS_PATH_FINDER_H #define MANGOS_PATH_FINDER_H @@ -34,8 +38,8 @@ class Unit; // 74*4.0f=296y number_of_points*interval = max_path_len // this is way more than actual evade range // I think we can safely cut those down even more -#define MAX_PATH_LENGTH 74 -#define MAX_POINT_PATH_LENGTH 74 +#define MAX_PATH_LENGTH 148 //This value is doubled from the original and then used only half by findpath. If the same value is used by findpath and findsmooth path no result will be found by the second at max length. +#define MAX_POINT_PATH_LENGTH 148 #define SMOOTH_PATH_STEP_SIZE 4.0f #define SMOOTH_PATH_SLOP 0.3f @@ -55,18 +59,20 @@ static float FarPolySearchBound[VERTEX_SIZE] = { 10.0f, 10.0f, 10.0f }; enum PathType { - PATHFIND_BLANK = 0x0000, // path not built yet - PATHFIND_NORMAL = 0x0001, // normal path - PATHFIND_SHORTCUT = 0x0002, // travel through obstacles, terrain, air, etc (old behavior) - PATHFIND_INCOMPLETE = 0x0004, // we have partial path to follow - getting closer to target - PATHFIND_NOPATH = 0x0008, // no valid path at all or error in generating one + PATHFIND_BLANK = 0x0000, // path not built yet + PATHFIND_NORMAL = 0x0001, // normal path + PATHFIND_SHORTCUT = 0x0002, // travel through obstacles, terrain, air, etc (old behavior) + PATHFIND_INCOMPLETE = 0x0004, // we have partial path to follow - getting closer to target + PATHFIND_NOPATH = 0x0008, // no valid path at all or error in generating one PATHFIND_NOT_USING_PATH = 0x0010, // used when we are either flying/swiming or on map w/o mmaps - PATHFIND_SHORT = 0x0020, // path is longer or equal to its limited path length + PATHFIND_SHORT = 0x0020, // path is longer or equal to its limited path length }; class PathFinder { public: + PathFinder(); + PathFinder(uint32 mapId, uint32 instanceId = 0); PathFinder(Unit const* owner, bool ignoreNormalization = false); ~PathFinder(); @@ -90,6 +96,12 @@ class PathFinder PointsArray& getPath() { return m_pathPoints; } PathType getPathType() const { return m_type; } + void setArea(uint32 mapId, float x, float y, float z, uint32 area = 1, float range = 10.0f); + uint32 getArea(uint32 mapId, float x, float y, float z); + unsigned short getFlags(uint32 mapId, float x, float y, float z); + + void setAreaCost(uint32 area = 1, float cost = 0.0f); + private: PointsArray m_pathPoints; // our actual (x,y,z) path to the target diff --git a/src/game/MotionGenerators/PathMovementGenerator.cpp b/src/game/MotionGenerators/PathMovementGenerator.cpp index dd984d0726a..9a533227d99 100644 --- a/src/game/MotionGenerators/PathMovementGenerator.cpp +++ b/src/game/MotionGenerators/PathMovementGenerator.cpp @@ -26,8 +26,8 @@ #include -AbstractPathMovementGenerator::AbstractPathMovementGenerator(const Movement::PointsArray& path, float orientation, int32 offset/* = 0*/) : - m_pathIndex(offset), m_orientation(orientation), m_speedChanged(false), m_firstCycle(false), m_startPoint(0) +AbstractPathMovementGenerator::AbstractPathMovementGenerator(const Movement::PointsArray& path, float orientation, int32 offset/* = 0*/, bool cyclic/* = true*/) : + m_pathIndex(offset), m_orientation(orientation), m_speedChanged(false), m_firstCycle(false), m_startPoint(0), m_cyclic(cyclic) { for (size_t i = 0; i < path.size(); ++i) m_path[i] = { path[i].x, path[i].y, path[i].z, ((i + 1) == path.size() ? orientation : 0), 0, 0 }; diff --git a/src/game/MotionGenerators/PathMovementGenerator.h b/src/game/MotionGenerators/PathMovementGenerator.h index f4d91025e86..a139fad6256 100644 --- a/src/game/MotionGenerators/PathMovementGenerator.h +++ b/src/game/MotionGenerators/PathMovementGenerator.h @@ -29,7 +29,7 @@ class AbstractPathMovementGenerator : public MovementGenerator { public: - explicit AbstractPathMovementGenerator(const Movement::PointsArray& path, float orientation = 0, int32 offset = 0); + explicit AbstractPathMovementGenerator(const Movement::PointsArray& path, float orientation = 0, int32 offset = 0, bool cyclic = true); explicit AbstractPathMovementGenerator(const WaypointPath* path, int32 offset = 0, bool cyclic = false, ObjectGuid guid = ObjectGuid()); void Initialize(Unit& owner) override; @@ -61,10 +61,10 @@ class AbstractPathMovementGenerator : public MovementGenerator class FixedPathMovementGenerator : public AbstractPathMovementGenerator { public: - FixedPathMovementGenerator(const Movement::PointsArray &path, float orientation, uint32 forcedMovement, bool flying = false, float speed = 0, int32 offset = 0) : - AbstractPathMovementGenerator(path, orientation, offset), m_flying(flying), m_speed(speed), m_forcedMovement(forcedMovement) {} - FixedPathMovementGenerator(const Movement::PointsArray& path, uint32 forcedMovement, bool flying = false, float speed = 0, int32 offset = 0) : - FixedPathMovementGenerator(path, 0, forcedMovement, flying, speed, offset) {} + FixedPathMovementGenerator(const Movement::PointsArray& path, float orientation, uint32 forcedMovement, bool flying = false, float speed = 0, int32 offset = 0, bool cyclic = true) : + AbstractPathMovementGenerator(path, orientation, offset, cyclic), m_flying(flying), m_speed(speed), m_forcedMovement(forcedMovement) {} + FixedPathMovementGenerator(const Movement::PointsArray& path, uint32 forcedMovement, bool flying = false, float speed = 0, int32 offset = 0, bool cyclic = true) : + FixedPathMovementGenerator(path, 0, forcedMovement, flying, speed, offset, cyclic) {} FixedPathMovementGenerator(Unit& unit, int32 pathId, WaypointPathOrigin wpOrigin, ForcedMovement forcedMovement, bool flying = false, float speed = 0, int32 offset = 0, bool cyclic = false, ObjectGuid guid = ObjectGuid()); FixedPathMovementGenerator(Creature& creature); diff --git a/src/game/MotionGenerators/TargetedMovementGenerator.cpp b/src/game/MotionGenerators/TargetedMovementGenerator.cpp index 8d68dfeb8d5..9d981bb2319 100644 --- a/src/game/MotionGenerators/TargetedMovementGenerator.cpp +++ b/src/game/MotionGenerators/TargetedMovementGenerator.cpp @@ -51,8 +51,16 @@ bool TargetedMovementGeneratorMedium::Update(T& owner, const uint32& time_ // Trying to detect error if (i_target->GetMap() != owner.GetMap()) { - sLog.outCustomLog("TargetedMovementGeneratorMedium::Update(): Target %s left map id %u for map id %u out of order!", - i_target->GetGuidStr().c_str(), i_target->GetMapId(), owner.GetMapId()); + if (i_target.getTarget() && i_target.getSource()) + { + sLog.outCustomLog("TargetedMovementGeneratorMedium::Update(): Target %s left map id %u for map id %u out of order!", + i_target->GetGuidStr().c_str(), i_target->GetMapId(), owner.GetMapId()); + } + else + { + sLog.outCustomLog("TargetedMovementGeneratorMedium::Update(): Target left for map id %u out of order!", + owner.GetMapId()); + } return !static_cast(this)->RemoveOnInvalid(); } @@ -733,6 +741,9 @@ float FollowMovementGenerator::GetSpeed(Unit& owner) const // Followers sync with master's speed when not in combat // Use default speed when a mix of PC and NPC units involved (escorting?) if (owner.HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED) == i_target->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PLAYER_CONTROLLED)) +#ifdef ENABLE_PLAYERBOTS + if (!(!m_boost && owner.IsPlayer() && !((Player*)(&owner))->isRealPlayer())) //Do not speed up bots when not boosting. +#endif speed = i_target->GetSpeedInMotion(); // Catchup boost is not allowed, stop here: diff --git a/src/game/MotionGenerators/TargetedMovementGenerator.h b/src/game/MotionGenerators/TargetedMovementGenerator.h index 39fd8bcb141..5ca0a6984fd 100644 --- a/src/game/MotionGenerators/TargetedMovementGenerator.h +++ b/src/game/MotionGenerators/TargetedMovementGenerator.h @@ -50,7 +50,7 @@ class TargetedMovementGeneratorMedium i_path(nullptr), i_faceTarget(true) { } - ~TargetedMovementGeneratorMedium() { delete i_path; } + ~TargetedMovementGeneratorMedium() { /*delete i_path;*/ } public: bool Update(T&, const uint32&); diff --git a/src/game/Server/DBCStores.cpp b/src/game/Server/DBCStores.cpp index 0894cb8c20d..c8b4f81a596 100644 --- a/src/game/Server/DBCStores.cpp +++ b/src/game/Server/DBCStores.cpp @@ -90,6 +90,12 @@ DBCStorage sDurabilityCostsStore(DurabilityCostsfmt); DBCStorage sEmotesStore(EmotesEntryfmt); DBCStorage sEmotesTextStore(EmotesTextEntryfmt); +#ifdef ENABLE_PLAYERBOTS +typedef std::tuple EmotesTextSoundKey; +static std::map sEmotesTextSoundMap; +DBCStorage sEmotesTextSoundStore(EmotesTextSoundEntryfmt); +#endif + typedef std::map FactionTeamMap; static FactionTeamMap sFactionTeamMap; DBCStorage sFactionStore(FactionEntryfmt); @@ -465,6 +471,13 @@ void LoadDBCStores(const std::string& dataPath) } } +#ifdef ENABLE_PLAYERBOTS + LoadDBC(availableDbcLocales, bar, bad_dbc_files, sEmotesTextSoundStore, dbcPath, "EmotesTextSound.dbc"); + for (uint32 i = 0; i < sEmotesTextSoundStore.GetNumRows(); ++i) + if (EmotesTextSoundEntry const* entry = sEmotesTextSoundStore.LookupEntry(i)) + sEmotesTextSoundMap[EmotesTextSoundKey(entry->EmotesTextId, entry->RaceId, entry->SexId)] = entry; +#endif + LoadDBC(availableDbcLocales, bar, bad_dbc_files, sFactionTemplateStore, dbcPath, "FactionTemplate.dbc"); LoadDBC(availableDbcLocales, bar, bad_dbc_files, sGameObjectArtKitStore, dbcPath, "GameObjectArtKit.dbc"); LoadDBC(availableDbcLocales, bar, bad_dbc_files, sGameObjectDisplayInfoStore, dbcPath, "GameObjectDisplayInfo.dbc"); @@ -1184,3 +1197,10 @@ LFGDungeonEntry const* GetLFGDungeon(uint32 mapId, Difficulty difficulty) return nullptr; } +#ifdef ENABLE_PLAYERBOTS +EmotesTextSoundEntry const* FindTextSoundEmoteFor(uint32 emote, uint32 race, uint32 gender) +{ + auto itr = sEmotesTextSoundMap.find(EmotesTextSoundKey(emote, race, gender)); + return itr != sEmotesTextSoundMap.end() ? itr->second : nullptr; +} +#endif diff --git a/src/game/Server/DBCStores.h b/src/game/Server/DBCStores.h index b7ff5eec2eb..bc01a5da516 100644 --- a/src/game/Server/DBCStores.h +++ b/src/game/Server/DBCStores.h @@ -103,6 +103,13 @@ float GetModelMidpoint(uint32 modelId); uint32 GetDefaultMapLight(uint32 mapId); +#ifdef ENABLE_PLAYERBOTS +EmotesTextSoundEntry const* FindTextSoundEmoteFor(uint32 emote, uint32 race, uint32 gender); +CharSectionsEntry const* GetCharSectionEntry(uint8 race, CharSectionType genType, uint8 gender, uint8 type, uint8 color); +typedef std::multimap CharSectionsMap; +extern CharSectionsMap sCharSectionMap; +#endif + extern DBCStorage sAchievementStore; extern DBCStorage sAchievementCriteriaStore; extern DBCStorage sAreaStore;// recommend access using functions @@ -111,7 +118,11 @@ extern DBCStorage sAuctionHouseStore; extern DBCStorage sBankBagSlotPricesStore; extern DBCStorage sBarberShopStyleStore; extern DBCStorage sBattlemasterListStore; -// extern DBCStorage sChatChannelsStore; -- accessed using function, no usable index +#ifdef ENABLE_PLAYERBOTS +extern DBCStorage sChatChannelsStore; //has function for access aswell +#else +//extern DBCStorage sChatChannelsStore; //has function for access aswell +#endif extern DBCStorage sCharStartOutfitStore; extern DBCStorage sCharTitlesStore; extern DBCStorage sChatChannelsStore; diff --git a/src/game/Server/DBCStructure.h b/src/game/Server/DBCStructure.h index d463dc74d20..b2e5b964170 100644 --- a/src/game/Server/DBCStructure.h +++ b/src/game/Server/DBCStructure.h @@ -964,6 +964,21 @@ struct EmotesTextEntry // m_emoteText }; +#ifdef ENABLE_PLAYERBOTS +/** +* \struct EmotesTextSoundEntry +* \brief Entry repsenting the text sound for given emote. +*/ +struct EmotesTextSoundEntry +{ + uint32 Id; // 0 + uint32 EmotesTextId; // 1 + uint32 RaceId; // 2 + uint32 SexId; // 3, 0 male / 1 female + uint32 SoundId; // 4 +}; +#endif + struct FactionEntry { uint32 ID; // 0 m_ID diff --git a/src/game/Server/DBCfmt.h b/src/game/Server/DBCfmt.h index c18a42e6672..8a3afcc999d 100644 --- a/src/game/Server/DBCfmt.h +++ b/src/game/Server/DBCfmt.h @@ -50,6 +50,11 @@ const char DurabilityCostsfmt[] = "niiiiiiiiiiiiiiiiiiiiiiiiiiiii"; const char DurabilityQualityfmt[] = "nf"; const char EmotesEntryfmt[] = "nxxiiix"; const char EmotesTextEntryfmt[] = "nxixxxxxxxxxxxxxxxx"; + +#ifdef ENABLE_PLAYERBOTS +char const EmotesTextSoundEntryfmt[] = "niiii"; +#endif + const char FactionEntryfmt[] = "niiiiiiiiiiiiiiiiiiffixssssssssssssssssxxxxxxxxxxxxxxxxxx"; const char FactionTemplateEntryfmt[] = "niiiiiiiiiiiii"; const char GameObjectArtKitfmt[] = "nxxxxxxx"; diff --git a/src/game/Server/WorldSession.cpp b/src/game/Server/WorldSession.cpp index 2c85b2928c0..ba83526864b 100644 --- a/src/game/Server/WorldSession.cpp +++ b/src/game/Server/WorldSession.cpp @@ -41,6 +41,7 @@ #include "GMTickets/GMTicketMgr.h" #include "Loot/LootMgr.h" #include "Anticheat/Anticheat.hpp" +#include "AI/ScriptDevAI/scripts/custom/Transmogrification.h" #include @@ -54,6 +55,10 @@ #include "PlayerBot/Base/PlayerbotAI.h" #endif +#ifdef ENABLE_PLAYERBOTS +#include "playerbot.h" +#endif + // select opcodes appropriate for processing in Map::Update context for current session state static bool MapSessionFilterHelper(WorldSession* session, OpcodeHandler const& opHandle) { @@ -212,6 +217,15 @@ void WorldSession::SendPacket(WorldPacket const& packet) const } #endif +#ifdef ENABLE_PLAYERBOTS + if (GetPlayer()) { + if (GetPlayer()->GetPlayerbotAI()) + GetPlayer()->GetPlayerbotAI()->HandleBotOutgoingPacket(packet); + else if (GetPlayer()->GetPlayerbotMgr()) + GetPlayer()->GetPlayerbotMgr()->HandleMasterOutgoingPacket(packet); + } +#endif + if (!m_Socket || m_Socket->IsClosed()) return; @@ -387,6 +401,10 @@ bool WorldSession::Update(uint32 diff) #ifdef BUILD_PLAYERBOT if (_player && _player->GetPlayerbotMgr()) _player->GetPlayerbotMgr()->HandleMasterIncomingPacket(*packet); +#endif +#ifdef ENABLE_PLAYERBOTS + if (_player && _player->GetPlayerbotMgr()) + _player->GetPlayerbotMgr()->HandleMasterIncomingPacket(*packet); #endif break; case STATUS_LOGGEDIN_OR_RECENTLY_LOGGEDOUT: @@ -468,6 +486,10 @@ bool WorldSession::Update(uint32 diff) GetPlayer()->GetPlayerbotMgr()->RemoveBots(); } #endif +#ifdef ENABLE_PLAYERBOTS + if (GetPlayer() && GetPlayer()->GetPlayerbotMgr()) + GetPlayer()->GetPlayerbotMgr()->UpdateSessions(0); +#endif // check if we are safe to proceed with logout // logout procedure should happen only in World::UpdateSessions() method!!! @@ -579,6 +601,19 @@ void WorldSession::UpdateMap(uint32 diff) } } +#ifdef ENABLE_PLAYERBOTS +void WorldSession::HandleBotPackets() +{ + while (!m_recvQueue.empty()) + { + auto const packet = std::move(m_recvQueue.front()); + m_recvQueue.pop_front(); + OpcodeHandler const& opHandle = opcodeTable[packet->GetOpcode()]; + (this->*opHandle.handler)(*packet); + } +} +#endif + /// %Log the player out void WorldSession::LogoutPlayer() { @@ -599,12 +634,21 @@ void WorldSession::LogoutPlayer() if (_player->GetPlayerbotMgr()) _player->GetPlayerbotMgr()->LogoutAllBots(true); #endif - +#ifdef ENABLE_PLAYERBOTS + sLog.outChar("Account: %d (IP: %s) Logout Character:[%s] (guid: %u)", GetAccountId(), m_Socket ? GetRemoteAddress().c_str() : "bot", _player->GetName(), _player->GetGUIDLow()); +#else sLog.outChar("Account: %d (IP: %s) Logout Character:[%s] (guid: %u)", GetAccountId(), GetRemoteAddress().c_str(), _player->GetName(), _player->GetGUIDLow()); +#endif if (Loot* loot = sLootMgr.GetLoot(_player)) loot->Release(_player); +#ifdef ENABLE_PLAYERBOTS + if (_player->GetPlayerbotMgr() && (!_player->GetPlayerbotAI() || _player->GetPlayerbotAI()->IsRealPlayer())) + _player->GetPlayerbotMgr()->LogoutAllBots(); + sRandomPlayerbotMgr.OnPlayerLogout(_player); +#endif + if (_player->GetDeathTimer()) { _player->getHostileRefManager().deleteReferences(); @@ -703,6 +747,7 @@ void WorldSession::LogoutPlayer() ///- Leave all channels before player delete... _player->CleanupChannels(); +#ifndef ENABLE_PLAYERBOTS ///- If the player is in a group (or invited), remove him. If the group if then only 1 person, disband the group. _player->UninviteFromGroup(); @@ -710,6 +755,7 @@ void WorldSession::LogoutPlayer() // a) in group; b) not in raid group; c) logging out normally (not being kicked or disconnected) if (_player->GetGroup() && !_player->GetGroup()->IsRaidGroup() && m_Socket && !m_Socket->IsClosed()) _player->RemoveFromGroup(); +#endif ///- Send update to group if (Group* group = _player->GetGroup()) @@ -725,11 +771,30 @@ void WorldSession::LogoutPlayer() // GM ticket notification sTicketMgr.OnPlayerOnlineState(*_player, false); + ObjectGuid pGUID = _player->GetObjectGuid(); + for (Transmogrification::transmog2Data::const_iterator it = sTransmogrification->entryMap[pGUID].begin(); it != sTransmogrification->entryMap[pGUID].end(); ++it) + sTransmogrification->dataMap.erase(it->first); + sTransmogrification->entryMap.erase(pGUID); + +#ifdef PRESETS + if (sTransmogrification->GetEnableSets()) + sTransmogrification->UnloadPlayerSets(pGUID); +#endif + #ifdef BUILD_PLAYERBOT // Remember player GUID for update SQL below uint32 guid = _player->GetGUIDLow(); #endif +#ifdef ENABLE_PLAYERBOTS + // Remember player GUID for update SQL below + uint32 guid = _player->GetGUIDLow(); +#endif + +//Start Solocraft Function + CharacterDatabase.PExecute("DELETE FROM custom_solocraft_character_stats WHERE GUID = %u", _player->GetGUIDLow()); +//End Solocraft Function + ///- Remove the player from the world // the player may not be in the world when logging out // e.g if he got disconnected during a transfer to another map @@ -762,11 +827,18 @@ void WorldSession::LogoutPlayer() // Different characters can be alive as bots SqlStatement stmt = CharacterDatabase.CreateStatement(updChars, "UPDATE characters SET online = 0 WHERE guid = ?"); stmt.PExecute(guid); +#else +#ifdef ENABLE_PLAYERBOTS + // Set for only character instead of accountid + // Different characters can be alive as bots + stmt = CharacterDatabase.CreateStatement(updChars, "UPDATE characters SET online = 0 WHERE guid = ?"); + stmt.PExecute(guid); #else ///- Since each account can only have one online character at any given time, ensure all characters for active account are marked as offline // No SQL injection as AccountId is uint32 stmt = CharacterDatabase.CreateStatement(updChars, "UPDATE characters SET online = 0 WHERE account = ?"); stmt.PExecute(GetAccountId()); +#endif #endif DEBUG_LOG("SESSION: Sent SMSG_LOGOUT_COMPLETE Message"); @@ -1299,6 +1371,15 @@ void WorldSession::SetNoAnticheat() #endif +#ifdef ENABLE_PLAYERBOTS + +void WorldSession::SetNoAnticheat() +{ + m_anticheat.reset(new NullSessionAnticheat(this)); +} + +#endif + void WorldSession::HandleWardenDataOpcode(WorldPacket& recv_data) { m_anticheat->WardenPacket(recv_data); diff --git a/src/game/Server/WorldSession.h b/src/game/Server/WorldSession.h index e6174058f24..22f579d8a06 100644 --- a/src/game/Server/WorldSession.h +++ b/src/game/Server/WorldSession.h @@ -199,6 +199,9 @@ enum AccountFlags ACCOUNT_FLAG_SILENCED = 0x02, ACCOUNT_FLAG_SHOW_ANTISPAM = 0x04, ACCOUNT_FLAG_HIDDEN = 0x08, + ACCOUNT_FLAG_COLLECTOR_CLASSIC = 0x10, + ACCOUNT_FLAG_COLLECTOR_TBC = 0x20, + ACCOUNT_FLAG_COLLECTOR_WRATH = 0x40, }; // class to deal with packet processing @@ -301,8 +304,13 @@ class WorldSession #ifdef BUILD_PLAYERBOT // Players connected without socket are bot const std::string GetRemoteAddress() const { return m_Socket ? m_Socket->GetRemoteAddress() : "disconnected/bot"; } +#else +#ifdef ENABLE_PLAYERBOTS + // Players connected without socket are bot + const std::string GetRemoteAddress() const { return m_Socket ? m_Socket->GetRemoteAddress() : "disconnected/bot"; } #else const std::string GetRemoteAddress() const { return m_Socket ? m_Socket->GetRemoteAddress() : "disconnected"; } +#endif #endif const std::string& GetLocalAddress() const { return m_localAddress; } @@ -319,6 +327,10 @@ class WorldSession void SetNoAnticheat(); #endif +#ifdef ENABLE_PLAYERBOTS + void SetNoAnticheat(); +#endif + /// Session in auth.queue currently void SetInQueue(bool state) { m_inQueue = state; } @@ -493,6 +505,7 @@ class WorldSession // Misc void SendKnockBack(Unit* who, float angle, float horizontalSpeed, float verticalSpeed); void SendPlaySpellVisual(ObjectGuid guid, uint32 spellArtKit) const; + void SendTeleportToObservers(float x, float y, float z, float orientation); void SendAuthOk() const; void SendAuthQueued() const; @@ -1008,6 +1021,12 @@ class WorldSession void SetPacketLogging(bool state); +#ifdef ENABLE_PLAYERBOTS + // Playerbots + void HandleBotPackets(); +#endif + + std::deque GetOpcodeHistory(); private: // Additional private opcode handlers void HandleComplainMail(WorldPacket& recv_data); diff --git a/src/game/Social/SocialMgr.cpp b/src/game/Social/SocialMgr.cpp index 2371faab5f3..61668132fd1 100644 --- a/src/game/Social/SocialMgr.cpp +++ b/src/game/Social/SocialMgr.cpp @@ -273,12 +273,15 @@ void SocialMgr::BroadcastToFriendListers(Player* player, WorldPacket const& pack AccountTypes gmLevelInWhoList = AccountTypes(sWorld.getConfig(CONFIG_UINT32_GM_LEVEL_IN_WHO_LIST)); bool allowTwoSideWhoList = sWorld.getConfig(CONFIG_BOOL_ALLOW_TWO_SIDE_WHO_LIST); - for (SocialMap::const_iterator itr = m_socialMap.begin(); itr != m_socialMap.end(); ++itr) + for (const auto& itr : m_socialMap) { - PlayerSocialMap::const_iterator itr2 = itr->second.m_playerSocialMap.find(guid); - if (itr2 != itr->second.m_playerSocialMap.end() && (itr2->second.Flags & SOCIAL_FLAG_FRIEND)) + if (!itr.second.m_playerSocialMap.size()) + continue; + + PlayerSocialMap::const_iterator itr2 = itr.second.m_playerSocialMap.find(guid); + if (itr2 != itr.second.m_playerSocialMap.end() && (itr2->second.Flags & SOCIAL_FLAG_FRIEND)) { - Player* pFriend = ObjectAccessor::FindPlayer(ObjectGuid(HIGHGUID_PLAYER, itr->first)); + Player* pFriend = ObjectAccessor::FindPlayer(ObjectGuid(HIGHGUID_PLAYER, itr.first)); // PLAYER see his team only and PLAYER can't see MODERATOR, GAME MASTER, ADMINISTRATOR characters // MODERATOR, GAME MASTER, ADMINISTRATOR can see all diff --git a/src/game/Spells/Spell.cpp b/src/game/Spells/Spell.cpp index e4768cfef2e..fae76b2a24a 100644 --- a/src/game/Spells/Spell.cpp +++ b/src/game/Spells/Spell.cpp @@ -50,6 +50,10 @@ #include "Entities/ObjectGuid.h" #include "Entities/Transports.h" +#ifdef ENABLE_PLAYERBOTS +#include "PlayerbotAI.h" +#endif + extern pEffect SpellEffects[MAX_SPELL_EFFECTS]; class PrioritizeManaUnitWraper @@ -364,7 +368,7 @@ void SpellLog::Initialize() { m_spellLogData.Initialize(SMSG_SPELLLOGEXECUTE); //m_spellLogData << m_spell->GetCaster()->GetPackGUID(); - m_spellLogData.appendPackGUID(m_spell->GetTrueCaster()->GetObjectGuid().GetRawValue()); + m_spellLogData.appendPackGUID(m_spell->GetTrueCaster() ? m_spell->GetTrueCaster()->GetObjectGuid().GetRawValue() :ObjectGuid().GetRawValue()); m_spellLogData << uint32(m_spell->m_spellInfo->Id); m_spellLogDataEffectsCounterPos = m_spellLogData.wpos(); m_spellLogData << uint32(0); //placeholder @@ -404,12 +408,20 @@ void SpellLog::SendToSet() if (!m_spellLogDataEffectsCounter) return; + if (!m_spell->GetTrueCaster() || !m_spell->GetCaster()) + { + return; + } + // check if one of previous target is not finalized FinalizePrevious(); // put total effect counter in packet m_spellLogData.put(m_spellLogDataEffectsCounterPos, m_spellLogDataEffectsCounter); - m_spell->GetTrueCaster()->SendMessageToSet(m_spellLogData, true); + if (m_spell->GetTrueCaster()) + m_spell->GetTrueCaster()->SendMessageToAllWhoSeeMe(m_spellLogData, true); + //else if (m_spell->GetCaster()) + // m_spell->GetCaster()->SendMessageToAllWhoSeeMe(m_spellLogData, true); // make it ready for another log if need Initialize(); @@ -492,7 +504,7 @@ Spell::Spell(WorldObject * caster, SpellEntry const* info, uint32 triggeredFlags m_powerCost = 0; // setup to correct value in Spell::prepare, don't must be used before. m_casttime = 0; // setup to correct value in Spell::prepare, don't must be used before. m_timer = 0; // will set to cast time in prepare - m_creationTime = m_trueCaster->GetMap()->GetCurrentMSTime(); + m_creationTime = m_trueCaster ? m_trueCaster->GetMap()->GetCurrentMSTime() : sWorld.GetCurrentMSTime(); m_updated = false; m_duration = 0; m_maxRange = 0.f; @@ -3259,6 +3271,9 @@ SpellCastResult Spell::SpellStart(SpellCastTargets const* targets, Aura* trigger { if (!m_trueCaster) m_trueCaster = m_caster; + if (!m_trueCaster) + return SPELL_NOT_FOUND; + m_spellState = SPELL_STATE_TARGETING; m_targets = *targets; @@ -3267,7 +3282,8 @@ SpellCastResult Spell::SpellStart(SpellCastTargets const* targets, Aura* trigger // create and add update event for this spell m_spellEvent = new SpellEvent(this); - m_trueCaster->m_events.AddEvent(m_spellEvent, m_trueCaster->m_events.CalculateTime(1)); + if (m_trueCaster) + m_trueCaster->m_events.AddEvent(m_spellEvent, m_trueCaster->m_events.CalculateTime(1)); if (!m_trueCaster->IsGameObject()) // gameobjects dont have a sense of already casting a spell { @@ -5870,6 +5886,17 @@ SpellCastResult Spell::CheckCast(bool strict) if (m_spellInfo->HasAttribute(SPELL_ATTR_EX5_NOT_ON_TRIVIAL) && target->IsTrivialForTarget(m_caster)) return SPELL_FAILED_TARGET_IS_TRIVIAL; + +#ifdef ENABLE_PLAYERBOTS + if (target->IsPlayer()) + { + PlayerbotAI* bot = ((Player*)target)->GetPlayerbotAI(); + if (bot && bot->IsImmuneToSpell(m_spellInfo->Id)) + { + return SPELL_FAILED_IMMUNE; + } + } +#endif } } @@ -8174,7 +8201,7 @@ void Spell::DelayedChannel() void Spell::UpdateOriginalCasterPointer() { - if (m_originalCasterGUID == m_trueCaster->GetObjectGuid() && m_trueCaster->IsUnit()) + if (m_trueCaster && m_originalCasterGUID == m_trueCaster->GetObjectGuid() && m_trueCaster->IsUnit()) m_originalCaster = static_cast(m_trueCaster); else if (m_originalCasterGUID.IsGameObject()) { @@ -8390,6 +8417,17 @@ bool Spell::CheckTarget(Unit* target, SpellEffectIndex eff, bool targetB, CheckE if (m_spellInfo->MaxTargetLevel && target->GetLevel() > m_spellInfo->MaxTargetLevel) return false; +#ifdef ENABLE_PLAYERBOTS + if (target->IsPlayer()) + { + PlayerbotAI* bot = ((Player*)target)->GetPlayerbotAI(); + if (bot && bot->IsImmuneToSpell(m_spellInfo->Id)) + { + return false; + } + } +#endif + return OnCheckTarget(target, eff); } diff --git a/src/game/Spells/SpellAuras.cpp b/src/game/Spells/SpellAuras.cpp index e508e27de45..0352d3e0348 100755 --- a/src/game/Spells/SpellAuras.cpp +++ b/src/game/Spells/SpellAuras.cpp @@ -1100,7 +1100,8 @@ void Aura::PickTargetsForSpellTrigger(Unit*& triggerCaster, Unit*& triggerTarget break; case TARGET_UNIT_CHANNEL_TARGET: // Electrified net triggerCaster = GetCaster(); - triggerTarget = dynamic_cast(triggerCaster->GetChannelObject()); + if (triggerCaster) + triggerTarget = dynamic_cast(triggerCaster->GetChannelObject()); break; case TARGET_LOCATION_CHANNEL_TARGET_DEST: triggerCaster = GetCaster(); @@ -4018,6 +4019,7 @@ void Aura::HandleAuraModShapeshift(bool apply, bool Real) Unit* target = GetTarget(); + m_modifier.m_amount = 0; if (ssEntry->modelID_A) { // i will asume that creatures will always take the defined model from the dbc diff --git a/src/game/Spells/SpellEffects.cpp b/src/game/Spells/SpellEffects.cpp index a02e1b48494..1f7c52dfa51 100644 --- a/src/game/Spells/SpellEffects.cpp +++ b/src/game/Spells/SpellEffects.cpp @@ -12557,16 +12557,20 @@ void Spell::EffectSpiritHeal(SpellEffectIndex /*eff_idx*/) if (Player* player = static_cast(unitTarget)) { + player->RemoveAurasDueToSpell(2584); player->ResurrectPlayer(1.0f); player->SpawnCorpseBones(); if (player->getClass() == CLASS_HUNTER) { - Pet* pet = new Pet; - if (!pet->LoadPetFromDB(player, pet->GetPetSpawnPosition(player), 0, 0, false, 100, true)) + if (!player->GetPet()) { - delete pet; - return; + Pet* pet = new Pet; + if (!pet->LoadPetFromDB(player, pet->GetPetSpawnPosition(player), 0, 0, false, 100, true)) + { + delete pet; + return; + } } } // resurrects last active minion - Imp, Voidwalker, Succubus, Felhunter, or Felguard diff --git a/src/game/World/World.cpp b/src/game/World/World.cpp index b05a731fc1b..1152d691773 100644 --- a/src/game/World/World.cpp +++ b/src/game/World/World.cpp @@ -72,6 +72,7 @@ #include "Anticheat/Anticheat.hpp" #include "LFG/LFGMgr.h" #include "Vmap/GameObjectModel.h" +#include "AI/ScriptDevAI/scripts/custom/Transmogrification.h" #ifdef BUILD_AHBOT #include "AuctionHouseBot/AuctionHouseBot.h" @@ -81,6 +82,12 @@ #include "Metric/Metric.h" #endif +#ifdef ENABLE_PLAYERBOTS +#include "AhBot.h" +#include "PlayerbotAIConfig.h" +#include "RandomPlayerbotMgr.h" +#endif + #include #include @@ -100,6 +107,11 @@ uint32 World::m_relocation_ai_notify_delay = 1000u; uint32 World::m_currentMSTime = 0; TimePoint World::m_currentTime = TimePoint(); uint32 World::m_currentDiff = 0; +uint32 World::m_currentDiffSum = 0; +uint32 World::m_currentDiffSumIndex = 0; +uint32 World::m_averageDiff = 0; +uint32 World::m_maxDiff = 0; +list World::m_histDiff; /// World constructor World::World() : mail_timer(0), mail_timer_expires(0), m_NextDailyQuestReset(0), m_NextWeeklyQuestReset(0), m_NextMonthlyQuestReset(0), m_opcodeCounters(NUM_MSG_TYPES) @@ -112,6 +124,7 @@ World::World() : mail_timer(0), mail_timer_expires(0), m_NextDailyQuestReset(0), m_startTime = m_gameTime; m_maxActiveSessionCount = 0; m_maxQueuedSessionCount = 0; + m_maxDiff = 0; m_defaultDbcLocale = DEFAULT_LOCALE; m_availableDbcLocaleMask = 0; @@ -569,11 +582,15 @@ void World::LoadConfigSettings(bool reload) setConfigMinMax(CONFIG_UINT32_START_ARENA_POINTS, "StartArenaPoints", 0, 0, getConfig(CONFIG_UINT32_MAX_ARENA_POINTS)); + setConfig(CONFIG_BOOL_COLLECTORS_EDITION, "Custom.CollectorsEdition", true); + setConfig(CONFIG_BOOL_ALWAYS_SHOW_QUEST_GREETING, "AlwaysShowQuestGreeting", false); setConfig(CONFIG_BOOL_TAXI_FLIGHT_CHAT_FIX, "TaxiFlightChatFix", false); setConfig(CONFIG_BOOL_LONG_TAXI_PATHS_PERSISTENCE, "LongFlightPathsPersistence", false); setConfig(CONFIG_BOOL_ALL_TAXI_PATHS, "AllFlightPaths", false); + setConfig(CONFIG_BOOL_INSTANT_TAXI, "InstantFlightPaths", false); + setConfig(CONFIG_BOOL_FAR_VISIBLE_TAXI, "TaxiFlightFarVisibility", false); setConfig(CONFIG_BOOL_INSTANCE_IGNORE_LEVEL, "Instance.IgnoreLevel", false); setConfig(CONFIG_BOOL_INSTANCE_IGNORE_RAID, "Instance.IgnoreRaid", false); @@ -897,6 +914,227 @@ void World::LoadConfigSettings(bool reload) setConfig(CONFIG_UINT32_SUNSREACH_COUNTER, "Sunsreach.CounterMax", 10000); +// Start Solocraft Config + setConfig(CONFIG_BOOL_SOLOCRAFT_ENABLED, "Solocraft.Enable", true); + setConfig(CONFIG_BOOL_SOLOCRAFT_ANNOUNCE, "Solocraft.Announce", true); + + //Balancing + setConfig(CONFIG_BOOL_SOLOCRAFT_DEBUFF_ENABLE, "SoloCraft.Debuff.Enable", 1); + setConfig(CONFIG_FLOAT_SOLOCRAFT_SPELLPOWER_MULT, "SoloCraft.Spellpower.Mult", 2.5); + setConfig(CONFIG_FLOAT_SOLOCRAFT_STATS_MULT, "SoloCraft.Stats.Mult", 100.0); + //Level Thresholds + setConfig(CONFIG_UINT32_SOLOCRAFT_MAX_LEVEL_DIFF, "Solocraft.Max.Level.Diff", 10); + //Dungeon Values + //Default + setConfig(CONFIG_UINT32_DUNGEON_LEVEL, "Solocraft.Dungeon.Level", 80); + //Classic Instances + setConfig(CONFIG_UINT32_SHADOWFANGKEEP_LEVEL, "Solocraft.ShadowfangKeep.Level", 15); + setConfig(CONFIG_UINT32_STOCKADES_LEVEL, "Solocraft.Stockades.Level", 22); + setConfig(CONFIG_UINT32_DEADMINES_LEVEL, "Solocraft.Deadmines.Level", 18); + setConfig(CONFIG_UINT32_WAILINGCAVERNS_LEVEL, "Solocraft.WailingCaverns.Level", 17); + setConfig(CONFIG_UINT32_RAZORFENKRAULINSTANCE_LEVEL, "Solocraft.RazorfenKraulInstance.Level", 30); + setConfig(CONFIG_UINT32_BLACKFATHOM_LEVEL, "Solocraft.Blackfathom.Level", 20); + setConfig(CONFIG_UINT32_ULDAMAN_LEVEL, "Solocraft.Uldaman.Level", 40); + setConfig(CONFIG_UINT32_GNOMERAGONINSTANCE_LEVEL, "Solocraft.GnomeragonInstance.Level", 24); + setConfig(CONFIG_UINT32_SUNKENTEMPLE_LEVEL, "Solocraft.SunkenTemple.Level", 50); + setConfig(CONFIG_UINT32_RAZORFENDOWNS_LEVEL, "Solocraft.RazorfenDowns.Level", 40); + setConfig(CONFIG_UINT32_MONASTERYINSTANCES_LEVEL, "Solocraft.MonasteryInstances.Level", 35); + setConfig(CONFIG_UINT32_TANARISINSTANCE_LEVEL, "Solocraft.TanarisInstance.Level", 44); + setConfig(CONFIG_UINT32_BLACKROCKSPIRE_LEVEL, "Solocraft.BlackRockSpire.Level", 55); + setConfig(CONFIG_UINT32_BLACKROCKDEPTHS_LEVEL, "Solocraft.BlackrockDepths.Level", 50); + setConfig(CONFIG_UINT32_ONYXIALAIRINSTANCE_LEVEL, "Solocraft.OnyxiaLairInstance.Level", 60); + setConfig(CONFIG_UINT32_SCHOOLOFNECROMANCY_LEVEL, "Solocraft.SchoolofNecromancy.Level", 55); + setConfig(CONFIG_UINT32_ZULGURUB_LEVEL, "Solocraft.Zul'gurub.Level", 60); + setConfig(CONFIG_UINT32_STRATHOLME_LEVEL, "Solocraft.Stratholme.Level", 55); + setConfig(CONFIG_UINT32_MAURADON_LEVEL, "Solocraft.Mauradon.Level", 48); + setConfig(CONFIG_UINT32_ORGRIMMARINSTANCE_LEVEL, "Solocraft.OrgrimmarInstance.Level", 15); + setConfig(CONFIG_UINT32_MOLTENCORE_LEVEL, "Solocraft.MoltenCore.Level", 60); + setConfig(CONFIG_UINT32_DIREMAUL_LEVEL, "Solocraft.DireMaul.Level", 48); + setConfig(CONFIG_UINT32_BLACKWINGLAIR_LEVEL, "Solocraft.BlackwingLair.Level", 40); + setConfig(CONFIG_UINT32_AHNQIRAJ_LEVEL, "Solocraft.AhnQiraj.Level", 60); + setConfig(CONFIG_UINT32_AHNQIRAJTEMPLE_LEVEL, "Solocraft.AhnQirajTemple.Level", 60); + //TBC Instances + setConfig(CONFIG_UINT32_CAVERNSOFTIME_LEVEL, "Solocraft.CavernsOfTime.Level", 68); + setConfig(CONFIG_UINT32_KARAZAHN_LEVEL, "Solocraft.Karazahn.Level", 68); + setConfig(CONFIG_UINT32_HYJALPAST_LEVEL, "Solocraft.HyjalPast.Level", 70); + setConfig(CONFIG_UINT32_HELLFIREMILITARY_LEVEL, "Solocraft.HellfireMilitary.Level", 68); + setConfig(CONFIG_UINT32_HELLFIREDEMON_LEVEL, "Solocraft.HellfireDemon.Level", 68); + setConfig(CONFIG_UINT32_HELLFIRERAMPART_LEVEL, "Solocraft.HellfireRampart.Level", 68); + setConfig(CONFIG_UINT32_HELLFIRERAID_LEVEL, "Solocraft.HellfireRaid.Level", 68); + setConfig(CONFIG_UINT32_COILFANGPUMPING_LEVEL, "Solocraft.CoilfangPumping.Level", 68); + setConfig(CONFIG_UINT32_COILFANGMARSH_LEVEL, "Solocraft.CoilfangMarsh.Level", 68); + setConfig(CONFIG_UINT32_COILFANGDRAENEI_LEVEL, "Solocraft.CoilfangDraenei.Level", 68); + setConfig(CONFIG_UINT32_COILFANGRAID_LEVEL, "Solocraft.CoilfangRaid.Level", 70); + setConfig(CONFIG_UINT32_TEMPESTKEEPRAID_LEVEL, "Solocraft.TempestKeepRaid.Level", 70); + setConfig(CONFIG_UINT32_TEMPESTKEEPARCANE_LEVEL, "Solocraft.TempestKeepArcane.Level", 68); + setConfig(CONFIG_UINT32_TEMPESTKEEPATRIUM_LEVEL, "Solocraft.TempestKeepAtrium.Level", 68); + setConfig(CONFIG_UINT32_TEMPESTKEEPFACTORY_LEVEL, "Solocraft.TempestKeepFactory.Level", 68); + setConfig(CONFIG_UINT32_AUCHINDOUNSHADOW_LEVEL, "Solocraft.AuchindounShadow.Level", 68); + setConfig(CONFIG_UINT32_AUCHINDOUNDEMON_LEVEL, "Solocraft.AuchindounDemon.Level", 68); + setConfig(CONFIG_UINT32_AUCHINDOUNETHEREAL_LEVEL, "Solocraft.AuchindounEthereal.Level", 68); + setConfig(CONFIG_UINT32_AUCHINDOUNDRAENEI_LEVEL, "Solocraft.AuchindounDraenei.Level", 68); + setConfig(CONFIG_UINT32_HILLSBRADPAST_LEVEL, "Solocraft.HillsbradPast.Level", 68); + setConfig(CONFIG_UINT32_BLACKTEMPLE_LEVEL, "Solocraft.BlackTemple.Level", 70); + setConfig(CONFIG_UINT32_GRUULSLAIR_LEVEL, "Solocraft.GruulsLair.Level", 70); + setConfig(CONFIG_UINT32_ZULAMAN_LEVEL, "Solocraft.ZulAman.Level", 68); + setConfig(CONFIG_UINT32_SUNWELLPLATEAU_LEVEL, "Solocraft.SunwellPlateau.Level", 70); + setConfig(CONFIG_UINT32_SUNWELL5MANFIX_LEVEL, "Solocraft.Sunwell5ManFix.Level", 68); + //WotLK Instances + setConfig(CONFIG_UINT32_STRATHOLMERAID_LEVEL, "Solocraft.StratholmeRaid.Level", 78); + setConfig(CONFIG_UINT32_VALGARDE70_LEVEL, "Solocraft.Valgarde70.Level", 78); + setConfig(CONFIG_UINT32_UTGARDEPINNACLE_LEVEL, "Solocraft.UtgardePinnacle.Level", 78); + setConfig(CONFIG_UINT32_NEXUS70_LEVEL, "Solocraft.Nexus70.Level", 78); + setConfig(CONFIG_UINT32_NEXUS80_LEVEL, "Solocraft.Nexus80.Level", 78); + setConfig(CONFIG_UINT32_STRATHOLMECOT_LEVEL, "Solocraft.StratholmeCOT.Level", 78); + setConfig(CONFIG_UINT32_ULDUAR70_LEVEL, "Solocraft.Ulduar70.Level", 78); + setConfig(CONFIG_UINT32_DRAKTHERONKEEP_LEVEL, "Solocraft.DrakTheronKeep.Level", 78); + setConfig(CONFIG_UINT32_AZJOL_UPPERCITY_LEVEL, "Solocraft.Azjol_Uppercity.Level", 78); + setConfig(CONFIG_UINT32_ULDUAR80_LEVEL, "Solocraft.Ulduar80.Level", 78); + setConfig(CONFIG_UINT32_ULDUARRAID_LEVEL, "Solocraft.UlduarRaid.Level", 80); + setConfig(CONFIG_UINT32_GUNDRAK_LEVEL, "Solocraft.GunDrak.Level", 78); + setConfig(CONFIG_UINT32_DALARANPRISON_LEVEL, "Solocraft.DalaranPrison.Level", 78); + setConfig(CONFIG_UINT32_CHAMBEROFASPECTSBLACK_LEVEL, "Solocraft.ChamberOfAspectsBlack.Level", 80); + setConfig(CONFIG_UINT32_NEXUSRAID_LEVEL, "Solocraft.NexusRaid.Level", 80); + setConfig(CONFIG_UINT32_AZJOL_LOWERCITY_LEVEL, "Solocraft.Azjol_LowerCity.Level", 78); + setConfig(CONFIG_UINT32_ICECROWNCITADEL_LEVEL, "Solocraft.IcecrownCitadel.Level", 80); + setConfig(CONFIG_UINT32_ICECROWNCITADEL5MAN_LEVEL, "Solocraft.IcecrownCitadel5Man.Level", 78); + setConfig(CONFIG_UINT32_ARGENTTOURNAMENTRAID_LEVEL, "Solocraft.ArgentTournamentRaid.Level", 80); + setConfig(CONFIG_UINT32_ARGENTTOURNAMENTDUNGEON_LEVEL, "Solocraft.ArgentTournamentDungeon.Level", 80); + setConfig(CONFIG_UINT32_QUARRYOFTEARS_LEVEL, "Solocraft.QuarryOfTears.Level", 78); + setConfig(CONFIG_UINT32_HALLSOFREFLECTION_LEVEL, "Solocraft.HallsOfReflection.Level", 78); + setConfig(CONFIG_UINT32_CHAMBEROFASPECTSRED_LEVEL, "Solocraft.ChamberOfAspectsRed.Level", 80); + + //Dungeon Difficulty + //Catch alls + setConfig(CONFIG_FLOAT_DUNGEON_DIFF, "Solocraft.Dungeon", 5.0); + setConfig(CONFIG_FLOAT_HEROIC_DIFF, "Solocraft.Heroic", 10.0); + setConfig(CONFIG_FLOAT_RAID25_DIFF, "Solocraft.Raid25", 25.0); + setConfig(CONFIG_FLOAT_RAID40_DIFF, "Solocraft.Raid40", 40.0); + //Classic Instances + setConfig(CONFIG_FLOAT_SHADOWFANGKEEP_DIFF, "Solocraft.ShadowfangKeep", 5.0); + setConfig(CONFIG_FLOAT_STOCKADES_DIFF, "Solocraft.Stockades", 5.0); + setConfig(CONFIG_FLOAT_DEADMINES_DIFF, "Solocraft.Deadmines", 5.0); + setConfig(CONFIG_FLOAT_WAILINGCAVERNS_DIFF, "Solocraft.WailingCaverns", 5.0); + setConfig(CONFIG_FLOAT_RAZORFENKRAULINSTANCE_DIFF, "Solocraft.RazorfenKraulInstance", 5.0); + setConfig(CONFIG_FLOAT_BLACKFATHOM_DIFF, "Solocraft.Blackfathom", 5.0); + setConfig(CONFIG_FLOAT_ULDAMAN_DIFF, "Solocraft.Uldaman", 5.0); + setConfig(CONFIG_FLOAT_GNOMERAGONINSTANCE_DIFF, "Solocraft.GnomeragonInstance", 5.0); + setConfig(CONFIG_FLOAT_SUNKENTEMPLE_DIFF, "Solocraft.SunkenTemple", 5.0); + setConfig(CONFIG_FLOAT_RAZORFENDOWNS_DIFF, "Solocraft.RazorfenDowns", 5.0); + setConfig(CONFIG_FLOAT_MONASTERYINSTANCES_DIFF, "Solocraft.MonasteryInstances", 5.0); + setConfig(CONFIG_FLOAT_TANARISINSTANCE_DIFF, "Solocraft.TanarisInstance", 5.0); + setConfig(CONFIG_FLOAT_BLACKROCKSPIRE_DIFF, "Solocraft.BlackRockSpire", 10.0); + setConfig(CONFIG_FLOAT_BLACKROCKDEPTHS_DIFF, "Solocraft.BlackrockDepths", 5.0); + setConfig(CONFIG_FLOAT_ONYXIALAIRINSTANCE_DIFF, "Solocraft.OnyxiaLairInstance", 40.0); + setConfig(CONFIG_FLOAT_SCHOOLOFNECROMANCY_DIFF, "Solocraft.SchoolofNecromancy", 5.0); + setConfig(CONFIG_FLOAT_ZULGURUB_DIFF, "Solocraft.Zul'gurub", 20.0); + setConfig(CONFIG_FLOAT_STRATHOLME_DIFF, "Solocraft.Stratholme", 5.0); + setConfig(CONFIG_FLOAT_MAURADON_DIFF, "Solocraft.Mauradon", 5.0); + setConfig(CONFIG_FLOAT_ORGRIMMARINSTANCE_DIFF, "Solocraft.OrgrimmarInstance", 5.0); + setConfig(CONFIG_FLOAT_MOLTENCORE_DIFF, "Solocraft.MoltenCore", 40.0); + setConfig(CONFIG_FLOAT_DIREMAUL_DIFF, "Solocraft.DireMaul", 5.0); + setConfig(CONFIG_FLOAT_BLACKWINGLAIR_DIFF, "Solocraft.BlackwingLair", 40.0); + setConfig(CONFIG_FLOAT_AHNQIRAJ_DIFF, "Solocraft.AhnQiraj", 20.0); + setConfig(CONFIG_FLOAT_AHNQIRAJTEMPLE_DIFF, "Solocraft.AhnQirajTemple", 40.0); + //TBC Instances + setConfig(CONFIG_FLOAT_CAVERNSOFTIME_DIFF, "Solocraft.CavernsOfTime", 5.0); + setConfig(CONFIG_FLOAT_KARAZAHN_DIFF, "Solocraft.Karazahn", 10.0); + setConfig(CONFIG_FLOAT_HYJALPAST_DIFF, "Solocraft.HyjalPast", 25.0); + setConfig(CONFIG_FLOAT_HELLFIREMILITARY_DIFF, "Solocraft.HellfireMilitary", 5.0); + setConfig(CONFIG_FLOAT_HELLFIREDEMON_DIFF, "Solocraft.HellfireDemon", 5.0); + setConfig(CONFIG_FLOAT_HELLFIRERAMPART_DIFF, "Solocraft.HellfireRampart", 5.0); + setConfig(CONFIG_FLOAT_HELLFIRERAID_DIFF, "Solocraft.HellfireRaid", 25.0); + setConfig(CONFIG_FLOAT_COILFANGPUMPING_DIFF, "Solocraft.CoilfangPumping", 5.0); + setConfig(CONFIG_FLOAT_COILFANGMARSH_DIFF, "Solocraft.CoilfangMarsh", 5.0); + setConfig(CONFIG_FLOAT_COILFANGDRAENEI_DIFF, "Solocraft.CoilfangDraenei", 5.0); + setConfig(CONFIG_FLOAT_COILFANGRAID_DIFF, "Solocraft.CoilfangRaid", 25.0); + setConfig(CONFIG_FLOAT_TEMPESTKEEPRAID_DIFF, "Solocraft.TempestKeepRaid", 25.0); + setConfig(CONFIG_FLOAT_TEMPESTKEEPARCANE_DIFF, "Solocraft.TempestKeepArcane", 5.0); + setConfig(CONFIG_FLOAT_TEMPESTKEEPATRIUM_DIFF, "Solocraft.TempestKeepAtrium", 5.0); + setConfig(CONFIG_FLOAT_TEMPESTKEEPFACTORY_DIFF, "Solocraft.TempestKeepFactory", 5.0); + setConfig(CONFIG_FLOAT_AUCHINDOUNSHADOW_DIFF, "Solocraft.AuchindounShadow", 5.0); + setConfig(CONFIG_FLOAT_AUCHINDOUNDEMON_DIFF, "Solocraft.AuchindounDemon", 5.0); + setConfig(CONFIG_FLOAT_AUCHINDOUNETHEREAL_DIFF, "Solocraft.AuchindounEthereal", 5.0); + setConfig(CONFIG_FLOAT_AUCHINDOUNDRAENEI_DIFF, "Solocraft.AuchindounDraenei", 5.0); + setConfig(CONFIG_FLOAT_HILLSBRADPAST_DIFF, "Solocraft.HillsbradPast", 5.0); + setConfig(CONFIG_FLOAT_BLACKTEMPLE_DIFF, "Solocraft.BlackTemple", 25.0); + setConfig(CONFIG_FLOAT_GRUULSLAIR_DIFF, "Solocraft.GruulsLair", 25.0); + setConfig(CONFIG_FLOAT_ZULAMAN_DIFF, "Solocraft.ZulAman", 5.0); + setConfig(CONFIG_FLOAT_SUNWELLPLATEAU_DIFF, "Solocraft.SunwellPlateau", 25.0); + setConfig(CONFIG_FLOAT_SUNWELL5MANFIX_DIFF, "Solocraft.Sunwell5ManFix", 5.0); + //WotLK Instances + setConfig(CONFIG_FLOAT_STRATHOLMERAID_DIFF, "Solocraft.StratholmeRaid", 10.0); + setConfig(CONFIG_FLOAT_VALGARDE70_DIFF, "Solocraft.Valgarde70", 5.0); + setConfig(CONFIG_FLOAT_UTGARDEPINNACLE_DIFF, "Solocraft.UtgardePinnacle", 5.0); + setConfig(CONFIG_FLOAT_NEXUS70_DIFF, "Solocraft.Nexus70", 5.0); + setConfig(CONFIG_FLOAT_NEXUS80_DIFF, "Solocraft.Nexus80", 5.0); + setConfig(CONFIG_FLOAT_STRATHOLMECOT_DIFF, "Solocraft.StratholmeCOT", 5.0); + setConfig(CONFIG_FLOAT_ULDUAR70_DIFF, "Solocraft.Ulduar70", 5.0); + setConfig(CONFIG_FLOAT_DRAKTHERONKEEP_DIFF, "Solocraft.DrakTheronKeep", 5.0); + setConfig(CONFIG_FLOAT_AZJOL_UPPERCITY_DIFF, "Solocraft.Azjol_Uppercity", 5.0); + setConfig(CONFIG_FLOAT_ULDUAR80_DIFF, "Solocraft.Ulduar80", 5.0); + setConfig(CONFIG_FLOAT_ULDUARRAID_DIFF, "Solocraft.UlduarRaid", 10.0); + setConfig(CONFIG_FLOAT_GUNDRAK_DIFF, "Solocraft.GunDrak", 5.0); + setConfig(CONFIG_FLOAT_DALARANPRISON_DIFF, "Solocraft.DalaranPrison", 5.0); + setConfig(CONFIG_FLOAT_CHAMBEROFASPECTSBLACK_DIFF, "Solocraft.ChamberOfAspectsBlack", 10.0); + setConfig(CONFIG_FLOAT_NEXUSRAID_DIFF, "Solocraft.NexusRaid", 10.0); + setConfig(CONFIG_FLOAT_AZJOL_LOWERCITY_DIFF, "Solocraft.Azjol_LowerCity", 5.0); + setConfig(CONFIG_FLOAT_ICECROWNCITADEL_DIFF, "Solocraft.IcecrownCitadel", 10.0); + setConfig(CONFIG_FLOAT_ICECROWNCITADEL5MAN_DIFF, "Solocraft.IcecrownCitadel5Man", 5.0); + setConfig(CONFIG_FLOAT_ARGENTTOURNAMENTRAID_DIFF, "Solocraft.ArgentTournamentRaid", 10.0); + setConfig(CONFIG_FLOAT_ARGENTTOURNAMENTDUNGEON_DIFF, "Solocraft.ArgentTournamentDungeon", 5.0); + setConfig(CONFIG_FLOAT_QUARRYOFTEARS_DIFF, "Solocraft.QuarryOfTears", 5.0); + setConfig(CONFIG_FLOAT_HALLSOFREFLECTION_DIFF, "Solocraft.HallsOfReflection", 5.0); + setConfig(CONFIG_FLOAT_CHAMBEROFASPECTSRED_DIFF, "Solocraft.ChamberOfAspectsRed", 10.0); + //TBC Heroics + setConfig(CONFIG_FLOAT_HEROIC_CAVERNSOFTIME_DIFF, "Solocraft.CavernsOfTimeH", 5.0); + setConfig(CONFIG_FLOAT_HEROIC_HELLFIREMILITARY_DIFF, "Solocraft.HellfireMilitaryH", 5.0); + setConfig(CONFIG_FLOAT_HEROIC_HELLFIREDEMON_DIFF, "Solocraft.HellfireDemonH", 5.0); + setConfig(CONFIG_FLOAT_HEROIC_HELLFIRERAMPART_DIFF, "Solocraft.HellfireRampartH", 5.0); + setConfig(CONFIG_FLOAT_HEROIC_COILFANGPUMPING_DIFF, "Solocraft.CoilfangPumpingH", 5.0); + setConfig(CONFIG_FLOAT_HEROIC_COILFANGMARSH_DIFF, "Solocraft.CoilfangMarshH", 5.0); + setConfig(CONFIG_FLOAT_HEROIC_COILFANGDRAENEI_DIFF, "Solocraft.CoilfangDraeneiH", 5.0); + setConfig(CONFIG_FLOAT_HEROIC_TEMPESTKEEPARCANE_DIFF, "Solocraft.TempestKeepArcaneH", 5.0); + setConfig(CONFIG_FLOAT_HEROIC_TEMPESTKEEPATRIUM_DIFF, "Solocraft.TempestKeepAtriumH", 5.0); + setConfig(CONFIG_FLOAT_HEROIC_TEMPESTKEEPFACTORY_DIFF, "Solocraft.TempestKeepFactoryH", 5.0); + setConfig(CONFIG_FLOAT_HEROIC_AUCHINDOUNSHADOW_DIFF, "Solocraft.AuchindounShadowH", 5.0); + setConfig(CONFIG_FLOAT_HEROIC_AUCHINDOUNDEMON_DIFF, "Solocraft.AuchindounDemonH", 5.0); + setConfig(CONFIG_FLOAT_HEROIC_AUCHINDOUNETHEREAL_DIFF, "Solocraft.AuchindounEtherealH", 5.0); + setConfig(CONFIG_FLOAT_HEROIC_AUCHINDOUNDRAENEI_DIFF, "Solocraft.AuchindounDraeneiH", 5.0); + setConfig(CONFIG_FLOAT_HEROIC_HILLSBRADPAST_DIFF, "Solocraft.HillsbradPastH", 5.0); + setConfig(CONFIG_FLOAT_HEROIC_ZULAMAN_DIFF, "Solocraft.ZulAmanH", 5.0); + setConfig(CONFIG_FLOAT_HEROIC_SUNWELL5MANFIX_DIFF, "Solocraft.Sunwell5ManFixH", 5.0); + //WotLK Instances + setConfig(CONFIG_FLOAT_HEROIC_STRATHOLMERAID_DIFF, "Solocraft.StratholmeRaidH", 25.0); + setConfig(CONFIG_FLOAT_HEROIC_VALGARDE70_DIFF, "Solocraft.Valgarde70H", 5.0); + setConfig(CONFIG_FLOAT_HEROIC_UTGARDEPINNACLE_DIFF, "Solocraft.UtgardePinnacleH", 5.0); + setConfig(CONFIG_FLOAT_HEROIC_NEXUS70_DIFF, "Solocraft.Nexus70H", 5.0); + setConfig(CONFIG_FLOAT_HEROIC_NEXUS80_DIFF, "Solocraft.Nexus80H", 5.0); + setConfig(CONFIG_FLOAT_HEROIC_STRATHOLMECOT_DIFF, "Solocraft.StratholmeCOTH", 5.0); + setConfig(CONFIG_FLOAT_HEROIC_ULDUAR70_DIFF, "Solocraft.Ulduar70H", 5.0); + setConfig(CONFIG_FLOAT_HEROIC_DRAKTHERONKEEP_DIFF, "Solocraft.DrakTheronKeepH", 5.0); + setConfig(CONFIG_FLOAT_HEROIC_AZJOL_UPPERCITY_DIFF, "Solocraft.Azjol_UppercityH", 5.0); + setConfig(CONFIG_FLOAT_HEROIC_ULDUAR80_DIFF, "Solocraft.Ulduar80H", 5.0); + setConfig(CONFIG_FLOAT_HEROIC_ULDUARRAID_DIFF, "Solocraft.UlduarRaidH", 25.0); + setConfig(CONFIG_FLOAT_HEROIC_GUNDRAK_DIFF, "Solocraft.GunDrakH", 5.0); + setConfig(CONFIG_FLOAT_HEROIC_DALARANPRISON_DIFF, "Solocraft.DalaranPrisonH", 5.0); + setConfig(CONFIG_FLOAT_HEROIC_CHAMBEROFASPECTSBLACK_DIFF, "Solocraft.ChamberOfAspectsBlackH", 25.0); + setConfig(CONFIG_FLOAT_HEROIC_NEXUSRAID_DIFF, "Solocraft.NexusRaidH", 25.0); + setConfig(CONFIG_FLOAT_HEROIC_AZJOL_LOWERCITY_DIFF, "Solocraft.Azjol_LowerCityH", 5.0); + setConfig(CONFIG_FLOAT_HEROIC_ICECROWNCITADEL_DIFF, "Solocraft.IcecrownCitadelH", 25.0); + setConfig(CONFIG_FLOAT_HEROIC_ICECROWNCITADEL5MAN_DIFF, "Solocraft.IcecrownCitadel5ManH", 5.0); + setConfig(CONFIG_FLOAT_HEROIC_ARGENTTOURNAMENTRAID_DIFF, "Solocraft.ArgentTournamentRaidH", 25.0); + setConfig(CONFIG_FLOAT_HEROIC_ARGENTTOURNAMENTDUNGEON_DIFF, "Solocraft.ArgentTournamentDungeonH", 5.0); + setConfig(CONFIG_FLOAT_HEROIC_QUARRYOFTEARS_DIFF, "Solocraft.QuarryOfTearsH", 5.0); + setConfig(CONFIG_FLOAT_HEROIC_HALLSOFREFLECTION_DIFF, "Solocraft.HallsOfReflectionH", 5.0); + setConfig(CONFIG_FLOAT_HEROIC_CHAMBEROFASPECTSRED_DIFF, "Solocraft.ChamberOfAspectsRedH", 25.0); + //Special Cases + setConfig(CONFIG_FLOAT_HEROIC_ARGENTTOURNAMENTRAID10_DIFF, "Solocraft.ArgentTournamentRaidH10", 10.0); + setConfig(CONFIG_FLOAT_HEROIC_ARGENTTOURNAMENTRAID25_DIFF, "Solocraft.ArgentTournamentRaidH25", 25.0); +//End Solocraft Config + + sTransmogrification->LoadConfig(reload); + sLog.outString(); } @@ -1517,6 +1755,19 @@ void World::SetInitialWorldSettings() #ifdef BUILD_PLAYERBOT PlayerbotMgr::SetInitialWorldSettings(); #endif + +#ifdef ENABLE_PLAYERBOTS + sPlayerbotAIConfig.Initialize(); +#endif + + sTransmogrification->LoadConfig(false); + CharacterDatabase.Execute("DELETE FROM custom_transmogrification WHERE NOT EXISTS (SELECT 1 FROM item_instance WHERE item_instance.guid = custom_transmogrification.GUID)"); +#ifdef PRESETS + // Clean even if disabled + // Dont delete even if player has more presets than should + CharacterDatabase.Execute("DELETE FROM `custom_transmogrification_sets` WHERE NOT EXISTS(SELECT 1 FROM characters WHERE characters.guid = custom_transmogrification_sets.Owner)"); +#endif + sLog.outString("---------------------------------------"); sLog.outString(" CMANGOS: World initialized "); sLog.outString("---------------------------------------"); @@ -1579,6 +1830,57 @@ void World::Update(uint32 diff) m_currentMSTime = WorldTimer::getMSTime(); m_currentTime = std::chrono::time_point_cast(Clock::now()); m_currentDiff = diff; + m_currentDiffSum += diff; + m_currentDiffSumIndex++; + m_histDiff.push_back(diff); + m_maxDiff = std::max(m_maxDiff, diff); + + while (m_histDiff.size() >= 600) + { + m_currentDiffSum -= m_histDiff.front(); + m_histDiff.pop_front(); + } + + m_averageDiff = (uint32)(m_currentDiffSum / m_histDiff.size()); + + if (m_currentDiffSumIndex && m_currentDiffSumIndex % 60 == 0) + { + //m_averageDiff = (uint32)(m_currentDiffSum / m_currentDiffSumIndex); + //if (m_maxDiff < m_averageDiff) + // m_maxDiff = m_averageDiff; + sLog.outBasic("Avg Diff: %u. Sessions online: %u.", m_averageDiff, (uint32)GetActiveSessionCount()); + sLog.outBasic("Max Diff: %u.", m_maxDiff); + } + if (m_currentDiffSum % 3000 == 0) + { + m_maxDiff = *std::max_element(m_histDiff.begin(), m_histDiff.end()); + } + /*if (m_currentDiffSum > 300000) + { + m_currentDiffSum = 0; + m_currentDiffSumIndex = 0; + if (m_maxDiff > m_averageDiff) + { + m_maxDiff = m_averageDiff; + sLog.outBasic("Max Diff reset to: %u.", m_maxDiff); + } + } + if (GetActiveSessionCount()) + { + if (m_currentDiffSumIndex && (m_currentDiffSumIndex % 5 == 0)) + { + uint32 tempDiff = (uint32)(m_currentDiffSum / m_currentDiffSumIndex); + if (tempDiff > m_averageDiff) + { + m_averageDiff = tempDiff; + } + if (m_maxDiff < tempDiff) + { + m_maxDiff = tempDiff; + sLog.outBasic("Max Diff Increased: %u.", m_maxDiff); + } + } + }*/ ///- Update the different timers for (auto& m_timer : m_timers) @@ -1639,6 +1941,19 @@ void World::Update(uint32 diff) } #endif +#ifdef ENABLE_PLAYERBOTS +#ifndef BUILD_AHBOT + ///
  • Handle AHBot operations + if (m_timers[WUPDATE_AHBOT].Passed()) + { + auctionbot.Update(); + m_timers[WUPDATE_AHBOT].Reset(); + } +#endif + sRandomPlayerbotMgr.UpdateAI(diff); + sRandomPlayerbotMgr.UpdateSessions(diff); +#endif + ///
  • Handle session updates #ifdef BUILD_METRICS auto preSessionTime = std::chrono::time_point_cast(Clock::now()); @@ -2160,6 +2475,11 @@ void World::ShutdownServ(uint32 time, uint32 options, uint8 exitcode) m_ShutdownTimer = time; ShutdownMsg(true); } + +#ifdef ENABLE_PLAYERBOTS + //sRandomPlayerbotMgr.LogoutAllBots(); +#endif + } /// Display a shutdown message to the user(s) @@ -2267,6 +2587,7 @@ void World::UpdateResultQueue() CharacterDatabase.ProcessResultQueue(); WorldDatabase.ProcessResultQueue(); LoginDatabase.ProcessResultQueue(); + PlayerbotDatabase.ProcessResultQueue(); } void World::UpdateRealmCharCount(uint32 accountId) diff --git a/src/game/World/World.h b/src/game/World/World.h index f20e494bc90..9f3cfd5119f 100644 --- a/src/game/World/World.h +++ b/src/game/World/World.h @@ -224,6 +224,89 @@ enum eConfigUInt32Values CONFIG_UINT32_MAX_RECRUIT_A_FRIEND_BONUS_PLAYER_LEVEL, CONFIG_UINT32_MAX_RECRUIT_A_FRIEND_BONUS_PLAYER_LEVEL_DIFFERENCE, CONFIG_UINT32_SUNSREACH_COUNTER, +//Start Solocraft Defines + //Level Thresholds + CONFIG_UINT32_SOLOCRAFT_MAX_LEVEL_DIFF, + //Default Instance Level + CONFIG_UINT32_DUNGEON_LEVEL, + //Classic Instances + CONFIG_UINT32_SHADOWFANGKEEP_LEVEL, + CONFIG_UINT32_STOCKADES_LEVEL, + CONFIG_UINT32_DEADMINES_LEVEL, + CONFIG_UINT32_WAILINGCAVERNS_LEVEL, + CONFIG_UINT32_RAZORFENKRAULINSTANCE_LEVEL, + CONFIG_UINT32_BLACKFATHOM_LEVEL, + CONFIG_UINT32_ULDAMAN_LEVEL, + CONFIG_UINT32_GNOMERAGONINSTANCE_LEVEL, + CONFIG_UINT32_SUNKENTEMPLE_LEVEL, + CONFIG_UINT32_RAZORFENDOWNS_LEVEL, + CONFIG_UINT32_MONASTERYINSTANCES_LEVEL, + CONFIG_UINT32_TANARISINSTANCE_LEVEL, + CONFIG_UINT32_BLACKROCKSPIRE_LEVEL, + CONFIG_UINT32_BLACKROCKDEPTHS_LEVEL, + CONFIG_UINT32_ONYXIALAIRINSTANCE_LEVEL, + CONFIG_UINT32_SCHOOLOFNECROMANCY_LEVEL, + CONFIG_UINT32_ZULGURUB_LEVEL, + CONFIG_UINT32_STRATHOLME_LEVEL, + CONFIG_UINT32_MAURADON_LEVEL, + CONFIG_UINT32_ORGRIMMARINSTANCE_LEVEL, + CONFIG_UINT32_MOLTENCORE_LEVEL, + CONFIG_UINT32_DIREMAUL_LEVEL, + CONFIG_UINT32_BLACKWINGLAIR_LEVEL, + CONFIG_UINT32_AHNQIRAJ_LEVEL, + CONFIG_UINT32_AHNQIRAJTEMPLE_LEVEL, + //TBC Instances + CONFIG_UINT32_CAVERNSOFTIME_LEVEL, + CONFIG_UINT32_KARAZAHN_LEVEL, + CONFIG_UINT32_HYJALPAST_LEVEL, + CONFIG_UINT32_HELLFIREMILITARY_LEVEL, + CONFIG_UINT32_HELLFIREDEMON_LEVEL, + CONFIG_UINT32_HELLFIRERAMPART_LEVEL, + CONFIG_UINT32_HELLFIRERAID_LEVEL, + CONFIG_UINT32_COILFANGPUMPING_LEVEL, + CONFIG_UINT32_COILFANGMARSH_LEVEL, + CONFIG_UINT32_COILFANGDRAENEI_LEVEL, + CONFIG_UINT32_COILFANGRAID_LEVEL, + CONFIG_UINT32_TEMPESTKEEPRAID_LEVEL, + CONFIG_UINT32_TEMPESTKEEPARCANE_LEVEL, + CONFIG_UINT32_TEMPESTKEEPATRIUM_LEVEL, + CONFIG_UINT32_TEMPESTKEEPFACTORY_LEVEL, + CONFIG_UINT32_AUCHINDOUNSHADOW_LEVEL, + CONFIG_UINT32_AUCHINDOUNDEMON_LEVEL, + CONFIG_UINT32_AUCHINDOUNETHEREAL_LEVEL, + CONFIG_UINT32_AUCHINDOUNDRAENEI_LEVEL, + CONFIG_UINT32_HILLSBRADPAST_LEVEL, + CONFIG_UINT32_BLACKTEMPLE_LEVEL, + CONFIG_UINT32_GRUULSLAIR_LEVEL, + CONFIG_UINT32_ZULAMAN_LEVEL, + CONFIG_UINT32_SUNWELLPLATEAU_LEVEL, + CONFIG_UINT32_SUNWELL5MANFIX_LEVEL, + //WotLK Instances + CONFIG_UINT32_STRATHOLMERAID_LEVEL, + CONFIG_UINT32_VALGARDE70_LEVEL, + CONFIG_UINT32_UTGARDEPINNACLE_LEVEL, + CONFIG_UINT32_NEXUS70_LEVEL, + CONFIG_UINT32_NEXUS80_LEVEL, + CONFIG_UINT32_STRATHOLMECOT_LEVEL, + CONFIG_UINT32_ULDUAR70_LEVEL, + CONFIG_UINT32_DRAKTHERONKEEP_LEVEL, + CONFIG_UINT32_AZJOL_UPPERCITY_LEVEL, + CONFIG_UINT32_ULDUAR80_LEVEL, + CONFIG_UINT32_ULDUARRAID_LEVEL, + CONFIG_UINT32_GUNDRAK_LEVEL, + CONFIG_UINT32_DALARANPRISON_LEVEL, + CONFIG_UINT32_CHAMBEROFASPECTSBLACK_LEVEL, + CONFIG_UINT32_NEXUSRAID_LEVEL, + CONFIG_UINT32_AZJOL_LOWERCITY_LEVEL, + CONFIG_UINT32_ICECROWNCITADEL_LEVEL, + CONFIG_UINT32_ICECROWNCITADEL5MAN_LEVEL, + CONFIG_UINT32_ARGENTTOURNAMENTRAID_LEVEL, + CONFIG_UINT32_ARGENTTOURNAMENTDUNGEON_LEVEL, + CONFIG_UINT32_QUARRYOFTEARS_LEVEL, + CONFIG_UINT32_HALLSOFREFLECTION_LEVEL, + CONFIG_UINT32_CHAMBEROFASPECTSRED_LEVEL, +//End Solocraft Defines + CONFIG_BOOL_COLLECTORS_EDITION, CONFIG_UINT32_VALUE_COUNT }; @@ -236,6 +319,7 @@ enum eConfigInt32Values CONFIG_INT32_QUEST_LOW_LEVEL_HIDE_DIFF, CONFIG_INT32_QUEST_HIGH_LEVEL_HIDE_DIFF, CONFIG_INT32_VALUE_COUNT + }; /// Server config @@ -319,6 +403,137 @@ enum eConfigFloatValues CONFIG_FLOAT_MOD_INCREASED_XP, CONFIG_FLOAT_MOD_INCREASED_GOLD, CONFIG_FLOAT_MAX_RECRUIT_A_FRIEND_DISTANCE, +//Start Solocraft Defines + //Balancing + CONFIG_FLOAT_SOLOCRAFT_SPELLPOWER_MULT, + CONFIG_FLOAT_SOLOCRAFT_STATS_MULT, + //Dungeon Catch-all + CONFIG_FLOAT_DUNGEON_DIFF, + CONFIG_FLOAT_HEROIC_DIFF, + CONFIG_FLOAT_RAID25_DIFF, + CONFIG_FLOAT_RAID40_DIFF, + //Classic Instances + CONFIG_FLOAT_SHADOWFANGKEEP_DIFF, + CONFIG_FLOAT_STOCKADES_DIFF, + CONFIG_FLOAT_DEADMINES_DIFF, + CONFIG_FLOAT_WAILINGCAVERNS_DIFF, + CONFIG_FLOAT_RAZORFENKRAULINSTANCE_DIFF, + CONFIG_FLOAT_BLACKFATHOM_DIFF, + CONFIG_FLOAT_ULDAMAN_DIFF, + CONFIG_FLOAT_GNOMERAGONINSTANCE_DIFF, + CONFIG_FLOAT_SUNKENTEMPLE_DIFF, + CONFIG_FLOAT_RAZORFENDOWNS_DIFF, + CONFIG_FLOAT_MONASTERYINSTANCES_DIFF, + CONFIG_FLOAT_TANARISINSTANCE_DIFF, + CONFIG_FLOAT_BLACKROCKSPIRE_DIFF, + CONFIG_FLOAT_BLACKROCKDEPTHS_DIFF, + CONFIG_FLOAT_ONYXIALAIRINSTANCE_DIFF, + CONFIG_FLOAT_SCHOOLOFNECROMANCY_DIFF, + CONFIG_FLOAT_ZULGURUB_DIFF, + CONFIG_FLOAT_STRATHOLME_DIFF, + CONFIG_FLOAT_MAURADON_DIFF, + CONFIG_FLOAT_ORGRIMMARINSTANCE_DIFF, + CONFIG_FLOAT_MOLTENCORE_DIFF, + CONFIG_FLOAT_DIREMAUL_DIFF, + CONFIG_FLOAT_BLACKWINGLAIR_DIFF, + CONFIG_FLOAT_AHNQIRAJ_DIFF, + CONFIG_FLOAT_AHNQIRAJTEMPLE_DIFF, + //TBC Instances + CONFIG_FLOAT_CAVERNSOFTIME_DIFF, + CONFIG_FLOAT_KARAZAHN_DIFF, + CONFIG_FLOAT_HYJALPAST_DIFF, + CONFIG_FLOAT_HELLFIREMILITARY_DIFF, + CONFIG_FLOAT_HELLFIREDEMON_DIFF, + CONFIG_FLOAT_HELLFIRERAMPART_DIFF, + CONFIG_FLOAT_HELLFIRERAID_DIFF, + CONFIG_FLOAT_COILFANGPUMPING_DIFF, + CONFIG_FLOAT_COILFANGMARSH_DIFF, + CONFIG_FLOAT_COILFANGDRAENEI_DIFF, + CONFIG_FLOAT_COILFANGRAID_DIFF, + CONFIG_FLOAT_TEMPESTKEEPRAID_DIFF, + CONFIG_FLOAT_TEMPESTKEEPARCANE_DIFF, + CONFIG_FLOAT_TEMPESTKEEPATRIUM_DIFF, + CONFIG_FLOAT_TEMPESTKEEPFACTORY_DIFF, + CONFIG_FLOAT_AUCHINDOUNSHADOW_DIFF, + CONFIG_FLOAT_AUCHINDOUNDEMON_DIFF, + CONFIG_FLOAT_AUCHINDOUNETHEREAL_DIFF, + CONFIG_FLOAT_AUCHINDOUNDRAENEI_DIFF, + CONFIG_FLOAT_HILLSBRADPAST_DIFF, + CONFIG_FLOAT_BLACKTEMPLE_DIFF, + CONFIG_FLOAT_GRUULSLAIR_DIFF, + CONFIG_FLOAT_ZULAMAN_DIFF, + CONFIG_FLOAT_SUNWELLPLATEAU_DIFF, + CONFIG_FLOAT_SUNWELL5MANFIX_DIFF, + //WotLK Instances + CONFIG_FLOAT_STRATHOLMERAID_DIFF, + CONFIG_FLOAT_VALGARDE70_DIFF, + CONFIG_FLOAT_UTGARDEPINNACLE_DIFF, + CONFIG_FLOAT_NEXUS70_DIFF, + CONFIG_FLOAT_NEXUS80_DIFF, + CONFIG_FLOAT_STRATHOLMECOT_DIFF, + CONFIG_FLOAT_ULDUAR70_DIFF, + CONFIG_FLOAT_DRAKTHERONKEEP_DIFF, + CONFIG_FLOAT_AZJOL_UPPERCITY_DIFF, + CONFIG_FLOAT_ULDUAR80_DIFF, + CONFIG_FLOAT_ULDUARRAID_DIFF, + CONFIG_FLOAT_GUNDRAK_DIFF, + CONFIG_FLOAT_DALARANPRISON_DIFF, + CONFIG_FLOAT_CHAMBEROFASPECTSBLACK_DIFF, + CONFIG_FLOAT_NEXUSRAID_DIFF, + CONFIG_FLOAT_AZJOL_LOWERCITY_DIFF, + CONFIG_FLOAT_ICECROWNCITADEL_DIFF, + CONFIG_FLOAT_ICECROWNCITADEL5MAN_DIFF, + CONFIG_FLOAT_ARGENTTOURNAMENTRAID_DIFF, + CONFIG_FLOAT_ARGENTTOURNAMENTDUNGEON_DIFF, + CONFIG_FLOAT_QUARRYOFTEARS_DIFF, + CONFIG_FLOAT_HALLSOFREFLECTION_DIFF, + CONFIG_FLOAT_CHAMBEROFASPECTSRED_DIFF, + //TBC Heroic Instances + CONFIG_FLOAT_HEROIC_CAVERNSOFTIME_DIFF, + CONFIG_FLOAT_HEROIC_HELLFIREMILITARY_DIFF, + CONFIG_FLOAT_HEROIC_HELLFIREDEMON_DIFF, + CONFIG_FLOAT_HEROIC_HELLFIRERAMPART_DIFF, + CONFIG_FLOAT_HEROIC_COILFANGPUMPING_DIFF, + CONFIG_FLOAT_HEROIC_COILFANGMARSH_DIFF, + CONFIG_FLOAT_HEROIC_COILFANGDRAENEI_DIFF, + CONFIG_FLOAT_HEROIC_TEMPESTKEEPARCANE_DIFF, + CONFIG_FLOAT_HEROIC_TEMPESTKEEPATRIUM_DIFF, + CONFIG_FLOAT_HEROIC_TEMPESTKEEPFACTORY_DIFF, + CONFIG_FLOAT_HEROIC_AUCHINDOUNSHADOW_DIFF, + CONFIG_FLOAT_HEROIC_AUCHINDOUNDEMON_DIFF, + CONFIG_FLOAT_HEROIC_AUCHINDOUNETHEREAL_DIFF, + CONFIG_FLOAT_HEROIC_AUCHINDOUNDRAENEI_DIFF, + CONFIG_FLOAT_HEROIC_HILLSBRADPAST_DIFF, + CONFIG_FLOAT_HEROIC_ZULAMAN_DIFF, + CONFIG_FLOAT_HEROIC_SUNWELL5MANFIX_DIFF, + //WotLK Heroic Instances + CONFIG_FLOAT_HEROIC_STRATHOLMERAID_DIFF, + CONFIG_FLOAT_HEROIC_VALGARDE70_DIFF, + CONFIG_FLOAT_HEROIC_UTGARDEPINNACLE_DIFF, + CONFIG_FLOAT_HEROIC_NEXUS70_DIFF, + CONFIG_FLOAT_HEROIC_NEXUS80_DIFF, + CONFIG_FLOAT_HEROIC_STRATHOLMECOT_DIFF, + CONFIG_FLOAT_HEROIC_ULDUAR70_DIFF, + CONFIG_FLOAT_HEROIC_DRAKTHERONKEEP_DIFF, + CONFIG_FLOAT_HEROIC_AZJOL_UPPERCITY_DIFF, + CONFIG_FLOAT_HEROIC_ULDUAR80_DIFF, + CONFIG_FLOAT_HEROIC_ULDUARRAID_DIFF, + CONFIG_FLOAT_HEROIC_GUNDRAK_DIFF, + CONFIG_FLOAT_HEROIC_DALARANPRISON_DIFF, + CONFIG_FLOAT_HEROIC_CHAMBEROFASPECTSBLACK_DIFF, + CONFIG_FLOAT_HEROIC_NEXUSRAID_DIFF, + CONFIG_FLOAT_HEROIC_AZJOL_LOWERCITY_DIFF, + CONFIG_FLOAT_HEROIC_ICECROWNCITADEL_DIFF, + CONFIG_FLOAT_HEROIC_ICECROWNCITADEL5MAN_DIFF, + CONFIG_FLOAT_HEROIC_ARGENTTOURNAMENTRAID_DIFF, + CONFIG_FLOAT_HEROIC_ARGENTTOURNAMENTDUNGEON_DIFF, + CONFIG_FLOAT_HEROIC_QUARRYOFTEARS_DIFF, + CONFIG_FLOAT_HEROIC_HALLSOFREFLECTION_DIFF, + CONFIG_FLOAT_HEROIC_CHAMBEROFASPECTSRED_DIFF, + //Special Cases + CONFIG_FLOAT_HEROIC_ARGENTTOURNAMENTRAID10_DIFF, + CONFIG_FLOAT_HEROIC_ARGENTTOURNAMENTRAID25_DIFF, +//End Solocraft Defines CONFIG_FLOAT_VALUE_COUNT }; @@ -368,6 +583,8 @@ enum eConfigBoolValues CONFIG_BOOL_TAXI_FLIGHT_CHAT_FIX, CONFIG_BOOL_LONG_TAXI_PATHS_PERSISTENCE, CONFIG_BOOL_ALL_TAXI_PATHS, + CONFIG_BOOL_INSTANT_TAXI, + CONFIG_BOOL_FAR_VISIBLE_TAXI, CONFIG_BOOL_DECLINED_NAMES_USED, CONFIG_BOOL_SKILL_MILLING, CONFIG_BOOL_SKILL_FAIL_LOOT_FISHING, @@ -400,6 +617,11 @@ enum eConfigBoolValues CONFIG_BOOL_PATH_FIND_OPTIMIZE, CONFIG_BOOL_PATH_FIND_NORMALIZE_Z, CONFIG_BOOL_ALWAYS_SHOW_QUEST_GREETING, +//Start Solocraft Defines + CONFIG_BOOL_SOLOCRAFT_ENABLED, + CONFIG_BOOL_SOLOCRAFT_ANNOUNCE, + CONFIG_BOOL_SOLOCRAFT_DEBUFF_ENABLE, +//End Solocraft Defines CONFIG_BOOL_VALUE_COUNT }; @@ -671,6 +893,8 @@ class World static uint32 GetCurrentMSTime() { return m_currentMSTime; } static TimePoint GetCurrentClockTime() { return m_currentTime; } static uint32 GetCurrentDiff() { return m_currentDiff; } + static uint32 GetAverageDiff() { return m_averageDiff; } + static uint32 GetMaxDiff() { return m_maxDiff; } template void ExecuteForAllSessions(T executor) @@ -808,6 +1032,11 @@ class World static uint32 m_currentMSTime; static TimePoint m_currentTime; static uint32 m_currentDiff; + static uint32 m_currentDiffSum; + static uint32 m_currentDiffSumIndex; + static uint32 m_averageDiff; + static uint32 m_maxDiff; + static std::list m_histDiff; Messager m_messager; diff --git a/src/mangosd/CliRunnable.cpp b/src/mangosd/CliRunnable.cpp index 65202998e3b..031f37aa50e 100644 --- a/src/mangosd/CliRunnable.cpp +++ b/src/mangosd/CliRunnable.cpp @@ -500,7 +500,7 @@ bool ChatHandler::HandleAccountCreateCommand(char* args) if (ExtractUInt32(&args, expansion)) result = sAccountMgr.CreateAccount(account_name, password, expansion); else - result = sAccountMgr.CreateAccount(account_name, password); + result = sAccountMgr.CreateAccount(account_name, password, MAX_EXPANSION); switch (result) { case AOR_OK: diff --git a/src/mangosd/Main.cpp b/src/mangosd/Main.cpp index 73af5d6b874..736f13c33a3 100644 --- a/src/mangosd/Main.cpp +++ b/src/mangosd/Main.cpp @@ -62,6 +62,7 @@ DatabaseType WorldDatabase; ///< Accessor to the DatabaseType CharacterDatabase; ///< Accessor to the character database DatabaseType LoginDatabase; ///< Accessor to the realm/login database DatabaseType LogsDatabase; ///< Accessor to the logs database +DatabaseType PlayerbotDatabase; ///< Accessor to the playerbot database uint32 realmID; ///< Id of the realm diff --git a/src/mangosd/Master.cpp b/src/mangosd/Master.cpp index c6a5fbe7aaf..d296cc2c23c 100644 --- a/src/mangosd/Master.cpp +++ b/src/mangosd/Master.cpp @@ -132,6 +132,7 @@ int Master::Run() WorldDatabase.AllowAsyncTransactions(); LoginDatabase.AllowAsyncTransactions(); LogsDatabase.AllowAsyncTransactions(); + PlayerbotDatabase.AllowAsyncTransactions(); ///- Catch termination signals _HookSignals(); @@ -362,6 +363,39 @@ bool Master::_StartDB() CharacterDatabase.HaltDelayThread(); return false; } +#ifdef BUILD_PLAYERBOT + if (!CharacterDatabase.CheckRequiredField("playerbot_db_version", REVISION_DB_PLAYERBOTAI)) + { + ///- Wait for already started DB delay threads to end + WorldDatabase.HaltDelayThread(); + CharacterDatabase.HaltDelayThread(); + return false; + } +#endif + /// Playerbot Database + dbstring = sConfig.GetStringDefault("PlayerbotDatabaseInfo", ""); + nConnections = sConfig.GetIntDefault("PlayerbotDatabaseConnections", 1); + if (dbstring.empty()) + { + sLog.outError("Playerbot Database not specified in configuration file"); + + ///- Wait for already started DB delay threads to end + WorldDatabase.HaltDelayThread(); + CharacterDatabase.HaltDelayThread(); + return false; + } + sLog.outString("Playerbot Database total connections: %i", nConnections + 1); + + ///- Initialise the Playerbot database + if (!PlayerbotDatabase.Initialize(dbstring.c_str(), nConnections)) + { + sLog.outError("Can not connect to Playerbot database %s", dbstring.c_str()); + + ///- Wait for already started DB delay threads to end + CharacterDatabase.HaltDelayThread(); + WorldDatabase.HaltDelayThread(); + return false; + } ///- Get login database info from configuration file dbstring = sConfig.GetStringDefault("LoginDatabaseInfo"); @@ -373,6 +407,7 @@ bool Master::_StartDB() ///- Wait for already started DB delay threads to end WorldDatabase.HaltDelayThread(); CharacterDatabase.HaltDelayThread(); + PlayerbotDatabase.HaltDelayThread(); return false; } @@ -385,6 +420,7 @@ bool Master::_StartDB() ///- Wait for already started DB delay threads to end WorldDatabase.HaltDelayThread(); CharacterDatabase.HaltDelayThread(); + PlayerbotDatabase.HaltDelayThread(); return false; } @@ -393,6 +429,7 @@ bool Master::_StartDB() ///- Wait for already started DB delay threads to end WorldDatabase.HaltDelayThread(); CharacterDatabase.HaltDelayThread(); + PlayerbotDatabase.HaltDelayThread(); LoginDatabase.HaltDelayThread(); return false; } @@ -407,6 +444,7 @@ bool Master::_StartDB() ///- Wait for already started DB delay threads to end WorldDatabase.HaltDelayThread(); CharacterDatabase.HaltDelayThread(); + PlayerbotDatabase.HaltDelayThread(); LoginDatabase.HaltDelayThread(); return false; } @@ -420,6 +458,7 @@ bool Master::_StartDB() ///- Wait for already started DB delay threads to end WorldDatabase.HaltDelayThread(); CharacterDatabase.HaltDelayThread(); + PlayerbotDatabase.HaltDelayThread(); LoginDatabase.HaltDelayThread(); return false; } @@ -429,6 +468,7 @@ bool Master::_StartDB() ///- Wait for already started DB delay threads to end WorldDatabase.HaltDelayThread(); CharacterDatabase.HaltDelayThread(); + PlayerbotDatabase.HaltDelayThread(); LoginDatabase.HaltDelayThread(); return false; } @@ -444,6 +484,7 @@ bool Master::_StartDB() ///- Wait for already started DB delay threads to end WorldDatabase.HaltDelayThread(); CharacterDatabase.HaltDelayThread(); + PlayerbotDatabase.HaltDelayThread(); LoginDatabase.HaltDelayThread(); return false; } diff --git a/src/mangosd/mangosd.conf.dist.in b/src/mangosd/mangosd.conf.dist.in index 648cdc986e4..e60f199cf97 100644 --- a/src/mangosd/mangosd.conf.dist.in +++ b/src/mangosd/mangosd.conf.dist.in @@ -71,10 +71,12 @@ LoginDatabaseInfo = "127.0.0.1;3306;mangos;mangos;wotlkrealmd" WorldDatabaseInfo = "127.0.0.1;3306;mangos;mangos;wotlkmangos" CharacterDatabaseInfo = "127.0.0.1;3306;mangos;mangos;wotlkcharacters" LogsDatabaseInfo = "127.0.0.1;3306;mangos;mangos;wotlklogs" +PlayerbotDatabaseInfo = "127.0.0.1;3306;mangos;mangos;wotlkplayerbots" LoginDatabaseConnections = 1 WorldDatabaseConnections = 1 CharacterDatabaseConnections = 1 LogsDatabaseConnections = 1 +PlayerbotDatabaseConnections = 1 MaxPingTime = 30 WorldServerPort = 8085 BindIP = "0.0.0.0" @@ -635,6 +637,16 @@ LogColors = "" # Default: 0 (false) # 1 (true) # +# InstantFlightPaths +# Flight paths will take players to their destination instantly, instead of making them wait to fly there. +# Default: 0 (false) +# 1 (true) +# +# TaxiFlightFarVisibility +# Visibility of taxi flying characters is increased up to 400y, making them visible from afar. +# Default: 0 (false) +# 1 (true) +# # AlwaysMaxSkillForLevel # Players will automatically gain max level dependent (weapon/defense) skill when logging in, leveling up etc. # Default: 0 (false) @@ -893,6 +905,8 @@ AlwaysShowQuestGreeting = 0 TaxiFlightChatFix = 0 LongFlightPathsPersistence = 0 AllFlightPaths = 0 +InstantFlightPaths = 0 +TaxiFlightFarVisibility = 0 AlwaysMaxSkillForLevel = 0 ActivateWeather = 1 CastUnstuck = 1 @@ -1793,6 +1807,368 @@ Battlefield.CooldownDuraton = 150 Battlefield.BattleDuration = 30 Battlefield.MaxPlayersPerTeam = 120 +################################################################################################################### +# SOLOCRAFT SETTINGS +# +# Solocraft.Enable +# Enable Solocraft module +# Default: 1 (Enabled) +# 0 (Disabled) +# +# Solocraft.Announce +# Announce the module when the player logs in? +# Default: 1 (Enabled) +# 0 (Disabled) +# +# Solocraft.Debuff.Enable +# Enable Debuff of new party members who enter the dungeon to avoid overloading the dungeon? +# Overloading is when a group member already in the dungeon has the full offset/buff of the +# dungeon applied then they invite new group members who enter the dungeon and also receive an +# offset which creates a huge imbalance. +# The debuff will make the new members weak until all members leave the instance/dungeon +# instance/dungeon and re-enter which will then split the offset among all the group members. +# Default: 1 (Enabled) +# 0 (Disabled) +# +# SoloCraft.Spellpower.Mult +# Spellpower Bonus Multiplier +# Spellcasters spellpower bonus multiplier to offset no spellpower received from stats +# Formula: (player->level * multiplier) * difficulty +# Example Level 24 Mage with default modifier and a dungeon difficulty of 5 will receive a base +# Spellpower increase of 300. +# Default: 2.5 +# 0.0 (Disabled) +# +# SoloCraft.Stats.Mult +# Stats Percentage Bonus Multiplier +# Forumla: player->Stats * (difficulty * multiplier) +# Default: 100.0 +# 0.0 (Disable) +################################################################################################################### +Solocraft.Enable = 1 +Solocraft.Announce = 1 +SoloCraft.Debuff.Enable = 1 +SoloCraft.Spellpower.Mult = 2.5 +SoloCraft.Stats.Mult = 100.0 +################################################################################################ +#Catch all Bucket - Difficulty Offset Defaults +################################################################################################ +# Difficulty Multipliers +# Defaults: 5, 10, 25, 40 +Solocraft.Dungeon = 5.0 +Solocraft.Heroic = 10.0 +Solocraft.Raid25 = 25.0 +Solocraft.Raid40 = 40.0 +################################################################################################ +#Classic Instances - Difficulty +################################################################################################ +Solocraft.ShadowfangKeep = 5.0 +Solocraft.Stockades = 5.0 +Solocraft.Deadmines = 5.0 +Solocraft.WailingCaverns = 5.0 +Solocraft.RazorfenKraulInstance = 5.0 +Solocraft.Blackfathom = 5.0 +Solocraft.Uldaman = 5.0 +Solocraft.GnomeragonInstance = 5.0 +Solocraft.SunkenTemple = 5.0 +Solocraft.RazorfenDowns = 5.0 +# Scarlet Monastery +Solocraft.MonasteryInstances = 5.0 +# Zul'Farrak +Solocraft.TanarisInstance = 5.0 +Solocraft.BlackRockSpire = 10.0 +Solocraft.BlackrockDepths = 5.0 +Solocraft.OnyxiaLairInstance = 40.0 +#Scholomance +Solocraft.SchoolofNecromancy = 5.0 +Solocraft.Zul'gurub = 20.0 +Solocraft.Stratholme = 5.0 +Solocraft.Mauradon = 5.0 +# Ragefire Chasm +Solocraft.OrgrimmarInstance = 5.0 +Solocraft.MoltenCore = 40.0 +Solocraft.DireMaul = 5.0 +Solocraft.BlackwingLair = 40.0 +# Ruins of Ahn'Qiraj +Solocraft.AhnQiraj = 20.0 +Solocraft.AhnQirajTemple = 40.0 +################################################################################################ +#BC Instances - Difficulty +################################################################################################ +# The Black Morass +Solocraft.CavernsOfTime = 5.0 +Solocraft.CavernsOfTimeH = 5.0 +Solocraft.Karazahn = 10.0 +# The Battle for Mount Hyjal - Hyjal Summit +Solocraft.HyjalPast = 25.0 +# The Shattered Halls +Solocraft.HellfireMilitary = 5.0 +Solocraft.HellfireMilitaryH = 5.0 +# The Blood Furnace +Solocraft.HellfireDemon = 5.0 +Solocraft.HellfireDemonH = 5.0 +Solocraft.HellfireRampart = 5.0 +Solocraft.HellfireRampartH = 5.0 +# Magtheridon's Lair +Solocraft.HellfireRaid = 25.0 +# The Steamvault +Solocraft.CoilfangPumping = 5.0 +Solocraft.CoilfangPumpingH = 5.0 +# The Underbog +Solocraft.CoilfangMarsh = 5.0 +Solocraft.CoilfangMarshH = 5.0 +# The Slavepens +Solocraft.CoilfangDraenei = 5.0 +Solocraft.CoilfangDraeneiH = 5.0 +# Serpentshrine Cavern +Solocraft.CoilfangRaid = 25.0 +# The Eye +Solocraft.TempestKeepRaid = 25.0 +# The Arcatraz +Solocraft.TempestKeepArcane = 5.0 +Solocraft.TempestKeepArcaneH = 5.0 +# The Botanica +Solocraft.TempestKeepAtrium = 5.0 +Solocraft.TempestKeepAtriumH = 5.0 +# The Mechanar +Solocraft.TempestKeepFactory = 5.0 +Solocraft.TempestKeepFactoryH = 5.0 +# Shadow Labyrinth +Solocraft.AuchindounShadow = 5.0 +Solocraft.AuchindounShadowH = 5.0 +# Sethekk Halls +Solocraft.AuchindounDemon = 5.0 +Solocraft.AuchindounDemonH = 5.0 +# Mana-Tombs +Solocraft.AuchindounEthereal = 5.0 +Solocraft.AuchindounEtherealH = 5.0 +# Auchenai Crypts +Solocraft.AuchindounDraenei = 5.0 +Solocraft.AuchindounDraeneiH = 5.0 +# Old Hillsbrad Foothills +Solocraft.HillsbradPast = 5.0 +Solocraft.HillsbradPastH = 5.0 +Solocraft.BlackTemple = 25.0 +Solocraft.GruulsLair = 25.0 +Solocraft.ZulAman = 5.0 +Solocraft.ZulAmanH = 5.0 +Solocraft.SunwellPlateau = 25.0 +# Magister's Terrace +Solocraft.Sunwell5ManFix = 5.0 +Solocraft.Sunwell5ManFixH = 5.0 +################################################################################################ +#WOTLK Instances - Difficulty +################################################################################################ +# Naxxramas +Solocraft.StratholmeRaid = 10.0 +Solocraft.StratholmeRaidH = 25.0 +# Utgarde Keep +Solocraft.Valgarde70 = 5.0 +Solocraft.Valgarde70H = 5.0 +Solocraft.UtgardePinnacle = 5.0 +Solocraft.UtgardePinnacleH = 5.0 +# The Nexus +Solocraft.Nexus70 = 5.0 +Solocraft.Nexus70H = 5.0 +# The Occulus +Solocraft.Nexus80 = 5.0 +Solocraft.Nexus80H = 5.0 +# The Culling of Stratholme +Solocraft.StratholmeCOT = 5.0 +Solocraft.StratholmeCOTH = 5.0 +# Halls of Stone +Solocraft.Ulduar70 = 5.0 +Solocraft.Ulduar70H = 5.0 +# Drak'Tharon Keep +Solocraft.DrakTheronKeep = 5.0 +Solocraft.DrakTheronKeepH = 5.0 +# Azjol-Nerub +Solocraft.Azjol_Uppercity = 5.0 +Solocraft.Azjol_UppercityH = 5.0 +# Halls of Lighting +Solocraft.Ulduar80 = 5.0 +Solocraft.Ulduar80H = 5.0 +# Ulduar +Solocraft.UlduarRaid = 10.0 +Solocraft.UlduarRaidH = 25.0 +Solocraft.GunDrak = 5.0 +Solocraft.GunDrakH = 5.0 +# Violet Hold +Solocraft.DalaranPrison = 5.0 +Solocraft.DalaranPrisonH = 5.0 +# The Obsidian Sanctum +Solocraft.ChamberOfAspectsBlack = 10.0 +Solocraft.ChamberOfAspectsBlackH = 25.0 +# The Eye of Eternity +Solocraft.NexusRaid = 10.0 +Solocraft.NexusRaidH = 25.0 +# Ahn'kahet: The Old Kingdom +Solocraft.Azjol_LowerCity = 5.0 +Solocraft.Azjol_LowerCityH = 5.0 +Solocraft.IcecrownCitadel = 10.0 +Solocraft.IcecrownCitadelH = 25.0 +# The Forge of Souls +Solocraft.IcecrownCitadel5Man = 5.0 +Solocraft.IcecrownCitadel5ManH = 5.0 +# Trial of the Crusader +Solocraft.ArgentTournamentRaid = 10.0 +Solocraft.ArgentTournamentRaidH = 25.0 +# Grand Trial of the Crusader Heroic +Solocraft.ArgentTournamentRaidH10 = 10.0 +Solocraft.ArgentTournamentRaidH25 = 25.0 +# Trial of the Champion +Solocraft.ArgentTournamentDungeon = 5.0 +Solocraft.ArgentTournamentDungeonH = 5.0 +# Pit of Saron +Solocraft.QuarryOfTears = 5.0 +Solocraft.QuarryOfTearsH = 5.0 +# Halls of Reflection +Solocraft.HallsOfReflection = 5.0 +Solocraft.HallsOfReflectionH = 5.0 +# The Ruby Sanctum +Solocraft.ChamberOfAspectsRed = 5.0 +Solocraft.ChamberOfAspectsRedH = 5.0 +################################################################################################ +#Player Level Thresholds +################################################################################################ +#Max player level difference over the Dungeon's Level for Solocraft to apply the +# difficulty offset buff. +# Default: 10 +# Disable: 80 +Solocraft.Max.Level.Diff = 10 +################################################################################################ +#Dungeon Base Levels +################################################################################################ +#Catch All - Default 80 +Solocraft.Dungeon.Level = 80 +################################################################################################ +#Classic Instances - Base Level +################################################################################################ +Solocraft.ShadowfangKeep.Level = 15 +Solocraft.Stockades.Level = 22 +Solocraft.Deadmines.Level = 18 +Solocraft.WailingCaverns.Level = 17 +Solocraft.RazorfenKraulInstance.Level = 30 +Solocraft.Blackfathom.Level = 20 +Solocraft.Uldaman.Level = 40 +Solocraft.GnomeragonInstance.Level = 24 +Solocraft.SunkenTemple.Level = 50 +Solocraft.RazorfenDowns.Level = 40 +# Scarlet Monastery +Solocraft.MonasteryInstances.Level = 35 +# Zul'Farrak +Solocraft.TanarisInstance.Level = 44 +Solocraft.BlackRockSpire.Level = 55 +Solocraft.BlackrockDepths.Level = 50 +Solocraft.OnyxiaLairInstance.Level = 60 +#Scholomance +Solocraft.SchoolofNecromancy.Level = 55 +Solocraft.Zul'gurub.Level = 60 +Solocraft.Stratholme.Level = 55 +Solocraft.Mauradon.Level = 48 +# Ragefire Chasm +Solocraft.OrgrimmarInstance.Level = 15 +Solocraft.MoltenCore.Level = 60 +Solocraft.DireMaul.Level = 48 +Solocraft.BlackwingLair.Level = 40 +# Ruins of Ahn'Qiraj +Solocraft.AhnQiraj.Level = 60 +Solocraft.AhnQirajTemple.Level = 60 +################################################################################################ +#BC Instances - Base Level +################################################################################################ +# The Black Morass +Solocraft.CavernsOfTime.Level = 68 +Solocraft.Karazahn.Level = 68 +# The Battle for Mount Hyjal - Hyjal Summit +Solocraft.HyjalPast.Level = 70 +# The Shattered Halls +Solocraft.HellfireMilitary.Level = 68 +# The Blood Furnace +Solocraft.HellfireDemon.Level = 68 +Solocraft.HellfireRampart.Level = 68 +# Magtheridon's Lair +Solocraft.HellfireRaid.Level = 68 +# The Steamvault +Solocraft.CoilfangPumping.Level = 68 +# The Underbog +Solocraft.CoilfangMarsh.Level = 68 +# The Slavepens +Solocraft.CoilfangDraenei.Level = 68 +# Serpentshrine Cavern +Solocraft.CoilfangRaid.Level = 70 +# The Eye +Solocraft.TempestKeepRaid.Level = 70 +# The Arcatraz +Solocraft.TempestKeepArcane.Level = 68 +# The Botanica +Solocraft.TempestKeepAtrium.Level = 68 +# The Mechanar +Solocraft.TempestKeepFactory.Level = 68 +# Shadow Labyrinth +Solocraft.AuchindounShadow.Level = 68 +# Sethekk Halls +Solocraft.AuchindounDemon.Level = 68 +# Mana-Tombs +Solocraft.AuchindounEthereal.Level = 68 +# Auchenai Crypts +Solocraft.AuchindounDraenei.Level = 68 +# Old Hillsbrad Foothills +Solocraft.HillsbradPast.Level = 68 +Solocraft.BlackTemple.Level = 70 +Solocraft.GruulsLair.Level = 70 +Solocraft.ZulAman.Level = 68 +Solocraft.SunwellPlateau.Level = 70 +# Magister's Terrace +Solocraft.Sunwell5ManFix.Level = 68 +################################################################################################ +#WOTLK Instances - Base Level +################################################################################################ +# Naxxramas +Solocraft.StratholmeRaid.Level = 78 +# Utgarde Keep +Solocraft.Valgarde70.Level = 78 +Solocraft.UtgardePinnacle.Level = 78 +# The Nexus +Solocraft.Nexus70.Level = 78 +# The Occulus +Solocraft.Nexus80.Level = 78 +# The Culling of Stratholme +Solocraft.StratholmeCOT.Level = 78 +# Halls of Stone +Solocraft.Ulduar70.Level = 78 +# Drak'Tharon Keep +Solocraft.DrakTheronKeep.Level = 78 +# Azjol-Nerub +Solocraft.Azjol_Uppercity.Level = 78 +# Halls of Lighting +Solocraft.Ulduar80.Level = 78 +# Ulduar +Solocraft.UlduarRaid.Level = 80 +Solocraft.GunDrak.Level = 78 +# Violet Hold +Solocraft.DalaranPrison.Level = 78 +# The Obsidian Sanctum +Solocraft.ChamberOfAspectsBlack.Level = 80 +# The Eye of Eternity +Solocraft.NexusRaid.Level = 80 +# Ahn'kahet: The Old Kingdom +Solocraft.Azjol_LowerCity.Level = 78 +#Icecrown Citadel +Solocraft.IcecrownCitadel.Level = 80 +# The Forge of Souls +Solocraft.IcecrownCitadel5Man.Level = 78 +# Trial of the Champion +Solocraft.ArgentTournamentDungeon.Level = 80 +# Trial of the Crusader +Solocraft.ArgentTournamentRaid.Level = 80 +# Pit of Saron +Solocraft.QuarryOfTears.Level = 78 +# Halls of Reflection +Solocraft.HallsOfReflection.Level = 78 +# The Ruby Sanctum +Solocraft.ChamberOfAspectsRed.Level = 80 + ################################################################################################################### # NETWORK CONFIG # diff --git a/src/modules/Bots b/src/modules/Bots new file mode 160000 index 00000000000..a9fa1822212 --- /dev/null +++ b/src/modules/Bots @@ -0,0 +1 @@ +Subproject commit a9fa1822212634e758f3dda18721d8598da7bede diff --git a/src/modules/CMakeLists.txt b/src/modules/CMakeLists.txt new file mode 100644 index 00000000000..10efa42e94f --- /dev/null +++ b/src/modules/CMakeLists.txt @@ -0,0 +1,24 @@ +# MaNGOS is a full featured server for World of Warcraft, supporting +# the following clients: 1.12.x, 2.4.3, 3.3.5a, 4.3.4a and 5.4.8 +# +# Copyright (C) 2005-2018 MaNGOS project +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +# Include Player Bots if enabled +if(BUILD_IKE3_BOTS) + add_subdirectory(Bots) +endif() \ No newline at end of file diff --git a/src/shared/Database/DatabaseEnv.h b/src/shared/Database/DatabaseEnv.h index ea7b4658ef1..7581c282b2c 100644 --- a/src/shared/Database/DatabaseEnv.h +++ b/src/shared/Database/DatabaseEnv.h @@ -50,5 +50,6 @@ extern DatabaseType WorldDatabase; extern DatabaseType CharacterDatabase; extern DatabaseType LoginDatabase; extern DatabaseType LogsDatabase; +extern DatabaseType PlayerbotDatabase; #endif diff --git a/src/shared/Util/Util.h b/src/shared/Util/Util.h index 4a0fcf89d6f..80b43dd43cb 100644 --- a/src/shared/Util/Util.h +++ b/src/shared/Util/Util.h @@ -310,12 +310,12 @@ inline bool isEastAsianString(const std::wstring& wstr, bool numericOrSpace) inline void strToUpper(std::string& str) { - std::transform(str.begin(), str.end(), str.begin(), toupper); + std::transform(str.begin(), str.end(), str.begin(), ::toupper); } inline void strToLower(std::string& str) { - std::transform(str.begin(), str.end(), str.begin(), tolower); + std::transform(str.begin(), str.end(), str.begin(), ::tolower); } inline wchar_t wcharToUpper(wchar_t wchar)