diff --git a/main-src/Code/!atoms.dm b/main-src/Code/!atoms.dm index 77697de..52e7c8e 100644 --- a/main-src/Code/!atoms.dm +++ b/main-src/Code/!atoms.dm @@ -2601,47 +2601,6 @@ Total SMES charging rate should not exceed total power generation rate, or an ov -/obj/machinery/power/generator - name = "generator" - desc = "A high efficiency thermoelectric generator." - icon_state = "teg" - anchored = 1 - density = 1 - - var/obj/machinery/circulator/circ1 - var/obj/machinery/circulator/circ2 - - var/c1on = 0 - var/c2on = 0 - var/c1rate = 10 - var/c2rate = 10 - var/lastgen = 0 - var/lastgenlev = -1 - - - -#define SMESMAXCHARGELEVEL 60000 -#define SMESMAXOUTPUT 60000 - -/obj/machinery/power/smes - name = "power storage unit" - desc = "A high-capacity superconducting magnetic energy storage (SMES) unit." - icon_state = "smes" - density = 1 - anchored = 1 - var/output = 30000 - var/lastout = 0 - var/loaddemand = 0 - var/capacity = 5e6 - var/charge = 1e6 - var/charging = 0 - var/chargemode = 0 - var/chargecount = 0 - var/chargelevel = 30000 - var/online = 1 - var/n_tag = null - var/obj/machinery/power/terminal/terminal = null - /obj/machinery/power/portable_gen @@ -2652,32 +2611,6 @@ Total SMES charging rate should not exceed total power generation rate, or an ov netnum = -1 directwired = 0 -/obj/machinery/compressor - name = "compressor" - desc = "The compressor stage of a gas turbine generator." - icon = 'pipes.dmi' - icon_state = "compressor" - anchored = 1 - density = 1 - var/obj/machinery/power/turbine/turbine - var/obj/substance/gas/gas - var/turf/inturf - var/starter = 0 - var/rpm = 0 - var/rpmtarget = 0 - var/capacity = 1e6 - -/obj/machinery/power/turbine - name = "gas turbine generator" - desc = "A gas turbine used to for backup power generation." - icon = 'pipes.dmi' - icon_state = "turbine" - anchored = 1 - density = 1 - var/obj/machinery/compressor/compressor - directwired = 1 - var/turf/outturf - var/lastgen diff --git a/main-src/Code/Machinery/Power/_power.dm b/main-src/Code/Machinery/Power/_power.dm index cbae6d3..be45412 100644 --- a/main-src/Code/Machinery/Power/_power.dm +++ b/main-src/Code/Machinery/Power/_power.dm @@ -77,7 +77,7 @@ obj/machinery/power var/list/res = list() var/cdir - for(var/turf/T in orange(1, src)) + for(var/turf/T in orange(1, src.loc)) cdir = get_dir(T, src) diff --git a/main-src/Code/Machinery/Power/generator.dm b/main-src/Code/Machinery/Power/generator.dm new file mode 100644 index 0000000..149d716 --- /dev/null +++ b/main-src/Code/Machinery/Power/generator.dm @@ -0,0 +1,258 @@ +/* + * Generator - The main power generation machine + * + * A generator makes power using the heat difference between the gas levels in two circulator machines. + * + */ + +#define GENRATE 0.0015 // generator output coefficient + +obj/machinery/power/generator + name = "generator" + desc = "A high efficiency thermoelectric generator." + icon_state = "teg" + anchored = 1 + density = 1 + + var + obj/machinery/circulator/circ1 // the cold gas circulator; must be to west of generator + obj/machinery/circulator/circ2 // the hot gas circulator; must be to east of generator + + c1on = 0 // true if circulator 1 is on + c2on = 0 // " " " 2 " " + c1rate = 10 // circulator 1's pumping rate (percentage) + c2rate = 10 // circulator 2's pumping rate (percentage) + lastgen = 0 // the power generated in the last cycle + lastgenlev = -1 // the last bargraph overlay level + + + // Create a generator, and locate the two circulators on either side + + New() + ..() + + spawn(5) + circ1 = locate(/obj/machinery/circulator) in get_step(src,WEST) + circ2 = locate(/obj/machinery/circulator) in get_step(src,EAST) + if(!circ1 || !circ2) + stat |= BROKEN + + updateicon() + + + // Update the icon overlays depending on status and power output level + + proc/updateicon() + + if(stat & (NOPOWER|BROKEN)) + overlays = null + else + overlays = null + + if(lastgenlev != 0) + overlays += image('power.dmi', "teg-op[lastgenlev]") + + overlays += image('power.dmi', "teg-oc[c1on][c2on]") + + + // Generate power, depending on the gas amounts and temperatures in the circulators + + // The device works as a semi-realistic heat engine; heat from the hot reservoir is converted to energy + // at a set efficiency (65% of Carnot). The waste heat is dumped into the cold reservoir + // This can only be sustained while the cold reservoir is cooler than the hot one. + + process() + + if(circ1 && circ2) // both circulators must be present + + + var/gc = circ1.gas2.shc() + var/gh = circ2.gas2.shc() + + var/tc = circ1.gas2.temperature + var/th = circ2.gas2.temperature + var/deltat = th-tc + + var/eta = (1-tc/th)*0.65 // efficiency 65% of Carnot + + if(gc > 0 && deltat >0) // require some cold gas (for sink) and a positive temp gradient + var/ghoc = gh/gc + + //var/qc = gc*tc + //var/qh = gh*th + + var/fdt = 1/( (1-eta)*ghoc + 1) // min timestep + + fdt = min(fdt, 0.1) // max timestep + + var/q = fdt*eta*gh*(deltat) // heat generated + + var/thp = th - fdt * deltat + var/tcp = tc + fdt * (1 - eta) * (ghoc) * deltat + + lastgen = q * GENRATE + add_avail(lastgen) + + circ1.ngas2.temperature = tcp + circ2.ngas2.temperature = thp + + else + lastgen = 0 + + + // update icon overlays only if displayed level has changed + + var/genlev = max(0, min( round(11*lastgen / 100000), 11)) + if(genlev != lastgenlev) + lastgenlev = genlev + updateicon() + + for(var/mob/M in viewers(1, src)) + if ((M.client && M.machine == src)) + src.interact(M) + + + // Attack with hand, open interaction window + + attack_hand(mob/user) + + add_fingerprint(user) + + if(stat & (BROKEN|NOPOWER)) return + + interact(user) + + + // Display interaction window + + proc/interact(mob/user) + + if ( (get_dist(src, user) > 1 )) + user.machine = null + user << browse(null, "window=teg") + return + + user.machine = src + + var/t = "
Thermo-Electric Generator" + user << browse(t, "window=teg;size=460x300") + return + + + // Handle topic links from 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=teg") + usr.machine = null + return + + else if( href_list["c1p"] ) + c1on = !c1on + circ1.control(c1on, c1rate) // used to control the circulator power and rate settings + updateicon() + else if( href_list["c2p"] ) + c2on = !c2on + circ2.control(c2on, c2rate) + updateicon() + + else if( href_list["c1r"] ) + + var/i = text2num(href_list["c1r"]) + + var/d = 0 + switch(i) + if(-3) + c1rate = 0 + if(3) + c1rate = 100 + + if(1) + d = 1 + if(-1) + d = -1 + if(2) + d = 10 + if(-2) + d = -10 + + c1rate += d + c1rate = max(1, min(100, c1rate)) // clamp to range + + circ1.control(c1on, c1rate) + updateicon() + + else if( href_list["c2r"] ) + + var/i = text2num(href_list["c2r"]) + + var/d = 0 + switch(i) + if(-3) + c2rate = 0 + if(3) + c2rate = 100 + + if(1) + d = 1 + if(-1) + d = -1 + if(2) + d = 10 + if(-2) + d = -10 + + c2rate += d + c2rate = max(1, min(100, c2rate)) // clamp to range + + circ2.control(c2on, c2rate) + updateicon() + + 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=teg") + usr.machine = null + + return + + + // When are power changes, perfrom default action and update icon overlays + + power_change() + ..() + updateicon() + diff --git a/main-src/Code/Machinery/Power/smes.dm b/main-src/Code/Machinery/Power/smes.dm new file mode 100644 index 0000000..63435c0 --- /dev/null +++ b/main-src/Code/Machinery/Power/smes.dm @@ -0,0 +1,338 @@ +/* + * SMES -- Superconduction magnetic energy storage unit + * + * A machine that stores power. + * + * SMESes have two "sides" where the are connected to the cable network: Input and output. + * The input side is connected via a terminal to the SMES; this is where the SMES draws power from to charge + * The output side is directly wired to the SMES object; this is where the SMES outputs stored power + * If the two sides are part of the same powernet, the SMES will still work correctly + * + * TODO: See note a proc/restore() below + */ + +#define SMESMAXCHARGELEVEL 60000 // This is the maximum rate at which you can charge the SMES + +#define SMESMAXOUTPUT 60000 // This is the maxmium output power of the SMES + +#define SMESRATE 0.05 // rate of internal charge to external power output + + +/obj/machinery/power/smes + name = "power storage unit" + desc = "A high-capacity superconducting magnetic energy storage (SMES) unit." + icon_state = "smes" + density = 1 + anchored = 1 + var + output = 30000 // the current power output level - limited to SMEXMAXOUTPUT + lastout = 0 // the output during the last cycle + loaddemand = 0 // the actual amount during the last cycle (may be lower if powernet load is lower) + capacity = 5e6 // the total maximum charge capacity + charge = 1e6 // the current charge level; default is 20% of maximum + charging = 0 // true if charging + chargemode = 0 // true if set to automatically charge + chargecount = 0 // count of number of times excess power has been availiable (used to control when charging is started) + chargelevel = 30000 // the amount of power to use to charge the SMES - limited to SMESMAXCHARGELEVEL + online = 1 // true if online (outputing power) + n_tag = null // a string nametag to display on the control panel (e.g. Main No. 1) + obj/machinery/power/terminal/terminal = null // the input terminal connected to this SMES + + + // Create a new SMES + // After waiting for the powernets to be built, look for a matching terminal in the 4 cardinal directions + // If found, store the terminal, otherwise mark the SMES as broken + + New() + ..() + + spawn(5) + dir_loop: + for(var/d in cardinal) + var/turf/T = get_step(src, d) + for(var/obj/machinery/power/terminal/term in T) + if(term && term.dir == turn(d, 180)) // terminal must have wires pointing towards the SMES + terminal = term + break dir_loop + + if(!terminal) + stat |= BROKEN + return + + terminal.master = src + + updateicon() + + + + // Updates the SMES icon to show overlays representing charging state, online state, and charge level + + proc/updateicon() + + overlays = null + if(stat & BROKEN) + return + + overlays += image('power.dmi', "smes-op[online]") + + if(charging) + overlays += image('power.dmi', "smes-oc1") + else + if(chargemode) + overlays += image('power.dmi', "smes-oc0") + + var/clevel = chargedisplay() + if(clevel>0) + overlays += image('power.dmi', "smes-og[clevel]") + + + // Returns the level (0-5) of the bargraph overlay (representing the charge level) to display + + proc/chargedisplay() + return round(5.5*charge/capacity) + + + + // Timed process; recharge the SMES if power is available, output power if online + + process() + if(stat & BROKEN) + return + + //store machine state to see if we need to update the icon overlays + var/last_disp = chargedisplay() + var/last_chrg = charging + var/last_onln = online + + if(terminal) + var/excess = terminal.surplus() + + if(charging) + if(excess >= 0) // if there's power available, try to charge + + var/load = min((capacity-charge)/SMESRATE, chargelevel) // charge at set rate, limited to spare capacity + + charge += load * SMESRATE // increase the charge + + add_load(load) // add the load to the terminal side network + + else // if not enough capcity + charging = 0 // stop charging + chargecount = 0 + + else + if(chargemode) + if(chargecount > rand(3,10)) // random count to switch to charging reduces thrashing + charging = 1 + chargecount = 0 + + if(excess > chargelevel) + chargecount++ + else + chargecount = 0 + else + chargecount = 0 + + if(online) // if outputting + lastout = min( charge/SMESRATE, output) //limit output to that stored + + charge -= lastout*SMESRATE // reduce the storage (may be recovered in /restore() if excessive) + + add_avail(lastout) // add output to powernet (smes side) + + if(charge < 0.0001) + online = 0 // stop output if charge falls to zero + + // only update icon if state changed + if(last_disp != chargedisplay() || last_chrg != charging || last_onln != online) + updateicon() + + for(var/mob/M in viewers(1, src)) + if ((M.client && M.machine == src)) + src.interact(M) + + + + // A special routine for SMES + // Called by the main game loop after all other power processes are finished + // SMESes make availabe a set amount of power per cycle, but should only have as much power drained + // as the actual load that cycle. This restore() proc restores the excess charge that wasn't really used. + + // TODO: Make the charge restoration more logical. Either need a priority setting, or some way to evenly + // share load between SMESes. + + proc/restore() + if(stat & BROKEN) + return + + if(!online) + loaddemand = 0 + return + + var/excess = powernet.netexcess // this was how much wasn't used on the network last ptick, minus any removed by other SMESes + + excess = min(lastout, excess) // clamp it to how much was actually output by this SMES last ptick + + excess = min((capacity-charge)/SMESRATE, excess) // for safety, also limit recharge by space capacity of SMES (shouldn't happen) + + // now recharge this amount + + var/clev = chargedisplay() + + charge += excess * SMESRATE + powernet.netexcess -= excess // remove the excess from the powernet, so later SMESes don't try to use it + + loaddemand = lastout-excess + + if(clev != chargedisplay() ) + updateicon() + + + // Add a load amount. Loading is done throught the terminal's powernet for SMESes + + add_load(var/amount) + if(terminal && terminal.powernet) + terminal.powernet.newload += amount + + + // Attack to open interaction window + + attack_hand(mob/user) + + add_fingerprint(user) + + if(stat & BROKEN) return + + interact(user) + + + // Display interaction window + + proc/interact(mob/user) + + if ( (get_dist(src, user) > 1 )) + user.machine = null + user << browse(null, "window=smes") + return + + user.machine = src + + + var/t = "SMES Power Storage Unit [n_tag? "([n_tag])" : null]
" + + t += "Output : [round(lastgen)] W
" + + t += "Cold loop
" + t += "Temperature Inlet: [round(circ1.ngas1.temperature, 0.1)] K Outlet: [round(circ1.ngas2.temperature, 0.1)] K
" + + t += "Circulator: [c1on ? "On Off" : "On Off "]
" + t += "Rate: M - - [add_lspace(c1rate,3)]% + + M
" + + t += "Hot loop
" + t += "Temperature Inlet: [round(circ2.ngas1.temperature, 0.1)] K Outlet: [round(circ2.ngas2.temperature, 0.1)] K
" + + t += "Circulator: [c2on ? "On Off" : "On Off "]
" + t += "Rate: M - - [add_lspace(c2rate,3)]% + + M
" + + t += "
Close" + + t += "
" + + t += "Stored capacity : [round(100.0*charge/capacity, 0.1)]%
" + + t += "Input: [charging ? "Charging" : "Not Charging"] [chargemode ? "Auto Off" : "Auto Off "]
" + + + t += "Input level: M - - - [add_lspace(chargelevel,5)] + + + M
" + + t += "
" + + t += "Output: [online ? "Online Offline" : "Online Offline "]
" + + t += "Output level: M - - - [add_lspace(output,5)] + + + M
" + + t += "Output load: [round(loaddemand)] W
" + + t += "
" + + var/gen = max(0, lastgen - (compressor.starter * COMPSTARTERLOAD) ) + t += "Generated power : [round(gen)] W
" + + t += "Turbine: [round(compressor.rpm)] RPM
" + + t += "Starter: [ compressor.starter ? "Off On" : "Off On"]
" + + //t += "Gas: [compressor.gas.tostring()]
" + + t += "
Thermo-Electric Generator" - user << browse(t, "window=teg;size=460x300") - return - -/obj/machinery/power/generator/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=teg") - usr.machine = null - return - - else if( href_list["c1p"] ) - c1on = !c1on - circ1.control(c1on, c1rate) - updateicon() - else if( href_list["c2p"] ) - c2on = !c2on - circ2.control(c2on, c2rate) - updateicon() - - else if( href_list["c1r"] ) - - var/i = text2num(href_list["c1r"]) - - var/d = 0 - switch(i) - if(-3) - c1rate = 0 - if(3) - c1rate = 100 - - if(1) - d = 1 - if(-1) - d = -1 - if(2) - d = 10 - if(-2) - d = -10 - - c1rate += d - c1rate = max(1, min(100, c1rate)) // clamp to range - - circ1.control(c1on, c1rate) - updateicon() - - else if( href_list["c2r"] ) - - var/i = text2num(href_list["c2r"]) - - var/d = 0 - switch(i) - if(-3) - c2rate = 0 - if(3) - c2rate = 100 - - if(1) - d = 1 - if(-1) - d = -1 - if(2) - d = 10 - if(-2) - d = -10 - - c2rate += d - c2rate = max(1, min(100, c2rate)) // clamp to range - - circ2.control(c2on, c2rate) - updateicon() - - 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=teg") - usr.machine = null - - return - -/obj/machinery/power/generator/power_change() - ..() - updateicon() - - - - - // the power cable object /obj/cable/New() @@ -947,455 +704,3 @@ -// the power monitoring computer -// for the moment, just report the status of all APCs in the same powernet - - - -// the SMES -// stores power - -/obj/machinery/power/smes/New() - ..() - - spawn(5) - dir_loop: - for(var/d in cardinal) - var/turf/T = get_step(src, d) - for(var/obj/machinery/power/terminal/term in T) - if(term && term.dir == turn(d, 180)) - terminal = term - break dir_loop - - if(!terminal) - stat |= BROKEN - return - - terminal.master = src - - updateicon() - - -/obj/machinery/power/smes/proc/updateicon() - - overlays = null - if(stat & BROKEN) - return - - - overlays += image('power.dmi', "smes-op[online]") - - if(charging) - overlays += image('power.dmi', "smes-oc1") - else - if(chargemode) - overlays += image('power.dmi', "smes-oc0") - - var/clevel = chargedisplay() - if(clevel>0) - overlays += image('power.dmi', "smes-og[clevel]") - -/obj/machinery/power/smes/proc/chargedisplay() - return round(5.5*charge/capacity) - -#define SMESRATE 0.05 // rate of internal charge to external power - - -/obj/machinery/power/smes/process() - - if(stat & BROKEN) - return - - - //store machine state to see if we need to update the icon overlays - var/last_disp = chargedisplay() - var/last_chrg = charging - var/last_onln = online - - if(terminal) - var/excess = terminal.surplus() - - if(charging) - if(excess >= 0) // if there's power available, try to charge - - var/load = min((capacity-charge)/SMESRATE, chargelevel) // charge at set rate, limited to spare capacity - - charge += load * SMESRATE // increase the charge - - add_load(load) // add the load to the terminal side network - - else // if not enough capcity - charging = 0 // stop charging - chargecount = 0 - - else - if(chargemode) - if(chargecount > rand(3,10)) - charging = 1 - chargecount = 0 - - if(excess > chargelevel) - chargecount++ - else - chargecount = 0 - else - chargecount = 0 - - if(online) // if outputting - lastout = min( charge/SMESRATE, output) //limit output to that stored - - charge -= lastout*SMESRATE // reduce the storage (may be recovered in /restore() if excessive) - - add_avail(lastout) // add output to powernet (smes side) - - if(charge < 0.0001) - online = 0 // stop output if charge falls to zero - - // only update icon if state changed - if(last_disp != chargedisplay() || last_chrg != charging || last_onln != online) - updateicon() - - for(var/mob/M in viewers(1, src)) - if ((M.client && M.machine == src)) - src.interact(M) - - -// called after all power processes are finished -// restores charge level to smes if there was excess this ptick - -/obj/machinery/power/smes/proc/restore() - if(stat & BROKEN) - return - - if(!online) - loaddemand = 0 - return - - var/excess = powernet.netexcess // this was how much wasn't used on the network last ptick, minus any removed by other SMESes - - excess = min(lastout, excess) // clamp it to how much was actually output by this SMES last ptick - - excess = min((capacity-charge)/SMESRATE, excess) // for safety, also limit recharge by space capacity of SMES (shouldn't happen) - - // now recharge this amount - - var/clev = chargedisplay() - - charge += excess * SMESRATE - powernet.netexcess -= excess // remove the excess from the powernet, so later SMESes don't try to use it - - loaddemand = lastout-excess - - if(clev != chargedisplay() ) - updateicon() - - -/obj/machinery/power/smes/add_load(var/amount) - if(terminal && terminal.powernet) - terminal.powernet.newload += amount - -/obj/machinery/power/smes/attack_hand(mob/user) - - add_fingerprint(user) - - if(stat & BROKEN) return - - interact(user) - - - -/obj/machinery/power/smes/proc/interact(mob/user) - - if ( (get_dist(src, user) > 1 )) - user.machine = null - user << browse(null, "window=smes") - return - - user.machine = src - - - var/t = "SMES Power Storage Unit [n_tag? "([n_tag])" : null]
" - - t += "Output : [round(lastgen)] W
" - - t += "Cold loop
" - t += "Temperature Inlet: [round(circ1.ngas1.temperature, 0.1)] K Outlet: [round(circ1.ngas2.temperature, 0.1)] K
" - - t += "Circulator: [c1on ? "On Off" : "On Off "]
" - t += "Rate: M - - [add_lspace(c1rate,3)]% + + M
" - - t += "Hot loop
" - t += "Temperature Inlet: [round(circ2.ngas1.temperature, 0.1)] K Outlet: [round(circ2.ngas2.temperature, 0.1)] K
" - - t += "Circulator: [c2on ? "On Off" : "On Off "]
" - t += "Rate: M - - [add_lspace(c2rate,3)]% + + M
" - - t += "
Close" - - t += "
" - - t += "Stored capacity : [round(100.0*charge/capacity, 0.1)]%
" - - t += "Input: [charging ? "Charging" : "Not Charging"] [chargemode ? "Auto Off" : "Auto Off "]
" - - - t += "Input level: M - - - [add_lspace(chargelevel,5)] + + + M
" - - t += "
" - - t += "Output: [online ? "Online Offline" : "Online Offline "]
" - - t += "Output level: M - - - [add_lspace(output,5)] + + + M
" - - t += "Output load: [round(loaddemand)] W
" - - t += "
" - - var/gen = max(0, lastgen - (compressor.starter * COMPSTARTERLOAD) ) - t += "Generated power : [round(gen)] W
" - - t += "Turbine: [round(compressor.rpm)] RPM
" - - t += "Starter: [ compressor.starter ? "Off On" : "Off On"]
" - - //t += "Gas: [compressor.gas.tostring()]
" - - t += "