diff --git a/_maps/RandomRuins/SpaceRuins/advancedlab.dmm b/_maps/RandomRuins/SpaceRuins/advancedlab.dmm index a27ad0a497..c12458ed16 100644 --- a/_maps/RandomRuins/SpaceRuins/advancedlab.dmm +++ b/_maps/RandomRuins/SpaceRuins/advancedlab.dmm @@ -132,8 +132,8 @@ /obj/item/clothing/under/pants/classicjeans, /obj/item/clothing/neck/scarf, /obj/item/clothing/neck/stripedgreenscarf, -/obj/item/toy/tennis/rainbow, -/obj/item/toy/tennis, +/obj/item/toy/fluff/tennis_poly/tri/squeak/rainbow, +/obj/item/toy/fluff/tennis_poly, /obj/item/clothing/suit/hooded/wintercoat/miner, /obj/item/clothing/shoes/sneakers/brown, /obj/item/clothing/under/pants/classicjeans, diff --git a/code/__DEFINES/blackmarket.dm b/code/__DEFINES/blackmarket.dm new file mode 100644 index 0000000000..3ec45a3a99 --- /dev/null +++ b/code/__DEFINES/blackmarket.dm @@ -0,0 +1,5 @@ +// Shipping methods + +#define SHIPPING_METHOD_LTSRBT "LTSRBT" // The BEST way of shipping items: accurate, "undetectable" +#define SHIPPING_METHOD_TELEPORT "Teleport" // Picks a random area to teleport the item to and gives you a minute to get there before it is sent. +#define SHIPPING_METHOD_LAUNCH "Launch" // Throws the item from somewhere at the station. diff --git a/code/controllers/subsystem/blackmarket.dm b/code/controllers/subsystem/blackmarket.dm new file mode 100644 index 0000000000..c26a030e0a --- /dev/null +++ b/code/controllers/subsystem/blackmarket.dm @@ -0,0 +1,89 @@ +SUBSYSTEM_DEF(blackmarket) + name = "Blackmarket" + flags = SS_BACKGROUND + init_order = INIT_ORDER_DEFAULT + + // Descriptions for each shipping method. + var/shipping_method_descriptions = list( + SHIPPING_METHOD_LAUNCH="Launches the item at the station from space, cheap but you might not recieve your item at all.", + SHIPPING_METHOD_LTSRBT="Long-To-Short-Range-Bluespace-Transceiver, a machine that recieves items outside the station and then teleports them to the location of the uplink.", + SHIPPING_METHOD_TELEPORT="Teleports the item in a random area in the station, you get 60 seconds to get there first though." + ) + + var/list/datum/blackmarket_market/markets = list() // List of all existing markets. + var/list/obj/machinery/ltsrbt/telepads = list() // List of existing ltsrbts. + var/list/queued_purchases = list() // Currently queued purchases. + +/datum/controller/subsystem/blackmarket/Initialize(timeofday) + for(var/market in subtypesof(/datum/blackmarket_market)) + markets[market] += new market + for(var/item in subtypesof(/datum/blackmarket_item)) + var/datum/blackmarket_item/I = new item() + if(!I.item) + continue + for(var/M in I.markets) + if(!markets[M]) + stack_trace("SSblackmarket: Item [I] available in market that does not exist.") + continue + markets[M].add_item(item) + qdel(I) + . = ..() + +/datum/controller/subsystem/blackmarket/fire(resumed) + while(length(queued_purchases)) + var/datum/blackmarket_purchase/purchase = queued_purchases[1] + queued_purchases.Cut(1,2) + if(!purchase.uplink || QDELETED(purchase.uplink)) // Uh oh, uplink is gone. We will just keep the money and you will not get your order. + queued_purchases -= purchase + qdel(purchase) + continue + switch(purchase.method) + if(SHIPPING_METHOD_LTSRBT) // Find a ltsrbt pad and make it handle the shipping. + if(!telepads.len) + continue + var/free_pad_found = FALSE // Prioritize pads that don't have a cooldown active. + for(var/obj/machinery/ltsrbt/pad in telepads) + if(pad.recharge_cooldown) + continue + pad.add_to_queue(purchase) + queued_purchases -= purchase + free_pad_found = TRUE + break + if(free_pad_found) + continue + var/obj/machinery/ltsrbt/pad = pick(telepads) + to_chat(recursive_loc_check(purchase.uplink.loc, /mob), "[purchase.uplink] flashes a message noting that the order is being processed by [pad].") + queued_purchases -= purchase + pad.add_to_queue(purchase) + if(SHIPPING_METHOD_TELEPORT) // Get random area, throw it somewhere there. + var/turf/targetturf = get_safe_random_station_turf() + if (!targetturf) // This shouldn't happen. + continue + to_chat(recursive_loc_check(purchase.uplink.loc, /mob), "[purchase.uplink] flashes a message noting that the order is being teleported to [get_area(targetturf)] in 60 seconds.") + addtimer(CALLBACK(src, /datum/controller/subsystem/blackmarket/proc/fake_teleport, purchase.entry.spawn_item(), targetturf), 60 SECONDS) // do_teleport does not want to teleport items from nullspace, so it just forceMoves and does sparks. + queued_purchases -= purchase + qdel(purchase) + if(SHIPPING_METHOD_LAUNCH) // Get the current location of the uplink if it exists, then throws the item from space at the station from a random direction. + var/startSide = pick(GLOB.cardinals) + var/turf/T = get_turf(purchase.uplink) + var/pickedloc = spaceDebrisStartLoc(startSide, T.z) + var/atom/movable/item = purchase.entry.spawn_item(pickedloc) + item.throw_at(purchase.uplink, 3, 3, spin = FALSE) + to_chat(recursive_loc_check(purchase.uplink.loc, /mob), "[purchase.uplink] flashes a message noting the order is being launched at the station from [dir2text(startSide)].") + queued_purchases -= purchase + qdel(purchase) + if(MC_TICK_CHECK) + break + +/datum/controller/subsystem/blackmarket/proc/fake_teleport(atom/movable/item, turf/target) // Used to make a teleportation effect as do_teleport does not like moving items from nullspace. + item.forceMove(target) + var/datum/effect_system/spark_spread/sparks = new + sparks.set_up(5, 1, target) + sparks.attach(item) + sparks.start() + +/datum/controller/subsystem/blackmarket/proc/queue_item(datum/blackmarket_purchase/P) // Used to add /datum/blackmarket_purchase to queued_purchases var. Returns TRUE when queued. + if(P.method == SHIPPING_METHOD_LTSRBT && !telepads.len) + return FALSE + queued_purchases += P + return TRUE diff --git a/code/controllers/subsystem/job.dm b/code/controllers/subsystem/job.dm index fd48e4fcbb..958c335092 100644 --- a/code/controllers/subsystem/job.dm +++ b/code/controllers/subsystem/job.dm @@ -495,42 +495,29 @@ SUBSYSTEM_DEF(job) job.after_spawn(H, M, joined_late) // note: this happens before the mob has a key! M will always have a client, H might not. equip_loadout(N, H, TRUE)//CIT CHANGE - makes players spawn with in-backpack loadout items properly. A little hacky but it works - if(ishuman(H) && H.client && N) - if(H.client && H.client.prefs && length(H.client.prefs.tcg_cards)) - var/obj/item/tcgcard_binder/binder = new(get_turf(H)) - H.equip_to_slot_if_possible(binder, SLOT_IN_BACKPACK, disable_warning = TRUE, bypass_equip_delay_self = TRUE) - for(var/card_type in H.client.prefs.tcg_cards) - if(card_type) - if(islist(H.client.prefs.tcg_cards[card_type])) - for(var/duplicate in H.client.prefs.tcg_cards[card_type]) - var/obj/item/tcg_card/card = new(get_turf(H), card_type, duplicate) - card.forceMove(binder) - binder.cards.Add(card) - else - var/obj/item/tcg_card/card = new(get_turf(H), card_type, H.client.prefs.tcg_cards[card_type]) + var/list/tcg_cards + if(ishuman(H)) + if(length(H.client?.prefs?.tcg_cards)) + tcg_cards = H.client.prefs.tcg_cards + else if(length(N?.client?.prefs?.tcg_cards)) + tcg_cards = N.client.prefs.tcg_cards + if(tcg_cards) + var/obj/item/tcgcard_binder/binder = new(get_turf(H)) + H.equip_to_slot_if_possible(binder, SLOT_IN_BACKPACK, disable_warning = TRUE, bypass_equip_delay_self = TRUE) + for(var/card_type in N.client.prefs.tcg_cards) + if(card_type) + if(islist(H.client.prefs.tcg_cards[card_type])) + for(var/duplicate in N.client.prefs.tcg_cards[card_type]) + var/obj/item/tcg_card/card = new(get_turf(H), card_type, duplicate) card.forceMove(binder) binder.cards.Add(card) - binder.check_for_exodia() - if(length(H.client.prefs.tcg_decks)) - binder.decks = H.client.prefs.tcg_decks - else - if(H && N.client.prefs && length(N.client.prefs.tcg_cards)) - var/obj/item/tcgcard_binder/binder = new(get_turf(H)) - H.equip_to_slot_if_possible(binder, SLOT_IN_BACKPACK, disable_warning = TRUE, bypass_equip_delay_self = TRUE) - for(var/card_type in N.client.prefs.tcg_cards) - if(card_type) - if(islist(H.client.prefs.tcg_cards[card_type])) - for(var/duplicate in N.client.prefs.tcg_cards[card_type]) - var/obj/item/tcg_card/card = new(get_turf(H), card_type, duplicate) - card.forceMove(binder) - binder.cards.Add(card) - else - var/obj/item/tcg_card/card = new(get_turf(H), card_type, N.client.prefs.tcg_cards[card_type]) - card.forceMove(binder) - binder.cards.Add(card) - binder.check_for_exodia() - if(length(N.client.prefs.tcg_decks)) - binder.decks = N.client.prefs.tcg_decks + else + var/obj/item/tcg_card/card = new(get_turf(H), card_type, N.client.prefs.tcg_cards[card_type]) + card.forceMove(binder) + binder.cards.Add(card) + binder.check_for_exodia() + if(length(N.client.prefs.tcg_decks)) + binder.decks = N.client.prefs.tcg_decks return H /* diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm index 05377dce71..9870d0c00a 100755 --- a/code/controllers/subsystem/ticker.dm +++ b/code/controllers/subsystem/ticker.dm @@ -622,9 +622,9 @@ SUBSYSTEM_DEF(ticker) var/list/ded = SSblackbox.first_death if(ded.len) var/last_words = ded["last_words"] ? " Their last words were: \"[ded["last_words"]]\"" : "" - news_message += " NT Sanctioned Psykers picked up faint traces of someone near the station, allegedly having had died. Their name was: [ded["name"]], [ded["role"]], at [ded["area"]].[last_words]" + news_message += "\nNT Sanctioned Psykers picked up faint traces of someone near the station, allegedly having had died. Their name was: [ded["name"]], [ded["role"]], at [ded["area"]].[last_words]" else - news_message += " NT Sanctioned Psykers proudly confirm reports that nobody died this shift!" + news_message += "\nNT Sanctioned Psykers proudly confirm reports that nobody died this shift!" if(news_message) send2otherserver(news_source, news_message,"News_Report") diff --git a/code/datums/components/crafting/recipes/recipes_misc.dm b/code/datums/components/crafting/recipes/recipes_misc.dm index d85df8c010..032dd0ea90 100644 --- a/code/datums/components/crafting/recipes/recipes_misc.dm +++ b/code/datums/components/crafting/recipes/recipes_misc.dm @@ -336,6 +336,18 @@ subcategory = CAT_TOOL category = CAT_MISC +/datum/crafting_recipe/blackmarket_uplink + name = "Black Market Uplink" + result = /obj/item/blackmarket_uplink + time = 20 + tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER) + reqs = list(/obj/item/stock_parts/subspace/amplifier = 1, + /obj/item/stack/cable_coil = 15, + /obj/item/radio = 1, + /obj/item/analyzer = 1) + subcategory = CAT_MISCELLANEOUS + category = CAT_MISC + /datum/crafting_recipe/heretic/codex name = "Codex Cicatrix" result = /obj/item/forbidden_book diff --git a/code/datums/mutations/body.dm b/code/datums/mutations/body.dm index 866ea85dbf..f7e7960585 100644 --- a/code/datums/mutations/body.dm +++ b/code/datums/mutations/body.dm @@ -261,7 +261,7 @@ desc = "The user's chemical balance is more robust." quality = POSITIVE text_gain_indication = "You feel stimmed." - difficulty = 16 + difficulty = 18 /datum/mutation/human/paranoia name = "Paranoia" diff --git a/code/datums/mutations/combined.dm b/code/datums/mutations/combined.dm index 465706c897..2b2030e8b1 100644 --- a/code/datums/mutations/combined.dm +++ b/code/datums/mutations/combined.dm @@ -30,3 +30,7 @@ /datum/generecipe/hulk required = "/datum/mutation/human/strong; /datum/mutation/human/radioactive" result = HULK + +/datum/generecipe/thermal + required = "/datum/mutation/human/nearsight; /datum/mutation/human/stimmed" + result = THERMAL diff --git a/code/datums/mutations/sight.dm b/code/datums/mutations/sight.dm index c8bc1a1597..84effe8eaa 100644 --- a/code/datums/mutations/sight.dm +++ b/code/datums/mutations/sight.dm @@ -42,7 +42,8 @@ difficulty = 18 text_gain_indication = "You can see the heat rising off of your skin..." time_coeff = 2 - instability = 25 + instability = 40 + locked = TRUE var/visionflag = TRAIT_THERMAL_VISION /datum/mutation/human/thermal/on_acquiring(mob/living/carbon/human/owner) @@ -63,7 +64,7 @@ name = "X Ray Vision" desc = "A strange genome that allows the user to see between the spaces of walls." //actual x-ray would mean you'd constantly be blasting rads, wich might be fun for later //hmb text_gain_indication = "The walls suddenly disappear!" - instability = 35 + instability = 50 locked = TRUE visionflag = TRAIT_XRAY_VISION diff --git a/code/datums/shuttles.dm b/code/datums/shuttles.dm index 62f8cef5dc..add0668950 100644 --- a/code/datums/shuttles.dm +++ b/code/datums/shuttles.dm @@ -204,12 +204,11 @@ // first 10 minutes only return world.time - SSticker.round_start_time < 6000 -// this is broken and does not work. Thanks TG -// /datum/map_template/shuttle/emergency/airless/post_load() -// . = ..() -// //enable buying engines from cargo -// var/datum/supply_pack/P = SSshuttle.supply_packs[/datum/supply_pack/engineering/shuttle_engine] -// P.special_enabled = TRUE +/datum/map_template/shuttle/emergency/construction/post_load() + . = ..() + //enable buying engines from cargo + var/datum/supply_pack/P = SSshuttle.supply_packs[/datum/supply_pack/engineering/shuttle_engine] + P.special_enabled = TRUE /datum/map_template/shuttle/emergency/asteroid diff --git a/code/game/machinery/autolathe.dm b/code/game/machinery/autolathe.dm index d8a5f7d2c7..0e7b4aa0fa 100644 --- a/code/game/machinery/autolathe.dm +++ b/code/game/machinery/autolathe.dm @@ -48,13 +48,14 @@ ) /obj/machinery/autolathe/Initialize() - AddComponent(/datum/component/material_container, SSmaterials.materialtypes_by_category[MAT_CATEGORY_RIGID], 0, TRUE, null, null, CALLBACK(src, .proc/AfterMaterialInsert)) . = ..() - wires = new /datum/wires/autolathe(src) stored_research = new /datum/techweb/specialized/autounlocking/autolathe matching_designs = list() +/obj/machinery/autolathe/ComponentInitialize() + AddComponent(/datum/component/material_container, SSmaterials.materialtypes_by_category[MAT_CATEGORY_RIGID], 0, TRUE, null, null, CALLBACK(src, .proc/AfterMaterialInsert)) + /obj/machinery/autolathe/Destroy() QDEL_NULL(wires) return ..() @@ -439,6 +440,9 @@ /obj/machinery/autolathe/secure/Initialize() . = ..() + // let's not leave the parent datum floating, right? + if(stored_research) + QDEL_NULL(stored_research) stored_research = new /datum/techweb/specialized/autounlocking/autolathe/public /obj/machinery/autolathe/toy @@ -457,8 +461,18 @@ "Misc", "Imported" ) +/obj/machinery/autolathe/toy/Initialize() + . = ..() + // let's not leave the parent datum floating, right? + if(stored_research) + QDEL_NULL(stored_research) + stored_research = new /datum/techweb/specialized/autounlocking/autolathe/toy /obj/machinery/autolathe/toy/hacked/Initialize() . = ..() adjust_hacked(TRUE) - stored_research = new /datum/techweb/specialized/autounlocking/autolathe/toy + +// override the base to allow plastics +/obj/machinery/autolathe/ComponentInitialize() + var/list/extra_mats = list(/datum/material/plastic) + AddComponent(/datum/component/material_container, SSmaterials.materialtypes_by_category[MAT_CATEGORY_RIGID] + extra_mats, 0, TRUE, null, null, CALLBACK(src, .proc/AfterMaterialInsert)) diff --git a/code/game/machinery/constructable_frame.dm b/code/game/machinery/constructable_frame.dm index d5bbcb0adc..bdf7a80c96 100644 --- a/code/game/machinery/constructable_frame.dm +++ b/code/game/machinery/constructable_frame.dm @@ -36,7 +36,7 @@ /obj/structure/frame/machine/examine(user) . = ..() if(state == 3 && req_components && req_component_names) - var/hasContent = 0 + var/hasContent = FALSE var/requires = "It requires" for(var/i = 1 to req_components.len) @@ -46,10 +46,10 @@ continue var/use_and = i == req_components.len requires += "[(hasContent ? (use_and ? ", and" : ",") : "")] [amt] [amt == 1 ? req_component_names[tname] : "[req_component_names[tname]]\s"]" - hasContent = 1 + hasContent = TRUE if(hasContent) - . += requires + "." + . += "[requires]." else . += "It does not require any more components." @@ -76,7 +76,7 @@ amt += req_components[path] return amt -/obj/structure/frame/machine/attackby(obj/item/P, mob/user, params) +/obj/structure/frame/machine/attackby(obj/item/P, mob/living/user, params) switch(state) if(1) if(istype(P, /obj/item/circuitboard/machine)) @@ -88,6 +88,7 @@ if(istype(P, /obj/item/stack/cable_coil)) if(!P.tool_start_check(user, amount=5)) return + to_chat(user, "You start to add cables to the frame...") if(P.use_tool(src, user, 20, volume=50, amount=5, extra_checks = CALLBACK(src, .proc/check_state, 1))) to_chat(user, "You add cables to the frame.") @@ -97,39 +98,41 @@ return if(P.tool_behaviour == TOOL_SCREWDRIVER && !anchored) user.visible_message("[user] disassembles the frame.", \ - "You start to disassemble the frame...", "You hear banging and clanking.") + "You start to disassemble the frame...", "You hear banging and clanking.") if(P.use_tool(src, user, 40, volume=50, extra_checks = CALLBACK(src, .proc/check_state, 1))) - to_chat(user, "You disassemble the frame.") - var/obj/item/stack/sheet/metal/M = new (loc, 5) - M.add_fingerprint(user) - qdel(src) + if(state == 1) + to_chat(user, "You disassemble the frame.") + var/obj/item/stack/sheet/metal/M = new (loc, 5) + M.add_fingerprint(user) + qdel(src) return if(P.tool_behaviour == TOOL_WRENCH) - to_chat(user, "You start [anchored ? "un" : ""]securing [name]...") + to_chat(user, "You start [anchored ? "un" : ""]securing [src]...") if(P.use_tool(src, user, 40, volume=75, extra_checks = CALLBACK(src, .proc/check_state, 1))) - to_chat(user, "You [anchored ? "un" : ""]secure [name].") - setAnchored(!anchored) + if(state == 1) + to_chat(user, "You [anchored ? "un" : ""]secure [src].") + set_anchored(!anchored) return if(2) if(P.tool_behaviour == TOOL_WRENCH) - to_chat(user, "You start [anchored ? "un" : ""]securing [name]...") + to_chat(user, "You start [anchored ? "un" : ""]securing [src]...") if(P.use_tool(src, user, 40, volume=75, extra_checks = CALLBACK(src, .proc/check_state, 2))) - to_chat(user, "You [anchored ? "un" : ""]secure [name].") - setAnchored(!anchored) + to_chat(user, "You [anchored ? "un" : ""]secure [src].") + set_anchored(!anchored) return if(istype(P, /obj/item/circuitboard/machine)) var/obj/item/circuitboard/machine/B = P if(!B.build_path) - to_chat(user, "This circuitboard seems to be broken.") + to_chat(user, "This circuitboard seems to be broken.") return if(!anchored && B.needs_anchored) to_chat(user, "The frame needs to be secured first!") return if(!user.transferItemToLoc(B, src)) return - playsound(src.loc, 'sound/items/deconstruct.ogg', 50, 1) + playsound(src.loc, 'sound/items/deconstruct.ogg', 50, TRUE) to_chat(user, "You add the circuit board to the frame.") circuit = B icon_state = "box_2" @@ -171,34 +174,49 @@ return if(P.tool_behaviour == TOOL_WRENCH && !circuit.needs_anchored) - to_chat(user, "You start [anchored ? "un" : ""]securing [name]...") + to_chat(user, "You start [anchored ? "un" : ""]securing [src]...") if(P.use_tool(src, user, 40, volume=75, extra_checks = CALLBACK(src, .proc/check_state, 3))) - to_chat(user, "You [anchored ? "un" : ""]secure [name].") - setAnchored(!anchored) + to_chat(user, "You [anchored ? "un" : ""]secure [src].") + set_anchored(!anchored) return if(P.tool_behaviour == TOOL_SCREWDRIVER) - var/component_check = 1 + var/component_check = TRUE for(var/R in req_components) if(req_components[R] > 0) - component_check = 0 + component_check = FALSE break if(component_check) P.play_tool_sound(src) var/obj/machinery/new_machine = new circuit.build_path(loc) - if(new_machine.circuit) - QDEL_NULL(new_machine.circuit) - new_machine.circuit = circuit - new_machine.setAnchored(anchored) - new_machine.on_construction() - for(var/obj/O in new_machine.component_parts) - qdel(O) - new_machine.component_parts = list() - for(var/obj/O in src) - O.moveToNullspace() - new_machine.component_parts += O - circuit.moveToNullspace() - new_machine.RefreshParts() + if(istype(new_machine)) + // Machines will init with a set of default components. Move to nullspace so we don't trigger handle_atom_del, then qdel. + // Finally, replace with this frame's parts. + if(new_machine.circuit) + // Move to nullspace and delete. + new_machine.circuit.moveToNullspace() + QDEL_NULL(new_machine.circuit) + for(var/obj/old_part in new_machine.component_parts) + // Move to nullspace and delete. + old_part.moveToNullspace() + qdel(old_part) + + // Set anchor state and move the frame's parts over to the new machine. + // Then refresh parts and call on_construction(). + + new_machine.set_anchored(anchored) + new_machine.component_parts = list() + + circuit.forceMove(new_machine) + new_machine.component_parts += circuit + new_machine.circuit = circuit + + for(var/obj/new_part in src) + new_part.forceMove(new_machine) + new_machine.component_parts += new_part + new_machine.RefreshParts() + + new_machine.on_construction() qdel(src) return @@ -232,12 +250,15 @@ for(var/obj/item/part in added_components) if(istype(part,/obj/item/stack)) - var/obj/item/stack/S = part - var/obj/item/stack/NS = locate(S.merge_type) in components //find a stack to merge with - if(NS) - S.merge(NS) + var/obj/item/stack/incoming_stack = part + for(var/obj/item/stack/merge_stack in components) + if(incoming_stack.can_merge(merge_stack)) + incoming_stack.merge(merge_stack) + if(QDELETED(incoming_stack)) + break if(!QDELETED(part)) //If we're a stack and we merged we might not exist anymore components += part + part.forceMove(src) to_chat(user, "[part.name] applied.") if(added_components.len) replacer.play_rped_sound() @@ -267,9 +288,9 @@ to_chat(user, "You add [P] to [src].") components += P req_components[I]-- - return 1 + return TRUE to_chat(user, "You cannot add that to the machine!") - return 0 + return FALSE if(user.a_intent == INTENT_HARM) return ..() @@ -280,5 +301,4 @@ for(var/X in components) var/obj/item/I = X I.forceMove(loc) - ..() diff --git a/code/game/objects/items/balls.dm b/code/game/objects/items/balls.dm deleted file mode 100644 index 2e5a385ead..0000000000 --- a/code/game/objects/items/balls.dm +++ /dev/null @@ -1,92 +0,0 @@ -/* BALLS - GLORIOUS BALLS -// -// Includes:- -// 1) Tennis balls, lines 10 - 92 -// -// -// -*/ - -/obj/item/toy/tennis - name = "tennis ball" - desc = "A classical tennis ball. It appears to have faint bite marks scattered all over its surface." - icon = 'modular_citadel/icons/obj/balls.dmi' - icon_state = "tennis_classic" - lefthand_file = 'modular_citadel/icons/mob/inhands/balls_left.dmi' - righthand_file = 'modular_citadel/icons/mob/inhands/balls_right.dmi' - item_state = "tennis_classic" - mob_overlay_icon = 'modular_citadel/icons/mob/mouthball.dmi' - slot_flags = ITEM_SLOT_HEAD | ITEM_SLOT_NECK | ITEM_SLOT_EARS //Fluff item, put it wherever you want! - throw_range = 14 - w_class = WEIGHT_CLASS_SMALL - -/obj/item/toy/tennis/alt_pre_attack(atom/A, mob/living/user, params) //checks if it can do right click memes - altafterattack(A, user, TRUE, params) - return TRUE - -/obj/item/toy/tennis/altafterattack(atom/target, mob/living/carbon/user, proximity_flag, click_parameters) //does right click memes - if(istype(user)) - user.visible_message("[user] waggles [src] at [target].", "You waggle [src] at [target].") - return TRUE - -/obj/item/toy/tennis/rainbow - name = "pseudo-euclidean interdimensional tennis sphere" - desc = "A tennis ball from another plane of existance. Really groovy." - icon_state = "tennis_rainbow" - item_state = "tennis_rainbow" - actions_types = list(/datum/action/item_action/squeeze) //Giving the masses easy access to unilimted honks would be annoying - -/obj/item/toy/tennis/rainbow/Initialize() - . = ..() - AddComponent(/datum/component/squeak) - -/obj/item/toy/tennis/rainbow/izzy //izzyinbox's donator item - name = "Katlin's Ball" - desc = "A tennis ball that's seen a good bit of love, being covered in a few black and white hairs and slobber." - icon_state = "tennis_izzy" - item_state = "tennis_izzy" - -/obj/item/toy/tennis/red //da red wuns go fasta - name = "red tennis ball" - desc = "A red tennis ball. It goes three times faster!" - icon_state = "tennis_red" - item_state = "tennis_red" - throw_speed = 9 - -/obj/item/toy/tennis/yellow //because yellow is hot I guess - name = "yellow tennis ball" - desc = "A yellow tennis ball. It seems to have a flame-retardant coating." - icon_state = "tennis_yellow" - item_state = "tennis_yellow" - resistance_flags = FIRE_PROOF - -/obj/item/toy/tennis/green //pestilence - name = "green tennis ball" - desc = "A green tennis ball. It seems to have an impermeable coating." - icon_state = "tennis_green" - item_state = "tennis_green" - permeability_coefficient = 0.9 - -/obj/item/toy/tennis/cyan //electric - name = "cyan tennis ball" - desc = "A cyan tennis ball. It seems to have odd electrical properties." - icon_state = "tennis_cyan" - item_state = "tennis_cyan" - siemens_coefficient = 0.9 - -/obj/item/toy/tennis/blue //reliability - name = "blue tennis ball" - desc = "A blue tennis ball. It seems ever so slightly more robust than normal." - icon_state = "tennis_blue" - item_state = "tennis_blue" - max_integrity = 300 - -/obj/item/toy/tennis/purple //because purple dyes have high pH and would neutralize acids I guess - name = "purple tennis ball" - desc = "A purple tennis ball. It seems to have an acid-resistant coating." - icon_state = "tennis_purple" - item_state = "tennis_purple" - resistance_flags = ACID_PROOF - -/datum/action/item_action/squeeze - name = "Squeak!" diff --git a/code/game/objects/items/circuitboards/computer_circuitboards.dm b/code/game/objects/items/circuitboards/computer_circuitboards.dm index 01bed631fa..afab49ac76 100644 --- a/code/game/objects/items/circuitboards/computer_circuitboards.dm +++ b/code/game/objects/items/circuitboards/computer_circuitboards.dm @@ -549,6 +549,7 @@ contraband = TRUE obj_flags |= EMAGGED to_chat(user, "You adjust [src]'s routing and receiver spectrum, unlocking special supplies and contraband.") + return TRUE /obj/item/circuitboard/computer/cargo/configure_machine(obj/machinery/computer/cargo/machine) if(!istype(machine)) @@ -565,10 +566,12 @@ build_path = /obj/machinery/computer/cargo/express /obj/item/circuitboard/computer/cargo/express/emag_act(mob/living/user) + . = ..() if(!(obj_flags & EMAGGED)) contraband = TRUE obj_flags |= EMAGGED to_chat(user, "You change the routing protocols, allowing the Drop Pod to land anywhere on the station.") + return TRUE /obj/item/circuitboard/computer/cargo/express/multitool_act(mob/living/user) if (!(obj_flags & EMAGGED)) diff --git a/code/game/objects/items/circuitboards/machine_circuitboards.dm b/code/game/objects/items/circuitboards/machine_circuitboards.dm index 286506a7d1..6bf6e7d312 100644 --- a/code/game/objects/items/circuitboards/machine_circuitboards.dm +++ b/code/game/objects/items/circuitboards/machine_circuitboards.dm @@ -609,6 +609,7 @@ /obj/machinery/vending/cigarette = "ShadyCigs Deluxe", /obj/machinery/vending/games = "\improper Good Clean Fun", /obj/machinery/vending/kink = "KinkMate", + /obj/machinery/vending/barkbox = "Bark Box", /obj/machinery/vending/autodrobe = "AutoDrobe", /obj/machinery/vending/wardrobe/sec_wardrobe = "SecDrobe", /obj/machinery/vending/wardrobe/det_wardrobe = "DetDrobe", diff --git a/code/game/objects/items/fluff.dm b/code/game/objects/items/fluff.dm new file mode 100644 index 0000000000..7105e57c30 --- /dev/null +++ b/code/game/objects/items/fluff.dm @@ -0,0 +1,160 @@ +/* Balls, Bones, and Bountiful Fun +// +// Includes:- +// 1) Fluff Content, lines 12 - 131 +// +// 1) Tennis balls, lines 39 - 99 +// 2) Chew bones, lines 101 - 138 +// 3) Frisbee, lines 140 - 166 +*/ + +/obj/item/toy/fluff + name = "Fluff Item" + desc = "You shouldn't be seeing this." + icon = 'icons/obj/barkbox_fluff.dmi' + icon_state = "poly_tennis" + item_state = "poly_tennis" + lefthand_file = 'icons/mob/inhands/fluff_lefthand.dmi' + righthand_file = 'icons/mob/inhands/fluff_righthand.dmi' + mob_overlay_icon = 'icons/mob/mouthfluff.dmi' + slot_flags = ITEM_SLOT_HEAD | ITEM_SLOT_NECK | ITEM_SLOT_EARS + var/poly_states = 0 + var/poly_colors = list() + +/obj/item/toy/fluff/alt_pre_attack(atom/A, mob/living/user, params) //checks if it can do right click memes + altafterattack(A, user, TRUE, params) + return TRUE + +/obj/item/toy/fluff/altafterattack(atom/target, mob/living/carbon/user, proximity_flag, click_parameters) //does right click memes + if(istype(user)) + user.visible_message("[user] waggles [src] at [target].", "You waggle [src] at [target].") + return TRUE + +/obj/item/toy/fluff/ComponentInitialize() + . = ..() + if(!poly_states) + return + AddElement(/datum/element/polychromic, poly_colors, poly_states, _flags = POLYCHROMIC_ACTION) + +/obj/item/toy/fluff/tennis_poly + name = "polychromic tennis ball" + desc = "A polychromic tennis ball. There's a half torn tag read: WARNIN-, surely it means nothing. Right?" + throw_range = 14 + w_class = WEIGHT_CLASS_SMALL + poly_states = 2 + poly_colors = list("#CCFF00", "#FFFFFF") + +/obj/item/toy/fluff/tennis_poly/red + poly_colors = list("#FF4C00", "#FFFFFF") + +/obj/item/toy/fluff/tennis_poly/yellow + poly_colors = list("#FFCC00", "#FFFFFF") + +/obj/item/toy/fluff/tennis_poly/green + poly_colors = list("#99FF00", "#FFFFFF") + +/obj/item/toy/fluff/tennis_poly/cyan + poly_colors = list("#00FFB2", "#FFFFFF") + +/obj/item/toy/fluff/tennis_poly/blue + poly_colors = list("#007FFF", "#FFFFFF") + +/obj/item/toy/fluff/tennis_poly/purple + poly_colors = list("#CC00FF", "#FFFFFF") + +/obj/item/toy/fluff/tennis_poly/tri + name = "tricolor-polychromic tennis ball" + desc = "A tricolor-polychromic tennis ball. Triple the shocking!" + icon_state = "tripoly_tennis" + item_state = "tripoly_tennis" + poly_states = 3 + poly_colors = list("#FFFFFF", "#FFFFFF", "#FFFFFF") + +/obj/item/toy/fluff/tennis_poly/tri/squeak + name = "tricolor-polychromic tennis sphere" + desc = "A tricolor-polychromic tennis ball. This one seems to emit a squeak when squeezed." + actions_types = list(/datum/action/item_action/squeeze) + +/obj/item/toy/fluff/tennis_poly/tri/squeak/izzy //izzyinbox's donator item + name = "Katlin's Ball" + desc = "A tennis ball that's seen a good bit of love, being covered in a few black and white hairs and slobber." + poly_colors = list("#8FED56", "#51cfde", "#FFFFFF") + +/obj/item/toy/fluff/tennis_poly/tri/squeak/rainbow + name = "pseudo-euclidean interdimensional tennis sphere" + desc = "A tennis ball from another plane of existance. Really groovy." + icon_state = "tennis_rainbow" + item_state = "tennis_rainbow" + poly_states = 0 + actions_types = list(/datum/action/item_action/squeeze) + +/obj/item/toy/fluff/tennis_poly/tri/squeak/Initialize() + . = ..() + AddComponent(/datum/component/squeak) + +/obj/item/toy/fluff/bone_poly + name = "polychromic bone" + desc = "A polychromic chew bone. Nothing like a good bone to chew on." + icon_state = "poly_bone" + item_state = "poly_bone" + throw_range = 7 + w_class = WEIGHT_CLASS_SMALL + poly_states = 1 + poly_colors = list("#FFFFFF") + +/obj/item/toy/fluff/bone_poly/red + poly_colors = list("#FF4C00") + +/obj/item/toy/fluff/bone_poly/yellow + poly_colors = list("#FFCC00") + +/obj/item/toy/fluff/bone_poly/green + poly_colors = list("#99FF00") + +/obj/item/toy/fluff/bone_poly/cyan + poly_colors = list("#00FFB2") + +/obj/item/toy/fluff/bone_poly/blue + poly_colors = list("#007FFF") + +/obj/item/toy/fluff/bone_poly/purple + poly_colors = list("#CC00FF") + +/obj/item/toy/fluff/bone_poly/squeak + name = "polychromic bone" + desc = "A polychromic chew bone. Makes a small squeak when squeezed." + +/obj/item/toy/fluff/bone_poly/squeak/Initialize() + . = ..() + AddComponent(/datum/component/squeak) + +/datum/action/item_action/squeeze + name = "Squeak!" + +/obj/item/toy/fluff/frisbee_poly + name = "polychromic frisbee" + desc = "A polychromic frisbee. Warning: May induce shock." + icon_state = "poly_frisbee" + item_state = "poly_frisbee" + throw_range = 14 + w_class = WEIGHT_CLASS_NORMAL + poly_states = 2 + poly_colors = list("#CCFF00", "#FFFFFF") + +/obj/item/toy/fluff/frisbee_poly/red + poly_colors = list("#FF4C00", "#FFFFFF") + +/obj/item/toy/fluff/frisbee_poly/yellow + poly_colors = list("#FFCC00", "#FFFFFF") + +/obj/item/toy/fluff/frisbee_poly/green + poly_colors = list("#99FF00", "#FFFFFF") + +/obj/item/toy/fluff/frisbee_poly/cyan + poly_colors = list("#00FFB2", "#FFFFFF") + +/obj/item/toy/fluff/frisbee_poly/blue + poly_colors = list("#007FFF", "#FFFFFF") + +/obj/item/toy/fluff/frisbee_poly/purple + poly_colors = list("#CC00FF", "#FFFFFF") diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm index 7b251c075b..d126e39873 100644 --- a/code/game/objects/items/stacks/stack.dm +++ b/code/game/objects/items/stacks/stack.dm @@ -459,8 +459,8 @@ /obj/item/stack/AltClick(mob/living/user) . = ..() - if(isturf(loc)) // to prevent people that are alt clicking a tile to see its content from getting undesidered pop ups - return + // if(isturf(loc)) // to prevent people that are alt clicking a tile to see its content from getting undesidered pop ups + // return if(is_cyborg || !user.canUseTopic(src, BE_CLOSE, TRUE, FALSE) || zero_amount()) //, !iscyborg(user) return //get amount from user diff --git a/code/game/objects/items/storage/fancy.dm b/code/game/objects/items/storage/fancy.dm index b8cd2a1389..d72f46e175 100644 --- a/code/game/objects/items/storage/fancy.dm +++ b/code/game/objects/items/storage/fancy.dm @@ -435,6 +435,20 @@ STR.max_items = 6 STR.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/nugget)) +/obj/item/storage/fancy/treat_box + name = "treat box" + desc = "A cardboard box used for holding dog treats." + icon = 'icons/obj/food/containers.dmi' + icon_state = "treatbox" + icon_type = "treat" + spawn_type = /obj/item/reagent_containers/food/snacks/dogtreat + +/obj/item/storage/fancy/treat_box/ComponentInitialize() + . = ..() + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 6 + STR.can_hold = typecacheof(list(/obj/item/reagent_containers/food/snacks/dogtreat)) + /obj/item/storage/fancy/cracker_pack name = "cracker pack" desc = "A pack of delicious crackers. Keep away from parrots!" diff --git a/code/game/objects/items/tanks/jetpack.dm b/code/game/objects/items/tanks/jetpack.dm index 29961d12b4..92e0cee7e8 100644 --- a/code/game/objects/items/tanks/jetpack.dm +++ b/code/game/objects/items/tanks/jetpack.dm @@ -15,7 +15,7 @@ var/datum/effect_system/trail_follow/ion/ion_trail /obj/item/tank/jetpack/Initialize() - ..() + . = ..() ion_trail = new ion_trail.set_up(src) @@ -88,7 +88,7 @@ return TRUE /obj/item/tank/jetpack/suicide_act(mob/user) - if (istype(user, /mob/living/carbon/human/)) + if (ishuman(user)) var/mob/living/carbon/human/H = user H.forcesay("WHAT THE FUCK IS CARBON DIOXIDE?") H.visible_message("[user] is suffocating [user.p_them()]self with [src]! It looks like [user.p_they()] didn't read what that jetpack says!") diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm index ed0f6b7012..c502e36ae8 100644 --- a/code/game/objects/structures/crates_lockers/closets.dm +++ b/code/game/objects/structures/crates_lockers/closets.dm @@ -4,6 +4,10 @@ icon = 'icons/obj/closet.dmi' icon_state = "generic" density = TRUE + max_integrity = 200 + integrity_failure = 0.25 + armor = list("melee" = 20, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 60) + var/icon_door = null var/icon_door_override = FALSE //override to have open overlay use icon different to its base's var/secure = FALSE //secure locker or not, also used if overriding a non-secure locker with a secure door overlay to add fancy lights @@ -12,9 +16,6 @@ var/locked = FALSE var/large = TRUE var/wall_mounted = 0 //never solid (You can always pass over it) - max_integrity = 200 - integrity_failure = 0.25 - armor = list("melee" = 20, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 60) var/breakout_time = 1200 var/message_cooldown var/can_weld_shut = TRUE @@ -40,12 +41,12 @@ var/should_populate_contents = TRUE /obj/structure/closet/Initialize(mapload) + if(mapload && !opened) // if closed, any item at the crate's loc is put in the contents + addtimer(CALLBACK(src, .proc/take_contents), 0) . = ..() update_icon() if(should_populate_contents) PopulateContents() - if(mapload && !opened) // if closed, any item at the crate's loc is put in the contents - addtimer(CALLBACK(src, .proc/take_contents), 0) if(secure) lockerelectronics = new(src) lockerelectronics.accesses = req_access @@ -60,10 +61,10 @@ /obj/structure/closet/update_icon() . = ..() - if(!opened) - layer = OBJ_LAYER - else - layer = BELOW_OBJ_LAYER + if(istype(src, /obj/structure/closet/supplypod)) + return + + layer = opened ? BELOW_OBJ_LAYER : OBJ_LAYER /obj/structure/closet/update_overlays() . = ..() @@ -71,61 +72,57 @@ /obj/structure/closet/proc/closet_update_overlays(list/new_overlays) . = new_overlays - if(!opened) - if(icon_door) - . += "[icon_door]_door" - else - . += "[icon_state]_door" - if(welded) - . += "welded" - if(!secure) - return - if(broken) - . += "off" - . += "sparking" - else if(locked) - . += "locked" - else - . += "unlocked" - else - if(icon_door_override) - . += "[icon_door]_open" - else - . += "[icon_state]_open" + if(opened) + . += "[icon_door_override ? icon_door : icon_state]_open" + return + + . += "[icon_door || icon_state]_door" + if(welded) + . += icon_welded + + if(!secure) + return + if(broken) + . += "off" + . += "sparking" + //Overlay is similar enough for both that we can use the same mask for both + SSvis_overlays.add_vis_overlay(src, icon, "locked", EMISSIVE_LAYER, EMISSIVE_PLANE, dir, alpha) + . += locked ? "locked" : "unlocked" /obj/structure/closet/examine(mob/user) . = ..() if(welded) - . += "It's welded shut." + . += "It's welded shut." if(anchored) . += "It is bolted to the ground." if(opened) . += "The parts are welded together." else if(secure && !opened) + . += "Alt-click to [locked ? "unlock" : "lock"]." else if(broken) . += "The lock is screwed in." - else if(secure) - . += "Alt-click to [locked ? "unlock" : "lock"]." - if(isliving(user)) - var/mob/living/L = user - if(HAS_TRAIT(L, TRAIT_SKITTISH)) - . += "Ctrl-Shift-click [src] to jump inside." + if(isobserver(user)) . += "It contains: [english_list(contents)]." investigate_log("had its contents examined by [user] as a ghost.", INVESTIGATE_GHOST) + if(HAS_TRAIT(user, TRAIT_SKITTISH)) + . += "If you bump into [p_them()] while running, you will jump inside." + /obj/structure/closet/CanPass(atom/movable/mover, turf/target) if(wall_mounted) return TRUE return !density -/obj/structure/closet/proc/can_open(mob/living/user) +/obj/structure/closet/proc/can_open(mob/living/user, force = FALSE) + if(force) + return TRUE if(welded || locked) return FALSE var/turf/T = get_turf(src) for(var/mob/living/L in T) - if(L.move_resist >= MOVE_FORCE_VERY_STRONG || (horizontal && L.mob_size > MOB_SIZE_TINY && L.density)) + if(L.anchored || L.move_resist >= MOVE_FORCE_VERY_STRONG || (horizontal && L.mob_size > MOB_SIZE_TINY && L.density)) if(user) to_chat(user, "There's something large on top of [src], preventing it from opening." ) return FALSE @@ -137,44 +134,16 @@ if(closet != src && !closet.wall_mounted) return FALSE for(var/mob/living/L in T) - if(L.move_resist >= MOVE_FORCE_VERY_STRONG || horizontal && L.mob_size > MOB_SIZE_TINY && L.density) + if(L.anchored || L.move_resist >= MOVE_FORCE_VERY_STRONG || (horizontal && L.mob_size > MOB_SIZE_TINY && L.density)) if(user) to_chat(user, "There's something too large in [src], preventing it from closing.") return FALSE return TRUE -/obj/structure/closet/proc/can_lock(mob/living/user, var/check_access = TRUE) //set check_access to FALSE if you only need to check if a locker has a functional lock rather than access - if(!secure) - return FALSE - if(broken) - to_chat(user, "[src] is broken!") - return FALSE - if(QDELETED(lockerelectronics) && !locked) //We want to be able to unlock it regardless of electronics, but only lockable with electronics - to_chat(user, "[src] is missing locker electronics!") - return FALSE - if(!check_access) - return TRUE - if(allowed(user)) - return TRUE - to_chat(user, "Access denied.") - -/obj/structure/closet/proc/togglelock(mob/living/user) - add_fingerprint(user) - if(eigen_target) - return - if(opened) - return - if(!can_lock(user)) - return - locked = !locked - user.visible_message("[user] [locked ? null : "un"]locks [src].", - "You [locked ? null : "un"]lock [src].") - update_icon() - -/obj/structure/closet/proc/dump_contents(var/override = TRUE) //Override is for not revealing the locker electronics when you open the locker, for example +/obj/structure/closet/proc/dump_contents(override = TRUE) //Override is for not revealing the locker electronics when you open the locker, for example var/atom/L = drop_location() for(var/atom/movable/AM in src) - if(AM == lockerelectronics && override) + if(AM == lockerelectronics && override) // this stops the electronics from being dumped out? huh continue AM.forceMove(L) if(throwing) // you keep some momentum when getting out of a thrown closet @@ -187,18 +156,30 @@ for(var/atom/movable/AM in L) if(AM != src && insert(AM) == -1) // limit reached break + // for(var/i in reverseRange(L.GetAllContents())) + // var/atom/movable/thing = i + // SEND_SIGNAL(thing, COMSIG_TRY_STORAGE_HIDE_ALL) -/obj/structure/closet/proc/open(mob/living/user) - if(opened || !can_open(user)) +/obj/structure/closet/proc/open(mob/living/user, force = FALSE) + if(!can_open(user, force)) return - playsound(loc, open_sound, 15, 1, -3) + if(opened) + return + welded = FALSE + locked = FALSE // if you manage to open it, then its not welded/locked, hello?! + playsound(loc, open_sound, 15, TRUE, -3) opened = TRUE if(!dense_when_open) density = FALSE climb_time *= 0.5 //it's faster to climb onto an open thing dump_contents() update_icon() - return 1 + after_open(user, force) + return TRUE + +///Proc to override for effects after opening a door +/obj/structure/closet/proc/after_open(mob/living/user, force = FALSE) + return /obj/structure/closet/proc/insert(atom/movable/AM) if(contents.len >= storage_capacity) @@ -208,19 +189,19 @@ do_teleport(AM, get_turf(eigen_target), 0) if(eigen_target.opened == FALSE) eigen_target.bust_open() - else - AM.forceMove(src) + return TRUE + + AM.forceMove(src) return TRUE else return FALSE - /obj/structure/closet/proc/insertion_allowed(atom/movable/AM) if(ismob(AM)) if(!isliving(AM)) //let's not put ghosts or camera mobs inside closets... return FALSE var/mob/living/L = AM - if(L.move_resist >= MOVE_FORCE_VERY_STRONG || L.buckled || L.incorporeal_move || L.has_buckled_mobs()) + if(L.anchored || L.move_resist >= MOVE_FORCE_VERY_STRONG || L.buckled || L.incorporeal_move || L.has_buckled_mobs()) return FALSE if(L.mob_size > MOB_SIZE_TINY) // Tiny mobs are treated as items. if(horizontal && L.density) @@ -236,13 +217,13 @@ else if(istype(AM, /obj/structure/closet)) return FALSE - else if(istype(AM, /obj/effect)) + else if(iseffect(AM)) // todo: move to atom/movable return FALSE else if(isobj(AM)) if((!allow_dense && AM.density) || AM.anchored || AM.has_buckled_mobs()) return FALSE - if(isitem(AM) && !HAS_TRAIT(AM, TRAIT_NODROP)) + else if(isitem(AM) && !HAS_TRAIT(AM, TRAIT_NODROP)) return TRUE else if(!allow_objects && !istype(AM, /obj/effect/dummy/chameleon)) return FALSE @@ -255,25 +236,356 @@ if(!opened || !can_close(user)) return FALSE take_contents() - playsound(loc, close_sound, 15, 1, -3) - climb_time = initial(climb_time) + playsound(loc, close_sound, 15, TRUE, -3) opened = FALSE density = TRUE update_icon() + after_close(user) return TRUE +///Proc to override for effects after closing a door +/obj/structure/closet/proc/after_close(mob/living/user) + return + + /obj/structure/closet/proc/toggle(mob/living/user) if(opened) return close(user) else return open(user) + +/obj/structure/closet/deconstruct(disassembled = TRUE) + if(ispath(material_drop) && material_drop_amount && !(flags_1 & NODECONSTRUCT_1)) + new material_drop(loc, material_drop_amount) + qdel(src) + +/obj/structure/closet/obj_break(damage_flag) + if(!broken && !(flags_1 & NODECONSTRUCT_1)) + bust_open() + +/obj/structure/closet/attackby(obj/item/W, mob/user, params) + if(user in src) + return + if(src.tool_interact(W,user)) + return 1 // No afterattack + else + return ..() + +/obj/structure/closet/proc/tool_interact(obj/item/W, mob/living/user)//returns TRUE if attackBy call shouldn't be continued (because tool was used/closet was of wrong type), FALSE if otherwise + . = TRUE + if(opened) + if(istype(W, /obj/item/weldingtool)) + if(W.tool_behaviour == TOOL_WELDER) + // eigen check + if(eigen_teleport) + to_chat(user, "The unstable nature of \the [src] makes it impossible to deconstruct!") + return + + if(!W.tool_start_check(user, amount=0)) + return + + to_chat(user, "You begin cutting \the [src] apart...") + if(W.use_tool(src, user, 40, volume=50)) + if(!opened) + return + user.visible_message("[user] slices apart \the [src].", + "You cut \the [src] apart with \the [W].", + "You hear welding.") + deconstruct(TRUE) + return + else // for example cardboard box is cut with wirecutters + user.visible_message("[user] cut apart \the [src].", \ + "You cut \the [src] apart with \the [W].") + deconstruct(TRUE) + return + if(user.transferItemToLoc(W, drop_location())) // so we put in unlit welder too + return + else if(W.tool_behaviour == TOOL_WELDER && can_weld_shut) + // eigen check + if(eigen_teleport) + to_chat(user, "The unstable nature of \the [src] makes it impossible to deconstruct!") + return + if(!W.tool_start_check(user, amount=0)) + return + + to_chat(user, "You begin [welded ? "unwelding":"welding"] \the [src]...") + if(W.use_tool(src, user, 40, volume=50)) + if(opened) + return + welded = !welded + after_weld(welded) + user.visible_message("[user] [welded ? "welds shut" : "unwelded"] \the [src].", + "You [welded ? "weld" : "unwelded"] \the [src] with \the [W].", + "You hear welding.") + log_game("[key_name(user)] [welded ? "welded":"unwelded"] closet [src] with [W] at [AREACOORD(src)]") + update_icon() + else if(W.tool_behaviour == TOOL_WRENCH && anchorable) + if(isinspace() && !anchored) + return + set_anchored(!anchored) + W.play_tool_sound(src, 75) + user.visible_message("[user] [anchored ? "anchored" : "unanchored"] \the [src] [anchored ? "to" : "from"] the ground.", \ + "You [anchored ? "anchored" : "unanchored"] \the [src] [anchored ? "to" : "from"] the ground.", \ + "You hear a ratchet.") + else if(user.a_intent != INTENT_HARM && !(W.item_flags & NOBLUDGEON)) + var/item_is_id = W.GetID() + if(!item_is_id) + return FALSE + if(item_is_id || !toggle(user)) + togglelock(user) + // cit addons + else if(istype(W, /obj/item/electronics/airlock)) + handle_lock_addition(user, W) + else if(W.tool_behaviour == TOOL_SCREWDRIVER) + handle_lock_removal(user, W) + + else + return FALSE + +/obj/structure/closet/proc/after_weld(weld_state) + return + +/obj/structure/closet/MouseDrop_T(atom/movable/O, mob/living/user) + if(!istype(O) || O.anchored || istype(O, /obj/screen)) + return + if(!istype(user) || user.incapacitated() || user.lying) + return + if(!Adjacent(user) || !user.Adjacent(O)) + return + if(user == O) //try to climb onto it + return ..() + if(!opened) + return + if(!isturf(O.loc)) + return + + var/actuallyismob = 0 + if(isliving(O)) + actuallyismob = 1 + else if(!isitem(O)) + return + var/turf/T = get_turf(src) + var/list/targets = list(O, src) + add_fingerprint(user) + user.visible_message("[user] [actuallyismob ? "tries to ":""]stuff [O] into [src].", \ + "You [actuallyismob ? "try to ":""]stuff [O] into [src].", \ + "You hear clanging.") + if(actuallyismob) + if(do_after_mob(user, targets, 40)) + user.visible_message("[user] stuffs [O] into [src].", \ + "You stuff [O] into [src].", \ + "You hear a loud metal bang.") + var/mob/living/L = O + if(!issilicon(L)) + L.DefaultCombatKnockdown(40) + if(istype(src, /obj/structure/closet/supplypod/extractionpod)) + O.forceMove(src) + else + O.forceMove(T) + close() + else + O.forceMove(T) + return 1 + +/obj/structure/closet/relaymove(mob/living/user, direction) + if(user.stat || !isturf(loc)) + return + if(locked) + if(message_cooldown <= world.time) + message_cooldown = world.time + 50 + to_chat(user, "[src]'s door won't budge!") + return + container_resist(user) + +/obj/structure/closet/on_attack_hand(mob/user) + if(user.lying && get_dist(src, user) > 0) + return + + if(!toggle(user)) + togglelock(user) + + +/obj/structure/closet/attack_paw(mob/user) + return attack_hand(user) + +/obj/structure/closet/attack_robot(mob/user) + if(user.Adjacent(src)) + return attack_hand(user) + +// tk grab then use on self +/obj/structure/closet/attack_self_tk(mob/user) + return attack_hand(user) + +/obj/structure/closet/verb/verb_toggleopen() + set src in view(1) + set category = "Object" + set name = "Toggle Open" + + if(!usr.canUseTopic(src, BE_CLOSE) || !isturf(loc)) + return + + if(iscarbon(usr) || issilicon(usr) || isdrone(usr)) + return toggle(usr) + else + to_chat(usr, "This mob type can't use this verb.") + +// Objects that try to exit a locker by stepping were doing so successfully, +// and due to an oversight in turf/Enter() were going through walls. That +// should be independently resolved, but this is also an interesting twist. +/obj/structure/closet/Exit(atom/movable/AM) + open() + if(AM.loc == src) + return FALSE + return TRUE + +/obj/structure/closet/container_resist(mob/living/user) + if(opened) + return + if(ismovable(loc)) + // user.changeNext_move(CLICK_CD_BREAKOUT) + // user.last_special = world.time + CLICK_CD_BREAKOUT + var/atom/movable/AM = loc + AM.relay_container_resist(user, src) + return + if(!welded && !locked) + open() + return + + //okay, so the closet is either welded or locked... resist!!! + // user.changeNext_move(CLICK_CD_BREAKOUT) + // user.last_special = world.time + CLICK_CD_BREAKOUT + user.visible_message("[src] begins to shake violently!", \ + "You lean on the back of [src] and start pushing the door open... (this will take about [DisplayTimeText(breakout_time)].)", \ + "You hear banging from [src].") + if(do_after(user,(breakout_time), target = src, required_mobility_flags = MOBILITY_RESIST)) + if(!user || user.stat != CONSCIOUS || user.loc != src || opened || (!locked && !welded) ) + return + //we check after a while whether there is a point of resisting anymore and whether the user is capable of resisting + user.visible_message("[user] successfully broke out of [src]!", + "You successfully break out of [src]!") + bust_open() + else + if(user.loc == src) //so we don't get the message if we resisted multiple times and succeeded. + to_chat(user, "You fail to break out of [src]!") + /obj/structure/closet/proc/bust_open() welded = FALSE //applies to all lockers locked = FALSE //applies to critter crates and secure lockers only broken = TRUE //applies to secure lockers only open() +/obj/structure/closet/AltClick(mob/user) + ..() + if(!user.canUseTopic(src, BE_CLOSE) || !isturf(loc)) + return + if(opened || !secure) + return + else + togglelock(user) + +/obj/structure/closet/proc/togglelock(mob/living/user, silent) + if(secure && !broken) + if(allowed(user)) + if(iscarbon(user)) + add_fingerprint(user) + locked = !locked + user.visible_message("[user] [locked ? null : "un"]locks [src].", + "You [locked ? null : "un"]lock [src].") + update_icon() + else if(!silent) + to_chat(user, "Access Denied.") + else if(secure && broken) + to_chat(user, "\The [src] is broken!") + +/obj/structure/closet/CtrlShiftClick(mob/living/user) + if(!HAS_TRAIT(user, TRAIT_SKITTISH)) + return ..() + if(!user.canUseTopic(src) || !isturf(user.loc) || !user.Adjacent(src) || !user.CanReach(src)) + return + dive_into(user) + +/obj/structure/closet/emag_act(mob/user) + . = ..() + if(!secure || broken) + return + if(user) + user.visible_message("Sparks fly from [src]!", + "You scramble [src]'s lock, breaking it open!", + "You hear a faint electrical spark.") + playsound(src, "sparks", 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) + broken = TRUE + locked = FALSE + if(!QDELETED(lockerelectronics)) + QDEL_NULL(lockerelectronics) + update_icon() + +/obj/structure/closet/get_remote_view_fullscreens(mob/user) + if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS))) + user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 1) + +/obj/structure/closet/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + if (!(. & EMP_PROTECT_CONTENTS)) + for(var/obj/O in src) + O.emp_act(severity) + if(secure && !broken && !(. & EMP_PROTECT_SELF)) + if(prob(50 / severity)) + locked = !locked + update_icon() + if(prob(20 / severity) && !opened) + if(!locked) + open() + else + req_access = list() + req_access += pick(get_all_accesses()) + if(!QDELETED(lockerelectronics)) + lockerelectronics.accesses = req_access + +/obj/structure/closet/contents_explosion(severity, target) + for(var/atom/A in contents) + A.ex_act(severity, target) + CHECK_TICK + +/obj/structure/closet/singularity_act() + dump_contents() + ..() + +/obj/structure/closet/AllowDrop() + return TRUE + + +/obj/structure/closet/return_temperature() + return + +/obj/structure/closet/proc/dive_into(mob/living/user) + var/turf/T1 = get_turf(user) + var/turf/T2 = get_turf(src) + if(!opened) + if(locked) + togglelock(user, TRUE) + if(!open(user)) + to_chat(user, "It won't budge!") + return + step_towards(user, T2) + T1 = get_turf(user) + if(T1 == T2) + user.set_resting(TRUE, TRUE) + if(!close(user)) + to_chat(user, "You can't get [src] to close!") + user.set_resting(FALSE, TRUE) + return + user.set_resting(FALSE, TRUE) + togglelock(user) + T1.visible_message("[user] dives into [src]!") + +/obj/structure/closet/canReachInto(atom/user, atom/target, list/next, view_only, obj/item/tool) + return (user in src) + +/// cit specific /// + /obj/structure/closet/proc/handle_lock_addition(mob/user, obj/item/electronics/airlock/E) add_fingerprint(user) if(lock_in_use) @@ -336,294 +648,17 @@ update_icon() return TRUE - -/obj/structure/closet/deconstruct(disassembled = TRUE) - if(ispath(material_drop) && material_drop_amount && !(flags_1 & NODECONSTRUCT_1)) - new material_drop(loc, material_drop_amount) - qdel(src) - -/obj/structure/closet/obj_break(damage_flag) - if(!broken && !(flags_1 & NODECONSTRUCT_1)) - bust_open() - -/obj/structure/closet/attackby(obj/item/W, mob/user, params) - if(user in src) - return - if(src.tool_interact(W,user)) - return 1 // No afterattack - else - return ..() - -/obj/structure/closet/proc/tool_interact(obj/item/W, mob/user)//returns TRUE if attackBy call shouldnt be continued (because tool was used/closet was of wrong type), FALSE if otherwise - . = TRUE - if(opened) - if(istype(W, cutting_tool)) - var/welder = FALSE - if(W.tool_behaviour == TOOL_WELDER) - if(!W.tool_start_check(user, amount=0)) - return - to_chat(user, "You begin [welder ? "slicing" : "deconstructing"] \the [src] apart...") - welder = TRUE - if(W.use_tool(src, user, 40, volume=50)) - if(eigen_teleport) - to_chat(user, "The unstable nature of \the [src] makes it impossible to [welder ? "slice" : "deconstruct"]!") - return - if(!opened) - return - user.visible_message("[user] [welder ? "slice" : "deconstruct"]s apart \the [src].", - "You [welder ? "slice" : "deconstruct"] \the [src] apart with \the [W].", - "You hear [welder ? "welding" : "rustling of screws and metal"].") - deconstruct(TRUE) - return - if(user.a_intent != INTENT_HARM && user.transferItemToLoc(W, drop_location())) // so we put in unlit welder too - return TRUE - else if(istype(W, /obj/item/electronics/airlock)) - handle_lock_addition(user, W) - else if(W.tool_behaviour == TOOL_SCREWDRIVER) - handle_lock_removal(user, W) - else if(W.tool_behaviour == TOOL_WELDER && can_weld_shut) - if(!W.tool_start_check(user, amount=0)) - return - - to_chat(user, "You begin [welded ? "unwelding":"welding"] \the [src]...") - if(W.use_tool(src, user, 40, volume=50)) - if(eigen_teleport) - to_chat(user, "The unstable nature of \the [src] makes it impossible to weld!") - return - if(opened) - return - welded = !welded - after_weld(welded) - user.visible_message("[user] [welded ? "welds shut" : "unwelds"] \the [src].", - "You [welded ? "weld" : "unwelded"] \the [src] with \the [W].", - "You hear welding.") - update_icon() - else if(W.tool_behaviour == TOOL_WRENCH && anchorable) - if(isinspace() && !anchored) - return - setAnchored(!anchored) - W.play_tool_sound(src, 75) - user.visible_message("[user] [anchored ? "anchored" : "unanchored"] \the [src] [anchored ? "to" : "from"] the ground.", \ - "You [anchored ? "anchored" : "unanchored"] \the [src] [anchored ? "to" : "from"] the ground.", \ - "You hear a ratchet.") - else if(user.a_intent != INTENT_HARM && !(W.item_flags & NOBLUDGEON)) - if(W.GetID() || !toggle(user)) - togglelock(user) - else +/obj/structure/closet/proc/can_lock(mob/living/user, var/check_access = TRUE) //set check_access to FALSE if you only need to check if a locker has a functional lock rather than access + if(!secure) return FALSE - -/obj/structure/closet/proc/after_weld(weld_state) - return - -/obj/structure/closet/MouseDrop_T(atom/movable/O, mob/living/user) - if(!istype(O) || O.anchored || istype(O, /obj/screen)) - return - if(!istype(user) || user.incapacitated() || user.lying) - return - if(!Adjacent(user) || !user.Adjacent(O)) - return - if(user == O) //try to climb onto it - return ..() - if(!opened) - return - if(!isturf(O.loc)) - return - - var/actuallyismob = 0 - if(isliving(O)) - actuallyismob = 1 - else if(!isitem(O)) - return - var/turf/T = get_turf(src) - var/list/targets = list(O, src) - add_fingerprint(user) - user.visible_message("[user] [actuallyismob ? "tries to ":""]stuff [O] into [src].", \ - "You [actuallyismob ? "try to ":""]stuff [O] into [src].", \ - "You hear clanging.") - if(actuallyismob) - if(do_after_mob(user, targets, 40)) - user.visible_message("[user] stuffs [O] into [src].", \ - "You stuff [O] into [src].", \ - "You hear a loud metal bang.") - var/mob/living/L = O - if(!issilicon(L)) - L.DefaultCombatKnockdown(40) - if(istype(src, /obj/structure/closet/supplypod/extractionpod)) - O.forceMove(src) - else - O.forceMove(T) - close() - else - O.forceMove(T) - return 1 - -/obj/structure/closet/relaymove(mob/user) - if(user.stat || !isturf(loc) || !isliving(user)) - return - if(locked || welded) - if(message_cooldown <= world.time) - message_cooldown = world.time + 50 - to_chat(user, "[src]'s door won't budge!") - return - container_resist(user) - -/obj/structure/closet/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags) - if(user.lying && get_dist(src, user) > 0) - return - - if(!toggle(user)) - togglelock(user) - -/obj/structure/closet/attack_paw(mob/user) - return attack_hand(user) - -/obj/structure/closet/attack_robot(mob/user) - if(user.Adjacent(src)) - return attack_hand(user) - -// tk grab then use on self -/obj/structure/closet/attack_self_tk(mob/user) - return attack_hand(user) - -/obj/structure/closet/verb/verb_toggleopen() - set src in oview(1) - set category = "Object" - set name = "Toggle Open" - - var/mob/living/L = usr - if(!istype(L) || !CHECK_MOBILITY(L, MOBILITY_USE)) + if(broken) + to_chat(user, "[src] is broken!") return FALSE - - if(iscarbon(usr) || issilicon(usr) || isdrone(usr)) - return attack_hand(usr) - else - to_chat(usr, "This mob type can't use this verb.") - -// Objects that try to exit a locker by stepping were doing so successfully, -// and due to an oversight in turf/Enter() were going through walls. That -// should be independently resolved, but this is also an interesting twist. -/obj/structure/closet/Exit(atom/movable/AM) - open() - if(AM.loc == src) - return 0 - return 1 - -/obj/structure/closet/container_resist(mob/living/user) - if(opened) - return - if(ismovable(loc)) - var/atom/movable/AM = loc - AM.relay_container_resist(user, src) - return - if(!welded && !locked) - open() - return - - //okay, so the closet is either welded or locked... resist!!! - user.visible_message("[src] begins to shake violently!", \ - "You lean on the back of [src] and start pushing the door open... (this will take about [DisplayTimeText(breakout_time)].)", \ - "You hear banging from [src].") - if(do_after(user,(breakout_time), target = src, required_mobility_flags = MOBILITY_RESIST)) - if(!user || user.stat != CONSCIOUS || user.loc != src || opened || (!locked && !welded) ) - return - //we check after a while whether there is a point of resisting anymore and whether the user is capable of resisting - user.visible_message("[user] successfully broke out of [src]!", - "You successfully break out of [src]!") - bust_open() - else - if(user.loc == src) //so we don't get the message if we resisted multiple times and succeeded. - to_chat(user, "You fail to break out of [src]!") - -/obj/structure/closet/AltClick(mob/user) - . = ..() - if(!user.canUseTopic(src, be_close=TRUE) || !isturf(loc)) - to_chat(user, "You can't do that right now!") + if(QDELETED(lockerelectronics) && !locked) //We want to be able to unlock it regardless of electronics, but only lockable with electronics + to_chat(user, "[src] is missing locker electronics!") + return FALSE + if(!check_access) return TRUE - togglelock(user) - return TRUE - -/obj/structure/closet/CtrlShiftClick(mob/living/user) - if(!HAS_TRAIT(user, TRAIT_SKITTISH)) - return ..() - if(!user.canUseTopic(src) || !isturf(user.loc) || !user.Adjacent(src) || !user.CanReach(src)) - return - dive_into(user) - -/obj/structure/closet/emag_act(mob/user) - . = ..() - if(!secure || broken) - return - user.visible_message("Sparks fly from [src]!", - "You scramble [src]'s lock, breaking it open!", - "You hear a faint electrical spark.") - playsound(src, "sparks", 50, 1) - broken = TRUE - locked = FALSE - if(!QDELETED(lockerelectronics)) - QDEL_NULL(lockerelectronics) - update_icon() - -/obj/structure/closet/get_remote_view_fullscreens(mob/user) - if(user.stat == DEAD || !(user.sight & (SEEOBJS|SEEMOBS))) - user.overlay_fullscreen("remote_view", /obj/screen/fullscreen/impaired, 1) - -/obj/structure/closet/emp_act(severity) - . = ..() - if(. & EMP_PROTECT_SELF) - return - if (!(. & EMP_PROTECT_CONTENTS)) - for(var/obj/O in src) - O.emp_act(severity) - if(!secure || broken) - return ..() - if(prob(severity/2)) - locked = !locked - update_icon() - if(prob(severity/5) && !opened) - if(!locked) - open() - else - req_access = list() - req_access += pick(get_all_accesses()) - if(!QDELETED(lockerelectronics)) - lockerelectronics.accesses = req_access - -/obj/structure/closet/contents_explosion(severity, target) - for(var/atom/A in contents) - A.ex_act(severity, target) - CHECK_TICK - -/obj/structure/closet/singularity_act() - dump_contents() - ..() - -/obj/structure/closet/AllowDrop() - return TRUE - - -/obj/structure/closet/return_temperature() - return - -/obj/structure/closet/proc/dive_into(mob/living/user) - var/turf/T1 = get_turf(user) - var/turf/T2 = get_turf(src) - if(!opened) - if(locked) - togglelock(user, TRUE) - if(!open(user)) - to_chat(user, "It won't budge!") - return - step_towards(user, T2) - T1 = get_turf(user) - if(T1 == T2) - user.set_resting(TRUE, TRUE) - if(!close(user)) - to_chat(user, "You can't get [src] to close!") - user.set_resting(FALSE, TRUE) - return - user.set_resting(FALSE, TRUE) - togglelock(user) - T1.visible_message("[user] dives into [src]!") - -/obj/structure/closet/canReachInto(atom/user, atom/target, list/next, view_only, obj/item/tool) - return (user in src) + if(allowed(user)) + return TRUE + to_chat(user, "Access denied.") diff --git a/code/game/objects/structures/crates_lockers/crates.dm b/code/game/objects/structures/crates_lockers/crates.dm index ee202f9504..030f7f4edf 100644 --- a/code/game/objects/structures/crates_lockers/crates.dm +++ b/code/game/objects/structures/crates_lockers/crates.dm @@ -17,10 +17,13 @@ material_drop_amount = 5 var/obj/item/paper/fluff/jobs/cargo/manifest/manifest -/obj/structure/closet/crate/New() - ..() +/obj/structure/closet/crate/Initialize() + . = ..() if(icon_state == "[initial(icon_state)]open") opened = TRUE + // AddElement(/datum/element/climbable, climb_time = crate_climb_time * 0.5, climb_stun = 0) + // else + // AddElement(/datum/element/climbable, climb_time = crate_climb_time, climb_stun = 0) update_icon() /obj/structure/closet/crate/CanPass(atom/movable/mover, turf/target) @@ -46,21 +49,16 @@ if(manifest) tear_manifest(user) -/obj/structure/closet/crate/tool_interact(obj/item/W, mob/user) - if(W.tool_behaviour == TOOL_WIRECUTTER && manifest) - tear_manifest(user) - return TRUE - return ..() - -/obj/structure/closet/crate/open(mob/living/user) +/obj/structure/closet/crate/open(mob/living/user, force = FALSE) . = ..() if(. && manifest) to_chat(user, "The manifest is torn off [src].") - playsound(src, 'sound/items/poster_ripped.ogg', 75, 1) + playsound(src, 'sound/items/poster_ripped.ogg', 75, TRUE) manifest.forceMove(get_turf(src)) manifest = null update_icon() +// cit specific /obj/structure/closet/crate/handle_lock_addition() return @@ -69,7 +67,7 @@ /obj/structure/closet/crate/proc/tear_manifest(mob/user) to_chat(user, "You tear the manifest off of [src].") - playsound(src, 'sound/items/poster_ripped.ogg', 75, 1) + playsound(src, 'sound/items/poster_ripped.ogg', 75, TRUE) manifest.forceMove(loc) if(ishuman(user)) diff --git a/code/game/objects/structures/crates_lockers/crates/secure.dm b/code/game/objects/structures/crates_lockers/crates/secure.dm index 2c923c1905..eb652c5180 100644 --- a/code/game/objects/structures/crates_lockers/crates/secure.dm +++ b/code/game/objects/structures/crates_lockers/crates/secure.dm @@ -80,16 +80,25 @@ name = "private crate" desc = "A crate cover designed to only open for who purchased its contents." icon_state = "privatecrate" + ///Account of the person buying the crate if private purchasing. var/datum/bank_account/buyer_account + ///Department of the person buying the crate if buying via the NIRN app. + var/datum/bank_account/department/department_account + ///Is the secure crate opened or closed? var/privacy_lock = TRUE + ///Is the crate being bought by a person, or a budget card? + var/department_purchase = FALSE /obj/structure/closet/crate/secure/owned/examine(mob/user) . = ..() - to_chat(user, "It's locked with a privacy lock, and can only be unlocked by the buyer's ID.") + . += "It's locked with a privacy lock, and can only be unlocked by the buyer's ID." /obj/structure/closet/crate/secure/owned/Initialize(mapload, datum/bank_account/_buyer_account) . = ..() buyer_account = _buyer_account + if(istype(buyer_account, /datum/bank_account/department)) + department_purchase = TRUE + department_account = buyer_account /obj/structure/closet/crate/secure/owned/togglelock(mob/living/user, silent) if(privacy_lock) @@ -97,7 +106,7 @@ var/obj/item/card/id/id_card = user.get_idcard(TRUE) if(id_card) if(id_card.registered_account) - if(id_card.registered_account == buyer_account) + if(id_card.registered_account == buyer_account || (department_purchase && (id_card.registered_account?.account_job?.paycheck_department) == (department_account.department_id))) if(iscarbon(user)) add_fingerprint(user) locked = !locked diff --git a/code/modules/antagonists/changeling/powers/biodegrade.dm b/code/modules/antagonists/changeling/powers/biodegrade.dm index db4e47aa78..8b3b1659d1 100644 --- a/code/modules/antagonists/changeling/powers/biodegrade.dm +++ b/code/modules/antagonists/changeling/powers/biodegrade.dm @@ -12,24 +12,34 @@ /obj/effect/proc_holder/changeling/biodegrade/sting_action(mob/living/carbon/human/user) var/used = FALSE // only one form of shackles removed per use - if(!user.restrained() && isopenturf(user.loc)) + if(!user.restrained() && !user.legcuffed && isopenturf(user.loc)) to_chat(user, "We are already free!") - return 0 + return FALSE if(user.handcuffed) var/obj/O = user.get_item_by_slot(SLOT_HANDCUFFED) if(!istype(O)) - return 0 + return FALSE user.visible_message("[user] vomits a glob of acid on [user.p_their()] [O]!", \ "We vomit acidic ooze onto our restraints!") addtimer(CALLBACK(src, .proc/dissolve_handcuffs, user, O), 30) used = TRUE + if(user.legcuffed) + var/obj/O = user.get_item_by_slot(SLOT_LEGCUFFED) + if(!istype(O)) + return FALSE + user.visible_message("[user] vomits a glob of acid on [user.p_their()] [O]!", \ + "We vomit acidic ooze onto our restraints!") + + addtimer(CALLBACK(src, .proc/dissolve_legcuffs, user, O), 30) + used = TRUE + if(user.wear_suit && user.wear_suit.breakouttime && !used) var/obj/item/clothing/suit/S = user.get_item_by_slot(SLOT_WEAR_SUIT) if(!istype(S)) - return 0 + return FALSE user.visible_message("[user] vomits a glob of acid across the front of [user.p_their()] [S]!", \ "We vomit acidic ooze onto our straight jacket!") addtimer(CALLBACK(src, .proc/dissolve_straightjacket, user, S), 30) @@ -39,7 +49,7 @@ if(istype(user.loc, /obj/structure/closet) && !used) var/obj/structure/closet/C = user.loc if(!istype(C)) - return 0 + return FALSE C.visible_message("[C]'s hinges suddenly begin to melt and run!") to_chat(user, "We vomit acidic goop onto the interior of [C]!") addtimer(CALLBACK(src, .proc/open_closet, user, C), 70) @@ -48,7 +58,7 @@ if(istype(user.loc, /obj/structure/spider/cocoon) && !used) var/obj/structure/spider/cocoon/C = user.loc if(!istype(C)) - return 0 + return FALSE C.visible_message("[src] shifts and starts to fall apart!") to_chat(user, "We secrete acidic enzymes from our skin and begin melting our cocoon...") addtimer(CALLBACK(src, .proc/dissolve_cocoon, user, C), 25) //Very short because it's just webs @@ -62,6 +72,12 @@ new /obj/effect/decal/cleanable/greenglow(O.drop_location()) qdel(O) +/obj/effect/proc_holder/changeling/biodegrade/proc/dissolve_legcuffs(mob/living/carbon/human/user, obj/O) + if(O && user.legcuffed == O) + user.visible_message("[O] dissolve[O.gender==PLURAL?"":"s"] into a puddle of sizzling goop.") + new /obj/effect/decal/cleanable/greenglow(O.drop_location()) + qdel(O) + /obj/effect/proc_holder/changeling/biodegrade/proc/dissolve_straightjacket(mob/living/carbon/human/user, obj/S) if(S && user.wear_suit == S) user.visible_message("[S] dissolves into a puddle of sizzling goop.") diff --git a/code/modules/atmospherics/machinery/portable/canister.dm b/code/modules/atmospherics/machinery/portable/canister.dm index 9c3e0ef64a..90f8680da9 100644 --- a/code/modules/atmospherics/machinery/portable/canister.dm +++ b/code/modules/atmospherics/machinery/portable/canister.dm @@ -202,8 +202,9 @@ filled = 1 release_pressure = ONE_ATMOSPHERE*2 -/obj/machinery/portable_atmospherics/canister/Initialize(mapload, datum/gas_mixture/existing_mixture) - . = ..() +/obj/machinery/portable_atmospherics/canister/New(loc, datum/gas_mixture/existing_mixture) + ..() + if(existing_mixture) air_contents.copy_from(existing_mixture) else @@ -221,10 +222,8 @@ air_contents.set_temperature(starter_temp) /obj/machinery/portable_atmospherics/canister/air/create_gas() - var/oh_two = /datum/gas/oxygen - var/dihydrogen = /datum/gas/nitrogen //how to piss of chemists - air_contents.set_moles(oh_two, (O2STANDARD * maximum_pressure * filled) * air_contents.return_volume() / (R_IDEAL_GAS_EQUATION * air_contents.return_temperature())) - air_contents.set_moles(dihydrogen, (N2STANDARD * maximum_pressure * filled) * air_contents.return_volume() / (R_IDEAL_GAS_EQUATION * air_contents.return_temperature())) + air_contents.set_moles(/datum/gas/oxygen, (O2STANDARD * maximum_pressure * filled) * air_contents.return_volume() / (R_IDEAL_GAS_EQUATION * air_contents.return_temperature())) + air_contents.set_moles(/datum/gas/nitrogen, (N2STANDARD * maximum_pressure * filled) * air_contents.return_volume() / (R_IDEAL_GAS_EQUATION * air_contents.return_temperature())) /obj/machinery/portable_atmospherics/canister/update_icon_state() if(stat & BROKEN) diff --git a/code/modules/cargo/blackmarket/blackmarket_item.dm b/code/modules/cargo/blackmarket/blackmarket_item.dm new file mode 100644 index 0000000000..53c0004a7f --- /dev/null +++ b/code/modules/cargo/blackmarket/blackmarket_item.dm @@ -0,0 +1,51 @@ +/datum/blackmarket_item + var/name // Name for the item entry used in the uplink. + var/desc // Description for the item entry used in the uplink. + var/category // The category this item belongs to, should be already declared in the market that this item is accessible in. + var/list/markets = list(/datum/blackmarket_market/blackmarket) // "/datum/blackmarket_market"s that this item should be in, used by SSblackmarket on init. + + var/price // Price for the item, if not set creates a price according to the *_min and *_max vars. + var/stock // How many of this type of item is available, if not set creates a price according to the *_min and *_max vars. + + var/item // Path to or the item itself what this entry is for, this should be set even if you override spawn_item to spawn your item. + + var/price_min = 0 // Minimum price for the item if generated randomly. + var/price_max = 0 // Maximum price for the item if generated randomly. + var/stock_min = 1 // Minimum amount that there should be of this item in the market if generated randomly. This defaults to 1 as most items will have it as 1. + var/stock_max = 0 // Maximum amount that there should be of this item in the market if generated randomly. + var/availability_prob = 0 // Probability for this item to be available. Used by SSblackmarket on init. + +/datum/blackmarket_item/New() + if(isnull(price)) + price = rand(price_min, price_max) + if(isnull(stock)) + stock = rand(stock_min, stock_max) + +/datum/blackmarket_item/proc/spawn_item(loc) // Used for spawning the wanted item, override if you need to do something special with the item. + return new item(loc) + +/datum/blackmarket_item/proc/buy(obj/item/blackmarket_uplink/uplink, mob/buyer, shipping_method) // Buys the item and makes SSblackmarket handle it. + // Sanity + if(!istype(uplink) || !istype(buyer)) + return FALSE + if(!item || stock <= 0) // This shouldn't be able to happen unless there was some manipulation or admin fuckery. + return FALSE + var/datum/blackmarket_purchase/purchase = new(src, uplink, shipping_method) // Alright, the item has been purchased. + if(SSblackmarket.queue_item(purchase)) // SSblackmarket takes care of the shipping. + stock-- + log_game("[key_name(buyer)] has succesfully purchased [name] using [shipping_method] for shipping.") + return TRUE + return FALSE + +/datum/blackmarket_purchase // This exists because it is easier to keep track of all the vars this way. + var/datum/blackmarket_item/entry // The entry being purchased. + var/item // Instance of the item being sent. + var/obj/item/blackmarket_uplink/uplink // The uplink where this purchase was done from. + var/method // Shipping method used to buy this item. + +/datum/blackmarket_purchase/New(_entry, _uplink, _method) + entry = _entry + if(!ispath(entry.item)) + item = entry.item + uplink = _uplink + method = _method diff --git a/code/modules/cargo/blackmarket/blackmarket_items/clothing.dm b/code/modules/cargo/blackmarket/blackmarket_items/clothing.dm new file mode 100644 index 0000000000..a6ee19e38d --- /dev/null +++ b/code/modules/cargo/blackmarket/blackmarket_items/clothing.dm @@ -0,0 +1,55 @@ +/datum/blackmarket_item/clothing + category = "Clothing" + +/datum/blackmarket_item/clothing/ninja_mask + name = "Space Ninja Mask" + desc = "Apart from being acid, lava, fireproof and being hard to take off someone it does nothing special on it's own." + item = /obj/item/clothing/mask/gas/space_ninja + price_min = 200 + price_max = 500 + stock_max = 3 + availability_prob = 40 + +/datum/blackmarket_item/clothing/durathread_vest + name = "Durathread Vest" + desc = "Dont let them tell you this stuff is \"Like asbestos\" or \"Pulled from the market for safety concerns\". It could be the difference between a robusting and a retaliation." + item = /obj/item/clothing/suit/armor/vest/durathread + price_min = 200 + price_max = 400 + stock_max = 4 + availability_prob = 50 + +/datum/blackmarket_item/clothing/durathread_helmet + name = "Durathread Helmet" + desc = "Customers ask why it's called a helmet when it's just made from armoured fabric and I always say the same thing: No refunds." + item = /obj/item/clothing/head/helmet/durathread + price_min = 100 + price_max = 200 + stock_max = 4 + availability_prob = 50 + +/datum/blackmarket_item/clothing/full_spacesuit_set + name = "Nanotrasen Branded Spacesuit Box" + desc = "A few boxes of \"Old Style\" space suits fell off the back of a space truck." + item = /obj/item/storage/box + price_min = 1500 + price_max = 4000 + stock_max = 3 + availability_prob = 30 + +/datum/blackmarket_item/clothing/full_spacesuit_set/spawn_item(loc) + var/obj/item/storage/box/B = ..() + B.name = "Spacesuit Box" + B.desc = "It has a NT logo on it." + new /obj/item/clothing/suit/space(B) + new /obj/item/clothing/head/helmet/space(B) + return B + +/datum/blackmarket_item/clothing/chameleon_hat + name = "Chameleon Hat" + desc = "Pick any hat you want with this Handy device. Not Quality Tested." + item = /obj/item/clothing/head/chameleon/broken + price_min = 100 + price_max = 200 + stock_max = 2 + availability_prob = 70 diff --git a/code/modules/cargo/blackmarket/blackmarket_items/consumables.dm b/code/modules/cargo/blackmarket/blackmarket_items/consumables.dm new file mode 100644 index 0000000000..469c467c2f --- /dev/null +++ b/code/modules/cargo/blackmarket/blackmarket_items/consumables.dm @@ -0,0 +1,58 @@ +/datum/blackmarket_item/consumable + category = "Consumables" + +/datum/blackmarket_item/consumable/clown_tears + name = "Bowl of Clown's Tears" + desc = "Guaranteed fresh from Weepy Boggins Tragic Kitchen" + item = /obj/item/reagent_containers/food/snacks/soup/clownstears + stock = 1 + price_min = 520 + price_max = 600 + availability_prob = 10 + +/datum/blackmarket_item/consumable/donk_pocket_box + name = "Box of Donk Pockets" + desc = "A well packaged box containing the favourite snack of every spacefarer." + item = /obj/item/storage/box/donkpockets + stock_min = 2 + stock_max = 5 + price_min = 325 + price_max = 400 + availability_prob = 80 + +/datum/blackmarket_item/consumable/suspicious_pills + name = "Bottle of Suspicious Pills" + desc = "A random cocktail of luxury drugs that are sure to put a smile on your face!" + item = /obj/item/storage/pill_bottle + stock_min = 2 + stock_max = 3 + price_min = 400 + price_max = 700 + availability_prob = 50 + +/datum/blackmarket_item/consumable/suspicious_pills/spawn_item(loc) + var/pillbottle = pick(list(/obj/item/storage/pill_bottle/zoom, + /obj/item/storage/pill_bottle/happy, + /obj/item/storage/pill_bottle/lsd, + /obj/item/storage/pill_bottle/aranesp, + /obj/item/storage/pill_bottle/stimulant)) + return new pillbottle(loc) + +/datum/blackmarket_item/consumable/floor_pill + name = "Strange Pill" + desc = "The Russian Roulette of the Maintenance Tunnels." + item = /obj/item/reagent_containers/pill/floorpill + stock_min = 5 + stock_max = 35 + price_min = 10 + price_max = 60 + availability_prob = 50 + +/datum/blackmarket_item/consumable/pumpup + name = "Shoddy Stimulants" + desc = "Feel the energy inside each needle!" + item = /obj/item/reagent_containers/hypospray/medipen/stimpack + stock_max = 5 + price_min = 50 + price_max = 150 + availability_prob = 90 diff --git a/code/modules/cargo/blackmarket/blackmarket_items/misc.dm b/code/modules/cargo/blackmarket/blackmarket_items/misc.dm new file mode 100644 index 0000000000..474be8d29c --- /dev/null +++ b/code/modules/cargo/blackmarket/blackmarket_items/misc.dm @@ -0,0 +1,53 @@ +/datum/blackmarket_item/misc + category = "Miscellaneous" + +/datum/blackmarket_item/misc/cap_gun + name = "Cap Gun" + desc = "Prank your friends with this harmless gun! Harmlessness guranteed." + item = /obj/item/toy/gun + price_min = 50 + price_max = 200 + stock_max = 6 + availability_prob = 80 + +/datum/blackmarket_item/misc/shoulder_holster + name = "Shoulder holster" + desc = "Yeehaw, hardboiled friends! This holster is the first step in your dream of becoming a detective and being allowed to shoot real guns!" + item = /obj/item/storage/belt/holster + price_min = 400 + price_max = 800 + stock_max = 8 + availability_prob = 60 + +/datum/blackmarket_item/misc/holywater + name = "Flask of holy water" + desc = "Father Lootius' own brand of ready-made holy water." + item = /obj/item/reagent_containers/food/drinks/bottle/holywater + price_min = 400 + price_max = 600 + stock_max = 3 + availability_prob = 40 + +/datum/blackmarket_item/misc/holywater/spawn_item(loc) + if (prob(6.66)) + return new /obj/item/reagent_containers/glass/beaker/unholywater(loc) + return ..() + +/datum/blackmarket_item/misc/strange_seed + name = "Strange Seeds" + desc = "An Exotic Variety of seed that can contain anything from glow to acid." + item = /obj/item/seeds/random + price_min = 320 + price_max = 360 + stock_min = 2 + stock_max = 5 + availability_prob = 50 + +/datum/blackmarket_item/misc/smugglers_satchel + name = "Smuggler's Satchel" + desc = "This easily hidden satchel can become a versatile tool to anybody with the desire to keep certain items out of sight and out of mind." + item = /obj/item/storage/backpack/satchel/flat + price_min = 750 + price_max = 1000 + stock_max = 2 + availability_prob = 30 diff --git a/code/modules/cargo/blackmarket/blackmarket_items/tools.dm b/code/modules/cargo/blackmarket/blackmarket_items/tools.dm new file mode 100644 index 0000000000..3da10b16fd --- /dev/null +++ b/code/modules/cargo/blackmarket/blackmarket_items/tools.dm @@ -0,0 +1,74 @@ +/datum/blackmarket_item/tool + category = "Tools" + +/datum/blackmarket_item/tool/caravan_wrench + name = "Experimental Wrench" + desc = "The extra fast and handy wrench you always wanted!" + item = /obj/item/wrench/caravan + stock = 1 + price_min = 400 + price_max = 800 + availability_prob = 20 + +/datum/blackmarket_item/tool/caravan_wirecutters + name = "Experimental Wirecutters" + desc = "The extra fast and handy wirecutters you always wanted!" + item = /obj/item/wirecutters/caravan + stock = 1 + price_min = 400 + price_max = 800 + availability_prob = 20 + +/datum/blackmarket_item/tool/caravan_screwdriver + name = "Experimental Screwdriver" + desc = "The extra fast and handy screwdriver you always wanted!" + item = /obj/item/screwdriver/caravan + stock = 1 + price_min = 400 + price_max = 800 + availability_prob = 20 + +/datum/blackmarket_item/tool/caravan_crowbar + name = "Experimental Crowbar" + desc = "The extra fast and handy crowbar you always wanted!" + item = /obj/item/crowbar/red/caravan + stock = 1 + price_min = 400 + price_max = 800 + availability_prob = 20 + +/datum/blackmarket_item/tool/binoculars + name = "Binoculars" + desc = "Increase your sight by 150% with this handy tool!" + item = /obj/item/binoculars + stock = 1 + price_min = 400 + price_max = 960 + availability_prob = 30 + +/datum/blackmarket_item/tool/riot_shield + name = "Riot Shield" + desc = "Protect yourself from an unexpected riot at your local police department!" + item = /obj/item/shield/riot + price_min = 450 + price_max = 650 + stock_max = 2 + availability_prob = 50 + +/datum/blackmarket_item/tool/thermite_bottle + name = "Thermite Bottle" + desc = "30 units of Thermite to assist in creating a quick access point or get away!" + item = /obj/item/reagent_containers/glass/bottle/thermite + price_min = 500 + price_max = 1500 + stock_max = 3 + availability_prob = 30 + +/datum/blackmarket_item/tool/science_goggles + name = "Science Goggles" + desc = "These glasses scan the contents of containers and projects their contents to the user in an easy-to-read format." + item = /obj/item/clothing/glasses/science + price_min = 150 + price_max = 200 + stock_max = 3 + availability_prob = 50 diff --git a/code/modules/cargo/blackmarket/blackmarket_items/weapons.dm b/code/modules/cargo/blackmarket/blackmarket_items/weapons.dm new file mode 100644 index 0000000000..4978d688ae --- /dev/null +++ b/code/modules/cargo/blackmarket/blackmarket_items/weapons.dm @@ -0,0 +1,41 @@ +/datum/blackmarket_item/weapon + category = "Weapons" + +/datum/blackmarket_item/weapon/bear_trap + name = "Bear Trap" + desc = "Get the janitor back at his own game with this affordable prank kit." + item = /obj/item/restraints/legcuffs/beartrap + price_min = 300 + price_max = 550 + stock_max = 3 + availability_prob = 40 + +/datum/blackmarket_item/weapon/shotgun_dart + name = "Shotgun Dart" + desc = "These handy darts can be filled up with any chemical and be shot with a shotgun! \ + Prank your friends by shooting them with laughter! \ + Not recommended for commercial use." + item = /obj/item/ammo_casing/shotgun/dart + price_min = 10 + price_max = 50 + stock_min = 5 + stock_max = 30 + availability_prob = 40 + +/datum/blackmarket_item/weapon/bone_spear + name = "Bone Spear" + desc = "Authentic tribal spear, made from real bones! A steal at any price, especially if you're a caveman." + item = /obj/item/spear/bonespear + price_min = 200 + price_max = 300 + stock_max = 3 + availability_prob = 60 + +/datum/blackmarket_item/weapon/emp_grenade + name = "EMP Grenade" + desc = "Use this grenade for SHOCKING results!" + item = /obj/item/grenade/empgrenade + price_min = 100 + price_max = 400 + stock_max = 2 + availability_prob = 50 diff --git a/code/modules/cargo/blackmarket/blackmarket_market.dm b/code/modules/cargo/blackmarket/blackmarket_market.dm new file mode 100644 index 0000000000..ee95c82d77 --- /dev/null +++ b/code/modules/cargo/blackmarket/blackmarket_market.dm @@ -0,0 +1,40 @@ +/datum/blackmarket_market + var/name = "huh?" // Name for the market. + + var/list/shipping // Available shipping methods and prices, just leave the shipping method out that you don't want to have. + + // Automatic vars, do not touch these. + var/list/available_items = list() // Items available from this market, populated by SSblackmarket on initialization. + var/list/categories = list() // Item categories available from this market, only items which are in these categories can be gotten from this market. + +/datum/blackmarket_market/proc/add_item(datum/blackmarket_item/item) // Adds item to the available items and add it's category if it is not in categories yet. + if(!prob(initial(item.availability_prob))) + return FALSE + if(ispath(item)) + item = new item() + if(!(item.category in categories)) + categories += item.category + available_items[item.category] = list() + available_items[item.category] += item + return TRUE + +/datum/blackmarket_market/proc/purchase(item, category, method, obj/item/blackmarket_uplink/uplink, user) // Handles buying the item, this is mainly for future use and moving the code away from the uplink. + if(!istype(uplink) || !(method in shipping)) + return FALSE + for(var/datum/blackmarket_item/I in available_items[category]) + if(I.type != item) + continue + var/price = I.price + shipping[method] + if(uplink.money < price) // I can't get the price of the item and shipping in a clean way to the UI, so I have to do this. + to_chat("You don't have enough credits in [uplink] for [I] with [method] shipping.") + return FALSE + if(I.buy(uplink, user, method)) + uplink.money -= price + return TRUE + return FALSE + +/datum/blackmarket_market/blackmarket + name = "Black Market" + shipping = list(SHIPPING_METHOD_LTSRBT =50, + SHIPPING_METHOD_LAUNCH =10, + SHIPPING_METHOD_TELEPORT=75) diff --git a/code/modules/cargo/blackmarket/blackmarket_telepad.dm b/code/modules/cargo/blackmarket/blackmarket_telepad.dm new file mode 100644 index 0000000000..eb986a443a --- /dev/null +++ b/code/modules/cargo/blackmarket/blackmarket_telepad.dm @@ -0,0 +1,92 @@ +/obj/item/circuitboard/machine/ltsrbt + name = "LTSRBT (Machine Board)" + icon_state = "bluespacearray" + build_path = /obj/machinery/ltsrbt + req_components = list( + /obj/item/stack/ore/bluespace_crystal = 2, + /obj/item/stock_parts/subspace/ansible = 1, + /obj/item/stock_parts/micro_laser = 1, + /obj/item/stock_parts/scanning_module = 2) + def_components = list(/obj/item/stack/ore/bluespace_crystal = /obj/item/stack/ore/bluespace_crystal/artificial) + +/obj/machinery/ltsrbt + name = "Long-To-Short-Range-Bluespace-Transciever" + desc = "The LTSRBT is a compact teleportation machine for recieving and sending items outside the station and inside the station.\nUsing teleportation frequencies stolen from NT it is near undetectable.\nEssential for any illegal market operations on NT stations.\n" + icon_state = "exonet_node" + circuit = /obj/item/circuitboard/machine/ltsrbt + density = TRUE + idle_power_usage = 200 + var/power_efficiency = 1 // Divider for power_usage_per_teleport. + var/power_usage_per_teleport = 10000 // Power used per teleported which gets divided by power_efficiency. + var/recharge_time = 0 // The time it takes for the machine to recharge before being able to send or recieve items. + var/recharge_cooldown = 0 // Current recharge progress. + var/base_recharge_time = 100 // Base recharge time which is used to get recharge_time. + var/recieving // Current /datum/blackmarket_purchase being recieved. + var/transmitting // Current /datum/blackmarket_purchase being sent to the target uplink. + var/list/datum/blackmarket_purchase/queue = list() // Queue for purchases that the machine should recieve and send. + +/obj/machinery/ltsrbt/Initialize() + . = ..() + SSblackmarket.telepads += src + +/obj/machinery/ltsrbt/Destroy() + SSblackmarket.telepads -= src + if(SSblackmarket.telepads.len) // Bye bye orders. + for(var/datum/blackmarket_purchase/P in queue) + SSblackmarket.queue_item(P) + . = ..() + +/obj/machinery/ltsrbt/RefreshParts() + recharge_time = base_recharge_time + for(var/obj/item/stock_parts/scanning_module/scan in component_parts) // On tier 4 recharge_time should be 20 and by default it is 80 as scanning modules should be tier 1. + recharge_time -= scan.rating * 10 + recharge_cooldown = recharge_time + power_efficiency = 0 + for(var/obj/item/stock_parts/micro_laser/laser in component_parts) + power_efficiency += laser.rating + if(!power_efficiency) // Shouldn't happen but you never know. + power_efficiency = 1 + +/obj/machinery/ltsrbt/proc/add_to_queue(datum/blackmarket_purchase/purchase) // Adds /datum/blackmarket_purchase to queue unless the machine is free, then it sets the purchase to be instantly recieved + if(!recharge_cooldown && !recieving && !transmitting) + recieving = purchase + return + queue += purchase + +/obj/machinery/ltsrbt/process() + if(stat & NOPOWER) + return + if(recharge_cooldown) + recharge_cooldown-- + return + var/turf/T = get_turf(src) + if(recieving) + var/datum/blackmarket_purchase/P = recieving + if(!P.item || ispath(P.item)) + P.item = P.entry.spawn_item(T) + else + var/atom/movable/M = P.item + M.forceMove(T) + use_power(power_usage_per_teleport / power_efficiency) + var/datum/effect_system/spark_spread/sparks = new + sparks.set_up(5, 1, get_turf(src)) + sparks.attach(P.item) + sparks.start() + recieving = null + transmitting = P + recharge_cooldown = recharge_time + return + else if(transmitting) + var/datum/blackmarket_purchase/P = transmitting + if(!P.item) + QDEL_NULL(transmitting) + if(!(P.item in T.contents)) + QDEL_NULL(transmitting) + return + do_teleport(P.item, get_turf(P.uplink)) + use_power(power_usage_per_teleport / power_efficiency) + QDEL_NULL(transmitting) + recharge_cooldown = recharge_time + return + if(queue.len) + recieving = pick_n_take(queue) diff --git a/code/modules/cargo/blackmarket/blackmarket_uplink.dm b/code/modules/cargo/blackmarket/blackmarket_uplink.dm new file mode 100644 index 0000000000..bcbed59b38 --- /dev/null +++ b/code/modules/cargo/blackmarket/blackmarket_uplink.dm @@ -0,0 +1,141 @@ +/obj/item/blackmarket_uplink + name = "Black Market Uplink" + icon = 'icons/obj/blackmarket.dmi' + icon_state = "uplink" + // UI variables. + var/ui_x = 720 + var/ui_y = 480 + var/viewing_category + var/viewing_market + var/selected_item + var/buying + var/money = 0 // How much money is inserted into the uplink. + var/list/accessible_markets = list(/datum/blackmarket_market/blackmarket) // List of typepaths for "/datum/blackmarket_market"s that this uplink can access. + +/obj/item/blackmarket_uplink/Initialize() + . = ..() + if(accessible_markets.len) + viewing_market = accessible_markets[1] + var/list/categories = SSblackmarket.markets[viewing_market].categories + if(categories && categories.len) + viewing_category = categories[1] + +/obj/item/blackmarket_uplink/attackby(obj/item/I, mob/user, params) + if(istype(I, /obj/item/holochip) || istype(I, /obj/item/stack/spacecash) || istype(I, /obj/item/coin)) + var/worth = I.get_item_credit_value() + if(!worth) + to_chat(user, "[I] doesn't seem to be worth anything!") + money += worth + to_chat(user, "You slot [I] into [src] and it reports a total of [money] credits inserted.") + qdel(I) + return + . = ..() + +/obj/item/blackmarket_uplink/AltClick(mob/user) + if(!isliving(user) || !user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + var/amount_to_remove = FLOOR(input(user, "How much do you want to withdraw? Current Amount: [money]", "Withdraw Funds", 5) as num|null, 1) + if(!user.canUseTopic(src, BE_CLOSE, FALSE, NO_TK)) + return + if(!amount_to_remove || amount_to_remove < 0) + return + if(amount_to_remove > money) + to_chat(user, "There is only [money] credits in [src]") + return + var/obj/item/holochip/holochip = new (user.drop_location(), amount_to_remove) + money -= amount_to_remove + holochip.name = "washed " + holochip.name + user.put_in_hands(holochip) + to_chat(user, "You withdraw [amount_to_remove] credits into a holochip.") + +/obj/item/blackmarket_uplink/ui_interact(mob/user, ui_key = "BlackMarketUplink", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/ui_state/state = GLOB.default_state) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, ui_key, name, ui_x, ui_y, master_ui, state) + ui.open() + +/obj/item/blackmarket_uplink/ui_data(mob/user) + var/list/data = list() + var/datum/blackmarket_market/market = viewing_market ? SSblackmarket.markets[viewing_market] : null + data["categories"] = market ? market.categories : null + data["delivery_methods"] = list() + if(market) + for(var/delivery in market.shipping) + data["delivery_methods"] += list(list("name" = delivery, "price" = market.shipping[delivery])) + data["money"] = money + data["buying"] = buying + data["items"] = list() + data["viewing_category"] = viewing_category + data["viewing_market"] = viewing_market + if(viewing_category && market) + if(market.available_items[viewing_category]) + for(var/datum/blackmarket_item/I in market.available_items[viewing_category]) + data["items"] += list(list( + "id" = I.type, + "name" = I.name, + "cost" = I.price, + "amount" = I.stock, + "desc" = I.desc || I.name + )) + return data + +/obj/item/blackmarket_uplink/ui_static_data(mob/user) + var/list/data = list() + data["delivery_method_description"] = SSblackmarket.shipping_method_descriptions + data["ltsrbt_built"] = SSblackmarket.telepads.len + data["markets"] = list() + for(var/M in accessible_markets) + var/datum/blackmarket_market/BM = SSblackmarket.markets[M] + data["markets"] += list(list( + "id" = M, + "name" = BM.name + )) + return data + +/obj/item/blackmarket_uplink/ui_act(action, params) + if(..()) + return + switch(action) + if("set_category") + if(isnull(params["category"])) + return + if(isnull(viewing_market)) + return + if(!(params["category"] in SSblackmarket.markets[viewing_market].categories)) + return + viewing_category = params["category"] + . = TRUE + if("set_market") + if(isnull(params["market"])) + return + var/market = text2path(params["market"]) + if(!(market in accessible_markets)) + return + viewing_market = market + var/list/categories = SSblackmarket.markets[viewing_market].categories + if(categories && categories.len) + viewing_category = categories[1] + else + viewing_category = null + . = TRUE + if("select") + if(isnull(params["item"])) + return + var/item = text2path(params["item"]) + selected_item = item + buying = TRUE + . = TRUE + if("cancel") + selected_item = null + buying = FALSE + . = TRUE + if("buy") + if(isnull(params["method"])) + return + if(isnull(selected_item)) + buying = FALSE + return + var/datum/blackmarket_market/market = SSblackmarket.markets[viewing_market] + market.purchase(selected_item, viewing_category, params["method"], src, usr) + buying = FALSE + selected_item = null diff --git a/code/modules/cargo/order.dm b/code/modules/cargo/order.dm index 4fa6a4eade..ad9fa2018e 100644 --- a/code/modules/cargo/order.dm +++ b/code/modules/cargo/order.dm @@ -16,7 +16,7 @@ errors |= MANIFEST_ERROR_ITEM /obj/item/paper/fluff/jobs/cargo/manifest/proc/is_approved() - return stamped && stamped.len && !is_denied() + return stamped?.len && !is_denied() /obj/item/paper/fluff/jobs/cargo/manifest/proc/is_denied() return stamped && ("stamp-deny" in stamped) @@ -49,6 +49,7 @@ P.info += "

[station_name()] Supply Requisition

" P.info += "
" P.info += "Order #[id]
" + P.info += "Time of Order: [STATION_TIME_TIMESTAMP("hh:mm:ss", world.time)]
" P.info += "Item: [pack.name]
" P.info += "Access Restrictions: [get_access_desc(pack.access)]
" P.info += "Requested by: [orderer]
" @@ -68,10 +69,10 @@ P.name = "shipping manifest - [packname?"#[id] ([pack.name])":"(Grouped Item Crate)"]" P.info += "

[command_name()] Shipping Manifest

" P.info += "
" - if(id && !(id == "Cargo")) + if(owner && !(owner == "Cargo")) P.info += "Direct purchase from [owner]
" P.name += " - Purchased by [owner]" - P.info += "Order #[id]
" + P.info += "Order[packname?"":"s"]: [id]
" P.info += "Destination: [station_name]
" if(packname) P.info += "Item: [packname]
" @@ -82,8 +83,11 @@ var/obj/structure/closet/C = container ignore_this += C.lockerelectronics for(var/atom/movable/AM in container.contents - ignore_this) - if((P.errors & MANIFEST_ERROR_CONTENTS) && prob(50)) - continue + if((P.errors & MANIFEST_ERROR_CONTENTS)) + if(prob(50)) + P.info += "
  • [AM.name]
  • " + else + continue P.info += "
  • [AM.name]
  • " P.info += "" P.info += "

    Stamp below to confirm receipt of goods:

    " @@ -94,7 +98,7 @@ /obj/structure/closet/crate/large, /obj/structure/closet/secure_closet/goodies )) - if(blacklisted_error[container.type]) + if(is_type_in_list(container, blacklisted_error)) P.errors &= ~MANIFEST_ERROR_ITEM else var/lost = max(round(container.contents.len / 10), 1) @@ -108,15 +112,23 @@ var/obj/structure/closet/crate/C = container C.manifest = P C.update_icon() + else + // manifest goes in if it's not a crate + container.contents += P return P /datum/supply_order/proc/generate(atom/A) + var/account_holder + if(paying_account) + account_holder = paying_account.account_holder + else + account_holder = "Cargo" var/obj/structure/closet/crate/C = pack.generate(A, paying_account) - generateManifest(C, paying_account, pack) + generateManifest(C, account_holder, pack) return C -/datum/supply_order/proc/generateCombo(var/miscbox, var/misc_own, var/misc_contents) +/datum/supply_order/proc/generateCombo(miscbox, misc_own, misc_contents) for (var/I in misc_contents) new I(miscbox) generateManifest(miscbox, misc_own, "") diff --git a/code/modules/cargo/packs/misc.dm b/code/modules/cargo/packs/misc.dm index 4576de0a06..9e54cb5b93 100644 --- a/code/modules/cargo/packs/misc.dm +++ b/code/modules/cargo/packs/misc.dm @@ -434,6 +434,17 @@ /obj/item/stack/tile/noslip/thirty) crate_name = "high-traction floor tiles crate" +/datum/supply_pack/misc/blackmarket_telepad + name = "Black Market LTSRBT" + desc = "Need a faster and better way of transporting your illegal goods from and to the station? Fear not, the Long-To-Short-Range-Bluespace-Transceiver (LTSRBT for short) is here to help. Contains a LTSRBT circuit, two bluespace crystals, and one ansible." + cost = 8000 + contraband = TRUE + contains = list(/obj/item/circuitboard/machine/ltsrbt, + /obj/item/stack/ore/bluespace_crystal/artificial, + /obj/item/stack/ore/bluespace_crystal/artificial, + /obj/item/stock_parts/subspace/ansible) + crate_name = "crate" + ////////////////////////////////////////////////////////////////////////////// //////////////////////////////// Lewd Supplies /////////////////////////////// ////////////////////////////////////////////////////////////////////////////// diff --git a/code/modules/cargo/packs/vending.dm b/code/modules/cargo/packs/vending.dm index e55f24d91e..6d978d629f 100644 --- a/code/modules/cargo/packs/vending.dm +++ b/code/modules/cargo/packs/vending.dm @@ -67,7 +67,7 @@ /datum/supply_pack/vending/hydro name = "Hydroponics Supply Crate" - desc = "Arnt you glad you dont have to do it the natural way? Contains a megaseed and nutrimax vending machine refill." + desc = "Aren't you glad you dont have to do it the natural way? Contains a megaseed and nutrimax vending machine refill." cost = 5000 contains = list(/obj/item/vending_refill/hydroseeds, /obj/item/vending_refill/hydronutrients) @@ -82,6 +82,14 @@ contains = list(/obj/item/vending_refill/kink) crate_name = "Kinkmate construction kit" +/datum/supply_pack/vending/barkbox + name = "Bark Box Supply Crate" + desc = "Running of out things to keep your pets happy?" + cost = 2000 + contraband = TRUE + contains = list(/obj/item/vending_refill/barkbox) + crate_name = "bark box supply crate" + /datum/supply_pack/vending/medical name = "Medical Vending Crate" desc = "Contains refills for medical vending machines." diff --git a/code/modules/cargo/supplypod.dm b/code/modules/cargo/supplypod.dm index 90adf8e7ff..3d2264a593 100644 --- a/code/modules/cargo/supplypod.dm +++ b/code/modules/cargo/supplypod.dm @@ -130,26 +130,28 @@ /obj/structure/closet/supplypod/update_overlays() . = ..() - if (style == STYLE_INVISIBLE) + if(style == STYLE_INVISIBLE) return - if (rubble) + + if(rubble) . += rubble.getForeground(src) - if (style == STYLE_SEETHROUGH) - for (var/atom/A in contents) + + if(style == STYLE_SEETHROUGH) + for(var/atom/A in contents) var/mutable_appearance/itemIcon = new(A) itemIcon.transform = matrix().Translate(-1 * SUPPLYPOD_X_OFFSET, 0) . += itemIcon - for (var/t in turfs_in_cargo)//T is just a turf's type + for(var/t in turfs_in_cargo)//T is just a turf's type var/turf/turf_type = t var/mutable_appearance/itemIcon = mutable_appearance(initial(turf_type.icon), initial(turf_type.icon_state)) itemIcon.transform = matrix().Translate(-1 * SUPPLYPOD_X_OFFSET, 0) . += itemIcon return - if (opened) //We're opened means all we have to worry about is masking a decal if we have one - if (!decal) //We don't have a decal to mask + if(opened) //We're opened means all we have to worry about is masking a decal if we have one + if(!decal) //We don't have a decal to mask return - if (!door) //We have a decal but no door, so let's just add the decal + if(!door) //We have a decal but no door, so let's just add the decal . += decal return var/icon/masked_decal = new(icon, decal) //The decal we want to apply @@ -159,23 +161,25 @@ door_masker.Blend("#000000", ICON_SUBTRACT) masked_decal.Blend(door_masker, ICON_ADD) . += masked_decal - else //If we're closed - if (!door) //We have no door, lets see if we have a decal. If not, theres nothing we need to do - if (decal) - . += decal - return - else if (GLOB.podstyles[style][POD_SHAPE] != POD_SHAPE_NORML) //If we're not a normal pod shape (aka, if we don't have fins), just add the door without masking - . += door - else - var/icon/masked_door = new(icon, door) //The door we want to apply - var/icon/fin_masker = new(icon, "mask_[fin_mask]") //The fin shape we want to 'cut out' of the door - fin_masker.MapColors(0,0,0,1, 0,0,0,1, 0,0,0,1, 1,1,1,0, 0,0,0,1) - fin_masker.SwapColor("#ffffffff", null) - fin_masker.Blend("#000000", ICON_SUBTRACT) - masked_door.Blend(fin_masker, ICON_ADD) - . += masked_door - if (decal) + return + + //If we're closed + if(!door) //We have no door, lets see if we have a decal. If not, theres nothing we need to do + if(decal) . += decal + return + else if (GLOB.podstyles[style][POD_SHAPE] != POD_SHAPE_NORML) //If we're not a normal pod shape (aka, if we don't have fins), just add the door without masking + . += door + else + var/icon/masked_door = new(icon, door) //The door we want to apply + var/icon/fin_masker = new(icon, "mask_[fin_mask]") //The fin shape we want to 'cut out' of the door + fin_masker.MapColors(0,0,0,1, 0,0,0,1, 0,0,0,1, 1,1,1,0, 0,0,0,1) + fin_masker.SwapColor("#ffffffff", null) + fin_masker.Blend("#000000", ICON_SUBTRACT) + masked_door.Blend(fin_masker, ICON_ADD) + . += masked_door + if(decal) + . += decal /obj/structure/closet/supplypod/tool_interact(obj/item/W, mob/user) if(bluespace) //We dont want to worry about interacting with bluespace pods, as they are due to delete themselves soon anyways. @@ -192,7 +196,7 @@ /obj/structure/closet/supplypod/toggle(mob/living/user) return -/obj/structure/closet/supplypod/open(mob/living/user, force = TRUE) +/obj/structure/closet/supplypod/open(mob/living/user, force = FALSE) return /obj/structure/closet/supplypod/proc/handleReturnAfterDeparting(atom/movable/holder = src) @@ -550,9 +554,6 @@ var/obj/effect/pod_landingzone_effect/helper var/list/smoke_effects = new /list(13) -/obj/effect/ex_act() - return - /obj/effect/pod_landingzone/Initialize(mapload, podParam, single_order = null, clientman) . = ..() if (ispath(podParam)) //We can pass either a path for a pod (as expressconsoles do), or a reference to an instantiated pod (as the centcom_podlauncher does) @@ -647,4 +648,12 @@ desc = "This disk provides a firmware update to the Express Supply Console, granting the use of Nanotrasen's Bluespace Drop Pods to the supply department." icon = 'icons/obj/module.dmi' icon_state = "cargodisk" + // inhand_icon_state = "card-id" w_class = WEIGHT_CLASS_SMALL + +// let's not. +/obj/structure/closet/supplypod/handle_lock_addition() + return + +/obj/structure/closet/supplypod/handle_lock_removal() + return diff --git a/code/modules/clothing/neck/_neck.dm b/code/modules/clothing/neck/_neck.dm index b0af7aa0d5..28a3343ae6 100644 --- a/code/modules/clothing/neck/_neck.dm +++ b/code/modules/clothing/neck/_neck.dm @@ -190,6 +190,12 @@ tagname = stripped_input(user, "Would you like to change the name on the tag?", "Name your new pet", "Spot", MAX_NAME_LEN) name = "[initial(name)] - [tagname]" +/obj/item/clothing/neck/petcollar/ribbon + name = "ribbon pet collar" + icon_state = "ribboncollar" + poly_states = 2 + poly_colors = list("#454545", "#292929") + /obj/item/clothing/neck/petcollar/leather name = "leather pet collar" icon_state = "leathercollar" @@ -225,6 +231,12 @@ return ..() +/obj/item/clothing/neck/petcollar/locked/ribbon + name = "ribbon pet collar" + icon_state = "ribboncollar" + poly_states = 2 + poly_colors = list("#454545", "#292929") + /obj/item/clothing/neck/petcollar/locked/leather name = "leather pet collar" icon_state = "leathercollar" diff --git a/code/modules/clothing/suits/miscellaneous.dm b/code/modules/clothing/suits/miscellaneous.dm index 3e72765234..354d488703 100644 --- a/code/modules/clothing/suits/miscellaneous.dm +++ b/code/modules/clothing/suits/miscellaneous.dm @@ -567,6 +567,26 @@ attack_verb = list("warned", "cautioned", "smashed") armor = list("melee" = 5, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) +/obj/item/clothing/suit/petharness + name = "pet harness" + desc = "For your pet! Or not! Has a little clip on the back." + icon_state = "petharness" + item_state = "petharness" + body_parts_covered = NONE + mutantrace_variation = STYLE_DIGITIGRADE|STYLE_NO_ANTHRO_ICON + allowed = /obj/item/toy/fluff + +/obj/item/clothing/suit/petharness/mesh + name = "mesh pet harness" + desc = "For your pet! Or not! Has some mesh to cover up your more sensitive bits and a little clip on the back." + icon_state = "petharness_mesh" + item_state = "petharness_mesh" + body_parts_covered = CHEST + +/obj/item/clothing/suit/petharness/ComponentInitialize() + . = ..() + AddElement(/datum/element/polychromic, list("#0056D9", "#959595"), 2, _flags = POLYCHROMIC_ACTION) + // WINTER COATS /obj/item/clothing/suit/hooded/wintercoat diff --git a/code/modules/food_and_drinks/food/snacks_pastry.dm b/code/modules/food_and_drinks/food/snacks_pastry.dm index 94960a2658..8c5631336e 100644 --- a/code/modules/food_and_drinks/food/snacks_pastry.dm +++ b/code/modules/food_and_drinks/food/snacks_pastry.dm @@ -466,6 +466,17 @@ foodtype = GRAIN | SUGAR dunkable = TRUE +/obj/item/reagent_containers/food/snacks/dogtreat + name = "dog treat" + desc = "It's a scooby-snack. Right?" + icon_state = "dogtreat" + bitesize = 1 + bonus_reagents = list(/datum/reagent/consumable/nutriment = 1) + list_reagents = list(/datum/reagent/consumable/nutriment = 1) + filling_color = "#635444" + tastes = list("meat" = 1, "dough" = 1) + foodtype = GRAIN | MEAT + /obj/item/reagent_containers/food/snacks/donkpocket name = "\improper Donk-pocket" desc = "The food of choice for the seasoned traitor." diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_pastry.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_pastry.dm index 6c418ab612..5343595f89 100644 --- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_pastry.dm +++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_pastry.dm @@ -214,6 +214,17 @@ result = /obj/item/reagent_containers/food/snacks/cracker subcategory = CAT_PASTRY +/datum/crafting_recipe/food/dogtreat + time = 15 + name = "Dog Treat" + reqs = list( + /datum/reagent/consumable/sodiumchloride = 1, + /obj/item/reagent_containers/food/snacks/meat/cutlet = 1, + /obj/item/reagent_containers/food/snacks/pastrybase = 1, + ) + result = /obj/item/reagent_containers/food/snacks/dogtreat + subcategory = CAT_PASTRY + /datum/crafting_recipe/food/fortunecookie time = 15 name = "Fortune cookie" diff --git a/code/modules/mob/living/carbon/human/human_helpers.dm b/code/modules/mob/living/carbon/human/human_helpers.dm index 5f30d902b1..3e65bb6e66 100644 --- a/code/modules/mob/living/carbon/human/human_helpers.dm +++ b/code/modules/mob/living/carbon/human/human_helpers.dm @@ -115,13 +115,13 @@ . = ..() if(!.) return + if(HAS_TRAIT(src, TRAIT_NOGUNS)) + to_chat(src, "You can't bring yourself to use a ranged weapon!") + return FALSE if(G.trigger_guard == TRIGGER_GUARD_NORMAL) if(HAS_TRAIT(src, TRAIT_CHUNKYFINGERS)) to_chat(src, "Your meaty finger is much too large for the trigger guard!") return FALSE - if(HAS_TRAIT(src, TRAIT_NOGUNS)) - to_chat(src, "Your fingers don't fit in the trigger guard!") - return FALSE /mob/living/carbon/human/proc/get_bank_account() RETURN_TYPE(/datum/bank_account) diff --git a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm index feb4d083fe..2ab8bb1d53 100644 --- a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm @@ -182,6 +182,10 @@ if(T.light_range && !isspaceturf(T)) //no fairy grass or light tile can escape the fury of the darkness. to_chat(user, "You scrape away [T] with your [name] and snuff out its lights.") T.ScrapeAway(flags = CHANGETURF_INHERIT_AIR) + else if(is_cleanable(AM)) + var/obj/effect/E = AM + if(E.light_range && E.light_power) + disintegrate(E) else if(isliving(AM)) var/mob/living/L = AM if(isethereal(AM)) diff --git a/code/modules/modular_computers/computers/item/laptop.dm b/code/modules/modular_computers/computers/item/laptop.dm index 7616e31aa8..37eedcb312 100644 --- a/code/modules/modular_computers/computers/item/laptop.dm +++ b/code/modules/modular_computers/computers/item/laptop.dm @@ -25,6 +25,7 @@ /obj/item/modular_computer/laptop/examine(mob/user) . = ..() + . += "Drag it in your hand to pick it up." if(screen_on) . += "Alt-click to close it." @@ -76,6 +77,9 @@ M.put_in_hand(src, H.held_index) /obj/item/modular_computer/laptop/on_attack_hand(mob/user) + . = ..() + if(!.) + return if(screen_on && isturf(loc)) return attack_self(user) diff --git a/code/modules/modular_computers/file_system/programs/budgetordering.dm b/code/modules/modular_computers/file_system/programs/budgetordering.dm index 65ff97dd1b..162f480c02 100644 --- a/code/modules/modular_computers/file_system/programs/budgetordering.dm +++ b/code/modules/modular_computers/file_system/programs/budgetordering.dm @@ -1,8 +1,9 @@ /datum/computer_file/program/budgetorders filename = "orderapp" - filedesc = "Nanotrasen Internal Requisition Network (NIRN)" + filedesc = "NT IRN" + // category = PROGRAM_CATEGORY_SUPL program_icon_state = "request" - extended_desc = "A request network that utilizes the Nanotrasen Ordering network to purchase supplies using a department budget account." + extended_desc = "Nanotrasen Internal Requisition Network interface for supply purchasing using a department budget account." requires_ntnet = TRUE transfer_access = ACCESS_HEADS usage_flags = PROGRAM_LAPTOP | PROGRAM_TABLET @@ -79,7 +80,6 @@ data["supplies"] = list() for(var/pack in SSshuttle.supply_packs) var/datum/supply_pack/P = SSshuttle.supply_packs[pack] - // todo: replace to P.access_view if(!is_visible_pack(usr, P.access , null, P.contraband) || P.hidden) continue if(!data["supplies"][P.group]) @@ -105,7 +105,7 @@ data["docked"] = SSshuttle.supply.mode == SHUTTLE_IDLE data["loan"] = !!SSshuttle.shuttle_loan data["loan_dispatched"] = SSshuttle.shuttle_loan && SSshuttle.shuttle_loan.dispatched - data["can_send"] = FALSE //There is no situation where I want the app to be able to send the shuttle AWAY from the station, but conversely is fine. + data["can_send"] = FALSE //There is no situation where I want the app to be able to send the shuttle AWAY from the station, but conversely is fine. data["can_approve_requests"] = can_approve_requests data["app_cost"] = TRUE var/message = "Remember to stamp and send back the supply manifests." diff --git a/code/modules/modular_computers/file_system/programs/jobmanagement.dm b/code/modules/modular_computers/file_system/programs/jobmanagement.dm index 3f21d2cf2c..0babfcdddd 100644 --- a/code/modules/modular_computers/file_system/programs/jobmanagement.dm +++ b/code/modules/modular_computers/file_system/programs/jobmanagement.dm @@ -20,7 +20,8 @@ "Head of Security", "Chief Engineer", "Research Director", - "Chief Medical Officer") + "Chief Medical Officer", + "Quartermaster") //The scaling factor of max total positions in relation to the total amount of people on board the station in % var/max_relative_positions = 30 //30%: Seems reasonable, limit of 6 @ 20 players @@ -43,7 +44,7 @@ /datum/computer_file/program/job_management/proc/can_close_job(datum/job/job) if(!(job?.title in blacklisted)) - if(job.total_positions > length(GLOB.player_list) * (max_relative_positions / 100)) + if(job.total_positions > job.current_positions) var/delta = (world.time / 10) - GLOB.time_last_changed_position if((change_position_cooldown < delta) || (opened_positions[job.title] > 0)) return TRUE @@ -67,7 +68,7 @@ if(!j || !can_open_job(j)) return if(opened_positions[edit_job_target] >= 0) - GLOB.time_last_changed_position = world.time / 10 + GLOB.time_last_changed_position = world.time / 10 // global cd j.total_positions++ opened_positions[edit_job_target]++ playsound(computer, 'sound/machines/terminal_prompt_confirm.ogg', 50, FALSE) diff --git a/code/modules/reagents/chemistry/recipes/medicine.dm b/code/modules/reagents/chemistry/recipes/medicine.dm index f2e9bd9e1a..918ccf01e5 100644 --- a/code/modules/reagents/chemistry/recipes/medicine.dm +++ b/code/modules/reagents/chemistry/recipes/medicine.dm @@ -335,7 +335,7 @@ required_reagents = list( /datum/reagent/medicine/mannitol = 2, /datum/reagent/water = 2, /datum/reagent/impedrezene = 1) /datum/chemical_reaction/medsuture - required_reagents = list(/datum/reagent/cellulose = 10, /datum/reagent/toxin/formaldehyde = 20, /datum/reagent/medicine/polypyr = 15) //This might be a bit much, reagent cost should be reviewed after implementation. + required_reagents = list(/datum/reagent/cellulose = 5, /datum/reagent/toxin/formaldehyde = 5, /datum/reagent/medicine/polypyr = 5) //This might be a bit much, reagent cost should be reviewed after implementation. /datum/chemical_reaction/medsuture/on_reaction(datum/reagents/holder, created_volume) var/location = get_turf(holder.my_atom) @@ -343,7 +343,7 @@ new /obj/item/stack/medical/suture/medicated(location) /datum/chemical_reaction/medmesh - required_reagents = list(/datum/reagent/cellulose = 20, /datum/reagent/consumable/aloejuice = 20, /datum/reagent/space_cleaner/sterilizine = 10) + required_reagents = list(/datum/reagent/cellulose = 5, /datum/reagent/consumable/aloejuice = 5, /datum/reagent/space_cleaner/sterilizine = 5) /datum/chemical_reaction/medmesh/on_reaction(datum/reagents/holder, created_volume) var/location = get_turf(holder.my_atom) diff --git a/code/modules/reagents/reagent_containers/bottle.dm b/code/modules/reagents/reagent_containers/bottle.dm index 76b08c7cdf..5d68d00200 100644 --- a/code/modules/reagents/reagent_containers/bottle.dm +++ b/code/modules/reagents/reagent_containers/bottle.dm @@ -453,3 +453,8 @@ /obj/item/reagent_containers/glass/bottle/ichor/green name = "green potion" list_reagents = list(/datum/reagent/green_ichor = 1) + +/obj/item/reagent_containers/glass/bottle/thermite + name = "thermite bottle" + list_reagents = list(/datum/reagent/thermite = 30) + diff --git a/code/modules/research/techweb/nodes/medical_nodes.dm b/code/modules/research/techweb/nodes/medical_nodes.dm index af23804c57..0eca3ea877 100644 --- a/code/modules/research/techweb/nodes/medical_nodes.dm +++ b/code/modules/research/techweb/nodes/medical_nodes.dm @@ -104,7 +104,7 @@ display_name = "Combat Cybernetic Implants" description = "Military grade combat implants to improve performance." prereq_ids = list("adv_cyber_implants","weaponry","NVGtech","high_efficiency") - design_ids = list("ci-xray", "ci-thermals", "ci-antidrop", "ci-antistun", "ci-thrusters", "ci-shield") + design_ids = list("ci-thermals", "ci-antidrop", "ci-antistun", "ci-thrusters", "ci-shield") research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) /////////////////////////Advanced Surgery///////////////////////// diff --git a/code/modules/research/techweb/nodes/syndicate_nodes.dm b/code/modules/research/techweb/nodes/syndicate_nodes.dm index 193ed8653b..e659e49ac7 100644 --- a/code/modules/research/techweb/nodes/syndicate_nodes.dm +++ b/code/modules/research/techweb/nodes/syndicate_nodes.dm @@ -4,7 +4,7 @@ display_name = "Illegal Technology" description = "Dangerous research used to create dangerous objects." prereq_ids = list("adv_engi", "adv_weaponry", "explosive_weapons") - design_ids = list("decloner", "borg_syndicate_module", "suppressor", "largecrossbow", "donksofttoyvendor", "donksoft_refill", "syndiesleeper") + design_ids = list("decloner", "borg_syndicate_module", "suppressor", "largecrossbow", "donksofttoyvendor", "donksoft_refill", "syndiesleeper", "ci-xray") research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 10000) hidden = TRUE diff --git a/code/modules/shuttle/supply.dm b/code/modules/shuttle/supply.dm index 7af0315934..899e2b2f6f 100644 --- a/code/modules/shuttle/supply.dm +++ b/code/modules/shuttle/supply.dm @@ -25,7 +25,14 @@ GLOBAL_LIST_INIT(blacklisted_cargo_types, typecacheof(list( /obj/item/shared_storage, /obj/structure/extraction_point, /obj/machinery/syndicatebomb, - /obj/item/hilbertshotel + /obj/item/hilbertshotel, + /obj/machinery/launchpad, + /obj/machinery/disposal, + /obj/structure/disposalpipe, + /obj/item/hilbertshotel, + /obj/machinery/camera, + /obj/item/gps, + /obj/structure/checkoutmachine ))) GLOBAL_LIST_INIT(cargo_shuttle_leave_behind_typecache, typecacheof(list( @@ -33,6 +40,11 @@ GLOBAL_LIST_INIT(cargo_shuttle_leave_behind_typecache, typecacheof(list( /mob/living/simple_animal/slaughter ))) +/// How many goody orders we can fit in a lockbox before we upgrade to a crate +#define GOODY_FREE_SHIPPING_MAX 5 +/// How much to charge oversized goody orders +#define CRATE_TAX 700 + /obj/docking_port/mobile/supply name = "supply shuttle" id = "supply" @@ -58,6 +70,7 @@ GLOBAL_LIST_INIT(cargo_shuttle_leave_behind_typecache, typecacheof(list( return check_blacklist(shuttle_areas, GLOB.blacklisted_cargo_types - GLOB.cargo_shuttle_leave_behind_typecache) return ..() +// fuc off /obj/docking_port/mobile/supply/enterTransit() var/list/leave_behind = list() for(var/i in check_blacklist(shuttle_areas, GLOB.cargo_shuttle_leave_behind_typecache)) @@ -98,12 +111,11 @@ GLOBAL_LIST_INIT(cargo_shuttle_leave_behind_typecache, typecacheof(list( sell() /obj/docking_port/mobile/supply/proc/buy() - if(!SSshuttle.shoppinglist.len) - return - var/list/obj/miscboxes = list() //miscboxes are combo boxes that contain all goody orders grouped var/list/misc_order_num = list() //list of strings of order numbers, so that the manifest can show all orders in a box var/list/misc_contents = list() //list of lists of items that each box will contain + if(!SSshuttle.shoppinglist.len) + return var/list/empty_turfs = list() for(var/place in shuttle_areas) @@ -113,30 +125,39 @@ GLOBAL_LIST_INIT(cargo_shuttle_leave_behind_typecache, typecacheof(list( continue empty_turfs += T - var/datum/bank_account/cargo_budget = SSeconomy.get_dep_account(ACCOUNT_CAR) var/value = 0 var/purchases = 0 + var/list/goodies_by_buyer = list() // if someone orders more than GOODY_FREE_SHIPPING_MAX goodies, we upcharge to a normal crate so they can't carry around 20 combat shotties + for(var/datum/supply_order/SO in SSshuttle.shoppinglist) if(!empty_turfs.len) break - var/price = SO.pack.cost if(SO.applied_coupon) price *= (1 - SO.applied_coupon.discount_pct_off) + var/datum/bank_account/D if(SO.paying_account) //Someone paid out of pocket D = SO.paying_account + var/list/current_buyer_orders = goodies_by_buyer[SO.paying_account] // so we can access the length a few lines down if(!SO.pack.goody) price *= 1.1 //TODO make this customizable by the quartermaster + + // note this is before we increment, so this is the GOODY_FREE_SHIPPING_MAX + 1th goody to ship. also note we only increment off this step if they successfully pay the fee, so there's no way around it + else if(LAZYLEN(current_buyer_orders) == GOODY_FREE_SHIPPING_MAX) + price += CRATE_TAX + D.bank_card_talk("Goody order size exceeds free shipping limit: Assessing [CRATE_TAX] credit S&H fee.") else - D = cargo_budget + D = SSeconomy.get_dep_account(ACCOUNT_CAR) if(D) - if(!D.adjust_money(-SO.pack.cost)) + if(!D.adjust_money(-price)) if(SO.paying_account) D.bank_card_talk("Cargo order #[SO.id] rejected due to lack of funds. Credits required: [price]") continue if(SO.paying_account) + if(SO.pack.goody) + LAZYADD(goodies_by_buyer[SO.paying_account], SO) D.bank_card_talk("Cargo order #[SO.id] has shipped. [price] credits have been charged to your bank account.") var/datum/bank_account/department/cargo = SSeconomy.get_dep_account(ACCOUNT_CAR) cargo.adjust_money(price - SO.pack.cost) //Cargo gets the handling fee @@ -145,30 +166,7 @@ GLOBAL_LIST_INIT(cargo_shuttle_leave_behind_typecache, typecacheof(list( SSshuttle.orderhistory += SO QDEL_NULL(SO.applied_coupon) - if(SO.pack.goody) //goody means it gets piled in the miscbox - if(SO.paying_account) - if(!miscboxes.len || !miscboxes[D.account_holder]) //if there's no miscbox for this person - miscboxes[D.account_holder] = new /obj/item/storage/lockbox/order(pick_n_take(empty_turfs)) - var/obj/item/storage/lockbox/order/our_box = miscboxes[D.account_holder] - our_box.buyer_account = SO.paying_account - miscboxes[D.account_holder].name = "small items case - purchased by [D.account_holder]" - misc_contents[D.account_holder] = list() - for (var/item in SO.pack.contains) - misc_contents[D.account_holder] += item - misc_order_num[D.account_holder] = "[misc_order_num[D.account_holder]]#[SO.id] " - else //No private payment, so we just stuff it all into a generic crate - if(!miscboxes.len || !miscboxes["Cargo"]) - miscboxes["Cargo"] = new /obj/structure/closet/secure_closet/goodies(pick_n_take(empty_turfs)) - miscboxes["Cargo"].name = "small items closet" - misc_contents["Cargo"] = list() - miscboxes["Cargo"].req_access = list() - for (var/item in SO.pack.contains) - misc_contents["Cargo"] += item - //new item(miscboxes["Cargo"]) - if(SO.pack.access) - miscboxes["Cargo"].req_access += SO.pack.access - misc_order_num["Cargo"] = "[misc_order_num["Cargo"]]#[SO.id] " - else + if(!SO.pack.goody) //we handle goody crates below SO.generate(pick_n_take(empty_turfs)) SSblackbox.record_feedback("nested tally", "cargo_imports", 1, list("[SO.pack.cost]", "[SO.pack.name]")) @@ -177,16 +175,43 @@ GLOBAL_LIST_INIT(cargo_shuttle_leave_behind_typecache, typecacheof(list( message_admins("\A [SO.pack.name] ordered by [ADMIN_LOOKUPFLW(SO.orderer_ckey)], paid by [D.account_holder] has shipped.") purchases++ + // we handle packing all the goodies last, since the type of crate we use depends on how many goodies they ordered. If it's more than GOODY_FREE_SHIPPING_MAX + // then we send it in a crate (including the CRATE_TAX cost), otherwise send it in a free shipping case + for(var/D in goodies_by_buyer) + var/list/buying_account_orders = goodies_by_buyer[D] + var/datum/bank_account/buying_account = D + var/buyer = buying_account.account_holder + + if(buying_account_orders.len > GOODY_FREE_SHIPPING_MAX) // no free shipping, send a crate + var/obj/structure/closet/crate/secure/owned/our_crate = new /obj/structure/closet/crate/secure/owned(pick_n_take(empty_turfs)) + our_crate.buyer_account = buying_account + our_crate.name = "goody crate - purchased by [buyer]" + miscboxes[buyer] = our_crate + else //free shipping in a case + miscboxes[buyer] = new /obj/item/storage/lockbox/order(pick_n_take(empty_turfs)) + var/obj/item/storage/lockbox/order/our_case = miscboxes[buyer] + our_case.buyer_account = buying_account + miscboxes[buyer].name = "goody case - purchased by [buyer]" + misc_contents[buyer] = list() + + for(var/O in buying_account_orders) + var/datum/supply_order/our_order = O + for (var/item in our_order.pack.contains) + misc_contents[buyer] += item + misc_order_num[buyer] = "[misc_order_num[buyer]]#[our_order.id] " + for(var/I in miscboxes) var/datum/supply_order/SO = new/datum/supply_order() SO.id = misc_order_num[I] SO.generateCombo(miscboxes[I], I, misc_contents[I]) qdel(SO) + var/datum/bank_account/cargo_budget = SSeconomy.get_dep_account(ACCOUNT_CAR) investigate_log("[purchases] orders in this shipment, worth [value] credits. [cargo_budget.account_balance] credits left.", INVESTIGATE_CARGO) /obj/docking_port/mobile/supply/proc/sell() var/datum/bank_account/D = SSeconomy.get_dep_account(ACCOUNT_CAR) + var/presale_points = D.account_balance var/gain = 0 if(!GLOB.exports_list.len) // No exports list? Generate it! @@ -204,6 +229,8 @@ GLOBAL_LIST_INIT(cargo_shuttle_leave_behind_typecache, typecacheof(list( continue if(bounty_ship_item_and_contents(AM, dry_run = FALSE)) matched_bounty = TRUE + // ignore mech checks because the mech is ONLY for bounty + continue if(!AM.anchored || istype(AM, /obj/mecha)) export_item_and_contents(AM, export_categories , dry_run = FALSE, external_report = ex) @@ -230,4 +257,7 @@ GLOBAL_LIST_INIT(cargo_shuttle_leave_behind_typecache, typecacheof(list( msg = copytext_char(msg, 1, MAX_MESSAGE_LEN) SSshuttle.centcom_message = msg - investigate_log("Shuttle contents sold for [gain] credits. Contents: [ex.exported_atoms || "none."] Message: [SSshuttle.centcom_message || "none."]", INVESTIGATE_CARGO) + investigate_log("Shuttle contents sold for [D.account_balance - presale_points] credits. Contents: [ex.exported_atoms ? ex.exported_atoms.Join(",") + "." : "none."] Message: [SSshuttle.centcom_message || "none."]", INVESTIGATE_CARGO) + +#undef GOODY_FREE_SHIPPING_MAX +#undef CRATE_TAX diff --git a/code/modules/vending/barkbox.dm b/code/modules/vending/barkbox.dm new file mode 100644 index 0000000000..4b5d9734dd --- /dev/null +++ b/code/modules/vending/barkbox.dm @@ -0,0 +1,36 @@ +/obj/machinery/vending/barkbox + name = "Bark Box" + desc = "For all your pet needs!" + icon_state = "barkbox" + product_slogans = "Whuff!;Bark!;Give me a treat!" + products = list( + /obj/item/storage/fancy/treat_box = 8, + /obj/item/clothing/neck/petcollar = 5, + /obj/item/clothing/neck/petcollar/ribbon = 5, + /obj/item/clothing/neck/petcollar/leather = 5, + /obj/item/clothing/suit/petharness = 4, + /obj/item/clothing/suit/petharness/mesh = 4, + /obj/item/toy/fluff/tennis_poly = 4, + /obj/item/toy/fluff/tennis_poly/tri = 2, + /obj/item/toy/fluff/bone_poly = 4, + /obj/item/toy/fluff/frisbee_poly = 4 + ) + contraband = list( + /obj/item/clothing/neck/petcollar/locked = 2, + /obj/item/clothing/neck/petcollar/locked/ribbon = 2, + /obj/item/clothing/neck/petcollar/locked/leather = 2, + /obj/item/key/collar = 2, + /obj/item/dildo/knotted = 3 + ) + premium = list( + /obj/item/toy/fluff/tennis_poly/tri/squeak = 1, + /obj/item/toy/fluff/bone_poly/squeak = 1 + ) + refill_canister = /obj/item/vending_refill/barkbox + default_price = PRICE_CHEAP + extra_price = PRICE_BELOW_NORMAL + payment_department = NO_FREEBIES + +/obj/item/vending_refill/barkbox + machine_name = "Bark Box" + icon_state = "refill_barkbox" diff --git a/html/changelog.html b/html/changelog.html index e463ce31e1..d5bdbde479 100644 --- a/html/changelog.html +++ b/html/changelog.html @@ -50,6 +50,12 @@ -->
    +

    23 March 2021

    +

    LetterN updated:

    + +

    21 March 2021

    Arturlang updated: