diff --git a/.github/workflows/ci-installer.yml b/.github/workflows/ci-installer.yml index bdbe5bae..5164992e 100644 --- a/.github/workflows/ci-installer.yml +++ b/.github/workflows/ci-installer.yml @@ -26,7 +26,7 @@ env: CONFIGURATION: ${{ inputs.configuration != '' && inputs.configuration || 'Release' }} DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 DOTNET_NOLOGO: 1 - INSTALLER_DOTNET_VERSION: '8.0' + INSTALLER_DOTNET_VERSION: '9.0' INSTALLER_PLATFORM: 'win-x64' jobs: diff --git a/ConsoleCommands/BaseTemplateCommand.cs b/ConsoleCommands/BaseTemplateCommand.cs index 4c0562af..e20d1f30 100644 --- a/ConsoleCommands/BaseTemplateCommand.cs +++ b/ConsoleCommands/BaseTemplateCommand.cs @@ -1,9 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Comfort.Common; -using EFT.InventoryLogic; - #nullable enable namespace EFT.Trainer.ConsoleCommands; @@ -11,25 +8,4 @@ namespace EFT.Trainer.ConsoleCommands; internal abstract class BaseTemplateCommand : ConsoleCommandWithArgument { public override string Pattern => RequiredArgumentPattern; - - protected static ItemTemplate[] FindTemplates(string searchShortNameOrTemplateId) - { - if (!Singleton.Instantiated) - return []; - - var templates = Singleton - .Instance - .ItemTemplates; - - // Match by TemplateId - if (templates.TryGetValue(searchShortNameOrTemplateId, out var template)) - return [template]; - - // Match by short name(s) - return templates - .Values - .Where(t => t.ShortNameLocalizationKey.Localized().IndexOf(searchShortNameOrTemplateId, StringComparison.OrdinalIgnoreCase) >= 0 - || t.NameLocalizationKey.Localized().IndexOf(searchShortNameOrTemplateId, StringComparison.OrdinalIgnoreCase) >= 0) - .ToArray(); - } } diff --git a/ConsoleCommands/Spawn.cs b/ConsoleCommands/Spawn.cs index 0f1f0c65..ccec5dc3 100644 --- a/ConsoleCommands/Spawn.cs +++ b/ConsoleCommands/Spawn.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -10,7 +11,6 @@ using EFT.Trainer.Features; using EFT.Trainer.Properties; using JetBrains.Annotations; -using UnityEngine; using Random = UnityEngine.Random; #nullable enable @@ -33,7 +33,7 @@ public override void Execute(Match match) return; var search = matchGroup.Value; - var templates = FindTemplates(search); + var templates = TemplateHelper.FindTemplates(search); switch (templates.Length) { @@ -54,7 +54,7 @@ public override void Execute(Match match) internal static void SpawnTemplate(string template, Player player, ConsoleCommand command, Func filter) { - var result = FindTemplates(template) + var result = TemplateHelper.FindTemplates(template) .FirstOrDefault(filter); if (result == null) @@ -79,7 +79,7 @@ private static void SpawnTemplate(ItemTemplate template, Player player, ConsoleC } else { - var itemFactory = Singleton.Instance; + var itemFactory = Singleton.Instance; var item = itemFactory.CreateItem(MongoID.Generate(), template._id, null); if (item == null) { @@ -87,13 +87,11 @@ private static void SpawnTemplate(ItemTemplate template, Player player, ConsoleC } else { - item.SpawnedInSession = true; // found in raid - _ = new TraderControllerClass(item, item.Id, item.ShortName); var go = poolManager.CreateLootPrefab(item, ECameraType.Default); go.SetActive(value: true); - var lootItem = Singleton.Instance.CreateLootWithRigidbody(go, item, item.ShortName, Singleton.Instance, randomRotation: false, null, out _); + var lootItem = Singleton.Instance.CreateLootWithRigidbody(go, item, item.ShortName, randomRotation: false, null, out _, true); var transform = player.Transform; var position = transform.position @@ -103,6 +101,9 @@ private static void SpawnTemplate(ItemTemplate template, Player player, ConsoleC lootItem.transform.SetPositionAndRotation(position, transform.rotation); lootItem.LastOwner = player; + + // setup after loot item is created, else we are hitting issues with weapon + SetupItem(itemFactory, item); } } }); @@ -110,4 +111,76 @@ private static void SpawnTemplate(ItemTemplate template, Player player, ConsoleC return Task.CompletedTask; }); } + + private static void SetupItem(ItemFactoryClass itemFactory, Item item) + { + item.SpawnedInSession = true; // found in raid + + if (item.TryGetItemComponent(out var dogtag)) + { + dogtag.AccountId = Random.Range(0, int.MaxValue).ToString(); + dogtag.ProfileId = Random.Range(0, int.MaxValue).ToString(); + dogtag.Nickname = $"Rambo{Random.Range(1, 256)}"; + dogtag.Side = Enum.GetValues(typeof(EPlayerSide)).Cast().Random(); + dogtag.Level = Random.Range(1, 69); + dogtag.Time = DateTime.Now; + dogtag.Status = "died"; + dogtag.KillerAccountId = Random.Range(0, int.MaxValue).ToString(); + dogtag.KillerProfileId = Random.Range(0, int.MaxValue).ToString(); + dogtag.KillerName = ""; + dogtag.WeaponName = ""; + } + + if (item.TryGetItemComponent(out var armorHolder)) + FillSlots(itemFactory, armorHolder.ArmorSlots); + + if (item.TryGetItemComponent(out var repairable)) + { + repairable.MaxDurability = repairable.TemplateDurability; + repairable.Durability = repairable.MaxDurability; + } + + if (item is CompoundItem compound) + FillSlots(itemFactory, compound.AllSlots); + + if (item is IAmmoContainer container) // AmmoBox or Magazine + FillStackSlot(itemFactory, container.Cartridges); + } + + private static void FillSlots(ItemFactoryClass itemFactory, IEnumerable slots) + { + foreach (var slot in slots) + { + if (slot.Items.Any()) + continue; + + var filter = slot + .Filters.FirstOrDefault()? + .Filter.Random(); + + if (filter == null) + continue; + + var item = itemFactory.CreateItem(MongoID.Generate(), filter, null); + SetupItem(itemFactory, item); + + slot.AddWithoutRestrictions(item); + } + } + + private static void FillStackSlot(ItemFactoryClass itemFactory, StackSlot slot) + { + var filter = slot + .Filters.FirstOrDefault()? + .Filter.Random(); + + if (filter == null) + return; + + while (slot.Count < slot.MaxCount) + { + var item = itemFactory.CreateItem(MongoID.Generate(), filter, null); + slot.Add(item, false); + } + } } diff --git a/ConsoleCommands/SpawnHideoutItems.cs b/ConsoleCommands/SpawnHideoutItems.cs new file mode 100644 index 00000000..f74520e0 --- /dev/null +++ b/ConsoleCommands/SpawnHideoutItems.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.Linq; +using EFT.Trainer.Extensions; +using EFT.Trainer.Features; +using EFT.Trainer.Properties; +using HarmonyLib; +using JetBrains.Annotations; + +#nullable enable + +namespace EFT.Trainer.ConsoleCommands; + +[UsedImplicitly] +internal class SpawnHideoutItems : ConsoleCommandWithoutArgument +{ + public override string Name => Strings.CommandSpawnHideoutItems; + + public override void Execute() + { + var player = GameState.Current?.LocalPlayer; + if (!player.IsValid()) + return; + + var manager = player.Profile?.WishlistManager; + if (manager == null) + return; + + // Find the obfuscated method that returns the computed hidout items + // We need to have the auto-add hideout items enabled in EFT settings + var method = AccessTools + .GetDeclaredMethods(manager.GetType()) + .FirstOrDefault(m => m.ReturnType == typeof(IEnumerable)); + + if (method?.Invoke(manager, []) is not IEnumerable templates) + return; + + foreach (var template in templates) + Spawn.SpawnTemplate(template, player, this, i => true); + } +} diff --git a/ConsoleCommands/Template.cs b/ConsoleCommands/Template.cs index 81f73492..a3e36b20 100644 --- a/ConsoleCommands/Template.cs +++ b/ConsoleCommands/Template.cs @@ -1,7 +1,6 @@ -using System.Linq; -using System.Text.RegularExpressions; -using Comfort.Common; +using System.Text.RegularExpressions; using EFT.Trainer.Extensions; +using EFT.Trainer.Features; using EFT.Trainer.Properties; using JetBrains.Annotations; @@ -20,12 +19,9 @@ public override void Execute(Match match) if (matchGroup is not { Success: true }) return; - if (!Singleton.Instantiated) - return; - var search = matchGroup.Value; - var templates = FindTemplates(search).ToArray(); + var templates = TemplateHelper.FindTemplates(search); foreach (var template in templates) AddConsoleLog(string.Format(Strings.CommandTemplateEnumerateFormat, template._id, template.ShortNameLocalizationKey.Localized().Green(), template.NameLocalizationKey.Localized())); diff --git a/ConsoleCommands/TrackList.cs b/ConsoleCommands/TrackList.cs index d074a034..67995a57 100644 --- a/ConsoleCommands/TrackList.cs +++ b/ConsoleCommands/TrackList.cs @@ -23,7 +23,7 @@ internal static void ShowTrackList(ConsoleCommand command, Features.LootItems lo command.AddConsoleLog(Strings.CommandTrackListUpdated); foreach (var templateId in lootItems.Wishlist) - command.AddConsoleLog(string.Format(Strings.CommandTrackListWishListEnumerateFormat, templateId.LocalizedShortName())); + command.AddConsoleLog(string.Format(Strings.CommandTrackListWishListEnumerateFormat, ((MongoID)templateId).LocalizedShortName())); foreach (var item in lootItems.TrackedNames) { diff --git a/Extensions/ItemExtensions.cs b/Extensions/ItemExtensions.cs index d5ffc5eb..bbad3c6b 100644 --- a/Extensions/ItemExtensions.cs +++ b/Extensions/ItemExtensions.cs @@ -21,10 +21,10 @@ public static bool IsFiltered(this Item item) if (ItemViewFactory.IsSecureContainer(item)) return true; - if (item.CurrentAddress?.Container is { ParentItem.TemplateId: KnownTemplateIds.BossContainer }) + if (item.CurrentAddress?.Container?.ParentItem?.TemplateId.ToString() == KnownTemplateIds.BossContainer) return true; - return item.TemplateId switch + return item.TemplateId.ToString() switch { KnownTemplateIds.DefaultInventory or KnownTemplateIds.Pockets => true, _ => false diff --git a/Features/Aimbot.cs b/Features/Aimbot.cs index e1933f2d..fb36956b 100644 --- a/Features/Aimbot.cs +++ b/Features/Aimbot.cs @@ -74,7 +74,7 @@ protected static bool CreateShotPrefix(object ammo, Vector3 origin, ref Vector3 } [UsedImplicitly] - protected static bool ApplyShotPrefix(DamageInfo damageInfo, EBodyPart bodyPartType, EBodyPartColliderType colliderType, EArmorPlateCollider armorPlateCollider, object shotId, Player? __instance) + protected static bool ApplyShotPrefix(DamageInfoStruct damageInfo, EBodyPart bodyPartType, EBodyPartColliderType colliderType, EArmorPlateCollider armorPlateCollider, object shotId, Player? __instance) { var feature = FeatureFactory.GetFeature(); if (feature == null || !feature.SilentAim || feature._silentAimTarget == null) diff --git a/Features/AirDrop.cs b/Features/AirDrop.cs new file mode 100644 index 00000000..f3e95605 --- /dev/null +++ b/Features/AirDrop.cs @@ -0,0 +1,31 @@ +using System.Linq; +using Comfort.Common; +using EFT.InventoryLogic; +using EFT.Trainer.Properties; +using JetBrains.Annotations; +using UnityEngine; + +#nullable enable + +namespace EFT.Trainer.Features; + +[UsedImplicitly] +internal class AirDrop : TriggerFeature +{ + public override string Name => Strings.FeatureAirDropName; + public override string Description => Strings.FeatureAirDropDescription; + + public override KeyCode Key { get; set; } = KeyCode.None; + + protected override void UpdateOnceWhenTriggered() + { + var player = GameState.Current?.LocalPlayer; + if (player == null) + return; + + if (TemplateHelper.FindTemplates(KnownTemplateIds.RedSignalFlare).FirstOrDefault() is not AmmoTemplate template) + return; + + player.HandleFlareSuccessEvent(player.Transform.position, template); + } +} diff --git a/Features/Ammunition.cs b/Features/Ammunition.cs index 8c1b6779..7f0c224f 100644 --- a/Features/Ammunition.cs +++ b/Features/Ammunition.cs @@ -40,7 +40,7 @@ private static void ShootPostfix(object shot) var magazine = weapon.GetCurrentMagazine(); if (magazine != null) { - if (magazine is CylinderMagazineClass cylinderMagazine) + if (magazine is CylinderMagazineItemClass cylinderMagazine) { // Rhino case foreach (var slot in cylinderMagazine.Camoras) @@ -62,11 +62,11 @@ private static void ShootPostfix(object shot) private static Item CreateAmmo(Item ammo) { - var instantiated = Singleton.Instantiated; + var instantiated = Singleton.Instantiated; if (!instantiated) return ammo; - var instance = Singleton.Instance; + var instance = Singleton.Instance; var itemId = Guid.NewGuid().ToString("N").Substring(0, 24); return instance.CreateItem(itemId, ammo.TemplateId, null) ?? ammo; } diff --git a/Features/Commands.cs b/Features/Commands.cs index 9af1b99d..4cb923d0 100644 --- a/Features/Commands.cs +++ b/Features/Commands.cs @@ -42,6 +42,10 @@ protected override void Update() if (!PreloaderUI.Instantiated) return; +#if DEBUG + ConsoleScreen.IAmDevShowMeLogs = true; +#endif + RegisterPropertyDisplays(); RegisterCommands(); } diff --git a/Features/Examine.cs b/Features/Examine.cs index 0dfd8cfe..00f0b83c 100644 --- a/Features/Examine.cs +++ b/Features/Examine.cs @@ -1,6 +1,8 @@ using EFT.InventoryLogic; +using EFT.Trainer.ConsoleCommands; using EFT.Trainer.Properties; using JetBrains.Annotations; +using static EFT.Player; #nullable enable @@ -22,32 +24,30 @@ protected static bool ExaminedPrefix(ref bool __result) return true; // keep using original code, we are not enabled __result = true; - return false; // skip the original code and all other prefix methods + return false; // skip the original code and all other prefix methods } +#pragma warning disable IDE0060 [UsedImplicitly] - protected static bool GetSearchStatePrefix(SearchableItemClass __instance) + protected static bool SinglePlayerInventoryControllerConstructorPrefix(Player player, Profile profile, bool isBot, ref bool examined) { var feature = FeatureFactory.GetFeature(); if (feature == null || !feature.Enabled) return true; // keep using original code, we are not enabled - var player = GameState.Current?.LocalPlayer; - if (player == null) - return true; - - __instance.UncoverAll(player.ProfileId); + // this will make the game use the passthrough type implementing IPlayerSearchController, ISearchController with all items known and searched + examined = true; return true; } - +#pragma warning restore IDE0060 protected override void UpdateWhenEnabled() { HarmonyPatchOnce(harmony => { - HarmonyPrefix(harmony, typeof(Profile), nameof(Profile.Examined), nameof(ExaminedPrefix), [typeof(string)]); + HarmonyPrefix(harmony, typeof(Profile), nameof(Profile.Examined), nameof(ExaminedPrefix), [typeof(MongoID)]); HarmonyPrefix(harmony, typeof(Profile), nameof(Profile.Examined), nameof(ExaminedPrefix), [typeof(Item)]); - HarmonyPrefix(harmony, typeof(SearchableItemClass), nameof(SearchableItemClass.GetSearchState), nameof(GetSearchStatePrefix)); + HarmonyConstructorPrefix(harmony, typeof(SinglePlayerInventoryController), nameof(SinglePlayerInventoryControllerConstructorPrefix), [typeof(Player), typeof(Profile), typeof(bool), typeof(bool)]); }); } } diff --git a/Features/Feature.cs b/Features/Feature.cs index c4b97820..efc8a2a4 100644 --- a/Features/Feature.cs +++ b/Features/Feature.cs @@ -1,4 +1,5 @@ using System; +using System.Reflection; using EFT.InputSystem; using EFT.UI; using Newtonsoft.Json; @@ -30,48 +31,58 @@ public void HarmonyPatchOnce(Action action) action(harmony); } - public void HarmonyPrefix(HarmonyLib.Harmony harmony, Type originalType, string originalMethod, string newMethod, Type[]? parameters = null) + public void HarmonyDispatch(HarmonyLib.Harmony harmony, Type originalType, string? originalMethod, string? newPrefixMethod, string? newPostfixMethod, Type[]? parameters = null) { - var original = HarmonyLib.AccessTools.Method(originalType, originalMethod, parameters); + MethodBase original = originalMethod == null + ? HarmonyLib.AccessTools.Constructor(originalType, parameters) + : HarmonyLib.AccessTools.Method(originalType, originalMethod, parameters); + if (original == null) { - AddConsoleLog(string.Format(Properties.Strings.ErrorCannotFindOriginalMethodFormat, $"{originalType}.{originalMethod}")); + AddConsoleLog(string.Format(Properties.Strings.ErrorCannotFindOriginalMethodFormat, $"{originalType}.{originalMethod ?? "ctor"}").Red()); return; } - var prefix = HarmonyLib.AccessTools.Method(GetType(), newMethod); - if (prefix == null) - { - AddConsoleLog(string.Format(Properties.Strings.ErrorCannotFindPrefixMethodFormat, newMethod)); + var prefix = GetTargetMethod(newPrefixMethod, Properties.Strings.ErrorCannotFindPrefixMethodFormat); + var postfix = GetTargetMethod(newPostfixMethod, Properties.Strings.ErrorCannotFindPostfixMethodFormat); + + if (prefix != null && postfix != null) + return; + + if (prefix == null && postfix == null) return; - } - harmony.Patch(original, prefix: new HarmonyLib.HarmonyMethod(prefix)); + harmony.Patch(original, prefix: prefix, postfix: postfix); #if DEBUG - AddConsoleLog(string.Format(Properties.Strings.DebugPatchedMethodFormat, $"{originalType}.{originalMethod}", $"{GetType()}.{newMethod}")); + AddConsoleLog(string.Format(Properties.Strings.DebugPatchedMethodFormat, $"{originalType}.{originalMethod}", $"{GetType()}.{newPrefixMethod ?? newPostfixMethod}")); #endif } - public void HarmonyPostfix(HarmonyLib.Harmony harmony, Type originalType, string originalMethod, string newMethod) + private HarmonyLib.HarmonyMethod? GetTargetMethod(string? methodName, string errorFormat) { - var original = HarmonyLib.AccessTools.Method(originalType, originalMethod); - if (original == null) - { - AddConsoleLog(string.Format(Properties.Strings.ErrorCannotFindOriginalMethodFormat, $"{originalType}.{originalMethod}")); - return; - } + if (methodName == null) + return null; - var postfix = HarmonyLib.AccessTools.Method(GetType(), newMethod); - if (postfix == null) - { - AddConsoleLog(string.Format(Properties.Strings.ErrorCannotFindPostfixMethodFormat, newMethod)); - return; - } + var method = HarmonyLib.AccessTools.Method(GetType(), methodName); + if (method == null) + AddConsoleLog(string.Format(errorFormat, methodName).Red()); - harmony.Patch(original, postfix: new HarmonyLib.HarmonyMethod(postfix)); -#if DEBUG - AddConsoleLog(string.Format(Properties.Strings.DebugPatchedMethodFormat, $"{originalType}.{originalMethod}", $"{GetType()}.{newMethod}")); -#endif + return new HarmonyLib.HarmonyMethod(method); + } + + public void HarmonyPrefix(HarmonyLib.Harmony harmony, Type originalType, string originalMethod, string newMethod, Type[]? parameters = null) + { + HarmonyDispatch(harmony, originalType, originalMethod, newPrefixMethod: newMethod, newPostfixMethod: null, parameters); + } + + public void HarmonyConstructorPrefix(HarmonyLib.Harmony harmony, Type originalType, string newMethod, Type[]? parameters) + { + HarmonyDispatch(harmony, originalType, null, newPrefixMethod: newMethod, newPostfixMethod: null, parameters); + } + + public void HarmonyPostfix(HarmonyLib.Harmony harmony, Type originalType, string originalMethod, string newMethod, Type[]? parameters = null) + { + HarmonyDispatch(harmony, originalType, originalMethod, newPrefixMethod: null, newPostfixMethod: newMethod, parameters); } protected void AddConsoleLog(string log) diff --git a/Features/Health.cs b/Features/Health.cs index 7cc5dd67..3e554565 100644 --- a/Features/Health.cs +++ b/Features/Health.cs @@ -3,6 +3,7 @@ using EFT.HealthSystem; using EFT.Trainer.Configuration; using EFT.Trainer.Extensions; +using EFT.Trainer.Model; using EFT.Trainer.Properties; using JetBrains.Annotations; @@ -33,7 +34,11 @@ internal class Health : ToggleFeature [UsedImplicitly] protected static bool ApplyDamagePrefix(EBodyPart bodyPart, ActiveHealthController? __instance, ref float __result) { - if (UseBuiltinDamageLogic(__instance?.Player, bodyPart)) + if (__instance == null) + return true; // keep using original code + + var wrapper = new ActiveHealthControllerWrapper(__instance); + if (UseBuiltinDamageLogic(wrapper.Player, bodyPart)) return true; // keep using original code __result = 0f; diff --git a/Features/Hits.cs b/Features/Hits.cs index d74c142f..7402a598 100644 --- a/Features/Hits.cs +++ b/Features/Hits.cs @@ -2,6 +2,7 @@ using EFT.HealthSystem; using EFT.Trainer.Configuration; using EFT.Trainer.Extensions; +using EFT.Trainer.Model; using EFT.Trainer.Properties; using EFT.Trainer.UI; using JetBrains.Annotations; @@ -44,10 +45,10 @@ internal class Hits : ToggleFeature public bool ShowHealthDamage { get; set; } = true; - internal class HitMarker(DamageInfo damageInfo) + internal class HitMarker(DamageInfoStruct damageInfo) { public float ElapsedTime { get; set; } = 0.0f; - public DamageInfo DamageInfo { get; set; } = damageInfo; + public DamageInfoStruct DamageInfo { get; set; } = damageInfo; public bool IsTaggedForDeletion { get; set; } = false; } @@ -55,7 +56,7 @@ internal class HitMarker(DamageInfo damageInfo) #pragma warning disable IDE0060 [UsedImplicitly] - protected static void ApplyDamagePostfix(EBodyPart bodyPart, float damage, DamageInfo damageInfo, ActiveHealthController? __instance) + protected static void ApplyDamagePostfix(EBodyPart bodyPart, float damage, DamageInfoStruct damageInfo, ActiveHealthController? __instance) { var feature = FeatureFactory.GetFeature(); if (feature == null || !feature.Enabled) @@ -64,7 +65,7 @@ protected static void ApplyDamagePostfix(EBodyPart bodyPart, float damage, Damag if (__instance == null) return; - var victim = __instance.Player; + var victim = new ActiveHealthControllerWrapper(__instance).Player; if (victim == null || victim.IsYourPlayer) return; diff --git a/Features/LootItems.cs b/Features/LootItems.cs index 1c751b5e..96061290 100644 --- a/Features/LootItems.cs +++ b/Features/LootItems.cs @@ -7,7 +7,6 @@ using EFT.Trainer.Configuration; using EFT.Trainer.Extensions; using EFT.Trainer.Properties; -using EFT.UI; using JsonType; using UnityEngine; @@ -39,6 +38,9 @@ internal class LootItems : PointOfInterests [ConfigurationProperty] public bool TrackWishlist { get; set; } = false; + [ConfigurationProperty] + public bool TrackAutoWishlist { get; set; } = false; + public override float CacheTimeInSec { get; set; } = 3f; public override Color GroupingColor => Color; @@ -71,19 +73,24 @@ public bool UnTrack(string lootname) private HashSet RefreshWishlist() { - var result = new HashSet(); - if (!TrackWishlist) - return result; + if (!TrackWishlist && !TrackAutoWishlist) + return []; - var uiContextInstance = ItemUiContext.Instance; // warning instance is a MonoBehavior so no null propagation permitted - if (uiContextInstance == null) - return result; + var player = GameState.Current?.LocalPlayer; + if (!player.IsValid()) + return []; - var rawWishList = uiContextInstance.Session?.RagFair?.Wishlist; - if (rawWishList == null) - return result; + var manager = player.Profile?.WishlistManager; + if (manager == null) + return []; - return [.. rawWishList.Keys]; + return TrackWishlist switch + { + true when TrackAutoWishlist => [.. manager.GetWishlist().Keys], // this will get user items + auto-add hideout items if enabled in settings + true when !TrackAutoWishlist => [.. manager.UserItems.Keys], // this will get user items only + false when TrackAutoWishlist => [.. manager.GetWishlist().Keys.Except(manager.UserItems.Keys)], + _ => [] + }; } public override void RefreshData(List data) diff --git a/Features/Mortar.cs b/Features/Mortar.cs new file mode 100644 index 00000000..5c9cbddf --- /dev/null +++ b/Features/Mortar.cs @@ -0,0 +1,30 @@ +using Comfort.Common; +using EFT.Trainer.Properties; +using JetBrains.Annotations; +using UnityEngine; + +#nullable enable + +namespace EFT.Trainer.Features; + +[UsedImplicitly] +internal class Mortar : TriggerFeature +{ + public override string Name => Strings.FeatureMortarName; + public override string Description => Strings.FeatureMortarDescription; + + public override KeyCode Key { get; set; } = KeyCode.None; + + protected override void UpdateOnceWhenTriggered() + { + var world = Singleton.Instance; + if (world == null) + return; + + var player = GameState.Current?.LocalPlayer; + if (player == null) + return; + + world.ServerShellingController?.StartShellingPosition(player.Transform.position); + } +} diff --git a/Features/NoFlash.cs b/Features/NoFlash.cs new file mode 100644 index 00000000..c9cc992b --- /dev/null +++ b/Features/NoFlash.cs @@ -0,0 +1,34 @@ +using EFT.Trainer.Properties; +using JetBrains.Annotations; + +#nullable enable + +namespace EFT.Trainer.Features; + +[UsedImplicitly] +internal class NoFlash : ToggleFeature +{ + public override string Name => Strings.FeatureNoFlashName; + public override string Description => Strings.FeatureNoFlashDescription; + + public override bool Enabled { get; set; } = false; + + protected override void UpdateWhenEnabled() + { + var camera = GameState.Current?.Camera; + if (camera == null) + return; + + if (camera.GetComponent() is { enabled: true } flash) + { + flash.enabled = false; + flash.EffectStrength = 0; + } + + if (camera.GetComponent() is { enabled: true } eyeburn) + { + eyeburn.enabled = false; + eyeburn.EyesBurn = false; + } + } +} diff --git a/Features/PointOfInterests.cs b/Features/PointOfInterests.cs index 5e7ee5cc..7feb2e1b 100644 --- a/Features/PointOfInterests.cs +++ b/Features/PointOfInterests.cs @@ -2,7 +2,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using System.Text; using EFT.Trainer.Configuration; using EFT.Trainer.Extensions; using EFT.Trainer.Properties; diff --git a/Features/Quests.cs b/Features/Quests.cs index 8b215a19..286615bd 100644 --- a/Features/Quests.cs +++ b/Features/Quests.cs @@ -102,7 +102,7 @@ private void RefreshFindItemLocations(QuestDataClass[] startedQuests, GameWorld { foreach (var condition in quest.Template!.Conditions[EQuestStatus.AvailableForFinish].OfType()) { - if (!condition.target.Contains(lootItem.Item.TemplateId) || quest.CompletedConditions.Contains(condition.id)) + if (!condition.target.Contains(lootItem.Item.TemplateId.ToString()) || quest.CompletedConditions.Contains(condition.id)) continue; var position = lootItem.transform.position; @@ -129,7 +129,7 @@ private void RefreshPlaceOrRepairItemLocations(QuestDataClass[] startedQuests, P if (quest.CompletedConditions.Contains(condition.id)) continue; - var result = allPlayerItems.FirstOrDefault(x => condition.target.Contains(x.TemplateId)); + var result = allPlayerItems.FirstOrDefault(x => condition.target.Contains(x.TemplateId.ToString())); if (result == null) continue; diff --git a/Features/QuickThrow.cs b/Features/QuickThrow.cs index 39bbf922..79971425 100644 --- a/Features/QuickThrow.cs +++ b/Features/QuickThrow.cs @@ -29,7 +29,7 @@ protected override void UpdateOnceWhenTriggered() var grenade = inventory .GetPlayerItems(EPlayerItems.Equipment) - .OfType() + .OfType() .FirstOrDefault(); if (grenade == null) diff --git a/Features/TemplateHelper.cs b/Features/TemplateHelper.cs new file mode 100644 index 00000000..c11ecb30 --- /dev/null +++ b/Features/TemplateHelper.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Comfort.Common; +using EFT.InventoryLogic; + +#nullable enable + +namespace EFT.Trainer.Features; + +internal class TemplateHelper +{ + // We cannot properly search by "partial" mongoId, even if we have implicit conversion to string + // So keep our own cache of templates + private static readonly Dictionary _templates = []; + + private static void UpdateTemplates() + { + if (!Singleton.Instantiated) + return; + + var mongoTemplates = Singleton + .Instance + .ItemTemplates; + + if (_templates.Count == mongoTemplates.Count) + return; + + foreach (var kv in mongoTemplates) + { + _templates.Add(kv.Key.ToString(), kv.Value); + } + } + + internal static ItemTemplate[] FindTemplates(string searchShortNameOrTemplateId) + { + UpdateTemplates(); + + // Match by TemplateId + if (_templates.TryGetValue(searchShortNameOrTemplateId, out var template)) + { + return [template]; + } + + // Match by short name(s) + return _templates + .Values + .Where(t => t.ShortNameLocalizationKey.Localized().IndexOf(searchShortNameOrTemplateId, StringComparison.OrdinalIgnoreCase) >= 0 + || t.NameLocalizationKey.Localized().IndexOf(searchShortNameOrTemplateId, StringComparison.OrdinalIgnoreCase) >= 0) + .ToArray(); + } +} diff --git a/Installer/InstallCommand.cs b/Installer/InstallCommand.cs index 0dc7659e..d781c216 100644 --- a/Installer/InstallCommand.cs +++ b/Installer/InstallCommand.cs @@ -66,6 +66,19 @@ public override async Task ExecuteAsync(CommandContext commandContext, Sett AnsiConsole.MarkupLine($"Target [green]EscapeFromTarkov ({installation.Version})[/] in [blue]{installation.Location.EscapeMarkup()}[/]."); + if (installation.UsingSptAki) + { + AnsiConsole.MarkupLine("[green][[SPT-AKI]][/] detected. Please make sure you have run the game at least once before installing the trainer."); + AnsiConsole.MarkupLine("SPT-AKI is patching binaries during the first run, and we [underline]need[/] to compile against those patched binaries."); + AnsiConsole.MarkupLine("If you install this trainer on stock binaries, we'll be unable to compile or the game will freeze at the startup screen."); + + if (installation.UsingSptAkiButNeverRun) + AnsiConsole.MarkupLine("[yellow]Warning: it seems that you have never run your SPT-AKI installation. You should quit now and rerun this installer once it's done.[/]"); + + if (!AnsiConsole.Confirm("Continue installation (yes I have run the game at least once) ?")) + return (int)ExitCode.Canceled; + } + const string features = "Features"; const string commands = "ConsoleCommands"; @@ -81,16 +94,6 @@ public override async Task ExecuteAsync(CommandContext commandContext, Sett return (int)ExitCode.CompilationFailed; } - if (installation.UsingSptAki) - { - AnsiConsole.MarkupLine("[green][[SPT-AKI]][/] detected. Please make sure you have run the game at least once before installing the trainer."); - AnsiConsole.MarkupLine("SPT-AKI is patching binaries during the first run, and we [underline]need[/] to compile against those patched binaries."); - AnsiConsole.MarkupLine("If you install this trainer on stock binaries, the game will freeze at the startup screen."); - - if (!AnsiConsole.Confirm("Continue installation (yes I have run the game at least once) ?")) - return (int)ExitCode.Canceled; - } - if (!CreateDll(installation, "NLog.EFT.Trainer.dll", dllPath => result.Compilation.Emit(dllPath, manifestResources: result.Resources))) return (int)ExitCode.CreateDllFailed; diff --git a/Installer/Installation.cs b/Installer/Installation.cs index b9475e3b..5753c162 100644 --- a/Installer/Installation.cs +++ b/Installer/Installation.cs @@ -5,7 +5,7 @@ using System.IO; using System.Linq; using System.Runtime.Versioning; -using Microsoft.Win32; +using System.Text; using Spectre.Console; namespace Installer; @@ -14,8 +14,11 @@ internal class Installation { public Version Version { get; } public bool UsingSptAki { get; private set; } + public bool UsingSptAkiButNeverRun { get; private set; } public bool UsingBepInEx { get; private set; } public string Location { get; } + public string DisplayString { get; private set; } = string.Empty; + public string Data => Path.Combine(Location, "EscapeFromTarkov_Data"); public string Managed => Path.Combine(Data, "Managed"); public string BepInEx => Path.Combine(Location, "BepInEx"); @@ -56,10 +59,10 @@ public override int GetHashCode() installations = DiscoverInstallations() .Distinct() .ToList(); - }); - if (path is not null && TryDiscoverInstallation(path, out var installation)) - installations.Add(installation); + if (path is not null && TryDiscoverInstallation(path, out var installation)) + installations.Add(installation); + }); installations = [.. installations.Distinct().OrderBy(i => i.Location)]; @@ -72,11 +75,7 @@ public override int GetHashCode() var first = installations.First(); return AnsiConsole.Confirm($"Continue with [green]EscapeFromTarkov ({first.Version})[/] in [blue]{first.Location.EscapeMarkup()}[/] ?") ? first : null; default: - var prompt = new SelectionPrompt - { - Converter = i => i.Location.EscapeMarkup(), - Title = promptTitle - }; + var prompt = new SelectionPrompt { Title = promptTitle }; prompt.AddChoices(installations); return AnsiConsole.Prompt(prompt); } @@ -91,18 +90,18 @@ private static IEnumerable DiscoverInstallations() if (TryDiscoverInstallation(Path.GetDirectoryName(AppContext.BaseDirectory), out installation)) yield return installation; - using var hive = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32); - using var eft = hive.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Uninstall\EscapeFromTarkov", false); - - if (eft == null) - yield break; + // SPT-AKI default installation path + if (TryDiscoverInstallation(Path.Combine(Path.GetPathRoot(Environment.GetFolderPath(Environment.SpecialFolder.System))!, "SPT"), out installation)) + yield return installation; - var exe = eft.GetValue("DisplayIcon") as string; - if (string.IsNullOrEmpty(exe) || !File.Exists(exe)) - yield break; + // SPT-AKI locations from MUI cache + foreach (var sptpath in Registry.GetSptAkiInstallationsFromMuiCache()) + { + if (TryDiscoverInstallation(sptpath, out installation)) + yield return installation; + } - var path = Path.GetDirectoryName(exe); - if (string.IsNullOrEmpty(path) || !Directory.Exists(path)) + if (!Registry.TryGetEscapeFromTarkovInstallationPath(out var path)) yield break; if (TryDiscoverInstallation(path, out installation)) @@ -143,8 +142,15 @@ private static bool TryDiscoverInstallation(string? path, [NotNullWhen(true)] ou var akiFolder = Path.Combine(path, "SPT_Data"); installation.UsingSptAki = Directory.Exists(akiFolder) || Directory.Exists(legacyAkiFolder); + + var battleye = Path.Combine(path, "BattlEye"); + var user = Path.Combine(path, "user"); + installation.UsingSptAkiButNeverRun = installation.UsingSptAki && (Directory.Exists(battleye) || !Directory.Exists(user)); + installation.UsingBepInEx = Directory.Exists(installation.BepInExPlugins); + installation.DisplayString = installation.ComputeDisplayString(); + return true; } catch (IOException) @@ -152,4 +158,21 @@ private static bool TryDiscoverInstallation(string? path, [NotNullWhen(true)] ou return false; } } + + private string ComputeDisplayString() + { + var sb = new StringBuilder(); + sb.Append($"{Location.EscapeMarkup()} - [[{Version}]] "); + sb.Append(UsingSptAki ? "[b]SPT-AKI[/] " : "Vanilla "); + + if (UsingSptAki && VersionChecker.IsVersionSupported(Version)) + sb.Append("[green](Supported)[/]"); + + return sb.ToString(); + } + + public override string ToString() + { + return DisplayString; + } } diff --git a/Installer/Installer.csproj b/Installer/Installer.csproj index 14b5c563..fd9464e0 100644 --- a/Installer/Installer.csproj +++ b/Installer/Installer.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net9.0 latest enable true @@ -17,28 +17,29 @@ Sebastien Lebreton https://github.com/sailro/EscapeFromTarkov-Trainer - 3.0.0.0 + 3.2.0.0 Sebastien Lebreton - + - - + + - - + + - - - + + + + - - - + + + diff --git a/Installer/Properties/PublishProfiles/FolderProfile.pubxml b/Installer/Properties/PublishProfiles/FolderProfile.pubxml index f4255b6b..72c9ee24 100644 --- a/Installer/Properties/PublishProfiles/FolderProfile.pubxml +++ b/Installer/Properties/PublishProfiles/FolderProfile.pubxml @@ -6,7 +6,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121. Release Any CPU - bin\Release\net8.0\win-x64\publish\ + bin\Release\net9.0\win-x64\publish\ FileSystem <_TargetId>Folder diff --git a/Installer/Registry.cs b/Installer/Registry.cs new file mode 100644 index 00000000..802b52ac --- /dev/null +++ b/Installer/Registry.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Runtime.Versioning; +using Microsoft.Win32; + +namespace Installer +{ + [SupportedOSPlatform("windows")] + internal class Registry + { + public static bool TryGetEscapeFromTarkovInstallationPath([NotNullWhen(true)] out string? installationPath) + { + installationPath = null; + + try + { + using var hive = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32); + using var eft = hive.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Uninstall\EscapeFromTarkov", false); + + if (eft == null) + return false; + + var exe = eft.GetValue("DisplayIcon") as string; + if (string.IsNullOrEmpty(exe) || !File.Exists(exe)) + return false; + + var path = Path.GetDirectoryName(exe); + if (string.IsNullOrEmpty(path) || !Directory.Exists(path)) + return false; + + installationPath = path; + return true; + } + catch + { + return false; + } + } + + public static IEnumerable GetSptAkiInstallationsFromMuiCache() + { + try + { + using var hive = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry32); + using var mui = hive.OpenSubKey(@"Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\MuiCache", false); + + if (mui == null) + return []; + + const string attribute = ".FriendlyAppName"; + string[] candidates = ["SPT.Launcher.exe", "SPT.Server.exe", "Aki.Launcher.exe"]; + + return mui + .GetValueNames() + .Where(v => candidates.Any(c => v.Contains($"{c}{attribute}", StringComparison.OrdinalIgnoreCase))) + .Select(v => Path.GetDirectoryName(v.Replace(attribute, string.Empty))) + .Distinct(); + } + catch + { + return []; + } + } + } +} diff --git a/Installer/VersionChecker.cs b/Installer/VersionChecker.cs new file mode 100644 index 00000000..3d8ce2f7 --- /dev/null +++ b/Installer/VersionChecker.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; + +namespace Installer; + +internal class VersionChecker +{ + + private static readonly Dictionary _versions = []; + private static readonly HttpClient _client = new(); + private static readonly SemaphoreSlim _semaphore = new(1, 1); + + public static async Task IsVersionSupportedAsync(Version version) + { + await _semaphore.WaitAsync(); + + try + { + if (_versions.TryGetValue(version, out var supported)) + return supported; + + var branch = $"dev-{version}"; + var uri = new Uri($"https://github.com/sailro/EscapeFromTarkov-Trainer/tree/{branch}"); + var result = await _client.GetAsync(uri); + _versions[version] = result.IsSuccessStatusCode; + } + catch (Exception e) + { +#if DEBUG + Spectre.Console.AnsiConsole.WriteException(e); +#endif + _ = e; + _versions[version] = false; + } + finally + { + _semaphore.Release(); + } + + return _versions[version]; + } + + public static bool IsVersionSupported(Version version) + { +#pragma warning disable VSTHRD002 + return IsVersionSupportedAsync(version) + .GetAwaiter() + .GetResult(); +#pragma warning restore VSTHRD002 + } +} diff --git a/KnownTemplateIds.cs b/KnownTemplateIds.cs index 2e875322..ba306ef1 100644 --- a/KnownTemplateIds.cs +++ b/KnownTemplateIds.cs @@ -12,11 +12,12 @@ public static class KnownTemplateIds public const string DefaultInventory = "55d7217a4bdc2d86028b456d"; public const string BossContainer = "5c0a794586f77461c458f892"; - //air drop id. After testing, only the common one is used even the air drop is the other type. public const string AirDropCommon = "6223349b3136504a544d1608"; public const string AirDropMedical = "622334c873090231d904a9fc"; public const string AirDropSupply = "622334fa3136504a544d160c"; public const string AirDropWeapon = "6223351bb5d97a7b2c635ca7"; - public static string DefaultInventoryLocalizedShortName = DefaultInventory.LocalizedShortName(); + public const string RedSignalFlare = "624c09cfbc2e27219346d955"; + + public static string DefaultInventoryLocalizedShortName = ((MongoID)DefaultInventory).LocalizedShortName(); } diff --git a/Model/ActiveHealthControllerWrapper.cs b/Model/ActiveHealthControllerWrapper.cs new file mode 100644 index 00000000..20f89f71 --- /dev/null +++ b/Model/ActiveHealthControllerWrapper.cs @@ -0,0 +1,10 @@ +using EFT.HealthSystem; + +#nullable enable + +namespace EFT.Trainer.Model; + +internal class ActiveHealthControllerWrapper(ActiveHealthController instance) : ReflectionWrapper(instance) +{ + public Player? Player => GetFieldValue(nameof(Player)); +} diff --git a/NLog.EFT.Trainer.csproj b/NLog.EFT.Trainer.csproj index 6efbaf83..f34badbe 100644 --- a/NLog.EFT.Trainer.csproj +++ b/NLog.EFT.Trainer.csproj @@ -131,6 +131,7 @@ + @@ -163,6 +164,7 @@ + @@ -185,6 +187,7 @@ + @@ -197,9 +200,11 @@ + + @@ -209,6 +214,7 @@ + @@ -236,8 +242,8 @@ - - + + diff --git a/Properties/Strings.Designer.cs b/Properties/Strings.Designer.cs index d1502a1b..d291332d 100644 --- a/Properties/Strings.Designer.cs +++ b/Properties/Strings.Designer.cs @@ -260,6 +260,15 @@ internal static string CommandSpawnBotEnumerateFormat { } } + /// + /// Looks up a localized string similar to spawnhi. + /// + internal static string CommandSpawnHideoutItems { + get { + return ResourceManager.GetString("CommandSpawnHideoutItems", resourceCulture); + } + } + /// /// Looks up a localized string similar to spawnqi. /// @@ -683,6 +692,24 @@ internal static string FeatureAimbotName { } } + /// + /// Looks up a localized string similar to Triggers an airdrop at the player's location.. + /// + internal static string FeatureAirDropDescription { + get { + return ResourceManager.GetString("FeatureAirDropDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to airdrop. + /// + internal static string FeatureAirDropName { + get { + return ResourceManager.GetString("FeatureAirDropName", resourceCulture); + } + } + /// /// Looks up a localized string similar to Unlimited ammo.. /// @@ -1115,6 +1142,24 @@ internal static string FeatureMapName { } } + /// + /// Looks up a localized string similar to Triggers a mortar strike at the player's location.. + /// + internal static string FeatureMortarDescription { + get { + return ResourceManager.GetString("FeatureMortarDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to mortar. + /// + internal static string FeatureMortarName { + get { + return ResourceManager.GetString("FeatureMortarName", resourceCulture); + } + } + /// /// Looks up a localized string similar to Night vision.. /// @@ -1151,6 +1196,24 @@ internal static string FeatureNoCollisionName { } } + /// + /// Looks up a localized string similar to No persistent flash or eye-burn effect after a flash grenade.. + /// + internal static string FeatureNoFlashDescription { + get { + return ResourceManager.GetString("FeatureNoFlashDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to noflash. + /// + internal static string FeatureNoFlashName { + get { + return ResourceManager.GetString("FeatureNoFlashName", resourceCulture); + } + } + /// /// Looks up a localized string similar to No weapon malfunctions: no misfires or failures to eject or feed. No jammed bolts or overheating.. /// @@ -1224,7 +1287,7 @@ internal static string FeatureNoVisorName { } /// - /// Looks up a localized string similar to Wallhack (you'll see Bear/Boss/Cultist/Scav/Usec with configurable colors through walls). Charms, boxes, info (weapon and health), skeletons and distance.. + /// Looks up a localized string similar to Players (you'll see Bear/Boss/Cultist/Scav/Usec with configurable colors through walls). Charms, boxes, info (weapon and health), skeletons and distance.. /// internal static string FeaturePlayersDescription { get { @@ -1242,7 +1305,7 @@ internal static string FeaturePlayersFormat { } /// - /// Looks up a localized string similar to wallhack. + /// Looks up a localized string similar to players. /// internal static string FeaturePlayersName { get { @@ -2312,6 +2375,15 @@ internal static string PropertyThickness { } } + /// + /// Looks up a localized string similar to Track Auto Wishlist. + /// + internal static string PropertyTrackAutoWishlist { + get { + return ResourceManager.GetString("PropertyTrackAutoWishlist", resourceCulture); + } + } + /// /// Looks up a localized string similar to Example: ["foo", "bar"] or with extended properties: [{"Name":"foo","Color":[1.0,0.0,0.0,1.0]},{"Name":"bar","Color":[1.0,1.0,1.0,0.8],"Rarity":"Rare"}]. /// diff --git a/Properties/Strings.fr.resx b/Properties/Strings.fr.resx index d1329f57..825bdd35 100644 --- a/Properties/Strings.fr.resx +++ b/Properties/Strings.fr.resx @@ -523,14 +523,14 @@ Les couleurs sont stockées sous la forme d'un tableau de valeurs flottantes 'RG novisor - Wallhack (vous verrez Bear/Boss/Cultist/Scav/Usec avec des couleurs configurables à travers les murs). Silhouettes, boîtes, infos (armes et santé), squelettes et distance. + Joueurs (vous verrez Bear/Boss/Cultist/Scav/Usec avec des couleurs configurables à travers les murs). Silhouettes, boîtes, infos (armes et santé), squelettes et distance. {0} {1}% {2} Weapon name, health %, distance - wallhack + players [{0}m] @@ -937,4 +937,28 @@ Les couleurs sont stockées sous la forme d'un tableau de valeurs flottantes 'RG spawnqi + + Suivre la liste de souhaits auto + + + spawnhi + + + noflash + + + Pas d'effet de flash persistant ou de brûlure oculaire après une grenade flash. + + + mortar + + + Déclenche une attaque de mortier à l'endroit où se trouve le joueur. + + + airdrop + + + Déclenche un largage aérien à l'endroit où se trouve le joueur. + \ No newline at end of file diff --git a/Properties/Strings.resx b/Properties/Strings.resx index cc86fb41..71efdc5c 100644 --- a/Properties/Strings.resx +++ b/Properties/Strings.resx @@ -523,14 +523,14 @@ Colors are stored as an array of 'RGBA' floats novisor - Wallhack (you'll see Bear/Boss/Cultist/Scav/Usec with configurable colors through walls). Charms, boxes, info (weapon and health), skeletons and distance. + Players (you'll see Bear/Boss/Cultist/Scav/Usec with configurable colors through walls). Charms, boxes, info (weapon and health), skeletons and distance. {0} {1}% {2} Weapon name, health %, distance - wallhack + players [{0}m] @@ -937,4 +937,28 @@ Colors are stored as an array of 'RGBA' floats spawnqi + + Track Auto Wishlist + + + spawnhi + + + noflash + + + No persistent flash or eye-burn effect after a flash grenade. + + + mortar + + + Triggers a mortar strike at the player's location. + + + airdrop + + + Triggers an airdrop at the player's location. + \ No newline at end of file diff --git a/Properties/Strings.zh-cn.resx b/Properties/Strings.zh-cn.resx index 3e34b1ab..e7568e69 100644 --- a/Properties/Strings.zh-cn.resx +++ b/Properties/Strings.zh-cn.resx @@ -523,7 +523,7 @@ 无视野遮挡 - 透视(通过墙壁你会看到Bear/Boss/邪教徒/Scav/Usec)。可配置颜色、描边、人物盒子、人物信息(武器和生命值),人物骨骼和距离。 + 显示玩家(通过墙壁你会看到Bear/Boss/邪教徒/Scav/Usec)。可配置颜色、描边、人物盒子、人物信息(武器和生命值),人物骨骼和距离。 @@ -531,7 +531,7 @@ Weapon name, health %, distance - 辅助视野 + 显示玩家 [{0}米] @@ -938,4 +938,28 @@ spawnqi + + 自动跟踪愿望单 + + + spawnhi + + + 不闪烁 + + + 闪光弹爆炸后不会产生持续闪光或灼伤眼睛的效果。 + + + 灰浆 + + + 触发对玩家所在位置的迫击炮攻击。 + + + 空投 + + + 触发玩家所在位置的空投。 + \ No newline at end of file diff --git a/README.md b/README.md index e09ca801..cc0228f8 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,11 @@ [![Sponsor](https://img.shields.io/badge/sponsor-%E2%9D%A4-lightgrey?logo=github&style=flat-square)](https://github.com/sponsors/sailro) -*I'm not responsible for any consequences that result from using this code. BattleState / BattlEye will ban you if you try to use it 'live'.* +*I'm not responsible for any consequences that result from using this code. BattleState / BattlEye will ban you if you try to use it 'live'. Use it safely offline with [SPT-AKI](https://sp-tarkov.com/).* ***TLDR => Use the [Universal Installer](https://github.com/sailro/EscapeFromTarkov-Trainer/releases).*** Default key for in-game GUI is `Right-Alt`. -This is an attempt -for educational purposes only- to alter a Unity game at runtime without patching the binaries (so without using [Cecil](https://github.com/jbevain/cecil) nor [Reflexil](https://github.com/sailro/reflexil)). - -`master` branch can build against `EFT 0.14.9.30626` (tested with [`spt-aki Version 3.9.8`](https://hub.sp-tarkov.com/files/file/16-spt-aki/#versions)). If you are looking for another version, see [`branches`](https://github.com/sailro/EscapeFromTarkov-Trainer/branches) and [`releases`](https://github.com/sailro/EscapeFromTarkov-Trainer/releases). +`master` branch can build against `EFT 0.15.2.32678` (tested with [`SPT-AKI Version 3.10.0`](https://hub.sp-tarkov.com/files/file/16-spt-aki/#versions)). If you are looking for another version, see [`branches`](https://github.com/sailro/EscapeFromTarkov-Trainer/branches) and [`releases`](https://github.com/sailro/EscapeFromTarkov-Trainer/releases). > If you want to compile the code yourself, make sure you cleaned-up your solution properly after upgrading your EFT/sptarkov bits (even removing `bin` and `obj` folders) and check all your references. @@ -22,6 +20,7 @@ This is an attempt -for educational purposes only- to alter a Unity game at runt | trainer.ini section | GUI/console | Description | |---------------------------|--------------|-------------| | `Aimbot` | `aimbot` | Aimbot (distance, smoothness, silent aim with speed factor and shot delay, fov radius, fov circle). | +| `AirDrop` | `airdrop` | Triggers an airdrop at the player's location. | | `Ammunition` | `ammo` | Unlimited ammo. | | `AutomaticGun` | `autogun` | Force all guns (even bolt action guns) to use automatic firing mode with customizable fire rate. | | `Commands` | `commands` | Popup window to enable/disable all features (use right-alt or setup your own key in [trainer.ini](#sample-trainerini-configuration-file)). | @@ -40,13 +39,15 @@ This is an attempt -for educational purposes only- to alter a Unity game at runt | `LootableContainers` | `stash` | Hidden/special stashes like buried barrels, ground caches, air drops or corpses. | | `LootItems` | `loot` | List all lootable items and track any item by name or rarity or in-game wishlist in raid (even in containers and corpses). | | `Map` | `map` | Full screen map with radar esp. | +| `Mortar` | `mortar` | Triggers a mortar strike at the player's location. | | `NightVision` | `night` | Night vision. | | `NoCollision` | `nocoll` | No physical collisions, making you immune to bullets, grenades and barbed wires. | +| `NoFlash` | `noflash` | No persistent flash or eye-burn effect after a flash grenade. | | `NoMalfunctions` | `nomal` | No weapon malfunctions: no misfires or failures to eject or feed. No jammed bolts or overheating. | | `NoRecoil` | `norecoil` | No recoil. | | `NoSway` | `nosway` | No sway. | | `NoVisor` | `novisor` | No visor, so even when using a face shield-visor you won't see it. | -| `Players` | `wallhack` | Wallhack (you'll see Bear/Boss/Cultist/Scav/Usec with configurable colors through walls). Charms, boxes, info (weapon and health), skeletons and distance. | +| `Players` | `players` | Players (you'll see Bear/Boss/Cultist/Scav/Usec with configurable colors through walls). Charms, boxes, info (weapon and health), skeletons and distance. | | `Quests` | `quest` | Locations for taking/placing quest items. Only items related to your started quests are displayed. | | `QuickThrow` | `quickthrow` | Quick-throw grenades. | | `Radar` | `radar` | 2D radar. | @@ -61,7 +62,7 @@ This is an attempt -for educational purposes only- to alter a Unity game at runt You can Load/Save all settings using the `console` or the `GUI`. -![Wallhack](https://user-images.githubusercontent.com/638167/222186879-a88a267e-16ba-4532-85ec-8cb385737947.png) +![Players](https://user-images.githubusercontent.com/638167/222186879-a88a267e-16ba-4532-85ec-8cb385737947.png) ![Radar](https://user-images.githubusercontent.com/638167/222524208-589dc7ff-f053-4b0c-902b-49fa8d1f7ddd.png) ![Map](https://user-images.githubusercontent.com/769465/224330696-d09960a2-8940-4980-8489-0533b44534f9.png) ![Exfils](https://user-images.githubusercontent.com/638167/135586735-143ab160-ca20-4ec9-8ad4-9ce7bde58295.png) @@ -76,26 +77,6 @@ You can Load/Save all settings using the `console` or the `GUI`. Simply use the [Universal Installer](https://github.com/sailro/EscapeFromTarkov-Trainer/releases). -## Manual installation - -You can try to compile the code yourself (you will need a recent Visual Studio, because we are using CSharp 9). You can use a precompiled release as well. - -Copy all files in your EFT directory like `C:\Battlestate Games\EFT`: - -- `EscapeFromTarkov_Data\Managed\NLog.EFT.Trainer.dll` (this is the compiled code for the trainer) -- `EscapeFromTarkov_Data\outline` (this is the dedicated shader we use to outline players [wallhack]) - -### If you are using the Live version (you should NOT do that, you'll be detected and banned): - -Rename `EscapeFromTarkov_Data\Managed\NLog.dll.nlog-live` to `NLog.dll.nlog`. This will work only for legacy versions. Given EscapeFromTarkov `0.13.0.21531` or later prevent this trainer to be loaded using NLog configuration. It is now mandatory to use SPT-AKI/BepInEx for recent versions. - -### If you are using sptarkov (https://www.sp-tarkov.com): - -Overwrite the existing `EscapeFromTarkov_Data\Managed\NLog.dll.nlog` using `NLog.dll.nlog-sptarkov`, or update the existing file accordingly. We must include the following -`` in the `targets` section for the trainer to be loaded properly. This is for legacy versions before EscapeFromTarkov `0.13.0.21531`. - -For newer versions, copy `aki-efttrainer.dll` (this is the compiled code for the BepInEx plugin) to `BepInEx\plugins`. - ## Configuration ![console](https://user-images.githubusercontent.com/638167/149630825-7d76b102-0836-4eb9-a27f-d33fb519452f.png) @@ -126,16 +107,19 @@ This trainer hooks into the command system, so you can easily setup features usi | loot | `on` or `off` | | Show/Hide tracked items | | night | `on` or `off` | `off` | Enable/Disable night vision | | nocoll | `on` or `off` | `off` | Disable/Enable physical collisions | +| noflash | `on` or `off` | `off` | Disable/Enable flash/eyeburn effects | | nomal | `on` or `off` | `off` | Disable/Enable weapon malfunctions | | norecoil | `on` or `off` | `off` | Disable/Enable recoil | | nosway | `on` or `off` | `off` | Disable/Enable sway | | novisor | `on` or `off` | `off` | Disable/Enable visor | +| players | `on` or `off` | `on` | Show/hide players | | quest | `on` or `off` | `off` | Show/Hide quest POI | | radar | `on` or `off` | `off` | Show/Hide radar | | save | | | Save settings to `trainer.ini` | | savetl | `[filename]` | | Save current tracklist to file | | spawn | `[name]` | | Spawn object in front of player | | spawnbot | `[name]` or `*` | | Spawn a bot, ex `spawnbot bossKilla` | +| spawnhi | | | Spawn required hideout items | | spawnqi | | | Spawn items-to-find in active quests | | stamina | `on` or `off` | `off` | Enable/Disable unlimited stamina | | stash | `on` or `off` | `off` | Show/Hide stashes | @@ -149,7 +133,6 @@ This trainer hooks into the command system, so you can easily setup features usi | tracksr | same as `track` | | Track super rare items only | | tracklist | | | Show tracked items | | untrack | `[name]` or `*` | | Untrack a `name` or `*` for all | -| wallhack | `on` or `off` | `on` | Show/hide players | | wallshoot | `on` or `off` | `on` | Enable/Disable shoot through walls | ## Translations