diff --git a/code/__DEFINES/components.dm b/code/__DEFINES/components.dm index a836c90386d..8e8846da0e2 100644 --- a/code/__DEFINES/components.dm +++ b/code/__DEFINES/components.dm @@ -253,6 +253,10 @@ // /obj/item/projectile signals (sent to the firer) #define COMSIG_PROJECTILE_ON_HIT "projectile_on_hit" // from base of /obj/item/projectile/proc/on_hit(): (atom/movable/firer, atom/target, Angle) #define COMSIG_PROJECTILE_BEFORE_FIRE "projectile_before_fire" // from base of /obj/item/projectile/proc/fire(): (obj/item/projectile, atom/original_target) +#define COMSIG_PROJECTILE_PREHIT "com_proj_prehit" // sent to targets during the process_hit proc of projectiles + +// /obj/mecha signals +#define COMSIG_MECHA_ACTION_ACTIVATE "mecha_action_activate" //sent from mecha action buttons to the mecha they're linked to // /mob/living/carbon/human signals #define COMSIG_HUMAN_MELEE_UNARMED_ATTACK "human_melee_unarmed_attack" //from mob/living/carbon/human/UnarmedAttack(): (atom/target) diff --git a/code/game/mecha/combat/durand.dm b/code/game/mecha/combat/durand.dm index 0ff90cdba32..5fe76b9eeeb 100644 --- a/code/game/mecha/combat/durand.dm +++ b/code/game/mecha/combat/durand.dm @@ -11,6 +11,20 @@ infra_luminosity = 8 force = 40 wreckage = /obj/structure/mecha_wreckage/durand + var/obj/durand_shield/shield + +/obj/mecha/combat/durand/Initialize() + shield = new/obj/durand_shield + shield.chassis = src + shield.layer = layer + RegisterSignal(src, COMSIG_MECHA_ACTION_ACTIVATE, .proc/relay) + RegisterSignal(src, COMSIG_PROJECTILE_PREHIT, .proc/prehit) + . = ..() + +/obj/mecha/combat/durand/Destroy() + if(shield) + qdel(shield) + . = ..() /obj/mecha/combat/durand/GrantActions(mob/living/user, human_occupant = 0) ..() @@ -20,3 +34,176 @@ ..() defense_action.Remove(user) +/obj/mecha/combat/durand/process() + . = ..() + if(defense_mode && !use_power(100)) + defense_action.Activate(forced_state = TRUE) + +/obj/mecha/combat/durand/domove(direction) + . = ..() + if(shield) + shield.forceMove(loc) + shield.dir = dir + +/obj/mecha/combat/durand/forceMove(var/turf/T) + . = ..() + shield.forceMove(T) + +/obj/mecha/combat/durand/go_out(forced, atom/newloc = loc) + if(defense_mode) + defense_action.Activate(forced_state = TRUE) + . = ..() + +///Relays the signal from the action button to the shield, and creates a new shield if the old one is MIA. +/obj/mecha/combat/durand/proc/relay(datum/source, list/signal_args) + if(!shield) //if the shield somehow got deleted + shield = new/obj/durand_shield + shield.chassis = src + shield.layer = layer + shield.forceMove(loc) + shield.dir = 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/item/projectile/source, list/signal_args) + if(defense_check(source.loc) && shield) + signal_args[2] = shield + + +/**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(var/turf/aloc) + if (!defense_mode || !shield || shield.switching) + return FALSE + . = FALSE + switch(dir) + if (1) + if(abs(x - aloc.x) <= (y - aloc.y) * -2) + . = TRUE + if (2) + if(abs(x - aloc.x) <= (y - aloc.y) * 2) + . = TRUE + if (4) + if(abs(y - aloc.y) <= (x - aloc.x) * -2) + . = TRUE + if (8) + if(abs(y - aloc.y) <= (x - aloc.x) * 2) + . = TRUE + 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)) + 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/blob_act(obj/structure/blob/B) + if(defense_check(B.loc)) + log_message("Attack by blob. Attacker - [B].", LOG_MECHA, color="red") + log_message("Attack absorbed by defense field.", LOG_MECHA, color="orange") + shield.blob_act(B) + else + . = ..() + +/obj/mecha/combat/durand/attackby(obj/item/W as obj, mob/user as mob, params) + if(defense_check(user.loc)) + 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)) + log_message("Impact with [AM] absorbed by defense field.", LOG_MECHA, color="orange") + shield.hitby(AM, skipcatch, hitpush, blocked, throwingdatum) + else + . = ..() + +//////////////////////////// +///// Shield processing //// +//////////////////////////// + +/**An object to take the hit for us when using the Durand's defense mode. +It is spawned in during the durand's initilization, and always stays on the same tile. +Normally invisible, until defense mode is actvated. When the durand detects an attack that should be blocked, the +attack is passed to the shield. The shield takes the damage, uses it to calculate charge cost, and then sets its +own integrity back to max. Shield is automatically dropped if we run out of power or the user gets out.*/ + +/obj/durand_shield //projectiles get passed to this when defense mode is enabled + name = "defense grid" + icon = 'icons/mecha/durand_shield.dmi' + icon_state = "shield_null" + invisibility = INVISIBILITY_MAXIMUM //no showing on right-click + pixel_y = 4 + max_integrity = 10000 + obj_integrity = 10000 + var/obj/mecha/combat/durand/chassis ///Our link back to the durand + var/switching = FALSE ///To keep track of things during the animation + +/obj/durand_shield/Initialize() + . = ..() + RegisterSignal(src, COMSIG_MECHA_ACTION_ACTIVATE, .proc/activate) + +/obj/durand_shield/Destroy() + if(chassis) + chassis.shield = null + . = ..() + +/**Handles activating and deactivating the shield. This proc is called by a signal sent from the mech's action button +and relayed by the mech itself. The "forced" variabe, signal_args[1], will skip the to-pilot text and is meant for when +the shield is disabled by means other than the action button (like running out of power)*/ + +/obj/durand_shield/proc/activate(datum/source, var/datum/action/innate/mecha/mech_defense_mode/button, list/signal_args) + if(!chassis || !chassis.occupant) + return + if(switching && !signal_args[1]) + return + if(!chassis.defense_mode && (!chassis.cell || chassis.cell.charge < 100)) //If it's off, and we have less than 100 units of power + chassis.occupant_message("Insufficient power; cannot activate defense mode.") + return + switching = TRUE + chassis.defense_mode = !chassis.defense_mode + chassis.defense_action.button_icon_state = "mech_defense_mode_[chassis.defense_mode ? "on" : "off"]" //This is backwards because we haven't changed the var yet + if(!signal_args[1]) + chassis.occupant_message("Defense mode [chassis.defense_mode?"enabled":"disabled"].") + chassis.log_message("User has toggled defense mode -- now [chassis.defense_mode?"enabled":"disabled"].", LOG_MECHA) + else + chassis.log_message("defense mode state changed -- now [chassis.defense_mode?"enabled":"disabled"].", LOG_MECHA) + chassis.defense_action.UpdateButtonIcon() + + if(chassis.defense_mode) + invisibility = 0 + flick("shield_raise", src) + playsound(src, 'sound/mecha/mech_shield_raise.ogg', 50, FALSE) + set_light(l_range = MINIMUM_USEFUL_LIGHT_RANGE , l_power = 5, l_color = "#00FFFF") + sleep(3) + icon_state = "shield" + else + flick("shield_drop", src) + playsound(src, 'sound/mecha/mech_shield_drop.ogg', 50, FALSE) + sleep(5) + set_light(0) + icon_state = "shield_null" + invisibility = INVISIBILITY_MAXIMUM //no showing on right-click + switching = FALSE + +/obj/durand_shield/take_damage() + if(!chassis) + qdel(src) + return + if(!chassis.defense_mode) //if defense mode is disabled, we're taking damage that we shouldn't be taking + return + . = ..() + flick("shield_impact", src) + if(!chassis.use_power((max_integrity - obj_integrity) * 100)) + chassis.cell?.charge = 0 + chassis.defense_action.Activate(forced_state = TRUE) + obj_integrity = 10000 + +/obj/durand_shield/play_attack_sound() + playsound(src, 'sound/mecha/mech_shield_deflect.ogg', 100, TRUE) + +/obj/durand_shield/bullet_act() + play_attack_sound() + . = ..() \ No newline at end of file diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm index 3f38dd948d1..f4ff16dd0d1 100644 --- a/code/game/mecha/mecha.dm +++ b/code/game/mecha/mecha.dm @@ -98,7 +98,7 @@ var/datum/action/innate/mecha/mech_toggle_lights/lights_action = new var/datum/action/innate/mecha/mech_view_stats/stats_action = new var/datum/action/innate/mecha/mech_toggle_thrusters/thrusters_action = new - var/datum/action/innate/mecha/mech_defence_mode/defense_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/effect_system/smoke_spread/smoke_system = new //not an action, but trigged by one var/datum/action/innate/mecha/mech_smoke/smoke_action = new @@ -109,8 +109,7 @@ //Action vars var/thrusters_active = FALSE - var/defence_mode = FALSE - var/defence_mode_deflect_chance = 35 + var/defense_mode = FALSE var/leg_overload_mode = FALSE var/leg_overload_coeff = 100 var/zoom_mode = FALSE @@ -577,11 +576,6 @@ return 0 if(!has_charge(step_energy_drain)) return 0 - if(defence_mode) - if(world.time - last_message > 20) - occupant_message("Unable to move while in defence mode") - last_message = world.time - return 0 if(zoom_mode) if(world.time - last_message > 20) occupant_message("Unable to move while in zoom mode.") @@ -1100,8 +1094,7 @@ GLOBAL_VAR_INIT(year_integer, text2num(year)) // = 2013??? return max(0, cell.charge) /obj/mecha/proc/use_power(amount) - if(get_charge()) - cell.use(amount) + if(get_charge() && cell.use(amount)) return 1 return 0 diff --git a/code/game/mecha/mecha_actions.dm b/code/game/mecha/mecha_actions.dm index 30d5b8266fa..ffd53b7ea19 100644 --- a/code/game/mecha/mecha_actions.dm +++ b/code/game/mecha/mecha_actions.dm @@ -164,27 +164,13 @@ chassis.log_message("Toggled thrusters.", LOG_MECHA) chassis.occupant_message("Thrusters [chassis.thrusters_active ?"en":"dis"]abled.") - -/datum/action/innate/mecha/mech_defence_mode - name = "Toggle Defence Mode" +/datum/action/innate/mecha/mech_defense_mode + name = "Toggle an energy shield that blocks all attacks from the faced direction at a heavy power cost." button_icon_state = "mech_defense_mode_off" + var/image/def_overlay -/datum/action/innate/mecha/mech_defence_mode/Activate(forced_state = null) - if(!owner || !chassis || chassis.occupant != owner) - return - if(!isnull(forced_state)) - chassis.defence_mode = forced_state - else - chassis.defence_mode = !chassis.defence_mode - button_icon_state = "mech_defense_mode_[chassis.defence_mode ? "on" : "off"]" - if(chassis.defence_mode) - chassis.deflect_chance = chassis.defence_mode_deflect_chance - chassis.occupant_message("You enable [chassis] defence mode.") - else - chassis.deflect_chance = initial(chassis.deflect_chance) - chassis.occupant_message("You disable [chassis] defence mode.") - chassis.log_message("Toggled defence mode.", LOG_MECHA) - UpdateButtonIcon() +/datum/action/innate/mecha/mech_defense_mode/Activate(forced_state = FALSE) + SEND_SIGNAL(chassis, COMSIG_MECHA_ACTION_ACTIVATE, args) ///Signal sent to the mech, to be handed to the shield. See durand.dm for more details /datum/action/innate/mecha/mech_overload_mode name = "Toggle leg actuators overload" diff --git a/code/game/mecha/mecha_defense.dm b/code/game/mecha/mecha_defense.dm index 98115e8fa5c..92a27489358 100644 --- a/code/game/mecha/mecha_defense.dm +++ b/code/game/mecha/mecha_defense.dm @@ -53,7 +53,6 @@ if(.) . *= booster_damage_modifier - /obj/mecha/attack_hand(mob/living/user) . = ..() if(.) @@ -67,7 +66,6 @@ /obj/mecha/attack_paw(mob/user as mob) return attack_hand(user) - /obj/mecha/attack_alien(mob/living/user) log_message("Attack by alien. Attacker - [user].", LOG_MECHA, color="red") playsound(src.loc, 'sound/weapons/slash.ogg', 100, 1) @@ -87,8 +85,8 @@ if(user.obj_damage) animal_damage = user.obj_damage animal_damage = min(animal_damage, 20*user.environment_smash) - attack_generic(user, animal_damage, user.melee_damage_type, "melee", play_soundeffect) log_combat(user, src, "attacked") + attack_generic(user, animal_damage, user.melee_damage_type, "melee", play_soundeffect) return 1 @@ -102,6 +100,7 @@ log_combat(user, src, "punched", "hulk powers") /obj/mecha/blob_act(obj/structure/blob/B) + log_message("Attack by blob. Attacker - [B].", LOG_MECHA, color="red") take_damage(30, BRUTE, "melee", 0, get_dir(src, B)) /obj/mecha/attack_tk() @@ -111,7 +110,6 @@ log_message("Hit by [AM].", LOG_MECHA, color="red") . = ..() - /obj/mecha/bullet_act(obj/item/projectile/Proj) //wrapper if (!enclosed && occupant && !silicon_pilot && !Proj.force_hit && (Proj.def_zone == BODY_ZONE_HEAD || Proj.def_zone == BODY_ZONE_CHEST)) //allows bullets to hit the pilot of open-canopy mechs occupant.bullet_act(Proj) //If the sides are open, the occupant can be hit diff --git a/code/game/mecha/mecha_topic.dm b/code/game/mecha/mecha_topic.dm index 62b4674e991..62e414cef64 100644 --- a/code/game/mecha/mecha_topic.dm +++ b/code/game/mecha/mecha_topic.dm @@ -91,7 +91,7 @@ Cabin temperature: [internal_tank?"[return_temperature()]°K|[return_temperature() - T0C]°C":"N/A"]
[dna_lock?"DNA-locked:
[dna_lock] \[Reset\]
":""]
[thrusters_action.owner ? "Thrusters: [thrusters_active ? "Enabled" : "Disabled"]
" : ""] - [defense_action.owner ? "Defence Mode: [defence_mode ? "Enabled" : "Disabled"]
" : ""] + [defense_action.owner ? "Defense Mode: [defense_mode ? "Enabled" : "Disabled"]
" : ""] [overload_action.owner ? "Leg Actuators Overload: [leg_overload_mode ? "Enabled" : "Disabled"]
" : ""] [smoke_action.owner ? "Smoke: [smoke]
" : ""] [zoom_action.owner ? "Zoom: [zoom_mode ? "Enabled" : "Disabled"]
" : ""] diff --git a/code/modules/mob/living/simple_animal/hostile/mecha_pilot.dm b/code/modules/mob/living/simple_animal/hostile/mecha_pilot.dm index 2e783d84d84..b42627b2bbd 100644 --- a/code/modules/mob/living/simple_animal/hostile/mecha_pilot.dm +++ b/code/modules/mob/living/simple_animal/hostile/mecha_pilot.dm @@ -32,7 +32,7 @@ //Vars that control when the pilot uses their mecha's abilities (if the mecha has that ability) var/threat_use_mecha_smoke = 5 //5 mobs is enough to engage crowd control - var/defence_mode_chance = 35 //Chance to engage Defence mode when damaged + var/defense_mode_chance = 35 //Chance to engage Defense mode when damaged var/smoke_chance = 20 //Chance to deploy smoke for crowd control var/retreat_chance = 40 //Chance to run away @@ -226,19 +226,19 @@ if(mecha.smoke_action && mecha.smoke_action.owner && mecha.smoke) mecha.smoke_action.Activate() - //Heavy damage - Defence Power or Retreat + //Heavy damage - Defense Power or Retreat if(mecha.obj_integrity < mecha.max_integrity*0.25) - if(prob(defence_mode_chance)) - if(mecha.defense_action && mecha.defense_action.owner && !mecha.defence_mode) + if(prob(defense_mode_chance)) + if(mecha.defense_action && mecha.defense_action.owner && !mecha.defense_mode) mecha.leg_overload_mode = 0 mecha.defense_action.Activate(TRUE) - addtimer(CALLBACK(mecha.defense_action, /datum/action/innate/mecha/mech_defence_mode.proc/Activate, FALSE), 100) //10 seconds of defence, then toggle off + addtimer(CALLBACK(mecha.defense_action, /datum/action/innate/mecha/mech_defense_mode.proc/Activate, FALSE), 100) //10 seconds of defense, then toggle off else if(prob(retreat_chance)) //Speed boost if possible if(mecha.overload_action && mecha.overload_action.owner && !mecha.leg_overload_mode) mecha.overload_action.Activate(TRUE) - addtimer(CALLBACK(mecha.overload_action, /datum/action/innate/mecha/mech_defence_mode.proc/Activate, FALSE), 100) //10 seconds of speeeeed, then toggle off + addtimer(CALLBACK(mecha.overload_action, /datum/action/innate/mecha/mech_defense_mode.proc/Activate, FALSE), 100) //10 seconds of speeeeed, then toggle off retreat_distance = 50 spawn(100) diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index befffbe2bcd..b835ec9c178 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -257,6 +257,7 @@ permutated |= target //Make sure we're never hitting it again. If we ever run into weirdness with piercing projectiles needing to hit something multiple times.. well.. that's a to-do. if(!prehit(target)) return process_hit(T, select_target(T), qdel_self, hit_something) //Hit whatever else we can since that didn't work. + SEND_SIGNAL(target, COMSIG_PROJECTILE_PREHIT, args) var/result = target.bullet_act(src, def_zone) if(result == BULLET_ACT_FORCE_PIERCE) if(!CHECK_BITFIELD(movement_type, UNSTOPPABLE)) diff --git a/icons/mecha/durand_shield.dmi b/icons/mecha/durand_shield.dmi new file mode 100644 index 00000000000..0600e352b31 Binary files /dev/null and b/icons/mecha/durand_shield.dmi differ diff --git a/sound/mecha/mech_shield_deflect.ogg b/sound/mecha/mech_shield_deflect.ogg new file mode 100644 index 00000000000..5c1970d8724 Binary files /dev/null and b/sound/mecha/mech_shield_deflect.ogg differ diff --git a/sound/mecha/mech_shield_drop.ogg b/sound/mecha/mech_shield_drop.ogg new file mode 100644 index 00000000000..21c6cb5edb9 Binary files /dev/null and b/sound/mecha/mech_shield_drop.ogg differ diff --git a/sound/mecha/mech_shield_raise.ogg b/sound/mecha/mech_shield_raise.ogg new file mode 100644 index 00000000000..65ad70ad14d Binary files /dev/null and b/sound/mecha/mech_shield_raise.ogg differ