diff --git a/code/__DEFINES/dcs/signals/atom/signals_obj.dm b/code/__DEFINES/dcs/signals/atom/signals_obj.dm
index c870a55ed746..50a08e418fa8 100644
--- a/code/__DEFINES/dcs/signals/atom/signals_obj.dm
+++ b/code/__DEFINES/dcs/signals/atom/signals_obj.dm
@@ -30,6 +30,14 @@
/// from /obj/proc/afterbuckle()
#define COSMIG_OBJ_AFTER_BUCKLE "signal_obj_after_buckle"
+/// from /datum/cm_objective/retrieve_data/disk/process()
+#define COMSIG_INTEL_DISK_LOST_POWER "intel_disk_lost_power"
+
+/// from /datum/cm_objective/retrieve_data/disk/complete()
+#define COMSIG_INTEL_DISK_COMPLETED "intel_disk_completed"
+
+/// from /obj/vehicle/multitile/arc/toggle_antenna()
+#define COMSIG_ARC_ANTENNA_TOGGLED "arc_antenna_toggled"
/// from /obj/structure/machinery/cryopod/go_out()
#define COMSIG_CRYOPOD_GO_OUT "cryopod_go_out"
diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm
index 8d77920a59cc..71fc9162dcaa 100644
--- a/code/_onclick/item_attack.dm
+++ b/code/_onclick/item_attack.dm
@@ -16,7 +16,8 @@
return FALSE
/atom/movable/attackby(obj/item/W, mob/living/user)
- if(W)
+ . = ..()
+ if(W && !.)
if(!(W.flags_item & NOBLUDGEON))
visible_message(SPAN_DANGER("[src] has been hit by [user] with [W]."), null, null, 5, CHAT_TYPE_MELEE_HIT)
user.animation_attack_on(src)
diff --git a/code/datums/ammo/bullet/arc.dm b/code/datums/ammo/bullet/arc.dm
new file mode 100644
index 000000000000..5e74508e04b2
--- /dev/null
+++ b/code/datums/ammo/bullet/arc.dm
@@ -0,0 +1,14 @@
+/datum/ammo/bullet/re700
+ name = "rotary cannon bullet"
+ icon_state = "autocannon"
+ damage_falloff = 0
+ flags_ammo_behavior = AMMO_BALLISTIC
+
+ accuracy = HIT_ACCURACY_TIER_7
+ scatter = 0
+ damage = 30
+ damage_var_high = PROJECTILE_VARIANCE_TIER_8
+ penetration = ARMOR_PENETRATION_TIER_2
+ accurate_range = 10
+ max_range = 12
+ shell_speed = AMMO_SPEED_TIER_6
diff --git a/code/datums/components/disk_reader.dm b/code/datums/components/disk_reader.dm
new file mode 100644
index 000000000000..2b74ec35423b
--- /dev/null
+++ b/code/datums/components/disk_reader.dm
@@ -0,0 +1,87 @@
+/datum/component/disk_reader
+ dupe_mode = COMPONENT_DUPE_UNIQUE
+ /// Ref to the inserted disk
+ var/obj/item/disk/objective/disk
+
+/datum/component/disk_reader/Initialize()
+ . = ..()
+ if(!isatom(parent))
+ return COMPONENT_INCOMPATIBLE
+
+/datum/component/disk_reader/Destroy(force, silent)
+ handle_qdel()
+ return ..()
+
+/datum/component/disk_reader/RegisterWithParent()
+ ..()
+ RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, PROC_REF(on_disk_insert))
+ RegisterSignal(parent, COMSIG_PARENT_QDELETING, PROC_REF(handle_qdel))
+ RegisterSignal(parent, COMSIG_INTEL_DISK_COMPLETED, PROC_REF(on_disk_complete))
+ RegisterSignal(parent, COMSIG_INTEL_DISK_LOST_POWER, PROC_REF(on_power_lost))
+
+/datum/component/disk_reader/UnregisterFromParent()
+ ..()
+ handle_qdel()
+
+/datum/component/disk_reader/proc/handle_qdel()
+ SIGNAL_HANDLER
+ QDEL_NULL(disk)
+
+/datum/component/disk_reader/proc/on_disk_insert(datum/source, obj/item/disk/objective/potential_disk, mob/living/inserter, params)
+ SIGNAL_HANDLER
+
+ if(!istype(potential_disk) || !potential_disk.objective)
+ return
+
+ if(disk)
+ to_chat(inserter, SPAN_WARNING("There's already a disk inside [parent], wait for it to finish first!"))
+ return COMPONENT_NO_AFTERATTACK
+
+ if(potential_disk.objective.state == OBJECTIVE_COMPLETE)
+ to_chat(inserter, SPAN_WARNING("The reader displays a message stating this disk has already been read and refuses to accept it."))
+ return COMPONENT_NO_AFTERATTACK
+
+ INVOKE_ASYNC(src, PROC_REF(handle_disk_insert), potential_disk, inserter)
+ return COMPONENT_NO_AFTERATTACK
+
+/datum/component/disk_reader/proc/handle_disk_insert(obj/item/disk/objective/potential_disk, mob/living/inserter)
+ if(tgui_input_text(inserter, "Enter the encryption key", "Decrypting [potential_disk]", "") != potential_disk.objective.decryption_password)
+ to_chat(inserter, SPAN_WARNING("The reader buzzes, ejecting the disk."))
+ return
+
+ if(disk)
+ to_chat(inserter, SPAN_WARNING("There's already a disk inside [parent], wait for it to finish first!"))
+ return
+
+ if(!(potential_disk in inserter.contents))
+ return
+
+ potential_disk.objective.activate()
+
+ inserter.drop_inv_item_to_loc(potential_disk, parent)
+ disk = potential_disk
+ to_chat(inserter, SPAN_NOTICE("You insert [potential_disk] and enter the decryption key."))
+ inserter.count_niche_stat(STATISTICS_NICHE_DISK)
+
+/datum/component/disk_reader/proc/on_disk_complete(datum/source)
+ SIGNAL_HANDLER
+ var/atom/atom_parent = parent
+
+ atom_parent.visible_message("[atom_parent] pings softly as the upload finishes and ejects [disk].")
+ playsound(atom_parent, 'sound/machines/screen_output1.ogg', 25, 1)
+ disk.forceMove(get_turf(atom_parent))
+ disk.name = "[disk.name] (complete)"
+ disk.objective.award_points()
+ disk.retrieve_objective.state = OBJECTIVE_ACTIVE
+ disk.retrieve_objective.activate()
+ disk = null
+
+/datum/component/disk_reader/proc/on_power_lost(datum/source)
+ SIGNAL_HANDLER
+ var/atom/atom_parent = parent
+
+ atom_parent.visible_message(SPAN_WARNING("\The [atom_parent] powers down mid-operation as the area loses power."))
+ playsound(atom_parent, 'sound/machines/terminal_shutdown.ogg', 25, 1)
+ SSobjectives.stop_processing_objective(src)
+ disk.forceMove(get_turf(atom_parent))
+ disk = null
diff --git a/code/datums/elements/bullet_trait/iff.dm b/code/datums/elements/bullet_trait/iff.dm
index ab48b29f4812..cee36acbed80 100644
--- a/code/datums/elements/bullet_trait/iff.dm
+++ b/code/datums/elements/bullet_trait/iff.dm
@@ -46,7 +46,7 @@
// The cache is reset when the user drops their ID
/datum/element/bullet_trait_iff/proc/get_user_iff_group(mob/living/carbon/human/user)
if(!ishuman(user))
- return user.faction_group
+ return user?.faction_group
var/iff_group = LAZYACCESS(iff_group_cache, user)
if(isnull(iff_group))
diff --git a/code/datums/skills/uscm.dm b/code/datums/skills/uscm.dm
index 8a6d2fd2c8c2..323a7270f33b 100644
--- a/code/datums/skills/uscm.dm
+++ b/code/datums/skills/uscm.dm
@@ -239,7 +239,8 @@ COMMAND STAFF
SKILL_JTAC = SKILL_JTAC_MASTER,
SKILL_SPEC_WEAPONS = SKILL_SPEC_ALL,
SKILL_EXECUTION = SKILL_EXECUTION_TRAINED, //can BE people
- SKILL_INTEL = SKILL_INTEL_EXPERT
+ SKILL_INTEL = SKILL_INTEL_EXPERT,
+ SKILL_VEHICLE = SKILL_VEHICLE_LARGE,
)
/datum/skills/commander
@@ -261,7 +262,8 @@ COMMAND STAFF
SKILL_JTAC = SKILL_JTAC_MASTER,
SKILL_EXECUTION = SKILL_EXECUTION_TRAINED, //can BE people
SKILL_INTEL = SKILL_INTEL_EXPERT,
- SKILL_NAVIGATIONS = SKILL_NAVIGATIONS_TRAINED //can change ship alt
+ SKILL_NAVIGATIONS = SKILL_NAVIGATIONS_TRAINED, //can change ship alt
+ SKILL_VEHICLE = SKILL_VEHICLE_LARGE,
)
/datum/skills/XO
@@ -282,6 +284,7 @@ COMMAND STAFF
SKILL_JTAC = SKILL_JTAC_MASTER,
SKILL_INTEL = SKILL_INTEL_EXPERT,
SKILL_NAVIGATIONS = SKILL_NAVIGATIONS_TRAINED,
+ SKILL_VEHICLE = SKILL_VEHICLE_LARGE,
)
/datum/skills/SO
@@ -298,6 +301,7 @@ COMMAND STAFF
SKILL_POWERLOADER = SKILL_POWERLOADER_TRAINED,
SKILL_JTAC = SKILL_JTAC_EXPERT,
SKILL_INTEL = SKILL_INTEL_TRAINED,
+ SKILL_VEHICLE = SKILL_VEHICLE_LARGE,
)
/datum/skills/SEA
diff --git a/code/datums/supply_packs/vehicle_ammo.dm b/code/datums/supply_packs/vehicle_ammo.dm
index 5dad91d27ed4..43ce36ec2b64 100644
--- a/code/datums/supply_packs/vehicle_ammo.dm
+++ b/code/datums/supply_packs/vehicle_ammo.dm
@@ -148,3 +148,15 @@
containertype = /obj/structure/closet/crate/ammo
containername = "M-87F Flare Launcher ammo crate"
group = "Vehicle Ammo"
+
+/datum/supply_packs/ammo_arcsentry
+ name = "RE700 Rotary Cannon magazines (x3)"
+ contains = list(
+ /obj/item/ammo_magazine/hardpoint/arc_sentry,
+ /obj/item/ammo_magazine/hardpoint/arc_sentry,
+ /obj/item/ammo_magazine/hardpoint/arc_sentry,
+ )
+ cost = 20
+ containertype = /obj/structure/closet/crate/ammo
+ containername = "RE700 Rotary Cannon ammo crate"
+ group = "Vehicle Ammo"
diff --git a/code/datums/supply_packs/vehicle_equipment.dm b/code/datums/supply_packs/vehicle_equipment.dm
new file mode 100644
index 000000000000..df106761d467
--- /dev/null
+++ b/code/datums/supply_packs/vehicle_equipment.dm
@@ -0,0 +1,9 @@
+/datum/supply_packs/arcsentry_replacement
+ name = "Replacement RE700 Rotary Cannon (x1)"
+ contains = list(
+ /obj/item/hardpoint/primary/arc_sentry,
+ )
+ cost = 25
+ containertype = /obj/structure/closet/crate/weapon
+ containername = "RE700 Rotary Cannon crate"
+ group = "Vehicle Equipment"
diff --git a/code/datums/vehicles.dm b/code/datums/vehicles.dm
index 36ac96938c6b..67070dd04c0b 100644
--- a/code/datums/vehicles.dm
+++ b/code/datums/vehicles.dm
@@ -37,3 +37,7 @@
/datum/map_template/interior/van
name = "Van"
interior_id = "van"
+
+/datum/map_template/interior/arc
+ name = "ARC"
+ interior_id = "arc"
diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm
index 21f7b6b0a9be..52a35b715b1a 100644
--- a/code/game/atoms_movable.dm
+++ b/code/game/atoms_movable.dm
@@ -45,7 +45,10 @@
if(orbiting)
orbiting.end_orbit(src)
orbiting = null
- vis_contents.Cut()
+
+ vis_locs = null //clears this atom out of all viscontents
+ if(length(vis_contents))
+ vis_contents.Cut()
. = ..()
moveToNullspace() //so we move into null space. Must be after ..() b/c atom's Dispose handles deleting our lighting stuff
diff --git a/code/game/machinery/computer/computer.dm b/code/game/machinery/computer/computer.dm
index c33517796271..f6efe6edb5e2 100644
--- a/code/game/machinery/computer/computer.dm
+++ b/code/game/machinery/computer/computer.dm
@@ -126,7 +126,7 @@
src.attack_alien(user)
return
src.attack_hand(user)
- return
+ return ..()
/obj/structure/machinery/computer/attack_hand()
. = ..()
diff --git a/code/game/machinery/vending/vendor_types/crew/vehicle_crew.dm b/code/game/machinery/vending/vendor_types/crew/vehicle_crew.dm
index 6877c2b4b5b3..0586f4b72fa5 100644
--- a/code/game/machinery/vending/vendor_types/crew/vehicle_crew.dm
+++ b/code/game/machinery/vending/vendor_types/crew/vehicle_crew.dm
@@ -76,6 +76,9 @@
else
display_list = GLOB.cm_vending_vehicle_crew_tank_spare
+ else if(selected_vehicle == "ARC")
+ display_list = GLOB.cm_vending_vehicle_crew_arc
+
else if(selected_vehicle == "APC")
if(available_categories)
display_list = GLOB.cm_vending_vehicle_crew_apc
@@ -245,6 +248,11 @@ GLOBAL_LIST_INIT(cm_vending_vehicle_crew_apc_spare, list(
list("WHEELS", 0, null, null, null),
list("APC Wheels", 200, /obj/item/hardpoint/locomotion/apc_wheels, null, VENDOR_ITEM_REGULAR)))
+GLOBAL_LIST_INIT(cm_vending_vehicle_crew_arc, list(
+ list("STARTING KIT SELECTION:", 0, null, null, null),
+
+ list("WHEELS", 0, null, null, null),
+ list("Replacement ARC Wheels", 0, /obj/item/hardpoint/locomotion/arc_wheels, VEHICLE_TREADS_AVAILABLE, VENDOR_ITEM_MANDATORY)))
//------------WEAPONS RACK---------------
diff --git a/code/game/supplyshuttle.dm b/code/game/supplyshuttle.dm
index c4698e5722c5..d7e552133206 100644
--- a/code/game/supplyshuttle.dm
+++ b/code/game/supplyshuttle.dm
@@ -410,6 +410,7 @@ GLOBAL_DATUM_INIT(supply_controller, /datum/controller/supply, new())
"Operations",
"Weapons",
"Vehicle Ammo",
+ "Vehicle Equipment",
"Attachments",
"Ammo",
"Weapons Specialist Ammo",
@@ -1365,6 +1366,13 @@ GLOBAL_DATUM_INIT(supply_controller, /datum/controller/supply, new())
name = "Barebones M577 Armored Personal Carrier"
ordered_vehicle = /obj/effect/vehicle_spawner/apc/unarmed/broken
+/datum/vehicle_order/arc
+ name = "M540 Armored Recon Carrier"
+ ordered_vehicle = /obj/effect/vehicle_spawner/arc
+
+/datum/vehicle_order/arc/has_vehicle_lock()
+ return
+
/obj/structure/machinery/computer/supplycomp/vehicle/Initialize()
. = ..()
diff --git a/code/modules/almayer/machinery.dm b/code/modules/almayer/machinery.dm
index 74ce9a81eb88..9411c229d2f3 100644
--- a/code/modules/almayer/machinery.dm
+++ b/code/modules/almayer/machinery.dm
@@ -73,7 +73,7 @@
/obj/structure/machinery/prop/almayer/CICmap
name = "map table"
- desc = "A table that displays a map of the current target location"
+ desc = "A table that displays a map of the current operation location."
icon = 'icons/obj/structures/machinery/computer.dmi'
icon_state = "maptable"
anchored = TRUE
@@ -103,6 +103,11 @@
map.tgui_interact(user)
+/obj/structure/machinery/prop/almayer/CICmap/computer
+ name = "map terminal"
+ desc = "A terminal that displays a map of the current operation location."
+ icon_state = "security"
+
/obj/structure/machinery/prop/almayer/CICmap/upp
minimap_type = MINIMAP_FLAG_UPP
faction = FACTION_UPP
diff --git a/code/modules/cm_marines/overwatch.dm b/code/modules/cm_marines/overwatch.dm
index e68ef467faa9..0fbfca556498 100644
--- a/code/modules/cm_marines/overwatch.dm
+++ b/code/modules/cm_marines/overwatch.dm
@@ -865,6 +865,9 @@
/obj/structure/machinery/computer/overwatch/almayer/broken
name = "Broken Overwatch Console"
+/obj/structure/machinery/computer/overwatch/almayer/small
+ icon_state = "engineering_terminal"
+
/obj/structure/machinery/computer/overwatch/clf
faction = FACTION_CLF
/obj/structure/machinery/computer/overwatch/upp
diff --git a/code/modules/cm_tech/techs/marine/tier1/arc.dm b/code/modules/cm_tech/techs/marine/tier1/arc.dm
new file mode 100644
index 000000000000..1ea5a0afb6c1
--- /dev/null
+++ b/code/modules/cm_tech/techs/marine/tier1/arc.dm
@@ -0,0 +1,40 @@
+/datum/tech/arc
+ name = "M540 Armored Recon Carrier"
+ desc = "Purchase an M540 Armored Recon Carrier, specialized in assisting groundside command. Able to be driven by Staff Officers, Executive Officers, and Commanding Officers."
+ icon_state = "upgrade"
+
+ required_points = 5
+
+ tier = /datum/tier/one
+
+ announce_name = "M540 ARC ACQUIRED"
+ announce_message = "An M540 Armored Recon Carrier has been authorized and will be delivered in the vehicle bay."
+
+ flags = TREE_FLAG_MARINE
+
+/datum/tech/arc/on_unlock()
+ . = ..()
+
+ var/obj/structure/machinery/computer/supplycomp/vehicle/comp = GLOB.VehicleElevatorConsole
+ var/obj/structure/machinery/cm_vending/gear/vehicle_crew/gearcomp = GLOB.VehicleGearConsole
+
+ if(!comp || !gearcomp)
+ return FALSE
+
+ comp.spent = FALSE
+ QDEL_NULL_LIST(comp.vehicles)
+ comp.vehicles = list(
+ new /datum/vehicle_order/arc()
+ )
+ comp.allowed_roles = list(JOB_SYNTH, JOB_SEA, JOB_SO, JOB_XO, JOB_CO, JOB_GENERAL)
+ comp.req_access = list(ACCESS_MARINE_COMMAND)
+ comp.req_one_access = list()
+ comp.spent = FALSE
+
+ gearcomp.req_access = list(ACCESS_MARINE_COMMAND)
+ gearcomp.req_one_access = list()
+ gearcomp.vendor_role = list()
+ gearcomp.selected_vehicle = "ARC"
+ gearcomp.available_categories = VEHICLE_ALL_AVAILABLE
+
+ return TRUE
diff --git a/code/modules/mob/living/carbon/human/human_abilities.dm b/code/modules/mob/living/carbon/human/human_abilities.dm
index a568e93df5c0..2d7f472952cc 100644
--- a/code/modules/mob/living/carbon/human/human_abilities.dm
+++ b/code/modules/mob/living/carbon/human/human_abilities.dm
@@ -605,3 +605,26 @@ CULT
var/mob/living/carbon/human/human_user = owner
SEND_SIGNAL(human_user, COMSIG_MOB_MG_EXIT)
+
+/datum/action/human_action/toggle_arc_antenna
+ name = "Toggle Sensor Antenna"
+ action_icon_state = "recoil_compensation"
+
+/datum/action/human_action/toggle_arc_antenna/give_to(mob/user)
+ . = ..()
+ RegisterSignal(user, COMSIG_MOB_RESET_VIEW, PROC_REF(remove_from))
+
+/datum/action/human_action/toggle_arc_antenna/remove_from(mob/user)
+ . = ..()
+ UnregisterSignal(user, COMSIG_MOB_RESET_VIEW)
+
+/datum/action/human_action/toggle_arc_antenna/action_activate()
+ if(!can_use_action())
+ return
+
+ var/mob/living/carbon/human/human_user = owner
+ if(istype(human_user.buckled, /obj/structure/bed/chair/comfy/vehicle))
+ var/obj/structure/bed/chair/comfy/vehicle/vehicle_chair = human_user.buckled
+ if(istype(vehicle_chair.vehicle, /obj/vehicle/multitile/arc))
+ var/obj/vehicle/multitile/arc/vehicle = vehicle_chair.vehicle
+ vehicle.toggle_antenna(human_user)
diff --git a/code/modules/objectives/data_retrieval.dm b/code/modules/objectives/data_retrieval.dm
index f66c578f48fb..ee5053f26c4b 100644
--- a/code/modules/objectives/data_retrieval.dm
+++ b/code/modules/objectives/data_retrieval.dm
@@ -117,29 +117,14 @@
/datum/cm_objective/retrieve_data/disk/process()
var/obj/structure/machinery/computer/disk_reader/reader = disk.loc
if(!reader.powered())
- reader.visible_message(SPAN_WARNING("\The [reader] powers down mid-operation as the area looses power."))
- playsound(reader, 'sound/machines/terminal_shutdown.ogg', 25, 1)
- SSobjectives.stop_processing_objective(src)
- disk.forceMove(reader.loc)
- reader.disk = null
+ SEND_SIGNAL(reader, COMSIG_INTEL_DISK_LOST_POWER)
return
..()
/datum/cm_objective/retrieve_data/disk/complete()
state = OBJECTIVE_COMPLETE
- var/obj/structure/machinery/computer/disk_reader/reader = disk.loc
- reader.visible_message("\The [reader] pings softly as the upload finishes and ejects the disk.")
- playsound(reader, 'sound/machines/screen_output1.ogg', 25, 1)
- disk.forceMove(reader.loc)
- disk.name = "[disk.name] (complete)"
- reader.disk = null
- award_points()
-
- // Now enable the objective to store this disk in the lab.
- disk.retrieve_objective.state = OBJECTIVE_ACTIVE
- disk.retrieve_objective.activate()
-
+ SEND_SIGNAL(disk.loc, COMSIG_INTEL_DISK_COMPLETED)
..()
/datum/cm_objective/retrieve_data/disk/get_tgui_data()
@@ -295,34 +280,6 @@
unslashable = TRUE
unacidable = TRUE
-/obj/structure/machinery/computer/disk_reader/attack_hand(mob/living/user)
- if(isxeno(user))
- return
- if(disk)
- to_chat(user, SPAN_NOTICE("[disk] is currently being uploaded to ARES."))
-
-/obj/structure/machinery/computer/disk_reader/attackby(obj/item/W, mob/living/user)
- if(istype(W, /obj/item/disk/objective))
- if(istype(disk))
- to_chat(user, SPAN_WARNING("There is a disk in the drive being uploaded already!"))
- return FALSE
- var/obj/item/disk/objective/newdisk = W
- if(newdisk.objective.state == OBJECTIVE_COMPLETE)
- to_chat(user, SPAN_WARNING("The reader displays a message stating this disk has already been read and refuses to accept it."))
- return FALSE
- if(input(user,"Enter the encryption key","Decrypting [newdisk]","") != newdisk.objective.decryption_password)
- to_chat(user, SPAN_WARNING("The reader asks for the encryption key for this disk, not having the correct key you eject the disk."))
- return FALSE
- if(istype(disk))
- to_chat(user, SPAN_WARNING("There is a disk in the drive being uploaded already!"))
- return FALSE
-
- if(!(newdisk in user.contents))
- return FALSE
-
- newdisk.objective.activate()
-
- user.drop_inv_item_to_loc(W, src)
- disk = W
- to_chat(user, SPAN_NOTICE("You insert \the [W] and enter the decryption key."))
- user.count_niche_stat(STATISTICS_NICHE_DISK)
+/obj/structure/machinery/computer/disk_reader/Initialize()
+ . = ..()
+ AddComponent(/datum/component/disk_reader)
diff --git a/code/modules/objectives/objective_memory_storage.dm b/code/modules/objectives/objective_memory_storage.dm
index 161c78d4d1ba..de2ab30691cc 100644
--- a/code/modules/objectives/objective_memory_storage.dm
+++ b/code/modules/objectives/objective_memory_storage.dm
@@ -218,6 +218,15 @@ GLOBAL_DATUM_INIT(intel_system, /datum/intel_system, new())
GLOB.intel_system.store_single_objective(O)
return 1
+/obj/structure/machinery/computer/intel/disk_reader // ARC computer to save on tile space
+ name = "\improper SIGINT terminal"
+ desc = "An USCM computer capable of uploading data to the intelligence database. It has a disk reader slot built into the bottom, as well."
+ icon_state = "terminal"
+
+/obj/structure/machinery/computer/intel/disk_reader/Initialize()
+ . = ..()
+ AddComponent(/datum/component/disk_reader)
+
// --------------------------------------------
// *** View objectives with the computer ***
// --------------------------------------------
diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm
index 3ed10129f4d6..8b189266c9cb 100644
--- a/code/modules/projectiles/projectile.dm
+++ b/code/modules/projectiles/projectile.dm
@@ -341,14 +341,7 @@
SEND_SIGNAL(src, COMSIG_BULLET_TERMINAL)
// Check we can reach the turf at all based on pathed grid
- var/proj_dir = get_dir(current_turf, next_turf)
- if((proj_dir & (proj_dir - 1)) && !current_turf.Adjacent(next_turf))
- ammo.on_hit_turf(current_turf, src)
- current_turf.bullet_act(src)
- return TRUE
-
- // Check for hits that would occur when moving to turf, such as a blocking cade
- if(scan_a_turf(next_turf, proj_dir))
+ if(check_canhit(current_turf, next_turf))
return TRUE
// Actually move
@@ -516,7 +509,8 @@
else
direct_hit = TRUE
- SEND_SIGNAL(firer, COMSIG_BULLET_DIRECT_HIT, L)
+ if(firer)
+ SEND_SIGNAL(firer, COMSIG_BULLET_DIRECT_HIT, L)
// At present, Xenos have no inherent effects or localized damage stemming from limb targeting
// Therefore we exempt the shooter from direct hit accuracy penalties as well,
@@ -583,6 +577,19 @@
if(SEND_SIGNAL(src, COMSIG_BULLET_POST_HANDLE_MOB, L, .) & COMPONENT_BULLET_PASS_THROUGH)
return FALSE
+/obj/projectile/proc/check_canhit(turf/current_turf, turf/next_turf)
+ var/proj_dir = get_dir(current_turf, next_turf)
+ if((proj_dir & (proj_dir - 1)) && !current_turf.Adjacent(next_turf))
+ ammo.on_hit_turf(current_turf, src)
+ current_turf.bullet_act(src)
+ return TRUE
+
+ // Check for hits that would occur when moving to turf, such as a blocking cade
+ if(scan_a_turf(next_turf, proj_dir))
+ return TRUE
+
+ return FALSE
+
//----------------------------------------------------------
// \\
// HITTING THE TARGET \\
diff --git a/code/modules/vehicles/apc/apc_command.dm b/code/modules/vehicles/apc/apc_command.dm
index e0862ae4f2ab..54647279ec3b 100644
--- a/code/modules/vehicles/apc/apc_command.dm
+++ b/code/modules/vehicles/apc/apc_command.dm
@@ -43,8 +43,6 @@
return ..()
/obj/vehicle/multitile/apc/command/process()
- . = ..()
-
var/turf/apc_turf = get_turf(src)
if(health == 0 || !visible_in_tacmap || !is_ground_level(apc_turf.z))
return
diff --git a/code/modules/vehicles/arc/arc.dm b/code/modules/vehicles/arc/arc.dm
new file mode 100644
index 000000000000..c73441c3754c
--- /dev/null
+++ b/code/modules/vehicles/arc/arc.dm
@@ -0,0 +1,263 @@
+/obj/vehicle/multitile/arc
+ name = "\improper M540-A Armored Recon Carrier"
+ desc = "An M540-A Armored Recon Carrier. A lightly armored reconnaissance and intelligence vehicle. Entrances on the sides."
+
+ icon = 'icons/obj/vehicles/arc.dmi'
+ icon_state = "arc_base"
+ pixel_x = -48
+ pixel_y = -48
+
+ bound_width = 96
+ bound_height = 96
+
+ bound_x = -32
+ bound_y = -32
+
+ health = 800
+
+ interior_map = /datum/map_template/interior/arc
+
+ passengers_slots = 3
+ xenos_slots = 5
+
+ entrances = list(
+ "left" = list(2, 0),
+ "right" = list(-2, 0),
+ )
+
+ entrance_speed = 0.5 SECONDS
+
+ required_skill = SKILL_VEHICLE_LARGE
+
+ movement_sound = 'sound/vehicles/tank_driving.ogg'
+
+ luminosity = 7
+
+ hardpoints_allowed = list(
+ /obj/item/hardpoint/locomotion/arc_wheels,
+ /obj/item/hardpoint/primary/arc_sentry,
+ /obj/item/hardpoint/support/arc_antenna,
+ )
+
+ seats = list(
+ VEHICLE_DRIVER = null,
+ )
+
+ active_hp = list(
+ VEHICLE_DRIVER = null,
+ )
+
+ vehicle_flags = VEHICLE_CLASS_LIGHT
+
+ mob_size_required_to_hit = MOB_SIZE_XENO
+
+ dmg_multipliers = list(
+ "all" = 1,
+ "acid" = 1.8,
+ "slash" = 1.1,
+ "bullet" = 0.6,
+ "explosive" = 0.8,
+ "blunt" = 0.8,
+ "abstract" = 1,
+ )
+
+ move_max_momentum = 2.2
+ move_momentum_build_factor = 1.5
+ move_turn_momentum_loss_factor = 0.8
+
+ vehicle_ram_multiplier = VEHICLE_TRAMPLE_DAMAGE_APC_REDUCTION
+
+ /// If the ARC has its antenna up, making it unable to move but enabling the turret and sensor wallhack
+ var/antenna_deployed = FALSE
+ /// How long it takes to deploy or retract the antenna
+ var/antenna_toggle_time = 10 SECONDS
+ /// Range of the ARC's xenomorph wallhacks
+ var/sensor_radius = 45
+ /// weakrefs of xenos temporarily added to the marine minimap
+ var/list/minimap_added = list()
+
+/obj/vehicle/multitile/arc/Initialize()
+ . = ..()
+
+ var/turf/gotten_turf = get_turf(src)
+ if(gotten_turf?.z)
+ SSminimaps.add_marker(src, gotten_turf.z, MINIMAP_FLAG_USCM, "arc", 'icons/ui_icons/map_blips_large.dmi')
+
+ RegisterSignal(src, COMSIG_ARC_ANTENNA_TOGGLED, PROC_REF(on_antenna_toggle))
+
+/obj/vehicle/multitile/arc/crew_mousedown(datum/source, atom/object, turf/location, control, params)
+ return
+
+
+/obj/vehicle/multitile/arc/get_examine_text(mob/user)
+ . = ..()
+ if(!isxeno(user))
+ return
+
+ if(health > 0)
+ . += SPAN_XENO("[src] can be crawled under once destroyed.")
+ else
+ . += SPAN_XENO("[src] can be crawled under by dragging our sprite to it.")
+
+/obj/vehicle/multitile/arc/proc/on_antenna_toggle(datum/source)
+ SIGNAL_HANDLER
+
+ if(antenna_deployed)
+ START_PROCESSING(SSslowobj, src)
+
+ else
+ STOP_PROCESSING(SSslowobj, src)
+
+/obj/vehicle/multitile/arc/process()
+ var/turf/arc_turf = get_turf(src)
+ if((health <= 0) || !visible_in_tacmap || !is_ground_level(arc_turf.z))
+ return
+
+ var/obj/item/hardpoint/support/arc_antenna/antenna = locate() in hardpoints
+ if(!antenna || (antenna.health <= 0))
+ for(var/datum/weakref/xeno as anything in minimap_added)
+ SSminimaps.remove_marker(xeno.resolve())
+ minimap_added.Remove(xeno)
+ return
+
+ for(var/mob/living/carbon/xenomorph/current_xeno as anything in GLOB.living_xeno_list)
+ var/turf/xeno_turf = get_turf(current_xeno)
+ if(!is_ground_level(xeno_turf.z))
+ continue
+
+ if(get_dist(src, current_xeno) <= sensor_radius)
+ if(WEAKREF(current_xeno) in minimap_added)
+ continue
+
+ SSminimaps.remove_marker(current_xeno)
+ current_xeno.add_minimap_marker(MINIMAP_FLAG_USCM|MINIMAP_FLAG_XENO)
+ minimap_added += WEAKREF(current_xeno)
+ else
+ if(WEAKREF(current_xeno) in minimap_added)
+ SSminimaps.remove_marker(current_xeno)
+ current_xeno.add_minimap_marker()
+ minimap_added -= WEAKREF(current_xeno)
+
+/obj/vehicle/multitile/arc/relaymove(mob/user, direction)
+ if(antenna_deployed)
+ return FALSE
+
+ return ..()
+
+/obj/vehicle/multitile/arc/load_role_reserved_slots()
+ var/datum/role_reserved_slots/RRS = new
+ RRS.category_name = "CIC Officer"
+ RRS.roles = list(JOB_SO, JOB_SEA, JOB_XO, JOB_CO, JOB_GENERAL)
+ RRS.total = 2
+ role_reserved_slots += RRS
+
+ RRS = new
+ RRS.category_name = "Intelligence Officer"
+ RRS.roles = list(JOB_INTEL)
+ RRS.total = 1
+ role_reserved_slots += RRS
+
+/obj/vehicle/multitile/arc/set_seated_mob(seat, mob/living/M)
+ . = ..()
+ if(!.)
+ return
+
+ give_action(M, /datum/action/human_action/toggle_arc_antenna)
+
+/obj/vehicle/multitile/arc/add_seated_verbs(mob/living/M, seat)
+ if(!M.client)
+ return
+ add_verb(M.client, list(
+ /obj/vehicle/multitile/proc/get_status_info,
+ //obj/vehicle/multitile/proc/open_controls_guide,
+ /obj/vehicle/multitile/proc/toggle_door_lock,
+ /obj/vehicle/multitile/proc/activate_horn,
+ /obj/vehicle/multitile/proc/name_vehicle,
+ /obj/vehicle/multitile/arc/proc/toggle_antenna,
+ ))
+
+/obj/vehicle/multitile/arc/remove_seated_verbs(mob/living/M, seat)
+ if(!M.client)
+ return
+ remove_verb(M.client, list(
+ /obj/vehicle/multitile/proc/get_status_info,
+ //obj/vehicle/multitile/proc/open_controls_guide,
+ /obj/vehicle/multitile/proc/toggle_door_lock,
+ /obj/vehicle/multitile/proc/activate_horn,
+ /obj/vehicle/multitile/proc/name_vehicle,
+ /obj/vehicle/multitile/arc/proc/toggle_antenna,
+ ))
+ SStgui.close_user_uis(M, src)
+
+/obj/vehicle/multitile/arc/initialize_cameras(change_tag = FALSE)
+ if(!camera)
+ camera = new /obj/structure/machinery/camera/vehicle(src)
+ if(change_tag)
+ camera.c_tag = "#[rand(1,100)] M540-A \"[nickname]\" ARC"
+ if(camera_int)
+ camera_int.c_tag = camera.c_tag + " interior"
+ else
+ camera.c_tag = "#[rand(1,100)] 540-A ARC"
+ if(camera_int)
+ camera_int.c_tag = camera.c_tag + " interior"
+
+/obj/vehicle/multitile/arc/MouseDrop_T(mob/M, mob/user)
+ . = ..()
+ if((M != user) || !isxeno(user))
+ return
+
+ if(health > 0)
+ to_chat(user, SPAN_XENO("We can't go under [src] until it is destroyed!"))
+ return
+
+ var/turf/current_turf = get_turf(user)
+ var/dir_to_go = get_dir(current_turf, src)
+ for(var/i in 1 to 3)
+ current_turf = get_step(current_turf, dir_to_go)
+ if(!(current_turf in locs))
+ break
+
+ if(current_turf.density)
+ to_chat(user, SPAN_XENO("The path under [src] is obstructed!"))
+ return
+
+ // Now we check to make sure the turf on the other side of the ARC isn't dense too
+ current_turf = get_step(current_turf, dir_to_go)
+ if(current_turf.density)
+ to_chat(user, SPAN_XENO("The path under [src] is obstructed!"))
+ return
+
+ to_chat(user, SPAN_XENO("We begin to crawl under [src]..."))
+ if(!do_after(user, 3 SECONDS, INTERRUPT_ALL, BUSY_ICON_HOSTILE))
+ to_chat(user, SPAN_XENO("We stop crawling under [src]."))
+ return
+
+ user.forceMove(current_turf)
+
+/*
+** PRESETS SPAWNERS
+*/
+/obj/effect/vehicle_spawner/arc
+ name = "ARC Transport Spawner"
+ icon = 'icons/obj/vehicles/apc.dmi'
+ icon_state = "apc_base"
+ pixel_x = -48
+ pixel_y = -48
+
+/obj/effect/vehicle_spawner/arc/Initialize()
+ . = ..()
+ spawn_vehicle()
+ qdel(src)
+
+/obj/effect/vehicle_spawner/arc/spawn_vehicle()
+ var/obj/vehicle/multitile/arc/ARC = new (loc)
+
+ load_misc(ARC)
+ load_hardpoints(ARC)
+ handle_direction(ARC)
+ ARC.update_icon()
+
+/obj/effect/vehicle_spawner/arc/load_hardpoints(obj/vehicle/multitile/arc/vehicle)
+ vehicle.add_hardpoint(new /obj/item/hardpoint/locomotion/arc_wheels)
+ vehicle.add_hardpoint(new /obj/item/hardpoint/primary/arc_sentry)
+ vehicle.add_hardpoint(new /obj/item/hardpoint/support/arc_antenna)
diff --git a/code/modules/vehicles/arc/verbs.dm b/code/modules/vehicles/arc/verbs.dm
new file mode 100644
index 000000000000..9b4409d16994
--- /dev/null
+++ b/code/modules/vehicles/arc/verbs.dm
@@ -0,0 +1,121 @@
+/obj/vehicle/multitile/arc/proc/toggle_antenna(mob/toggler)
+ set name = "Toggle Sensor Antenna"
+ set desc = "Raises or lowers the external sensor antenna. While raised, the ARC cannot move."
+ set category = "Vehicle"
+
+ var/mob/user = toggler || usr
+ if(!user || !istype(user))
+ return
+
+ var/obj/vehicle/multitile/arc/vehicle = user.interactee
+ if(!istype(vehicle))
+ return
+
+ var/seat
+ for(var/vehicle_seat in vehicle.seats)
+ if(vehicle.seats[vehicle_seat] == user)
+ seat = vehicle_seat
+ break
+
+ if(!seat)
+ return
+
+ if(vehicle.health < initial(vehicle.health) * 0.5)
+ to_chat(user, SPAN_WARNING("[vehicle]'s hull is too damaged to operate!"))
+ return
+
+ var/obj/item/hardpoint/support/arc_antenna/antenna = locate() in vehicle.hardpoints
+ if(!antenna)
+ to_chat(user, SPAN_WARNING("[vehicle] has no antenna mounted!"))
+ return
+
+ if(antenna.deploying)
+ return
+
+ if(antenna.health <= 0)
+ to_chat(user, SPAN_WARNING("[antenna] is broken!"))
+ return
+
+ if(vehicle.antenna_deployed)
+ to_chat(user, SPAN_NOTICE("You begin to retract [antenna]..."))
+ antenna.deploying = TRUE
+ if(!do_after(user, max(vehicle.antenna_toggle_time - antenna.deploy_animation_time, 1 SECONDS), target = vehicle))
+ to_chat(user, SPAN_NOTICE("You stop retracting [antenna]."))
+ antenna.deploying = FALSE
+ return
+
+ antenna.retract_antenna()
+ addtimer(CALLBACK(vehicle, PROC_REF(finish_antenna_retract), vehicle, user), antenna.deploy_animation_time)
+
+ else
+ to_chat(user, SPAN_NOTICE("You begin to extend [antenna]..."))
+ antenna.deploying = TRUE
+ if(!do_after(user, max(vehicle.antenna_toggle_time - antenna.deploy_animation_time, 1 SECONDS), target = vehicle))
+ to_chat(user, SPAN_NOTICE("You stop extending [antenna]."))
+ antenna.deploying = FALSE
+ return
+
+ antenna.deploy_antenna()
+ addtimer(CALLBACK(vehicle, PROC_REF(finish_antenna_deploy), vehicle, user), antenna.deploy_animation_time)
+
+/obj/vehicle/multitile/arc/proc/finish_antenna_retract(obj/vehicle/multitile/arc/vehicle, mob/user)
+ var/obj/item/hardpoint/support/arc_antenna/antenna = locate() in vehicle.hardpoints
+ if(!antenna)
+ antenna.deploying = FALSE
+ return
+
+ STOP_PROCESSING(SSslowobj, vehicle)
+ to_chat(user, SPAN_NOTICE("You retract [antenna], enabling the ARC to move again."))
+ playsound(user, 'sound/machines/hydraulics_2.ogg', 80, TRUE)
+ vehicle.antenna_deployed = !vehicle.antenna_deployed
+ antenna.deploying = FALSE
+ vehicle.update_icon()
+ SEND_SIGNAL(vehicle, COMSIG_ARC_ANTENNA_TOGGLED)
+
+/obj/vehicle/multitile/arc/proc/finish_antenna_deploy(obj/vehicle/multitile/arc/vehicle, mob/user)
+ var/obj/item/hardpoint/support/arc_antenna/antenna = locate() in vehicle.hardpoints
+ if(!antenna)
+ antenna.deploying = FALSE
+ return
+
+ START_PROCESSING(SSslowobj, vehicle)
+ to_chat(user, SPAN_NOTICE("You extend [antenna], locking the ARC in place."))
+ playsound(user, 'sound/machines/hydraulics_2.ogg', 80, TRUE)
+ vehicle.antenna_deployed = !vehicle.antenna_deployed
+ antenna.deploying = FALSE
+ vehicle.update_icon()
+ SEND_SIGNAL(vehicle, COMSIG_ARC_ANTENNA_TOGGLED)
+
+/obj/vehicle/multitile/arc/open_controls_guide()
+ set name = "Vehicle Controls Guide"
+ set desc = "MANDATORY FOR FIRST PLAY AS VEHICLE CREWMAN OR AFTER UPDATES."
+ set category = "Vehicle"
+
+ var/mob/user = usr
+ if(!istype(user))
+ return
+
+ var/obj/vehicle/multitile/arc/vehicle = user.interactee
+ if(!istype(vehicle))
+ return
+
+ var/seat
+ for(var/vehicle_seat in vehicle.seats)
+ if(vehicle.seats[vehicle_seat] == user)
+ seat = vehicle_seat
+ break
+
+ if(!seat)
+ return
+
+ var/dat = "Common verbs:
\
+ 1. \"G: Name Vehicle\" - used to add a custom name to the vehicle. Single use. 26 characters maximum.
\
+ 2. \"I: Get Status Info\" - brings up \"Vehicle Status Info\" window with all available information about your vehicle.
\
+ 3. \"G: Toggle Sensor Antenna\" - extend or retract the ARC's sensor antenna. While extended, all unknown lifeforms within a large range can be seen by all on the tacmap, but the ARC cannot move. Additionally enables the automated RE700 cannon.\
+ Driver verbs:
1. \"G: Activate Horn\" - activates vehicle horn. Keep in mind, that vehicle horn is very loud and can be heard from afar by both allies and foes.
\
+ 2. \"G: Toggle Door Locks\" - toggles vehicle's access restrictions. Crewman, Brig and Command accesses bypass these restrictions.
\
+ Driver shortcuts:
1. \"CTRL + Click\" - activates vehicle horn.
"
+
+ show_browser(user, dat, "Vehicle Controls Guide", "vehicle_help", "size=900x500")
+ onclose(user, "vehicle_help")
+ return
diff --git a/code/modules/vehicles/hardpoints/hardpoint.dm b/code/modules/vehicles/hardpoints/hardpoint.dm
index 21e3e4b29f89..1214e0903f70 100644
--- a/code/modules/vehicles/hardpoints/hardpoint.dm
+++ b/code/modules/vehicles/hardpoints/hardpoint.dm
@@ -581,7 +581,6 @@
/// Wrapper proc for the autofire system to ensure the important args aren't null.
/obj/item/hardpoint/proc/fire_wrapper(atom/target, mob/living/user, params)
- SHOULD_NOT_OVERRIDE(TRUE)
if(!target)
target = src.target
if(!user)
@@ -792,3 +791,7 @@
/obj/item/hardpoint/get_applying_acid_time()
return 10 SECONDS //you are not supposed to be able to easily combat-melt irreplaceable things.
+
+/// Proc to be overridden if you want to have special conditions preventing the removal of the hardpoint. Add chat messages in this proc if you want to tell the player why
+/obj/item/hardpoint/proc/can_be_removed(mob/remover)
+ return TRUE
diff --git a/code/modules/vehicles/hardpoints/hardpoint_ammo/arc_sentry_ammo.dm b/code/modules/vehicles/hardpoints/hardpoint_ammo/arc_sentry_ammo.dm
new file mode 100644
index 000000000000..f9c28e151514
--- /dev/null
+++ b/code/modules/vehicles/hardpoints/hardpoint_ammo/arc_sentry_ammo.dm
@@ -0,0 +1,16 @@
+/obj/item/ammo_magazine/hardpoint/arc_sentry
+ name = "\improper RE700 Rotary Cannon Magazine"
+ desc = "A magazine for RE700 Rotary Cannon filled with 20mm rounds. Supports IFF."
+ caliber = "20mm"
+ icon_state = "ace_autocannon"
+ w_class = SIZE_LARGE
+ default_ammo = /datum/ammo/bullet/re700
+ max_rounds = 500
+ gun_type = /obj/item/hardpoint/primary/arc_sentry
+
+/obj/item/ammo_magazine/hardpoint/arc_sentry/update_icon()
+ if(current_rounds > 0)
+ icon_state = "ace_autocannon"
+ else
+ icon_state = "ace_autocannon_empty"
+
diff --git a/code/modules/vehicles/hardpoints/primary/arc_sentry.dm b/code/modules/vehicles/hardpoints/primary/arc_sentry.dm
new file mode 100644
index 000000000000..139be5ce1219
--- /dev/null
+++ b/code/modules/vehicles/hardpoints/primary/arc_sentry.dm
@@ -0,0 +1,317 @@
+// APC cannons
+/obj/item/hardpoint/primary/arc_sentry
+ name = "\improper RE700 Rotary Cannon"
+ desc = "A primary two-barrel cannon for the ARC that shoots 12.7mm IFF-compatible rounds."
+ icon = 'icons/obj/vehicles/hardpoints/arc.dmi'
+
+ icon_state = "autocannon"
+ disp_icon = "arc"
+ disp_icon_state = "autocannon"
+ activation_sounds = list('sound/weapons/gun_m60.ogg')
+
+ damage_multiplier = 0.1
+ health = 125
+
+ origins = list(0, 0)
+
+ ammo = new /obj/item/ammo_magazine/hardpoint/arc_sentry
+ max_clips = 2
+
+ use_muzzle_flash = TRUE
+ angle_muzzleflash = FALSE
+ muzzleflash_icon_state = "muzzle_flash_double"
+
+ muzzle_flash_pos = list(
+ "1" = list(1, 0),
+ "2" = list(1, -25),
+ "4" = list(16, -4),
+ "8" = list(-16, -4)
+ )
+ gun_firemode = GUN_FIREMODE_BURSTFIRE
+ gun_firemode_list = list(
+ GUN_FIREMODE_BURSTFIRE,
+ )
+ burst_delay = 2
+ burst_amount = 3
+
+ /// Potential targets the turret can shoot at
+ var/list/targets = list()
+ /// The currently focused sentry target
+ var/atom/movable/sentry_target = null
+ /// The range that this turret can shoot at the furthest
+ var/turret_range = 5
+ /// What factions this sentry is aligned with
+ var/faction_group = FACTION_LIST_MARINE
+
+/obj/item/hardpoint/primary/arc_sentry/on_install(obj/vehicle/multitile/V)
+ . = ..()
+ RegisterSignal(owner, COMSIG_ARC_ANTENNA_TOGGLED, PROC_REF(toggle_processing))
+ toggle_processing() // We can't know that the antenna is in the same position as when the gun was removed
+
+/obj/item/hardpoint/primary/arc_sentry/on_uninstall(obj/vehicle/multitile/V)
+ . = ..()
+ UnregisterSignal(owner, COMSIG_ARC_ANTENNA_TOGGLED)
+ STOP_PROCESSING(SSfastobj, src)
+
+/obj/item/hardpoint/primary/arc_sentry/Destroy()
+ STOP_PROCESSING(SSfastobj, src)
+ sentry_target = null
+ return ..()
+
+/obj/item/hardpoint/primary/arc_sentry/proc/toggle_processing()
+ SIGNAL_HANDLER
+ if(!owner)
+ return
+
+ var/obj/vehicle/multitile/arc/vehicle = owner
+ if(vehicle.antenna_deployed)
+ START_PROCESSING(SSfastobj, src)
+
+ else
+ STOP_PROCESSING(SSfastobj, src)
+
+/obj/item/hardpoint/primary/arc_sentry/process()
+ for(var/mob/living/in_range_mob in range(turret_range, owner))
+ targets |= in_range_mob
+
+ if(!length(targets))
+ return FALSE
+
+ if(!sentry_target && length(targets))
+ sentry_target = pick(targets)
+
+ get_target(sentry_target)
+ return TRUE
+
+/obj/item/hardpoint/primary/arc_sentry/set_bullet_traits()
+ ..()
+ LAZYADD(traits_to_give, list(
+ BULLET_TRAIT_ENTRY(/datum/element/bullet_trait_iff)
+ ))
+
+/obj/item/hardpoint/primary/arc_sentry/fire_wrapper(atom/target, mob/living/user, params)
+ if(!target)
+ target = src.target
+ if(!target)
+ return NONE
+
+ return try_fire(target, null, params)
+
+/obj/item/hardpoint/primary/arc_sentry/clear_los()
+ var/turf/muzzle_turf = get_origin_turf()
+
+ var/turf/checking_turf = muzzle_turf
+ while(!(owner in checking_turf))
+ // Dense turfs block LoS
+ if(checking_turf.density)
+ return FALSE
+
+ // Ensure that we can pass over all objects in the turf
+ for(var/obj/object in checking_turf)
+ // Since vehicles are multitile the
+ if(object == owner)
+ continue
+
+ // Non-dense objects are irrelevant
+ if(!object.density)
+ continue
+
+ // Make sure we can pass object from all directions
+ if(!HAS_FLAG(object.pass_flags.flags_can_pass_all, PASS_OVER_THROW_ITEM))
+ if(!HAS_FLAG(object.flags_atom, ON_BORDER))
+ return FALSE
+ //If we're behind the object, check the behind pass flags
+ else if(dir == object.dir && !HAS_FLAG(object.pass_flags.flags_can_pass_behind, PASS_OVER_THROW_ITEM))
+ return FALSE
+ //If we're in front, check front pass flags
+ else if(dir == turn(object.dir, 180) && !HAS_FLAG(object.pass_flags.flags_can_pass_front, PASS_OVER_THROW_ITEM))
+ return FALSE
+
+ // Trace back towards the vehicle
+ checking_turf = get_step(checking_turf, turn(dir,180))
+
+ return TRUE
+
+/obj/item/hardpoint/primary/arc_sentry/handle_fire(atom/target, mob/living/user, params)
+ var/turf/origin_turf = get_origin_turf()
+
+ var/obj/projectile/arc_sentry/new_bullet = generate_bullet(origin_turf)
+ ammo.current_rounds--
+ SEND_SIGNAL(new_bullet, COMSIG_BULLET_USER_EFFECTS, user)
+ new_bullet.runtime_iff_group = faction_group // Technically shouldn't be directly modifying this, but sue me
+
+ // turf-targeted projectiles are fired without scatter, because proc would raytrace them further away
+ var/ammo_flags = new_bullet.ammo.flags_ammo_behavior | new_bullet.projectile_override_flags
+ if(!HAS_FLAG(ammo_flags, AMMO_HITS_TARGET_TURF) && !HAS_FLAG(ammo_flags, AMMO_EXPLOSIVE)) //AMMO_EXPLOSIVE is also a turf-targeted projectile
+ new_bullet.scatter = scatter
+ target = simulate_scatter(new_bullet, target, origin_turf, get_turf(target), user)
+
+ INVOKE_ASYNC(new_bullet, TYPE_PROC_REF(/obj/projectile, fire_at), target, user, src, new_bullet.ammo.max_range, new_bullet.ammo.shell_speed)
+ new_bullet = null
+
+ shots_fired++
+ play_firing_sounds()
+ if(use_muzzle_flash)
+ muzzle_flash(Get_Angle(origin_turf, target))
+
+ set_fire_cooldown(gun_firemode)
+ return AUTOFIRE_CONTINUE
+
+
+/obj/item/hardpoint/primary/arc_sentry/generate_bullet(turf/origin_turf)
+ var/obj/projectile/arc_sentry/new_proj = new(origin_turf, create_cause_data(initial(name), owner))
+ new_proj.generate_bullet(new ammo.default_ammo)
+ new_proj.permutated += owner
+ // Apply bullet traits from gun
+ for(var/entry in traits_to_give)
+ var/list/trait_list
+ // Check if this is an ID'd bullet trait
+ if(istext(entry))
+ trait_list = traits_to_give[entry].Copy()
+ else
+ // Prepend the bullet trait to the list
+ trait_list = list(entry) + traits_to_give[entry]
+ new_proj.apply_bullet_trait(trait_list)
+ return new_proj
+
+/obj/item/hardpoint/primary/arc_sentry/start_fire(datum/source, atom/object, turf/location, control, params)
+ if(istype(object, /atom/movable/screen))
+ return
+
+ if(QDELETED(object))
+ return
+
+ if(!auto_firing && !burst_firing && !COOLDOWN_FINISHED(src, fire_cooldown))
+ if(max(fire_delay, burst_delay + extra_delay) >= 2.0 SECONDS) //filter out guns with high firerate to prevent message spam.
+ to_chat(source, SPAN_WARNING("You need to wait [SPAN_HELPFUL(COOLDOWN_SECONDSLEFT(src, fire_cooldown))] seconds before [name] can be used again."))
+ return
+
+ set_target(object)
+
+ if(gun_firemode == GUN_FIREMODE_SEMIAUTO)
+ var/fire_return = try_fire(object, source, params)
+ //end-of-fire, show ammo (if changed)
+ if(fire_return == AUTOFIRE_CONTINUE)
+ reset_fire()
+ display_ammo(source)
+ else
+ SEND_SIGNAL(src, COMSIG_GUN_FIRE)
+
+/obj/item/hardpoint/primary/arc_sentry/proc/get_target(atom/movable/new_target)
+ if(!islist(targets))
+ return
+ if(!targets.Find(new_target))
+ targets.Add(new_target)
+
+ if(!length(targets))
+ return
+
+ var/list/conscious_targets = list()
+ var/list/unconscious_targets = list()
+
+ for(var/atom/movable/movable as anything in targets) // orange allows sentry to fire through gas and darkness
+ if(isliving(movable))
+ var/mob/living/living_mob = movable
+ if(living_mob.stat & DEAD)
+ if(movable == sentry_target)
+ sentry_target = null
+ targets.Remove(movable)
+ continue
+
+ if(living_mob.get_target_lock(faction_group) || living_mob.invisibility || HAS_TRAIT(living_mob, TRAIT_ABILITY_BURROWED))
+ if(living_mob == sentry_target)
+ sentry_target = null
+ targets.Remove(living_mob)
+ continue
+
+ var/list/turf/path = get_line(get_turf(src), movable)
+ if(!length(path)|| get_dist(get_turf(src), movable) > turret_range)
+ if(movable == sentry_target)
+ sentry_target = null
+ targets.Remove(movable)
+ continue
+
+ var/blocked = FALSE
+ for(var/turf/tile as anything in path)
+ if(tile.density || tile.opacity)
+ blocked = TRUE
+ break
+
+ for(var/obj/structure/struct in tile)
+ if(struct.opacity)
+ blocked = TRUE
+ break
+
+ for(var/obj/vehicle/multitile/vehicle in tile)
+ if(vehicle == owner) // Some of the tiles will inevitably be the ARC itself
+ continue
+ blocked = TRUE
+ break
+
+ if(locate(/obj/effect/particle_effect/smoke) in tile)
+ blocked = TRUE
+ break
+
+ if(blocked)
+ if(movable == sentry_target)
+ sentry_target = null
+ targets.Remove(movable)
+ continue
+
+ if(isliving(movable))
+ var/mob/living/living_mob = movable
+ if(living_mob.stat & UNCONSCIOUS)
+ unconscious_targets += living_mob
+ else
+ conscious_targets += living_mob
+
+ if((sentry_target in conscious_targets) || (sentry_target in unconscious_targets))
+ sentry_target = sentry_target
+
+ else if(length(conscious_targets))
+ sentry_target = pick(conscious_targets)
+
+ else if(length(unconscious_targets))
+ sentry_target = pick(unconscious_targets)
+
+ if(!sentry_target) //No targets, don't bother firing
+ return
+
+ start_fire(object = sentry_target)
+
+/obj/item/hardpoint/primary/arc_sentry/can_be_removed(mob/remover)
+ var/obj/vehicle/multitile/arc/arc_owner = owner
+ if(!istype(arc_owner))
+ return TRUE
+
+ if(arc_owner.antenna_deployed)
+ to_chat(remover, SPAN_WARNING("[src] cannot be removed from [owner] while its antenna is deployed."))
+ return FALSE
+
+ return TRUE
+
+/obj/projectile/arc_sentry/Initialize(mapload, datum/cause_data/cause_data)
+ . = ..()
+ RegisterSignal(src, COMSIG_BULLET_POST_HANDLE_OBJ, PROC_REF(check_passthrough))
+
+/obj/projectile/arc_sentry/check_canhit(turf/current_turf, turf/next_turf)
+ var/proj_dir = get_dir(current_turf, next_turf)
+ var/obj/item/hardpoint/arc_sentry = shot_from
+ if(!(arc_sentry.owner in current_turf) && !(arc_sentry.owner in next_turf) && (proj_dir & (proj_dir - 1)) && !current_turf.Adjacent(next_turf))
+ ammo.on_hit_turf(current_turf, src)
+ current_turf.bullet_act(src)
+ return TRUE
+
+ // Check for hits that would occur when moving to turf, such as a blocking cade
+ if(scan_a_turf(next_turf, proj_dir))
+ return TRUE
+
+ return FALSE
+
+/obj/projectile/arc_sentry/proc/check_passthrough(datum/source, obj/hit_obj, bool)
+ if(!istype(shot_from, /obj/item/hardpoint))
+ return
+
+ var/obj/item/hardpoint/sentry = shot_from
+ if(sentry.owner == hit_obj)
+ return COMPONENT_BULLET_PASS_THROUGH
diff --git a/code/modules/vehicles/hardpoints/support/antenna.dm b/code/modules/vehicles/hardpoints/support/antenna.dm
new file mode 100644
index 000000000000..8988ce802b3a
--- /dev/null
+++ b/code/modules/vehicles/hardpoints/support/antenna.dm
@@ -0,0 +1,83 @@
+/obj/item/hardpoint/support/arc_antenna
+ name = "\improper U-56 Radar Antenna"
+ desc = "A heavy-duty antenna built for the ARC."
+ icon = 'icons/obj/vehicles/hardpoints/arc.dmi'
+
+ icon_state = "antenna"
+ disp_icon = "arc"
+ disp_icon_state = "antenna"
+
+ damage_multiplier = 0.1
+
+ health = 500
+
+ /// How long the antenna deploy/retract animation is, keep accurate to the sprite in the dmi
+ var/deploy_animation_time = 1.7 SECONDS
+ /// If the antenna is already deploying
+ var/deploying = FALSE
+
+/obj/item/hardpoint/support/arc_antenna/proc/deploy_antenna()
+ set waitfor = FALSE
+
+ disp_icon_state = ""
+ if(owner)
+ owner.update_icon()
+ var/obj/dummy_obj = new()
+ dummy_obj.icon = 'icons/obj/vehicles/arc.dmi'
+ dummy_obj.icon_state = "antenna_cover_0"
+ dummy_obj.dir = owner.dir
+ dummy_obj.vis_flags = VIS_INHERIT_ID | VIS_INHERIT_LAYER | VIS_INHERIT_PLANE
+ owner.vis_contents += dummy_obj
+ flick("antenna_extending", dummy_obj)
+ sleep(deploy_animation_time)
+ qdel(dummy_obj)
+ disp_icon_state = initial(disp_icon_state)
+
+/obj/item/hardpoint/support/arc_antenna/proc/retract_antenna()
+ set waitfor = FALSE
+
+ disp_icon_state = ""
+ if(owner)
+ owner.update_icon()
+ var/obj/dummy_obj = new()
+ dummy_obj.icon = 'icons/obj/vehicles/arc.dmi'
+ dummy_obj.icon_state = "antenna_cover_0"
+ dummy_obj.dir = owner.dir
+ dummy_obj.vis_flags = VIS_INHERIT_ID | VIS_INHERIT_LAYER | VIS_INHERIT_PLANE
+ owner.vis_contents += dummy_obj
+ flick("antenna_retracting", dummy_obj)
+ sleep(deploy_animation_time)
+ qdel(dummy_obj)
+ disp_icon_state = initial(disp_icon_state)
+
+/obj/item/hardpoint/support/arc_antenna/get_icon_image(x_offset, y_offset, new_dir)
+ var/is_broken = health <= 0
+ var/antenna_extended = FALSE
+ if(istype(owner, /obj/vehicle/multitile/arc))
+ var/obj/vehicle/multitile/arc/arc_owner = owner
+ antenna_extended = arc_owner.antenna_deployed
+
+ var/image/I = image(icon = disp_icon, icon_state = "[disp_icon_state]_[antenna_extended ? "extended" : "cover"]_[is_broken ? "1" : "0"]", pixel_x = x_offset, pixel_y = y_offset, dir = new_dir)
+ switch(round((health / initial(health)) * 100))
+ if(0 to 20)
+ I.color = "#4e4e4e"
+ if(21 to 40)
+ I.color = "#6e6e6e"
+ if(41 to 60)
+ I.color = "#8b8b8b"
+ if(61 to 80)
+ I.color = "#bebebe"
+ else
+ I.color = null
+ return I
+
+/obj/item/hardpoint/support/arc_antenna/can_be_removed(mob/remover)
+ var/obj/vehicle/multitile/arc/arc_owner = owner
+ if(!istype(arc_owner))
+ return TRUE
+
+ if(arc_owner.antenna_deployed)
+ to_chat(remover, SPAN_WARNING("[src] cannot be removed from [owner] while its antenna is deployed."))
+ return FALSE
+
+ return TRUE
diff --git a/code/modules/vehicles/hardpoints/wheels/arc_wheels.dm b/code/modules/vehicles/hardpoints/wheels/arc_wheels.dm
new file mode 100644
index 000000000000..9bb6c31746e0
--- /dev/null
+++ b/code/modules/vehicles/hardpoints/wheels/arc_wheels.dm
@@ -0,0 +1,17 @@
+/obj/item/hardpoint/locomotion/arc_wheels
+ name = "ARC Wheels"
+ desc = "Integral to the movement of the ARC."
+ icon = 'icons/obj/vehicles/hardpoints/arc.dmi'
+
+ damage_multiplier = 0.15
+
+ icon_state = "tires"
+ disp_icon = "arc"
+ disp_icon_state = "arc_wheels"
+
+ health = 500
+
+ move_delay = VEHICLE_SPEED_SUPERFAST
+ move_max_momentum = 2
+ move_momentum_build_factor = 1.5
+ move_turn_momentum_loss_factor = 0.5
diff --git a/code/modules/vehicles/interior/areas.dm b/code/modules/vehicles/interior/areas.dm
index 254bcb6b26ea..399e55e11450 100644
--- a/code/modules/vehicles/interior/areas.dm
+++ b/code/modules/vehicles/interior/areas.dm
@@ -29,5 +29,9 @@
name = "van interior"
icon_state = "van"
+/area/interior/vehicle/arc
+ name = "\improper ARC interior"
+ icon_state = "arc"
+
/area/interior/fancylocker
name = "closet interior"
diff --git a/code/modules/vehicles/multitile/multitile.dm b/code/modules/vehicles/multitile/multitile.dm
index f3b7be510b08..18dade67b834 100644
--- a/code/modules/vehicles/multitile/multitile.dm
+++ b/code/modules/vehicles/multitile/multitile.dm
@@ -334,11 +334,12 @@
// Checked here because we want to be able to null the mob in a seat
if(!istype(M))
- return
+ return FALSE
M.set_interaction(src)
M.reset_view(src)
give_action(M, /datum/action/human_action/vehicle_unbuckle)
+ return TRUE
/// Get crewmember of seat.
/obj/vehicle/multitile/proc/get_seat_mob(seat)
diff --git a/code/modules/vehicles/multitile/multitile_hardpoints.dm b/code/modules/vehicles/multitile/multitile_hardpoints.dm
index 2a6f97dda06f..b94b8459890f 100644
--- a/code/modules/vehicles/multitile/multitile_hardpoints.dm
+++ b/code/modules/vehicles/multitile/multitile_hardpoints.dm
@@ -149,7 +149,7 @@
hps += H
var/chosen_hp = tgui_input_list(usr, "Select a hardpoint to remove", "Hardpoint Removal", (hps + "Cancel"))
- if(chosen_hp == "Cancel" || !chosen_hp || !in_range(src, user))
+ if(chosen_hp == "Cancel" || !chosen_hp || (get_dist(src, user) > 2)) //get_dist uses 2 because the vehicle is 3x3
return
var/obj/item/hardpoint/old = chosen_hp
@@ -158,6 +158,9 @@
to_chat(user, SPAN_WARNING("There is nothing installed there."))
return
+ if(!old.can_be_removed(user))
+ return
+
// It's in a holder
if(!(old in hardpoints))
for(var/obj/item/hardpoint/holder/H in hardpoints)
diff --git a/code/modules/vehicles/vehicle.dm b/code/modules/vehicles/vehicle.dm
index 014452426a3c..76107a4a1776 100644
--- a/code/modules/vehicles/vehicle.dm
+++ b/code/modules/vehicles/vehicle.dm
@@ -159,10 +159,11 @@
// Checked here because we want to be able to null the mob in a seat
if(!istype(M))
- return
+ return FALSE
M.forceMove(src)
M.set_interaction(src)
+ return TRUE
/obj/vehicle/proc/turn_on()
if(stat)
diff --git a/colonialmarines.dme b/colonialmarines.dme
index 67f16fc49081..a58c1631d566 100644
--- a/colonialmarines.dme
+++ b/colonialmarines.dme
@@ -381,6 +381,7 @@
#include "code\datums\ammo\rocket.dm"
#include "code\datums\ammo\shrapnel.dm"
#include "code\datums\ammo\xeno.dm"
+#include "code\datums\ammo\bullet\arc.dm"
#include "code\datums\ammo\bullet\bullet.dm"
#include "code\datums\ammo\bullet\lever_action.dm"
#include "code\datums\ammo\bullet\pistol.dm"
@@ -403,6 +404,7 @@
#include "code\datums\components\cluster_stack.dm"
#include "code\datums\components\connect_mob_behalf.dm"
#include "code\datums\components\crate_tag.dm"
+#include "code\datums\components\disk_reader.dm"
#include "code\datums\components\footstep.dm"
#include "code\datums\components\healing_reduction.dm"
#include "code\datums\components\id_lock.dm"
@@ -673,6 +675,7 @@
#include "code\datums\supply_packs\restricted_equipment.dm"
#include "code\datums\supply_packs\spec_ammo.dm"
#include "code\datums\supply_packs\vehicle_ammo.dm"
+#include "code\datums\supply_packs\vehicle_equipment.dm"
#include "code\datums\supply_packs\weapons.dm"
#include "code\datums\tutorial\_tutorial.dm"
#include "code\datums\tutorial\_tutorial_menu.dm"
@@ -1682,6 +1685,7 @@
#include "code\modules\cm_tech\resources\resource.dm"
#include "code\modules\cm_tech\techs\abstract\repeatable.dm"
#include "code\modules\cm_tech\techs\abstract\transitory.dm"
+#include "code\modules\cm_tech\techs\marine\tier1\arc.dm"
#include "code\modules\cm_tech\techs\marine\tier1\points.dm"
#include "code\modules\cm_tech\techs\marine\tier2\orbital_ammo.dm"
#include "code\modules\cm_tech\techs\marine\tier3\cryo_spec.dm"
@@ -2388,6 +2392,8 @@
#include "code\modules\vehicles\apc\apc_command.dm"
#include "code\modules\vehicles\apc\apc_medical.dm"
#include "code\modules\vehicles\apc\interior.dm"
+#include "code\modules\vehicles\arc\arc.dm"
+#include "code\modules\vehicles\arc\verbs.dm"
#include "code\modules\vehicles\hardpoints\hardpoint.dm"
#include "code\modules\vehicles\hardpoints\armor\armor.dm"
#include "code\modules\vehicles\hardpoints\armor\ballistic.dm"
@@ -2395,6 +2401,7 @@
#include "code\modules\vehicles\hardpoints\armor\concussive.dm"
#include "code\modules\vehicles\hardpoints\armor\paladin.dm"
#include "code\modules\vehicles\hardpoints\armor\snowplow.dm"
+#include "code\modules\vehicles\hardpoints\hardpoint_ammo\arc_sentry_ammo.dm"
#include "code\modules\vehicles\hardpoints\hardpoint_ammo\autocannon_ammo.dm"
#include "code\modules\vehicles\hardpoints\hardpoint_ammo\cupola_ammo.dm"
#include "code\modules\vehicles\hardpoints\hardpoint_ammo\dualcannon_ammo.dm"
@@ -2410,6 +2417,7 @@
#include "code\modules\vehicles\hardpoints\hardpoint_ammo\tow_ammo.dm"
#include "code\modules\vehicles\hardpoints\holder\holder.dm"
#include "code\modules\vehicles\hardpoints\holder\tank_turret.dm"
+#include "code\modules\vehicles\hardpoints\primary\arc_sentry.dm"
#include "code\modules\vehicles\hardpoints\primary\autocannon.dm"
#include "code\modules\vehicles\hardpoints\primary\dual_cannon.dm"
#include "code\modules\vehicles\hardpoints\primary\flamer.dm"
@@ -2424,12 +2432,14 @@
#include "code\modules\vehicles\hardpoints\secondary\tow.dm"
#include "code\modules\vehicles\hardpoints\special\firing_port_weapon.dm"
#include "code\modules\vehicles\hardpoints\special\special.dm"
+#include "code\modules\vehicles\hardpoints\support\antenna.dm"
#include "code\modules\vehicles\hardpoints\support\artillery.dm"
#include "code\modules\vehicles\hardpoints\support\flare.dm"
#include "code\modules\vehicles\hardpoints\support\iwsa.dm"
#include "code\modules\vehicles\hardpoints\support\overdrive.dm"
#include "code\modules\vehicles\hardpoints\support\support.dm"
#include "code\modules\vehicles\hardpoints\wheels\apc_wheels.dm"
+#include "code\modules\vehicles\hardpoints\wheels\arc_wheels.dm"
#include "code\modules\vehicles\hardpoints\wheels\locomotion.dm"
#include "code\modules\vehicles\hardpoints\wheels\treads.dm"
#include "code\modules\vehicles\hardpoints\wheels\van_wheels.dm"
diff --git a/icons/obj/vehicles/arc.dmi b/icons/obj/vehicles/arc.dmi
new file mode 100644
index 000000000000..b44cf45b3a5d
Binary files /dev/null and b/icons/obj/vehicles/arc.dmi differ
diff --git a/icons/obj/vehicles/hardpoints/arc.dmi b/icons/obj/vehicles/hardpoints/arc.dmi
new file mode 100644
index 000000000000..72854adf2382
Binary files /dev/null and b/icons/obj/vehicles/hardpoints/arc.dmi differ
diff --git a/icons/turf/areas_interiors.dmi b/icons/turf/areas_interiors.dmi
index 47a95da322ea..6fc30badd763 100644
Binary files a/icons/turf/areas_interiors.dmi and b/icons/turf/areas_interiors.dmi differ
diff --git a/icons/ui_icons/map_blips_large.dmi b/icons/ui_icons/map_blips_large.dmi
index 0cf41b52a4dc..f03433c8c8ef 100644
Binary files a/icons/ui_icons/map_blips_large.dmi and b/icons/ui_icons/map_blips_large.dmi differ
diff --git a/maps/interiors/arc.dmm b/maps/interiors/arc.dmm
new file mode 100644
index 000000000000..4da63cbff383
--- /dev/null
+++ b/maps/interiors/arc.dmm
@@ -0,0 +1,296 @@
+//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE
+"aD" = (
+/obj/effect/landmark/interior/spawn/weapons_loader,
+/turf/open/shuttle/vehicle{
+ icon_state = "floor_3_12"
+ },
+/area/interior/vehicle/arc)
+"be" = (
+/obj/structure/interior_wall/apc{
+ icon_state = "door_back"
+ },
+/turf/open/void/vehicle,
+/area/space)
+"cJ" = (
+/obj/structure/interior_wall/apc{
+ icon_state = "rear_1"
+ },
+/turf/open/void/vehicle,
+/area/space)
+"dM" = (
+/obj/structure/interior_wall/apc{
+ icon_state = "rear_2"
+ },
+/turf/open/void/vehicle,
+/area/space)
+"dU" = (
+/obj/effect/landmark/interior/spawn/vehicle_driver_seat/armor{
+ dir = 4
+ },
+/obj/structure/machinery/cm_vending/sorted/medical/wall_med/vehicle{
+ pixel_x = 6;
+ pixel_y = 28
+ },
+/obj/item/device/megaphone,
+/turf/open/shuttle/vehicle{
+ icon_state = "floor_3_9_1"
+ },
+/area/interior/vehicle/arc)
+"jb" = (
+/turf/open/shuttle/vehicle{
+ icon_state = "floor_3_11"
+ },
+/area/interior/vehicle/arc)
+"ml" = (
+/obj/structure/interior_wall/apc{
+ icon_state = "wall"
+ },
+/obj/effect/landmark/interior/spawn/telephone,
+/turf/open/void/vehicle,
+/area/space)
+"mR" = (
+/obj/structure/interior_wall/apc{
+ icon_state = "front_wheel_R"
+ },
+/turf/open/void/vehicle,
+/area/space)
+"ro" = (
+/obj/structure/interior_wall/apc{
+ alpha = 100;
+ icon_state = "wall_door_front";
+ layer = 5.2;
+ pixel_y = 32
+ },
+/turf/open/void/vehicle,
+/area/space)
+"ru" = (
+/turf/open/shuttle/vehicle{
+ icon_state = "floor_1_6"
+ },
+/area/interior/vehicle/arc)
+"tD" = (
+/obj/structure/interior_wall/apc{
+ icon_state = "rear_6"
+ },
+/turf/open/void/vehicle,
+/area/space)
+"vt" = (
+/obj/structure/interior_wall/apc{
+ icon_state = "rear_wheel_L"
+ },
+/turf/open/void/vehicle,
+/area/space)
+"we" = (
+/obj/structure/interior_wall/apc{
+ alpha = 100;
+ icon_state = "door_front";
+ layer = 5.2;
+ pixel_y = 32
+ },
+/turf/open/void/vehicle,
+/area/space)
+"wK" = (
+/obj/structure/interior_wall/apc{
+ icon_state = "wheel_front_top_1";
+ pixel_x = 1;
+ pixel_y = -4
+ },
+/turf/open/void/vehicle,
+/area/space)
+"ym" = (
+/obj/structure/interior_wall/apc{
+ icon_state = "rear_wheel_R";
+ opacity = 0
+ },
+/turf/open/void/vehicle,
+/area/space)
+"yX" = (
+/turf/open/shuttle/vehicle{
+ icon_state = "floor_3_8_1"
+ },
+/area/interior/vehicle/arc)
+"CB" = (
+/obj/structure/interior_wall/apc{
+ icon_state = "wall";
+ opacity = 0
+ },
+/turf/open/void/vehicle,
+/area/space)
+"Dn" = (
+/obj/structure/machinery/prop/almayer/CICmap/computer{
+ dir = 4;
+ pixel_y = 8
+ },
+/obj/structure/surface/table/reinforced/prison{
+ pixel_y = -3
+ },
+/obj/structure/machinery/computer/overwatch/almayer/small{
+ dir = 4;
+ layer = 3.01;
+ pixel_y = -5
+ },
+/turf/open/shuttle/vehicle{
+ icon_state = "floor_3_10_1"
+ },
+/area/interior/vehicle/arc)
+"Gy" = (
+/obj/structure/interior_wall/apc{
+ icon_state = "front_1"
+ },
+/turf/open/void/vehicle,
+/area/space)
+"HB" = (
+/obj/structure/interior_wall/apc{
+ icon_state = "corner_small_L"
+ },
+/obj/structure/interior_wall/apc{
+ icon_state = "rear_5"
+ },
+/turf/open/void/vehicle,
+/area/space)
+"HJ" = (
+/obj/structure/interior_wall/apc{
+ icon_state = "front_6"
+ },
+/turf/open/void/vehicle,
+/area/space)
+"Jc" = (
+/obj/structure/interior_wall/apc{
+ icon_state = "corner_small_R"
+ },
+/obj/structure/interior_wall/apc{
+ icon_state = "front_5"
+ },
+/turf/open/void/vehicle,
+/area/space)
+"Ng" = (
+/obj/structure/interior_wall/apc{
+ icon_state = "wall"
+ },
+/turf/open/void/vehicle,
+/area/space)
+"NS" = (
+/obj/structure/bed/chair/comfy{
+ dir = 8
+ },
+/turf/open/shuttle/vehicle{
+ icon_state = "floor_3_5"
+ },
+/area/interior/vehicle/arc)
+"Ol" = (
+/obj/effect/landmark/interior/spawn/entrance{
+ alpha = 50;
+ exit_type = /obj/structure/interior_exit/vehicle/apc;
+ name = "Right APC door";
+ tag = "right"
+ },
+/obj/effect/landmark/interior/spawn/interior_viewport{
+ dir = 8;
+ pixel_x = 5;
+ pixel_y = 1
+ },
+/turf/open/shuttle/vehicle{
+ icon_state = "floor_1_12"
+ },
+/area/interior/vehicle/arc)
+"Po" = (
+/obj/structure/surface/table/reinforced/prison{
+ pixel_y = -3
+ },
+/obj/structure/machinery/computer/intel/disk_reader{
+ dir = 4;
+ pixel_y = 6
+ },
+/obj/structure/machinery/computer/groundside_operations{
+ dir = 4;
+ pixel_y = -7
+ },
+/turf/open/shuttle/vehicle{
+ icon_state = "floor_3_8_1"
+ },
+/area/interior/vehicle/arc)
+"Rb" = (
+/turf/open/void/vehicle,
+/area/space)
+"Rf" = (
+/obj/structure/interior_wall/apc{
+ icon_state = "front_2"
+ },
+/turf/open/void/vehicle,
+/area/space)
+"Zs" = (
+/obj/structure/bed/chair/comfy{
+ dir = 8
+ },
+/turf/open/shuttle/vehicle{
+ icon_state = "floor_3_8_1"
+ },
+/area/interior/vehicle/arc)
+"ZF" = (
+/obj/effect/landmark/interior/spawn/interior_viewport{
+ dir = 8;
+ pixel_x = 5;
+ pixel_y = 8
+ },
+/obj/effect/landmark/interior/spawn/entrance{
+ alpha = 50;
+ dir = 1;
+ exit_type = /obj/structure/interior_exit/vehicle/apc;
+ name = "Left APC door";
+ pixel_y = 32;
+ tag = "left"
+ },
+/turf/open/shuttle/vehicle{
+ icon_state = "floor_1_11"
+ },
+/area/interior/vehicle/arc)
+
+(1,1,1) = {"
+tD
+HB
+dM
+cJ
+Rb
+"}
+(2,1,1) = {"
+Ng
+Po
+Dn
+vt
+Rb
+"}
+(3,1,1) = {"
+ml
+Zs
+NS
+ym
+Rb
+"}
+(4,1,1) = {"
+Ng
+jb
+yX
+aD
+ro
+"}
+(5,1,1) = {"
+be
+ZF
+ru
+Ol
+we
+"}
+(6,1,1) = {"
+wK
+CB
+dU
+mR
+Rb
+"}
+(7,1,1) = {"
+HJ
+Jc
+Rf
+Gy
+Rb
+"}
diff --git a/strings/xenotips.txt b/strings/xenotips.txt
index 08f56bf1aa8e..8cc44753d802 100644
--- a/strings/xenotips.txt
+++ b/strings/xenotips.txt
@@ -37,3 +37,5 @@ You can filter out the Xenomorphs displayed in hive status by health, allowing y
Each xeno has their own ‘tackle counter’ on a marine. The range to successfully tackle can be anywhere from two to six tackles based on caste. If a marine gets stunned or knocked over by other means it will reset everyone's tackle counters and they may get up!
As a Xenomorph, the list of available tunnels is sorted by their distance to the player!
As a Xenomorph, pay attention to what a marine is wearing. If they have no helmet, aiming for the head will be significantly more damaging, if they aren't wearing gloves, then think about going for their hands.
+The M540-A Armored Recon Carrier can be crawled under as a xenomorph when destroyed.
+The M540-A Armored Recon Carrier is very vulnerable to acid damage.