// the standard tube light fixture /obj/machinery/light name = "light fixture" icon = 'icons/obj/lighting.dmi' icon_state = "tube" desc = "A lighting fixture." layer = WALL_OBJ_LAYER plane = GAME_PLANE_UPPER max_integrity = 100 use_power = ACTIVE_POWER_USE idle_power_usage = BASE_MACHINE_IDLE_CONSUMPTION * 0.02 active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION * 0.02 power_channel = AREA_USAGE_LIGHT //Lights are calc'd via area so they dont need to be in the machine list always_area_sensitive = TRUE ///What overlay the light should use var/overlay_icon = 'icons/obj/lighting_overlay.dmi' ///base description and icon_state var/base_state = "tube" ///Is the light on? var/on = FALSE ///Amount of power used var/static_power_used = 0 ///Luminosity when on, also used in power calculation var/brightness = 8 ///Basically the alpha of the emitted light source var/bulb_power = 1 ///Default colour of the light. var/bulb_colour = "#f3fffa" ///LIGHT_OK, _EMPTY, _BURNED or _BROKEN var/status = LIGHT_OK ///Should we flicker? var/flickering = FALSE ///The type of light item var/light_type = /obj/item/light/tube ///String of the light type, used in descriptions and in examine var/fitting = "tube" ///Count of number of times switched on/off, this is used to calculate the probability the light burns out var/switchcount = 0 ///True if rigged to explode var/rigged = FALSE ///Cell reference var/obj/item/stock_parts/cell/cell ///If true, this fixture generates a very weak cell at roundstart var/start_with_cell = TRUE ///Currently in night shift mode? var/nightshift_enabled = FALSE ///Set to FALSE to never let this light get switched to night mode. var/nightshift_allowed = TRUE ///Brightness of the nightshift light var/nightshift_brightness = 8 ///Alpha of the nightshift light var/nightshift_light_power = 0.45 ///Basecolor of the nightshift light var/nightshift_light_color = "#FFDDCC" ///If true, the light is in low power mode var/low_power_mode = FALSE ///If true, this light cannot ever be in low power mode var/no_low_power = FALSE ///If true, overrides lights to use emergency lighting var/major_emergency = FALSE ///Multiplier for this light's base brightness during a cascade var/bulb_major_emergency_brightness_mul = 0.75 ///Colour of the light when major emergency mode is on var/bulb_emergency_colour = "#ff4e4e" ///Multiplier for this light's base brightness in low power power mode var/bulb_low_power_brightness_mul = 0.25 ///Determines the colour of the light while it's in low power mode var/bulb_low_power_colour = "#FF3232" ///The multiplier for determining the light's power in low power mode var/bulb_low_power_pow_mul = 0.75 ///The minimum value for the light's power in low power mode var/bulb_low_power_pow_min = 0.5 ///Power usage - W per unit of luminosity var/power_consumption_rate = 20 /obj/machinery/light/Move() if(status != LIGHT_BROKEN) break_light_tube(TRUE) return ..() // create a new lighting fixture /obj/machinery/light/Initialize(mapload) . = ..() if(!mapload) //sync up nightshift lighting for player made lights var/area/our_area = get_area(src) var/obj/machinery/power/apc/temp_apc = our_area.apc nightshift_enabled = temp_apc?.nightshift_lights if(start_with_cell && !no_low_power) cell = new/obj/item/stock_parts/cell/emergency_light(src) RegisterSignal(src, COMSIG_LIGHT_EATER_ACT, .proc/on_light_eater) AddElement(/datum/element/atmos_sensitive, mapload) return INITIALIZE_HINT_LATELOAD /obj/machinery/light/LateInitialize() . = ..() switch(fitting) if("tube") if(prob(2)) break_light_tube(TRUE) if("bulb") if(prob(5)) break_light_tube(TRUE) addtimer(CALLBACK(src, .proc/update, FALSE), 0.1 SECONDS) /obj/machinery/light/Destroy() var/area/local_area = get_area(src) if(local_area) on = FALSE QDEL_NULL(cell) return ..() /obj/machinery/light/update_icon_state() switch(status) // set icon_states if(LIGHT_OK) //var/area/local_area = get_area(src) SKYRAT EDIT REMOVAL if(low_power_mode || major_emergency) // SKYRAT EDIT CHANGE icon_state = "[base_state]_emergency" else icon_state = "[base_state]" if(LIGHT_EMPTY) icon_state = "[base_state]-empty" if(LIGHT_BURNED) icon_state = "[base_state]-burned" if(LIGHT_BROKEN) icon_state = "[base_state]-broken" return ..() /obj/machinery/light/update_overlays() . = ..() if(!on || status != LIGHT_OK) return /* SKYRAT EDIT START - ORIGINAL: var/area/local_area = get_area(src) if(emergency_mode || (local_area?.fire)) */ if(low_power_mode || major_emergency) // SKYRAT EDIT END . += mutable_appearance(overlay_icon, "[base_state]_emergency") return if(nightshift_enabled) . += mutable_appearance(overlay_icon, "[base_state]_nightshift") return . += mutable_appearance(overlay_icon, base_state) //SKYRAT EDIT ADDITION BEGIN - AESTHETICS #define LIGHT_ON_DELAY_UPPER 3 SECONDS #define LIGHT_ON_DELAY_LOWER 1 SECONDS //SKYRAT EDIT END // Area sensitivity is traditionally tied directly to power use, as an optimization // But since we want it for fire reacting, we disregard that /obj/machinery/light/setup_area_power_relationship() . = ..() if(!.) return var/area/our_area = get_area(src) RegisterSignal(our_area, COMSIG_AREA_FIRE_CHANGED, .proc/handle_fire) /obj/machinery/light/on_enter_area(datum/source, area/area_to_register) ..() RegisterSignal(area_to_register, COMSIG_AREA_FIRE_CHANGED, .proc/handle_fire) handle_fire(area_to_register, area_to_register.fire) /obj/machinery/light/on_exit_area(datum/source, area/area_to_unregister) ..() UnregisterSignal(area_to_unregister, COMSIG_AREA_FIRE_CHANGED) /obj/machinery/light/proc/handle_fire(area/source, new_fire) SIGNAL_HANDLER update() // update the icon_state and luminosity of the light depending on its state /obj/machinery/light/proc/update(trigger = TRUE, instant = FALSE, play_sound = TRUE) //SKYRAT EDIT CHANGE switch(status) if(LIGHT_BROKEN,LIGHT_BURNED,LIGHT_EMPTY) on = FALSE low_power_mode = FALSE if(on) /* SKYRAT EDIT ORIGINAL var/brightness_set = brightness var/power_set = bulb_power var/color_set = bulb_colour if(color) color_set = color var/area/local_area = get_area(src) if (local_area?.fire) color_set = bulb_low_power_colour else if (nightshift_enabled) brightness_set = nightshift_brightness power_set = nightshift_light_power if(!color) color_set = nightshift_light_color else if (major_emergency) color_set = bulb_low_power_colour brightness_set = brightness * bulb_major_emergency_brightness_mul var/matching = light && brightness_set == light.light_range && power_set == light.light_power && color_set == light.light_color if(!matching) switchcount++ if(rigged) if(status == LIGHT_OK && trigger) explode() else if( prob( min(60, (switchcount**2)*0.01) ) ) if(trigger) burn_out() else use_power = ACTIVE_POWER_USE set_light( l_range = brightness_set, l_power = power_set, l_color = color_set ) */ //SKYRAT EDIT CHANGE BEGIN - AESTHETICS if(instant) turn_on(trigger, play_sound) else if(maploaded) turn_on(trigger, play_sound) maploaded = FALSE else if(!turning_on) turning_on = TRUE addtimer(CALLBACK(src, .proc/turn_on, trigger, play_sound), rand(LIGHT_ON_DELAY_LOWER, LIGHT_ON_DELAY_UPPER)) //SKYRAT EDIT END else if(has_emergency_power(LIGHT_EMERGENCY_POWER_USE) && !turned_off()) use_power = IDLE_POWER_USE low_power_mode = TRUE START_PROCESSING(SSmachines, src) else use_power = IDLE_POWER_USE set_light(l_range = 0) update_appearance() update_current_power_usage() broken_sparks(start_only=TRUE) //SKYRAT EDIT ADDITION BEGIN - AESTHETICS #undef LIGHT_ON_DELAY_UPPER #undef LIGHT_ON_DELAY_LOWER //SKYRAT EDIT END /obj/machinery/light/update_current_power_usage() if(!on && static_power_used > 0) //Light is off but still powered removeStaticPower(static_power_used, AREA_USAGE_STATIC_LIGHT) static_power_used = 0 else if(on) //Light is on, just recalculate usage var/static_power_used_new = 0 var/area/local_area = get_area(src) if (nightshift_enabled && !local_area?.fire) static_power_used_new = nightshift_brightness * nightshift_light_power * power_consumption_rate else static_power_used_new = brightness * bulb_power * power_consumption_rate if(static_power_used != static_power_used_new) //Consumption changed - update removeStaticPower(static_power_used, AREA_USAGE_STATIC_LIGHT) static_power_used = static_power_used_new addStaticPower(static_power_used, AREA_USAGE_STATIC_LIGHT) /obj/machinery/light/update_atom_colour() ..() update() /obj/machinery/light/proc/broken_sparks(start_only=FALSE) if(!QDELETED(src) && status == LIGHT_BROKEN && has_power() && MC_RUNNING()) if(!start_only) do_sparks(3, TRUE, src) var/delay = rand(BROKEN_SPARKS_MIN, BROKEN_SPARKS_MAX) addtimer(CALLBACK(src, .proc/broken_sparks), delay, TIMER_UNIQUE | TIMER_NO_HASH_WAIT) /obj/machinery/light/process() if (!cell) return PROCESS_KILL if(has_power()) if (cell.charge == cell.maxcharge) return PROCESS_KILL cell.charge = min(cell.maxcharge, cell.charge + LIGHT_EMERGENCY_POWER_USE) //Recharge emergency power automatically while not using it if(low_power_mode && !use_emergency_power(LIGHT_EMERGENCY_POWER_USE)) update(FALSE) //Disables emergency mode and sets the color to normal /obj/machinery/light/proc/burn_out() if(status == LIGHT_OK) status = LIGHT_BURNED icon_state = "[base_state]-burned" on = FALSE set_light(l_range = 0) // attempt to set the light's on/off status // will not switch on if broken/burned/empty /obj/machinery/light/proc/set_on(turn_on) on = (turn_on && status == LIGHT_OK) update() /obj/machinery/light/get_cell() return cell // examine verb /obj/machinery/light/examine(mob/user) . = ..() switch(status) if(LIGHT_OK) . += "It is turned [on? "on" : "off"]." if(LIGHT_EMPTY) . += "The [fitting] has been removed." if(LIGHT_BURNED) . += "The [fitting] is burnt out." if(LIGHT_BROKEN) . += "The [fitting] has been smashed." if(cell) . += "Its backup power charge meter reads [round((cell.charge / cell.maxcharge) * 100, 0.1)]%." //SKYRAT EDIT ADDITION if(constant_flickering) . += span_danger("The lighting ballast appears to be damaged, this could be fixed with a multitool.") //SKYRAT EDIT END // attack with item - insert light (if right type), otherwise try to break the light /obj/machinery/light/attackby(obj/item/tool, mob/living/user, params) //Light replacer code if(istype(tool, /obj/item/lightreplacer)) var/obj/item/lightreplacer/replacer = tool replacer.ReplaceLight(src, user) return //SKYRAT EDIT ADDITION if(istype(tool, /obj/item/multitool) && constant_flickering) to_chat(user, span_notice("You start repairing the ballast of [src] with [tool].")) if(do_after(user, 2 SECONDS, src)) stop_flickering() to_chat(user, span_notice("You repair the ballast of [src]!")) return //SKYRAT EDTI END // attempt to insert light if(istype(tool, /obj/item/light)) if(status == LIGHT_OK) to_chat(user, span_warning("There is a [fitting] already inserted!")) return add_fingerprint(user) var/obj/item/light/light_object = tool if(!istype(light_object, light_type)) to_chat(user, span_warning("This type of light requires a [fitting]!")) return if(!user.temporarilyRemoveItemFromInventory(light_object)) return add_fingerprint(user) if(status != LIGHT_EMPTY) drop_light_tube(user) to_chat(user, span_notice("You replace [light_object].")) else to_chat(user, span_notice("You insert [light_object].")) status = light_object.status switchcount = light_object.switchcount rigged = light_object.rigged brightness = light_object.brightness on = has_power() update() qdel(light_object) if(on && rigged) explode() return // attempt to stick weapon into light socket if(status != LIGHT_EMPTY) return ..() if(tool.tool_behaviour == TOOL_SCREWDRIVER) //If it's a screwdriver open it. tool.play_tool_sound(src, 75) user.visible_message(span_notice("[user.name] opens [src]'s casing."), \ span_notice("You open [src]'s casing."), span_hear("You hear a noise.")) deconstruct() return to_chat(user, span_userdanger("You stick \the [tool] into the light socket!")) if(has_power() && (tool.flags_1 & CONDUCT_1)) do_sparks(3, TRUE, src) if (prob(75)) electrocute_mob(user, get_area(src), src, (rand(7,10) * 0.1), TRUE) /obj/machinery/light/deconstruct(disassembled = TRUE) if(flags_1 & NODECONSTRUCT_1) qdel(src) return var/obj/structure/light_construct/new_light = null var/current_stage = 2 if(!disassembled) current_stage = 1 switch(fitting) if("tube") new_light = new /obj/structure/light_construct(loc) new_light.icon_state = "tube-construct-stage[current_stage]" if("bulb") new_light = new /obj/structure/light_construct/small(loc) new_light.icon_state = "bulb-construct-stage[current_stage]" new_light.setDir(dir) new_light.stage = current_stage if(!disassembled) new_light.take_damage(new_light.max_integrity * 0.5, sound_effect=FALSE) if(status != LIGHT_BROKEN) break_light_tube() if(status != LIGHT_EMPTY) drop_light_tube() new /obj/item/stack/cable_coil(loc, 1, "red") transfer_fingerprints_to(new_light) if(!QDELETED(cell)) new_light.cell = cell cell.forceMove(new_light) cell = null qdel(src) /obj/machinery/light/attacked_by(obj/item/attacking_object, mob/living/user) ..() if(status != LIGHT_BROKEN && status != LIGHT_EMPTY) return if(!on || !(attacking_object.flags_1 & CONDUCT_1)) return if(prob(12)) electrocute_mob(user, get_area(src), src, 0.3, TRUE) /obj/machinery/light/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1) . = ..() if(. && !QDELETED(src)) if(prob(damage_amount * 5)) break_light_tube() /obj/machinery/light/play_attack_sound(damage_amount, damage_type = BRUTE, damage_flag = 0) switch(damage_type) if(BRUTE) switch(status) if(LIGHT_EMPTY) playsound(loc, 'sound/weapons/smash.ogg', 50, TRUE) if(LIGHT_BROKEN) playsound(loc, 'sound/effects/hit_on_shattered_glass.ogg', 90, TRUE) else playsound(loc, 'sound/effects/glasshit.ogg', 90, TRUE) if(BURN) playsound(loc, 'sound/items/welder.ogg', 100, TRUE) // returns if the light has power /but/ is manually turned off // if a light is turned off, it won't activate emergency power /obj/machinery/light/proc/turned_off() var/area/local_area = get_area(src) return !local_area.lightswitch && local_area.power_light || flickering || constant_flickering //SKYRAT EDIT CHANGE // returns whether this light has power // true if area has power and lightswitch is on /obj/machinery/light/proc/has_power() var/area/local_area = get_area(src) //SKYRAT EDIT ADDITION BEGIN if(isnull(local_area)) return FALSE //SKYRAT EDIT END return local_area.lightswitch && local_area.power_light // returns whether this light has emergency power // can also return if it has access to a certain amount of that power /obj/machinery/light/proc/has_emergency_power(power_usage_amount) if(no_low_power || !cell) return FALSE if(power_usage_amount ? cell.charge >= power_usage_amount : cell.charge) return status == LIGHT_OK // attempts to use power from the installed emergency cell, returns true if it does and false if it doesn't /obj/machinery/light/proc/use_emergency_power(power_usage_amount = LIGHT_EMERGENCY_POWER_USE) if(!has_emergency_power(power_usage_amount)) return FALSE if(cell.charge > 300) //it's meant to handle 120 W, ya doofus visible_message(span_warning("[src] short-circuits from too powerful of a power cell!")) burn_out() return FALSE cell.use(power_usage_amount) set_light( l_range = brightness * bulb_low_power_brightness_mul, l_power = max(bulb_low_power_pow_min, bulb_low_power_pow_mul * (cell.charge / cell.maxcharge)), l_color = bulb_low_power_colour ) return TRUE /obj/machinery/light/proc/flicker(amount = rand(10, 20)) set waitfor = FALSE if(flickering) return flickering = TRUE if(on && status == LIGHT_OK) for(var/i in 1 to amount) if(status != LIGHT_OK) break on = !on update(FALSE, TRUE) //SKYRAT EDIT CHANGE sleep(rand(5, 15)) on = (status == LIGHT_OK) update(FALSE, TRUE) //SKYRAT EDIT CHANGE . = TRUE //did we actually flicker? flickering = FALSE // ai attack - make lights flicker, because why not /obj/machinery/light/attack_ai(mob/user) no_low_power = !no_low_power to_chat(user, span_notice("Emergency lights for this fixture have been [no_low_power ? "disabled" : "enabled"].")) update(FALSE) return // attack with hand - remove tube/bulb // if hands aren't protected and the light is on, burn the player /obj/machinery/light/attack_hand(mob/living/carbon/human/user, list/modifiers) . = ..() if(.) return user.changeNext_move(CLICK_CD_MELEE) add_fingerprint(user) if(status == LIGHT_EMPTY) to_chat(user, span_warning("There is no [fitting] in this light!")) return // make it burn hands unless you're wearing heat insulated gloves or have the RESISTHEAT/RESISTHEATHANDS traits if(!on) to_chat(user, span_notice("You remove the light [fitting].")) // create a light tube/bulb item and put it in the user's hand drop_light_tube(user) return var/protection_amount = 0 var/mob/living/carbon/human/electrician = user if(istype(electrician)) var/obj/item/organ/internal/stomach/maybe_stomach = electrician.getorganslot(ORGAN_SLOT_STOMACH) if(istype(maybe_stomach, /obj/item/organ/internal/stomach/ethereal)) var/obj/item/organ/internal/stomach/ethereal/stomach = maybe_stomach if(stomach.drain_time > world.time) return to_chat(electrician, span_notice("You start channeling some power through the [fitting] into your body.")) stomach.drain_time = world.time + LIGHT_DRAIN_TIME while(do_after(user, LIGHT_DRAIN_TIME, target = src)) stomach.drain_time = world.time + LIGHT_DRAIN_TIME if(istype(stomach)) to_chat(electrician, span_notice("You receive some charge from the [fitting].")) stomach.adjust_charge(LIGHT_POWER_GAIN) else to_chat(electrician, span_warning("You can't receive charge from the [fitting]!")) return if(electrician.gloves) var/obj/item/clothing/gloves/electrician_gloves = electrician.gloves if(electrician_gloves.max_heat_protection_temperature) protection_amount = (electrician_gloves.max_heat_protection_temperature > 360) else protection_amount = 1 if(protection_amount > 0 || HAS_TRAIT(user, TRAIT_RESISTHEAT) || HAS_TRAIT(user, TRAIT_RESISTHEATHANDS)) to_chat(user, span_notice("You remove the light [fitting].")) else if(istype(user) && user.dna.check_mutation(/datum/mutation/human/telekinesis)) to_chat(user, span_notice("You telekinetically remove the light [fitting].")) else var/obj/item/bodypart/affecting = electrician.get_bodypart("[(user.active_hand_index % 2 == 0) ? "r" : "l" ]_arm") if(affecting?.receive_damage( 0, 5 )) // 5 burn damage electrician.update_damage_overlays() if(HAS_TRAIT(user, TRAIT_LIGHTBULB_REMOVER)) to_chat(user, span_notice("You feel like you're burning, but you can push through.")) if(!do_after(user, 5 SECONDS, target = src)) return if(affecting?.receive_damage( 0, 10 )) // 10 more burn damage electrician.update_damage_overlays() to_chat(user, span_notice("You manage to remove the light [fitting], shattering it in process.")) break_light_tube() else to_chat(user, span_warning("You try to remove the light [fitting], but you burn your hand on it!")) return // create a light tube/bulb item and put it in the user's hand drop_light_tube(user) /obj/machinery/light/proc/set_major_emergency_light() major_emergency = TRUE update() /obj/machinery/light/proc/unset_major_emergency_light() major_emergency = FALSE update() /obj/machinery/light/proc/drop_light_tube(mob/user) var/obj/item/light/light_object = new light_type() light_object.status = status light_object.rigged = rigged light_object.brightness = brightness // light item inherits the switchcount, then zero it light_object.switchcount = switchcount switchcount = 0 light_object.update() light_object.forceMove(loc) if(user) //puts it in our active hand light_object.add_fingerprint(user) user.put_in_active_hand(light_object) status = LIGHT_EMPTY update() return light_object /obj/machinery/light/attack_tk(mob/user) if(status == LIGHT_EMPTY) to_chat(user, span_warning("There is no [fitting] in this light!")) return to_chat(user, span_notice("You telekinetically remove the light [fitting].")) // create a light tube/bulb item and put it in the user's hand var/obj/item/light/light_tube = drop_light_tube() return light_tube.attack_tk(user) // break the light and make sparks if was on /obj/machinery/light/proc/break_light_tube(skip_sound_and_sparks = FALSE) if(status == LIGHT_EMPTY || status == LIGHT_BROKEN) return if(!skip_sound_and_sparks) if(status == LIGHT_OK || status == LIGHT_BURNED) playsound(loc, 'sound/effects/glasshit.ogg', 75, TRUE) if(on) do_sparks(3, TRUE, src) status = LIGHT_BROKEN update() /obj/machinery/light/proc/fix() if(status == LIGHT_OK) return status = LIGHT_OK brightness = initial(brightness) on = TRUE update() /obj/machinery/light/zap_act(power, zap_flags) var/explosive = zap_flags & ZAP_MACHINE_EXPLOSIVE zap_flags &= ~(ZAP_MACHINE_EXPLOSIVE | ZAP_OBJ_DAMAGE) . = ..() if(explosive) explosion(src, flame_range = 5, adminlog = FALSE) qdel(src) // called when area power state changes /obj/machinery/light/power_change() SHOULD_CALL_PARENT(FALSE) var/area/local_area = get_area(src) set_on(local_area.lightswitch && local_area.power_light) // called when heated /obj/machinery/light/should_atmos_process(datum/gas_mixture/air, exposed_temperature) return exposed_temperature > 673 /obj/machinery/light/atmos_expose(datum/gas_mixture/air, exposed_temperature) if(prob(max(0, exposed_temperature - 673))) //0% at <400C, 100% at >500C break_light_tube() // explode the light /obj/machinery/light/proc/explode() set waitfor = 0 break_light_tube() // break it first to give a warning sleep(2) explosion(src, light_impact_range = 2, flash_range = -1) sleep(1) qdel(src) /obj/machinery/light/proc/on_light_eater(obj/machinery/light/source, datum/light_eater) SIGNAL_HANDLER . = COMPONENT_BLOCK_LIGHT_EATER if(status == LIGHT_EMPTY) return var/obj/item/light/tube = drop_light_tube() tube?.burn() return /obj/machinery/light/floor name = "floor light" icon = 'icons/obj/lighting.dmi' base_state = "floor" // base description and icon_state icon_state = "floor" brightness = 4 layer = LOW_OBJ_LAYER plane = FLOOR_PLANE light_type = /obj/item/light/bulb fitting = "bulb"