using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Reflection; using Microsoft.Extensions.DependencyInjection; using PhoenixLib.Events; using PhoenixLib.Logging; using Plugin.Raids.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.Object.Common; using WingsAPI.Scripting.Object.Common.Map; using WingsAPI.Scripting.Object.Raid; using WingsAPI.Scripting.ScriptManager; using WingsEmu.DTOs.Maps; using WingsEmu.Game; using WingsEmu.Game.Entities; using WingsEmu.Game.Entities.Event; using WingsEmu.Game.Helpers.Damages; using WingsEmu.Game.Managers.StaticData; using WingsEmu.Game.Maps; using WingsEmu.Game.Monster; using WingsEmu.Game.Portals; using WingsEmu.Game.Raids; using WingsEmu.Packets.Enums; namespace Plugin.Raids; public class RaidFactory : IRaidFactory { private static readonly IEnumerable Converters; private readonly IAsyncEventPipeline _eventPipeline; private readonly IMapManager _mapManager; private readonly IMonsterEntityFactory _monsterEntityFactory; private readonly INpcMonsterManager _npcMonsterManager; private readonly IPortalFactory _portalFactory; private readonly IRaidScriptManager _raidScriptManager; static RaidFactory() { Converters = typeof(IScriptedEventConverter).Assembly.GetTypes() .Concat(typeof(RaidFactory).Assembly.GetTypes()) .Where(x => typeof(IScriptedEventConverter).IsAssignableFrom(x)) .Where(x => !x.IsAbstract && !x.IsInterface); } public RaidFactory(IRaidScriptManager raidScriptManager, IMapManager mapManager, IAsyncEventPipeline eventPipeline, IMonsterEntityFactory monsterEntityFactory, INpcMonsterManager npcMonsterManager, IPortalFactory portalFactory) { _raidScriptManager = raidScriptManager; _mapManager = mapManager; _eventPipeline = eventPipeline; _monsterEntityFactory = monsterEntityFactory; _npcMonsterManager = npcMonsterManager; _portalFactory = portalFactory; } public RaidInstance CreateRaid(RaidParty raidParty) { SRaid scriptedRaid = _raidScriptManager.GetScriptedRaid(raidParty.Type.ToSRaidType()); if (scriptedRaid == null) { Log.Warn($"Can't create raid {raidParty.Type}, Couldn't find it in RaidScriptManager"); return default; } IServiceCollection serviceLocator = new ServiceCollection(); foreach (Type converter in Converters) { serviceLocator.AddTransient(typeof(IScriptedEventConverter), converter); } var raidSubInstances = new Dictionary(); GenerateMaps(scriptedRaid, raidSubInstances); RaidReward raidReward = InitializeRaidRewards(scriptedRaid); serviceLocator.AddSingleton(raidSubInstances); var portals = new Dictionary(); var monsters = new Dictionary(); var buttons = new Dictionary(); // Register portals, monsters and buttons to service locator serviceLocator.AddSingleton(portals); serviceLocator.AddSingleton(monsters); serviceLocator.AddSingleton(buttons); foreach (SMap scriptedMap in scriptedRaid.Maps) { RaidSubInstance raidSubInstance = raidSubInstances[scriptedMap.Id]; IMapInstance mapInstance = raidSubInstance.MapInstance; serviceLocator.AddSingleton(raidParty); serviceLocator.AddSingleton(raidSubInstance); serviceLocator.AddSingleton(mapInstance); // This dictionary will contains all events to attach later var events = new Dictionary>> { [raidSubInstance] = scriptedMap.Events }; // Add Raid Waves AddWaves(scriptedMap, raidSubInstance); AddPortals(scriptedMap, mapInstance, raidSubInstances, portals); AddMonsters(scriptedMap, mapInstance, monsters, events, raidSubInstance); AddMapObjects(scriptedMap, mapInstance, events, raidSubInstance, buttons); // Get event converters using service locator var converters = serviceLocator.BuildServiceProvider() .GetServices() .ToDictionary(x => x.EventType, x => x); // Add all events using event converters foreach ((IEventTriggerContainer eventContainer, IDictionary> containerEvents) in events) { foreach ((string trigger, IEnumerable scriptedEvents) in containerEvents) { foreach (SEvent scriptedEvent in scriptedEvents) { 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(); if (attribute == null) { throw new InvalidOperationException($"Failed to find attribute for: {scriptedEvent.GetType().Name}"); } eventContainer.AddEvent(trigger, e, attribute.IsRemovedOnTrigger); } } } } return new RaidInstance(raidSubInstances.Values, raidSubInstances[scriptedRaid.Spawn.MapId], new Position(scriptedRaid.Spawn.Position.X, scriptedRaid.Spawn.Position.Y), TimeSpan.FromSeconds(scriptedRaid.DurationInSeconds), (byte)raidParty.Members.Count, raidReward); } private void AddWaves(SMap scriptedMap, RaidSubInstance raidSubInstance) { var raidWaves = new Dictionary(); byte wave = 0; foreach (SMonsterWave scriptedRaidWave in scriptedMap.MonsterWaves) { var waveMonsters = new List(); foreach (SMonster mobWave in scriptedRaidWave.Monsters) { IMonsterData monsterData = _npcMonsterManager.GetNpc(mobWave.Vnum); if (monsterData == null) { continue; } ConcurrentDictionary waypointsDictionary = null; if (mobWave.Waypoints != null) { byte waypoints = 0; foreach (SWaypoint waypoint in mobWave.Waypoints) { waypointsDictionary ??= new ConcurrentDictionary(); var newWaypoint = new Waypoint { X = waypoint.X, Y = waypoint.Y, WaitTime = waypoint.WaitTime }; waypointsDictionary.TryAdd(waypoints, newWaypoint); waypoints++; } } var newToSummon = new ToSummon { VNum = mobWave.Vnum, SpawnCell = mobWave.IsRandomPosition ? null : new Position(mobWave.Position.X, mobWave.Position.Y), IsHostile = true, IsMoving = monsterData.CanWalk, SetHitChance = 5, GoToBossPosition = mobWave.GoToBossPosition == default ? null : new Position(mobWave.GoToBossPosition.X, mobWave.GoToBossPosition.Y), Waypoints = waypointsDictionary, SummonType = SummonType.MONSTER_WAVE, Direction = mobWave.Direction }; waveMonsters.Add(newToSummon); } var newRaidWave = new RaidWave(waveMonsters, scriptedRaidWave.TimeInSeconds); raidWaves[wave] = newRaidWave; wave++; } raidSubInstance.AddRaidWave(raidWaves); } private void AddMapObjects(SMap scriptedMap, IMapInstance mapInstance, Dictionary>> events, RaidSubInstance raidSubInstance, Dictionary buttons) { 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, mapInstance, _eventPipeline, isObjective: scriptedButton.IsObjective, onlyOnce: scriptedButton.OnlyOnce == false ? null : false); events[button] = scriptedButton.Events; buttons[scriptedObject.Id] = button; raidSubInstance.AddRaidButton(button); } } private void AddMonsters(SMap scriptedMap, IMapInstance mapInstance, Dictionary monsters, Dictionary>> events, RaidSubInstance raidSubInstance) { // Add monsters foreach (SMonster scriptedMonster in scriptedMap.Monsters) { var drop = new List(); foreach (SDropChance dropChance in scriptedMonster.Drop) { drop.Add(new DropChance(dropChance.Chance, dropChance.ItemVnum, dropChance.Amount)); } IMonsterEntity monster = _monsterEntityFactory.CreateMonster(scriptedMonster.Vnum, mapInstance, new MonsterEntityBuilder { IsRespawningOnDeath = false, IsBoss = scriptedMonster.IsBoss, IsTarget = scriptedMonster.IsTarget, IsWalkingAround = scriptedMonster.CanMove, IsHostile = true, SetHitChance = 5, HpMultiplier = scriptedMonster.IsBoss ? null : scriptedMonster.IsTarget ? 21f : 6f, MpMultiplier = scriptedMonster.IsBoss ? null : scriptedMonster.IsTarget ? 21f : 6f, RaidDrop = drop, GeneratedGuid = scriptedMonster.Id, Direction = scriptedMonster.Direction }); if (scriptedMonster.Waypoints != null) { byte waypoints = 0; foreach (SWaypoint waypoint in scriptedMonster.Waypoints) { monster.Waypoints ??= new ConcurrentDictionary(); var newWaypoint = new Waypoint { X = waypoint.X, Y = waypoint.Y, WaitTime = waypoint.WaitTime }; monster.Waypoints.TryAdd(waypoints, newWaypoint); waypoints++; } } monster.EmitEventAsync(new MapJoinMonsterEntityEvent(monster, scriptedMonster.Position.X, scriptedMonster.Position.Y)); monsters[scriptedMonster.Id] = monster; events[monster] = scriptedMonster.Events; raidSubInstance.AddRaidMonster(monster); } } private void AddPortals(SMap scriptedMap, IMapInstance mapInstance, Dictionary raidSubInstances, Dictionary portals) { // Add portals foreach (SPortal scriptedPortal in scriptedMap.Portals) { var sourcePos = new Position(scriptedPortal.SourcePosition.X, scriptedPortal.SourcePosition.Y); var destPos = new Position(scriptedPortal.DestinationPosition.X, scriptedPortal.DestinationPosition.Y); IPortalEntity portal = _portalFactory.CreatePortal((PortalType)scriptedPortal.Type, mapInstance, sourcePos, raidSubInstances[scriptedPortal.DestinationId].MapInstance.Id, destPos); portals[scriptedPortal.Id] = portal; mapInstance.AddPortalToMap(portal); } } private static RaidReward InitializeRaidRewards(SRaid scriptedRaid) { var raidBoxRarities = new List(); foreach (SRaidBoxRarity boxRarity in scriptedRaid.Reward.RaidBox.RaidBoxRarity) { var newRarity = new RaidBoxRarity(boxRarity.Rarity, boxRarity.Chance); raidBoxRarities.Add(newRarity); } var raidBox = new RaidBox(scriptedRaid.Reward.RaidBox.RewardBox, raidBoxRarities); var raidReward = new RaidReward(raidBox, scriptedRaid.Reward.DefaultReputation, scriptedRaid.Reward.FixedReputation); return raidReward; } private void GenerateMaps(SRaid scriptedRaid, Dictionary raidSubInstances) { foreach (SMap scriptedMap in scriptedRaid.Maps) { IMapInstance map = scriptedMap.MapType == SMapType.MapId ? _mapManager.GenerateMapInstanceByMapId(scriptedMap.MapIdVnum, MapInstanceType.RaidInstance) : _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.RaidInstance); raidSubInstances[scriptedMap.Id] = new RaidSubInstance(map, _eventPipeline); } } }