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 a059d080e8ee..af746ceb20a2 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()
. = ..()
@@ -1401,12 +1409,13 @@ GLOBAL_DATUM_INIT(supply_controller, /datum/controller/supply, new())
if(!SSshuttle.vehicle_elevator)
return
- dat += "Platform position: "
- if (SSshuttle.vehicle_elevator.timeLeft())
+ dat += "Platform position:
"
+ if (SSshuttle.vehicle_elevator.mode != SHUTTLE_IDLE)
dat += "Moving"
else
if(is_mainship_level(SSshuttle.vehicle_elevator.z))
- dat += "Raised"
+ dat += "Raised
"
+ dat += "\[Lower\]"
else
dat += "Lowered"
dat += "
"
@@ -1442,15 +1451,11 @@ GLOBAL_DATUM_INIT(supply_controller, /datum/controller/supply, new())
world.log << "## ERROR: Eek. The supply/elevator datum is missing somehow."
return
- if(!should_block_game_interaction(SSshuttle.vehicle_elevator))
- to_chat(usr, SPAN_WARNING("The elevator needs to be in the cargo bay dock to call a vehicle up. Ask someone to send it away."))
- return
-
if(isturf(loc) && ( in_range(src, usr) || isSilicon(usr) ) )
usr.set_interaction(src)
if(href_list["get_vehicle"])
- if(is_mainship_level(SSshuttle.vehicle_elevator.z))
+ if(is_mainship_level(SSshuttle.vehicle_elevator.z) || SSshuttle.vehicle_elevator.mode != SHUTTLE_IDLE)
return
// dunno why the +1 is needed but the vehicles spawn off-center
var/turf/middle_turf = get_turf(SSshuttle.vehicle_elevator)
@@ -1458,9 +1463,11 @@ GLOBAL_DATUM_INIT(supply_controller, /datum/controller/supply, new())
var/obj/vehicle/multitile/ordered_vehicle
var/datum/vehicle_order/VO = locate(href_list["get_vehicle"])
+ if(!(VO in vehicles))
+ return
- if(!VO) return
- if(VO.has_vehicle_lock()) return
+ if(VO?.has_vehicle_lock())
+ return
spent = TRUE
ordered_vehicle = new VO.ordered_vehicle(middle_turf)
@@ -1470,5 +1477,11 @@ GLOBAL_DATUM_INIT(supply_controller, /datum/controller/supply, new())
SEND_GLOBAL_SIGNAL(COMSIG_GLOB_VEHICLE_ORDERED, ordered_vehicle)
+ else if(href_list["lower_elevator"])
+ if(!is_mainship_level(SSshuttle.vehicle_elevator.z))
+ return
+
+ SSshuttle.vehicle_elevator.request(SSshuttle.getDock("adminlevel vehicle"))
+
add_fingerprint(usr)
updateUsrDialog()
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..c491cc35314b
--- /dev/null
+++ b/code/modules/vehicles/arc/arc.dm
@@ -0,0 +1,263 @@
+/obj/vehicle/multitile/arc
+ name = "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..dff08004ba95 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)
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..c0b18f33708e
--- /dev/null
+++ b/code/modules/vehicles/hardpoints/primary/arc_sentry.dm
@@ -0,0 +1,305 @@
+// 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))
+
+/obj/item/hardpoint/primary/arc_sentry/on_uninstall(obj/vehicle/multitile/V)
+ . = ..()
+ UnregisterSignal(owner, COMSIG_ARC_ANTENNA_TOGGLED)
+ START_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/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..38b08656c067
--- /dev/null
+++ b/code/modules/vehicles/hardpoints/support/antenna.dm
@@ -0,0 +1,72 @@
+/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
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..078cfc4b199a 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
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 b509aa695847..fd7f5833e345 100644
--- a/colonialmarines.dme
+++ b/colonialmarines.dme
@@ -379,6 +379,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"
@@ -401,6 +402,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"
@@ -670,6 +672,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"
@@ -1672,6 +1675,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"
@@ -2375,6 +2379,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"
@@ -2382,6 +2388,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"
@@ -2397,6 +2404,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"
@@ -2411,12 +2419,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.