diff --git a/code/__DEFINES/cargo.dm b/code/__DEFINES/cargo.dm index 52a2d56697..bba5b5c5ed 100644 --- a/code/__DEFINES/cargo.dm +++ b/code/__DEFINES/cargo.dm @@ -32,4 +32,8 @@ list("", "\improper S.T.E.A.L.T.H. pod MKVII", "A supply pod that, under normal circumstances, is completely invisible to conventional methods of detection. How are you even seeing this?"),\ list("gondolapod", "gondola", "The silent walker. This one seems to be part of a delivery agency."),\ list("", "", "")\ -) \ No newline at end of file +) + +#define PACK_GOODY_NONE 0 +#define PACK_GOODY_PUBLIC 1 //can be bought by both privates and cargo +#define PACK_GOODY_PRIVATE 2 //can be bought only by privates \ No newline at end of file diff --git a/code/datums/components/omen.dm b/code/datums/components/omen.dm new file mode 100644 index 0000000000..3ea7677710 --- /dev/null +++ b/code/datums/components/omen.dm @@ -0,0 +1,74 @@ +/** + * omen.dm: For when you want someone to have a really bad day + * + * When you attach an omen component to someone, they start running the risk of all sorts of bad environmental injuries, like nearby vending machines randomly falling on you, + * or hitting your head really hard when you slip and fall, or... well, for now those two are all I have. More will come. + * + * Omens are removed once the victim is either maimed by one of the possible injuries, or if they receive a blessing (read: bashing with a bible) from the chaplain. + */ +/datum/component/omen + dupe_mode = COMPONENT_DUPE_UNIQUE + + /// Whatever's causing the omen, if there is one. Destroying the vessel won't stop the omen, but we destroy the vessel (if one exists) upon the omen ending + var/obj/vessel + +/datum/component/omen/Initialize(silent=FALSE, vessel) + if(!isliving(parent)) + return COMPONENT_INCOMPATIBLE + var/mob/person = parent + if(!silent) + to_chat(person, "You get a bad feeling...") + src.vessel = vessel + +/datum/component/omen/Destroy(force, silent) + if(vessel) + vessel.visible_message("[vessel] burns up in a sinister flash, taking an evil energy with it...") + vessel = null + return ..() + +/datum/component/omen/RegisterWithParent() + RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/check_accident) + RegisterSignal(parent, COMSIG_LIVING_STATUS_KNOCKDOWN, .proc/check_slip) + RegisterSignal(parent, COMSIG_ADD_MOOD_EVENT, .proc/check_bless) + +/datum/component/omen/UnregisterFromParent() + UnregisterSignal(parent, list(COMSIG_LIVING_STATUS_KNOCKDOWN, COMSIG_MOVABLE_MOVED, COMSIG_ADD_MOOD_EVENT)) + +/** + * check_accident() is called each step we take + * + * While we're walking around, roll to see if there's any environmental hazards (currently only vending machines) on one of the adjacent tiles we can trigger. + * We do the prob() at the beginning to A. add some tension for /when/ it will strike, and B. (more importantly) ameliorate the fact that we're checking up to 5 turfs's contents each time + */ +/datum/component/omen/proc/check_accident(atom/movable/our_guy) + if(!prob(15)) + return + for(var/t in get_adjacent_open_turfs(our_guy)) + var/turf/the_turf = t + for(var/obj/machinery/vending/darth_vendor in the_turf) + if(darth_vendor.tiltable) + darth_vendor.tilt(our_guy) + qdel(src) + return + +/// If we get knocked down, see if we have a really bad slip and bash our head hard +/datum/component/omen/proc/check_slip(mob/living/our_guy, amount) + if(amount <= 0 || prob(50)) // 50% chance to bonk our head + return + + var/obj/item/bodypart/the_head = our_guy.get_bodypart(BODY_ZONE_HEAD) + if(!the_head) + return + + playsound(get_turf(our_guy), "sound/effects/tableheadsmash.ogg", 90, TRUE) + our_guy.visible_message("[our_guy] hits [our_guy.p_their()] head really badly falling down!", "You hit your head really badly falling down!") + the_head.receive_damage(75) + our_guy.adjustOrganLoss(ORGAN_SLOT_BRAIN, 100) + qdel(src) + +/// Hijack the mood system to see if we get the blessing mood event to cancel the omen +/datum/component/omen/proc/check_bless(mob/living/our_guy, category) + if(category != "blessing") + return + to_chat(our_guy, "You feel a horrible omen lifted off your shoulders!") + qdel(src) diff --git a/code/datums/elements/embed.dm b/code/datums/elements/embed.dm index 86189b67e1..8a7acde9cb 100644 --- a/code/datums/elements/embed.dm +++ b/code/datums/elements/embed.dm @@ -72,8 +72,8 @@ /// Checking to see if we're gonna embed into a human -/datum/element/embed/proc/checkEmbedMob(obj/item/weapon, mob/living/carbon/victim, hit_zone, datum/thrownthing/throwingdatum, forced=FALSE) - if(!istype(victim) || HAS_TRAIT(victim, TRAIT_PIERCEIMMUNE)) +/datum/element/embed/proc/checkEmbedMob(obj/item/weapon, mob/living/carbon/victim, hit_zone, datum/thrownthing/throwingdatum, blocked = FALSE, forced = FALSE) + if(blocked || !istype(victim) || HAS_TRAIT(victim, TRAIT_PIERCEIMMUNE)) return var/actual_chance = embed_chance @@ -89,7 +89,7 @@ return var/roll_embed = prob(actual_chance) - var/pass = forced || ((((throwingdatum ? throwingdatum.speed : weapon.throw_speed) >= EMBED_THROWSPEED_THRESHOLD) || ignore_throwspeed_threshold) && roll_embed) + var/pass = forced || ((((throwingdatum ? throwingdatum.speed : weapon.throw_speed) >= EMBED_THROWSPEED_THRESHOLD) || ignore_throwspeed_threshold) && roll_embed && (!HAS_TRAIT(victim, TRAIT_AUTO_CATCH_ITEM) || victim.incapacitated() || victim.get_active_held_item())) if(!pass) return diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index 2a45267c65..635522fa41 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -413,8 +413,8 @@ // shock user with probability prb (if all connections & power are working) // returns TRUE if shocked, FALSE otherwise // The preceding comment was borrowed from the grille's shock script -/obj/machinery/door/airlock/proc/shock(mob/user, prb) - if(!hasPower()) // unpowered, no shock +/obj/machinery/door/airlock/proc/shock(mob/living/user, prb) + if(!istype(user) || !hasPower()) // unpowered, no shock return FALSE if(shockCooldown > world.time) return FALSE //Already shocked someone recently? diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 5aa47cfb76..60bae4016a 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -257,7 +257,7 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb . += "[src] is made of cold-resistant materials." if(resistance_flags & FIRE_PROOF) . += "[src] is made of fire-retardant materials." - + if(item_flags & (ITEM_CAN_BLOCK | ITEM_CAN_PARRY)) var/datum/block_parry_data/data = return_block_parry_datum(block_parry_data) . += "[src] has the capacity to be used to block and/or parry. \[Show Stats\]" @@ -635,6 +635,18 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb var/itempush = 1 if(w_class < 4) itempush = 0 //too light to push anything + if(isliving(hit_atom)) //Living mobs handle hit sounds differently. + var/volume = get_volume_by_throwforce_and_or_w_class() + if (throwforce > 0) + if (throwhitsound) + playsound(hit_atom, throwhitsound, volume, TRUE, -1) + else if(hitsound) + playsound(hit_atom, hitsound, volume, TRUE, -1) + else + playsound(hit_atom, 'sound/weapons/genhit.ogg',volume, TRUE, -1) + else + playsound(hit_atom, 'sound/weapons/throwtap.ogg', 1, volume, -1) + return hit_atom.hitby(src, 0, itempush, throwingdatum=throwingdatum) /obj/item/throw_at(atom/target, range, speed, mob/thrower, spin=1, diagonals_first = 0, datum/callback/callback, force, messy_throw = TRUE) diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm index 55719c2758..c11b619431 100644 --- a/code/game/objects/items/stacks/sheets/sheet_types.dm +++ b/code/game/objects/items/stacks/sheets/sheet_types.dm @@ -779,6 +779,9 @@ GLOBAL_LIST_INIT(plastic_recipes, list( /obj/item/stack/sheet/plastic/fifty amount = 50 +/obj/item/stack/sheet/plastic/twenty + amount = 20 + /obj/item/stack/sheet/plastic/five amount = 5 diff --git a/code/game/objects/items/stacks/tiles/tile_types.dm b/code/game/objects/items/stacks/tiles/tile_types.dm index ff81b4340a..0635c55ca3 100644 --- a/code/game/objects/items/stacks/tiles/tile_types.dm +++ b/code/game/objects/items/stacks/tiles/tile_types.dm @@ -266,6 +266,9 @@ /obj/item/stack/tile/carpet/blackred/twenty amount = 20 +/obj/item/stack/tile/carpet/blackred/thirty + amount = 30 + /obj/item/stack/tile/carpet/blackred/fifty amount = 50 @@ -275,6 +278,9 @@ /obj/item/stack/tile/carpet/monochrome/twenty amount = 20 +/obj/item/stack/tile/carpet/monochrome/thirty + amount = 30 + /obj/item/stack/tile/carpet/monochrome/fifty amount = 50 @@ -284,6 +290,9 @@ /obj/item/stack/tile/carpet/blue/twenty amount = 20 +/obj/item/stack/tile/carpet/blue/thirty + amount = 30 + /obj/item/stack/tile/carpet/blue/fifty amount = 50 @@ -293,6 +302,9 @@ /obj/item/stack/tile/carpet/cyan/twenty amount = 20 +/obj/item/stack/tile/carpet/cyan/thirty + amount = 30 + /obj/item/stack/tile/carpet/cyan/fifty amount = 50 @@ -302,6 +314,9 @@ /obj/item/stack/tile/carpet/green/twenty amount = 20 +/obj/item/stack/tile/carpet/green/thirty + amount = 30 + /obj/item/stack/tile/carpet/green/fifty amount = 50 @@ -311,6 +326,9 @@ /obj/item/stack/tile/carpet/orange/twenty amount = 20 +/obj/item/stack/tile/carpet/orange/thirty + amount = 30 + /obj/item/stack/tile/carpet/orange/fifty amount = 50 @@ -320,6 +338,9 @@ /obj/item/stack/tile/carpet/purple/twenty amount = 20 +/obj/item/stack/tile/carpet/purple/thirty + amount = 30 + /obj/item/stack/tile/carpet/purple/fifty amount = 50 @@ -329,6 +350,9 @@ /obj/item/stack/tile/carpet/red/twenty amount = 20 +/obj/item/stack/tile/carpet/red/thirty + amount = 30 + /obj/item/stack/tile/carpet/red/fifty amount = 50 @@ -338,6 +362,9 @@ /obj/item/stack/tile/carpet/royalblack/twenty amount = 20 +/obj/item/stack/tile/carpet/royalblack/thirty + amount = 30 + /obj/item/stack/tile/carpet/royalblack/fifty amount = 50 @@ -347,6 +374,9 @@ /obj/item/stack/tile/carpet/royalblue/twenty amount = 20 +/obj/item/stack/tile/carpet/royalblue/thirty + amount = 30 + /obj/item/stack/tile/carpet/royalblue/fifty amount = 50 diff --git a/code/game/objects/items/storage/fancy.dm b/code/game/objects/items/storage/fancy.dm index b78bd99d92..821fd52bd1 100644 --- a/code/game/objects/items/storage/fancy.dm +++ b/code/game/objects/items/storage/fancy.dm @@ -138,6 +138,22 @@ icon_type = "cigarette" spawn_type = /obj/item/clothing/mask/cigarette/space_cigarette custom_price = PRICE_ALMOST_CHEAP + var/spawn_coupon = TRUE + +/obj/item/storage/fancy/cigarettes/attack_self(mob/user) + if(contents.len == 0 && spawn_coupon) + to_chat(user, "You rip the back off \the [src] and get a coupon!") + var/obj/item/coupon/attached_coupon = new + user.put_in_hands(attached_coupon) + attached_coupon.generate() + attached_coupon = null + spawn_coupon = FALSE + name = "discarded cigarette packet" + desc = "An old cigarette packet with the back torn off, worth less than nothing now." + var/datum/component/storage/STR = GetComponent(/datum/component/storage) + STR.max_items = 0 + return + return ..() /obj/item/storage/fancy/cigarettes/ComponentInitialize() . = ..() @@ -148,6 +164,8 @@ /obj/item/storage/fancy/cigarettes/examine(mob/user) . = ..() . += "Alt-click to extract contents." + if(spawn_coupon) + . += "There's a coupon on the back of the pack! You can tear it off once it's empty." /obj/item/storage/fancy/cigarettes/AltClick(mob/living/carbon/user) if(!istype(user) || !user.canUseTopic(src, BE_CLOSE, ismonkey(user))) @@ -308,6 +326,7 @@ w_class = WEIGHT_CLASS_NORMAL icon_type = "premium cigar" spawn_type = /obj/item/clothing/mask/cigarette/cigar + spawn_coupon = FALSE /obj/item/storage/fancy/cigarettes/cigars/ComponentInitialize() . = ..() diff --git a/code/game/objects/items/storage/lockbox.dm b/code/game/objects/items/storage/lockbox.dm index 5b6089d430..ce4e8b9df1 100644 --- a/code/game/objects/items/storage/lockbox.dm +++ b/code/game/objects/items/storage/lockbox.dm @@ -188,19 +188,52 @@ new /obj/item/clothing/accessory/medal/plasma/nobel_science(src) /obj/item/storage/lockbox/medal/engineering - name = "engineering medal box" - desc = "A locked box used to store medals to be given to the members of the engineering department." - req_access = list(ACCESS_CE) + name = "engineering medal box" + desc = "A locked box used to store medals to be given to the members of the engineering department." + req_access = list(ACCESS_CE) /obj/item/storage/lockbox/medal/engineering/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/clothing/accessory/medal/engineer(src) + for(var/i in 1 to 3) + new /obj/item/clothing/accessory/medal/engineer(src) /obj/item/storage/lockbox/medal/medical - name = "medical medal box" - desc = "A locked box used to store medals to be given to the members of the medical department." - req_access = list(ACCESS_CMO) + name = "medical medal box" + desc = "A locked box used to store medals to be given to the members of the medical department." + req_access = list(ACCESS_CMO) /obj/item/storage/lockbox/medal/medical/PopulateContents() - for(var/i in 1 to 3) - new /obj/item/clothing/accessory/medal/ribbon/medical_doctor(src) \ No newline at end of file + for(var/i in 1 to 3) + new /obj/item/clothing/accessory/medal/ribbon/medical_doctor(src) + +/obj/item/storage/lockbox/order + name = "order lockbox" + desc = "A box used to secure small cargo orders from being looted by those who didn't order it. Yeah, cargo tech, that means you." + icon = 'icons/obj/storage.dmi' + icon_state = "secure" + item_state = "sec-case" + lefthand_file = 'icons/mob/inhands/equipment/briefcase_lefthand.dmi' + righthand_file = 'icons/mob/inhands/equipment/briefcase_righthand.dmi' + w_class = WEIGHT_CLASS_HUGE + var/datum/bank_account/buyer_account + var/privacy_lock = TRUE + +/obj/item/storage/lockbox/order/Initialize(datum/bank_account/_buyer_account) + . = ..() + buyer_account = _buyer_account + +/obj/item/storage/lockbox/order/attackby(obj/item/W, mob/user, params) + if(!istype(W, /obj/item/card/id)) + return ..() + + var/obj/item/card/id/id_card = W + if(iscarbon(user)) + add_fingerprint(user) + + if(id_card.registered_account != buyer_account) + to_chat(user, "Bank account does not match with buyer![user] [privacy_lock ? "" : "un"]locks [src]'s privacy lock.", + "You [privacy_lock ? "" : "un"]lock [src]'s privacy lock.") diff --git a/code/game/objects/structures/crates_lockers/closets/secure/secure_closets.dm b/code/game/objects/structures/crates_lockers/closets/secure/secure_closets.dm index 57520f6f40..0c314d988c 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/secure_closets.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/secure_closets.dm @@ -6,8 +6,16 @@ max_integrity = 250 armor = list("melee" = 30, "bullet" = 50, "laser" = 50, "energy" = 100, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 80, "acid" = 80) secure = TRUE + var/melee_min_damage = 20 /obj/structure/closet/secure_closet/run_obj_armor(damage_amount, damage_type, damage_flag = 0, attack_dir) - if(damage_flag == "melee" && damage_amount < 20) + if(damage_flag == "melee" && damage_amount < melee_min_damage) return 0 - . = ..() \ No newline at end of file + . = ..() + +// Exists to work around the minimum 700 cr price for goodies / small items +/obj/structure/closet/secure_closet/goodies + icon_state = "goodies" + desc = "A sturdier card-locked storage unit used for bulky shipments." + max_integrity = 500 // Same as crates. + melee_min_damage = 25 // Idem. diff --git a/code/modules/cargo/console.dm b/code/modules/cargo/console.dm index 6968a5ccd8..d010cee761 100644 --- a/code/modules/cargo/console.dm +++ b/code/modules/cargo/console.dm @@ -18,6 +18,7 @@ var/obj/item/radio/headset/radio /// var that tracks message cooldown var/message_cooldown + var/list/loaded_coupons light_color = "#E2853D"//orange @@ -134,6 +135,8 @@ "cost" = P.cost, "id" = pack, "desc" = P.desc || P.name, // If there is a description, use it. Otherwise use the pack's name. + "private_goody" = P.goody == PACK_GOODY_PRIVATE, + "goody" = P.goody == PACK_GOODY_PUBLIC, "access" = P.access, "can_private_buy" = P.can_private_buy )) @@ -215,8 +218,22 @@ if(isnull(reason) || ..()) return + if(pack.goody == PACK_GOODY_PRIVATE && !self_paid) + playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE) + say("ERROR: Private small crates may only be purchased by private accounts.") + return + + var/obj/item/coupon/applied_coupon + for(var/i in loaded_coupons) + var/obj/item/coupon/coupon_check = i + if(pack.type == coupon_check.discounted_pack) + say("Coupon found! [round(coupon_check.discount_pct_off * 100)]% off applied!") + coupon_check.moveToNullspace() + applied_coupon = coupon_check + break + var/turf/T = get_turf(src) - var/datum/supply_order/SO = new(pack, name, rank, ckey, reason, account) + var/datum/supply_order/SO = new(pack, name, rank, ckey, reason, account, applied_coupon) SO.generateRequisition(T) if(requestonly && !self_paid) SSshuttle.requestlist += SO @@ -229,6 +246,9 @@ var/id = text2num(params["id"]) for(var/datum/supply_order/SO in SSshuttle.shoppinglist) if(SO.id == id) + if(SO.applied_coupon) + say("Coupon refunded.") + SO.applied_coupon.forceMove(get_turf(src)) SSshuttle.shoppinglist -= SO . = TRUE break diff --git a/code/modules/cargo/coupon.dm b/code/modules/cargo/coupon.dm new file mode 100644 index 0000000000..c77050c530 --- /dev/null +++ b/code/modules/cargo/coupon.dm @@ -0,0 +1,50 @@ + +#define COUPON_OMEN "omen" + +/obj/item/coupon + name = "coupon" + desc = "It doesn't matter if you didn't want it before, what matters now is that you've got a coupon for it!" + icon_state = "data_1" + icon = 'icons/obj/card.dmi' + item_flags = NOBLUDGEON + w_class = WEIGHT_CLASS_TINY + var/datum/supply_pack/discounted_pack + var/discount_pct_off = 0.05 + var/obj/machinery/computer/cargo/inserted_console + +/// Choose what our prize is :D +/obj/item/coupon/proc/generate() + discounted_pack = pick(subtypesof(/datum/supply_pack/goody)) + var/list/chances = list("0.10" = 4, "0.15" = 8, "0.20" = 10, "0.25" = 8, "0.50" = 4, COUPON_OMEN = 1) + discount_pct_off = pickweight(chances) + if(discount_pct_off == COUPON_OMEN) + name = "coupon - fuck you" + desc = "The small text reads, 'You will be slaughtered'... That doesn't sound right, does it?" + if(ismob(loc)) + var/mob/M = loc + to_chat(M, "The coupon reads 'fuck you' in large, bold text... is- is that a prize, or?") + M.AddComponent(/datum/component/omen, TRUE, src) + else + discount_pct_off = text2num(discount_pct_off) + name = "coupon - [round(discount_pct_off * 100)]% off [initial(discounted_pack.name)]" + +/obj/item/coupon/attack_obj(obj/O, mob/living/user) + if(!istype(O, /obj/machinery/computer/cargo)) + return ..() + if(discount_pct_off == COUPON_OMEN) + to_chat(user, "\The [O] validates the coupon as authentic, but refuses to accept it...") + O.say("Coupon fulfillment already in progress...") + return + + inserted_console = O + LAZYADD(inserted_console.loaded_coupons, src) + inserted_console.say("Coupon for [initial(discounted_pack.name)] applied!") + forceMove(inserted_console) + +/obj/item/coupon/Destroy() + if(inserted_console) + LAZYREMOVE(inserted_console.loaded_coupons, src) + inserted_console = null + . = ..() + +#undef COUPON_OMEN diff --git a/code/modules/cargo/order.dm b/code/modules/cargo/order.dm index 3d1caf6ba6..4fa6a4eade 100644 --- a/code/modules/cargo/order.dm +++ b/code/modules/cargo/order.dm @@ -27,10 +27,12 @@ var/orderer_rank var/orderer_ckey var/reason + var/discounted_pct var/datum/supply_pack/pack var/datum/bank_account/paying_account + var/obj/item/coupon/applied_coupon -/datum/supply_order/New(datum/supply_pack/pack, orderer, orderer_rank, orderer_ckey, reason, paying_account) +/datum/supply_order/New(datum/supply_pack/pack, orderer, orderer_rank, orderer_ckey, reason, paying_account, coupon) id = SSshuttle.ordernum++ src.pack = pack src.orderer = orderer @@ -38,6 +40,7 @@ src.orderer_ckey = orderer_ckey src.reason = reason src.paying_account = paying_account + src.applied_coupon = coupon /datum/supply_order/proc/generateRequisition(turf/T) var/obj/item/paper/P = new(T) @@ -57,58 +60,64 @@ P.update_icon() return P -/datum/supply_order/proc/generateManifest(obj/structure/closet/crate/C) - var/obj/item/paper/fluff/jobs/cargo/manifest/P = new(C, id, pack.cost) +/datum/supply_order/proc/generateManifest(obj/container, owner, packname) //generates-the-manifests. + var/obj/item/paper/fluff/jobs/cargo/manifest/P = new(container, id, 0) var/station_name = (P.errors & MANIFEST_ERROR_NAME) ? new_station_name() : station_name() - P.name = "shipping manifest - #[id] ([pack.name])" + P.name = "shipping manifest - [packname?"#[id] ([pack.name])":"(Grouped Item Crate)"]" P.info += "