Reworks the morph. Makes it a more stealth ambush oriented antag (#15208)

* Reworks the morph

* Hunger points. Reproduce. Sprites. Other tweaks

* vent open. Convert to spells. Valid checks. Minor tweaks

* undo life hack

* Icons, Fixes, No more morphed lights

* Item eat fix. All eating takes 3 seconds

* Add sounds

* Fixes and tweaks. Pass airlock ability. Ambush screen alert

* Steel review fixes

* New dead morph sprite from Pewtershmitz

* Pass airlock sprite provided by Pewtershmitz

* No barsigns or cryo cells as mimic

* Gluttany ruin change

* Color fix. No forms fix

* fix the stupid icons

* Apply suggestions from code review

Co-authored-by: dearmochi <shenesis@gmail.com>

Co-authored-by: joep van der velden <15887760+farie82@users.noreply.github.com>
Co-authored-by: dearmochi <shenesis@gmail.com>
This commit is contained in:
Farie82
2021-11-17 17:25:59 +01:00
committed by GitHub
co-authored by dearmochi joep van der velden
parent 34b40c7860
commit c4cda0930f
19 changed files with 713 additions and 101 deletions
@@ -16,7 +16,7 @@
/turf/simulated/floor/plating/lava/smooth,
/area/ruin/powered/gluttony)
"f" = (
/obj/item/reagent_containers/syringe/gluttony,
/obj/item/dnainjector/eatmut,
/turf/simulated/floor/plasteel/freezer,
/area/ruin/powered/gluttony)
"g" = (
+2 -2
View File
@@ -225,7 +225,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell))
charge_counter = charge_max
/obj/effect/proc_holder/spell/proc/perform(list/targets, recharge = 1, mob/user = usr, make_attack_logs = TRUE) //if recharge is started is important for the trigger spells
before_cast(targets)
before_cast(targets, user)
invocation()
if(user && user.ckey && make_attack_logs)
add_attack_logs(user, targets, "cast the spell [name]", ATKLOG_ALL)
@@ -244,7 +244,7 @@ GLOBAL_LIST_INIT(spells, typesof(/obj/effect/proc_holder/spell))
if(action)
action.UpdateButtonIcon()
/obj/effect/proc_holder/spell/proc/before_cast(list/targets)
/obj/effect/proc_holder/spell/proc/before_cast(list/targets, mob/user)
if(vampire_ability)
if(!before_cast_vampire(targets))
return
+3 -3
View File
@@ -10,7 +10,7 @@
var/sound2 = 'sound/weapons/zapbang.ogg'
/obj/effect/proc_holder/spell/targeted/area_teleport/perform(list/targets, recharge = 1, mob/living/user = usr)
var/thearea = before_cast(targets)
var/thearea = before_cast(targets, user)
if(!thearea || !cast_check(FALSE, FALSE, user))
revert_cast()
return
@@ -21,7 +21,7 @@
cast(targets,thearea)
after_cast(targets)
/obj/effect/proc_holder/spell/targeted/area_teleport/before_cast(list/targets)
/obj/effect/proc_holder/spell/targeted/area_teleport/before_cast(list/targets, mob/user)
var/A = null
if(!randomise_selection)
@@ -35,7 +35,7 @@
var/area/thearea = SSmapping.teleportlocs[A]
if(thearea.tele_proof && !istype(thearea, /area/wizard_station))
to_chat(usr, "A mysterious force disrupts your arcane spell matrix, and you remain where you are.")
to_chat(user, "A mysterious force disrupts your arcane spell matrix, and you remain where you are.")
return
return thearea
+203
View File
@@ -0,0 +1,203 @@
/obj/effect/proc_holder/spell/targeted/click/mimic
name = "Mimic"
desc = "Learn a new form to mimic or become one of your known forms"
clothes_req = FALSE
charge_max = 3 SECONDS
include_user = TRUE // To change forms
action_icon_state = "genetic_morph"
allowed_type = /atom/movable
auto_target_single = FALSE
click_radius = -1
selection_activated_message = "<span class='sinister'>Click on a target to remember it's form. Click on yourself to change form.</span>"
create_logs = FALSE
action_icon_state = "morph_mimic"
/// Which form is currently selected
var/datum/mimic_form/selected_form
/// Which forms the user can become
var/list/available_forms = list()
/// How many forms the user can remember
var/max_forms = 5
/// Which index will be overriden next when the user wants to remember another form
var/next_override_index = 1
/// If a message is shown when somebody examines the user from close range
var/perfect_disguise = FALSE
var/static/list/black_listed_form_types = list(/obj/screen, /obj/singularity, /obj/effect, /mob/living/simple_animal/hostile/megafauna, /atom/movable/lighting_object, /obj/machinery/dna_vault,
/obj/machinery/power/bluespace_tap, /obj/structure/sign/barsign, /obj/machinery/atmospherics/unary/cryo_cell)
/obj/effect/proc_holder/spell/targeted/click/mimic/valid_target(atom/target, user)
if(is_type_in_list(target, black_listed_form_types))
return FALSE
if(istype(target, /atom/movable))
var/atom/movable/AM = target
if(AM.bound_height > world.icon_size || AM.bound_width > world.icon_size)
return FALSE // No multitile structures
if(user != target && istype(target, /mob/living/simple_animal/hostile/morph))
return FALSE
return ..()
/obj/effect/proc_holder/spell/targeted/click/mimic/cast(list/targets, mob/user)
var/atom/movable/A = targets[1]
if(A == user)
INVOKE_ASYNC(src, .proc/pick_form, user)
return
INVOKE_ASYNC(src, .proc/remember_form, A, user)
/obj/effect/proc_holder/spell/targeted/click/mimic/proc/remember_form(atom/movable/A, mob/user)
if(A.name in available_forms)
to_chat(user, "<span class='warning'>[A] is already an available form.</span>")
revert_cast(user)
return
if(length(available_forms) >= max_forms)
to_chat(user, "<span class='warning'>You start to forget the form of [available_forms[next_override_index]] to learn a new one.</span>")
to_chat(user, "<span class='sinister'>You start remembering the form of [A].</span>")
if(!do_after(user, 2 SECONDS, FALSE, user))
to_chat(user, "<span class='warning'>You lose focus.</span>")
return
// Forget the old form if needed
if(length(available_forms) >= max_forms)
qdel(available_forms[available_forms[next_override_index]]) // Delete the value using the key
available_forms[next_override_index++] = A.name
// Reset if needed
if(next_override_index > max_forms)
next_override_index = 1
available_forms[A.name] = new /datum/mimic_form(A, user)
to_chat(user, "<span class='sinister'>You learn the form of [A].</span>")
/obj/effect/proc_holder/spell/targeted/click/mimic/proc/pick_form(mob/user)
if(!length(available_forms) && !selected_form)
to_chat(user, "<span class='warning'>No available forms. Learn more forms by using this spell on other objects first.</span>")
revert_cast(user)
return
var/list/forms = list()
if(selected_form)
forms += "Original Form"
forms += available_forms.Copy()
var/what = input(user, "Which form do you want to become?", "Mimic") as null|anything in forms
if(!what)
to_chat(user, "<span class='notice'>You decide against changing forms.</span>")
revert_cast(user)
return
if(what == "Original Form")
restore_form(user)
return
to_chat(user, "<span class='sinister'>You start becoming [what].</span>")
if(!do_after(user, 2 SECONDS, FALSE, user))
to_chat(user, "<span class='warning'>You lose focus.</span>")
return
take_form(available_forms[what], user)
/obj/effect/proc_holder/spell/targeted/click/mimic/proc/take_form(datum/mimic_form/form, mob/user)
var/old_name = "[user]"
if(ishuman(user))
// Not fully finished yet
var/mob/living/carbon/human/H = user
H.name_override = form.name
else
user.appearance = form.appearance
user.transform = initial(user.transform)
user.pixel_y = initial(user.pixel_y)
user.pixel_x = initial(user.pixel_x)
user.layer = MOB_LAYER // Avoids weirdness when mimicing something below the vent layer
playsound(user, "bonebreak", 75, TRUE)
show_change_form_message(user, old_name, "[user]")
user.create_log(MISC_LOG, "Mimicked into [user]")
if(!selected_form)
RegisterSignal(user, COMSIG_PARENT_EXAMINE, .proc/examine_override)
RegisterSignal(user, COMSIG_MOB_DEATH, .proc/on_death)
selected_form = form
/obj/effect/proc_holder/spell/targeted/click/mimic/proc/show_change_form_message(mob/user, old_name, new_name)
user.visible_message("<span class='warning'>[old_name] contorts and slowly becomes [new_name]!</span>", "<span class='sinister'>You take form of [new_name].</span>", "You hear loud cracking noises!")
/obj/effect/proc_holder/spell/targeted/click/mimic/proc/restore_form(mob/user, show_message = TRUE)
selected_form = null
var/old_name = "[user]"
user.cut_overlays()
user.icon = initial(user.icon)
user.icon_state = initial(user.icon_state)
if(ishuman(user))
var/mob/living/carbon/human/H = user
H.name_override = null
H.regenerate_icons()
else
user.name = initial(user.name)
user.desc = initial(user.desc)
user.color = initial(user.color)
playsound(user, "bonebreak", 150, TRUE)
if(show_message)
show_restore_form_message(user, old_name, "[user]")
UnregisterSignal(user, list(COMSIG_PARENT_EXAMINE, COMSIG_MOB_DEATH))
/obj/effect/proc_holder/spell/targeted/click/mimic/proc/show_restore_form_message(mob/user, old_name, new_name)
user.visible_message("<span class='warning'>[old_name] shakes and contorts and quickly becomes [new_name]!</span>", "<span class='sinister'>You take return to your normal self.</span>", "You hear loud cracking noises!")
/obj/effect/proc_holder/spell/targeted/click/mimic/proc/examine_override(datum/source, mob/user, list/examine_list)
examine_list.Cut()
examine_list += selected_form.examine_text
if(!perfect_disguise && get_dist(user, source) <= 3)
examine_list += "<span class='warning'>It doesn't look quite right...</span>"
/obj/effect/proc_holder/spell/targeted/click/mimic/proc/on_death(mob/user, gibbed)
if(!gibbed)
restore_form(user, FALSE)
show_death_message(user)
/obj/effect/proc_holder/spell/targeted/click/mimic/proc/show_death_message(mob/user)
user.visible_message("<span class='warning'>[user] shakes and contorts as [user.p_they()] die[user.p_s()], returning to [user.p_their()] true form!</span>", "<span class='deadsay'>Your disguise fails as your life forces drain away.</span>", "You hear loud cracking noises followed by a thud!")
/datum/mimic_form
/// How does the form look like?
var/appearance
/// What is the examine text paired with this form
var/examine_text
/// What the name of the form is
var/name
/datum/mimic_form/New(atom/movable/form, mob/user)
appearance = form.appearance
examine_text = form.examine(user)
name = form.name
/obj/effect/proc_holder/spell/targeted/click/mimic/morph
action_background_icon_state = "bg_morph"
/obj/effect/proc_holder/spell/targeted/click/mimic/morph/valid_target(atom/target, user)
if(target != user && istype(target, /mob/living/simple_animal/hostile/morph))
return FALSE
return ..()
/obj/effect/proc_holder/spell/targeted/click/mimic/morph/take_form(datum/mimic_form/form, mob/living/simple_animal/hostile/morph/user)
..()
user.assume()
/obj/effect/proc_holder/spell/targeted/click/mimic/morph/restore_form(mob/living/simple_animal/hostile/morph/user, show_message = TRUE)
..()
user.restore()
/obj/effect/proc_holder/spell/targeted/click/mimic/morph/show_change_form_message(mob/user, old_name, new_name)
user.visible_message("<span class='warning'>[old_name] suddenly twists and changes shape, becoming a copy of [new_name]!</span>", \
"<span class='notice'>You twist your body and assume the form of [new_name].</span>")
/obj/effect/proc_holder/spell/targeted/click/mimic/morph/show_restore_form_message(mob/user, old_name, new_name)
user.visible_message("<span class='warning'>[old_name] suddenly collapses in on itself, dissolving into a pile of green flesh!</span>", \
"<span class='notice'>You reform to your normal body.</span>")
/obj/effect/proc_holder/spell/targeted/click/mimic/morph/show_death_message(mob/user)
user.visible_message("<span class='warning'>[user] twists and dissolves into a pile of green flesh!</span>", \
"<span class='userdanger'>Your skin ruptures! Your flesh breaks apart! No disguise can ward off de--</span>")
+228 -83
View File
@@ -1,4 +1,5 @@
#define MORPH_COOLDOWN 50
#define MORPHED_SPEED 2.5
#define ITEM_EAT_COST 5
/mob/living/simple_animal/hostile/morph
name = "morph"
@@ -10,11 +11,12 @@
icon_state = "morph"
icon_living = "morph"
icon_dead = "morph_dead"
speed = 2
speed = 1.5
a_intent = INTENT_HARM
stop_automated_movement = 1
status_flags = CANPUSH
pass_flags = PASSTABLE
move_resist = MOVE_FORCE_STRONG // Fat being
ventcrawler = 2
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
@@ -24,8 +26,8 @@
health = 150
environment_smash = 1
obj_damage = 50
melee_damage_lower = 20
melee_damage_upper = 20
melee_damage_lower = 15
melee_damage_upper = 15
see_in_dark = 8
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
vision_range = 1 // Only attack when target is close
@@ -34,18 +36,40 @@
attack_sound = 'sound/effects/blobattack.ogg'
butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab = 2)
var/morphed = 0
var/atom/movable/form = null
var/morph_time = 0
/// If the morph is disguised or not
var/morphed = FALSE
/// If the morph is ready to perform an ambush
var/ambush_prepared = FALSE
/// How much damage a successful ambush attack does
var/ambush_damage = 25
/// How much weaken a successful ambush attack applies
var/ambush_weaken = 3
/// The spell the morph uses to morph
var/obj/effect/proc_holder/spell/targeted/click/mimic/morph/mimic_spell
/// The ambush action used by the morph
var/obj/effect/proc_holder/spell/morph/ambush/ambush_spell
/// The spell the morph uses to pass through airlocks
var/obj/effect/proc_holder/spell/targeted/click/pass_airlock/pass_airlock_spell
var/list/examine_text_list
/// How much the morph has gathered in terms of food. Used to reproduce and such
var/gathered_food = 20 // Start with a bit to use abilities
var/playstyle_string = "<b><font size=3 color='red'>You are a morph.</font><br> As an abomination created primarily with changeling cells, \
you may take the form of anything nearby by shift-clicking it. This process will alert any nearby \
observers, and can only be performed once every five seconds.<br> While morphed, you move faster, but do \
less damage. In addition, anyone within three tiles will note an uncanny wrongness if examining you. \
You can restore yourself to your original form while morphed by shift-clicking yourself.<br> \
Finally, you can attack any item or dead creature to consume it - creatures will restore 1/3 of your max health.</b>"
/mob/living/simple_animal/hostile/morph/Initialize(mapload)
. = ..()
mimic_spell = new
AddSpell(mimic_spell)
ambush_spell = new
AddSpell(ambush_spell)
AddSpell(new /obj/effect/proc_holder/spell/morph/reproduce)
AddSpell(new /obj/effect/proc_holder/spell/morph/open_vent)
pass_airlock_spell = new
AddSpell(pass_airlock_spell)
/mob/living/simple_animal/hostile/morph/Stat(Name, Value)
..()
if(statpanel("Status"))
stat(null, "Food Stored: [gathered_food]")
return TRUE
/mob/living/simple_animal/hostile/morph/wizard
name = "magical morph"
@@ -57,78 +81,117 @@
AddSpell(new /obj/effect/proc_holder/spell/targeted/smoke)
AddSpell(new /obj/effect/proc_holder/spell/targeted/forcewall)
/mob/living/simple_animal/hostile/morph/examine(mob/user)
if(morphed)
. = examine_text_list.Copy()
if(get_dist(user, src) <= 3)
. += "<span class='warning'>It doesn't look quite right...</span>"
else
. = ..()
/mob/living/simple_animal/hostile/morph/proc/allowed(atom/movable/A) // make it into property/proc ? not sure if worth it
if(istype(A,/obj/screen))
return 0
if(istype(A,/obj/singularity))
return 0
if(istype(A,/mob/living/simple_animal/hostile/morph))
return 0
return 1
/mob/living/simple_animal/hostile/morph/proc/try_eat(atom/movable/A)
var/food_value = calc_food_gained(A)
if(food_value + gathered_food < 0)
to_chat(src, "<span class='warning'>You can't force yourself to eat more disgusting items. Eat some living things first.</span>")
return
var/eat_self_message
if(food_value < 0)
eat_self_message = "<span class='warning'>You start eating [A]... disgusting....</span>"
else
eat_self_message = "<span class='notice'>You start eating [A].</span>"
visible_message("<span class='warning'>[src] starts eating [target]!</span>", eat_self_message, "You hear loud crunching!")
if(do_after(src, 3 SECONDS, target = A))
if(food_value + gathered_food < 0)
to_chat(src, "<span class='warning'>You can't force yourself to eat more disgusting items. Eat some living things first.</span>")
return
eat(A)
/mob/living/simple_animal/hostile/morph/proc/eat(atom/movable/A)
if(A && A.loc != src)
visible_message("<span class='warning'>[src] swallows [A] whole!</span>")
var/mob/living/carbon/human/H = A
if(istype(H) && H.w_uniform && istype(H.w_uniform, /obj/item/clothing/under))
var/obj/item/clothing/under/U = H.w_uniform
U.turn_sensors_off()
A.extinguish_light()
A.forceMove(src)
return 1
return 0
var/food_value = calc_food_gained(A)
add_food(food_value)
if(food_value > 0)
adjustHealth(-food_value)
add_attack_logs(src, A, "morph ate")
return TRUE
return FALSE
/mob/living/simple_animal/hostile/morph/ShiftClickOn(atom/movable/A)
if(morph_time <= world.time && !stat)
if(A == src)
restore()
return
if(istype(A) && allowed(A))
assume(A)
else
to_chat(src, "<span class='warning'>Your chameleon skin is still repairing itself!</span>")
..()
/mob/living/simple_animal/hostile/morph/proc/calc_food_gained(mob/living/L)
if(!istype(L))
return -ITEM_EAT_COST // Anything other than a tasty mob will make me sad ;(
var/gained_food = max(5, 10 * L.mob_size) // Tiny things are worth less
if(ishuman(L) && !ismonkeybasic(L))
gained_food += 10 // Humans are extra tasty
/mob/living/simple_animal/hostile/morph/proc/assume(atom/movable/target)
morphed = 1
form = target
visible_message("<span class='warning'>[src] suddenly twists and changes shape, becoming a copy of [target]!</span>", \
"<span class='notice'>You twist your body and assume the form of [target].</span>")
return gained_food
appearance = target.appearance
transform = initial(transform)
pixel_y = initial(pixel_y)
pixel_x = initial(pixel_x)
//Morphed is weaker
/mob/living/simple_animal/hostile/morph/proc/use_food(amount)
if(amount > gathered_food)
return FALSE
add_food(-amount)
return TRUE
/**
* Adds the given amount of food to the gathered food and updates the actions.
* Does not include a check to see if it goes below 0 or not
*/
/mob/living/simple_animal/hostile/morph/proc/add_food(amount)
gathered_food += amount
for(var/obj/effect/proc_holder/spell/morph/MS in mind.spell_list)
MS.updateButtonIcon()
/mob/living/simple_animal/hostile/morph/proc/assume()
morphed = TRUE
//Morph is weaker initially when disguised
melee_damage_lower = 5
melee_damage_upper = 5
speed = 0
examine_text_list = form.examine(src)
morph_time = world.time + MORPH_COOLDOWN
return
speed = MORPHED_SPEED
ambush_spell.updateButtonIcon()
pass_airlock_spell.updateButtonIcon()
move_resist = MOVE_FORCE_DEFAULT // They become more fragile and easier to move
/mob/living/simple_animal/hostile/morph/proc/restore()
if(!morphed)
return
morphed = 0
form = null
examine_text_list = null // Free that memory
visible_message("<span class='warning'>[src] suddenly collapses in on itself, dissolving into a pile of green flesh!</span>", \
"<span class='notice'>You reform to your normal body.</span>")
name = initial(name)
icon = initial(icon)
icon_state = initial(icon_state)
overlays.Cut()
morphed = FALSE
//Baseline stats
melee_damage_lower = initial(melee_damage_lower)
melee_damage_upper = initial(melee_damage_upper)
speed = initial(speed)
if(ambush_prepared)
to_chat(src, "<span class='warning'>The ambush potential has faded as you take your true form.</span>")
failed_ambush()
pass_airlock_spell.updateButtonIcon()
move_resist = MOVE_FORCE_STRONG // Return to their fatness
/mob/living/simple_animal/hostile/morph/proc/prepare_ambush()
ambush_prepared = TRUE
to_chat(src, "<span class='sinister'>You are ready to ambush any unsuspected target. Your next attack will hurt a lot more and weaken the target! Moving will break your focus. Standing still will perfect your disguise.</span>")
apply_status_effect(/datum/status_effect/morph_ambush)
RegisterSignal(src, COMSIG_MOVABLE_MOVED, .proc/on_move)
/mob/living/simple_animal/hostile/morph/proc/failed_ambush()
ambush_prepared = FALSE
ambush_spell.updateButtonIcon()
mimic_spell.perfect_disguise = FALSE // Reset the perfect disguise
remove_status_effect(/datum/status_effect/morph_ambush)
UnregisterSignal(src, COMSIG_MOVABLE_MOVED)
/mob/living/simple_animal/hostile/morph/proc/perfect_ambush()
mimic_spell.perfect_disguise = TRUE // Reset the perfect disguise
to_chat(src, "<span class='sinister'>You've perfected your disguise. Making you indistinguishable from the real form!</span>")
/mob/living/simple_animal/hostile/morph/proc/on_move()
failed_ambush()
to_chat(src, "<span class='warning'>You moved out of your ambush spot!</span>")
morph_time = world.time + MORPH_COOLDOWN
/mob/living/simple_animal/hostile/morph/death(gibbed)
. = ..()
@@ -140,40 +203,122 @@
// Only execute the below if we successfully died
if(!.)
return FALSE
if(morphed)
visible_message("<span class='warning'>[src] twists and dissolves into a pile of green flesh!</span>", \
"<span class='userdanger'>Your skin ruptures! Your flesh breaks apart! No disguise can ward off de--</span>")
restore()
/mob/living/simple_animal/hostile/morph/Aggro() // automated only
..()
restore()
/mob/living/simple_animal/hostile/morph/attack_hand(mob/living/carbon/human/M)
if(ambush_prepared)
to_chat(M, "<span class='warning'>[src] feels a bit different from normal... it feels more.. </span><span class='userdanger'>SLIMEY?!</span>")
ambush_attack(M, TRUE)
else
return ..()
#define MORPH_ATTACKED if((. = ..()) && morphed) mimic_spell.restore_form(src)
/mob/living/simple_animal/hostile/morph/attackby(obj/item/O, mob/living/user)
if(user.a_intent == INTENT_HELP && ambush_prepared)
to_chat(user, "<span class='warning'>You try to use [O] on [src]... it seems different than no-</span>")
ambush_attack(user, TRUE)
return TRUE
MORPH_ATTACKED
/mob/living/simple_animal/hostile/morph/attack_animal(mob/living/simple_animal/M)
if(M.a_intent == INTENT_HELP && ambush_prepared)
to_chat(M, "<span class='notice'>You nuzzle [src].</span><span class='danger'> And [src] nuzzles back!</span>")
ambush_attack(M, TRUE)
return TRUE
MORPH_ATTACKED
/mob/living/simple_animal/hostile/morph/attack_hulk(mob/living/carbon/human/user, does_attack_animation) // Me SMASH
MORPH_ATTACKED
/mob/living/simple_animal/hostile/morph/attack_larva(mob/living/carbon/alien/larva/L)
MORPH_ATTACKED
/mob/living/simple_animal/hostile/morph/attack_alien(mob/living/carbon/alien/humanoid/M)
MORPH_ATTACKED
/mob/living/simple_animal/hostile/morph/attack_tk(mob/user)
MORPH_ATTACKED
/mob/living/simple_animal/hostile/morph/attack_slime(mob/living/simple_animal/slime/M)
MORPH_ATTACKED
#undef MORPH_ATTACKED
/mob/living/simple_animal/hostile/morph/proc/ambush_attack(mob/living/L, touched)
ambush_prepared = FALSE
var/total_weaken = ambush_weaken
var/total_damage = ambush_damage
if(touched) // Touching a morph while he's ready to kill you is a bad idea
total_weaken *= 2
total_damage *= 2
L.Weaken(total_weaken)
L.apply_damage(total_damage, BRUTE)
add_attack_logs(src, L, "morph ambush attacked")
do_attack_animation(L, ATTACK_EFFECT_BITE)
visible_message("<span class='danger'>[src] suddenly leaps towards [L]!</span>", "<span class='warning'>You strike [L] when [L.p_they()] least expected it!</span>", "You hear a horrible crunch!")
mimic_spell.restore_form(src)
/mob/living/simple_animal/hostile/morph/LoseAggro()
vision_range = initial(vision_range)
/mob/living/simple_animal/hostile/morph/AIShouldSleep(list/possible_targets)
. = ..()
if(.)
if(. && !morphed)
var/list/things = list()
for(var/atom/movable/A in view(src))
if(allowed(A))
if(mimic_spell.valid_target(A, src))
things += A
var/atom/movable/T = pick(things)
assume(T)
mimic_spell.take_form(new /datum/mimic_form(T, src), src)
prepare_ambush() // They cheat okay
/mob/living/simple_animal/hostile/morph/AttackingTarget()
if(isliving(target)) // Eat Corpses to regen health
var/mob/living/L = target
if(L.stat == DEAD)
if(do_after(src, 30, target = L))
if(eat(L))
adjustHealth(-50)
return
try_eat(L)
return TRUE
if(ambush_prepared)
ambush_attack(L)
return TRUE // No double attack
else if(istype(target,/obj/item)) // Eat items just to be annoying
var/obj/item/I = target
if(!I.anchored)
if(do_after(src, 20, target = I))
eat(I)
return
return ..()
try_eat(I)
return TRUE
. = ..()
if(. && morphed)
mimic_spell.restore_form(src)
/mob/living/simple_animal/hostile/morph/proc/make_morph_antag(give_default_objectives = TRUE)
mind.assigned_role = SPECIAL_ROLE_MORPH
mind.special_role = SPECIAL_ROLE_MORPH
SSticker.mode.traitors |= mind
to_chat(src, "<b><font size=3 color='red'>You are a morph.</font><br></b>")
to_chat(src, "<span class='sinister'>You hunger for living beings and desire to procreate. Achieve this goal by ambushing unsuspecting pray using your abilities.</span>")
to_chat(src, "<span class='specialnotice'>As an abomination created primarily with changeling cells you may take the form of anything nearby by using your <span class='specialnoticebold'>Mimic ability</span>.</span>")
to_chat(src, "<span class='specialnotice'>The transformation will not go unnoticed for bystanding observers.</span>")
to_chat(src, "<span class='specialnoticebold'>While morphed</span><span class='specialnotice'>, you move slower and do less damage. In addition, anyone within three tiles will note an uncanny wrongness if examining you.</span>")
to_chat(src, "<span class='specialnotice'>From this form you can however <span class='specialnoticebold'>Prepare an Ambush</span> using your ability.</span>")
to_chat(src, "<span class='specialnotice'>This will allow you to deal a lot of damage the first hit. And if they touch you then even more.</span>")
to_chat(src, "<span class='specialnotice'>Finally, you can attack any item or dead creature to consume it - creatures will restore 1/3 of your max health and will add to your stored food while eating items will reduce your stored food</span>.")
to_chat(src, "<span class='motd'>For more information, check the wiki page: ([GLOB.configuration.url.wiki_url]/index.php/Morph)</span>")
SEND_SOUND(src, sound('sound/magic/mutate.ogg'))
if(give_default_objectives)
var/datum/objective/eat = new /datum/objective
eat.owner = mind
eat.explanation_text = "Eat as many living beings as possible to still the hunger within you."
eat.completed = TRUE
mind.objectives += eat
var/datum/objective/procreate = new /datum/objective
procreate.owner = mind
procreate.explanation_text = "Split yourself in as many other [name]'s as possible!"
procreate.completed = TRUE
mind.objectives += procreate
mind.announce_objectives()
#undef MORPHED_SPEED
#undef ITEM_EAT_COST
@@ -22,12 +22,7 @@
return
var/mob/living/simple_animal/hostile/morph/S = new /mob/living/simple_animal/hostile/morph(pick(GLOB.xeno_spawn))
player_mind.transfer_to(S)
player_mind.assigned_role = SPECIAL_ROLE_MORPH
player_mind.special_role = SPECIAL_ROLE_MORPH
SSticker.mode.traitors |= player_mind
to_chat(S, S.playstyle_string)
to_chat(S, "<span class='motd'>For more information, check the wiki page: ([GLOB.configuration.url.wiki_url]/index.php/Morph)</span>")
SEND_SOUND(S, sound('sound/magic/mutate.ogg'))
S.make_morph_antag()
message_admins("[key_of_morph] has been made into morph by an event.")
log_game("[key_of_morph] was spawned as a morph by an event.")
@@ -0,0 +1,60 @@
#define MORPH_AMBUSH_PERFECTION_TIME 15 SECONDS
/obj/effect/proc_holder/spell/morph/ambush
name = "Prepare Ambush"
desc = "Prepare an ambush. Dealing significantly more damage on the first hit and you will weaken the target. Only works while morphed. If the target tries to use you with their hands then you will do even more damage. \
Keeping still for another 10 seconds will perfect your disguise."
action_icon_state = "morph_ambush"
charge_max = 8 SECONDS
/obj/effect/proc_holder/spell/morph/ambush/cast_check(charge_check = TRUE, start_recharge = TRUE, mob/living/simple_animal/hostile/morph/user = usr)
if(!istype(user))
return ..() // Message is in there
if(!user.morphed)
to_chat(user, "<span class='warning'>You can only prepare an ambush if you're disguised!</span>")
return FALSE
if(user.ambush_prepared)
to_chat(user, "<span class='warning'>You are already prepared!</span>")
return FALSE
return ..()
/obj/effect/proc_holder/spell/morph/ambush/can_cast(mob/living/simple_animal/hostile/morph/user, charge_check, show_message)
if(!istype(user) || !user.morphed || user.ambush_prepared)
return FALSE
return ..()
/obj/effect/proc_holder/spell/morph/ambush/choose_targets(mob/user)
perform(list(user), TRUE, user, FALSE)
/obj/effect/proc_holder/spell/morph/ambush/cast(list/targets, mob/living/simple_animal/hostile/morph/user)
to_chat(user, "<span class='sinister'>You start preparing an ambush.</span>")
if(!do_after(user, 6 SECONDS, FALSE, user, TRUE, list(CALLBACK(src, .proc/prepare_check, user)), FALSE))
if(!user.morphed)
to_chat(user, "<span class='warning'>You need to stay morphed to prepare the ambush!</span>")
return
to_chat(user, "<span class='warning'>You need to stay still to prepare the ambush!</span>")
return
user.prepare_ambush()
/obj/effect/proc_holder/spell/morph/ambush/proc/prepare_check(mob/living/simple_animal/hostile/morph/user)
return !user.morphed
/datum/status_effect/morph_ambush
id = "morph_ambush"
duration = -1
tick_interval = MORPH_AMBUSH_PERFECTION_TIME
alert_type = /obj/screen/alert/status_effect/morph_ambush
/datum/status_effect/morph_ambush/tick()
STOP_PROCESSING(SSfastprocess, src)
var/mob/living/simple_animal/hostile/morph/M = owner
M.perfect_ambush()
linked_alert.name = "Perfect Ambush!"
linked_alert.desc = "You have prepared an ambush! Your disguise is flawless!"
/obj/screen/alert/status_effect/morph_ambush
name = "Ambush!"
desc = "You have prepared an ambush!"
icon_state = "morph_ambush"
#undef MORPH_AMBUSH_PERFECTION_TIME
@@ -0,0 +1,36 @@
/obj/effect/proc_holder/spell/morph
action_background_icon_state = "bg_morph"
clothes_req = FALSE
/// How much food it costs the morph to use this
var/hunger_cost = 0
/obj/effect/proc_holder/spell/morph/Initialize(mapload)
. = ..()
if(hunger_cost)
name = "[name] ([hunger_cost])"
/obj/effect/proc_holder/spell/morph/cast_check(charge_check = TRUE, start_recharge = TRUE, mob/living/simple_animal/hostile/morph/user = usr)
if(!istype(user))
to_chat(user, "<span class='warning'>You should not be able to use this abilty! Report this as a bug on github please.</span>")
stack_trace()
log_debug("[user] has the spell [src] while he is not a morph")
return FALSE
if(user.gathered_food < hunger_cost)
to_chat(user, "<span class='warning'>You require at least [hunger_cost] stored food to use this ability!</span>")
return FALSE
return ..()
/obj/effect/proc_holder/spell/morph/can_cast(mob/living/simple_animal/hostile/morph/user, charge_check, show_message)
if(!istype(user) || user.gathered_food < hunger_cost)
return FALSE
return ..()
/obj/effect/proc_holder/spell/morph/before_cast(list/targets, mob/living/simple_animal/hostile/morph/user)
user.use_food(hunger_cost)
if(hunger_cost)
to_chat(user, "<span class='boldnotice'>You have [user.gathered_food] left to use.</span>")
/obj/effect/proc_holder/spell/morph/revert_cast(mob/living/simple_animal/hostile/morph/user)
user.add_food(hunger_cost)
to_chat(user, "<span class='boldnotice'>You have [user.gathered_food] left to use.</span>")
..()
@@ -0,0 +1,46 @@
/obj/effect/proc_holder/spell/morph/open_vent
name = "Open Vents"
desc = "Spit out acidic puke on nearby vents or scrubbers. Will take a little while for the acid to take effect. Not usable from inside a vent."
action_icon_state = "acid_vent"
charge_max = 10 SECONDS
hunger_cost = 10
/obj/effect/proc_holder/spell/morph/open_vent/choose_targets(mob/user)
var/list/targets = list()
for(var/obj/machinery/atmospherics/unary/U in view(user, 1))
if(istype(U, /obj/machinery/atmospherics/unary/vent_scrubber))
var/obj/machinery/atmospherics/unary/vent_scrubber/S = U
if(S.welded)
targets += S
else if(istype(U, /obj/machinery/atmospherics/unary/vent_pump))
var/obj/machinery/atmospherics/unary/vent_scrubber/V = U
if(V.welded)
targets += V
perform(targets, TRUE, user)
/obj/effect/proc_holder/spell/morph/open_vent/cast(list/targets, mob/user)
if(!length(targets))
to_chat(user, "<span class='warning'>No nearby welded vents found!</span>")
revert_cast(user)
return
to_chat(user, "<span class='sinister'>You begin regurgitating up some acidic puke!</span>")
if(!do_after(user, 2 SECONDS, FALSE, user))
to_chat(user, "<span class='warning'>You swallow the acid again.</span>")
revert_cast(user)
return
for(var/thing in targets)
var/obj/machinery/atmospherics/unary/U = thing
U.add_overlay(GLOB.acid_overlay, TRUE)
addtimer(CALLBACK(src, .proc/unweld_vent, U), 2 SECONDS)
playsound(U, 'sound/items/welder.ogg', 100, TRUE)
/obj/effect/proc_holder/spell/morph/open_vent/proc/unweld_vent(obj/machinery/atmospherics/unary/U)
if(istype(U, /obj/machinery/atmospherics/unary/vent_scrubber))
var/obj/machinery/atmospherics/unary/vent_scrubber/S = U
S.welded = FALSE
else if(istype(U, /obj/machinery/atmospherics/unary/vent_pump))
var/obj/machinery/atmospherics/unary/vent_scrubber/V = U
V.welded = FALSE
U.update_icon()
U.cut_overlay(GLOB.acid_overlay, TRUE)
@@ -0,0 +1,52 @@
// TODO refactor when spell code is component based instead of OO based
/obj/effect/proc_holder/spell/targeted/click/pass_airlock
name = "Pass Airlock"
desc = "Reform yourself so you can fit through a non bolted airlock. Takes a while to do and can only be used in a non disguised form."
action_background_icon_state = "bg_morph"
action_icon_state = "morph_airlock"
clothes_req = FALSE
charge_max = 10 SECONDS
range = 1
allowed_type = /obj/machinery/door/airlock
selection_activated_message = "<span class='sinister'>Click on an airlock to try pass it.</span>"
click_radius = -1
/obj/effect/proc_holder/spell/targeted/click/pass_airlock/cast_check(charge_check = TRUE, start_recharge = TRUE, mob/living/simple_animal/hostile/morph/user = usr)
if(!istype(user))
to_chat(user, "<span class='warning'>You should not be able to use this abilty! Report this as a bug on github please.</span>")
stack_trace()
log_debug("[user] has the spell [src] while he is not a morph")
return FALSE
if(user.morphed)
to_chat(user, "<span class='warning'>You can only pass through airlocks in your true form!</span>")
return FALSE
return ..()
/obj/effect/proc_holder/spell/targeted/click/pass_airlock/can_cast(mob/living/simple_animal/hostile/morph/user, charge_check, show_message)
if(!istype(user) || user.morphed)
return FALSE
return ..()
/obj/effect/proc_holder/spell/targeted/click/pass_airlock/cast(list/targets, mob/living/simple_animal/hostile/morph/user)
var/obj/machinery/door/airlock/A = targets[1]
if(A.locked)
to_chat(user, "<span class='warning'>[A] is bolted shut! You're unable to create a crack to pass through!</span>")
revert_cast(user)
return
user.visible_message("<span class='warning'>[user] starts pushing itself against [A]!</span>", "<span class='sinister'>You try to pry [A] open enough to get through.</span>")
if(!do_after(user, 6 SECONDS, FALSE, user, TRUE, list(CALLBACK(src, .proc/pass_check, user, A)), FALSE))
if(user.morphed)
to_chat(user, "<span class='warning'>You need to stay in your true form to pass through [A]!</span>")
else if(A.locked)
to_chat(user, "<span class='warning'>[A] is bolted shut! You're unable to create a crack to pass through!</span>")
else
to_chat(user, "<span class='warning'>You need to stay still to pass through [A]!</span>")
revert_cast(user)
return
user.visible_message("<span class='warning'>[user] briefly opens [A] slightly and passes through!</span>", "<span class='sinister'>You slide through the open crack in [A].</span>")
user.forceMove(A.loc) // Move into the turf of the airlock
/obj/effect/proc_holder/spell/targeted/click/pass_airlock/proc/pass_check(mob/living/simple_animal/hostile/morph/user, obj/machinery/door/airlock/A)
return user.morphed || A.locked
@@ -0,0 +1,42 @@
/obj/effect/proc_holder/spell/morph/reproduce
name = "Reproduce"
desc = "Split yourself in half making a new morph. Can only be used while on a floor. Makes you temporarily unable to vent crawl."
hunger_cost = 150 // 5 humans
charge_max = 30 SECONDS
action_icon_state = "morph_reproduce"
/obj/effect/proc_holder/spell/morph/reproduce/choose_targets(mob/user)
perform(list(user), TRUE, user, FALSE)
/obj/effect/proc_holder/spell/morph/reproduce/cast_check(charge_check = TRUE, start_recharge = TRUE, mob/living/simple_animal/hostile/morph/user = usr)
if(!isturf(user.loc))
to_chat(user, "<span class='warning'>You can only split while on flooring!</span>")
return FALSE
return ..()
/obj/effect/proc_holder/spell/morph/reproduce/can_cast(mob/living/simple_animal/hostile/morph/user, charge_check, show_message)
. = ..()
if(!.)
return
if(!isturf(user.loc))
return FALSE
/obj/effect/proc_holder/spell/morph/reproduce/cast(list/targets, mob/living/simple_animal/hostile/morph/user)
to_chat(user, "<span class='sinister'>You prepare to split in two, making you unable to vent crawl!</span>")
user.ventcrawler = FALSE // Temporarily disable it
var/list/candidates = SSghost_spawns.poll_candidates("Do you want to play as a morph?", ROLE_MORPH, TRUE, poll_time = 10 SECONDS, source = /mob/living/simple_animal/hostile/morph)
if(!length(candidates))
to_chat(user, "<span class='warning'>Your body refuses to split at the moment. Try again later.</span>")
revert_cast(user)
user.ventcrawler = initial(user.ventcrawler) // re enable the crawling
return
var/mob/C = pick(candidates)
var/mob/living/simple_animal/hostile/morph/new_morph = new /mob/living/simple_animal/hostile/morph(get_turf(user))
var/datum/mind/player_mind = new /datum/mind(C.key)
player_mind.active = TRUE
player_mind.transfer_to(new_morph)
new_morph.make_morph_antag()
user.create_log(MISC_LOG, "Made a new morph using [src]", new_morph)
user.ventcrawler = initial(user.ventcrawler) // re enable the crawling
+1 -1
View File
@@ -38,7 +38,7 @@
C.update_handcuffed()
return
else
..()
return ..()
/obj/item/wirecutters/suicide_act(mob/user)
user.visible_message("<span class='suicide'>[user] is cutting at [user.p_their()] arteries with [src]! It looks like [user.p_theyre()] trying to commit suicide!</span>")
@@ -395,6 +395,28 @@
block = GLOB.smallsizeblock
..()
/obj/item/dnainjector/eatmut
name = "DNA-Injector (Matter Eater)"
desc = "Gives you an appetite for anything."
datatype = DNA2_BUF_SE
value = 0xFFF
forcedmutation = TRUE
/obj/item/dnainjector/eatmut/Initialize()
block = GLOB.eatblock
return ..()
/obj/item/dnainjector/antieat
name = "DNA-Injector (Anti-Matter Eater)"
desc = "Makes you regain your normal appetite."
datatype = DNA2_BUF_SE
value = 0x001
forcedmutation = TRUE
/obj/item/dnainjector/antieat/Initialize()
block = GLOB.eatblock
return ..()
/////////////////////////////////////
/obj/item/dnainjector/antiglasses
name = "DNA-Injector (Anti-Glasses)"
@@ -269,10 +269,7 @@
/obj/item/antag_spawner/morph/spawn_antag(client/C, turf/T, type = "", mob/user)
var/mob/living/simple_animal/hostile/morph/wizard/M = new /mob/living/simple_animal/hostile/morph/wizard(pick(GLOB.xeno_spawn))
M.key = C.key
M.mind.assigned_role = SPECIAL_ROLE_MORPH
M.mind.special_role = SPECIAL_ROLE_MORPH
to_chat(M, M.playstyle_string)
SSticker.mode.traitors += M.mind
M.make_morph_antag(FALSE)
var/datum/objective/assassinate/KillDaWiz = new /datum/objective/assassinate
KillDaWiz.owner = M.mind
KillDaWiz.target = user.mind
@@ -285,4 +282,3 @@
M.mind.objectives += KillDaCrew
to_chat(M, "<B>Objective #[1]</B>: [KillDaWiz.explanation_text]")
to_chat(M, "<B>Objective #[2]</B>: [KillDaCrew.explanation_text]")
SEND_SOUND(M, sound('sound/magic/mutate.ogg'))
+9
View File
@@ -285,6 +285,15 @@ BLIND // can't see anything
else
return ..()
/**
* Tries to turn the sensors off. Returns TRUE if it succeeds
*/
/obj/item/clothing/under/proc/turn_sensors_off()
if(has_sensor != 1)
return FALSE
sensor_mode = SUIT_SENSOR_OFF
return TRUE
/obj/item/clothing/under/proc/set_sensors(mob/user as mob)
var/mob/M = user
if(istype(M, /mob/dead/)) return
Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 295 KiB

After

Width:  |  Height:  |  Size: 272 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 53 KiB

+6
View File
@@ -433,6 +433,7 @@
#include "code\datums\spells\magnet.dm"
#include "code\datums\spells\mime.dm"
#include "code\datums\spells\mime_malaise.dm"
#include "code\datums\spells\mimic.dm"
#include "code\datums\spells\mind_transfer.dm"
#include "code\datums\spells\night_vision.dm"
#include "code\datums\spells\projectile.dm"
@@ -591,6 +592,11 @@
#include "code\game\gamemodes\miniantags\guardian\types\standard.dm"
#include "code\game\gamemodes\miniantags\morph\morph.dm"
#include "code\game\gamemodes\miniantags\morph\morph_event.dm"
#include "code\game\gamemodes\miniantags\morph\spells\ambush.dm"
#include "code\game\gamemodes\miniantags\morph\spells\morph_spell.dm"
#include "code\game\gamemodes\miniantags\morph\spells\open_vent.dm"
#include "code\game\gamemodes\miniantags\morph\spells\pass_airlock.dm"
#include "code\game\gamemodes\miniantags\morph\spells\reproduce.dm"
#include "code\game\gamemodes\miniantags\revenant\revenant.dm"
#include "code\game\gamemodes\miniantags\revenant\revenant_abilities.dm"
#include "code\game\gamemodes\miniantags\revenant\revenant_spawn_event.dm"