From 2c4163a121b972e1bf6882a9221f7df2aeb89044 Mon Sep 17 00:00:00 2001 From: c#min7 Date: Wed, 18 Mar 2026 13:05:35 +0000 Subject: [PATCH] Integrated Circuitry Miniupdate (#21971) - Updates the **medical analysers** to get health values significant to Brainmed instead of Pointmed. About 6 years late? - Adds a **radiation scanner**. - Adds a **light scanner**. - Adds a **custom ticker** that can be calibrated between 2 (fast ticker equivalent) and 600 seconds. - Adds a **face scanner** that can get basic information about humanoids, including: name (same as an examiner), general flavourtext, face flavourtext, species, height, estimated age, and eye colour. Has applications for species-specific circuitry, identity confirmation, or flavour usage. - Adds a **flasher** that functions similarly to the **shocker**, but applies a non-weakening/non-stunning flash instead. Shocker nerfed to have much higher power draw, which the flasher also mimics. - Redid all of the **tickers** to actually use seconds (as advertised in their descriptions). Their intervals have also been changed to be more significant: 10-secs, 30-secs, 5-mins, 2-secs (upgraded printer only), custom 2sec to 10mins (upgraded printer only). - Adds an **Alden-Saraspova counter circuit**. Scans only immediate (in-view) surroundings for mineral deposits with artefacts inside and returns a distance. - Adds a **bluespace portal circuit**. Consumes bluespace crystals (either tear apart Telescience OR get high tech levels + every material) to open a portal with a destination based on a supplied *direction* and *power*. Worse than telescience in every way (5x5m variance; cardinal/ordinals only; no cross z-level teleporting; 1-way; BUT it's portable!) - Adds a **bubble shield circuit**. Relies on relative coordinate inputs to generate a low health shield. Max range of 3 tiles. Max lifespan of 60 seconds. Can only project 3 shields at a time. **To-Do:** - [x] Test everything - [x] Add any other quick requests before I hit review - [x] Fuck around with circuits ingame for a bit to figure out any other gaps - [x] Test everything again --------- Signed-off-by: c#min7 Co-authored-by: SleepyGemmy <99297919+SleepyGemmy@users.noreply.github.com> --- .../integrated_electronics/subtypes/input.dm | 239 ++++++++++++++++-- .../subtypes/manipulation.dm | 207 ++++++++++++++- .../integrated_electronics/subtypes/time.dm | 55 ++-- .../kermit-circuitry-miniupdate.yml | 12 + 4 files changed, 465 insertions(+), 48 deletions(-) create mode 100644 html/changelogs/kermit-circuitry-miniupdate.yml 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