diff --git a/code/game/mecha/combat/durand.dm b/code/game/mecha/combat/durand.dm index 4ccf400e6ab11..a42e1e29f7eec 100644 --- a/code/game/mecha/combat/durand.dm +++ b/code/game/mecha/combat/durand.dm @@ -12,23 +12,24 @@ force = 40 wreckage = /obj/structure/mecha_wreckage/durand var/obj/durand_shield/shield + var/shield_type = /obj/durand_shield var/shield_passive_drain = 300 + /obj/mecha/combat/durand/clip - desc = "An aging combat exosuit appropriated from abandoned Nanotrasen facilities, now supplied to the CMM-BARD anti-xenofauna division." + desc = "An aging combat exosuit appropriated from abandoned Nanotrasen facilities, now supplied to the CMM-BARD anti-xenofauna division. The defence grid has been modified to disperse controlled electric shocks on contact, at the cost of its ability to block ranged projectiles." name = "\improper Paladin" icon_state = "clipdurand" wreckage = /obj/structure/mecha_wreckage/durand/clip armor = list("melee" = 40, "bullet" = 35, "laser" = 15, "energy" = 10, "bomb" = 20, "bio" = 0, "rad" = 50, "fire" = 100, "acid" = 100) - - //TODO: Custom melee backlash shield with no projectile protection + shield_passive_drain = 0 + shield_type = /obj/durand_shield/clip /obj/mecha/combat/durand/Initialize() . = ..() - shield = new /obj/durand_shield(loc, src, layer, dir) + shield = new shield_type(loc, src, layer, dir) RegisterSignal(src, COMSIG_MECHA_ACTION_ACTIVATE, PROC_REF(relay)) - RegisterSignal(src, COMSIG_PROJECTILE_PREHIT, PROC_REF(prehit)) /obj/mecha/combat/durand/Destroy() @@ -71,22 +72,21 @@ if(!shield) //if the shield somehow got deleted stack_trace("Durand triggered relay without a shield") - shield = new /obj/durand_shield(loc, src, layer) + shield = new shield_type(loc, src, layer) shield.setDir(dir) SEND_SIGNAL(shield, COMSIG_MECHA_ACTION_ACTIVATE, source, signal_args) //Redirects projectiles to the shield if defense_check decides they should be blocked and returns true. -/obj/mecha/combat/durand/proc/prehit(obj/projectile/source, list/signal_args) - SIGNAL_HANDLER - - if(defense_check(source.loc) && shield) - signal_args[2] = shield - +/obj/mecha/combat/durand/bullet_act(obj/projectile/source) + if(defense_check(source.loc, shield.ranged_pass)) + shield.bullet_act(source) + else + . = ..() /**Checks if defense mode is enabled, and if the attacker is standing in an area covered by the shield. -Expects a turf. Returns true if the attack should be blocked, false if not.*/ -/obj/mecha/combat/durand/proc/defense_check(turf/aloc) - if (!defense_mode || !shield || shield.switching) +Expects a turf. Returns true if the attack should be blocked, false if not. Skip defence will make the proc return false and the attack will go through*/ +/obj/mecha/combat/durand/proc/defense_check(turf/aloc, skip_defence = FALSE) + if (!defense_mode || !shield || shield.switching || skip_defence) return FALSE . = FALSE switch(dir) @@ -105,26 +105,38 @@ Expects a turf. Returns true if the attack should be blocked, false if not.*/ return /obj/mecha/combat/durand/attack_generic(mob/user, damage_amount = 0, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, armor_penetration = 0) - if(defense_check(user.loc)) + if(defense_check(user.loc, shield.melee_pass)) log_message("Attack absorbed by defense field. Attacker - [user].", LOG_MECHA, color="orange") shield.attack_generic(user, damage_amount, damage_type, damage_flag, sound_effect, armor_penetration) else . = ..() /obj/mecha/combat/durand/attackby(obj/item/W as obj, mob/user as mob, params) - if(defense_check(user.loc)) + if(defense_check(user.loc, shield.melee_pass)) log_message("Attack absorbed by defense field. Attacker - [user], with [W]", LOG_MECHA, color="orange") shield.attackby(W, user, params) else . = ..() /obj/mecha/combat/durand/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum) - if(defense_check(AM.loc)) + if(defense_check(AM.loc, shield.ranged_pass)) log_message("Impact with [AM] absorbed by defense field.", LOG_MECHA, color="orange") shield.hitby(AM, skipcatch, hitpush, blocked, throwingdatum) else . = ..() +// Walking into the Paladin's shield shocks you. + +/obj/mecha/combat/durand/clip/Bump(atom/obstacle) + . = ..() + if(defense_check(obstacle.loc) && isliving(obstacle)) + shield.contact(obstacle) + +/obj/mecha/combat/durand/clip/Bumped(atom/movable/AM) + . = ..() + if(defense_check(AM.loc) && isliving(AM)) + shield.contact(AM) + //////////////////////////// ///// Shield processing //// //////////////////////////// @@ -151,7 +163,14 @@ own integrity back to max. Shield is automatically dropped if we run out of powe light_on = FALSE var/obj/mecha/combat/durand/chassis ///Our link back to the durand var/switching = FALSE ///To keep track of things during the animation + /// if this shield lets melee attacks pass and hit the mech directly + var/melee_pass = FALSE + /// if this shield lets projectiles pass and hit the mech directly + var/ranged_pass = FALSE +/obj/durand_shield/clip + name = "electric repulsion grid" + ranged_pass = TRUE /obj/durand_shield/Initialize(mapload, _chassis, _layer, _dir) . = ..() @@ -230,3 +249,35 @@ the shield is disabled by means other than the action button (like running out o /obj/durand_shield/bullet_act() play_attack_sound() . = ..() + +/// a mob has bumped into the shield +/obj/durand_shield/proc/contact(mob/living/contactor) + return + +/// Clippy shield +/obj/durand_shield/clip/attack_generic(mob/user, damage_amount, damage_type, damage_flag, sound_effect, armor_penetration) + . = ..() + apply_shock(user) + +/obj/durand_shield/clip/attackby(obj/item/I, mob/living/user, params) + . = ..() + apply_shock(user) + +/obj/durand_shield/clip/contact(mob/living/contactor) + . = ..() + apply_shock(contactor) + +/obj/durand_shield/clip/proc/apply_shock(mob/attacker) + var/did_shock = FALSE + if(iscarbon(attacker)) + var/mob/living/carbon/victim = attacker + if(electrocute_mob(victim, chassis.cell, src, 1, FALSE, FALSE)) + did_shock = TRUE + else if(isliving(attacker)) + var/mob/living/victim = attacker + if(victim.apply_damage_type(20,BURN)) + to_chat(victim,span_userdanger("You're shocked by \the [src]!")) + did_shock = TRUE + if(did_shock) + visible_message(span_bolddanger("\The [src] repels \the [attacker] on contact, shocking [attacker.p_them()].")) + do_sparks(5,TRUE,src) diff --git a/code/game/mecha/combat/gygax.dm b/code/game/mecha/combat/gygax.dm index 5fe5d9350c14f..02f66b54236a4 100644 --- a/code/game/mecha/combat/gygax.dm +++ b/code/game/mecha/combat/gygax.dm @@ -58,6 +58,13 @@ return cell = new /obj/item/stock_parts/cell/bluespace(src) +/obj/mecha/combat/gygax/inteq + name = "\improper Basenji" + desc = "A lightweight security exosuit, modified to IRMG standards. The leg actuators have been maxed out, allowing for powerful short ranged charges." + icon_state = "inteqgygax" + charge_break_walls = TRUE + charge_toss_structures = TRUE + charge_toss_mobs = TRUE /obj/mecha/combat/gygax/GrantActions(mob/living/user, human_occupant = 0) ..() @@ -67,3 +74,12 @@ /obj/mecha/combat/gygax/RemoveActions(mob/living/user, human_occupant = 0) ..() overload_action.Remove(user) + +/obj/mecha/combat/gygax/inteq/GrantActions(mob/living/user, human_occupant = 0) + ..() + overload_action.Remove(user) + charge_action.Grant(user,src) + +/obj/mecha/combat/gygax/inteq/RemoveActions(mob/living/user, human_occupant) + . = ..() + charge_action.Remove(user) diff --git a/code/game/mecha/equipment/tools/work_tools.dm b/code/game/mecha/equipment/tools/work_tools.dm index ee8481255f2d4..44c8c6626dae2 100644 --- a/code/game/mecha/equipment/tools/work_tools.dm +++ b/code/game/mecha/equipment/tools/work_tools.dm @@ -575,3 +575,10 @@ icon_state = "clipupgrade" source_mech = list(/obj/mecha/combat/durand) result_mech = /obj/mecha/combat/durand/clip + +/obj/item/mecha_parts/mecha_equipment/conversion_kit/inteq_gygax + name = "IRMG Basenji Conversion Kit" + desc = "An IRMG-custom conversion kit for a Gygax combat exosuit, to convert it to the specialized Pyrnese breaching exosuit." + source_mech = list(/obj/mecha/combat/gygax,/obj/mecha/combat/gygax/dark) + result_mech = /obj/mecha/combat/gygax/inteq + diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm index a1b46fd2fbfa5..db59620b39fd3 100644 --- a/code/game/mecha/mecha.dm +++ b/code/game/mecha/mecha.dm @@ -41,6 +41,16 @@ var/last_user_hud = 1 // used to show/hide the mecha hud while preserving previous preference var/completely_disabled = FALSE //stops the mech from doing anything + ///Vars for mech charges + var/charging = FALSE + var/charge_ready = TRUE + var/charge_cooldown = 50 + var/charge_power_consume = 200 + var/charge_distance = 5 + var/charge_break_walls = FALSE + var/charge_toss_structures = FALSE + var/charge_toss_mobs = FALSE + var/bumpsmash = 0 //Whether or not the mech destroys walls by running into it. //inner atmos var/use_internal_tank = 0 @@ -90,6 +100,7 @@ var/datum/action/innate/mecha/mech_view_stats/stats_action = new var/datum/action/innate/mecha/mech_defense_mode/defense_action = new var/datum/action/innate/mecha/mech_overload_mode/overload_action = new + var/datum/action/innate/mecha/mech_charge_mode/charge_action = new var/datum/effect_system/smoke_spread/smoke_system = new //not an action, but trigged by one var/datum/action/innate/mecha/mech_smoke/smoke_action = new var/datum/action/innate/mecha/mech_zoom/zoom_action = new @@ -643,6 +654,7 @@ step_silent = FALSE /obj/mecha/Bump(atom/obstacle) + var/atom/throw_target = get_edge_target_turf(obstacle, dir) if(phasing && get_charge() >= phasing_energy_drain && !throwing) if(!can_move) return @@ -654,6 +666,27 @@ forceMove(get_step(src,dir)) use_power(phasing_energy_drain) addtimer(VARSET_CALLBACK(src, can_move, TRUE), step_in*3) + else if(charging) + if(charge_break_walls && iswallturf(obstacle)) + var/turf/closed/wall/crushed = obstacle + playsound(src, 'sound/effects/meteorimpact.ogg', 100, TRUE) + visible_message(span_danger("[src] smashes through [obstacle]")) + crushed.dismantle_wall(TRUE) + if(isobj(obstacle)) + var/obj/object = obstacle + obstacle.mech_melee_attack(src) + if(!(object.resistance_flags & INDESTRUCTIBLE) && charge_toss_structures) + object.throw_at(throw_target, 4, 3) + visible_message(span_danger("[src] crashes into [obstacle]!")) + playsound(src, 'sound/effects/bang.ogg', 50, TRUE) + if(ishuman(obstacle)) + var/mob/living/carbon/human/H = obstacle + H.throw_at(throw_target,4,3) + visible_message(span_danger("[src] slams into \the [obstacle], sending [obstacle.p_them()] flying!")) + playsound(H, 'sound/effects/bang.ogg', 100, FALSE, -1) + H.Paralyze(20) + H.adjustStaminaLoss(30) + H.apply_damage(rand(20,35), BRUTE) else if(..()) //mech was thrown return @@ -1212,3 +1245,30 @@ GLOBAL_VAR_INIT(year_integer, text2num(year)) // = 2013??? else to_chat(user, "None of the equipment on this exosuit can use this ammo!") return FALSE + + +/////////////////////// +////// Charging ///// +/////////////////////// + +/obj/mecha/proc/start_charge() + Shake(15, 15, 1 SECONDS) + var/obj/effect/temp_visual/decoy/new_decoy = new /obj/effect/temp_visual/decoy(loc,src) + animate(new_decoy, alpha = 0, color = "#5a5858", transform = matrix()*2, time = 2) + addtimer(CALLBACK(src,PROC_REF(handle_charge)),0.5 SECONDS, TIMER_STOPPABLE) + +/obj/mecha/proc/handle_charge() + var/turf/mecha_loc = get_turf(src) + charging = TRUE + var/turf/charge_target = get_ranged_target_turf(mecha_loc,dir,charge_distance) + if(!charge_target) + charging = FALSE + return + cell.use(charge_power_consume) + walk_towards(src, charge_target, 0.7) + sleep(get_dist(src, charge_target) * 0.7) + charge_end() + +/obj/mecha/proc/charge_end() + walk(src,0) + charging = FALSE diff --git a/code/game/mecha/mecha_actions.dm b/code/game/mecha/mecha_actions.dm index 6d860558a6522..f10fb49c29bc8 100644 --- a/code/game/mecha/mecha_actions.dm +++ b/code/game/mecha/mecha_actions.dm @@ -183,6 +183,20 @@ chassis.occupant_message("You disable leg actuators overload.") UpdateButtonIcon() +/datum/action/innate/mecha/mech_charge_mode + name = "Charge" + button_icon_state = "mech_overload_off" + +/datum/action/innate/mecha/mech_charge_mode/Activate() + if(!owner || !chassis || chassis.occupant != owner) + return + if(chassis.charge_ready && !chassis.charging) + chassis.start_charge() + chassis.charge_ready = FALSE + addtimer(VARSET_CALLBACK(chassis, charge_ready, TRUE), chassis.charge_cooldown) + else + chassis.occupant_message(span_warning("The leg actuators are still recharging!")) + /datum/action/innate/mecha/mech_smoke name = "Smoke" button_icon_state = "mech_smoke" diff --git a/code/modules/power/power.dm b/code/modules/power/power.dm index a386a739622d6..7c7dc8692cb5b 100644 --- a/code/modules/power/power.dm +++ b/code/modules/power/power.dm @@ -377,8 +377,9 @@ //source is an object caused electrocuting (airlock, grille, etc) //siemens_coeff - layman's terms, conductivity //dist_check - set to only shock mobs within 1 of source (vendors, airlocks, etc.) +//drain_energy - whether the shock will drain power from the mech. Enabled by default. //No animations will be performed by this proc. -/proc/electrocute_mob(mob/living/carbon/victim, power_source, obj/source, siemens_coeff = 1, dist_check = FALSE) +/proc/electrocute_mob(mob/living/carbon/victim, power_source, obj/source, siemens_coeff = 1, dist_check = FALSE, drain_energy = TRUE) if(!istype(victim) || ismecha(victim.loc)) return FALSE //feckin mechs are dumb diff --git a/icons/mecha/inteq_gygax.dmi b/icons/mecha/inteq_gygax.dmi new file mode 100644 index 0000000000000..08105d783ab99 Binary files /dev/null and b/icons/mecha/inteq_gygax.dmi differ diff --git a/icons/mecha/mecha.dmi b/icons/mecha/mecha.dmi index 9b9c2b479c7b0..2993487cb8505 100644 Binary files a/icons/mecha/mecha.dmi and b/icons/mecha/mecha.dmi differ