This commit is contained in:
Neerti
2018-05-21 21:12:47 -04:00
parent 98ea2b1a2c
commit 7b61dc16c1
35 changed files with 847 additions and 88 deletions
+85
View File
@@ -127,6 +127,18 @@
usr << "<span class='notice'>Right Mouse Button on turf/obj/mob = Reset glowing</span>"
usr << "<span class='notice'>Right Mouse Button on buildmode button = Change glow properties</span>"
usr << "<span class='notice'>***********************************************************</span>"
if(9) // Control mobs with ai_holders.
usr << "<span class='notice'>***********************************************************</span>"
usr << "<span class='notice'>Left Mouse Button on AI mob = Select/Deselect mob</span>"
usr << "<span class='notice'>Left Mouse Button + alt on AI mob = Toggle hostility on mob</span>"
usr << "<span class='notice'>Left Mouse Button + ctrl on AI mob = Reset target/following/movement</span>"
usr << "<span class='notice'>Right Mouse Button on enemy mob = Command selected mobs to attack mob</span>"
usr << "<span class='notice'>Right Mouse Button on allied mob = Command selected mobs to follow mob</span>"
usr << "<span class='notice'>Right Mouse Button + shift on any mob = Command selected mobs to follow mob regardless of faction</span>"
usr << "<span class='notice'>Right Mouse Button on tile = Command selected mobs to move to tile (will cancel if enemies are seen)</span>"
usr << "<span class='notice'>Right Mouse Button + shift on tile = Command selected mobs to reposition to tile (will not be inturrupted by enemies)</span>"
usr << "<span class='notice'>Right Mouse Button + alt on obj/turfs = Command selected mobs to attack obj/turf</span>"
usr << "<span class='notice'>***********************************************************</span>"
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]")
@@ -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.
*/
*/
/datum/ai_holder/simple_mob/hivebot
conserve_ammo = TRUE
firing_lanes = TRUE
can_flee = FALSE // Fearless dumb machines.
+6 -1
View File
@@ -327,4 +327,9 @@
return null
if(client && !ai_holder.autopilot)
return null
return ai_holder.stance
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)
+2 -1
View File
@@ -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.
+5 -2
View File
@@ -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)
+1 -1
View File
@@ -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)
+14 -1
View File
@@ -80,4 +80,17 @@
emote_see = list("clacks")
/datum/say_list/spider
emote_hear = list("chitters")
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.")
+18
View File
@@ -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.
@@ -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 = "<span class='danger'>You combust into flames!</span>"
on_expired_text = "<span class='warning'>The fire starts to fade.</span>"
stacks = MODIFIER_STACK_ALLOWED // Multiple instances will hurt a lot.
var/damage_per_tick = 5
/datum/modifier/fire/tick()
holder.adjustFireLoss(damage_per_tick)
@@ -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(!..())
+23 -1
View File
@@ -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.
@@ -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)
@@ -237,3 +237,5 @@
/obj/item/projectile/bullet/hivebot
damage = 10
damage_type = BRUTE
#undef LASERS_TO_KILL
@@ -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
@@ -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
@@ -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)
@@ -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()
@@ -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 <b>can perform a tunneling attack</b> by clicking on someone from a distance while on natural terrain.<br>\
player_msg = "You <b>can perform a tunneling attack</b> by clicking on someone from a distance.<br>\
There is a noticable travel delay as you tunnel towards the tile the target was at when you started the tunneling attack.<br>\
Any entities inbetween you and the targeted tile will be stunned for a brief period of time.<br>\
Whatever is on the targeted tile when you arrive will suffer a potent stun.<br>\
If nothing is on the targeted tile, you will overshoot and keep going for a few more tiles.<br>\
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 = "<span class='warning'>You feel vulnerable...</span>"
on_expired_text = "<span class='notice'>You feel better.</span>"
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 ..()
*/
incoming_damage_percent = 2
evasion = -100
@@ -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
@@ -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 <b>rapid fire attack</b>."
// 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 <b>laser attack</b>."
// 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 <b>ranged ion attack</b>, which is very strong against other synthetics.<br>\
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 <b>damage over time</b> effect, that will \
harm your target slowly. The effect stacks with further attacks.<br>\
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 <b>very long range bombardment attacks</b>.<br>\
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.<br>\
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
@@ -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 <b>increase the performance of other hivebots near you</b> passively.<br>\
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 = "<span class='notice'>Signal established with commander. Optimizating combat performance...</span>"
on_expired_text = "<span class='warning'>Lost signal to commander. Optimization halting.</span>"
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 <b>passively restore 'charges' to allies with special abilities</b> 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.
@@ -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 = "<span class='warning'>You feel weak...</span>"
on_expired_text = "<span class='notice'>You feel better.</span>"
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))
@@ -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
+8
View File
@@ -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
+127
View File
@@ -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)