diff --git a/code/__DEFINES/dcs/signals/atom/mob/living/signals_xeno.dm b/code/__DEFINES/dcs/signals/atom/mob/living/signals_xeno.dm index 4e044735793c..534ac101986b 100644 --- a/code/__DEFINES/dcs/signals/atom/mob/living/signals_xeno.dm +++ b/code/__DEFINES/dcs/signals/atom/mob/living/signals_xeno.dm @@ -95,3 +95,7 @@ /// From /mob/living/carbon/xenomorph/proc/do_evolve() #define COMSIG_XENO_EVOLVE_TO_NEW_CASTE "xeno_evolve_to_new_caste" + +/// From /obj/structure/tunnel/attack_alien() : (mob/living/carbon/xenomorph/xeno) +#define COMSIG_XENO_ENTER_TUNNEL "xeno_enter_tunnel" + #define COMPONENT_CANCEL_TUNNEL (1<<0) diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index adc76b727914..8a5109eaf83c 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -513,3 +513,6 @@ GLOBAL_LIST(trait_name_map) #define HACKED_TRAIT "hacked" /// traits from chloroform usage #define CHLOROFORM_TRAIT "chloroform" + +// from watchtower.dm +#define TRAIT_ON_WATCHTOWER "on_watchtower" diff --git a/code/datums/components/weed_food.dm b/code/datums/components/weed_food.dm index 4c8d6112fde2..1c51cdf869e4 100644 --- a/code/datums/components/weed_food.dm +++ b/code/datums/components/weed_food.dm @@ -234,6 +234,8 @@ return FALSE if(!parent_turf?.weeds) return FALSE + if(HAS_TRAIT(parent_mob, TRAIT_ON_WATCHTOWER)) + return FALSE if(SEND_SIGNAL(parent_mob, COMSIG_ATTEMPT_MOB_PULL) & COMPONENT_CANCEL_MOB_PULL) return FALSE diff --git a/code/datums/elements/bullet_trait/direct_only.dm b/code/datums/elements/bullet_trait/direct_only.dm new file mode 100644 index 000000000000..0e3a609d5ecb --- /dev/null +++ b/code/datums/elements/bullet_trait/direct_only.dm @@ -0,0 +1,43 @@ +// This trait makes the projectile only hit targets directly clicked + +/datum/element/bullet_trait_direct_only + // General bullet trait vars + element_flags = ELEMENT_DETACH|ELEMENT_BESPOKE + id_arg_index = 2 + +/datum/element/bullet_trait_direct_only/Attach(datum/target) + . = ..() + if(!istype(target, /obj/projectile)) + return ELEMENT_INCOMPATIBLE + + RegisterSignal(target, COMSIG_BULLET_CHECK_MOB_SKIPPING, PROC_REF(check_distance)) + +/datum/element/bullet_trait_direct_only/Detach(datum/target) + UnregisterSignal(target, COMSIG_BULLET_CHECK_MOB_SKIPPING) + + return ..() + +/datum/element/bullet_trait_direct_only/proc/check_distance(obj/projectile/projectile, mob/living/carbon/human/projectile_target) + SIGNAL_HANDLER + + if(projectile.original != projectile_target) + return COMPONENT_SKIP_MOB + +/datum/element/bullet_trait_direct_only/watchtower/check_distance(obj/projectile/projectile, mob/living/carbon/human/projectile_target) + if(!HAS_TRAIT(projectile.firer, TRAIT_ON_WATCHTOWER)) + if(!istype(projectile.firer, /mob)) + return + var/mob/firer = projectile.firer + var/obj/item/weapon/gun/gun = firer.get_inactive_hand() + if(istype(gun)) + gun.remove_bullet_traits(list("watchtower_arc")) + + gun = firer.get_active_hand() + if(istype(gun)) + gun.remove_bullet_traits(list("watchtower_arc")) + return + + if(HAS_TRAIT(projectile_target, TRAIT_ON_WATCHTOWER)) + return + + return ..() diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 8099fb2fe7b1..644a23affec7 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -880,8 +880,6 @@ SPAN_NOTICE("You look up from [zoom_device].")) zoom = !zoom COOLDOWN_START(user, zoom_cooldown, 20) - SEND_SIGNAL(user, COMSIG_LIVING_ZOOM_OUT, src) - SEND_SIGNAL(src, COMSIG_ITEM_UNZOOM, user) UnregisterSignal(src, list( COMSIG_ITEM_DROPPED, COMSIG_ITEM_UNWIELD, @@ -894,6 +892,9 @@ user.client.pixel_x = 0 user.client.pixel_y = 0 + SEND_SIGNAL(user, COMSIG_LIVING_ZOOM_OUT, src) + SEND_SIGNAL(src, COMSIG_ITEM_UNZOOM, user) + /obj/item/proc/zoom_handle_mob_move_or_look(mob/living/mover, actually_moving, direction, specific_direction) SIGNAL_HANDLER diff --git a/code/game/objects/structures/girders.dm b/code/game/objects/structures/girders.dm index 3771fa18181e..aacc4d8c2d89 100644 --- a/code/game/objects/structures/girders.dm +++ b/code/game/objects/structures/girders.dm @@ -62,7 +62,7 @@ . += SPAN_NOTICE("Plasteel attached. [SPAN_HELPFUL("Weld")] to finish.") return if(STATE_DISPLACED) - . += SPAN_NOTICE("It looks dislodged. [SPAN_HELPFUL("Crowbar")] to secure it.") + . += SPAN_NOTICE("It looks dislodged. [SPAN_HELPFUL("Crowbar")] to secure it. Or [SPAN_HELPFUL("Weld")] with a 2x2 of dislodged girders to start constructing a watchtower.") /obj/structure/girder/update_icon() . = ..() @@ -114,7 +114,7 @@ if(object.density) to_chat(user, SPAN_WARNING("[object] is blocking you from welding [src] together!")) return - if(do_after(user,30, INTERRUPT_ALL|BEHAVIOR_IMMOBILE, BUSY_ICON_BUILD)) + if(do_after(user, 3 SECONDS, INTERRUPT_ALL|BEHAVIOR_IMMOBILE, BUSY_ICON_BUILD)) if(QDELETED(src)) return to_chat(user, SPAN_NOTICE("You weld the girder together!")) @@ -176,6 +176,37 @@ if(STATE_REINFORCED_WALL) return do_reinforced_wall(W, user) if(STATE_DISPLACED) + if(HAS_TRAIT(W, TRAIT_TOOL_BLOWTORCH)) + var/area/area = get_area(src) + if(CEILING_IS_PROTECTED(area.ceiling, CEILING_GLASS)) + to_chat(user, SPAN_WARNING("Watchtowers can only be built in the open.")) + return + + var/list/turf/turfs = CORNER_BLOCK(get_turf(src), 2, 2) + var/list/obj/structure/girder/girders = list() + + for(var/turf/current_turf in turfs) + var/found_girder = FALSE + for(var/obj/structure/girder/girder in current_turf) + if(girder.state == STATE_DISPLACED) + found_girder = TRUE + girders += girder + if(!found_girder) + return + + to_chat(user, SPAN_NOTICE("You start welding the displaced girders together.")) + playsound(loc, 'sound/items/Welder.ogg', 25, 1) + + if(!do_after(user, 3 SECONDS, INTERRUPT_ALL|BEHAVIOR_IMMOBILE, BUSY_ICON_BUILD)) + return + + to_chat(user, SPAN_NOTICE("You weld the displaced girders together, creating a watchtower frame.")) + + new /obj/structure/watchtower(loc) + + for(var/list/obj/structure/girder as anything in girders) + qdel(girder) + if(HAS_TRAIT(W, TRAIT_TOOL_CROWBAR)) var/turf/open/floor = loc if(!floor.allow_construction) @@ -393,6 +424,13 @@ anchored = FALSE state = STATE_DISPLACED +/obj/structure/girder/broken + health = 0 + icon_state = "girder_damaged" + anchored = FALSE + density = FALSE + state = STATE_STANDARD + /obj/structure/girder/reinforced icon_state = "reinforced" health = 500 diff --git a/code/modules/cm_aliens/structures/tunnel.dm b/code/modules/cm_aliens/structures/tunnel.dm index 15540ddccb2c..4fa6923c55a9 100644 --- a/code/modules/cm_aliens/structures/tunnel.dm +++ b/code/modules/cm_aliens/structures/tunnel.dm @@ -211,6 +211,9 @@ . = attack_alien(M) /obj/structure/tunnel/attack_alien(mob/living/carbon/xenomorph/M) + if(SEND_SIGNAL(M, COMSIG_XENO_ENTER_TUNNEL) & COMPONENT_CANCEL_TUNNEL) + return XENO_NO_DELAY_ACTION + if(!istype(M) || M.is_mob_incapacitated(TRUE)) return XENO_NO_DELAY_ACTION diff --git a/code/modules/movement/movement.dm b/code/modules/movement/movement.dm index 8151d2df6707..13e14df27282 100644 --- a/code/modules/movement/movement.dm +++ b/code/modules/movement/movement.dm @@ -68,6 +68,7 @@ setDir(old_dir) else if(old_dir != direct) setDir(direct) + l_move_time = world.time if ((oldloc != loc && oldloc && oldloc.z == z)) last_move_dir = get_dir(oldloc, loc) diff --git a/code/modules/watchtower/blockers.dm b/code/modules/watchtower/blockers.dm new file mode 100644 index 000000000000..3802d3abf183 --- /dev/null +++ b/code/modules/watchtower/blockers.dm @@ -0,0 +1,45 @@ +/// Invisible Blocker Walls, they link up with the watchtower and collapse with it +/obj/structure/blocker/watchtower + name = "Watchtower Blocker" + icon = 'icons/obj/structures/barricades.dmi' + icon_state = "folding_0" // for map editing only + flags_atom = ON_BORDER + invisibility = INVISIBILITY_MAXIMUM + density = TRUE + opacity = FALSE // Unfortunately this doesn't behave as we'd want with ON_BORDER so we can't make tent opaque + throwpass = TRUE // Needs this so xenos can attack through the blocker and hit the tents or people inside + /// The watchtower this blocker relates to, will be destroyed along with it + var/obj/structure/watchtower/linked_watchtower + +/obj/structure/blocker/watchtower/Initialize(mapload, ...) + . = ..() + icon_state = null + linked_watchtower = locate(/obj/structure/watchtower) in loc + if(!linked_watchtower) + return INITIALIZE_HINT_QDEL + RegisterSignal(linked_watchtower, COMSIG_PARENT_QDELETING, PROC_REF(collapse)) + +/obj/structure/blocker/watchtower/Destroy(force) + . = ..() + linked_watchtower = null + +/obj/structure/blocker/watchtower/proc/collapse() + SIGNAL_HANDLER + qdel(src) + +/obj/structure/blocker/watchtower/initialize_pass_flags(datum/pass_flags_container/PF) + ..() + if (PF) + PF.flags_can_pass_all = NONE + PF.flags_can_pass_front = NONE + PF.flags_can_pass_behind = NONE + +/obj/structure/blocker/watchtower/get_projectile_hit_boolean(obj/projectile/P) + . = ..() + return FALSE // Always fly through the watchtower + +//Blocks all direction, basically an invisible wall +/obj/structure/blocker/watchtower/full_tile + flags_atom = NO_FLAGS + icon = 'icons/landmarks.dmi' + icon_state = "invisible_wall" diff --git a/code/modules/watchtower/watchtower.dm b/code/modules/watchtower/watchtower.dm new file mode 100644 index 000000000000..652fbaa9b298 --- /dev/null +++ b/code/modules/watchtower/watchtower.dm @@ -0,0 +1,434 @@ +#define WATCHTOWER_STAGE_WELDED 1 +#define WATCHTOWER_STAGE_COLUMNS 2 +#define WATCHTOWER_STAGE_HEIGHTNED_WELDER 2.5 +#define WATCHTOWER_STAGE_HEIGHTNED_WRENCH 3 +#define WATCHTOWER_STAGE_FLOOR 4 +#define WATCHTOWER_STAGE_BARRICADED 5 +#define WATCHTOWER_STAGE_ROOF_SUPPORT 6 +#define WATCHTOWER_STAGE_COMPLETE 7 + +/obj/structure/watchtower + name = "watchtower" + desc = "A watchtower used to view the area around and protect it." + icon = 'icons/obj/structures/watchtower.dmi' + icon_state = "stage1" + + density = FALSE + bound_width = 64 + bound_height = 96 + health = 1000 + layer = ABOVE_TURF_LAYER + var/max_health = 1000 + + var/stage = 1 + var/image/roof_image + +/obj/structure/watchtower/Initialize() + var/list/turf/blocked_turfs = CORNER_BLOCK(get_turf(src), 2, 1) + CORNER_BLOCK_OFFSET(get_turf(src), 2, 1, 0, 2) + + var/atom/west_blocker = new /obj/structure/blocker/watchtower(locate(x, y+1, z)) + var/atom/east_blocker = new /obj/structure/blocker/watchtower(locate(x+1, y+1, z)) + west_blocker.dir = WEST + east_blocker.dir = EAST + + for(var/turf/current_turf in blocked_turfs) + new /obj/structure/blocker/watchtower/full_tile(current_turf) + + update_icon() + + return ..() + +/obj/structure/watchtower/Destroy() + playsound(src, 'sound/effects/metal_crash.ogg', 50, 1) + var/list/turf/top_turfs = CORNER_BLOCK_OFFSET(get_turf(src), 2, 1, 0, 1) + + for(var/turf/current_turf in top_turfs) + for(var/mob/falling_mob in current_turf.contents) + falling_mob.ex_act(100, 0) + on_leave(falling_mob) + + new /obj/structure/girder(get_turf(src)) + new /obj/structure/girder/broken(locate(x+1, y, z)) + new /obj/structure/girder/broken(locate(x, y+1, z)) + new /obj/item/stack/sheet/metal(locate(x+1, y+1, z), 10) + new /obj/item/stack/rods(locate(x+1, y+1, z), 20) + + return ..() + +/obj/structure/watchtower/update_icon() + . = ..() + icon_state = "stage[stage]" + + overlays.Cut() + + if(stage >= WATCHTOWER_STAGE_BARRICADED) + overlays += image(icon=icon, icon_state="railings", layer=ABOVE_MOB_LAYER, pixel_y=25) + + if (stage == WATCHTOWER_STAGE_COMPLETE) + roof_image = image(icon=icon, icon_state="roof", layer=ABOVE_MOB_LAYER, pixel_y=51) + roof_image.plane = ROOF_PLANE + roof_image.appearance_flags = KEEP_APART + overlays += roof_image + +/obj/structure/watchtower/get_examine_text(mob/user) + . = ..() + switch(stage) + if(WATCHTOWER_STAGE_WELDED) + . += SPAN_NOTICE("Add 60 metal [SPAN_HELPFUL("rods")] to construct the connection rods.") + return + if(WATCHTOWER_STAGE_COLUMNS) + . += SPAN_NOTICE("Use a [SPAN_HELPFUL("welder")] to weld the connection rods to the frame.") + return + if(WATCHTOWER_STAGE_HEIGHTNED_WELDER) + . += SPAN_NOTICE("Use a [SPAN_HELPFUL("wrench")] to elevate the frame.") + return + if(WATCHTOWER_STAGE_HEIGHTNED_WRENCH) + . += SPAN_NOTICE("Use a [SPAN_HELPFUL("screwdriver")] and 50 [SPAN_HELPFUL("metal")] sheets to construct the platform.") + return + if(WATCHTOWER_STAGE_FLOOR) + . += SPAN_NOTICE("Use a [SPAN_HELPFUL("crowbar")] and 25 [SPAN_HELPFUL("plasteel")] sheets to construct [src] railings.") + return + if(WATCHTOWER_STAGE_BARRICADED) + . += SPAN_NOTICE("Use a [SPAN_HELPFUL("wrench")] and 60 metal [SPAN_HELPFUL("rods")] to construct [src] support rods.") + return + if(WATCHTOWER_STAGE_ROOF_SUPPORT) + . += SPAN_NOTICE("Use a [SPAN_HELPFUL("blowtorch")] and 25 [SPAN_HELPFUL("plasteel")] sheets to construct the roof.") + return + if(WATCHTOWER_STAGE_COMPLETE) + . += SPAN_NOTICE("Use a [SPAN_HELPFUL("blowtorch")] and [SPAN_HELPFUL("metal")] sheets to repair.") + return + +/obj/structure/watchtower/attackby(obj/item/item, mob/user) + if(user.action_busy) + return + + if(istool(item) && !skillcheck(user, SKILL_CONSTRUCTION, SKILL_CONSTRUCTION_ENGI)) + to_chat(user, SPAN_WARNING("You are not trained to configure [src]...")) + return TRUE + + switch(stage) + if(WATCHTOWER_STAGE_WELDED) + if(!istype(item, /obj/item/stack/rods)) + return + + var/obj/item/stack/rods/rods = item + + to_chat(user, SPAN_NOTICE("You start adding connection rods to [src].")) + playsound(loc, 'sound/items/Screwdriver.ogg', 25, 1) + + if(!do_after(user, 4 SECONDS * user.get_skill_duration_multiplier(SKILL_CONSTRUCTION), INTERRUPT_NO_NEEDHAND|BEHAVIOR_IMMOBILE, BUSY_ICON_FRIENDLY, src)) + return + + if(rods.use(60)) + to_chat(user, SPAN_NOTICE("You add connection rods to [src].")) + stage = WATCHTOWER_STAGE_COLUMNS + update_icon() + else + to_chat(user, SPAN_NOTICE("You failed to construct the connection rods. You need more rods.")) + + return + if(WATCHTOWER_STAGE_COLUMNS) + if(!iswelder(item)) + return + + if(!HAS_TRAIT(item, TRAIT_TOOL_BLOWTORCH)) + to_chat(user, SPAN_WARNING("You need a stronger blowtorch!")) + return + + to_chat(user, SPAN_NOTICE("You start welding the connection rods to the frame.")) + playsound(loc, 'sound/items/Welder.ogg', 25, 1) + + if(!do_after(user, 4 SECONDS * user.get_skill_duration_multiplier(SKILL_CONSTRUCTION), INTERRUPT_ALL|BEHAVIOR_IMMOBILE, BUSY_ICON_BUILD)) + return + + to_chat(user, SPAN_NOTICE("You weld the connection rods to the frame.")) + stage = WATCHTOWER_STAGE_HEIGHTNED_WELDER + + return + if(WATCHTOWER_STAGE_HEIGHTNED_WELDER) + if(!HAS_TRAIT(item, TRAIT_TOOL_WRENCH)) + return + + to_chat(user, SPAN_NOTICE("You start elevating the frame and screwing it up top.")) + playsound(loc, 'sound/items/Ratchet.ogg', 25, 1) + + if(!do_after(user, 4 SECONDS * user.get_skill_duration_multiplier(SKILL_CONSTRUCTION), INTERRUPT_ALL|BEHAVIOR_IMMOBILE, BUSY_ICON_BUILD)) + return + + to_chat(user, SPAN_NOTICE("You elevate the the frame and screw it up top.")) + stage = WATCHTOWER_STAGE_HEIGHTNED_WRENCH + update_icon() + + return + if(WATCHTOWER_STAGE_HEIGHTNED_WRENCH) + if(!HAS_TRAIT(item, TRAIT_TOOL_SCREWDRIVER)) + return + + var/obj/item/stack/sheet/metal/metal = user.get_inactive_hand() + if(!istype(metal)) + to_chat(user, SPAN_BOLDWARNING("You need metal sheets in your offhand to continue construction of [src].")) + return FALSE + + to_chat(user, SPAN_NOTICE("You start constructing [src] platform.")) + playsound(loc, 'sound/items/Screwdriver.ogg', 25, 1) + + if(!do_after(user, 4 SECONDS * user.get_skill_duration_multiplier(SKILL_CONSTRUCTION), INTERRUPT_ALL|BEHAVIOR_IMMOBILE, BUSY_ICON_BUILD)) + return + + if(metal.use(50)) + to_chat(user, SPAN_NOTICE("You construct [src] platform.")) + stage = WATCHTOWER_STAGE_FLOOR + update_icon() + else + to_chat(user, SPAN_NOTICE("You failed to construct [src] platform, you need more metal sheets in your offhand.")) + + return + if(WATCHTOWER_STAGE_FLOOR) + if(!HAS_TRAIT(item, TRAIT_TOOL_CROWBAR)) + return + + var/obj/item/stack/sheet/plasteel/plasteel = user.get_inactive_hand() + if(!istype(plasteel)) + to_chat(user, SPAN_BOLDWARNING("You need plasteel sheets in your offhand to continue construction of [src].")) + return FALSE + + to_chat(user, SPAN_NOTICE("You start constructing [src] railing.")) + playsound(loc, 'sound/items/Crowbar.ogg', 25, 1) + + if(!do_after(user, 4 SECONDS * user.get_skill_duration_multiplier(SKILL_CONSTRUCTION), INTERRUPT_ALL|BEHAVIOR_IMMOBILE, BUSY_ICON_BUILD)) + return + + if(plasteel.use(25)) + to_chat(user, SPAN_NOTICE("You construct [src] railing.")) + stage = WATCHTOWER_STAGE_BARRICADED + update_icon() + else + to_chat(user, SPAN_NOTICE("You failed to construct [src] railing, you need more plasteel sheets in your offhand.")) + + return + if(WATCHTOWER_STAGE_BARRICADED) + if (!HAS_TRAIT(item, TRAIT_TOOL_WRENCH)) + return + + var/obj/item/stack/rods/rods = user.get_inactive_hand() + if(!istype(rods)) + to_chat(user, SPAN_BOLDWARNING("You need metal rods in your offhand to continue construction of [src].")) + return FALSE + + to_chat(user, SPAN_NOTICE("You start constructing [src] support rods.")) + playsound(loc, 'sound/items/Ratchet.ogg', 25, 1) + + if(!do_after(user, 4 SECONDS * user.get_skill_duration_multiplier(SKILL_CONSTRUCTION), INTERRUPT_ALL|BEHAVIOR_IMMOBILE, BUSY_ICON_BUILD)) + return + + if(rods.use(60)) + to_chat(user, SPAN_NOTICE("You construct [src] support rods.")) + stage = WATCHTOWER_STAGE_ROOF_SUPPORT + update_icon() + else + to_chat(user, SPAN_NOTICE("You failed to construct [src] support rods, you need more metal rods in your offhand.")) + + return + if(WATCHTOWER_STAGE_ROOF_SUPPORT) + if (!iswelder(item)) + return + + if(!HAS_TRAIT(item, TRAIT_TOOL_BLOWTORCH)) + to_chat(user, SPAN_WARNING("You need a stronger blowtorch!")) + return + + var/obj/item/stack/sheet/plasteel/plasteel = user.get_inactive_hand() + if(!istype(plasteel)) + to_chat(user, SPAN_BOLDWARNING("You need plasteel sheets in your offhand to continue construction of [src].")) + return FALSE + + to_chat(user, SPAN_NOTICE("You start completing [src].")) + playsound(loc, 'sound/items/Welder.ogg', 25, 1) + + if(!do_after(user, 4 SECONDS * user.get_skill_duration_multiplier(SKILL_CONSTRUCTION), INTERRUPT_ALL|BEHAVIOR_IMMOBILE, BUSY_ICON_BUILD)) + return + + if(plasteel.use(25)) + to_chat(user, SPAN_NOTICE("You complete [src].")) + stage = WATCHTOWER_STAGE_COMPLETE + update_icon() + else + to_chat(user, SPAN_NOTICE("You failed to complete [src], you need more plasteel sheets in your offhand.")) + + return + if(WATCHTOWER_STAGE_COMPLETE) + if (!iswelder(item)) + return + + if(!HAS_TRAIT(item, TRAIT_TOOL_BLOWTORCH)) + to_chat(user, SPAN_WARNING("You need a stronger blowtorch!")) + return + + var/obj/item/stack/sheet/metal/metal = user.get_inactive_hand() + if(!istype(metal)) + to_chat(user, SPAN_BOLDWARNING("You need metal sheets in your offhand to patch [src].")) + return + + if(health >= max_health) + to_chat(user, SPAN_NOTICE("[src] is in good condition.")) + return + + to_chat(user, SPAN_NOTICE("You start patching [src] with the metal sheets.")) + playsound(loc, 'sound/items/Welder.ogg', 25, 1) + + if(!do_after(user, 4 SECONDS * user.get_skill_duration_multiplier(SKILL_CONSTRUCTION), INTERRUPT_ALL|BEHAVIOR_IMMOBILE, BUSY_ICON_BUILD)) + return + + if(metal.use(5)) + to_chat(user, SPAN_NOTICE("You patch [src] with the metal sheets.")) + update_health(-50) + else + to_chat(user, SPAN_NOTICE("You failed to patch [src], you need more metal sheets in your offhand.")) + +/obj/structure/watchtower/get_examine_text(mob/user) + . = ..() + + var/dam = health / max_health + + if(dam <= 0.3) + . += SPAN_WARNING("It looks heavily damaged.") + else if(dam <= 0.6) + . += SPAN_WARNING("It looks moderately damaged.") + else if (dam < 1) + . += SPAN_DANGER("It looks slightly damaged.") + + +/obj/structure/watchtower/attack_hand(mob/user) + if (stage < WATCHTOWER_STAGE_COMPLETE) + return + + if(get_turf(user) == locate(x, y-1, z)) + var/people_on_watchtower = 0 + + for(var/turf/current_turf in CORNER_BLOCK_OFFSET(src, 2, 1, 0, 1)) + for(var/mob/mob in current_turf.contents) + if(mob.stat != DEAD) + people_on_watchtower++ + + if(people_on_watchtower >= 2) + to_chat(user, SPAN_NOTICE("[src] is too crowded!")) + return + + if(!do_after(user, 3 SECONDS, INTERRUPT_ALL|BEHAVIOR_IMMOBILE, BUSY_ICON_BUILD)) + return + + + var/turf/actual_turf = locate(x, y+1, z) + if(people_on_watchtower < 1 && user.pulling) + user.pulling.forceMove(actual_turf) + on_enter(user.pulling) + + user.forceMove(actual_turf) + on_enter(user) + + + else if(get_turf(user) == locate(x, y+1, z)) + if(!do_after(user, 3 SECONDS, INTERRUPT_ALL|BEHAVIOR_IMMOBILE, BUSY_ICON_BUILD)) + return + + var/turf/actual_turf = locate(x, y-1, z) + if(user.pulling) + on_leave(user.pulling) + user.pulling.forceMove(actual_turf) + + user.forceMove(actual_turf) + on_leave(user) + +/obj/structure/watchtower/proc/on_enter(mob/user) + ADD_TRAIT(user, TRAIT_ON_WATCHTOWER, "watchtower") + if(user.client) + user.client.change_view(user.client.view + 2) + var/atom/movable/screen/plane_master/roof/roof_plane = user.hud_used?.plane_masters["[ROOF_PLANE]"] + roof_plane?.invisibility = INVISIBILITY_MAXIMUM + add_trait_to_all_guns(user) + RegisterSignal(user, COMSIG_ITEM_PICKUP, PROC_REF(item_picked_up)) + RegisterSignal(user, COMSIG_LIVING_ZOOM_OUT, PROC_REF(on_unzoom)) + + if(isxeno(user)) + RegisterSignal(user, COMSIG_XENO_ENTER_TUNNEL, PROC_REF(on_tunnel)) + +/obj/structure/watchtower/proc/on_unzoom(mob/user) + SIGNAL_HANDLER + if(user.client) + user.client.change_view(user.client.view + 2) + +/obj/structure/watchtower/proc/on_tunnel() + SIGNAL_HANDLER + return COMPONENT_CANCEL_TUNNEL + +/obj/structure/watchtower/proc/on_leave(mob/user) + REMOVE_TRAIT(user, TRAIT_ON_WATCHTOWER, "watchtower") + if(user.client) + user.client.change_view(max(user.client.view - 2, 7)) + var/atom/movable/screen/plane_master/roof/roof_plane = user.hud_used?.plane_masters["[ROOF_PLANE]"] + roof_plane?.invisibility = 0 + UnregisterSignal(user, COMSIG_ITEM_PICKUP) + UnregisterSignal(user, COMSIG_LIVING_ZOOM_OUT) + + if(isxeno(user)) + UnregisterSignal(user, COMSIG_XENO_ENTER_TUNNEL) + +/obj/structure/watchtower/proc/add_trait_to_all_guns(mob/user) + for(var/obj/item/weapon/gun/gun in user) + gun.add_bullet_traits(list(BULLET_TRAIT_ENTRY_ID("watchtower_arc", /datum/element/bullet_trait_direct_only/watchtower))) + + for(var/obj/item/storage/storage in user) + for(var/obj/item/weapon/gun/gun in storage.contents) + gun.add_bullet_traits(list(BULLET_TRAIT_ENTRY_ID("watchtower_arc", /datum/element/bullet_trait_direct_only/watchtower))) + +/obj/structure/watchtower/proc/item_picked_up(obj/item/picked_up_item, mob/living/carbon/human/user) + SIGNAL_HANDLER + if(!istype(picked_up_item, /obj/item/weapon/gun)) + return + + var/obj/item/weapon/gun/gun = picked_up_item + gun.add_bullet_traits(list(BULLET_TRAIT_ENTRY_ID("watchtower_arc", /datum/element/bullet_trait_direct_only/watchtower))) + +/obj/structure/watchtower/attack_alien(mob/living/carbon/xenomorph/xeno) + if(get_turf(xeno) == locate(x, y-1, z) && xeno.a_intent != INTENT_HARM && xeno.mob_size < MOB_SIZE_BIG && stage >= WATCHTOWER_STAGE_COMPLETE) + if(!do_after(xeno, 3 SECONDS, INTERRUPT_ALL|BEHAVIOR_IMMOBILE, BUSY_ICON_HOSTILE)) + return + + var/turf/actual_turf = locate(x, y+1, z) + xeno.forceMove(actual_turf) + on_enter(xeno) + else if(get_turf(xeno) == locate(x, y+1, z) && xeno.a_intent != INTENT_HARM && xeno.mob_size < MOB_SIZE_BIG && stage >= WATCHTOWER_STAGE_COMPLETE) + if(!do_after(xeno, 3 SECONDS, INTERRUPT_ALL|BEHAVIOR_IMMOBILE, BUSY_ICON_HOSTILE)) + return + + var/turf/actual_turf = locate(x, y-1, z) + xeno.forceMove(actual_turf) + on_leave(xeno) + else + xeno.animation_attack_on(src) + playsound(src, "alien_claw_metal", 25, TRUE) + update_health(rand(xeno.melee_damage_lower, xeno.melee_damage_upper)) + return XENO_ATTACK_ACTION + +// For Mappers +/obj/structure/watchtower/stage1 + stage = WATCHTOWER_STAGE_WELDED + icon_state = "stage1" +/obj/structure/watchtower/stage2 + stage = WATCHTOWER_STAGE_COLUMNS + icon_state = "stage2" +/obj/structure/watchtower/stage3 + stage = WATCHTOWER_STAGE_HEIGHTNED_WRENCH + icon_state = "stage3" +/obj/structure/watchtower/stage4 + stage = WATCHTOWER_STAGE_FLOOR + icon_state = "stage4" +/obj/structure/watchtower/stage5 + stage = WATCHTOWER_STAGE_BARRICADED + icon_state = "stage5" +/obj/structure/watchtower/stage6 + stage = WATCHTOWER_STAGE_ROOF_SUPPORT + icon_state = "stage6" +/obj/structure/watchtower/complete + stage = WATCHTOWER_STAGE_COMPLETE + icon_state = "stage7" diff --git a/colonialmarines.dme b/colonialmarines.dme index b49e016f633e..1d60a5a03137 100644 --- a/colonialmarines.dme +++ b/colonialmarines.dme @@ -509,6 +509,7 @@ #include "code\datums\elements\suturing.dm" #include "code\datums\elements\yautja_tracked_item.dm" #include "code\datums\elements\bullet_trait\damage_boost.dm" +#include "code\datums\elements\bullet_trait\direct_only.dm" #include "code\datums\elements\bullet_trait\iff.dm" #include "code\datums\elements\bullet_trait\ignored_range.dm" #include "code\datums\elements\bullet_trait\incendiary.dm" @@ -2582,6 +2583,8 @@ #include "code\modules\vox\vox_tgui.dm" #include "code\modules\vox\vox_sounds\vox.dm" #include "code\modules\vox\vox_sounds\vox_military.dm" +#include "code\modules\watchtower\blockers.dm" +#include "code\modules\watchtower\watchtower.dm" #include "interface\fonts.dm" #include "interface\interface.dm" #include "interface\skin.dmf" diff --git a/icons/obj/structures/watchtower.dmi b/icons/obj/structures/watchtower.dmi new file mode 100644 index 000000000000..1aab83e0afee Binary files /dev/null and b/icons/obj/structures/watchtower.dmi differ