using System;
using System.Collections.Generic;
using System.Linq;
using ACE.Common;
using ACE.Entity.Enum;
using ACE.Server.WorldObjects;
namespace ACE.Server.Entity
{
///
/// The different categories death items are sorted into
/// This is used to determine the adjusted value
///
public enum DeathItemCategory
{
None,
MeleeWeapon,
MissileWeapon,
MagicCaster,
Armor,
Clothing,
Jewelry,
Food,
Gem,
SpellComponent,
ManaStone,
CraftingIngredient,
ParchmentBook,
Key,
TradeNote,
Misc
};
///
/// An inventory sorted by which items drop first during death
///
public class DeathItems
{
public List Inventory;
public Dictionary> InventoryGroups; // for debugging
///
/// Constructs a list of death items
///
/// The list of possible inventory items that can be dropped on death
public DeathItems(List inventory)
{
Inventory = new List();
foreach (var item in inventory)
Inventory.Add(new DeathItem(item));
BuildGroups();
// Here's the system, taken straight from the developer spec, that Asheron's Call uses to determine the way items are lost:
// First, we sort the inventory by order of value.
// Second, we go back through the inventory starting at the most expensive item and look at each item's category.
// If we've seen the item category before, we divide the value of the item in half.
// At this point, we add a random 0 - 10 % variance to each item's value.
// Reference: http://acpedia.org/wiki/Recovering_from_Death
// exclude unknown types
Inventory = Inventory.Where(i => i.Category != DeathItemCategory.None).ToList();
// sort by original value and category
// todo: Switch this to use ThenBy to avoid having to iterate over the list twice. If we use ThenBy, the left/right sides probably need to be swapped.
// todo: If you do switch this, it needs to be tested thoroughly.
Inventory = Inventory.OrderByDescending(i => i.AdjustedValue).OrderBy(i => i.Category).ToList();
// halve the values of every item, except the most valued item in each category
HalfValues(Inventory);
// adjusted value by randomized variance
foreach (var item in Inventory)
item.AdjustedValue = GetValueWithVariance(item.AdjustedValue);
// re-sort by final adjusted value
Inventory = Inventory.OrderByDescending(i => i.AdjustedValue).ToList();
}
///
/// Builds the list of death items grouped by category
///
public void BuildGroups()
{
InventoryGroups = new Dictionary>();
foreach (var item in Inventory)
{
if (!InventoryGroups.ContainsKey(item.Category))
InventoryGroups.Add(item.Category, new List());
InventoryGroups[item.Category].Add(item);
}
}
///
/// Randomize the original item values by a variance %
///
public static float MaxVariance = 0.10f;
///
/// Returns the input value adjusted between -variance and +variance
///
public static int GetValueWithVariance(int value)
{
// At this point, we add a random 0 - 10 % variance to each item's value.
// should this be +/- 10%, or even +/- 5%?
// http://www.postcount.net/forum/showthread.php?79784-death-item-formula
// according to this post, it could be +/- 10%
var variance = ThreadSafeRandom.Next(-MaxVariance, MaxVariance);
return (int)(value + value * variance);
}
///
/// Halves the values of every item, except the most valued item in each category
///
public static void HalfValues(List inventory)
{
var prevCategory = DeathItemCategory.None;
// assumes list has been pre-sorted by category and item value
foreach (var item in inventory)
{
if (item.Category != prevCategory)
prevCategory = item.Category;
else
item.AdjustedValue /= 2;
}
}
///
/// An inventory item that can be possibly dropped on death
///
public class DeathItem
{
public WorldObject WorldObject;
public DeathItemCategory Category;
public string Name;
///
/// The original value is randomized slightly,
/// and the non-most expensive items in each category are halved
///
public int AdjustedValue;
public DeathItem(WorldObject wo)
{
WorldObject = wo;
Name = wo.Name;
Category = GetCategory(wo);
AdjustedValue = wo.Value ?? 0; // stack size?
if ((wo.StackSize ?? 1) > 1)
AdjustedValue /= wo.StackSize.Value;
}
public static DeathItemCategory GetCategory(WorldObject wo)
{
switch (wo.ItemType)
{
case ItemType.MeleeWeapon:
return DeathItemCategory.MeleeWeapon;
case ItemType.MissileWeapon:
return DeathItemCategory.MissileWeapon;
case ItemType.Caster:
case ItemType.MagicWieldable:
return DeathItemCategory.MagicCaster;
case ItemType.Armor:
return DeathItemCategory.Armor;
case ItemType.Clothing:
return DeathItemCategory.Clothing;
case ItemType.Jewelry:
return DeathItemCategory.Jewelry;
case ItemType.Food:
return DeathItemCategory.Food;
case ItemType.Gem:
return DeathItemCategory.Gem;
case ItemType.SpellComponents:
return DeathItemCategory.SpellComponent;
case ItemType.ManaStone:
return DeathItemCategory.ManaStone;
case ItemType.CraftAlchemyBase:
case ItemType.CraftAlchemyIntermediate:
case ItemType.CraftCookingBase:
case ItemType.CraftFletchingBase:
case ItemType.CraftFletchingIntermediate:
return DeathItemCategory.CraftingIngredient;
case ItemType.Writable:
return DeathItemCategory.ParchmentBook;
case ItemType.Key:
return DeathItemCategory.Key;
case ItemType.PromissoryNote:
return DeathItemCategory.TradeNote; // should not drop?
case ItemType.Misc:
return DeathItemCategory.Misc;
case ItemType.Container:
return DeathItemCategory.None; // containers don't drop?
default:
//Console.WriteLine("Unknown death item type: " + wo.ItemType);
return DeathItemCategory.None;
}
}
}
}
}