diff --git a/aurorastation.dme b/aurorastation.dme index e835075e320..3cfeef7dbce 100644 --- a/aurorastation.dme +++ b/aurorastation.dme @@ -2102,6 +2102,7 @@ #include "code\modules\mob\living\simple_animal\hostile\krampus.dm" #include "code\modules\mob\living\simple_animal\hostile\mimic.dm" #include "code\modules\mob\living\simple_animal\hostile\moghesfauna.dm" +#include "code\modules\mob\living\simple_animal\hostile\morph.dm" #include "code\modules\mob\living\simple_animal\hostile\phoron_worm.dm" #include "code\modules\mob\living\simple_animal\hostile\pirate.dm" #include "code\modules\mob\living\simple_animal\hostile\pra.dm" diff --git a/code/__defines/color.dm b/code/__defines/color.dm index 522b3fffbfc..9f4e671dc7a 100644 --- a/code/__defines/color.dm +++ b/code/__defines/color.dm @@ -91,7 +91,7 @@ #define COLOR_OIL "#030303" #define COLOR_ASH "#615C5B" #define COLOR_SNOW "#9CADAD" -#define COLOR_LING_HIVEMIND "#94582e" +#define COLOR_LING_HIVEMIND "#583012" // Blood colors diff --git a/code/game/gamemodes/changeling/helpers/_store.dm b/code/game/gamemodes/changeling/helpers/_store.dm index 4aa63b2050b..7268650bf2f 100644 --- a/code/game/gamemodes/changeling/helpers/_store.dm +++ b/code/game/gamemodes/changeling/helpers/_store.dm @@ -1,5 +1,3 @@ -// READ: Don't use the apostrophe in name or desc. Causes script errors. - var/list/powers = typesof(/datum/power/changeling) - /datum/power/changeling //needed for the badmin verb for now var/list/datum/power/changeling/powerinstances = list() @@ -88,6 +86,12 @@ var/list/datum/power/changeling/powerinstances = list() genomecost = 0 verbpath = /mob/proc/changeling_eject_hivemind +/datum/power/changeling/hivemind_morph + name = "Hivemind Release Morph" + desc = "We release a hivemind member as a morph at the cost of a limb. They will be able to crawl inside vents and disguise themselves as objects." + genomecost = 2 + verbpath = /mob/living/carbon/human/proc/changeling_release_morph + //Stings and sting accessorries //Rest in pieces, unfat sting. - Geeves @@ -211,6 +215,7 @@ var/list/datum/power/changeling/powerinstances = list() // Modularchangling, totally stolen from the new player panel. YAYY //I'm too afraid to touch this, you win this time, oldcode - Geeves +// After an HTML course, I finally conquered this. Convert into VueUi eventually. - Geeves /datum/changeling/proc/EvolutionMenu()//The new one set category = "Changeling" set desc = "Buy new abilities with the genomes we obtained." @@ -233,38 +238,34 @@ var/list/datum/power/changeling/powerinstances = list() var locked_tabs = new Array(); - function updateSearch(){ + function updateSearch() { var filter_text = document.getElementById('filter'); var filter = filter_text.value.toLowerCase(); - if(complete_list != null && complete_list != ""){ + if(complete_list != null && complete_list != "") { var mtbl = document.getElementById("maintable_data_archive"); mtbl.innerHTML = complete_list; } - if(filter.value == ""){ + if(filter.value == "") { return; - }else{ - + } else { var maintable_data = document.getElementById('maintable_data'); var ltr = maintable_data.getElementsByTagName("tr"); for ( var i = 0; i < ltr.length; ++i ) { - try{ + try { var tr = ltr\[i\]; - if(tr.getAttribute("id").indexOf("data") != 0){ + if(tr.getAttribute("id").indexOf("data") != 0) { continue; } var ltd = tr.getElementsByTagName("td"); var td = ltd\[0\]; var lsearch = td.getElementsByTagName("b"); var search = lsearch\[0\]; - //var inner_span = li.getElementsByTagName("span")\[1\] //Should only ever contain one element. - //document.write("

"+search.innerText+"
"+filter+"
"+search.innerText.indexOf(filter)) - if ( search.innerText.toLowerCase().indexOf(filter) == -1 ) - { + if ( search.innerText.toLowerCase().indexOf(filter) == -1 ) { //document.write("a"); //ltr.removeChild(tr); td.innerHTML = ""; @@ -283,29 +284,24 @@ var/list/datum/power/changeling/powerinstances = list() } function expand(id,name,desc,helptext,power,ownsthis){ - clearAll(); var span = document.getElementById(id); body = "
"; - body += ""; + body += ""+desc+"
"; - body += ""+desc+"
" + if(helptext) { + body += ""+helptext+"
"; + } - body += ""+helptext+"
" - - if(!ownsthis) - { - body += "Evolve" + if(!ownsthis) { + body += "Evolve"; } body += "
"; - body += "
"; - - span.innerHTML = body } @@ -316,13 +312,13 @@ var/list/datum/power/changeling/powerinstances = list() var id = span.getAttribute("id"); - if(!(id.indexOf("item")==0)) + if(!(id.indexOf("item") == 0)) continue; var pass = 1; - for(var j = 0; j < locked_tabs.length; j++){ - if(locked_tabs\[j\]==id){ + for(var j = 0; j < locked_tabs.length; j++) { + if(locked_tabs\[j\]==id) { pass = 0; break; } @@ -331,9 +327,6 @@ var/list/datum/power/changeling/powerinstances = list() if(pass != 1) continue; - - - span.innerHTML = ""; } } @@ -341,17 +334,17 @@ var/list/datum/power/changeling/powerinstances = list() function addToLocked(id,link_id,notice_span_id){ var link = document.getElementById(link_id); var decision = link.getAttribute("name"); - if(decision == "1"){ + if(decision == "1") { link.setAttribute("name","2"); - }else{ + } else { link.setAttribute("name","1"); removeFromLocked(id,link_id,notice_span_id); return; } var pass = 1; - for(var j = 0; j < locked_tabs.length; j++){ - if(locked_tabs\[j\]==id){ + for(var j = 0; j < locked_tabs.length; j++) { + if(locked_tabs\[j\] == id) { pass = 0; break; } @@ -361,12 +354,9 @@ var/list/datum/power/changeling/powerinstances = list() locked_tabs.push(id); var notice_span = document.getElementById(notice_span_id); notice_span.innerHTML = "Locked "; - //link.setAttribute("onClick","attempt('"+id+"','"+link_id+"','"+notice_span_id+"');"); - //document.write("removeFromLocked('"+id+"','"+link_id+"','"+notice_span_id+"')"); - //document.write("aa - "+link.getAttribute("onClick")); } - function attempt(ab){ + function attempt(ab) { return ab; } @@ -374,8 +364,8 @@ var/list/datum/power/changeling/powerinstances = list() //document.write("a"); var index = 0; var pass = 0; - for(var j = 0; j < locked_tabs.length; j++){ - if(locked_tabs\[j\]==id){ + for(var j = 0; j < locked_tabs.length; j++) { + if(locked_tabs\[j\] == id) { pass = 1; index = j; break; @@ -386,8 +376,6 @@ var/list/datum/power/changeling/powerinstances = list() locked_tabs\[index\] = ""; var notice_span = document.getElementById(notice_span_id); notice_span.innerHTML = ""; - //var link = document.getElementById(link_id); - //link.setAttribute("onClick","addToLocked('"+id+"','"+link_id+"','"+notice_span_id+"')"); } function selectTextField(){ @@ -440,9 +428,9 @@ var/list/datum/power/changeling/powerinstances = list() ownsthis = 1 - var/color = "#e6e6e6" + var/color = "#2b2b2b" if(i%2 == 0) - color = "#f2f2f2" + color = "#363636" dat += {" @@ -450,11 +438,7 @@ var/list/datum/power/changeling/powerinstances = list() - - Evolve [P] - Cost: [ownsthis ? "Purchased" : P.genomecost] - + Evolve [P] - Cost: [ownsthis ? "Purchased" : P.genomecost]
@@ -476,7 +460,9 @@ var/list/datum/power/changeling/powerinstances = list() "} - usr << browse(dat, "window=powers;size=900x480") + var/datum/browser/power_win = new(usr, "powers", "Changeling Powers", 900, 480) + power_win.set_content(dat) + power_win.open() /datum/changeling/Topic(href, href_list) diff --git a/code/game/gamemodes/changeling/implements/hivemind.dm b/code/game/gamemodes/changeling/implements/hivemind.dm index 4fddfda3fe6..556cab80860 100644 --- a/code/game/gamemodes/changeling/implements/hivemind.dm +++ b/code/game/gamemodes/changeling/implements/hivemind.dm @@ -53,4 +53,18 @@ /mob/abstract/hivemind/emote() to_chat(src, SPAN_WARNING("You cannot emote.")) - return \ No newline at end of file + return + +/mob/abstract/hivemind/proc/release_as_morph() + to_chat(src, SPAN_DANGER(FONT_LARGE("You are a morph, released by [changeling_mob]!"))) + to_chat(src, SPAN_DANGER("As a morph, you can disguise as objects by alt-clicking on them.")) + to_chat(src, SPAN_DANGER("You can eat people and items by clicking on them, but only if they're dead.")) + + relay_hivemind("Hivemind member [src] has been released into the outside world as a morph!", changeling_mob) + log_and_message_admins("has released [src] as a morph.", changeling_mob, get_turf(changeling_mob)) + + var/mob/living/simple_animal/hostile/morph/M = new /mob/living/simple_animal/hostile/morph(get_turf(changeling_mob)) + M.stop_thinking = TRUE // prevent the AI from taking over when the player ghosts + M.ckey = ckey + changeling_mob.mind.changeling.hivemind_members -= src + qdel(src) \ No newline at end of file diff --git a/code/game/gamemodes/changeling/implements/powers/hivemind.dm b/code/game/gamemodes/changeling/implements/powers/hivemind.dm index 16a5b761e20..597e7071c5c 100644 --- a/code/game/gamemodes/changeling/implements/powers/hivemind.dm +++ b/code/game/gamemodes/changeling/implements/powers/hivemind.dm @@ -9,7 +9,7 @@ if(!mind.changeling.hivemind_members.len) return var/chosen_player = input("Choose a hivemind member to eject.", "Eject") as null|anything in mind.changeling.hivemind_members - if(!chosen_player || chosen_player == "None") + if(!chosen_player) return var/mob/abstract/hivemind/M = mind.changeling.hivemind_members[chosen_player] M.ghost() //Deuces @@ -34,4 +34,47 @@ to_chat(ling, message) /mob/proc/changeling_message_process(var/message) - return "[src] says, \"[message]\"" \ No newline at end of file + message = capitalize(message) + + var/static/list/correct_punctuation = list("!" = TRUE, "." = TRUE, "?" = TRUE, "-" = TRUE, "~" = TRUE, "*" = TRUE, "/" = TRUE, ">" = TRUE, "\"" = TRUE, "'" = TRUE, "," = TRUE, ":" = TRUE, ";" = TRUE) + var/ending = copytext(message, length(message), (length(message) + 1)) + if(ending && !correct_punctuation[ending]) + message += "." + + return "[src] says, \"[message]\"" + +/mob/living/carbon/human/proc/changeling_release_morph() + set category = "Changeling" + set name = "Hivemind Release Morph" + set desc = "Releases a member of our internal hivemind as a morph, at the cost of one of our limbs." + + if(!mind?.changeling) + return + + if(!length(mind.changeling.hivemind_members)) + to_chat(src, SPAN_WARNING("We have no internal hivemind members to release!")) + return + + var/chosen_player = input("Choose a hivemind member to release as a morph.", "Hivemind Morph") as null|anything in mind.changeling.hivemind_members + if(!chosen_player) + return + + var/list/selectable_limb = list() + for(var/organ_name in list(BP_L_ARM, BP_R_ARM, BP_L_LEG, BP_R_LEG)) + var/obj/item/organ/external/limb = organs_by_name[organ_name] + if(limb && !limb.is_stump()) + selectable_limb += limb + + if(!length(selectable_limb)) + to_chat(src, SPAN_WARNING("We have no limbs to sacrifice!")) + return + + var/obj/item/organ/external/chosen_limb = input("Choose a limb to sacrifice.", "Limb Sacrifice") as null|anything in selectable_limb + if(!chosen_limb) + return + + chosen_limb.droplimb(TRUE, DROPLIMB_BLUNT) + + var/mob/abstract/hivemind/M = mind.changeling.hivemind_members[chosen_player] + M.release_as_morph() + return TRUE \ No newline at end of file diff --git a/code/modules/mob/living/simple_animal/hostile/hostile.dm b/code/modules/mob/living/simple_animal/hostile/hostile.dm index 782114ce320..ab2e6725dd5 100644 --- a/code/modules/mob/living/simple_animal/hostile/hostile.dm +++ b/code/modules/mob/living/simple_animal/hostile/hostile.dm @@ -205,6 +205,10 @@ mob/living/simple_animal/hostile/hitby(atom/movable/AM as mob|obj,var/speed = TH /mob/living/simple_animal/hostile/think() ..() + + if(stop_thinking) + return + switch(stance) if(HOSTILE_STANCE_IDLE) targets = ListTargets(10) diff --git a/code/modules/mob/living/simple_animal/hostile/morph.dm b/code/modules/mob/living/simple_animal/hostile/morph.dm new file mode 100644 index 00000000000..51edbacc6c6 --- /dev/null +++ b/code/modules/mob/living/simple_animal/hostile/morph.dm @@ -0,0 +1,205 @@ +#define MORPH_COOLDOWN 50 + +/mob/living/simple_animal/hostile/morph + name = "morph" + desc = "A revolting, pulsating pile of flesh." + speak_emote = list("gurgles") + emote_hear = list("gurgles") + icon = 'icons/mob/npc/animal.dmi' + icon_state = "morph" + icon_living = "morph" + icon_dead = "morph_dead" + speed = 2.5 + stop_automated_movement = TRUE + + response_help = "pets" + response_disarm = "shoves" + response_harm = "harmlessly punches" + + mob_size = 15 + + universal_speak = TRUE + universal_understand = TRUE + + tameable = FALSE + + status_flags = CANPUSH + pass_flags = PASSTABLE + + maxHealth = 125 + health = 125 + + melee_damage_lower = 12 + melee_damage_upper = 16 + + see_in_dark = 8 + see_invisible = SEE_INVISIBLE_NOLIGHTING + + minbodytemp = 0 + maxbodytemp = 350 + min_oxy = 0 + max_co2 = 0 + max_tox = 0 + + wander = FALSE + + attacktext = "glomped" + attack_sound = 'sound/effects/blobattack.ogg' + + var/morphed = FALSE + var/melee_damage_disguised = 0 + var/eat_while_disguised = FALSE + var/atom/movable/form = null + var/morph_time = 0 + var/static/list/blacklist_typecache = typecacheof(list(/obj/screen, /obj/singularity, /mob/living/simple_animal/hostile/morph, /obj/effect)) + +/mob/living/simple_animal/hostile/morph/Initialize() + . = ..() + verbs += /mob/living/proc/ventcrawl + +/mob/living/simple_animal/hostile/morph/examine(mob/user) + if(morphed) + . = form.examine(user) + if(get_dist(src, user) > 2) + return + to_chat(user, SPAN_WARNING("It doesn't look quite right...")) + else + return ..() + +/mob/living/simple_animal/hostile/morph/proc/allowed(atom/movable/A) + return !is_type_in_typecache(A, blacklist_typecache) && (isobj(A) || ismob(A)) + +/mob/living/simple_animal/hostile/morph/proc/eat(var/atom/movable/A, var/delay = 20) + if(morphed && !eat_while_disguised) + to_chat(src, SPAN_WARNING("You can't eat anything while you are disguised!")) + return FALSE + if(A?.loc == src) + return FALSE + visible_message(SPAN_WARNING("\The [src] begins swallowing \the [A] whole!"), SPAN_NOTICE("You begin swallowing \the [A] whole.")) + if(do_after(src, delay, act_target = A)) + visible_message(SPAN_WARNING("\The [src] swallows \the [A] whole!"), SPAN_NOTICE("You swallow \the [A] whole.")) + A.forceMove(src) + return TRUE + +/mob/living/simple_animal/hostile/morph/AltClickOn(atom/movable/A) + if(morph_time <= world.time && !stat) + if(A == src) + restore() + else if(istype(A) && allowed(A)) + assume(A) + else + to_chat(src, SPAN_WARNING("Your chameleon skin is still repairing itself!")) + +/mob/living/simple_animal/hostile/morph/proc/assume(atom/movable/target) + if(morphed) + to_chat(src, SPAN_WARNING("You must restore to your original form first!")) + return + morphed = TRUE + form = target + visible_message(SPAN_WARNING("\The [src] suddenly twists and changes shape, becoming a copy of \the [target]!"), SPAN_NOTICE("You twist your body and assume the form of \the [target].")) + appearance = target.appearance + alpha = max(alpha, 150) //fucking chameleons + transform = initial(transform) + pixel_y = initial(pixel_y) + pixel_x = initial(pixel_x) + + //Morphed is weaker + melee_damage_lower = melee_damage_disguised + melee_damage_upper = melee_damage_disguised + speed = 1 + mob_size = MOB_MINISCULE + + morph_time = world.time + MORPH_COOLDOWN + return + +/mob/living/simple_animal/hostile/morph/proc/restore() + if(!morphed) + to_chat(src, SPAN_WARNING("You're already in your normal form!")) + return + morphed = FALSE + form = null + alpha = initial(alpha) + color = initial(color) + desc = initial(desc) + animate_movement = SLIDE_STEPS + maptext = null + + visible_message(SPAN_WARNING("\The [src] suddenly collapses in on itself, dissolving into a pile of green flesh!"), SPAN_NOTICE("You reform to your normal body.")) + name = initial(name) + icon = initial(icon) + icon_state = initial(icon_state) + cut_overlays() + overlays = null + + //Baseline stats + melee_damage_lower = initial(melee_damage_lower) + melee_damage_upper = initial(melee_damage_upper) + speed = 2.5 + mob_size = 15 + + morph_time = world.time + MORPH_COOLDOWN + +/mob/living/simple_animal/hostile/morph/death(gibbed) + if(morphed) + visible_message(SPAN_WARNING("\The [src] twists and dissolves into a pile of green flesh!"), SPAN_DANGER("Your skin ruptures! Your flesh breaks apart! No disguise can ward off de--")) + restore() + barf_contents() + ..() + +/mob/living/simple_animal/hostile/morph/proc/barf_contents() + for(var/atom/movable/AM in src) + AM.forceMove(loc) + if(prob(90)) + step(AM, pick(global.alldirs)) + +/mob/living/simple_animal/hostile/morph/UnarmedAttack(atom/A, proximity) + if(morphed && !melee_damage_disguised) + to_chat(src, SPAN_WARNING("You can't attack while disguised!")) + return + if(isliving(A)) //Eat Corpses to regen health + var/mob/living/L = A + if(L.stat == DEAD) + if(eat(L, 30)) + adjustBruteLoss(-50) + return + else if(istype(A, /obj/item)) //Eat items just to be annoying + var/obj/item/I = A + if(!I.anchored) + eat(I, 20) + return + return ..() + +/mob/living/simple_animal/hostile/bullet_act(obj/item/projectile/P, def_zone) + ..() + if (ismob(P.firer) && target_mob != P.firer) + target_mob = P.firer + stance = HOSTILE_STANCE_ATTACK + +/mob/living/simple_animal/hostile/morph/attackby(obj/item/O, mob/user) + ..() + if(morphed && user != src) + restore() + +/mob/living/simple_animal/hostile/morph/hitby(atom/movable/AM, speed) + ..() + if(morphed) + restore() + +/mob/living/simple_animal/hostile/morph/attack_generic(mob/user, damage, attack_message) + ..() + if(morphed) + restore() + +/mob/living/simple_animal/hostile/morph/attack_hand(mob/living/carbon/human/M) + ..() + if(morphed && M != src) + restore() + +/mob/living/simple_animal/hostile/morph/start_ventcrawl() + if(morphed) + to_chat(src, SPAN_WARNING("You can't ventcrawl while disguised!")) + return + return ..() + +/mob/living/simple_animal/hostile/morph/do_animate_chat(var/message, var/datum/language/language, var/small, var/list/show_to, var/duration, var/list/message_override) + INVOKE_ASYNC(src, /atom/movable/proc/animate_chat, message, language, small, show_to, duration) \ No newline at end of file diff --git a/code/modules/mob/living/simple_animal/simple_animal.dm b/code/modules/mob/living/simple_animal/simple_animal.dm index 5162133e800..d0bc9d9eaeb 100644 --- a/code/modules/mob/living/simple_animal/simple_animal.dm +++ b/code/modules/mob/living/simple_animal/simple_animal.dm @@ -27,6 +27,7 @@ universal_speak = 0 //No, just no. var/meat_amount = 0 var/meat_type + var/stop_thinking = FALSE // prevents them from doing any AI stuff whatsoever var/stop_automated_movement = 0 //Use this to temporarely stop random movement or to if you write special movement code for animals. var/wander = 1 // Does the mob wander around when idle? var/stop_automated_movement_when_pulled = 1 //When set to 1 this stops the animal from moving when someone is pulling it. @@ -239,6 +240,10 @@ /mob/living/simple_animal/think() ..() + + if(stop_thinking) + return + if(!stop_automated_movement && wander && !anchored) if(isturf(loc) && !resting && !buckled && canmove) //This is so it only moves if it's not inside a closet, gentics machine, etc. if(turns_since_move >= turns_per_move && !(stop_automated_movement_when_pulled && pulledby)) //Some animals don't move when pulled diff --git a/code/modules/ventcrawl/ventcrawl.dm b/code/modules/ventcrawl/ventcrawl.dm index 45305a0c55f..7f96ef520fa 100644 --- a/code/modules/ventcrawl/ventcrawl.dm +++ b/code/modules/ventcrawl/ventcrawl.dm @@ -58,9 +58,12 @@ var/global/list/can_enter_vent_with = list( /mob/living/proc/ventcrawl_carry() for(var/atom/A in src.contents) if(!is_allowed_vent_crawl_item(A)) - to_chat(src, "You can't be carrying that when vent crawling!") - return 0 - return 1 + to_chat(src, SPAN_WARNING("You can't be carrying that when vent crawling!")) + return FALSE + return TRUE + +/mob/living/simple_animal/hostile/morph/ventcrawl_carry() + return TRUE /obj/machinery/atmospherics/AltClick(mob/living/user) if(is_type_in_list(src, ventcrawl_machinery) && user.can_ventcrawl()) diff --git a/html/changelogs/geeves-morph_ling.yml b/html/changelogs/geeves-morph_ling.yml new file mode 100644 index 00000000000..4344d5d5539 --- /dev/null +++ b/html/changelogs/geeves-morph_ling.yml @@ -0,0 +1,9 @@ +author: Geeves, Sue + +delete-after: True + +changes: + - rscadd: "Lings can now evolve the ability to release members of their internal hivemind as morphs at the cost of their arm. The ability itself costs two genome points." + - rscadd: "Added morphs, creature capable of assuming the form of any object." + - tweak: "Messages spoken in the internal hivemind will automatically be capitalized and punctuated. Also improved the colour for readability." + - bugfix: "Fixed the script error in the Changeling DNA buy menu, improved the look of it." \ No newline at end of file diff --git a/icons/mob/npc/animal.dmi b/icons/mob/npc/animal.dmi index c42e0f9646a..102c9556efc 100644 Binary files a/icons/mob/npc/animal.dmi and b/icons/mob/npc/animal.dmi differ