mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2025-12-14 11:42:27 +00:00
* Converts traders to basic mobs (#79187) ## About The Pull Request This PR converts the two trader mobs into basic mobs, these being the basic debug trader that buys ectoplasm and sells ghost burgers, and Mr Bones, who buys empty milk cartons and bones, and sells bone relate paraphernalia. Traders now use dynamic appearance generation. The old sprites still exist as hallucinations, and as shop signs. Trader UI is now summoned via `COMSIG_ATOM_ATTACK_HAND`, which properly cancels the attack chain, so there is no longer need to put it on Interact. I kept most of the original behaviour, but moved them off into a component. I have also cached all the images generated for the radials, I hope I have not overengineered it. I have also created a new datum, which stores the trader's wares, needs, and speech patterns. Admins can put the component along with the trader data on any living mobs with an AI controller, turning them into traders. Keep in mind that most AI has random idle movement, meaning they have a chance to walk off, closing your trader radial.  The trader AI consists of the following, first, when a trader sees someone, they will deploy their shop, if one does not already exists. The shop consists of a chair, and a holographic sign. If you attack them, they will chase you with their weapons, and then return to their chair when victorious. If the chair is somehow destroyed, they will create a new shop when they see a new potential customer.  Mr Bones uses a variant of the AI, where they will run at you, and deploy their shop when they reach you. I call this the jumpscare variant. Below you can see me getting actually jumpscared because Mr Bones has stepped on a yelling frog when I opened the maintenance door.  I have also made an element that toggles an ai controlled combat mode when it gains a target, and when it loses it. I am using it to make Traders unable to trade while they are trying to kill a robber. To aid this, I a have made `/datum/ai_controller/proc/sig_remove_from_blackboard` send the `COMSIG_AI_BLACKBOARD_KEY_CLEARED` signal, in case the trader kills a mob that deletes itself on death. This means I could remove a signup `/datum/component/appearance_on_aggro` was doing towards Qdeleting. Below you can see Mr Bones shooting me with candy corn.   Traders actually only shoot you until you are conscious, so I survived here in crit. Most mobs don't have crit state, so they just die, so I am sticking by this voice line. Thank you @ CoiledLamb for help with the sale sign! ## Why It's Good For The Game Two more mobs off the list. The AI and Componentized behaviours allows us to set up new kind of traders. ## Changelog 🆑 refactor: Traders are basic mobs now. Please alert us of any strange behaviours! code: If there is only one option, radial lists will autopick it. This behaviour can be turned off via a new argument. /🆑 * Converts traders to basic mobs --------- Co-authored-by: Profakos <profakos@gmail.com>
458 lines
19 KiB
Plaintext
458 lines
19 KiB
Plaintext
#define TRADER_RADIAL_BUY "TRADER_RADIAL_BUY"
|
|
#define TRADER_RADIAL_SELL "TRADER_RADIAL_SELL"
|
|
#define TRADER_RADIAL_TALK "TRADER_RADIAL_TALK"
|
|
#define TRADER_RADIAL_LORE "TRADER_RADIAL_LORE"
|
|
#define TRADER_RADIAL_NO "TRADER_RADIAL_NO"
|
|
#define TRADER_RADIAL_YES "TRADER_RADIAL_YES"
|
|
#define TRADER_RADIAL_OUT_OF_STOCK "TRADER_RADIAL_OUT_OF_STOCK"
|
|
#define TRADER_RADIAL_DISCUSS_BUY "TRADER_RADIAL_DISCUSS_BUY"
|
|
#define TRADER_RADIAL_DISCUSS_SELL "TRADER_RADIAL_DISCUSS_SELL"
|
|
|
|
#define TRADER_OPTION_BUY "Buy"
|
|
#define TRADER_OPTION_SELL "Sell"
|
|
#define TRADER_OPTION_TALK "Talk"
|
|
#define TRADER_OPTION_LORE "Lore"
|
|
#define TRADER_OPTION_NO "No"
|
|
#define TRADER_OPTION_YES "Yes"
|
|
#define TRADER_OPTION_BUYING "Buying?"
|
|
#define TRADER_OPTION_SELLING "Selling?"
|
|
|
|
//The defines below show the index the info is located in the product_info entry list
|
|
|
|
#define TRADER_PRODUCT_INFO_PRICE 1
|
|
#define TRADER_PRODUCT_INFO_QUANTITY 2
|
|
//Only valid for wanted_items
|
|
#define TRADER_PRODUCT_INFO_PRICE_MOD_DESCRIPTION 3
|
|
|
|
/**
|
|
* # Trader NPC Component
|
|
* Manages the barks and the stocks of the traders
|
|
* Also manages the interactive radial menu
|
|
*/
|
|
/datum/component/trader
|
|
|
|
/**
|
|
* Format; list(TYPEPATH = list(PRICE, QUANTITY))
|
|
* Associated list of items the NPC sells with how much they cost and the quantity available before a restock
|
|
* This list is filled by Initialize(), if you want to change the starting products, modify initial_products()
|
|
* *
|
|
*/
|
|
var/list/obj/item/products = list()
|
|
/**
|
|
* A list of wanted items that the trader would wish to buy, each typepath has a assigned value, quantity and additional flavor text
|
|
*
|
|
* CHILDREN OF TYPEPATHS INCLUDED IN WANTED_ITEMS WILL BE TREATED AS THE PARENT IF NO ENTRY EXISTS FOR THE CHILDREN
|
|
*
|
|
* As an additional note; if you include multiple children of a typepath; the typepath with the most children should be placed after all other typepaths
|
|
* Bad; list(/obj/item/milk = list(100, 1, ""), /obj/item/milk/small = list(50, 2, ""))
|
|
* Good; list(/obj/item/milk/small = list(50, 2, ""), /obj/item/milk = list(100, 1, ""))
|
|
* This is mainly because sell_item() uses a istype(item_being_sold, item_in_entry) to determine what parent should the child be automatically considered as
|
|
* If /obj/item/milk/small/spooky was being sold; /obj/item/milk/small would be the first to check against rather than /obj/item/milk
|
|
*
|
|
* Format; list(TYPEPATH = list(PRICE, QUANTITY, ADDITIONAL_DESCRIPTION))
|
|
* Associated list of items able to be sold to the NPC with the money given for them.
|
|
* The price given should be the "base" price; any price manipulation based on variables should be done with apply_sell_price_mods()
|
|
* ADDITIONAL_DESCRIPTION is any additional text added to explain how the variables of the item effect the price; if it's stack based, it's final price depends how much is in the stack
|
|
* EX; /obj/item/stack/sheet/mineral/diamond = list(500, INFINITY, ", per 100 cm3 sheet of diamond")
|
|
* This list is filled by Initialize(), if you want to change the starting wanted items, modify initial_wanteds()
|
|
*/
|
|
var/list/wanted_items = list()
|
|
|
|
///Contains images of all radial icons
|
|
var/static/list/radial_icons_cache = list()
|
|
|
|
///Contains information of a specific trader
|
|
var/datum/trader_data/trader_data
|
|
|
|
/*
|
|
Can accept both a type path, and an instance of a datum. Type path has priority.
|
|
*/
|
|
/datum/component/trader/Initialize(trader_data_path = null, trader_data = null)
|
|
. = ..()
|
|
if(!isliving(parent))
|
|
return COMPONENT_INCOMPATIBLE
|
|
|
|
if(ispath(trader_data_path, /datum/trader_data))
|
|
trader_data = new trader_data_path
|
|
if(isnull(trader_data))
|
|
CRASH("Initialised trader component with no trader data.")
|
|
|
|
src.trader_data = trader_data
|
|
|
|
radial_icons_cache = list(
|
|
TRADER_RADIAL_BUY = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_buy"),
|
|
TRADER_RADIAL_SELL = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_sell"),
|
|
TRADER_RADIAL_TALK = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_talk"),
|
|
TRADER_RADIAL_LORE = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_lore"),
|
|
TRADER_RADIAL_DISCUSS_BUY = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_buying"),
|
|
TRADER_RADIAL_DISCUSS_SELL = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_selling"),
|
|
TRADER_RADIAL_YES = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_yes"),
|
|
TRADER_RADIAL_NO = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_no"),
|
|
TRADER_RADIAL_OUT_OF_STOCK = image(icon = 'icons/hud/radial.dmi', icon_state = "radial_center"),
|
|
)
|
|
|
|
restock_products()
|
|
renew_item_demands()
|
|
|
|
/datum/component/trader/RegisterWithParent()
|
|
RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, PROC_REF(on_attack_hand))
|
|
|
|
/datum/component/trader/UnregisterFromParent()
|
|
UnregisterSignal(parent, COMSIG_ATOM_ATTACK_HAND)
|
|
|
|
///If our trader is alive, and the customer left clicks them with an empty hand without combat mode
|
|
/datum/component/trader/proc/on_attack_hand(atom/source, mob/living/carbon/customer)
|
|
SIGNAL_HANDLER
|
|
if(!can_trade(customer) || customer.combat_mode)
|
|
return
|
|
var/list/npc_options = list()
|
|
if(length(products))
|
|
npc_options[TRADER_OPTION_BUY] = radial_icons_cache[TRADER_RADIAL_BUY]
|
|
if(length(wanted_items))
|
|
npc_options[TRADER_OPTION_SELL] = radial_icons_cache[TRADER_RADIAL_SELL]
|
|
if(length(trader_data.say_phrases))
|
|
npc_options[TRADER_OPTION_TALK] = radial_icons_cache[TRADER_RADIAL_TALK]
|
|
if(!length(npc_options))
|
|
return
|
|
|
|
var/mob/living/trader = parent
|
|
trader.face_atom(customer)
|
|
|
|
INVOKE_ASYNC(src, PROC_REF(open_npc_options), customer, npc_options)
|
|
|
|
return COMPONENT_CANCEL_ATTACK_CHAIN
|
|
|
|
/**
|
|
* Generates a radial of the initial radials of the NPC
|
|
* Called via asynch, due to the sleep caused by show_radial_menu
|
|
* Arguments:
|
|
* * customer - (Mob REF) The mob trying to buy something
|
|
*/
|
|
/datum/component/trader/proc/open_npc_options(mob/living/carbon/customer, list/npc_options)
|
|
if(!can_trade(customer))
|
|
return
|
|
var/npc_result = show_radial_menu(customer, parent, npc_options, custom_check = CALLBACK(src, PROC_REF(check_menu), customer), require_near = TRUE, tooltips = TRUE)
|
|
switch(npc_result)
|
|
if(TRADER_OPTION_BUY)
|
|
buy_item(customer)
|
|
if(TRADER_OPTION_SELL)
|
|
try_sell(customer)
|
|
if(TRADER_OPTION_TALK)
|
|
discuss(customer)
|
|
|
|
/**
|
|
* Checks if the customer is ok to use the radial
|
|
*
|
|
* Checks if the customer is not a mob or is incapacitated or not adjacent to the source of the radial, in those cases returns FALSE, otherwise returns TRUE
|
|
* Arguments:
|
|
* * customer - (Mob REF) The mob checking the menu
|
|
*/
|
|
/datum/component/trader/proc/check_menu(mob/customer)
|
|
if(!istype(customer))
|
|
return FALSE
|
|
if(IS_DEAD_OR_INCAP(customer) || !customer.Adjacent(parent))
|
|
return FALSE
|
|
return TRUE
|
|
|
|
/**
|
|
* Generates a radial of the items the NPC sells and lets the user try to buy one
|
|
* Arguments:
|
|
* * customer - (Mob REF) The mob trying to buy something
|
|
*/
|
|
/datum/component/trader/proc/buy_item(mob/customer)
|
|
if(!can_trade(customer))
|
|
return
|
|
|
|
if(!LAZYLEN(products))
|
|
return
|
|
|
|
var/list/display_names = list()
|
|
var/list/items = list()
|
|
var/list/product_info
|
|
|
|
for(var/obj/item/thing as anything in products)
|
|
display_names["[initial(thing.name)]"] = thing
|
|
|
|
if(!radial_icons_cache[thing])
|
|
radial_icons_cache[thing] = image(icon = initial(thing.icon), icon_state = initial(thing.icon_state_preview) ? initial(thing.icon_state_preview) : initial(thing.icon_state))
|
|
|
|
var/image/item_image = radial_icons_cache[thing]
|
|
product_info = products[thing]
|
|
|
|
if(product_info[TRADER_PRODUCT_INFO_QUANTITY] <= 0) //out of stock
|
|
item_image.overlays += radial_icons_cache[TRADER_RADIAL_OUT_OF_STOCK]
|
|
|
|
items += list("[initial(thing.name)]" = item_image)
|
|
|
|
var/pick = show_radial_menu(customer, parent, items, custom_check = CALLBACK(src, PROC_REF(check_menu), customer), require_near = TRUE, tooltips = TRUE)
|
|
if(!pick || !can_trade(customer))
|
|
return
|
|
|
|
var/obj/item/item_to_buy = display_names[pick]
|
|
var/mob/living/trader = parent
|
|
trader.face_atom(customer)
|
|
product_info = products[item_to_buy]
|
|
|
|
if(!product_info[TRADER_PRODUCT_INFO_QUANTITY])
|
|
trader.say("[initial(item_to_buy.name)] appears to be out of stock.")
|
|
return
|
|
|
|
trader.say("It will cost you [product_info[TRADER_PRODUCT_INFO_PRICE]] [trader_data.currency_name] to buy \the [initial(item_to_buy.name)]. Are you sure you want to buy it?")
|
|
var/list/npc_options = list(
|
|
TRADER_OPTION_YES = radial_icons_cache[TRADER_RADIAL_YES],
|
|
TRADER_OPTION_NO = radial_icons_cache[TRADER_RADIAL_NO],
|
|
)
|
|
|
|
var/buyer_will_buy = show_radial_menu(customer, trader, npc_options, custom_check = CALLBACK(src, PROC_REF(check_menu), customer), require_near = TRUE, tooltips = TRUE)
|
|
if(buyer_will_buy != TRADER_OPTION_YES || !can_trade(customer))
|
|
return
|
|
|
|
trader.face_atom(customer)
|
|
|
|
if(!spend_buyer_offhand_money(customer, product_info[TRADER_PRODUCT_INFO_PRICE]))
|
|
trader.say(trader_data.return_trader_phrase(NO_CASH_PHRASE))
|
|
return
|
|
|
|
item_to_buy = new item_to_buy(get_turf(customer))
|
|
customer.put_in_hands(item_to_buy)
|
|
playsound(trader, trader_data.sell_sound, 50, TRUE)
|
|
log_econ("[item_to_buy] has been sold to [customer] (typepath used for product info; [item_to_buy.type]) by [trader] for [product_info[TRADER_PRODUCT_INFO_PRICE]] cash.")
|
|
product_info[TRADER_PRODUCT_INFO_QUANTITY] -= 1
|
|
trader.say(trader_data.return_trader_phrase(BUY_PHRASE))
|
|
|
|
///Calculates the value of money in the hand of the buyer and spends it if it's sufficient
|
|
/datum/component/trader/proc/spend_buyer_offhand_money(mob/customer, the_cost)
|
|
var/value = 0
|
|
var/obj/item/holochip/cash = customer.is_holding_item_of_type(/obj/item/holochip)
|
|
if(cash)
|
|
value += cash.credits
|
|
if((value >= the_cost) && cash)
|
|
return cash.spend(the_cost)
|
|
return FALSE //Purchase unsuccessful
|
|
|
|
/**
|
|
* Tries to call sell_item on one of the customer's held items, if fail gives a chat message
|
|
*
|
|
* Gets both items in the customer's hands, and then tries to call sell_item on them, if both fail, he gives a chat message
|
|
* Arguments:
|
|
* * customer - (Mob REF) The mob trying to sell something
|
|
*/
|
|
/datum/component/trader/proc/try_sell(mob/customer)
|
|
if(!can_trade(customer))
|
|
return
|
|
var/sold_item = FALSE
|
|
for(var/obj/item/an_item in customer.held_items)
|
|
if(sell_item(customer, an_item))
|
|
sold_item = TRUE
|
|
break
|
|
if(!sold_item && can_trade(customer)) //only talk if you are not dead or in combat
|
|
var/mob/living/trader = parent
|
|
trader.say(trader_data.return_trader_phrase(ITEM_REJECTED_PHRASE))
|
|
|
|
|
|
/**
|
|
* Checks if an item is in the list of wanted items and if it is after a Yes/No radial returns generate_cash with the value of the item for the NPC
|
|
* Arguments:
|
|
* * customer - (Mob REF) The mob trying to sell something
|
|
* * selling - (Item REF) The item being sold
|
|
*/
|
|
/datum/component/trader/proc/sell_item(mob/customer, obj/item/selling)
|
|
if(isnull(selling))
|
|
return FALSE
|
|
var/list/product_info
|
|
//Keep track of the typepath; rather mundane but it's required for correctly modifying the wanted_items
|
|
//should a product be sellable because even if it doesn't have a entry because it's a child of a parent that is present on the list
|
|
var/typepath_for_product_info
|
|
|
|
if(selling.type in wanted_items)
|
|
product_info = wanted_items[selling.type]
|
|
typepath_for_product_info = selling.type
|
|
else //Assume wanted_items is setup in the correct way; read wanted_items documentation for more info
|
|
for(var/typepath in wanted_items)
|
|
if(!istype(selling, typepath))
|
|
continue
|
|
|
|
product_info = wanted_items[typepath]
|
|
typepath_for_product_info = typepath
|
|
break
|
|
|
|
if(!product_info) //Nothing interesting to sell
|
|
return FALSE
|
|
|
|
var/mob/living/trader = parent
|
|
|
|
if(product_info[TRADER_PRODUCT_INFO_QUANTITY] <= 0)
|
|
trader.say(trader_data.return_trader_phrase(TRADER_HAS_ENOUGH_ITEM_PHRASE))
|
|
return FALSE
|
|
|
|
var/cost = apply_sell_price_mods(selling, product_info[TRADER_PRODUCT_INFO_PRICE])
|
|
if(cost <= 0)
|
|
trader.say(trader_data.return_trader_phrase(ITEM_IS_WORTHLESS_PHRASE))
|
|
return FALSE
|
|
|
|
trader.say(trader_data.return_trader_phrase(INTERESTED_PHRASE))
|
|
trader.say("You will receive [cost] [trader_data.currency_name] for the [selling].")
|
|
var/list/npc_options = list(
|
|
TRADER_OPTION_YES = radial_icons_cache[TRADER_RADIAL_YES],
|
|
TRADER_OPTION_NO = radial_icons_cache[TRADER_RADIAL_NO],
|
|
)
|
|
|
|
trader.face_atom(customer)
|
|
|
|
var/npc_result = show_radial_menu(customer, trader, npc_options, custom_check = CALLBACK(src, PROC_REF(check_menu), customer), require_near = TRUE, tooltips = TRUE)
|
|
if(!can_trade(customer))
|
|
return
|
|
if(npc_result != TRADER_OPTION_YES)
|
|
trader.say(trader_data.return_trader_phrase(ITEM_SELLING_CANCELED_PHRASE))
|
|
return TRUE
|
|
|
|
trader.say(trader_data.return_trader_phrase(ITEM_SELLING_ACCEPTED_PHRASE))
|
|
playsound(trader, trader_data.sell_sound, 50, TRUE)
|
|
log_econ("[selling] has been sold to [trader] (typepath used for product info; [typepath_for_product_info]) by [customer] for [cost] cash.")
|
|
exchange_sold_items(selling, cost, typepath_for_product_info)
|
|
generate_cash(cost, customer)
|
|
return TRUE
|
|
|
|
/**
|
|
* Modifies the 'base' price of a item based on certain variables
|
|
*
|
|
* Arguments:
|
|
* * Reference to the item; this is the item being sold
|
|
* * Original cost; the original cost of the item, to be manipulated depending on the variables of the item, one example is using item.amount if it's a stack
|
|
*/
|
|
/datum/component/trader/proc/apply_sell_price_mods(obj/item/selling, original_cost)
|
|
if(isstack(selling))
|
|
var/obj/item/stack/stackoverflow = selling
|
|
original_cost *= stackoverflow.amount
|
|
return original_cost
|
|
|
|
/**
|
|
* Handles modifying/deleting the items to ensure that a proper amount is converted into cash; put into it's own proc to make the children of this not override a 30+ line sell_item()
|
|
*
|
|
* Arguments:
|
|
* * selling - (Item REF) this is the item being sold
|
|
* * value_exchanged_for - (Number) the "value", useful for a scenario where you want to remove enough items equal to the value
|
|
* * original_typepath - (Typepath) For scenarios where a children of a parent is being sold but we want to modify the parent's product information
|
|
*/
|
|
/datum/component/trader/proc/exchange_sold_items(obj/item/selling, value_exchanged_for, original_typepath)
|
|
var/list/product_info = wanted_items[original_typepath]
|
|
if(isstack(selling))
|
|
var/obj/item/stack/the_stack = selling
|
|
var/actually_sold = min(the_stack.amount, product_info[TRADER_PRODUCT_INFO_QUANTITY])
|
|
the_stack.use(actually_sold)
|
|
product_info[TRADER_PRODUCT_INFO_QUANTITY] -= (actually_sold)
|
|
else
|
|
qdel(selling)
|
|
product_info[TRADER_PRODUCT_INFO_QUANTITY] -= 1
|
|
|
|
/**
|
|
* Creates an item equal to the value set by the proc and puts it in the user's hands if possible
|
|
* Arguments:
|
|
* * value - A number; The amount of cash that will be on the holochip
|
|
* * customer - Reference to a mob; The mob we put the holochip in hands of
|
|
*/
|
|
/datum/component/trader/proc/generate_cash(value, mob/customer)
|
|
var/obj/item/holochip/chip = new /obj/item/holochip(get_turf(customer), value)
|
|
customer.put_in_hands(chip)
|
|
|
|
///Talk about what items are being sold/wanted by the trader and in what quantity or lore
|
|
/datum/component/trader/proc/discuss(mob/customer)
|
|
var/list/npc_options = list(
|
|
TRADER_OPTION_LORE = radial_icons_cache[TRADER_RADIAL_LORE],
|
|
TRADER_OPTION_SELLING = radial_icons_cache[TRADER_RADIAL_DISCUSS_SELL],
|
|
TRADER_OPTION_BUYING = radial_icons_cache[TRADER_RADIAL_DISCUSS_BUY],
|
|
)
|
|
var/pick = show_radial_menu(customer, parent, npc_options, custom_check = CALLBACK(src, PROC_REF(check_menu), customer), require_near = TRUE, tooltips = TRUE)
|
|
if(!can_trade(customer))
|
|
return
|
|
switch(pick)
|
|
if(TRADER_OPTION_LORE)
|
|
var/mob/living/trader = parent
|
|
trader.say(trader_data.return_trader_phrase(TRADER_LORE_PHRASE))
|
|
if(TRADER_OPTION_BUYING)
|
|
trader_buys_what(customer)
|
|
if(TRADER_OPTION_SELLING)
|
|
trader_sells_what(customer)
|
|
|
|
///Displays to the customer what the trader is willing to buy and how much until a restock happens
|
|
/datum/component/trader/proc/trader_buys_what(mob/customer)
|
|
if(!can_trade(customer))
|
|
return
|
|
if(!length(wanted_items))
|
|
var/mob/living/trader = parent
|
|
trader.say(trader_data.return_trader_phrase(TRADER_NOT_BUYING_ANYTHING))
|
|
return
|
|
|
|
var/list/buy_info = list(span_green("I'm willing to buy the following:"))
|
|
|
|
var/list/product_info
|
|
for(var/obj/item/thing as anything in wanted_items)
|
|
product_info = wanted_items[thing]
|
|
var/tern_op_result = (product_info[TRADER_PRODUCT_INFO_QUANTITY] == INFINITY ? "as many as I can." : "[product_info[TRADER_PRODUCT_INFO_QUANTITY]]") //Coder friendly string concat
|
|
if(product_info[TRADER_PRODUCT_INFO_QUANTITY] <= 0) //Zero demand
|
|
buy_info += span_notice("• [span_red("(DOESN'T WANT MORE)")] [initial(thing.name)] for [product_info[TRADER_PRODUCT_INFO_PRICE]] [trader_data.currency_name][product_info[TRADER_PRODUCT_INFO_PRICE_MOD_DESCRIPTION]]; willing to buy [span_red("[tern_op_result]")] more.")
|
|
else
|
|
buy_info += span_notice("• [initial(thing.name)] for [product_info[TRADER_PRODUCT_INFO_PRICE]] [trader_data.currency_name][product_info[TRADER_PRODUCT_INFO_PRICE_MOD_DESCRIPTION]]; willing to buy [span_green("[tern_op_result]")]")
|
|
|
|
to_chat(customer, examine_block(buy_info.Join("\n")))
|
|
|
|
///Displays to the customer what the trader is selling and how much is in stock
|
|
/datum/component/trader/proc/trader_sells_what(mob/customer)
|
|
if(!can_trade(customer))
|
|
return
|
|
var/mob/living/trader = parent
|
|
if(!length(products))
|
|
trader.say(trader_data.return_trader_phrase(TRADER_NOT_SELLING_ANYTHING))
|
|
return
|
|
var/list/sell_info = list(span_green("I'm currently selling the following:"))
|
|
var/list/product_info
|
|
for(var/obj/item/thing as anything in products)
|
|
product_info = products[thing]
|
|
var/tern_op_result = (product_info[TRADER_PRODUCT_INFO_QUANTITY] == INFINITY ? "an infinite amount" : "[product_info[TRADER_PRODUCT_INFO_QUANTITY]]") //Coder friendly string concat
|
|
if(product_info[TRADER_PRODUCT_INFO_QUANTITY] <= 0) //Out of stock
|
|
sell_info += span_notice("• [span_red("(OUT OF STOCK)")] [initial(thing.name)] for [product_info[TRADER_PRODUCT_INFO_PRICE]] [trader_data.currency_name]; [span_red("[tern_op_result]")] left in stock")
|
|
else
|
|
sell_info += span_notice("• [initial(thing.name)] for [product_info[TRADER_PRODUCT_INFO_PRICE]] [trader_data.currency_name]; [span_green("[tern_op_result]")] left in stock")
|
|
to_chat(customer, examine_block(sell_info.Join("\n")))
|
|
|
|
///Sets quantity of all products to initial(quanity); this proc is currently called during initialize
|
|
/datum/component/trader/proc/restock_products()
|
|
products = trader_data.initial_products.Copy()
|
|
|
|
///Sets quantity of all wanted_items to initial(quanity); this proc is currently called during initialize
|
|
/datum/component/trader/proc/renew_item_demands()
|
|
wanted_items = trader_data.initial_wanteds.Copy()
|
|
|
|
///Returns if the trader is conscious and its combat mode is disabled.
|
|
/datum/component/trader/proc/can_trade(mob/customer)
|
|
var/mob/living/trader = parent
|
|
if(trader.combat_mode)
|
|
trader.balloon_alert(customer, "in combat!")
|
|
return FALSE
|
|
if(IS_DEAD_OR_INCAP(trader))
|
|
trader.balloon_alert(customer, "indisposed!")
|
|
return FALSE
|
|
return TRUE
|
|
|
|
#undef TRADER_RADIAL_BUY
|
|
#undef TRADER_RADIAL_SELL
|
|
#undef TRADER_RADIAL_TALK
|
|
#undef TRADER_RADIAL_LORE
|
|
#undef TRADER_RADIAL_DISCUSS_BUY
|
|
#undef TRADER_RADIAL_DISCUSS_SELL
|
|
#undef TRADER_RADIAL_NO
|
|
#undef TRADER_RADIAL_YES
|
|
#undef TRADER_RADIAL_OUT_OF_STOCK
|
|
#undef TRADER_PRODUCT_INFO_PRICE
|
|
#undef TRADER_PRODUCT_INFO_QUANTITY
|
|
#undef TRADER_PRODUCT_INFO_PRICE_MOD_DESCRIPTION
|
|
|
|
#undef TRADER_OPTION_BUY
|
|
#undef TRADER_OPTION_SELL
|
|
#undef TRADER_OPTION_TALK
|
|
#undef TRADER_OPTION_LORE
|
|
#undef TRADER_OPTION_NO
|
|
#undef TRADER_OPTION_YES
|
|
#undef TRADER_OPTION_BUYING
|
|
#undef TRADER_OPTION_SELLING
|