Example #1
0
        public PyDataType AssembleShip(PyList itemIDs, CallInformation call)
        {
            foreach (PyInteger itemID in itemIDs.GetEnumerable <PyInteger>())
            {
                this.AssembleShip(itemID, call);
            }

            return(null);
        }
Example #2
0
        public PyDataType MultiMerge(PyList merges, CallInformation call)
        {
            foreach (PyTuple merge in merges.GetEnumerable <PyTuple>())
            {
                if (merge[0] is PyInteger == false || merge[1] is PyInteger == false || merge[2] is PyInteger == false)
                {
                    continue;
                }

                PyInteger fromItemID = merge[0] as PyInteger;
                PyInteger toItemID   = merge[1] as PyInteger;
                PyInteger quantity   = merge[2] as PyInteger;

                if (this.mInventory.Items.TryGetValue(toItemID, out ItemEntity toItem) == false)
                {
                    continue;
                }

                ItemEntity fromItem = this.ItemFactory.GetItem(fromItemID);

                // ignore singleton items
                if (fromItem.Singleton == true || toItem.Singleton == true)
                {
                    continue;
                }
                // ignore items that are not the same type
                if (fromItem.Type.ID != toItem.Type.ID)
                {
                    continue;
                }
                // if we're fully merging two stacks, just remove one item
                if (quantity == fromItem.Quantity)
                {
                    int oldLocationID = fromItem.LocationID;
                    // remove the item
                    this.ItemFactory.DestroyItem(fromItem);
                    // notify the client about the item too
                    call.Client.NotifyMultiEvent(OnItemChange.BuildLocationChange(fromItem, oldLocationID));
                }
                else
                {
                    // change the item's quantity
                    fromItem.Quantity -= quantity;
                    // notify the client about the change
                    call.Client.NotifyMultiEvent(OnItemChange.BuildQuantityChange(fromItem, fromItem.Quantity + quantity));
                    fromItem.Persist();
                }

                toItem.Quantity += quantity;
                call.Client.NotifyMultiEvent(OnItemChange.BuildQuantityChange(toItem, toItem.Quantity - quantity));
                toItem.Persist();
            }

            return(null);
        }
Example #3
0
        private void HandleNotification(PyPacket packet, Client client)
        {
            if (packet.Source is PyAddressAny)
            {
                this.HandleBroadcastNotification(packet);
                return;
            }

            PyTuple callInfo = ((packet.Payload[0] as PyTuple)[1] as PySubStream).Stream as PyTuple;

            PyList objectIDs = callInfo[0] as PyList;
            string call      = callInfo[1] as PyString;

            if (call != "ClientHasReleasedTheseObjects")
            {
                Log.Error($"Received notification from client with unknown method {call}");
                return;
            }

            // search for the given objects in the bound service
            // and sure they're freed
            foreach (PyTuple objectID in objectIDs.GetEnumerable <PyTuple>())
            {
                if (objectID[0] is PyString == false)
                {
                    Log.Fatal("Expected bound call with bound string, but got something different");
                    return;
                }

                string boundString = objectID[0] as PyString;

                // parse the bound string to get back proper node and bound ids
                Match regexMatch = Regex.Match(boundString, "N=([0-9]+):([0-9]+)");

                if (regexMatch.Groups.Count != 3)
                {
                    Log.Fatal($"Cannot find nodeID and boundID in the boundString {boundString}");
                    return;
                }

                int nodeID  = int.Parse(regexMatch.Groups[1].Value);
                int boundID = int.Parse(regexMatch.Groups[2].Value);

                if (nodeID != this.Container.NodeID)
                {
                    Log.Fatal("Got a ClientHasReleasedTheseObjects call for an object ID that doesn't belong to us");
                    // TODO: MIGHT BE A GOOD IDEA TO RELAY THIS CALL TO THE CORRECT NODE
                    // TODO: INSIDE THE NETWORK, AT LEAST THAT'S WHAT CCP IS DOING BASED
                    // TODO: ON THE CLIENT'S CODE... NEEDS MORE INVESTIGATION
                    return;
                }

                this.BoundServiceManager.FreeBoundService(boundID);
            }
        }
Example #4
0
        public PyDataType BatchCertificateGrant(PyList certificateList, CallInformation call)
        {
            int       callerCharacterID = call.Client.EnsureCharacterIsSelected();
            Character character         = this.ItemFactory.GetItem <Character>(callerCharacterID);

            PyList <PyInteger>      result = new PyList <PyInteger>();
            Dictionary <int, Skill> skills = character.InjectedSkillsByTypeID;
            List <int> grantedCertificates = this.DB.GetCertificateListForCharacter(callerCharacterID);

            foreach (PyInteger certificateID in certificateList.GetEnumerable <PyInteger>())
            {
                if (this.CertificateRelationships.TryGetValue(certificateID, out List <Relationship> relationships) == true)
                {
                    bool requirementsMet = true;

                    foreach (Relationship relationship in relationships)
                    {
                        if (relationship.ParentTypeID != 0 && (skills.TryGetValue(relationship.ParentTypeID, out Skill skill) == false || skill.Level < relationship.ParentLevel))
                        {
                            requirementsMet = false;
                        }
                        if (relationship.ParentID != 0 && grantedCertificates.Contains(relationship.ParentID) == false)
                        {
                            requirementsMet = false;
                        }
                    }

                    if (requirementsMet == false)
                    {
                        continue;
                    }
                }

                // grant the certificate and add it to the list of granted certs
                this.DB.GrantCertificate(callerCharacterID, certificateID);
                // ensure the result includes that certificate list
                result.Add(certificateID);
                // add the cert to the list so certs that depend on others are properly granted
                grantedCertificates.Add(certificateID);
            }

            // notify the client about the granting of certificates
            call.Client.NotifyMultiEvent(new OnCertificateIssued());

            return(result);
        }
Example #5
0
        public PyDataType GetQuotes(PyList itemIDs, CallInformation call)
        {
            Character character = this.ItemFactory.GetItem <Character>(call.Client.EnsureCharacterIsSelected());

            PyDictionary <PyInteger, PyDataType> result = new PyDictionary <PyInteger, PyDataType>();

            foreach (PyInteger itemID in itemIDs.GetEnumerable <PyInteger>())
            {
                if (this.mInventory.Items.TryGetValue(itemID, out ItemEntity item) == false)
                {
                    throw new MktNotOwner();
                }

                result[itemID] = this.GetQuote(character, item);
            }

            return(result);
        }
Example #6
0
        public PyDataType GetCharacterAppearanceList(PyList ids, CallInformation call)
        {
            PyList result = new PyList(ids.Count);

            int index = 0;

            foreach (PyInteger id in ids.GetEnumerable <PyInteger>())
            {
                Rowset dbResult = this.DB.GetCharacterAppearanceInfo(id);

                if (dbResult.Rows.Count != 0)
                {
                    result[index] = dbResult;
                }

                index++;
            }

            return(result);
        }
Example #7
0
        public PyDataType Reprocess(PyList itemIDs, PyInteger ownerID, PyInteger flag, PyBool unknown, PyList skipChecks, CallInformation call)
        {
            Character character = this.ItemFactory.GetItem <Character>(call.Client.EnsureCharacterIsSelected());

            // TODO: TAKE INTO ACCOUNT OWNERID AND FLAG, THESE MOST LIKELY WILL BE USED BY CORP STUFF
            foreach (PyInteger itemID in itemIDs.GetEnumerable <PyInteger>())
            {
                if (this.mInventory.Items.TryGetValue(itemID, out ItemEntity item) == false)
                {
                    throw new MktNotOwner();
                }

                // reprocess the item
                this.Reprocess(character, item, call.Client);
                int oldLocationID = item.LocationID;
                // finally remove the item from the inventories
                this.ItemFactory.DestroyItem(item);
                // notify the client about the item being destroyed
                call.Client.NotifyMultiEvent(OnItemChange.BuildLocationChange(item, oldLocationID));
            }

            return(null);
        }
Example #8
0
        public PyDataType MultiAdd(PyList adds, PyInteger quantity, PyInteger flag, CallInformation call)
        {
            if (quantity == null)
            {
                // null quantity means all the items in the list
                foreach (PyInteger itemID in adds.GetEnumerable <PyInteger>())
                {
                    ItemEntity item = this.ItemFactory.GetItem(itemID);

                    // check and then move the item
                    this.PreMoveItemCheck(item, (Flags)(int)flag, item.Quantity);
                    this.MoveItemHere(item, (Flags)(int)flag);
                }
            }
            else
            {
                // an specific quantity means we'll need to grab part of the stacks selected

                throw new CustomError("Not supported yet!");
            }

            return(null);
        }
Example #9
0
        public PyDataType TrashItems(PyList itemIDs, PyInteger stationID, CallInformation call)
        {
            foreach (PyInteger itemID in itemIDs.GetEnumerable <PyInteger>())
            {
                // do not trash the active ship
                if (itemID == call.Client.ShipID)
                {
                    throw new CantMoveActiveShip();
                }

                ItemEntity item = this.ItemFactory.GetItem(itemID);
                // store it's location id
                int   oldLocation = item.LocationID;
                Flags oldFlag     = item.Flag;
                // remove the item off the ItemManager
                this.ItemFactory.DestroyItem(item);
                // notify the client of the change
                call.Client.NotifyMultiEvent(OnItemChange.BuildLocationChange(item, oldFlag, oldLocation));
                // TODO: CHECK IF THE ITEM HAS ANY META INVENTORY AND/OR BOUND SERVICE
                // TODO: AND FREE THOSE TOO SO THE ITEMS CAN BE REMOVED OFF THE DATABASE
            }

            return(null);
        }
Example #10
0
        public PyDataType InjectSkillIntoBrain(PyList itemIDs, CallInformation call)
        {
            foreach (PyInteger item in itemIDs.GetEnumerable <PyInteger>())
            {
                try
                {
                    // get the item by it's ID and change the location of it
                    Skill skill = this.ItemFactory.GetItem <Skill>(item);

                    // check if the character already has this skill injected
                    if (this.Character.InjectedSkillsByTypeID.ContainsKey(skill.Type.ID) == true)
                    {
                        throw new CharacterAlreadyKnowsSkill(skill.Type);
                    }

                    // is this a stack of skills?
                    if (skill.Quantity > 1)
                    {
                        // add one of the skill into the character's brain
                        Skill newStack = this.ItemFactory.CreateSkill(skill.Type, this.Character, 0, SkillHistoryReason.None);

                        // subtract one from the quantity
                        skill.Quantity -= 1;

                        // save to database
                        skill.Persist();

                        // finally notify the client
                        call.Client.NotifyMultiEvent(OnItemChange.BuildQuantityChange(skill, skill.Quantity + 1));
                        call.Client.NotifyMultiEvent(OnItemChange.BuildNewItemChange(newStack));
                    }
                    else
                    {
                        // store old values for the notification
                        int   oldLocationID = skill.LocationID;
                        Flags oldFlag       = skill.Flag;

                        // now set the new values
                        skill.LocationID = this.Character.ID;
                        skill.Flag       = Flags.Skill;
                        skill.Level      = 0;
                        skill.Singleton  = true;

                        // ensure the character has the skill in his/her brain
                        this.Character.AddItem(skill);

                        // ensure the changes are saved
                        skill.Persist();

                        // notify the character of the change in the item
                        call.Client.NotifyMultiEvent(OnItemChange.BuildLocationChange(skill, oldFlag, oldLocationID));
                        call.Client.NotifyMultiEvent(OnItemChange.BuildSingletonChange(skill, false));
                    }
                }
                catch (CharacterAlreadyKnowsSkill)
                {
                    throw;
                }
                catch (Exception)
                {
                    Log.Error($"Cannot inject itemID {item} into {this.Character.ID}'s brain...");
                    throw;
                }
            }

            // send the skill injected notification to refresh windows if needed
            call.Client.NotifyMultiEvent(new OnSkillInjected());

            return(null);
        }
Example #11
0
 public PyDataType GetMultiInvTypesEx(PyList typeIDs, CallInformation call)
 {
     return(this.DB.GetMultiInvTypesEx(typeIDs.GetEnumerable <PyInteger>()));
 }
Example #12
0
 public PyDataType GetMultiAllianceShortNamesEx(PyList ids, CallInformation call)
 {
     return(this.DB.GetMultiAllianceShortNamesEx(ids.GetEnumerable <PyInteger>()));
 }
Example #13
0
 public PyDataType GetMultiLocationsEx(PyList ids, CallInformation call)
 {
     return(this.DB.GetMultiLocationsEx(ids.GetEnumerable <PyInteger>()));
 }
Example #14
0
        public PyDataType UnasembleItems(PyDictionary validIDsByStationID, PyList skipChecks, CallInformation call)
        {
            int characterID = call.Client.EnsureCharacterIsSelected();
            List <RepairDB.ItemRepackageEntry> entries = new List <RepairDB.ItemRepackageEntry>();

            bool ignoreContractVoiding       = false;
            bool ignoreRepackageWithUpgrades = false;

            foreach (PyString check in skipChecks.GetEnumerable <PyString>())
            {
                if (check == "RepairUnassembleVoidsContract")
                {
                    ignoreContractVoiding = true;
                }
                if (check == "ConfirmRepackageSomethingWithUpgrades")
                {
                    ignoreRepackageWithUpgrades = true;
                }
            }

            foreach ((PyInteger stationID, PyList itemIDs) in validIDsByStationID.GetEnumerable <PyInteger, PyList>())
            {
                foreach (PyInteger itemID in itemIDs.GetEnumerable <PyInteger>())
                {
                    RepairDB.ItemRepackageEntry entry = this.RepairDB.GetItemToRepackage(itemID, characterID, stationID);

                    if (entry.HasContract == true && ignoreContractVoiding == false)
                    {
                        throw new RepairUnassembleVoidsContract(itemID);
                    }
                    if (entry.HasUpgrades == true && ignoreRepackageWithUpgrades == false)
                    {
                        throw new ConfirmRepackageSomethingWithUpgrades();
                    }
                    if (entry.Damage != 0.0)
                    {
                        throw new CantRepackageDamagedItem();
                    }

                    entries.Add(entry);
                }
            }

            foreach (RepairDB.ItemRepackageEntry entry in entries)
            {
                if (entry.Singleton == false)
                {
                    continue;
                }

                // extra situation, the repair is happening on a item in our node, the client must know immediately
                if (entry.NodeID == this.Container.NodeID || this.SystemManager.StationBelongsToUs(entry.LocationID) == true)
                {
                    ItemEntity item = this.ItemFactory.LoadItem(entry.ItemID, out bool loadRequired);

                    // the item is an inventory, take everything out!
                    if (item is ItemInventory inventory)
                    {
                        foreach ((int _, ItemEntity itemInInventory) in inventory.Items)
                        {
                            // if the item is in a rig slot, destroy it
                            if (itemInInventory.IsInRigSlot() == true)
                            {
                                Flags oldFlag = itemInInventory.Flag;
                                this.ItemFactory.DestroyItem(itemInInventory);
                                // notify the client about the change
                                call.Client.NotifyMultiEvent(OnItemChange.BuildLocationChange(itemInInventory, oldFlag, entry.ItemID));
                            }
                            else
                            {
                                Flags oldFlag = itemInInventory.Flag;
                                // update item's location
                                itemInInventory.LocationID = entry.LocationID;
                                itemInInventory.Flag       = Flags.Hangar;

                                // notify the client about the change
                                call.Client.NotifyMultiEvent(OnItemChange.BuildLocationChange(itemInInventory, oldFlag, entry.ItemID));
                                // save the item
                                itemInInventory.Persist();
                            }
                        }
                    }

                    // update the singleton flag too
                    item.Singleton = false;
                    call.Client.NotifyMultiEvent(OnItemChange.BuildSingletonChange(item, true));

                    // load was required, the item is not needed anymore
                    if (loadRequired == true)
                    {
                        this.ItemFactory.UnloadItem(item);
                    }
                }
                else
                {
                    long nodeID = this.SystemManager.GetNodeStationBelongsTo(entry.LocationID);

                    if (nodeID > 0)
                    {
                        Notifications.Nodes.Inventory.OnItemChange change = new Notifications.Nodes.Inventory.OnItemChange();

                        change.AddChange(entry.ItemID, "singleton", true, false);

                        this.NotificationManager.NotifyNode(nodeID, change);
                    }
                }

                // finally repackage the item
                this.RepairDB.RepackageItem(entry.ItemID, entry.LocationID);
                // remove any insurance contract for the ship
                this.InsuranceDB.UnInsureShip(entry.ItemID);
            }

            return(null);
        }
Example #15
0
        public PyDataType RepairItems(PyList itemIDs, PyDecimal iskRepairValue, CallInformation call)
        {
            // ensure the player has enough balance to do the fixing
            Station station = this.ItemFactory.GetStaticStation(call.Client.EnsureCharacterIsInStation());

            // take the wallet lock and ensure the character has enough balance
            using Wallet wallet = this.WalletManager.AcquireWallet(call.Client.EnsureCharacterIsSelected(), 1000);
            {
                wallet.EnsureEnoughBalance(iskRepairValue);
                // build a list of items to be fixed
                List <ItemEntity> items = new List <ItemEntity>();

                double quantityLeft = iskRepairValue;

                foreach (PyInteger itemID in itemIDs.GetEnumerable <PyInteger>())
                {
                    // ensure the given item is in the list
                    if (this.mInventory.Items.TryGetValue(itemID, out ItemEntity item) == false)
                    {
                        continue;
                    }

                    // calculate how much to fix it
                    if (item is Ship)
                    {
                        quantityLeft -= Math.Min(item.Attributes[Attributes.damage] * (item.Type.BasePrice * BASEPRICE_MULTIPLIER_SHIP), quantityLeft);
                    }
                    else
                    {
                        quantityLeft -= Math.Min(item.Attributes[Attributes.damage] * (item.Type.BasePrice * BASEPRICE_MULTIPLIER_MODULE), quantityLeft);
                    }

                    // add the item to the list
                    items.Add(item);

                    // if there's not enough money left then break the loop and fix whatever's possible
                    if (quantityLeft <= 0.0)
                    {
                        break;
                    }
                }

                quantityLeft = iskRepairValue;

                // go through all the items again and fix them
                foreach (ItemEntity item in items)
                {
                    double repairPrice = 0.0f;

                    if (item is Ship)
                    {
                        repairPrice = item.Attributes[Attributes.damage] * (item.Type.BasePrice * BASEPRICE_MULTIPLIER_SHIP);
                    }
                    else
                    {
                        repairPrice = item.Attributes[Attributes.damage] * (item.Type.BasePrice * BASEPRICE_MULTIPLIER_MODULE);
                    }

                    // full item can be repaired!
                    if (repairPrice <= quantityLeft)
                    {
                        item.Attributes[Attributes.damage].Integer = 0;
                    }
                    else
                    {
                        int repairUnits = 0;

                        // calculate how much can be repaired with the quantity left
                        if (item is Ship)
                        {
                            repairUnits = (int)(quantityLeft / (item.Type.BasePrice * BASEPRICE_MULTIPLIER_SHIP));
                            repairPrice = repairUnits * (item.Type.BasePrice * BASEPRICE_MULTIPLIER_SHIP);
                        }
                        else
                        {
                            repairUnits = (int)(quantityLeft / (item.Type.BasePrice * BASEPRICE_MULTIPLIER_MODULE));
                            repairPrice = repairUnits * (item.Type.BasePrice * BASEPRICE_MULTIPLIER_MODULE);
                        }

                        // only perform changes on the damage if there's units we can pay for repair
                        if (repairUnits > 0)
                        {
                            item.Attributes[Attributes.damage] -= repairUnits;
                        }
                    }

                    quantityLeft -= repairPrice;
                    // persist item changes
                    item.Persist();
                }

                wallet.CreateJournalRecord(MarketReference.RepairBill, station.OwnerID, null, -(iskRepairValue - quantityLeft));
            }

            return(null);
        }
Example #16
0
        public PyDataType DeleteBookmarks(PyList bookmarkIDs, CallInformation call)
        {
            this.DB.DeleteBookmark(bookmarkIDs.GetEnumerable <PyInteger>(), call.Client.EnsureCharacterIsSelected());

            return(null);
        }
Example #17
0
 public override PyDataType FetchByKey(PyList keyList, CallInformation call)
 {
     return(this.DB.GetMembers(keyList.GetEnumerable <PyInteger>(), this.Corporation.ID, this.SparseRowset, this.RowsIndex));
 }
Example #18
0
        public PyDataType SaveSkillQueue(PyList queue, CallInformation call)
        {
            if (this.Character.SkillQueue.Count > 0)
            {
                // calculate current skill in training points
                Skill currentSkill = this.Character.SkillQueue[0].Skill;

                if (currentSkill.ExpiryTime > 0)
                {
                    // get the total amount of minutes the skill would have taken to train completely
                    long pointsLeft = (long)(currentSkill.GetSkillPointsForLevel(this.Character.SkillQueue[0].TargetLevel) - currentSkill.Points);

                    TimeSpan timeLeft   = TimeSpan.FromMinutes(pointsLeft / this.Character.GetSkillPointsPerMinute(currentSkill));
                    DateTime endTime    = DateTime.FromFileTimeUtc(currentSkill.ExpiryTime);
                    DateTime startTime  = endTime.Subtract(timeLeft);
                    TimeSpan timePassed = DateTime.UtcNow - startTime;

                    // calculate the skill points to add
                    double skillPointsToAdd = timePassed.TotalMinutes * this.Character.GetSkillPointsPerMinute(currentSkill);

                    currentSkill.Points += skillPointsToAdd;
                }

                // remove the timer associated with the queue
                this.FreeSkillQueueTimers();

                foreach (Character.SkillQueueEntry entry in this.Character.SkillQueue)
                {
                    entry.Skill.Flag = Flags.Skill;

                    call.Client.NotifyMultiEvent(OnItemChange.BuildLocationChange(entry.Skill, Flags.SkillInTraining));

                    // send notification of skill training stopped
                    call.Client.NotifyMultiEvent(new OnSkillTrainingStopped(entry.Skill));

                    // create history entry
                    this.DB.CreateSkillHistoryRecord(entry.Skill.Type, this.Character, SkillHistoryReason.SkillTrainingCancelled,
                                                     entry.Skill.Points);

                    entry.Skill.ExpiryTime = 0;
                    entry.Skill.Persist();
                }

                this.Character.SkillQueue.Clear();
            }

            DateTime startDateTime = DateTime.UtcNow;
            bool     first         = true;

            foreach (PyTuple entry in queue.GetEnumerable <PyTuple>())
            {
                // ignore wrong entries
                if (entry.Count != 2)
                {
                    continue;
                }

                int typeID = entry[0] as PyInteger;
                int level  = entry[1] as PyInteger;

                // search for an item with the given typeID
                ItemEntity item = this.Character.Items.First(x => x.Value.Type.ID == typeID && (x.Value.Flag == Flags.Skill || x.Value.Flag == Flags.SkillInTraining)).Value;

                // ignore items that are not skills
                if (item is Skill == false)
                {
                    continue;
                }

                Skill skill = item as Skill;

                double skillPointsLeft = skill.GetSkillPointsForLevel(level) - skill.Points;

                TimeSpan duration = TimeSpan.FromMinutes(skillPointsLeft / this.Character.GetSkillPointsPerMinute(skill));

                DateTime expiryTime = startDateTime + duration;

                skill.ExpiryTime = expiryTime.ToFileTimeUtc();
                skill.Flag       = Flags.SkillInTraining;

                call.Client.NotifyMultiEvent(OnItemChange.BuildLocationChange(skill, Flags.Skill));

                startDateTime = expiryTime;

                // skill added to the queue, persist the character to ensure all the changes are saved
                this.Character.SkillQueue.Add(new Character.SkillQueueEntry()
                {
                    Skill = skill, TargetLevel = level
                });

                if (first == true)
                {
                    // skill was trained, send the success message
                    call.Client.NotifyMultiEvent(new OnSkillStartTraining(skill));

                    // create history entry
                    this.DB.CreateSkillHistoryRecord(skill.Type, this.Character, SkillHistoryReason.SkillTrainingStarted, skill.Points);

                    first = false;
                }

                skill.Persist();
            }

            // ensure the timer is present for the first skill in the queue
            this.SetupTimerForNextSkillInQueue();

            // finally persist the data to the database
            this.Character.Persist();

            return(null);
        }
Example #19
0
        public PyDataType GetDamageReports(PyList itemIDs, CallInformation call)
        {
            PyDictionary <PyInteger, PyDataType> response = new PyDictionary <PyInteger, PyDataType>();

            foreach (PyInteger itemID in itemIDs.GetEnumerable <PyInteger>())
            {
                // ensure the given item is in the list
                if (this.mInventory.Items.TryGetValue(itemID, out ItemEntity item) == false)
                {
                    continue;
                }

                Rowset quote = new Rowset(
                    new PyList <PyString>(6)
                {
                    [0] = "itemID",
                    [1] = "typeID",
                    [2] = "groupID",
                    [3] = "damage",
                    [4] = "maxHealth",
                    [5] = "costToRepairOneUnitOfDamage"
                }
                    );

                if (item is Ship ship)
                {
                    foreach ((int _, ItemEntity module) in ship.Items)
                    {
                        if (module.IsInModuleSlot() == false && module.IsInRigSlot() == false)
                        {
                            continue;
                        }

                        quote.Rows.Add(
                            new PyList()
                        {
                            module.ID,
                            module.Type.ID,
                            module.Type.Group.ID,
                            module.Attributes[Attributes.damage],
                            module.Attributes[Attributes.hp],
                            // modules should calculate this value differently, but for now this will suffice
                            module.Type.BasePrice * BASEPRICE_MULTIPLIER_MODULE
                        }
                            );
                    }


                    quote.Rows.Add(
                        new PyList()
                    {
                        item.ID,
                        item.Type.ID,
                        item.Type.Group.ID,
                        item.Attributes[Attributes.damage],
                        item.Attributes[Attributes.hp],
                        item.Type.BasePrice * BASEPRICE_MULTIPLIER_SHIP
                    }
                        );
                }
                else
                {
                    quote.Rows.Add(
                        new PyList()
                    {
                        item.ID,
                        item.Type.ID,
                        item.Type.Group.ID,
                        item.Attributes[Attributes.damage],
                        item.Attributes[Attributes.hp],
                        item.Type.BasePrice * BASEPRICE_MULTIPLIER_MODULE
                    }
                        );
                }

                // the client used to send a lot of extra information on this call
                // but in reality that data is not used by the client at all
                // most likely remnants of older eve client versions
                response[itemID] = new Row(
                    new PyList <PyString>(1)
                {
                    [0] = "quote"
                },
                    new PyList(1)
                {
                    [0] = quote
                }
                    );
            }

            return(response);
        }