From 71cc3c5c2275b915de21b421873e26d3b1668d4b Mon Sep 17 00:00:00 2001 From: Contrabang <91113370+Contrabang@users.noreply.github.com> Date: Sat, 7 Oct 2023 18:46:33 -0400 Subject: [PATCH] [TGUI HELL] Adds a Shopping Cart System to the syndicate uplink (#22039) * hoooooly shit * slight suggestion rework * lewc review * sanity + locking uplink removes 0 amt cart items * review * shit forgot to apply this * update bundle * m * bundle * j * l * fix this shit * pretty * yea * they lied * teegooeye * tgui * farie review * farie review --- code/datums/uplink_items/uplink_general.dm | 60 ++--- code/game/objects/items/devices/uplinks.dm | 150 ++++++++++- tgui/packages/tgui/components/Box.js | 1 + tgui/packages/tgui/components/Button.js | 6 + tgui/packages/tgui/components/Section.js | 8 +- tgui/packages/tgui/interfaces/Uplink.js | 253 ++++++++++++++++-- tgui/packages/tgui/public/tgui.bundle.css | 2 +- tgui/packages/tgui/public/tgui.bundle.js | 6 +- .../tgui/styles/components/Section.scss | 3 + .../tgui/styles/themes/syndicate.scss | 7 + 10 files changed, 427 insertions(+), 69 deletions(-) diff --git a/code/datums/uplink_items/uplink_general.dm b/code/datums/uplink_items/uplink_general.dm index 16f098df0a57..8a4389154526 100644 --- a/code/datums/uplink_items/uplink_general.dm +++ b/code/datums/uplink_items/uplink_general.dm @@ -111,47 +111,47 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item)) desc = replacetext(initial(temp.desc), "\n", "
") return desc -/datum/uplink_item/proc/buy(obj/item/uplink/hidden/U, mob/user) +/datum/uplink_item/proc/buy_uplink_item(obj/item/uplink/hidden/U, mob/user, put_in_hands = TRUE) if(!istype(U)) - return FALSE + return if(user.stat || user.restrained()) - return FALSE + return - if(!(ishuman(user))) - return FALSE + if(!ishuman(user)) + return // If the uplink's holder is in the user's contents if((U.loc in user.contents || (in_range(U.loc, user) && isturf(U.loc.loc)))) if(cost > U.uses) - return FALSE - - var/obj/I = spawn_item(get_turf(user), U) - - if(I) - if(ishuman(user)) - var/mob/living/carbon/human/A = user - if(limited_stock > 0) - log_game("[key_name(user)] purchased [name]. [name] was discounted to [cost].") - if(!user.mind.special_role) - message_admins("[key_name_admin(user)] purchased [name] (discounted to [cost]), as a non antagonist.") - - else - log_game("[key_name(user)] purchased [name].") - if(!user.mind.special_role) - message_admins("[key_name_admin(user)] purchased [name], as a non antagonist.") - - A.put_in_any_hand_if_possible(I) + return - if(istype(I,/obj/item/storage/box/) && I.contents.len>0) - for(var/atom/o in I) - U.purchase_log += "[bicon(o)]" - else - U.purchase_log += "[bicon(I)]" + var/obj/I = spawn_item(get_turf(user), U) - return TRUE - return FALSE + if(!I || I == UPLINK_SPECIAL_SPAWNING) + return // Failed to spawn, or we handled it with special spawning + if(limited_stock > 0) + limited_stock-- + log_game("[key_name(user)] purchased [name]. [name] was discounted to [cost].") + if(!user.mind.special_role) + message_admins("[key_name_admin(user)] purchased [name] (discounted to [cost]), as a non antagonist.") + + else + log_game("[key_name(user)] purchased [name].") + if(!user.mind.special_role) + message_admins("[key_name_admin(user)] purchased [name], as a non antagonist.") + + if(istype(I, /obj/item/storage/box) && length(I.contents)) + for(var/atom/o in I) + U.purchase_log += "[bicon(o)]" + + else + U.purchase_log += "[bicon(I)]" + + if(put_in_hands) + user.put_in_any_hand_if_possible(I) + return I /* // diff --git a/code/game/objects/items/devices/uplinks.dm b/code/game/objects/items/devices/uplinks.dm index c1366ef36088..4b8adfc8e58d 100644 --- a/code/game/objects/items/devices/uplinks.dm +++ b/code/game/objects/items/devices/uplinks.dm @@ -104,13 +104,29 @@ GLOBAL_LIST_EMPTY(world_uplinks) if(UI.limited_stock == 0) to_chat(usr, "You have redeemed this discount already.") return - UI.buy(src,usr) - if(UI.limited_stock > 0) // only decrement it if it's actually limited - UI.limited_stock-- + UI.buy_uplink_item(src,usr) SStgui.update_uis(src) return TRUE +/obj/item/uplink/proc/mass_purchase(datum/uplink_item/UI, reference, quantity = 1) + // jamming check happens in ui_act + if(!UI) + return + if(quantity <= 0) + return + if(UI.limited_stock == 0) + return + if(UI.limited_stock > 0 && UI.limited_stock < quantity) + quantity = UI.limited_stock + var/list/bought_things = list() + for(var/i in 1 to quantity) + var/item = UI.buy_uplink_item(src, usr, put_in_hands = FALSE) + if(isnull(item)) + break + bought_things += item + return bought_things + /obj/item/uplink/proc/refund(mob/user as mob) var/obj/item/I = user.get_active_hand() if(I) // Make sure there's actually something in the hand before even bothering to check @@ -144,6 +160,12 @@ GLOBAL_LIST_EMPTY(world_uplinks) name = "hidden uplink" desc = "There is something wrong if you're examining this." var/active = FALSE + /// An assoc list of references (the variable called reference on an uplink item) and its value being how many of the item + var/list/shopping_cart + /// A cached version of shopping_cart containing all the data for the tgui side + var/list/cached_cart + /// A list of 3 categories and item indexes in uplink_cats, to show as recommendedations + var/list/lucky_numbers // The hidden uplink MUST be inside an obj/item's contents. /obj/item/uplink/hidden/New(loc) @@ -184,6 +206,10 @@ GLOBAL_LIST_EMPTY(world_uplinks) data["crystals"] = uses + data["cart"] = generate_tgui_cart() + data["cart_price"] = calculate_cart_tc() + data["lucky_numbers"] = lucky_numbers + return data /obj/item/uplink/hidden/ui_static_data(mob/user) @@ -192,6 +218,8 @@ GLOBAL_LIST_EMPTY(world_uplinks) // Actual items if(!uplink_cats || !uplink_items) generate_item_lists(user) + if(!lucky_numbers) // Make sure these are generated AFTER the categories, otherwise shit will get messed up + shuffle_lucky_numbers() data["cats"] = uplink_cats // Exploitable info @@ -210,26 +238,57 @@ GLOBAL_LIST_EMPTY(world_uplinks) return data +/obj/item/uplink/hidden/proc/calculate_cart_tc() + . = 0 + for(var/reference in shopping_cart) + var/datum/uplink_item/item = uplink_items[reference] + var/purchase_amt = shopping_cart[reference] + . += item.cost * purchase_amt + +/obj/item/uplink/hidden/proc/generate_tgui_cart(update = FALSE) + if(!update) + return cached_cart + + if(!length(shopping_cart)) + shopping_cart = null + cached_cart = null + return cached_cart + + cached_cart = list() + for(var/reference in shopping_cart) + var/datum/uplink_item/I = uplink_items[reference] + cached_cart += list(list( + "name" = sanitize(I.name), + "desc" = sanitize(I.description()), + "cost" = I.cost, + "hijack_only" = I.hijack_only, + "obj_path" = I.reference, + "amount" = shopping_cart[reference], + "limit" = I.limited_stock)) // Interaction code. Gathers a list of items purchasable from the paren't uplink and displays it. It also adds a lock button. /obj/item/uplink/hidden/interact(mob/user) ui_interact(user) // The purchasing code. -/obj/item/uplink/hidden/ui_act(action, list/params) +/obj/item/uplink/hidden/ui_act(action, list/params, datum/tgui/ui) if(..()) return . = TRUE + switch(action) if("lock") toggle() uses += hidden_crystals hidden_crystals = 0 SStgui.close_uis(src) + for(var/reference in shopping_cart) + if(shopping_cart[reference] == 0) // I know this isn't lazy, but this should runtime on purpose if we can't access this for some reason + remove_from_cart(reference) if("refund") - refund(usr) + refund(ui.user) if("buyRandom") var/datum/uplink_item/UI = chooseRandomItem() @@ -239,6 +298,87 @@ GLOBAL_LIST_EMPTY(world_uplinks) var/datum/uplink_item/UI = uplink_items[params["item"]] return buy(UI, UI ? UI.reference : "") + if("add_to_cart") + var/datum/uplink_item/UI = uplink_items[params["item"]] + if(LAZYIN(shopping_cart, params["item"])) + to_chat(ui.user, "[UI.name] is already in your cart!") + return + var/startamount = 1 + if(UI.limited_stock == 0) + startamount = 0 + LAZYSET(shopping_cart, params["item"], startamount) + generate_tgui_cart(TRUE) + + if("remove_from_cart") + remove_from_cart(params["item"]) + + if("set_cart_item_quantity") + var/amount = text2num(params["quantity"]) + LAZYSET(shopping_cart, params["item"], max(amount, 0)) + generate_tgui_cart(TRUE) + + if("purchase_cart") + if(!LAZYLEN(shopping_cart)) // sanity check + return + if(calculate_cart_tc() > uses) + to_chat(ui.user, "[src] buzzes, it doesn't contain enough telecrystals!") + return + if(is_jammed) + to_chat(ui.user, "[src] seems to be jammed - it cannot be used here!") + return + + // Buying of the uplink stuff + var/list/bought_things = list() + for(var/reference in shopping_cart) + var/datum/uplink_item/item = uplink_items[reference] + var/purchase_amt = shopping_cart[reference] + if(purchase_amt <= 0) + continue + bought_things += mass_purchase(item, item ? item.reference : "", purchase_amt) + + // Check how many of them are items + var/list/obj/item/items_for_crate = list() + for(var/obj/item/thing in bought_things) + // because sometimes you can buy items like crates from surpluses and stuff + // the crates will already be on the ground, so we dont need to worry about them + if(isitem(thing)) + items_for_crate += thing + + // If we have more than 2 of them, put them in a crate + if(length(items_for_crate) > 2) + var/obj/structure/closet/crate/C = new(get_turf(src)) + for(var/obj/item/item as anything in items_for_crate) + item.forceMove(C) + // Otherwise, just put the items in their hands + else if(length(items_for_crate)) + for(var/obj/item/item as anything in items_for_crate) + ui.user.put_in_any_hand_if_possible(item) + + empty_cart() + SStgui.update_uis(src) + + if("empty_cart") + empty_cart() + + if("shuffle_lucky_numbers") + // lets see paul allen's random uplink item + shuffle_lucky_numbers() + +/obj/item/uplink/hidden/proc/shuffle_lucky_numbers() + lucky_numbers = list() + for(var/i in 1 to 4) + var/cate_number = rand(1, length(uplink_cats)) + var/item_number = rand(1, length(uplink_cats[cate_number]["items"])) + lucky_numbers += list(list("cat" = cate_number - 1, "item" = item_number - 1)) // dm lists are 1 based, js lists are 0 based, gotta -1 + +/obj/item/uplink/hidden/proc/remove_from_cart(item_reference) // i want to make it eventually remove all instances + LAZYREMOVE(shopping_cart, item_reference) + generate_tgui_cart(TRUE) + +/obj/item/uplink/hidden/proc/empty_cart() + shopping_cart = null + generate_tgui_cart(TRUE) + // I placed this here because of how relevant it is. // You place this in your uplinkable item to check if an uplink is active or not. // If it is, it will display the uplink menu and return 1, else it'll return false. diff --git a/tgui/packages/tgui/components/Box.js b/tgui/packages/tgui/components/Box.js index 4ebcf6ebb7f1..3dbc0c7af7e5 100644 --- a/tgui/packages/tgui/components/Box.js +++ b/tgui/packages/tgui/components/Box.js @@ -94,6 +94,7 @@ const styleMapperByPropName = { textTransform: mapRawPropTo('text-transform'), wordWrap: mapRawPropTo('word-wrap'), textOverflow: mapRawPropTo('text-overflow'), + borderRadius: mapRawPropTo('border-radius'), // Boolean props inline: mapBooleanPropTo('display', 'inline-block'), bold: mapBooleanPropTo('font-weight', 'bold'), diff --git a/tgui/packages/tgui/components/Button.js b/tgui/packages/tgui/components/Button.js index 71d2406f5c90..33182aa03341 100644 --- a/tgui/packages/tgui/components/Button.js +++ b/tgui/packages/tgui/components/Button.js @@ -189,6 +189,10 @@ export class ButtonInput extends Component { } setInInput(inInput) { + const { disabled } = this.props; + if (disabled) { + return; + } this.setState({ inInput, }); @@ -230,6 +234,7 @@ export class ButtonInput extends Component { tooltip, tooltipPosition, color = 'default', + disabled, placeholder, maxLength, multiLine, @@ -241,6 +246,7 @@ export class ButtonInput extends Component { className={classes([ 'Button', fluid && 'Button--fluid', + disabled && 'Button--disabled', 'Button--color--' + color, multiLine + 'Button--multiLine', ])} diff --git a/tgui/packages/tgui/components/Section.js b/tgui/packages/tgui/components/Section.js index d82314a93cfb..aa98b7f919d7 100644 --- a/tgui/packages/tgui/components/Section.js +++ b/tgui/packages/tgui/components/Section.js @@ -10,6 +10,7 @@ export const Section = (props) => { content, stretchContents, noTopPadding, + showBottom = true, children, ...rest } = props; @@ -26,7 +27,12 @@ export const Section = (props) => { {...rest} > {hasTitle && ( -
+
{title}
{buttons}
diff --git a/tgui/packages/tgui/interfaces/Uplink.js b/tgui/packages/tgui/interfaces/Uplink.js index 24a501e17008..edd8c3c87207 100644 --- a/tgui/packages/tgui/interfaces/Uplink.js +++ b/tgui/packages/tgui/interfaces/Uplink.js @@ -13,6 +13,8 @@ const PickTab = (index) => { case 0: return ; case 1: + return ; + case 2: return ; default: return 'SOMETHING WENT VERY WRONG PLEASE AHELP'; @@ -21,6 +23,7 @@ const PickTab = (index) => { export const Uplink = (props, context) => { const { act, data } = useBackend(context); + const { cart } = data; const [tabIndex, setTabIndex] = useLocalState(context, 'tabIndex', 0); const [searchText, setSearchText] = useLocalState(context, 'searchText', ''); @@ -37,17 +40,29 @@ export const Uplink = (props, context) => { setTabIndex(0); setSearchText(''); }} - icon="shopping-cart" + icon="store" > - Purchase Equipment + View Market { setTabIndex(1); setSearchText(''); }} + icon="shopping-cart" + > + View Shopping Cart{' '} + {cart && cart.length ? '(' + cart.length + ')' : ''} + + { + setTabIndex(2); + setSearchText(''); + }} icon="user" > Exploitable Information @@ -90,20 +105,27 @@ const ItemsPage = (_properties, context) => { ])(cat); }; const handleSearch = (value) => { + setSearchText(value); if (value === '') { return setUplinkItems(cats[0].items); } - setSearchText(value); setUplinkItems( SelectEquipment(cats.map((category) => category.items).flat(), value) ); }; + const [showDesc, setShowDesc] = useLocalState(context, 'showDesc', 1); + return (
+ setShowDesc(!showDesc)} + />
+ + + ); +}; +const Advert = (_properties, context) => { + const { act, data } = useBackend(context); + const { cats, lucky_numbers } = data; + + return ( +
act('shuffle_lucky_numbers')} + /> + } + > + + {lucky_numbers.map((number, index) => ( + + + + ))} + +
+ ); +}; + +const UplinkItem = (props, context) => { + const { + i, + showDecription = 1, + buttons = , + } = props; + + return ( +
+ {showDecription ? {decodeHtmlEntities(i.desc)} : null} +
+ ); +}; + +const UplinkItemButtons = (props, context) => { + const { act, data } = useBackend(context); + const { i } = props; + const { crystals } = data; + + return ( + +