diff --git a/code/__defines/color.dm b/code/__defines/color.dm index d439958d76..c1416a5e92 100644 --- a/code/__defines/color.dm +++ b/code/__defines/color.dm @@ -17,3 +17,4 @@ #define COLOR_ASSEMBLY_HOT_PINK "#FF69B4" #define COLOR_ASTEROID_ROCK "#735555" +#define COLOR_GOLD "#ffcc33" diff --git a/code/__defines/misc.dm b/code/__defines/misc.dm index c63b1fb54d..a555c5ac33 100644 --- a/code/__defines/misc.dm +++ b/code/__defines/misc.dm @@ -129,6 +129,13 @@ //Area flags, possibly more to come #define RAD_SHIELDED 1 //shielded from radiation, clearly +// OnTopic return values +#define TOPIC_NOACTION 0 +#define TOPIC_HANDLED 1 +#define TOPIC_REFRESH 2 +#define TOPIC_UPDATE_PREVIEW 4 +#define TOPIC_REFRESH_UPDATE_PREVIEW (TOPIC_REFRESH|TOPIC_UPDATE_PREVIEW) + // Convoluted setup so defines can be supplied by Bay12 main server compile script. // Should still work fine for people jamming the icons into their repo. #ifndef CUSTOM_ITEM_OBJ diff --git a/code/_global_vars/lists/misc.dm b/code/_global_vars/lists/misc.dm index aa9680a5f6..c10fcba530 100644 --- a/code/_global_vars/lists/misc.dm +++ b/code/_global_vars/lists/misc.dm @@ -1 +1,2 @@ GLOBAL_LIST_INIT(speech_toppings, list("|" = "i", "+" = "b", "_" = "u")) +GLOBAL_LIST_EMPTY(meteor_list) diff --git a/code/game/gamemodes/meteor/meteors.dm b/code/game/gamemodes/meteor/meteors.dm index 0ecf3904d8..c49a4d693b 100644 --- a/code/game/gamemodes/meteor/meteors.dm +++ b/code/game/gamemodes/meteor/meteors.dm @@ -126,10 +126,10 @@ // Multiply this and the hits var to get a rough idea of how penetrating a meteor is. var/wall_power = 100 -/obj/effect/meteor/New() - ..() +/obj/effect/meteor/Initialize() + . = ..() z_original = z - + GLOB.meteor_list += src /obj/effect/meteor/Move() if(z != z_original || loc == dest) @@ -149,6 +149,7 @@ /obj/effect/meteor/Destroy() walk(src,0) //this cancels the walk_towards() proc + GLOB.meteor_list -= src return ..() /obj/effect/meteor/New() diff --git a/code/game/machinery/machinery.dm b/code/game/machinery/machinery.dm index ecbf2fa911..9f3132207f 100644 --- a/code/game/machinery/machinery.dm +++ b/code/game/machinery/machinery.dm @@ -123,7 +123,7 @@ Class Procs: ..(l) if(d) set_dir(d) - if(circuit) + if(ispath(circuit)) circuit = new circuit(src) /obj/machinery/Initialize() diff --git a/code/game/machinery/pointdefense.dm b/code/game/machinery/pointdefense.dm new file mode 100644 index 0000000000..97bde089cd --- /dev/null +++ b/code/game/machinery/pointdefense.dm @@ -0,0 +1,341 @@ +// +// Control computer for point defense batteries. +// Handles control UI, but also coordinates their fire to avoid overkill. +// + +GLOBAL_LIST_BOILERPLATE(pointdefense_controllers, /obj/machinery/pointdefense_control) +GLOBAL_LIST_BOILERPLATE(pointdefense_turrets, /obj/machinery/pointdefense) + +/obj/machinery/pointdefense_control + name = "fire assist mainframe" + desc = "A specialized computer designed to synchronize a variety of weapon systems and a vessel's astronav data." + icon = 'icons/obj/pointdefense.dmi' + icon_state = "control" + density = TRUE + anchored = TRUE + circuit = /obj/item/weapon/circuitboard/pointdefense_control + var/list/targets = list() // Targets being engaged by associated batteries + var/ui_template = "pointdefense_control.tmpl" + var/id_tag = null + +/obj/machinery/pointdefense_control/Initialize(mapload) + . = ..() + if(id_tag) + //No more than 1 controller please. + for(var/thing in pointdefense_controllers) + var/obj/machinery/pointdefense_control/PC = thing + if(PC != src && PC.id_tag == id_tag) + warning("Two [src] with the same id_tag of [id_tag]") + id_tag = null + // TODO - Remove this bit once machines are converted to Initialize + if(ispath(circuit)) + circuit = new circuit(src) + default_apply_parts() + +/obj/machinery/pointdefense_control/get_description_interaction() + . = ..() + if(!id_tag) + . += "[desc_panel_image("multitool")]to set ident tag" + +/obj/machinery/pointdefense_control/ui_interact(var/mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1) + if(ui_template) + var/list/data = build_ui_data() + ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open) + if (!ui) + ui = new(user, src, ui_key, ui_template, name, 400, 600) + ui.set_initial_data(data) + ui.open() + ui.set_auto_update(1) + +/obj/machinery/pointdefense_control/attack_hand(mob/user) + if((. = ..())) + return + if(CanUseTopic(user, global.default_state) > STATUS_CLOSE) + ui_interact(user) + return TRUE + +/obj/machinery/pointdefense_control/Topic(var/mob/user, var/href_list) + if((. = ..())) + return + + if(href_list["toggle_active"]) + var/obj/machinery/pointdefense/PD = locate(href_list["toggle_active"]) + if(!istype(PD)) + return TOPIC_NOACTION + + //if(!lan || !lan.is_connected(PD)) + if(PD.id_tag != id_tag) + return TOPIC_NOACTION + + if(!(get_z(PD) in GetConnectedZlevels(get_z(src)))) + to_chat(user, "[PD] is not within control range.") + return TOPIC_NOACTION + + if(!PD.Activate()) //Activate() whilst the device is active will return false. + PD.Deactivate() + return TOPIC_REFRESH + +/obj/machinery/pointdefense_control/proc/build_ui_data() + var/list/data = list() + data["id"] = id_tag + data["name"] = name + var/list/turrets = list() + if(id_tag) + var/list/connected_z_levels = GetConnectedZlevels(get_z(src)) + for(var/i = 1 to LAZYLEN(pointdefense_turrets)) + var/obj/machinery/pointdefense/PD = pointdefense_turrets[i] + if(!(PD.id_tag == id_tag && get_z(PD) in connected_z_levels)) + continue + var/list/turret = list() + turret["id"] = "#[i]" + turret["ref"] = "\ref[PD]" + turret["active"] = PD.active + turret["effective_range"] = PD.active ? "[PD.kill_range] meter\s" : "OFFLINE." + turret["reaction_wheel_delay"] = PD.active ? "[(PD.rotation_speed / (1 SECONDS))] second\s" : "OFFLINE." + turret["recharge_time"] = PD.active ? "[(PD.charge_cooldown / (1 SECONDS))] second\s" : "OFFLINE." + + turrets += list(turret) + + data["turrets"] = turrets + return data + +/obj/machinery/pointdefense_control/attackby(var/obj/item/W, var/mob/user) + if(W?.is_multitool()) + var/new_ident = input(user, "Enter a new ident tag.", "[src]", id_tag) as null|text + if(new_ident && new_ident != id_tag && user.Adjacent(src) && CanInteract(user, physical_state)) + // Check for duplicate controllers with this ID + for(var/thing in pointdefense_controllers) + var/obj/machinery/pointdefense_control/PC = thing + if(PC != src && PC.id_tag == id_tag) + to_chat(user, "The [new_ident] network already has a controller.") + return + to_chat(user, "You register [src] with the [new_ident] network.") + id_tag = new_ident + return + if(default_deconstruction_screwdriver(user, W)) + return + if(default_deconstruction_crowbar(user, W)) + return + if(default_part_replacement(user, W)) + return + return ..() + +// +// The acutal point defense battery +// + +/obj/machinery/pointdefense + name = "\improper point defense battery" + icon = 'icons/obj/pointdefense.dmi' + icon_state = "pointdefense" + desc = "A Kuiper pattern anti-meteor battery. Capable of destroying most threats in a single salvo." + description_info = "Must have the same ident tag as a fire assist mainframe on the same facility." + density = TRUE + anchored = TRUE + circuit = /obj/item/weapon/circuitboard/pointdefense + idle_power_usage = 0.1 KILOWATTS + appearance_flags = PIXEL_SCALE + var/active = TRUE + var/charge_cooldown = 1 SECOND //time between it can fire at different targets + var/last_shot = 0 + var/kill_range = 18 + var/rotation_speed = 0.25 SECONDS //How quickly we turn to face threats + var/engaging = FALSE + var/id_tag = null + +/obj/machinery/pointdefense/Initialize() + . = ..() + // TODO - Remove this bit once machines are converted to Initialize + if(ispath(circuit)) + circuit = new circuit(src) + default_apply_parts() + update_icon() + +/obj/machinery/pointdefense/get_description_interaction() + . = ..() + if(!id_tag) + . += "[desc_panel_image("multitool")]to set ident tag and connect to a mainframe." + +/obj/machinery/pointdefense/update_icon() + if(!active || !id_tag || inoperable()) + icon_state = "[initial(icon_state)]_off" + else + icon_state = initial(icon_state) + +/obj/machinery/pointdefense/power_change() + var/old_stat = stat + ..() + if(old_stat != stat) + update_icon() + +// Find controller with the same tag on connected z levels (if any) +/obj/machinery/pointdefense/proc/get_controller() + if(!id_tag) + return null + var/list/connected_z_levels = GetConnectedZlevels(get_z(src)) + for(var/thing in pointdefense_controllers) + var/obj/machinery/pointdefense_control/PDC = thing + if(PDC.id_tag == id_tag && (get_z(PDC) in connected_z_levels)) + return PDC + +/obj/machinery/pointdefense/attackby(var/obj/item/W, var/mob/user) + if(W?.is_multitool()) + var/new_ident = input(user, "Enter a new ident tag.", "[src]", id_tag) as null|text + if(new_ident && new_ident != id_tag && user.Adjacent(src) && CanInteract(user, physical_state)) + to_chat(user, "You register [src] with the [new_ident] network.") + id_tag = new_ident + return + if(default_deconstruction_screwdriver(user, W)) + return + if(default_deconstruction_crowbar(user, W)) + return + if(default_part_replacement(user, W)) + return + return ..() + +//Guns cannot shoot through hull or generally dense turfs. +/obj/machinery/pointdefense/proc/space_los(meteor) + for(var/turf/T in getline(src,meteor)) + if(T.density) + return FALSE + return TRUE + +/obj/machinery/pointdefense/proc/Shoot(var/weakref/target) + var/obj/effect/meteor/M = target.resolve() + if(!istype(M)) + return + engaging = TRUE + var/Angle = round(Get_Angle(src,M)) + var/matrix/rot_matrix = matrix() + rot_matrix.Turn(Angle) + addtimer(CALLBACK(src, .proc/finish_shot, target), rotation_speed) + animate(src, transform = rot_matrix, rotation_speed, easing = SINE_EASING) + + set_dir(ATAN2(transform.b, transform.a) > 0 ? NORTH : SOUTH) + +/obj/machinery/pointdefense/proc/finish_shot(var/weakref/target) + //Cleanup from list + var/obj/machinery/pointdefense_control/PC = get_controller() + if(istype(PC)) + PC.targets -= target + + engaging = FALSE + last_shot = world.time + var/obj/effect/meteor/M = target.resolve() + if(!istype(M)) + return + //We throw a laser but it doesnt have to hit for meteor to explode + var/obj/item/projectile/beam/pointdefense/beam = new(get_turf(src)) + playsound(src, 'sound/weapons/mandalorian.ogg', 75, 1) + use_power_oneoff(idle_power_usage * 10) + beam.launch_projectile(target = M.loc, user = src) + + M.make_debris() + qdel(M) + +/obj/machinery/pointdefense/process() + ..() + if(stat & (NOPOWER|BROKEN)) + return + if(!active) + return + var/desiredir = ATAN2(transform.b, transform.a) > 0 ? NORTH : SOUTH + if(dir != desiredir) + set_dir(desiredir) + if(LAZYLEN(GLOB.meteor_list) > 0) + find_and_shoot() + +/obj/machinery/pointdefense/proc/find_and_shoot() + if(LAZYLEN(GLOB.meteor_list) == 0) + return + if(engaging || ((world.time - last_shot) < charge_cooldown)) + return + + var/obj/machinery/pointdefense_control/PC = get_controller() + if(!istype(PC)) + return + + var/list/connected_z_levels = GetConnectedZlevels(get_z(src)) + for(var/obj/effect/meteor/M in GLOB.meteor_list) + var/already_targeted = FALSE + for(var/weakref/WR in PC.targets) + var/obj/effect/meteor/m = WR.resolve() + if(m == M) + already_targeted = TRUE + break + if(!istype(m)) + PC.targets -= WR + + if(already_targeted) + continue + + if(!(M.z in connected_z_levels)) + continue + if(get_dist(M, src) > kill_range) + continue + if(!emagged && space_los(M)) + var/weakref/target = weakref(M) + PC.targets += target + Shoot(target) + return + +/obj/machinery/pointdefense/RefreshParts() + . = ..() + // Calculates an average rating of components that affect shooting rate + var/shootrate_divisor = total_component_rating_of_type(/obj/item/weapon/stock_parts/capacitor) + + charge_cooldown = 2 SECONDS / (shootrate_divisor ? shootrate_divisor : 1) + + //Calculate max shooting range + var/killrange_multiplier = total_component_rating_of_type(/obj/item/weapon/stock_parts/capacitor) + killrange_multiplier += 1.5 * total_component_rating_of_type(/obj/item/weapon/stock_parts/scanning_module) + + kill_range = 10 + 4 * killrange_multiplier + + var/rotation_divisor = total_component_rating_of_type(/obj/item/weapon/stock_parts/manipulator) + rotation_speed = 0.5 SECONDS / (rotation_divisor ? rotation_divisor : 1) + +/obj/machinery/pointdefense/proc/Activate() + if(active) + return FALSE + + playsound(src, 'sound/weapons/flash.ogg', 100, 0) + update_use_power(USE_POWER_IDLE) + active = TRUE + update_icon() + return TRUE + +/obj/machinery/pointdefense/proc/Deactivate() + if(!active) + return FALSE + playsound(src, 'sound/machines/apc_nopower.ogg', 50, 0) + update_use_power(USE_POWER_OFF) + active = FALSE + update_icon() + return TRUE + +// +// Projectile Beam Definitions +// + +/obj/item/projectile/beam/pointdefense + name = "point defense salvo" + icon_state = "laser" + damage = 15 + damage_type = ELECTROCUTE //You should be safe inside a voidsuit + sharp = FALSE //"Wide" spectrum beam + light_color = COLOR_GOLD + + muzzle_type = /obj/effect/projectile/muzzle/pointdefense + tracer_type = /obj/effect/projectile/tracer/pointdefense + impact_type = /obj/effect/projectile/impact/pointdefense + + +/obj/effect/projectile/tracer/pointdefense + icon_state = "beam_pointdef" + +/obj/effect/projectile/muzzle/pointdefense + icon_state = "muzzle_pointdef" + +/obj/effect/projectile/impact/pointdefense + icon_state = "impact_pointdef" diff --git a/code/game/objects/items/weapons/circuitboards/machinery/ships.dm b/code/game/objects/items/weapons/circuitboards/machinery/ships.dm new file mode 100644 index 0000000000..dd37bc2519 --- /dev/null +++ b/code/game/objects/items/weapons/circuitboards/machinery/ships.dm @@ -0,0 +1,23 @@ +#ifndef T_BOARD +#error T_BOARD macro is not defined but we need it! +#endif + +/obj/item/weapon/circuitboard/pointdefense + name = T_BOARD("point defense battery") + board_type = new /datum/frame/frame_types/machine + desc = "Control systems for a Kuiper pattern point defense battery. Aim away from vessel." + build_path = /obj/machinery/pointdefense + origin_tech = list(TECH_ENGINEERING = 3, TECH_COMBAT = 2) + req_components = list( + /obj/item/mecha_parts/mecha_equipment/weapon/energy/laser = 1, + /obj/item/weapon/stock_parts/manipulator = 2, + /obj/item/weapon/stock_parts/capacitor = 2, + ) + +/obj/item/weapon/circuitboard/pointdefense_control + name = T_BOARD("fire assist mainframe") + board_type = new /datum/frame/frame_types/machine + desc = "A control computer to synchronize point defense batteries." + build_path = /obj/machinery/pointdefense_control + origin_tech = list(TECH_ENGINEERING = 3, TECH_COMBAT = 2) + req_components = list() diff --git a/code/modules/client/preference_setup/preference_setup.dm b/code/modules/client/preference_setup/preference_setup.dm index a55ca0ccca..01a0d75fec 100644 --- a/code/modules/client/preference_setup/preference_setup.dm +++ b/code/modules/client/preference_setup/preference_setup.dm @@ -1,9 +1,3 @@ -#define TOPIC_NOACTION 0 -#define TOPIC_HANDLED 1 -#define TOPIC_REFRESH 2 -#define TOPIC_UPDATE_PREVIEW 4 -#define TOPIC_REFRESH_UPDATE_PREVIEW (TOPIC_REFRESH|TOPIC_UPDATE_PREVIEW) - #define PREF_FBP_CYBORG "cyborg" #define PREF_FBP_POSI "posi" #define PREF_FBP_SOFTWARE "software" diff --git a/code/modules/research/designs/circuits/circuits.dm b/code/modules/research/designs/circuits/circuits.dm index 63150b8967..4028342577 100644 --- a/code/modules/research/designs/circuits/circuits.dm +++ b/code/modules/research/designs/circuits/circuits.dm @@ -621,6 +621,19 @@ CIRCUITS BELOW build_path = /obj/item/weapon/circuitboard/microwave/advanced sort_string = "MAAAC" +/datum/design/circuit/pointdefense + name = "point defense battery" + id = "pointdefense" + req_tech = list(TECH_DATA = 2, TECH_ENGINEERING = 3, TECH_COMBAT = 4) + build_path = /obj/item/weapon/circuitboard/pointdefense + sort_string = "OAABA" + +/datum/design/circuit/pointdefense_control + name = "deluxe microwave" + id = "pointdefense_control" + req_tech = list(TECH_DATA = 4, TECH_ENGINEERING = 3, TECH_COMBAT = 2) + build_path = /obj/item/weapon/circuitboard/pointdefense_control + sort_string = "OAABB" /* I have no idea how this was even running before, but it doesn't seem to be necessary. /////////////////////////////////// diff --git a/icons/obj/pointdefense.dmi b/icons/obj/pointdefense.dmi new file mode 100644 index 0000000000..8b852e69fe Binary files /dev/null and b/icons/obj/pointdefense.dmi differ diff --git a/icons/obj/projectiles_impact.dmi b/icons/obj/projectiles_impact.dmi index fd9d314310..19f6d1db96 100644 Binary files a/icons/obj/projectiles_impact.dmi and b/icons/obj/projectiles_impact.dmi differ diff --git a/icons/obj/projectiles_muzzle.dmi b/icons/obj/projectiles_muzzle.dmi index d88b9a283e..6c9d8cf694 100644 Binary files a/icons/obj/projectiles_muzzle.dmi and b/icons/obj/projectiles_muzzle.dmi differ diff --git a/icons/obj/projectiles_tracer.dmi b/icons/obj/projectiles_tracer.dmi index 153794a929..935e3afd21 100644 Binary files a/icons/obj/projectiles_tracer.dmi and b/icons/obj/projectiles_tracer.dmi differ diff --git a/nano/templates/pointdefense_control.tmpl b/nano/templates/pointdefense_control.tmpl new file mode 100644 index 0000000000..19f85d3dba --- /dev/null +++ b/nano/templates/pointdefense_control.tmpl @@ -0,0 +1,54 @@ +

Fire Assist Mainframe: {{:data.id || "[no tag]"}}

+{{if !(data.id)}} +
+ This system has not been assigned an ident tag. Please contact your system administrator or conduct a manual update with a standard multitool. +
+ {{/if}} +{{for data.turrets}} +

{{:value.id}}

+
+
+ Battery status +
+
+ {{if value.active}} + Online {{:helper.link('Shut down', null, {'toggle_active': value.ref})}} + {{else}} + Offline {{:helper.link('Start up', null, {'toggle_active': value.ref})}} + {{/if}} +
+
+
+
+ Effective range +
+
+ {{:value.effective_range}} +
+
+
+
+ Reaction wheel delay +
+
+ {{:value.reaction_wheel_delay}} +
+
+
+
+ Recharge time +
+
+ {{:value.recharge_time}} +
+{{empty}} +
+
+ Error: +
+
+ No weapon systems detected. Please check network connection. +
+
+
+{{/for}} diff --git a/sound/machines/apc_nopower.ogg b/sound/machines/apc_nopower.ogg new file mode 100644 index 0000000000..b73c50d9b0 Binary files /dev/null and b/sound/machines/apc_nopower.ogg differ diff --git a/vorestation.dme b/vorestation.dme index 913192e8c1..806182a185 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -783,6 +783,7 @@ #include "code\game\machinery\painter_vr.dm" #include "code\game\machinery\partslathe_vr.dm" #include "code\game\machinery\pda_multicaster.dm" +#include "code\game\machinery\pointdefense.dm" #include "code\game\machinery\portable_turret.dm" #include "code\game\machinery\portable_turret_vr.dm" #include "code\game\machinery\recharger.dm" @@ -1208,6 +1209,7 @@ #include "code\game\objects\items\weapons\circuitboards\machinery\recharge_station.dm" #include "code\game\objects\items\weapons\circuitboards\machinery\research.dm" #include "code\game\objects\items\weapons\circuitboards\machinery\shieldgen.dm" +#include "code\game\objects\items\weapons\circuitboards\machinery\ships.dm" #include "code\game\objects\items\weapons\circuitboards\machinery\telecomms.dm" #include "code\game\objects\items\weapons\circuitboards\machinery\unary_atmos.dm" #include "code\game\objects\items\weapons\grenades\anti_photon_grenade.dm"