From e523d73dd9ac0db33adc8e0d54b115fa52e9046a Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Sat, 6 Sep 2025 01:53:25 -0300 Subject: [PATCH 01/60] Tank Desant: Rideable Tank And Overhauled Interactions --- code/__DEFINES/layers.dm | 3 + code/_onclick/human.dm | 17 + code/game/objects/effects/aliens.dm | 2 +- code/game/objects/items/explosives/mine.dm | 2 + .../structures/barricade/deployable.dm | 6 + code/game/turfs/open_space.dm | 21 + code/modules/cm_aliens/XenoStructures.dm | 4 + code/modules/cm_aliens/structures/trap.dm | 6 + .../structures/xeno_structures_boilertrap.dm | 8 + .../cm_marines/equipment/mortar/mortars.dm | 6 + code/modules/cm_marines/m2c.dm | 5 + .../modules/cm_tech/techs/marine/tier1/arc.dm | 13 +- code/modules/mob/living/carbon/carbon.dm | 9 + .../mob/living/carbon/xenomorph/XenoProcs.dm | 11 + .../abilities/ability_helper_procs.dm | 3 +- .../xenomorph/abilities/general_powers.dm | 11 + .../strains/castes/boiler/trapper.dm | 10 +- code/modules/mob/living/living.dm | 101 ++++- code/modules/mob/living/living_defense.dm | 5 + code/modules/mob/living/living_defines.dm | 4 + code/modules/mob/mob_grab.dm | 19 + code/modules/mob/mob_verbs.dm | 17 + code/modules/projectiles/projectile.dm | 16 + code/modules/vehicles/hardpoints/hardpoint.dm | 3 + .../vehicles/interior/interactable/seats.dm | 4 + .../vehicles/multitile/multitile_movement.dm | 12 +- code/modules/vehicles/tank/tank.dm | 368 +++++++++++++++-- code/modules/vehicles/tank/tank_helpers.dm | 374 ++++++++++++++++++ colonialmarines.dme | 1 + 29 files changed, 1022 insertions(+), 39 deletions(-) create mode 100644 code/modules/vehicles/tank/tank_helpers.dm diff --git a/code/__DEFINES/layers.dm b/code/__DEFINES/layers.dm index fd93d5657f84..98c3bf2300bb 100644 --- a/code/__DEFINES/layers.dm +++ b/code/__DEFINES/layers.dm @@ -137,6 +137,9 @@ /// For WEATHER #define WEATHER_LAYER 4.14 +/// For mobs riding atop a tank +#define TANK_RIDER_LAYER 4.51 + //#define FLY_LAYER 5 #define RIPPLE_LAYER 5.1 diff --git a/code/_onclick/human.dm b/code/_onclick/human.dm index 2dedf48debe6..15505c3b3809 100644 --- a/code/_onclick/human.dm +++ b/code/_onclick/human.dm @@ -157,6 +157,23 @@ grab_level = GRAB_CARRY + if(tank_on_top_of && !target.tank_on_top_of) + var/obj/vehicle/multitile/tank/T = tank_on_top_of + + if(istype(T)) + var/turf/target_turf = get_turf(target) + var/adjacent_to_tank = FALSE + for(var/turf/tank_turf in T.locs) + if(get_dist(target_turf, tank_turf) <= 1 && target_turf != tank_turf) + adjacent_to_tank = TRUE + break + + if(adjacent_to_tank) + target.forceMove(user.loc) + T.mark_on_top(target) + target.update_transform(TRUE) + return + target.Move(user.loc, get_dir(target.loc, user.loc)) target.update_transform(TRUE) diff --git a/code/game/objects/effects/aliens.dm b/code/game/objects/effects/aliens.dm index 33119816e748..49feff7c76ae 100644 --- a/code/game/objects/effects/aliens.dm +++ b/code/game/objects/effects/aliens.dm @@ -43,7 +43,7 @@ density = FALSE opacity = FALSE anchored = TRUE - layer = ABOVE_OBJ_LAYER + layer = TANK_RIDER_LAYER mouse_opacity = MOUSE_OPACITY_TRANSPARENT var/datum/cause_data/cause_data diff --git a/code/game/objects/items/explosives/mine.dm b/code/game/objects/items/explosives/mine.dm index 7e97b3353796..58357bc90e62 100644 --- a/code/game/objects/items/explosives/mine.dm +++ b/code/game/objects/items/explosives/mine.dm @@ -227,6 +227,8 @@ return if(HAS_TRAIT(enemy, TRAIT_ABILITY_BURROWED)) return + if(enemy.is_on_tank_hull()) + return enemy.visible_message(SPAN_DANGER("[icon2html(src, viewers(src))] The [name] clicks as [enemy] moves in front of it."), SPAN_DANGER("[icon2html(src, enemy)] The [name] clicks as you move in front of it."), diff --git a/code/game/objects/structures/barricade/deployable.dm b/code/game/objects/structures/barricade/deployable.dm index 727629f763b9..28f62aecc2c4 100644 --- a/code/game/objects/structures/barricade/deployable.dm +++ b/code/game/objects/structures/barricade/deployable.dm @@ -147,6 +147,12 @@ to_chat(usr, SPAN_WARNING("[singular_name] cannot be built here!")) return + if(isliving(user)) + var/mob/living/L = user + if(L.is_on_tank_hull()) + to_chat(user, SPAN_WARNING("[singular_name] cannot be built here!")) + return + user.visible_message(SPAN_NOTICE("[user] begins deploying [singular_name]."), SPAN_NOTICE("You begin deploying [singular_name].")) diff --git a/code/game/turfs/open_space.dm b/code/game/turfs/open_space.dm index 75a6785e4a30..90973ab80f47 100644 --- a/code/game/turfs/open_space.dm +++ b/code/game/turfs/open_space.dm @@ -68,7 +68,18 @@ GLOBAL_DATUM_INIT(openspace_backdrop_one_for_all, /atom/movable/openspace_backdr while(istype(below, /turf/open_space)) below = SSmapping.get_turf_below(below) + var/obj/vehicle/multitile/tank/tank_at_destination = null + for(var/obj/vehicle/multitile/tank/T in below.contents) + if(below in T.locs) + tank_at_destination = T + break + user.forceMove(below) + + if(tank_at_destination && isliving(user)) + var/mob/living/L = user + tank_at_destination.mark_on_top(L) + return /turf/open_space/proc/check_fall(atom/movable/movable) @@ -85,6 +96,16 @@ GLOBAL_DATUM_INIT(openspace_backdrop_one_for_all, /atom/movable/openspace_backdr movable.forceMove(below) movable.onZImpact(below, height) + var/obj/vehicle/multitile/tank/tank_at_destination = null + for(var/obj/vehicle/multitile/tank/T in below.contents) + if(below in T.locs) + tank_at_destination = T + break + + if(tank_at_destination && isliving(movable)) + var/mob/living/L = movable + tank_at_destination.mark_on_top(L) + /turf/solid_open_space name = "open space" diff --git a/code/modules/cm_aliens/XenoStructures.dm b/code/modules/cm_aliens/XenoStructures.dm index 90c6219d032d..6bde6a7295b7 100644 --- a/code/modules/cm_aliens/XenoStructures.dm +++ b/code/modules/cm_aliens/XenoStructures.dm @@ -228,6 +228,10 @@ if(HAS_TRAIT(H, TRAIT_HAULED)) return + // mobs atop the tank shouldn't get foot-stabbed + if(H.is_on_tank_hull()) + return + H.apply_armoured_damage(damage, penetration = penetration, def_zone = pick(target_limbs)) H.last_damage_data = construction_data diff --git a/code/modules/cm_aliens/structures/trap.dm b/code/modules/cm_aliens/structures/trap.dm index 10665b00b8ff..009e69120f24 100644 --- a/code/modules/cm_aliens/structures/trap.dm +++ b/code/modules/cm_aliens/structures/trap.dm @@ -94,6 +94,9 @@ if(RESIN_TRAP_HUGGER) if(can_hug(victim, hivenumber) && !isyautja(victim) && !issynth(victim) && !isthrall(victim)) var/mob/living/victim_mob = victim + // tank treads block the hugger's hole. This behavior can be also added to normal acid traps. + if(victim_mob.tank_on_top_of && istype(victim_mob.tank_on_top_of) && (get_turf(victim_mob) in victim_mob.tank_on_top_of.locs)) + return victim_mob.visible_message(SPAN_WARNING("[victim_mob] trips on [src]!")) to_chat(victim_mob, SPAN_DANGER("You trip on [src]!")) victim_mob.apply_effect(1, WEAKEN) @@ -109,6 +112,9 @@ if(isxeno(victim)) var/mob/living/carbon/xenomorph/victim_xeno = victim if(victim_xeno.hivenumber != hivenumber) + // just in case a Greeno decides to ride on the tank. + if(victim_xeno.tank_on_top_of && istype(victim_xeno.tank_on_top_of) && (get_turf(victim_xeno) in victim_xeno.tank_on_top_of.locs)) + return trigger_trap() if(isVehicleMultitile(victim) && trap_type != RESIN_TRAP_GAS) trigger_trap() diff --git a/code/modules/cm_aliens/structures/xeno_structures_boilertrap.dm b/code/modules/cm_aliens/structures/xeno_structures_boilertrap.dm index 0194ea74d3a6..ef2368f57133 100644 --- a/code/modules/cm_aliens/structures/xeno_structures_boilertrap.dm +++ b/code/modules/cm_aliens/structures/xeno_structures_boilertrap.dm @@ -68,6 +68,10 @@ if (X.hivenumber != hivenumber) trigger_trap(A) else if(ishuman(A)) + // boiler's foul resin 'spikes' do not stab you through the tank + var/mob/living/M = A + if(M && M.is_on_tank_hull()) + return trigger_trap(A) return ..() @@ -91,4 +95,8 @@ return if(ishuman(A)) + // mobs atop the tank shouldn't trigger tripwires + var/mob/living/M = A + if(M && M.is_on_tank_hull()) + return linked_trap.trigger_trap(A) diff --git a/code/modules/cm_marines/equipment/mortar/mortars.dm b/code/modules/cm_marines/equipment/mortar/mortars.dm index 99dd36b20548..c5d02cb46717 100644 --- a/code/modules/cm_marines/equipment/mortar/mortars.dm +++ b/code/modules/cm_marines/equipment/mortar/mortars.dm @@ -611,6 +611,12 @@ if(CEILING_IS_PROTECTED(area.ceiling, CEILING_PROTECTION_TIER_1) && is_ground_level(deploy_turf.z)) to_chat(user, SPAN_WARNING("You probably shouldn't deploy [src] indoors.")) return + if(isliving(user)) + var/mob/living/L = user + if(L.is_on_tank_hull()) + to_chat(user, SPAN_WARNING("You can't deploy [src] here!")) + return + user.visible_message(SPAN_NOTICE("[user] starts deploying [src]."), SPAN_NOTICE("You start deploying [src].")) playsound(deploy_turf, 'sound/items/Deconstruct.ogg', 25, 1) diff --git a/code/modules/cm_marines/m2c.dm b/code/modules/cm_marines/m2c.dm index 23066841db91..21fa49be278e 100644 --- a/code/modules/cm_marines/m2c.dm +++ b/code/modules/cm_marines/m2c.dm @@ -104,6 +104,11 @@ if(OT.density || !isturf(OT) || !OT.allow_construction) to_chat(user, SPAN_WARNING("You can't set up \the [src] here.")) return FALSE + if(isliving(user)) + var/mob/living/L = user + if(L.is_on_tank_hull()) + to_chat(user, SPAN_WARNING("You can't set up \the [src] here.")) + return FALSE if(rotate_check.density) to_chat(user, SPAN_WARNING("You can't set up \the [src] that way, there's a wall behind you!")) return FALSE diff --git a/code/modules/cm_tech/techs/marine/tier1/arc.dm b/code/modules/cm_tech/techs/marine/tier1/arc.dm index 5a7b84a9aa0a..20497b7d431a 100644 --- a/code/modules/cm_tech/techs/marine/tier1/arc.dm +++ b/code/modules/cm_tech/techs/marine/tier1/arc.dm @@ -16,11 +16,14 @@ /datum/tech/arc/can_unlock(mob/unlocking_mob) . = ..() - var/obj/structure/machinery/cm_vending/gear/vehicle_crew/gearcomp = GLOB.VehicleGearConsole - - if(gearcomp.selected_vehicle == "TANK") - to_chat(unlocking_mob, SPAN_WARNING ("A vehicle has already been selected for this operation.")) - return FALSE + // ARC can be bought even after the Tank has been selected. + // With all of the TANK's gear being taken at roundstart (hopefully), this means that the ARC ... + // ... can overwrite the vehicle ASRS' contents without much of an issue. + + // var/obj/structure/machinery/cm_vending/gear/vehicle_crew/gearcomp = GLOB.VehicleGearConsole + // if(gearcomp.selected_vehicle == "TANK") + // to_chat(unlocking_mob, SPAN_WARNING ("A vehicle has already been selected for this operation.")) + // return FALSE return TRUE diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 109b788b8d05..1f30f2e8e7c8 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -29,6 +29,15 @@ halimage = null halbody = null +// Same as the parent proc, but adds 1 additional check for climb_down +/mob/living/carbon/_handle_tank_edge_move(NewLoc, direct) + var/obj/vehicle/multitile/tank/T = tank_on_top_of + var/result = ..() + if(result) + var/turf/edge = get_step(get_turf(src), direct) + if(T.climb_down(src, edge)) + return TRUE + return result /mob/living/carbon/Move(NewLoc, direct) . = ..() diff --git a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm index 5a9b099f2123..505bfdf5a16b 100644 --- a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm +++ b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm @@ -368,6 +368,17 @@ obj_launch_collision(O) return + // Castes that can pounce can use this ability to instantly climb ontop or down a tank with no delay. + var/obj/vehicle/multitile/tank/T = null + if(istype(O, /obj/vehicle/multitile/tank)) + T = O + if(T) + var/turf/current = get_turf(src) + var/turf/facing = get_step(current, dir) + if(facing && (facing in T.locs)) + forceMove(facing) + T.mark_on_top(src) + if (pounceAction.should_destroy_objects) if(istype(O, /obj/structure/surface/table) || istype(O, /obj/structure/surface/rack) || istype(O, /obj/structure/window_frame)) var/obj/structure/S = O diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/ability_helper_procs.dm b/code/modules/mob/living/carbon/xenomorph/abilities/ability_helper_procs.dm index 69027af7b1b3..f010695c76df 100644 --- a/code/modules/mob/living/carbon/xenomorph/abilities/ability_helper_procs.dm +++ b/code/modules/mob/living/carbon/xenomorph/abilities/ability_helper_procs.dm @@ -300,7 +300,8 @@ var/atom/movable/temp = new spray_path() var/atom/movable/blocker = LinkBlocked(temp, prev_turf, turf) qdel(temp) - if(blocker) + // despite the tank being a blocker, acid sprays should still go through it to hit marines ontop. + if(blocker && !locate(/obj/vehicle/multitile/tank) in turf.contents) blocker.acid_spray_act(src) break diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/general_powers.dm b/code/modules/mob/living/carbon/xenomorph/abilities/general_powers.dm index e4dedbe44c4b..c82b797026fb 100644 --- a/code/modules/mob/living/carbon/xenomorph/abilities/general_powers.dm +++ b/code/modules/mob/living/carbon/xenomorph/abilities/general_powers.dm @@ -1051,18 +1051,29 @@ if(distance > stab_range) return FALSE + // allows tailstabs into mobs riding atop the tank + var/obj/vehicle/multitile/tank/skip_tank = null + if(ismob(targetted_atom)) + var/mob/living/M = targetted_atom + if(istype(M) && M.tank_on_top_of) + skip_tank = M.tank_on_top_of + var/list/turf/path = get_line(stabbing_xeno, targetted_atom, include_start_atom = FALSE) for(var/turf/path_turf as anything in path) if(path_turf.density) to_chat(stabbing_xeno, SPAN_WARNING("There's something blocking our strike!")) return FALSE for(var/obj/path_contents in path_turf.contents) + if(skip_tank && path_contents == skip_tank) + continue if(path_contents != targetted_atom && path_contents.density && !path_contents.throwpass) to_chat(stabbing_xeno, SPAN_WARNING("There's something blocking our strike!")) return FALSE var/atom/barrier = path_turf.handle_barriers(stabbing_xeno, null, (PASS_MOB_THRU_XENO|PASS_OVER_THROW_MOB|PASS_TYPE_CRAWLER)) if(barrier != path_turf) + if(skip_tank && barrier == skip_tank) + continue var/tail_stab_cooldown_multiplier = barrier.handle_tail_stab(stabbing_xeno) if(!tail_stab_cooldown_multiplier) to_chat(stabbing_xeno, SPAN_WARNING("There's something blocking our strike!")) diff --git a/code/modules/mob/living/carbon/xenomorph/strains/castes/boiler/trapper.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/boiler/trapper.dm index e56721299d52..751bfeae59ee 100644 --- a/code/modules/mob/living/carbon/xenomorph/strains/castes/boiler/trapper.dm +++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/boiler/trapper.dm @@ -226,12 +226,18 @@ xeno.visible_message(SPAN_XENOWARNING("[xeno] fires a blast of acid at [affected_atom]!"), SPAN_XENOWARNING("We fire a blast of acid at [affected_atom]!")) - var/turf/target_turf = locate(affected_atom.x, affected_atom.y, affected_atom.z) + // Allows acid shotgun ability to play nice with rideable tank + var/atom/shot_target + if(ismob(affected_atom)) + shot_target = affected_atom + else + shot_target = locate(affected_atom.x, affected_atom.y, affected_atom.z) + var/obj/projectile/proj = new(xeno.loc, create_cause_data("acid shotgun", xeno)) var/datum/ammo/ammoDatum = new ammo_type() proj.generate_bullet(ammoDatum) - proj.fire_at(target_turf, xeno, xeno, ammoDatum.max_range, ammoDatum.shell_speed) + proj.fire_at(shot_target, xeno, xeno, ammoDatum.max_range, ammoDatum.shell_speed) apply_cooldown() return ..() diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index bc9e86fa3558..9b9a8985ecc1 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -156,6 +156,76 @@ return +/** + * _handle_tank_edge_move blocks consensual movement outside of tank tiles, unless you invoke climb_down() + * + * This proc makes it so that you cannot instantly dismount the tank, nor accidentally walk out of it. + * Marines and xenos alike become unable to dismount unless: + * - They are dismounted forcefully (e.g, by an explosion, grab, et-cetera) + * > Marines eat a 3 second stun if this happens. Xenos currently do not get punished. + * - They use some kind of skill to dismount (e.g pouncing outside) + * - They wait 1 second until climb_down finishes. + * + * OOPS! As a fix for LINTERS, climb_down() is called on the proc in carbon.dm + * + * Arguments: + * * NewLoc - Wherever we're trying to step down towards. + * * Direct - Direction we're facing + * + * Returns: + * * TRUE - When dismounting is impossible (blocked turf) or consensual (climb_down) + * * FALSE - When the mob dismounts improperly, and must be punished. + */ +/mob/living/proc/_handle_tank_edge_move(NewLoc, direct) + var/obj/vehicle/multitile/tank/T = tank_on_top_of + + if(!T) + return FALSE + + var/turf/here = get_turf(src) + if(!(here in T.locs)) + T.clear_on_top(src) + return FALSE + + // only care about steps that would leave the hull. + if(isturf(NewLoc) && !(NewLoc in T.locs)) + if(!(flags_atom & DIRLOCK)) + setDir(direct) + SEND_SIGNAL(src, COMSIG_MOB_MOVE_OR_LOOK, TRUE, direct, direct) + if(pulledby || throwing) + return FALSE + var/turf/edge = get_step(here, direct) + if(!edge || edge.z != z || (edge in T.locs)) + return FALSE + if(T._blocked_except_mobs(edge)) + return TRUE + // special interaction for simple_animals and (edge case) silicons... + // ...to prevent SpacemanDMM from sleeping and still allow dismounts. + if(!iscarbon(src)) + T.clear_on_top(src) + src.forceMove(NewLoc) + return TRUE + return TRUE + + return FALSE + +/** + * is_on_tank_hull checks if a mob has a valid tank_on_top_of. + * + * This proc checks the tank_on_top_of var of a mob. + * If it is anything except null, it means this mob is atop a tank tile. + * + * Returns: + * * TRUE - If there is a valid tile under the tank, and if mob and tank are on the same Z-level. + * * FALSE - If tank_on_top_of is null, if the Z-levels of the mob and Tank mistmach, if the tile Tu is invalid, + * * ...Or if the tile Tu is not, in fact, underneath a tank. + */ +/mob/living/proc/is_on_tank_hull() + var/obj/vehicle/multitile/tank/T = tank_on_top_of + if(!T || z != T.z) return FALSE + var/turf/Tu = get_turf(src) + return Tu && (Tu in T.locs) + /mob/living/Move(NewLoc, direct) if(lying_angle != 0) lying_angle_on_movement(direct) @@ -165,6 +235,18 @@ else return FALSE + if(is_on_tank_hull()) + if(_handle_tank_edge_move(NewLoc, direct)) + return FALSE + if(isturf(NewLoc)) + var/obj/vehicle/multitile/tank/T = tank_on_top_of + if(T && !(NewLoc in T.locs)) + if(ishuman(src)) + var/mob/living/carbon/human/H = src + H.apply_effect(3, WEAKEN) + playsound(src, "punch", 25, TRUE) + T.clear_on_top(src) + var/atom/movable/pullee = pulling if(pullee && get_dist(src, pullee) > 1) //Is the pullee adjacent? if(!pullee.clone || (pullee.clone && get_dist(src, pullee.clone) > 2)) //Be lenient with the close @@ -377,6 +459,23 @@ now_pushing = FALSE return + // tank-related collision rules + var/src_on_tank = is_on_tank_hull() + var/target_on_tank = living_mob.is_on_tank_hull() + + if(src_on_tank != target_on_tank) + now_pushing = FALSE + return + + if(src_on_tank && target_on_tank) + if(tank_on_top_of == living_mob.tank_on_top_of) + var/obj/vehicle/multitile/tank/T = tank_on_top_of + var/turf/destination = get_step(living_mob.loc, dir) + if(destination && !(destination in T.locs)) + if(src.a_intent != INTENT_HELP || living_mob.a_intent != INTENT_HELP) + now_pushing = FALSE + return + if(!living_mob.buckled && !living_mob.anchored) var/mob_swap //the puller can always swap with its victim if on grab intent @@ -620,7 +719,7 @@ //so mob lying always appear behind standing mobs, but dead ones appear behind living ones if(pulledby && pulledby.grab_level == GRAB_CARRY) layer = ABOVE_MOB_LAYER - else if (body_position == LYING_DOWN && stat == DEAD) + else if (body_position == LYING_DOWN && stat == DEAD && !is_on_tank_hull()) layer = LYING_DEAD_MOB_LAYER // Dead mobs should layer under living ones else if(body_position == LYING_DOWN && layer == initial(layer)) //to avoid things like hiding larvas. //i have no idea what this means layer = LYING_LIVING_MOB_LAYER diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index d892c96b90a5..d113055b404d 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -224,9 +224,14 @@ clear_fullscreen("weather") /mob/living/handle_flamer_fire(obj/flamer_fire/fire, damage, delta_time) + if(is_on_tank_hull()) + return . = ..() fire.set_on_fire(src) /mob/living/handle_flamer_fire_crossed(obj/flamer_fire/fire) + if(is_on_tank_hull()) + return . = ..() fire.set_on_fire(src) + diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index 197d3018d89b..1e7dd21b836a 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -144,3 +144,7 @@ // for multiz looking up var/atom/observed_atom + + // keeps track of the tank we are currently riding atop of. + // unecessary, but saves us from having to do a locate() for it on a turf every time we need to check it. + var/tmp/obj/vehicle/multitile/tank/tank_on_top_of = null diff --git a/code/modules/mob/mob_grab.dm b/code/modules/mob/mob_grab.dm index a768d17f8f9d..fdc1ef3da192 100644 --- a/code/modules/mob/mob_grab.dm +++ b/code/modules/mob/mob_grab.dm @@ -106,6 +106,25 @@ playsound(loc, 'sound/weapons/thudswoosh.ogg', 25, 1, 7) user.visible_message(SPAN_WARNING("[user] holds [victim] by the neck and starts choking them!"), null, null, 5) msg_admin_attack("[key_name(user)] started to choke [key_name(victim)] at [get_area_name(victim)]", victim.loc.x, victim.loc.y, victim.loc.z) + + // allows pulling ppl onto the tank if you are already ontop of it.. + // the idea is to allow the tank to charge into an incend OB and for corpsmen ontop to recover people. + if(user.tank_on_top_of && !victim.tank_on_top_of) + var/obj/vehicle/multitile/tank/T = user.tank_on_top_of + if(istype(T)) + var/turf/victim_turf = get_turf(victim) + var/adjacent_to_tank = FALSE + for(var/turf/tank_turf in T.locs) + if(get_dist(victim_turf, tank_turf) <= 1 && victim_turf != tank_turf) + adjacent_to_tank = TRUE + break + + if(adjacent_to_tank) + victim.forceMove(user.loc) + T.mark_on_top(victim) + victim.update_transform(TRUE) + return + victim.Move(user.loc, get_dir(victim.loc, user.loc)) victim.update_transform(TRUE) diff --git a/code/modules/mob/mob_verbs.dm b/code/modules/mob/mob_verbs.dm index df802d9b5357..f25596a7aa10 100644 --- a/code/modules/mob/mob_verbs.dm +++ b/code/modules/mob/mob_verbs.dm @@ -252,17 +252,34 @@ if(observed_atom) QDEL_NULL(observed_atom) + if(buckled && istype(buckled, /obj/structure/bed/chair/comfy/vehicle)) + var/obj/structure/bed/chair/comfy/vehicle/vehicle_seat = buckled + if(vehicle_seat.vehicle && (vehicle_seat.seat == VEHICLE_DRIVER || vehicle_seat.seat == VEHICLE_GUNNER)) + if(client) + client.change_view(8, vehicle_seat.vehicle) + vehicle_seat.vehicle.set_seated_mob(vehicle_seat.seat, src) return if(HAS_TRAIT(src, TRAIT_ABILITY_BURROWED)) to_chat(src, SPAN_WARNING("We cannot look up here, we are burrowed!")) return + var/turf/vehicle_turf = null + if(buckled && istype(buckled, /obj/structure/bed/chair/comfy/vehicle)) + var/obj/structure/bed/chair/comfy/vehicle/vehicle_seat = buckled + if(vehicle_seat.vehicle && (vehicle_seat.seat == VEHICLE_DRIVER || vehicle_seat.seat == VEHICLE_GUNNER)) + vehicle_turf = get_turf(vehicle_seat.vehicle) + else + to_chat(src, SPAN_WARNING("You cannot look up from this position.")) + return + if(!isturf(loc)) to_chat(src, SPAN_WARNING("You cannot look up here.")) return var/turf/above = locate(x, y, z+1) + if(!(vehicle_turf == null)) + above = locate(vehicle_turf.x, vehicle_turf.y, vehicle_turf.z+1) if(!istransparentturf(above)) to_chat(src, SPAN_WARNING("You cannot look up here.")) diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index 79c7fa08bc34..17fdf12a2896 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -467,6 +467,16 @@ return FALSE permutated |= O + // this block allows us to shoot people ontop of the tank by sprite clicking + var/obj/vehicle/multitile/tank/M = _owning_tank_of(O) + if(M) + var/mob/living/F = isliving(firer) ? firer : null + var/mob/living/OR = isliving(original) ? original : null + if(F && F.tank_on_top_of == M) + return FALSE + if(OR && OR.tank_on_top_of == M) + return FALSE + var/hit_chance = O.get_projectile_hit_boolean(src) if(hit_chance) // Calculated from combination of both ammo accuracy and gun accuracy SEND_SIGNAL(src, COMSIG_BULLET_PRE_HANDLE_OBJ, O) @@ -1298,6 +1308,12 @@ if(dx == -1 || dx == 1) return TRUE +// helper proc to see if we are about to hit a tank +/obj/projectile/proc/_owning_tank_of(atom/A) + if(istype(A, /obj/vehicle/multitile/tank)) + return A + return null + /obj/projectile/vulture accuracy_range_falloff = 10 /// The odds of hitting a xeno in less than your gun's range. Doesn't apply to humans. diff --git a/code/modules/vehicles/hardpoints/hardpoint.dm b/code/modules/vehicles/hardpoints/hardpoint.dm index a75fb9353b43..c09c56b0c4d8 100644 --- a/code/modules/vehicles/hardpoints/hardpoint.dm +++ b/code/modules/vehicles/hardpoints/hardpoint.dm @@ -630,6 +630,9 @@ fire_angle += rand(-total_scatter_angle, total_scatter_angle) target = get_angle_target_turf(curloc, fire_angle, 30) + if(curloc.z != targloc.z) + target = locate(target.x, target.y, targloc.z) + return target /// Get turf at hardpoint origin offset, used as the muzzle. diff --git a/code/modules/vehicles/interior/interactable/seats.dm b/code/modules/vehicles/interior/interactable/seats.dm index 7ab4419f2107..2faedb5427aa 100644 --- a/code/modules/vehicles/interior/interactable/seats.dm +++ b/code/modules/vehicles/interior/interactable/seats.dm @@ -47,6 +47,10 @@ M.client.pixel_x = 0 M.client.pixel_y = 0 M.reset_view() + if(isliving(M)) + var/mob/living/living_mob = M + if(living_mob.observed_atom) + QDEL_NULL(living_mob.observed_atom) else if(M.stat == DEAD) unbuckle() diff --git a/code/modules/vehicles/multitile/multitile_movement.dm b/code/modules/vehicles/multitile/multitile_movement.dm index f38ddb10327a..3af84ff76e58 100644 --- a/code/modules/vehicles/multitile/multitile_movement.dm +++ b/code/modules/vehicles/multitile/multitile_movement.dm @@ -163,11 +163,12 @@ if(T in old_turfs) continue - if(!T.Enter(src)) + if(!T.Enter(src) || locate(/obj/structure/shuttle/part) in T) can_move = FALSE // Crashed with something that stopped us if(!can_move) + on_crash() move_momentum = floor(move_momentum/2) update_next_move() interior_crash_effect() @@ -304,3 +305,12 @@ return else cell_explosion(target, explosion_strength, explosion_falloff, EXPLOSION_FALLOFF_SHAPE_LINEAR, null, cause_data) + +/** + * Base proc for defining special behavior when crashing + * + * on_crash is a proc meant to be overridable by child classes + * That way, all of the effects that happen when crashing are in the same spot. + */ +/obj/vehicle/multitile/proc/on_crash() + return diff --git a/code/modules/vehicles/tank/tank.dm b/code/modules/vehicles/tank/tank.dm index 981c37c4ba50..832a45f6b298 100644 --- a/code/modules/vehicles/tank/tank.dm +++ b/code/modules/vehicles/tank/tank.dm @@ -75,6 +75,8 @@ explosive_resistance = 400 + var/list/on_top_mobs = list() /// keeps track of all mobs currently atop the tank + /obj/vehicle/multitile/tank/initialize_cameras(change_tag = FALSE) if(!camera) camera = new /obj/structure/machinery/camera/vehicle(src) @@ -147,11 +149,31 @@ if(!(locate(/obj/item/hardpoint/locomotion/treads) in hardpoints)) return FALSE - return ..() + // this if statement seems redundant with var/success = ..() below + // but it prevents a massive performance overhead: + // we only run transform math when we are ready to move... + // ...instead of every instant while a movement key is held + if(!(world.time < next_move)) + // PRE-STATE snapshot + var/list/oldc = _current_center() + var/old_cx = oldc[1] + var/old_cy = oldc[2] + var/old_dir = dir + + var/success = ..() + if(success) + // POST-STATE snapshot + var/list/newc = _current_center() + var/new_cx = newc[1] + var/new_cy = newc[2] + var/new_dir = dir + if(new_cx != old_cx || new_cy != old_cy || new_dir != old_dir) + _update_riders_after_motion(old_cx, old_cy, old_dir, new_cx, new_cy, new_dir) + revalidate_on_top() + return TRUE if(user != seats[VEHICLE_GUNNER]) return FALSE - var/obj/item/hardpoint/holder/tank_turret/T = null for(var/obj/item/hardpoint/holder/tank_turret/TT in hardpoints) T = TT @@ -164,42 +186,332 @@ T.user_rotation(user, turning_angle(T.dir, direction)) update_icon() - return TRUE -/obj/vehicle/multitile/tank/MouseDrop_T(mob/dropped, mob/user) - . = ..() - if((dropped != user) || !isxeno(user)) +// !!!! No point in keeping this now that you can freely climb onto the tank. !!!! +// Unless, maybe, you end up stuck in the center (turret) tile. + +// /obj/vehicle/multitile/tank/MouseDrop_T(mob/dropped, mob/user) +// . = ..() +// if((dropped != user) || !isxeno(user)) +// return + +// if(health > 0) +// to_chat(user, SPAN_XENO("We can't jump over [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 over [src] is obstructed!")) +// return + +// // Now we check to make sure the turf on the other side of the tank isn't dense too +// current_turf = get_step(current_turf, dir_to_go) +// if(current_turf.density) +// to_chat(user, SPAN_XENO("The path over [src] is obstructed!")) +// return + +// to_chat(user, SPAN_XENO("We begin to jump over [src]...")) +// if(!do_after(user, 3 SECONDS, INTERRUPT_ALL, BUSY_ICON_HOSTILE)) +// to_chat(user, SPAN_XENO("We stop jumping over [src].")) +// return + +// user.forceMove(current_turf) +// to_chat(user, SPAN_XENO("We jump to the other side of [src].")) + +/** + * Applies a jousting effect when crashing above a certain speed. + * + * This is a somewhat hacky implementation, since the maximum momentum of vehicles + * can change based on the type of wheels used. IE: Reinforced treads make it easier to attain + * the needed 1.6 momentum, while it's a bit harder with normal treads. + * + * 1.6, while a magic number and a code smell, was the value that worked best to prevent riders from being scattered + * at low speeds, while also ensuring they were scattered after the tank accelerated 2 tiles on normal treads. + */ +/obj/vehicle/multitile/tank/on_crash() + if(abs(move_momentum) < 1.6) + return + _scatter_riders_on_crash() + revalidate_on_top() + +/** + * Collided handles what happens when something bumps into the tank. + * + * If we are being bumped by our riders, we'll do nothing. + * Otherwise, we will attempt to climb if we pass through the edge cases on each if statement. + * + * Arguments: + * * atom/movable/AM - The atom bumping into the tank. + */ +/obj/vehicle/multitile/tank/Collided(atom/movable/AM) + if(_is_our_rider(AM)) + return + + if(!ismob(AM)) + if(istype(AM, /obj)) + var/obj/O = AM + if(O.buckled_mob) + Collided(O.buckled_mob) + return ..() + + var/mob/living/M = AM + if(!istype(M)) + return ..() + if(M.action_busy) + return + if(get_turf(M) in locs) + return + if(M.pulledby || M.throwing) + return + var/turf/facing_turf = get_step(get_turf(M), M.dir) + if(!facing_turf) + return ..() + + climb_onto(M, facing_turf) + return + +/** + * climb_onto is called by Collided to allow a mob to climb onto the tank. + * + * We run a number of edge case checks first, then we begin the process of climbing properly. + * It takes 1.5 seconds, and, after that, if the spot is still reachable (AKA, if the tank hasn't driven away) + * then we'll climb up! + * + * We also call _carry_move_with_grabs to pull grabbed mobs in with us. + * ^^ Kind of how you can grab people when stepping into a window frame to pull them in. + * + * Arguments: + * * mob/living/user - The mob trying to get atop the tank + * * turf/preferred - The exact 'turf' of the tank we want to climb upon. + * * As in: Bumping into the middle tile of a side shouldn't make you climb up onto another tile. + */ +/obj/vehicle/multitile/tank/proc/climb_onto(mob/living/user, turf/preferred) + if(!istype(user)) + return + if(user.action_busy) return + if(!_validate_climb_target(user, preferred, TRUE)) { + to_chat(user, SPAN_WARNING("You can't climb there.")) + return + } + user.visible_message( + SPAN_WARNING("[user] starts climbing onto [src]."), + SPAN_WARNING("You start climbing onto [src].")) + if(!do_after(user, 1.5 SECONDS, INTERRUPT_MOVED, BUSY_ICON_CLIMBING)) { + to_chat(user, SPAN_WARNING("You stop climbing onto [src].")) + return + } + if(!_validate_climb_target(user, preferred, TRUE)) { + to_chat(user, SPAN_WARNING("The spot on [src] is no longer reachable.")) + return + } + _carry_move_with_grabs(user, preferred, TRUE) + user.visible_message( + SPAN_WARNING("[user] climbs onto [src]."), + SPAN_WARNING("You climb onto [src].")) + return - if(health > 0) - to_chat(user, SPAN_XENO("We can't jump over [src] until it is destroyed!")) +/** + * climb_down is called inside living.dm by _handle_tank_edge_move(), which is called by Move() + * + * This proc handles climbing down normally, and, if allowed to run, will result in a successful disembark. + * + * Climbing down was a bit more tricky to implement than climbing up, since we need to block movement into- + * what would otherwise be a 'free', open tile. + * + * The code is pretty self-explanatory. Of note is the fact that we only interrupt on an incapacitation. This is to allow + * easier disembarking even when the tank is moving. + * Arguments: + * * mob/living/user - The mob trying to get down the tank + * * turf/target - The exact 'turf' of the tank we want to climb down onto. + */ +/obj/vehicle/multitile/tank/proc/climb_down(mob/living/user, turf/target) + if(user.action_busy) 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 over [src] is obstructed!")) - return - - // Now we check to make sure the turf on the other side of the tank isn't dense too - current_turf = get_step(current_turf, dir_to_go) - if(current_turf.density) - to_chat(user, SPAN_XENO("The path over [src] is obstructed!")) + user.visible_message( + SPAN_WARNING("[user] starts climbing down from [src]."), + SPAN_WARNING("You start climbing down from [src].")) + + if(!do_after(user, 1 SECONDS, INTERRUPT_INCAPACITATED, BUSY_ICON_GENERIC)) { + to_chat(user, SPAN_WARNING("You stop climbing down from [src].")) + return + } + // edge case where tank moves while we're climbing down + if(!_validate_climb_target(user, target, FALSE)) { + to_chat(user, SPAN_WARNING("The spot is no longer reachable.")) return + } + clear_on_top(user) + user.forceMove(target) + user.visible_message( + SPAN_WARNING("[user] climbs down from [src]."), + SPAN_WARNING("You climb down from [src].")) + return - to_chat(user, SPAN_XENO("We begin to jump over [src]...")) - if(!do_after(user, 3 SECONDS, INTERRUPT_ALL, BUSY_ICON_HOSTILE)) - to_chat(user, SPAN_XENO("We stop jumping over [src].")) +/** + * mark_on_top marks mobs as riders of a specific tank. + * + * This proc does three things: + * + * It adds a mob to the tank's list of passengers (on_top_mobs) + * It adds the tank to a mob's tank_on_top_of var + * It calls _apply_rider_visuals() to set the layer atop the tank's + * + * Arguments: + * * mob/living/M - The mob being marked ontop. + */ +/obj/vehicle/multitile/tank/proc/mark_on_top(mob/living/M) + if(!istype(M)) + return + if(M.z != z) + return + if(!(get_turf(M) in locs)) + return + if(M.tank_on_top_of == src) + return + on_top_mobs |= M + M.tank_on_top_of = src + _apply_rider_visuals(M) + +/** + * clear_on_top removes rider effects from a mob who was previously atop the tank. + * + * This proc resets the layer and plane of a rider. + * This proc removes a rider from the on_top_mobs list of the tank. + * This proc sets the tank_on_top_of variable of a mob to null. + * + * Arguments: + * * mob/living/M - The mob who has disembarked. + */ +/obj/vehicle/multitile/tank/proc/clear_on_top(mob/living/M) + if(!istype(M)) return + on_top_mobs -= M + if(M.tank_on_top_of == src) + M.tank_on_top_of = null + M.layer = initial(M.layer) + M.plane = initial(M.plane) + +/** + * Destroy proc. This shouldn't normally be called, but just in case. + * + * This proc ensures all mobs atop the tank are cleared. + * we also cut the on_top_mobs list, just in case DM doesn't have garbage collection. + * + */ +/obj/vehicle/multitile/tank/Destroy() + for(var/mob/living/M in on_top_mobs.Copy()) + if(M) + clear_on_top(M) + on_top_mobs.Cut() + return ..() + +/** + * revalidate_on_top checks if a mob is still atop the tank. + * + * This proc goes through all of the mobs in the tank's on_top_mobs list and either: + * - removes them from the list if they're not on the same tile anymore + * - refreshes rider visuals + * + * This proc probably doesn't need to be called, and you might be able to get away with removing it if it's a performance issue. + * It's just there for safety. + */ +/obj/vehicle/multitile/tank/proc/revalidate_on_top() + for(var/mob/living/M in on_top_mobs.Copy()) + if(!M || M.z != z || !(get_turf(M) in locs)) + clear_on_top(M) + else + _apply_rider_visuals(M) + +/** + * BlockedPassDirs allows us to move atop the tank if we're on top of it. + * + * You might've noticed that this method is in UpperCamelCase instead of snake_case. + * This is because we're overriding a method from /atom. + * + * This proc checks if the atom mover is a mob, and, if it is, allows it to move freely atop the tank. + * + * * Arguments: + * * atom/movable/mover - The atom attempting to move in a tank turf. + * * target_dir - The direction we're moving towards. + */ +/obj/vehicle/multitile/tank/BlockedPassDirs(atom/movable/mover, target_dir) + if(ismob(mover)) + var/mob/living/M = mover + if(istype(M) && M.tank_on_top_of == src) + var/turf/start = get_turf(M) + var/turf/target = get_step(start, target_dir) + if(target && (target in src.locs)) + return NO_BLOCKED_MOVEMENT + var/ret = ..() + if(ret != null) + return ret + return null + +/** + * take_damage_type handles taking damage when the tank gets attacked. + * + * Because xenos can now hop atop the tank to attack it in a fairly one-sided display of force, I've introduced + * this function to make destroying the tank a bit slower. This might be removed if/once someone (i) get(s) around to + * making tank hardpoints repairable. + * + * Xenos atop the tank are flat-out unable to hit the Treads module. This is to allow the tank to run back + * towards marines. Also, it's impossible to break the treads from ontop. How are you going to reach them? + * + * Xenos atop the tank also suffer a 30% damage penalty, again, to give time for tank crewmembers to retreat to + * safety. + * + * * Arguments: + * * damage - Numeric value of damage. + * * type - Damage type. Brute, Slash, Acid... + * * atom/attacker - Whoever is damaging the tank + */ +/obj/vehicle/multitile/tank/take_damage_type(damage, type, atom/attacker) + if(type == "slash" && istype(attacker, /mob/living/carbon/xenomorph)) + var/mob/living/carbon/xenomorph/X = attacker + + // must actually be on top of THIS tank (exterior), same z, and stading on one of our tiles + if(X.tank_on_top_of == src && X.z == z) + var/turf/xturf = get_turf(X) + if(xturf && (xturf in src.locs)) + var/adj_damage = damage * 0.7 + var/all_broken = TRUE + var/dmg_multi = get_dmg_multi(type) + + for(var/obj/item/hardpoint/H in hardpoints) + if(istype(H, /obj/item/hardpoint/locomotion/treads)) + continue + // turret doesn't count as a 'hardpoint' for all_broken, otherwise the tank is... too tanky. + if(istype(H, /obj/item/hardpoint/holder/tank_turret)) + H.take_damage(floor(adj_damage * dmg_multi)) + continue + if(H.can_take_damage()) + H.take_damage(floor(adj_damage * dmg_multi)) + all_broken = FALSE + + // If all *eligible* hardpoints are broken, the frame begins taking full damage... + // ...otherwise, the frame takes one tenth, just like the base behavior. + if(all_broken) + health = max(0, health - adj_damage * dmg_multi) + else + health = max(0, health - floor(adj_damage * dmg_multi / 10)) + if(ismob(attacker)) + var/mob/M = attacker + log_attack("[src] took [adj_damage] [type] damage (top-hit penalty) from [M] ([M.client ? M.client.ckey : "disconnected"]).") + else + log_attack("[src] took [adj_damage] [type] damage (top-hit penalty) from [attacker].") + update_icon() + return + return ..() - user.forceMove(current_turf) - to_chat(user, SPAN_XENO("We jump to the other side of [src].")) /* ** PRESETS SPAWNERS */ diff --git a/code/modules/vehicles/tank/tank_helpers.dm b/code/modules/vehicles/tank/tank_helpers.dm new file mode 100644 index 000000000000..d282c13fe047 --- /dev/null +++ b/code/modules/vehicles/tank/tank_helpers.dm @@ -0,0 +1,374 @@ +// --- Helper for crash response + +/** + * _scatter_riders_on_crash joustles mobs riding atop the tank in a random direction. + * + * This proc applies a 1.5s WEAKEN effect to all mobs atop it when the tank crashes into something with sufficient speed. + * This includes large mobs such as ravagers, queens, kings, et-cetera. + * + * If a mob happens to be thrown OFF of a tank tile, they will instead get a 3 second stun. + * ^^ See Move() function inside living.dm + * + * The intent of this proc is double: Allowing tank drivers to defend themselves and have a chance against xenos + * atop the tank and also punish tank drivers for bad driving by fucking up the marines on top. + * + */ +/obj/vehicle/multitile/tank/proc/_scatter_riders_on_crash() + if(abs(move_momentum) < 1.6) + return + + for(var/mob/living/M in on_top_mobs.Copy()) + if(!M || M.z != src.z) + clear_on_top(M) + continue + + var/sweep_range = 3 + var/turf/start = get_turf(M) + var/throw_dir = get_dir(src, start) + if(!throw_dir) + throw_dir = pick(GLOB.cardinals) + + var/turf/next_out = get_step(start, throw_dir) + if(next_out && !(next_out in src.locs)) + clear_on_top(M) + step(M, throw_dir) + else + step_away(M, src, sweep_range, 3) + + var/turf/cur = get_turf(M) + var/turf/next2 = get_step(cur, throw_dir) + if(next2 && !(next2 in src.locs)) + clear_on_top(M) + step(M, throw_dir) + + to_chat(M, SPAN_WARNING("You're thrown from [src]!")) + playsound(M, "punch", 25, TRUE) + shake_camera(M, 2, 1) + + if(!(get_turf(M) in src.locs)) + M.apply_effect(3, WEAKEN) + clear_on_top(M) + continue + + var/turf/current = get_turf(M) + var/list/hull_neighbors = list() + for(var/d in GLOB.cardinals) + var/turf/H = get_step(current, d) + if(H && (H in src.locs)) + hull_neighbors += H + if(hull_neighbors.len) + var/turf/spot = pick(hull_neighbors) + var/ndir = get_dir(current, spot) + if(ndir) + step(M, ndir) + M.apply_effect(1.5, WEAKEN) + mark_on_top(M) + +// --- motion geometry and transform math + +/** + * _current_center() builds a matrix of the tank's tiles and returns the center pair. + * + * The tank occupies a 3x3 set of tiles. We can imagine it as a matrix: + * + *(10,20) (11,20) (12,20) + *(10,21) (11,21) (12,21) + *(10,22) (11,22) (12,22) + * + * This proc gets the middle (1,1) element of the matrix and returns it, in this case, (11,21) + * + * Returns: + * * A LIST of two integer elements. [11,21] + */ +/obj/vehicle/multitile/tank/proc/_current_center() + var/first = TRUE + var/minx + var/miny + var/maxx + var/maxy + for(var/turf/T in locs) + if(first) + minx = maxx = T.x + miny = maxy = T.y + first = FALSE + else + if(T.x < minx) + minx = T.x + if(T.x > maxx) + maxx = T.x + if(T.y < miny) + miny = T.y + if(T.y > maxy) + maxy = T.y + + return list(minx + 1, miny + 1) + +/** + * calculates how many 90-degree rotations are needed to go from one cardinal direction to another. + * + * NORTH → EAST = +1 (90° CW) + * NORTH → WEST = -1 (90° CCW) + * NORTH → SOUTH = +2 (180°) + * EAST → SOUTH = +1 (90° CW) + * SOUTH → WEST = +1 (90° CW) + * + * This proc is a key part of the positioning system when the vehicle rotates. + * + * Arguments: + * * old_dir = The direction we are initially at + * * new_dir = The direction we want to be at + * + * Returns: + * * 0 : No rotation needed + * * 1 : 90° clockwise rotation + * * -1: 90° counter-clockwise rotation + * * 2 : 180° rotation. Will never happen because the tank can't turn over like that in one move. + */ +/obj/vehicle/multitile/tank/proc/_quarter_turns(old_dir, new_dir) + if(old_dir == new_dir) + return 0 + switch(old_dir) + if(NORTH) + if(new_dir == EAST) + return 1 + if(new_dir == WEST) + return -1 + if(new_dir == SOUTH) + return 2 + if(SOUTH) + if(new_dir == WEST) + return 1 + if(new_dir == EAST) + return -1 + if(new_dir == NORTH) + return 2 + if(EAST) + if(new_dir == SOUTH) + return 1 + if(new_dir == NORTH) + return -1 + if(new_dir == WEST) + return 2 + if(WEST) + if(new_dir == NORTH) + return 1 + if(new_dir == SOUTH) + return -1 + if(new_dir == EAST) + return 2 + return 0 + +/** + * _rotated_offset takes an offset (dx, dy) and rotates it by 90% degree increments + * + * this proc applies 2D coordinate rotation mathematics to transform a position offset + * based on the rotation amount calculated by _quarter_turns. + * + * Arguments: + * * dx = x axis position, relative to the tank + * * dy = y axis position, relative to the tank + * * k = a numeric value acquired from _quarter_turns to know how many turns we have to do + * + * Returns: + * * a LIST, containing a 2-slot permutation of {{dx, -dx} {dy, -dy}} depending on K. + */ +/obj/vehicle/multitile/tank/proc/_rotated_offset(dx, dy, k) + if(!k) + return list(dx, dy) + if(k == 2) + return list(-dx, -dy) // 180° + if(k == 1) + return list(dy, -dx) // CW 90° + // k == -1 (CCW 90°) + return list(-dy, dx) + +/** + * _update_riders_after_motion coordinates moving all mobs atop the tank whenever it moves or rotates. + * + * This proc is the main coordination function that handles moving all riders atop the tank. + * It combines translation, rotation, and collision detection to keep riders properly positioned on the hull. + * + * First it calls _quarter_turns to understand what kind of rotations it'll need to turn from one orientation to the other. + * Then it calls _rotated_offset to know where to position the riders. + * Lastly, it applies those changes. + * + * Arguments: + * * old_cx = the X axis position currently occupied by the tank + * * old_cy = the y axis position currently occupied by the tank + * * old_dir = the direction the tank is currently facing. N/E/S/W + * * new_cx = the X axis position that we are moving to + * * new_cy = the y axis position that we are moving to + * * new_dir = the direction we want to face. N/E/S/W + * + */ +/obj/vehicle/multitile/tank/proc/_update_riders_after_motion(old_cx, old_cy, old_dir, new_cx, new_cy, new_dir) + var/k = _quarter_turns(old_dir, new_dir) // -1,0,1,2 + + for(var/mob/living/M in on_top_mobs.Copy()) + if(!M || M.z != src.z) + clear_on_top(M) + continue + + var/turf/from = get_turf(M) + if(!from) + clear_on_top(M) + continue + + // Offset from OLD center -> rotate by k -> translate to NEW center + var/odx = from.x - old_cx + var/ody = from.y - old_cy + var/list/rd = _rotated_offset(odx, ody, k) + var/ndx = rd[1] + var/ndy = rd[2] + var/turf/target = locate(new_cx + ndx, new_cy + ndy, src.z) + + // this shouldn't happen, but just to be safe. + if(!target || !(target in src.locs)) + if(from in src.locs) + mark_on_top(M) + else + clear_on_top(M) + continue + + // If target is blocked by a non-rider dense atom, (shouldn't happen cuz tank runs over shit) keep if possible + var/blocked = FALSE + if(target.density) + blocked = TRUE + else + for(var/atom/A in target) + if(A != src && A.density && (!ismob(A) || !(A in on_top_mobs))) + blocked = TRUE + break + + if(blocked) + if(from in src.locs) + mark_on_top(M) + else + clear_on_top(M) + continue + + // Move and keep elevation + if(target != from) + M.forceMove(target) + mark_on_top(M) + +// --- Helper for layering effects + +/** + * simply brings a rider's layer atop the tanks. + * + * TANK_RIDER_LAYER being at 4.51 is not arbitrary. + * The tank chassis is set at 4.1, but the turret is at 4.5. + * Therefore, 4.51 is needed to avoid clipping. + * + * Arguments: + * * mob/living/M = Mob whose layer priority is being increased. + */ +/obj/vehicle/multitile/tank/proc/_apply_rider_visuals(mob/living/M) + M.layer = TANK_RIDER_LAYER + +// --- helpers for handlign interactions with grabbed mobs + +/** + * This proc allows a marine to pull another one up the tank once he finishes climbing. + * + * _carry_move_with_grabs does something similar to grabbing someone as you climb a window frame: + * You'll pull the marine you're grabbing up with you as soon as YOUR climb_onto finishes. + * The marine pulled up will get a 2 second weaken, but they can be shaked out of it. + * + * + * Arguments: + * * mob/living/user = Mob doing the pulling. + * * turf/dest = Which turf on the tank we're moving to + */ +/obj/vehicle/multitile/tank/proc/_carry_move_with_grabs(mob/living/user, turf/dest) + var/list/grabbed_things = list() + for(var/obj/item/grab/G in list(user.l_hand, user.r_hand)) + if(G?.grabbed_thing) + grabbed_things += G.grabbed_thing + G.grabbed_thing.forceMove(user.loc) + + user.forceMove(dest) + mark_on_top(user) + + + for(var/atom/movable/thing as anything in grabbed_things) + thing.forceMove(dest) + if(isliving(thing)) + var/mob/living/L = thing + L.apply_effect(2, WEAKEN) + mark_on_top(L) + + +// --- blocking helpers. Checks if turf is blocked, etc. + +/** + * This proc checks a specific turf and returns false if anything dense is on it, except for a mob. + * + * _blocked_except_mobs is a helper that check if a turf/T has anything dense in it. + * If there is something dense in it and it is NOT a mob, it returns TRUE, otherwise, it returns false. + * + * Arguments: + * * turf/T = Which turf we're checking for dense atoms in + * + * Returns: + * * TRUE = If something dense is present, and it is not a mob. + * * FALSE = If the turf is clear of dense objects, or if the only dense objects in it are mobs. + */ +/obj/vehicle/multitile/tank/proc/_blocked_except_mobs(turf/T) + if(!istype(T)) + return TRUE + if(T.density) + return TRUE + for(var/atom/A in T) + if(A == src) + continue + if(ismob(A)) + continue + if(A.density) + return TRUE + return FALSE + +/** + * This proc checks if a specific turf/spot is a valid climbing target + * + * We are mostly interested in ensuring that the tank hasn't moved too far away, and this function helps with that. + * It also has a couple of other edge cases such as being in a different Z, or having a climb-spot blocked by + * anything other than a mob, juuuuust to be safe. + * + * Arguments: + * * turf/spot = Which turf we want to climb onto + * * mob/living/user = Whoever wants to climb onto spot + * + * Returns: + * * TRUE = If nothing is blocking the turf + * * FALSE = If something is blocking the turf. + */ +/obj/vehicle/multitile/tank/proc/_validate_climb_target(mob/living/user, turf/spot) + if(!istype(user) || !istype(spot)) + return FALSE + if(user.z != z) + return FALSE + if(get_dist(spot, get_turf(user)) != 1) + return FALSE + if(_blocked_except_mobs(spot)) + return FALSE + return TRUE + +/** + * Helper function to know if a mob is a rider of this specific tank + * + * Checks if this tank is the 'tank_on_top_of' of a given mob. + * + * Arguments: + * * atom/movable/AM = whatever mob/atom we want to check if its a rider. + * + * Returns: + * * TRUE = If AM is a mob/living and this tank is tank_on_top_of. + * * FALSE = If AM is not a mob/living, or if this tank is not tank_on_top_of. + */ +/obj/vehicle/multitile/tank/proc/_is_our_rider(atom/movable/AM) + if(!ismob(AM)) + return FALSE + var/mob/living/M = AM + return istype(M) && M.tank_on_top_of == src diff --git a/colonialmarines.dme b/colonialmarines.dme index 14e84c5de98c..2c0a71ad9340 100644 --- a/colonialmarines.dme +++ b/colonialmarines.dme @@ -2645,6 +2645,7 @@ #include "code\modules\vehicles\multitile\multitile_verbs.dm" #include "code\modules\vehicles\tank\interior.dm" #include "code\modules\vehicles\tank\tank.dm" +#include "code\modules\vehicles\tank\tank_helpers.dm" #include "code\modules\vehicles\van\box_van.dm" #include "code\modules\vehicles\van\clf_van.dm" #include "code\modules\vehicles\van\interior.dm" From 370d7551d640fce129a30e74318981d1d8f0bc3a Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Sun, 7 Sep 2025 14:16:17 -0300 Subject: [PATCH 02/60] Tank Desant: Collide with open_space turfs --- code/modules/vehicles/multitile/multitile_movement.dm | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/code/modules/vehicles/multitile/multitile_movement.dm b/code/modules/vehicles/multitile/multitile_movement.dm index 3af84ff76e58..68da0c496495 100644 --- a/code/modules/vehicles/multitile/multitile_movement.dm +++ b/code/modules/vehicles/multitile/multitile_movement.dm @@ -163,6 +163,12 @@ if(T in old_turfs) continue + if(istype(T, /turf/open_space)) + // early return so we skip crash behavior. + move_momentum = floor(move_momentum/2) + update_next_move() + return FALSE + if(!T.Enter(src) || locate(/obj/structure/shuttle/part) in T) can_move = FALSE From 81d1ed82ffc7b02f545335f149f2fb5299e7112f Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Mon, 22 Sep 2025 17:52:57 -0300 Subject: [PATCH 03/60] Reduces mob stun time from medium vehicle collision --- code/modules/vehicles/multitile/multitile_bump.dm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/code/modules/vehicles/multitile/multitile_bump.dm b/code/modules/vehicles/multitile/multitile_bump.dm index 495b353edc38..0d751df524d2 100644 --- a/code/modules/vehicles/multitile/multitile_bump.dm +++ b/code/modules/vehicles/multitile/multitile_bump.dm @@ -611,8 +611,9 @@ apply_damage(10 + rand(0, 10), BRUTE) else if(V.vehicle_flags & VEHICLE_CLASS_MEDIUM) - apply_effect(3, WEAKEN) - apply_damage(10 + rand(0, 10), BRUTE) + apply_effect(1.5, WEAKEN) + apply_effect(3, SLOW) + apply_damage(5 + rand(0, 5), BRUTE) dmg = TRUE else if(V.vehicle_flags & VEHICLE_CLASS_HEAVY) From 87dbebdbf87ea1fdb4bdc5a3bf11b160b312f5c0 Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Mon, 22 Sep 2025 18:24:11 -0300 Subject: [PATCH 04/60] Marines can crawl away from underneath the tank This is an important fix to an edge case where a marine might get stuck in the middle tile of the 3x3 Tank and be unable to get out. Now they can simply crawl away from underneath the Longstreet tank. Tested thoroughly with other grab interactions. --- code/modules/vehicles/tank/tank.dm | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/code/modules/vehicles/tank/tank.dm b/code/modules/vehicles/tank/tank.dm index 832a45f6b298..777e2b0a1e9b 100644 --- a/code/modules/vehicles/tank/tank.dm +++ b/code/modules/vehicles/tank/tank.dm @@ -266,13 +266,16 @@ return ..() if(M.action_busy) return - if(get_turf(M) in locs) - return if(M.pulledby || M.throwing) return var/turf/facing_turf = get_step(get_turf(M), M.dir) if(!facing_turf) return ..() + if(M.resting) + M.forceMove(facing_turf) + return ..() + if(get_turf(M) in locs) + return climb_onto(M, facing_turf) return From 9d91bcd3b56ca15b3e56740719f513b4f4dd677a Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Mon, 22 Sep 2025 19:10:38 -0300 Subject: [PATCH 05/60] Entities atop the tank now get a pixel_y offset --- code/modules/animations/animation_library.dm | 5 ++++- code/modules/vehicles/tank/tank.dm | 1 + code/modules/vehicles/tank/tank_helpers.dm | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/code/modules/animations/animation_library.dm b/code/modules/animations/animation_library.dm index 7a69adc512ef..d6775f01aa71 100644 --- a/code/modules/animations/animation_library.dm +++ b/code/modules/animations/animation_library.dm @@ -207,7 +207,10 @@ Can look good elsewhere as well.*/ pixel_x_diff = -pixel_offset pixel_y_diff = -pixel_offset animate(src, pixel_x = pixel_x + pixel_x_diff, pixel_y = pixel_y + pixel_y_diff, time = 2, flags = ANIMATION_PARALLEL) - animate(pixel_x = initial(pixel_x), pixel_y = initial(pixel_y), time = 2) + if(isliving(src) && src:is_on_tank_hull()) + animate(pixel_x = initial(pixel_x), pixel_y = initial(pixel_y) + 12, time = 2) + else + animate(pixel_x = initial(pixel_x), pixel_y = initial(pixel_y), time = 2) /atom/proc/animation_spin(speed = 5, loop_amount = -1, clockwise = TRUE, sections = 3, angular_offset = 0, pixel_fuzz = 0) if(!sections) diff --git a/code/modules/vehicles/tank/tank.dm b/code/modules/vehicles/tank/tank.dm index 777e2b0a1e9b..123f3e540c76 100644 --- a/code/modules/vehicles/tank/tank.dm +++ b/code/modules/vehicles/tank/tank.dm @@ -402,6 +402,7 @@ M.tank_on_top_of = null M.layer = initial(M.layer) M.plane = initial(M.plane) + M.pixel_y = initial(M.pixel_y) /** * Destroy proc. This shouldn't normally be called, but just in case. diff --git a/code/modules/vehicles/tank/tank_helpers.dm b/code/modules/vehicles/tank/tank_helpers.dm index d282c13fe047..246b1e36d8b1 100644 --- a/code/modules/vehicles/tank/tank_helpers.dm +++ b/code/modules/vehicles/tank/tank_helpers.dm @@ -266,6 +266,7 @@ */ /obj/vehicle/multitile/tank/proc/_apply_rider_visuals(mob/living/M) M.layer = TANK_RIDER_LAYER + M.pixel_y = M.old_y + 12 // --- helpers for handlign interactions with grabbed mobs From 0e1ad2d87f7987c39a873b4fbcd5066e8fdec8e5 Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Mon, 22 Sep 2025 19:12:32 -0300 Subject: [PATCH 06/60] Being pulled off the tank is less punishing The whole idea of receiving a debuff when you're pulled off of the tank is to penalize drivers who use the tank to spearhead a push and allow xenos to take advantage of poorly positioned marines atop the tank. A 3 second weaken is a bit too punishing and basically guarantees a cap. Now, the marine will get a shorter stun and a hefty slowdown, but they'll be able to fight back and can be saved if other marines pay attention. --- code/modules/mob/living/living.dm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 9b9a8985ecc1..9adb52da3dcb 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -243,7 +243,9 @@ if(T && !(NewLoc in T.locs)) if(ishuman(src)) var/mob/living/carbon/human/H = src - H.apply_effect(3, WEAKEN) + H.apply_effect(1.5, WEAKEN) + H.apply_effect(3, SUPERSLOW) + H.apply_effect(5, SLOW) playsound(src, "punch", 25, TRUE) T.clear_on_top(src) From 44a3a7f8e3197d95a8ccd5aa23199076f55d974c Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Mon, 22 Sep 2025 20:34:29 -0300 Subject: [PATCH 07/60] WIELDED weapons accuracy debuff when fired atop moving tank --- code/modules/projectiles/gun.dm | 21 +++++++++++++++++++-- code/modules/vehicles/tank/tank.dm | 6 ++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index 0cd966bcbed8..e33305aaf291 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -1188,6 +1188,8 @@ and you're good to go. var/atom/original_target = target //This is for burst mode, in case the target changes per scatter chance in between fired bullets. + var/obj/vehicle/multitile/tank/tank_on_top_of = user.tank_on_top_of // Used to calculate inaccuracy when firing atop a moving tank. + if(loc != user || (flags_gun_features & GUN_WIELDED_FIRING_ONLY && !(flags_item & WIELDED))) return TRUE @@ -1200,7 +1202,7 @@ and you're good to go. var/original_scatter = projectile_to_fire.scatter var/original_accuracy = projectile_to_fire.accuracy - apply_bullet_scatter(projectile_to_fire, user, reflex, dual_wield) //User can be passed as null. + apply_bullet_scatter(projectile_to_fire, user, reflex, dual_wield, tank_on_top_of) //User can be passed as null. curloc = get_turf(user) if(QDELETED(original_target)) //If the target's destroyed, shoot at where it was last. @@ -1767,13 +1769,25 @@ not all weapons use normal magazines etc. load_into_chamber() itself is designed return TRUE //This proc calculates scatter and accuracy -/obj/item/weapon/gun/proc/apply_bullet_scatter(obj/projectile/projectile_to_fire, mob/user, reflex = 0, dual_wield = 0) +/obj/item/weapon/gun/proc/apply_bullet_scatter(obj/projectile/projectile_to_fire, mob/user, reflex = 0, dual_wield = 0, tank_on_top_of = null) var/gun_accuracy_mult = accuracy_mult_unwielded var/gun_scatter = scatter_unwielded if(flags_item & WIELDED || flags_gun_features & GUN_ONE_HAND_WIELDED) gun_accuracy_mult = accuracy_mult gun_scatter = scatter + // increases scatter to penalize marines firing atop a moving tank instead of outright restricting it + // only for wielded firearms. + if(tank_on_top_of) + user.visible_message(SPAN_NOTICE("Entered 1781: if(tank_on_top_of)"), null, 4) + var/obj/vehicle/multitile/tank/TANK = tank_on_top_of + if(world.time < TANK.on_top_mobs_shooting_inaccuracy_time) + if(world.time % 3) + to_chat(gun_user, SPAN_DANGER("You struggle to keep your aim centered as the [TANK] moves!")) + user.visible_message(SPAN_NOTICE("Tank inaccuracy applied"), null, 4) + gun_accuracy_mult = max(0.1, gun_accuracy_mult * 0.4) // 60% accuracy loss + gun_scatter += SCATTER_AMOUNT_TIER_4 + else if(user && world.time - user.l_move_time < 5) //moved during the last half second //accuracy and scatter penalty if the user fires unwielded right after moving gun_accuracy_mult = max(0.1, gun_accuracy_mult - max(0,movement_onehanded_acc_penalty_mult * HIT_ACCURACY_MULT_TIER_3)) @@ -1800,6 +1814,9 @@ not all weapons use normal magazines etc. load_into_chamber() itself is designed projectile_to_fire.accuracy = floor(projectile_to_fire.accuracy * gun_accuracy_mult) // Apply gun accuracy multiplier to projectile accuracy projectile_to_fire.scatter += gun_scatter + user.visible_message(SPAN_NOTICE("projectile_to_fire.scatter = [projectile_to_fire.scatter]"), null, 4) + user.visible_message(SPAN_NOTICE("projectile_to_fire.accuracy = [projectile_to_fire.accuracy]"), null, 4) + /// When the gun is about to shoot this is called to play the specific gun's firing sound. Requires the firing projectile and the gun's user as the first and second argument /obj/item/weapon/gun/proc/play_firing_sounds(obj/projectile/projectile_to_fire, mob/user) if(!user) //The gun only messages when fired by a user. diff --git a/code/modules/vehicles/tank/tank.dm b/code/modules/vehicles/tank/tank.dm index 123f3e540c76..3724357a3244 100644 --- a/code/modules/vehicles/tank/tank.dm +++ b/code/modules/vehicles/tank/tank.dm @@ -76,6 +76,12 @@ explosive_resistance = 400 var/list/on_top_mobs = list() /// keeps track of all mobs currently atop the tank + var/on_top_mobs_shooting_inaccuracy_time = 0 /// world_time must be bigger than this so mobs don't get penalized for shooting while atop the tank. + +/obj/vehicle/multitile/tank/update_next_move() + var/anti_build_factor = 1/((max(abs(move_momentum), 1)/move_max_momentum) * move_momentum_build_factor) + on_top_mobs_shooting_inaccuracy_time = world.time + move_delay * move_momentum_build_factor * anti_build_factor * misc_multipliers["move"] * 5 + . = ..() /obj/vehicle/multitile/tank/initialize_cameras(change_tag = FALSE) if(!camera) From 2a3af3d293fae344921d52e3560284bc8aff71d7 Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Mon, 22 Sep 2025 20:53:13 -0300 Subject: [PATCH 08/60] TANK DESANT now wont block VTOL open_space movement --- code/modules/vehicles/multitile/multitile_movement.dm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/code/modules/vehicles/multitile/multitile_movement.dm b/code/modules/vehicles/multitile/multitile_movement.dm index 68da0c496495..36c3fcf4f3ee 100644 --- a/code/modules/vehicles/multitile/multitile_movement.dm +++ b/code/modules/vehicles/multitile/multitile_movement.dm @@ -163,7 +163,9 @@ if(T in old_turfs) continue - if(istype(T, /turf/open_space)) + // This first istype() check is probably a bad practice but it'll allow V-TOL to still enter... + // ... open_space turfs in case Tank Desant and VTOL get TM'd together. + if(istype(src, /obj/vehicle/multitile/tank) && istype(T, /turf/open_space)) // early return so we skip crash behavior. move_momentum = floor(move_momentum/2) update_next_move() From 15197d890822ab03634ec6696fc9f0b9be90f3e3 Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Mon, 22 Sep 2025 21:01:25 -0300 Subject: [PATCH 09/60] removes debug statements from gun.dm Oopsie daisy, almost forgot. --- code/modules/projectiles/gun.dm | 5 ----- 1 file changed, 5 deletions(-) diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index e33305aaf291..715ea0047b27 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -1779,12 +1779,10 @@ not all weapons use normal magazines etc. load_into_chamber() itself is designed // increases scatter to penalize marines firing atop a moving tank instead of outright restricting it // only for wielded firearms. if(tank_on_top_of) - user.visible_message(SPAN_NOTICE("Entered 1781: if(tank_on_top_of)"), null, 4) var/obj/vehicle/multitile/tank/TANK = tank_on_top_of if(world.time < TANK.on_top_mobs_shooting_inaccuracy_time) if(world.time % 3) to_chat(gun_user, SPAN_DANGER("You struggle to keep your aim centered as the [TANK] moves!")) - user.visible_message(SPAN_NOTICE("Tank inaccuracy applied"), null, 4) gun_accuracy_mult = max(0.1, gun_accuracy_mult * 0.4) // 60% accuracy loss gun_scatter += SCATTER_AMOUNT_TIER_4 @@ -1814,9 +1812,6 @@ not all weapons use normal magazines etc. load_into_chamber() itself is designed projectile_to_fire.accuracy = floor(projectile_to_fire.accuracy * gun_accuracy_mult) // Apply gun accuracy multiplier to projectile accuracy projectile_to_fire.scatter += gun_scatter - user.visible_message(SPAN_NOTICE("projectile_to_fire.scatter = [projectile_to_fire.scatter]"), null, 4) - user.visible_message(SPAN_NOTICE("projectile_to_fire.accuracy = [projectile_to_fire.accuracy]"), null, 4) - /// When the gun is about to shoot this is called to play the specific gun's firing sound. Requires the firing projectile and the gun's user as the first and second argument /obj/item/weapon/gun/proc/play_firing_sounds(obj/projectile/projectile_to_fire, mob/user) if(!user) //The gun only messages when fired by a user. From 6b363dda242e60225c6db87ee1c0087f9f153b19 Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Tue, 23 Sep 2025 00:36:45 -0300 Subject: [PATCH 10/60] improves climb_down logic Checks if we have a valid turf to climb down before climbing down, otherwise, we'd 'begin climbing down' even if we tried to disembark onto a wall. (Which would obviously fail, but, why allow it to start in the first place?) --- code/modules/vehicles/tank/tank.dm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/code/modules/vehicles/tank/tank.dm b/code/modules/vehicles/tank/tank.dm index 3724357a3244..429ed577a6c1 100644 --- a/code/modules/vehicles/tank/tank.dm +++ b/code/modules/vehicles/tank/tank.dm @@ -345,6 +345,10 @@ if(user.action_busy) return + if(!_validate_climb_target(user, target, FALSE)) { + return + } + user.visible_message( SPAN_WARNING("[user] starts climbing down from [src]."), SPAN_WARNING("You start climbing down from [src].")) From c790fa9655785abc65021a1bb3815fcc92a7807a Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Tue, 23 Sep 2025 00:40:47 -0300 Subject: [PATCH 11/60] Projectile interaction changes upon colliding with TANK Projectiles now by default go 'over' the tank when crossing tank tiles, automatically hitting mobs on top. Sprite clicking is not necessary anymore. If the projectile does not find a valid target atop the tank, it will automatically hit the tank upon attempting to leave turfs with a tank tile. You still can not shoot entities opposite from you on the tank. This will make it easier for xenos to land their ranged abilities, and the hedgehog Rav's shield, bone spurs, and other such abilities have been tested to be working correctly. As projectiles now try to hit mobs ontop first... Meatshield infantry meta? --- .../strains/castes/boiler/trapper.dm | 10 +---- code/modules/projectiles/projectile.dm | 38 ++++++++++++++++--- 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/code/modules/mob/living/carbon/xenomorph/strains/castes/boiler/trapper.dm b/code/modules/mob/living/carbon/xenomorph/strains/castes/boiler/trapper.dm index 751bfeae59ee..e56721299d52 100644 --- a/code/modules/mob/living/carbon/xenomorph/strains/castes/boiler/trapper.dm +++ b/code/modules/mob/living/carbon/xenomorph/strains/castes/boiler/trapper.dm @@ -226,18 +226,12 @@ xeno.visible_message(SPAN_XENOWARNING("[xeno] fires a blast of acid at [affected_atom]!"), SPAN_XENOWARNING("We fire a blast of acid at [affected_atom]!")) - // Allows acid shotgun ability to play nice with rideable tank - var/atom/shot_target - if(ismob(affected_atom)) - shot_target = affected_atom - else - shot_target = locate(affected_atom.x, affected_atom.y, affected_atom.z) - + var/turf/target_turf = locate(affected_atom.x, affected_atom.y, affected_atom.z) var/obj/projectile/proj = new(xeno.loc, create_cause_data("acid shotgun", xeno)) var/datum/ammo/ammoDatum = new ammo_type() proj.generate_bullet(ammoDatum) - proj.fire_at(shot_target, xeno, xeno, ammoDatum.max_range, ammoDatum.shell_speed) + proj.fire_at(target_turf, xeno, xeno, ammoDatum.max_range, ammoDatum.shell_speed) apply_cooldown() return ..() diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index 17fdf12a2896..ce3142bb3da4 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -90,6 +90,9 @@ var/damage_boosted = 0 var/last_damage_mult = 1 + /// Tracks if the projectile is inside obj/vehicle/multitile/tank + var/obj/vehicle/multitile/tank/inside_tank = null + /obj/projectile/Initialize(mapload, datum/cause_data/cause_data) . = ..() path = list() @@ -410,6 +413,26 @@ if(!istype(T)) return FALSE + // Checkc if we're inside a tank and trying to exit + if(inside_tank) + var/tank_found = FALSE + for(var/obj/O in T) + var/obj/vehicle/multitile/tank/M = _owning_tank_of(O) + if(M == inside_tank || O == inside_tank) + tank_found = TRUE + break + + // hits the tank if there are no tank parts inside the turf + if(!tank_found) + // unless it was shot by a mob atop the tank. + if(isliving(firer)) + var/mob/living/M = firer + if(M.is_on_tank_hull()) + return FALSE + ammo.on_hit_obj(inside_tank, src) + inside_tank.bullet_act(src) + return TRUE + if(T.density) // Handle wall hit var/ammo_flags = ammo.flags_ammo_behavior | projectile_override_flags @@ -467,15 +490,20 @@ return FALSE permutated |= O - // this block allows us to shoot people ontop of the tank by sprite clicking var/obj/vehicle/multitile/tank/M = _owning_tank_of(O) + if(!M) + if(istype(O, /obj/vehicle/multitile/tank)) + M = O + + // this block allows projectiles fired from outside the tank to travel inside it. if(M) - var/mob/living/F = isliving(firer) ? firer : null - var/mob/living/OR = isliving(original) ? original : null - if(F && F.tank_on_top_of == M) + if(!inside_tank) + inside_tank = M return FALSE - if(OR && OR.tank_on_top_of == M) + else if(inside_tank == M) return FALSE + else // if inside_tank exists but is not M, it means we're firing on another tank, so, we return true. + return TRUE var/hit_chance = O.get_projectile_hit_boolean(src) if(hit_chance) // Calculated from combination of both ammo accuracy and gun accuracy From 09580281dae59dd6834126304f3cd522da265b7a Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Tue, 23 Sep 2025 00:41:56 -0300 Subject: [PATCH 12/60] Edge case fix: Dropping hauled mob atop tank If a xenomorph walks atop the tank with a hauled mob, its sprite will now be layered properly atop the tank. Moreover, releasing a hauled mob atop the tank now correctly marks such a mob as being on top of said tank. --- code/modules/mob/living/carbon/carbon.dm | 6 +++++- .../mob/living/carbon/xenomorph/XenoProcs.dm | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 1f30f2e8e7c8..75c10b138594 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -548,6 +548,9 @@ var/location = get_turf(loc) remove_traits(list(TRAIT_HAULED, TRAIT_NO_STRAY, TRAIT_FLOORED, TRAIT_IMMOBILIZED), TRAIT_SOURCE_XENO_HAUL) pixel_y = 0 + var/obj/vehicle/multitile/tank/TANK = null + if(hauling_xeno.is_on_tank_hull()) + TANK = hauling_xeno.tank_on_top_of UnregisterSignal(src, list(COMSIG_ATTEMPT_MOB_PULL, COMSIG_LIVING_PREIGNITION, COMSIG_LIVING_FLAMER_CROSSED, COMSIG_LIVING_FLAMER_FLAMED)) UnregisterSignal(hauling_xeno, COMSIG_MOB_DEATH) hauling_xeno = null @@ -561,7 +564,8 @@ object.Crossed(src) next_haul_resist = 0 SEND_SIGNAL(src, COMSIG_MOB_UNHAULED) - + if(TANK) + TANK.mark_on_top(src) /mob/living/carbon/proc/extinguish_mob(mob/living/carbon/C) adjust_fire_stacks(-5, min_stacks = 0) diff --git a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm index 505bfdf5a16b..3e8fc252d770 100644 --- a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm +++ b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm @@ -220,11 +220,24 @@ return . = ..() +// this proc only serves to fix a visual bug where hauled mobs get layered below the tank. +// this is a rare edge case, because xenos won't usually be hauling mobs atop the tank, but just for completeness... +/mob/living/carbon/xenomorph/proc/check_on_tank_hauled_mob(mob/victim) + var/obj/vehicle/multitile/tank/T = null + if(src.is_on_tank_hull()) + T = src.tank_on_top_of + T._apply_rider_visuals(victim) + else + victim.layer = initial(victim.layer) + victim.plane = initial(victim.plane) + victim.pixel_y = initial(victim.pixel_y) + /mob/living/carbon/xenomorph/Move(NewLoc, direct) . = ..() var/mob/user = hauled_mob?.resolve() if(user) user.forceMove(loc) + check_on_tank_hauled_mob(user) /mob/living/carbon/xenomorph/forceMove(atom/destination) . = ..() @@ -232,8 +245,10 @@ if(user) if(!isturf(destination)) user.forceMove(src) + check_on_tank_hauled_mob(user) else user.forceMove(loc) + check_on_tank_hauled_mob(user) /mob/living/carbon/xenomorph/relaymove(mob/user, direction) . = ..() From 3575bc9928e64294978520f02010986726683593 Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Fri, 31 Oct 2025 23:22:25 -0300 Subject: [PATCH 13/60] Item Desant: Part 1 Items can be thrown across the tank, even if you're not atop it. Items can now also move alongside the tank when dropped or thrown atop. Exceptionally, roller beds, cryo bags and bogybags (deployed versions) can also be brought up and down the tank despite being obj/structures. Further refinement of the procs that handle transitioning between the tank top and down with a dragged mob. TODO: Handle edge case for roller beds with stasis or bodybags ontop. --- code/__DEFINES/layers.dm | 2 + code/game/objects/items.dm | 14 ++ code/game/objects/items/bodybag.dm | 8 ++ code/game/objects/objs.dm | 44 ++++++- .../structures/stool_bed_chair_nest/bed.dm | 8 ++ code/modules/movement/launching/launching.dm | 6 + code/modules/vehicles/tank/tank.dm | 65 ++++++++- code/modules/vehicles/tank/tank_helpers.dm | 124 +++++++++++++++++- 8 files changed, 261 insertions(+), 10 deletions(-) diff --git a/code/__DEFINES/layers.dm b/code/__DEFINES/layers.dm index 98c3bf2300bb..26dc45a9ab95 100644 --- a/code/__DEFINES/layers.dm +++ b/code/__DEFINES/layers.dm @@ -137,6 +137,8 @@ /// For WEATHER #define WEATHER_LAYER 4.14 +/// For objs atop a tank +#define TANK_RIDER_OBJ_LAYER 4.505 /// For mobs riding atop a tank #define TANK_RIDER_LAYER 4.51 diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index cf0c58f08dd8..8313fd4e2396 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -167,6 +167,9 @@ /// Special storages this item prioritizes var/list/preferred_storage + /// Rifles, shells, grenades, metal sheets, et-cetera should all be able to 'fit' atop the tank. + is_allowed_atop_vehicle = TRUE + /obj/item/Initialize(mapload, ...) . = ..() @@ -410,6 +413,17 @@ playsound(src, drop_sound, dropvol, drop_vary) src.do_drop_animation(user) + if(isliving(user)) + var/mob/living/L = user + var/obj/vehicle/multitile/tank/T = L.tank_on_top_of + if(T && !src.is_atop_vehicle) // tank exists, our user is atop the tank, we are not marked as atop the tank yet. + T.obj_mark_on_top(src) + else if (!T && src.is_atop_vehicle) // only remove from vehicle if it is atop a vehicle to begin with + src.tank_on_top_of.obj_clear_on_top(src) + else if (T && !src.is_atop_vehicle) // User on tank AND already marked + src.pixel_y = initial(src.pixel_y) + 12 // Just refresh the pixel offset and layer to avoid a visual bug with animations + src.layer = TANK_RIDER_OBJ_LAYER + appearance_flags &= ~NO_CLIENT_COLOR //So saturation/desaturation etc. effects affect it. /// Called just as an item is picked up (loc is not yet changed) and will return TRUE if the pickup wasn't canceled. diff --git a/code/game/objects/items/bodybag.dm b/code/game/objects/items/bodybag.dm index efa6342f6c86..c572231df984 100644 --- a/code/game/objects/items/bodybag.dm +++ b/code/game/objects/items/bodybag.dm @@ -29,6 +29,13 @@ var/obj/structure/closet/bodybag/R = new unfolded_path(location, src) R.add_fingerprint(user) R.open(user) + if(isliving(user)) + var/mob/living/L = user + var/obj/vehicle/multitile/tank/T = L.tank_on_top_of + if(T && !R.is_atop_vehicle) // tank exists, our user is atop the tank, we are not marked as atop the tank yet. + T.obj_mark_on_top(R) + else if (!T && R.is_atop_vehicle) // only remove from vehicle if it is atop a vehicle to begin with + R.tank_on_top_of.obj_clear_on_top(R) user.temp_drop_inv_item(src) qdel(src) @@ -83,6 +90,7 @@ /// How many extra pixels to offset the bag by when buckled, since rollerbeds are set up to offset a centered horizontal human sprite. var/buckle_offset = 5 store_items = FALSE + is_allowed_atop_vehicle = TRUE // Allows bodybags, exceptionally, as structures, to be used atop vehicles such as the tank. /obj/structure/closet/bodybag/Initialize() . = ..() diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index a15da2ef98d3..577284116bc0 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -37,6 +37,15 @@ vis_flags = VIS_INHERIT_PLANE + /// Is this item allowed atop a climbable vehicle? + /// Not at all intended for structures: This is meant for grabbable items. Weapons, ammo, pizza crates, etc... + /// ...However, defining it as part of obj instead of obj/item helps us handle exceptions. + /// E.G: Exceptionally, as QoL for corpsmen: bodybags, stasis bags, and roller beds are allowed. + var/is_allowed_atop_vehicle = FALSE + /// Tracks whether this item is currently atop a vehicle. + var/is_atop_vehicle = FALSE + /// Which vehicle are we ontop of? + var/tmp/obj/vehicle/multitile/tank/tank_on_top_of = null /obj/Initialize(mapload, ...) . = ..() @@ -348,10 +357,39 @@ SPAN_NOTICE("You are buckled in to [src] by [user]."), SPAN_NOTICE("You hear metal clanking")) +/obj/Moved(atom/oldloc, direction, Forced = FALSE) + . = ..() + if(is_atop_vehicle && isturf(loc)) + var/still_on_tank = FALSE + for(var/obj/vehicle/multitile/tank/T in loc) + if(loc in T.locs) + still_on_tank = TRUE + break + if(!still_on_tank) + src.tank_on_top_of.obj_clear_on_top(src) + /obj/Move(NewLoc, direct) . = ..() handle_rotation() - if(. && buckled_mob && !handle_buckled_mob_movement(loc,direct)) //movement fails if buckled mob's move fails. + if(src.is_atop_vehicle) + var/still_on_tank = FALSE + if(isturf(NewLoc)) + for(var/obj/vehicle/multitile/tank/T in NewLoc) + if(NewLoc in T.locs) + still_on_tank = TRUE + break + + if(!still_on_tank && buckled_mob && ismob(buckled_mob)) + var/mob/living/M = buckled_mob + var/obj/vehicle/multitile/tank/temp_tank = M.tank_on_top_of + if(temp_tank) + temp_tank.clear_on_top(M) + + src.forceMove(NewLoc) + if(buckled_mob) + buckled_mob.forceMove(NewLoc) + return + if(. && buckled_mob && !handle_buckled_mob_movement(loc,direct)) . = FALSE /obj/forceMove(atom/dest) @@ -471,6 +509,10 @@ return /obj/handle_flamer_fire(obj/flamer_fire/fire, damage, delta_time) + if(isitem(src)) + var/obj/item/T = src + if(T.is_atop_vehicle) // don't process flamer fire if the item is atop a climbable vehicle. + return . = ..() flamer_fire_act(damage, fire.weapon_cause_data) diff --git a/code/game/objects/structures/stool_bed_chair_nest/bed.dm b/code/game/objects/structures/stool_bed_chair_nest/bed.dm index 0750a495476e..76c28065b6a2 100644 --- a/code/game/objects/structures/stool_bed_chair_nest/bed.dm +++ b/code/game/objects/structures/stool_bed_chair_nest/bed.dm @@ -214,6 +214,7 @@ foldabletype = /obj/item/roller accepts_bodybag = TRUE base_bed_icon = "roller" + is_allowed_atop_vehicle = TRUE // Allows roller beds, exceptionally, as structures, to be used atop vehicles such as the tank. /obj/structure/bed/roller/MouseDrop(atom/over_object) if(foldabletype && !buckled_mob && !buckled_bodybag) @@ -295,6 +296,13 @@ roller.add_fingerprint(user) user.temp_drop_inv_item(src) forceMove(roller) + if(isliving(user)) + var/mob/living/L = user + var/obj/vehicle/multitile/tank/T = L.tank_on_top_of + if(T && !roller.is_atop_vehicle) // tank exists, our user is atop the tank, we are not marked as atop the tank yet. + T.obj_mark_on_top(roller) + else if (!T && roller.is_atop_vehicle) // only remove from vehicle if it is atop a vehicle to begin with + roller.tank_on_top_of.obj_clear_on_top(roller) SEND_SIGNAL(user, COMSIG_MOB_ITEM_ROLLER_DEPLOYED, roller) /obj/item/roller/afterattack(obj/target, mob/user, proximity) diff --git a/code/modules/movement/launching/launching.dm b/code/modules/movement/launching/launching.dm index c598482f3227..43f4e618ef41 100644 --- a/code/modules/movement/launching/launching.dm +++ b/code/modules/movement/launching/launching.dm @@ -204,6 +204,8 @@ break if (LM.dist >= LM.range) break + if(locate(/obj/vehicle/multitile/tank) in T) + layer = TANK_RIDER_OBJ_LAYER // prevents thrown items from being layered under a vehicle during throw. if (!Move(T)) // If this returns FALSE, then a collision happened break last_loc = loc @@ -212,10 +214,14 @@ sleep(delay) //done throwing, either because it hit something or it finished moving + src.layer = initial(src.layer) if ((isobj(src) || ismob(src)) && throwing && !early_exit) var/turf/T = get_turf(src) if(!istype(T)) return + var/obj/vehicle/multitile/tank/tank = locate(/obj/vehicle/multitile/tank) + if(tank && (tank in T)) + tank.obj_mark_on_top(src) var/atom/hit_atom = ismob(LM.target) ? null : T // TODO, just check for LM.target, the ismob is to prevent funky behavior with grenades 'n crates if(!hit_atom) for(var/atom/A in T) diff --git a/code/modules/vehicles/tank/tank.dm b/code/modules/vehicles/tank/tank.dm index 429ed577a6c1..043bcbf6509c 100644 --- a/code/modules/vehicles/tank/tank.dm +++ b/code/modules/vehicles/tank/tank.dm @@ -76,6 +76,7 @@ explosive_resistance = 400 var/list/on_top_mobs = list() /// keeps track of all mobs currently atop the tank + var/list/on_top_obj = list() /// keeps track of all objs currently atop the tank var/on_top_mobs_shooting_inaccuracy_time = 0 /// world_time must be bigger than this so mobs don't get penalized for shooting while atop the tank. /obj/vehicle/multitile/tank/update_next_move() @@ -362,8 +363,7 @@ to_chat(user, SPAN_WARNING("The spot is no longer reachable.")) return } - clear_on_top(user) - user.forceMove(target) + _carry_remove_with_grabs(user, target) user.visible_message( SPAN_WARNING("[user] climbs down from [src]."), SPAN_WARNING("You climb down from [src].")) @@ -394,6 +394,37 @@ M.tank_on_top_of = src _apply_rider_visuals(M) +/** + * obj_mark_on_top WOULD be an ad-hoc polymorph of mark_on_top. It does the same thing, but with an obj/ type. + * + * This proc does three things: + * + * Checks if the obj can be brought atop a tank. + * It adds the obj to the tank's list of objs (on_top_objs) + * It adds the tank to a obj'ss tank_on_top_of var + * It calls _obj_apply_rider_visuals() to set the layer atop the tank's + * + * Arguments: + * * obj/O - The obj being marked ontop. + */ +/obj/vehicle/multitile/tank/proc/obj_mark_on_top(obj/O) + if(!istype(O)) + return + if(!O.is_allowed_atop_vehicle) + return + if(O.z != z) + return + if(!(get_turf(O) in locs)) + return + if(O.tank_on_top_of == src) + O.layer = TANK_RIDER_OBJ_LAYER // prevents a visual bug with layering + return + on_top_obj |= O + O.tank_on_top_of = src + O.is_atop_vehicle = TRUE + O.pixel_y = initial(O.pixel_y) + 12 + O.layer = TANK_RIDER_OBJ_LAYER + /** * clear_on_top removes rider effects from a mob who was previously atop the tank. * @@ -408,24 +439,41 @@ if(!istype(M)) return on_top_mobs -= M - if(M.tank_on_top_of == src) - M.tank_on_top_of = null + M.tank_on_top_of = null M.layer = initial(M.layer) M.plane = initial(M.plane) M.pixel_y = initial(M.pixel_y) +/** + * obj_clear_on_top removes rider effects from an obj atop the tank. + * + * This behaves as an ad-hoc polymorphic version of clear_on_top. + */ +/obj/vehicle/multitile/tank/proc/obj_clear_on_top(obj/O) + if(!istype(O)) + return + on_top_obj -= O + O.tank_on_top_of = null + O.is_atop_vehicle = FALSE + O.pixel_y = initial(O.pixel_y) + O.layer = initial(O.layer) + /** * Destroy proc. This shouldn't normally be called, but just in case. * * This proc ensures all mobs atop the tank are cleared. - * we also cut the on_top_mobs list, just in case DM doesn't have garbage collection. + * we also cut the on_top_mobs and on_top_obj list, just in case DM doesn't have garbage collection. * */ /obj/vehicle/multitile/tank/Destroy() for(var/mob/living/M in on_top_mobs.Copy()) if(M) clear_on_top(M) + for(var/obj/O in on_top_obj.Copy()) + if(O) + obj_clear_on_top(O) on_top_mobs.Cut() + on_top_obj.Cut() return ..() /** @@ -465,6 +513,13 @@ var/turf/target = get_step(start, target_dir) if(target && (target in src.locs)) return NO_BLOCKED_MOVEMENT + else if(isobj(mover)) + var/obj/O = mover + if(O.is_atop_vehicle) + var/turf/start = get_turf(O) + var/turf/target = get_step(start, target_dir) + if(target && (target in src.locs)) + return NO_BLOCKED_MOVEMENT var/ret = ..() if(ret != null) return ret diff --git a/code/modules/vehicles/tank/tank_helpers.dm b/code/modules/vehicles/tank/tank_helpers.dm index 246b1e36d8b1..f140f0bab73b 100644 --- a/code/modules/vehicles/tank/tank_helpers.dm +++ b/code/modules/vehicles/tank/tank_helpers.dm @@ -12,17 +12,45 @@ * The intent of this proc is double: Allowing tank drivers to defend themselves and have a chance against xenos * atop the tank and also punish tank drivers for bad driving by fucking up the marines on top. * + * Also scatters objs on top! + * */ /obj/vehicle/multitile/tank/proc/_scatter_riders_on_crash() if(abs(move_momentum) < 1.6) return + var/sweep_range = 3 + + // Obj throwing is handled differently for performance + // there's still a lot to optimize in case this is performance intensive. (I don't expect joustling to happen that often) + // First order of business would be finding a way to do those calculations outside of the for loop ... + // and use one single cur,throw_dir and start for all objs but that might produce throws with inconsistent directions + + for(var/obj/O in on_top_obj.Copy()) + if(!O || O.z != src.z) + obj_clear_on_top(O) + continue + + var/turf/start = get_turf(O) + var/throw_dir = get_dir(src, start) + if(!throw_dir) + throw_dir = pick(GLOB.cardinals) + + var/turf/target = get_step(start, throw_dir) + if(!target || (target in src.locs)) + step_away(O, src, sweep_range, 3) + var/turf/cur = get_turf(O) + target = get_step(cur, throw_dir) + + if(target && !(target in src.locs)) + obj_clear_on_top(O) + O.throw_atom(target, sweep_range, SPEED_FAST) + for(var/mob/living/M in on_top_mobs.Copy()) if(!M || M.z != src.z) clear_on_top(M) continue - var/sweep_range = 3 var/turf/start = get_turf(M) var/throw_dir = get_dir(src, start) if(!throw_dir) @@ -204,6 +232,28 @@ /obj/vehicle/multitile/tank/proc/_update_riders_after_motion(old_cx, old_cy, old_dir, new_cx, new_cy, new_dir) var/k = _quarter_turns(old_dir, new_dir) // -1,0,1,2 + // simpler version for objs + for(var/obj/O in on_top_obj.Copy()) + if(!O || O.z != src.z) + obj_clear_on_top(O) + continue + + var/turf/from = get_turf(O) + if(!from) + obj_clear_on_top(O) + continue + + var/list/rd = _rotated_offset(from.x - old_cx, from.y - old_cy, k) + var/turf/target = locate(new_cx + rd[1], new_cy + rd[2], src.z) + + if(target && (target in src.locs) && target != from) + O.forceMove(target) + obj_mark_on_top(O) + else if(from in src.locs) + obj_mark_on_top(O) + else + obj_clear_on_top(O) + for(var/mob/living/M in on_top_mobs.Copy()) if(!M || M.z != src.z) clear_on_top(M) @@ -214,6 +264,10 @@ clear_on_top(M) continue + // If the mob is buckled to something, we'll skip it, because the buckled obj has already moved with it. + if(M.buckled) + continue + // Offset from OLD center -> rotate by k -> translate to NEW center var/odx = from.x - old_cx var/ody = from.y - old_cy @@ -287,19 +341,81 @@ for(var/obj/item/grab/G in list(user.l_hand, user.r_hand)) if(G?.grabbed_thing) grabbed_things += G.grabbed_thing - G.grabbed_thing.forceMove(user.loc) + + for(var/atom/movable/thing as anything in grabbed_things) + if(isliving(thing)) + var/mob/living/L = thing + L.apply_effect(2, WEAKEN) + // Edge case: We're hauling a mob atop the tank who is buckled to a roller bed. (Grab is on mob) + // In that case, also bring the roller to the top of the tank. + if(L.buckled && istype(L.buckled, /obj/structure/bed/roller)) + var/obj/structure/bed/roller/R = L.buckled + R.forceMove(dest) + obj_mark_on_top(R) + L.forceMove(dest) + mark_on_top(L) + + else if(isobj(thing)) + var/obj/O = thing + if(O.is_allowed_atop_vehicle) + O.forceMove(dest) + obj_mark_on_top(O) + // Edge case: We're hauling a roller bed atop the tank with a mob buckled to it. (Grab is on roller bed) + // In that case, also bring the mob atop to the top of the tank. + if(istype(O, /obj/structure/bed/roller)) + var/obj/structure/bed/roller/R = O + if(R.buckled_mob) + var/mob/living/L = R.buckled_mob + L.forceMove(dest) + mark_on_top(L) user.forceMove(dest) mark_on_top(user) +/** + * This proc allows a marine to pull another one DOWN the tank once he finishes climbing down. + * + * Opposite of carry_move_with_grabs. This one does the same thing but for climbing DOWN. + * + * Arguments: + * * mob/living/user = Mob doing the pulling. + * * turf/dest = Which turf on the tank we're moving to + */ +/obj/vehicle/multitile/tank/proc/_carry_remove_with_grabs(mob/living/user, turf/dest) + var/list/grabbed_things = list() + for(var/obj/item/grab/G in list(user.l_hand, user.r_hand)) + if(G?.grabbed_thing) + grabbed_things += G.grabbed_thing for(var/atom/movable/thing as anything in grabbed_things) - thing.forceMove(dest) if(isliving(thing)) var/mob/living/L = thing L.apply_effect(2, WEAKEN) - mark_on_top(L) + // Edge case: We're hauling a mob down the tank who is buckled to a roller bed. (Grab is on mob) + // In that case, also bring the roller down the top of the tank. + if(L.buckled && istype(L.buckled, /obj/structure/bed/roller)) + var/obj/structure/bed/roller/R = L.buckled + R.forceMove(dest) + obj_clear_on_top(R) + L.forceMove(dest) + clear_on_top(L) + + else if(isobj(thing)) + var/obj/O = thing + if(O.is_allowed_atop_vehicle) + O.forceMove(dest) + obj_clear_on_top(O) + // Edge case: We're hauling a roller bed down the tank with a mob buckled to it. (Grab is on roller bed) + // In that case, also bring the mob down the top of the tank. + if(istype(O, /obj/structure/bed/roller)) + var/obj/structure/bed/roller/R = O + if(R.buckled_mob) + var/mob/living/L = R.buckled_mob + L.forceMove(dest) + clear_on_top(L) + user.forceMove(dest) + clear_on_top(user) // --- blocking helpers. Checks if turf is blocked, etc. From fad6163e631a581975031a482817e3e497f86364 Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Fri, 7 Nov 2025 23:40:40 -0300 Subject: [PATCH 14/60] Improves roller beds and stasis bags on tank --- code/__DEFINES/layers.dm | 4 + code/game/objects/items/bodybag.dm | 17 +- code/game/objects/objs.dm | 8 + .../structures/crates_lockers/closets.dm | 10 + .../structures/stool_bed_chair_nest/bed.dm | 20 +- code/modules/vehicles/tank/tank.dm | 42 ++-- code/modules/vehicles/tank/tank_helpers.dm | 184 ++++++++++++++---- 7 files changed, 227 insertions(+), 58 deletions(-) diff --git a/code/__DEFINES/layers.dm b/code/__DEFINES/layers.dm index 26dc45a9ab95..d2e0dd975aec 100644 --- a/code/__DEFINES/layers.dm +++ b/code/__DEFINES/layers.dm @@ -139,6 +139,10 @@ /// For objs atop a tank #define TANK_RIDER_OBJ_LAYER 4.505 +/// Mostly for bodybags atop a tank +/// Not used yet. This is a reminder for me to add a specific tank_layer var to OBJs to keep track of +/// ...which specific layer they need to go when brought atop. +#define TANK_RIDER_ABOVE_OBJ_LAYER 4.506 /// For mobs riding atop a tank #define TANK_RIDER_LAYER 4.51 diff --git a/code/game/objects/items/bodybag.dm b/code/game/objects/items/bodybag.dm index c572231df984..437d1f98c785 100644 --- a/code/game/objects/items/bodybag.dm +++ b/code/game/objects/items/bodybag.dm @@ -217,7 +217,8 @@ /obj/structure/closet/bodybag/forceMove(atom/destination) if(roller_buckled) - roller_buckled.unbuckle() + if(!is_atop_vehicle && !roller_buckled.is_atop_vehicle) + roller_buckled.unbuckle() . = ..() @@ -281,10 +282,16 @@ overlays.Cut() // makes sure any previous triage cards are removed if(!stasis_mob) - layer = initial(layer) + if(is_atop_vehicle) + layer = TANK_RIDER_OBJ_LAYER + else + layer = initial(layer) return - layer = LYING_BETWEEN_MOB_LAYER + if(is_atop_vehicle) + layer = TANK_RIDER_OBJ_LAYER + else + layer = LYING_BETWEEN_MOB_LAYER if(stasis_mob.holo_card_color && !opened) var/image/holo_card_icon = image('icons/obj/bodybag.dmi', src, "cryocard_[stasis_mob.holo_card_color]") @@ -299,6 +306,10 @@ if(stasis_mob) stasis_mob.in_stasis = FALSE UnregisterSignal(stasis_mob, COMSIG_HUMAN_TRIAGE_CARD_UPDATED) + if(src.is_atop_vehicle) + src.tank_on_top_of.mark_on_top(stasis_mob) + else if (stasis_mob.tank_on_top_of) + stasis_mob.tank_on_top_of.clear_on_top(stasis_mob) stasis_mob = null STOP_PROCESSING(SSobj, src) if(used > max_uses) diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index 577284116bc0..6f2d29d2b65b 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -249,8 +249,16 @@ SEND_SIGNAL(src, COMSIG_OBJ_AFTER_BUCKLE, buckled_mob) if(!buckled_mob) UnregisterSignal(M, COMSIG_PARENT_QDELETING) + if(isliving(M)) + var/mob/living/living_M + if(living_M.tank_on_top_of) + living_M.tank_on_top_of.clear_on_top(living_M) else RegisterSignal(buckled_mob, COMSIG_PARENT_QDELETING, PROC_REF(unbuckle)) + if(isliving(M)) + var/mob/living/living_M + if(src.is_atop_vehicle) + src.tank_on_top_of.mark_on_top(living_M) return buckled_mob /obj/proc/unbuckle() diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm index 6cb02285b87f..b9a7b7e94c0e 100644 --- a/code/game/objects/structures/crates_lockers/closets.dm +++ b/code/game/objects/structures/crates_lockers/closets.dm @@ -79,6 +79,11 @@ for(var/obj/I in src) I.forceMove(loc) + // closets can't go atop vehicles, but this sets behavior for bodybags, which can. + if(is_atop_vehicle) + tank_on_top_of.obj_mark_on_top(I) + else if (I.tank_on_top_of) + tank_on_top_of.obj_clear_on_top(I) for(var/mob/M in src) M.forceMove(loc) @@ -89,6 +94,11 @@ if(living_M.mobility_flags & MOBILITY_MOVE) M.visible_message(SPAN_WARNING("[M] suddenly gets out of [src]!"), SPAN_WARNING("You get out of [src] and get your bearings!")) + // closets can't go atop vehicles, but this sets behavior for bodybags, which can. + if(is_atop_vehicle) + tank_on_top_of.mark_on_top(living_M) + else if (living_M.tank_on_top_of) + living_M.tank_on_top_of.clear_on_top(living_M) /obj/structure/closet/proc/open() if(opened) diff --git a/code/game/objects/structures/stool_bed_chair_nest/bed.dm b/code/game/objects/structures/stool_bed_chair_nest/bed.dm index 76c28065b6a2..84db0261b76b 100644 --- a/code/game/objects/structures/stool_bed_chair_nest/bed.dm +++ b/code/game/objects/structures/stool_bed_chair_nest/bed.dm @@ -60,14 +60,20 @@ /obj/structure/bed/afterbuckle(mob/M) . = ..() if(. && buckled_mob == M) - M.pixel_y = buckling_y + if(is_atop_vehicle) + M.pixel_y = buckling_y + 12 // Magic number bad. TODO: Make tank pixel offset its own variable. + else + M.pixel_y = buckling_y M.old_y = buckling_y M.pixel_x = buckling_x M.old_x = buckling_x if(base_bed_icon) density = TRUE else - M.pixel_y = initial(buckled_mob.pixel_y) + if(is_atop_vehicle) + M.pixel_y = initial(buckled_mob.pixel_y) + 12 // Magic number bad. TODO: Make tank pixel offset its own variable. + else + M.pixel_y = initial(buckled_mob.pixel_y) M.old_y = initial(buckled_mob.pixel_y) M.pixel_x = initial(buckled_mob.pixel_x) M.old_x = initial(buckled_mob.pixel_x) @@ -85,7 +91,10 @@ buckled_bodybag = B density = TRUE update_icon() - if(buckling_y) + if(is_atop_vehicle) + tank_on_top_of.obj_mark_on_top(buckled_bodybag) + buckled_bodybag.pixel_y = buckled_bodybag.buckle_offset + 12 // Magic number bad. TODO: Make tank pixel offset its own variable. + else if(buckling_y) buckled_bodybag.pixel_y = buckled_bodybag.buckle_offset + buckling_y add_fingerprint(user) var/mob/living/carbon/human/contained_mob = locate() in B.contents @@ -94,7 +103,10 @@ /obj/structure/bed/unbuckle() if(buckled_bodybag) - buckled_bodybag.pixel_y = initial(buckled_bodybag.pixel_y) + if(is_atop_vehicle) + buckled_bodybag.pixel_y = initial(buckled_bodybag.pixel_y) + 12 // Magic number bad. TODO: Make tank pixel offset its own variable. + else + buckled_bodybag.pixel_y = initial(buckled_bodybag.pixel_y) buckled_bodybag.roller_buckled = null buckled_bodybag = null density = FALSE diff --git a/code/modules/vehicles/tank/tank.dm b/code/modules/vehicles/tank/tank.dm index 043bcbf6509c..1d78c553f3f9 100644 --- a/code/modules/vehicles/tank/tank.dm +++ b/code/modules/vehicles/tank/tank.dm @@ -419,11 +419,18 @@ if(O.tank_on_top_of == src) O.layer = TANK_RIDER_OBJ_LAYER // prevents a visual bug with layering return - on_top_obj |= O - O.tank_on_top_of = src + on_top_obj |= O + O.tank_on_top_of = src O.is_atop_vehicle = TRUE - O.pixel_y = initial(O.pixel_y) + 12 - O.layer = TANK_RIDER_OBJ_LAYER + O.layer = TANK_RIDER_OBJ_LAYER + if(istype(O, /obj/structure/closet/bodybag)) + var/obj/structure/closet/bodybag/BB = O + if(BB.roller_buckled) + BB.pixel_y = BB.buckle_offset + 12 + else + BB.pixel_y = initial(O.pixel_y) + 12 + else + O.pixel_y = initial(O.pixel_y) + 12 /** * clear_on_top removes rider effects from a mob who was previously atop the tank. @@ -438,11 +445,11 @@ /obj/vehicle/multitile/tank/proc/clear_on_top(mob/living/M) if(!istype(M)) return - on_top_mobs -= M - M.tank_on_top_of = null - M.layer = initial(M.layer) - M.plane = initial(M.plane) - M.pixel_y = initial(M.pixel_y) + on_top_mobs -= M + M.tank_on_top_of = null + M.layer = initial(M.layer) + M.plane = initial(M.plane) + M.pixel_y = initial(M.pixel_y) /** * obj_clear_on_top removes rider effects from an obj atop the tank. @@ -452,11 +459,18 @@ /obj/vehicle/multitile/tank/proc/obj_clear_on_top(obj/O) if(!istype(O)) return - on_top_obj -= O - O.tank_on_top_of = null - O.is_atop_vehicle = FALSE - O.pixel_y = initial(O.pixel_y) - O.layer = initial(O.layer) + on_top_obj -= O + O.tank_on_top_of = null + O.is_atop_vehicle = FALSE + O.layer = initial(O.layer) + if(istype(O, /obj/structure/closet/bodybag)) + var/obj/structure/closet/bodybag/BB = O + if(BB.roller_buckled) + BB.pixel_y = BB.buckle_offset + else + BB.pixel_y = initial(O.pixel_y) + else + O.pixel_y = initial(O.pixel_y) /** * Destroy proc. This shouldn't normally be called, but just in case. diff --git a/code/modules/vehicles/tank/tank_helpers.dm b/code/modules/vehicles/tank/tank_helpers.dm index f140f0bab73b..47bb2a2aae91 100644 --- a/code/modules/vehicles/tank/tank_helpers.dm +++ b/code/modules/vehicles/tank/tank_helpers.dm @@ -238,6 +238,12 @@ obj_clear_on_top(O) continue + // Don't move body bags atop roller beds. We will handle those when we move the roller beds themselves. + if(istype(O, /obj/structure/closet/bodybag)) + var/obj/structure/closet/bodybag/BB = O + if(BB.roller_buckled) + continue + var/turf/from = get_turf(O) if(!from) obj_clear_on_top(O) @@ -249,6 +255,11 @@ if(target && (target in src.locs) && target != from) O.forceMove(target) obj_mark_on_top(O) + if(istype(O, /obj/structure/bed/roller)) + var/obj/structure/bed/roller/R = O + if(R.buckled_bodybag) + R.buckled_bodybag.forceMove(target) + obj_mark_on_top(R.buckled_bodybag) else if(from in src.locs) obj_mark_on_top(O) else @@ -331,6 +342,7 @@ * You'll pull the marine you're grabbing up with you as soon as YOUR climb_onto finishes. * The marine pulled up will get a 2 second weaken, but they can be shaked out of it. * + * !!!! The order of the operations of clearing, marking and moving is not arbitrary !!!! * * Arguments: * * mob/living/user = Mob doing the pulling. @@ -346,37 +358,84 @@ if(isliving(thing)) var/mob/living/L = thing L.apply_effect(2, WEAKEN) - // Edge case: We're hauling a mob atop the tank who is buckled to a roller bed. (Grab is on mob) - // In that case, also bring the roller to the top of the tank. - if(L.buckled && istype(L.buckled, /obj/structure/bed/roller)) - var/obj/structure/bed/roller/R = L.buckled - R.forceMove(dest) - obj_mark_on_top(R) + _add_to_top_buckled_to(L, dest) L.forceMove(dest) mark_on_top(L) else if(isobj(thing)) var/obj/O = thing if(O.is_allowed_atop_vehicle) - O.forceMove(dest) - obj_mark_on_top(O) - // Edge case: We're hauling a roller bed atop the tank with a mob buckled to it. (Grab is on roller bed) - // In that case, also bring the mob atop to the top of the tank. - if(istype(O, /obj/structure/bed/roller)) - var/obj/structure/bed/roller/R = O - if(R.buckled_mob) - var/mob/living/L = R.buckled_mob - L.forceMove(dest) - mark_on_top(L) + _add_to_top_buckled_entity(O, dest) user.forceMove(dest) mark_on_top(user) +/** + * Handles the addition of a roller bed whom a Mob is buckled to onto the top of the tank + * + * Mobs can be buckled to roller beds and pulled atop the tank. If this happens, we need to take the roller bed up with us + * + * Arguments: + * * mob/living/L = The mob that may be buckled to a roller bed. + * * turf/dest = Which turf onto the tank we're moving to + */ + +/obj/vehicle/multitile/tank/proc/_add_to_top_buckled_to(mob/living/L, turf/dest) + if(L.buckled && istype(L.buckled, /obj/structure/bed/roller)) + var/obj/structure/bed/roller/R = L.buckled + R.forceMove(dest) + obj_mark_on_top(R) + +/** + * Handles the addition of entities buckled to an obj to atop the tank + * + * Roller beds can have mobs and body or stasis bags attached to it. If we are pulling a roller bed onhto the tank with an attached... + * bag or mob, we should ensure those go up with it correctly. + * + * Arguments: + * * obj/object = The object that may have living beings buckled to it + * * turf/dest = Which turf onto the tank we're moving to + */ + +/obj/vehicle/multitile/tank/proc/_add_to_top_buckled_entity(obj/object, turf/dest) + if(istype(object, /obj/structure/bed/roller)) + var/obj/structure/bed/roller/R = object + + R.forceMove(dest) + obj_mark_on_top(R) + if(R.buckled_mob) + var/mob/living/L = R.buckled_mob + L.forceMove(dest) + mark_on_top(L) + else if (R.buckled_bodybag) + var/obj/structure/closet/bodybag/BB = R.buckled_bodybag + BB.forceMove(dest) + obj_mark_on_top(BB) + else if (istype(object, /obj/structure/closet/bodybag)) // Grab is on a bodybag, possibly atop a roller. + var/obj/structure/closet/bodybag/BB = object + + if (BB.roller_buckled) + var/obj/structure/bed/roller/R = BB.roller_buckled + R.forceMove(dest) + obj_mark_on_top(R) // mark ASAP to prevent bodybag from unbuckling + BB.forceMove(dest) + obj_mark_on_top(BB) + + else // lone bodybag + BB.forceMove(dest) + obj_mark_on_top(BB) + + else // any other obj + object.forceMove(dest) + obj_mark_on_top(object) + /** * This proc allows a marine to pull another one DOWN the tank once he finishes climbing down. * * Opposite of carry_move_with_grabs. This one does the same thing but for climbing DOWN. * + * !!!! The order of the operations of clearing, marking and moving is not arbitrary !!!! + * * Arguments: * * mob/living/user = Mob doing the pulling. * * turf/dest = Which turf on the tank we're moving to @@ -388,34 +447,85 @@ grabbed_things += G.grabbed_thing for(var/atom/movable/thing as anything in grabbed_things) - if(isliving(thing)) + if(isliving(thing)) // GRAB is on MOB var/mob/living/L = thing - L.apply_effect(2, WEAKEN) - // Edge case: We're hauling a mob down the tank who is buckled to a roller bed. (Grab is on mob) - // In that case, also bring the roller down the top of the tank. - if(L.buckled && istype(L.buckled, /obj/structure/bed/roller)) - var/obj/structure/bed/roller/R = L.buckled - R.forceMove(dest) - obj_clear_on_top(R) - L.forceMove(dest) + _remove_from_top_buckled_to(L, dest) clear_on_top(L) + L.forceMove(dest) - else if(isobj(thing)) + else if(isobj(thing)) // GRAB is on OBJ var/obj/O = thing if(O.is_allowed_atop_vehicle) - O.forceMove(dest) - obj_clear_on_top(O) - // Edge case: We're hauling a roller bed down the tank with a mob buckled to it. (Grab is on roller bed) - // In that case, also bring the mob down the top of the tank. - if(istype(O, /obj/structure/bed/roller)) - var/obj/structure/bed/roller/R = O - if(R.buckled_mob) - var/mob/living/L = R.buckled_mob - L.forceMove(dest) - clear_on_top(L) + _remove_from_top_buckled_entity(O, dest) - user.forceMove(dest) clear_on_top(user) + user.forceMove(dest) + +/** + * Handles the removal of a roller bed whom a Mob is buckled to off the top of the tank + * + * Mobs can be buckled to roller beds and pulled off of the tank. If this happens, we need to take the roller bed down with us + * + * Arguments: + * * mob/living/L = The mob that may be buckled to a roller bed. + * * turf/dest = Which turf off the tank we're moving to + */ + +/obj/vehicle/multitile/tank/proc/_remove_from_top_buckled_to(mob/living/L, turf/dest) + if(L.buckled && istype(L.buckled, /obj/structure/bed/roller)) + var/obj/structure/bed/roller/R = L.buckled + R.forceMove(dest) + obj_clear_on_top(R) + +/** + * Handles the removal of entities buckled to an obj from atop the tank + * + * Roller beds can have mobs and body or stasis bags attached to it. If we are pulling a roller bed down the tank with an attached... + * bag or mob, we should ensure those go down with it correctly. + * + * Arguments: + * * obj/object = The object that may have living beings buckled to it + * * turf/dest = Which turf off the tank we're moving to + */ + +/obj/vehicle/multitile/tank/proc/_remove_from_top_buckled_entity(obj/object, turf/dest) + if(istype(object, /obj/structure/bed/roller)) + var/obj/structure/bed/roller/R = object + + if(R.buckled_mob) + var/mob/living/L = R.buckled_mob + obj_clear_on_top(object) // clear first then move to prevent buckled mobs from getting debuffed for improperly dismounting. + object.forceMove(dest) + clear_on_top(L) + + else if (R.buckled_bodybag) + var/obj/structure/closet/bodybag/BB = R.buckled_bodybag + object.forceMove(dest) + obj_clear_on_top(object) + BB.forceMove(dest) // must be moved last to avoid unbuckling + obj_clear_on_top(BB) + + else // empty roller bed + object.forceMove(dest) + obj_clear_on_top(object) + + else if (istype(object, /obj/structure/closet/bodybag)) // Grab is on a bodybag, possibly atop a roller. + var/obj/structure/closet/bodybag/BB = object + + if (BB.roller_buckled) + var/obj/structure/bed/roller/R = BB.roller_buckled + R.forceMove(dest) + obj_clear_on_top(R) + BB.forceMove(dest) // must be moved last to avoid unbuckling + obj_clear_on_top(BB) + + else // lone bodybag + object.forceMove(dest) + obj_clear_on_top(object) + + else // any other item + object.forceMove(dest) + obj_clear_on_top(object) // --- blocking helpers. Checks if turf is blocked, etc. From cf6367893b1be013337651766676a6a0409e1546 Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Fri, 7 Nov 2025 23:54:59 -0300 Subject: [PATCH 15/60] Optimizes rider jousting and rider motion --- code/modules/vehicles/tank/tank_helpers.dm | 88 +++++++++++----------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/code/modules/vehicles/tank/tank_helpers.dm b/code/modules/vehicles/tank/tank_helpers.dm index 47bb2a2aae91..913f3cfd5e9c 100644 --- a/code/modules/vehicles/tank/tank_helpers.dm +++ b/code/modules/vehicles/tank/tank_helpers.dm @@ -20,14 +20,11 @@ return var/sweep_range = 3 - - // Obj throwing is handled differently for performance - // there's still a lot to optimize in case this is performance intensive. (I don't expect joustling to happen that often) - // First order of business would be finding a way to do those calculations outside of the for loop ... - // and use one single cur,throw_dir and start for all objs but that might produce throws with inconsistent directions + var/src_z = src.z + var/list/src_locs = src.locs for(var/obj/O in on_top_obj.Copy()) - if(!O || O.z != src.z) + if(!O || O.z != src_z) obj_clear_on_top(O) continue @@ -37,17 +34,19 @@ throw_dir = pick(GLOB.cardinals) var/turf/target = get_step(start, throw_dir) - if(!target || (target in src.locs)) + if(!target || (target in src_locs) || target.density) step_away(O, src, sweep_range, 3) var/turf/cur = get_turf(O) target = get_step(cur, throw_dir) - if(target && !(target in src.locs)) + if(target && !(target in src_locs) && !target.density) obj_clear_on_top(O) O.throw_atom(target, sweep_range, SPEED_FAST) + else if(!(start in src_locs)) + obj_clear_on_top(O) for(var/mob/living/M in on_top_mobs.Copy()) - if(!M || M.z != src.z) + if(!M || M.z != src_z) clear_on_top(M) continue @@ -57,36 +56,42 @@ throw_dir = pick(GLOB.cardinals) var/turf/next_out = get_step(start, throw_dir) - if(next_out && !(next_out in src.locs)) - clear_on_top(M) - step(M, throw_dir) - else - step_away(M, src, sweep_range, 3) + var/successfully_moved = FALSE + if(next_out && !(next_out in src_locs) && !next_out.density) + successfully_moved = step(M, throw_dir) + if(successfully_moved) + clear_on_top(M) + + if(!successfully_moved) + step_away(M, src, sweep_range, 3) var/turf/cur = get_turf(M) var/turf/next2 = get_step(cur, throw_dir) - if(next2 && !(next2 in src.locs)) - clear_on_top(M) - step(M, throw_dir) + if(next2 && !(next2 in src_locs) && !next2.density) + successfully_moved = step(M, throw_dir) + if(successfully_moved) + clear_on_top(M) to_chat(M, SPAN_WARNING("You're thrown from [src]!")) playsound(M, "punch", 25, TRUE) shake_camera(M, 2, 1) - if(!(get_turf(M) in src.locs)) + var/turf/final_pos = get_turf(M) + + if(!(final_pos in src_locs)) M.apply_effect(3, WEAKEN) clear_on_top(M) continue - var/turf/current = get_turf(M) var/list/hull_neighbors = list() for(var/d in GLOB.cardinals) - var/turf/H = get_step(current, d) - if(H && (H in src.locs)) + var/turf/H = get_step(final_pos, d) + if(H && (H in src_locs) && !H.density) hull_neighbors += H + if(hull_neighbors.len) var/turf/spot = pick(hull_neighbors) - var/ndir = get_dir(current, spot) + var/ndir = get_dir(final_pos, spot) if(ndir) step(M, ndir) M.apply_effect(1.5, WEAKEN) @@ -231,10 +236,12 @@ */ /obj/vehicle/multitile/tank/proc/_update_riders_after_motion(old_cx, old_cy, old_dir, new_cx, new_cy, new_dir) var/k = _quarter_turns(old_dir, new_dir) // -1,0,1,2 + var/src_z = src.z + var/list/src_locs = src.locs // simpler version for objs for(var/obj/O in on_top_obj.Copy()) - if(!O || O.z != src.z) + if(!O || O.z != src_z) obj_clear_on_top(O) continue @@ -250,9 +257,9 @@ continue var/list/rd = _rotated_offset(from.x - old_cx, from.y - old_cy, k) - var/turf/target = locate(new_cx + rd[1], new_cy + rd[2], src.z) + var/turf/target = locate(new_cx + rd[1], new_cy + rd[2], src_z) - if(target && (target in src.locs) && target != from) + if(target && (target in src_locs) && target != from) O.forceMove(target) obj_mark_on_top(O) if(istype(O, /obj/structure/bed/roller)) @@ -260,53 +267,46 @@ if(R.buckled_bodybag) R.buckled_bodybag.forceMove(target) obj_mark_on_top(R.buckled_bodybag) - else if(from in src.locs) + else if(from in src_locs) obj_mark_on_top(O) else obj_clear_on_top(O) for(var/mob/living/M in on_top_mobs.Copy()) - if(!M || M.z != src.z) + if(!M || M.z != src_z) clear_on_top(M) continue + // If the mob is buckled to something, we'll skip it, because the buckled obj has already moved with it. + if(M.buckled) + continue var/turf/from = get_turf(M) if(!from) clear_on_top(M) continue - // If the mob is buckled to something, we'll skip it, because the buckled obj has already moved with it. - if(M.buckled) - continue - // Offset from OLD center -> rotate by k -> translate to NEW center - var/odx = from.x - old_cx - var/ody = from.y - old_cy - var/list/rd = _rotated_offset(odx, ody, k) - var/ndx = rd[1] - var/ndy = rd[2] - var/turf/target = locate(new_cx + ndx, new_cy + ndy, src.z) + var/list/rd = _rotated_offset(from.x - old_cx, from.y - old_cy, k) + var/turf/target = locate(new_cx + rd[1], new_cy + rd[2], src_z) // this shouldn't happen, but just to be safe. - if(!target || !(target in src.locs)) - if(from in src.locs) + if(!target || !(target in src_locs)) + if(from in src_locs) mark_on_top(M) else clear_on_top(M) continue // If target is blocked by a non-rider dense atom, (shouldn't happen cuz tank runs over shit) keep if possible - var/blocked = FALSE - if(target.density) - blocked = TRUE - else + var/blocked = target.density // Start with turf density check + if(!blocked) for(var/atom/A in target) if(A != src && A.density && (!ismob(A) || !(A in on_top_mobs))) blocked = TRUE break if(blocked) - if(from in src.locs) + if(from in src_locs) mark_on_top(M) else clear_on_top(M) From 9097142fde7b75764690de511ee5df3f93e89791 Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Fri, 7 Nov 2025 23:57:57 -0300 Subject: [PATCH 16/60] Fixes item layering bug when dropped atop tank --- code/game/objects/items.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 8313fd4e2396..e34c3197acca 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -420,7 +420,7 @@ T.obj_mark_on_top(src) else if (!T && src.is_atop_vehicle) // only remove from vehicle if it is atop a vehicle to begin with src.tank_on_top_of.obj_clear_on_top(src) - else if (T && !src.is_atop_vehicle) // User on tank AND already marked + else if (T && src.is_atop_vehicle) // User on tank AND already marked src.pixel_y = initial(src.pixel_y) + 12 // Just refresh the pixel offset and layer to avoid a visual bug with animations src.layer = TANK_RIDER_OBJ_LAYER From 73fa9d8300b6c988058233a7e129da72c254c796 Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Sat, 8 Nov 2025 00:17:17 -0300 Subject: [PATCH 17/60] Fixes mobs atop not falling down Fixes an oversight caused by cf6367893b1be013337651766676a6a0409e1546 --- code/modules/vehicles/tank/tank_helpers.dm | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/code/modules/vehicles/tank/tank_helpers.dm b/code/modules/vehicles/tank/tank_helpers.dm index 913f3cfd5e9c..e98a36e704f0 100644 --- a/code/modules/vehicles/tank/tank_helpers.dm +++ b/code/modules/vehicles/tank/tank_helpers.dm @@ -56,21 +56,17 @@ throw_dir = pick(GLOB.cardinals) var/turf/next_out = get_step(start, throw_dir) - var/successfully_moved = FALSE if(next_out && !(next_out in src_locs) && !next_out.density) - successfully_moved = step(M, throw_dir) - if(successfully_moved) - clear_on_top(M) - - if(!successfully_moved) + M.forceMove(next_out) + clear_on_top(M) + else step_away(M, src, sweep_range, 3) var/turf/cur = get_turf(M) var/turf/next2 = get_step(cur, throw_dir) if(next2 && !(next2 in src_locs) && !next2.density) - successfully_moved = step(M, throw_dir) - if(successfully_moved) - clear_on_top(M) + M.forceMove(next2) + clear_on_top(M) to_chat(M, SPAN_WARNING("You're thrown from [src]!")) playsound(M, "punch", 25, TRUE) @@ -91,9 +87,7 @@ if(hull_neighbors.len) var/turf/spot = pick(hull_neighbors) - var/ndir = get_dir(final_pos, spot) - if(ndir) - step(M, ndir) + M.forceMove(spot) M.apply_effect(1.5, WEAKEN) mark_on_top(M) From 7a19a9840edbda114ccd5b5e10e49ec9ae3a0e68 Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Mon, 10 Nov 2025 18:48:30 -0300 Subject: [PATCH 18/60] medevac rollers can't deploy atop tank --- code/game/objects/structures/stool_bed_chair_nest/bed.dm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/code/game/objects/structures/stool_bed_chair_nest/bed.dm b/code/game/objects/structures/stool_bed_chair_nest/bed.dm index 84db0261b76b..db098ff7fd06 100644 --- a/code/game/objects/structures/stool_bed_chair_nest/bed.dm +++ b/code/game/objects/structures/stool_bed_chair_nest/bed.dm @@ -465,6 +465,11 @@ GLOBAL_LIST_EMPTY(activated_medevac_stretchers) matter = list("plastic" = 5000, "metal" = 5000) /obj/item/roller/medevac/deploy_roller(mob/user, atom/location) + if(isliving(user)) + var/mob/living/L = user + if(L.is_on_tank_hull()) + to_chat(user, SPAN_WARNING("You can't set up \the [src] here.")) + return var/obj/structure/bed/medevac_stretcher/medevac_stretcher = new rollertype(location) medevac_stretcher.add_fingerprint(user) user.temp_drop_inv_item(src) From 582dbcbf5c6acd6e0bd434115cc4a0f1e70ca392 Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Mon, 10 Nov 2025 18:52:17 -0300 Subject: [PATCH 19/60] resin doors now properly block tank movement --- code/modules/cm_aliens/XenoStructures.dm | 4 ++++ code/modules/vehicles/multitile/multitile.dm | 7 +++++++ code/modules/vehicles/multitile/multitile_bump.dm | 6 ++++++ code/modules/vehicles/multitile/multitile_movement.dm | 9 ++++++++- 4 files changed, 25 insertions(+), 1 deletion(-) diff --git a/code/modules/cm_aliens/XenoStructures.dm b/code/modules/cm_aliens/XenoStructures.dm index 6bde6a7295b7..d30cc01da81a 100644 --- a/code/modules/cm_aliens/XenoStructures.dm +++ b/code/modules/cm_aliens/XenoStructures.dm @@ -419,6 +419,10 @@ else return attack_hand(user) +/obj/structure/mineral_door/resin/proc/take_damage(damage) + update_health(damage) + healthcheck() + /obj/structure/mineral_door/resin/TryToSwitchState(atom/user) if(isxeno(user)) var/mob/living/carbon/xenomorph/xeno_user = user diff --git a/code/modules/vehicles/multitile/multitile.dm b/code/modules/vehicles/multitile/multitile.dm index 398983035837..6cb347c681f5 100644 --- a/code/modules/vehicles/multitile/multitile.dm +++ b/code/modules/vehicles/multitile/multitile.dm @@ -168,6 +168,13 @@ var/move_on_turn = FALSE + // Structures that we should collide with, but that aren't being collided with when we call T.Enter in multitile_movement + // associative list should guarantee an O(1) lookup in case this needs to be expanded. + var/static/list/blocking_structures = list( + /obj/structure/shuttle/part = TRUE, + /obj/structure/mineral_door/resin = TRUE, + /obj/structure/mineral_door/resin/thick = TRUE + ) /obj/vehicle/multitile/Initialize() . = ..() diff --git a/code/modules/vehicles/multitile/multitile_bump.dm b/code/modules/vehicles/multitile/multitile_bump.dm index 0d751df524d2..02ce13e9a867 100644 --- a/code/modules/vehicles/multitile/multitile_bump.dm +++ b/code/modules/vehicles/multitile/multitile_bump.dm @@ -46,6 +46,12 @@ return TRUE return FALSE +/obj/structure/mineral_door/resin/handle_vehicle_bump(obj/vehicle/multitile/V) + take_damage(V.wall_ram_damage) + visible_message(SPAN_DANGER("\The [V] rams [src]!")) + playsound(V, 'sound/effects/metal_crash.ogg', 20) + return FALSE + /obj/structure/barricade/handle_vehicle_bump(obj/vehicle/multitile/V) if(!(V.vehicle_flags & VEHICLE_CLASS_WEAK)) take_damage(maxhealth) diff --git a/code/modules/vehicles/multitile/multitile_movement.dm b/code/modules/vehicles/multitile/multitile_movement.dm index 36c3fcf4f3ee..0e054e6a133a 100644 --- a/code/modules/vehicles/multitile/multitile_movement.dm +++ b/code/modules/vehicles/multitile/multitile_movement.dm @@ -171,8 +171,15 @@ update_next_move() return FALSE - if(!T.Enter(src) || locate(/obj/structure/shuttle/part) in T) + if(!T.Enter(src)) can_move = FALSE + break + + // any other tile-blocking items that weren't caught by !T.Enter + for(var/atom/A in T) + if(blocking_structures[A.type]) + can_move = FALSE + break // Crashed with something that stopped us if(!can_move) From 791535b28b989a705d028844810c64684c975ed1 Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Mon, 10 Nov 2025 18:54:55 -0300 Subject: [PATCH 20/60] climb up and down time tweaks. Xenos now climb down faster and take longer to climb up. Marines now climb up faster but still take a while to climb down. The speed of climbing up and down now depends on vehicle momentum. --- code/modules/vehicles/tank/tank.dm | 56 +++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/code/modules/vehicles/tank/tank.dm b/code/modules/vehicles/tank/tank.dm index 1d78c553f3f9..5c6488486b62 100644 --- a/code/modules/vehicles/tank/tank.dm +++ b/code/modules/vehicles/tank/tank.dm @@ -291,7 +291,7 @@ * climb_onto is called by Collided to allow a mob to climb onto the tank. * * We run a number of edge case checks first, then we begin the process of climbing properly. - * It takes 1.5 seconds, and, after that, if the spot is still reachable (AKA, if the tank hasn't driven away) + * It takes a few seconds or less, and, after that, if the spot is still reachable (AKA, if the tank hasn't driven away) * then we'll climb up! * * We also call _carry_move_with_grabs to pull grabbed mobs in with us. @@ -305,23 +305,43 @@ /obj/vehicle/multitile/tank/proc/climb_onto(mob/living/user, turf/preferred) if(!istype(user)) return + if(user.action_busy) return - if(!_validate_climb_target(user, preferred, TRUE)) { + + if(!_validate_climb_target(user, preferred, TRUE)) to_chat(user, SPAN_WARNING("You can't climb there.")) return - } + user.visible_message( SPAN_WARNING("[user] starts climbing onto [src]."), SPAN_WARNING("You start climbing onto [src].")) - if(!do_after(user, 1.5 SECONDS, INTERRUPT_MOVED, BUSY_ICON_CLIMBING)) { + + var/climb_speed_factor = (move_momentum / move_max_momentum) // % of our max speed attained. + + to_chat(world, SPAN_WARNING("user = [user] climbing up with move_momentum=[move_momentum]")) + + if(health == 0) // When broken, everyone takes 0.20 seconds to climb up. Keep in mind that the real perceived value is around 2-3x higher when you take in account lag. + if(!do_after (user, 0.20 SECONDS, INTERRUPT_MOVED, BUSY_ICON_CLIMBING, numticks = 1)) + to_chat(user, SPAN_WARNING("You stop climbing onto [src].")) + return + + else if(isxeno(user)) // Xenos take longer to climb to avoid accidentally getting on top while slashing inside boiler gas. + // takes between 0.8 seconds when still and 2 seconds when at full speed. (You'd have to climb through one of the sides) OR pounce on it. + if(!do_after(user, max((2 * climb_speed_factor), 0.80) SECONDS, INTERRUPT_MOVED, BUSY_ICON_CLIMBING, numticks = 4)) + to_chat(user, SPAN_WARNING("You stop climbing onto [src].")) + return + + // Humans and preds climb up faster. Humans can't climb down instantly. + // takes between 0.25 seconds when still and 1.20 second when at full speed (You'd have to climb through one of the sides) + else if(!do_after(user, max((1.20 * climb_speed_factor), 0.30) SECONDS, INTERRUPT_MOVED, BUSY_ICON_CLIMBING, numticks = 3)) to_chat(user, SPAN_WARNING("You stop climbing onto [src].")) return - } - if(!_validate_climb_target(user, preferred, TRUE)) { + + if(!_validate_climb_target(user, preferred, TRUE)) to_chat(user, SPAN_WARNING("The spot on [src] is no longer reachable.")) return - } + _carry_move_with_grabs(user, preferred, TRUE) user.visible_message( SPAN_WARNING("[user] climbs onto [src]."), @@ -346,23 +366,33 @@ if(user.action_busy) return - if(!_validate_climb_target(user, target, FALSE)) { + if(!_validate_climb_target(user, target, FALSE)) return - } + + var/climb_speed_factor = (move_momentum / move_max_momentum) // % of our max speed attained. user.visible_message( SPAN_WARNING("[user] starts climbing down from [src]."), SPAN_WARNING("You start climbing down from [src].")) - if(!do_after(user, 1 SECONDS, INTERRUPT_INCAPACITATED, BUSY_ICON_GENERIC)) { + to_chat(world, SPAN_WARNING("user = [user] climbing down with move_momentum=[move_momentum]")) + + if(!ishuman(user)) // Xenos and preds can always climb down with almost no delay to avoid situations where they get stuck atop the tank. + // The tiny delay here is meant to prevent fast castes from accidentally disembarking from a misinput + if(!do_after(user, 0.20 SECONDS, INTERRUPT_INCAPACITATED, BUSY_ICON_GENERIC, numticks = 1)) + to_chat(user, SPAN_WARNING("You stop climbing down from [src].")) + return + + // Humans take at most 0.9 seconds to climb down from a tank at full speed, and 0.30 when standing still. + else if(!do_after(user, max((0.9 * climb_speed_factor), 0.30) SECONDS, INTERRUPT_INCAPACITATED, BUSY_ICON_GENERIC, numticks = 3)) to_chat(user, SPAN_WARNING("You stop climbing down from [src].")) return - } + + // edge case where tank moves while we're climbing down - if(!_validate_climb_target(user, target, FALSE)) { + if(!_validate_climb_target(user, target, FALSE)) to_chat(user, SPAN_WARNING("The spot is no longer reachable.")) return - } _carry_remove_with_grabs(user, target) user.visible_message( SPAN_WARNING("[user] climbs down from [src]."), From baa1ed71aea3f9a4c052386a22286244f25c68b1 Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Mon, 10 Nov 2025 18:59:25 -0300 Subject: [PATCH 21/60] prevents xenos from hiding atop the tank. --- .../mob/living/carbon/xenomorph/XenoProcs.dm | 4 ++++ .../xenomorph/abilities/general_abilities.dm | 14 ++++++++++---- code/modules/vehicles/tank/tank.dm | 9 +++++++++ 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm index 3e8fc252d770..7ce8407b265c 100644 --- a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm +++ b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm @@ -363,6 +363,10 @@ ADD_TRAIT(src, TRAIT_IMMOBILIZED, TRAIT_SOURCE_ABILITY("Pounce")) pounceAction.freeze_timer_id = addtimer(CALLBACK(src, PROC_REF(unfreeze_pounce)), pounceAction.freeze_time, TIMER_STOPPABLE) pounceAction.additional_effects(M) + if(L.tank_on_top_of) + var/obj/vehicle/multitile/tank/T = L.tank_on_top_of + src.forceMove(get_turf(L)) + T.mark_on_top(src) if(pounceAction.slash) M.attack_alien(src, pounceAction.slash_bonus_damage) diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/general_abilities.dm b/code/modules/mob/living/carbon/xenomorph/abilities/general_abilities.dm index 979ef0c2397a..44aeed04cf72 100644 --- a/code/modules/mob/living/carbon/xenomorph/abilities/general_abilities.dm +++ b/code/modules/mob/living/carbon/xenomorph/abilities/general_abilities.dm @@ -398,12 +398,19 @@ /datum/action/xeno_action/onclick/xenohide/can_use_action() var/mob/living/carbon/xenomorph/xeno = owner - if(xeno && !xeno.buckled && !xeno.is_mob_incapacitated()) - if(!(SEND_SIGNAL(xeno, COMSIG_LIVING_SHIMMY_LAYER) & COMSIG_LIVING_SHIMMY_LAYER_CANCEL)) - return TRUE + if(xeno) + if(xeno.tank_on_top_of) // Prevents cheesy layering underneath the tank. + return FALSE + if(!xeno.buckled && !xeno.is_mob_incapacitated()) + if(!(SEND_SIGNAL(xeno, COMSIG_LIVING_SHIMMY_LAYER) & COMSIG_LIVING_SHIMMY_LAYER_CANCEL)) + return TRUE /// remove hide and apply modified attack cooldown /datum/action/xeno_action/onclick/xenohide/proc/post_attack() + remove_hide_status() + apply_cooldown(4) //2 second cooldown after attacking + +/datum/action/xeno_action/onclick/xenohide/proc/remove_hide_status() var/mob/living/carbon/xenomorph/xeno = owner UnregisterSignal(xeno, COMSIG_MOB_STATCHANGE) if(xeno.layer == XENO_HIDING_LAYER) @@ -411,7 +418,6 @@ button.icon_state = "template" xeno.update_wounds() xeno.update_layer() - apply_cooldown(4) //2 second cooldown after attacking /datum/action/xeno_action/onclick/xenohide/give_to(mob/living/living_mob) . = ..() diff --git a/code/modules/vehicles/tank/tank.dm b/code/modules/vehicles/tank/tank.dm index 5c6488486b62..bef8dc951a35 100644 --- a/code/modules/vehicles/tank/tank.dm +++ b/code/modules/vehicles/tank/tank.dm @@ -408,6 +408,8 @@ * It adds the tank to a mob's tank_on_top_of var * It calls _apply_rider_visuals() to set the layer atop the tank's * + * If a xeno is hidden, it gets un-hidden. + * * Arguments: * * mob/living/M - The mob being marked ontop. */ @@ -424,6 +426,13 @@ M.tank_on_top_of = src _apply_rider_visuals(M) + if(isxeno(M)) + var/mob/living/carbon/xenomorph/X = M + if(X.layer == XENO_HIDING_LAYER) + var/datum/action/xeno_action/onclick/xenohide/hide = get_action(X, /datum/action/xeno_action/onclick/xenohide) + if(hide) + hide.remove_hide_status() // prevents cheesing the layer system + /** * obj_mark_on_top WOULD be an ad-hoc polymorph of mark_on_top. It does the same thing, but with an obj/ type. * From b456b34231e528e13570d349598d7108918dcc8e Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Mon, 10 Nov 2025 19:19:16 -0300 Subject: [PATCH 22/60] Adds more tank riding layers --- code/__DEFINES/layers.dm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/code/__DEFINES/layers.dm b/code/__DEFINES/layers.dm index d2e0dd975aec..b229d5226cbe 100644 --- a/code/__DEFINES/layers.dm +++ b/code/__DEFINES/layers.dm @@ -144,7 +144,10 @@ /// ...which specific layer they need to go when brought atop. #define TANK_RIDER_ABOVE_OBJ_LAYER 4.506 /// For mobs riding atop a tank +#define TANK_BELOW_RIDER_LAYER 4.508 +#define TANK_LYING_RIDER_LAYER 4.509 #define TANK_RIDER_LAYER 4.51 +#define TANK_ABOVE_RIDER_LAYER 4.515 //#define FLY_LAYER 5 From a105c69cfb51890e6882ba83d6f8f572ffdd1034 Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Mon, 10 Nov 2025 19:20:26 -0300 Subject: [PATCH 23/60] runner pounce fix fixes runners being dismounted improperly (or not at all) from pounces from atop the tank --- .../mob/living/carbon/xenomorph/XenoProcs.dm | 17 +++++++++++------ code/modules/mob/living/living.dm | 17 +++++++++++++---- code/modules/movement/launching/launching.dm | 10 +++++++++- 3 files changed, 33 insertions(+), 11 deletions(-) diff --git a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm index 7ce8407b265c..4f9058d29f3b 100644 --- a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm +++ b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm @@ -367,6 +367,8 @@ var/obj/vehicle/multitile/tank/T = L.tank_on_top_of src.forceMove(get_turf(L)) T.mark_on_top(src) + else if(src.tank_on_top_of) + src.tank_on_top_of.clear_on_top(src) if(pounceAction.slash) M.attack_alien(src, pounceAction.slash_bonus_damage) @@ -391,12 +393,15 @@ var/obj/vehicle/multitile/tank/T = null if(istype(O, /obj/vehicle/multitile/tank)) T = O - if(T) - var/turf/current = get_turf(src) - var/turf/facing = get_step(current, dir) - if(facing && (facing in T.locs)) - forceMove(facing) - T.mark_on_top(src) + if(T) + var/turf/current = get_turf(src) + var/turf/facing = get_step(current, dir) + if(facing && (facing in T.locs)) + forceMove(facing) + T.mark_on_top(src) + else if(src.tank_on_top_of) + if(!(locate(/obj/vehicle/multitile/tank) in get_turf(src))) + src.tank_on_top_of.clear_on_top(src) // if we're not atop the tank still, clear us from it. if (pounceAction.should_destroy_objects) if(istype(O, /obj/structure/surface/table) || istype(O, /obj/structure/surface/rack) || istype(O, /obj/structure/window_frame)) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 9adb52da3dcb..c12e1b130c25 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -720,11 +720,20 @@ /mob/living/proc/update_layer() //so mob lying always appear behind standing mobs, but dead ones appear behind living ones if(pulledby && pulledby.grab_level == GRAB_CARRY) - layer = ABOVE_MOB_LAYER - else if (body_position == LYING_DOWN && stat == DEAD && !is_on_tank_hull()) - layer = LYING_DEAD_MOB_LAYER // Dead mobs should layer under living ones + if(is_on_tank_hull()) + layer = TANK_ABOVE_RIDER_LAYER + else + layer = ABOVE_MOB_LAYER + else if (body_position == LYING_DOWN && stat == DEAD) + if(is_on_tank_hull()) + layer = TANK_BELOW_RIDER_LAYER + else + layer = LYING_DEAD_MOB_LAYER // Dead mobs should layer under living ones else if(body_position == LYING_DOWN && layer == initial(layer)) //to avoid things like hiding larvas. //i have no idea what this means - layer = LYING_LIVING_MOB_LAYER + if(is_on_tank_hull()) + layer = TANK_LYING_RIDER_LAYER + else + layer = LYING_LIVING_MOB_LAYER /// Called when mob changes from a standing position into a prone while lacking the ability to stand up at the moment. /mob/living/proc/on_fall() diff --git a/code/modules/movement/launching/launching.dm b/code/modules/movement/launching/launching.dm index 43f4e618ef41..ddb652eb915d 100644 --- a/code/modules/movement/launching/launching.dm +++ b/code/modules/movement/launching/launching.dm @@ -214,7 +214,15 @@ sleep(delay) //done throwing, either because it hit something or it finished moving - src.layer = initial(src.layer) + // prevents xeno pounces from layering under the tank, as well as other species with pounces. + if (isliving(src)) + var/mob/living/L = src + L.update_layer() + if(L.tank_on_top_of) + if(!(locate(/obj/vehicle/multitile/tank) in get_turf(L))) + L.tank_on_top_of.clear_on_top(L) // if we're not atop the tank still, clear us from it. + else + src.layer = initial(src.layer) if ((isobj(src) || ismob(src)) && throwing && !early_exit) var/turf/T = get_turf(src) if(!istype(T)) From 84a96bc4429706bf0bb196d78da020e971a57209 Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Mon, 10 Nov 2025 19:20:54 -0300 Subject: [PATCH 24/60] tank now goes over cargo trolleys --- code/modules/vehicles/multitile/multitile_bump.dm | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/code/modules/vehicles/multitile/multitile_bump.dm b/code/modules/vehicles/multitile/multitile_bump.dm index 02ce13e9a867..80b54d645dc5 100644 --- a/code/modules/vehicles/multitile/multitile_bump.dm +++ b/code/modules/vehicles/multitile/multitile_bump.dm @@ -530,6 +530,17 @@ playsound(V, 'sound/effects/metal_crash.ogg', 35) return FALSE +// legacy cargo train gets ran over and crushed without a fuss by medium and heavy vehicles +/obj/vehicle/train/cargo/handle_vehicle_bump(obj/vehicle/multitile/V) + if(V.vehicle_flags & VEHICLE_CLASS_MEDIUM || V.vehicle_flags & VEHICLE_CLASS_HEAVY) + health = 0 + healthcheck() + + visible_message(SPAN_DANGER("\The [V] crushes into \the [src]!")) + playsound(V, 'sound/effects/metal_crash.ogg', 35) + return TRUE + . = ..() + //----------------------------------------------------------- //-------------------------MOBS------------------------------ //----------------------------------------------------------- From d48424a999bebcd00b61a992d6634bb15516f1a2 Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Mon, 10 Nov 2025 19:21:07 -0300 Subject: [PATCH 25/60] penalty from being run over now scales with momentum --- code/modules/vehicles/multitile/multitile_bump.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/modules/vehicles/multitile/multitile_bump.dm b/code/modules/vehicles/multitile/multitile_bump.dm index 80b54d645dc5..0476b79eb6ab 100644 --- a/code/modules/vehicles/multitile/multitile_bump.dm +++ b/code/modules/vehicles/multitile/multitile_bump.dm @@ -628,8 +628,8 @@ apply_damage(10 + rand(0, 10), BRUTE) else if(V.vehicle_flags & VEHICLE_CLASS_MEDIUM) - apply_effect(1.5, WEAKEN) - apply_effect(3, SLOW) + apply_effect((1.5 * (V.move_momentum / V.move_max_momentum)), WEAKEN) // Even with the nerfs, still by far the most frustrating thing about the tank. + apply_effect((3 * (V.move_momentum / V.move_max_momentum)), SLOW) apply_damage(5 + rand(0, 5), BRUTE) dmg = TRUE From 71614bab55aa26f989676e3e2e140d7cd8a33213 Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Mon, 10 Nov 2025 19:22:30 -0300 Subject: [PATCH 26/60] Tank momentum and jousting refactor --- code/modules/vehicles/multitile/multitile.dm | 10 ++++ .../vehicles/multitile/multitile_movement.dm | 58 +++++++++++++++++-- code/modules/vehicles/tank/tank.dm | 11 ++-- 3 files changed, 68 insertions(+), 11 deletions(-) diff --git a/code/modules/vehicles/multitile/multitile.dm b/code/modules/vehicles/multitile/multitile.dm index 6cb347c681f5..0bb0a60f8ef6 100644 --- a/code/modules/vehicles/multitile/multitile.dm +++ b/code/modules/vehicles/multitile/multitile.dm @@ -53,7 +53,13 @@ // Determines how much slower the vehicle is when it lacks its full momentum // When the vehicle has 0 momentum, it's movement delay will be move_delay * momentum_build_factor // The movement delay gradually reduces up to move_delay when momentum increases + //// This is mostly vestigial and it looks like it cancels itself out when calculating movement delay??? -bwsb var/move_momentum_build_factor = 1.3 + // How fast momentum decays when you stop moving. + var/move_momentum_loss_factor = 1 + // How long do you have to go without moving for momentum decay to start + // Slower moving vehicles like the tank require more time, or else they won't get past minimum speed. + var/idle_time_required = 10 // 10 seconds. Tested to be OK on APC and Van. //Sound to play when moving var/movement_sound @@ -175,6 +181,10 @@ /obj/structure/mineral_door/resin = TRUE, /obj/structure/mineral_door/resin/thick = TRUE ) + + var/momentum_decay_active = FALSE // Track if momentum decay loop is running + var/last_input_time = 0 // Track when last movement input was received + /obj/vehicle/multitile/Initialize() . = ..() diff --git a/code/modules/vehicles/multitile/multitile_movement.dm b/code/modules/vehicles/multitile/multitile_movement.dm index 0e054e6a133a..bdc894ed6ede 100644 --- a/code/modules/vehicles/multitile/multitile_movement.dm +++ b/code/modules/vehicles/multitile/multitile_movement.dm @@ -34,6 +34,8 @@ if(health <= 0) return FALSE + last_input_time = world.time + return pre_movement(direction) // This determines what type of movement to execute @@ -54,6 +56,9 @@ if(move_on_turn) try_move(direction) + if(success) + start_momentum_decay_if_needed() + return success // Attempts to execute the given movement input @@ -118,10 +123,6 @@ // Increases/decreases the vehicle's momentum according to whether or not the user is steppin' on the gas or not /obj/vehicle/multitile/proc/update_momentum(direction) - // If we've stood still for long enough we go back to 0 momentum - if(world.time > next_move + move_delay*move_momentum_build_factor) - move_momentum = 0 - if(direction == dir) move_momentum = min(move_momentum + 1, move_max_momentum) else @@ -137,12 +138,12 @@ /obj/vehicle/multitile/proc/update_next_move() // 1/((m/M)*b) where m is momentum, M is max momentum and b is the build factor + //// move_momentum_build_factor seems to cancel itself out here. It's worth to revisit this section and maybe refactor it. var/anti_build_factor = 1/((max(abs(move_momentum), 1)/move_max_momentum) * move_momentum_build_factor) next_move = world.time + move_delay * move_momentum_build_factor * anti_build_factor * misc_multipliers["move"] l_move_time = world.time - // This just checks if the vehicle can physically move in the given direction /obj/vehicle/multitile/proc/can_move(direction) var/can_move = TRUE @@ -329,3 +330,50 @@ */ /obj/vehicle/multitile/proc/on_crash() return + + +/** + * Starts the momentum decay loop if not already running and momentum exists. + * This should be called after any movement to ensure decay starts when player stops. + */ +/obj/vehicle/multitile/proc/start_momentum_decay_if_needed() + if(abs(move_momentum) > 0 && !momentum_decay_active) + momentum_decay_active = TRUE + spawn(0) + momentum_decay_loop() + +/** + * The main momentum decay loop - runs continuously while vehicle has momentum. + * Checks every second if the player has stopped giving input, and decays momentum accordingly. + * + * decay_interval controls how fast mometum will decay. By default, it decays every second. + * + * idle_time_required is how long you have to go without pressing a movement key. + * + * So, for example, if idle time is 20 and decay interval is 5, you will start losing momentum twice per second after you spend 2 seconds without moving. + * + */ +/obj/vehicle/multitile/proc/momentum_decay_loop() + var/decay_interval = 10 // Decay every 1 second + + while(abs(move_momentum) > 0) + sleep(decay_interval) + + var/time_since_input = world.time - last_input_time + if(time_since_input < idle_time_required) + continue + + var/momentum_abs = abs(move_momentum) + momentum_abs -= move_momentum_loss_factor + if(momentum_abs <= 0) + move_momentum = 0 + momentum_decay_active = FALSE + return + + if(move_momentum > 0) + move_momentum = momentum_abs + + else + move_momentum = -momentum_abs + + momentum_decay_active = FALSE diff --git a/code/modules/vehicles/tank/tank.dm b/code/modules/vehicles/tank/tank.dm index bef8dc951a35..d63928304fad 100644 --- a/code/modules/vehicles/tank/tank.dm +++ b/code/modules/vehicles/tank/tank.dm @@ -35,6 +35,7 @@ move_max_momentum = 3 move_momentum_build_factor = 1.8 move_turn_momentum_loss_factor = 0.6 + idle_time_required = 20 vehicle_light_range = 7 @@ -235,15 +236,13 @@ /** * Applies a jousting effect when crashing above a certain speed. * - * This is a somewhat hacky implementation, since the maximum momentum of vehicles - * can change based on the type of wheels used. IE: Reinforced treads make it easier to attain - * the needed 1.6 momentum, while it's a bit harder with normal treads. + * Because momentum defines how close a vehicle is to its top speed, 0.5 should mean that the tank will joust its riders... + * ...After it crashes at anything while above half of its max speed. * - * 1.6, while a magic number and a code smell, was the value that worked best to prevent riders from being scattered - * at low speeds, while also ensuring they were scattered after the tank accelerated 2 tiles on normal treads. + * Keep in mind that momentum decays while standing still, and it can be negative in case we are moving backwards. */ /obj/vehicle/multitile/tank/on_crash() - if(abs(move_momentum) < 1.6) + if(abs(move_momentum) < move_max_momentum * 0.5) return _scatter_riders_on_crash() revalidate_on_top() From 74c85aabdf54d318010fcce3c6a81bf8a951130d Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Mon, 10 Nov 2025 19:30:23 -0300 Subject: [PATCH 27/60] Linters: space indentation fix --- code/modules/vehicles/multitile/multitile.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/modules/vehicles/multitile/multitile.dm b/code/modules/vehicles/multitile/multitile.dm index 0bb0a60f8ef6..7f48c1aa897d 100644 --- a/code/modules/vehicles/multitile/multitile.dm +++ b/code/modules/vehicles/multitile/multitile.dm @@ -177,8 +177,8 @@ // Structures that we should collide with, but that aren't being collided with when we call T.Enter in multitile_movement // associative list should guarantee an O(1) lookup in case this needs to be expanded. var/static/list/blocking_structures = list( - /obj/structure/shuttle/part = TRUE, - /obj/structure/mineral_door/resin = TRUE, + /obj/structure/shuttle/part = TRUE, + /obj/structure/mineral_door/resin = TRUE, /obj/structure/mineral_door/resin/thick = TRUE ) From b5ec4b9c701d93c7f8b51ff031eb78b8cf740a83 Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Mon, 10 Nov 2025 19:59:30 -0300 Subject: [PATCH 28/60] Removes leftover debug statements woops! --- code/modules/vehicles/tank/tank.dm | 4 ---- 1 file changed, 4 deletions(-) diff --git a/code/modules/vehicles/tank/tank.dm b/code/modules/vehicles/tank/tank.dm index d63928304fad..c2ddbbc9d612 100644 --- a/code/modules/vehicles/tank/tank.dm +++ b/code/modules/vehicles/tank/tank.dm @@ -318,8 +318,6 @@ var/climb_speed_factor = (move_momentum / move_max_momentum) // % of our max speed attained. - to_chat(world, SPAN_WARNING("user = [user] climbing up with move_momentum=[move_momentum]")) - if(health == 0) // When broken, everyone takes 0.20 seconds to climb up. Keep in mind that the real perceived value is around 2-3x higher when you take in account lag. if(!do_after (user, 0.20 SECONDS, INTERRUPT_MOVED, BUSY_ICON_CLIMBING, numticks = 1)) to_chat(user, SPAN_WARNING("You stop climbing onto [src].")) @@ -374,8 +372,6 @@ SPAN_WARNING("[user] starts climbing down from [src]."), SPAN_WARNING("You start climbing down from [src].")) - to_chat(world, SPAN_WARNING("user = [user] climbing down with move_momentum=[move_momentum]")) - if(!ishuman(user)) // Xenos and preds can always climb down with almost no delay to avoid situations where they get stuck atop the tank. // The tiny delay here is meant to prevent fast castes from accidentally disembarking from a misinput if(!do_after(user, 0.20 SECONDS, INTERRUPT_INCAPACITATED, BUSY_ICON_GENERIC, numticks = 1)) From a155b1827ca43005b49a890c8858720c6dad97e5 Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Mon, 10 Nov 2025 20:34:42 -0300 Subject: [PATCH 29/60] fixes movement collision oversight --- code/modules/vehicles/multitile/multitile.dm | 7 ++++++- code/modules/vehicles/multitile/multitile_movement.dm | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/code/modules/vehicles/multitile/multitile.dm b/code/modules/vehicles/multitile/multitile.dm index 7f48c1aa897d..0353feba610e 100644 --- a/code/modules/vehicles/multitile/multitile.dm +++ b/code/modules/vehicles/multitile/multitile.dm @@ -179,7 +179,6 @@ var/static/list/blocking_structures = list( /obj/structure/shuttle/part = TRUE, /obj/structure/mineral_door/resin = TRUE, - /obj/structure/mineral_door/resin/thick = TRUE ) var/momentum_decay_active = FALSE // Track if momentum decay loop is running @@ -476,3 +475,9 @@ SIGNAL_HANDLER forceMove(get_turf(mover)) + +/obj/vehicle/multitile/proc/is_blocking_structure(atom/A) + for(var/blocked_type in blocking_structures) + if(ispath(A.type, blocked_type)) + return TRUE + return FALSE diff --git a/code/modules/vehicles/multitile/multitile_movement.dm b/code/modules/vehicles/multitile/multitile_movement.dm index bdc894ed6ede..ccb4235b72ca 100644 --- a/code/modules/vehicles/multitile/multitile_movement.dm +++ b/code/modules/vehicles/multitile/multitile_movement.dm @@ -178,7 +178,7 @@ // any other tile-blocking items that weren't caught by !T.Enter for(var/atom/A in T) - if(blocking_structures[A.type]) + if(is_blocking_structure(A)) can_move = FALSE break From 853edc030481b90f597aa17fe2ac75d044c2e182 Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Mon, 10 Nov 2025 20:34:42 -0300 Subject: [PATCH 30/60] fixes movement collision oversight --- code/modules/vehicles/multitile/multitile.dm | 7 ++++++- code/modules/vehicles/multitile/multitile_movement.dm | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/code/modules/vehicles/multitile/multitile.dm b/code/modules/vehicles/multitile/multitile.dm index 7f48c1aa897d..c1a7fc1473cf 100644 --- a/code/modules/vehicles/multitile/multitile.dm +++ b/code/modules/vehicles/multitile/multitile.dm @@ -179,7 +179,6 @@ var/static/list/blocking_structures = list( /obj/structure/shuttle/part = TRUE, /obj/structure/mineral_door/resin = TRUE, - /obj/structure/mineral_door/resin/thick = TRUE ) var/momentum_decay_active = FALSE // Track if momentum decay loop is running @@ -476,3 +475,9 @@ SIGNAL_HANDLER forceMove(get_turf(mover)) + +/obj/vehicle/multitile/proc/is_blocking_structure(atom/A) + for(var/blocked_type in blocking_structures) + if(ispath(A.type, blocked_type)) + return TRUE + return FALSE diff --git a/code/modules/vehicles/multitile/multitile_movement.dm b/code/modules/vehicles/multitile/multitile_movement.dm index bdc894ed6ede..ccb4235b72ca 100644 --- a/code/modules/vehicles/multitile/multitile_movement.dm +++ b/code/modules/vehicles/multitile/multitile_movement.dm @@ -178,7 +178,7 @@ // any other tile-blocking items that weren't caught by !T.Enter for(var/atom/A in T) - if(blocking_structures[A.type]) + if(is_blocking_structure(A)) can_move = FALSE break From 03c0e75ff7e281a59e50299e5fa23a8535fff185 Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Wed, 4 Feb 2026 13:18:59 -0300 Subject: [PATCH 31/60] Linters Fix --- code/game/atoms_movable.dm | 18 +++ code/game/objects/objs.dm | 143 ------------------ .../xenomorph/abilities/general_abilities.dm | 6 +- code/modules/mob/living/living_defines.dm | 4 - code/modules/mob/mob_verbs.dm | 2 +- 5 files changed, 22 insertions(+), 151 deletions(-) diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 990d5143eb40..1670f5cb4aa4 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -39,6 +39,16 @@ /// Holds a reference to the emissive blocker overlay var/emissive_overlay + /// Is this item allowed atop a climbable vehicle? + /// Not at all intended for structures: This is meant for grabbable items. Weapons, ammo, pizza crates, etc... + /// ...However, defining it as part of obj instead of obj/item helps us handle exceptions. + /// E.G: Exceptionally, as QoL for corpsmen: bodybags, stasis bags, and roller beds are allowed. + var/is_allowed_atop_vehicle = FALSE + /// Tracks whether this item is currently atop a vehicle. + var/is_atop_vehicle = FALSE + /// Which vehicle are we ontop of? + var/tmp/obj/vehicle/multitile/tank/tank_on_top_of = null + //=========================================================================== /atom/movable/Destroy(force) for(var/atom/movable/I in contents) @@ -486,8 +496,16 @@ SEND_SIGNAL(src, COMSIG_OBJ_AFTER_BUCKLE, buckled_mob) if(!buckled_mob) UnregisterSignal(buckle_target, COMSIG_PARENT_QDELETING) + if(isliving(buckle_target)) + var/mob/living/living_M + if(living_M.tank_on_top_of) + living_M.tank_on_top_of.clear_on_top(living_M) else RegisterSignal(buckled_mob, COMSIG_PARENT_QDELETING, PROC_REF(unbuckle)) + if(isliving(buckled_mob)) + var/mob/living/living_M + if(src.is_atop_vehicle) + src.tank_on_top_of.mark_on_top(living_M) return buckled_mob /atom/movable/proc/handle_rotation() diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index 9456f03fe09d..4382627205f5 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -34,16 +34,6 @@ vis_flags = VIS_INHERIT_PLANE - /// Is this item allowed atop a climbable vehicle? - /// Not at all intended for structures: This is meant for grabbable items. Weapons, ammo, pizza crates, etc... - /// ...However, defining it as part of obj instead of obj/item helps us handle exceptions. - /// E.G: Exceptionally, as QoL for corpsmen: bodybags, stasis bags, and roller beds are allowed. - var/is_allowed_atop_vehicle = FALSE - /// Tracks whether this item is currently atop a vehicle. - var/is_atop_vehicle = FALSE - /// Which vehicle are we ontop of? - var/tmp/obj/vehicle/multitile/tank/tank_on_top_of = null - /obj/Initialize(mapload, ...) . = ..() if(garbage) @@ -242,127 +232,6 @@ buckle_mob(M, user) else . = ..() -/obj/proc/afterbuckle(mob/M as mob) // Called after somebody buckled / unbuckled - handle_rotation() // To be removed when we have full dir support in set_buckled - SEND_SIGNAL(src, COMSIG_OBJ_AFTER_BUCKLE, buckled_mob) - if(!buckled_mob) - UnregisterSignal(M, COMSIG_PARENT_QDELETING) - if(isliving(M)) - var/mob/living/living_M - if(living_M.tank_on_top_of) - living_M.tank_on_top_of.clear_on_top(living_M) - else - RegisterSignal(buckled_mob, COMSIG_PARENT_QDELETING, PROC_REF(unbuckle)) - if(isliving(M)) - var/mob/living/living_M - if(src.is_atop_vehicle) - src.tank_on_top_of.mark_on_top(living_M) - return buckled_mob - -/obj/proc/unbuckle() - SIGNAL_HANDLER - if(buckled_mob && buckled_mob.buckled == src) - buckled_mob.clear_alert(ALERT_BUCKLED) - buckled_mob.set_buckled(null) - buckled_mob.anchored = initial(buckled_mob.anchored) - - var/M = buckled_mob - REMOVE_TRAITS_IN(buckled_mob, TRAIT_SOURCE_BUCKLE) - buckled_mob = null - - afterbuckle(M) - - -/obj/proc/manual_unbuckle(mob/user as mob) - if(buckled_mob) - if(buckled_mob.buckled == src) - if(buckled_mob != user) - buckled_mob.visible_message( - SPAN_NOTICE("[buckled_mob.name] was unbuckled by [user.name]!"), - SPAN_NOTICE("You were unbuckled from [src] by [user.name]."), - SPAN_NOTICE("You hear metal clanking.")) - else - buckled_mob.visible_message( - SPAN_NOTICE("[buckled_mob.name] unbuckled [buckled_mob.p_them()]self!"), - SPAN_NOTICE("You unbuckle yourself from [src]."), - SPAN_NOTICE("You hear metal clanking.")) - unbuckle(buckled_mob) - add_fingerprint(user) - return 1 - - return 0 - - -//trying to buckle a mob -/obj/proc/buckle_mob(mob/M, mob/user) - if (!ismob(M) || (get_dist(src, user) > 1) || user.stat || buckled_mob || M.buckled || !isturf(user.loc)) - return - - if (user.is_mob_incapacitated() || HAS_TRAIT(user, TRAIT_IMMOBILIZED) || HAS_TRAIT(user, TRAIT_FLOORED)) - to_chat(user, SPAN_WARNING("You can't do this right now.")) - return - - if (isxeno(user) && !HAS_TRAIT(user, TRAIT_OPPOSABLE_THUMBS)) - to_chat(user, SPAN_WARNING("You don't have the dexterity to do that, try a nest.")) - return - if (iszombie(user)) - return - - if(density) - density = FALSE - if(!step(M, get_dir(M, src)) && loc != M.loc) - density = TRUE - return - density = TRUE - else - if(M.loc != src.loc) - step_towards(M, src) //buckle if you're right next to it - if(M.loc != src.loc) - return - . = buckle_mob(M) - if (M.mob_size <= MOB_SIZE_XENO) - if ((M.stat == DEAD && istype(src, /obj/structure/bed/roller) || HAS_TRAIT(M, TRAIT_OPPOSABLE_THUMBS))) - do_buckle(M, user) - return - if ((M.mob_size > MOB_SIZE_HUMAN)) - if(istype(src, /obj/structure/bed/roller)) - var/obj/structure/bed/roller/roller = src - if(!roller.can_carry_big) - to_chat(user, SPAN_WARNING("[M] is too big to buckle in.")) - return - if(M.stat != DEAD) - to_chat(user, SPAN_WARNING("[M] resists your attempt to buckle!")) - return - if(M.stat != DEAD) - return - do_buckle(M, user) - -// the actual buckling proc -// Yes I know this is not style but its unreadable otherwise -/obj/proc/do_buckle(mob/living/target, mob/user) - send_buckling_message(target, user) - if (src && src.loc) - target.throw_alert(ALERT_BUCKLED, /atom/movable/screen/alert/buckled) - target.set_buckled(src) - target.forceMove(src.loc) - target.setDir(dir) - src.buckled_mob = target - src.add_fingerprint(user) - afterbuckle(target) - return TRUE - -/obj/proc/send_buckling_message(mob/M, mob/user) - if (M == user) - M.visible_message( - SPAN_NOTICE("[M] buckles in!"), - SPAN_NOTICE("You buckle yourself to [src]."), - SPAN_NOTICE("You hear metal clanking.")) - else - M.visible_message( - SPAN_NOTICE("[M] is buckled in to [src] by [user]!"), - SPAN_NOTICE("You are buckled in to [src] by [user]."), - SPAN_NOTICE("You hear metal clanking.")) - /obj/Moved(atom/oldloc, direction, Forced = FALSE) . = ..() if(is_atop_vehicle && isturf(loc)) @@ -405,18 +274,6 @@ if(buckled_mob && loc == dest) buckled_mob.forceMove(dest) -/obj/proc/handle_buckled_mob_movement(NewLoc, direct) - if(!buckled_mob.Move(NewLoc, direct)) - forceMove(buckled_mob.loc) - last_move_dir = buckled_mob.last_move_dir - buckled_mob.inertia_dir = last_move_dir - return FALSE - - // Even if the movement is entirely managed by the object, notify the buckled mob that it's moving for its handler. - //It won't be called otherwise because it's a function of client_move or pulled mob, neither of which accounts for this. - SEND_SIGNAL(buckled_mob, COMSIG_MOB_MOVE_OR_LOOK, TRUE, direct, direct) - return TRUE - /obj/BlockedPassDirs(atom/movable/mover, target_dir) if(mover == buckled_mob) //can't collide with the thing you're buckled to return NO_BLOCKED_MOVEMENT diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/general_abilities.dm b/code/modules/mob/living/carbon/xenomorph/abilities/general_abilities.dm index cac0d7f55fee..acb901edf224 100644 --- a/code/modules/mob/living/carbon/xenomorph/abilities/general_abilities.dm +++ b/code/modules/mob/living/carbon/xenomorph/abilities/general_abilities.dm @@ -408,9 +408,9 @@ if(xeno) if(xeno.tank_on_top_of) // Prevents cheesy layering underneath the tank. return FALSE - if(!xeno.buckled && !xeno.is_mob_incapacitated() && !LAZYLEN(xeno.buckled_mobs)) - if(!(SEND_SIGNAL(xeno, COMSIG_LIVING_SHIMMY_LAYER) & COMSIG_LIVING_SHIMMY_LAYER_CANCEL)) - return TRUE + if(!xeno.buckled && !xeno.is_mob_incapacitated() && !LAZYLEN(xeno.buckled_mobs)) + if(!(SEND_SIGNAL(xeno, COMSIG_LIVING_SHIMMY_LAYER) & COMSIG_LIVING_SHIMMY_LAYER_CANCEL)) + return TRUE /// remove hide and apply modified attack cooldown /datum/action/xeno_action/onclick/xenohide/proc/post_attack() remove_hide_status() diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm index c2a4887d1783..21626c3a528d 100644 --- a/code/modules/mob/living/living_defines.dm +++ b/code/modules/mob/living/living_defines.dm @@ -148,7 +148,3 @@ // for multiz looking up var/atom/observed_atom - - // keeps track of the tank we are currently riding atop of. - // unecessary, but saves us from having to do a locate() for it on a turf every time we need to check it. - var/tmp/obj/vehicle/multitile/tank/tank_on_top_of = null diff --git a/code/modules/mob/mob_verbs.dm b/code/modules/mob/mob_verbs.dm index 65482346e375..523b1bb10bfd 100644 --- a/code/modules/mob/mob_verbs.dm +++ b/code/modules/mob/mob_verbs.dm @@ -289,7 +289,7 @@ to_chat(src, SPAN_WARNING("You cannot look up here.")) return - // this is probably a bit hacky. + // this is probably a bit hacky. if(!(vehicle_turf == null)) above = locate(vehicle_turf.x, vehicle_turf.y, vehicle_turf.z+1) From af663eea2a9df9f38bc3cd4ba934df27654f00d5 Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Sun, 17 May 2026 13:53:43 -0300 Subject: [PATCH 32/60] First round of linter fixes --- code/modules/cm_aliens/XenoStructures.dm | 6 +----- code/modules/projectiles/projectile.dm | 12 ++++++------ 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/code/modules/cm_aliens/XenoStructures.dm b/code/modules/cm_aliens/XenoStructures.dm index 01aeaddcf34a..ff53fc5ba9c7 100644 --- a/code/modules/cm_aliens/XenoStructures.dm +++ b/code/modules/cm_aliens/XenoStructures.dm @@ -412,7 +412,7 @@ return 1 /obj/structure/mineral_door/resin/proc/take_damage(dam, mob/mob) - health -= dam + update_health(dam) healthcheck() /obj/structure/mineral_door/resin/attackby(obj/item/W, mob/living/user) @@ -428,10 +428,6 @@ else return attack_hand(user) -/obj/structure/mineral_door/resin/proc/take_damage(damage) - update_health(damage) - healthcheck() - /obj/structure/mineral_door/resin/TryToSwitchState(atom/user) if(isxeno(user)) var/mob/living/carbon/xenomorph/xeno_user = user diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index 1b8cafc643ff..a505cb880a07 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -425,9 +425,9 @@ // Checkc if we're inside a tank and trying to exit if(inside_tank) var/tank_found = FALSE - for(var/obj/O in T) - var/obj/vehicle/multitile/tank/M = _owning_tank_of(O) - if(M == inside_tank || O == inside_tank) + for(var/obj/obj in turf) + var/obj/vehicle/multitile/tank/M = _owning_tank_of(obj) + if(M == inside_tank || obj == inside_tank) tank_found = TRUE break @@ -499,10 +499,10 @@ return FALSE permutated |= obj - var/obj/vehicle/multitile/tank/M = _owning_tank_of(O) + var/obj/vehicle/multitile/tank/M = _owning_tank_of(obj) if(!M) - if(istype(O, /obj/vehicle/multitile/tank)) - M = O + if(istype(obj, /obj/vehicle/multitile/tank)) + M = obj // this block allows projectiles fired from outside the tank to travel inside it. if(M) From 532aec69d4318c9aef80c7056f4b8dbf6a040145 Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Sun, 17 May 2026 19:46:21 -0300 Subject: [PATCH 33/60] Prevents tank riders from being FF'd by non IFF projectiles. --- code/modules/vehicles/hardpoints/hardpoint.dm | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/code/modules/vehicles/hardpoints/hardpoint.dm b/code/modules/vehicles/hardpoints/hardpoint.dm index c09c56b0c4d8..100c132b5b72 100644 --- a/code/modules/vehicles/hardpoints/hardpoint.dm +++ b/code/modules/vehicles/hardpoints/hardpoint.dm @@ -586,7 +586,16 @@ /obj/item/hardpoint/proc/handle_fire(atom/target, mob/living/user, params) var/turf/origin_turf = get_origin_turf() - var/obj/projectile/projectile_to_fire = generate_bullet(user, origin_turf) + // Spawn projectile outside the vehicle hull so riders aren't hit. + var/turf/spawn_turf = origin_turf + if(owner && length(owner.locs) > 1) + var/list/path = get_line(origin_turf, get_turf(target)) + for(var/turf/T as anything in path) + if(!(T in owner.locs)) + spawn_turf = T + break + + var/obj/projectile/projectile_to_fire = generate_bullet(user, spawn_turf) ammo.current_rounds-- SEND_SIGNAL(projectile_to_fire, COMSIG_BULLET_USER_EFFECTS, user) From 5e4690e8b5fedeb5c8763861fc79e6a9d8e9f589 Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Mon, 18 May 2026 14:19:00 -0300 Subject: [PATCH 34/60] Barricades no longer 'HARD CRASH' the tank. Nor do they get destroyed when running over them IF you don't attempt to cross over them. (Aka, you can drive the tank flush to cades from the inside side without destroying them.) --- code/modules/vehicles/multitile/multitile_bump.dm | 8 ++++---- code/modules/vehicles/multitile/multitile_movement.dm | 11 ++++++++++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/code/modules/vehicles/multitile/multitile_bump.dm b/code/modules/vehicles/multitile/multitile_bump.dm index b07ba9fc391c..38fed407120f 100644 --- a/code/modules/vehicles/multitile/multitile_bump.dm +++ b/code/modules/vehicles/multitile/multitile_bump.dm @@ -62,12 +62,12 @@ return FALSE /obj/structure/barricade/plasteel/handle_vehicle_bump(obj/vehicle/multitile/V) + if(V.vehicle_flags & VEHICLE_CLASS_MEDIUM || V.vehicle_flags & VEHICLE_CLASS_HEAVY) + close(src) + return TRUE if(V.seats[VEHICLE_DRIVER]) close(src) - return FALSE - else - . = ..() - return FALSE + return ..() /obj/structure/barricade/deployable/handle_vehicle_bump(obj/vehicle/multitile/V) visible_message(SPAN_DANGER("\The [V] crushes [src]!")) diff --git a/code/modules/vehicles/multitile/multitile_movement.dm b/code/modules/vehicles/multitile/multitile_movement.dm index dabd1c0a303d..42e23795b1e6 100644 --- a/code/modules/vehicles/multitile/multitile_movement.dm +++ b/code/modules/vehicles/multitile/multitile_movement.dm @@ -159,6 +159,11 @@ var/bound_height_tiles = bound_height / world.icon_size var/list/old_turfs = CORNER_BLOCK(min_turf, bound_width_tiles, bound_height_tiles) + for(var/turf/T as anything in old_turfs) + for(var/obj/structure/barricade/B in T) + if(!QDELETED(B) && B.last_bumped != world.time && (B.dir & direction)) + Collide(B) + var/turf/new_loc = get_step(src, direction) min_turf = locate(new_loc.x + bound_x_tiles, new_loc.y + bound_y_tiles, z) @@ -177,7 +182,11 @@ if(!T.Enter(src)) can_move = FALSE - break + + for(var/obj/structure/barricade/B in T) + if(!QDELETED(B) && B.last_bumped != world.time) + if(REVERSE_DIR(B.dir) & direction) + Collide(B) // any other tile-blocking items that weren't caught by !T.Enter for(var/atom/A in T) From 658bf8a0e6277b88b867e976fe226abe2478b11b Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Wed, 1 Jul 2026 13:48:39 -0300 Subject: [PATCH 35/60] one letter var removals, changes for clarity, tank_on_top weakref --- code/_onclick/human.dm | 10 +- code/datums/components/tank_rider.dm | 41 +++++++ code/game/atoms_movable.dm | 27 +++-- code/game/objects/items.dm | 15 +-- code/game/objects/items/bodybag.dm | 35 +++--- code/game/objects/objs.dm | 21 ++-- .../structures/crates_lockers/closets.dm | 26 ++-- .../structures/stool_bed_chair_nest/bed.dm | 24 ++-- code/modules/cm_aliens/structures/trap.dm | 6 +- code/modules/mob/living/carbon/carbon.dm | 6 +- .../mob/living/carbon/xenomorph/XenoProcs.dm | 25 ++-- .../xenomorph/abilities/general_abilities.dm | 2 +- .../xenomorph/abilities/general_powers.dm | 6 +- code/modules/mob/living/living.dm | 42 +++---- code/modules/mob/mob_grab.dm | 10 +- code/modules/movement/launching/launching.dm | 11 +- code/modules/projectiles/gun.dm | 10 +- code/modules/vehicles/tank/tank.dm | 112 +++++++++--------- code/modules/vehicles/tank/tank_helpers.dm | 10 +- 19 files changed, 252 insertions(+), 187 deletions(-) create mode 100644 code/datums/components/tank_rider.dm diff --git a/code/_onclick/human.dm b/code/_onclick/human.dm index 62d3327cae80..1b7ae362098e 100644 --- a/code/_onclick/human.dm +++ b/code/_onclick/human.dm @@ -156,20 +156,20 @@ grab_level = GRAB_CARRY - if(tank_on_top_of && !target.tank_on_top_of) - var/obj/vehicle/multitile/tank/T = tank_on_top_of + var/obj/vehicle/multitile/tank/tank = get_tank_on_top_of() + if(tank && !target.get_tank_on_top_of()) - if(istype(T)) + if(istype(tank)) var/turf/target_turf = get_turf(target) var/adjacent_to_tank = FALSE - for(var/turf/tank_turf in T.locs) + for(var/turf/tank_turf in tank.locs) if(get_dist(target_turf, tank_turf) <= 1 && target_turf != tank_turf) adjacent_to_tank = TRUE break if(adjacent_to_tank) target.forceMove(user.loc) - T.mark_on_top(target) + tank.mark_on_top(target) target.update_transform(TRUE) return diff --git a/code/datums/components/tank_rider.dm b/code/datums/components/tank_rider.dm new file mode 100644 index 000000000000..8d9ae3d1c1e0 --- /dev/null +++ b/code/datums/components/tank_rider.dm @@ -0,0 +1,41 @@ +/** + * tank_rider tracks whether an atom/movable (a mob or a droppable obj) is currently riding atop a tank's hull. + * + * Added by /obj/vehicle/multitile/tank/proc/mark_on_top and obj_mark_on_top when something climbs onto, + * or is otherwise placed atop, a tank. Removed by clear_on_top/obj_clear_on_top whenit leaves + * + * holds a weakref to the tank rather than a direct reference so a rider never keeps a destroyed tank + * referenced, and so the component can detect a qdeld tank and clean ittself up on the next lookup + */ +/datum/component/tank_rider + dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS + var/datum/weakref/tank_ref + +/datum/component/tank_rider/Initialize(obj/vehicle/multitile/tank/tank) + if(!ismovable(parent) || !istype(tank)) + return COMPONENT_INCOMPATIBLE + tank_ref = WEAKREF(tank) + +/datum/component/tank_rider/InheritComponent(datum/component/tank_rider/new_comp, i_am_original, obj/vehicle/multitile/tank/tank) + if(istype(tank)) + tank_ref = WEAKREF(tank) + +/datum/component/tank_rider/Destroy(force, silent) + tank_ref = null + return ..() + +/datum/component/tank_rider/RegisterWithParent() + RegisterSignal(parent, COMSIG_PARENT_QDELETING, PROC_REF(on_parent_qdel)) + +/datum/component/tank_rider/UnregisterFromParent() + UnregisterSignal(parent, COMSIG_PARENT_QDELETING) + +/datum/component/tank_rider/proc/on_parent_qdel() + SIGNAL_HANDLER + qdel(src) + +/datum/component/tank_rider/proc/get_tank() + var/obj/vehicle/multitile/tank/tank = tank_ref?.resolve() + if(!tank) + qdel(src) + return tank diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 24daddb64dae..200a76f3cc22 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -44,10 +44,6 @@ /// ...However, defining it as part of obj instead of obj/item helps us handle exceptions. /// E.G: Exceptionally, as QoL for corpsmen: bodybags, stasis bags, and roller beds are allowed. var/is_allowed_atop_vehicle = FALSE - /// Tracks whether this item is currently atop a vehicle. - var/is_atop_vehicle = FALSE - /// Which vehicle are we ontop of? - var/tmp/obj/vehicle/multitile/tank/tank_on_top_of = null /// A weakref to the mob currently interacting with us. var/datum/weakref/interactor @@ -510,15 +506,17 @@ if(!buckled_mob) UnregisterSignal(buckle_target, COMSIG_PARENT_QDELETING) if(isliving(buckle_target)) - var/mob/living/living_M - if(living_M.tank_on_top_of) - living_M.tank_on_top_of.clear_on_top(living_M) + var/mob/living/living_M = buckle_target + var/obj/vehicle/multitile/tank/tank = living_M.get_tank_on_top_of() + if(tank) + tank.clear_on_top(living_M) else RegisterSignal(buckled_mob, COMSIG_PARENT_QDELETING, PROC_REF(unbuckle)) if(isliving(buckled_mob)) - var/mob/living/living_M - if(src.is_atop_vehicle) - src.tank_on_top_of.mark_on_top(living_M) + var/mob/living/living_M = buckled_mob + var/obj/vehicle/multitile/tank/tank = get_tank_on_top_of() + if(tank) + tank.mark_on_top(living_M) return buckled_mob /atom/movable/proc/handle_rotation() @@ -565,3 +563,12 @@ return NO_BLOCKED_MOVEMENT return ..() + +/atom/movable/proc/get_tank_on_top_of() + var/datum/component/tank_rider/rider = GetComponent(/datum/component/tank_rider) + if(!rider) + return null + return rider.get_tank() + +/atom/movable/proc/is_atop_vehicle() + return GetComponent(/datum/component/tank_rider) ? TRUE : FALSE diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 014793c92d5c..8ef8ba3507f4 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -414,13 +414,14 @@ src.do_drop_animation(user) if(isliving(user)) - var/mob/living/L = user - var/obj/vehicle/multitile/tank/T = L.tank_on_top_of - if(T && !src.is_atop_vehicle) // tank exists, our user is atop the tank, we are not marked as atop the tank yet. - T.obj_mark_on_top(src) - else if (!T && src.is_atop_vehicle) // only remove from vehicle if it is atop a vehicle to begin with - src.tank_on_top_of.obj_clear_on_top(src) - else if (T && src.is_atop_vehicle) // User on tank AND already marked + var/mob/living/living_mob = user + var/obj/vehicle/multitile/tank/tank = living_mob.get_tank_on_top_of() + var/obj/vehicle/multitile/tank/current_tank = src.get_tank_on_top_of() + if(tank && !current_tank) // tank exists, our user is atop the tank, we are not marked as atop the tank yet. + tank.obj_mark_on_top(src) + else if (!tank && current_tank) // only remove from vehicle if it is atop a vehicle to begin with + current_tank.obj_clear_on_top(src) + else if (tank && current_tank) // User on tank AND already marked src.pixel_y = initial(src.pixel_y) + 12 // Just refresh the pixel offset and layer to avoid a visual bug with animations src.layer = TANK_RIDER_OBJ_LAYER diff --git a/code/game/objects/items/bodybag.dm b/code/game/objects/items/bodybag.dm index b91f9dbad773..4f70bd0150b8 100644 --- a/code/game/objects/items/bodybag.dm +++ b/code/game/objects/items/bodybag.dm @@ -26,16 +26,17 @@ deploy_bodybag(user, T) /obj/item/bodybag/proc/deploy_bodybag(mob/user, atom/location) - var/obj/structure/closet/bodybag/R = new unfolded_path(location, src) - R.add_fingerprint(user) - R.open(user) + var/obj/structure/closet/bodybag/bodybag = new unfolded_path(location, src) + bodybag.add_fingerprint(user) + bodybag.open(user) if(isliving(user)) - var/mob/living/L = user - var/obj/vehicle/multitile/tank/T = L.tank_on_top_of - if(T && !R.is_atop_vehicle) // tank exists, our user is atop the tank, we are not marked as atop the tank yet. - T.obj_mark_on_top(R) - else if (!T && R.is_atop_vehicle) // only remove from vehicle if it is atop a vehicle to begin with - R.tank_on_top_of.obj_clear_on_top(R) + var/mob/living/living_mob = user + var/obj/vehicle/multitile/tank/tank = living_mob.get_tank_on_top_of() + var/obj/vehicle/multitile/tank/current_tank = bodybag.get_tank_on_top_of() + if(tank && !current_tank) // tank exists, our user is atop the tank, we are not marked as atop the tank yet. + tank.obj_mark_on_top(bodybag) + else if (!tank && current_tank) // only remove from vehicle if it is atop a vehicle to begin with + current_tank.obj_clear_on_top(bodybag) user.temp_drop_inv_item(src) qdel(src) @@ -216,7 +217,7 @@ /obj/structure/closet/bodybag/forceMove(atom/destination) if(roller_buckled) - if(!is_atop_vehicle && !roller_buckled.is_atop_vehicle) + if(!is_atop_vehicle() && !roller_buckled.is_atop_vehicle()) roller_buckled.unbuckle() . = ..() @@ -281,13 +282,13 @@ overlays.Cut() // makes sure any previous triage cards are removed if(!stasis_mob) - if(is_atop_vehicle) + if(is_atop_vehicle()) layer = TANK_RIDER_OBJ_LAYER else layer = initial(layer) return - if(is_atop_vehicle) + if(is_atop_vehicle()) layer = TANK_RIDER_OBJ_LAYER else layer = LYING_BETWEEN_MOB_LAYER @@ -312,10 +313,12 @@ if(stasis_mob) stasis_mob.in_stasis = FALSE UnregisterSignal(stasis_mob, COMSIG_HUMAN_TRIAGE_CARD_UPDATED) - if(src.is_atop_vehicle) - src.tank_on_top_of.mark_on_top(stasis_mob) - else if (stasis_mob.tank_on_top_of) - stasis_mob.tank_on_top_of.clear_on_top(stasis_mob) + var/obj/vehicle/multitile/tank/tank = src.get_tank_on_top_of() + var/obj/vehicle/multitile/tank/mob_tank = stasis_mob.get_tank_on_top_of() + if(tank) + tank.mark_on_top(stasis_mob) + else if (mob_tank) + mob_tank.clear_on_top(stasis_mob) stasis_mob = null STOP_PROCESSING(SSobj, src) if(used > max_uses) diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index 8b23cc00717b..966cd010cf02 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -236,19 +236,20 @@ /obj/Moved(atom/oldloc, direction, Forced = FALSE) . = ..() - if(is_atop_vehicle && isturf(loc)) + var/obj/vehicle/multitile/tank/tank = src.get_tank_on_top_of() + if(tank && isturf(loc)) var/still_on_tank = FALSE - for(var/obj/vehicle/multitile/tank/T in loc) - if(loc in T.locs) + for(var/obj/vehicle/multitile/tank/candidate in loc) + if(loc in candidate.locs) still_on_tank = TRUE break if(!still_on_tank) - src.tank_on_top_of.obj_clear_on_top(src) + tank.obj_clear_on_top(src) /obj/Move(NewLoc, direct) . = ..() handle_rotation() - if(src.is_atop_vehicle) + if(src.is_atop_vehicle()) var/still_on_tank = FALSE if(isturf(NewLoc)) for(var/obj/vehicle/multitile/tank/T in NewLoc) @@ -257,10 +258,10 @@ break if(!still_on_tank && buckled_mob && ismob(buckled_mob)) - var/mob/living/M = buckled_mob - var/obj/vehicle/multitile/tank/temp_tank = M.tank_on_top_of + var/mob/living/buckled_living = buckled_mob + var/obj/vehicle/multitile/tank/temp_tank = buckled_living.get_tank_on_top_of() if(temp_tank) - temp_tank.clear_on_top(M) + temp_tank.clear_on_top(buckled_living) src.forceMove(NewLoc) if(buckled_mob) @@ -380,8 +381,8 @@ /obj/handle_flamer_fire(obj/flamer_fire/fire, damage, delta_time) if(isitem(src)) - var/obj/item/T = src - if(T.is_atop_vehicle) // don't process flamer fire if the item is atop a climbable vehicle. + var/obj/item/dropped_item = src + if(dropped_item.is_atop_vehicle()) // don't process flamer fire if the item is atop a climbable vehicle. return . = ..() flamer_fire_act(damage, fire.weapon_cause_data) diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm index e42985e2f2c4..7edefcf837b3 100644 --- a/code/game/objects/structures/crates_lockers/closets.dm +++ b/code/game/objects/structures/crates_lockers/closets.dm @@ -76,13 +76,17 @@ /obj/structure/closet/proc/dump_contents() - for(var/obj/I in src) - I.forceMove(loc) + var/obj/vehicle/multitile/tank/tank = src.get_tank_on_top_of() + + for(var/obj/object in src) + object.forceMove(loc) // closets can't go atop vehicles, but this sets behavior for bodybags, which can. - if(is_atop_vehicle) - tank_on_top_of.obj_mark_on_top(I) - else if (I.tank_on_top_of) - tank_on_top_of.obj_clear_on_top(I) + if(tank) + tank.obj_mark_on_top(object) + else + var/obj/vehicle/multitile/tank/obj_tank = object.get_tank_on_top_of() + if(obj_tank) + obj_tank.obj_clear_on_top(object) for(var/mob/M in src) M.forceMove(loc) @@ -94,10 +98,12 @@ M.visible_message(SPAN_WARNING("[M] suddenly gets out of [src]!"), SPAN_WARNING("You get out of [src] and get your bearings!")) // closets can't go atop vehicles, but this sets behavior for bodybags, which can. - if(is_atop_vehicle) - tank_on_top_of.mark_on_top(living_M) - else if (living_M.tank_on_top_of) - living_M.tank_on_top_of.clear_on_top(living_M) + if(tank) + tank.mark_on_top(living_M) + else + var/obj/vehicle/multitile/tank/mob_tank = living_M.get_tank_on_top_of() + if(mob_tank) + mob_tank.clear_on_top(living_M) /// Attempts to open this closet by user, skipping checks that prevent opening if forced /obj/structure/closet/proc/open(mob/user, force) diff --git a/code/game/objects/structures/stool_bed_chair_nest/bed.dm b/code/game/objects/structures/stool_bed_chair_nest/bed.dm index f357a47dac1d..e3d68055e101 100644 --- a/code/game/objects/structures/stool_bed_chair_nest/bed.dm +++ b/code/game/objects/structures/stool_bed_chair_nest/bed.dm @@ -60,7 +60,7 @@ /obj/structure/bed/afterbuckle(mob/M) . = ..() if(. && buckled_mob == M) - if(is_atop_vehicle) + if(is_atop_vehicle()) M.pixel_y = buckling_y + 12 // Magic number bad. TODO: Make tank pixel offset its own variable. else M.pixel_y = buckling_y @@ -70,7 +70,7 @@ if(base_bed_icon) density = TRUE else - if(is_atop_vehicle) + if(is_atop_vehicle()) M.pixel_y = initial(buckled_mob.pixel_y) + 12 // Magic number bad. TODO: Make tank pixel offset its own variable. else M.pixel_y = initial(buckled_mob.pixel_y) @@ -91,8 +91,9 @@ buckled_bodybag = B density = TRUE update_icon() - if(is_atop_vehicle) - tank_on_top_of.obj_mark_on_top(buckled_bodybag) + var/obj/vehicle/multitile/tank/tank = get_tank_on_top_of() + if(tank) + tank.obj_mark_on_top(buckled_bodybag) buckled_bodybag.pixel_y = buckled_bodybag.buckle_offset + 12 // Magic number bad. TODO: Make tank pixel offset its own variable. else if(buckling_y) buckled_bodybag.pixel_y = buckled_bodybag.buckle_offset + buckling_y @@ -103,7 +104,7 @@ /obj/structure/bed/unbuckle() if(buckled_bodybag) - if(is_atop_vehicle) + if(is_atop_vehicle()) buckled_bodybag.pixel_y = initial(buckled_bodybag.pixel_y) + 12 // Magic number bad. TODO: Make tank pixel offset its own variable. else buckled_bodybag.pixel_y = initial(buckled_bodybag.pixel_y) @@ -325,12 +326,13 @@ user.temp_drop_inv_item(src) forceMove(roller) if(isliving(user)) - var/mob/living/L = user - var/obj/vehicle/multitile/tank/T = L.tank_on_top_of - if(T && !roller.is_atop_vehicle) // tank exists, our user is atop the tank, we are not marked as atop the tank yet. - T.obj_mark_on_top(roller) - else if (!T && roller.is_atop_vehicle) // only remove from vehicle if it is atop a vehicle to begin with - roller.tank_on_top_of.obj_clear_on_top(roller) + var/mob/living/living_mob = user + var/obj/vehicle/multitile/tank/tank = living_mob.get_tank_on_top_of() + var/obj/vehicle/multitile/tank/roller_tank = roller.get_tank_on_top_of() + if(tank && !roller_tank) // tank exists, our user is atop the tank, we are not marked as atop the tank yet. + tank.obj_mark_on_top(roller) + else if (!tank && roller_tank) // only remove from vehicle if it is atop a vehicle to begin with + roller_tank.obj_clear_on_top(roller) SEND_SIGNAL(user, COMSIG_MOB_ITEM_ROLLER_DEPLOYED, roller) if(target_mob) roller.buckle_mob(target_mob, user) diff --git a/code/modules/cm_aliens/structures/trap.dm b/code/modules/cm_aliens/structures/trap.dm index 48792b9fef39..067e54dd5390 100644 --- a/code/modules/cm_aliens/structures/trap.dm +++ b/code/modules/cm_aliens/structures/trap.dm @@ -95,7 +95,8 @@ if(can_hug(victim, hivenumber) && !isyautja(victim) && !issynth(victim) && !isthrall(victim)) var/mob/living/victim_mob = victim // tank treads block the hugger's hole. This behavior can be also added to normal acid traps. - if(victim_mob.tank_on_top_of && istype(victim_mob.tank_on_top_of) && (get_turf(victim_mob) in victim_mob.tank_on_top_of.locs)) + var/obj/vehicle/multitile/tank/victim_tank = victim_mob.get_tank_on_top_of() + if(victim_tank && (get_turf(victim_mob) in victim_tank.locs)) return victim_mob.visible_message(SPAN_WARNING("[victim_mob] trips on [src]!")) to_chat(victim_mob, SPAN_DANGER("You trip on [src]!")) @@ -113,7 +114,8 @@ var/mob/living/carbon/xenomorph/victim_xeno = victim if(victim_xeno.hivenumber != hivenumber) // just in case a Greeno decides to ride on the tank. - if(victim_xeno.tank_on_top_of && istype(victim_xeno.tank_on_top_of) && (get_turf(victim_xeno) in victim_xeno.tank_on_top_of.locs)) + var/obj/vehicle/multitile/tank/victim_tank = victim_xeno.get_tank_on_top_of() + if(victim_tank && (get_turf(victim_xeno) in victim_tank.locs)) return trigger_trap() if(isVehicleMultitile(victim) && trap_type != RESIN_TRAP_GAS) diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 7546dbd5a8b8..eecb9ee648cb 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -31,11 +31,11 @@ // Same as the parent proc, but adds 1 additional check for climb_down /mob/living/carbon/_handle_tank_edge_move(NewLoc, direct) - var/obj/vehicle/multitile/tank/T = tank_on_top_of + var/obj/vehicle/multitile/tank/tank = get_tank_on_top_of() var/result = ..() if(result) var/turf/edge = get_step(get_turf(src), direct) - if(T.climb_down(src, edge)) + if(tank.climb_down(src, edge)) return TRUE return result @@ -550,7 +550,7 @@ pixel_y = 0 var/obj/vehicle/multitile/tank/TANK = null if(hauling_xeno.is_on_tank_hull()) - TANK = hauling_xeno.tank_on_top_of + TANK = hauling_xeno.get_tank_on_top_of() UnregisterSignal(src, list(COMSIG_ATTEMPT_MOB_PULL, COMSIG_LIVING_PREIGNITION, COMSIG_LIVING_FLAMER_CROSSED, COMSIG_LIVING_FLAMER_FLAMED)) UnregisterSignal(hauling_xeno, COMSIG_MOB_DEATH) hauling_xeno = null diff --git a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm index a65ae6513e2f..a1a374ea5d12 100644 --- a/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm +++ b/code/modules/mob/living/carbon/xenomorph/XenoProcs.dm @@ -236,10 +236,10 @@ // this proc only serves to fix a visual bug where hauled mobs get layered below the tank. // this is a rare edge case, because xenos won't usually be hauling mobs atop the tank, but just for completeness... /mob/living/carbon/xenomorph/proc/check_on_tank_hauled_mob(mob/victim) - var/obj/vehicle/multitile/tank/T = null + var/obj/vehicle/multitile/tank/tank = null if(src.is_on_tank_hull()) - T = src.tank_on_top_of - T._apply_rider_visuals(victim) + tank = src.get_tank_on_top_of() + tank._apply_rider_visuals(victim) else victim.layer = initial(victim.layer) victim.plane = initial(victim.plane) @@ -369,12 +369,14 @@ ADD_TRAIT(src, TRAIT_IMMOBILIZED, TRAIT_SOURCE_ABILITY("Pounce")) pounceAction.freeze_timer_id = addtimer(CALLBACK(src, PROC_REF(unfreeze_pounce)), pounceAction.freeze_time, TIMER_STOPPABLE) pounceAction.additional_effects(carbon_mob) - if(pounced_mob.tank_on_top_of) - var/obj/vehicle/multitile/tank/T = pounced_mob.tank_on_top_of + var/obj/vehicle/multitile/tank/pounced_tank = pounced_mob.get_tank_on_top_of() + if(pounced_tank) src.forceMove(get_turf(pounced_mob)) - T.mark_on_top(src) - else if(src.tank_on_top_of) - src.tank_on_top_of.clear_on_top(src) + pounced_tank.mark_on_top(src) + else + var/obj/vehicle/multitile/tank/tank = src.get_tank_on_top_of() + if(tank) + tank.clear_on_top(src) if(pounceAction.slash) carbon_mob.attack_alien(src, pounceAction.slash_bonus_damage) @@ -405,9 +407,10 @@ if(facing && (facing in T.locs)) forceMove(facing) T.mark_on_top(src) - else if(src.tank_on_top_of) - if(!(locate(/obj/vehicle/multitile/tank) in get_turf(src))) - src.tank_on_top_of.clear_on_top(src) // if we're not atop the tank still, clear us from it. + else + var/obj/vehicle/multitile/tank/current_tank = src.get_tank_on_top_of() + if(current_tank && !(locate(/obj/vehicle/multitile/tank) in get_turf(src))) + current_tank.clear_on_top(src) // if we're not atop the tank still, clear us from it. if (pounceAction.should_destroy_objects) if(istype(O, /obj/structure/surface/table) || istype(O, /obj/structure/surface/rack) || istype(O, /obj/structure/window_frame)) diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/general_abilities.dm b/code/modules/mob/living/carbon/xenomorph/abilities/general_abilities.dm index d29a8ac82fb8..c561e161de0d 100644 --- a/code/modules/mob/living/carbon/xenomorph/abilities/general_abilities.dm +++ b/code/modules/mob/living/carbon/xenomorph/abilities/general_abilities.dm @@ -409,7 +409,7 @@ /datum/action/xeno_action/onclick/xenohide/can_use_action() var/mob/living/carbon/xenomorph/xeno = owner if(xeno) - if(xeno.tank_on_top_of) // Prevents cheesy layering underneath the tank. + if(xeno.get_tank_on_top_of()) // Prevents cheesy layering underneath the tank. return FALSE if(!xeno.buckled && !xeno.is_mob_incapacitated() && !LAZYLEN(xeno.buckled_mobs)) if(!(SEND_SIGNAL(xeno, COMSIG_LIVING_SHIMMY_LAYER) & COMSIG_LIVING_SHIMMY_LAYER_CANCEL)) diff --git a/code/modules/mob/living/carbon/xenomorph/abilities/general_powers.dm b/code/modules/mob/living/carbon/xenomorph/abilities/general_powers.dm index c4472facb8db..e431482ae7fc 100644 --- a/code/modules/mob/living/carbon/xenomorph/abilities/general_powers.dm +++ b/code/modules/mob/living/carbon/xenomorph/abilities/general_powers.dm @@ -1129,9 +1129,9 @@ // allows tailstabs into mobs riding atop the tank var/obj/vehicle/multitile/tank/skip_tank = null if(ismob(targetted_atom)) - var/mob/living/M = targetted_atom - if(istype(M) && M.tank_on_top_of) - skip_tank = M.tank_on_top_of + var/mob/living/target_mob = targetted_atom + if(istype(target_mob)) + skip_tank = target_mob.get_tank_on_top_of() var/list/turf/path = get_line(stabbing_xeno, targetted_atom, include_start_atom = FALSE) for(var/turf/path_turf as anything in path) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index f8396f404e63..558e7f9e8739 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -177,32 +177,32 @@ * * FALSE - When the mob dismounts improperly, and must be punished. */ /mob/living/proc/_handle_tank_edge_move(NewLoc, direct) - var/obj/vehicle/multitile/tank/T = tank_on_top_of + var/obj/vehicle/multitile/tank/tank = get_tank_on_top_of() - if(!T) + if(!tank) return FALSE var/turf/here = get_turf(src) - if(!(here in T.locs)) - T.clear_on_top(src) + if(!(here in tank.locs)) + tank.clear_on_top(src) return FALSE // only care about steps that would leave the hull. - if(isturf(NewLoc) && !(NewLoc in T.locs)) + if(isturf(NewLoc) && !(NewLoc in tank.locs)) if(!(flags_atom & DIRLOCK)) setDir(direct) SEND_SIGNAL(src, COMSIG_MOB_MOVE_OR_LOOK, TRUE, direct, direct) if(pulledby || throwing) return FALSE var/turf/edge = get_step(here, direct) - if(!edge || edge.z != z || (edge in T.locs)) + if(!edge || edge.z != z || (edge in tank.locs)) return FALSE - if(T._blocked_except_mobs(edge)) + if(tank._blocked_except_mobs(edge)) return TRUE // special interaction for simple_animals and (edge case) silicons... // ...to prevent SpacemanDMM from sleeping and still allow dismounts. if(!iscarbon(src)) - T.clear_on_top(src) + tank.clear_on_top(src) src.forceMove(NewLoc) return TRUE return TRUE @@ -210,21 +210,21 @@ return FALSE /** - * is_on_tank_hull checks if a mob has a valid tank_on_top_of. + * is_on_tank_hull checks if a mob has a valid tank rider component. * - * This proc checks the tank_on_top_of var of a mob. - * If it is anything except null, it means this mob is atop a tank tile. + * This proc checks the tank this mob's tank_rider component (if any) points to. + * If there is a valid tank, it means this mob is atop a tank tile. * * Returns: * * TRUE - If there is a valid tile under the tank, and if mob and tank are on the same Z-level. - * * FALSE - If tank_on_top_of is null, if the Z-levels of the mob and Tank mistmach, if the tile Tu is invalid, + * * FALSE - If the mob has no tank_rider component, if the Z-levels of the mob and Tank mistmach, if the tile Tu is invalid, * * ...Or if the tile Tu is not, in fact, underneath a tank. */ /mob/living/proc/is_on_tank_hull() - var/obj/vehicle/multitile/tank/T = tank_on_top_of - if(!T || z != T.z) return FALSE + var/obj/vehicle/multitile/tank/tank = get_tank_on_top_of() + if(!tank || z != tank.z) return FALSE var/turf/Tu = get_turf(src) - return Tu && (Tu in T.locs) + return Tu && (Tu in tank.locs) /mob/living/Move(NewLoc, direct) if(lying_angle != 0) @@ -239,15 +239,15 @@ if(_handle_tank_edge_move(NewLoc, direct)) return FALSE if(isturf(NewLoc)) - var/obj/vehicle/multitile/tank/T = tank_on_top_of - if(T && !(NewLoc in T.locs)) + var/obj/vehicle/multitile/tank/tank = get_tank_on_top_of() + if(tank && !(NewLoc in tank.locs)) if(ishuman(src)) var/mob/living/carbon/human/H = src H.apply_effect(1.5, WEAKEN) H.apply_effect(3, SUPERSLOW) H.apply_effect(5, SLOW) playsound(src, "punch", 25, TRUE) - T.clear_on_top(src) + tank.clear_on_top(src) var/atom/movable/pullee = pulling if(pullee && get_dist(src, pullee) > 1) //Is the pullee adjacent? @@ -482,10 +482,10 @@ return if(src_on_tank && target_on_tank) - if(tank_on_top_of == living_mob.tank_on_top_of) - var/obj/vehicle/multitile/tank/T = tank_on_top_of + var/obj/vehicle/multitile/tank/tank = get_tank_on_top_of() + if(tank == living_mob.get_tank_on_top_of()) var/turf/destination = get_step(living_mob.loc, dir) - if(destination && !(destination in T.locs)) + if(destination && !(destination in tank.locs)) if(src.a_intent != INTENT_HELP || living_mob.a_intent != INTENT_HELP) now_pushing = FALSE return diff --git a/code/modules/mob/mob_grab.dm b/code/modules/mob/mob_grab.dm index 66297488bbbd..872d8aa12c22 100644 --- a/code/modules/mob/mob_grab.dm +++ b/code/modules/mob/mob_grab.dm @@ -119,19 +119,19 @@ // allows pulling ppl onto the tank if you are already ontop of it.. // the idea is to allow the tank to charge into an incend OB and for corpsmen ontop to recover people. - if(user.tank_on_top_of && !victim.tank_on_top_of) - var/obj/vehicle/multitile/tank/T = user.tank_on_top_of - if(istype(T)) + var/obj/vehicle/multitile/tank/tank = user.get_tank_on_top_of() + if(tank && !victim.get_tank_on_top_of()) + if(istype(tank)) var/turf/victim_turf = get_turf(victim) var/adjacent_to_tank = FALSE - for(var/turf/tank_turf in T.locs) + for(var/turf/tank_turf in tank.locs) if(get_dist(victim_turf, tank_turf) <= 1 && victim_turf != tank_turf) adjacent_to_tank = TRUE break if(adjacent_to_tank) victim.forceMove(user.loc) - T.mark_on_top(victim) + tank.mark_on_top(victim) victim.update_transform(TRUE) return diff --git a/code/modules/movement/launching/launching.dm b/code/modules/movement/launching/launching.dm index 56907a80bb11..a1ceb2a6b80f 100644 --- a/code/modules/movement/launching/launching.dm +++ b/code/modules/movement/launching/launching.dm @@ -223,11 +223,12 @@ //done throwing, either because it hit something or it finished moving // prevents xeno pounces from layering under the tank, as well as other species with pounces. if (isliving(src)) - var/mob/living/L = src - L.update_layer() - if(L.tank_on_top_of) - if(!(locate(/obj/vehicle/multitile/tank) in get_turf(L))) - L.tank_on_top_of.clear_on_top(L) // if we're not atop the tank still, clear us from it. + var/mob/living/living_mob = src + living_mob.update_layer() + var/obj/vehicle/multitile/tank/rider_tank = living_mob.get_tank_on_top_of() + if(rider_tank) + if(!(locate(/obj/vehicle/multitile/tank) in get_turf(living_mob))) + rider_tank.clear_on_top(living_mob) // if we're not atop the tank still, clear us from it. else src.layer = initial(src.layer) if ((isobj(src) || ismob(src)) && throwing && !early_exit) diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index 6367aa40fd57..89f31cd8a0bd 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -1278,7 +1278,7 @@ and you're good to go. var/atom/original_target = target //This is for burst mode, in case the target changes per scatter chance in between fired bullets. - var/obj/vehicle/multitile/tank/tank_on_top_of = user.tank_on_top_of // Used to calculate inaccuracy when firing atop a moving tank. + var/obj/vehicle/multitile/tank/rider_tank = user.get_tank_on_top_of() // Used to calculate inaccuracy when firing atop a moving tank. if(loc != user || (flags_gun_features & GUN_WIELDED_FIRING_ONLY && !(flags_item & WIELDED))) return TRUE @@ -1292,7 +1292,7 @@ and you're good to go. var/original_scatter = projectile_to_fire.scatter var/original_accuracy = projectile_to_fire.accuracy - apply_bullet_scatter(projectile_to_fire, user, reflex, dual_wield, tank_on_top_of) //User can be passed as null. + apply_bullet_scatter(projectile_to_fire, user, reflex, dual_wield, rider_tank) //User can be passed as null. curloc = get_turf(user) if(QDELETED(original_target)) //If the target's destroyed, shoot at where it was last. @@ -1916,7 +1916,7 @@ not all weapons use normal magazines etc. load_into_chamber() itself is designed return TRUE //This proc calculates scatter and accuracy -/obj/item/weapon/gun/proc/apply_bullet_scatter(obj/projectile/projectile_to_fire, mob/user, reflex = 0, dual_wield = 0, tank_on_top_of = null) +/obj/item/weapon/gun/proc/apply_bullet_scatter(obj/projectile/projectile_to_fire, mob/user, reflex = 0, dual_wield = 0, obj/vehicle/multitile/tank/rider_tank = null) var/gun_accuracy_mult = accuracy_mult_unwielded var/gun_scatter = scatter_unwielded @@ -1925,8 +1925,8 @@ not all weapons use normal magazines etc. load_into_chamber() itself is designed gun_scatter = scatter // increases scatter to penalize marines firing atop a moving tank instead of outright restricting it // only for wielded firearms. - if(tank_on_top_of) - var/obj/vehicle/multitile/tank/TANK = tank_on_top_of + if(rider_tank) + var/obj/vehicle/multitile/tank/TANK = rider_tank if(world.time < TANK.on_top_mobs_shooting_inaccuracy_time) if(world.time % 3) to_chat(gun_user, SPAN_DANGER("You struggle to keep your aim centered as the [TANK] moves!")) diff --git a/code/modules/vehicles/tank/tank.dm b/code/modules/vehicles/tank/tank.dm index e007e9f97fd9..c82566234c97 100644 --- a/code/modules/vehicles/tank/tank.dm +++ b/code/modules/vehicles/tank/tank.dm @@ -401,29 +401,29 @@ * This proc does three things: * * It adds a mob to the tank's list of passengers (on_top_mobs) - * It adds the tank to a mob's tank_on_top_of var + * It attaches a /datum/component/tank_rider to the mob, marking us as the tank it's atop of * It calls _apply_rider_visuals() to set the layer atop the tank's * * If a xeno is hidden, it gets un-hidden. * * Arguments: - * * mob/living/M - The mob being marked ontop. + * * mob/living/rider_mob - The mob being marked ontop. */ -/obj/vehicle/multitile/tank/proc/mark_on_top(mob/living/M) - if(!istype(M)) +/obj/vehicle/multitile/tank/proc/mark_on_top(mob/living/rider_mob) + if(!istype(rider_mob)) return - if(M.z != z) + if(rider_mob.z != z) return - if(!(get_turf(M) in locs)) + if(!(get_turf(rider_mob) in locs)) return - if(M.tank_on_top_of == src) + if(rider_mob.get_tank_on_top_of() == src) return - on_top_mobs |= M - M.tank_on_top_of = src - _apply_rider_visuals(M) + on_top_mobs |= rider_mob + rider_mob.AddComponent(/datum/component/tank_rider, src) + _apply_rider_visuals(rider_mob) - if(isxeno(M)) - var/mob/living/carbon/xenomorph/X = M + if(isxeno(rider_mob)) + var/mob/living/carbon/xenomorph/X = rider_mob if(X.layer == XENO_HIDING_LAYER) var/datum/action/xeno_action/onclick/xenohide/hide = get_action(X, /datum/action/xeno_action/onclick/xenohide) if(hide) @@ -436,76 +436,74 @@ * * Checks if the obj can be brought atop a tank. * It adds the obj to the tank's list of objs (on_top_objs) - * It adds the tank to a obj'ss tank_on_top_of var + * It attaches a /datum/component/tank_rider to the obj, marking us as the tank it's atop of * It calls _obj_apply_rider_visuals() to set the layer atop the tank's * * Arguments: - * * obj/O - The obj being marked ontop. + * * obj/rider_obj - The obj being marked ontop. */ -/obj/vehicle/multitile/tank/proc/obj_mark_on_top(obj/O) - if(!istype(O)) +/obj/vehicle/multitile/tank/proc/obj_mark_on_top(obj/rider_obj) + if(!istype(rider_obj)) return - if(!O.is_allowed_atop_vehicle) + if(!rider_obj.is_allowed_atop_vehicle) return - if(O.z != z) + if(rider_obj.z != z) return - if(!(get_turf(O) in locs)) + if(!(get_turf(rider_obj) in locs)) return - if(O.tank_on_top_of == src) - O.layer = TANK_RIDER_OBJ_LAYER // prevents a visual bug with layering + if(rider_obj.get_tank_on_top_of() == src) + rider_obj.layer = TANK_RIDER_OBJ_LAYER // prevents a visual bug with layering return - on_top_obj |= O - O.tank_on_top_of = src - O.is_atop_vehicle = TRUE - O.layer = TANK_RIDER_OBJ_LAYER - if(istype(O, /obj/structure/closet/bodybag)) - var/obj/structure/closet/bodybag/BB = O + on_top_obj |= rider_obj + rider_obj.AddComponent(/datum/component/tank_rider, src) + rider_obj.layer = TANK_RIDER_OBJ_LAYER + if(istype(rider_obj, /obj/structure/closet/bodybag)) + var/obj/structure/closet/bodybag/BB = rider_obj if(BB.roller_buckled) BB.pixel_y = BB.buckle_offset + 12 else - BB.pixel_y = initial(O.pixel_y) + 12 + BB.pixel_y = initial(rider_obj.pixel_y) + 12 else - O.pixel_y = initial(O.pixel_y) + 12 + rider_obj.pixel_y = initial(rider_obj.pixel_y) + 12 /** * clear_on_top removes rider effects from a mob who was previously atop the tank. * * This proc resets the layer and plane of a rider. * This proc removes a rider from the on_top_mobs list of the tank. - * This proc sets the tank_on_top_of variable of a mob to null. + * This proc removes the mob's /datum/component/tank_rider. * * Arguments: - * * mob/living/M - The mob who has disembarked. + * * mob/living/rider_mob - The mob who has disembarked. */ -/obj/vehicle/multitile/tank/proc/clear_on_top(mob/living/M) - if(!istype(M)) +/obj/vehicle/multitile/tank/proc/clear_on_top(mob/living/rider_mob) + if(!istype(rider_mob)) return - on_top_mobs -= M - M.tank_on_top_of = null - M.layer = initial(M.layer) - M.plane = initial(M.plane) - M.pixel_y = initial(M.pixel_y) + on_top_mobs -= rider_mob + qdel(rider_mob.GetComponent(/datum/component/tank_rider)) + rider_mob.layer = initial(rider_mob.layer) + rider_mob.plane = initial(rider_mob.plane) + rider_mob.pixel_y = initial(rider_mob.pixel_y) /** * obj_clear_on_top removes rider effects from an obj atop the tank. * * This behaves as an ad-hoc polymorphic version of clear_on_top. */ -/obj/vehicle/multitile/tank/proc/obj_clear_on_top(obj/O) - if(!istype(O)) +/obj/vehicle/multitile/tank/proc/obj_clear_on_top(obj/rider_obj) + if(!istype(rider_obj)) return - on_top_obj -= O - O.tank_on_top_of = null - O.is_atop_vehicle = FALSE - O.layer = initial(O.layer) - if(istype(O, /obj/structure/closet/bodybag)) - var/obj/structure/closet/bodybag/BB = O + on_top_obj -= rider_obj + qdel(rider_obj.GetComponent(/datum/component/tank_rider)) + rider_obj.layer = initial(rider_obj.layer) + if(istype(rider_obj, /obj/structure/closet/bodybag)) + var/obj/structure/closet/bodybag/BB = rider_obj if(BB.roller_buckled) BB.pixel_y = BB.buckle_offset else - BB.pixel_y = initial(O.pixel_y) + BB.pixel_y = initial(rider_obj.pixel_y) else - O.pixel_y = initial(O.pixel_y) + rider_obj.pixel_y = initial(rider_obj.pixel_y) /** * Destroy proc. This shouldn't normally be called, but just in case. @@ -556,16 +554,16 @@ */ /obj/vehicle/multitile/tank/BlockedPassDirs(atom/movable/mover, target_dir) if(ismob(mover)) - var/mob/living/M = mover - if(istype(M) && M.tank_on_top_of == src) - var/turf/start = get_turf(M) + var/mob/living/mover_mob = mover + if(istype(mover_mob) && mover_mob.get_tank_on_top_of() == src) + var/turf/start = get_turf(mover_mob) var/turf/target = get_step(start, target_dir) if(target && (target in src.locs)) return NO_BLOCKED_MOVEMENT else if(isobj(mover)) - var/obj/O = mover - if(O.is_atop_vehicle) - var/turf/start = get_turf(O) + var/obj/mover_obj = mover + if(mover_obj.is_atop_vehicle()) + var/turf/start = get_turf(mover_obj) var/turf/target = get_step(start, target_dir) if(target && (target in src.locs)) return NO_BLOCKED_MOVEMENT @@ -594,11 +592,11 @@ */ /obj/vehicle/multitile/tank/take_damage_type(damage, type, atom/attacker) if(type == "slash" && istype(attacker, /mob/living/carbon/xenomorph)) - var/mob/living/carbon/xenomorph/X = attacker + var/mob/living/carbon/xenomorph/attacker_xeno = attacker // must actually be on top of THIS tank (exterior), same z, and stading on one of our tiles - if(X.tank_on_top_of == src && X.z == z) - var/turf/xturf = get_turf(X) + if(attacker_xeno.get_tank_on_top_of() == src && attacker_xeno.z == z) + var/turf/xturf = get_turf(attacker_xeno) if(xturf && (xturf in src.locs)) var/adj_damage = damage * 0.7 var/all_broken = TRUE diff --git a/code/modules/vehicles/tank/tank_helpers.dm b/code/modules/vehicles/tank/tank_helpers.dm index e98a36e704f0..3bee83afa067 100644 --- a/code/modules/vehicles/tank/tank_helpers.dm +++ b/code/modules/vehicles/tank/tank_helpers.dm @@ -579,17 +579,17 @@ /** * Helper function to know if a mob is a rider of this specific tank * - * Checks if this tank is the 'tank_on_top_of' of a given mob. + * Checks if this tank is the tank the given mob's /datum/component/tank_rider is tracking. * * Arguments: * * atom/movable/AM = whatever mob/atom we want to check if its a rider. * * Returns: - * * TRUE = If AM is a mob/living and this tank is tank_on_top_of. - * * FALSE = If AM is not a mob/living, or if this tank is not tank_on_top_of. + * * TRUE = If AM is a mob/living and this tank is what its tank_rider component points to. + * * FALSE = If AM is not a mob/living, or if this tank isn't what its tank_rider component points to. */ /obj/vehicle/multitile/tank/proc/_is_our_rider(atom/movable/AM) if(!ismob(AM)) return FALSE - var/mob/living/M = AM - return istype(M) && M.tank_on_top_of == src + var/mob/living/rider_mob = AM + return istype(rider_mob) && rider_mob.get_tank_on_top_of() == src From bf170faa9f8f5f85535e487337b89cae48ce3701 Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Wed, 1 Jul 2026 13:50:15 -0300 Subject: [PATCH 36/60] weather layer increased --- code/__DEFINES/layers.dm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/code/__DEFINES/layers.dm b/code/__DEFINES/layers.dm index b4f196702bfb..8613755e2d89 100644 --- a/code/__DEFINES/layers.dm +++ b/code/__DEFINES/layers.dm @@ -132,8 +132,6 @@ #define FACEHUGGER_LAYER 4.13 /// For Signs above everything but below weather #define BILLBOARD_LAYER 4.13 -/// For WEATHER -#define WEATHER_LAYER 4.14 /// For objs atop a tank #define TANK_RIDER_OBJ_LAYER 4.505 @@ -147,6 +145,9 @@ #define TANK_RIDER_LAYER 4.51 #define TANK_ABOVE_RIDER_LAYER 4.515 +/// For WEATHER +#define WEATHER_LAYER 5 + //#define FLY_LAYER 5 #define RIPPLE_LAYER 5.1 From d627caa68de60937b3e0be2ebab07eccde91726e Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Wed, 1 Jul 2026 13:52:20 -0300 Subject: [PATCH 37/60] forgot the .dme --- colonialmarines.dme | 1 + 1 file changed, 1 insertion(+) diff --git a/colonialmarines.dme b/colonialmarines.dme index 73f166b7299c..7c2e2895c1df 100644 --- a/colonialmarines.dme +++ b/colonialmarines.dme @@ -460,6 +460,7 @@ #include "code\datums\components\speed_modifier.dm" #include "code\datums\components\status_effect_component.dm" #include "code\datums\components\tacmap.dm" +#include "code\datums\components\tank_rider.dm" #include "code\datums\components\temporary_mute.dm" #include "code\datums\components\toxin_buildup.dm" #include "code\datums\components\tracker_bullets.dm" From c657a8915602f391a515e7d2063545d8a614ee1a Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Sat, 4 Jul 2026 17:57:17 -0300 Subject: [PATCH 38/60] Tank INTERCOMM system --- .../interior/interactable/intercom.dm | 82 +++++++++++++++++++ .../vehicles/interior/interior_landmarks.dm | 26 ++++++ code/modules/vehicles/multitile/multitile.dm | 20 +++++ .../vehicles/multitile/multitile_movement.dm | 1 + colonialmarines.dme | 1 + maps/interiors/tank.dmm | 11 ++- 6 files changed, 139 insertions(+), 2 deletions(-) create mode 100644 code/modules/vehicles/interior/interactable/intercom.dm diff --git a/code/modules/vehicles/interior/interactable/intercom.dm b/code/modules/vehicles/interior/interactable/intercom.dm new file mode 100644 index 000000000000..ef5064a00289 --- /dev/null +++ b/code/modules/vehicles/interior/interactable/intercom.dm @@ -0,0 +1,82 @@ +/obj/structure/vehicle_intercom + name = "vehicle intercom" + desc = "A wall-mounted intercom wired into the vehicle's hull, relaying speech between the crew compartment and the outside world." + icon = 'icons/obj/structures/phone.dmi' + icon_state = "wall_phone" + anchored = TRUE + density = FALSE + layer = 3.2 + + unacidable = TRUE + unslashable = TRUE + explo_proof = TRUE + + flags_atom = USES_HEARING + + var/obj/vehicle/multitile/vehicle = null + var/obj/item/phone/handset + + // Mic = picks up interior speech to broadcast outside. Speaker = plays exterior speech inside. + var/mic_muted = FALSE + var/speaker_muted = FALSE + +/obj/structure/vehicle_intercom/Initialize(mapload, ...) + . = ..() + handset = new /obj/item/phone(src) + +/obj/structure/vehicle_intercom/Destroy() + QDEL_NULL(handset) + vehicle = null + return ..() + +/obj/structure/vehicle_intercom/attack_hand(mob/user) + . = ..() + if(.) + return + + var/choice = tgui_input_list(user, "Adjust the intercom's microphone and speaker.", "Vehicle Intercom", + list("Toggle Microphone ([mic_muted ? "Muted" : "Live"])", "Toggle Speaker ([speaker_muted ? "Muted" : "Live"])", "Cancel")) + + if(!choice || choice == "Cancel" || !Adjacent(user)) + return + + if(findtext(choice, "Microphone")) + mic_muted = !mic_muted + to_chat(user, SPAN_NOTICE("You [mic_muted ? "mute" : "unmute"] the intercom's microphone. [mic_muted ? "Nobody outside will hear the crew compartment." : "The crew compartment can be heard outside again."]")) + else if(findtext(choice, "Speaker")) + speaker_muted = !speaker_muted + to_chat(user, SPAN_NOTICE("You [speaker_muted ? "mute" : "unmute"] the intercom's speaker. [speaker_muted ? "The crew compartment will no longer hear the outside." : "The crew compartment can hear the outside again."]")) + +// anyone speaking near the inttercom inside the interior gets relayed outside. +/obj/structure/vehicle_intercom/hear_talk(mob/living/M, msg, verb = "says", datum/language/speaking, italics = 0) + if(!mic_muted) + relay_to_exterior(M, msg, verb, speaking, italics) + ..() + +/obj/structure/vehicle_intercom/proc/relay_to_exterior(mob/living/M, msg, verb, datum/language/speaking, italics) + if(!vehicle) + return + var/turf/exterior_turf = get_turf(vehicle) + if(!exterior_turf) + return + var/list/listeners = get_mobs_in_view(7, exterior_turf) + if(!length(listeners)) + return + for(var/mob/L in listeners) + L.hear_radio(msg, verb, speaking, part_a = "", + part_b = " ", vname = "[vehicle] Intercom", + speaker = M, no_paygrade = TRUE) + vehicle.langchat_speech(msg, listeners, speaking, M.langchat_color, FALSE, LANGCHAT_FAST_POP, list(M.langchat_styles)) + +// Called by /obj/vehicle/multitile/hear_talk() when someone speaks near the vehicle's exterior. +/obj/structure/vehicle_intercom/proc/relay_exterior_speech(mob/living/M, msg, verb, datum/language/speaking, italics) + if(speaker_muted || !vehicle || !vehicle.interior) + return + var/list/listeners = vehicle.interior.get_passengers() + if(!length(listeners)) + return + for(var/mob/L in listeners) + L.hear_radio(msg, verb, speaking, part_a = "", + part_b = " ", vname = "Intercom", + speaker = M, no_paygrade = TRUE) + langchat_speech(msg, listeners, speaking, M.langchat_color, FALSE, LANGCHAT_FAST_POP, list(M.langchat_styles)) diff --git a/code/modules/vehicles/interior/interior_landmarks.dm b/code/modules/vehicles/interior/interior_landmarks.dm index b57defea5f4b..02cf7a68cb55 100644 --- a/code/modules/vehicles/interior/interior_landmarks.dm +++ b/code/modules/vehicles/interior/interior_landmarks.dm @@ -247,6 +247,32 @@ /obj/effect/landmark/interior/spawn/telephone/wy icon = 'icons/obj/vehicles/interiors/general_wy.dmi' +// Landmark for spawning the ambient vehicle intercom +/obj/effect/landmark/interior/spawn/intercom + name = "vehicle intercom spawner" + icon = 'icons/obj/vehicles/interiors/general.dmi' + icon_state = "wall_phone" + color = "cyan" + +/obj/effect/landmark/interior/spawn/intercom/on_load(datum/interior/interior_data) + var/obj/structure/vehicle_intercom/Intercom = new(loc) + var/obj/vehicle/multitile/vehicle = interior_data.exterior + + Intercom.icon = icon + Intercom.icon_state = icon_state + Intercom.layer = layer + Intercom.setDir(dir) + Intercom.alpha = alpha + Intercom.update_icon() + Intercom.pixel_x = pixel_x + Intercom.pixel_y = pixel_y + Intercom.vehicle = vehicle + + vehicle.intercom = Intercom + vehicle.flags_atom |= USES_HEARING + + qdel(src) + // Landmark for spawning the reloader /obj/effect/landmark/interior/spawn/weapons_loader name = "vehicle weapons reloader spawner" diff --git a/code/modules/vehicles/multitile/multitile.dm b/code/modules/vehicles/multitile/multitile.dm index 63c9aacc8275..d33230478483 100644 --- a/code/modules/vehicles/multitile/multitile.dm +++ b/code/modules/vehicles/multitile/multitile.dm @@ -95,6 +95,7 @@ // Map file name of the vehicle interior var/interior_map = null var/datum/interior/interior = null + var/obj/structure/vehicle_intercom/intercom = null //common passenger slots var/passengers_slots = 2 @@ -196,6 +197,7 @@ var/angle_to_turn = turning_angle(SOUTH, dir) rotate_entrances(angle_to_turn) rotate_bounds(angle_to_turn) + update_langchat_height() if(bound_width > world.icon_size || bound_height > world.icon_size) lighting_holder = new(src) @@ -241,6 +243,24 @@ return ..() +// No-op unless an intercom landmark spawned one into this vehicle's interior. +/obj/vehicle/multitile/hear_talk(mob/living/speaker, msg, verb = "says", datum/language/speaking, italics = 0) + if(intercom) + intercom.relay_exterior_speech(speaker, msg, verb, speaking, italics) + if(!intercom.speaker_muted) + for(var/seat_key in seats) + var/mob/seated = seats[seat_key] + if(seated?.client?.prefs && !seated.client.prefs.lang_chat_disabled && !seated.ear_deaf && seated.say_understands(speaker, speaking)) + speaker.langchat_display_image(seated) + ..() + +// offsets langchat height to show, properly, above and around the middle of the vehicle. +/obj/vehicle/multitile/proc/update_langchat_height() + langchat_height = bound_height - bound_y - 8 + +/obj/vehicle/multitile/get_maxptext_x_offset(image/maptext_image) + return ..() - bound_x - 16 + /obj/vehicle/multitile/proc/initialize_cameras() return diff --git a/code/modules/vehicles/multitile/multitile_movement.dm b/code/modules/vehicles/multitile/multitile_movement.dm index 42e23795b1e6..1e943e2b20f4 100644 --- a/code/modules/vehicles/multitile/multitile_movement.dm +++ b/code/modules/vehicles/multitile/multitile_movement.dm @@ -107,6 +107,7 @@ rotate_hardpoints(deg) rotate_entrances(deg) rotate_bounds(deg) + update_langchat_height() setDir(turn(dir, deg), TRUE) last_move_dir = dir diff --git a/colonialmarines.dme b/colonialmarines.dme index 7c2e2895c1df..e27966ef6ad4 100644 --- a/colonialmarines.dme +++ b/colonialmarines.dme @@ -2694,6 +2694,7 @@ #include "code\modules\vehicles\interior\interior_hull.dm" #include "code\modules\vehicles\interior\interior_landmarks.dm" #include "code\modules\vehicles\interior\interactable\doors.dm" +#include "code\modules\vehicles\interior\interactable\intercom.dm" #include "code\modules\vehicles\interior\interactable\seats.dm" #include "code\modules\vehicles\interior\interactable\vehicle_locker.dm" #include "code\modules\vehicles\interior\interactable\vendors.dm" diff --git a/maps/interiors/tank.dmm b/maps/interiors/tank.dmm index e5c019e01993..e36eead70aab 100644 --- a/maps/interiors/tank.dmm +++ b/maps/interiors/tank.dmm @@ -13,7 +13,13 @@ phone_category = "Vehicles"; phone_id = "M34A2 Longstreet Light Tank"; pixel_x = 22; - pixel_y = -14 + pixel_y = -6 + }, +/obj/effect/landmark/interior/spawn/intercom{ + pixel_x = 22; + pixel_y = -15; + dir = 8; + drag_delay = 3.05 }, /turf/open/shuttle/vehicle/floor_1_12, /area/interior/vehicle/tank) @@ -67,7 +73,8 @@ alpha = 50; icon_state = "exterior_2"; layer = 5.2; - pixel_y = 32 + pixel_y = 32; + pixel_x = 0 }, /turf/open/void/vehicle, /area/space) From 51a604ce9999851f736e15337ea824cc4562bfcb Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Sat, 4 Jul 2026 18:14:19 -0300 Subject: [PATCH 39/60] Turret aim rework: Debug verbs, Arc-transversal & Gun slaving --- .../dcs/signals/atom/mob/signals_mob.dm | 1 + code/__DEFINES/vehicle.dm | 19 ++ code/__HELPERS/unsorted.dm | 40 ++- code/_onclick/click_hold.dm | 13 + .../mob/living/carbon/carbon_debug_verbs.dm | 195 +++++++++++ code/modules/vehicles/hardpoints/hardpoint.dm | 313 +++++++++++++++++- .../vehicles/hardpoints/holder/holder.dm | 5 + .../vehicles/hardpoints/holder/tank_turret.dm | 112 ++++++- .../vehicles/hardpoints/primary/autocannon.dm | 8 +- .../hardpoints/primary/dual_cannon.dm | 2 +- .../vehicles/hardpoints/primary/flamer.dm | 12 +- .../vehicles/hardpoints/primary/ltb.dm | 8 +- .../vehicles/hardpoints/primary/minigun.dm | 2 +- .../vehicles/hardpoints/secondary/cupola.dm | 9 +- .../vehicles/hardpoints/secondary/flamer.dm | 15 +- .../hardpoints/secondary/frontal_cannon.dm | 2 +- .../hardpoints/secondary/grenade_launcher.dm | 16 +- .../hardpoints/secondary/secondary.dm | 45 +++ .../vehicles/hardpoints/secondary/tow.dm | 16 +- .../hardpoints/special/firing_port_weapon.dm | 2 +- .../vehicles/hardpoints/support/flare.dm | 2 +- .../multitile/multitile_interaction.dm | 40 ++- .../vehicles/multitile/multitile_verbs.dm | 51 +++ code/modules/vehicles/tank/tank.dm | 21 +- colonialmarines.dme | 1 + .../tgui/interfaces/TankPivotTuner.tsx | 119 +++++++ 26 files changed, 1009 insertions(+), 60 deletions(-) create mode 100644 code/modules/mob/living/carbon/carbon_debug_verbs.dm create mode 100644 tgui/packages/tgui/interfaces/TankPivotTuner.tsx diff --git a/code/__DEFINES/dcs/signals/atom/mob/signals_mob.dm b/code/__DEFINES/dcs/signals/atom/mob/signals_mob.dm index 04afcd3beb90..1b272624d18f 100644 --- a/code/__DEFINES/dcs/signals/atom/mob/signals_mob.dm +++ b/code/__DEFINES/dcs/signals/atom/mob/signals_mob.dm @@ -143,6 +143,7 @@ #define COMSIG_MOB_MOUSEDOWN "mob_mousedown" //from /client/MouseDown(): (atom/object, turf/location, control, params) #define COMSIG_MOB_MOUSEUP "mob_mouseup" //from /client/MouseUp(): (atom/object, turf/location, control, params) #define COMSIG_MOB_MOUSEDRAG "mob_mousedrag" //from /client/MouseDrag(): (atom/src_object, atom/over_object, turf/src_location, turf/over_location, src_control, over_control, params) +#define COMSIG_MOB_MOUSEMOVE "mob_mousemove" //from /client/MouseMove(): (atom/object, turf/location, control, params) #define COMSIG_MOB_CLICK_CANCELED (1<<0) #define COMSIG_MOB_CLICK_HANDLED (1<<1) diff --git a/code/__DEFINES/vehicle.dm b/code/__DEFINES/vehicle.dm index d40e89826eff..2a833eb37b85 100644 --- a/code/__DEFINES/vehicle.dm +++ b/code/__DEFINES/vehicle.dm @@ -73,3 +73,22 @@ #define BUCKLE_PREVENTS_PULL (1<<3) #define BUCKLE_NEEDS_HAND (1<<4) #define BUCKLE_NEEDS_TWO_HANDS (1<<5) + +//Tank turret mouse-aim rotation + +/// How close current_angle needs to be to desired_angle before the turret rotation loop considers itself settled and stops ticking. +#define ROTATION_SETTLE_TOLERANCE 0.5 +/// How close current_angle needs to be to the angle-to-target before a turret-mounted weapon is allowed to fire. +#define FIRING_GATE_TOLERANCE 5 +/// Baseline turn speed (degrees per decisecond) for a turret whose median mounted traverse_arc equals TURRET_ARC_NORMALIZATION. +#define TURRET_BASE_ANGULAR_VELOCITY 6 +/// The traverse_arc value that maps 1:1 onto TURRET_BASE_ANGULAR_VELOCITY. +#define TURRET_ARC_NORMALIZATION 90 +/// Turn speed floor, so a turret can never be fully immobilized by a degenerate (e.g. 0) traverse_arc value. +#define TURRET_MIN_ANGULAR_VELOCITY 2 +/// Turn speed used when the turret has no weapons mounted at all. +#define TURRET_DEFAULT_ANGULAR_VELOCITY 4 +/// Flat angular acceleration (degrees per decisecond^2) used to ramp angular_velocity up/down. +#define TURRET_ANGULAR_ACCEL 1 +/// Half-width, in degrees, of the arc a self_gimballed weapon's driver-controlled aim is clamped to around the turret's own current facing (see /obj/item/hardpoint/proc/update_desired_angle()) - keeps the visual swivel (see /obj/item/hardpoint/holder/tank_turret/get_hardpoint_image()) to a modest look-around instead of a full swing to any absolute angle. +#define SLAVED_GIMBAL_ARC_HALF_WIDTH 30 diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index f562f6b913c4..46c73c019fba 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -63,11 +63,14 @@ // GLOBAL PROCS // /// Gives X position on pixel grid of an object, accounting for offsets -/proc/get_pixel_position_x(atom/subject, relative = FALSE) +/proc/get_pixel_position_x(atom/subject, relative = FALSE, include_size_adjustment = TRUE) . = subject.pixel_x + subject.base_pixel_x if(!relative) . += world.icon_size * subject.x + if(!include_size_adjustment) + return + if(ismob(subject)) // Mobs use baked in icon_size due to eg. Xenos only using a visual size var/mob/mob_subject = subject . += (mob_subject.icon_size - world.icon_size) / 2 @@ -77,11 +80,14 @@ . += (big_subject.bound_width - world.icon_size) / 2 /// Gives Y position on pixel grid of an object, accounting for offsets -/proc/get_pixel_position_y(atom/subject, relative = FALSE) +/proc/get_pixel_position_y(atom/subject, relative = FALSE, include_size_adjustment = TRUE) . = subject.pixel_y + subject.base_pixel_y if(!relative) . += world.icon_size * subject.y + if(!include_size_adjustment) + return + if(ismob(subject)) // Mobs use baked in icon_size due to eg. Xenos only using a visual size var/mob/mob_subject = subject . += (mob_subject.icon_size - world.icon_size) / 2 @@ -106,12 +112,42 @@ var/dx = get_pixel_position_x(end) - get_pixel_position_x(start) return delta_to_angle(dx, dy) +/** + * Same as Get_Angle(), but skips the automatic "sprite is bigger than a tile, so nudge the position + * by half the extra size" compensation baked into get_pixel_position_x/y() + * + * only used for actual tturret aiming/hit-resolution math (update_desired_angle(), track_and_charge()), not for purely + * cosmetic uses (LTB reticle placement, muzzle flashes, beams). + * + * That automatic guess assumes a sprite bigger than world.icon_size is centered on its tile, which + * is wrong for a mob like the Queen (icon_size 64) whose sprite is anchored at the feet and simply + * extends upward... +*/ +/proc/Get_Angle_Grounded(atom/start, atom/end) + if(!start || !end) + return 0 + if(!start.z) + start = get_turf(start) + if(!start) + return 0 + if(!end.z) + end = get_turf(end) + if(!end) + return 0 + var/dy = get_pixel_position_y(end, include_size_adjustment = FALSE) - get_pixel_position_y(start, include_size_adjustment = FALSE) + var/dx = get_pixel_position_x(end, include_size_adjustment = FALSE) - get_pixel_position_x(start, include_size_adjustment = FALSE) + return delta_to_angle(dx, dy) + /// Calculate the angle produced by a pair of x and y deltas. Uses north-clockwise convention: NORTH = 0, EAST = 90, etc. /proc/delta_to_angle(dx, dy) . = arctan(dy, dx) //y-then-x results in north-clockwise convention: https://en.wikipedia.org/wiki/Atan2#East-counterclockwise,_north-clockwise_and_south-clockwise_conventions,_etc. if(. < 0) . += 360 +/// Shortest signed angle difference from b to a, in the range -180..180. Positive means a is clockwise of b. +/proc/angle_delta(a, b) + return ((a - b + 540) % 360) - 180 + /proc/angle_to_dir(angle) angle = ((angle % 360) + 382.5) % 360 switch(angle) //diagonal directions get priority over straight directions in edge cases diff --git a/code/_onclick/click_hold.dm b/code/_onclick/click_hold.dm index 2963dddfd9a3..da8b6edd5422 100644 --- a/code/_onclick/click_hold.dm +++ b/code/_onclick/click_hold.dm @@ -103,6 +103,19 @@ // Add the hovered atom to the trace LAZYADD(mouse_trace_history, over_obj) +// Fires at near-pixel granularity while the cursor moves over the map, even with no button held. +// if this ever gets used anywhere else, be careful: Listeners are expected to rate-limit themselves - BWSB +/client/MouseMove(atom/object, turf/location, control, params) + if(!object) + return + + var/click_catcher_click = FALSE + CONVERT_CLICK_CATCHER(object, location, click_catcher_click) + if(click_catcher_click) + params += CLICK_CATCHER_ADD_PARAM + + SEND_SIGNAL(mob, COMSIG_MOB_MOUSEMOVE, object, location, control, params) + /client/MouseDrop(datum/src_object, datum/over_object, src_location, over_location, src_control, over_control, params) . = ..() if(HAS_TRAIT(usr, TRAIT_HAULED)) diff --git a/code/modules/mob/living/carbon/carbon_debug_verbs.dm b/code/modules/mob/living/carbon/carbon_debug_verbs.dm new file mode 100644 index 000000000000..84d26ce7c87d --- /dev/null +++ b/code/modules/mob/living/carbon/carbon_debug_verbs.dm @@ -0,0 +1,195 @@ +/// Shared lookup for the debug verbs below: finds the tank_turret on the tank this mob is currently seated in, if any. Reports why to src and returns null on failure. +/mob/living/carbon/proc/get_seated_tank_turret() + if(!istype(buckled, /obj/structure/bed/chair/comfy/vehicle)) + to_chat(src, SPAN_WARNING("You're not seated in a vehicle.")) + return null + + var/obj/structure/bed/chair/comfy/vehicle/seat_chair = buckled + var/obj/vehicle/multitile/tank/tank_vehicle = seat_chair.vehicle + if(!istype(tank_vehicle)) + to_chat(src, SPAN_WARNING("You're not seated in a tank.")) + return null + + var/obj/item/hardpoint/holder/tank_turret/turret + for(var/obj/item/hardpoint/holder/tank_turret/TT in tank_vehicle.hardpoints) + turret = TT + break + if(!turret) + to_chat(src, SPAN_WARNING("This tank has no turret installed.")) + return null + + return turret + +// Debug tool for live-tuning tank turret weapon sprite alignment +/mob/living/carbon/verb/set_tank_weapon_rotation_pivot() + set name = "Set Tank Weapon Rotation Pivot" + set category = "Debug" + set desc = "Live-tunes the rotation_pivot of a weapon mounted on the turret of the tank you're seated in." + + if(!check_rights(R_DEBUG)) + return + + var/obj/item/hardpoint/holder/tank_turret/turret = get_seated_tank_turret() + if(!turret) + return + + if(!LAZYLEN(turret.hardpoints)) + to_chat(src, SPAN_WARNING("This turret has no weapons mounted.")) + return + + var/datum/tank_pivot_tuner/tuner = new + tuner.turret = turret + for(var/obj/item/hardpoint/H in turret.hardpoints) + tuner.weapon = H + break + tuner.tgui_interact(src) + +/datum/tank_pivot_tuner + var/obj/item/hardpoint/holder/tank_turret/turret + var/obj/item/hardpoint/weapon + +/datum/tank_pivot_tuner/ui_state(mob/user) + return GLOB.always_state + +/datum/tank_pivot_tuner/ui_close(mob/user) + . = ..() + qdel(src) + +/datum/tank_pivot_tuner/tgui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "TankPivotTuner") + ui.open() + ui.set_autoupdate(TRUE) + +/// The nearest of the 8 cardinal/diagonal dir constants to the turret's current continuous facing +/datum/tank_pivot_tuner/proc/get_turret_octant() + return angle_to_dir(turret.current_angle) + +/datum/tank_pivot_tuner/ui_data(mob/user) + . = list() + if(QDELETED(turret)) + return + + var/turret_octant = get_turret_octant() + .["dir_name"] = dir2text(turret_octant) + + var/dir_key = "[turret_octant]" + var/list/weapon_list = list() + for(var/obj/item/hardpoint/H in turret.hardpoints) + var/list/pivot = LAZYACCESS(H.rotation_pivot, dir_key) || list(0, 0) + weapon_list += list(list( + "ref" = "\ref[H]", + "name" = H.name, + "pivot_x" = pivot[1], + "pivot_y" = pivot[2], + "selected" = (H == weapon), + )) + .["weapons"] = weapon_list + + if(QDELETED(weapon)) + return + var/list/selected_pivot = LAZYACCESS(weapon.rotation_pivot, dir_key) || list(0, 0) + .["tuning_label"] = "turret facing: [dir2text(turret_octant)]" + .["selected_name"] = weapon.name + .["pivot_x"] = selected_pivot[1] + .["pivot_y"] = selected_pivot[2] + + var/list/selected_offset = LAZYACCESS(weapon.px_offsets, dir_key) || list(0, 0) + .["offset_x"] = selected_offset[1] + .["offset_y"] = selected_offset[2] + + .["is_gimballed"] = weapon.self_gimballed + if(weapon.self_gimballed) + var/list/selected_gimbal_pivot = LAZYACCESS(weapon.gimbal_pivot, dir_key) || list(0, 0) + .["gimbal_pivot_x"] = selected_gimbal_pivot[1] + .["gimbal_pivot_y"] = selected_gimbal_pivot[2] + +/datum/tank_pivot_tuner/ui_act(action, list/params, datum/tgui/ui) + . = ..() + if(.) + return + if(QDELETED(turret)) + return + + switch(action) + if("select_weapon") + var/obj/item/hardpoint/H = locate(params["ref"]) in turret.hardpoints + if(!H) + return + weapon = H + . = TRUE + if("adjust") + if(QDELETED(weapon)) + return + var/axis = params["axis"] + var/delta = params["delta"] + var/target = params["target"] + if(!delta || !(axis == "x" || axis == "y") || !(target == "rotation" || target == "gimbal" || target == "offset")) + return + if(target == "gimbal" && !weapon.self_gimballed) + return + + var/list/pivot_list + switch(target) + if("gimbal") + pivot_list = weapon.gimbal_pivot + if("offset") + pivot_list = weapon.px_offsets + else + pivot_list = weapon.rotation_pivot + var/dir_key = "[get_turret_octant()]" + var/list/pivot = LAZYACCESS(pivot_list, dir_key) || list(0, 0) + var/list/new_pivot = list(pivot[1], pivot[2]) + if(axis == "x") + new_pivot[1] = clamp(pivot[1] + delta, -200, 200) + else + new_pivot[2] = clamp(pivot[2] + delta, -200, 200) + LAZYSET(pivot_list, dir_key, new_pivot) + switch(target) + if("gimbal") + weapon.gimbal_pivot = pivot_list + if("offset") + weapon.px_offsets = pivot_list + else + weapon.rotation_pivot = pivot_list + + turret.owner.update_icon() + . = TRUE + +// instantly swaps whichever primary/secondary weapon is mounted on the tank turret + +/mob/living/carbon/verb/cycle_tank_weapon_hardpoint() + set name = "Cycle Tank Weapon Hardpoint" + set category = "Debug" + set desc = "Swaps the turret's primary/secondary weapon (whichever slot the next type in the cycle belongs to) to the next available type." + + if(!check_rights(R_DEBUG)) + return + + var/obj/item/hardpoint/holder/tank_turret/turret = get_seated_tank_turret() + if(!turret) + return + + if(!LAZYLEN(turret.accepted_hardpoints)) + to_chat(src, SPAN_WARNING("This turret doesn't accept any hardpoints.")) + return + + turret.debug_cycle_index = (turret.debug_cycle_index % length(turret.accepted_hardpoints)) + 1 + var/next_type = turret.accepted_hardpoints[turret.debug_cycle_index] + + var/obj/item/hardpoint/next_hardpoint = new next_type() + + var/obj/item/hardpoint/existing + for(var/obj/item/hardpoint/H in turret.hardpoints) + if(H.slot == next_hardpoint.slot) + existing = H + break + if(existing) + turret.remove_hardpoint(existing, get_turf(turret)) + qdel(existing) + + turret.add_hardpoint(next_hardpoint) + turret.owner.update_icon() + + to_chat(src, SPAN_NOTICE("Installed [next_hardpoint.name] on the turret ([turret.debug_cycle_index]/[length(turret.accepted_hardpoints)]).")) diff --git a/code/modules/vehicles/hardpoints/hardpoint.dm b/code/modules/vehicles/hardpoints/hardpoint.dm index 100c132b5b72..4199d5ee4a63 100644 --- a/code/modules/vehicles/hardpoints/hardpoint.dm +++ b/code/modules/vehicles/hardpoints/hardpoint.dm @@ -33,6 +33,11 @@ /// List of pixel offsets for each direction. var/list/px_offsets + // pivots used to calculate the correctt rotation both for secondary hardpoints and for primary hardpoints. + // because the secondary hardpoints are rotating WITH the turret, but also AROUND themselves, they need two pivots. + var/list/rotation_pivot + var/list/gimbal_pivot + /// Visual layer of hardpoint when on vehicle. var/hdpt_layer = HDPT_LAYER_WHEELS @@ -68,8 +73,27 @@ /// Used to prevent welder click spam. var/being_repaired = FALSE - /// The firing arc of this hardpoint. - var/firing_arc = 0 //in degrees. 0 skips whole arc of fire check + /// How fast this weapon can traverse. On a turret-mounted weapon, contributes to the shared turret's turn speed (see recalculate_turn_rate()). On a fixed-mount weapon, this is a static firing cone instead (see in_firing_arc()). + var/traverse_arc = 0 //in degrees. 0 skips whole arc of fire check on fixed mounts + + /// If TRUE, this hardpoint runs its own independent mouse-aim rotation (current_angle/rotation_loop below) instead of following its holder's rotate() cascade - see /obj/item/hardpoint/secondary/proc/toggle_slaved_to_driver(). + var/self_gimballed = FALSE + /// The hardpoint's live, continuously-simulated facing (0-360, north-clockwise). Distinct from dir, which stays snapped to a cardinal for sprite selection. Only meaningful for the turret itself, or a self_gimballed hardpoint. + var/current_angle = 0 + /// Where the mouse currently wants this hardpoint to face (0-360, north-clockwise). current_angle chases this. + var/desired_angle = 0 + /// Current turn speed, in degrees per tick, ramped toward max_angular_velocity. + var/angular_velocity = 0 + /// Turn speed cap. For the turret, derived from the median traverse_arc of mounted weapons (see recalculate_turn_rate()). For a self_gimballed weapon, derived from its own traverse_arc. + var/max_angular_velocity = TURRET_DEFAULT_ANGULAR_VELOCITY + /// How fast angular_velocity ramps up/down, in degrees per tick^2. + var/angular_accel = TURRET_ANGULAR_ACCEL + /// Guards against spawning more than one concurrent rotation_loop(). + var/rotation_active = FALSE + /// world.time of the last processed mouse-move, used to rate-limit update_desired_angle(). + var/last_desired_update_time = 0 + /// While TRUE, update_desired_angle() no-ops - set on whichever object get_rotation_owner() resolves + /// to while a track_and_charge() lock-on is in progress, so live mouse input can't fight it. // Muzzleflash var/use_muzzle_flash = FALSE @@ -96,6 +120,8 @@ var/shots_fired = 0 /// Delay before a new firing sequence can start. COOLDOWN_DECLARE(fire_cooldown) + /// Set by cancel_firing_arc_wait() to break out of wait_for_firing_arc() early. + var/fire_wait_cancelled = FALSE // Firemodes. /// Current selected firemode of the gun. @@ -169,6 +195,8 @@ /obj/item/hardpoint/proc/generate_bullet(mob/user, turf/origin_turf) var/obj/projectile/P = new projectile_type(origin_turf, create_cause_data(initial(name), user)) P.generate_bullet(new ammo.default_ammo) + P.effective_range_max = P.ammo.effective_range_max + P.effective_range_min = P.ammo.effective_range_min // Apply bullet traits from gun for(var/entry in traits_to_give) var/list/L @@ -547,14 +575,17 @@ set_target(get_turf_on_clickcatcher(object, source, params)) 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) + INVOKE_ASYNC(src, PROC_REF(fire_semiauto), object, source, params) else SEND_SIGNAL(src, COMSIG_GUN_FIRE) +// Fires a single semi-auto shot and handles its result so it can be invoked asynchronous for linters +/obj/item/hardpoint/proc/fire_semiauto(atom/target, mob/living/user, params) + var/fire_return = try_fire(target, user, params) + if(fire_return == AUTOFIRE_CONTINUE) + reset_fire() + display_ammo(user) + /// 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) if(!target) @@ -576,22 +607,76 @@ click_empty(user) return NONE - if(!in_firing_arc(target)) - to_chat(user, SPAN_WARNING("The target is not within your firing arc!")) + // stops you from firing at your own hull. prevents a bug found in testing where guns shoot straight up. + if(owner && (get_turf(target) in owner.locs)) + to_chat(user, SPAN_WARNING("Invalid target!")) return NONE - return handle_fire(target, user, params) + var/atom/original_target = target + var/obj/item/hardpoint/holder/tank_turret/turret = loc + var/turret_mounted = istype(turret) + if((self_gimballed || turret_mounted) && gun_firemode != GUN_FIREMODE_SEMIAUTO) + // Held-trigger fire modes (burst/automatic) don't wait for the turret to finish swinging onto target + var/obj/item/hardpoint/facing_source = self_gimballed ? src : turret + target = facing_source.redirect_to_current_facing(get_origin_turf(), target) + else if(!in_firing_arc(target)) + // Turret-mounted weapons wait for the turret (or their own gimbal) to finish swinging onto the target before firing + if(!turret_mounted || !wait_for_firing_arc(target, user)) + to_chat(user, SPAN_WARNING("The target is not within your firing arc!")) + return NONE + + return handle_fire(target, user, params, original_target) + +/** + * Waits for in_firing_arc(target) to become true, instead of instantly rejecting the shot. + * + * Returns TRUE once in arc and ready to fire, FALSE if the wait was cancelled or the shot became + * impossible for some other reason while waiting (weapon/target gone, broke, ran dry, trigger let go). + */ +/obj/item/hardpoint/proc/wait_for_firing_arc(atom/target, mob/user) + fire_wait_cancelled = FALSE + RegisterSignal(src, list(COMSIG_GUN_STOP_FIRE, COMSIG_GUN_INTERRUPT_FIRE), PROC_REF(cancel_firing_arc_wait)) + + . = TRUE + while(!in_firing_arc(target)) + sleep(1) + if(fire_wait_cancelled || QDELETED(src) || QDELETED(target) || health <= 0 || (ammo && ammo.current_rounds <= 0)) + . = FALSE + break + + UnregisterSignal(src, list(COMSIG_GUN_STOP_FIRE, COMSIG_GUN_INTERRUPT_FIRE)) + +/obj/item/hardpoint/proc/cancel_firing_arc_wait() + SIGNAL_HANDLER + fire_wait_cancelled = TRUE +/** + * Resolves which object actually owns continuous rotation (current_angle/desired_angle/rotation_loop) + * for this hardpoint - itself if self_gimballed, or the shared turret holder if turret-mounted. + * Mirrors the same dispatch already used implicitly by in_firing_arc()/check_gimbal_firing_arc()/ + * in_turret_firing_arc() above. + */ +/obj/item/hardpoint/proc/get_rotation_owner() + if(self_gimballed) + return src + var/obj/item/hardpoint/holder/tank_turret/turret = loc + if(istype(turret)) + return turret + return src /// Actually fires the gun, sets up the projectile and fires it. -/obj/item/hardpoint/proc/handle_fire(atom/target, mob/living/user, params) +/obj/item/hardpoint/proc/handle_fire(atom/target, mob/living/user, params, atom/original_target) + if(isnull(original_target)) + original_target = target var/turf/origin_turf = get_origin_turf() // Spawn projectile outside the vehicle hull so riders aren't hit. var/turf/spawn_turf = origin_turf if(owner && length(owner.locs) > 1) - var/list/path = get_line(origin_turf, get_turf(target)) + var/turf/target_turf = get_turf(target) + var/list/path = get_line(origin_turf, target_turf) for(var/turf/T as anything in path) - if(!(T in owner.locs)) + // preventts a bug where the projectile disappears if it occupies the same tile the projectile spawns in + if(!(T in owner.locs) && T != target_turf) spawn_turf = T break @@ -631,6 +716,10 @@ /// Adjust target based on random scatter angle. /obj/item/hardpoint/proc/simulate_scatter(obj/projectile/projectile_to_fire, atom/target, turf/curloc, turf/targloc) + // without this, clicking a point-blank target that resolves to the same turf as the muzzle + if(curloc == targloc) + return target + var/fire_angle = Get_Angle(curloc, targloc) var/total_scatter_angle = projectile_to_fire.scatter @@ -661,7 +750,14 @@ /// Determines whether something is in firing arc of a hardpoint. /obj/item/hardpoint/proc/in_firing_arc(atom/target) - if(!firing_arc || !ISINRANGE_EX(firing_arc, 0, 360)) + if(self_gimballed) + return check_gimbal_firing_arc(target) + + var/obj/item/hardpoint/holder/tank_turret/turret = loc + if(istype(turret)) + return turret.in_turret_firing_arc(src, target) + + if(!traverse_arc || !ISINRANGE_EX(traverse_arc, 0, 360)) return TRUE var/turf/muzzle_turf = get_origin_turf() @@ -677,7 +773,194 @@ else if(angle_diff > 180) angle_diff -= 360 - return abs(angle_diff) <= (firing_arc * 0.5) + return abs(angle_diff) <= (traverse_arc * 0.5) + +/** + * Firing-arc gate for a self-gimballed hardpoint (see toggle_slaved_to_driver()): current_angle + * must have caught up to within FIRING_GATE_TOLERANCE of the angle from the weapon's own muzzle to + * target. Same shape as /obj/item/hardpoint/holder/tank_turret/proc/in_turret_firing_arc(), but + * measured against this hardpoint's own current_angle instead of a parent turret's. + */ +/obj/item/hardpoint/proc/check_gimbal_firing_arc(atom/target) + var/turf/muzzle_turf = get_origin_turf() + var/turf/target_turf = get_turf(target) + + //same tile angle is undefined for Gett_Angle, returning FALSE to match the legacy static-arc check + if(muzzle_turf == target_turf) + return FALSE + + var/target_angle = Get_Angle(muzzle_turf, target_turf) + return abs(angle_delta(target_angle, current_angle)) <= FIRING_GATE_TOLERANCE +/** + * Ticks current_angle toward desired_angle with inertia (accelerating up to max_angular_velocity, + * decelerating on approach). Self-terminates once current_angle settles within ROTATION_SETTLE_TOLERANCE of + * desired_angle, and restarts on demand from update_desired_angle(). + */ +/obj/item/hardpoint/proc/rotation_loop() + while(TRUE) + sleep(1) + + if(QDELETED(src) || health <= 0) + angular_velocity = 0 + rotation_active = FALSE + return + + var/delta = angle_delta(desired_angle, current_angle) + if(abs(delta) < ROTATION_SETTLE_TOLERANCE) + current_angle = desired_angle + angular_velocity = 0 + drag_self_gimballed_weapons(delta) + apply_current_angle() + rotation_active = FALSE + return + + // Accelerate toward max_angular_velocity, decelerate once we're close enough to stop in time. + var/stopping_distance = (angular_velocity ** 2) / (2 * angular_accel) + if(abs(delta) <= stopping_distance) + angular_velocity = max(angular_velocity - angular_accel, 0) + else + angular_velocity = min(angular_velocity + angular_accel, max_angular_velocity) + + var/turn_sign = (delta > 0) - (delta < 0) + var/step = min(angular_velocity, abs(delta)) * turn_sign + current_angle = ((current_angle + step) % 360 + 360) % 360 + drag_self_gimballed_weapons(step) + apply_current_angle() + +/** + * No-op by default. /obj/item/hardpoint/holder/tank_turret overrides this to drag every + * self_gimballed mounted weapon's own current_angle/desired_angle along by the same delta this + * hardpoint's current_angle just moved by. + * + * Without this, a slaved weapon's current_angle stays a fixed absolute world angle. + * This would be fine, but, since we can slave the secondaries to the driver, then they also need to drag by themselves. + */ +/obj/item/hardpoint/proc/drag_self_gimballed_weapons(delta) + return + +/// Snaps a continuous angle down to the nearest 90-degree cardinal quadrant +/obj/item/hardpoint/proc/angle_to_cardinal(angle) + angle = ((angle % 360) + 360) % 360 + if(angle >= 315 || angle < 45) + return NORTH + if(angle < 135) + return EAST + if(angle < 225) + return SOUTH + return WEST + +/** + * Keeps dir/origins in sync with current_angle's quadrant, then refreshes the vehicle's sprite. + */ +/obj/item/hardpoint/proc/apply_current_angle() + if(owner) + owner.update_icon() + +/** + * Builds a matrix that rotates by tilt degrees around pivot (a local x,y point relative to the + * icon's own center) instead of the icon's default center. pivot defaults to (0,0) if null/unset. + * + * (Translate(pivot), called last, applied last) - Translate(-pivot) -> Turn(tilt) -> Translate(pivot). + */ +/obj/item/hardpoint/proc/build_tilt_matrix(tilt, list/pivot) + var/matrix/tilt_matrix = matrix() + if(pivot && (pivot[1] || pivot[2])) + tilt_matrix.Translate(-pivot[1], -pivot[2]) + tilt_matrix.Turn(tilt) + tilt_matrix.Translate(pivot[1], pivot[2]) + else + tilt_matrix.Turn(tilt) + return tilt_matrix + +/** + * Like build_tilt_matrix(), but composes two nested rotations onto one matrix instead of one: + * inner_tilt around inner_pivot is applied first (closest to the raw icon), then outter_tilt around + * outer_pivot is applied on top of that, carrying the already-tilted icon along with it. Used to + * render a self_gimballed weapon, its own idependent swivel (inner, pivoting around its own local + * mount joint, gimbal_pivot) rides along with the turret's own lean (outer, pivoting around the + * turret's own rotation center, rotation_pivot) instead of replacing it - two separate pivots of + * rotation, one nested inside the other, same as a real two-stage gimbal mountted on a turret. + */ +/obj/item/hardpoint/proc/build_nested_tilt_matrix(inner_tilt, list/inner_pivot, outer_tilt, list/outer_pivot) + var/matrix/tilt_matrix = build_tilt_matrix(inner_tilt, inner_pivot) + if(outer_tilt) + if(outer_pivot && (outer_pivot[1] || outer_pivot[2])) + tilt_matrix.Translate(-outer_pivot[1], -outer_pivot[2]) + tilt_matrix.Turn(outer_tilt) + tilt_matrix.Translate(outer_pivot[1], outer_pivot[2]) + else + tilt_matrix.Turn(outer_tilt) + return tilt_matrix + +/** + * Looks up the pivot for a given angle out of up to 8 keyed keyframes (dir constants, same + * convention as dir2angle()), blending between a cardinal and an adjacent diagonal if that + * diagonal has a tuned entry - otherwise hard-snaps to the cardinal (old angle_to_cardinal() + * behavior), so untuned weapons render unchanged. Returns null if pivot_data is empty. + * + * Uses a direct equality switch rather than angle_to_dir() that proc rounds to the NEARESTT dir + * and misresolves exact cardinal angles (0/90/180/270) to an adjacent diagonal, which a multiple of 45 avoids + */ +/obj/item/hardpoint/proc/interpolate_pivot(list/pivot_data, angle) + if(!pivot_data) + return null + + angle = ((angle % 360) + 360) % 360 + var/lower_anchor = FLOOR(angle, 45) + var/upper_anchor = (lower_anchor + 45) % 360 + + var/list/lower_pivot = LAZYACCESS(pivot_data, "[anchor_angle_to_dir(lower_anchor)]") + var/list/upper_pivot = LAZYACCESS(pivot_data, "[anchor_angle_to_dir(upper_anchor)]") + + if(!lower_pivot) + return upper_pivot + if(!upper_pivot) + return lower_pivot + + var/frac = (angle - lower_anchor) / 45 + return list( + lower_pivot[1] + (upper_pivot[1] - lower_pivot[1]) * frac, + lower_pivot[2] + (upper_pivot[2] - lower_pivot[2]) * frac, + ) + +/// Exact reverse of dir2angle() for one of the 8 cardinal/diagonal 45-degree-multiple angles - see interpolate_pivot()'s doc comment for why this can't just reuse the global angle_to_dir() nearest-neighbor helper. +/obj/item/hardpoint/proc/anchor_angle_to_dir(angle) + switch(angle) + if(0) + return NORTH + if(45) + return NORTHEAST + if(90) + return EAST + if(135) + return SOUTHEAST + if(180) + return SOUTH + if(225) + return SOUTHWEST + if(270) + return WEST + if(315) + return NORTHWEST + return NORTH + +/** + * Redirects a target to preserve its range from origin_turf but along this hardpoint's actual + * current facing (current_angle) instead of the originally-aimed direction. Used by held-ttrigger + * fire modes (see try_fire()), which don't wait for the turret (or a self-gimballed weapon's own gimbal) to finish swinging onto target. + */ +/obj/item/hardpoint/proc/redirect_to_current_facing(turf/origin_turf, atom/target) + var/turf/target_turf = get_turf(target) + if(!origin_turf || !target_turf || origin_turf == target_turf) + return target + + // Once the barrel has fully caught up to its own tracked aim point, there's no + // rotational lag left to simulate. fire directly at the target. + if(abs(angle_delta(desired_angle, current_angle)) <= FIRING_GATE_TOLERANCE) + return target + + var/range = get_dist_euclidian(origin_turf, target_turf) + return get_angle_target_turf(origin_turf, current_angle, range) //----------------------------- //------ICON PROCS---------- diff --git a/code/modules/vehicles/hardpoints/holder/holder.dm b/code/modules/vehicles/hardpoints/holder/holder.dm index a1797cb68b8b..f85538dc3ef4 100644 --- a/code/modules/vehicles/hardpoints/holder/holder.dm +++ b/code/modules/vehicles/hardpoints/holder/holder.dm @@ -142,6 +142,11 @@ H.on_uninstall(owner) H.reset_rotation() + + if(H.self_gimballed) + H.self_gimballed = FALSE + H.allowed_seat = initial(H.allowed_seat) + hardpoints -= H H.owner = null diff --git a/code/modules/vehicles/hardpoints/holder/tank_turret.dm b/code/modules/vehicles/hardpoints/holder/tank_turret.dm index edb119cc1327..6156fafb3842 100644 --- a/code/modules/vehicles/hardpoints/holder/tank_turret.dm +++ b/code/modules/vehicles/hardpoints/holder/tank_turret.dm @@ -92,6 +92,40 @@ return I +/** + * Layers a continuous visual tilt on top of the turret's (and every mounted weapon's) cardinal + * sprite. The base dir still snaps every 90 degrees for sprite selection. + */ +/obj/item/hardpoint/holder/tank_turret/get_hardpoint_image() + var/list/images = ..() + + var/tilt = angle_delta(current_angle, dir2angle(dir)) + + var/image/turret_image = images[1] + if(turret_image && tilt) + turret_image.transform = build_tilt_matrix(tilt, interpolate_pivot(rotation_pivot, current_angle)) + + var/image_index = 2 + for(var/obj/item/hardpoint/weapon in hardpoints) + var/image/weapon_image = images[image_index] + image_index++ + if(!weapon_image) + continue + + // A self_gimballed weapon needs TWO nested rotations around TWO genuinely different pivots. + // Collapsing these into one combined rotation only works if both + // pivots happen to coincide, which isn't the case. + if(weapon.self_gimballed) + var/own_tilt = angle_delta(weapon.current_angle, current_angle) + if(own_tilt || tilt) + var/list/inner_pivot = weapon.interpolate_pivot(weapon.gimbal_pivot, current_angle) || weapon.interpolate_pivot(weapon.rotation_pivot, current_angle) + var/list/outer_pivot = weapon.interpolate_pivot(weapon.rotation_pivot, current_angle) + weapon_image.transform = weapon.build_nested_tilt_matrix(own_tilt, inner_pivot, tilt, outer_pivot) + else if(tilt) + weapon_image.transform = build_tilt_matrix(tilt, weapon.interpolate_pivot(weapon.rotation_pivot, current_angle)) + + return images + // no picking this big beast up /obj/item/hardpoint/holder/tank_turret/attack_hand(mob/user) return @@ -141,30 +175,82 @@ gyro = !gyro to_chat(user, SPAN_NOTICE("You toggle \the [src]'s gyroscopic stabilizer [gyro ? "ON" :"OFF"].")) -/obj/item/hardpoint/holder/tank_turret/proc/user_rotation(mob/user, deg) - // no rotating a broken turret - if(health <= 0) - return +/// Keeps dir/origins in sync with current_angle's quadrant, then refreshes the vehicle's sprite. +/obj/item/hardpoint/holder/tank_turret/apply_current_angle() + var/target_cardinal = angle_to_cardinal(current_angle) + if(target_cardinal != dir) + rotate(turning_angle(dir, target_cardinal), override_gyro = TRUE, sync_angle = FALSE) + owner.update_icon() - if(rotating) +// See base proc's doc comment (hardpoint.dm) - drags any self_gimballed mounted weapon's angle along with this turret's own, so it keeps riding the turret's motion instead of holding a fixed absolute world angle. +/obj/item/hardpoint/holder/tank_turret/drag_self_gimballed_weapons(delta) + if(!delta) + return + for(var/obj/item/hardpoint/mounted_weapon in hardpoints) + if(!mounted_weapon.self_gimballed) + continue + mounted_weapon.current_angle = ((mounted_weapon.current_angle + delta) % 360 + 360) % 360 + mounted_weapon.desired_angle = ((mounted_weapon.desired_angle + delta) % 360 + 360) % 360 + +/** + * Gates firing for a weapon mounted on this turret. current_angle must have caught up to within + * FIRING_GATE_TOLERANCE of the angle from tthe weapon's own muzzle to target. + */ +/obj/item/hardpoint/holder/tank_turret/proc/in_turret_firing_arc(obj/item/hardpoint/weapon, atom/target) + var/turf/muzzle_turf = weapon.get_origin_turf() + var/turf/target_turf = get_turf(target) + + //same tile angle is undefined for Get_Angle, returning FALSE to match the legacy static-arc check + if(muzzle_turf == target_turf) + return FALSE + + var/target_angle = Get_Angle(muzzle_turf, target_turf) + return abs(angle_delta(target_angle, current_angle)) <= FIRING_GATE_TOLERANCE + +// Recomputes max_angular_velocity from the median traverse_arc of currently-mounted weapons. +/obj/item/hardpoint/holder/tank_turret/proc/recalculate_turn_rate() + var/list/arcs = list() + for(var/obj/item/hardpoint/mounted_weapon in hardpoints) + if(!mounted_weapon.traverse_arc) + continue + arcs += mounted_weapon.traverse_arc + + if(!length(arcs)) + max_angular_velocity = TURRET_DEFAULT_ANGULAR_VELOCITY return - rotating = TRUE - to_chat(user, SPAN_NOTICE("You begin rotating the turret towards the [dir2text(turn(dir,deg))].")) + sortTim(arcs) + var/weapon_count = length(arcs) + var/median_arc = (weapon_count % 2) ? arcs[(weapon_count + 1) / 2] : (arcs[weapon_count / 2] + arcs[weapon_count / 2 + 1]) / 2 - if(!do_after(user, rotation_windup, INTERRUPT_ALL, BUSY_ICON_GENERIC)) - rotating = FALSE - return - rotating = FALSE + max_angular_velocity = max(TURRET_BASE_ANGULAR_VELOCITY * (median_arc / TURRET_ARC_NORMALIZATION), TURRET_MIN_ANGULAR_VELOCITY) + +/obj/item/hardpoint/holder/tank_turret/add_hardpoint(obj/item/hardpoint/new_hardpoint) + . = ..() + recalculate_turn_rate() - rotate(deg, TRUE) +/obj/item/hardpoint/holder/tank_turret/remove_hardpoint(obj/item/hardpoint/removed_hardpoint, turf/uninstall_to) + . = ..() + recalculate_turn_rate() -/obj/item/hardpoint/holder/tank_turret/rotate(deg, override_gyro = FALSE) +/** + * override_gyro - bypasses the gyro lock (used by mouse-driven aiming, which should always work). + * sync_angle - whether to drag current_angle along by deg. TRUE for hull-turn cascades (rotate_hardpoints), + * FALSE when apply_current_angle() calls this to snap dir to a quadrant it already accounted for. + */ +/obj/item/hardpoint/holder/tank_turret/rotate(deg, override_gyro = FALSE, sync_angle = TRUE) if(gyro && !override_gyro) return ..(deg) + if(sync_angle) + // deg is in turn()'s rotational convention, which is the opposite + // sign of dir2angle()'s north-clockwise convention that current_angle is tracked in + // subtract, don't add, or current_angle drifts away from the direction dir actually turned. + current_angle = ((current_angle - deg) % 360 + 360) % 360 + drag_self_gimballed_weapons(-deg) + var/obj/vehicle/multitile/tank/C = owner var/obj/item/hardpoint/support/artillery_module/AM for(var/obj/item/hardpoint/support/artillery_module/A in C.hardpoints) diff --git a/code/modules/vehicles/hardpoints/primary/autocannon.dm b/code/modules/vehicles/hardpoints/primary/autocannon.dm index 8148e5357dd8..99ab163a49a5 100644 --- a/code/modules/vehicles/hardpoints/primary/autocannon.dm +++ b/code/modules/vehicles/hardpoints/primary/autocannon.dm @@ -8,7 +8,7 @@ activation_sounds = list('sound/weapons/vehicles/autocannon_fire.ogg') health = 500 - firing_arc = 60 + traverse_arc = 60 ammo = new /obj/item/ammo_magazine/hardpoint/ace_autocannon max_clips = 2 @@ -19,6 +19,12 @@ "4" = list(32, 0), "8" = list(-32, 0) ) + rotation_pivot = list( + "1" = list(0, -22), + "2" = list(0, 32), + "4" = list(-32, 0), + "8" = list(32, 0) + ) scatter = 1 gun_firemode = GUN_FIREMODE_AUTOMATIC diff --git a/code/modules/vehicles/hardpoints/primary/dual_cannon.dm b/code/modules/vehicles/hardpoints/primary/dual_cannon.dm index 9f4c7286afeb..33e483721644 100644 --- a/code/modules/vehicles/hardpoints/primary/dual_cannon.dm +++ b/code/modules/vehicles/hardpoints/primary/dual_cannon.dm @@ -12,7 +12,7 @@ damage_multiplier = 0.2 health = 500 - firing_arc = 60 + traverse_arc = 60 origins = list(0, 1) diff --git a/code/modules/vehicles/hardpoints/primary/flamer.dm b/code/modules/vehicles/hardpoints/primary/flamer.dm index 60a7979cd5fb..8119bf4bd244 100644 --- a/code/modules/vehicles/hardpoints/primary/flamer.dm +++ b/code/modules/vehicles/hardpoints/primary/flamer.dm @@ -8,17 +8,23 @@ activation_sounds = list('sound/weapons/vehicles/flamethrower.ogg') health = 400 - firing_arc = 90 + traverse_arc = 90 ammo = new /obj/item/ammo_magazine/hardpoint/primary_flamer max_clips = 1 px_offsets = list( - "1" = list(0, 21), - "2" = list(0, -32), + "1" = list(0, 27), + "2" = list(0, -26), "4" = list(32, 1), "8" = list(-32, 1) ) + rotation_pivot = list( + "1" = list(0, -27), + "2" = list(0, 26), + "4" = list(-32, -1), + "8" = list(32, -1) + ) use_muzzle_flash = FALSE diff --git a/code/modules/vehicles/hardpoints/primary/ltb.dm b/code/modules/vehicles/hardpoints/primary/ltb.dm index 6c818f30b8d2..77c4a0852c4f 100644 --- a/code/modules/vehicles/hardpoints/primary/ltb.dm +++ b/code/modules/vehicles/hardpoints/primary/ltb.dm @@ -8,7 +8,7 @@ activation_sounds = list('sound/weapons/vehicles/cannon_fire1.ogg', 'sound/weapons/vehicles/cannon_fire2.ogg') health = 500 - firing_arc = 60 + traverse_arc = 60 ammo = new /obj/item/ammo_magazine/hardpoint/ltb_cannon max_clips = 3 @@ -19,6 +19,12 @@ "4" = list(32, 0), "8" = list(-32, 0) ) + rotation_pivot = list( + "1" = list(0, -21), + "2" = list(0, 32), + "4" = list(-32, 0), + "8" = list(32, 0) + ) muzzle_flash_pos = list( "1" = list(0, 59), diff --git a/code/modules/vehicles/hardpoints/primary/minigun.dm b/code/modules/vehicles/hardpoints/primary/minigun.dm index fd1f81990445..7d6e4c0d59d5 100644 --- a/code/modules/vehicles/hardpoints/primary/minigun.dm +++ b/code/modules/vehicles/hardpoints/primary/minigun.dm @@ -7,7 +7,7 @@ disp_icon_state = "ltaaap_minigun" health = 350 - firing_arc = 90 + traverse_arc = 90 ammo = new /obj/item/ammo_magazine/hardpoint/ltaaap_minigun max_clips = 1 diff --git a/code/modules/vehicles/hardpoints/secondary/cupola.dm b/code/modules/vehicles/hardpoints/secondary/cupola.dm index cf32340070b0..7914be4a5a2e 100644 --- a/code/modules/vehicles/hardpoints/secondary/cupola.dm +++ b/code/modules/vehicles/hardpoints/secondary/cupola.dm @@ -8,11 +8,18 @@ activation_sounds = list('sound/weapons/gun_smartgun1.ogg', 'sound/weapons/gun_smartgun2.ogg', 'sound/weapons/gun_smartgun3.ogg', 'sound/weapons/gun_smartgun4.ogg') health = 350 - firing_arc = 120 + traverse_arc = 120 ammo = new /obj/item/ammo_magazine/hardpoint/m56_cupola max_clips = 1 + gimbal_pivot = list( + "1" = list(8, 4), + "2" = list(-7, 11), + "4" = list(-6, 6), + "8" = list(5, 24) + ) + muzzle_flash_pos = list( "1" = list(8, -1), "2" = list(-7, -15), diff --git a/code/modules/vehicles/hardpoints/secondary/flamer.dm b/code/modules/vehicles/hardpoints/secondary/flamer.dm index 56a9995b60c5..6039a487e605 100644 --- a/code/modules/vehicles/hardpoints/secondary/flamer.dm +++ b/code/modules/vehicles/hardpoints/secondary/flamer.dm @@ -8,7 +8,7 @@ activation_sounds = list('sound/weapons/vehicles/flamethrower.ogg') health = 300 - firing_arc = 120 + traverse_arc = 120 ammo = new /obj/item/ammo_magazine/hardpoint/secondary_flamer max_clips = 1 @@ -23,6 +23,19 @@ "4" = list(3, 0), "8" = list(-3, 18) ) + rotation_pivot = list( + "1" = list(-2, -14), + "2" = list(2, -3), + "4" = list(-3, 0), + "8" = list(3, -18) + ) + + gimbal_pivot = list( + "1" = list(6, -6), + "2" = list(-6, 6), + "4" = list(-6, 6), + "8" = list(6, 6) + ) scatter = 6 fire_delay = 3.0 SECONDS diff --git a/code/modules/vehicles/hardpoints/secondary/frontal_cannon.dm b/code/modules/vehicles/hardpoints/secondary/frontal_cannon.dm index 57ce6e2e5ff8..9ca5ec2a3237 100644 --- a/code/modules/vehicles/hardpoints/secondary/frontal_cannon.dm +++ b/code/modules/vehicles/hardpoints/secondary/frontal_cannon.dm @@ -11,7 +11,7 @@ damage_multiplier = 0.11 health = 350 - firing_arc = 120 + traverse_arc = 120 origins = list(0, -1) diff --git a/code/modules/vehicles/hardpoints/secondary/grenade_launcher.dm b/code/modules/vehicles/hardpoints/secondary/grenade_launcher.dm index ee06e7405e07..486350bee25b 100644 --- a/code/modules/vehicles/hardpoints/secondary/grenade_launcher.dm +++ b/code/modules/vehicles/hardpoints/secondary/grenade_launcher.dm @@ -8,7 +8,7 @@ activation_sounds = list('sound/weapons/gun_m92_attachable.ogg') health = 500 - firing_arc = 90 + traverse_arc = 120 ammo = new /obj/item/ammo_magazine/hardpoint/tank_glauncher max_clips = 3 @@ -22,6 +22,20 @@ "8" = list(-6, 17) ) + rotation_pivot = list( + "1" = list(0, -17), + "2" = list(0, 0), + "4" = list(-6, 0), + "8" = list(6, -17) + ) + + gimbal_pivot = list( + "1" = list(6, -6), + "2" = list(-6, 6), + "4" = list(-6, 6), + "8" = list(6, 6) + ) + scatter = 10 fire_delay = 3.0 SECONDS diff --git a/code/modules/vehicles/hardpoints/secondary/secondary.dm b/code/modules/vehicles/hardpoints/secondary/secondary.dm index 366c7fcfa9fb..61114ca14c04 100644 --- a/code/modules/vehicles/hardpoints/secondary/secondary.dm +++ b/code/modules/vehicles/hardpoints/secondary/secondary.dm @@ -7,3 +7,48 @@ damage_multiplier = 0.125 activatable = TRUE + +/** + * Toggles control of this secondary between the gunner (default,, follows the turret's shared + * rotation like any other mounted weapon) and the driver (self-gimballed, its own independent unrestricted 360-degree mouse-aim + */ +/obj/item/hardpoint/secondary/proc/toggle_slaved_to_driver(mob/user) + if(health <= 0) + to_chat(user, SPAN_WARNING("\The [src]'s controls are busted!")) + return + + var/obj/item/hardpoint/holder/tank_turret/turret = loc + if(!istype(turret)) + return + + self_gimballed = !self_gimballed + SEND_SIGNAL(src, COMSIG_GUN_INTERRUPT_FIRE) + + if(self_gimballed) + allowed_seat = VEHICLE_DRIVER + max_angular_velocity = max(TURRET_BASE_ANGULAR_VELOCITY * (traverse_arc / TURRET_ARC_NORMALIZATION), TURRET_MIN_ANGULAR_VELOCITY) + current_angle = dir2angle(dir) + desired_angle = current_angle + angular_velocity = 0 + rotation_active = FALSE + + if(owner.active_hp[VEHICLE_GUNNER] == src) + owner.active_hp[VEHICLE_GUNNER] = null + + to_chat(user, SPAN_NOTICE("You slave \the [src]'s controls to the driver's seat.")) + var/mob/driver = owner.seats[VEHICLE_DRIVER] + if(driver) + to_chat(driver, SPAN_NOTICE("\The [src] has been slaved to your controls. Use \"Cycle Active Hardpoint\" to select it.")) + else + allowed_seat = initial(allowed_seat) + + if(owner.active_hp[VEHICLE_DRIVER] == src) + owner.active_hp[VEHICLE_DRIVER] = null + + // dir already always mirrors the turret's own (unaffected by self_gimballed) so just settle current_angle back to zero swivel. + current_angle = dir2angle(dir) + desired_angle = current_angle + angular_velocity = 0 + rotation_active = FALSE + + to_chat(user, SPAN_NOTICE("You return \the [src]'s controls to the gunner's seat.")) diff --git a/code/modules/vehicles/hardpoints/secondary/tow.dm b/code/modules/vehicles/hardpoints/secondary/tow.dm index c15f8aa1ef63..2247a6f80d96 100644 --- a/code/modules/vehicles/hardpoints/secondary/tow.dm +++ b/code/modules/vehicles/hardpoints/secondary/tow.dm @@ -7,7 +7,7 @@ disp_icon_state = "towlauncher" health = 500 - firing_arc = 60 + traverse_arc = 120 ammo = new /obj/item/ammo_magazine/hardpoint/towlauncher max_clips = 1 @@ -19,6 +19,20 @@ "8" = list(0, 18) ) + rotation_pivot = list( + "1" = list(-1, -10), + "2" = list(1, -5), + "4" = list(0, 0), + "8" = list(0, -18) + ) + + gimbal_pivot = list( + "1" = list(6, -6), + "2" = list(-6, 6), + "4" = list(-6, 6), + "8" = list(6, 6) + ) + muzzle_flash_pos = list( "1" = list(8, -1), "2" = list(-8, -16), diff --git a/code/modules/vehicles/hardpoints/special/firing_port_weapon.dm b/code/modules/vehicles/hardpoints/special/firing_port_weapon.dm index d9d68e0af237..9e3688d972a8 100644 --- a/code/modules/vehicles/hardpoints/special/firing_port_weapon.dm +++ b/code/modules/vehicles/hardpoints/special/firing_port_weapon.dm @@ -10,7 +10,7 @@ activation_sounds = list('sound/weapons/gun_smartgun1.ogg', 'sound/weapons/gun_smartgun2.ogg', 'sound/weapons/gun_smartgun3.ogg', 'sound/weapons/gun_smartgun4.ogg') health = 100 - firing_arc = 120 + traverse_arc = 120 //FPWs reload automatically var/reloading = FALSE var/reload_time = 10 SECONDS diff --git a/code/modules/vehicles/hardpoints/support/flare.dm b/code/modules/vehicles/hardpoints/support/flare.dm index 0fa37de34d2f..bfc36e9d54b3 100644 --- a/code/modules/vehicles/hardpoints/support/flare.dm +++ b/code/modules/vehicles/hardpoints/support/flare.dm @@ -13,7 +13,7 @@ activatable = TRUE health = 500 - firing_arc = 120 + traverse_arc = 120 ammo = new /obj/item/ammo_magazine/hardpoint/flare_launcher max_clips = 3 diff --git a/code/modules/vehicles/multitile/multitile_interaction.dm b/code/modules/vehicles/multitile/multitile_interaction.dm index d3106ea8183c..d069dca5861f 100644 --- a/code/modules/vehicles/multitile/multitile_interaction.dm +++ b/code/modules/vehicles/multitile/multitile_interaction.dm @@ -373,9 +373,10 @@ RegisterSignal(user, COMSIG_MOB_MOUSEDOWN, PROC_REF(crew_mousedown)) RegisterSignal(user, COMSIG_MOB_MOUSEDRAG, PROC_REF(crew_mousedrag)) RegisterSignal(user, COMSIG_MOB_MOUSEUP, PROC_REF(crew_mouseup)) + RegisterSignal(user, COMSIG_MOB_MOUSEMOVE, PROC_REF(crew_mousemove)) /obj/vehicle/multitile/on_unset_interaction(mob/user) - UnregisterSignal(user, list(COMSIG_MOB_MOUSEUP, COMSIG_MOB_MOUSEDOWN, COMSIG_MOB_MOUSEDRAG)) + UnregisterSignal(user, list(COMSIG_MOB_MOUSEUP, COMSIG_MOB_MOUSEDOWN, COMSIG_MOB_MOUSEDRAG, COMSIG_MOB_MOUSEMOVE)) var/obj/item/hardpoint/hardpoint = get_mob_hp(user) if(hardpoint) @@ -399,6 +400,43 @@ hardpoint.change_target(source, src_object, over_object, src_location, over_location, src_control, over_control, params) + // BYOND doesn't send MouseMove while a button is held - MouseDrag takes over. Feed it into + // the same tracking patths so aiming doesn't freeze while the crew is firing/holding M1 + track_gunner_mouse(source, over_object, params) + track_driver_mouse(source, over_object, params) + +/// Relays live cursor position to the gunner's turret / driver's slaved secondary, if any, so they can track the mouse. +/obj/vehicle/multitile/proc/crew_mousemove(datum/source, atom/object, turf/location, control, params) + SIGNAL_HANDLER + track_gunner_mouse(source, object, params) + track_driver_mouse(source, object, params) + +/// Updates the gunner's turret (if any) to aim toward the given screen position. +/obj/vehicle/multitile/proc/track_gunner_mouse(mob/source, atom/object, params) + if(get_mob_seat(source) != VEHICLE_GUNNER) + return + + var/obj/item/hardpoint/holder/tank_turret/turret = locate() in hardpoints + if(!turret) + return + + turret.update_desired_angle(object, source, params) + +/// Updates the driver's slaved secondary (if any) to aim toward the given screen position. +/obj/vehicle/multitile/proc/track_driver_mouse(mob/source, atom/object, params) + if(get_mob_seat(source) != VEHICLE_DRIVER) + return + + var/obj/item/hardpoint/holder/tank_turret/turret = locate() in hardpoints + if(!turret) + return + + for(var/obj/item/hardpoint/secondary/secondary_weapon in turret.hardpoints) + if(!secondary_weapon.self_gimballed) + continue + secondary_weapon.update_desired_angle(object, source, params) + return + /// Checks for special control keybinds, else relays crew mouse press to active hardpoint. /obj/vehicle/multitile/proc/crew_mousedown(datum/source, atom/object, turf/location, control, params) SIGNAL_HANDLER diff --git a/code/modules/vehicles/multitile/multitile_verbs.dm b/code/modules/vehicles/multitile/multitile_verbs.dm index df53803f9f78..89a5f5329c4d 100644 --- a/code/modules/vehicles/multitile/multitile_verbs.dm +++ b/code/modules/vehicles/multitile/multitile_verbs.dm @@ -242,6 +242,57 @@ return T.toggle_gyro(usr) +//hands control of the secondary hardpoint (aim + fire) between the gunner and the driver +/obj/vehicle/multitile/proc/toggle_slave_secondary_to_driver() + set name = "Slave Secondary to Driver" + set desc = "Toggles handing control of the secondary weapon's aim and fire between yourself and the driver." + set category = "Vehicle" + + var/mob/user = usr + if(!user || !istype(user)) + return + + var/obj/vehicle/multitile/vehicle = user.interactee + if(!istype(vehicle)) + return + + var/obj/item/hardpoint/holder/tank_turret/turret = locate() in vehicle.hardpoints + if(!turret) + return + + var/obj/item/hardpoint/secondary/secondary_weapon = null + for(var/obj/item/hardpoint/secondary/candidate in turret.hardpoints) + if(!candidate.is_activatable()) // passive hardpoints (e.g. the mounted flag) can't be slaved/selected + continue + secondary_weapon = candidate + break + if(!secondary_weapon) + to_chat(user, SPAN_WARNING("No secondary weapon installed.")) + return + + secondary_weapon.toggle_slaved_to_driver(usr) + +//toggles the fire mode for hardpoints. currently only used for the primary and secondary flamers to switch between glob and stream +/obj/vehicle/multitile/proc/toggle_hardpoint_fire_mode() + set name = "Toggle Hardpoint Fire Mode" + set desc = "Toggles the fire mode of your currently selected hardpoint, if it supports one." + set category = "Vehicle" + + var/mob/user = usr + if(!user || !istype(user)) + return + + var/obj/vehicle/multitile/vehicle = user.interactee + if(!istype(vehicle)) + return + + var/obj/item/hardpoint/selected_hardpoint = vehicle.get_mob_hp(user) + if(!selected_hardpoint) + to_chat(user, SPAN_WARNING("You have no hardpoint selected.")) + return + + selected_hardpoint.toggle_fire_mode(usr) + //single use verb that allows VCs to add a nickname in "" at the end of their vehicle name /obj/vehicle/multitile/proc/name_vehicle() set name = "Name Vehicle" diff --git a/code/modules/vehicles/tank/tank.dm b/code/modules/vehicles/tank/tank.dm index c82566234c97..2b5f7e7822b4 100644 --- a/code/modules/vehicles/tank/tank.dm +++ b/code/modules/vehicles/tank/tank.dm @@ -121,11 +121,13 @@ add_verb(M.client, list( /obj/vehicle/multitile/proc/toggle_door_lock, /obj/vehicle/multitile/proc/activate_horn, + /obj/vehicle/multitile/proc/cycle_hardpoint, )) else if(seat == VEHICLE_GUNNER) add_verb(M.client, list( /obj/vehicle/multitile/proc/cycle_hardpoint, /obj/vehicle/multitile/proc/toggle_gyrostabilizer, + /obj/vehicle/multitile/proc/toggle_slave_secondary_to_driver, )) @@ -143,11 +145,13 @@ remove_verb(M.client, list( /obj/vehicle/multitile/proc/toggle_door_lock, /obj/vehicle/multitile/proc/activate_horn, + /obj/vehicle/multitile/proc/cycle_hardpoint, )) else if(seat == VEHICLE_GUNNER) remove_verb(M.client, list( /obj/vehicle/multitile/proc/cycle_hardpoint, /obj/vehicle/multitile/proc/toggle_gyrostabilizer, + /obj/vehicle/multitile/proc/toggle_slave_secondary_to_driver, )) //Called when players try to move vehicle @@ -181,21 +185,8 @@ revalidate_on_top() return TRUE - if(user != seats[VEHICLE_GUNNER]) - return FALSE - var/obj/item/hardpoint/holder/tank_turret/T = null - for(var/obj/item/hardpoint/holder/tank_turret/TT in hardpoints) - T = TT - break - if(!T) - return FALSE - - if(direction == GLOB.reverse_dir[T.dir] || direction == T.dir) - return FALSE - - T.user_rotation(user, turning_angle(T.dir, direction)) - update_icon() - return TRUE + // Gunner turret aiming is mouse-driven now (see crew_mousemove() in multitile_interaction.dm). + return FALSE // !!!! No point in keeping this now that you can freely climb onto the tank. !!!! // Unless, maybe, you end up stuck in the center (turret) tile. diff --git a/colonialmarines.dme b/colonialmarines.dme index e27966ef6ad4..e0ee0996a85d 100644 --- a/colonialmarines.dme +++ b/colonialmarines.dme @@ -2113,6 +2113,7 @@ #include "code\modules\mob\living\brain\MMI.dm" #include "code\modules\mob\living\brain\say.dm" #include "code\modules\mob\living\carbon\carbon.dm" +#include "code\modules\mob\living\carbon\carbon_debug_verbs.dm" #include "code\modules\mob\living\carbon\carbon_defines.dm" #include "code\modules\mob\living\carbon\carbon_helpers.dm" #include "code\modules\mob\living\carbon\give.dm" diff --git a/tgui/packages/tgui/interfaces/TankPivotTuner.tsx b/tgui/packages/tgui/interfaces/TankPivotTuner.tsx new file mode 100644 index 000000000000..3d74d3bd4856 --- /dev/null +++ b/tgui/packages/tgui/interfaces/TankPivotTuner.tsx @@ -0,0 +1,119 @@ +import type { BooleanLike } from 'common/react'; +import { useBackend } from 'tgui/backend'; +import { Button, LabeledList, Section, Stack } from 'tgui/components'; +import { Window } from 'tgui/layouts'; + +type Weapon = { + ref: string; + name: string; + pivot_x: number; + pivot_y: number; + selected: BooleanLike; +}; + +type Data = { + dir_name: string; + weapons: Weapon[]; + selected_name: string; + tuning_label: string; + pivot_x: number; + pivot_y: number; + offset_x: number; + offset_y: number; + is_gimballed: BooleanLike; + gimbal_pivot_x: number; + gimbal_pivot_y: number; +}; + +const AXIS_STEPS = [-10, -1, 1, 10]; + +const AxisRow = (props: { + axis: 'x' | 'y'; + value: number; + target: 'rotation' | 'gimbal' | 'offset'; +}) => { + const { act } = useBackend(); + const { axis, value, target } = props; + return ( + + + {AXIS_STEPS.map((step) => ( + + + + ))} + + + ); +}; + +export const TankPivotTuner = (props) => { + const { act, data } = useBackend(); + const { + dir_name, + weapons = [], + selected_name, + tuning_label, + pivot_x, + pivot_y, + offset_x, + offset_y, + is_gimballed, + gimbal_pivot_x, + gimbal_pivot_y, + } = data; + + return ( + + +
+ + {weapons.map((weaponEntry) => ( + + + + ))} + +
+ {selected_name && ( +
+ + + + +
+ )} + {selected_name && ( +
+ + + + +
+ )} + {selected_name && is_gimballed && ( +
+ + + + +
+ )} +
+
+ ); +}; From 66ac197dae8e9914f49f10a0e864545cc7af3879 Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Sat, 4 Jul 2026 18:19:33 -0300 Subject: [PATCH 40/60] LTB Rework: Lock-on Mechanic & Sniper-like Tracking --- code/modules/vehicles/hardpoints/hardpoint.dm | 150 ++++++++++++++++ .../vehicles/hardpoints/primary/ltb.dm | 165 ++++++++++++++++++ 2 files changed, 315 insertions(+) diff --git a/code/modules/vehicles/hardpoints/hardpoint.dm b/code/modules/vehicles/hardpoints/hardpoint.dm index 4199d5ee4a63..fd99634ead0b 100644 --- a/code/modules/vehicles/hardpoints/hardpoint.dm +++ b/code/modules/vehicles/hardpoints/hardpoint.dm @@ -94,6 +94,13 @@ var/last_desired_update_time = 0 /// While TRUE, update_desired_angle() no-ops - set on whichever object get_rotation_owner() resolves /// to while a track_and_charge() lock-on is in progress, so live mouse input can't fight it. + var/aim_locked = FALSE + /// Guards against spawning more than one concurrent track_and_charge() on this hardpoint. + var/charging = FALSE + /// Set by cancel_charge() (COMSIG_GUN_STOP_FIRE/COMSIG_GUN_INTERRUPT_FIRE) to abort an in-progress + /// track_and_charge() loop. Distinct from fire_wait_cancelled, which guards a different wait + /// (arc-entry, not an active charge) and could otherwise interfere if both were active at once. + var/charge_cancelled = FALSE // Muzzleflash var/use_muzzle_flash = FALSE @@ -663,6 +670,97 @@ if(istype(turret)) return turret return src + +/// Single choke point for locking/unlocking mouse-driven aim on whichever object get_rotation_owner() +/// resolves to, so ttrack_and_charge() below can lock/unlock symmetrically. +/obj/item/hardpoint/proc/lock_rotation(lock) + get_rotation_owner().aim_locked = lock + +/** + * Locks onto target and keeps re-aiming at its live position for aim_time, cancellable if the target + * drifts further behind the current facing than this hardpoint's own traverse_arc allows, giving the + * mount a litttle bit of allowance to catch up before the shot is considered lost + * + * Recomputes the true world-space angle to the target fresh every tick from this hardpoint's current + * position, so it needs no special-casing for the vehicle itself moving/turning mid-charge + * + * + * Returns TRUE if the charge completed with the target still valid and in arc, FALSE if cancelled. + */ +/obj/item/hardpoint/proc/track_and_charge(atom/target, mob/living/user, aim_time) + if(charging) + return FALSE + charging = TRUE + + set_target(target) + lock_rotation(TRUE) + charge_cancelled = FALSE + RegisterSignal(src, list(COMSIG_GUN_STOP_FIRE, COMSIG_GUN_INTERRUPT_FIRE), PROC_REF(cancel_charge)) + start_aim_visuals(target, user) + + . = TRUE + var/elapsed = 0 + while(elapsed < aim_time) + sleep(1) + elapsed += 1 + + if(charge_cancelled || QDELETED(src) || QDELETED(target) || health <= 0 || (ammo && ammo.current_rounds <= 0)) + . = FALSE + break + + var/turf/origin_turf = get_origin_turf() + var/turf/target_turf = get_turf(target) + if(origin_turf == target_turf) + . = FALSE + break + + // Angle uses the target atom itself, not target_turf + var/angle_to_target = Get_Angle_Grounded(origin_turf, target) + var/obj/item/hardpoint/rotation_owner = get_rotation_owner() + var/obj/item/hardpoint/holder/tank_turret/mount = self_gimballed ? loc : null + + if(istype(mount)) // self-gimballed, mounted on a turret + var/turret_facing = mount.current_angle + var/raw_delta = angle_delta(angle_to_target, turret_facing) + if(abs(raw_delta) > SLAVED_GIMBAL_ARC_HALF_WIDTH) + to_chat(user, SPAN_WARNING("Target moved out of the firing arc!")) + . = FALSE + break + var/swing = clamp(raw_delta, -SLAVED_GIMBAL_ARC_HALF_WIDTH, SLAVED_GIMBAL_ARC_HALF_WIDTH) + rotation_owner.desired_angle = ((turret_facing + swing) % 360 + 360) % 360 + else + rotation_owner.desired_angle = angle_to_target + if(traverse_arc && abs(angle_delta(angle_to_target, rotation_owner.current_angle)) > traverse_arc * 0.5) + to_chat(user, SPAN_WARNING("Target moved out of the firing arc!")) + . = FALSE + break + + rotation_owner.start_rotation_if_needed() + on_track_tick(target) + + UnregisterSignal(src, list(COMSIG_GUN_STOP_FIRE, COMSIG_GUN_INTERRUPT_FIRE)) + end_aim_visuals(target, user, .) + lock_rotation(FALSE) + charging = FALSE + +/obj/item/hardpoint/proc/cancel_charge() + SIGNAL_HANDLER + charge_cancelled = TRUE + +/// No-op hook - override to spawn lock-on visuals (overlays/beams) when a track_and_charge() begins. +// currently overriden by LTB and mounted BRUTE launcher only. Same goes for the other hooks below. - BWSB +/obj/item/hardpoint/proc/start_aim_visuals(atom/target, mob/living/user) + return + +/// No-op hook - override to tear down whatever start_aim_visuals() spawned. Always called, success or not. +/obj/item/hardpoint/proc/end_aim_visuals(atom/target, mob/living/user, success) + return + +/// No-op hook - called once per tick during track_and_charge(), after desired_angle has been updated. +/// Override to keep any tick-dependent visuals (e.g. a beam anchor) following the live muzzle position. +/obj/item/hardpoint/proc/on_track_tick(atom/target) + return + /// Actually fires the gun, sets up the projectile and fires it. /obj/item/hardpoint/proc/handle_fire(atom/target, mob/living/user, params, atom/original_target) if(isnull(original_target)) @@ -791,6 +889,58 @@ var/target_angle = Get_Angle(muzzle_turf, target_turf) return abs(angle_delta(target_angle, current_angle)) <= FIRING_GATE_TOLERANCE + +/** + * records where the mouse currently wants this hardpoint to face. Called on every processed + * mouse-move while a relevant crew member is seated. + * + * A self_gimballed weapon's desired_angle gets clamped to within SLAVED_GIMBAL_ARC_HALF_WIDTH + * degrees of the turret's own current continuous facing. the turret can sit anywhere within its own lean, e.g. 168 + * degrees, and the allowed swing needs to follow that actual angle, not snap to whichever cardinal it's nearest to. + */ +/obj/item/hardpoint/proc/update_desired_angle(atom/object, mob/user, params) + if(health <= 0) + return + if(aim_locked) // a track_and_charge() lock-on is in progress - mouse input can't fight it + return + if(world.time == last_desired_update_time) // collapses same-tick MouseMove spam into one update + return + last_desired_update_time = world.time + + // Aim at the hovered mob/object itself, not its bare turf + var/atom/aim_target + if(ismob(object) || isobj(object)) + aim_target = object + else + aim_target = get_turf_on_clickcatcher(object, user, params) + if(!aim_target) + return + var/turf/target_turf = get_turf(aim_target) + var/turf/origin_turf = get_origin_turf() + if(!origin_turf || target_turf == origin_turf) + return + + // Using the raw pixel-precise angle here aims at a tall sprite's visual midpoint instead of the tile it's + // actually standing on, which is what actually needs to be hit. very shittty bug to fix during testing + // which made the autocannon and minigun DPS plummet. + desired_angle = Get_Angle_Grounded(origin_turf, aim_target) + + if(self_gimballed) + var/obj/item/hardpoint/holder/tank_turret/turret = loc + if(istype(turret)) + var/turret_facing = turret.current_angle + var/swing = clamp(angle_delta(desired_angle, turret_facing), -SLAVED_GIMBAL_ARC_HALF_WIDTH, SLAVED_GIMBAL_ARC_HALF_WIDTH) + desired_angle = ((turret_facing + swing) % 360 + 360) % 360 + + start_rotation_if_needed() + +/obj/item/hardpoint/proc/start_rotation_if_needed() + if(rotation_active) + return + rotation_active = TRUE + spawn(0) + rotation_loop() + /** * Ticks current_angle toward desired_angle with inertia (accelerating up to max_angular_velocity, * decelerating on approach). Self-terminates once current_angle settles within ROTATION_SETTLE_TOLERANCE of diff --git a/code/modules/vehicles/hardpoints/primary/ltb.dm b/code/modules/vehicles/hardpoints/primary/ltb.dm index 77c4a0852c4f..f70dee7f1a67 100644 --- a/code/modules/vehicles/hardpoints/primary/ltb.dm +++ b/code/modules/vehicles/hardpoints/primary/ltb.dm @@ -1,3 +1,9 @@ +/// Low end of the LTB's aiming laser opacity ramp +#define LTB_LASER_MIN_ALPHA 50 + +/// Multiply-tint applied to the "dark" phase of the flicker +#define LTB_LASER_DARK_TINT "#787878" + /obj/item/hardpoint/primary/cannon name = "\improper LTB Cannon" desc = "A primary cannon for tanks that shoots explosive rounds." @@ -35,3 +41,162 @@ scatter = 2 fire_delay = 20.0 SECONDS + /// How long the lock-on charge takes before firing is allowed. + var/f_aiming_time = 4 SECONDS + /// Stashed for generate_bullet() below, which doesn't receive target directly. + var/atom/homing_target + + var/image/lockon_icon + var/image/lockon_direction_icon + var/datum/beam/laser_beam + var/obj/effect/beam_anchor/anchor + /// Incrementted every start_aim_visuals(), lets a stale cycle_laser_color() invocation from a + /// previous charge recognize it's outdated and stop, even if a new charge starts before it wakes + /// up from its own sleep() and notices laser beam has already been replaced. fixes a very annoying bug where more than one reticle can appear. + var/aim_generation = 0 + /// world.time the current charge started - used to ramp the beam's opacity up gradually across the whole charge + var/charge_start_time = 0 + +/** + * Same lock-on/tracking charge as the secondary BRUTE launcher. Targets any living mob, blocked by the same things Aimed Shot is + * (dense+opaque turfs, opaque objects, obscuring smoke... + * The fired round homes in on the target same as Aimed Shot's own /datum/component/homing_projectile. + */ +/obj/item/hardpoint/primary/cannon/handle_fire(atom/target, mob/living/user, params) + if(charging) + return NONE + + if(!isliving(target)) + to_chat(user, SPAN_WARNING("Invalid target!")) + return NONE + + var/mob/living/living_target = target + if(living_target.is_dead()) + to_chat(user, SPAN_WARNING("Invalid target!")) + return NONE + + if(check_shot_is_blocked(target)) + to_chat(user, SPAN_WARNING("Target obscured!")) + return NONE + + if(!track_and_charge(target, user, f_aiming_time)) + return NONE + + // Re-validate once more after the charge + if(QDELETED(target) || living_target.is_dead() || check_shot_is_blocked(target)) + to_chat(user, SPAN_WARNING("You lose the shot!")) + return NONE + + homing_target = target + . = ..() + homing_target = null + return . + +/obj/item/hardpoint/primary/cannon/generate_bullet(mob/user, turf/origin_turf) + var/obj/projectile/fired_projectile = ..() + if(homing_target) + fired_projectile.AddComponent(/datum/component/homing_projectile, homing_target, user) + return fired_projectile + +/obj/item/hardpoint/primary/cannon/proc/check_shot_is_blocked(atom/target) + var/turf/origin_turf = get_origin_turf() + var/turf/target_turf = get_turf(target) + var/list/turf/path = get_line(origin_turf, target_turf, include_start_atom = FALSE) + if(!length(path)) + return TRUE + + for(var/turf/path_turf in path) + if(path_turf.density && path_turf.opacity) + return TRUE + + for(var/obj/path_obj in path_turf) + if(path_obj.get_projectile_hit_boolean(null) && path_obj.opacity) + return TRUE + + for(var/obj/effect/particle_effect/smoke/smoke in path_turf) + if(smoke.obscuring) + return TRUE + + return FALSE + +/obj/item/hardpoint/primary/cannon/start_aim_visuals(atom/target, mob/living/user) + anchor = new(get_origin_turf()) + charge_start_time = world.time + build_lockon_icons(target) + apply_beam_visuals(target, FALSE) + aim_generation++ + INVOKE_ASYNC(src, PROC_REF(cycle_laser_color), target, aim_generation) + +//Builds the two reticle overlay images once for the whole charge +/obj/item/hardpoint/primary/cannon/proc/build_lockon_icons(atom/target) + var/turf/origin_turf = get_origin_turf() + + var/mob/living_target = target + var/x_offset = -living_target.pixel_x + living_target.base_pixel_x + var/y_offset = (living_target.icon_size - world.icon_size) * 0.5 - living_target.pixel_y + living_target.base_pixel_y + + lockon_icon = image(icon = 'icons/effects/Targeted.dmi', icon_state = "sniper_lockon_intense") + lockon_icon.pixel_x = x_offset + lockon_icon.pixel_y = y_offset + target.overlays += lockon_icon + + lockon_direction_icon = image(icon = 'icons/effects/Targeted.dmi', icon_state = "sniper_lockon_intense_direction", dir = get_cardinal_dir(target, origin_turf)) + lockon_direction_icon.pixel_x = x_offset + lockon_direction_icon.pixel_y = y_offset + target.overlays += lockon_direction_icon + +/** + * Recolors the (already-built) reticle icons and rebuilds the beam using the AMR's focused-fire beam, + * the flicker alternates between this sprite's native blue and a darker shade of the same blue. + * + * Also refreshes the direction arrow's facing (the target/tank can have moved since the last cycle) + * and ramps the beam's opacity based on total elapsed charge time (charge_start_time). + * + * The intent, with the flickering, and the blue color of the AMR focused shot, is to make it clear for xenos that + * they are being targeted by something dangerous. + */ +/obj/item/hardpoint/primary/cannon/proc/apply_beam_visuals(atom/target, use_dark_look) + var/turf/origin_turf = get_origin_turf() + + lockon_icon.color = use_dark_look ? LTB_LASER_DARK_TINT : null + lockon_direction_icon.color = use_dark_look ? LTB_LASER_DARK_TINT : null + lockon_direction_icon.dir = get_cardinal_dir(target, origin_turf) + + QDEL_NULL(laser_beam) + laser_beam = anchor.beam(target, "laser_beam_intense", 'icons/effects/beam.dmi', (f_aiming_time + 1 SECONDS), beam_type = /obj/effect/ebeam/laser/intense/tank_mounted) + if(use_dark_look) + laser_beam.visuals.color = LTB_LASER_DARK_TINT + + var/elapsed = world.time - charge_start_time + var/current_progress = clamp(elapsed / f_aiming_time, 0, 1) + var/next_progress = clamp((elapsed + 5) / f_aiming_time, 0, 1) + laser_beam.visuals.alpha = LTB_LASER_MIN_ALPHA + (255 - LTB_LASER_MIN_ALPHA) * current_progress + animate(laser_beam.visuals, alpha = LTB_LASER_MIN_ALPHA + (255 - LTB_LASER_MIN_ALPHA) * next_progress, 0.5 SECONDS, easing = SINE_EASING|EASE_OUT) + +/// Flickers the reticle/beam between the AMR-intense blue's native brightness and a darker shade of +/// the same blue about twice a second while charging +/obj/item/hardpoint/primary/cannon/proc/cycle_laser_color(atom/locked_target, my_generation) + var/use_dark_look = FALSE + while(laser_beam && !QDELETED(laser_beam) && !QDELETED(locked_target) && aim_generation == my_generation) + sleep(5) + if(!laser_beam || QDELETED(laser_beam) || QDELETED(locked_target) || aim_generation != my_generation) + break + use_dark_look = !use_dark_look + apply_beam_visuals(locked_target, use_dark_look) + +/obj/item/hardpoint/primary/cannon/on_track_tick(atom/target) + if(!anchor) + return + var/turf/origin_turf = get_origin_turf() + if(anchor.loc != origin_turf) + anchor.forceMove(origin_turf) + +/obj/item/hardpoint/primary/cannon/end_aim_visuals(atom/target, mob/living/user, success) + if(lockon_icon) + target.overlays -= lockon_icon + lockon_icon = null + if(lockon_direction_icon) + target.overlays -= lockon_direction_icon + lockon_direction_icon = null + QDEL_NULL(laser_beam) + QDEL_NULL(anchor) From 47495bb44d86b3a467da0a87702ee41dae232c3d Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Sat, 4 Jul 2026 18:26:53 -0300 Subject: [PATCH 41/60] LTB Rework: Single-Shot ammo, magazine removal --- .../vending/vendor_types/crew/vehicle_crew.dm | 17 ++++++++++++----- code/modules/vehicles/hardpoints/hardpoint.dm | 7 +++++++ .../hardpoints/hardpoint_ammo/ltb_ammo.dm | 10 ++++++++++ .../vehicles/hardpoints/holder/holder.dm | 2 +- .../vehicles/hardpoints/primary/ltb.dm | 4 ++-- .../interior/interactable/weapons_loader.dm | 17 ++++++++++++++--- .../multitile/multitile_hardpoints.dm | 2 +- .../guns/ammo_by_faction/USCM/vehicles.dmi | Bin 2384 -> 2748 bytes 8 files changed, 47 insertions(+), 12 deletions(-) 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 3ced90bfe322..a3a683d0a16f 100644 --- a/code/game/machinery/vending/vendor_types/crew/vehicle_crew.dm +++ b/code/game/machinery/vending/vendor_types/crew/vehicle_crew.dm @@ -375,11 +375,18 @@ GLOBAL_LIST_INIT(cm_vending_clothing_vehicle_crew, list( desc = "A giant cannon firing explosive 86mm shells. You'd be lucky if this even leaves the dust of whatever you hit with it." spawned_gear_list = list( /obj/item/hardpoint/primary/cannon, - /obj/item/ammo_magazine/hardpoint/ltb_cannon, - /obj/item/ammo_magazine/hardpoint/ltb_cannon, - /obj/item/ammo_magazine/hardpoint/ltb_cannon, - /obj/item/ammo_magazine/hardpoint/ltb_cannon, - /obj/item/ammo_magazine/hardpoint/ltb_cannon, + /obj/item/ammo_magazine/hardpoint/ltb_cannon/he_shell, + /obj/item/ammo_magazine/hardpoint/ltb_cannon/he_shell, + /obj/item/ammo_magazine/hardpoint/ltb_cannon/he_shell, + /obj/item/ammo_magazine/hardpoint/ltb_cannon/he_shell, + /obj/item/ammo_magazine/hardpoint/ltb_cannon/he_shell, + /obj/item/ammo_magazine/hardpoint/ltb_cannon/he_shell, + /obj/item/ammo_magazine/hardpoint/ltb_cannon/he_shell, + /obj/item/ammo_magazine/hardpoint/ltb_cannon/he_shell, + /obj/item/ammo_magazine/hardpoint/ltb_cannon/he_shell, + /obj/item/ammo_magazine/hardpoint/ltb_cannon/he_shell, + /obj/item/ammo_magazine/hardpoint/ltb_cannon/he_shell, + /obj/item/ammo_magazine/hardpoint/ltb_cannon/he_shell, ) /obj/effect/essentials_set/tank/gatling diff --git a/code/modules/vehicles/hardpoints/hardpoint.dm b/code/modules/vehicles/hardpoints/hardpoint.dm index fd99634ead0b..f4f6268c0e18 100644 --- a/code/modules/vehicles/hardpoints/hardpoint.dm +++ b/code/modules/vehicles/hardpoints/hardpoint.dm @@ -112,6 +112,11 @@ /// Currently loaded ammo that we shoot from. var/obj/item/ammo_magazine/hardpoint/ammo + /// The type of magazine this hardpoint accepts, cached from ammo's class-default in Initialize() + /// unlike ammo itself, this stays set even while ammo is temporary null, so + /// get_hardpoints_with_ammo()/try_add_clip()/the weapons loader can still recognize this + /// hardpoint as reloadable and know whatt magazine type to accept + var/ammo_type /// Spare magazines that we can reload from. var/list/backup_clips /// Maximum amount of spare mags. @@ -174,6 +179,8 @@ /obj/item/hardpoint/Initialize() . = ..() set_bullet_traits() + if(ammo) + ammo_type = ammo.type AddComponent(/datum/component/automatedfire/autofire, fire_delay, burst_delay, burst_amount, gun_firemode, autofire_slow_mult, CALLBACK(src, PROC_REF(set_burst_firing)), CALLBACK(src, PROC_REF(reset_fire)), CALLBACK(src, PROC_REF(fire_wrapper)), callback_set_firing = CALLBACK(src, PROC_REF(set_auto_firing))) /obj/item/hardpoint/Destroy() diff --git a/code/modules/vehicles/hardpoints/hardpoint_ammo/ltb_ammo.dm b/code/modules/vehicles/hardpoints/hardpoint_ammo/ltb_ammo.dm index ee6de433eb36..aa7b0803dad9 100644 --- a/code/modules/vehicles/hardpoints/hardpoint_ammo/ltb_ammo.dm +++ b/code/modules/vehicles/hardpoints/hardpoint_ammo/ltb_ammo.dm @@ -11,3 +11,13 @@ /obj/item/ammo_magazine/hardpoint/ltb_cannon/update_icon() icon_state = "ltbcannon_[current_rounds]" + +// Single-round alternative magazine, still istype()-matched against the hardpoint's cached ammo_type +/obj/item/ammo_magazine/hardpoint/ltb_cannon/he_shell + name = "86x455mm HE Shell" + desc = "A single high-explosive 86x455mm shell for the LTB Cannon." + icon_state = "lbtshell_1" + max_rounds = 1 + +/obj/item/ammo_magazine/hardpoint/ltb_cannon/he_shell/update_icon() + icon_state = "lbtshell_[current_rounds]" diff --git a/code/modules/vehicles/hardpoints/holder/holder.dm b/code/modules/vehicles/hardpoints/holder/holder.dm index f85538dc3ef4..9fa4cd3a5775 100644 --- a/code/modules/vehicles/hardpoints/holder/holder.dm +++ b/code/modules/vehicles/hardpoints/holder/holder.dm @@ -166,7 +166,7 @@ /obj/item/hardpoint/holder/proc/get_hardpoints_with_ammo(seat) var/list/hps = list() for(var/obj/item/hardpoint/H in hardpoints) - if(!H.ammo || seat && seat != H.allowed_seat) + if(!H.ammo_type || seat && seat != H.allowed_seat) continue hps += H return hps diff --git a/code/modules/vehicles/hardpoints/primary/ltb.dm b/code/modules/vehicles/hardpoints/primary/ltb.dm index f70dee7f1a67..b05a094fd605 100644 --- a/code/modules/vehicles/hardpoints/primary/ltb.dm +++ b/code/modules/vehicles/hardpoints/primary/ltb.dm @@ -16,8 +16,8 @@ health = 500 traverse_arc = 60 - ammo = new /obj/item/ammo_magazine/hardpoint/ltb_cannon - max_clips = 3 + ammo = new /obj/item/ammo_magazine/hardpoint/ltb_cannon/he_shell + max_clips = 1 px_offsets = list( "1" = list(0, 21), diff --git a/code/modules/vehicles/interior/interactable/weapons_loader.dm b/code/modules/vehicles/interior/interactable/weapons_loader.dm index 18b3f894e2a2..2c26b3e6de0e 100644 --- a/code/modules/vehicles/interior/interactable/weapons_loader.dm +++ b/code/modules/vehicles/interior/interactable/weapons_loader.dm @@ -88,7 +88,16 @@ return if(!LAZYLEN(HP.backup_clips)) - to_chat(user, SPAN_WARNING("\The [HP] has no remaining backup magazines!")) + if(!HP.ammo) + to_chat(user, SPAN_WARNING("\The [HP] has no remaining backup magazines, and no magazine currently loaded!")) + return + + to_chat(user, SPAN_WARNING("\The [HP] has no remaining backup magazines, so you remove the current magazine instead.")) + HP.ammo.forceMove(get_turf(user)) + HP.ammo.update_icon() + HP.ammo = null + HP.owner?.update_icon() + playsound(loc, 'sound/machines/hydraulics_3.ogg', 50) return var/obj/item/ammo_magazine/M = LAZYACCESS(HP.backup_clips, 1) @@ -102,10 +111,12 @@ to_chat(user, SPAN_WARNING("Something interrupted you while reloading \the [HP].")) return - HP.ammo.forceMove(get_turf(src)) - HP.ammo.update_icon() + if(HP.ammo) + HP.ammo.forceMove(get_turf(src)) + HP.ammo.update_icon() HP.ammo = M LAZYREMOVE(HP.backup_clips, M) + HP.owner?.update_icon() playsound(loc, 'sound/machines/hydraulics_3.ogg', 50) to_chat(user, SPAN_NOTICE("You reload \the [HP]. Ammo: [SPAN_HELPFUL(HP.ammo.current_rounds)]/[SPAN_HELPFUL(HP.ammo.max_rounds)] | Mags: [SPAN_HELPFUL(LAZYLEN(HP.backup_clips))]/[SPAN_HELPFUL(HP.max_clips)]")) diff --git a/code/modules/vehicles/multitile/multitile_hardpoints.dm b/code/modules/vehicles/multitile/multitile_hardpoints.dm index d40749aedcf5..f15eb383da03 100644 --- a/code/modules/vehicles/multitile/multitile_hardpoints.dm +++ b/code/modules/vehicles/multitile/multitile_hardpoints.dm @@ -29,7 +29,7 @@ var/obj/item/hardpoint/holder/HP = H if(HP.hardpoints) hps += HP.get_hardpoints_with_ammo(seat) - if(!H.ammo || seat && seat != H.allowed_seat) + if(!H.ammo_type || seat && seat != H.allowed_seat) continue hps += H return hps diff --git a/icons/obj/items/weapons/guns/ammo_by_faction/USCM/vehicles.dmi b/icons/obj/items/weapons/guns/ammo_by_faction/USCM/vehicles.dmi index 612d4cc9f43ae907d1719d46ae51bb5c08b07542..ed1df292723649dd6ccb0849223624bd90c4d339 100644 GIT binary patch delta 2543 zcmVynwdm9~ng@fCM0RN*Is%8L#PXIwD02LDx z8XzJuD=SDsLevZlVq078?(QryGdn>*NKj8_YibuJ8Nk56Vq{`VNK3uFy?uRsTpb+{ z5DSG^2Wl4=hZYt)MKB>EBf*CwG&MJzY#BR2G)p!RUt?%QM@il~DY1V>K14-H+R*aw z00001bW%=J06^y0W&i*H>3URHbVOxyV{&P5bZKvH004NLoz%S!!XOX;;JG{n(cSpB zx|lY`L0^HK?X?gQ8n|kFdmF3mWB|7#d>n8`nDc#cIG%ZaI^h@Ry(-i5RahI(3w)A6YB)KO0dhI)pRMr7;BPj z_zaT`-)*v+1iKAdpHMTOP_wdYt%iuC9~jP9J8KAZM9QxW=Y$dkbYzEeC31ncmY%^y zm1~Tsbw5F1u8I7wJ%lHx+K6WY5QPMW*@e7B9n+nW{%p^j7w3Qc{hho3xe`XrFlrFTv4Q)-qY0`bkLTycmW3cQjPISEB&=?aO z;Q#-*?~P=8G&7o!WJ@;Z=n9V#ZWMjK?~No67)Gi|Xb$CkH5}Y=HpH{cN&YTwWmxg% z9RJl^@_g6kN4kH;6LKXsrcmF^{B@K&3V{4qNhb(Z^#XEL<^hxLL8xzLe&PBj*B9I| z!&R9BOneq+<`)4F<&K#}p~_QLW_~b9t_>#1i~(YaJv}H+WJfosq}I))p>9sv&nzr( zfbbV`z~$xTTXMjq+jnlStgL(ryQRB#$MYq>o|`061mJ(W_1V2;zW|vNd@cfn@7EMX79PTpgTuRC4m*1^ERj0ExW)IND7;4d zTmzF3*MpLuxbf}1DFzrMLEpP@BGM-~WdWcwi|I}R$UI7qKA|KiZHedRhC|Y9RGHO9 z(CUJ-lO{OlepH%SK=+R^h58zJGhrSUWKXNp`8a>aNV&3L%mAQH)1JktQfX&V+`g7i zxjq-dUt3%A@zL@1^?=Itx#0fV!|xuhNqxuh^L2Qd^||1_3n1}5fFY}_Tb~Qz1Hkud zQr~f;z8+AsJ{Q6VfFIVRzT-&!0DyxgmaosTzT4yz_4R;m37|Wq>vN3n_RwT~R4(Tk zI6!|~pYs5EKR7eKdxFXONcHH)95YBlblcZ&eU5ALiTk-nk8;c)9G)38v3z}Q)#_5revXq2aJmTw>cJULP`y4E zvrpS^s2MD=p$kyE{1vya)$9Xm*5~5(0}#&m%0jACsZym%6{qs+>v@mh`y_p>)_)ST zo}{nm+YrS5hPZvLHaC9$h1q_^abuJ9e-q^O$2&d%;O~F%8+U>~IdOpF9UlOY`OSZS z{rwM){0TNa;sM7y9z{Q@j0PT1TOtkMc*h(3(!&#uFux(rvWhjj1-Z*_NvFHwI`e|M6^=5{QUWgXn^YMD&-Px6<^<#25_7NO=W^3KzWQhz!y$-kpW#P z09{#Dxnx-?A0_&u5|_*Il(<@}9Uhhrst{$RDyHc8)mjxPt0H2&C3Ysr(J{hNiOpE4 z9)Zt@^Iw!>Eb*ZKtV-zsld6B>p2=ufUYcIbNs-Gi)&yNZt6D3Us7E5FoOrH-C;TZ2Cv{eWZ>lGW zI{_@Ggpi^$U448I=NpA$!IK~l0DhGf@0-L_CkUw=AJ;4sig<+SK@or98y_FU`9^`% zo$Fu`Q47U74@kU#yl)P^Ac!glgu+>;rezH-Bj%|U*arYt*nW~8)A>U{pBlRW73&mo zp)xJ@{5sz#RN!r!XJ(;j!U!Q$yeHE613=#%99-HgL@*BzVP}Q#2>_6NJU?V(p21^X z#Y3R;2Y>;dvml())8l_*>CMdFuM`V#e(1e35GC+l$aOxno)P%dh~K?dhESR6_Vj2zc>*S|X4Uoc_$D8-cB2kPyB>ujH}ep`gd=XXK;~8#${(LLvRwRjK3L zq!$pJuXO=X&Qva?+g?X&~8G4QLjUtUav<1-<)@)fZ!t}$`|kw055$3;3|%aHqQwL^Y#tky*_{2 z5ua|U-|6;HU5S710yLXVe*n9g1-@*g2uZ)w>-Rh7Z_fb|0Q>v{x#JH3c|Q<<-E6d4 z*+vvVa@C_H9EkL;NEkmD093b~Z8xG!WPB2*-Q^_Im{eB-t`i+pF$`1r!XD{(q z*uHE>0PJ58Jo~$M@A?P<-w>JaFp7R40O-(WqtR%#BQ)Uv;QjmeO27{vJ}3Y+0N4U8 zI?coX&!)+gSOZ{R;19q%h;Tcm0PG}?0V@1J0O&We?Pe4}gLFxk5qulY4*~!_&htnK zivLUy)(>L<0MA;D=ztTD4uG~m7B7=i15^Xp_67848~CRMr~!aS)eCq77m*)X$CVBM z_?_p)>Ghxn0Db`OG*jaX0D%0>X|9cE0qAFvfFD zZ*Bkpc$}5fOAdn|5C-72oPy}S@wM%y(KIgV706Waks=rv+w}G|wXLfL*v*8GADD!Q zp2~Ao)8bN*JJ)7Zk{)S(h)M7mTO8IVOGz;Bu^(`233>L+A(E6FaRW zf^dlZN_!?WoWX)>8y94T7^m|@!fRr$9za-DH(p2w92wn zv!s4kN}un>+Dtc8LZ!r32K6nk-9-7L2B?0Qc7jmVAfQrZ6)^2N2=y(mRc?NEv*M3s zrOFCm>Z`cCwhn)QD1R)kYgNuv<+aHoxiMKJIRnHJ2j-x@kcDn>8LitJQ{7&*U#?UX zK=^?faC39>ju~*{?!CL!YV~tCZQQ@VoNst}Wsw*WfbY&14>rRBly2~)4iL-N@yf~~ z(cesbz^_8`v%cP_ep7w;@Zq;`G9?tt*Z5!#1{aBW4nlu`p@3ES2H)ub)hGbU$8BbP z3$$Jlr;u$XBEaPmqI}$D9z6zaRm91>%|r!=i}2nC&LKFN=a}SE*K2@K;zy7i0`il& z#8CmlDwJ+uT&2-~kv{G!5Z#tY5CKiwOk{w`?t{n((LELk@)8>q9bnQ@=b$d!qeJ$< zG^ETy!*74wdh#H{0Ov_C^j$a=(FtZO02Hg3pA3M~ztXc$C@G38aj7&N(q`i-Z>@v2 zD&k~Ju;hPK$}OP(Wo1xb3*Stne-=#tt!DFaPEurN!O8(3rL0$Rp+e*;O4=v+BKvbO z{93IR;-eGl(}0ovx%vIt)9;_wjJ|NfdKB7ODw2$)+u%tl+B>Hnn`v?enJ{@$x=>A;NJ_5qhkOmcy=+7nXb3l2KXn;h2 zE@_{fgBIPNOV}sophdSH687uQLDL~D!Km(1!hT(m2{79P=bD2#PcXVam$1*L-&8p) zu|)+)w!f10N#!4q=+7nX&q0{;&O)I=g$jQaDwL!eJ3Ebl;CY%pshyuS?WF0`e7lz!hQT(IIY)}Eo5)pvQU2mzahvy1mV^?2gyN&dG(5!DG1|`CVzRDVD`Ur?A&IW%4 zaA#lBPMSWM;NJeh!9nxzkUtI%n}-Mcd-44yJPsD}$prWI;TpJNQzK2EoP#+K*`I$aRH#s)LWNS2ZMU*Uh?M>E<*QcK5Fx_sknh#2 zR>mY|@$78i%W0AQ>|O$2UIBS_3j?;>?W5@5=p5s*2pg9z&^q1j>(_7MU*7KZdPi^J z0MNGaH=J$<4=3yu9A5Jvx7BGZq2h zy4{ZJM)@e+pOv`Vja_0kX!i$$_R+}^Kmy>vKF^11;TOZlhGNTgD*+(-*gu*F$48beN?Z9&x1Gj=b!6zjxnhr zo~4YY<>l#(M2bp=OD4zyQWJml0o~!K+Zm3|&qu>f$~ zE69b*bLIO}zGXX}$GmILZh0^=&vSw|AbbueFA@!KvbDL0;GLer$&KMN0AL^S{*aA# z{gHW2fPnHX))V`nFL-}yg$TmAy1KY9zL|ylPRoY-!`!pN4Pz2--uNF0 z@(9TLb8WwQWA4voD!)HB1vK^NvJ~B)%UEQ8E^CqfxvWL@=du>ryvbUV_LpEZw7;^} z1n|?RPdNq5eTk|5T;`g;KbMta^ydl{DpaUYiuxbdneeI6G(V#N0000 From 88aee29f5289e33e3f86e8a47dfc83674669201c Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Sat, 4 Jul 2026 18:27:37 -0300 Subject: [PATCH 42/60] LTB Rework: HE damage adjustment; IFF --- code/datums/ammo/rocket.dm | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/code/datums/ammo/rocket.dm b/code/datums/ammo/rocket.dm index ec74c8622a7a..2c5aeef2a097 100644 --- a/code/datums/ammo/rocket.dm +++ b/code/datums/ammo/rocket.dm @@ -159,20 +159,26 @@ shell_speed = AMMO_SPEED_TIER_3 /datum/ammo/rocket/ltb/on_hit_mob(mob/mob, obj/projectile/projectile) - cell_explosion(get_turf(mob), 220, 50, EXPLOSION_FALLOFF_SHAPE_LINEAR, null, projectile.weapon_cause_data) - cell_explosion(get_turf(mob), 200, 100, EXPLOSION_FALLOFF_SHAPE_LINEAR, null, projectile.weapon_cause_data) + cell_explosion(get_turf(mob), 200, 200, EXPLOSION_FALLOFF_SHAPE_LINEAR, null, projectile.weapon_cause_data) + cell_explosion(get_turf(mob), 100, 25, EXPLOSION_FALLOFF_SHAPE_LINEAR, null, projectile.weapon_cause_data) /datum/ammo/rocket/ltb/on_hit_obj(obj/object, obj/projectile/projectile) - cell_explosion(get_turf(object), 220, 50, EXPLOSION_FALLOFF_SHAPE_LINEAR, null, projectile.weapon_cause_data) - cell_explosion(get_turf(object), 200, 100, EXPLOSION_FALLOFF_SHAPE_LINEAR, null, projectile.weapon_cause_data) + cell_explosion(get_turf(object), 200, 200, EXPLOSION_FALLOFF_SHAPE_LINEAR, null, projectile.weapon_cause_data) + cell_explosion(get_turf(object), 100, 25, EXPLOSION_FALLOFF_SHAPE_LINEAR, null, projectile.weapon_cause_data) /datum/ammo/rocket/ltb/on_hit_turf(turf/turf, obj/projectile/projectile) - cell_explosion(get_turf(turf), 220, 50, EXPLOSION_FALLOFF_SHAPE_LINEAR, null, projectile.weapon_cause_data) - cell_explosion(get_turf(turf), 200, 100, EXPLOSION_FALLOFF_SHAPE_LINEAR, null, projectile.weapon_cause_data) + cell_explosion(get_turf(turf), 200, 200, EXPLOSION_FALLOFF_SHAPE_LINEAR, null, projectile.weapon_cause_data) + cell_explosion(get_turf(turf), 100, 25, EXPLOSION_FALLOFF_SHAPE_LINEAR, null, projectile.weapon_cause_data) /datum/ammo/rocket/ltb/do_at_max_range(obj/projectile/projectile) - cell_explosion(get_turf(projectile), 220, 50, EXPLOSION_FALLOFF_SHAPE_LINEAR, null, projectile.weapon_cause_data) - cell_explosion(get_turf(projectile), 200, 100, EXPLOSION_FALLOFF_SHAPE_LINEAR, null, projectile.weapon_cause_data) + cell_explosion(get_turf(projectile), 200, 200, EXPLOSION_FALLOFF_SHAPE_LINEAR, null, projectile.weapon_cause_data) + cell_explosion(get_turf(projectile), 100, 25, EXPLOSION_FALLOFF_SHAPE_LINEAR, null, projectile.weapon_cause_data) + +/datum/ammo/rocket/ltb/set_bullet_traits() + . = ..() + LAZYADD(traits_to_give, list( + BULLET_TRAIT_ENTRY(/datum/element/bullet_trait_iff) + )) /datum/ammo/rocket/wp name = "white phosphorous rocket" From 3cf42c7c404fbc3d5cff9951d12f534888cc1090 Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Sat, 4 Jul 2026 18:30:08 -0300 Subject: [PATCH 43/60] LZR-N; DRG-N: Double-Mode Capability; Custom Reagents --- code/__DEFINES/keybinding.dm | 1 + code/__DEFINES/vehicle.dm | 7 ++ code/datums/ammo/misc.dm | 27 +++++- code/datums/keybinding/vehicles.dm | 22 +++++ code/modules/vehicles/hardpoints/hardpoint.dm | 75 +++++++++++++-- .../hardpoint_ammo/primary_flamer_ammo.dm | 93 +++++++++++++++++- .../hardpoint_ammo/secondary_flamer_ammo.dm | 94 +++++++++++++++++++ .../vehicles/hardpoints/primary/flamer.dm | 44 +++++++++ .../vehicles/hardpoints/secondary/flamer.dm | 44 +++++---- code/modules/vehicles/tank/tank.dm | 4 + 10 files changed, 385 insertions(+), 26 deletions(-) diff --git a/code/__DEFINES/keybinding.dm b/code/__DEFINES/keybinding.dm index bcf1b06b6fe4..cffa10c309ee 100644 --- a/code/__DEFINES/keybinding.dm +++ b/code/__DEFINES/keybinding.dm @@ -232,6 +232,7 @@ #define COMSIG_KB_VEHICLE_CHANGE_SELECTED_WEAPON "keybinding_change_selected_weapon" #define COMSIG_KB_VEHICLE_ACTIVATE_HORN "keybinding_activate_horn" #define COMSIG_KB_VEHICLE_RELOAD_WEAPON "keybinding_reload_weapon" +#define COMSIG_KB_VEHICLE_TOGGLE_HARDPOINT_FIRE_MODE "keybinding_vehicle_toggle_hardpoint_fire_mode" #define CATEGORY_CLIENT "CLIENT" #define CATEGORY_EMOTE "EMOTE" diff --git a/code/__DEFINES/vehicle.dm b/code/__DEFINES/vehicle.dm index 2a833eb37b85..4ab95812473c 100644 --- a/code/__DEFINES/vehicle.dm +++ b/code/__DEFINES/vehicle.dm @@ -92,3 +92,10 @@ #define TURRET_ANGULAR_ACCEL 1 /// Half-width, in degrees, of the arc a self_gimballed weapon's driver-controlled aim is clamped to around the turret's own current facing (see /obj/item/hardpoint/proc/update_desired_angle()) - keeps the visual swivel (see /obj/item/hardpoint/holder/tank_turret/get_hardpoint_image()) to a modest look-around instead of a full swing to any absolute angle. #define SLAVED_GIMBAL_ARC_HALF_WIDTH 30 + +// Flamer hardpoint fire modes - see /obj/item/hardpoint/proc/toggle_fire_mode() +/// Directional line stream (walks toward the target, self-propagating tile by tile). +#define FLAME_MODE_STREAM "stream" +/// Single traveling projectile that plants one AOE ignition point on impact. +#define FLAME_MODE_GLOB "glob" +/// should these go into a _DEFINES for hardpoints instead??? diff --git a/code/datums/ammo/misc.dm b/code/datums/ammo/misc.dm index 8f7659dd9871..06539abeff5c 100644 --- a/code/datums/ammo/misc.dm +++ b/code/datums/ammo/misc.dm @@ -48,16 +48,41 @@ /datum/ammo/flamethrower/do_at_max_range(obj/projectile/P) drop_flame(get_turf(P), P.weapon_cause_data) +// "Glob shot" mode ammo for the tank's primary/secondary flamer hardpoints /datum/ammo/flamethrower/tank_flamer flamer_reagent_id = "highdamagenapalm" max_range = 8 shell_speed = 1.5 + var/loaded_chem_id /datum/ammo/flamethrower/tank_flamer/drop_flame(turf/turf, datum/cause_data/cause_data) if(!istype(turf)) return - var/datum/reagent/napalm/high_damage/reagent = new() + var/datum/reagent/reagent = loaded_chem_id ? GLOB.chemical_reagents_list[loaded_chem_id] : null + if(!reagent) + reagent = new /datum/reagent/napalm/high_damage() + new /obj/flamer_fire(turf, cause_data, reagent, 1) + + var/datum/effect_system/smoke_spread/landingsmoke = new /datum/effect_system/smoke_spread + landingsmoke.set_up(1, 0, turf, null, 4, cause_data) + landingsmoke.start() + landingsmoke = null + +/// Same as tank_flamer above, but for the secondary flamer hardpoint's smaller glob. +/datum/ammo/flamethrower/tank_flamer_secondary + flamer_reagent_id = "highdamagenapalm" + max_range = 8 + shell_speed = 1.5 + var/loaded_chem_id + +/datum/ammo/flamethrower/tank_flamer_secondary/drop_flame(turf/turf, datum/cause_data/cause_data) + if(!istype(turf)) + return + + var/datum/reagent/reagent = loaded_chem_id ? GLOB.chemical_reagents_list[loaded_chem_id] : null + if(!reagent) + reagent = new /datum/reagent/napalm/high_damage() new /obj/flamer_fire(turf, cause_data, reagent, 1) var/datum/effect_system/smoke_spread/landingsmoke = new /datum/effect_system/smoke_spread diff --git a/code/datums/keybinding/vehicles.dm b/code/datums/keybinding/vehicles.dm index 22c20e678c4b..dcc4b95e6c59 100644 --- a/code/datums/keybinding/vehicles.dm +++ b/code/datums/keybinding/vehicles.dm @@ -84,3 +84,25 @@ var/obj/vehicle/multitile/vehicle_user = user.mob.interactee vehicle_user.reload_firing_port_weapon() return TRUE + +/** + * Toggles the fire mode of hardpoints with more than one fire mode. currently only used for the primary and secondary + * flamers + */ +/datum/keybinding/vehicles/toggle_hardpoint_fire_mode + hotkey_keys = list("Unbound") + classic_keys = list("Unbound") + name = "Toggle hardpoint fire mode" + full_name = "Toggle Hardpoint Fire Mode" + keybind_signal = COMSIG_KB_VEHICLE_TOGGLE_HARDPOINT_FIRE_MODE + +/datum/keybinding/vehicles/toggle_hardpoint_fire_mode/down(client/user) + . = ..() + if(.) + return + var/obj/vehicle/multitile/vehicle_user = user.mob.interactee + var/obj/item/hardpoint/HP = vehicle_user.get_mob_hp(user.mob) + if(!HP) + return + HP.toggle_fire_mode(user.mob) + return TRUE diff --git a/code/modules/vehicles/hardpoints/hardpoint.dm b/code/modules/vehicles/hardpoints/hardpoint.dm index f4f6268c0e18..d5d3e95cd6c9 100644 --- a/code/modules/vehicles/hardpoints/hardpoint.dm +++ b/code/modules/vehicles/hardpoints/hardpoint.dm @@ -165,6 +165,8 @@ var/atom/target /// The type of projectile to fire var/projectile_type = /obj/projectile + /// How many ammo.current_rounds a single base handle_fire() shot costs. The flamer hardpoints (primary/flamer.dm, secondary/flamer.dm) set this to 0 and do their own reagent-based fuel deduction instead, since their ammo tracks real chemical volume rather than plain integer rounds. + var/ammo_cost_per_shot = 1 //----------------------------- //------GENERAL PROCS---------- @@ -396,17 +398,23 @@ sleep(20) - forceMove(ammo, get_turf(src)) - ammo.update_icon() + if(ammo) + forceMove(ammo, get_turf(src)) + ammo.update_icon() ammo = A LAZYREMOVE(backup_clips, A) + owner?.update_icon() to_chat(user, SPAN_NOTICE("You reload \the [name].")) +/// Whether this hardpoint will accept a given magazine for reloading. Eg, m56d cupola accepts m56d drums. +/obj/item/hardpoint/proc/accepts_magazine(obj/item/ammo_magazine/magazine) + return istype(magazine, ammo_type) + //try adding magazine to hardpoint's backup clips. Called via weapons loader /obj/item/hardpoint/proc/try_add_clip(obj/item/ammo_magazine/A, mob/user) - if(!ammo) - to_chat(user, SPAN_WARNING("\The [name] doesn't use ammunition.")) + if(!ammo_type) + to_chat(user, SPAN_WARNING("\The [name] doesn't use ammunition.")) // UA flag return FALSE if(max_clips == 0) to_chat(user, SPAN_WARNING("\The [name] does not have room for additional ammo.")) @@ -429,7 +437,7 @@ playsound(loc, 'sound/machines/hydraulics_2.ogg', 50) LAZYADD(backup_clips, A) - to_chat(user, SPAN_NOTICE("You load \the [A] into \the [name]. Ammo: [SPAN_HELPFUL(ammo.current_rounds)]/[SPAN_HELPFUL(ammo.max_rounds)] | Mags: [SPAN_HELPFUL(LAZYLEN(backup_clips))]/[SPAN_HELPFUL(max_clips)]")) + to_chat(user, SPAN_NOTICE("You load \the [A] into \the [name]. Ammo: [SPAN_HELPFUL(ammo ? ammo.current_rounds : 0)]/[SPAN_HELPFUL(ammo ? ammo.max_rounds : 0)] | Mags: [SPAN_HELPFUL(LAZYLEN(backup_clips))]/[SPAN_HELPFUL(max_clips)]")) return TRUE /obj/item/hardpoint/attackby(obj/item/O, mob/user) @@ -617,7 +625,14 @@ to_chat(user, SPAN_WARNING("\The [name] is broken!")) return NONE - if(ammo && ammo.current_rounds <= 0) + // A hardpoint can legitimately have no magazine installed at all. This is needed especifically so the + // DRGN flamer and the secondary version can 'eject' their magazines to be refueled from a fuel source.. + // so, other guns can now eject their full magazines and have nothing loaded. + if(!ammo) + click_empty(user) + return NONE + + if(ammo.current_rounds <= 0) click_empty(user) return NONE @@ -786,8 +801,9 @@ break var/obj/projectile/projectile_to_fire = generate_bullet(user, spawn_turf) - ammo.current_rounds-- + ammo.current_rounds -= ammo_cost_per_shot SEND_SIGNAL(projectile_to_fire, COMSIG_BULLET_USER_EFFECTS, user) + projectile_to_fire.original = original_target // turf-targeted projectiles are fired without scatter, because proc would raytrace them further away var/ammo_flags = projectile_to_fire.ammo.flags_ammo_behavior | projectile_to_fire.projectile_override_flags @@ -807,6 +823,51 @@ return AUTOFIRE_CONTINUE +/** + * No-op by default - only the flamer hardpoints (primary/flamer.dm, secondary/flamer.dm) override + * this to flip between FLAME_MODE_STTREAM and FLAME_MODE_GLOB + */ +/obj/item/hardpoint/proc/toggle_fire_mode(mob/user) + return + +/** + * Proc that makes the flamer stream work more closely to how the M240 Incinerator is implemented in game + * instead of drawing from an arbitrary ammo pool, it now draws from the chem reagents inside a tank, just like the m240. + */ +/obj/item/hardpoint/proc/fire_flame_stream(atom/target, mob/living/user, max_range, flameshape = FLAMESHAPE_LINE) + //step forward along path so flame starts outside hull + var/list/turfs = get_line(get_origin_turf(), get_turf(target)) + var/turf/origin_turf + for(var/turf/turf as anything in turfs) + if(turf in owner.locs) + continue + origin_turf = turf + break + + var/distance = get_dist(origin_turf, get_turf(target)) + var/fire_amount = min(distance+1, max_range) + + var/datum/reagent/chem = LAZYACCESS(ammo.reagents?.reagent_list, 1) + new /obj/flamer_fire(origin_turf, create_cause_data(initial(name), user), chem, fire_amount, ammo.reagents, flameshape, target, CALLBACK(src, PROC_REF(sync_ammo_from_reagents))) + sync_ammo_from_reagents() + + play_firing_sounds() + + COOLDOWN_START(src, fire_cooldown, fire_delay) + + return AUTOFIRE_CONTINUE + +/** + * Re-syncs ammo.current_rounds to match ammo.reagents.total_volume.sync_ammo_from_reagents() + * Called both right after firing and again once a since reagent consumption happens asynchronously tile by tile + * as the flame propagates, not all at once at the moment of firing. Also refreshes the mounted sprite + * immediately. + */ +/obj/item/hardpoint/proc/sync_ammo_from_reagents() + if(ammo && ammo.reagents) + ammo.current_rounds = round(ammo.reagents.total_volume) + owner?.update_icon() + /// Start cooldown to respect delay of firemode. /obj/item/hardpoint/proc/set_fire_cooldown(firemode) var/cooldown_time = 0 diff --git a/code/modules/vehicles/hardpoints/hardpoint_ammo/primary_flamer_ammo.dm b/code/modules/vehicles/hardpoints/hardpoint_ammo/primary_flamer_ammo.dm index 8384b3f6d94e..1c596f54c207 100644 --- a/code/modules/vehicles/hardpoints/hardpoint_ammo/primary_flamer_ammo.dm +++ b/code/modules/vehicles/hardpoints/hardpoint_ammo/primary_flamer_ammo.dm @@ -5,12 +5,103 @@ icon = 'icons/obj/items/weapons/guns/ammo_by_faction/USCM/vehicles.dmi' icon_state = "drgn_flametank" w_class = SIZE_LARGE - max_rounds = 60 + max_rounds = 300 gun_type = /obj/item/hardpoint/primary/flamer default_ammo = /datum/ammo/flamethrower/tank_flamer + // Reagent id this tank starts filled with + var/flamer_chem = "highdamagenapalm" +/obj/item/ammo_magazine/hardpoint/primary_flamer/Initialize(mapload, ...) + . = ..() + create_reagents(max_rounds) + if(flamer_chem) + reagents.add_reagent(flamer_chem, max_rounds) + current_rounds = round(reagents.total_volume) + update_icon() + +/** + * Refuels this tank from a fuel source, exactly mirroring /obj/item/ammo_magazine/flamer_tank/afterattack() + * + * also follows the same rules regarding what fuel types are or aren't allowed. + */ +/obj/item/ammo_magazine/hardpoint/primary_flamer/afterattack(obj/target, mob/user, flag) + if(get_dist(user, target) > 1) + return ..() + if(!istype(target, /obj/structure/reagent_dispensers/tank/fuel) && !istype(target, /obj/item/tool/weldpack) && !istype(target, /obj/item/storage/backpack/marine/engineerpack) && !istype(target, /obj/item/ammo_magazine/hardpoint/primary_flamer)) + return ..() + + if(!target.reagents || length(target.reagents.reagent_list) < 1) + to_chat(user, SPAN_WARNING("[target] is empty!")) + return + + if(!reagents) + create_reagents(max_rounds) + + var/datum/reagent/to_add = target.reagents.reagent_list[1] + + if(!istype(to_add) || (length(reagents.reagent_list) && flamer_chem != to_add.id) || length(target.reagents.reagent_list) > 1) + to_chat(user, SPAN_WARNING("You can't mix fuel mixtures!")) + return + + if(!to_add.intensityfire && to_add.id != "stablefoam") + to_chat(user, SPAN_WARNING("This chemical is not potent enough to be used in a flamethrower!")) + return + + var/fuel_amt_to_remove = clamp(to_add.volume, 0, max_rounds - reagents.get_reagent_amount(to_add.id)) + if(!fuel_amt_to_remove) + if(!max_rounds) + to_chat(user, SPAN_WARNING("[target] is empty!")) + return + + target.reagents.remove_reagent(to_add.id, fuel_amt_to_remove) + reagents.add_reagent(to_add.id, fuel_amt_to_remove) + playsound(loc, 'sound/effects/refill.ogg', 25, 1, 3) + caliber = to_add.name + flamer_chem = to_add.id + current_rounds = round(reagents.total_volume) + + to_chat(user, SPAN_NOTICE("You refill [src] with [caliber].")) + update_icon() + + +// Colored fuel strip overlay /obj/item/ammo_magazine/hardpoint/primary_flamer/update_icon() + overlays.Cut() if(current_rounds > 0) icon_state = "drgn_flametank" + var/image/strip = image(icon, icon_state = "drgn_flametank_strip") + if(reagents) + strip.color = mix_color_from_reagents(reagents.reagent_list) + overlays += strip else icon_state = "drgn_flametank_empty" + +// Lists loaded fuel by name/volume on examine +/obj/item/ammo_magazine/hardpoint/primary_flamer/get_examine_text(mob/user) + . = ..() + . += SPAN_NOTICE("It contains:") + if(reagents && length(reagents.reagent_list)) + for(var/datum/reagent/reagent in reagents.reagent_list) + . += SPAN_NOTICE(" [reagent.volume] units of [reagent.name].") + else + . += SPAN_NOTICE(" Nothing.") + +// "Empty canister" verb, also found in the m240 flamer. +/obj/item/ammo_magazine/hardpoint/primary_flamer/verb/remove_reagents() + set name = "Empty canister" + set category = "Object" + + set src in usr + + if(usr.get_active_hand() != src) + return + + if(alert(usr, "Do you really want to empty out [src]?", "Empty canister", "Yes", "No") != "Yes") + return + + reagents.clear_reagents() + current_rounds = round(reagents.total_volume) + + playsound(loc, 'sound/effects/refill.ogg', 25, 1, 3) + to_chat(usr, SPAN_NOTICE("You empty out [src]")) + update_icon() diff --git a/code/modules/vehicles/hardpoints/hardpoint_ammo/secondary_flamer_ammo.dm b/code/modules/vehicles/hardpoints/hardpoint_ammo/secondary_flamer_ammo.dm index 4a09046c9678..d2d3cb34a872 100644 --- a/code/modules/vehicles/hardpoints/hardpoint_ammo/secondary_flamer_ammo.dm +++ b/code/modules/vehicles/hardpoints/hardpoint_ammo/secondary_flamer_ammo.dm @@ -7,3 +7,97 @@ w_class = SIZE_LARGE max_rounds = 150 gun_type = /obj/item/hardpoint/secondary/small_flamer + // Only used when the hardpoint is toggled to FLAME_MODE_GLOB + default_ammo = /datum/ammo/flamethrower/tank_flamer_secondary + // Reagent id this tank starts filled with + var/flamer_chem = "napalm" + +/obj/item/ammo_magazine/hardpoint/secondary_flamer/Initialize(mapload, ...) + . = ..() + create_reagents(max_rounds) + if(flamer_chem) + reagents.add_reagent(flamer_chem, max_rounds) + current_rounds = round(reagents.total_volume) + update_icon() + +// Colored fuel strip overlay +/obj/item/ammo_magazine/hardpoint/secondary_flamer/update_icon() + overlays.Cut() + var/image/strip = image(icon, icon_state = "flametank_large_custom_strip") + if(reagents && length(reagents.reagent_list)) + strip.color = mix_color_from_reagents(reagents.reagent_list) + overlays += strip + +/** + * Refuels this tank from a fuel source, exactly mirroring /obj/item/ammo_magazine/flamer_tank/afterattack() + * + * also follows the same rules regarding what fuel types are or aren't allowed. + */ +/obj/item/ammo_magazine/hardpoint/secondary_flamer/afterattack(obj/target, mob/user, flag) + if(get_dist(user, target) > 1) + return ..() + if(!istype(target, /obj/structure/reagent_dispensers/tank/fuel) && !istype(target, /obj/item/tool/weldpack) && !istype(target, /obj/item/storage/backpack/marine/engineerpack) && !istype(target, /obj/item/ammo_magazine/hardpoint/secondary_flamer)) + return ..() + + if(!target.reagents || length(target.reagents.reagent_list) < 1) + to_chat(user, SPAN_WARNING("[target] is empty!")) + return + + if(!reagents) + create_reagents(max_rounds) + + var/datum/reagent/to_add = target.reagents.reagent_list[1] + + if(!istype(to_add) || (length(reagents.reagent_list) && flamer_chem != to_add.id) || length(target.reagents.reagent_list) > 1) + to_chat(user, SPAN_WARNING("You can't mix fuel mixtures!")) + return + + if(!to_add.intensityfire && to_add.id != "stablefoam") + to_chat(user, SPAN_WARNING("This chemical is not potent enough to be used in a flamethrower!")) + return + + var/fuel_amt_to_remove = clamp(to_add.volume, 0, max_rounds - reagents.get_reagent_amount(to_add.id)) + if(!fuel_amt_to_remove) + if(!max_rounds) + to_chat(user, SPAN_WARNING("[target] is empty!")) + return + + target.reagents.remove_reagent(to_add.id, fuel_amt_to_remove) + reagents.add_reagent(to_add.id, fuel_amt_to_remove) + playsound(loc, 'sound/effects/refill.ogg', 25, 1, 3) + caliber = to_add.name + flamer_chem = to_add.id + current_rounds = round(reagents.total_volume) + + to_chat(user, SPAN_NOTICE("You refill [src] with [caliber].")) + update_icon() + +// Lists loaded fuel by name/volume when examining +/obj/item/ammo_magazine/hardpoint/secondary_flamer/get_examine_text(mob/user) + . = ..() + . += SPAN_NOTICE("It contains:") + if(reagents && length(reagents.reagent_list)) + for(var/datum/reagent/reagent in reagents.reagent_list) + . += SPAN_NOTICE(" [reagent.volume] units of [reagent.name].") + else + . += SPAN_NOTICE(" Nothing.") + +// "Empty canister" verb, also found in the m240 flamer. +/obj/item/ammo_magazine/hardpoint/secondary_flamer/verb/remove_reagents() + set name = "Empty canister" + set category = "Object" + + set src in usr + + if(usr.get_active_hand() != src) + return + + if(alert(usr, "Do you really want to empty out [src]?", "Empty canister", "Yes", "No") != "Yes") + return + + reagents.clear_reagents() + current_rounds = round(reagents.total_volume) + + playsound(loc, 'sound/effects/refill.ogg', 25, 1, 3) + to_chat(usr, SPAN_NOTICE("You empty out [src]")) + update_icon() diff --git a/code/modules/vehicles/hardpoints/primary/flamer.dm b/code/modules/vehicles/hardpoints/primary/flamer.dm index 8119bf4bd244..e541d6dc643c 100644 --- a/code/modules/vehicles/hardpoints/primary/flamer.dm +++ b/code/modules/vehicles/hardpoints/primary/flamer.dm @@ -31,6 +31,14 @@ scatter = 5 fire_delay = 2.0 SECONDS + // Range for FLAME_MODE_STREAM + var/max_range = 8 + var/flame_mode = FLAME_MODE_GLOB + // Flat fuel cost for a single FLAME_MODE_GLOB shot + var/glob_fuel_cost = 5 + // The reagent-based ammo/fuel deduction is done manually in handle_fire() below + ammo_cost_per_shot = 0 + /obj/item/hardpoint/primary/flamer/set_bullet_traits() ..() LAZYADD(traits_to_give, list( @@ -43,3 +51,39 @@ return NONE return ..() + +// Toggles between FLAME_MODE_GLOB and FLAME_MODE_STREAM +/obj/item/hardpoint/primary/flamer/toggle_fire_mode(mob/user) + flame_mode = (flame_mode == FLAME_MODE_STREAM) ? FLAME_MODE_GLOB : FLAME_MODE_STREAM + to_chat(user, SPAN_NOTICE("You switch \the [src] to [flame_mode == FLAME_MODE_STREAM ? "stream" : "glob shot"] mode.")) + +/obj/item/hardpoint/primary/flamer/handle_fire(atom/target, mob/living/user, params) + if(flame_mode == FLAME_MODE_STREAM) + return fire_flame_stream(target, user, max_range, FLAMESHAPE_TRIANGLE) + + var/datum/reagent/chem = LAZYACCESS(ammo.reagents?.reagent_list, 1) + if(!chem || ammo.reagents.get_reagent_amount(chem.id) < glob_fuel_cost) + click_empty(user) + return NONE + ammo.reagents.remove_reagent(chem.id, glob_fuel_cost) + sync_ammo_from_reagents() + return ..() + +// Tags the freshly-generated glob projectile's ammo datum with the id of whichever fuel is actually loaded + +/obj/item/hardpoint/primary/flamer/generate_bullet(mob/user, turf/origin_turf) + . = ..() + var/obj/projectile/fired = . + if(istype(fired?.ammo, /datum/ammo/flamethrower/tank_flamer)) + var/datum/ammo/flamethrower/tank_flamer/glob_ammo = fired.ammo + var/datum/reagent/chem = LAZYACCESS(ammo.reagents?.reagent_list, 1) + glob_ammo.loaded_chem_id = chem?.id + +// Layers an overlay on the mounted/turret sprite, colored via mix_color_from_reagents() to match whatever fuel is currently loaded +/obj/item/hardpoint/primary/flamer/get_icon_image(x_offset, y_offset, new_dir) + var/image/base_image = ..() + var/image/strip = image(icon = disp_icon, icon_state = "drgn_flamer_strip", dir = new_dir) + if(ammo?.reagents && length(ammo.reagents.reagent_list)) + strip.color = mix_color_from_reagents(ammo.reagents.reagent_list) + base_image.overlays += strip + return base_image diff --git a/code/modules/vehicles/hardpoints/secondary/flamer.dm b/code/modules/vehicles/hardpoints/secondary/flamer.dm index 6039a487e605..661c9724667a 100644 --- a/code/modules/vehicles/hardpoints/secondary/flamer.dm +++ b/code/modules/vehicles/hardpoints/secondary/flamer.dm @@ -40,6 +40,11 @@ scatter = 6 fire_delay = 3.0 SECONDS + var/flame_mode = FLAME_MODE_STREAM + var/glob_fuel_cost = 3 + /// The reagent-based ammo/fuel deduction is done manually in handle_fire() + ammo_cost_per_shot = 0 + /obj/item/hardpoint/secondary/small_flamer/try_fire(atom/target, mob/living/user, params) if(get_turf(target) in owner.locs) to_chat(user, SPAN_WARNING("The target is too close.")) @@ -47,24 +52,29 @@ return ..() -/obj/item/hardpoint/secondary/small_flamer/handle_fire(atom/target, mob/living/user, params) - //step forward along path so flame starts outside hull - var/list/turfs = get_line(get_origin_turf(), get_turf(target)) - var/turf/origin_turf - for(var/turf/turf as anything in turfs) - if(turf in owner.locs) - continue - origin_turf = turf - break - - var/distance = get_dist(origin_turf, get_turf(target)) - var/fire_amount = min(ammo.current_rounds, distance+1, max_range) - ammo.current_rounds -= fire_amount - new /obj/flamer_fire(origin_turf, create_cause_data(initial(name), user), null, fire_amount, null, FLAMESHAPE_LINE, target) +// Toggles between FLAME_MODE_STREAM and FLAME_MODE_GLOB +/obj/item/hardpoint/secondary/small_flamer/toggle_fire_mode(mob/user) + flame_mode = (flame_mode == FLAME_MODE_STREAM) ? FLAME_MODE_GLOB : FLAME_MODE_STREAM + to_chat(user, SPAN_NOTICE("You switch \the [src] to [flame_mode == FLAME_MODE_STREAM ? "stream" : "glob shot"] mode.")) - play_firing_sounds() +/obj/item/hardpoint/secondary/small_flamer/handle_fire(atom/target, mob/living/user, params) + if(flame_mode == FLAME_MODE_STREAM) + return fire_flame_stream(target, user, max_range) - COOLDOWN_START(src, fire_cooldown, fire_delay) + var/datum/reagent/chem = LAZYACCESS(ammo.reagents?.reagent_list, 1) + if(!chem || ammo.reagents.get_reagent_amount(chem.id) < glob_fuel_cost) + click_empty(user) + return NONE + ammo.reagents.remove_reagent(chem.id, glob_fuel_cost) + sync_ammo_from_reagents() + return ..() - return AUTOFIRE_CONTINUE +// Tags the freshly-generated glob projectile's ammo datum with the id of whichever fuel is actually loaded +/obj/item/hardpoint/secondary/small_flamer/generate_bullet(mob/user, turf/origin_turf) + . = ..() + var/obj/projectile/fired = . + if(istype(fired?.ammo, /datum/ammo/flamethrower/tank_flamer_secondary)) + var/datum/ammo/flamethrower/tank_flamer_secondary/glob_ammo = fired.ammo + var/datum/reagent/chem = LAZYACCESS(ammo.reagents?.reagent_list, 1) + glob_ammo.loaded_chem_id = chem?.id diff --git a/code/modules/vehicles/tank/tank.dm b/code/modules/vehicles/tank/tank.dm index 2b5f7e7822b4..3d38be6c0c7a 100644 --- a/code/modules/vehicles/tank/tank.dm +++ b/code/modules/vehicles/tank/tank.dm @@ -122,12 +122,14 @@ /obj/vehicle/multitile/proc/toggle_door_lock, /obj/vehicle/multitile/proc/activate_horn, /obj/vehicle/multitile/proc/cycle_hardpoint, + /obj/vehicle/multitile/proc/toggle_hardpoint_fire_mode, )) else if(seat == VEHICLE_GUNNER) add_verb(M.client, list( /obj/vehicle/multitile/proc/cycle_hardpoint, /obj/vehicle/multitile/proc/toggle_gyrostabilizer, /obj/vehicle/multitile/proc/toggle_slave_secondary_to_driver, + /obj/vehicle/multitile/proc/toggle_hardpoint_fire_mode, )) @@ -146,12 +148,14 @@ /obj/vehicle/multitile/proc/toggle_door_lock, /obj/vehicle/multitile/proc/activate_horn, /obj/vehicle/multitile/proc/cycle_hardpoint, + /obj/vehicle/multitile/proc/toggle_hardpoint_fire_mode, )) else if(seat == VEHICLE_GUNNER) remove_verb(M.client, list( /obj/vehicle/multitile/proc/cycle_hardpoint, /obj/vehicle/multitile/proc/toggle_gyrostabilizer, /obj/vehicle/multitile/proc/toggle_slave_secondary_to_driver, + /obj/vehicle/multitile/proc/toggle_hardpoint_fire_mode, )) //Called when players try to move vehicle From e524385361c9bc98e089aaaaf1c8e05e74656355 Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Sat, 4 Jul 2026 18:31:17 -0300 Subject: [PATCH 44/60] Turret Smoke Launcher -> Turret Flare Launcher --- code/datums/ammo/misc.dm | 28 ++++- code/datums/supply_packs/explosives.dm | 8 +- code/datums/supply_packs/vehicle_ammo.dm | 20 ++-- .../vending/vendor_types/crew/vehicle_crew.dm | 4 +- code/game/objects/items/storage/belt.dm | 2 +- .../hardpoints/hardpoint_ammo/flare_ammo.dm | 13 ++ .../hardpoints/hardpoint_ammo/smoke_ammo.dm | 13 -- .../vehicles/hardpoints/holder/tank_turret.dm | 111 ++++++++++++------ .../interior/interactable/weapons_loader.dm | 18 ++- .../vehicles/multitile/multitile_verbs.dm | 2 +- maps/map_files/Tyrargo_Rift/Tyrargo_Rift.dmm | 4 +- 11 files changed, 149 insertions(+), 74 deletions(-) create mode 100644 code/modules/vehicles/hardpoints/hardpoint_ammo/flare_ammo.dm delete mode 100644 code/modules/vehicles/hardpoints/hardpoint_ammo/smoke_ammo.dm diff --git a/code/datums/ammo/misc.dm b/code/datums/ammo/misc.dm index 06539abeff5c..c69aa18bc4b6 100644 --- a/code/datums/ammo/misc.dm +++ b/code/datums/ammo/misc.dm @@ -361,6 +361,29 @@ damage = 2.5 flare_type = /obj/item/device/flashlight/flare/on/starshell_ash +// Fired from the tank turret's flare launcher +/datum/ammo/flare/starshell/burst + name = "star shell burst" + max_range = 7 + +/datum/ammo/flare/starshell/burst/on_hit_mob(mob/M, obj/projectile/P) + detonate(get_turf(M), P) + +/datum/ammo/flare/starshell/burst/on_hit_obj(obj/O, obj/projectile/P) + detonate(get_turf(O), P) + +/datum/ammo/flare/starshell/burst/on_hit_turf(turf/T, obj/projectile/P) + if(T.density && isturf(P.loc)) + detonate(P.loc, P) + else + detonate(T, P) + +/datum/ammo/flare/starshell/burst/do_at_max_range(obj/projectile/P, mob/firer) + detonate(get_turf(P), P) + +/datum/ammo/flare/starshell/burst/proc/detonate(turf/hit_turf, obj/projectile/fired_projectile) + create_shrapnel(hit_turf, 8, fired_projectile.dir, 360, /datum/ammo/flare/starshell, fired_projectile.weapon_cause_data, FALSE, 0) + /datum/ammo/flare/starshell/set_bullet_traits() LAZYADD(traits_to_give, list( BULLET_TRAIT_ENTRY(/datum/element/bullet_trait_iff), @@ -465,11 +488,6 @@ /datum/ammo/grenade_container/rifle flags_ammo_behavior = NO_FLAGS -/datum/ammo/grenade_container/smoke - name = "smoke grenade shell" - nade_type = /obj/item/explosive/grenade/smokebomb - icon_state = "smoke_shell" - /datum/ammo/grenade_container/tank_glauncher max_range = 8 diff --git a/code/datums/supply_packs/explosives.dm b/code/datums/supply_packs/explosives.dm index bf17a60a7880..b987103b2bfb 100644 --- a/code/datums/supply_packs/explosives.dm +++ b/code/datums/supply_packs/explosives.dm @@ -238,8 +238,14 @@ contains = list( /obj/item/storage/box/packet/flare, /obj/item/storage/box/packet/flare, + /obj/item/storage/box/packet/flare, + /obj/item/storage/box/packet/flare, + /obj/item/storage/box/packet/flare, + /obj/item/storage/box/packet/flare, + /obj/item/storage/box/packet/flare, + /obj/item/storage/box/packet/flare, ) - cost = 40 + cost = 10 containertype = /obj/structure/closet/crate/explosives containername = "M74 AGM-Star Shell Grenade Crate" group = "Explosives" diff --git a/code/datums/supply_packs/vehicle_ammo.dm b/code/datums/supply_packs/vehicle_ammo.dm index 43ce36ec2b64..04f968733522 100644 --- a/code/datums/supply_packs/vehicle_ammo.dm +++ b/code/datums/supply_packs/vehicle_ammo.dm @@ -83,16 +83,22 @@ containername = "M92T Grenade Launcher ammo crate" group = "Vehicle Ammo" +// Mimicks the star shell pack in the explosive sections. Also, does it really make sense to have those cost 4k ?? /datum/supply_packs/ammo_slauncher - name = "M34A2-A Multipurpose Turret smoke screen magazines (x4)" + name = "M74 AGM-S star shell packets (x8)" contains = list( - /obj/item/ammo_magazine/hardpoint/turret_smoke, - /obj/item/ammo_magazine/hardpoint/turret_smoke, - /obj/item/ammo_magazine/hardpoint/turret_smoke, + /obj/item/storage/box/packet/flare, + /obj/item/storage/box/packet/flare, + /obj/item/storage/box/packet/flare, + /obj/item/storage/box/packet/flare, + /obj/item/storage/box/packet/flare, + /obj/item/storage/box/packet/flare, + /obj/item/storage/box/packet/flare, + /obj/item/storage/box/packet/flare, ) - cost = 20 - containertype = /obj/structure/closet/crate/ammo - containername = "M34A2-A Multipurpose Turret smoke screen ammo crate" + cost = 10 + containertype = /obj/structure/closet/crate/explosives + containername = "M34A2-A Multipurpose Turret flare ammo crate" group = "Vehicle Ammo" /datum/supply_packs/ammo_towlauncher 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 a3a683d0a16f..d65f84178fc3 100644 --- a/code/game/machinery/vending/vendor_types/crew/vehicle_crew.dm +++ b/code/game/machinery/vending/vendor_types/crew/vehicle_crew.dm @@ -450,8 +450,8 @@ GLOBAL_LIST_INIT(cm_vending_clothing_vehicle_crew, list( /obj/effect/essentials_set/tank/turret spawned_gear_list = list( /obj/item/hardpoint/holder/tank_turret, - /obj/item/ammo_magazine/hardpoint/turret_smoke, - /obj/item/ammo_magazine/hardpoint/turret_smoke, + /obj/item/storage/box/packet/flare, + /obj/item/storage/box/packet/flare, ) /obj/effect/essentials_set/apc/dualcannon diff --git a/code/game/objects/items/storage/belt.dm b/code/game/objects/items/storage/belt.dm index 4e6511195a5a..fe7ed404a6c5 100644 --- a/code/game/objects/items/storage/belt.dm +++ b/code/game/objects/items/storage/belt.dm @@ -2832,7 +2832,7 @@ /obj/item/ammo_magazine/hardpoint/towlauncher, /obj/item/ammo_magazine/hardpoint/m56_cupola, /obj/item/ammo_magazine/hardpoint/tank_glauncher, - /obj/item/ammo_magazine/hardpoint/turret_smoke, + /obj/item/storage/box/packet/flare, /obj/item/ammo_magazine/hardpoint/boyars_dualcannon, /obj/item/ammo_magazine/hardpoint/flare_launcher, ) diff --git a/code/modules/vehicles/hardpoints/hardpoint_ammo/flare_ammo.dm b/code/modules/vehicles/hardpoints/hardpoint_ammo/flare_ammo.dm new file mode 100644 index 000000000000..099fef47c49e --- /dev/null +++ b/code/modules/vehicles/hardpoints/hardpoint_ammo/flare_ammo.dm @@ -0,0 +1,13 @@ +/obj/item/ammo_magazine/hardpoint/turret_flare + name = "Turret Flare Screen Magazine" + desc = "The turret's internal star shell holders. Loaded directly with individual M74 AGM-S star shells or star shell packets, not swapped as a whole magazine." + caliber = "flare" + icon = 'icons/obj/items/weapons/guns/ammo_by_faction/USCM/vehicles.dmi' + icon_state = "slauncher_1" + w_class = SIZE_LARGE + default_ammo = /datum/ammo/flare/starshell/burst + max_rounds = 6 + gun_type = /obj/item/hardpoint/holder/tank_turret + +/obj/item/ammo_magazine/hardpoint/turret_flare/update_icon() + icon_state = "slauncher_[current_rounds <= 0 ? "0" : "1"]" diff --git a/code/modules/vehicles/hardpoints/hardpoint_ammo/smoke_ammo.dm b/code/modules/vehicles/hardpoints/hardpoint_ammo/smoke_ammo.dm deleted file mode 100644 index 04846d74c423..000000000000 --- a/code/modules/vehicles/hardpoints/hardpoint_ammo/smoke_ammo.dm +++ /dev/null @@ -1,13 +0,0 @@ -/obj/item/ammo_magazine/hardpoint/turret_smoke - name = "Turret Smoke Screen Magazine" - desc = "A smoke grenades magazine used by tank turret." - caliber = "grenade" - icon = 'icons/obj/items/weapons/guns/ammo_by_faction/USCM/vehicles.dmi' - icon_state = "slauncher_1" - w_class = SIZE_LARGE - default_ammo = /datum/ammo/grenade_container/smoke - max_rounds = 10 - gun_type = /obj/item/hardpoint/holder/tank_turret - -/obj/item/ammo_magazine/hardpoint/turret_smoke/update_icon() - icon_state = "slauncher_[current_rounds <= 0 ? "0" : "1"]" diff --git a/code/modules/vehicles/hardpoints/holder/tank_turret.dm b/code/modules/vehicles/hardpoints/holder/tank_turret.dm index 6156fafb3842..35a9e538e48f 100644 --- a/code/modules/vehicles/hardpoints/holder/tank_turret.dm +++ b/code/modules/vehicles/hardpoints/holder/tank_turret.dm @@ -1,6 +1,6 @@ /obj/item/hardpoint/holder/tank_turret name = "\improper M34A2-A Multipurpose Turret" - desc = "The centerpiece of the tank. Designed to support quick installation and deinstallation of various tank weapon modules. Has inbuilt smoke screen deployment system." + desc = "The centerpiece of the tank. Designed to support quick installation and deinstallation of various tank weapon modules. Has inbuilt flare launcher." icon = 'icons/obj/vehicles/tank.dmi' icon_state = "tank_turret_0" @@ -14,8 +14,8 @@ activatable = TRUE - ammo = new /obj/item/ammo_magazine/hardpoint/turret_smoke - max_clips = 2 + ammo = new /obj/item/ammo_magazine/hardpoint/turret_flare + max_clips = 0 use_muzzle_flash = FALSE w_class = SIZE_MASSIVE @@ -53,19 +53,24 @@ var/gyro = FALSE - // How long the windup is before the turret rotates - var/rotation_windup = 15 - // Used during the windup - var/rotating = FALSE + /// Index into accepted_hardpoints, used by /mob/living/carbon/verb/cycle_tank_weapon_hardpoint() to track cycling progress. + var/debug_cycle_index = 0 scatter = 4 - gun_firemode = GUN_FIREMODE_BURSTFIRE - gun_firemode_list = list( - GUN_FIREMODE_BURSTFIRE, - ) - burst_amount = 2 - burst_delay = 1.0 SECONDS - extra_delay = 13.0 SECONDS + fire_delay = 7.0 SECONDS + + // Total width, in degrees, of the arc in front of the hull that each star shell can land within. + var/flare_spread = 45 + // Minimum/maximum tiles in front of the hull (not the turret's own facing) each star shell can land. + var/flare_range_min = 5 + var/flare_range_max = 7 + +/obj/item/hardpoint/holder/tank_turret/Initialize() + . = ..() + current_angle = dir2angle(dir) + desired_angle = current_angle + // Loaded shell-by-shell rather than swapped as a whole magazine. + ammo_type = null /obj/item/hardpoint/holder/tank_turret/update_icon() var/broken = (health <= 0) @@ -131,6 +136,14 @@ return /obj/item/hardpoint/holder/tank_turret/attackby(obj/item/I, mob/user) + if(istype(I, /obj/item/explosive/grenade/high_explosive/airburst/starshell)) + load_star_shell(I, user) + return TRUE + + if(istype(I, /obj/item/storage/box/packet/flare)) + load_star_shell_packet(I, user) + return TRUE + if(istype(I, /obj/item/powerloader_clamp)) var/obj/item/powerloader_clamp/PC = I if(!PC.linked_powerloader) @@ -147,16 +160,53 @@ return TRUE ..() +// Loads a single loose star shell directly into the turret's internal holders. +/obj/item/hardpoint/holder/tank_turret/proc/load_star_shell(obj/item/explosive/grenade/high_explosive/airburst/starshell/shell, mob/user) + if(shell.active) + to_chat(user, SPAN_WARNING("You can't load a primed star shell into \the [src]!")) + return + if(ammo.current_rounds >= ammo.max_rounds) + to_chat(user, SPAN_WARNING("\The [src]'s shell holders are already full.")) + return + user.visible_message(SPAN_NOTICE("[user] loads \a [shell] into \the [src]."), SPAN_NOTICE("You load \a [shell] into \the [src]'s shell holders.")) + playsound(loc, 'sound/weapons/gun_shotgun_shell_insert.ogg', 25, TRUE) + ammo.current_rounds++ + qdel(shell) + +// Loads as many star shells as fit out of a star shell packet into the turret's internal holders. +/obj/item/hardpoint/holder/tank_turret/proc/load_star_shell_packet(obj/item/storage/box/packet/flare/packet, mob/user) + var/space_left = ammo.max_rounds - ammo.current_rounds + if(space_left <= 0) + to_chat(user, SPAN_WARNING("\The [src]'s shell holders are already full.")) + return + + var/list/shells_to_load = list() + for(var/obj/item/explosive/grenade/high_explosive/airburst/starshell/shell in packet.contents) + shells_to_load += shell + if(length(shells_to_load) >= space_left) + break + + if(!length(shells_to_load)) + to_chat(user, SPAN_WARNING("\The [packet] has no star shells left to load.")) + return + + for(var/obj/item/explosive/grenade/high_explosive/airburst/starshell/shell as anything in shells_to_load) + qdel(shell) + + ammo.current_rounds += length(shells_to_load) + packet.update_icon() + user.visible_message(SPAN_NOTICE("[user] loads [length(shells_to_load)] star shell\s from \the [packet] into \the [src]."), SPAN_NOTICE("You load [length(shells_to_load)] star shell\s from \the [packet] into \the [src]'s shell holders.")) + playsound(loc, 'sound/weapons/gun_shotgun_shell_insert.ogg', 25, TRUE) /obj/item/hardpoint/holder/tank_turret/get_tgui_info() var/list/data = list() - data += list(list( // turret smokescreen data - "name" = "M34A2-A Turret Smoke Screen", + data += list(list( // turret flare launcher data + "name" = "M34A2-A Turret Flare Launcher", "health" = health <= 0 ? null : floor(get_integrity_percent()), "uses_ammo" = TRUE, - "current_rounds" = ammo.current_rounds / 2, - "max_rounds"= ammo.max_rounds / 2, + "current_rounds" = ammo.current_rounds, + "max_rounds"= ammo.max_rounds, "mags" = LAZYLEN(backup_clips), "max_mags" = max_clips, )) @@ -275,26 +325,9 @@ user.client.set_pixel_x(-1 * AM.view_tile_offset * 32) user.client.set_pixel_y(0) +// lobs a single starshell forwards /obj/item/hardpoint/holder/tank_turret/try_fire(atom/target, mob/living/user, params) - var/turf/L - var/turf/R - switch(owner.dir) - if(NORTH) - L = locate(owner.x - 2, owner.y + 4, owner.z) - R = locate(owner.x + 2, owner.y + 4, owner.z) - if(SOUTH) - L = locate(owner.x + 2, owner.y - 4, owner.z) - R = locate(owner.x - 2, owner.y - 4, owner.z) - if(EAST) - L = locate(owner.x + 4, owner.y + 2, owner.z) - R = locate(owner.x + 4, owner.y - 2, owner.z) - else - L = locate(owner.x - 4, owner.y + 2, owner.z) - R = locate(owner.x - 4, owner.y - 2, owner.z) - - if(shots_fired) - target = R - else - target = L - + var/fire_angle = dir2angle(owner.dir) + rand(-flare_spread * 0.5, flare_spread * 0.5) + var/fire_range = rand(flare_range_min, flare_range_max) + target = get_angle_target_turf(owner, fire_angle, fire_range) return ..() diff --git a/code/modules/vehicles/interior/interactable/weapons_loader.dm b/code/modules/vehicles/interior/interactable/weapons_loader.dm index 2c26b3e6de0e..7b77865c6820 100644 --- a/code/modules/vehicles/interior/interactable/weapons_loader.dm +++ b/code/modules/vehicles/interior/interactable/weapons_loader.dm @@ -14,7 +14,19 @@ // Loading new magazines /obj/structure/weapons_loader/attackby(obj/item/I, mob/user) - if(!istype(I, /obj/item/ammo_magazine/hardpoint)) + // The tank turret's flare launcher isn't loaded via a swappable magazine so it + // shouldn't be picked up by the generic magazine-matching loop below. Delegate loose star shells and + // star shell packets straight to the turret's own attackby(). + if(istype(I, /obj/item/explosive/grenade/high_explosive/airburst/starshell) || istype(I, /obj/item/storage/box/packet/flare)) + if(!skillcheck(user, SKILL_VEHICLE, SKILL_VEHICLE_LARGE)) + to_chat(user, SPAN_NOTICE("You have no idea how to operate this thing!")) + return + var/obj/item/hardpoint/holder/tank_turret/turret = locate() in vehicle?.hardpoints + if(!turret) + return ..() + return turret.attackby(I, user) + + if(!istype(I, /obj/item/ammo_magazine)) return ..() if(!skillcheck(user, SKILL_VEHICLE, SKILL_VEHICLE_LARGE)) @@ -24,10 +36,10 @@ // Check if any of the hardpoints accept the magazine var/obj/item/hardpoint/reloading_hardpoint = null for(var/obj/item/hardpoint/H in vehicle.get_hardpoints_with_ammo()) - if(QDELETED(H) || QDELETED(H.ammo)) + if(QDELETED(H)) continue - if(istype(I, H.ammo.type)) + if(H.accepts_magazine(I)) reloading_hardpoint = H break diff --git a/code/modules/vehicles/multitile/multitile_verbs.dm b/code/modules/vehicles/multitile/multitile_verbs.dm index 89a5f5329c4d..5807a9dcfc3a 100644 --- a/code/modules/vehicles/multitile/multitile_verbs.dm +++ b/code/modules/vehicles/multitile/multitile_verbs.dm @@ -4,7 +4,7 @@ //Used to swap which module a position is using -//e.g. swapping primary gunner from the minigun to the smoke launcher +//e.g. swapping primary gunner from the minigun to the flare launcher /obj/vehicle/multitile/proc/switch_hardpoint() set name = "Change Active Hardpoint" set category = "Vehicle" diff --git a/maps/map_files/Tyrargo_Rift/Tyrargo_Rift.dmm b/maps/map_files/Tyrargo_Rift/Tyrargo_Rift.dmm index cf030992a157..f80ac950d5e5 100644 --- a/maps/map_files/Tyrargo_Rift/Tyrargo_Rift.dmm +++ b/maps/map_files/Tyrargo_Rift/Tyrargo_Rift.dmm @@ -56373,8 +56373,8 @@ /area/tyrargo/oob/outdoors) "cKo" = ( /obj/structure/surface/rack, -/obj/item/ammo_magazine/hardpoint/turret_smoke, -/obj/item/ammo_magazine/hardpoint/turret_smoke, +/obj/item/storage/box/packet/flare, +/obj/item/storage/box/packet/flare, /obj/item/ammo_magazine/hardpoint/m56_cupola, /obj/item/ammo_magazine/hardpoint/m56_cupola, /turf/open/auto_turf/tyrargo_grass/layer0_mud, From 079e5eca36ba78670f2ac85930c7cd94b3a0f407 Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Sat, 4 Jul 2026 18:34:10 -0300 Subject: [PATCH 45/60] LTB Rebalance: Shorter firerate & Readd to Vendor --- code/game/machinery/vending/vendor_types/crew/vehicle_crew.dm | 1 + code/modules/vehicles/hardpoints/primary/ltb.dm | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) 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 d65f84178fc3..01b7f1477bcd 100644 --- a/code/game/machinery/vending/vendor_types/crew/vehicle_crew.dm +++ b/code/game/machinery/vending/vendor_types/crew/vehicle_crew.dm @@ -141,6 +141,7 @@ GLOBAL_LIST_INIT(cm_vending_vehicle_crew_tank, list( list("AC3-E Autocannon", 0, /obj/effect/essentials_set/tank/autocannon, VEHICLE_PRIMARY_AVAILABLE, VENDOR_ITEM_RECOMMENDED), list("DRG-N Offensive Flamer Unit", 0, /obj/effect/essentials_set/tank/dragonflamer, VEHICLE_PRIMARY_AVAILABLE, VENDOR_ITEM_REGULAR), list("LTAA-AP Minigun", 0, /obj/effect/essentials_set/tank/gatling, VEHICLE_PRIMARY_AVAILABLE, VENDOR_ITEM_REGULAR), + list("LTB Cannon", 0, /obj/effect/essentials_set/tank/ltb, VEHICLE_PRIMARY_AVAILABLE, VENDOR_ITEM_RECOMMENDED), list("SECONDARY WEAPON", 0, null, null, null), list("M92T Grenade Launcher", 0, /obj/effect/essentials_set/tank/tankgl, VEHICLE_SECONDARY_AVAILABLE, VENDOR_ITEM_REGULAR), diff --git a/code/modules/vehicles/hardpoints/primary/ltb.dm b/code/modules/vehicles/hardpoints/primary/ltb.dm index b05a094fd605..1b51bdb8d9f9 100644 --- a/code/modules/vehicles/hardpoints/primary/ltb.dm +++ b/code/modules/vehicles/hardpoints/primary/ltb.dm @@ -40,7 +40,8 @@ ) scatter = 2 - fire_delay = 20.0 SECONDS + fire_delay = 15.0 SECONDS + /// How long the lock-on charge takes before firing is allowed. var/f_aiming_time = 4 SECONDS /// Stashed for generate_bullet() below, which doesn't receive target directly. From 2bdcb6bf16c9c600ccbfff40ed5c6bbc39987c4a Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Sat, 4 Jul 2026 18:34:38 -0300 Subject: [PATCH 46/60] LZR-N & DRG-N: Custom fuel greyscale sprite overlays --- icons/obj/vehicles/hardpoints/tank.dmi | Bin 11204 -> 13207 bytes icons/obj/vehicles/tank.dmi | Bin 182540 -> 215725 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/icons/obj/vehicles/hardpoints/tank.dmi b/icons/obj/vehicles/hardpoints/tank.dmi index c9af30849eb84fd3720a6307dbde818d81785fc7..eb6512e616297799a6688ac1ec3a2d72203d89af 100644 GIT binary patch literal 13207 zcmZvDWmp^E6K?QQ+$j_-TC})(aVr#eclY3KKinM(#R>%X;4a18U4pxF)BpW&zuf29 z?4D`Bd0|1`cDG6ZvURKl)i)Z=}QRj_b}3lWhS z){3Vdsj?H0Vawrd9H*<^G?k9OJuVSWT6Nd?^g(05nV>YutCY8xGaAAB?< z-~;BE`#AQTwT6nlEbRU8+B_t4Fv3hdiO%HN&R{kn- z$&hE<#|@9=g73#VLdZq$&aa0p#swzo%xUWUuLW!>wIH9?-d>+Cd!mU$g}9$!+aK%= z`=juBqVO2tl^~Bm;}>c7#*6v+O1?5z>U&(5+JmJRxF1sqfZ4fu9K14*Y6l00!HtbP zIREy(qW8~qADwY^i+^Zc;q+TMCrsRo#Q&w@t zhI47<^OAZ`3kRH^OKYG*aXmXhlYzJ|Y?S|PZW>ZjQtoy_kAR@y;Ed}YOZ1mZ?C$RF z`X+t#h9=PSMK4C1Z%(IfCOw^;((Q&D2M32CDkp|XFIppx?Dq9BCFj|f+;fm}5F*~LGp=wUH1FrWmiuIeJK6)R+MoQGR~ z_zRtM67uCkOhjWs7O{0fV>V1qjt?9>QaDydJmptBXW^VKJkjtg!u>GqS^`G=dg+HV z6aL{9;_aI}*RWe*+w0Od4VDzAsnGG!m8Op}yh=Swz3JNn0LNneiDv^}ig#9v<6Bh3 zpsfD)8C>qL`_3n^(o)W==%poYYAhFL=a7hq54Cl5GBPqwR`07W6H6x`-4>N~E<)7Z z-Q5q`RI|2lVUiNjAud4-r&LsZ+{^fuEL`Fw{p>D~sK-V_&^YcT$lYMx8 zzUKd1U|^WC6h2XuP21uHnG}Xj>H8D$wZ!Zjrypycx}G6&^;zfp zVLsUp^ZmV3ozQU)leleG5{{{)_3UQBW9?c93Z`4-P2C|4*8H`+AhPlhk zZLo=EGji&i&*NWjfB>r%JXFZ-K$$N**7X;A{O6ay zw>9s80sBSDTAhap)IIzTm2Fni>3Mk$bq2b+^48Yn<&_d+GD$Qr)#l?wP@#&g|4528 zAunpGB8nu6&97S*fakV`aF0hq)H1u)(Xn$qZ{sNjgVD5gyyo=r z8`4+Kge4{BCG5}Bd z_Sc-8rd-_LBP9**-I_9mtfFnZ4}Y5MkN8cc+N1xj8z9}0GH4zjpDnf(I>Tl3rp^DA zmzUT6zB$JYw*F*p;AU39NK!IP{FPp{cxZ*5p1%ERgdy`_nkwJdTR{dL*=>Nxo|to> z>;)Q*yiO;ql`yfSijhO5gkR;*sHmc2)<;y}5`KJ3PQm4^Tu9<+X$)u~E{0WJnIBn2 zQ-Z}DGP)w{)e|8#iTLktD&*%Z5%Ml;O6U~TfaWb;gQRs_P95*tqEz0&9Ku1qy`YH; z8-j;)>G%4}ok8}F>UeQ0D`zW=!l|K96oS=G80PrQ%} zFFX+3%HU3rNFXOma*~?EuyosJCnp8#_^UnnFrZ@n-%LggYn*IcE7&HQanJoP7Vv-1&P2c%f6eC_Q*8(Mk3a6yi)bIe8mzTBc z`s!+G{m-5Fh5auCc6K1V#DkgXrQVzZVq`^e$Gljso*N+1@o_6j&~MgqPWUec+#!a@ z&_LnJiJ#LUH9V{S(gj!kf5)q~moTc35a|cYaUj$u5t)gPMqOYt5ABbd7C6>FqrAa)gXmA*)m z@XCcr{{WqX#g;27_T0A9%qpBduFWDhoIT$b9BguTX2koM!w~pQK!WA=x7r8(*Sp&i zihFX#qxA;*$lo;2R?3*y2A=`A21)m$P&@xyG3G0E=$V=4Z+y-~C@C#HLbAgQsjDnL zprfPxt|*EqdV`Dj4}XQ}wtYCIR_0}w zet4Tb@f_0>OiHQ#8D@R^&GR{rL+=5-(<+pq2K>1*pm z>dF=Lrr)TIrxo%g{q?sjR|mzqPbKMf&DiN8Dtvl6zjb-%x!Mh)p5<$$t6Vu~gAv>< zqOF~&+ZuF@S+UreOVQlV*zrooK=1sag~^;fe4_%cUd-v!7r)Q_5oA(M$KwK_t6qns z_eGFP8BCefY*NY6wP2`3BYGZ6hff;{|HZAu7Pt!>wa&fpm^WDdq`>!x#gwbTF*d#4 zxwX$Bo`sq%DSw|{FpvZNgZj3aMKsvY+qgC8=f=_rVH(9E^kt79r)Y!XA?|1^1`A*s z9|ypPN1VI4yen_c^RMRu30C91<8icb)Hq_5g*@L^v3kK9IHRXhJD+Wps+a&06PeG; zz$TDt&>zgtWMmUB1Y6qTteP@}BqR!Uc9r9k(f!60bac`)#9wK@G_$i0n0-iH4p?X- zSe;&#k6vQLsVS~i@soPltxAWyF%uR!bO-v-io6ojpDZdeYY*72|AyQ^F zft%HqEhWtNi`VYT%D#aKPB6GX4ak|9WAhGtVaU?}NN;Pf?;VSUftHxa7D~*|ukGyu zKq5OkJGLeDD&1iQPd0Q*jGMh92bjBx7T-|Ka3jfts2gsyXIRS+WF=?|m7aN`5HXSid+FqdrY2oe z^zYN29x*7^8yX+~4os`CRdu)FlK1xJ$4W3-6#_EeHb~O(DAtg7kX&8fn)Q(sW{-@@ zMlCkp!j&ii&8&F;;Y9(CY}a59_uIm&{;Qk{w6IDELm z#r6y80sEkiz=C>xu3%wZ@G?5Q{F9l1CDN6^3G(iKX6U|`S(p6R`L>4*S$XW%5i;v4pC80D>-I8^k z7Is*en2fD-JINs;&pNLrc7H8YGtja=v}RaNm`9@nZP;?zB+62*UuiX6K0d0S$()ZK zlhA?~kQ)T@*`?Q&WyI3pllc&#(P%s|yGjRnLPXr&f&8I&_}1n>Lw)u7cKBh8DU@*Z zk7eg{Y-irXm*%$le4TZShdCN4Er?m&3dLf^o3;^G)}yeK(~E-}XF)*$mnlcqINN0v z?a7L}HtD1va{;zB4nsC}_0F*xAj4-GGxGPJnhoG%t-PXIOBTG(^_Jc4DcV*B3)0T{ zL1~Ufi;9aA6BEUtn4$exfmctCGAGgJ@$u0aqj|-y%T7mlavYjL z)TfVUnkE7(t(`r3SjfGF96xil*qm6(j*JeEWKxCiCSEx|-9s$uFnES^Pgi>Ve3&7Z z{mEnxLP_TvgRi~vKN~`@%h`YU8OGXX-98x8Y;?xalFIHwCWu@7(<42&#*A#|f#?U! zsi(<(!HWoFb@6@Tq&2HKWL@@X6# zk1HoC6UD1%RhQp*Vjt<=263jD`DJ-NwOE8IgWa@}mP~tQe7kn|CO|&wNL35hVIfpb zjrIQ(3>#@!xacMS-YKLdu;JN|=YG5c5{1l4p%D;P;tg?J%V>Jx8Rf_|^J*G??<1^_o zH`mr|Xyk5W5PTMJ7HG*MqlA#w)ZaT=Kb;dj3WT||_OPSh~9V0xNB7Y0E=pKO`(LqQz+hIKr7;tmJ1@>e#@kf^bH#`@qCjc;Nx9U!M zfa%%Xz*-~u=+H77^1z`c3tsGr8LV;-|LiR9P|5ux)LAsQ62=L4sc@br5 z5f9ct3#^^G#|YLjnmVr0M~TK~!8>%>fi-GE_7uFH@hXT_u<4|-I9h|hJ6&M{YcmhlNcl>&ZGg@-Q*QNc2(H z?VtbYNGh`D$oQ}T$zV1AuUx48k08yFFux--*?HJM(L#)i-hAGun`4Ok6E>R0RKBa+ z2zPoPNV;TD@7l7N9H0`#$|>!2q?~+5-)n4aEI_l_^L_iCA68tPkc~XW<$J#|GU})J zuPaQ?M}<9RF6_kXSi%h#n`0TUF!PL}I*qoE>d4Eg2M(+mmaG|`ZuZCkgXia=)&}C< zbRPXNkK32%ZRg13Dx~*C2<@lax|N5ME|}c6hKD*o5Ew3img{~&z(8Ie_dqGHCP59? z;1}%f-;jD?;26)+{uuWl$je_$y|@wSMA5G`>LWz9?yNXGKBCETiW2ZQPZ*=C<(KhU zD}))a?&pt#F+Q<%de0pxED$T)7tC()QAg-)F$^BN1tz08ck_UO6l=hAb+~HPw;gQN zmhxbqyFv2s_~@ix(|Ynq0K`L1f0qx*HjLU~bk#ot$zWySa5oG%I$ahekw;ZuxT zA2n62#UPhfYI>sVV2Zn94v&2u%hJQckq zF#2U@JDuMKG0UUWAM=1#4*$c{j~}!1a|xIPN@fiY@wd14B@N<X?T+Dub6Fq$3$j znv6M$6YojXDDWaesjesf6)T7M-^d>79@;tP<`WWA!wRaEX$rdSk#`I9x&sQq3><_u zP>EB*fZyCkj&Qbner+{bES{{aG%u5JnDYGiR+IUhR5G!B(!wsdfbs@~%Kl2YT5BZd zX}PImIZbE?hA-f4kBz^1410mRg}nY%$x0r1#!zjFFk~9b4o<=e`8ehW{N4$w>gwO_ zqbIKCiESRm!+b z^o)EXVLoXfT?%H3Tt{D?m}1GE+bx0ij)ZDUQ>C)@s+)QaM*QgG$D1uI++CeIX8By9 z=ynjs4|}?rr8L{K2`%ZC0tpubEh!rVGUmUBbCBCA-QU~Iu zj}d(WvT^Dkw%~+13mZCiLK)e}t(mnFPmkM!RbIajgBUq=j}%3dw6B=dhza!;6Z1m{ z&9*ILQ-xA3wo`xaZ?6pWe|J=+K4Hw+-TB{9fBRNI7-ODxuyIwuspRad$Hem9zqNyj zZds!V-8?-Ncsc^N9izZVMUAn=%Y2MX_*$S4Q-dErx>pw{NJxvH*|DJ`GC?LO^sPEiJ-!3Og z={PYz+vi9!-&RfdxU6!*%tidBX;Tk@M;xYNRIf;{D6@)}(`yrv)g}r(u-PLkDr<~& z^z}?TsCgqiI+AhLLr%-;R<^4@8l(H6rmE!nle)pp&G|vC$C4u{F;%j#Z0h#$dC=*& z-i|1iK?9_$wj;p3nbQ=Rp`QXb)e#tYvgTSl+Oy3%c|uGWQ5R?_XYEJ%wl13JBZ}AN1Yo}UI?kWRoeiHEs96fCD%S61@NagBn`z9L z-ExGy*f(C!Uf7{az`-Mivl@y-32-3|J#QHqad2^39cL-Tta5SasUZ#Rs?K!Qxi|c! z{-2>BkuR<)6ZJ<+7NNacCOp6!I*&Cs0;xDsvzKPA&%&;X_SYPlL~3zc6$eW>xgf!+ zy_bb32qz{}w_gQ%{;D!%xRSST-8O0I9MjOmOa((X6UKwxLCN#*W3)u(*sO+zNh#L| zbp(at(*EOmk<4x$-65893*7VdJ;#;+Nrbxwh!gYl<4U9blnf5iEaoqab9X&Q(JtbY zyuW&EOu9GK7j}u~Kp;2rv`9VCygh2+5VK4?Es*K(78w2;G}1(-{p3ggx@V^Zkp-5( zpNu~qN*&vbAON%OE3H_%Wb2i#?|>%+AgOix!9?5WkJx$sH;Af>R!V zV13dkr{A5at%}HtS=TdbQJS}HC<0H<-4D-YMVKz)=ctuT=F3&?BhN4$zPiWM7>hZr z*sIoXts_eJGctiEf8={6e6ZqWSz6)fQNXE#iHTv}<^P)_&qPm8@W+pNj$t>q+Z!}g z*Q%xn`U`CqgS&S7wDqG_L#1L}t8RY0QFcbJLyB>4AtxU}B#iIYWqQX8;Mc}|hs)sa zu?N2AIZbXD0z4WIj-%!|sq0#L8iR=EJvaK1Nahpxevfr|HCBV@`fn>6t*?Oh?V7=m zY^}P783^cl>+X~}?z$`3#q5_)vsv9)Akx7{j~H-PVR&cqeG1URjXJ|wr?M(70E~$- zmdgaNPbj91{Y@1vNCoST^!Hen>IVA)KYVu&JE^jl3NPxsL;8y4?%YUhAI!C4&G!5> zVnP?k;-veB%Ii8RaWY_sVu49<&rL-Fi>rmCCG`{ z+cSeYp1!uUw4k?ER50rMs(JHTgS>IBpe6d3q82$Zj}t+`Dgw$!d?sxPXqEQ%_EthN zN)*eWRS(Kj^&uzEw(8)H8Y zyR;$ooH4`2I0L9_eVf;ak^%nyJ>ufX7xyLNPOAWJd`&M3N(P4LFRdTKq{0JgMDP3n z&w>5X-={e_IbpcVkl-II2bs?@I09>%6IM_-!=&X~Bj{2eO|@NqUZ!&5Ke7tnEZ_TZy+}ytnfd$r2SmRf8dgLr3D%dhkU;oR+cO4%T)LUV6uxWsKZTmIRbOz&1 zDY^rl1@aGG&;DGj3T@cXFrKUJgsDFF5aPP>rcvmcoedFUlxMy*GD^qek{KT6g8T#_ z2udxssOZ!S-8S-%_rEu>#H z?mo_jnI|{XzzXrW6mK$$7PbwIjb&%2^TkWnElJvUIt2UN$2lBZ=SkBsFr^3&I5QoV zy>F0On~r9G3mqDg_VM+JjER&y*pVaK5RwRy!o(-)VUK$aH?Qh3c;Dl0f0#wN3wK3< zI%u|5XYNv+MN&$NnK)Yj=e^b-Od6BU{^cbn@&goB25DPse$v|-&H(Boy_Vfhg z-R1kDsi{e5WMov{N<-UOsET2kM_ALUGL}Jbcz|SQr^xP>N+=t7iMQq$0J=_jiY$Y> zhs_=mwwlr6N$^)GMD!6h82iN+fKA+@zG5Gc6H|viG%0#S_z^{`{fRHjGm(QT(ByA!TMp zZQy^G!g#&|5g$lLMJz_&O`DN>n4x|BB;d(Pz^og8&w^)=NFph*Aa*-(N3u^Vd~=8m zaQYw_gx^iY!O1x_IVqy9jt@1zghWI}P3tZ5V$R*@Kaf0P`$Lwc#lQ2}0G*7LJakyK zf41q_@1F6y9+~3fRFv85&!5;?>6^;3)lCaQ(`Rp5_q?YFd0;wp_4nf?VVG*a8LsKD zSSluU41@~z`$|H}k~G|wO?(KU%y z$s96Y>3kRV-+!n`-ey^#f`V?*^9z6X=aDM7B1Neha>=SMQnAyW2EyYiL8j*wGr#Ix z5Gz@E%|>vyK4W5GbqBN4Tz?m(z>(xD9*p1pv+)Qp@V~sv70Q(oF$@Tf6cJ9nXgE2gO;t(N&ZX5 zUD->+!oq6593WOCnc~E9933QsoYaN6HyxDR*owZrr3ZX7Ir~-oOQqqbSgi(7l2h7! zt$Q8p?FNVwyd~?oucLvvc&(73qSgua*0M@qeE*dn-}$=tesp#@jw{r8?8l)Tye$6B zcffVa!k=LHZa7y75Pw%-p)zc@TrZQBl=MZ~G-TVbpPGG39%M+$>l9SU^RrKw@@H_98R;V(ZQY($Ze^>vfGX`KX^kNg&P-!+uiCz*azmviaJ%KdAgoL$?8 zfU^*wVxp5OTjSv4<1ZglZQ5oo%NSP1^Ft8YZ2Y~$(%sf8$VY`YG<(T%-%fX5 zpYPAFAB5^ZahCpmhF&%XUGPxlGkBb~^#g*DFoJGmDB~j< zntnY@<36|kjyYaBrbd;Q#f_sU6|Xq|6-)4!;?`E9LX!f(c9gt3Ix&%Un%w~7-T&Vq zGpua95X-JeqfNl^w>IJ+2L#=0cBwn{yY-1rC*mh==LiN!H&mA#aYxj7TvS(HVgl63 zMJ{tFXLwM=u#V8cl~>{@$wv*v3_UPwXr0!MctOtN>0kf=o97=F01U+vtzG}>BlMf& zXgmFA-yKd6HgJ`7f*F~ZE+qWGRp;h`-0&Va(W%S?XL)XU0#cWSByFZSv{yPgA?ui{ zX#WZ*hk2R5_(bPbA1l#WV7xJ{;KFYrUetrq0!YPVzx;w?l>?xJA49iX7ikFow}#y& z;4?-$*f4H$?AbT1+IT?T$W+AOl!xrp9~>>fs-2P`&Ch$G{x_kY9~k9R#_| zh164ol#P%Vh>PGg*oX647nvYjtLUV0eDclthK@_1abrNgrx8QAeJ}52{bQt0Z*VZo z!wxnISyfbF$IjOTp$;x;k)k-3Uj87h*1n$Qgq6?&FTXUI-DA2pTUc6DeiSjB^VTm8Zh`w^hn zRL?5FlfKh`JL{pFa-Tv??m))UI1iJM9yiceGom?;L0M?J)W-@&L$>>1dB%4*R3HvR z2dXIY@AOMgN-|U7n~{lkRc2XXa9a!H=Z%`%wEa=v){Hk~5f{{`4mFI|c_q0dqNWV( zuqf)V`t;li=Fh9-fO7euaeb(8`>FUDRCAo1A*pGqdSCXtOL<(Bs4nXeb0A7vPws(1>xad1)B( zCWcOI212;d_suERr8L^G{@h-KTYPGU9Vprh$3=Hl_TMKL0)p$#1hc=c0H(@H#`mVd zHv6lMbioNO>hHG3uyU!4SkNZeK_c!5{L=OQc#^6RKt7#inT|keWmK1XXg(;>GeCS> z4w>w9F;pMaY3}9FP0TMmUzu*Cr_B(m)fL8Jef8rjAGK&oEw{U0#X4fLfT6^K!dH5x z-^ITY=Nq}UjZI)U-1?&shB@(4YMUH~9MxbnR+e?wQO$yEpHWXI#@?A+xWFUXNC#JA zFA{H%SK%SH0K4nF&Cd1r;8B-S(NFdb5hVy_#_}G!YYK@==gP&bbi9ERi5qiPcHTgF zWNc)mrh3O_2MwB~j`{y;ZTPE14EzTgqvUj%fHkk3#}L*-6wzWD#wS*E4_z2v!f?F7 zR8!d@cpu9xF&J_xx}h-n72!qsx_+_lr8`A5Y(^WBTB zfQK&wG&I%cs!m#x8A#mX8RR-W?AQLI$FU#7q|Wzy4%)PQl@KPzly1eA)Vt^0#lkI)jtH6VaOW5= z-1*vj-aPVVWmFk+kZ4?@4?67=2o$+{Lb-q9xM>arr6IRUrf(A%V}9uHWMCv?UkLbc(Olt${&ZjgqV)Vyl2*=oR6- z|I;ZNF_hGz5_AH3w}wn1ZhT2Rg(^a!nyd#EKKMLi;D{+pk7OPO9OH9+FpdLwd9BFk ze|de8{@fT}@@D71AS7^W)PC#^jkbpe(%q^W6JFp$wKy-d{<`&wAryb!d6T7>?G9R9 z7i{*9Q{aHh7h|MqJ0-I%p0X{z%(i6rUe|5(uh!~_BamY#bDX}sL5PTnqNL}|Fi`Z2 zZWmZ|c4B*?vbiFYi%Dp1=E;|BOfjWZ%1d4GiZN%00&Tlo!XvSwlHHe<>yYrUtGzVY zg9>yrs5)(?#?1<*Zc+&UlyD~d2H(l*Pv5!JnTnEkZmttc73?lUkNs#Vf+2SQg()Zi|P2Z2hEEj1Ok z?25``&&zMFS_c%B#<{3k?YP~}*Vp$C{ts&)4jIpzqV$m3V#SltTmfGqza;upz3Qp5 z&8t1Gx!PJ?@Hf-4Zz}U@(1*Zsa2<<sPw!LMG#(?pi39~db#F|>Etb-4Z*LDh?-NUII3eL# zaA6X2irx9l&CMmNseOBj<^T6p;`R=dK~%UshR9bnV8k&$>2w<(YrQw ziM86Cc?ogrS_q6%kF7^9OT<=ran;{}(VRtOGc=kJBKdj6P-yCJF)_2XBc~M{iEY9} z>mD^oNB>owDJA!V1e^29x%rL@~JUw3TBdbUal17(kdESGi=Q~oz+Rne% zdS-@Zw(=>oxY|Ko<;vhPqi%L~Zfbo!_NMdU`O(N<(ExMJ9_s1Pv%IcJDOX@vnCA!z6UzjA7GlXmW8m6{}Z-_F2qM zFGa4UUPvPIzhhST>Chup+azA;`=`54UCgJAIX_!#+n{mxV43FD5`YcyK)SU2FZU_- zk38y3owWzU%z>iO>pm0u)vS~8dCThv9`A{4x_}Lh;pu9bo25_p{A+8^N_1r}SK??T zeN(Xz``jn3mn1Te+mJZt`!y50<3Z>O`$p!_>ZYiKZMq@tO8fFvaPtOAmjYIEy16Na zGXutrrfZ6;p!PFjMl)K-Q9I^jC~WQr2a#TkYRPqLdS zQxdbkz&*8o@5xFGP-z3v#?fObUl|4{d^nZ4Q5Rdr!LmJo;rmhw9_!F63(blRSP1qp z%t0%}7y8|8!i1~OS5odlb6!aWucKs6W-gkOf5+xAgv3n`^ZPWH>K6xd37)z)HaG*y zNs7tWE^_#Z``Kmt+>y??=r{DPHdZ~D--D`&!>TO=XFI9)0T~$?+)vo{R-P`s zo=)Oh(262AF1QixVO!20t4OKx&Gs>_{q)Z{9q>1lJ(ea!m2*RJ)ZX!wx8U|GkboPV z)Vm8>Mr<02&&q<_YU?+LNc~HXa1o1t4?jCyI&nQ6e}(+ahW@0WMA~wCsI-BCU+30{ z&fbo?0mQ@B=?GCj51x$P;)1EGtqlpZvg+#}9L_V@XxTAIbIbQ$pPZfTdvrP2dqrDX z5_9_Vuq;KnAm^^?4=67$r(($aEslm&gO zikq?3b=U-3=ykPDOvRb4xR^#AOjQ-dz|4$D4!!u{!87+ijfr+@FA-`{Y8`J%;2?|x zqb{&(IEi*89#m7407CbT1asGiQ#n{#0$K?YXTpnIc87lGj|~ANj02xZm1UHN@BV-~ zu6D88oBuk>iqMDXe`VZNF)E0uI-I7CV%CE-TzzOOx3pnqFR(-C*8s;Npr}S-TF%&0dd&pen!ur?(!CC)38V1yMw3wtn}>xw^Un8ma!- z_RhluUv8trHz5-bNx>!*Q|L~{DyhMJnGz>wT9 z6A;pieNR|`HA(DKE6~TsXLt2mmDow9f0g9hp)__#kzv#=5!c1 zBxTQ<_=^a~KJjKUUj+pP0V2}BNtc(a3*WnFcXqru1Xdw1op2WjQdb%BG_HIkTU%Qa z{e6?l8sS2eusMq1ikPb(Yak01d+#p?ED8E*H%B{Eu4OYeF_v(cUy`x5{t9h@ zF^k(uS?cS z5wr@B7&~wY2z<^_l=}W1fiEXEaoZRc4i2XD4@k;baigmPs|`ggrp?g(fMox|&r?T)eERoQ9Gz=x?kdv?ih3 zs9Ej$iX0?j-0AlrD4)pqJc~EDt4jo`*R9)aG7;*@V#k`@FDf_RKRvc=OPFUz2f27q zjz-Yc1mv58{*YZ4*!%wY41BLI`f(5l&W(@9EdToFaHa%9vJlIF%mC^=8f_gxnm;|E zlwj#_On&>9H9}d`4?!Xp7PMiB*zPBbTQzBk`*4|RDyGtTZP%7S*#jEzmIH5sXHAmJ@G0en!)Wo8n+wAL0fh*xxW3e#S7ii(IF;vzp>4xSx+ zOfa;z7CWb6sZaCVK;yXyx<6Se%o6f#MMDk_3SxCrkOf@+IhgidIt$aZ@bufOYc0w1 zR*x6R^CdoqW+%`-`2qBh3HjdM%$v`LdOb{=cL?QZ(*Ou<{(E?M!2E=Vtm!=#gBG*! z**K?rrKXv4(^X*zN>@Z6BO`-qEF>g!_;h$!h=Y_3;_TB$0oD?n=;1h>gZ>XWaw|^& literal 11204 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

?1VWOsMd^ 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%0AyAu1*aQcT4M@A+*TfytKkJ|=~T}(IV>o& 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 JF6*2UngG?YXCVLp literal 182540 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 From bce2a2ccddb7bba4f20019baeb35284d75eca75b Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Sat, 4 Jul 2026 18:35:36 -0300 Subject: [PATCH 47/60] Mounted UA FLAG module --- .../vending/vendor_types/crew/vehicle_crew.dm | 7 + .../vehicles/hardpoints/secondary/flag.dm | 159 ++++++++++++++++++ 2 files changed, 166 insertions(+) create mode 100644 code/modules/vehicles/hardpoints/secondary/flag.dm 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 01b7f1477bcd..1b5c25a92efa 100644 --- a/code/game/machinery/vending/vendor_types/crew/vehicle_crew.dm +++ b/code/game/machinery/vending/vendor_types/crew/vehicle_crew.dm @@ -147,6 +147,7 @@ GLOBAL_LIST_INIT(cm_vending_vehicle_crew_tank, list( list("M92T Grenade Launcher", 0, /obj/effect/essentials_set/tank/tankgl, VEHICLE_SECONDARY_AVAILABLE, VENDOR_ITEM_REGULAR), list("M56 Cupola", 0, /obj/effect/essentials_set/tank/m56cupola, VEHICLE_SECONDARY_AVAILABLE, VENDOR_ITEM_REGULAR), list("LZR-N Flamer Unit", 0, /obj/effect/essentials_set/tank/tankflamer, VEHICLE_SECONDARY_AVAILABLE, VENDOR_ITEM_RECOMMENDED), + list("Mounted UA Flag", 0, /obj/effect/essentials_set/tank/united_americas_flag, VEHICLE_SECONDARY_AVAILABLE, VENDOR_ITEM_RECOMMENDED), list("SUPPORT MODULE", 0, null, null, null), list("Artillery Module", 0, /obj/item/hardpoint/support/artillery_module, VEHICLE_SUPPORT_AVAILABLE, VENDOR_ITEM_REGULAR), @@ -448,6 +449,12 @@ GLOBAL_LIST_INIT(cm_vending_clothing_vehicle_crew, list( /obj/item/ammo_magazine/hardpoint/tank_glauncher, ) +/obj/effect/essentials_set/tank/united_americas_flag + desc = "An United Americas Flag for mounting atop the tank." + spawned_gear_list = list( + /obj/item/hardpoint/secondary/united_americas_flag + ) + /obj/effect/essentials_set/tank/turret spawned_gear_list = list( /obj/item/hardpoint/holder/tank_turret, diff --git a/code/modules/vehicles/hardpoints/secondary/flag.dm b/code/modules/vehicles/hardpoints/secondary/flag.dm new file mode 100644 index 000000000000..c5221bafcbff --- /dev/null +++ b/code/modules/vehicles/hardpoints/secondary/flag.dm @@ -0,0 +1,159 @@ +#define PLANTED_FLAG_BUFF 4 +#define PLANTED_FLAG_RANGE 11 + +/obj/item/hardpoint/secondary/united_americas_flag + name = "\improper Mounted UA Flag" + desc = "An United Americas flag on a mounting support for the M34A2-A Multipurpose Turret. You feel a burst of energy by its mere sight." + + icon_state = "mounted_flag" + disp_icon = "tank" + disp_icon_state = "mountedflag" + activatable = FALSE // Cannot be toggled - always active when mounted + health = 300 + use_muzzle_flash = FALSE + var/buff_range = PLANTED_FLAG_RANGE + var/buff_strength = PLANTED_FLAG_BUFF + var/faction = FACTION_MARINE + var/flag_active = FALSE + var/datum/shape/range_bounds + var/luminosity_strength = 3 + + /// lets a fitted tank be selected as a laser/airstrike aiming point, same system as a planted flag turret + var/datum/cas_signal/signal + /// Dedicated per-tank-flag counter for the display name below + var/static/tank_flag_count = 0 + var/flag_number + + px_offsets = list( + "1" = list(0, 0), + "2" = list(0, 1), + "4" = list(0, 0), + "8" = list(0, 1) + ) + + +/obj/item/hardpoint/secondary/united_americas_flag/Destroy() + if(flag_active) + deactivate_flag() + range_bounds = null + return ..() + +/obj/item/hardpoint/secondary/united_americas_flag/on_install(obj/vehicle/multitile/vehicle) + ..() + activate_flag() + +/obj/item/hardpoint/secondary/united_americas_flag/on_uninstall(obj/vehicle/multitile/vehicle) + if(flag_active) + deactivate_flag() + ..() + +/obj/item/hardpoint/secondary/united_americas_flag/proc/activate_flag() + if(flag_active) + return + if(!owner) + return + flag_active = TRUE + set_light(luminosity_strength) + start_processing() + activate_signal() + +/obj/item/hardpoint/secondary/united_americas_flag/proc/deactivate_flag() + if(!flag_active) + return + + flag_active = FALSE + set_light(0) + stop_processing() + range_bounds = null + deactivate_signal() + +/obj/item/hardpoint/secondary/united_americas_flag/proc/activate_signal() + if(signal || !owner) + return + if(!faction || !GLOB.cas_groups[faction]) + return + + if(!flag_number) + flag_number = ++tank_flag_count + + // signal_loc = owner (the tank itself, a movable atom) rather than a snapshotted turf + signal = new(owner) + signal.target_id = ++GLOB.cas_tracking_id_increment + signal.name = "TANK-[flag_number < 10 ? "0[flag_number]" : "[flag_number]"]" + signal.linked_cam = new(get_turf(owner), signal.name) + GLOB.cas_groups[faction].add_signal(signal) + +/obj/item/hardpoint/secondary/united_americas_flag/proc/deactivate_signal() + if(!signal) + return + if(faction && GLOB.cas_groups[faction]) + GLOB.cas_groups[faction].remove_signal(signal) + QDEL_NULL(signal) + +/obj/item/hardpoint/secondary/united_americas_flag/proc/start_processing() + START_PROCESSING(SSobj, src) + +/obj/item/hardpoint/secondary/united_americas_flag/proc/stop_processing() + STOP_PROCESSING(SSobj, src) + +/obj/item/hardpoint/secondary/united_americas_flag/process() + if(!flag_active || !owner) + stop_processing() + return + + if(health <= 0) + deactivate_flag() + return + + apply_area_effect() + +/obj/item/hardpoint/secondary/united_americas_flag/proc/apply_area_effect() + if(!owner || !flag_active) + return + + // Update range bounds to follow the vehicle + var/turf/owner_turf = get_turf(owner) + if(!owner_turf) + return + + // Keep the CAS signal's z-lock in sync with the tank's real position + if(signal && signal.z_initial != owner_turf.z) + signal.z_initial = owner_turf.z + + range_bounds = SQUARE(owner_turf.x, owner_turf.y, buff_range) + + var/list/targets = SSquadtree.players_in_range(range_bounds, owner_turf.z, QTREE_SCAN_MOBS | QTREE_FILTER_LIVING) + if(!targets) + return + + for(var/mob/living/carbon/human/H in targets) + // Check if the human is of the correct faction + if(!H.faction || !(faction in H.faction_group)) + continue + + apply_buff_to_player(H) + +/obj/item/hardpoint/secondary/united_americas_flag/proc/apply_buff_to_player(mob/living/carbon/human/H) + H.activate_order_buff(COMMAND_ORDER_HOLD, buff_strength, 5 SECONDS) + H.activate_order_buff(COMMAND_ORDER_FOCUS, buff_strength, 5 SECONDS) + H.activate_order_buff(COMMAND_ORDER_MOVE, buff_strength, 5 SECONDS) + +/obj/item/hardpoint/secondary/united_americas_flag/on_destroy() + if(flag_active) + deactivate_flag() + ..() + +// Movement handling, update buff area when vehicle moves +/obj/item/hardpoint/secondary/united_americas_flag/on_move(turf/old, turf/new_turf, move_dir) + ..() + if(flag_active) + // Force immediate buff reapplication at new position + apply_area_effect() + // Keep the CAS camera view following the tank + if(signal?.linked_cam) + var/turf/owner_turf = get_turf(owner) + if(owner_turf) + signal.linked_cam.forceMove(owner_turf) + +#undef PLANTED_FLAG_BUFF +#undef PLANTED_FLAG_RANGE From 802e12dc7a7ee8555c3a0c069ccdf8bc3881cfbf Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Sat, 4 Jul 2026 18:36:18 -0300 Subject: [PATCH 48/60] Mounted BRUTE Module --- code/datums/supply_packs/vehicle_ammo.dm | 16 +++ .../vending/vendor_types/crew/vehicle_crew.dm | 13 ++ .../vehicles/hardpoints/holder/tank_turret.dm | 4 +- .../hardpoints/secondary/brute_launcher.dm | 117 ++++++++++++++++++ 4 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 code/modules/vehicles/hardpoints/secondary/brute_launcher.dm diff --git a/code/datums/supply_packs/vehicle_ammo.dm b/code/datums/supply_packs/vehicle_ammo.dm index 04f968733522..d3fe5cf957bd 100644 --- a/code/datums/supply_packs/vehicle_ammo.dm +++ b/code/datums/supply_packs/vehicle_ammo.dm @@ -109,6 +109,22 @@ containername = "TOW launcher ammo crate" group = "Vehicle Ammo" +// same as the regular BRUTE ammo. This is just in the vehicle section to avoid mistakes. +/datum/supply_packs/brute_rockets/vehicle + name = "M6H-BRUTE Breaching Rocket Crate (x6)" + contains = list( + /obj/item/ammo_magazine/rocket/brute, + /obj/item/ammo_magazine/rocket/brute, + /obj/item/ammo_magazine/rocket/brute, + /obj/item/ammo_magazine/rocket/brute, + /obj/item/ammo_magazine/rocket/brute, + /obj/item/ammo_magazine/rocket/brute, + ) + cost = 30 + containertype = /obj/structure/closet/crate/explosives + containername = "\improper M6H-BRUTE Breaching Rocket Crate (WARNING)" + group = "Vehicle Ammo" + /datum/supply_packs/ammo_m56_cupola name = "M56 Cupola magazines (x2)" contains = list( 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 1b5c25a92efa..c8e0ffe11754 100644 --- a/code/game/machinery/vending/vendor_types/crew/vehicle_crew.dm +++ b/code/game/machinery/vending/vendor_types/crew/vehicle_crew.dm @@ -147,6 +147,7 @@ GLOBAL_LIST_INIT(cm_vending_vehicle_crew_tank, list( list("M92T Grenade Launcher", 0, /obj/effect/essentials_set/tank/tankgl, VEHICLE_SECONDARY_AVAILABLE, VENDOR_ITEM_REGULAR), list("M56 Cupola", 0, /obj/effect/essentials_set/tank/m56cupola, VEHICLE_SECONDARY_AVAILABLE, VENDOR_ITEM_REGULAR), list("LZR-N Flamer Unit", 0, /obj/effect/essentials_set/tank/tankflamer, VEHICLE_SECONDARY_AVAILABLE, VENDOR_ITEM_RECOMMENDED), + list("M6H-BRUTE Launcher", 0, /obj/effect/essentials_set/tank/brute_launcher, VEHICLE_SECONDARY_AVAILABLE, VENDOR_ITEM_REGULAR), list("Mounted UA Flag", 0, /obj/effect/essentials_set/tank/united_americas_flag, VEHICLE_SECONDARY_AVAILABLE, VENDOR_ITEM_RECOMMENDED), list("SUPPORT MODULE", 0, null, null, null), @@ -449,6 +450,18 @@ GLOBAL_LIST_INIT(cm_vending_clothing_vehicle_crew, list( /obj/item/ammo_magazine/hardpoint/tank_glauncher, ) +/obj/effect/essentials_set/tank/brute_launcher + desc = "A tank-mounted BRUTE breaching rocket launcher. Laser-guides a single 90mm shaped-charge rocket onto structures and walls, given a few seconds to lock on." + spawned_gear_list = list( + /obj/item/hardpoint/secondary/brute_launcher, + /obj/item/ammo_magazine/rocket/brute, + /obj/item/ammo_magazine/rocket/brute, + /obj/item/ammo_magazine/rocket/brute, + /obj/item/ammo_magazine/rocket/brute, + /obj/item/ammo_magazine/rocket/brute, + /obj/item/ammo_magazine/rocket/brute, + ) + /obj/effect/essentials_set/tank/united_americas_flag desc = "An United Americas Flag for mounting atop the tank." spawned_gear_list = list( diff --git a/code/modules/vehicles/hardpoints/holder/tank_turret.dm b/code/modules/vehicles/hardpoints/holder/tank_turret.dm index 35a9e538e48f..3b2a94c32dcb 100644 --- a/code/modules/vehicles/hardpoints/holder/tank_turret.dm +++ b/code/modules/vehicles/hardpoints/holder/tank_turret.dm @@ -40,7 +40,9 @@ /obj/item/hardpoint/secondary/small_flamer, /obj/item/hardpoint/secondary/towlauncher, /obj/item/hardpoint/secondary/m56cupola, - /obj/item/hardpoint/secondary/grenade_launcher + /obj/item/hardpoint/secondary/grenade_launcher, + /obj/item/hardpoint/secondary/brute_launcher, + /obj/item/hardpoint/secondary/united_americas_flag ) hdpt_layer = HDPT_LAYER_TURRET diff --git a/code/modules/vehicles/hardpoints/secondary/brute_launcher.dm b/code/modules/vehicles/hardpoints/secondary/brute_launcher.dm new file mode 100644 index 000000000000..b555093a786c --- /dev/null +++ b/code/modules/vehicles/hardpoints/secondary/brute_launcher.dm @@ -0,0 +1,117 @@ +/obj/item/hardpoint/secondary/brute_launcher + name = "\improper M6H-BRUTE Launcher" + desc = "A tank-mounted variant of the M6H-BRUTE breaching rocket system. Laser-guides a 90mm shaped-charge rocket onto a fortified position, given a few seconds to lock on." + + icon_state = "brute_launcher" + disp_icon = "tank" + disp_icon_state = "brutelauncher" + + health = 500 + traverse_arc = 120 + activation_sounds = list('sound/weapons/gun_rocketlauncher.ogg') + + // reuses the handheld BRUTE launcher's own rocket item directly + // It's very silly that the M56D also needed its own 'vehicle magazines' instead of just using the default drum. This has properly been changed. + ammo = new /obj/item/ammo_magazine/rocket/brute + max_clips = 4 + + px_offsets = list( + "1" = list(1, 10), + "2" = list(-1, 5), + "4" = list(0, 0), + "8" = list(0, 18) + ) + rotation_pivot = list( + "1" = list(-1, -10), + "2" = list(1, -5), + "4" = list(0, 0), + "8" = list(0, -18) + ) + gimbal_pivot = list( + "1" = list(6, -6), + "2" = list(-6, 6), + "4" = list(-6, 6), + "8" = list(6, 6) + ) + muzzle_flash_pos = list( + "1" = list(8, -1), + "2" = list(-8, -16), + "4" = list(5, -8), + "8" = list(-5, 10) + ) + + scatter = 4 // irrelevant - /datum/ammo/rocket/brute sets AMMO_HITS_TARGET_TURF, which skips scatter entirely + fire_delay = 15.0 SECONDS + + // How long the lock-on charge takes before firing is allowed. + var/f_aiming_time = 5 SECONDS + + var/image/lockon_icon + var/image/lockon_direction_icon + var/datum/beam/laser_beam + var/obj/effect/beam_anchor/anchor + +/obj/item/hardpoint/secondary/brute_launcher/handle_fire(atom/target, mob/living/user, params) + if(charging) + return NONE + + if(!(istype(target, /obj/structure) || istype(target, /turf/closed/wall))) + to_chat(user, SPAN_WARNING("Invalid target!")) + return NONE + + var/turf/origin_turf = get_origin_turf() + var/list/turf/path = get_line(origin_turf, get_turf(target), include_start_atom = FALSE) + for(var/turf/turf_path in path) + if(turf_path.opacity && turf_path != target) + to_chat(user, SPAN_WARNING("Target obscured!")) + return NONE + + if(!track_and_charge(target, user, f_aiming_time)) + return NONE + + return ..() + +/obj/item/hardpoint/secondary/brute_launcher/start_aim_visuals(atom/target, mob/living/user) + var/turf/origin_turf = get_origin_turf() + + lockon_icon = image(icon = 'icons/effects/Targeted.dmi', icon_state = "sniper_lockon_guided") + target.overlays += lockon_icon + + lockon_direction_icon = image(icon = 'icons/effects/Targeted.dmi', icon_state = "sniper_lockon_guided_direction", dir = get_cardinal_dir(target, origin_turf)) + target.overlays += lockon_direction_icon + + anchor = new(origin_turf) + laser_beam = anchor.beam(target, "laser_beam_guided", 'icons/effects/beam.dmi', (f_aiming_time + 1 SECONDS), beam_type = /obj/effect/ebeam/laser/intense/tank_mounted) + laser_beam.visuals.alpha = 0 + animate(laser_beam.visuals, alpha = initial(laser_beam.visuals.alpha), f_aiming_time, easing = SINE_EASING|EASE_OUT) + +/obj/item/hardpoint/secondary/brute_launcher/on_track_tick(atom/target) + if(!anchor) + return + var/turf/origin_turf = get_origin_turf() + if(anchor.loc != origin_turf) + anchor.forceMove(origin_turf) + +/obj/item/hardpoint/secondary/brute_launcher/end_aim_visuals(atom/target, mob/living/user, success) + if(lockon_icon) + target.overlays -= lockon_icon + lockon_icon = null + if(lockon_direction_icon) + target.overlays -= lockon_direction_icon + lockon_direction_icon = null + QDEL_NULL(laser_beam) + QDEL_NULL(anchor) + +// Plain /obj/effect/ebeam/laser/intense defaults to the base /obj layer, which renders underneath the +// tank's own ABOVE_MOB_LAYER (4.1) sprite, so, we bump it above it. +/obj/effect/ebeam/laser/intense/tank_mounted + layer = 4.501 + +// Small invisible movable used solely as a beam origin that actually moves with the muzzle +/obj/effect/beam_anchor + name = "" + icon = null + anchored = FALSE + density = FALSE + invisibility = INVISIBILITY_MAXIMUM + mouse_opacity = MOUSE_OPACITY_TRANSPARENT From 1a63247feca8e971c2fbcbc2bb7ac0e472d72a1a Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Sat, 4 Jul 2026 18:37:12 -0300 Subject: [PATCH 49/60] M56D Tweak: Now takes M56D Drums instead of Snowflake-y Tank Ammo --- code/datums/supply_packs/vehicle_ammo.dm | 4 ++-- code/game/machinery/vending/vendor_types/crew/vehicle_crew.dm | 2 +- code/game/objects/items/storage/belt.dm | 2 +- code/modules/vehicles/hardpoints/secondary/cupola.dm | 3 ++- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/code/datums/supply_packs/vehicle_ammo.dm b/code/datums/supply_packs/vehicle_ammo.dm index d3fe5cf957bd..0f03c3f53b28 100644 --- a/code/datums/supply_packs/vehicle_ammo.dm +++ b/code/datums/supply_packs/vehicle_ammo.dm @@ -128,8 +128,8 @@ /datum/supply_packs/ammo_m56_cupola name = "M56 Cupola magazines (x2)" contains = list( - /obj/item/ammo_magazine/hardpoint/m56_cupola, - /obj/item/ammo_magazine/hardpoint/m56_cupola, + /obj/item/ammo_magazine/m56d, + /obj/item/ammo_magazine/m56d, ) cost = 20 containertype = /obj/structure/closet/crate/ammo 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 c8e0ffe11754..5ea5008fcb81 100644 --- a/code/game/machinery/vending/vendor_types/crew/vehicle_crew.dm +++ b/code/game/machinery/vending/vendor_types/crew/vehicle_crew.dm @@ -437,7 +437,7 @@ GLOBAL_LIST_INIT(cm_vending_clothing_vehicle_crew, list( desc = "A permanently fixed M56D, firing standard issue 10x28mm rounds." spawned_gear_list = list( /obj/item/hardpoint/secondary/m56cupola, - /obj/item/ammo_magazine/hardpoint/m56_cupola, + /obj/item/ammo_magazine/m56d, ) /obj/effect/essentials_set/tank/tankgl diff --git a/code/game/objects/items/storage/belt.dm b/code/game/objects/items/storage/belt.dm index fe7ed404a6c5..a389f5774128 100644 --- a/code/game/objects/items/storage/belt.dm +++ b/code/game/objects/items/storage/belt.dm @@ -2830,7 +2830,7 @@ /obj/item/ammo_magazine/hardpoint/secondary_flamer, /obj/item/ammo_magazine/hardpoint/ace_autocannon, /obj/item/ammo_magazine/hardpoint/towlauncher, - /obj/item/ammo_magazine/hardpoint/m56_cupola, + /obj/item/ammo_magazine/m56d, /obj/item/ammo_magazine/hardpoint/tank_glauncher, /obj/item/storage/box/packet/flare, /obj/item/ammo_magazine/hardpoint/boyars_dualcannon, diff --git a/code/modules/vehicles/hardpoints/secondary/cupola.dm b/code/modules/vehicles/hardpoints/secondary/cupola.dm index 7914be4a5a2e..c4e185fdba9f 100644 --- a/code/modules/vehicles/hardpoints/secondary/cupola.dm +++ b/code/modules/vehicles/hardpoints/secondary/cupola.dm @@ -10,7 +10,8 @@ health = 350 traverse_arc = 120 - ammo = new /obj/item/ammo_magazine/hardpoint/m56_cupola + // Uses the real M56D drum magazine. + ammo = new /obj/item/ammo_magazine/m56d max_clips = 1 gimbal_pivot = list( From 923ed2f727cc32d9e163199504833cb2e7b24342 Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Sat, 4 Jul 2026 18:37:47 -0300 Subject: [PATCH 50/60] Minigun rebalance: Shorter spinup, low scatter; huge falloff --- code/datums/ammo/bullet/tank.dm | 3 +++ .../vehicles/hardpoints/primary/minigun.dm | 19 +++++++++++++------ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/code/datums/ammo/bullet/tank.dm b/code/datums/ammo/bullet/tank.dm index e9da5d110d97..ed56acf6b5b4 100644 --- a/code/datums/ammo/bullet/tank.dm +++ b/code/datums/ammo/bullet/tank.dm @@ -90,6 +90,9 @@ damage = 40 penetration = ARMOR_PENETRATION_TIER_6 damage_armor_punch = 1 + // Close-range weapon by design: full damage out to 4 tiles, roughly halved by 7 tiles, tapers to nothing by 10 tiles. + effective_range_max = 4 + damage_falloff = 6.7 /datum/ammo/bullet/tank/setup_faction_clash_values() . = ..() diff --git a/code/modules/vehicles/hardpoints/primary/minigun.dm b/code/modules/vehicles/hardpoints/primary/minigun.dm index 7d6e4c0d59d5..b7a9d923e95f 100644 --- a/code/modules/vehicles/hardpoints/primary/minigun.dm +++ b/code/modules/vehicles/hardpoints/primary/minigun.dm @@ -18,6 +18,12 @@ "4" = list(32, 0), "8" = list(-32, 0) ) + rotation_pivot = list( + "1" = list(0, -21), + "2" = list(0, 32), + "4" = list(-32, 0), + "8" = list(32, 0) + ) muzzle_flash_pos = list( "1" = list(0, 57), @@ -26,7 +32,7 @@ "8" = list(-77, 0) ) - scatter = 18 //base scatter, modified by stake_delay_mult + scatter = 2 gun_firemode = GUN_FIREMODE_AUTOMATIC gun_firemode_list = list( GUN_FIREMODE_AUTOMATIC, @@ -35,7 +41,7 @@ activation_sounds = list('sound/weapons/gun_minigun.ogg') /// Active firing time to reach max spin_stage. - var/spinup_time = 10 SECONDS + var/spinup_time = 3 SECONDS /// Grace period before losing spin_stage. var/spindown_grace_time = 2 SECONDS COOLDOWN_DECLARE(spindown_grace_cooldown) @@ -43,8 +49,9 @@ var/spindown_time = 3 SECONDS /// Index of stage_rate. var/spin_stage = 1 - /// Shots fired per fire_delay at a particular spin_stage. - var/list/stage_rate = list(1, 1, 2, 2, 3, 3, 3, 4, 4, 4, 5) + /// this is meant to be an already-fearsome close range weapon even before it spins + /// all the way up, not a weapon that needs several seconds of commitment before it does anything. + var/list/stage_rate = list(3, 3, 4, 4, 4, 5, 5, 5, 5, 5, 5) /// Fire delay multiplier for current spin_stage. var/stage_delay_mult = 1 /// When it was last fired, related to world.time. @@ -52,7 +59,7 @@ /obj/item/hardpoint/primary/minigun/set_fire_delay(value) fire_delay = value - SEND_SIGNAL(src, COMSIG_GUN_AUTOFIREDELAY_MODIFIED, fire_delay * stage_delay_mult, scatter * stage_delay_mult) + SEND_SIGNAL(src, COMSIG_GUN_AUTOFIREDELAY_MODIFIED, fire_delay * stage_delay_mult) /obj/item/hardpoint/primary/minigun/set_fire_cooldown() calculate_stage_delay_mult() //needs to check grace_cooldown before refreshed @@ -79,7 +86,7 @@ var/new_stage_rate = stage_rate[floor(spin_stage)] if(old_stage_rate != new_stage_rate) - scatter = initial(scatter) * (1/new_stage_rate) + // deliberate low scatter at every stage instead of the old huge-scatter-while-spinning-up behavior. stage_delay_mult = 1 / new_stage_rate SEND_SIGNAL(src, COMSIG_GUN_AUTOFIREDELAY_MODIFIED, fire_delay * stage_delay_mult) From 959cda0e5c227d75832dcb441e266b93251c818f Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Sat, 4 Jul 2026 18:39:47 -0300 Subject: [PATCH 51/60] Autocannon Rebalance; See DESC 2.2x~ Damage 0.5x Fire Rate + 24 Shrapnel Applies a AMR Microstun against non-big xenos --- code/datums/ammo/bullet/tank.dm | 35 +++++++++++++------ .../vehicles/hardpoints/primary/autocannon.dm | 2 +- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/code/datums/ammo/bullet/tank.dm b/code/datums/ammo/bullet/tank.dm index ed56acf6b5b4..268ca21d44bb 100644 --- a/code/datums/ammo/bullet/tank.dm +++ b/code/datums/ammo/bullet/tank.dm @@ -12,11 +12,11 @@ sound_hit = 'sound/weapons/sting_boom_small1.ogg' damage_falloff = 0 flags_ammo_behavior = AMMO_BALLISTIC - accurate_range_min = 4 + accurate_range_min = 3 accuracy = HIT_ACCURACY_TIER_8 scatter = 0 - damage = 60 + damage = 140 damage_var_high = PROJECTILE_VARIANCE_TIER_8 penetration = ARMOR_PENETRATION_TIER_6 accurate_range = 32 @@ -24,21 +24,36 @@ shell_speed = AMMO_SPEED_TIER_6 /datum/ammo/bullet/tank/flak/on_hit_mob(mob/M,obj/projectile/P) - burst(get_turf(M),P,damage_type, 2 , 3) - burst(get_turf(M),P,damage_type, 1 , 3 , 0) + create_shrapnel(get_turf(M), 24, rand(0, 359), 180, /datum/ammo/bullet/shrapnel/flak, P.weapon_cause_data, FALSE, 0.15, TRUE) + apply_micro_stun(M) /datum/ammo/bullet/tank/flak/on_near_target(turf/T, obj/projectile/P) - burst(get_turf(T),P,damage_type, 2 , 3) - burst(get_turf(T),P,damage_type, 1 , 3, 0) + create_shrapnel(get_turf(T), 24, rand(0, 359), 180, /datum/ammo/bullet/shrapnel/flak, P.weapon_cause_data, FALSE, 0.15, TRUE) return 1 /datum/ammo/bullet/tank/flak/on_hit_obj(obj/O,obj/projectile/P) - burst(get_turf(P),P,damage_type, 2 , 3) - burst(get_turf(P),P,damage_type, 1 , 3 , 0) + create_shrapnel(get_turf(P), 24, rand(0, 359), 180, /datum/ammo/bullet/shrapnel/flak, P.weapon_cause_data, FALSE, 0.15, TRUE) /datum/ammo/bullet/tank/flak/on_hit_turf(turf/T,obj/projectile/P) - burst(get_turf(T),P,damage_type, 2 , 3) - burst(get_turf(T),P,damage_type, 1 , 3 , 0) + create_shrapnel(get_turf(T), 24, rand(0, 359), 180, /datum/ammo/bullet/shrapnel/flak, P.weapon_cause_data, FALSE, 0.15, TRUE) + +// applies a micro-stun modelled after the AMR SPEC's second shot. +// unlike the AMR spec, this only works on xenos that aren't big sized. +/datum/ammo/bullet/tank/flak/proc/apply_micro_stun(mob/living/living_mob) + if(!isxeno(living_mob) || living_mob.mob_size >= MOB_SIZE_BIG) + return + var/mob/living/carbon/xenomorph/target = living_mob + target.KnockDown(0.15) + +/datum/ammo/bullet/tank/flak/set_bullet_traits() + . = ..() + LAZYADD(traits_to_give, list( + BULLET_TRAIT_ENTRY(/datum/element/bullet_trait_iff) + )) + +/// same shrapnel as any other fragmentation source, just capped to a 4 tile radius +/datum/ammo/bullet/shrapnel/flak + max_range = 4 /datum/ammo/bullet/tank/dualcannon name = "dualcannon bullet" diff --git a/code/modules/vehicles/hardpoints/primary/autocannon.dm b/code/modules/vehicles/hardpoints/primary/autocannon.dm index 99ab163a49a5..863c7eef5c17 100644 --- a/code/modules/vehicles/hardpoints/primary/autocannon.dm +++ b/code/modules/vehicles/hardpoints/primary/autocannon.dm @@ -31,4 +31,4 @@ gun_firemode_list = list( GUN_FIREMODE_AUTOMATIC, ) - fire_delay = 0.7 SECONDS + fire_delay = 1.4 SECONDS From 862a2f01f28bed5d036c86f7ae7c8659f358a16b Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Sat, 4 Jul 2026 18:40:17 -0300 Subject: [PATCH 52/60] Adds recommended to Cupola; Removes from Flamer --- code/game/machinery/vending/vendor_types/crew/vehicle_crew.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 5ea5008fcb81..c70bdc6f7495 100644 --- a/code/game/machinery/vending/vendor_types/crew/vehicle_crew.dm +++ b/code/game/machinery/vending/vendor_types/crew/vehicle_crew.dm @@ -145,9 +145,9 @@ GLOBAL_LIST_INIT(cm_vending_vehicle_crew_tank, list( list("SECONDARY WEAPON", 0, null, null, null), list("M92T Grenade Launcher", 0, /obj/effect/essentials_set/tank/tankgl, VEHICLE_SECONDARY_AVAILABLE, VENDOR_ITEM_REGULAR), - list("M56 Cupola", 0, /obj/effect/essentials_set/tank/m56cupola, VEHICLE_SECONDARY_AVAILABLE, VENDOR_ITEM_REGULAR), - list("LZR-N Flamer Unit", 0, /obj/effect/essentials_set/tank/tankflamer, VEHICLE_SECONDARY_AVAILABLE, VENDOR_ITEM_RECOMMENDED), + list("M56 Cupola", 0, /obj/effect/essentials_set/tank/m56cupola, VEHICLE_SECONDARY_AVAILABLE, VENDOR_ITEM_RECOMMENDED), list("M6H-BRUTE Launcher", 0, /obj/effect/essentials_set/tank/brute_launcher, VEHICLE_SECONDARY_AVAILABLE, VENDOR_ITEM_REGULAR), + list("LZR-N Flamer Unit", 0, /obj/effect/essentials_set/tank/tankflamer, VEHICLE_SECONDARY_AVAILABLE, VENDOR_ITEM_REGULAR), list("Mounted UA Flag", 0, /obj/effect/essentials_set/tank/united_americas_flag, VEHICLE_SECONDARY_AVAILABLE, VENDOR_ITEM_RECOMMENDED), list("SUPPORT MODULE", 0, null, null, null), From 67f081e40d14d97acd91933e0c8ba11f6bdd6edd Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Sat, 4 Jul 2026 18:40:32 -0300 Subject: [PATCH 53/60] Final .dme Update --- colonialmarines.dme | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/colonialmarines.dme b/colonialmarines.dme index e0ee0996a85d..706807b60cae 100644 --- a/colonialmarines.dme +++ b/colonialmarines.dme @@ -2653,6 +2653,7 @@ #include "code\modules\vehicles\hardpoints\hardpoint_ammo\cupola_ammo.dm" #include "code\modules\vehicles\hardpoints\hardpoint_ammo\dualcannon_ammo.dm" #include "code\modules\vehicles\hardpoints\hardpoint_ammo\firing_port_weapon_ammo.dm" +#include "code\modules\vehicles\hardpoints\hardpoint_ammo\flare_ammo.dm" #include "code\modules\vehicles\hardpoints\hardpoint_ammo\flare_launcher_ammo.dm" #include "code\modules\vehicles\hardpoints\hardpoint_ammo\gl_ammo.dm" #include "code\modules\vehicles\hardpoints\hardpoint_ammo\hardpoint_ammo.dm" @@ -2660,7 +2661,6 @@ #include "code\modules\vehicles\hardpoints\hardpoint_ammo\minigun_ammo.dm" #include "code\modules\vehicles\hardpoints\hardpoint_ammo\primary_flamer_ammo.dm" #include "code\modules\vehicles\hardpoints\hardpoint_ammo\secondary_flamer_ammo.dm" -#include "code\modules\vehicles\hardpoints\hardpoint_ammo\smoke_ammo.dm" #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" @@ -2671,7 +2671,9 @@ #include "code\modules\vehicles\hardpoints\primary\ltb.dm" #include "code\modules\vehicles\hardpoints\primary\minigun.dm" #include "code\modules\vehicles\hardpoints\primary\primary.dm" +#include "code\modules\vehicles\hardpoints\secondary\brute_launcher.dm" #include "code\modules\vehicles\hardpoints\secondary\cupola.dm" +#include "code\modules\vehicles\hardpoints\secondary\flag.dm" #include "code\modules\vehicles\hardpoints\secondary\flamer.dm" #include "code\modules\vehicles\hardpoints\secondary\frontal_cannon.dm" #include "code\modules\vehicles\hardpoints\secondary\grenade_launcher.dm" From ee93aa50d82976bc5c032cfffc2a5effb79c2456 Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Sat, 4 Jul 2026 19:55:22 -0300 Subject: [PATCH 54/60] Linter fix --- code/game/machinery/vending/vendor_types/crew/vehicle_crew.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 c70bdc6f7495..836f1eb586c4 100644 --- a/code/game/machinery/vending/vendor_types/crew/vehicle_crew.dm +++ b/code/game/machinery/vending/vendor_types/crew/vehicle_crew.dm @@ -465,7 +465,7 @@ GLOBAL_LIST_INIT(cm_vending_clothing_vehicle_crew, list( /obj/effect/essentials_set/tank/united_americas_flag desc = "An United Americas Flag for mounting atop the tank." spawned_gear_list = list( - /obj/item/hardpoint/secondary/united_americas_flag + /obj/item/hardpoint/secondary/united_americas_flag, ) /obj/effect/essentials_set/tank/turret From 55c67d75a9227c1e3071145457e93e53af596f8c Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Sat, 4 Jul 2026 20:07:55 -0300 Subject: [PATCH 55/60] Second linter fix --- code/datums/supply_packs/vehicle_ammo.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/datums/supply_packs/vehicle_ammo.dm b/code/datums/supply_packs/vehicle_ammo.dm index 0f03c3f53b28..76f1a6a11a17 100644 --- a/code/datums/supply_packs/vehicle_ammo.dm +++ b/code/datums/supply_packs/vehicle_ammo.dm @@ -110,7 +110,7 @@ group = "Vehicle Ammo" // same as the regular BRUTE ammo. This is just in the vehicle section to avoid mistakes. -/datum/supply_packs/brute_rockets/vehicle +/datum/supply_packs/vehicle_brute_rockets name = "M6H-BRUTE Breaching Rocket Crate (x6)" contains = list( /obj/item/ammo_magazine/rocket/brute, From 7f5e8a12496b58501c8f3e9364a4f0d46ad4bf67 Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Sat, 4 Jul 2026 20:21:06 -0300 Subject: [PATCH 56/60] Third linter fix? I have no idea why bloody_feet seems to be triggering the linters for this PR, but here we go. --- code/datums/elements/bloody_feet.dm | 4 +++- code/datums/supply_packs/vehicle_ammo.dm | 16 ---------------- tgui/packages/tgui/interfaces/TankPivotTuner.tsx | 14 +++++--------- 3 files changed, 8 insertions(+), 26 deletions(-) diff --git a/code/datums/elements/bloody_feet.dm b/code/datums/elements/bloody_feet.dm index 8964eac5c882..2ebc4260e3e8 100644 --- a/code/datums/elements/bloody_feet.dm +++ b/code/datums/elements/bloody_feet.dm @@ -32,9 +32,11 @@ RegisterSignal(shoes, COMSIG_ITEM_DROPPED, PROC_REF(on_shoes_removed), override = TRUE) if(dry_time) - addtimer(CALLBACK(src, PROC_REF(clear_blood), target), dry_time) + dry_timer_id = addtimer(CALLBACK(src, PROC_REF(clear_blood), target), dry_time, TIMER_STOPPABLE) /datum/element/bloody_feet/Detach(datum/target, force) + deltimer(dry_timer_id) + dry_timer_id = null UnregisterSignal(target, list( COMSIG_MOVABLE_MOVED, COMSIG_HUMAN_BLOOD_CROSSED, diff --git a/code/datums/supply_packs/vehicle_ammo.dm b/code/datums/supply_packs/vehicle_ammo.dm index 76f1a6a11a17..9e783d424ec7 100644 --- a/code/datums/supply_packs/vehicle_ammo.dm +++ b/code/datums/supply_packs/vehicle_ammo.dm @@ -109,22 +109,6 @@ containername = "TOW launcher ammo crate" group = "Vehicle Ammo" -// same as the regular BRUTE ammo. This is just in the vehicle section to avoid mistakes. -/datum/supply_packs/vehicle_brute_rockets - name = "M6H-BRUTE Breaching Rocket Crate (x6)" - contains = list( - /obj/item/ammo_magazine/rocket/brute, - /obj/item/ammo_magazine/rocket/brute, - /obj/item/ammo_magazine/rocket/brute, - /obj/item/ammo_magazine/rocket/brute, - /obj/item/ammo_magazine/rocket/brute, - /obj/item/ammo_magazine/rocket/brute, - ) - cost = 30 - containertype = /obj/structure/closet/crate/explosives - containername = "\improper M6H-BRUTE Breaching Rocket Crate (WARNING)" - group = "Vehicle Ammo" - /datum/supply_packs/ammo_m56_cupola name = "M56 Cupola magazines (x2)" contains = list( diff --git a/tgui/packages/tgui/interfaces/TankPivotTuner.tsx b/tgui/packages/tgui/interfaces/TankPivotTuner.tsx index 3d74d3bd4856..329cd3258533 100644 --- a/tgui/packages/tgui/interfaces/TankPivotTuner.tsx +++ b/tgui/packages/tgui/interfaces/TankPivotTuner.tsx @@ -28,9 +28,9 @@ type Data = { const AXIS_STEPS = [-10, -1, 1, 10]; const AxisRow = (props: { - axis: 'x' | 'y'; - value: number; - target: 'rotation' | 'gimbal' | 'offset'; + readonly axis: 'x' | 'y'; + readonly value: number; + readonly target: 'rotation' | 'gimbal' | 'offset'; }) => { const { act } = useBackend(); const { axis, value, target } = props; @@ -77,9 +77,7 @@ export const TankPivotTuner = (props) => { @@ -96,9 +94,7 @@ export const TankPivotTuner = (props) => { )} {selected_name && ( -

+
From ffd9ab5e715b3e7a23cbd30cfd2200b66411baa8 Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Sat, 4 Jul 2026 20:24:31 -0300 Subject: [PATCH 57/60] Forgot the var, lol. Fourth linter fix. --- code/datums/elements/bloody_feet.dm | 1 + 1 file changed, 1 insertion(+) diff --git a/code/datums/elements/bloody_feet.dm b/code/datums/elements/bloody_feet.dm index 2ebc4260e3e8..2990e360ad99 100644 --- a/code/datums/elements/bloody_feet.dm +++ b/code/datums/elements/bloody_feet.dm @@ -11,6 +11,7 @@ /// Whether the human has moved into the turf giving them bloody feet /// Necessary because of how Crossed is called before Moved var/list/entered_bloody_turf + var/dry_timer_id /datum/element/bloody_feet/Attach(datum/target, dry_time, obj/item/clothing/shoes, steps, bcolor) . = ..() From 2e8e687358b2baa1acfe2f947de67ea874dc506d Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Sat, 4 Jul 2026 20:31:12 -0300 Subject: [PATCH 58/60] Fifth linter fix. Adds a return type to get_rotation_owner() --- code/modules/vehicles/hardpoints/hardpoint.dm | 1 + 1 file changed, 1 insertion(+) diff --git a/code/modules/vehicles/hardpoints/hardpoint.dm b/code/modules/vehicles/hardpoints/hardpoint.dm index d5d3e95cd6c9..56c4ea4714c9 100644 --- a/code/modules/vehicles/hardpoints/hardpoint.dm +++ b/code/modules/vehicles/hardpoints/hardpoint.dm @@ -686,6 +686,7 @@ * in_turret_firing_arc() above. */ /obj/item/hardpoint/proc/get_rotation_owner() + RETURN_TYPE(/obj/item/hardpoint) if(self_gimballed) return src var/obj/item/hardpoint/holder/tank_turret/turret = loc From 9f17226d9cf2949e3f0b427b785111c116e8e8fa Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Tue, 7 Jul 2026 13:53:39 -0300 Subject: [PATCH 59/60] bug fixes; see desc fixes a visual bug where the aiming reticle didn't vanish after losing lock; a bug where the tank shell didn't properly home-in on a moving target after it was fired, and a bug where the tank barrel didn't properly turn to face the target it was aiming at. Also fixes CAS camera not self-updating as the tank moves. --- .../dcs/signals/atom/signals_projectile.dm | 2 + .../homing_projectile_component.dm | 15 ++++- code/modules/projectiles/projectile.dm | 6 ++ code/modules/vehicles/hardpoints/hardpoint.dm | 64 ++++++++++++------- .../vehicles/hardpoints/holder/tank_turret.dm | 23 ++----- .../vehicles/hardpoints/primary/ltb.dm | 62 ++++++++---------- .../hardpoints/secondary/brute_launcher.dm | 1 + .../vehicles/hardpoints/secondary/flag.dm | 16 ++--- 8 files changed, 99 insertions(+), 90 deletions(-) diff --git a/code/__DEFINES/dcs/signals/atom/signals_projectile.dm b/code/__DEFINES/dcs/signals/atom/signals_projectile.dm index 46014d5351d5..f9030051cb89 100644 --- a/code/__DEFINES/dcs/signals/atom/signals_projectile.dm +++ b/code/__DEFINES/dcs/signals/atom/signals_projectile.dm @@ -29,6 +29,8 @@ #define COMSIG_BULLET_PRE_HANDLE_TURF "bullet_pre_handle_turf" #define COMPONENT_BULLET_PASS_THROUGH (1<<0) #define COMSIG_BULLET_TERMINAL "bullet_terminal" +/// From /obj/projectile/fly(), every tick of flight (unlike COMSIG_BULLET_TERMINAL, which only fires once, right before impact): () +#define COMSIG_BULLET_STEP "bullet_step" /// Called when a bullet hits a living mob on a sprite click (original target is final target) #define COMSIG_BULLET_DIRECT_HIT "bullet_direct_hit" diff --git a/code/modules/projectiles/homing_projectile_component.dm b/code/modules/projectiles/homing_projectile_component.dm index 8fd36fd67f31..903bff54deab 100644 --- a/code/modules/projectiles/homing_projectile_component.dm +++ b/code/modules/projectiles/homing_projectile_component.dm @@ -22,17 +22,30 @@ src.homing_target = homing_target /datum/component/homing_projectile/RegisterWithParent() + RegisterSignal(parent, COMSIG_BULLET_STEP, PROC_REF(step_retarget)) RegisterSignal(parent, COMSIG_BULLET_TERMINAL, PROC_REF(terminal_retarget)) /datum/component/homing_projectile/UnregisterFromParent() - UnregisterSignal(parent, COMSIG_BULLET_TERMINAL) + UnregisterSignal(parent, list(COMSIG_BULLET_STEP, COMSIG_BULLET_TERMINAL)) homing_target = null /datum/component/homing_projectile/Destroy() homing_target = null return ..() +// Continuously re-aims toward the target's live position every tick of flight +/datum/component/homing_projectile/proc/step_retarget() + SIGNAL_HANDLER + if(QDELETED(homing_target)) + return + var/obj/projectile/projectile = parent + var/turf/homing_turf = get_turf(homing_target) + if(!homing_turf || homing_turf == get_turf(projectile)) + return + projectile.retarget(homing_turf, keep_angle = FALSE) + /datum/component/homing_projectile/proc/terminal_retarget() + SIGNAL_HANDLER var/obj/projectile/projectile = parent var/turf/homing_turf = get_turf(homing_target) projectile.speed *= 2 // Double speed to ensure hitting next tick despite eventual movement diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index 65849efde884..c41576dd938c 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -361,6 +361,11 @@ SHOULD_NOT_SLEEP(TRUE) PRIVATE_PROC(TRUE) var/turf/current_turf = get_turf(src) + + // Lets homing-style components continuously re-aim toward their live ttarget + // keeps the shot actually curving to follow a moving target over the whole flight. + SEND_SIGNAL(src, COMSIG_BULLET_STEP) + var/turf/next_turf = popleft(path) // Terminal projectiles (about to hit) are handled firer for retarget logic @@ -411,6 +416,7 @@ path.Cut(1, 2) // remove the turf we're already on var/atom/source = keep_angle ? original : current_turf update_angle(source, new_target) + target_turf = get_turf(new_target) /obj/projectile/proc/scan_a_turf(turf/turf, proj_dir) . = TRUE // Sleep safeguard: stop the bullet diff --git a/code/modules/vehicles/hardpoints/hardpoint.dm b/code/modules/vehicles/hardpoints/hardpoint.dm index 56c4ea4714c9..05e411ac7685 100644 --- a/code/modules/vehicles/hardpoints/hardpoint.dm +++ b/code/modules/vehicles/hardpoints/hardpoint.dm @@ -101,6 +101,7 @@ /// track_and_charge() loop. Distinct from fire_wait_cancelled, which guards a different wait /// (arc-entry, not an active charge) and could otherwise interfere if both were active at once. var/charge_cancelled = FALSE + var/trigger_held = FALSE // Muzzleflash var/use_muzzle_flash = FALSE @@ -573,6 +574,7 @@ /// Reset variables used in firing and remove the gun from the autofire system. /obj/item/hardpoint/proc/stop_fire(datum/source, atom/object, turf/location, control, params) + trigger_held = FALSE SEND_SIGNAL(src, COMSIG_GUN_STOP_FIRE) if(auto_firing) reset_fire() //automatic fire doesn't reset itself from COMSIG_GUN_STOP_FIRE @@ -595,6 +597,7 @@ return set_target(get_turf_on_clickcatcher(object, source, params)) + trigger_held = TRUE if(gun_firemode == GUN_FIREMODE_SEMIAUTO) INVOKE_ASYNC(src, PROC_REF(fire_semiauto), object, source, params) @@ -669,7 +672,7 @@ . = TRUE while(!in_firing_arc(target)) sleep(1) - if(fire_wait_cancelled || QDELETED(src) || QDELETED(target) || health <= 0 || (ammo && ammo.current_rounds <= 0)) + if(fire_wait_cancelled || !trigger_held || QDELETED(src) || QDELETED(target) || health <= 0 || (ammo && ammo.current_rounds <= 0)) . = FALSE break @@ -727,39 +730,51 @@ sleep(1) elapsed += 1 - if(charge_cancelled || QDELETED(src) || QDELETED(target) || health <= 0 || (ammo && ammo.current_rounds <= 0)) + if(charge_cancelled || !trigger_held || QDELETED(src) || QDELETED(target) || health <= 0 || (ammo && ammo.current_rounds <= 0)) . = FALSE break + if(isliving(target)) + var/mob/living/living_target = target + if(living_target.is_dead()) + . = FALSE + break + var/turf/origin_turf = get_origin_turf() var/turf/target_turf = get_turf(target) if(origin_turf == target_turf) . = FALSE break - // Angle uses the target atom itself, not target_turf - var/angle_to_target = Get_Angle_Grounded(origin_turf, target) - var/obj/item/hardpoint/rotation_owner = get_rotation_owner() - var/obj/item/hardpoint/holder/tank_turret/mount = self_gimballed ? loc : null - - if(istype(mount)) // self-gimballed, mounted on a turret - var/turret_facing = mount.current_angle - var/raw_delta = angle_delta(angle_to_target, turret_facing) - if(abs(raw_delta) > SLAVED_GIMBAL_ARC_HALF_WIDTH) - to_chat(user, SPAN_WARNING("Target moved out of the firing arc!")) - . = FALSE - break - var/swing = clamp(raw_delta, -SLAVED_GIMBAL_ARC_HALF_WIDTH, SLAVED_GIMBAL_ARC_HALF_WIDTH) - rotation_owner.desired_angle = ((turret_facing + swing) % 360 + 360) % 360 - else - rotation_owner.desired_angle = angle_to_target - if(traverse_arc && abs(angle_delta(angle_to_target, rotation_owner.current_angle)) > traverse_arc * 0.5) - to_chat(user, SPAN_WARNING("Target moved out of the firing arc!")) - . = FALSE - break + try + // Angle uses the target atom itself, not target_turf + var/angle_to_target = Get_Angle_Grounded(origin_turf, target) + var/obj/item/hardpoint/rotation_owner = get_rotation_owner() + var/obj/item/hardpoint/holder/tank_turret/mount = self_gimballed ? loc : null + + if(istype(mount)) // self-gimballed, mounted on a turret + var/turret_facing = mount.current_angle + var/raw_delta = angle_delta(angle_to_target, turret_facing) + if(abs(raw_delta) > SLAVED_GIMBAL_ARC_HALF_WIDTH) + to_chat(user, SPAN_WARNING("Target moved out of the firing arc!")) + . = FALSE + else + var/swing = clamp(raw_delta, -SLAVED_GIMBAL_ARC_HALF_WIDTH, SLAVED_GIMBAL_ARC_HALF_WIDTH) + rotation_owner.desired_angle = ((turret_facing + swing) % 360 + 360) % 360 + else + rotation_owner.desired_angle = angle_to_target + if(traverse_arc && abs(angle_delta(angle_to_target, rotation_owner.current_angle)) > traverse_arc * 0.5) + to_chat(user, SPAN_WARNING("Target moved out of the firing arc!")) + . = FALSE + if(. != FALSE) + rotation_owner.start_rotation_if_needed() + on_track_tick(target) + catch(var/exception/tick_error) + log_runtime(tick_error) + . = FALSE - rotation_owner.start_rotation_if_needed() - on_track_tick(target) + if(. == FALSE) + break UnregisterSignal(src, list(COMSIG_GUN_STOP_FIRE, COMSIG_GUN_INTERRUPT_FIRE)) end_aim_visuals(target, user, .) @@ -993,6 +1008,7 @@ // actually standing on, which is what actually needs to be hit. very shittty bug to fix during testing // which made the autocannon and minigun DPS plummet. desired_angle = Get_Angle_Grounded(origin_turf, aim_target) + desired_angle = Get_Angle_Grounded(origin_turf, target_turf) if(self_gimballed) var/obj/item/hardpoint/holder/tank_turret/turret = loc diff --git a/code/modules/vehicles/hardpoints/holder/tank_turret.dm b/code/modules/vehicles/hardpoints/holder/tank_turret.dm index 3b2a94c32dcb..bc949ec6a396 100644 --- a/code/modules/vehicles/hardpoints/holder/tank_turret.dm +++ b/code/modules/vehicles/hardpoints/holder/tank_turret.dm @@ -307,25 +307,10 @@ var/obj/item/hardpoint/support/artillery_module/AM for(var/obj/item/hardpoint/support/artillery_module/A in C.hardpoints) AM = A - if(AM && AM.is_active) - var/mob/user = C.seats[VEHICLE_GUNNER] - if(user && user.client) - user = C.seats[VEHICLE_GUNNER] - user.client.change_view(AM.view_buff, src) - - switch(dir) - if(NORTH) - user.client.set_pixel_x(0) - user.client.set_pixel_y(AM.view_tile_offset * 32) - if(SOUTH) - user.client.set_pixel_x(0) - user.client.set_pixel_y(-1 * AM.view_tile_offset * 32) - if(EAST) - user.client.set_pixel_x(AM.view_tile_offset * 32) - user.client.set_pixel_y(0) - if(WEST) - user.client.set_pixel_x(-1 * AM.view_tile_offset * 32) - user.client.set_pixel_y(0) + if(AM) + var/mob/living/user = C.seats[VEHICLE_GUNNER] + if(user && (user in AM.optics_users)) + AM.apply_gunner_view(user) // lobs a single starshell forwards /obj/item/hardpoint/holder/tank_turret/try_fire(atom/target, mob/living/user, params) diff --git a/code/modules/vehicles/hardpoints/primary/ltb.dm b/code/modules/vehicles/hardpoints/primary/ltb.dm index 1b51bdb8d9f9..02af0955eff8 100644 --- a/code/modules/vehicles/hardpoints/primary/ltb.dm +++ b/code/modules/vehicles/hardpoints/primary/ltb.dm @@ -1,9 +1,6 @@ /// Low end of the LTB's aiming laser opacity ramp #define LTB_LASER_MIN_ALPHA 50 -/// Multiply-tint applied to the "dark" phase of the flicker -#define LTB_LASER_DARK_TINT "#787878" - /obj/item/hardpoint/primary/cannon name = "\improper LTB Cannon" desc = "A primary cannon for tanks that shoots explosive rounds." @@ -49,6 +46,8 @@ var/image/lockon_icon var/image/lockon_direction_icon + var/lockon_pixel_x = 0 + var/lockon_pixel_y = 0 var/datum/beam/laser_beam var/obj/effect/beam_anchor/anchor /// Incrementted every start_aim_visuals(), lets a stale cycle_laser_color() invocation from a @@ -65,6 +64,7 @@ */ /obj/item/hardpoint/primary/cannon/handle_fire(atom/target, mob/living/user, params) if(charging) + SEND_SIGNAL(src, COMSIG_GUN_INTERRUPT_FIRE) return NONE if(!isliving(target)) @@ -123,28 +123,14 @@ /obj/item/hardpoint/primary/cannon/start_aim_visuals(atom/target, mob/living/user) anchor = new(get_origin_turf()) charge_start_time = world.time - build_lockon_icons(target) - apply_beam_visuals(target, FALSE) - aim_generation++ - INVOKE_ASYNC(src, PROC_REF(cycle_laser_color), target, aim_generation) - -//Builds the two reticle overlay images once for the whole charge -/obj/item/hardpoint/primary/cannon/proc/build_lockon_icons(atom/target) - var/turf/origin_turf = get_origin_turf() var/mob/living_target = target - var/x_offset = -living_target.pixel_x + living_target.base_pixel_x - var/y_offset = (living_target.icon_size - world.icon_size) * 0.5 - living_target.pixel_y + living_target.base_pixel_y + lockon_pixel_x = -living_target.pixel_x + living_target.base_pixel_x + lockon_pixel_y = (living_target.icon_size - world.icon_size) * 0.5 - living_target.pixel_y + living_target.base_pixel_y - lockon_icon = image(icon = 'icons/effects/Targeted.dmi', icon_state = "sniper_lockon_intense") - lockon_icon.pixel_x = x_offset - lockon_icon.pixel_y = y_offset - target.overlays += lockon_icon - - lockon_direction_icon = image(icon = 'icons/effects/Targeted.dmi', icon_state = "sniper_lockon_intense_direction", dir = get_cardinal_dir(target, origin_turf)) - lockon_direction_icon.pixel_x = x_offset - lockon_direction_icon.pixel_y = y_offset - target.overlays += lockon_direction_icon + apply_beam_visuals(target, TRUE) + aim_generation++ + INVOKE_ASYNC(src, PROC_REF(cycle_laser_color), target, aim_generation) /** * Recolors the (already-built) reticle icons and rebuilds the beam using the AMR's focused-fire beam, @@ -156,17 +142,26 @@ * The intent, with the flickering, and the blue color of the AMR focused shot, is to make it clear for xenos that * they are being targeted by something dangerous. */ -/obj/item/hardpoint/primary/cannon/proc/apply_beam_visuals(atom/target, use_dark_look) +/obj/item/hardpoint/primary/cannon/proc/apply_beam_visuals(atom/target, use_intense) var/turf/origin_turf = get_origin_turf() - lockon_icon.color = use_dark_look ? LTB_LASER_DARK_TINT : null - lockon_direction_icon.color = use_dark_look ? LTB_LASER_DARK_TINT : null - lockon_direction_icon.dir = get_cardinal_dir(target, origin_turf) + if(lockon_icon) + target.overlays -= lockon_icon + if(lockon_direction_icon) + target.overlays -= lockon_direction_icon + + lockon_icon = image(icon = 'icons/effects/Targeted.dmi', icon_state = use_intense ? "sniper_lockon_intense" : "sniper_lockon") + lockon_icon.pixel_x = lockon_pixel_x + lockon_icon.pixel_y = lockon_pixel_y + target.overlays += lockon_icon + + lockon_direction_icon = image(icon = 'icons/effects/Targeted.dmi', icon_state = use_intense ? "sniper_lockon_intense_direction" : "sniper_lockon_direction", dir = get_cardinal_dir(target, origin_turf)) + lockon_direction_icon.pixel_x = lockon_pixel_x + lockon_direction_icon.pixel_y = lockon_pixel_y + target.overlays += lockon_direction_icon QDEL_NULL(laser_beam) - laser_beam = anchor.beam(target, "laser_beam_intense", 'icons/effects/beam.dmi', (f_aiming_time + 1 SECONDS), beam_type = /obj/effect/ebeam/laser/intense/tank_mounted) - if(use_dark_look) - laser_beam.visuals.color = LTB_LASER_DARK_TINT + laser_beam = anchor.beam(target, use_intense ? "laser_beam_intense" : "laser_beam", 'icons/effects/beam.dmi', (f_aiming_time + 1 SECONDS), beam_type = /obj/effect/ebeam/laser/intense/tank_mounted) var/elapsed = world.time - charge_start_time var/current_progress = clamp(elapsed / f_aiming_time, 0, 1) @@ -174,16 +169,15 @@ laser_beam.visuals.alpha = LTB_LASER_MIN_ALPHA + (255 - LTB_LASER_MIN_ALPHA) * current_progress animate(laser_beam.visuals, alpha = LTB_LASER_MIN_ALPHA + (255 - LTB_LASER_MIN_ALPHA) * next_progress, 0.5 SECONDS, easing = SINE_EASING|EASE_OUT) -/// Flickers the reticle/beam between the AMR-intense blue's native brightness and a darker shade of -/// the same blue about twice a second while charging +/// Flickers the reticle/beam between red and blue about twice a second while charging /obj/item/hardpoint/primary/cannon/proc/cycle_laser_color(atom/locked_target, my_generation) - var/use_dark_look = FALSE + var/use_intense = TRUE while(laser_beam && !QDELETED(laser_beam) && !QDELETED(locked_target) && aim_generation == my_generation) sleep(5) if(!laser_beam || QDELETED(laser_beam) || QDELETED(locked_target) || aim_generation != my_generation) break - use_dark_look = !use_dark_look - apply_beam_visuals(locked_target, use_dark_look) + use_intense = !use_intense + apply_beam_visuals(locked_target, use_intense) /obj/item/hardpoint/primary/cannon/on_track_tick(atom/target) if(!anchor) diff --git a/code/modules/vehicles/hardpoints/secondary/brute_launcher.dm b/code/modules/vehicles/hardpoints/secondary/brute_launcher.dm index b555093a786c..164d7fd2a484 100644 --- a/code/modules/vehicles/hardpoints/secondary/brute_launcher.dm +++ b/code/modules/vehicles/hardpoints/secondary/brute_launcher.dm @@ -53,6 +53,7 @@ /obj/item/hardpoint/secondary/brute_launcher/handle_fire(atom/target, mob/living/user, params) if(charging) + SEND_SIGNAL(src, COMSIG_GUN_INTERRUPT_FIRE) return NONE if(!(istype(target, /obj/structure) || istype(target, /turf/closed/wall))) diff --git a/code/modules/vehicles/hardpoints/secondary/flag.dm b/code/modules/vehicles/hardpoints/secondary/flag.dm index c5221bafcbff..6f3c03b46178 100644 --- a/code/modules/vehicles/hardpoints/secondary/flag.dm +++ b/code/modules/vehicles/hardpoints/secondary/flag.dm @@ -120,6 +120,10 @@ if(signal && signal.z_initial != owner_turf.z) signal.z_initial = owner_turf.z + // Keep the CAS camera view following the tank. + if(signal?.linked_cam && signal.linked_cam.loc != owner_turf) + signal.linked_cam.forceMove(owner_turf) + range_bounds = SQUARE(owner_turf.x, owner_turf.y, buff_range) var/list/targets = SSquadtree.players_in_range(range_bounds, owner_turf.z, QTREE_SCAN_MOBS | QTREE_FILTER_LIVING) @@ -143,17 +147,5 @@ deactivate_flag() ..() -// Movement handling, update buff area when vehicle moves -/obj/item/hardpoint/secondary/united_americas_flag/on_move(turf/old, turf/new_turf, move_dir) - ..() - if(flag_active) - // Force immediate buff reapplication at new position - apply_area_effect() - // Keep the CAS camera view following the tank - if(signal?.linked_cam) - var/turf/owner_turf = get_turf(owner) - if(owner_turf) - signal.linked_cam.forceMove(owner_turf) - #undef PLANTED_FLAG_BUFF #undef PLANTED_FLAG_RANGE From 69c6ec822b56628ecc5bed1983c03326052acef7 Mon Sep 17 00:00:00 2001 From: BugWasabi <143133070+SunriseOverYourHead@users.noreply.github.com> Date: Tue, 7 Jul 2026 13:54:30 -0300 Subject: [PATCH 60/60] Artillery Module: Nerf & Buff --- .../vehicles/hardpoints/support/artillery.dm | 151 ++++++++++++++---- .../vehicles/multitile/multitile_verbs.dm | 42 +++++ code/modules/vehicles/tank/tank.dm | 17 ++ 3 files changed, 176 insertions(+), 34 deletions(-) diff --git a/code/modules/vehicles/hardpoints/support/artillery.dm b/code/modules/vehicles/hardpoints/support/artillery.dm index 758f6b828cb3..cbdb9804f2b2 100644 --- a/code/modules/vehicles/hardpoints/support/artillery.dm +++ b/code/modules/vehicles/hardpoints/support/artillery.dm @@ -1,6 +1,6 @@ /obj/item/hardpoint/support/artillery_module name = "\improper Artillery Module" - desc = "Allows the user to look far into the distance." + desc = "Allows the user to look far into the distance, and offers a night vision overlay." icon_state = "artillery" disp_icon = "tank" @@ -8,30 +8,62 @@ health = 250 - activatable = TRUE + // Not fired/toggled through the normal active-hardpoint cycle anymore. Uses itts own verbs. + activatable = FALSE - var/is_active = 0 var/view_buff = 10 //This way you can VV for more or less fun - var/view_tile_offset = 7 + var/view_tile_offset = 2 -/obj/item/hardpoint/support/artillery_module/handle_fire(atom/target, mob/living/user, params) - if(!user.client) + /// Mobs (driver and/or gunner) that currently have the magnified optics view toggled on for themselves. + var/list/mob/living/optics_users = list() + /// Mobs (driver and/or gunner) that currently have the night vision overlay toggled on for themselves. + var/list/mob/living/nvg_users = list() + + /// Color used for the night vision overlay's tint. + var/nvg_color = NV_COLOR_GREEN + /// The alpha of darkness the user is set to see through while night vision is active. + var/nvg_lighting_alpha = 100 + +/obj/item/hardpoint/support/artillery_module/proc/toggle_optics(mob/living/user) + if(health <= 0) + to_chat(user, SPAN_WARNING("\The [src] is broken!")) + return + if(!user?.client) return - if(is_active) - user.client.change_view(8, owner) - user.client.set_pixel_x(0) - user.client.set_pixel_y(0) - is_active = FALSE + if(user in optics_users) + disable_optics(user) + else + enable_optics(user) + +/obj/item/hardpoint/support/artillery_module/proc/enable_optics(mob/living/user) + if(!user?.client || (user in optics_users)) return + optics_users += user - var/atom/holder = owner - for(var/obj/item/hardpoint/holder/tank_turret/T in owner.hardpoints) - holder = T - break + var/obj/vehicle/multitile/vehicle = owner + if(user == vehicle.seats[VEHICLE_GUNNER]) + apply_gunner_view(user) + else + apply_driver_view(user) + +/obj/item/hardpoint/support/artillery_module/proc/disable_optics(mob/living/user) + if(!(user in optics_users)) + return + optics_users -= user + reset_view(user) + +/// Zooms the gunner's view and shifts it forward along the turret's current facing. +/obj/item/hardpoint/support/artillery_module/proc/apply_gunner_view(mob/living/user) + if(!user || !user.client) + return user.client.change_view(view_buff, owner) - is_active = TRUE + + var/atom/holder = owner + for(var/obj/item/hardpoint/holder/tank_turret/turret in owner.hardpoints) + holder = turret + break switch(holder.dir) if(NORTH) @@ -47,25 +79,76 @@ user.client.set_pixel_x(-1 * view_tile_offset * 32) user.client.set_pixel_y(0) -/obj/item/hardpoint/support/artillery_module/deactivate() - if(!is_active) +/// Zooms the driver's view, centered on the tank instead of shifted forwards +/obj/item/hardpoint/support/artillery_module/proc/apply_driver_view(mob/living/user) + if(!user || !user.client) return + user.client.change_view(view_buff, owner) + user.client.set_pixel_x(0) + user.client.set_pixel_y(0) - var/obj/vehicle/multitile/C = owner - for(var/seat in C.seats) - if(!ismob(C.seats[seat])) - continue - var/mob/user = C.seats[seat] - if(!user.client) - continue - user.client.change_view(GLOB.world_view_size, owner) - user.client.set_pixel_x(0) - user.client.set_pixel_y(0) - is_active = FALSE - -/obj/item/hardpoint/support/artillery_module/try_fire(target, user, params) +/obj/item/hardpoint/support/artillery_module/proc/reset_view(mob/living/user) + if(!user || !user.client) + return + user.client.change_view(8, owner) + user.client.set_pixel_x(0) + user.client.set_pixel_y(0) + +/obj/item/hardpoint/support/artillery_module/proc/toggle_nvg(mob/living/user) if(health <= 0) - to_chat(usr, SPAN_WARNING("\The [src] is broken!")) - return NONE + to_chat(user, SPAN_WARNING("\The [src] is broken!")) + return + if(!ishuman(user)) + return + + if(user in nvg_users) + disable_nvg(user) + else + enable_nvg(user) + +/obj/item/hardpoint/support/artillery_module/proc/enable_nvg(mob/living/carbon/human/user) + if(!ishuman(user) || (user in nvg_users)) + return + nvg_users += user + + RegisterSignal(user, COMSIG_HUMAN_POST_UPDATE_SIGHT, PROC_REF(on_update_sight)) + user.add_client_color_matrix("tank_artillery_nvg", 99, color_matrix_multiply(color_matrix_saturation(0), color_matrix_from_string(nvg_color))) + user.overlay_fullscreen("tank_artillery_nvg", /atom/movable/screen/fullscreen/flash/noise/nvg) + user.overlay_fullscreen("tank_artillery_nvg_blur", /atom/movable/screen/fullscreen/brute/nvg, 3) + user.update_sight() + to_chat(user, SPAN_NOTICE("You activate \the [src]'s night vision overlay.")) + +/obj/item/hardpoint/support/artillery_module/proc/disable_nvg(mob/living/carbon/human/user) + if(!(user in nvg_users)) + return + nvg_users -= user + + UnregisterSignal(user, COMSIG_HUMAN_POST_UPDATE_SIGHT) + user.remove_client_color_matrix("tank_artillery_nvg", 1 SECONDS) + user.clear_fullscreen("tank_artillery_nvg", 0.5 SECONDS) + user.clear_fullscreen("tank_artillery_nvg_blur", 0.5 SECONDS) + user.update_sight() + to_chat(user, SPAN_NOTICE("You deactivate \the [src]'s night vision overlay.")) + +/obj/item/hardpoint/support/artillery_module/proc/on_update_sight(mob/living/carbon/human/user) + SIGNAL_HANDLER + if(nvg_lighting_alpha < 255) + user.see_in_dark = 12 + user.lighting_alpha = nvg_lighting_alpha + user.sync_lighting_plane_alpha() + +/// Clears both effects off of one specific user +/obj/item/hardpoint/support/artillery_module/proc/clear_user_effects(mob/living/user) + disable_optics(user) + disable_nvg(user) + +/// Clears both effects off of everyone currently using them if the module gets broken or uninstalled when used +/obj/item/hardpoint/support/artillery_module/deactivate() + for(var/mob/living/user as anything in optics_users.Copy()) + disable_optics(user) + for(var/mob/living/user as anything in nvg_users.Copy()) + disable_nvg(user) - return handle_fire(target, user, params) +/obj/item/hardpoint/support/artillery_module/on_uninstall(obj/vehicle/multitile/vehicle) + deactivate() + ..() diff --git a/code/modules/vehicles/multitile/multitile_verbs.dm b/code/modules/vehicles/multitile/multitile_verbs.dm index 5807a9dcfc3a..7df1f0897210 100644 --- a/code/modules/vehicles/multitile/multitile_verbs.dm +++ b/code/modules/vehicles/multitile/multitile_verbs.dm @@ -242,6 +242,48 @@ return T.toggle_gyro(usr) +//toggles the artillery module's magnified optics for your own view - available to both driver and gunner +/obj/vehicle/multitile/proc/toggle_artillery_optics() + set name = "Toggle Artillery Optics" + set desc = "Toggles the artillery module's magnified optics for your own view." + set category = "Vehicle" + + var/mob/living/user = usr + if(!user || !istype(user)) + return + + var/obj/vehicle/multitile/vehicle = user.interactee + if(!istype(vehicle)) + return + + var/obj/item/hardpoint/support/artillery_module/AM = locate() in vehicle.hardpoints + if(!AM) + to_chat(user, SPAN_WARNING("No artillery module installed.")) + return + + AM.toggle_optics(user) + +//toggles the artillery module's night vision overlay for your own view - available to both driver and gunner +/obj/vehicle/multitile/proc/toggle_artillery_nvg() + set name = "Toggle Artillery Night Vision" + set desc = "Toggles the artillery module's night vision overlay for your own view." + set category = "Vehicle" + + var/mob/living/user = usr + if(!user || !istype(user)) + return + + var/obj/vehicle/multitile/vehicle = user.interactee + if(!istype(vehicle)) + return + + var/obj/item/hardpoint/support/artillery_module/AM = locate() in vehicle.hardpoints + if(!AM) + to_chat(user, SPAN_WARNING("No artillery module installed.")) + return + + AM.toggle_nvg(user) + //hands control of the secondary hardpoint (aim + fire) between the gunner and the driver /obj/vehicle/multitile/proc/toggle_slave_secondary_to_driver() set name = "Slave Secondary to Driver" diff --git a/code/modules/vehicles/tank/tank.dm b/code/modules/vehicles/tank/tank.dm index 3d38be6c0c7a..fc5a3818b769 100644 --- a/code/modules/vehicles/tank/tank.dm +++ b/code/modules/vehicles/tank/tank.dm @@ -131,6 +131,11 @@ /obj/vehicle/multitile/proc/toggle_slave_secondary_to_driver, /obj/vehicle/multitile/proc/toggle_hardpoint_fire_mode, )) + if(seat == VEHICLE_DRIVER || seat == VEHICLE_GUNNER) + add_verb(M.client, list( + /obj/vehicle/multitile/proc/toggle_artillery_optics, + /obj/vehicle/multitile/proc/toggle_artillery_nvg, + )) /obj/vehicle/multitile/tank/remove_seated_verbs(mob/living/M, seat) @@ -157,6 +162,18 @@ /obj/vehicle/multitile/proc/toggle_slave_secondary_to_driver, /obj/vehicle/multitile/proc/toggle_hardpoint_fire_mode, )) + if(seat == VEHICLE_DRIVER || seat == VEHICLE_GUNNER) + remove_verb(M.client, list( + /obj/vehicle/multitile/proc/toggle_artillery_optics, + /obj/vehicle/multitile/proc/toggle_artillery_nvg, + )) + var/obj/item/hardpoint/support/artillery_module/AM = get_artillery_module() + AM?.clear_user_effects(M) + +/obj/vehicle/multitile/tank/proc/get_artillery_module() + for(var/obj/item/hardpoint/support/artillery_module/AM in hardpoints) + return AM + return null //Called when players try to move vehicle //Another wrapper for try_move()