/**
 * Author: dkanus
 * Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore
 * License: GPL
 * Copyright 2021-2024 Anton Tarasenko
 *------------------------------------------------------------------------------
 * This file is part of Acedia.
 *
 * Acedia is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 3 of the License, or
 * (at your option) any later version.
 *
 * Acedia is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Acedia.  If not, see <https://www.gnu.org/licenses/>.
 */
class EItem extends EInterface
    dependson(TradingGameplayAPI)
    abstract;

//! Interface for working with items.
//! Provides generic item methods along with methods that act of tradable items
//! specifically.
//! In case item is not a tradable, they simply do nothing.

/// Returns UI-usable name of the caller [`EItem`].
///
/// Allowed to be empty, but not allowed to be `none` for existent items.
public function Text GetName();

/// Changes UI-usable name of the caller [`EItem`].
///
/// Allowed to be empty, but not allowed to be `none` for existent items.
public function AcediaError SetName(BaseText newName);

/// Checks whether this item can be removed as a result of player's action.
///
/// We will not enforce items to be completely unremovable through the API, so
/// this only marks an item as one unintended to be removed from inventory.
/// Enforcing of this rule is up to the implementation.
/// 9mm pistol is a good example for Killing Floor.
///
/// Note that item being removable does not mean game must always (or ever)
/// provide players with a way to remove that item, just that it can.
public function bool IsRemovable();

/// Changes whether this item can be removed as a result of player's action.
///
/// We will not enforce items to be completely unremovable through the API, so
/// this only marks an item as one unintended to be removed from inventory.
/// Enforcing of this rule is up to the implementation.
/// 9mm pistol is a good example for Killing Floor.
///
/// Note that item being removable does not mean game must always (or ever)
/// provide players with a way to remove that item, just that it can.
public function AcediaError SetRemovable(bool newRemovable);

/// Returns current amount of this item in the stack.
///
/// Some items in the inventory can be stored in non-singular quantities
/// (e.g. ammo, perishables, money).
/// This method returns current amount inside the stack represented by caller
/// [`EItem`].
///
/// Guaranteed to not be negative, but can exceed maximum value
/// (see [`EItem::GetMaxStackSize()`]).
public function int GetStackSize();

/// Returns current amount of this item inside inventory of the owner of
/// referred `EItem` item.
///
/// Some items in the inventory can be stored in non-singular quantities
/// (e.g. ammo, perishables, money).
/// This method returns current amount inside the inventory of the owner of
/// referred [`EItem`] item.
///
/// Guaranteed to not be negative, but can exceed maximum value
/// (see [`EItem::GetMaxTotalAmount()`] and also [`EItem::GetMaxStackSize()`]).
public function int GetTotalAmount();

/// Changes current amount of this item in the stack.
///
/// Some items in the inventory can be stored in non-singular quantities
/// (e.g. ammo, perishables, money). This method allows to configure how much of
/// the same item can belong to the same stack.
///
/// Negative values will be treated as `0`.
/// Values that exceed maximum (total) amount will be automatically reduced to
/// said maximum amount (see [`EItem::GetMaxStackSize()`] and
/// [`EItem::GetMaxTotalAmount()`]), unless [`forceAddition`] is also set to
/// `true`.
/// If [`forceAddition`] is `true`, then new stack size is allowed to go outside
/// maximum allowed range.
/// Cannot force total count to go into negative values.
public function AcediaError SetStackSize(int amount, optional bool forceAddition);

/// Changes amount of items inside the referred stack by given `amount`.
///
/// Negative argument values will decrease it ("adding" a negative amount).
///
/// New value cannot go below zero and can only exceed maximum amount
/// (see [`EItem::GetMaxStackSize()`] and [`EItem::GetMaxTotalAmount()`]) if
/// [`forceAddition`] is also set to `true`.
/// If [`forceAddition`] is set to `false` and resulting value is to go over
/// the limits - it will be clamped inside allowed range.
/// Cannot force total count to go into negative values.
public function AcediaError Add(int amount, optional bool forceAddition);

/// Returns maximum amount of items in the stack referred [`EItem`] supports.
///
/// Some items in the inventory can be stored in non-singular quantities
/// (e.g. ammo, perishables, money). This method returns current amount inside
/// the inventory of the owner of referred [`EItem`] item.
///
/// This is not a hard limit and can be bypassed by [`EItem::SetAmount()`] and
/// [`EItem::Add()`] methods, meaning that it is possible that
/// `GetStackSize() > GetMaxStackSize()`.
/// Inventory can also override such limits.
///
/// This method is different from [`EItem::GetMaxTotalAmount()`] in that it only
/// checks limit for its own stack and not owner's inventory-wide limit.
///
/// Treat this value like a soft limit obtainable through "normal means", that
/// can only be exceeded through cheats or special powerups of some kind.
///
/// Returning negative value means that there is no upper limit.
/// Zero is considered a valid value.
public function int GetMaxStackSize();

/// Returns maximum total amount of items of type of the referred [`EItem`] that
/// its owner can hold.
///
/// Some items in the inventory can be stored in non-singular quantities
/// (e.g. ammo, perishables, money). This method returns current amount inside
/// the inventory of the owner of referred [`EItem`] item.
///
/// This is not a hard limit and can be bypassed by [`EItem::SetAmount()`] and
/// [`EItem::Add()`] methods, meaning that it is possible that
/// `GetTotalAmount() > GetMaxTotalAmount()`.
///
/// This method is different from [`EItem::GetMaxStackSize()`] in that it checks
/// the limit for the whole inventory of its owner, instead of just inside its
/// own stack.
///
/// Treat this value like a limit obtainable through "normal means", that
/// can only be exceeded through cheats or special powerups of some kind.
///
/// Returning negative value means that there is no upper limit.
/// Zero is considered a valid value.
public function int GetMaxTotalAmount();

/// Changes maximum amount of items in the stack referred [`EItem`] supports.
///
/// Some items in the inventory can be stored in non-singular quantities
/// (e.g. ammo, perishables, money). This method returns current amount inside
/// the inventory of the owner of referred [`EItem`] item.
///
/// [`leaveCurrentAmount`] parameter allows to specify that already stored items
/// shouldn't be discarded in case their current amount goes over the new limit.
///
/// This method is different from [`EItem::SetMaxTotalAmount()`] in that it only
/// changes limit for its own stack and not owner's inventory-wide limit.
///
/// Treat this value like a soft limit obtainable through "normal means", that
/// can only be exceeded through cheats or special powerups of some kind.
///
/// The referred item is not required to support this method and may refuse to
/// change the maximum value.
/// It can also only support certain ranges of values.
public function AcediaError SetMaxStackSize(int newMaxAmmo, optional bool leaveCurrentAmount);

/// Returns maximum total amount of items of type of the referred [`EItem`] that
/// its owner can hold.
///
/// Some items in the inventory can be stored in non-singular quantities
/// (e.g. ammo, perishables, money). This method returns current amount inside
/// the inventory of the owner of referred [`EItem`] item.
///
/// [`leaveCurrentAmount`] parameter allows to specify that already stored items
/// shouldn't be discarded in case their current amount goes over the new limit.
///
/// This method is different from [`EItem::SetMaxStackSize()`] in that it
/// changes the limit for the whole inventory of its owner, instead of just
/// inside its own stack.
///
/// Treat this value like a limit obtainable through "normal means", that
/// can only be exceeded through cheats or special powerups of some kind.
///
/// The referred item is not required to support this method and may refuse to
/// change the maximum value.
/// It can also only support certain ranges of values.
public function AcediaError SetMaxTotalAmount(
    int newTotalMaxAmmo,
    optional bool leaveCurrentAmount
);

/// Returns "weight" of the caller [`EItem`].
///
/// Weight is allowed to have any integer value.
///
/// Caller [`EItem`] is allowed to refuse this call and keep the old weight.
/// The item must either keep the old weight or set it to newWeight;
/// setting it to a different value is forbidden.
public function AcediaError SetWeight(int newWeight);

/// Returns current weight of the caller [`EItem`].
///
/// Weight is allowed to have any integer value.
///
/// If concept of weight is not applicable, this method should return `0`.
/// However inverse does not hold - returning `0` does not mean that weight
/// is not applicable for the caller [`EItem`].
public function int GetWeight();

/// Checks if caller [`EItem`] can be bought by the player.
///
/// Buying caller [`EItem`] can still be prevented by other mechanics.
public function bool IsBuyable();

/// Checks if caller [`EItem`] can be sold to the player.
///
/// Selling caller [`EItem`] can still be prevented by other mechanics.
public function bool IsSellable();

/// Changes whether caller [`EItem`] can be bought by the player.
///
/// Item must support "tradable" component for this method to work.
public function AcediaError SetBuyable(bool value);

/// Changes whether caller [`EItem`] can be sold to the player.
///
/// Item must support "tradable" component for this method to work.
public function AcediaError SetSellable(bool value);

/// Returns the lot size of this item.
///
/// Lot size is minimal transaction amount when selling or buying an item.
/// One of the main uses of this value is to specify how much copies of the item
/// are we buying with / selling for its available costs.
///
/// Note, it enforcement need not be strict and implementation can use it simply
/// as a recommendation for how the UI should present buying options.
public function int GetLotSize();

/// Changes the lot size of this item.
///
/// This value must be positive, otherwise method shouldn't do anything.
///
/// Lot size is minimal transaction amount when selling or buying an item.
/// One of the main uses of this value is to specify how much copies of the item
/// are we buying with / selling for its available costs.
///
/// Note, it enforcement need not be strict and implementation can use it simply
/// as a recommendation for how the UI should present buying options.
public function AcediaError SetLotSize(int newLotSize);

/// Returns all available item costs for buying.
///
/// Item can support several costs to support buying it with different
/// currencies.
/// Amount of item bought for each specified cost is equal to its lot size
/// (see [`EItem::GetLotSize()`] and [`EItem::SetLotSize()`]).
///
/// Optional argument [`buyer`] can be used to adjust returned costs,
/// recalculating them for the given player (e.g. due to class discounts or
/// other bonuses).
///
/// Item can have some buyable costs specified and not be buyable
/// (in the sense of [`EItem::IsBuyable()`]).
/// Item can also be buyable and not have any costs.
/// How to deal with the latter situation is up to the implementation.
public function array<TradingGameplayAPI.TradableCost> GetBuyingCosts(optional EPlayer buyer);

/// Changes available item costs for buying.
///
/// Item can support several costs to support buying it with different
/// currencies.
/// Amount of item bought for each specified cost is equal to its lot size
/// (see [`EItem::GetLotSize()`] and [`EItem::SetLotSize()`]).
///
/// Item can have some buyable costs specified and not be buyable
/// (in the sense of [`EItem::IsBuyable()`]).
/// Item can also be buyable and not have any costs.
/// How to deal with the latter situation is up to the implementation.
public function SetBuyingCosts(array<TradingGameplayAPI.TradableCost> newCosts);

/// Checks if this item uses a separate array of costs for selling.
///
/// If returned value is `false`, then selling costs are determined by
/// the buying cost.
///
/// See [`EItem::SetSellingCostsMode()`] for changing this value.
public function IsUsingSellingCosts();

/// Changes whether this item uses a separate array of costs for selling.
///
/// See [`EItem::SetSellingCostsMode()`] for reading this value.
public function AcediaError SetSellingCostsMode(bool useSellingCosts);

/// Returns all available item costs for selling.
///
/// Item can support several costs to support selling it for different
/// currencies.
/// Amount of item sold for each specified cost is equal to its lot size
/// (see [`EItem::GetLotSize()`] and [`EItem::SetLotSize()`]).
///
/// Optional argument [`seller`] can be used to adjust returned costs,
/// recalculating them for the given player (e.g. due to class discounts or
/// other bonuses).
///
/// If [`EItem::IsUsingSellingCosts()`] returns `true`, i.e. sell costs are
/// scaled off of buying costs, this method will return properly rescaled
/// selling costs instead of those set by the [`EItem::SetSellingCosts()`].
///
/// Item can have some sellable costs specified and not be actually sellable
/// (in the sense of [`EItem::IsSellable()`]).
/// Item can also be sellable and not have any costs.
/// How to deal with the latter situation is up to the implementation.
public function array<TradingGameplayAPI.TradableCost> GetSellingCosts(optional EPlayer seller);

/// Changes available item costs for selling.
///
/// Item can support several costs to support selling it for different
/// currencies.
/// Amount of item sold for each specified cost is equal to its lot size
/// (see [`EItem::GetLotSize()`] and [`EItem::SetLotSize()`]).
///
/// If [`EItem::IsUsingSellingCosts()`] returns `true`, i.e. sell costs are
/// scaled off of buying costs, this method won't actually affect the gameplay.
/// However, once [`EItem::SetSellingCostsMode()`] is used to enable using
/// specified selling costs, last specified value must be used.
///
/// Item can have some sellable costs specified and not be actually sellable
/// (in the sense of [`EItem::IsSellable()`]).
/// Item can also be sellable and not have any costs.
/// How to deal with the latter situation is up to the implementation.
public function SetSellingCosts(array<TradingGameplayAPI.TradableCost> newCosts);

/// Changes buying price of the caller [`EItem`] in the default currency.
///
/// This method changes (or adds, if it was missing) a cost in only the default
/// trading currency.
///
/// For selling price, see [`EItem::SetSellingPrice()`].
///
/// This is a convenience method for setting buying prices in default currency.
/// It is particularly useful for making modes for game with only one or
/// a single very dominant currency (like Killing Floor with dosh).
///
/// For more details refer to [`EItem::SetBuyingCosts()`].
public function AcediaError SetPrice(int newPrice);

/// Returns buying price of the caller [`EItem`] in the default currency.
///
/// This method finds lowest cost in only the default trading currency and
/// returns its value.
/// If there's no such cost, it will return negative value instead.
///
/// Optional argument [`buyer`] can be used to adjust returned cost,
/// recalculating it for the given player (e.g. due to class discounts or
/// other bonuses).
///
/// For selling price, see [`EItem::GetSellingPrice()`].
///
/// This is a convenience method for setting buying prices in default currency.
/// It is particularly useful for making modes for game with only one or
/// a single very dominant currency (like Killing Floor with dosh).
///
/// For more details refer to [`EItem::SetBuyingCosts()`].
public function int GetPrice(optional EPlayer buyer);

/// Compute how much [`amount`] instances of referred [`EItem`] would cost
/// in default currency.
///
/// For example, for ammo it can be used to compute the cost of a specific
/// amount of bullets.
///
/// This method finds lowest cost in only the default trading currency and
/// uses its value for calculations.
/// If there's no such cost, it will return negative value instead.
///
/// Optional argument [`buyer`] can be used to adjust returned cost,
/// recalculating it for the given player (e.g. due to class discounts or
/// other bonuses).
///
/// For selling price, see [`EItem::GetSellingPriceOf()`].
///
/// This is a convenience method for setting buying prices in default currency.
/// It is particularly useful for making modes for game with only one or
/// a single very dominant currency (like Killing Floor with dosh).
///
/// This method ignores lot size (see [`EItem::GetLotSize()`] for more details)
/// and tries to compute the price of the specified amount even if it is
/// impossible to buy (e.g. it estimates price of 15 bullets even if you can buy
/// 20 at the minimum).
public function int GetPriceOf(int amount, optional EPlayer buyer);

/// Changes selling price of the caller [`EItem`] in the default currency.
///
/// This method changes (or adds, if it was missing) a cost in only the default
/// trading currency.
///
/// For buying price, see [`EItem::SetPrice()`].
///
/// If [`EItem::IsUsingSellingCosts()`] returns `true`, i.e. sell costs are
/// scaled off of buying costs, this method won't actually affect the gameplay.
/// However, once [`EItem::SetSellingCostsMode()`] is used to enable using
/// specified selling costs, last specified value must be used.
///
/// This is a convenience method for setting selling prices in default currency.
/// It is particularly useful for making modes for game with only one or
/// a single very dominant currency (like Killing Floor with dosh).
///
/// For more details refer to [`EItem::SetSellingCosts()`].
public function AcediaError SetSellingPrice(int newPrice);

/// Returns selling price of the caller [`EItem`] in the default currency.
///
/// This method finds lowest cost in only the default trading currency and
/// returns its value.
/// If there's no such cost, it will return negative value instead.
///
/// Optional argument [`seller`] can be used to adjust returned cost,
/// recalculating it for the given player (e.g. due to class discounts or
/// other bonuses).
///
/// For buying price, see [`EItem::GetPrice()`].
///
/// If [`EItem::IsUsingSellingCosts()`] returns `true`, i.e. sell costs are
/// scaled off of buying costs, this method won't actually affect the gameplay.
/// However, once [`EItem::SetSellingCostsMode()`] is used to enable using
/// specified selling costs, last specified value must be used.
///
/// This is a convenience method for getting selling prices in default currency.
/// It is particularly useful for making modes for game with only one or
/// a single very dominant currency (like Killing Floor with dosh).
///
/// For more details refer to [`EItem::GetSellingCosts()`].
public function int GetSellingPrice(optional EPlayer seller);

/// Compute how much [`amount`] instances of referred [`EItem`] would sell for
/// in default currency.
///
/// For example, for ammo it can be used to compute the cost of a specific
/// amount of bullets.
///
/// This method finds lowest cost in only the default trading currency and
/// uses its value for calculations.
/// If there's no such cost, it will return negative value instead.
///
/// Optional argument [`seller`] can be used to adjust returned cost,
/// recalculating it for the given player (e.g. due to class discounts or
/// other bonuses).
///
/// For buying price, see [`EItem::GetPriceOf()`].
///
/// If [`EItem::IsUsingSellingCosts()`] returns `true`, i.e. sell costs are
/// scaled off of buying costs, this method won't actually affect the gameplay.
/// However, once [`EItem::SetSellingCostsMode()`] is used to enable using
/// specified selling costs, last specified value must be used.
///
/// This is a convenience method for getting selling prices in default currency.
/// It is particularly useful for making modes for game with only one or
/// a single very dominant currency (like Killing Floor with dosh).
///
/// This method ignores lot size (see [`EItem::GetLotSize()`] for more details)
/// and tries to compute the price of the specified amount even if it is
/// impossible to sell at such volume (e.g. it estimates price of 15 bullets
/// even if you can sell 20 at the minimum).
public function int GetSellingPriceOf(int amount, optional EPlayer seller);

defaultproperties {
}