diff --git a/code/modules/overmap/disperser/disperser.dm b/code/modules/overmap/disperser/disperser.dm new file mode 100644 index 0000000000..b51c0fb709 --- /dev/null +++ b/code/modules/overmap/disperser/disperser.dm @@ -0,0 +1,65 @@ +//Most interesting stuff happens in disperser_fire.dm +//This is just basic construction and deconstruction and the like + +/obj/machinery/disperser + name = "abstract parent for disperser" + desc = "You should never see one of these, bap your mappers." + icon = 'icons/obj/disperser.dmi' + idle_power_usage = 200 + density = TRUE + anchored = TRUE + +/obj/machinery/disperser/Initialize() + . = ..() + // TODO - Remove this bit once machines are converted to Initialize + if(ispath(circuit)) + circuit = new circuit(src) + default_apply_parts() + +/obj/machinery/disperser/examine(mob/user) + . = ..() + if(panel_open) + to_chat(user, "The maintenance panel is open.") + +/obj/machinery/disperser/attackby(obj/item/I, mob/user) + if(I && I.is_wrench()) + if(panel_open) + user.visible_message("\The [user] rotates \the [src] with \the [I].", + "You rotate \the [src] with \the [I].") + set_dir(turn(dir, 90)) + playsound(src, 'sound/items/jaws_pry.ogg', 50, 1) + else + to_chat(user, "The maintenance panel must be screwed open for this!") + return + if(default_deconstruction_screwdriver(user, I)) + return + if(default_deconstruction_crowbar(user, I)) + return + if(default_part_replacement(user, I)) + return + return ..() + +/obj/machinery/disperser/front + name = "obstruction removal ballista beam generator" + desc = "A complex machine which shoots concentrated material beams.\ +
A sign on it reads: STAY CLEAR! DO NOT BLOCK!" + icon_state = "front" + circuit = /obj/item/weapon/circuitboard/disperserfront + +/obj/machinery/disperser/middle + name = "obstruction removal ballista fusor" + desc = "A complex machine which transmits immense amount of data \ + from the material deconstructor to the particle beam generator.\ +
A sign on it reads: EXPLOSIVE! DO NOT OVERHEAT!" + icon_state = "middle" + circuit = /obj/item/weapon/circuitboard/dispersermiddle + // maximum_component_parts = list(/obj/item/weapon/stock_parts = 15) + +/obj/machinery/disperser/back + name = "obstruction removal ballista material deconstructor" + desc = "A prototype machine which can deconstruct materials atom by atom.\ +
A sign on it reads: KEEP AWAY FROM LIVING MATERIAL!" + icon_state = "back" + circuit = /obj/item/weapon/circuitboard/disperserback + density = FALSE + layer = UNDER_JUNK_LAYER //So the charges go above us. diff --git a/code/modules/overmap/disperser/disperser_charge.dm b/code/modules/overmap/disperser/disperser_charge.dm new file mode 100644 index 0000000000..22026086d5 --- /dev/null +++ b/code/modules/overmap/disperser/disperser_charge.dm @@ -0,0 +1,51 @@ +/obj/structure/ship_munition/disperser_charge + name = "unknown disperser charge" + desc = "A charge to power the obstruction removal ballista with. It looks impossibly round and shiny. This charge does not have a defined purpose." + icon = 'icons/obj/munitions.dmi' + icon_state = "slug" + w_class = ITEMSIZE_HUGE + density = TRUE + // atom_flags = ATOM_FLAG_NO_TEMP_CHANGE | ATOM_FLAG_CLIMBABLE + var/chargetype + var/chargedesc + var/static/list/move_sounds = list( // some nasty sounds to make when moving the board + 'sound/effects/metalscrape1.ogg', + 'sound/effects/metalscrape2.ogg', + 'sound/effects/metalscrape3.ogg' + ) + +// make a screeching noise to drive people mad +/obj/structure/ship_munition/disperser_charge/Move(atom/newloc, direct = 0) + if((. = ..()) && prob(50)) + var/turf/T = get_turf(src) + if(!isspace(T) && !istype(T, /turf/simulated/floor/carpet)) + playsound(T, pick(move_sounds), 50, 1) + + +/obj/structure/ship_munition/disperser_charge/fire + name = "FR1-ENFER charge" + color = "#b95a00" + desc = "A charge to power the obstruction removal ballista with. It looks impossibly round and shiny. This charge is designed to release a localised fire on impact." + chargetype = OVERMAP_WEAKNESS_FIRE + chargedesc = "ENFER" + +/obj/structure/ship_munition/disperser_charge/emp + name = "EM2-QUASAR charge" + color = "#6a97b0" + desc = "A charge to power the obstruction removal ballista with. It looks impossibly round and shiny. This charge is designed to release a blast of electromagnetic pulse on impact." + chargetype = OVERMAP_WEAKNESS_EMP + chargedesc = "QUASAR" + +/obj/structure/ship_munition/disperser_charge/mining + name = "MN3-BERGBAU charge" + color = "#cfcf55" + desc = "A charge to power the obstruction removal ballista with. It looks impossibly round and shiny. This charge is designed to mine ores on impact." + chargetype = OVERMAP_WEAKNESS_MINING + chargedesc = "BERGBAU" + +/obj/structure/ship_munition/disperser_charge/explosive + name = "XP4-INDARRA charge" + color = "#aa5f61" + desc = "A charge to power the obstruction removal ballista with. It looks impossibly round and shiny. This charge is designed to explode on impact." + chargetype = OVERMAP_WEAKNESS_EXPLOSIVE + chargedesc = "INDARRA" diff --git a/code/modules/overmap/disperser/disperser_circuit.dm b/code/modules/overmap/disperser/disperser_circuit.dm new file mode 100644 index 0000000000..7ecf4ebf1b --- /dev/null +++ b/code/modules/overmap/disperser/disperser_circuit.dm @@ -0,0 +1,35 @@ +#ifndef T_BOARD +#error T_BOARD macro is not defined but we need it! +#endif + +/obj/item/weapon/circuitboard/disperser + name = T_BOARD("obstruction removal ballista control") + build_path = /obj/machinery/computer/ship/disperser + origin_tech = list(TECH_ENGINEERING = 2, TECH_COMBAT = 2, TECH_BLUESPACE = 2) + +/obj/item/weapon/circuitboard/disperserfront + name = T_BOARD("obstruction removal ballista beam generator") + build_path = /obj/machinery/disperser/front + board_type = new /datum/frame/frame_types/machine + origin_tech = list(TECH_ENGINEERING = 2, TECH_COMBAT = 2, TECH_BLUESPACE = 2) + req_components = list ( + /obj/item/weapon/stock_parts/manipulator/pico = 5 + ) + +/obj/item/weapon/circuitboard/dispersermiddle + name = T_BOARD("obstruction removal ballista fusor") + build_path = /obj/machinery/disperser/middle + board_type = new /datum/frame/frame_types/machine + origin_tech = list(TECH_ENGINEERING = 2, TECH_COMBAT = 2, TECH_BLUESPACE = 2) + req_components = list ( + /obj/item/weapon/stock_parts/subspace/crystal = 10 + ) + +/obj/item/weapon/circuitboard/disperserback + name = T_BOARD("obstruction removal ballista material deconstructor") + build_path = /obj/machinery/disperser/back + board_type = new /datum/frame/frame_types/machine + origin_tech = list(TECH_ENGINEERING = 2, TECH_COMBAT = 2, TECH_BLUESPACE = 2) + req_components = list ( + /obj/item/weapon/stock_parts/capacitor/super = 5 + ) diff --git a/code/modules/overmap/disperser/disperser_console.dm b/code/modules/overmap/disperser/disperser_console.dm new file mode 100644 index 0000000000..0128f347c9 --- /dev/null +++ b/code/modules/overmap/disperser/disperser_console.dm @@ -0,0 +1,192 @@ +//Amazing disperser from Bxil(tm). Some icons, sounds, and some code shamelessly stolen from ParadiseSS13. + +/obj/machinery/computer/ship/disperser + name = "obstruction removal ballista control" + icon = 'icons/obj/computer.dmi' + icon_state = "computer" + circuit = /obj/item/weapon/circuitboard/disperser + + core_skill = /datum/skill/pilot + var/skill_offset = SKILL_ADEPT - 1 //After which skill level it starts to matter. -1, because we have to index from zero + + icon_keyboard = "rd_key" + icon_screen = "teleport" + + var/obj/machinery/disperser/front/front + var/obj/machinery/disperser/middle/middle + var/obj/machinery/disperser/back/back + var/const/link_range = 16 //How far can the above stuff be maximum before we start complaining + + var/overmapdir = 0 + + var/caldigit = 4 //number of digits that needs calibration + var/list/calibration //what it is + var/list/calexpected //what is should be + + var/range = 1 //range of the explosion + var/strength = 1 //strength of the explosion + var/next_shot = 0 //round time where the next shot can start from + var/const/coolinterval = 2 MINUTES //time to wait between safe shots in deciseconds + +/obj/machinery/computer/ship/disperser/Initialize() + . = ..() + link_parts() + reset_calibration() + +/obj/machinery/computer/ship/disperser/Destroy() + release_links() + . = ..() + +/obj/machinery/computer/ship/disperser/proc/link_parts() + if(is_valid_setup()) + return TRUE + + for(var/obj/machinery/disperser/front/F in global.machines) + if(get_dist(src, F) >= link_range) + continue + var/backwards = turn(F.dir, 180) + var/obj/machinery/disperser/middle/M = locate() in get_step(F, backwards) + if(!M || get_dist(src, M) >= link_range) + continue + var/obj/machinery/disperser/back/B = locate() in get_step(M, backwards) + if(!B || get_dist(src, B) >= link_range) + continue + front = F + middle = M + back = B + if(is_valid_setup()) + GLOB.destroyed_event.register(F, src, .proc/release_links) + GLOB.destroyed_event.register(M, src, .proc/release_links) + GLOB.destroyed_event.register(B, src, .proc/release_links) + return TRUE + return FALSE + +obj/machinery/computer/ship/disperser/proc/is_valid_setup() + if(front && middle && back) + var/everything_in_range = (get_dist(src, front) < link_range) && (get_dist(src, middle) < link_range) && (get_dist(src, back) < link_range) + var/everything_in_order = (middle.Adjacent(front) && middle.Adjacent(back)) && (front.dir == middle.dir && middle.dir == back.dir) + return everything_in_order && everything_in_range + return FALSE + +/obj/machinery/computer/ship/disperser/proc/release_links() + GLOB.destroyed_event.unregister(front, src, .proc/release_links) + GLOB.destroyed_event.unregister(middle, src, .proc/release_links) + GLOB.destroyed_event.unregister(back, src, .proc/release_links) + front = null + middle = null + back = null + +/obj/machinery/computer/ship/disperser/proc/get_calibration() + var/list/calresult[caldigit] + for(var/i = 1 to caldigit) + if(calibration[i] == calexpected[i]) + calresult[i] = 2 + else if(calibration[i] in calexpected) + calresult[i] = 1 + else + calresult[i] = 0 + return calresult + +/obj/machinery/computer/ship/disperser/proc/reset_calibration() + calexpected = new /list(caldigit) + calibration = new /list(caldigit) + for(var/i = 1 to caldigit) + calexpected[i] = rand(0,9) + calibration[i] = 0 + +/obj/machinery/computer/ship/disperser/proc/cal_accuracy() + var/top = 0 + var/divisor = caldigit * 2 //maximum possible value, aka 100% accuracy + for(var/i in get_calibration()) + top += i + return round(top * 100 / divisor) + +/obj/machinery/computer/ship/disperser/proc/get_next_shot_seconds() + return max(0, (next_shot - world.time) / 10) + +/obj/machinery/computer/ship/disperser/proc/cool_failchance() + return get_next_shot_seconds() * 1000 / coolinterval + +/obj/machinery/computer/ship/disperser/proc/get_charge_type() + var/obj/structure/ship_munition/disperser_charge/B = locate() in get_turf(back) + if(B) + return B.chargetype + return OVERMAP_WEAKNESS_NONE + +/obj/machinery/computer/ship/disperser/proc/get_charge() + var/obj/structure/ship_munition/disperser_charge/B = locate() in get_turf(back) + if(B) + return B + +/obj/machinery/computer/ship/disperser/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = TRUE) + if(!linked) + display_reconnect_dialog(user, "disperser synchronization") + return + + var/data[0] + + if (!link_parts()) + data["faillink"] = TRUE + else + data["calibration"] = calibration + data["overmapdir"] = overmapdir + data["cal_accuracy"] = cal_accuracy() + data["strength"] = strength + data["range"] = range + data["next_shot"] = round(get_next_shot_seconds()) + data["nopower"] = !data["faillink"] && (!front.powered() || !middle.powered() || !back.powered()) + data["skill"] = user.get_skill_value(core_skill) > skill_offset + + var/charge = "UNKNOWN ERROR" + if(get_charge_type() == OVERMAP_WEAKNESS_NONE) + charge = "ERROR: No valid charge detected." + else + var/obj/structure/ship_munition/disperser_charge/B = get_charge() + charge = B.chargedesc + data["chargeload"] = charge + + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open) + if (!ui) + ui = new(user, src, ui_key, "disperser.tmpl", "[linked.name] ORB control", 400, 550) + ui.set_initial_data(data) + ui.open() + ui.set_auto_update(1) + +/obj/machinery/computer/ship/disperser/OnTopic(mob/user, list/href_list, state) + . = ..() + if(.) + return + + if(!linked) + return TOPIC_HANDLED + + if (href_list["choose"]) + overmapdir = sanitize_integer(text2num(href_list["choose"]), 0, 9, 0) + reset_calibration() + + if(href_list["calibration"]) + var/input = input("0-9", "disperser calibration", 0) as num|null + if(!isnull(input)) //can be zero so we explicitly check for null + var/calnum = sanitize_integer(text2num(href_list["calibration"]), 0, caldigit)//sanitiiiiize + calibration[calnum + 1] = sanitize_integer(input, 0, 9, 0)//must add 1 because nanoui indexes from 0 + + if(href_list["skill_calibration"]) + for(var/i = 1 to min(caldigit, user.get_skill_value(core_skill) - skill_offset)) + calibration[i] = calexpected[i] + + if(href_list["strength"]) + var/input = input("1-5", "disperser strength", 1) as num|null + if(input && CanInteract(user, state)) + strength = sanitize_integer(input, 1, 5, 1) + middle.idle_power_usage = strength * range * 100 + + if(href_list["range"]) + var/input = input("1-5", "disperser radius", 1) as num|null + if(input && CanInteract(user, state)) + range = sanitize_integer(input, 1, 5, 1) + middle.idle_power_usage = strength * range * 100 + + if(href_list["fire"]) + fire(user) + + return TOPIC_REFRESH diff --git a/code/modules/overmap/disperser/disperser_fire.dm b/code/modules/overmap/disperser/disperser_fire.dm new file mode 100644 index 0000000000..421c9b4a20 --- /dev/null +++ b/code/modules/overmap/disperser/disperser_fire.dm @@ -0,0 +1,100 @@ +/obj/machinery/computer/ship/disperser/proc/fire(mob/user) + log_and_message_admins("attempted to launch a disperser beam.") + if(!link_parts()) + return FALSE //no disperser, no service + if(!front.powered() || !middle.powered() || !back.powered()) + return FALSE //no power, no boom boom + var/chargetype = get_charge_type() + if(chargetype <= 0) + return FALSE //no dear, you cannot fire the captain out of a cannon... unless you put him in a box of course + + var/atom/movable/atomcharge = get_charge() + + var/turf/start = front + var/direction = front.dir + + var/distance = 0 + for(var/turf/T in getline(get_step(front,front.dir), get_target_turf(start, direction))) + distance++ + if(T.density) + if(distance < 7) + explosion(T,1,2,3) + continue + else + T.ex_act(1) + for(var/atom/A in T) + if(A.density) + if(distance < 7) + explosion(A,1,2,3) + break + else + A.ex_act(1) + + var/list/relevant_z = GetConnectedZlevels(start.z) + for(var/mob/M in global.player_list) + var/turf/T = get_turf(M) + if(!T || !(T.z in relevant_z)) + continue + shake_camera(M, 25) + if(!isdeaf(M)) + M << sound('sound/effects/explosionfar.ogg', volume=10) + + if(front) //Meanwhile front might have exploded + front.layer = ABOVE_JUNK_LAYER //So the beam goes below us. Looks a lot better + playsound(start, 'sound/machines/disperser_fire.ogg', 100, 1) + handle_beam(start, direction) + handle_overbeam() + qdel(atomcharge) + + //Some moron disregarded the cooldown warning. Let's blow in their face. + if(prob(cool_failchance())) + explosion(middle,rand(1,2),rand(2,3),rand(3,4)) + next_shot = coolinterval + world.time + + //Success, but we missed. + if(prob(100 - cal_accuracy())) + return TRUE + + reset_calibration() + + var/list/candidates = list() + + for(var/obj/effect/overmap/event/O in get_step(linked, overmapdir)) + candidates += O + + //Way to waste a charge + if(!length(candidates)) + return TRUE + + var/obj/effect/overmap/event/finaltarget = pick(candidates) + log_and_message_admins("A type [chargetype] disperser beam was launched at [finaltarget].", location=finaltarget) + + fire_at_event(finaltarget, chargetype) + return TRUE + +/obj/machinery/computer/ship/disperser/proc/fire_at_event(obj/effect/overmap/event/finaltarget, chargetype) + if(chargetype & finaltarget.weaknesses) + var/turf/T = finaltarget.loc + qdel(finaltarget) + GLOB.overmap_event_handler.update_hazards(T) + +/obj/machinery/computer/ship/disperser/proc/handle_beam(turf/start, direction) + set waitfor = FALSE + start.Beam(get_target_turf(start, direction), "bsa_beam", time = 50, maxdistance = world.maxx) + if(front) + front.layer = initial(front.layer) + +/obj/machinery/computer/ship/disperser/proc/handle_overbeam() + set waitfor = FALSE + linked.Beam(get_step(linked, overmapdir), "bsa_beam", time = 150, maxdistance = world.maxx) + +/obj/machinery/computer/ship/disperser/proc/get_target_turf(turf/start, direction) + switch(direction) + if(NORTH) + return locate(start.x,world.maxy,start.z) + if(SOUTH) + return locate(start.x,1,start.z) + if(WEST) + return locate(1,start.y,start.z) + if(EAST) + return locate(world.maxx,start.y,start.z) \ No newline at end of file diff --git a/icons/obj/disperser.dmi b/icons/obj/disperser.dmi new file mode 100644 index 0000000000..12f4e81776 Binary files /dev/null and b/icons/obj/disperser.dmi differ diff --git a/icons/obj/munitions.dmi b/icons/obj/munitions.dmi new file mode 100644 index 0000000000..4631b35ba4 Binary files /dev/null and b/icons/obj/munitions.dmi differ diff --git a/nano/templates/disperser.tmpl b/nano/templates/disperser.tmpl new file mode 100644 index 0000000000..08db5ed2ba --- /dev/null +++ b/nano/templates/disperser.tmpl @@ -0,0 +1,91 @@ +{{if data.faillink}} +
+ ERROR: Machine is incomplete, out of range, or misaligned! +
+{{else}} +
+

Targeting

+
+
+
+ {{:helper.link('', 'triangle-1-nw', { 'choose' : 9 }, data.overmapdir == 9 ? 'selected' : null, null)}} + {{:helper.link('', 'triangle-1-n', { 'choose' : 1 }, data.overmapdir == 1 ? 'selected' : null, null)}} + {{:helper.link('', 'triangle-1-ne', { 'choose' : 5 }, data.overmapdir == 5 ? 'selected' : null, null)}} +
+
+ {{:helper.link('', 'triangle-1-w', { 'choose' : 8 }, data.overmapdir == 8 ? 'selected' : null, null)}} + {{:helper.link('', 'circle-close', { 'choose' : 0 }, data.overmapdir == 0 ? 'selected' : null, null)}} + {{:helper.link('', 'triangle-1-e', { 'choose' : 4 }, data.overmapdir == 4 ? 'selected' : null, null)}} +
+
+ {{:helper.link('', 'triangle-1-sw', { 'choose' : 10 }, data.overmapdir == 10 ? 'selected' : null, null)}} + {{:helper.link('', 'triangle-1-s', { 'choose' : 2 }, data.overmapdir == 2 ? 'selected' : null, null)}} + {{:helper.link('', 'triangle-1-se', { 'choose' : 6 }, data.overmapdir == 6 ? 'selected' : null, null)}} +
+
+
+
+
+

Charge

+
+ {{if data.nopower}} + At least one part of the machine is unpowered. + {{/if}} +
+ Charge Load Type +
+
+ {{:data.chargeload}} +
+
+ Cooldown +
+
+ {{if data.next_shot == 0}} + Ready + {{else}} + {{:data.next_shot}} seconds +
Warning: Do not fire during cooldown. + {{/if}} +
+
+
+
+
+

Calibration

+
+
+ {{:data.cal_accuracy}}% +
+
+ {{:helper.link('Pre-Calibration', 'transfer-e-w', { 'skill_calibration' : 1 }, data.skill ? null : 'disabled', null)}} +

+ {{for data.calibration}} +
+ {{:helper.link(value, 'shuffle', { 'calibration' : index }, null, null)}} +
+
+ {{/for}} +
+
+
+

Setup

+
+
+ Strength +
+
+ {{:helper.link(data.strength, 'lightbulb', { 'strength' : 1 }, null, null)}} +
+
+ Radius +
+
+ {{:helper.link(data.range, 'arrow-4-diag', { 'range' : 1 }, null, null)}} +
+
+
+
+{{:helper.link("Fire", 'alert', { 'fire' : 1 }, null, null)}} +
+{{/if}} \ No newline at end of file diff --git a/sound/effects/metalscrape1.ogg b/sound/effects/metalscrape1.ogg new file mode 100644 index 0000000000..77cd19d93c Binary files /dev/null and b/sound/effects/metalscrape1.ogg differ diff --git a/sound/effects/metalscrape2.ogg b/sound/effects/metalscrape2.ogg new file mode 100644 index 0000000000..39da935a92 Binary files /dev/null and b/sound/effects/metalscrape2.ogg differ diff --git a/sound/effects/metalscrape3.ogg b/sound/effects/metalscrape3.ogg new file mode 100644 index 0000000000..219b0cee30 Binary files /dev/null and b/sound/effects/metalscrape3.ogg differ diff --git a/sound/machines/disperser_fire.ogg b/sound/machines/disperser_fire.ogg new file mode 100644 index 0000000000..19244ff69d Binary files /dev/null and b/sound/machines/disperser_fire.ogg differ diff --git a/vorestation.dme b/vorestation.dme index 44c049f82b..7104696b6a 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -2912,6 +2912,11 @@ #include "code\modules\overmap\sectors.dm" #include "code\modules\overmap\spacetravel.dm" #include "code\modules\overmap\turfs.dm" +#include "code\modules\overmap\disperser\disperser.dm" +#include "code\modules\overmap\disperser\disperser_charge.dm" +#include "code\modules\overmap\disperser\disperser_circuit.dm" +#include "code\modules\overmap\disperser\disperser_console.dm" +#include "code\modules\overmap\disperser\disperser_fire.dm" #include "code\modules\overmap\events\event_handler.dm" #include "code\modules\overmap\events\generation.dm" #include "code\modules\overmap\events\overmap_event.dm"