<commit_before>/*
	Holodeck Update

	The on-station holodeck area is of type [holodeck_type].
	All subtypes of [program_type] are loaded into the program cache or emag programs list.
	If init_program is null, a random program will be loaded on startup.
	If you don't wish this, set it to the offline program or another of your choosing.

	You can use this to add holodecks with minimal code:
	1) Define new areas for the holodeck programs
	2) Map them
	3) Create a new control console that uses those areas

	Non-mapped areas should be skipped but you should probably comment them out anyway.
	The base of program_type will always be ignored; only subtypes will be loaded.
*/

/obj/machinery/computer/holodeck
	name = "holodeck control console"
	desc = "A computer used to control a nearby holodeck."
	icon_screen = "holocontrol"
	idle_power_usage = 10
	active_power_usage = 50
	var/area/holodeck/linked
	var/area/holodeck/program
	var/area/holodeck/last_program
	var/area/offline_program = /area/holodeck/rec_center/offline

	var/list/program_cache = list()
	var/list/emag_programs = list()

	// Splitting this up allows two holodecks of the same size
	// to use the same source patterns.  Y'know, if you want to.
	var/holodeck_type = /area/holodeck/rec_center	// locate(this) to get the target holodeck
	var/program_type = /area/holodeck/rec_center	// subtypes of this (but not this itself) are loadable programs

	// set this if you want it to start with some particular program.
	var/init_program = null
	// or this to get anything
	var/random_program = 0

	var/active = 0
	var/damaged = 0
	var/list/spawned = list()
	var/list/effects = list()
	var/last_change = 0

/obj/machinery/computer/holodeck/New()

	if(ispath(holodeck_type,/area))
		linked = locate(holodeck_type)
	if(ispath(offline_program,/area))
		offline_program = locate(offline_program)
	// the following is necessary for power reasons
	var/area/AS = get_area(src)
	if(istype(AS,/area/holodeck))
		world.log << "### MAPPING ERROR"
		world.log << "Holodeck computer cannot be in a holodeck."
		world.log << "This would cause circular power dependency."
		qdel(src)  // todo handle constructed computers
		return
	else
		linked.linked = src // todo detect multiple/constructed computers

	if(ticker && ticker.current_state >= GAME_STATE_PLAYING)
		initialize()
	..()

/obj/machinery/computer/holodeck/initialize()
	program_cache = list()
	emag_programs = list()
	for(var/typekey in subtypesof(program_type))
		var/area/holodeck/A = locate(typekey)
		if(!A || A == offline_program) continue
		if(A.contents.len == 0) continue // not loaded
		if(A.restricted)
			emag_programs += A
		else
			program_cache += A
		if(typekey == init_program)
			load_program(A,force=1)
	if(random_program && program_cache.len && init_program == null)
		load_program(pick(program_cache),force=1)
	else if(!program)
		load_program(offline_program)

/obj/machinery/computer/holodeck/power_change()
	..()
	toggle_power(!stat)

/obj/machinery/computer/holodeck/proc/toggle_power(toggleOn = 0)
	if(active == toggleOn) return

	if(toggleOn)
		if(last_program && last_program != offline_program)
			load_program(last_program, delay = 1)
		active = 1
	else
		last_program = program
		load_program(offline_program,force=1)
		active = 0

/obj/machinery/computer/holodeck/proc/emergency_shutdown()
	last_program = program
	load_program(offline_program,1)
	active = 0

/obj/machinery/computer/holodeck/process()
	if(damaged)
		if(prob(10))
			for(var/turf/T in linked)
				if(prob(5))
					var/datum/effect_system/spark_spread/s = new
					s.set_up(2, 1, T)
					s.start()
					return

	if(!..() || !active)
		return

	if(!floorcheck())
		emergency_shutdown()
		damaged = 1
		for(var/mob/M in urange(10,src))
			M.show_message("The holodeck overloads!")

		for(var/turf/T in linked)
			if(prob(30))
				var/datum/effect_system/spark_spread/s = new
				s.set_up(2, 1, T)
				s.start()
			T.ex_act(3)
			T.hotspot_expose(1000,500,1)

	if(!emagged)
		for(var/item in spawned)
			if(!(get_turf(item) in linked))
				derez(item, 0)
	for(var/obj/effect/holodeck_effect/HE in effects)
		HE.tick()

	active_power_usage = 50 + spawned.len * 3 + effects.len * 5

/obj/machinery/computer/holodeck/proc/floorcheck()
	for(var/turf/T in linked)
		if(!T.intact || istype(T,/turf/open/space))
			return 0
	return 1

/obj/machinery/computer/holodeck/Topic(href, list/href_list)
	if(..())
		return
	if(!Adjacent(usr) && !istype(usr, /mob/living/silicon))
		return
	usr.set_machine(src)
	add_fingerprint(usr)
	if(href_list["loadarea"])
		var/areapath = text2path(href_list["loadarea"])
		if(!ispath(areapath, /area/holodeck))
			return
		var/area/holodeck/area = locate(areapath)
		if(!istype(area))
			return
		if(area == offline_program || (area in program_cache) || (emagged && (area in emag_programs)))
			load_program(area)
	else if("safety" in href_list)
		var/safe = text2num(href_list["safety"])
		emagged = !safe
		if(!program)
			return
		if(safe && (program in emag_programs))
			emergency_shutdown()
		nerf(safe)
	updateUsrDialog()

/obj/machinery/computer/holodeck/proc/nerf(active)
	for(var/obj/item/I in spawned)
		I.damtype = (active? STAMINA : initial(I.damtype) )
	for(var/obj/effect/holodeck_effect/HE in effects)
		HE.safety(active)

/obj/machinery/computer/holodeck/emag_act(mob/user as mob)
	if(!emagged)
		if(!emag_programs.len)
			user << "[src] does not seem to have a card swipe port.  It must be an inferior model."
			return
		playsound(loc, 'sound/effects/sparks4.ogg', 75, 1)
		emagged = 1
		user << "<span class='warning'>You vastly increase projector power and override the safety and security protocols.</span>"
		user << "Warning.  Automatic shutoff and derezing protocols have been corrupted.  Please call Nanotrasen maintenance and do not use the simulator."
		log_game("[key_name(user)] emagged the Holodeck Control Console")
		updateUsrDialog()
		nerf(!emagged)

/obj/machinery/computer/holodeck/Destroy()<commit_msg>Fix holodeck safeties href exploit.<commit_after>/*
	Holodeck Update

	The on-station holodeck area is of type [holodeck_type].
	All subtypes of [program_type] are loaded into the program cache or emag programs list.
	If init_program is null, a random program will be loaded on startup.
	If you don't wish this, set it to the offline program or another of your choosing.

	You can use this to add holodecks with minimal code:
	1) Define new areas for the holodeck programs
	2) Map them
	3) Create a new control console that uses those areas

	Non-mapped areas should be skipped but you should probably comment them out anyway.
	The base of program_type will always be ignored; only subtypes will be loaded.
*/

/obj/machinery/computer/holodeck
	name = "holodeck control console"
	desc = "A computer used to control a nearby holodeck."
	icon_screen = "holocontrol"
	idle_power_usage = 10
	active_power_usage = 50
	var/area/holodeck/linked
	var/area/holodeck/program
	var/area/holodeck/last_program
	var/area/offline_program = /area/holodeck/rec_center/offline

	var/list/program_cache = list()
	var/list/emag_programs = list()

	// Splitting this up allows two holodecks of the same size
	// to use the same source patterns.  Y'know, if you want to.
	var/holodeck_type = /area/holodeck/rec_center	// locate(this) to get the target holodeck
	var/program_type = /area/holodeck/rec_center	// subtypes of this (but not this itself) are loadable programs

	// set this if you want it to start with some particular program.
	var/init_program = null
	// or this to get anything
	var/random_program = 0

	var/active = 0
	var/damaged = 0
	var/list/spawned = list()
	var/list/effects = list()
	var/last_change = 0

/obj/machinery/computer/holodeck/New()

	if(ispath(holodeck_type,/area))
		linked = locate(holodeck_type)
	if(ispath(offline_program,/area))
		offline_program = locate(offline_program)
	// the following is necessary for power reasons
	var/area/AS = get_area(src)
	if(istype(AS,/area/holodeck))
		world.log << "### MAPPING ERROR"
		world.log << "Holodeck computer cannot be in a holodeck."
		world.log << "This would cause circular power dependency."
		qdel(src)  // todo handle constructed computers
		return
	else
		linked.linked = src // todo detect multiple/constructed computers

	if(ticker && ticker.current_state >= GAME_STATE_PLAYING)
		initialize()
	..()

/obj/machinery/computer/holodeck/initialize()
	program_cache = list()
	emag_programs = list()
	for(var/typekey in subtypesof(program_type))
		var/area/holodeck/A = locate(typekey)
		if(!A || A == offline_program) continue
		if(A.contents.len == 0) continue // not loaded
		if(A.restricted)
			emag_programs += A
		else
			program_cache += A
		if(typekey == init_program)
			load_program(A,force=1)
	if(random_program && program_cache.len && init_program == null)
		load_program(pick(program_cache),force=1)
	else if(!program)
		load_program(offline_program)

/obj/machinery/computer/holodeck/power_change()
	..()
	toggle_power(!stat)

/obj/machinery/computer/holodeck/proc/toggle_power(toggleOn = 0)
	if(active == toggleOn) return

	if(toggleOn)
		if(last_program && last_program != offline_program)
			load_program(last_program, delay = 1)
		active = 1
	else
		last_program = program
		load_program(offline_program,force=1)
		active = 0

/obj/machinery/computer/holodeck/proc/emergency_shutdown()
	last_program = program
	load_program(offline_program,1)
	active = 0

/obj/machinery/computer/holodeck/process()
	if(damaged)
		if(prob(10))
			for(var/turf/T in linked)
				if(prob(5))
					var/datum/effect_system/spark_spread/s = new
					s.set_up(2, 1, T)
					s.start()
					return

	if(!..() || !active)
		return

	if(!floorcheck())
		emergency_shutdown()
		damaged = 1
		for(var/mob/M in urange(10,src))
			M.show_message("The holodeck overloads!")

		for(var/turf/T in linked)
			if(prob(30))
				var/datum/effect_system/spark_spread/s = new
				s.set_up(2, 1, T)
				s.start()
			T.ex_act(3)
			T.hotspot_expose(1000,500,1)

	if(!emagged)
		for(var/item in spawned)
			if(!(get_turf(item) in linked))
				derez(item, 0)
	for(var/obj/effect/holodeck_effect/HE in effects)
		HE.tick()

	active_power_usage = 50 + spawned.len * 3 + effects.len * 5

/obj/machinery/computer/holodeck/proc/floorcheck()
	for(var/turf/T in linked)
		if(!T.intact || istype(T,/turf/open/space))
			return 0
	return 1

/obj/machinery/computer/holodeck/Topic(href, list/href_list)
	if(..())
		return
	if(!Adjacent(usr) && !istype(usr, /mob/living/silicon))
		return
	usr.set_machine(src)
	add_fingerprint(usr)
	if(href_list["loadarea"])
		var/areapath = text2path(href_list["loadarea"])
		if(!ispath(areapath, /area/holodeck))
			return
		var/area/holodeck/area = locate(areapath)
		if(!istype(area))
			return
		if(area == offline_program || (area in program_cache) || (emagged && (area in emag_programs)))
			load_program(area)
	else if("safety" in href_list)
		if(!issilicon(usr))
			message_admins("EXPLOIT: [usr] attempted to override [src]'s safeties without being a silicon.")
			return
		var/safe = text2num(href_list["safety"])
		emagged = !safe
		if(!program)
			return
		if(safe && (program in emag_programs))
			emergency_shutdown()
		nerf(safe)
	updateUsrDialog()

/obj/machinery/computer/holodeck/proc/nerf(active)
	for(var/obj/item/I in spawned)
		I.damtype = (active? STAMINA : initial(I.damtype) )
	for(var/obj/effect/holodeck_effect/HE in effects)
		HE.safety(active)

/obj/machinery/computer/holodeck/emag_act(mob/user as mob)
	if(!emagged)
		if(!emag_programs.len)
			user << "[src] does not seem to have a card swipe port.  It must be an inferior model."
			return
		playsound(loc, 'sound/effects/sparks4.ogg', 75, 1)
		emagged = 1
		user << "<span class='warning'>You vastly increase projector power and override the safety and security protocols.</span>"
		user << "Warning.  Automatic shutoff and derezing protocols have been corrupted.  Please call Nanotrasen maintenance and do not use the simulator."
		log_game("[key_name(user)] emagged the Holodeck Control Console")
		updateUsrDialog()
		nerf(!emagged)

/obj/machinery/computer/holodeck/Destroy()
{'fraction_non_alphanumeric': 0.11768111147866359, 'fraction_numerical': 0.009262322196493549, 'mean_word_length': 4.185677530017153, 'pattern_counts': {'":': 0, '<': 31, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4019641', 'n_tokens_mistral': 4778, 'n_tokens_neox': 4303, 'n_words': 1070}