diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm index 6e3d1e3e52d..48d4f22934f 100644 --- a/code/controllers/configuration.dm +++ b/code/controllers/configuration.dm @@ -71,6 +71,9 @@ var/health_threshold_crit = 0 var/health_threshold_dead = -100 + var/organ_health_multiplier = 1 + var/organ_regeneration_multiplier = 1 + var/revival_pod_plants = 1 var/revival_cloning = 1 var/revival_brain_life = -1 @@ -365,6 +368,10 @@ config.metroid_delay = value if("animal_delay") config.animal_delay = value + if("organ_health_multiplier") + config.organ_health_multiplier = value / 100 + if("organ_regeneration_multiplier") + config.organ_regeneration_multiplier = value / 100 else diary << "Unknown setting in configuration: '[name]'" diff --git a/code/datums/organs/organ.dm b/code/datums/organs/organ.dm index 81b45f51287..ba7b1bf014c 100644 --- a/code/datums/organs/organ.dm +++ b/code/datums/organs/organ.dm @@ -1,8 +1,6 @@ -//This file was auto-corrected by findeclaration.exe on 25.5.2012 20:42:33 - /datum/organ var/name = "organ" - var/mob/owner = null + var/mob/living/carbon/human/owner = null ///datum/organ/proc/process() // return 0 @@ -10,5 +8,8 @@ ///datum/organ/proc/receive_chem(chemical as obj) // return 0 + proc/process() + return 0 - + proc/receive_chem(chemical as obj) + return 0 \ No newline at end of file diff --git a/code/datums/organs/organ_external.dm b/code/datums/organs/organ_external.dm index 85df6bf56fe..9d8dc5c45ce 100644 --- a/code/datums/organs/organ_external.dm +++ b/code/datums/organs/organ_external.dm @@ -1,142 +1,640 @@ +/**************************************************** + EXTERNAL ORGANS +****************************************************/ /datum/organ/external name = "external" var/icon_name = null var/body_part = null - var/brutestate = 0 - var/burnstate = 0 + + var/damage_state = "00" var/brute_dam = 0 var/burn_dam = 0 var/max_damage = 0 -// var/bandaged = 0 -// var/wound_size = 0 -// var/max_size = 0 + var/max_size = 0 + var/tmp/list/obj/item/weapon/implant/implant + + var/display_name + var/list/wounds = list() + var/number_wounds = 0 // cache the number of wounds, which is NOT wounds.len! + + var/tmp/perma_injury = 0 + var/tmp/perma_dmg = 0 + var/tmp/destspawn = 0 //Has it spawned the broken limb? + var/tmp/amputated = 0 // Whether this has been cleanly amputated, thus causing no pain + var/min_broken_damage = 30 + + var/datum/organ/external/parent + var/list/datum/organ/external/children + + var/damage_msg = "\red You feel an intense pain" + + var/status = 0 + var/broken_description + var/open = 0 + var/stage = 0 + + proc/take_damage(brute, burn, sharp, used_weapon = null, list/forbidden_limbs = list()) + // TODO: this proc needs to be rewritten to not update damages directly + if((brute <= 0) && (burn <= 0)) + return 0 + if(status & ORGAN_DESTROYED) + return 0 + if(status & ORGAN_ROBOT) + brute *= 0.66 //~2/3 damage for ROBOLIMBS + burn *= 0.66 //~2/3 damage for ROBOLIMBS + + //if(owner && !(status & ORGAN_ROBOT)) + // owner.pain(display_name, (brute+burn)*3, 1, burn > brute) + + if(sharp) + var/nux = brute * rand(10,15) + if(brute_dam >= max_damage * config.organ_health_multiplier) + if(prob(5 * brute)) + status |= ORGAN_DESTROYED + droplimb() + return + + else if(prob(nux)) + createwound( CUT, brute ) + if(!(status & ORGAN_ROBOT)) + owner << "You feel something wet on your [display_name]" + + if((brute_dam + burn_dam + brute + burn) < max_damage) + if(brute) + brute_dam += brute + if( (prob(brute*2) && !sharp) || sharp ) + createwound( CUT, brute ) + else if(!sharp) + createwound( BRUISE, brute ) + if(burn) + burn_dam += burn + createwound( BURN, burn ) + else + var/can_inflict = max_damage - (brute_dam + burn_dam) //How much damage can we actually cause? + if(can_inflict) + if (brute > 0 && burn > 0) + brute = can_inflict/2 + burn = can_inflict/2 + var/ratio = brute / (brute + burn) + brute_dam += ratio * can_inflict + burn_dam += (1 - ratio) * can_inflict + else + if (brute > 0) + brute = can_inflict + brute_dam += brute + createwound(BRUISE, brute) + else + burn = can_inflict + burn_dam += burn + createwound(BRUISE, brute) + else if(!(status & ORGAN_ROBOT)) + var/passed_dam = (brute + burn) - can_inflict //Getting how much overdamage we have. + var/list/datum/organ/external/possible_points = list() + if(parent) + possible_points += parent + if(children) + possible_points += children + if(forbidden_limbs.len) + possible_points -= forbidden_limbs + if(!possible_points.len) + message_admins("Oh god WHAT! [owner]'s [src] was unable to find an organ to pass overdamage too!") + else + var/datum/organ/external/target = pick(possible_points) + if(brute) + target.take_damage(passed_dam, 0, sharp, used_weapon, forbidden_limbs + src) + else + target.take_damage(0, passed_dam, sharp, used_weapon, forbidden_limbs + src) + else + droplimb(1) //Robot limbs just kinda fail at full damage. + + + if(status & ORGAN_BROKEN) + owner.emote("scream") + + owner.updatehealth() + + // sync the organ's damage with its wounds + src.update_damages() + + var/result = update_icon() + return result + + + + proc/heal_damage(brute, burn, internal = 0, robo_repair = 0) + if(status & ORGAN_ROBOT && !robo_repair) + return + + // heal damage on the individual wounds + for(var/datum/wound/W in wounds) + if(brute == 0 && burn == 0) + break + + // heal brute damage + if(W.damage_type == CUT || W.damage_type == BRUISE) + brute = W.heal_damage(brute) + else if(W.damage_type == BURN) + burn = W.heal_damage(burn) + + // sync organ damage with wound damages + update_damages() + + if(internal) + status &= ~ORGAN_BROKEN + perma_injury = 0 + + // sync the organ's damage with its wounds + src.update_damages() + + owner.updatehealth() + var/result = update_icon() + return result + + proc/update_damages() + number_wounds = 0 + brute_dam = 0 + burn_dam = 0 + status &= ~ORGAN_BLEEDING + for(var/datum/wound/W in wounds) + if(W.damage_type == CUT || W.damage_type == BRUISE) + brute_dam += W.damage + else if(W.damage_type == BURN) + burn_dam += W.damage + + if(!W.bandaged && W.damage > 4) + status |= ORGAN_BLEEDING + + number_wounds += W.amount + + proc/update_wounds() + for(var/datum/wound/W in wounds) + // wounds can disappear after 10 minutes at the earliest + if(W.damage == 0 && W.created + 10 * 10 * 60 <= world.time) + wounds -= W + // let the GC handle the deletion of the wound + if(W.is_treated()) + // slow healing + var/amount = 0.2 + if(W.bandaged) amount++ + if(W.salved) amount++ + if(W.disinfected) amount++ + // amount of healing is spread over all the wounds + W.heal_damage((amount * W.amount * config.organ_regeneration_multiplier) / (5*owner.number_wounds+1)) + + // sync the organ's damage with its wounds + src.update_damages() + + + proc/get_damage() //returns total damage + return max(brute_dam + burn_dam - perma_injury, perma_injury) //could use health? + + proc/get_damage_brute() + return max(brute_dam+perma_injury, perma_injury) + + proc/get_damage_fire() + return burn_dam + + process() + // process wounds, doing healing etc., only do this every 4 ticks to save processing power + if(owner.life_tick % 4 == 0) + update_wounds() + if(status & ORGAN_DESTROYED) + if(!destspawn) + droplimb() + return + if(!(status & ORGAN_BROKEN)) + perma_dmg = 0 + if(parent) + if(parent.status & ORGAN_DESTROYED) + status |= ORGAN_DESTROYED + owner:update_body() + return + if(brute_dam > min_broken_damage * config.organ_health_multiplier && !(status & ORGAN_ROBOT)) + if(!(status & ORGAN_BROKEN)) + owner.visible_message("\red You hear a loud cracking sound coming from \the [owner].","\red Something feels like it shattered in your [display_name]!","You hear a sickening crack.") + owner.emote("scream") + status |= ORGAN_BROKEN + broken_description = pick("broken","fracture","hairline fracture") + perma_injury = brute_dam + return + +// new damage icon system +// returns just the brute/burn damage code + proc/damage_state_text() + var/tburn = 0 + var/tbrute = 0 + + if(burn_dam ==0) + tburn =0 + else if (burn_dam < (max_damage * 0.25 / 2)) + tburn = 1 + else if (burn_dam < (max_damage * 0.75 / 2)) + tburn = 2 + else + tburn = 3 + + if (brute_dam == 0) + tbrute = 0 + else if (brute_dam < (max_damage * 0.25 / 2)) + tbrute = 1 + else if (brute_dam < (max_damage * 0.75 / 2)) + tbrute = 2 + else + tbrute = 3 + return "[tbrute][tburn]" + + +// new damage icon system +// adjusted to set damage_state to brute/burn code only (without r_name0 as before) + proc/update_icon() + var/n_is = damage_state_text() + if (n_is != damage_state) + damage_state = n_is + owner.update_body() + return 1 + return 0 + + proc/droplimb(var/override = 0,var/no_explode = 0) + if(override) + status |= ORGAN_DESTROYED + if(status & ORGAN_DESTROYED) + if(status & ORGAN_SPLINTED) + status &= ~ORGAN_SPLINTED + if(implant) + for(var/implants in implant) + del(implants) + //owner.unlock_medal("Lost something?", 0, "Lose a limb.", "easy") + + for(var/datum/organ/external/I in children) + if(I && !(I.status & ORGAN_DESTROYED)) + I.droplimb(1,1) + var/obj/item/weapon/organ/H + switch(body_part) + if(UPPER_TORSO) + owner.gib() + if(LOWER_TORSO) + owner << "\red You are now sterile." + if(HEAD) + H = new /obj/item/weapon/organ/head(owner.loc, owner) + if(ishuman(owner)) + if(owner.gender == FEMALE) + H.icon_state = "head_f_l" + H.overlays += owner.generate_head_icon() + H:transfer_identity(owner) + H.pixel_x = -10 + H.pixel_y = 6 + H.name = "[owner.real_name]'s head" + + // TODO: re-add this when dismemberment has been ported properly + //if(ishuman(owner)) + // owner.update_face() + owner.update_body() + owner.death() + if(ARM_RIGHT) + H = new /obj/item/weapon/organ/r_arm(owner.loc, owner) + if(ismonkey(owner)) + H.icon_state = "r_arm_l" + if(ARM_LEFT) + H = new /obj/item/weapon/organ/l_arm(owner.loc, owner) + if(ismonkey(owner)) + H.icon_state = "l_arm_l" + if(LEG_RIGHT) + H = new /obj/item/weapon/organ/r_leg(owner.loc, owner) + if(ismonkey(owner)) + H.icon_state = "r_leg_l" + if(LEG_LEFT) + H = new /obj/item/weapon/organ/l_leg(owner.loc, owner) + if(ismonkey(owner)) + H.icon_state = "l_leg_l" + if(HAND_RIGHT) + H = new /obj/item/weapon/organ/r_hand(owner.loc, owner) + if(ismonkey(owner)) + H.icon_state = "r_hand_l" + if(HAND_LEFT) + H = new /obj/item/weapon/organ/l_hand(owner.loc, owner) + if(ismonkey(owner)) + H.icon_state = "l_hand_l" + if(FOOT_RIGHT) + H = new /obj/item/weapon/organ/r_foot/(owner.loc, owner) + if(ismonkey(owner)) + H.icon_state = "r_foot_l" + if(FOOT_LEFT) + H = new /obj/item/weapon/organ/l_foot(owner.loc, owner) + if(ismonkey(owner)) + H.icon_state = "l_foot_l" + if(ismonkey(owner)) + H.icon = 'monkey.dmi' + var/lol = pick(cardinal) + step(H,lol) + destspawn = 1 + if(status & ORGAN_ROBOT) + owner.visible_message("\red \The [owner]'s [display_name] explodes violently!",\ + "\red Your [display_name] explodes!",\ + "You hear an explosion followed by a scream!") + if(!no_explode) + explosion(get_turf(owner),-1,-1,2,3) + var/datum/effect/effect/system/spark_spread/spark_system = new /datum/effect/effect/system/spark_spread() + spark_system.set_up(5, 0, src) + spark_system.attach(src) + spark_system.start() + spawn(10) + del(spark_system) + else + owner.visible_message("\red [owner.name]'s [display_name] flies off in an arc.",\ + "Your [display_name] goes flying off!",\ + "You hear a terrible sound of ripping tendons and flesh.") + owner.update_body() + // TODO: also add stuff to update the appropriate clothing + + proc/createwound(var/type = CUT, var/damage) + if(hasorgans(owner)) + var/wound_type + var/size = min( max( 1, damage/10 ) , 6) + + // first check whether we can widen an existing wound + if(wounds.len > 0 && prob(25)) + if((type == CUT || type == BRUISE) && damage >= 5) + var/datum/wound/W = pick(wounds) + if(W.amount == 1 && W.started_healing()) + W.open_wound() + owner.visible_message("\red The wound on [owner.name]'s [display_name] widens with a nasty ripping voice.",\ + "\red The wound on your [display_name] widens with a nasty ripping voice.",\ + "You hear a nasty ripping noise, as if flesh is being torn apart.") + + return + + if(damage == 0) return + + // the wound we will create + var/datum/wound/W + + switch(type) + if(CUT) + src.status |= ORGAN_BLEEDING + var/list/size_names = list(/datum/wound/cut, /datum/wound/deep_cut, /datum/wound/flesh_wound, /datum/wound/gaping_wound, /datum/wound/big_gaping_wound, /datum/wound/massive_wound) + wound_type = size_names[size] + + W = new wound_type(damage) + + if(BRUISE) + var/list/size_names = list(/datum/wound/bruise/tiny_bruise, /datum/wound/bruise/small_bruise, /datum/wound/bruise/moderate_bruise, /datum/wound/bruise/large_bruise, /datum/wound/bruise/huge_bruise, /datum/wound/bruise/monumental_bruise) + wound_type = size_names[size] + + W = new wound_type(damage) + if(BURN) + var/list/size_names = list(/datum/wound/moderate_burn, /datum/wound/large_burn, /datum/wound/severe_burn, /datum/wound/deep_burn, /datum/wound/carbonised_area) + wound_type = size_names[size] + + W = new wound_type(damage) + + + + // check whether we can add the wound to an existing wound + for(var/datum/wound/other in wounds) + if(other.desc == W.desc) + // okay, add it! + other.damage += W.damage + other.amount += 1 + W = null // to signify that the wound was added + break + + // if we couldn't add the wound to another wound, ignore + if(W) + wounds += W + + proc/emp_act(severity) + if(!(status & ORGAN_ROBOT)) + return + if(prob(30*severity)) + take_damage(4(4-severity), 0, 1, used_weapon = "EMP") + else + droplimb(1) + + proc/getDisplayName() + switch(name) + if("l_leg") + return "left leg" + if("r_leg") + return "right leg" + if("l_arm") + return "left arm" + if("r_arm") + return "right arm" + if("l_foot") + return "left foot" + if("r_foot") + return "right foot" + if("l_hand") + return "left hand" + if("r_hand") + return "right hand" + else + return name /datum/organ/external/chest name = "chest" icon_name = "chest" + display_name = "chest" max_damage = 150 + min_broken_damage = 75 body_part = UPPER_TORSO +/datum/organ/external/groin + name = "groin" + icon_name = "diaper" + display_name = "groin" + max_damage = 115 + min_broken_damage = 70 + body_part = LOWER_TORSO + /datum/organ/external/head name = "head" icon_name = "head" - max_damage = 125 + display_name = "head" + max_damage = 75 + min_broken_damage = 40 body_part = HEAD + var/disfigured = 0 /datum/organ/external/l_arm name = "l_arm" + display_name = "left arm" icon_name = "l_arm" max_damage = 75 + min_broken_damage = 30 body_part = ARM_LEFT /datum/organ/external/l_leg name = "l_leg" + display_name = "left leg" icon_name = "l_leg" max_damage = 75 + min_broken_damage = 30 body_part = LEG_LEFT /datum/organ/external/r_arm name = "r_arm" + display_name = "right arm" icon_name = "r_arm" max_damage = 75 + min_broken_damage = 30 body_part = ARM_RIGHT /datum/organ/external/r_leg name = "r_leg" + display_name = "right leg" icon_name = "r_leg" max_damage = 75 + min_broken_damage = 30 body_part = LEG_RIGHT -/*Leaving these here in case we want to use them later /datum/organ/external/l_foot - name = "l foot" + name = "l_foot" + display_name = "left foot" icon_name = "l_foot" + max_damage = 40 + min_broken_damage = 15 body_part = FOOT_LEFT /datum/organ/external/r_foot - name = "r foot" + name = "r_foot" + display_name = "right foot" icon_name = "r_foot" + max_damage = 40 + min_broken_damage = 15 body_part = FOOT_RIGHT /datum/organ/external/r_hand - name = "r hand" + name = "r_hand" + display_name = "right hand" icon_name = "r_hand" + max_damage = 40 + min_broken_damage = 15 body_part = HAND_RIGHT /datum/organ/external/l_hand - name = "l hand" + name = "l_hand" + display_name = "left hand" icon_name = "l_hand" + max_damage = 40 + min_broken_damage = 15 body_part = HAND_LEFT -/datum/organ/external/groin - name = "groin" - icon_name = "groin" - body_part = LOWER_TORSO -*/ +/**************************************************** + EXTERNAL ORGAN ITEMS +****************************************************/ -//Applies brute and burn damage to the organ. Returns 1 if the damage-icon states changed at all. -//Damage will not exceed max_damage using this proc -//Cannot apply negative damage -/datum/organ/external/proc/take_damage(brute, burn) - if(owner && owner.nodamage) return 0 //godmode - brute = max(brute,0) - burn = max(burn,0) +obj/item/weapon/organ + icon = 'human.dmi' - var/can_inflict = max_damage - (brute_dam + burn_dam) - if(!can_inflict) return 0 +obj/item/weapon/organ/New(loc, mob/living/carbon/human/H) + ..(loc) + if(!istype(H)) + return + if(H.dna) + if(!blood_DNA) + blood_DNA = list() + blood_DNA[H.dna.unique_enzymes] = H.dna.b_type - if((brute + burn) < can_inflict) - brute_dam += brute - burn_dam += burn + var/icon/I = new /icon(icon, icon_state) + + if (H.s_tone >= 0) + I.Blend(rgb(H.s_tone, H.s_tone, H.s_tone), ICON_ADD) else - if(brute > 0) - if(burn > 0) - brute = round( (brute/(brute+burn)) * can_inflict, 1 ) - burn = can_inflict - brute //gets whatever damage is left over - brute_dam += brute - burn_dam += burn + I.Blend(rgb(-H.s_tone, -H.s_tone, -H.s_tone), ICON_SUBTRACT) + icon = I + +obj/item/weapon/organ/head + name = "head" + icon_state = "head_m_l" + var/mob/living/carbon/brain/brainmob + var/brain_op_stage = 0 + +obj/item/weapon/organ/head/New() + ..() + spawn(5) + if(brainmob && brainmob.client) + brainmob.client.screen.len = null //clear the hud + +obj/item/weapon/organ/head/proc/transfer_identity(var/mob/living/carbon/human/H)//Same deal as the regular brain proc. Used for human-->head + brainmob = new(src) + brainmob.name = H.real_name + brainmob.real_name = H.real_name + brainmob.dna = H.dna + if(H.mind) + H.mind.transfer_to(brainmob) + brainmob.container = src + +obj/item/weapon/organ/head/attackby(obj/item/weapon/W as obj, mob/user as mob) + if(istype(W,/obj/item/weapon/scalpel)) + switch(brain_op_stage) + if(0) + for(var/mob/O in (oviewers(brainmob) - user)) + O.show_message("\red [brainmob] is beginning to have \his head cut open with [src] by [user].", 1) + brainmob << "\red [user] begins to cut open your head with [src]!" + user << "\red You cut [brainmob]'s head open with [src]!" + + brain_op_stage = 1 + + if(2) + for(var/mob/O in (oviewers(brainmob) - user)) + O.show_message("\red [brainmob] is having \his connections to the brain delicately severed with [src] by [user].", 1) + brainmob << "\red [user] begins to cut open your head with [src]!" + user << "\red You cut [brainmob]'s head open with [src]!" + + brain_op_stage = 3.0 else - brute_dam += can_inflict - else - if(burn > 0) - burn_dam += can_inflict + ..() + else if(istype(W,/obj/item/weapon/circular_saw)) + switch(brain_op_stage) + if(1) + for(var/mob/O in (oviewers(brainmob) - user)) + O.show_message("\red [brainmob] has \his skull sawed open with [src] by [user].", 1) + brainmob << "\red [user] begins to saw open your head with [src]!" + user << "\red You saw [brainmob]'s head open with [src]!" + + brain_op_stage = 2 + if(3) + for(var/mob/O in (oviewers(brainmob) - user)) + O.show_message("\red [brainmob] has \his spine's connection to the brain severed with [src] by [user].", 1) + brainmob << "\red [user] severs your brain's connection to the spine with [src]!" + user << "\red You sever [brainmob]'s brain's connection to the spine with [src]!" + + user.attack_log += "\[[time_stamp()]\] Debrained [brainmob.name] ([brainmob.ckey]) with [src.name] (INTENT: [uppertext(user.a_intent)])" + brainmob.attack_log += "\[[time_stamp()]\] Debrained by [user.name] ([user.ckey]) with [src.name] (INTENT: [uppertext(user.a_intent)])" + log_admin("ATTACK: [brainmob] ([brainmob.ckey]) debrained [user] ([user.ckey]).") + message_admins("ATTACK: [brainmob] ([brainmob.ckey]) debrained [user] ([user.ckey]).") + + var/obj/item/brain/B = new(loc) + B.transfer_identity(brainmob) + + brain_op_stage = 4.0 else - return 0 - return update_icon() + ..() + else + ..() - -//Heals brute and burn damage for the organ. Returns 1 if the damage-icon states changed at all. -//Damage cannot go below zero. -//Cannot remove negative damage (i.e. apply damage) -/datum/organ/external/proc/heal_damage(brute, burn) - brute = max(brute, 0) - burn = max(burn, 0) - brute_dam = max(brute_dam - brute, 0) - burn_dam = max(burn_dam - burn, 0) - return update_icon() - - -//Returns total damage...kinda pointless really -/datum/organ/external/proc/get_damage() //returns total damage - return brute_dam + burn_dam - - -//Updates an organ's brute/burn states for use by updateDamageIcon() -//Returns 1 if we need to update overlays. 0 otherwise. -/datum/organ/external/proc/update_icon() - var/tbrute = round( (brute_dam/max_damage)*3, 1 ) - var/tburn = round( (burn_dam/max_damage)*3, 1 ) - if((tbrute != brutestate) || (tburn != burnstate)) - brutestate = tbrute - burnstate = tburn - return 1 - return 0 - -//Returns a display name for the organ -/datum/organ/external/proc/getDisplayName() - switch(name) - if("l_leg") return "left leg" - if("r_leg") return "right leg" - if("l_arm") return "left arm" - if("r_arm") return "right arm" - else return name +obj/item/weapon/organ/l_arm + name = "left arm" + icon_state = "l_arm_l" +obj/item/weapon/organ/l_foot + name = "left foot" + icon_state = "l_foot_l" +obj/item/weapon/organ/l_hand + name = "left hand" + icon_state = "l_hand_l" +obj/item/weapon/organ/l_leg + name = "left leg" + icon_state = "l_leg_l" +obj/item/weapon/organ/r_arm + name = "right arm" + icon_state = "r_arm_l" +obj/item/weapon/organ/r_foot + name = "right foot" + icon_state = "r_foot_l" +obj/item/weapon/organ/r_hand + name = "right hand" + icon_state = "r_hand_l" +obj/item/weapon/organ/r_leg + name = "right leg" + icon_state = "r_leg_l" \ No newline at end of file diff --git a/code/datums/organs/organ_internal.dm b/code/datums/organs/organ_internal.dm index 66496141bbc..2f1738bea2d 100644 --- a/code/datums/organs/organ_internal.dm +++ b/code/datums/organs/organ_internal.dm @@ -1,3 +1,6 @@ +/**************************************************** + INTERNAL ORGANS +****************************************************/ /datum/organ/internal name = "internal" diff --git a/code/datums/organs/wound.dm b/code/datums/organs/wound.dm new file mode 100644 index 00000000000..1caf45b7587 --- /dev/null +++ b/code/datums/organs/wound.dm @@ -0,0 +1,196 @@ + +/**************************************************** + WOUNDS +****************************************************/ +/datum/wound + // stages such as "cut", "deep cut", etc. + var/list/stages + // number representing the current stage + var/current_stage = 0 + + // description of the wound + var/desc = "" + + // amount of damage this wound causes + var/damage = 0 + + // amount of damage the current wound type requires(less means we need to apply the next healing stage) + var/min_damage = 0 + + // one of CUT, BRUISE, BURN + var/damage_type = CUT + + // whether this wound needs a bandage/salve to heal at all + var/needs_treatment = 0 + + // is the wound bandaged? + var/tmp/bandaged = 0 + // is the wound salved? + var/tmp/salved = 0 + // is the wound disinfected? + var/tmp/disinfected = 0 + var/tmp/created = 0 + + // number of wounds of this type + var/tmp/amount = 1 + + // helper lists + var/tmp/list/desc_list = list() + var/tmp/list/damage_list = list() + New(var/damage) + + created = world.time + + // reading from a list("stage" = damage) is pretty difficult, so build two separate + // lists from them instead + for(var/V in stages) + desc_list += V + damage_list += stages[V] + + src.damage = damage + + // initialize with the first stage + next_stage() + + // this will ensure the size of the wound matches the damage + src.heal_damage(0) + + // returns 1 if there's a next stage, 0 otherwise + proc/next_stage() + if(current_stage + 1 > src.desc_list.len) + return 0 + + current_stage++ + + src.min_damage = damage_list[current_stage] + src.desc = desc_list[current_stage] + return 1 + + // returns 1 if the wound has started healing + proc/started_healing() + return (current_stage > 1) + + // checks whether the wound has been appropriately treated + // always returns 1 for wounds that don't need to be treated + proc/is_treated() + if(!needs_treatment) return 1 + + if(damage_type == BRUISE || damage_type == CUT) + return bandaged + else if(damage_type == BURN) + return salved + + // heal the given amount of damage, and if the given amount of damage was more + // than what needed to be healed, return how much heal was left + proc/heal_damage(amount) + var/healed_damage = min(src.damage, amount) + amount -= healed_damage + src.damage -= healed_damage + + while(src.damage / src.amount < damage_list[current_stage] && current_stage < src.desc_list.len) + current_stage++ + desc = desc_list[current_stage] + + // return amount of healing still leftover, can be used for other wounds + return amount + + // opens the wound again + proc/open_wound() + if(current_stage > 1) + // e.g. current_stage is 2, then reset it to 0 and do next_stage(), bringing it to 1 + src.current_stage -= 2 + next_stage() + src.damage = src.min_damage + 5 + +/** CUTS **/ +/datum/wound/cut + // link wound descriptions to amounts of damage + stages = list("cut" = 5, "healing cut" = 2, "small scab" = 0) + +/datum/wound/deep_cut + stages = list("deep cut" = 15, "clotted cut" = 8, "scab" = 2, "fresh skin" = 0) + +/datum/wound/flesh_wound + stages = list("flesh wound" = 25, "blood soaked clot" = 15, "large scab" = 5, "fresh skin" = 0) + +/datum/wound/gaping_wound + stages = list("gaping wound" = 50, "large blood soaked clot" = 25, "large clot" = 15, "small angry scar" = 5, \ + "small straight scar" = 0) + +/datum/wound/big_gaping_wound + stages = list("big gaping wound" = 60, "gauze wrapped wound" = 50, "blood soaked bandage" = 25,\ + "large angry scar" = 10, "large straight scar" = 0) + + needs_treatment = 1 // this only heals when bandaged + +/datum/wound/massive_wound + stages = list("massive wound" = 70, "massive blood soaked bandage" = 40, "huge bloody mess" = 20,\ + "massive angry scar" = 10, "massive jagged scar" = 0) + + needs_treatment = 1 // this only heals when bandaged + +/** BRUISES **/ +/datum/wound/bruise + stages = list("monumental bruise" = 80, "huge bruise" = 50, "large bruise" = 30,\ + "moderate bruise" = 20, "small bruise" = 10, "tiny bruise" = 5) + + needs_treatment = 1 // this only heals when bandaged + damage_type = BRUISE + +/datum/wound/bruise/monumental_bruise + +// implement sub-paths by starting at a later stage +/datum/wound/bruise/huge_bruise + current_stage = 1 + +/datum/wound/bruise/large_bruise + current_stage = 2 + +/datum/wound/bruise/moderate_bruise + current_stage = 3 + needs_treatment = 0 + +/datum/wound/bruise/small_bruise + current_stage = 4 + needs_treatment = 0 + +/datum/wound/bruise/tiny_bruise + current_stage = 5 + needs_treatment = 0 + +/** BURNS **/ +/datum/wound/moderate_burn + stages = list("moderate burn" = 5, "moderate salved burn" = 2, "fresh skin" = 0) + + needs_treatment = 1 // this only heals when bandaged + + damage_type = BURN + +/datum/wound/large_burn + stages = list("large burn" = 15, "large salved burn" = 5, "fresh skin" = 0) + + needs_treatment = 1 // this only heals when bandaged + + damage_type = BURN + +/datum/wound/severe_burn + stages = list("severe burn" = 30, "severe salved burn" = 10, "burn scar" = 0) + + needs_treatment = 1 // this only heals when bandaged + + damage_type = BURN + +/datum/wound/deep_burn + stages = list("deep burn" = 40, "deep salved burn" = 15, "large burn scar" = 0) + + needs_treatment = 1 // this only heals when bandaged + + damage_type = BURN + + +/datum/wound/carbonised_area + stages = list("carbonised area" = 50, "treated carbonised area" = 20, "massive burn scar" = 0) + + needs_treatment = 1 // this only heals when bandaged + + damage_type = BURN \ No newline at end of file diff --git a/code/defines/obj/weapon.dm b/code/defines/obj/weapon.dm index 81a0c4a1737..851445c6200 100644 --- a/code/defines/obj/weapon.dm +++ b/code/defines/obj/weapon.dm @@ -1355,6 +1355,7 @@ m_amt = 12000 origin_tech = "materials=1" attack_verb = list("slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") + sharp = 1 /obj/item/weapon/kitchenknife/ritual name = "ritual knife" @@ -1436,6 +1437,7 @@ icon_state = "knife" force = 10.0 throwforce = 10.0 + sharp = 1 /obj/item/weapon/kitchen/utensil/spoon name = "spoon" @@ -1458,6 +1460,7 @@ g_amt = 5000 origin_tech = "materials=1;biotech=1" attack_verb = list("attacked", "slashed", "stabbed", "sliced", "torn", "ripped", "diced", "cut") + sharp = 1 /obj/item/weapon/retractor name = "retractor" @@ -1524,6 +1527,7 @@ g_amt = 10000 origin_tech = "materials=1;biotech=1" attack_verb = list("attacked", "slashed", "sawed", "cut") + sharp = 1 /obj/item/weapon/syntiflesh name = "syntiflesh" diff --git a/code/game/dna.dm b/code/game/dna.dm index 0ea61c118e2..eec31a2565c 100644 --- a/code/game/dna.dm +++ b/code/game/dna.dm @@ -505,8 +505,6 @@ for(var/obj/T in (M.contents-implants)) del(T) - //for(var/R in M.organs) - // del(M.organs[text("[]", R)]) O.loc = M.loc diff --git a/code/game/gamemodes/events/ninja_equipment.dm b/code/game/gamemodes/events/ninja_equipment.dm index dd77de486bc..58bfa17ff48 100644 --- a/code/game/gamemodes/events/ninja_equipment.dm +++ b/code/game/gamemodes/events/ninja_equipment.dm @@ -844,15 +844,16 @@ ________________________________________________________________________________ U << "\red Procedure interrupted. Protocol terminated." return else if(istype(I, /obj/item/weapon/disk/tech_disk))//If it's a data disk, we want to copy the research on to the suit. - if(I:stored)//If it has something on it. + var/obj/item/weapon/disk/tech_disk/tdisk = I + if(tdisk.stored)//If it has something on it. U << "Research information detected, processing..." if(do_after(U,s_delay)) for(var/datum/tech/current_data in stored_research) - if(current_data.id==I:stored.id) - if(current_data.levelERROR: \black Procedure interrupted. Process terminated." diff --git a/code/game/objects/items/devices/PDA/PDA.dm b/code/game/objects/items/devices/PDA/PDA.dm index 8363837874f..80dc869698c 100644 --- a/code/game/objects/items/devices/PDA/PDA.dm +++ b/code/game/objects/items/devices/PDA/PDA.dm @@ -837,8 +837,8 @@ var/global/list/obj/item/device/pda/PDAs = list() C.loc = src user << "You insert [C] into [src]." cartridge = C - if(C:radio) - C:radio.hostpda = src + if(cartridge.radio) + cartridge.radio.hostpda = src else if(istype(C, /obj/item/weapon/card/id)) var/obj/item/weapon/card/id/idcard = C diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index 01f0f06a5d1..9d782959bdd 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -11,6 +11,7 @@ animate_movement = 2 var/throwforce = 1 var/list/attack_verb = list() //Used in attackby() to say how something was attacked "[x] has been [z.attack_verb] by [y] with [z]" + var/sharp = 0 // whether this object cuts /obj/proc/process() processing_objects.Remove(src) diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm index 5bc421dee4d..4277e3b301f 100644 --- a/code/modules/mob/inventory.dm +++ b/code/modules/mob/inventory.dm @@ -203,3 +203,16 @@ //if(hasvar(src,"r_hand")) if(src:r_hand) items += src:r_hand return items + +/** BS12's proc to get the item in the active hand. Couldn't find the /tg/ equivalent. **/ +/mob/proc/equipped() + if(issilicon(src)) + if(isrobot(src)) + if(src:module_active) + return src:module_active + else + if (hand) + return l_hand + else + return r_hand + return \ No newline at end of file diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm index 22142887241..0f1fedb8de4 100644 --- a/code/modules/mob/living/carbon/carbon_defines.dm +++ b/code/modules/mob/living/carbon/carbon_defines.dm @@ -10,3 +10,8 @@ var/silent = null //Can't talk. Value goes down every life proc. var/last_eating = 0 //Not sure what this does... I found it hidden in food.dm + + var/life_tick = 0 // The amount of life ticks that have processed on this mob. + + // total amount of wounds on mob, used to spread out healing and the like over all wounds + var/number_wounds = 0 \ No newline at end of file diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm index c1a12fd29d0..12160fcc265 100644 --- a/code/modules/mob/living/carbon/human/examine.dm +++ b/code/modules/mob/living/carbon/human/examine.dm @@ -201,40 +201,6 @@ msg += "...\n" else - - msg += "" - - var/temp = getBruteLoss() //no need to calculate each of these twice - if(temp) - if(temp < 30) - msg += "[t_He] [t_has] minor bruising.\n" - else - msg += "[t_He] [t_has] severe bruising!\n" - - temp = getFireLoss() - if(temp) - if(temp < 30) - msg += "[t_He] [t_has] minor burns.\n" - else - msg += "[t_He] [t_has] severe burns!\n" - - temp = getCloneLoss() - if(temp) - if(temp < 30) - msg += "[t_He] [t_has] minor genetic deformities.\n" - else - msg += "[t_He] [t_has] severe genetic deformities.\n" - - if(nutrition < 100) - msg += "[t_He] [t_is] severely malnourished.\n" - else if(nutrition >= 500) - if(usr.nutrition < 100) - msg += "[t_He] [t_is] plump and delicious looking - Like a fat little piggy. A tasty piggy.\n" - else - msg += "[t_He] [t_is] quite chubby.\n" - - msg += "" - if(stat == UNCONSCIOUS) msg += "[t_He] [t_is]n't responding to anything around [t_him] and seems to be asleep.\n" else if(getBrainLoss() >= 60) @@ -245,6 +211,148 @@ else if(!client) msg += "[t_He] [t_has] a vacant, braindead stare...\n" + msg += "" + + if(nutrition < 100) + msg += "[t_He] [t_is] severely malnourished.\n" + else if(nutrition >= 500) + if(usr.nutrition < 100) + msg += "[t_He] [t_is] plump and delicious looking - Like a fat little piggy. A tasty piggy.\n" + else + msg += "[t_He] [t_is] quite chubby.\n" + msg += "" + + var/list/wound_flavor_text = list() + var/list/is_destroyed = list() + var/list/is_bleeding = list() + for(var/datum/organ/external/temp in organs) + if(temp) + if(temp.status & ORGAN_DESTROYED) + is_destroyed["[temp.display_name]"] = 1 + wound_flavor_text["[temp.display_name]"] = "[t_He] is missing [t_his] [temp.display_name].\n" + continue + if(temp.status & ORGAN_ROBOT) + if(!(temp.brute_dam + temp.burn_dam)) + wound_flavor_text["[temp.display_name]"] = "[t_He] has a robot [temp.display_name]!\n" + continue + else + wound_flavor_text["[temp.display_name]"] = "[t_He] has a robot [temp.display_name], it has" + if(temp.brute_dam) switch(temp.brute_dam) + if(0 to 20) + wound_flavor_text["[temp.display_name]"] += " some dents" + if(21 to INFINITY) + wound_flavor_text["[temp.display_name]"] += pick(" a lot of dents"," severe denting") + if(temp.brute_dam && temp.burn_dam) + wound_flavor_text["[temp.display_name]"] += " and" + if(temp.burn_dam) switch(temp.burn_dam) + if(0 to 20) + wound_flavor_text["[temp.display_name]"] += " some burns" + if(21 to INFINITY) + wound_flavor_text["[temp.display_name]"] += pick(" a lot of burns"," severe melting") + wound_flavor_text["[temp.display_name]"] += "!\n" + else if(temp.wounds.len > 0) + var/list/wound_descriptors = list() + for(var/datum/wound/W in temp.wounds) + if(W.desc in wound_descriptors) + wound_descriptors[W.desc] += W.amount + continue + wound_descriptors[W.desc] = W.amount + var/list/flavor_text = list() + var/list/no_exclude = list("gaping wound", "big gaping wound", "massive wound", "large bruise",\ + "huge bruise", "massive bruise", "severe burn", "large burn", "deep burn", "carbonised area") + for(var/wound in wound_descriptors) + switch(wound_descriptors[wound]) + if(1) + if(!flavor_text.len) + flavor_text += "[t_He] has[prob(10) && !(wound in no_exclude) ? " what might be" : ""] a [wound]" + else + flavor_text += "[prob(10) && !(wound in no_exclude) ? " what might be" : ""] a [wound]" + if(2) + if(!flavor_text.len) + flavor_text += "[t_He] has[prob(10) && !(wound in no_exclude) ? " what might be" : ""] a pair of [wound]s" + else + flavor_text += "[prob(10) && !(wound in no_exclude) ? " what might be" : ""] a pair of [wound]s" + if(3 to 5) + if(!flavor_text.len) + flavor_text += "[t_He] has several [wound]s" + else + flavor_text += " several [wound]s" + if(6 to INFINITY) + if(!flavor_text.len) + flavor_text += "[t_He] has a bunch of [wound]s" + else + flavor_text += " a ton of [wound]\s" + var/flavor_text_string = "" + for(var/text = 1, text <= flavor_text.len, text++) + if(text == flavor_text.len && flavor_text.len > 1) + flavor_text_string += ", and" + else if(flavor_text.len > 1 && text > 1) + flavor_text_string += "," + flavor_text_string += flavor_text[text] + flavor_text_string += " on [t_his] [temp.display_name].
" + wound_flavor_text["[temp.name]"] = flavor_text_string + if(temp.status & ORGAN_BLEEDING) + is_bleeding["[temp.name]"] = 1 + else + wound_flavor_text["[temp.display_name]"] = "" + + //Handles the text strings being added to the actual description. + //If they have something that covers the limb, and it is not missing, put flavortext. If it is covered but bleeding, add other flavortext. + var/display_chest = 0 + var/display_shoes = 0 + var/display_gloves = 0 + if(wound_flavor_text["head"] && (is_destroyed["head"] || (!skipmask && !(wear_mask && istype(wear_mask, /obj/item/clothing/mask/gas))))) + msg += wound_flavor_text["head"] + else if(is_bleeding["head"]) + msg += "[src] has blood running down [t_his] face!\n" + if(wound_flavor_text["chest"] && !w_uniform && !skipjumpsuit) //No need. A missing chest gibs you. + msg += wound_flavor_text["chest"] + else if(is_bleeding["chest"]) + display_chest = 1 + if(wound_flavor_text["left arm"] && (is_destroyed["left arm"] || (!w_uniform && !skipjumpsuit))) + msg += wound_flavor_text["left arm"] + else if(is_bleeding["left arm"]) + display_chest = 1 + if(wound_flavor_text["left hand"] && (is_destroyed["left hand"] || (!gloves && !skipgloves))) + msg += wound_flavor_text["left hand"] + else if(is_bleeding["left hand"]) + display_gloves = 1 + if(wound_flavor_text["right arm"] && (is_destroyed["right arm"] || (!w_uniform && !skipjumpsuit))) + msg += wound_flavor_text["right arm"] + else if(is_bleeding["right arm"]) + display_chest = 1 + if(wound_flavor_text["right hand"] && (is_destroyed["right hand"] || (!gloves && !skipgloves))) + msg += wound_flavor_text["right hand"] + else if(is_bleeding["right hand"]) + display_gloves = 1 + if(wound_flavor_text["groin"] && (is_destroyed["groin"] || (!w_uniform && !skipjumpsuit))) + msg += wound_flavor_text["groin"] + else if(is_bleeding["groin"]) + display_chest = 1 + if(wound_flavor_text["left leg"] && (is_destroyed["left leg"] || (!w_uniform && !skipjumpsuit))) + msg += wound_flavor_text["left leg"] + else if(is_bleeding["left leg"]) + display_chest = 1 + if(wound_flavor_text["left foot"]&& (is_destroyed["left foot"] || (!shoes && !skipshoes))) + msg += wound_flavor_text["left foot"] + else if(is_bleeding["left foot"]) + display_shoes = 1 + if(wound_flavor_text["right leg"] && (is_destroyed["right leg"] || (!w_uniform && !skipjumpsuit))) + msg += wound_flavor_text["right leg"] + else if(is_bleeding["right leg"]) + display_chest = 1 + if(wound_flavor_text["right foot"]&& (is_destroyed["right foot"] || (!shoes && !skipshoes))) + msg += wound_flavor_text["right foot"] + else if(is_bleeding["right foot"]) + display_shoes = 1 + if(display_chest) + msg += "[src] has blood soaking through from under [t_his] clothing!\n" + if(display_shoes) + msg += "[src] has blood running from [t_his] shoes!\n" + if(display_gloves) + msg += "[src] has blood running from under [t_his] gloves!\n" + + if(digitalcamo) msg += "[t_He] [t_is] repulsively uncanny!\n" diff --git a/code/modules/mob/living/carbon/human/hud.dm b/code/modules/mob/living/carbon/human/hud.dm index 4d017d14d59..e0e3361924e 100644 --- a/code/modules/mob/living/carbon/human/hud.dm +++ b/code/modules/mob/living/carbon/human/hud.dm @@ -587,6 +587,8 @@ mymob.flash.screen_loc = "1,1 to 15,15" mymob.flash.layer = 17 + mymob.pain = new /obj/screen( null ) + /* mymob.hands = new /obj/screen( null ) mymob.hands.icon = ui_style diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index f9c572ae67b..b2d5cf58624 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -21,8 +21,31 @@ dna = new /datum/dna(null) //initialise organs - organs = newlist(/datum/organ/external/chest, /datum/organ/external/head, /datum/organ/external/l_arm, \ - /datum/organ/external/r_arm, /datum/organ/external/r_leg, /datum/organ/external/l_leg) + organs = list() + organs_by_name["chest"] = new/datum/organ/external/chest() + organs_by_name["head"] = new/datum/organ/external/head() + organs_by_name["l_arm"] = new/datum/organ/external/l_arm() + organs_by_name["r_arm"] = new/datum/organ/external/r_arm() + organs_by_name["r_leg"] = new/datum/organ/external/r_leg() + organs_by_name["l_leg"] = new/datum/organ/external/l_leg() + organs_by_name["l_hand"] = new/datum/organ/external/l_hand() + organs_by_name["r_hand"] = new/datum/organ/external/r_hand() + organs_by_name["l_foot"] = new/datum/organ/external/l_foot() + organs_by_name["r_foot"] = new/datum/organ/external/r_foot() + + // connect feet to legs and hands to arms + var/datum/organ/external/organ = organs_by_name["l_hand"] + organ.parent = organs_by_name["l_arm"] + organ = organs_by_name["r_hand"] + organ.parent = organs_by_name["r_arm"] + organ = organs_by_name["l_foot"] + organ.parent = organs_by_name["l_leg"] + organ = organs_by_name["r_foot"] + organ.parent = organs_by_name["r_leg"] + + for(var/name in organs_by_name) + organs += organs_by_name[name] + for(var/datum/organ/external/O in organs) O.owner = src @@ -518,8 +541,8 @@ //Returns "Unknown" if facially disfigured and real_name if not. Useful for setting name when polyacided or when updating a human's name variable /mob/living/carbon/human/proc/get_face_name() - var/datum/organ/external/O = get_organ("head") - if( (status_flags&DISFIGURED) || (O.brutestate+O.burnstate)>2 || !real_name ) //disfigured. use id-name if possible + var/datum/organ/external/head/head = get_organ("head") + if( !head || head.disfigured || !real_name ) //disfigured. use id-name if possible return "Unknown" return real_name diff --git a/code/modules/mob/living/carbon/human/human_damage.dm b/code/modules/mob/living/carbon/human/human_damage.dm index 13672c0baf3..e2110f90fa5 100644 --- a/code/modules/mob/living/carbon/human/human_damage.dm +++ b/code/modules/mob/living/carbon/human/human_damage.dm @@ -86,11 +86,11 @@ //Damages ONE external organ, organ gets randomly selected from damagable ones. //It automatically updates damage overlays if necesary //It automatically updates health status -/mob/living/carbon/human/take_organ_damage(var/brute, var/burn) +/mob/living/carbon/human/take_organ_damage(var/brute, var/burn, var/sharp = 0) var/list/datum/organ/external/parts = get_damageable_organs() if(!parts.len) return var/datum/organ/external/picked = pick(parts) - if(picked.take_damage(brute,burn)) + if(picked.take_damage(brute,burn,sharp)) UpdateDamageIcon() updatehealth() @@ -116,7 +116,7 @@ if(update) UpdateDamageIcon() // damage MANY external organs, in random order -/mob/living/carbon/human/take_overall_damage(var/brute, var/burn) +/mob/living/carbon/human/take_overall_damage(var/brute, var/burn, var/sharp = 0) if(nodamage) return //godmode var/list/datum/organ/external/parts = get_damageable_organs() var/update = 0 @@ -126,7 +126,7 @@ var/brute_was = picked.brute_dam var/burn_was = picked.burn_dam - update |= picked.take_damage(brute,burn) + update |= picked.take_damage(brute,burn,sharp) brute -= (picked.brute_dam - brute_was) burn -= (picked.burn_dam - burn_was) @@ -150,19 +150,17 @@ /mob/living/carbon/human/proc/get_organ(var/zone) if(!zone) zone = "chest" - for(var/datum/organ/external/O in organs) - if(O.name == zone) - return O - return null + return organs_by_name[zone] - -/mob/living/carbon/human/apply_damage(var/damage = 0,var/damagetype = BRUTE, var/def_zone = null, var/blocked = 0) +/mob/living/carbon/human/apply_damage(var/damage = 0,var/damagetype = BRUTE, var/def_zone = null, var/blocked = 0, var/obj/item/weapon) if((damagetype != BRUTE) && (damagetype != BURN)) ..(damage, damagetype, def_zone, blocked) return 1 if(blocked >= 2) return 0 + var/sharp = weapon.sharp + var/datum/organ/external/organ = null if(isorgan(def_zone)) organ = def_zone @@ -178,10 +176,10 @@ switch(damagetype) if(BRUTE) - if(organ.take_damage(damage, 0)) + if(organ.take_damage(damage, 0, sharp)) UpdateDamageIcon() if(BURN) - if(organ.take_damage(0, damage)) + if(organ.take_damage(0, damage, sharp)) UpdateDamageIcon() updatehealth() return 1 diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm index c593b32b1dc..ad2d4dc294e 100644 --- a/code/modules/mob/living/carbon/human/human_defines.dm +++ b/code/modules/mob/living/carbon/human/human_defines.dm @@ -44,6 +44,7 @@ var/icon/lying_icon = null var/list/organs = list() + var/list/organs_by_name = list() // map organ names to organs var/miming = null //Toggle for the mime's abilities. diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm index 75bacdc02b4..e62aca49398 100644 --- a/code/modules/mob/living/carbon/human/life.dm +++ b/code/modules/mob/living/carbon/human/life.dm @@ -53,6 +53,8 @@ fire_alert = 0 //Reset this here, because both breathe() and handle_environment() have a chance to set it. //TODO: seperate this out + // update the current life tick, can be used to e.g. only do something every 4 ticks + life_tick++ var/datum/gas_mixture/environment = loc.return_air() //No need to update all of these procs if the guy is dead. @@ -167,6 +169,71 @@ if(3) emote("drool") + proc/handle_organs() + // take care of organ related updates, such as broken and missing limbs + + // recalculate number of wounds + number_wounds = 0 + for(var/datum/organ/external/E in organs) + if(!E) + world << name + continue + number_wounds += E.number_wounds + + var/leg_tally = 2 + for(var/datum/organ/external/E in organs) + E.process() + if(E.status & ORGAN_ROBOT && prob(E.brute_dam + E.burn_dam)) + if(E.name == "l_hand" || E.name == "l_arm") + if(hand && equipped()) + drop_item() + emote("custom v drops what they were holding, their [E.display_name?"[E.display_name]":"[E]"] malfunctioning!") + var/datum/effect/effect/system/spark_spread/spark_system = new /datum/effect/effect/system/spark_spread() + spark_system.set_up(5, 0, src) + spark_system.attach(src) + spark_system.start() + spawn(10) + del(spark_system) + else if(E.name == "r_hand" || E.name == "r_arm") + if(!hand && equipped()) + drop_item() + emote("custom v drops what they were holding, their [E.display_name?"[E.display_name]":"[E]"] malfunctioning!") + var/datum/effect/effect/system/spark_spread/spark_system = new /datum/effect/effect/system/spark_spread() + spark_system.set_up(5, 0, src) + spark_system.attach(src) + spark_system.start() + spawn(10) + del(spark_system) + else if(E.name == "l_leg" || E.name == "l_foot" \ + || E.name == "r_leg" || E.name == "r_foot" && !lying) + leg_tally-- // let it fail even if just foot&leg + if(E.status & ORGAN_BROKEN || E.status & ORGAN_DESTROYED) + if(E.name == "l_hand" || E.name == "l_arm") + if(hand && equipped()) + if(E.status & ORGAN_SPLINTED && prob(10)) + drop_item() + emote("scream") + else + drop_item() + emote("scream") + else if(E.name == "r_hand" || E.name == "r_arm") + if(!hand && equipped()) + if(E.status & ORGAN_SPLINTED && prob(10)) + drop_item() + emote("scream") + else + drop_item() + emote("scream") + else if(E.name == "l_leg" || E.name == "l_foot" \ + || E.name == "r_leg" || E.name == "r_foot" && !lying) + if(!(E.status & ORGAN_SPLINTED)) + leg_tally-- // let it fail even if just foot&leg + // can't stand + if(leg_tally == 0 && !paralysis && !(lying || resting)) + emote("scream") + emote("collapse") + paralysis = 10 + proc/handle_mutations_and_radiation() if(getFireLoss()) @@ -817,6 +884,7 @@ silent = 0 else //ALIVE. LIGHTS ARE ON updatehealth() //TODO + handle_organs() if(health <= config.health_threshold_dead || brain_op_stage == 4.0) death() blinded = 1 diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm index 8f9135de521..eeaf1e23aa5 100644 --- a/code/modules/mob/living/carbon/human/update_icons.dm +++ b/code/modules/mob/living/carbon/human/update_icons.dm @@ -122,6 +122,7 @@ Please contact me on #coderbus IRC. ~Carn x /mob/living/carbon/human var/list/overlays_lying[TOTAL_LAYERS] var/list/overlays_standing[TOTAL_LAYERS] + var/previous_damage_appearance // store what the body last looked like, so we only have to update it if something changed //UPDATES OVERLAYS FROM OVERLAYS_LYING/OVERLAYS_STANDING @@ -164,39 +165,108 @@ Please contact me on #coderbus IRC. ~Carn x //DAMAGE OVERLAYS //constructs damage icon for each organ from mask * damage field and saves it in our overlays_ lists /mob/living/carbon/human/UpdateDamageIcon(var/update_icons=1) - var/image/standing = image("icon" = 'icons/mob/dam_human.dmi', "icon_state" = "blank") - var/image/lying = image("icon" = 'icons/mob/dam_human.dmi', "icon_state" = "blank2") - for(var/datum/organ/external/O in organs) - if(O.brutestate) - standing.overlays += "[O.icon_name]_[O.brutestate]0" //we're adding icon_states of the base image as overlays - lying.overlays += "[O.icon_name]2_[O.brutestate]0" - if(O.burnstate) - standing.overlays += "[O.icon_name]_0[O.burnstate]" - lying.overlays += "[O.icon_name]2_0[O.burnstate]" + // first check whether something actually changed about damage appearance + var/damage_appearance = "" + + for(var/datum/organ/external/O in organs) + if(O.status & ORGAN_DESTROYED) damage_appearance += "d" + else + damage_appearance += O.damage_state + + if(damage_appearance == previous_damage_appearance) + // nothing to do here + return + + previous_damage_appearance = damage_appearance + + var/icon/standing = new /icon('dam_human.dmi', "00") + var/icon/lying = new /icon('dam_human.dmi', "00-2") + + // blend the individual damage states with our icons + for(var/datum/organ/external/O in organs) + if(!(O.status & ORGAN_DESTROYED)) + O.update_icon() + var/icon/DI = new /icon('icons/mob/dam_human.dmi', O.damage_state) // the damage icon for whole human + DI.Blend(new /icon('dam_mask.dmi', O.icon_name), ICON_MULTIPLY) // mask with this organ's pixels + + standing.Blend(DI,ICON_OVERLAY) + DI = new /icon('icons/mob/dam_human.dmi', "[O.damage_state]-2") // repeat for lying icons + DI.Blend(new /icon('dam_mask.dmi', "[O.icon_name]2"), ICON_MULTIPLY) + + lying.Blend(DI,ICON_OVERLAY) + + var/image/standing_image = new /image("icon" = standing) + var/image/lying_image = new /image("icon" = lying) + + overlays_standing[DAMAGE_LAYER] = standing_image + overlays_lying[DAMAGE_LAYER] = lying_image - overlays_standing[DAMAGE_LAYER] = standing - overlays_lying[DAMAGE_LAYER] = lying if(update_icons) update_icons() //BASE MOB SPRITE /mob/living/carbon/human/proc/update_body(var/update_icons=1) + if(stand_icon) del(stand_icon) if(lying_icon) del(lying_icon) if(dna && dna.mutantrace) return - var/husk = (HUSK in src.mutations) - var/fat = (FAT in src.mutations) + var/g = "m" if(gender == FEMALE) g = "f" + var/husk = (HUSK in src.mutations) + var/obese = (FAT in src.mutations) + + // whether to draw the individual limbs + var/individual_limbs = 1 + //Base mob icon - if(husk) - stand_icon = new /icon('icons/mob/human.dmi', "husk_s") - lying_icon = new /icon('icons/mob/human.dmi', "husk_l") - else if(fat) + if(obese) + // Sorry, no dismemberment for fat people. stand_icon = new /icon('icons/mob/human.dmi', "fatbody_s") lying_icon = new /icon('icons/mob/human.dmi', "fatbody_l") + individual_limbs = 0 else - stand_icon = new /icon('icons/mob/human.dmi', "body_[g]_s") - lying_icon = new /icon('icons/mob/human.dmi', "body_[g]_l") + stand_icon = new /icon('icons/mob/human.dmi', "torso_[g]_s") + lying_icon = new /icon('icons/mob/human.dmi', "torso_[g]_l") + + // Draw each individual limb + if(individual_limbs) + stand_icon.Blend(new /icon('icons/mob/human.dmi', "chest_[g]_s"), ICON_OVERLAY) + lying_icon.Blend(new /icon('icons/mob/human.dmi', "chest_[g]_l"), ICON_OVERLAY) + + var/datum/organ/external/head = get_organ("head") + if(head && !(head.status & ORGAN_DESTROYED)) + stand_icon.Blend(new /icon('icons/mob/human.dmi', "head_[g]_s"), ICON_OVERLAY) + lying_icon.Blend(new /icon('icons/mob/human.dmi', "head_[g]_l"), ICON_OVERLAY) + + for(var/datum/organ/external/part in organs) + if(!istype(part, /datum/organ/external/groin) \ + && !istype(part, /datum/organ/external/chest) \ + && !istype(part, /datum/organ/external/head) \ + && !(part.status & ORGAN_DESTROYED)) + var/icon/temp = new /icon('human.dmi', "[part.icon_name]_s") + if(part.status & ORGAN_ROBOT) temp.MapColors(rgb(77,77,77), rgb(150,150,150), rgb(28,28,28), rgb(0,0,0)) + stand_icon.Blend(temp, ICON_OVERLAY) + temp = new /icon('human.dmi', "[part.icon_name]_l") + if(part.status & ORGAN_ROBOT) temp.MapColors(rgb(77,77,77), rgb(150,150,150), rgb(28,28,28), rgb(0,0,0)) + lying_icon.Blend(temp , ICON_OVERLAY) + + stand_icon.Blend(new /icon('human.dmi', "groin_[g]_s"), ICON_OVERLAY) + lying_icon.Blend(new /icon('human.dmi', "groin_[g]_l"), ICON_OVERLAY) + + if (husk) + var/icon/husk_s = new /icon('human.dmi', "husk_s") + var/icon/husk_l = new /icon('human.dmi', "husk_l") + + for(var/datum/organ/external/part in organs) + if(!istype(part, /datum/organ/external/groin) \ + && !istype(part, /datum/organ/external/chest) \ + && !istype(part, /datum/organ/external/head) \ + && (part.status & ORGAN_DESTROYED)) + husk_s.Blend(new /icon('dam_mask.dmi', "[part.icon_name]"), ICON_SUBTRACT) + husk_l.Blend(new /icon('dam_mask.dmi', "[part.icon_name]2"), ICON_SUBTRACT) + + stand_icon.Blend(husk_s, ICON_OVERLAY) + lying_icon.Blend(husk_l, ICON_OVERLAY) //Skin tone if(s_tone >= 0) @@ -207,6 +277,7 @@ Please contact me on #coderbus IRC. ~Carn x lying_icon.Blend(rgb(-s_tone, -s_tone, -s_tone), ICON_SUBTRACT) //Eyes + // Note: These used to be in update_face(), and the fact they're here will make it difficult to create a disembodied head var/icon/eyes_s = new/icon('icons/mob/human_face.dmi', "eyes_s") var/icon/eyes_l = new/icon('icons/mob/human_face.dmi', "eyes_l") eyes_s.Blend(rgb(r_eyes, g_eyes, b_eyes), ICON_ADD) @@ -219,11 +290,11 @@ Please contact me on #coderbus IRC. ~Carn x lying_icon.Blend(new/icon('icons/mob/human_face.dmi', "mouth_[g]_l"), ICON_OVERLAY) //Underwear - if(underwear < 12 && underwear > 0) - if(!fat) - stand_icon.Blend(new /icon('icons/mob/human.dmi', "underwear[underwear]_[g]_s"), ICON_OVERLAY) - lying_icon.Blend(new /icon('icons/mob/human.dmi', "underwear[underwear]_[g]_l"), ICON_OVERLAY) - if(update_icons) update_icons() + if(underwear < 6 && underwear > 0) + if(!obese) + stand_icon.Blend(new /icon('human.dmi', "underwear[underwear]_[g]_s"), ICON_OVERLAY) + lying_icon.Blend(new /icon('human.dmi', "underwear[underwear]_[g]_l"), ICON_OVERLAY) + //HAIR OVERLAY @@ -649,6 +720,42 @@ Please contact me on #coderbus IRC. ~Carn x overlays_standing[L_HAND_LAYER] = null if(update_icons) update_icons() +// Used mostly for creating head items +/mob/living/carbon/human/proc/generate_head_icon() + var/g = "m" + if (gender == FEMALE) g = "f" + + //base icons + var/icon/face_lying = new /icon('icons/mob/human_face.dmi',"bald_l") + + if(f_style) + var/datum/sprite_accessory/facial_hair_style = facial_hair_styles_list[f_style] + if(facial_hair_style) + var/icon/facial_l = new/icon("icon" = facial_hair_style.icon, "icon_state" = "[facial_hair_style.icon_state]_l") + facial_l.Blend(rgb(r_facial, g_facial, b_facial), ICON_ADD) + face_lying.Blend(facial_l, ICON_OVERLAY) + + if(h_style) + var/datum/sprite_accessory/hair_style = hair_styles_list[h_style] + if(hair_style) + var/icon/hair_l = new/icon("icon" = hair_style.icon, "icon_state" = "[hair_style.icon_state]_l") + hair_l.Blend(rgb(r_hair, g_hair, b_hair), ICON_ADD) + face_lying.Blend(hair_l, ICON_OVERLAY) + + //Eyes + // Note: These used to be in update_face(), and the fact they're here will make it difficult to create a disembodied head + var/icon/eyes_s = new/icon('icons/mob/human_face.dmi', "eyes_s") + var/icon/eyes_l = new/icon('icons/mob/human_face.dmi', "eyes_l") + eyes_s.Blend(rgb(r_eyes, g_eyes, b_eyes), ICON_ADD) + eyes_l.Blend(rgb(r_eyes, g_eyes, b_eyes), ICON_ADD) + face_lying.Blend(eyes_l, ICON_OVERLAY) + + //Mouth + face_lying.Blend(new/icon('icons/mob/human_face.dmi', "mouth_[g]_l"), ICON_OVERLAY) + + var/image/face_lying_image = new /image(icon = face_lying) + return face_lying_image + //Human Overlays Indexes///////// #undef MUTANTRACE_LAYER #undef MUTATIONS_LAYER diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm index 4a442e48e62..c46484f1e63 100644 --- a/code/modules/mob/mob_defines.dm +++ b/code/modules/mob/mob_defines.dm @@ -30,6 +30,7 @@ var/obj/screen/nutrition_icon = null var/obj/screen/pressure = null var/obj/screen/damageoverlay = null + var/obj/screen/pain = null /*A bunch of this stuff really needs to go under their own defines instead of being globally attached to mob. A variable should only be globally attached to turfs/objects/whatever, when it is in fact needed as such. diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index c7921f001b4..6702beab34d 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -115,6 +115,9 @@ proc/isorgan(A) return 1 return 0 +proc/hasorgans(A) + return (ishuman(A) || ismonkey(A)) + /proc/hsl2rgb(h, s, l) return diff --git a/code/setup.dm b/code/setup.dm index e77c20295f4..ad54c4008ab 100644 --- a/code/setup.dm +++ b/code/setup.dm @@ -500,3 +500,36 @@ var/list/liftable_structures = list(\ //some arbitrary defines to be used by self-pruning global lists. (see master_controller) #define PROCESS_KILL 26 //Used to trigger removal from a processing list + +//Damage things +#define CUT "cut" +#define BRUISE "bruise" +#define BRUTE "brute" +#define BURN "fire" +#define TOX "tox" +#define OXY "oxy" +#define CLONE "clone" +#define HALLOSS "halloss" + +#define STUN "stun" +#define WEAKEN "weaken" +#define PARALYZE "paralize" +#define IRRADIATE "irradiate" +#define STUTTER "stutter" +#define SLUR "slur" +#define EYE_BLUR "eye_blur" +#define DROWSY "drowsy" + +///////////////// +//ORGAN DEFINES// +///////////////// +#define ORGAN_CUT_AWAY 1 +#define ORGAN_GAUZED 2 +#define ORGAN_ATTACHABLE 4 +#define ORGAN_BLEEDING 8 +#define ORGAN_BANDAGED 16 +#define ORGAN_BROKEN 32 +#define ORGAN_DESTROYED 64 +#define ORGAN_ROBOT 128 +#define ORGAN_SPLINTED 256 +#define SALVED 512 \ No newline at end of file diff --git a/config/game_options.txt b/config/game_options.txt index 9ec32a29484..9eef19a5c8e 100644 --- a/config/game_options.txt +++ b/config/game_options.txt @@ -6,6 +6,14 @@ HEALTH_THRESHOLD_CRIT 0 ## level of health at which a mob becomes dead HEALTH_THRESHOLD_DEAD -100 +## multiplier which enables organs to take more damage before bones breaking or limbs being destroyed +## 100 means normal, 50 means half +ORGAN_HEALTH_MULTIPLIER 100 + +## multiplier which influences how fast organs regenerate naturally +## 100 means normal, 50 means half +ORGAN_REGENERATION_MULTIPLIER 100 + ### REVIVAL ### ## whether pod plants work or not diff --git a/icons/mob/dam_human.dmi b/icons/mob/dam_human.dmi index 379f7dc5eb8..c68299554c8 100644 Binary files a/icons/mob/dam_human.dmi and b/icons/mob/dam_human.dmi differ diff --git a/icons/mob/dam_mask.dmi b/icons/mob/dam_mask.dmi new file mode 100644 index 00000000000..5cf1e09f19b Binary files /dev/null and b/icons/mob/dam_mask.dmi differ diff --git a/icons/mob/human.dmi b/icons/mob/human.dmi index 1526448c6fd..1dff87d69e0 100644 Binary files a/icons/mob/human.dmi and b/icons/mob/human.dmi differ diff --git a/tgstation.dme b/tgstation.dme index 800185439ab..7caa8572108 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -223,7 +223,6 @@ // END_FILE_DIR // BEGIN_PREFERENCES -#define DEBUG // END_PREFERENCES // BEGIN_INCLUDE @@ -307,6 +306,7 @@ #include "code\datums\helper_datums\topic_input.dm" #include "code\datums\organs\organ.dm" #include "code\datums\organs\organ_external.dm" +#include "code\datums\organs\wound.dm" #include "code\datums\spells\area_teleport.dm" #include "code\datums\spells\conjure.dm" #include "code\datums\spells\emplosion.dm"