Does some AI work.

This commit is contained in:
Neerti
2018-10-14 01:50:54 -04:00
parent f98e9bf96c
commit df16611a3a
64 changed files with 1622 additions and 613 deletions

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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."

View File

@@ -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.

View File

@@ -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)

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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()

View File

@@ -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)

View File

@@ -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()

View File

@@ -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)

View File

@@ -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

View File

@@ -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>"

View File

@@ -19,6 +19,7 @@
health = 200
pass_flags = PASSTABLE
movement_cooldown = 10
poison_resist = 0.5
see_in_dark = 10

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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 ..()

View 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

View File

@@ -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)

View 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)

View File

@@ -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)

View File

@@ -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."))

View File

@@ -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
*/

View File

@@ -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

View File

@@ -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)

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 ..()

View File

@@ -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,

View File

@@ -11,6 +11,7 @@
faction = "plants"
maxHealth = 15
health = 15
poison_resist = 1.0
response_help = "prods"
response_disarm = "pushes aside"

View File

@@ -13,6 +13,7 @@
faction = "plants"
maxHealth = 250
health = 250
poison_resist = 1.0
response_help = "brushes"
response_disarm = "pushes"

View File

@@ -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).

View File

@@ -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()

View File

@@ -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,

View File

@@ -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

View File

@@ -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)

View File

@@ -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))

View File

@@ -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]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 362 KiB

After

Width:  |  Height:  |  Size: 407 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 KiB

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 59 KiB

View File

@@ -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"