diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm
index 541d89f293e6..2083dda5dd65 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 b11c4bc24a7c..b31673d3312d 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 000000000000..973f3c0229f0
--- /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 d5362e35b5c6..806767153a13 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 5060d8c32ccb..d6faf63bfe0f 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 000000000000..b6d0e9487d63
--- /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 000000000000..9c9a26c13250
--- /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 000000000000..6c6068eadd7a
--- /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 a27d3415cf3b..8b2e2b0043c9 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 62920cbf20a3..b2e0d6fdfee0 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 33c3f0fe2601..137c33a52e16 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 0d84f42eeb93..5580f600dfbb 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 d472f52f9fd9..90499093986d 100644
Binary files a/icons/obj/library.dmi and b/icons/obj/library.dmi differ
diff --git a/paradise.dme b/paradise.dme
index a841a4e9ba64..11f99b4df6bf 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"