This commit is contained in:
Seris02
2020-03-14 11:41:39 +08:00
1787 changed files with 407246 additions and 233473 deletions
+1 -1
View File
@@ -35,7 +35,7 @@
return FALSE
return TRUE
/obj/machinery/ntnet_relay/update_icon()
/obj/machinery/ntnet_relay/update_icon_state()
if(is_operational())
icon_state = "bus"
else
+1 -1
View File
@@ -63,7 +63,7 @@
addtimer(CALLBACK(src, .proc/emagNotify), 150)
return TRUE
/obj/machinery/vr_sleeper/update_icon()
/obj/machinery/vr_sleeper/update_icon_state()
icon_state = "[initial(icon_state)][state_open ? "-open" : ""]"
/obj/machinery/vr_sleeper/open_machine()
+2 -2
View File
@@ -9,9 +9,9 @@
/world/IsBanned(key,address,computer_id,type,real_bans_only=FALSE)
var/static/key_cache = list()
if(!real_bans_only)
if(key_cache[key])
if(key_cache[key] >= REALTIMEOFDAY)
return list("reason"="concurrent connection attempts", "desc"="You are attempting to connect too fast. Try again.")
key_cache[key] = 1
key_cache[key] = REALTIMEOFDAY+10 //this shouldn't runtime, but if it does, expiry attempts will cover it to ensure genuine connection attemps wont get trapped in limbo
if (!key || !address || !computer_id)
if(real_bans_only)
+9 -9
View File
@@ -240,7 +240,7 @@
if( isemptylist(GLOB.news_network.network_channels) )
dat+="<I>No active channels found...</I>"
else
for(var/datum/newscaster/feed_channel/CHANNEL in GLOB.news_network.network_channels)
for(var/datum/news/feed_channel/CHANNEL in GLOB.news_network.network_channels)
if(CHANNEL.is_admin_channel)
dat+="<B><FONT style='BACKGROUND-COLOR: LightGreen'><A href='?src=[REF(src)];ac_show_channel=[REF(CHANNEL)]'>[CHANNEL.channel_name]</A></FONT></B><BR>"
else
@@ -277,7 +277,7 @@
if(src.admincaster_feed_channel.channel_name =="" || src.admincaster_feed_channel.channel_name == "\[REDACTED\]")
dat+="<FONT COLOR='maroon'>•Invalid channel name.</FONT><BR>"
var/check = 0
for(var/datum/newscaster/feed_channel/FC in GLOB.news_network.network_channels)
for(var/datum/news/feed_channel/FC in GLOB.news_network.network_channels)
if(FC.channel_name == src.admincaster_feed_channel.channel_name)
check = 1
break
@@ -294,7 +294,7 @@
dat+="<I>No feed messages found in channel...</I><BR>"
else
var/i = 0
for(var/datum/newscaster/feed_message/MESSAGE in src.admincaster_feed_channel.messages)
for(var/datum/news/feed_message/MESSAGE in src.admincaster_feed_channel.messages)
i++
dat+="-[MESSAGE.returnBody(-1)] <BR>"
if(MESSAGE.img)
@@ -302,7 +302,7 @@
dat+="<img src='tmp_photo[i].png' width = '180'><BR><BR>"
dat+="<FONT SIZE=1>\[Story by <FONT COLOR='maroon'>[MESSAGE.returnAuthor(-1)]</FONT>\]</FONT><BR>"
dat+="[MESSAGE.comments.len] comment[MESSAGE.comments.len > 1 ? "s" : ""]:<br>"
for(var/datum/newscaster/feed_comment/comment in MESSAGE.comments)
for(var/datum/news/feed_comment/comment in MESSAGE.comments)
dat+="[comment.body]<br><font size=1>[comment.author] [comment.time_stamp]</font><br>"
dat+="<br>"
dat+="<BR><HR><A href='?src=[REF(src)];[HrefToken()];ac_refresh=1'>Refresh</A>"
@@ -315,7 +315,7 @@
if(isemptylist(GLOB.news_network.network_channels))
dat+="<I>No feed channels found active...</I><BR>"
else
for(var/datum/newscaster/feed_channel/CHANNEL in GLOB.news_network.network_channels)
for(var/datum/news/feed_channel/CHANNEL in GLOB.news_network.network_channels)
dat+="<A href='?src=[REF(src)];[HrefToken()];ac_pick_censor_channel=[REF(CHANNEL)]'>[CHANNEL.channel_name]</A> [(CHANNEL.censored) ? ("<FONT COLOR='red'>***</FONT>") : ""]<BR>"
dat+="<BR><A href='?src=[REF(src)];[HrefToken()];ac_setScreen=[0]'>Cancel</A>"
if(11)
@@ -326,7 +326,7 @@
if(isemptylist(GLOB.news_network.network_channels))
dat+="<I>No feed channels found active...</I><BR>"
else
for(var/datum/newscaster/feed_channel/CHANNEL in GLOB.news_network.network_channels)
for(var/datum/news/feed_channel/CHANNEL in GLOB.news_network.network_channels)
dat+="<A href='?src=[REF(src)];[HrefToken()];ac_pick_d_notice=[REF(CHANNEL)]'>[CHANNEL.channel_name]</A> [(CHANNEL.censored) ? ("<FONT COLOR='red'>***</FONT>") : ""]<BR>"
dat+="<BR><A href='?src=[REF(src)];[HrefToken()];ac_setScreen=[0]'>Back</A>"
@@ -337,11 +337,11 @@
if( isemptylist(src.admincaster_feed_channel.messages) )
dat+="<I>No feed messages found in channel...</I><BR>"
else
for(var/datum/newscaster/feed_message/MESSAGE in src.admincaster_feed_channel.messages)
for(var/datum/news/feed_message/MESSAGE in src.admincaster_feed_channel.messages)
dat+="-[MESSAGE.returnBody(-1)] <BR><FONT SIZE=1>\[Story by <FONT COLOR='maroon'>[MESSAGE.returnAuthor(-1)]</FONT>\]</FONT><BR>"
dat+="<FONT SIZE=2><A href='?src=[REF(src)];[HrefToken()];ac_censor_channel_story_body=[REF(MESSAGE)]'>[(MESSAGE.bodyCensor) ? ("Undo story censorship") : ("Censor story")]</A> - <A href='?src=[REF(src)];[HrefToken()];ac_censor_channel_story_author=[REF(MESSAGE)]'>[(MESSAGE.authorCensor) ? ("Undo Author Censorship") : ("Censor message Author")]</A></FONT><BR>"
dat+="[MESSAGE.comments.len] comment[MESSAGE.comments.len > 1 ? "s" : ""]: <a href='?src=[REF(src)];[HrefToken()];ac_lock_comment=[REF(MESSAGE)]'>[MESSAGE.locked ? "Unlock" : "Lock"]</a><br>"
for(var/datum/newscaster/feed_comment/comment in MESSAGE.comments)
for(var/datum/news/feed_comment/comment in MESSAGE.comments)
dat+="[comment.body] <a href='?src=[REF(src)];[HrefToken()];ac_del_comment=[REF(comment)];ac_del_comment_msg=[REF(MESSAGE)]'>X</a><br><font size=1>[comment.author] [comment.time_stamp]</font><br>"
dat+="<BR><A href='?src=[REF(src)];[HrefToken()];ac_setScreen=[10]'>Back</A>"
if(13)
@@ -354,7 +354,7 @@
if( isemptylist(src.admincaster_feed_channel.messages) )
dat+="<I>No feed messages found in channel...</I><BR>"
else
for(var/datum/newscaster/feed_message/MESSAGE in src.admincaster_feed_channel.messages)
for(var/datum/news/feed_message/MESSAGE in src.admincaster_feed_channel.messages)
dat+="-[MESSAGE.returnBody(-1)] <BR><FONT SIZE=1>\[Story by <FONT COLOR='maroon'>[MESSAGE.returnAuthor(-1)]</FONT>\]</FONT><BR>"
dat+="<BR><A href='?src=[REF(src)];[HrefToken()];ac_setScreen=[11]'>Back</A>"
if(14)
+1 -2
View File
@@ -23,8 +23,7 @@ GLOBAL_PROTECT(protected_ranks)
name = init_name
if(!name)
qdel(src)
throw EXCEPTION("Admin rank created without name.")
return
CRASH("Admin rank created without name.")
if(init_rights)
rights = init_rights
include_rights = rights
+1 -1
View File
@@ -714,7 +714,7 @@ GLOBAL_PROTECT(admin_verbs_hideable)
AI_Interact = !AI_Interact
if(mob && IsAdminGhost(mob))
mob.has_unlimited_silicon_privilege = AI_Interact
mob.silicon_privileges = AI_Interact ? ALL : NONE
log_admin("[key_name(usr)] has [AI_Interact ? "activated" : "deactivated"] Admin AI Interact")
message_admins("[key_name_admin(usr)] has [AI_Interact ? "activated" : "deactivated"] their AI interaction")
+3
View File
@@ -40,6 +40,9 @@
H.dna.features["insect_wings"] = pick(GLOB.insect_wings_list)
H.dna.features["deco_wings"] = pick(GLOB.deco_wings_list)
H.dna.features["insect_fluff"] = pick(GLOB.insect_fluffs_list)
H.dna.features["flavor_text"] = "" //Oh no.
SEND_SIGNAL(H, COMSIG_HUMAN_ON_RANDOMIZE)
H.update_body()
H.update_hair()
+5 -7
View File
@@ -19,9 +19,9 @@ GLOBAL_PROTECT(href_token)
var/spamcooldown = 0
var/admincaster_screen = 0 //TODO: remove all these 5 variables, they are completly unacceptable
var/datum/newscaster/feed_message/admincaster_feed_message = new /datum/newscaster/feed_message
var/datum/newscaster/wanted_message/admincaster_wanted_message = new /datum/newscaster/wanted_message
var/datum/newscaster/feed_channel/admincaster_feed_channel = new /datum/newscaster/feed_channel
var/datum/news/feed_message/admincaster_feed_message = new /datum/news/feed_message
var/datum/news/wanted_message/admincaster_wanted_message = new /datum/news/wanted_message
var/datum/news/feed_channel/admincaster_feed_channel = new /datum/news/feed_channel
var/admin_signature
var/href_token
@@ -39,12 +39,10 @@ GLOBAL_PROTECT(href_token)
return
if(!ckey)
QDEL_IN(src, 0)
throw EXCEPTION("Admin datum created without a ckey")
return
CRASH("Admin datum created without a ckey")
if(!istype(R))
QDEL_IN(src, 0)
throw EXCEPTION("Admin datum created without a rank")
return
CRASH("Admin datum created without a rank")
target = ckey
name = "[ckey]'s admin datum ([R])"
rank = R
+15 -15
View File
@@ -2481,7 +2481,7 @@
if(!check_rights(R_ADMIN))
return
var/check = 0
for(var/datum/newscaster/feed_channel/FC in GLOB.news_network.network_channels)
for(var/datum/news/feed_channel/FC in GLOB.news_network.network_channels)
if(FC.channel_name == src.admincaster_feed_channel.channel_name)
check = 1
break
@@ -2500,7 +2500,7 @@
if(!check_rights(R_ADMIN))
return
var/list/available_channels = list()
for(var/datum/newscaster/feed_channel/F in GLOB.news_network.network_channels)
for(var/datum/news/feed_channel/F in GLOB.news_network.network_channels)
available_channels += F.channel_name
src.admincaster_feed_channel.channel_name = adminscrub(input(usr, "Choose receiving Feed Channel.", "Network Channel Handler") in available_channels )
src.access_news_network()
@@ -2606,28 +2606,28 @@
else if(href_list["ac_censor_channel_author"])
if(!check_rights(R_ADMIN))
return
var/datum/newscaster/feed_channel/FC = locate(href_list["ac_censor_channel_author"])
var/datum/news/feed_channel/FC = locate(href_list["ac_censor_channel_author"])
FC.toggleCensorAuthor()
src.access_news_network()
else if(href_list["ac_censor_channel_story_author"])
if(!check_rights(R_ADMIN))
return
var/datum/newscaster/feed_message/MSG = locate(href_list["ac_censor_channel_story_author"])
var/datum/news/feed_message/MSG = locate(href_list["ac_censor_channel_story_author"])
MSG.toggleCensorAuthor()
src.access_news_network()
else if(href_list["ac_censor_channel_story_body"])
if(!check_rights(R_ADMIN))
return
var/datum/newscaster/feed_message/MSG = locate(href_list["ac_censor_channel_story_body"])
var/datum/news/feed_message/MSG = locate(href_list["ac_censor_channel_story_body"])
MSG.toggleCensorBody()
src.access_news_network()
else if(href_list["ac_pick_d_notice"])
if(!check_rights(R_ADMIN))
return
var/datum/newscaster/feed_channel/FC = locate(href_list["ac_pick_d_notice"])
var/datum/news/feed_channel/FC = locate(href_list["ac_pick_d_notice"])
src.admincaster_feed_channel = FC
src.admincaster_screen=13
src.access_news_network()
@@ -2635,7 +2635,7 @@
else if(href_list["ac_toggle_d_notice"])
if(!check_rights(R_ADMIN))
return
var/datum/newscaster/feed_channel/FC = locate(href_list["ac_toggle_d_notice"])
var/datum/news/feed_channel/FC = locate(href_list["ac_toggle_d_notice"])
FC.toggleCensorDclass()
src.access_news_network()
@@ -2651,17 +2651,17 @@
src.admincaster_screen = text2num(href_list["ac_setScreen"])
if (src.admincaster_screen == 0)
if(src.admincaster_feed_channel)
src.admincaster_feed_channel = new /datum/newscaster/feed_channel
src.admincaster_feed_channel = new /datum/news/feed_channel
if(src.admincaster_feed_message)
src.admincaster_feed_message = new /datum/newscaster/feed_message
src.admincaster_feed_message = new /datum/news/feed_message
if(admincaster_wanted_message)
admincaster_wanted_message = new /datum/newscaster/wanted_message
admincaster_wanted_message = new /datum/news/wanted_message
src.access_news_network()
else if(href_list["ac_show_channel"])
if(!check_rights(R_ADMIN))
return
var/datum/newscaster/feed_channel/FC = locate(href_list["ac_show_channel"])
var/datum/news/feed_channel/FC = locate(href_list["ac_show_channel"])
src.admincaster_feed_channel = FC
src.admincaster_screen = 9
src.access_news_network()
@@ -2669,7 +2669,7 @@
else if(href_list["ac_pick_censor_channel"])
if(!check_rights(R_ADMIN))
return
var/datum/newscaster/feed_channel/FC = locate(href_list["ac_pick_censor_channel"])
var/datum/news/feed_channel/FC = locate(href_list["ac_pick_censor_channel"])
src.admincaster_feed_channel = FC
src.admincaster_screen = 12
src.access_news_network()
@@ -2688,8 +2688,8 @@
else if(href_list["ac_del_comment"])
if(!check_rights(R_ADMIN))
return
var/datum/newscaster/feed_comment/FC = locate(href_list["ac_del_comment"])
var/datum/newscaster/feed_message/FM = locate(href_list["ac_del_comment_msg"])
var/datum/news/feed_comment/FC = locate(href_list["ac_del_comment"])
var/datum/news/feed_message/FM = locate(href_list["ac_del_comment_msg"])
FM.comments -= FC
qdel(FC)
src.access_news_network()
@@ -2697,7 +2697,7 @@
else if(href_list["ac_lock_comment"])
if(!check_rights(R_ADMIN))
return
var/datum/newscaster/feed_message/FM = locate(href_list["ac_lock_comment"])
var/datum/news/feed_message/FM = locate(href_list["ac_lock_comment"])
FM.locked ^= 1
src.access_news_network()
+2 -2
View File
@@ -303,7 +303,7 @@ GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/SDQL2_VV_all, new(null
//These three are weird. For best performance, they are only a number when they're not being changed by the SDQL searching/execution code. They only become numbers when they finish changing.
var/list/obj_count_all
var/list/obj_count_eligible
var/list/obj_count_finished
var/obj_count_finished
//Statclick
var/obj/effect/statclick/SDQL2_delete/delete_click
@@ -682,7 +682,7 @@ GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/SDQL2_VV_all, new(null
SDQL2_TICK_CHECK
SDQL2_HALT_CHECK
if(islist(obj_count_finished))
obj_count_finished = obj_count_finished.len
obj_count_finished = length(obj_count_finished)
state = SDQL2_STATE_SWITCHING
/datum/SDQL2_query/proc/SDQL_print(object, list/text_list, print_nulls = TRUE)
+1 -1
View File
@@ -21,6 +21,6 @@
target.gib(1, 1)
else
target.adjustBruteLoss(min(99,(target.health - 1)))
target.Knockdown(400)
target.DefaultCombatKnockdown(400)
target.stuttering = 20
+3 -3
View File
@@ -47,7 +47,7 @@
"emagged" = borg.emagged,
"active_module" = "[borg.module.type]",
"lawupdate" = borg.lawupdate,
"lockdown" = borg.lockcharge,
"lockdown" = borg.locked_down,
"scrambledcodes" = borg.scrambledcodes
)
.["upgrades"] = list()
@@ -122,8 +122,8 @@
message_admins("[key_name_admin(user)] disabled lawsync on [ADMIN_LOOKUPFLW(borg)].")
log_admin("[key_name(user)] disabled lawsync on [key_name(borg)].")
if ("toggle_lockdown")
borg.SetLockdown(!borg.lockcharge)
if (borg.lockcharge)
borg.SetLockdown(!borg.locked_down)
if (borg.locked_down)
message_admins("[key_name_admin(user)] locked down [ADMIN_LOOKUPFLW(borg)].")
log_admin("[key_name(user)] locked down [key_name(borg)].")
else
+3 -3
View File
@@ -487,7 +487,7 @@ GLOBAL_PROTECT(AdminProcCallSpamPrevention)
if(alert("This mob is being controlled by [M.key]. Are you sure you wish to assume control of it? [M.key] will be made a ghost.",,"Yes","No") != "Yes")
return
else
var/mob/dead/observer/ghost = new/mob/dead/observer(M,1)
var/mob/dead/observer/ghost = new/mob/dead/observer(get_turf(M), M)
ghost.ckey = M.ckey
message_admins("<span class='adminnotice'>[key_name_admin(usr)] assumed direct control of [M].</span>")
log_admin("[key_name(usr)] assumed direct control of [M].")
@@ -1058,7 +1058,7 @@ GLOBAL_PROTECT(AdminProcCallSpamPrevention)
set name = "Start Line Profiling"
set desc = "Starts tracking line by line profiling for code lines that support it"
PROFILE_START
LINE_PROFILE_START
message_admins("<span class='adminnotice'>[key_name_admin(src)] started line by line profiling.</span>")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Start Line Profiling")
@@ -1069,7 +1069,7 @@ GLOBAL_PROTECT(AdminProcCallSpamPrevention)
set name = "Stops Line Profiling"
set desc = "Stops tracking line by line profiling for code lines that support it"
PROFILE_STOP
LINE_PROFILE_STOP
message_admins("<span class='adminnotice'>[key_name_admin(src)] stopped line by line profiling.</span>")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Stop Line Profiling")
+1 -1
View File
@@ -135,7 +135,7 @@ GLOBAL_LIST_EMPTY(dirty_vars)
if(!(locate(/obj/structure/grille) in T))
var/window_check = 0
for(var/obj/structure/window/W in T)
if (W.dir == turn(C1.dir,180) || W.dir in list(5,6,9,10) )
if(W.dir == turn(C1.dir,180) || (W.dir in list(5,6,9,10)))
window_check = 1
break
if(!window_check)
@@ -14,7 +14,8 @@ GLOBAL_LIST_EMPTY(antagonists)
var/list/objectives = list()
var/antag_memory = ""//These will be removed with antag datum
var/antag_moodlet //typepath of moodlet that the mob will gain with their status
var/can_hijack = HIJACK_NEUTRAL //If these antags are alone on shuttle hijack happens.
/// If above 0, this is the multiplier for the speed at which we hijack the shuttle. Do not directly read, use hijack_speed().
var/hijack_speed = 0
//Antag panel properties
var/show_in_antagpanel = TRUE //This will hide adding this antag type in antag panel, use only for internal subtypes that shouldn't be added directly but still show if possessed by mind
@@ -120,15 +121,16 @@ GLOBAL_LIST_EMPTY(antagonists)
SEND_SIGNAL(owner.current, COMSIG_CLEAR_MOOD_EVENT, "antag_moodlet")
/datum/antagonist/proc/remove_blacklisted_quirks()
var/mob/living/L = owner
var/mob/living/L = owner.current
if(istype(L))
var/list/cut = list()
cut = SSquirks.filter_quirks(L.roundstart_quirks,blacklisted_quirks)
for(var/q in cut)
var/list/my_quirks = L.client?.prefs.all_quirks.Copy()
SSquirks.filter_quirks(my_quirks,blacklisted_quirks)
for(var/q in L.roundstart_quirks)
var/datum/quirk/Q = q
if(Q.antag_removal_text)
to_chat(L, "<span class='boldannounce'>[Q.antag_removal_text]</span>")
L.remove_quirk(Q.type)
if(!(SSquirks.quirk_name_by_path(Q.type) in my_quirks))
if(initial(Q.antag_removal_text))
to_chat(L, "<span class='boldannounce'>[initial(Q.antag_removal_text)]</span>")
L.remove_quirk(Q.type)
//Returns the team antagonist belongs to if any.
/datum/antagonist/proc/get_team()
@@ -228,6 +230,13 @@ GLOBAL_LIST_EMPTY(antagonists)
return
antag_memory = new_memo
/// Gets how fast we can hijack the shuttle, return 0 for can not hijack. Defaults to hijack_speed var, override for custom stuff like buffing hijack speed for hijack objectives or something.
/datum/antagonist/proc/hijack_speed()
var/datum/objective/hijack/H = locate() in objectives
if(!isnull(H?.hijack_speed_override))
return H.hijack_speed_override
return hijack_speed
//This one is created by admin tools for custom objectives
/datum/antagonist/custom
antagpanel_category = "Custom"
@@ -4,6 +4,7 @@
var/name = "team"
var/member_name = "member"
var/list/objectives = list() //common objectives, these won't be added or removed automatically, subtypes handle this, this is here for bookkeeping purposes.
var/show_roundend_report = TRUE
/datum/team/New(starting_members)
. = ..()
@@ -25,6 +26,8 @@
//Display members/victory/failure/objectives for the team
/datum/team/proc/roundend_report()
if(!show_roundend_report)
return
var/list/report = list()
report += "<span class='header'>[name]:</span>"
@@ -116,14 +116,13 @@
var/mob/living/carbon/human/M = loc
M.adjustStaminaLoss(-75)
M.SetUnconscious(0)
M.SetStun(0)
M.SetKnockdown(0)
M.SetAllImmobility(0)
combat_cooldown = 0
START_PROCESSING(SSobj, src)
/obj/item/clothing/suit/armor/abductor/vest/process()
combat_cooldown++
if(combat_cooldown==initial(combat_cooldown))
if(combat_cooldown == initial(combat_cooldown))
STOP_PROCESSING(SSobj, src)
/obj/item/clothing/suit/armor/abductor/Destroy()
@@ -420,8 +419,9 @@
<br>
Congratulations! You are now trained for invasive xenobiology research!"}
/obj/item/paper/guides/antag/abductor/update_icon()
return
/obj/item/paper/guides/antag/abductor/ComponentInitialize()
. = ..()
AddElement(/datum/element/update_icon_blocker)
/obj/item/paper/guides/antag/abductor/AltClick()
return //otherwise it would fold into a paperplane.
@@ -443,6 +443,10 @@
w_class = WEIGHT_CLASS_NORMAL
actions_types = list(/datum/action/item_action/toggle_mode)
/obj/item/abductor/baton/ComponentInitialize()
. = ..()
AddElement(/datum/element/update_icon_updates_onmob)
/obj/item/abductor/baton/proc/toggle(mob/living/user=usr)
mode = (mode+1)%BATON_MODES
var/txt
@@ -459,7 +463,7 @@
to_chat(usr, "<span class='notice'>You switch the baton to [txt] mode.</span>")
update_icon()
/obj/item/abductor/baton/update_icon()
/obj/item/abductor/baton/update_icon_state()
switch(mode)
if(BATON_STUN)
icon_state = "wonderprodStun"
@@ -512,7 +516,7 @@
L.lastattackerckey = user.ckey
L.adjustStaminaLoss(35) //because previously it took 5-6 hits to actually "incapacitate" someone for the purposes of the sleep inducement
L.Knockdown(140)
L.DefaultCombatKnockdown(140)
L.apply_effect(EFFECT_STUTTER, 7)
SEND_SIGNAL(L, COMSIG_LIVING_MINOR_SHOCK)
@@ -223,7 +223,7 @@
return
/obj/machinery/abductor/experiment/update_icon()
/obj/machinery/abductor/experiment/update_icon_state()
if(state_open)
icon_state = "experiment-open"
else
@@ -20,7 +20,7 @@
/mob/living/simple_animal/hostile/blob/update_icons()
if(overmind)
add_atom_colour(overmind.blob_reagent_datum.color, FIXED_COLOUR_PRIORITY)
add_atom_colour(overmind.blobstrain.color, FIXED_COLOUR_PRIORITY)
else
remove_atom_colour(FIXED_COLOUR_PRIORITY)
@@ -34,7 +34,7 @@
for(var/i in 1 to 2)
var/obj/effect/temp_visual/heal/H = new /obj/effect/temp_visual/heal(get_turf(src)) //hello yes you are being healed
if(overmind)
H.color = overmind.blob_reagent_datum.complementary_color
H.color = overmind.blobstrain.complementary_color
else
H.color = "#000000"
adjustHealth(-maxHealth*0.0125)
@@ -142,8 +142,10 @@
// Create the reagents to put into the air
create_reagents(10)
if(overmind && overmind.blob_reagent_datum)
reagents.add_reagent(overmind.blob_reagent_datum.type, 10)
if(overmind && overmind.blobstrain)
overmind.blobstrain.on_sporedeath(src)
else
reagents.add_reagent(/datum/reagent/toxin/spore, 10)
@@ -167,14 +169,14 @@
/mob/living/simple_animal/hostile/blob/blobspore/update_icons()
if(overmind)
add_atom_colour(overmind.blob_reagent_datum.complementary_color, FIXED_COLOUR_PRIORITY)
add_atom_colour(overmind.blobstrain.complementary_color, FIXED_COLOUR_PRIORITY)
else
remove_atom_colour(FIXED_COLOUR_PRIORITY)
if(is_zombie)
copy_overlays(oldguy, TRUE)
var/mutable_appearance/blob_head_overlay = mutable_appearance('icons/mob/blob.dmi', "blob_head")
if(overmind)
blob_head_overlay.color = overmind.blob_reagent_datum.complementary_color
blob_head_overlay.color = overmind.blobstrain.complementary_color
color = initial(color)//looks better.
add_overlay(blob_head_overlay)
@@ -218,11 +220,16 @@
/mob/living/simple_animal/hostile/blob/blobbernaut/Initialize()
. = ..()
if(!independent) //no pulling people deep into the blob
verbs -= /mob/living/verb/pulled
else
if(independent)
pass_flags &= ~PASSBLOB
/mob/living/simple_animal/hostile/blob/blobbernaut/start_pulling(atom/movable/AM, state, force = pull_force, supress_message = FALSE)
if(!independent && ismob(AM))
if(!supress_message)
to_chat(src, "<span class='warning'>You are unable to grasp people in this form.</span>")
return FALSE
return ..()
/mob/living/simple_animal/hostile/blob/blobbernaut/Life()
if(..())
var/list/blobs_in_area = range(2, src)
@@ -238,14 +245,14 @@
adjustHealth(-maxHealth*0.1)
var/obj/effect/temp_visual/heal/H = new /obj/effect/temp_visual/heal(get_turf(src)) //hello yes you are being healed
if(overmind)
H.color = overmind.blob_reagent_datum.complementary_color
H.color = overmind.blobstrain.complementary_color
else
H.color = "#000000"
if(locate(/obj/structure/blob/node) in blobs_in_area)
adjustHealth(-maxHealth*0.05)
var/obj/effect/temp_visual/heal/H = new /obj/effect/temp_visual/heal(get_turf(src))
if(overmind)
H.color = overmind.blob_reagent_datum.complementary_color
H.color = overmind.blobstrain.complementary_color
else
H.color = "#000000"
if(damagesources)
@@ -254,7 +261,7 @@
var/image/I = new('icons/mob/blob.dmi', src, "nautdamage", MOB_LAYER+0.01)
I.appearance_flags = RESET_COLOR
if(overmind)
I.color = overmind.blob_reagent_datum.complementary_color
I.color = overmind.blobstrain.complementary_color
flick_overlay_view(I, src, 8)
/mob/living/simple_animal/hostile/blob/blobbernaut/adjustHealth(amount, updating_health = TRUE, forced = FALSE)
@@ -269,16 +276,14 @@
/mob/living/simple_animal/hostile/blob/blobbernaut/AttackingTarget()
. = ..()
if(. && isliving(target) && overmind)
var/mob/living/L = target
var/mob_protection = L.get_permeability_protection()
overmind.blob_reagent_datum.reaction_mob(L, VAPOR, 20, 0, mob_protection, overmind)//this will do between 10 and 20 damage(reduced by mob protection), depending on chemical, plus 4 from base brute damage.
overmind.blobstrain.blobbernaut_attack(target)
/mob/living/simple_animal/hostile/blob/blobbernaut/update_icons()
..()
if(overmind) //if we have an overmind, we're doing chemical reactions instead of pure damage
melee_damage_lower = 4
melee_damage_upper = 4
attacktext = overmind.blob_reagent_datum.blobbernaut_message
attacktext = overmind.blobstrain.blobbernaut_message
else
melee_damage_lower = initial(melee_damage_lower)
melee_damage_upper = initial(melee_damage_upper)
@@ -8,12 +8,8 @@
explosion_block = 6
point_return = -1
health_regen = 0 //we regen in Life() instead of when pulsed
var/core_regen = 2
var/resource_delay = 0
var/point_rate = 2
/obj/structure/blob/core/Initialize(mapload, client/new_overmind = null, new_rate = 2, placed = 0)
/obj/structure/blob/core/Initialize(mapload, client/new_overmind = null, placed = 0)
GLOB.blob_cores += src
START_PROCESSING(SSobj, src)
GLOB.poi_list |= src
@@ -22,7 +18,6 @@
return INITIALIZE_HINT_QDEL
if(overmind)
update_icon()
point_rate = new_rate
addtimer(CALLBACK(src, .proc/generate_announcement), 1800)
. = ..()
@@ -37,7 +32,7 @@
color = null
var/mutable_appearance/blob_overlay = mutable_appearance('icons/mob/blob.dmi', "blob")
if(overmind)
blob_overlay.color = overmind.blob_reagent_datum.color
blob_overlay.color = overmind.blobstrain.color
add_overlay(blob_overlay)
add_overlay(mutable_appearance('icons/mob/blob.dmi', "blob_core_overlay"))
@@ -60,17 +55,13 @@
if(overmind) //we should have an overmind, but...
overmind.update_health_hud()
/obj/structure/blob/core/Life()
/obj/structure/blob/core/process()
if(QDELETED(src))
return
if(!overmind)
qdel(src)
else
if(resource_delay <= world.time)
resource_delay = world.time + 10 // 1 second
overmind.add_points(point_rate)
obj_integrity = min(max_integrity, obj_integrity+core_regen)
if(overmind)
overmind.blobstrain.core_process()
overmind.update_health_hud()
Pulse_Area(overmind, 12, 4, 3)
for(var/obj/structure/blob/normal/B in range(1, src))
@@ -22,7 +22,7 @@
color = null
var/mutable_appearance/blob_overlay = mutable_appearance('icons/mob/blob.dmi', "blob")
if(overmind)
blob_overlay.color = overmind.blob_reagent_datum.color
blob_overlay.color = overmind.blobstrain.color
add_overlay(blob_overlay)
add_overlay(mutable_appearance('icons/mob/blob.dmi', "blob_node_overlay"))
@@ -31,6 +31,6 @@
STOP_PROCESSING(SSobj, src)
return ..()
/obj/structure/blob/node/Life()
/obj/structure/blob/node/process()
if(overmind)
Pulse_Area(overmind, 10, 3, 2)
@@ -0,0 +1,74 @@
GLOBAL_LIST_INIT(valid_blobstrains, subtypesof(/datum/blobstrain) - list(/datum/blobstrain/reagent, /datum/blobstrain/multiplex))
/datum/blobstrain
var/name
var/description
var/color = "#000000"
var/complementary_color = "#000000" //a color that's complementary to the normal blob color
var/shortdesc = null //just damage and on_mob effects, doesn't include special, blob-tile only effects
var/effectdesc = null //any long, blob-tile specific effects
var/analyzerdescdamage = "Unknown. Report this bug to a coder, or just adminhelp."
var/analyzerdesceffect = "N/A"
var/blobbernaut_message = "slams" //blobbernaut attack verb
var/message = "The blob strikes you" //message sent to any mob hit by the blob
var/message_living = null //extension to first mob sent to only living mobs i.e. silicons have no skin to be burnt
var/core_regen = 2
var/resource_delay = 0
var/point_rate = 2
var/mob/camera/blob/overmind
/datum/blobstrain/New(mob/camera/blob/new_overmind)
if (!istype(new_overmind))
stack_trace("blobstrain created without overmind")
overmind = new_overmind
/datum/blobstrain/proc/on_gain()
overmind.color = complementary_color
for(var/BL in GLOB.blobs)
var/obj/structure/blob/B = BL
B.update_icon()
for(var/BLO in overmind.blob_mobs)
var/mob/living/simple_animal/hostile/blob/BM = BLO
BM.update_icons() //If it's getting a new strain, tell it what it does!
to_chat(BM, "Your overmind's blob strain is now: <b><font color=\"[color]\">[name]</b></font>!")
to_chat(BM, "The <b><font color=\"[color]\">[name]</b></font> strain [shortdesc ? "[shortdesc]" : "[description]"]")
/datum/blobstrain/proc/on_lose()
/datum/blobstrain/proc/on_sporedeath(mob/living/spore)
/datum/blobstrain/proc/send_message(mob/living/M)
var/totalmessage = message
if(message_living && !issilicon(M))
totalmessage += message_living
totalmessage += "!"
to_chat(M, "<span class='userdanger'>[totalmessage]</span>")
/datum/blobstrain/proc/core_process()
if(resource_delay <= world.time)
resource_delay = world.time + 10 // 1 second
overmind.add_points(point_rate)
overmind.blob_core.obj_integrity = min(overmind.blob_core.max_integrity, overmind.blob_core.obj_integrity+core_regen)
/datum/blobstrain/proc/attack_living(var/mob/living/L) // When the blob attacks people
send_message(L)
/datum/blobstrain/proc/blobbernaut_attack(mob/living/L) // When this blob's blobbernaut attacks people
/datum/blobstrain/proc/damage_reaction(obj/structure/blob/B, damage, damage_type, damage_flag, coefficient = 1) //when the blob takes damage, do this
return coefficient*damage
/datum/blobstrain/proc/death_reaction(obj/structure/blob/B, damage_flag, coefficient = 1) //when a blob dies, do this
return
/datum/blobstrain/proc/expand_reaction(obj/structure/blob/B, obj/structure/blob/newB, turf/T, mob/camera/blob/O, coefficient = 1) //when the blob expands, do this
return
/datum/blobstrain/proc/tesla_reaction(obj/structure/blob/B, power, coefficient = 1) //when the blob is hit by a tesla bolt, do this
return 1 //return 0 to ignore damage
/datum/blobstrain/proc/extinguish_reaction(obj/structure/blob/B, coefficient = 1) //when the blob is hit with water, do this
return
/datum/blobstrain/proc/emp_reaction(obj/structure/blob/B, severity, coefficient = 1) //when the blob is hit with an emp, do this
return
@@ -0,0 +1,33 @@
/datum/blobstrain/reagent // Blobs that mess with reagents, all "legacy" ones
var/datum/reagent/reagent
/datum/blobstrain/reagent/New(mob/camera/blob/new_overmind)
. = ..()
reagent = new reagent()
/datum/blobstrain/reagent/attack_living(var/mob/living/L)
var/mob_protection = L.get_permeability_protection()
reagent.reaction_mob(L, VAPOR, 25, 1, mob_protection, overmind)
send_message(L)
/datum/blobstrain/reagent/blobbernaut_attack(mob/living/L)
var/mob_protection = L.get_permeability_protection()
reagent.reaction_mob(L, VAPOR, 20, 0, mob_protection, overmind)//this will do between 10 and 20 damage(reduced by mob protection), depending on chemical, plus 4 from base brute damage.
/datum/blobstrain/reagent/on_sporedeath(mob/living/spore)
spore.reagents.add_reagent(reagent.type, 10)
// These can only be applied by blobs. They are what (reagent) blobs are made out of.
/datum/reagent/blob
name = "Unknown"
description = "shouldn't exist and you should adminhelp immediately."
color = "#FFFFFF"
taste_description = "bad code and slime"
can_synth = FALSE
/datum/reagent/blob/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O)
if(M.stat == DEAD || istype(M, /mob/living/simple_animal/hostile/blob))
return 0 //the dead, and blob mobs, don't cause reactions
return round(reac_volume * min(1.5 - touch_protection, 1), 0.1) //full touch protection means 50% volume, any prot below 0.5 means 100% volume.
@@ -0,0 +1,41 @@
//sets you on fire, does burn damage, explodes into flame when burnt, weak to water
/datum/blobstrain/reagent/blazing_oil
name = "Blazing Oil"
description = "will do medium burn damage and set targets on fire."
effectdesc = "will also release bursts of flame when burnt, but takes damage from water."
analyzerdescdamage = "Does medium burn damage and sets targets on fire."
analyzerdesceffect = "Releases fire when burnt, but takes damage from water and other extinguishing liquids."
color = "#B68D00"
complementary_color = "#BE5532"
blobbernaut_message = "splashes"
message = "The blob splashes you with burning oil"
message_living = ", and you feel your skin char and melt"
reagent = /datum/reagent/blob/blazing_oil
/datum/blobstrain/reagent/blazing_oil/extinguish_reaction(obj/structure/blob/B)
B.take_damage(1.5, BURN, "energy")
/datum/blobstrain/reagent/blazing_oil/damage_reaction(obj/structure/blob/B, damage, damage_type, damage_flag)
if(damage_type == BURN && damage_flag != "energy")
for(var/turf/open/T in range(1, B))
var/obj/structure/blob/C = locate() in T
if(!(C && C.overmind && C.overmind.blobstrain.type == B.overmind.blobstrain.type) && prob(80))
new /obj/effect/hotspot(T)
if(damage_flag == "fire")
return 0
return ..()
/datum/reagent/blob/blazing_oil
name = "Blazing Oil"
taste_description = "burning oil"
color = "#B68D00"
/datum/reagent/blob/blazing_oil/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O)
reac_volume = ..()
M.adjust_fire_stacks(round(reac_volume/10))
M.IgniteMob()
if(M)
M.apply_damage(0.8*reac_volume, BURN)
if(iscarbon(M))
M.emote("scream")
@@ -0,0 +1,32 @@
//does brute, burn, and toxin damage, and cools targets down
/datum/blobstrain/reagent/cryogenic_poison
name = "Cryogenic Poison"
description = "will inject targets with a freezing poison that does high damage over time."
analyzerdescdamage = "Injects targets with a freezing poison that will gradually solidify the target's internal organs."
color = "#8BA6E9"
complementary_color = "#7D6EB4"
blobbernaut_message = "injects"
message = "The blob stabs you"
message_living = ", and you feel like your insides are solidifying"
reagent = /datum/reagent/blob/cryogenic_poison
/datum/reagent/blob/cryogenic_poison
name = "Cryogenic Poison"
description = "will inject targets with a freezing poison that does high damage over time."
color = "#8BA6E9"
taste_description = "brain freeze"
/datum/reagent/blob/cryogenic_poison/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O)
reac_volume = ..()
if(M.reagents)
M.reagents.add_reagent("frostoil", 0.3*reac_volume)
M.reagents.add_reagent("ice", 0.3*reac_volume)
M.reagents.add_reagent("cryogenic_poison", 0.3*reac_volume)
M.apply_damage(0.2*reac_volume, BRUTE)
/datum/reagent/blob/cryogenic_poison/on_mob_life(mob/living/carbon/M)
M.adjustBruteLoss(0.3*REAGENTS_EFFECT_MULTIPLIER, 0)
M.adjustFireLoss(0.3*REAGENTS_EFFECT_MULTIPLIER, 0)
M.adjustToxLoss(0.3*REAGENTS_EFFECT_MULTIPLIER, 0)
. = 1
..()
@@ -0,0 +1,33 @@
//does burn damage and EMPs, slightly fragile
/datum/blobstrain/reagent/electromagnetic_web
name = "Electromagnetic Web"
color = "#83ECEC"
complementary_color = "#EC8383"
reagent = /datum/reagent/blob/electromagnetic_web
/datum/blobstrain/reagent/electromagnetic_web/damage_reaction(obj/structure/blob/B, damage, damage_type, damage_flag)
if(damage_type == BRUTE) //take full brute
switch(B.brute_resist)
if(0.5)
return damage * 2
if(0.25)
return damage * 4
if(0.1)
return damage * 10
return damage * 1.25 //a laser will do 25 damage, which will kill any normal blob
/datum/blobstrain/reagent/electromagnetic_web/death_reaction(obj/structure/blob/B, damage_flag)
if(damage_flag == "melee" || damage_flag == "bullet" || damage_flag == "laser")
empulse(B.loc, 1, 3) //less than screen range, so you can stand out of range to avoid it
/datum/reagent/blob/electromagnetic_web
name = "Electromagnetic Web"
taste_description = "pop rocks"
color = "#83ECEC"
/datum/reagent/blob/electromagnetic_web/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O)
reac_volume = ..()
if(prob(reac_volume*2))
M.emp_act(EMP_LIGHT)
if(M)
M.apply_damage(reac_volume, BURN)
@@ -0,0 +1,34 @@
//does tons of oxygen damage and a little stamina, immune to tesla bolts, weak to EMP
/datum/blobstrain/reagent/energized_jelly
name = "Energized Jelly"
description = "will cause low stamina and high oxygen damage, and cause targets to be unable to breathe."
effectdesc = "will also conduct electricity, but takes damage from EMPs."
analyzerdescdamage = "Does low stamina damage, high oxygen damage, and prevents targets from breathing."
analyzerdesceffect = "Is immune to electricity and will easily conduct it, but is weak to EMPs."
color = "#EFD65A"
complementary_color = "#00E5B1"
reagent = /datum/reagent/blob/energized_jelly
/datum/blobstrain/reagent/energized_jelly/damage_reaction(obj/structure/blob/B, damage, damage_type, damage_flag)
if((damage_flag == "melee" || damage_flag == "bullet" || damage_flag == "laser") && B.obj_integrity - damage <= 0 && prob(10))
do_sparks(rand(2, 4), FALSE, B)
return ..()
/datum/blobstrain/reagent/energized_jelly/tesla_reaction(obj/structure/blob/B, power)
return 0
/datum/blobstrain/reagent/energized_jelly/emp_reaction(obj/structure/blob/B, severity)
var/damage = rand(30, 50) - severity * rand(10, 15)
B.take_damage(damage, BURN, "energy")
/datum/reagent/blob/energized_jelly
name = "Energized Jelly"
taste_description = "gelatin"
color = "#EFD65A"
/datum/reagent/blob/energized_jelly/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O)
reac_volume = ..()
M.losebreath += round(0.2*reac_volume)
M.adjustStaminaLoss(0.4*reac_volume)
if(M)
M.apply_damage(0.6*reac_volume, OXY)
@@ -0,0 +1,40 @@
//does aoe brute damage when hitting targets, is immune to explosions
/datum/blobstrain/reagent/explosive_lattice
name = "Explosive Lattice"
description = "will do brute damage in an area around targets."
effectdesc = "will also resist explosions, but takes increased damage from fire and other energy sources."
analyzerdescdamage = "Does medium brute damage and causes damage to everyone near its targets."
analyzerdesceffect = "Is highly resistant to explosions, but takes increased damage from fire and other energy sources."
color = "#8B2500"
complementary_color = "#00668B"
blobbernaut_message = "blasts"
message = "The blob blasts you"
reagent = /datum/reagent/blob/explosive_lattice
/datum/blobstrain/reagent/explosive_lattice/damage_reaction(obj/structure/blob/B, damage, damage_type, damage_flag)
if(damage_flag == "bomb")
return 0
else if(damage_flag != "melee" && damage_flag != "bullet" && damage_flag != "laser")
return damage * 1.5
return ..()
/datum/reagent/blob/explosive_lattice
name = "Explosive Lattice"
taste_description = "the bomb"
color = "#8B2500"
/datum/reagent/blob/explosive_lattice/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O)
var/initial_volume = reac_volume
reac_volume = ..()
if(reac_volume >= 10) //if it's not a spore cloud, bad time incoming
var/obj/effect/temp_visual/explosion/fast/E = new /obj/effect/temp_visual/explosion/fast(get_turf(M))
E.alpha = 150
for(var/mob/living/L in orange(get_turf(M), 1))
if(ROLE_BLOB in L.faction) //no friendly fire
continue
var/aoe_volume = ..(L, TOUCH, initial_volume, 0, L.get_permeability_protection(), O)
L.apply_damage(0.4*aoe_volume, BRUTE)
if(M)
M.apply_damage(0.6*reac_volume, BRUTE)
else
M.apply_damage(0.6*reac_volume, BRUTE)
@@ -0,0 +1,40 @@
/datum/blobstrain/multiplex
var/list/blobstrains
var/typeshare
/datum/blobstrain/multiplex/New(mob/camera/blob/new_overmind, list/blobstrains)
. = ..()
for (var/bt in blobstrains)
if (ispath(bt, /datum/blobstrain))
src.blobstrains += new bt(overmind)
else if (istype(bt, /datum/blobstrain))
var/datum/blobstrain/bts = bt
bts.overmind = overmind
src.blobstrains += bt
typeshare = (0.8 * length(src.blobstrains)) - (length(src.blobstrains)-1) // 1 is 80%, 2 are 60% etc
/datum/blobstrain/multiplex/damage_reaction(obj/structure/blob/B, damage, damage_type, damage_flag, coefficient = 1) //when the blob takes damage, do this
for (var/datum/blobstrain/bt in blobstrains)
. += bt.damage_reaction(B, damage, damage_type, damage_flag, coefficient*typeshare)
/datum/blobstrain/multiplex/death_reaction(obj/structure/blob/B, damage_flag, coefficient = 1) //when a blob dies, do this
for (var/datum/blobstrain/bt in blobstrains)
. += bt.death_reaction(B, damage_flag, coefficient*typeshare)
/datum/blobstrain/multiplex/expand_reaction(obj/structure/blob/B, obj/structure/blob/newB, turf/T, mob/camera/blob/O, coefficient = 1) //when the blob expands, do this
for (var/datum/blobstrain/bt in blobstrains)
. += bt.expand_reaction(B, newB, T, O, coefficient*typeshare)
/datum/blobstrain/multiplex/tesla_reaction(obj/structure/blob/B, power, coefficient = 1) //when the blob is hit by a tesla bolt, do this
for (var/datum/blobstrain/bt in blobstrains)
. += bt.tesla_reaction(B, power, coefficient*typeshare)
if (prob(. / length(blobstrains) * 100))
return 1
/datum/blobstrain/multiplex/extinguish_reaction(obj/structure/blob/B, coefficient = 1) //when the blob is hit with water, do this
for (var/datum/blobstrain/bt in blobstrains)
. += bt.extinguish_reaction(B, coefficient*typeshare)
/datum/blobstrain/multiplex/emp_reaction(obj/structure/blob/B, severity, coefficient = 1) //when the blob is hit with an emp, do this
for (var/datum/blobstrain/bt in blobstrains)
. += bt.emp_reaction(B, severity, coefficient*typeshare)
@@ -0,0 +1,38 @@
//does massive brute and burn damage, but can only expand manually
/datum/blobstrain/reagent/networked_fibers
name = "Networked Fibers"
description = "will do high brute and burn damage and will generate resources quicker, but can only expand manually."
shortdesc = "will do high brute and burn damage."
effectdesc = "will move your core when manually expanding near it."
analyzerdescdamage = "Does high brute and burn damage."
analyzerdesceffect = "Is highly mobile and generates resources rapidly."
color = "#CDC0B0"
complementary_color = "#FFF68F"
reagent = /datum/reagent/blob/networked_fibers
/datum/blobstrain/reagent/networked_fibers/expand_reaction(obj/structure/blob/B, obj/structure/blob/newB, turf/T, mob/camera/blob/O)
if(!O && newB.overmind)
if(!istype(B, /obj/structure/blob/node))
newB.overmind.add_points(1)
qdel(newB)
else
var/area/A = get_area(T)
if(!isspaceturf(T) && !istype(A, /area/shuttle))
for(var/obj/structure/blob/core/C in range(1, newB))
if(C.overmind == O)
newB.forceMove(get_turf(C))
C.forceMove(T)
C.setDir(get_dir(newB, C))
O.add_points(1)
//does massive brute and burn damage, but can only expand manually
/datum/reagent/blob/networked_fibers
name = "Networked Fibers"
taste_description = "efficiency"
color = "#CDC0B0"
/datum/reagent/blob/networked_fibers/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O)
reac_volume = ..()
M.apply_damage(0.6*reac_volume, BRUTE)
if(M)
M.apply_damage(0.6*reac_volume, BURN)
@@ -0,0 +1,51 @@
//does low brute damage, oxygen damage, and stamina damage and wets tiles when damaged
/datum/blobstrain/reagent/pressurized_slime
name = "Pressurized Slime"
description = "will do low brute, oxygen, and stamina damage, and wet tiles under targets."
effectdesc = "will also wet tiles near blobs that are attacked or killed."
analyzerdescdamage = "Does low brute damage, low oxygen damage, drains stamina, and wets tiles under targets, extinguishing them."
analyzerdesceffect = "When attacked or killed, lubricates nearby tiles, extinguishing anything on them."
color = "#AAAABB"
complementary_color = "#BBBBAA"
blobbernaut_message = "emits slime at"
message = "The blob splashes into you"
message_living = ", and you gasp for breath"
reagent = /datum/reagent/blob/pressurized_slime
/datum/blobstrain/reagent/pressurized_slime/damage_reaction(obj/structure/blob/B, damage, damage_type, damage_flag)
if((damage_flag == "melee" || damage_flag == "bullet" || damage_flag == "laser") || damage_type != BURN)
extinguisharea(B, damage)
return ..()
/datum/blobstrain/reagent/pressurized_slime/death_reaction(obj/structure/blob/B, damage_flag)
if(damage_flag == "melee" || damage_flag == "bullet" || damage_flag == "laser")
B.visible_message("<span class='boldwarning'>The blob ruptures, spraying the area with liquid!</span>")
extinguisharea(B, 50)
/datum/blobstrain/reagent/pressurized_slime/proc/extinguisharea(obj/structure/blob/B, probchance)
for(var/turf/open/T in range(1, B))
if(prob(probchance))
T.MakeSlippery(TURF_WET_LUBE, min_wet_time = 10 SECONDS, wet_time_to_add = 5 SECONDS)
for(var/obj/O in T)
O.extinguish()
for(var/mob/living/L in T)
L.adjust_fire_stacks(-2.5)
L.ExtinguishMob()
/datum/reagent/blob/pressurized_slime
name = "Pressurized Slime"
taste_description = "a sponge"
color = "#AAAABB"
/datum/reagent/blob/pressurized_slime/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O)
reac_volume = ..()
var/turf/open/T = get_turf(M)
if(istype(T) && prob(reac_volume))
T.MakeSlippery(TURF_WET_LUBE, min_wet_time = 10 SECONDS, wet_time_to_add = 5 SECONDS)
M.adjust_fire_stacks(-(reac_volume / 10))
M.ExtinguishMob()
M.apply_damage(0.4*reac_volume, BRUTE)
if(M)
M.apply_damage(0.4*reac_volume, OXY)
if(M)
M.adjustStaminaLoss(0.2*reac_volume)
@@ -0,0 +1,30 @@
//does brute damage through armor and bio resistance
/datum/blobstrain/reagent/reactive_spines
name = "Reactive Spines"
description = "will do medium brute damage through armor and bio resistance."
effectdesc = "will also react when attacked with brute damage, attacking all near the attacked blob."
analyzerdescdamage = "Does medium brute damage, ignoring armor and bio resistance."
analyzerdesceffect = "When attacked with brute damage, will lash out, attacking everything near it."
color = "#9ACD32"
complementary_color = "#FFA500"
blobbernaut_message = "stabs"
message = "The blob stabs you"
reagent = /datum/reagent/blob/reactive_spines
/datum/blobstrain/reagent/reactive_spines/damage_reaction(obj/structure/blob/B, damage, damage_type, damage_flag)
if(damage && damage_type == BRUTE && B.obj_integrity - damage > 0) //is there any damage, is it brute, and will we be alive
if(damage_flag == "melee")
B.visible_message("<span class='boldwarning'>The blob retaliates, lashing out!</span>")
for(var/atom/A in range(1, B))
A.blob_act(B)
return ..()
/datum/reagent/blob/reactive_spines
name = "Reactive Spines"
taste_description = "rock"
color = "#9ACD32"
/datum/reagent/blob/reactive_spines/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O)
if(M.stat == DEAD || istype(M, /mob/living/simple_animal/hostile/blob))
return 0 //the dead, and blob mobs, don't cause reactions
M.adjustBruteLoss(0.8*reac_volume)
@@ -0,0 +1,33 @@
//does toxin damage, hallucination, targets think they're not hurt at all
/datum/blobstrain/reagent/regenerative_materia
name = "Regenerative Materia"
description = "will do toxin damage and cause targets to believe they are fully healed."
analyzerdescdamage = "Does toxin damage and injects a toxin that causes the target to believe they are fully healed."
color = "#C8A5DC"
complementary_color = "#CD7794"
message_living = ", and you feel <i>alive</i>"
reagent = /datum/reagent/blob/regenerative_materia
/datum/reagent/blob/regenerative_materia
name = "Regenerative Materia"
taste_description = "heaven"
color = "#C8A5DC"
/datum/reagent/blob/regenerative_materia/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O)
reac_volume = ..()
M.adjust_drugginess(reac_volume)
if(M.reagents)
M.reagents.add_reagent("regenerative_materia", 0.2*reac_volume)
M.reagents.add_reagent("spore", 0.2*reac_volume)
M.apply_damage(0.7*reac_volume, TOX)
/datum/reagent/blob/regenerative_materia/on_mob_life(mob/living/carbon/C)
C.adjustToxLoss(1*REAGENTS_EFFECT_MULTIPLIER)
C.hal_screwyhud = SCREWYHUD_HEALTHY //fully healed, honest
..()
/datum/reagent/blob/regenerative_materia/on_mob_delete(mob/living/M)
if(iscarbon(M))
var/mob/living/carbon/N = M
N.hal_screwyhud = 0
..()
@@ -0,0 +1,34 @@
/datum/blobstrain/reagent/replicating_foam
description = "will do medium brute damage and occasionally expand again when expanding."
shortdesc = "will do medium brute damage."
effectdesc = "will also expand when attacked with burn damage, but takes more brute damage."
color = "#7B5A57"
complementary_color = "#57787B"
analyzerdescdamage = "Does medium brute damage."
analyzerdesceffect = "Expands when attacked with burn damage, will occasionally expand again when expanding, and is fragile to brute damage."
reagent = /datum/reagent/blob/replicating_foam
/datum/blobstrain/reagent/replicating_foam/damage_reaction(obj/structure/blob/B, damage, damage_type, damage_flag)
if(damage_type == BRUTE)
damage = damage * 2
else if(damage_type == BURN && damage > 0 && B.obj_integrity - damage > 0 && prob(60))
var/obj/structure/blob/newB = B.expand(null, null, 0)
if(newB)
newB.obj_integrity = B.obj_integrity - damage
newB.update_icon()
return ..()
/datum/blobstrain/reagent/replicating_foam/expand_reaction(obj/structure/blob/B, obj/structure/blob/newB, turf/T, mob/camera/blob/O)
if(prob(30))
newB.expand(null, null, 0) //do it again!
/datum/reagent/blob/replicating_foam
name = "Replicating Foam"
taste_description = "duplication"
color = "#7B5A57"
/datum/reagent/blob/replicating_foam/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O)
reac_volume = ..()
M.apply_damage(0.7*reac_volume, BRUTE)
@@ -0,0 +1,35 @@
//does brute damage, shifts away when damaged
/datum/blobstrain/reagent/shifting_fragments
description = "will do medium brute damage."
effectdesc = "will also cause blob parts to shift away when attacked."
analyzerdescdamage = "Does medium brute damage."
analyzerdesceffect = "When attacked, may shift away from the attacker."
color = "#C8963C"
complementary_color = "#3C6EC8"
reagent = /datum/reagent/blob/shifting_fragments
/datum/blobstrain/reagent/shifting_fragments/expand_reaction(obj/structure/blob/B, obj/structure/blob/newB, turf/T, mob/camera/blob/O)
if(istype(B, /obj/structure/blob/normal) || (istype(B, /obj/structure/blob/shield) && prob(25)))
newB.forceMove(get_turf(B))
B.forceMove(T)
/datum/blobstrain/reagent/shifting_fragments/damage_reaction(obj/structure/blob/B, damage, damage_type, damage_flag)
if((damage_flag == "melee" || damage_flag == "bullet" || damage_flag == "laser") && damage > 0 && B.obj_integrity - damage > 0 && prob(60-damage))
var/list/blobstopick = list()
for(var/obj/structure/blob/OB in orange(1, B))
if((istype(OB, /obj/structure/blob/normal) || (istype(OB, /obj/structure/blob/shield) && prob(25))) && OB.overmind && OB.overmind.blobstrain.type == B.overmind.blobstrain.type)
blobstopick += OB //as long as the blob picked is valid; ie, a normal or shield blob that has the same chemical as we do, we can swap with it
if(blobstopick.len)
var/obj/structure/blob/targeted = pick(blobstopick) //randomize the blob chosen, because otherwise it'd tend to the lower left
var/turf/T = get_turf(targeted)
targeted.forceMove(get_turf(B))
B.forceMove(T) //swap the blobs
return ..()
/datum/reagent/blob/shifting_fragments
name = "Shifting Fragments"
color = "#C8963C"
/datum/reagent/blob/shifting_fragments/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O)
reac_volume = ..()
M.apply_damage(0.7*reac_volume, BRUTE)
@@ -0,0 +1,38 @@
//does brute damage, bonus damage for each nearby blob, and spreads damage out
/datum/blobstrain/reagent/synchronous_mesh
name = "Synchronous Mesh"
description = "will do massively increased brute damage for each blob near the target."
effectdesc = "will also spread damage between each blob near the attacked blob."
analyzerdescdamage = "Does brute damage, increasing for each blob near the target."
analyzerdesceffect = "When attacked, spreads damage between all blobs near the attacked blob."
color = "#65ADA2"
complementary_color = "#AD6570"
blobbernaut_message = "synchronously strikes"
message = "The blobs strike you"
/datum/blobstrain/reagent/synchronous_mesh/damage_reaction(obj/structure/blob/B, damage, damage_type, damage_flag)
if(damage_flag == "melee" || damage_flag == "bullet" || damage_flag == "laser") //the cause isn't fire or bombs, so split the damage
var/damagesplit = 1 //maximum split is 9, reducing the damage each blob takes to 11% but doing that damage to 9 blobs
for(var/obj/structure/blob/C in orange(1, B))
if(!istype(C, /obj/structure/blob/core) && !istype(C, /obj/structure/blob/node) && C.overmind && C.overmind.blobstrain.type == B.overmind.blobstrain.type) //if it doesn't have the same chemical or is a core or node, don't split damage to it
damagesplit += 1
for(var/obj/structure/blob/C in orange(1, B))
if(!istype(C, /obj/structure/blob/core) && !istype(C, /obj/structure/blob/node) && C.overmind && C.overmind.blobstrain.type == B.overmind.blobstrain.type) //only hurt blobs that have the same overmind chemical and aren't cores or nodes
C.take_damage(damage/damagesplit, CLONE, 0, 0)
return damage / damagesplit
else
return damage * 1.25
/datum/reagent/blob/synchronous_mesh
name = "Synchronous Mesh"
taste_description = "toxic mold"
color = "#65ADA2"
/datum/reagent/blob/synchronous_mesh/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O)
reac_volume = ..()
M.apply_damage(0.2*reac_volume, BRUTE)
if(M && reac_volume)
for(var/obj/structure/blob/B in range(1, M)) //if the target is completely surrounded, this is 2.4*reac_volume bonus damage, total of 2.6*reac_volume
if(M)
B.blob_attack_animation(M) //show them they're getting a bad time
M.apply_damage(0.3*reac_volume, BRUTE)
@@ -0,0 +1,46 @@
//kills sleeping targets and turns them into blob zombies, produces fragile spores when killed or on expanding
/datum/blobstrain/reagent/zombifying_pods
name = "Zombifying Pods"
description = "will do very low toxin damage and harvest sleeping targets for additional resources and a blob zombie."
effectdesc = "will also produce fragile spores when killed and on expanding."
shortdesc = "will do very low toxin damage and harvest sleeping targets for additional resources(for your overmind) and a blob zombie."
analyzerdescdamage = "Does very low toxin damage and kills unconscious humans, turning them into blob zombies."
analyzerdesceffect = "Produces spores when expanding and when killed."
color = "#E88D5D"
complementary_color = "#823ABB"
message_living = ", and you feel tired"
reagent = /datum/reagent/blob/zombifying_pods
/datum/blobstrain/reagent/zombifying_pods/damage_reaction(obj/structure/blob/B, damage, damage_type, damage_flag)
if((damage_flag == "melee" || damage_flag == "bullet" || damage_flag == "laser") && damage <= 20 && B.obj_integrity - damage <= 0 && prob(30)) //if the cause isn't fire or a bomb, the damage is less than 21, we're going to die from that damage, 20% chance of a shitty spore.
B.visible_message("<span class='warning'><b>A spore floats free of the blob!</b></span>")
var/mob/living/simple_animal/hostile/blob/blobspore/weak/BS = new/mob/living/simple_animal/hostile/blob/blobspore/weak(B.loc)
BS.overmind = B.overmind
BS.update_icons()
B.overmind.blob_mobs.Add(BS)
return ..()
/datum/blobstrain/reagent/zombifying_pods/expand_reaction(obj/structure/blob/B, obj/structure/blob/newB, turf/T, mob/camera/blob/O)
if(prob(10))
var/mob/living/simple_animal/hostile/blob/blobspore/weak/BS = new/mob/living/simple_animal/hostile/blob/blobspore/weak(T)
BS.overmind = B.overmind
BS.update_icons()
newB.overmind.blob_mobs.Add(BS)
/datum/reagent/blob/zombifying_pods
name = "Zombifying Pods"
color = "#E88D5D"
/datum/reagent/blob/zombifying_pods/reaction_mob(mob/living/M, method=TOUCH, reac_volume, show_message, touch_protection, mob/camera/blob/O)
reac_volume = ..()
M.apply_damage(0.6*reac_volume, TOX)
if(O && ishuman(M) && M.stat == UNCONSCIOUS)
M.death() //sleeping in a fight? bad plan.
var/points = rand(5, 10)
var/mob/living/simple_animal/hostile/blob/blobspore/BS = new/mob/living/simple_animal/hostile/blob/blobspore/weak(get_turf(M))
BS.overmind = O
BS.update_icons()
O.blob_mobs.Add(BS)
BS.Zombify(M)
O.add_points(points)
to_chat(O, "<span class='notice'>Gained [points] resources from the zombification of [M].</span>")
+41 -20
View File
@@ -20,25 +20,25 @@ GLOBAL_LIST_EMPTY(blob_nodes)
pass_flags = PASSBLOB
faction = list(ROLE_BLOB)
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
call_life = TRUE
hud_type = /datum/hud/blob_overmind
var/obj/structure/blob/core/blob_core = null // The blob overmind's core
var/blob_points = 0
var/max_blob_points = 100
var/max_blob_points = 250
var/last_attack = 0
var/datum/reagent/blob/blob_reagent_datum = new/datum/reagent/blob()
var/datum/blobstrain/blobstrain
var/list/blob_mobs = list()
var/list/resource_blobs = list()
var/free_chem_rerolls = 1 //one free chemical reroll
var/free_strain_rerolls = 1 //one free strain reroll
var/last_reroll_time = 0 //time since we last rerolled, used to give free rerolls
var/nodes_required = 1 //if the blob needs nodes to place resource and factory blobs
var/placed = 0
var/base_point_rate = 2 //for blob core placement
var/manualplace_min_time = 600 //in deciseconds //a minute, to get bearings
var/autoplace_max_time = 3600 //six minutes, as long as should be needed
var/list/blobs_legit = list()
var/max_count = 0 //The biggest it got before death
var/blobwincount = 400
var/victory_in_progress = FALSE
var/rerolling = FALSE
/mob/camera/blob/Initialize(mapload, starting_points = 60)
validate_location()
@@ -50,13 +50,14 @@ GLOBAL_LIST_EMPTY(blob_nodes)
name = new_name
real_name = new_name
last_attack = world.time
var/datum/reagent/blob/BC = pick((subtypesof(/datum/reagent/blob)))
blob_reagent_datum = new BC
color = blob_reagent_datum.complementary_color
var/datum/blobstrain/BS = pick(GLOB.valid_blobstrains)
set_strain(BS)
color = blobstrain.complementary_color
if(blob_core)
blob_core.update_icon()
SSshuttle.registerHostileEnvironment(src)
.= ..()
. = ..()
START_PROCESSING(SSobj, src)
/mob/camera/blob/proc/validate_location()
var/turf/T = get_turf(src)
@@ -70,13 +71,28 @@ GLOBAL_LIST_EMPTY(blob_nodes)
CRASH("No blobspawnpoints and blob spawned in nullspace.")
forceMove(T)
/mob/camera/blob/proc/set_strain(datum/blobstrain/new_strain)
if (ispath(new_strain))
var/hadstrain = FALSE
if (istype(blobstrain))
blobstrain.on_lose()
qdel(blobstrain)
hadstrain = TRUE
blobstrain = new new_strain(src)
blobstrain.on_gain()
if (hadstrain)
to_chat(src, "Your strain is now: <b><font color=\"[blobstrain.color]\">[blobstrain.name]</b></font>!")
to_chat(src, "The <b><font color=\"[blobstrain.color]\">[blobstrain.name]</b></font> strain [blobstrain.description]")
if(blobstrain.effectdesc)
to_chat(src, "The <b><font color=\"[blobstrain.color]\">[blobstrain.name]</b></font> strain [blobstrain.effectdesc]")
/mob/camera/blob/proc/is_valid_turf(turf/T)
var/area/A = get_area(T)
if((A && !A.blob_allowed) || !T || !is_station_level(T.z) || isspaceturf(T))
return FALSE
return TRUE
/mob/camera/blob/Life()
/mob/camera/blob/process()
if(!blob_core)
if(!placed)
if(manualplace_min_time && world.time >= manualplace_min_time)
@@ -84,7 +100,7 @@ GLOBAL_LIST_EMPTY(blob_nodes)
to_chat(src, "<span class='big'><font color=\"#EE4000\">You will automatically place your blob core in [DisplayTimeText(autoplace_max_time - world.time)].</font></span>")
manualplace_min_time = 0
if(autoplace_max_time && world.time >= autoplace_max_time)
place_blob_core(base_point_rate, 1)
place_blob_core(1)
else
qdel(src)
else if(!victory_in_progress && (blobs_legit.len >= blobwincount))
@@ -94,11 +110,12 @@ GLOBAL_LIST_EMPTY(blob_nodes)
max_blob_points = INFINITY
blob_points = INFINITY
addtimer(CALLBACK(src, .proc/victory), 450)
else if(!free_strain_rerolls && (last_reroll_time + BLOB_REROLL_TIME<world.time))
to_chat(src, "<b><span class='big'><font color=\"#EE4000\">You have gained another free strain re-roll.</font></span></b>")
free_strain_rerolls = 1
if(!victory_in_progress && max_count < blobs_legit.len)
max_count = blobs_legit.len
..()
/mob/camera/blob/proc/victory()
sound_to_playing_players('sound/machines/alarm.ogg')
@@ -129,7 +146,7 @@ GLOBAL_LIST_EMPTY(blob_nodes)
continue
if(!A.blob_allowed)
continue
A.color = blob_reagent_datum.color
A.color = blobstrain.color
A.name = "blob"
A.icon = 'icons/mob/blob.dmi'
A.icon_state = "blob_shield"
@@ -157,8 +174,12 @@ GLOBAL_LIST_EMPTY(blob_nodes)
BM.overmind = null
BM.update_icons()
GLOB.overminds -= src
blob_mobs = null
resource_blobs = null
blobs_legit = null
SSshuttle.clearHostileEnvironment(src)
STOP_PROCESSING(SSobj, src)
return ..()
@@ -170,9 +191,9 @@ GLOBAL_LIST_EMPTY(blob_nodes)
add_points(0)
/mob/camera/blob/examine(mob/user)
. = ..()
if(blob_reagent_datum)
. += "Its chemical is <font color=\"[blob_reagent_datum.color]\">[blob_reagent_datum.name]</font>."
..()
if(blobstrain)
to_chat(user, "Its strain is <font color=\"[blobstrain.color]\">[blobstrain.name]</font>.")
/mob/camera/blob/update_health_hud()
if(blob_core)
@@ -211,7 +232,7 @@ GLOBAL_LIST_EMPTY(blob_nodes)
src.log_talk(message, LOG_SAY)
var/message_a = say_quote(message)
var/rendered = "<span class='big'><font color=\"#EE4000\"><b>\[Blob Telepathy\] [name](<font color=\"[blob_reagent_datum.color]\">[blob_reagent_datum.name]</font>)</b> [message_a]</font></span>"
var/rendered = "<span class='big'><font color=\"#EE4000\"><b>\[Blob Telepathy\] [name](<font color=\"[blobstrain.color]\">[blobstrain.name]</font>)</b> [message_a]</font></span>"
for(var/mob/M in GLOB.mob_list)
if(isovermind(M) || istype(M, /mob/living/simple_animal/hostile/blob))
@@ -230,8 +251,8 @@ GLOBAL_LIST_EMPTY(blob_nodes)
stat(null, "Core Health: [blob_core.obj_integrity]")
stat(null, "Power Stored: [blob_points]/[max_blob_points]")
stat(null, "Blobs to Win: [blobs_legit.len]/[blobwincount]")
if(free_chem_rerolls)
stat(null, "You have [free_chem_rerolls] Free Chemical Reroll\s Remaining")
if(free_strain_rerolls)
stat(null, "You have [free_strain_rerolls] Free Strain Reroll\s Remaining")
if(!placed)
if(manualplace_min_time)
stat(null, "Time Before Manual Placement: [max(round((manualplace_min_time - world.time)*0.1, 0.1), 0)]")
+38 -40
View File
@@ -7,7 +7,7 @@
// Power verbs
/mob/camera/blob/proc/place_blob_core(point_rate, placement_override , pop_override = FALSE)
/mob/camera/blob/proc/place_blob_core(placement_override, pop_override = FALSE)
if(placed && placement_override != -1)
return 1
if(!placement_override)
@@ -47,7 +47,7 @@
if(placed && blob_core)
blob_core.forceMove(loc)
else
var/obj/structure/blob/core/core = new(get_turf(src), src, point_rate, 1)
var/obj/structure/blob/core/core = new(get_turf(src), src, 1)
core.overmind = src
blobs_legit += src
blob_core = core
@@ -71,13 +71,13 @@
var/list/nodes = list()
for(var/i in 1 to GLOB.blob_nodes.len)
var/obj/structure/blob/node/B = GLOB.blob_nodes[i]
nodes["Blob Node #[i] ([B.overmind ? "[B.overmind.blob_reagent_datum.name]":"No Chemical"])"] = B
nodes["Blob Node #[i] ([B.overmind ? "[B.overmind.blobstrain.name]":"No Strain"])"] = B
var/node_name = input(src, "Choose a node to jump to.", "Node Jump") in nodes
var/obj/structure/blob/node/chosen_node = nodes[node_name]
if(chosen_node)
forceMove(chosen_node.loc)
/mob/camera/blob/proc/createSpecial(price, blobType, nearEquals, needsNode, turf/T)
/mob/camera/blob/proc/createSpecial(price, blobstrain, nearEquals, needsNode, turf/T)
if(!T)
T = get_turf(src)
var/obj/structure/blob/B = (locate(/obj/structure/blob) in T)
@@ -93,12 +93,12 @@
return //handholdotron 2000
if(nearEquals)
for(var/obj/structure/blob/L in orange(nearEquals, T))
if(L.type == blobType)
if(L.type == blobstrain)
to_chat(src, "<span class='warning'>There is a similar blob nearby, move more than [nearEquals] tiles away from it!</span>")
return
if(!can_buy(price))
return
var/obj/structure/blob/N = B.change_to(blobType, src)
var/obj/structure/blob/N = B.change_to(blobstrain, src)
return N
/mob/camera/blob/verb/toggle_node_req()
@@ -123,6 +123,7 @@
if(!can_buy(15))
return
if(S.obj_integrity < S.max_integrity * 0.5)
add_points(BLOB_REFLECTOR_COST)
to_chat(src, "<span class='warning'>This shield blob is too damaged to be modified properly!</span>")
return
to_chat(src, "<span class='warning'>You secrete a reflective ooze over the shield blob, allowing it to reflect projectiles at the cost of reduced intregrity.</span>")
@@ -166,7 +167,9 @@
if(!can_buy(40))
return
var/list/mob/candidates = pollGhostCandidates("Do you want to play as a [blob_reagent_datum.name] blobbernaut?", ROLE_BLOB, null, ROLE_BLOB, 50) //players must answer rapidly
B.naut = TRUE //temporary placeholder to prevent creation of more than one per factory.
to_chat(src, "<span class='notice'>You attempt to produce a blobbernaut.</span>")
var/list/mob/dead/observer/candidates = pollGhostCandidates("Do you want to play as a [blobstrain.name] blobbernaut?", ROLE_BLOB, null, ROLE_BLOB, 50) //players must answer rapidly
if(LAZYLEN(candidates)) //if we got at least one candidate, they're a blobbernaut now.
B.max_integrity = initial(B.max_integrity) * 0.25 //factories that produced a blobbernaut have much lower health
B.obj_integrity = min(B.obj_integrity, B.max_integrity)
@@ -188,8 +191,8 @@
to_chat(blobber, "<b>You are a blobbernaut!</b>")
to_chat(blobber, "You are powerful, hard to kill, and slowly regenerate near nodes and cores, <span class='cultlarge'>but will slowly die if not near the blob</span> or if the factory that made you is killed.")
to_chat(blobber, "You can communicate with other blobbernauts and overminds via <b>:b</b>")
to_chat(blobber, "Your overmind's blob reagent is: <b><font color=\"[blob_reagent_datum.color]\">[blob_reagent_datum.name]</b></font>!")
to_chat(blobber, "The <b><font color=\"[blob_reagent_datum.color]\">[blob_reagent_datum.name]</b></font> reagent [blob_reagent_datum.shortdesc ? "[blob_reagent_datum.shortdesc]" : "[blob_reagent_datum.description]"]")
to_chat(blobber, "Your overmind's blob reagent is: <b><font color=\"[blobstrain.color]\">[blobstrain.name]</b></font>!")
to_chat(blobber, "The <b><font color=\"[blobstrain.color]\">[blobstrain.name]</b></font> reagent [blobstrain.shortdesc ? "[blobstrain.shortdesc]" : "[blobstrain.description]"]")
else
to_chat(src, "<span class='warning'>You could not conjure a sentience for your blobbernaut. Your points have been refunded. Try again later.</span>")
add_points(40)
@@ -265,9 +268,7 @@
continue
if(L.stat != DEAD)
attacksuccess = TRUE
var/mob_protection = L.get_permeability_protection()
blob_reagent_datum.reaction_mob(L, VAPOR, 25, 1, mob_protection, src)
blob_reagent_datum.send_message(L)
blobstrain.attack_living(L)
var/obj/structure/blob/B = locate() in T
if(B)
if(attacksuccess) //if we successfully attacked a turf with a blob on it, don't refund shit
@@ -321,7 +322,7 @@
set category = "Blob"
set name = "Blob Broadcast"
set desc = "Speak with your blob spores and blobbernauts as your mouthpieces."
var/speak_text = input(src, "What would you like to say with your minions?", "Blob Broadcast", null) as text
var/speak_text = stripped_input(src, "What would you like to say with your minions?", "Blob Broadcast", null)
if(!speak_text)
return
else
@@ -331,41 +332,38 @@
if(BM.stat == CONSCIOUS)
BM.say(speak_text)
/mob/camera/blob/verb/chemical_reroll()
/mob/camera/blob/verb/strain_reroll()
set category = "Blob"
set name = "Reactive Chemical Adaptation (40)"
set desc = "Replaces your chemical with a random, different one."
if(free_chem_rerolls || can_buy(40))
set_chemical()
if(free_chem_rerolls)
free_chem_rerolls--
set name = "Reactive Strain Adaptation (40)"
set desc = "Replaces your strain with a random, different one."
if(!rerolling && (free_strain_rerolls || can_buy(40)))
rerolling = TRUE
reroll_strain()
rerolling = FALSE
if(free_strain_rerolls)
free_strain_rerolls--
last_reroll_time = world.time
/mob/camera/blob/proc/set_chemical()
var/datum/reagent/blob/BC = pick((subtypesof(/datum/reagent/blob) - blob_reagent_datum.type))
blob_reagent_datum = new BC
color = blob_reagent_datum.complementary_color
for(var/BL in GLOB.blobs)
var/obj/structure/blob/B = BL
B.update_icon()
for(var/BLO in blob_mobs)
var/mob/living/simple_animal/hostile/blob/BM = BLO
BM.update_icons() //If it's getting a new chemical, tell it what it does!
to_chat(BM, "Your overmind's blob reagent is now: <b><font color=\"[blob_reagent_datum.color]\">[blob_reagent_datum.name]</b></font>!")
to_chat(BM, "The <b><font color=\"[blob_reagent_datum.color]\">[blob_reagent_datum.name]</b></font> reagent [blob_reagent_datum.shortdesc ? "[blob_reagent_datum.shortdesc]" : "[blob_reagent_datum.description]"]")
to_chat(src, "Your reagent is now: <b><font color=\"[blob_reagent_datum.color]\">[blob_reagent_datum.name]</b></font>!")
to_chat(src, "The <b><font color=\"[blob_reagent_datum.color]\">[blob_reagent_datum.name]</b></font> reagent [blob_reagent_datum.description]")
if(blob_reagent_datum.effectdesc)
to_chat(src, "The <b><font color=\"[blob_reagent_datum.color]\">[blob_reagent_datum.name]</b></font> reagent [blob_reagent_datum.effectdesc]")
/mob/camera/blob/proc/reroll_strain()
var/list/choices = list()
while (length(choices) < 4)
var/datum/blobstrain/bs = pick((GLOB.valid_blobstrains))
choices[initial(bs.name)] = bs
var/choice = input(usr, "Please choose a new strain","Strain") as anything in choices
if (choice && choices[choice] && !QDELETED(src))
var/datum/blobstrain/bs = choices[choice]
set_strain(bs)
/mob/camera/blob/verb/blob_help()
set category = "Blob"
set name = "*Blob Help*"
set desc = "Help on how to blob."
to_chat(src, "<b>As the overmind, you can control the blob!</b>")
to_chat(src, "Your blob reagent is: <b><font color=\"[blob_reagent_datum.color]\">[blob_reagent_datum.name]</b></font>!")
to_chat(src, "The <b><font color=\"[blob_reagent_datum.color]\">[blob_reagent_datum.name]</b></font> reagent [blob_reagent_datum.description]")
if(blob_reagent_datum.effectdesc)
to_chat(src, "The <b><font color=\"[blob_reagent_datum.color]\">[blob_reagent_datum.name]</b></font> reagent [blob_reagent_datum.effectdesc]")
to_chat(src, "Your blob reagent is: <b><font color=\"[blobstrain.color]\">[blobstrain.name]</b></font>!")
to_chat(src, "The <b><font color=\"[blobstrain.color]\">[blobstrain.name]</b></font> reagent [blobstrain.description]")
if(blobstrain.effectdesc)
to_chat(src, "The <b><font color=\"[blobstrain.color]\">[blobstrain.name]</b></font> reagent [blobstrain.effectdesc]")
to_chat(src, "<b>You can expand, which will attack people, damage objects, or place a Normal Blob if the tile is clear.</b>")
to_chat(src, "<i>Normal Blobs</i> will expand your reach and can be upgraded into special blobs that perform certain functions.")
to_chat(src, "<b>You can upgrade normal blobs into the following types of blob:</b>")
+16 -23
View File
@@ -83,16 +83,10 @@
/obj/structure/blob/update_icon() //Updates color based on overmind color if we have an overmind.
if(overmind)
add_atom_colour(overmind.blob_reagent_datum.color, FIXED_COLOUR_PRIORITY)
add_atom_colour(overmind.blobstrain.color, FIXED_COLOUR_PRIORITY)
else
remove_atom_colour(FIXED_COLOUR_PRIORITY)
/obj/structure/blob/process()
Life()
/obj/structure/blob/proc/Life()
return
/obj/structure/blob/proc/Pulse_Area(mob/camera/blob/pulsing_overmind, claim_range = 10, pulse_range = 3, expand_range = 2)
if(QDELETED(pulsing_overmind))
pulsing_overmind = overmind
@@ -148,10 +142,10 @@
O.setDir(dir)
if(controller)
var/mob/camera/blob/BO = controller
O.color = BO.blob_reagent_datum.color
O.color = BO.blobstrain.color
O.alpha = 200
else if(overmind)
O.color = overmind.blob_reagent_datum.color
O.color = overmind.blobstrain.color
if(A)
O.do_attack_animation(A) //visually attack the whatever
return O //just in case you want to do something to the animation.
@@ -192,7 +186,7 @@
B.forceMove(T)
B.update_icon()
if(B.overmind && expand_reaction)
B.overmind.blob_reagent_datum.expand_reaction(src, B, T, controller)
B.overmind.blobstrain.expand_reaction(src, B, T, controller)
return B
else
blob_attack_animation(T, controller)
@@ -209,14 +203,14 @@
return
if(severity > 0)
if(overmind)
overmind.blob_reagent_datum.emp_reaction(src, severity)
overmind.blobstrain.emp_reaction(src, severity)
if(prob(100 - severity * 30))
new /obj/effect/temp_visual/emp(get_turf(src))
/obj/structure/blob/tesla_act(power)
..()
if(overmind)
if(overmind.blob_reagent_datum.tesla_reaction(src, power))
if(overmind.blobstrain.tesla_reaction(src, power))
take_damage(power/400, BURN, "energy")
else
take_damage(power/400, BURN, "energy")
@@ -224,7 +218,7 @@
/obj/structure/blob/extinguish()
..()
if(overmind)
overmind.blob_reagent_datum.extinguish_reaction(src)
overmind.blobstrain.extinguish_reaction(src)
/obj/structure/blob/hulk_damage()
return 15
@@ -243,13 +237,13 @@
else
return ..()
/obj/structure/blob/proc/chemeffectreport()
/obj/structure/blob/proc/chemeffectreport(mob/user)
RETURN_TYPE(/list)
. = list()
if(overmind)
. += "<b>Material: <font color=\"[overmind.blob_reagent_datum.color]\">[overmind.blob_reagent_datum.name]</font><span class='notice'>.</span></b>"
. += "<b>Material Effects:</b> <span class='notice'>[overmind.blob_reagent_datum.analyzerdescdamage]</span>"
. += "<b>Material Properties:</b> <span class='notice'>[overmind.blob_reagent_datum.analyzerdesceffect]</span><br>"
to_chat(user, "<b>Material: <font color=\"[overmind.blobstrain.color]\">[overmind.blobstrain.name]</font><span class='notice'>.</span></b>")
to_chat(user, "<b>Material Effects:</b> <span class='notice'>[overmind.blobstrain.analyzerdescdamage]</span>")
to_chat(user, "<b>Material Properties:</b> <span class='notice'>[overmind.blobstrain.analyzerdesceffect]</span><br>")
else
. += "<b>No Material Detected!</b><br>"
@@ -288,7 +282,7 @@
armor_protection = armor.getRating(damage_flag)
damage_amount = round(damage_amount * (100 - armor_protection)*0.01, 0.1)
if(overmind && damage_flag)
damage_amount = overmind.blob_reagent_datum.damage_reaction(src, damage_amount, damage_type, damage_flag)
damage_amount = overmind.blobstrain.damage_reaction(src, damage_amount, damage_type, damage_flag)
return damage_amount
/obj/structure/blob/take_damage(damage_amount, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, attack_dir)
@@ -298,13 +292,12 @@
/obj/structure/blob/obj_destruction(damage_flag)
if(overmind)
overmind.blob_reagent_datum.death_reaction(src, damage_flag)
overmind.blobstrain.death_reaction(src, damage_flag)
..()
/obj/structure/blob/proc/change_to(type, controller)
if(!ispath(type))
throw EXCEPTION("change_to(): invalid type for blob")
return
CRASH("change_to(): invalid type for blob")
var/obj/structure/blob/B = new type(src.loc, controller)
B.creation_action()
B.update_icon()
@@ -333,8 +326,8 @@
/obj/structure/blob/proc/get_chem_name()
if(overmind)
return overmind.blob_reagent_datum.name
return "an unknown variant"
return overmind.blobstrain.name
return "some kind of organic tissue"
/obj/structure/blob/normal
name = "normal blob"
@@ -315,7 +315,7 @@
bloodsuckerdatum.handle_eat_human_food(food_nutrition)
/datum/antagonist/bloodsucker/proc/handle_eat_human_food(var/food_nutrition) // Called from snacks.dm and drinks.dm
/datum/antagonist/bloodsucker/proc/handle_eat_human_food(food_nutrition, puke_blood = TRUE, masquerade_override) // Called from snacks.dm and drinks.dm
set waitfor = FALSE
if(!owner.current || !iscarbon(owner.current))
return
@@ -324,14 +324,14 @@
C.nutrition -= food_nutrition
foodInGut += food_nutrition
// Already ate some bad clams? Then we can back out, because we're already sick from it.
if (foodInGut != food_nutrition)
if(foodInGut != food_nutrition)
return
// Haven't eaten, but I'm in a Human Disguise.
else if (poweron_masquerade)
else if(poweron_masquerade && !masquerade_override)
to_chat(C, "<span class='notice'>Your stomach turns, but your \"human disguise\" keeps the food down...for now.</span>")
// Keep looping until we purge. If we have activated our Human Disguise, we ignore the food. But it'll come up eventually...
var/sickphase = 0
while (foodInGut)
while(foodInGut)
sleep(50)
C.adjust_disgust(10 * sickphase)
// Wait an interval...
@@ -340,24 +340,29 @@
if(C.stat == DEAD)
return
// Put up disguise? Then hold off the vomit.
if(poweron_masquerade)
if(poweron_masquerade && !masquerade_override)
if(sickphase > 0)
to_chat(C, "<span class='notice'>Your stomach settles temporarily. You regain your composure...for now.</span>")
sickphase = 0
continue
switch(sickphase)
if (1)
if(1)
to_chat(C, "<span class='warning'>You feel unwell. You can taste ash on your tongue.</span>")
C.Stun(10)
if (2)
if(2)
to_chat(C, "<span class='warning'>Your stomach turns. Whatever you ate tastes of grave dirt and brimstone.</span>")
C.Dizzy(15)
C.Stun(13)
if (3)
if(3)
to_chat(C, "<span class='warning'>You purge the food of the living from your viscera! You've never felt worse.</span>")
C.vomit(foodInGut * 4, foodInGut * 2, 0) // (var/lost_nutrition = 10, var/blood = 0, var/stun = 1, var/distance = 0, var/message = 1, var/toxic = 0)
C.blood_volume = max(0, C.blood_volume - foodInGut * 2)
//Puke blood only if puke_blood is true, and loose some blood, else just puke normally.
if(puke_blood)
C.blood_volume = max(0, C.blood_volume - foodInGut * 2)
C.vomit(foodInGut * 4, foodInGut * 2, 0)
else
C.vomit(foodInGut * 4, FALSE, 0)
C.Stun(30)
//C.Dizzy(50)
foodInGut = 0
SEND_SIGNAL(C, COMSIG_ADD_MOOD_EVENT, "vampdisgust", /datum/mood_event/bloodsucker_disgust)
sickphase ++
@@ -208,7 +208,7 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/*
// Destroy the Solar Arrays
/datum/objective/bloodsucker/solars
@@ -228,7 +228,7 @@
if (SC && SC.lastgen > 0 && SC.connected_panels.len > 0 && SC.connected_tracker)
return FALSE
return TRUE
*/
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -96,7 +96,7 @@
// Incap?
if(must_be_capacitated)
var/mob/living/L = owner
if (L.incapacitated(TRUE, TRUE) || L.resting && !can_be_immobilized)
if (L.incapacitated(TRUE, TRUE) || !CHECK_MOBILITY(L, MOBILITY_STAND) && !can_be_immobilized)
if(display_error)
to_chat(owner, "<span class='warning'>Not while you're incapacitated!</span>")
return FALSE
@@ -286,10 +286,10 @@
var/mob/living/L = owner.current
level_bloodcost = maxBloodVolume * 0.2
//If the blood volume of the bloodsucker is lower than the cost to level up, return and inform the bloodsucker
//TODO: Make this into a radial, or perhaps a tgui next UI
// Purchase Power Prompt
var/list/options = list()
var/list/options = list()
for(var/pickedpower in typesof(/datum/action/bloodsucker))
var/datum/action/bloodsucker/power = pickedpower
// If I don't own it, and I'm allowed to buy it.
@@ -379,20 +379,21 @@
protege_objective.generate_objective()
add_objective(protege_objective)
if (rand(0,1) == 0)
//if (rand(0,1) == 0)
// Heart Thief Objective
var/datum/objective/bloodsucker/heartthief/heartthief_objective = new
heartthief_objective.owner = owner
heartthief_objective.generate_objective()
add_objective(heartthief_objective)
var/datum/objective/bloodsucker/heartthief/heartthief_objective = new
heartthief_objective.owner = owner
heartthief_objective.generate_objective()
add_objective(heartthief_objective)
/*
else
// Solars Objective
// Solars Objective, doesnt work due to TG updates.
var/datum/objective/bloodsucker/solars/solars_objective = new
solars_objective.owner = owner
solars_objective.generate_objective()
add_objective(solars_objective)
*/
// Survive Objective
var/datum/objective/bloodsucker/survive/survive_objective = new
survive_objective.owner = owner
@@ -1,9 +1,5 @@
// organ_internal.dm -- /obj/item/organ
// Do I have a stake in my heart?
/mob/living/AmStaked()
var/obj/item/bodypart/BP = get_bodypart("chest")
@@ -13,16 +9,14 @@
if (istype(I,/obj/item/stake/))
return TRUE
return FALSE
/mob/proc/AmStaked()
return FALSE
/mob/living/proc/StakeCanKillMe()
return IsSleeping() || stat >= UNCONSCIOUS || blood_volume <= 0 || HAS_TRAIT(src, TRAIT_DEATHCOMA) // NOTE: You can't go to sleep in a coffin with a stake in you.
///obj/item/weapon/melee/stake
/obj/item/stake/
/obj/item/stake
name = "wooden stake"
desc = "A simple wooden stake carved to a sharp point."
icon = 'icons/obj/items_and_weapons.dmi'
@@ -96,6 +90,7 @@
user.dropItemToGround(src, TRUE) //user.drop_item() // "drop item" doesn't seem to exist anymore. New proc is user.dropItemToGround() but it doesn't seem like it's needed now?
var/obj/item/bodypart/B = C.get_bodypart("chest") // This was all taken from hitby() in human_defense.dm
B.embedded_objects |= src
embedded()
add_mob_blood(target)//Place blood on the stake
loc = C // Put INSIDE the character
B.receive_damage(w_class * embedding.embedded_impact_pain_multiplier)
@@ -112,8 +107,7 @@
// Can this target be staked? If someone stands up before this is complete, it fails. Best used on someone stationary.
/mob/living/carbon/proc/can_be_staked()
//return resting || IsKnockdown() || IsUnconscious() || (stat && (stat != SOFT_CRIT || pulledby)) || (has_trait(TRAIT_FAKEDEATH)) || resting || IsStun() || IsFrozen() || (pulledby && pulledby.grab_state >= GRAB_NECK)
return (resting || lying || IsUnconscious() || pulledby && pulledby.grab_state >= GRAB_NECK)
return !CHECK_MOBILITY(src, MOBILITY_STAND)
// ABOVE: Taken from update_mobility() in living.dm
/obj/item/stake/hardened
@@ -42,25 +42,18 @@
/obj/structure/closet/crate
var/mob/living/resident // This lets bloodsuckers claim any "closet" as a Coffin, so long as they could get into it and close it. This locks it in place, too.
/obj/structure/closet/crate/coffin
var/pryLidTimer = 250
can_weld_shut = FALSE
breakout_time = 200
/obj/structure/closet/crate/coffin/blackcoffin
name = "black coffin"
desc = "For those departed who are not so dear."
icon_state = "coffin"
icon = 'icons/obj/vamp_obj.dmi'
can_weld_shut = FALSE
resistance_flags = 0 // Start off with no bonuses.
open_sound = 'sound/bloodsucker/coffin_open.ogg'
close_sound = 'sound/bloodsucker/coffin_close.ogg'
breakout_time = 600
pryLidTimer = 400
resistance_flags = NONE
integrity_failure = 70
max_integrity = 100
integrity_failure = 0.5
armor = list("melee" = 50, "bullet" = 20, "laser" = 30, "energy" = 0, "bomb" = 50, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 60)
/obj/structure/closet/crate/coffin/meatcoffin
@@ -68,8 +61,6 @@
desc = "When you're ready to meat your maker, the steaks can never be too high."
icon_state = "meatcoffin"
icon = 'icons/obj/vamp_obj.dmi'
can_weld_shut = FALSE
resistance_flags = 0 // Start off with no bonuses.
open_sound = 'sound/effects/footstep/slime1.ogg'
close_sound = 'sound/effects/footstep/slime1.ogg'
breakout_time = 200
@@ -77,24 +68,23 @@
resistance_flags = NONE
material_drop = /obj/item/reagent_containers/food/snacks/meat/slab
material_drop_amount = 3
integrity_failure = 40
integrity_failure = 0.57
armor = list("melee" = 70, "bullet" = 10, "laser" = 10, "energy" = 0, "bomb" = 70, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 100)
/obj/structure/closet/crate/coffin/metalcoffin
name = "metal coffin"
desc = "A big metal sardine can inside of another big metal sardine can, in space."
icon_state = "metalcoffin"
icon = 'icons/obj/vamp_obj.dmi'
can_weld_shut = FALSE
resistance_flags = FIRE_PROOF | LAVA_PROOF
open_sound = 'sound/effects/pressureplate.ogg'
close_sound = 'sound/effects/pressureplate.ogg'
breakout_time = 300
pryLidTimer = 200
resistance_flags = NONE
material_drop = /obj/item/stack/sheet/metal
material_drop_amount = 5
integrity_failure = 60
max_integrity = 200
integrity_failure = 0.25
armor = list("melee" = 40, "bullet" = 15, "laser" = 50, "energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 60)
//////////////////////////////////////////////
@@ -205,7 +205,7 @@
buckled_mob.pixel_y = buckled_mob.get_standard_pixel_y_offset(180)
src.visible_message(text("<span class='danger'>[buckled_mob][buckled_mob.stat==DEAD?"'s corpse":""] slides off of the rack.</span>"))
density = FALSE
buckled_mob.AdjustKnockdown(30)
buckled_mob.DefaultCombatKnockdown(30)
update_icon()
useLock = FALSE // Failsafe
@@ -258,7 +258,7 @@
/obj/structure/bloodsucker/vassalrack/proc/torture_victim(mob/living/user, mob/living/target)
var/datum/antagonist/bloodsucker/bloodsuckerdatum = user.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
// Check Bloodmob/living/M, force = FALSE, check_loc = TRUE
var/convert_cost = 200 + 200 * bloodsuckerdatum.vassals
var/convert_cost = 200 + 200 * bloodsuckerdatum.vassals
if(user.blood_volume < convert_cost + 5)
to_chat(user, "<span class='notice'>You don't have enough blood to initiate the Dark Communion with [target].</span>")
return
@@ -449,7 +449,7 @@
/obj/structure/bloodsucker/candelabrum/Destroy()
STOP_PROCESSING(SSobj, src)
/obj/structure/bloodsucker/candelabrum/update_icon()
/obj/structure/bloodsucker/candelabrum/update_icon_state()
icon_state = "candelabrum[lit ? "_lit" : ""]"
/obj/structure/bloodsucker/candelabrum/examine(mob/user)
@@ -72,8 +72,7 @@
if(rand(5 + powerlevel) >= 5)
target.visible_message("<span class='danger'>[user] lands a vicious punch, sending [target] away!</span>", \
"<span class='userdanger'>[user] has landed a horrifying punch on you, sending you flying!!</span>", null, COMBAT_MESSAGE_RANGE)
target.Knockdown(min(5, rand(10, 10 * powerlevel)) )
target.DefaultCombatKnockdown(min(5, rand(10, 10 * powerlevel)) )
// Attack!
playsound(get_turf(target), 'sound/weapons/punch4.ogg', 60, 1, -1)
user.do_attack_animation(target, ATTACK_EFFECT_SMASH)
@@ -145,7 +144,7 @@
// Knock Down (if Living)
if (isliving(M))
var/mob/living/L = M
L.Knockdown(pull_power * 10 + 20)
L.DefaultCombatKnockdown(pull_power * 10 + 20)
// Knock Back (before Knockdown, which probably cancels pull)
var/send_dir = get_dir(owner, M)
var/turf/T = get_ranged_target_turf(M, send_dir, pull_power)
@@ -2,40 +2,59 @@
/datum/action/bloodsucker/cloak
name = "Cloak of Darkness"
desc = "Blend into the shadows and become invisible to the untrained eye."
desc = "Blend into the shadows and become invisible to the untrained eye. Movement is slowed in brightly lit areas."
button_icon_state = "power_cloak"
bloodcost = 5
cooldown = 50
bloodsucker_can_buy = TRUE
amToggle = TRUE
warn_constant_cost = TRUE
var/was_running
var/light_min = 0.2 // If lum is above this, no good.
var/moveintent_was_run
var/runintent
var/walk_threshold = 0.4 // arbitrary number, to be changed. edit in last commit: this is fine after testing on box station for a bit
var/lum
/datum/action/bloodsucker/cloak/CheckCanUse(display_error)
. = ..()
if(!.)
return
// Must be Dark
var/turf/T = owner.loc
if(istype(T) && T.get_lumcount() > light_min)
to_chat(owner, "<span class='warning'>This area is not dark enough to blend in</span>")
return FALSE
// must have nobody around to see the cloak
var/watchers = viewers(9,get_turf(owner))
for(var/mob/living/M in watchers)
if(M != owner)
to_chat(owner, "<span class='warning'>You may only vanish into the shadows unseen.</span>")
return FALSE
return TRUE
/datum/action/bloodsucker/cloak/ActivatePower()
var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
var/mob/living/user = owner
was_running = (user.m_intent == MOVE_INTENT_RUN)
if(was_running)
user.toggle_move_intent()
ADD_TRAIT(user, TRAIT_NORUNNING, "cloak of darkness")
moveintent_was_run = (user.m_intent == MOVE_INTENT_RUN)
while(bloodsuckerdatum && ContinueActive(user))
// Pay Blood Toll (if awake)
owner.alpha = max(20, owner.alpha - min(75, 10 + 5 * level_current))
owner.alpha = max(35, owner.alpha - min(75, 10 + 5 * level_current))
bloodsuckerdatum.AddBloodVolume(-0.2)
sleep(5) // Check every few ticks that we haven't disabled this power
runintent = (user.m_intent == MOVE_INTENT_RUN)
var/turf/T = get_turf(user)
lum = T.get_lumcount()
if(istype(owner.loc))
if(lum > walk_threshold)
if(runintent)
user.toggle_move_intent()
ADD_TRAIT(user, TRAIT_NORUNNING, "cloak of darkness")
if(lum < walk_threshold)
if(!runintent)
user.toggle_move_intent()
REMOVE_TRAIT(user, TRAIT_NORUNNING, "cloak of darkness")
sleep(5) // Check every few ticks
/datum/action/bloodsucker/cloak/ContinueActive(mob/living/user, mob/living/target)
if (!..())
@@ -43,15 +62,14 @@
if(user.stat == !CONSCIOUS) // Must be CONSCIOUS
to_chat(owner, "<span class='warning'>Your cloak failed due to you falling unconcious! </span>")
return FALSE
var/turf/T = owner.loc // Must be DARK
if(istype(T) && T.get_lumcount() > light_min)
to_chat(owner, "<span class='warning'>Your cloak failed due to there being too much light!</span>")
return FALSE
return TRUE
/datum/action/bloodsucker/cloak/DeactivatePower(mob/living/user = owner, mob/living/target)
..()
REMOVE_TRAIT(user, TRAIT_NORUNNING, "cloak of darkness")
user.alpha = 255
if(was_running && user.m_intent != MOVE_INTENT_RUN)
runintent = (user.m_intent == MOVE_INTENT_RUN)
if(!runintent && moveintent_was_run)
user.toggle_move_intent()
@@ -170,7 +170,7 @@
// Warn Feeder about Witnesses...
var/was_unnoticed = TRUE
for(var/mob/living/M in viewers(notice_range, owner))
if(M != owner && M != target && iscarbon(M) && M.mind && !M.has_unlimited_silicon_privilege && !M.eye_blind && !M.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER))
if(M != owner && M != target && iscarbon(M) && M.mind && !M.silicon_privileges && !M.eye_blind && !M.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER))
was_unnoticed = FALSE
break
if(was_unnoticed)
@@ -306,7 +306,7 @@
// Bloodsuckers not affected by "the Kiss" of another vampire
if(!target.mind || !target.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER))
target.Unconscious(50,0)
target.Knockdown(40 + 5 * level_current,1)
target.DefaultCombatKnockdown(40 + 5 * level_current,1)
// NOTE: THis is based on level of power!
if(ishuman(target))
target.adjustStaminaLoss(5, forced = TRUE)// Base Stamina Damage
@@ -321,4 +321,4 @@
// My mouth is no longer full
REMOVE_TRAIT(owner, TRAIT_MUTE, "bloodsucker_feed")
// Let me move immediately
user.update_canmove()
user.update_mobility()
@@ -28,9 +28,9 @@
to_chat(owner, "<span class='warning'>Your coffin has been destroyed!</span>")
return FALSE
return TRUE
/datum/action/bloodsucker/gohome/proc/flicker_lights(var/flicker_range, var/beat_volume)
for(var/obj/machinery/light/L in view(flicker_range, get_turf(owner)))
for(var/obj/machinery/light/L in view(flicker_range, get_turf(owner)))
playsound(get_turf(owner), 'sound/effects/singlebeat.ogg', beat_volume, 1)
@@ -45,7 +45,7 @@
flicker_lights(4, 40)
sleep(50)
flicker_lights(4, 60)
for(var/obj/machinery/light/L in view(6, get_turf(owner)))
for(var/obj/machinery/light/L in view(6, get_turf(owner)))
L.flicker(5)
playsound(get_turf(owner), 'sound/effects/singlebeat.ogg', 60, 1)
// ( STEP TWO: Lights OFF? )
@@ -56,7 +56,7 @@
if(!owner)
return
// SEEN?: (effects ONLY if there are witnesses! Otherwise you just POOF)
var/am_seen = FALSE // Do Effects (seen by anyone)
var/drop_item = FALSE // Drop Stuff (seen by non-vamp)
if(isturf(owner.loc)) // Only check if I'm not in a Locker or something.
@@ -65,7 +65,7 @@
if(T && T.lighting_object && T.get_lumcount()>= 0.1)
// B) Check for Viewers
for(var/mob/living/M in viewers(get_turf(owner)))
if(M != owner && isliving(M) && M.mind && !M.has_unlimited_silicon_privilege && !M.eye_blind) // M.client <--- add this in after testing!
if(M != owner && isliving(M) && M.mind && !M.silicon_privileges && !M.eye_blind) // M.client <--- add this in after testing!
am_seen = TRUE
if (!M.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER))
drop_item = TRUE
@@ -95,13 +95,13 @@
puff.effect_type = /obj/effect/particle_effect/smoke/vampsmoke
puff.set_up(3, 0, get_turf(owner))
puff.start()
//STEP FIVE: Create animal at prev location
var/mob/living/simple_animal/SA = pick(/mob/living/simple_animal/mouse,/mob/living/simple_animal/mouse,/mob/living/simple_animal/mouse, /mob/living/simple_animal/hostile/retaliate/bat) //prob(300) /mob/living/simple_animal/mouse,
new SA (owner.loc)
// TELEPORT: Move to Coffin & Close it!
do_teleport(owner, bloodsuckerdatum.coffin, no_effects = TRUE, forced = TRUE, channel = TELEPORT_CHANNEL_QUANTUM)
user.resting = TRUE
user.set_resting(TRUE, TRUE, FALSE)
do_teleport(owner, bloodsuckerdatum.coffin, no_effects = TRUE, forced = TRUE, channel = TELEPORT_CHANNEL_QUANTUM)
user.Stun(30,1)
// CLOSE LID: If fail, force me in.
if(!bloodsuckerdatum.coffin.close(owner))
@@ -112,4 +112,4 @@
bloodsuckerdatum.coffin.update_icon()
// Lock Coffin
bloodsuckerdatum.coffin.LockMe(owner)
@@ -14,6 +14,9 @@
message_Trigger = ""//"Whom will you subvert to your will?"
bloodsucker_can_buy = TRUE
must_be_capacitated = TRUE
var/list/hit //current hit, set while power is in use as we can't pass the list as an extra calling argument in registersignal.
/// If set, uses this speed in deciseconds instead of world.tick_lag
var/speed_override
/datum/action/bloodsucker/targeted/haste/CheckCanUse(display_error)
. = ..()
@@ -43,43 +46,47 @@
return TRUE
/datum/action/bloodsucker/targeted/haste/FireTargetedPower(atom/A)
// set waitfor = FALSE <---- DONT DO THIS!We WANT this power to hold up ClickWithPower(), so that we can unlock the power when it's done.
// This is a non-async proc to make sure the power is "locked" until this finishes.
hit = list()
RegisterSignal(owner, COMSIG_MOVABLE_MOVED, .proc/on_move)
var/mob/living/user = owner
var/turf/T = isturf(A) ? A : get_turf(A)
// Pulled? Not anymore.
owner.pulledby = null
// Step One: Heatseek toward Target's Turf
walk_to(owner, T, 0, 0.01, 20) // NOTE: this runs in the background! to cancel it, you need to use walk(owner.current,0), or give them a new path.
user.pulledby?.stop_pulling()
// Go to target turf
// DO NOT USE WALK TO.
playsound(get_turf(owner), 'sound/weapons/punchmiss.ogg', 25, 1, -1)
var/safety = 20
while(get_turf(owner) != T && safety > 0 && !(isliving(target) && target.Adjacent(owner)))
user.canmove = FALSE //Dont move while doing the thing, or itll break
safety --
// Did I get knocked down?
if(owner && owner.incapacitated(ignore_restraints=TRUE, ignore_grab=TRUE))// owner.incapacitated())
// We're gonna cancel. But am I on the ground? Spin me!
if(user.resting)
var/send_dir = get_dir(owner, T)
new /datum/forced_movement(owner, get_ranged_target_turf(owner, send_dir, 1), 1, FALSE)
owner.spin(10)
var/safety = get_dist(user, T) * 3 + 1
var/consequetive_failures = 0
var/speed = isnull(speed_override)? world.tick_lag : speed_override
while(--safety && (get_turf(user) != T))
var/success = step_towards(user, T) //This does not try to go around obstacles.
if(!success)
success = step_to(user, T) //this does
if(!success)
if(++consequetive_failures >= 3) //if 3 steps don't work
break //just stop
else
consequetive_failures = 0
if(user.resting)
user.setDir(turn(user.dir, 90)) //down? spin2win :^)
if(user.incapacitated(ignore_restraints = TRUE, ignore_grab = TRUE)) //actually down? stop.
break
// Spin/Stun people we pass.
//var/mob/living/newtarget = locate(/mob/living) in oview(1, owner)
var/list/mob/living/foundtargets = list()
for(var/mob/living/newtarget in oview(1, owner))
if (newtarget && newtarget != target && !(newtarget in foundtargets))//!newtarget.IsKnockdown())
if (rand(0, 5) < level_current)
playsound(get_turf(newtarget), "sound/weapons/punch[rand(1,4)].ogg", 15, 1, -1)
newtarget.Knockdown(10 + level_current * 5)
if(newtarget.IsStun())
newtarget.spin(10,1)
if (rand(0,4))
newtarget.drop_all_held_items()
foundtargets += newtarget
sleep(1)
if(user)
user.update_canmove() //Let the poor guy move again
if(success) //don't sleep if we failed to move.
sleep(speed)
UnregisterSignal(owner, COMSIG_MOVABLE_MOVED)
hit = null
user.update_mobility()
/datum/action/bloodsucker/targeted/haste/DeactivatePower(mob/living/user = owner, mob/living/target)
..() // activate = FALSE
user.update_canmove()
user.update_mobility()
/datum/action/bloodsucker/targeted/haste/proc/on_move()
for(var/mob/living/L in dview(1, get_turf(owner)))
if(!hit[L] && (L != owner))
hit[L] = TRUE
playsound(L, "sound/weapons/punch[rand(1,4)].ogg", 15, 1, -1)
L.DefaultCombatKnockdown(10 + level_current * 5)
L.Paralyze(0.1)
L.spin(10, 1)
@@ -9,7 +9,7 @@
cooldown = 120
target_range = 3
power_activates_immediately = TRUE
message_Trigger = ""//"Whom will you subvert to your will?"
message_Trigger = "Whom will you ensnare within your grasp?"
must_be_capacitated = TRUE
bloodsucker_can_buy = TRUE
@@ -52,32 +52,37 @@
// set waitfor = FALSE <---- DONT DO THIS!We WANT this power to hold up ClickWithPower(), so that we can unlock the power when it's done.
var/mob/living/carbon/target = A
var/turf/T = get_turf(target)
var/mob/living/L = owner
// Clear Vars
owner.pulling = null
// Will we Knock them Down?
var/do_knockdown = !is_A_facing_B(target,owner) || owner.alpha <= 0 || istype(owner.loc, /obj/structure/closet)
// CAUSES: Target has their back to me, I'm invisible, or I'm in a Closet
// Step One: Heatseek toward Target's Turf
walk_towards(owner, T, 0.1, 10) // NOTE: this runs in the background! to cancel it, you need to use walk(owner.current,0), or give them a new path.
addtimer(CALLBACK(owner, .proc/_walk, 0), 2 SECONDS)
if(get_turf(owner) != T && !(isliving(target) && target.Adjacent(owner)) && owner.incapacitated() && owner.resting)
var/send_dir = get_dir(owner, T)
new /datum/forced_movement(owner, get_ranged_target_turf(owner, send_dir, 1), 1, FALSE)
owner.spin(10)
// Step Two: Check if I'm at/adjectent to Target's CURRENT turf (not original...that was just a destination)
sleep(1)
if(target.Adjacent(owner))
// LEVEL 2: If behind target, mute or unconscious!
if(do_knockdown) // && level_current >= 1)
target.Knockdown(15 + 10 * level_current,1)
target.adjustStaminaLoss(40 + 10 * level_current)
// Cancel Walk (we were close enough to contact them)
walk(owner, 0)
target.Stun(10,1) //Without this the victim can just walk away
target.grabbedby(owner) // Taken from mutations.dm under changelings
target.grippedby(owner, instant = TRUE) //instant aggro grab
addtimer(CALLBACK(GLOBAL_PROC, .proc/_walk, owner, 0), 2 SECONDS)
target.playsound_local(get_turf(owner), 'sound/bloodsucker/lunge_warn.ogg', 60, FALSE, pressure_affected = FALSE) // target-only telegraphing
owner.playsound_local(owner, 'sound/bloodsucker/lunge_warn.ogg', 60, FALSE, pressure_affected = FALSE) // audio feedback to the user
if(do_mob(owner, owner, 7, TRUE, TRUE))
walk_towards(owner, T, 0.1, 10) // yes i know i shouldn't use this but i don't know how to work in anything better
if(get_turf(owner) != T && !(isliving(target) && target.Adjacent(owner)) && owner.incapacitated() && !CHECK_MOBILITY(L, MOBILITY_STAND))
var/send_dir = get_dir(owner, T)
new /datum/forced_movement(owner, get_ranged_target_turf(owner, send_dir, 1), 1, FALSE)
owner.spin(10)
// Step Two: Check if I'm at/adjectent to Target's CURRENT turf (not original...that was just a destination)
for(var/i in 1 to 6)
if (target.Adjacent(owner))
// LEVEL 2: If behind target, mute or unconscious!
if(do_knockdown) // && level_current >= 1)
target.Knockdown(15 + 10 * level_current,1)
target.adjustStaminaLoss(40 + 10 * level_current)
// Cancel Walk (we were close enough to contact them)
walk(owner, 0)
target.Stun(10,1) //Without this the victim can just walk away
target.grabbedby(owner) // Taken from mutations.dm under changelings
target.grippedby(owner, instant = TRUE) //instant aggro grab
break
sleep(3)
/datum/action/bloodsucker/targeted/lunge/DeactivatePower(mob/living/user = owner, mob/living/target)
..() // activate = FALSE
user.update_canmove()
user.update_mobility()
@@ -54,8 +54,8 @@
REMOVE_TRAIT(user, TRAIT_VIRUSIMMUNE, "bloodsucker")
var/obj/item/organ/heart/vampheart/H = user.getorganslot(ORGAN_SLOT_HEART)
var/obj/item/organ/eyes/vassal/bloodsucker/E = user.getorganslot(ORGAN_SLOT_EYES)
E.flash_protect = 0
E.flash_protect = 0
// WE ARE ALIVE! //
bloodsuckerdatum.poweron_masquerade = TRUE
while(bloodsuckerdatum && ContinueActive(user))
@@ -11,24 +11,25 @@
button_icon_state = "power_mez"
bloodcost = 30
cooldown = 300
target_range = 1
power_activates_immediately = FALSE
target_range = 2
power_activates_immediately = TRUE
message_Trigger = "Whom will you subvert to your will?"
must_be_capacitated = TRUE
bloodsucker_can_buy = TRUE
var/success
/datum/action/bloodsucker/targeted/mesmerize/CheckCanUse(display_error)
. = ..()
if(!.)
return
if (!owner.getorganslot(ORGAN_SLOT_EYES))
if(!owner.getorganslot(ORGAN_SLOT_EYES))
if (display_error)
to_chat(owner, "<span class='warning'>You have no eyes with which to mesmerize.</span>")
return FALSE
// Check: Eyes covered?
var/mob/living/L = owner
if (istype(L) && L.is_eyes_covered() || !isturf(owner.loc))
if (display_error)
if(istype(L) && L.is_eyes_covered() || !isturf(owner.loc))
if(display_error)
to_chat(owner, "<span class='warning'>Your eyes are concealed from sight.</span>")
return FALSE
return TRUE
@@ -38,74 +39,99 @@
/datum/action/bloodsucker/targeted/mesmerize/CheckCanTarget(atom/A,display_error)
// Check: Self
if (A == owner)
if(A == owner)
return FALSE
var/mob/living/carbon/target = A // We already know it's carbon due to CheckValidTarget()
// Bloodsucker
if (target.mind && target.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER))
if(target.mind && target.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER))
if (display_error)
to_chat(owner, "<span class='warning'>Bloodsuckers are immune to [src].</span>")
return FALSE
// Dead/Unconscious
if (target.stat > CONSCIOUS)
if(target.stat > CONSCIOUS)
if (display_error)
to_chat(owner, "<span class='warning'>Your victim is not [(target.stat == DEAD || HAS_TRAIT(target, TRAIT_FAKEDEATH))?"alive":"conscious"].</span>")
return FALSE
// Check: Target has eyes?
if (!target.getorganslot(ORGAN_SLOT_EYES))
if(!target.getorganslot(ORGAN_SLOT_EYES))
if (display_error)
to_chat(owner, "<span class='warning'>They have no eyes!</span>")
return FALSE
// Check: Target blind?
if (target.eye_blind > 0)
if(target.eye_blind > 0)
if (display_error)
to_chat(owner, "<span class='warning'>Your victim's eyes are glazed over. They cannot perceive you.</span>")
return FALSE
// Check: Target See Me? (behind wall)
if (!(owner in view(target_range, get_turf(target))))
if(!(target in view(target_range, get_turf(owner))))
// Sub-Check: GET CLOSER
//if (!(owner in range(target_range, get_turf(target)))
// if (display_error)
// to_chat(owner, "<span class='warning'>You're too far from your victim.</span>")
if (display_error)
if(display_error)
to_chat(owner, "<span class='warning'>You're too far outside your victim's view.</span>")
return FALSE
if(target.has_status_effect(STATUS_EFFECT_MESMERIZE)) // ignores facing once the windup has started
return TRUE
// Check: Facing target?
if (!is_A_facing_B(owner,target)) // in unsorted.dm
if(!is_A_facing_B(owner,target)) // in unsorted.dm
if (display_error)
to_chat(owner, "<span class='warning'>You must be facing your victim.</span>")
return FALSE
// Check: Target facing me?
if (!target.resting && !is_A_facing_B(target,owner))
if (display_error)
if (CHECK_MOBILITY(target, MOBILITY_STAND) && !is_A_facing_B(target,owner))
if(display_error)
to_chat(owner, "<span class='warning'>Your victim must be facing you to see into your eyes.</span>")
return FALSE
return TRUE
/datum/action/bloodsucker/targeted/mesmerize/proc/ContinueTarget(atom/A)
var/mob/living/carbon/target = A
var/mob/living/user = owner
var/cancontinue=CheckCanTarget(target)
if(!cancontinue)
success = FALSE
target.remove_status_effect(STATUS_EFFECT_MESMERIZE)
user.remove_status_effect(STATUS_EFFECT_MESMERIZE)
DeactivatePower()
DeactivateRangedAbility()
StartCooldown()
to_chat(user, "<span class='warning'>[target] has escaped your gaze!</span>")
UnregisterSignal(target, COMSIG_MOVABLE_MOVED)
/datum/action/bloodsucker/targeted/mesmerize/FireTargetedPower(atom/A)
// set waitfor = FALSE <---- DONT DO THIS!We WANT this power to hold up ClickWithPower(), so that we can unlock the power when it's done.
var/mob/living/carbon/target = A
var/mob/living/user = owner
if(istype(target))
target.Stun(40) //Utterly useless without this, its okay since there are so many checks to go through
target.apply_status_effect(STATUS_EFFECT_MESMERIZE, 45) //So you cant rotate with combat mode, plus fancy status alert
if(do_mob(user, target, 40, 0, TRUE, extra_checks = CALLBACK(src, .proc/ContinueActive, user, target)))
PowerActivatedSuccessfully() // PAY COST! BEGIN COOLDOWN!
var/power_time = 90 + level_current * 12
target.apply_status_effect(STATUS_EFFECT_MESMERIZE, power_time + 80)
to_chat(user, "<span class='notice'>[target] is fixed in place by your hypnotic gaze.</span>")
target.Stun(power_time)
target.next_move = world.time + power_time // <--- Use direct change instead. We want an unmodified delay to their next move // target.changeNext_move(power_time) // check click.dm
target.notransform = TRUE // <--- Fuck it. We tried using next_move, but they could STILL resist. We're just doing a hard freeze.
spawn(power_time)
if(istype(target))
target.notransform = FALSE
// They Woke Up! (Notice if within view)
if(istype(user) && target.stat == CONSCIOUS && (target in view(10, get_turf(user))) )
to_chat(user, "<span class='warning'>[target] has snapped out of their trance.</span>")
success = TRUE
var/power_time = 138 + level_current * 12
target.apply_status_effect(STATUS_EFFECT_MESMERIZE, 30)
user.apply_status_effect(STATUS_EFFECT_MESMERIZE, 30)
RegisterSignal(target, COMSIG_MOVABLE_MOVED, .proc/ContinueTarget)
// 3 second windup
sleep(30)
if(success)
PowerActivatedSuccessfully() // blood & cooldown only altered if power activated successfully - less "fuck you"-y
target.face_atom(user)
target.apply_status_effect(STATUS_EFFECT_MESMERIZE, power_time) // pretty much purely cosmetic
target.Stun(power_time)
to_chat(user, "<span class='notice'>[target] is fixed in place by your hypnotic gaze.</span>")
target.next_move = world.time + power_time // <--- Use direct change instead. We want an unmodified delay to their next move // target.changeNext_move(power_time) // check click.dm
target.notransform = TRUE // <--- Fuck it. We tried using next_move, but they could STILL resist. We're just doing a hard freeze.
spawn(power_time)
if(istype(target) && success)
target.notransform = FALSE
// They Woke Up! (Notice if within view)
if(istype(user) && target.stat == CONSCIOUS && (target in view(10, get_turf(user))) )
to_chat(user, "<span class='warning'>[target] has snapped out of their trance.</span>")
/datum/action/bloodsucker/targeted/mesmerize/ContinueActive(mob/living/user, mob/living/target)
return ..() && CheckCanUse() && CheckCanTarget(target)
@@ -92,7 +92,7 @@
H.socks = random_socks(H.gender)
//H.eye_color = random_eye_color()
REMOVE_TRAIT(H, TRAIT_DISFIGURED, null) //
H.dna.features = random_features()
H.dna.features = random_features(H.dna.species?.id)
// Apply Appearance
H.update_body() // Outfit and underware, also body.
@@ -5,7 +5,6 @@
var/special_role = ROLE_BROTHER
var/datum/team/brother_team/team
antag_moodlet = /datum/mood_event/focused
can_hijack = HIJACK_HIJACKER
/datum/antagonist/brother/create_team(datum/team/brother_team/new_team)
if(!new_team)
@@ -81,7 +81,7 @@
if(istype(our_target, /datum/cellular_emporium))
cellular_emporium = our_target
else
throw EXCEPTION("cellular_emporium action created with non emporium")
CRASH("cellular_emporium action created with non emporium")
/datum/action/innate/cellular_emporium/Activate()
cellular_emporium.ui_interact(owner)
@@ -13,17 +13,14 @@
var/mob/living/carbon/human/H = user //SHOULD always be human, because req_human = 1
if(!istype(H)) // req_human could be done in can_sting stuff.
return
var/datum/mutation/human/HM = GLOB.mutations_list[CHAMELEON]
if(HM in H.dna.mutations)
HM.force_lose(H)
if(H.dna.get_mutation(CHAMELEON))
H.dna.remove_mutation(CHAMELEON)
else
HM.force_give(H)
H.dna.add_mutation(CHAMELEON)
return TRUE
/obj/effect/proc_holder/changeling/chameleon_skin/on_refund(mob/user)
action.Remove(user)
if(user.has_dna())
var/mob/living/carbon/C = user
var/datum/mutation/human/HM = GLOB.mutations_list[CHAMELEON]
if(HM in C.dna.mutations)
HM.force_lose(C)
C.dna.remove_mutation(CHAMELEON)
@@ -18,7 +18,6 @@
user.tod = STATION_TIME_TIMESTAMP("hh:mm:ss", world.time)
user.fakedeath("changeling") //play dead
user.update_stat()
user.update_canmove()
addtimer(CALLBACK(src, .proc/ready_to_regenerate, user), LING_FAKEDEATH_TIME, TIMER_UNIQUE)
return TRUE
@@ -30,7 +30,7 @@
H.confused += 3
for(var/mob/living/silicon/S in range(2,user))
to_chat(S, "<span class='userdanger'>Your sensors are disabled by a shower of blood!</span>")
S.Knockdown(60)
S.DefaultCombatKnockdown(60)
var/turf = get_turf(user)
user.gib()
. = TRUE
@@ -24,7 +24,7 @@
if(issilicon(M))
SEND_SOUND(M, sound('sound/weapons/flash.ogg'))
M.Knockdown(rand(100,200))
M.DefaultCombatKnockdown(rand(100,200))
for(var/obj/machinery/light/L in range(4, user))
L.on = 1
@@ -26,7 +26,7 @@
changeling.chem_recharge_slowdown -= 0.5
if(stacks >= 20)
to_chat(user, "<span class='danger'>We collapse in exhaustion.</span>")
user.Knockdown(60)
user.DefaultCombatKnockdown(60)
user.emote("gasp")
INVOKE_ASYNC(src, .proc/muscle_loop, user)
@@ -40,7 +40,7 @@
if(user.stat != CONSCIOUS || user.staminaloss >= 90)
active = !active
to_chat(user, "<span class='notice'>Our muscles relax without the energy to strengthen them.</span>")
user.Knockdown(40)
user.DefaultCombatKnockdown(40)
user.remove_movespeed_modifier(MOVESPEED_ID_CHANGELING_MUSCLES)
changeling.chem_recharge_slowdown -= 0.5
break
@@ -80,7 +80,7 @@
if(iscultist(L)) //No longer stuns cultists, instead sets them on fire and burns them
to_chat(L, "<span class='heavy_brass'>\"Watch your step, wretch.\"</span>")
L.adjustFireLoss(10)
L.Knockdown(20, FALSE)
L.DefaultCombatKnockdown(20, FALSE)
L.adjust_fire_stacks(5) //Burn!
L.IgniteMob()
else
@@ -155,7 +155,7 @@
if(brutedamage || burndamage)
L.adjustBruteLoss(-(brutedamage * 0.25))
L.adjustFireLoss(-(burndamage * 0.25))
L.Knockdown(50) //Completely defenseless for five seconds - mainly to give them time to read over the information they've just been presented with
L.DefaultCombatKnockdown(50) //Completely defenseless for five seconds - mainly to give them time to read over the information they've just been presented with
if(iscarbon(L))
var/mob/living/carbon/C = L
C.silent += 5
@@ -251,6 +251,7 @@
return TRUE
/obj/effect/clockwork/sigil/transmission/update_icon()
. = ..()
var/power_charge = get_clockwork_power()
if(GLOB.ratvar_awakens)
alpha = 255
@@ -196,7 +196,7 @@
if(!iscultist(L))
L.visible_message("<span class='warning'>[L]'s eyes blaze with brilliant light!</span>", \
"<span class='userdanger'>Your vision suddenly screams with white-hot light!</span>")
L.Knockdown(15, TRUE, FALSE, 15)
L.DefaultCombatKnockdown(15, TRUE, FALSE, 15)
L.apply_status_effect(STATUS_EFFECT_KINDLE)
L.flash_act(1, 1)
if(issilicon(target))
@@ -57,15 +57,15 @@
else if(!..())
if(!L.anti_magic_check())
if(issilicon(L))
L.Knockdown(100)
L.DefaultCombatKnockdown(100)
else if(iscultist(L))
L.confused += CLAMP(10 - L.confused, 0, 5) // Spearthrow now confuses enemy cultists + just deals extra damage / sets on fire instead of hardstunning + damage
to_chat(L, "<span class ='userdanger'>[src] crashes into you with burning force, sending you reeling!</span>")
L.adjust_fire_stacks(2)
L.Knockdown(1)
L.DefaultCombatKnockdown(1)
L.IgniteMob()
else
L.Knockdown(40)
L.DefaultCombatKnockdown(40)
GLOB.clockwork_vitality += L.adjustFireLoss(bonus_burn * 3) //normally a total of 40 damage, 70 with ratvar
break_spear(T)
else
@@ -176,7 +176,7 @@
var/datum/status_effect/belligerent/B = C.apply_status_effect(STATUS_EFFECT_BELLIGERENT)
if(!QDELETED(B))
B.duration = world.time + 30
C.Knockdown(5) //knocks down for half a second if affected
C.DefaultCombatKnockdown(5) //knocks down for half a second if affected
sleep(!GLOB.ratvar_approaches ? 16 : 10)
name = "judicial blast"
layer = ABOVE_ALL_MOB_LAYER
@@ -196,7 +196,7 @@
L.visible_message("<span class='warning'>Strange energy flows into [L]'s [I.name]!</span>", \
"<span class='userdanger'>Your [I.name] shields you from [src]!</span>")
continue
L.Knockdown(15) //knocks down briefly when exploding
L.DefaultCombatKnockdown(15) //knocks down briefly when exploding
if(!iscultist(L))
L.visible_message("<span class='warning'>[L] is struck by a judicial explosion!</span>", \
"<span class='userdanger'>[!issilicon(L) ? "An unseen force slams you into the ground!" : "ERROR: Motor servos disabled by external source!"]</span>")
@@ -68,8 +68,8 @@
/obj/item/clockwork/replica_fabricator/pre_attack(atom/target, mob/living/user, params)
if(!target || !user || !is_servant_of_ratvar(user) || istype(target, /obj/item/storage))
return TRUE
return fabricate(target, user)
return ..()
return !fabricate(target, user)
//A note here; return values are for if we CAN BE PUT ON A TABLE, not IF WE ARE SUCCESSFUL, unless no_table_check is TRUE
/obj/item/clockwork/replica_fabricator/proc/fabricate(atom/target, mob/living/user, silent, no_table_check)
@@ -37,7 +37,7 @@
/mob/living/simple_animal/hostile/clockwork/ratvar_act()
fully_heal(TRUE)
/mob/living/simple_animal/hostile/clockwork/electrocute_act(shock_damage, obj/source, siemens_coeff = 1, safety = 0, tesla_shock = 0, illusion = 0, stun = TRUE)
/mob/living/simple_animal/hostile/clockwork/electrocute_act(shock_damage, source, siemens_coeff = 1, flags = NONE)
return 0 //ouch, my metal-unlikely-to-be-damaged-by-electricity-body
/mob/living/simple_animal/hostile/clockwork/examine(mob/user)
@@ -8,7 +8,7 @@
name = "clockwork marauder"
desc = "The stalwart apparition of a soldier, blazing with crimson flames. It's armed with a gladius and shield."
icon_state = "clockwork_marauder"
mob_biotypes = list(MOB_INORGANIC, MOB_HUMANOID)
mob_biotypes = MOB_HUMANOID
health = 120
maxHealth = 120
force_threshold = 8
@@ -118,7 +118,7 @@
continue
if(ishostile(L))
var/mob/living/simple_animal/hostile/H = L
if(("ratvar" in H.faction) || (!H.mind && "neutral" in H.faction))
if(("ratvar" in H.faction) || (!H.mind && ("neutral" in H.faction)))
continue
if(ismegafauna(H) || (!H.mind && H.AIStatus == AI_OFF))
continue
@@ -57,5 +57,5 @@
L.confused = min(L.confused + 15, 50)
L.dizziness = min(L.dizziness + 15, 50)
if(L.confused >= 25)
L.Knockdown(FLOOR(L.confused * 0.8, 1))
L.DefaultCombatKnockdown(FLOOR(L.confused * 0.8, 1))
take_damage(max_integrity)
@@ -22,7 +22,7 @@
if(buckled_mobs && LAZYLEN(buckled_mobs))
var/mob/living/L = buckled_mobs[1]
if(iscarbon(L))
L.Knockdown(100)
L.DefaultCombatKnockdown(100)
L.visible_message("<span class='warning'>[L] is maimed as the skewer shatters while still in [L.p_their()] body!</span>")
L.adjustBruteLoss(15)
unbuckle_mob(L)
@@ -117,6 +117,6 @@
return
skewee.visible_message("<span class='danger'>[skewee] comes free of [src] with a squelching pop!</span>", \
"<span class='boldannounce'>You come free of [src]!</span>")
skewee.Knockdown(30)
skewee.DefaultCombatKnockdown(30)
playsound(skewee, 'sound/misc/desceration-03.ogg', 50, TRUE)
unbuckle_mob(skewee)
@@ -120,7 +120,7 @@
hierophant_network.Grant(current)
current.throw_alert("clockinfo", /obj/screen/alert/clockwork/infodump)
var/obj/structure/destructible/clockwork/massive/celestial_gateway/G = GLOB.ark_of_the_clockwork_justiciar
if(G.active && ishuman(current))
if(G && G.active && ishuman(current))
current.add_overlay(mutable_appearance('icons/effects/genetics.dmi', "servitude", -MUTATIONS_LAYER))
/datum/antagonist/clockcult/remove_innate_effects(mob/living/mob_override)
@@ -174,9 +174,12 @@
log_admin("[key_name(admin)] has made [new_owner.current] into a servant of Ratvar.")
/datum/antagonist/clockcult/admin_remove(mob/user)
remove_servant_of_ratvar(owner.current, TRUE)
message_admins("[key_name_admin(user)] has removed clockwork servant status from [owner.current].")
log_admin("[key_name(user)] has removed clockwork servant status from [owner.current].")
var/mob/target = owner.current
if(!target)
return
remove_servant_of_ratvar(target, TRUE)
message_admins("[key_name_admin(user)] has removed clockwork servant status from [target].")
log_admin("[key_name(user)] has removed clockwork servant status from [target].")
/datum/antagonist/clockcult/get_admin_commands()
. = ..()
+12 -5
View File
@@ -143,7 +143,7 @@
//Cult Blood Spells
/datum/action/innate/cult/blood_spell/stun
name = "Stun"
desc = "A potent spell that will stun and mute victims upon contact."
desc = "A potent spell that will stun and mute victims upon contact. When the cult ascends, so does the spell, it burns and throws back the victim!"
button_icon_state = "hand"
magic_path = "/obj/item/melee/blood_magic/stun"
health_cost = 10
@@ -343,7 +343,7 @@
icon = 'icons/obj/items_and_weapons.dmi'
icon_state = "disintegrate"
item_state = null
item_flags = NEEDS_PERMIT | ABSTRACT | DROPDEL
item_flags = NEEDS_PERMIT | ABSTRACT | DROPDEL | NO_ATTACK_CHAIN_SOFT_STAMCRIT
w_class = WEIGHT_CLASS_HUGE
throwforce = 0
@@ -437,9 +437,16 @@
else
target.visible_message("<span class='warning'>[L] starts to glow in a halo of light!</span>", \
"<span class='userdanger'>A feeling of warmth washes over you, rays of holy light surround your body and protect you from the flash of light!</span>")
else
if(!iscultist(L))
L.Knockdown(160)
else // cult doesn't stun any longer when halos are out, instead it does burn damage + knockback!
var/datum/antagonist/cult/user_antag = user.mind.has_antag_datum(/datum/antagonist/cult,TRUE)
if(user_antag.cult_team.cult_ascendent)
if(!iscultist(L))
L.adjustFireLoss(20)
if(L.move_resist < MOVE_FORCE_STRONG)
var/atom/throw_target = get_edge_target_turf(L, user.dir)
L.throw_at(throw_target, 7, 1, user)
else if(!iscultist(L))
L.DefaultCombatKnockdown(160)
L.adjustStaminaLoss(140) //Ensures hard stamcrit
L.flash_act(1,1)
if(issilicon(target))
+17 -16
View File
@@ -46,10 +46,11 @@
/obj/item/melee/cultblade/Initialize()
. = ..()
AddComponent(/datum/component/butchering, 40, 100)
AddElement(/datum/element/sword_point)
/obj/item/melee/cultblade/attack(mob/living/target, mob/living/carbon/human/user)
if(!iscultist(user))
user.Knockdown(100)
user.DefaultCombatKnockdown(100)
user.dropItemToGround(src, TRUE)
user.visible_message("<span class='warning'>A powerful force shoves [user] away from [target]!</span>", \
"<span class='cultlarge'>\"You shouldn't play with sharp things. You'll poke someone's eye out.\"</span>")
@@ -148,7 +149,7 @@
user.emote("scream")
user.apply_damage(30, BRUTE, pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM))
user.dropItemToGround(src, TRUE)
user.Knockdown(50)
user.DefaultCombatKnockdown(50)
return
force = initial(force)
jaunt.Grant(user, src)
@@ -263,7 +264,7 @@
/obj/item/restraints/legcuffs/bola/cult/pickup(mob/living/user)
if(!iscultist(user))
to_chat(user, "<span class='warning'>The bola seems to take on a life of its own!</span>")
throw_impact(user)
ensnare(user)
/obj/item/restraints/legcuffs/bola/cult/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
if(iscultist(hit_atom))
@@ -371,7 +372,7 @@
max = 40
prefix = "darkened"
/obj/item/sharpener/cult/update_icon()
/obj/item/sharpener/cult/update_icon_state()
var/old_state = icon_state
icon_state = "cult_sharpener[used ? "_used" : ""]"
if(old_state != icon_state)
@@ -405,7 +406,7 @@
to_chat(user, "<span class='warning'>An overwhelming sense of nausea overpowers you!</span>")
user.dropItemToGround(src, TRUE)
user.Dizzy(30)
user.Knockdown(100)
user.DefaultCombatKnockdown(100)
else
to_chat(user, "<span class='cultlarge'>\"Trying to use things you don't own is bad, you know.\"</span>")
to_chat(user, "<span class='userdanger'>The armor squeezes at your body!</span>")
@@ -457,7 +458,7 @@
to_chat(user, "<span class='warning'>An overwhelming sense of nausea overpowers you!</span>")
user.dropItemToGround(src, TRUE)
user.Dizzy(30)
user.Knockdown(100)
user.DefaultCombatKnockdown(100)
else
to_chat(user, "<span class='cultlarge'>\"Trying to use things you don't own is bad, you know.\"</span>")
to_chat(user, "<span class='userdanger'>The robes squeeze at your body!</span>")
@@ -478,7 +479,7 @@
to_chat(user, "<span class='cultlarge'>\"You want to be blind, do you?\"</span>")
user.dropItemToGround(src, TRUE)
user.Dizzy(30)
user.Knockdown(100)
user.DefaultCombatKnockdown(100)
user.blind_eyes(30)
/obj/item/reagent_containers/glass/beaker/unholywater
@@ -499,7 +500,7 @@
/obj/item/shuttle_curse/attack_self(mob/living/user)
if(!iscultist(user))
user.dropItemToGround(src, TRUE)
user.Knockdown(100)
user.DefaultCombatKnockdown(100)
to_chat(user, "<span class='warning'>A powerful force shoves you away from [src]!</span>")
return
if(curselimit > 1)
@@ -687,7 +688,7 @@
qdel(spear_act)
..()
/obj/item/twohanded/cult_spear/update_icon()
/obj/item/twohanded/cult_spear/update_icon_state()
icon_state = "bloodspear[wielded]"
/obj/item/twohanded/cult_spear/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
@@ -705,10 +706,10 @@
if(is_servant_of_ratvar(L))
to_chat(L, "<span class='cultlarge'>\"Kneel for me, scum\"</span>")
L.confused += CLAMP(10 - L.confused, 0, 5) //confuses and lightly knockdowns + damages hostile cultists instead of hardstunning like before
L.Knockdown(15)
L.DefaultCombatKnockdown(15)
L.adjustBruteLoss(10)
else
L.Knockdown(50)
L.DefaultCombatKnockdown(50)
break_spear(T)
else
..()
@@ -843,7 +844,7 @@
INVOKE_ASYNC(src, .proc/pewpew, user, params)
var/obj/structure/emergency_shield/invoker/N = new(user.loc)
if(do_after(user, 90, target = user))
user.Knockdown(40)
user.DefaultCombatKnockdown(40)
to_chat(user, "<span class='cult italic'>You have exhausted the power of this spell!</span>")
firing = FALSE
if(N)
@@ -908,7 +909,7 @@
else
var/mob/living/L = target
if(L.density)
L.Knockdown(20)
L.DefaultCombatKnockdown(20)
L.adjustBruteLoss(45)
playsound(L, 'sound/hallucinations/wail.ogg', 50, 1)
L.emote("scream")
@@ -944,7 +945,7 @@
T.visible_message("<span class='warning'>The sheer force from [P] shatters the mirror shield!</span>")
new /obj/effect/temp_visual/cult/sparks(T)
playsound(T, 'sound/effects/glassbr3.ogg', 100)
owner.Knockdown(25)
owner.DefaultCombatKnockdown(25)
qdel(src)
return FALSE
if(P.is_reflectable)
@@ -1001,9 +1002,9 @@
else if(!..())
if(!L.anti_magic_check())
if(is_servant_of_ratvar(L))
L.Knockdown(60)
L.DefaultCombatKnockdown(60)
else
L.Knockdown(30)
L.DefaultCombatKnockdown(30)
if(D.thrower)
for(var/mob/living/Next in orange(2, T))
if(!Next.density || iscultist(Next))
+1 -1
View File
@@ -116,7 +116,7 @@ This file contains the cult dagger and rune list code
if(user.blood_volume)
user.apply_damage(initial(rune_to_scribe.scribe_damage), BRUTE, pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM))
var/scribe_mod = initial(rune_to_scribe.scribe_delay)
if(istype(get_turf(user), /turf/open/floor/engine/cult))
if(istype(get_turf(user), /turf/open/floor/engine/cult) && !(ispath(rune_to_scribe, /obj/effect/rune/narsie)))
scribe_mod *= 0.5
if(!do_after(user, scribe_mod, target = get_turf(user)))
for(var/V in shields)
+3 -3
View File
@@ -219,7 +219,7 @@ structure_check() searches for nearby cultist structures required for the invoca
L.visible_message("<span class='warning'>[L]'s eyes glow a defiant yellow!</span>", \
"<span class='cultlarge'>\"Stop resisting. You <i>will</i> be mi-\"</span>\n\
<span class='large_brass'>\"Give up and you will feel pain unlike anything you've ever felt!\"</span>")
L.Knockdown(80)
L.DefaultCombatKnockdown(80)
else if(is_convertable)
do_convert(L, invokers)
else
@@ -908,7 +908,7 @@ structure_check() searches for nearby cultist structures required for the invoca
if(affecting.key)
affecting.visible_message("<span class='warning'>[affecting] slowly relaxes, the glow around [affecting.p_them()] dimming.</span>", \
"<span class='danger'>You are re-united with your physical form. [src] releases its hold over you.</span>")
affecting.Knockdown(40)
affecting.DefaultCombatKnockdown(40)
break
if(affecting.health <= 10)
to_chat(G, "<span class='cultitalic'>Your body can no longer sustain the connection!</span>")
@@ -970,7 +970,7 @@ structure_check() searches for nearby cultist structures required for the invoca
playsound(T, 'sound/magic/enter_blood.ogg', 100, 1)
visible_message("<span class='warning'>A colossal shockwave of energy bursts from the rune, disintegrating it in the process!</span>")
for(var/mob/living/L in range(src, 3))
L.Knockdown(30)
L.DefaultCombatKnockdown(30)
empulse(T, 0.42*(intensity), 1)
var/list/images = list()
var/zmatch = T.z
+1 -1
View File
@@ -477,7 +477,7 @@ GLOBAL_LIST_INIT(devil_suffix, list(" the Red", " the Soulless", " the Master",
if(SOULVALUE >= ARCH_THRESHOLD && ascendable)
A.convert_to_archdevil()
else
throw EXCEPTION("Unable to find a blobstart landmark for hellish resurrection")
CRASH("Unable to find a blobstart landmark for hellish resurrection")
/datum/antagonist/devil/proc/update_hud()
@@ -32,7 +32,7 @@
if(BANE_HARVEST)
if(istype(weapon, /obj/item/reagent_containers/food/snacks/grown/))
visible_message("<span class='warning'>The spirits of the harvest aid in the exorcism.</span>", "<span class='notice'>The harvest spirits are harming you.</span>")
Knockdown(40)
DefaultCombatKnockdown(40)
qdel(weapon)
return 2
return 1
+1 -1
View File
@@ -13,7 +13,7 @@
icon = 'icons/mob/mob.dmi'
icon_state = "imp"
icon_living = "imp"
mob_biotypes = list(MOB_ORGANIC, MOB_HUMANOID)
mob_biotypes = MOB_ORGANIC|MOB_HUMANOID
speed = 1
a_intent = INTENT_HARM
stop_automated_movement = 1
@@ -5,49 +5,49 @@ is currently following.
*/
GLOBAL_LIST_INIT(disease_ability_singletons, list(
new /datum/disease_ability/action/cough,
new /datum/disease_ability/action/sneeze,
new /datum/disease_ability/action/infect,
new /datum/disease_ability/symptom/mild/cough,
new /datum/disease_ability/symptom/mild/sneeze,
new /datum/disease_ability/symptom/medium/shedding,
new /datum/disease_ability/symptom/medium/beard,
new /datum/disease_ability/symptom/medium/hallucigen,
new /datum/disease_ability/symptom/medium/choking,
new /datum/disease_ability/symptom/medium/confusion,
new /datum/disease_ability/symptom/medium/vomit,
new /datum/disease_ability/symptom/medium/voice_change,
new /datum/disease_ability/symptom/medium/visionloss,
new /datum/disease_ability/symptom/medium/deafness,
new /datum/disease_ability/symptom/powerful/narcolepsy,
new /datum/disease_ability/symptom/medium/fever,
new /datum/disease_ability/symptom/medium/shivering,
new /datum/disease_ability/symptom/medium/headache,
new /datum/disease_ability/symptom/medium/nano_boost,
new /datum/disease_ability/symptom/medium/nano_destroy,
new /datum/disease_ability/symptom/medium/viraladaptation,
new /datum/disease_ability/symptom/medium/viralevolution,
new /datum/disease_ability/symptom/medium/vitiligo,
new /datum/disease_ability/symptom/medium/revitiligo,
new /datum/disease_ability/symptom/medium/itching,
new /datum/disease_ability/symptom/medium/heal/weight_loss,
new /datum/disease_ability/symptom/medium/heal/sensory_restoration,
new /datum/disease_ability/symptom/medium/heal/mind_restoration,
new /datum/disease_ability/symptom/powerful/fire,
new /datum/disease_ability/symptom/powerful/flesh_eating,
// new /datum/disease_ability/symptom/powerful/genetic_mutation,
new /datum/disease_ability/symptom/powerful/inorganic_adaptation,
new /datum/disease_ability/symptom/powerful/heal/starlight,
new /datum/disease_ability/symptom/powerful/heal/oxygen,
new /datum/disease_ability/symptom/powerful/heal/chem,
new /datum/disease_ability/symptom/powerful/heal/metabolism,
new /datum/disease_ability/symptom/powerful/heal/dark,
new /datum/disease_ability/symptom/powerful/heal/water,
new /datum/disease_ability/symptom/powerful/heal/plasma,
new /datum/disease_ability/symptom/powerful/heal/radiation,
new /datum/disease_ability/symptom/powerful/heal/coma,
new /datum/disease_ability/symptom/powerful/youth
))
new /datum/disease_ability/action/cough,
new /datum/disease_ability/action/sneeze,
new /datum/disease_ability/action/infect,
new /datum/disease_ability/symptom/mild/cough,
new /datum/disease_ability/symptom/mild/sneeze,
new /datum/disease_ability/symptom/medium/shedding,
new /datum/disease_ability/symptom/medium/beard,
new /datum/disease_ability/symptom/medium/hallucigen,
new /datum/disease_ability/symptom/medium/choking,
new /datum/disease_ability/symptom/medium/confusion,
new /datum/disease_ability/symptom/medium/vomit,
new /datum/disease_ability/symptom/medium/voice_change,
new /datum/disease_ability/symptom/medium/visionloss,
new /datum/disease_ability/symptom/medium/deafness,
new /datum/disease_ability/symptom/powerful/narcolepsy,
new /datum/disease_ability/symptom/medium/fever,
new /datum/disease_ability/symptom/medium/shivering,
new /datum/disease_ability/symptom/medium/headache,
new /datum/disease_ability/symptom/medium/nano_boost,
new /datum/disease_ability/symptom/medium/nano_destroy,
new /datum/disease_ability/symptom/medium/viraladaptation,
new /datum/disease_ability/symptom/medium/viralevolution,
new /datum/disease_ability/symptom/medium/disfiguration,
new /datum/disease_ability/symptom/medium/polyvitiligo,
new /datum/disease_ability/symptom/medium/itching,
new /datum/disease_ability/symptom/medium/heal/weight_loss,
new /datum/disease_ability/symptom/medium/heal/sensory_restoration,
new /datum/disease_ability/symptom/medium/heal/mind_restoration,
new /datum/disease_ability/symptom/powerful/fire,
new /datum/disease_ability/symptom/powerful/flesh_eating,
new /datum/disease_ability/symptom/powerful/genetic_mutation,
new /datum/disease_ability/symptom/powerful/inorganic_adaptation,
new /datum/disease_ability/symptom/powerful/heal/starlight,
new /datum/disease_ability/symptom/powerful/heal/oxygen,
new /datum/disease_ability/symptom/powerful/heal/chem,
new /datum/disease_ability/symptom/powerful/heal/metabolism,
new /datum/disease_ability/symptom/powerful/heal/dark,
new /datum/disease_ability/symptom/powerful/heal/water,
new /datum/disease_ability/symptom/powerful/heal/plasma,
new /datum/disease_ability/symptom/powerful/heal/radiation,
new /datum/disease_ability/symptom/powerful/heal/coma,
new /datum/disease_ability/symptom/powerful/youth
))
/datum/disease_ability
var/name
@@ -57,7 +57,7 @@ GLOBAL_LIST_INIT(disease_ability_singletons, list(
var/short_desc = ""
var/long_desc = ""
var/stat_block = ""
var/threshold_block = list()
var/threshold_block = ""
var/category = ""
var/list/symptoms
@@ -76,7 +76,7 @@ GLOBAL_LIST_INIT(disease_ability_singletons, list(
resistance += initial(S.resistance)
stage_speed += initial(S.stage_speed)
transmittable += initial(S.transmittable)
threshold_block += initial(S.threshold_desc)
threshold_block += "<br><br>[initial(S.threshold_desc)]"
stat_block = "Resistance: [resistance]<br>Stealth: [stealth]<br>Stage Speed: [stage_speed]<br>Transmissibility: [transmittable]<br><br>"
if(symptoms.len == 1) //lazy boy's dream
name = initial(S.name)
@@ -106,10 +106,8 @@ GLOBAL_LIST_INIT(disease_ability_singletons, list(
for(var/T in symptoms)
var/datum/symptom/S = new T()
SD.symptoms += S
S.OnAdd(SD)
if(SD.processing)
if(S.Start(SD))
S.next_activation = world.time + rand(S.symptom_delay_min * 10, S.symptom_delay_max * 10)
S.Start(SD)
SD.Refresh()
for(var/T in actions)
var/datum/action/A = new T()
@@ -136,7 +134,6 @@ GLOBAL_LIST_INIT(disease_ability_singletons, list(
var/datum/symptom/S = locate(T) in SD.symptoms
if(S)
SD.symptoms -= S
S.OnRemove(SD)
if(SD.processing)
S.End(SD)
qdel(S)
@@ -296,7 +293,6 @@ GLOBAL_LIST_INIT(disease_ability_singletons, list(
cost = 8
category = "Symptom (Strong+)"
/******MILD******/
/datum/disease_ability/symptom/mild/cough
@@ -377,11 +373,11 @@ GLOBAL_LIST_INIT(disease_ability_singletons, list(
/datum/disease_ability/symptom/medium/viralevolution
symptoms = list(/datum/symptom/viralevolution)
/datum/disease_ability/symptom/medium/vitiligo
symptoms = list(/datum/symptom/vitiligo)
/datum/disease_ability/symptom/medium/polyvitiligo
symptoms = list(/datum/symptom/polyvitiligo)
/datum/disease_ability/symptom/medium/revitiligo
symptoms = list(/datum/symptom/revitiligo)
/datum/disease_ability/symptom/medium/disfiguration
symptoms = list(/datum/symptom/disfiguration)
/datum/disease_ability/symptom/medium/itching
symptoms = list(/datum/symptom/itching)
@@ -409,11 +405,9 @@ GLOBAL_LIST_INIT(disease_ability_singletons, list(
/datum/disease_ability/symptom/powerful/flesh_eating
symptoms = list(/datum/symptom/flesh_eating)
/*
/datum/disease_ability/symptom/powerful/genetic_mutation
symptoms = list(/datum/symptom/genetic_mutation)
cost = 8
*/
/datum/disease_ability/symptom/powerful/inorganic_adaptation
symptoms = list(/datum/symptom/inorganic_adaptation)
@@ -457,4 +451,4 @@ GLOBAL_LIST_INIT(disease_ability_singletons, list(
/datum/disease_ability/symptom/powerful/heal/coma
symptoms = list(/datum/symptom/heal/coma)
short_desc = "Cause victims to fall into a healing coma when hurt."
long_desc = "Cause victims to fall into a healing coma when hurt."
long_desc = "Cause victims to fall into a healing coma when hurt."
-1
View File
@@ -12,7 +12,6 @@
var/list/name_source
show_in_antagpanel = FALSE
antag_moodlet = /datum/mood_event/focused
can_hijack = HIJACK_PREVENT
/datum/antagonist/ert/on_gain()
update_name()
@@ -3,7 +3,7 @@
var/obj/item/claymore/highlander/sword
show_in_antagpanel = FALSE
show_name_in_check_antagonists = TRUE
can_hijack = HIJACK_HIJACKER
hijack_speed = 2 //if you kill everyone and actually haev a hand to hijack with, you win??
/datum/antagonist/highlander/apply_innate_effects(mob/living/mob_override)
var/mob/living/L = owner.current || mob_override
+4 -17
View File
@@ -8,11 +8,6 @@
var/give_objectives = TRUE
var/give_equipment = TRUE
/datum/antagonist/ninja/New()
if(helping_station)
can_hijack = HIJACK_PREVENT
. = ..()
/datum/antagonist/ninja/apply_innate_effects(mob/living/mob_override)
var/mob/living/M = mob_override || owner.current
update_ninja_icons_added(M)
@@ -75,16 +70,10 @@
O.explanation_text = "Protect \the [M.current.real_name], the [M.assigned_role], from harm."
objectives += O
if(4) //flavor
if(helping_station)
var/datum/objective/flavor/ninja_helping/O = new /datum/objective/flavor/ninja_helping
O.owner = owner
O.forge_objective()
objectives += O
else
var/datum/objective/flavor/ninja_syndie/O = new /datum/objective/flavor/ninja_helping
O.owner = owner
O.forge_objective()
objectives += O
var/datum/objective/flavor/O = helping_station ? new /datum/objective/flavor/ninja_helping : new /datum/objective/flavor/ninja_syndie
O.owner = owner
O.forge_objective()
objectives += O
else
break
var/datum/objective/O = new /datum/objective/survive()
@@ -135,8 +124,6 @@
adj = "objectiveless"
else
return
if(helping_station)
can_hijack = HIJACK_PREVENT
new_owner.assigned_role = ROLE_NINJA
new_owner.special_role = ROLE_NINJA
new_owner.add_antag_datum(src)
@@ -31,6 +31,7 @@
var/interior = ""
var/proper_bomb = TRUE //Please
var/obj/effect/countdown/nuclearbomb/countdown
var/nuclear_cooldown //used to stop global spam.
/obj/machinery/nuclearbomb/Initialize()
. = ..()
@@ -178,23 +179,22 @@
else
return NUKE_OFF_UNLOCKED
/obj/machinery/nuclearbomb/update_icon()
if(deconstruction_state == NUKESTATE_INTACT)
switch(get_nuke_state())
if(NUKE_OFF_LOCKED, NUKE_OFF_UNLOCKED)
icon_state = "nuclearbomb_base"
update_icon_interior()
update_icon_lights()
if(NUKE_ON_TIMING)
cut_overlays()
icon_state = "nuclearbomb_timing"
if(NUKE_ON_EXPLODING)
cut_overlays()
icon_state = "nuclearbomb_exploding"
else
/obj/machinery/nuclearbomb/update_icon_state()
if(deconstruction_state != NUKESTATE_INTACT)
icon_state = "nuclearbomb_base"
update_icon_interior()
update_icon_lights()
return
switch(get_nuke_state())
if(NUKE_OFF_LOCKED, NUKE_OFF_UNLOCKED)
icon_state = "nuclearbomb_base"
if(NUKE_ON_TIMING)
icon_state = "nuclearbomb_timing"
if(NUKE_ON_EXPLODING)
icon_state = "nuclearbomb_exploding"
/obj/machinery/nuclearbomb/update_overlays()
. = ..()
update_icon_interior()
update_icon_lights()
/obj/machinery/nuclearbomb/proc/update_icon_interior()
cut_overlay(interior)
@@ -381,6 +381,7 @@
playsound(src, 'sound/machines/nuke/confirm_beep.ogg', 50, FALSE)
set_active()
update_ui_mode()
. = TRUE
else
playsound(src, 'sound/machines/nuke/angry_beep.ogg', 50, FALSE)
if("anchor")
@@ -416,12 +417,16 @@
return
timing = !timing
if(timing)
if(nuclear_cooldown > world.time)
to_chat(usr, "<span class='danger'>[src]'s timer protocols are currently on cooldown, please stand by.</span>")
return
previous_level = get_security_level()
detonation_timer = world.time + (timer_set * 10)
for(var/obj/item/pinpointer/nuke/syndicate/S in GLOB.pinpointer_list)
S.switch_mode_to(TRACK_INFILTRATOR)
countdown.start()
set_security_level("delta")
nuclear_cooldown = world.time + 15 SECONDS
if(GLOB.war_declared)
var/area/A = get_area(src)
@@ -8,7 +8,6 @@
var/always_new_team = FALSE //If not assigned a team by default ops will try to join existing ones, set this to TRUE to always create new team.
var/send_to_spawnpoint = TRUE //Should the user be moved to default spawnpoint.
var/nukeop_outfit = /datum/outfit/syndicate
can_hijack = HIJACK_HIJACKER //Alternative way to wipe out the station.
/datum/antagonist/nukeop/proc/update_synd_icons_added(mob/living/M)
var/datum/atom_hud/antag/opshud = GLOB.huds[ANTAG_HUD_OPS]
@@ -4,7 +4,6 @@
show_in_antagpanel = FALSE
var/datum/objective/mission
var/datum/team/ert/ert_team
can_hijack = HIJACK_PREVENT
/datum/antagonist/official/greet()
to_chat(owner, "<B><font size=3 color=red>You are a CentCom Official.</font></B>")
@@ -9,7 +9,7 @@
throw_speed = 3
throw_range = 5
w_class = WEIGHT_CLASS_SMALL
materials = list(MAT_METAL=600, MAT_GLASS=200)
custom_materials = list(/datum/material/iron=600, /datum/material/glass=200)
var/uses = 2
/obj/item/overthrow_converter/proc/convert(mob/living/carbon/human/target, mob/living/carbon/human/user) // Should probably also delete any mindshield implant. Not sure.
@@ -49,7 +49,7 @@
else
to_chat(user, "<span class='warning'>[user] fails to implant [M].</span>")
/obj/item/overthrow_converter/update_icon()
/obj/item/overthrow_converter/update_icon_state()
if(uses)
icon_state = "implanter1"
else
@@ -16,7 +16,7 @@
var/icon_stun = "revenant_stun"
var/icon_drain = "revenant_draining"
var/stasis = FALSE
mob_biotypes = list(MOB_SPIRIT)
mob_biotypes = MOB_SPIRIT
incorporeal_move = INCORPOREAL_MOVE_JAUNT
invisibility = INVISIBILITY_REVENANT
health = INFINITY //Revenants don't use health, they use essence instead
@@ -213,7 +213,7 @@
continue
L.Beam(M,icon_state="purple_lightning",time=5)
if(!M.anti_magic_check(FALSE, TRUE))
M.electrocute_act(shock_damage, L, safety=TRUE)
M.electrocute_act(shock_damage, L, flags = SHOCK_NOGLOVES)
do_sparks(4, FALSE, M)
playsound(M, 'sound/machines/defib_zap.ogg', 50, 1, -1)
@@ -12,7 +12,7 @@
icon = 'icons/mob/mob.dmi'
icon_state = "daemon"
icon_living = "daemon"
mob_biotypes = list(MOB_ORGANIC, MOB_HUMANOID)
mob_biotypes = MOB_ORGANIC|MOB_HUMANOID
speed = 1
a_intent = INTENT_HARM
stop_automated_movement = 1
@@ -76,8 +76,9 @@
icon_state = "demon_heart-on"
decay_factor = 0
/obj/item/organ/heart/demon/update_icon()
return //always beating visually
/obj/item/organ/heart/demon/ComponentInitialize()
. = ..()
AddElement(/datum/element/update_icon_blocker)
/obj/item/organ/heart/demon/attack(mob/M, mob/living/carbon/user, obj/target)
if(M != user)
+7 -7
View File
@@ -4,7 +4,7 @@
desc = "A shell of swarmer that was completely powered down. It can no longer activate itself."
icon = 'icons/mob/swarmer.dmi'
icon_state = "swarmer_unactivated"
materials = list(MAT_METAL=10000, MAT_GLASS=4000)
custom_materials = list(/datum/material/iron=10000, /datum/material/glass=4000)
/obj/effect/mob_spawn/swarmer
name = "unactivated swarmer"
@@ -62,7 +62,7 @@
speak_emote = list("tones")
initial_language_holder = /datum/language_holder/swarmer
bubble_icon = "swarmer"
mob_biotypes = list(MOB_ROBOTIC)
mob_biotypes = MOB_ROBOTIC
health = 40
maxHealth = 40
status_flags = CANPUSH
@@ -191,7 +191,7 @@
return 0
/obj/item/IntegrateAmount() //returns the amount of resources gained when eating this item
if(materials[MAT_METAL] || materials[MAT_GLASS])
if(custom_materials[SSmaterials.GetMaterialRef(/datum/material/iron)] || custom_materials[SSmaterials.GetMaterialRef(/datum/material/glass)])
return 1
return ..()
@@ -492,8 +492,8 @@
playsound(src,'sound/effects/sparks4.ogg',50,1)
do_teleport(target, F, 0, channel = TELEPORT_CHANNEL_BLUESPACE)
/mob/living/simple_animal/hostile/swarmer/electrocute_act(shock_damage, obj/source, siemens_coeff = 1, safety = FALSE, tesla_shock = FALSE, illusion = FALSE, stun = TRUE)
if(!tesla_shock)
/mob/living/simple_animal/hostile/swarmer/electrocute_act(shock_damage, source, siemens_coeff = 1, flags = NONE)
if(!(flags & SHOCK_TESLA))
return FALSE
return ..()
@@ -584,9 +584,9 @@
var/mob/living/L = AM
if(!istype(L, /mob/living/simple_animal/hostile/swarmer))
playsound(loc,'sound/effects/snap.ogg',50, 1, -1)
L.electrocute_act(0, src, 1, 1, 1)
L.electrocute_act(0, src, 1, flags = SHOCK_NOGLOVES|SHOCK_ILLUSION)
if(iscyborg(L))
L.Knockdown(100)
L.DefaultCombatKnockdown(100)
qdel(src)
..()
@@ -5,6 +5,7 @@
max_occurrences = 0
earliest_start = 30 MINUTES
min_players = 15
gamemode_blacklist = list("dynamic")
/datum/round_event/spawn_swarmer
@@ -13,7 +13,8 @@
var/should_give_codewords = TRUE
var/should_equip = TRUE
var/traitor_kind = TRAITOR_HUMAN //Set on initial assignment
can_hijack = HIJACK_HIJACKER
var/datum/contractor_hub/contractor_hub
hijack_speed = 0.5 //10 seconds per hijack stage by default
/datum/antagonist/traitor/on_gain()
if(owner.current && isAI(owner.current))
@@ -60,6 +61,7 @@
message = GLOB.syndicate_code_response_regex.Replace(message, "<span class='red'>$1</span>")
hearing_args[HEARING_RAW_MESSAGE] = message
// needs to be refactored to base /datum/antagonist sometime..
/datum/antagonist/traitor/proc/add_objective(datum/objective/O)
objectives += O
@@ -412,6 +414,9 @@
var/special_role_text = lowertext(name)
if(contractor_hub)
result += contractor_round_end()
if(traitorwin)
result += "<span class='greentext'>The [special_role_text] was successful!</span>"
else
@@ -420,12 +425,44 @@
return result.Join("<br>")
/// Proc detailing contract kit buys/completed contracts/additional info
/datum/antagonist/traitor/proc/contractor_round_end()
var result = ""
var total_spent_rep = 0
var/completed_contracts = 0
var/tc_total = contractor_hub.contract_TC_payed_out + contractor_hub.contract_TC_to_redeem
for(var/datum/syndicate_contract/contract in contractor_hub.assigned_contracts)
if(contract.status == CONTRACT_STATUS_COMPLETE)
completed_contracts++
var/contractor_item_icons = "" // Icons of purchases
var/contractor_support_unit = "" // Set if they had a support unit - and shows appended to their contracts completed
for(var/datum/contractor_item/contractor_purchase in contractor_hub.purchased_items) // Get all the icons/total cost for all our items bought
contractor_item_icons += "<span class='tooltip_container'>\[ <i class=\"fas [contractor_purchase.item_icon]\"></i><span class='tooltip_hover'><b>[contractor_purchase.name] - [contractor_purchase.cost] Rep</b><br><br>[contractor_purchase.desc]</span> \]</span>"
total_spent_rep += contractor_purchase.cost
if(istype(contractor_purchase, /datum/contractor_item/contractor_partner)) // Special case for reinforcements, we want to show their ckey and name on round end.
var/datum/contractor_item/contractor_partner/partner = contractor_purchase
contractor_support_unit += "<br><b>[partner.partner_mind.key]</b> played <b>[partner.partner_mind.current.name]</b>, their contractor support unit."
if (contractor_hub.purchased_items.len)
result += "<br>(used [total_spent_rep] Rep)"
result += contractor_item_icons
result += "<br>"
if(completed_contracts > 0)
var/pluralCheck = "contract"
if(completed_contracts > 1)
pluralCheck = "contracts"
result += "Completed <span class='greentext'>[completed_contracts]</span> [pluralCheck] for a total of \
<span class='greentext'>[tc_total] TC</span>!<br>"
return result
/datum/antagonist/traitor/roundend_report_footer()
var/phrases = jointext(GLOB.syndicate_code_phrase, ", ")
var/responses = jointext(GLOB.syndicate_code_response, ", ")
var message = "<br><b>The code phrases were:</b> <span class='bluetext'>[phrases]</span><br>\
<b>The code responses were:</b> <span class='redtext'>[responses]</span><br>"
<b>The code responses were:</b> <span class='redtext'>[responses]</span><br>"
return message
@@ -0,0 +1,227 @@
// Support unit gets it's own very basic antag datum for admin logging.
/datum/antagonist/traitor/contractor_support
name = "Contractor Support Unit"
antag_moodlet = /datum/mood_event/focused
show_in_roundend = FALSE /// We're already adding them in to the contractor's roundend.
give_objectives = TRUE /// We give them their own custom objective.
show_in_antagpanel = FALSE /// Not a proper/full antag.
should_equip = FALSE /// Don't give them an uplink.
var/datum/team/contractor_team/contractor_team
/datum/team/contractor_team // Team for storing both the contractor and their support unit - only really for the HUD and admin logging.
show_roundend_report = FALSE
/datum/antagonist/traitor/contractor_support/forge_traitor_objectives()
var/datum/objective/generic_objective = new
generic_objective.name = "Follow Contractor's Orders"
generic_objective.explanation_text = "Follow your orders. Assist agents in this mission area."
generic_objective.completed = TRUE
add_objective(generic_objective)
/datum/contractor_hub
var/contract_rep = 0
var/list/hub_items = list()
var/list/purchased_items = list()
var/static/list/contractor_items = typecacheof(/datum/contractor_item/, TRUE)
var/datum/syndicate_contract/current_contract
var/list/datum/syndicate_contract/assigned_contracts = list()
var/list/assigned_targets = list() // used as a blacklist to make sure we're not assigning targets already assigned
var/contract_TC_payed_out = 0 // Keeping track for roundend reporting
var/contract_TC_to_redeem = 0 // Used internally and roundend reporting - what TC we have available to cashout.
/datum/contractor_hub/proc/create_hub_items()
for(var/path in contractor_items)
var/datum/contractor_item/contractor_item = new path
hub_items.Add(contractor_item)
/datum/contractor_hub/proc/create_contracts(datum/mind/owner) // 6 initial contracts
var/list/to_generate = list(
CONTRACT_PAYOUT_LARGE,
CONTRACT_PAYOUT_MEDIUM,
CONTRACT_PAYOUT_SMALL,
CONTRACT_PAYOUT_SMALL,
CONTRACT_PAYOUT_SMALL,
CONTRACT_PAYOUT_SMALL
)
var/lowest_TC_threshold = 30 // We don't want the sum of all the payouts to be under this amount
var/total = 0
var/lowest_paying_sum = 0
var/datum/syndicate_contract/lowest_paying_contract
to_generate = shuffle(to_generate) // Randomise order, so we don't have contracts always in payout order.
var/start_index = 1 // Support contract generation happening multiple times
if(assigned_contracts.len != 0)
start_index = assigned_contracts.len + 1
for(var/i = 1; i <= to_generate.len; i++) // Generate contracts, and find the lowest paying.
var/datum/syndicate_contract/contract_to_add = new(owner, assigned_targets, to_generate[i])
var/contract_payout_total = contract_to_add.contract.payout + contract_to_add.contract.payout_bonus
assigned_targets.Add(contract_to_add.contract.target)
if(!lowest_paying_contract || (contract_payout_total < lowest_paying_sum))
lowest_paying_sum = contract_payout_total
lowest_paying_contract = contract_to_add
total += contract_payout_total
contract_to_add.id = start_index
assigned_contracts.Add(contract_to_add)
start_index++
if(total < lowest_TC_threshold) // If the threshold for TC payouts isn't reached, boost the lowest paying contract
lowest_paying_contract.contract.payout_bonus += (lowest_TC_threshold - total)
/datum/contractor_item
var/name // Name of item
var/desc // description of item
var/item // item path, no item path means the purchase needs it's own handle_purchase()
var/item_icon = "fa-broadcast-tower" // fontawesome icon to use inside the hub - https://fontawesome.com/icons/
var/limited = -1 // Any number above 0 for how many times it can be bought in a round for a single traitor. -1 is unlimited.
var/cost // Cost of the item in contract rep.
/datum/contractor_item/contract_reroll
name = "Contract Reroll"
desc = "Request a reroll of your current contract list. Will generate a new target, payment, and dropoff for the contracts you currently have available."
item_icon = "fa-dice"
limited = 2
cost = 0
/datum/contractor_item/contract_reroll/handle_purchase(var/datum/contractor_hub/hub)
. = ..()
if (.)
var/list/new_target_list = list() // We're not regenerating already completed/aborted/extracting contracts, but we don't want to repeat their targets.
for(var/datum/syndicate_contract/contract_check in hub.assigned_contracts)
if (contract_check.status != CONTRACT_STATUS_ACTIVE && contract_check.status != CONTRACT_STATUS_INACTIVE)
if (contract_check.contract.target)
new_target_list.Add(contract_check.contract.target)
continue
for(var/datum/syndicate_contract/rerolling_contract in hub.assigned_contracts) // Reroll contracts without duplicates
if (rerolling_contract.status != CONTRACT_STATUS_ACTIVE && rerolling_contract.status != CONTRACT_STATUS_INACTIVE)
continue
rerolling_contract.generate(new_target_list)
new_target_list.Add(rerolling_contract.contract.target)
hub.assigned_targets = new_target_list // Set our target list with the new set we've generated.
/datum/contractor_item/contractor_pinpointer
name = "Contractor Pinpointer"
desc = "A pinpointer that finds targets even without active suit sensors. Due to taking advantage of an exploit within the system, it can't pinpoint to the same accuracy as the traditional models. Becomes permanently locked to the user that first activates it."
item = /obj/item/pinpointer/crew/contractor
item_icon = "fa-search-location"
limited = 2
cost = 1
/datum/contractor_item/fulton_extraction_kit
name = "Fulton Extraction Kit"
desc = "For getting your target across the station to those difficult dropoffs. Place the beacon somewhere secure, and link the pack. Activating the pack on your target in space will send them over to the beacon - make sure they're not just going to run away though!"
item = /obj/item/storage/box/contractor/fulton_extraction
item_icon = "fa-parachute-box"
limited = 1
cost = 1
/datum/contractor_item/contractor_partner
name = "Reinforcements"
desc = "Upon purchase we'll contact available units in the area. Should there be an agent free, we'll send them down to assist you immediately. If no units are free, we give a full refund."
item_icon = "fa-user-friends"
limited = 1
cost = 2
var/datum/mind/partner_mind = null
/datum/contractor_item/contractor_partner/handle_purchase(var/datum/contractor_hub/hub, mob/living/user)
. = ..()
if (.)
to_chat(user, "<span class='notice'>The uplink vibrates quietly, connecting to nearby agents...</span>")
var/list/mob/dead/observer/candidates = pollGhostCandidates("Do you want to play as the Contractor Support Unit for [user.real_name]?", ROLE_PAI, null, FALSE, 100, POLL_IGNORE_CONTRACTOR_SUPPORT)
if(LAZYLEN(candidates))
var/mob/dead/observer/C = pick(candidates)
spawn_contractor_partner(user, C.key)
else
to_chat(user, "<span class='notice'>No available agents at this time, please try again later.</span>")
limited += 1 // refund and add the limit back.
hub.contract_rep += cost
hub.purchased_items -= src
/datum/outfit/contractor_partner
name = "Contractor Support Unit"
uniform = /obj/item/clothing/under/chameleon
suit = /obj/item/clothing/suit/chameleon
back = /obj/item/storage/backpack
belt = /obj/item/pda/chameleon
mask = /obj/item/clothing/mask/cigarette/syndicate
shoes = /obj/item/clothing/shoes/chameleon/noslip
ears = /obj/item/radio/headset/chameleon
id = /obj/item/card/id/syndicate
r_hand = /obj/item/storage/toolbox/syndicate
backpack_contents = list(/obj/item/storage/box/survival, /obj/item/implanter/uplink, /obj/item/clothing/mask/chameleon,
/obj/item/storage/fancy/cigarettes/cigpack_syndicate, /obj/item/lighter)
/datum/outfit/contractor_partner/post_equip(mob/living/carbon/human/H, visualsOnly)
. = ..()
var/obj/item/clothing/mask/cigarette/syndicate/cig = H.get_item_by_slot(SLOT_WEAR_MASK)
cig.light() // pre-light their cig for extra badass
/datum/contractor_item/contractor_partner/proc/spawn_contractor_partner(mob/living/user, key)
var/mob/living/carbon/human/partner = new()
var/datum/outfit/contractor_partner/partner_outfit = new()
partner_outfit.equip(partner)
var/obj/structure/closet/supplypod/arrival_pod = new()
arrival_pod.style = STYLE_SYNDICATE
arrival_pod.explosionSize = list(0,0,0,1)
arrival_pod.bluespace = TRUE
var/turf/free_location = find_obstruction_free_location(2, user)
if (!free_location) // We really want to send them - if we can't find a nice location just land it on top of them.
free_location = get_turf(user)
partner.forceMove(arrival_pod)
partner.ckey = key
partner_mind = partner.mind // We give a reference to the mind that'll be the support unit
partner_mind.make_Contractor_Support()
to_chat(partner_mind.current, "\n<span class='alertwarning'>[user.real_name] is your superior. Follow any, and all orders given by them. You're here to support their mission only.</span>")
to_chat(partner_mind.current, "<span class='alertwarning'>Should they perish, or be otherwise unavailable, you're to assist other active agents in this mission area to the best of your ability.</span>\n\n")
new /obj/effect/abstract/DPtarget(free_location, arrival_pod)
/datum/contractor_item/blackout
name = "Blackout"
desc = "Request Syndicate Command to distrupt the station's powernet. Disables power across the station for a short duration."
item_icon = "fa-bolt"
limited = 2
cost = 3
/datum/contractor_item/blackout/handle_purchase(var/datum/contractor_hub/hub)
. = ..()
if (.)
power_fail(35, 50)
priority_announce("Abnormal activity detected in [station_name()]'s powernet. As a precautionary measure, the station's power will be shut off for an indeterminate duration.", "Critical Power Failure", "poweroff")
// Subtract cost, and spawn if it's an item.
/datum/contractor_item/proc/handle_purchase(var/datum/contractor_hub/hub, mob/living/user)
if (hub.contract_rep >= cost)
hub.contract_rep -= cost
else
return FALSE
if (limited >= 1)
limited -= 1
else if (limited == 0)
return FALSE
hub.purchased_items.Add(src)
if (item && ispath(item))
var/atom/item_to_create = new item(get_turf(user))
if(user.put_in_hands(item_to_create))
to_chat(user, "<span class='notice'>Your purchase materializes into your hands!</span>")
else
to_chat(user, "<span class='notice'>Your purchase materializes onto the floor.</span>")
return item_to_create
return TRUE
/obj/item/pinpointer/crew/contractor
name = "contractor pinpointer"
desc = "A handheld tracking device that locks onto certain signals. Ignores suit sensors, but is much less accurate."
icon_state = "pinpointer_syndicate"
minimum_range = 25
has_owner = TRUE
ignore_suit_sensor_level = TRUE
/obj/item/storage/box/contractor/fulton_extraction
name = "Fulton Extraction Kit"
icon_state = "syndiebox"
illustration = "writing_syndie"
/obj/item/storage/box/contractor/fulton_extraction/PopulateContents()
new /obj/item/extraction_pack(src)
new /obj/item/fulton_core(src)
@@ -0,0 +1,150 @@
/datum/syndicate_contract
var/id = 0
var/status = CONTRACT_STATUS_INACTIVE
var/datum/objective/contract/contract = new()
var/target_rank
var/ransom = 0
var/payout_type = null
var/list/victim_belongings = list()
/datum/syndicate_contract/New(contract_owner, blacklist, type=CONTRACT_PAYOUT_SMALL)
contract.owner = contract_owner
payout_type = type
generate(blacklist)
/datum/syndicate_contract/proc/generate(blacklist)
contract.find_target(null, blacklist)
var/datum/data/record/record = find_record("name", contract.target.name, GLOB.data_core.general)
if(record)
target_rank = record.fields["rank"]
else
target_rank = "Unknown"
if (payout_type == CONTRACT_PAYOUT_LARGE)
contract.payout_bonus = rand(9,13)
else if(payout_type == CONTRACT_PAYOUT_MEDIUM)
contract.payout_bonus = rand(6,8)
else
contract.payout_bonus = rand(2,4)
contract.payout = rand(0, 2)
contract.generate_dropoff()
ransom = 100 * rand(18, 45)
/datum/syndicate_contract/proc/handle_extraction(var/mob/living/user)
if (contract.target && contract.dropoff_check(user, contract.target.current))
var/turf/free_location = find_obstruction_free_location(3, user, contract.dropoff)
if(free_location) // We've got a valid location, launch.
launch_extraction_pod(free_location)
return TRUE
return FALSE
// Launch the pod to collect our victim.
/datum/syndicate_contract/proc/launch_extraction_pod(turf/empty_pod_turf)
var/obj/structure/closet/supplypod/extractionpod/empty_pod = new()
RegisterSignal(empty_pod, COMSIG_ATOM_ENTERED, .proc/enter_check)
empty_pod.stay_after_drop = TRUE
empty_pod.reversing = TRUE
empty_pod.explosionSize = list(0,0,0,1)
empty_pod.leavingSound = 'sound/effects/podwoosh.ogg'
new /obj/effect/abstract/DPtarget(empty_pod_turf, empty_pod)
/datum/syndicate_contract/proc/enter_check(datum/source, sent_mob)
if(istype(source, /obj/structure/closet/supplypod/extractionpod))
if(isliving(sent_mob))
var/mob/living/M = sent_mob
var/datum/antagonist/traitor/traitor_data = contract.owner.has_antag_datum(/datum/antagonist/traitor)
if(M == contract.target.current)
traitor_data.contractor_hub.contract_TC_to_redeem += contract.payout
if(M.stat != DEAD)
traitor_data.contractor_hub.contract_TC_to_redeem += contract.payout_bonus
status = CONTRACT_STATUS_COMPLETE
if(traitor_data.contractor_hub.current_contract == src)
traitor_data.contractor_hub.current_contract = null
traitor_data.contractor_hub.contract_rep += 2
else
status = CONTRACT_STATUS_ABORTED // Sending a target that wasn't even yours is as good as just aborting it
if(traitor_data.contractor_hub.current_contract == src)
traitor_data.contractor_hub.current_contract = null
if(iscarbon(M))
for(var/obj/item/W in M)
if(ishuman(M))
var/mob/living/carbon/human/H = M
if(W == H.w_uniform || W == H.shoes)
continue //So all they're left with are shoes and uniform.
M.transferItemToLoc(W)
victim_belongings.Add(W)
var/obj/structure/closet/supplypod/extractionpod/pod = source
pod.send_up(pod) // Handle the pod returning
if(ishuman(M))
var/mob/living/carbon/human/target = M // After we remove items, at least give them what they need to live.
target.dna.species.give_important_for_life(target)
handleVictimExperience(M) // After pod is sent we start the victim narrative/heal.
var/points_to_check = SSshuttle.points // This is slightly delayed because of the sleep calls above to handle the narrative. We don't want to tell the station instantly.
if(points_to_check >= ransom)
SSshuttle.points -= ransom
else
SSshuttle.points -= points_to_check
priority_announce("One of your crew was captured by a rival organisation - we've needed to pay their ransom to bring them back. \
As is policy we've taken a portion of the station's funds to offset the overall cost.", null, "attention", null, "Nanotrasen Asset Protection")
/datum/syndicate_contract/proc/handleVictimExperience(var/mob/living/M) // They're off to holding - handle the return timer and give some text about what's going on.
addtimer(CALLBACK(src, .proc/returnVictim, M), (60 * 10) * 4) // Ship 'em back - dead or alive... 4 minutes wait.
if(M.stat != DEAD) //Even if they weren't the target, we're still treating them the same.
M.reagents.add_reagent(/datum/reagent/medicine/omnizine, 20) // Heal them up - gets them out of crit/soft crit.
M.flash_act()
M.confused += 10
M.blur_eyes(5)
to_chat(M, "<span class='warning'>You feel strange...</span>")
sleep(60)
to_chat(M, "<span class='warning'>That pod did something to you...</span>")
M.Dizzy(35)
sleep(65)
to_chat(M, "<span class='warning'>Your head pounds... It feels like it's going to burst out your skull!</span>")
M.flash_act()
M.confused += 20
M.blur_eyes(3)
sleep(30)
to_chat(M, "<span class='warning'>Your head pounds...</span>")
sleep(100)
M.flash_act()
M.Unconscious(200)
to_chat(M, "<span class='reallybig hypnophrase'>A million voices echo in your head... <i>\"Your mind held many valuable secrets - \
we thank you for providing them. Your value is expended, and you will be ransomed back to your station. We always get paid, \
so it's only a matter of time before we ship you back...\"</i></span>")
M.blur_eyes(10)
M.Dizzy(15)
M.confused += 20
/datum/syndicate_contract/proc/returnVictim(var/mob/living/M) // We're returning the victim
var/list/possible_drop_loc = list()
for(var/turf/possible_drop in contract.dropoff.contents)
if(!is_blocked_turf(possible_drop))
possible_drop_loc.Add(possible_drop)
if(possible_drop_loc.len > 0)
var/pod_rand_loc = rand(1, possible_drop_loc.len)
var/obj/structure/closet/supplypod/return_pod = new()
return_pod.bluespace = TRUE
return_pod.explosionSize = list(0,0,0,0)
return_pod.style = STYLE_SYNDICATE
do_sparks(8, FALSE, M)
M.visible_message("<span class='notice'>[M] vanishes...</span>")
for(var/obj/item/W in M)
if(ishuman(M))
var/mob/living/carbon/human/H = M
if(W == H.w_uniform || W == H.shoes)
continue //So all they're left with are shoes and uniform.
M.dropItemToGround(W)
for(var/obj/item/W in victim_belongings)
W.forceMove(return_pod)
M.forceMove(return_pod)
M.flash_act()
M.blur_eyes(30)
M.Dizzy(35)
M.confused += 20
new /obj/effect/abstract/DPtarget(possible_drop_loc[pod_rand_loc], return_pod)
else
to_chat(M, "<span class='reallybig hypnophrase'>A million voices echo in your head... <i>\"Seems where you got sent here from won't \
be able to handle our pod... You will die here instead.\"</i></span>")
if(iscarbon(M))
var/mob/living/carbon/C = M
if(C.can_heartattack())
C.set_heartattack(TRUE)

Some files were not shown because too many files have changed in this diff Show More