From f5df463785b9b4d4048b7ff9da069804992a9ee5 Mon Sep 17 00:00:00 2001 From: hobnob13 Date: Wed, 16 Apr 2008 06:41:35 +0000 Subject: [PATCH] Further code conversion, some of /obj/machinery/power to new format --- main-src/Code/!atoms.dm | 83 +- main-src/Code/Machinery/Power/_power.dm | 152 +++ main-src/Code/Machinery/Power/apc.dm | 702 +++++++++++ main-src/Code/Machinery/Power/monitor.dm | 107 ++ main-src/Code/Machinery/Power/solar.dm | 133 ++ .../Code/Machinery/Power/solar_control.dm | 288 +++++ main-src/Code/Machinery/Power/terminal.dm | 45 + main-src/Code/power.dm | 1100 ----------------- main-src/spacestation13.dme | 11 +- 9 files changed, 1437 insertions(+), 1184 deletions(-) create mode 100644 main-src/Code/Machinery/Power/_power.dm create mode 100644 main-src/Code/Machinery/Power/apc.dm create mode 100644 main-src/Code/Machinery/Power/monitor.dm create mode 100644 main-src/Code/Machinery/Power/solar.dm create mode 100644 main-src/Code/Machinery/Power/solar_control.dm create mode 100644 main-src/Code/Machinery/Power/terminal.dm diff --git a/main-src/Code/!atoms.dm b/main-src/Code/!atoms.dm index b4e8716..77697de 100644 --- a/main-src/Code/!atoms.dm +++ b/main-src/Code/!atoms.dm @@ -2598,53 +2598,8 @@ Total SMES charging rate should not exceed total power generation rate, or an ov icon = 'wire.dmi' -/obj/machinery/power - name = null - icon = 'power.dmi' - anchored = 1.0 - var/datum/powernet/powernet = null - var/netnum = 0 - var/directwired = 1 // by default, power machines are connected by a cable in a neighbouring turf - // if set to 0, requires a 0-X cable on this turf -/obj/machinery/power/apc - name = "area power controller" - icon_state = "apc0" - anchored = 1 - var/area/area - var/obj/item/weapon/cell/cell - var/start_charge = 90 // initial cell charge % - var/cell_type = 1 // 0=no cell, 1=regular, 2=high-cap (x5) - var/opened = 0 - var/lighting = 3 - var/equipment = 3 - var/environ = 3 - var/operating = 1 - var/charging = 0 - var/chargemode = 1 - var/chargecount = 0 - var/locked = 1 - var/coverlocked = 1 - var/tdir = null - var/obj/machinery/power/terminal/terminal = null - var/lastused_light = 0 - var/lastused_equip = 0 - var/lastused_environ = 0 - var/lastused_total = 0 - var/main_status = 0 - netnum = -1 // set so that APCs aren't found as powernet nodes - var/access = "4000/0002/0030" - var/allowed = "Systems" - -/obj/machinery/power/terminal - name = "terminal" - icon_state = "term" - desc = "An underfloor wiring terminal for power equipment" - level = 1 - var/obj/machinery/power/master = null - anchored = 1 - directwired = 0 // must have a cable on same turf connecting to terminal /obj/machinery/power/generator name = "generator" @@ -2663,12 +2618,7 @@ Total SMES charging rate should not exceed total power generation rate, or an ov var/lastgen = 0 var/lastgenlev = -1 -/obj/machinery/power/monitor - name = "power monitoring computer" - icon = 'stationobjs.dmi' - icon_state = "power_computer" - density = 1 - anchored = 1 + #define SMESMAXCHARGELEVEL 60000 #define SMESMAXOUTPUT 60000 @@ -2692,38 +2642,7 @@ Total SMES charging rate should not exceed total power generation rate, or an ov var/n_tag = null var/obj/machinery/power/terminal/terminal = null -/obj/machinery/power/solar - name = "solar panel" - desc = "A solar electrical generator." - icon = 'power.dmi' - icon_state = "sp_base" - anchored = 1 - density = 1 - directwired = 1 - var/id = 1 - var/obscured = 0 - var/sunfrac = 0 - var/adir = SOUTH - var/ndir = SOUTH - var/turn_angle = 0 - var/obj/machinery/power/solar_control/control -/obj/machinery/power/solar_control - name = "solar panel control" - desc = "A controller for solar panel arrays." - icon = 'enginecomputer.dmi' - icon_state = "solar_con" - anchored = 1 - density = 1 - directwired = 1 - var/id = 1 - var/cdir = 0 - var/gen = 0 - var/lastgen = 0 - var/track = 0 // on/off - var/trackrate = 600 // 300-900 seconds - var/trackdir = 1 // 0 =CCW, 1=CW - var/nexttime = 0 /obj/machinery/power/portable_gen name = "portable generator" diff --git a/main-src/Code/Machinery/Power/_power.dm b/main-src/Code/Machinery/Power/_power.dm new file mode 100644 index 0000000..cbae6d3 --- /dev/null +++ b/main-src/Code/Machinery/Power/_power.dm @@ -0,0 +1,152 @@ +/* + * Power Machines -- Base type of machines that connect to the cable network. + * + * Includes generator, SMES, APC, Solar panels, etc. + * + * Note: While almost all machines comsume power, most do so through their area use_power() proc. + * Power machines are those that connect directly to the /obj/cable network and supply and use power from it. + */ + + +obj/machinery/power + name = null + icon = 'power.dmi' + anchored = 1.0 + + var + datum/powernet/powernet = null // The powernet connected to this machine. + // powernets are the data structures that hold information (power usage, + // connected devices, etc.) about a contiguous network of cables and devices. + + netnum = 0 // the number of the connected powernet (index of the global list/powernets). + // if 0 or -1, the machine is not connected to a powernet. + + directwired = 1 // by default, power machines are connected by a cable in a neighbouring turf + // if set to 0, requires stub cable (ending at the centre) on this turf + + + // Common helper procs for all power machines + // Some machines override these. + + + // Add an amount of power to the connected powernet + // Done by generator-type machines to make power available + + proc/add_avail(var/amount) + if(powernet) + powernet.newavail += amount + + + // Add an amount of load to the connected powernet + // Done by machines that draw power from the network + + proc/add_load(var/amount) + if(powernet) + powernet.newload += amount + + + // Returns the surplus power available on the connected powernet + + proc/surplus() + if(powernet) + return powernet.avail-powernet.load + else + return 0 + + + // Returns the total available power (neglecting current load) available on the connected powernet + + proc/avail() + if(powernet) + return powernet.avail + else + return 0 + + + // Routines used when constructing power cable networks + + // Returns a list of cables that connect to this machine + // Searches all turfs within 1 step for a cable that points towards the machine + // If the machine is non-directwired, calls and returns get_indirect_connections() instead + + proc/get_connections() + + if(!directwired) + return get_indirect_connections() + + var/list/res = list() + var/cdir + + for(var/turf/T in orange(1, src)) + + cdir = get_dir(T, src) + + for(var/obj/cable/C in T) + + if(C.netnum) + continue + + if(C.d1 == cdir || C.d2 == cdir) + res += C + + return res + + + // Returns a list of stub cables (1/2 length cables that end in the centre of a turf) + // On the same turf as a non-directwired machine. + + proc/get_indirect_connections() + + var/list/res = list() + + for(var/obj/cable/C in src.loc) + + if(C.netnum) + continue + + if(C.d1 == 0) + res += C + + return res + + + // Attacking a power machine with a cable_coil object attaches a wire to the machine + // leading from the turf the player is standing on. + // Only machines with directwired=1 need or use this. + + attackby(obj/item/weapon/W, mob/user) + + if(istype(W, /obj/item/weapon/cable_coil)) + + var/obj/item/weapon/cable_coil/coil = W + + var/turf/T = user.loc + + if(T.intact || !istype(T, /turf/station/floor)) + return + + if(get_dist(src, user) > 1) + return + + if(!directwired) // only for attaching to directwired machines + return + + var/dirn = get_dir(user, src) + + + for(var/obj/cable/LC in T) + if(LC.d1 == dirn || LC.d2 == dirn) + user << "There's already a cable at that position." + return + + var/obj/cable/NC = new(T) + NC.d1 = 0 + NC.d2 = dirn + NC.add_fingerprint() + NC.updateicon() + NC.update_network() + coil.use(1) + return + else + ..() + return diff --git a/main-src/Code/Machinery/Power/apc.dm b/main-src/Code/Machinery/Power/apc.dm new file mode 100644 index 0000000..523480d --- /dev/null +++ b/main-src/Code/Machinery/Power/apc.dm @@ -0,0 +1,702 @@ +/* + * APC - Area power controller. + * + * APCs control the distribution of power from the cable power network to each area + * They have three channels, equipment, lighting, and environmental, which can be independently controlled + * Each channel may be switched off to reduce power demand, and may do so automatically. + * + * The APC contains a power cell object that is used as a backup power supply for the area, which charges when + * external power is sufficient. The cell may also be removed if the cover lock is diengaged. + * + * The APC controls require an ID card swipe to be unlocked. Once unlocked, each channel can be controlled. + * + * TODO: More graceful behaviour when there is more than one APC per area. Currenly, they may conflict if this happens. + * and cell drain rate is not accurate. + * TODO: Extend logic when a cell is not inserted. Currently, the APC turns off completely if no cell is inside. + * Would be better to do some kind of channel switching depending on available external power. + * TODO: Reduction of thrashing when total load exceeds demand. Perhaps needs some kind of (settable?) priority + * for each APC, so more important APCs are powered first. Thrashing occurs because all APCs see surplus power, + * then switch on charging, which reduces available power below the limit, thus causing all APCs to stop charging. + * Fix requires some kind of cumulative load variable in the powenet, but map order processing of this would be + * unsuitable. + */ + + +obj/machinery/power/apc + name = "area power controller" + + icon_state = "apc0" + anchored = 1 + netnum = -1 // Always -1, set so that APCs aren't found as powernet nodes + // instead, all connections are done through the associated terminal object + var + area/area // the area that this APC controls + obj/item/weapon/cell/cell // the power cell object inserted in this APC (or null if none) + start_charge = 90 // initial cell charge % + cell_type = 1 // the cell type: 0=no cell, 1=regular, 2=high-cap (x5) + opened = 0 // true if the APC is opened (cell exposed) + locked = 1 // true if the APC interface is locked (non-alterable) + coverlocked = 1 // true if the APC cover is locked + + lighting = 3 // The status of the 3 area power channels + equipment = 3 // 0=Off, 1=Auto Off, 2=On, 3=Auto On + environ = 3 // When set to "auto", the channel will change between states depending on power conditions + + operating = 1 // True if the APC is turned on (main breaker) + charging = 0 // Cell: 0=Not charging 1=Charging, 2=Fully Charged + chargemode = 1 // Cell charging mode 0=Off 1=Auto (charge whenever power available) + chargecount = 0 // Count used to ensure power status is stable before switching to charging mode + + tdir = null // Direction of APC from terminal + obj/machinery/power/terminal/terminal = null // The associated power terminal + + lastused_light = 0 // Last power usage of lighting channel in this area + lastused_equip = 0 // Last power usage of equipment channel + lastused_environ = 0 // Last power usage of environmental channel + lastused_total = 0 // Total last power usage of this area + + main_status = 0 // Main power (powernet) status: 0=Node, 1=Low, 2=Good + access = "4000/0002/0030" // ID card access levels needed to lock/unlock interface + allowed = "Systems" // ID card job assignment needed to lock/unlock interface + + + + // Create an APC + + New() + ..() + + // offset 24 pixels in direction of dir + // this allows the APC to be embedded in a wall, yet still inside an area + + tdir = dir + dir = SOUTH // to fix Vars bug + + // Pixel offsets so that APC actually appears embedded in the wall + pixel_x = (tdir & 3)? 0 : (tdir == 4 ? 24 : -24) + pixel_y = (tdir & 3)? (tdir ==1 ? 24 : -24) : 0 + + + // if starting with a power cell installed, create it and set its charge level + if(cell_type) + src.cell = new/obj/item/weapon/cell(src) + cell.maxcharge = cell_type==1 ? 1000 : 5000 // if type=2, make a hp cell + cell.charge = start_charge * cell.maxcharge / 100.0 // (convert percentage to actual value) + + + var/area/A = src.loc.loc // the area this APC controls + + if(isarea(A)) + src.area = A + + updateicon() + + // create a terminal object at the same position as original turf loc + // wires will attach to this rather than the APC itself + + terminal = new/obj/machinery/power/terminal(src.loc) + terminal.dir = tdir + terminal.master = src + + spawn(5) + update() + + + // Examine verb + + examine() + set src in oview(1) + + if(stat & BROKEN) return + + if(usr && !usr.stat) + usr << "A control terminal for the area electrical systems." + if(opened) + usr << "The cover is open and the power cell is [ cell ? "installed" : "missing"]." + else + usr << "The cover is closed." + + + // Update the APC icon to show the three base states (normal, opened with cell, opened without cell) + // also add overlays for indicator lights + + proc/updateicon() + if(opened) + icon_state = "[ cell ? "apc2" : "apc1" ]" // if opened, show cell if it's inserted + src.overlays = null // also delete all overlays + else + icon_state = "apc0" + + // if closed, update overlays for channel status + + src.overlays = null + + overlays += image('power.dmi', "apcox-[locked]") // 0=blue 1=red + overlays += image('power.dmi', "apco3-[charging]") // 0=red, 1=yellow/black 2=green + + + if(operating) + overlays += image('power.dmi', "apco0-[equipment]") // 0=red, 1=green, 2=blue + overlays += image('power.dmi', "apco1-[lighting]") + overlays += image('power.dmi', "apco2-[environ]") + + + //Attack with an item - open/close cover, insert cell, or (un)lock interface + + attackby(obj/item/weapon/W, mob/user) + + if(stat & BROKEN) return + + if (istype(W, /obj/item/weapon/screwdriver)) // screwdriver means open or close the cover + if(opened) + opened = 0 + updateicon() + else + if(coverlocked) + user << "The cover is locked and cannot be opened." + else + opened = 1 + updateicon() + + else if (istype(W, /obj/item/weapon/cell) && opened) // trying to put a cell inside + if(cell) + user << "There is a power cell already installed." + else + user.drop_item() + W.loc = src + cell = W + user << "You insert the power cell." + chargecount = 0 + + updateicon() + else if (istype(W, /obj/item/weapon/card/id) ) // trying to unlock the interface with an ID card + + if(opened) + user << "You must close the cover to swipe an ID card." + else + var/obj/item/weapon/card/id/I = W + if (I.check_access(access, allowed)) + locked = !locked + user << "You [ locked ? "lock" : "unlock"] the APC interface." + updateicon() + else + user << "\red Access denied." + + else if (istype(W, /obj/item/weapon/card/emag) ) // trying to unlock with an emag card + + if(opened) + user << "You must close the cover to swipe an ID card." + else + flick("apc-spark", src) + sleep(6) + if(prob(50)) + locked = !locked + user << "You [ locked ? "lock" : "unlock"] the APC interface." + updateicon() + else + user << "You fail to [ locked ? "unlock" : "lock"] the APC interface." + + + // Attack with hand - remove cell (if present and cover open) or interact with the APC + + attack_hand(mob/user) + + add_fingerprint(user) + + if(stat & BROKEN) return + + if(opened) + if(cell) + cell.loc = usr + cell.layer = 20 + if (user.hand ) + user.l_hand = cell + else + user.r_hand = cell + + cell.add_fingerprint(user) + cell.updateicon() + + src.cell = null + user << "You remove the power cell." + charging = 0 + src.updateicon() + + else + // do APC interaction + src.interact(user) + + + // Shows APC interaction window + + proc/interact(mob/user) + + if ( (get_dist(src, user) > 1 )) + user.machine = null + user << browse(null, "window=apc") + return + + user.machine = src + var/t = "Area Power Controller ([area.name])
" + + if(locked) // If interface is locked, show status only + t += "(Swipe ID card to unlock inteface.)
" + t += "Main breaker : [operating ? "On" : "Off"]
" + t += "External power : [ main_status ? (main_status ==2 ? "Good" : "Low") : "None"]
" + t += "Power cell: [cell ? "[round(cell.percent())]%" : "Not connected."]" + if(cell) + t += " ([charging ? ( charging == 1 ? "Charging" : "Fully charged" ) : "Not charging"])" + t += " ([chargemode ? "Auto" : "Off"])" + + t += "

Power channels
"
+
+			var/list/L = list ("Off","Off (Auto)", "On", "On (Auto)")
+
+			t += "Equipment:    [add_lspace(lastused_equip, 6)] W : [L[equipment+1]]
" + t += "Lighting: [add_lspace(lastused_light, 6)] W : [L[lighting+1]]
" + t += "Environmental:[add_lspace(lastused_environ, 6)] W : [L[environ+1]]
" + + t += "
Total load: [lastused_light + lastused_equip + lastused_environ] W
" + t += "
Cover lock: [coverlocked ? "Engaged" : "Disengaged"]" + + else // If interface is unlocked, show status and control links + t += "(Swipe ID card to lock interface.)
" + t += "Main breaker: [operating ? "On Off" : "On Off" ]
" + t += "External power : [ main_status ? (main_status ==2 ? "Good" : "Low") : "None"]
" + if(cell) + t += "Power cell: [round(cell.percent())]%" + t += " ([charging ? ( charging == 1 ? "Charging" : "Fully charged" ) : "Not charging"])" + t += " ([chargemode ? "Off Auto" : "Off Auto"])" + + else + t += "Power cell: Not connected." + + t += "

Power channels
"
+
+
+			t += "Equipment:    [add_lspace(lastused_equip, 6)] W : "
+			switch(equipment)
+				if(0)
+					t += "Off On Auto"
+				if(1)
+					t += "Off On Auto (Off)"
+				if(2)
+					t += "Off On Auto"
+				if(3)
+					t += "Off On Auto (On)"
+			t +="
" + + t += "Lighting: [add_lspace(lastused_light, 6)] W : " + + switch(lighting) + if(0) + t += "Off On Auto" + if(1) + t += "Off On Auto (Off)" + if(2) + t += "Off On Auto" + if(3) + t += "Off On Auto (On)" + t +="
" + + + t += "Environmental:[add_lspace(lastused_environ, 6)] W : " + switch(environ) + if(0) + t += "Off On Auto" + if(1) + t += "Off On Auto (Off)" + if(2) + t += "Off On Auto" + if(3) + t += "Off On Auto (On)" + + + + t += "
Total load: [lastused_light + lastused_equip + lastused_environ] W
" + t += "
Cover lock: [coverlocked ? "Engaged" : "Disengaged"]" + + t += "

Close" + + t += "
" + user << browse(t, "window=apc") + return + + + //Returns a string showing the status of the APC. + + proc/report() + return "[area.name] : [equipment]/[lighting]/[environ] ([lastused_equip+lastused_light+lastused_environ]) : [cell? cell.percent() : "N/C"] ([charging])" + + + + // Called whenever the status of an APC control changes. + // Sets the underlying area power status variables, and informs the area that something has changed. + + proc/update() + if(operating) + area.power_light = (lighting > 1) + area.power_equip = (equipment > 1) + area.power_environ = (environ > 1) + else + area.power_light = 0 + area.power_equip = 0 + area.power_environ = 0 + + area.power_change() + + + // Handle topic links from the interaction window + + Topic(href, href_list) + + ..() + + if (usr.stat || usr.restrained() ) + return + if ((!( istype(usr, /mob/human) ) && (!( ticker ) || (ticker && ticker.mode != "monkey")))) + usr << "\red You don't have the dexterity to do this!" + return + + if (( (get_dist(src, usr) <= 1 && istype(src.loc, /turf)))) + + usr.machine = src + if (href_list["lock"]) + coverlocked = !coverlocked + + else if (href_list["breaker"]) + operating = !operating + src.update() + updateicon() + + else if (href_list["cmode"]) + chargemode = !chargemode + if(!chargemode) + charging = 0 + updateicon() + + else if (href_list["eqp"]) + var/val = text2num(href_list["eqp"]) + + equipment = (val==1) ? 0 : val + + updateicon() + update() + + else if (href_list["lgt"]) + var/val = text2num(href_list["lgt"]) + + lighting = (val==1) ? 0 : val + + updateicon() + update() + else if (href_list["env"]) + var/val = text2num(href_list["env"]) + + environ = (val==1) ? 0 :val + + updateicon() + update() + else if( href_list["close"] ) + usr << browse(null, "window=apc") + usr.machine = null + return + + + for(var/mob/M in viewers(1, src)) + if ((M.client && M.machine == src)) + src.interact(M) + //Foreach goto(275) + else + usr << browse(null, "window=apc") + usr.machine = null + + return + + + // Helper functions for interfacing to the powernet + // Overriden for APC since they connect to the powernet only through the terminal + + // Return the surplus power of the powernet + + surplus() + if(terminal) + return terminal.surplus() + else + return 0 + + + // Add a load amount to the powernet + + add_load(var/amount) + if(terminal && terminal.powernet) + terminal.powernet.newload += amount + + + // Return the available power (neglecting load) of the powernet + + avail() + if(terminal) + return terminal.avail() + else + return 0 + +// Constant - amount of power used to charge the power cell + +#define CHARGELEVEL 500 + + + // APC timed process - executed ~once per second + // Calculates the channel settings and cell charging status depending on area power usage, + // power available from the network, and cell charge level. + + process() + + if(stat & BROKEN) + return + + if(!area.requires_power) // set for an area if it never requires power + return + + area.calc_lighting() // calculate the power used for lighting an area (by number of turfs) + + // Find the cumulative power usage for the APC's area. Then reset it. + + lastused_light = area.usage(LIGHT) + lastused_equip = area.usage(EQUIP) + lastused_environ = area.usage(ENVIRON) + area.clear_usage() + + lastused_total = lastused_light + lastused_equip + lastused_environ + + + //cache control states so to update icon only if any change during this proc + var/last_lt = lighting + var/last_eq = equipment + var/last_en = environ + var/last_ch = charging + + + var/excess = surplus() + + // Set the external power display depending on the surplus power on the powernet + + if(!src.avail()) + main_status = 0 + else if(excess < 0) + main_status = 1 + else + main_status = 2 + + + // Perapc is the calculated power surplus evenly divided by every APC in the network. This is used in some + // anti-thrashing calculations + + var/perapc = 0 + if(terminal && terminal.powernet) + perapc = terminal.powernet.perapc + + if(cell) // If a power cell is present + + // draw power from cell + + var/cellused = min(cell.charge, CELLRATE * lastused_total) // clamp deduction to a max, amount left in cell + + cell.charge -= cellused // reduce the cell charge level + + + + // set channels depending on how much charge we have left + + + if(cell.charge <= 0) // zero charge, turn all off + equipment = autoset(equipment, 2) + lighting = autoset(lighting, 2) + environ = autoset(environ, 2) + else if(cell.percent() < 15) // <15%, turn off lighting & equipment + equipment = autoset(equipment, 2) + lighting = autoset(lighting, 2) + environ = autoset(environ, 1) + else if(cell.percent() < 30) // <30%, turn off equipment + equipment = autoset(equipment, 2) + lighting = autoset(lighting, 1) + environ = autoset(environ, 1) + else // otherwise all can be on + equipment = autoset(equipment, 1) + lighting = autoset(lighting, 1) + environ = autoset(environ, 1) + + + if(excess > 0 || perapc > lastused_total) // if power excess, or enough anyway, recharge the cell + // by the same amount just used + + cell.charge = min(cell.maxcharge, cell.charge + cellused) + + add_load(cellused/CELLRATE) // add the load used to recharge the cell + + + else // no excess, and not enough per-apc + + if( (cell.charge/CELLRATE+perapc) >= lastused_total) // can we draw enough from cell+grid to cover last usage? + + cell.charge = min(cell.maxcharge, cell.charge + CELLRATE * perapc) //recharge with what we can + + add_load(perapc) // so draw what we can from the grid + charging = 0 + + else // not enough! + charging = 0 // kill everything + chargecount = 0 + equipment = autoset(equipment, 0) + lighting = autoset(lighting, 0) + environ = autoset(environ, 0) + + + + // now trickle-charge the cell + + + if(chargemode && charging == 1) + if(excess > 0) // check to make sure we have enough to charge + + var/ch = min(CHARGELEVEL, (cell.maxcharge - cell.charge)/CELLRATE ) // clamp charging to max free in cell + + ch = min(ch, perapc) // clamp charging to our share + + add_load(CHARGELEVEL) + + cell.charge += ch * CELLRATE // actually recharge the cell + + else + + charging = 0 // stop charging + chargecount = 0 + + + + // show cell as fully charged if so + + if(cell.charge >= cell.maxcharge) + charging = 2 + + // switch between charging and not depending on stability of external power + + if(chargemode) + if(!charging) + if(excess > CHARGELEVEL) + chargecount++ + else + chargecount = 0 + + + if(chargecount == 5) // right amount of excess power must be available for 5 second before switching to charge + + chargecount = 0 + charging = 1 + + else // chargemode off + charging = 0 + chargecount = 0 + + + + + else + // no cell + + // for now, switch everything off + + // TODO: Something more logical here, depending on excess external power + + charging = 0 + chargecount = 0 + equipment = autoset(equipment, 0) + lighting = autoset(lighting, 0) + environ = autoset(environ, 0) + + + + // update icon & area power only if anything changed + + if(last_lt != lighting || last_eq != equipment || last_en != environ || last_ch != charging) + updateicon() + update() + + // update any player looking at the interaction window + + for(var/mob/M in viewers(1, src)) + if ((M.client && M.machine == src)) + src.interact(M) + + + // If APC hit by a meteor, break it + + meteorhit(var/obj/O) + set_broken() + return + + // APC in explosion, chance to break depending on the severity + + ex_act(severity) + + switch(severity) + if(1.0) + set_broken() + del(src) + return + if(2.0) + if (prob(50)) + set_broken() + if(3.0) + if (prob(25)) + set_broken() + else + return + + // Blob attack + + blob_act() + if (prob(50)) + set_broken() + + + // Called to set the APC into a broken state. + // Set Stat and broken icon_state, inform area to turn everything off. + // Can't (yet) be fixed again. + + proc/set_broken() + stat |= BROKEN + icon_state = "apc-b" + overlays = null + + operating = 0 + update() + + +// Global helper proc, used only in apc/process() +// returns the new state of a channel control, given the current settings +// This is so a channel can switch between AutoOn and AutoOff depending on available power levels +// But a channel set to On will stay on until power is zero, when it will switch to Off and stay there + +// val 0=off, 1=off(auto) 2=on 3=on(auto) +// on 0=off, 1=on, 2=autooff + +/proc/autoset(var/val, var/on) + + if(on==0) + if(val==2) // if on, return off + return 0 + else if(val==3) // if auto-on, return auto-off + return 1 + + else if(on==1) + if(val==1) // if auto-off, return auto-on + return 3 + + else if(on==2) + if(val==3) // if auto-on, return auto-off + return 1 + + return val + + diff --git a/main-src/Code/Machinery/Power/monitor.dm b/main-src/Code/Machinery/Power/monitor.dm new file mode 100644 index 0000000..a9b98c3 --- /dev/null +++ b/main-src/Code/Machinery/Power/monitor.dm @@ -0,0 +1,107 @@ +/* + * Power Monitor - reports the available power, load, and status of APCs on the same power network. + * + * TODO: Make the power monitor also able to remote-control APCs (with sufficient ID access) to shut down things remotely. + */ + +obj/machinery/power/monitor + name = "power monitoring computer" + icon = 'stationobjs.dmi' + icon_state = "power_computer" + density = 1 + anchored = 1 + + + // Attack with hand, show report window + + attack_hand(mob/user) + add_fingerprint(user) + + if(stat & (BROKEN|NOPOWER)) + return + interact(user) + + + // Show the interaction window to the user + + proc/interact(mob/user) + + if ( (get_dist(src, user) > 1 ) || (stat & (BROKEN|NOPOWER)) ) + user.machine = null + user << browse(null, "window=powcomp") + return + + + user.machine = src + var/t = "Power Monitoring
" + + + if(!powernet) + t += "\red No connection" + else + + var/list/L = list() + for(var/obj/machinery/power/terminal/term in powernet.nodes) + if(istype(term.master, /obj/machinery/power/apc)) + var/obj/machinery/power/apc/A = term.master + L += A + + t += "
Total power: [powernet.avail] W
Total load: [num2text(powernet.viewload,10)] W
" + + t += "" + + if(L.len > 0) + + t += "Area Eqp./Lgt./Env. Load Cell
" + + var/list/S = list(" Off","AOff"," On", " AOn") + var/list/chg = list("N","C","F") + + for(var/obj/machinery/power/apc/A in L) + + t += copytext(add_tspace(A.area.name, 30), 1, 30) + t += " [S[A.equipment+1]] [S[A.lighting+1]] [S[A.environ+1]] [add_lspace(A.lastused_total, 6)] [A.cell ? "[add_lspace(round(A.cell.percent()), 3)]% [chg[A.charging+1]]" : " N/C"]
" + + t += "
" + + t += "

Close
" + + user << browse(t, "window=powcomp;size=420x700") + + + // Handle topic links from the interaction window (close only at the moment) + + Topic(href, href_list) + ..() + if( href_list["close"] ) + usr << browse(null, "window=powcomp") + usr.machine = null + return + + // Timed process - use power, update window to viewers + + process() + if(!(stat & (NOPOWER|BROKEN)) ) + + use_power(250) + + + for(var/mob/M in viewers(1, src)) + if ((M.client && M.machine == src)) + src.interact(M) + + + // Power changed in location area - update icon to unpowered state, set stat + + power_change() + + if(stat & BROKEN) + icon_state = "broken" + else + if( powered() ) + icon_state = initial(icon_state) + stat &= ~NOPOWER + else + spawn(rand(0, 15)) + src.icon_state = "c_unpowered" + stat |= NOPOWER diff --git a/main-src/Code/Machinery/Power/solar.dm b/main-src/Code/Machinery/Power/solar.dm new file mode 100644 index 0000000..75d4622 --- /dev/null +++ b/main-src/Code/Machinery/Power/solar.dm @@ -0,0 +1,133 @@ +/* + * Solar -- Solar panel power generator machine. + * + * + */ + +#define SOLARGENRATE 1500 // maximum power output of a single panel when it is facing exactly towards the sun + +obj/machinery/power/solar + name = "solar panel" + desc = "A solar electrical generator." + icon = 'power.dmi' + icon_state = "sp_base" + anchored = 1 + density = 1 + directwired = 1 + var + id = 1 // solar_control must have matching id (and be on same powernet) to control this machine + obscured = 0 // true if the panel is in shadow (thus does not generate power) + sunfrac = 0 // fraction (0.0-1.0) of the maximum exposure of the solar panel to the sun + // calculated from the relative angle of the sun and the panel + adir = SOUTH // the current direction of the solar panel + ndir = SOUTH // the new set direction of the panel + turn_angle = 0 // the angle to turn through to get to the set angle; -45 or +45 + obj/machinery/power/solar_control/control // the controller for this panel + + + // Create a new solar panel + // Search the connected powernet for a solar_control with matching ID; if found, set as the controller + + New() + ..() + spawn(10) // wait until sure powernets have been built + updateicon() + updatefrac() + + if(powernet) + for(var/obj/machinery/power/solar_control/SC in powernet.nodes) + if(SC.id == id) + control = SC + + + // Updates the icon for the solar panel + // The object icon is just the base, with the panel itself being an 8-direction overlay + + proc/updateicon() + src.overlays = null + if(stat & BROKEN) + overlays += image('power.dmi', "solar_panel-b", FLY_LAYER) + else + overlays += image('power.dmi', "solar_panel", FLY_LAYER, adir) + + + // Calculate the fraction of power produced by the panel + // Depends if the panel is obscured by shadow, and the relative angle of the panel and the sun + // The global /datum/sun/sun holds the current sun position + + proc/updatefrac() + + if(obscured) + sunfrac = 0 + return + + var/p_angle = dir2angle(adir) - sun.angle + + if(abs(p_angle) > 90) // if facing more than 90deg from sun, zero output + sunfrac = 0 + return + + sunfrac = cos(p_angle)*cos(p_angle) // this is the fraction of the panel area which subtends the sun's output + + + // Timed process. Generate power (if in sunlight), and turn the current panel angle if it is not at the set angle + + process() + + if(stat & BROKEN) + return + + if(!obscured) + var/sgen = SOLARGENRATE * sunfrac + add_avail(sgen) + if(powernet && control) + if(control in powernet.nodes) + control.gen += sgen // notify the controller of how much was generated + + + if(adir == ndir) // if current angle == set angle, stop turning + turn_angle = 0 + else // otherwise turn + spawn(rand(0,10)) // slight random delay is to stop all panels turning in lockstep + adir = turn(adir, turn_angle) + updateicon() + updatefrac() // update the panel icon and the fractional power production for the new angle + + + // Makes the panel broken and updates the icon to the broken state + // No way to fix panels (yet) + + proc/broken() + stat |= BROKEN + updateicon() + + + // When hit by a meteor, break + + meteorhit() + broken() + + + // In an explosion, totally destroyed or a chance of breaking, depending on severity + + ex_act(severity) + switch(severity) + if(1.0) + //SN src = null + del(src) + return + if(2.0) + if (prob(50)) + broken() + if(3.0) + if (prob(25)) + broken() + return + + + // Blob attack + + blob_act() + if (prob(50)) + broken() + src.density = 0 \ No newline at end of file diff --git a/main-src/Code/Machinery/Power/solar_control.dm b/main-src/Code/Machinery/Power/solar_control.dm new file mode 100644 index 0000000..e41c487 --- /dev/null +++ b/main-src/Code/Machinery/Power/solar_control.dm @@ -0,0 +1,288 @@ +/* + * Solar_control -- A machine that controls solar panels (/obj/machinery/power/solar) + * + * The controller must be on the same powernet and have the same id value as the solar panel + */ + + +obj/machinery/power/solar_control + name = "solar panel control" + desc = "A controller for solar panel arrays." + icon = 'enginecomputer.dmi' + icon_state = "solar_con" + anchored = 1 + density = 1 + directwired = 1 + var + id = 1 // must match that of the solar panels to control + cdir = 0 // the current direction of the controlled solar panels + gen = 0 // the current power generation by the controlled panels + lastgen = 0 // the last power generation value from the previous cycle + track = 0 // direction tracking on/off + trackrate = 600 // time between tracking turns, 300-900 seconds + trackdir = 1 // direction to turn when tracking, 0 =CCW, 1=CW + nexttime = 0 // timeofday until next tracking turn + + + // Create a new solar controller. + + New() + ..() + + spawn(15) // wait for powernets to be built after map load + + if(powernet) + for(var/obj/machinery/power/solar/S in powernet.nodes) + if(S.id == id) + cdir = S.adir // find the contorlled solar panels and set the current direction to match + updateicon() // also update the icon overlay + + + // Update the icon_state + // If broken or unpowered, set those states + // Otherwise, add an overlay showing the current panel direction + + proc/updateicon() + if(stat & BROKEN) + icon_state = "broken" + overlays = null + return + if(stat & NOPOWER) + icon_state = "c_unpowered" + overlays = null + return + + icon_state = "solar_con" + overlays = null + if(cdir > 0) + overlays += image('enginecomputer.dmi', "solcon-o", FLY_LAYER, cdir) + + + + // Attack by hand, open interaction window + + attack_hand(mob/user) + add_fingerprint(user) + + if(stat & (BROKEN | NOPOWER)) return + + interact(user) + + + // Timed process + // Update lastgen so it has the value of total power generated by panels last cycle + // Reset the current generation value to zero (to be added to in each /solar/process() proc) + // If tracking, rotate the panels if next tracking turn timer has expired + + process() + lastgen = gen + gen = 0 + + if(stat & (NOPOWER | BROKEN)) + return + + use_power(250) + + if(track && nexttime < world.timeofday) + if(trackdir) + cdir = turn(cdir, -45) + else + cdir = turn(cdir, 45) + set_panels(cdir) + + nexttime = world.timeofday + 10*trackrate + updateicon() + + + // Update all players view interaction window + + for(var/mob/M in viewers(1, src)) + if ((M.client && M.machine == src)) + src.interact(M) + + + // Display the interaction window + + proc/interact(mob/user) + + if ( (get_dist(src, user) > 1 )) + user.machine = null + user << browse(null, "window=solcon") + return + + user.machine = src + + var/t = "Solar Generator Control
"
+
+		t += "Generated power : [round(lastgen)] W

" + + t += "Current panel orientation: [uppertext(dir2text(cdir))]
" + + t += "
Set orientation:
" + + var/list/D = list(-1, NORTHWEST, NORTH, NORTHEAST, -1, WEST, 0, EAST, -1, SOUTHWEST, SOUTH, SOUTHEAST) + var/list/disp = list("|", "|", "", "-", "/", "\\", "", "-", "\\", "/") + + for(var/d in D) + if(d == 0) + t += " " + continue + if(d == -1) + t += "
" + continue + + if(d==cdir) + t +=" [disp[d]]" + else + t +=" O" + + + t += "


" + + t += "Tracking: [ track ? "Off On" : "Off On"]" + + t += " [trackdir ? "CCW CW" : "CCW CW"]
" + + t += "Rate: - - - [trackrate] + + + (seconds per turn)
" + + t += "

Close" + + t += "
" + user << browse(t, "window=solcon") + + return + + + // Handle topic links from the interaction window + + Topic(href, href_list) + ..() + + if (usr.stat || usr.restrained() ) + return + if ((!( istype(usr, /mob/human) ) && (!( ticker ) || (ticker && ticker.mode != "monkey")))) + usr << "\red You don't have the dexterity to do this!" + return + + //world << "[href] ; [href_list[href]]" + + if (( usr.machine==src && (get_dist(src, usr) <= 1 && istype(src.loc, /turf)))) + + + if( href_list["close"] ) + usr << browse(null, "window=solcon") + usr.machine = null + return + + else if( href_list["dir"] ) + cdir = text2num(href_list["dir"]) + + spawn(1) + set_panels(cdir) + + updateicon() + else if( href_list["tdir"] ) + trackdir = !trackdir + + else if( href_list["track"] ) + track = !track + nexttime = world.timeofday + 10*trackrate + + else if( href_list["trk"] ) + var/inc = text2num(href_list["trk"]) + + switch(inc) + if(1, -1) + trackrate += inc + if(2,-2) + trackrate += 10*inc/abs(inc) + if(3,-3) + trackrate += 100*inc/abs(inc) + + trackrate = min( max(trackrate, 10), 900) + nexttime = world.timeofday + 10*trackrate + + spawn(0) + for(var/mob/M in viewers(1, src)) + if ((M.client && M.machine == src)) + src.interact(M) + + else + usr << browse(null, "window=solcon") + usr.machine = null + + return + + + // Set a new set direction for all panels controlled by this controller + + proc/set_panels(var/cdir) + if(powernet) + for(var/obj/machinery/power/solar/S in powernet.nodes) + if(S.id == id) + S.control = src + + var/delta = dir2angle(S.adir) - dir2angle(cdir) + + delta = (delta+360)%360 + + if(delta>180) + S.turn_angle = -45 + else + S.turn_angle = 45 + + S.ndir = cdir + + + // Called when area power changes; update icons and stat to reflect state of equipment channel power + + power_change() + + if( powered() ) + stat &= ~NOPOWER + updateicon() + else + spawn(rand(0, 15)) + stat |= NOPOWER + updateicon() + + + // Set the solar_control as broken + + proc/broken() + stat |= BROKEN + updateicon() + + + // If hit by a meteor, break + + meteorhit() + + broken() + return + + + // If in an explosion, delete or chance to break depending on severity + + ex_act(severity) + + switch(severity) + if(1.0) + //SN src = null + del(src) + return + if(2.0) + if (prob(50)) + broken() + if(3.0) + if (prob(25)) + broken() + return + + // Attack by blob, chance to break + + blob_act() + if (prob(50)) + broken() + src.density = 0 + diff --git a/main-src/Code/Machinery/Power/terminal.dm b/main-src/Code/Machinery/Power/terminal.dm new file mode 100644 index 0000000..9c10bcb --- /dev/null +++ b/main-src/Code/Machinery/Power/terminal.dm @@ -0,0 +1,45 @@ +/* + * Terminal -- A wiring terminal power machine. + * + * A terminal does not do anything of itself. It is used to connect powernets to other power machines, + * either for those where direct connection is undesirable (APCs) + * or for those that need to be connected to two separate powernets (SMESes). + * + */ + +obj/machinery/power/terminal + name = "terminal" + icon_state = "term" + desc = "An underfloor wiring terminal for power equipment" + level = 1 // the terminal is always underfloor (level=1) + anchored = 1 + directwired = 0 // must have a cable on same turf connecting to terminal + + var + obj/machinery/power/master = null // the master power machine this terminal connects to + + + // Create a new terminal. The terminal is underfloor (level=1), so hide it if the floor is intact. + + // Note: terminals are auto-created when APCs are spawned + // All cable connections go to this object instead of the APC + // This solves the problem of having the APC in a wall yet also inside an area + + New() + ..() + var/turf/T = src.loc + if(level==1) hide(T.intact) + + + // Hide the terminal if "i" is true. + // Sets the terminal icon to invisible and to a faded icon_state + // This is done so T-scanners need only changes the invisibility setting to reveal a faded terminal icon + + hide(var/i) + + if(i) + invisibility = 101 + icon_state = "term-f" + else + invisibility = 0 + icon_state = "term" \ No newline at end of file diff --git a/main-src/Code/power.dm b/main-src/Code/power.dm index 08f2470..4499493 100644 --- a/main-src/Code/power.dm +++ b/main-src/Code/power.dm @@ -41,641 +41,13 @@ -// common helper procs for all power machines - -/obj/machinery/power/proc/add_avail(var/amount) - if(powernet) - powernet.newavail += amount - -/obj/machinery/power/proc/add_load(var/amount) - if(powernet) - powernet.newload += amount - -/obj/machinery/power/proc/surplus() - if(powernet) - return powernet.avail-powernet.load - else - return 0 - -/obj/machinery/power/proc/avail() - if(powernet) - return powernet.avail - else - return 0 - -// the Area Power Controller (APC), formerly Power Distribution Unit (PDU) -// one per area, needs wire conection to power network - -// controls power to devices in that area -// may be opened to change power cell -// three different channels (lighting/equipment/environ) - may each be set to on, off, or auto - -/obj/machinery/power/apc/New() - ..() - - // offset 24 pixels in direction of dir - // this allows the APC to be embedded in a wall, yet still inside an area - - tdir = dir // to fix Vars bug - dir = SOUTH - - pixel_x = (tdir & 3)? 0 : (tdir == 4 ? 24 : -24) - pixel_y = (tdir & 3)? (tdir ==1 ? 24 : -24) : 0 - - - // is starting with a power cell installed, create it and set its charge level - if(cell_type) - src.cell = new/obj/item/weapon/cell(src) - cell.maxcharge = cell_type==1 ? 1000 : 5000 // if type=2, make a hp cell - cell.charge = start_charge * cell.maxcharge / 100.0 // (convert percentage to actual value) - - - var/area/A = src.loc.loc - - if(isarea(A)) - src.area = A - - updateicon() - - // create a terminal object at the same position as original turf loc - // wires will attach to this - terminal = new/obj/machinery/power/terminal(src.loc) - terminal.dir = tdir - terminal.master = src - - spawn(5) - src.update() - -/obj/machinery/power/apc/examine() - set src in oview(1) - - if(stat & BROKEN) return - - if(usr && !usr.stat) - usr << "A control terminal for the area electrical systems." - if(opened) - usr << "The cover is open and the power cell is [ cell ? "installed" : "missing"]." - else - usr << "The cover is closed." - - - -// update the APC icon to show the three base states -// also add overlays for indicator lights -/obj/machinery/power/apc/proc/updateicon() - if(opened) - icon_state = "[ cell ? "apc2" : "apc1" ]" // if opened, show cell if it's inserted - src.overlays = null // also delete all overlays - else - icon_state = "apc0" - - // if closed, update overlays for channel status - - src.overlays = null - - overlays += image('power.dmi', "apcox-[locked]") // 0=blue 1=red - overlays += image('power.dmi', "apco3-[charging]") // 0=red, 1=yellow/black 2=green - - - if(operating) - overlays += image('power.dmi', "apco0-[equipment]") // 0=red, 1=green, 2=blue - overlays += image('power.dmi', "apco1-[lighting]") - overlays += image('power.dmi', "apco2-[environ]") - - - -//attack with an item - open/close cover, insert cell, or (un)lock interface - -/obj/machinery/power/apc/attackby(obj/item/weapon/W, mob/user) - - if(stat & BROKEN) return - - if (istype(W, /obj/item/weapon/screwdriver)) // screwdriver means open or close the cover - if(opened) - opened = 0 - updateicon() - else - if(coverlocked) - user << "The cover is locked and cannot be opened." - else - opened = 1 - updateicon() - - else if (istype(W, /obj/item/weapon/cell) && opened) // trying to put a cell inside - if(cell) - user << "There is a power cell already installed." - else - user.drop_item() - W.loc = src - cell = W - user << "You insert the power cell." - chargecount = 0 - - updateicon() - else if (istype(W, /obj/item/weapon/card/id) ) // trying to unlock the interface with an ID card - - if(opened) - user << "You must close the cover to swipe an ID card." - else - var/obj/item/weapon/card/id/I = W - if (I.check_access(access, allowed)) - locked = !locked - user << "You [ locked ? "lock" : "unlock"] the APC interface." - updateicon() - else - user << "\red Access denied." - - else if (istype(W, /obj/item/weapon/card/emag) ) // trying to unlock with an emag card - - if(opened) - user << "You must close the cover to swipe an ID card." - else - flick("apc-spark", src) - sleep(6) - if(prob(50)) - locked = !locked - user << "You [ locked ? "lock" : "unlock"] the APC interface." - updateicon() - else - user << "You fail to [ locked ? "unlock" : "lock"] the APC interface." - - -// attack with hand - remove cell (if cover open) or interact with the APC - -/obj/machinery/power/apc/attack_hand(mob/user) - - add_fingerprint(user) - - if(stat & BROKEN) return - - if(opened) - if(cell) - cell.loc = usr - cell.layer = 20 - if (user.hand ) - user.l_hand = cell - else - user.r_hand = cell - - cell.add_fingerprint(user) - cell.updateicon() - - src.cell = null - user << "You remove the power cell." - charging = 0 - src.updateicon() - - else - // do APC interaction - src.interact(user) - - - -/obj/machinery/power/apc/proc/interact(mob/user) - - if ( (get_dist(src, user) > 1 )) - user.machine = null - user << browse(null, "window=apc") - return - - user.machine = src - var/t = "Area Power Controller ([area.name])
" - - if(locked) - t += "(Swipe ID card to unlock inteface.)
" - t += "Main breaker : [operating ? "On" : "Off"]
" - t += "External power : [ main_status ? (main_status ==2 ? "Good" : "Low") : "None"]
" - t += "Power cell: [cell ? "[round(cell.percent())]%" : "Not connected."]" - if(cell) - t += " ([charging ? ( charging == 1 ? "Charging" : "Fully charged" ) : "Not charging"])" - t += " ([chargemode ? "Auto" : "Off"])" - - t += "

Power channels
"
-
-		var/list/L = list ("Off","Off (Auto)", "On", "On (Auto)")
-
-		t += "Equipment:    [add_lspace(lastused_equip, 6)] W : [L[equipment+1]]
" - t += "Lighting: [add_lspace(lastused_light, 6)] W : [L[lighting+1]]
" - t += "Environmental:[add_lspace(lastused_environ, 6)] W : [L[environ+1]]
" - - t += "
Total load: [lastused_light + lastused_equip + lastused_environ] W
" - t += "
Cover lock: [coverlocked ? "Engaged" : "Disengaged"]" - - else - t += "(Swipe ID card to lock interface.)
" - t += "Main breaker: [operating ? "On Off" : "On Off" ]
" - t += "External power : [ main_status ? (main_status ==2 ? "Good" : "Low") : "None"]
" - if(cell) - t += "Power cell: [round(cell.percent())]%" - t += " ([charging ? ( charging == 1 ? "Charging" : "Fully charged" ) : "Not charging"])" - t += " ([chargemode ? "Off Auto" : "Off Auto"])" - - else - t += "Power cell: Not connected." - - t += "

Power channels
"
-
-
-		t += "Equipment:    [add_lspace(lastused_equip, 6)] W : "
-		switch(equipment)
-			if(0)
-				t += "Off On Auto"
-			if(1)
-				t += "Off On Auto (Off)"
-			if(2)
-				t += "Off On Auto"
-			if(3)
-				t += "Off On Auto (On)"
-		t +="
" - - t += "Lighting: [add_lspace(lastused_light, 6)] W : " - - switch(lighting) - if(0) - t += "Off On Auto" - if(1) - t += "Off On Auto (Off)" - if(2) - t += "Off On Auto" - if(3) - t += "Off On Auto (On)" - t +="
" - - - t += "Environmental:[add_lspace(lastused_environ, 6)] W : " - switch(environ) - if(0) - t += "Off On Auto" - if(1) - t += "Off On Auto (Off)" - if(2) - t += "Off On Auto" - if(3) - t += "Off On Auto (On)" - - - - t += "
Total load: [lastused_light + lastused_equip + lastused_environ] W
" - t += "
Cover lock: [coverlocked ? "Engaged" : "Disengaged"]" - - t += "

Close" - - t += "
" - user << browse(t, "window=apc") - return - -/obj/machinery/power/apc/proc/report() - return "[area.name] : [equipment]/[lighting]/[environ] ([lastused_equip+lastused_light+lastused_environ]) : [cell? cell.percent() : "N/C"] ([charging])" - - - - -/obj/machinery/power/apc/proc/update() - if(operating) - area.power_light = (lighting > 1) - area.power_equip = (equipment > 1) - area.power_environ = (environ > 1) - else - area.power_light = 0 - area.power_equip = 0 - area.power_environ = 0 - - area.power_change() - - -/obj/machinery/power/apc/Topic(href, href_list) - - ..() - - if (usr.stat || usr.restrained() ) - return - if ((!( istype(usr, /mob/human) ) && (!( ticker ) || (ticker && ticker.mode != "monkey")))) - usr << "\red You don't have the dexterity to do this!" - return - - if (( (get_dist(src, usr) <= 1 && istype(src.loc, /turf)))) - - usr.machine = src - if (href_list["lock"]) - coverlocked = !coverlocked - - else if (href_list["breaker"]) - operating = !operating - src.update() - updateicon() - - else if (href_list["cmode"]) - chargemode = !chargemode - if(!chargemode) - charging = 0 - updateicon() - - else if (href_list["eqp"]) - var/val = text2num(href_list["eqp"]) - - equipment = (val==1) ? 0 : val - - updateicon() - update() - - else if (href_list["lgt"]) - var/val = text2num(href_list["lgt"]) - - lighting = (val==1) ? 0 : val - - updateicon() - update() - else if (href_list["env"]) - var/val = text2num(href_list["env"]) - - environ = (val==1) ? 0 :val - - updateicon() - update() - else if( href_list["close"] ) - usr << browse(null, "window=apc") - usr.machine = null - return - - - for(var/mob/M in viewers(1, src)) - if ((M.client && M.machine == src)) - src.interact(M) - //Foreach goto(275) - else - usr << browse(null, "window=apc") - usr.machine = null - - return - - - -#define CHARGELEVEL 500 - -/obj/machinery/power/apc/surplus() - if(terminal) - return terminal.surplus() - else - return 0 - -/obj/machinery/power/apc/add_load(var/amount) - if(terminal && terminal.powernet) - terminal.powernet.newload += amount - -/obj/machinery/power/apc/avail() - if(terminal) - return terminal.avail() - else - return 0 - -/obj/machinery/power/apc/process() - - if(stat & BROKEN) - return - - if(!area.requires_power) - return - - area.calc_lighting() - - lastused_light = area.usage(LIGHT) - lastused_equip = area.usage(EQUIP) - lastused_environ = area.usage(ENVIRON) - area.clear_usage() - - lastused_total = lastused_light + lastused_equip + lastused_environ - - - //store states to update icon if any change - var/last_lt = lighting - var/last_eq = equipment - var/last_en = environ - var/last_ch = charging - - - var/excess = surplus() - - if(!src.avail()) - main_status = 0 - else if(excess < 0) - main_status = 1 - else - main_status = 2 - - var/perapc = 0 - if(terminal && terminal.powernet) - perapc = terminal.powernet.perapc - - if(cell) - - // draw power from cell as before - - var/cellused = min(cell.charge, CELLRATE * lastused_total) // clamp deduction to a max, amount left in cell - - cell.charge -= cellused - - - - // set channels depending on how much charge we have left - - - if(cell.charge <= 0) // zero charge, turn all off - equipment = autoset(equipment, 2) - lighting = autoset(lighting, 2) - environ = autoset(environ, 2) - else if(cell.percent() < 15) // <15%, turn off lighting & equipment - equipment = autoset(equipment, 2) - lighting = autoset(lighting, 2) - environ = autoset(environ, 1) - else if(cell.percent() < 30) // <30%, turn off equipment - equipment = autoset(equipment, 2) - lighting = autoset(lighting, 1) - environ = autoset(environ, 1) - else // otherwise all can be on - equipment = autoset(equipment, 1) - lighting = autoset(lighting, 1) - environ = autoset(environ, 1) - - - if(excess > 0 || perapc > lastused_total) // if power excess, or enough anyway, recharge the cell - // by the same amount just used - - cell.charge = min(cell.maxcharge, cell.charge + cellused) - - add_load(cellused/CELLRATE) // add the load used to recharge the cell - - - else // no excess, and not enough per-apc - - if( (cell.charge/CELLRATE+perapc) >= lastused_total) // can we draw enough from cell+grid to cover last usage? - - cell.charge = min(cell.maxcharge, cell.charge + CELLRATE * perapc) //recharge with what we can - - add_load(perapc) // so draw what we can from the grid - charging = 0 - - else // not enough! - charging = 0 // kill everything - chargecount = 0 - equipment = autoset(equipment, 0) - lighting = autoset(lighting, 0) - environ = autoset(environ, 0) - - - - // now trickle-charge the cell - - - if(chargemode && charging == 1) - if(excess > 0) // check to make sure we have enough to charge - - var/ch = min(CHARGELEVEL, (cell.maxcharge - cell.charge)/CELLRATE ) // clamp charging to max free in cell - - ch = min(ch, perapc) // clamp charging to our share - - add_load(CHARGELEVEL) - - cell.charge += ch * CELLRATE // actually recharge the cell - - else - - charging = 0 // stop charging - chargecount = 0 - - - - // show cell as fully charged if so - - if(cell.charge >= cell.maxcharge) - charging = 2 - - - if(chargemode) - if(!charging) - if(excess > CHARGELEVEL) - chargecount++ - else - chargecount = 0 - - - if(chargecount == 5) - - chargecount = 0 - charging = 1 - - else // chargemode off - charging = 0 - chargecount = 0 - - - - - else - // no cell - - // for now, switch everything off - - charging = 0 - chargecount = 0 - equipment = autoset(equipment, 0) - lighting = autoset(lighting, 0) - environ = autoset(environ, 0) - - - - // update icon & area power if anything changed - - - if(last_lt != lighting || last_eq != equipment || last_en != environ || last_ch != charging) - updateicon() - update() - - - - for(var/mob/M in viewers(1, src)) - if ((M.client && M.machine == src)) - src.interact(M) - - -// val 0=off, 1=off(auto) 2=on 3=on(auto) -// on 0=off, 1=on, 2=autooff - -/proc/autoset(var/val, var/on) - - if(on==0) - if(val==2) // if on, return off - return 0 - else if(val==3) // if auto-on, return auto-off - return 1 - - else if(on==1) - if(val==1) // if auto-off, return auto-on - return 3 - - else if(on==2) - if(val==3) // if auto-on, return auto-off - return 1 - - return val - -// damage and destruction acts - -/obj/machinery/power/apc/meteorhit(var/obj/O as obj) - - set_broken() - return - -/obj/machinery/power/apc/ex_act(severity) - - switch(severity) - if(1.0) - set_broken() - del(src) - return - if(2.0) - if (prob(50)) - set_broken() - if(3.0) - if (prob(25)) - set_broken() - else - return - -/obj/machinery/power/apc/blob_act() - if (prob(50)) - set_broken() - - -/obj/machinery/power/apc/proc/set_broken() - stat |= BROKEN - icon_state = "apc-b" - overlays = null - - operating = 0 - update() // the underfloor wiring terminal for the APC // autogenerated when an APC is placed // all conduit connects go to this object instead of the APC // using this solves the problem of having the APC in a wall yet also inside an area -/obj/machinery/power/terminal/New() - ..() - - var/turf/T = src.loc - - if(level==1) hide(T.intact) - - -/obj/machinery/power/terminal/hide(var/i) - - if(i) - invisibility = 101 - icon_state = "term-f" - else - invisibility = 0 - icon_state = "term" @@ -910,44 +282,6 @@ -// attach a wire to a power machine - leads from the turf you are standing on - -/obj/machinery/power/attackby(obj/item/weapon/W, mob/user) - - if(istype(W, /obj/item/weapon/cable_coil)) - - var/obj/item/weapon/cable_coil/coil = W - - var/turf/T = user.loc - - if(T.intact || !istype(T, /turf/station/floor)) - return - - if(get_dist(src, user) > 1) - return - - if(!directwired) // only for attaching to directwired machines - return - - var/dirn = get_dir(user, src) - - - for(var/obj/cable/LC in T) - if(LC.d1 == dirn || LC.d2 == dirn) - user << "There's already a cable at that position." - return - - var/obj/cable/NC = new(T) - NC.d1 = 0 - NC.d2 = dirn - NC.add_fingerprint() - NC.updateicon() - NC.update_network() - coil.use(1) - return - else - ..() - return // the power cable object @@ -1430,41 +764,6 @@ return res -/obj/machinery/power/proc/get_connections() - - if(!directwired) - return get_indirect_connections() - - var/list/res = list() - var/cdir - - for(var/turf/T in orange(1, src)) - - cdir = get_dir(T, src) - - for(var/obj/cable/C in T) - - if(C.netnum) - continue - - if(C.d1 == cdir || C.d2 == cdir) - res += C - - return res - -/obj/machinery/power/proc/get_indirect_connections() - - var/list/res = list() - - for(var/obj/cable/C in src.loc) - - if(C.netnum) - continue - - if(C.d1 == 0) - res += C - - return res /proc/powernet_nextlink(var/obj/O, var/num) @@ -1651,90 +950,6 @@ // the power monitoring computer // for the moment, just report the status of all APCs in the same powernet -/obj/machinery/power/monitor/attack_hand(mob/user) - add_fingerprint(user) - - if(stat & (BROKEN|NOPOWER)) - return - interact(user) - - -/obj/machinery/power/monitor/proc/interact(mob/user) - - if ( (get_dist(src, user) > 1 ) || (stat & (BROKEN|NOPOWER)) ) - user.machine = null - user << browse(null, "window=powcomp") - return - - - user.machine = src - var/t = "Power Monitoring
" - - - if(!powernet) - t += "\red No connection" - else - - var/list/L = list() - for(var/obj/machinery/power/terminal/term in powernet.nodes) - if(istype(term.master, /obj/machinery/power/apc)) - var/obj/machinery/power/apc/A = term.master - L += A - - t += "
Total power: [powernet.avail] W
Total load: [num2text(powernet.viewload,10)] W
" - - t += "" - - if(L.len > 0) - - t += "Area Eqp./Lgt./Env. Load Cell
" - - var/list/S = list(" Off","AOff"," On", " AOn") - var/list/chg = list("N","C","F") - - for(var/obj/machinery/power/apc/A in L) - - t += copytext(add_tspace(A.area.name, 30), 1, 30) - t += " [S[A.equipment+1]] [S[A.lighting+1]] [S[A.environ+1]] [add_lspace(A.lastused_total, 6)] [A.cell ? "[add_lspace(round(A.cell.percent()), 3)]% [chg[A.charging+1]]" : " N/C"]
" - - t += "
" - - t += "

Close
" - - user << browse(t, "window=powcomp;size=420x700") - - -/obj/machinery/power/monitor/Topic(href, href_list) - ..() - if( href_list["close"] ) - usr << browse(null, "window=powcomp") - usr.machine = null - return - -/obj/machinery/power/monitor/process() - if(!(stat & (NOPOWER|BROKEN)) ) - - use_power(250) - - - for(var/mob/M in viewers(1, src)) - if ((M.client && M.machine == src)) - src.interact(M) - - - -/obj/machinery/power/monitor/power_change() - - if(stat & BROKEN) - icon_state = "broken" - else - if( powered() ) - icon_state = initial(icon_state) - stat &= ~NOPOWER - else - spawn(rand(0, 15)) - src.icon_state = "c_unpowered" - stat |= NOPOWER // the SMES @@ -2016,321 +1231,6 @@ return -/obj/machinery/power/solar/New() - ..() - spawn(10) - updateicon() - updatefrac() - - if(powernet) - for(var/obj/machinery/power/solar_control/SC in powernet.nodes) - if(SC.id == id) - control = SC - -/obj/machinery/power/solar/proc/updateicon() - src.overlays = null - if(stat & BROKEN) - overlays += image('power.dmi', "solar_panel-b", FLY_LAYER) - else - overlays += image('power.dmi', "solar_panel", FLY_LAYER, adir) - -/obj/machinery/power/solar/proc/updatefrac() - - if(obscured) - sunfrac = 0 - return - - var/p_angle = dir2angle(adir) - sun.angle - - if(abs(p_angle) > 90) // if facing more than 90deg from sun, zero output - sunfrac = 0 - return - - sunfrac = cos(p_angle)*cos(p_angle) // - -#define SOLARGENRATE 1500 - -/obj/machinery/power/solar/process() - - if(stat & BROKEN) - return - - if(!obscured) - var/sgen = SOLARGENRATE * sunfrac - add_avail(sgen) - if(powernet && control) - if(control in powernet.nodes) - control.gen += sgen - - - if(adir == ndir) - turn_angle = 0 - else - spawn(rand(0,10)) - adir = turn(adir, turn_angle) - updateicon() - updatefrac() - -/obj/machinery/power/solar/proc/broken() - stat |= BROKEN - updateicon() - -/obj/machinery/power/solar/meteorhit() - - broken() - return - -/obj/machinery/power/solar/ex_act(severity) - - switch(severity) - if(1.0) - //SN src = null - del(src) - return - if(2.0) - if (prob(50)) - broken() - if(3.0) - if (prob(25)) - broken() - return - -/obj/machinery/power/solar/blob_act() - if (prob(50)) - broken() - src.density = 0 - - -/obj/machinery/power/solar_control/New() - ..() - - spawn(15) - - if(powernet) - for(var/obj/machinery/power/solar/S in powernet.nodes) - if(S.id == id) - cdir = S.adir - updateicon() - - - -/obj/machinery/power/solar_control/proc/updateicon() - if(stat & BROKEN) - icon_state = "broken" - overlays = null - return - if(stat & NOPOWER) - icon_state = "c_unpowered" - overlays = null - return - - icon_state = "solar_con" - overlays = null - if(cdir > 0) - overlays += image('enginecomputer.dmi', "solcon-o", FLY_LAYER, cdir) - - - -/obj/machinery/power/solar_control/attack_hand(mob/user) - - add_fingerprint(user) - - if(stat & (BROKEN | NOPOWER)) return - - interact(user) - -/obj/machinery/power/solar_control/process() - lastgen = gen - gen = 0 - - if(stat & (NOPOWER | BROKEN)) - return - - use_power(250) - - if(track && nexttime < world.timeofday) - if(trackdir) - cdir = turn(cdir, -45) - else - cdir = turn(cdir, 45) - set_panels(cdir) - - nexttime = world.timeofday + 10*trackrate - updateicon() - - - for(var/mob/M in viewers(1, src)) - if ((M.client && M.machine == src)) - src.interact(M) - - -/obj/machinery/power/solar_control/proc/interact(mob/user) - - if ( (get_dist(src, user) > 1 )) - user.machine = null - user << browse(null, "window=solcon") - return - - user.machine = src - - var/t = "Solar Generator Control
"
-
-	t += "Generated power : [round(lastgen)] W

" - - t += "Current panel orientation: [uppertext(dir2text(cdir))]
" - - t += "
Set orientation:
" - - var/list/D = list(-1, NORTHWEST, NORTH, NORTHEAST, -1, WEST, 0, EAST, -1, SOUTHWEST, SOUTH, SOUTHEAST) - var/list/disp = list("|", "|", "", "-", "/", "\\", "", "-", "\\", "/") - - for(var/d in D) - if(d == 0) - t += " " - continue - if(d == -1) - t += "
" - continue - - if(d==cdir) - t +=" [disp[d]]" - else - t +=" O" - - - t += "


" - - t += "Tracking: [ track ? "Off On" : "Off On"]" - - t += " [trackdir ? "CCW CW" : "CCW CW"]
" - - t += "Rate: - - - [trackrate] + + + (seconds per turn)
" - - t += "

Close" - - t += "
" - user << browse(t, "window=solcon") - - return - -/obj/machinery/power/solar_control/Topic(href, href_list) - ..() - - if (usr.stat || usr.restrained() ) - return - if ((!( istype(usr, /mob/human) ) && (!( ticker ) || (ticker && ticker.mode != "monkey")))) - usr << "\red You don't have the dexterity to do this!" - return - - //world << "[href] ; [href_list[href]]" - - if (( usr.machine==src && (get_dist(src, usr) <= 1 && istype(src.loc, /turf)))) - - - if( href_list["close"] ) - usr << browse(null, "window=solcon") - usr.machine = null - return - - else if( href_list["dir"] ) - cdir = text2num(href_list["dir"]) - - spawn(1) - set_panels(cdir) - - updateicon() - else if( href_list["tdir"] ) - trackdir = !trackdir - - else if( href_list["track"] ) - track = !track - nexttime = world.timeofday + 10*trackrate - - else if( href_list["trk"] ) - var/inc = text2num(href_list["trk"]) - - switch(inc) - if(1, -1) - trackrate += inc - if(2,-2) - trackrate += 10*inc/abs(inc) - if(3,-3) - trackrate += 100*inc/abs(inc) - - trackrate = min( max(trackrate, 10), 900) - nexttime = world.timeofday + 10*trackrate - - spawn(0) - for(var/mob/M in viewers(1, src)) - if ((M.client && M.machine == src)) - src.interact(M) - - else - usr << browse(null, "window=solcon") - usr.machine = null - - return - -/obj/machinery/power/solar_control/proc/set_panels(var/cdir) - if(powernet) - for(var/obj/machinery/power/solar/S in powernet.nodes) - if(S.id == id) - S.control = src - - var/delta = dir2angle(S.adir) - dir2angle(cdir) - - delta = (delta+360)%360 - - if(delta>180) - S.turn_angle = -45 - else - S.turn_angle = 45 - - S.ndir = cdir - - -/obj/machinery/power/solar_control/power_change() - - if( powered() ) - stat &= ~NOPOWER - updateicon() - else - spawn(rand(0, 15)) - stat |= NOPOWER - updateicon() - - - -/obj/machinery/power/solar_control/proc/broken() - stat |= BROKEN - updateicon() - -/obj/machinery/power/solar_control/meteorhit() - - broken() - return - -/obj/machinery/power/solar_control/ex_act(severity) - - switch(severity) - if(1.0) - //SN src = null - del(src) - return - if(2.0) - if (prob(50)) - broken() - if(3.0) - if (prob(25)) - broken() - return - -/obj/machinery/power/solar_control/blob_act() - if (prob(50)) - broken() - src.density = 0 - - // the inlet stage of the gas turbine electricity generator /obj/machinery/compressor/New() diff --git a/main-src/spacestation13.dme b/main-src/spacestation13.dme index dde1b25..24ac2dd 100644 --- a/main-src/spacestation13.dme +++ b/main-src/spacestation13.dme @@ -4,8 +4,8 @@ // BEGIN_INTERNALS /* -FILE: Code\newobjects.dm -DIR: Code Code\Machinery Code\Machinery\Atmoalter +FILE: Code\!atoms.dm +DIR: Code Code\Machinery\Atmoalter Code\Machinery\Power MAP_ICON_TYPE: 0 AUTO_FILE_DIR: ON */ @@ -15,6 +15,7 @@ AUTO_FILE_DIR: ON #define FILE_DIR "Code" #define FILE_DIR "Code/Machinery" #define FILE_DIR "Code/Machinery/Atmoalter" +#define FILE_DIR "Code/Machinery/Power" #define FILE_DIR "Icons" #define FILE_DIR "Interface" // END_FILE_DIR @@ -91,5 +92,11 @@ AUTO_FILE_DIR: ON #include "Code\Machinery\Atmoalter\canister.dm" #include "Code\Machinery\Atmoalter\heater.dm" #include "Code\Machinery\Atmoalter\siphs.dm" +#include "Code\Machinery\Power\_power.dm" +#include "Code\Machinery\Power\apc.dm" +#include "Code\Machinery\Power\monitor.dm" +#include "Code\Machinery\Power\solar.dm" +#include "Code\Machinery\Power\solar_control.dm" +#include "Code\Machinery\Power\terminal.dm" // END_INCLUDE