diff --git a/code/game/objects/effects/step_triggers.dm b/code/game/objects/effects/step_triggers.dm
index a1123ea4c4..76c61e9353 100644
--- a/code/game/objects/effects/step_triggers.dm
+++ b/code/game/objects/effects/step_triggers.dm
@@ -175,12 +175,9 @@ var/global/list/tele_landmarks = list() // Terrible, but the alternative is loop
if(isliving(A)) // Someday, implement parachutes. For now, just turbomurder whoever falls.
var/mob/living/L = A
- for(var/i = 1 to 6)
- L.adjustBruteLoss(100)
+ L.fall_impact(T, 42, 90, FALSE, TRUE) //You will not be defibbed from this.
message_admins("\The [A] fell out of the sky.")
- explosion(T, 0, 1, 2)
A.forceMove(T)
- T.visible_message("\A [A] falls out of the sky and crashes into \the [T]!")
else
message_admins("ERROR: planetary_fall step trigger lacks a planet to fall onto.")
return
diff --git a/code/game/objects/items/weapons/storage/backpack.dm b/code/game/objects/items/weapons/storage/backpack.dm
index e7c5d9068a..d34bb3565d 100644
--- a/code/game/objects/items/weapons/storage/backpack.dm
+++ b/code/game/objects/items/weapons/storage/backpack.dm
@@ -5,6 +5,7 @@
/obj/item/weapon/storage/backpack
name = "backpack"
desc = "You wear this on your back and put items into it."
+ icon = 'icons/obj/clothing/backpack.dmi'
icon_state = "backpack"
sprite_sheets = list(
"Teshari" = 'icons/mob/species/seromi/back.dmi'
@@ -347,6 +348,8 @@
/obj/item/weapon/storage/backpack/messenger/black
icon_state = "courierbagblk"
+
+//Purses
/obj/item/weapon/storage/backpack/purse
name = "purse"
desc = "A small, fashionable bag typically worn over the shoulder."
@@ -355,3 +358,69 @@
w_class = ITEMSIZE_LARGE
max_w_class = ITEMSIZE_NORMAL
max_storage_space = ITEMSIZE_COST_NORMAL * 5
+
+//Parachutes
+/obj/item/weapon/storage/backpack/parachute
+ name = "parachute"
+ desc = "A specially made backpack, designed to help one survive jumping from incredible heights. It sacrifices some storage space for that added functionality."
+ icon_state = "parachute"
+ item_state_slots = list(slot_r_hand_str = "backpack", slot_l_hand_str = "backpack")
+ max_storage_space = ITEMSIZE_COST_NORMAL * 5
+
+/obj/item/weapon/storage/backpack/parachute/examine(mob/user)
+ var/msg = desc
+ if(get_dist(src, user) <= 1)
+ if(parachute)
+ msg += " It seems to be packed."
+ else
+ msg += " It seems to be unpacked."
+ to_chat(user, msg)
+
+/obj/item/weapon/storage/backpack/parachute/handleParachute()
+ parachute = FALSE //If you parachute in, the parachute has probably been used.
+
+/obj/item/weapon/storage/backpack/parachute/verb/pack_parachute()
+
+ set name = "Pack/Unpack Parachute"
+ set category = "Object"
+ set src in usr
+
+ if(!istype(src.loc, /mob/living))
+ return
+
+ var/mob/living/carbon/human/H = usr
+
+ if(!istype(H))
+ return
+ if(H.stat)
+ return
+ if(H.back == src)
+ to_chat(H, "How do you expect to work on \the [src] while it's on your back?")
+ return
+
+ if(!parachute) //This packs the parachute
+ visible_message("\The [H] starts to pack \the [src]!", \
+ "You start to pack \the [src]!", \
+ "You hear the shuffling of cloth.")
+ if(do_after(H, 50))
+ visible_message("\The [H] finishes packing \the [src]!", \
+ "You finish packing \the [src]!", \
+ "You hear the shuffling of cloth.")
+ parachute = TRUE
+ else
+ visible_message("\The [src] gives up on packing \the [src]!", \
+ "You give up on packing \the [src]!")
+ return
+ else //This unpacks the parachute
+ visible_message("\The [src] starts to unpack \the [src]!", \
+ "You start to unpack \the [src]!", \
+ "You hear the shuffling of cloth.")
+ if(do_after(H, 25))
+ visible_message("\The [src] finishes unpacking \the [src]!", \
+ "You finish unpacking \the [src]!", \
+ "You hear the shuffling of cloth.")
+ parachute = FALSE
+ else
+ visible_message("\The [src] decides not to unpack \the [src]!", \
+ "You decide not to unpack \the [src]!")
+ return
\ No newline at end of file
diff --git a/code/modules/mob/living/simple_animal/animals/parrot.dm b/code/modules/mob/living/simple_animal/animals/parrot.dm
index bf84d01e0e..59e04cf1d6 100644
--- a/code/modules/mob/living/simple_animal/animals/parrot.dm
+++ b/code/modules/mob/living/simple_animal/animals/parrot.dm
@@ -56,6 +56,10 @@
meat_type = /obj/item/weapon/reagent_containers/food/snacks/cracker
+ hovering = TRUE
+ softfall = TRUE
+ parachuting = TRUE
+
var/parrot_state = PARROT_WANDER //Hunt for a perch when created
var/parrot_sleep_max = 25 //The time the parrot sits while perched before looking around. Mosly a way to avoid the parrot's AI in life() being run every single tick.
var/parrot_sleep_dur = 25 //Same as above, this is the var that physically counts down
diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm
index 089f629be5..9e9ca371ff 100644
--- a/code/modules/mob/mob_defines.dm
+++ b/code/modules/mob/mob_defines.dm
@@ -211,4 +211,9 @@
var/mob_size = MOB_MEDIUM
var/forbid_seeing_deadchat = FALSE // Used for lings to not see deadchat, and to have ghosting behave as if they were not really dead.
- var/seedarkness = 1 //Determines mob's ability to see shadows. 1 = Normal vision, 0 = darkvision
\ No newline at end of file
+ var/seedarkness = 1 //Determines mob's ability to see shadows. 1 = Normal vision, 0 = darkvision
+
+ // Falling things
+ var/hovering = FALSE // Is the mob floating or flying in some way? If so, don't fall normally. //Not implemented yet, idea is to let them ignore terrain slowdown and falling down floors
+ var/softfall = FALSE // Is the mob able to lessen their impact upon falling?
+ var/parachuting = FALSE // Is the mob able to jump out of planes and survive? Don't check this directly outside of CanParachute().
\ No newline at end of file
diff --git a/code/modules/multiz/movement.dm b/code/modules/multiz/movement.dm
index 0d24cea1e4..25d099155d 100644
--- a/code/modules/multiz/movement.dm
+++ b/code/modules/multiz/movement.dm
@@ -285,26 +285,141 @@
/obj/structure/stairs/CheckFall(var/atom/movable/falling_atom)
return 1
-// Called by CheckFall when we actually hit something. Oof
-/atom/movable/proc/fall_impact(var/atom/hit_atom)
- visible_message("\The [src] falls from above and slams into \the [hit_atom]!", "You hear something slam into \the [hit_atom].")
+// Can't fall onto ghosts
+/mob/observer/dead/CheckFall()
+ return 0
+
+// Called by CheckFall when we actually hit something. Various Vars will be described below
+// hit_atom is the thing we fall on
+// damage_min is the smallest amount of damage a thing (currently only mobs and mechs) will take from falling
+// damage_max is the largest amount of damage a thing (currently only mobs and mechs) will take from falling.
+// If silent is True, the proc won't play sound or give a message.
+// If planetary is True, it's harder to stop the fall damage
+
+/atom/movable/proc/fall_impact(var/atom/hit_atom, var/damage_min = 0, var/damage_max = 10, var/silent = FALSE, var/planetary = FALSE)
+ if(!silent)
+ visible_message("\The [src] falls from above and slams into \the [hit_atom]!", "You hear something slam into \the [hit_atom].")
// Take damage from falling and hitting the ground
-/mob/living/carbon/human/fall_impact(var/turf/landing)
- visible_message("\The [src] falls from above and slams into \the [landing]!", \
- "You fall off and hit \the [landing]!", \
- "You hear something slam into \the [landing].")
- playsound(loc, "punch", 25, 1, -1)
- var/damage = 15 // Because wounds heal rather quickly, 15 should be enough to discourage jumping off but not be enough to ruin you, at least for the first time.
- apply_damage(rand(0, damage), BRUTE, BP_HEAD)
- apply_damage(rand(0, damage), BRUTE, BP_TORSO)
- apply_damage(rand(0, damage), BRUTE, BP_L_LEG)
- apply_damage(rand(0, damage), BRUTE, BP_R_LEG)
- apply_damage(rand(0, damage), BRUTE, BP_L_ARM)
- apply_damage(rand(0, damage), BRUTE, BP_R_ARM)
- Weaken(4)
- updatehealth()
+/mob/living/fall_impact(var/turf/landing, var/damage_min = 0, var/damage_max = 30, var/silent = FALSE, var/planetary = FALSE)
+ if(planetary && src.CanParachute())
+ if(!silent)
+ visible_message("\The [src] glides in from above and lands on \the [landing]!", \
+ "You land on \the [landing]!", \
+ "You hear something land \the [landing].")
+ return
+ else if(!planetary && src.softfall) // Falling one floor and falling one atmosphere are very different things
+ if(!silent)
+ visible_message("\The [src] falls from above and lands on \the [landing]!", \
+ "You land on \the [landing]!", \
+ "You hear something land \the [landing].")
+ return
+ else
+ if(!silent)
+ if(planetary)
+ visible_message("\A [src] falls out of the sky and crashes into \the [landing]!", \
+ " You fall out of the skiy and crash into \the [landing]!", \
+ "You hear something slam into \the [landing].")
+ var/turf/T = get_turf(landing)
+ explosion(T, 0, 1, 2)
+ else
+ visible_message("\The [src] falls from above and slams into \the [landing]!", \
+ "You fall off and hit \the [landing]!", \
+ "You hear something slam into \the [landing].")
+ playsound(loc, "punch", 25, 1, -1)
+ if(planetary) //Since the planetary fall damage is calibrated for humans, we need to up this a bit
+ damage_min *= 2
+ damage_max *= 2
+
+ adjustBruteLoss(rand(damage_min, damage_max))
+ return
+ return
+
+/mob/living/carbon/human/fall_impact(var/turf/landing, var/damage_min = 0, var/damage_max = 10, var/silent = FALSE, var/planetary = FALSE)
+ if(planetary && src.CanParachute())
+ if(!silent)
+ visible_message("\The [src] glides in from above and lands on \the [landing]!", \
+ "You land on \the [landing]!", \
+ "You hear something land \the [landing].")
+ return
+ else if(!planetary && src.softfall) // Falling one floor and falling one atmosphere are very different things
+ if(!silent)
+ visible_message("\The [src] falls from above and lands on \the [landing]!", \
+ "You land on \the [landing]!", \
+ "You hear something land \the [landing].")
+ return
+ else
+ if(!silent)
+ if(planetary)
+ visible_message("\A [src] falls out of the sky and crashes into \the [landing]!", \
+ " You fall out of the skiy and crash into \the [landing]!", \
+ "You hear something slam into \the [landing].")
+ var/turf/T = get_turf(landing)
+ explosion(T, 0, 1, 2)
+ else
+ visible_message("\The [src] falls from above and slams into \the [landing]!", \
+ "You fall off and hit \the [landing]!", \
+ "You hear something slam into \the [landing].")
+ playsound(loc, "punch", 25, 1, -1)
+
+ // Because wounds heal rather quickly, 10 should be enough to discourage jumping off but not be enough to ruin you, at least for the first time.
+ apply_damage(rand(damage_min, damage_max), BRUTE, BP_HEAD)
+ apply_damage(rand(damage_min, damage_max), BRUTE, BP_TORSO)
+ apply_damage(rand(damage_min, damage_max), BRUTE, BP_GROIN)
+ apply_damage(rand(damage_min, damage_max), BRUTE, BP_L_LEG)
+ apply_damage(rand(damage_min, damage_max), BRUTE, BP_R_LEG)
+ apply_damage(rand(damage_min, damage_max), BRUTE, BP_L_FOOT)
+ apply_damage(rand(damage_min, damage_max), BRUTE, BP_R_FOOT)
+ apply_damage(rand(damage_min, damage_max), BRUTE, BP_L_ARM)
+ apply_damage(rand(damage_min, damage_max), BRUTE, BP_R_ARM)
+ apply_damage(rand(damage_min, damage_max), BRUTE, BP_L_HAND)
+ apply_damage(rand(damage_min, damage_max), BRUTE, BP_R_HAND)
+ Weaken(4)
+ updatehealth()
+ return
+ return
+
+//Checks if the mob is allowed to survive a fall from space
+/mob/living/proc/CanParachute()
+ return parachuting
+
+//For humans, this needs to be a wee bit more complicated
+/mob/living/carbon/human/CanParachute()
+ //Certain slots don't really need to be checked for parachute ability, i.e. pockets, ears, etc. If this changes, just add them to the loop, I guess?
+ //This is done in Priority Order, so items lower down the list don't call handleParachute() unless they're actually used.
+ if(back && back.isParachute())
+ back.handleParachute()
+ return TRUE
+ if(s_store && s_store.isParachute())
+ back.handleParachute()
+ return TRUE
+ if(belt && belt.isParachute())
+ back.handleParachute()
+ return TRUE
+ if(wear_suit && wear_suit.isParachute())
+ back.handleParachute()
+ return TRUE
+ if(w_uniform && w_uniform.isParachute())
+ back.handleParachute()
+ return TRUE
+ else
+ return parachuting
+
+//For human falling code
+//Using /obj instead of /obj/item because I'm not sure what all humans can pick up or wear
+/obj
+ var/parachute = FALSE
+
+/obj/proc/isParachute()
+ return parachute
+
+//This is what makes the parachute items know they've been used.
+//I made it /atom/movable so it can be retooled for other things (mobs, mechs, etc), though it's only currently called in human/CanParachute().
+/atom/movable/proc/handleParachute()
+ return
+
+//Mech Code
/obj/mecha/handle_fall(var/turf/landing)
// First things first, break any lattice
var/obj/structure/lattice/lattice = locate(/obj/structure/lattice, loc)
@@ -316,7 +431,7 @@
// Then call parent to have us actually fall
return ..()
-/obj/mecha/fall_impact(var/atom/hit_atom)
+/obj/mecha/fall_impact(var/atom/hit_atom, var/damage_min = 15, var/damage_max = 30, var/silent = FALSE, var/planetary = FALSE)
// Tell the pilot that they just dropped down with a superheavy mecha.
if(occupant)
to_chat(occupant, "\The [src] crashed down onto \the [hit_atom]!")
@@ -328,9 +443,9 @@
L.Weaken(8)
// Now to hurt the mech.
- take_damage(rand(15, 30))
+ take_damage(rand(damage_min, damage_max))
// And hurt the floor.
if(istype(hit_atom, /turf/simulated/floor))
var/turf/simulated/floor/ground = hit_atom
- ground.break_tile()
+ ground.break_tile()
\ No newline at end of file
diff --git a/icons/mob/back.dmi b/icons/mob/back.dmi
index ac581ad8e8..ced2f3683c 100644
Binary files a/icons/mob/back.dmi and b/icons/mob/back.dmi differ
diff --git a/icons/obj/clothing/backpack.dmi b/icons/obj/clothing/backpack.dmi
new file mode 100644
index 0000000000..a5b109af2b
Binary files /dev/null and b/icons/obj/clothing/backpack.dmi differ
diff --git a/icons/obj/storage.dmi b/icons/obj/storage.dmi
index d15a86e2f3..c9e19dd40b 100644
Binary files a/icons/obj/storage.dmi and b/icons/obj/storage.dmi differ