diff --git a/code/__defines/nifsoft.dm b/code/__defines/nifsoft.dm
index e4d4f12293..06d1b0c7aa 100644
--- a/code/__defines/nifsoft.dm
+++ b/code/__defines/nifsoft.dm
@@ -16,27 +16,29 @@
#define NIF_UVFILTER 12
#define NIF_FLASHPROT 13
//Health-related
-#define NIF_ORGANIC_HEAL 14
-#define NIF_SYNTH_HEAL 15
-#define NIF_AUTOSTASIS 16 //These two are just part of
-#define NIF_MED_ALARM 17 //medichines right now
-#define NIF_TOXHEAL 18
-#define NIF_SPAREBREATH 19
+#define NIF_CREWMONITOR 14
+#define NIF_ORGANIC_HEAL 15
+#define NIF_SYNTH_HEAL 16
+#define NIF_AUTOSTASIS 17 //These two are just part of
+#define NIF_MED_ALARM 18 //medichines right now
+#define NIF_TOXHEAL 19 //And this, for organics
+#define NIF_SPAREBREATH 20
//Combat Related
-#define NIF_BRUTEARMOR 20
-#define NIF_BURNARMOR 21
-#define NIF_PAINKILLERS 22
-#define NIF_HARDCLAWS 23
-#define NIF_HIDDENLASER 24
+#define NIF_BRUTEARMOR 21
+#define NIF_BURNARMOR 22
+#define NIF_PAINKILLERS 23
+#define NIF_HARDCLAWS 24
+#define NIF_HIDDENLASER 25
//Other
-#define NIF_COMMLINK 25
-#define NIF_SUITSENSORS 26
+#define NIF_COMMLINK 26
#define NIF_APCCHARGE 27
#define NIF_PRESSURE 28
#define NIF_HEATSINK 29
+#define NIF_COMPLIANCE 30
+#define NIF_SIZECHANGE 31
// Must be equal to the highest number above
-#define TOTAL_NIF_SOFTWARE 29
+#define TOTAL_NIF_SOFTWARE 31
//////////////////////
// NIF flag list hints
@@ -78,10 +80,9 @@
//Other
#define NIF_O_COMMLINK 0x1
-#define NIF_O_SENSORS 0x2
-#define NIF_O_APCCHARGE 0x4
-#define NIF_O_PRESSURESEAL 0x8
-#define NIF_O_HEATSINKS 0x10
+#define NIF_O_APCCHARGE 0x2
+#define NIF_O_PRESSURESEAL 0x4
+#define NIF_O_HEATSINKS 0x8
///////////////////
// applies_to flags
diff --git a/code/game/objects/structures/trash_pile.dm b/code/game/objects/structures/trash_pile.dm
index 5490251280..0959f93004 100644
--- a/code/game/objects/structures/trash_pile.dm
+++ b/code/game/objects/structures/trash_pile.dm
@@ -225,7 +225,8 @@
prob(4);/obj/item/device/radio_jammer,
prob(2);/obj/item/weapon/storage/box/syndie_kit/spy,
prob(2);/obj/item/weapon/grenade/anti_photon,
- prob(1);/obj/item/weapon/cell/hyper/empty)
+ prob(1);/obj/item/weapon/cell/hyper/empty,
+ prob(1);/obj/item/weapon/disk/nifsoft/compliance)
var/obj/item/I = new path()
return I
diff --git a/code/modules/client/preference_setup/vore/08_nif.dm b/code/modules/client/preference_setup/vore/08_nif.dm
new file mode 100644
index 0000000000..52586d3bcf
--- /dev/null
+++ b/code/modules/client/preference_setup/vore/08_nif.dm
@@ -0,0 +1,45 @@
+//Pretty small file, mostly just for storage.
+/datum/preferences
+ var/obj/item/device/nif/nif_path
+ var/nif_durability
+
+// Definition of the stuff for NIFs
+/datum/category_item/player_setup_item/vore/nif
+ name = "NIF Data"
+ sort_order = 8
+
+/datum/category_item/player_setup_item/vore/nif/load_character(var/savefile/S)
+ S["nif_path"] >> pref.nif_path
+ S["nif_durability"] >> pref.nif_durability
+
+/datum/category_item/player_setup_item/vore/nif/save_character(var/savefile/S)
+ S["nif_path"] << pref.nif_path
+ S["nif_durability"] << pref.nif_durability
+
+/datum/category_item/player_setup_item/vore/nif/sanitize_character()
+ if(pref.nif_path && !ispath(pref.nif_path)) //We have at least a text string that should be a path.
+ pref.nif_path = text2path(pref.nif_path) //Try to convert it to a hard path.
+ if(!pref.nif_path) //If we couldn't, kill it.
+ pref.nif_path = null //Kill!
+ WARNING("Loaded a NIF but it was an invalid path, [pref.real_name]")
+
+ if(ispath(pref.nif_path) && !pref.nif_durability) //How'd you lose this?
+ pref.nif_durability = initial(pref.nif_path.durability) //Well, have a new one, my bad.
+ WARNING("Loaded a NIF but with no durability, [pref.real_name]")
+
+/datum/category_item/player_setup_item/vore/nif/copy_to_mob(var/mob/living/carbon/human/character)
+ //If you had a NIF...
+ if((character.type == /mob/living/carbon/human) && ispath(pref.nif_path) && pref.nif_durability)
+ new pref.nif_path(character,pref.nif_durability)
+
+ //And now here's the trick. We wipe these so that if they die, they lose the NIF.
+ //Backup implants will start saving this again periodically, and so will cryo'ing out.
+ pref.nif_path = null
+ pref.nif_durability = null
+ var/savefile/S = new /savefile(pref.path)
+ if(!S) WARNING ("Couldn't load NIF save savefile? [pref.real_name]")
+ S.cd = "/character[pref.default_slot]"
+ save_character(S)
+
+/datum/category_item/player_setup_item/vore/nif/content(var/mob/user)
+ . += "NIF: [ispath(pref.nif_path) ? "Present" : "None"]"
diff --git a/code/modules/mob/living/carbon/human/human_attackhand.dm b/code/modules/mob/living/carbon/human/human_attackhand.dm
index 3af1a52bf6..b321d288fb 100644
--- a/code/modules/mob/living/carbon/human/human_attackhand.dm
+++ b/code/modules/mob/living/carbon/human/human_attackhand.dm
@@ -1,5 +1,6 @@
/mob/living/carbon/human/proc/get_unarmed_attack(var/mob/living/carbon/human/target, var/hit_zone)
// VOREStation Edit - Begin
+ if(nif && nif.flag_check(NIF_C_HARDCLAWS,NIF_FLAGS_COMBAT)){return unarmed_hardclaws}
if(src.default_attack && src.default_attack.is_usable(src, target, hit_zone))
if(pulling_punches)
var/datum/unarmed_attack/soft_type = src.default_attack.get_sparring_variant()
diff --git a/code/modules/mob/living/carbon/human/human_damage.dm b/code/modules/mob/living/carbon/human/human_damage.dm
index 30ee7c64d6..d1f468500b 100644
--- a/code/modules/mob/living/carbon/human/human_damage.dm
+++ b/code/modules/mob/living/carbon/human/human_damage.dm
@@ -104,6 +104,7 @@
amount *= M.incoming_damage_percent
if(!isnull(M.incoming_brute_damage_percent))
amount *= M.incoming_brute_damage_percent
+ if(nif && nif.flag_check(NIF_C_BRUTEARMOR,NIF_FLAGS_COMBAT)){amount *= 0.7} //VOREStation Edit - NIF mod for damage resistance for this type of damage
take_overall_damage(amount, 0)
else
for(var/datum/modifier/M in modifiers)
@@ -120,6 +121,7 @@
amount *= M.incoming_damage_percent
if(!isnull(M.incoming_fire_damage_percent))
amount *= M.incoming_fire_damage_percent
+ if(nif && nif.flag_check(NIF_C_BURNARMOR,NIF_FLAGS_COMBAT)){amount *= 0.7} //VOREStation Edit - NIF mod for damage resistance for this type of damage
take_overall_damage(0, amount)
else
for(var/datum/modifier/M in modifiers)
@@ -139,6 +141,7 @@
amount *= M.incoming_damage_percent
if(!isnull(M.incoming_brute_damage_percent))
amount *= M.incoming_brute_damage_percent
+ if(nif && nif.flag_check(NIF_C_BRUTEARMOR,NIF_FLAGS_COMBAT)){amount *= 0.7} //VOREStation Edit - NIF mod for damage resistance for this type of damage
O.take_damage(amount, 0, sharp=is_sharp(damage_source), edge=has_edge(damage_source), used_weapon=damage_source)
else
for(var/datum/modifier/M in modifiers)
@@ -160,6 +163,7 @@
amount *= M.incoming_damage_percent
if(!isnull(M.incoming_fire_damage_percent))
amount *= M.incoming_fire_damage_percent
+ if(nif && nif.flag_check(NIF_C_BURNARMOR,NIF_FLAGS_COMBAT)){amount *= 0.7} //VOREStation Edit - NIF mod for damage resistance for this type of damage
O.take_damage(0, amount, sharp=is_sharp(damage_source), edge=has_edge(damage_source), used_weapon=damage_source)
else
for(var/datum/modifier/M in modifiers)
diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm
index 75d598fb69..fd21301044 100644
--- a/code/modules/mob/living/carbon/human/life.dm
+++ b/code/modules/mob/living/carbon/human/life.dm
@@ -666,7 +666,8 @@
pressure_alert = -1
else
if( !(COLD_RESISTANCE in mutations))
- take_overall_damage(brute=LOW_PRESSURE_DAMAGE, used_weapon = "Low Pressure")
+ if(!isSynthetic() || !nif || !nif.flag_check(NIF_O_PRESSURESEAL,NIF_FLAGS_OTHER)) //VOREStation Edit - NIF pressure seals
+ take_overall_damage(brute=LOW_PRESSURE_DAMAGE, used_weapon = "Low Pressure")
if(getOxyLoss() < 55) // 11 OxyLoss per 4 ticks when wearing internals; unconsciousness in 16 ticks, roughly half a minute
adjustOxyLoss(4) // 16 OxyLoss per 4 ticks when no internals present; unconsciousness in 13 ticks, roughly twenty seconds
pressure_alert = -2
@@ -703,7 +704,8 @@
// FBPs will overheat, prosthetic limbs are fine.
if(robobody_count)
- bodytemperature += round(robobody_count*1.75)
+ if(!nif || !nif.flag_check(NIF_O_HEATSINKS,NIF_FLAGS_OTHER)) //VOREStation Edit - NIF heatsinks
+ bodytemperature += round(robobody_count*1.75)
var/body_temperature_difference = species.body_temperature - bodytemperature
diff --git a/code/modules/nano/interaction/inventory_vr.dm b/code/modules/nano/interaction/inventory_vr.dm
index 373e9f382b..21ee1ca590 100644
--- a/code/modules/nano/interaction/inventory_vr.dm
+++ b/code/modules/nano/interaction/inventory_vr.dm
@@ -1,7 +1,7 @@
/*
This state checks that the src_object is on the user's glasses slot.
*/
-/var/global/datum/topic_state/inventory_state/glasses_state = new()
+/var/global/datum/topic_state/glasses_state/glasses_state = new()
/datum/topic_state/glasses_state/can_use_topic(var/src_object, var/mob/user)
if(ishuman(user))
@@ -10,3 +10,23 @@
return user.shared_nano_interaction()
return STATUS_CLOSE
+
+/var/global/datum/topic_state/nif_state/nif_state = new()
+
+/datum/topic_state/nif_state/can_use_topic(var/src_object, var/mob/user)
+ if(ishuman(user))
+ var/mob/living/carbon/human/H = user
+ if(H.nif && H.nif.stat == NIF_WORKING && src_object == H.nif)
+ return user.shared_nano_interaction()
+
+ return STATUS_CLOSE
+
+/var/global/datum/topic_state/commlink_state/commlink_state = new()
+
+/datum/topic_state/commlink_state/can_use_topic(var/src_object, var/mob/user)
+ if(ishuman(user))
+ var/mob/living/carbon/human/H = user
+ if(H.nif && H.nif.stat == NIF_WORKING && H.nif.comm == src_object)
+ return user.shared_nano_interaction()
+
+ return STATUS_CLOSE
diff --git a/code/modules/nifsoft/nif.dm b/code/modules/nifsoft/nif.dm
index b4ef9eafd7..dc958718cc 100644
--- a/code/modules/nifsoft/nif.dm
+++ b/code/modules/nifsoft/nif.dm
@@ -3,8 +3,8 @@
//Nanotech Implant Foundation
/obj/item/device/nif
- name = "nanotech implant foundation"
- desc = "A somewhat degraded copy of a Kitsuhana working surface, in a box. Can print new \
+ name = "nanite implant framework"
+ desc = "A somewhat diminished knockoff of a Kitsuhana nano working surface, in a box. Can print new \
implants inside living hosts on the fly based on software uploads. Must be surgically \
implanted in the head to work. May eventually wear out and break."
@@ -14,7 +14,7 @@
w_class = ITEMSIZE_TINY
var/durability = 100 // Durability remaining
- var/burn_factor = 100 // Divisor for power charge from nutrition (efficiency basically)
+ var/bioadap = FALSE // If it'll work in fancy species
var/tmp/power_usage = 0 // Nifsoft adds to this
var/tmp/mob/living/carbon/human/human // Our owner!
@@ -31,31 +31,51 @@
var/tmp/install_done // Time when install will finish
var/tmp/open = FALSE // If it's open for maintenance (1-3)
- var/obj/item/clothing/glasses/hud/nif_hud/nif_hud
+ var/obj/item/clothing/glasses/hud/nif_hud/nif_hud // The AR ones require this
+ var/obj/item/device/communicator/commlink/comm // The commlink requires this
var/global/icon/big_icon
- var/global/click_sound = 'sound/effects/pop.ogg'
+ var/global/click_sound = 'sound/items/nif_click.ogg'
+ var/global/bad_sound = 'sound/items/nif_tone_bad.ogg'
+ var/global/good_sound = 'sound/items/nif_tone_good.ogg'
+ var/global/list/look_messages = list(
+ "flicks their eyes around",
+ "looks at something unseen",
+ "reads some invisible text",
+ "seems to be daydreaming",
+ "focuses elsewhere for a moment")
//Constructor comes with a free AR HUD
/obj/item/device/nif/New(var/newloc,var/wear)
..(newloc)
- new /datum/nifsoft/ar_civ(src)
+
+ //Required for AR stuff.
nif_hud = new(src)
+ //First one to spawn in the game, make a big icon
if(!big_icon)
big_icon = new(icon,icon_state = "nif_full")
- //Probably loading from a save
+ //If given a human on spawn (probably from persistence)
if(ishuman(newloc))
var/mob/living/carbon/human/H = newloc
- implant(H)
- owner = H.mind.name
- name = initial(name) + " ([owner])"
- stat = NIF_WORKING
+ if(!quick_implant(H))
+ WARNING("NIF spawned in [H] failed to implant")
+ spawn(0)
+ qdel(src)
+ else
+ //Free commlink for return customers
+ new /datum/nifsoft/commlink(src)
+ //Free civilian AR included
+ new /datum/nifsoft/ar_civ(src)
+
+ //If given wear (like when spawned) then done
if(wear)
durability = wear
+ wear(0) //Just make it update.
+ //Draw me yo.
update_icon()
//Destructor cleans up references
@@ -66,12 +86,20 @@
for(var/S in nifsofts)
if(S)
qdel(S)
+ if(nif_hud)
+ qdel(nif_hud)
+ if(comm)
+ qdel(comm)
+
nifsofts.Cut()
..()
//Being implanted in some mob
/obj/item/device/nif/proc/implant(var/mob/living/carbon/human/H)
- if(istype(H) && !H.nif && H.species && !(H.species.flags & NO_SCAN) && (loc == H.get_organ(BP_HEAD))) //NO_SCAN is the default 'too complicated' flag.
+ if(istype(H) && !H.nif && H.species && (loc == H.get_organ(BP_HEAD)))
+ if(!bioadap && (H.species.flags & NO_SCAN)) //NO_SCAN is the default 'too complicated' flag
+ return FALSE
+
human = H
human.nif = src
stat = NIF_INSTALLING
@@ -87,8 +115,10 @@
return FALSE
src.forceMove(head)
head.implants += src
- owner = H.real_name
- return implant(H)
+ spawn(0) //Let the character finish spawning yo.
+ owner = H.mind.name
+ implant(H)
+ return TRUE
return FALSE
@@ -121,20 +151,20 @@
//Wear update/check proc
/obj/item/device/nif/proc/wear(var/wear = 0)
- if(wear)
- durability -= wear * rand(0.85,1.15) // Also +/- 15%
+ wear *= (rand(85,115) / 100) //Apparently rand() only takes integers.
+ durability -= wear
if(durability <= 0)
- stat = NIF_TEMPFAIL
- update_icon()
notify("Danger! General system insta#^!($",TRUE)
to_chat(human,"Your NIF vision overlays disappear and your head suddenly seems very quiet...")
+ stat = NIF_TEMPFAIL
+ update_icon()
//Attackby proc, for maintenance
/obj/item/device/nif/attackby(obj/item/weapon/W, mob/user as mob)
if(open == 0 && istype(W,/obj/item/weapon/screwdriver))
if(do_after(user, 4 SECONDS, src) && open == 0)
- user.visible_message("[user] unscrews and pries open \the [src].","You unscrew and pry open \the [src].")
+ user.visible_message("[user] unscrews and pries open \the [src].","You unscrew and pry open \the [src].")
playsound(user, 'sound/items/Screwdriver.ogg', 50, 1)
open = 1
update_icon()
@@ -144,25 +174,25 @@
to_chat(user,"You need at least three coils of wire to add them to \the [src].")
return
if(do_after(user, 6 SECONDS, src) && open == 1 && C.use(3))
- user.visible_message("[user] replaces some wiring in \the [src].","You replace any burned out wiring in \the [src].")
+ user.visible_message("[user] replaces some wiring in \the [src].","You replace any burned out wiring in \the [src].")
playsound(user, 'sound/items/Deconstruct.ogg', 50, 1)
open = 2
update_icon()
else if(open == 2 && istype(W,/obj/item/device/multitool))
if(do_after(user, 8 SECONDS, src) && open == 2)
- user.visible_message("[user] resets several circuits in \the [src].","You find and repair any faulty circuits in \the [src].")
+ user.visible_message("[user] resets several circuits in \the [src].","You find and repair any faulty circuits in \the [src].")
open = 3
update_icon()
else if(open == 3 && istype(W,/obj/item/weapon/screwdriver))
if(do_after(user, 3 SECONDS, src) && open == 3)
- user.visible_message("[user] closes up \the [src].","You re-seal \the [src] for use once more.")
+ user.visible_message("[user] closes up \the [src].","You re-seal \the [src] for use once more.")
playsound(user, 'sound/items/Screwdriver.ogg', 50, 1)
open = FALSE
durability = initial(durability)
stat = NIF_PREINSTALL
update_icon()
-//Wear update/check proc
+//Icon updating
/obj/item/device/nif/update_icon()
if(open)
icon_state = "nif_open[open]"
@@ -181,19 +211,24 @@
//The (dramatic) install process
/obj/item/device/nif/proc/handle_install()
- if(human.stat) //No stuff while KO. Sleeping it off is viable, and doesn't start until you wake up from surgery
+ if(human.stat || !human.mind) //No stuff while KO or not sleeved
return FALSE
//Firsties
if(!install_done)
- if(human.real_name == owner)
+ if(human.mind.name == owner)
install_done = world.time + 1 MINUTE
notify("Welcome back, [owner]! Performing quick-calibration...")
- else
+ else if(!owner)
install_done = world.time + 30 MINUTES
notify("Adapting to new user...")
sleep(5 SECONDS)
notify("Adjoining optic [human.isSynthetic() ? "interface" : "nerve"], please be patient.",TRUE)
+ else
+ notify("You are not an authorized user for this device. Please contact [owner].",TRUE)
+ unimplant()
+ stat = NIF_TEMPFAIL
+ return FALSE
var/percent_done = (world.time - (install_done - (30 MINUTES))) / (30 MINUTES)
@@ -229,7 +264,7 @@
//Finishing up
if(1.0 to INFINITY)
stat = NIF_WORKING
- owner = human.real_name
+ owner = human.mind.name
name = initial(name) + " ([owner])"
notify("Calibration complete! User data stored!")
@@ -272,12 +307,19 @@
//Prints 'AR' messages to the user
/obj/item/device/nif/proc/notify(var/message,var/alert = 0)
- if(!human) return
+ if(!human || stat == NIF_TEMPFAIL) return
to_chat(human,"\[\icon[src.big_icon]NIF\] displays, \"[message]\"")
+ if(prob(1)) human.visible_message("\The [human] [pick(look_messages)].")
+ if(alert)
+ human << bad_sound
+ else
+ human << good_sound
//Called to spend nutrition, returns 1 if it was able to
/obj/item/device/nif/proc/use_charge(var/use_charge)
+ if(stat != NIF_WORKING) return FALSE
+
//You don't want us to take any? Well okay.
if(!use_charge)
return TRUE
@@ -292,6 +334,8 @@
//Install a piece of software
/obj/item/device/nif/proc/install(var/datum/nifsoft/new_soft)
+ if(stat == NIF_TEMPFAIL) return FALSE
+
if(nifsofts[new_soft.list_pos])
return FALSE
@@ -305,6 +349,7 @@
notify("The software \"[new_soft]\" is not supported in organic life.",TRUE)
return FALSE
+ wear(new_soft.wear)
nifsofts[new_soft.list_pos] = new_soft
power_usage += new_soft.p_drain
@@ -320,7 +365,6 @@
if(new_soft.other_flags)
other_flags |= new_soft.other_flags
- wear(new_soft.wear)
return TRUE
//Uninstall a piece of software
@@ -353,7 +397,10 @@
//Activate a nifsoft
/obj/item/device/nif/proc/activate(var/datum/nifsoft/soft)
+ if(stat != NIF_WORKING) return FALSE
+
if(human)
+ if(prob(5)) human.visible_message("\The [human] [pick(look_messages)].")
var/applies_to = soft.applies_to
var/synth = human.isSynthetic()
if(synth && !(applies_to & NIF_SYNTHETIC))
@@ -366,6 +413,7 @@
return FALSE
if(!use_charge(soft.a_drain))
+ notify("Not enough power to activate \"[soft]\" NIFsoft!",TRUE)
return FALSE
if(soft.tick_flags == NIF_ACTIVETICK)
@@ -378,6 +426,9 @@
//Deactivate a nifsoft
/obj/item/device/nif/proc/deactivate(var/datum/nifsoft/soft)
+ if(human)
+ if(prob(5)) human.visible_message("\The [human] [pick(look_messages)].")
+
if(soft.tick_flags == NIF_ACTIVETICK)
nifsofts_life -= soft
@@ -427,7 +478,7 @@
//Check for an installed implant
/obj/item/device/nif/proc/imp_check(var/soft)
- if(!stat == NIF_WORKING) return FALSE
+ if(stat != NIF_WORKING) return FALSE
ASSERT(soft)
if(ispath(soft))
@@ -439,7 +490,8 @@
//Check for a set flag
/obj/item/device/nif/proc/flag_check(var/flag,var/hint)
- if(!stat == NIF_WORKING) return FALSE
+ if(stat != NIF_WORKING) return FALSE
+
ASSERT(flag && hint)
var/result = FALSE
@@ -457,6 +509,7 @@
return result
+///////////////////////////////////////////////////////////////////////////////////////
//NIF HUD object becasue HUD handling is trash and should be rewritten
/obj/item/clothing/glasses/hud/nif_hud/var/obj/item/device/nif/nif
@@ -464,6 +517,12 @@
..(newloc)
nif = newloc
+/obj/item/clothing/glasses/hud/nif_hud/Destroy()
+ if(nif)
+ nif.nif_hud = null
+ nif = null
+ ..()
+
/obj/item/clothing/glasses/hud/nif_hud/process_hud(M,var/thing)
//Faster checking with local var, and this is called often so I want fast.
var/visflags = nif.vision_flags
@@ -479,3 +538,22 @@
process_omni_hud(nif.human, "sci")
else if(NIF_V_AR_CIVILIAN & visflags)
process_omni_hud(nif.human, "civ")
+
+// Alternate NIFs
+/obj/item/device/nif/bad
+ name = "second-hand NIF"
+ desc = "A copy of a copy of a copy of a copy of... this can't be any good, right? Surely?"
+ durability = 10
+
+/obj/item/device/nif/authentic
+ name = "\improper Kitsuhana NIF"
+ desc = "An actual Kitsuhana working surface, in a box. From a society slightly less afraid \
+ of self-replicating nanotechnology. Basically just a high-endurance NIF."
+ durability = 1000
+
+/obj/item/device/nif/bioadap
+ name = "bioadaptive NIF"
+ desc = "A NIF that goes out of it's way to accomidate strange body types. \
+ Will function in species where it normally wouldn't."
+ durability = 25
+ bioadap = TRUE
diff --git a/code/modules/nifsoft/nif_softshop.dm b/code/modules/nifsoft/nif_softshop.dm
index 3680b47786..4d0da46843 100644
--- a/code/modules/nifsoft/nif_softshop.dm
+++ b/code/modules/nifsoft/nif_softshop.dm
@@ -20,7 +20,7 @@
starting_illegal_nifsoft = list()
for(var/P in subtypesof(/datum/nifsoft) - /datum/nifsoft/package)
var/datum/nifsoft/NS = P
- if(initial(NS.initial))
+ if(initial(NS.vended))
switch(initial(NS.illegal))
if(TRUE)
starting_illegal_nifsoft += NS
@@ -40,7 +40,13 @@
for(var/entry in current_list[1])
var/datum/nifsoft/NS = entry
- var/name = initial(NS.name)
+ var/applies_to = initial(NS.applies_to)
+ var/context = ""
+ if(!(applies_to & NIF_SYNTHETIC))
+ context = " (Org Only)"
+ else if(!(applies_to & NIF_ORGANIC))
+ context = " (Syn Only)"
+ var/name = "[initial(NS.name)][context]"
var/datum/stored_item/vending_product/product = new/datum/stored_item/vending_product(src, entry, name)
product.price = initial(NS.cost)
@@ -61,6 +67,104 @@
return ..()
+/*
+/obj/proc/allowed(mob/M)
+ //check if it doesn't require any access at all
+ if(src.check_access(null))
+ return 1
+
+ var/id = M.GetIdCard()
+ if(id)
+ return check_access(id)
+ return 0
+
+///obj/item/proc/GetAccess()
+// return list()
+
+/atom/movable/proc/GetAccess()
+ var/obj/item/weapon/card/id/id = GetIdCard()
+ return id ? id.GetAccess() : list()
+
+/obj/proc/GetID()
+ return null
+
+/obj/proc/check_access(obj/item/I)
+ return check_access_list(I ? I.GetAccess() : list())
+
+/obj/proc/check_access_list(var/list/L)
+ if(!req_access) req_access = list()
+ if(!req_one_access) req_one_access = list()
+ if(!L) return 0
+ if(!istype(L, /list)) return 0
+ return has_access(req_access, req_one_access, L)
+*/
+
+//Had to override this too
+/obj/machinery/vending/nifsoft_shop/Topic(href, href_list)
+ if(stat & (BROKEN|NOPOWER))
+ return
+ if(usr.stat || usr.restrained())
+ return
+
+ if(href_list["remove_coin"] && !istype(usr,/mob/living/silicon))
+ if(!coin)
+ usr << "There is no coin in this machine."
+ return
+
+ coin.forceMove(src.loc)
+ if(!usr.get_active_hand())
+ usr.put_in_hands(coin)
+ usr << "You remove \the [coin] from \the [src]"
+ coin = null
+ categories &= ~CAT_COIN
+
+ if((usr.contents.Find(src) || (in_range(src, usr) && istype(src.loc, /turf))))
+ if((href_list["vend"]) && (vend_ready) && (!currently_vending))
+ if((!allowed(usr)) && !emagged && scan_id) //For SECURE VENDING MACHINES YEAH
+ usr << "Access denied." //Unless emagged of course
+ flick(icon_deny,src)
+ return
+
+ var/key = text2num(href_list["vend"])
+ var/datum/stored_item/vending_product/R = product_records[key]
+
+ // This should not happen unless the request from NanoUI was bad
+ if(!(R.category & categories))
+ return
+
+ //Specific soft access checking
+ var/datum/nifsoft/path = R.item_path
+ if(initial(path.access))
+ var/list/soft_access = list(initial(path.access))
+ var/list/usr_access = usr.GetAccess()
+ if(!has_access(soft_access, list(), usr_access) && !emagged)
+ usr << "You aren't authorized to buy [initial(path.name)]."
+ flick(icon_deny,src)
+ return
+
+ if(R.price <= 0)
+ vend(R, usr)
+ else if(istype(usr,/mob/living/silicon)) //If the item is not free, provide feedback if a synth is trying to buy something.
+ usr << "Artificial unit recognized. Artificial units cannot complete this transaction. Purchase canceled."
+ return
+ else
+ currently_vending = R
+ if(!vendor_account || vendor_account.suspended)
+ status_message = "This machine is currently unable to process payments due to problems with the associated account."
+ status_error = 1
+ else
+ status_message = "[initial(path.desc)]
Please swipe a card or insert cash to pay for the item."
+ status_error = 0
+
+ else if(href_list["cancelpurchase"])
+ currently_vending = null
+
+ else if((href_list["togglevoice"]) && (panel_open))
+ shut_up = !shut_up
+
+ add_fingerprint(usr)
+ nanomanager.update_uis(src)
+
// Also special treatment!
/obj/machinery/vending/nifsoft_shop/vend(datum/stored_item/vending_product/R, mob/user)
var/mob/living/carbon/human/H = user
diff --git a/code/modules/nifsoft/nif_statpanel.dm b/code/modules/nifsoft/nif_statpanel.dm
index 990bf43565..4ddba7a3c9 100644
--- a/code/modules/nifsoft/nif_statpanel.dm
+++ b/code/modules/nifsoft/nif_statpanel.dm
@@ -4,7 +4,7 @@
SetupNifStat()
/mob/living/carbon/human/proc/SetupNifStat()
- var/nif_status
+ var/nif_status = ""
switch(nif.stat)
if(NIF_WORKING)
nif_status = "Operating Normally"
@@ -16,6 +16,7 @@
nif_status = "Adapting To User"
else
nif_status = "Unknown - Error"
+ nif_status += " ([round((nif.durability/initial(nif.durability))*100)]%)"
stat("NIF Status", nif_status)
if(!nif.stat)
@@ -39,17 +40,13 @@
nifsoft = new_soft
nifsoft_name = new_soft.name
name = new_soft.name
- toggleable = new_soft.activates
/obj/effect/nif_stat/Destroy()
nifsoft = null
..()
/obj/effect/nif_stat/proc/atom_button_text()
- if(!toggleable)
- name = "Always On"
- else
- name = "[nifsoft.active ? "Active" : "Disabled"]"
+ name = nifsoft.stat_text()
return src
/obj/effect/nif_stat/Click(var/location, var/control, var/params)
diff --git a/code/modules/nifsoft/nifsoft.dm b/code/modules/nifsoft/nifsoft.dm
index 453e00d79b..f86e0150e3 100644
--- a/code/modules/nifsoft/nifsoft.dm
+++ b/code/modules/nifsoft/nifsoft.dm
@@ -9,9 +9,9 @@
var/cost = 1000 // Cost in cash of buying this software from a terminal
//TODO - While coding
- var/initial = TRUE // This is available in NIFSoft Shops at the start of the game
+ var/vended = TRUE // This is available in NIFSoft Shops at the start of the game
var/wear = 1 // The wear (+/- 10% when applied) that this causes to the NIF
- var/list/req_one_access // What access they need to buy it
+ var/access // What access they need to buy it, can only set one for ~reasons~
var/illegal = FALSE // If this is a black-market nifsoft (emag option)
var/active = FALSE // Whether the active mode of this implant is on
@@ -63,6 +63,7 @@
deactivate()
if(nif)
. = nif.uninstall(src)
+ nif = null
qdel(src)
//Called every life() tick on a mob on active implants
@@ -88,6 +89,17 @@
uninstall()
return
+//Called when installed from a disk
+/datum/nifsoft/proc/disk_install(var/mob/living/carbon/human/target,var/mob/living/carbon/human/user)
+ return TRUE
+
+//Stat-line clickable text
+/datum/nifsoft/proc/stat_text()
+ if(activates)
+ return "[active ? "Active" : "Disabled"]"
+
+ return "Always On"
+
//////////////////////
//A package of NIF software
/datum/nifsoft/package
@@ -108,3 +120,62 @@
software.Cut()
software = null
..()
+
+/////////////////
+// A NIFSoft Disk
+/obj/item/weapon/disk/nifsoft
+ name = "NIFSoft Disk"
+ desc = "It has a small label: \n\
+ \"Portable NIFSoft Disk. \n\
+ Insert directly into brain.\""
+ icon = 'icons/obj/cloning.dmi'
+ icon_state = "datadisk2"
+ item_state = "card-id"
+ w_class = ITEMSIZE_SMALL
+ var/datum/nifsoft/stored = null
+ var/laws = ""
+
+/obj/item/weapon/disk/nifsoft/afterattack(var/A, mob/user, flag, params)
+ if(!in_range(user, A))
+ return
+
+ if(!ishuman(user) || !ishuman(A))
+ return
+
+ var/mob/living/carbon/human/Ht = A
+ var/mob/living/carbon/human/Hu = user
+
+ if(!Ht.nif || Ht.nif.stat != NIF_WORKING)
+ to_chat(user,"Either they don't have a NIF, or the disk can't connect.")
+ return
+
+ Ht.visible_message("[Hu] begins uploading new NIFSoft into [Ht]!","[Hu] is uploading new NIFSoft into you!")
+ if(do_after(Ht,10 SECONDS,Hu))
+ var/extra = extra_params()
+ new stored(Ht.nif,extra)
+ qdel(src)
+
+//So disks can pass fancier stuff.
+/obj/item/weapon/disk/nifsoft/proc/extra_params()
+ return null
+
+/obj/item/weapon/disk/nifsoft/compliance
+ name = "NIFSoft Disk (Compliance)"
+ desc = "Wow, adding laws to people? That seems illegal. It probably is. Okay, it really is."
+ stored = /datum/nifsoft/compliance
+
+/obj/item/weapon/disk/nifsoft/compliance/afterattack(var/A, mob/user, flag, params)
+ if(!laws)
+ to_chat(user,"You haven't set any laws yet. Use the disk in-hand first.")
+ return
+ ..(A,user,flag,params)
+
+/obj/item/weapon/disk/nifsoft/compliance/attack_self(mob/user)
+ var/newlaws = input(user,"Please Input Laws","Compliance Laws",laws) as message
+ newlaws = sanitize(newlaws,2048)
+ if(newlaws)
+ to_chat(user,"You set the laws to:
[newlaws]")
+ laws = newlaws
+
+/obj/item/weapon/disk/nifsoft/compliance/extra_params()
+ return laws
diff --git a/code/modules/nifsoft/software/01_vision.dm b/code/modules/nifsoft/software/01_vision.dm
index 15f253d9b2..d222cd67dd 100644
--- a/code/modules/nifsoft/software/01_vision.dm
+++ b/code/modules/nifsoft/software/01_vision.dm
@@ -5,16 +5,15 @@
desc = "Provides a general identification and health status overlay on your vision with no frills."
list_pos = NIF_CIVILIAN_AR
cost = 500
- initial = TRUE
a_drain = 0.05
activate()
- if(..())
+ if((. = ..()))
nif.deactivate_these(NIF_MEDICAL_AR,NIF_SECURITY_AR,NIF_ENGINE_AR,NIF_SCIENCE_AR,NIF_OMNI_AR)
nif.set_flag(NIF_V_AR_CIVILIAN,NIF_FLAGS_VISION)
deactivate()
- if(..())
+ if((. = ..()))
nif.clear_flag(NIF_V_AR_CIVILIAN,NIF_FLAGS_VISION)
/datum/nifsoft/ar_med
@@ -22,17 +21,16 @@
desc = "Like the civilian model, but provides medical records access and virus database lookup."
list_pos = NIF_MEDICAL_AR
cost = 750
- initial = TRUE
- req_one_access = list(access_medical)
+ access = access_medical
a_drain = 0.05
activate()
- if(..())
+ if((. = ..()))
nif.deactivate_these(NIF_CIVILIAN_AR,NIF_SECURITY_AR,NIF_ENGINE_AR,NIF_SCIENCE_AR,NIF_OMNI_AR)
nif.set_flag(NIF_V_AR_MEDICAL,NIF_FLAGS_VISION)
deactivate()
- if(..())
+ if((. = ..()))
nif.clear_flag(NIF_V_AR_MEDICAL,NIF_FLAGS_VISION)
/datum/nifsoft/ar_sec
@@ -40,17 +38,16 @@
desc = "Like the civilian model, but provides access to arrest status and security records."
list_pos = NIF_SECURITY_AR
cost = 750
- initial = TRUE
- req_one_access = list(access_security)
+ access = access_security
a_drain = 0.05
activate()
- if(..())
+ if((. = ..()))
nif.deactivate_these(NIF_CIVILIAN_AR,NIF_MEDICAL_AR,NIF_ENGINE_AR,NIF_SCIENCE_AR,NIF_OMNI_AR)
nif.set_flag(NIF_V_AR_SECURITY,NIF_FLAGS_VISION)
deactivate()
- if(..())
+ if((. = ..()))
nif.clear_flag(NIF_V_AR_SECURITY,NIF_FLAGS_VISION)
/datum/nifsoft/ar_eng
@@ -58,17 +55,16 @@
desc = "Like the civilian model, but provides station alert notices."
list_pos = NIF_ENGINE_AR
cost = 750
- initial = TRUE
- req_one_access = list(access_engine)
+ access = access_engine
a_drain = 0.05
activate()
- if(..())
+ if((. = ..()))
nif.deactivate_these(NIF_CIVILIAN_AR,NIF_MEDICAL_AR,NIF_SECURITY_AR,NIF_SCIENCE_AR,NIF_OMNI_AR)
nif.set_flag(NIF_V_AR_ENGINE,NIF_FLAGS_VISION)
deactivate()
- if(..())
+ if((. = ..()))
nif.clear_flag(NIF_V_AR_ENGINE,NIF_FLAGS_VISION)
/datum/nifsoft/ar_science
@@ -76,17 +72,16 @@
desc = "Like the civilian model, but provides ... well, nothing. For now."
list_pos = NIF_SCIENCE_AR
cost = 750
- initial = TRUE
- req_one_access = list(access_research)
+ access = access_research
a_drain = 0.05
activate()
- if(..())
+ if((. = ..()))
nif.deactivate_these(NIF_CIVILIAN_AR,NIF_MEDICAL_AR,NIF_SECURITY_AR,NIF_ENGINE_AR,NIF_OMNI_AR)
nif.set_flag(NIF_V_AR_SCIENCE,NIF_FLAGS_VISION)
deactivate()
- if(..())
+ if((. = ..()))
nif.clear_flag(NIF_V_AR_SCIENCE,NIF_FLAGS_VISION)
/datum/nifsoft/ar_omni
@@ -94,17 +89,16 @@
desc = "Like the civilian model, but provides most of the features of the medical and security overlays as well."
list_pos = NIF_OMNI_AR
cost = 750
- initial = TRUE
- req_one_access = list(access_captain)
+ access = access_captain
a_drain = 0.05
activate()
- if(..())
+ if((. = ..()))
nif.deactivate_these(NIF_CIVILIAN_AR,NIF_MEDICAL_AR,NIF_SECURITY_AR,NIF_ENGINE_AR,NIF_SCIENCE_AR)
nif.set_flag(NIF_V_AR_OMNI,NIF_FLAGS_VISION)
deactivate()
- if(..())
+ if((. = ..()))
nif.clear_flag(NIF_V_AR_OMNI,NIF_FLAGS_VISION)
//////////////
@@ -117,11 +111,11 @@
a_drain = 0.025
activate()
- if(..())
+ if((. = ..()))
nif.set_flag(NIF_V_CORRECTIVE,NIF_FLAGS_VISION)
deactivate()
- if(..())
+ if((. = ..()))
nif.clear_flag(NIF_V_CORRECTIVE,NIF_FLAGS_VISION)
/datum/nifsoft/uvblocker
@@ -132,11 +126,11 @@
a_drain = 0.2
activate()
- if(..())
+ if((. = ..()))
nif.set_flag(NIF_V_UVFILTER,NIF_FLAGS_VISION)
deactivate()
- if(..())
+ if((. = ..()))
nif.clear_flag(NIF_V_UVFILTER,NIF_FLAGS_VISION)
/datum/nifsoft/flashprot
@@ -144,15 +138,15 @@
desc = "Enables a high-speed shielding response to intense light, such as flashes, to block them."
list_pos = NIF_FLASHPROT
cost = 600
- req_one_access = list(access_security)
+ access = access_security
a_drain = 0.05
activate()
- if(..())
+ if((. = ..()))
nif.set_flag(NIF_V_FLASHPROT,NIF_FLAGS_VISION)
deactivate()
- if(..())
+ if((. = ..()))
nif.clear_flag(NIF_V_FLASHPROT,NIF_FLAGS_VISION)
////////////////
@@ -163,19 +157,20 @@
list_pos = NIF_MESONS
cost = 1000
a_drain = 0.1
+ access = access_engine
tick_flags = NIF_ACTIVETICK
activate()
- if(..())
+ if((. = ..()))
nif.deactivate_these(NIF_MATERIAL,NIF_THERMALS,NIF_NIGHTVIS)
nif.set_flag(NIF_V_MESONS,NIF_FLAGS_VISION)
deactivate()
- if(..())
+ if((. = ..()))
nif.clear_flag(NIF_V_MESONS,NIF_FLAGS_VISION)
life()
- if(..())
+ if((. = ..()))
var/mob/living/carbon/human/H = nif.human
H.client.screen |= global_hud.meson
H.sight |= SEE_TURFS
@@ -186,19 +181,20 @@
list_pos = NIF_MATERIAL
cost = 1000
a_drain = 0.1
+ access = access_research
tick_flags = NIF_ACTIVETICK
activate()
- if(..())
+ if((. = ..()))
nif.deactivate_these(NIF_MESONS,NIF_THERMALS,NIF_NIGHTVIS)
nif.set_flag(NIF_V_MATERIAL,NIF_FLAGS_VISION)
deactivate()
- if(..())
+ if((. = ..()))
nif.clear_flag(NIF_V_MATERIAL,NIF_FLAGS_VISION)
life()
- if(..())
+ if((. = ..()))
nif.human.sight |= SEE_OBJS
/datum/nifsoft/thermals
@@ -206,21 +202,21 @@
desc = "Similar to the worn Thermal Goggles, these allow you to see heat-emitting creatures through walls."
list_pos = NIF_THERMALS
cost = 1000
- req_one_access = list(access_security)
a_drain = 0.1
+ access = access_captain
tick_flags = NIF_ACTIVETICK
activate()
- if(..())
+ if((. = ..()))
nif.deactivate_these(NIF_MESONS,NIF_MATERIAL,NIF_NIGHTVIS)
nif.set_flag(NIF_V_THERMALS,NIF_FLAGS_VISION)
deactivate()
- if(..())
+ if((. = ..()))
nif.clear_flag(NIF_V_THERMALS,NIF_FLAGS_VISION)
life()
- if(..())
+ if((. = ..()))
var/mob/living/carbon/human/H = nif.human
H.sight |= SEE_MOBS
H.client.screen |= global_hud.thermal
@@ -231,21 +227,21 @@
desc = "Similar to the worn Night Vision Goggles, these allow you to see in complete darkness."
list_pos = NIF_NIGHTVIS
cost = 1000
- req_one_access = list(access_security)
a_drain = 0.1
+ access = access_security
tick_flags = NIF_ACTIVETICK
activate()
- if(..())
+ if((. = ..()))
nif.deactivate_these(NIF_MESONS,NIF_MATERIAL,NIF_THERMALS)
nif.set_flag(NIF_V_NIGHTVIS,NIF_FLAGS_VISION)
deactivate()
- if(..())
+ if((. = ..()))
nif.clear_flag(NIF_V_NIGHTVIS,NIF_FLAGS_VISION)
life()
- if(..())
+ if((. = ..()))
var/mob/living/carbon/human/H = nif.human
H.see_in_dark += 7
H.client.screen |= global_hud.nvg
diff --git a/code/modules/nifsoft/software/05_health.dm b/code/modules/nifsoft/software/05_health.dm
index 4bc83c407f..1d606d2653 100644
--- a/code/modules/nifsoft/software/05_health.dm
+++ b/code/modules/nifsoft/software/05_health.dm
@@ -1,14 +1,41 @@
-/*
-#define NIF_SPAREBREATH 17
-*/
+// #define NIF_CREWMONITOR 14
+/datum/nifsoft/crewmonitor
+ name = "Crew Monitor"
+ desc = "A link to the local crew monitor sensors. Useful for finding people in trouble."
+ list_pos = NIF_CREWMONITOR
+ access = access_medical
+ cost = 1250
+ p_drain = 0.025
+ var/datum/nano_module/crew_monitor/arscreen
+
+ New()
+ ..()
+ arscreen = new(nif)
+
+ Destroy()
+ qdel(arscreen)
+ ..()
+
+ activate()
+ if((. = ..()))
+ arscreen.ui_interact(nif.human,"main",null,1,nif_state)
+ return TRUE
+
+ deactivate()
+ if((. = ..()))
+ return TRUE
+
+ stat_text()
+ return "Show Monitor"
/datum/nifsoft/medichines_org
- name = "Medichines (Org)"
+ name = "Medichines"
desc = "An internal swarm of nanites to make sure you stay in good shape and to promote healing, or to preserve you if you are critically injured."
list_pos = NIF_ORGANIC_HEAL
cost = 2500
p_drain = 0.05
a_drain = 0.1 //This is messed with manually below.
+ wear = 2
activates = FALSE //It is automatic in emergencies, not manually controllable.
tick_flags = NIF_ALWAYSTICK
applies_to = NIF_ORGANIC
@@ -16,19 +43,19 @@
//These self-activate on their own, these aren't user-settable to on/off.
activate()
- if(..())
+ if((. = ..()))
nif.set_flag(NIF_H_ORGREPAIR,NIF_FLAGS_HEALTH)
mode = 1
deactivate()
- if(..())
+ if((. = ..()))
nif.clear_flag(NIF_H_ORGREPAIR,NIF_FLAGS_HEALTH)
a_drain = initial(a_drain)
mode = initial(mode)
- nif.human.in_stasis = 0
+ nif.human.Stasis(0)
life()
- if(..())
+ if((. = ..()))
var/mob/living/carbon/human/H = nif.human
var/HP_percent = H.health/H.getMaxHealth()
@@ -67,21 +94,22 @@
//Patient critical - emergency stasis
if(mode >= 3)
if(HP_percent <= 0)
- H.in_stasis = 3
+ H.Stasis(3)
if(HP_percent > 0.2)
- H.in_stasis = 0
+ H.Stasis(0)
nif.notify("Ending emergency stasis.",TRUE)
mode = 2
return TRUE
/datum/nifsoft/medichines_syn
- name = "Medichines (Syn)"
+ name = "Medichines"
desc = "A swarm of mechanical repair nanites, able to repair relatively minor damage to synthetic bodies. Large repairs must still be performed manually."
list_pos = NIF_SYNTH_HEAL
cost = 2500
p_drain = 0.05
a_drain = 0.00 //This is manually drained below.
+ wear = 2
activates = FALSE //It is automatic in emergencies, not manually controllable.
tick_flags = NIF_ALWAYSTICK
applies_to = NIF_SYNTHETIC
@@ -89,17 +117,17 @@
//These self-activate on their own, these aren't user-settable to on/off.
activate()
- if(..())
+ if((. = ..()))
nif.set_flag(NIF_H_SYNTHREPAIR,NIF_FLAGS_HEALTH)
mode = 1
deactivate()
- if(..())
+ if((. = ..()))
nif.clear_flag(NIF_H_SYNTHREPAIR,NIF_FLAGS_HEALTH)
mode = 0
life()
- if(..())
+ if((. = ..()))
//We're good!
if(!nif.human.bad_external_organs.len)
if(mode || active)
@@ -135,6 +163,7 @@
cost = 650
p_drain = 0.05
a_drain = 0.1
+ wear = 2
tick_flags = NIF_ALWAYSTICK
applies_to = NIF_ORGANIC
var/filled = 100 //Tracks the internal tank 'refilling', which still uses power
@@ -143,17 +172,17 @@
if(!(filled > 50))
nif.notify("Respirocytes not saturated!",TRUE)
return FALSE
- if(..())
+ if((. = ..()))
nif.set_flag(NIF_H_SPAREBREATH,NIF_FLAGS_HEALTH)
nif.notify("Now taking air from reserves.")
deactivate()
- if(..())
+ if((. = ..()))
nif.clear_flag(NIF_H_SPAREBREATH,NIF_FLAGS_HEALTH)
nif.notify("Now taking air from environment and refilling reserves.")
life()
- if(..())
+ if((. = ..()))
if(active) //Supplying air, not recharging it
switch(filled) //Text warnings
if(75)
diff --git a/code/modules/nifsoft/software/10_combat.dm b/code/modules/nifsoft/software/10_combat.dm
index 64f3ae9a25..38fe1b86e2 100644
--- a/code/modules/nifsoft/software/10_combat.dm
+++ b/code/modules/nifsoft/software/10_combat.dm
@@ -1,7 +1,140 @@
/*
-#define NIF_BRUTEARMOR 18
-#define NIF_BURNARMOR 19
-#define NIF_PAINKILLERS 20
-#define NIF_HARDCLAWS 21
#define NIF_HIDDENLASER 22
-*/
\ No newline at end of file
+*/
+
+/datum/nifsoft/brute_armor
+ name = "Bullhide Mod"
+ desc = "A difficult-to-produce thickening of the dermis and skeletal structure, allowing a user to absorb more external trauma from physical sources."
+ list_pos = NIF_BRUTEARMOR
+ cost = 3200
+ p_drain = 0.05
+ illegal = TRUE
+ wear = 3
+ access = 999 //Prevents anyone from buying it without an emag.
+ activates = FALSE //It's armor.
+
+ install()
+ if((. = ..()))
+ nif.set_flag(NIF_C_BRUTEARMOR,NIF_FLAGS_COMBAT)
+
+ uninstall()
+ . = ..()
+ nif.clear_flag(NIF_C_BRUTEARMOR,NIF_FLAGS_COMBAT)
+
+/datum/nifsoft/burn_armor
+ name = "Dragon's Skin"
+ desc = "A thin layer of material under the skin provides heat disappation for burns, reducing the trauma from lasers and fire. Not effective against ongoing environmental heat."
+ list_pos = NIF_BURNARMOR
+ cost = 3200
+ p_drain = 0.05
+ illegal = TRUE
+ wear = 3
+ access = 999 //Prevents anyone from buying it without an emag.
+ activates = FALSE //It's armor.
+
+ install()
+ if((. = ..()))
+ nif.set_flag(NIF_C_BURNARMOR,NIF_FLAGS_COMBAT)
+
+ uninstall()
+ . = ..()
+ nif.clear_flag(NIF_C_BURNARMOR,NIF_FLAGS_COMBAT)
+
+/datum/nifsoft/painkillers
+ name = "Nova Shock"
+ desc = "A constant stream of high-grade painkillers numb the user's body to all pain. Generally results in extreme addiction or overdose."
+ list_pos = NIF_PAINKILLERS
+ cost = 2600
+ a_drain = 1 //Gotta produce them drugs, yo.
+ illegal = TRUE
+ wear = 2
+ access = 999 //Prevents anyone from buying it without an emag.
+ tick_flags = NIF_ACTIVETICK
+
+ activate()
+ if((. = ..()))
+ nif.set_flag(NIF_C_PAINKILLERS,NIF_FLAGS_COMBAT)
+
+ deactivate()
+ if((. = ..()))
+ nif.clear_flag(NIF_C_PAINKILLERS,NIF_FLAGS_COMBAT)
+
+ life()
+ if((. = ..()))
+ var/mob/living/carbon/human/H = nif.human
+ H.bloodstr.add_reagent("numbenzyme",0.5)
+
+/datum/nifsoft/hardclaws
+ name = "Bloodletters"
+ desc = "Generates monofilament wires from one's fingertips, allowing one to slash through almost any armor with relative ease. The monofilaments need to be replaced constantly, though, which does use power."
+ list_pos = NIF_HARDCLAWS
+ cost = 2200
+ a_drain = 0.5
+ illegal = TRUE
+ wear = 4
+ access = 999 //Prevents anyone from buying it without an emag.
+
+ activate()
+ if((. = ..()))
+ nif.set_flag(NIF_C_HARDCLAWS,NIF_FLAGS_COMBAT)
+
+ deactivate()
+ if((. = ..()))
+ nif.clear_flag(NIF_C_HARDCLAWS,NIF_FLAGS_COMBAT)
+
+// The unarmed attack to go with the hardclaws
+var/global/datum/unarmed_attack/hardclaws/unarmed_hardclaws = new()
+/datum/unarmed_attack/hardclaws
+ attack_verb = list("claws")
+ attack_noun = list("talons")
+ damage = 15
+ attack_sound = "punch"
+ miss_sound = 'sound/weapons/punchmiss.ogg'
+ sharp = 1
+ edge = 1
+ sparring_variant_type = /datum/unarmed_attack/hardclaws
+
+/datum/nifsoft/hidelaser
+ name = "Dazzle"
+ desc = "Fabricates a 2-shot holdout laser inside your body, which can be deployed (somewhat painfully) on demand. Only enough materials to generate one."
+ list_pos = NIF_HIDDENLASER
+ cost = 2200
+ //a_drain = 50 //Done manually below.
+ illegal = TRUE
+ wear = 6
+ access = 999 //Prevents anyone from buying it without an emag.
+ var/used = FALSE
+
+ activate()
+ if((. = ..()))
+ nif.set_flag(NIF_C_HIDELASER,NIF_FLAGS_COMBAT)
+ if(used)
+ nif.notify("You do not have a hidden weapon to deploy anymore!",TRUE)
+ deactivate()
+ return FALSE
+ if(!nif.use_charge(50))
+ nif.notify("Insufficient energy to deploy weapon!",TRUE)
+ deactivate()
+ return FALSE
+
+ var/mob/living/carbon/human/H = nif.human
+ H.adjustHalLoss(30)
+ var/obj/item/weapon/gun/energy/gun/martin/dazzle/dgun = new(get_turf(H))
+ H.put_in_hands(dgun)
+ nif.notify("Weapon deployed!",TRUE)
+ used = TRUE
+ spawn(0)
+ uninstall()
+
+ deactivate()
+ if((. = ..()))
+ nif.clear_flag(NIF_C_HIDELASER,NIF_FLAGS_COMBAT)
+
+//The gun to go with this implant
+/obj/item/weapon/gun/energy/gun/martin/dazzle
+ name = "Microlaser"
+ desc = "A tiny nanofabricated laser."
+
+ icon = 'icons/obj/gun_vr.dmi'
+ icon_state = "PDW"
+ item_state = "gun"
diff --git a/code/modules/nifsoft/software/14_commlink.dm b/code/modules/nifsoft/software/14_commlink.dm
new file mode 100644
index 0000000000..4380c53952
--- /dev/null
+++ b/code/modules/nifsoft/software/14_commlink.dm
@@ -0,0 +1,132 @@
+///////////
+// Commlink - Has a bunch of extra stuff due to communicator defines.
+/datum/nifsoft/commlink
+ name = "Commlink"
+ desc = "An internal communicator for keeping in touch with people."
+ list_pos = NIF_COMMLINK
+ cost = 500
+ wear = 0
+ p_drain = 0.01
+
+ install()
+ if((. = ..()))
+ nif.comm = new(nif,src)
+
+ activate()
+ if((. = ..()))
+ nif.set_flag(NIF_O_COMMLINK,NIF_FLAGS_OTHER)
+ nif.comm.initialize_exonet(nif.human)
+ nif.comm.ui_interact(nif.human,key_state = commlink_state)
+ spawn(0)
+ deactivate()
+
+ deactivate()
+ if((. = ..()))
+ nif.clear_flag(NIF_O_COMMLINK,NIF_FLAGS_OTHER)
+
+ stat_text()
+ return "Show Commlink"
+
+/datum/nifsoft/commlink/Topic(href, href_list)
+ if(href_list["open"])
+ activate()
+
+/obj/item/device/communicator/commlink
+ name = "commlink"
+ desc = "An internal communicator, basically."
+ occupation = "\[Commlink\]"
+ var/obj/item/device/nif/nif
+ var/datum/nifsoft/commlink/nifsoft
+
+ New(var/newloc,var/soft)
+ ..()
+ nif = newloc
+ nifsoft = soft
+ register_device(nif.human)
+ qdel(camera) //Not supported on internal one.
+
+ Destroy()
+ if(nif)
+ nif.comm = null
+ nif = null
+ ..()
+
+//So that only the owner's chat is relayed to others.
+/obj/item/device/communicator/commlink/hear_talk(mob/living/M, text, verb, datum/language/speaking)
+ if(M != nif.human) return
+ for(var/obj/item/device/communicator/comm in communicating)
+
+ var/turf/T = get_turf(comm)
+ if(!T) return
+
+ var/icon_object = src
+
+ var/list/mobs_to_relay
+ if(istype(comm,/obj/item/device/communicator/commlink))
+ var/obj/item/device/communicator/commlink/CL = comm
+ mobs_to_relay = list(CL.nif.human)
+ icon_object = CL.nif.big_icon
+ else
+ var/list/in_range = get_mobs_and_objs_in_view_fast(T,world.view,0)
+ mobs_to_relay = in_range["mobs"]
+
+ for(var/mob/mob in mobs_to_relay)
+ //Can whoever is hearing us understand?
+ if(!mob.say_understands(M, speaking))
+ if(speaking)
+ text = speaking.scramble(text)
+ else
+ text = stars(text)
+ var/name_used = M.GetVoice()
+ var/rendered = null
+ if(speaking) //Language being used
+ rendered = "\icon[icon_object] [name_used] [speaking.format_message(text, verb)]"
+ else
+ rendered = "\icon[icon_object] [name_used] [verb], \"[text]\""
+ mob.show_message(rendered, 2)
+
+//Not supported by the internal one
+/obj/item/device/communicator/commlink/show_message(msg, type, alt, alt_type)
+ return
+
+//The silent treatment
+/obj/item/device/communicator/commlink/request(var/atom/candidate)
+ if(candidate in voice_requests)
+ return
+ var/who = null
+ if(isobserver(candidate))
+ who = candidate.name
+ else if(istype(candidate, /obj/item/device/communicator))
+ var/obj/item/device/communicator/comm = candidate
+ who = comm.owner
+ comm.voice_invites |= src
+
+ if(!who)
+ return
+
+ voice_requests |= candidate
+
+ if(ringer && nif.human)
+ nif.notify("New commlink call from [who]. (Open)")
+
+//Similar reason
+/obj/item/device/communicator/commlink/request_im(var/atom/candidate, var/origin_address, var/text)
+ var/who = null
+ if(isobserver(candidate))
+ var/mob/observer/dead/ghost = candidate
+ who = ghost
+ im_list += list(list("address" = origin_address, "to_address" = exonet.address, "im" = text))
+ else if(istype(candidate, /obj/item/device/communicator))
+ var/obj/item/device/communicator/comm = candidate
+ who = comm.owner
+ comm.im_contacts |= src
+ im_list += list(list("address" = origin_address, "to_address" = exonet.address, "im" = text))
+ else return
+
+ im_contacts |= candidate
+
+ if(!who)
+ return
+
+ if(ringer && nif.human)
+ nif.notify("Commlink message from [who]: \"[text]\" (Open)")
diff --git a/code/modules/nifsoft/software/15_misc.dm b/code/modules/nifsoft/software/15_misc.dm
index a20e26c3a6..cc8d6b8b10 100644
--- a/code/modules/nifsoft/software/15_misc.dm
+++ b/code/modules/nifsoft/software/15_misc.dm
@@ -1,9 +1,182 @@
-/*
-#define NIF_COMMLINK 23
-#define NIF_SUITSENSORS 24
-#define NIF_APCCHARGE 25
-#define NIF_PRESSURE 26
-#define NIF_HEATSINK 27
-#define NIF_UVFILTER 28
-#define NIF_FLASHPROT 29
-*/
\ No newline at end of file
+/datum/nifsoft/apc_recharge
+ name = "APC Connector"
+ desc = "A small attachment that allows synthmorphs to recharge themselves from APCs."
+ list_pos = NIF_APCCHARGE
+ cost = 1250
+ wear = 2
+ applies_to = NIF_SYNTHETIC
+ tick_flags = NIF_ACTIVETICK
+ var/obj/machinery/power/apc/apc
+
+ activate()
+ if((. = ..()))
+ var/mob/living/carbon/human/H = nif.human
+ nif.set_flag(NIF_O_APCCHARGE,NIF_FLAGS_OTHER)
+ apc = locate(/obj/machinery/power/apc) in get_step(H,H.dir)
+ if(!apc)
+ apc = locate(/obj/machinery/power/apc) in get_step(H,0)
+ if(!apc)
+ nif.notify("You must be facing an APC to connect to.",TRUE)
+ spawn(0)
+ deactivate()
+ return FALSE
+
+ H.visible_message("Thin snakelike tendrils grow from [H] and connect to \the [apc].","Thin snakelike tendrils grow from you and connect to \the [apc].")
+
+ deactivate()
+ if((. = ..()))
+ nif.clear_flag(NIF_O_APCCHARGE,NIF_FLAGS_OTHER)
+ apc = null
+
+ life()
+ if((. = ..()))
+ var/mob/living/carbon/human/H = nif.human
+ if(apc && (get_dist(H,apc) <= 1) && H.nutrition < 450)
+ H.nutrition = min(H.nutrition+10, 450)
+ apc.drain_power(7000/450*10) //This is from the large rechargers. No idea what the math is.
+ return TRUE
+ else
+ nif.notify("APC charging has ended.")
+ H.visible_message("[H]'s snakelike tendrils whip back into their body from \the [apc].","The APC connector tendrils return to your body.")
+ deactivate()
+ return FALSE
+
+/datum/nifsoft/pressure
+ name = "Pressure Seals"
+ desc = "Creates pressure seals around important synthetic components to protect them from vacuum. Almost impossible on organics."
+ list_pos = NIF_PRESSURE
+ cost = 1750
+ a_drain = 0.5
+ wear = 3
+ applies_to = NIF_SYNTHETIC
+
+ activate()
+ if((. = ..()))
+ nif.set_flag(NIF_O_PRESSURESEAL,NIF_FLAGS_OTHER)
+
+ deactivate()
+ if((. = ..()))
+ nif.clear_flag(NIF_O_PRESSURESEAL,NIF_FLAGS_OTHER)
+
+/datum/nifsoft/heatsinks
+ name = "Heat Sinks"
+ desc = "Advanced heat sinks for internal heat storage of heat on a synth until able to vent it in atmosphere."
+ list_pos = NIF_HEATSINK
+ cost = 1450
+ a_drain = 0.25
+ wear = 3
+ var/used = 0
+ tick_flags = NIF_ALWAYSTICK
+ applies_to = NIF_SYNTHETIC
+
+ activate()
+ if((. = ..()))
+ if(used >= 50)
+ nif.notify("Heat sinks not safe to operate again yet!",TRUE)
+ spawn(0)
+ deactivate()
+ return FALSE
+ nif.set_flag(NIF_O_HEATSINKS,NIF_FLAGS_OTHER)
+
+ deactivate()
+ if((. = ..()))
+ nif.clear_flag(NIF_O_HEATSINKS,NIF_FLAGS_OTHER)
+
+ life()
+ if((. = ..()))
+ //Not being used, all clean.
+ if(!active && !used)
+ return TRUE
+
+ //Being used, and running out.
+ else if(active && ++used == 100)
+ nif.notify("Heat sinks overloaded! Shutting down!",TRUE)
+ deactivate()
+
+ //Being cleaned, and finishing empty.
+ else if(!active && --used == 0)
+ nif.notify("Heat sinks re-chilled.")
+
+/datum/nifsoft/compliance
+ name = "Compliance Module"
+ desc = "A system that allows one to apply 'laws' to sapient life. Extremely illegal, of course."
+ list_pos = NIF_COMPLIANCE
+ cost = 8200
+ wear = 4
+ illegal = TRUE
+ vended = FALSE
+ access = 999 //Prevents anyone from buying it without an emag.
+ var/laws = "Be nice to people!"
+
+ New(var/newloc,var/newlaws)
+ laws = newlaws //Sanitize before this (the disk does)
+ ..(newloc)
+
+ activate()
+ if((. = ..()))
+ to_chat(nif.human,"You are compelled to follow these rules: \n[laws]")
+
+ install()
+ if((. = ..()))
+ to_chat(nif.human,"You feel suddenly compelled to follow these rules: \n[laws]")
+
+ uninstall()
+ nif.notify("ERROR! Unable to comply!",TRUE)
+ return FALSE //NOPE.
+
+ stat_text()
+ return "Show Laws"
+
+/datum/nifsoft/sizechange
+ name = "Mass Alteration"
+ desc = "A system that allows one to change their size, through drastic mass rearrangement. Causes significant wear when installed."
+ list_pos = NIF_SIZECHANGE
+ cost = 750
+ wear = 6
+
+ activate()
+ if((. = ..()))
+ var/choice = alert(nif.human,"Change which way?","Mass Alteration","Size Up","Cancel", "Size Down")
+ if(choice == "Cancel")
+ spawn(0) deactivate()
+ return FALSE
+
+ if(!nif.use_charge(100))
+ nif.notify("Insufficient energy to resize!",TRUE)
+ spawn(0) deactivate()
+ return FALSE
+
+ if(choice == "Size Up")
+ switch(nif.human.size_multiplier)
+ if(RESIZE_BIG to RESIZE_HUGE)
+ nif.human.resize(RESIZE_HUGE)
+ if(RESIZE_NORMAL to RESIZE_BIG)
+ nif.human.resize(RESIZE_BIG)
+ if(RESIZE_SMALL to RESIZE_NORMAL)
+ nif.human.resize(RESIZE_NORMAL)
+ if((0 - INFINITY) to RESIZE_TINY)
+ nif.human.resize(RESIZE_SMALL)
+
+ else if(choice == "Size Down")
+ switch(nif.human.size_multiplier)
+ if(RESIZE_HUGE to INFINITY)
+ nif.human.resize(RESIZE_BIG)
+ if(RESIZE_BIG to RESIZE_HUGE)
+ nif.human.resize(RESIZE_NORMAL)
+ if(RESIZE_NORMAL to RESIZE_BIG)
+ nif.human.resize(RESIZE_SMALL)
+ if((0 - INFINITY) to RESIZE_NORMAL)
+ nif.human.resize(RESIZE_TINY)
+
+ nif.human.visible_message("Swirling grey mist envelops [nif.human] as they change size!","Swirling streams of nanites wrap around you as you change size!")
+ nif.human.update_icons()
+
+ spawn(0)
+ deactivate()
+
+ deactivate()
+ if((. = ..()))
+ return TRUE
+
+ stat_text()
+ return "Change Size"
\ No newline at end of file
diff --git a/code/modules/organs/organ_external.dm b/code/modules/organs/organ_external.dm
index 63a1d2ff50..478ebc4d38 100644
--- a/code/modules/organs/organ_external.dm
+++ b/code/modules/organs/organ_external.dm
@@ -98,6 +98,8 @@
while(null in owner.organs)
owner.organs -= null
+ implants.Cut() //VOREStation Add - Remove these too!
+
return ..()
/obj/item/organ/external/emp_act(severity)
diff --git a/code/modules/research/designs_vr.dm b/code/modules/research/designs_vr.dm
index 07285cf921..64ed9b5d30 100644
--- a/code/modules/research/designs_vr.dm
+++ b/code/modules/research/designs_vr.dm
@@ -77,6 +77,14 @@
build_path = /obj/item/device/universal_translator/ear
sort_string = "HABBB"
+/datum/design/item/nif
+ name = "nanite implant framework"
+ id = "nif"
+ req_tech = list(TECH_MAGNET = 5, TECH_BLUESPACE = 5, TECH_MATERIAL = 5, TECH_ENGINEERING = 5, TECH_DATA = 5)
+ materials = list(DEFAULT_WALL_MATERIAL = 5000, "glass" = 8000, "uranium" = 6000, "diamond" = 6000)
+ build_path = /obj/item/device/nif
+ sort_string = "HABBC"
+
// Resleeving Circuitboards
/datum/design/circuit/transhuman_clonepod
diff --git a/code/modules/resleeving/implant.dm b/code/modules/resleeving/implant.dm
index d5e2dfac6a..e6dd47b705 100644
--- a/code/modules/resleeving/implant.dm
+++ b/code/modules/resleeving/implant.dm
@@ -49,7 +49,8 @@
BITSET(H.hud_updateflag, BACKUP_HUD)
//Okay we've got a mind at least
if(H == imp_in && H.mind && H.stat < DEAD)
- transcore.m_backup(H.mind)
+ transcore.m_backup(H.mind,H.nif)
+ persist_nif_data(H)
spawn(attempt_delay)
backup()
diff --git a/code/modules/resleeving/infocore.dm b/code/modules/resleeving/infocore.dm
index e03e3291a9..dc0b06d8bd 100644
--- a/code/modules/resleeving/infocore.dm
+++ b/code/modules/resleeving/infocore.dm
@@ -40,7 +40,7 @@ var/datum/transhuman/infocore/transcore = new/datum/transhuman/infocore
spawn(process_time)
process()
-/datum/transhuman/infocore/proc/m_backup(var/datum/mind/mind)
+/datum/transhuman/infocore/proc/m_backup(var/datum/mind/mind,var/obj/item/device/nif/nif)
ASSERT(mind)
if(!mind.name || core_dumped)
return 0
@@ -50,6 +50,20 @@ var/datum/transhuman/infocore/transcore = new/datum/transhuman/infocore
if(mind.name in backed_up)
MR = backed_up[mind.name]
MR.last_update = world.time
+ if(nif)
+ MR.nif_path = nif.type
+ MR.nif_durability = nif.durability
+ var/list/nifsofts = list()
+ for(var/N in nif.nifsofts)
+ if(N)
+ var/datum/nifsoft/nifsoft = N
+ nifsofts += nifsoft.type
+ MR.nif_software = nifsofts
+ else
+ MR.nif_path = null
+ MR.nif_durability = null
+ MR.nif_software = null
+
else
MR = new(mind, mind.current, 1)
@@ -114,6 +128,10 @@ var/datum/transhuman/infocore/transcore = new/datum/transhuman/infocore
var/languages
var/mind_oocnotes
+ var/nif_path
+ var/nif_durability
+ var/list/nif_software
+
/datum/transhuman/mind_record/New(var/datum/mind/mind,var/mob/living/carbon/human/M,var/obj/item/weapon/implant/backup/imp,var/add_to_db = 1)
ASSERT(mind && M && imp)
diff --git a/code/modules/resleeving/machines.dm b/code/modules/resleeving/machines.dm
index abab41e929..4216f6fb0a 100644
--- a/code/modules/resleeving/machines.dm
+++ b/code/modules/resleeving/machines.dm
@@ -504,6 +504,12 @@
occupant.ooc_notes = MR.mind_oocnotes
occupant.apply_vore_prefs() //Cheap hack for now to give them SOME bellies.
+ //Re-supply a NIF if one was backed up with them.
+ if(MR.nif_path)
+ var/obj/item/device/nif/nif = new MR.nif_path(occupant,MR.nif_durability)
+ for(var/path in MR.nif_software)
+ new path(nif)
+
// If it was a custom sleeve (not owned by anyone), update namification sequences
if(!occupant.original_player)
occupant.real_name = occupant.mind.name
diff --git a/code/modules/vore/persist/persist_vr.dm b/code/modules/vore/persist/persist_vr.dm
index 977473812a..bddd9bc7b9 100644
--- a/code/modules/vore/persist/persist_vr.dm
+++ b/code/modules/vore/persist/persist_vr.dm
@@ -78,6 +78,10 @@
WARNING("persist_interround_data failed to prep [occupant] for persisting")
return
+ //This one doesn't rely on persistence prefs
+ if(ishuman(occupant) && occupant.stat != DEAD)
+ persist_nif_data(occupant, prefs)
+
if(!prefs.persistence_settings)
return // Persistence disabled by preference settings
@@ -94,6 +98,7 @@
if(prefs.persistence_settings & PERSIST_WEIGHT)
resolve_excess_nutrition(H)
prefs.weight_vr = H.weight
+
prefs.save_character()
// Saves mob's current coloration state to prefs
@@ -212,7 +217,30 @@
* towards future shenanigans such as upgradable NIFs or different types or things of that nature,
* without invoking the need for a bunch of different save file variables.
*/
-/proc/persist_nif_data(var/mob/living/carbon/human/H)
+/proc/persist_nif_data(var/mob/living/carbon/human/H,var/datum/preferences/prefs)
if(!istype(H))
CRASH("persist_nif_data given a nonhuman: [H]")
+ if(!prefs)
+ prefs = prep_for_persist(H)
+
+ if(!prefs)
+ WARNING("persist_nif_data failed to prep [H] for persisting")
+ return
+
+ var/obj/item/device/nif/nif = H.nif
+
+ if(nif)
+ prefs.nif_path = nif.type
+ prefs.nif_durability = nif.durability
+ else
+ prefs.nif_path = null
+ prefs.nif_durability = null
+
+ var/datum/category_group/player_setup_category/vore_cat = prefs.player_setup.categories_by_name["VORE"]
+ var/datum/category_item/player_setup_item/vore/nif/nif_prefs = vore_cat.items_by_name["NIF Data"]
+
+ var/savefile/S = new /savefile(prefs.path)
+ if(!S) WARNING ("Couldn't load NIF save savefile? [prefs.real_name]")
+ S.cd = "/character[prefs.default_slot]"
+ nif_prefs.save_character(S)
diff --git a/icons/obj/vending_vr.dmi b/icons/obj/vending_vr.dmi
index b6c7cbb8e6..e7f1f48d32 100644
Binary files a/icons/obj/vending_vr.dmi and b/icons/obj/vending_vr.dmi differ
diff --git a/maps/tether/tether-01-surface.dmm b/maps/tether/tether-01-surface.dmm
index a9046f15e7..564f67fe31 100644
--- a/maps/tether/tether-01-surface.dmm
+++ b/maps/tether/tether-01-surface.dmm
@@ -1152,7 +1152,7 @@
"awh" = (/obj/structure/cable{d1 = 1; d2 = 2; icon_state = "1-2"; pixel_y = 0},/obj/structure/disposalpipe/segment{dir = 4},/turf/simulated/floor/tiled,/area/tether/surfacebase/atrium_one)
"awi" = (/obj/effect/floor_decal/borderfloor{dir = 4},/obj/machinery/atmospherics/pipe/simple/hidden/supply,/obj/structure/cable{d1 = 1; d2 = 2; icon_state = "1-2"; pixel_y = 0},/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/obj/structure/disposalpipe/junction{dir = 1; icon_state = "pipe-j2"},/obj/effect/floor_decal/industrial/danger{dir = 4},/obj/effect/floor_decal/steeldecal/steel_decals7{dir = 9},/obj/effect/floor_decal/steeldecal/steel_decals7{dir = 10},/turf/simulated/floor/tiled,/area/tether/surfacebase/atrium_one)
"awj" = (/obj/machinery/status_display{pixel_y = 30},/obj/machinery/vending/fitness,/obj/effect/floor_decal/corner/lightgrey{dir = 9},/obj/effect/floor_decal/corner/lightgrey{dir = 6},/turf/simulated/floor/tiled/monotile,/area/crew_quarters/locker/laundry_arrival)
-"awk" = (/obj/machinery/vending/cola,/obj/effect/floor_decal/corner/lightgrey{dir = 9},/obj/effect/floor_decal/corner/lightgrey{dir = 6},/turf/simulated/floor/tiled/monotile,/area/crew_quarters/locker/laundry_arrival)
+"awk" = (/obj/structure/disposalpipe/segment{dir = 4},/obj/effect/floor_decal/borderfloor{dir = 1},/obj/effect/floor_decal/corner/lightgrey/border{dir = 1},/obj/effect/floor_decal/steeldecal/steel_decals7{dir = 4},/obj/effect/floor_decal/steeldecal/steel_decals7,/obj/machinery/vending/nifsoft_shop,/turf/simulated/floor/tiled,/area/tether/surfacebase/atrium_three)
"awl" = (/obj/machinery/vending/snack,/obj/effect/floor_decal/corner/lightgrey{dir = 9},/obj/effect/floor_decal/corner/lightgrey{dir = 6},/turf/simulated/floor/tiled/monotile,/area/crew_quarters/locker/laundry_arrival)
"awm" = (/obj/machinery/camera/network/civilian,/obj/machinery/vending/coffee,/obj/effect/floor_decal/corner/lightgrey{dir = 9},/obj/effect/floor_decal/corner/lightgrey{dir = 6},/turf/simulated/floor/tiled/monotile,/area/crew_quarters/locker/laundry_arrival)
"awn" = (/obj/effect/floor_decal/borderfloor{dir = 9},/obj/effect/floor_decal/corner/lightgrey/border{dir = 9},/turf/simulated/floor/tiled,/area/crew_quarters/locker/laundry_arrival)
@@ -4237,7 +4237,7 @@
"bDy" = (/obj/machinery/atmospherics/pipe/simple/hidden/supply,/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/obj/structure/disposalpipe/segment,/obj/effect/floor_decal/steeldecal/steel_decals4{dir = 4},/obj/effect/floor_decal/steeldecal/steel_decals4{dir = 9},/turf/simulated/floor/tiled,/area/tether/surfacebase/atrium_three)
"bDz" = (/obj/structure/cable{icon_state = "1-2"},/obj/effect/floor_decal/borderfloor{dir = 1},/obj/effect/floor_decal/corner/lightgrey/border{dir = 1},/obj/effect/floor_decal/borderfloor/corner2{dir = 4},/obj/effect/floor_decal/corner/lightgrey/bordercorner2{dir = 4},/obj/effect/floor_decal/steeldecal/steel_decals7{dir = 4},/obj/effect/floor_decal/steeldecal/steel_decals7,/turf/simulated/floor/tiled,/area/tether/surfacebase/atrium_three)
"bDA" = (/obj/structure/disposalpipe/segment{dir = 4; icon_state = "pipe-c"},/obj/machinery/firealarm{dir = 2; layer = 3.3; pixel_x = 0; pixel_y = 26},/obj/effect/floor_decal/borderfloor{dir = 1},/obj/effect/floor_decal/corner/lightgrey/border{dir = 1},/obj/effect/floor_decal/steeldecal/steel_decals7{dir = 4},/obj/effect/floor_decal/steeldecal/steel_decals7,/turf/simulated/floor/tiled,/area/tether/surfacebase/atrium_three)
-"bDB" = (/obj/structure/disposalpipe/segment{dir = 4},/obj/effect/floor_decal/borderfloor{dir = 1},/obj/effect/floor_decal/corner/lightgrey/border{dir = 1},/obj/effect/floor_decal/steeldecal/steel_decals7{dir = 4},/obj/effect/floor_decal/steeldecal/steel_decals7,/turf/simulated/floor/tiled,/area/tether/surfacebase/atrium_three)
+"bDB" = (/obj/effect/floor_decal/corner/lightgrey{dir = 9},/obj/effect/floor_decal/corner/lightgrey{dir = 6},/obj/machinery/vending/nifsoft_shop,/turf/simulated/floor/tiled/monotile,/area/crew_quarters/locker/laundry_arrival)
"bDC" = (/obj/machinery/disposal,/obj/structure/disposalpipe/trunk{dir = 8},/obj/effect/floor_decal/borderfloor{dir = 5},/obj/effect/floor_decal/corner/lightgrey/border{dir = 5},/turf/simulated/floor/tiled,/area/tether/surfacebase/atrium_three)
"bDD" = (/obj/structure/cable/green{d1 = 1; d2 = 2; icon_state = "1-2"},/obj/machinery/atmospherics/pipe/simple/hidden/supply,/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/turf/simulated/floor/tiled/dark,/area/tcomsat{name = "\improper Telecomms Lobby"})
"bDE" = (/obj/machinery/atmospherics/unary/vent_pump/on{dir = 1},/obj/machinery/porta_turret{dir = 6},/turf/simulated/floor/tiled/dark,/area/tcomsat{name = "\improper Telecomms Lobby"})
@@ -5455,7 +5455,7 @@ aaaaadaadaadaadaadaadaadaadaadaagaagamGasrassastasuasuasuasuasuasuasvaswasxasyas
aaaaadaadaadaadaadaadaadaadaadaagamGamGamGamGatcaoZaoZatdateatfatgathaoZatiatjatkatlatmatnatoatparpatqatratratratratsasLattatuatvatwatxatxatxatyasLatzatAasOatBatCatCatCatCatCatCagIagIagIaagaagaagaagaagaagaagagIatDatEatFatGatGatGatGatGatHatGatGatGatGatGatGatIatJanRatKaoTaoTatLatMatNatOaoTaoWaslanRaagapCapCapCapCatPataaqWaqnaqoarCarBarCarCarCarCarBarCaqrapCaaa
aaaaadaadaadaadaadaadaadaadaagaagamGatQatRatSatTaoZaquaotatQamGatUatVatWamGatXatYarparparparparparpatZauaauaauaauaauaasLaubaucaudaueaufaufaufaugasLbeQauibeSaukatCaulaulaulaulatCaumaunagIagIagIagIauoagIagIagIagIaupakuauqaurausautautauuauvauwauxautautautauyajPajQanRauzauzauAauBauCauDauEauFauGauHanRaagapCauIauIapCauJataaqWaqnaqoarCarBarCarCarCarCarBarCaqrapCaaa
aaaaadaadaadaadaadaadaadaadaagaagamGauKaoZaoZatTaoZauLaotauMamGatUauNauOamGauPauQbfxarXarXarXarXauSauTauaauUauVauWauXauYauZavaavbavcavdavdavdaveasLaZkavgasOavhatCaulaulaulaulaulaviavjavkavlavmavnavnavoavpavqavravsavtavuagIagIavvavvavvavvavvavvavwavvavvavxajPajQanRanRanRanRavyavzanRanRanRanRanRavAagIapCavBavBapCavCavDaqWaqnaqoarCarBarCarCarCarCarBarCaqYapCaaa
-aaaaadaadaadaadaadaadaadaadaadaagamGavEavFavGavHbhtavJaotatQamGavKavLavMamGavNavOauaauaauaauaauaauaauaauaavPavQavRavSavTavUavVavWavXavYavYavZawaasLawbavgasOavhatCaulaulaulaulaulaviaviaviawcaviaviawdaviaweatGawfawgawhawiamgagXavvawjawkawlawmawnawoawpavvawqawrawsawtawuawvawwawxawyawzawAawBawCawDawEawuawFawGawGawHawIawJaqWaqnaqoarCarBarCarCarCarCarBarCaqrapCaaa
+aaaaadaadaadaadaadaadaadaadaadaagamGavEavFavGavHbhtavJaotatQamGavKavLavMamGavNavOauaauaauaauaauaauaauaauaavPavQavRavSavTavUavVavWavXavYavYavZawaasLawbavgasOavhatCaulaulaulaulaulaviaviaviawcaviaviawdaviaweatGawfawgawhawiamgagXavvawjbDBawlawmawnawoawpavvawqawrawsawtawuawvawwawxawyawzawAawBawCawDawEawuawFawGawGawHawIawJaqWaqnaqoarCarBarCarCarCarCarBarCaqrapCaaa
aaaaadaadaadaadaadaadaadaadaadaagawKawKawKawKawKawKawKawKawKawKawKawKawKawKawLavOauaawMawNawOawPawQawRawSawTawUawVawWauYawXawYawZaxaaxbaxcaxdaxeblMaxgblPasOavhatCaxiaulaulaulatCaxjaxkaxlaxmaxnaxoaxpaxqaxraxsaxtaxuaxvaxwagIagIavvaxxaxyaxzaxzaxAbZmaxCaxDaxEasRawgaxFatGatGaxGatGaxHaxIatGatGawgatGatGaxJaxKaxLaxMaxNaxKaxOaqWaqnaqoarCarBarCarCarCarCarBarCaqratbaaa
aaaaadaadaadaadaadaadaadaadaadaagarpaxPaxQaxRaxRaxSaxRaxTaxUaxRaxRaxUaxRbphaxWavOauaaxXaxYaxZayaaybayaayaayaayaaycaydauYayeayfaygayhayiayjaykaylasLakYaymasObpiatCatCatCatCatCatCayoaypagIagIagIagIagIagIayqagIagIagIagIagIagIaagavvayraysaytayuayvaywayxayyayzayAayBayCayDayEayFayDayGayHayHayIayJayKayHayHayLayMayNayOayPayQaqWaqnaqoarCarBarCarCarCarCarBarCaqrapCaaa
aaaaadaadaadaadaadaadaadaadaadaagarpayRaySayTayTayTayTayTayTayTayTayTayTayTayUayVauaayWawUayXayYayZayZayZazaazbazcazdasLasLasLauYauYazeasLasLasLasLaagazfasOasOasPasPasPasPasPasPagIagIagIaagaagaagaagazgazhazgaagaagaagaagaagaagavvaziazjazkazkavvazlazmavvagIagIaznagIagIagIagIagIazoagIagIagIagIagIauoagIapCazpazpapCavCazqaqWaqnaqoarCarBarCarCarCarCarBarCaqYapCaaa
@@ -5687,7 +5687,7 @@ boYboZboZboZboZboZboZboZboZboZboZboZboZbpabpabpabpabpabpabpabpabpabpabpabpabpabp
boYboZboZboZboZboZboZboZboZboZboZboZboZbpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabqfbzhbCibAWbBFbBGbBGbBHbBHbBHbBHbBHbBIbBJbAFbAGbCjbznbCkbClbClbCmbCnbAHbCobznbpabpabpabpabzrbBnbCpbzrbCqbCqbzrbCrbBqbzwbAhbAibAibAibAibAibAibAibAibAibAibAibAibAibAibAibAjbvEbCsbCtbCubCubCubCvbCubCwbCxbCwbCwbCwbCwbCybCybCybCybpabpaboZboZboZboZboZboZboZboZboZboZboZboZboY
boYboZboZboZboZboZboZboZboZboZboZboZboZbpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabzhbCzbAWbBFbBGbBGbCAbzObCBbBHbBHbBIbBJbAFbAGbCCbznbCDbCEbCFbCGbCHbAHbCIbznbpabpabpabpabzrbCJbCKbzrbCqbCqbzrbCLbBqbzwbAhbAibAibAibAibAibAibAibAibAibAibAibAibAibAibAibAjbvEbBUbCMbCNaMZaNybCQbCRbCSbCTbCUbCVbCWbCSbCXbCYbCZbDabpabpabpabpaboZboZboZboZboZboZboZboZboZboZboY
boYboZboZboZboZboZboZboZboZboZboZboZboZbpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabzGbAVbAWbBFbBGbBGbDbbDcbDdbBHbBHbBIbBJbAFbAGbCjbznbznbznbznbDebDfbzobznbznbwObwOaHnaHnbzrbzrbzrbzrbzrbzrbzrbDgbDhbzwbIIbAibAibAibAibAibAibAibAibAibAibAibAibAibAibAibAjbvEbDibDjbDkbDlbDmbDnbDobCSbDpbCTbCTbDqbCSbDrbDsbDtbDabpabpabpabpaboZboZboZboZboZboZboZboZboZboZboY
-boYboZboZboZboZboZboZboZboZboZboZboZbpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabzGbDubDvbBFbBGbBGbBGbBGbBGbBHbBHbBIbBJbAFbAGbAGbzibDwbwVbDxbDybDzbxbbxobDAbDBbDCaHnbmbbdqbaMaHobwsaPTaPFbwsbDIbBqaQRaQQbAhbAibAibAibAibAibAibAibAibAibAibAibAibAibAibAjbvEbBUbDJbwsbwsbDKbDLbDMbCSbDNbCTbCTbDObCSbDsbDsbDPbDabpabpabpabpaboZboZboZboZboZboZboZboZboZboZboY
+boYboZboZboZboZboZboZboZboZboZboZboZbpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabzGbDubDvbBFbBGbBGbBGbBGbBGbBHbBHbBIbBJbAFbAGbAGbzibDwbwVbDxbDybDzbxbbxobDAawkbDCaHnbmbbdqbaMaHobwsaPTaPFbwsbDIbBqaQRaQQbAhbAibAibAibAibAibAibAibAibAibAibAibAibAibAibAjbvEbBUbDJbwsbwsbDKbDLbDMbCSbDNbCTbCTbDObCSbDsbDsbDPbDabpabpabpabpaboZboZboZboZboZboZboZboZboZboZboY
boYboZboZboZboZboZboZboZboZboZboZboZbpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabzGbAVbDQbBFbBGbBGbBGbBGbBGbBHbBHbBIbDRbDSbDTbDUbDVbDWbyobyobDXbDYbyxbyxbDZbygbEaaHnbmmbmlbmlbmjbwsbEebEfbEgbEhbEiaRsaRrbAhbAibAibAibAibAibAibAibAibAibAibAibAibAibAibAjbEkbElbEmbEnbEobEpbEqaRGbCSbEsbEtbEubEvbCSbEwbExbDsbDabpabpabpabpaboZboZboZboZboZboZboZboZboZboZboY
boYboZboZboZboZboZboZboZboZboZboZboZbpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabzhbEybDQbEzbEAbEAbEAbEAbEAbEBbEBbECbEDbEEbEFbEGbEHbEIbEJbEJbEJbEKbELbEMbENbEObEPbmtbmwbmvbmvbmubwsaRZbDHbwsbEVbzvaSdaSabAhbAibAibAibAibAibAibAibAibAibAibAibAibAibAibAjbEXbBUbEYbEZbwsaSCbCQaSBbCSbFcbFdbFebFfbCSbFgbFhbFibDabpabpabpabpaboZboZboZboZboZboZboZboZboZboZboY
boYboZboZboZboZboZboZboZboZboZboZboZbpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabpabzhbFjbFkbFlbFlbFlbFlbFlbFlbFlbFlbFlbFmbFnbFobFpbzhbwsbzZbzZbzZbwsbwsbvEbyVbFqbFraHnbmbbmybmxaHobwsbwsbwsbwsbFubzvaTeaTabAhbAibAibAibAibAibAibAibAibAibAibAibAibAibAibAjbEXbBUbFvbDkbDkbDkaTybDkbCSbCSbFxbFybCSbCSbFzbFAbFzbFBbpabpabpabpaboZboZboZboZboZboZboZboZboZboZboY
diff --git a/maps/tether/tether-03-station.dmm b/maps/tether/tether-03-station.dmm
index 221a24b85b..3c0a2abc28 100644
--- a/maps/tether/tether-03-station.dmm
+++ b/maps/tether/tether-03-station.dmm
@@ -2680,7 +2680,7 @@
"aZB" = (/obj/machinery/door/firedoor/glass,/obj/structure/disposalpipe/segment,/obj/machinery/door/blast/regular{density = 0; dir = 4; icon_state = "pdoor0"; id = "englockdown"; name = "Engineering Lockdown"; opacity = 0},/obj/machinery/door/airlock/glass_engineering{name = "Engineering Mezzenine"; req_one_access = list()},/turf/simulated/floor/tiled/steel_grid,/area/engineering/foyer_mezzenine)
"aZC" = (/obj/machinery/door/airlock/maintenance/engi,/obj/structure/cable{icon_state = "1-2"},/obj/machinery/atmospherics/pipe/simple/hidden/supply,/obj/machinery/atmospherics/pipe/simple/hidden/scrubbers,/obj/machinery/door/firedoor/glass,/turf/simulated/floor/plating,/area/hallway/station/port)
"aZD" = (/obj/machinery/vending/snack,/obj/effect/floor_decal/corner/lightgrey{dir = 9},/obj/effect/floor_decal/corner/lightgrey{dir = 6},/obj/effect/floor_decal/steeldecal/steel_decals9,/obj/effect/floor_decal/steeldecal/steel_decals9{dir = 4},/obj/effect/floor_decal/steeldecal/steel_decals9{dir = 1},/obj/effect/floor_decal/steeldecal/steel_decals9{dir = 8},/turf/simulated/floor/tiled,/area/hallway/station/port)
-"aZE" = (/obj/machinery/vending/cola,/obj/effect/floor_decal/corner/lightgrey{dir = 9},/obj/effect/floor_decal/corner/lightgrey{dir = 6},/obj/effect/floor_decal/steeldecal/steel_decals9,/obj/effect/floor_decal/steeldecal/steel_decals9{dir = 4},/obj/effect/floor_decal/steeldecal/steel_decals9{dir = 1},/obj/effect/floor_decal/steeldecal/steel_decals9{dir = 8},/turf/simulated/floor/tiled,/area/hallway/station/starboard)
+"aZE" = (/obj/effect/floor_decal/corner/lightgrey{dir = 9},/obj/effect/floor_decal/corner/lightgrey{dir = 6},/obj/effect/floor_decal/steeldecal/steel_decals9,/obj/effect/floor_decal/steeldecal/steel_decals9{dir = 4},/obj/effect/floor_decal/steeldecal/steel_decals9{dir = 1},/obj/effect/floor_decal/steeldecal/steel_decals9{dir = 8},/obj/machinery/vending/nifsoft_shop,/turf/simulated/floor/tiled,/area/hallway/station/starboard)
"aZF" = (/obj/machinery/vending/coffee,/obj/effect/floor_decal/corner/lightgrey{dir = 9},/obj/effect/floor_decal/corner/lightgrey{dir = 6},/obj/effect/floor_decal/steeldecal/steel_decals9,/obj/effect/floor_decal/steeldecal/steel_decals9{dir = 4},/obj/effect/floor_decal/steeldecal/steel_decals9{dir = 1},/obj/effect/floor_decal/steeldecal/steel_decals9{dir = 8},/turf/simulated/floor/tiled,/area/hallway/station/starboard)
"aZG" = (/turf/simulated/wall/r_wall,/area/hallway/station/starboard)
"aZH" = (/obj/structure/cable{d1 = 1; d2 = 2; icon_state = "1-2"; pixel_y = 0},/obj/effect/floor_decal/borderfloor{dir = 8},/obj/effect/floor_decal/corner/lightgrey/border{dir = 8},/obj/effect/floor_decal/steeldecal/steel_decals7{dir = 5},/obj/effect/floor_decal/steeldecal/steel_decals7{dir = 6},/turf/simulated/floor/tiled,/area/hallway/station/starboard)
diff --git a/maps/tether/tether-06-colony.dmm b/maps/tether/tether-06-colony.dmm
index 9c04bd82cf..45192898b2 100644
--- a/maps/tether/tether-06-colony.dmm
+++ b/maps/tether/tether-06-colony.dmm
@@ -1363,6 +1363,8 @@
"Ak" = (/obj/structure/table/reinforced,/obj/item/device/camera,/obj/item/weapon/storage/box/ids,/obj/effect/floor_decal/borderfloorblack{dir = 8},/obj/effect/floor_decal/corner/blue/border{dir = 8},/obj/effect/floor_decal/borderfloorblack/corner2{dir = 10},/obj/effect/floor_decal/corner/blue/bordercorner2{dir = 10},/turf/unsimulated/floor{icon_state = "vault"; dir = 5},/area/centcom/security{name = "\improper CentCom Residential Security"})
"Al" = (/obj/structure/table/reinforced,/obj/machinery/computer/skills,/obj/effect/floor_decal/borderfloorblack{dir = 4},/obj/effect/floor_decal/corner/blue/border{dir = 4},/obj/effect/floor_decal/borderfloorblack/corner2{dir = 5},/obj/effect/floor_decal/corner/blue/bordercorner2{dir = 5},/turf/unsimulated/floor{icon_state = "vault"; dir = 5},/area/centcom/security{name = "\improper CentCom Residential Security"})
"Am" = (/obj/effect/floor_decal/borderfloorblack,/obj/effect/floor_decal/corner/blue/border,/obj/effect/floor_decal/borderfloorblack/corner2{dir = 9},/obj/effect/floor_decal/borderfloorblack/corner2,/obj/effect/floor_decal/corner/blue/bordercorner2{dir = 9},/obj/effect/floor_decal/corner/blue/bordercorner2,/turf/unsimulated/floor{icon_state = "vault"; dir = 5},/area/centcom/security{name = "\improper CentCom Residential Security"})
+"An" = (/obj/effect/floor_decal/borderfloor,/obj/effect/floor_decal/corner/blue/border,/obj/machinery/vending/nifsoft_shop,/turf/unsimulated/floor/steel,/area/centcom/security)
+"Ao" = (/obj/machinery/vending/nifsoft_shop,/turf/unsimulated/floor/steel{icon = 'icons/turf/floors_vr.dmi'; icon_state = "wood"},/area/centcom/restaurant)
(1,1,1) = {"
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
@@ -1441,7 +1443,7 @@ aaababababababababababababababababababababababababababjMkikhkhkikjkkjMababababab
aaababababababababababababababababababababababababababjMlLkhkhkikjkkjMababababababacgGgGgGgGgGgGgGnHnInJnKnLhrhrnMnNnOkHnPnQnRlUlUnSnTlUnSnTlUlUnUnVnWkHltlololololonXnYnZlololololooakHahahahejobocodoeofogmdmdejkJhlhlhlhlnEohnFohnFnFoinGkKeS
aaabababababababababababababababojokokokokokokokokokokolomononomomomomababababababacacacacacacacgGgGgGgGhthtoohrgGgGgGkHkHkHkHopoqkHkHorkHkHosoqkHkHkHotototototototototototototototototahahahouejejejejejovjKjKejkJhlkFkDkEhlowowowowowowhlkKeS
aaabababababababababababababababomoxoyozoyoyoyoyozoyoAoBoCoCoCoDoEabababababababababacacacacacacgGoFoGoHoGoIhrhroJoGoKkHoLoMoNlUlUoOoPlUoQoRlUlUoSoToLotoUoVoWoXotoYoZpaoZoYotpbpbpbpbotahahahpclGkekelHkekdhlhlkclFhlkKejkJhlhlhlhlhlhlhlhlkKeS
-aaabababababababababababababababomoxoyfyeWeWeWeWfyoyoAoBoCoCoCpfoEabababababababababacacacacacacgGpglPhrlPphhrhrhrhrpikHpjlUlUlUlUpklTnwlXpllUlUlUlUpmotoUpnoWoXotpopopopopootppppppppotahahahpckFkChlkBkDkDkChlkBkChlpqejprkDkDkDkEkFkDpsptpueS
+aaabababababababababababababababomoxoyfyeWeWeWeWfyoyoAoBoCoCoCpfoEabababababababababacacacacacacgGpglPhrlPphhrhrhrhrpikHpjlUlUlUlUpklTnwlXpllUlUlUlUpmotoUpnoWoXotpopopopopootppppppppotahahahpckFkChlkBkDkDkChlkBkChlpqejprAnkDkDkEkFkDpsptpueS
aaabababababababababababababababomoxfyfTfRhchcipfTfyoAoBoCoCoCpzoEabababababababababacacacacacacgGpAmkpBpCmkhrhrhrhrpDkHpjlUlUlUlUpEpFpFpFpGlUlUlUlUpHotoUpnoWpnpIpopopopopopJpopopopKotahahahouejejpcejejejejpLejejpcejejpMpNpNpNlFlGpNpNpNpOeS
aaabababababababababababababababompPeWpdkMhchckMpeeWoAoBoCoCoCpzoEabababababababababacacacacacacgGmKhrhrhrhrhrhrhrhrpTkHpjlUlUlUlUlUlUlUlUlUlUlUlUlUpmotoUpnpnpnpIpopopopopopJpopopopKotbHahahjJpUpVhlpWpXjJsfhlpvpwhlqbejqcqdqdqdhlhlqdqdqdqeeS
aaabababababababababababababababomoxeWpxpxhchcpxpxeWoAoBoCoCoCpzoEabababababababababababacacacacgGmKhrhrhrhrhrhrhrhrqgkHpjlUlUlUlUlUlUlUlUlUlUlUlUlUpmotoUpnpnqhotpoqipoqipootqjqjqjqjotahahahjJqknGhlhlqlqmqnhlhlqoqpqqejqrqsqsqsqsqsqsqsqsqteS
@@ -1452,7 +1454,7 @@ aaabababababababababababababababomoxqfpxpxhchcpxpxqfoAoBoCoCahrKahahahahahahahah
aaabababababababababababababababomoxqfipiphchcipipqfoAoBoCoCrTrKahahahahahahahahahahahahahahahahahrpahahahahahahahahahahahahahahahahahahahahahahahahahahahahahahahahahahahahahahahahahjDahahahjDahahahahahrLahrMrMrMrMahrNrzrzrzrzrOrUrQrRrRrSrI
aaabababababababababababababababomoxeWqvkMhchckMqweWoAoBoCoCadriahahahahahahahahahrMrMrMrMrMrMahahqUahahahahahahahahahahahahahahahahahahahahahahahahahahahahahahahahahahahahahahahahahjDahahahjDahahahahahrXrYrYrYrYrYrYrZrzrzrzrzrCrDsasbsbscrI
aaabababababababababababababababomoxfyqVpxhchcpxrgfyoAoBoCoCqWqXqXqXqXqXqXqXqXqXqXqXqXqXrhrVrJrJrVrhqXqXqXqXshjDqXqXshjDqXqXqXqXsisjsksjsisislslsisislslsijDjDjDotototsmototototslslslsijDjDjDejejejejejejousnsnsnsnsnsnouejejejejejejejejejejeS
-aaabababababababababababababababquoxpyhchchchchchcpQoAoBoCoCoCoDoEabababababababababababrhsdrWsesdrhsrsrsraiahahssstahahaisrsrsrsisisisisisusvswsxswsyszsisAahahotsBsCsDsDsEsFotsGsHsIslahahahejsJsKsKsKsLejhlpYhlhlpYhlejsMsNsOsPsQejsRsSsTsUeS
+aaabababababababababababababababquoxpyhchchchchchcpQoAoBoCoCoCoDoEabababababababababababrhsdrWsesdrhsrsrsraiahahssstahahaisrsrsrsisisisisisusvAosxswsyszsisAahahotsBsCsDsDsEsFotsGsHsIslahahahejsJsKsKsKsLejhlpYhlhlpYhlejsMsNsOsPsQejsRsSsTsUeS
aaabababababababababababababababomoxsopxhcipiphcspsooAoBoCoCoCpzoEabababababababababababrhsVsqsXsWrhahtbtctctctctctctctctctctdahsiszsytetfswswswswswswswsiahahahottgsDsDthototottitjtkslahahahejtltmtmtmtnejejovtotoovejejtpfcfcfcfctqfcfcfctreS
aaabababababababababababababababompPsosYhcipiphcsZsooAoBoCoCoCpzoEabababababababababababrhtstatattrhahtwtxtxtxtytxtxtytxtxtxtzahsiswswtAtAswswswtAtAswswsiahahahottgsDsDsDtBsFottCtDtEslahahahejtFfcfcfcfctGtHtIfcfctJtKtLfcfcfcfctMejtNfcfctreS
aaabababababababababababababababomoxfyfTtupxtvhcfTfyoAoBoCoCoCpzoEabababababababababababrhtPtOuHtQrhtTtwtxtUtVtVtVtVtVtVtUtxtztWslswtXtYtZuaswtXubucuaswslahahahotudsDsDthotototuetDtEsiahahahejufugfcuhuiujukulfcfculumunuoupfcuqurejusututuueS
diff --git a/maps/tether/tether-07-misc.dmm b/maps/tether/tether-07-misc.dmm
index 9f4b9f4d35..c1189bb371 100644
--- a/maps/tether/tether-07-misc.dmm
+++ b/maps/tether/tether-07-misc.dmm
@@ -930,7 +930,7 @@
"rT" = (/obj/structure/table/rack,/obj/item/device/radio,/obj/item/device/radio,/obj/item/device/radio,/obj/item/device/radio,/obj/item/device/radio,/obj/effect/floor_decal/borderfloorblack,/turf/unsimulated/floor{dir = 2; icon_state = "dark"},/area/antag/antag_base)
"rU" = (/obj/structure/table/rack,/obj/item/weapon/pinpointer/nukeop,/obj/item/weapon/pinpointer/nukeop,/obj/item/weapon/pinpointer/nukeop,/obj/item/weapon/pinpointer/nukeop,/obj/item/weapon/pinpointer/nukeop,/obj/item/weapon/pinpointer/nukeop,/obj/effect/floor_decal/borderfloorblack,/turf/unsimulated/floor{dir = 2; icon_state = "dark"},/area/antag/antag_base)
"rV" = (/obj/structure/table/rack,/obj/item/device/flashlight/flare,/obj/item/device/flashlight/flare,/obj/item/device/flashlight/flare,/obj/item/device/flashlight/flare,/obj/item/device/flashlight/flare,/obj/item/device/flashlight/flare,/obj/item/device/flashlight/flare,/obj/effect/floor_decal/borderfloorblack{dir = 6},/turf/unsimulated/floor{dir = 2; icon_state = "dark"},/area/antag/antag_base)
-"rW" = (/obj/effect/floor_decal/borderfloorblack{dir = 8},/turf/unsimulated/floor{dir = 2; icon_state = "dark"},/area/antag/antag_base)
+"rW" = (/obj/effect/floor_decal/borderfloorblack{dir = 8},/obj/machinery/vending/nifsoft_shop{categories = 111; emagged = 1; name = "Hacked NIFSoft Shop"; prices = list()},/turf/unsimulated/floor{dir = 2; icon_state = "dark"},/area/antag/antag_base)
"rX" = (/obj/machinery/telecomms/allinone{intercept = 1},/turf/unsimulated/floor{dir = 2; icon_state = "dark"},/area/antag/antag_base)
"rY" = (/obj/machinery/door/airlock/external{frequency = 1380; icon_state = "door_locked"; id_tag = "antag_ground_shuttle_hatch"; locked = 1; name = "Shuttle Hatch"; req_access = list(13)},/turf/simulated/shuttle/floor/black,/area/shuttle/antag_ground/base)
"rZ" = (/obj/machinery/door/airlock/external{frequency = 1380; icon_state = "door_locked"; id_tag = "antag_space_shuttle_hatch"; locked = 1; name = "Shuttle Hatch"; req_access = list(13)},/turf/simulated/shuttle/floor/black,/area/shuttle/antag_space/base)
diff --git a/sound/items/nif_click.ogg b/sound/items/nif_click.ogg
new file mode 100644
index 0000000000..4bb8db9f30
Binary files /dev/null and b/sound/items/nif_click.ogg differ
diff --git a/sound/items/nif_tone_bad.ogg b/sound/items/nif_tone_bad.ogg
new file mode 100644
index 0000000000..d2c237acf3
Binary files /dev/null and b/sound/items/nif_tone_bad.ogg differ
diff --git a/sound/items/nif_tone_good.ogg b/sound/items/nif_tone_good.ogg
new file mode 100644
index 0000000000..e6365ba8db
Binary files /dev/null and b/sound/items/nif_tone_good.ogg differ
diff --git a/vorestation.dme b/vorestation.dme
index 86ab4afd95..41f8895e95 100644
--- a/vorestation.dme
+++ b/vorestation.dme
@@ -1304,6 +1304,7 @@
#include "code\modules\client\preference_setup\vore\05_persistence.dm"
#include "code\modules\client\preference_setup\vore\06_vantag.dm"
#include "code\modules\client\preference_setup\vore\07_traits.dm"
+#include "code\modules\client\preference_setup\vore\08_nif.dm"
#include "code\modules\clothing\chameleon.dm"
#include "code\modules\clothing\clothing.dm"
#include "code\modules\clothing\clothing_accessories.dm"
@@ -1971,6 +1972,7 @@
#include "code\modules\nifsoft\software\01_vision.dm"
#include "code\modules\nifsoft\software\05_health.dm"
#include "code\modules\nifsoft\software\10_combat.dm"
+#include "code\modules\nifsoft\software\14_commlink.dm"
#include "code\modules\nifsoft\software\15_misc.dm"
#include "code\modules\organs\blood.dm"
#include "code\modules\organs\misc.dm"