/obj/item/analyzer name = "analyzer" desc = "A hand-held environmental scanner which reports current gas levels." icon = 'icons/obj/device.dmi' icon_state = "atmos" inhand_icon_state = "analyzer" slot_flags = ITEM_SLOT_BELT w_class = WEIGHT_CLASS_SMALL flags = CONDUCT throw_speed = 3 materials = list(MAT_METAL = 210, MAT_GLASS = 140) origin_tech = "magnets=1;engineering=1" var/cooldown = FALSE var/cooldown_time = 250 var/accuracy // 0 is the best accuracy. /// FALSE: Sum gas mixes then present. TRUE: Present each mix individually var/show_detailed = FALSE /obj/item/analyzer/examine(mob/user) . = ..() . += SPAN_NOTICE("Alt-click [src] to activate the barometer function.") . += SPAN_NOTICE("Alt-Shift-click [src] to toggle detailed reporting on or off.") /obj/item/analyzer/attack_self__legacy__attackchain(mob/user as mob) if(user.stat) return var/turf/location = user.loc if(!isturf(location)) return atmos_scan(user = user, target = location, detailed = show_detailed) add_fingerprint(user) /obj/item/analyzer/AltShiftClick(mob/user) show_detailed = !show_detailed to_chat(user, SPAN_NOTICE("You toggle detailed reporting [show_detailed ? "on" : "off"]")) /obj/item/analyzer/AltClick(mob/user) //Barometer output for measuring when the next storm happens ..() if(!user.incapacitated() && Adjacent(user)) if(cooldown) to_chat(user, SPAN_WARNING("[src]'s barometer function is preparing itself.")) return var/turf/T = get_turf(user) if(!T) return playsound(src, 'sound/effects/pop.ogg', 100) var/area/user_area = T.loc var/datum/weather/ongoing_weather = null if(!user_area.outdoors) to_chat(user, SPAN_WARNING("[src]'s barometer function won't work indoors!")) return for(var/V in SSweather.processing) var/datum/weather/W = V if(W.barometer_predictable && (T.z in W.impacted_z_levels) && is_type_in_list(user_area, W.area_types) && !(W.stage == WEATHER_END_STAGE)) ongoing_weather = W break if(ongoing_weather) if((ongoing_weather.stage == WEATHER_MAIN_STAGE) || (ongoing_weather.stage == WEATHER_WIND_DOWN_STAGE)) to_chat(user, SPAN_WARNING("[src]'s barometer function can't trace anything while the storm is [ongoing_weather.stage == WEATHER_MAIN_STAGE ? "already here!" : "winding down."]")) return to_chat(user, SPAN_NOTICE("The next [ongoing_weather] will hit in [butchertime(ongoing_weather.next_hit_time - world.time)].")) if(ongoing_weather.aesthetic) to_chat(user, SPAN_WARNING("[src]'s barometer function says that the next storm will breeze on by.")) else var/next_hit = SSweather.next_hit_by_zlevel["[T.z]"] var/fixed = next_hit ? next_hit - world.time : -1 if(fixed < 0) to_chat(user, SPAN_WARNING("[src]'s barometer function was unable to trace any weather patterns.")) else to_chat(user, SPAN_WARNING("[src]'s barometer function says a storm will land in approximately [butchertime(fixed)].")) cooldown = TRUE addtimer(CALLBACK(src, PROC_REF(ping)), cooldown_time) /obj/item/analyzer/proc/ping() if(isliving(loc)) var/mob/living/L = loc to_chat(L, SPAN_NOTICE("[src]'s barometer function is ready!")) playsound(src, 'sound/machines/click.ogg', 100) cooldown = FALSE /obj/item/analyzer/proc/butchertime(amount) if(!amount) return if(accuracy) var/inaccurate = round(accuracy * (1 / 3)) if(prob(50)) amount -= inaccurate if(prob(50)) amount += inaccurate return DisplayTimeText(max(1, amount)) /obj/item/analyzer/afterattack__legacy__attackchain(atom/target, mob/user, proximity, params) . = ..() if(!can_see(user, target, 1)) return if(target.return_analyzable_air()) atmos_scan(user, target, detailed = show_detailed) else atmos_scan(user, get_turf(target), detailed = show_detailed) /** * Outputs a message to the user describing the target's gasmixes. * Used in chat-based gas scans. */ /proc/atmos_scan(mob/user, atom/target, silent = FALSE, print = TRUE, milla_turf_details = FALSE, detailed = FALSE) var/list/airs var/list/milla = null if(milla_turf_details && istype(target, /turf)) // This is one of the few times when it's OK to call MILLA directly, as we need more information than we normally keep, aren't trying to modify it, and don't need it to be synchronized with anything. milla = new/list(MILLA_TILE_SIZE) get_tile_atmos(target, milla) var/datum/gas_mixture/air = new() air.copy_from_milla(milla) airs = list(air) else airs = target.return_analyzable_air() if(!airs) return FALSE if(!islist(airs)) airs = list(airs) var/list/message = list() if(!silent && isliving(user)) user.visible_message(SPAN_NOTICE("[user] uses the analyzer on [target]."), SPAN_NOTICE("You use the analyzer on [target].")) message += SPAN_BOLDNOTICE("Results of analysis of [bicon(target)] [target].") if(!print) return TRUE var/total_moles = 0 var/pressure = 0 var/volume = 0 var/heat_capacity = 0 var/thermal_energy = 0 var/oxygen = 0 var/nitrogen = 0 var/toxins var/carbon_dioxide = 0 var/sleeping_agent = 0 var/agent_b = 0 var/hydrogen = 0 var/water_vapor = 0 if(detailed)// Present all mixtures one by one for(var/datum/gas_mixture/air as anything in airs) total_moles = air.total_moles() pressure = air.return_pressure() volume = air.return_volume() //could just do mixture.volume... but safety, I guess? heat_capacity = air.heat_capacity() thermal_energy = air.thermal_energy() if(total_moles) message += SPAN_NOTICE("Total: [round(total_moles, 0.01)] moles") if(air.oxygen() && (milla_turf_details || air.oxygen() / total_moles > 0.01)) message += " [SPAN_OXYGEN("Oxygen: [round(air.oxygen(), 0.01)] moles ([round(air.oxygen() / total_moles * 100, 0.01)] %)")]" if(air.nitrogen() && (milla_turf_details || air.nitrogen() / total_moles > 0.01)) message += " [SPAN_NITROGEN("Nitrogen: [round(air.nitrogen(), 0.01)] moles ([round(air.nitrogen() / total_moles * 100, 0.01)] %)")]" if(air.carbon_dioxide() && (milla_turf_details || air.carbon_dioxide() / total_moles > 0.01)) message += " [SPAN_CARBON_DIOXIDE("Carbon Dioxide: [round(air.carbon_dioxide(), 0.01)] moles ([round(air.carbon_dioxide() / total_moles * 100, 0.01)] %)")]" if(air.toxins() && (milla_turf_details || air.toxins() / total_moles > 0.01)) message += " [SPAN_PLASMA("Plasma: [round(air.toxins(), 0.01)] moles ([round(air.toxins() / total_moles * 100, 0.01)] %)")]" if(air.sleeping_agent() && (milla_turf_details || air.sleeping_agent() / total_moles > 0.01)) message += " [SPAN_SLEEPING_AGENT("Nitrous Oxide: [round(air.sleeping_agent(), 0.01)] moles ([round(air.sleeping_agent() / total_moles * 100, 0.01)] %)")]" if(air.agent_b() && (milla_turf_details || air.agent_b() / total_moles > 0.01)) message += " [SPAN_AGENT_B("Agent B: [round(air.agent_b(), 0.01)] moles ([round(air.agent_b() / total_moles * 100, 0.01)] %)")]" if(air.hydrogen() && (milla_turf_details || air.hydrogen() / total_moles > 0.01)) message += " [SPAN_HYDROGEN("Hydrogen: [round(air.hydrogen(), 0.01)] moles ([round(air.hydrogen() / total_moles * 100, 0.01)] %)")]" if(air.water_vapor() && (milla_turf_details || air.water_vapor() / total_moles > 0.01)) message += " [SPAN_WATER_VAPOR("Water Vapor: [round(air.water_vapor(), 0.01)] moles ([round(air.water_vapor() / total_moles * 100, 0.01)] %)")]" message += SPAN_NOTICE("Temperature: [round(air.temperature()-T0C)] °C ([round(air.temperature())] K)") message += SPAN_NOTICE("Volume: [round(volume)] Liters") message += SPAN_NOTICE("Pressure: [round(pressure, 0.1)] kPa") message += SPAN_NOTICE("Heat Capacity: [DisplayJoules(heat_capacity)] / K") message += SPAN_NOTICE("Thermal Energy: [DisplayJoules(thermal_energy)]") else message += SPAN_NOTICE("[target] is empty!") message += SPAN_NOTICE("Volume: [round(volume)] Liters") // don't want to change the order volume appears in, suck it else// Sum mixtures then present for(var/datum/gas_mixture/air as anything in airs) if(isnull(air)) continue total_moles += air.total_moles() volume += air.return_volume() heat_capacity += air.heat_capacity() thermal_energy += air.thermal_energy() oxygen += air.oxygen() nitrogen += air.nitrogen() toxins += air.toxins() carbon_dioxide += air.carbon_dioxide() sleeping_agent += air.sleeping_agent() agent_b += air.agent_b() hydrogen += air.hydrogen() water_vapor += air.water_vapor() var/temperature = heat_capacity ? thermal_energy / heat_capacity : 0 pressure = volume ? total_moles * R_IDEAL_GAS_EQUATION * temperature / volume : 0 if(total_moles) message += SPAN_NOTICE("Total: [round(total_moles, 0.01)] moles") if(oxygen && (milla_turf_details || oxygen / total_moles > 0.01)) message += " [SPAN_OXYGEN("Oxygen: [round(oxygen, 0.01)] moles ([round(oxygen / total_moles * 100, 0.01)] %)")]" if(nitrogen && (milla_turf_details || nitrogen / total_moles > 0.01)) message += " [SPAN_NITROGEN("Nitrogen: [round(nitrogen, 0.01)] moles ([round(nitrogen / total_moles * 100, 0.01)] %)")]" if(carbon_dioxide && (milla_turf_details || carbon_dioxide / total_moles > 0.01)) message += " [SPAN_CARBON_DIOXIDE("Carbon Dioxide: [round(carbon_dioxide, 0.01)] moles ([round(carbon_dioxide / total_moles * 100, 0.01)] %)")]" if(toxins && (milla_turf_details || toxins / total_moles > 0.01)) message += " [SPAN_PLASMA("Plasma: [round(toxins, 0.01)] moles ([round(toxins / total_moles * 100, 0.01)] %)")]" if(sleeping_agent && (milla_turf_details || sleeping_agent / total_moles > 0.01)) message += " [SPAN_SLEEPING_AGENT("Nitrous Oxide: [round(sleeping_agent, 0.01)] moles ([round(sleeping_agent / total_moles * 100, 0.01)] %)")]" if(agent_b && (milla_turf_details || agent_b / total_moles > 0.01)) message += " [SPAN_AGENT_B("Agent B: [round(agent_b, 0.01)] moles ([round(agent_b / total_moles * 100, 0.01)] %)")]" if(hydrogen && (milla_turf_details || hydrogen / total_moles > 0.01)) message += " [SPAN_HYDROGEN("Hydrogen: [round(hydrogen, 0.01)] moles ([round(hydrogen / total_moles * 100, 0.01)] %)")]" if(water_vapor && (milla_turf_details || (water_vapor / total_moles > 0.01))) message += " [SPAN_WATER_VAPOR("Water Vapor: [round(water_vapor, 0.01)] moles ([round(water_vapor / total_moles * 100, 0.01)] %)")]" message += SPAN_NOTICE("Temperature: [round(temperature-T0C)] °C ([round(temperature)] K)") message += SPAN_NOTICE("Volume: [round(volume)] Liters") message += SPAN_NOTICE("Pressure: [round(pressure, 0.1)] kPa") message += SPAN_NOTICE("Heat Capacity: [DisplayJoules(heat_capacity)] / K") message += SPAN_NOTICE("Thermal Energy: [DisplayJoules(thermal_energy)]") else message += SPAN_NOTICE("[target] is empty!") message += SPAN_NOTICE("Volume: [round(volume)] Liters") // don't want to change the order volume appears in, suck it if(milla) // Values from milla/src/lib.rs, +1 due to array indexing difference. message += SPAN_NOTICE("Airtight N/E/S/W: [(milla[MILLA_INDEX_AIRTIGHT_DIRECTIONS] & MILLA_NORTH) ? "yes" : "no"]/[(milla[MILLA_INDEX_AIRTIGHT_DIRECTIONS] & MILLA_EAST) ? "yes" : "no"]/[(milla[MILLA_INDEX_AIRTIGHT_DIRECTIONS] & MILLA_SOUTH) ? "yes" : "no"]/[(milla[MILLA_INDEX_AIRTIGHT_DIRECTIONS] & MILLA_WEST) ? "yes" : "no"]") switch(milla[MILLA_INDEX_ATMOS_MODE]) // These are enum values, so they don't get increased. if(0) message += SPAN_NOTICE("Atmos Mode: Space") if(1) message += SPAN_NOTICE("Atmos Mode: Sealed") if(2) message += SPAN_NOTICE("Atmos Mode: Exposed to Environment (ID: [milla[MILLA_INDEX_ENVIRONMENT_ID]])") else message += SPAN_NOTICE("Atmos Mode: Unknown ([milla[MILLA_INDEX_ATMOS_MODE]]), contact a coder.") message += SPAN_NOTICE("Superconductivity N/E/S/W: [milla[MILLA_INDEX_SUPERCONDUCTIVITY_NORTH]]/[milla[MILLA_INDEX_SUPERCONDUCTIVITY_EAST]]/[milla[MILLA_INDEX_SUPERCONDUCTIVITY_SOUTH]]/[milla[MILLA_INDEX_SUPERCONDUCTIVITY_WEST]]") message += SPAN_NOTICE("Turf's Innate Heat Capacity: [milla[MILLA_INDEX_INNATE_HEAT_CAPACITY]]") message += SPAN_NOTICE("Hotspot: [floor(milla[MILLA_INDEX_HOTSPOT_TEMPERATURE]-T0C)] °C ([floor(milla[MILLA_INDEX_HOTSPOT_TEMPERATURE])] K), [round(milla[MILLA_INDEX_HOTSPOT_VOLUME] * CELL_VOLUME, 1)] Liters ([milla[MILLA_INDEX_HOTSPOT_VOLUME]]x)") message += SPAN_NOTICE("Wind: ([round(milla[MILLA_INDEX_WIND_X], 0.001)], [round(milla[MILLA_INDEX_WIND_Y], 0.001)])") message += SPAN_NOTICE("Fuel burnt last tick: [milla[MILLA_INDEX_FUEL_BURNT]] moles") to_chat(user, chat_box_examine(message.Join("
"))) return TRUE