server-master/srcs/_plugins/Plugin.Act4/DungeonFactory.cs
2026-02-10 18:21:30 +01:00

299 lines
No EOL
13 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using PhoenixLib.Events;
using PhoenixLib.Logging;
using Plugin.Act4.Event;
using Plugin.Act4.Extension;
using WingsAPI.Game.Extensions.PacketGeneration;
using WingsAPI.Scripting.Attribute;
using WingsAPI.Scripting.Converter;
using WingsAPI.Scripting.Enum;
using WingsAPI.Scripting.Event;
using WingsAPI.Scripting.Event.Common;
using WingsAPI.Scripting.Object.Common;
using WingsAPI.Scripting.Object.Common.Map;
using WingsAPI.Scripting.Object.Dungeon;
using WingsAPI.Scripting.Object.Raid;
using WingsAPI.Scripting.ScriptManager;
using WingsEmu.DTOs.Maps;
using WingsEmu.Game;
using WingsEmu.Game.Act4;
using WingsEmu.Game.Act4.Entities;
using WingsEmu.Game.Act4.Event;
using WingsEmu.Game.Entities;
using WingsEmu.Game.Entities.Event;
using WingsEmu.Game.Helpers.Damages;
using WingsEmu.Game.Maps;
using WingsEmu.Game.Portals;
using WingsEmu.Game.Raids;
using WingsEmu.Packets.Enums;
namespace Plugin.Act4;
public class DungeonFactory : IDungeonFactory
{
private static readonly IEnumerable<Type> Converters;
private readonly IAsyncEventPipeline _eventPipeline;
private readonly HatusDragonHead _hatusHead = new()
{
BluePositionX = 23,
RedPositionX = 36,
GreenPositionX = 49
};
private readonly IMapManager _mapManager;
private readonly IMonsterEntityFactory _monsterEntityFactory;
private readonly IPortalFactory _portalFactory;
private readonly IDungeonScriptManager _raidScriptManager;
static DungeonFactory()
{
Converters = typeof(IScriptedEventConverter).Assembly.GetTypes()
.Concat(typeof(DungeonFactory).Assembly.GetTypes())
.Where(x => typeof(IScriptedEventConverter).IsAssignableFrom(x))
.Where(x => !x.IsAbstract && !x.IsInterface);
}
public DungeonFactory(IDungeonScriptManager raidScriptManager, IMapManager mapManager, IAsyncEventPipeline eventPipeline, IMonsterEntityFactory monsterEntityFactory, IPortalFactory portalFactory)
{
_raidScriptManager = raidScriptManager;
_mapManager = mapManager;
_eventPipeline = eventPipeline;
_monsterEntityFactory = monsterEntityFactory;
_portalFactory = portalFactory;
}
public DungeonInstance CreateDungeon(long familyId, DungeonType dungeonType)
{
SDungeon scriptedDungeon = _raidScriptManager.GetScriptedDungeon(dungeonType.ToSDungeonType());
if (scriptedDungeon == null)
{
Log.Error($"Unable to create dungeon of DungeonType: '{dungeonType.ToString()}', the script couldn't be found inside DungeonScriptManager",
new Act4DungeonSystemException($"Unable to create act4 dungeon - DungeonType: {dungeonType.ToString()}, FamilyId: {familyId}"));
return default;
}
var raidBoxRarities = new List<RaidBoxRarity>();
foreach (SRaidBoxRarity boxRarity in scriptedDungeon.Reward.RaidBox.RaidBoxRarity)
{
var newRarity = new RaidBoxRarity(boxRarity.Rarity, boxRarity.Chance);
raidBoxRarities.Add(newRarity);
}
var raidBox = new RaidBox(scriptedDungeon.Reward.RaidBox.RewardBox, raidBoxRarities);
var raidReward = new RaidReward(raidBox, scriptedDungeon.Reward.DefaultReputation, scriptedDungeon.Reward.FixedReputation);
//Preparing game object locators for the conversion of ScriptEvents to GameEvents
Dictionary<Guid, DungeonSubInstance> dungeonSubInstancesByScriptId = new();
Dictionary<Guid, IPortalEntity> portalsByScriptId = new();
Dictionary<Guid, IMonsterEntity> monstersByScriptId = new();
Dictionary<Guid, ButtonMapItem> buttonsByScriptId = new();
Dictionary<Guid, List<(IEventTriggerContainer container, IDictionary<string, IEnumerable<SEvent>> events)>> eventsToConvertAndAdd = new();
HatusHeads hatusHeads = dungeonType == DungeonType.Hatus ? new HatusHeads(_hatusHead) : null;
//We prepare first portals to allow the creation of portals
//(portals connect two maps and pre-categorizing the maps by their ScriptIds lets us obtain their GameIds, allowing us to generate gamePortals as we require of GameIds to do so)
foreach (SMap scriptedMap in scriptedDungeon.Maps)
{
IMapInstance map = scriptedMap.MapType == SMapType.MapId
? _mapManager.GenerateMapInstanceByMapId(scriptedMap.MapIdVnum, MapInstanceType.Act4Dungeon)
: _mapManager.GenerateMapInstanceByMapVNum(new ServerMapDto
{
Flags = scriptedMap.Flags.Select(mapFlag => (MapFlags)(int)mapFlag).ToList(),
Id = scriptedMap.MapIdVnum,
MapVnum = scriptedMap.MapIdVnum,
NameId = scriptedMap.NameId,
MusicId = scriptedMap.MusicId
}, MapInstanceType.Act4Dungeon);
dungeonSubInstancesByScriptId[scriptedMap.Id] = new DungeonSubInstance(map, _eventPipeline, hatusHeads);
eventsToConvertAndAdd[scriptedMap.Id] = new List<(IEventTriggerContainer container, IDictionary<string, IEnumerable<SEvent>> events)>
{
(dungeonSubInstancesByScriptId[scriptedMap.Id], scriptedMap.Events)
};
}
foreach (SMap scriptedMap in scriptedDungeon.Maps)
{
DungeonSubInstance dungeonSubInstance = dungeonSubInstancesByScriptId[scriptedMap.Id];
var dungeonWaves = new List<DungeonLinearWave>();
var dungeonLoopWaves = new List<DungeonLoopWave>();
// Add Monster Waves
foreach (SMonsterWave monsterWave in scriptedMap.MonsterWaves)
{
var summons = new List<ToSummon>();
foreach (SMonster mobWave in monsterWave.Monsters)
{
summons.Add(new ToSummon
{
VNum = mobWave.Vnum,
IsHostile = true,
IsMoving = true,
SpawnCell = mobWave.IsRandomPosition ? null : new Position(mobWave.Position.X, mobWave.Position.Y),
SummonType = SummonType.MONSTER_WAVE
});
}
if (monsterWave.Loop && monsterWave.LoopTick.HasValue)
{
dungeonLoopWaves.Add(new DungeonLoopWave
{
Delay = TimeSpan.FromSeconds(monsterWave.TimeInSeconds),
Monsters = summons,
TickDelay = TimeSpan.FromSeconds(monsterWave.LoopTick.Value),
IsScaledWithPlayerAmount = monsterWave.IsScaledWithPlayerAmount
});
}
else
{
dungeonWaves.Add(new DungeonLinearWave
{
Monsters = summons,
Delay = TimeSpan.FromSeconds(monsterWave.TimeInSeconds)
});
}
}
dungeonSubInstance.SetLinearWaves(dungeonWaves);
dungeonSubInstance.SetLoopWaves(dungeonLoopWaves);
var portalGenerators = new List<PortalGenerator>();
foreach (SPortal scriptedPortal in scriptedMap.Portals)
{
bool isReturnPortal = scriptedPortal.IsReturn;
var sourcePos = new Position(scriptedPortal.SourcePosition.X, scriptedPortal.SourcePosition.Y);
IMapInstance sourceMap = dungeonSubInstancesByScriptId[scriptedMap.Id].MapInstance;
var destPos = new Position(scriptedPortal.DestinationPosition.X, scriptedPortal.DestinationPosition.Y);
IMapInstance destinationMap = isReturnPortal ? null : dungeonSubInstancesByScriptId[scriptedPortal.DestinationId].MapInstance;
IPortalEntity portal = _portalFactory.CreatePortal((PortalType)scriptedPortal.Type, sourceMap, sourcePos, destinationMap, destPos);
portalsByScriptId[scriptedPortal.Id] = portal;
if (scriptedPortal.CreationDelay.HasValue)
{
portalGenerators.Add(new PortalGenerator
{
Portal = portal,
Delay = TimeSpan.FromSeconds(scriptedPortal.CreationDelay.Value)
});
continue;
}
dungeonSubInstance.MapInstance.AddPortalToMap(portal);
}
dungeonSubInstance.SetPortalGenerators(portalGenerators);
// Add monsters
foreach (SMonster scriptedMonster in scriptedMap.Monsters)
{
IMonsterEntity monster = _monsterEntityFactory.CreateMonster(scriptedMonster.Vnum, dungeonSubInstance.MapInstance,
new MonsterEntityBuilder
{
IsRespawningOnDeath = false,
IsBoss = scriptedMonster.IsBoss,
IsTarget = scriptedMonster.IsTarget,
IsWalkingAround = scriptedMonster.CanMove,
IsHostile = true
});
monster.EmitEventAsync(new MapJoinMonsterEntityEvent(monster, scriptedMonster.Position.X, scriptedMonster.Position.Y));
monstersByScriptId[scriptedMonster.Id] = monster;
eventsToConvertAndAdd[scriptedMap.Id].Add((monster, scriptedMonster.Events));
//Adding this to obtain the subInstance via the monsters' Script Id
dungeonSubInstancesByScriptId[scriptedMonster.Id] = dungeonSubInstance;
dungeonSubInstance.AddDungeonMonster(monster);
}
// Add map objects
foreach (SMapObject scriptedObject in scriptedMap.Objects)
{
if (scriptedObject is not SButton scriptedButton)
{
continue;
}
var button = new ButtonMapItem(scriptedButton.Position.X, scriptedButton.Position.Y, scriptedButton.DeactivatedVnum, scriptedButton.ActivatedVnum, false,
dungeonSubInstance.MapInstance, _eventPipeline, scriptedButton.IsObjective);
eventsToConvertAndAdd[scriptedMap.Id].Add((button, scriptedButton.Events));
dungeonSubInstance.AddDungeonButton(button);
}
}
//Yeah, we need an ugly wrapper because the instance is not constructed until the end, and our events require that instance for the future.
DungeonInstanceWrapper dungeonInstanceWrapper = new();
IServiceCollection serviceLocator = new ServiceCollection();
serviceLocator.AddSingleton(dungeonSubInstancesByScriptId);
serviceLocator.AddSingleton(portalsByScriptId);
serviceLocator.AddSingleton(monstersByScriptId);
serviceLocator.AddSingleton(buttonsByScriptId);
serviceLocator.AddSingleton(dungeonInstanceWrapper);
foreach (Type converter in Converters)
{
serviceLocator.AddTransient(typeof(IScriptedEventConverter), converter);
}
foreach ((Guid scriptedMapId, List<(IEventTriggerContainer container, IDictionary<string, IEnumerable<SEvent>> events)> eventsForContainers) in eventsToConvertAndAdd)
{
serviceLocator.AddSingleton(dungeonSubInstancesByScriptId[scriptedMapId]);
var converters = serviceLocator.BuildServiceProvider()
.GetServices<IScriptedEventConverter>()
.ToDictionary(x => x.EventType, x => x);
foreach ((IEventTriggerContainer container, IDictionary<string, IEnumerable<SEvent>> containerEvents) in eventsForContainers)
{
foreach ((string key, IEnumerable<SEvent> events) in containerEvents)
{
foreach (SEvent scriptedEvent in events)
{
IAsyncEvent e = converters.GetValueOrDefault(scriptedEvent.GetType())?.Convert(scriptedEvent);
if (e == null)
{
throw new InvalidOperationException($"Failed to convert {scriptedEvent.GetType().Name} to async event");
}
ScriptEventAttribute attribute = scriptedEvent.GetType().GetCustomAttribute<ScriptEventAttribute>();
if (attribute == null)
{
throw new InvalidOperationException($"Failed to find attribute for: {scriptedEvent.GetType().Name}");
}
//quick win
if (attribute.Name == SRemovePortalEvent.Name)
{
container.AddEvent(key, new Act4DungeonBroadcastBossClosedEvent
{
DungeonInstanceWrapper = dungeonInstanceWrapper
}, attribute.IsRemovedOnTrigger);
}
container.AddEvent(key, e, attribute.IsRemovedOnTrigger);
}
}
}
}
return dungeonInstanceWrapper.DungeonInstance = new DungeonInstance(familyId, dungeonType, dungeonSubInstancesByScriptId.Values, dungeonSubInstancesByScriptId[scriptedDungeon.Spawn.MapId],
new Position(scriptedDungeon.Spawn.Position.X, scriptedDungeon.Spawn.Position.Y), raidReward);
}
}