diff --git a/code/__defines/armor.dm b/code/__defines/armor.dm index c43e7cc2f26..e39a42430a7 100644 --- a/code/__defines/armor.dm +++ b/code/__defines/armor.dm @@ -2,12 +2,12 @@ // Some levels are marked with what they intend to block in such way. #define ARMOR_BALLISTIC_MINOR 10 -#define ARMOR_BALLISTIC_SMALL 25 -#define ARMOR_BALLISTIC_PISTOL 50 //Blocks holdout and normal pistol ammo -#define ARMOR_BALLISTIC_RESISTANT 65 -#define ARMOR_BALLISTIC_RIFLE 80 //Blocks rifle rounds -#define ARMOR_BALLISTIC_AP 95 -#define ARMOR_BALLISTIC_HEAVY 110 +#define ARMOR_BALLISTIC_SMALL 15 +#define ARMOR_BALLISTIC_PISTOL 30 +#define ARMOR_BALLISTIC_RESISTANT 60 +#define ARMOR_BALLISTIC_RIFLE 90 +#define ARMOR_BALLISTIC_AP 120 +#define ARMOR_BALLISTIC_HEAVY 150 #define ARMOR_LASER_MINOR 10 #define ARMOR_LASER_SMALL 25 //Blocks small e-guns diff --git a/code/game/objects/items/__item.dm b/code/game/objects/items/__item.dm index d9164b23c5e..29d237a4a47 100644 --- a/code/game/objects/items/__item.dm +++ b/code/game/objects/items/__item.dm @@ -225,7 +225,7 @@ for(var/type in armor) if(armor[type]) // Don't set it if it gives no armor anyway, which is many items. set_extension(src, armor_type, armor, armor_degradation_speed) - break + if(randpixel && (!pixel_x && !pixel_y) && isturf(loc)) //hopefully this will prevent us from messing with mapper-set pixel_x/y pixel_x = rand(-randpixel, randpixel) pixel_y = rand(-randpixel, randpixel) diff --git a/maps/modpack_testing/modpack_testing.dm b/maps/modpack_testing/modpack_testing.dm index 47b6b146814..3f733708096 100644 --- a/maps/modpack_testing/modpack_testing.dm +++ b/maps/modpack_testing/modpack_testing.dm @@ -12,6 +12,7 @@ #include "../../mods/content/bigpharma/_bigpharma.dme" #include "../../mods/content/biomods/_biomods.dme" #include "../../mods/content/blacksmithy/_blacksmithy.dme" + #include "../../mods/content/bosniastation/_bosniastation.dme" #include "../../mods/content/blob/_blob.dme" #include "../../mods/content/breath_holding/_breath_holding.dme" #include "../../mods/content/byond_membership/_byond_membership.dm" diff --git a/mods/content/bosniastation/_bosniastation.dm b/mods/content/bosniastation/_bosniastation.dm new file mode 100644 index 00000000000..f49eccb91c3 --- /dev/null +++ b/mods/content/bosniastation/_bosniastation.dm @@ -0,0 +1,2 @@ +/decl/modpack/bosnia + name = "Bosniastation" \ No newline at end of file diff --git a/mods/content/bosniastation/_bosniastation.dme b/mods/content/bosniastation/_bosniastation.dme new file mode 100644 index 00000000000..88169455548 --- /dev/null +++ b/mods/content/bosniastation/_bosniastation.dme @@ -0,0 +1,9 @@ +#ifndef MODPACK_BOSNIA +#define BOSNIA +// BEGIN_INCLUDE +#include "_bosniastation.dm" +#include "armor_rework.dm" +#include "organ_rework.dm" +#include "organ_tweaks.dm" +// END_INCLUDE +#endif \ No newline at end of file diff --git a/mods/content/bosniastation/armor_rework.dm b/mods/content/bosniastation/armor_rework.dm new file mode 100644 index 00000000000..29ffb5e34b3 --- /dev/null +++ b/mods/content/bosniastation/armor_rework.dm @@ -0,0 +1,383 @@ +//first step of our evil plan is to replace most armor extensions with an ablative equivalent + +/datum/extension/armor/ablative/on_blocking(damage, damage_type, damage_flags, armor_pen, blocked) + if(!(damage_type == BRUTE || damage_type == BURN)) + return + if(armor_degradation_coef) + var/key = SSmaterials.get_armor_key(damage_type, damage_flags) + var/damage_blocked = round(damage * blocked) + if(damage_blocked) + var/new_armor = max(0, get_value(key) - armor_degradation_coef * (damage_blocked + armor_pen)) + set_value(key, new_armor) + var/mob/M = holder.get_recursive_loc_of_type(/mob) + if(istype(M)) + var/list/visible = get_visible_damage() + for(var/k in visible) + if(LAZYACCESS(last_reported_damage, k) != visible[k]) + LAZYSET(last_reported_damage, k, visible[k]) + if (visible[k] == "completely destroyed") + to_chat(M, SPAN_DANGER("The [k] armor on \the [holder] is [visible[k]]!")) + else + to_chat(M, SPAN_WARNING("The [k] armor on \the [holder] has [visible[k]] damage now!")) + +/datum/extension/armor/ablative/proc/repair_damage(damage_type, arg_key) + get_damage() + var/list/keys + switch (damage_type) + if (BRUTE) + keys = list(ARMOR_BULLET, ARMOR_BOMB, ARMOR_MELEE) + if (BURN) + keys = list(ARMOR_LASER, ARMOR_BOMB, ARMOR_ENERGY) + if (TOX) + keys = list(ARMOR_BIO) + if (IRRADIATE) + keys = list(ARMOR_RAD) + if (ELECTROCUTE) + keys = list(ARMOR_ENERGY) + if (arg_key) + keys = list(arg_key) + var/success = FALSE + for (var/key in keys) + if (0 < max_armor_values[key] - armor_values[key]) + success = TRUE + set_value(key, max_armor_values[key]) + return success + +/datum/extension/armor/ablative/get_visible_damage() + var/list/damages = get_damage() + if(!LAZYLEN(damages)) + return + var/result = list() + for(var/key in damages) + switch(round(100 * damages[key]/max_armor_values[key])) + if(5 to 10) + result[key] = "minor" + if(11 to 25) + result[key] = "moderate" + if(26 to 50) + result[key] = "serious" + if(51 to 99) + result[key] = "catastrophic" + if(100) + result[key] = "completely destroyed" + return result + +/datum/extension/armor/ablative/rig + var/sealed = FALSE + +/datum/extension/armor/ablative/rig/get_value(key) + if(key == ARMOR_BIO && sealed) + return 100 + return ..() + +/obj/item/clothing/get_examine_strings(mob/user, distance, infix, suffix) + . = ..() + var/datum/extension/armor/ablative/armor_datum = get_extension(src, /datum/extension/armor) + if(istype(armor_datum, /datum/extension/armor/ablative) && length(armor_datum.get_visible_damage())) + . += SPAN_WARNING("It has some damage.") + + if(LAZYLEN(accessories)) + . += "It has the following attached: [counting_english_list(accessories)]" + + switch(ironed_state) + if(WRINKLES_WRINKLY) + . += "It's wrinkly." + if(WRINKLES_NONE) + . += "It's completely wrinkle-free!" + + var/obj/item/clothing/sensor/vitals/sensor = locate() in accessories + if(sensor) + switch(sensor.sensor_mode) + if(VITALS_SENSOR_OFF) + . += "Its sensors appear to be disabled." + if(VITALS_SENSOR_BINARY) + . += "Its binary life sensors appear to be enabled." + if(VITALS_SENSOR_VITAL) + . += "Its vital tracker appears to be enabled." + if(VITALS_SENSOR_TRACKING) + . += "Its vital tracker and tracking beacon appear to be enabled." + +#define RAG_COUNT(X) ceil((LAZYACCESS(X.matter, /decl/material/solid/organic/cloth) * 0.65) / SHEET_MATERIAL_AMOUNT) + +/obj/item/clothing/get_examine_hints(mob/user, distance, infix, suffix) + . = ..() + var/rags = RAG_COUNT(src) + if(rags) + LAZYADD(., SPAN_SUBTLE("With a sharp object, you could cut \the [src] up into [rags] section\s.")) + + if(length(clothing_state_modifiers)) + var/list/interactions = list() + for(var/modifier_type in clothing_state_modifiers) + var/decl/clothing_state_modifier/modifier = GET_DECL(modifier_type) + interactions += modifier.name + LAZYADD(., SPAN_SUBTLE("Use alt-click to [english_list(interactions, and_text = " or ")].")) + +#undef RAG_COUNT + +/obj/item/clothing/Topic(href, href_list, datum/topic_state/state) + var/mob/user = usr + if(istype(user)) + var/turf/T = get_turf(src) + var/can_see = T.CanUseTopic(user, global.view_topic_state) != STATUS_CLOSE + if(href_list["list_ungabunga"]) + if(length(accessories) && can_see) + var/list/ties = list() + for(var/accessory in accessories) + ties += "[html_icon(accessory)] \a [accessory]" + to_chat(user, "Attached to \the [src] [length(ties) == 1 ? "is" : "are"] [english_list(ties)].") + return TOPIC_HANDLED + if(href_list["list_armor_damage"] && can_see) + var/datum/extension/armor/ablative/armor_datum = get_extension(src, /datum/extension/armor) + if(istype(armor_datum)) + var/list/damages = armor_datum.get_visible_damage() + to_chat(user, "\The [src] [html_icon(src)] has some damage:") + for(var/key in damages) + if (damages[key] == "completely destroyed") + to_chat(user, "
  • [capitalize(damages[key])] [key] armor.") + else + to_chat(user, "
  • [capitalize(damages[key])] damage to the [key] armor.") + return TOPIC_HANDLED + . = ..() + +//we also change how blocking works +/datum/extension/armor/get_blocked(damage_type, damage_flags, armor_pen = 0, damage = 5) + var/key = SSmaterials.get_armor_key(damage_type, damage_flags) + if(!key) + return 0 + + var/armor = get_value(key) - armor_pen + if (damage_flags & (DAM_SHARP | DAM_LASER)) + if (armor < 0) + return 0 + if (armor > 0) + return 0.98 + else + return 0.5 + armor = max(0, armor) + if(!armor) + return 0 + + var/efficiency = min(damage / (armor_range_mult * armor), 1) + var/coef = damage <= armor ? under_armor_mult : over_armor_mult + return max(1 - coef * efficiency, 0) + +/mob/living/human/get_thickness(obj/item/organ/external/def_zone) + if(!def_zone) + def_zone = ran_zone() + if(!istype(def_zone)) + def_zone = GET_EXTERNAL_ORGAN(src, def_zone) + if(!def_zone) + return ..() + + . = list() + for(var/slot in global.standard_clothing_slots) + var/obj/item/clothing/gear = get_equipped_item(slot) + if(!istype(gear)) + continue + if(length(gear.accessories)) + for(var/obj/item/clothing/accessory in gear.accessories) + if(accessory.body_parts_covered & def_zone.body_part) + . *= accessory.agony_mod + if(gear.body_parts_covered & def_zone.body_part) + . *= gear.armor + +/mob/living/apply_damage(damage = 0, damagetype = BRUTE, def_zone, damage_flags = 0, obj/used_weapon, armor_pen, silent = FALSE, obj/item/organ/external/given_organ) + + if(status_flags & GODMODE) + return FALSE + + if(!damage) + return FALSE + var/list/before_armor = list(damage, damagetype, damage_flags) + var/list/after_armor = modify_damage_by_armor(def_zone, damage, damagetype, damage_flags, src, armor_pen, silent) + damage = after_armor[1] + damagetype = after_armor[2] + damage_flags = after_armor[3] // args modifications in case of parent calls + if(!damage) + return FALSE + + if (before_armor[3] & DAM_SHARP) + var/agony_mod = get_thickness(def_zone) + var/raw_block = before_armor[1] - after_armor[1] + take_damage(raw_block * agony_mod, PAIN) + + switch(damagetype) + if(BURN) + if(has_genetic_condition(GENE_COND_COLD_RESISTANCE)) + return + take_damage(damage, BURN, damage_flags, used_weapon, armor_pen) + if(ELECTROCUTE) + electrocute_act(damage, used_weapon, 1, def_zone) + else + take_damage(damage, damagetype, damage_flags, used_weapon, armor_pen) + return TRUE + +/mob/living/proc/get_thickness(obj/item/organ/external/def_zone) + return 0.5 + +//this is just so armor datums match up + +/mob/GetVoice() + var/voice_sub + var/obj/item/rig/rig = get_rig() + if(rig?.speech?.voice_holder?.active && rig.speech.voice_holder.voice) + voice_sub = rig.speech.voice_holder.voice + + if(!voice_sub) + + var/list/check_gear = list(get_equipped_item(slot_wear_mask_str), get_equipped_item(slot_head_str)) + if(rig) + var/datum/extension/armor/ablative/rig/armor_datum = get_extension(rig, /datum/extension/armor) + if(istype(armor_datum) && armor_datum.sealed && rig.helmet == get_equipped_item(slot_head_str)) + check_gear |= rig + + for(var/obj/item/gear in check_gear) + if(!gear) + continue + var/obj/item/voice_changer/changer = locate() in gear + if(changer && changer.active && changer.voice) + voice_sub = changer.voice + + if(voice_sub) + return voice_sub + + return real_name || name + +//repairs +/obj/item/clothing/suit/space/proc/repair_armor(var/damtype, var/mob/user) + var/datum/extension/armor/ablative/armor_datum = get_extension(src, /datum/extension/armor) + if (istype(armor_datum) && armor_datum.repair_damage(damtype, null)) + user.visible_message( + SPAN_NOTICE("\The [user] repairs the armor of \the [src]."), + SPAN_NOTICE("You repair the armor of \the [src].") + ) + +/obj/item/clothing/suit/space/attackby(obj/item/used_item, mob/user) + if(istype(used_item,/obj/item/stack/material)) + var/repair_power = 0 + switch(used_item.get_material_type()) + if(/decl/material/solid/metal/steel) + repair_power = 2 + if(/decl/material/solid/organic/plastic) + repair_power = 1 + + if(!repair_power) + return FALSE + + if(ishuman(loc)) + var/mob/living/human/H = loc + if(H.get_equipped_item(slot_wear_suit_str) == src) + to_chat(user, SPAN_WARNING("You cannot repair \the [src] while it is being worn.")) + return TRUE + + var/obj/item/stack/P = used_item + var/use_amt = min(P.get_amount(), 3) + if(!use_amt || !P.use(use_amt)) + return FALSE + + repair_armor(BURN, user) + + if(burn_damage <= 0) + to_chat(user, "There is no surface damage on \the [src] to repair.") //maybe change the descriptor to more obvious? idk what + return TRUE + + repair_breaches(BURN, use_amt * repair_power, user) + return TRUE + + else if(IS_WELDER(used_item)) + + if(ishuman(loc)) + var/mob/living/human/H = loc + if(H.get_equipped_item(slot_wear_suit_str) == src) + to_chat(user, SPAN_WARNING("You cannot repair \the [src] while it is being worn.")) + return TRUE + + var/obj/item/weldingtool/welder = used_item + if(!welder.weld(5)) + to_chat(user, SPAN_WARNING("You need more welding fuel to repair this suit.")) + return TRUE + + repair_armor(BRUTE, user) + + if (brute_damage <= 0) + to_chat(user, "There is no structural damage on \the [src] to repair.") + return TRUE + + repair_breaches(BRUTE, 3, user) + return TRUE + + else if(istype(used_item, /obj/item/stack/tape_roll/duct_tape)) + var/datum/breach/target_breach //Target the largest unpatched breach. + for(var/datum/breach/B in breaches) + if(B.patched) + continue + if(!target_breach || (B.class > target_breach.class)) + target_breach = B + + if(!target_breach) + to_chat(user, "There are no open breaches to seal with \the [used_item].") + else + var/obj/item/stack/tape_roll/duct_tape/D = used_item + var/amount_needed = ceil(target_breach.class * 2) + if(!D.can_use(amount_needed)) + to_chat(user, SPAN_WARNING("There's not enough [D.plural_name] in your [src] to seal \the [target_breach.descriptor] on \the [src]! You need at least [amount_needed] [D.plural_name].")) + return TRUE + + if(do_after(user, user.get_equipped_item(slot_wear_suit_str) == src? 6 SECONDS : 3 SECONDS, isliving(loc)? loc : null)) //Sealing a breach on your own suit is awkward and time consuming + D.use(amount_needed) + playsound(src, 'sound/effects/tape.ogg',25) + user.visible_message( + SPAN_NOTICE("\The [user] uses some [D.plural_name] to seal \the [target_breach.descriptor] on \the [src]."), + SPAN_NOTICE("You use [amount_needed] [D.plural_name] of \the [used_item] to seal \the [target_breach.descriptor] on \the [src].") + ) + target_breach.patched = TRUE + target_breach.update_descriptor() + calc_breach_damage() + return TRUE + return ..() + +//now we change clothing to match + +/obj/item/rig + armor_type = /datum/extension/armor/ablative/rig + armor_degradation_speed = 0.075 + var/agony_mod = 0.75 + +/obj/item/rig/light + agony_mod = 1 + +/obj/item/rig/merc/heavy + agony_mod = 0.5 + +/obj/item/clothing + armor_type = /datum/extension/armor/ablative + armor_degradation_speed = 0.1 + var/agony_mod = 0.9 + +/obj/item/clothing/suit/armor/bulletproof + agony_mod = 0.75 + +/obj/item/clothing/suit/armor/riot + agony_mod = 0.5 + +/obj/item/clothing/shoes/legguards/riot + agony_mod = 0.5 + +/obj/item/clothing/gloves/armguards/riot + agony_mod = 0.5 + +/obj/item/clothing/suit/bomb_suit + agony_mod = 0.75 + +/obj/item/clothing/suit/armor/forged + armor_degradation_speed = 0.2 + +/obj/item/clothing/suit/armor/crafted + armor_degradation_speed = 0.2 + +/obj/item/clothing/armor_attachment/plate + armor_degradation_speed = 0.3 + agony_mod = 1 + +/obj/item/clothing/head/helmet/riot + agony_mod = 0.5 \ No newline at end of file diff --git a/mods/content/bosniastation/organ_rework.dm b/mods/content/bosniastation/organ_rework.dm new file mode 100644 index 00000000000..600b15470e4 --- /dev/null +++ b/mods/content/bosniastation/organ_rework.dm @@ -0,0 +1,362 @@ +//we separate this horror from the rest of organ stuff as it is almost entirely one line changes in massive blocks of code but also kind of has to be a mod + +#define DAM_ARTERY 128 // Massively reduces chance of organ damage, while increasing chance of severing an artery + +/decl/grab/normal/attack_throat(var/obj/item/grab/grab, var/obj/item/used_item, mob/user) + var/mob/living/affecting = grab.get_affecting_mob() + if(!affecting) + return + if(!user.check_intent(I_FLAG_HARM)) + return 0 // Not trying to hurt them. + + if(!used_item.has_edge() || !used_item.get_attack_force(user) || used_item.atom_damage_type != BRUTE) + return 0 //unsuitable weapon + user.visible_message("\The [user] begins to slit [affecting]'s throat with \the [used_item]!") + + user.next_move = world.time + 20 //also should prevent user from triggering this repeatedly + if(!do_after(user, 20*user.skill_delay_mult(SKILL_COMBAT) , progress = 0)) + return 0 + if(!(grab && grab.affecting == affecting)) //check that we still have a grab + return 0 + + var/damage_mod = 1 + var/damage_flags = used_item.damage_flags() | DAM_ARTERY + //presumably, if they are wearing a helmet that stops pressure effects, then it probably covers the throat as well + var/force = used_item.expend_attack_force(user) + var/obj/item/clothing/head/helmet = affecting.get_equipped_item(slot_head_str) + if(istype(helmet) && (helmet.body_parts_covered & SLOT_HEAD) && (helmet.item_flags & ITEM_FLAG_AIRTIGHT) && !isnull(helmet.max_pressure_protection)) + var/datum/extension/armor/armor_datum = get_extension(helmet, /datum/extension/armor) + if(armor_datum) + damage_mod -= armor_datum.get_blocked(BRUTE, damage_flags, used_item.armor_penetration, force*1.5) + + var/total_damage = 0 + for(var/i in 1 to 3) + var/damage = min(force*1.5, 20)*damage_mod + affecting.apply_damage(damage, used_item.atom_damage_type, BP_HEAD, damage_flags, armor_pen = 100, used_weapon=used_item) + total_damage += damage + + if(total_damage) + user.visible_message("\The [user] slit [affecting]'s throat open with \the [used_item]!") + + if(used_item.hitsound) + playsound(affecting.loc, used_item.hitsound, 50, 1, -1) + + grab.last_action = world.time + + admin_attack_log(user, affecting, "Knifed their victim", "Was knifed", "knifed") + return 1 + +/obj/item/organ/external/take_damage(damage, damage_type = BRUTE, damage_flags, inflicter, armor_pen = 0, silent, do_update_health, override_droplimb) + + if(!owner) + return ..() + + var/final_brute_mod = get_brute_mod(damage_flags) + (0.2 * burn_dam/max_damage) // extra brute taken if you have burn damage. why? ask whoever originally coded it. + var/final_burn_mod = get_burn_mod(damage_flags) + + var/brute = damage_type == BRUTE ? round(damage * final_brute_mod, 0.1) : 0 + var/burn = damage_type == BURN ? round(damage * final_burn_mod, 0.1) : 0 + + if((brute <= 0) && (burn <= 0)) + return 0 + + var/sharp = (damage_flags & DAM_SHARP) + var/edge = (damage_flags & DAM_EDGE) + var/laser = (damage_flags & DAM_LASER) + var/blunt = !!(brute && !sharp && !edge) + + // Handle some status-based damage multipliers. + if(BP_IS_CRYSTAL(src) && burn && laser) + brute += burn // Stress fracturing from heat! + owner.bodytemperature += burn + burn = 0 + if(prob(25)) + owner.visible_message(SPAN_WARNING("\The [owner]'s crystalline [name] shines with absorbed energy!")) + + if(inflicter) + add_autopsy_data(inflicter, brute + burn) + + var/spillover = 0 + var/pure_brute = brute + if(!is_damageable(brute + burn)) + spillover = brute_dam + burn_dam + brute - max_damage + if(spillover > 0) + brute = max(brute - spillover, 0) + else + spillover = brute_dam + burn_dam + brute + burn - max_damage + if(spillover > 0) + burn = max(burn - spillover, 0) + + //If limb took enough damage, try to cut or tear it off + if(owner && loc == owner) + owner.update_health() //droplimb will call update_health() again if it does end up being called + if((limb_flags & ORGAN_FLAG_CAN_AMPUTATE) && get_config_value(/decl/config/toggle/on/health_limbs_can_break)) + var/total_damage = brute_dam + burn_dam + brute + burn + spillover + var/threshold = max_damage * get_config_value(/decl/config/num/health_organ_health_multiplier) + if(total_damage > threshold) + if(attempt_dismemberment(pure_brute, burn, sharp, edge, inflicter, spillover, total_damage > threshold*6, override_droplimb = override_droplimb)) + return + + //blunt damage is gud at fracturing + if(brute_dam + brute > min_broken_damage && prob(brute_dam + brute * (1+blunt)) ) + fracture() + + // High brute damage or sharp objects may damage internal organs + if(LAZYLEN(internal_organs) && damage_internal_organs(brute, burn, damage_flags)) + brute /= 2 + burn /= 2 + + if((status & ORGAN_BROKEN) && brute) + jostle_bone(brute) + if(can_feel_pain() && prob(40)) + owner.emote(/decl/emote/audible/scream) //getting hit on broken hand hurts + + // If the limbs can break, make sure we don't exceed the maximum damage a limb can take before breaking + var/datum/wound/created_wound + var/block_cut = (species.species_flags & SPECIES_FLAG_NO_MINOR_CUT) && brute <= 15 + var/can_cut = !block_cut && !BP_IS_PROSTHETIC(src) && (sharp || prob(brute)) + + if(brute) + var/to_create = BRUISE + if(can_cut) + to_create = CUT + //need to check sharp again here so that blunt damage that was strong enough to break skin doesn't give puncture wounds + if(sharp && !edge) + to_create = PIERCE + var/arterial = damage_flags & DAM_ARTERY + created_wound = createwound(to_create, brute, arterial) + + if(burn) + if(laser) + created_wound = createwound(LASER, burn) + if(prob(40)) + owner.ignite_fire() + else + created_wound = createwound(BURN, burn) + + //Initial pain spike + add_pain(0.6*burn + 0.4*brute) + + //Disturb treated burns + if(brute > 5) + var/disturbed = 0 + for(var/datum/wound/burn/wound in wounds) + if((wound.disinfected || wound.salved) && prob(brute + wound.damage)) + wound.disinfected = 0 + wound.salved = 0 + disturbed += wound.damage + if(disturbed) + to_chat(owner,"Ow! Your burns were disturbed.") + add_pain(0.5*disturbed) + + //If there are still hurties to dispense + if (spillover) + owner.shock_stage += spillover * get_config_value(/decl/config/num/health_organ_damage_spillover_multiplier) + + // sync the organ's damage with its wounds + update_damages() + if(do_update_health) + owner.update_health() + if(status & ORGAN_BLEEDING) + owner.update_bandages() + + if(owner && update_damstate()) + owner.update_damage_overlays() + + if(created_wound && isobj(inflicter)) + var/obj/O = inflicter + O.after_wounding(src, created_wound) + + return created_wound + +/obj/item/organ/external/createwound(var/type = CUT, var/damage, var/surgical, var/arterial = FALSE) + + if(!owner || damage <= 0) + return + + if(BP_IS_CRYSTAL(src)) + type = SHATTER + if(damage >= 15 || prob(1)) + playsound(loc, 'sound/effects/hit_on_shattered_glass.ogg', 40, 1) // Crash! + else if((limb_flags & ORGAN_FLAG_SKELETAL) || (BP_IS_PROSTHETIC(src) && !bodytype.is_robotic)) + if(type == BURN) + type = CHARRED + else + type = SHATTER + + //moved these before the open_wound check so that having many small wounds for example doesn't somehow protect you from taking internal damage (because of the return) + //Brute damage can possibly trigger an internal wound, too. + var/local_damage = brute_dam + burn_dam + damage + if(!surgical && (type in list(CUT, PIERCE, BRUISE)) && damage > 15 && local_damage > 30) + + var/internal_damage + if(prob(damage + (arterial)*50) && sever_artery()) + internal_damage = TRUE + if(prob(ceil(damage/4)) && sever_tendon()) + internal_damage = TRUE + if(internal_damage) + owner.custom_pain("You feel something rip in your [name]!", 50, affecting = src) + + //Burn damage can cause fluid loss due to blistering and cook-off + if((type in list(BURN, LASER)) && (damage > 5 || damage + burn_dam >= 15) && !BP_IS_PROSTHETIC(src)) + var/fluid_loss_severity + switch(type) + if(BURN) fluid_loss_severity = FLUIDLOSS_WIDE_BURN + if(LASER) fluid_loss_severity = FLUIDLOSS_CONC_BURN + var/fluid_loss = (damage/(owner.get_max_health() - get_config_value(/decl/config/num/health_health_threshold_dead))) * SPECIES_BLOOD_DEFAULT * fluid_loss_severity + owner.remove_blood(fluid_loss) + + // first check whether we can widen an existing wound + if(!surgical && LAZYLEN(wounds) && prob(max(50+(number_wounds-1)*10,90))) + if((type == CUT || type == BRUISE) && damage >= 5) + //we need to make sure that the wound we are going to worsen is compatible with the type of damage... + var/list/compatible_wounds = list() + for (var/datum/wound/wound in wounds) + if (wound.can_worsen(type, damage)) + compatible_wounds += wound + + if(compatible_wounds.len) + var/datum/wound/wound = pick(compatible_wounds) + wound.open_wound(damage) + if(owner && prob(25)) + if(BP_IS_CRYSTAL(src)) + owner.visible_message(SPAN_DANGER("The cracks in \the [owner]'s [name] spread."),\ + SPAN_DANGER("The cracks in your [name] spread."),\ + SPAN_DANGER("You hear the cracking of crystal.")) + else if(BP_IS_PROSTHETIC(src)) + owner.visible_message(SPAN_DANGER("The damage to \the [owner]'s [name] worsens."),\ + SPAN_DANGER("The damage to your [name] worsens."),\ + SPAN_DANGER("You hear the screech of abused metal.")) + else + owner.visible_message(SPAN_DANGER("The wound on \the [owner]'s [name] widens with a nasty ripping noise."),\ + SPAN_DANGER("The wound on your [name] widens with a nasty ripping noise."),\ + SPAN_DANGER("You hear a nasty ripping noise, as if flesh is being torn apart.")) + return wound + + //Creating wound + var/wound_type = get_wound_type(type, damage) + + if(wound_type) + var/datum/wound/wound = new wound_type(damage, src, surgical) + + //Check whether we can add the wound to an existing wound + if(surgical) + wound.autoheal_cutoff = 0 + else + for(var/datum/wound/other in wounds) + if(other.can_merge_wounds(wound)) + other.merge_wound(wound) + return other + LAZYADD(wounds, wound) + return wound + +/obj/item/organ/external/damage_internal_organs(brute, burn, damage_flags) + if(!LAZYLEN(internal_organs)) + return FALSE + + var/laser = (damage_flags & DAM_LASER) + var/sharp = (damage_flags & DAM_SHARP) + + var/damage_amt = brute + var/cur_damage = brute_dam + if(laser || BP_IS_PROSTHETIC(src)) + damage_amt += burn + cur_damage += burn_dam + + if(!damage_amt) + return FALSE + + var/organ_damage_threshold = 10 + if(sharp || organ_tag == BP_HEAD) + organ_damage_threshold *= 0.5 + if(laser) + organ_damage_threshold *= 2 + var/arterial = damage_flags & DAM_ARTERY + if(!(cur_damage + damage_amt >= max_damage) && !(damage_amt >= organ_damage_threshold + arterial * 50)) + return FALSE + var/success = FALSE + var/list/victims = list() + for(var/obj/item/organ/internal/organ in internal_organs) + if(organ.get_organ_damage() < organ.max_damage) + victims[organ] = min(organ.relative_size + 3 * damage_amt/organ_damage_threshold, 100) + var/organ_chance = victims[organ] + if (!sharp) + organ_chance = min(100, organ_chance*1.5) + + if(prob(organ_chance)) + var/local_damage_reduction = 0 + if(encased && !(status & ORGAN_BROKEN)) //ribs protect + local_damage_reduction += 0.35 + if (damage_flags & DAM_SHARP) + local_damage_reduction += 0.25 + damage_amt -= damage_amt*organ.damage_reduction + damage_amt -= damage_amt*local_damage_reduction + damage_amt = max(damage_amt, 0) + organ.take_damage(damage_amt) + success = TRUE + return success + +/obj/item/gun/play_fire_sound(atom/movable/firer, obj/item/projectile/P) + var/shot_sound = fire_sound + var/shot_sound_vol = 50 + if((istype(P) && P.fire_sound)) + shot_sound = P.fire_sound + shot_sound_vol = P.fire_sound_vol + if(silencer) + shot_sound_vol = P.fire_sound_vol_silenced + + playsound(firer, shot_sound, shot_sound_vol, 1) + +//Suicide handling. + +/obj/item/gun/handle_suicide(mob/living/user) + if(!ishuman(user)) + return + var/mob/living/human/M = user + + mouthshoot = 1 + admin_attacker_log(user, "is attempting to suicide with \a [src]") + M.visible_message("[user] sticks their gun in their mouth, ready to pull the trigger...") + if(!do_after(user, 40, progress=0)) + M.visible_message(SPAN_NOTICE("[user] decided life was worth living.")) + mouthshoot = 0 + return + + if(safety()) + user.visible_message("*click click*", SPAN_DANGER("*click*")) + playsound(src.loc, 'sound/weapons/empty.ogg', 100, 1) + mouthshoot = 0 + return + + var/obj/item/projectile/in_chamber = consume_next_projectile() + if (istype(in_chamber)) + user.visible_message("[user] pulls the trigger.") + var/shot_sound = in_chamber.fire_sound? in_chamber.fire_sound : fire_sound + if(silencer) + playsound(user, shot_sound, 10, 1) + else + playsound(user, shot_sound, 50, 1) + if(istype(in_chamber, /obj/item/projectile/beam/lastertag)) + user.show_message("You feel rather silly, trying to commit suicide with a toy.") + mouthshoot = 0 + return + + in_chamber.on_hit(M) + if (in_chamber.atom_damage_type != PAIN) + log_and_message_admins("[key_name(user)] commited suicide using \a [src]") + var/shot_damage = in_chamber.damage + if (istype (in_chamber, /obj/item/projectile/bullet/pellet)) //handle buckshot + var/obj/item/projectile/bullet/pellet/shell = in_chamber + shot_damage = shell.pellets*shell.damage + user.apply_damage(shot_damage, in_chamber.atom_damage_type, BP_HEAD, in_chamber.damage_flags(), used_weapon = "Point blank shot in the mouth with \a [in_chamber]") + user.death() + else + to_chat(user, "Ow...") + user.apply_effect(110,PAIN,0) + qdel(in_chamber) + mouthshoot = 0 + return + else + handle_click_empty(user) + mouthshoot = 0 + return diff --git a/mods/content/bosniastation/organ_tweaks.dm b/mods/content/bosniastation/organ_tweaks.dm new file mode 100644 index 00000000000..44ce4982f73 --- /dev/null +++ b/mods/content/bosniastation/organ_tweaks.dm @@ -0,0 +1,18 @@ +/obj/item/organ/internal/brain //brain tweaks are inadvisable without the full rework, but they are separate for clarity + relative_size = 40 + damage_reduction = -2 + +/obj/item/organ/internal/brain/take_damage(damage, damage_type = BRUTE, damage_flags, inflicter, armor_pen = 0, silent, do_update_health) + . = ..() + if (owner && damage >= 10 && _organ_damage > 75) //This probably won't be triggered by oxyloss or mercury. Probably. + var/damage_secondary = damage * 0.20 + owner.flash_eyes() + SET_STATUS_MAX(owner, STAT_BLURRY, damage_secondary) + SET_STATUS_MAX(owner, STAT_CONFUSE, damage_secondary * 2) + SET_STATUS_MAX(owner, STAT_PARA, damage_secondary) + SET_STATUS_MAX(owner, STAT_WEAK, round(damage, 1)) + if (prob(30)) + addtimer(CALLBACK(src, PROC_REF(brain_damage_callback), damage), rand(6, 20) SECONDS, TIMER_UNIQUE) + +/obj/item/organ/internal/heart + damage_reduction = 0 \ No newline at end of file