diff --git a/code/defines/mob/living/silicon/hologram.dm b/code/defines/mob/living/silicon/hologram.dm
new file mode 100644
index 00000000000..4bf5dd555cd
--- /dev/null
+++ b/code/defines/mob/living/silicon/hologram.dm
@@ -0,0 +1,7 @@
+/mob/living/silicon/aihologram
+ name = "AI hologram projection"
+ voice_name = "synthesized voice"
+ icon = 'mob.dmi'//
+ icon_state = "ai-holo"
+ density = 0
+ var/parent_ai = null
\ No newline at end of file
diff --git a/code/defines/obj/machinery.dm b/code/defines/obj/machinery.dm
index 741bfc5e4fa..433a9ac3b77 100644
--- a/code/defines/obj/machinery.dm
+++ b/code/defines/obj/machinery.dm
@@ -576,32 +576,13 @@
var/gibtime = 40 // Time from starting until meat appears
var/mob/occupant // Mob who has been put inside
-/obj/machinery/travel
- name = "travel thingie"
- desc = "it is used for travel idunno"
- icon = 'travel.dmi'
- density = 1
+/obj/machinery/holopad
+ name = "holopad"
+ desc = "A floor-mounted device for projecting AI holograms."
+ icon_state = "holopad0"
anchored = 1
-
- //note - didn't use a single loc list just because it's easier to edit via adminmagics if this is done this way
- var/z_connect[world.maxz] //the z position of the tiles it connects to
- var/x_connect[world.maxx] //the x position of the tiles it connects to
- var/y_connect[world.maxy] //the y position of the tiles it connects to
-
-/obj/machinery/travel/ladder
- icon_state = "ladder_up"
- name = "ladder"
- desc = "It is a ladder."
- var/connected = 0
-
-/obj/machinery/travel/elevator
- icon_state = "elevator_closed"
- name = "elevator"
- desc = "It is an elevator. The display shows \"1\"."
-
- var/current_floor = 1 //current floor the elevator is on. do NOT confuse with current z-level of the elevator
- var/list/elevators_connected = list() //list of elevators it is connected to
- var/list/floors = list() //dependant on the number of elevators, converts z-levels to floors, basically
+ var/state = "off"
+ var/slave_holo = null
/* //By Darem, will implement.... eventually...
/obj/machinery/circuitprinter
diff --git a/code/game/machinery/hologram.dm b/code/game/machinery/hologram.dm
index 534abd77eb7..0330d6f3a8b 100644
--- a/code/game/machinery/hologram.dm
+++ b/code/game/machinery/hologram.dm
@@ -1,3 +1,74 @@
+/obj/machinery/holopad/attack_ai(user as mob)
+ //branches depending on four things - is the user currently a hologram? does it have power?
+ //is the holopad on? (moot if q2 = 0) is the user the one who is using this holopad? (moot if q3 = 0)
+ if(!istype(user,/mob/living/silicon/aihologram/)) //question number one
+ if(!(stat & NOPOWER)) //question number two
+ if(src.state == "off") //question number three
+ var/mob/living/silicon/aihologram/holo = new /mob/living/silicon/aihologram(src.loc)
+ holo.parent_ai = user
+ holo.client = usr.client //should move the client there
+ src.state = "on"
+ src.icon_state = "holopad1"
+ src.slave_holo = holo
+ return
+ else
+ if(user != src.slave_holo:parent_ai) //question number four //there is always a slave_holo if it's on
+ user << "\red You're not the one AI who is currently using this holopad!"
+ return
+ else
+ user << "\red \b Something is very wrong, you should be in a hologram by now"
+ return
+ else
+ user << "\red This holopad has no power."
+ return
+ else
+ if(!(stat & NOPOWER)) //question number two
+ if(src.state == "off") //question number three
+ user << "\red This holopad is off, you should find your original holopad."
+ return
+ else
+ if(user == src.slave_holo) //question number four
+ del(user) //code for returning the control back to the AI is in the mob's del() code
+ src.state = "off"
+ src.icon_state = "holopad0"
+ src.slave_holo = null
+ return
+ else
+ user << "\red You're not the one AI who is currently using this holopad!"
+ return
+ else
+ user << "\red This holopad is off, you should find your original holopad."
+ return
+
+/obj/machinery/holopad/process()
+ if((stat & NOPOWER) && src.state == "on")
+ src.state = "off"
+ if(src.state == "on")
+ if(!(src.slave_holo in view(src,5))) //if the hologram strayed too far, destroy it
+ if(src.slave_holo)
+ del(src.slave_holo) //code for returning the control back to the AI is in the mob's del() code
+ src.state = "off"
+ src.icon_state = "holopad0"
+ src.slave_holo = null
+ else
+ use_power(300)
+ if(src.state == "off" && src.slave_holo) //usually happens if the power ran out
+ del(src.slave_holo) //code for returning the control back to the AI is in the mob's del() code
+ src.slave_holo = null
+ return 1
+
+/obj/machinery/holopad/power_change()
+ if (powered())
+ stat &= ~NOPOWER
+ else
+ stat |= ~NOPOWER
+
+/obj/machinery/holopad/Del()
+ if(src.slave_holo)
+ del(src.slave_holo) //code for returning the control back to the AI is in the mob's del() code
+ ..()
+
+/* Old code which didn't work, I believe
/obj/machinery/hologram_ai/New()
..()
@@ -76,4 +147,5 @@
render()
else if (href_list["temp"])
src.temp = null
- src.show_console(usr)
\ No newline at end of file
+ src.show_console(usr)
+*/
\ No newline at end of file
diff --git a/code/modules/mob/living/silicon/aihologram/aihologram.dm b/code/modules/mob/living/silicon/aihologram/aihologram.dm
new file mode 100644
index 00000000000..92e932a833f
--- /dev/null
+++ b/code/modules/mob/living/silicon/aihologram/aihologram.dm
@@ -0,0 +1,42 @@
+/mob/living/silicon/aihologram/New()
+ ..()
+ if(parent_ai)
+ src.name = parent_ai:name
+ src.real_name = parent_ai:real_name
+
+/mob/living/silicon/aihologram/Stat()
+ ..()
+ statpanel("Status")
+ if (src.client.statpanel == "Status")
+ if(emergency_shuttle.online && emergency_shuttle.location < 2)
+ var/timeleft = emergency_shuttle.timeleft()
+ if (timeleft)
+ stat(null, "ETA-[(timeleft / 60) % 60]:[add_zero(num2text(timeleft % 60), 2)]")
+
+/mob/living/silicon/aihologram/proc/ai_roster()
+ set category = "AI Commands"
+ set name = "Show Crew Manifest"
+
+ var/dat = "
Crew RosterCrew Roster:
"
+
+ for (var/datum/data/record/t in data_core.general)
+ dat += "[t.fields["name"]] - [t.fields["rank"]]
"
+
+ dat += ""
+
+ src << browse(dat, "window=airoster")
+ onclose(src, "airoster")
+
+/mob/living/silicon/aihologram/ex_act(severity) //something immaterial is immune to bombs and everything else, really
+ return
+
+/mob/living/silicon/aihologram/meteorhit(obj/O as obj)
+ return
+
+/mob/living/silicon/aihologram/bullet_act(flag)
+ return
+
+/mob/living/silicon/aihologram/proc/back_to_ai()
+ set category = "AI Commands"
+ set name = "Return To AI"
+ del(src)
diff --git a/code/modules/mob/living/silicon/aihologram/death.dm b/code/modules/mob/living/silicon/aihologram/death.dm
new file mode 100644
index 00000000000..cdbab8fdd73
--- /dev/null
+++ b/code/modules/mob/living/silicon/aihologram/death.dm
@@ -0,0 +1,4 @@
+/mob/living/silicon/aihologram/Del() //can't "die" per se, so it's only Del()
+ if(parent_ai)
+ parent_ai:client = src.client //parent_ai is always an ai mob, so : is okay
+ ..()
\ No newline at end of file
diff --git a/code/modules/mob/living/silicon/aihologram/emote.dm b/code/modules/mob/living/silicon/aihologram/emote.dm
new file mode 100644
index 00000000000..ee321cda0ba
--- /dev/null
+++ b/code/modules/mob/living/silicon/aihologram/emote.dm
@@ -0,0 +1,451 @@
+/mob/living/silicon/aihologram/emote(var/act)
+ var/param = null
+
+ if (findtext(act, "-", 1, null))
+ var/t1 = findtext(act, "-", 1, null)
+ param = copytext(act, t1 + 1, length(act) + 1)
+ act = copytext(act, 1, t1)
+
+ var/muzzled = istype(src.wear_mask, /obj/item/clothing/mask/muzzle)
+ var/m_type = 1
+
+ for (var/obj/item/weapon/implant/I in src)
+ if (I.implanted)
+ I.trigger(act, src)
+
+ var/message
+
+ switch(act)
+ if ("airguitar")
+ if (!src.restrained())
+ message = "[src] is strumming the air and headbanging like a safari chimp."
+ m_type = 1
+
+ if ("blink")
+ message = "[src] blinks."
+ m_type = 1
+
+ if ("blink_r")
+ message = "[src] blinks rapidly."
+ m_type = 1
+
+ if ("bow")
+ if (!src.buckled)
+ var/M = null
+ if (param)
+ for (var/mob/A in view(null, null))
+ if (param == A.name)
+ M = A
+ break
+ if (!M)
+ param = null
+
+ if (param)
+ message = "[src] bows to [param]."
+ else
+ message = "[src] bows."
+ m_type = 1
+
+ if ("custom")
+ var/input = input("Choose an emote to display.")
+ var/input2 = input("Is this a visible or hearable emote?") in list("Visible","Hearable")
+ if (input2 == "Visible")
+ m_type = 1
+ else if (input2 == "Hearable")
+ if (src.miming)
+ return
+ m_type = 2
+ else
+ alert("Unable to use this emote, must be either hearable or visible.")
+ return
+ message = "[src] [input]"
+
+
+ if ("salute")
+ if (!src.buckled)
+ var/M = null
+ if (param)
+ for (var/mob/A in view(null, null))
+ if (param == A.name)
+ M = A
+ break
+ if (!M)
+ param = null
+
+ if (param)
+ message = "[src] salutes to [param]."
+ else
+ message = "[src] salutes."
+ m_type = 1
+
+ if ("choke")
+ if (!muzzled)
+ message = "[src] chokes!"
+ m_type = 2
+ else
+ message = "[src] makes a strong noise."
+ m_type = 2
+
+ if ("clap")
+ if (!src.restrained())
+ message = "[src] claps."
+ m_type = 2
+ if ("flap")
+ if (!src.restrained())
+ message = "[src] flaps his wings."
+ m_type = 2
+
+ if ("aflap")
+ if (!src.restrained())
+ message = "[src] flaps his wings ANGRILY!"
+ m_type = 2
+
+ if ("drool")
+ message = "[src] drools."
+ m_type = 1
+
+ if ("eyebrow")
+ message = "[src] raises an eyebrow."
+ m_type = 1
+
+ if ("chuckle")
+ if (!muzzled)
+ message = "[src] chuckles."
+ m_type = 2
+ else
+ message = "[src] makes a noise."
+ m_type = 2
+
+ if ("twitch")
+ message = "[src] twitches violently."
+ m_type = 1
+
+ if ("twitch_s")
+ message = "[src] twitches."
+ m_type = 1
+
+ if ("faint")
+ message = "[src] faints."
+ src.sleeping = 1
+ m_type = 1
+
+ if ("cough")
+ if (!muzzled)
+ message = "[src] coughs!"
+ m_type = 2
+ else
+ message = "[src] makes a strong noise."
+ m_type = 2
+
+ if ("frown")
+ message = "[src] frowns."
+ m_type = 1
+
+ if ("nod")
+ message = "[src] nods."
+ m_type = 1
+
+ if ("blush")
+ message = "[src] blushes."
+ m_type = 1
+
+ if ("gasp")
+ if (!muzzled)
+ message = "[src] gasps!"
+ m_type = 2
+ else
+ message = "[src] makes a weak noise."
+ m_type = 2
+
+ if ("deathgasp")
+ message = "[src] seizes up and falls limp, \his eyes dead and lifeless..."
+ m_type = 1
+
+ if ("giggle")
+ if (!muzzled)
+ message = "[src] giggles."
+ m_type = 2
+ else
+ message = "[src] makes a noise."
+ m_type = 2
+
+ if ("glare")
+ var/M = null
+ if (param)
+ for (var/mob/A in view(null, null))
+ if (param == A.name)
+ M = A
+ break
+ if (!M)
+ param = null
+
+ if (param)
+ message = "[src] glares at [param]."
+ else
+ message = "[src] glares."
+
+ if ("stare")
+ var/M = null
+ if (param)
+ for (var/mob/A in view(null, null))
+ if (param == A.name)
+ M = A
+ break
+ if (!M)
+ param = null
+
+ if (param)
+ message = "[src] stares at [param]."
+ else
+ message = "[src] stares."
+
+ if ("look")
+ var/M = null
+ if (param)
+ for (var/mob/A in view(null, null))
+ if (param == A.name)
+ M = A
+ break
+
+ if (!M)
+ param = null
+
+ if (param)
+ message = "[src] looks at [param]."
+ else
+ message = "[src] looks."
+ m_type = 1
+
+ if ("grin")
+ message = "[src] grins."
+ m_type = 1
+
+ if ("cry")
+ if (!muzzled)
+ message = "[src] cries."
+ m_type = 2
+ else
+ message = "[src] makes a weak noise. \He frowns."
+ m_type = 2
+
+ if ("sigh")
+ if (!muzzled)
+ message = "[src] sighs."
+ m_type = 2
+ else
+ message = "[src] makes a weak noise."
+ m_type = 2
+
+ if ("laugh")
+ if (!muzzled)
+ message = "[src] laughs."
+ m_type = 2
+ else
+ message = "[src] makes a noise."
+ m_type = 2
+
+ if ("mumble")
+ message = "[src] mumbles!"
+ m_type = 2
+
+ if ("grumble")
+ if (!muzzled)
+ message = "[src] grumbles!"
+ m_type = 2
+ else
+ message = "[src] makes a noise."
+ m_type = 2
+
+ if ("groan")
+ if (!muzzled)
+ message = "[src] groans!"
+ m_type = 2
+ else
+ message = "[src] makes a loud noise."
+ m_type = 2
+
+ if ("moan")
+ message = "[src] moans!"
+ m_type = 2
+
+ if ("johnny")
+ var/M
+ if (param)
+ M = param
+ if (!M)
+ param = null
+ else
+ message = "[src] says, \"[M], please. He had a family.\" [src.name] takes a drag from a cigarette and blows his name out in smoke."
+ m_type = 2
+
+ if ("point")
+ if (!src.restrained())
+ var/mob/M = null
+ if (param)
+ for (var/atom/A as mob|obj|turf|area in view(null, null))
+ if (param == A.name)
+ M = A
+ break
+
+ if (!M)
+ message = "[src] points."
+ else
+ M.point()
+
+ if (M)
+ message = "[src] points to [M]."
+ else
+ m_type = 1
+
+ if ("raise")
+ if (!src.restrained())
+ message = "[src] raises a hand."
+ m_type = 1
+
+ if("shake")
+ message = "[src] shakes \his head."
+ m_type = 1
+
+ if ("shrug")
+ message = "[src] shrugs."
+ m_type = 1
+
+ if ("signal")
+ if (!src.restrained())
+ var/t1 = round(text2num(param))
+ if (isnum(t1))
+ if (t1 <= 5 && (!src.r_hand || !src.l_hand))
+ message = "[src] raises [t1] finger\s."
+ else if (t1 <= 10 && (!src.r_hand && !src.l_hand))
+ message = "[src] raises [t1] finger\s."
+ m_type = 1
+
+ if ("smile")
+ message = "[src] smiles."
+ m_type = 1
+
+ if ("shiver")
+ message = "[src] shivers."
+ m_type = 2
+
+ if ("pale")
+ message = "[src] goes pale for a second."
+ m_type = 1
+
+ if ("tremble")
+ message = "[src] trembles in fear!"
+ m_type = 1
+
+ if ("sneeze")
+ if (!muzzled)
+ message = "[src] sneezes."
+ m_type = 2
+ else
+ message = "[src] makes a strange noise."
+ m_type = 2
+
+ if ("sniff")
+ message = "[src] sniffs."
+ m_type = 2
+
+ if ("snore")
+ if (!muzzled)
+ message = "[src] snores."
+ m_type = 2
+ else
+ message = "[src] makes a noise."
+ m_type = 2
+
+ if ("whimper")
+ if (!muzzled)
+ message = "[src] whimpers."
+ m_type = 2
+ else
+ message = "[src] makes a weak noise."
+ m_type = 2
+
+ if ("wink")
+ message = "[src] winks."
+ m_type = 1
+
+ if ("yawn")
+ if (!muzzled)
+ message = "[src] yawns."
+ m_type = 2
+
+ if ("collapse")
+ if (!src.paralysis)
+ src.paralysis += 2
+ message = "[src] collapses!"
+ m_type = 2
+
+ if("hug")
+ m_type = 1
+ if (!src.restrained())
+ var/M = null
+ if (param)
+ for (var/mob/A in view(1, null))
+ if (param == A.name)
+ M = A
+ break
+ if (M == src)
+ M = null
+
+ if (M)
+ message = "[src] hugs [M]."
+ else
+ message = "[src] hugs \himself."
+
+ if ("handshake")
+ m_type = 1
+ if (!src.restrained() && !src.r_hand)
+ var/mob/M = null
+ if (param)
+ for (var/mob/A in view(1, null))
+ if (param == A.name)
+ M = A
+ break
+ if (M == src)
+ M = null
+
+ if (M)
+ if (M.canmove && !M.r_hand && !M.restrained())
+ message = "[src] shakes hands with [M]."
+ else
+ message = "[src] holds out \his hand to [M]."
+
+ if("daps")
+ m_type = 1
+ if (!src.restrained())
+ var/M = null
+ if (param)
+ for (var/mob/A in view(1, null))
+ if (param == A.name)
+ M = A
+ break
+ if (M)
+ message = "[src] gives daps to [M]."
+ else
+ message = "[src] sadly can't find anybody to give daps to, and daps \himself. Shameful."
+
+ if ("scream")
+ if (!muzzled)
+ message = "[src] screams!"
+ m_type = 2
+ else
+ message = "[src] makes a very loud noise."
+ m_type = 2
+
+ if ("help")
+ src << "blink, blink_r, blush, bow-(none)/mob, burp, choke, chuckle, clap, collapse, cough,\ncry, custom, deathgasp, drool, eyebrow, frown, gasp, giggle, groan, grumble, handshake, hug-(none)/mob, glare-(none)/mob,\ngrin, laugh, look-(none)/mob, moan, mumble, nod, pale, point-atom, raise, salute, shake, shiver, shrug,\nsigh, signal-#1-10, smile, sneeze, sniff, snore, stare-(none)/mob, tremble, twitch, twitch_s, whimper,\nwink, yawn"
+
+ else
+ src << "\blue Unusable emote '[act]'. Say *help for a list."
+
+ if (message)
+ if (m_type & 1)
+ for (var/mob/O in viewers(src, null))
+ O.show_message(message, m_type)
+ else if (m_type & 2)
+ for (var/mob/O in hearers(src, null))
+ O.show_message(message, m_type)
diff --git a/code/modules/mob/living/silicon/aihologram/examine.dm b/code/modules/mob/living/silicon/aihologram/examine.dm
new file mode 100644
index 00000000000..6345ddf7fc5
--- /dev/null
+++ b/code/modules/mob/living/silicon/aihologram/examine.dm
@@ -0,0 +1,5 @@
+/mob/living/silicon/aihologram/examine()
+ set src in oview()
+ usr << "\blue *---------*"
+ usr << text("\blue This is \icon[] []!", src, src.name)
+ return
\ No newline at end of file
diff --git a/code/modules/mob/living/silicon/aihologram/hud.dm b/code/modules/mob/living/silicon/aihologram/hud.dm
new file mode 100644
index 00000000000..3c54d0eac68
--- /dev/null
+++ b/code/modules/mob/living/silicon/aihologram/hud.dm
@@ -0,0 +1,10 @@
+/obj/hud/proc/aihologram_hud()
+
+ src.station_explosion = new src.h_type( src )
+ src.station_explosion.icon = 'station_explosion.dmi'
+ src.station_explosion.icon_state = "start"
+ src.station_explosion.layer = 20
+ src.station_explosion.mouse_opacity = 0
+ src.station_explosion.screen_loc = "1,3"
+
+ return
\ No newline at end of file
diff --git a/code/modules/mob/living/silicon/aihologram/laws.dm b/code/modules/mob/living/silicon/aihologram/laws.dm
new file mode 100644
index 00000000000..77c2cf9f946
--- /dev/null
+++ b/code/modules/mob/living/silicon/aihologram/laws.dm
@@ -0,0 +1 @@
+//I don't even know -- Urist
\ No newline at end of file
diff --git a/code/modules/mob/living/silicon/aihologram/login.dm b/code/modules/mob/living/silicon/aihologram/login.dm
new file mode 100644
index 00000000000..dc48c6628f5
--- /dev/null
+++ b/code/modules/mob/living/silicon/aihologram/login.dm
@@ -0,0 +1,10 @@
+/mob/dead/aihologram/Login()
+ ..()
+
+ src.client.screen = null
+
+ if (!isturf(src.loc))
+ src.client.eye = src.loc
+ src.client.perspective = EYE_PERSPECTIVE
+
+ return
\ No newline at end of file
diff --git a/code/modules/mob/living/silicon/aihologram/say.dm b/code/modules/mob/living/silicon/aihologram/say.dm
new file mode 100644
index 00000000000..a37d07f7474
--- /dev/null
+++ b/code/modules/mob/living/silicon/aihologram/say.dm
@@ -0,0 +1,16 @@
+/mob/living/silicon/aihologram/say_understands(var/other)
+ if (istype(other, /mob/living/carbon/human))
+ return 1
+ if (istype(other, /mob/living/silicon))
+ return 1
+ return ..()
+
+/mob/living/silicon/aihologram/say_quote(var/text)
+ var/ending = copytext(text, length(text))
+
+ if (ending == "?")
+ return "queries, \"[text]\"";
+ else if (ending == "!")
+ return "declares, \"[copytext(text, 1, length(text))]\"";
+
+ return "states, \"[text]\"";
\ No newline at end of file
diff --git a/config/admins.txt b/config/admins.txt
index 73d716133d8..75836e84875 100644
--- a/config/admins.txt
+++ b/config/admins.txt
@@ -1,5 +1,6 @@
deuryn - Host
tle - Host
+uristmcdorf - Host
mport2004 - Coder
\ No newline at end of file
diff --git a/tgstation.dme b/tgstation.dme
index 9523c649d26..090472489c6 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -336,6 +336,7 @@
#include "code\game\machinery\flasher.dm"
#include "code\game\machinery\Freezer.dm"
#include "code\game\machinery\gibber.dm"
+#include "code\game\machinery\hologram.dm"
#include "code\game\machinery\hydroponics.dm"
#include "code\game\machinery\igniter.dm"
#include "code\game\machinery\lightswitch.dm"
@@ -644,6 +645,14 @@
#include "code\modules\mob\living\silicon\ai\logout.dm"
#include "code\modules\mob\living\silicon\ai\move.dm"
#include "code\modules\mob\living\silicon\ai\say.dm"
+#include "code\modules\mob\living\silicon\aihologram\aihologram.dm"
+#include "code\modules\mob\living\silicon\aihologram\death.dm"
+#include "code\modules\mob\living\silicon\aihologram\emote.dm"
+#include "code\modules\mob\living\silicon\aihologram\examine.dm"
+#include "code\modules\mob\living\silicon\aihologram\hud.dm"
+#include "code\modules\mob\living\silicon\aihologram\laws.dm"
+#include "code\modules\mob\living\silicon\aihologram\login.dm"
+#include "code\modules\mob\living\silicon\aihologram\say.dm"
#include "code\modules\mob\living\silicon\decoy\death.dm"
#include "code\modules\mob\living\silicon\decoy\decoy.dm"
#include "code\modules\mob\living\silicon\decoy\life.dm"