From 790176b07b5377b29471788614f448272055d90b Mon Sep 17 00:00:00 2001 From: Anorak2024 Date: Thu, 22 Aug 2024 10:47:11 +0300 Subject: [PATCH 01/40] bugfix --- code/modules/mob/living/simple_animal/hulk.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/mob/living/simple_animal/hulk.dm b/code/modules/mob/living/simple_animal/hulk.dm index ea3b7acf9ae..401f3c386d9 100644 --- a/code/modules/mob/living/simple_animal/hulk.dm +++ b/code/modules/mob/living/simple_animal/hulk.dm @@ -129,7 +129,7 @@ custom_emote(EMOTE_AUDIBLE, "задыха%(ет,ют)%ся") SetWeakened(0) - adjustBruteLoss(modifier - health_regen) + adjustBruteLoss(modifier - health_regen * 4) if(health < 1) death() From 1d87a2f6ef76161d9f536fe1eb0dd5326f473cff Mon Sep 17 00:00:00 2001 From: Anorak2024 Date: Thu, 22 Aug 2024 10:49:05 +0300 Subject: [PATCH 02/40] 12312321 --- code/modules/mob/living/simple_animal/hulk.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/mob/living/simple_animal/hulk.dm b/code/modules/mob/living/simple_animal/hulk.dm index 401f3c386d9..ea3b7acf9ae 100644 --- a/code/modules/mob/living/simple_animal/hulk.dm +++ b/code/modules/mob/living/simple_animal/hulk.dm @@ -129,7 +129,7 @@ custom_emote(EMOTE_AUDIBLE, "задыха%(ет,ют)%ся") SetWeakened(0) - adjustBruteLoss(modifier - health_regen * 4) + adjustBruteLoss(modifier - health_regen) if(health < 1) death() From fd908fd1cd93b77bb5eb2c7f9a9e5bb3af96bdaf Mon Sep 17 00:00:00 2001 From: Anorak2024 Date: Thu, 22 Aug 2024 11:17:30 +0300 Subject: [PATCH 03/40] Revert "12312321" This reverts commit 1d87a2f6ef76161d9f536fe1eb0dd5326f473cff. --- code/modules/mob/living/simple_animal/hulk.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/mob/living/simple_animal/hulk.dm b/code/modules/mob/living/simple_animal/hulk.dm index ea3b7acf9ae..401f3c386d9 100644 --- a/code/modules/mob/living/simple_animal/hulk.dm +++ b/code/modules/mob/living/simple_animal/hulk.dm @@ -129,7 +129,7 @@ custom_emote(EMOTE_AUDIBLE, "задыха%(ет,ют)%ся") SetWeakened(0) - adjustBruteLoss(modifier - health_regen) + adjustBruteLoss(modifier - health_regen * 4) if(health < 1) death() From 651ab163b4a4c53a1fbe4f07442ba8e67658f703 Mon Sep 17 00:00:00 2001 From: Anorak2024 Date: Thu, 22 Aug 2024 11:18:43 +0300 Subject: [PATCH 04/40] Revert "bugfix" This reverts commit 790176b07b5377b29471788614f448272055d90b. --- code/modules/mob/living/simple_animal/hulk.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/modules/mob/living/simple_animal/hulk.dm b/code/modules/mob/living/simple_animal/hulk.dm index 401f3c386d9..ea3b7acf9ae 100644 --- a/code/modules/mob/living/simple_animal/hulk.dm +++ b/code/modules/mob/living/simple_animal/hulk.dm @@ -129,7 +129,7 @@ custom_emote(EMOTE_AUDIBLE, "задыха%(ет,ют)%ся") SetWeakened(0) - adjustBruteLoss(modifier - health_regen * 4) + adjustBruteLoss(modifier - health_regen) if(health < 1) death() From 3c70f80302215057c637abcffb00b216cc3b8b31 Mon Sep 17 00:00:00 2001 From: Anorak2024 Date: Fri, 23 Aug 2024 10:27:01 +0300 Subject: [PATCH 05/40] 123 --- code/game/machinery/vending.dm | 20 +++++++++++++++++++ .../objects/items/weapons/vending_items.dm | 4 ++++ 2 files changed, 24 insertions(+) diff --git a/code/game/machinery/vending.dm b/code/game/machinery/vending.dm index c1aaf852953..043c44b2039 100644 --- a/code/game/machinery/vending.dm +++ b/code/game/machinery/vending.dm @@ -3613,6 +3613,26 @@ /obj/item/mecha_parts/mecha_equipment/wormhole_generator = 10, ) +/obj/machinery/vending/custom + name = "Торговый автомат с уникальным содержимым" + desc = "Fabricator with advanced technology of bluespace transporting of resources." + + icon_state = "paivend_off" + panel_overlay = "paivend_panel" + screen_overlay = "paivend" + lightmask_overlay = "paivend_lightmask" + broken_overlay = "paivend_broken" + broken_lightmask_overlay = "paivend_broken_lightmask" + + ads_list = list("Купи самый дорогой предмет из моего содержимого! Не пожалеешь!","Мое содержимое разнообразней чем вся твоя жизнь!","У меня богатый внутренний мир.","Во мне может быть что угодно.","Не ядерный ли это диск во мне продается, всего за 1984 кредита?", "Не хочешь платить за содержимое? Сломай меня и получи все бесплатно!", "Товары на любой вкус и цвет!", "Может во мне продается контробанда?", "Не нравится мое содержимое? Создай свой кастомат, со своим уникальным содержимым!") + resistance_flags = FIRE_PROOF | LAVA_PROOF | FIRE_PROOF | ACID_PROOF | FREEZE_PROOF + products = null + prices = null + var/containtment = list() + + refill_canister = /obj/item/vending_refill/custom + + #undef FLICK_NONE #undef FLICK_VEND #undef FLICK_DENY diff --git a/code/game/objects/items/weapons/vending_items.dm b/code/game/objects/items/weapons/vending_items.dm index 174a998c3a7..15ba4636006 100644 --- a/code/game/objects/items/weapons/vending_items.dm +++ b/code/game/objects/items/weapons/vending_items.dm @@ -187,3 +187,7 @@ /obj/item/vending_refill/pai machine_name = "RoboFriends" icon_state = "restock_pai" + +/obj/item/vending_refill/custom + machine_name = "Торговый автомат с уникальным содержимым" + icon_state = "restock_pai" From 68fb23b0d8d1e425d49df47ba5bab7f8e6c80bc7 Mon Sep 17 00:00:00 2001 From: Anorak2024 Date: Sat, 24 Aug 2024 10:08:40 +0300 Subject: [PATCH 06/40] 1232123 --- code/game/machinery/customat.dm | 773 ++++++++++++++++++++++++++++++++ code/game/machinery/vending.dm | 8 - paradise.dme | 1 + 3 files changed, 774 insertions(+), 8 deletions(-) create mode 100644 code/game/machinery/customat.dm diff --git a/code/game/machinery/customat.dm b/code/game/machinery/customat.dm new file mode 100644 index 00000000000..e9eaa998c43 --- /dev/null +++ b/code/game/machinery/customat.dm @@ -0,0 +1,773 @@ +// Vendor flick sequence bitflags +/// Machine is not using vending/denying overlays +#define FLICK_NONE 0 +/// Machine is currently vending wares, and will not update its icon, unless its stat change. +#define FLICK_VEND 1 +/// Machine is currently denying wares, and will not update its icon, unless its stat change. +#define FLICK_DENY 2 + + +// !! Не забыть поудалять старые комментарии и сделать свои +// ! Добавить звуки и сообщения разным взаимодействиям с автоматом + + +/** + * Datum used to hold information about a product in a vending machine + */ +/datum/data/customat_product + name = "generic" + ///How many of this product we currently have + var/amount = 0 + var/list/obj/item/containtment = list() + var/price = 0 // Price to buy one + var/product_icon = '' + var/product_icon_state = "" + +/datum/data/customat_product/New(obj/item/I) + name = I.name + amound = 1 + containtment = lisT() + price = 0 + product_icon = I.icon + product_icon_state = I.icon_state + + +/obj/machinery/customat + name = "\improper Castomomat" + desc = "Торговый автомат с кастомным содержимым." + icon = 'icons/obj/machines/vending.dmi' + icon_state = "generic_off" + layer = BELOW_OBJ_LAYER + anchored = TRUE + density = TRUE + max_integrity = 600 // base vending integrity * 2 + armor = list(melee = 20, bullet = 0, laser = 0, energy = 0, bomb = 0, bio = 0, rad = 0, fire = 50, acid = 70) // base vending protection + resistance_flags = FIRE_PROOF + + // All the overlay controlling variables + /// Overlay of vendor maintenance panel. + var/panel_overlay = "" + /// Overlay of a vendor screen, will not apply of stat is NOPOWER. + var/screen_overlay = "" + /// Lightmask used when vendor is working properly. + var/lightmask_overlay = "" + /// Damage overlay applied if vendor is damaged enough. + var/broken_overlay = "" + /// Special lightmask for broken overlay. If vendor is BROKEN, but not dePOWERED we will see this, instead of `lightmask_overlay`. + var/broken_lightmask_overlay = "" + /// Overlay applied when machine is vending goods. + var/vend_overlay = "" + /// Special lightmask that will override default `lightmask_overlay`, while machine is vending goods. + var/vend_lightmask = "" + /// Amount of time until vending sequence is reseted. + var/vend_overlay_time = 5 SECONDS + /// Overlay applied when machine is denying its wares. + var/deny_overlay = "" + /// Special lightmask that will override default `lightmask_overlay`, while machine is denying its wares. + var/deny_lightmask = "" + /// Amount of time until denying sequence is reseted. + var/deny_overlay_time = 1.5 SECONDS + /// Flags used to correctly manipulate with vend/deny sequences. + var/flick_sequence = FLICK_NONE + /// If `TRUE` machine will only react to BROKEN/NOPOWER stat, when updating overlays. + var/skip_non_primary_icon_updates = FALSE + + // Power + use_power = IDLE_POWER_USE + idle_power_usage = 10 + var/vend_power_usage = 150 + + // Vending-related + /// No sales pitches if off + var/active = TRUE + /// If off, vendor is busy and unusable until current action finishes + var/vend_ready = TRUE + /// How long vendor takes to vend one item. + var/vend_delay = 1 SECONDS + /// Item currently being bought + var/datum/data/customat_product/currently_vending = null + + // List of vending_product items available. + var/list/product_records = list() + var/list/imagelist = list() + + // Stuff relating vocalizations + /// List of slogans the vendor will say, optional + var/list/ads_list = list("Купи самый дорогой предмет из моего содержимого! Не пожалеешь!", + "Мое содержимое разнообразней чем вся твоя жизнь!", + "У меня богатый внутренний мир.", + "Во мне может быть что угодно.", + "Не ядерный ли это диск во мне продается, всего за 1984 кредита?", + "Не хочешь платить за содержимое? Сломай меня и получи все бесплатно!", + "Товары на любой вкус и цвет!", + "Может во мне продается контробанда?", + "Не нравится мое содержимое? Создай свой кастомат, со своим уникальным содержимым!", + "Каждый раз, когда вы что-то покупаете, где-то в мире радуется один ассистент!") + + var/list/vend_reply = list("Спасибо за покупку, приходите еще!", + "Вы купили что-то, а разнообразие моего содержимого не уменьшилось!", + "Ваши кредиты пойдут на разработку новых уникальных товаров!", + "Спасибо что выбрали нас!", + "А ведь мог сломать и не платить...") + + /// If true, prevent saying sales pitches + var/shut_up = FALSE + var/last_reply = 0 + var/last_slogan = 0 //When did we last pitch? + var/slogan_delay = 6000 //How long until we can pitch again? + var/reply_delay = 200 + + //The type of refill canisters used by this machine. + var/obj/item/vending_refill/canister = null + + // Things that can go wrong + /// Allows people to access a vendor that's normally access restricted. + emagged = 0 + + var/scan_id = TRUE + + /// blocks further flickering while true + var/flickering = FALSE + /// do I look unpowered, even when powered? + var/force_no_power_icon_state = FALSE + + var/light_range_on = 1 + var/light_power_on = 0.5 + + var/list/remembered_costs = list("akula plushie" = 666) // Why not? + var/obj/item/card/id/connected_id = null + var/fast_insert = FALSE + + // To be filled out at compile time + var/list/products = list() + +/obj/machinery/customat/Initialize(mapload) + . = ..() + var/build_inv = FALSE + if(!canister) + build_inv = TRUE + else + component_parts = list() + var/obj/item/circuitboard/vendor/V = new + V.set_type(replacetext(initial(name), "\improper", "")) + component_parts += V + component_parts += new canister + RefreshParts() + + for(var/datum/data/vending_product/R in (product_records)) + var/obj/item/I = R.product_path + var/pp = path2assetID(R.product_path) + imagelist[pp] = "[icon2base64(icon(initial(I.icon), initial(I.icon_state), SOUTH, 1, FALSE))]" + if(LAZYLEN(slogan_list)) + // So not all machines speak at the exact same time. + // The first time this machine says something will be at slogantime + this random value, + // so if slogantime is 10 minutes, it will say it at somewhere between 10 and 20 minutes after the machine is created. + last_slogan = world.time + rand(0, slogan_delay) + + update_icon(UPDATE_OVERLAYS) + + +/obj/machinery/customat/Destroy() + QDEL_NULL(inserted_item) + return ..() + +/obj/machinery/customat/RefreshParts() //Better would be to make constructable child + if(!component_parts) + return + + product_records = list() + if(canister) + build_inventory(products, product_records, start_empty = TRUE) + + +/obj/machinery/customat/update_icon(updates = ALL) + if(skip_non_primary_icon_updates && !(stat & (NOPOWER|BROKEN))) + return ..(NONE) + return ..() + + +/obj/machinery/customat/update_overlays() + . = ..() + + underlays.Cut() + + if((stat & NOPOWER) || force_no_power_icon_state) + if(broken_overlay && (stat & BROKEN)) + . += broken_overlay + + if(panel_overlay && panel_open) + . += panel_overlay + return + + if(stat & BROKEN) + if(broken_overlay) + . += broken_overlay + if(broken_lightmask_overlay) + underlays += emissive_appearance(icon, broken_lightmask_overlay, src) + if(panel_overlay && panel_open) + . += panel_overlay + return + + if(screen_overlay) + . += screen_overlay + + var/lightmask_used = FALSE + if(vend_overlay && (flick_sequence & FLICK_VEND)) + . += vend_overlay + if(vend_lightmask) + lightmask_used = TRUE + . += vend_lightmask + + else if(deny_overlay && (flick_sequence & FLICK_DENY)) + . += deny_overlay + if(deny_lightmask) + lightmask_used = TRUE + . += deny_lightmask + + if(!lightmask_used && lightmask_overlay) + underlays += emissive_appearance(icon, lightmask_overlay, src) + + if(panel_overlay && panel_open) + . += panel_overlay + + +/obj/machinery/customat/power_change(forced = FALSE) + . = ..() + if(stat & NOPOWER) + set_light_on(FALSE) + else + set_light(light_range_on, light_power_on, l_on = TRUE) + if(.) + update_icon(UPDATE_OVERLAYS) + + +/obj/machinery/customat/extinguish_light(force = FALSE) + if(light_on) + set_light_on(FALSE) + underlays.Cut() + + +/obj/machinery/customat/proc/flick_vendor_overlay(flick_flag = FLICK_NONE) + if(flick_sequence & (FLICK_VEND|FLICK_DENY)) + return + if((flick_flag & FLICK_VEND) && !vend_overlay) + return + if((flick_flag & FLICK_DENY) && !deny_overlay) + return + flick_sequence = flick_flag + update_icon(UPDATE_OVERLAYS) + skip_non_primary_icon_updates = TRUE + var/flick_time = (flick_flag & FLICK_VEND) ? vend_overlay_time : (flick_flag & FLICK_DENY) ? deny_overlay_time : 0 + addtimer(CALLBACK(src, PROC_REF(flick_reset)), flick_time) + + +/obj/machinery/customat/proc/flick_reset() + skip_non_primary_icon_updates = FALSE + flick_sequence = FLICK_NONE + update_icon(UPDATE_OVERLAYS) + + +/* + * Reimp, flash the screen on and off repeatedly. + */ +/obj/machinery/customat/flicker() + if(flickering) + return FALSE + + if(stat & (BROKEN|NOPOWER)) + return FALSE + + flickering = TRUE + INVOKE_ASYNC(src, TYPE_PROC_REF(/obj/machinery/customat, flicker_event)) + + return TRUE + +/* + * Proc to be called by invoke_async in the above flicker() proc. + */ +/obj/machinery/customat/proc/flicker_event() + var/amount = rand(5, 15) + + for(var/i in 1 to amount) + force_no_power_icon_state = TRUE + update_icon(UPDATE_OVERLAYS) + sleep(rand(1, 3)) + + force_no_power_icon_state = FALSE + update_icon(UPDATE_OVERLAYS) + sleep(rand(1, 10)) + update_icon(UPDATE_OVERLAYS) + flickering = FALSE + +/** + * Build src.produdct_records from the products lists + * + * src.products, src.contraband, src.premium, and src.prices allow specifying + * products that the vending machine is to carry without manually populating + * src.product_records. + */ +/obj/machinery/customat/proc/build_inventory(list/productlist, list/recordlist, start_empty = FALSE) + for(var/typepath in productlist) + var/amount = productlist[typepath] + if(isnull(amount)) + amount = 0 + + var/atom/temp = typepath + var/datum/data/vending_product/R = new /datum/data/vending_product() + R.name = initial(temp.name) + R.product_path = typepath + if(!start_empty) + R.amount = amount + R.max_amount = amount + R.price = (typepath in prices) ? prices[typepath] : 0 + recordlist += R + +/** + * Set up a refill canister that matches this machines products + * + * This is used when the machine is deconstructed, so the items aren't "lost" + */ +/obj/machinery/customat/proc/update_canister() + if(!component_parts) + return + + var/obj/item/vending_refill/R = locate() in component_parts + if(!R) + CRASH("Constructible vending machine did not have a refill canister") + + R.products = unbuild_inventory(product_records) + +/** + * Given a record list, go through and and return a list of type -> amount + */ +/obj/machinery/customat/proc/unbuild_inventory(list/recordlist) + . = list() + for(var/R in recordlist) + var/datum/data/vending_product/record = R + .[record.product_path] += record.amount + +/obj/machinery/customat/deconstruct(disassembled = TRUE) + eject_item() + if(!canister) //the non constructable vendors drop metal instead of a machine frame. + new /obj/item/stack/sheet/metal(loc, 3) + qdel(src) + else + ..() + +/obj/machinery/customat/proc/idcard_act(mob/user, obj/item/I) + if (!connected_id) + connected_id = I + balloon_alert(user, "Автомат заблокирован.") + else if (connected_id == I) + connected_id = null + balloon_alert(user, "Автомат разблокирован.") + else + balloon_alert(user, "Карта не подходит.") + +/obj/machinery/customat/proc/get_key(obj/item/I, cost) + return I.name + "_" + cost + +/obj/machinery/customat/proc/insert(mob/user, obj/item/I, cost) + remembered_costs[I.name] = cost + var/key = get_key(I, cost) + if (key in products) + var/datum/data/customat_product/product = products[key] + product.containtment += I + product.amount++ + I.forcemove(src) + else + products[key] = new /datum/data/customat_product(I) + products[key].price = cost + +/obj/machinery/customat/proc/try_insert(mob/user, obj/item/I, from_tube = FALSE) + var/cost = 100 + if (fast_insert || from_tube) + if (I.name in remembered_costs) + cost = remembered_costs[I.name] + else + var/new_cost = input("Пожалуйста, выберите цену для этого товара. Цена не может быть ниже 0 и выше 1000000 кредитов.", "Выбор цены", M.s_tone) as null|text + if(new_cost) + cost = clamp(new_cost, 0, 1000000) + insert(user, I, cost) + +/obj/machinery/customat/attackby(obj/item/I, mob/user, params) + if(user.a_intent == INTENT_HARM) + return ..() + + if (panel_open) + if (istype(I, /obj/item/card/id)) + idcard_act(user, I) + return ATTACK_CHAIN_BLOCKED_ALL + else + try_insert(user, I) + return ATTACK_CHAIN_BLOCKED_ALL + + if(item_slot_check(user, I)) + add_fingerprint(user) + insert_item(user, I) + return ATTACK_CHAIN_BLOCKED_ALL + + return ..() + + +/obj/machinery/customat/crowbar_act(mob/user, obj/item/I) + if(!component_parts) + return + . = TRUE + default_deconstruction_crowbar(user, I) + +/obj/machinery/customat/screwdriver_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = I.tool_volume)) + return + if(anchored) + panel_open = !panel_open + panel_open ? SCREWDRIVER_OPEN_PANEL_MESSAGE : SCREWDRIVER_CLOSE_PANEL_MESSAGE + update_icon() + SStgui.update_uis(src) + +/obj/machinery/customat/wrench_act(mob/user, obj/item/I) + . = TRUE + if(!I.use_tool(src, user, 0, volume = 0)) + return + default_unfasten_wrench(user, I, time = 60) + +//Override this proc to do per-machine checks on the inserted item, but remember to call the parent to handle these generic checks before your logic! +/obj/machinery/customat/proc/item_slot_check(mob/user, obj/item/I) + if(!item_slot) + return FALSE + if(inserted_item) + to_chat(user, span_warning("There is something already inserted!")) + return FALSE + return TRUE + +/obj/machinery/customat/exchange_parts(mob/user, obj/item/storage/part_replacer/W) + if(!istype(W)) + return FALSE + if(!W.works_from_distance) + return FALSE + if(!component_parts || !canister) + return FALSE + + var/moved = 0 + if(panel_open || W.works_from_distance) + if(W.works_from_distance) + to_chat(user, display_parts(user)) + else + to_chat(user, display_parts(user)) + if(moved) + to_chat(user, "[moved] items restocked.") + W.play_rped_sound() + return TRUE + +/obj/machinery/customat/on_deconstruction() + update_canister() + . = ..() + +/obj/machinery/customat/proc/insert_item(mob/user, obj/item/I) + if(!item_slot || inserted_item) + return + if(!user.drop_transfer_item_to_loc(I, src)) + to_chat(user, span_warning("[I] is stuck to your hand, you can't seem to put it down!")) + return + inserted_item = I + to_chat(user, span_notice("You insert [I] into [src].")) + SStgui.update_uis(src) + +/obj/machinery/customat/proc/eject_item(mob/user) + if(!item_slot || !inserted_item) + return + var/put_on_turf = TRUE + if(user && iscarbon(user) && user.Adjacent(src)) + inserted_item.forceMove_turf() + if(user.put_in_hands(inserted_item, ignore_anim = FALSE)) + put_on_turf = FALSE + if(put_on_turf) + var/turf/T = get_turf(src) + inserted_item.forceMove(T) + inserted_item = null + SStgui.update_uis(src) + +/obj/machinery/customat/emag_act(mob/user) + emagged = TRUE + if(user) + to_chat(user, "You short out the product lock on [src]") + +/obj/machinery/customat/attack_ai(mob/user) + return attack_hand(user) + +/obj/machinery/customat/attack_ghost(mob/user) + return attack_hand(user) + +/obj/machinery/customat/attack_hand(mob/user) + if(stat & (BROKEN|NOPOWER)) + return + + if(..()) + return TRUE + + add_fingerprint(user) + ui_interact(user) + +/obj/machinery/customat/ui_interact(mob/user, datum/tgui/ui = null) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + var/estimated_height = 100 + min(length(product_records) * 34, 500) + if(length(prices) > 0) + estimated_height += 100 // to account for the "current user" interface + ui = new(user, src, "Vending", name) + ui.open() + +/obj/machinery/customat/ui_data(mob/user) + var/list/data = list() + var/datum/money_account/A = null + data["guestNotice"] = "No valid ID card detected. Wear your ID, or present cash."; + data["userMoney"] = 0 + data["user"] = null + if(issilicon(user) && !istype(user, /mob/living/silicon/robot/drone) && !istype(user, /mob/living/silicon/pai)) + A = get_card_account(user) + data["user"] = list() + data["user"]["name"] = A.owner_name + data["userMoney"] = A.money + data["user"]["job"] = "Silicon" + if(ishuman(user)) + A = get_card_account(user) + var/mob/living/carbon/human/H = user + var/obj/item/stack/spacecash/S = H.get_active_hand() + if(istype(S)) + data["userMoney"] = S.amount + data["guestNotice"] = "Accepting Cash. You have: [S.amount] credits." + else if(istype(H)) + var/obj/item/card/id/C = H.get_id_card() + if(istype(A)) + data["user"] = list() + data["user"]["name"] = A.owner_name + data["userMoney"] = A.money + data["user"]["job"] = (istype(C) && C.rank) ? C.rank : "No Job" + else + data["guestNotice"] = "Unlinked ID detected. Present cash to pay."; + data["stock"] = list() + for (var/datum/data/vending_product/R in product_records) + data["stock"][R.name] = R.amount + data["vend_ready"] = vend_ready + data["panel_open"] = panel_open ? TRUE : FALSE + data["speaker"] = shut_up ? FALSE : TRUE + data["item_slot"] = item_slot // boolean + data["inserted_item_name"] = inserted_item ? inserted_item.name : FALSE + return data + + +/obj/machinery/customat/ui_static_data(mob/user) + var/list/data = list() + data["chargesMoney"] = length(prices) > 0 ? TRUE : FALSE + data["product_records"] = list() + var/i = 1 + for (var/datum/data/vending_product/R in product_records) + var/list/data_pr = list( + path = replacetext(replacetext("[R.product_path]", "/obj/item/", ""), "/", "-"), + name = R.name, + price = (R.product_path in prices) ? prices[R.product_path] : 0, + max_amount = R.max_amount, + is_hidden = FALSE, + inum = i + ) + data["product_records"] += list(data_pr) + i++ + data["imagelist"] = imagelist + return data + +/obj/machinery/customat/ui_act(action, params) + . = ..() + if(.) + return + if(issilicon(usr) && !isrobot(usr)) + to_chat(usr, span_warning("The vending machine refuses to interface with you, as you are not in its target demographic!")) + return + switch(action) + if("toggle_voice") + if(panel_open) + shut_up = !shut_up + . = TRUE + if("eject_item") + eject_item(usr) + . = TRUE + if("vend") + if(!vend_ready) + to_chat(usr, span_warning("The vending machine is busy!")) + return + if(panel_open) + to_chat(usr, span_warning("The vending machine cannot dispense products while its service panel is open!")) + return + var/key = text2num(params["inum"]) + var/list/display_records = product_records + if(key < 1 || key > length(display_records)) + to_chat(usr, span_warning("ERROR: invalid inum passed to vendor. Report this bug.")) + return + var/datum/data/vending_product/R = display_records[key] + if(!istype(R)) + to_chat(usr, span_warning("ERROR: unknown vending_product record. Report this bug.")) + return + var/list/record_to_check = product_records + if(!R || !istype(R) || !R.product_path) + to_chat(usr, span_warning("ERROR: unknown product record. Report this bug.")) + return + else if (!(R in record_to_check)) + // Exploit prevention, stop the user + message_admins("Vending machine exploit attempted by [ADMIN_LOOKUPFLW(usr)]!") + return + if (R.amount <= 0) + to_chat(usr, "Sold out of [R.name].") + flick_vendor_overlay(FLICK_VEND) + return + + vend_ready = FALSE // From this point onwards, vendor is locked to performing this transaction only, until it is resolved. + + if(!(ishuman(usr) || issilicon(usr)) || R.price <= 0) + // Either the purchaser is not human nor silicon, or the item is free. + // Skip all payment logic. + vend(R, usr) + add_fingerprint(usr) + vend_ready = TRUE + . = TRUE + return + + // --- THE REST OF THIS PROC IS JUST PAYMENT LOGIC --- + if(!GLOB.vendor_account || GLOB.vendor_account.suspended) + to_chat(usr, "Vendor account offline. Unable to process transaction.") + flick_vendor_overlay(FLICK_DENY) + vend_ready = TRUE + return + + currently_vending = R + var/paid = FALSE + + if(istype(usr.get_active_hand(), /obj/item/stack/spacecash)) + var/obj/item/stack/spacecash/S = usr.get_active_hand() + paid = pay_with_cash(S, usr, currently_vending.price, currently_vending.name) + else if(get_card_account(usr)) + // Because this uses H.get_id_card(), it will attempt to use: + // active hand, inactive hand, wear_id, pda, and then w_uniform ID in that order + // this is important because it lets people buy stuff with someone else's ID by holding it while using the vendor + paid = pay_with_card(usr, currently_vending.price, currently_vending.name) + else if(usr.can_advanced_admin_interact()) + to_chat(usr, span_notice("Vending object due to admin interaction.")) + paid = TRUE + else + to_chat(usr, span_warning("Payment failure: you have no ID or other method of payment.")) + vend_ready = TRUE + flick_vendor_overlay(FLICK_DENY) + . = TRUE // we set this because they shouldn't even be able to get this far, and we want the UI to update. + return + if(paid) + vend(currently_vending, usr) + . = TRUE + else + to_chat(usr, span_warning("Payment failure: unable to process payment.")) + vend_ready = TRUE + if(.) + add_fingerprint(usr) + + + + +/obj/machinery/customat/proc/vend(datum/data/vending_product/R, mob/user) + if(!allowed(user) && !user.can_admin_interact() && !emagged && scan_id) + balloon_alert(user, "Access denied.") + flick_vendor_overlay(FLICK_DENY) + vend_ready = TRUE + return + + if(!R.amount) + to_chat(user, span_warning("В автомате не осталось содержимого.")) + vend_ready = TRUE + return + + vend_ready = FALSE //One thing at a time!! + + R.amount-- + + if(((last_reply + (vend_delay + reply_delay)) <= world.time) && vend_reply) + speak(pick(src.vend_reply)) + last_reply = world.time + + use_power(vend_power_usage) //actuators and stuff + flick_vendor_overlay(FLICK_VEND) //Show the vending animation if needed + playsound(get_turf(src), 'sound/machines/machine_vend.ogg', 50, TRUE) + addtimer(CALLBACK(src, PROC_REF(delayed_vend), R, user), vend_delay) + + +/obj/machinery/customat/proc/delayed_vend(datum/data/vending_product/R, mob/user) + do_vend(R, user) + vend_ready = TRUE + currently_vending = null + + +/** + * Override this proc to add handling for what to do with the vended product + * when you have a inserted item and remember to include a parent call for this generic handling + */ +/obj/machinery/customat/proc/do_vend(datum/data/vending_product/R, mob/user) + if(!item_slot || !inserted_item) + var/put_on_turf = TRUE + var/obj/item/vended = new R.product_path(drop_location()) + if(istype(vended) && user && iscarbon(user) && user.Adjacent(src)) + if(user.put_in_hands(vended, ignore_anim = FALSE)) + put_on_turf = FALSE + if(put_on_turf) + var/turf/T = get_turf(src) + vended.forceMove(T) + return TRUE + return FALSE + +/obj/machinery/customat/process() + if(stat & (BROKEN|NOPOWER)) + return + + if(!active) + return + + //Pitch to the people! Really sell it! + if(((last_slogan + src.slogan_delay) <= world.time) && (LAZYLEN(slogan_list)) && (!shut_up) && prob(5)) + var/slogan = pick(src.slogan_list) + speak(slogan) + last_slogan = world.time + + +/obj/machinery/customat/proc/speak(message) + if(stat & NOPOWER) + return + if(!message) + return + + atom_say(message) + + +/obj/machinery/customat/obj_break(damage_flag) + if(stat & BROKEN) + return + + stat |= BROKEN + update_icon(UPDATE_OVERLAYS) + + var/dump_amount = 0 + var/found_anything = TRUE + while (found_anything) + found_anything = FALSE + for(var/record in shuffle(product_records)) + var/datum/data/vending_product/R = record + if(R.amount <= 0) //Try to use a record that actually has something to dump. + continue + var/dump_path = R.product_path + if(!dump_path) + continue + R.amount-- + // busting open a vendor will destroy some of the contents + if(found_anything && prob(80)) + continue + + var/obj/O = new dump_path(loc) + step(O, pick(GLOB.alldirs)) + found_anything = TRUE + dump_amount++ + if(dump_amount >= 16) + return diff --git a/code/game/machinery/vending.dm b/code/game/machinery/vending.dm index 043c44b2039..8dc60de4aa1 100644 --- a/code/game/machinery/vending.dm +++ b/code/game/machinery/vending.dm @@ -3617,14 +3617,6 @@ name = "Торговый автомат с уникальным содержимым" desc = "Fabricator with advanced technology of bluespace transporting of resources." - icon_state = "paivend_off" - panel_overlay = "paivend_panel" - screen_overlay = "paivend" - lightmask_overlay = "paivend_lightmask" - broken_overlay = "paivend_broken" - broken_lightmask_overlay = "paivend_broken_lightmask" - - ads_list = list("Купи самый дорогой предмет из моего содержимого! Не пожалеешь!","Мое содержимое разнообразней чем вся твоя жизнь!","У меня богатый внутренний мир.","Во мне может быть что угодно.","Не ядерный ли это диск во мне продается, всего за 1984 кредита?", "Не хочешь платить за содержимое? Сломай меня и получи все бесплатно!", "Товары на любой вкус и цвет!", "Может во мне продается контробанда?", "Не нравится мое содержимое? Создай свой кастомат, со своим уникальным содержимым!") resistance_flags = FIRE_PROOF | LAVA_PROOF | FIRE_PROOF | ACID_PROOF | FREEZE_PROOF products = null prices = null diff --git a/paradise.dme b/paradise.dme index a0baf16e6aa..8c7c6617ac8 100644 --- a/paradise.dme +++ b/paradise.dme @@ -891,6 +891,7 @@ #include "code\game\machinery\constructable_frame.dm" #include "code\game\machinery\cryo.dm" #include "code\game\machinery\cryopod.dm" +#include "code\game\machinery\customat.dm" #include "code\game\machinery\dance_machine.dm" #include "code\game\machinery\defib_mount.dm" #include "code\game\machinery\deployable.dm" From b2f0c369f06bf599be4c3312e403d39dad59242a Mon Sep 17 00:00:00 2001 From: Anorak2024 Date: Mon, 26 Aug 2024 17:05:18 +0300 Subject: [PATCH 07/40] 213 --- code/game/machinery/customat.dm | 197 +++++++----------- code/game/machinery/vending.dm | 12 -- .../objects/items/weapons/vending_items.dm | 5 + tgui/packages/tgui/interfaces/Customat.js | 186 +++++++++++++++++ 4 files changed, 264 insertions(+), 136 deletions(-) create mode 100644 tgui/packages/tgui/interfaces/Customat.js diff --git a/code/game/machinery/customat.dm b/code/game/machinery/customat.dm index e9eaa998c43..73ecac1da30 100644 --- a/code/game/machinery/customat.dm +++ b/code/game/machinery/customat.dm @@ -20,13 +20,13 @@ var/amount = 0 var/list/obj/item/containtment = list() var/price = 0 // Price to buy one - var/product_icon = '' + var/product_icon = "123.dmi" var/product_icon_state = "" /datum/data/customat_product/New(obj/item/I) name = I.name - amound = 1 - containtment = lisT() + amount = 0 + containtment = list() price = 0 product_icon = I.icon product_icon_state = I.icon_state @@ -114,8 +114,8 @@ var/shut_up = FALSE var/last_reply = 0 var/last_slogan = 0 //When did we last pitch? - var/slogan_delay = 6000 //How long until we can pitch again? - var/reply_delay = 200 + var/slogan_delay = 600 SECONDS //How long until we can pitch again? + var/reply_delay = 20 SECONDS //The type of refill canisters used by this machine. var/obj/item/vending_refill/canister = null @@ -141,24 +141,23 @@ // To be filled out at compile time var/list/products = list() + var/inserted_items_count = 0 + var/max_items_inside = 50 + /obj/machinery/customat/Initialize(mapload) . = ..() - var/build_inv = FALSE - if(!canister) - build_inv = TRUE - else - component_parts = list() - var/obj/item/circuitboard/vendor/V = new - V.set_type(replacetext(initial(name), "\improper", "")) - component_parts += V - component_parts += new canister - RefreshParts() + component_parts = list() + var/obj/item/circuitboard/vendor/V = new + V.set_type(replacetext(initial(name), "\improper", "")) + component_parts += V + component_parts += new canister + RefreshParts() for(var/datum/data/vending_product/R in (product_records)) var/obj/item/I = R.product_path var/pp = path2assetID(R.product_path) imagelist[pp] = "[icon2base64(icon(initial(I.icon), initial(I.icon_state), SOUTH, 1, FALSE))]" - if(LAZYLEN(slogan_list)) + if(LAZYLEN(ads_list)) // So not all machines speak at the exact same time. // The first time this machine says something will be at slogantime + this random value, // so if slogantime is 10 minutes, it will say it at somewhere between 10 and 20 minutes after the machine is created. @@ -166,9 +165,16 @@ update_icon(UPDATE_OVERLAYS) +/obj/machinery/customat/proc/eject_all() + for (var/datum/data/customat_product/product in products) + for (var/obj/item/I in product.containtment) + I.forceMove(get_turf(src)) + product.amount = 0 + inserted_items_count -= product.containtment.len + product.containtment = list() /obj/machinery/customat/Destroy() - QDEL_NULL(inserted_item) + eject_all() return ..() /obj/machinery/customat/RefreshParts() //Better would be to make constructable child @@ -176,8 +182,6 @@ return product_records = list() - if(canister) - build_inventory(products, product_records, start_empty = TRUE) /obj/machinery/customat/update_icon(updates = ALL) @@ -299,29 +303,6 @@ update_icon(UPDATE_OVERLAYS) flickering = FALSE -/** - * Build src.produdct_records from the products lists - * - * src.products, src.contraband, src.premium, and src.prices allow specifying - * products that the vending machine is to carry without manually populating - * src.product_records. - */ -/obj/machinery/customat/proc/build_inventory(list/productlist, list/recordlist, start_empty = FALSE) - for(var/typepath in productlist) - var/amount = productlist[typepath] - if(isnull(amount)) - amount = 0 - - var/atom/temp = typepath - var/datum/data/vending_product/R = new /datum/data/vending_product() - R.name = initial(temp.name) - R.product_path = typepath - if(!start_empty) - R.amount = amount - R.max_amount = amount - R.price = (typepath in prices) ? prices[typepath] : 0 - recordlist += R - /** * Set up a refill canister that matches this machines products * @@ -347,7 +328,6 @@ .[record.product_path] += record.amount /obj/machinery/customat/deconstruct(disassembled = TRUE) - eject_item() if(!canister) //the non constructable vendors drop metal instead of a machine frame. new /obj/item/stack/sheet/metal(loc, 3) qdel(src) @@ -368,16 +348,24 @@ return I.name + "_" + cost /obj/machinery/customat/proc/insert(mob/user, obj/item/I, cost) + if (inserted_items_count == max_items_inside) + return remembered_costs[I.name] = cost var/key = get_key(I, cost) - if (key in products) - var/datum/data/customat_product/product = products[key] - product.containtment += I - product.amount++ - I.forcemove(src) - else - products[key] = new /datum/data/customat_product(I) - products[key].price = cost + if(!user.drop_transfer_item_to_loc(I, src)) + to_chat(usr, span_warning("Вы не можете положить это внутрь.")) + return + if (!(key in products)) + var/datum/data/customat_product/product = new /datum/data/customat_product(I) + product.price = cost + product.product_icon = I.icon + product.product_icon_state = I.icon_state + products[key] = product + + var/datum/data/customat_product/product = products[key] + product.containtment += I + product.amount++ + inserted_items_count++ /obj/machinery/customat/proc/try_insert(mob/user, obj/item/I, from_tube = FALSE) var/cost = 100 @@ -385,9 +373,12 @@ if (I.name in remembered_costs) cost = remembered_costs[I.name] else - var/new_cost = input("Пожалуйста, выберите цену для этого товара. Цена не может быть ниже 0 и выше 1000000 кредитов.", "Выбор цены", M.s_tone) as null|text + var/new_cost = input("Пожалуйста, выберите цену для этого товара. Цена не может быть ниже 0 и выше 1000000 кредитов.", "Выбор цены", 0) as null|text if(new_cost) cost = clamp(new_cost, 0, 1000000) + if (get_dist(get_turf(user), get_turf(src)) > 1) + to_chat(usr, span_warning("Вы слишком далеко!")) + return insert(user, I, cost) /obj/machinery/customat/attackby(obj/item/I, mob/user, params) @@ -402,11 +393,8 @@ try_insert(user, I) return ATTACK_CHAIN_BLOCKED_ALL - if(item_slot_check(user, I)) - add_fingerprint(user) - insert_item(user, I) - return ATTACK_CHAIN_BLOCKED_ALL - + if(user.a_intent == INTENT_HARM) + playsound(src, 'sound/machines/burglar_alarm.ogg', I.force * 5, 0) return ..() @@ -414,6 +402,7 @@ if(!component_parts) return . = TRUE + eject_all() default_deconstruction_crowbar(user, I) /obj/machinery/customat/screwdriver_act(mob/user, obj/item/I) @@ -432,15 +421,6 @@ return default_unfasten_wrench(user, I, time = 60) -//Override this proc to do per-machine checks on the inserted item, but remember to call the parent to handle these generic checks before your logic! -/obj/machinery/customat/proc/item_slot_check(mob/user, obj/item/I) - if(!item_slot) - return FALSE - if(inserted_item) - to_chat(user, span_warning("There is something already inserted!")) - return FALSE - return TRUE - /obj/machinery/customat/exchange_parts(mob/user, obj/item/storage/part_replacer/W) if(!istype(W)) return FALSE @@ -464,29 +444,6 @@ update_canister() . = ..() -/obj/machinery/customat/proc/insert_item(mob/user, obj/item/I) - if(!item_slot || inserted_item) - return - if(!user.drop_transfer_item_to_loc(I, src)) - to_chat(user, span_warning("[I] is stuck to your hand, you can't seem to put it down!")) - return - inserted_item = I - to_chat(user, span_notice("You insert [I] into [src].")) - SStgui.update_uis(src) - -/obj/machinery/customat/proc/eject_item(mob/user) - if(!item_slot || !inserted_item) - return - var/put_on_turf = TRUE - if(user && iscarbon(user) && user.Adjacent(src)) - inserted_item.forceMove_turf() - if(user.put_in_hands(inserted_item, ignore_anim = FALSE)) - put_on_turf = FALSE - if(put_on_turf) - var/turf/T = get_turf(src) - inserted_item.forceMove(T) - inserted_item = null - SStgui.update_uis(src) /obj/machinery/customat/emag_act(mob/user) emagged = TRUE @@ -512,38 +469,35 @@ /obj/machinery/customat/ui_interact(mob/user, datum/tgui/ui = null) ui = SStgui.try_update_ui(user, src, ui) if(!ui) - var/estimated_height = 100 + min(length(product_records) * 34, 500) - if(length(prices) > 0) - estimated_height += 100 // to account for the "current user" interface - ui = new(user, src, "Vending", name) + ui = new(user, src, "Customat", name) ui.open() /obj/machinery/customat/ui_data(mob/user) var/list/data = list() - var/datum/money_account/A = null - data["guestNotice"] = "No valid ID card detected. Wear your ID, or present cash."; + var/datum/money_account/account = null + data["guestNotice"] = "Идентификационной карты не обнаружено."; data["userMoney"] = 0 data["user"] = null if(issilicon(user) && !istype(user, /mob/living/silicon/robot/drone) && !istype(user, /mob/living/silicon/pai)) - A = get_card_account(user) + account = get_card_account(user) data["user"] = list() - data["user"]["name"] = A.owner_name - data["userMoney"] = A.money + data["user"]["name"] = account.owner_name + data["userMoney"] = account.money data["user"]["job"] = "Silicon" if(ishuman(user)) - A = get_card_account(user) + account = get_card_account(user) var/mob/living/carbon/human/H = user var/obj/item/stack/spacecash/S = H.get_active_hand() if(istype(S)) data["userMoney"] = S.amount data["guestNotice"] = "Accepting Cash. You have: [S.amount] credits." else if(istype(H)) - var/obj/item/card/id/C = H.get_id_card() - if(istype(A)) + var/obj/item/card/id/idcard = H.get_id_card() + if(istype(account)) data["user"] = list() - data["user"]["name"] = A.owner_name - data["userMoney"] = A.money - data["user"]["job"] = (istype(C) && C.rank) ? C.rank : "No Job" + data["user"]["name"] = account.owner_name + data["userMoney"] = account.money + data["user"]["job"] = (istype(idcard) && idcard.rank) ? idcard.rank : "No Job" else data["guestNotice"] = "Unlinked ID detected. Present cash to pay."; data["stock"] = list() @@ -552,21 +506,17 @@ data["vend_ready"] = vend_ready data["panel_open"] = panel_open ? TRUE : FALSE data["speaker"] = shut_up ? FALSE : TRUE - data["item_slot"] = item_slot // boolean - data["inserted_item_name"] = inserted_item ? inserted_item.name : FALSE return data /obj/machinery/customat/ui_static_data(mob/user) var/list/data = list() - data["chargesMoney"] = length(prices) > 0 ? TRUE : FALSE data["product_records"] = list() var/i = 1 for (var/datum/data/vending_product/R in product_records) var/list/data_pr = list( path = replacetext(replacetext("[R.product_path]", "/obj/item/", ""), "/", "-"), name = R.name, - price = (R.product_path in prices) ? prices[R.product_path] : 0, max_amount = R.max_amount, is_hidden = FALSE, inum = i @@ -588,9 +538,6 @@ if(panel_open) shut_up = !shut_up . = TRUE - if("eject_item") - eject_item(usr) - . = TRUE if("vend") if(!vend_ready) to_chat(usr, span_warning("The vending machine is busy!")) @@ -707,17 +654,15 @@ * when you have a inserted item and remember to include a parent call for this generic handling */ /obj/machinery/customat/proc/do_vend(datum/data/vending_product/R, mob/user) - if(!item_slot || !inserted_item) - var/put_on_turf = TRUE - var/obj/item/vended = new R.product_path(drop_location()) - if(istype(vended) && user && iscarbon(user) && user.Adjacent(src)) - if(user.put_in_hands(vended, ignore_anim = FALSE)) - put_on_turf = FALSE - if(put_on_turf) - var/turf/T = get_turf(src) - vended.forceMove(T) - return TRUE - return FALSE + var/put_on_turf = TRUE + var/obj/item/vended = new R.product_path(drop_location()) + if(istype(vended) && user && iscarbon(user) && user.Adjacent(src)) + if(user.put_in_hands(vended, ignore_anim = FALSE)) + put_on_turf = FALSE + if(put_on_turf) + var/turf/T = get_turf(src) + vended.forceMove(T) + return TRUE /obj/machinery/customat/process() if(stat & (BROKEN|NOPOWER)) @@ -727,8 +672,8 @@ return //Pitch to the people! Really sell it! - if(((last_slogan + src.slogan_delay) <= world.time) && (LAZYLEN(slogan_list)) && (!shut_up) && prob(5)) - var/slogan = pick(src.slogan_list) + if(((last_slogan + src.slogan_delay) <= world.time) && (LAZYLEN(ads_list)) && (!shut_up) && prob(5)) + var/slogan = pick(src.ads_list) speak(slogan) last_slogan = world.time @@ -771,3 +716,7 @@ dump_amount++ if(dump_amount >= 16) return + +#undef FLICK_NONE +#undef FLICK_VEND +#undef FLICK_DENY diff --git a/code/game/machinery/vending.dm b/code/game/machinery/vending.dm index 8dc60de4aa1..c1aaf852953 100644 --- a/code/game/machinery/vending.dm +++ b/code/game/machinery/vending.dm @@ -3613,18 +3613,6 @@ /obj/item/mecha_parts/mecha_equipment/wormhole_generator = 10, ) -/obj/machinery/vending/custom - name = "Торговый автомат с уникальным содержимым" - desc = "Fabricator with advanced technology of bluespace transporting of resources." - - resistance_flags = FIRE_PROOF | LAVA_PROOF | FIRE_PROOF | ACID_PROOF | FREEZE_PROOF - products = null - prices = null - var/containtment = list() - - refill_canister = /obj/item/vending_refill/custom - - #undef FLICK_NONE #undef FLICK_VEND #undef FLICK_DENY diff --git a/code/game/objects/items/weapons/vending_items.dm b/code/game/objects/items/weapons/vending_items.dm index 15ba4636006..aacea470725 100644 --- a/code/game/objects/items/weapons/vending_items.dm +++ b/code/game/objects/items/weapons/vending_items.dm @@ -191,3 +191,8 @@ /obj/item/vending_refill/custom machine_name = "Торговый автомат с уникальным содержимым" icon_state = "restock_pai" + var/id_number + +/obj/item/vending_refill/custom/attack_self(mob/user) + var/new_id_number = input("Пожалуйста, введите номер счета, на который будут начисляться кредиты при покупке.", "Выбор счета", id_number) as null|text + id_number = new_id_number diff --git a/tgui/packages/tgui/interfaces/Customat.js b/tgui/packages/tgui/interfaces/Customat.js new file mode 100644 index 00000000000..5ef86c5ee11 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Customat.js @@ -0,0 +1,186 @@ +import { classes } from 'common/react'; +import { useBackend } from '../backend'; +import { Box, Button, Section, Stack, Table } from '../components'; +import { Window } from '../layouts'; + +const VendingRow = (props, context) => { + const { act, data } = useBackend(context); + const { product, productStock, productImage } = props; + const { + chargesMoney, + user, + userMoney, + vend_ready, + coin_name, + inserted_item_name, + } = data; + const free = !chargesMoney || product.price === 0; + let buttonText = 'ERROR!'; + let rowIcon = ''; + if (product.req_coin) { + buttonText = 'COIN'; + rowIcon = 'circle'; + } else if (free) { + buttonText = 'FREE'; + rowIcon = 'arrow-circle-down'; + } else { + buttonText = product.price; + rowIcon = 'shopping-cart'; + } + let buttonDisabled = + !vend_ready || + (!coin_name && product.req_coin) || + productStock === 0 || + (!free && product.price > userMoney); + return ( + + + + + {product.name} + + + {productStock} in stock + + + +