Aetheria/Assets/Scripts/ServerShared/GameContext.cs
Reinier Vens 1f90d3639a Asteroids & Refactoring
We have proper belts that hug the Grid, reactive properties for planet instance data, and more!
2020-12-01 00:16:11 +00:00

808 lines
No EOL
31 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Unity.Mathematics;
using static Unity.Mathematics.math;
using float2 = Unity.Mathematics.float2;
using Random = Unity.Mathematics.Random;
using JM.LinqFaster;
using UniRx;
using float4 = Unity.Mathematics.float4;
public class GameContext
{
public Random Random = new Random((uint) (DateTime.Now.Ticks%uint.MaxValue));
public Dictionary<string, GalaxyMapLayerData> MapLayers = new Dictionary<string, GalaxyMapLayerData>();
public SimpleCommodityData[] Resources;
public Dictionary<Guid, List<IController>> CorporationControllers = new Dictionary<Guid, List<IController>>();
public Dictionary<Guid, ZoneDefinition> GalaxyZones;
private Action<string> _logger;
private Dictionary<BlueprintStatEffect, PerformanceStat> AffectedStats =
new Dictionary<BlueprintStatEffect, PerformanceStat>();
private double _time;
private float _deltaTime;
private GlobalData _globalData;
private Dictionary<Guid, Zone> _zones = new Dictionary<Guid, Zone>();
// private Guid _forceLoadZone;
public GlobalData GlobalData => _globalData ?? (_globalData = Cache.GetAll<GlobalData>().FirstOrDefault());
public DatabaseCache Cache { get; }
// public Guid ForceLoadZone
// {
// get => _forceLoadZone;
// set
// {
// if(_forceLoadZone != Guid.Empty && ZoneEntities[_forceLoadZone].Count == 0)
// UnloadZone(_forceLoadZone);
// _forceLoadZone = value;
// if(!_loadedZones.Contains(_forceLoadZone))
// LoadZone(_forceLoadZone);
// }
// }
public double Time
{
get => _time;
set
{
_deltaTime = (float) (value - _time);
_time = value;
//Log($"GameContext delta time: {_deltaTime}");
}
}
// private readonly Dictionary<CraftedItemData, int> Tier = new Dictionary<CraftedItemData, int>();
public GameContext(DatabaseCache cache, Action<string> logger)
{
Cache = cache;
MapLayers = cache.GetAll<GalaxyMapLayerData>().ToDictionary(ml => ml.Name);
Resources = cache.GetAll<SimpleCommodityData>().Where(i => i.ResourceDensity.Any()).ToArray();
_logger = logger;
// var globalData = Cache.GetAll<GlobalData>().FirstOrDefault();
// if (globalData == null)
// {
// globalData = new GlobalData();
// Cache.Add(globalData);
// }
}
public void Log(string s)
{
_logger(s);
}
// public void Update()
// {
// foreach(var zone in _zones.Values)
// zone.Update((float) Time, _deltaTime);
//
// foreach (var corporation in Cache.GetAll<Corporation>())
// {
// foreach (var tasks in corporation.Tasks
// .Select(id => Cache.Get<AgentTask>(id)) // Fetch the tasks from the database cache
// .Where(task => !task.Reserved) // Filter out tasks that have already been reserved
// .GroupBy(task => task.Type)) // Group tasks by type
// {
// // Create a list of available controllers for this task type
// var availableControllers = CorporationControllers[corporation.ID]
// .Where(controller => controller.Available && controller.TaskType == tasks.Key).ToList();
//
// // Iterate over the highest priority tasks for which controllers are available
// foreach (var task in tasks.OrderByDescending(task => task.Priority).Take(availableControllers.Count))
// {
// // Find the nearest controller for this task
// IController nearestController = availableControllers[0];
// List<ZoneDefinition> nearestControllerPath = FindPath(GalaxyZones[availableControllers.First().Zone.Data.ID], GalaxyZones[task.Zone], true);
// foreach (var controller in availableControllers.Skip(1))
// {
// var path = FindPath(GalaxyZones[controller.Zone.Data.ID], GalaxyZones[task.Zone], true);
// if (path.Count < nearestControllerPath.Count)
// {
// nearestControllerPath = path;
// nearestController = controller;
// }
// }
// task.Reserved = true;
// nearestController.AssignTask(task.ID);
// }
// }
// }
//
// }
// public Zone GetZone(Guid zone)
// {
// if (_zones.ContainsKey(zone)) return _zones[zone];
//
// // var zoneData = Cache.Get<ZoneData>(zone);
// // if (zoneData == null) throw new ArgumentException("Zone parameter is not a valid zone ID!", nameof(zone));
// //
// // return GetZone(zoneData);
// return null;
// }
//
// public Zone GetZone(ZonePack zonePack)
// {
// var z = new Zone(this, zonePack);
// _zones.Add(zonePack.Data.ID, z);
// return z;
// }
//
// public void UnloadZone(Guid zone)
// {
// _zones.Remove(zone);
// }
// public void Warp(Entity entity, Guid targetZone)
// {
// if (length(entity.Position - WormholePosition(entity.Zone, targetZone)) < GlobalData.WarpDistance)
// {
// var sourceZone = entity.Zone;
//
// // Remove entity from source zone
// ZoneEntities[sourceZone].Remove(entity.ID);
//
// // Unload source zone if empty and unobserved
// if(ForceLoadZone != sourceZone && ZoneEntities[sourceZone].Count == 0)
// UnloadZone(sourceZone);
//
// // Load target zone if not yet loaded
// if(!_loadedZones.Contains(targetZone))
// LoadZone(targetZone);
//
// entity.Zone = targetZone;
// entity.Position = WormholePosition(targetZone, sourceZone);
// ZoneEntities[targetZone][entity.ID] = entity;
// }
// }
public void PlaceMegas()
{
var megas = Cache.GetAll<MegaCorporation>();
var availableZones = Cache.GetAll<ZoneData>()
.Where(z => megas.All(m => m.HomeZone != z.ID))
.ToList();
foreach(var megaPlacement in megas
.OrderBy(m=>m.PlacementType)
.GroupBy(m=>m.PlacementType))
switch (megaPlacement.Key)
{
case MegaPlacementType.Mass:
foreach (var mega in megaPlacement)
{
var zone = availableZones
.MaxBy(z => z.Mass);
availableZones.Remove(zone);
mega.HomeZone = zone.ID;
}
break;
// case MegaPlacementType.Planets:
// foreach (var mega in megaPlacement)
// {
// var zone = availableZones
// .MaxBy(z => z.Planets.Length);
// availableZones.Remove(zone);
// mega.HomeZone = zone.ID;
// }
// break;
// case MegaPlacementType.Resources:
// foreach (var mega in megaPlacement)
// {
// var zone = availableZones
// .MaxBy(z => z.Planets
// .SelectMany(id=>Cache.Get<PlanetData>(id).Resources
// .Select(r=>r.Key))
// .Distinct()
// .Count());
// availableZones.Remove(zone);
// mega.HomeZone = zone.ID;
// }
// break;
case MegaPlacementType.Connected:
foreach (var mega in megaPlacement)
{
var zone = availableZones
.OrderByDescending(z => z.Wormholes.Count)
.ThenBy(z=>megas
.Sum(m=>m.HomeZone==Guid.Empty?0:lengthsq(Cache.Get<ZoneData>(m.HomeZone).Position - z.Position)))
.First();
availableZones.Remove(zone);
mega.HomeZone = zone.ID;
}
break;
case MegaPlacementType.Isolated:
foreach (var mega in megaPlacement)
{
var zone = availableZones
.Where(z=>z.Wormholes.Count==1)
.MaxBy(z =>
{
int soloChain = 0;
var previous = z;
var next = Cache.Get<ZoneData>(z.Wormholes.First());
while (next.Wormholes.Count == 2)
{
var temp = next;
next = Cache.Get<ZoneData>(next.Wormholes.First(w=>w != previous.ID));
previous = temp;
soloChain++;
}
return soloChain;
});
availableZones.Remove(zone);
mega.HomeZone = zone.ID;
}
break;
default:
throw new ArgumentOutOfRangeException();
}
}
// public int ItemTier(CraftedItemData itemData)
// {
// if (Tier.ContainsKey(itemData)) return Tier[itemData];
//
// Tier[itemData] = itemData.Ingredients.Keys.Max(ci => _cache.Get<ItemData>(ci) is CraftedItemData craftableIngredient ? ItemTier(craftableIngredient) : 0);
//
// return Tier[itemData];
// }
public ItemData GetData(ItemInstance item)
{
return Cache.Get<ItemData>(item.Data);
}
public SimpleCommodityData GetData(SimpleCommodity item)
{
return Cache.Get<SimpleCommodityData>(item.Data);
}
public CraftedItemData GetData(CraftedItemInstance item)
{
return Cache.Get<CraftedItemData>(item.Data);
}
public EquippableItemData GetData(Gear gear)
{
return Cache.Get<EquippableItemData>(gear.Data);
}
public float GetMass(ItemInstance item)
{
var data = GetData(item);
switch (item)
{
case CraftedItemInstance _:
return data.Mass;
case SimpleCommodity commodity:
return data.Mass * commodity.Quantity;
}
return 0;
}
public float GetSize(ItemInstance item)
{
var data = GetData(item);
switch (item)
{
case CraftedItemInstance _:
return data.Size;
case SimpleCommodity commodity:
return data.Size * commodity.Quantity;
}
return 0;
}
public float GetThermalMass(ItemInstance item)
{
var data = GetData(item);
switch (item)
{
case CraftedItemInstance _:
return data.Mass * data.SpecificHeat;
case SimpleCommodity commodity:
return data.Mass * data.SpecificHeat * commodity.Quantity;
}
return 0;
}
public PerformanceStat GetAffectedStat(BlueprintData blueprint, BlueprintStatEffect effect)
{
// We've already cached this effect's stat, return it directly
if (AffectedStats.ContainsKey(effect)) return AffectedStats[effect];
// Get the data for the item the blueprint is for, return null if not found or not equippable
var blueprintItem = Cache.Get<EquippableItemData>(blueprint.Item);
if (blueprintItem == null)
{
_logger($"Attempted to get stat effect but Blueprint {blueprint.ID} is not for an equippable item!");
return AffectedStats[effect] = null;
}
// Get the first behavior of the type specified in the blueprint stat effect, return null if not found
var effectObject = effect.StatReference.Target == blueprintItem.Name ? (object) blueprintItem :
blueprintItem.Behaviors.FirstOrDefault(b => b.GetType().Name == effect.StatReference.Target);
if (effectObject == null)
{
_logger($"Attempted to get stat effect for Blueprint {blueprint.ID} but stat references missing behavior \"{effect.StatReference.Target}\"!");
return AffectedStats[effect] = null;
}
// Get the first field in the behavior matching the name specified in the stat effect, return null if not found
var type = effectObject.GetType();
var field = type.GetField(effect.StatReference.Stat);
if (field == null)
{
_logger($"Attempted to get stat effect for Blueprint {blueprint.ID} but object {effect.StatReference.Target} does not have a stat named \"{effect.StatReference.Stat}\"!");
return AffectedStats[effect] = null;
}
// Finally we've confirmed the stat effect is valid, return the affected stat
return AffectedStats[effect] = field.GetValue(effectObject) as PerformanceStat;
}
private readonly Dictionary<CraftedItemInstance, float> ItemQuality = new Dictionary<CraftedItemInstance, float>();
public float CompoundQuality(CraftedItemInstance item)
{
if (ItemQuality.ContainsKey(item)) return ItemQuality[item];
var quality = item.Quality;
var craftedIngredients = item.Ingredients.Where(i => i is CraftedItemInstance).ToArray();
if (craftedIngredients.Length > 0)
{
var ingredientQualityWeight = Cache.Get<CraftedItemData>(item.Data).IngredientQualityWeight;
quality = quality * (1 - ingredientQualityWeight) +
craftedIngredients.Cast<CraftedItemInstance>().Average(CompoundQuality) * ingredientQualityWeight;
}
ItemQuality[item] = quality;
return ItemQuality[item];
}
// Determine quality of either the item itself or the specific ingredient this stat depends on
public float Quality(PerformanceStat stat, Gear item)
{
var blueprint = Cache.Get<BlueprintData>(item.Blueprint);
var activeEffects = blueprint.StatEffects.Where(x => GetAffectedStat(blueprint, x) == stat).ToArray();
float quality;
if (!activeEffects.Any())
quality = CompoundQuality(item);
else
{
var ingredientInstances = item.Ingredients.Select(i => Cache.Get<ItemInstance>(i)).ToArray();
var ingredients = ingredientInstances.Where(i => activeEffects.Any(e => e.Ingredient == i.Data)).ToArray();
var distinctIngredients = ingredients.Select(i => i.Data).Distinct();
if(distinctIngredients.Count() != activeEffects.Length)
{
_logger($"Item {item.ID} does not have the ingredients specified by the stat effects of its blueprint!");
return 0;
}
float sum = 0;
foreach (var i in ingredients)
{
if (i is CraftedItemInstance ci)
sum += CompoundQuality(ci);
else _logger($"Blueprint stat effect for item {item.ID} specifies invalid (non crafted) ingredient!");
}
quality = sum / ingredients.Length;
}
return quality;
}
// Returns stat when not equipped
public float Evaluate(PerformanceStat stat, Gear item)
{
var quality = pow(Quality(stat, item), stat.QualityExponent);
var result = lerp(stat.Min, stat.Max, quality);
if (float.IsNaN(result))
return stat.Min;
return result;
}
// Returns stat using ship temperature and modifiers
public float Evaluate(PerformanceStat stat, Gear item, Entity entity)
{
var itemData = GetData(item);
var heat = !stat.HeatDependent ? 1 : pow(itemData.Performance(entity.Temperature), Evaluate(itemData.HeatExponent,item));
var durability = !stat.DurabilityDependent ? 1 : pow(item.Durability / Evaluate(itemData.Durability, item), Evaluate(itemData.DurabilityExponent,item));
var quality = pow(Quality(stat, item), stat.QualityExponent);
var scaleModifier = stat.GetScaleModifiers(entity).Values.Aggregate(1.0f, (current, mod) => current * mod);
var constantModifier = stat.GetConstantModifiers(entity).Values.Sum();
var result = lerp(stat.Min, stat.Max, heat * durability * quality) * scaleModifier + constantModifier;
if (float.IsNaN(result))
return stat.Min;
return result;
}
public Entity CreateEntity(Guid zoneID, Guid corporation, Guid loadout)
{
if (!(Cache.Get(loadout) is LoadoutData loadoutData))
{
_logger("Attempted to spawn invalid loadout ID");
return null;
}
if (!(Cache.Get(loadoutData.Hull) is HullData hullData))
{
_logger("Attempted to spawn loadout with invalid hull ID");
return null;
}
if (!(Cache.Get(zoneID) is ZoneData zoneData))
{
_logger("Attempted to spawn entity with invalid zone ID");
return null;
}
if (!(Cache.Get(corporation) is Corporation corpData))
{
_logger("Attempted to spawn entity with invalid corporation ID");
return null;
}
var gearData = loadoutData.Gear
.Take(hullData.Hardpoints.Count)
.Where(id => id != Guid.Empty)
.Select(id => Cache.Get<GearData>(id))
.ToArray();
if (gearData.Any(g=>g==null))
{
_logger("Attempted to spawn loadout with invalid gear ID");
return null;
}
var gear = gearData
.Select(gd => CreateInstance(gd.ID, GlobalData.StartingGearQualityMin, GlobalData.StartingGearQualityMax))
.ToArray();
if (gear.Any(g=>g==null))
return null;
foreach(var g in gear)
Cache.Add(g);
var cargoData = loadoutData.CompoundCargo
.SelectMany(x => Enumerable.Repeat(Cache.Get<CraftedItemData>(x.Key), x.Value))
.ToArray();
if (cargoData.Any(g=>g==null))
{
_logger("Attempted to spawn loadout with invalid cargo ID");
return null;
}
var cargo = cargoData
.Select(gd => CreateInstance(gd.ID, GlobalData.StartingGearQualityMin, GlobalData.StartingGearQualityMax))
.ToArray();
if (cargo.Any(g=>g==null))
return null;
foreach(var c in cargo)
Cache.Add(c);
var simpleCargo = loadoutData.SimpleCargo?
.Select(x => CreateInstance(x.Key, x.Value))
.ToArray() ?? new SimpleCommodity[0];
if (simpleCargo.Any(g=>g==null))
return null;
var hull = CreateInstance(loadoutData.Hull, GlobalData.StartingGearQualityMin, GlobalData.StartingGearQualityMax);
if(hull==null)
return null;
// Get target zone
var zone = _zones[zoneID];
if (hullData.HullType == HullType.Ship)
{
var entity = CreateShip(hull.ID, gear.Select(g => g.ID),
cargo.Select(c => c.ID).Concat(simpleCargo.Select(c => c.ID)), zone, corporation, Guid.Empty, loadoutData.Name);
entity.Active = true;
return entity;
}
if (hullData.HullType == HullType.Station)
{
var distance = lerp(.2f, .8f, Random.NextFloat()) * zoneData.Radius;
var orbit = new OrbitData
{
Context = this,
Distance = new ReactiveProperty<float>(distance),
Parent = zone.Orbits.Values.Select(o=>o.Data)
.First(orbitData => orbitData.Parent == Guid.Empty).ID,
Phase = Random.NextFloat()
};
zone.AddOrbit(orbit);
var entity = new OrbitalEntity(this, hull.ID, gear.Select(g => g.ID),
cargo.Select(c => c.ID).Concat(simpleCargo.Select(c => c.ID)), orbit.ID, zone, corporation)
{
Name = $"{loadoutData.Name} {Random.NextInt(1,255):X}",
Temperature = 293,
Population = 4
};
entity.Active = true;
var parentCorp = Cache.Get<MegaCorporation>(corpData.Parent);
foreach (var attribute in parentCorp.Personality)
entity.Personality[attribute.Key] = attribute.Value;
Cache.Add(entity);
zone.Entities[entity.ID] = entity;
return entity;
}
return null;
}
public Ship CreateShip(Guid hull, IEnumerable<Guid> gear, IEnumerable<Guid> cargo, Zone zone, Guid corporation, Guid homeEntity, string typeName)
{
var ship = new Ship(this, hull, gear, cargo, zone, corporation)
{
Name = $"{typeName} {Random.NextInt(1,255):X}",
HomeEntity = homeEntity
};
Cache.Add(ship);
zone.Entities[ship.ID] = ship;
return ship;
}
public SimpleCommodity CreateInstance(Guid data, int count)
{
var item = Cache.Get<SimpleCommodityData>(data);
if (item != null)
{
var newItem = new SimpleCommodity
{
Context = this,
Data = data,
Quantity = count
};
Cache.Add(newItem);
return newItem;
}
_logger("Attempted to create Simple Commodity instance using missing or incorrect item id");
return null;
}
public CraftedItemInstance CreateInstance(Guid data, float qualityMin, float qualityMax)
{
var item = Cache.Get<CraftedItemData>(data);
if (item == null)
{
_logger("Attempted to create crafted item instance using missing or incorrect item id!");
return null;
}
var blueprint = Cache.GetAll<BlueprintData>().FirstOrDefault(b => b.Item == data);
if (blueprint == null)
{
_logger("Attempted to create crafted item instance which has no blueprint!");
return null;
}
bool invalidIngredientFound = false;
ItemData invalidIngredient = null;
var ingredients = new List<ItemInstance>();
foreach (var ingredient in blueprint.Ingredients)
{
var itemData = Cache.Get<ItemData>(ingredient.Key);
if (itemData is SimpleCommodityData)
{
var itemInstance = CreateInstance(ingredient.Key, ingredient.Value);
if(itemInstance == null)
{
invalidIngredientFound = true;
invalidIngredient = itemData;
}
else ingredients.Add(itemInstance);
}
else
{
for (int i = 0; i < ingredient.Value; i++)
{
var itemInstance = CreateInstance(ingredient.Key, qualityMin, qualityMax);
if (itemInstance == null)
{
invalidIngredientFound = true;
invalidIngredient = itemData;
break;
}
ingredients.Add(itemInstance);
}
}
}
if (invalidIngredientFound)
{
_logger($"Unable to create crafted item ingredient: {invalidIngredient?.Name??"null"} for item {item.Name}");
}
Cache.AddAll(ingredients);
if (item is EquippableItemData equippableItemData)
{
var newGear = new Gear
{
Context = this,
Data = data,
Ingredients = ingredients.Select(i=>i.ID).ToList(),
Quality = Random.NextFloat(qualityMin, qualityMax),
Blueprint = blueprint.ID,
Name = $"{item.Name}"
};
newGear.Durability = Evaluate(equippableItemData.Durability, newGear);
Cache.Add(newGear);
return newGear;
}
var newCommodity = new CompoundCommodity
{
Context = this,
Data = data,
Ingredients = ingredients.Select(i=>i.ID).ToList(),
Quality = Random.NextFloat(qualityMin, qualityMax),
Blueprint = blueprint.ID,
Name = $"{item.Name}"
};
Cache.Add(newCommodity);
return newCommodity;
}
class DijkstraNode
{
public float Cost;
public DijkstraNode Parent;
public ZoneDefinition Zone;
}
public List<ZoneDefinition> FindPath(ZoneDefinition source, ZoneDefinition target, bool bestFirst = false)
{
SortedList<float,DijkstraNode> members = new SortedList<float,DijkstraNode>{{0,new DijkstraNode{Zone = source}}};
List<DijkstraNode> searched = new List<DijkstraNode>();
while (true)
{
var s = members.FirstOrDefault(m => !searched.Contains(m.Value)).Value; // Lowest cost unsearched node
if (s == null) return null; // No vertices left unsearched
if (s.Zone == target) // We found the path
{
Stack<DijkstraNode> path = new Stack<DijkstraNode>(); // Since we start at the end, use a LIFO collection
path.Push(s);
while(path.Peek().Parent!=null) // Keep pushing until we reach the start, which has no parent
path.Push(path.Peek().Parent);
return path.Select(dv => dv.Zone).ToList();
}
// For each adjacent star (filter already visited zones unless heuristic is in use)
foreach (var dijkstraStar in s.Zone.Links.WhereSelectF(i => !bestFirst || members.All(m => m.Value.Zone != GalaxyZones[i]),
// Cost is parent cost plus distance
i => new DijkstraNode {Parent = s, Zone = GalaxyZones[i], Cost = s.Cost + length(s.Zone.Position - GalaxyZones[i].Position)}))
// Add new member to list, sorted by cost plus optional heuristic
members.Add(bestFirst ? dijkstraStar.Cost + length(dijkstraStar.Zone.Position - target.Position) : dijkstraStar.Cost, dijkstraStar);
searched.Add(s);
}
}
// public bool MoveCargo(Entity source, Entity target, ItemInstance item, int quantity = int.MaxValue)
// {
// if (item is CraftedItemInstance craftedItemInstance)
// {
// var craftedItemData = Cache.Get<CraftedItemData>(craftedItemInstance.Data);
// if (target.Capacity - target.OccupiedCapacity > craftedItemData.Size)
// {
// // Target has the cargo capacity for the item, simply move the instance
// source.Cargo.Remove(item.ID);
// target.Cargo.Add(item.ID);
// source.RecalculateMass();
// target.RecalculateMass();
// return true;
// }
// return false;
// }
//
// if (item is SimpleCommodity simpleCommoditySource)
// {
// quantity = min(quantity, simpleCommoditySource.Quantity);
// var simpleCommodityData = Cache.Get<SimpleCommodityData>(simpleCommoditySource.Data);
// var spareCapacity = (int) (target.Capacity - target.OccupiedCapacity);
// var simpleCommodityTarget = target.Cargo
// .Select(i => Cache.Get<ItemInstance>(i))
// .FirstOrDefault(i => i.Data == simpleCommodityData.ID) as SimpleCommodity;
// var success = spareCapacity >= quantity;
// quantity = min(quantity, spareCapacity);
// if (simpleCommodityTarget == null)
// {
// if (quantity == simpleCommoditySource.Quantity)
// {
// // Target has the cargo capacity to hold the full quantity,
// // and the target has no matching item instance;
// // simply move the existing item instance to the target
// source.Cargo.Remove(simpleCommoditySource.ID);
// target.Cargo.Add(simpleCommoditySource.ID);
// source.RecalculateMass();
// target.RecalculateMass();
// return success;
// }
// else
// {
// // Target has the cargo capacity to hold the desired quantity
// // and the target has no matching item instance;
// // Create a new item instance on the target and decrement the quantity of the source instance
// var newSimpleCommodity = new SimpleCommodity
// {
// Context = this,
// Data = simpleCommodityData.ID,
// Quantity = simpleCommoditySource.Quantity
// };
// Cache.Add(newSimpleCommodity);
// target.Cargo.Add(newSimpleCommodity.ID);
// simpleCommoditySource.Quantity -= quantity;
// source.RecalculateMass();
// target.RecalculateMass();
// return success;
// }
// }
// else
// {
// if (quantity == simpleCommoditySource.Quantity)
// {
// // Target has the cargo capacity to hold the full quantity,
// // and there is already a matching item instance;
// // delete the source entity's item instance and increment the target's quantity
// source.Cargo.Remove(simpleCommoditySource.ID);
// Cache.Delete(simpleCommoditySource);
// simpleCommodityTarget.Quantity += quantity;
// source.RecalculateMass();
// target.RecalculateMass();
// return success;
// }
// else
// {
// // Target has the cargo capacity to hold the desired quantity,
// // and there is already a matching item instance;
// // decrement the source's quantity and increment the target's quantity
// simpleCommoditySource.Quantity -= quantity;
// simpleCommodityTarget.Quantity += quantity;
// source.RecalculateMass();
// target.RecalculateMass();
// return success;
// }
// }
//
// }
//
// return false;
// }
}