diff --git a/__DEFINES/setup.dm b/__DEFINES/setup.dm index d404cb5dd38..28ade639901 100644 --- a/__DEFINES/setup.dm +++ b/__DEFINES/setup.dm @@ -52,6 +52,7 @@ var/global/disable_vents = 0 #define HAZARD_LOW_PRESSURE 20 //This is when the black ultra-low pressure icon is displayed. (This one is set as a constant) #define TEMPERATURE_DAMAGE_COEFFICIENT 1.5 //This is used in handle_temperature_damage() for humans, and in reagents that affect body temperature. Temperature damage is multiplied by this amount. +#define SPLASH_SCALD_DAMAGE_COEFFICIENT (2/3) //Reagent scalding damage via splashing is multiplied by this. #define BODYTEMP_AUTORECOVERY_DIVISOR 0.5 //This is the divisor which handles how much of the temperature difference between the current body temperature and 310.15K (optimal temperature) humans auto-regenerate each tick. The higher the number, the slower the recovery. This is applied each tick, so long as the mob is alive. #define BODYTEMP_AUTORECOVERY_MAXIMUM 2.0 //Maximum amount of kelvin moved toward 310.15K per tick. So long as abs(310.15 - bodytemp) is more than 0.5 . diff --git a/code/game/objects/items/weapons/pan.dm b/code/game/objects/items/weapons/pan.dm index 0dc4d17a1ab..663aafc802a 100644 --- a/code/game/objects/items/weapons/pan.dm +++ b/code/game/objects/items/weapons/pan.dm @@ -38,6 +38,7 @@ /obj/item/weapon/reagent_containers/food/condiment, /obj/item/weapon/reagent_containers/syringe, /obj/item/weapon/reagent_containers/dropper) + var/open_container_override = FALSE /obj/item/weapon/reagent_containers/pan/New() @@ -258,15 +259,22 @@ if((dropper.a_intent != I_HELP) && ismob(target)) var/mob/M = target M.visible_message( \ - "The contents of [src] spill out onto [M][spanclass == "warning" ? "!" : "."]", \ - "The contents of [src] spill out onto you[spanclass == "warning" ? "!" : "."]") + "[src]'s contents spill out onto [M][spanclass == "warning" ? "!" : "."]", \ + "[src]'s contents spill out onto you[spanclass == "warning" ? "!" : "."]") //otherwise, say that the wielder spills it onto the target else dropper.visible_message( \ - "[dropper] [splashverb][target ? "" : " out"] the contents of [src][target ? " onto [target == dropper ? get_reflexive_pronoun(dropper.gender) : target]" : ""][spanclass == "warning" ? "!" : "."]", \ - "You [shift_verb_tense(splashverb)][target ? "" : " out"] the contents of [src][target ? " onto [target == dropper ? "yourself" : target]" : ""].") + "[dropper] [splashverb][target ? "" : " out"] [src]'s contents [target ? " onto [target == dropper ? get_reflexive_pronoun(dropper.gender) : target]" : ""][spanclass == "warning" ? "!" : "."]", \ + "You [shift_verb_tense(splashverb)][target ? "" : " out"] [src]'s contents [target ? " onto [target == dropper ? "yourself" : target]" : ""].") else - visible_message("Everything [splashverb] out of [src] [target ? "onto [target]" : ""]!") + var/mob/living/carbon/on_head_someone = is_on_someones_head() + if (on_head_someone) + spanclass = "notice" + on_head_someone.visible_message( \ + "[src]'s contents spill out onto [on_head_someone][spanclass == "warning" ? "!" : "."]", \ + "[src]'s contents spill out onto you[spanclass == "warning" ? "!" : "."]") + else + visible_message("[src]'s contents [shift_verb_tense(splashverb)] out[target ? " onto [target]" : ""]!") cook_abort() //sanity update_icon() @@ -276,7 +284,7 @@ var/datum/organ/external/affecting = user && user.zone_sel ? user.zone_sel.selecting : null //Find what the player is aiming at - reagents.reaction(target, TOUCH, amount_override = max(0,amount), zone_sels = affecting ? list(affecting) : ALL_LIMBS) + reagents.reaction(target, TOUCH, amount_override = max(0,amount), zone_sels = affecting ? list(affecting) : (is_on_someones_head() ? LIMB_HEAD : ALL_LIMBS)) if(user) user.investigation_log(I_CHEMS, "has splashed [amount > 0 ? "[amount]u of [reagents.get_reagent_ids()]" : "[reagents.get_reagent_ids(1)]"] from \a [reagents.my_atom] \ref[reagents.my_atom] onto \the [target][ishuman(target) ? "'s [parse_zone(affecting)]" : ""].") @@ -446,7 +454,13 @@ . = .. () chef = user if(slot == slot_head) + //Have to temporarily change a few values to get this to work properly. + open_container_override = TRUE + var/prev_heat_conductivity = heat_conductivity + heat_conductivity = 1 pour_on_self(user) + open_container_override = FALSE + heat_conductivity = prev_heat_conductivity /obj/item/weapon/reagent_containers/pan/proc/pour_on_self(mob/user) drop_ingredients(target = user, dropper = null) @@ -460,7 +474,7 @@ /obj/item/weapon/reagent_containers/pan/is_open_container() if(is_on_someones_head()) - return FALSE + return open_container_override return ..() /////////////////////Areas for to consider for further expansion///////////////////// @@ -472,13 +486,12 @@ //Getting pans by crafting, cargo crates, and vending machines. //Food being ready making a steam sprite that turns to smoke and fire if left on too long. //Sizzling sound with hot reagents in the pan. - //Scalding people with hot reagents (the reagents are already heated on the pan I'm just not sure if there's a way to scald someone with hot reagents). //Body-part specific splash text and also when you dump it onto yourself upon equipping to the head. //Hot pans with glowing red sprite and extra damage. //Stuff dumping out of the pan when attacking a breakable object, window, camera, etc. //Generalize thermal transfer parameter. //Componentize cooking vessels. - //Spilling (including onto people) when thrown impacting. + //Spilling when thrown impacting. //Different cook timings based on heat, or cooking with heat transfer (defined at the recipe level?) rather than a timer. //Frying stuff in oil (could use recipes for this). //Address cases with large ingredient sprites (see the note in update_icon()). diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index 41f9868bfdd..1c424c1cb62 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -50,9 +50,6 @@ emp_act /mob/living/carbon/human/getarmor(var/def_zone, var/type) - var/armorval = 0 - var/organnum = 0 - if(def_zone) if(isorgan(def_zone)) return checkarmor(def_zone, type) @@ -61,14 +58,14 @@ emp_act //If a specific bodypart is targetted, check how that bodypart is protected and return the value. //If you don't specify a bodypart, it checks ALL your bodyparts for protection, and averages out the values + var/armorval = 0 + var/organnum = 0 for(var/datum/organ/external/organ in organs) armorval += checkarmor(organ, type) organnum++ return (armorval/max(organnum, 1)) /mob/living/carbon/human/getarmorabsorb(var/def_zone, var/type) - var/armorval = 0 - var/organnum = 0 if(def_zone) if(isorgan(def_zone)) return checkarmorabsorb(def_zone, type) @@ -77,11 +74,43 @@ emp_act //If a specific bodypart is targetted, check how that bodypart is protected and return the value. //If you don't specify a bodypart, it checks ALL your bodyparts for protection, and averages out the values + var/armorval = 0 + var/organnum = 0 for(var/datum/organ/external/organ in organs) armorval += checkarmorabsorb(organ, type) organnum++ return (armorval/max(organnum, 1)) +/mob/living/carbon/human/proc/getthermalprot(var/def_zone) + if(def_zone) + if(isorgan(def_zone)) + return checkthermalprot(def_zone) + var/datum/organ/external/affecting = get_organ(ran_zone(def_zone)) + return checkthermalprot(affecting) + //If a specific bodypart is targetted, check how that bodypart is protected and return the value. + + //If you don't specify a bodypart, it checks ALL your bodyparts for protection, and averages out the values + var/thermal_prot = 0 + var/organnum = 0 + for(var/datum/organ/external/organ in organs) + thermal_prot += checkthermalprot(organ, type) + organnum++ + return (thermal_prot/max(organnum, 1)) + +/mob/living/carbon/human/proc/checkthermalprot(var/datum/organ/external/def_zone) + var/thermal_pass = 1 //1 means no protection, 0 means total protection + for(var/ci in get_clothing_items()) + if(isitem(ci)) + var/obj/item/C = ci + if(C.body_parts_covered & def_zone.body_part) + thermal_pass *= C.heat_conductivity + if(istype(C, /obj/item/clothing)) + var/obj/item/clothing/CC = C + for(var/obj/item/clothing/accessory/A in CC.accessories) + if(A.body_parts_covered & def_zone.body_part) + thermal_pass *= A.heat_conductivity + return thermal_pass + /mob/living/carbon/human/proc/get_siemens_coefficient_organ(var/datum/organ/external/def_zone) if(!def_zone) @@ -135,7 +164,6 @@ emp_act protection += A.get_armor_absorb(type) return protection - /mob/living/carbon/human/proc/check_body_part_coverage(var/body_part_flags=0, var/obj/item/ignored) if(!body_part_flags) return 0 diff --git a/code/modules/mob/living/helper_procs.dm b/code/modules/mob/living/helper_procs.dm index 6d91467285d..d5a79c73c4d 100644 --- a/code/modules/mob/living/helper_procs.dm +++ b/code/modules/mob/living/helper_procs.dm @@ -45,6 +45,9 @@ default behaviour is: if(stat == DEAD || health <= config.health_threshold_crit) return TRUE +/mob/living/proc/get_splash_burn_damage(splash_vol, splash_temp) + return round(TEMPERATURE_DAMAGE_COEFFICIENT * SPLASH_SCALD_DAMAGE_COEFFICIENT * abs(splash_vol ** (1/3) * log(get_safe_temperature_excursion(splash_temp) + 1))) + /mob/living/proc/get_safe_temperature_excursion(the_temp) //Returns how many degrees K a temperature is outside of the safe range the mob can tolerate. returns 0 if within the safe range. can be negative for cold. return 0 @@ -74,3 +77,4 @@ default behaviour is: else if (the_temp < BODYTEMP_COLD_DAMAGE_LIMIT) return the_temp - BODYTEMP_COLD_DAMAGE_LIMIT return 0 + \ No newline at end of file diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 61cdec247cf..d4ba3c73668 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -46,7 +46,6 @@ return final_damage - /mob/living/bullet_act(var/obj/item/projectile/P, var/def_zone) var/obj/item/weapon/cloaking_device/C = locate(/obj/item/weapon/cloaking_device) in src if(C && C.active) @@ -141,7 +140,7 @@ return if(!O.fingerprintslast) return - + var/client/assailant = directory[ckey(O.fingerprintslast)] if(assailant && assailant.ckey && assailant.mob) msg_admin_attack("[src.name] ([src.ckey]) was hit by a thrown [O], last touched by [assailant.mob.name] ([assailant.ckey]) (speed: [speed]) (JMP)") diff --git a/code/modules/reagents/Chemistry-Holder.dm b/code/modules/reagents/Chemistry-Holder.dm index 3f41da8bae8..80c3e449946 100644 --- a/code/modules/reagents/Chemistry-Holder.dm +++ b/code/modules/reagents/Chemistry-Holder.dm @@ -580,30 +580,77 @@ trans_to_atmos(var/datum/gas_mixture/target, var/amount=1, var/multiplier=1, var return 0 /datum/reagents/proc/reaction(var/atom/A, var/method=TOUCH, var/volume_modifier=0, var/amount_override = 0, var/list/zone_sels = ALL_LIMBS) - for(var/datum/reagent/R in reagent_list) - var/amount_splashed = (R.volume+volume_modifier) - if (amount_override) - amount_splashed = amount_override - if(ismob(A) && R) - if(isanimal(A) && R) + if (isliving(A)) + handle_reagents_mob_thermal_interaction(A, method, zone_sels, amount_override) + for (var/datum/reagent/R in reagent_list) + var/amount_splashed = amount_override ? amount_override : (R.volume + volume_modifier) + if (ismob(A)) + if (isanimal(A)) R.reaction_animal(A, method, amount_splashed) else R.reaction_mob(A, method, amount_splashed, zone_sels) - if(isturf(A) && R) + else if (isturf(A)) R.reaction_turf(A, amount_splashed) - if(istype(A, /obj) && R) + else if (istype(A, /obj)) R.reaction_obj(A, amount_splashed) /datum/reagents/proc/reaction_dropper(var/atom/A, var/volume_modifier=0) - if(ismob(A)) + if (ismob(A)) + if (isliving(A)) + handle_reagents_mob_thermal_interaction(A, TOUCH, TARGET_EYES) for(var/datum/reagent/R in reagent_list) R.reaction_dropper_mob(A) - if(istype(A, /obj)) + else if(istype(A, /obj)) for(var/datum/reagent/R in reagent_list) R.reaction_dropper_obj(A, R.volume+volume_modifier) +#define SCALD_PAINFUL 15 +#define SCALD_AGONIZING 30 + +/datum/reagents/proc/handle_reagents_mob_thermal_interaction(mob/living/L, method, list/zone_sels = ALL_LIMBS, volume_used) + if (!total_volume) + return + if (!isliving(L)) + return + var/ignore_thermal_prot = FALSE + if (method == INGEST) //Eating or drinking burns the mouth (head) regardless of targeting and isn't blocked by head thermal protection. + zone_sels = TARGET_MOUTH + ignore_thermal_prot = TRUE + var/burn_dmg = L.get_splash_burn_damage(volume_used ? volume_used : total_volume, chem_temp) + var/datum/organ/external/which_organ + if (ishuman(L)) //Although monkeys can wear clothes, only humans have explicit organs that can be covered by specific worn items, so for now only humans get protection here. If this is expanded to include things like monkeys wearing clothes and getting non-organ-specific thermal protection, this could be changed to use type inheritance. + var/mob/living/carbon/human/H = L + which_organ = H.get_organ(pick(zone_sels)) + if (!ignore_thermal_prot) + burn_dmg = round(burn_dmg * H.getthermalprot(which_organ)) + if (burn_dmg) + if(ishuman(L)) + var/mob/living/carbon/human/H = L + var/post_mod_predicted_dmg = burn_dmg * L.burn_damage_modifier + var/custom_pain_msg + if (post_mod_predicted_dmg >= SCALD_AGONIZING) + var/first = pick("A searing", "A roaring", "A blazing", "An exquisite") + var/list/second_list = list("torrent", "sea", "wall", "inferno", "river") + if (first != "A roaring") + second_list += "roar" //don't say "roaring roar" + custom_pain_msg = "[first] [pick(second_list)] of agony [pick("envelops", "cascades through", "courses through", "rushes into", "consumes", "fills")] [which_organ ? "your " + which_organ.display_name : "you"]!" + else if (post_mod_predicted_dmg >= SCALD_PAINFUL) + var/second = pick("rush", "wave", "lance", "spike") + var/list/third_list = list("shoots through", "stabs through", "washes through") + if (second != "lance") + third_list += "lances through" //don't say "lance lances" + custom_pain_msg = "[pick("A burning", "A searing", "A boiling")] [second] of pain [pick(third_list)] [which_organ ? "your " + which_organ.display_name : "you"]!" + else + custom_pain_msg = "Pain sears [which_organ ? " your " + which_organ.display_name : ""]!" + H.custom_pain(custom_pain_msg, post_mod_predicted_dmg >= SCALD_AGONIZING, post_mod_predicted_dmg >= SCALD_PAINFUL) + L.apply_effect(burn_dmg * 5, AGONY) //pain + L.apply_damage(burn_dmg, BURN, which_organ) + +#undef SCALD_PAINFUL +#undef SCALD_AGONIZING + /datum/reagents/proc/get_equalized_temperature(temperature_A, thermalmass_A, temperature_B, thermalmass_B) //Gets the equalized temperature of two thermal masses if(temperature_A == temperature_B) diff --git a/code/modules/reagents/machinery/chem_master.dm b/code/modules/reagents/machinery/chem_master.dm index c5724f2e3b1..6b6f7e019e8 100644 --- a/code/modules/reagents/machinery/chem_master.dm +++ b/code/modules/reagents/machinery/chem_master.dm @@ -421,6 +421,20 @@ var/global/list/pillIcon2Name = list("oblong purple-pink", "oblong green-white", var/logged_message = " - [key_name(usr)] has made [count] pill[count > 1 ? "s, each" : ""] named '[name]' and containing " + //Bring the pills to ambient temperature, due to contact with the pilling machinery. + var/datum/gas_mixture/A = return_air() + if (A) + if(abs(reagents.chem_temp - A.temperature) < MINIMUM_TEMPERATURE_DELTA_TO_CONSIDER) + return + var/new_temp + if (istype(A, /datum/gas_mixture/unsimulated)) + new_temp = A.temperature + else + new_temp = reagents.get_equalized_temperature(reagents.chem_temp, reagents.get_thermal_mass(), A.temperature, A.heat_capacity()) + A.temperature = new_temp + reagents.chem_temp = new_temp + reagents.handle_reactions() + while(count>0) count-- if(amount_per_pill == 0 || reagents.total_volume == 0) @@ -439,7 +453,7 @@ var/global/list/pillIcon2Name = list("oblong purple-pink", "oblong green-white", P.forceMove(loaded_pill_bottle) if(count == 0) //only do this ONCE logged_message += "[P.reagents.get_reagent_ids(1)]. Icon: [pillIcon2Name[text2num(pillsprite)]]" - + investigation_log(I_CHEMS, logged_message) @@ -478,7 +492,7 @@ var/global/list/pillIcon2Name = list("oblong purple-pink", "oblong green-white", bottletype.pixel_y = rand(-7, 7) * PIXEL_MULTIPLIER //bottletype.icon_state = "bottle"+bottlesprite reagents.trans_to(bottletype,amount_per_bottle) - + src.updateUsrDialog() return 1 @@ -490,10 +504,10 @@ var/global/list/pillIcon2Name = list("oblong purple-pink", "oblong green-white", if(href_list["createpacket_multiple"]) count = isgoodnumber(input("Select the number of packets to make.", "Amount:", 10) as num) count = clamp(count, 1, 10) - + var/amount_per_packet = reagents.total_volume > 0 ? reagents.total_volume/count : 0 amount_per_packet = min(amount_per_packet,5) - + var/name = stripped_input(usr,"Name:", "Name your packet!","[reagents.get_master_reagent_name()] ([amount_per_packet] units)") if(!name) to_chat(usr, "[bicon(src)] Invalid name!") diff --git a/code/modules/reagents/reagent_containers.dm b/code/modules/reagents/reagent_containers.dm index 4f7bf2ba706..7fff81378fe 100644 --- a/code/modules/reagents/reagent_containers.dm +++ b/code/modules/reagents/reagent_containers.dm @@ -258,6 +258,8 @@ var/list/LOGGED_SPLASH_REAGENTS = list(FUEL, THERMITE) var/tx_amount = transfer_sub(target, src, reagents.maximum_volume, user) if (tx_amount > 0) to_chat(user, "You fill \the [src][src.is_full() ? " to the brim" : ""] with [tx_amount] units of the contents of \the [target].") + var/obj/machinery/cooking/deepfryer/F = target + F.empty_icon() return tx_amount // Transfer to container if (can_send /*&& target.reagents**/)