diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index 1d133b1af99..ef5426e0115 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -76,6 +76,7 @@ GLOBAL_LIST_INIT(turfs_without_ground, typecacheof(list( #define isdullahan(A) (is_species(A, /datum/species/dullahan)) #define ismonkey(A) (is_species(A, /datum/species/monkey)) #define isandroid(A) (is_species(A, /datum/species/android)) +#define ispreternis(A) (is_species(A, /datum/species/preternis)) //more carbon mobs diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index 54eb27eb2c1..0a90b388aae 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -445,3 +445,17 @@ //Saves a proc call, life is suffering. If who has no targets_from var, we assume it's just who #define GET_TARGETS_FROM(who) (who.targets_from ? who.get_targets_from() : who) + +#define PRETERNIS_LEVEL_FULL 550 +#define PRETERNIS_LEVEL_WELL_FED 450 +#define PRETERNIS_LEVEL_FED 350 +#define PRETERNIS_LEVEL_HUNGRY 250 +#define PRETERNIS_LEVEL_STARVING 150 +#define PRETERNIS_LEVEL_NONE 0 + +#define ELECTRICITY_TO_NUTRIMENT_FACTOR 0.44 //1 power unit to 44 preternis charge they can uncharge an apc to 50% at most + +#define PRETERNIS_NV_OFF 2 //numbers of tile they can see +#define PRETERNIS_NV_ON 8 + +#define BODYPART_ANY -1 //use this when healing with something that needs a specefied bodypart type for all diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index d83c5d41cce..433c353ca3f 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -226,6 +226,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_NOSOFTCRIT "nosoftcrit" #define TRAIT_MINDSHIELD "mindshield" #define TRAIT_DISSECTED "dissected" +#define TRAIT_MEDICALIGNORE "medical_ignore" /// Can hear observers #define TRAIT_SIXTHSENSE "sixth_sense" #define TRAIT_FEARLESS "fearless" diff --git a/code/datums/diseases/_MobProcs.dm b/code/datums/diseases/_MobProcs.dm index e20fb3ba1c9..9da603b4246 100644 --- a/code/datums/diseases/_MobProcs.dm +++ b/code/datums/diseases/_MobProcs.dm @@ -137,6 +137,10 @@ if(HAS_TRAIT(src, TRAIT_VIRUSIMMUNE) && !D.bypasses_immunity) return FALSE + var/infectchance = dna.species ? dna.species.virus_infect_chance : 100 + if(!prob(infectchance)) + return FALSE + for(var/thing in D.required_organs) if(!((locate(thing) in bodyparts) || (locate(thing) in internal_organs))) return FALSE diff --git a/code/datums/diseases/advance/advance.dm b/code/datums/diseases/advance/advance.dm index a62917f82eb..bdac2bfa12a 100644 --- a/code/datums/diseases/advance/advance.dm +++ b/code/datums/diseases/advance/advance.dm @@ -222,6 +222,13 @@ SSdisease.archive_diseases[the_id] = Copy() if(new_name) AssignName() + if(affected_mob?.dna) + var/datum/species/S = affected_mob.dna.species + properties["resistance"] += S.virus_resistance_boost + properties["stealth"] += S.virus_stealth_boost + properties["stage_rate"] += S.virus_stage_rate_boost + properties["transmittable"] += S.virus_transmittable_boost + AssignProperties() //Generate disease properties based on the effects. Returns an associated list. /datum/disease/advance/proc/GenerateProperties() diff --git a/code/game/objects/items/defib.dm b/code/game/objects/items/defib.dm index fbfa8da26b9..4fbd9ddeab5 100644 --- a/code/game/objects/items/defib.dm +++ b/code/game/objects/items/defib.dm @@ -634,8 +634,8 @@ var/mobhealth = H.health H.adjustOxyLoss((mobhealth - HALFWAYCRITDEATH) * (H.getOxyLoss() / overall_damage), 0) H.adjustToxLoss((mobhealth - HALFWAYCRITDEATH) * (H.getToxLoss() / overall_damage), 0, TRUE) // force tox heal for toxin lovers too - H.adjustFireLoss((mobhealth - HALFWAYCRITDEATH) * (total_burn / overall_damage), 0) - H.adjustBruteLoss((mobhealth - HALFWAYCRITDEATH) * (total_brute / overall_damage), 0) + H.adjustFireLoss((mobhealth - HALFWAYCRITDEATH) * (total_burn / overall_damage), 0, required_status = BODYPART_ANY) + H.adjustBruteLoss((mobhealth - HALFWAYCRITDEATH) * (total_brute / overall_damage), 0, required_status = BODYPART_ANY) H.updatehealth() // Previous "adjust" procs don't update health, so we do it manually. user.visible_message(span_notice("[req_defib ? "[defib]" : "[src]"] pings: Resuscitation successful.")) playsound(src, 'sound/machines/defib_success.ogg', 50, FALSE) diff --git a/code/game/objects/items/inducer.dm b/code/game/objects/items/inducer.dm index 7d29a4ce4a7..619a08eefaf 100644 --- a/code/game/objects/items/inducer.dm +++ b/code/game/objects/items/inducer.dm @@ -107,6 +107,38 @@ if(istype(A, /obj/item/gun/energy)) to_chat(user, span_alert("Error unable to interface with device.")) return FALSE + if(ishuman(A)) + var/mob/living/carbon/human/H = A + if(ispreternis(H)) //let's charge some dumb robot players + if(user.zone_selected != BODY_ZONE_CHEST) + to_chat(user, span_warning("You need to target [A]'s chest with [src] to recharge [H.p_them()]!")) + recharging = FALSE + return TRUE + coefficient = 0.1 + var/totransfer = min(cell.charge,(powertransfer * coefficient)) + var/datum/species/preternis/preternis = H.dna.species + var/done_any = FALSE + if(preternis.charge >= PRETERNIS_LEVEL_FULL - 25) + to_chat(user, span_notice("[A] is fully charged!")) + recharging = FALSE + return TRUE + user.visible_message("[user] starts recharging [A] with [src].",span_notice("You start recharging [A] with [src].")) + while(preternis.charge < PRETERNIS_LEVEL_FULL - 25) + if(do_after(user, 1 SECONDS, target = user) && cell.charge) + done_any = TRUE + cell.use(totransfer*coefficient) + preternis.charge = clamp(preternis.charge + (powertransfer*coefficient), PRETERNIS_LEVEL_NONE, PRETERNIS_LEVEL_FULL) + H.apply_damage(totransfer*coefficient, BURN, BODY_ZONE_CHEST, wound_bonus = CANT_WOUND) + user.visible_message("Smoke rises off of [A]'s body!",span_notice("You smell something burning as [A] is charged by the [src]!")) + do_sparks(1, FALSE, A) + if(O) + O.update_icon() + else + break + if(done_any) // Only show a message if we succeeded at least once + user.visible_message("[user] recharged [A]!",span_notice("You recharged [A]!")) + recharging = FALSE + return TRUE if(istype(A, /obj)) O = A if(C) diff --git a/code/modules/antagonists/changeling/powers/augmented_eyesight.dm b/code/modules/antagonists/changeling/powers/augmented_eyesight.dm index 19f211014a8..d96afc95a41 100644 --- a/code/modules/antagonists/changeling/powers/augmented_eyesight.dm +++ b/code/modules/antagonists/changeling/powers/augmented_eyesight.dm @@ -25,6 +25,11 @@ ..() var/obj/item/organ/eyes/E = user.getorganslot(ORGAN_SLOT_EYES) if(E) + if(istype(E, /obj/item/organ/eyes/robotic/preternis)) + E.Remove(user, 1) + var/obj/item/organ/eyes/neweyes = new(user) + neweyes.Insert(user, 1) + neweyes.flash_protect = 2 if(!active) E.sight_flags |= SEE_MOBS | SEE_OBJS | SEE_TURFS //Add sight flags to the user's eyes E.flash_protect = FLASH_PROTECTION_SENSITIVE //Adjust the user's eyes' flash protection diff --git a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm index 7738ba4a36d..1a8e4549396 100644 --- a/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm +++ b/code/modules/atmospherics/machinery/components/unary_devices/cryo.dm @@ -237,6 +237,15 @@ GLOBAL_VAR_INIT(cryo_overlay_cover_off, mutable_appearance('icons/obj/cryogenics return if(mob_occupant.stat == DEAD) // We don't bother with dead people. return + + if(HAS_TRAIT(mob_occupant,TRAIT_MEDICALIGNORE) && !(mob_occupant.getCloneLoss() + mob_occupant.getToxLoss() + mob_occupant.getOxyLoss())) + src.visible_message("[src] is unable to treat [mob_occupant] as they cannot be treated with conventional medicine.") + playsound(src,'sound/machines/cryo_warning_ignore.ogg',60,1) + on = FALSE + sleep(2)// here for timing. Shuts off right at climax of the effect before falloff. + update_icon() + return + if(mob_occupant.get_organic_health() >= mob_occupant.getMaxHealth()) // Don't bother with fully healed people. if(iscarbon(mob_occupant)) var/mob/living/carbon/C = mob_occupant diff --git a/code/modules/language/language_holder.dm b/code/modules/language/language_holder.dm index 4a524998622..a96991e7082 100644 --- a/code/modules/language/language_holder.dm +++ b/code/modules/language/language_holder.dm @@ -265,6 +265,12 @@ Key procs spoken_languages = list(/datum/language/slime = list(LANGUAGE_ATOM)) blocked_languages = list(/datum/language/common = list(LANGUAGE_ATOM)) +/datum/language_holder/preternis + understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), + /datum/language/machine = list(LANGUAGE_ATOM),) + spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM), + /datum/language/machine = list(LANGUAGE_ATOM),) + /datum/language_holder/lizard understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM), /datum/language/draconic = list(LANGUAGE_ATOM)) diff --git a/code/modules/mob/living/carbon/damage_procs.dm b/code/modules/mob/living/carbon/damage_procs.dm index 4a77ff1b8c6..e7de845fef1 100644 --- a/code/modules/mob/living/carbon/damage_procs.dm +++ b/code/modules/mob/living/carbon/damage_procs.dm @@ -159,7 +159,7 @@ var/list/obj/item/bodypart/parts = list() for(var/X in bodyparts) var/obj/item/bodypart/BP = X - if(status && (BP.status != status)) + if(!(status == BODYPART_ANY) && (status && (BP.status != status))) continue if((brute && BP.brute_dam) || (burn && BP.burn_dam) || (stamina && BP.stamina_dam)) parts += BP @@ -194,12 +194,12 @@ * It automatically updates health status */ /mob/living/carbon/heal_bodypart_damage(brute = 0, burn = 0, stamina = 0, updating_health = TRUE, required_status) - var/list/obj/item/bodypart/parts = get_damaged_bodyparts(brute,burn,stamina,required_status) + var/list/obj/item/bodypart/parts = get_damaged_bodyparts(brute,burn,stamina,required_status ? required_status : BODYPART_ORGANIC) if(!parts.len) return var/obj/item/bodypart/picked = pick(parts) var/damage_calculator = picked.get_damage(TRUE) //heal_damage returns update status T/F instead of amount healed so we dance gracefully around this - if(picked.heal_damage(brute, burn, stamina, required_status)) + if(picked.heal_damage(brute, burn, stamina, required_status ? required_status : BODYPART_ORGANIC)) update_damage_overlays() return max(damage_calculator - picked.get_damage(TRUE), 0) diff --git a/code/modules/mob/living/carbon/human/emote.dm b/code/modules/mob/living/carbon/human/emote.dm index 8d05212f104..cf96cc4b592 100644 --- a/code/modules/mob/living/carbon/human/emote.dm +++ b/code/modules/mob/living/carbon/human/emote.dm @@ -79,6 +79,8 @@ 'sound/creatures/monkey/monkey_screech_5.ogg', 'sound/creatures/monkey/monkey_screech_6.ogg', 'sound/creatures/monkey/monkey_screech_7.ogg') + else if (ispreternis(H)) + return 'goon/sound/robot_scream.ogg' /datum/emote/living/carbon/human/scream/screech //If a human tries to screech it'll just scream. key = "screech" diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm index 5f6f1867fd8..6d87f6c0889 100644 --- a/code/modules/mob/living/carbon/human/species.dm +++ b/code/modules/mob/living/carbon/human/species.dm @@ -207,6 +207,17 @@ GLOBAL_LIST_EMPTY(roundstart_races) ///List of visual overlays created by handle_body() var/list/body_vis_overlays = list() + var/draw_robot_hair = FALSE //DAMN ROBOTS STEALING OUR HAIR AND AIR + + ///Chance of a mob with this species to infect with a virus + var/virus_infect_chance = 100 + + ///How big boost to it's propetries gets a disease that infects a mob with this species + var/virus_resistance_boost = 0 + var/virus_stealth_boost = 0 + var/virus_stage_rate_boost = 0 + var/virus_transmittable_boost = 0 + /////////// // PROCS // /////////// @@ -440,6 +451,10 @@ GLOBAL_LIST_EMPTY(roundstart_races) C.add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/species, multiplicative_slowdown=speedmod) + if(draw_robot_hair) + for(var/obj/item/bodypart/BP in C.bodyparts) + BP.draw_robot_hair = TRUE + SEND_SIGNAL(C, COMSIG_SPECIES_GAIN, src, old_species) /** @@ -482,6 +497,9 @@ GLOBAL_LIST_EMPTY(roundstart_races) C.remove_movespeed_modifier(/datum/movespeed_modifier/species) + for(var/obj/item/bodypart/BP in C.bodyparts) + BP.draw_robot_hair = initial(BP.draw_robot_hair) + SEND_SIGNAL(C, COMSIG_SPECIES_LOSS, src) /** @@ -510,7 +528,7 @@ GLOBAL_LIST_EMPTY(roundstart_races) var/dynamic_fhair_suffix = "" //for augmented heads - if(noggin.status == BODYPART_ROBOTIC) + if(noggin.status == BODYPART_ROBOTIC && !draw_robot_hair) return //we check if our hat or helmet hides our facial hair. @@ -1561,6 +1579,9 @@ GLOBAL_LIST_EMPTY(roundstart_races) // called before a projectile hit return 0 +/datum/species/proc/spec_AltClickOn(atom/A,mob/living/carbon/human/H) + return FALSE + ////////////////////////// // ENVIRONMENT HANDLERS // ////////////////////////// diff --git a/code/modules/mob/living/carbon/human/species_types/preternis/organs.dm b/code/modules/mob/living/carbon/human/species_types/preternis/organs.dm new file mode 100644 index 00000000000..a7d98834918 --- /dev/null +++ b/code/modules/mob/living/carbon/human/species_types/preternis/organs.dm @@ -0,0 +1,97 @@ +/obj/item/organ/eyes/robotic/preternis + name = "preternis eyes" + desc = "An experimental upgraded version of eyes that can see in the dark. They are designed to fit preternis" + see_in_dark = PRETERNIS_NV_ON + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE + //preternis eyes need to be powered by a preternis to function, in a non preternis they slowly power down to blindness + organ_flags = ORGAN_SYNTHETIC + + low_threshold_passed = span_info("Your Preternis eyes switch to battery saver mode.") + high_threshold_passed = span_info("Your Preternis eyes only show a sliver of battery life left!") + now_failing = span_warning("An empty battery icon is all you can see as your eyes shut off!") + now_fixed = span_info("Lines of text scroll in your vision as your eyes begin rebooting.") + high_threshold_cleared = span_info("Your Preternis eyes have recharged enough to re-enable most functionality.") + low_threshold_cleared = span_info("Your Preternis eyes have almost fully recharged.") + var/powered = TRUE + actions_types = list(/datum/action/item_action/organ_action/use) + var/night_vision = TRUE + +/obj/item/organ/eyes/robotic/preternis/ui_action_click() + if(damage > low_threshold) + //no nightvision if your eyes are hurt + return + var/datum/species/preternis/S = owner.dna.species + if(S.charge < PRETERNIS_LEVEL_FED) + return + sight_flags = initial(sight_flags) + switch(lighting_alpha) + if (LIGHTING_PLANE_ALPHA_VISIBLE) + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE + if (LIGHTING_PLANE_ALPHA_MOSTLY_VISIBLE) + lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE + if (LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE) + lighting_alpha = LIGHTING_PLANE_ALPHA_INVISIBLE + else + lighting_alpha = LIGHTING_PLANE_ALPHA_VISIBLE + sight_flags &= ~SEE_BLACKNESS + owner.update_sight() + +/obj/item/organ/eyes/robotic/preternis/on_life() + . = ..() + if(!owner) + return + if(ispreternis(owner) && !powered) + powered = TRUE + to_chat(owner, span_notice("A battery icon disappears from your vision as your [src] switch to external power.")) + if(!ispreternis(owner) && powered) //these eyes depend on being inside a preternis for power + powered = FALSE + to_chat(owner, span_boldwarning("Your [src] flash warnings that they've lost their power source, and are running on emergency power!")) + if(powered) + //when powered, they recharge by healing + owner.adjustOrganLoss(ORGAN_SLOT_EYES,-0.5) + else + //to simulate running out of power, they take damage + owner.adjustOrganLoss(ORGAN_SLOT_EYES,0.5) + + if(damage < low_threshold) + if(see_in_dark == PRETERNIS_NV_OFF) + see_in_dark = PRETERNIS_NV_ON + owner.update_sight() + else + if(see_in_dark == PRETERNIS_NV_ON) + see_in_dark = PRETERNIS_NV_OFF + owner.update_sight() + if(lighting_alpha < LIGHTING_PLANE_ALPHA_VISIBLE) + lighting_alpha = LIGHTING_PLANE_ALPHA_VISIBLE + sight_flags &= ~SEE_BLACKNESS + owner.update_sight() + +/obj/item/organ/eyes/robotic/preternis/examine(mob/user) + . = ..() + if(status == ORGAN_ROBOTIC && (organ_flags & ORGAN_FAILING)) + . += span_warning("[src] appears to be completely out of charge. However, that's nothing popping them back in a Preternis wouldn't fix.") + + else if(organ_flags & ORGAN_FAILING) + . += span_warning("[src] appears to be completely out of charge. If they were put back in a Preternis they would surely recharge in time.") + + else if(damage > high_threshold) + . += span_warning("[src] seem to flicker on and off. They must be pretty low on charge without being in a Preternis") + +/obj/item/organ/lungs/preternis + name = "preternis lungs" + desc = "An experimental set of lungs. Due to the cybernetic nature of these lungs,they are less resistant to heat and cold but are more efficent at filtering oxygen." + icon_state = "lungs-c" + safe_oxygen_min = 12 + safe_toxins_max = 10 + gas_stimulation_min = 0.1 //fucking filters removing my stimulants + + cold_level_1_threshold = 280 + cold_level_1_damage = 1.5 + cold_level_2_threshold = 260 + cold_level_2_damage = 3 + cold_level_3_threshold = 200 + cold_level_3_damage = 4.5 + + heat_level_1_threshold = 320 + heat_level_2_threshold = 400 + heat_level_3_threshold = 600 //HALP MY LUNGS ARE ON FIRE diff --git a/code/modules/mob/living/carbon/human/species_types/preternis/power_suck.dm b/code/modules/mob/living/carbon/human/species_types/preternis/power_suck.dm new file mode 100644 index 00000000000..522090c5ada --- /dev/null +++ b/code/modules/mob/living/carbon/human/species_types/preternis/power_suck.dm @@ -0,0 +1,189 @@ +/mob/living/carbon/AltClickOn(atom/A) + dna?.species.spec_AltClickOn(A,src) + return ..() + +/datum/species/preternis/spec_AltClickOn(atom/A,H) + return drain_power_from(H, A) + +/datum/species/preternis/proc/drain_power_from(mob/living/carbon/human/H, atom/A) + + if(get_dist(get_turf(H), get_turf(A)) > 1) + to_chat(H, span_warning("[A] is too far away!")) + return FALSE + + if(!istype(H) || !A) + return FALSE + + if(draining) + to_chat(H,span_info("CONSUME protocols can only be used on one object at any single time.")) + return FALSE + if(!A.can_consume_power_from()) + return FALSE //if it returns text, we want it to continue so we can get the error message later. + + draining = TRUE + + var/siemens_coefficient = 1 + + if(H.reagents.has_reagent("teslium")) + siemens_coefficient *= 1.5 + + if (charge >= PRETERNIS_LEVEL_FULL - 25) //just to prevent spam a bit + to_chat(H,span_notice("CONSUME protocol reports no need for additional power at this time.")) + draining = FALSE + return TRUE + + if(H.gloves) + if(!H.gloves.siemens_coefficient) + to_chat(H, span_info("NOTICE: [H.gloves] prevent electrical contact - CONSUME protocol aborted.")) + draining = FALSE + return TRUE + else + if(H.gloves.siemens_coefficient < 1) + to_chat(H, span_info("NOTICE: [H.gloves] are interfering with electrical contact - advise removal before activating CONSUME protocol.")) + siemens_coefficient *= H.gloves.siemens_coefficient + + H.face_atom(A) + H.visible_message(span_warning("[H] starts placing their hands on [A]..."), span_warning("You start placing your hands on [A]...")) + if(!do_after(H, 20, target = A)) + to_chat(H, span_info("CONSUME protocol aborted.")) + draining = FALSE + return TRUE + + to_chat(H, span_info("Extracutaneous implants detect viable power source. Initiating CONSUME protocol.")) + + var/done = FALSE + var/drain = 150 * siemens_coefficient + + var/cycle = 0 + var/datum/effect_system/spark_spread/spark_system = new /datum/effect_system/spark_spread() + spark_system.attach(A) + spark_system.set_up(5, 0, A) + + + + while(!done) + cycle++ + var/nutritionIncrease = drain * ELECTRICITY_TO_NUTRIMENT_FACTOR + + if(charge + nutritionIncrease > PRETERNIS_LEVEL_FULL) + nutritionIncrease = clamp(PRETERNIS_LEVEL_FULL - charge, PRETERNIS_LEVEL_NONE,PRETERNIS_LEVEL_FULL) //if their nutrition goes up from some other source, this could be negative, which would cause bad things to happen. + drain = nutritionIncrease/ELECTRICITY_TO_NUTRIMENT_FACTOR + + if (do_after(H,5, target = A)) + var/can_drain = A.can_consume_power_from() + if(!can_drain || istext(can_drain)) + if(istext(can_drain)) + to_chat(H,can_drain) + done = TRUE + else + playsound(A.loc, "sparks", 50, 1) + if(prob(75)) + spark_system.start() + var/drained = A.consume_power_from(drain) + if(drained < drain) + to_chat(H,span_info("[A]'s power has been depleted, CONSUME protocol halted.")) + done = TRUE + charge = clamp(charge + (drained * ELECTRICITY_TO_NUTRIMENT_FACTOR),PRETERNIS_LEVEL_NONE,PRETERNIS_LEVEL_FULL) + + if(!done) + if(charge > (PRETERNIS_LEVEL_FULL - 25)) + to_chat(H,span_info("CONSUME protocol complete. Physical nourishment refreshed.")) + done = TRUE + else if(!(cycle % 4)) + var/nutperc = round((charge / PRETERNIS_LEVEL_FULL) * 100) + to_chat(H,span_info("CONSUME protocol continues. Current satiety level: [nutperc]%.")) + else + done = TRUE + qdel(spark_system) + draining = FALSE + return TRUE + +/atom/proc/can_consume_power_from() + return FALSE //if a string is returned, it will evaluate as false and be output to the person draining. + +/atom/proc/consume_power_from(amount) + return FALSE //return the amount that was drained. + +#define MIN_DRAINABLE_POWER 10 + +//CELL// +/obj/item/stock_parts/cell/can_consume_power_from() + if(charge < MIN_DRAINABLE_POWER) + return span_info("Power cell depleted, cannot consume power.") + return TRUE + +/obj/item/stock_parts/cell/consume_power_from(amount) + if((charge - amount) < MIN_DRAINABLE_POWER) + amount = max(charge - MIN_DRAINABLE_POWER, 0) + use(amount) + return amount + +//APC// +/obj/machinery/power/apc/can_consume_power_from() + if(!cell) + return span_info("APC cell absent, cannot consume power.") + if(machine_stat & BROKEN) + return span_info("APC is damaged, cannot consume power.") + if(!operating || shorted) + return span_info("APC main breaker is off, cannot consume power.") + if(cell.charge < MIN_DRAINABLE_POWER) + return span_info("APC cell depleted, cannot consume power.") + return TRUE + +/obj/machinery/power/apc/consume_power_from(amount) + if((cell.charge - amount) < MIN_DRAINABLE_POWER) + amount = max(cell.charge - MIN_DRAINABLE_POWER, 0) + cell.use(amount) + if(charging == 2) + charging = 0 //if we do not do this here, the APC can get stuck thinking it is fully charged. + update() + return amount + +//SMES// +/obj/machinery/power/smes/can_consume_power_from() + if(machine_stat & BROKEN) + return span_info("SMES is damaged, cannot consume power.") + if(!output_attempt) + return span_info("SMES is not outputting power, cannot consume power.") + if(charge < MIN_DRAINABLE_POWER) + return span_info("SMES cells depleted, cannot consume power.") + return TRUE + +/obj/machinery/power/smes/consume_power_from(amount) + if((charge - amount) < MIN_DRAINABLE_POWER) + amount = max(charge - MIN_DRAINABLE_POWER, 0) + charge -= amount + return amount + +//MECH// +/obj/vehicle/sealed/mecha/can_consume_power_from() + if(!cell) + return span_info("Mech power cell absent, cannot consume power.") + if(cell.charge < MIN_DRAINABLE_POWER) + return span_info("Mech power cell depleted, cannot consume power.") + return TRUE + +/obj/vehicle/sealed/mecha/consume_power_from(amount) + to_chat(occupants,span_danger("Warning: Unauthorized access through sub-route 4, block H, detected.")) + if((cell.charge - amount) < MIN_DRAINABLE_POWER) + amount = max(cell.charge - MIN_DRAINABLE_POWER, 0) + cell.use(amount) + return amount + +//BORG// +/mob/living/silicon/robot/can_consume_power_from() + if(!cell) + return span_info("Cyborg power cell absent, cannot consume power.") + if(cell.charge < MIN_DRAINABLE_POWER) + return span_info("Cyborg power cell depleted, cannot consume power.") + return TRUE + +/mob/living/silicon/robot/consume_power_from(amount) + to_chat(src,span_danger("Warning: Unauthorized access through sub-route 12, block C, detected.")) + if((cell.charge - amount) < MIN_DRAINABLE_POWER) + amount = max(cell.charge - MIN_DRAINABLE_POWER, 0) + cell.use(amount) + return amount + +#undef MIN_DRAINABLE_POWER + diff --git a/code/modules/mob/living/carbon/human/species_types/preternis/preternis.dm b/code/modules/mob/living/carbon/human/species_types/preternis/preternis.dm new file mode 100644 index 00000000000..7a5434a7198 --- /dev/null +++ b/code/modules/mob/living/carbon/human/species_types/preternis/preternis.dm @@ -0,0 +1,171 @@ +/* +procs: +handle_charge - called in spec_life(),handles the alert indicators,the power loss death and decreasing the charge level +adjust_charge - take a positive or negative value to adjust the charge level +*/ + +/datum/species/preternis + name = "Preternis" + id = "preternis" + default_color = "FFFFFF" + changesource_flags = MIRROR_BADMIN | WABBAJACK | MIRROR_PRIDE | MIRROR_MAGIC | RACE_SWAP | ERT_SPAWN | SLIME_EXTRACT + inherent_traits = list(TRAIT_NOHUNGER,TRAIT_RADIMMUNE,TRAIT_MEDICALIGNORE, TRAIT_ADVANCEDTOOLUSER, TRAIT_CAN_STRIP) + species_traits = list(EYECOLOR,HAIR,LIPS) + say_mod = "intones" + attack_verb = "assault" + meat = /obj/item/food/meat/slab/synthmeat + toxic_food = NONE + liked_food = FRIED | SUGAR | JUNKFOOD | FRUIT + disliked_food = GROSS | VEGETABLES + brutemod = 1.25 + burnmod = 1.5 + draw_robot_hair = TRUE + mutanteyes = /obj/item/organ/eyes/robotic/preternis + mutantlungs = /obj/item/organ/lungs/preternis + virus_infect_chance = 20 + virus_resistance_boost = 10 //YEOUTCH,good luck getting it out + species_language_holder = /datum/language_holder/preternis + + var/charge = PRETERNIS_LEVEL_FULL + var/eating_msg_cooldown = FALSE + var/emag_lvl = 0 + var/power_drain = 0.5 //probably going to have to tweak this shit + var/tesliumtrip = FALSE + var/draining = FALSE + + +/datum/species/preternis/on_species_gain(mob/living/carbon/C, datum/species/old_species, pref_load) + . = ..() + RegisterSignal(C, COMSIG_ATOM_EMAG_ACT, .proc/on_emag_act) + RegisterSignal(C, COMSIG_ATOM_EMP_ACT, .proc/on_emp_act) + for (var/V in C.bodyparts) + var/obj/item/bodypart/BP = V + BP.change_bodypart_status(ORGAN_ROBOTIC,FALSE,TRUE) + BP.burn_reduction = 0 + BP.brute_reduction = 0 + if(istype(BP,/obj/item/bodypart/chest) || istype(BP,/obj/item/bodypart/head)) + continue + BP.max_damage = 35 + C.grant_language(/datum/language/machine) + +/datum/species/preternis/on_species_loss(mob/living/carbon/human/C, datum/species/new_species, pref_load) + . = ..() + UnregisterSignal(C, COMSIG_ATOM_EMAG_ACT) + UnregisterSignal(C, COMSIG_ATOM_EMP_ACT) + for (var/V in C.bodyparts) + var/obj/item/bodypart/BP = V + BP.change_bodypart_status(ORGAN_ORGANIC,FALSE,TRUE) + BP.burn_reduction = initial(BP.burn_reduction) + BP.brute_reduction = initial(BP.brute_reduction) + C.clear_alert("preternis_emag") //this means a changeling can transform from and back to a preternis to clear the emag status but w/e i cant find a solution to not do that + C.clear_fullscreen("preternis_emag") + C.remove_movespeed_modifier(/datum/movespeed_modifier/reagent/preternis_teslium, TRUE) + if(C.job != "Curator")// if not curator, removes the encoded audio language. + C.remove_language(/datum/language/machine) + +/datum/species/preternis/proc/on_emp_act(mob/living/carbon/human/H, severity) + SIGNAL_HANDLER + switch(severity) + if(EMP_HEAVY) + H.adjustBruteLoss(20) + H.adjustFireLoss(20) + H.Paralyze(50) + charge *= 0.4 + H.visible_message(span_danger("Electricity ripples over [H]'s subdermal implants, smoking profusely."), \ + span_userdanger(">A surge of searing pain erupts throughout your very being! As the pain subsides, a terrible sensation of emptiness is left in its wake.")) + if(EMP_LIGHT) + H.adjustBruteLoss(10) + H.adjustFireLoss(10) + H.Paralyze(20) + charge *= 0.6 + H.visible_message(span_danger("A faint fizzling emanates from [H]."), \ + span_userdanger("A fit of twitching overtakes you as your subdermal implants convulse violently from the electromagnetic disruption. Your sustenance reserves have been partially depleted from the blast.")) + +/datum/species/preternis/proc/on_emag_act(mob/living/carbon/human/H, mob/user) + SIGNAL_HANDLER + if(emag_lvl == 2) + return + emag_lvl = min(emag_lvl + 1,2) + playsound(H.loc, 'sound/machines/warning-buzzer.ogg', 50, 1, 1) + H.Paralyze(60) + switch(emag_lvl) + if(1) + H.adjustOrganLoss(ORGAN_SLOT_BRAIN, 50) //HALP AM DUMB + to_chat(H,span_danger("ALERT! MEMORY UNIT [rand(1,5)] FAILURE.NERVEOUS SYSTEM DAMAGE.")) + if(2) + H.overlay_fullscreen("preternis_emag", /atom/movable/screen/fullscreen/high) + H.throw_alert("preternis_emag", /atom/movable/screen/alert/high/preternis) + to_chat(H,span_danger("ALERT! OPTIC SENSORS FAILURE.VISION PROCESSOR COMPROMISED.")) + +/datum/species/preternis/handle_chemicals(datum/reagent/chem, mob/living/carbon/human/H) + . = ..() + + if(H.reagents.has_reagent(/datum/reagent/fuel/oil)) + H.adjustFireLoss(-2*REAGENTS_EFFECT_MULTIPLIER,FALSE,FALSE, BODYPART_ANY) + + if(H.reagents.has_reagent(/datum/reagent/fuel)) + H.adjustFireLoss(-1*REAGENTS_EFFECT_MULTIPLIER,FALSE,FALSE, BODYPART_ANY) + + if(H.reagents.has_reagent(/datum/reagent/teslium,10)) //10 u otherwise it wont update and they will remain quikk + H.add_movespeed_modifier(/datum/movespeed_modifier/reagent/preternis_teslium, TRUE) + if(H.health < 50 && H.health > 0) + H.adjustOxyLoss(-1*REAGENTS_EFFECT_MULTIPLIER) + H.adjustBruteLoss(-1*REAGENTS_EFFECT_MULTIPLIER,FALSE,FALSE, BODYPART_ANY) + H.adjustFireLoss(-1*REAGENTS_EFFECT_MULTIPLIER,FALSE,FALSE, BODYPART_ANY) + H.AdjustParalyzed(-3) + H.AdjustStun(-3) + H.AdjustKnockdown(-3) + H.adjustStaminaLoss(-5*REAGENTS_EFFECT_MULTIPLIER) + charge = clamp(charge - 10 * REAGENTS_METABOLISM,PRETERNIS_LEVEL_NONE,PRETERNIS_LEVEL_FULL) + burnmod = 200 + tesliumtrip = TRUE + else if(tesliumtrip) + burnmod = initial(burnmod) + tesliumtrip = FALSE + H.remove_movespeed_modifier(/datum/movespeed_modifier/reagent/preternis_teslium, TRUE) + + if (istype(chem,/datum/reagent/consumable)) + var/datum/reagent/consumable/food = chem + if (food.nutriment_factor) + var/nutrition = food.nutriment_factor * 0.2 + charge = clamp(charge + nutrition,PRETERNIS_LEVEL_NONE,PRETERNIS_LEVEL_FULL) + if (!eating_msg_cooldown) + eating_msg_cooldown = TRUE + addtimer(VARSET_CALLBACK(src, eating_msg_cooldown, FALSE), 2 MINUTES) + to_chat(H,span_info("NOTICE: Digestive subroutines are inefficient. Seek sustenance via power-cell C.O.N.S.U.M.E. technology induction.")) + + if(chem.current_cycle >= 20) + H.reagents.del_reagent(chem.type) + + + return FALSE + +/datum/species/preternis/spec_fully_heal(mob/living/carbon/human/H) + . = ..() + charge = PRETERNIS_LEVEL_FULL + emag_lvl = 0 + H.clear_alert("preternis_emag") + H.clear_fullscreen("preternis_emag") + burnmod = initial(burnmod) + tesliumtrip = FALSE + H.remove_movespeed_modifier("preternis_teslium") //full heal removes chems so it wont update the teslium speed up until they eat something + +/datum/species/preternis/spec_life(mob/living/carbon/human/H) + . = ..() + if(H.stat == DEAD) + return + handle_charge(H) + +/datum/species/preternis/proc/handle_charge(mob/living/carbon/human/H) + charge = clamp(charge - power_drain,PRETERNIS_LEVEL_NONE,PRETERNIS_LEVEL_FULL) + if(charge == PRETERNIS_LEVEL_NONE) + to_chat(H,span_danger("Warning! System power criti-$#@$")) + H.death() + else if(charge < PRETERNIS_LEVEL_STARVING) + H.throw_alert("preternis_charge", /atom/movable/screen/alert/preternis_charge, 3) + else if(charge < PRETERNIS_LEVEL_HUNGRY) + H.throw_alert("preternis_charge", /atom/movable/screen/alert/preternis_charge, 2) + else if(charge < PRETERNIS_LEVEL_FED) + H.throw_alert("preternis_charge", /atom/movable/screen/alert/preternis_charge, 1) + else + H.clear_alert("preternis_charge") diff --git a/code/modules/mob/living/carbon/human/species_types/preternis/screen_alerts.dm b/code/modules/mob/living/carbon/human/species_types/preternis/screen_alerts.dm new file mode 100644 index 00000000000..5a4435fe5ff --- /dev/null +++ b/code/modules/mob/living/carbon/human/species_types/preternis/screen_alerts.dm @@ -0,0 +1,8 @@ +/atom/movable/screen/alert/high/preternis + name = "Optic sensor failure" + desc = "Main unit optic sensors damage detected.Vision processor compromised" + +/atom/movable/screen/alert/preternis_charge + name = "Low power" + desc = "Find a power cell to recharge from or you may lose power." + icon_state = "low_cell" diff --git a/code/modules/mob/living/simple_animal/bot/medbot.dm b/code/modules/mob/living/simple_animal/bot/medbot.dm index 83ef5d45be0..8a6831f8584 100644 --- a/code/modules/mob/living/simple_animal/bot/medbot.dm +++ b/code/modules/mob/living/simple_animal/bot/medbot.dm @@ -445,6 +445,9 @@ if(emagged == 2) //Everyone needs our medicine. (Our medicine is toxins) return TRUE + if(HAS_TRAIT(C,TRAIT_MEDICALIGNORE)) + return FALSE + if(HAS_TRAIT(C,TRAIT_MEDIBOTCOMINGTHROUGH) && !HAS_TRAIT_FROM(C,TRAIT_MEDIBOTCOMINGTHROUGH,tag)) //the early medbot gets the worm (or in this case the patient) return FALSE diff --git a/code/modules/movespeed/modifiers/reagent.dm b/code/modules/movespeed/modifiers/reagent.dm index ac2f3cdd4c9..b2a1f9d6016 100644 --- a/code/modules/movespeed/modifiers/reagent.dm +++ b/code/modules/movespeed/modifiers/reagent.dm @@ -39,3 +39,7 @@ /datum/movespeed_modifier/reagent/nooartrium multiplicative_slowdown = 2 + +/datum/movespeed_modifier/reagent/preternis_teslium + multiplicative_slowdown = -2 + priority = 101 diff --git a/code/modules/surgery/amputation.dm b/code/modules/surgery/amputation.dm index 8c9ad4f9cd1..bf05e72ff92 100644 --- a/code/modules/surgery/amputation.dm +++ b/code/modules/surgery/amputation.dm @@ -40,3 +40,42 @@ var/obj/item/bodypart/target_limb = surgery.operated_bodypart target_limb.drop_limb() return ..() + +/datum/surgery/amputation/mechanic + name = "Detach mechanical limb" + self_operable = TRUE + requires_bodypart_type = BODYPART_ROBOTIC + possible_locs = list(BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) + target_mobtypes = list(/mob/living/carbon/human) + lying_required = FALSE + steps = list( + /datum/surgery_step/mechanic_open, + /datum/surgery_step/open_hatch, + /datum/surgery_step/mechanic_unwrench, + /datum/surgery_step/prepare_electronics, + /datum/surgery_step/sever_limb/mechanic + ) + +/datum/surgery/amputation/mechanic/can_start(mob/user, mob/living/carbon/target) + return ispreternis(target) + +/datum/surgery_step/sever_limb/mechanic + name = "uninstall limb" + preop_sound = 'sound/items/ratchet.ogg' + accept_hand = TRUE + +/datum/surgery_step/sever_limb/mechanic/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) + display_results(user, target, span_notice("You begin to detach [target]'s [parse_zone(target_zone)]..."), + "[user] begins to detach [target]'s [parse_zone(target_zone)]!", + "[user] begins to detach [target]'s [parse_zone(target_zone)]!") + +/datum/surgery_step/sever_limb/mechanic/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) + var/mob/living/carbon/human/L = target + display_results(user, target, span_notice("You detached [L]'s [parse_zone(target_zone)]."), + "[user] detached [L]'s [parse_zone(target_zone)]!", + "[user] detached [L]'s [parse_zone(target_zone)]!") + if(surgery.operated_bodypart) + var/obj/item/bodypart/target_limb = surgery.operated_bodypart + target_limb.drop_limb() + + return 1 diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm index ef1f5b21a8d..cf435ad9242 100644 --- a/code/modules/surgery/bodyparts/_bodyparts.dm +++ b/code/modules/surgery/bodyparts/_bodyparts.dm @@ -100,6 +100,7 @@ var/last_maxed /// How much generic bleedstacks we have on this bodypart var/generic_bleedstacks + var/draw_robot_hair = FALSE /// If we have a gauze wrapping currently applied (not including splints) var/obj/item/stack/current_gauze /// If something is currently grasping this bodypart and trying to staunch bleeding (see [/obj/item/self_grasp]) @@ -228,7 +229,7 @@ if(owner && (owner.status_flags & GODMODE)) return FALSE //godmode - if(required_status && (status != required_status)) + if(!(required_status == -1) && (required_status && (status != required_status))) return FALSE var/dmg_multi = CONFIG_GET(number/damage_multiplier) * hit_percent @@ -505,7 +506,7 @@ //Cannot remove negative damage (i.e. apply damage) /obj/item/bodypart/proc/heal_damage(brute, burn, stamina, required_status, updating_health = TRUE) - if(required_status && status != required_status) //So we can only heal certain kinds of limbs, ie robotic vs organic. + if(!(required_status == BODYPART_ANY) && (required_status && (status != required_status)) ) //So we can only heal certain kinds of limbs, ie robotic vs organic. return if(brute) diff --git a/code/modules/surgery/bodyparts/head.dm b/code/modules/surgery/bodyparts/head.dm index 71d1ed59783..c839c540a3a 100644 --- a/code/modules/surgery/bodyparts/head.dm +++ b/code/modules/surgery/bodyparts/head.dm @@ -103,7 +103,7 @@ /obj/item/bodypart/head/drop_organs(mob/user, violent_removal) var/turf/head_turf = get_turf(src) - if(status != BODYPART_ROBOTIC) + if(status != BODYPART_ROBOTIC || draw_robot_hair) playsound(head_turf, 'sound/misc/splort.ogg', 50, TRUE, -1) for(var/obj/item/head_item in src) if(head_item == brain) diff --git a/code/modules/surgery/cavity_implant.dm b/code/modules/surgery/cavity_implant.dm index 14d1adb156e..0d1a02ef5e9 100644 --- a/code/modules/surgery/cavity_implant.dm +++ b/code/modules/surgery/cavity_implant.dm @@ -63,3 +63,11 @@ else to_chat(user, span_warning("You don't find anything in [target]'s [target_zone].")) return FALSE + +/datum/surgery/cavity_implant/mechanical + name = "Prosthesis cavity implant" + steps = list(/datum/surgery_step/mechanic_open, /datum/surgery_step/open_hatch, /datum/surgery_step/prepare_electronics, /datum/surgery_step/handle_cavity, /datum/surgery_step/mechanic_close) + possible_locs = list(BODY_ZONE_CHEST) + requires_bodypart_type = BODYPART_ROBOTIC + lying_required = FALSE + self_operable = TRUE diff --git a/code/modules/surgery/implant_removal.dm b/code/modules/surgery/implant_removal.dm index 1a29b1ce5d6..8498048d37b 100644 --- a/code/modules/surgery/implant_removal.dm +++ b/code/modules/surgery/implant_removal.dm @@ -64,6 +64,8 @@ /datum/surgery/implant_removal/mechanic name = "implant removal" requires_bodypart_type = BODYPART_ROBOTIC + lying_required = FALSE + self_operable = TRUE steps = list( /datum/surgery_step/mechanic_open, /datum/surgery_step/open_hatch, diff --git a/code/modules/surgery/organs/tongue.dm b/code/modules/surgery/organs/tongue.dm index 46d857dc550..52c9c0398d6 100644 --- a/code/modules/surgery/organs/tongue.dm +++ b/code/modules/surgery/organs/tongue.dm @@ -28,7 +28,8 @@ /datum/language/sylvan, /datum/language/shadowtongue, /datum/language/terrum, - /datum/language/nekomimetic + /datum/language/nekomimetic, + /datum/language/machine )) /obj/item/organ/tongue/Initialize(mapload) diff --git a/code/modules/surgery/prosthetic_replacement.dm b/code/modules/surgery/prosthetic_replacement.dm index 8232e34ebe1..b5c6b66d6ba 100644 --- a/code/modules/surgery/prosthetic_replacement.dm +++ b/code/modules/surgery/prosthetic_replacement.dm @@ -18,7 +18,23 @@ return TRUE return FALSE +/datum/surgery/prosthetic_replacement/mechanic_limb + name = "Attach mechanical limb" + self_operable = TRUE + possible_locs = list(BODY_ZONE_R_ARM, BODY_ZONE_L_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG) + target_mobtypes = list(/mob/living/carbon/human) + lying_required = FALSE + steps = list( + /datum/surgery_step/prepare_electronics, + /datum/surgery_step/add_prosthetic + ) + +/datum/surgery/prosthetic_replacement/mechanic_limb/can_start(mob/user, mob/living/carbon/target) + if(!ispreternis(target)) + return FALSE + var/mob/living/carbon/C = target + return !C.get_bodypart(user.zone_selected) //can only start if limb is missing /datum/surgery_step/add_prosthetic name = "add prosthetic" diff --git a/config/game_options.txt b/config/game_options.txt index 5c6cdc06628..8e3b34f4833 100644 --- a/config/game_options.txt +++ b/config/game_options.txt @@ -420,6 +420,7 @@ ROUNDSTART_RACES lizard ROUNDSTART_RACES moth ROUNDSTART_RACES plasmaman #ROUNDSTART_RACES shadow +ROUNDSTART_RACES preternis ## Races that are better than humans in some ways, but worse in others ROUNDSTART_RACES ethereal diff --git a/goon/sound/robot_scream.ogg b/goon/sound/robot_scream.ogg new file mode 100644 index 00000000000..5db056a7713 Binary files /dev/null and b/goon/sound/robot_scream.ogg differ diff --git a/sound/machines/cryo_warning_ignore.ogg b/sound/machines/cryo_warning_ignore.ogg new file mode 100644 index 00000000000..6b95567cb54 Binary files /dev/null and b/sound/machines/cryo_warning_ignore.ogg differ diff --git a/tgstation.dme b/tgstation.dme index c49cee2d826..c111a0cff2b 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -2759,6 +2759,10 @@ #include "code\modules\mob\living\carbon\human\species_types\humans.dm" #include "code\modules\mob\living\carbon\human\species_types\jellypeople.dm" #include "code\modules\mob\living\carbon\human\species_types\lizardpeople.dm" +#include "code\modules\mob\living\carbon\human\species_types\preternis\organs.dm" +#include "code\modules\mob\living\carbon\human\species_types\preternis\power_suck.dm" +#include "code\modules\mob\living\carbon\human\species_types\preternis\preternis.dm" +#include "code\modules\mob\living\carbon\human\species_types\preternis\screen_alerts.dm" #include "code\modules\mob\living\carbon\human\species_types\monkeys.dm" #include "code\modules\mob\living\carbon\human\species_types\mothmen.dm" #include "code\modules\mob\living\carbon\human\species_types\mushpeople.dm"