diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm
index f006caf1c1..6900f36a33 100644
--- a/code/__DEFINES/dcs/signals.dm
+++ b/code/__DEFINES/dcs/signals.dm
@@ -209,6 +209,8 @@
#define COMSIG_MOB_SWAP_HANDS "mob_swap_hands" //from base of mob/swap_hand(): (obj/item)
#define COMPONENT_BLOCK_SWAP 1
+#define COMSIG_PROCESS_BORGCHARGER_OCCUPANT "living_charge"
+
// /client signals
#define COMSIG_MOB_CLIENT_LOGIN "mob_client_login" //sent when a mob/login() finishes: (client)
#define COMSIG_MOB_CLIENT_LOGOUT "mob_client_logout" //sent when a mob/logout() starts: (client)
diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm
index 3aa5a07480..d66521d945 100644
--- a/code/__DEFINES/is_helpers.dm
+++ b/code/__DEFINES/is_helpers.dm
@@ -74,6 +74,7 @@ GLOBAL_LIST_INIT(turfs_without_ground, typecacheof(list(
#define ismush(A) (is_species(A, /datum/species/mush))
#define isshadow(A) (is_species(A, /datum/species/shadow))
#define isskeleton(A) (is_species(A, /datum/species/skeleton))
+#define isethereal(A) (is_species(A, /datum/species/ethereal))
// Citadel specific species
#define isipcperson(A) (is_species(A, /datum/species/ipc))
diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm
index 52657e9104..782095f883 100644
--- a/code/__DEFINES/mobs.dm
+++ b/code/__DEFINES/mobs.dm
@@ -176,6 +176,15 @@
#define DISGUST_LEVEL_VERYGROSS 50
#define DISGUST_LEVEL_GROSS 25
+//Charge levels for Ethereals
+#define ETHEREAL_CHARGE_NONE 0
+#define ETHEREAL_CHARGE_LOWPOWER 20
+#define ETHEREAL_CHARGE_NORMAL 50
+#define ETHEREAL_CHARGE_ALMOSTFULL 75
+#define ETHEREAL_CHARGE_FULL 100
+#define ETHEREAL_CHARGE_OVERLOAD 125
+#define ETHEREAL_CHARGE_DANGEROUS 150
+
//Slime evolution threshold. Controls how fast slimes can split/grow
#define SLIME_EVOLUTION_THRESHOLD 10
@@ -284,6 +293,7 @@
#define DOOR_CRUSH_DAMAGE 15 //the amount of damage that airlocks deal when they crush you
#define HUNGER_FACTOR 0.1 //factor at which mob nutrition decreases
+#define ETHEREAL_CHARGE_FACTOR 0.08 //factor at which ethereal's charge decreases
#define REAGENTS_METABOLISM 0.4 //How many units of reagent are consumed per tick, by default.
#define REAGENTS_EFFECT_MULTIPLIER (REAGENTS_METABOLISM / 0.4) // By defining the effect multiplier this way, it'll exactly adjust all effects according to how they originally were with the 0.4 metabolism
diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm
index ca12567b59..c4964022e5 100644
--- a/code/__HELPERS/mobs.dm
+++ b/code/__HELPERS/mobs.dm
@@ -264,6 +264,13 @@
if(!findname(.))
break
+/proc/random_unique_ethereal_name(attempts_to_find_unique_name=10)
+ for(var/i in 1 to attempts_to_find_unique_name)
+ . = capitalize(ethereal_name())
+
+ if(!findname(.))
+ break
+
/proc/random_unique_moth_name(attempts_to_find_unique_name=10)
for(var/i in 1 to attempts_to_find_unique_name)
. = capitalize(pick(GLOB.moth_first)) + " " + capitalize(pick(GLOB.moth_last))
diff --git a/code/__HELPERS/names.dm b/code/__HELPERS/names.dm
index 8b699e3eee..e1848b21a2 100644
--- a/code/__HELPERS/names.dm
+++ b/code/__HELPERS/names.dm
@@ -4,6 +4,12 @@
else
return "[pick(GLOB.lizard_names_female)]-[pick(GLOB.lizard_names_female)]"
+/proc/ethereal_name()
+ var/tempname = "[pick(GLOB.ethereal_names)] [random_capital_letter()]"
+ if(prob(65))
+ tempname += random_capital_letter()
+ return tempname
+
/proc/plasmaman_name()
return "[pick(GLOB.plasmaman_names)] \Roman[rand(1,99)]"
diff --git a/code/__HELPERS/text.dm b/code/__HELPERS/text.dm
index 6dfb685331..7c251edd88 100644
--- a/code/__HELPERS/text.dm
+++ b/code/__HELPERS/text.dm
@@ -776,6 +776,10 @@ GLOBAL_LIST_INIT(binary, list("0","1"))
else
return "[number]\th"
+
+/proc/random_capital_letter()
+ return uppertext(pick(GLOB.alphabet))
+
/proc/unintelligize(message)
var/regex/word_boundaries = regex(@"\b[\S]+\b", "g")
var/prefix = message[1]
diff --git a/code/_globalvars/lists/names.dm b/code/_globalvars/lists/names.dm
index e334d08040..b80fcf0bbf 100644
--- a/code/_globalvars/lists/names.dm
+++ b/code/_globalvars/lists/names.dm
@@ -17,6 +17,7 @@ GLOBAL_LIST_INIT(golem_names, world.file2list("strings/names/golem.txt"))
GLOBAL_LIST_INIT(moth_first, world.file2list("strings/names/moth_first.txt"))
GLOBAL_LIST_INIT(moth_last, world.file2list("strings/names/moth_last.txt"))
GLOBAL_LIST_INIT(plasmaman_names, world.file2list("strings/names/plasmaman.txt"))
+GLOBAL_LIST_INIT(ethereal_names, world.file2list("strings/names/ethereal.txt"))
GLOBAL_LIST_INIT(posibrain_names, world.file2list("strings/names/posibrain.txt"))
GLOBAL_LIST_INIT(nightmare_names, world.file2list("strings/names/nightmare.txt"))
GLOBAL_LIST_INIT(megacarp_first_names, world.file2list("strings/names/megacarp1.txt"))
diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm
index 8111680c41..6dc1433bc8 100644
--- a/code/_onclick/hud/alert.dm
+++ b/code/_onclick/hud/alert.dm
@@ -493,6 +493,16 @@ Recharging stations are available in robotics, the dormitory bathrooms, and the
desc = "Unit's power cell is running low. Recharging stations are available in robotics, the dormitory bathrooms, and the AI satellite."
icon_state = "lowcell"
+/obj/screen/alert/etherealcharge
+ name = "Low Blood Charge"
+ desc = "Your blood's electric charge is running low, find a source of charge for your blood. Use a recharging station found in robotics or the dormitory bathrooms, or eat some Ethereal-friendly food."
+ icon_state = "etherealcharge"
+
+/obj/screen/alert/ethereal_overcharge
+ name = "Blood Overcharge"
+ desc = "Your blood's electric charge is becoming dangerously high, find an outlet for your energy. Use Grab Intent on an APC to channel your energy into it."
+ icon_state = "ethereal_overcharge"
+
//Need to cover all use cases - emag, illegal upgrade module, malf AI hack, traitor cyborg
/obj/screen/alert/hacked
name = "Hacked"
diff --git a/code/datums/components/mood.dm b/code/datums/components/mood.dm
index 90fea9ec8a..87bc681651 100644
--- a/code/datums/components/mood.dm
+++ b/code/datums/components/mood.dm
@@ -307,6 +307,10 @@
/datum/component/mood/proc/HandleNutrition(mob/living/L)
+ if(isethereal(L))
+ HandleCharge(L)
+ if(HAS_TRAIT(L, TRAIT_NOHUNGER))
+ return FALSE //no mood events for nutrition
switch(L.nutrition)
if(NUTRITION_LEVEL_FULL to INFINITY)
add_event(null, "nutrition", /datum/mood_event/fat)
@@ -321,6 +325,22 @@
if(0 to NUTRITION_LEVEL_STARVING)
add_event(null, "nutrition", /datum/mood_event/starving)
+/datum/component/mood/proc/HandleCharge(mob/living/carbon/human/H)
+ var/datum/species/ethereal/E = H.dna.species
+ switch(E.get_charge(H))
+ if(ETHEREAL_CHARGE_NONE to ETHEREAL_CHARGE_LOWPOWER)
+ add_event(null, "charge", /datum/mood_event/decharged)
+ if(ETHEREAL_CHARGE_LOWPOWER to ETHEREAL_CHARGE_NORMAL)
+ add_event(null, "charge", /datum/mood_event/lowpower)
+ if(ETHEREAL_CHARGE_NORMAL to ETHEREAL_CHARGE_ALMOSTFULL)
+ clear_event(null, "charge")
+ if(ETHEREAL_CHARGE_ALMOSTFULL to ETHEREAL_CHARGE_FULL)
+ add_event(null, "charge", /datum/mood_event/charged)
+ if(ETHEREAL_CHARGE_FULL to ETHEREAL_CHARGE_OVERLOAD)
+ add_event(null, "charge", /datum/mood_event/overcharged)
+ if(ETHEREAL_CHARGE_OVERLOAD to ETHEREAL_CHARGE_DANGEROUS)
+ add_event(null, "charge", /datum/mood_event/supercharged)
+
/datum/component/mood/proc/update_beauty(area/A)
if(A.outdoors) //if we're outside, we don't care.
clear_event(null, "area_beauty")
diff --git a/code/datums/mood_events/needs_events.dm b/code/datums/mood_events/needs_events.dm
index 962681eb94..b307bcb0aa 100644
--- a/code/datums/mood_events/needs_events.dm
+++ b/code/datums/mood_events/needs_events.dm
@@ -19,6 +19,27 @@
description = "I'm starving!\n"
mood_change = -15
+//charge
+/datum/mood_event/supercharged
+ description = "I can't possibly keep all this power inside, I need to release some quick!\n"
+ mood_change = -10
+
+/datum/mood_event/overcharged
+ description = "I feel dangerously overcharged, perhaps I should release some power.\n"
+ mood_change = -4
+
+/datum/mood_event/charged
+ description = "I feel the power in my veins!\n"
+ mood_change = 6
+
+/datum/mood_event/lowpower
+ description = "My power is running low, I should go charge up somewhere.\n"
+ mood_change = -6
+
+/datum/mood_event/decharged
+ description = "I'm in desperate need of some electricity!\n"
+ mood_change = -10
+
//Disgust
/datum/mood_event/gross
description = "I saw something gross.\n"
diff --git a/code/game/machinery/rechargestation.dm b/code/game/machinery/rechargestation.dm
index bbafe99006..0accd0994c 100644
--- a/code/game/machinery/rechargestation.dm
+++ b/code/game/machinery/rechargestation.dm
@@ -126,17 +126,6 @@
update_icon()
/obj/machinery/recharge_station/proc/process_occupant()
- if(occupant && iscyborg(occupant))
- var/mob/living/silicon/robot/R = occupant
- restock_modules()
- if(repairs)
- R.heal_bodypart_damage(repairs, repairs - 1)
- if(R.cell)
- R.cell.charge = min(R.cell.charge + recharge_speed, R.cell.maxcharge)
-
-/obj/machinery/recharge_station/proc/restock_modules()
- if(occupant)
- var/mob/living/silicon/robot/R = occupant
- if(R && R.module)
- var/coeff = recharge_speed * 0.005
- R.module.respawn_consumable(R, coeff)
+ if(!occupant)
+ return
+ SEND_SIGNAL(occupant, COMSIG_PROCESS_BORGCHARGER_OCCUPANT, recharge_speed, repairs)
diff --git a/code/game/objects/items/trash.dm b/code/game/objects/items/trash.dm
index 4c6bcc08b4..2109d1038e 100644
--- a/code/game/objects/items/trash.dm
+++ b/code/game/objects/items/trash.dm
@@ -35,6 +35,10 @@
name = "syndi-cakes"
icon_state = "syndi_cakes"
+/obj/item/trash/energybar
+ name = "energybar wrapper"
+ icon_state = "energybar"
+
/obj/item/trash/waffles
name = "waffles tray"
icon_state = "waffles"
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index 41c3fe5105..13e8877440 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -2184,8 +2184,18 @@
if(!ishuman(H))
to_chat(usr, "This can only be used on instances of type /mob/living/carbon/human.")
return
-
- var/obj/item/reagent_containers/food/snacks/cookie/cookie = new(H)
+ //let's keep it simple
+ //milk to plasmemes and skeletons, meat to lizards, electricity bars to ethereals, cookies to everyone else
+ var/cookiealt = /obj/item/reagent_containers/food/snacks/cookie
+ if(isskeleton(H))
+ cookiealt = /obj/item/reagent_containers/food/condiment/milk
+ else if(isplasmaman(H))
+ cookiealt = /obj/item/reagent_containers/food/condiment/milk
+ else if(isethereal(H))
+ cookiealt = /obj/item/reagent_containers/food/snacks/energybar
+ else if(islizard(H))
+ cookiealt = /obj/item/reagent_containers/food/snacks/meat/slab
+ var/obj/item/cookie = new cookiealt(H)
if(H.put_in_hands(cookie))
H.update_inv_hands()
else
diff --git a/code/modules/food_and_drinks/food/snacks/meat.dm b/code/modules/food_and_drinks/food/snacks/meat.dm
index fe70739ab5..4d287fdb86 100644
--- a/code/modules/food_and_drinks/food/snacks/meat.dm
+++ b/code/modules/food_and_drinks/food/snacks/meat.dm
@@ -162,6 +162,14 @@
tastes = list("brains" = 1, "meat" = 1)
foodtype = RAW | MEAT | TOXIC
+/obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/ethereal
+ icon_state = "etherealmeat"
+ desc = "So shiny you feel like ingesting it might make you shine too"
+ filling_color = "#97ee63"
+ list_reagents = list(/datum/reagent/consumable/liquidelectricity = 3)
+ tastes = list("pure electricity" = 2, "meat" = 1)
+ foodtype = RAW | MEAT | TOXIC
+
/obj/item/reagent_containers/food/snacks/carpmeat/aquatic
name = "fillet"
desc = "A fillet of one of the local water dwelling species."
diff --git a/code/modules/food_and_drinks/food/snacks_vend.dm b/code/modules/food_and_drinks/food/snacks_vend.dm
index 38f7ecf5b1..b4c7c89b74 100644
--- a/code/modules/food_and_drinks/food/snacks_vend.dm
+++ b/code/modules/food_and_drinks/food/snacks_vend.dm
@@ -91,3 +91,13 @@
tastes = list("sweetness" = 3, "cake" = 1)
foodtype = GRAIN | FRUIT | VEGETABLES
custom_price = PRICE_CHEAP
+
+/obj/item/reagent_containers/food/snacks/energybar
+ name = "High-power energy bars"
+ icon_state = "energybar"
+ desc = "An energy bar with a lot of punch, you probably shouldn't eat this if you're not an Ethereal."
+ trash = /obj/item/trash/energybar
+ list_reagents = list(/datum/reagent/consumable/nutriment = 3, /datum/reagent/consumable/liquidelectricity = 3)
+ filling_color = "#97ee63"
+ tastes = list("pure electricity" = 3, "fitness" = 2)
+ foodtype = TOXIC
diff --git a/code/modules/language/language_holder.dm b/code/modules/language/language_holder.dm
index 55e9352843..c1677117e9 100644
--- a/code/modules/language/language_holder.dm
+++ b/code/modules/language/language_holder.dm
@@ -324,6 +324,12 @@ Key procs
/datum/language/sylvan = list(LANGUAGE_ATOM))
spoken_languages = list(/datum/language/sylvan = list(LANGUAGE_ATOM))
+/datum/language_holder/ethereal
+ understood_languages = list(/datum/language/common = list(LANGUAGE_ATOM),
+ /datum/language/voltaic = list(LANGUAGE_ATOM))
+ spoken_languages = list(/datum/language/common = list(LANGUAGE_ATOM),
+ /datum/language/voltaic = list(LANGUAGE_ATOM))
+
/datum/language_holder/empty
understood_languages = list()
spoken_languages = list()
diff --git a/code/modules/language/voltaic.dm b/code/modules/language/voltaic.dm
new file mode 100644
index 0000000000..ead7fe7c7f
--- /dev/null
+++ b/code/modules/language/voltaic.dm
@@ -0,0 +1,14 @@
+// One of these languages will actually work, I'm certain of it.
+/datum/language/voltaic
+ name = "Voltaic"
+ desc = "A sparky language made by manipulating electrical discharge."
+ key = "v"
+ space_chance = 20
+ syllables = list(
+ "bzzt", "skrrt", "zzp", "mmm", "hzz", "tk", "shz", "k", "z",
+ "bzt", "zzt", "skzt", "skzz", "hmmt", "zrrt", "hzzt", "hz",
+ "vzt", "zt", "vz", "zip", "tzp", "lzzt", "dzzt", "zdt", "kzt",
+ "zzzz", "mzz"
+ )
+ icon_state = "volt"
+ default_priority = 90
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index b402260611..38b420aaba 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -1042,7 +1042,7 @@
/mob/living/carbon/human/updatehealth()
. = ..()
-
+ dna?.species.spec_updatehealth(src)
if(HAS_TRAIT(src, TRAIT_IGNORESLOWDOWN)) //if we want to ignore slowdown from damage and equipment
remove_movespeed_modifier(/datum/movespeed_modifier/damage_slowdown)
remove_movespeed_modifier(/datum/movespeed_modifier/damage_slowdown_flying)
@@ -1199,6 +1199,9 @@
/mob/living/carbon/human/species/lizard
race = /datum/species/lizard
+/mob/living/carbon/human/species/ethereal
+ race = /datum/species/ethereal
+
/mob/living/carbon/human/species/lizard/ashwalker
race = /datum/species/lizard/ashwalker
diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm
index dc7fe86aca..54c3b314b9 100644
--- a/code/modules/mob/living/carbon/human/species.dm
+++ b/code/modules/mob/living/carbon/human/species.dm
@@ -56,6 +56,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
var/list/mutant_organs = list() //Internal organs that are unique to this race.
var/speedmod = 0 // this affects the race's speed. positive numbers make it move slower, negative numbers make it move faster
var/armor = 0 // overall defense for the race... or less defense, if it's negative.
+ var/attack_type = BRUTE // the type of damage unarmed attacks from this species do
var/brutemod = 1 // multiplier for brute damage
var/burnmod = 1 // multiplier for burn damage
var/coldmod = 1 // multiplier for cold damage
@@ -1364,6 +1365,10 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
var/hungry = (500 - H.nutrition) / 5 //So overeat would be 100 and default level would be 80
if(hungry >= 70)
H.add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/hunger, multiplicative_slowdown = (hungry / 50))
+ else if(isethereal(H))
+ var/datum/species/ethereal/E = H.dna.species
+ if(E.get_charge(H) <= ETHEREAL_CHARGE_NORMAL)
+ H.add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/hunger, multiplicative_slowdown = (1.5 * (1 - E.get_charge(H) / 100)))
else
H.remove_movespeed_modifier(/datum/movespeed_modifier/hunger)
@@ -1420,6 +1425,12 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
// ATTACK PROCS //
//////////////////
+/datum/species/proc/spec_updatehealth(mob/living/carbon/human/H)
+ return
+
+/datum/species/proc/spec_fully_heal(mob/living/carbon/human/H)
+ return
+
/datum/species/proc/help(mob/living/carbon/human/user, mob/living/carbon/human/target, datum/martial_art/attacker_style)
if(target.health >= 0 && !HAS_TRAIT(target, TRAIT_FAKEDEATH))
target.help_shake_act(user)
@@ -1538,11 +1549,11 @@ GLOBAL_LIST_EMPTY(roundstart_race_names)
target.dismembering_strike(user, affecting.body_zone)
if(atk_verb == ATTACK_EFFECT_KICK)//kicks deal 1.5x raw damage + 0.5x stamina damage
- target.apply_damage(damage*1.5, BRUTE, affecting, armor_block)
+ target.apply_damage(damage*1.5, attack_type, affecting, armor_block)
target.apply_damage(damage*0.5, STAMINA, affecting, armor_block)
log_combat(user, target, "kicked")
else//other attacks deal full raw damage + 2x in stamina damage
- target.apply_damage(damage, BRUTE, affecting, armor_block)
+ target.apply_damage(damage, attack_type, affecting, armor_block)
target.apply_damage(damage*2, STAMINA, affecting, armor_block)
log_combat(user, target, "punched")
diff --git a/code/modules/mob/living/carbon/human/species_types/ethereal.dm b/code/modules/mob/living/carbon/human/species_types/ethereal.dm
new file mode 100644
index 0000000000..27338f598f
--- /dev/null
+++ b/code/modules/mob/living/carbon/human/species_types/ethereal.dm
@@ -0,0 +1,182 @@
+#define ETHEREAL_COLORS list("#00ffff", "#ffc0cb", "#9400D3", "#4B0082", "#0000FF", "#00FF00", "#FFFF00", "#FF7F00", "#FF0000")
+
+/datum/species/ethereal
+ name = "Ethereal"
+ id = "ethereal"
+ attack_verb = "burn"
+ attack_sound = 'sound/weapons/etherealhit.ogg'
+ miss_sound = 'sound/weapons/etherealmiss.ogg'
+ meat = /obj/item/reagent_containers/food/snacks/meat/slab/human/mutant/ethereal
+ mutantstomach = /obj/item/organ/stomach/ethereal
+ mutanttongue = /obj/item/organ/tongue/ethereal
+ exotic_blood = /datum/reagent/consumable/liquidelectricity //Liquid Electricity. fuck you think of something better gamer
+ siemens_coeff = 0.5 //They thrive on energy
+ brutemod = 1.25 //They're weak to punches
+ attack_type = BURN //burn bish
+ damage_overlay_type = "" //We are too cool for regular damage overlays
+ species_traits = list(MUTCOLORS, NO_UNDERWEAR, HAIR, HAS_FLESH, HAS_BONE) // i mean i guess they have blood so they can have wounds too
+ species_language_holder = /datum/language_holder/ethereal
+ inherent_traits = list(TRAIT_NOHUNGER)
+ sexes = FALSE
+ toxic_food = NONE
+ /*
+ citadel doesn't have per-species temperatures, yet
+ // Body temperature for ethereals is much higher then humans as they like hotter environments
+ bodytemp_normal = (BODYTEMP_NORMAL + 50)
+ bodytemp_heat_damage_limit = FIRE_MINIMUM_TEMPERATURE_TO_SPREAD // about 150C
+ // Cold temperatures hurt faster as it is harder to move with out the heat energy
+ bodytemp_cold_damage_limit = (T20C - 10) // about 10c
+ */
+ hair_color = "fixedmutcolor"
+ hair_alpha = 140
+ var/current_color
+ var/EMPeffect = FALSE
+ var/emageffect = FALSE
+ var/r1
+ var/g1
+ var/b1
+ var/static/r2 = 237
+ var/static/g2 = 164
+ var/static/b2 = 149
+ //this is shit but how do i fix it? no clue.
+ var/drain_time = 0 //used to keep ethereals from spam draining power sources
+
+/datum/species/ethereal/on_species_gain(mob/living/carbon/C, datum/species/old_species, pref_load)
+ .=..()
+ if(ishuman(C))
+ var/mob/living/carbon/human/H = C
+ default_color = "#" + H.dna.features["mcolor"]
+ r1 = GETREDPART(default_color)
+ g1 = GETGREENPART(default_color)
+ b1 = GETBLUEPART(default_color)
+ spec_updatehealth(H)
+ RegisterSignal(C, COMSIG_ATOM_EMAG_ACT, .proc/on_emag_act)
+ RegisterSignal(C, COMSIG_ATOM_EMP_ACT, .proc/on_emp_act)
+
+/datum/species/ethereal/on_species_loss(mob/living/carbon/human/C, datum/species/new_species, pref_load)
+ .=..()
+ C.set_light(0)
+ UnregisterSignal(C, COMSIG_ATOM_EMAG_ACT)
+ UnregisterSignal(C, COMSIG_ATOM_EMP_ACT)
+
+/datum/species/ethereal/random_name(gender,unique,lastname)
+ if(unique)
+ return random_unique_ethereal_name()
+
+ var/randname = ethereal_name()
+
+ return randname
+
+/datum/species/ethereal/spec_updatehealth(mob/living/carbon/human/H)
+ .=..()
+ if(H.stat != DEAD && !EMPeffect)
+ var/healthpercent = max(H.health, 0) / 100
+ if(!emageffect)
+ current_color = rgb(r2 + ((r1-r2)*healthpercent), g2 + ((g1-g2)*healthpercent), b2 + ((b1-b2)*healthpercent))
+ H.set_light(1 + (2 * healthpercent), 1 + (1 * healthpercent), current_color)
+ fixed_mut_color = copytext_char(current_color, 2)
+ else
+ H.set_light(0)
+ fixed_mut_color = rgb(128,128,128)
+ H.update_body()
+
+/datum/species/ethereal/proc/on_emp_act(mob/living/carbon/human/H, severity)
+ EMPeffect = TRUE
+ spec_updatehealth(H)
+ to_chat(H, "You feel the light of your body leave you.")
+ switch(severity)
+ if(EMP_LIGHT)
+ addtimer(CALLBACK(src, .proc/stop_emp, H), 10 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE) //We're out for 10 seconds
+ if(EMP_HEAVY)
+ addtimer(CALLBACK(src, .proc/stop_emp, H), 20 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE) //We're out for 20 seconds
+
+/datum/species/ethereal/proc/on_emag_act(mob/living/carbon/human/H, mob/user)
+ if(emageffect)
+ return
+ emageffect = TRUE
+ if(user)
+ to_chat(user, "You tap [H] on the back with your card.")
+ H.visible_message("[H] starts flickering in an array of colors!")
+ handle_emag(H)
+ addtimer(CALLBACK(src, .proc/stop_emag, H), 30 SECONDS) //Disco mode for 30 seconds! This doesn't affect the ethereal at all besides either annoying some players, or making someone look badass.
+
+
+/datum/species/ethereal/spec_life(mob/living/carbon/human/H)
+ .=..()
+ handle_charge(H)
+
+
+/datum/species/ethereal/proc/stop_emp(mob/living/carbon/human/H)
+ EMPeffect = FALSE
+ spec_updatehealth(H)
+ to_chat(H, "You feel more energized as your shine comes back.")
+
+
+/datum/species/ethereal/proc/handle_emag(mob/living/carbon/human/H)
+ if(!emageffect)
+ return
+ current_color = pick(ETHEREAL_COLORS)
+ spec_updatehealth(H)
+ addtimer(CALLBACK(src, .proc/handle_emag, H), 5) //Call ourselves every 0.5 seconds to change color
+
+/datum/species/ethereal/proc/stop_emag(mob/living/carbon/human/H)
+ emageffect = FALSE
+ spec_updatehealth(H)
+ H.visible_message("[H] stops flickering and goes back to their normal state!")
+
+/datum/species/ethereal/proc/handle_charge(mob/living/carbon/human/H)
+ brutemod = 1.25
+ switch(get_charge(H))
+ if(ETHEREAL_CHARGE_NONE)
+ H.throw_alert("ethereal_charge", /obj/screen/alert/etherealcharge, 3)
+ if(ETHEREAL_CHARGE_NONE to ETHEREAL_CHARGE_LOWPOWER)
+ H.throw_alert("ethereal_charge", /obj/screen/alert/etherealcharge, 2)
+ if(H.health > 10.5)
+ apply_damage(0.65, TOX, null, null, H)
+ brutemod = 1.75
+ if(ETHEREAL_CHARGE_LOWPOWER to ETHEREAL_CHARGE_NORMAL)
+ H.throw_alert("ethereal_charge", /obj/screen/alert/etherealcharge, 1)
+ brutemod = 1.5
+ if(ETHEREAL_CHARGE_FULL to ETHEREAL_CHARGE_OVERLOAD)
+ H.throw_alert("ethereal_overcharge", /obj/screen/alert/ethereal_overcharge, 1)
+ apply_damage(0.2, TOX, null, null, H)
+ brutemod = 1.5
+ if(ETHEREAL_CHARGE_OVERLOAD to ETHEREAL_CHARGE_DANGEROUS)
+ H.throw_alert("ethereal_overcharge", /obj/screen/alert/ethereal_overcharge, 2)
+ apply_damage(0.65, TOX, null, null, H)
+ brutemod = 1.75
+ if(prob(10)) //10% each tick for ethereals to explosively release excess energy if it reaches dangerous levels
+ discharge_process(H)
+ else
+ H.clear_alert("ethereal_charge")
+ H.clear_alert("ethereal_overcharge")
+
+/datum/species/ethereal/proc/discharge_process(mob/living/carbon/human/H)
+ to_chat(H, "You begin to lose control over your charge!")
+ H.visible_message("[H] begins to spark violently!")
+ var/static/mutable_appearance/overcharge //shameless copycode from lightning spell
+ overcharge = overcharge || mutable_appearance('icons/effects/effects.dmi', "electricity", EFFECTS_LAYER)
+ H.add_overlay(overcharge)
+ if(do_mob(H, H, 50, 1))
+ H.flash_lighting_fx(5, 7, current_color)
+ var/obj/item/organ/stomach/ethereal/stomach = H.getorganslot(ORGAN_SLOT_STOMACH)
+ playsound(H, 'sound/magic/lightningshock.ogg', 100, TRUE, extrarange = 5)
+ H.cut_overlay(overcharge)
+ tesla_zap(H, 2, stomach.crystal_charge*50, ZAP_OBJ_DAMAGE | ZAP_ALLOW_DUPLICATES)
+ if(istype(stomach))
+ stomach.adjust_charge(100 - stomach.crystal_charge)
+ to_chat(H, "You violently discharge energy!")
+ H.visible_message("[H] violently discharges energy!")
+ if(prob(10)) //chance of developing heart disease to dissuade overcharging oneself
+ var/datum/disease/D = new /datum/disease/heart_failure
+ H.ForceContractDisease(D)
+ to_chat(H, "You're pretty sure you just felt your heart stop for a second there..")
+ H.playsound_local(H, 'sound/effects/singlebeat.ogg', 100, 0)
+ H.Paralyze(100)
+ return
+
+/datum/species/ethereal/proc/get_charge(mob/living/carbon/H) //this feels like it should be somewhere else. Eh?
+ var/obj/item/organ/stomach/ethereal/stomach = H.getorganslot(ORGAN_SLOT_STOMACH)
+ if(istype(stomach))
+ return stomach.crystal_charge
+ return ETHEREAL_CHARGE_NONE
diff --git a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm
index b9864bce16..b760af9850 100644
--- a/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/shadowpeople.dm
@@ -185,6 +185,8 @@
T.ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
else if(isliving(AM))
var/mob/living/L = AM
+ if(isethereal(AM))
+ AM.emp_act(EMP_LIGHT)
if(iscyborg(AM))
var/mob/living/silicon/robot/borg = AM
if(borg.lamp_intensity)
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index 4fcd6d1dbd..9018c49b2c 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -16,6 +16,8 @@
wires = new /datum/wires/robot(src)
AddElement(/datum/element/empprotection, EMP_PROTECT_WIRES)
+ RegisterSignal(src, COMSIG_PROCESS_BORGCHARGER_OCCUPANT, .proc/charge)
+
robot_modules_background = new()
robot_modules_background.icon_state = "block"
robot_modules_background.layer = HUD_LAYER //Objects that appear on screen are on layer ABOVE_HUD_LAYER, UI should be just below it.
@@ -1097,6 +1099,15 @@
for(var/i in connected_ai.aicamera.stored)
aicamera.stored[i] = TRUE
+/mob/living/silicon/robot/proc/charge(datum/source, amount, repairs)
+ if(module)
+ var/coeff = amount * 0.005
+ module.respawn_consumable(src, coeff)
+ if(repairs)
+ heal_bodypart_damage(repairs, repairs - 1)
+ if(cell)
+ cell.charge = min(cell.charge + amount, cell.maxcharge)
+
/mob/living/silicon/robot/proc/rest_style()
set name = "Switch Rest Style"
set category = "Robot Commands"
diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm
index ba51eb3bef..3f3c9b3f94 100644
--- a/code/modules/power/apc.dm
+++ b/code/modules/power/apc.dm
@@ -838,6 +838,46 @@
// attack with hand - remove cell (if cover open) or interact with the APC
/obj/machinery/power/apc/on_attack_hand(mob/user, act_intent = user.a_intent, unarmed_attack_flags)
+ if(isethereal(user))
+ var/mob/living/carbon/human/H = user
+ if(H.a_intent == INTENT_HARM)
+ if(cell.charge <= (cell.maxcharge / 2)) // if charge is under 50% you shouldnt drain it
+ to_chat(H, "The APC doesn't have much power, you probably shouldn't drain any.")
+ return
+ var/obj/item/organ/stomach/ethereal/stomach = H.getorganslot(ORGAN_SLOT_STOMACH)
+ if(stomach.crystal_charge > 145)
+ to_chat(H, "Your charge is full!")
+ return
+ to_chat(H, "You start channeling some power through the APC into your body.")
+ if(do_after(user, 75, target = src))
+ if(cell.charge <= (cell.maxcharge / 2) || (stomach.crystal_charge > 145))
+ return
+ if(istype(stomach))
+ to_chat(H, "You receive some charge from the APC.")
+ stomach.adjust_charge(10)
+ cell.charge -= 10
+ else
+ to_chat(H, "You can't receive charge from the APC!")
+ return
+ if(H.a_intent == INTENT_GRAB)
+ if(cell.charge == cell.maxcharge)
+ to_chat(H, "The APC is full!")
+ return
+ var/obj/item/organ/stomach/ethereal/stomach = H.getorganslot(ORGAN_SLOT_STOMACH)
+ if(stomach.crystal_charge < 10)
+ to_chat(H, "Your charge is too low!")
+ return
+ to_chat(H, "You start channeling power through your body into the APC.")
+ if(do_after(user, 75, target = src))
+ if(cell.charge == cell.maxcharge || (stomach.crystal_charge < 10))
+ return
+ if(istype(stomach))
+ to_chat(H, "You transfer some power to the APC.")
+ stomach.adjust_charge(-10)
+ cell.charge += 10
+ else
+ to_chat(H, "You can't transfer power to the APC!")
+ return
if(opened && (!issilicon(user)))
if(cell)
user.visible_message("[user] removes \the [cell] from [src]!","You remove \the [cell].")
diff --git a/code/modules/power/cell.dm b/code/modules/power/cell.dm
index ba6311a94d..6425feac31 100644
--- a/code/modules/power/cell.dm
+++ b/code/modules/power/cell.dm
@@ -151,6 +151,27 @@
if(prob(25))
corrupt()
+/obj/item/stock_parts/cell/attack_self(mob/user)
+ if(isethereal(user))
+ var/mob/living/carbon/human/H = user
+ if(charge < 100)
+ to_chat(H, "The [src] doesn't have enough power!")
+ return
+ var/obj/item/organ/stomach/ethereal/stomach = H.getorganslot(ORGAN_SLOT_STOMACH)
+ if(stomach.crystal_charge > 146)
+ to_chat(H, "Your charge is full!")
+ return
+ to_chat(H, "You clumsily channel power through the [src] and into your body, wasting some in the process.")
+ if(do_after(user, 5, target = src))
+ if((charge < 100) || (stomach.crystal_charge > 146))
+ return
+ if(istype(stomach))
+ to_chat(H, "You receive some charge from the [src].")
+ stomach.adjust_charge(3)
+ charge -= 100 //you waste way more than you receive, so that ethereals cant just steal one cell and forget about hunger
+ else
+ to_chat(H, "You can't receive charge from the [src]!")
+ return
/obj/item/stock_parts/cell/blob_act(obj/structure/blob/B)
ex_act(EXPLODE_DEVASTATE)
diff --git a/code/modules/power/lighting.dm b/code/modules/power/lighting.dm
index 20f7ce099a..4c76c4b5b1 100644
--- a/code/modules/power/lighting.dm
+++ b/code/modules/power/lighting.dm
@@ -610,7 +610,18 @@
var/mob/living/carbon/human/H = user
if(istype(H))
-
+ var/datum/species/ethereal/eth_species = H.dna?.species
+ if(istype(eth_species))
+ to_chat(H, "You start channeling some power through the [fitting] into your body.")
+ if(do_after(user, 50, target = src))
+ var/obj/item/organ/stomach/ethereal/stomach = H.getorganslot(ORGAN_SLOT_STOMACH)
+ if(istype(stomach))
+ to_chat(H, "You receive some charge from the [fitting].")
+ stomach.adjust_charge(2)
+ else
+ to_chat(H, "You can't receive charge from the [fitting]!")
+ return
+
if(H.gloves)
var/obj/item/clothing/gloves/G = H.gloves
if(G.max_heat_protection_temperature)
diff --git a/code/modules/reagents/chemistry/reagents/food_reagents.dm b/code/modules/reagents/chemistry/reagents/food_reagents.dm
index d449fa310c..a6e78ae98c 100644
--- a/code/modules/reagents/chemistry/reagents/food_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/food_reagents.dm
@@ -768,7 +768,6 @@
color = "#97ee63"
taste_description = "pure electricity"
-/* //We don't have ethereals here, so I'll just comment it out.
/datum/reagent/consumable/liquidelectricity/reaction_mob(mob/living/M, method=TOUCH, reac_volume) //can't be on life because of the way blood works.
if((method == INGEST || method == INJECT || method == PATCH) && iscarbon(M))
@@ -776,10 +775,9 @@
var/obj/item/organ/stomach/ethereal/stomach = C.getorganslot(ORGAN_SLOT_STOMACH)
if(istype(stomach))
stomach.adjust_charge(reac_volume * REM)
-*/
/datum/reagent/consumable/liquidelectricity/on_mob_life(mob/living/carbon/M)
- if(prob(25)) // && !isethereal(M))
+ if(prob(25) && !isethereal(M))
M.electrocute_act(rand(10,15), "Liquid Electricity in their body", 1) //lmao at the newbs who eat energy bars
playsound(M, "sparks", 50, TRUE)
return ..()
diff --git a/code/modules/surgery/organs/stomach.dm b/code/modules/surgery/organs/stomach.dm
index d9cbf9be03..44b4f6362a 100755
--- a/code/modules/surgery/organs/stomach.dm
+++ b/code/modules/surgery/organs/stomach.dm
@@ -91,3 +91,36 @@
/obj/item/organ/stomach/ipc
name = "ipc stomach"
icon_state = "stomach-ipc"
+
+
+/obj/item/organ/stomach/ethereal
+ name = "biological battery"
+ icon_state = "stomach-p" //Welp. At least it's more unique in functionaliy.
+ desc = "A crystal-like organ that stores the electric charge of ethereals."
+ var/crystal_charge = ETHEREAL_CHARGE_FULL
+
+/obj/item/organ/stomach/ethereal/on_life()
+ ..()
+ adjust_charge(-ETHEREAL_CHARGE_FACTOR)
+
+/obj/item/organ/stomach/ethereal/Insert(mob/living/carbon/M, special = 0, drop_if_replaced = TRUE)
+ ..()
+ RegisterSignal(owner, COMSIG_PROCESS_BORGCHARGER_OCCUPANT, .proc/charge)
+ RegisterSignal(owner, COMSIG_LIVING_ELECTROCUTE_ACT, .proc/on_electrocute)
+
+/obj/item/organ/stomach/ethereal/Remove(mob/living/carbon/M, special = 0)
+ UnregisterSignal(owner, COMSIG_PROCESS_BORGCHARGER_OCCUPANT)
+ UnregisterSignal(owner, COMSIG_LIVING_ELECTROCUTE_ACT)
+ ..()
+
+/obj/item/organ/stomach/ethereal/proc/charge(datum/source, amount, repairs)
+ adjust_charge(amount / 70)
+
+/obj/item/organ/stomach/ethereal/proc/on_electrocute(datum/source, shock_damage, siemens_coeff = 1, flags = NONE)
+ if(flags & SHOCK_ILLUSION)
+ return
+ adjust_charge(shock_damage * siemens_coeff * 2)
+ to_chat(owner, "You absorb some of the shock into your body!")
+
+/obj/item/organ/stomach/ethereal/proc/adjust_charge(amount)
+ crystal_charge = clamp(crystal_charge + amount, ETHEREAL_CHARGE_NONE, ETHEREAL_CHARGE_DANGEROUS)
diff --git a/code/modules/surgery/organs/tongue.dm b/code/modules/surgery/organs/tongue.dm
index 1c4a2d3043..7090ab62e2 100644
--- a/code/modules/surgery/organs/tongue.dm
+++ b/code/modules/surgery/organs/tongue.dm
@@ -312,3 +312,26 @@
desc = "A voice synthesizer used by IPCs to smoothly interface with organic lifeforms."
electronics_magic = FALSE
organ_flags = ORGAN_SYNTHETIC
+
+/obj/item/organ/tongue/ethereal
+ name = "electric discharger"
+ desc = "A sophisticated ethereal organ, capable of synthesising speech via electrical discharge."
+ icon_state = "electrotongue"
+ say_mod = "crackles"
+ attack_verb = list("shocked", "jolted", "zapped")
+ taste_sensitivity = 101 // Not a tongue, they can't taste shit
+ var/static/list/languages_possible_ethereal = typecacheof(list(
+ /datum/language/common,
+ /datum/language/draconic,
+ /datum/language/codespeak,
+ /datum/language/monkey,
+ /datum/language/narsie,
+ /datum/language/beachbum,
+ /datum/language/aphasia,
+ /datum/language/sylvan,
+ /datum/language/voltaic
+ ))
+
+/obj/item/organ/tongue/ethereal/Initialize(mapload)
+ . = ..()
+ languages_possible = languages_possible_ethereal
diff --git a/code/modules/vending/snack.dm b/code/modules/vending/snack.dm
index 7aef2b627c..ff8fd46676 100644
--- a/code/modules/vending/snack.dm
+++ b/code/modules/vending/snack.dm
@@ -12,7 +12,8 @@
/obj/item/reagent_containers/food/snacks/no_raisin = 5,
/obj/item/reagent_containers/food/snacks/spacetwinkie = 5,
/obj/item/reagent_containers/food/snacks/cheesiehonkers = 5,
- /obj/item/reagent_containers/food/snacks/cornchips = 5)
+ /obj/item/reagent_containers/food/snacks/cornchips = 5,
+ /obj/item/reagent_containers/food/snacks/energybar = 6)
contraband = list(
/obj/item/reagent_containers/food/snacks/cracker = 10,
/obj/item/reagent_containers/food/snacks/honeybar = 5,
diff --git a/config/game_options.txt b/config/game_options.txt
index 405ec0405a..1585d45e6d 100644
--- a/config/game_options.txt
+++ b/config/game_options.txt
@@ -445,6 +445,7 @@ ROUNDSTART_RACES plasmaman
#ROUNDSTART_RACES shadow
ROUNDSTART_RACES felinid
ROUNDSTART_RACES dwarf
+ROUNDSTART_RACES ethereal
## Races that are better than humans in some ways, but worse in others
#ROUNDSTART_RACES jelly
diff --git a/icons/misc/language.dmi b/icons/misc/language.dmi
index 155dbab98d..9501dc9216 100644
Binary files a/icons/misc/language.dmi and b/icons/misc/language.dmi differ
diff --git a/icons/obj/food/food.dmi b/icons/obj/food/food.dmi
index 28cdefd331..078cadfd60 100644
Binary files a/icons/obj/food/food.dmi and b/icons/obj/food/food.dmi differ
diff --git a/icons/obj/janitor.dmi b/icons/obj/janitor.dmi
index 1f520a42bd..b240391328 100644
Binary files a/icons/obj/janitor.dmi and b/icons/obj/janitor.dmi differ
diff --git a/sound/weapons/etherealhit.ogg b/sound/weapons/etherealhit.ogg
new file mode 100644
index 0000000000..19da870961
Binary files /dev/null and b/sound/weapons/etherealhit.ogg differ
diff --git a/sound/weapons/etherealmiss.ogg b/sound/weapons/etherealmiss.ogg
new file mode 100644
index 0000000000..8feb7cdc91
Binary files /dev/null and b/sound/weapons/etherealmiss.ogg differ
diff --git a/strings/names/ethereal.txt b/strings/names/ethereal.txt
new file mode 100644
index 0000000000..d3e6a26e6e
--- /dev/null
+++ b/strings/names/ethereal.txt
@@ -0,0 +1,38 @@
+Aten
+Apollo
+Arche
+Atlas
+Eos
+Halo
+Kale
+Nysa
+Orion
+Pallas
+Rigel
+Themis
+Aurora
+Andromeda
+Lyra
+Saggitarius
+Crux
+Canis
+Cygnus
+Corvus
+Cepheus
+Auriga
+Corona
+Aquilla
+Serpens
+Cetus
+Puppis
+Ophiuchus
+Carina
+Cassiopeia
+Canes
+Fornax
+Berenices
+Coma
+Vela
+Triangulum
+Tau
+Ceti
\ No newline at end of file
diff --git a/tgstation.dme b/tgstation.dme
index faa1ce990c..c34bd2a1d8 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -2271,6 +2271,7 @@
#include "code\modules\language\swarmer.dm"
#include "code\modules\language\sylvan.dm"
#include "code\modules\language\vampiric.dm"
+#include "code\modules\language\voltaic.dm"
#include "code\modules\language\xenocommon.dm"
#include "code\modules\library\lib_codex_gigas.dm"
#include "code\modules\library\lib_items.dm"
@@ -2499,6 +2500,7 @@
#include "code\modules\mob\living\carbon\human\species_types\corporate.dm"
#include "code\modules\mob\living\carbon\human\species_types\dullahan.dm"
#include "code\modules\mob\living\carbon\human\species_types\dwarves.dm"
+#include "code\modules\mob\living\carbon\human\species_types\ethereal.dm"
#include "code\modules\mob\living\carbon\human\species_types\felinid.dm"
#include "code\modules\mob\living\carbon\human\species_types\flypeople.dm"
#include "code\modules\mob\living\carbon\human\species_types\furrypeople.dm"