diff --git a/code/modules/integrated_electronics/subtypes/input.dm b/code/modules/integrated_electronics/subtypes/input.dm
index 057f23486a3..50cfddd1f81 100644
--- a/code/modules/integrated_electronics/subtypes/input.dm
+++ b/code/modules/integrated_electronics/subtypes/input.dm
@@ -102,8 +102,13 @@
complexity = 4
inputs = list("target" = IC_PINTYPE_REF)
outputs = list(
- "total health %" = IC_PINTYPE_NUMBER,
- "total missing health" = IC_PINTYPE_NUMBER
+ // Basic medical analyser stuff
+ "brain activity" = IC_PINTYPE_NUMBER,
+ "heart pulse" = IC_PINTYPE_NUMBER,
+ "blood pressure" = IC_PINTYPE_LIST,
+ "blood oxygenation" = IC_PINTYPE_NUMBER,
+ "body temperature" = IC_PINTYPE_NUMBER,
+ "in cardiac arrest?" = IC_PINTYPE_BOOLEAN,
)
activators = list("scan" = IC_PINTYPE_PULSE_IN, "on scanned" = IC_PINTYPE_PULSE_OUT)
spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH
@@ -114,12 +119,18 @@
var/mob/living/carbon/human/H = get_pin_data_as_type(IC_INPUT, 1, /mob/living/carbon/human)
if(!istype(H)) //Invalid input
return
- if(H.Adjacent(get_turf(src))) // Like normal analysers, it can't be used at range.
- var/total_health = round(H.health/H.getMaxHealth(), 0.01)*100
- var/missing_health = H.getMaxHealth() - H.health
- set_pin_data(IC_OUTPUT, 1, total_health)
- set_pin_data(IC_OUTPUT, 2, missing_health)
+ if(H.isSynthetic() || H.is_diona()) // incompatible anatomy
+ return
+
+ if(H.Adjacent(get_turf(src))) // Like normal analysers, it can't be used at range.
+ // Basic medical analyser stuff
+ set_pin_data(IC_OUTPUT, 1, H.get_brain_result())
+ set_pin_data(IC_OUTPUT, 2, H.get_pulse_as_number(GETPULSE_TOOL))
+ set_pin_data(IC_OUTPUT, 3, H.blood_pressure())
+ set_pin_data(IC_OUTPUT, 4, H.get_blood_oxygenation())
+ set_pin_data(IC_OUTPUT, 5, H.bodytemperature)
+ set_pin_data(IC_OUTPUT, 6, H.is_asystole())
push_data()
activate_pin(2)
@@ -132,13 +143,19 @@
complexity = 12
inputs = list("target" = IC_PINTYPE_REF)
outputs = list(
- "total health %" = IC_PINTYPE_NUMBER,
- "total missing health" = IC_PINTYPE_NUMBER,
- "brute damage" = IC_PINTYPE_NUMBER,
- "burn damage" = IC_PINTYPE_NUMBER,
- "tox damage" = IC_PINTYPE_NUMBER,
- "oxy damage" = IC_PINTYPE_NUMBER,
- "clone damage" = IC_PINTYPE_NUMBER
+ // Basic medical analyser stuff
+ "brain activity" = IC_PINTYPE_NUMBER,
+ "heart pulse" = IC_PINTYPE_NUMBER,
+ "blood pressure" = IC_PINTYPE_LIST,
+ "blood oxygenation" = IC_PINTYPE_NUMBER,
+ "body temperature" = IC_PINTYPE_NUMBER,
+ "in cardiac arrest?" = IC_PINTYPE_BOOLEAN,
+ // Advanced
+ "brute damage" = IC_PINTYPE_NUMBER,
+ "burn damage" = IC_PINTYPE_NUMBER,
+ "toxin damage" = IC_PINTYPE_NUMBER,
+ "radiation dose" = IC_PINTYPE_NUMBER,
+ "genetic instability" = IC_PINTYPE_NUMBER
)
activators = list("scan" = IC_PINTYPE_PULSE_IN, "on scanned" = IC_PINTYPE_PULSE_OUT)
spawn_flags = IC_SPAWN_RESEARCH
@@ -149,17 +166,24 @@
var/mob/living/carbon/human/H = get_pin_data_as_type(IC_INPUT, 1, /mob/living/carbon/human)
if(!istype(H)) //Invalid input
return
- if(H.Adjacent(get_turf(src))) // Like normal analysers, it can't be used at range.
- var/total_health = round(H.health/H.getMaxHealth(), 0.01)*100
- var/missing_health = H.getMaxHealth() - H.health
- set_pin_data(IC_OUTPUT, 1, total_health)
- set_pin_data(IC_OUTPUT, 2, missing_health)
- set_pin_data(IC_OUTPUT, 3, H.getBruteLoss())
- set_pin_data(IC_OUTPUT, 4, H.getFireLoss())
- set_pin_data(IC_OUTPUT, 5, H.getToxLoss())
- set_pin_data(IC_OUTPUT, 6, H.getOxyLoss())
- set_pin_data(IC_OUTPUT, 7, H.getCloneLoss())
+ if(H.isSynthetic() || H.is_diona()) // incompatible anatomy
+ return
+
+ if(H.Adjacent(get_turf(src))) // Like normal analysers, it can't be used at range.
+ // Basic medical analyser stuff
+ set_pin_data(IC_OUTPUT, 1, H.get_brain_result())
+ set_pin_data(IC_OUTPUT, 2, H.get_pulse_as_number(GETPULSE_TOOL))
+ set_pin_data(IC_OUTPUT, 3, H.blood_pressure())
+ set_pin_data(IC_OUTPUT, 4, H.get_blood_oxygenation())
+ set_pin_data(IC_OUTPUT, 5, H.bodytemperature)
+ set_pin_data(IC_OUTPUT, 6, H.is_asystole())
+ // Advanced
+ set_pin_data(IC_OUTPUT, 7, H.getBruteLoss())
+ set_pin_data(IC_OUTPUT, 8, H.getFireLoss())
+ set_pin_data(IC_OUTPUT, 9, H.getToxLoss())
+ set_pin_data(IC_OUTPUT, 10, H.total_radiation)
+ set_pin_data(IC_OUTPUT, 11, H.getCloneLoss())
push_data()
activate_pin(2)
@@ -532,7 +556,7 @@
GAS_DEUTERIUM = IC_PINTYPE_NUMBER,
GAS_TRITIUM = IC_PINTYPE_NUMBER,
GAS_HELIUM = IC_PINTYPE_NUMBER,
- GAS_HELIUMFUE = IC_PINTYPE_NUMBER,
+ GAS_HELIUMFUEL = IC_PINTYPE_NUMBER,
GAS_SULFUR = IC_PINTYPE_NUMBER,
GAS_NO2 = IC_PINTYPE_NUMBER,
GAS_CHLORINE = IC_PINTYPE_NUMBER,
@@ -760,9 +784,6 @@
var/gas_display_name = GAS_OXYGEN
/obj/item/integrated_circuit/input/gas_sensor/Initialize()
- name = "[gas_display_name] sensor"
- displayed_name = "[gas_display_name] sensor"
- desc = "A tiny [gas_display_name] sensor module similar to that found in a PDA atmosphere analyser."
outputs = list(
gas_name = IC_PINTYPE_NUMBER
)
@@ -785,50 +806,86 @@
activate_pin(2)
/obj/item/integrated_circuit/input/gas_sensor/co2
+ name = "carbon dioxide sensor"
+ displayed_name = "carbon dioxide sensor"
+ desc = "A tiny carbon dioxide sensor module similar to that found in a PDA atmosphere analyser."
gas_name = GAS_CO2
gas_display_name = "carbon dioxide"
/obj/item/integrated_circuit/input/gas_sensor/nitrogen
+ name = "nitrogen sensor"
+ displayed_name = "nitrogen sensor"
+ desc = "A tiny nitrogen sensor module similar to that found in a PDA atmosphere analyser."
gas_name = GAS_NITROGEN
gas_display_name = "nitrogen"
/obj/item/integrated_circuit/input/gas_sensor/phoron
+ name = "phoron sensor"
+ displayed_name = "phoron sensor"
+ desc = "A tiny phoron sensor module similar to that found in a PDA atmosphere analyser."
gas_name = GAS_PHORON
gas_display_name = GAS_PHORON
/obj/item/integrated_circuit/input/gas_sensor/hydrogen_level
+ name = "hydrogen sensor"
+ displayed_name = "hydrogen sensor"
+ desc = "A tiny hydrogen sensor module similar to that found in a PDA atmosphere analyser."
gas_name = GAS_HYDROGEN
gas_display_name = GAS_HYDROGEN
/obj/item/integrated_circuit/input/gas_sensor/deuterium_level
+ name = "deuterium sensor"
+ displayed_name = "deuterium sensor"
+ desc = "A tiny deuterium sensor module similar to that found in a PDA atmosphere analyser."
gas_name = GAS_DEUTERIUM
gas_display_name = GAS_DEUTERIUM
/obj/item/integrated_circuit/input/gas_sensor/tritium_level
+ name = "tritium sensor"
+ displayed_name = "tritium sensor"
+ desc = "A tiny tritium sensor module similar to that found in a PDA atmosphere analyser."
gas_name = GAS_TRITIUM
gas_display_name = GAS_TRITIUM
/obj/item/integrated_circuit/input/gas_sensor/helium_level
+ name = "helium sensor"
+ displayed_name = "helium sensor"
+ desc = "A tiny helium sensor module similar to that found in a PDA atmosphere analyser."
gas_name = GAS_HELIUM
gas_display_name = GAS_HELIUM
/obj/item/integrated_circuit/input/gas_sensor/helium3_level
+ name = "helium-3 sensor"
+ displayed_name = "helium-3 sensor"
+ desc = "A tiny helium-3 sensor module similar to that found in a PDA atmosphere analyser."
gas_name = GAS_HELIUMFUEL
gas_display_name = GAS_HELIUMFUEL
/obj/item/integrated_circuit/input/gas_sensor/sulfurdioxide_level
+ name = "sulphur dioxide sensor"
+ displayed_name = "sulphur dioxide sensor"
+ desc = "A tiny sulphur dioxide sensor module similar to that found in a PDA atmosphere analyser."
gas_name = GAS_SULFUR
gas_display_name = GAS_SULFUR
/obj/item/integrated_circuit/input/gas_sensor/nitrogendioxide_level
+ name = "nitrogen dioxide sensor"
+ displayed_name = "nitrogen dioxide sensor"
+ desc = "A tiny nitrogen dioxide sensor module similar to that found in a PDA atmosphere analyser."
gas_name = GAS_NO2
gas_display_name = GAS_NO2
/obj/item/integrated_circuit/input/gas_sensor/chlorine_level
+ name = "chlorine sensor"
+ displayed_name = "chlorine sensor"
+ desc = "A tiny chlorine sensor module similar to that found in a PDA atmosphere analyser."
gas_name = GAS_CHLORINE
gas_display_name = GAS_CHLORINE
/obj/item/integrated_circuit/input/gas_sensor/watervapor_level
+ name = "water vapour sensor"
+ displayed_name = "water vapour sensor"
+ desc = "A tiny water vapour sensor module similar to that found in a PDA atmosphere analyser."
gas_name = GAS_WATERVAPOR
gas_display_name = GAS_WATERVAPOR
@@ -1073,3 +1130,129 @@
push_data()
activate_pin(1)
return TRUE
+
+/obj/item/integrated_circuit/input/radiation_sensor
+ name = "radiation sensor"
+ desc = "A tiny radiation sensor module similar to that found in a geiger counter."
+ icon_state = "medscan_adv"
+ complexity = 3
+ inputs = list()
+ outputs = list(
+ "radiation" = IC_PINTYPE_NUMBER
+ )
+ activators = list("scan" = IC_PINTYPE_PULSE_IN, "on scanned" = IC_PINTYPE_PULSE_OUT)
+ spawn_flags = IC_SPAWN_DEFAULT
+ origin_tech = list(TECH_ENGINEERING = 3, TECH_DATA = 3)
+ power_draw_per_use = 20
+
+/obj/item/integrated_circuit/input/radiation_sensor/do_work()
+ var/turf/T = get_turf(src)
+ if(!istype(T)) //Invalid input
+ return
+
+ var/radiation_count = SSradiation.get_rads_at_turf(get_turf(src))
+
+ if(radiation_count)
+ set_pin_data(IC_OUTPUT, 1, round(radiation_count,0.1))
+ else
+ set_pin_data(IC_OUTPUT, 1, 0)
+ push_data()
+
+/obj/item/integrated_circuit/input/light_sensor
+ name = "light sensor"
+ desc = "A tiny light sensor module."
+ icon_state = "medscan_adv"
+ complexity = 3
+ inputs = list()
+ outputs = list(
+ "lumens" = IC_PINTYPE_NUMBER
+ )
+ activators = list("scan" = IC_PINTYPE_PULSE_IN, "on scanned" = IC_PINTYPE_PULSE_OUT)
+ spawn_flags = IC_SPAWN_DEFAULT
+ origin_tech = list(TECH_ENGINEERING = 3, TECH_DATA = 3)
+ power_draw_per_use = 20
+
+/obj/item/integrated_circuit/input/light_sensor/do_work()
+ var/turf/T = get_turf(src)
+ if(!istype(T)) //Invalid input
+ return
+
+ var/lumens = T.get_lumcount(0, 3) * 5
+ if(lumens)
+ set_pin_data(IC_OUTPUT, 1, round(lumens,0.1))
+ else
+ set_pin_data(IC_OUTPUT, 1, 0)
+ push_data()
+
+/obj/item/integrated_circuit/input/face_scanner
+ name = "face scanner"
+ desc = "A complex camera and in-built scanner, similar to an examiner but specifically for people.\
+ It can identify species type, height and eye colour, and it can estimate age (to the half-decade).\
+ It also provides a short description of the person and their face (if able)."
+ icon_state = "video_camera"
+ complexity = 12
+ inputs = list("target" = IC_PINTYPE_REF)
+ outputs = list(
+ "name" = IC_PINTYPE_STRING,
+ "description (general)" = IC_PINTYPE_STRING,
+ "description (face)" = IC_PINTYPE_STRING,
+ "species" = IC_PINTYPE_STRING,
+ "height" = IC_PINTYPE_NUMBER,
+ "estimated age" = IC_PINTYPE_NUMBER,
+ "eye colour" = IC_PINTYPE_COLOR
+ )
+ activators = list("scan" = IC_PINTYPE_PULSE_IN, "on scanned" = IC_PINTYPE_PULSE_OUT, "not scanned" = IC_PINTYPE_PULSE_OUT)
+ spawn_flags = IC_SPAWN_RESEARCH
+ origin_tech = list(TECH_ENGINEERING = 3, TECH_DATA = 3, TECH_BIO = 5)
+ power_draw_per_use = 100
+
+/obj/item/integrated_circuit/input/face_scanner/do_work()
+ var/mob/living/carbon/human/H = get_pin_data_as_type(IC_INPUT, 1, /mob/living/carbon/human)
+ if(!istype(H)) //Invalid input
+ return
+
+ if(H in view(get_turf(src))) // This is a camera. It can't examine things that it can't see.
+ set_pin_data(IC_OUTPUT, 1, H.name)
+ set_pin_data(IC_OUTPUT, 2, H.flavor_texts["general"])
+ set_pin_data(IC_OUTPUT, 3, H.flavor_texts["face"])
+ set_pin_data(IC_OUTPUT, 4, H.species.name)
+ set_pin_data(IC_OUTPUT, 5, H.height)
+ set_pin_data(IC_OUTPUT, 6, round(H.age, 5))
+ set_pin_data(IC_OUTPUT, 7, rgb(H.r_eyes, H.g_eyes, H.b_eyes))
+ push_data()
+ activate_pin(2)
+ else
+ activate_pin(3)
+
+/obj/item/integrated_circuit/input/anomaly_scanner
+ name = "alden-saraspova counter"
+ desc = "A miniaturised Alden-Saraspova counter, used to detected anomalous readings. Often used by xenoarchaeologists."
+ extended_desc = "It can only scan a 14x14 area and has an built-in 15 second cooldown."
+ icon_state = "medscan_adv"
+ complexity = 12
+ outputs = list(
+ "exotic particle type" = IC_PINTYPE_STRING,
+ "range detected at" = IC_PINTYPE_NUMBER
+ )
+ activators = list("scan" = IC_PINTYPE_PULSE_IN, "anomaly found" = IC_PINTYPE_PULSE_OUT)
+ spawn_flags = IC_SPAWN_RESEARCH
+ origin_tech = list(TECH_ENGINEERING = 4, TECH_DATA = 4, TECH_MAGNET = 6, TECH_BLUESPACE = 4)
+ power_draw_per_use = 200
+ cooldown_per_use = 15 SECONDS // sizeable cooldown to prevent view spam
+
+/obj/item/integrated_circuit/input/anomaly_scanner/do_work()
+ if(!SSxenoarch)
+ return
+
+ var/turf/our_turf = get_turf(src)
+ for(var/turf/simulated/mineral/scanned_turf in view(our_turf)) // Restrict range to only visible tiles, instead of the entire Z-level like standalone AS counters
+ if(scanned_turf.artifact_find)
+ if(scanned_turf.artifact_find.artifact_id)
+ set_pin_data(IC_OUTPUT, 1, scanned_turf.artifact_find.artifact_id)
+ else
+ set_pin_data(IC_OUTPUT, 1, "Exotic Particles (Various)")
+ set_pin_data(IC_OUTPUT, 2, get_dist(our_turf, scanned_turf))
+ push_data()
+ activate_pin(2)
+ break
+ ..()
diff --git a/code/modules/integrated_electronics/subtypes/manipulation.dm b/code/modules/integrated_electronics/subtypes/manipulation.dm
index f4b4234780d..e5940ec0ebe 100644
--- a/code/modules/integrated_electronics/subtypes/manipulation.dm
+++ b/code/modules/integrated_electronics/subtypes/manipulation.dm
@@ -313,7 +313,7 @@
desc = "Used to shock adjacent creatures with electricity."
icon_state = "shocker"
extended_desc = "The circuit accepts a reference to creature to shock. It can shock a target on adjacent tiles. \
- Severity determines the power draw and usage of each shock. It accepts values between 0 and 20."
+ Severity determines the power draw and usage of each shock. It accepts values between 0 and 20. It has a 5 second cooldown."
w_class = WEIGHT_CLASS_TINY
complexity = 24
inputs = list("target" = IC_PINTYPE_REF,"severity" = IC_PINTYPE_NUMBER)
@@ -325,7 +325,7 @@
/obj/item/integrated_circuit/manipulation/shocker/on_data_written()
var/s = get_pin_data(IC_INPUT, 2)
- power_draw_per_use = clamp(s,0,20)*8
+ power_draw_per_use = clamp(s,0,20)*20 // big power draw to shock large creatures
/obj/item/integrated_circuit/manipulation/shocker/do_work()
..()
@@ -339,8 +339,211 @@
to_chat(M, SPAN_DANGER("You feel a light tingle from [src]. Luckily it was charging!"))
return
else
+ msg_admin_attack("An integrated circuit with a shocker circuit was used to shock [M.name] ([M.ckey]) (JMP)")
to_chat(M, SPAN_DANGER("You feel a sharp shock from the [src]!"))
spark(get_turf(M), 3, 1)
M.stun_effect_act(0, clamp(get_pin_data(IC_INPUT, 2),0,20), null)
shocktime = world.time
return
+
+/obj/item/integrated_circuit/manipulation/flasher
+ name = "flasher circuit"
+ desc = "Used to flash adjacent creatures."
+ icon_state = "video_camera"
+ extended_desc = "The circuit accepts a reference to creature to flash. It can shock a target on adjacent tiles. \
+ Severity determines the power draw and duration of each flash. It accepts values between 1 and 4. It has a 5 second cooldown."
+ w_class = WEIGHT_CLASS_TINY
+ complexity = 24
+ inputs = list("target" = IC_PINTYPE_REF,"severity" = IC_PINTYPE_NUMBER)
+ outputs = list()
+ activators = list("flash" = IC_PINTYPE_PULSE_IN)
+ spawn_flags = IC_SPAWN_RESEARCH
+ power_draw_per_use = 0
+ var/flashtime
+
+/obj/item/integrated_circuit/manipulation/flasher/on_data_written()
+ var/s = get_pin_data(IC_INPUT, 2)
+ power_draw_per_use = clamp(s,1,4)*200 // big power draw to achieve a blinding flash
+
+/obj/item/integrated_circuit/manipulation/flasher/do_work()
+ ..()
+ var/turf/T = get_turf(src)
+ var/mob/living/scanned_mob = get_pin_data_as_type(IC_INPUT, 1, /mob/living)
+ if(!istype(scanned_mob)) //Invalid input
+ return
+ if(!T.Adjacent(scanned_mob))
+ return //Can't reach
+ if((flashtime + (5 SECONDS)) > world.time) // Cooldown
+ visible_message(SPAN_WARNING("You see a weak flash from the [src]."))
+ else
+ var/flash_duration = clamp(get_pin_data(IC_INPUT, 2),1,4)
+
+ scanned_mob.flash_act(length = flash_duration SECONDS)
+
+ flashtime = world.time
+ msg_admin_attack("An integrated circuit with a flasher was used to flash [scanned_mob.name] ([scanned_mob.ckey]) (JMP)")
+ playsound(src.loc, 'sound/weapons/flash.ogg', 100, 1)
+ return
+
+/obj/item/integrated_circuit/manipulation/portal_opener // basically a mini telescience setup, but consumes bluespace crystals, is one-way, only along cardinals and ordinals, cannot cross z-levels, and more imprecise (higher chance of getting beamed into a wall/table).
+ name = "bluespace portal circuit"
+ desc = "A miniaturised circuit that uses bluespace crystals to open a one-way bluespace portal. Costlier to use than a standard telescience set up, but portable."
+ extended_desc = "The circuit can store 3 bluespace crystals; each crystal is 1 use. Power determines number of tiles jumped (max 50m). Lifespan determines how long the portal is there (3-15 sec). Imprecise with a high variance (DANGER IN CLOSED SPACES!). It has a cooldown of 30 seconds."
+ icon_state = "shocker"
+ w_class = WEIGHT_CLASS_TINY
+ size = 20
+ complexity = 30 // needs a big assembly if you want to use this in any non-simplistic way
+ inputs = list("direction" = IC_PINTYPE_DIR, "power" = IC_PINTYPE_NUMBER, "lifespan" = IC_PINTYPE_NUMBER)
+ outputs = list("bluespace crystals stored" = IC_PINTYPE_NUMBER)
+ activators = list("open portal" = IC_PINTYPE_PULSE_IN, "portal opened" = IC_PINTYPE_PULSE_OUT, "cannot open portal" = IC_PINTYPE_PULSE_OUT)
+ spawn_flags = IC_SPAWN_RESEARCH
+ power_draw_per_use = 100 // multiplied by up to 50 during on_data_written(). should slurp up power in smaller assemblies or those without power generation
+ cooldown_per_use = 30 SECONDS // big cooldown
+ origin_tech = list(TECH_DATA = 4, TECH_ENGINEERING = 4, TECH_MATERIAL = 4, TECH_BLUESPACE = 5)
+
+ /// Lazylist of stored bluespace crystals stored in the device for amount checks/deletions.
+ var/list/obj/item/bluespace_crystal/crystals
+
+ /// Maximum number of crystals the circuit can store.
+ var/max_crystals = 3
+
+/obj/item/integrated_circuit/manipulation/portal_opener/Destroy()
+ for(var/obj/item/bluespace_crystal/crystal in crystals)
+ crystal.dropInto(get_turf(assembly))
+ LAZYNULL(crystals)
+ return ..()
+
+/obj/item/integrated_circuit/manipulation/portal_opener/attackby(obj/item/attacking_item, mob/user, params)
+ if(istype(attacking_item, /obj/item/bluespace_crystal))
+ if(LAZYLEN(crystals) >= max_crystals)
+ to_chat(user, SPAN_WARNING("There are not enough crystal slots."))
+ return
+
+ user.visible_message("[user] inserts [attacking_item] into \the [src]'s crystal slot.", SPAN_NOTICE("You insert [attacking_item] into \the [src]'s crystal slot."))
+ user.drop_item(src)
+ LAZYADD(crystals, attacking_item)
+ attacking_item.forceMove(null)
+ else
+ ..()
+
+/obj/item/integrated_circuit/manipulation/portal_opener/on_data_written()
+ power_draw_per_use = round(between(100, 100*(get_pin_data(IC_INPUT, 2)), 5000), 50) // potentially big power draw for a maximum jump range
+ ..()
+
+/obj/item/integrated_circuit/manipulation/portal_opener/do_work()
+ if(!LAZYLEN(crystals) >= 1) // no crystals, no portal
+ activate_pin(3)
+ return
+
+ var/turf/assembly_turf = get_turf(assembly)
+ if(assembly_turf)
+ // maffs
+ var/proj_power = round(between(1, get_pin_data(IC_INPUT, 2), 50), 1) // max range of 50 metres
+ var/proj_rotation = dir2angle(get_pin_data(IC_INPUT, 1)) // in degrees please
+ var/proj_angle = 60
+
+ var/datum/projectile_data/proj_data = projectile_trajectory(assembly_turf.x, assembly_turf.y, proj_rotation, proj_angle, proj_power)
+
+ var/destination_x = clamp(round(proj_data.dest_x, 1), 1, world.maxx) // dont want to exit map
+ var/destination_y = clamp(round(proj_data.dest_y, 1), 1, world.maxy)
+
+ var/turf/portal_destination = locate(destination_x, destination_y, assembly_turf.z)
+
+ // effects
+ playsound(assembly.loc, 'sound/weapons/flash.ogg', 25, 1)
+ spark(assembly, 5, GLOB.alldirs)
+
+ // portal creation
+ var/lifespan = between(30, SecondsToTicks(get_pin_data(IC_INPUT, 2)), 150)
+ var/obj/effect/portal/our_portal = new /obj/effect/portal(assembly_turf, null, null, lifespan, 0)
+ our_portal.set_target(portal_destination)
+ our_portal.precision = 2 // very imprecise, 5x5 turf variance. best used in very open spaces
+ our_portal.has_failed = FALSE
+
+ // consume a bluespace crystal
+ var/obj/item/bluespace_crystal/consumed_bsc = LAZYACCESS(crystals, 1)
+ LAZYREMOVE(crystals, consumed_bsc)
+ qdel(consumed_bsc)
+
+ activate_pin(2)
+ else
+ activate_pin(3)
+
+ set_pin_data(IC_OUTPUT, 1, LAZYLEN(crystals)) // set the bluespace crystals counter
+ push_data()
+ ..()
+
+/obj/item/integrated_circuit/manipulation/bubble_shield
+ name = "bubble shield circuit"
+ desc = "A large bubble shield generator circuit, like those found in atmospheric emergency shields... sans all the automated features."
+ extended_desc = "Receives relative coordinates to project a shield upon (must be within 3 tiles). Strength determines shield hitpoints (range: 10-50). Lifespan given in seconds (range: 2-60 secs). Can only sustain 3 shields."
+ icon_state = "power_transmitter"
+ w_class = WEIGHT_CLASS_TINY
+ size = 30 // needs a medium-sized assembly minimum
+ complexity = 20
+ inputs = list("relative X" = IC_PINTYPE_NUMBER, "relative Y" = IC_PINTYPE_NUMBER, "strength" = IC_PINTYPE_NUMBER, "lifespan" = IC_PINTYPE_NUMBER)
+ outputs = list("shields projected" = IC_PINTYPE_NUMBER)
+ activators = list("create shield" = IC_PINTYPE_PULSE_IN, "shield created" = IC_PINTYPE_PULSE_OUT, "shield capacity reached" = IC_PINTYPE_PULSE_OUT)
+ spawn_flags = IC_SPAWN_RESEARCH
+ power_draw_per_use = 100
+ origin_tech = list(TECH_ENGINEERING = 4, TECH_MAGNET = 5)
+
+ /// A list of shields deployed by this circuit for amount checks/shield deletions.
+ var/list/obj/machinery/shield/deployed_shields
+
+/obj/item/integrated_circuit/manipulation/bubble_shield/Destroy()
+ QDEL_LAZYLIST(deployed_shields)
+ return ..()
+
+/obj/item/integrated_circuit/manipulation/bubble_shield/proc/kill_shield(var/obj/machinery/shield/shield)
+ LAZYREMOVE(deployed_shields, shield)
+ qdel(shield)
+ set_pin_data(IC_OUTPUT, 1, LAZYLEN(deployed_shields))
+
+/obj/item/integrated_circuit/manipulation/bubble_shield/do_work()
+ if(!assembly)
+ return
+
+ var/strength = between(10, get_pin_data(IC_INPUT, 3), 50)
+ var/lifespan = between(2, get_pin_data(IC_INPUT, 4), 60)
+
+ // find target turf based on relative coordinates
+ var/target_relative_x = round(get_pin_data(IC_INPUT, 1))
+ var/target_relative_y = round(get_pin_data(IC_INPUT, 2))
+
+ var/turf/our_turf = get_turf(assembly)
+ var/target_x = our_turf.x + target_relative_x
+ var/target_y = our_turf.y + target_relative_y
+ if (target_x > world.maxx || target_x < 1 || target_y > world.maxy || target_y < 1)
+ return // Off the edge of the map.
+
+ var/target_turf = locate(target_x, target_y, our_turf.z)
+
+ // create the shield
+ if(target_turf && (target_turf in view(3, our_turf))) // must be in view and within 3 tiles
+ if(locate(/obj/machinery/shield) in target_turf) // already got a shield
+ return
+
+ if(LAZYLEN(deployed_shields) >= 3) // do not generate more than 3 shields
+ activate_pin(2)
+ return
+ //actually creating the shield
+ var/obj/machinery/shield/shield = new /obj/machinery/shield(target_turf)
+ shield.name = "bubble shield"
+ shield.health = strength
+ LAZYADD(deployed_shields, shield)
+ addtimer(CALLBACK(src, PROC_REF(kill_shield), shield), lifespan SECONDS, TIMER_DELETE_ME) // clean up after ourselves later
+ set_pin_data(IC_OUTPUT, 1, LAZYLEN(deployed_shields))
+ activate_pin(1)
+
+ // power stuff
+ if(deployed_shields)
+ power_draw_per_use = initial(power_draw_per_use)*(strength/10)*LAZYLEN(deployed_shields)
+ else
+ power_draw_per_use = initial(power_draw_per_use)
+
+ ..()
+
+/obj/item/integrated_circuit/manipulation/bubble_shield/power_fail()
+ for(var/obj/machinery/shield/shield in deployed_shields)
+ kill_shield(shield)
diff --git a/code/modules/integrated_electronics/subtypes/time.dm b/code/modules/integrated_electronics/subtypes/time.dm
index 8c886fe8501..a98f9ea2cd5 100644
--- a/code/modules/integrated_electronics/subtypes/time.dm
+++ b/code/modules/integrated_electronics/subtypes/time.dm
@@ -59,25 +59,21 @@
inputs = list("delay time" = IC_PINTYPE_NUMBER)
spawn_flags = IC_SPAWN_RESEARCH
-/obj/item/integrated_circuit/time/delay/custom/do_work()
- var/delay_input = get_pin_data(IC_INPUT, 1)
- if(delay_input && isnum(delay_input) )
- var/new_delay = between(1, delay_input, 1 HOUR)
- delay = new_delay
-
+/obj/item/integrated_circuit/time/delay/custom/on_data_written()
+ delay = between(1, get_pin_data(IC_INPUT, 1), 1 HOUR)
..()
/obj/item/integrated_circuit/time/ticker
- name = "ticker circuit"
- desc = "This circuit sends an automatic pulse every four seconds."
+ name = "ten second ticker"
+ desc = "This circuit sends an automatic pulse every ten seconds."
icon_state = "tick-m"
complexity = 8
- var/ticks_to_pulse = 4
+ var/seconds_to_pulse = 10 SECONDS
var/ticks_completed = 0
var/is_running = FALSE
inputs = list("enable ticking" = IC_PINTYPE_BOOLEAN)
activators = list("outgoing pulse" = IC_PINTYPE_PULSE_OUT)
- spawn_flags = IC_SPAWN_RESEARCH
+ spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH
power_draw_per_use = 4
/obj/item/integrated_circuit/time/ticker/Destroy()
@@ -97,31 +93,54 @@
/obj/item/integrated_circuit/time/ticker/process()
var/process_ticks = SSelectronics.wait
ticks_completed += process_ticks
- if(ticks_completed >= ticks_to_pulse)
- if(ticks_to_pulse >= process_ticks)
- ticks_completed -= ticks_to_pulse
+ if(ticks_completed >= SecondsToTicks(seconds_to_pulse))
+ if(SecondsToTicks(seconds_to_pulse) >= process_ticks)
+ ticks_completed -= SecondsToTicks(seconds_to_pulse)
else
ticks_completed = 0
activate_pin(1)
/obj/item/integrated_circuit/time/ticker/fast
- name = "fast ticker"
+ name = "two second ticker"
desc = "This advanced circuit sends an automatic pulse every two seconds."
icon_state = "tick-f"
complexity = 12
- ticks_to_pulse = 2
+ seconds_to_pulse = 2 SECONDS
spawn_flags = IC_SPAWN_RESEARCH
power_draw_per_use = 8
/obj/item/integrated_circuit/time/ticker/slow
- name = "slow ticker"
- desc = "This simple circuit sends an automatic pulse every six seconds."
+ name = "thirty second ticker"
+ desc = "This simple circuit sends an automatic pulse every thirty seconds."
icon_state = "tick-s"
complexity = 4
- ticks_to_pulse = 6
+ seconds_to_pulse = 30 SECONDS
spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH
power_draw_per_use = 2
+/obj/item/integrated_circuit/time/ticker/very_slow
+ name = "five minute ticker"
+ desc = "This simple circuit sends an automatic pulse every five minutes (three hundred seconds)."
+ icon_state = "tick-s"
+ complexity = 4
+ seconds_to_pulse = 300 SECONDS
+ spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH
+ power_draw_per_use = 2
+
+/obj/item/integrated_circuit/time/ticker/custom
+ name = "custom ticker"
+ desc = "This advanced circuit sends an automatic pulse with a configurable interval between 2 and 600 seconds. Default: 60 seconds."
+ icon_state = "tick-f"
+ complexity = 15
+ seconds_to_pulse = 60 SECONDS
+ spawn_flags = IC_SPAWN_RESEARCH
+ power_draw_per_use = 8
+ inputs = list("enable ticking" = IC_PINTYPE_BOOLEAN, "ticker time" = IC_PINTYPE_NUMBER)
+
+/obj/item/integrated_circuit/time/ticker/custom/on_data_written()
+ seconds_to_pulse = between(2 SECONDS, get_pin_data(IC_INPUT, 2), 600 SECONDS)
+ ..()
+
/obj/item/integrated_circuit/time/clock
name = "integrated clock"
desc = "Tells you what the local time is, specific to your station, planet, or facility."
diff --git a/html/changelogs/kermit-circuitry-miniupdate.yml b/html/changelogs/kermit-circuitry-miniupdate.yml
new file mode 100644
index 00000000000..9dfc30ef5bf
--- /dev/null
+++ b/html/changelogs/kermit-circuitry-miniupdate.yml
@@ -0,0 +1,12 @@
+author: kermit
+
+delete-after: True
+
+changes:
+ - rscadd: "Circuitry tickers now actually use seconds as advertised. Made the intervals more significant. Adds a Custom Ticker."
+ - rscadd: "Circuity medical analysers scan for health data relevant to Brainmed and not Painmed. 6 years late."
+ - rscadd: "Adds a face scanner circuit. Takes a reference; returns some visual characteristics (eg. height, age, face flavourtext). Upgraded printer only."
+ - rscadd: "Adds a flasher circuit. Same complexity/powerusage/etc as the shocker. Non-stunning/non-weakening. Upgraded printer only."
+ - rscadd: "Adds an Alden-Saraspova anomaly counter circuit. Only scans minerals in-view. Upgraded printer only."
+ - rscadd: "Adds a bluespace portal circuit. Consumes bluespace crystals. Inferior to just using the telescience lab, but portable/miniaturised. Upgraded printer only."
+ - bugfix: "Fixes the atmospheric sensor circuits all being named 'oxygen sensor'."
\ No newline at end of file