336 lines
No EOL
14 KiB
C#
336 lines
No EOL
14 KiB
C#
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<Type> 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<Guid, RaidSubInstance>();
|
|
GenerateMaps(scriptedRaid, raidSubInstances);
|
|
|
|
RaidReward raidReward = InitializeRaidRewards(scriptedRaid);
|
|
|
|
serviceLocator.AddSingleton(raidSubInstances);
|
|
|
|
var portals = new Dictionary<Guid, IPortalEntity>();
|
|
var monsters = new Dictionary<Guid, IMonsterEntity>();
|
|
var buttons = new Dictionary<Guid, ButtonMapItem>();
|
|
|
|
// 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<IEventTriggerContainer, IDictionary<string, IEnumerable<SEvent>>>
|
|
{
|
|
[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<IScriptedEventConverter>()
|
|
.ToDictionary(x => x.EventType, x => x);
|
|
|
|
// Add all events using event converters
|
|
foreach ((IEventTriggerContainer eventContainer, IDictionary<string, IEnumerable<SEvent>> containerEvents) in events)
|
|
{
|
|
foreach ((string trigger, IEnumerable<SEvent> 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<ScriptEventAttribute>();
|
|
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, RaidWave>();
|
|
byte wave = 0;
|
|
foreach (SMonsterWave scriptedRaidWave in scriptedMap.MonsterWaves)
|
|
{
|
|
var waveMonsters = new List<ToSummon>();
|
|
|
|
foreach (SMonster mobWave in scriptedRaidWave.Monsters)
|
|
{
|
|
IMonsterData monsterData = _npcMonsterManager.GetNpc(mobWave.Vnum);
|
|
if (monsterData == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ConcurrentDictionary<byte, Waypoint> waypointsDictionary = null;
|
|
if (mobWave.Waypoints != null)
|
|
{
|
|
byte waypoints = 0;
|
|
foreach (SWaypoint waypoint in mobWave.Waypoints)
|
|
{
|
|
waypointsDictionary ??= new ConcurrentDictionary<byte, Waypoint>();
|
|
|
|
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<IEventTriggerContainer, IDictionary<string, IEnumerable<SEvent>>> events, RaidSubInstance raidSubInstance,
|
|
Dictionary<Guid, ButtonMapItem> 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<Guid, IMonsterEntity> monsters, Dictionary<IEventTriggerContainer, IDictionary<string, IEnumerable<SEvent>>> events,
|
|
RaidSubInstance raidSubInstance)
|
|
{
|
|
// Add monsters
|
|
foreach (SMonster scriptedMonster in scriptedMap.Monsters)
|
|
{
|
|
var drop = new List<DropChance>();
|
|
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<byte, Waypoint>();
|
|
|
|
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<Guid, RaidSubInstance> raidSubInstances, Dictionary<Guid, IPortalEntity> 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<RaidBoxRarity>();
|
|
|
|
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<Guid, RaidSubInstance> 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);
|
|
}
|
|
}
|
|
} |