HornedCommander.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604
  1. using System.Drawing;
  2. using Server.MirDatabase;
  3. using Server.MirEnvir;
  4. using S = ServerPackets;
  5. namespace Server.MirObjects.Monsters
  6. {
  7. /// <summary>
  8. /// Attack1 - Basic melee attack
  9. /// Attack2 - Basic melee attack alt
  10. /// Attack3 - Charge up then Spin attack (between 5 and 10 damage multiplier) twice
  11. /// Attack4 - Charge up then AOE rock fall (between 5 and 10 damage multiplier)
  12. /// Attack5 - Immunity shield, Summons slave (Under 10% HP)
  13. /// AttackRange1 - Teleport
  14. /// AttackRange2 - Hammer smash AOE in front
  15. /// AttackRange3 - Summon rock spikes (Between 10% - 30% HP)
  16. /// Summons Rock Slaves
  17. /// </summary>
  18. public class HornedCommander : MonsterObject
  19. {
  20. private bool _StartAdvanced;
  21. private bool _Immune;
  22. private Point _MapCentre
  23. {
  24. get
  25. {
  26. if (CurrentMap == null) return Point.Empty;
  27. //Should be spawned on "HY01_morae_chon". Change me to correct centre if its spawned on another map.
  28. return new Point(26, 32);
  29. }
  30. }
  31. //Phase 0
  32. private readonly byte _BoulderHealthPercent = 80;
  33. private bool _CalledBoulders, _StartedBoulderWalk;
  34. //Phase 1
  35. private readonly byte _RockHealthPercent = 50;
  36. private bool _CalledRockSpikes;
  37. private long _RockSpikeTime;
  38. private readonly Point[,] _RockSpikeArea = new Point[7, 7];
  39. private readonly List<SpellObject> _RockSpikeEffects = new List<SpellObject>();
  40. //Phase 2
  41. private readonly byte _ShieldHealthPercent = 10;
  42. private bool _CalledShield;
  43. private readonly byte _ShieldSeconds = 20;
  44. protected internal HornedCommander(MonsterInfo info)
  45. : base(info)
  46. {
  47. }
  48. public override bool IsAttackTarget(MonsterObject attacker)
  49. {
  50. return !_Immune && base.IsAttackTarget(attacker);
  51. }
  52. public override bool IsAttackTarget(HumanObject attacker)
  53. {
  54. return !_Immune && base.IsAttackTarget(attacker);
  55. }
  56. protected override void ProcessAI()
  57. {
  58. if ((HealthPercent < _BoulderHealthPercent || _StartedBoulderWalk) && !_CalledBoulders)
  59. {
  60. _StartedBoulderWalk = true;
  61. SpawnBoulder();
  62. }
  63. if (_Immune) return;
  64. if (HealthPercent < _ShieldHealthPercent && !_CalledShield)
  65. {
  66. KillRockSpikes();
  67. _CalledShield = true;
  68. _Immune = true;
  69. Broadcast(new S.ObjectAttack { ObjectID = ObjectID, Direction = Direction, Location = CurrentLocation, Type = 4, Level = _ShieldSeconds });
  70. AddBuff(BuffType.HornedCommanderShield, this, Settings.Second * _ShieldSeconds, new Stats());
  71. SpawnSlave();
  72. return;
  73. }
  74. if (HealthPercent < _RockHealthPercent && HealthPercent >= _ShieldHealthPercent)
  75. {
  76. if (!_CalledRockSpikes)
  77. {
  78. SetupRockSpike();
  79. _CalledRockSpikes = true;
  80. Broadcast(new S.ObjectRangeAttack { ObjectID = ObjectID, Direction = Direction, Location = CurrentLocation, Type = 2 });
  81. }
  82. }
  83. if (_CalledRockSpikes && Envir.Time > _RockSpikeTime)
  84. {
  85. var spawned = SpawnRockSpikes();
  86. _RockSpikeTime = Envir.Time + 5000;
  87. if (!spawned)
  88. {
  89. _RockSpikeTime = long.MaxValue;
  90. }
  91. }
  92. if (HealthPercent < 100 && !_StartAdvanced)
  93. {
  94. _StartAdvanced = true;
  95. }
  96. else if (HealthPercent == 100 && _StartAdvanced)
  97. {
  98. Reset();
  99. }
  100. base.ProcessAI();
  101. }
  102. private void Reset()
  103. {
  104. _StartAdvanced = false;
  105. _StartedBoulderWalk = false;
  106. _CalledBoulders = false;
  107. _CalledRockSpikes = false;
  108. _CalledShield = false;
  109. _RockSpikeTime = 0;
  110. KillRockSpikes();
  111. KillSlaves();
  112. }
  113. protected override void ProcessBuffEnd(Buff buff)
  114. {
  115. if (buff.Type == BuffType.HornedCommanderShield)
  116. {
  117. _Immune = false;
  118. AttackTime = Envir.Time + AttackSpeed;
  119. ActionTime = Envir.Time + 300;
  120. }
  121. }
  122. protected override void ProcessTarget()
  123. {
  124. if (Target == null || !CanAttack) return;
  125. if (InAttackRange())
  126. {
  127. Attack();
  128. if (Target != null && Target.Dead)
  129. {
  130. FindTarget();
  131. }
  132. return;
  133. }
  134. if (Envir.Time < ShockTime)
  135. {
  136. Target = null;
  137. return;
  138. }
  139. MoveTo(Target.CurrentLocation);
  140. }
  141. protected override void Attack()
  142. {
  143. if (!Target.IsAttackTarget(this))
  144. {
  145. Target = null;
  146. return;
  147. }
  148. AttackTime = Envir.Time + AttackSpeed;
  149. ShockTime = 0;
  150. Direction = Functions.DirectionFromPoint(CurrentLocation, Target.CurrentLocation);
  151. //Charge-Up AOE Rock Fall
  152. if (_StartAdvanced && Envir.Random.Next(20) == 0)
  153. {
  154. byte rockFallLoops = (byte)Envir.Random.Next(5, 10);
  155. int rockFallDuration = rockFallLoops * 500;
  156. _Immune = true;
  157. ActionTime = Envir.Time + (rockFallDuration) + 500;
  158. AttackTime = Envir.Time + (rockFallDuration) + 500 + AttackSpeed;
  159. Broadcast(new S.ObjectAttack { ObjectID = ObjectID, Direction = Direction, Location = CurrentLocation, Type = 3, Level = rockFallLoops });
  160. var front = Functions.PointMove(CurrentLocation, Direction, 2);
  161. MassSpawnRockFall(front, rockFallDuration);
  162. int damage = GetAttackPower(Stats[Stat.MinDC], Stats[Stat.MaxDC]) * rockFallLoops;
  163. DelayedAction action = new DelayedAction(DelayedType.RangeDamage, Envir.Time + (rockFallDuration) + 500, Target, damage, DefenceType.AC, 5);
  164. ActionList.Add(action);
  165. return;
  166. }
  167. //Charge-Up Spin Hit
  168. if (_StartAdvanced && Envir.Random.Next(15) == 0)
  169. {
  170. byte spinLoops = (byte)Envir.Random.Next(5, 10);
  171. int spinDuration = spinLoops * 700;
  172. _Immune = true;
  173. ActionTime = Envir.Time + spinDuration + 1500;
  174. AttackTime = Envir.Time + spinDuration + 1500 + AttackSpeed;
  175. Broadcast(new S.ObjectAttack { ObjectID = ObjectID, Direction = Direction, Location = CurrentLocation, Type = 2, Level = spinLoops });
  176. int damage = GetAttackPower(Stats[Stat.MinDC], Stats[Stat.MaxDC]) * spinLoops;
  177. DelayedAction action = new DelayedAction(DelayedType.Damage, Envir.Time + spinDuration + 500, Target, damage, DefenceType.AC, true);
  178. ActionList.Add(action);
  179. action = new DelayedAction(DelayedType.Damage, Envir.Time + spinDuration + 1000, Target, damage, DefenceType.AC, true);
  180. ActionList.Add(action);
  181. return;
  182. }
  183. //Hammer Smash
  184. if (_StartAdvanced && Envir.Random.Next(10) == 0)
  185. {
  186. Broadcast(new S.ObjectRangeAttack { ObjectID = ObjectID, Direction = Direction, Location = CurrentLocation, Type = 1 });
  187. int damage = GetAttackPower(Stats[Stat.MinDC], Stats[Stat.MaxDC]);
  188. if (damage == 0) return;
  189. DelayedAction action = new DelayedAction(DelayedType.RangeDamage, Envir.Time + 300, Target, damage, DefenceType.AC, 3);
  190. ActionList.Add(action);
  191. return;
  192. }
  193. //Teleport
  194. if (_StartAdvanced && Envir.Random.Next(10) == 0)
  195. {
  196. Broadcast(new S.ObjectRangeAttack { ObjectID = ObjectID, Direction = Direction, Location = CurrentLocation, Type = 0 });
  197. DelayedAction action = new DelayedAction(DelayedType.RangeDamage, Envir.Time + 300);
  198. ActionList.Add(action);
  199. return;
  200. }
  201. //Normal Attacks
  202. if (Envir.Random.Next(2) == 0)
  203. {
  204. Broadcast(new S.ObjectAttack { ObjectID = ObjectID, Direction = Direction, Location = CurrentLocation, Type = 0 });
  205. int damage = GetAttackPower(Stats[Stat.MinDC], Stats[Stat.MaxDC]);
  206. if (damage == 0) return;
  207. DelayedAction action = new DelayedAction(DelayedType.Damage, Envir.Time + 500, Target, damage, DefenceType.ACAgility, false);
  208. ActionList.Add(action);
  209. }
  210. else
  211. {
  212. Broadcast(new S.ObjectAttack { ObjectID = ObjectID, Direction = Direction, Location = CurrentLocation, Type = 1 });
  213. int damage = GetAttackPower(Stats[Stat.MinDC], Stats[Stat.MaxDC]);
  214. if (damage == 0) return;
  215. DelayedAction action = new DelayedAction(DelayedType.Damage, Envir.Time + 500, Target, damage, DefenceType.ACAgility, false);
  216. ActionList.Add(action);
  217. }
  218. }
  219. protected override void CompleteAttack(IList<object> data)
  220. {
  221. _Immune = false;
  222. if (data.Count > 0)
  223. {
  224. MapObject target = (MapObject)data[0];
  225. int damage = (int)data[1];
  226. DefenceType defence = (DefenceType)data[2];
  227. bool aoe = (bool)data[3];
  228. if (target == null || !target.IsAttackTarget(this) || target.CurrentMap != CurrentMap || target.Node == null) return;
  229. if (aoe)
  230. {
  231. var targets = FindAllTargets(3, CurrentLocation, false);
  232. for (int i = 0; i < targets.Count; i++)
  233. {
  234. targets[i].Attacked(this, damage, defence);
  235. }
  236. }
  237. else
  238. {
  239. target.Attacked(this, damage, defence);
  240. }
  241. }
  242. }
  243. protected override void CompleteRangeAttack(IList<object> data)
  244. {
  245. _Immune = false;
  246. if (data.Count > 0)
  247. {
  248. MapObject target = (MapObject)data[0];
  249. int damage = (int)data[1];
  250. DefenceType defence = (DefenceType)data[2];
  251. int aoeSize = (int)data[3];
  252. if (target == null || !target.IsAttackTarget(this) || target.CurrentMap != CurrentMap || target.Node == null) return;
  253. var front = Functions.PointMove(CurrentLocation, Direction, 2);
  254. var targets = FindAllTargets(aoeSize, front, false);
  255. for (int i = 0; i < targets.Count; i++)
  256. {
  257. targets[i].Attacked(this, damage, defence);
  258. }
  259. }
  260. else
  261. {
  262. TeleportRandom(10, 10);
  263. Target = null;
  264. }
  265. }
  266. public override bool TeleportRandom(int attempts, int distance, Map temp = null)
  267. {
  268. for (int i = 0; i < attempts; i++)
  269. {
  270. Point location;
  271. if (distance <= 0)
  272. location = new Point(Envir.Random.Next(CurrentMap.Width), Envir.Random.Next(CurrentMap.Height));
  273. else
  274. location = new Point(CurrentLocation.X + Envir.Random.Next(-distance, distance + 1),
  275. CurrentLocation.Y + Envir.Random.Next(-distance, distance + 1));
  276. if (Teleport(CurrentMap, location, true, 10)) return true;
  277. }
  278. return false;
  279. }
  280. private void MassSpawnRockFall(Point centerPoint, int duration)
  281. {
  282. int range = 15;
  283. for (int x = -range; x < range; x+=10)
  284. {
  285. for (int y = -range; y < range; y += 10)
  286. {
  287. var location = new Point(centerPoint.X + x, centerPoint.Y + y);
  288. SpawnRockFall(location, Envir.Random.Next(0, 200), duration);
  289. }
  290. }
  291. }
  292. private void SpawnRockFall(Point location, int offset, int duration)
  293. {
  294. if (Dead) return;
  295. for (int y = location.Y - 10; y <= location.Y + 10; y++)
  296. {
  297. if (y < 0) continue;
  298. if (y >= CurrentMap.Height) break;
  299. for (int x = location.X - 10; x <= location.X + 10; x++)
  300. {
  301. if (x < 0) continue;
  302. if (x >= CurrentMap.Width) break;
  303. if (x == CurrentLocation.X && y == CurrentLocation.Y) continue;
  304. var cell = CurrentMap.GetCell(x, y);
  305. if (!cell.Valid) continue;
  306. int damage = GetAttackPower(Stats[Stat.MinMC], Stats[Stat.MaxMC]);
  307. var start = 500 + offset;
  308. SpellObject ob = new SpellObject
  309. {
  310. Spell = Spell.HornedCommanderRockFall,
  311. Value = damage,
  312. ExpireTime = Envir.Time + duration + start,
  313. TickSpeed = 2000,
  314. CurrentLocation = new Point(x, y),
  315. CastLocation = location,
  316. Show = location.X == x && location.Y == y,
  317. CurrentMap = CurrentMap,
  318. Caster = this
  319. };
  320. DelayedAction action = new DelayedAction(DelayedType.Spawn, Envir.Time + start, ob);
  321. CurrentMap.ActionList.Add(action);
  322. }
  323. }
  324. }
  325. private void SetupRockSpike()
  326. {
  327. var xLength = _RockSpikeArea.GetLength(0);
  328. var yLength = _RockSpikeArea.GetLength(1);
  329. var midX = ((int)Math.Ceiling((decimal)xLength / 2) - 1) * -1;
  330. var midY = ((int)Math.Ceiling((decimal)yLength / 2) - 1) * -1;
  331. var actualX = midX;
  332. var actualY = midY;
  333. for (int x = 0; x < xLength; x++)
  334. {
  335. for (int y = 0; y < yLength; y++)
  336. {
  337. var point = new Point(_MapCentre.X + (actualX * 5), _MapCentre.Y + (actualY * 5));
  338. _RockSpikeArea[x, y] = point;
  339. actualY++;
  340. }
  341. actualY = midY;
  342. actualX++;
  343. }
  344. }
  345. private bool SpawnRockSpikes()
  346. {
  347. var spawned = false;
  348. var xLength = _RockSpikeArea.GetLength(0);
  349. var yLength = _RockSpikeArea.GetLength(1);
  350. for (int x = 0; x < xLength; x++)
  351. {
  352. for (int y = 0; y < yLength; y++)
  353. {
  354. var point = _RockSpikeArea[x, y];
  355. var existing = _RockSpikeEffects.Any(x => x.CastLocation == point);
  356. if (existing) continue;
  357. spawned = SpawnRockSpike(point);
  358. if (spawned)
  359. {
  360. return true;
  361. }
  362. }
  363. if (spawned)
  364. {
  365. return true;
  366. }
  367. }
  368. return false;
  369. }
  370. private bool SpawnRockSpike(Point location)
  371. {
  372. var spawned = false;
  373. for (int y = location.Y - 2; y <= location.Y + 2; y++)
  374. {
  375. if (y < 0) continue;
  376. if (y >= CurrentMap.Height) break;
  377. for (int x = location.X - 2; x <= location.X + 2; x++)
  378. {
  379. if (x < 0) continue;
  380. if (x >= CurrentMap.Width) break;
  381. var cell = CurrentMap.GetCell(x, y);
  382. if (!cell.Valid) continue;
  383. if (location.X == x && location.Y == y)
  384. {
  385. spawned = true;
  386. }
  387. int damage = GetAttackPower(Stats[Stat.MinMC], Stats[Stat.MaxMC]);
  388. var start = 500;
  389. SpellObject ob = new SpellObject
  390. {
  391. Spell = Spell.HornedCommanderRockSpike,
  392. Value = damage,
  393. ExpireTime = Envir.Time + start + (Settings.Minute * 10),
  394. TickSpeed = 1000,
  395. CurrentLocation = new Point(x, y),
  396. CastLocation = location,
  397. Show = location.X == x && location.Y == y,
  398. CurrentMap = CurrentMap,
  399. Caster = this
  400. };
  401. DelayedAction action = new DelayedAction(DelayedType.Spawn, Envir.Time + start, ob);
  402. CurrentMap.ActionList.Add(action);
  403. _RockSpikeEffects.Add(ob);
  404. }
  405. }
  406. return spawned;
  407. }
  408. private void SpawnBoulder()
  409. {
  410. if (Functions.InRange(CurrentLocation, _MapCentre, 20))
  411. {
  412. Teleport(CurrentMap, _MapCentre, true, 10);
  413. }
  414. _CalledBoulders = true;
  415. for (int i = 0; i < 8; i++)
  416. {
  417. var mob = GetMonster(Envir.GetMonsterInfo(Settings.HornedCommanderBombMob));
  418. var odd = i % 2 != 0;
  419. var point = Functions.PointMove(CurrentLocation, (MirDirection)i, odd ? 7 : 9);
  420. if (mob == null) continue;
  421. mob.Direction = Functions.DirectionFromPoint(point, CurrentLocation);
  422. if (mob.Spawn(CurrentMap, point))
  423. {
  424. mob.Target = Target;
  425. mob.ActionTime = Envir.Time;
  426. SlaveList.Add(mob);
  427. }
  428. }
  429. }
  430. private void SpawnSlave()
  431. {
  432. ActionTime = Envir.Time + 300;
  433. AttackTime = Envir.Time + AttackSpeed;
  434. var mob = GetMonster(Envir.GetMonsterInfo(Settings.HornedCommanderMob));
  435. if (mob == null) return;
  436. if (!mob.Spawn(CurrentMap, Front))
  437. mob.Spawn(CurrentMap, CurrentLocation);
  438. mob.Target = Target;
  439. mob.ActionTime = Envir.Time;
  440. SlaveList.Add(mob);
  441. }
  442. private void KillRockSpikes()
  443. {
  444. _RockSpikeTime = long.MaxValue;
  445. foreach (var effect in _RockSpikeEffects)
  446. {
  447. effect.ExpireTime = Envir.Time;
  448. }
  449. _RockSpikeEffects.Clear();
  450. }
  451. private void KillSlaves()
  452. {
  453. //Kill Minions
  454. for (int i = SlaveList.Count - 1; i >= 0; i--)
  455. {
  456. if (!SlaveList[i].Dead && SlaveList[i].Node != null)
  457. {
  458. SlaveList[i].Die();
  459. }
  460. }
  461. }
  462. public override void Die()
  463. {
  464. base.Die();
  465. KillRockSpikes();
  466. KillSlaves();
  467. }
  468. }
  469. }