diff --git a/code/__HELPERS/icon_smoothing.dm b/code/__HELPERS/icon_smoothing.dm
index 5db8ea4c612..20aa9e00036 100644
--- a/code/__HELPERS/icon_smoothing.dm
+++ b/code/__HELPERS/icon_smoothing.dm
@@ -422,6 +422,7 @@
/atom
var/icon_type_smooth
var/junction
+ var/bypass_interactions = FALSE
/atom/proc/recalculate_junction()
junction = 0
diff --git a/code/_onclick/drag_drop.dm b/code/_onclick/drag_drop.dm
index 05a9ba6a7c7..0eae9aca64d 100644
--- a/code/_onclick/drag_drop.dm
+++ b/code/_onclick/drag_drop.dm
@@ -65,8 +65,18 @@ GLOBAL_VAR_INIT(use_experimental_clickdrag_thing, TRUE)
if(!object.IsAutoclickable())
return
var/obj/item/h = get_active_held_item()
- if(h)
+ if(istype(loc, /obj/mecha))
+ var/obj/mecha/piloting = loc
+ if(piloting.selected && istype(piloting.selected, /obj/item/mecha_parts/mecha_equipment/weapon))
+ var/obj/item/mecha_parts/mecha_equipment/weapon/selectedweapon = piloting.selected
+ . = selectedweapon.is_automatic
+ else if(h)
. = h.CanItemAutoclick(object, location, params)
+ if(istype(loc, /obj/mecha))
+ var/obj/mecha/piloting = loc
+ if(piloting.selected && istype(piloting.selected, /obj/item/mecha_parts/mecha_equipment/weapon))
+ var/obj/item/mecha_parts/mecha_equipment/weapon/selectedweapon = piloting.selected
+ . = selectedweapon.is_automatic
/mob/proc/canMobMousedown(atom/object, location, params)
diff --git a/code/controllers/subsystem/jukeboxes.dm b/code/controllers/subsystem/jukeboxes.dm
index db1e2d19e4a..aae9b1f0105 100644
--- a/code/controllers/subsystem/jukeboxes.dm
+++ b/code/controllers/subsystem/jukeboxes.dm
@@ -106,9 +106,18 @@ SUBSYSTEM_DEF(jukeboxes)
if(!(M.client.prefs.toggles & SOUND_INSTRUMENTS) || !M.can_hear())
M.stop_sound_channel(jukeinfo[2])
continue
-
- if(jukebox.z == M.z) //todo - expand this to work with mining planet z-levels when robust jukebox audio gets merged to master
+ var/turf/juketurf = get_turf(jukebox)
+ var/turf/mturf = get_turf(M)
+ if(juketurf.z == mturf.z) //todo - expand this to work with mining planet z-levels when robust jukebox audio gets merged to master
song_played.status = SOUND_UPDATE
+ else if(juketurf.z == mturf.z -1)
+ var/turf/juketurf_above = SSmapping.get_turf_above(juketurf)
+ if(istype(juketurf_above, /turf/open/transparent))
+ song_played.status = SOUND_UPDATE
+ else if(juketurf.z == mturf.z +1)
+ var/turf/mturf_above = SSmapping.get_turf_above(mturf)
+ if(istype(mturf_above, /turf/open/transparent) || istype(juketurf,/turf/open/transparent))
+ song_played.status = SOUND_UPDATE
else
song_played.status = SOUND_MUTE | SOUND_UPDATE //Setting volume = 0 doesn't let the sound properties update at all, which is lame.
diff --git a/code/controllers/subsystem/throwing.dm b/code/controllers/subsystem/throwing.dm
index 9798b513ca9..87e6f68bbb0 100644
--- a/code/controllers/subsystem/throwing.dm
+++ b/code/controllers/subsystem/throwing.dm
@@ -147,7 +147,14 @@ SUBSYSTEM_DEF(throwing)
if (obstacle == actual_target || (obstacle.density && !(obstacle.flags_1 & ON_BORDER_1)))
finalize(TRUE, obstacle)
return
-
+ var/turf/starting_turf = get_turf(AM)
+ if(AM.z < target_turf.z)
+ var/turf/new_turf = SSmapping.get_turf_above(starting_turf)
+ AM.forceMove(new_turf)
+ if(starting_turf.z > target_turf.z)
+ var/turf/new_turf = SSmapping.get_turf_below(starting_turf)
+ AM.forceMove(new_turf)
+
var/atom/step
last_move = world.time
diff --git a/code/datums/components/crafting/recipes/recipes_tools.dm b/code/datums/components/crafting/recipes/recipes_tools.dm
index 99b72a1febd..110448fb210 100644
--- a/code/datums/components/crafting/recipes/recipes_tools.dm
+++ b/code/datums/components/crafting/recipes/recipes_tools.dm
@@ -130,3 +130,27 @@
/obj/item/stack/rods = 2)
category = CAT_CRAFTING
subcategory = CAT_TOOL
+
+/datum/crafting_recipe/cellupgrade
+ name = "High cell to Ultra cell convertion"
+ result = /obj/item/stock_parts/cell/bluespace
+ time = 80
+ reqs = list(/obj/item/stock_parts/cell/high = 4,
+ /obj/item/stack/cable_coil = 10,
+ /obj/item/toy/crayon/spraycan = 1)
+ tools = list(TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
+ category = CAT_CRAFTING
+ subcategory = CAT_TOOL
+
+/datum/crafting_recipe/carpart/stereo
+ name = "Mounted Stereo"
+ result = /obj/item/mecha_parts/mecha_equipment/stereo
+ reqs = list(/obj/item/stack/sheet/metal = 5,
+ /obj/item/stack/crafting/metalparts = 10,
+ /obj/item/circuitboard/machine/jukebox = 1,
+ /obj/item/stack/rods = 5)
+ tools = list(TOOL_WORKBENCH)
+ time = 90
+ category = CAT_VEHICLES
+ subcategory = CAT_VEHICLEPARTS
+
diff --git a/code/datums/components/crafting/recipes/recipes_weapon_and_ammo.dm b/code/datums/components/crafting/recipes/recipes_weapon_and_ammo.dm
index cafcfd35d4c..072bf2ecd21 100644
--- a/code/datums/components/crafting/recipes/recipes_weapon_and_ammo.dm
+++ b/code/datums/components/crafting/recipes/recipes_weapon_and_ammo.dm
@@ -681,6 +681,52 @@
category = CAT_WEAPONRY
subcategory = CAT_WEAPON
+/*/datum/crafting_recipe/gun/minigunVehicle
+ name = "Minigun"
+ result = /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/minigun
+ reqs = list(/obj/item/stack/crafting/metalparts = 10,
+ /obj/item/stack/crafting/goodparts = 5,
+ /obj/item/stack/crafting/electronicparts = 5,
+ /obj/item/stack/sheet/metal = 10,
+ /obj/item/stack/sheet/mineral/titanium = 20,
+ /obj/item/stack/rods = 6,
+ /obj/item/advanced_crafting_components/assembly = 1,
+ /obj/item/advanced_crafting_components/receiver = 1,
+ /obj/item/advanced_crafting_components/alloys = 1)
+ tools = list(TOOL_WORKBENCH)
+ time = 180
+ category = CAT_WEAPONRY
+ subcategory = CAT_WEAPON*/
+
+/datum/crafting_recipe/gun/PheumonicLauncherVehicle
+ name = "Mounted Pheumonic launcher"
+ result = /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/anykind
+ reqs = list(/obj/item/stack/crafting/metalparts = 20,
+ /obj/item/stack/crafting/goodparts = 10,
+ /obj/item/stack/crafting/electronicparts = 5,
+ /obj/item/stack/sheet/metal = 30,
+ /obj/item/stack/sheet/mineral/titanium = 20,
+ /obj/item/stack/rods = 8,
+ /obj/item/advanced_crafting_components/assembly = 1,
+ /obj/item/advanced_crafting_components/receiver = 1)
+ tools = list(TOOL_WORKBENCH)
+ time = 180
+ category = CAT_WEAPONRY
+ subcategory = CAT_WEAPON
+
+/*/datum/crafting_recipe/mech_ammo/brm8_missiles
+ name = "Minigun Ammo Pack"
+ result = /obj/item/mecha_ammo/minigun
+ reqs = list(/obj/item/ammo_box/magazine/ammobelt/m1919 = 3,
+ /obj/item/stack/sheet/metal = 10,
+ /obj/item/stack/sheet/mineral/titanium = 20,
+ /obj/item/stack/crafting/powder = 30)
+ tools = list(TOOL_WORKBENCH)
+ time = 180
+ category = CAT_WEAPONRY
+ subcategory = CAT_AMMO*/
+
+
/datum/crafting_recipe/gun/HMGvehicle
name = "Improvised HMG (for vehicles)"
result = /obj/item/mecha_parts/mecha_equipment/weapon/ballistic/lmg/hobo
diff --git a/code/game/mecha/equipment/tools/work_tools.dm b/code/game/mecha/equipment/tools/work_tools.dm
index b7dc7d07eb9..a8b913fe805 100644
--- a/code/game/mecha/equipment/tools/work_tools.dm
+++ b/code/game/mecha/equipment/tools/work_tools.dm
@@ -479,3 +479,220 @@
//NC.mergeConnectedNetworksOnTurf()
last_piece = NC
return 1
+
+
+/obj/item/mecha_parts/mecha_equipment/stereo
+ name = "exosuit Stereo System"
+ desc = "a stereo system hooked up a jukebox, modified for easy transport."
+ icon_state = "mecha_stereo"
+ range = MELEE
+ var/active = FALSE
+ var/list/rangers = list()
+ var/stop = 0
+ var/volume = 70
+ var/datum/track/selection = null
+ var/open_tray = TRUE
+ var/list/obj/item/record_disk/record_disks = list()
+ var/obj/item/record_disk/selected_disk = null
+
+/obj/item/mecha_parts/mecha_equipment/stereo/attach(obj/mecha/M)
+ . = ..()
+ bypass_interactions = TRUE
+
+/obj/item/mecha_parts/mecha_equipment/stereo/detach(obj/mecha/M)
+ . = ..()
+ bypass_interactions = FALSE
+
+/obj/item/mecha_parts/mecha_equipment/stereo/attackby(obj/item/O, mob/user, params)
+ . = ..()
+ if(!active)
+ if(istype(O, /obj/item/record_disk)) //this one checks for a record disk and if the jukebox is open, it adds it to the machine
+ if(open_tray == FALSE)
+ to_chat(usr, "The Disk Tray is not open!")
+ return
+ var/obj/item/record_disk/I = O
+ if(!I.R.song_associated_id)
+ to_chat(user, span_warning("This record is empty!"))
+ return
+ for(var/datum/track/RT in SSjukeboxes.songs)
+ if(I.R.song_associated_id == RT.song_associated_id)
+ to_chat(user, span_warning("this track is already added to the jukebox!"))
+ return
+ record_disks += I
+ O.forceMove(src)
+ playsound(src, 'sound/effects/plastic_click.ogg', 100, 0)
+ if(I.R.song_path)
+ SSjukeboxes.add_song(I.R)
+ return
+
+/obj/item/mecha_parts/mecha_equipment/stereo/proc/eject_record(obj/item/record_disk/M) //BIG IRON EDIT -start- ejects a record as defined and removes it's song from the list
+ if(!M)
+ visible_message("no disk to eject")
+ return
+ playsound(src, 'sound/effects/disk_tray.ogg', 100, 0)
+ src.visible_message(" ejected the [selected_disk] from the [src]!")
+ M.forceMove(get_turf(src))
+ SSjukeboxes.remove_song(M.R)
+ record_disks -= M
+ selected_disk = null
+
+/obj/item/mecha_parts/mecha_equipment/stereo/ui_status(mob/user)
+ if(!SSjukeboxes.songs.len && !isobserver(user))
+ to_chat(user,"Error: No music tracks have been authorized for your station. Petition Central Command to resolve this issue.")
+ playsound(src, 'sound/misc/compiler-failure.ogg', 25, TRUE)
+ return UI_CLOSE
+ return ..()
+
+/obj/item/mecha_parts/mecha_equipment/stereo/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "Jukebox", name)
+ ui.open()
+
+/obj/item/mecha_parts/mecha_equipment/stereo/ui_data(mob/user)
+ var/list/data = list()
+ data["active"] = active
+ data["songs"] = list()
+ for(var/datum/track/S in SSjukeboxes.songs)
+ var/list/track_data = list(
+ name = S.song_name
+ )
+ data["songs"] += list(track_data)
+ data["track_selected"] = null
+ data["track_length"] = null
+ data["track_beat"] = null
+ data["disks"] = list()
+ for(var/obj/item/record_disk/RD in record_disks)
+ var/list/tracks_data = list(
+ name = RD.name
+ )
+ data["disks"] += list(tracks_data)
+ data["disk_selected"] = null //BIG IRON EDIT- start more tracks data
+ data["disk_selected_lenght"] = null
+ data["disk_beat"] = null //BIG IRON EDIT -end
+ if(selection)
+ data["track_selected"] = selection.song_name
+ data["track_length"] = DisplayTimeText(selection.song_length)
+ data["track_beat"] = selection.song_beat
+ if(selected_disk)
+ data["disk_selected"] = selected_disk
+ data["disk_selected_length"] = DisplayTimeText(selected_disk.R.song_length)
+ data["disk_selected_beat"] = selected_disk.R.song_beat
+ data["volume"] = volume
+ return data
+
+/obj/item/mecha_parts/mecha_equipment/stereo/ui_act(action, list/params)
+ . = ..()
+ if(.)
+ return
+
+ switch(action)
+ if("toggle")
+ if(QDELETED(src))
+ return
+ if(!active)
+ if(stop > world.time)
+ to_chat(usr, "Error: The device is still resetting from the last activation, it will be ready again in [DisplayTimeText(stop-world.time)].")
+ playsound(src, 'sound/misc/compiler-failure.ogg', 50, TRUE)
+ return
+ activate_music()
+ START_PROCESSING(SSobj, src)
+ return TRUE
+ else
+ stop = 0
+ return TRUE
+ if("select_track")
+ if(active)
+ to_chat(usr, "Error: You cannot change the song until the current one is over.")
+ return
+ var/list/available = list()
+ for(var/datum/track/S in SSjukeboxes.songs)
+ available[S.song_name] = S
+ var/selected = params["track"]
+ if(QDELETED(src) || !selected || !istype(available[selected], /datum/track))
+ return
+ selection = available[selected]
+ return TRUE
+ if("select_record")
+ if(!record_disks.len)
+ to_chat(usr, "Error: no tracks on the bin!.")
+ return
+ var/list/obj/item/record_disk/availabledisks = list()
+ for(var/obj/item/record_disk/RR in record_disks)
+ availabledisks[RR.name] = RR
+ var/selecteddisk = params["record"]
+ if(QDELETED(src) || !selecteddisk)
+ return
+ selected_disk = availabledisks[selecteddisk]
+ updateUsrDialog()
+ if("eject_disk") // sanity check for the disk ejection
+ if(!record_disks.len)
+ to_chat(usr, "Error: no disks in trays.")
+ return
+ if(!selected_disk)
+ to_chat(usr,"Error: no disk chosen." )
+ return
+ if(selection == selected_disk.R)
+ selection = null
+ eject_record(selected_disk)
+ return TRUE
+ if("set_volume")
+ var/new_volume = params["volume"]
+ if(new_volume == "reset")
+ volume = initial(volume)
+ return TRUE
+ else if(new_volume == "min")
+ volume = 0
+ return TRUE
+ else if(new_volume == "max")
+ volume = 100
+ return TRUE
+ else if(text2num(new_volume) != null)
+ volume = text2num(new_volume)
+ return TRUE
+
+/obj/item/mecha_parts/mecha_equipment/stereo/proc/activate_music()
+ if(!selection)
+ visible_message("Track is no longer avaible")
+ return
+ var/jukeboxslottotake = SSjukeboxes.addjukebox(src, selection, 2)
+ if(jukeboxslottotake)
+ active = TRUE
+ update_icon()
+ START_PROCESSING(SSobj, src)
+ stop = world.time + selection.song_length
+ return TRUE
+ else
+ return FALSE
+
+/obj/item/mecha_parts/mecha_equipment/stereo/get_equip_info()
+ var/output = ..()
+ if(output)
+ var/temp = ""
+ temp = "Dashboard"
+ return "[output] [temp]"
+ return
+
+/obj/item/mecha_parts/mecha_equipment/stereo/Topic(href,href_list)
+ ..()
+ if(href_list["dashboard"])
+ var/mob/user = chassis.occupant
+ ui_interact(user)
+ return
+
+/obj/item/mecha_parts/mecha_equipment/stereo/process()
+ if(active && world.time >= stop)
+ active = FALSE
+ dance_over()
+ playsound(src,'sound/machines/terminal_off.ogg',50,1)
+ update_icon()
+ stop = world.time + 100
+
+/obj/item/mecha_parts/mecha_equipment/stereo/proc/dance_over()
+ var/position = SSjukeboxes.findjukeboxindex(src)
+ if(!position)
+ return
+ SSjukeboxes.removejukebox(position)
+ STOP_PROCESSING(SSobj, src)
+ rangers = list()
+
diff --git a/code/game/mecha/equipment/weapons/mecha_ammo.dm b/code/game/mecha/equipment/weapons/mecha_ammo.dm
index f5844ec5e61..3128e8f912f 100644
--- a/code/game/mecha/equipment/weapons/mecha_ammo.dm
+++ b/code/game/mecha/equipment/weapons/mecha_ammo.dm
@@ -99,3 +99,10 @@
round_term = "cluster"
direct_load = TRUE
ammo_type = "clusterbang"
+
+/obj/item/mecha_ammo/minigun
+ name = "Minigun ammo pack"
+ desc = "A box of high caliber ammo, ready to be consumed in nano seconds. Cannot be primed by hand."
+ icon_state = "lmg"
+ rounds = 600
+ ammo_type = "minigun"
diff --git a/code/game/mecha/equipment/weapons/weapons.dm b/code/game/mecha/equipment/weapons/weapons.dm
index 5865e2f3b7f..f7b91b53f0b 100644
--- a/code/game/mecha/equipment/weapons/weapons.dm
+++ b/code/game/mecha/equipment/weapons/weapons.dm
@@ -268,6 +268,7 @@
projectiles_cache = 300
projectiles_cache_max = 1200
projectiles_per_shot = 3
+ is_automatic = TRUE
variance = 6
randomspread = 1
projectile_delay = 2
@@ -308,6 +309,88 @@
harmful = TRUE
ammo_type = "lmg"
+/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/minigun
+ name = "\improper Minigun"
+ desc = "A heavy machine gun capable of rapidly firing 7.62mm rounds. ready for vehicle mounting, with internal ammo box."
+ icon_state = "mecha_uac2"
+ fire_sound = 'sound/f13weapons/antimaterielfire.ogg'
+ equip_cooldown = 1
+ projectile = /obj/item/projectile/bullet/a762
+ projectiles = 300
+ projectiles_cache = 300
+ projectiles_cache_max = 600
+ projectiles_per_shot = 1
+ variance = 6
+ is_automatic = TRUE
+ randomspread = 112
+ harmful = TRUE
+ ammo_type = "minigun"
+ var/overheat = 0
+ var/overheat_max = 160
+ var/heat_diffusion = 2.5 //How much heat is lost per tick
+ var/damage = 25
+
+
+
+/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/minigun/Initialize()
+ . = ..()
+ START_PROCESSING(SSobj, src)
+
+/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/minigun/Destroy()
+ STOP_PROCESSING(SSobj, src)
+ return ..()
+
+/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/minigun/process()
+ overheat = max(0, overheat - heat_diffusion)
+
+/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/minigun/action(atom/target, params)
+ if(!action_checks(target))
+ return 0
+ var/turf/curloc = get_turf(chassis)
+ var/turf/targloc = get_turf(target)
+ if (!targloc || !istype(targloc) || !curloc)
+ return 0
+ if (targloc == curloc)
+ return 0
+ if(overheat < overheat_max)
+ overheat += projectiles_per_shot
+ else
+ chassis.occupant_message("The gun's heat sensor locked the trigger to prevent barrel damage.")
+ return
+ chassis.occupant.DelayNextAction(3)
+ set_ready_state(0)
+ for(var/i=1 to get_shot_amount())
+ var/obj/item/projectile/A = new projectile(curloc)
+ A.firer = chassis.occupant
+ A.original = target
+ A.damage = damage
+ if(!A.suppressed && firing_effect_type)
+ new firing_effect_type(get_turf(src), chassis.dir)
+
+ var/spread = 0
+ if(variance)
+ if(randomspread)
+ spread = round((rand() - 0.5) * variance)
+ else
+ spread = round((i / projectiles_per_shot - 0.5) * variance)
+ A.preparePixelProjectile(target, chassis.occupant, params, spread)
+
+ A.fire()
+ overheat++
+ projectiles--
+ playsound(chassis, fire_sound, 50, 1)
+ chassis.occupant.DelayNextAction(1)
+
+ if(kickback)
+ chassis.newtonian_move(turn(chassis.dir,180))
+
+ return 1
+
+
+
+
+
+
/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/missile_rack
name = "\improper SRM-8 missile rack"
desc = "A weapon for combat exosuits. Launches light explosive missiles."
@@ -356,6 +439,89 @@
/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/proc/proj_init(obj/O)
return
+/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/anykind
+ name = "\improper Pheumonic launcher"
+ desc = "A weapon for combat exosuits. anything loaded in it."
+ icon_state = "mecha_grenadelnchr"
+ projectile = null
+ fire_sound = 'sound/weapons/grenadelaunch.ogg'
+ projectiles = 0
+ projectiles_cache = 15
+ projectiles_cache_max = 20
+ missile_speed = 1.5
+ equip_cooldown = 10
+ var/det_time = 20
+ ammo_type = "Anything"
+ var/list/obj/stuffs = new
+ var/open = FALSE
+
+/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/anykind/action(target)
+ if(!action_checks(target))
+ return
+ if(!stuffs.len)
+ chassis.occupant_message("Nothing to shoot!")
+ return
+ var/obj/O = stuffs[1]
+ playsound(chassis, fire_sound, 50, 1)
+ mecha_log_message("Launched a [O.name] from [name], targeting [target].")
+ stuffs -= stuffs[1]
+ proj_init(O)
+ var/turf/nextt = (get_turf(src))
+ O.forceMove(nextt)
+ O.throw_at(target, missile_range, missile_speed, chassis.occupant, FALSE, diagonals_first = diags_first)
+ return 1
+
+/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/anykind/proj_init(obj/ammo)
+ var/turf/T = get_turf(src)
+ message_admins("[ADMIN_LOOKUPFLW(chassis.occupant)] fired a [src] in [ADMIN_VERBOSEJMP(T)]")
+ log_game("[key_name(chassis.occupant)] fired a [src] in [AREACOORD(T)]")
+ if(istype(ammo, /obj/item/grenade/))
+ var/obj/item/grenade/payload = ammo
+ addtimer(CALLBACK(payload, TYPE_PROC_REF(/obj/item/grenade, prime)), det_time)
+
+/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/anykind/attackby(obj/item/W, mob/user, params)
+ if(open)
+ if(stuffs.len < projectiles_cache_max)
+ W.forceMove(src)
+ stuffs += W
+ projectiles++
+ else
+ to_chat(user, "The [src] is full!")
+ return
+ . = ..()
+
+/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/anykind/screwdriver_act(mob/living/carbon/user, obj/item/I)
+ if(user.a_intent != INTENT_DISARM)
+ if(open)
+ to_chat(user, "You close the [src]!.")
+ else
+ to_chat(user, "You open the [src]!.")
+ open = !open
+ return TRUE
+ . = ..()
+
+/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/anykind/attack_self(mob/user)
+ if(open && stuffs.len)
+ var/obj/selectedthing = input(user, "Chosee an item to take out.", "Stuffs inside") as null|anything in stuffs
+ if(!selectedthing)
+ return
+ stuffs -= selectedthing
+ projectiles--
+ selectedthing.forceMove(get_turf(src))
+ user.put_in_hand(selectedthing)
+ return
+ . = ..()
+
+/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/anykind/AltClick(mob/user)
+ if(open && stuffs.len)
+ for(var/obj/I in stuffs)
+ I.forceMove(get_turf(src))
+ stuffs -= I
+ projectiles--
+ to_chat(user, "You empty the [src]!.")
+ return
+ . = ..()
+
/obj/item/mecha_parts/mecha_equipment/weapon/ballistic/launcher/flashbang
name = "\improper SGL-6 grenade launcher"
diff --git a/code/game/sound.dm b/code/game/sound.dm
index 98466a6e6ce..095da3cc606 100644
--- a/code/game/sound.dm
+++ b/code/game/sound.dm
@@ -43,6 +43,9 @@
if(below_turf && istransparentturf(turf_source))
listeners += hearers(maxdistance,below_turf)
+ if(ismecha(source))
+ var/obj/mecha/mechasound = source
+ listeners += mechasound?.occupant
else
if(above_turf && istransparentturf(above_turf))
diff --git a/code/modules/keybindings/keybind/carbon.dm b/code/modules/keybindings/keybind/carbon.dm
index bd0e073826d..e7c736c4193 100644
--- a/code/modules/keybindings/keybind/carbon.dm
+++ b/code/modules/keybindings/keybind/carbon.dm
@@ -72,3 +72,39 @@
var/mob/living/carbon/C = user.mob
C.do_wield()
return TRUE
+
+/datum/keybinding/carbon/lookup
+ hotkey_keys = list(",")
+ name = "Look_up"
+ full_name = "Look up"
+ description = "looks up"
+ category = CATEGORY_CARBON
+
+/datum/keybinding/carbon/lookup/down(client/user)
+ var/mob/living/carbon/C = user.mob
+ C.lookup()
+ return TRUE
+
+/datum/keybinding/carbon/lookdown
+ hotkey_keys = list(".")
+ name = "Look_down"
+ full_name = "looks down"
+ description = "looks down your feet"
+ category = CATEGORY_CARBON
+
+/datum/keybinding/carbon/lookdown/down(client/user)
+ var/mob/living/carbon/C = user.mob
+ C.lookdown()
+ return TRUE
+
+/datum/keybinding/carbon/lookstop
+ hotkey_keys = list("-")
+ name = "Look_stop"
+ full_name = "stop looking"
+ description = "stop looking around and see foward"
+ category = CATEGORY_CARBON
+
+/datum/keybinding/carbon/lookstop/down(client/user)
+ var/mob/living/carbon/C = user.mob
+ C.stop_looking()
+ return TRUE
diff --git a/code/modules/mob/inventory.dm b/code/modules/mob/inventory.dm
index 3692d46be44..5e888b9fe61 100644
--- a/code/modules/mob/inventory.dm
+++ b/code/modules/mob/inventory.dm
@@ -329,6 +329,8 @@
if(!no_move && !(I.item_flags & DROPDEL)) //item may be moved/qdel'd immedietely, don't bother moving it
if (isnull(newloc))
I.moveToNullspace()
+ if(ismecha(loc))
+ I.forceMove(loc)
else
I.forceMove(newloc)
on_item_dropped(I)
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index f5cb6cff257..becd3945fbf 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -432,6 +432,9 @@
set category = "IC"
if(src.incapacitated())
to_chat(src, span_warning("You can't look up right now!"))
+ if(client.eye != src && !istype(client.eye, /obj/mecha))
+ stop_looking()
+ return
var/turf/T = SSmapping.get_turf_above(get_turf(src))
if(!istype(T, /turf/open/transparent/openspace))
if(istype(T, /turf/open) || istype(T, /turf/closed))
@@ -440,16 +443,82 @@
else
src.reset_perspective(T)
RegisterSignal(src, COMSIG_MOB_CLIENT_CHANGE_VIEW, PROC_REF(stop_looking_up)) //no binos/scops
- RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(stop_looking_up))
+ RegisterSignal(src, COMSIG_MOVABLE_MOVED,PROC_REF(followcameraup))
+ if(istype(loc, /obj/mecha))
+ RegisterSignal(loc, COMSIG_MOVABLE_MOVED,PROC_REF(followcameraup))
RegisterSignal(src, COMSIG_LIVING_STATUS_KNOCKDOWN, PROC_REF(stop_looking_up))
RegisterSignal(src, COMSIG_LIVING_STATUS_PARALYZE, PROC_REF(stop_looking_up))
RegisterSignal(src, COMSIG_LIVING_STATUS_UNCONSCIOUS, PROC_REF(stop_looking_up))
RegisterSignal(src, COMSIG_LIVING_STATUS_SLEEP, PROC_REF(stop_looking_up))
+/mob/living/verb/stop_looking()
+ set name = "Stop Looking"
+ set category = "IC"
+ src.stop_looking_up(null)
+
/mob/living/proc/stop_looking_up()
+ if(istype(loc, /obj/mecha))
+ UnregisterSignal(loc, COMSIG_MOVABLE_MOVED)
reset_perspective(null)
UnregisterSignal(src, list(COMSIG_LIVING_STATUS_PARALYZE, COMSIG_LIVING_STATUS_UNCONSCIOUS, COMSIG_LIVING_STATUS_SLEEP, COMSIG_LIVING_STATUS_KNOCKDOWN, COMSIG_MOVABLE_MOVED, COMSIG_MOB_CLIENT_CHANGE_VIEW))
+/mob/living/verb/lookdown()
+ set name = "Look down"
+ set category = "IC"
+ if(src.incapacitated())
+ to_chat(src, "You can't look down right now!")
+ if(client.eye != src && !istype(client.eye, /obj/mecha))
+ stop_looking()
+ return
+ var/turf/T = get_turf(src)
+ if(!istype(T, /turf/open/transparent/openspace))
+ var/turf/nt = get_step(T, dir)
+ if(!istype(nt, /turf/open/transparent/openspace))
+ if(istype(nt, /turf/open) || istype(nt, /turf/closed))
+ to_chat(src, "You look up at the floor. You can see floor.")
+ return
+ else
+ var/turf/nl = SSmapping.get_turf_below(nt)
+ src.reset_perspective(nl)
+ RegisterSignal(src, COMSIG_MOB_CLIENT_CHANGE_VIEW,PROC_REF(stop_looking_down)) //no binos/scops
+ RegisterSignal(src, COMSIG_MOVABLE_MOVED,PROC_REF(followcameradown))
+ if(istype(loc, /obj/mecha))
+ RegisterSignal(loc, COMSIG_MOVABLE_MOVED,PROC_REF(followcameradown))
+ RegisterSignal(src, COMSIG_LIVING_STATUS_KNOCKDOWN,PROC_REF(stop_looking_down))
+ RegisterSignal(src, COMSIG_LIVING_STATUS_PARALYZE,PROC_REF(stop_looking_down))
+ RegisterSignal(src, COMSIG_LIVING_STATUS_UNCONSCIOUS,PROC_REF(stop_looking_down))
+ RegisterSignal(src, COMSIG_LIVING_STATUS_SLEEP,PROC_REF(stop_looking_down))
+ else
+ var/turf/nl = SSmapping.get_turf_below(T)
+ src.reset_perspective(nl)
+ RegisterSignal(src, COMSIG_MOB_CLIENT_CHANGE_VIEW,PROC_REF(stop_looking_down)) //no binos/scops
+ RegisterSignal(src, COMSIG_MOVABLE_MOVED,PROC_REF(followcameradown))
+ if(istype(loc, /obj/mecha))
+ RegisterSignal(loc, COMSIG_MOVABLE_MOVED,PROC_REF(followcameradown))
+ RegisterSignal(src, COMSIG_LIVING_STATUS_KNOCKDOWN,PROC_REF(stop_looking_down))
+ RegisterSignal(src, COMSIG_LIVING_STATUS_PARALYZE,PROC_REF(stop_looking_down))
+ RegisterSignal(src, COMSIG_LIVING_STATUS_UNCONSCIOUS,PROC_REF(stop_looking_down))
+ RegisterSignal(src, COMSIG_LIVING_STATUS_SLEEP,PROC_REF(stop_looking_down))
+
+/mob/living/proc/stop_looking_down()
+ reset_perspective(null)
+ UnregisterSignal(src, list(COMSIG_LIVING_STATUS_PARALYZE, COMSIG_LIVING_STATUS_UNCONSCIOUS, COMSIG_LIVING_STATUS_SLEEP, COMSIG_LIVING_STATUS_KNOCKDOWN, COMSIG_MOVABLE_MOVED, COMSIG_MOB_CLIENT_CHANGE_VIEW))
+
+/mob/living/proc/followcameraup()
+ var/turf/T = get_turf(src)
+ var/turf/nl = SSmapping.get_turf_above(T)
+ if(istype(nl, /turf/open/transparent/openspace))
+ reset_perspective(nl)
+ else
+ reset_perspective(null)
+
+/mob/living/proc/followcameradown()
+ var/turf/T = get_turf(src)
+ var/turf/nl = SSmapping.get_turf_below(T)
+ if(istype(T, /turf/open/transparent/openspace))
+ reset_perspective(nl)
+ else
+ reset_perspective(null)
/mob/living/incapacitated(ignore_restraints = FALSE, ignore_grab = FALSE, check_immobilized = FALSE)
if(stat || IsUnconscious() || IsStun() || IsParalyzed() || (combat_flags & COMBAT_FLAG_HARD_STAMCRIT) || (check_immobilized && IsImmobilized()) || (!ignore_restraints && restrained(ignore_grab)))
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 52e8c0fa40b..087cde342a0 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -898,7 +898,7 @@ GLOBAL_VAR_INIT(exploit_warn_spam_prevention, 0)
//Can the mob interact() with an atom?
/mob/proc/can_interact_with(atom/A)
- return IsAdminGhost(src) || Adjacent(A) || A.hasSiliconAccessInArea(src)
+ return IsAdminGhost(src) || Adjacent(A) || A.hasSiliconAccessInArea(src) || A.bypass_interactions
//Can the mob use Topic to interact with machines
/mob/proc/canUseTopic(atom/movable/M, be_close=FALSE, no_dextery=FALSE, no_tk=FALSE)
diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm
index 62729a2835f..41a6ce0b5ea 100644
--- a/code/modules/projectiles/projectile.dm
+++ b/code/modules/projectiles/projectile.dm
@@ -562,6 +562,11 @@
if(spread_override)
setAngle(Angle + rand(-spread_override, spread_override))
var/turf/starting = get_turf(src)
+ if(original)
+ if(starting.z > original?.z)
+ starting = SSmapping.get_turf_below(starting)
+ if(starting.z < original?.z)
+ starting = SSmapping.get_turf_above(starting)
if(isnull(Angle)) //Try to resolve through offsets if there's no angle set.
if(isnull(xo) || isnull(yo))
stack_trace("WARNING: Projectile [type] deleted due to being unable to resolve a target after angle was null!")
diff --git a/icons/mecha/mecha_equipment.dmi b/icons/mecha/mecha_equipment.dmi
index 5e277af7cca..90929fff46c 100644
Binary files a/icons/mecha/mecha_equipment.dmi and b/icons/mecha/mecha_equipment.dmi differ