diff --git a/code/controllers/subsystem/garbage.dm.rej b/code/controllers/subsystem/garbage.dm.rej
new file mode 100644
index 0000000000..2075155c49
--- /dev/null
+++ b/code/controllers/subsystem/garbage.dm.rej
@@ -0,0 +1,9 @@
+diff a/code/controllers/subsystem/garbage.dm b/code/controllers/subsystem/garbage.dm (rejected hunks)
+@@ -725,7 +725,6 @@ var/datum/controller/subsystem/garbage_collector/SSgarbage
+ SearchVar(multiverse)
+ SearchVar(announcement_systems)
+ SearchVar(doppler_arrays)
+- SearchVar(HOLOPAD_MODE)
+ SearchVar(holopads)
+ SearchVar(news_network)
+ SearchVar(allCasters)
diff --git a/code/datums/holocall.dm b/code/datums/holocall.dm
new file mode 100644
index 0000000000..0153e41414
--- /dev/null
+++ b/code/datums/holocall.dm
@@ -0,0 +1,154 @@
+#define HOLOPAD_MAX_DIAL_TIME 200
+
+/mob/camera/aiEye/remote/holo/setLoc()
+ . = ..()
+ var/obj/machinery/holopad/H = origin
+ H.move_hologram(eye_user, loc)
+
+//this datum manages it's own references
+
+/datum/holocall
+ var/mob/living/user //the one that called
+ var/obj/machinery/holopad/calling_holopad //the one that sent the call
+ var/obj/machinery/holopad/connected_holopad //the one that answered the call (may be null)
+ var/list/dialed_holopads //all things called, will be cleared out to just connected_holopad once answered
+
+ var/mob/camera/aiEye/remote/holo/eye //user's eye, once connected
+ var/obj/effect/overlay/holo_pad_hologram/hologram //user's hologram, once connected
+
+ var/call_start_time
+
+//creates a holocall made by `caller` from `calling_pad` to `callees`
+/datum/holocall/New(mob/living/caller, obj/machinery/holopad/calling_pad, list/callees)
+ call_start_time = world.time
+ user = caller
+ calling_pad.outgoing_call = src
+ calling_holopad = calling_pad
+ dialed_holopads = list()
+
+ for(var/I in callees)
+ var/obj/machinery/holopad/H = I
+ if(!QDELETED(H) && H.is_operational())
+ dialed_holopads += H
+ LAZYADD(H.holo_calls, src)
+
+ if(!dialed_holopads.len)
+ calling_pad.say("Connection failure.")
+ qdel(src)
+
+ testing("Holocall started")
+
+//cleans up ALL references :)
+/datum/holocall/Destroy()
+ QDEL_NULL(eye)
+
+ user.reset_perspective()
+
+ user = null
+ hologram.HC = null
+ hologram = null
+ calling_holopad.outgoing_call = null
+
+ for(var/I in dialed_holopads)
+ var/obj/machinery/holopad/H = I
+ LAZYREMOVE(H.holo_calls, src)
+ dialed_holopads.Cut()
+
+ if(calling_holopad)
+ calling_holopad.SetLightsAndPower()
+ calling_holopad = null
+ if(connected_holopad)
+ connected_holopad.SetLightsAndPower()
+ connected_holopad = null
+
+ testing("Holocall destroyed")
+
+ return ..()
+
+//Gracefully disconnects a holopad `H` from a call. Pads not in the call are ignored. Notifies participants of the disconnection
+/datum/holocall/proc/Disconnect(obj/machinery/holopad/H)
+ testing("Holocall disconnect")
+ if(H == connected_holopad)
+ calling_holopad.say("[usr] disconnected.")
+ else if(H == calling_holopad && connected_holopad)
+ connected_holopad.say("[usr] disconnected.")
+
+ ConnectionFailure(H, TRUE)
+
+//Forcefully disconnects a holopad `H` from a call. Pads not in the call are ignored.
+/datum/holocall/proc/ConnectionFailure(obj/machinery/holopad/H, graceful = FALSE)
+ testing("Holocall connection failure: graceful [graceful]")
+ if(H == connected_holopad || H == calling_holopad)
+ if(!graceful)
+ calling_holopad.say("Connection failure.")
+ qdel(src)
+ return
+
+ LAZYREMOVE(H.holo_calls, src)
+ dialed_holopads -= H
+ if(!dialed_holopads.len)
+ if(graceful)
+ calling_holopad.say("Call rejected.")
+ testing("No recipients, terminating")
+ qdel(src)
+
+//Answers a call made to a holopad `H` which cannot be the calling holopad. Pads not in the call are ignored
+/datum/holocall/proc/Answer(obj/machinery/holopad/H)
+ testing("Holocall answer")
+ if(H == calling_holopad)
+ CRASH("How cute, a holopad tried to answer itself.")
+
+ if(!(H in dialed_holopads))
+ return
+
+ if(connected_holopad)
+ CRASH("Multi-connection holocall")
+
+ connected_holopad = H
+ for(var/I in dialed_holopads)
+ if(I == H)
+ continue
+ var/obj/machinery/holopad/Holo = I
+ LAZYREMOVE(Holo.holo_calls, src)
+ dialed_holopads -= Holo
+
+ if(!Check())
+ return
+
+ hologram = H.activate_holo(user)
+ hologram.HC = src
+
+ //eyeobj code is horrid, this is the best copypasta I could make
+ eye = new
+ eye.origin = H
+ eye.eye_initialized = TRUE
+ eye.eye_user = user
+ eye.name = "Camera Eye ([user.name])"
+ user.remote_control = eye
+ user.reset_perspective(eye)
+ eye.setLoc(H.loc)
+
+//Checks the validity of a holocall and qdels itself if it's not. Returns TRUE if valid, FALSE otherwise
+/datum/holocall/proc/Check()
+ for(var/I in dialed_holopads)
+ var/obj/machinery/holopad/H = I
+ if(!H.is_operational())
+ ConnectionFailure(H)
+
+ if(QDELETED(src))
+ return FALSE
+
+ . = !QDELETED(user) && !user.incapacitated() && !QDELETED(calling_holopad) && calling_holopad.is_operational() && user.loc == calling_holopad.loc
+
+ if(.)
+ if(connected_holopad)
+ . = !QDELETED(connected_holopad) && connected_holopad.is_operational()
+ else
+ . = world.time < (call_start_time + HOLOPAD_MAX_DIAL_TIME)
+ if(!.)
+ calling_holopad.say("No answer recieved.")
+ calling_holopad.temp = ""
+
+ if(!.)
+ testing("Holocall Check fail")
+ qdel(src)
diff --git a/code/game/machinery/hologram.dm b/code/game/machinery/hologram.dm
index ae8c1ac1c4..bc73c41f08 100644
--- a/code/game/machinery/hologram.dm
+++ b/code/game/machinery/hologram.dm
@@ -26,14 +26,13 @@ Possible to do for anyone motivated enough:
#define HOLOPAD_PASSIVE_POWER_USAGE 1
#define HOLOGRAM_POWER_USAGE 2
-#define RANGE_BASED 4
-#define AREA_BASED 6
+GLOBAL_LIST_EMPTY(holopads)
#define HOLOPAD_MODE RANGE_BASED
/obj/machinery/holopad
- name = "\improper AI holopad"
- desc = "It's a floor-mounted device for projecting holographic images. It is activated remotely."
+ name = "Holopad"
+ desc = "It's a floor-mounted device for projecting holographic images."
icon_state = "holopad0"
layer = LOW_OBJ_LAYER
flags = HEAR
@@ -44,21 +43,32 @@ Possible to do for anyone motivated enough:
obj_integrity = 300
max_integrity = 300
armor = list(melee = 50, bullet = 20, laser = 20, energy = 20, bomb = 0, bio = 0, rad = 0, fire = 50, acid = 0)
- var/list/masters = list()//List of AIs that use the holopad
+ var/list/masters = list()//List of living mobs that use the holopad
var/last_request = 0 //to prevent request spam. ~Carn
var/holo_range = 5 // Change to change how far the AI can move away from the holopad before deactivating.
var/temp = ""
+ var/list/holo_calls //array of /datum/holocalls
+ var/datum/holocall/outgoing_call //do not modify the datums only check and call the public procs
+ var/static/force_answer_call = FALSE //Calls will be automatically answered after a couple rings, here for debugging
var/static/list/holopads = list()
-/obj/machinery/holopad/New()
+/obj/machinery/holopad/Initialize()
..()
var/obj/item/weapon/circuitboard/machine/B = new /obj/item/weapon/circuitboard/machine/holopad(null)
B.apply_default_parts(src)
holopads += src
/obj/machinery/holopad/Destroy()
- for (var/mob/living/silicon/ai/master in masters)
- clear_holo(master)
+ if(outgoing_call)
+ LAZYADD(holo_calls, outgoing_call)
+
+ for(var/I in holo_calls)
+ var/datum/holocall/HC = I
+ HC.ConnectionFailure(src)
+ LAZYCLEARLIST(holo_calls)
+
+ for (var/I in masters)
+ clear_holo(I)
holopads -= src
return ..()
@@ -91,20 +101,58 @@ Possible to do for anyone motivated enough:
return
return ..()
+/obj/machinery/holopad/proc/CheckCallClose()
+ for(var/I in holo_calls)
+ var/datum/holocall/HC = I
+ if(usr == HC.eye)
+ HC.Disconnect(HC.calling_holopad) //disconnect via clicking the called holopad
+ return TRUE
+ return FALSE
+
+/obj/machinery/holopad/Click(location,control,params)
+ if(!CheckCallClose())
+ return ..()
+
/obj/machinery/holopad/AltClick(mob/living/carbon/human/user)
- interact(user)
+ if(!CheckCallClose())
+ interact(user)
/obj/machinery/holopad/interact(mob/living/carbon/human/user) //Carn: Hologram requests.
if(!istype(user))
return
- if(user.stat || !is_operational())
+
+ if(outgoing_call || user.incapacitated() || !is_operational())
return
+
user.set_machine(src)
var/dat
if(temp)
dat = temp
else
- dat = "request an AI's presence."
+ dat = "Request an AI's presence.
"
+ dat += "Call another holopad.
"
+
+ if(LAZYLEN(holo_calls))
+ dat += "=====================================================
"
+
+ var/one_answered_call = FALSE
+ var/one_unanswered_call = FALSE
+ for(var/I in holo_calls)
+ var/datum/holocall/HC = I
+ if(HC.connected_holopad != src)
+ dat += "Answer call from [get_area(HC.calling_holopad)].
"
+ one_unanswered_call = TRUE
+ else
+ one_answered_call = TRUE
+
+ if(one_answered_call && one_unanswered_call)
+ dat += "=====================================================
"
+ //we loop twice for formatting
+ for(var/I in holo_calls)
+ var/datum/holocall/HC = I
+ if(HC.connected_holopad == src)
+ dat += "Disconnect call from [HC.user].
"
+
var/datum/browser/popup = new(user, "holopad", name, 300, 130)
popup.set_content(dat)
@@ -112,7 +160,10 @@ Possible to do for anyone motivated enough:
popup.open()
/obj/machinery/holopad/Topic(href, href_list)
- if(..() || !is_operational())
+ if(..() || isAI(usr))
+ return
+ add_fingerprint(usr)
+ if(!is_operational())
return
if (href_list["AIrequest"])
if(last_request + 200 < world.time)
@@ -120,7 +171,7 @@ Possible to do for anyone motivated enough:
temp = "You requested an AI's presence.
"
temp += "Main Menu"
var/area/area = get_area(src)
- for(var/mob/living/silicon/ai/AI in GLOB.living_mob_list)
+ for(var/mob/living/silicon/ai/AI in GLOB.silicon_mobs)
if(!AI.client)
continue
to_chat(AI, "Your presence is requested at \the [area].")
@@ -128,12 +179,49 @@ Possible to do for anyone motivated enough:
temp = "A request for AI presence was already sent recently.
"
temp += "Main Menu"
- else if(href_list["mainmenu"])
+ else if(href_list["Holocall"])
+ if(outgoing_call)
+ return
+
+ temp = "You must stand on the holopad to make a call!
"
+ temp += "Main Menu"
+ if(usr.loc == loc)
+ var/list/callnames = list()
+ for(var/I in holopads)
+ var/area/A = get_area(I)
+ if(A)
+ LAZYADD(callnames[A], I)
+ callnames -= get_area(src)
+
+ var/result = input(usr, "Choose an area to call", "Holocall") as null|anything in callnames
+ if(QDELETED(usr) || !result || outgoing_call)
+ return
+
+ if(usr.loc == loc)
+ temp = "Dialing...
"
+ temp += "Main Menu"
+ new /datum/holocall(usr, src, callnames[result])
+
+ else if(href_list["connectcall"])
+ var/datum/holocall/call_to_connect = locate(href_list["connectcall"])
+ if(!QDELETED(call_to_connect))
+ call_to_connect.Answer(src)
temp = ""
- updateDialog()
- add_fingerprint(usr)
+ else if(href_list["disconnectcall"])
+ var/datum/holocall/call_to_disconnect = locate(href_list["disconnectcall"])
+ if(!QDELETED(call_to_disconnect))
+ call_to_disconnect.Disconnect(src)
+ temp = ""
+ else if(href_list["mainmenu"])
+ temp = ""
+ if(outgoing_call)
+ outgoing_call.Disconnect()
+
+ updateDialog()
+
+//do not allow AIs to answer calls or people will use it to meta the AI sattelite
/obj/machinery/holopad/attack_ai(mob/living/silicon/ai/user)
if (!istype(user))
return
@@ -148,39 +236,75 @@ Possible to do for anyone motivated enough:
clear_holo(user)
/obj/machinery/holopad/process()
- if(masters.len)//If there is a hologram.
- for (var/mob/living/silicon/ai/master in masters)
- if(master && !master.stat && master.client && master.eyeobj)//If there is an AI attached, it's not incapacitated, it has a client, and the client eye is centered on the projector.
- if(!(stat & NOPOWER))//If the machine has power.
- if(HOLOPAD_MODE == RANGE_BASED)
- if(get_dist(master.eyeobj, src) <= holo_range)
- return TRUE
- else
- var/obj/machinery/holopad/pad_close = get_closest_atom(/obj/machinery/holopad, holopads, master.eyeobj)
- if(get_dist(pad_close, master.eyeobj) <= holo_range)
- var/obj/effect/overlay/holo_pad_hologram/h = masters[master]
- unset_holo(master)
- pad_close.set_holo(master, h)
- return TRUE
+ for(var/I in masters)
+ var/mob/living/master = I
+ var/mob/living/silicon/ai/AI = master
+ if(!istype(AI))
+ AI = null
- else if (HOLOPAD_MODE == AREA_BASED)
+ if(!QDELETED(master) && !master.incapacitated() && master.client && (!AI || AI.eyeobj))//If there is an AI attached, it's not incapacitated, it has a client, and the client eye is centered on the projector.
+ if(is_operational())//If the machine has power.
+ if(AI) //ais are range based
+ if(get_dist(AI.eyeobj, src) <= holo_range)
+ continue
+ else
+ var/obj/machinery/holopad/pad_close = get_closest_atom(/obj/machinery/holopad, holopads, AI.eyeobj)
+ if(get_dist(pad_close, AI.eyeobj) <= holo_range)
+ var/obj/effect/overlay/holo_pad_hologram/h = masters[master]
+ unset_holo(master)
+ pad_close.set_holo(master, h)
+ continue
+ else
+ continue
+ clear_holo(master)//If not, we want to get rid of the hologram.
- var/area/holo_area = get_area(src)
- var/area/eye_area = get_area(master.eyeobj)
+ if(outgoing_call)
+ outgoing_call.Check()
- if(eye_area in holo_area.related)
- return TRUE
+ for(var/I in holo_calls)
+ var/datum/holocall/HC = I
+ if(HC.connected_holopad != src)
+ if(force_answer_call && world.time > (HC.call_start_time + (HOLOPAD_MAX_DIAL_TIME / 2)))
+ HC.Answer(src)
+ break
+ if(outgoing_call)
+ HC.Disconnect(src)//can't answer calls while calling
+ else
+ playsound(src, 'sound/machines/twobeep.ogg', 100) //bring, bring!
- clear_holo(master)//If not, we want to get rid of the hologram.
- return TRUE
+/obj/machinery/holopad/proc/activate_holo(mob/living/user)
+ var/mob/living/silicon/ai/AI = user
+ if(!istype(AI))
+ AI = null
-/obj/machinery/holopad/proc/activate_holo(mob/living/silicon/ai/user)
- if(!(stat & NOPOWER) && user.eyeobj.loc == src.loc)//If the projector has power and client eye is on it
- if (istype(user.current, /obj/machinery/holopad))
+ if(is_operational() && (!AI || AI.eyeobj.loc == loc))//If the projector has power and client eye is on it
+ if (AI && istype(AI.current, /obj/machinery/holopad))
to_chat(user, "ERROR: \black Image feed in progress.")
return
- create_holo(user)//Create one.
- src.visible_message("A holographic image of [user] flicks to life right before your eyes!")
+
+ var/obj/effect/overlay/holo_pad_hologram/Hologram = new(loc)//Spawn a blank effect at the location.
+ if(AI)
+ Hologram.icon = AI.holo_icon
+ else //make it like real life
+ Hologram.icon = user.icon
+ Hologram.icon_state = user.icon_state
+ Hologram.copy_overlays(user, TRUE)
+ //codersprite some holo effects here
+ Hologram.alpha = 100
+ Hologram.add_atom_colour("#77abff", FIXED_COLOUR_PRIORITY)
+ Hologram.Impersonation = user
+
+ Hologram.languages = user.languages
+ Hologram.mouse_opacity = 0//So you can't click on it.
+ Hologram.layer = FLY_LAYER//Above all the other objects/mobs. Or the vast majority of them.
+ Hologram.anchored = 1//So space wind cannot drag it.
+ Hologram.name = "[user.name] (Hologram)"//If someone decides to right click.
+ Hologram.set_light(2) //hologram lighting
+
+ set_holo(user, Hologram)
+ visible_message("A holographic image of [user] flicks to life right before your eyes!")
+
+ return Hologram
else
to_chat(user, "ERROR: \black Unable to project hologram.")
@@ -192,58 +316,80 @@ For the other part of the code, check silicon say.dm. Particularly robot talk.*/
if(masters[master] && speaker != master)
master.relay_speech(message, speaker, message_language, raw_message, radio_freq, spans, message_mode)
-/obj/machinery/holopad/proc/create_holo(mob/living/silicon/ai/A, turf/T = loc)
- var/obj/effect/overlay/holo_pad_hologram/h = new(T)//Spawn a blank effect at the location.
- h.icon = A.holo_icon
- h.mouse_opacity = 0//So you can't click on it.
- h.layer = FLY_LAYER//Above all the other objects/mobs. Or the vast majority of them.
- h.anchored = 1//So space wind cannot drag it.
- h.name = "[A.name] (Hologram)"//If someone decides to right click.
- h.set_light(2) //hologram lighting
- set_holo(A, h)
+ for(var/I in holo_calls)
+ var/datum/holocall/HC = I
+ if(HC.connected_holopad == src && speaker != HC.hologram)
+ HC.user.Hear(message, speaker, message_language, raw_message, radio_freq, spans)
+
+ if(outgoing_call && speaker == outgoing_call.user)
+ outgoing_call.hologram.say(raw_message)
+
+/obj/machinery/holopad/proc/SetLightsAndPower()
+ var/total_users = masters.len + LAZYLEN(holo_calls)
+ use_power = HOLOPAD_PASSIVE_POWER_USAGE + HOLOGRAM_POWER_USAGE * total_users
+ if(total_users)
+ set_light(2)
+ icon_state = "holopad1"
+ else
+ set_light(0)
+ icon_state = "holopad0"
+
+/obj/machinery/holopad/proc/set_holo(mob/living/user, var/obj/effect/overlay/holo_pad_hologram/h)
+ masters[user] = h
+ var/mob/living/silicon/ai/AI = user
+ if(istype(AI))
+ AI.current = src
+ SetLightsAndPower()
return TRUE
-/obj/machinery/holopad/proc/set_holo(mob/living/silicon/ai/A, var/obj/effect/overlay/holo_pad_hologram/h)
- masters[A] = h
- set_light(2) // pad lighting
- icon_state = "holopad1"
- A.current = src
- use_power += HOLOGRAM_POWER_USAGE
- return TRUE
-
-/obj/machinery/holopad/proc/clear_holo(mob/living/silicon/ai/user)
+/obj/machinery/holopad/proc/clear_holo(mob/living/user)
qdel(masters[user]) // Get rid of user's hologram
unset_holo(user)
return TRUE
-/obj/machinery/holopad/proc/unset_holo(mob/living/silicon/ai/user)
- if(user.current == src)
- user.current = null
+/obj/machinery/holopad/proc/unset_holo(mob/living/user)
+ var/mob/living/silicon/ai/AI = user
+ if(istype(AI) && AI.current == src)
+ AI.current = null
masters -= user // Discard AI from the list of those who use holopad
- use_power = max(HOLOPAD_PASSIVE_POWER_USAGE, use_power - HOLOGRAM_POWER_USAGE)//Reduce power usage
- if (!masters.len) // If no users left
- set_light(0) // pad lighting (hologram lighting will be handled automatically since its owner was deleted)
- icon_state = "holopad0"
- use_power = HOLOPAD_PASSIVE_POWER_USAGE
+ SetLightsAndPower()
return TRUE
-/obj/machinery/holopad/proc/move_hologram(mob/living/silicon/ai/user)
+/obj/machinery/holopad/proc/move_hologram(mob/living/user, turf/new_turf)
if(masters[user])
- step_to(masters[user], user.eyeobj)
var/obj/effect/overlay/holo_pad_hologram/H = masters[user]
- H.loc = get_turf(user.eyeobj)
+ step_to(H, new_turf)
+ H.loc = new_turf
+ var/area/holo_area = get_area(src)
+ var/area/eye_area = new_turf.loc
+
+ if(!(eye_area in holo_area.related))
+ clear_holo(user)
return TRUE
+/obj/effect/overlay/holo_pad_hologram
+ var/mob/living/Impersonation
+ var/datum/holocall/HC
+
+/obj/effect/overlay/holo_pad_hologram/Destroy()
+ Impersonation = null
+ if(HC)
+ HC.Disconnect(HC.calling_holopad)
+ return ..()
+
/obj/effect/overlay/holo_pad_hologram/Process_Spacemove(movement_dir = 0)
return 1
+/obj/effect/overlay/holo_pad_hologram/examine(mob/user)
+ if(Impersonation)
+ return Impersonation.examine(user)
+ return ..()
+
/obj/item/weapon/circuitboard/machine/holopad
name = "AI Holopad (Machine Board)"
build_path = /obj/machinery/holopad
origin_tech = "programming=1"
req_components = list(/obj/item/weapon/stock_parts/capacitor = 1)
-#undef RANGE_BASED
-#undef AREA_BASED
#undef HOLOPAD_PASSIVE_POWER_USAGE
-#undef HOLOGRAM_POWER_USAGE
+#undef HOLOGRAM_POWER_USAGE
\ No newline at end of file
diff --git a/code/game/machinery/hologram.dm.rej b/code/game/machinery/hologram.dm.rej
new file mode 100644
index 0000000000..5dcbcc9d54
--- /dev/null
+++ b/code/game/machinery/hologram.dm.rej
@@ -0,0 +1,226 @@
+diff a/code/game/machinery/hologram.dm b/code/game/machinery/hologram.dm (rejected hunks)
+@@ -26,16 +26,12 @@ Possible to do for anyone motivated enough:
+
+ #define HOLOPAD_PASSIVE_POWER_USAGE 1
+ #define HOLOGRAM_POWER_USAGE 2
+-#define RANGE_BASED 4
+-#define AREA_BASED 6
+-
+-var/const/HOLOPAD_MODE = RANGE_BASED
+
+ var/list/holopads = list()
+
+ /obj/machinery/holopad
+- name = "\improper AI holopad"
+- desc = "It's a floor-mounted device for projecting holographic images. It is activated remotely."
++ name = "Holopad"
++ desc = "It's a floor-mounted device for projecting holographic images."
+ icon_state = "holopad0"
+ layer = LOW_OBJ_LAYER
+ flags = HEAR
+@@ -48,10 +44,13 @@ var/list/holopads = list()
+ obj_integrity = 300
+ max_integrity = 300
+ armor = list(melee = 50, bullet = 20, laser = 20, energy = 20, bomb = 0, bio = 0, rad = 0, fire = 50, acid = 0)
+- var/list/masters = list()//List of AIs that use the holopad
++ var/list/masters = list()//List of living mobs that use the holopad
+ var/last_request = 0 //to prevent request spam. ~Carn
+ var/holo_range = 5 // Change to change how far the AI can move away from the holopad before deactivating.
+ var/temp = ""
++ var/list/holo_calls //array of /datum/holocalls
++ var/datum/holocall/outgoing_call //do not modify the datums only check and call the public procs
++ var/static/force_answer_call = FALSE //Calls will be automatically answered after a couple rings, here for debugging
+
+ /obj/machinery/holopad/Initialize()
+ ..()
+@@ -94,20 +101,60 @@ var/list/holopads = list()
+ return
+ return ..()
+
++/obj/machinery/holopad/proc/CheckCallClose()
++ for(var/I in holo_calls)
++ var/datum/holocall/HC = I
++ if(usr == HC.eye)
++ HC.Disconnect(HC.calling_holopad) //disconnect via clicking the called holopad
++ return TRUE
++ return FALSE
++
++/obj/machinery/holopad/Click(location,control,params)
++ if(!CheckCallClose())
++ return ..()
++
+ /obj/machinery/holopad/AltClick(mob/living/carbon/human/user)
+- interact(user)
++ if(!CheckCallClose())
++ interact(user)
+
+ /obj/machinery/holopad/interact(mob/living/carbon/human/user) //Carn: Hologram requests.
+ if(!istype(user))
+ return
+- if(user.stat || stat & (NOPOWER|BROKEN))
++ if(user.incapacitated() || !is_operational())
++ return
++
++ if(outgoing_call) //someone is making a call, leave them be
+ return
++
+ user.set_machine(src)
+ var/dat
+ if(temp)
+ dat = temp
+ else
+- dat = "request an AI's presence."
++ dat = "Request an AI's presence.
"
++ dat += "Call another holopad.
"
++
++ if(LAZYLEN(holo_calls))
++ dat += "=====================================================
"
++
++ var/one_answered_call = FALSE
++ var/one_unanswered_call = FALSE
++ for(var/I in holo_calls)
++ var/datum/holocall/HC = I
++ if(HC.connected_holopad != src)
++ dat += "Answer call from [get_area(HC.calling_holopad)].
"
++ one_unanswered_call = TRUE
++ else
++ one_answered_call = TRUE
++
++ if(one_answered_call && one_unanswered_call)
++ dat += "=====================================================
"
++ //we loop twice for formatting
++ for(var/I in holo_calls)
++ var/datum/holocall/HC = I
++ if(HC.connected_holopad == src)
++ dat += "Disconnect call from [HC.user].
"
++
+
+ var/datum/browser/popup = new(user, "holopad", name, 300, 130)
+ popup.set_content(dat)
+@@ -115,7 +162,10 @@ var/list/holopads = list()
+ popup.open()
+
+ /obj/machinery/holopad/Topic(href, href_list)
+- if(..())
++ if(..() || isAI(usr))
++ return
++ add_fingerprint(usr)
++ if(!is_operational())
+ return
+ if (href_list["AIrequest"])
+ if(last_request + 200 < world.time)
+@@ -194,59 +317,81 @@ For the other part of the code, check silicon say.dm. Particularly robot talk.*/
+ for(var/mob/living/silicon/ai/master in masters)
+ if(masters[master] && speaker != master)
+ master.relay_speech(message, speaker, message_langs, raw_message, radio_freq, spans)
++
++ for(var/I in holo_calls)
++ var/datum/holocall/HC = I
++ if(HC.connected_holopad == src && speaker != HC.hologram)
++ HC.user.Hear(message, speaker, message_langs, raw_message, radio_freq, spans)
++
++ if(outgoing_call && speaker == outgoing_call.user)
++ outgoing_call.hologram.say(raw_message)
++
++/obj/machinery/holopad/proc/SetLightsAndPower()
++ var/total_users = masters.len + LAZYLEN(holo_calls)
++ use_power = HOLOPAD_PASSIVE_POWER_USAGE + HOLOGRAM_POWER_USAGE * total_users
++ if(total_users)
++ set_light(2)
++ icon_state = "holopad1"
++ else
++ set_light(0)
++ icon_state = "holopad0"
+
+-/obj/machinery/holopad/proc/create_holo(mob/living/silicon/ai/A, turf/T = loc)
+- var/obj/effect/overlay/holo_pad_hologram/h = new(T)//Spawn a blank effect at the location.
+- h.icon = A.holo_icon
+- h.mouse_opacity = 0//So you can't click on it.
+- h.layer = FLY_LAYER//Above all the other objects/mobs. Or the vast majority of them.
+- h.anchored = 1//So space wind cannot drag it.
+- h.name = "[A.name] (Hologram)"//If someone decides to right click.
+- h.set_light(2) //hologram lighting
+- set_holo(A, h)
+- return TRUE
+-
+-/obj/machinery/holopad/proc/set_holo(mob/living/silicon/ai/A, var/obj/effect/overlay/holo_pad_hologram/h)
+- masters[A] = h
+- set_light(2) // pad lighting
+- icon_state = "holopad1"
+- A.current = src
+- use_power += HOLOGRAM_POWER_USAGE
++/obj/machinery/holopad/proc/set_holo(mob/living/user, var/obj/effect/overlay/holo_pad_hologram/h)
++ masters[user] = h
++ var/mob/living/silicon/ai/AI = user
++ if(istype(AI))
++ AI.current = src
++ SetLightsAndPower()
+ return TRUE
+
+-/obj/machinery/holopad/proc/clear_holo(mob/living/silicon/ai/user)
++/obj/machinery/holopad/proc/clear_holo(mob/living/user)
+ qdel(masters[user]) // Get rid of user's hologram
+ unset_holo(user)
+ return TRUE
+
+-/obj/machinery/holopad/proc/unset_holo(mob/living/silicon/ai/user)
+- if(user.current == src)
+- user.current = null
++/obj/machinery/holopad/proc/unset_holo(mob/living/user)
++ var/mob/living/silicon/ai/AI = user
++ if(istype(AI) && AI.current == src)
++ AI.current = null
+ masters -= user // Discard AI from the list of those who use holopad
+- use_power = max(HOLOPAD_PASSIVE_POWER_USAGE, use_power - HOLOGRAM_POWER_USAGE)//Reduce power usage
+- if (!masters.len) // If no users left
+- set_light(0) // pad lighting (hologram lighting will be handled automatically since its owner was deleted)
+- icon_state = "holopad0"
+- use_power = HOLOPAD_PASSIVE_POWER_USAGE
++ SetLightsAndPower()
+ return TRUE
+
+-/obj/machinery/holopad/proc/move_hologram(mob/living/silicon/ai/user)
++/obj/machinery/holopad/proc/move_hologram(mob/living/user, turf/new_turf)
+ if(masters[user])
+- step_to(masters[user], user.eyeobj)
+ var/obj/effect/overlay/holo_pad_hologram/H = masters[user]
+- H.loc = get_turf(user.eyeobj)
++ step_to(H, new_turf)
++ H.loc = new_turf
++ var/area/holo_area = get_area(src)
++ var/area/eye_area = new_turf.loc
++
++ if(!(eye_area in holo_area.related))
++ clear_holo(user)
+ return TRUE
+
++/obj/effect/overlay/holo_pad_hologram
++ var/mob/living/Impersonation
++ var/datum/holocall/HC
++
++/obj/effect/overlay/holo_pad_hologram/Destroy()
++ Impersonation = null
++ if(HC)
++ HC.Disconnect(HC.calling_holopad)
++ return ..()
++
+ /obj/effect/overlay/holo_pad_hologram/Process_Spacemove(movement_dir = 0)
+ return 1
+
++/obj/effect/overlay/holo_pad_hologram/examine(mob/user)
++ if(Impersonation)
++ return Impersonation.examine(user)
++ return ..()
++
+ /obj/item/weapon/circuitboard/machine/holopad
+ name = "AI Holopad (Machine Board)"
+ build_path = /obj/machinery/holopad
+ origin_tech = "programming=1"
+ req_components = list(/obj/item/weapon/stock_parts/capacitor = 1)
+
+-#undef RANGE_BASED
+-#undef AREA_BASED
+ #undef HOLOPAD_PASSIVE_POWER_USAGE
+-#undef HOLOGRAM_POWER_USAGE
++#undef HOLOGRAM_POWER_USAGE
+\ No newline at end of file
diff --git a/code/modules/mob/living/silicon/ai/freelook/eye.dm b/code/modules/mob/living/silicon/ai/freelook/eye.dm
index dce38bd664..56c43211e4 100644
--- a/code/modules/mob/living/silicon/ai/freelook/eye.dm
+++ b/code/modules/mob/living/silicon/ai/freelook/eye.dm
@@ -28,7 +28,7 @@
//Holopad
if(istype(ai.current, /obj/machinery/holopad))
var/obj/machinery/holopad/H = ai.current
- H.move_hologram(ai)
+ H.move_hologram(ai, T)
/mob/camera/aiEye/Move()
return 0
diff --git a/tgstation.dme b/tgstation.dme
index 1f3ef27802..bff0d9a902 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -244,6 +244,7 @@
#include "code\datums\dog_fashion.dm"
#include "code\datums\emotes.dm"
#include "code\datums\forced_movement.dm"
+#include "code\datums\holocall.dm"
#include "code\datums\hud.dm"
#include "code\datums\map_config.dm"
#include "code\datums\martial.dm"
diff --git a/tgstation.dme.rej b/tgstation.dme.rej
new file mode 100644
index 0000000000..0f29d8f023
--- /dev/null
+++ b/tgstation.dme.rej
@@ -0,0 +1,9 @@
+diff a/tgstation.dme b/tgstation.dme (rejected hunks)
+@@ -206,6 +206,7 @@
+ #include "code\datums\emotes.dm"
+ #include "code\datums\forced_movement.dm"
+ #include "code\datums\gas_overrides.dm"
++#include "code\datums\holocall.dm"
+ #include "code\datums\hud.dm"
+ #include "code\datums\map_config.dm"
+ #include "code\datums\martial.dm"