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