#define HOLOPAD_MAX_DIAL_TIME 200 #define HOLORECORD_DELAY "delay" #define HOLORECORD_SAY "say" #define HOLORECORD_SOUND "sound" #define HOLORECORD_LANGUAGE "lang" #define HOLORECORD_PRESET "preset" #define HOLORECORD_RENAME "rename" #define HOLORECORD_MAX_LENGTH 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/datum/action/innate/end_holocall/hangup //hangup action 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 H.say("Incoming call.") LAZYADD(H.holo_calls, src) if(!dialed_holopads.len) calling_pad.say("Connection failure.") qdel(src) return testing("Holocall started") //cleans up ALL references :) /datum/holocall/Destroy() QDEL_NULL(hangup) var/user_good = !QDELETED(user) if(user_good) user.reset_perspective() user.remote_control = null if(!QDELETED(eye)) QDEL_NULL(eye) if(connected_holopad && !QDELETED(hologram)) hologram = null connected_holopad.clear_holo(user) user = null //Hologram survived holopad destro if(!QDELETED(hologram)) hologram.HC = null QDEL_NULL(hologram) 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.outgoing_call = null 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) var/area/A = get_area(connected_holopad) calling_holopad.say("[A] holopad disconnected.") else if(H == calling_holopad && connected_holopad) connected_holopad.say("[user] 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 && H != calling_holopad) 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") for(var/I in dialed_holopads) if(I == H) continue Disconnect(I) for(var/I in H.holo_calls) var/datum/holocall/HC = I if(HC != src) HC.Disconnect(H) connected_holopad = H 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) hangup = new(eye, src) hangup.Grant(user) //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) . = world.time < (call_start_time + HOLOPAD_MAX_DIAL_TIME) if(!.) calling_holopad.say("No answer received.") calling_holopad.temp = "" if(!.) testing("Holocall Check fail") qdel(src) /datum/action/innate/end_holocall name = "End Holocall" icon_icon = 'icons/mob/actions/actions_silicon.dmi' button_icon_state = "camera_off" var/datum/holocall/hcall /datum/action/innate/end_holocall/New(Target, datum/holocall/HC) ..() hcall = HC /datum/action/innate/end_holocall/Activate() hcall.Disconnect(hcall.calling_holopad) //RECORDS /datum/holorecord var/caller_name = "Unknown" //Caller name var/image/caller_image var/list/entries = list() var/language = /datum/language/common //Initial language, can be changed by HOLORECORD_LANGUAGE entries /datum/holorecord/proc/set_caller_image(mob/user) var/olddir = user.dir user.setDir(SOUTH) caller_image = image(user) user.setDir(olddir) /obj/item/disk/holodisk name = "holorecord disk" desc = "Stores recorder holocalls." icon_state = "holodisk" obj_flags = UNIQUE_RENAME materials = list(MAT_METAL = 100, MAT_GLASS = 100) var/datum/holorecord/record //Preset variables var/preset_image_type var/preset_record_text /obj/item/disk/holodisk/Initialize(mapload) . = ..() if(preset_record_text) build_record() /obj/item/disk/holodisk/Destroy() QDEL_NULL(record) return ..() /obj/item/disk/holodisk/attackby(obj/item/W, mob/user, params) if(istype(W, /obj/item/disk/holodisk)) var/obj/item/disk/holodisk/holodiskOriginal = W if (holodiskOriginal.record) if (!record) record = new record.caller_name = holodiskOriginal.record.caller_name record.caller_image = holodiskOriginal.record.caller_image record.entries = holodiskOriginal.record.entries.Copy() record.language = holodiskOriginal.record.language to_chat(user, "You copy the record from [holodiskOriginal] to [src] by connecting the ports!") name = holodiskOriginal.name else to_chat(user, "[holodiskOriginal] has no record on it!") ..() /obj/item/disk/holodisk/proc/build_record() record = new var/list/lines = splittext(preset_record_text,"\n") for(var/line in lines) var/prepared_line = trim(line) if(!length(prepared_line)) continue var/splitpoint = findtext(prepared_line," ") if(!splitpoint) continue var/command = copytext(prepared_line, 1, splitpoint) var/value = copytext(prepared_line, splitpoint + length(prepared_line[splitpoint])) switch(command) if("DELAY") var/delay_value = text2num(value) if(!delay_value) continue record.entries += list(list(HOLORECORD_DELAY,delay_value)) if("NAME") if(!record.caller_name) record.caller_name = value else record.entries += list(list(HOLORECORD_RENAME,value)) if("SAY") record.entries += list(list(HOLORECORD_SAY,value)) if("SOUND") record.entries += list(list(HOLORECORD_SOUND,value)) if("LANGUAGE") var/lang_type = text2path(value) if(ispath(lang_type,/datum/language)) record.entries += list(list(HOLORECORD_LANGUAGE,lang_type)) if("PRESET") var/preset_type = text2path(value) if(ispath(preset_type,/datum/preset_holoimage)) record.entries += list(list(HOLORECORD_PRESET,preset_type)) if(!preset_image_type) record.caller_image = image('icons/mob/animal.dmi',"old") else var/datum/preset_holoimage/H = new preset_image_type record.caller_image = H.build_image() //These build caller image from outfit and some additional data, for use by mappers for ruin holorecords /datum/preset_holoimage var/nonhuman_mobtype //Fill this if you just want something nonhuman var/outfit_type var/species_type = /datum/species/human /datum/preset_holoimage/proc/build_image() if(nonhuman_mobtype) var/mob/living/L = nonhuman_mobtype . = image(initial(L.icon),initial(L.icon_state)) else var/mob/living/carbon/human/dummy/mannequin = generate_or_wait_for_human_dummy("HOLODISK_PRESET") if(species_type) mannequin.set_species(species_type) if(outfit_type) mannequin.equipOutfit(outfit_type,TRUE) mannequin.setDir(SOUTH) COMPILE_OVERLAYS(mannequin) . = image(mannequin) unset_busy_human_dummy("HOLODISK_PRESET") /obj/item/disk/holodisk/example preset_image_type = /datum/preset_holoimage/clown preset_record_text = {" NAME Clown DELAY 10 SAY Why did the chaplain cross the maint ? DELAY 20 SAY He wanted to get to the other side! SOUND clownstep DELAY 30 LANGUAGE /datum/language/narsie SAY Helped him get there! DELAY 10 SAY ALSO IM SECRETLY A GORILLA DELAY 10 PRESET /datum/preset_holoimage/gorilla NAME Gorilla LANGUAGE /datum/language/common SAY OOGA DELAY 20"} /datum/preset_holoimage/engineer outfit_type = /datum/outfit/job/engineer/gloved/rig /datum/preset_holoimage/researcher outfit_type = /datum/outfit/job/scientist /datum/preset_holoimage/captain outfit_type = /datum/outfit/job/captain /datum/preset_holoimage/nanotrasenprivatesecurity outfit_type = /datum/outfit/nanotrasensoldiercorpse2 /datum/preset_holoimage/gorilla nonhuman_mobtype = /mob/living/simple_animal/hostile/gorilla /datum/preset_holoimage/corgi nonhuman_mobtype = /mob/living/simple_animal/pet/dog/corgi /datum/preset_holoimage/clown outfit_type = /datum/outfit/job/clown