diff --git a/UncreatedWarfare/Commands/Structure/StructureCommand.cs b/UncreatedWarfare/Commands/Structure/StructureCommand.cs index df9371a0..c8fddc8c 100644 --- a/UncreatedWarfare/Commands/Structure/StructureCommand.cs +++ b/UncreatedWarfare/Commands/Structure/StructureCommand.cs @@ -1,4 +1,4 @@ -using Uncreated.Warfare.Interaction.Commands; +using Uncreated.Warfare.Interaction.Commands; using Uncreated.Warfare.Players; using Uncreated.Warfare.Teams; using Uncreated.Warfare.Translations; @@ -47,10 +47,10 @@ public sealed class StructureTranslations : PropertiesTranslationCollection public readonly Translation StructureExamineLastOwnerChat = new Translation("<#c6d4b8>Last owner of <#e6e3d5>{0}: {1} ({2}), Team: {3}.", TranslationOptions.TMProUI | TranslationOptions.NoRichText, arg0Fmt: RarityColorAddon.Instance, arg1Fmt: WarfarePlayer.FormatColoredPlayerName, arg2Fmt: WarfarePlayer.FormatSteam64, arg3Fmt: FactionInfo.FormatColorDisplayName); [TranslationData] - public readonly Translation VehicleExamineLastOwnerPrompt = new Translation("Owner of {0}: {1}, Team: {2}. Previous Owner: {3} ({4}).", TranslationOptions.TMProUI | TranslationOptions.NoRichText, arg1Fmt: WarfarePlayer.FormatPlayerName, arg2Fmt: FactionInfo.FormatDisplayName); + public readonly Translation VehicleExamineLastOwnerPrompt = new Translation("Owner of {0}: {1}, Team: {2}.", TranslationOptions.TMProUI | TranslationOptions.NoRichText, arg1Fmt: WarfarePlayer.FormatPlayerName, arg2Fmt: FactionInfo.FormatDisplayName); [TranslationData] - public readonly Translation VehicleExamineLastOwnerChat = new Translation("<#c6d4b8>Owner of <#e6e3d5>{0}: {1} ({2}), Team: {3}. Previous Owner: {4} ({5}).", TranslationOptions.TMProUI | TranslationOptions.NoRichText, arg0Fmt: RarityColorAddon.Instance, arg1Fmt: WarfarePlayer.FormatColoredPlayerName, arg2Fmt: WarfarePlayer.FormatSteam64, arg3Fmt: FactionInfo.FormatColorDisplayName); + public readonly Translation VehicleExamineLastOwnerChat = new Translation("<#c6d4b8>Owner of <#e6e3d5>{0}: {1} ({2}), Team: {3}.", TranslationOptions.TMProUI | TranslationOptions.NoRichText, arg0Fmt: RarityColorAddon.Instance, arg1Fmt: WarfarePlayer.FormatColoredPlayerName, arg2Fmt: WarfarePlayer.FormatSteam64, arg3Fmt: FactionInfo.FormatColorDisplayName); [TranslationData(IsPriorityTranslation = false)] public readonly Translation StructureSaveInvalidProperty = new Translation("<#ff8c69>{0} isn't a valid a structure property. Try putting 'owner' or 'group'."); diff --git a/UncreatedWarfare/Commands/Structure/StructureExamineCommand.cs b/UncreatedWarfare/Commands/Structure/StructureExamineCommand.cs index 42533c9b..ccfe8345 100644 --- a/UncreatedWarfare/Commands/Structure/StructureExamineCommand.cs +++ b/UncreatedWarfare/Commands/Structure/StructureExamineCommand.cs @@ -1,4 +1,4 @@ -using Uncreated.Warfare.Components; +using Uncreated.Warfare.Components; using Uncreated.Warfare.Interaction.Commands; using Uncreated.Warfare.Layouts.Teams; using Uncreated.Warfare.Players; @@ -56,27 +56,19 @@ private async Task ExamineVehicle(InteractableVehicle vehicle, WarfarePlayer pla else { Team team = _teamManager.GetTeam(vehicle.lockedGroup); - ulong prevOwner = vehicle.transform.TryGetComponent(out VehicleComponent vcomp) ? vcomp.PreviousOwner : 0ul; IPlayer names = await _userDataService.GetUsernamesAsync(vehicle.lockedOwner.m_SteamID, token).ConfigureAwait(false); - string prevOwnerName; - if (prevOwner != 0ul) - { - PlayerNames pl = await _userDataService.GetUsernamesAsync(prevOwner, token).ConfigureAwait(false); - prevOwnerName = pl.GetDisplayNameOrPlayerName(); - } - else prevOwnerName = "None"; await UniTask.SwitchToMainThread(token); if (sendurl) { Context.ReplySteamProfileUrl(_translations.VehicleExamineLastOwnerPrompt - .Translate(vehicle.asset, names, team.Faction, prevOwnerName, prevOwner, player, canUseIMGUI: true), vehicle.lockedOwner); + .Translate(vehicle.asset, names, team.Faction, player, canUseIMGUI: true), vehicle.lockedOwner); } else { OfflinePlayer pl = new OfflinePlayer(vehicle.lockedOwner); await pl.CacheUsernames(_userDataService, token).ConfigureAwait(false); await UniTask.SwitchToMainThread(token); - Context.Reply(_translations.VehicleExamineLastOwnerChat, vehicle.asset, names, pl, team.Faction, prevOwnerName, prevOwner); + Context.Reply(_translations.VehicleExamineLastOwnerChat, vehicle.asset, names, pl, team.Faction); } } } diff --git a/UncreatedWarfare/Commands/TeamsCommand.cs b/UncreatedWarfare/Commands/TeamsCommand.cs index 564e3e6a..c1c3bbd2 100644 --- a/UncreatedWarfare/Commands/TeamsCommand.cs +++ b/UncreatedWarfare/Commands/TeamsCommand.cs @@ -1,6 +1,7 @@ -using Uncreated.Warfare.Interaction.Commands; +using Uncreated.Warfare.Interaction.Commands; using Uncreated.Warfare.Layouts.Teams; using Uncreated.Warfare.Lobby; +using Uncreated.Warfare.Players; using Uncreated.Warfare.Players.Permissions; using Uncreated.Warfare.Translations; using Uncreated.Warfare.Zones; diff --git a/UncreatedWarfare/Commands/WarfareDev/DebugClearCooldownsCommand.cs b/UncreatedWarfare/Commands/WarfareDev/DebugClearCooldownsCommand.cs index 6793b954..f6c918c2 100644 --- a/UncreatedWarfare/Commands/WarfareDev/DebugClearCooldownsCommand.cs +++ b/UncreatedWarfare/Commands/WarfareDev/DebugClearCooldownsCommand.cs @@ -1,4 +1,4 @@ -using Uncreated.Warfare.Interaction.Commands; +using Uncreated.Warfare.Interaction.Commands; using Uncreated.Warfare.Players; using Uncreated.Warfare.Players.Management; diff --git a/UncreatedWarfare/Components/VehicleComponent.cs b/UncreatedWarfare/Components/VehicleComponent.cs deleted file mode 100644 index 00693b31..00000000 --- a/UncreatedWarfare/Components/VehicleComponent.cs +++ /dev/null @@ -1,699 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using System; -using System.Collections.Generic; -using Uncreated.Warfare.Events.Models.Vehicles; -using Uncreated.Warfare.FOBs.SupplyCrates; -using Uncreated.Warfare.Kits; -using Uncreated.Warfare.Players; -using Uncreated.Warfare.Players.Management; -using Uncreated.Warfare.Players.UI; -using Uncreated.Warfare.Util; -using Uncreated.Warfare.Util.List; -using Uncreated.Warfare.Vehicles; -using Uncreated.Warfare.Vehicles.WarfareVehicles; -using Uncreated.Warfare.Vehicles.Spawners; -using Uncreated.Warfare.Vehicles.UI; - -namespace Uncreated.Warfare.Components; - -public class VehicleComponent : MonoBehaviour -{ - private const int StartingFlaresAttackHeli = 30; - private const int StartingFlaresTransportHeli = 50; - private const int StartingFlaresJet = 30; - public const int FlareBurstCount = 10; - public const int FlareCooldown = 11; - - private IPlayerService _playerService = null!; - private TipService _tipService; - private VehicleHUD _hud; - - private Vector3 _lastPosInterval; - private float _totalDistance; - private float _lastCheck; - private float _lastZoneCheck; - private float _timeLastFlare; - private float _timeLastFlareDrop; - private int _flareBurst; - public ulong LastDriver; - public float LastDriverTime; - public float LastDriverDistance; - public Guid LastItem; - public bool LastItemIsVehicle; - public InteractableVehicle Vehicle; - public InteractableVehicle? LastDamagedFromVehicle; - public EDamageOrigin LastDamageOrigin; - public ulong LastInstigator; - public CSteamID LastLocker; - private bool _isResupplied; - private Coroutine? _quotaLoop; - private Coroutine? _autoSupplyLoop; - private PlayerDictionary _timeEnteredTable; - private PlayerDictionary _timeRewardedTable; - public Dictionary> DamageTable; - public ulong PreviousOwner; - public float TotalDistanceTravelled => _totalDistance; - public float TimeRequested { get; set; } - public Stack OwnerHistory { get; } = new Stack(2); - public ulong Team => Vehicle.lockedGroup.m_SteamID; // todo - public PlayerDictionary TransportTable { get; private set; } - public PlayerDictionary UsageTable { get; private set; } - public float Quota { get; set; } - public float RequiredQuota { get; set; } - public Coroutine? ForceSupplyLoop { get; private set; } - public WarfareVehicleInfo? VehicleData { get; private set; } - - /// - /// The spawn the vehicle was created at, if any. - /// - public VehicleSpawner? Spawn { get; private set; } - -#if false - public Zone? SafezoneZone - { - get - { - if (Data.Gamemode is null) - return null; - if (Time.time - _lastZoneCheck < Data.Gamemode.EventLoopSpeed) - CheckZones(); - return _safezoneZone; - } - } - public Zone? NoDropZone - { - get - { - if (Data.Gamemode is null) - return null; - if (Time.time - _lastZoneCheck < Data.Gamemode.EventLoopSpeed) - CheckZones(); - return _noDropZone; - } - } - public Zone? NoPickZone - { - get - { - if (Data.Gamemode is null) - return null; - if (Time.time - _lastZoneCheck < Data.Gamemode.EventLoopSpeed) - CheckZones(); - return _noPickZone; - } - } -#endif - - public bool IsGroundVehicle => VehicleData != null && VehicleData.Type.IsGroundVehicle(); - public bool IsArmor => VehicleData != null && VehicleData.Type.IsArmor(); - public bool IsLogistics => VehicleData != null && VehicleData.Type.IsLogistics(); - public bool IsAircraft => VehicleData != null && VehicleData.Type.IsAircraft(); - public bool IsAssaultAircraft => VehicleData != null && VehicleData.Type.IsAssaultAircraft(); - public bool IsEmplacement => VehicleData != null && VehicleData.Type.IsEmplacement(); - public bool IsInVehiclebay => VehicleData != null; - public bool CanTransport => VehicleData != null && VehicleData.CanTransport(Vehicle); - - // note that this is called at the beginning of every session to replace the scoped services - public void Initialize(InteractableVehicle vehicle, IServiceProvider serviceProvider) - { - Vehicle = vehicle; - TransportTable = new PlayerDictionary(8); - UsageTable = new PlayerDictionary(8); - _timeEnteredTable = new PlayerDictionary(); - _timeRewardedTable = new PlayerDictionary(); - DamageTable = new Dictionary>(); - _isResupplied = true; - - OwnerHistory.Clear(); - if (vehicle.lockedOwner.GetEAccountType() == EAccountType.k_EAccountTypeIndividual) - OwnerHistory.Push(vehicle.lockedOwner.m_SteamID); - - Quota = 0; - RequiredQuota = -1; - - _playerService = serviceProvider.GetRequiredService(); - _tipService = serviceProvider.GetRequiredService(); - _hud = serviceProvider.GetRequiredService(); - - VehicleInfoStore? vehicleInfoStore = serviceProvider.GetService(); - if (vehicleInfoStore != null) - { - VehicleData = vehicleInfoStore.GetVehicleInfo(Vehicle.asset); - } - - _lastPosInterval = transform.position; - -#if false // todo - foreach (var passenger in Vehicle.turrets) - { - if (VehicleBay.Config.GroundAAWeapons.ContainsId(passenger.turret.itemID)) - passenger.turretAim.gameObject.GetOrAddComponent().Initialize(700, 1500, Gamemode.Config.EffectLockOn1, 0.7f, 14.6f); - if (VehicleBay.Config.AirAAWeapons.ContainsId(passenger.turret.itemID)) - passenger.turretAim.gameObject.GetOrAddComponent().Initialize(600, Gamemode.Config.EffectLockOn2, 1, 11); - } -#endif - - ReloadCountermeasures(); - } - - internal void UnlinkFromSpawn(VehicleSpawner spawn) - { - GameThread.AssertCurrent(); - - if (spawn == null) - throw new ArgumentNullException(nameof(spawn)); - - if (!Equals(Spawn, spawn)) - { - throw new InvalidOperationException("The given spawn is not linked to this vehicle."); - } - - if (Spawn?.LinkedVehicle == Vehicle) - { - throw new InvalidOperationException("The old linked spawn is still linked to this vehicle."); - } - - Spawn = null; - } - - internal void LinkToSpawn(VehicleSpawner spawn) - { - GameThread.AssertCurrent(); - - if (spawn == null) - throw new ArgumentNullException(nameof(spawn)); - - if (spawn.LinkedVehicle != Vehicle) - { - throw new InvalidOperationException("The given spawn is not linked to this vehicle."); - } - - Spawn = spawn; - } - - public static void TryAddOwnerToHistory(InteractableVehicle vehicle, ulong steam64) - { - if (vehicle.TryGetComponent(out VehicleComponent comp)) - { - if (comp.OwnerHistory.Count > 0) - comp.PreviousOwner = comp.OwnerHistory.Peek(); - else - comp.TimeRequested = Time.realtimeSinceStartup; - comp.OwnerHistory.Push(steam64); - } - } - public bool IsType(VehicleType type) => VehicleData != null && VehicleData.Type == type; - private void CheckZones() - { - // todo not sure if we're keeping this - _lastZoneCheck = Time.time; -#if false - if (Data.Singletons.TryGetSingleton(out ZoneList zoneList)) - { - zoneList.WriteWait(); - try - { - _safezoneZone = null; - _noDropZone = null; - _noPickZone = null; - for (int i = 0; i < zoneList.Items.Count; ++i) - { - Zone? zone = zoneList.Items[i]?.Item; - if (zone is null || zone.Data.Flags == ZoneFlags.None || !zone.IsInside(transform.position)) - continue; - if ((zone.Data.Flags & ZoneFlags.Safezone) != 0) - { - if (_safezoneZone is null || _safezoneZone.BoundsArea > zone.BoundsArea) - { - _safezoneZone = zone; - L.LogDebug($"Vehicle {Vehicle.asset.name} in sz {_safezoneZone.Name}."); - } - } - if ((zone.Data.Flags & ZoneFlags.NoDropItems) != 0) - { - if (_noDropZone is null || _noDropZone.BoundsArea > zone.BoundsArea) - { - _noDropZone = zone; - L.LogDebug($"Vehicle {Vehicle.asset.name} in ndz {_noDropZone.Name}."); - } - } - if ((zone.Data.Flags & ZoneFlags.NoPickItems) != 0) - { - if (_noPickZone is null || _noPickZone.BoundsArea > zone.BoundsArea) - { - _noPickZone = zone; - L.LogDebug($"Vehicle {Vehicle.asset.name} in npz {_noPickZone.Name}."); - } - } - } - } - finally - { - zoneList.WriteRelease(); - } - } -#endif - } - - private void ShowHUD(WarfarePlayer player, byte seat) - { - if (VehicleData == null) - return; - - if (!IsAircraft) - return; - - if (!VehicleData.IsCrewSeat(seat)) - return; - - _hud.SendToPlayer(player.Connection); - - _hud.MissileWarning.SetVisibility(player.Connection, false); - _hud.MissileWarningDriver.SetVisibility(player.Connection, false); - _hud.FlareCount.SetVisibility(player.Connection, seat == 0); - - if (seat == 0) - _hud.FlareCount.SetText(player.Connection, "FLARES: " + _totalFlaresLeft); - } - private void UpdateHUDFlares() - { - if (!IsAircraft) - return; - - var driver = Vehicle.passengers[0].player; - if (driver != null) - _hud.FlareCount.SetText(driver.transportConnection, "FLARES: " + _totalFlaresLeft); - } - private void HideHUD(WarfarePlayer player) - { - _hud.ClearFromPlayer(player.Connection); - } - internal void OnPlayerEnteredVehicle(EnterVehicle e) - { - // todo - WarfarePlayer player = e.Player; - - byte toSeat = (byte)e.PassengerIndex; - if (toSeat == 0) - { - LastDriver = player.Steam64.m_SteamID; - LastDriverTime = Time.realtimeSinceStartup; - _totalDistance = 0; - } - - if (VehicleData.IsCrewSeat(toSeat)) - { - TransportTable[player] = player.Position; - } - - _timeEnteredTable[player] = DateTime.UtcNow; - - if (_quotaLoop is null) - { - RequiredQuota = 0; // todo VehicleData.TicketCost * 0.5f; - _quotaLoop = StartCoroutine(QuotaLoop()); - } - - ShowHUD(player, toSeat); - } - public void OnPlayerExitedVehicle(ExitVehicle e) - { - if (IsInVehiclebay) - EvaluateUsage(e.Player.UnturnedPlayer.channel.owner); - - HideHUD(e.Player); - - if (e.Player.Component().ActiveClass == Class.Squadleader && - (VehicleData != null && VehicleData.Type.IsLogistics()) - // && !F.IsInMain(e.Player.Position) && - // Data.Singletons.GetSingleton()?.FindNearestFOB(e.Player.Position, e.Player.GetTeam()) == null - ) - { - // todo _tipService.TryGiveTip(e.Player, 300, T.TipPlaceRadio); - } - - if (e.Vehicle.Vehicle.passengers.Length > 0 && e.Vehicle.Vehicle.passengers[0] == null || e.Vehicle.Vehicle.passengers[0].player == null || - e.Vehicle.Vehicle.passengers[0].player.player.channel.owner.playerID.steamID.m_SteamID == e.Player.Steam64.m_SteamID) - { - if (LastDriver == e.Player.Steam64.m_SteamID) - LastDriverDistance = _totalDistance; - return; - } - if (TransportTable.TryGetValue(e.Player, out Vector3 original)) - { - float distance = (e.Player.Position - original).magnitude; - if (distance >= 200 && e.Player.Component().ActiveClass is not Class.Crewman and not Class.Pilot) - { - if (!(_timeRewardedTable.TryGetValue(e.Player, out DateTime time) && (DateTime.UtcNow - time).TotalSeconds < 60)) - { - int amount = (int)(Math.Floor(distance / 100) * 2) + 5; - - Player player = e.Vehicle.Vehicle.passengers[0].player.player; - //WarfarePlayer? warfarePlayer = _playerService.GetOnlinePlayerOrNull(player); - //if (warfarePlayer != null) - // Points.AwardXP(warfarePlayer, XPReward.TransportingPlayer, T.XPToastTransportingPlayers, amount); - - Quota += 0.5F; - - _timeRewardedTable[e.Player] = DateTime.UtcNow; - } - } - - TransportTable.Remove(e.Player); - } - } - public void OnPlayerSwapSeatRequested(VehicleSwappedSeat e) - { - // todo - if (e.NewPassengerIndex == 0) - { - // new driver - LastDriver = e.Player.Steam64.m_SteamID; - LastDriverTime = Time.realtimeSinceStartup; - _totalDistance = 0; - } - - if (IsInVehiclebay) - { - EvaluateUsage(e.Player.SteamPlayer); - - if (VehicleData.IsCrewSeat((byte)e.NewPassengerIndex)) - TransportTable.TryAdd(e.Player, e.Player.Position); - else - TransportTable.Remove(e.Player); - } - - ShowHUD(e.Player, (byte)e.NewPassengerIndex); - } - public void EvaluateUsage(SteamPlayer player) - { - byte currentSeat = player.player.movement.getSeat(); - bool isCrewSeat = VehicleData.IsCrewSeat(currentSeat); - - if (currentSeat != 0 && !isCrewSeat) - return; - - CSteamID s64 = player.playerID.steamID; - - if (!_timeEnteredTable.TryGetValue(s64, out DateTime start)) - return; - - double time = (DateTime.UtcNow - start).TotalSeconds; - if (!UsageTable.ContainsPlayer(s64)) - UsageTable.Add(s64, time); - else - UsageTable[s64] += time; - } - private int _totalFlaresLeft; - public void ReloadCountermeasures() - { - if (VehicleData != null) - { - _totalFlaresLeft = VehicleData.Type switch - { - VehicleType.AttackHeli => StartingFlaresAttackHeli, - VehicleType.TransportAir => StartingFlaresTransportHeli, - VehicleType.Jet => StartingFlaresJet, - _ => _totalFlaresLeft - }; - } - - UpdateHUDFlares(); - } - public void ReceiveMissileWarning() - { - if (_warningRoutine is not null) - StopCoroutine(_warningRoutine); - - _warningRoutine = StartCoroutine(WarningRoutine()); - } - private Coroutine _warningRoutine; - private IEnumerator WarningRoutine() - { - ToggleMissileWarning(true); - yield return new WaitForSeconds(1); - ToggleMissileWarning(false); - } - private void ToggleMissileWarning(bool enabled) - { - if (VehicleData is null) - return; - - for (byte i = 0; i < Vehicle.passengers.Length; i++) - { - Passenger passenger = Vehicle.passengers[i]; - if (passenger?.player == null || !VehicleData.IsCrewSeat(i)) - continue; - - _hud.MissileWarning.SetVisibility(passenger.player.transportConnection, enabled); - if (i == 0) - _hud.MissileWarningDriver.SetVisibility(passenger.player.transportConnection, enabled); - } - } - public void StartForceLoadSupplies(WarfarePlayer caller, SupplyType type, int amount) - { - ForceSupplyLoop = StartCoroutine(ForceSupplyLoopCoroutine(caller, type, amount)); - } - public bool TryStartAutoLoadSupplies() - { - if (_isResupplied || _autoSupplyLoop != null || VehicleData?.Trunk == null || Vehicle.trunkItems == null) - return false; - - _autoSupplyLoop = StartCoroutine(AutoSupplyLoop()); - return true; - } - private IEnumerator ForceSupplyLoopCoroutine(WarfarePlayer caller, SupplyType type, int amount) - { -#if false - ItemAsset? supplyAsset; - - if (type is SupplyType.Build) - TeamManager.GetFaction(Team).Build.TryGetAsset(out supplyAsset); - else if (type is SupplyType.Ammo) - TeamManager.GetFaction(Team).Ammo.TryGetAsset(out supplyAsset); - else - { - caller.SendChat(T.UnknownError); - yield break; - } - if (supplyAsset == null) - { - caller.SendChat(T.UnknownError); - yield break; - } - int addedNewCount = 0; - int loaderBreak = 0; - for (int i = 0; i < amount; i++) - { - if (!TeamManager.IsInAnyMain(Vehicle.transform.position)) - break; - if (!Vehicle.trunkItems.tryAddItem(new Item(supplyAsset.id, true))) - break; - - addedNewCount++; - loaderBreak++; - if (loaderBreak >= 3) - { - loaderBreak = 0; - F.TryTriggerSupplyEffect(type, Vehicle.transform.position); - yield return new WaitForSeconds(1); - - while (!(Vehicle.ReplicatedSpeed >= -1 && Vehicle.ReplicatedSpeed <= 1)) - yield return new WaitForSeconds(1); - } - } - - caller.SendChat(type is SupplyType.Build ? T.LoadCompleteBuild : T.LoadCompleteAmmo, addedNewCount); -#endif - ForceSupplyLoop = null; - yield break; - } - private IEnumerator AutoSupplyLoop() - { -#if false - TeamManager.GetFaction(Team).Build.TryGetAsset(out ItemAsset? build); - TeamManager.GetFaction(Team).Ammo.TryGetAsset(out ItemAsset? ammo); - - WarfarePlayer? driver = _playerService.GetOnlinePlayerOrNull(LastDriver); - - int loaderCount = 0; - - bool shouldMessagePlayer = false; - - if (VehicleData == null) - yield break; - - if (VehicleData.Trunk != null) - { - IReadOnlyList trunk = VehicleData.Trunk; - for (int i = 0; i < trunk.Count; i++) - { - ItemAsset? asset; - WarfareVehicleInfo.TrunkItem trunkItem = trunk[i]; - if (build != null && trunkItem.Item.Guid == build.GUID) - { - asset = build; - } - else if (ammo != null && trunkItem.Item.Guid == ammo.GUID) - { - asset = ammo; - } - else - { - asset = trunkItem.Item.GetAsset(); - } - - if (asset == null - || !Vehicle.trunkItems.checkSpaceEmpty(trunkItem.X, trunkItem.Y, asset.size_x, asset.size_y, trunkItem.Rotation) - || !TeamManager.IsInMain(Vehicle.lockedGroup.m_SteamID.GetTeam(), Vehicle.transform.position)) - { - continue; - } - - byte[] state; - if (trunkItem.State is { Length: > 0 }) - { - state = new byte[trunkItem.State.Length]; - Buffer.BlockCopy(trunkItem.State, 0, state, 0, state.Length); - } - else - { - state = Array.Empty(); - } - - Item item = new Item(asset.id, asset.amount, 100, state); - Vehicle.trunkItems.addItem(trunkItem.X, trunkItem.Y, trunkItem.Rotation, item); - loaderCount++; - - if (loaderCount < 3) - continue; - - loaderCount = 0; - F.TryTriggerSupplyEffect(asset == build ? SupplyType.Build : SupplyType.Ammo, Vehicle.transform.position); - shouldMessagePlayer = true; - - yield return new WaitForSeconds(1); - while (Vehicle.ReplicatedSpeed is < -1 or > 1) - yield return new WaitForSeconds(1); - } - } - - _isResupplied = true; - _autoSupplyLoop = null; - - if (shouldMessagePlayer && driver is { IsOnline: true } && F.IsInMain(driver.Position) && VehicleData != null) - { - TipService.TryGiveTip(driver, 120, T.TipLogisticsVehicleResupplied, VehicleData.Type); - } -#endif - yield break; - } - private IEnumerator QuotaLoop() - { -#if false - int tick = 0; - - while (true) - { - yield return new WaitForSeconds(3); - if (F.IsInMain(Vehicle.transform.position)) - { - //var ammoCrate = BarricadeUtility.CountBarricadesInRange(30, Vehicle.transform.position, Gamemode.Config.Barricades.AmmoCrateGUID, true).FirstOrDefault(); - if (Vehicle.ReplicatedSpeed is >= -1 and <= 1) - { - TryStartAutoLoadSupplies(); - } - } - else if (_isResupplied) - { - _isResupplied = false; - } - - tick++; - if (tick >= 20) - { - Quota += 0.5F; - tick = 0; - } - } -#endif - yield break; - } - - [UsedImplicitly] - private void Update() - { - float time = Time.time; - if (time - _lastCheck > 3f) - { - _lastCheck = time; - if (Vehicle.passengers[0]?.player == null) - { - return; - } - - Vector3 pos = transform.position; - if (pos == _lastPosInterval) - { - return; - } - - float old = _totalDistance; - _totalDistance += (_lastPosInterval - pos).magnitude; - // todo QuestManager.OnDistanceUpdated(LastDriver, _totalDistance, _totalDistance - old, this); - _lastPosInterval = pos; - } - } - - public void TryDropFlares() - { - if (Time.time - _timeLastFlareDrop < FlareCooldown || _totalFlaresLeft < 0) - return; - - _flareBurst = FlareBurstCount; - _timeLastFlareDrop = Time.time; - -#if false // todo - if (!VehicleBay.Config.CountermeasureEffectID.TryGetAsset(out EffectAsset? countermeasureEffect)) - return; - - for (byte seat = 0; seat < Vehicle.passengers.Length; seat++) - { - if (Vehicle.passengers[seat].player != null && VehicleData.IsCrewSeat(seat)) - EffectManager.sendUIEffect(countermeasureEffect.id, -1, Vehicle.passengers[seat].player.transportConnection, true); - } -#endif - } - - [UsedImplicitly] - private void FixedUpdate() - { - if (_totalFlaresLeft <= 0 || _flareBurst <= 0 || !(Time.time - _timeLastFlare > 0.2f)) - return; - - _timeLastFlare = Time.time; - -#if false // todo - if (!VehicleBay.Config.CountermeasureGUID.TryGetAsset(out VehicleAsset? countermeasureAsset)) - return; - - InteractableVehicle? countermeasureVehicle = VehicleManager.spawnVehicleV2(countermeasureAsset, Vehicle.transform.TransformPoint(0, -4, 0), Vehicle.transform.rotation); - - float sideforce = Random.Range(20f, 30f); - - if (_flareBurst % 2 == 0) sideforce = -sideforce; - - Rigidbody? rigidbody = countermeasureVehicle.transform.GetComponent(); - Vector3 velocity = Vehicle.transform.forward * Vehicle.ReplicatedSpeed * 0.9f - Vehicle.transform.up * 15 + Vehicle.transform.right * sideforce; - rigidbody.velocity = velocity; - - var countermeasure = countermeasureVehicle.gameObject.AddComponent(); - - Countermeasure.ActiveCountermeasures.Add(countermeasure); - - _totalFlaresLeft--; - _flareBurst--; - UpdateHUDFlares(); -#endif - } -} \ No newline at end of file diff --git a/UncreatedWarfare/Database/Manual/ManualMySqlProvider.cs b/UncreatedWarfare/Database/Manual/ManualMySqlProvider.cs index 7335eb31..cc6caa67 100644 --- a/UncreatedWarfare/Database/Manual/ManualMySqlProvider.cs +++ b/UncreatedWarfare/Database/Manual/ManualMySqlProvider.cs @@ -136,6 +136,7 @@ public async Task QueryAsync(string query, IReadOnlyList? paramete { await connection.OpenAsync(token); } + catch (OperationCanceledException) when (token.IsCancellationRequested) { throw; } catch (Exception ex) { if (ex is DbException) @@ -166,6 +167,7 @@ public async Task QueryAsync(string query, IReadOnlyList? paramete callback?.Invoke(dataReader); } } + catch (OperationCanceledException) when (token.IsCancellationRequested) { throw; } catch (Exception ex) { _logger.LogWarning("Query failed to execute: {0}.", QueryToString(query, parameters, index, length)); @@ -203,6 +205,7 @@ public async Task QueryAsync(string query, IReadOnlyList? paramete { await connection.OpenAsync(token); } + catch (OperationCanceledException) when (token.IsCancellationRequested) { throw; } catch (Exception ex) { if (ex is DbException) @@ -234,6 +237,7 @@ public async Task QueryAsync(string query, IReadOnlyList? paramete break; } } + catch (OperationCanceledException) when (token.IsCancellationRequested) { throw; } catch (Exception ex) { _logger.LogWarning("Query failed to execute: {0}.", QueryToString(query, parameters, index, length)); @@ -263,6 +267,7 @@ public async Task NonQueryAsync(string query, IReadOnlyList? param { await connection.OpenAsync(token); } + catch (OperationCanceledException) when (token.IsCancellationRequested) { throw; } catch (Exception ex) { if (ex is DbException) @@ -284,6 +289,7 @@ public async Task NonQueryAsync(string query, IReadOnlyList? param { return await command.ExecuteNonQueryAsync(token).ConfigureAwait(false); } + catch (OperationCanceledException) when (token.IsCancellationRequested) { throw; } catch (Exception ex) { _logger.LogWarning("Command failed to execute: {0}.", QueryToString(query, parameters, index, length)); diff --git a/UncreatedWarfare/Deaths/DeathTracker.cs b/UncreatedWarfare/Deaths/DeathTracker.cs index e6e588bb..ba9dadf2 100644 --- a/UncreatedWarfare/Deaths/DeathTracker.cs +++ b/UncreatedWarfare/Deaths/DeathTracker.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Uncreated.Warfare.Components; using Uncreated.Warfare.Configuration; using Uncreated.Warfare.Events.Components; using Uncreated.Warfare.Events.Models.Players; @@ -14,6 +13,7 @@ using Uncreated.Warfare.Players.Management; using Uncreated.Warfare.Services; using Uncreated.Warfare.Util; +using Uncreated.Warfare.Vehicles.WarfareVehicles; namespace Uncreated.Warfare.Deaths; public class DeathTracker : IHostedService @@ -41,6 +41,9 @@ UniTask IHostedService.StartAsync(CancellationToken token) // not using event dispatcher for this because this class is responsible for dispatching the player died event. PlayerLife.onPlayerDied += OnPlayerDied; + UseableGun.onProjectileSpawned += UseableGunOnProjectileSpawned; + UseableThrowable.onThrowableSpawned += OnThrowableSpawned; + UseableConsumeable.onConsumePerformed += UseableConsumeableOnConsumePerformed; EDeathCause[] causes = Enum.GetValues(typeof(EDeathCause)).Cast().ToArray(); if (causes.Contains(InEnemyMainDeathCause)) @@ -60,11 +63,66 @@ UniTask IHostedService.StartAsync(CancellationToken token) } } - UseableThrowable.onThrowableSpawned += OnThrowableSpawned; return UniTask.CompletedTask; } + UniTask IHostedService.StopAsync(CancellationToken token) + { + PlayerLife.onPlayerDied -= OnPlayerDied; + UseableGun.onProjectileSpawned -= UseableGunOnProjectileSpawned; + UseableThrowable.onThrowableSpawned -= OnThrowableSpawned; + UseableConsumeable.onConsumePerformed -= UseableConsumeableOnConsumePerformed; + + return UniTask.CompletedTask; + } + + private static void UseableConsumeableOnConsumePerformed(Player instigatingPlayer, ItemConsumeableAsset consumeableAsset) + { + PlayerDeathTrackingComponent deathTrackingComponent = PlayerDeathTrackingComponent.GetOrAdd(instigatingPlayer); + + deathTrackingComponent.LastExplosiveConsumed = null; + + if (consumeableAsset.IsExplosive) + { + deathTrackingComponent.LastExplosiveConsumed = AssetLink.Create(consumeableAsset); + } + else if (consumeableAsset.virus != 0) + { + deathTrackingComponent.LastInfectionItemConsumed = AssetLink.Create(consumeableAsset); + } + } + + private static void UseableGunOnProjectileSpawned(UseableGun sender, GameObject projectile) + { + PlayerDeathTrackingComponent deathTrackingComponent = PlayerDeathTrackingComponent.GetOrAdd(sender.player); + + ItemGunAsset gun = sender.equippedGunAsset; + + deathTrackingComponent.LastRocketShot = AssetLink.Create(gun); + + InteractableVehicle? vehicle = sender.player.movement.getVehicle(); + if (vehicle is null) + { + deathTrackingComponent.LastRocketShotFromVehicle = null; + return; + } + + byte seat = sender.player.movement.getSeat(); + if (seat >= vehicle.passengers.Length || vehicle.passengers[seat].turret == null || !deathTrackingComponent.LastRocketShot.MatchId(vehicle.passengers[seat].turret.itemID)) + { + deathTrackingComponent.LastRocketShotFromVehicle = null; + return; + } + + deathTrackingComponent.LastRocketShotFromVehicle = vehicle; + + if (seat != 0 && vehicle.isDriven) + { + deathTrackingComponent.LastRocketShotFromVehicleDriverAssist = vehicle.passengers[0].player.playerID.steamID; + } + } + private void OnThrowableSpawned(UseableThrowable useable, GameObject throwable) { ThrowableComponent comp = throwable.AddComponent(); @@ -81,13 +139,6 @@ private void OnThrowableSpawned(UseableThrowable useable, GameObject throwable) comp.Team = player?.Team ?? Team.NoTeam; } - UniTask IHostedService.StopAsync(CancellationToken token) - { - PlayerLife.onPlayerDied -= OnPlayerDied; - - return UniTask.CompletedTask; - } - private void OnPlayerDied(PlayerLife sender, EDeathCause cause, ELimb limb, CSteamID instigator) { WarfarePlayer dead = _playerService.GetOnlinePlayer(sender.player); @@ -442,13 +493,13 @@ internal void FillArgs(WarfarePlayer dead, EDeathCause cause, ELimb limb, CSteam break; } - VehicleComponent vComp = killerData.LastVehicleExploded; + WarfareVehicle vComp = killerData.LastVehicleExploded; e.MessageFlags |= DeathFlags.Item; e.PrimaryAsset = AssetLink.Create(vComp.Vehicle.asset); - if (vComp.LastItem != Guid.Empty && Assets.find(vComp.LastItem) is ItemAsset lastVehicleHitItem) + if (vComp.DamageTracker.LatestInstigatorWeapon != null) { - e.SecondaryAsset = AssetLink.Create(lastVehicleHitItem); + e.SecondaryAsset = AssetLink.Create(vComp.DamageTracker.LatestInstigatorWeapon); e.MessageFlags |= DeathFlags.Item2; } @@ -501,11 +552,12 @@ internal void FillArgs(WarfarePlayer dead, EDeathCause cause, ELimb limb, CSteam { e.PrimaryAsset = AssetLink.Create(lastRocketShot); e.MessageFlags |= DeathFlags.Item; - e.TurretVehicleOwner = killerData.LastRocketShotFromVehicle; + InteractableVehicle? turretOwner = killerData.LastRocketShotFromVehicle; - if (e.TurretVehicleOwner == null) + if (turretOwner is null) break; + e.TurretVehicleOwner = AssetLink.Create(turretOwner.asset); e.SecondaryAsset = e.TurretVehicleOwner; e.MessageFlags |= DeathFlags.Item2; @@ -708,7 +760,8 @@ private void GetItems(EDeathCause cause, ulong killerId, Player? killer, PlayerD if (killerData != null && killerData.LastVehicleExploded != null) { item1 = AssetLink.Create(killerData.LastVehicleExploded.Vehicle.asset); - item2 = AssetLink.Create(killerData.LastVehicleExploded.LastItem); + if (killerData.LastVehicleExploded.DamageTracker.LatestInstigatorWeapon != null) + item2 = AssetLink.Create(killerData.LastVehicleExploded.DamageTracker.LatestInstigatorWeapon); } else item1 = null; break; diff --git a/UncreatedWarfare/Deaths/PlayerDeathTrackingComponent.cs b/UncreatedWarfare/Deaths/PlayerDeathTrackingComponent.cs index 05778330..ab7f27f1 100644 --- a/UncreatedWarfare/Deaths/PlayerDeathTrackingComponent.cs +++ b/UncreatedWarfare/Deaths/PlayerDeathTrackingComponent.cs @@ -1,10 +1,10 @@ -using System; +using System; using System.Collections.Generic; -using Uncreated.Warfare.Components; using Uncreated.Warfare.Configuration; using Uncreated.Warfare.Events.Components; using Uncreated.Warfare.Events.Models.Players; using Uncreated.Warfare.Util; +using Uncreated.Warfare.Vehicles.WarfareVehicles; namespace Uncreated.Warfare.Deaths; internal class PlayerDeathTrackingComponent : MonoBehaviour @@ -64,7 +64,7 @@ internal class PlayerDeathTrackingComponent : MonoBehaviour /// /// The vehicle from which was shot (only if it's a turret). /// - internal IAssetLink? LastRocketShotFromVehicle { get; set; } + internal InteractableVehicle? LastRocketShotFromVehicle { get; set; } /// /// The driver of the vehicle from which was shot (only if it's a turret). @@ -92,9 +92,9 @@ internal class PlayerDeathTrackingComponent : MonoBehaviour internal ThrowableComponent? ThrowableTrapTrigger { get; set; } /// - /// The component of the last vehicle this player caused the explosion for. + /// The component of the last vehicle this player caused the explosion for. /// - internal VehicleComponent? LastVehicleExploded { get; set; } + internal WarfareVehicle? LastVehicleExploded { get; set; } /// /// List of all throwables that this player has thrown which are pending being cleaned up by the game. diff --git a/UncreatedWarfare/Events/DispatchHandlers/VehicleDispatches.cs b/UncreatedWarfare/Events/DispatchHandlers/VehicleDispatches.cs index f2f4c1a7..65874443 100644 --- a/UncreatedWarfare/Events/DispatchHandlers/VehicleDispatches.cs +++ b/UncreatedWarfare/Events/DispatchHandlers/VehicleDispatches.cs @@ -1,6 +1,5 @@ using SDG.NetTransport; using System; -using Uncreated.Warfare.Components; using Uncreated.Warfare.Configuration; using Uncreated.Warfare.Events.Models.Vehicles; using Uncreated.Warfare.Events.Patches; @@ -29,7 +28,7 @@ private void VehicleManagerOnToggleVehicleLockRequested(InteractableVehicle vehi if (player is null) return; - WarfareVehicle warfareVehicle = vehicle.transform.GetComponent().WarfareVehicle; + WarfareVehicle warfareVehicle = _vehicleService.GetVehicle(vehicle); ChangeVehicleLockRequested args = new ChangeVehicleLockRequested { @@ -44,11 +43,7 @@ private void VehicleManagerOnToggleVehicleLockRequested(InteractableVehicle vehi if (args.Vehicle == null || args.Vehicle.Vehicle.isDead || args.Vehicle.Vehicle.isLocked == isLocking) return; - if (args.Vehicle.Vehicle.TryGetComponent(out VehicleComponent vehicleComponent)) // todo: remove old VehicleComponent - { - vehicleComponent.LastLocker = args.Player.Steam64; - } - + args.Vehicle.DamageTracker.LastLockingPlayer = null; VehicleManager.ServerSetVehicleLock(args.Vehicle.Vehicle, args.Player.Steam64, args.Player.GroupId, args.IsLocking); _firemodeEffect ??= AssetLink.Create(new Guid("bc41e0feaebe4e788a3612811b8722d3")); @@ -73,11 +68,8 @@ private void VehicleManagerOnToggleVehicleLockRequested(InteractableVehicle vehi if (!shouldallow) return; - - if (vehicle.TryGetComponent(out VehicleComponent vehicleComponent)) - { - vehicleComponent.LastLocker = player.Steam64; - } + + args.Vehicle.DamageTracker.LastLockingPlayer = player; } /// @@ -85,8 +77,10 @@ private void VehicleManagerOnToggleVehicleLockRequested(InteractableVehicle vehi /// private void VehicleManagerOnToggledVehicleLock(InteractableVehicle vehicle) { - WarfarePlayer? player = null; - WarfareVehicle warfareVehicle = vehicle.transform.GetComponent().WarfareVehicle; + WarfareVehicle warfareVehicle = _vehicleService.GetVehicle(vehicle); + + WarfarePlayer? player = warfareVehicle.DamageTracker.LastLockingPlayer; + warfareVehicle.DamageTracker.LastLockingPlayer = null; if (vehicle.lockedOwner.GetEAccountType() == EAccountType.k_EAccountTypeIndividual) { @@ -107,7 +101,7 @@ private void VehicleManagerOnToggledVehicleLock(InteractableVehicle vehicle) /// private void VehicleManagerOnVehicleExploded(InteractableVehicle vehicle) { - WarfareVehicle warfareVehicle = vehicle.transform.GetComponent().WarfareVehicle; + WarfareVehicle warfareVehicle = _vehicleService.GetVehicle(vehicle); ITeamManager? teamManager = _warfare.IsLayoutActive() ? _warfare.ScopedProvider.Resolve>() : null; @@ -159,7 +153,7 @@ private void VehicleManagerOnPassengerExitRequested(Player unturnedPlayer, Inter { WarfarePlayer player = _playerService.GetOnlinePlayer(unturnedPlayer); - WarfareVehicle warfareVehicle = vehicle.transform.GetComponent().WarfareVehicle; + WarfareVehicle warfareVehicle = _vehicleService.GetVehicle(vehicle); byte seat = player.UnturnedPlayer.movement.getSeat(); @@ -197,7 +191,7 @@ private void VehicleManagerOnSwapSeatRequested(Player unturnedPlayer, Interactab WarfarePlayer player = _playerService.GetOnlinePlayer(unturnedPlayer); - WarfareVehicle warfareVehicle = vehicle.transform.GetComponent().WarfareVehicle; + WarfareVehicle warfareVehicle = _vehicleService.GetVehicle(vehicle); VehicleSwapSeatRequested args = new VehicleSwapSeatRequested { @@ -221,7 +215,7 @@ private void VehicleManagerOnSwapSeatRequested(Player unturnedPlayer, Interactab private void OnDamageVehicleRequested(CSteamID instigatorsSteamID, InteractableVehicle vehicle, ref ushort pendingTotalDamage, ref bool canRepair, ref bool shouldAllow, EDamageOrigin damageOrigin) { - WarfareVehicle warfareVehicle = vehicle.transform.GetComponent().WarfareVehicle; + WarfareVehicle warfareVehicle = _vehicleService.GetVehicle(vehicle); DamageVehicleRequested args = new DamageVehicleRequested { @@ -239,7 +233,7 @@ private void OnDamageVehicleRequested(CSteamID instigatorsSteamID, InteractableV private void VehicleManagerOnPreDestroyVehicle(InteractableVehicle vehicle) { - WarfareVehicle warfareVehicle = vehicle.transform.GetComponent().WarfareVehicle; + WarfareVehicle warfareVehicle = _vehicleService.GetVehicle(vehicle); VehicleDespawned args = new VehicleDespawned { diff --git a/UncreatedWarfare/Events/EventDispatcher.cs b/UncreatedWarfare/Events/EventDispatcher.cs index 95a14bf3..bd846ef3 100644 --- a/UncreatedWarfare/Events/EventDispatcher.cs +++ b/UncreatedWarfare/Events/EventDispatcher.cs @@ -21,6 +21,7 @@ using Uncreated.Warfare.Services; using Uncreated.Warfare.Util; using Uncreated.Warfare.Util.List; +using Uncreated.Warfare.Vehicles; using Service = Autofac.Core.Service; namespace Uncreated.Warfare.Events; @@ -42,6 +43,7 @@ public partial class EventDispatcher : IHostedService, IDisposable private readonly IPlayerService _playerService; private readonly CancellationToken _unloadToken; private readonly ILogger _logger; + private readonly VehicleService _vehicleService; private IServiceProvider? _scopedServiceProvider; private readonly ILoggerFactory _loggerFactory; private WarfareTimeComponent _timeComponent; @@ -69,6 +71,8 @@ public EventDispatcher(IServiceProvider serviceProvider) _playerService = serviceProvider.GetRequiredService(); + _vehicleService = serviceProvider.GetRequiredService(); + _timeComponent = serviceProvider.GetRequiredService(); _warfare = serviceProvider.GetRequiredService(); diff --git a/UncreatedWarfare/Events/Models/Vehicles/EnterVehicle.cs b/UncreatedWarfare/Events/Models/Vehicles/EnterVehicle.cs index 51b78dff..c899dbe2 100644 --- a/UncreatedWarfare/Events/Models/Vehicles/EnterVehicle.cs +++ b/UncreatedWarfare/Events/Models/Vehicles/EnterVehicle.cs @@ -1,4 +1,3 @@ -using Uncreated.Warfare.Components; using Uncreated.Warfare.Vehicles.WarfareVehicles; namespace Uncreated.Warfare.Events.Models.Vehicles; diff --git a/UncreatedWarfare/Events/Models/Vehicles/VehiclePreDamaged.cs b/UncreatedWarfare/Events/Models/Vehicles/VehiclePreDamaged.cs index c3b94b8b..9ffa6e12 100644 --- a/UncreatedWarfare/Events/Models/Vehicles/VehiclePreDamaged.cs +++ b/UncreatedWarfare/Events/Models/Vehicles/VehiclePreDamaged.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Text; using Uncreated.Warfare.Vehicles.WarfareVehicles; namespace Uncreated.Warfare.Events.Models.Vehicles; @@ -8,7 +5,6 @@ namespace Uncreated.Warfare.Events.Models.Vehicles; /// /// Event listener args which handles a patch listening just before a vehicle is damage (before the InteractableVehicle's health is changed). /// -/// public class VehiclePreDamaged { public required WarfareVehicle Vehicle { get; init; } diff --git a/UncreatedWarfare/Events/Patches/InteractableTrapOnTriggerEnter.cs b/UncreatedWarfare/Events/Patches/InteractableTrapOnTriggerEnter.cs index 5acc5d90..91430552 100644 --- a/UncreatedWarfare/Events/Patches/InteractableTrapOnTriggerEnter.cs +++ b/UncreatedWarfare/Events/Patches/InteractableTrapOnTriggerEnter.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Reflection; -using Uncreated.Warfare.Components; using Uncreated.Warfare.Configuration; using Uncreated.Warfare.Deaths; using Uncreated.Warfare.Events.Components; @@ -14,6 +13,8 @@ using Uncreated.Warfare.Patches; using Uncreated.Warfare.Players; using Uncreated.Warfare.Players.Management; +using Uncreated.Warfare.Vehicles; +using Uncreated.Warfare.Vehicles.WarfareVehicles; namespace Uncreated.Warfare.Events.Patches; @@ -121,12 +122,10 @@ private static bool Prefix(Collider other, InteractableTrap __instance, float __ if (playerTriggerer == null) { - if (vehicle.TryGetComponent(out VehicleComponent comp2)) - playerTriggerer = playerService.GetOnlinePlayerOrNull(comp2.LastDriver); + WarfareVehicle wVehicle = WarfareModule.Singleton.ServiceProvider.Resolve().GetVehicle(vehicle); + playerTriggerer = wVehicle.TranportTracker.LastKnownDriver.HasValue ? playerService.GetOnlinePlayerOrNull(wVehicle.TranportTracker.LastKnownDriver.Value) : null; if (playerTriggerer == null) - { return false; - } } triggerTeam = playerTriggerer.Team; @@ -229,6 +228,8 @@ private static void TriggerTrap(TriggerTrapRequested args) PlayerDeathTrackingComponent? ownerData = null, triggererData = null; + CSteamID instigator = new CSteamID(args.ServersideData.owner); + if (args.BarricadeOwner is { IsOnline: true }) { ownerData = PlayerDeathTrackingComponent.GetOrAdd(args.BarricadeOwner.UnturnedPlayer); @@ -245,7 +246,7 @@ private static void TriggerTrap(TriggerTrapRequested args) } Vector3 position = args.ServersideData.point; - DamageTool.explode(new ExplosionParameters(position, args.ExplosiveRange, EDeathCause.LANDMINE, new CSteamID(args.ServersideData.owner)) + DamageTool.explode(new ExplosionParameters(position, args.ExplosiveRange, EDeathCause.LANDMINE, instigator) { playerDamage = args.PlayerDamage, zombieDamage = args.ZombieDamage, diff --git a/UncreatedWarfare/Events/Patches/ProviderPlayerJoiningEvents.cs b/UncreatedWarfare/Events/Patches/ProviderPlayerJoiningEvents.cs index 9bbba2c6..d0108cb0 100644 --- a/UncreatedWarfare/Events/Patches/ProviderPlayerJoiningEvents.cs +++ b/UncreatedWarfare/Events/Patches/ProviderPlayerJoiningEvents.cs @@ -14,6 +14,7 @@ using Uncreated.Warfare.Players.PendingTasks; using Uncreated.Warfare.Players.Saves; using Uncreated.Warfare.Translations.Languages; +using Uncreated.Warfare.Util; namespace Uncreated.Warfare.Events.Patches; @@ -191,6 +192,13 @@ private static async Task InvokePrePlayerConnectAsync(PlayerPending args, Player isCancelled = true; } } + catch (OperationCanceledException ex) + { + if (string.IsNullOrEmpty(args.RejectReason)) + args.RejectReason = "Connection cancelled by player task."; + isCancelled = true; + logger.LogWarning(ex, "Cancellation executing player tasks for player {0}.", args.Steam64); + } catch (Exception ex) { isCancelled = true; diff --git a/UncreatedWarfare/Events/Patches/VehicleOnPreDamage.cs b/UncreatedWarfare/Events/Patches/VehicleOnPreDamage.cs index 9c36ad10..4e390ecd 100644 --- a/UncreatedWarfare/Events/Patches/VehicleOnPreDamage.cs +++ b/UncreatedWarfare/Events/Patches/VehicleOnPreDamage.cs @@ -1,4 +1,4 @@ -using DanielWillett.ReflectionTools.Formatting; +using DanielWillett.ReflectionTools.Formatting; using DanielWillett.ReflectionTools; using System; using System.Collections.Generic; @@ -101,28 +101,32 @@ private static void PreVehicleDamageInvoker(InteractableVehicle vehicle, ushort if (onlineInstigator != null && onlineInstigator.UnturnedPlayer.movement.getVehicle() != null) instigatorVehicle = onlineInstigator.UnturnedPlayer.movement.getVehicle().transform.GetComponent().WarfareVehicle; - if (onlineInstigator != null) + // landmines get special treatment in VehicleDamageTrackerItemTweak + if (damageOrigin != EDamageOrigin.Trap_Explosion) { - if (instigatorVehicle != null) - warfareVehicle.DamageTracker.RecordDamage(onlineInstigator, instigatorVehicle, pendingDamage, damageOrigin); + if (onlineInstigator != null) + { + if (instigatorVehicle != null) + warfareVehicle.DamageTracker.RecordDamage(onlineInstigator, instigatorVehicle, pendingDamage, damageOrigin); + else + warfareVehicle.DamageTracker.RecordDamage(onlineInstigator, pendingDamage, damageOrigin); + } + else if (instigatorId.GetEAccountType() == EAccountType.k_EAccountTypeIndividual) + warfareVehicle.DamageTracker.RecordDamage(instigatorId, pendingDamage, damageOrigin); else - warfareVehicle.DamageTracker.RecordDamage(onlineInstigator, pendingDamage, damageOrigin); + warfareVehicle.DamageTracker.RecordDamage(damageOrigin); } - else if (instigatorId != default) - warfareVehicle.DamageTracker.RecordDamage(instigatorId, pendingDamage, damageOrigin); - else - warfareVehicle.DamageTracker.RecordDamage(damageOrigin); VehiclePreDamaged args = new VehiclePreDamaged { Vehicle = warfareVehicle, PendingDamage = pendingDamage, CanRepair = canRepair, - InstantaneousInstigator = instigatorId != default ? instigatorId : null, + InstantaneousInstigator = instigatorId.GetEAccountType() == EAccountType.k_EAccountTypeIndividual ? instigatorId : null, LastKnownInstigator = warfareVehicle.DamageTracker.LastKnownDamageInstigator, InstantaneousDamageOrigin = damageOrigin }; - _ = WarfareModule.EventDispatcher.DispatchEventAsync(args); + _ = WarfareModule.EventDispatcher.DispatchEventAsync(args, allowAsync: false); } } diff --git a/UncreatedWarfare/FOBs/Construction/RepairStation.cs b/UncreatedWarfare/FOBs/Construction/RepairStation.cs index bbc73021..ebae4825 100644 --- a/UncreatedWarfare/FOBs/Construction/RepairStation.cs +++ b/UncreatedWarfare/FOBs/Construction/RepairStation.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using Uncreated.Warfare.Buildables; using Uncreated.Warfare.Components; @@ -9,6 +9,7 @@ using Uncreated.Warfare.Layouts.Teams; using Uncreated.Warfare.Util; using Uncreated.Warfare.Util.Timing; +using Uncreated.Warfare.Vehicles.WarfareVehicles; namespace Uncreated.Warfare.FOBs.Construction; public class RepairStation : IBuildableFobEntity @@ -90,9 +91,9 @@ private void Repair(InteractableVehicle vehicle) if (vehicle.health + amount >= vehicle.asset.health) { newHealth = vehicle.asset.health; - if (vehicle.transform.TryGetComponent(out VehicleComponent c)) + if (vehicle.transform.TryGetComponent(out WarfareVehicleComponent c)) { - c.DamageTable.Clear(); + c.WarfareVehicle.DamageTracker.ClearDamage(); } } diff --git a/UncreatedWarfare/FOBs/Deployment/DeploySettings.cs b/UncreatedWarfare/FOBs/Deployment/DeploySettings.cs index d223f64a..33a3074a 100644 --- a/UncreatedWarfare/FOBs/Deployment/DeploySettings.cs +++ b/UncreatedWarfare/FOBs/Deployment/DeploySettings.cs @@ -1,4 +1,5 @@ -using System; +using System; +using Uncreated.Warfare.Players; namespace Uncreated.Warfare.FOBs.Deployment; public struct DeploySettings @@ -57,7 +58,7 @@ public DeploySettings() { } /// /// Optionally change the cooldown type from . /// - public CooldownType? CooldownType { get; set; } = Warfare.CooldownType.Deploy; + public CooldownType? CooldownType { get; set; } = Players.CooldownType.Deploy; /// /// If chat interaction with the player should be used for the initial check. diff --git a/UncreatedWarfare/FOBs/Deployment/DeploymentTranslations.cs b/UncreatedWarfare/FOBs/Deployment/DeploymentTranslations.cs index 48086011..900e0253 100644 --- a/UncreatedWarfare/FOBs/Deployment/DeploymentTranslations.cs +++ b/UncreatedWarfare/FOBs/Deployment/DeploymentTranslations.cs @@ -1,4 +1,5 @@ -using Uncreated.Warfare.Translations; +using Uncreated.Warfare.Players; +using Uncreated.Warfare.Translations; using Uncreated.Warfare.Translations.Addons; using Uncreated.Warfare.Zones; diff --git a/UncreatedWarfare/FOBs/SupplyCrates/AmmoTranslations.cs b/UncreatedWarfare/FOBs/SupplyCrates/AmmoTranslations.cs index dec26d9c..0e640f0f 100644 --- a/UncreatedWarfare/FOBs/SupplyCrates/AmmoTranslations.cs +++ b/UncreatedWarfare/FOBs/SupplyCrates/AmmoTranslations.cs @@ -1,4 +1,5 @@ -using Uncreated.Warfare.Interaction.Commands; +using Uncreated.Warfare.Interaction.Commands; +using Uncreated.Warfare.Players; using Uncreated.Warfare.Players.Management; using Uncreated.Warfare.Translations; diff --git a/UncreatedWarfare/Kits/Items/HotkeyPlayerComponent.cs b/UncreatedWarfare/Kits/Items/HotkeyPlayerComponent.cs index 558532a8..631b646e 100644 --- a/UncreatedWarfare/Kits/Items/HotkeyPlayerComponent.cs +++ b/UncreatedWarfare/Kits/Items/HotkeyPlayerComponent.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; using Uncreated.Warfare.Events.Models; @@ -7,6 +7,7 @@ using Uncreated.Warfare.Players; using Uncreated.Warfare.Players.Management; using Uncreated.Warfare.Teams; +using Uncreated.Warfare.Util; namespace Uncreated.Warfare.Kits.Items; diff --git a/UncreatedWarfare/Moderation/ModerationEventHandlers.cs b/UncreatedWarfare/Moderation/ModerationEventHandlers.cs index 2d2641ee..086b15f5 100644 --- a/UncreatedWarfare/Moderation/ModerationEventHandlers.cs +++ b/UncreatedWarfare/Moderation/ModerationEventHandlers.cs @@ -247,8 +247,10 @@ async UniTask IAsyncEventListener.HandleEventAsync(PlayerPending if (!e.PendingPlayer.transportConnection.TryGetIPv4Address(out uint ip)) { +#if RELEASE if (!Provider.configData.Server.Use_FakeIP) e.Reject("Unable to get IPv4 address. This may be caused by connecting in an unusual way like a connection code."); +#endif } StringBuilder? queryBuilder = new StringBuilder(); diff --git a/UncreatedWarfare/Moderation/PlayerModerationCacheComponent.cs b/UncreatedWarfare/Moderation/PlayerModerationCacheComponent.cs index b2cba958..d9902b7a 100644 --- a/UncreatedWarfare/Moderation/PlayerModerationCacheComponent.cs +++ b/UncreatedWarfare/Moderation/PlayerModerationCacheComponent.cs @@ -30,7 +30,7 @@ void IPlayerComponent.Init(IServiceProvider serviceProvider, bool isOnJoin) { await RefreshActiveMute(); } - catch (OperationCanceledException) { throw; } + catch (OperationCanceledException) { throw; } catch (Exception ex) { serviceProvider.GetRequiredService>().LogError(ex, "Error fetching mute info."); diff --git a/UncreatedWarfare/Patches/VehicleExplodeAddInstigatorPatch.cs b/UncreatedWarfare/Patches/VehicleExplodeAddInstigatorPatch.cs index a54db980..caaeca34 100644 --- a/UncreatedWarfare/Patches/VehicleExplodeAddInstigatorPatch.cs +++ b/UncreatedWarfare/Patches/VehicleExplodeAddInstigatorPatch.cs @@ -1,10 +1,12 @@ -using DanielWillett.ReflectionTools; +using DanielWillett.ReflectionTools; using DanielWillett.ReflectionTools.Formatting; using HarmonyLib; +using System; using System.Reflection; -using Uncreated.Warfare.Components; using Uncreated.Warfare.Deaths; using Uncreated.Warfare.Util; +using Uncreated.Warfare.Vehicles; +using Uncreated.Warfare.Vehicles.WarfareVehicles; namespace Uncreated.Warfare.Patches; @@ -47,10 +49,9 @@ void IHarmonyPatch.Unpatch(ILogger logger, Harmony patcher) /// private static bool Prefix(InteractableVehicle __instance) { - if (!__instance.TryGetComponent(out VehicleComponent vehicleData)) - return true; - - EDamageOrigin lastDamageType = vehicleData.LastDamageOrigin; + WarfareVehicle vehicle = WarfareModule.Singleton.ServiceProvider.Resolve().GetVehicle(__instance); + + EDamageOrigin lastDamageType = vehicle.DamageTracker.LatestDamageCause.GetValueOrDefault(EDamageOrigin.Unknown); if (lastDamageType == EDamageOrigin.Unknown) return true; @@ -60,7 +61,7 @@ private static bool Prefix(InteractableVehicle __instance) // no one at fault default: case EDamageOrigin.VehicleDecay: - instigator2 = CSteamID.Nil; + instigator2 = default; break; // blame driver @@ -79,13 +80,13 @@ private static bool Prefix(InteractableVehicle __instance) if (__instance.passengers[0].player != null) { instigator2 = __instance.passengers[0].player.playerID.steamID; - vehicleData.LastInstigator = instigator2.m_SteamID; + vehicle.DamageTracker.RecordDamage(instigator2, vehicle.Vehicle.health, lastDamageType); } // no current driver, check if the last driver exited the vehicle within the last 30 seconds - else if (vehicleData.LastDriver != 0 && Time.realtimeSinceStartup - vehicleData.LastDriverTime <= 30f) + else if (vehicle.TranportTracker.LastKnownDriver.HasValue && (DateTime.UtcNow - vehicle.TranportTracker.LastKnownDriverExitTime.GetValueOrDefault()).TotalSeconds <= 30f) { - instigator2 = new CSteamID(vehicleData.LastDriver); - vehicleData.LastInstigator = instigator2.m_SteamID; + instigator2 = vehicle.TranportTracker.LastKnownDriver.Value; + vehicle.DamageTracker.RecordDamage(vehicle.TranportTracker.LastKnownDriver.Value, vehicle.Vehicle.health, lastDamageType); } else instigator2 = CSteamID.Nil; } @@ -104,7 +105,7 @@ private static bool Prefix(InteractableVehicle __instance) case EDamageOrigin.Bullet_Explosion: case EDamageOrigin.Food_Explosion: case EDamageOrigin.Trap_Explosion: - instigator2 = new CSteamID(vehicleData.LastInstigator); + instigator2 = vehicle.DamageTracker.LastKnownDamageInstigator.GetValueOrDefault(); break; } @@ -115,7 +116,7 @@ private static bool Prefix(InteractableVehicle __instance) if (player != null) { data = PlayerDeathTrackingComponent.GetOrAdd(player); - data.LastVehicleExploded = vehicleData; + data.LastVehicleExploded = vehicle; } } diff --git a/UncreatedWarfare/CooldownManager.cs b/UncreatedWarfare/Players/CooldownManager.cs similarity index 98% rename from UncreatedWarfare/CooldownManager.cs rename to UncreatedWarfare/Players/CooldownManager.cs index b17847c6..1232fcda 100644 --- a/UncreatedWarfare/CooldownManager.cs +++ b/UncreatedWarfare/Players/CooldownManager.cs @@ -4,7 +4,6 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; -using Uncreated.Warfare.Players; using Uncreated.Warfare.Players.Management; using Uncreated.Warfare.Services; using Uncreated.Warfare.Translations; @@ -13,7 +12,7 @@ using Uncreated.Warfare.Translations.ValueFormatters; using Uncreated.Warfare.Util; -namespace Uncreated.Warfare; +namespace Uncreated.Warfare.Players; public class CooldownManager : IHostedService, ILayoutHostedService { @@ -208,7 +207,7 @@ public override string ToString() } /// Translated . - public static readonly SpecialFormat FormatName = new SpecialFormat("Type (" + nameof(Warfare.CooldownType) + ")", "n"); + public static readonly SpecialFormat FormatName = new SpecialFormat("Type (" + nameof(Players.CooldownType) + ")", "n"); /// 3 hours and 4 minutes public static readonly SpecialFormat FormatTimeLong = new SpecialFormat("Long Time", "tl1"); diff --git a/UncreatedWarfare/Players/PendingTasks/UpdateUserDataTask.cs b/UncreatedWarfare/Players/PendingTasks/UpdateUserDataTask.cs index 6f73509c..a047c3d0 100644 --- a/UncreatedWarfare/Players/PendingTasks/UpdateUserDataTask.cs +++ b/UncreatedWarfare/Players/PendingTasks/UpdateUserDataTask.cs @@ -1,12 +1,11 @@ -using SDG.NetTransport; +using SDG.NetTransport; using System; -using System.Collections.Generic; -using System.Linq; using Uncreated.Warfare.Database.Abstractions; using Uncreated.Warfare.Events.Models.Players; using Uncreated.Warfare.Models.Users; using Uncreated.Warfare.Moderation; using Uncreated.Warfare.Players.Management; +using Uncreated.Warfare.Util; namespace Uncreated.Warfare.Players.PendingTasks; @@ -54,21 +53,25 @@ private bool TryUpdateIdentifiers(PlayerPending e, WarfareUserData data, IDbCont return false; } - if (!connection.TryGetIPv4Address(out uint ipv4)) + bool hasIpv4 = false; + uint ipv4 = 0; + if (!Provider.configData.Server.Use_FakeIP && !(hasIpv4 = connection.TryGetIPv4Address(out ipv4))) { +#if !DEBUG // allow server codes for testing e.RejectReason = "No valid IPv4 address."; return false; +#else + WarfareModule.Singleton.ServiceProvider.Resolve>().LogWarning("Player {0} connecting without an IP address (probably via Server Code).", e.Steam64); +#endif } - _isRemotePlay = _moderationSql.IsRemotePlay(ipv4); + _isRemotePlay = hasIpv4 && _moderationSql.IsRemotePlay(ipv4); - IEnumerable hwidsEnum = e.PendingPlayer.playerID.GetHwids(); - if (hwidsEnum is not byte[][] hwids) - hwids = hwidsEnum.ToArray(); + byte[][] hwids = e.PendingPlayer.playerID.GetHwids().ToArrayFast(); if (hwids.Length is not 2 and not 3 || Array.Exists(hwids, x => x.Length != HWID.Size)) { - e.RejectReason = "Suspected HWID spoofer."; + e.RejectReason = "Su" + "spe" + "cte" + "d H" + "WI" + "D " + "sp" + "o" + "of" + "er."; return false; } @@ -101,6 +104,9 @@ private bool TryUpdateIdentifiers(PlayerPending e, WarfareUserData data, IDbCont data.HWIDs.Add(newHwid); } + if (!hasIpv4) + return true; + found = false; foreach (PlayerIPAddress existingIp in data.IPAddresses) { @@ -125,8 +131,8 @@ private bool TryUpdateIdentifiers(PlayerPending e, WarfareUserData data, IDbCont RemotePlay = _isRemotePlay }; - dbContext.Add(newIp); data.IPAddresses.Add(newIp); + return true; } diff --git a/UncreatedWarfare/Teams/FactionDataStore.cs b/UncreatedWarfare/Teams/FactionDataStore.cs index b99ccd9f..7274c453 100644 --- a/UncreatedWarfare/Teams/FactionDataStore.cs +++ b/UncreatedWarfare/Teams/FactionDataStore.cs @@ -1,4 +1,4 @@ -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; @@ -63,20 +63,23 @@ public static class FactionDataStoreExtensions IReadOnlyList factions = dataStore.Factions; foreach (FactionInfo faction in factions) { - if (F.RoughlyEquals(search, faction.FactionId)) + if (search.Equals(faction.FactionId, StringComparison.OrdinalIgnoreCase)) return faction; } foreach (FactionInfo faction in factions) { - if (F.RoughlyEquals(search, faction.Name)) + if (search.Equals(faction.Name, StringComparison.OrdinalIgnoreCase)) return faction; } foreach (FactionInfo faction in factions) { - if (faction.ShortName != null && F.RoughlyEquals(search, faction.ShortName)) + if (search.Equals(faction.ShortName, StringComparison.OrdinalIgnoreCase)) return faction; } + if (exact) + return null; + FactionInfo? match = null; foreach (FactionInfo faction in factions) { diff --git a/UncreatedWarfare/Translations/Languages/MySqlLanguageDataStore.cs b/UncreatedWarfare/Translations/Languages/MySqlLanguageDataStore.cs index 8949c69f..c110b6ec 100644 --- a/UncreatedWarfare/Translations/Languages/MySqlLanguageDataStore.cs +++ b/UncreatedWarfare/Translations/Languages/MySqlLanguageDataStore.cs @@ -64,16 +64,16 @@ public MySqlLanguageDataStore(IServiceProvider serviceProvider) if (_langs == null) return null; - LanguageInfo lang = _langs.Find(x => F.RoughlyEquals(x.Code, name)); + LanguageInfo lang = _langs.Find(x => x.Code.Equals(name, StringComparison.InvariantCultureIgnoreCase)); if (lang != null || exactOnly) return lang; - lang = _langs.Find(x => F.RoughlyEquals(x.DisplayName, name)); + lang = _langs.Find(x => x.DisplayName.Equals(name, StringComparison.InvariantCultureIgnoreCase)); if (lang != null) return lang; string[] words = name.Split(SpaceSplit); - lang = _langs.Find(x => words.All(l => x.Aliases.Any(x => F.RoughlyEquals(l, x.Alias)))); + lang = _langs.Find(x => words.All(l => x.Aliases.Any(x => x.Alias.Equals(l, StringComparison.InvariantCultureIgnoreCase)))); if (lang != null) return lang; diff --git a/UncreatedWarfare/Tweaks/VehicleDamageTrackerItemTweak.cs b/UncreatedWarfare/Tweaks/VehicleDamageTrackerItemTweak.cs new file mode 100644 index 00000000..33f3b852 --- /dev/null +++ b/UncreatedWarfare/Tweaks/VehicleDamageTrackerItemTweak.cs @@ -0,0 +1,131 @@ +using System; +using Uncreated.Warfare.Deaths; +using Uncreated.Warfare.Events; +using Uncreated.Warfare.Events.Components; +using Uncreated.Warfare.Events.Models; +using Uncreated.Warfare.Events.Models.Vehicles; +using Uncreated.Warfare.Players; +using Uncreated.Warfare.Players.Management; +using Uncreated.Warfare.Vehicles.WarfareVehicles; +using Uncreated.Warfare.Vehicles.WarfareVehicles.Damage; + +namespace Uncreated.Warfare.Tweaks; +internal sealed class VehicleDamageTrackerItemTweak : IEventListener +{ + private readonly IPlayerService _playerService; + + public VehicleDamageTrackerItemTweak(IPlayerService playerService) + { + _playerService = playerService; + } + + [EventListener(MustRunInstantly = true)] + void IEventListener.HandleEvent(VehiclePreDamaged e, IServiceProvider serviceProvider) + { + WarfareVehicle vehicle = e.Vehicle; + VehicleDamageTracker tracker = vehicle.DamageTracker; + + if (!e.InstantaneousInstigator.HasValue) + { + if (e.InstantaneousDamageOrigin == EDamageOrigin.Trap_Explosion) + { + tracker.RecordDamage(EDamageOrigin.Trap_Explosion); + } + return; + } + + CSteamID instigator64 = e.InstantaneousInstigator.Value; + + WarfarePlayer? instigator = _playerService.GetOnlinePlayerOrNull(instigator64); + + PlayerDeathTrackingComponent? deathComponent = instigator == null ? null : PlayerDeathTrackingComponent.GetOrAdd(instigator.UnturnedPlayer); + + switch (e.InstantaneousDamageOrigin) + { + default: + tracker.UpdateLatestInstigatorWeapon(null); + break; + + // Throwable + case EDamageOrigin.Grenade_Explosion: + ThrowableComponent? throwable = deathComponent?.ActiveThrownItems.Find(x => x.Throwable is { isExplosive: true }); + tracker.UpdateLatestInstigatorWeapon(throwable?.Throwable); + break; + + // Projectile + case EDamageOrigin.Rocket_Explosion: + tracker.UpdateLatestInstigatorWeapon(deathComponent?.LastRocketShot?.GetAsset()); + break; + + // Another vehicle exploding damaged this vehicle + case EDamageOrigin.Vehicle_Explosion: + tracker.UpdateLatestInstigatorWeapon(deathComponent?.LastVehicleExploded?.Asset); + break; + + // Instant damage that comes from a useable. + case EDamageOrigin.Bullet_Explosion: + case EDamageOrigin.Useable_Gun: + case EDamageOrigin.Useable_Melee: + ItemAsset? equippedGun = instigator?.UnturnedPlayer.equipment.asset; + tracker.UpdateLatestInstigatorWeapon(equippedGun); + break; + + // Eplosive chewing gum + case EDamageOrigin.Food_Explosion: + tracker.UpdateLatestInstigatorWeapon(deathComponent?.LastExplosiveConsumed?.GetAsset()); + break; + + // C4 Charge (Charge_Self_Destruct is the damage used to destroy a charge after it detonates, irrelevant here) + case EDamageOrigin.Charge_Explosion: + tracker.UpdateLatestInstigatorWeapon(deathComponent?.LastChargeDetonated?.GetAsset()); + break; + + // Landmines + case EDamageOrigin.Trap_Explosion: + WarfarePlayer? triggerer = null; + CSteamID barricadeGroupId = default; + + bool setWeapon = false; + + // find exploding landmine from other players to find who triggered the landmine + // TriggeredTrapExplosive gets set to null right after the explosion so there can only ever be one active at once + foreach (SteamPlayer player in Provider.clients) + { + PlayerDeathTrackingComponent comp = PlayerDeathTrackingComponent.GetOrAdd(player.player); + if (comp.TriggeredTrapExplosive == null) + continue; + + triggerer = _playerService.GetOnlinePlayer(player); + tracker.UpdateLatestInstigatorWeapon(comp.TriggeredTrapExplosive.asset); + barricadeGroupId = new CSteamID(comp.TriggeredTrapExplosive.GetServersideData().group); + setWeapon = true; + break; + } + + if (!setWeapon) + { + if (deathComponent?.OwnedTrap is { } trap) + { + tracker.UpdateLatestInstigatorWeapon(trap.asset); + barricadeGroupId = new CSteamID(trap.GetServersideData().group); + } + else + { + tracker.UpdateLatestInstigatorWeapon(null); + } + } + + // lay fault onto the triggerer if they were on the same team as the player that placed the landmine + if (triggerer != null && triggerer.Team.IsFriendly(barricadeGroupId)) + { + tracker.RecordDamage(triggerer.Steam64, e.PendingDamage, EDamageOrigin.Trap_Explosion); + } + else + { + tracker.RecordDamage(instigator64, e.PendingDamage, EDamageOrigin.Trap_Explosion); + } + + break; + } + } +} diff --git a/UncreatedWarfare/F.cs b/UncreatedWarfare/Util/CancellationTokenExtensions.cs similarity index 86% rename from UncreatedWarfare/F.cs rename to UncreatedWarfare/Util/CancellationTokenExtensions.cs index 387f059d..5c9fb1c3 100644 --- a/UncreatedWarfare/F.cs +++ b/UncreatedWarfare/Util/CancellationTokenExtensions.cs @@ -1,15 +1,10 @@ using System; -using System.Globalization; -namespace Uncreated.Warfare; +namespace Uncreated.Warfare.Util; -public static class F +public static class CancellationTokenExtensions { - - public static bool RoughlyEquals(string? a, string? b) => string.Compare(a, b, CultureInfo.InvariantCulture, - CompareOptions.IgnoreCase | CompareOptions.IgnoreKanaType | CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreSymbols) == 0; - - + [MustUseReturnValue("CombinedTokenSources must be disposed.")] public static CombinedTokenSources CombineTokensIfNeeded(this ref CancellationToken token, CancellationToken other) { if (token.CanBeCanceled) @@ -27,12 +22,20 @@ public static CombinedTokenSources CombineTokensIfNeeded(this ref CancellationTo if (!other.CanBeCanceled) return default; - + token = other; return new CombinedTokenSources(other, null); - } + } + + [MustUseReturnValue("CombinedTokenSources must be disposed.")] public static CombinedTokenSources CombineTokensIfNeeded(this ref CancellationToken token, CancellationToken other1, CancellationToken other2) { + if (other1 == other2 || token == other2) + return token.CombineTokensIfNeeded(other1); + + if (other1 == token) + return token.CombineTokensIfNeeded(other2); + CancellationTokenSource src; if (token.CanBeCanceled) { @@ -52,12 +55,12 @@ public static CombinedTokenSources CombineTokensIfNeeded(this ref CancellationTo if (!other2.CanBeCanceled) return new CombinedTokenSources(token, null); - + src = CancellationTokenSource.CreateLinkedTokenSource(token, other2); token = src.Token; return new CombinedTokenSources(token, src); } - + if (other1.CanBeCanceled) { if (other2.CanBeCanceled) diff --git a/UncreatedWarfare/Util/DamageTracking/DamageTracker.cs b/UncreatedWarfare/Util/DamageTracking/DamageTracker.cs index d2c587e0..ec550fb7 100644 --- a/UncreatedWarfare/Util/DamageTracking/DamageTracker.cs +++ b/UncreatedWarfare/Util/DamageTracking/DamageTracker.cs @@ -1,24 +1,31 @@ -using System; +using System; using System.Collections.Generic; -using System.Text; using Uncreated.Warfare.Players; -using Uncreated.Warfare.Util; namespace Uncreated.Warfare.Util.DamageTracking; public class DamageTracker { private readonly PlayerContributionTracker _damageContributors; + public DateTime TimeLastDamaged { get; private set; } + /// /// The very last player to damage the object, if the latest damaged was from a player. Will be if the object's most recent damage was not from a player. /// public CSteamID? LatestDamageInstigator { get; private set; } + /// /// The player who is the last known person to have damaged this object. Will not be even if the object's most recent damage was not from a player. /// public CSteamID? LastKnownDamageInstigator { get; private set; } + public EDamageOrigin? LatestDamageCause { get; private set; } - public ItemAsset? LatestInstigatorWeapon { get; private set; } + + /// + /// This can be an item or vehicle (ex. a vehicle explodes next to this vehicle). + /// + /// It may not necessarily be a weapon if its an item. Could be melee, explosive consumable, landmine, C4 charge, etc. + public Asset? LatestInstigatorWeapon { get; private set; } public DamageTracker() { TimeLastDamaged = DateTime.MinValue; @@ -27,6 +34,19 @@ public DamageTracker() LatestInstigatorWeapon = null; _damageContributors = new PlayerContributionTracker(); } + + /// + /// Invoked when damage becomes irrelevant, like when the object is repaired. + /// + public virtual void ClearDamage() + { + TimeLastDamaged = default; + LatestDamageCause = null; + LatestDamageInstigator = null; + LastKnownDamageInstigator = null; + _damageContributors.Clear(); + } + public virtual void RecordDamage(WarfarePlayer onlineInstigator, ushort damage, EDamageOrigin cause) { TimeLastDamaged = DateTime.Now; @@ -50,12 +70,13 @@ public virtual void RecordDamage(EDamageOrigin cause) LatestDamageCause = cause; } - public void UpdateLatestInstigatorWeapon(ItemAsset asset) // todo: need to call this method places, otherwise advanced damage won't work properly + public void UpdateLatestInstigatorWeapon(Asset? asset) // todo: need to call this method places, otherwise advanced damage won't work properly { LatestInstigatorWeapon = asset; + Console.WriteLine("Damage upated to: {0}", asset); } - public PlayerWork? GetDamageContribution(CSteamID playerId) => _damageContributors.GetContribution(playerId); - public PlayerWork? GetDamageContribution(CSteamID playerId, DateTime after) => _damageContributors.GetContribution(playerId, after); + public float GetDamageContribution(CSteamID playerId, out float total) => _damageContributors.GetContribution(playerId, out total); + public float GetDamageContribution(CSteamID playerId, DateTime after, out float total) => _damageContributors.GetContribution(playerId, after, out total); public float GetDamageContributionPercentage(CSteamID playerId) => _damageContributors.GetContributionPercentage(playerId); public float GetDamageContributionPercentage(CSteamID playerId, DateTime after) => _damageContributors.GetContributionPercentage(playerId, after); public IEnumerable Contributors => _damageContributors.Contributors; diff --git a/UncreatedWarfare/Util/PlayerContributionTracker.cs b/UncreatedWarfare/Util/PlayerContributionTracker.cs index 24ded060..a47d7bfe 100644 --- a/UncreatedWarfare/Util/PlayerContributionTracker.cs +++ b/UncreatedWarfare/Util/PlayerContributionTracker.cs @@ -1,8 +1,5 @@ -using SDG.Unturned; using System; using System.Collections.Generic; -using System.Linq; -using Uncreated.Warfare.Players; namespace Uncreated.Warfare.Util; @@ -11,114 +8,224 @@ namespace Uncreated.Warfare.Util; /// public class PlayerContributionTracker : IEnumerable { - private readonly Dictionary _contributions; + private PlayerWork[] _work; + private int _workCount; private float _totalWorkDone; - private bool _anyDuplicateSessions; /// /// Total number of ticks in the collection. /// - public float TotalWorkDone - { - get - { - lock (_contributions) - return _totalWorkDone; - } - } + public float TotalWorkDone => _totalWorkDone; public PlayerContributionTracker() : this(4) { } public PlayerContributionTracker(int capacity) { - _contributions = new Dictionary(capacity); + _work = new PlayerWork[capacity]; } - public int NumberOfContributors + public int ContributorCount => _workCount; + + public void Clear() { - get + lock (_work) { - lock (_contributions) - return _contributions.Count; + _workCount = 0; + Array.Clear(_work, 0, _work.Length); } } - public void RetrieveLock() => Monitor.Enter(_contributions); - public void ReturnLock() => Monitor.Exit(_contributions); - public float GetWorkDoneNoLock() => _totalWorkDone; /// The percentage of the work has done (from 0 to 1). public float GetContributionPercentage(CSteamID player) { - lock (_contributions) - { - if (_contributions.TryGetValue(player, out PlayerWork work)) - return work.WorkPoints / _totalWorkDone; - } - - return 0f; + float contribution = GetContribution(player, out float total); + return total != 0 ? contribution / total : 0; } + public float GetContributionPercentage(CSteamID player, DateTime after) { - lock (_contributions) + float contribution = GetContribution(player, after, out float total); + return total != 0 ? contribution / total : 0; + } + + /// + /// A indicating how much work the player has contributed. + /// + public float GetContribution(CSteamID player, out float total) + { + for (int i = 0; i < _workCount; ++i) { - if (_contributions.TryGetValue(player, out PlayerWork work) && work.LastUpdated >= after) + ref PlayerWork w = ref _work[i]; + if (w.PlayerId.m_SteamID != player.m_SteamID) + continue; + + lock (_work) { - float totalWorkDoneAfterSpecifiedTime = _contributions.Values.Where(w => w.LastUpdated >= after).Sum(w => w.WorkPoints); - - return work.WorkPoints / totalWorkDoneAfterSpecifiedTime; + total = _totalWorkDone; + return w.WorkPoints; } } + total = 0; return 0f; } - /// A indicating how much work the player has contributed. - public PlayerWork? GetContribution(CSteamID player) - { - if (_contributions.TryGetValue(player, out PlayerWork work)) - return work; - return null; - } - /// A indicating how much work the player has contributed. - public PlayerWork? GetContribution(CSteamID player, DateTime after) + /// + /// A indicating how much work the player has contributed. + /// + public float GetContribution(CSteamID player, DateTime after, out float total) { - if (_contributions.TryGetValue(player, out PlayerWork work) && work.LastUpdated >= after) - return work; + after = after.ToUniversalTime(); + float totalPoints = 0; + float points = 0; + lock (_work) + { + for (int i = 0; i < _workCount; ++i) + { + ref PlayerWork w = ref _work[i]; + if (w.LastUpdated < after || w.PlayerId.m_SteamID == 0) + continue; + + totalPoints += w.WorkPoints; + if (w.PlayerId.m_SteamID == player.m_SteamID) + points = w.WorkPoints; + break; + } + } - return null; + total = totalPoints; + return points; } + /// /// Increment a player's work points by . /// public void RecordWork(CSteamID player, float workPoints, DateTime? timePerformed = null) { - lock (_contributions) + DateTime time = timePerformed?.ToUniversalTime() ?? DateTime.UtcNow; + lock (_work) { _totalWorkDone += workPoints; - if (_contributions.TryGetValue(player, out PlayerWork work)) + for (int i = 0; i < _workCount; ++i) { - _contributions[player] = new PlayerWork(player, work.WorkPoints + workPoints, timePerformed ?? DateTime.Now); + ref PlayerWork w = ref _work[i]; + if (w.PlayerId.m_SteamID != player.m_SteamID) + continue; + + w.WorkPoints += workPoints; + w.LastUpdated = time; + return; } - else + + PlayerWork[] newWork = new PlayerWork[_work.Length * 2]; + lock (newWork) { - _contributions[player] = new PlayerWork(player, workPoints, timePerformed ?? DateTime.Now); + _work = newWork; + _work[_workCount] = new PlayerWork(player, workPoints, time); } } } - public IEnumerable Contributors => _contributions.Keys; - public IEnumerator GetEnumerator() => _contributions.Values.GetEnumerator(); + public ContributorEnumerator Contributors => new ContributorEnumerator(this); + public PlayerWorkEnumerator GetEnumerator() => new PlayerWorkEnumerator(this); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public struct ContributorEnumerator : IEnumerator, IEnumerable + { + private readonly PlayerContributionTracker _tracker; + private int _index; + + /// + public CSteamID Current { get; private set; } + + public ContributorEnumerator(PlayerContributionTracker tracker) + { + _tracker = tracker; + } + + public bool MoveNext() + { + PlayerWork work; + do + { + ++_index; + if (_index >= _tracker._workCount) + return false; + + work = _tracker._work[_index]; + } while (work.PlayerId.m_SteamID == 0); + + Current = work.PlayerId; + return true; + } + + public void Reset() + { + _index = -1; + } + + object IEnumerator.Current => Current; + + public ContributorEnumerator GetEnumerator() + { + ContributorEnumerator n = this; + n._index = -1; + return n; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + public void Dispose() { } + } + + public struct PlayerWorkEnumerator : IEnumerator + { + private readonly PlayerContributionTracker _tracker; + private int _index; + + /// + public PlayerWork Current { get; private set; } + + public PlayerWorkEnumerator(PlayerContributionTracker tracker) + { + _tracker = tracker; + } + + public bool MoveNext() + { + PlayerWork work; + do + { + ++_index; + if (_index >= _tracker._workCount) + return false; + + work = _tracker._work[_index]; + } while (work.PlayerId.m_SteamID == 0); + + Current = work; + return true; + } + + public void Reset() + { + _index = -1; + } + + object IEnumerator.Current => Current; + public void Dispose() { } + } } -public readonly struct PlayerWork +public struct PlayerWork { public readonly CSteamID PlayerId; - public readonly float WorkPoints; - public readonly DateTime LastUpdated; + public float WorkPoints; + public DateTime LastUpdated; public PlayerWork(CSteamID playerId, float ticks) { PlayerId = playerId; diff --git a/UncreatedWarfare/Util/StringUtility.cs b/UncreatedWarfare/Util/StringUtility.cs index e7be71eb..1a396f3f 100644 --- a/UncreatedWarfare/Util/StringUtility.cs +++ b/UncreatedWarfare/Util/StringUtility.cs @@ -170,12 +170,12 @@ public static unsafe int LevenshteinDistance(char* a, int aChars, char* b, int b private static bool IsIgnored(char c, LevenshteinOptions options) { - if ((options & LevenshteinOptions.IgnoreWhitespace) != 0) + if ((options & LevenshteinOptions.IgnoreWhitespace) == LevenshteinOptions.IgnoreWhitespace) { if (char.IsWhiteSpace(c)) return true; } - if ((options & LevenshteinOptions.IgnorePunctuation) != 0) + if ((options & LevenshteinOptions.IgnorePunctuation) == LevenshteinOptions.IgnorePunctuation) { if (char.IsPunctuation(c)) return true; diff --git a/UncreatedWarfare/Vehicles/Events/Tweaks/AdvancedDamage/AdvancedVehicleDamageTweaks.cs b/UncreatedWarfare/Vehicles/Events/Tweaks/AdvancedDamage/AdvancedVehicleDamageTweaks.cs index 20fca829..897a3953 100644 --- a/UncreatedWarfare/Vehicles/Events/Tweaks/AdvancedDamage/AdvancedVehicleDamageTweaks.cs +++ b/UncreatedWarfare/Vehicles/Events/Tweaks/AdvancedDamage/AdvancedVehicleDamageTweaks.cs @@ -50,7 +50,7 @@ public void HandleEvent(DamageVehicleRequested e, IServiceProvider serviceProvid { float finalMultiplier = 1; - ItemAsset? latestInstigatorWeapon = e.Vehicle.DamageTracker.LatestInstigatorWeapon; + Asset? latestInstigatorWeapon = e.Vehicle.DamageTracker.LatestInstigatorWeapon; AdvancedVehicleDamageApplier.AdvancedDamagePending? directHit = e.Vehicle.AdvancedDamageApplier.ApplyLatestPendingDirectHit(); diff --git a/UncreatedWarfare/Vehicles/Spawners/VehicleSpawnerStore.cs b/UncreatedWarfare/Vehicles/Spawners/VehicleSpawnerStore.cs index ac8307b0..dfa17052 100644 --- a/UncreatedWarfare/Vehicles/Spawners/VehicleSpawnerStore.cs +++ b/UncreatedWarfare/Vehicles/Spawners/VehicleSpawnerStore.cs @@ -1,24 +1,11 @@ -using DanielWillett.ReflectionTools; -using DanielWillett.SpeedBytes; -using DanielWillett.SpeedBytes.Unity; +using DanielWillett.ReflectionTools; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.FileProviders; -using SDG.Unturned; using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.IO; -using System.Linq; -using Uncreated.Warfare.Buildables; using Uncreated.Warfare.Configuration; -using Uncreated.Warfare.Database; -using Uncreated.Warfare.Events.Models.Fobs; -using Uncreated.Warfare.Events.Models.Vehicles; using Uncreated.Warfare.Services; -using Uncreated.Warfare.Util; using Uncreated.Warfare.Vehicles.Spawners; -using UnityEngine; -using UnityEngine.Profiling; namespace Uncreated.Warfare.Vehicles; @@ -76,7 +63,7 @@ private static string GetFolderPath() ServerSavedata.directoryName, Provider.serverID, "Level", - Level.info.name, + Provider.map, "VehicleSpawners.yml" ); } diff --git a/UncreatedWarfare/Vehicles/UI/VehicleHUD.cs b/UncreatedWarfare/Vehicles/UI/VehicleHUD.cs index 08a89f1a..ebaec89c 100644 --- a/UncreatedWarfare/Vehicles/UI/VehicleHUD.cs +++ b/UncreatedWarfare/Vehicles/UI/VehicleHUD.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using Uncreated.Framework.UI; using Uncreated.Framework.UI.Reflection; using Uncreated.Warfare.Configuration; @@ -31,6 +30,7 @@ public void ShowForPlayer(WarfarePlayer player, WarfareVehicle vehicle, bool dis if (displayFlareCount) FlareCount.SetText(player.Connection, "FLARES: " + vehicle.FlareEmitter?.TotalFlaresLeft); } + public void UpdateFlaresForRelevantPassengers(WarfareVehicle vehicle) { for (int i = 0; i < vehicle.Vehicle.passengers.Length; i++) @@ -45,6 +45,7 @@ public void UpdateFlaresForRelevantPassengers(WarfareVehicle vehicle) FlareCount.SetText(passenger.player.transportConnection, "FLARES: " + vehicle.FlareEmitter?.TotalFlaresLeft); } } + public void HideForPlayer(WarfarePlayer player) { ClearFromPlayer(player.Connection); @@ -63,4 +64,4 @@ public void ToggleMissileWarning(WarfareVehicle vehicle, bool isEnabled) MissileWarningDriver.SetVisibility(passenger.player.transportConnection, isEnabled); } } -} +} \ No newline at end of file diff --git a/UncreatedWarfare/Vehicles/VehicleDamageCalculator.cs b/UncreatedWarfare/Vehicles/VehicleDamageCalculator.cs deleted file mode 100644 index 9ea9fbc9..00000000 --- a/UncreatedWarfare/Vehicles/VehicleDamageCalculator.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using Uncreated.Warfare.Components; - -namespace Uncreated.Warfare.Vehicles; - -public class VehicleDamageCalculator -{ - private static readonly Dictionary _damageRegister = new Dictionary(); - // TODO: move this to config later - private static readonly List AirAttackOnly = new List() - { - new Guid("d9447148f8aa41f0ad885edd24ac5a02"), // J-10 - new Guid("0b21724b3a1f40e7b88de9484a1733bc"), // JH-7A - new Guid("0a58fa7fdad2470c97af71e03059f181"), // Eurofighter - new Guid("661a347f5e56406e85510a1b427bc4d6"), // F-15E - new Guid("58b18a3fa1104ca58a7bdebef3ab6b29"), // Stinger - new Guid("5ae39e59d299415d8c4d08b233206302"), // Igla - }; - private static readonly List GroundAttackOnly = new List() - { - //commented these out since not sure if people would like this - //new Guid("06cdbbf06436409b9c3ba9237cc486aa"), // AH-1Z rockets - //new Guid("fc81c5f027364023b212900e9c3c7697"), // Mi-28 rockets - //new Guid("45de0912d5994947800e1732937de373"), // Z-10 rockets - //new Guid("e929441a7bf7471b80072654275255c1"), // AH-1Z gun - //new Guid("222f108a7c50462f9c2f4d8730365df3"), // Mi-28 gun - //new Guid("229d2003673d41949cfed6bdb07c9c5a"), // Eurocopter gun - //new Guid("61423656d457489fb7f6928e907c03b3"), // Z-10 gun - }; - private static readonly List IgnoreArmorMultiplier = new List() - { - new Guid("06cdbbf06436409b9c3ba9237cc486aa"), // F-15 bombs - new Guid("8b61f77fa7194baaba65d1ec0a5c0e87"), // Su-34 bombs - new Guid("433ea5249699420eb7adb67791a98134"), // F-15 laser guided - new Guid("3754ca2527ee40e2ad0951c8930efb07"), // Su-34 laser guided - }; - - public static float GetComponentDamageMultiplier(ProjectileComponent projectileComponent, Collider vehicleCollider) - { - float multiplier = 1; - - if (vehicleCollider.name.StartsWith("damage_")) - if (float.TryParse(vehicleCollider.name.Substring(7), NumberStyles.Any, CultureInfo.InvariantCulture, out float result)) - multiplier = result; - - return multiplier; - } - public static float GetComponentDamageMultiplier(InputInfo input) - { - if (input.vehicle != null && input.colliderTransform != null) - { - float multiplier = 1; - - if (input.colliderTransform.name.StartsWith("damage_")) - if (float.TryParse(input.colliderTransform.name.Substring(7), NumberStyles.Any, CultureInfo.InvariantCulture, out float result)) - multiplier = result; - - return multiplier; - } - - return 1; - } - public static void RegisterForAdvancedDamage(InteractableVehicle vehicle, float multiplier) - { - if (_damageRegister.ContainsKey(vehicle)) - _damageRegister[vehicle] = multiplier; - else - _damageRegister.Add(vehicle, multiplier); - - vehicle.StartCoroutine(TimeOutDamage(vehicle)); - } - public static void ApplyAdvancedDamage(InteractableVehicle vehicle, ref ushort finalDamage) - { - VehicleComponent? vehicleComponent = vehicle.transform.GetComponentInChildren(); - - //L.LogDebug("Attempting to apply damage..."); - if (_damageRegister.TryGetValue(vehicle, out float multiplier)) - { - finalDamage = (ushort)Mathf.RoundToInt(finalDamage * multiplier); - - if (AirAttackOnly.Contains(vehicleComponent.LastItem) && !vehicleComponent.IsAircraft) - finalDamage = (ushort)Mathf.RoundToInt(finalDamage * 0.1f); - - if (GroundAttackOnly.Contains(vehicleComponent.LastItem) && vehicleComponent.IsAircraft) - finalDamage = (ushort)Mathf.RoundToInt(finalDamage * 0.1f); - - _damageRegister.Remove(vehicle); - //L.LogDebug($"Successfully applied {multiplier}x damage: {finalDamage}"); - } - else if (!IgnoreArmorMultiplier.Contains(vehicleComponent.LastItem) && !vehicleComponent.IsEmplacement) - { - finalDamage = (ushort)Mathf.RoundToInt(finalDamage * 0.1f); - //L.LogDebug($"No direct hit, applied 0.1x damage: {finalDamage}"); - } - } - private static IEnumerator TimeOutDamage(InteractableVehicle vehicle) - { - yield return new WaitForFixedUpdate(); - _damageRegister.Remove(vehicle); - } -} diff --git a/UncreatedWarfare/Vehicles/VehicleService.cs b/UncreatedWarfare/Vehicles/VehicleService.cs index e92e1921..9aa2fdb9 100644 --- a/UncreatedWarfare/Vehicles/VehicleService.cs +++ b/UncreatedWarfare/Vehicles/VehicleService.cs @@ -2,17 +2,13 @@ using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using Uncreated.Warfare.Components; using Uncreated.Warfare.Configuration; using Uncreated.Warfare.Events.Models; using Uncreated.Warfare.Events.Models.Vehicles; using Uncreated.Warfare.Services; using Uncreated.Warfare.Util; -using Uncreated.Warfare.Util.List; -using Uncreated.Warfare.Vehicles.WarfareVehicles; using Uncreated.Warfare.Vehicles.Spawners; +using Uncreated.Warfare.Vehicles.WarfareVehicles; namespace Uncreated.Warfare.Vehicles { @@ -21,6 +17,8 @@ public class VehicleService : ILayoutHostedService, IEventListener { + private readonly List _vehicles; + private readonly IServiceProvider _serviceProvider; private readonly ILogger _logger; private readonly VehicleInfoStore _vehicleInfoStore; @@ -29,11 +27,13 @@ public class VehicleService : private const float VehicleSpawnOffset = 5f; public const ushort MaxBatteryCharge = 10000; - public TrackingList Vehicles { get; } + public IReadOnlyList Vehicles { get; } public VehicleService(IServiceProvider serviceProvider, ILogger logger) { - Vehicles = new TrackingList(); + _vehicles = new List(64); + Vehicles = _vehicles.AsReadOnly(); + _logger = logger; _serviceProvider = serviceProvider; _vehicleInfoStore = serviceProvider.GetRequiredService(); @@ -49,26 +49,75 @@ public UniTask StopAsync(CancellationToken token) { return UniTask.CompletedTask; } + public WarfareVehicle RegisterWarfareVehicle(InteractableVehicle vehicle) { - WarfareVehicleInfo info = _vehicleInfoStore.GetVehicleInfo(vehicle.asset) ?? WarfareVehicleInfo.CreateDefault(vehicle.asset); + WarfareVehicle warfareVehicle; + lock (_vehicles) + { + uint instId = vehicle.instanceID; + for (int i = 0; i < _vehicles.Count; ++i) + { + if (_vehicles[i].InstanceId == instId) + return _vehicles[i]; + } + + WarfareVehicleInfo info = _vehicleInfoStore.GetVehicleInfo(vehicle.asset) ?? WarfareVehicleInfo.CreateDefault(vehicle.asset); + + warfareVehicle = new WarfareVehicle(vehicle, info, _serviceProvider); + _vehicles.Add(warfareVehicle); + } - WarfareVehicle warfareVehicle = new(vehicle, info, _serviceProvider); - Vehicles.Add(warfareVehicle); - _logger.LogDebug($"Registered vehicle: {warfareVehicle.Info.VehicleAsset.ToDisplayString()}"); return warfareVehicle; } - public WarfareVehicle? GetVehicle(InteractableVehicle vehicle) + + public WarfareVehicle? DeregisterWarfareVehicle(InteractableVehicle vehicle) + { + lock (_vehicles) + { + uint instId = vehicle.instanceID; + for (int i = 0; i < _vehicles.Count; ++i) + { + WarfareVehicle info = _vehicles[i]; + if (info.InstanceId != instId) + continue; + + _vehicles.RemoveAt(i); + info.Dispose(); + return info; + } + } + + return null; + } + + public WarfareVehicle GetVehicle(InteractableVehicle vehicle) { - return Vehicles.FirstOrDefault(f => f.Vehicle.instanceID == vehicle.instanceID); + if (GameThread.IsCurrent) + return GetVehicleLocked(vehicle); + + lock (_vehicles) + { + // auto register if it wasn't for some reason + return GetVehicleLocked(vehicle); + } } - public WarfareVehicle? DeregisterWarfareVehicle(InteractableVehicle vehicle) + + private WarfareVehicle GetVehicleLocked(InteractableVehicle vehicle) { - WarfareVehicle? existing = Vehicles.FindAndRemove(f => f.Vehicle.instanceID == vehicle.instanceID); - existing?.Dispose(); - return existing; + uint instId = vehicle.instanceID; + // ReSharper disable InconsistentlySynchronizedField + for (int i = 0; i < _vehicles.Count; ++i) + { + if (_vehicles[i].InstanceId == instId) + return _vehicles[i]; + } + + // ReSharper restore InconsistentlySynchronizedField + return RegisterWarfareVehicle(vehicle); } + /// /// Spawn a vehicle at a given vehicle spawner. /// @@ -247,13 +296,10 @@ private void PrepareToDeleteVehicle(InteractableVehicle vehicle) } } - if (!vehicle.TryGetComponent(out VehicleComponent component) || component.Spawn == null) - return; - // unlink vehicle from it's spawner if it had one try { - component.Spawn.UnlinkVehicle(); + GetVehicle(vehicle).Spawn?.UnlinkVehicle(); } catch (InvalidOperationException ex) { diff --git a/UncreatedWarfare/Vehicles/WarfareVehicles/Damage/AdvancedVehicleDamageApplier.cs b/UncreatedWarfare/Vehicles/WarfareVehicles/Damage/AdvancedVehicleDamageApplier.cs index 3b5a5abc..33b85de2 100644 --- a/UncreatedWarfare/Vehicles/WarfareVehicles/Damage/AdvancedVehicleDamageApplier.cs +++ b/UncreatedWarfare/Vehicles/WarfareVehicles/Damage/AdvancedVehicleDamageApplier.cs @@ -54,7 +54,7 @@ public static float GetComponentDamageMultiplier(Transform colliderTransform) if (!colliderTransform.name.StartsWith("damage_")) return 1; - if (!float.TryParse(colliderTransform.name[7..], NumberStyles.Any, + if (!float.TryParse(colliderTransform.name.AsSpan(7), NumberStyles.Any, CultureInfo.InvariantCulture, out float multiplier)) return 1; diff --git a/UncreatedWarfare/Vehicles/WarfareVehicles/Damage/VehicleDamageTracker.cs b/UncreatedWarfare/Vehicles/WarfareVehicles/Damage/VehicleDamageTracker.cs index 26dbfa27..73dac34a 100644 --- a/UncreatedWarfare/Vehicles/WarfareVehicles/Damage/VehicleDamageTracker.cs +++ b/UncreatedWarfare/Vehicles/WarfareVehicles/Damage/VehicleDamageTracker.cs @@ -1,12 +1,14 @@ -using System; -using System.Collections.Generic; -using System.Text; using Uncreated.Warfare.Players; using Uncreated.Warfare.Util.DamageTracking; namespace Uncreated.Warfare.Vehicles.WarfareVehicles.Damage; public class VehicleDamageTracker : DamageTracker { + /// + /// Used to track who most recently locked the player for the event. + /// + public WarfarePlayer? LastLockingPlayer { get; set; } + public WarfareVehicle? LatestDamageInstigatorVehicle { get; private set; } public void RecordDamage(WarfarePlayer onlineEnemyGunner, WarfareVehicle instigatorVehicle, ushort damage, EDamageOrigin cause) { diff --git a/UncreatedWarfare/Vehicles/WarfareVehicles/Flares/FlareEmitter.cs b/UncreatedWarfare/Vehicles/WarfareVehicles/Flares/FlareEmitter.cs index 93ec2eed..5c020fa0 100644 --- a/UncreatedWarfare/Vehicles/WarfareVehicles/Flares/FlareEmitter.cs +++ b/UncreatedWarfare/Vehicles/WarfareVehicles/Flares/FlareEmitter.cs @@ -32,7 +32,7 @@ public class FlareEmitter : MonoBehaviour public FlareEmitter Init(WarfareVehicle vehicle, AssetConfiguration assetConfiguration) { - Vehicle = vehicle; + Vehicle = vehicle; // todo _flareAsset = assetConfiguration.GetAssetLink("llalllalalalala").GetAssetOrFail(); _dropFlaresSound = assetConfiguration.GetAssetLink("llalllalalalala").GetAssetOrFail(); ReloadCountermeasures(); diff --git a/UncreatedWarfare/Vehicles/WarfareVehicles/Transport/TranportTracker.cs b/UncreatedWarfare/Vehicles/WarfareVehicles/Transport/TranportTracker.cs index 5eb87b41..f27ceab2 100644 --- a/UncreatedWarfare/Vehicles/WarfareVehicles/Transport/TranportTracker.cs +++ b/UncreatedWarfare/Vehicles/WarfareVehicles/Transport/TranportTracker.cs @@ -1,11 +1,11 @@ -using System; +using System; using System.Collections.Generic; -using System.Text; namespace Uncreated.Warfare.Vehicles.WarfareVehicles.Transport; public class TranportTracker { public CSteamID? LastKnownDriver { get; private set; } + public DateTime? LastKnownDriverExitTime { get; private set; } private readonly Dictionary _playerEntryPositions; public TranportTracker() @@ -16,11 +16,20 @@ public void RecordPlayerEntry(ulong steam64, Vector3 entryPoint, int seatIndex) { _playerEntryPositions[steam64] = entryPoint; if (seatIndex == 0) + { LastKnownDriver = new CSteamID(steam64); + LastKnownDriverExitTime = DateTime.UtcNow; + } } /// The total distance travelled by the exiting player from their original recorded entry point. public float RecordPlayerExit(ulong steam64, Vector3 exitPoint) { + if (LastKnownDriver.HasValue && LastKnownDriver.Value.m_SteamID == steam64) + { + LastKnownDriver = null; + LastKnownDriverExitTime = DateTime.UtcNow; + } + if (_playerEntryPositions.TryGetValue(steam64, out Vector3 originalEntryPoint)) { _playerEntryPositions.Remove(steam64); diff --git a/UncreatedWarfare/Vehicles/WarfareVehicles/WarfareVehicle.cs b/UncreatedWarfare/Vehicles/WarfareVehicles/WarfareVehicle.cs index efc5c2b0..d48efa1b 100644 --- a/UncreatedWarfare/Vehicles/WarfareVehicles/WarfareVehicle.cs +++ b/UncreatedWarfare/Vehicles/WarfareVehicles/WarfareVehicle.cs @@ -1,97 +1,227 @@ -using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; using System; -using System.Collections.Generic; -using System.Text; +using System.Diagnostics.CodeAnalysis; using Uncreated.Warfare.Configuration; using Uncreated.Warfare.Util; -using Uncreated.Warfare.Util.DamageTracking; using Uncreated.Warfare.Vehicles.Spawners; using Uncreated.Warfare.Vehicles.UI; using Uncreated.Warfare.Vehicles.WarfareVehicles.Damage; using Uncreated.Warfare.Vehicles.WarfareVehicles.Flares; using Uncreated.Warfare.Vehicles.WarfareVehicles.Transport; -namespace Uncreated.Warfare.Vehicles.WarfareVehicles +namespace Uncreated.Warfare.Vehicles.WarfareVehicles; + +[CannotApplyEqualityOperator] +public class WarfareVehicle : IDisposable, ITransformObject, IEquatable, IEquatable, IComparable, IComparable { - public class WarfareVehicle : IDisposable - { - public InteractableVehicle Vehicle { get; } - public WarfareVehicleComponent Component { get; } - public WarfareVehicleInfo Info { get; } - public VehicleSpawner? Spawn { get; private set; } - public VehicleDamageTracker DamageTracker { get; } - public AdvancedVehicleDamageApplier AdvancedDamageApplier { get; } - public TranportTracker TranportTracker { get; } - public VehicleHUD? VehicleHUD { get; } - public FlareEmitter? FlareEmitter { get; } - public Vector3 Position => Vehicle.transform.position; - public Quaternion Rotation => Vehicle.transform.rotation; - public bool NeedsAutoResupply { get; internal set; } - public WarfareVehicle(InteractableVehicle interactableVehicle, WarfareVehicleInfo info, IServiceProvider serviceProvider) + private bool _isSettingUpComponent; + + public InteractableVehicle Vehicle { get; } + public WarfareVehicleInfo Info { get; } + public VehicleSpawner? Spawn { get; private set; } + public VehicleDamageTracker DamageTracker { get; } + public VehicleAsset Asset { get; } + public AdvancedVehicleDamageApplier AdvancedDamageApplier { get; } + public TranportTracker TranportTracker { get; } + public VehicleHUD? VehicleHUD { get; } + public FlareEmitter? FlareEmitter { get; private set; } + + [field: MaybeNull] + public WarfareVehicleComponent Component + { + get { - Vehicle = interactableVehicle; - Info = info; - Component = interactableVehicle.transform.GetOrAddComponent().Init(this); - VehicleHUD = serviceProvider.GetService(); - if (info.Type.IsAircraft()) + if (_isSettingUpComponent) { - FlareEmitter = interactableVehicle.transform.GetOrAddComponent(); - FlareEmitter.Init(this, serviceProvider.GetRequiredService()); + if (!GameThread.IsCurrent) + throw new InvalidOperationException("WarfareVehicle was created on a non-game thread, so the Component won't be initialized until the next Update tick."); + + SetupComponents(WarfareModule.Singleton.ServiceProvider.Resolve()); } - DamageTracker = new VehicleDamageTracker(); - TranportTracker = new TranportTracker(); - AdvancedDamageApplier = new AdvancedVehicleDamageApplier(); - NeedsAutoResupply = false; - } - public void Dispose() - { - Object.Destroy(Component); + return field!; } + private set; + } - internal void UnlinkFromSpawn(VehicleSpawner spawn) - { - GameThread.AssertCurrent(); + public Vector3 Position + { + get => Vehicle.transform.position; + set => SetPosition(value); + } - if (spawn == null) - throw new ArgumentNullException(nameof(spawn)); + public Quaternion Rotation + { + get => Vehicle.transform.rotation; + set => SetRotation(value); + } - if (!Equals(Spawn, spawn)) - { - throw new InvalidOperationException("The given spawn is not linked to this vehicle."); - } + public bool NeedsAutoResupply { get; internal set; } + public uint InstanceId { get; } - if (Spawn?.LinkedVehicle == Vehicle) + /// + /// If the vehicle is driveable or submerged. + /// + public bool Alive => Vehicle is { isDead: false, isExploded: false, isActiveAndEnabled: true }; + + public WarfareVehicle(InteractableVehicle interactableVehicle, WarfareVehicleInfo info, IServiceProvider serviceProvider) + { + Vehicle = interactableVehicle; + InstanceId = interactableVehicle.instanceID; + Asset = interactableVehicle.asset; + Info = info; + VehicleHUD = serviceProvider.GetService(); + DamageTracker = new VehicleDamageTracker(); + TranportTracker = new TranportTracker(); + AdvancedDamageApplier = new AdvancedVehicleDamageApplier(); + NeedsAutoResupply = false; + + // allow thread-safe initialization so GetVehicle can add the component if it doesn't already exist + if (GameThread.IsCurrent) + { + SetupComponents(serviceProvider); + } + else + { + _isSettingUpComponent = true; + UniTask.Create(async () => { - throw new InvalidOperationException("The old linked spawn is still linked to this vehicle."); - } + await UniTask.SwitchToMainThread(); + if (Vehicle == null) + return; - Spawn = null; + SetupComponents(serviceProvider); + }); } + } - internal void LinkToSpawn(VehicleSpawner spawn) - { - GameThread.AssertCurrent(); + private void SetupComponents(IServiceProvider serviceProvider) + { + if (!_isSettingUpComponent) + return; - if (spawn == null) - throw new ArgumentNullException(nameof(spawn)); + Component = Vehicle.transform.GetOrAddComponent().Init(this); + _isSettingUpComponent = false; + if (!Info.Type.IsAircraft()) + return; - if (spawn.LinkedVehicle != Vehicle) - { - throw new InvalidOperationException("The given spawn is not linked to this vehicle."); - } + FlareEmitter = Vehicle.transform.GetOrAddComponent(); + FlareEmitter.Init(this, serviceProvider.GetRequiredService()); + } - Spawn = spawn; + public void Dispose() + { + Object.Destroy(Component); + } + + internal void UnlinkFromSpawn(VehicleSpawner spawn) + { + GameThread.AssertCurrent(); + + if (spawn == null) + throw new ArgumentNullException(nameof(spawn)); + + if (!Equals(Spawn, spawn)) + { + throw new InvalidOperationException("The given spawn is not linked to this vehicle."); } - public override bool Equals(object? obj) + if (Spawn?.LinkedVehicle == Vehicle) { - return obj is WarfareVehicle vehicle && Vehicle.GetNetId().Equals(vehicle.Vehicle.GetNetId()); + throw new InvalidOperationException("The old linked spawn is still linked to this vehicle."); } - public override int GetHashCode() + Spawn = null; + } + + internal void LinkToSpawn(VehicleSpawner spawn) + { + GameThread.AssertCurrent(); + + if (spawn == null) + throw new ArgumentNullException(nameof(spawn)); + + if (spawn.LinkedVehicle != Vehicle) { - return HashCode.Combine(Vehicle.GetNetId()); + throw new InvalidOperationException("The given spawn is not linked to this vehicle."); } + + Spawn = spawn; + } + + /// + public bool Equals(WarfareVehicle other) + { + return other.InstanceId == InstanceId; + } + + /// + public bool Equals(InteractableVehicle other) + { + return other.instanceID == InstanceId; + } + + /// + public int CompareTo(WarfareVehicle other) + { + return InstanceId.CompareTo(other.InstanceId); + } + + /// + public int CompareTo(InteractableVehicle other) + { + return InstanceId.CompareTo(other.instanceID); + } + + public override bool Equals(object? obj) + { + return obj switch + { + WarfareVehicle v => Equals(v), + InteractableVehicle v => Equals(v), + _ => false + }; + } + + public override int GetHashCode() + { + return unchecked ( (int)InstanceId ); + } + + public void SetPosition(Vector3 position) + { + GameThread.AssertCurrent(); + + if (Vehicle.isDriven) + throw new InvalidOperationException("Vehicle is driven, unable to set position."); + + Vehicle.transform.position = position; + } + + public void SetRotation(Quaternion rotation) + { + GameThread.AssertCurrent(); + + if (Vehicle.isDriven) + throw new InvalidOperationException("Vehicle is driven, unable to set rotation."); + + Vehicle.transform.rotation = rotation; + } + + public void SetPositionAndRotation(Vector3 position, Quaternion rotation) + { + GameThread.AssertCurrent(); + + if (Vehicle.isDriven) + throw new InvalidOperationException("Vehicle is driven, unable to set position and rotation."); + + Vehicle.transform.position = position; + Vehicle.transform.rotation = rotation; + } + + Vector3 ITransformObject.Scale + { + get => Vector3.one; + set => throw new NotSupportedException(); } -} +} \ No newline at end of file