diff --git a/code/game/turfs/simulated/water.dm b/code/game/turfs/simulated/water.dm index e4b762d733..cf06303740 100644 --- a/code/game/turfs/simulated/water.dm +++ b/code/game/turfs/simulated/water.dm @@ -112,6 +112,8 @@ adjust_fire_stacks(-amount * 5) for(var/atom/movable/AM in contents) AM.water_act(amount) + remove_modifiers_of_type(/datum/modifier/fire) + inflict_water_damage(20 * amount) // Only things vulnerable to water will actually be harmed (slimes/prommies). var/list/shoreline_icon_cache = list() diff --git a/code/modules/admin/verbs/buildmode.dm b/code/modules/admin/verbs/buildmode.dm index a57b17d15f..54a1cbacf9 100644 --- a/code/modules/admin/verbs/buildmode.dm +++ b/code/modules/admin/verbs/buildmode.dm @@ -127,6 +127,18 @@ usr << "Right Mouse Button on turf/obj/mob = Reset glowing" usr << "Right Mouse Button on buildmode button = Change glow properties" usr << "***********************************************************" + if(9) // Control mobs with ai_holders. + usr << "***********************************************************" + usr << "Left Mouse Button on AI mob = Select/Deselect mob" + usr << "Left Mouse Button + alt on AI mob = Toggle hostility on mob" + usr << "Left Mouse Button + ctrl on AI mob = Reset target/following/movement" + usr << "Right Mouse Button on enemy mob = Command selected mobs to attack mob" + usr << "Right Mouse Button on allied mob = Command selected mobs to follow mob" + usr << "Right Mouse Button + shift on any mob = Command selected mobs to follow mob regardless of faction" + usr << "Right Mouse Button on tile = Command selected mobs to move to tile (will cancel if enemies are seen)" + usr << "Right Mouse Button + shift on tile = Command selected mobs to reposition to tile (will not be inturrupted by enemies)" + usr << "Right Mouse Button + alt on obj/turfs = Command selected mobs to attack obj/turf" + usr << "***********************************************************" return 1 /obj/effect/bmode/buildquit @@ -146,6 +158,7 @@ var/obj/effect/bmode/buildmode/buildmode = null var/obj/effect/bmode/buildquit/buildquit = null var/atom/movable/throw_atom = null + var/list/selected_mobs = list() /obj/effect/bmode/buildholder/Destroy() qdel(builddir) @@ -158,6 +171,7 @@ buildquit = null throw_atom = null cl = null + selected_mobs.Cut() return ..() /obj/effect/bmode/buildmode @@ -210,6 +224,9 @@ master.cl.buildmode = 8 src.icon_state = "buildmode8" if(8) + master.cl.buildmode = 9 + src.icon_state = "buildmode9" + if(9) master.cl.buildmode = 1 src.icon_state = "buildmode1" @@ -416,6 +433,74 @@ if(pa.Find("right")) if(object) object.set_light(0, 0, "#FFFFFF") + if(9) // AI control + if(pa.Find("left")) + if(isliving(object)) + var/mob/living/L = object + // Reset processes. + if(pa.Find("ctrl")) + if(!isnull(L.get_AI_stance())) // Null means there's no AI datum or it has one but is player controlled w/o autopilot on. + var/datum/ai_holder/AI = L.ai_holder + AI.forget_everything() + to_chat(user, span("notice", "\The [L]'s AI has forgotten its target/movement destination/leader.")) + else + to_chat(user, span("warning", "\The [L] is not AI controlled.")) + return + + // Toggle hostility + if(pa.Find("alt")) + if(!isnull(L.get_AI_stance())) + var/datum/ai_holder/AI = L.ai_holder + AI.hostile = !AI.hostile + to_chat(user, span("notice", "\The [L] is now [AI.hostile ? "hostile" : "passive"].")) + else + to_chat(user, span("warning", "\The [L] is not AI controlled.")) + return + + // Select/Deselect + if(!isnull(L.get_AI_stance())) + if(L in holder.selected_mobs) + // Todo: Select graphic only the admin can see? + holder.selected_mobs -= L + to_chat(user, span("notice", "Deselected \the [L].")) + else + holder.selected_mobs += L + to_chat(user, span("notice", "Selected \the [L].")) + else + to_chat(user, span("warning", "\The [L] is not AI controlled.")) + + if(pa.Find("right")) + if(istype(object, /atom/movable)) // Force attack. + var/atom/movable/AM = object + + if(pa.Find("alt")) + for(var/thing in holder.selected_mobs) + var/mob/living/unit = thing + var/datum/ai_holder/AI = unit.ai_holder + AI.give_target(AM) + to_chat(user, span("notice", "Commanded [holder.selected_mobs.len] mob\s to attack \the [AM].")) + + if(isliving(object)) // Follow or attack. + var/mob/living/L = object + + for(var/thing in holder.selected_mobs) + var/mob/living/unit = thing + var/datum/ai_holder/AI = unit.ai_holder + if(L.IIsAlly(unit) || !AI.hostile || pa.Find("shift")) + AI.set_follow(L) + else + AI.give_target(L) + to_chat(user, span("notice", "Commanded [holder.selected_mobs.len] mob\s to attack or follow \the [L].")) + + if(isturf(object)) // Move or reposition. + var/turf/T = object + + for(var/thing in holder.selected_mobs) + var/mob/living/unit = thing + var/datum/ai_holder/AI = unit.ai_holder + AI.give_destination(T, pa.Find("shift")) + to_chat(user, span("notice", "Commanded [holder.selected_mobs.len] mob\s to move to \the [T].")) + /obj/effect/bmode/buildmode/proc/get_path_from_partial_text(default_path) var/desired_path = input("Enter full or partial typepath.","Typepath","[default_path]") diff --git a/code/modules/ai/aI_holder_subtypes/simple_mob_ai.dm b/code/modules/ai/aI_holder_subtypes/simple_mob_ai.dm index c32140edcc..39e291d192 100644 --- a/code/modules/ai/aI_holder_subtypes/simple_mob_ai.dm +++ b/code/modules/ai/aI_holder_subtypes/simple_mob_ai.dm @@ -194,4 +194,9 @@ move_to = get_step_away(move_to, L, 7) if(move_to) give_destination(move_to, min_distance = 2, combat = TRUE) // This will switch our stance. -*/ \ No newline at end of file +*/ + +/datum/ai_holder/simple_mob/hivebot + conserve_ammo = TRUE + firing_lanes = TRUE + can_flee = FALSE // Fearless dumb machines. diff --git a/code/modules/ai/ai_holder.dm b/code/modules/ai/ai_holder.dm index f41627fd4f..a241fe0239 100644 --- a/code/modules/ai/ai_holder.dm +++ b/code/modules/ai/ai_holder.dm @@ -327,4 +327,9 @@ return null if(client && !ai_holder.autopilot) return null - return ai_holder.stance \ No newline at end of file + return ai_holder.stance + +// 'Taunts' the AI into attacking the taunter. +/mob/living/proc/taunt(atom/movable/taunter, force_target_switch = FALSE) + if(ai_holder) + ai_holder.receive_taunt(taunter, force_target_switch) \ No newline at end of file diff --git a/code/modules/ai/ai_holder_combat.dm b/code/modules/ai/ai_holder_combat.dm index 0a2aea14ab..8f96612282 100644 --- a/code/modules/ai/ai_holder_combat.dm +++ b/code/modules/ai/ai_holder_combat.dm @@ -20,7 +20,8 @@ ai_log("engage_target() : Entering.", AI_LOG_DEBUG) // Can we still see them? - if(!target || !can_attack(target) || (!(target in list_targets())) ) +// if(!target || !can_attack(target) || (!(target in list_targets())) ) + if(!target || !can_attack(target)) ai_log("engage_target() : Lost sight of target.", AI_LOG_TRACE) lose_target() // We lost them. diff --git a/code/modules/ai/ai_holder_targeting.dm b/code/modules/ai/ai_holder_targeting.dm index a621af2051..0fe8702300 100644 --- a/code/modules/ai/ai_holder_targeting.dm +++ b/code/modules/ai/ai_holder_targeting.dm @@ -116,7 +116,8 @@ return FALSE // Turrets won't get hurt if they're still in their cover. return TRUE - return FALSE + return TRUE +// return FALSE // Override this for special targeting criteria. // If it returns true, the mob will always select it as the target. @@ -210,9 +211,11 @@ // Causes targeting to prefer targeting the taunter if possible. // This generally occurs if more than one option is within striking distance, including the taunter. // Otherwise the default filter will prefer the closest target. -/datum/ai_holder/proc/receive_taunt(atom/movable/taunter) +/datum/ai_holder/proc/receive_taunt(atom/movable/taunter, force_target_switch = FALSE) ai_log("receive_taunt() : Was taunted by [taunter].", AI_LOG_INFO) preferred_target = taunter + if(force_target_switch) + give_target(taunter) /datum/ai_holder/proc/lose_taunt() ai_log("lose_taunt() : Resetting preferred_target.", AI_LOG_INFO) diff --git a/code/modules/ai/interfaces.dm b/code/modules/ai/interfaces.dm index a864471a43..974ce1b64f 100644 --- a/code/modules/ai/interfaces.dm +++ b/code/modules/ai/interfaces.dm @@ -37,7 +37,7 @@ return FALSE /mob/living/simple_mob/ICheckSpecialAttack(atom/A) - return can_special_attack(A) + return can_special_attack(A) && should_special_attack(A) // Just because we can doesn't mean we should. /mob/living/proc/ISay(message) diff --git a/code/modules/ai/say_list.dm b/code/modules/ai/say_list.dm index 37f14823e5..05c38f978f 100644 --- a/code/modules/ai/say_list.dm +++ b/code/modules/ai/say_list.dm @@ -80,4 +80,17 @@ emote_see = list("clacks") /datum/say_list/spider - emote_hear = list("chitters") \ No newline at end of file + emote_hear = list("chitters") + +/datum/say_list/hivebot + speak = list( + "Resuming task: Protect area.", + "No threats found.", + "Error: No targets found." + ) + emote_hear = list("hums ominously", "whirrs softly", "grinds a gear") + emote_see = list("looks around the area", "turns from side to side") + say_understood = list("Affirmative.", "Positive.") + say_cannot = list("Denied.", "Negative.") + say_maybe_target = list("Possible threat detected. Investigating.", "Motion detected.", "Investigating.") + say_got_target = list("Threat detected.", "New task: Remove threat.", "Threat removal engaged.", "Engaging target.") \ No newline at end of file diff --git a/code/modules/mob/_modifiers/auras.dm b/code/modules/mob/_modifiers/auras.dm new file mode 100644 index 0000000000..37dc6561cc --- /dev/null +++ b/code/modules/mob/_modifiers/auras.dm @@ -0,0 +1,18 @@ +/* +'Aura' modifiers are semi-permanent, in that they do not have a set duration, but will expire if out of range of the 'source' of the aura. +Note: The source is defined as an argument in New(), and if not specified, it is assumed the holder is the source, +making it not expire ever, which is likely not what you want. +*/ + +/datum/modifier/aura + var/aura_max_distance = 5 // If more than this many tiles away from the source, the modifier expires next tick. + +/datum/modifier/aura/check_if_valid() + if(!origin) + expire() + var/atom/A = origin.resolve() + if(istype(A)) // Make sure we're not null. + if(get_dist(holder, A) > aura_max_distance) + expire() + expire() // Source got deleted or something. + diff --git a/code/modules/mob/_modifiers/modifiers_misc.dm b/code/modules/mob/_modifiers/modifiers_misc.dm index ebcffc62c8..5cac1ad5cf 100644 --- a/code/modules/mob/_modifiers/modifiers_misc.dm +++ b/code/modules/mob/_modifiers/modifiers_misc.dm @@ -180,3 +180,22 @@ the artifact triggers the rage. accuracy = -75 // Aiming requires focus. accuracy_dispersion = 3 // Ditto. evasion = -45 // Too angry to dodge. + + + + +// Ignition, but confined to the modifier system. +// This makes it more predictable and thus, easier to balance. +/datum/modifier/fire + name = "on fire" + desc = "You are on fire! You will be harmed until the fire goes out or you extinguish it with water." + mob_overlay_state = "on_fire" + + on_created_text = "You combust into flames!" + on_expired_text = "The fire starts to fade." + stacks = MODIFIER_STACK_ALLOWED // Multiple instances will hurt a lot. + var/damage_per_tick = 5 + +/datum/modifier/fire/tick() + holder.adjustFireLoss(damage_per_tick) + diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index db20f7f744..6227ddd46d 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -135,6 +135,27 @@ emp_act return siemens_coefficient +// Similar to above but is for the mob's overall protection, being the average of all slots. +/mob/living/carbon/human/proc/get_siemens_coefficient_average() + var/siemens_value = 0 + var/total = 0 + for(var/organ_name in organs_by_name) + if(organ_name in organ_rel_size) + var/obj/item/organ/external/organ = organs_by_name[organ_name] + if(organ) + var/weight = organ_rel_size[organ_name] + siemens_value += get_siemens_coefficient_organ(organ) * weight + total += weight + + if(fire_stacks < 0) // Water makes you more conductive. + siemens_value *= 1.5 + + return (siemens_value/max(total, 1)) + +// Returns a number between 0 to 1, with 1 being total protection. +/mob/living/carbon/human/get_shock_protection() + return between(0, 1-get_siemens_coefficient_average(), 1) + // Returns a list of clothing that is currently covering def_zone. /mob/living/carbon/human/proc/get_clothing_list_organ(var/obj/item/organ/external/def_zone, var/type) var/list/results = list() @@ -540,6 +561,20 @@ emp_act return perm +// This is for preventing harm by being covered in water, which only prometheans need to deal with. +// This is not actually used for now since the code for prometheans gets changed a lot. +/mob/living/carbon/human/get_water_protection() + var/protection = ..() // Todo: Replace with species var later. + if(protection == 1) // No point doing permeability checks if it won't matter. + return protection + // Wearing clothing with a low permeability_coefficient can protect from water. + + var/converted_protection = 1 - protection + var/perm = reagent_permeability() + converted_protection *= perm + return 1-converted_protection + + /mob/living/carbon/human/shank_attack(obj/item/W, obj/item/weapon/grab/G, mob/user, hit_zone) if(!..()) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 3892f8310e..a54234a0b1 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -499,6 +499,28 @@ default behaviour is: // ++++ROCKDTBEN++++ MOB PROCS //END +// Applies direct "cold" damage while checking protection against the cold. +/mob/living/proc/inflict_cold_damage(amount) + amount *= 1 - get_cold_protection(50) // Within spacesuit protection. + if(amount > 0) + adjustFireLoss(amount) + +// Ditto, but for "heat". +/mob/living/proc/inflict_heat_damage(amount) + amount *= 1 - get_heat_protection(10000) // Within firesuit protection. + if(amount > 0) + adjustFireLoss(amount) + +// and one for electricity because why not +/mob/living/proc/inflict_shock_damage(amount) + electrocute_act(amount, null, get_shock_protection()) + +// also one for water (most things resist it entirely, except for slimes) +/mob/living/proc/inflict_water_damage(amount) + amount *= 1 - get_water_protection() + if(amount > 0) + adjustToxLoss(amount) + /mob/proc/get_contents() @@ -1015,7 +1037,7 @@ default behaviour is: if(lying != lying_prev) lying_prev = lying update_transform() - + return canmove // Adds overlays for specific modifiers. diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index e0e85edbcd..7f75cdb316 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -426,6 +426,12 @@ /mob/living/proc/get_heat_protection() return 0 +/mob/living/proc/get_shock_protection() + return 0 + +/mob/living/proc/get_water_protection() + return 1 // Water won't hurt most things. + //Finds the effective temperature that the mob is burning at. /mob/living/proc/fire_burn_temperature() if (fire_stacks <= 0) diff --git a/code/modules/mob/living/simple_animal/aliens/hivebot.dm b/code/modules/mob/living/simple_animal/aliens/hivebot.dm index 70e3d20f84..04fee362e9 100644 --- a/code/modules/mob/living/simple_animal/aliens/hivebot.dm +++ b/code/modules/mob/living/simple_animal/aliens/hivebot.dm @@ -237,3 +237,5 @@ /obj/item/projectile/bullet/hivebot damage = 10 damage_type = BRUTE + +#undef LASERS_TO_KILL \ No newline at end of file diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index f2281823a1..27467de997 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -1547,13 +1547,13 @@ return 0 else return armorval - +/* // Force it to target something /mob/living/simple_animal/proc/taunt(var/mob/living/new_target, var/forced = FALSE) if(intelligence_level == SA_HUMANOID && !forced) return set_target(new_target) - +*/ /mob/living/simple_animal/is_sentient() return intelligence_level != SA_PLANT && intelligence_level != SA_ROBOTIC diff --git a/code/modules/mob/living/simple_mob/combat.dm b/code/modules/mob/living/simple_mob/combat.dm index 08ed8a4d83..a0e86e5bdd 100644 --- a/code/modules/mob/living/simple_mob/combat.dm +++ b/code/modules/mob/living/simple_mob/combat.dm @@ -128,12 +128,17 @@ return TRUE +// Should we do one? Used to make the AI not waste their special attacks. Only checked for AI. Players are free to screw up on their own. +/mob/living/simple_mob/proc/should_special_attack(atom/A) + return TRUE // Special attacks, like grenades or blinding spit or whatever. // Don't override this, override do_special_attack() for your blinding spit/etc. /mob/living/simple_mob/proc/special_attack_target(atom/A) last_special_attack = world.time if(do_special_attack(A)) + if(special_attack_charges) + special_attack_charges -= 1 return TRUE return FALSE diff --git a/code/modules/mob/living/simple_mob/defense.dm b/code/modules/mob/living/simple_mob/defense.dm index dac6bfeafc..fd2585c660 100644 --- a/code/modules/mob/living/simple_mob/defense.dm +++ b/code/modules/mob/living/simple_mob/defense.dm @@ -113,6 +113,10 @@ if(bombdam > maxHealth) gib() +// Cold stuff. +/mob/living/simple_mob/get_cold_protection() + return cold_resist + // Fire stuff. Not really exciting at the moment. /mob/living/simple_mob/handle_fire() @@ -124,6 +128,8 @@ /mob/living/simple_mob/ExtinguishMob() return +/mob/living/simple_mob/get_heat_protection() + return heat_resist // Electricity /mob/living/simple_mob/electrocute_act(var/shock_damage, var/obj/source, var/siemens_coeff = 1.0, var/def_zone = null) @@ -138,6 +144,8 @@ s.set_up(5, 1, loc) s.start() +/mob/living/simple_mob/get_shock_protection() + return shock_resist // Shot with taser/stunvolver /mob/living/simple_mob/stun_effect_act(var/stun_amount, var/agony_amount, var/def_zone, var/used_weapon=null) @@ -174,6 +182,9 @@ adjustFireLoss(min(7, getMaxHealth()*0.0625)) // adjustFireLoss(rand(1, 6)) +// Water +/mob/living/simple_mob/get_water_protection() + return water_resist // Armor /mob/living/simple_mob/getarmor(def_zone, attack_flag) diff --git a/code/modules/mob/living/simple_mob/simple_mob.dm b/code/modules/mob/living/simple_mob/simple_mob.dm index 499efd8314..f874a9a43f 100644 --- a/code/modules/mob/living/simple_mob/simple_mob.dm +++ b/code/modules/mob/living/simple_mob/simple_mob.dm @@ -122,6 +122,12 @@ "bio" = 0, "rad" = 0 ) + // Protection against heat/cold/electric/water effects. + // 0 is no protection, 1 is total protection. Negative numbers increase vulnerability. + var/heat_resist = 0.0 + var/cold_resist = 0.0 + var/shock_resist = 0.0 + var/water_resist = 1.0 var/purge = 0 // Cult stuff. var/supernatural = FALSE // Ditto. @@ -165,9 +171,10 @@ ..(act, type, desc) -/mob/living/simple_mob/SelfMove() +/mob/living/simple_mob/SelfMove(turf/n, direct) + var/turf/old_turf = get_turf(src) . = ..() - if(movement_sound) + if(movement_sound && old_turf != get_turf(src)) playsound(src, movement_sound, 50, 1) /mob/living/simple_mob/movement_delay() diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/tunneler.dm b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/tunneler.dm index bf95f40b6c..82a68d4c1f 100644 --- a/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/tunneler.dm +++ b/code/modules/mob/living/simple_mob/subtypes/animal/giant_spider/tunneler.dm @@ -14,7 +14,7 @@ health = 120 melee_damage_lower = 10 - melee_damage_upper = 20 + melee_damage_upper = 10 poison_chance = 15 poison_per_bite = 3 @@ -22,12 +22,12 @@ // ai_holder_type = /datum/ai_holder/simple_mob/melee/tunneler - player_msg = "You can perform a tunneling attack by clicking on someone from a distance while on natural terrain.
\ + player_msg = "You can perform a tunneling attack by clicking on someone from a distance.
\ There is a noticable travel delay as you tunnel towards the tile the target was at when you started the tunneling attack.
\ Any entities inbetween you and the targeted tile will be stunned for a brief period of time.
\ Whatever is on the targeted tile when you arrive will suffer a potent stun.
\ If nothing is on the targeted tile, you will overshoot and keep going for a few more tiles.
\ - If you hit a wall or other solid structure during that time, you will suffer a lengthy stun." + If you hit a wall or other solid structure during that time, you will suffer a lengthy stun and be vulnerable to more harm." // Tunneling is a special attack, similar to the hunter's Leap. special_attack_min_range = 2 @@ -43,6 +43,21 @@ /mob/living/simple_mob/animal/giant_spider/tunneler/fast tunnel_tile_speed = 1 +/mob/living/simple_mob/animal/giant_spider/tunneler/should_special_attack(atom/A) + // Make sure its possible for the spider to reach the target so it doesn't try to go through a window. + var/turf/destination = get_turf(A) + var/turf/starting_turf = get_turf(src) + var/turf/T = starting_turf + for(var/i = 1 to get_dist(starting_turf, destination)) + if(T == destination) + break + + T = get_step(T, get_dir(T, destination)) + if(T.check_density(ignore_mobs = TRUE)) + return FALSE + return T == destination + + /mob/living/simple_mob/animal/giant_spider/tunneler/do_special_attack(atom/A) set waitfor = FALSE set_AI_busy(TRUE) @@ -57,14 +72,17 @@ // Do the dig! visible_message(span("danger","\The [src] tunnels towards \the [A]!")) + submerge() if(handle_tunnel(destination) == FALSE) set_AI_busy(FALSE) + emerge() return FALSE // Did we make it? if(!(src in destination)) set_AI_busy(FALSE) + emerge() return FALSE var/overshoot = TRUE @@ -75,11 +93,13 @@ continue visible_message(span("danger","\The [src] erupts from underneath, and hits \the [L]!")) + playsound(L, 'sound/weapons/heavysmash.ogg', 75, 1) L.Weaken(3) overshoot = FALSE if(!overshoot) // We hit the target, or something, at destination, so we're done. set_AI_busy(FALSE) + emerge() return TRUE // Otherwise we need to keep going. @@ -91,9 +111,11 @@ if(handle_tunnel(destination) == FALSE) set_AI_busy(FALSE) + emerge() return FALSE set_AI_busy(FALSE) + emerge() return FALSE @@ -115,94 +137,49 @@ to_chat(src, span("critical", "You hit something really solid!")) playsound(src, "punch", 75, 1) Weaken(5) + add_modifier(/datum/modifier/tunneler_vulnerable, 10 SECONDS) return FALSE // Hit a wall. // Stun anyone in our way. for(var/mob/living/L in T) + playsound(L, 'sound/weapons/heavysmash.ogg', 75, 1) L.Weaken(2) // Get into the tile. forceMove(T) // Visuals and sound. - new /obj/item/weapon/ore/glass(T) + dig_under_floor(get_turf(src)) playsound(src, 'sound/effects/break_stone.ogg', 75, 1) sleep(tunnel_tile_speed) +// For visuals. +/mob/living/simple_mob/animal/giant_spider/tunneler/proc/submerge() + alpha = 0 + dig_under_floor(get_turf(src)) + new /obj/effect/temporary_effect/tunneler_hole(get_turf(src)) +// Ditto. +/mob/living/simple_mob/animal/giant_spider/tunneler/proc/emerge() + alpha = 255 + dig_under_floor(get_turf(src)) + new /obj/effect/temporary_effect/tunneler_hole(get_turf(src)) -/* -// The actual leaping attack. -/mob/living/simple_mob/animal/giant_spider/hunter/do_special_attack(atom/A) - set waitfor = FALSE - set_AI_busy(TRUE) +/mob/living/simple_mob/animal/giant_spider/tunneler/proc/dig_under_floor(turf/T) + new /obj/item/weapon/ore/glass(T) // This will be rather weird when on station but the alternative is too much work. - // Telegraph, since getting stunned suddenly feels bad. - do_windup_animation(A, leap_warmup) - sleep(leap_warmup) // For the telegraphing. +/obj/effect/temporary_effect/tunneler_hole + name = "hole" + desc = "A collapsing tunnel hole." + icon_state = "tunnel_hole" + time_to_die = 1 MINUTE - // Do the actual leap. - status_flags |= LEAPING // Lets us pass over everything. - visible_message(span("danger","\The [src] leaps at \the [A]!")) - throw_at(get_step(get_turf(A), get_turf(src)), special_attack_max_range+1, 1, src) - playsound(src, 'sound/weapons/spiderlunge.ogg', 75, 1) +/datum/modifier/tunneler_vulnerable + name = "Vulnerable" + desc = "You are vulnerable to more harm than usual." + on_created_text = "You feel vulnerable..." + on_expired_text = "You feel better." + stacks = MODIFIER_STACK_EXTEND - sleep(5) // For the throw to complete. It won't hold up the AI ticker due to waitfor being false. - - if(status_flags & LEAPING) - status_flags &= ~LEAPING // Revert special passage ability. - - var/turf/T = get_turf(src) // Where we landed. This might be different than A's turf. - - . = FALSE - - // Now for the stun. - var/mob/living/victim = null - for(var/mob/living/L in T) // So player-controlled spiders only need to click the tile to stun them. - if(L == src) - continue - - if(ishuman(L)) - var/mob/living/carbon/human/H = L - if(H.check_shields(damage = 0, damage_source = src, attacker = src, def_zone = null, attack_text = "the leap")) - continue // We were blocked. - - victim = L - break - - if(victim) - victim.Weaken(2) - victim.visible_message(span("danger","\The [src] knocks down \the [victim]!")) - to_chat(victim, span("critical", "\The [src] jumps on you!")) - . = TRUE - - set_AI_busy(FALSE) -*/ - -/* -/mob/living/simple_animal/hostile/giant_spider/tunneler - desc = "Sandy and brown, it makes you shudder to look at it. This one has glittering yellow eyes." - icon_state = "tunneler" - icon_living = "tunneler" - icon_dead = "tunneler_dead" - - maxHealth = 120 - health = 120 - move_to_delay = 4 - - melee_damage_lower = 10 - melee_damage_upper = 20 - - poison_chance = 15 - poison_per_bite = 3 - poison_type = "serotrotium_v" - -/mob/living/simple_animal/hostile/giant_spider/tunneler/death() - spawn(1) - for(var/I = 1 to rand(3,6)) - if(src) - new/obj/item/weapon/ore/glass(src.loc) - else - break - return ..() -*/ \ No newline at end of file + incoming_damage_percent = 2 + evasion = -100 \ No newline at end of file diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/hivebot.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/hivebot.dm new file mode 100644 index 0000000000..e0b47c1b7d --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/hivebot.dm @@ -0,0 +1,42 @@ +// Hivebots are tuned towards how many default lasers are needed to kill them. +// As such, if laser damage is ever changed, you should change this define. +#define LASERS_TO_KILL *30 + +/mob/living/simple_mob/mechanical/hivebot + name = "hivebot" + desc = "A robot. It appears to be somewhat resilient, but lacks a true weapon." + icon = 'icons/mob/hivebot.dmi' + icon_state = "basic" + icon_living = "basic" + icon_dead = "basic" + + faction = "hivebot" + + maxHealth = 3 LASERS_TO_KILL + health = 3 LASERS_TO_KILL + water_resist = 0.5 + movement_sound = 'sound/effects/servostep.ogg' + + attacktext = list("clawed") + projectilesound = 'sound/weapons/Gunshot.ogg' + projectiletype = /obj/item/projectile/bullet/hivebot + + ai_holder_type = /datum/ai_holder/simple_mob/hivebot + say_list_type = /datum/say_list/hivebot + + +/mob/living/simple_mob/mechanical/hivebot/death() + ..() + visible_message(span("warning","\The [src] blows apart!")) + new /obj/effect/decal/cleanable/blood/gibs/robot(src.loc) + var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread + s.set_up(3, 1, src) + s.start() + qdel(src) + +// The hivebot's default projectile. +/obj/item/projectile/bullet/hivebot + damage = 10 + damage_type = BRUTE + sharp = FALSE + edge = FALSE diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/ranged_damage.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/ranged_damage.dm new file mode 100644 index 0000000000..9f9094b3d2 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/ranged_damage.dm @@ -0,0 +1,106 @@ +// These hivebots are intended for general damage causing, at range. + +/mob/living/simple_mob/mechanical/hivebot/ranged_damage + maxHealth = 2 LASERS_TO_KILL // 60 health + health = 2 LASERS_TO_KILL + + +// The regular ranged hivebot, that fires somewhat weak projectiles. +/mob/living/simple_mob/mechanical/hivebot/ranged_damage/basic + name = "ranged hivebot" + desc = "A robot with a makeshift integrated ballistic weapon." + + +// This one shoots quickly, and is considerably more dangerous. +/mob/living/simple_mob/mechanical/hivebot/ranged_damage/rapid + name = "rapid hivebot" + desc = "A robot with a crude but deadly integrated rifle." + base_attack_cooldown = 5 // Two attacks a second or so. + player_msg = "You have a rapid fire attack." + + +// Shoots deadly lasers. +/mob/living/simple_mob/mechanical/hivebot/ranged_damage/laser + name = "laser hivebot" + desc = "A robot with a photonic weapon integrated into itself." + projectiletype = /obj/item/projectile/beam/blue + projectilesound = 'sound/weapons/Laser.ogg' + player_msg = "You have a laser attack." + + +// Shoots EMPs, to screw over other robots. +/mob/living/simple_mob/mechanical/hivebot/ranged_damage/ion + name = "ionic hivebot" + desc = "A robot with an electromagnetic pulse projector." + projectiletype = /obj/item/projectile/ion + projectilesound = 'sound/weapons/Laser.ogg' + player_msg = "You have a ranged ion attack, which is very strong against other synthetics.
\ + Be careful to not hit yourself or your team, as it will affect you as well." + +// Beefy and ranged. +/mob/living/simple_mob/mechanical/hivebot/ranged_damage/strong + name = "strong hivebot" + desc = "A robot with a crude ballistic weapon and strong armor." + maxHealth = 4 LASERS_TO_KILL // 120 health. + health = 4 LASERS_TO_KILL + melee_damage_lower = 15 + melee_damage_upper = 15 + +// Also beefy, but tries to stay at their 'home', ideal for base defense. +/mob/living/simple_mob/mechanical/hivebot/ranged_damage/strong/guard + name = "guard hivebot" + desc = "A robot that seems to be guarding something." +// ai_holder_type = todo + + +// Inflicts a damage-over-time modifier on things it hits. +// It is able to stack with repeated attacks. +/mob/living/simple_mob/mechanical/hivebot/ranged_damage/dot + name = "ember hivebot" + desc = "A robot that appears to utilize fire to cook their enemies." + projectiletype = /obj/item/projectile/fire + heat_resist = 1 + player_msg = "Your attacks inflict a damage over time effect, that will \ + harm your target slowly. The effect stacks with further attacks.
\ + You are also immune to fire." + +/obj/item/projectile/fire + name = "ember" + icon_state = "fireball" + modifier_type_to_apply = /datum/modifier/fire + modifier_duration = 6 SECONDS // About 15 damage per stack, as Life() ticks every two seconds. + damage = 0 + nodamage = TRUE + + +// Very long ranged hivebot that rains down hell. +// Their projectiles arc, meaning they go over everything until it hits the ground. +// This means they're somewhat easier to avoid, but go over most defenses (like allies, or barriers), +// and tend to do more harm than a regular projectile, due to being AoE. +/mob/living/simple_mob/mechanical/hivebot/ranged_damage/siege + name = "siege engine hivebot" + desc = "A large robot capable of delivering long range bombardment." + projectiletype = /obj/item/projectile/arc/test + icon_scale = 2 + player_msg = "You are capable of firing very long range bombardment attacks.
\ + To use, click on a tile or enemy at a long range. Note that the projectile arcs in the air, \ + so it will fly over everything inbetween you and the target.
\ + The bombardment is most effective when attacking a static structure, as it cannot avoid your fire." + +// Fires EMP blasts. +/mob/living/simple_mob/mechanical/hivebot/ranged_damage/siege/emp + name = "ionic artillery hivebot" + desc = "A large robot capable of annihilating electronics from a long distance." + projectiletype = /obj/item/projectile/arc/emp_blast + +// Fires shots that irradiate the tile hit. +/mob/living/simple_mob/mechanical/hivebot/ranged_damage/siege/radiation + name = "desolator hivebot" + desc = "A large robot capable of irradiating a large area from afar." + projectiletype = /obj/item/projectile/arc/radioactive + +// Essentially a long ranged frag grenade. +/mob/living/simple_mob/mechanical/hivebot/ranged_damage/siege/fragmentation + name = "anti-personnel artillery hivebot" + desc = "A large robot capable of delivering fragmentation shells to rip apart their fleshy enemies." + projectiletype = /obj/item/projectile/arc/fragmentation diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/support.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/support.dm new file mode 100644 index 0000000000..4aa9b4051e --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/support.dm @@ -0,0 +1,87 @@ +// These hivebots help their team in various ways, and can be very powerful with allies, but are otherwise very weak when alone. + +/mob/living/simple_mob/mechanical/hivebot/support + attacktext = list("prodded") + movement_cooldown = 5 + melee_damage_lower = 2 + melee_damage_upper = 2 + + +// This hivebot supplies a general buff to nearby hivebots that improve their performance. +// Note that the commander itself does not receive the buff. +/mob/living/simple_mob/mechanical/hivebot/support/commander + name = "commander hivebot" + desc = "A robot that appears to be directing the others." + maxHealth = 5 LASERS_TO_KILL // 150 health + health = 5 LASERS_TO_KILL + player_msg = "You increase the performance of other hivebots near you passively.
\ + You are otherwise very weak offensively." + +/mob/living/simple_mob/mechanical/hivebot/support/commander/handle_special() + world << "Wew" + for(var/mob/living/L in range(4)) + if(L == src) + continue // Don't buff ourselves. + if(L.faction == src.faction && L.isSynthetic()) // Don't buff enemies. + world << "Adding modifier." + L.add_modifier(/datum/modifier/aura/hivebot_commander_buff, null, src) + +// Modifier added to friendly hivebots nearby. +// Boosts most stats by 30%. +// The boost is lost if the commander is too far away or dies. +/datum/modifier/aura/hivebot_commander_buff + name = "Strategicals" + on_created_text = "Signal established with commander. Optimizating combat performance..." + on_expired_text = "Lost signal to commander. Optimization halting." + stacks = MODIFIER_STACK_FORBID + aura_max_distance = 4 + mob_overlay_state = "signal_blue" + + disable_duration_percent = 0.7 + outgoing_melee_damage_percent = 1.3 + attack_speed_percent = 1.3 + accuracy = 30 + slowdown = -1 + evasion = 30 + +// Variant that automatically commands nearby allies to follow it when created. +// Useful to avoid having to manually set follow to a lot of hivebots that are gonna die in the next minute anyways. +/mob/living/simple_mob/mechanical/hivebot/support/commander/autofollow/initialize() + for(var/mob/living/L in hearers(7, src)) + if(!L.ai_holder) + continue + if(L.faction != src.faction) + continue + var/datum/ai_holder/AI = L.ai_holder + AI.set_follow(src) + return ..() + + +// This hivebot adds charges to nearby allied hivebots that use the charge system for their special attacks. +// A charge is given to a nearby ally every so often. +// Charges cannot exceed the initial starting amount. +/mob/living/simple_mob/mechanical/hivebot/support/logistics + name = "logistics hivebot" + desc = "A robot that resupplies their allies." + maxHealth = 3 LASERS_TO_KILL // 90 health + health = 3 LASERS_TO_KILL + player_msg = "You passively restore 'charges' to allies with special abilities who are \ + limited to using them a specific number of times." + var/resupply_range = 5 + var/resupply_cooldown = 4 SECONDS + var/last_resupply = null + +/mob/living/simple_mob/mechanical/hivebot/support/logistics/handle_special() + if(last_resupply + resupply_cooldown > world.time) + return // On cooldown. + + for(var/mob/living/simple_mob/SM in hearers(resupply_range, src)) + if(SM == src) + continue // We don't use charges buuuuut in case that changes in the future... + if(SM.faction == src.faction) // Don't resupply enemies. + if(!isnull(SM.special_attack_charges) && SM.special_attack_charges < initial(SM.special_attack_charges)) + SM.special_attack_charges += 1 + to_chat(SM, span("notice", "\The [src] has resupplied you, and you can use your special ability one additional time.")) + to_chat(src, span("notice", "You have resupplied \the [SM].")) + last_resupply = world.time + break // Only one resupply per pulse. diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/tank.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/tank.dm new file mode 100644 index 0000000000..007f190c04 --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/hivebot/tank.dm @@ -0,0 +1,166 @@ +// These hivebots are harder to kill than normal, and are meant to protect their squad by +// distracting their enemies. This is done by being seen as very threatening. +// Their melee attacks weaken whatever they hit. + +/mob/living/simple_mob/mechanical/hivebot/tank + attacktext = list("prodded") + projectiletype = null // To force the AI to melee. + movement_cooldown = 10 + melee_damage_lower = 3 + melee_damage_upper = 3 + attack_sound = 'sound/weapons/Egloves.ogg' + +// All tank hivebots apply a modifier to their target, and force them to attack them if they're AI controlled. +/mob/living/simple_mob/mechanical/hivebot/tank/apply_melee_effects(atom/A) + if(isliving(A)) + var/mob/living/L = A + L.taunt(src, TRUE) + L.add_modifier(/datum/modifier/hivebot_weaken, 3 SECONDS) + +// Modifier applied to whatever a tank hivebot hits, intended to make the target do even less damage. +/datum/modifier/hivebot_weaken + name = "Shocked" + desc = "You feel less able to exert yourself after being prodded." + on_created_text = "You feel weak..." + on_expired_text = "You feel better." + stacks = MODIFIER_STACK_EXTEND + mob_overlay_state = "electricity" + + attack_speed_percent = 0.6 + outgoing_melee_damage_percent = 0.7 + accuracy = -40 + accuracy_dispersion = 1 + slowdown = 1 + evasion = -20 + +// This one is tanky by having a massive amount of health. +/mob/living/simple_mob/mechanical/hivebot/tank/meatshield + name = "bulky hivebot" + desc = "A large robot." + maxHealth = 10 LASERS_TO_KILL // 300 health + health = 10 LASERS_TO_KILL + icon_scale = 2 + player_msg = "You have a very large amount of health." + + +// This one is tanky by having armor. +/mob/living/simple_mob/mechanical/hivebot/tank/armored + name = "armored hivebot" + desc = "A robot clad in heavy armor." + maxHealth = 5 LASERS_TO_KILL // 150 health. + health = 5 LASERS_TO_KILL + icon_scale = 1.5 + player_msg = "You are heavily armored." + // Note that armor effectively makes lasers do about 9 damage instead of 30, + // so it has an effective health of ~16.6 LASERS_TO_KILL if regular lasers are used. + // Xrays will do much better against this. + armor = list( + "melee" = 40, + "bullet" = 40, + "laser" = 40, + "energy" = 30, + "bomb" = 30, + "bio" = 100, + "rad" = 100 + ) + armor_soak = list( + "melee" = 15, + "bullet" = 10, + "laser" = 15, + "energy" = 0, + "bomb" = 0, + "bio" = 0, + "rad" = 0 + ) + +/mob/living/simple_mob/mechanical/hivebot/tank/armored/anti_melee + name = "riot hivebot" + desc = "A robot specialized in close quarters combat." + player_msg = "You are heavily armored against close quarters combat." + armor = list( + "melee" = 70, + "bullet" = 0, + "laser" = 0, + "energy" = 0, + "bomb" = 0, + "bio" = 100, + "rad" = 100 + ) + armor_soak = list( + "melee" = 20, + "bullet" = 0, + "laser" = 0, + "energy" = 0, + "bomb" = 0, + "bio" = 0, + "rad" = 0 + ) + +/mob/living/simple_mob/mechanical/hivebot/tank/armored/anti_bullet + name = "bulletproof hivebot" + desc = "A robot specialized in ballistic defense." + player_msg = "You are heavily armored against ballistic weapons." + armor = list( + "melee" = 0, + "bullet" = 70, + "laser" = 0, + "energy" = 0, + "bomb" = 0, + "bio" = 100, + "rad" = 100 + ) + armor_soak = list( + "melee" = 0, + "bullet" = 20, + "laser" = 0, + "energy" = 0, + "bomb" = 0, + "bio" = 0, + "rad" = 0 + ) + +/mob/living/simple_mob/mechanical/hivebot/tank/armored/anti_laser + name = "ablative hivebot" + desc = "A robot specialized in photonic defense." + player_msg = "You are heavily armored against laser weapons." + armor = list( + "melee" = 0, + "bullet" = 0, + "laser" = 70, + "energy" = 0, + "bomb" = 0, + "bio" = 100, + "rad" = 100 + ) + armor_soak = list( + "melee" = 0, + "bullet" = 0, + "laser" = 20, + "energy" = 0, + "bomb" = 0, + "bio" = 0, + "rad" = 0 + ) + var/reflect_chance = 40 // Same as regular ablative. + +// Ablative Hivebots can reflect lasers just like humans. +/mob/living/simple_mob/mechanical/hivebot/tank/armored/anti_laser/bullet_act(obj/item/projectile/P) + if(istype(P, /obj/item/projectile/energy) || istype(P, /obj/item/projectile/beam)) + var/reflect_prob = reflect_chance - round(P.damage/3) + if(prob(reflect_prob)) + visible_message(span("danger", "The [P.name] gets reflected by [src]'s armor!"), \ + span("userdanger", "The [P.name] gets reflected by [src]'s armor!")) + + // Find a turf near or on the original location to bounce to + if(P.starting) + var/new_x = P.starting.x + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3) + var/new_y = P.starting.y + pick(0, 0, -1, 1, -2, 2, -2, 2, -2, 2, -3, 3, -3, 3) + var/turf/curloc = get_turf(src) + + // redirect the projectile + P.redirect(new_x, new_y, curloc, src) + P.reflected = 1 + + return -1 // complete projectile permutation + + return (..(P)) diff --git a/code/modules/mob/living/simple_mob/subtypes/mechanical/mechanical.dm b/code/modules/mob/living/simple_mob/subtypes/mechanical/mechanical.dm index 7a800056f6..57d4055b26 100644 --- a/code/modules/mob/living/simple_mob/subtypes/mechanical/mechanical.dm +++ b/code/modules/mob/living/simple_mob/subtypes/mechanical/mechanical.dm @@ -1,3 +1,4 @@ +// Mechanical mobs don't care about the atmosphere and cannot be hurt by tasers. /mob/living/simple_mob/mechanical mob_class = MOB_CLASS_SYNTHETIC min_oxy = 0 diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm index ad005e8aa5..8b113299b3 100644 --- a/code/modules/projectiles/projectile.dm +++ b/code/modules/projectiles/projectile.dm @@ -59,6 +59,8 @@ var/drowsy = 0 var/agony = 0 var/reflected = 0 // This should be set to 1 if reflected by any means, to prevent infinite reflections. + var/modifier_type_to_apply = null // If set, will apply a modifier to mobs that are hit by this projectile. + var/modifier_duration = null // How long the above modifier should last for. Leave null to be permanent. embed_chance = 0 //Base chance for a projectile to embed @@ -86,6 +88,8 @@ // if(isanimal(target)) return 0 var/mob/living/L = target L.apply_effects(stun, weaken, paralyze, irradiate, stutter, eyeblur, drowsy, agony, blocked, incendiary, flammability) // add in AGONY! + if(modifier_type_to_apply) + L.add_modifier(modifier_type_to_apply, modifier_duration) return 1 //called when the projectile stops flying because it collided with something @@ -323,6 +327,7 @@ before_move() Move(location.return_turf()) + after_move() if(!bumped && !isturf(original)) if(loc == get_turf(original)) @@ -348,6 +353,9 @@ /obj/item/projectile/proc/before_move() return +/obj/item/projectile/proc/after_move() + return + /obj/item/projectile/proc/setup_trajectory(turf/startloc, turf/targloc, var/x_offset = 0, var/y_offset = 0) // setup projectile state starting = startloc diff --git a/code/modules/projectiles/projectile/arc.dm b/code/modules/projectiles/projectile/arc.dm new file mode 100644 index 0000000000..28e0c39a12 --- /dev/null +++ b/code/modules/projectiles/projectile/arc.dm @@ -0,0 +1,127 @@ +// These projectiles are somewhat different from the other projectiles in the code. +// First, these have an 'arcing' visual, that is accomplished by having the projectile icon rotate as its flying, and +// moving up, then down as it approaches the target. There is also a small shadow effect that follows the projectile +// as its flying. + +// Besides the visuals, arcing projectiles do not collide with anything until they reach the target, as they fly over them. +// For best effect, use this only when it makes sense to do so, IE on the Surface. The projectiles don't care about ceilings or gravity. + +/obj/item/projectile/arc + name = "arcing shot" + icon_state = "fireball" // WIP + step_delay = 2 // Travel a bit slower, to really sell the arc visuals. + plane = ABOVE_PLANE // Since projectiles are 'in the air', they might visually overlap mobs while in flight, so the projectile needs to be above their plane. + var/target_distance = null // How many tiles the impact site is. + var/fired_dir = null // Which direction was the projectile fired towards. Needed to invert the projectile turning based on if facing left or right. + var/obj/effect/projectile_shadow/shadow = null // Visual indicator for the projectile's 'true' position. Needed due to being bound to two dimensions in reality. + +/obj/item/projectile/arc/initialize() + shadow = new(get_turf(src)) + return ..() + +/obj/item/projectile/arc/Destroy() + qdel_null(shadow) + return ..() + +/obj/item/projectile/arc/Bump(atom/A, forced=0) + return 0 +// if(get_turf(src) != original) +// return 0 +// else +// return ..() + +// This is a test projectile in the sense that its testing the code to make sure it works, +// as opposed to a 'can I hit this thing' projectile. +/obj/item/projectile/arc/test/on_impact(turf/T) + new /obj/effect/explosion(T) + return ..() + +/obj/item/projectile/arc/launch(atom/target, target_zone, x_offset=0, y_offset=0, angle_offset=0) + var/expected_distance = get_dist(target, loc) + kill_count = expected_distance // So the projectile "hits the ground." + target_distance = expected_distance + fired_dir = get_dir(loc, target) + ..() // Does the regular launching stuff. + if(fired_dir & EAST) + transform = turn(transform, -45) + else if(fired_dir & WEST) + transform = turn(transform, 45) + + +// Visuals. +/obj/item/projectile/arc/after_move() + // Handle projectile turning in flight. + // This won't turn if fired north/south, as it looks weird. + var/turn_per_step = 90 / target_distance + if(fired_dir & EAST) + transform = turn(transform, turn_per_step) + else if(fired_dir & WEST) + transform = turn(transform, -turn_per_step) + + // Now for the fake height. + // We need to know how far along our "arc" we are. + var/arc_progress = get_dist(src, original) + var/arc_max_height = (target_distance * world.icon_size) / 2 // TODO: Real math. +// var/arc_center = target_distance / 2 +// var/projectile_position = abs(arc_progress - arc_center) +// var/height_multiplier = projectile_position / arc_center +// height_multiplier = abs(height_multiplier - 1) +// height_multiplier = height_multiplier ** 2 + + +// animate(src, pixel_z = arc_max_height * height_multiplier, time = step_delay) + var/projectile_position = arc_progress / target_distance + var/sine_position = projectile_position * 180 + var/pixel_z_position = arc_max_height * sin(sine_position) + animate(src, pixel_z = pixel_z_position, time = step_delay) + + // Update our shadow. + shadow.forceMove(loc) + + + +/obj/effect/projectile_shadow + name = "shadow" + desc = "You better avoid the thing coming down!" + icon = 'icons/obj/projectiles.dmi' + icon_state = "arc_shadow" + anchored = TRUE + + +// Subtypes + +/obj/item/projectile/arc/emp_blast + name = "emp blast" + icon_state = "bluespace" + +/obj/item/projectile/arc/emp_blast/on_impact(turf/T) + empulse(T, 2, 4, 7, 10) // Normal EMP grenade. + return ..() + +/obj/item/projectile/arc/emp_blast/weak/on_impact(turf/T) + empulse(T, 1, 2, 3, 4) // Sec EMP grenade. + return ..() + + +/obj/item/projectile/arc/radioactive + name = "radiation blast" + icon_state = "green_pellet" + icon_scale = 2 + var/rad_power = 50 + +/obj/item/projectile/arc/radioactive/on_impact(turf/T) + radiation_repository.radiate(T, rad_power) + + +/obj/item/projectile/arc/fragmentation + name = "fragmentation shot" + icon_state = "shell" + var/list/fragment_types = list( + /obj/item/projectile/bullet/pellet/fragment, /obj/item/projectile/bullet/pellet/fragment, \ + /obj/item/projectile/bullet/pellet/fragment, /obj/item/projectile/bullet/pellet/fragment/strong + ) + var/fragment_amount = 63 // Same as a grenade. + var/spread_range = 7 + +/obj/item/projectile/arc/fragmentation/on_impact(turf/T) + fragmentate(T, fragment_amount, spread_range, fragment_types) diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi index c1b5a8963a..a3e4725192 100644 Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ diff --git a/icons/misc/buildmode.dmi b/icons/misc/buildmode.dmi index c294178f91..dc8de0b6d0 100644 Binary files a/icons/misc/buildmode.dmi and b/icons/misc/buildmode.dmi differ diff --git a/icons/mob/modifier_effects.dmi b/icons/mob/modifier_effects.dmi index 947c6532f9..882598aa0b 100644 Binary files a/icons/mob/modifier_effects.dmi and b/icons/mob/modifier_effects.dmi differ diff --git a/icons/obj/projectiles.dmi b/icons/obj/projectiles.dmi index 35d09318ee..6944780fc2 100644 Binary files a/icons/obj/projectiles.dmi and b/icons/obj/projectiles.dmi differ diff --git a/polaris.dme b/polaris.dme index 203a9d0529..e8c3a5c63e 100644 --- a/polaris.dme +++ b/polaris.dme @@ -1697,6 +1697,7 @@ #include "code\modules\mob\transform_procs.dm" #include "code\modules\mob\typing_indicator.dm" #include "code\modules\mob\update_icons.dm" +#include "code\modules\mob\_modifiers\auras.dm" #include "code\modules\mob\_modifiers\cloning.dm" #include "code\modules\mob\_modifiers\modifiers.dm" #include "code\modules\mob\_modifiers\modifiers_misc.dm" @@ -1962,6 +1963,10 @@ #include "code\modules\mob\living\simple_mob\subtypes\mechanical\combat_drone.dm" #include "code\modules\mob\living\simple_mob\subtypes\mechanical\mechanical.dm" #include "code\modules\mob\living\simple_mob\subtypes\mechanical\viscerator.dm" +#include "code\modules\mob\living\simple_mob\subtypes\mechanical\hivebot\hivebot.dm" +#include "code\modules\mob\living\simple_mob\subtypes\mechanical\hivebot\ranged_damage.dm" +#include "code\modules\mob\living\simple_mob\subtypes\mechanical\hivebot\support.dm" +#include "code\modules\mob\living\simple_mob\subtypes\mechanical\hivebot\tank.dm" #include "code\modules\mob\living\voice\voice.dm" #include "code\modules\mob\new_player\login.dm" #include "code\modules\mob\new_player\logout.dm" @@ -2162,6 +2167,7 @@ #include "code\modules\projectiles\guns\projectile\sniper.dm" #include "code\modules\projectiles\guns\projectile\sniper\collapsible_sniper.dm" #include "code\modules\projectiles\projectile\animate.dm" +#include "code\modules\projectiles\projectile\arc.dm" #include "code\modules\projectiles\projectile\beams.dm" #include "code\modules\projectiles\projectile\bullets.dm" #include "code\modules\projectiles\projectile\change.dm" diff --git a/sound/effects/break_stone.ogg b/sound/effects/break_stone.ogg new file mode 100644 index 0000000000..711fd50d48 Binary files /dev/null and b/sound/effects/break_stone.ogg differ diff --git a/sound/effects/servostep.ogg b/sound/effects/servostep.ogg new file mode 100644 index 0000000000..9999b99ccb Binary files /dev/null and b/sound/effects/servostep.ogg differ diff --git a/sound/effects/suitstep1.ogg b/sound/effects/suitstep1.ogg new file mode 100644 index 0000000000..fdc56bb9e4 Binary files /dev/null and b/sound/effects/suitstep1.ogg differ diff --git a/sound/effects/suitstep2.ogg b/sound/effects/suitstep2.ogg new file mode 100644 index 0000000000..17a528feeb Binary files /dev/null and b/sound/effects/suitstep2.ogg differ