Files
Bubberstation/code/modules/vehicles/mecha/mecha_mob_interaction.dm
XackiiGitHubtgstation-server-ci[bot] <161980869+tgstation-server-ci[bot]@users.noreply.github.com>tgstation-ci[bot] <179393467+tgstation-ci[bot]@users.noreply.github.com>Time-Green
9b28da6e38 Justice mech: Charge attack and code refactor/New blocking system/some balance and sprites! (#91434)
## About The Pull Request

New changes for Justice mech - traitor mech that can be created on
emaged fabricator(you need robo skillchip to emag it)
1. Charge attack refactor
Charged attack is now used not by pressing the action button but on
holding right click
![2025-06-0119-05-471-ezgif
com-video-to-gif-converter](https://github.com/user-attachments/assets/d5102cd7-bd23-4e05-916c-797a41668fd9)
While you press right click scope will focus on tile mouse looking for
and when you release right button you will charge on this tile (cooldown
5 seconds as before). if your mouse look at position you unable to
charge your mouse icon will become red
2.  New Justice block
Now Justice don't have 60% chance to block any damage. Instead of this
rng, justice has 3 engines that orbit around him
![2025-06-0119-05-472-ezgif
com-video-to-gif-converter](https://github.com/user-attachments/assets/fb5a615f-714e-499e-9d1b-6b6954b1e540)
Engines can be active and disabled. While all engines active they will
block any range projictiles back.
![2025-06-0119-05-473-ezgif
com-video-to-gif-converter](https://github.com/user-attachments/assets/a1d82eed-4cbd-4e91-891b-16000c78346d)
To deactivate engine you need to melee hit mech. When all engines
deactivated mech loses its ability to deflect for 10 seconds, after
which all cores are recharged and the cycle continues again.
Justice can also recharge one engine when pilot hits someone who not in
crit.
![2025-06-0119-05-474-ezgif
com-video-to-gif-converter](https://github.com/user-attachments/assets/eae7a108-ac8b-4274-9c1d-075b9bb3b2e7)
3. 
Some balance changes:
Mech hp now 300
Mech melee armor now 50
Mech bullet and laser armor now 30
 Mech now don't have speed change  when you switch safety mod. 
Instead mech becomes faster in invisibility and slower when holding the
right mouse button to charge a charge attack.
Invisibility mode now no longer temporary. You can be invisible as long
as you want until you attack/ bump with someone/ take damage or turn it
off yourself
AoE attack from invisibility is now x2 faster
![2025-06-0119-05-475-ezgif
com-video-to-gif-converter](https://github.com/user-attachments/assets/1b10c9bd-c130-4f1b-875a-18228da4026a)

## Why It's Good For The Game

Lots of changes from recived feedback. 
Basically the most annoying thing was the block chances. Praying on luck
to not die from laser spam turned out to be not a very interesting
mechanic. Now mech justice requires more strategic actions to destroy it
and the mechanics create more fun for both the mech user and those who
fight with it. You need to get close to the mech to hit it and only then
will you be able to shoot the laser and user of justice has the ability
to recharge his cores by attacking enemies (if you don't attack, then
you won't be able to recharge the cores (well, only if your other cores
are broken)). This all creates more risk reward elements and not just
rng.
Accordance with block chance changes i also increased HP and armor of
Justice to balance the block changes a bit.
AoE attack speed was needed simply because 100/100 cases everyone said
that the aoe attack is weak and useless because it is almost very easy
to get away from it.
Changing the charge attack work mechanic is necessary to improve the
maneuverability of the mech. pressing the charge button and then getting
messages that you chose the wrong direction is very inconvenient and
deprives comfortable gameplay and pleasant use of the mech.

## Changelog

🆑
add: Justice mech: New block and charge system! Build and try it now!
qol: Justice mech: charge attack is now much more pleasant and
convenient to use!
balance: Justice mech: Increased HP and armor.
balance: Justice mech: Invisibility is now infinite.
balance: Justice mech: stealth aoe attack is now 2x faster.
refactor: Justice mech: Some code clenup and mechanic improvements.
/🆑

---------

Co-authored-by: tgstation-server-ci[bot] <161980869+tgstation-server-ci[bot]@users.noreply.github.com>
Co-authored-by: tgstation-ci[bot] <179393467+tgstation-ci[bot]@users.noreply.github.com>
Co-authored-by: Time-Green <7501474+Time-Green@users.noreply.github.com>
2025-07-21 12:22:55 +02:00

230 lines
8.9 KiB
Plaintext

/obj/vehicle/sealed/mecha/mob_try_enter(mob/M)
if(!ishuman(M)) // no silicons or drones in mechas.
return
if(HAS_TRAIT(M, TRAIT_PRIMITIVE)) //no lavalizards either.
to_chat(M, span_warning("The knowledge to use this device eludes you!"))
return
log_message("[M] tried to move into [src].", LOG_MECHA)
if(dna_lock && M.has_dna())
var/mob/living/carbon/entering_carbon = M
if(entering_carbon.dna.unique_enzymes != dna_lock)
to_chat(M, span_warning("Access denied. [name] is secured with a DNA lock."))
log_message("Permission denied (DNA LOCK).", LOG_MECHA)
return
if((mecha_flags & ID_LOCK_ON) && !allowed(M))
to_chat(M, span_warning("Access denied. Insufficient operation keycodes."))
log_message("Permission denied (No keycode).", LOG_MECHA)
return
. = ..()
if(.)
moved_inside(M)
/obj/vehicle/sealed/mecha/enter_checks(mob/M)
if(M.incapacitated)
return FALSE
if(atom_integrity <= 0)
to_chat(M, span_warning("You cannot get in the [src], it has been destroyed!"))
return FALSE
if(M.buckled)
to_chat(M, span_warning("You can't enter the exosuit while buckled."))
log_message("Permission denied (Buckled).", LOG_MECHA)
return FALSE
if(M.has_buckled_mobs())
to_chat(M, span_warning("You can't enter the exosuit with other creatures attached to you!"))
log_message("Permission denied (Attached mobs).", LOG_MECHA)
return FALSE
for(var/obj/item/thing in M.held_items)
if(!(thing.item_flags & (ABSTRACT|HAND_ITEM)))
to_chat(M, span_warning("You can't enter the exosuit while your hands are occupied!"))
return FALSE
return ..()
///proc called when a new non-mmi mob enters this mech
/obj/vehicle/sealed/mecha/proc/moved_inside(mob/living/newoccupant)
if(!(newoccupant?.client))
return FALSE
if(ishuman(newoccupant) && !Adjacent(newoccupant))
return FALSE
mecha_flags &= ~PANEL_OPEN //Close panel if open
newoccupant.forceMove(src)
newoccupant.update_mouse_pointer()
add_fingerprint(newoccupant)
log_message("[newoccupant] moved in as pilot.", LOG_MECHA)
setDir(SOUTH)
playsound(src, 'sound/machines/windowdoor.ogg', 50, TRUE)
set_mouse_pointer()
if(!internal_damage)
SEND_SOUND(newoccupant, sound('sound/vehicles/mecha/nominal.ogg',volume=50))
return TRUE
///proc called when a new mmi mob tries to enter this mech
/obj/vehicle/sealed/mecha/proc/mmi_move_inside(obj/item/mmi/brain_obj, mob/user)
if(!(mecha_flags & MMI_COMPATIBLE))
to_chat(user, span_warning("This mecha is not compatible with MMIs!"))
return FALSE
if(!brain_obj.brain_check(user))
return FALSE
var/mob/living/brain/brain_mob = brain_obj.brainmob
if(LAZYLEN(occupants) >= max_occupants)
to_chat(user, span_warning("It's full!"))
return FALSE
if(dna_lock && (!brain_mob.stored_dna || (dna_lock != brain_mob.stored_dna.unique_enzymes)))
to_chat(user, span_warning("Access denied. [name] is secured with a DNA lock."))
return FALSE
visible_message(span_notice("[user] starts to insert an MMI into [name]."))
if(!do_after(user, 4 SECONDS, target = src))
to_chat(user, span_notice("You stop inserting the MMI."))
return FALSE
if(LAZYLEN(occupants) < max_occupants)
return mmi_moved_inside(brain_obj, user)
to_chat(user, span_warning("Maximum occupants exceeded!"))
return FALSE
///proc called when a new mmi mob enters this mech
/obj/vehicle/sealed/mecha/proc/mmi_moved_inside(obj/item/mmi/brain_obj, mob/user)
if(!(Adjacent(brain_obj) && Adjacent(user)))
return FALSE
if(!brain_obj.brain_check(user))
return FALSE
var/mob/living/brain/brain_mob = brain_obj.brainmob
if(!user.transferItemToLoc(brain_obj, src))
to_chat(user, span_warning("[brain_obj] is stuck to your hand, you cannot put it in [src]!"))
return FALSE
brain_obj.set_mecha(src)
add_occupant(brain_mob)//Note this forcemoves the brain into the mech to allow relaymove
mecha_flags &= ~PANEL_OPEN //Close panel if open
mecha_flags |= SILICON_PILOT
brain_mob.reset_perspective(src)
brain_mob.remote_control = src
brain_mob.update_mouse_pointer()
setDir(SOUTH)
log_message("[brain_obj] moved in as pilot.", LOG_MECHA)
if(!internal_damage)
SEND_SOUND(brain_obj, sound('sound/vehicles/mecha/nominal.ogg',volume=50))
user.log_message("has put the MMI/posibrain of [key_name(brain_mob)] into [src]", LOG_GAME)
brain_mob.log_message("was put into [src] by [key_name(user)]", LOG_GAME, log_globally = FALSE)
return TRUE
/obj/vehicle/sealed/mecha/mob_exit(mob/M, silent = FALSE, randomstep = FALSE, forced = FALSE)
var/atom/movable/mob_container
var/turf/newloc = get_turf(src)
if(ishuman(M))
mob_container = M
else if(isbrain(M))
var/mob/living/brain/brain = M
mob_container = brain.container
else if(isAI(M))
var/mob/living/silicon/ai/AI = M
mob_container = AI
//stop listening to this signal, as the static update is now handled by the eyeobj's setLoc
AI.eyeobj?.UnregisterSignal(src, COMSIG_MOVABLE_MOVED)
AI.eyeobj?.forceMove(newloc) //kick the eye out as well
AI.controlled_equipment = null
AI.remote_control = null
if(forced)
if(!AI.linked_core) //if the victim AI has no core
if (!AI.can_shunt || !length(AI.hacked_apcs))
AI.investigate_log("has been gibbed by being forced out of their mech.", INVESTIGATE_DEATHS)
/// If an AI with no core (and no shunting abilities) gets forced out of their mech
/// (in a way that isn't handled by the normal handling of their mech being destroyed)
/// we gib 'em here, too.
AI.gib(DROP_ALL_REMAINS)
AI = null
mecha_flags &= ~SILICON_PILOT
return ..()
else
var/obj/machinery/power/apc/emergency_shunt_apc = pick(AI.hacked_apcs)
emergency_shunt_apc.malfoccupy(AI) //get shunted into a random APC (you don't get to choose which)
AI = null
mecha_flags &= ~SILICON_PILOT
return ..()
if(!forced && !silent)
to_chat(AI, span_notice("Returning to core..."))
mecha_flags &= ~SILICON_PILOT
newloc = get_turf(AI.linked_core)
qdel(AI.linked_core)
AI.forceMove(newloc)
if(forced)
to_chat(AI, span_danger("ZZUZULU.ERR--ERRR-NEUROLOG-- PERCEP--- DIST-B**@"))
for(var/count in 1 to 5)
addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(do_sparks), rand(10, 20), FALSE, AI), count SECONDS)
addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(empulse), get_turf(AI), /*heavy_range = */10, /*light_range = */20), 10 SECONDS)
return ..()
else if(isliving(M))
mob_container = M
else
return ..()
var/mob/living/ejector = M
mob_container.forceMove(newloc)//ejecting mob container
log_message("[mob_container] moved out.", LOG_MECHA)
SStgui.close_user_uis(M, src)
if(istype(mob_container, /obj/item/mmi))
var/obj/item/mmi/mmi = mob_container
if(mmi.brainmob)
ejector.forceMove(mmi)
ejector.reset_perspective()
remove_occupant(ejector)
mmi.set_mecha(null)
mmi.update_appearance()
setDir(SOUTH)
SEND_SIGNAL(src, COMSIG_MECHA_MOB_EXIT)
return ..()
/obj/vehicle/sealed/mecha/add_occupant(mob/driver, control_flags)
RegisterSignal(driver, COMSIG_MOB_CLICKON, PROC_REF(on_mouseclick), TRUE)
RegisterSignal(driver, COMSIG_MOB_SAY, PROC_REF(display_speech_bubble), TRUE)
RegisterSignal(driver, COMSIG_MOVABLE_KEYBIND_FACE_DIR, PROC_REF(on_turn), TRUE)
RegisterSignal(driver, COMSIG_MOB_ALTCLICKON, PROC_REF(on_click_alt))
. = ..()
update_appearance()
/obj/vehicle/sealed/mecha/remove_occupant(mob/driver)
UnregisterSignal(driver, list(
COMSIG_MOB_CLICKON,
COMSIG_MOB_SAY,
COMSIG_MOVABLE_KEYBIND_FACE_DIR,
COMSIG_MOB_ALTCLICKON,
))
driver.clear_alert(ALERT_CHARGE)
driver.clear_alert(ALERT_MECH_DAMAGE)
if(driver.client)
driver.update_mouse_pointer()
driver.client.view_size.resetToDefault()
zoom_mode = FALSE
. = ..()
update_appearance()
/obj/vehicle/sealed/mecha/container_resist_act(mob/living/user)
if(isAI(user))
var/mob/living/silicon/ai/AI = user
if(!AI.linked_core)
to_chat(AI, span_userdanger("Inactive core destroyed. Unable to return."))
if(!AI.can_shunt || !AI.hacked_apcs.len)
to_chat(AI, span_warning("[AI.can_shunt ? "No hacked APCs available." : "No shunting capabilities."]"))
return
var/confirm = tgui_alert(AI, "Shunt to a random APC? You won't have anywhere else to go!", "Confirm Emergency Shunt", list("Yes", "No"))
if(confirm == "Yes")
/// Mechs with open cockpits can have the pilot shot by projectiles, or EMPs may destroy the AI inside
/// Alternatively, destroying the mech will shunt the AI if they can shunt, or a deadeye wizard can hit
/// them with a teleportation bolt
if (AI.stat == DEAD || AI.loc != src)
return
mob_exit(AI, forced = TRUE)
return
to_chat(user, span_notice("You begin the ejection procedure. Equipment is disabled during this process. Hold still to finish ejecting."))
is_currently_ejecting = TRUE
if(do_after(user, has_gravity() ? exit_delay : 0 , target = src))
to_chat(user, span_notice("You exit the mech."))
if(cabin_sealed)
set_cabin_seal(user, FALSE)
mob_exit(user, silent = TRUE)
else
to_chat(user, span_notice("You stop exiting the mech. Weapons are enabled again."))
is_currently_ejecting = FALSE