Merge pull request #9564 from VOREStation/upstream-merge-7781

[MIRROR] Blob Genesis
This commit is contained in:
Novacat
2021-01-12 19:22:18 -05:00
committed by GitHub
21 changed files with 243 additions and 28 deletions
+1 -1
View File
@@ -68,7 +68,7 @@
#define LANGUAGE_ALAI "Alai"
#define LANGUAGE_ZADDAT "Vedahq"
#define LANGUAGE_PROMETHEAN "Promethean Biolinguistics"
#define LANGUAGE_BLOB "Blob"
#define LANGUAGE_BLOB "Chemosense Transmission"
#define LANGUAGE_GIBBERISH "Babel"
// Language flags.
+41 -4
View File
@@ -22,6 +22,7 @@ GLOBAL_LIST_EMPTY(all_blobs)
/obj/structure/blob/Initialize(newloc, new_overmind)
if(new_overmind)
overmind = new_overmind
faction = overmind.blob_type.faction
update_icon()
if(!integrity)
integrity = max_integrity
@@ -47,19 +48,24 @@ GLOBAL_LIST_EMPTY(all_blobs)
color = null
set_light(0)
/obj/structure/blob/update_transform()
var/matrix/M = matrix()
M.Scale(icon_scale_x, icon_scale_y)
animate(src, transform = M, time = 10)
// Blob tiles are not actually dense so we need Special Code(tm).
/obj/structure/blob/CanPass(atom/movable/mover, turf/target)
if(istype(mover) && mover.checkpass(PASSBLOB))
return TRUE
else if(istype(mover, /mob/living))
var/mob/living/L = mover
if(L.faction == "blob")
if(L.faction == faction)
return TRUE
else if(istype(mover, /obj/item/projectile))
var/obj/item/projectile/P = mover
if(istype(P.firer, /obj/structure/blob))
return TRUE
if(istype(P.firer) && P.firer.faction == "blob")
if(istype(P.firer) && P.firer.faction == faction)
return TRUE
return FALSE
@@ -88,6 +94,7 @@ GLOBAL_LIST_EMPTY(all_blobs)
update_icon()
pulse_timestamp = world.time + 1 SECOND
if(overmind)
faction = overmind.blob_type.faction
overmind.blob_type.on_pulse(src)
return TRUE //we did it, we were pulsed!
return FALSE //oh no we failed
@@ -106,6 +113,9 @@ GLOBAL_LIST_EMPTY(all_blobs)
for(var/L in blobs_to_affect)
var/obj/structure/blob/B = L
if(B.faction != faction)
continue
if(!B.overmind && !istype(B, /obj/structure/blob/core) && prob(30))
B.overmind = pulsing_overmind //reclaim unclaimed, non-core blobs.
B.update_icon()
@@ -138,7 +148,8 @@ GLOBAL_LIST_EMPTY(all_blobs)
var/dirn = pick(dirs)
dirs.Remove(dirn)
T = get_step(src, dirn)
if(!(locate(/obj/structure/blob) in T))
var/obj/structure/blob/B = locate(/obj/structure/blob) in T
if(!B || B.faction != faction) // Allow opposing blobs to fight.
break
else
T = null
@@ -164,6 +175,7 @@ GLOBAL_LIST_EMPTY(all_blobs)
if(make_blob) //well, can we?
var/obj/structure/blob/B = new /obj/structure/blob/normal(src.loc)
B.faction = faction
if(controller)
B.overmind = controller
else
@@ -340,7 +352,7 @@ GLOBAL_LIST_EMPTY(all_blobs)
if(!P)
return
if(istype(P.firer) && P.firer.faction == "blob")
if(istype(P.firer) && P.firer.faction == faction)
return
var/damage = P.get_structure_damage() // So tasers don't hurt the blob.
@@ -366,6 +378,31 @@ GLOBAL_LIST_EMPTY(all_blobs)
if(overmind)
overmind.blob_type.on_water(src, amount)
/obj/structure/blob/blob_act(var/obj/structure/blob/B)
. = ..()
if(B)
if(!B.overmind)
return
if(B.faction != faction)
var/damage = rand(B.overmind.blob_type.damage_lower, B.overmind.blob_type.damage_upper)
var/inc_damage_type = B.overmind.blob_type.damage_type
if(overmind)
damage = overmind.blob_type.on_received_damage(src, damage, inc_damage_type, B)
else
faction = B.faction
overmind = B.overmind
update_icon()
return
adjust_integrity(-1 * damage)
return
/obj/structure/blob/proc/adjust_integrity(amount)
integrity = between(0, integrity + amount, max_integrity)
if(integrity == 0)
+1 -1
View File
@@ -35,7 +35,7 @@
var/mob/living/simple_mob/blob/spore/S = null
if(overmind)
S = new overmind.blob_type.spore_type(src.loc, src)
S.faction = "blob"
S.faction = overmind.blob_type.faction
if(istype(S))
S.overmind = overmind
overmind.blob_mobs.Add(S)
+10 -1
View File
@@ -19,4 +19,13 @@
if(overmind)
name = "[overmind.blob_type.name]"
else
name = "inert [base_name]"
name = "inert [base_name]"
/obj/structure/blob/normal/pulsed()
..()
if(prob(30))
adjust_scale((rand(10, 13) / 10), (rand(10, 13) / 10))
else
adjust_scale(1)
+55 -1
View File
@@ -5,6 +5,7 @@
description_info = "Some blob types will have core effects when the chunk is used in-hand, toggled with an alt click, or constantly active."
icon = 'icons/mob/blob.dmi'
icon_state = "blobcore"
flags = OPENCONTAINER
var/datum/blob_type/blob_type // The blob type this dropped from.
var/active_ability_cooldown = 20 SECONDS
@@ -15,11 +16,17 @@
var/passive_ability_cooldown = 5 SECONDS
var/last_passive_use = 0
var/can_genesis = TRUE // Can the core chunk be used to grow a new blob?
drop_sound = 'sound/effects/slime_squish.ogg'
/obj/item/weapon/blobcore_chunk/is_open_container()
return 1
/obj/item/weapon/blobcore_chunk/New(var/atom/newloc, var/datum/blob_type/parentblob = null)
..(newloc)
create_reagents(120)
setup_blobtype(parentblob)
/obj/item/weapon/blobcore_chunk/Destroy()
@@ -91,10 +98,57 @@
blob_type.on_chunk_tick(src)
/obj/item/weapon/blobcore_chunk/AltClick(mob/living/carbon/user)
if(blob_type &&blob_type.chunk_active_type == BLOB_CHUNK_TOGGLE)
if(blob_type && blob_type.chunk_active_type == BLOB_CHUNK_TOGGLE)
should_tick = !should_tick
if(should_tick)
to_chat(user, "<span class='alien'>\The [src] shudders with life.</span>")
else
to_chat(user, "<span class='alien'>\The [src] stills, returning to a death-like state.</span>")
/obj/item/weapon/blobcore_chunk/proc/regen(var/newfaction = null)
if(istype(blob_type))
if(newfaction)
blob_type.faction = newfaction
var/obj/structure/blob/core/NC = new (get_turf(src))
NC.overmind.blob_type = blob_type
NC.overmind.blob_core.update_icon()
return TRUE
return FALSE
/datum/chemical_reaction/blob_reconstitution
name = "Hostile Blob Revival"
id = "blob_revival"
result = null
required_reagents = list("phoron" = 60)
result_amount = 1
/datum/chemical_reaction/blob_reconstitution/can_happen(var/datum/reagents/holder)
if(holder.my_atom && istype(holder.my_atom, /obj/item/weapon/blobcore_chunk))
return ..()
return FALSE
/datum/chemical_reaction/blob_reconstitution/on_reaction(var/datum/reagents/holder)
var/obj/item/weapon/blobcore_chunk/chunk = holder.my_atom
if(chunk.can_genesis && chunk.regen())
chunk.visible_message("<span class='notice'>[chunk] bubbles, surrounding itself with a rapidly expanding mass of [chunk.blob_type.name]!</span>")
chunk.can_genesis = FALSE
else
chunk.visible_message("<span class='warning'>[chunk] shifts strangely, but falls still.</span>")
/datum/chemical_reaction/blob_reconstitution/domination
name = "Allied Blob Revival"
id = "blob_friend"
result = null
required_reagents = list("hydrophoron" = 40, "peridaxon" = 20, "mutagen" = 20)
result_amount = 1
/datum/chemical_reaction/blob_reconstitution/domination/on_reaction(var/datum/reagents/holder)
var/obj/item/weapon/blobcore_chunk/chunk = holder.my_atom
if(chunk.can_genesis && chunk.regen("neutral"))
chunk.visible_message("<span class='notice'>[chunk] bubbles, surrounding itself with a rapidly expanding mass of [chunk.blob_type.name]!</span>")
chunk.can_genesis = FALSE
else
chunk.visible_message("<span class='warning'>[chunk] shifts strangely, but falls still.</span>")
+74 -2
View File
@@ -23,6 +23,14 @@ var/list/overminds = list()
var/ai_controlled = TRUE
var/auto_pilot = FALSE // If true, and if a client is attached, the AI routine will continue running.
universal_understand = TRUE
var/list/has_langs = list(LANGUAGE_BLOB)
var/datum/language/default_language = null
/mob/observer/blob/get_default_language()
return default_language
/mob/observer/blob/Initialize(newloc, pre_placed = 0, starting_points = 60, desired_blob_type = null)
blob_points = starting_points
if(pre_placed) //we already have a core!
@@ -41,6 +49,11 @@ var/list/overminds = list()
if(blob_core)
blob_core.update_icon()
for(var/L in has_langs)
languages |= GLOB.all_languages[L]
if(languages.len)
default_language = languages[1]
return ..(newloc)
/mob/observer/blob/Destroy()
@@ -67,9 +80,9 @@ var/list/overminds = list()
stat(null, "Power Stored: [blob_points]/[max_blob_points]")
stat(null, "Total Blobs: [GLOB.all_blobs.len]")
/mob/observer/blob/Move(NewLoc, Dir = 0)
/mob/observer/blob/Move(var/atom/NewLoc, Dir = 0)
if(placed)
var/obj/structure/blob/B = locate() in range("3x3", NewLoc)
var/obj/structure/blob/B = (locate() in view("5x5", NewLoc))
if(B)
forceMove(NewLoc)
return TRUE
@@ -93,3 +106,62 @@ var/list/overminds = list()
if(blob_points >= 100)
if(!auto_factory() && !auto_resource())
auto_node()
/mob/observer/blob/say(var/message, var/datum/language/speaking = null, var/whispering = 0)
message = sanitize(message)
if(!message)
return
//If you're muted for IC chat
if(client)
if(message)
client.handle_spam_prevention(MUTE_IC)
if((client.prefs.muted & MUTE_IC) || say_disabled)
to_chat(src, "<span class='warning'>You cannot speak in IC (Muted).</span>")
return
//These will contain the main receivers of the message
var/list/listening = list()
var/list/listening_obj = list()
var/turf/T = get_turf(src)
if(T)
//Obtain the mobs and objects in the message range
var/list/results = get_mobs_and_objs_in_view_fast(T, world.view, remote_ghosts = client ? TRUE : FALSE)
listening = results["mobs"]
listening_obj = results["objs"]
else
return 1 //If we're in nullspace, then forget it.
var/list/message_pieces = parse_languages(message)
if(istype(message_pieces, /datum/multilingual_say_piece)) // Little quirk for dealing with hivemind/signlang languages.
var/datum/multilingual_say_piece/S = message_pieces // Yay for BYOND's hilariously broken typecasting for allowing us to do this.
S.speaking.broadcast(src, S.message)
return 1
if(!LAZYLEN(message_pieces))
log_runtime(EXCEPTION("Message failed to generate pieces. [message] - [json_encode(message_pieces)]"))
return 0
//Handle nonverbal languages here
for(var/datum/multilingual_say_piece/S in message_pieces)
if(S.speaking.flags & NONVERBAL)
custom_emote(1, "[pick(S.speaking.signlang_verb)].")
for(var/mob/M in listening)
spawn()
if(M && src)
if(get_dist(M, src) <= world.view || (M.stat == DEAD && !forbid_seeing_deadchat))
M.hear_say(message_pieces, "conveys", (M.faction == blob_type.faction), src)
//Object message delivery
for(var/obj/O in listening_obj)
spawn(0)
if(O && src) //If we still exist, when the spawn processes
var/dst = get_dist(get_turf(O),get_turf(src))
if(dst <= world.view)
O.hear_talk(src, message_pieces, "conveys")
log_say(message, src)
return 1
+7 -7
View File
@@ -80,8 +80,8 @@
potential_blobs -= temp // Don't take up the core's shield spot.
else if(!istype(temp, /obj/structure/blob/normal))
potential_blobs -= temp // Not a normal blob.
else if(temp.overmind != src)
potential_blobs -= temp // Not our blob.
else if(temp.overmind != src || temp.overmind.blob_type.faction != blob_type.faction)
potential_blobs -= temp // Not our blob, or even an ally.
else
B = temp
break
@@ -123,8 +123,8 @@
potential_blobs -= temp // Don't take up the core's shield spot.
else if(!istype(temp, /obj/structure/blob/normal))
potential_blobs -= temp // Not a normal blob.
else if(temp.overmind != src)
potential_blobs -= temp // Not our blob.
else if(temp.overmind != src || temp.overmind.blob_type.faction != blob_type.faction)
potential_blobs -= temp // Not our blob, or even an ally
else
B = temp
break
@@ -165,8 +165,8 @@
potential_blobs -= temp
else if(!istype(temp, /obj/structure/blob/normal))
potential_blobs -= temp
else if(temp.overmind != src)
potential_blobs -= temp // Not our blob.
else if(temp.overmind != src || temp.overmind.blob_type.faction != blob_type.faction)
potential_blobs -= temp // Not our blob, or even our ally.
else
B = temp
break
@@ -216,7 +216,7 @@
for(var/mob/living/L in view(src))
if(L.stat == DEAD)
continue // Already dying or dead.
if(L.faction == "blob")
if(L.faction == blob_type.faction)
continue // No friendly fire.
if(locate(/obj/structure/blob) in L.loc)
continue // Already has a blob over them.
+2
View File
@@ -9,6 +9,8 @@
var/color = "#FFFFFF" // The actual blob's color.
var/complementary_color = "#000000" //a color that's complementary to the normal blob color. Blob mobs are colored in this.
var/faction = "blob" // The blob's faction.
var/attack_message = "The blob attacks you" // Base message the mob gets when blob_act() gets called on them by the blob. An exclaimation point is added to the end.
var/attack_message_living = null // Appended to attack_message, if the target fails isSynthetic() check.
var/attack_message_synth = null // Ditto, but if they pass isSynthetic().
@@ -33,6 +33,8 @@
env.add_thermal_energy(10 * 1000)
/datum/blob_type/blazing_oil/on_chunk_tick(obj/item/weapon/blobcore_chunk/B)
B.reagents.add_reagent("thermite_v", 0.5)
var/turf/T = get_turf(B)
if(!T)
return
@@ -46,6 +46,8 @@
env.add_thermal_energy(-10 * 1000)
/datum/blob_type/cryogenic_goo/on_chunk_tick(obj/item/weapon/blobcore_chunk/B)
B.reagents.add_reagent("cryoslurry", 0.5)
var/turf/simulated/T = get_turf(B)
if(!istype(T))
return
@@ -35,7 +35,7 @@
listclearnulls(active_beams)
var/atom/movable/beam_origin = B
for(var/mob/living/L in oview(world.view, B))
if(L.stat == DEAD || L.faction == "blob")
if(L.stat == DEAD || L.faction == faction)
continue
if(prob(5))
var/beamtarget_exists = FALSE
@@ -82,7 +82,7 @@
if(nearby_mobs.len)
listclearnulls(active_beams)
for(var/mob/living/L in nearby_mobs)
if(L.stat == DEAD || L.faction == "blob")
if(L.stat == DEAD || L.faction == faction)
continue
if(prob(5))
var/beamtarget_exists = FALSE
@@ -30,7 +30,7 @@
for(var/mob/living/L in range(get_turf(victim), 1)) // We don't use orange(), in case there is more than one mob on the target tile.
if(L == victim) // Already hit.
continue
if(L.faction == "blob") // No friendly fire
if(L.faction == faction) // No friendly fire
continue
L.blob_act()
@@ -24,9 +24,10 @@
var/mob/living/simple_mob/blob/spore/S = new spore_type(T)
if(istype(S))
S.overmind = O
S.faction = faction
O.blob_mobs.Add(S)
else
S.faction = "blob"
S.faction = faction
S.update_icons()
/datum/blob_type/fulminant_organism/on_death(obj/structure/blob/B)
@@ -35,9 +36,10 @@
B.visible_message("<span class='danger'>\The [S] floats free from the [name]!</span>")
if(istype(S))
S.overmind = B.overmind
S.faction = faction
B.overmind.blob_mobs.Add(S)
else
S.faction = "blob"
S.faction = faction
S.update_icons()
/datum/blob_type/fulminant_organism/on_chunk_use(obj/item/weapon/blobcore_chunk/B, mob/living/user)
@@ -14,6 +14,7 @@
attack_message = "The tide tries to swallow you"
attack_message_living = ", and you feel your skin dissolve"
attack_message_synth = ", and your external plating dissolves"
faction = "nanomachines"
/datum/blob_type/grey_goo/on_emp(obj/structure/blob/B, severity)
B.adjust_integrity(-(20 / severity))
@@ -44,7 +44,7 @@
if(!istype(L))
return
if(istype(B, /obj/structure/blob/factory) && L.stat != DEAD && prob(ai_aggressiveness) && L.faction != "blob")
if(istype(B, /obj/structure/blob/factory) && L.stat != DEAD && prob(ai_aggressiveness) && L.faction != faction)
var/obj/item/projectile/arc/spore/P = new(get_turf(B))
P.launch_projectile(L, BP_TORSO, B)
+28
View File
@@ -154,3 +154,31 @@
key = "]"
flags = RESTRICTED
syllables = list("chan","ange","thi","se")
//Bloblang.
/datum/language/blob
name = LANGUAGE_BLOB
desc = "The massive processing power of the Blob's core gives the overmind finely tuned abilities to transmit messages to nearby life-forms through chemical signals."
speech_verb = "resonates"
ask_verb = "reverberates"
exclaim_verb = "shudders"
colour = "blob"
key = "}"
machine_understands = TRUE
flags = RESTRICTED
syllables = list("^", "˅", "-", "°", "~")
/datum/language/corticalborer/broadcast(var/mob/living/speaker,var/message,var/speaker_mask)
var/mob/living/simple_mob/animal/borer/B
if(istype(speaker,/mob/living/carbon))
var/mob/living/carbon/M = speaker
B = M.has_brain_worms()
else if(istype(speaker,/mob/living/simple_mob/animal/borer))
B = speaker
if(B)
speaker_mask = B.true_name
..(speaker,message,speaker_mask)
+1 -1
View File
@@ -181,7 +181,7 @@
..()
/mob/living/blob_act(var/obj/structure/blob/B)
if(stat == DEAD || faction == "blob")
if(stat == DEAD || faction == B.faction)
return
var/damage = rand(30, 40)
@@ -50,9 +50,13 @@
/mob/living/simple_mob/blob/blob_act(obj/structure/blob/B)
if(!overmind && B.overmind)
overmind = B.overmind
faction = B.overmind.blob_type.faction
update_icon()
if(stat != DEAD && health < maxHealth)
if(faction != B.faction && B.overmind)
adjustBruteLoss(rand(B.overmind.blob_type.damage_lower, B.overmind.blob_type.damage_upper))
else if(stat != DEAD && health < maxHealth)
adjustBruteLoss(-maxHealth*0.0125)
adjustFireLoss(-maxHealth*0.0125)
@@ -73,7 +77,7 @@
for(var/obj/item/I in items)
if(istype(I, /obj/item/weapon/blobcore_chunk))
var/obj/item/weapon/blobcore_chunk/BC = I
if(!overmind || (BC.blob_type && overmind.blob_type.type == BC.blob_type.type))
if(!overmind || (BC.blob_type && overmind.blob_type.type == BC.blob_type.type) || BC.blob_type.faction == faction)
ally = TRUE
break
+1 -1
View File
@@ -289,7 +289,7 @@
// If we have a grab
var/list/grablist = my_mob.ret_grab()
if(grablist.len)
if(LAZYLEN(grablist))
grablist -= my_mob // Just in case we're in a circular grab chain
// It's just us and another person
+1
View File
@@ -173,6 +173,7 @@ h1.alert, h2.alert {color: #000000;}
.terminus {font-family: "Times New Roman", Times, serif, sans-serif}
.interface {color: #330033;}
.spacer {color: #9c660b;} /* VOREStation Add */
.blob {color: #ff950d; font-weight: bold; font-style: italic;}
.black {color: #000000;}
.darkgray {color: #808080;}
+2 -1
View File
@@ -111,7 +111,8 @@ h1.alert, h2.alert {color: #000000;}
.say_quote {font-family: Georgia, Verdana, sans-serif;}
.terminus {font-family: "Times New Roman", Times, serif, sans-serif}
.interface {color: #330033;}
.spacer {color: #9c660b;}
.spacer {color: #9c660b;} /* VOREStation Add */
.blob {color: #ff950d; font-weight: bold; font-style: italic;}
BIG IMG.icon {width: 32px; height: 32px;}