530 lines
No EOL
18 KiB
C#
530 lines
No EOL
18 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.Extensions.Hosting;
|
|
using PhoenixLib.Events;
|
|
using PhoenixLib.Logging;
|
|
using Plugin.Act4.Const;
|
|
using Plugin.Act4.Extension;
|
|
using WingsEmu.Game.Act4;
|
|
using WingsEmu.Game.Act4.Entities;
|
|
using WingsEmu.Game.Act4.Event;
|
|
using WingsEmu.Game.Battle;
|
|
using WingsEmu.Game.Buffs;
|
|
using WingsEmu.Game.Characters;
|
|
using WingsEmu.Game.Characters.Events;
|
|
using WingsEmu.Game.Configurations;
|
|
using WingsEmu.Game.Entities;
|
|
using WingsEmu.Game.Entities.Event;
|
|
using WingsEmu.Game.Extensions;
|
|
using WingsEmu.Game.Extensions.Mates;
|
|
using WingsEmu.Game.Helpers.Damages;
|
|
using WingsEmu.Game.Maps.Event;
|
|
using WingsEmu.Game.Mates;
|
|
using WingsEmu.Game.Mates.Events;
|
|
using WingsEmu.Game.Monster.Event;
|
|
|
|
namespace Plugin.Act4.RecurrentJob;
|
|
|
|
public class Act4DungeonSystem : BackgroundService
|
|
{
|
|
private static readonly TimeSpan Interval = TimeSpan.FromSeconds(1);
|
|
private readonly IAct4DungeonManager _act4DungeonManager;
|
|
private readonly IAsyncEventPipeline _asyncEventPipeline;
|
|
private readonly IBuffFactory _buffFactory;
|
|
private readonly IDungeonManager _dungeonManager;
|
|
private readonly GameRevivalConfiguration _gameRevivalConfiguration;
|
|
|
|
public Act4DungeonSystem(IAct4DungeonManager act4dungeonManager, IAsyncEventPipeline asyncEventPipeline,
|
|
IDungeonManager dungeonManager, GameRevivalConfiguration gameRevivalConfiguration, IBuffFactory buffFactory)
|
|
{
|
|
_act4DungeonManager = act4dungeonManager;
|
|
_asyncEventPipeline = asyncEventPipeline;
|
|
_dungeonManager = dungeonManager;
|
|
_gameRevivalConfiguration = gameRevivalConfiguration;
|
|
_buffFactory = buffFactory;
|
|
}
|
|
|
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
|
{
|
|
Log.Info("[ACT4_DUNGEON_SYSTEM] Started!");
|
|
while (!stoppingToken.IsCancellationRequested)
|
|
{
|
|
try
|
|
{
|
|
await Process(stoppingToken);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Log.Error("[ACT4_DUNGEON_SYSTEM]", e);
|
|
}
|
|
|
|
await Task.Delay(Interval, stoppingToken);
|
|
}
|
|
}
|
|
|
|
private async Task Process(CancellationToken stoppingToken)
|
|
{
|
|
if (!_act4DungeonManager.DungeonsActive)
|
|
{
|
|
return;
|
|
}
|
|
|
|
DateTime currentTime = DateTime.UtcNow;
|
|
|
|
foreach (DungeonInstance dungeon in _act4DungeonManager.Dungeons.ToArray())
|
|
{
|
|
try
|
|
{
|
|
await ProcessAct4DungeonInstanceAfterSlowMo(dungeon, currentTime);
|
|
await ProcessAct4DungeonInstanceCleanUp(dungeon, currentTime);
|
|
|
|
foreach (DungeonSubInstance subInstance in dungeon.DungeonSubInstances.Values)
|
|
{
|
|
try
|
|
{
|
|
await ProcessAct4HatusInstance(subInstance, currentTime);
|
|
await ProcessAct4DungeonCalvinas(subInstance, currentTime, dungeon.DungeonType);
|
|
await ProcessAct4DungeonSubInstanceLoopWave(subInstance, currentTime, stoppingToken);
|
|
await ProcessAct4DungeonSubInstanceWave(subInstance, currentTime, stoppingToken);
|
|
await ProcessAct4DungeonSubInstancePortalGeneration(dungeon, subInstance, currentTime, stoppingToken);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Log.Error($"[DUNGEON_SUBINSTANCE_PROCESS] familyId: {dungeon.FamilyId}", e);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Log.Error($"[DUNGEON_PROCESS] familyId: {dungeon.FamilyId}", e);
|
|
}
|
|
}
|
|
|
|
if (_act4DungeonManager.DungeonEnd < currentTime)
|
|
{
|
|
await _asyncEventPipeline.ProcessEventAsync(new Act4DungeonSystemStopEvent(), stoppingToken);
|
|
}
|
|
}
|
|
|
|
private async Task ProcessAct4DungeonCalvinas(DungeonSubInstance dungeon, DateTime currentTime, DungeonType dungeonType)
|
|
{
|
|
if (dungeonType != DungeonType.Calvinas)
|
|
{
|
|
return;
|
|
}
|
|
|
|
CalvinasState calvinasState = _dungeonManager.GetCalvinasDragons(dungeon.MapInstance.Id);
|
|
if (calvinasState == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (calvinasState.CastTime > currentTime)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_dungeonManager.RemoveCalvinasDragons(dungeon.MapInstance.Id);
|
|
|
|
if (calvinasState.CalvinasDragonsList == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
IEnumerable<IBattleEntity> entities = dungeon.MapInstance.GetNonMonsterBattleEntities();
|
|
|
|
IMonsterEntity boss = dungeon.Bosses.FirstOrDefault();
|
|
foreach (IBattleEntity entity in entities)
|
|
{
|
|
// checks if the player/mate is inside the (dragon) rectangle
|
|
foreach (CalvinasDragon dragon in calvinasState.CalvinasDragonsList)
|
|
{
|
|
bool isInX = false;
|
|
bool isInY = false;
|
|
if (dragon.Axis == CoordType.X)
|
|
{
|
|
isInY = entity.IsInLineY(dragon.At, dragon.Size);
|
|
|
|
if (dragon.Start < dragon.End)
|
|
{
|
|
if (dragon.Start <= entity.PositionX && entity.PositionX <= dragon.End)
|
|
{
|
|
isInX = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (dragon.End <= entity.PositionX && entity.PositionX <= dragon.Start)
|
|
{
|
|
isInX = true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
isInX = entity.IsInLineX(dragon.At, dragon.Size);
|
|
|
|
if (dragon.Start < dragon.End)
|
|
{
|
|
if (dragon.Start <= entity.PositionY && entity.PositionY <= dragon.End)
|
|
{
|
|
isInY = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (dragon.End <= entity.PositionY && entity.PositionY <= dragon.Start)
|
|
{
|
|
isInY = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!isInX || !isInY)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!entity.IsAlive())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (await boss.ShouldSaveDefender(entity, entity.MaxHp, _gameRevivalConfiguration, _buffFactory))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
entity.Hp = 0;
|
|
await entity.EmitEventAsync(new GenerateEntityDeathEvent
|
|
{
|
|
Entity = entity
|
|
});
|
|
boss.BroadcastCleanSuPacket(entity, entity.MaxHp);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private async Task ProcessAct4HatusInstance(DungeonSubInstance dungeon, DateTime currentTime)
|
|
{
|
|
try
|
|
{
|
|
if (dungeon?.HatusHeads?.DragonHeads == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (dungeon.Bosses.Count == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
HatusState hatusState = _dungeonManager.GetHatusState(dungeon.MapInstance.Id);
|
|
if (hatusState == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
HatusHeads heads = dungeon.HatusHeads;
|
|
|
|
if (heads.HeadsState == HatusDragonHeadState.HIDE_HEAD)
|
|
{
|
|
return;
|
|
}
|
|
|
|
short headsChange = 0;
|
|
|
|
HatusDragonHeadState toChange = heads.HeadsState;
|
|
short[] xToHit = new short[3];
|
|
|
|
switch (heads.HeadsState)
|
|
{
|
|
case HatusDragonHeadState.SHOW:
|
|
case HatusDragonHeadState.IDLE:
|
|
|
|
if (hatusState.BlueAttack)
|
|
{
|
|
headsChange += 1;
|
|
heads.DragonHeads.BluePositionX = hatusState.BlueX;
|
|
heads.DragonHeads.BlueIsActive = true;
|
|
toChange = HatusDragonHeadState.ATTACK_CAST;
|
|
}
|
|
|
|
if (hatusState.RedAttack)
|
|
{
|
|
headsChange += 2;
|
|
heads.DragonHeads.RedPositionX = hatusState.RedX;
|
|
heads.DragonHeads.RedIsActive = true;
|
|
toChange = HatusDragonHeadState.ATTACK_CAST;
|
|
}
|
|
|
|
if (hatusState.GreenAttack)
|
|
{
|
|
headsChange += 4;
|
|
heads.DragonHeads.GreenPositionX = hatusState.GreenX;
|
|
heads.DragonHeads.GreenIsActive = true;
|
|
toChange = HatusDragonHeadState.ATTACK_CAST;
|
|
}
|
|
|
|
heads.CastTime = currentTime + hatusState.CastTime;
|
|
|
|
break;
|
|
case HatusDragonHeadState.ATTACK_CAST:
|
|
|
|
if (heads.CastTime < currentTime)
|
|
{
|
|
toChange = HatusDragonHeadState.ATTACK_USE;
|
|
|
|
if (heads.DragonHeads.BlueIsActive)
|
|
{
|
|
headsChange += 1;
|
|
}
|
|
|
|
if (heads.DragonHeads.RedIsActive)
|
|
{
|
|
headsChange += 2;
|
|
}
|
|
|
|
if (heads.DragonHeads.GreenIsActive)
|
|
{
|
|
headsChange += 4;
|
|
}
|
|
|
|
heads.CastTime = currentTime + hatusState.CastTime;
|
|
}
|
|
|
|
break;
|
|
case HatusDragonHeadState.ATTACK_USE:
|
|
|
|
if (heads.CastTime > currentTime)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (heads.DragonHeads.BlueIsActive)
|
|
{
|
|
headsChange += 1;
|
|
heads.DragonHeads.BlueIsActive = false;
|
|
xToHit[0] = heads.DragonHeads.BluePositionX;
|
|
}
|
|
|
|
if (heads.DragonHeads.RedIsActive)
|
|
{
|
|
headsChange += 2;
|
|
heads.DragonHeads.RedIsActive = false;
|
|
xToHit[1] = heads.DragonHeads.RedPositionX;
|
|
}
|
|
|
|
if (heads.DragonHeads.GreenIsActive)
|
|
{
|
|
headsChange += 4;
|
|
heads.DragonHeads.GreenIsActive = false;
|
|
xToHit[2] = heads.DragonHeads.GreenPositionX;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (headsChange == 0)
|
|
{
|
|
if (heads.CastTime > currentTime)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_dungeonManager.RemoveHatusState(dungeon.MapInstance.Id);
|
|
heads.HeadsState = HatusDragonHeadState.IDLE;
|
|
return;
|
|
}
|
|
|
|
dungeon.MapInstance.Broadcast(Act4DungeonExtension.HatusHeadStatePacket(headsChange, heads));
|
|
heads.HeadsState = toChange;
|
|
|
|
if (heads.HeadsState == HatusDragonHeadState.ATTACK_USE)
|
|
{
|
|
if (heads.CastTime > currentTime)
|
|
{
|
|
return;
|
|
}
|
|
|
|
List<IBattleEntity> entitesToAttack = new();
|
|
IMonsterEntity boss = dungeon.Bosses.FirstOrDefault();
|
|
|
|
foreach (short x in xToHit)
|
|
{
|
|
entitesToAttack.AddRange(dungeon.MapInstance.GetNonMonsterBattleEntities().Where(e => e.IsInLineX(x, (short)heads.DragonAttackWidth)));
|
|
}
|
|
|
|
foreach (IBattleEntity entity in entitesToAttack)
|
|
{
|
|
if (!entity.IsAlive())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
int damage = (int)(entity.MaxHp * hatusState.DealtDamage);
|
|
if (await boss.ShouldSaveDefender(entity, damage, _gameRevivalConfiguration, _buffFactory))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (entity.Hp - damage <= 0)
|
|
{
|
|
entity.Hp = 0;
|
|
await entity.EmitEventAsync(new GenerateEntityDeathEvent
|
|
{
|
|
Entity = entity
|
|
});
|
|
|
|
boss.BroadcastCleanSuPacket(entity, damage);
|
|
continue;
|
|
}
|
|
|
|
entity.Hp -= damage;
|
|
|
|
switch (entity)
|
|
{
|
|
case IPlayerEntity character:
|
|
character.LastDefence = DateTime.UtcNow;
|
|
character.Session.RefreshStat();
|
|
|
|
if (character.IsSitting)
|
|
{
|
|
await character.Session.RestAsync(force: true);
|
|
}
|
|
|
|
break;
|
|
case IMateEntity mate:
|
|
mate.LastDefence = DateTime.UtcNow;
|
|
mate.Owner.Session.SendMateLife(mate);
|
|
|
|
if (mate.IsSitting)
|
|
{
|
|
await mate.Owner.Session.EmitEventAsync(new MateRestEvent
|
|
{
|
|
MateEntity = mate,
|
|
Force = true
|
|
});
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
boss.BroadcastCleanSuPacket(entity, damage);
|
|
}
|
|
|
|
heads.HeadsState = HatusDragonHeadState.IDLE;
|
|
_dungeonManager.RemoveHatusState(dungeon.MapInstance.Id);
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Log.Error("[PROCESS_HATUS]", e);
|
|
}
|
|
}
|
|
|
|
private async Task ProcessAct4DungeonSubInstanceLoopWave(DungeonSubInstance dungeonSubInstance, DateTime currentTime, CancellationToken stoppingToken)
|
|
{
|
|
try
|
|
{
|
|
if (dungeonSubInstance.LastDungeonWaveLoop == null || dungeonSubInstance.LoopWaves.Count < 1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
foreach (DungeonLoopWave wave in dungeonSubInstance.LoopWaves)
|
|
{
|
|
if (wave.FirstSpawnWave > currentTime)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (wave.LastMonsterSpawn > currentTime)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
wave.LastMonsterSpawn = currentTime + wave.TickDelay;
|
|
short? scaledWithPlayersAmount = wave.IsScaledWithPlayerAmount ? (short?)dungeonSubInstance.MapInstance.Sessions.Count : null;
|
|
await _asyncEventPipeline.ProcessEventAsync(new MonsterSummonEvent(dungeonSubInstance.MapInstance, wave.Monsters, scaledWithPlayerAmount: scaledWithPlayersAmount), stoppingToken);
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Log.Error("[PROCESS_DUNGEON_LOOP]", e);
|
|
}
|
|
}
|
|
|
|
private async Task ProcessAct4DungeonSubInstanceWave(DungeonSubInstance dungeonSubInstance, DateTime currentTime, CancellationToken stoppingToken)
|
|
{
|
|
try
|
|
{
|
|
DungeonLinearWave wave = dungeonSubInstance.LinearWaves.FirstOrDefault();
|
|
if (wave == null || dungeonSubInstance.LastDungeonWaveLinear == null || currentTime < dungeonSubInstance.LastDungeonWaveLinear + wave.Delay)
|
|
{
|
|
return;
|
|
}
|
|
|
|
dungeonSubInstance.LinearWaves.Remove(wave);
|
|
await _asyncEventPipeline.ProcessEventAsync(new MonsterSummonEvent(dungeonSubInstance.MapInstance, wave.Monsters), stoppingToken);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Log.Error("[PROCESS_DUNGEON_WAVE]", e);
|
|
}
|
|
}
|
|
|
|
private async Task ProcessAct4DungeonSubInstancePortalGeneration(DungeonInstance dungeonInstance, DungeonSubInstance dungeonSubInstance, DateTime currentTime, CancellationToken stoppingToken)
|
|
{
|
|
PortalGenerator portalGenerator = dungeonSubInstance.PortalGenerators.FirstOrDefault();
|
|
if (portalGenerator == null || dungeonSubInstance.LastPortalGeneration == null || currentTime < _act4DungeonManager.DungeonStart + portalGenerator.Delay)
|
|
{
|
|
return;
|
|
}
|
|
|
|
dungeonSubInstance.PortalGenerators.Remove(portalGenerator);
|
|
|
|
await _asyncEventPipeline.ProcessEventAsync(new SpawnPortalEvent(dungeonSubInstance.MapInstance, portalGenerator.Portal), stoppingToken);
|
|
//quick win
|
|
await _asyncEventPipeline.ProcessEventAsync(new Act4DungeonBroadcastPacketEvent
|
|
{
|
|
DungeonInstance = dungeonInstance
|
|
}, stoppingToken);
|
|
await _asyncEventPipeline.ProcessEventAsync(new Act4DungeonBroadcastBossOpenEvent
|
|
{
|
|
DungeonInstance = dungeonInstance
|
|
}, stoppingToken);
|
|
}
|
|
|
|
private static async Task ProcessAct4DungeonInstanceAfterSlowMo(DungeonInstance dungeonInstance, DateTime currentTime)
|
|
{
|
|
if (dungeonInstance.FinishSlowMoDate == null || currentTime < dungeonInstance.FinishSlowMoDate)
|
|
{
|
|
return;
|
|
}
|
|
|
|
dungeonInstance.FinishSlowMoDate = DateTime.MaxValue;
|
|
foreach (DungeonSubInstance subInstance in dungeonInstance.DungeonSubInstances.Values)
|
|
{
|
|
await subInstance.TriggerEvents(DungeonConstEventKeys.RaidSubInstanceAfterSlowMo);
|
|
}
|
|
}
|
|
|
|
private static async Task ProcessAct4DungeonInstanceCleanUp(DungeonInstance dungeonInstance, DateTime currentTime)
|
|
{
|
|
if (dungeonInstance.CleanUpBossMapDate == null || currentTime < dungeonInstance.CleanUpBossMapDate)
|
|
{
|
|
return;
|
|
}
|
|
|
|
dungeonInstance.CleanUpBossMapDate = null;
|
|
foreach (DungeonSubInstance subInstance in dungeonInstance.DungeonSubInstances.Values)
|
|
{
|
|
await subInstance.TriggerEvents(DungeonConstEventKeys.RaidSubInstanceCleanUp);
|
|
}
|
|
}
|
|
} |