From d42d2f647dc215ea0cf5613527f7f528537afc98 Mon Sep 17 00:00:00 2001 From: Qwertytoforty <52090703+Qwertytoforty@users.noreply.github.com> Date: Wed, 13 Sep 2023 16:32:30 -0400 Subject: [PATCH] Added the Combat Bakery Kit (#21901) * QUASO * Apply suggestions from code review Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com> * blame lewc for giving me spaces * Update code/datums/uplink_items/uplink_traitor.dm Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com> * Update code/datums/components/boomerang.dm Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Henri215 <77684085+Henri215@users.noreply.github.com> * Update code/game/objects/items/granters/_granters.dm Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com> * purge that --------- Co-authored-by: Luc <89928798+lewcc@users.noreply.github.com> Co-authored-by: Burzah <116982774+Burzah@users.noreply.github.com> Co-authored-by: Henri215 <77684085+Henri215@users.noreply.github.com> --- code/__DEFINES/dcs/signals.dm | 2 + code/controllers/subsystem/SSthrowing.dm | 1 + code/datums/components/boomerang.dm | 86 ++++++++++++++ code/datums/spells/charge.dm | 10 ++ code/datums/uplink_items/uplink_traitor.dm | 9 ++ code/game/objects/items/granters/_granters.dm | 106 ++++++++++++++++++ .../crafting_granters/_crafting_granter.dm | 18 +++ .../crafting_granters/combat_baking.dm | 19 ++++ .../items/weapons/storage/uplink_kits.dm | 5 + code/modules/crafting/recipes.dm | 11 ++ .../food_and_drinks/food/foods/baked_goods.dm | 10 ++ .../food_and_drinks/food/foods/bread.dm | 8 ++ icons/obj/library.dmi | Bin 26447 -> 26767 bytes paradise.dme | 4 + 14 files changed, 289 insertions(+) create mode 100644 code/datums/components/boomerang.dm create mode 100644 code/game/objects/items/granters/_granters.dm create mode 100644 code/game/objects/items/granters/crafting_granters/_crafting_granter.dm create mode 100644 code/game/objects/items/granters/crafting_granters/combat_baking.dm diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index 541d89f293e..2083dda5dd6 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -281,6 +281,8 @@ #define COMPONENT_CANCEL_THROW (1<<0) ///from base of atom/movable/throw_at(): (datum/thrownthing, spin) #define COMSIG_MOVABLE_POST_THROW "movable_post_throw" +///from base of datum/thrownthing/finalize(): (obj/thrown_object, datum/thrownthing) used for when a throw is finished +#define COMSIG_MOVABLE_THROW_LANDED "movable_throw_landed" ///from base of atom/movable/onTransitZ(): (old_z, new_z) #define COMSIG_MOVABLE_Z_CHANGED "movable_ztransit" ///called when the movable is placed in an unaccessible area, used for stationloving: () diff --git a/code/controllers/subsystem/SSthrowing.dm b/code/controllers/subsystem/SSthrowing.dm index b11c4bc24a7..b31673d3312 100644 --- a/code/controllers/subsystem/SSthrowing.dm +++ b/code/controllers/subsystem/SSthrowing.dm @@ -142,6 +142,7 @@ SUBSYSTEM_DEF(throwing) if(callback) callback.Invoke() + SEND_SIGNAL(thrownthing, COMSIG_MOVABLE_THROW_LANDED, src) thrownthing.end_throw() /datum/thrownthing/proc/hit_atom(atom/A) diff --git a/code/datums/components/boomerang.dm b/code/datums/components/boomerang.dm new file mode 100644 index 00000000000..973f3c0229f --- /dev/null +++ b/code/datums/components/boomerang.dm @@ -0,0 +1,86 @@ +///The cooldown period between last_boomerang_throw and it's methods of implementing a rebound proc. +#define BOOMERANG_REBOUND_INTERVAL (1 SECONDS) + +/** + * If an object is given the boomerang component, it should be thrown back to the thrower after either hitting it's target, or landing on the thrown tile. + * Thrown objects should be thrown back to the original thrower with this component, a number of tiles defined by boomerang_throw_range. + */ +/datum/component/boomerang + ///How far should the boomerang try to travel to return to the thrower? + var/boomerang_throw_range = 3 + ///If this boomerang is thrown, does it re-enable the throwers throw mode? + var/thrower_easy_catch_enabled = FALSE + ///This cooldown prevents our 2 throwing signals from firing too often based on how we implement those signals within thrown impacts. + var/last_boomerang_throw = 0 + +/datum/component/boomerang/Initialize(boomerang_throw_range, thrower_easy_catch_enabled) + . = ..() + if(!isitem(parent)) //Only items support being thrown around like a boomerang, feel free to make this apply to humans later on. + return COMPONENT_INCOMPATIBLE + + //Assignments + src.boomerang_throw_range = boomerang_throw_range + src.thrower_easy_catch_enabled = thrower_easy_catch_enabled + +/datum/component/boomerang/RegisterWithParent() + RegisterSignal(parent, COMSIG_MOVABLE_POST_THROW, PROC_REF(prepare_throw)) //Collect data on current thrower and the throwing datum + RegisterSignal(parent, COMSIG_MOVABLE_THROW_LANDED, PROC_REF(return_missed_throw)) + RegisterSignal(parent, COMSIG_MOVABLE_IMPACT, PROC_REF(return_hit_throw)) + +/datum/component/boomerang/UnregisterFromParent() + UnregisterSignal(parent, list(COMSIG_MOVABLE_POST_THROW, COMSIG_MOVABLE_THROW_LANDED, COMSIG_MOVABLE_IMPACT)) + +/** + * Proc'd before the first thrown is performed in order to gather information regarding each throw as well as handle throw_mode as necessary. + * * source: Datum src from original signal call. + * * thrown_thing: The thrownthing datum from the parent object's latest throw. Updates thrown_boomerang. + * * spin: Carry over from POST_THROW, the speed of rotation on the boomerang when thrown. + */ +/datum/component/boomerang/proc/prepare_throw(datum/source, datum/thrownthing/thrown_thing, spin) + SIGNAL_HANDLER + if(thrower_easy_catch_enabled && iscarbon(thrown_thing?.thrower)) + var/mob/living/carbon/C = thrown_thing.thrower + C.throw_mode_on() + +/** + * Proc that triggers when the thrown boomerang hits an object. + * * source: Datum src from original signal call. + * * hit_atom: The atom that has been hit by the boomerang component. + * * init_throwing_datum: The thrownthing datum that originally impacted the object, that we use to build the new throwing datum for the rebound. + */ +/datum/component/boomerang/proc/return_hit_throw(datum/source, atom/hit_atom, datum/thrownthing/init_throwing_datum) + SIGNAL_HANDLER + if(world.time <= last_boomerang_throw) + return + var/obj/item/true_parent = parent + aerodynamic_swing(init_throwing_datum, true_parent) + +/** + * Proc that triggers when the thrown boomerang does not hit a target. + * * source: Datum src from original signal call. + * * throwing_datum: The thrownthing datum that originally impacted the object, that we use to build the new throwing datum for the rebound. + */ +/datum/component/boomerang/proc/return_missed_throw(datum/source, datum/thrownthing/throwing_datum) + SIGNAL_HANDLER + if(world.time <= last_boomerang_throw) + return + var/obj/item/true_parent = parent + aerodynamic_swing(throwing_datum, true_parent) + +/** + * Proc that triggers when the thrown boomerang has been fully thrown, rethrowing the boomerang back to the thrower, and producing visible feedback. + * * throwing_datum: The thrownthing datum that originally impacted the object, that we use to build the new throwing datum for the rebound. + * * hit_atom: The atom that has been hit by the boomerang'd object. + */ +/datum/component/boomerang/proc/aerodynamic_swing(datum/thrownthing/throwing_datum, obj/item/true_parent) + var/mob/thrown_by = locateUID(true_parent.thrownby) + if(istype(thrown_by)) + var/dir = get_dir(true_parent, thrown_by) + var/turf/T = get_ranged_target_turf(thrown_by, dir, 2) + addtimer(CALLBACK(true_parent, TYPE_PROC_REF(/atom/movable, throw_at), T, boomerang_throw_range, throwing_datum.speed, null, TRUE), 1) + last_boomerang_throw = world.time + BOOMERANG_REBOUND_INTERVAL + true_parent.visible_message("[true_parent] is flying back at [throwing_datum.thrower]!", \ + "You see [true_parent] fly back at you!", \ + "You hear an aerodynamic woosh!") + +#undef BOOMERANG_REBOUND_INTERVAL diff --git a/code/datums/spells/charge.dm b/code/datums/spells/charge.dm index d5362e35b5c..806767153a1 100644 --- a/code/datums/spells/charge.dm +++ b/code/datums/spells/charge.dm @@ -47,6 +47,16 @@ to_chat(L, "Glowing red letters appear on the front cover...") to_chat(L, "[pick("NICE TRY BUT NO!","CLEVER BUT NOT CLEVER ENOUGH!", "SUCH FLAGRANT CHEESING IS WHY WE ACCEPTED YOUR APPLICATION!", "CUTE!", "YOU DIDN'T THINK IT'D BE THAT EASY, DID YOU?")]") burnt_out = TRUE + else if(istype(item, /obj/item/book/granter)) + var/obj/item/book/granter/I = item + if(prob(80)) + L.visible_message("[I] catches fire!") + qdel(I) + else + I.uses += 1 + charged_item = I + break + else if(istype(item, /obj/item/gun/magic)) var/obj/item/gun/magic/I = item if(prob(80) && !I.can_charge) diff --git a/code/datums/uplink_items/uplink_traitor.dm b/code/datums/uplink_items/uplink_traitor.dm index 5060d8c32cc..d6faf63bfe0 100644 --- a/code/datums/uplink_items/uplink_traitor.dm +++ b/code/datums/uplink_items/uplink_traitor.dm @@ -67,6 +67,15 @@ cost = 50 job = list("Mime") +/datum/uplink_item/jobspecific/combat_baking + name = "Combat Bakery Kit" + desc = "A kit of clandestine baked weapons. Contains a baguette which a skilled mime could use as a sword, \ + a pair of throwing croissants, and the recipe to make more on demand. Once the job is done, eat the evidence." + reference = "CBK" + item = /obj/item/storage/box/syndie_kit/combat_baking + cost = 25 //A chef can get a knife that sharp easily, though it won't block. While you can get endless boomerang, they are less deadly than a stech, and slower / more predictable. + job = list("Mime", "Chef") + /datum/uplink_item/jobspecific/pressure_mod name = "Kinetic Accelerator Pressure Mod" desc = "A modification kit which allows Kinetic Accelerators to do greatly increased damage while indoors. Occupies 35% mod capacity." diff --git a/code/game/objects/items/granters/_granters.dm b/code/game/objects/items/granters/_granters.dm new file mode 100644 index 00000000000..b6d0e9487d6 --- /dev/null +++ b/code/game/objects/items/granters/_granters.dm @@ -0,0 +1,106 @@ +/** + * Books that teach things. + * + * (Intrinsic actions like bar flinging, spells like fireball or smoke, or martial arts) + */ +/obj/item/book/granter + /// Flavor messages displayed to mobs reading the granter + var/list/remarks = list() + /// Controls how long a mob must keep the book in his hand to actually successfully learn + var/pages_to_mastery = 3 + /// Sanity, whether it's currently being read + var/reading = FALSE + /// The amount of uses on the granter. + var/uses = 1 + /// The time it takes to read the book + var/reading_time = 5 SECONDS + /// The sounds played as the user's reading the book. + var/list/book_sounds = list( + 'sound/effects/pageturn1.ogg', + 'sound/effects/pageturn2.ogg', + 'sound/effects/pageturn3.ogg' + ) + +/obj/item/book/granter/attack_self(mob/living/user) + if(reading) + to_chat(user, "You're already reading this!") + return FALSE + if(!user.has_vision()) + to_chat(user, "You are blind and can't read anything!") + return FALSE + if(!isliving(user)) + return FALSE + if(!can_learn(user)) + return FALSE + + if(uses <= 0) + recoil(user) + return FALSE + + on_reading_start(user) + reading = TRUE + for(var/i in 1 to pages_to_mastery) + if(!turn_page(user)) + on_reading_stopped() + reading = FALSE + return + if(do_after(user, reading_time, src)) + uses-- + on_reading_finished(user) + reading = FALSE + + return TRUE + +/// Called when the user starts to read the granter. +/obj/item/book/granter/proc/on_reading_start(mob/living/user) + to_chat(user, "You start reading [name]...") + +/// Called when the reading is interrupted without finishing. +/obj/item/book/granter/proc/on_reading_stopped(mob/living/user) + to_chat(user, "You stop reading...") + +/// Called when the reading is completely finished. This is where the actual granting should happen. +/obj/item/book/granter/proc/on_reading_finished(mob/living/user) + to_chat(user, "You finish reading [name]!") + +/// The actual "turning over of the page" flavor bit that happens while someone is reading the granter. +/obj/item/book/granter/proc/turn_page(mob/living/user) + playsound(user, pick(book_sounds), 30, TRUE) + + if(!do_after(user, reading_time, src)) + return FALSE + + to_chat(user, "[length(remarks) ? pick(remarks) : "You keep reading..."]") + return TRUE + +/// Effects that occur whenever the book is read when it has no uses left. +/obj/item/book/granter/proc/recoil(mob/living/user) + return + +/// Checks if the user can learn whatever this granter... grants +/obj/item/book/granter/proc/can_learn(mob/living/user) + return TRUE + +// Generic action giver +/obj/item/book/granter/action + /// The typepath of action that is given + var/datum/action/granted_action + /// The name of the action, formatted in a more text-friendly way. + var/action_name = "" + +/obj/item/book/granter/action/can_learn(mob/living/user) + if(!granted_action) + CRASH("Someone attempted to learn [type], which did not have an action set.") + if(locate(granted_action) in user.actions) + to_chat(user, "You already know all about [action_name]!") + return FALSE + return TRUE + +/obj/item/book/granter/action/on_reading_start(mob/living/user) + to_chat(user, "You start reading about [action_name]...") + +/obj/item/book/granter/action/on_reading_finished(mob/living/user) + to_chat(user, "You feel like you've got a good handle on [action_name]!") + // Action goes on the mind as the user actually learns the thing in your brain + var/datum/action/new_action = new granted_action(user.mind || user) + new_action.Grant(user) diff --git a/code/game/objects/items/granters/crafting_granters/_crafting_granter.dm b/code/game/objects/items/granters/crafting_granters/_crafting_granter.dm new file mode 100644 index 00000000000..9c9a26c1325 --- /dev/null +++ b/code/game/objects/items/granters/crafting_granters/_crafting_granter.dm @@ -0,0 +1,18 @@ +/obj/item/book/granter/crafting_recipe + /// A list of all recipe types we grant on learn + var/list/crafting_recipe_types = list() + +/obj/item/book/granter/crafting_recipe/on_reading_finished(mob/user) + ..() + if(!user.mind) + return + for(var/datum/crafting_recipe/crafting_recipe_type as anything in crafting_recipe_types) + user.mind.teach_crafting_recipe(crafting_recipe_type) + to_chat(user, "You learned how to make [initial(crafting_recipe_type.name)].") + +/obj/item/book/granter/crafting_recipe/dusting + icon_state = "book1" + +/obj/item/book/granter/crafting_recipe/dusting/recoil(mob/living/user) + to_chat(user, "The book turns to dust in your hands.") + qdel(src) diff --git a/code/game/objects/items/granters/crafting_granters/combat_baking.dm b/code/game/objects/items/granters/crafting_granters/combat_baking.dm new file mode 100644 index 00000000000..6c6068eadd7 --- /dev/null +++ b/code/game/objects/items/granters/crafting_granters/combat_baking.dm @@ -0,0 +1,19 @@ +/obj/item/book/granter/crafting_recipe/combat_baking + name = "the anarchist's cookbook" + desc = "A widely illegal recipe book which will teach you how to bake croissants to die for." + crafting_recipe_types = list( + /datum/crafting_recipe/throwing_croissant + ) + icon_state = "cooking_learing_illegal" + remarks = list( + "\"Austrian? Not French?\"", + "\"Got to get the butter ratio right...\"", + "\"This is the greatest thing since sliced bread!\"", + "\"I'll leave no trace except crumbs!\"", + "\"Who knew that bread could hurt a man so badly?\"" + ) + +/obj/item/book/granter/crafting_recipe/combat_baking/recoil(mob/living/user) + to_chat(user, "The book dissolves into burnt flour!") + new /obj/effect/decal/cleanable/ash(get_turf(src)) + qdel(src) diff --git a/code/game/objects/items/weapons/storage/uplink_kits.dm b/code/game/objects/items/weapons/storage/uplink_kits.dm index a27d3415cf3..8b2e2b0043c 100644 --- a/code/game/objects/items/weapons/storage/uplink_kits.dm +++ b/code/game/objects/items/weapons/storage/uplink_kits.dm @@ -282,6 +282,11 @@ new /obj/item/spellbook/oneuse/mime/greaterwall(src) new /obj/item/spellbook/oneuse/mime/fingergun(src) +/obj/item/storage/box/syndie_kit/combat_baking/populate_contents() + new /obj/item/reagent_containers/food/snacks/baguette/combat(src) + for(var/i in 1 to 2) + new /obj/item/reagent_containers/food/snacks/croissant/throwing(src) + new /obj/item/book/granter/crafting_recipe/combat_baking(src) /obj/item/storage/box/syndie_kit/atmosn2ogrenades name = "atmos N2O grenades" diff --git a/code/modules/crafting/recipes.dm b/code/modules/crafting/recipes.dm index 62920cbf20a..b2e0d6fdfee 100644 --- a/code/modules/crafting/recipes.dm +++ b/code/modules/crafting/recipes.dm @@ -67,6 +67,17 @@ category = CAT_WEAPONRY subcategory = CAT_WEAPON +/datum/crafting_recipe/throwing_croissant + name = "Throwing croissant" + reqs = list( + /obj/item/reagent_containers/food/snacks/croissant = 1, + /obj/item/stack/rods = 1 + ) + result = list(/obj/item/reagent_containers/food/snacks/croissant) + category = CAT_WEAPONRY + subcategory = CAT_WEAPON + always_availible = FALSE + /datum/crafting_recipe/advancedegun name = "Advanced Energy Gun" tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER) diff --git a/code/modules/food_and_drinks/food/foods/baked_goods.dm b/code/modules/food_and_drinks/food/foods/baked_goods.dm index 33c3f0fe260..137c33a52e1 100644 --- a/code/modules/food_and_drinks/food/foods/baked_goods.dm +++ b/code/modules/food_and_drinks/food/foods/baked_goods.dm @@ -551,5 +551,15 @@ list_reagents = list("nutriment" = 4, "sugar" = 2) tastes = list("croissant" = 1) +/obj/item/reagent_containers/food/snacks/croissant/throwing + throwforce = 20 + throw_range = 9 //now with extra throwing action + tastes = list("croissant" = 2, "butter" = 1, "metal" = 1) + list_reagents = list("nutriment" = 4, "sugar" = 2, "iron" = 1) + +/obj/item/reagent_containers/food/snacks/croissant/throwing/Initialize(mapload) + . = ..() + AddComponent(/datum/component/boomerang, throw_range, TRUE) + #undef DONUT_NORMAL #undef DONUT_FROSTED diff --git a/code/modules/food_and_drinks/food/foods/bread.dm b/code/modules/food_and_drinks/food/foods/bread.dm index 0d84f42eeb9..5580f600dfb 100644 --- a/code/modules/food_and_drinks/food/foods/bread.dm +++ b/code/modules/food_and_drinks/food/foods/bread.dm @@ -170,6 +170,14 @@ list_reagents = list("nutriment" = 6, "vitamin" = 1) tastes = list("bread" = 2) +/obj/item/reagent_containers/food/snacks/baguette/combat + sharp = TRUE + force = 20 + +/obj/item/reagent_containers/food/snacks/baguette/combat/Initialize(mapload) + . = ..() + AddComponent(/datum/component/parry, _stamina_constant = 2, _stamina_coefficient = 0.5, _parryable_attack_types = ALL_ATTACK_TYPES) + /obj/item/reagent_containers/food/snacks/twobread name = "two bread" desc = "It is very bitter and winy." diff --git a/icons/obj/library.dmi b/icons/obj/library.dmi index d472f52f9fd967bc5ea741a010eec3ee22eae107..90499093986d8acddd983ddc1a8fb830143909c8 100644 GIT binary patch delta 5476 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 Ddkh_m delta 5168 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 diff --git a/paradise.dme b/paradise.dme index a841a4e9ba6..11f99b4df6b 100644 --- a/paradise.dme +++ b/paradise.dme @@ -363,6 +363,7 @@ #include "code\datums\cache\crew.dm" #include "code\datums\cache\powermonitor.dm" #include "code\datums\components\_component.dm" +#include "code\datums\components\boomerang.dm" #include "code\datums\components\caltrop.dm" #include "code\datums\components\deadchat_control.dm" #include "code\datums\components\decal.dm" @@ -987,6 +988,9 @@ #include "code\game\objects\items\devices\radio\headset.dm" #include "code\game\objects\items\devices\radio\intercom.dm" #include "code\game\objects\items\devices\radio\radio_objects.dm" +#include "code\game\objects\items\granters\_granters.dm" +#include "code\game\objects\items\granters\crafting_granters\_crafting_granter.dm" +#include "code\game\objects\items\granters\crafting_granters\combat_baking.dm" #include "code\game\objects\items\mountable_frames\air_alarm_frame.dm" #include "code\game\objects\items\mountable_frames\apc_frame.dm" #include "code\game\objects\items\mountable_frames\buttons_switches.dm"