using System;
using System.Collections.Generic;
using System.Linq;
using ACE.Common;
using ACE.Common.Extensions;
using ACE.Database;
using ACE.Database.Models.World;
using ACE.Entity.Enum;
namespace ACE.Server.WorldObjects
{
partial class Creature
{
///
/// Determines the monster inventory items to wield
///
public List SelectWieldedTreasure()
{
/*foreach (var item in Inventory.Values)
Console.WriteLine($"{item.Name} - {item.WeenieType}");*/
var wieldedClothing = SelectWieldedClothing();
var wieldedArmor = SelectWieldedArmor();
var wieldedWeapons = SelectWieldedWeapons();
var wieldedShield = SelectWieldedShield();
if (wieldedShield != null && (wieldedWeapons.Count == 0 || !wieldedWeapons[0].IsRanged))
wieldedWeapons.Add(wieldedShield);
return wieldedWeapons;
}
///
/// Selects the clothing to wear from a monster's inventory
///
public List SelectWieldedClothing()
{
var clothing = GetInventoryItemsOfTypeWeenieType(WeenieType.Clothing).Where(c => ((uint)(c.ClothingPriority ?? 0) & (uint)CoverageMaskHelper.Underwear) != 0 || ((uint)(c.ValidLocations ?? 0) & (uint)EquipMask.Cloak) != 0).ToList();
if (clothing.Count == 0) return new List();
// sort by # of areas covered
// prioritize clothing that covers more areas
// ie., a shirt that covers both upper arms and lower arms
clothing.Sort(ValidLocationComparer);
clothing.Reverse();
var shirts = clothing.Where(c => ((uint)(c.ClothingPriority ?? 0) & (uint)CoverageMaskHelper.UnderwearShirt) != 0).ToList();
var pants = clothing.Where(c => ((uint)(c.ClothingPriority ?? 0) & (uint)CoverageMaskHelper.UnderwearPants) != 0).ToList();
/*Console.WriteLine("\nSelectWieldedClothing\nShirts:");
foreach (var item in shirts)
DebugArmorClothing(item);
Console.WriteLine("Pants:");
foreach (var item in pants)
DebugArmorClothing(item);*/
// try to equip the clothing at top of lists
var equipped = new List();
if (pants.Count > 0)
{
var item = pants[0];
TryRemoveFromInventory(item.Guid);
if (TryWieldObjectWithBroadcasting(item, item.ValidLocations ?? 0))
equipped.Add(item);
}
if (shirts.Count > 0)
{
var item = shirts[0];
TryRemoveFromInventory(item.Guid);
if (TryWieldObjectWithBroadcasting(item, item.ValidLocations ?? 0))
equipped.Add(item);
}
var cloaks = clothing.Where(c => ((uint)(c.ValidLocations ?? 0) & (uint)EquipMask.Cloak) != 0).ToList();
if (cloaks.Count > 0)
{
var item = cloaks[0];
TryRemoveFromInventory(item.Guid);
if (TryWieldObjectWithBroadcasting(item, item.ValidLocations ?? 0))
equipped.Add(item);
}
return equipped;
}
public List SelectWieldedArmor()
{
// technically selecting all outerwear,
// includes things like hats and slippers
var armor = GetInventoryItemsOfTypeWeenieType(WeenieType.Clothing).Where(a => ((uint)(a.ClothingPriority ?? 0) & (uint)CoverageMaskHelper.Outerwear) != 0).ToList();
if (armor.Count == 0) return new List();
// sort by # of areas covered, and then AL
// prioritize AL first, and then armor that covers more areas
armor.Sort(ValidLocationComparer);
armor.Reverse();
armor.Sort(ArmorLevelComparer);
armor.Reverse();
// divide up into slots?
// use CoverageMask or EquipMask?
// boots / lowerLegs:
// Boots - Locations: LowerLegWear, FootWear, Coverage: Feet
// coverage mask data seems like it could be buggy / inconsistent in PY16...
var head = armor.Where(c => ((uint)(c.ValidLocations ?? 0) & (uint)EquipMask.HeadWear) != 0).ToList();
var chest = armor.Where(c => ((uint)(c.ValidLocations ?? 0) & (uint)(EquipMask.ChestArmor | EquipMask.ChestWear)) != 0).ToList();
var upperArms = armor.Where(c => ((uint)(c.ValidLocations ?? 0) & (uint)(EquipMask.UpperArmArmor | EquipMask.UpperArmWear)) != 0).ToList(); // this will also grab chest pieces that also cover upper arms etc.
var lowerArms = armor.Where(c => ((uint)(c.ValidLocations ?? 0) & (uint)(EquipMask.LowerArmArmor | EquipMask.LowerArmWear)) != 0).ToList();
var hands = armor.Where(c => ((uint)(c.ValidLocations ?? 0) & (uint)EquipMask.HandWear) != 0).ToList();
var abdomen = armor.Where(c => ((uint)(c.ValidLocations ?? 0) & (uint)(EquipMask.AbdomenArmor)) != 0).ToList();
var upperLegs = armor.Where(c => ((uint)(c.ValidLocations ?? 0) & (uint)(EquipMask.UpperLegArmor | EquipMask.UpperLegWear)) != 0).ToList();
var lowerLegs = armor.Where(c => ((uint)(c.ValidLocations ?? 0) & (uint)(EquipMask.LowerLegArmor | EquipMask.LowerLegWear)) != 0).ToList();
var feet = armor.Where(c => ((uint)(c.ValidLocations ?? 0) & (uint)EquipMask.FootWear) != 0).ToList();
/*Console.WriteLine("\nSelectWieldedArmor");
foreach (var item in armor)
DebugArmorClothing(item);*/
// try to equip the clothing at top of lists
var equipped = new List();
var sorted = head.Concat(chest).Concat(upperArms).Concat(lowerArms).Concat(hands).Concat(upperLegs).Concat(abdomen).Concat(lowerLegs).Concat(feet).ToList();
foreach (var item in sorted)
{
TryRemoveFromInventory(item.Guid);
if (TryWieldObjectWithBroadcasting(item, item.ValidLocations ?? 0))
equipped.Add(item);
}
return equipped;
}
///
/// Displays information about a a piece of armor / clothing
///
public void DebugArmorClothing(WorldObject item)
{
var locations = item.ValidLocations;
var coverage = item.ClothingPriority;
Console.WriteLine($"{item.Name} - Locations: {locations}, Coverage: {coverage}");
}
///
/// Compares the number of body parts covered by 2 pieces of clothing
///
public int ValidLocationComparer(WorldObject a, WorldObject b)
{
return EnumHelper.NumFlags((uint)a.ValidLocations).CompareTo(EnumHelper.NumFlags((uint)b.ValidLocations));
}
///
/// Compares the number of body parts covered by 2 pieces of clothing
///
public int ArmorLevelComparer(WorldObject a, WorldObject b)
{
return (a.ArmorLevel ?? 0).CompareTo(b.ArmorLevel ?? 0);
}
public void GetMonsterInventory(List allWeapons, List ammo)
{
// similar to GetInventoryItemsOfTypeWeenieType, optimized for this particular scenario
foreach (var item in Inventory.Values)
{
switch (item.WeenieType)
{
case WeenieType.MeleeWeapon:
case WeenieType.MissileLauncher:
case WeenieType.Missile:
case WeenieType.Caster:
allWeapons.Add(item);
break;
case WeenieType.Ammunition:
ammo.Add(item);
break;
default:
// 6873 - Ulgrim the Unpleasant wields => 161 - Mug
// 70995 - Ulgrim the Unquiet wields => 27808 - Great Elariwood Idol
if (IsNPC && (item.ValidLocations & EquipMask.Selectable) != 0)
allWeapons.Add(item);
break;
}
}
}
public List SelectWieldedWeapons()
{
//Console.WriteLine($"{Name}.SelectWieldedWeapons()");
var allWeapons = new List();
var ammo = new List();
GetMonsterInventory(allWeapons, ammo);
if (allWeapons.Count == 0) return new List();
//DebugTreasure();
/*Console.WriteLine("\nSelectWieldedCombatItems");
Console.WriteLine("Melee Weapons: " + meleeWeapons.Count);
Console.WriteLine("Missile weapons: " + missileWeapons.Count);
Console.WriteLine("Ammunition: " + ammo.Count);*/
// select the best weapon available
while (true)
{
var weapon = FindInventoryWeapon(allWeapons);
if (weapon == null) return new List();
// does this weapon require ammo?
if (weapon.IsAmmoLauncher)
{
// find the best ammo for this weapon
var ammoType = ammo.Where(i => i.AmmoType == weapon.AmmoType).ToList();
var curAmmo = FindInventoryWeapon(ammoType);
if (curAmmo == null)
{
// npcs don't require ammo
if (IsNPC)
return new List { weapon };
allWeapons.Remove(weapon); // remove from possible selections
continue; // find next best weapon
}
//Console.WriteLine("Ammo type: " + (AmmoType)(weapon.AmmoType ?? 0));
return new List() { weapon, curAmmo };
}
// CombatUse / DefaultCombatStyle / ValidLocations?
if (AiAllowedCombatStyle.HasFlag(CombatStyle.DualWield))
{
if (weapon.WeenieType == WeenieType.MeleeWeapon && !weapon.IsTwoHanded)
{
var dualWield = allWeapons.FirstOrDefault(i => i.AutoWieldLeft);
if (dualWield != null)
return new List { weapon, dualWield };
}
}
return new List() { weapon };
}
}
public WorldObject FindInventoryWeapon(List weapons)
{
if (weapons == null) return null;
//var highestMax = weapons.OrderByDescending(w => w.GetBaseDamage().Max).FirstOrDefault();
//var highestAvg = weapons.OrderByDescending(w => w.GetBaseDamage().Avg).FirstOrDefault();
//return highestMax;
// did monsters select best weapons, or just a random weapon?
// see: lugians (wielded treasure table 439), 100% spawn with rocks if most damage potential selected
weapons.Shuffle();
return weapons.FirstOrDefault(i => !i.AutoWieldLeft);
/*var rng = ThreadSafeRandom.Next(0, weapons.Count);
if (rng == weapons.Count)
return null; // choose no weapon? lugians should have ~33% chance to select rock, according to retail pcaps
return weapons[rng];*/
}
public WorldObject SelectWieldedShield()
{
var shields = Inventory.Values.Where(wo => wo.IsShield).ToList();
if (shields.Count == 0)
return null;
// select a random shield
var rng = ThreadSafeRandom.Next(0, shields.Count - 1);
return shields[rng];
}
public void DebugTreasure()
{
if (WieldedTreasure == null) return;
Console.WriteLine($"{Name} possible wielded treasure:");
foreach (var item in WieldedTreasure)
{
var weenie = DatabaseManager.World.GetCachedWeenie(item.WeenieClassId);
var probability = Math.Round(item.Probability * 100, 2);
Console.WriteLine($"{weenie.ClassName} - {probability}%");
}
var totalProbability = Math.Round(GetTotalProbability(WieldedTreasure) * 100, 2);
Console.WriteLine($"Probability of spawning any items: {totalProbability}%");
}
public float GetTotalProbability(List items)
{
if (items == null) return 0.0f;
var prob = items.Select(i => i.Probability).ToList();
var totalSum = prob.Sum();
var totalProduct = prob.Product();
return totalSum - totalProduct;
}
public void EquipInventoryItems(bool weaponsOnly = false)
{
var items = weaponsOnly ? SelectWieldedWeapons() : SelectWieldedTreasure();
if (items == null) return;
foreach (var item in items)
{
if (item.ValidLocations == null)
continue;
//Console.WriteLine($"{Name} equipping {item.Name}");
if (!TryRemoveFromInventory(item.Guid))
continue;
var success = weaponsOnly ? TryWieldObjectWithBroadcasting(item, item.ValidLocations ?? 0)
: TryWieldObject(item, item.ValidLocations ?? 0);
if (!success)
TryAddToInventory(item);
}
}
}
}