Advertisement
Guest User

vas

a guest
Jun 6th, 2017
223
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 19.27 KB | None | 0 0
  1. /*
  2. * Copyright (C) 2012 CVMagic <http://www.trinitycore.org/f/topic/6551-vas-autobalance/>
  3. * Copyright (C) 2008-2010 TrinityCore <http://www.trinitycore.org/>
  4. * Copyright (C) 2006-2009 ScriptDev2 <https://scriptdev2.svn.sourceforge.net/>
  5. * Copyright (C) 1985-2010 {VAS} KalCorp <http://vasserver.dyndns.org/>
  6. *
  7. * This program is free software; you can redistribute it and/or modify it
  8. * under the terms of the GNU General Public License as published by the
  9. * Free Software Foundation; either version 2 of the License, or (at your
  10. * option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful, but WITHOUT
  13. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  14. * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
  15. * more details.
  16. *
  17. * You should have received a copy of the GNU General Public License along
  18. * with this program. If not, see <http://www.gnu.org/licenses/>.
  19. */
  20.  
  21. /*
  22. * Script Name: AutoBalance
  23. * Original Authors: KalCorp and Vaughner
  24. * Maintainer(s): CVMagic
  25. * Original Script Name: VAS.AutoBalance
  26. * Description: This script is intended to scale based on number of players, instance mobs & world bosses' health, mana, and damage.
  27. */
  28.  
  29.  
  30. #include "Configuration/Config.h"
  31. #include "Unit.h"
  32. #include "Chat.h"
  33. #include "Creature.h"
  34. #include "Player.h"
  35. #include "ObjectMgr.h"
  36. #include "MapManager.h"
  37. #include "World.h"
  38. #include "Map.h"
  39. #include "ScriptMgr.h"
  40. #include <vector>
  41.  
  42.  
  43. struct AutoBalanceCreatureInfo
  44. {
  45. uint32 instancePlayerCount;
  46. float DamageMultiplier;
  47. };
  48.  
  49. static std::map<uint32, AutoBalanceCreatureInfo> CreatureInfo; // A hook should be added to remove the mapped entry when the creature is dead or this should be added into the creature object
  50. static std::map<int, int> forcedCreatureIds; // The map values correspond with the VAS.AutoBalance.XX.Name entries in the configuration file.
  51. static int8 PlayerCountDifficultyOffset; //cheaphack for difficulty server-wide. Another value TODO in player class for the party leader's value to determine dungeon difficulty.
  52. int GetValidDebugLevel()
  53. {
  54. int debugLevel = sConfigMgr->GetIntDefault("VASAutoBalance.DebugLevel", 2);
  55.  
  56. if ((debugLevel < 0) || (debugLevel > 3))
  57. {
  58. return 1;
  59. }
  60. return debugLevel;
  61. }
  62.  
  63. void LoadForcedCreatureIdsFromString(std::string creatureIds, int forcedPlayerCount) // Used for reading the string from the configuration file to for those creatures who need to be scaled for XX number of players.
  64. {
  65. std::string delimitedValue;
  66. std::stringstream creatureIdsStream;
  67.  
  68. creatureIdsStream.str(creatureIds);
  69. while (std::getline(creatureIdsStream, delimitedValue, ',')) // Process each Creature ID in the string, delimited by the comma - ","
  70. {
  71. int creatureId = atoi(delimitedValue.c_str());
  72. if (creatureId >= 0)
  73. {
  74. forcedCreatureIds[creatureId] = forcedPlayerCount;
  75. }
  76. }
  77. }
  78.  
  79. int GetForcedCreatureId(int creatureId)
  80. {
  81. if (forcedCreatureIds.find(creatureId) == forcedCreatureIds.end()) // Don't want the forcedCreatureIds map to blowup to a massive empty array
  82. {
  83. return 0;
  84. }
  85. return forcedCreatureIds[creatureId];
  86. }
  87.  
  88. class VAS_AutoBalance_WorldScript : public WorldScript
  89. {
  90. public:
  91. VAS_AutoBalance_WorldScript()
  92. : WorldScript("VAS_AutoBalance_WorldScript")
  93. {
  94. }
  95.  
  96. void OnBeforeConfigLoad(bool reload) override
  97. {
  98. /* from skeleton module */
  99. if (!reload) {
  100. std::string conf_path = _CONF_DIR;
  101. std::string cfg_file = conf_path+"/VASAutoBalance.conf";
  102. #ifdef WIN32
  103. cfg_file = "VASAutoBalance.conf";
  104. #endif
  105. std::string cfg_def_file = cfg_file + ".dist";
  106. sConfigMgr->LoadMore(cfg_def_file.c_str());
  107.  
  108. sConfigMgr->LoadMore(cfg_file.c_str());
  109. }
  110. /* end from skeleton module */
  111. }
  112. void OnStartup()
  113. {
  114. }
  115.  
  116. void SetInitialWorldSettings()
  117. {
  118. forcedCreatureIds.clear();
  119. LoadForcedCreatureIdsFromString(sConfigMgr->GetStringDefault("VASAutoBalance.ForcedID40", ""), 40);
  120. LoadForcedCreatureIdsFromString(sConfigMgr->GetStringDefault("VASAutoBalance.ForcedID25", ""), 25);
  121. LoadForcedCreatureIdsFromString(sConfigMgr->GetStringDefault("VASAutoBalance.ForcedID10", ""), 10);
  122. LoadForcedCreatureIdsFromString(sConfigMgr->GetStringDefault("VASAutoBalance.ForcedID5", ""), 5);
  123. LoadForcedCreatureIdsFromString(sConfigMgr->GetStringDefault("VASAutoBalance.ForcedID2", ""), 2);
  124. PlayerCountDifficultyOffset = 0;
  125. }
  126. };
  127.  
  128. class VAS_AutoBalance_PlayerScript : public PlayerScript
  129. {
  130. public:
  131. VAS_AutoBalance_PlayerScript()
  132. : PlayerScript("VAS_AutoBalance_PlayerScript")
  133. {
  134. }
  135.  
  136. void OnLogin(Player *Player)
  137. {
  138. ChatHandler(Player->GetSession()).PSendSysMessage("This server is running a VAS_AutoBalance Module.");
  139. }
  140. };
  141.  
  142. class VAS_AutoBalance_UnitScript : public UnitScript
  143. {
  144. public:
  145. VAS_AutoBalance_UnitScript()
  146. : UnitScript("VAS_AutoBalance_UnitScript", true)
  147. {
  148. }
  149.  
  150. uint32 DealDamage(Unit* AttackerUnit, Unit *playerVictim, uint32 damage, DamageEffectType damagetype)
  151. {
  152. if ((AttackerUnit->GetMap()->IsDungeon() && playerVictim->GetMap()->IsDungeon()) || sConfigMgr->GetIntDefault("VASAutoBalance.DungeonsOnly", 1) < 1)
  153. if (AttackerUnit->GetTypeId() != TYPEID_PLAYER)
  154. {
  155. damage = VAS_Modifer_DealDamage(AttackerUnit, damage);
  156. }
  157. return damage;
  158. }
  159.  
  160. uint32 HandlePeriodicDamageAurasTick(Unit *target, Unit *caster, int32 damage)
  161. {
  162. if ((caster->GetMap()->IsDungeon() && target->GetMap()->IsDungeon()) || sConfigMgr->GetIntDefault("VASAutoBalance.DungeonsOnly", 1) < 1)
  163. if (caster->GetTypeId() != TYPEID_PLAYER)
  164. {
  165. if (!((caster->IsHunterPet() || caster->IsPet() || caster->IsSummon()) && caster->IsControlledByPlayer()))
  166. damage = (float)damage * (float)CreatureInfo[caster->GetGUID()].DamageMultiplier;
  167. }
  168. return damage;
  169. }
  170.  
  171. void CalculateSpellDamageTaken(SpellNonMeleeDamage *damageInfo, int32 damage, SpellInfo const *spellInfo, WeaponAttackType attackType, bool crit)
  172. {
  173. if (sConfigMgr->GetIntDefault("VASAutoBalance.DungeonsOnly", 1) < 1 || (damageInfo->attacker->GetMap()->IsDungeon() && damageInfo->target->GetMap()->IsDungeon()) || (damageInfo->attacker->GetMap()->IsBattleground() && damageInfo->target->GetMap()->IsBattleground()))
  174. {
  175. if (damageInfo->attacker->GetTypeId() != TYPEID_PLAYER)
  176. {
  177. if ((damageInfo->attacker->IsHunterPet() || damageInfo->attacker->IsPet() || damageInfo->attacker->IsSummon()) && damageInfo->attacker->IsControlledByPlayer())
  178. return;
  179. damageInfo->damage = (float)damageInfo->damage * (float)CreatureInfo[damageInfo->attacker->GetGUID()].DamageMultiplier;
  180. }
  181. }
  182. return;
  183. }
  184.  
  185. void CalculateMeleeDamage(Unit *playerVictim, uint32 damage, CalcDamageInfo *damageInfo, WeaponAttackType attackType)
  186. {
  187. // Make sure the Attacker and the Victim are in the same location, in addition that the attacker is not player.
  188. if ((sConfigMgr->GetIntDefault("VASAutoBalance.DungeonsOnly", 1) < 1 || (damageInfo->attacker->GetMap()->IsDungeon() && damageInfo->target->GetMap()->IsDungeon()) || (damageInfo->attacker->GetMap()->IsBattleground() && damageInfo->target->GetMap()->IsBattleground())) && (damageInfo->attacker->GetTypeId() != TYPEID_PLAYER))
  189. if (!((damageInfo->attacker->IsHunterPet() || damageInfo->attacker->IsPet() || damageInfo->attacker->IsSummon()) && damageInfo->attacker->IsControlledByPlayer())) // Make sure that the attacker Is not a Pet of some sort
  190. {
  191. damageInfo->damage = (float)damageInfo->damage * (float)CreatureInfo[damageInfo->attacker->GetGUID()].DamageMultiplier;
  192. }
  193. return;
  194. }
  195.  
  196. uint32 VAS_Modifer_DealDamage(Unit* AttackerUnit, uint32 damage)
  197. {
  198. if ((AttackerUnit->IsHunterPet() || AttackerUnit->IsPet() || AttackerUnit->IsSummon()) && AttackerUnit->IsControlledByPlayer())
  199. return damage;
  200.  
  201. float damageMultiplier = CreatureInfo[AttackerUnit->GetGUID()].DamageMultiplier;
  202.  
  203. return damage * damageMultiplier;
  204.  
  205. }
  206. };
  207.  
  208.  
  209. class VAS_AutoBalance_AllMapScript : public AllMapScript
  210. {
  211. public:
  212. VAS_AutoBalance_AllMapScript()
  213. : AllMapScript("VAS_AutoBalance_AllMapScript")
  214. {
  215. }
  216.  
  217. void OnPlayerEnterAll(Map* map, Player* player)
  218. {
  219. if (sConfigMgr->GetIntDefault("VASAutoBalance.PlayerChangeNotify", 1) > 0)
  220. {
  221. if ((map->GetEntry()->IsDungeon()) && !player->IsGameMaster())
  222. {
  223. Map::PlayerList const &playerList = map->GetPlayers();
  224. if (!playerList.isEmpty())
  225. {
  226. for (Map::PlayerList::const_iterator playerIteration = playerList.begin(); playerIteration != playerList.end(); ++playerIteration)
  227. {
  228. if (Player* playerHandle = playerIteration->GetSource())
  229. {
  230. ChatHandler chatHandle = ChatHandler(playerHandle->GetSession());
  231. chatHandle.PSendSysMessage("|cffFF0000 [AutoBalance]|r|cffFF8000 %s entered the Instance %s. Auto setting player count to %u (Player Difficulty Offset = %u) |r", player->GetName().c_str(), map->GetMapName(), map->GetPlayersCountExceptGMs() + PlayerCountDifficultyOffset, PlayerCountDifficultyOffset);
  232. }
  233. }
  234. }
  235. }
  236. }
  237. }
  238.  
  239. void OnPlayerLeaveAll(Map* map, Player* player)
  240. {
  241.  
  242. int instancePlayerCount = map->GetPlayersCountExceptGMs() - 1;
  243.  
  244. if (instancePlayerCount >= 1)
  245. {
  246. if (sConfigMgr->GetIntDefault("VASAutoBalance.PlayerChangeNotify", 1) > 0)
  247. {
  248. if ((map->GetEntry()->IsDungeon()) && !player->IsGameMaster())
  249. {
  250. Map::PlayerList const &playerList = map->GetPlayers();
  251. if (!playerList.isEmpty())
  252. {
  253. for (Map::PlayerList::const_iterator playerIteration = playerList.begin(); playerIteration != playerList.end(); ++playerIteration)
  254. {
  255. if (Player* playerHandle = playerIteration->GetSource())
  256. {
  257. ChatHandler chatHandle = ChatHandler(playerHandle->GetSession());
  258. chatHandle.PSendSysMessage("|cffFF0000 [VAS-AutoBalance]|r|cffFF8000 %s left the Instance %s. Auto setting player count to %u (Player Difficulty Offset = %u) |r", player->GetName().c_str(), map->GetMapName(), instancePlayerCount, PlayerCountDifficultyOffset);
  259. }
  260. }
  261. }
  262. }
  263. }
  264. }
  265. }
  266. };
  267.  
  268.  
  269. class VAS_AutoBalance_AllCreatureScript : public AllCreatureScript
  270. {
  271. public:
  272. VAS_AutoBalance_AllCreatureScript()
  273. : AllCreatureScript("VAS_AutoBalance_AllCreatureScript")
  274. {
  275. }
  276.  
  277.  
  278. void Creature_SelectLevel(const CreatureTemplate *creatureTemplate, Creature* creature)
  279. {
  280.  
  281. if (creature->GetMap()->IsDungeon() || sConfigMgr->GetIntDefault("VASAutoBalance.DungeonsOnly", 1) < 1)
  282. {
  283. ModifyCreatureAttributes(creature);
  284. CreatureInfo[creature->GetGUID()].instancePlayerCount = creature->GetMap()->GetPlayersCountExceptGMs() + PlayerCountDifficultyOffset;
  285. }
  286. }
  287.  
  288. void OnAllCreatureUpdate(Creature* creature, uint32 diff)
  289. {
  290. if (!(CreatureInfo[creature->GetGUID()].instancePlayerCount == (creature->GetMap()->GetPlayersCountExceptGMs() + PlayerCountDifficultyOffset)))
  291. {
  292. if (creature->GetMap()->IsDungeon() || creature->GetMap()->IsBattleground() || sConfigMgr->GetIntDefault("VASAutoBalance.DungeonsOnly", 1) < 1)
  293. ModifyCreatureAttributes(creature);
  294. CreatureInfo[creature->GetGUID()].instancePlayerCount = creature->GetMap()->GetPlayersCountExceptGMs() + PlayerCountDifficultyOffset;
  295. }
  296. }
  297.  
  298. void ModifyCreatureAttributes(Creature* creature)
  299. {
  300. if (((creature->IsHunterPet() || creature->IsPet() || creature->IsSummon()) && creature->IsControlledByPlayer()) || (creature->GetMap()->IsDungeon() && sConfigMgr->GetIntDefault("VASAutoBalance.Instances", 1) < 1) || creature->GetMap()->GetPlayersCountExceptGMs() <= 0)
  301. {
  302. return;
  303. }
  304.  
  305. CreatureTemplate const *creatureTemplate = creature->GetCreatureTemplate();
  306. CreatureBaseStats const* creatureStats = sObjectMgr->GetCreatureBaseStats(creature->getLevel(), creatureTemplate->unit_class);
  307.  
  308. float damageMultiplier = 1.0f;
  309. float healthMultiplier = 1.0f;
  310.  
  311. uint32 baseHealth = creatureStats->GenerateHealth(creatureTemplate);
  312. uint32 baseMana = creatureStats->GenerateMana(creatureTemplate);
  313. uint32 instancePlayerCount = creature->GetMap()->GetPlayersCountExceptGMs() + PlayerCountDifficultyOffset;
  314. uint32 maxNumberOfPlayers = ((InstanceMap*)sMapMgr->FindMap(creature->GetMapId(), creature->GetInstanceId()))->GetMaxPlayers();
  315. uint32 scaledHealth = 0;
  316. uint32 scaledMana = 0;
  317.  
  318. // VAS SOLO - By MobID
  319. if (GetForcedCreatureId(creatureTemplate->Entry) > 0)
  320. {
  321. maxNumberOfPlayers = GetForcedCreatureId(creatureTemplate->Entry); // Force maxNumberOfPlayers to be changed to match the Configuration entry.
  322. }
  323.  
  324. // (tanh((X-2.2)/1.5) +1 )/2 // 5 Man formula X = Number of Players
  325. // (tanh((X-5)/2) +1 )/2 // 10 Man Formula X = Number of Players
  326. // (tanh((X-16.5)/6.5) +1 )/2 // 25 Man Formula X = Number of players
  327. //
  328. // Note: The 2.2, 5, and 16.5 are the number of players required to get 50% health.
  329. // It's not required this be a whole number, you'd adjust this to raise or lower
  330. // the hp modifier for per additional player in a non-whole group. These
  331. // values will eventually be part of the configuration file once I finalize the mod.
  332. //
  333. // The 1.5, 2, and 6.5 modify the rate of percentage increase between
  334. // number of players. Generally the closer to the value of 1 you have this
  335. // the less gradual the rate will be. For example in a 5 man it would take 3
  336. // total players to face a mob at full health.
  337. //
  338. // The +1 and /2 values raise the TanH function to a positive range and make
  339. // sure the modifier never goes above the value or 1.0 or below 0.
  340. //
  341. // Lastly this formula has one side effect on full groups Bosses and mobs will
  342. // never have full health, this can be tested against by making sure the number
  343. // of players match the maxNumberOfPlayers variable.
  344.  
  345. switch (maxNumberOfPlayers)
  346. {
  347. case 40:
  348. healthMultiplier = (float)instancePlayerCount / (float)maxNumberOfPlayers; // 40 Man Instances oddly enough scale better with the old formula
  349. break;
  350. case 25:
  351. healthMultiplier = (tanh((instancePlayerCount - 16.5f) / 1.5f) + 1.0f) / 2.0f;
  352. break;
  353. case 10:
  354. healthMultiplier = (tanh((instancePlayerCount - 4.5f) / 1.5f) + 1.0f) / 2.0f;
  355. break;
  356. case 2:
  357. healthMultiplier = (float)instancePlayerCount / (float)maxNumberOfPlayers; // Two Man Creatures are too easy if handled by the 5 man formula, this would only
  358. break; // apply in the situation where it's specified in the configuration file.
  359. default:
  360. healthMultiplier = (tanh((instancePlayerCount - 2.2f) / 1.5f) + 1.0f) / 2.0f; // default to a 5 man group
  361. }
  362.  
  363. // VAS SOLO - Map 0,1 and 530 ( World Mobs ) // This may be where VAS_AutoBalance_CheckINIMaps might have come into play. None the less this is
  364. if ((creature->GetMapId() == 0 || creature->GetMapId() == 1 || creature->GetMapId() == 530) && (creature->isElite() || creature->isWorldBoss())) // specific to World Bosses and elites in those Maps, this is going to use the entry XPlayer in place of instancePlayerCount.
  365. {
  366. if (baseHealth > 800000) {
  367. healthMultiplier = (tanh((sConfigMgr->GetFloatDefault("VASAutoBalance.numPlayer", 1.0f) - 5.0f) / 1.5f) + 1.0f) / 2.0f;
  368.  
  369. }
  370. else {
  371. healthMultiplier = (tanh((sConfigMgr->GetFloatDefault("VASAutoBalance.numPlayer", 1.0f) - 2.2f) / 1.5f) + 1.0f) / 2.0f; // Assuming a 5 man configuration, as World Bosses have been relatively retired since BC so unless the boss has some substantial baseHealth
  372. }
  373.  
  374. }
  375.  
  376. // Ensure that the healthMultiplier is not lower than the configuration specified value. -- This may be Deprecated later.
  377. if (healthMultiplier <= sConfigMgr->GetFloatDefault("VASAutoBalance.MinHPModifier", 0.1f))
  378. {
  379. healthMultiplier = sConfigMgr->GetFloatDefault("VASAutoBalance.MinHPModifier", 0.1f);
  380. }
  381.  
  382. //Getting the list of Classes in this group - this will be used later on to determine what additional scaling will be required based on the ratio of tank/dps/healer
  383. //GetPlayerClassList(creature, playerClassList); // Update playerClassList with the list of all the participating Classes
  384.  
  385.  
  386. scaledHealth = uint32((baseHealth * healthMultiplier) + 1.0f);
  387. // Now adjusting Mana, Mana is something that can be scaled linearly
  388. if (maxNumberOfPlayers == 0) {
  389. scaledMana = uint32((baseMana * healthMultiplier) + 1.0f);
  390. // Now Adjusting Damage, this too is linear for now .... this will have to change I suspect.
  391. damageMultiplier = healthMultiplier;
  392. }
  393. else {
  394. scaledMana = ((baseMana / maxNumberOfPlayers) * instancePlayerCount);
  395. // Now Adjusting Damage, this too is linear for now .... this will have to change I suspect.
  396. damageMultiplier = (float)instancePlayerCount / (float)maxNumberOfPlayers;
  397.  
  398. }
  399.  
  400. // Can not be less then Min_D_Mod
  401. if (damageMultiplier <= sConfigMgr->GetFloatDefault("VASAutoBalance.MinDamageModifier", 0.1f))
  402. {
  403. damageMultiplier = sConfigMgr->GetFloatDefault("VASAutoBalance.MinDamageModifier", 0.1f);
  404. }
  405.  
  406. creature->SetCreateHealth(scaledHealth);
  407. creature->SetMaxHealth(scaledHealth);
  408. creature->ResetPlayerDamageReq();
  409. creature->SetCreateMana(scaledMana);
  410. creature->SetMaxPower(POWER_MANA, scaledMana);
  411. creature->SetPower(POWER_MANA, scaledMana);
  412. creature->SetModifierValue(UNIT_MOD_HEALTH, BASE_VALUE, (float)scaledHealth);
  413. creature->SetModifierValue(UNIT_MOD_MANA, BASE_VALUE, (float)scaledMana);
  414. CreatureInfo[creature->GetGUID()].DamageMultiplier = damageMultiplier;
  415. }
  416. };
  417. class VAS_AutoBalance_CommandScript : public CommandScript
  418. {
  419. public:
  420. VAS_AutoBalance_CommandScript() : CommandScript("VAS_AutoBalance_CommandScript") { }
  421.  
  422. std::vector<ChatCommand> GetCommands() const
  423. {
  424. static std::vector<ChatCommand> vasCommandTable =
  425. {
  426. { "setoffset", SEC_GAMEMASTER, true, &HandleVasSetOffsetCommand, "" },
  427. { "getoffset", SEC_GAMEMASTER, true, &HandleVasGetOffsetCommand, "" },
  428. };
  429.  
  430. static std::vector<ChatCommand> commandTable =
  431. {
  432. { "vas", SEC_GAMEMASTER, false, NULL, "", vasCommandTable },
  433. };
  434. return commandTable;
  435. }
  436.  
  437. static bool HandleVasSetOffsetCommand(ChatHandler* handler, const char* args)
  438. {
  439. if (!*args)
  440. {
  441. handler->PSendSysMessage(".vas setoffset #");
  442. handler->PSendSysMessage("Sets the Player Difficulty Offset for instances. Example: (You + offset(1) = 2 player difficulty).");
  443. return false;
  444. }
  445. char* offset = strtok((char*)args, " ");
  446. int32 offseti = -1;
  447.  
  448. if (offset)
  449. {
  450. offseti = (uint32)atoi(offset);
  451. handler->PSendSysMessage("Changing Player Difficulty Offset to %i.", offseti);
  452. PlayerCountDifficultyOffset = offseti;
  453. return true;
  454. }
  455. else
  456. handler->PSendSysMessage("Error changing Player Difficulty Offset! Please try again.");
  457. return false;
  458. }
  459. static bool HandleVasGetOffsetCommand(ChatHandler* handler, const char* /*args*/)
  460. {
  461. handler->PSendSysMessage("Current Player Difficulty Offset = %i", PlayerCountDifficultyOffset);
  462. return true;
  463. }
  464. };
  465. void AddVASAutoBalanceScripts()
  466. {
  467. new VAS_AutoBalance_WorldScript;
  468. new VAS_AutoBalance_PlayerScript;
  469. new VAS_AutoBalance_UnitScript;
  470. new VAS_AutoBalance_AllCreatureScript;
  471. new VAS_AutoBalance_AllMapScript;
  472. new VAS_AutoBalance_CommandScript;
  473. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement