<commit_before>#load "common.csx"
#load "colors.csx"
#load "Specs.csx"
#load "ignore.csx"
#load "equip.csx"
#load "warehouse.csx"

using System;
using System.Linq;
using System.Collections.Generic;
using System.Threading;
using Infusion.Commands;

public static class Looting
{
    // Create a "private" instance of journal for cooking. If you delete this journal it
    // doesn't affect either UO.Journal or other instances of SpeechJournal so
    // you can try looting and healing in parallel
    // and you can still use journal.Delete method in both scripts at the same time.
    // It means, that you don't need tricks like UO.SetJournalLine(number,text) in
    // Injection.
    private static SpeechJournal journal = UO.CreateSpeechJournal();

    public static Warehouse Warehouse { get; set; } = new Warehouse();

    public static ItemSpec UselessLoot { get; } = new[]
    {
        Specs.Club, Specs.SpruceArrow, Specs.TightBoots, Specs.HorseShoes,
        Specs.Torsos, Specs.Rocks, Specs.Corpse,
        // ignoring some "invisible" items
        Specs.Hairs
    };
    
    public static ItemSpec InterestingLoot { get; set; } = new[]
    {
        Specs.Gold, Specs.Regs, Specs.Gem, Specs.Ammunition,
        Specs.MagickyPytlik, Specs.MagickyVacek, Specs.TajemnaMapa,
        Specs.Food, Specs.VanocniBanka
    };
    
    public static ItemSpec DungeonLoot { get; set; } = new[]
    {
        Specs.Gold, Specs.Regs, Specs.Gem, Specs.Ammunition,
        Specs.MagickyPytlik, Specs.MagickyVacek, Specs.TajemnaMapa, Specs.VanocniBanka, Specs.VelikonocniVejce
    };
    
    public static MobileSpec NotRippableCorpses { get; set; } = new[] { Specs.Mounts };

    public static ScriptTrace Trace { get; } = UO.Trace.Create();

    public static ItemSpec IgnoredLoot { get; set; } = UselessLoot;
    public static ItemSpec AllowedLoot { get; set; } = null;
    public static ItemSpec OnGroundLoot { get; set; } = InterestingLoot;
    public static ItemSpec HumanCorpseLoot { get; set; } = OnGroundLoot;

    public static ObjectId? LootContainerId { get; set; }
    public static ItemSpec LootContainerSpec { get; set; }
    public static ItemSpec KnivesSpec { get; set; } = Specs.Knives;
    
    public static event Action<ObjectId> CorpseRipped;
    public static event Action<ObjectId> CorpseLooted;
    
    public static IgnoredItems lootedCorpses = new IgnoredItems();
    public static IgnoredItems rippedCorpses = new IgnoredItems();

    public static IEnumerable<Corpse> GetLootableCorpses()
    {
        var corpses = UO.Corpses
            .MaxDistance(20)
            .Where(x => !lootedCorpses.IsIgnored(x))
            .OrderByDistance().ToArray();

        return corpses;
    }

    public static IEnumerable<Corpse> GetUnrippedCorpses()
    {
        var corpses = UO.Corpses
            .MaxDistance(20)
            .Where(x => !rippedCorpses.IsIgnored(x.Id))
            .OrderByDistance().ToArray();

        return corpses;
    }
    
    private static EquipmentSet previousEquipmentSet;
    
    public static void RipAndLoot(Corpse corpse)
    {
        var handEquipmentSet = Equip.GetHand();
        if (handEquipmentSet.Any())
        {
            if (previousEquipmentSet != null)
                Trace.Log($"Previous equipment: {previousEquipmentSet.ToString() ?? "null"}");
            else
                Trace.Log("No previous equipment");
                
            Trace.Log($"Current equipment: {handEquipmentSet}");            
            previousEquipmentSet = handEquipmentSet;
        }
        else
            UO.Log("Cannot find equipment item.");


        bool humanCorpseLooting = Specs.Player.Matches(corpse.CorpseType);

        try
        {
            if (!humanCorpseLooting)
            {
                Rip(corpse);
                Loot(corpse);
            }
            else
            {
                Loot(corpse);
                Rip(corpse);
            }
        }
        catch (Exception ex)
        {
            UO.Log($"Cannot loot corpse: {ex.Message}");
        }

        if (previousEquipmentSet != null)
        {
            Equip.Set(previousEquipmentSet);
        }

        LootGround();
    }
    
    public static void RipAndLootNearest()
    {
        var corpses = GetLootableCorpses().ToArray();
        
        if (!corpses.Any())
            corpses = GetUnrippedCorpses().ToArray();

        var corpse = corpses.MaxDistance(3).FirstOrDefault();

        if (corpse != null)
        {
            var handEquipmentSet = Equip.GetHand();
            if (handEquipmentSet.Any())
            {
                if (previousEquipmentSet != null)
                    Trace.Log($"Previous equipment: {previousEquipmentSet.ToString() ?? "null"}");
                else
                    Trace.Log("No previous equipment");
                    
                Trace.Log($"Current equipment: {handEquipmentSet}");            
                previousEquipmentSet = handEquipmentSet;
            }
            else
                UO.Log("Cannot find equipment item.");

            bool humanCorpseLooting = Specs.Player.Matches(corpse.CorpseType);

            try
            {
                if (humanCorpseLooting)
                {
                    Loot(corpse);
                    Rip(corpse);
                }
                else
                {
                    Rip(corpse);
                    Loot(corpse);
                }
                if (corpses.Length - 1 > 0)
                    HighlightLootableCorpses(corpses.Except(new[] { corpse }));
                else
                    UO.ClientPrint($"No more corpses to loot remaining");
            }
            catch (Exception ex)
            {
                UO.Log($"Cannot loot corpse: {ex.Message}");
            }
            finally
            {
                // It seems that re-wearing an item directly
                // after ripping a body and right before
                // looting may crash the game client.
                if (previousEquipmentSet != null)
                {
                    Equip.Set(previousEquipmentSet);
                }
            }
        }
        else
        {
            if (corpses.Length > 0)
            {
                HighlightLootableCorpses(corpses);
                UO.ClientPrint($"No corpse reacheable but {corpses.Length} corpses to loot remaining");
            }
            else
                UO.ClientPrint("no corpse found");
        }        

        LootGround();
    }
    
    private static IEnumerable<Item> GetLootableItems(Item corpse) =>
        UO.Items.InContainer(corpse)
                .Where(i => IsLootable(i));
        
    private static bool IsLootable(Item item)
        => AllowedLoot != null ? AllowedLoot.Matches(item) : !IgnoredLoot.Matches(item);

    private static bool IsLootableFromHuman(Item item)
        => AllowedLoot != null ? AllowedLoot.Matches(item) : !IgnoredLoot.Matches(item) && HumanCorpseLoot.Matches(item);

    public static void HighlightLootableCorpses(IEnumerable<Item> lootableCorpses)
    {
        UO.ClientPrint($"{lootableCorpses.Count()} corpses to loot remaining");
        foreach (var corpse in lootableCorpses)
        {
            int lootableItemsCount = GetLootableItems(corpse).Count();
            UO.ClientPrint($"--{lootableItemsCount}--", "System", corpse.Id, corpse.Type,
                SpeechType.Speech, Colors.Green, log: false);
        }
    }

    public static void LootNearest()
    {
        LootGround();

        var corpses = GetLootableCorpses().ToArray();
        var corpse = corpses.MaxDistance(3).FirstOrDefault();

        if (corpse != null)
        {
            Loot(corpse);
            if (corpses.Length - 1 > 0)
                HighlightLootableCorpses(corpses.Except(new[] { corpse }));
            else
                UO.ClientPrint($"No more corpses to loot remaining");
        }
        else
        {
            if (corpses.Length > 0)
            {
                UO.ClientPrint($"No corpse reacheable but {corpses.Length} corpses to loot remaining");
                HighlightLootableCorpses(corpses);
            }
            else
                UO.ClientPrint("no corpse found");
        }
    }

    private static Item lootContainer;
    
    public static Item LootContainer
    {
        get
        {        
            if (LootContainerId.HasValue)
            {
                lootContainer = UO.Items[LootContainerId.Value];
                if (lootContainer != null 
                    && (lootContainer.Id != UO.Me.BackPack.Id
                    && lootContainer.ContainerId != UO.Me.BackPack.Id))
                {
                    UO.ClientPrint($"Selected looting container must be in backpack! Trying to select another container.",
                        UO.Me, Colors.Red);
                    lootContainer = null;                        
                }
            }
            
            if (LootContainerSpec != null)
            {
                var potentialLootContainer = UO.Items
                    .InContainer(UO.Me.BackPack)
                    .Matching(LootContainerSpec)
                    .FirstOrDefault();
                
                if (potentialLootContainer != null)
                {
                    if (lootContainer == null)
                    {
                        lootContainer = potentialLootContainer;
                        UO.Log($"{LootContainerId?.ToString() ?? "null"} {lootContainer.Id}");
                        if (LootContainerId != lootContainer.Id)
                        {
                            UO.ClientPrint("Default loot container selected from your backpack.");
                            UO.Click(lootContainer);
                        }
                    }
                    else if (lootContainer.Id == UO.Me.BackPack.Id)
                    {
                        // override backpack with a new container matching LootContainerSpec
                        lootContainer = potentialLootContainer;
                    }
                }
            }
                            
            if (lootContainer == null)
            {
                lootContainer = Common.AskForContainer("Select loot container", UO.Me, Colors.Red);
                if (lootContainer == null)
                {
                    UO.ClientPrint("Loot container selection canceled, looting to backpack.", UO.Me, Colors.Red);
                    UO.ClientPrint("Type `,reload` To select other other looting container.");
                    lootContainer = UO.Me.BackPack;
                }
            }
            
            LootContainerId = lootContainer.Id;
            
            return lootContainer;
        }
    }

    public static void LootGround()
    {
        var itemsOnGround = UO.Items.Matching(OnGroundLoot).MaxDistance(3).OrderByDistance().ToArray();
        if (itemsOnGround.Length > 0)
            UO.ClientPrint("Looting ground");

        foreach (var item in itemsOnGround)
        {
            if (!Items.TryMoveItem(item, LootContainer))
            {
                UO.ClientPrint("Cannot pickup item, cancelling ground loot");
                break;
            }
        }
    }

    public static void Loot()
    {
        var container = UO.AskForItem();
        if (container != null)
        {
            Loot(container);
        }
        else
            UO.ClientPrint("no container for loot");
    }

    public static void Loot(ObjectId containerId)
    {
        var container = UO.Items[containerId];
        if (container == null)
            UO.ClientPrint($"Cannot find {containerId} container");

        Loot(container);
    }

    public static void Loot(Item container)
    {
        var originalLocation = UO.Me.Location;

        UO.Use(container);
        if (!Common.WaitForContainer())
        {
            UO.Log("Cannot open body, maybe it is not possible to loot the body");
            return;
        }

        UO.ClientPrint($"Number of items in container: {UO.Items.InContainer(container).Count()}");

        Func<Item, bool> isLootableFunc = IsLootable;
        if (container is Corpse containerCorpse)
        {
            UO.Log(containerCorpse.CorpseType.ToString());
            if (Specs.Player.Matches(containerCorpse.CorpseType))
            {
                isLootableFunc = IsLootableFromHuman;
            }
        }

        foreach (var itemToPickup in UO.Items.InContainer(container).ToArray())
        {
            if (container.GetDistance(UO.Me.Location) > 4)
            {
                UO.ClientPrint("corpse too far away, cancelling loot", "looting", UO.Me);
                return;
            }
        
            if (isLootableFunc(itemToPickup))
            {
                Trace.Log($"Looting {Specs.TranslateToName(itemToPickup)} ({itemToPickup.Amount})");
                if (!Items.TryMoveItem(itemToPickup, LootContainer))
                {
                    UO.ClientPrint($"Cannot pickup an item {Specs.TranslateToName(itemToPickup)} ({itemToPickup.Amount})");
                    return;
                }<commit_msg>Do not confirm item pick up and drop when looting.<commit_after>#load "common.csx"
#load "colors.csx"
#load "Specs.csx"
#load "ignore.csx"
#load "equip.csx"
#load "warehouse.csx"

using System;
using System.Linq;
using System.Collections.Generic;
using System.Threading;
using Infusion.Commands;

public static class Looting
{
    // Create a "private" instance of journal for cooking. If you delete this journal it
    // doesn't affect either UO.Journal or other instances of SpeechJournal so
    // you can try looting and healing in parallel
    // and you can still use journal.Delete method in both scripts at the same time.
    // It means, that you don't need tricks like UO.SetJournalLine(number,text) in
    // Injection.
    private static SpeechJournal journal = UO.CreateSpeechJournal();

    public static Warehouse Warehouse { get; set; } = new Warehouse();

    public static ItemSpec UselessLoot { get; } = new[]
    {
        Specs.Club, Specs.SpruceArrow, Specs.TightBoots, Specs.HorseShoes,
        Specs.Torsos, Specs.Rocks, Specs.Corpse,
        // ignoring some "invisible" items
        Specs.Hairs
    };
    
    public static ItemSpec InterestingLoot { get; set; } = new[]
    {
        Specs.Gold, Specs.Regs, Specs.Gem, Specs.Ammunition,
        Specs.MagickyPytlik, Specs.MagickyVacek, Specs.TajemnaMapa,
        Specs.Food, Specs.VanocniBanka
    };
    
    public static ItemSpec DungeonLoot { get; set; } = new[]
    {
        Specs.Gold, Specs.Regs, Specs.Gem, Specs.Ammunition,
        Specs.MagickyPytlik, Specs.MagickyVacek, Specs.TajemnaMapa, Specs.VanocniBanka, Specs.VelikonocniVejce
    };
    
    public static MobileSpec NotRippableCorpses { get; set; } = new[] { Specs.Mounts };

    public static ScriptTrace Trace { get; } = UO.Trace.Create();

    public static ItemSpec IgnoredLoot { get; set; } = UselessLoot;
    public static ItemSpec AllowedLoot { get; set; } = null;
    public static ItemSpec OnGroundLoot { get; set; } = InterestingLoot;
    public static ItemSpec HumanCorpseLoot { get; set; } = OnGroundLoot;

    public static ObjectId? LootContainerId { get; set; }
    public static ItemSpec LootContainerSpec { get; set; }
    public static ItemSpec KnivesSpec { get; set; } = Specs.Knives;
    
    public static event Action<ObjectId> CorpseRipped;
    public static event Action<ObjectId> CorpseLooted;
    
    public static IgnoredItems lootedCorpses = new IgnoredItems();
    public static IgnoredItems rippedCorpses = new IgnoredItems();

    public static IEnumerable<Corpse> GetLootableCorpses()
    {
        var corpses = UO.Corpses
            .MaxDistance(20)
            .Where(x => !lootedCorpses.IsIgnored(x))
            .OrderByDistance().ToArray();

        return corpses;
    }

    public static IEnumerable<Corpse> GetUnrippedCorpses()
    {
        var corpses = UO.Corpses
            .MaxDistance(20)
            .Where(x => !rippedCorpses.IsIgnored(x.Id))
            .OrderByDistance().ToArray();

        return corpses;
    }
    
    private static EquipmentSet previousEquipmentSet;
    
    public static void RipAndLoot(Corpse corpse)
    {
        var handEquipmentSet = Equip.GetHand();
        if (handEquipmentSet.Any())
        {
            if (previousEquipmentSet != null)
                Trace.Log($"Previous equipment: {previousEquipmentSet.ToString() ?? "null"}");
            else
                Trace.Log("No previous equipment");
                
            Trace.Log($"Current equipment: {handEquipmentSet}");            
            previousEquipmentSet = handEquipmentSet;
        }
        else
            UO.Log("Cannot find equipment item.");


        bool humanCorpseLooting = Specs.Player.Matches(corpse.CorpseType);

        try
        {
            if (!humanCorpseLooting)
            {
                Rip(corpse);
                Loot(corpse);
            }
            else
            {
                Loot(corpse);
                Rip(corpse);
            }
        }
        catch (Exception ex)
        {
            UO.Log($"Cannot loot corpse: {ex.Message}");
        }

        if (previousEquipmentSet != null)
        {
            Equip.Set(previousEquipmentSet);
        }

        LootGround();
    }
    
    public static void RipAndLootNearest()
    {
        var corpses = GetLootableCorpses().ToArray();
        
        if (!corpses.Any())
            corpses = GetUnrippedCorpses().ToArray();

        var corpse = corpses.MaxDistance(3).FirstOrDefault();

        if (corpse != null)
        {
            var handEquipmentSet = Equip.GetHand();
            if (handEquipmentSet.Any())
            {
                if (previousEquipmentSet != null)
                    Trace.Log($"Previous equipment: {previousEquipmentSet.ToString() ?? "null"}");
                else
                    Trace.Log("No previous equipment");
                    
                Trace.Log($"Current equipment: {handEquipmentSet}");            
                previousEquipmentSet = handEquipmentSet;
            }
            else
                UO.Log("Cannot find equipment item.");

            bool humanCorpseLooting = Specs.Player.Matches(corpse.CorpseType);

            try
            {
                if (humanCorpseLooting)
                {
                    Loot(corpse);
                    Rip(corpse);
                }
                else
                {
                    Rip(corpse);
                    Loot(corpse);
                }
                if (corpses.Length - 1 > 0)
                    HighlightLootableCorpses(corpses.Except(new[] { corpse }));
                else
                    UO.ClientPrint($"No more corpses to loot remaining");
            }
            catch (Exception ex)
            {
                UO.Log($"Cannot loot corpse: {ex.Message}");
            }
            finally
            {
                // It seems that re-wearing an item directly
                // after ripping a body and right before
                // looting may crash the game client.
                if (previousEquipmentSet != null)
                {
                    Equip.Set(previousEquipmentSet);
                }
            }
        }
        else
        {
            if (corpses.Length > 0)
            {
                HighlightLootableCorpses(corpses);
                UO.ClientPrint($"No corpse reacheable but {corpses.Length} corpses to loot remaining");
            }
            else
                UO.ClientPrint("no corpse found");
        }        

        LootGround();
    }
    
    private static IEnumerable<Item> GetLootableItems(Item corpse) =>
        UO.Items.InContainer(corpse)
                .Where(i => IsLootable(i));
        
    private static bool IsLootable(Item item)
        => AllowedLoot != null ? AllowedLoot.Matches(item) : !IgnoredLoot.Matches(item);

    private static bool IsLootableFromHuman(Item item)
        => AllowedLoot != null ? AllowedLoot.Matches(item) : !IgnoredLoot.Matches(item) && HumanCorpseLoot.Matches(item);

    public static void HighlightLootableCorpses(IEnumerable<Item> lootableCorpses)
    {
        UO.ClientPrint($"{lootableCorpses.Count()} corpses to loot remaining");
        foreach (var corpse in lootableCorpses)
        {
            int lootableItemsCount = GetLootableItems(corpse).Count();
            UO.ClientPrint($"--{lootableItemsCount}--", "System", corpse.Id, corpse.Type,
                SpeechType.Speech, Colors.Green, log: false);
        }
    }

    public static void LootNearest()
    {
        LootGround();

        var corpses = GetLootableCorpses().ToArray();
        var corpse = corpses.MaxDistance(3).FirstOrDefault();

        if (corpse != null)
        {
            Loot(corpse);
            if (corpses.Length - 1 > 0)
                HighlightLootableCorpses(corpses.Except(new[] { corpse }));
            else
                UO.ClientPrint($"No more corpses to loot remaining");
        }
        else
        {
            if (corpses.Length > 0)
            {
                UO.ClientPrint($"No corpse reacheable but {corpses.Length} corpses to loot remaining");
                HighlightLootableCorpses(corpses);
            }
            else
                UO.ClientPrint("no corpse found");
        }
    }

    private static Item lootContainer;
    
    public static Item LootContainer
    {
        get
        {        
            if (LootContainerId.HasValue)
            {
                lootContainer = UO.Items[LootContainerId.Value];
                if (lootContainer != null 
                    && (lootContainer.Id != UO.Me.BackPack.Id
                    && lootContainer.ContainerId != UO.Me.BackPack.Id))
                {
                    UO.ClientPrint($"Selected looting container must be in backpack! Trying to select another container.",
                        UO.Me, Colors.Red);
                    lootContainer = null;                        
                }
            }
            
            if (LootContainerSpec != null)
            {
                var potentialLootContainer = UO.Items
                    .InContainer(UO.Me.BackPack)
                    .Matching(LootContainerSpec)
                    .FirstOrDefault();
                
                if (potentialLootContainer != null)
                {
                    if (lootContainer == null)
                    {
                        lootContainer = potentialLootContainer;
                        UO.Log($"{LootContainerId?.ToString() ?? "null"} {lootContainer.Id}");
                        if (LootContainerId != lootContainer.Id)
                        {
                            UO.ClientPrint("Default loot container selected from your backpack.");
                            UO.Click(lootContainer);
                        }
                    }
                    else if (lootContainer.Id == UO.Me.BackPack.Id)
                    {
                        // override backpack with a new container matching LootContainerSpec
                        lootContainer = potentialLootContainer;
                    }
                }
            }
                            
            if (lootContainer == null)
            {
                lootContainer = Common.AskForContainer("Select loot container", UO.Me, Colors.Red);
                if (lootContainer == null)
                {
                    UO.ClientPrint("Loot container selection canceled, looting to backpack.", UO.Me, Colors.Red);
                    UO.ClientPrint("Type `,reload` To select other other looting container.");
                    lootContainer = UO.Me.BackPack;
                }
            }
            
            LootContainerId = lootContainer.Id;
            
            return lootContainer;
        }
    }

    public static void LootGround()
    {
        var itemsOnGround = UO.Items.Matching(OnGroundLoot).MaxDistance(3).OrderByDistance().ToArray();
        if (itemsOnGround.Length > 0)
            UO.ClientPrint("Looting ground");

        foreach (var item in itemsOnGround)
        {
            UO.DragItem(item);
            UO.Wait(10);
            UO.DropItem(item, LootContainer);
            UO.Wait(10);
        }
    }

    public static void Loot()
    {
        var container = UO.AskForItem();
        if (container != null)
        {
            Loot(container);
        }
        else
            UO.ClientPrint("no container for loot");
    }

    public static void Loot(ObjectId containerId)
    {
        var container = UO.Items[containerId];
        if (container == null)
            UO.ClientPrint($"Cannot find {containerId} container");

        Loot(container);
    }

    public static void Loot(Item container)
    {
        var originalLocation = UO.Me.Location;

        UO.Use(container);
        if (!Common.WaitForContainer())
        {
            UO.Log("Cannot open body, maybe it is not possible to loot the body");
            return;
        }

        UO.ClientPrint($"Number of items in container: {UO.Items.InContainer(container).Count()}");

        Func<Item, bool> isLootableFunc = IsLootable;
        if (container is Corpse containerCorpse)
        {
            UO.Log(containerCorpse.CorpseType.ToString());
            if (Specs.Player.Matches(containerCorpse.CorpseType))
            {
                isLootableFunc = IsLootableFromHuman;
            }
        }

        foreach (var itemToPickup in UO.Items.InContainer(container).ToArray())
        {
            if (container.GetDistance(UO.Me.Location) > 4)
            {
                UO.ClientPrint("corpse too far away, cancelling loot", "looting", UO.Me);
                return;
            }
        
            if (isLootableFunc(itemToPickup))
            {
                Trace.Log($"Looting {Specs.TranslateToName(itemToPickup)} ({itemToPickup.Amount})");
                UO.DragItem(itemToPickup);
                UO.Wait(10);
                UO.DropItem(itemToPickup, LootContainer);
                UO.Wait(10);
{'fraction_non_alphanumeric': 0.0952797868290826, 'fraction_numerical': 0.0014465169394746859, 'mean_word_length': 1.5783688291294533, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 41, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1346970', 'n_tokens_mistral': 7611, 'n_tokens_neox': 7156, 'n_words': 1561}