Does some AI work.
@@ -98,7 +98,7 @@
|
||||
/obj/screen/fullscreen/flash
|
||||
icon = 'icons/mob/screen1.dmi'
|
||||
screen_loc = "WEST,SOUTH to EAST,NORTH"
|
||||
icon_state = "flash"
|
||||
icon_state = "flash_static"
|
||||
|
||||
/obj/screen/fullscreen/flash/noise
|
||||
icon_state = "noise"
|
||||
|
||||
@@ -607,7 +607,7 @@ proc/findNullRod(var/atom/target)
|
||||
name = "sphere of agony"
|
||||
desc = "Call forth a portal to a dimension of naught but pain at your target."
|
||||
|
||||
spawner_type = /obj/effect/temporary_effect/pulsar/agonizing_sphere
|
||||
spawner_type = /obj/effect/temporary_effect/pulse/agonizing_sphere
|
||||
|
||||
/obj/item/weapon/spell/construct/spawner/agonizing_sphere/on_ranged_cast(atom/hit_atom, mob/user)
|
||||
if(within_range(hit_atom) && pay_energy(10))
|
||||
@@ -619,7 +619,7 @@ proc/findNullRod(var/atom/target)
|
||||
var/mob/living/L = hit_atom
|
||||
L.add_modifier(/datum/modifier/agonize, 10 SECONDS)
|
||||
|
||||
/obj/effect/temporary_effect/pulsar/agonizing_sphere
|
||||
/obj/effect/temporary_effect/pulse/agonizing_sphere
|
||||
name = "agonizing sphere"
|
||||
desc = "A portal to some hellish place. Its screams wrack your body with pain.."
|
||||
icon_state = "red_static_sphere"
|
||||
@@ -628,19 +628,15 @@ proc/findNullRod(var/atom/target)
|
||||
light_power = 5
|
||||
light_color = "#FF0000"
|
||||
pulses_remaining = 10
|
||||
pulse_delay = 1 SECOND
|
||||
|
||||
/obj/effect/temporary_effect/pulsar/agonizing_sphere/pulse_loop()
|
||||
while(pulses_remaining)
|
||||
sleep(1 SECONDS)
|
||||
spawn()
|
||||
for(var/mob/living/L in view(4,src))
|
||||
if(!iscultist(L) && !istype(L, /mob/living/simple_mob/construct))
|
||||
L.add_modifier(/datum/modifier/agonize, 2 SECONDS)
|
||||
if(L.isSynthetic())
|
||||
to_chat(L, "<span class='cult'>Your chassis warps as the [src] pulses!</span>")
|
||||
L.adjustFireLoss(4)
|
||||
pulses_remaining--
|
||||
qdel(src)
|
||||
/obj/effect/temporary_effect/pulse/agonizing_sphere/on_pulse()
|
||||
for(var/mob/living/L in view(4,src))
|
||||
if(!iscultist(L) && !istype(L, /mob/living/simple_mob/construct))
|
||||
L.add_modifier(/datum/modifier/agonize, 2 SECONDS)
|
||||
if(L.isSynthetic())
|
||||
to_chat(L, "<span class='cult'>Your chassis warps as the [src] pulses!</span>")
|
||||
L.adjustFireLoss(4)
|
||||
|
||||
//Artificer Heal
|
||||
|
||||
|
||||
@@ -1,258 +0,0 @@
|
||||
//An AI-controlled 'companion' for the Technomancer. It's tough, strong, and can also use spells.
|
||||
/mob/living/simple_animal/technomancer_golem
|
||||
name = "G.O.L.E.M."
|
||||
desc = "A rather unusual looking synthetic."
|
||||
icon = 'icons/mob/mob.dmi'
|
||||
icon_state = "technomancer_golem"
|
||||
health = 250
|
||||
maxHealth = 250
|
||||
stop_automated_movement = 1
|
||||
wander = 0
|
||||
response_help = "pets"
|
||||
response_disarm = "pushes away"
|
||||
response_harm = "punches"
|
||||
harm_intent_damage = 3
|
||||
|
||||
heat_damage_per_tick = 0
|
||||
cold_damage_per_tick = 0
|
||||
|
||||
min_oxy = 0
|
||||
max_oxy = 0
|
||||
min_tox = 0
|
||||
max_tox = 0
|
||||
min_co2 = 0
|
||||
max_co2 = 0
|
||||
min_n2 = 0
|
||||
max_n2 = 0
|
||||
unsuitable_atoms_damage = 0
|
||||
speed = 0
|
||||
|
||||
melee_damage_lower = 30 // It has a built in esword.
|
||||
melee_damage_upper = 30
|
||||
attack_sound = 'sound/weapons/blade1.ogg'
|
||||
attacktext = list("slashed")
|
||||
friendly = "hugs"
|
||||
resistance = 0
|
||||
melee_miss_chance = 0
|
||||
|
||||
var/obj/item/weapon/technomancer_core/golem/core = null
|
||||
var/obj/item/weapon/spell/active_spell = null // Shield and ranged spells
|
||||
var/mob/living/master = null
|
||||
|
||||
var/list/known_spells = list(
|
||||
"beam" = /obj/item/weapon/spell/projectile/beam,
|
||||
"chain lightning" = /obj/item/weapon/spell/projectile/chain_lightning,
|
||||
"force missile" = /obj/item/weapon/spell/projectile/force_missile,
|
||||
"ionic bolt" = /obj/item/weapon/spell/projectile/ionic_bolt,
|
||||
"lightning" = /obj/item/weapon/spell/projectile/lightning,
|
||||
"blink" = /obj/item/weapon/spell/blink,
|
||||
"dispel" = /obj/item/weapon/spell/dispel,
|
||||
"oxygenate" = /obj/item/weapon/spell/oxygenate,
|
||||
"mend life" = /obj/item/weapon/spell/modifier/mend_life,
|
||||
"mend synthetic" = /obj/item/weapon/spell/modifier/mend_synthetic,
|
||||
"mend organs" = /obj/item/weapon/spell/mend_organs,
|
||||
"purify" = /obj/item/weapon/spell/modifier/purify,
|
||||
"resurrect" = /obj/item/weapon/spell/resurrect,
|
||||
"passwall" = /obj/item/weapon/spell/passwall,
|
||||
"repel missiles" = /obj/item/weapon/spell/modifier/repel_missiles,
|
||||
"corona" = /obj/item/weapon/spell/modifier/corona,
|
||||
"haste" = /obj/item/weapon/spell/modifier/haste
|
||||
)
|
||||
|
||||
// Holds the overlays, when idle or attacking.
|
||||
var/image/sword_image = null
|
||||
var/image/spell_image = null
|
||||
// These contain icon_states for each frame of an attack animation, which is swapped in and out manually, because BYOND.
|
||||
// They are assoc lists, to hold the frame duration and the frame icon_state in one list.
|
||||
var/list/spell_pre_attack_states = list(
|
||||
"golem_spell_attack_1" = 1,
|
||||
"golem_spell_attack_2" = 2,
|
||||
"golem_spell_attack_3" = 2
|
||||
)
|
||||
var/list/spell_post_attack_states = list(
|
||||
"golem_spell_attack_4" = 2,
|
||||
"golem_spell_attack_5" = 3,
|
||||
"golem_spell_attack_6" = 3
|
||||
)
|
||||
var/list/sword_pre_attack_states = list(
|
||||
"golem_sword_attack_1" = 1,
|
||||
"golem_sword_attack_2" = 5
|
||||
)
|
||||
var/list/sword_post_attack_states = list(
|
||||
"golem_sword_attack_3" = 1,
|
||||
"golem_sword_attack_4" = 3
|
||||
)
|
||||
|
||||
/mob/living/simple_animal/technomancer_golem/New()
|
||||
..()
|
||||
core = new(src)
|
||||
sword_image = image(icon, src, "golem_sword")
|
||||
spell_image = image(icon, src, "golem_spell")
|
||||
update_icon()
|
||||
|
||||
/mob/living/simple_animal/technomancer_golem/Destroy()
|
||||
qdel(core)
|
||||
qdel(sword_image)
|
||||
qdel(spell_image)
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_animal/technomancer_golem/unref_spell()
|
||||
active_spell = null
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_animal/technomancer_golem/death()
|
||||
..()
|
||||
visible_message("\The [src] disintegrates!")
|
||||
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)
|
||||
|
||||
/mob/living/simple_animal/technomancer_golem/update_icon()
|
||||
overlays.Cut()
|
||||
overlays += sword_image
|
||||
overlays += spell_image
|
||||
update_modifier_visuals()
|
||||
|
||||
// Unfortunately, BYOND does not let you flick() images or other overlays, so we need to do this in a terrible way.
|
||||
/atom/proc/manual_flick(var/list/frames, var/image/I, var/reset_to = null)
|
||||
// Swap in and out each frame manually.
|
||||
for(var/frame in frames)
|
||||
overlays -= I
|
||||
I.icon_state = frame
|
||||
overlays += I
|
||||
sleep(frames[frame])
|
||||
if(reset_to)
|
||||
// One more time to reset it to what it was before.
|
||||
overlays -= I
|
||||
I.icon_state = reset_to
|
||||
overlays += I
|
||||
|
||||
/mob/living/simple_animal/technomancer_golem/proc/spellcast_pre_animation()
|
||||
setClickCooldown(5)
|
||||
manual_flick(spell_pre_attack_states, spell_image, reset_to = "golem_spell_attack_3")
|
||||
|
||||
/mob/living/simple_animal/technomancer_golem/proc/spellcast_post_animation()
|
||||
setClickCooldown(8)
|
||||
manual_flick(spell_post_attack_states, spell_image, reset_to = "golem_spell")
|
||||
|
||||
/mob/living/simple_animal/technomancer_golem/proc/sword_pre_animation()
|
||||
setClickCooldown(6)
|
||||
manual_flick(sword_pre_attack_states, sword_image)
|
||||
|
||||
/mob/living/simple_animal/technomancer_golem/proc/sword_post_animation()
|
||||
setClickCooldown(3)
|
||||
manual_flick(sword_post_attack_states, sword_image, reset_to = "golem_sword")
|
||||
|
||||
/mob/living/simple_animal/technomancer_golem/DoPunch(var/atom/A)
|
||||
sword_pre_animation()
|
||||
. = ..() // This does the actual attack and will check adjacency again.
|
||||
sword_post_animation()
|
||||
|
||||
/mob/living/simple_animal/technomancer_golem/isSynthetic()
|
||||
return TRUE // So Mend Synthetic will work on them.
|
||||
|
||||
/mob/living/simple_animal/technomancer_golem/speech_bubble_appearance()
|
||||
return "synthetic_evil"
|
||||
|
||||
/mob/living/simple_animal/technomancer_golem/place_spell_in_hand(var/path)
|
||||
if(!path || !ispath(path))
|
||||
return 0
|
||||
|
||||
if(active_spell)
|
||||
qdel(active_spell) // Get rid of our old spell.
|
||||
|
||||
var/obj/item/weapon/spell/S = new path(src)
|
||||
active_spell = S
|
||||
|
||||
/mob/living/simple_animal/technomancer_golem/verb/test_giving_spells()
|
||||
var/choice = input(usr, "What spell?", "Give spell") as null|anything in known_spells
|
||||
if(choice)
|
||||
place_spell_in_hand(known_spells[choice])
|
||||
else
|
||||
qdel(active_spell)
|
||||
|
||||
// Used to cast spells.
|
||||
/mob/living/simple_animal/technomancer_golem/RangedAttack(var/atom/A, var/params)
|
||||
if(active_spell)
|
||||
spellcast_pre_animation()
|
||||
if(active_spell.cast_methods & CAST_RANGED)
|
||||
active_spell.on_ranged_cast(A, src)
|
||||
spellcast_post_animation()
|
||||
|
||||
/mob/living/simple_animal/technomancer_golem/UnarmedAttack(var/atom/A, var/proximity)
|
||||
if(proximity)
|
||||
if(active_spell)
|
||||
spellcast_pre_animation()
|
||||
if(!Adjacent(A)) // Need to check again since they might've moved while 'warming up'.
|
||||
spellcast_post_animation()
|
||||
return
|
||||
var/effective_cooldown = round(active_spell.cooldown * core.cooldown_modifier, 5)
|
||||
if(active_spell.cast_methods & CAST_MELEE)
|
||||
active_spell.on_melee_cast(A, src)
|
||||
else if(active_spell.cast_methods & CAST_RANGED)
|
||||
active_spell.on_ranged_cast(A, src)
|
||||
spellcast_post_animation()
|
||||
src.setClickCooldown(effective_cooldown)
|
||||
else
|
||||
..()
|
||||
|
||||
/mob/living/simple_animal/technomancer_golem/get_technomancer_core()
|
||||
return core
|
||||
|
||||
/mob/living/simple_animal/technomancer_golem/proc/bind_to_mob(mob/user)
|
||||
if(!user || master)
|
||||
return
|
||||
master = user
|
||||
name = "[master]'s [initial(name)]"
|
||||
|
||||
/mob/living/simple_animal/technomancer_golem/examine(mob/user)
|
||||
..()
|
||||
if(user.mind && technomancers.is_antagonist(user.mind))
|
||||
user << "Your pride and joy. It's a very special synthetic robot, capable of using functions similar to you, and you built it \
|
||||
yourself! It'll always stand by your side, ready to help you out. You have no idea what GOLEM stands for, however..."
|
||||
|
||||
/mob/living/simple_animal/technomancer_golem/Life()
|
||||
..()
|
||||
handle_ai()
|
||||
|
||||
// This is where the real spaghetti begins.
|
||||
/mob/living/simple_animal/technomancer_golem/proc/handle_ai()
|
||||
if(!master)
|
||||
return
|
||||
if(get_dist(src, master) > 6 || src.z != master.z)
|
||||
targeted_blink(master)
|
||||
|
||||
// Give our allies buffs and heals.
|
||||
for(var/mob/living/L in view(src))
|
||||
if(L in friends)
|
||||
support_friend(L)
|
||||
return
|
||||
|
||||
/mob/living/simple_animal/technomancer_golem/proc/support_friend(var/mob/living/L)
|
||||
if(L.getBruteLoss() >= 10 || L.getFireLoss() >= 10)
|
||||
if(L.isSynthetic() && !L.has_modifier_of_type(/datum/modifier/technomancer/mend_synthetic))
|
||||
place_spell_in_hand(known_spells["mend synthetic"])
|
||||
targeted_blink(L)
|
||||
UnarmedAttack(L, 1)
|
||||
else if(!L.has_modifier_of_type(/datum/modifier/technomancer/mend_life))
|
||||
place_spell_in_hand(known_spells["mend life"])
|
||||
targeted_blink(L)
|
||||
UnarmedAttack(L, 1)
|
||||
return
|
||||
|
||||
|
||||
// Give them repel missiles if they lack it.
|
||||
if(!L.has_modifier_of_type(/datum/modifier/technomancer/repel_missiles))
|
||||
place_spell_in_hand(known_spells["repel missiles"])
|
||||
RangedAttack(L)
|
||||
return
|
||||
|
||||
/mob/living/simple_animal/technomancer_golem/proc/targeted_blink(var/atom/target)
|
||||
var/datum/effect/effect/system/spark_spread/spark_system = new()
|
||||
spark_system.set_up(5, 0, get_turf(src))
|
||||
spark_system.start()
|
||||
src.visible_message("<span class='notice'>\The [src] vanishes!</span>")
|
||||
src.forceMove(get_turf(target))
|
||||
return
|
||||
@@ -130,7 +130,7 @@
|
||||
for(var/mob/living/L in summoned_mobs)
|
||||
summoned_mobs -= L
|
||||
qdel(L)
|
||||
for(var/mob/living/simple_animal/ward/ward in wards_in_use)
|
||||
for(var/mob/living/ward in wards_in_use)
|
||||
wards_in_use -= ward
|
||||
qdel(ward)
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
aspect = ASPECT_LIGHT
|
||||
cast_methods = CAST_RANGED | CAST_USE
|
||||
var/atom/movable/copied = null
|
||||
var/mob/living/simple_animal/illusion/illusion = null
|
||||
var/mob/living/simple_mob/illusion/illusion = null
|
||||
|
||||
/obj/item/weapon/spell/illusion/on_ranged_cast(atom/hit_atom, mob/user)
|
||||
if(istype(hit_atom, /atom/movable))
|
||||
@@ -33,17 +33,13 @@
|
||||
if(pay_energy(500))
|
||||
illusion = new(T)
|
||||
illusion.copy_appearance(copied)
|
||||
if(ishuman(copied))
|
||||
var/mob/living/carbon/human/H = copied
|
||||
// This is to try to have the illusion move at the same rate the real mob world.
|
||||
illusion.step_delay = max(H.movement_delay() + 4, 3)
|
||||
user << "<span class='notice'>An illusion of \the [copied] is made on \the [T].</span>"
|
||||
user << 'sound/effects/pop.ogg'
|
||||
return 1
|
||||
else
|
||||
if(pay_energy(100))
|
||||
spawn(1)
|
||||
illusion.walk_loop(T)
|
||||
var/datum/ai_holder/AI = illusion.ai_holder
|
||||
AI.give_destination(T)
|
||||
|
||||
/obj/item/weapon/spell/illusion/on_use_cast(mob/user)
|
||||
if(illusion)
|
||||
@@ -76,116 +72,3 @@
|
||||
temp_image.transform = M
|
||||
// temp_image.pixel_y = 8
|
||||
src.overlays.Add(temp_image)
|
||||
|
||||
|
||||
/mob/living/simple_animal/illusion
|
||||
name = "illusion" // gets overwritten
|
||||
desc = "If you can read me, the game broke. Please report this to a coder."
|
||||
resistance = 1000 // holograms are tough
|
||||
wander = 0
|
||||
response_help = "pushes a hand through"
|
||||
response_disarm = "tried to disarm"
|
||||
response_harm = "tried to punch"
|
||||
var/atom/movable/copying = null
|
||||
universal_speak = 1
|
||||
var/realistic = 0
|
||||
var/list/path = list() //Used for AStar pathfinding.
|
||||
var/walking = 0
|
||||
var/step_delay = 10
|
||||
|
||||
/mob/living/simple_animal/illusion/update_icon() // We don't want the appearance changing AT ALL unless by copy_appearance().
|
||||
return
|
||||
|
||||
/mob/living/simple_animal/illusion/proc/copy_appearance(var/atom/movable/thing_to_copy)
|
||||
if(!thing_to_copy)
|
||||
return 0
|
||||
name = thing_to_copy.name
|
||||
desc = thing_to_copy.desc
|
||||
gender = thing_to_copy.gender
|
||||
appearance = thing_to_copy.appearance
|
||||
copying = thing_to_copy
|
||||
return 1
|
||||
|
||||
// We use special movement code for illusions, because BYOND's default pathfinding will use diagonal movement if it results
|
||||
// in the shortest path. As players are incapable of moving in diagonals, we must do this or else illusions will not be convincing.
|
||||
/mob/living/simple_animal/illusion/proc/calculate_path(var/turf/targeted_loc)
|
||||
if(!path.len || !path)
|
||||
spawn(0)
|
||||
path = AStar(loc, targeted_loc, /turf/proc/CardinalTurfs, /turf/proc/Distance, 0, 10, id = null)
|
||||
if(!path)
|
||||
path = list()
|
||||
return
|
||||
|
||||
/mob/living/simple_animal/illusion/proc/walk_path(var/turf/targeted_loc)
|
||||
if(path && path.len)
|
||||
step_to(src, path[1])
|
||||
path -= path[1]
|
||||
return
|
||||
else
|
||||
if(targeted_loc)
|
||||
calculate_path(targeted_loc)
|
||||
|
||||
/mob/living/simple_animal/illusion/proc/walk_loop(var/turf/targeted_loc)
|
||||
if(walking) //Already busy moving somewhere else.
|
||||
return 0
|
||||
walking = 1
|
||||
calculate_path(targeted_loc)
|
||||
if(!targeted_loc)
|
||||
walking = 0
|
||||
return 0
|
||||
if(path.len == 0)
|
||||
calculate_path(targeted_loc)
|
||||
while(loc != targeted_loc)
|
||||
walk_path(targeted_loc)
|
||||
sleep(step_delay)
|
||||
walking = 0
|
||||
return 1
|
||||
|
||||
// Because we can't perfectly duplicate some examine() output, we directly examine the AM it is copying. It's messy but
|
||||
// this is to prevent easy checks from the opposing force.
|
||||
/mob/living/simple_animal/illusion/examine(mob/user)
|
||||
if(copying)
|
||||
copying.examine(user)
|
||||
return
|
||||
..()
|
||||
|
||||
/mob/living/simple_animal/illusion/bullet_act(var/obj/item/projectile/P)
|
||||
if(!P)
|
||||
return
|
||||
|
||||
if(realistic)
|
||||
return ..()
|
||||
|
||||
return PROJECTILE_FORCE_MISS
|
||||
|
||||
/mob/living/simple_animal/illusion/attack_hand(mob/living/carbon/human/M)
|
||||
if(!realistic)
|
||||
playsound(loc, 'sound/weapons/punchmiss.ogg', 25, 1, -1)
|
||||
visible_message("<span class='warning'>[M]'s hand goes through \the [src]!</span>")
|
||||
return
|
||||
else
|
||||
switch(M.a_intent)
|
||||
|
||||
if(I_HELP)
|
||||
var/datum/gender/T = gender_datums[src.get_visible_gender()]
|
||||
M.visible_message("<span class='notice'>[M] hugs [src] to make [T.him] feel better!</span>", \
|
||||
"<span class='notice'>You hug [src] to make [T.him] feel better!</span>") // slightly redundant as at the moment most mobs still use the normal gender var, but it works and future-proofs it
|
||||
playsound(src.loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1)
|
||||
|
||||
if(I_DISARM)
|
||||
playsound(loc, 'sound/weapons/punchmiss.ogg', 25, 1, -1)
|
||||
visible_message("<span class='danger'>[M] attempted to disarm [src]!</span>")
|
||||
M.do_attack_animation(src)
|
||||
|
||||
if(I_GRAB)
|
||||
..()
|
||||
|
||||
if(I_HURT)
|
||||
adjustBruteLoss(harm_intent_damage)
|
||||
M.visible_message("<font color='red'>[M] [response_harm] \the [src]</font>")
|
||||
M.do_attack_animation(src)
|
||||
|
||||
return
|
||||
|
||||
/mob/living/simple_animal/illusion/ex_act()
|
||||
return
|
||||
|
||||
@@ -15,11 +15,13 @@
|
||||
|
||||
/obj/item/weapon/spell/modifier/on_melee_cast(atom/hit_atom, mob/user)
|
||||
if(istype(hit_atom, /mob/living))
|
||||
on_add_modifier(hit_atom)
|
||||
return on_add_modifier(hit_atom)
|
||||
return FALSE
|
||||
|
||||
/obj/item/weapon/spell/modifier/on_ranged_cast(atom/hit_atom, mob/user)
|
||||
if(istype(hit_atom, /mob/living))
|
||||
on_add_modifier(hit_atom)
|
||||
return on_add_modifier(hit_atom)
|
||||
return FALSE
|
||||
|
||||
|
||||
/obj/item/weapon/spell/modifier/proc/on_add_modifier(var/mob/living/L)
|
||||
@@ -32,6 +34,7 @@
|
||||
MT.spell_power = calculate_spell_power(1)
|
||||
log_and_message_admins("has casted [src] on [L].")
|
||||
qdel(src)
|
||||
return TRUE
|
||||
|
||||
// Technomancer specific subtype which keeps track of spell power and gets targeted specificially by Dispel.
|
||||
/datum/modifier/technomancer
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
icon_state = "radiance"
|
||||
cast_methods = CAST_RANGED | CAST_THROW
|
||||
aspect = ASPECT_EMP
|
||||
spawner_type = /obj/effect/temporary_effect/pulsar
|
||||
spawner_type = /obj/effect/temporary_effect/pulse/pulsar
|
||||
|
||||
/obj/item/weapon/spell/spawner/pulsar/New()
|
||||
..()
|
||||
@@ -25,7 +25,29 @@
|
||||
/obj/item/weapon/spell/spawner/pulsar/on_throw_cast(atom/hit_atom, mob/user)
|
||||
empulse(hit_atom, 1, 1, 1, 1, log=1)
|
||||
|
||||
/obj/effect/temporary_effect/pulsar
|
||||
// Does something every so often. Deletes itself when pulses_remaining hits zero.
|
||||
/obj/effect/temporary_effect/pulse
|
||||
var/pulses_remaining = 3
|
||||
var/pulse_delay = 2 SECONDS
|
||||
|
||||
/obj/effect/temporary_effect/pulse/initialize()
|
||||
spawn(0)
|
||||
pulse_loop()
|
||||
return ..()
|
||||
|
||||
/obj/effect/temporary_effect/pulse/proc/pulse_loop()
|
||||
while(pulses_remaining)
|
||||
sleep(pulse_delay)
|
||||
on_pulse()
|
||||
pulses_remaining--
|
||||
qdel(src)
|
||||
|
||||
// Override for specific effects.
|
||||
/obj/effect/temporary_effect/pulse/proc/on_pulse()
|
||||
|
||||
|
||||
|
||||
/obj/effect/temporary_effect/pulse/pulsar
|
||||
name = "pulsar"
|
||||
desc = "Not a real pulsar, but still emits loads of EMP."
|
||||
icon_state = "shield2"
|
||||
@@ -33,17 +55,14 @@
|
||||
light_range = 4
|
||||
light_power = 5
|
||||
light_color = "#2ECCFA"
|
||||
var/pulses_remaining = 3
|
||||
pulses_remaining = 3
|
||||
|
||||
/obj/effect/temporary_effect/pulse/pulsar/on_pulse()
|
||||
empulse(src, 1, 1, 2, 2, log = 1)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/obj/effect/temporary_effect/pulsar/New()
|
||||
..()
|
||||
spawn(0)
|
||||
pulse_loop()
|
||||
|
||||
/obj/effect/temporary_effect/pulsar/proc/pulse_loop()
|
||||
while(pulses_remaining)
|
||||
sleep(2 SECONDS)
|
||||
empulse(src, 1, 1, 2, 2, log = 1)
|
||||
pulses_remaining--
|
||||
qdel(src)
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"SPIDER" = /mob/living/simple_mob/animal/giant_spider,
|
||||
"SPIDER HUNTER" = /mob/living/simple_mob/animal/giant_spider/hunter,
|
||||
"SPIDER NURSE" = /mob/living/simple_mob/animal/giant_spider/nurse,
|
||||
"CARP" = /mob/living/simple_mob/space/carp,
|
||||
"CARP" = /mob/living/simple_mob/animal/space/carp,
|
||||
"BEAR" = /mob/living/simple_mob/animal/space/bear
|
||||
)
|
||||
cooldown = 30
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
/datum/technomancer/spell/summon_ward
|
||||
name = "Summon Ward"
|
||||
desc = "Teleports a prefabricated 'ward' drone to the target location, which will alert you and your allies when it sees entities \
|
||||
moving around it, or when it is attacked. They can see for up to five meters."
|
||||
enhancement_desc = "Wards can detect invisibile entities, and are more specific in relaying information about what it sees. \
|
||||
Invisible entities that are spotted by it will be decloaked."
|
||||
name = "Summon Monitor Ward"
|
||||
desc = "Teleports a prefabricated 'ward' drone to the target location, which will alert you when it sees entities \
|
||||
moving around it, or when it is attacked. They can see for up to five meters. It can also see invisible entities, and \
|
||||
forcefully decloak them if close enough."
|
||||
cost = 25
|
||||
obj_path = /obj/item/weapon/spell/summon/summon_ward
|
||||
category = UTILITY_SPELLS
|
||||
@@ -12,116 +11,10 @@
|
||||
name = "summon ward"
|
||||
desc = "Finally, someone you can depend on to watch your back."
|
||||
cast_methods = CAST_RANGED
|
||||
summoned_mob_type = /mob/living/simple_animal/ward
|
||||
summoned_mob_type = /mob/living/simple_mob/mechanical/ward/monitor
|
||||
cooldown = 10
|
||||
instability_cost = 5
|
||||
energy_cost = 500
|
||||
|
||||
/obj/item/weapon/spell/summon/summon_ward/on_summon(var/mob/living/simple_animal/ward/ward)
|
||||
ward.creator = owner
|
||||
if(check_for_scepter())
|
||||
ward.true_sight = 1
|
||||
ward.see_invisible = SEE_INVISIBLE_LEVEL_TWO
|
||||
|
||||
/mob/living/simple_animal/ward
|
||||
name = "ward"
|
||||
desc = "It's a little flying drone that seems to be watching you..."
|
||||
icon = 'icons/mob/critter.dmi'
|
||||
icon_state = "ward"
|
||||
resistance = 5
|
||||
wander = 0
|
||||
response_help = "pets the"
|
||||
response_disarm = "swats away"
|
||||
response_harm = "punches"
|
||||
min_oxy = 0
|
||||
max_oxy = 0
|
||||
min_tox = 0
|
||||
max_tox = 0
|
||||
min_co2 = 0
|
||||
max_co2 = 0
|
||||
min_n2 = 0
|
||||
max_n2 = 0
|
||||
minbodytemp = 0
|
||||
maxbodytemp = 0
|
||||
unsuitable_atoms_damage = 0
|
||||
heat_damage_per_tick = 0
|
||||
cold_damage_per_tick = 0
|
||||
|
||||
var/true_sight = 0 // If true, detects more than what the Technomancer normally can't.
|
||||
var/mob/living/carbon/human/creator = null
|
||||
var/list/seen_mobs = list()
|
||||
|
||||
/mob/living/simple_animal/ward/death()
|
||||
if(creator)
|
||||
creator << "<span class='danger'>Your ward inside [get_area(src)] was killed!</span>"
|
||||
..()
|
||||
qdel(src)
|
||||
|
||||
/mob/living/simple_animal/ward/proc/expire()
|
||||
if(creator && src)
|
||||
creator << "<span class='warning'>Your ward inside [get_area(src)] expired.</span>"
|
||||
qdel(src)
|
||||
|
||||
/mob/living/simple_animal/ward/Life()
|
||||
..()
|
||||
detect_mobs()
|
||||
update_icon()
|
||||
|
||||
/mob/living/simple_animal/ward/proc/detect_mobs()
|
||||
var/list/things_in_sight = view(5,src)
|
||||
var/list/newly_seen_mobs = list()
|
||||
for(var/mob/living/L in things_in_sight)
|
||||
if(L == creator) // I really wish is_ally() was usable here.
|
||||
continue
|
||||
|
||||
if(istype(L, /mob/living/simple_animal/ward))
|
||||
continue
|
||||
|
||||
if(istype(L, /mob/living/simple_animal))
|
||||
var/mob/living/simple_animal/SA = L
|
||||
if(creator in SA.friends)
|
||||
continue
|
||||
|
||||
if(!true_sight)
|
||||
var/turf/T = get_turf(L)
|
||||
var/light_amount = T.get_lumcount()
|
||||
if(light_amount <= 0.5)
|
||||
continue // Too dark to see.
|
||||
|
||||
if(L.alpha <= 127)
|
||||
continue // Too transparent, as a mercy to camo lings.
|
||||
|
||||
else
|
||||
L.break_cloak()
|
||||
|
||||
// Warn the Technomancer when it sees a new mob.
|
||||
if(!(L in seen_mobs))
|
||||
seen_mobs.Add(L)
|
||||
newly_seen_mobs.Add(L)
|
||||
if(creator)
|
||||
if(true_sight)
|
||||
creator << "<span class='notice'>Your ward at [get_area(src)] detected [english_list(newly_seen_mobs)].</span>"
|
||||
else
|
||||
creator << "<span class='notice'>Your ward at [get_area(src)] detected something.</span>"
|
||||
|
||||
// Now get rid of old mobs that left vision.
|
||||
for(var/mob/living/L in seen_mobs)
|
||||
if(!(L in things_in_sight))
|
||||
seen_mobs.Remove(L)
|
||||
|
||||
|
||||
/mob/living/simple_animal/ward/update_icon()
|
||||
if(seen_mobs.len)
|
||||
icon_state = "ward_spotted"
|
||||
set_light(3, 3, l_color = "FF0000")
|
||||
else
|
||||
icon_state = "ward"
|
||||
set_light(3, 3, l_color = "00FF00")
|
||||
if(true_sight)
|
||||
overlays.Cut()
|
||||
var/image/I = image('icons/mob/critter.dmi',"ward_truesight")
|
||||
overlays.Add(I)
|
||||
|
||||
/mob/living/simple_animal/ward/invisible_detect
|
||||
true_sight = 1
|
||||
see_invisible = SEE_INVISIBLE_LEVEL_TWO
|
||||
/obj/item/weapon/spell/summon/summon_ward/on_summon(var/mob/living/simple_mob/mechanical/ward/monitor/my_ward)
|
||||
my_ward.owner = owner
|
||||
|
||||
@@ -99,6 +99,14 @@
|
||||
name = "Dark Gygax wreckage"
|
||||
icon_state = "darkgygax-broken"
|
||||
|
||||
/obj/effect/decal/mecha_wreckage/gygax/adv
|
||||
name = "Advanced Dark Gygax wreckage"
|
||||
icon_state = "darkgygax_adv-broken"
|
||||
|
||||
/obj/effect/decal/mecha_wreckage/gygax/medgax
|
||||
name = "Medgax wreckage"
|
||||
icon_state = "medgax-broken"
|
||||
|
||||
/obj/effect/decal/mecha_wreckage/marauder
|
||||
name = "Marauder wreckage"
|
||||
icon_state = "marauder-broken"
|
||||
@@ -198,6 +206,9 @@
|
||||
parts -= part
|
||||
return
|
||||
|
||||
/obj/effect/decal/mecha_wreckage/odysseus/murdysseus
|
||||
icon_state = "murdysseus-broken"
|
||||
|
||||
/obj/effect/decal/mecha_wreckage/hoverpod
|
||||
name = "Hover pod wreckage"
|
||||
icon_state = "engineering_pod-broken"
|
||||
|
||||
@@ -99,3 +99,13 @@
|
||||
mouse_opacity = FALSE
|
||||
anchored = TRUE
|
||||
plane = ABOVE_PLANE
|
||||
|
||||
// Similar to the tesla ball but doesn't actually do anything and is purely visual.
|
||||
/obj/effect/overlay/energy_ball
|
||||
name = "energy ball"
|
||||
desc = "An energy ball."
|
||||
icon = 'icons/obj/tesla_engine/energy_ball.dmi'
|
||||
icon_state = "energy_ball"
|
||||
plane = PLANE_LIGHTING_ABOVE
|
||||
pixel_x = -32
|
||||
pixel_y = -32
|
||||
|
||||
@@ -35,6 +35,12 @@
|
||||
deliveryamt = 5
|
||||
origin_tech = list(TECH_MATERIAL = 3, TECH_MAGNET = 4, TECH_ILLEGAL = 4)
|
||||
|
||||
/obj/item/weapon/grenade/spawnergrenade/manhacks/mercenary
|
||||
spawner_type = /mob/living/simple_mob/mechanical/viscerator/mercenary
|
||||
|
||||
/obj/item/weapon/grenade/spawnergrenade/manhacks/raider
|
||||
spawner_type = /mob/living/simple_mob/mechanical/viscerator/raider
|
||||
|
||||
/obj/item/weapon/grenade/spawnergrenade/spesscarp
|
||||
name = "carp delivery grenade"
|
||||
spawner_type = /mob/living/simple_mob/animal/space/carp
|
||||
|
||||
@@ -759,6 +759,15 @@ Loot piles can be depleted, if loot_depleted is turned on. Note that players wh
|
||||
/obj/item/mecha_parts/mecha_equipment/weapon/energy/laser/heavy
|
||||
)
|
||||
|
||||
/obj/structure/loot_pile/mecha/gygax/dark
|
||||
icon_state = "darkgygax-broken"
|
||||
|
||||
// Todo: Better loot.
|
||||
/obj/structure/loot_pile/mecha/gygax/dark/adv
|
||||
icon_state = "darkgygax_adv-broken"
|
||||
icon_scale = 1.5
|
||||
pixel_y = 8
|
||||
|
||||
/obj/structure/loot_pile/mecha/durand
|
||||
name = "durand wreckage"
|
||||
desc = "The ruins of some unfortunate durand. Perhaps something is salvageable."
|
||||
|
||||
@@ -14,6 +14,35 @@
|
||||
hostile = FALSE
|
||||
can_flee = TRUE
|
||||
|
||||
|
||||
// Doesn't really act until told to by something on the outside.
|
||||
/datum/ai_holder/simple_mob/inert
|
||||
hostile = FALSE
|
||||
retaliate = FALSE
|
||||
can_flee = FALSE
|
||||
wander = FALSE
|
||||
speak_chance = 0
|
||||
cooperative = FALSE
|
||||
|
||||
// Used for technomancer illusions, to resemble player movement better.
|
||||
/datum/ai_holder/simple_mob/inert/astar
|
||||
use_astar = TRUE
|
||||
|
||||
// Can't attack but calls for help. Used by the monitor and spotter wards.
|
||||
// Special attacks are not blocked since they might be used for things besides attacking, and can be conditional.
|
||||
/datum/ai_holder/simple_mob/monitor
|
||||
hostile = TRUE // Required to call for help.
|
||||
cooperative = TRUE
|
||||
stand_ground = TRUE // So it doesn't run up to the thing it sees.
|
||||
wander = FALSE
|
||||
can_flee = FALSE
|
||||
|
||||
/datum/ai_holder/simple_mob/monitor/melee_attack(atom/A)
|
||||
return FALSE
|
||||
|
||||
/datum/ai_holder/simple_mob/monitor/ranged_attack(atom/A)
|
||||
return FALSE
|
||||
|
||||
// Ranged mobs.
|
||||
|
||||
/datum/ai_holder/simple_mob/ranged
|
||||
@@ -27,15 +56,30 @@
|
||||
/datum/ai_holder/simple_mob/ranged/kiting
|
||||
pointblank = TRUE // So we don't need to copypaste post_melee_attack().
|
||||
var/run_if_this_close = 4 // If anything gets within this range, it'll try to move away.
|
||||
var/moonwalk = TRUE // If true, mob turns to face the target while kiting, otherwise they turn in the direction they moved towards.
|
||||
|
||||
/datum/ai_holder/simple_mob/ranged/kiting/threatening
|
||||
threaten = TRUE
|
||||
threaten_delay = 1 SECOND // Less of a threat and more of pre-attack notice.
|
||||
threaten_timeout = 30 SECONDS
|
||||
|
||||
/datum/ai_holder/simple_mob/ranged/kiting/post_ranged_attack(atom/A)
|
||||
/datum/ai_holder/simple_mob/ranged/kiting/no_moonwalk
|
||||
moonwalk = FALSE
|
||||
|
||||
/datum/ai_holder/simple_mob/ranged/kiting/on_engagement(atom/A)
|
||||
if(get_dist(holder, A) < run_if_this_close)
|
||||
holder.IMove(get_step_away(holder, A, run_if_this_close))
|
||||
if(moonwalk)
|
||||
holder.face_atom(A)
|
||||
|
||||
// Closes distance from the target even while in range.
|
||||
/datum/ai_holder/simple_mob/ranged/aggressive
|
||||
pointblank = TRUE
|
||||
var/closest_distance = 1 // How close to get to the target. By default they will get into melee range (and then pointblank them).
|
||||
|
||||
/datum/ai_holder/simple_mob/ranged/aggressive/on_engagement(atom/A)
|
||||
if(get_dist(holder, A) > closest_distance)
|
||||
holder.IMove(get_step_towards(holder, A))
|
||||
holder.face_atom(A)
|
||||
|
||||
// The electric spider's AI.
|
||||
@@ -48,8 +92,84 @@
|
||||
return 1
|
||||
return ..() // Do ranged if possible otherwise.
|
||||
|
||||
// Switches intents based on specific criteria.
|
||||
// Used for special mobs who do different things based on intents (and aren't slimes).
|
||||
// Intent switching is generally done in pre_[ranged/special]_attack(), so that the mob can use the right attack for the right time.
|
||||
/datum/ai_holder/simple_mob/intentional
|
||||
|
||||
|
||||
// The Advanced Dark Gygax's AI.
|
||||
// The mob has three special attacks, based on the current intent.
|
||||
// This AI choose the appropiate intent for the situation, and tries to ensure it doesn't kill itself by firing missiles at its feet.
|
||||
/datum/ai_holder/simple_mob/intentional/adv_dark_gygax
|
||||
var/closest_safest_distance = 3 // Stay this many tiles away when using missiles or mini-singulo to avoid hitting ourselves.
|
||||
var/closest_desired_distance = 2 // Otherwise run up to them to be able to potentially shock or punch them.
|
||||
var/electric_defense_radius = 3 // How big to assume electric defense's area is.
|
||||
var/microsingulo_radius = 3 // Same but for microsingulo pull.
|
||||
var/rocket_explosive_radius = 2 // Explosion radius for the rockets.
|
||||
|
||||
// Used to control the mob's positioning based on which special attack it has done.
|
||||
// Note that the intent will not change again until the next special attack is about to happen.
|
||||
/datum/ai_holder/simple_mob/intentional/adv_dark_gygax/on_engagement(atom/A)
|
||||
// Make the AI backpeddle if using an AoE special attack.
|
||||
var/list/risky_intents = list(I_GRAB, I_HURT) // Mini-singulo and missiles.
|
||||
var/closest_distance = 1
|
||||
if(holder.a_intent in risky_intents)
|
||||
switch(holder.a_intent) // Plus one just in case.
|
||||
if(I_HURT)
|
||||
closest_distance = rocket_explosive_radius + 1
|
||||
if(I_GRAB)
|
||||
closest_distance = microsingulo_radius + 1
|
||||
|
||||
if(get_dist(holder, A) <= closest_safest_distance)
|
||||
holder.IMove(get_step_away(holder, A, closest_safest_distance))
|
||||
|
||||
// Otherwise get up close and personal.
|
||||
else if(get_dist(holder, A) > closest_desired_distance)
|
||||
holder.IMove(get_step_towards(holder, A))
|
||||
|
||||
// Changes the mob's intent, which controls which special attack is used.
|
||||
// I_DISARM causes Electric Defense, I_GRAB causes Micro-Singularity, and I_HURT causes Missile Barrage.
|
||||
/datum/ai_holder/simple_mob/intentional/adv_dark_gygax/pre_special_attack(atom/A)
|
||||
if(isliving(A))
|
||||
var/mob/living/target = A
|
||||
|
||||
// If we're surrounded, Electric Defense will quickly fix that.
|
||||
var/tally = 0
|
||||
for(var/mob/living/L in hearers(electric_defense_radius, holder))
|
||||
if(holder == L)
|
||||
continue
|
||||
if(L.IIsAlly(holder))
|
||||
continue
|
||||
if(L.stat)
|
||||
continue
|
||||
tally++
|
||||
|
||||
// Should we shock them?
|
||||
if(tally >= 2 || get_dist(target, holder) <= electric_defense_radius)
|
||||
holder.a_intent = I_DISARM
|
||||
return
|
||||
|
||||
// Otherwise they're a fair distance away and we're not getting mobbed up close.
|
||||
// See if we should use missiles or microsingulo.
|
||||
tally = 0 // Let's recycle the var.
|
||||
for(var/mob/living/L in hearers(3, target))
|
||||
if(holder == L)
|
||||
continue
|
||||
if(L.IIsAlly(holder))
|
||||
continue
|
||||
if(L.stat)
|
||||
continue
|
||||
tally++
|
||||
|
||||
// Lots of people means minisingulo would be more useful.
|
||||
if(tally >= 2)
|
||||
holder.a_intent = I_GRAB
|
||||
else // Otherwise use rockets.
|
||||
holder.a_intent = I_HURT
|
||||
|
||||
else
|
||||
holder.a_intent = I_HURT // Fire rockets if it's an obj/turf.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -4,15 +4,11 @@
|
||||
var/firing_lanes = FALSE // If ture, tries to refrain from shooting allies or the wall.
|
||||
var/conserve_ammo = FALSE // If true, the mob will avoid shooting anything that does not have a chance to hit a mob. Requires firing_lanes to be true.
|
||||
|
||||
// var/ranged = FALSE // If true, attempts to shoot at the enemy instead of charging at them wildly.
|
||||
var/shoot_range = 5 // How close the mob needs to be to attempt to shoot at the enemy, if the mob is capable of ranged attacks.
|
||||
var/pointblank = FALSE // If ranged is true, and this is true, people adjacent to the mob will suffer the ranged instead of using a melee attack.
|
||||
|
||||
var/special_attack_prob = 0 // The chance to ATTEMPT a special_attack(). If it fails, it will do a regular attack instead.
|
||||
var/special_attack_min_range = 2 // The minimum distance required for an attempt to be made.
|
||||
var/special_attack_max_range = 7 // The maximum for an attempt.
|
||||
|
||||
var/can_breakthrough = TRUE // If false, the AI will not try to break things like windows or other structures in the way.
|
||||
var/stand_ground = FALSE // If true, the AI won't try to get closer to an enemy if out of range.
|
||||
|
||||
|
||||
// This does the actual attacking.
|
||||
@@ -83,7 +79,7 @@
|
||||
ranged_attack(target)
|
||||
|
||||
// Run after them.
|
||||
else
|
||||
else if(!stand_ground)
|
||||
ai_log("engage_target() : Target ([target]) too far away. Exiting.", AI_LOG_DEBUG)
|
||||
set_stance(STANCE_APPROACH)
|
||||
|
||||
@@ -103,6 +99,7 @@
|
||||
|
||||
// Most mobs probably won't have this defined but we don't care.
|
||||
/datum/ai_holder/proc/special_attack(atom/movable/AM)
|
||||
pre_special_attack(AM)
|
||||
. = holder.ISpecialAttack(AM)
|
||||
if(.)
|
||||
post_special_attack(AM)
|
||||
@@ -118,6 +115,9 @@
|
||||
// Called before a melee attack is attempted.
|
||||
/datum/ai_holder/proc/pre_melee_attack(atom/A)
|
||||
|
||||
// Called before a 'special' attack is attempted.
|
||||
/datum/ai_holder/proc/pre_special_attack(atom/A)
|
||||
|
||||
// Called after a successful (IE not on cooldown) ranged attack.
|
||||
// Note that this is not whether the projectile actually hit, just that one was launched.
|
||||
/datum/ai_holder/proc/post_ranged_attack(atom/A)
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
var/spore_cooldown = 8 SECONDS
|
||||
|
||||
/obj/structure/blob/factory/Destroy()
|
||||
for(var/mob/living/simple_animal/hostile/blob/spore/spore in spores)
|
||||
for(var/mob/living/simple_mob/blob/spore/spore in spores)
|
||||
if(istype(spore) && spore.factory == src)
|
||||
spore.factory = null
|
||||
else
|
||||
@@ -31,7 +31,7 @@
|
||||
return
|
||||
flick("blob_factory_glow", src)
|
||||
spore_delay = world.time + spore_cooldown
|
||||
var/mob/living/simple_animal/hostile/blob/spore/S = null
|
||||
var/mob/living/simple_mob/blob/spore/S = null
|
||||
if(overmind)
|
||||
S = new overmind.blob_type.spore_type(src.loc, src)
|
||||
S.faction = "blob"
|
||||
@@ -39,10 +39,8 @@
|
||||
S.overmind = overmind
|
||||
overmind.blob_mobs.Add(S)
|
||||
if(overmind.blob_type.ranged_spores)
|
||||
S.ranged = TRUE
|
||||
S.projectiletype = overmind.blob_type.spore_projectile
|
||||
S.projectilesound = overmind.blob_type.spore_firesound
|
||||
S.shoot_range = overmind.blob_type.spore_range
|
||||
else //Other mobs don't add themselves in New. Ew.
|
||||
S.nest = src
|
||||
spores += S
|
||||
|
||||
@@ -53,7 +53,7 @@ var/list/overminds = list()
|
||||
B.update_icon() //reset anything that was ours
|
||||
|
||||
for(var/BLO in blob_mobs)
|
||||
var/mob/living/simple_animal/hostile/blob/BM = BLO
|
||||
var/mob/living/simple_mob/blob/spore/BM = BLO
|
||||
if(BM)
|
||||
BM.overmind = null
|
||||
BM.update_icons()
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
var/can_build_resources = FALSE // Ditto, for resource blobs.
|
||||
var/can_build_nodes = TRUE // Ditto, for nodes.
|
||||
|
||||
var/spore_type = /mob/living/simple_animal/hostile/blob/spore
|
||||
var/spore_type = /mob/living/simple_mob/blob/spore
|
||||
var/ranged_spores = FALSE // For proper spores of the type above.
|
||||
var/spore_firesound = 'sound/effects/slime_squish.ogg'
|
||||
var/spore_range = 7 // The range the spore can fire.
|
||||
@@ -227,9 +227,9 @@
|
||||
burn_multiplier = 3
|
||||
ai_aggressiveness = 40
|
||||
can_build_factories = TRUE
|
||||
spore_type = /mob/living/simple_animal/hostile/blob/spore/infesting
|
||||
spore_type = /mob/living/simple_mob/blob/spore/infesting
|
||||
|
||||
/datum/blob_type/fungal_bloom/on_spore_death(mob/living/simple_animal/hostile/blob/spore/S)
|
||||
/datum/blob_type/fungal_bloom/on_spore_death(mob/living/simple_mob/blob/spore/S)
|
||||
if(S.is_infesting)
|
||||
return // Don't make blobs if they were on someone's head.
|
||||
var/turf/T = get_turf(S)
|
||||
@@ -258,11 +258,11 @@
|
||||
brute_multiplier = 1.5
|
||||
ai_aggressiveness = 30 // The spores do most of the fighting.
|
||||
can_build_factories = TRUE
|
||||
spore_type = /mob/living/simple_animal/hostile/blob/spore/weak
|
||||
spore_type = /mob/living/simple_mob/blob/spore/weak
|
||||
|
||||
/datum/blob_type/fulminant_organism/on_expand(var/obj/structure/blob/B, var/obj/structure/blob/new_B, var/turf/T, var/mob/observer/blob/O)
|
||||
if(prob(10)) // 10% chance to make a weak spore when expanding.
|
||||
var/mob/living/simple_animal/hostile/blob/S = new spore_type(T)
|
||||
var/mob/living/simple_mob/blob/spore/S = new spore_type(T)
|
||||
if(istype(S))
|
||||
S.overmind = O
|
||||
O.blob_mobs.Add(S)
|
||||
@@ -272,7 +272,7 @@
|
||||
|
||||
/datum/blob_type/fulminant_organism/on_death(obj/structure/blob/B)
|
||||
if(prob(33)) // 33% chance to make a spore when dying.
|
||||
var/mob/living/simple_animal/hostile/blob/S = new spore_type(get_turf(B))
|
||||
var/mob/living/simple_mob/blob/spore/S = new spore_type(get_turf(B))
|
||||
B.visible_message("<span class='danger'>\The [S] floats free from the [name]!</span>")
|
||||
if(istype(S))
|
||||
S.overmind = B.overmind
|
||||
@@ -614,7 +614,7 @@
|
||||
attack_verb = "crashes against"
|
||||
can_build_factories = TRUE
|
||||
can_build_resources = TRUE
|
||||
spore_type = /mob/living/simple_animal/hostile/blob/spore/weak
|
||||
spore_type = /mob/living/simple_mob/blob/spore/weak
|
||||
ranged_spores = TRUE
|
||||
spore_range = 3
|
||||
spore_projectile = /obj/item/projectile/energy/blob/splattering
|
||||
|
||||
@@ -149,6 +149,13 @@
|
||||
/mob/living/proc/remove_specific_modifier(var/datum/modifier/M, var/silent = FALSE)
|
||||
M.expire(silent)
|
||||
|
||||
// Removes one modifier of a type
|
||||
/mob/living/proc/remove_a_modifier_of_type(var/modifier_type, var/silent = FALSE)
|
||||
for(var/datum/modifier/M in modifiers)
|
||||
if(ispath(M.type, modifier_type))
|
||||
M.expire(silent)
|
||||
break
|
||||
|
||||
// Removes all modifiers of a type
|
||||
/mob/living/proc/remove_modifiers_of_type(var/modifier_type, var/silent = FALSE)
|
||||
for(var/datum/modifier/M in modifiers)
|
||||
|
||||
@@ -214,4 +214,36 @@ the artifact triggers the rage.
|
||||
slowdown = 2
|
||||
evasion = -40
|
||||
attack_speed_percent = 1.4
|
||||
disable_duration_percent = 1.2
|
||||
disable_duration_percent = 1.2
|
||||
|
||||
|
||||
// Similar to being on fire, except poison tends to be more long term.
|
||||
// Antitoxins will remove stacks over time.
|
||||
// Synthetics can't receive this.
|
||||
/datum/modifier/poisoned
|
||||
name = "poisoned"
|
||||
desc = "You have poison inside of you. It will cause harm over a long span of time if not cured."
|
||||
mob_overlay_state = "poisoned"
|
||||
|
||||
on_created_text = "<span class='warning'>You feel sick...</span>"
|
||||
on_expired_text = "<span class='notice'>You feel a bit better.</span>"
|
||||
stacks = MODIFIER_STACK_ALLOWED // Multiple instances will hurt a lot.
|
||||
var/damage_per_tick = 1
|
||||
|
||||
/datum/modifier/poisoned/weak
|
||||
damage_per_tick = 0.5
|
||||
|
||||
/datum/modifier/poisoned/strong
|
||||
damage_per_tick = 2
|
||||
|
||||
/datum/modifier/poisoned/tick()
|
||||
if(holder.stat == DEAD)
|
||||
expire(silent = TRUE)
|
||||
holder.inflict_poison_damage(damage_per_tick)
|
||||
|
||||
/datum/modifier/poisoned/can_apply(mob/living/L)
|
||||
if(L.isSynthetic())
|
||||
return FALSE
|
||||
if(L.get_poison_protection() >= 1)
|
||||
return FALSE
|
||||
return TRUE
|
||||
@@ -526,7 +526,7 @@ default behaviour is:
|
||||
|
||||
// and one for electricity because why not
|
||||
/mob/living/proc/inflict_shock_damage(amount)
|
||||
electrocute_act(amount, null, get_shock_protection())
|
||||
electrocute_act(amount, null, 1 - get_shock_protection())
|
||||
|
||||
// also one for water (most things resist it entirely, except for slimes)
|
||||
/mob/living/proc/inflict_water_damage(amount)
|
||||
@@ -534,6 +534,14 @@ default behaviour is:
|
||||
if(amount > 0)
|
||||
adjustToxLoss(amount)
|
||||
|
||||
// one for abstracted away ""poison"" (mostly because simplemobs shouldn't handle reagents)
|
||||
/mob/living/proc/inflict_poison_damage(amount)
|
||||
if(isSynthetic())
|
||||
return
|
||||
amount *= 1 - get_poison_protection()
|
||||
if(amount > 0)
|
||||
adjustToxLoss(amount)
|
||||
|
||||
/mob/proc/get_contents()
|
||||
|
||||
|
||||
|
||||
@@ -432,6 +432,9 @@
|
||||
/mob/living/proc/get_water_protection()
|
||||
return 1 // Water won't hurt most things.
|
||||
|
||||
/mob/living/proc/get_poison_protection()
|
||||
return 0
|
||||
|
||||
//Finds the effective temperature that the mob is burning at.
|
||||
/mob/living/proc/fire_burn_temperature()
|
||||
if (fire_stacks <= 0)
|
||||
|
||||
@@ -1,45 +1,46 @@
|
||||
/mob/living/simple_mob/update_icon()
|
||||
. = ..()
|
||||
var/mutable_appearance/ma = new(src)
|
||||
ma.layer = layer
|
||||
ma.plane = plane
|
||||
cut_overlays()
|
||||
// var/mutable_appearance/ma = new(src)
|
||||
// ma.layer = layer
|
||||
// ma.plane = plane
|
||||
|
||||
ma.overlays = list(modifier_overlay)
|
||||
add_overlay(modifier_overlay)
|
||||
|
||||
//Awake and normal
|
||||
if((stat == CONSCIOUS) && (!icon_rest || !resting || !incapacitated(INCAPACITATION_DISABLED) ))
|
||||
ma.icon_state = icon_living
|
||||
icon_state = icon_living
|
||||
|
||||
//Dead
|
||||
else if(stat >= DEAD)
|
||||
ma.icon_state = icon_dead
|
||||
icon_state = icon_dead
|
||||
|
||||
//Resting or KO'd
|
||||
else if(((stat == UNCONSCIOUS) || resting || incapacitated(INCAPACITATION_DISABLED) ) && icon_rest)
|
||||
ma.icon_state = icon_rest
|
||||
icon_state = icon_rest
|
||||
|
||||
//Backup
|
||||
else
|
||||
ma.icon_state = initial(icon_state)
|
||||
icon_state = initial(icon_state)
|
||||
|
||||
if(has_hands)
|
||||
if(r_hand_sprite)
|
||||
ma.overlays += r_hand_sprite
|
||||
add_overlay(r_hand_sprite)
|
||||
if(l_hand_sprite)
|
||||
ma.overlays += l_hand_sprite
|
||||
add_overlay(l_hand_sprite)
|
||||
|
||||
if(has_eye_glow)
|
||||
add_eyes()
|
||||
|
||||
appearance = ma
|
||||
// appearance = ma
|
||||
|
||||
|
||||
// If your simple mob's update_icon() call calls overlays.Cut(), this needs to be called after this, or manually apply modifier_overly to overlays.
|
||||
/mob/living/simple_mob/update_modifier_visuals()
|
||||
var/image/effects = null
|
||||
if(modifier_overlay)
|
||||
overlays -= modifier_overlay
|
||||
modifier_overlay.overlays.Cut()
|
||||
cut_overlay(modifier_overlay)
|
||||
modifier_overlay.cut_overlays()
|
||||
effects = modifier_overlay
|
||||
else
|
||||
effects = new()
|
||||
@@ -48,10 +49,10 @@
|
||||
if(M.mob_overlay_state)
|
||||
var/image/I = image("icon" = 'icons/mob/modifier_effects.dmi', "icon_state" = M.mob_overlay_state)
|
||||
I.appearance_flags = RESET_COLOR // So colored mobs don't affect the overlay.
|
||||
effects.overlays += I
|
||||
effects.add_overlay(I)
|
||||
|
||||
modifier_overlay = effects
|
||||
overlays += modifier_overlay
|
||||
add_overlay(modifier_overlay)
|
||||
|
||||
|
||||
/mob/living/simple_mob/proc/add_eyes()
|
||||
@@ -59,10 +60,10 @@
|
||||
eye_layer = image(icon, "[icon_state]-eyes")
|
||||
eye_layer.plane = PLANE_LIGHTING_ABOVE
|
||||
|
||||
overlays += eye_layer
|
||||
add_overlay(eye_layer)
|
||||
|
||||
/mob/living/simple_mob/proc/remove_eyes()
|
||||
overlays -= eye_layer
|
||||
cut_overlay(eye_layer)
|
||||
|
||||
|
||||
/mob/living/simple_mob/gib()
|
||||
|
||||
@@ -8,17 +8,17 @@
|
||||
|
||||
face_atom(A)
|
||||
|
||||
if(attack_delay)
|
||||
if(melee_attack_delay)
|
||||
// their_T.color = "#FF0000"
|
||||
melee_pre_animation(A)
|
||||
handle_attack_delay(A) // This will sleep this proc for a bit, which is why waitfor is false.
|
||||
handle_attack_delay(A, melee_attack_delay) // This will sleep this proc for a bit, which is why waitfor is false.
|
||||
|
||||
// Cooldown testing is done at click code (for players) and interface code (for AI).
|
||||
setClickCooldown(get_attack_speed())
|
||||
|
||||
. = do_attack(A, their_T)
|
||||
|
||||
if(attack_delay)
|
||||
if(melee_attack_delay)
|
||||
melee_post_animation(A)
|
||||
// their_T.color = "#FFFFFF"
|
||||
|
||||
@@ -92,9 +92,9 @@
|
||||
|
||||
face_atom(A)
|
||||
|
||||
if(attack_delay)
|
||||
if(ranged_attack_delay)
|
||||
ranged_pre_animation(A)
|
||||
handle_attack_delay(A) // This will sleep this proc for a bit, which is why waitfor is false.
|
||||
handle_attack_delay(A, ranged_attack_delay) // This will sleep this proc for a bit, which is why waitfor is false.
|
||||
|
||||
if(needs_reload)
|
||||
if(reload_count >= reload_max)
|
||||
@@ -104,9 +104,9 @@
|
||||
visible_message("<span class='danger'><b>\The [src]</b> fires at \the [A]!</span>")
|
||||
shoot(A, src.loc, src)
|
||||
if(casingtype)
|
||||
new casingtype
|
||||
new casingtype(loc)
|
||||
|
||||
if(attack_delay)
|
||||
if(ranged_attack_delay)
|
||||
ranged_post_animation(A)
|
||||
|
||||
return TRUE
|
||||
@@ -135,13 +135,17 @@
|
||||
// return TRUE
|
||||
|
||||
/mob/living/simple_mob/proc/try_reload()
|
||||
set waitfor = FALSE
|
||||
set_AI_busy(TRUE)
|
||||
|
||||
if(do_after(src, reload_time))
|
||||
if(reload_sound)
|
||||
playsound(src.loc, reload_sound, 50, 1)
|
||||
reload_count = 0
|
||||
return TRUE
|
||||
. = TRUE
|
||||
else
|
||||
return FALSE
|
||||
. = FALSE
|
||||
set_AI_busy(FALSE)
|
||||
|
||||
// Can we currently do a special attack?
|
||||
/mob/living/simple_mob/proc/can_special_attack(atom/A)
|
||||
@@ -176,12 +180,21 @@
|
||||
// Don't override this, override do_special_attack() for your blinding spit/etc.
|
||||
/mob/living/simple_mob/proc/special_attack_target(atom/A)
|
||||
face_atom(A)
|
||||
|
||||
if(special_attack_delay)
|
||||
special_pre_animation(A)
|
||||
handle_attack_delay(A, special_attack_delay) // This will sleep this proc for a bit, which is why waitfor is false.
|
||||
|
||||
last_special_attack = world.time
|
||||
if(do_special_attack(A))
|
||||
if(special_attack_charges)
|
||||
special_attack_charges -= 1
|
||||
return TRUE
|
||||
return FALSE
|
||||
. = TRUE
|
||||
else
|
||||
. = FALSE
|
||||
|
||||
if(special_attack_delay)
|
||||
special_post_animation(A)
|
||||
|
||||
// Override this for the actual special attack.
|
||||
/mob/living/simple_mob/proc/do_special_attack(atom/A)
|
||||
@@ -189,12 +202,12 @@
|
||||
|
||||
// Sleeps the proc that called it for the correct amount of time.
|
||||
// Also makes sure the AI doesn't do anything stupid in the middle of the delay.
|
||||
/mob/living/simple_mob/proc/handle_attack_delay(atom/A)
|
||||
/mob/living/simple_mob/proc/handle_attack_delay(atom/A, delay_amount)
|
||||
set_AI_busy(TRUE)
|
||||
|
||||
// Click delay modifiers also affect telegraphing time.
|
||||
// This means berserked enemies will leave less time to dodge.
|
||||
var/true_attack_delay = attack_delay
|
||||
var/true_attack_delay = delay_amount
|
||||
for(var/datum/modifier/M in modifiers)
|
||||
if(!isnull(M.attack_speed_percent))
|
||||
true_attack_delay *= M.attack_speed_percent
|
||||
@@ -208,11 +221,16 @@
|
||||
|
||||
// Override these four for special custom animations (like the GOLEM).
|
||||
/mob/living/simple_mob/proc/melee_pre_animation(atom/A)
|
||||
do_windup_animation(A, attack_delay)
|
||||
do_windup_animation(A, melee_attack_delay)
|
||||
|
||||
/mob/living/simple_mob/proc/melee_post_animation(atom/A)
|
||||
|
||||
/mob/living/simple_mob/proc/ranged_pre_animation(atom/A)
|
||||
do_windup_animation(A, attack_delay) // Semi-placeholder.
|
||||
do_windup_animation(A, ranged_attack_delay) // Semi-placeholder.
|
||||
|
||||
/mob/living/simple_mob/proc/ranged_post_animation(atom/A)
|
||||
/mob/living/simple_mob/proc/ranged_post_animation(atom/A)
|
||||
|
||||
/mob/living/simple_mob/proc/special_pre_animation(atom/A)
|
||||
do_windup_animation(A, special_attack_delay) // Semi-placeholder.
|
||||
|
||||
/mob/living/simple_mob/proc/special_post_animation(atom/A)
|
||||
@@ -184,6 +184,10 @@
|
||||
/mob/living/simple_mob/get_water_protection()
|
||||
return water_resist
|
||||
|
||||
// "Poison" (aka what reagents would do if we wanted to deal with those).
|
||||
/mob/living/simple_mob/get_poison_protection()
|
||||
return poison_resist
|
||||
|
||||
// Armor
|
||||
/mob/living/simple_mob/getarmor(def_zone, attack_flag)
|
||||
var/armorval = armor[attack_flag]
|
||||
@@ -212,3 +216,9 @@
|
||||
ash()
|
||||
return // No point deafening something that wont exist.
|
||||
|
||||
// Injections.
|
||||
/mob/living/simple_mob/can_inject(mob/user, error_msg, target_zone, ignore_thickness)
|
||||
if(ignore_thickness)
|
||||
return TRUE
|
||||
return !thick_armor
|
||||
|
||||
|
||||
@@ -43,6 +43,8 @@
|
||||
//Movement things.
|
||||
var/movement_cooldown = 5 // Lower is faster.
|
||||
var/movement_sound = null // If set, will play this sound when it moves on its own will.
|
||||
var/turn_sound = null // If set, plays the sound when the mob's dir changes in most cases.
|
||||
var/movement_shake_radius = 0 // If set, moving will shake the camera of all living mobs within this radius slightly.
|
||||
|
||||
//Mob interaction
|
||||
var/response_help = "tries to help" // If clicked on help intent
|
||||
@@ -98,7 +100,10 @@
|
||||
var/attack_armor_pen = 0 // How much armor pen this attack has.
|
||||
var/attack_sharp = FALSE // Is the attack sharp?
|
||||
var/attack_edge = FALSE // Does the attack have an edge?
|
||||
var/attack_delay = null // If set, the mob will do a windup animation and can miss if the target moves out of the way.
|
||||
|
||||
var/melee_attack_delay = null // If set, the mob will do a windup animation and can miss if the target moves out of the way.
|
||||
var/ranged_attack_delay = null
|
||||
var/special_attack_delay = null
|
||||
|
||||
//Special attacks
|
||||
// var/special_attack_prob = 0 // The chance to ATTEMPT a special_attack_target(). If it fails, it will do a regular attack instead.
|
||||
@@ -137,6 +142,8 @@
|
||||
var/cold_resist = 0.0
|
||||
var/shock_resist = 0.0
|
||||
var/water_resist = 1.0
|
||||
var/poison_resist = 0.0
|
||||
var/thick_armor = FALSE // Stops injections and "injections".
|
||||
var/purge = 0 // Cult stuff.
|
||||
var/supernatural = FALSE // Ditto.
|
||||
|
||||
@@ -152,6 +159,8 @@
|
||||
|
||||
if(has_eye_glow)
|
||||
add_eyes()
|
||||
if(!icon_living)
|
||||
icon_living = icon_state
|
||||
return ..()
|
||||
|
||||
|
||||
@@ -182,10 +191,21 @@
|
||||
|
||||
/mob/living/simple_mob/SelfMove(turf/n, direct)
|
||||
var/turf/old_turf = get_turf(src)
|
||||
var/old_dir = dir
|
||||
. = ..()
|
||||
if(movement_sound && old_turf != get_turf(src))
|
||||
if(. && movement_shake_radius)
|
||||
for(var/mob/living/L in range(movement_shake_radius, src))
|
||||
shake_camera(L, 1, 1)
|
||||
if(turn_sound && dir != old_dir)
|
||||
playsound(src, turn_sound, 50, 1)
|
||||
else if(movement_sound && old_turf != get_turf(src)) // Playing both sounds at the same time generally sounds bad.
|
||||
playsound(src, movement_sound, 50, 1)
|
||||
|
||||
/*
|
||||
/mob/living/simple_mob/set_dir(new_dir)
|
||||
if(dir != new_dir)
|
||||
playsound(src, turn_sound, 50, 1)
|
||||
return ..()
|
||||
*/
|
||||
/mob/living/simple_mob/movement_delay()
|
||||
var/tally = 0 //Incase I need to add stuff other than "speed" later
|
||||
|
||||
@@ -255,16 +275,7 @@
|
||||
|
||||
|
||||
/mob/living/simple_mob/is_sentient()
|
||||
return mob_class & MOB_CLASS_HUMANOID|MOB_CLASS_ANIMAL // Update this if needed.
|
||||
// return intelligence_level != SA_PLANT && intelligence_level != SA_ROBOTIC
|
||||
|
||||
//Just some subpaths for easy searching
|
||||
/mob/living/simple_mob/hostile
|
||||
faction = "not yours"
|
||||
// ai_holder_type = /datum/ai_holder/regular/hostile
|
||||
|
||||
/mob/living/simple_mob/retaliate
|
||||
// ai_holder_type = /datum/ai_holder/regular/retaliate
|
||||
return mob_class & MOB_CLASS_HUMANOID|MOB_CLASS_ANIMAL|MOB_CLASS_SLIME // Update this if needed.
|
||||
|
||||
/mob/living/simple_mob/get_nametag_desc(mob/user)
|
||||
return "<i>[tt_desc]</i>"
|
||||
@@ -19,6 +19,7 @@
|
||||
health = 200
|
||||
pass_flags = PASSTABLE
|
||||
movement_cooldown = 10
|
||||
poison_resist = 0.5
|
||||
|
||||
see_in_dark = 10
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
attack_armor_pen = 35
|
||||
attack_sharp = 1
|
||||
attack_edge = 1
|
||||
attack_delay = 1 SECOND
|
||||
melee_attack_delay = 1 SECOND
|
||||
|
||||
meat_type = /obj/item/weapon/reagent_containers/food/snacks/meat
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
attack_armor_pen = 15
|
||||
attack_sharp = TRUE
|
||||
attack_edge = TRUE
|
||||
attack_delay = 1 SECOND
|
||||
melee_attack_delay = 1 SECOND
|
||||
attacktext = list("mauled")
|
||||
|
||||
say_list_type = /datum/say_list/savik
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
melee_damage_lower = 6
|
||||
melee_damage_upper = 14
|
||||
base_attack_cooldown = 1 SECOND
|
||||
attack_delay = 0.5 SECONDS
|
||||
melee_attack_delay = 0.5 SECONDS
|
||||
attack_armor_pen = 5
|
||||
attack_sharp = TRUE
|
||||
attack_edge = TRUE
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
attack_armor_pen = 15
|
||||
attack_sharp = TRUE
|
||||
attack_edge = TRUE
|
||||
attack_delay = 1 SECOND
|
||||
melee_attack_delay = 1 SECOND
|
||||
attacktext = list("mauled")
|
||||
|
||||
meat_type = /obj/item/weapon/reagent_containers/food/snacks/bearmeat
|
||||
|
||||
62
code/modules/mob/living/simple_mob/subtypes/blob/blob.dm
Normal file
@@ -0,0 +1,62 @@
|
||||
// Blob simple_mobs generally get made from the blob random event.
|
||||
// They're considered slimes for the purposes of attack bonuses from certain weapons.
|
||||
|
||||
// Do not spawn, this is a base type.
|
||||
/mob/living/simple_mob/blob
|
||||
icon = 'icons/mob/blob.dmi'
|
||||
pass_flags = PASSBLOB | PASSTABLE
|
||||
faction = "blob"
|
||||
|
||||
heat_damage_per_tick = 0
|
||||
cold_damage_per_tick = 0
|
||||
min_oxy = 0
|
||||
max_oxy = 0
|
||||
min_tox = 0
|
||||
max_tox = 0
|
||||
min_co2 = 0
|
||||
max_co2 = 0
|
||||
min_n2 = 0
|
||||
max_n2 = 0
|
||||
minbodytemp = 0
|
||||
|
||||
taser_kill = FALSE
|
||||
|
||||
var/mob/observer/blob/overmind = null
|
||||
var/obj/structure/blob/factory/factory = null
|
||||
|
||||
mob_class = MOB_CLASS_SLIME
|
||||
ai_holder_type = /datum/ai_holder/simple_mob/melee
|
||||
|
||||
/mob/living/simple_mob/blob/speech_bubble_appearance()
|
||||
return "slime"
|
||||
|
||||
/mob/living/simple_mob/blob/update_icons()
|
||||
if(overmind)
|
||||
color = overmind.blob_type.complementary_color
|
||||
else
|
||||
color = null
|
||||
..()
|
||||
|
||||
/mob/living/simple_mob/blob/Destroy()
|
||||
if(overmind)
|
||||
overmind.blob_mobs -= src
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_mob/blob/blob_act(obj/structure/blob/B)
|
||||
if(!overmind && B.overmind)
|
||||
overmind = B.overmind
|
||||
update_icon()
|
||||
|
||||
if(stat != DEAD && health < maxHealth)
|
||||
adjustBruteLoss(-maxHealth*0.0125)
|
||||
adjustFireLoss(-maxHealth*0.0125)
|
||||
|
||||
/mob/living/simple_mob/blob/CanPass(atom/movable/mover, turf/target)
|
||||
if(istype(mover, /obj/structure/blob)) // Don't block blobs from expanding onto a tile occupied by a blob mob.
|
||||
return TRUE
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_mob/blob/Process_Spacemove()
|
||||
for(var/obj/structure/blob/B in range(1, src))
|
||||
return TRUE
|
||||
return ..()
|
||||
148
code/modules/mob/living/simple_mob/subtypes/blob/spore.dm
Normal file
@@ -0,0 +1,148 @@
|
||||
// Spores are made from blob factories.
|
||||
// They are very weak and expendable, but can overwhelm when a lot of them are together.
|
||||
// When attacking, spores will hit harder if near other friendly spores.
|
||||
// Some blobs can infest dead non-robotic mobs, making them into Not Zombies.
|
||||
|
||||
/mob/living/simple_mob/blob/spore
|
||||
name = "blob spore"
|
||||
desc = "A floating, fragile spore."
|
||||
|
||||
icon_state = "blobpod"
|
||||
icon_living = "blobpod"
|
||||
glow_range = 3
|
||||
glow_intensity = 5
|
||||
layer = ABOVE_MOB_LAYER // Over the blob.
|
||||
|
||||
health = 30
|
||||
maxHealth = 30
|
||||
melee_damage_lower = 2
|
||||
melee_damage_upper = 4
|
||||
movement_cooldown = 0
|
||||
hovering = TRUE
|
||||
|
||||
attacktext = list("slams into")
|
||||
attack_sound = 'sound/effects/slime_squish.ogg'
|
||||
say_list_type = /datum/say_list/spore
|
||||
|
||||
var/mob/living/carbon/human/infested = null // The human this thing is totally not making into a zombie.
|
||||
var/can_infest = FALSE
|
||||
var/is_infesting = FALSE
|
||||
|
||||
/datum/say_list/spore
|
||||
emote_see = list("sways", "inflates briefly")
|
||||
|
||||
/datum/say_list/infested
|
||||
emote_see = list("shambles around", "twitches", "stares")
|
||||
|
||||
|
||||
/mob/living/simple_mob/blob/spore/infesting
|
||||
name = "infesting blob spore"
|
||||
can_infest = TRUE
|
||||
|
||||
/mob/living/simple_mob/blob/spore/weak
|
||||
name = "fragile blob spore"
|
||||
health = 15
|
||||
maxHealth = 15
|
||||
melee_damage_lower = 1
|
||||
melee_damage_upper = 2
|
||||
|
||||
/mob/living/simple_mob/blob/spore/initialize(mapload, var/obj/structure/blob/factory/my_factory)
|
||||
if(istype(my_factory))
|
||||
factory = my_factory
|
||||
factory.spores += src
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_mob/blob/spore/Destroy()
|
||||
if(factory)
|
||||
factory.spores -= src
|
||||
factory = null
|
||||
if(infested)
|
||||
infested.forceMove(get_turf(src))
|
||||
visible_message(span("warning", "\The [infested] falls to the ground as the blob spore bursts."))
|
||||
infested = null
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_mob/blob/spore/death(gibbed, deathmessage = "bursts!")
|
||||
if(overmind)
|
||||
overmind.blob_type.on_spore_death(src)
|
||||
..(gibbed, deathmessage)
|
||||
qdel(src)
|
||||
|
||||
/mob/living/simple_mob/blob/spore/update_icons()
|
||||
..() // This will cut our overlays.
|
||||
|
||||
if(overmind)
|
||||
color = overmind.blob_type.complementary_color
|
||||
glow_color = color
|
||||
glow_toggle = TRUE
|
||||
else
|
||||
color = null
|
||||
glow_color = null
|
||||
glow_toggle = FALSE
|
||||
|
||||
if(is_infesting)
|
||||
icon = infested.icon
|
||||
copy_overlays(infested)
|
||||
// overlays = infested.overlays
|
||||
var/mutable_appearance/blob_head_overlay = mutable_appearance('icons/mob/blob.dmi', "blob_head")
|
||||
if(overmind)
|
||||
blob_head_overlay.color = overmind.blob_type.complementary_color
|
||||
color = initial(color)//looks better.
|
||||
// overlays += blob_head_overlay
|
||||
add_overlay(blob_head_overlay, TRUE)
|
||||
|
||||
/mob/living/simple_mob/blob/spore/handle_special()
|
||||
..()
|
||||
if(can_infest && !is_infesting && isturf(loc))
|
||||
for(var/mob/living/carbon/human/H in view(src,1))
|
||||
if(H.stat != DEAD) // We want zombies.
|
||||
continue
|
||||
if(H.isSynthetic()) // Not philosophical zombies.
|
||||
continue
|
||||
infest(H)
|
||||
break
|
||||
|
||||
if(factory && z != factory.z) // This is to prevent spores getting lost in space and making the factory useless.
|
||||
qdel(src)
|
||||
|
||||
/mob/living/simple_mob/blob/spore/proc/infest(mob/living/carbon/human/H)
|
||||
is_infesting = TRUE
|
||||
if(H.wear_suit)
|
||||
var/obj/item/clothing/suit/A = H.wear_suit
|
||||
if(A.armor && A.armor["melee"])
|
||||
maxHealth += A.armor["melee"] //That zombie's got armor, I want armor!
|
||||
|
||||
maxHealth += 40
|
||||
health = maxHealth
|
||||
name = "Infested [H.real_name]" // Not using the Z word.
|
||||
desc = "A parasitic organism attached to a deceased body, controlling it directly as if it were a puppet."
|
||||
melee_damage_lower += 8 // 10 total.
|
||||
melee_damage_upper += 11 // 15 total.
|
||||
attacktext = list("claws")
|
||||
|
||||
H.forceMove(src)
|
||||
infested = H
|
||||
|
||||
say_list = new /datum/say_list/infested()
|
||||
|
||||
update_icons()
|
||||
visible_message(span("warning", "The corpse of [H.name] suddenly rises!"))
|
||||
|
||||
/mob/living/simple_mob/blob/spore/GetIdCard()
|
||||
if(infested) // If we've infested someone, use their ID.
|
||||
return infested.GetIdCard()
|
||||
|
||||
/mob/living/simple_mob/blob/spore/apply_bonus_melee_damage(A, damage_to_do)
|
||||
var/helpers = 0
|
||||
for(var/mob/living/simple_mob/blob/spore/S in view(1, src))
|
||||
if(S == src) // Don't count ourselves.
|
||||
continue
|
||||
if(!IIsAlly(S)) // Only friendly spores make us stronger.
|
||||
continue
|
||||
// Friendly spores contribute half of their averaged attack power to our attack.
|
||||
damage_to_do += ((S.melee_damage_lower + S.melee_damage_upper) / 2) / 2
|
||||
helpers++
|
||||
|
||||
if(helpers)
|
||||
to_chat(src, span("notice", "Your attack is assisted by [helpers] other spore\s."))
|
||||
return damage_to_do
|
||||
@@ -0,0 +1,99 @@
|
||||
// Illusion type mobs pretend to be other things visually, and generally cannot be harmed as they're not 'real'.
|
||||
|
||||
/mob/living/simple_mob/illusion
|
||||
name = "illusion"
|
||||
desc = "If you can read me, the game broke. Please report this to a coder."
|
||||
|
||||
resistance = 1000 // Holograms are tough.
|
||||
heat_resist = 1
|
||||
cold_resist = 1
|
||||
shock_resist = 1
|
||||
poison_resist = 1
|
||||
|
||||
movement_cooldown = 0
|
||||
|
||||
response_help = "pushes a hand through"
|
||||
response_disarm = "tried to disarm"
|
||||
response_harm = "tried to punch"
|
||||
|
||||
mob_class = MOB_CLASS_ILLUSION
|
||||
|
||||
ai_holder_type = /datum/ai_holder/simple_mob/inert/astar // Gets controlled manually by technomancers/admins, with AI pathfinding assistance.
|
||||
|
||||
var/atom/movable/copying = null // The thing we're trying to look like.
|
||||
var/realistic = FALSE // If true, things like bullets and weapons will hit it, to be a bit more convincing from a distance.
|
||||
|
||||
/mob/living/simple_mob/illusion/update_icon() // We don't want the appearance changing AT ALL unless by copy_appearance().
|
||||
return
|
||||
|
||||
/mob/living/simple_mob/illusion/proc/copy_appearance(atom/movable/thing_to_copy)
|
||||
if(!thing_to_copy)
|
||||
return FALSE
|
||||
appearance = thing_to_copy.appearance
|
||||
copying = thing_to_copy
|
||||
return TRUE
|
||||
|
||||
// Because we can't perfectly duplicate some examine() output, we directly examine the AM it is copying. It's messy but
|
||||
// this is to prevent easy checks from the opposing force.
|
||||
/mob/living/simple_mob/illusion/examine(mob/user)
|
||||
if(copying)
|
||||
copying.examine(user)
|
||||
return
|
||||
..()
|
||||
|
||||
/mob/living/simple_mob/illusion/bullet_act(obj/item/projectile/P)
|
||||
if(!P)
|
||||
return
|
||||
|
||||
if(realistic)
|
||||
return ..()
|
||||
|
||||
return PROJECTILE_FORCE_MISS
|
||||
|
||||
/mob/living/simple_mob/illusion/attack_hand(mob/living/carbon/human/M)
|
||||
if(!realistic)
|
||||
playsound(loc, 'sound/weapons/punchmiss.ogg', 25, 1, -1)
|
||||
visible_message(span("warning", "\The [M]'s hand goes through \the [src]!"))
|
||||
return
|
||||
else
|
||||
switch(M.a_intent)
|
||||
if(I_HELP)
|
||||
var/datum/gender/T = gender_datums[src.get_visible_gender()]
|
||||
M.visible_message(
|
||||
span("notice", "\The [M] hugs [src] to make [T.him] feel better!"), \
|
||||
span("notice", "You hug [src] to make [T.him] feel better!")
|
||||
) // slightly redundant as at the moment most mobs still use the normal gender var, but it works and future-proofs it
|
||||
playsound(src.loc, 'sound/weapons/thudswoosh.ogg', 50, 1, -1)
|
||||
|
||||
if(I_DISARM)
|
||||
playsound(loc, 'sound/weapons/punchmiss.ogg', 25, 1, -1)
|
||||
visible_message(span("danger", "\The [M] attempted to disarm [src]!"))
|
||||
M.do_attack_animation(src)
|
||||
|
||||
if(I_GRAB)
|
||||
..()
|
||||
|
||||
if(I_HURT)
|
||||
adjustBruteLoss(harm_intent_damage)
|
||||
M.visible_message(span("danger", "\The [M] [response_harm] \the [src]"))
|
||||
M.do_attack_animation(src)
|
||||
|
||||
/mob/living/simple_mob/illusion/hit_with_weapon(obj/item/I, mob/living/user, effective_force, hit_zone)
|
||||
if(realistic)
|
||||
return ..()
|
||||
|
||||
playsound(loc, 'sound/weapons/punchmiss.ogg', 25, 1, -1)
|
||||
visible_message(span("warning", "\The [user]'s [I] goes through \the [src]!"))
|
||||
return FALSE
|
||||
|
||||
/mob/living/simple_mob/illusion/ex_act()
|
||||
return
|
||||
|
||||
// Try to have the same tooltip, or else it becomes really obvious which one is fake.
|
||||
/mob/living/simple_mob/illusion/get_nametag_name(mob/user)
|
||||
if(copying)
|
||||
return copying.get_nametag_name(user)
|
||||
|
||||
/mob/living/simple_mob/illusion/get_nametag_desc(mob/user)
|
||||
if(copying)
|
||||
return copying.get_nametag_desc(user)
|
||||
180
code/modules/mob/living/simple_mob/subtypes/mechanical/golem.dm
Normal file
@@ -0,0 +1,180 @@
|
||||
// The GOLEM is a spell-flinging synthetic.
|
||||
|
||||
/mob/living/simple_mob/mechanical/technomancer_golem
|
||||
name = "unknown synthetic"
|
||||
desc = "A rather unusual looking synthetic."
|
||||
icon = 'icons/mob/mob.dmi'
|
||||
icon_state = "golem"
|
||||
health = 300
|
||||
maxHealth = 300
|
||||
|
||||
faction = "golem"
|
||||
|
||||
response_help = "pets"
|
||||
response_disarm = "pushes away"
|
||||
response_harm = "punches"
|
||||
harm_intent_damage = 3
|
||||
friendly = "hugs"
|
||||
|
||||
melee_damage_lower = 30 // It has a built in esword.
|
||||
melee_damage_upper = 30
|
||||
attack_sound = 'sound/weapons/blade1.ogg'
|
||||
attacktext = list("slashed")
|
||||
melee_attack_delay = 0.5 SECONDS // Even has custom attack animations.
|
||||
ranged_attack_delay = 0.5 SECONDS
|
||||
special_attack_delay = 1 SECOND
|
||||
|
||||
special_attack_min_range = 0
|
||||
special_attack_max_range = 7
|
||||
|
||||
ai_holder_type = /datum/ai_holder/simple_mob/melee
|
||||
|
||||
var/obj/item/weapon/technomancer_core/golem/core = null
|
||||
var/obj/item/weapon/spell/active_spell = null // Shield and ranged spells
|
||||
var/mob/living/master = null
|
||||
var/casting = FALSE // Used to ensure the correct animation is played. Testing if a spell exists won't always work as some spells delete themselves upon use.
|
||||
|
||||
var/list/known_spells = list(
|
||||
"beam" = /obj/item/weapon/spell/projectile/beam,
|
||||
"chain lightning" = /obj/item/weapon/spell/projectile/chain_lightning,
|
||||
"force missile" = /obj/item/weapon/spell/projectile/force_missile,
|
||||
"ionic bolt" = /obj/item/weapon/spell/projectile/ionic_bolt,
|
||||
"lightning" = /obj/item/weapon/spell/projectile/lightning,
|
||||
"blink" = /obj/item/weapon/spell/blink,
|
||||
"dispel" = /obj/item/weapon/spell/dispel,
|
||||
"oxygenate" = /obj/item/weapon/spell/oxygenate,
|
||||
"mend life" = /obj/item/weapon/spell/modifier/mend_life,
|
||||
"mend synthetic" = /obj/item/weapon/spell/modifier/mend_synthetic,
|
||||
"mend organs" = /obj/item/weapon/spell/mend_organs,
|
||||
"purify" = /obj/item/weapon/spell/modifier/purify,
|
||||
"resurrect" = /obj/item/weapon/spell/resurrect,
|
||||
"passwall" = /obj/item/weapon/spell/passwall,
|
||||
"repel missiles" = /obj/item/weapon/spell/modifier/repel_missiles,
|
||||
"corona" = /obj/item/weapon/spell/modifier/corona,
|
||||
"haste" = /obj/item/weapon/spell/modifier/haste
|
||||
)
|
||||
|
||||
/mob/living/simple_mob/mechanical/technomancer_golem/initialize()
|
||||
core = new(src)
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_mob/mechanical/technomancer_golem/Destroy()
|
||||
qdel(core)
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_mob/mechanical/technomancer_golem/unref_spell()
|
||||
active_spell = null
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_mob/mechanical/technomancer_golem/death()
|
||||
..()
|
||||
visible_message("\The [src] disintegrates!")
|
||||
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)
|
||||
|
||||
/mob/living/simple_mob/mechanical/technomancer_golem/place_spell_in_hand(var/path)
|
||||
if(!path || !ispath(path))
|
||||
return FALSE
|
||||
if(active_spell)
|
||||
qdel(active_spell)
|
||||
|
||||
active_spell = new path(src)
|
||||
|
||||
/mob/living/simple_mob/mechanical/technomancer_golem/verb/test_giving_spells()
|
||||
var/choice = input(usr, "What spell?", "Give spell") as null|anything in known_spells
|
||||
if(choice)
|
||||
place_spell_in_hand(known_spells[choice])
|
||||
else
|
||||
qdel(active_spell)
|
||||
|
||||
/mob/living/simple_mob/mechanical/technomancer_golem/get_technomancer_core()
|
||||
return core
|
||||
|
||||
/mob/living/simple_mob/mechanical/technomancer_golem/can_special_attack(atom/A)
|
||||
if(active_spell) // Don't bother checking everything else if no spell is ready.
|
||||
return ..()
|
||||
return FALSE
|
||||
|
||||
/mob/living/simple_mob/mechanical/technomancer_golem/should_special_attack(atom/A)
|
||||
return instability < 50 // Don't kill ourselves by casting everything.
|
||||
|
||||
|
||||
/mob/living/simple_mob/mechanical/technomancer_golem/do_special_attack(atom/A)
|
||||
var/proximity = Adjacent(A)
|
||||
if(active_spell)
|
||||
if(proximity && active_spell.cast_methods & CAST_MELEE) // Use melee method if available and close enough.
|
||||
return active_spell.on_melee_cast(A, src)
|
||||
else if(active_spell.cast_methods & CAST_RANGED) // Otherwise use ranged if possible. Will also work for point-blank range.
|
||||
return active_spell.on_ranged_cast(A, src)
|
||||
return ..()
|
||||
|
||||
|
||||
/*
|
||||
// Used to cast spells.
|
||||
/mob/living/simple_animal/technomancer_golem/RangedAttack(var/atom/A, var/params)
|
||||
if(active_spell)
|
||||
spellcast_pre_animation()
|
||||
if(active_spell.cast_methods & CAST_RANGED)
|
||||
active_spell.on_ranged_cast(A, src)
|
||||
spellcast_post_animation()
|
||||
|
||||
/mob/living/simple_animal/technomancer_golem/UnarmedAttack(var/atom/A, var/proximity)
|
||||
if(proximity)
|
||||
if(active_spell)
|
||||
spellcast_pre_animation()
|
||||
if(!Adjacent(A)) // Need to check again since they might've moved while 'warming up'.
|
||||
spellcast_post_animation()
|
||||
return
|
||||
var/effective_cooldown = round(active_spell.cooldown * core.cooldown_modifier, 5)
|
||||
if(active_spell.cast_methods & CAST_MELEE)
|
||||
active_spell.on_melee_cast(A, src)
|
||||
else if(active_spell.cast_methods & CAST_RANGED)
|
||||
active_spell.on_ranged_cast(A, src)
|
||||
spellcast_post_animation()
|
||||
src.setClickCooldown(effective_cooldown)
|
||||
else
|
||||
..()
|
||||
*/
|
||||
|
||||
/mob/living/simple_mob/mechanical/technomancer_golem/melee_pre_animation(atom/A)
|
||||
if(active_spell && active_spell.cast_methods & CAST_MELEE|CAST_RANGED) // If they're trying to melee-cast a spell, use the special animation instead.
|
||||
special_pre_animation(A)
|
||||
return
|
||||
|
||||
flick("golem_pre_melee", src) // To force the animation to restart.
|
||||
icon_living = "golem_pre_melee" // The animation will hold after this point until melee_post_animation() gets called.
|
||||
icon_state = "golem_pre_melee"
|
||||
setClickCooldown(2)
|
||||
|
||||
/mob/living/simple_mob/mechanical/technomancer_golem/melee_post_animation(atom/A)
|
||||
if(casting) // Some spells delete themselves when used, so we use a different variable set earlier instead.
|
||||
special_post_animation(A)
|
||||
return
|
||||
|
||||
flick("golem_post_melee", src)
|
||||
icon_living = "golem"
|
||||
icon_state = "golem"
|
||||
setClickCooldown(6)
|
||||
|
||||
/mob/living/simple_mob/mechanical/technomancer_golem/ranged_pre_animation(atom/A)
|
||||
flick("golem_pre_ranged", src)
|
||||
icon_living = "golem_pre_ranged"
|
||||
icon_state = "golem_pre_ranged"
|
||||
setClickCooldown(5)
|
||||
|
||||
/mob/living/simple_mob/mechanical/technomancer_golem/ranged_post_animation(atom/A)
|
||||
flick("golem_post_ranged", src)
|
||||
icon_living = "golem"
|
||||
icon_state = "golem"
|
||||
setClickCooldown(5)
|
||||
|
||||
/mob/living/simple_mob/mechanical/technomancer_golem/special_pre_animation(atom/A)
|
||||
casting = TRUE
|
||||
ranged_pre_animation(A) // Both have the same animation.
|
||||
|
||||
/mob/living/simple_mob/mechanical/technomancer_golem/special_post_animation(atom/A)
|
||||
casting = FALSE
|
||||
ranged_post_animation(A)
|
||||
@@ -0,0 +1,166 @@
|
||||
// Stronger than a regular Dark Gygax, this one has three special attacks, based on intents.
|
||||
// First special attack launches three arcing rockets at the current target.
|
||||
// Second special attack fires a side weapon that rapidly pumps out blue energy blasts at nearby enemies for a few seconds.
|
||||
// Third special attack TBD.
|
||||
/mob/living/simple_mob/mechanical/mecha/combat/gygax/dark/advanced
|
||||
name = "advanced dark gygax"
|
||||
desc = "An experimental exosuit that utilizes advanced materials to allow for greater protection while still being lightweight and fast. \
|
||||
It also is armed with an array of next-generation weaponry."
|
||||
icon_state = "darkgygax_adv"
|
||||
wreckage = /obj/structure/loot_pile/mecha/gygax/dark/adv
|
||||
icon_scale = 1.5
|
||||
movement_shake_radius = 14
|
||||
|
||||
maxHealth = 450
|
||||
deflect_chance = 25
|
||||
armor = list(
|
||||
"melee" = 50,
|
||||
"bullet" = 50,
|
||||
"laser" = 50,
|
||||
"energy" = 30,
|
||||
"bomb" = 30,
|
||||
"bio" = 100,
|
||||
"rad" = 100
|
||||
)
|
||||
|
||||
special_attack_min_range = 1
|
||||
special_attack_max_range = 7
|
||||
special_attack_cooldown = 10 SECONDS
|
||||
var/obj/effect/overlay/energy_ball/energy_ball = null
|
||||
|
||||
/mob/living/simple_mob/mechanical/mecha/combat/gygax/dark/advanced/Destroy()
|
||||
if(energy_ball)
|
||||
energy_ball.stop_orbit()
|
||||
qdel(energy_ball)
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_mob/mechanical/mecha/combat/gygax/dark/advanced/do_special_attack(atom/A)
|
||||
. = TRUE // So we don't fire a laser as well.
|
||||
switch(a_intent)
|
||||
if(I_DISARM) // Side gun
|
||||
electric_defense(A)
|
||||
if(I_HURT) // Rockets
|
||||
launch_rockets(A)
|
||||
if(I_GRAB) // Micro-singulo
|
||||
launch_microsingularity(A)
|
||||
|
||||
#define ELECTRIC_ZAP_POWER 20000
|
||||
|
||||
// Charges a tesla shot, while emitting a dangerous electric field. The exosuit is immune to electric damage while this is ongoing.
|
||||
/mob/living/simple_mob/mechanical/mecha/combat/gygax/dark/advanced/proc/electric_defense(atom/target)
|
||||
set waitfor = FALSE
|
||||
|
||||
// Temporary immunity to shock to avoid killing themselves with their own attack.
|
||||
var/old_shock_resist = shock_resist
|
||||
shock_resist = 1
|
||||
|
||||
// Make the energy ball. This is purely visual since the tesla ball is hyper-deadly.
|
||||
energy_ball = new(loc)
|
||||
energy_ball.adjust_scale(0.5)
|
||||
energy_ball.orbit(src, 32, TRUE, 1 SECOND)
|
||||
|
||||
visible_message(span("warning", "\The [src] creates \an [energy_ball] around itself!"))
|
||||
|
||||
playsound(src.loc, 'sound/effects/lightning_chargeup.ogg', 100, 1, extrarange = 30)
|
||||
|
||||
// Shock nearby things that aren't ourselves.
|
||||
for(var/i = 1 to 10)
|
||||
energy_ball.adjust_scale(0.5 + (i/10))
|
||||
energy_ball.set_light(i/2, i/2, "#0000FF")
|
||||
for(var/mob/living/L in range(3, src))
|
||||
if(L == src)
|
||||
continue
|
||||
if(L.stat)
|
||||
continue // Otherwise it can get pretty laggy if there's loads of corpses around.
|
||||
L.inflict_shock_damage(i * 2)
|
||||
if(L && L.has_AI()) // Some mobs delete themselves when dying.
|
||||
L.ai_holder.react_to_attack(src)
|
||||
sleep(1 SECOND)
|
||||
|
||||
// Shoot a tesla bolt, and flashes people who are looking at the mecha without sufficent eye protection.
|
||||
visible_message(span("warning", "\The [energy_ball] explodes in a flash of light, sending a shock everywhere!"))
|
||||
playsound(src.loc, 'sound/effects/lightningbolt.ogg', 100, 1, extrarange = 30)
|
||||
tesla_zap(src.loc, 5, ELECTRIC_ZAP_POWER, FALSE)
|
||||
for(var/mob/living/L in viewers(src))
|
||||
if(L == src)
|
||||
continue
|
||||
var/dir_towards_us = get_dir(L, src)
|
||||
if(L.dir && L.dir & dir_towards_us)
|
||||
to_chat(L, span("danger", "The flash of light blinds you briefly."))
|
||||
L.flash_eyes(intensity = FLASH_PROTECTION_MODERATE, override_blindness_check = FALSE, affect_silicon = TRUE)
|
||||
|
||||
// Get rid of our energy ball.
|
||||
energy_ball.stop_orbit()
|
||||
qdel(energy_ball)
|
||||
|
||||
sleep(1 SECOND)
|
||||
// Resist resistance to old value.
|
||||
shock_resist = old_shock_resist // Not using initial() in case the value gets modified by an admin or something.
|
||||
|
||||
#undef ELECTRIC_ZAP_POWER
|
||||
|
||||
/mob/living/simple_mob/mechanical/mecha/combat/gygax/dark/advanced/proc/launch_rockets(atom/target)
|
||||
set waitfor = FALSE
|
||||
|
||||
// Telegraph our next move.
|
||||
Beam(target, icon_state = "sat_beam", time = 3.5 SECONDS)
|
||||
visible_message(span("warning", "\The [src] deploys a missile rack!"))
|
||||
playsound(src, 'sound/effects/turret/move1.wav', 50, 1)
|
||||
sleep(0.5 SECONDS)
|
||||
|
||||
for(var/i = 1 to 3)
|
||||
if(target) // Might get deleted in the meantime.
|
||||
var/turf/T = get_turf(target)
|
||||
if(T)
|
||||
visible_message(span("warning", "\The [src] fires a rocket into the air!"))
|
||||
playsound(src, 'sound/weapons/rpg.ogg', 70, 1)
|
||||
face_atom(T)
|
||||
var/obj/item/projectile/arc/explosive_rocket/rocket = new(loc)
|
||||
rocket.launch(T)
|
||||
sleep(1 SECOND)
|
||||
|
||||
visible_message(span("warning", "\The [src] retracts the missile rack."))
|
||||
playsound(src, 'sound/effects/turret/move2.wav', 50, 1)
|
||||
|
||||
// Arcing rocket projectile that produces a weak explosion when it lands.
|
||||
// Shouldn't punch holes in the floor, but will still hurt.
|
||||
/obj/item/projectile/arc/explosive_rocket
|
||||
name = "rocket"
|
||||
icon_state = "mortar"
|
||||
|
||||
/obj/item/projectile/arc/explosive_rocket/on_impact(turf/T)
|
||||
new /obj/effect/explosion(T) // Weak explosions don't produce this on their own, apparently.
|
||||
explosion(T, 0, 0, 2, adminlog = FALSE)
|
||||
|
||||
/mob/living/simple_mob/mechanical/mecha/combat/gygax/dark/advanced/proc/launch_microsingularity(atom/target)
|
||||
var/turf/T = get_turf(target)
|
||||
visible_message(span("warning", "\The [src] fires an energetic sphere into the air!"))
|
||||
playsound(src, 'sound/weapons/Laser.ogg', 50, 1)
|
||||
face_atom(T)
|
||||
var/obj/item/projectile/arc/microsingulo/sphere = new(loc)
|
||||
sphere.launch(T)
|
||||
|
||||
/obj/item/projectile/arc/microsingulo
|
||||
name = "micro singularity"
|
||||
icon_state = "bluespace"
|
||||
|
||||
/obj/item/projectile/arc/microsingulo/on_impact(turf/T)
|
||||
new /obj/effect/temporary_effect/pulse/microsingulo(T)
|
||||
|
||||
|
||||
/obj/effect/temporary_effect/pulse/microsingulo
|
||||
name = "micro singularity"
|
||||
desc = "It's sucking everything in!"
|
||||
icon = 'icons/obj/objects.dmi'
|
||||
icon_state = "bhole3"
|
||||
light_range = 4
|
||||
light_power = 5
|
||||
light_color = "#2ECCFA"
|
||||
pulses_remaining = 10
|
||||
pulse_delay = 0.5 SECONDS
|
||||
var/pull_radius = 3
|
||||
var/pull_strength = STAGE_THREE
|
||||
|
||||
/obj/effect/temporary_effect/pulse/microsingulo/on_pulse()
|
||||
for(var/atom/A in range(pull_radius, src))
|
||||
A.singularity_pull(src, pull_strength)
|
||||
@@ -0,0 +1,38 @@
|
||||
// Base type for the 'combat' mechas like gygax/durand/maulers/etc.
|
||||
// They generally are walking tanks, and their melee attack knocks back and stuns, like the real deal.
|
||||
|
||||
/mob/living/simple_mob/mechanical/mecha/combat
|
||||
name = "combat mecha"
|
||||
desc = "An even bigger stompy mech!!"
|
||||
|
||||
movement_cooldown = 10
|
||||
melee_damage_lower = 30
|
||||
melee_damage_upper = 30
|
||||
melee_attack_delay = 1 SECOND
|
||||
attacktext = list("punched", "slammed", "uppercutted", "pummeled")
|
||||
|
||||
armor = list(
|
||||
"melee" = 30,
|
||||
"bullet" = 30,
|
||||
"laser" = 15,
|
||||
"energy" = 0,
|
||||
"bomb" = 20,
|
||||
"bio" = 100,
|
||||
"rad" = 100
|
||||
)
|
||||
|
||||
var/weaken_amount = 2 // Be careful with this number. High values can equal a permastun.
|
||||
|
||||
// Melee hits knock back by one tile (or more if already stunned to help prevent permastuns).
|
||||
/mob/living/simple_mob/mechanical/mecha/combat/apply_melee_effects(atom/A)
|
||||
if(isliving(A))
|
||||
var/mob/living/L = A
|
||||
if(L.mob_size <= MOB_MEDIUM)
|
||||
visible_message(span("danger", "\The [src] sends \the [L] flying with their mechanized fist!"))
|
||||
playsound(src, "punch", 50, 1)
|
||||
L.Weaken(weaken_amount)
|
||||
var/throw_dir = get_dir(src, L)
|
||||
var/throw_dist = L.incapacitated(INCAPACITATION_DISABLED) ? 4 : 1
|
||||
L.throw_at(get_edge_target_turf(L, throw_dir), throw_dist, 1, src)
|
||||
else
|
||||
to_chat(L, span("warning", "\The [src] punches you with incredible force, but you remain in place."))
|
||||
@@ -0,0 +1,78 @@
|
||||
// Gygaxes are tough but also fast.
|
||||
// Their AI, unlike most, will advance towards their target instead of remaining in place.
|
||||
|
||||
/mob/living/simple_mob/mechanical/mecha/combat/gygax
|
||||
name = "gygax"
|
||||
desc = "A lightweight, security exosuit. Popular among private and corporate security."
|
||||
icon_state = "gygax"
|
||||
movement_cooldown = 5
|
||||
wreckage = /obj/structure/loot_pile/mecha/gygax
|
||||
|
||||
maxHealth = 300
|
||||
armor = list(
|
||||
"melee" = 25,
|
||||
"bullet" = 20,
|
||||
"laser" = 30,
|
||||
"energy" = 15,
|
||||
"bomb" = 0,
|
||||
"bio" = 100,
|
||||
"rad" = 100
|
||||
)
|
||||
|
||||
projectiletype = /obj/item/projectile/beam/midlaser
|
||||
|
||||
ai_holder_type = /datum/ai_holder/simple_mob/intentional/adv_dark_gygax
|
||||
|
||||
/mob/living/simple_mob/mechanical/mecha/combat/gygax/manned
|
||||
pilot_type = /mob/living/simple_mob/humanoid/merc/ranged // Carries a pistol.
|
||||
|
||||
|
||||
// A stronger variant.
|
||||
/mob/living/simple_mob/mechanical/mecha/combat/gygax/dark
|
||||
name = "dark gygax"
|
||||
desc = "A significantly upgraded Gygax security mech, often utilized by corporate asset protection teams and \
|
||||
PMCs."
|
||||
icon_state = "darkgygax"
|
||||
wreckage = /obj/structure/loot_pile/mecha/gygax/dark
|
||||
|
||||
maxHealth = 400
|
||||
deflect_chance = 25
|
||||
armor = list(
|
||||
"melee" = 40,
|
||||
"bullet" = 40,
|
||||
"laser" = 50,
|
||||
"energy" = 35,
|
||||
"bomb" = 20,
|
||||
"bio" = 100,
|
||||
"rad" = 100
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/mob/living/simple_mob/mechanical/mecha/combat/gygax/medgax
|
||||
name = "medgax"
|
||||
desc = "An unorthodox fusion of the Gygax and Odysseus exosuits, this one is fast, sturdy, and carries a wide array of \
|
||||
potent chemicals and delivery mechanisms. The doctor is in!"
|
||||
icon_state = "medgax"
|
||||
wreckage = /obj/effect/decal/mecha_wreckage/gygax/medgax
|
||||
|
||||
|
||||
/*
|
||||
desc = "A lightweight, security exosuit. Popular among private and corporate security."
|
||||
name = "Gygax"
|
||||
icon_state = "gygax"
|
||||
initial_icon = "gygax"
|
||||
step_in = 3
|
||||
dir_in = 1 //Facing North.
|
||||
health = 300
|
||||
maxhealth = 300
|
||||
deflect_chance = 15
|
||||
damage_absorption = list("brute"=0.75,"fire"=1,"bullet"=0.8,"laser"=0.7,"energy"=0.85,"bomb"=1)
|
||||
max_temperature = 25000
|
||||
infra_luminosity = 6
|
||||
var/overload = 0
|
||||
var/overload_coeff = 2
|
||||
wreckage = /obj/effect/decal/mecha_wreckage/gygax
|
||||
*/
|
||||
@@ -0,0 +1,27 @@
|
||||
// Ranged, and capable of flight.
|
||||
/mob/living/simple_mob/mechanical/mecha/hoverpod
|
||||
name = "hover pod"
|
||||
desc = "Stubby and round, this space-capable craft is an ancient favorite. It has a jury-rigged welder-laser."
|
||||
icon_state = "engineering_pod"
|
||||
movement_sound = 'sound/machines/hiss.ogg'
|
||||
wreckage = /obj/effect/decal/mecha_wreckage/hoverpod
|
||||
|
||||
maxHealth = 150
|
||||
hovering = TRUE // Can fly.
|
||||
|
||||
projectiletype = /obj/item/projectile/beam
|
||||
base_attack_cooldown = 2 SECONDS
|
||||
|
||||
var/datum/effect/effect/system/ion_trail_follow/ion_trail
|
||||
|
||||
/mob/living/simple_mob/mechanical/mecha/hoverpod/manned
|
||||
pilot_type = /mob/living/simple_mob/humanoid/merc/ranged
|
||||
|
||||
/mob/living/simple_mob/mechanical/mecha/hoverpod/initialize()
|
||||
ion_trail = new /datum/effect/effect/system/ion_trail_follow()
|
||||
ion_trail.set_up(src)
|
||||
ion_trail.start()
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_mob/mechanical/mecha/hoverpod/Process_Spacemove(var/check_drift = 0)
|
||||
return TRUE
|
||||
@@ -0,0 +1,122 @@
|
||||
// Mecha simple_mobs are essentially fake mechs. Generally tough and scary to fight.
|
||||
// By default, they're automatically piloted by some kind of drone AI. They can be set to be "piloted" instead with a var.
|
||||
// Tries to be as similar to the real deal as possible.
|
||||
|
||||
/mob/living/simple_mob/mechanical/mecha
|
||||
name = "mecha"
|
||||
desc = "A big stompy mech!"
|
||||
icon = 'icons/mecha/mecha.dmi'
|
||||
|
||||
faction = "syndicate"
|
||||
movement_cooldown = 5
|
||||
movement_sound = 'sound/mecha/mechstep.ogg'
|
||||
turn_sound = 'sound/mecha/mechturn.ogg'
|
||||
maxHealth = 300
|
||||
mob_size = MOB_LARGE
|
||||
|
||||
// Very close to the base 'damage_absorption' var on the base mecha class.
|
||||
armor = list(
|
||||
"melee" = 20,
|
||||
"bullet" = 10,
|
||||
"laser" = 0,
|
||||
"energy" = 0,
|
||||
"bomb" = 0,
|
||||
"bio" = 100,
|
||||
"rad" = 100
|
||||
)
|
||||
|
||||
response_help = "taps on"
|
||||
response_disarm = "knocks on"
|
||||
response_harm = "uselessly hits"
|
||||
harm_intent_damage = 0
|
||||
|
||||
ai_holder_type = /datum/ai_holder/simple_mob/melee
|
||||
say_list_type = /datum/say_list/malf_drone
|
||||
|
||||
var/datum/effect/effect/system/spark_spread/sparks
|
||||
var/wreckage = /obj/effect/decal/mecha_wreckage/gygax/dark
|
||||
var/pilot_type = null // Set to spawn a pilot when destroyed. Setting this also makes the mecha vulnerable to things that affect sentient minds.
|
||||
var/deflect_chance = 10 // Chance to outright stop an attack, just like a normal exosuit.
|
||||
|
||||
|
||||
/mob/living/simple_mob/mechanical/mecha/initialize()
|
||||
sparks = new (src)
|
||||
sparks.set_up(3, 1, src)
|
||||
|
||||
if(!pilot_type)
|
||||
name = "autonomous [initial(name)] drone"
|
||||
desc = "[initial(desc)] It appears to be piloted by a drone intelligence."
|
||||
else
|
||||
say_list_type = /datum/say_list/merc
|
||||
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_mob/mechanical/mecha/Destroy()
|
||||
qdel(sparks)
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_mob/mechanical/mecha/death()
|
||||
..(0,"explodes!") // Do everything else first.
|
||||
|
||||
// Make the exploding more convincing with an actual explosion and some sparks.
|
||||
sparks.start()
|
||||
explosion(get_turf(src), 0, 0, 1, 3)
|
||||
|
||||
// 'Eject' our pilot, if one exists.
|
||||
if(pilot_type)
|
||||
var/mob/living/L = new pilot_type(loc)
|
||||
L.faction = src.faction
|
||||
|
||||
new wreckage(loc) // Leave some wreckage.
|
||||
|
||||
qdel(src) // Then delete us since we don't actually have a body.
|
||||
|
||||
/mob/living/simple_mob/mechanical/mecha/bullet_act()
|
||||
. = ..()
|
||||
sparks.start()
|
||||
|
||||
/mob/living/simple_mob/mechanical/mecha/speech_bubble_appearance()
|
||||
return pilot_type ? "" : ..()
|
||||
|
||||
// Piloted mechs are controlled by (presumably) something humanoid so they are vulnerable to certain things.
|
||||
/mob/living/simple_mob/mechanical/mecha/is_sentient()
|
||||
return pilot_type ? TRUE : FALSE
|
||||
|
||||
/*
|
||||
// Real mechs can't turn and run at the same time. This tries to simulate that.
|
||||
// Commented out because the AI can't handle it sadly.
|
||||
/mob/living/simple_mob/mechanical/mecha/SelfMove(turf/n, direct)
|
||||
if(direct != dir)
|
||||
set_dir(direct)
|
||||
return FALSE // We didn't actually move, and returning FALSE means the mob can try to actually move almost immediately and not have to wait the full movement cooldown.
|
||||
return ..()
|
||||
*/
|
||||
|
||||
/mob/living/simple_mob/mechanical/mecha/bullet_act(obj/item/projectile/P)
|
||||
if(prob(deflect_chance))
|
||||
visible_message(span("warning", "\The [P] is deflected by \the [src]'s armor!"))
|
||||
deflect_sprite()
|
||||
return 0
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_mob/mechanical/mecha/proc/deflect_sprite()
|
||||
var/image/deflect_image = image('icons/effects/effects.dmi', "deflect_static")
|
||||
add_overlay(deflect_image)
|
||||
sleep(1 SECOND)
|
||||
cut_overlay(deflect_image)
|
||||
qdel(deflect_image)
|
||||
// flick_overlay_view(deflect_image, src, duration = 1 SECOND, gc_after = TRUE)
|
||||
|
||||
/mob/living/simple_mob/mechanical/mecha/attackby(obj/item/I, mob/user)
|
||||
if(prob(deflect_chance))
|
||||
visible_message(span("warning", "\The [user]'s [I] bounces off \the [src]'s armor!"))
|
||||
deflect_sprite()
|
||||
user.setClickCooldown(user.get_attack_speed(I))
|
||||
return
|
||||
..()
|
||||
|
||||
/mob/living/simple_mob/mechanical/mecha/ex_act(severity)
|
||||
if(prob(deflect_chance))
|
||||
severity++ // This somewhat misleadingly makes it less severe.
|
||||
deflect_sprite()
|
||||
..(severity)
|
||||
@@ -0,0 +1,71 @@
|
||||
// Shoots syringe-darts at enemies, which applies a stacking poison modifier that hurts over time.
|
||||
// They also do this in melee.
|
||||
// Fortunately they're quite fragile and don't fire that fast.
|
||||
|
||||
/mob/living/simple_mob/mechanical/mecha/odysseus
|
||||
name = "odysseus"
|
||||
desc = "These exosuits are developed and produced by Vey-Med. This one has a syringe gun."
|
||||
icon_state = "odysseus"
|
||||
wreckage = /obj/effect/decal/mecha_wreckage/odysseus
|
||||
|
||||
maxHealth = 120
|
||||
movement_cooldown = 0
|
||||
turn_sound = 'sound/mecha/mechmove01.ogg'
|
||||
|
||||
melee_damage_lower = 5
|
||||
melee_damage_upper = 5
|
||||
base_attack_cooldown = 2 SECONDS
|
||||
attacktext = list("injected")
|
||||
projectiletype = /obj/item/projectile/fake_syringe/poison
|
||||
projectilesound = 'sound/weapons/empty.ogg' // Just like the syringe gun.
|
||||
|
||||
ai_holder_type = /datum/ai_holder/simple_mob/ranged/kiting/no_moonwalk
|
||||
|
||||
/mob/living/simple_mob/mechanical/mecha/odysseus/manned
|
||||
pilot_type = /mob/living/simple_mob/humanoid/merc/ranged // Carries a pistol.
|
||||
|
||||
|
||||
// Resprite of the regular one, perhaps for merc PoIs.
|
||||
/mob/living/simple_mob/mechanical/mecha/odysseus/murdysseus
|
||||
icon_state = "murdysseus"
|
||||
wreckage = /obj/effect/decal/mecha_wreckage/odysseus/murdysseus
|
||||
|
||||
/mob/living/simple_mob/mechanical/mecha/odysseus/murdysseus/manned
|
||||
pilot_type = /mob/living/simple_mob/humanoid/merc/ranged
|
||||
|
||||
|
||||
/mob/living/simple_mob/mechanical/mecha/odysseus/apply_melee_effects(atom/A)
|
||||
if(isliving(A))
|
||||
var/mob/living/L = A
|
||||
|
||||
var/target_zone = pick(BP_TORSO,BP_TORSO,BP_TORSO,BP_L_LEG,BP_R_LEG,BP_L_ARM,BP_R_ARM,BP_HEAD)
|
||||
if(L.can_inject(src, null, target_zone))
|
||||
to_chat(L, span("warning", "You feel a tiny prick."))
|
||||
if(L.get_poison_protection() < 1)
|
||||
L.add_modifier(/datum/modifier/poisoned, 30 SECONDS)
|
||||
L.inflict_poison_damage(5)
|
||||
|
||||
|
||||
// Fake syringe that tests if target can be injected before applying damage/modifiers/etc.
|
||||
/obj/item/projectile/fake_syringe
|
||||
name = "syringe"
|
||||
icon_state = "syringe"
|
||||
damage = 5 // Getting hit with a launched syringe probably hurts, and makes it at least slightly relevant against synthetics.
|
||||
var/piercing = FALSE // If true, ignores thick material.
|
||||
|
||||
/obj/item/projectile/fake_syringe/on_hit(atom/target, blocked = 0, def_zone = null)
|
||||
if(isliving(target))
|
||||
var/mob/living/L = target
|
||||
if(!L.can_inject(null, null, def_zone, piercing))
|
||||
return FALSE
|
||||
to_chat(L, span("warning", "You feel a tiny prick."))
|
||||
return ..() // This will add the modifier and return the correct value.
|
||||
|
||||
|
||||
// Fake syringe, which inflicts a long lasting modifier that slowly kills them.
|
||||
/obj/item/projectile/fake_syringe/poison
|
||||
modifier_type_to_apply = /datum/modifier/poisoned
|
||||
modifier_duration = 1 MINUTE // About 30 damage per stack over a minute.
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
// Beefy, but somewhat slow.
|
||||
// Melee attack is to bore you with its big drill, which has a lot of armor penetration and strikes rapidly.
|
||||
|
||||
/mob/living/simple_mob/mechanical/mecha/ripley
|
||||
name = "\improper APLU ripley"
|
||||
desc = "Autonomous Power Loader Unit. The workhorse of the exosuit world. This one has big drill."
|
||||
icon_state = "ripley"
|
||||
wreckage = /obj/effect/decal/mecha_wreckage/ripley
|
||||
|
||||
maxHealth = 200
|
||||
|
||||
melee_damage_lower = 10
|
||||
melee_damage_upper = 10
|
||||
base_attack_cooldown = 5 // About 20 DPS.
|
||||
attack_armor_pen = 50
|
||||
attack_sharp = TRUE
|
||||
attack_sound = 'sound/mecha/mechdrill.ogg'
|
||||
attacktext = list("drilled", "bored", "pierced")
|
||||
|
||||
/mob/living/simple_mob/mechanical/mecha/ripley/manned
|
||||
pilot_type = /mob/living/simple_mob/humanoid/merc/ranged // Carries a pistol.
|
||||
|
||||
/mob/living/simple_mob/mechanical/mecha/ripley/red_flames
|
||||
icon_state = "ripley_flames_red"
|
||||
|
||||
/mob/living/simple_mob/mechanical/mecha/ripley/blue_flames
|
||||
icon_state = "ripley_flames_blue"
|
||||
|
||||
|
||||
// Immune to heat damage, resistant to lasers, and somewhat beefier. Still tries to melee you.
|
||||
/mob/living/simple_mob/mechanical/mecha/ripley/firefighter
|
||||
name = "\improper APLU firefighter"
|
||||
desc = "A standard APLU chassis, refitted with additional thermal protection and cistern. This one has a big drill."
|
||||
icon_state = "firefighter"
|
||||
wreckage = /obj/effect/decal/mecha_wreckage/ripley/firefighter
|
||||
|
||||
maxHealth = 250
|
||||
heat_resist = 1
|
||||
armor = list(
|
||||
"melee" = 0,
|
||||
"bullet" = 20,
|
||||
"laser" = 50,
|
||||
"energy" = 0,
|
||||
"bomb" = 50,
|
||||
"bio" = 100,
|
||||
"rad" = 100
|
||||
)
|
||||
|
||||
/mob/living/simple_mob/mechanical/mecha/ripley/firefighter/manned
|
||||
pilot_type = /mob/living/simple_mob/humanoid/merc/ranged
|
||||
|
||||
// Mostly a joke mob, like the real DEATH-RIPLEY.
|
||||
/mob/living/simple_mob/mechanical/mecha/ripley/deathripley
|
||||
name = "\improper DEATH-RIPLEY"
|
||||
desc = "OH SHIT RUN!!! IT HAS A KILL CLAMP!"
|
||||
icon_state = "deathripley"
|
||||
wreckage = /obj/effect/decal/mecha_wreckage/ripley/deathripley
|
||||
|
||||
melee_damage_lower = 0
|
||||
melee_damage_upper = 0
|
||||
friendly = list("utterly obliterates", "furiously destroys", "permanently removes", "unflichingly decimates", "brutally murders", "absolutely demolishes", "completely annihilates")
|
||||
|
||||
/mob/living/simple_mob/mechanical/mecha/ripley/deathripley/manned
|
||||
pilot_type = /mob/living/simple_mob/humanoid/merc/ranged
|
||||
@@ -1,4 +1,7 @@
|
||||
// Mechanical mobs don't care about the atmosphere and cannot be hurt by tasers.
|
||||
// They're also immune to poisons as they're entirely metal, however this also makes most of them vulnerable to shocks.
|
||||
// They can also be hurt by EMP.
|
||||
|
||||
/mob/living/simple_mob/mechanical
|
||||
mob_class = MOB_CLASS_SYNTHETIC
|
||||
min_oxy = 0
|
||||
@@ -12,6 +15,8 @@
|
||||
minbodytemp = 0
|
||||
|
||||
taser_kill = FALSE
|
||||
poison_resist = 1.0
|
||||
shock_resist = -0.5
|
||||
|
||||
/mob/living/simple_mob/mechanical/isSynthetic()
|
||||
return TRUE
|
||||
|
||||
@@ -33,4 +33,19 @@
|
||||
|
||||
/mob/living/simple_mob/mechanical/viscerator/death()
|
||||
..(null,"is smashed into pieces!")
|
||||
qdel(src)
|
||||
qdel(src)
|
||||
|
||||
// Variant that is always loyal to mercenary antagonists.
|
||||
// Used for a special grenade, to ensure they don't attack the wrong thing.
|
||||
/mob/living/simple_mob/mechanical/viscerator/mercenary/IIsAlly(mob/living/L)
|
||||
. = ..()
|
||||
if(!.) // Not friendly, see if they're a baddie first.
|
||||
if(L.mind && mercs.is_antagonist(L.mind))
|
||||
return TRUE
|
||||
|
||||
// Similar to above but for raiders.
|
||||
/mob/living/simple_mob/mechanical/viscerator/raider/IIsAlly(mob/living/L)
|
||||
. = ..()
|
||||
if(!.) // Not friendly, see if they're a baddie first.
|
||||
if(L.mind && raiders.is_antagonist(L.mind))
|
||||
return TRUE
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
'Monitor' wards are drones that yell at their creator if they see someone besides them that they are hostile to.
|
||||
They can also force an invisible entity to uncloak if the invisible mob is hostile to the ward.
|
||||
If AI controlled, they will also warn their faction if they see a hostile entity, acting as floating cameras.
|
||||
*/
|
||||
|
||||
/mob/living/simple_mob/mechanical/ward/monitor
|
||||
desc = "It's a little flying drone. This one seems to be watching you..."
|
||||
icon_state = "ward"
|
||||
see_invisible = SEE_INVISIBLE_LEVEL_TWO
|
||||
|
||||
has_eye_glow = TRUE
|
||||
glow_range = 3
|
||||
glow_intensity = 3
|
||||
glow_toggle = TRUE
|
||||
|
||||
player_msg = "You will automatically alert your owner (if one exists) of enemies you see nearby.<br>\
|
||||
You can also <b>see invisible entities, and will automatically uncloak</b> nearby invisible or hidden enemies."
|
||||
|
||||
ai_holder_type = /datum/ai_holder/simple_mob/monitor
|
||||
|
||||
var/list/seen_mobs = list()
|
||||
var/view_range = 5
|
||||
|
||||
// For PoIs.
|
||||
/mob/living/simple_mob/mechanical/ward/monitor/syndicate
|
||||
faction = "syndicate"
|
||||
|
||||
/mob/living/simple_mob/mechanical/ward/monitor/crew
|
||||
faction = "neutral"
|
||||
|
||||
/mob/living/simple_mob/mechanical/ward/monitor/death()
|
||||
if(owner)
|
||||
to_chat(owner, span("warning", "Your [src.name] inside [get_area(src)] was destroyed!"))
|
||||
..()
|
||||
|
||||
/mob/living/simple_mob/mechanical/ward/monitor/handle_special()
|
||||
detect_mobs()
|
||||
|
||||
/mob/living/simple_mob/mechanical/ward/monitor/update_icon()
|
||||
if(seen_mobs.len)
|
||||
icon_living = "ward_spotted"
|
||||
glow_color = "#FF0000"
|
||||
else
|
||||
icon_living = "ward"
|
||||
glow_color = "#00FF00"
|
||||
handle_light() // Update the light immediately.
|
||||
..()
|
||||
|
||||
/mob/living/simple_mob/mechanical/ward/monitor/proc/detect_mobs()
|
||||
var/last_seen_mobs_len = seen_mobs.len
|
||||
var/list/things_in_sight = view(view_range, src)
|
||||
var/list/newly_seen_mobs = list()
|
||||
for(var/mob/living/L in things_in_sight)
|
||||
if(L == src) // Don't detect ourselves.
|
||||
continue
|
||||
|
||||
if(L.stat) // Dead mobs aren't concerning.
|
||||
continue
|
||||
|
||||
if(owner)
|
||||
if(L == owner) // Don't yell at our owner for seeing them.
|
||||
continue
|
||||
|
||||
if(L.IIsAlly(owner)) // Don't yell if they're friendly to our owner.
|
||||
continue
|
||||
|
||||
else
|
||||
if(src.IIsAlly(L))
|
||||
continue
|
||||
|
||||
// Decloak them .
|
||||
if(L.is_cloaked())
|
||||
Beam(L, icon_state = "solar_beam", time = 5)
|
||||
playsound(L, 'sound/effects/EMPulse.ogg', 75, 1)
|
||||
L.break_cloak()
|
||||
|
||||
to_chat(L, span("danger", "\The [src] disrupts your cloak!"))
|
||||
if(owner)
|
||||
to_chat(owner, span("notice", "Your [src.name] at [get_area(src)] uncloaked \the [L]."))
|
||||
|
||||
// Warn the owner when it sees a new mob.
|
||||
if(!(L in seen_mobs))
|
||||
seen_mobs += L
|
||||
newly_seen_mobs += L
|
||||
if(owner)
|
||||
to_chat(owner, span("notice", "Your [src.name] at [get_area(src)] detected [english_list(newly_seen_mobs)]."))
|
||||
|
||||
// Now get rid of old mobs that left vision.
|
||||
for(var/thing in seen_mobs)
|
||||
if(!(thing in things_in_sight))
|
||||
seen_mobs -= thing
|
||||
|
||||
// Check if we need to update icon.
|
||||
if(seen_mobs.len != last_seen_mobs_len)
|
||||
update_icon()
|
||||
last_seen_mobs_len = seen_mobs.len
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
Wards are a specific type of mechanical simplemob that generally fill a support role for their faction or for a specific mob.
|
||||
Generally they are helpless by themselves and are fragile, but can do very useful things if protected. This makes them a high priority target.
|
||||
*/
|
||||
|
||||
/mob/living/simple_mob/mechanical/ward
|
||||
name = "ward"
|
||||
desc = "A small floating machine. This one seems rather useless..."
|
||||
icon = 'icons/mob/critter.dmi'
|
||||
icon_state = "ward"
|
||||
icon_living = "ward"
|
||||
hovering = TRUE // Won't trigger landmines.
|
||||
response_help = "pets"
|
||||
response_disarm = "swats away"
|
||||
response_harm = "punches"
|
||||
faction = "wards" // Needed as most human mobs are in neutral faction. The owner is generally except from any ward hostility regardless.
|
||||
|
||||
maxHealth = 15
|
||||
health = 15
|
||||
movement_cooldown = 0
|
||||
hovering = TRUE
|
||||
|
||||
pass_flags = PASSTABLE
|
||||
mob_swap_flags = 0
|
||||
mob_push_flags = 0
|
||||
|
||||
melee_damage_lower = 0
|
||||
melee_damage_upper = 0
|
||||
|
||||
ai_holder_type = null
|
||||
var/mob/living/owner = null // The mob that made the ward, if any. Used to ensure the ward does not interfere with its creator.
|
||||
|
||||
/mob/living/simple_mob/mechanical/ward/death()
|
||||
..(null,"is smashed into pieces!")
|
||||
qdel(src)
|
||||
|
||||
/mob/living/simple_mob/mechanical/ward/Destroy()
|
||||
owner = null
|
||||
return ..()
|
||||
@@ -42,6 +42,7 @@
|
||||
minbodytemp = 0
|
||||
|
||||
shock_resist = 0.1 //Electricity isn't very effective on stone, especially that from hell.
|
||||
poison_resist = 1.0
|
||||
|
||||
armor = list(
|
||||
"melee" = 10,
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
faction = "plants"
|
||||
maxHealth = 15
|
||||
health = 15
|
||||
poison_resist = 1.0
|
||||
|
||||
response_help = "prods"
|
||||
response_disarm = "pushes aside"
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
faction = "plants"
|
||||
maxHealth = 250
|
||||
health = 250
|
||||
poison_resist = 1.0
|
||||
|
||||
response_help = "brushes"
|
||||
response_disarm = "pushes"
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
maxHealth = 300
|
||||
movement_cooldown = 10
|
||||
attack_delay = 5
|
||||
melee_attack_delay = 0.5 SECONDS
|
||||
|
||||
|
||||
// Slimebatoning/xenotasing it just makes it mad at you (which can be good if you're heavily armored and your friends aren't).
|
||||
|
||||
@@ -72,8 +72,9 @@
|
||||
/mob/living/simple_mob/slime/initialize()
|
||||
verbs += /mob/living/proc/ventcrawl
|
||||
update_mood()
|
||||
update_icon()
|
||||
glow_color = color
|
||||
handle_light()
|
||||
update_icon()
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_mob/slime/Destroy()
|
||||
|
||||
@@ -390,7 +390,7 @@
|
||||
player_msg = "Your <b>attacks knock back the target</b> a fair distance.<br>\
|
||||
You also hit harder, take less damage, and stuns affect you for less time."
|
||||
|
||||
attack_delay = 1 SECOND
|
||||
melee_attack_delay = 1 SECOND
|
||||
|
||||
slime_mutation = list(
|
||||
/mob/living/simple_mob/slime/xenobio/dark_purple,
|
||||
|
||||
@@ -217,15 +217,6 @@
|
||||
if(..())
|
||||
return species.can_fall(src)
|
||||
|
||||
/mob/living/simple_animal/parrot/can_fall() // Poly can fly.
|
||||
return FALSE
|
||||
|
||||
/mob/living/simple_mob/space/carp/can_fall() // So can carp apparently.
|
||||
return FALSE
|
||||
|
||||
/mob/living/simple_mob/construct/can_fall() //As do Constructs.
|
||||
return FALSE
|
||||
|
||||
// Check if this atom prevents things standing on it from falling. Return TRUE to allow the fall.
|
||||
/obj/proc/CanFallThru(atom/movable/mover as mob|obj, turf/target as turf)
|
||||
return TRUE
|
||||
|
||||
@@ -115,6 +115,15 @@
|
||||
ChangeTurf(get_base_turf_by_area(src))
|
||||
return 2
|
||||
|
||||
/turf/simulated/floor/singularity_pull(S, current_size)
|
||||
if(flooring && current_size >= STAGE_THREE)
|
||||
if(prob(current_size / 2))
|
||||
var/leave_tile = TRUE
|
||||
if(broken || burnt || flooring.flags & TURF_IS_FRAGILE)
|
||||
leave_tile = FALSE
|
||||
playsound(src, 'sound/items/crowbar.ogg', 50, 1)
|
||||
make_plating(leave_tile)
|
||||
|
||||
/turf/simulated/wall/singularity_pull(S, current_size)
|
||||
|
||||
if(!reinf_material)
|
||||
|
||||
@@ -287,7 +287,7 @@
|
||||
|
||||
else if(closest_mob)
|
||||
var/shock_damage = Clamp(round(power/400), 10, 90) + rand(-5, 5)
|
||||
closest_mob.electrocute_act(shock_damage, source, 1, ran_zone())
|
||||
closest_mob.electrocute_act(shock_damage, source, 1 - closest_mob.get_shock_protection(), ran_zone())
|
||||
log_game("TESLA([source.x],[source.y],[source.z]) Shocked [key_name(closest_mob)] for [shock_damage]dmg.")
|
||||
message_admins("Tesla zapped [key_name_admin(closest_mob)]!")
|
||||
if(issilicon(closest_mob))
|
||||
|
||||
@@ -106,6 +106,8 @@
|
||||
M.drowsyness = max(0, M.drowsyness - 6 * removed * chem_effective)
|
||||
M.hallucination = max(0, M.hallucination - 9 * removed * chem_effective)
|
||||
M.adjustToxLoss(-4 * removed * chem_effective)
|
||||
if(prob(10))
|
||||
M.remove_a_modifier_of_type(/datum/modifier/poisoned)
|
||||
|
||||
/datum/reagent/carthatoline
|
||||
name = "Carthatoline"
|
||||
@@ -121,6 +123,8 @@
|
||||
if(M.getToxLoss() && prob(10))
|
||||
M.vomit(1)
|
||||
M.adjustToxLoss(-8 * removed)
|
||||
if(prob(30))
|
||||
M.remove_a_modifier_of_type(/datum/modifier/poisoned)
|
||||
if(ishuman(M))
|
||||
var/mob/living/carbon/human/H = M
|
||||
var/obj/item/organ/internal/liver/L = H.internal_organs_by_name[O_LIVER]
|
||||
|
||||
|
Before Width: | Height: | Size: 362 KiB After Width: | Height: | Size: 407 KiB |
|
Before Width: | Height: | Size: 164 KiB After Width: | Height: | Size: 208 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 197 KiB After Width: | Height: | Size: 206 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 163 KiB After Width: | Height: | Size: 164 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 59 KiB |
17
polaris.dme
@@ -521,7 +521,6 @@
|
||||
#include "code\game\gamemodes\technomancer\spell_objs_helpers.dm"
|
||||
#include "code\game\gamemodes\technomancer\technomancer.dm"
|
||||
#include "code\game\gamemodes\technomancer\assistance\assistance.dm"
|
||||
#include "code\game\gamemodes\technomancer\assistance\golem.dm"
|
||||
#include "code\game\gamemodes\technomancer\devices\boots_of_speed.dm"
|
||||
#include "code\game\gamemodes\technomancer\devices\disposable_teleporter.dm"
|
||||
#include "code\game\gamemodes\technomancer\devices\gloves_of_regen.dm"
|
||||
@@ -1341,8 +1340,6 @@
|
||||
#include "code\modules\blob2\blobs\normal.dm"
|
||||
#include "code\modules\blob2\blobs\resource.dm"
|
||||
#include "code\modules\blob2\blobs\shield.dm"
|
||||
#include "code\modules\blob2\mobs\blob_mob.dm"
|
||||
#include "code\modules\blob2\mobs\spore.dm"
|
||||
#include "code\modules\blob2\overmind\overmind.dm"
|
||||
#include "code\modules\blob2\overmind\powers.dm"
|
||||
#include "code\modules\blob2\overmind\types.dm"
|
||||
@@ -2002,19 +1999,31 @@
|
||||
#include "code\modules\mob\living\simple_mob\subtypes\animal\space\carp.dm"
|
||||
#include "code\modules\mob\living\simple_mob\subtypes\animal\space\goose.dm"
|
||||
#include "code\modules\mob\living\simple_mob\subtypes\animal\space\space.dm"
|
||||
#include "code\modules\mob\living\simple_mob\subtypes\blob\blob.dm"
|
||||
#include "code\modules\mob\living\simple_mob\subtypes\blob\spore.dm"
|
||||
#include "code\modules\mob\living\simple_mob\subtypes\humanoid\clown.dm"
|
||||
#include "code\modules\mob\living\simple_mob\subtypes\humanoid\humanoid.dm"
|
||||
#include "code\modules\mob\living\simple_mob\subtypes\humanoid\kobold.dm"
|
||||
#include "code\modules\mob\living\simple_mob\subtypes\humanoid\pirates.dm"
|
||||
#include "code\modules\mob\living\simple_mob\subtypes\humanoid\russian.dm"
|
||||
#include "code\modules\mob\living\simple_mob\subtypes\humanoid\mercs\mercs.dm"
|
||||
#include "code\modules\mob\living\simple_mob\subtypes\illusion\illusion.dm"
|
||||
#include "code\modules\mob\living\simple_mob\subtypes\mechanical\combat_drone.dm"
|
||||
#include "code\modules\mob\living\simple_mob\subtypes\mechanical\golem.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\simple_mob\subtypes\mechanical\mecha\combat_mecha.dm"
|
||||
#include "code\modules\mob\living\simple_mob\subtypes\mechanical\mecha\gygax.dm"
|
||||
#include "code\modules\mob\living\simple_mob\subtypes\mechanical\mecha\hoverpod.dm"
|
||||
#include "code\modules\mob\living\simple_mob\subtypes\mechanical\mecha\mecha.dm"
|
||||
#include "code\modules\mob\living\simple_mob\subtypes\mechanical\mecha\odysseus.dm"
|
||||
#include "code\modules\mob\living\simple_mob\subtypes\mechanical\mecha\ripley.dm"
|
||||
#include "code\modules\mob\living\simple_mob\subtypes\mechanical\ward\monitor_ward.dm"
|
||||
#include "code\modules\mob\living\simple_mob\subtypes\mechanical\ward\ward.dm"
|
||||
#include "code\modules\mob\living\simple_mob\subtypes\occult\creature.dm"
|
||||
#include "code\modules\mob\living\simple_mob\subtypes\occult\faithless.dm"
|
||||
#include "code\modules\mob\living\simple_mob\subtypes\occult\constructs\_construct.dm"
|
||||
@@ -2567,7 +2576,7 @@
|
||||
#include "code\ZAS\Zone.dm"
|
||||
#include "interface\interface.dm"
|
||||
#include "interface\skin.dmf"
|
||||
#include "maps\southern_cross\southern_cross.dm"
|
||||
#include "maps\example\example.dm"
|
||||
#include "maps\submaps\space_submaps\space.dm"
|
||||
#include "maps\submaps\surface_submaps\mountains\mountains.dm"
|
||||
#include "maps\submaps\surface_submaps\mountains\mountains_areas.dm"
|
||||
|
||||