diff --git a/code/__DEFINES/__game.dm b/code/__DEFINES/__game.dm index e667ccf6db3f..459b66e86cf7 100644 --- a/code/__DEFINES/__game.dm +++ b/code/__DEFINES/__game.dm @@ -494,6 +494,9 @@ #define TURF_PROTECTION_CAS 2 #define TURF_PROTECTION_OB 3 +#define TURF_AA_PROTECTION_CAS_COVERED 4 +#define TURF_AA_PROTECTION_CAS_RESTRICTED 5 + /// Convert a turf protection level to a ceiling protection level /proc/get_ceiling_protection_level(turf_protection_level) switch(turf_protection_level) diff --git a/code/game/machinery/computer/dropship_weapons.dm b/code/game/machinery/computer/dropship_weapons.dm index 6f9862d3120a..ecb453627879 100644 --- a/code/game/machinery/computer/dropship_weapons.dm +++ b/code/game/machinery/computer/dropship_weapons.dm @@ -470,6 +470,9 @@ if(CEILING_IS_PROTECTED(location_area.ceiling, CEILING_PROTECTION_TIER_1)) to_chat(user, SPAN_WARNING("Target is obscured.")) return FALSE + if (!aa_protection_check(location, user)) + return FALSE + var/equipment_tag = params["equipment_id"] for(var/obj/structure/dropship_equipment/equipment as anything in shuttle.equipments) var/mount_point = equipment.ship_base.attach_id @@ -699,6 +702,9 @@ if (protected_by_pylon(TURF_PROTECTION_CAS, TU)) to_chat(weapon_operator, SPAN_WARNING("INVALID TARGET: biological-pattern interference with signal.")) return FALSE + + if (!aa_protection_check(TU, weapon_operator)) + return FALSE if(!DEW.ammo_equipped.can_fire_at(TU, weapon_operator)) return FALSE @@ -801,6 +807,25 @@ update_location(weapons_operator, cas_sig) return TRUE +/obj/structure/machinery/computer/dropship_weapons/proc/aa_protection_check(turf/target, mob/weapon_operator) + var/obj/docking_port/mobile/marine_dropship/dropship = SSshuttle.getShuttle(shuttle_tag) + var/obj/structure/machinery/defenses/planetary_anti_air/highest_aa = target.get_aa_with_highest_protection_level(faction) + if (!highest_aa) + return + + switch(highest_aa.get_protection_level(target)) + if(TURF_AA_PROTECTION_CAS_COVERED) + to_chat(weapon_operator, SPAN_WARNING("WARNING: AA protection persist in area.")) + highest_aa.fire() + dropship.on_planetary_aa_interception() + return TRUE + if(TURF_AA_PROTECTION_CAS_RESTRICTED) + to_chat(weapon_operator, SPAN_WARNING("WARNING: strong AA protection persist in area. Perfoming an evasive maneuver")) + highest_aa.fire() + dropship.on_planetary_aa_interception_heavy() + return FALSE + return TRUE + /obj/structure/machinery/computer/dropship_weapons/proc/initiate_firemission(mob/user, fmId, dir, offset_x, offset_y) set waitfor = 0 var/obj/docking_port/mobile/marine_dropship/dropship = SSshuttle.getShuttle(shuttle_tag) @@ -819,6 +844,8 @@ source.y + offset_y, source.z ) + if (!aa_protection_check(target, user)) + return FALSE var/result = firemission_envelope.execute_firemission(recorded_loc, target, dir, fmId) if(result != FIRE_MISSION_ALL_GOOD) to_chat(user, SPAN_WARNING("Screen beeps with an error: [firemission_envelope.mission_error]")) diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index 0082cb6ae0ae..6933c5f40317 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -29,6 +29,7 @@ var/intact_tile = 1 //used by floors to distinguish floor with/without a floortile(e.g. plating). var/can_bloody = TRUE //Can blood spawn on this turf? var/list/linked_pylons + var/list/linked_aa var/obj/effect/alien/weeds/weeds var/list/datum/automata_cell/autocells @@ -386,6 +387,7 @@ // return src var/pylons = linked_pylons + var/aa_to_copy = linked_aa var/list/old_baseturfs = baseturfs @@ -413,6 +415,7 @@ W.baseturfs = old_baseturfs W.linked_pylons = pylons + W.linked_aa = aa_to_copy W.hybrid_lights_affecting = old_hybrid_lights_affecting W.dynamic_lumcount = dynamic_lumcount @@ -732,6 +735,28 @@ return protection_level +/turf/proc/get_aa_with_highest_protection_level(faction) + var/obj/structure/machinery/defenses/planetary_anti_air/highest_aa = null + var/protection_level = TURF_PROTECTION_NONE + + for (var/obj/structure/machinery/defenses/planetary_anti_air/planetary_aa in linked_aa) + if (!(faction in planetary_aa.faction_group)) + var/protection_lvl = planetary_aa.get_protection_level(src) + if(protection_lvl > protection_level) + protection_level = protection_lvl + highest_aa = planetary_aa + return highest_aa + +/turf/proc/get_aa_protection_level(faction) + var/protection_level = TURF_PROTECTION_NONE + for (var/obj/structure/machinery/defenses/planetary_anti_air/planetary_aa in linked_aa) + if (!(faction in planetary_aa.faction_group)) + var/protection_lvl = planetary_aa.get_protection_level(src) + if(protection_lvl > protection_level) + protection_level = protection_lvl + + return protection_level + GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list( /turf/open/space, /turf/baseturf_bottom, diff --git a/code/modules/defenses/aa_tower.dm b/code/modules/defenses/aa_tower.dm new file mode 100644 index 000000000000..44caf3f97d3e --- /dev/null +++ b/code/modules/defenses/aa_tower.dm @@ -0,0 +1,266 @@ +#define DROPSIP_TIME_REDUCTION_IF_IN_AA_ZONE 0.9 + +/obj/structure/machinery/defenses/planetary_anti_air + name = "\improper Planetary AA" + desc = "\improper The Planetary AA provides cover from aerial attacks" + icon = 'icons/obj/structures/machinery/defenses/aa_tower.dmi' + icon_state = "" + can_be_near_defense = TRUE + defense_icon = null + faction_group = FACTION_LIST_MARINE + bound_width = 32 + bound_height = 32 + pixel_x = -15 + pixel_y = -8 + hack_time = 300 + disassemble_time = 150 + health = 500 + health_max = 500 + anchored = FALSE + placed = FALSE + static = TRUE + encryptable = FALSE + var/luminosity_strength = 2 + var/anchor_time = 40 + + //CAS cant operate in area + var/restricted_range = 20 + //CAS can operate but will get some effects + var/covered_range = 40 + + var/list/linked_turfs_restricted = list() + var/list/linked_turfs_covered = list() + + var/last_fired = 0 + var/fire_delay = 30 + +/obj/structure/machinery/defenses/planetary_anti_air/proc/fire() + if(!(world.time - last_fired >= fire_delay) || !turned_on || health <= 0) + return + + playsound(loc, 'sound/machines/twobeep.ogg', 50, 1) + sleep(3) + visible_message("[icon2html(src, viewers(src))] [SPAN_WARNING("The [name] fires projectiles into air!")]") + playsound(loc, 'sound/weapons/vehicles/autocannon_fire.ogg', 50, 1) + sleep(0.5) + playsound(loc, 'sound/weapons/vehicles/autocannon_fire.ogg', 50, 1) + last_fired = world.time + +/obj/structure/machinery/defenses/planetary_anti_air/get_examine_text(mob/user) + var/message = "" + if (health <= 0) + message += "It appears to be completely broken." + return list(message) + if(stat == DEFENSE_DAMAGED) + message += "It does not appear to be working.\n" + if(ishuman(user)) + if (FACTION_UPP in faction_group) + message += SPAN_INFO("It is currently allied to [FACTION_UPP].") + "\n" + if (FACTION_MARINE in faction_group) + message += SPAN_INFO("It is currently allied to [FACTION_MARINE].") + "\n" + message += SPAN_INFO("It has [SPAN_HELPFUL("[health]/[health_max]")] health.") + return list(message) + +/obj/structure/machinery/defenses/planetary_anti_air/attackby(obj/item/object as obj, mob/user as mob) + if(QDELETED(object)) + return + + if(HAS_TRAIT(object, TRAIT_TOOL_MULTITOOL)) + if(!friendly_faction(user.faction)) + to_chat(user, SPAN_WARNING("This doesn't seem safe...")) + var/additional_shock = 1 + if(!do_after(user, hack_time * user.get_skill_duration_multiplier(SKILL_CONSTRUCTION), INTERRUPT_ALL|BEHAVIOR_IMMOBILE, BUSY_ICON_BUILD, src)) + additional_shock++ + if(prob(50)) + var/mob/living/carbon/human/user_human = user + if(!skillcheck(user, SKILL_ENGINEER, SKILL_ENGINEER_NOVICE)) + if(turned_on) + additional_shock++ + user_human.electrocute_act(40, src, additional_shock)//god damn Hans... + setDir(get_dir(src, user_human))//Make sure he died + power_off() + power_on() + return + else + user_human.electrocute_act(20, src)//god bless him for stupid move + return + if(additional_shock >= 2) + return + LAZYCLEARLIST(faction_group) + for(var/i in user.faction_group) + LAZYADD(faction_group, i) + to_chat(user, SPAN_WARNING("You've hacked \the [src], it's now ours!")) + return + + if(!skillcheck(user, SKILL_ENGINEER, SKILL_ENGINEER_NOVICE)) + to_chat(user, SPAN_WARNING("You don't have the training to do this.")) + return + + if(static) + to_chat(user, SPAN_WARNING("\The [src] is bolted to the ground!")) + return + + user.visible_message(SPAN_NOTICE("[user] begins disassembling \the [src]."), SPAN_NOTICE("You begin disassembling \the [src].")) + + if(!do_after(user, disassemble_time * user.get_skill_duration_multiplier(SKILL_CONSTRUCTION), INTERRUPT_ALL|BEHAVIOR_IMMOBILE, BUSY_ICON_BUILD, src)) + return + + if(health < health_max * 0.25) //repeat check + to_chat(user, SPAN_WARNING("\The [src] is too damaged to pick up!")) + return + + user.visible_message(SPAN_NOTICE("[user] disassembles [src]."), SPAN_NOTICE("You disassemble [src].")) + + playsound(loc, 'sound/mecha/mechmove04.ogg', 30, 1) + var/turf/turf_under_aa = get_turf(src) + power_off() + HD.forceMove(turf_under_aa) + transfer_label_component(HD) + HD.dropped = 1 + HD.update_icon() + placed = 0 + forceMove(HD) + + return + + if(HAS_TRAIT(object, TRAIT_TOOL_WRENCH)) + if(anchored) + if(turned_on) + to_chat(user, SPAN_WARNING("[src] is currently active. You need to deactivate it first.")) + return + user.visible_message(SPAN_NOTICE("[user] begins unanchoring [src] from the ground."), + SPAN_NOTICE("You begin unanchoring [src] from the ground.")) + + if(!do_after(user, anchor_time * user.get_skill_duration_multiplier(SKILL_CONSTRUCTION), INTERRUPT_ALL|BEHAVIOR_IMMOBILE, BUSY_ICON_BUILD, src)) + return + user.visible_message(SPAN_NOTICE("[user] unanchors [src] from the ground."), + SPAN_NOTICE("You unanchor [src] from the ground.")) + anchored = FALSE + playsound(loc, 'sound/items/Ratchet.ogg', 25, 1) + return + else + var/turf/turf_under_aa = get_turf(src) + var/area/area = get_area(turf_under_aa) + if(CEILING_IS_PROTECTED(area.ceiling, CEILING_PROTECTION_TIER_1) || !is_ground_level(turf_under_aa.z)) + to_chat(user, SPAN_RED("You realize how bad of an idea this is and quickly stop.")) + return + + var/turf/open/floor = get_turf(src) + if(!floor.allow_construction) + to_chat(user, SPAN_WARNING("You cannot secure \the [src] here, find a more secure surface!")) + return FALSE + user.visible_message(SPAN_NOTICE("[user] begins securing [src] to the ground."), + SPAN_NOTICE("You begin securing [src] to the ground.")) + + if(!do_after(user, anchor_time * user.get_skill_duration_multiplier(SKILL_CONSTRUCTION), INTERRUPT_ALL|BEHAVIOR_IMMOBILE, BUSY_ICON_BUILD, src)) + return + user.visible_message(SPAN_NOTICE("[user] secures [src] to the ground."), + SPAN_NOTICE("You secure [src] to the ground.")) + anchored = TRUE + playsound(loc, 'sound/items/Ratchet.ogg', 25, 1) + return + + if(iswelder(object)) + if(!HAS_TRAIT(object, TRAIT_TOOL_BLOWTORCH)) + to_chat(user, SPAN_WARNING("You need a stronger blowtorch!")) + return + var/obj/item/tool/weldingtool/WT = object + if(health < 0) + to_chat(user, SPAN_WARNING("[src]'s internal circuitry is ruined, there's no way you can salvage this on the go.")) + return + + if(health >= health_max) + to_chat(user, SPAN_WARNING("[src] isn't in need of repairs.")) + return + + if(WT.remove_fuel(0, user)) + user.visible_message(SPAN_NOTICE("[user] begins repairing [src]."), + SPAN_NOTICE("You begin repairing [src].")) + if(do_after(user, 40 * user.get_skill_duration_multiplier(SKILL_CONSTRUCTION), INTERRUPT_ALL|BEHAVIOR_IMMOBILE, BUSY_ICON_FRIENDLY, src)) + user.visible_message(SPAN_NOTICE("[user] repairs [src]."), + SPAN_NOTICE("You repair [src].")) + if(stat == DEFENSE_DAMAGED) + stat &= ~DEFENSE_DAMAGED + update_health(-50) + playsound(loc, 'sound/items/Welder2.ogg', 25, 1) + return + return + + return TRUE + +/obj/structure/machinery/defenses/planetary_anti_air/update_icon() + if(turned_on) + if (FACTION_UPP in faction_group) + icon_state = "aa_upp" + else + icon_state = "aa_uscm" + else if(health <= 0) + icon_state = "aa_destroyed" + else + icon_state = "aa_off" + +/obj/structure/machinery/defenses/planetary_anti_air/upp + icon = 'icons/obj/structures/machinery/defenses/aa_tower.dmi' + icon_state = "aa_upp" + faction_group = FACTION_LIST_UPP + +/obj/structure/machinery/defenses/planetary_anti_air/proc/get_protection_level(turf/turf_to_check) + if (turf_to_check in linked_turfs_restricted) + return TURF_AA_PROTECTION_CAS_RESTRICTED + if (turf_to_check in linked_turfs_covered) + return TURF_AA_PROTECTION_CAS_COVERED + return TURF_PROTECTION_NONE + +/obj/structure/machinery/defenses/planetary_anti_air/power_on_action(mob/user) + var/turf/turf_under_aa = get_turf(src) + var/area/area = get_area(turf_under_aa) + + if(CEILING_IS_PROTECTED(area.ceiling, CEILING_PROTECTION_TIER_1)) + to_chat(user, SPAN_RED("[name] must be in the open air.")) + return + if(!is_ground_level(turf_under_aa.z)) + to_chat(user, SPAN_RED("You realize how bad of an idea this is and quickly stop.")) + return + + set_light(luminosity_strength) + visible_message("[icon2html(src, viewers(src))] [SPAN_NOTICE("The [name] hums to life and emits several beeps.")]") + + for(var/turf/turf_in_big_range as anything in RANGE_TURFS(covered_range, loc)) + LAZYADD(turf_in_big_range.linked_aa, src) + linked_turfs_covered += turf_in_big_range + for(var/turf/turf_in_close_range as anything in RANGE_TURFS(restricted_range, loc)) + linked_turfs_restricted += turf_in_close_range + +/obj/structure/machinery/defenses/planetary_anti_air/proc/remove_protected_area() + for(var/turf/turf_in_big_range as anything in RANGE_TURFS(covered_range, loc)) + LAZYREMOVE(turf_in_big_range.linked_aa, src) + linked_turfs_covered -= turf_in_big_range + for(var/turf/turf_in_close_range as anything in RANGE_TURFS(restricted_range, loc)) + linked_turfs_restricted -= turf_in_close_range + +/obj/structure/machinery/defenses/planetary_anti_air/power_off_action(mob/user) + set_light(0) + visible_message("[icon2html(src, viewers(src))] [SPAN_NOTICE("The [name] powers down and goes silent.")]") + remove_protected_area() + +/obj/structure/machinery/defenses/planetary_anti_air/Destroy() + remove_protected_area() + update_icon() + . = ..() + +/obj/structure/machinery/defenses/planetary_anti_air/destroyed_action() + remove_protected_area() + visible_message("[icon2html(src, viewers(src))] [SPAN_WARNING("The [name] starts spitting out sparks and smoke!")]") + playsound(loc, 'sound/mecha/critdestrsyndi.ogg', 25, 1) + sleep(10) + cell_explosion(loc, 20, 20, EXPLOSION_FALLOFF_SHAPE_LINEAR, null, create_cause_data("AA tower explosion")) + update_icon() + +/obj/structure/machinery/defenses/planetary_anti_air/damaged_action(damage) + if(health < health_max * 0.15) + visible_message(SPAN_DANGER("[icon2html(src, viewers(src))] The [name] cracks and breaks apart!")) + stat |= DEFENSE_DAMAGED + turned_on = FALSE + remove_protected_area() + set_light(0) + update_icon() diff --git a/code/modules/shuttle/shuttles/dropship.dm b/code/modules/shuttle/shuttles/dropship.dm index a1405d7e4e23..60070c8d2674 100644 --- a/code/modules/shuttle/shuttles/dropship.dm +++ b/code/modules/shuttle/shuttles/dropship.dm @@ -31,6 +31,29 @@ var/automated_timer var/datum/cas_signal/paradrop_signal +/obj/docking_port/mobile/marine_dropship/proc/on_planetary_aa_interception_heavy() + if (!in_flyby) + return + for(var/area/internal_area in shuttle_areas) + for(var/turf/internal_turf in internal_area) + for(var/mob/shuttle_turf in internal_turf) + to_chat(shuttle_turf, SPAN_DANGER("The ship jostles violently as explosions rock the ship!")) + to_chat(shuttle_turf, SPAN_DANGER("You feel the ship turning sharply as it adjusts its course!")) + shake_camera(shuttle_turf, 60, 2) + playsound_area(internal_area, 'sound/effects/antiair_explosions.ogg') + //Removing some time as penalty + modTimer(DROPSIP_TIME_REDUCTION_IF_IN_AA_ZONE) + +/obj/docking_port/mobile/marine_dropship/proc/on_planetary_aa_interception() + if (!in_flyby) + return + for(var/area/internal_area in shuttle_areas) + for(var/turf/internal_turf in internal_area) + for(var/mob/shuttle_turf in internal_turf) + to_chat(shuttle_turf, SPAN_DANGER("The ship jostles violently as explosions rock the ship!")) + shake_camera(shuttle_turf, 60, 2) + playsound_area(internal_area, 'sound/effects/antiair_explosions.ogg') + /obj/docking_port/mobile/marine_dropship/Initialize(mapload) . = ..() diff --git a/colonialmarines.dme b/colonialmarines.dme index 78f81b2e1213..0a9bd20fdc5a 100644 --- a/colonialmarines.dme +++ b/colonialmarines.dme @@ -1708,6 +1708,7 @@ #include "code\modules\decorators\mass_xeno_decorator.dm" #include "code\modules\decorators\weapon_decorator.dm" #include "code\modules\decorators\weapon_map_decorator.dm" +#include "code\modules\defenses\aa_tower.dm" #include "code\modules\defenses\bell_tower.dm" #include "code\modules\defenses\defenses.dm" #include "code\modules\defenses\handheld.dm" diff --git a/icons/obj/structures/machinery/defenses/aa_tower.dmi b/icons/obj/structures/machinery/defenses/aa_tower.dmi new file mode 100644 index 000000000000..66a4454bef9e Binary files /dev/null and b/icons/obj/structures/machinery/defenses/aa_tower.dmi differ