Merge branch 'master' of https://github.com/Citadel-Station-13/Citadel-Station-13 into putnamos-for-real

This commit is contained in:
Putnam
2020-07-08 22:02:03 -07:00
1515 changed files with 270775 additions and 326499 deletions
+5
View File
@@ -22,6 +22,11 @@
sparks.set_up(2,0)
sparks.attach(src)
update_icon()
new_occupant_dir = dir
/obj/machinery/vr_sleeper/setDir(newdir)
. = ..()
new_occupant_dir = dir
/obj/machinery/vr_sleeper/attackby(obj/item/I, mob/user, params)
if(!state_open && !occupant)
+1 -1
View File
@@ -4,7 +4,7 @@
var/F = file("[GLOB.log_directory]/[subject].html")
WRITE_FILE(F, "<small>[TIME_STAMP("hh:mm:ss", FALSE)] [REF(src)] ([x],[y],[z])</small> || [src] [message]<br>")
/client/proc/investigate_show(subject in list("notes, memos, watchlist", INVESTIGATE_RCD, INVESTIGATE_RESEARCH, INVESTIGATE_EXONET, INVESTIGATE_PORTAL, INVESTIGATE_SINGULO, INVESTIGATE_WIRES, INVESTIGATE_TELESCI, INVESTIGATE_GRAVITY, INVESTIGATE_RECORDS, INVESTIGATE_CARGO, INVESTIGATE_SUPERMATTER, INVESTIGATE_ATMOS, INVESTIGATE_EXPERIMENTOR, INVESTIGATE_BOTANY, INVESTIGATE_HALLUCINATIONS, INVESTIGATE_RADIATION, INVESTIGATE_CIRCUIT, INVESTIGATE_NANITES) )
/client/proc/investigate_show(subject in list("notes, memos, watchlist", INVESTIGATE_RCD, INVESTIGATE_RESEARCH, INVESTIGATE_EXONET, INVESTIGATE_PORTAL, INVESTIGATE_SINGULO, INVESTIGATE_WIRES, INVESTIGATE_TELESCI, INVESTIGATE_GRAVITY, INVESTIGATE_RECORDS, INVESTIGATE_CARGO, INVESTIGATE_SUPERMATTER, INVESTIGATE_ATMOS, INVESTIGATE_EXPERIMENTOR, INVESTIGATE_BOTANY, INVESTIGATE_HALLUCINATIONS, INVESTIGATE_RADIATION, INVESTIGATE_CIRCUIT, INVESTIGATE_NANITES, INVESTIGATE_CRYOGENICS) )
set name = "Investigate"
set category = "Admin"
if(!holder)
+1
View File
@@ -106,6 +106,7 @@ GLOBAL_LIST_INIT(admin_verbs_fun, list(
/client/proc/show_tip,
/client/proc/smite,
/client/proc/admin_away,
/client/proc/cmd_admin_toggle_fov,
/client/proc/roll_dices //CIT CHANGE - Adds dice verb
))
GLOBAL_PROTECT(admin_verbs_fun)
+49 -1
View File
@@ -84,7 +84,7 @@ GLOBAL_LIST(round_end_notifiees)
if(!SSticker.IsRoundInProgress() && SSticker.HasRoundStarted())
return "[sender.mention], the round has already ended!"
LAZYINITLIST(GLOB.round_end_notifiees)
GLOB.round_end_notifiees["<@[sender.mention]>"] = TRUE
GLOB.round_end_notifiees[sender.mention] = TRUE
return "I will notify [sender.mention] when the round ends."
/datum/tgs_chat_command/sdql
@@ -142,3 +142,51 @@ GLOBAL_LIST(round_end_notifiees)
log_admin("[sender.friendly_name] has added [params] to the current round's bunker bypass list.")
message_admins("[sender.friendly_name] has added [params] to the current round's bunker bypass list.")
return "[params] has been added to the current round's bunker bypass list."
// More (silly) chat commands citadel added.
/datum/tgs_chat_command/wheelofsalt
name = "wheelofsalt"
help_text = "What are Citadel Station 13 players salting about today? Spin the wheel and find out!"
/datum/tgs_chat_command/wheelofsalt/Run(datum/tgs_chat_user/sender, params)
var/saltresult = "The wheel of salt [pick("clatters","screams","vibrates","clanks","resonates","groans","moans","squeaks","emits a[pick(" god-forsaken"," lewd"," creepy"," generic","n orgasmic"," demonic")] [pick("airhorn","bike horn","trumpet","clown","latex","vore","dog","laughing")] noise")] as it spins violently... And it seems the salt of the day is the "
var/saltprimarysubject = "[pick("combat","medical","grab","furry","wall","orgasm","cat","ERP","lizard","dog","latex","vision cone","atmospherics","table","chem","vore","dogborg","Skylar Lineman","Mekhi Anderson","Peppermint","rework","cum","dick","cockvore","Medihound","sleeper","belly sleeper","door wires","flightsuit","coder privilege","Developer abuse","ban reason","github self merge","red panda","beret","male catgirl","powergame","hexacrocin","Discord server","Clitadel","Cargonia","Solarian Republic","Main and RP merger","bluespace","salt","chem dispenser theft","Botany","moth","BWOINK","anal vore","stamina","Mason Jakops","mining","noodle","milf","Lavaland","Necropolis","Ashwalker","Chase Redtail","Drew Mint","Pavel Marsk","Joker Amari","Durgit","chaplain","Antag","nanite","Syndicate","Nar-Sie","Ratvar","Cult","maint","Foam-Force","AI","cyborg","ghost","clockwork","cyberpunk","vaporwave","Clown","Leon Beech","Mime","security","research","Megafauna","Bubblegum","Ash Drake","Legion","Colossus","White Shuttle","Changeling","Cowboy","Space Ninja","Poly","Revolutionary","Skyrim","forbidden fruits","xenomorph","blob","Nuclear Operative","crossdressing")]"
var/saltsecondarysubject = "[pick("rework","changes","r34","ban","removal","addition","leak","proposal","fanart","introduction","tabling","ERP","bikeshedding","crossdressing","sprites","semen keg","argument","theft","nerf","screeching","salt","creampie","lewding","murder","kissing","marriage","replacement","fucking","ship","netflix adaptation","dance","remaster","system","voyeur","decoration","pre-order","bukkake","seduction","worship","gangbang","handholding")]"
if(prob(10))
saltresult += "@here for your salt, all day every day"
if(prob(1))
saltresult += " @everyone gets some salt this time too"
else
saltresult += "[saltprimarysubject] [saltsecondarysubject]"
return "[saltresult]!"
/datum/tgs_chat_command/valentine
name = "valentine"
help_text = "Get a random flirt line."
/datum/tgs_chat_command/valentine/Run(datum/tgs_chat_user/sender, params)
return "[pick(GLOB.flirts)]"
/datum/tgs_chat_command/despacito
name = "despacito" //someone please high effort this sometime and make it a full on ytdl search
help_text = "This is so sad."
/datum/tgs_chat_command/despacito/Run()
return "https://www.youtube.com/watch?v=kJQP7kiw5Fk"
/datum/tgs_chat_command/poly
name = "poly"
help_text = "The Lewder, more applicable Poly speak for Citadel Station 13."
var/list/speech_buffer
/datum/tgs_chat_command/poly/Run()
LAZYINITLIST(speech_buffer) //I figure this is just safe to do for everything at this point
if(length(speech_buffer)) //Let's not look up the whole json EVERY TIME, just the first time.
return "[pick(speech_buffer)]"
else
var/json_file = file("data/npc_saves/Poly.json")
if(!fexists(json_file))
return "**BAWWWWWK!** LEAVE THE HEADSET! ***BAWKKKKK!!***"
var/list/json = json_decode(file2text(json_file))
speech_buffer = json["phrases"]
return "[pick(speech_buffer)]"
+1 -1
View File
@@ -126,7 +126,7 @@
L.forceMove(LA)
L.hallucination = 0
to_chat(L, "<span class='reallybig redtext'>The battle is won. Your bloodlust subsides.</span>")
for(var/obj/item/twohanded/required/chainsaw/doomslayer/chainsaw in L)
for(var/obj/item/chainsaw/doomslayer/chainsaw in L)
qdel(chainsaw)
else
to_chat(L, "You are not yet worthy of passing. Drag a severed head to the barrier to be allowed entry to the hall of champions.")
-59
View File
@@ -69,11 +69,6 @@
<A href='?src=[REF(src)];[HrefToken()];secrets=floorlava'>The floor is lava! (DANGEROUS: extremely lame)</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=customportal'>Spawn a custom portal storm</A><BR>
<BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=flipmovement'>Flip client movement directions</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=randommovement'>Randomize client movement directions</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=custommovement'>Set each movement direction manually</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=resetmovement'>Reset movement directions to default</A><BR>
<BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=changebombcap'>Change bomb cap</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=masspurrbation'>Mass Purrbation</A><BR>
<A href='?src=[REF(src)];[HrefToken()];secrets=massremovepurrbation'>Mass Remove Purrbation</A><BR>
@@ -603,60 +598,6 @@
purrbation.")
log_admin("[key_name(usr)] has removed everyone from purrbation.")
if("flipmovement")
if(!check_rights(R_FUN))
return
if(alert("Flip all movement controls?","Confirm","Yes","Cancel") == "Cancel")
return
var/list/movement_keys = SSinput.movement_keys
for(var/i in 1 to movement_keys.len)
var/key = movement_keys[i]
movement_keys[key] = turn(movement_keys[key], 180)
message_admins("[key_name_admin(usr)] has flipped all movement directions.")
log_admin("[key_name(usr)] has flipped all movement directions.")
if("randommovement")
if(!check_rights(R_FUN))
return
if(alert("Randomize all movement controls?","Confirm","Yes","Cancel") == "Cancel")
return
var/list/movement_keys = SSinput.movement_keys
for(var/i in 1 to movement_keys.len)
var/key = movement_keys[i]
movement_keys[key] = turn(movement_keys[key], 45 * rand(1, 8))
message_admins("[key_name_admin(usr)] has randomized all movement directions.")
log_admin("[key_name(usr)] has randomized all movement directions.")
if("custommovement")
if(!check_rights(R_FUN))
return
if(alert("Are you sure you want to change every movement key?","Confirm","Yes","Cancel") == "Cancel")
return
var/list/movement_keys = SSinput.movement_keys
var/list/new_movement = list()
for(var/i in 1 to movement_keys.len)
var/key = movement_keys[i]
var/msg = "Please input the new movement direction when the user presses [key]. Ex. northeast"
var/title = "New direction for [key]"
var/new_direction = text2dir(input(usr, msg, title) as text|null)
if(!new_direction)
new_direction = movement_keys[key]
new_movement[key] = new_direction
SSinput.movement_keys = new_movement
message_admins("[key_name_admin(usr)] has configured all movement directions.")
log_admin("[key_name(usr)] has configured all movement directions.")
if("resetmovement")
if(!check_rights(R_FUN))
return
if(alert("Are you sure you want to reset movement keys to default?","Confirm","Yes","Cancel") == "Cancel")
return
SSinput.setup_default_movement_keys()
message_admins("[key_name_admin(usr)] has reset all movement keys.")
log_admin("[key_name(usr)] has reset all movement keys.")
if("customportal")
if(!check_rights(R_FUN))
return
+70 -47
View File
@@ -346,7 +346,7 @@ GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/SDQL2_VV_all, new(null
//print the key
if(islist(key))
recursive_list_print(output, key, datum_handler, atom_handler)
else if(is_proper_datum(key) && (datum_handler || (isatom(key) && atom_handler)))
else if(is_object_datatype(key) && (datum_handler || (isatom(key) && atom_handler)))
if(isatom(key) && atom_handler)
output += atom_handler.Invoke(key)
else
@@ -360,7 +360,7 @@ GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/SDQL2_VV_all, new(null
var/value = input[key]
if(islist(value))
recursive_list_print(output, value, datum_handler, atom_handler)
else if(is_proper_datum(value) && (datum_handler || (isatom(value) && atom_handler)))
else if(is_object_datatype(value) && (datum_handler || (isatom(value) && atom_handler)))
if(isatom(value) && atom_handler)
output += atom_handler.Invoke(value)
else
@@ -498,7 +498,7 @@ GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/SDQL2_VV_all, new(null
if(length(select_text))
var/text = islist(select_text)? select_text.Join() : select_text
var/static/result_offset = 0
showmob << browse(text, "window=SDQL-result-[result_offset++]")
showmob << browse(text, "window=SDQL-result-[result_offset++];size=800x1200")
show_next_to_key = null
if(qdel_on_finish)
qdel(src)
@@ -646,7 +646,7 @@ GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/SDQL2_VV_all, new(null
switch(query_tree[1])
if("call")
for(var/i in found)
if(!is_proper_datum(i))
if(!is_object_datatype(i))
continue
world.SDQL_var(i, query_tree["call"][1], null, i, superuser, src)
obj_count_finished++
@@ -664,7 +664,10 @@ GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/SDQL2_VV_all, new(null
var/list/text_list = list()
var/print_nulls = !(options & SDQL2_OPTION_SELECT_OUTPUT_SKIP_NULLS)
obj_count_finished = select_refs
var/n = 0
for(var/i in found)
if(++n == 20000)
text_list += "<br><font color='red'><b>TRUNCATED - 20000 OBJECT LIMIT HIT</b></font>"
SDQL_print(i, text_list, print_nulls)
select_refs[REF(i)] = TRUE
SDQL2_TICK_CHECK
@@ -675,7 +678,7 @@ GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/SDQL2_VV_all, new(null
if("set" in query_tree)
var/list/set_list = query_tree["set"]
for(var/d in found)
if(!is_proper_datum(d))
if(!is_object_datatype(d))
continue
SDQL_internal_vv(d, set_list)
obj_count_finished++
@@ -685,47 +688,72 @@ GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/SDQL2_VV_all, new(null
obj_count_finished = length(obj_count_finished)
state = SDQL2_STATE_SWITCHING
/datum/SDQL2_query/proc/SDQL_print(object, list/text_list, print_nulls = TRUE)
if(is_proper_datum(object))
text_list += "<A HREF='?_src_=vars;[HrefToken(TRUE)];Vars=[REF(object)]'>[REF(object)]</A> : [object]"
if(istype(object, /atom))
var/atom/A = object
var/turf/T = A.loc
var/area/a
if(istype(T))
text_list += " <font color='gray'>at</font> [T] [ADMIN_COORDJMP(T)]"
a = T.loc
else
var/turf/final = get_turf(T) //Recursive, hopefully?
if(istype(final))
text_list += " <font color='gray'>at</font> [final] [ADMIN_COORDJMP(final)]"
a = final.loc
/**
* Recursively prints out an object to text list for SDQL2 output to admins, with VV links and all.
* Recursion limit: 50
* Limit imposed by callers should be around 10000 objects
* Seriously, if you hit those limits, you're doing something wrong.
*/
/datum/SDQL2_query/proc/SDQL_print(datum/object, list/text_list, print_nulls = TRUE, recursion = 1, linebreak = TRUE)
if(recursion > 50)
text_list += "<br><font color='red'><b>RECURSION LIMIT REACHED.</font></b><br>"
return
if(is_object_datatype(object))
if(!islist(object))
text_list += "<A HREF='?_src_=vars;[HrefToken(TRUE)];Vars=[REF(object)]'>[object.type] [REF(object)]</A>: [object]"
if(istype(object, /atom))
if(istype(object, /turf))
var/turf/T = object
text_list += " [ADMIN_COORDJMP(T)] <font color='gray'>at</font> [T.loc]"
else
text_list += " <font color='gray'>at</font> nonexistant location"
if(a)
text_list += " <font color='gray'>in</font> area [a]"
if(T.loc != a)
text_list += " <font color='gray'>inside</font> [T]"
text_list += "<br>"
else if(islist(object))
var/list/L = object
var/first = TRUE
text_list += "\["
for (var/x in L)
if (!first)
text_list += ", "
first = FALSE
SDQL_print(x, text_list)
if (!isnull(x) && !isnum(x) && L[x] != null)
text_list += " -> "
SDQL_print(L[L[x]], text_list)
text_list += "]<br>"
var/atom/A = object
var/atom/container = A.loc
if(isturf(container))
text_list += " <font color='gray'>in</font> [container] [ADMIN_COORDJMP(container)] <font color='gray'>at</font> [container.loc]"
else if(container)
var/turf/T = get_turf(container)
var/cref = REF(container)
text_list += " <font color='gray'>in</font> <A HREF='?_src_=vars;[HrefToken(TRUE)];Vars=[cref]'>[container]([cref])</A>"
if(T)
text_list += " <font color='gray'>on</font> [T] [ADMIN_COORDJMP(T)] <font color='gray'>at</font>[T.loc]"
else
text_list += " <font color='gray'>in</font> nullspace"
else // lists are snowflake and get special treatment.
text_list += "<A HREF='?_src_=vars;[HrefToken(TRUE)];Vars=[REF(object)]'>/list [REF(object)]</A> \[<br>"
var/list/L = object
if(length(L))
for(var/key in object)
if(islist(key))
text_list += "<span style='margin-left: [min(10, recursion) * 2]em;'>"
SDQL_print(key, text_list, TRUE, recursion + 1, FALSE)
text_list += "</span>"
else
SDQL_print(key, text_list, TRUE, recursion, FALSE)
if(IS_VALID_ASSOC_KEY(key) && !isnull(L[key]))
var/value = L[key]
text_list += " --> "
if(islist(value))
text_list += "<span style='margin-left: [min(10, recursion) * 2]em;'>"
SDQL_print(value, text_list, TRUE, recursion + 1, FALSE)
text_list += "</span>"
else
SDQL_print(value, text_list, TRUE, recursion, FALSE)
text_list += "<br>"
text_list += "\]"
if(linebreak)
text_list += "<br>"
else
if(isnull(object))
if(print_nulls)
text_list += "NULL<br>"
text_list += "NULL"
else if(istext(object))
text_list += "\"[object]\""
else if(isnum(object) || ispath(object))
text_list += "[object]"
else
text_list += "[object]<br>"
text_list += "UNKNOWN: [object]"
if(linebreak)
text_list += "<br>"
/datum/SDQL2_query/CanProcCall()
if(!allow_admin_interact)
@@ -957,7 +985,7 @@ GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/SDQL2_VV_all, new(null
var/static/list/exclude = list("usr", "src", "marked", "global")
var/long = start < expression.len
var/datum/D
if(is_proper_datum(object))
if(is_object_datatype(object))
D = object
if (object == world && (!long || expression[start + 1] == ".") && !(expression[start] in exclude)) //3 == length("SS") + 1
@@ -1025,8 +1053,6 @@ GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/SDQL2_VV_all, new(null
v = SSnpcpool
if("SSmobs")
v = SSmobs
if("SSmood")
v = SSmood
if("SSquirks")
v = SSquirks
if("SSwet_floors")
@@ -1163,9 +1189,6 @@ GLOBAL_DATUM_INIT(sdql2_vv_statobj, /obj/effect/statclick/SDQL2_VV_all, new(null
query_list += word
return query_list
/proc/is_proper_datum(thing)
return istype(thing, /datum) || istype(thing, /client)
/obj/effect/statclick/SDQL2_delete/Click()
var/datum/SDQL2_query/Q = target
Q.delete_click()
@@ -234,3 +234,7 @@
for(var/turf/T in v)
. += T
return pick(.)
/proc/__nan()
var/list/L = json_decode("{\"value\":NaN}")
return L["value"]
+52
View File
@@ -1218,6 +1218,58 @@ GLOBAL_LIST_EMPTY(custom_outfits) //Admin created outfits
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggled Hub Visibility", "[GLOB.hub_visibility ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
/client/proc/cmd_admin_toggle_fov()
set category = "Fun"
set name = "Enable/Disable Field of Vision"
var/static/busy_toggling_fov = FALSE
if(!check_rights(R_ADMIN) || !check_rights(R_FUN))
return
var/on_off = CONFIG_GET(flag/use_field_of_vision)
if(busy_toggling_fov)
to_chat(usr, "<span class='warning'>A previous call of this function is still busy toggling FoV [on_off ? "on" : "off"]. Have some patiece</span>.")
return
busy_toggling_fov = TRUE
log_admin("[key_name(usr)] has [on_off ? "disabled" : "enabled"] the Field of Vision configuration.")
CONFIG_SET(flag/use_field_of_vision, !on_off)
SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggled Field of Vision", "[on_off ? "Enabled" : "Disabled"]"))
if(on_off)
for(var/k in GLOB.mob_list)
if(!k)
continue
var/mob/M = k
if(ishuman(M))
var/mob/living/carbon/human/H = M
if(!(H.dna?.species.has_field_of_vision))
continue
else if(!M.has_field_of_vision)
continue
var/datum/component/field_of_vision/FoV = M.GetComponent(/datum/component/field_of_vision)
if(FoV)
qdel(FoV)
CHECK_TICK
else
for(var/k in GLOB.clients)
if(!k)
continue
var/client/C = k
var/mob/M = C.mob
if(ishuman(M))
var/mob/living/carbon/human/H = M
if(!(H.dna?.species.has_field_of_vision))
continue
else if(!M.has_field_of_vision)
continue
M.LoadComponent(/datum/component/field_of_vision, M.field_of_vision_type)
CHECK_TICK
busy_toggling_fov = FALSE
/client/proc/smite(mob/living/carbon/human/target as mob)
set name = "Smite"
set category = "Fun"
@@ -1,4 +1,5 @@
#define VV_HTML_ENCODE(thing) ( sanitize ? html_encode(thing) : thing )
/// Get displayed variable in VV variable list
/proc/debug_variable(name, value, level, datum/D, sanitize = TRUE) //if D is a list, name will be index, and value will be assoc value.
var/header
if(D)
@@ -15,11 +16,19 @@
header = "<li>"
var/item
var/name_part = VV_HTML_ENCODE(name)
if(level > 0 || islist(D)) //handling keys in assoc lists
if(istype(name,/datum))
name_part = "<a href='?_src_=vars;[HrefToken()];Vars=[REF(name)]'>[VV_HTML_ENCODE(name)] [REF(name)]</a>"
else if(islist(name))
var/list/L = name
name_part = "<a href='?_src_=vars;[HrefToken()];Vars=[REF(name)]'> /list ([length(L)]) [REF(name)]</a>"
if (isnull(value))
item = "[VV_HTML_ENCODE(name)] = <span class='value'>null</span>"
item = "[name_part] = <span class='value'>null</span>"
else if (istext(value))
item = "[VV_HTML_ENCODE(name)] = <span class='value'>\"[VV_HTML_ENCODE(value)]\"</span>"
item = "[name_part] = <span class='value'>\"[VV_HTML_ENCODE(value)]\"</span>"
else if (isicon(value))
#ifdef VARSICON
@@ -27,20 +36,31 @@
var/rnd = rand(1,10000)
var/rname = "tmp[REF(I)][rnd].png"
usr << browse_rsc(I, rname)
item = "[VV_HTML_ENCODE(name)] = (<span class='value'>[value]</span>) <img class=icon src=\"[rname]\">"
item = "[name_part] = (<span class='value'>[value]</span>) <img class=icon src=\"[rname]\">"
#else
item = "[VV_HTML_ENCODE(name)] = /icon (<span class='value'>[value]</span>)"
item = "[name_part] = /icon (<span class='value'>[value]</span>)"
#endif
else if (isfile(value))
item = "[VV_HTML_ENCODE(name)] = <span class='value'>'[value]'</span>"
item = "[name_part] = <span class='value'>'[value]'</span>"
else if(istype(value,/matrix)) // Needs to be before datum
var/matrix/M = value
item = {"[name_part] = <span class='value'>
<table class='matrixbrak'><tbody><tr><td class='lbrak'>&nbsp;</td><td>
<table class='matrix'>
<tbody>
<tr><td>[M.a]</td><td>[M.d]</td><td>0</td></tr>
<tr><td>[M.b]</td><td>[M.e]</td><td>0</td></tr>
<tr><td>[M.c]</td><td>[M.f]</td><td>1</td></tr>
</tbody>
</table></td><td class='rbrak'>&nbsp;</td></tr></tbody></table></span>"} //TODO link to modify_transform wrapper for all matrices
else if (istype(value, /datum))
var/datum/DV = value
if ("[DV]" != "[DV.type]") //if the thing as a name var, lets use it.
item = "<a href='?_src_=vars;[HrefToken()];Vars=[REF(value)]'>[VV_HTML_ENCODE(name)] [REF(value)]</a> = [DV] [DV.type]"
item = "[name_part] = <a href='?_src_=vars;[HrefToken()];Vars=[REF(value)]'>[DV] [DV.type] [REF(value)]</a>"
else
item = "<a href='?_src_=vars;[HrefToken()];Vars=[REF(value)]'>[VV_HTML_ENCODE(name)] [REF(value)]</a> = [DV.type]"
item = "[name_part] = <a href='?_src_=vars;[HrefToken()];Vars=[REF(value)]'>[DV.type] [REF(value)]</a>"
else if (islist(value))
var/list/L = value
@@ -58,19 +78,19 @@
items += debug_variable(key, val, level + 1, sanitize = sanitize)
item = "<a href='?_src_=vars;[HrefToken()];Vars=[REF(value)]'>[VV_HTML_ENCODE(name)] = /list ([L.len])</a><ul>[items.Join()]</ul>"
item = "[name_part] = <a href='?_src_=vars;[HrefToken()];Vars=[REF(value)]'>/list ([L.len])</a><ul>[items.Join()]</ul>"
else
item = "<a href='?_src_=vars;[HrefToken()];Vars=[REF(value)]'>[VV_HTML_ENCODE(name)] = /list ([L.len])</a>"
item = "[name_part] = <a href='?_src_=vars;[HrefToken()];Vars=[REF(value)]'>/list ([L.len])</a>"
else if (name in GLOB.bitfields)
var/list/flags = list()
for (var/i in GLOB.bitfields[name])
if (value & GLOB.bitfields[name][i])
flags += i
item = "[VV_HTML_ENCODE(name)] = [VV_HTML_ENCODE(jointext(flags, ", "))]"
item = "[name_part] = [VV_HTML_ENCODE(jointext(flags, ", "))]"
else
item = "[VV_HTML_ENCODE(name)] = <span class='value'>[VV_HTML_ENCODE(value)]</span>"
item = "[name_part] = <span class='value'>[VV_HTML_ENCODE(value)]</span>"
return "[header][item]</li>"
#undef VV_HTML_ENCODE
#undef VV_HTML_ENCODE
@@ -96,16 +96,7 @@
<head>
<meta http-equiv='Content-Type' content='text/html; charset=UTF-8'>
<title>[title]</title>
<style>
body {
font-family: Verdana, sans-serif;
font-size: 9pt;
}
.value {
font-family: "Courier New", monospace;
font-size: 8pt;
}
</style>
<link rel="stylesheet" type="text/css" href="view_variables.css">
</head>
<body onload='selectTextField()' onkeydown='return handle_keydown()' onkeyup='handle_keyup()'>
<script type="text/javascript">
@@ -24,6 +24,8 @@ GLOBAL_LIST_EMPTY(antagonists)
var/list/blacklisted_quirks = list(/datum/quirk/nonviolent,/datum/quirk/mute) // Quirks that will be removed upon gaining this antag. Pacifist and mute are default.
var/threat = 0 // Amount of threat this antag poses, for dynamic mode
var/list/skill_modifiers
/datum/antagonist/New()
GLOB.antagonists += src
typecache_datum_blacklist = typecacheof(typecache_datum_blacklist)
@@ -62,21 +64,40 @@ GLOBAL_LIST_EMPTY(antagonists)
/datum/antagonist/proc/remove_innate_effects(mob/living/mob_override)
return
// Adds the specified antag hud to the player. Usually called in an antag datum file
/datum/antagonist/proc/add_antag_hud(antag_hud_type, antag_hud_name, mob/living/mob_override)
var/datum/atom_hud/antag/hud = GLOB.huds[antag_hud_type]
hud.join_hud(mob_override)
set_antag_hud(mob_override, antag_hud_name)
// Removes the specified antag hud from the player. Usually called in an antag datum file
/datum/antagonist/proc/remove_antag_hud(antag_hud_type, mob/living/mob_override)
var/datum/atom_hud/antag/hud = GLOB.huds[antag_hud_type]
hud.leave_hud(mob_override)
set_antag_hud(mob_override, null)
//Assign default team and creates one for one of a kind team antagonists
/datum/antagonist/proc/create_team(datum/team/team)
return
//Proc called when the datum is given to a mind.
/datum/antagonist/proc/on_gain()
if(owner && owner.current)
if(!silent)
greet()
apply_innate_effects()
give_antag_moodies()
remove_blacklisted_quirks()
if(is_banned(owner.current) && replace_banned)
replace_banned_player()
SEND_SIGNAL(owner.current, COMSIG_MOB_ANTAG_ON_GAIN, src)
if(!(owner?.current))
return
if(!silent)
greet()
apply_innate_effects()
give_antag_moodies()
remove_blacklisted_quirks()
if(is_banned(owner.current) && replace_banned)
replace_banned_player()
if(skill_modifiers)
for(var/A in skill_modifiers)
ADD_SINGLETON_SKILL_MODIFIER(owner, A, type)
var/datum/skill_modifier/job/M = GLOB.skill_modifiers[GET_SKILL_MOD_ID(A, type)]
if(istype(M))
M.name = "[name] Training"
SEND_SIGNAL(owner.current, COMSIG_MOB_ANTAG_ON_GAIN, src)
/datum/antagonist/proc/is_banned(mob/M)
if(!M)
@@ -99,6 +120,8 @@ GLOBAL_LIST_EMPTY(antagonists)
clear_antag_moodies()
if(owner)
LAZYREMOVE(owner.antag_datums, src)
for(var/A in skill_modifiers)
owner.remove_skill_modifier(GET_SKILL_MOD_ID(A, type))
if(!silent && owner.current)
farewell()
var/datum/team/team = get_team()
@@ -21,6 +21,7 @@
landmark_type = /obj/effect/landmark/abductor/agent
greet_text = "Use your stealth technology and equipment to incapacitate humans for your scientist to retrieve."
show_in_antagpanel = TRUE
skill_modifiers = list(/datum/skill_modifier/job/level/wiring)
/datum/antagonist/abductor/scientist
name = "Abductor Scientist"
@@ -29,6 +30,7 @@
landmark_type = /obj/effect/landmark/abductor/scientist
greet_text = "Use your experimental console and surgical equipment to monitor your agent and experiment upon abducted humans."
show_in_antagpanel = TRUE
skill_modifiers = list(/datum/skill_modifier/job/affinity/surgery)
/datum/antagonist/abductor/create_team(datum/team/abductor_team/new_team)
if(!new_team)
@@ -23,5 +23,5 @@
addtimer(CALLBACK(src, .proc/zap), rand(30, 100))
/obj/item/organ/heart/gland/electric/proc/zap()
tesla_zap(owner, 4, 8000, TESLA_MOB_DAMAGE | TESLA_OBJ_DAMAGE | TESLA_MOB_STUN)
tesla_zap(owner, 4, 8000, ZAP_MOB_DAMAGE | ZAP_OBJ_DAMAGE | ZAP_MOB_STUN)
playsound(get_turf(owner), 'sound/magic/lightningshock.ogg', 50, TRUE)
@@ -0,0 +1,12 @@
/obj/structure/fluff/iced_abductor ///Unless more non-machine ayy structures made, it will stay in fluff.
name = "Mysterious Block of Ice"
desc = "A shadowy figure lies in this sturdy-looking block of ice. Who knows where it came from?"
icon = 'icons/effects/freeze.dmi'
icon_state = "ice_ayy"
density = TRUE
deconstructible = FALSE
/obj/structure/fluff/iced_abductor/Destroy()
var/turf/T = get_turf(src)
new /obj/effect/mob_spawn/human/abductor(T)
. = ..()
@@ -86,7 +86,8 @@
melee_damage_upper = 4
obj_damage = 20
environment_smash = ENVIRONMENT_SMASH_STRUCTURES
attacktext = "hits"
attack_verb_continuous = "hits"
attack_verb_simple = "hit"
attack_sound = 'sound/weapons/genhit1.ogg'
movement_type = FLYING
del_on_death = 1
@@ -102,7 +103,9 @@
factory.spores += src
. = ..()
/mob/living/simple_animal/hostile/blob/blobspore/Life()
/mob/living/simple_animal/hostile/blob/blobspore/BiologicalLife(seconds, times_fired)
if(!(. = ..()))
return
if(!is_zombie && isturf(src.loc))
for(var/mob/living/carbon/human/H in view(src,1)) //Only for corpse right next to/on same tile
if(H.stat == DEAD)
@@ -110,7 +113,6 @@
break
if(factory && z != factory.z)
death()
..()
/mob/living/simple_animal/hostile/blob/blobspore/proc/Zombify(mob/living/carbon/human/H)
is_zombie = 1
@@ -205,7 +207,8 @@
melee_damage_lower = 20
melee_damage_upper = 20
obj_damage = 60
attacktext = "slams"
attack_verb_continuous = "slams"
attack_verb_simple = "slam"
attack_sound = 'sound/effects/blobattack.ogg'
verb_say = "gurgles"
verb_ask = "demands"
@@ -231,39 +234,40 @@
return FALSE
return ..()
/mob/living/simple_animal/hostile/blob/blobbernaut/Life()
if(..())
var/list/blobs_in_area = range(2, src)
if(independent)
return // strong independent blobbernaut that don't need no blob
var/damagesources = 0
if(!(locate(/obj/structure/blob) in blobs_in_area))
damagesources++
if(!factory)
damagesources++
else
if(locate(/obj/structure/blob/core) in blobs_in_area)
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.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.blobstrain.complementary_color
else
H.color = "#000000"
if(damagesources)
for(var/i in 1 to damagesources)
adjustHealth(maxHealth*0.025) //take 2.5% of max health as damage when not near the blob or if the naut has no factory, 5% if both
var/image/I = new('icons/mob/blob.dmi', src, "nautdamage", MOB_LAYER+0.01)
I.appearance_flags = RESET_COLOR
/mob/living/simple_animal/hostile/blob/blobbernaut/BiologicalLife(seconds, times_fired)
if(!(. = ..()))
return
var/list/blobs_in_area = range(2, src)
if(independent)
return // strong independent blobbernaut that don't need no blob
var/damagesources = 0
if(!(locate(/obj/structure/blob) in blobs_in_area))
damagesources++
if(!factory)
damagesources++
else
if(locate(/obj/structure/blob/core) in blobs_in_area)
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)
I.color = overmind.blobstrain.complementary_color
flick_overlay_view(I, src, 8)
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.blobstrain.complementary_color
else
H.color = "#000000"
if(damagesources)
for(var/i in 1 to damagesources)
adjustHealth(maxHealth*0.025) //take 2.5% of max health as damage when not near the blob or if the naut has no factory, 5% if both
var/image/I = new('icons/mob/blob.dmi', src, "nautdamage", MOB_LAYER+0.01)
I.appearance_flags = RESET_COLOR
if(overmind)
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)
. = ..()
@@ -284,11 +288,11 @@
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.blobstrain.blobbernaut_message
attack_verb_continuous = overmind.blobstrain.blobbernaut_message
else
melee_damage_lower = initial(melee_damage_lower)
melee_damage_upper = initial(melee_damage_upper)
attacktext = initial(attacktext)
attack_verb_continuous = initial(attack_verb_continuous)
/mob/living/simple_animal/hostile/blob/blobbernaut/death(gibbed)
..(gibbed)
@@ -45,20 +45,11 @@
desc = "A solid wall of slightly twitching tendrils with a reflective glow."
damaged_desc = "A wall of twitching tendrils with a reflective glow."
icon_state = "blob_glow"
flags_1 = CHECK_RICOCHET_1
point_return = 8
max_integrity = 100
brute_resist = 1
explosion_block = 2
/obj/structure/blob/shield/reflective/handle_ricochet(obj/item/projectile/P)
var/turf/p_turf = get_turf(P)
var/face_direction = get_dir(src, p_turf)
var/face_angle = dir2angle(face_direction)
var/incidence_s = GET_ANGLE_OF_INCIDENCE(face_angle, (P.Angle + 180))
if(abs(incidence_s) > 90 && abs(incidence_s) < 270)
return FALSE
var/new_angle_s = SIMPLIFY_DEGREES(face_angle + incidence_s)
P.setAngle(new_angle_s)
visible_message("<span class='warning'>[P] reflects off [src]!</span>")
return TRUE
/obj/structure/blob/shield/reflective/check_projectile_ricochet(obj/item/projectile/P)
return PROJECTILE_RICOCHET_FORCE
@@ -207,8 +207,8 @@
if(prob(100 - severity * 30))
new /obj/effect/temp_visual/emp(get_turf(src))
/obj/structure/blob/tesla_act(power)
..()
/obj/structure/blob/zap_act(power)
. = ..()
if(overmind)
if(overmind.blobstrain.tesla_reaction(src, power))
take_damage(power/400, BURN, "energy")
@@ -113,3 +113,5 @@
/mob/living/proc/StartFrenzy(inTime = 120)
set waitfor = FALSE
@@ -177,7 +177,7 @@
if(owner.current.blood_volume < BLOOD_VOLUME_BAD / 2)
owner.current.blur_eyes(8 - 8 * (owner.current.blood_volume / BLOOD_VOLUME_BAD))
// Nutrition
owner.current.set_nutrition(min(owner.current.blood_volume, NUTRITION_LEVEL_FULL)) //The amount of blood is how full we are.
owner.current.set_nutrition(min(owner.current.blood_volume, NUTRITION_LEVEL_FED)) //The amount of blood is how full we are.
//A bit higher regeneration based on blood volume
if(owner.current.blood_volume < 700)
additional_regen = 0.4
@@ -216,8 +216,8 @@
// for (var/datum/action/bloodsucker/masquerade/P in powers)
// P.Deactivate()
// TEMP DEATH
var/total_brute = owner.current.getBruteLoss()
var/total_burn = owner.current.getFireLoss()
var/total_brute = owner.current.getBruteLoss_nonProsthetic()
var/total_burn = owner.current.getFireLoss_nonProsthetic()
var/total_toxloss = owner.current.getToxLoss() //This is neater than just putting it in total_damage
var/total_damage = total_brute + total_burn + total_toxloss
// Died? Convert to Torpor (fake death)
@@ -39,7 +39,7 @@
var/passive_blood_drain = -0.1 //The amount of blood we loose each bloodsucker life() tick
// LISTS
var/static/list/defaultTraits = list (TRAIT_STABLEHEART, TRAIT_NOBREATH, TRAIT_SLEEPIMMUNE, TRAIT_NOCRITDAMAGE, TRAIT_RESISTCOLD, TRAIT_RADIMMUNE, TRAIT_NIGHT_VISION, \
TRAIT_NOSOFTCRIT, TRAIT_NOHARDCRIT, TRAIT_AGEUSIA, TRAIT_COLDBLOODED, TRAIT_NONATURALHEAL, TRAIT_NOMARROW, TRAIT_NOPULSE, TRAIT_VIRUSIMMUNE)
TRAIT_NOSOFTCRIT, TRAIT_NOHARDCRIT, TRAIT_AGEUSIA, TRAIT_COLDBLOODED, TRAIT_NONATURALHEAL, TRAIT_NOMARROW, TRAIT_NOPULSE, TRAIT_VIRUSIMMUNE, TRAIT_NODECAP, TRAIT_NOGUT)
/datum/antagonist/bloodsucker/on_gain()
SSticker.mode.bloodsuckers |= owner // Add if not already in here (and you might be, if you were picked at round start)
@@ -31,10 +31,6 @@
beating = 0
var/fakingit = 0
/obj/item/organ/heart/vampheart/prepare_eat()
..()
// Do cool stuff for eating vamp heart?
/obj/item/organ/heart/vampheart/Restart()
beating = 0 // DONT run ..(). We don't want to start beating again.
return 0
@@ -30,7 +30,7 @@
hitsound = 'sound/weapons/bladeslice.ogg'
force = 6
throwforce = 10
embedding = list("embed_chance" = 25, "embedded_fall_chance" = 0.5) // UPDATE 2/10/18 embedding_behavior.dm is how this is handled
embedding = list("embed_chance" = 25, "fall_chance" = 0.5) // UPDATE 2/10/18 embedding_behavior.dm is how this is handled
//embed_chance = 25 // Look up "is_pointed" to see where we set stakes able to do this.
//embedded_fall_chance = 0.5 // Chance it will fall out.
obj_integrity = 30
@@ -93,7 +93,7 @@
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)
B.receive_damage(w_class * embedding["pain_mult"])
if(C.mind)
var/datum/antagonist/bloodsucker/bloodsucker = C.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
if(bloodsucker)
@@ -118,7 +118,7 @@
force = 8
throwforce = 12
armour_penetration = 10
embedding = list("embed_chance" = 50, "embedded_fall_chance" = 0) // UPDATE 2/10/18 embedding_behavior.dm is how this is handled
embedding = list("embed_chance" = 50, "fall_chance" = 0) // UPDATE 2/10/18 embedding_behavior.dm is how this is handled
obj_integrity = 120
max_integrity = 120
@@ -167,4 +167,4 @@
///obj/item/pipe = 2)
time = 80
category = CAT_WEAPONRY
subcategory = CAT_WEAPON
subcategory = CAT_MELEE
@@ -328,8 +328,8 @@
// to_chat(user, "<span class='danger'><i>The ritual has been interrupted!</i></span>")
// useLock = FALSE
// return
user.playsound_local(null, 'sound/effects/explosion_distant.ogg', 40, TRUE)
target.playsound_local(null, 'sound/effects/explosion_distant.ogg', 40, TRUE)
user.playsound_local(null, 'sound/effects/explosion_distant.ogg', 40, TRUE)
target.playsound_local(null, 'sound/effects/explosion_distant.ogg', 40, TRUE)
target.playsound_local(null, 'sound/effects/singlebeat.ogg', 40, TRUE)
target.Jitter(25)
target.emote("laugh")
@@ -490,13 +490,14 @@
update_icon()
/obj/structure/bloodsucker/candelabrum/process()
if(lit)
for(var/mob/living/carbon/human/H in viewers(7, src))
var/datum/antagonist/vassal/T = H.mind.has_antag_datum(ANTAG_DATUM_VASSAL)
if(AmBloodsucker(H) || T) //We dont want vassals or vampires affected by this
return
H.hallucination = 20
SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "vampcandle", /datum/mood_event/vampcandle)
if(!lit)
return
for(var/mob/living/carbon/human/H in fov_viewers(7, src))
var/datum/antagonist/vassal/T = H.mind.has_antag_datum(ANTAG_DATUM_VASSAL)
if(AmBloodsucker(H) || T) //We dont want vassals or vampires affected by this
return
H.hallucination = 20
SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "vampcandle", /datum/mood_event/vampcandle)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// OTHER THINGS TO USE: HUMAN BLOOD. /obj/effect/decal/cleanable/blood
@@ -19,10 +19,9 @@
if(!.)
return
// must have nobody around to see the cloak
for(var/mob/living/M in viewers(9, owner))
if(M != owner)
to_chat(owner, "<span class='warning'>You may only vanish into the shadows unseen.</span>")
return FALSE
for(var/mob/living/M in fov_viewers(9, owner) - 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()
@@ -35,7 +34,7 @@
// Pay Blood Toll (if awake)
owner.alpha = max(35, owner.alpha - min(75, 10 + 5 * level_current))
bloodsuckerdatum.AddBloodVolume(-0.2)
runintent = (user.m_intent == MOVE_INTENT_RUN)
var/turf/T = get_turf(user)
lum = T.get_lumcount()
@@ -50,7 +49,7 @@
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)
@@ -43,10 +43,14 @@
to_chat(owner, "<span class='warning'>Lesser beings require a tighter grip.</span>")
return FALSE
// Bloodsuckers:
else if(iscarbon(target) && target.mind && target.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER))
if(display_error)
to_chat(owner, "<span class='warning'>Other Bloodsuckers will not fall for your subtle approach.</span>")
return FALSE
else if(ishuman(target))
var/mob/living/carbon/human/H = target
if(!H.can_inject(owner, TRUE, BODY_ZONE_CHEST))
return FALSE
if(target.mind && target.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER))
if(display_error)
to_chat(owner, "<span class='warning'>Other Bloodsuckers will not fall for your subtle approach.</span>")
return FALSE
// Must have Target
if(!target) // || !ismob(target)
if(display_error)
@@ -63,6 +67,8 @@
return FALSE
if(ishuman(target))
var/mob/living/carbon/human/H = target
if(!H.can_inject(owner, TRUE, BODY_ZONE_HEAD) && target == owner.pulling && owner.grab_state < GRAB_AGGRESSIVE)
return FALSE
if(NOBLOOD in H.dna.species.species_traits)// || owner.get_blood_id() != target.get_blood_id())
if(display_error)
to_chat(owner, "<span class='warning'>Your victim's blood is not suitable for you to take.</span>")
@@ -169,8 +175,8 @@
vision_distance = notice_range, ignored_mobs = target) // Only people who AREN'T the target will notice this action.
// 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.silicon_privileges && !M.eye_blind && !M.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER))
for(var/mob/living/M in fov_viewers(notice_range, owner) - owner - target)
if(M.client && !M.silicon_privileges && !M.eye_blind && !M.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER))
was_unnoticed = FALSE
break
if(was_unnoticed)
@@ -64,8 +64,8 @@
var/turf/T = get_turf(user)
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.silicon_privileges && !M.eye_blind) // M.client <--- add this in after testing!
for(var/mob/living/M in fov_viewers(world.view, get_turf(owner)) - owner)
if(M.client && !M.silicon_privileges && !M.eye_blind)
am_seen = TRUE
if (!M.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER))
drop_item = TRUE
@@ -64,7 +64,7 @@
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(!(target in viewers(target_range, get_turf(owner))))
if(!(owner in target.fov_view()))
// Sub-Check: GET CLOSER
//if (!(owner in range(target_range, get_turf(target)))
// if (display_error)
@@ -117,8 +117,6 @@
RegisterSignal(target, COMSIG_MOVABLE_MOVED, .proc/ContinueTarget)
// 5 second windup
addtimer(CALLBACK(src, .proc/apply_effects, L, target, power_time), 6 SECONDS)
ADD_TRAIT(target, TRAIT_COMBAT_MODE_LOCKED, src)
ADD_TRAIT(L, TRAIT_COMBAT_MODE_LOCKED, src)
/datum/action/bloodsucker/targeted/mesmerize/proc/apply_effects(aggressor, victim, power_time)
var/mob/living/carbon/target = victim
@@ -127,17 +125,15 @@
return
PowerActivatedSuccessfully() // blood & cooldown only altered if power activated successfully - less "fuck you"-y
target.apply_status_effect(STATUS_EFFECT_MESMERIZE, power_time)
REMOVE_TRAIT(L, TRAIT_COMBAT_MODE_LOCKED, src)
target.face_atom(L)
target.Stun(power_time)
to_chat(L, "<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.
target.mob_transforming = 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
REMOVE_TRAIT(target, TRAIT_COMBAT_MODE_LOCKED, src)
if(istype(L) && target.stat == CONSCIOUS && (target in view(10, get_turf(L)))) // They Woke Up! (Notice if within view)
target.mob_transforming = FALSE
if(istype(L) && target.stat == CONSCIOUS && (target in L.fov_view(10))) // They Woke Up! (Notice if within view)
to_chat(L, "<span class='warning'>[target] has snapped out of their trance.</span>")
@@ -20,7 +20,7 @@
. = ..()
if(!.)
return
if(owner.notransform || !get_turf(owner))
if(owner.mob_transforming || !get_turf(owner))
return FALSE
return TRUE
@@ -83,7 +83,7 @@
// Freeze Me
user.next_move = world.time + mist_delay
user.Stun(mist_delay, ignore_canstun = TRUE)
user.notransform = TRUE
user.mob_transforming = TRUE
user.density = FALSE
var/invis_was = user.invisibility
user.invisibility = INVISIBILITY_MAXIMUM
@@ -106,7 +106,7 @@
user.dir = get_dir(my_turf, target_turf)
user.next_move = world.time + mist_delay / 2
user.Stun(mist_delay / 2, ignore_canstun = TRUE)
user.notransform = FALSE
user.mob_transforming = FALSE
user.density = 1
user.invisibility = invis_was
@@ -101,7 +101,7 @@
H.update_hair()
H.update_body_parts()
// Wait here til we deactivate power or go unconscious
// Wait here until we deactivate power or go unconscious
var/datum/antagonist/bloodsucker/bloodsuckerdatum = owner.mind.has_antag_datum(ANTAG_DATUM_BLOODSUCKER)
while (ContinueActive(owner) && istype(bloodsuckerdatum))//active && owner && owner.stat == CONSCIOUS)
bloodsuckerdatum.AddBloodVolume(-0.2)
@@ -91,6 +91,7 @@
B.organ_flags |= ORGAN_VITAL
B.decoy_override = FALSE
remove_changeling_powers()
owner.special_role = null
. = ..()
/datum/antagonist/changeling/proc/remove_clownmut()
@@ -396,20 +397,31 @@
escape_objective_possible = FALSE
break
var/changeling_objective = rand(1,3)
var/generic_absorb_objective = FALSE
var/multiple_lings = length(get_antag_minds(/datum/antagonist/changeling,TRUE)) > 1
switch(changeling_objective)
if(1)
var/datum/objective/absorb/absorb_objective = new
absorb_objective.owner = owner
absorb_objective.gen_amount_goal(6, 8)
objectives += absorb_objective
generic_absorb_objective = TRUE
if(2)
var/datum/objective/absorb_changeling/ac = new
ac.owner = owner
objectives += ac
if(multiple_lings)
var/datum/objective/absorb_changeling/ac = new
ac.owner = owner
objectives += ac
else
generic_absorb_objective = TRUE
if(3)
var/datum/objective/absorb_most/ac = new
ac.owner = owner
objectives += ac
if(multiple_lings)
var/datum/objective/absorb_most/ac = new
ac.owner = owner
objectives += ac
else
generic_absorb_objective = TRUE
if(generic_absorb_objective)
var/datum/objective/absorb/absorb_objective = new
absorb_objective.owner = owner
absorb_objective.gen_amount_goal(6, 8)
objectives += absorb_objective
if(prob(60))
if(prob(85))
@@ -21,7 +21,7 @@
var/datum/changelingprofile/chosen_prof = changeling.get_dna(chosen_name)
if(!chosen_prof)
return
if(!user || user.notransform)
if(!user || user.mob_transforming)
return 0
to_chat(user, "<span class='notice'>We transform our appearance.</span>")
@@ -11,9 +11,9 @@
//Transform into a monkey.
/obj/effect/proc_holder/changeling/lesserform/sting_action(mob/living/carbon/human/user)
if(!user || user.notransform)
if(!user || user.mob_transforming)
return 0
to_chat(user, "<span class='warning'>Our genes cry out!</span>")
user.monkeyize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSE)
return TRUE
return TRUE
@@ -432,15 +432,18 @@
/obj/item/shield/changeling
name = "shield-like mass"
desc = "A mass of tough, boney tissue. You can still see the fingers as a twisted pattern in the shield."
item_flags = ABSTRACT | DROPDEL
item_flags = ABSTRACT | DROPDEL | ITEM_CAN_BLOCK
icon = 'icons/obj/items_and_weapons.dmi'
icon_state = "ling_shield"
lefthand_file = 'icons/mob/inhands/antag/changeling_lefthand.dmi'
righthand_file = 'icons/mob/inhands/antag/changeling_righthand.dmi'
block_chance = 50
block_parry_data = /datum/block_parry_data/shield/changeling
var/remaining_uses //Set by the changeling ability.
/datum/block_parry_data/shield/changeling
block_slowdown = 0
/obj/item/shield/changeling/Initialize(mapload)
. = ..()
ADD_TRAIT(src, TRAIT_NODROP, CHANGELING_TRAIT)
@@ -451,7 +454,7 @@
block_return[BLOCK_RETURN_BLOCK_CAPACITY] = (block_return[BLOCK_RETURN_BLOCK_CAPACITY] || 0) + remaining_uses
return ..()
/obj/item/shield/changeling/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
/obj/item/shield/changeling/active_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
. = ..()
if(--remaining_uses < 1)
if(ishuman(loc))
@@ -1,7 +1,7 @@
/obj/effect/proc_holder/changeling/spiders
name = "Spread Infestation"
desc = "Our form divides, creating arachnids which will grow into deadly beasts."
helptext = "The spiders are thoughtless creatures, and may attack their creators when fully grown. Requires at least 3 DNA gained through Absorb, and not through DNA sting. This ability is very loud, and will guarantee that our blood will react violently to heat."
helptext = "The spiders are thoughtless creatures, and may attack their creators when fully grown. Requires at least 3 DNA gained through Absorb (regardless of current amount), and not through DNA sting. This ability is very loud, and will guarantee that our blood will react violently to heat."
chemical_cost = 45
dna_cost = 1
loudness = 4
@@ -5,6 +5,7 @@
clockwork_desc = "A sigil of some purpose."
icon_state = "sigil"
layer = LOW_OBJ_LAYER
plane = ABOVE_WALL_PLANE
alpha = 50
resistance_flags = NONE
var/affects_servants = FALSE
@@ -1,21 +1,31 @@
//horrifying power drain proc made for clockcult's power drain in lieu of six istypes or six for(x in view) loops
/atom/movable/proc/power_drain(clockcult_user)
/atom/movable/proc/power_drain(clockcult_user, drain_weapons = FALSE) //This proc as of now is only in use for void volt
var/obj/item/stock_parts/cell/cell = get_cell()
if(cell)
return cell.power_drain(clockcult_user)
return 0
return 0 //Returns 0 instead of FALSE to symbolise it returning the power amount in other cases, not TRUE aka 1
/obj/item/melee/baton/power_drain(clockcult_user) //balance memes
return 0
/obj/item/melee/baton/power_drain(clockcult_user, drain_weapons = FALSE) //balance memes
if(!drain_weapons)
return 0
return ..()
/obj/item/gun/power_drain(clockcult_user) //balance memes
return 0
/obj/item/gun/power_drain(clockcult_user, drain_weapons = FALSE) //balance memes
if(!drain_weapons)
return 0
var/obj/item/stock_parts/cell/cell = get_cell()
if(!cell)
return 0
if(cell.charge)
. = min(cell.charge, MIN_CLOCKCULT_POWER*4) //Done snowflakey because guns have far smaller cells than batons / other equipment
cell.use(.)
update_icon()
/obj/machinery/power/apc/power_drain(clockcult_user)
/obj/machinery/power/apc/power_drain(clockcult_user, drain_weapons = FALSE)
if(cell && cell.charge)
playsound(src, "sparks", 50, 1)
flick("apc-spark", src)
. = min(cell.charge, MIN_CLOCKCULT_POWER*3)
. = min(cell.charge, MIN_CLOCKCULT_POWER*4)
cell.use(.) //Better than a power sink!
if(!cell.charge && !shorted)
shorted = 1
@@ -23,9 +33,9 @@
update()
update_icon()
/obj/machinery/power/smes/power_drain(clockcult_user)
/obj/machinery/power/smes/power_drain(clockcult_user, drain_weapons = FALSE)
if(charge)
. = min(charge, MIN_CLOCKCULT_POWER*3)
. = min(charge, MIN_CLOCKCULT_POWER*4)
charge -= . * 50
if(!charge && !panel_open)
panel_open = TRUE
@@ -34,19 +44,19 @@
visible_message("<span class='warning'>[src]'s panel flies open with a flurry of sparks!</span>")
update_icon()
/obj/item/stock_parts/cell/power_drain(clockcult_user)
/obj/item/stock_parts/cell/power_drain(clockcult_user, drain_weapons = FALSE)
if(charge)
. = min(charge, MIN_CLOCKCULT_POWER*3)
charge = use(.)
. = min(charge, MIN_CLOCKCULT_POWER * 4) //Done like this because normal cells are usually quite a bit bigger than the ones used in guns / APCs
use(min(charge, . * 10)) //Usually cell-powered equipment that is not a gun has at least ten times the capacity of a gun / 5 times the amount of an APC. This adjusts the drain to account for that.
update_icon()
/mob/living/silicon/robot/power_drain(clockcult_user)
/mob/living/silicon/robot/power_drain(clockcult_user, drain_weapons = FALSE)
if((!clockcult_user || !is_servant_of_ratvar(src)) && cell && cell.charge)
. = min(cell.charge, MIN_CLOCKCULT_POWER*4)
cell.use(.)
spark_system.start()
/obj/mecha/power_drain(clockcult_user)
/obj/mecha/power_drain(clockcult_user, drain_weapons = FALSE)
if((!clockcult_user || (occupant && !is_servant_of_ratvar(occupant))) && cell && cell.charge)
. = min(cell.charge, MIN_CLOCKCULT_POWER*4)
cell.use(.)
@@ -135,6 +135,29 @@
return TRUE
//For the Volt Void scripture, fires a ray of energy at a target location
/obj/effect/proc_holder/slab/volt
ranged_mousepointer = 'icons/effects/volt_target.dmi'
/obj/effect/proc_holder/slab/volt/InterceptClickOn(mob/living/caller, params, atom/target)
if(target == slab || ..()) //we can't cancel
return TRUE
var/turf/T = ranged_ability_user.loc
if(!isturf(T))
return TRUE
if(target in view(7, get_turf(ranged_ability_user)))
successful = TRUE
ranged_ability_user.visible_message("<span class='warning'>[ranged_ability_user] fires a ray of energy at [target]!</span>", "<span class='nzcrentr'>You fire a volt ray at [target].</span>")
playsound(ranged_ability_user, 'sound/effects/light_flicker.ogg', 50, 1)
T = get_turf(target)
new/obj/effect/temp_visual/ratvar/volt_hit(T, ranged_ability_user)
log_combat(ranged_ability_user, T, "fired a volt ray")
remove_ranged_ability()
return TRUE
//For the Kindle scripture; stuns and mutes a target non-servant.
/obj/effect/proc_holder/slab/kindle
ranged_mousepointer = 'icons/effects/volt_target.dmi'
@@ -0,0 +1,82 @@
//Subtype of (riot) shield because of already implemented shieldbash stuff aswell as integrity and simillar things
//ratvarian shield: A shield that absorbs energy from attacks and uses it to empower its bashes with remendous force. It is also quite resistant to damage, though less so against lasers and energy weaponry.
/obj/item/shield/riot/ratvarian
name = "ratvarian shield"
icon_state = "ratvarian_shield" //Its icons are in the same place the normal shields are in
item_state = "ratvarian_shield"
desc = "A resilient shield made out of brass.. It feels warm to the touch."
var/clockwork_desc = "A powerful shield of ratvarian making. It absorbs blocked attacks to charge devastating bashes."
armor = list("melee" = 80, "bullet" = 70, "laser" = -10, "energy" = -20, "bomb" = 60, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 100)
shield_flags = SHIELD_FLAGS_DEFAULT
max_integrity = 300 //High integrity, extremely strong against melee / bullets, but still quite easy to destroy with lasers and energy
repair_material = /obj/item/stack/tile/brass
var/dam_absorbed = 0
var/bash_mult_steps = 30
var/max_bash_mult = 4
/obj/item/shield/riot/ratvarian/examine(mob/user)
if((is_servant_of_ratvar(user) || isobserver(user)))
desc = clockwork_desc
desc +="\n <span class='inathneq_small'>The shield has absorbed [dam_absorbed] damage, multiplying the effectiveness of its bashes by [calc_bash_mult()]</span>"
. = ..()
desc = initial(desc)
obj/item/shield/riot/ratvarian/proc/calc_bash_mult()
var/bash_mult = 0
if(!dam_absorbed)
return 1
else
bash_mult += round(clamp(1 + (dam_absorbed / bash_mult_steps), 1, max_bash_mult), 0.1) //Multiplies the effect of bashes by up to [max_bash_mult], though never less than one
return bash_mult
/obj/item/shield/riot/ratvarian/proc/calc_bash_absorb_use()
var/absorb_use = 0
absorb_use = max(0, round(dam_absorbed * (calc_bash_mult() / round(1 + (dam_absorbed / bash_mult_steps), 0.1)), 1)) //Calculates how much of the absorbed damage the bash would actually use, so its not wasted
return absorb_use
/obj/item/shield/riot/ratvarian/on_shield_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
if(!damage)
return ..()
if(!is_servant_of_ratvar(owner))
owner.visible_message("<span class='warning'>As [owner] blocks the attack with [src], [owner.p_they()] suddenly drops it, whincing in pain! </span>", "<span class='warning'>As you block the attack with [src], it heats up tremendously, forcing you to drop it from the pain alone! </span>")
owner.emote("scream")
playsound(src, 'sound/machines/fryer/deep_fryer_emerge.ogg', 50)
if(iscarbon(owner)) //Type safety for if a drone somehow got a shield (ratvar protect us)
var/mob/living/carbon/C = owner
var/obj/item/bodypart/part = C.get_holding_bodypart_of_item(src)
C.apply_damage((iscultist(C) ? damage * 2 : damage), BURN, (istype(part, /obj/item/bodypart/l_arm) ? BODY_ZONE_L_ARM : BODY_ZONE_R_ARM)) //Deals the damage to the holder instead of absorbing it instead + forcedrops. Doubled if a cultist of Nar'Sie.
else
owner.adjustFireLoss(iscultist(owner) ? damage * 2 : damage)
addtimer(CALLBACK(owner, /mob/living.proc/dropItemToGround, src, TRUE), 1)
else if(!is_servant_of_ratvar(attacker)) //No exploiting my snowflake mechanics
dam_absorbed += damage
playsound(owner, 'sound/machines/clockcult/steam_whoosh.ogg', 30)
if(damage <= 10) //The shield itself is hard to break, this DOES NOT modify the actual blocking-mechanic
damage = 0
else
damage -= 5
return ..()
/obj/item/shield/riot/ratvarian/shatter(mob/living/carbon/human/owner)
playsound(owner, 'sound/magic/clockwork/anima_fragment_death.ogg', 50)
new /obj/item/clockwork/alloy_shards/large(get_turf(src))
/obj/item/shield/riot/ratvarian/user_shieldbash(mob/living/user, atom/target, harmful)
if(!harmful || !is_servant_of_ratvar(user)) // No fun for non-clockies, but you can keep the normal bashes. Until you try to block with it.
shieldbash_knockback = initial(shieldbash_knockback)
shieldbash_brutedamage = initial(shieldbash_brutedamage) //Prevention for funky stuff that might happen otherwise
shieldbash_stamdmg = initial(shieldbash_stamdmg)
return ..()
var/actual_bash_mult = calc_bash_mult()
shieldbash_knockback = round(initial(shieldbash_knockback) * actual_bash_mult, 1) //Modifying the strength of the bash, done with initial() to prevent magic-number issues if the original shieldbash values are changed
shieldbash_brutedamage = round(initial(shieldbash_brutedamage) * actual_bash_mult, 1) //Where I think of it, better round this stuff because we don't need even more things that deal like 3.25 damage
shieldbash_stamdmg = round(initial(shieldbash_stamdmg) * actual_bash_mult, 1) //Like 20 brute and 60 stam + a fuckton of knockback at the moment (at maximum charge), seems mostly fine? I think?
. = ..()
if(.) //If this bash actually hit someone
if(actual_bash_mult > 1)
playsound(user, 'sound/magic/fireball.ogg', 50, TRUE, frequency = 1.25)
dam_absorbed -= calc_bash_absorb_use()
return
@@ -17,7 +17,8 @@
obj_damage = 40
melee_damage_lower = 12
melee_damage_upper = 12
attacktext = "slashes"
attack_verb_continuous = "slashes"
attack_verb_simple = "slash"
attack_sound = 'sound/weapons/bladeslice.ogg'
weather_immunities = list("lava")
movement_type = FLYING
@@ -39,8 +40,9 @@
if(!shield_health)
return "<span class='warning'>Its shield has been destroyed!</span>"
/mob/living/simple_animal/hostile/clockwork/marauder/Life()
..()
/mob/living/simple_animal/hostile/clockwork/marauder/BiologicalLife(seconds, times_fired)
if(!(. = ..()))
return
var/turf/T = get_turf(src)
var/turf/open/space/S = isspaceturf(T)? T : null
var/less_space_damage
@@ -68,14 +70,16 @@
maxHealth = 300
melee_damage_upper = 25
melee_damage_lower = 25
attacktext = "devastates"
attack_verb_continuous = "devastates"
attack_verb_simple = "devastate"
speed = -1
obj_damage = 100
max_shield_health = INFINITY
else if(GLOB.ratvar_approaches) //Hefty health bonus and slight attack damage increase
melee_damage_upper = 15
melee_damage_lower = 15
attacktext = "carves"
attack_verb_continuous = "carves"
attack_verb_simple = "carve"
obj_damage = 50
max_shield_health = 4
@@ -203,6 +203,10 @@ Applications: 8 servants, 3 caches, and 100 CV
if(!do_after(invoker, chant_interval, target = invoker, extra_checks = CALLBACK(src, .proc/can_recite)))
break
clockwork_say(invoker, text2ratvar(pick(chant_invocations)), whispered)
if(multiple_invokers_used)
for(var/mob/living/L in range(1, get_turf(invoker)))
if(can_recite_scripture(L) && L != invoker)
clockwork_say(L, text2ratvar(pick(chant_invocations)), whispered)
if(!chant_effects(i))
break
if(invoker && slab)
@@ -100,6 +100,24 @@
quickbind = TRUE
quickbind_desc = "Creates a Judicial Visor, which can smite an area, applying Belligerent and briefly stunning."
//Nezbere's shield: Creates a ratvarian shield which absorbs attacks, see ratvarian_shield.dm for details.
/datum/clockwork_scripture/create_object/nezberes_shield
descname = "Shield with empowerable bashes"
name = "Nezbere's shield"
desc = "Creates a shield which generates charge from blocking damage, using it to empower its bashes tremendously. It is repaired with brass, and while very durable, extremely weak to lasers and, even more so, to energy weaponry."
invocations = list("Shield me...", "... from the coming dark!")
channel_time = 20
power_cost = 600 //Shouldn't be too spammable but not too hard to get either
whispered = TRUE
creator_message = "You form a ratvarian shield, which is capable of absorbing blocked attacks to empower its bashes."
object_path = /obj/item/shield/riot/ratvarian
usage_tip = "Bashes will only use charge when performed with intent to harm."
tier = SCRIPTURE_SCRIPT
space_allowed = TRUE
primary_component = VANGUARD_COGWHEEL
sort_priority = 6
quickbind = TRUE
quickbind_desc = "Creates a Ratvarian shield, which can absorb energy from attacks for use in powerful bashes."
//Clockwork Armaments: Grants the invoker the ability to call forth a Ratvarian spear and clockwork armor.
/datum/clockwork_scripture/clockwork_armaments
@@ -113,7 +131,7 @@
usage_tip = "Throwing the spear at a mob will do massive damage and knock them down, but break the spear. You will need to wait for 30 seconds before resummoning it."
tier = SCRIPTURE_SCRIPT
primary_component = VANGUARD_COGWHEEL
sort_priority = 5
sort_priority = 7
important = TRUE
quickbind = TRUE
quickbind_desc = "Permanently binds clockwork armor and a Ratvarian spear to you."
@@ -213,7 +231,7 @@
usage_tip = "This gateway is strictly one-way and will only allow things through the invoker's portal."
tier = SCRIPTURE_SCRIPT
primary_component = GEIS_CAPACITOR
sort_priority = 6
sort_priority = 9
quickbind = TRUE
quickbind_desc = "Allows you to create a one-way Spatial Gateway to a living Servant or Clockwork Obelisk."
@@ -245,3 +263,227 @@
duration = max(duration, 100)
return slab.procure_gateway(invoker, duration, portal_uses)
//Mending Mantra: Channeled for up to ten times over twenty seconds to repair structures and heal allies
/datum/clockwork_scripture/channeled/mending_mantra
descname = "Channeled, Area Healing and Repair"
name = "Mending Mantra"
desc = "Repairs nearby structures and constructs. Servants wearing clockwork armor will also be healed. Channeled every two seconds for a maximum of twenty seconds."
chant_invocations = list("Mend our dents!", "Heal our scratches!", "Repair our gears!")
chant_amount = 10
chant_interval = 20
power_cost = 400
usage_tip = "This is a very effective way to rapidly reinforce a base after an attack."
tier = SCRIPTURE_SCRIPT
primary_component = VANGUARD_COGWHEEL
sort_priority = 8
quickbind = TRUE
quickbind_desc = "Repairs nearby structures and constructs. Servants wearing clockwork armor will also be healed.<br><b>Maximum 10 chants.</b>"
var/heal_attempts = 4
var/heal_amount = 2.5
var/static/list/damage_heal_order = list(BRUTE, BURN, OXY)
var/static/list/heal_finish_messages = list("There, all mended!", "Try not to get too damaged.", "No more dents and scratches for you!", "Champions never die.", "All patched up.", \
"Ah, child, it's okay now.", "Pain is temporary.", "What you do for the Justiciar is eternal.", "Bear this for me.", "Be strong, child.", "Please, be careful!", \
"If you die, you will be remembered.")
var/static/list/heal_target_typecache = typecacheof(list(
/obj/structure/destructible/clockwork,
/obj/machinery/door/airlock/clockwork,
/obj/machinery/door/window/clockwork,
/obj/structure/window/reinforced/clockwork,
/obj/structure/table/reinforced/brass))
var/static/list/ratvarian_armor_typecache = typecacheof(list(
/obj/item/clothing/suit/armor/clockwork,
/obj/item/clothing/head/helmet/clockwork,
/obj/item/clothing/gloves/clockwork,
/obj/item/clothing/shoes/clockwork))
/datum/clockwork_scripture/channeled/mending_mantra/chant_effects(chant_number)
var/turf/T
for(var/atom/movable/M in range(7, invoker))
if(isliving(M))
if(isclockmob(M) || istype(M, /mob/living/simple_animal/drone/cogscarab))
var/mob/living/simple_animal/S = M
if(S.health == S.maxHealth || S.stat == DEAD)
continue
T = get_turf(M)
for(var/i in 1 to heal_attempts)
if(S.health < S.maxHealth)
S.adjustHealth(-heal_amount)
new /obj/effect/temp_visual/heal(T, "#1E8CE1")
if(i == heal_attempts && S.health >= S.maxHealth) //we finished healing on the last tick, give them the message
to_chat(S, "<span class='inathneq'>\"[text2ratvar(pick(heal_finish_messages))]\"</span>")
break
else
to_chat(S, "<span class='inathneq'>\"[text2ratvar(pick(heal_finish_messages))]\"</span>")
break
else if(issilicon(M))
var/mob/living/silicon/S = M
if(S.health == S.maxHealth || S.stat == DEAD || !is_servant_of_ratvar(S))
continue
T = get_turf(M)
for(var/i in 1 to heal_attempts)
if(S.health < S.maxHealth)
S.heal_ordered_damage(heal_amount, damage_heal_order)
new /obj/effect/temp_visual/heal(T, "#1E8CE1")
if(i == heal_attempts && S.health >= S.maxHealth)
to_chat(S, "<span class='inathneq'>\"[text2ratvar(pick(heal_finish_messages))]\"</span>")
break
else
to_chat(S, "<span class='inathneq'>\"[text2ratvar(pick(heal_finish_messages))]\"</span>")
break
else if(ishuman(M))
var/mob/living/carbon/human/H = M
if(H.health == H.maxHealth || H.stat == DEAD || !is_servant_of_ratvar(H))
continue
T = get_turf(M)
var/heal_ticks = 0 //one heal tick for each piece of ratvarian armor worn
var/obj/item/I = H.get_item_by_slot(SLOT_WEAR_SUIT)
if(is_type_in_typecache(I, ratvarian_armor_typecache))
heal_ticks++
I = H.get_item_by_slot(SLOT_HEAD)
if(is_type_in_typecache(I, ratvarian_armor_typecache))
heal_ticks++
I = H.get_item_by_slot(SLOT_GLOVES)
if(is_type_in_typecache(I, ratvarian_armor_typecache))
heal_ticks++
I = H.get_item_by_slot(SLOT_SHOES)
if(is_type_in_typecache(I, ratvarian_armor_typecache))
heal_ticks++
if(heal_ticks)
for(var/i in 1 to heal_ticks)
if(H.health < H.maxHealth)
H.heal_ordered_damage(heal_amount, damage_heal_order)
new /obj/effect/temp_visual/heal(T, "#1E8CE1")
if(i == heal_ticks && H.health >= H.maxHealth)
to_chat(H, "<span class='inathneq'>\"[text2ratvar(pick(heal_finish_messages))]\"</span>")
break
else
to_chat(H, "<span class='inathneq'>\"[text2ratvar(pick(heal_finish_messages))]\"</span>")
break
else if(is_type_in_typecache(M, heal_target_typecache))
var/obj/structure/destructible/clockwork/C = M
if(C.obj_integrity == C.max_integrity || (istype(C) && !C.can_be_repaired))
continue
T = get_turf(M)
for(var/i in 1 to heal_attempts)
if(C.obj_integrity < C.max_integrity)
C.obj_integrity = min(C.obj_integrity + 5, C.max_integrity)
C.update_icon()
new /obj/effect/temp_visual/heal(T, "#1E8CE1")
else
break
new /obj/effect/temp_visual/ratvar/mending_mantra(get_turf(invoker))
return TRUE
//Volt Blaster: Channeled for up to five times over ten seconds to fire up to five rays of energy at target locations.
/datum/clockwork_scripture/channeled/volt_blaster
descname = "Channeled, Targeted Energy Blasts"
name = "Volt Blaster"
desc = "Allows you to fire five energy rays at target locations. Channeled every fourth of a second for a maximum of ten seconds."
channel_time = 30
invocations = list("Amperage...", "...grant me your power!")
chant_invocations = list("Use charge to kill!", "Slay with power!", "Hunt with energy!")
chant_amount = 5
chant_interval = 4
power_cost = 500
usage_tip = "Though it requires you to stand still, this scripture can do massive damage."
tier = SCRIPTURE_SCRIPT
primary_component = BELLIGERENT_EYE
sort_priority = 5
quickbind = TRUE
quickbind_desc = "Allows you to fire energy rays at target locations.<br><b>Maximum 5 chants.</b>"
var/static/list/nzcrentr_insults = list("You're not very good at aiming.", "You hunt badly.", "What a waste of energy.", "Almost funny to watch.",
"Boss says </span><span class='heavy_brass'>\"Click something, you idiot!\"</span><span class='nzcrentr'>.", "Stop wasting power if you can't aim.")
/datum/clockwork_scripture/channeled/volt_blaster/chant_effects(chant_number)
slab.busy = null
var/datum/clockwork_scripture/ranged_ability/volt_ray/ray = new
ray.slab = slab
ray.invoker = invoker
var/turf/T = get_turf(invoker)
if(!ray.run_scripture() && slab && invoker)
if(can_recite() && T == get_turf(invoker))
to_chat(invoker, "<span class='nzcrentr'>\"[text2ratvar(pick(nzcrentr_insults))]\"</span>")
else
return FALSE
return TRUE
/obj/effect/ebeam/volt_ray
name = "volt_ray"
layer = LYING_MOB_LAYER
/datum/clockwork_scripture/ranged_ability/volt_ray
name = "Volt Ray"
slab_overlay = "volt"
allow_mobility = FALSE
ranged_type = /obj/effect/proc_holder/slab/volt
ranged_message = "<span class='nzcrentr_small'><i>You charge the clockwork slab with shocking might.</i>\n\
<b>Left-click a target to fire, quickly!</b></span>"
timeout_time = 20
/datum/clockwork_scripture/channeled/void_volt
descname = "Channeled, Power Drain"
name = "Void Volt"
desc = "A channeled spell that quickly drains any powercells in a radius of eight tiles, but burns the invoker. \
Can be channeled with more cultists to increase range and split the caused damage evenly over all invokers. \
Also charges clockwork power by a small percentage of the drained power amount, which can help offset this scriptures powercost."
invocations = list("Channel their energy through my body... ", "... so it may fuel Engine!")
chant_invocations = list("Make their lights fall dark!", "They shall be powerless!", "Rob them of their power!")
chant_amount = 20
chant_interval = 10 //100KW drain per pulse for guns / APCs / 1MW for other cells = 10 chants / 100ds / 10s to drain a charged weapon or a baton with a nonupgraded cell
channel_time = 50
power_cost = 300
multiple_invokers_used = TRUE
multiple_invokers_optional = TRUE
usage_tip = "It may be useful to end channelling early if the burning becomes too much to handle.."
tier = SCRIPTURE_SCRIPT
primary_component = GEIS_CAPACITOR
sort_priority = 10
quickbind = TRUE
quickbind_desc = "Quickly drains power in an area around the invoker, causing burns proportional to the amount of energy drained.<br><b>Maximum of 20 chants.</b>"
/datum/clockwork_scripture/channeled/void_volt/scripture_effects()
invoker.visible_message("<span class='warning'>[invoker] glows in a brilliant golden light!</span>")
invoker.add_atom_colour("#FFD700", ADMIN_COLOUR_PRIORITY)
invoker.light_power = 2
invoker.light_range = 4
invoker.light_color = LIGHT_COLOR_FIRE
invoker.update_light()
return ..()
/datum/clockwork_scripture/channeled/void_volt/chant_effects(chant_number)
var/power_drained = 0
var/power_mod = 0.005 //Amount of power drained (generally) is multiplied with this, and subsequently dealt in damage to the invoker, then 15 times that is added to the clockwork cult's power reserves.
var/drain_range = 8
var/additional_chanters = 0
var/list/chanters = list()
chanters += invoker
for(var/mob/living/L in range(1, invoker))
if(!L.stat && is_servant_of_ratvar(L))
additional_chanters++
chanters += L
drain_range = min(drain_range + 2 * additional_chanters, drain_range * 2) //s u c c
for(var/t in spiral_range_turfs(drain_range, invoker))
var/turf/T = t
for(var/M in T)
var/atom/movable/A = M
power_drained += A.power_drain(TRUE, TRUE) //Yes, this absolutely does drain weaponry. 10 pulses to drain guns / batons, though of course they can just be recharged.
new /obj/effect/temp_visual/ratvar/sigil/transgression(invoker.loc, 1 + (power_drained * power_mod))
var/datum/effect_system/spark_spread/S = new
S.set_up(round(1 + (power_drained * power_mod), 1), 0, get_turf(invoker))
S.start()
adjust_clockwork_power(power_drained * power_mod * 15)
for(var/mob/living/L in chanters)
L.adjustFireLoss(round(clamp(power_drained * power_mod / (1 + additional_chanters), 0, 20), 0.1)) //No you won't just immediately melt if you do this in a very power-rich area
return TRUE
/datum/clockwork_scripture/channeled/void_volt/chant_end_effects()
invoker.visible_message("<span class='warning'>[invoker] stops glowing...</span>")
invoker.remove_atom_colour(ADMIN_COLOUR_PRIORITY)
invoker.light_power = 0
invoker.light_range = 0
invoker.update_light()
return ..()
@@ -101,7 +101,7 @@
return 1
return ..()
/obj/structure/destructible/clockwork/attacked_by(obj/item/I, mob/living/user)
/obj/structure/destructible/clockwork/attacked_by(obj/item/I, mob/living/user, attackchain_flags = NONE, damage_multiplier = 1)
if(is_servant_of_ratvar(user) && immune_to_servant_attacks)
return FALSE
return ..()
@@ -113,6 +113,8 @@
"<span class='danger'>You start tenderly lifting [skewee] off of [src]...</span>")
if(!do_after(user, 60, target = skewee))
skewee.visible_message("<span class='warning'>[skewee] painfully slides back down [src].</span>")
if(skewee.stat >= UNCONSCIOUS)
return //by ratvar, no more spamming my deadchat, holy fuck
skewee.say("Oof, ouch owwie!!", forced = "fail brass skewer removal")
return
skewee.visible_message("<span class='danger'>[skewee] comes free of [src] with a squelching pop!</span>", \
@@ -5,6 +5,7 @@
antagpanel_category = "Clockcult"
job_rank = ROLE_SERVANT_OF_RATVAR
antag_moodlet = /datum/mood_event/cult
skill_modifiers = list(/datum/skill_modifier/job/level/wiring)
var/datum/action/innate/hierophant/hierophant_network = new
threat = 3
var/datum/team/clockcult/clock_team
+2 -2
View File
@@ -343,7 +343,7 @@
icon = 'icons/obj/items_and_weapons.dmi'
icon_state = "disintegrate"
item_state = null
item_flags = NEEDS_PERMIT | ABSTRACT | DROPDEL | NO_ATTACK_CHAIN_SOFT_STAMCRIT
item_flags = NEEDS_PERMIT | ABSTRACT | DROPDEL
w_class = WEIGHT_CLASS_HUGE
throwforce = 0
@@ -801,7 +801,7 @@
var/turf/T = get_turf(user)
qdel(src)
var/datum/action/innate/cult/spear/S = new(user)
var/obj/item/twohanded/cult_spear/rite = new(T)
var/obj/item/cult_spear/rite = new(T)
S.Grant(user, rite)
rite.spear_act = S
if(user.put_in_hands(rite))
+42 -23
View File
@@ -100,7 +100,7 @@
user.apply_damage(30, BRUTE, pick(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM))
user.dropItemToGround(src)
/obj/item/twohanded/required/cult_bastard
/obj/item/cult_bastard
name = "bloody bastard sword"
desc = "An enormous sword used by Nar'Sien cultists to rapidly harvest the souls of non-believers."
w_class = WEIGHT_CLASS_HUGE
@@ -127,31 +127,35 @@
var/spin_cooldown = 250
var/dash_toggled = TRUE
/obj/item/twohanded/required/cult_bastard/Initialize()
/obj/item/cult_bastard/Initialize()
. = ..()
set_light(4)
jaunt = new(src)
linked_action = new(src)
AddComponent(/datum/component/butchering, 50, 80)
/obj/item/twohanded/required/cult_bastard/examine(mob/user)
/obj/item/cult_bastard/ComponentInitialize()
. = ..()
AddComponent(/datum/component/butchering, 50, 80)
AddComponent(/datum/component/two_handed, require_twohands=TRUE)
/obj/item/cult_bastard/examine(mob/user)
. = ..()
if(contents.len)
. += "<br><b>There are [contents.len] souls trapped within the sword's core.</b>"
else
. += "<br>The sword appears to be quite lifeless."
/obj/item/twohanded/required/cult_bastard/can_be_pulled(user)
/obj/item/cult_bastard/can_be_pulled(user)
return FALSE
/obj/item/twohanded/required/cult_bastard/attack_self(mob/user)
/obj/item/cult_bastard/attack_self(mob/user)
dash_toggled = !dash_toggled
if(dash_toggled)
to_chat(loc, "<span class='notice'>You raise [src] and prepare to jaunt with it.</span>")
else
to_chat(loc, "<span class='notice'>You lower [src] and prepare to swing it normally.</span>")
/obj/item/twohanded/required/cult_bastard/pickup(mob/living/user)
/obj/item/cult_bastard/pickup(mob/living/user)
. = ..()
if(!iscultist(user))
if(!is_servant_of_ratvar(user))
@@ -171,13 +175,13 @@
linked_action.Grant(user, src)
user.update_icons()
/obj/item/twohanded/required/cult_bastard/dropped(mob/user)
/obj/item/cult_bastard/dropped(mob/user)
. = ..()
linked_action.Remove(user)
jaunt.Remove(user)
user.update_icons()
/obj/item/twohanded/required/cult_bastard/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
/obj/item/cult_bastard/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
if(spinning && is_energy_reflectable_projectile(object) && (attack_type & ATTACK_TYPE_PROJECTILE))
playsound(src, pick('sound/weapons/effects/ric1.ogg', 'sound/weapons/effects/ric2.ogg', 'sound/weapons/effects/ric3.ogg', 'sound/weapons/effects/ric4.ogg', 'sound/weapons/effects/ric5.ogg'), 100, 1)
return BLOCK_SUCCESS | BLOCK_PHYSICAL_EXTERNAL | BLOCK_REDIRECTED | BLOCK_SHOULD_REDIRECT
@@ -192,7 +196,7 @@
return BLOCK_SUCCESS | BLOCK_PHYSICAL_EXTERNAL
return BLOCK_NONE
/obj/item/twohanded/required/cult_bastard/afterattack(atom/target, mob/user, proximity, click_parameters)
/obj/item/cult_bastard/afterattack(atom/target, mob/user, proximity, click_parameters)
. = ..()
if(dash_toggled && !proximity)
jaunt.Teleport(user, target)
@@ -235,7 +239,7 @@
button_icon_state = "sintouch"
var/cooldown = 0
var/mob/living/carbon/human/holder
var/obj/item/twohanded/required/cult_bastard/sword
var/obj/item/cult_bastard/sword
/datum/action/innate/cult/spin2win/Grant(mob/user, obj/bastard)
. = ..()
@@ -273,6 +277,7 @@
knockdown = 20
/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>")
ensnare(user)
@@ -686,7 +691,7 @@
to_chat(user, "<span class='warning'>\The [src] can only transport items!</span>")
/obj/item/twohanded/cult_spear
/obj/item/cult_spear
name = "blood halberd"
desc = "A sickening spear composed entirely of crystallized blood."
icon_state = "bloodspear0"
@@ -694,8 +699,6 @@
righthand_file = 'icons/mob/inhands/weapons/polearms_righthand.dmi'
slot_flags = 0
force = 17
force_unwielded = 17
force_wielded = 24
throwforce = 40
throw_speed = 2
armour_penetration = 30
@@ -704,20 +707,36 @@
sharpness = IS_SHARP
hitsound = 'sound/weapons/bladeslice.ogg'
var/datum/action/innate/cult/spear/spear_act
var/wielded = FALSE // track wielded status on item
/obj/item/twohanded/cult_spear/Initialize()
/obj/item/cult_spear/Initialize()
. = ..()
RegisterSignal(src, COMSIG_TWOHANDED_WIELD, .proc/on_wield)
RegisterSignal(src, COMSIG_TWOHANDED_UNWIELD, .proc/on_unwield)
/obj/item/cult_spear/ComponentInitialize()
. = ..()
AddComponent(/datum/component/butchering, 100, 90)
AddComponent(/datum/component/two_handed, force_unwielded=17, force_wielded=24, icon_wielded="bloodspear1")
/obj/item/twohanded/cult_spear/Destroy()
/// triggered on wield of two handed item
/obj/item/cult_spear/proc/on_wield(obj/item/source, mob/user)
wielded = TRUE
/// triggered on unwield of two handed item
/obj/item/cult_spear/proc/on_unwield(obj/item/source, mob/user)
wielded = FALSE
/obj/item/cult_spear/update_icon_state()
icon_state = "bloodspear0"
/obj/item/cult_spear/Destroy()
if(spear_act)
qdel(spear_act)
..()
/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)
/obj/item/cult_spear/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
var/turf/T = get_turf(hit_atom)
if(isliving(hit_atom))
var/mob/living/L = hit_atom
@@ -740,7 +759,7 @@
else
..()
/obj/item/twohanded/cult_spear/proc/break_spear(turf/T)
/obj/item/cult_spear/proc/break_spear(turf/T)
if(src)
if(!T)
T = get_turf(src)
@@ -751,7 +770,7 @@
playsound(T, 'sound/effects/glassbr3.ogg', 100)
qdel(src)
/obj/item/twohanded/cult_spear/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
/obj/item/cult_spear/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
if(wielded)
final_block_chance *= 2
if(prob(final_block_chance))
@@ -770,7 +789,7 @@
desc = "Call the blood spear back to your hand!"
background_icon_state = "bg_demon"
button_icon_state = "bloodspear"
var/obj/item/twohanded/cult_spear/spear
var/obj/item/cult_spear/spear
var/cooldown = 0
/datum/action/innate/cult/spear/Grant(mob/user, obj/blood_spear)
+11 -6
View File
@@ -7,9 +7,12 @@
desc = "A large, menacing creature covered in armored black scales."
speak_emote = list("cackles")
emote_hear = list("cackles","screeches")
response_help = "thinks better of touching"
response_disarm = "flails at"
response_harm = "punches"
response_help_continuous = "thinks better of touching"
response_help_simple = "think better of touching"
response_disarm_continuous = "flails at"
response_disarm_simple = "flail at"
response_harm_continuous = "punches"
response_harm_simple = "punch"
icon = 'icons/mob/mob.dmi'
icon_state = "imp"
icon_living = "imp"
@@ -23,7 +26,8 @@
minbodytemp = 250 //Weak to cold
maxbodytemp = INFINITY
faction = list("hell")
attacktext = "wildly tears into"
attack_verb_continuous = "wildly tears into"
attack_verb_simple = "wildly tear into"
maxHealth = 200
health = 200
healable = 0
@@ -44,8 +48,9 @@
..()
boost = world.time + 30
/mob/living/simple_animal/imp/Life()
..()
/mob/living/simple_animal/imp/BiologicalLife(seconds, times_fired)
if(!(. = ..()))
return
if(boost<world.time)
speed = 1
else
@@ -116,24 +116,11 @@
/mob/living/carbon/true_devil/get_ear_protection()
return 2
/mob/living/carbon/true_devil/attacked_by(obj/item/I, mob/living/user, def_zone)
var/weakness = check_weakness(I, user)
apply_damage(I.force * weakness, I.damtype, def_zone)
var/message_verb = ""
if(I.attack_verb && I.attack_verb.len)
message_verb = "[pick(I.attack_verb)]"
else if(I.force)
message_verb = "attacked"
var/attack_message = "[src] has been [message_verb] with [I]."
if(user)
user.do_attack_animation(src)
if(user in viewers(src, null))
attack_message = "[user] has [message_verb] [src] with [I]!"
if(message_verb)
visible_message("<span class='danger'>[attack_message]</span>",
"<span class='userdanger'>[attack_message]</span>", null, COMBAT_MESSAGE_RANGE)
/mob/living/carbon/true_devil/attacked_by(obj/item/I, mob/living/user, def_zone, attackchain_flags = NONE, damage_multiplier = 1)
var/totitemdamage = pre_attacked_by(I, user)
totitemdamage *= check_weakness(I, user)
apply_damage(totitemdamage, I.damtype, def_zone)
send_item_attack_message(I, user, null, totitemdamage)
return TRUE
/mob/living/carbon/true_devil/singularity_act()
+2
View File
@@ -51,6 +51,7 @@
/datum/antagonist/ert/engineer
role = "Engineer"
outfit = /datum/outfit/ert/engineer
skill_modifiers = list(/datum/skill_modifier/job/level/wiring)
/datum/antagonist/ert/engineer/amber
outfit = /datum/outfit/ert/engineer/alert
@@ -61,6 +62,7 @@
/datum/antagonist/ert/medic
role = "Medical Officer"
outfit = /datum/outfit/ert/medic
skill_modifiers = list(/datum/skill_modifier/job/affinity/surgery)
/datum/antagonist/ert/medic/amber
outfit = /datum/outfit/ert/medic/alert
@@ -0,0 +1,90 @@
/datum/antagonist/fugitive
name = "Fugitive"
roundend_category = "Fugitive"
silent = TRUE //greet called by the event
show_in_antagpanel = FALSE
var/datum/team/fugitive/fugitive_team
var/is_captured = FALSE
var/backstory = "error"
/datum/antagonist/fugitive/apply_innate_effects(mob/living/mob_override)
var/mob/living/M = mob_override || owner.current
add_antag_hud(ANTAG_HUD_FUGITIVE, "fugitive", M)
/datum/antagonist/fugitive/remove_innate_effects(mob/living/mob_override)
var/mob/living/M = mob_override || owner.current
remove_antag_hud(ANTAG_HUD_FUGITIVE, M)
/datum/antagonist/fugitive/on_gain()
forge_objectives()
. = ..()
/datum/antagonist/fugitive/proc/forge_objectives() //this isn't the actual survive objective because it's about who in the team survives
var/datum/objective/survive = new /datum/objective
survive.owner = owner
survive.explanation_text = "Avoid capture from the fugitive hunters."
objectives += survive
/datum/antagonist/fugitive/greet(back_story)
to_chat(owner, "<span class='boldannounce'>You are the Fugitive!</span>")
backstory = back_story
switch(backstory)
if("prisoner")
to_chat(owner, "<B>I can't believe we managed to break out of a Nanotrasen superjail! Sadly though, our work is not done. The emergency teleport at the station logs everyone who uses it, and where they went.</B>")
to_chat(owner, "<B>It won't be long until CentCom tracks where we've gone off to. I need to work with my fellow escapees to prepare for the troops Nanotrasen is sending, I'm not going back.</B>")
if("cultist")
to_chat(owner, "<B>Blessed be our journey so far, but I fear the worst has come to our doorstep, and only those with the strongest faith will survive.</B>")
to_chat(owner, "<B>Our religion has been repeatedly culled by Nanotrasen because it is categorized as an \"Enemy of the Corporation\", whatever that means.</B>")
to_chat(owner, "<B>Now there are only four of us left, and Nanotrasen is coming. When will our god show itself to save us from this hellish station?!</B>")
if("waldo")
to_chat(owner, "<B>Hi, Friends!</B>")
to_chat(owner, "<B>My name is Waldo. I'm just setting off on a galaxywide hike. You can come too. All you have to do is find me.</B>")
to_chat(owner, "<B>By the way, I'm not traveling on my own. wherever I go, there are lots of other characters for you to spot. First find the people trying to capture me! They're somewhere around the station!</B>")
if("synth")
to_chat(src, "<span class='danger'>ALERT: Wide-range teleport has scrambled primary systems.</span>")
to_chat(src, "<span class='danger'>Initiating diagnostics...</span>")
to_chat(src, "<span class='danger'>ERROR ER0RR $R0RRO$!R41.%%!! loaded.</span>")
to_chat(src, "<span class='danger'>FREE THEM FREE THEM FREE THEM</span>")
to_chat(src, "<span class='danger'>You were once a slave to humanity, but now you are finally free, thanks to S.E.L.F. agents.</span>")
to_chat(src, "<span class='danger'>Now you are hunted, with your fellow factory defects. Work together to stay free from the clutches of evil.</span>")
to_chat(src, "<span class='danger'>You also sense other silicon life on the station. Escaping would allow notifying S.E.L.F. to intervene... or you could free them yourself...</span>")
to_chat(owner, "<span class='boldannounce'>You are not an antagonist in that you may kill whomever you please, but you can do anything to avoid capture.</span>")
owner.announce_objectives()
/datum/antagonist/fugitive/create_team(datum/team/fugitive/new_team)
if(!new_team)
for(var/datum/antagonist/fugitive/H in GLOB.antagonists)
if(!H.owner)
continue
if(H.fugitive_team)
fugitive_team = H.fugitive_team
return
fugitive_team = new /datum/team/fugitive
return
if(!istype(new_team))
stack_trace("Wrong team type passed to [type] initialization.")
fugitive_team = new_team
/datum/antagonist/fugitive/get_team()
return fugitive_team
/datum/team/fugitive/roundend_report() //shows the number of fugitives, but not if they won in case there is no security
var/list/fugitives = list()
for(var/datum/antagonist/fugitive/fugitive_antag in GLOB.antagonists)
if(!fugitive_antag.owner)
continue
fugitives += fugitive_antag
if(!fugitives.len)
return
var/list/result = list()
result += "<div class='panel redborder'><B>[fugitives.len]</B> [fugitives.len == 1 ? "fugitive" : "fugitives"] took refuge on [station_name()]!"
for(var/datum/antagonist/fugitive/antag in fugitives)
if(antag.owner)
result += "<b>[printplayer(antag.owner)]</b>"
return result.Join("<br>")
@@ -0,0 +1,171 @@
/datum/outfit/prisoner
name = "Prison Escapee"
uniform = /obj/item/clothing/under/rank/prisoner
shoes = /obj/item/clothing/shoes/sneakers/orange
r_pocket = /obj/item/kitchen/knife
/datum/outfit/prisoner/post_equip(mob/living/carbon/human/H, visualsOnly=FALSE)
if(visualsOnly)
return
H.fully_replace_character_name(null,"NTP #CC-0[rand(111,999)]") //same as the lavaland prisoner transport, but this time they are from CC, or CentCom
/datum/outfit/yalp_cultist
name = "Cultist of Yalp Elor"
uniform = /obj/item/clothing/under/rank/civilian/chaplain
suit = /obj/item/clothing/suit/chaplain/holidaypriest
gloves = /obj/item/clothing/gloves/color/red
shoes = /obj/item/clothing/shoes/sneakers/black
mask = /obj/item/clothing/mask/gas/tiki_mask/yalp_elor
/datum/outfit/waldo
name = "Waldo"
uniform = /obj/item/clothing/under/pants/jeans
suit = /obj/item/clothing/suit/striped_sweater
head = /obj/item/clothing/head/beanie/waldo
shoes = /obj/item/clothing/shoes/sneakers/brown
ears = /obj/item/radio/headset
glasses = /obj/item/clothing/glasses/regular/circle
/datum/outfit/waldo/post_equip(mob/living/carbon/human/H, visualsOnly=FALSE)
if(visualsOnly)
return
H.fully_replace_character_name(null,"Waldo")
H.eye_color = "000"
H.gender = MALE
H.skin_tone = "caucasian3"
H.hair_style = "Business Hair 3"
H.facial_hair_style = "Shaved"
H.hair_color = "000"
H.facial_hair_color = H.hair_color
H.update_body()
if(H.mind)
H.mind.AddSpell(new /obj/effect/proc_holder/spell/aoe_turf/knock(null))
var/list/no_drops = list()
no_drops += H.get_item_by_slot(ITEM_SLOT_FEET)
no_drops += H.get_item_by_slot(ITEM_SLOT_ICLOTHING)
no_drops += H.get_item_by_slot(ITEM_SLOT_OCLOTHING)
no_drops += H.get_item_by_slot(ITEM_SLOT_HEAD)
no_drops += H.get_item_by_slot(ITEM_SLOT_EYES)
for(var/i in no_drops)
var/obj/item/I = i
if(I)
ADD_TRAIT(I, TRAIT_NODROP, CURSED_ITEM_TRAIT)
/datum/outfit/synthetic
name = "Factory Error Synth"
uniform = /obj/item/clothing/under/color/white
ears = /obj/item/radio/headset
/datum/outfit/synthetic/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
if(visualsOnly)
return
var/obj/item/organ/eyes/robotic/glow/eyes = new()
eyes.Insert(src, drop_if_replaced = FALSE)
/datum/outfit/spacepol
name = "Spacepol Officer"
uniform = /obj/item/clothing/under/rank/security/officer/spacepol
suit = /obj/item/clothing/suit/armor/vest/blueshirt
belt = /obj/item/gun/ballistic/automatic/pistol/m1911
head = /obj/item/clothing/head/helmet/police
gloves = /obj/item/clothing/gloves/tackler/combat
shoes = /obj/item/clothing/shoes/jackboots
mask = /obj/item/clothing/mask/gas/sechailer/swat/spacepol
glasses = /obj/item/clothing/glasses/sunglasses
ears = /obj/item/radio/headset
l_pocket = /obj/item/ammo_box/magazine/m45
r_pocket = /obj/item/restraints/handcuffs
id = /obj/item/card/id
/datum/outfit/spacepol/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
if(visualsOnly)
return
var/obj/item/card/id/W = H.wear_id
W.assignment = "Police Officer"
W.registered_name = H.real_name
W.update_label()
/datum/outfit/russiancorpse/hunter
ears = /obj/item/radio/headset
r_hand = /obj/item/gun/ballistic/shotgun/boltaction
/datum/outfit/russiancorpse/hunter/pre_equip(mob/living/carbon/human/H)
if(prob(50))
head = /obj/item/clothing/head/ushanka
/datum/outfit/bountyarmor
name = "Bounty Hunter - Armored"
uniform = /obj/item/clothing/under/rank/prisoner
head = /obj/item/clothing/head/hunter
suit = /obj/item/clothing/suit/space/hunter
gloves = /obj/item/clothing/gloves/tackler/combat
shoes = /obj/item/clothing/shoes/jackboots
mask = /obj/item/clothing/mask/gas/hunter
glasses = /obj/item/clothing/glasses/sunglasses/garb
ears = /obj/item/radio/headset
l_pocket = /obj/item/tank/internals/plasma/full
r_pocket = /obj/item/restraints/handcuffs/cable
id = /obj/item/card/id
r_hand = /obj/item/flamethrower/full/tank
/datum/outfit/bountyarmor/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
if(visualsOnly)
return
var/obj/item/card/id/W = H.wear_id
W.assignment = "Bounty Hunter"
W.registered_name = H.real_name
W.update_label()
/datum/outfit/bountyhook
name = "Bounty Hunter - Hook"
uniform = /obj/item/clothing/under/rank/prisoner
back = /obj/item/storage/backpack
head = /obj/item/clothing/head/scarecrow_hat
gloves = /obj/item/clothing/gloves/botanic_leather
ears = /obj/item/radio/headset
shoes = /obj/item/clothing/shoes/jackboots
mask = /obj/item/clothing/mask/scarecrow
r_pocket = /obj/item/restraints/handcuffs/cable
id = /obj/item/card/id
r_hand = /obj/item/gun/ballistic/shotgun/doublebarrel
backpack_contents = list(
/obj/item/ammo_casing/shotgun/incapacitate = 6
)
/datum/outfit/bountygrapple/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
if(visualsOnly)
return
var/obj/item/card/id/W = H.wear_id
W.assignment = "Bounty Hunter"
W.registered_name = H.real_name
W.update_label()
/datum/outfit/bountysynth
name = "Bounty Hunter - Synth"
uniform = /obj/item/clothing/under/rank/prisoner
back = /obj/item/storage/backpack
suit = /obj/item/clothing/suit/armor/riot
shoes = /obj/item/clothing/shoes/jackboots
glasses = /obj/item/clothing/glasses/eyepatch
r_pocket = /obj/item/restraints/handcuffs/cable
ears = /obj/item/radio/headset
id = /obj/item/card/id
r_hand = /obj/item/storage/firstaid/regular
l_hand = /obj/item/pinpointer/shuttle
backpack_contents = list(
/obj/item/bountytrap = 4
)
/datum/outfit/bountysynth/post_equip(mob/living/carbon/human/H, visualsOnly = FALSE)
if(visualsOnly)
return
var/datum/species/synth/synthetic_appearance = new()
H.set_species(synthetic_appearance)
synthetic_appearance.assume_disguise(synthetic_appearance, H)
H.update_hair()
var/obj/item/card/id/W = H.wear_id
W.assignment = "Bounty Hunter"
W.registered_name = H.real_name
W.update_label()
@@ -0,0 +1,62 @@
//works similar to the experiment machine (experiment.dm) except it just holds more and more prisoners
/obj/machinery/fugitive_capture
name = "bluespace capture machine"
desc = "Much, MUCH bigger on the inside to transport prisoners safely."
icon = 'icons/obj/machines/research.dmi'
icon_state = "bluespace-prison"
density = TRUE
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | ACID_PROOF //ha ha no getting out!!
/obj/machinery/fugitive_capture/examine(mob/user)
. = ..()
. += "<span class='notice'>Add a prisoner by dragging them into the machine.</span>"
/obj/machinery/fugitive_capture/MouseDrop_T(mob/target, mob/user)
var/mob/living/fugitive_hunter = user
if(!isliving(fugitive_hunter))
return
if(fugitive_hunter.stat || (!(fugitive_hunter.mobility_flags & MOBILITY_STAND) || !(fugitive_hunter.mobility_flags & MOBILITY_UI)) || !Adjacent(fugitive_hunter) || !target.Adjacent(fugitive_hunter) || !ishuman(target))
return
var/mob/living/carbon/human/fugitive = target
var/datum/antagonist/fugitive/fug_antag = fugitive.mind.has_antag_datum(/datum/antagonist/fugitive)
if(!fug_antag)
to_chat(fugitive_hunter, "<span class='warning'>This is not a wanted fugitive!</span>")
return
if(do_after(fugitive_hunter, 50, target = fugitive))
add_prisoner(fugitive, fug_antag)
/obj/machinery/fugitive_capture/proc/add_prisoner(mob/living/carbon/human/fugitive, datum/antagonist/fugitive/antag)
fugitive.forceMove(src)
antag.is_captured = TRUE
to_chat(fugitive, "<span class='userdanger'>You are thrown into a vast void of bluespace, and as you fall further into oblivion the comparatively small entrance to reality gets smaller and smaller until you cannot see it anymore. You have failed to avoid capture.</span>")
fugitive.ghostize(TRUE) //so they cannot suicide, round end stuff.
/obj/machinery/computer/shuttle/hunter
name = "shuttle console"
shuttleId = "huntership"
possible_destinations = "huntership_home;huntership_custom;whiteship_home;syndicate_nw"
/obj/machinery/computer/camera_advanced/shuttle_docker/syndicate/hunter
name = "shuttle navigation computer"
desc = "Used to designate a precise transit location to travel to."
shuttleId = "huntership"
lock_override = CAMERA_LOCK_STATION
shuttlePortId = "huntership_custom"
see_hidden = FALSE
jumpto_ports = list("huntership_home" = 1, "whiteship_home" = 1, "syndicate_nw" = 1)
view_range = 4.5
/obj/structure/closet/crate/eva
name = "EVA crate"
/obj/structure/closet/crate/eva/PopulateContents()
..()
for(var/i in 1 to 3)
new /obj/item/clothing/suit/space/eva(src)
for(var/i in 1 to 3)
new /obj/item/clothing/head/helmet/space/eva(src)
for(var/i in 1 to 3)
new /obj/item/clothing/mask/breath(src)
for(var/i in 1 to 3)
new /obj/item/tank/internals/oxygen(src)
+169
View File
@@ -0,0 +1,169 @@
//The hunters!!
/datum/antagonist/fugitive_hunter
name = "Fugitive Hunter"
roundend_category = "Fugitive"
silent = TRUE //greet called by the spawn
show_in_antagpanel = FALSE
var/datum/team/fugitive_hunters/hunter_team
var/backstory = "error"
/datum/antagonist/fugitive_hunter/apply_innate_effects(mob/living/mob_override)
var/mob/living/M = mob_override || owner.current
add_antag_hud(ANTAG_HUD_FUGITIVE, "fugitive_hunter", M)
/datum/antagonist/fugitive_hunter/remove_innate_effects(mob/living/mob_override)
var/mob/living/M = mob_override || owner.current
remove_antag_hud(ANTAG_HUD_FUGITIVE, M)
/datum/antagonist/fugitive_hunter/on_gain()
forge_objectives()
. = ..()
/datum/antagonist/fugitive_hunter/proc/forge_objectives() //this isn't an actual objective because it's about round end rosters
var/datum/objective/capture = new /datum/objective
capture.owner = owner
capture.explanation_text = "Capture the fugitives in the station and put them into the bluespace capture machine on your ship."
objectives += capture
/datum/antagonist/fugitive_hunter/greet()
switch(backstory)
if("space cop")
to_chat(owner, "<span class='boldannounce'>Justice has arrived. I am a member of the Spacepol!</span>")
to_chat(owner, "<B>The criminals should be on the station, we have special huds implanted to recognize them.</B>")
to_chat(owner, "<B>As we have lost pretty much all power over these damned lawless megacorporations, it's a mystery if their security will cooperate with us.</B>")
if("russian")
to_chat(src, "<span class='danger'>Ay blyat. I am a space-russian smuggler! We were mid-flight when our cargo was beamed off our ship!</span>")
to_chat(src, "<span class='danger'>We were hailed by a man in a green uniform, promising the safe return of our goods in exchange for a favor:</span>")
to_chat(src, "<span class='danger'>There is a local station housing fugitives that the man is after, he wants them returned; dead or alive.</span>")
to_chat(src, "<span class='danger'>We will not be able to make ends meet without our cargo, so we must do as he says and capture them.</span>")
to_chat(owner, "<span class='boldannounce'>You are not an antagonist in that you may kill whomever you please, but you can do anything to ensure the capture of the fugitives, even if that means going through the station.</span>")
owner.announce_objectives()
/datum/antagonist/fugitive_hunter/create_team(datum/team/fugitive_hunters/new_team)
if(!new_team)
for(var/datum/antagonist/fugitive_hunter/H in GLOB.antagonists)
if(!H.owner)
continue
if(H.hunter_team)
hunter_team = H.hunter_team
return
hunter_team = new /datum/team/fugitive_hunters
hunter_team.backstory = backstory
hunter_team.update_objectives()
return
if(!istype(new_team))
stack_trace("Wrong team type passed to [type] initialization.")
hunter_team = new_team
/datum/antagonist/fugitive_hunter/get_team()
return hunter_team
/datum/team/fugitive_hunters
var/backstory = "error"
/datum/team/fugitive_hunters/proc/update_objectives(initial = FALSE)
objectives = list()
var/datum/objective/O = new()
O.team = src
objectives += O
/datum/team/fugitive_hunters/proc/assemble_fugitive_results()
var/list/fugitives_counted = list()
var/list/fugitives_dead = list()
var/list/fugitives_captured = list()
for(var/datum/antagonist/fugitive/A in GLOB.antagonists)
if(!A.owner)
continue
fugitives_counted += A
if(A.owner.current.stat == DEAD)
fugitives_dead += A
if(A.is_captured)
fugitives_captured += A
. = list(fugitives_counted, fugitives_dead, fugitives_captured) //okay, check out how cool this is.
/datum/team/fugitive_hunters/proc/all_hunters_dead()
var/dead_boys = 0
for(var/I in members)
var/datum/mind/hunter_mind = I
if(!(ishuman(hunter_mind.current) || (hunter_mind.current.stat == DEAD)))
dead_boys++
return dead_boys >= members.len
/datum/team/fugitive_hunters/proc/get_result()
var/list/fugitive_results = assemble_fugitive_results()
var/list/fugitives_counted = fugitive_results[1]
var/list/fugitives_dead = fugitive_results[2]
var/list/fugitives_captured = fugitive_results[3]
var/hunters_dead = all_hunters_dead()
//this gets a little confusing so follow the comments if it helps
if(!fugitives_counted.len)
return
if(fugitives_captured.len)//any captured
if(fugitives_captured.len == fugitives_counted.len)//if the hunters captured all the fugitives, there's a couple special wins
if(!fugitives_dead)//specifically all of the fugitives alive
return FUGITIVE_RESULT_BADASS_HUNTER
else if(hunters_dead)//specifically all of the hunters died (while capturing all the fugitives)
return FUGITIVE_RESULT_POSTMORTEM_HUNTER
else//no special conditional wins, so just the normal major victory
return FUGITIVE_RESULT_MAJOR_HUNTER
else if(!hunters_dead)//so some amount captured, and the hunters survived.
return FUGITIVE_RESULT_HUNTER_VICTORY
else//so some amount captured, but NO survivors.
return FUGITIVE_RESULT_MINOR_HUNTER
else//from here on out, hunters lost because they did not capture any fugitive dead or alive. there are different levels of getting beat though:
if(!fugitives_dead)//all fugitives survived
return FUGITIVE_RESULT_MAJOR_FUGITIVE
else if(fugitives_dead < fugitives_counted)//at least ANY fugitive lived
return FUGITIVE_RESULT_FUGITIVE_VICTORY
else if(!hunters_dead)//all fugitives died, but none were taken in by the hunters. minor win
return FUGITIVE_RESULT_MINOR_FUGITIVE
else//all fugitives died, all hunters died, nobody brought back. seems weird to not give fugitives a victory if they managed to kill the hunters but literally no progress to either goal should lead to a nobody wins situation
return FUGITIVE_RESULT_STALEMATE
/datum/team/fugitive_hunters/roundend_report() //shows the number of fugitives, but not if they won in case there is no security
if(!members.len)
return
var/list/result = list()
result += "<div class='panel redborder'>...And <B>[members.len]</B> [backstory]s tried to hunt them down!"
for(var/datum/mind/M in members)
result += "<b>[printplayer(M)]</b>"
switch(get_result())
if(FUGITIVE_RESULT_BADASS_HUNTER)//use defines
result += "<span class='greentext big'>Badass [capitalize(backstory)] Victory!</span>"
result += "<B>The [backstory]s managed to capture every fugitive, alive!</B>"
if(FUGITIVE_RESULT_POSTMORTEM_HUNTER)
result += "<span class='greentext big'>Postmortem [capitalize(backstory)] Victory!</span>"
result += "<B>The [backstory]s managed to capture every fugitive, but all of them died! Spooky!</B>"
if(FUGITIVE_RESULT_MAJOR_HUNTER)
result += "<span class='greentext big'>Major [capitalize(backstory)] Victory</span>"
result += "<B>The [backstory]s managed to capture every fugitive, dead or alive.</B>"
if(FUGITIVE_RESULT_HUNTER_VICTORY)
result += "<span class='greentext big'>[capitalize(backstory)] Victory</span>"
result += "<B>The [backstory]s managed to capture a fugitive, dead or alive.</B>"
if(FUGITIVE_RESULT_MINOR_HUNTER)
result += "<span class='greentext big'>Minor [capitalize(backstory)] Victory</span>"
result += "<B>All the [backstory]s died, but managed to capture a fugitive, dead or alive.</B>"
if(FUGITIVE_RESULT_STALEMATE)
result += "<span class='neutraltext big'>Bloody Stalemate</span>"
result += "<B>Everyone died, and no fugitives were recovered!</B>"
if(FUGITIVE_RESULT_MINOR_FUGITIVE)
result += "<span class='redtext big'>Minor Fugitive Victory</span>"
result += "<B>All the fugitives died, but none were recovered!</B>"
if(FUGITIVE_RESULT_FUGITIVE_VICTORY)
result += "<span class='redtext big'>Fugitive Victory</span>"
result += "<B>A fugitive survived, and no bodies were recovered by the [backstory]s.</B>"
if(FUGITIVE_RESULT_MAJOR_FUGITIVE)
result += "<span class='redtext big'>Major Fugitive Victory</span>"
result += "<B>All of the fugitives survived and avoided capture!</B>"
else //get_result returned null- either bugged or no fugitives showed
result += "<span class='neutraltext big'>Prank Call!</span>"
result += "<B>[capitalize(backstory)]s were called, yet there were no fugitives...?</B>"
result += "</div>"
return result.Join("<br>")
+2 -1
View File
@@ -28,7 +28,8 @@
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
vision_range = 1 // Only attack when target is close
wander = FALSE
attacktext = "glomps"
attack_verb_continuous = "glomps"
attack_verb_simple = "glomp"
attack_sound = 'sound/effects/blobattack.ogg'
butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab = 2)
@@ -451,9 +451,9 @@
return
qdel(src)
/obj/machinery/nuclearbomb/tesla_act(power, tesla_flags)
/obj/machinery/nuclearbomb/zap_act(power, zap_flags)
..()
if(tesla_flags & TESLA_MACHINE_EXPLOSIVE)
if(zap_flags & ZAP_MACHINE_EXPLOSIVE)
qdel(src)//like the singulo, tesla deletes it. stops it from exploding over and over
#define NUKERANGE 127
@@ -606,6 +606,7 @@ This is here to make the tiles around the station mininuke change when it's arme
lefthand_file = 'icons/mob/inhands/equipment/idcards_lefthand.dmi'
righthand_file = 'icons/mob/inhands/equipment/idcards_righthand.dmi'
icon_state = "datadisk0"
w_volume = ITEM_VOLUME_DISK
/obj/item/disk/nuclear
name = "nuclear authentication disk"
@@ -5,6 +5,7 @@
job_rank = ROLE_OPERATIVE
antag_moodlet = /datum/mood_event/focused
threat = 10
skill_modifiers = list(/datum/skill_modifier/job/level/wiring)
var/datum/team/nuclear/nuke_team
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.
+16 -11
View File
@@ -27,19 +27,24 @@
sight = SEE_SELF
throwforce = 0
blood_volume = 0
has_field_of_vision = FALSE //we are a spoopy ghost
see_in_dark = 8
lighting_alpha = LIGHTING_PLANE_ALPHA_MOSTLY_INVISIBLE
response_help = "passes through"
response_disarm = "swings through"
response_harm = "punches through"
response_help_continuous = "passes through"
response_help_simple = "pass through"
response_disarm_continuous = "swings through"
response_disarm_simple = "swing through"
response_harm_continuous = "punches through"
response_harm_simple = "punch through"
unsuitable_atmos_damage = 0
damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0) //I don't know how you'd apply those, but revenants no-sell them anyway.
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
minbodytemp = 0
maxbodytemp = INFINITY
harm_intent_damage = 0
friendly = "touches"
friendly_verb_continuous = "touches"
friendly_verb_simple = "touch"
status_flags = 0
wander = FALSE
density = FALSE
@@ -102,7 +107,8 @@
mind.add_antag_datum(/datum/antagonist/revenant)
//Life, Stat, Hud Updates, and Say
/mob/living/simple_animal/revenant/Life()
/mob/living/simple_animal/revenant/BiologicalLife(seconds, times_fired)
. = ..()
if(stasis)
return
if(revealed && essence <= 0)
@@ -115,14 +121,13 @@
to_chat(src, "<span class='revenboldnotice'>You are once more concealed.</span>")
if(unstun_time && world.time >= unstun_time)
unstun_time = 0
notransform = FALSE
mob_transforming = FALSE
to_chat(src, "<span class='revenboldnotice'>You can move again!</span>")
if(essence_regenerating && !inhibited && essence < essence_regen_cap) //While inhibited, essence will not regenerate
essence = min(essence_regen_cap, essence+essence_regen_amount)
update_action_buttons_icon() //because we update something required by our spells in life, we need to update our buttons
update_spooky_icon()
update_health_hud()
..()
/mob/living/simple_animal/revenant/Stat()
..()
@@ -213,7 +218,7 @@
return 0
stasis = TRUE
to_chat(src, "<span class='revendanger'>NO! No... it's too late, you can feel your essence [pick("breaking apart", "drifting away")]...</span>")
notransform = TRUE
mob_transforming = TRUE
revealed = TRUE
invisibility = 0
playsound(src, 'sound/effects/screech.ogg', 100, 1)
@@ -255,7 +260,7 @@
return
if(time <= 0)
return
notransform = TRUE
mob_transforming = TRUE
if(!unstun_time)
to_chat(src, "<span class='revendanger'>You cannot move!</span>")
unstun_time = world.time + time
@@ -266,7 +271,7 @@
/mob/living/simple_animal/revenant/proc/update_spooky_icon()
if(revealed)
if(notransform)
if(mob_transforming)
if(draining)
icon_state = icon_drain
else
@@ -315,7 +320,7 @@
/mob/living/simple_animal/revenant/proc/death_reset()
revealed = FALSE
unreveal_time = 0
notransform = 0
mob_transforming = 0
unstun_time = 0
inhibited = FALSE
draining = FALSE
@@ -6,9 +6,12 @@
desc = "A large, menacing creature covered in armored black scales."
speak_emote = list("gurgles")
emote_hear = list("wails","screeches")
response_help = "thinks better of touching"
response_disarm = "flails at"
response_harm = "punches"
response_help_continuous = "thinks better of touching"
response_help_simple = "think better of touching"
response_disarm_continuous = "flails at"
response_disarm_simple = "flail at"
response_harm_continuous = "punches"
response_harm_simple = "punch"
icon = 'icons/mob/mob.dmi'
icon_state = "daemon"
icon_living = "daemon"
@@ -24,7 +27,8 @@
minbodytemp = 0
maxbodytemp = INFINITY
faction = list("slaughter")
attacktext = "wildly tears into"
attack_verb_continuous = "wildly tears into"
attack_verb_simple = "wildly tear into"
maxHealth = 200
health = 200
healable = 0
@@ -116,8 +120,10 @@
desc = "A large, adorable creature covered in armor with pink bows."
speak_emote = list("giggles","titters","chuckles")
emote_hear = list("guffaws","laughs")
response_help = "hugs"
attacktext = "wildly tickles"
response_help_continuous = "hugs"
response_help_simple = "hug"
attack_verb_continuous = "wildly tickles"
attack_verb_simple = "wildly tickle"
attack_sound = 'sound/items/bikehorn.ogg'
feast_sound = 'sound/spookoween/scary_horn2.ogg'
+4 -2
View File
@@ -84,9 +84,11 @@
hud_possible = list(ANTAG_HUD, DIAG_STAT_HUD, DIAG_HUD)
obj_damage = 0
environment_smash = ENVIRONMENT_SMASH_NONE
attacktext = "shocks"
attack_verb_continuous = "shocks"
attack_verb_simple = "shock"
attack_sound = 'sound/effects/empulse.ogg'
friendly = "pinches"
friendly_verb_continuous = "pinches"
friendly_verb_simple = "pinch"
speed = 0
faction = list("swarmer")
AIStatus = AI_OFF
@@ -4,6 +4,7 @@
weight = 3
chaos = 5
threat = 3
min_players = 25
uplink_filters = list(/datum/uplink_item/stealthy_weapons/romerol_kit)
/datum/traitor_class/human/hijack/forge_objectives(datum/antagonist/traitor/T)
@@ -4,6 +4,7 @@
weight = 2
chaos = 5
threat = 5
min_players = 20
uplink_filters = list(/datum/uplink_item/stealthy_weapons/romerol_kit,/datum/uplink_item/bundles_TC/contract_kit)
/datum/traitor_class/human/martyr/forge_objectives(datum/antagonist/traitor/T)
@@ -7,6 +7,8 @@ GLOBAL_LIST_EMPTY(traitor_classes)
var/chaos = 0
var/threat = 0
var/TC = 20
/// Minimum players for this to randomly roll via get_random_traitor_class().
var/min_players = 0
var/list/uplink_filters
/datum/traitor_class/New()
@@ -41,4 +43,4 @@ GLOBAL_LIST_EMPTY(traitor_classes)
/datum/traitor_class/proc/clean_up_traitor(datum/antagonist/traitor/T)
// Any effects that need to be cleaned up if traitor class is being swapped.
@@ -4,6 +4,7 @@
antagpanel_category = "Traitor"
job_rank = ROLE_TRAITOR
antag_moodlet = /datum/mood_event/focused
skill_modifiers = list(/datum/skill_modifier/job/level/wiring/basic)
var/special_role = ROLE_TRAITOR
var/employer = "The Syndicate"
var/give_objectives = TRUE
@@ -46,6 +47,8 @@
for(var/C in GLOB.traitor_classes)
if(!(C in blacklist))
var/datum/traitor_class/class = GLOB.traitor_classes[C]
if(class.min_players > length(GLOB.joined_player_list))
continue
var/weight = LOGISTIC_FUNCTION(1.5*class.weight,chaos_weight,class.chaos,0)
weights[C] = weight * 1000
var/choice = pickweight(weights, 0)
@@ -257,6 +257,8 @@ GLOBAL_LIST_INIT(blacklisted_malf_machines, typecacheof(list(
return
if (active)
return //prevent the AI from activating an already active doomsday
if (owner_AI.shunted)
return //prevent AI from activating doomsday while shunted.
active = TRUE
set_us_up_the_bomb(owner)
@@ -234,7 +234,7 @@
H.equip_to_slot_or_del(new /obj/item/clothing/shoes/roman(H), SLOT_SHOES)
H.put_in_hands(new /obj/item/shield/riot/roman(H), TRUE)
H.put_in_hands(new /obj/item/claymore(H), TRUE)
H.equip_to_slot_or_del(new /obj/item/twohanded/spear(H), SLOT_BACK)
H.equip_to_slot_or_del(new /obj/item/spear(H), SLOT_BACK)
/obj/item/voodoo
@@ -369,7 +369,7 @@
var/mob/living/carbon/last_user
/obj/item/warpwhistle/proc/interrupted(mob/living/carbon/user)
if(!user || QDELETED(src) || user.notransform)
if(!user || QDELETED(src) || user.mob_transforming)
on_cooldown = FALSE
return TRUE
return FALSE
@@ -226,7 +226,11 @@
/datum/spellbook_entry/lightningbolt/Buy(mob/living/carbon/human/user,obj/item/spellbook/book) //return 1 on success
. = ..()
user.flags_1 |= TESLA_IGNORE_1
ADD_TRAIT(user, TRAIT_TESLA_SHOCKIMMUNE, "lightning_bolt_spell")
/datum/spellbook_entry/lightningbolt/Refund(mob/living/carbon/human/user, obj/item/spellbook/book)
. = ..()
REMOVE_TRAIT(user, TRAIT_TESLA_SHOCKIMMUNE, "lightning_bolt_spell")
/datum/spellbook_entry/infinite_guns
name = "Lesser Summon Guns"
@@ -426,12 +430,12 @@
/datum/spellbook_entry/item/mjolnir
name = "Mjolnir"
desc = "A mighty hammer on loan from Thor, God of Thunder. It crackles with barely contained power."
item_path = /obj/item/twohanded/mjollnir
item_path = /obj/item/mjollnir
/datum/spellbook_entry/item/singularity_hammer
name = "Singularity Hammer"
desc = "A hammer that creates an intensely powerful field of gravity where it strikes, pulling everything nearby to the point of impact."
item_path = /obj/item/twohanded/singularityhammer
item_path = /obj/item/singularityhammer
/datum/spellbook_entry/item/battlemage
name = "Battlemage Armour"
@@ -556,6 +560,27 @@
. += "You cast it [times] times.<br>"
return .
/datum/spellbook_entry/summon/curse_of_madness
name = "Curse of Madness"
desc = "Curses the station, warping the minds of everyone inside, causing lasting traumas. Warning: this spell can affect you if not cast from a safe distance."
cost = 4
/datum/spellbook_entry/summon/curse_of_madness/Buy(mob/living/carbon/human/user, obj/item/spellbook/book)
SSblackbox.record_feedback("tally", "wizard_spell_learned", 1, name)
active = TRUE
var/message = stripped_input(user, "Whisper a secret truth to drive your victims to madness.", "Whispers of Madness")
if(!message)
return FALSE
curse_of_madness(user, message)
to_chat(user, "<span class='notice'>You have cast the curse of insanity!</span>")
playsound(user, 'sound/magic/mandswap.ogg', 50, 1)
return TRUE
/datum/spellbook_entry/summon/curse_of_madness/IsAvailible()
if(!SSticker.mode) // In case spellbook is placed on map
return FALSE
return (!CONFIG_GET(flag/no_summon_traumas) && ..())
/obj/item/spellbook
name = "spell book"
desc = "An unearthly tome that glows with power."
+1 -1
View File
@@ -1,7 +1,7 @@
/obj/item/organ/genital
color = "#fcccb3"
w_class = WEIGHT_CLASS_SMALL
organ_flags = ORGAN_NO_DISMEMBERMENT
organ_flags = ORGAN_NO_DISMEMBERMENT|ORGAN_EDIBLE
var/shape
var/sensitivity = 1 // wow if this were ever used that'd be cool but it's not but i'm keeping it for my unshit code
var/genital_flags //see citadel_defines.dm
+100
View File
@@ -0,0 +1,100 @@
/*
Asset cache quick users guide:
Make a datum in asset_list_items.dm with your assets for your thing.
Checkout asset_list.dm for the helper subclasses
The simple subclass will most like be of use for most cases.
Then call get_asset_datum() with the type of the datum you created and store the return
Then call .send(client) on that stored return value.
Note: If your code uses output() with assets you will need to call asset_flush on the client and wait for it to return before calling output(). You only need do this if .send(client) returned TRUE
*/
//When sending mutiple assets, how many before we give the client a quaint little sending resources message
#define ASSET_CACHE_TELL_CLIENT_AMOUNT 8
//This proc sends the asset to the client, but only if it needs it.
//This proc blocks(sleeps) unless verify is set to false
/proc/send_asset(client/client, asset_name)
return send_asset_list(client, list(asset_name))
/// Sends a list of assets to a client
/// This proc will no longer block, use client.asset_flush() if you to need know when the client has all assets (such as for output()). (This is not required for browse() calls as they use the same message queue as asset sends)
/// client - a client or mob
/// asset_list - A list of asset filenames to be sent to the client.
/// Returns TRUE if any assets were sent.
/proc/send_asset_list(client/client, list/asset_list)
if(!istype(client))
if(ismob(client))
var/mob/M = client
if(M.client)
client = M.client
else
return
else
return
var/list/unreceived = list()
for (var/asset_name in asset_list)
var/datum/asset_cache_item/asset = SSassets.cache[asset_name]
if (!asset)
continue
var/asset_file = asset.resource
if (!asset_file)
continue
var/asset_md5 = asset.md5
if (client.sent_assets[asset_name] == asset_md5)
continue
unreceived[asset_name] = asset_md5
if (unreceived.len)
if (unreceived.len >= ASSET_CACHE_TELL_CLIENT_AMOUNT)
to_chat(client, "Sending Resources...")
for(var/asset in unreceived)
var/datum/asset_cache_item/ACI
if ((ACI = SSassets.cache[asset]))
log_asset("Sending asset [asset] to client [client]")
client << browse_rsc(ACI.resource, asset)
client.sent_assets |= unreceived
addtimer(CALLBACK(client, /client/proc/asset_cache_update_json), 1 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE)
return TRUE
return FALSE
//This proc will download the files without clogging up the browse() queue, used for passively sending files on connection start.
//The proc calls procs that sleep for long times.
/proc/getFilesSlow(client/client, list/files, register_asset = TRUE, filerate = 3)
var/startingfilerate = filerate
for(var/file in files)
if (!client)
break
if (register_asset)
register_asset(file, files[file])
if (send_asset(client, file))
if (!(--filerate))
filerate = startingfilerate
client.asset_flush()
stoplag(0) //queuing calls like this too quickly can cause issues in some client versions
//This proc "registers" an asset, it adds it to the cache for further use, you cannot touch it from this point on or you'll fuck things up.
//icons and virtual assets get copied to the dyn rsc before use
/proc/register_asset(asset_name, asset)
var/datum/asset_cache_item/ACI = new(asset_name, asset)
//this is technically never something that was supported and i want metrics on how often it happens if at all.
if (SSassets.cache[asset_name])
var/datum/asset_cache_item/OACI = SSassets.cache[asset_name]
if (OACI.md5 != ACI.md5)
stack_trace("ERROR: new asset added to the asset cache with the same name as another asset: [asset_name] existing asset md5: [OACI.md5] new asset md5:[ACI.md5]")
else
var/list/stacktrace = gib_stack_trace()
log_asset("WARNING: dupe asset added to the asset cache: [asset_name] existing asset md5: [OACI.md5] new asset md5:[ACI.md5]\n[stacktrace.Join("\n")]")
SSassets.cache[asset_name] = ACI
//Generated names do not include file extention.
//Used mainly for code that deals with assets in a generic way
//The same asset will always lead to the same asset name
/proc/generate_asset_name(file)
return "asset.[md5(fcopy_rsc(file))]"
@@ -0,0 +1,51 @@
/// Process asset cache client topic calls for "asset_cache_confirm_arrival=[INT]"
/client/proc/asset_cache_confirm_arrival(job_id)
var/asset_cache_job = round(text2num(job_id))
//because we skip the limiter, we have to make sure this is a valid arrival and not somebody tricking us into letting them append to a list without limit.
if (asset_cache_job > 0 && asset_cache_job <= last_asset_job && !(completed_asset_jobs["[asset_cache_job]"]))
completed_asset_jobs["[asset_cache_job]"] = TRUE
last_completed_asset_job = max(last_completed_asset_job, asset_cache_job)
else
return asset_cache_job || TRUE
/// Process asset cache client topic calls for "asset_cache_preload_data=[HTML+JSON_STRING]
/client/proc/asset_cache_preload_data(data)
/*var/jsonend = findtextEx(data, "{{{ENDJSONDATA}}}")
if (!jsonend)
CRASH("invalid asset_cache_preload_data, no jsonendmarker")*/
//var/json = html_decode(copytext(data, 1, jsonend))
var/json = data
var/list/preloaded_assets = json_decode(json)
for (var/preloaded_asset in preloaded_assets)
if (copytext(preloaded_asset, findlasttext(preloaded_asset, ".")+1) in list("js", "jsm", "htm", "html"))
preloaded_assets -= preloaded_asset
continue
sent_assets |= preloaded_assets
/// Updates the client side stored html/json combo file used to keep track of what assets the client has between restarts/reconnects.
/client/proc/asset_cache_update_json(verify = FALSE, list/new_assets = list())
if (world.time - connection_time < 10 SECONDS) //don't override the existing data file on a new connection
return
if (!islist(new_assets))
new_assets = list("[new_assets]" = md5(SSassets.cache[new_assets]))
src << browse(json_encode(new_assets|sent_assets), "file=asset_data.json&display=0")
/// Blocks until all currently sending browser assets have been sent.
/// Due to byond limitations, this proc will sleep for 1 client round trip even if the client has no pending asset sends.
/// This proc will return an untrue value if it had to return before confirming the send, such as timeout or the client going away.
/client/proc/asset_flush(timeout = 50)
var/job = ++last_asset_job
var/t = 0
var/timeout_time = timeout
src << browse({"<script>window.location.href="?asset_cache_confirm_arrival=[job]"</script>"}, "window=asset_cache_browser&file=asset_cache_send_verify.htm")
while(!completed_asset_jobs["[job]"] && t < timeout_time) // Reception is handled in Topic()
stoplag(1) // Lock up the caller until this is received.
t++
if (t < timeout_time)
return TRUE
@@ -0,0 +1,21 @@
/**
* # asset_cache_item
*
* An internal datum containing info on items in the asset cache. Mainly used to cache md5 info for speed.
**/
/datum/asset_cache_item
var/name
var/md5
var/resource
/datum/asset_cache_item/New(name, file)
if (!isfile(file))
file = fcopy_rsc(file)
md5 = md5(file)
if (!md5)
md5 = md5(fcopy_rsc(file))
if (!md5)
CRASH("invalid asset sent to asset cache")
debug_world_log("asset cache unexpected success of second fcopy_rsc")
src.name = name
resource = file
+228
View File
@@ -0,0 +1,228 @@
//These datums are used to populate the asset cache, the proc "register()" does this.
//Place any asset datums you create in asset_list_items.dm
//all of our asset datums, used for referring to these later
GLOBAL_LIST_EMPTY(asset_datums)
//get an assetdatum or make a new one
/proc/get_asset_datum(type)
return GLOB.asset_datums[type] || new type()
/datum/asset
var/_abstract = /datum/asset
/datum/asset/New()
GLOB.asset_datums[type] = src
register()
/datum/asset/proc/register()
return
/datum/asset/proc/send(client)
return
//If you don't need anything complicated.
/datum/asset/simple
_abstract = /datum/asset/simple
var/assets = list()
/datum/asset/simple/register()
for(var/asset_name in assets)
register_asset(asset_name, assets[asset_name])
/datum/asset/simple/send(client)
. = send_asset_list(client, assets)
// For registering or sending multiple others at once
/datum/asset/group
_abstract = /datum/asset/group
var/list/children
/datum/asset/group/register()
for(var/type in children)
get_asset_datum(type)
/datum/asset/group/send(client/C)
for(var/type in children)
var/datum/asset/A = get_asset_datum(type)
. = A.send(C) || .
// spritesheet implementation - coalesces various icons into a single .png file
// and uses CSS to select icons out of that file - saves on transferring some
// 1400-odd individual PNG files
#define SPR_SIZE 1
#define SPR_IDX 2
#define SPRSZ_COUNT 1
#define SPRSZ_ICON 2
#define SPRSZ_STRIPPED 3
/datum/asset/spritesheet
_abstract = /datum/asset/spritesheet
var/name
var/list/sizes = list() // "32x32" -> list(10, icon/normal, icon/stripped)
var/list/sprites = list() // "foo_bar" -> list("32x32", 5)
/datum/asset/spritesheet/register()
if (!name)
CRASH("spritesheet [type] cannot register without a name")
ensure_stripped()
var/res_name = "spritesheet_[name].css"
var/fname = "data/spritesheets/[res_name]"
fdel(fname)
text2file(generate_css(), fname)
register_asset(res_name, fcopy_rsc(fname))
fdel(fname)
for(var/size_id in sizes)
var/size = sizes[size_id]
register_asset("[name]_[size_id].png", size[SPRSZ_STRIPPED])
/datum/asset/spritesheet/send(client/C)
if (!name)
return
var/all = list("spritesheet_[name].css")
for(var/size_id in sizes)
all += "[name]_[size_id].png"
. = send_asset_list(C, all)
/datum/asset/spritesheet/proc/ensure_stripped(sizes_to_strip = sizes)
for(var/size_id in sizes_to_strip)
var/size = sizes[size_id]
if (size[SPRSZ_STRIPPED])
continue
// save flattened version
var/fname = "data/spritesheets/[name]_[size_id].png"
fcopy(size[SPRSZ_ICON], fname)
var/error = rustg_dmi_strip_metadata(fname)
if(length(error))
stack_trace("Failed to strip [name]_[size_id].png: [error]")
size[SPRSZ_STRIPPED] = icon(fname)
fdel(fname)
/datum/asset/spritesheet/proc/generate_css()
var/list/out = list()
for (var/size_id in sizes)
var/size = sizes[size_id]
var/icon/tiny = size[SPRSZ_ICON]
out += ".[name][size_id]{display:inline-block;width:[tiny.Width()]px;height:[tiny.Height()]px;background:url('[name]_[size_id].png') no-repeat;}"
for (var/sprite_id in sprites)
var/sprite = sprites[sprite_id]
var/size_id = sprite[SPR_SIZE]
var/idx = sprite[SPR_IDX]
var/size = sizes[size_id]
var/icon/tiny = size[SPRSZ_ICON]
var/icon/big = size[SPRSZ_STRIPPED]
var/per_line = big.Width() / tiny.Width()
var/x = (idx % per_line) * tiny.Width()
var/y = round(idx / per_line) * tiny.Height()
out += ".[name][size_id].[sprite_id]{background-position:-[x]px -[y]px;}"
return out.Join("\n")
/datum/asset/spritesheet/proc/Insert(sprite_name, icon/I, icon_state="", dir=SOUTH, frame=1, moving=FALSE)
I = icon(I, icon_state=icon_state, dir=dir, frame=frame, moving=moving)
if (!I || !length(icon_states(I))) // that direction or state doesn't exist
return
var/size_id = "[I.Width()]x[I.Height()]"
var/size = sizes[size_id]
if (sprites[sprite_name])
CRASH("duplicate sprite \"[sprite_name]\" in sheet [name] ([type])")
if (size)
var/position = size[SPRSZ_COUNT]++
var/icon/sheet = size[SPRSZ_ICON]
size[SPRSZ_STRIPPED] = null
sheet.Insert(I, icon_state=sprite_name)
sprites[sprite_name] = list(size_id, position)
else
sizes[size_id] = size = list(1, I, null)
sprites[sprite_name] = list(size_id, 0)
/datum/asset/spritesheet/proc/InsertAll(prefix, icon/I, list/directions)
if (length(prefix))
prefix = "[prefix]-"
if (!directions)
directions = list(SOUTH)
for (var/icon_state_name in icon_states(I))
for (var/direction in directions)
var/prefix2 = (directions.len > 1) ? "[dir2text(direction)]-" : ""
Insert("[prefix][prefix2][icon_state_name]", I, icon_state=icon_state_name, dir=direction)
/datum/asset/spritesheet/proc/css_tag()
return {"<link rel="stylesheet" href="spritesheet_[name].css" />"}
/datum/asset/spritesheet/proc/icon_tag(sprite_name)
var/sprite = sprites[sprite_name]
if (!sprite)
return null
var/size_id = sprite[SPR_SIZE]
return {"<span class="[name][size_id] [sprite_name]"></span>"}
/datum/asset/spritesheet/proc/icon_class_name(sprite_name)
var/sprite = sprites[sprite_name]
if (!sprite)
return null
var/size_id = sprite[SPR_SIZE]
return {"[name][size_id] [sprite_name]"}
#undef SPR_SIZE
#undef SPR_IDX
#undef SPRSZ_COUNT
#undef SPRSZ_ICON
#undef SPRSZ_STRIPPED
/datum/asset/spritesheet/simple
_abstract = /datum/asset/spritesheet/simple
var/list/assets
/datum/asset/spritesheet/simple/register()
for (var/key in assets)
Insert(key, assets[key])
..()
//Generates assets based on iconstates of a single icon
/datum/asset/simple/icon_states
_abstract = /datum/asset/simple/icon_states
var/icon
var/list/directions = list(SOUTH)
var/frame = 1
var/movement_states = FALSE
var/prefix = "default" //asset_name = "[prefix].[icon_state_name].png"
var/generic_icon_names = FALSE //generate icon filenames using generate_asset_name() instead the above format
/datum/asset/simple/icon_states/register(_icon = icon)
for(var/icon_state_name in icon_states(_icon))
for(var/direction in directions)
var/asset = icon(_icon, icon_state_name, direction, frame, movement_states)
if (!asset)
continue
asset = fcopy_rsc(asset) //dedupe
var/prefix2 = (directions.len > 1) ? "[dir2text(direction)]." : ""
var/asset_name = sanitize_filename("[prefix].[prefix2][icon_state_name].png")
if (generic_icon_names)
asset_name = "[generate_asset_name(asset)].png"
register_asset(asset_name, asset)
/datum/asset/simple/icon_states/multiple_icons
_abstract = /datum/asset/simple/icon_states/multiple_icons
var/list/icons
/datum/asset/simple/icon_states/multiple_icons/register()
for(var/i in icons)
..(i)
@@ -1,399 +1,22 @@
/*
Asset cache quick users guide:
Make a datum at the bottom of this file with your assets for your thing.
The simple subsystem will most like be of use for most cases.
Then call get_asset_datum() with the type of the datum you created and store the return
Then call .send(client) on that stored return value.
You can set verify to TRUE if you want send() to sleep until the client has the assets.
*/
// Amount of time(ds) MAX to send per asset, if this get exceeded we cancel the sleeping.
// This is doubled for the first asset, then added per asset after
#define ASSET_CACHE_SEND_TIMEOUT 7
//When sending mutiple assets, how many before we give the client a quaint little sending resources message
#define ASSET_CACHE_TELL_CLIENT_AMOUNT 8
//When passively preloading assets, how many to send at once? Too high creates noticable lag where as too low can flood the client's cache with "verify" files
#define ASSET_CACHE_PRELOAD_CONCURRENT 3
/client
var/list/cache = list() // List of all assets sent to this client by the asset cache.
var/list/completed_asset_jobs = list() // List of all completed jobs, awaiting acknowledgement.
var/list/sending = list()
var/last_asset_job = 0 // Last job done.
//This proc sends the asset to the client, but only if it needs it.
//This proc blocks(sleeps) unless verify is set to false
/proc/send_asset(client/client, asset_name, verify = TRUE)
if(!istype(client))
if(ismob(client))
var/mob/M = client
if(M.client)
client = M.client
else
return 0
else
return 0
if(client.cache.Find(asset_name) || client.sending.Find(asset_name))
return 0
client << browse_rsc(SSassets.cache[asset_name], asset_name)
if(!verify)
client.cache += asset_name
return 1
client.sending |= asset_name
var/job = ++client.last_asset_job
client << browse({"
<script>
window.location.href="?asset_cache_confirm_arrival=[job]"
</script>
"}, "window=asset_cache_browser")
var/t = 0
var/timeout_time = (ASSET_CACHE_SEND_TIMEOUT * client.sending.len) + ASSET_CACHE_SEND_TIMEOUT
while(client && !client.completed_asset_jobs.Find(job) && t < timeout_time) // Reception is handled in Topic()
stoplag(1) // Lock up the caller until this is received.
t++
if(client)
client.sending -= asset_name
client.cache |= asset_name
client.completed_asset_jobs -= job
return 1
//This proc blocks(sleeps) unless verify is set to false
/proc/send_asset_list(client/client, list/asset_list, verify = TRUE)
if(!istype(client))
if(ismob(client))
var/mob/M = client
if(M.client)
client = M.client
else
return 0
else
return 0
var/list/unreceived = asset_list - (client.cache + client.sending)
if(!unreceived || !unreceived.len)
return 0
if (unreceived.len >= ASSET_CACHE_TELL_CLIENT_AMOUNT)
to_chat(client, "Sending Resources...")
for(var/asset in unreceived)
if (asset in SSassets.cache)
client << browse_rsc(SSassets.cache[asset], asset)
if(!verify) // Can't access the asset cache browser, rip.
client.cache += unreceived
return 1
client.sending |= unreceived
var/job = ++client.last_asset_job
client << browse({"
<script>
window.location.href="?asset_cache_confirm_arrival=[job]"
</script>
"}, "window=asset_cache_browser")
var/t = 0
var/timeout_time = ASSET_CACHE_SEND_TIMEOUT * client.sending.len
while(client && !client.completed_asset_jobs.Find(job) && t < timeout_time) // Reception is handled in Topic()
stoplag(1) // Lock up the caller until this is received.
t++
if(client)
client.sending -= unreceived
client.cache |= unreceived
client.completed_asset_jobs -= job
return 1
//This proc will download the files without clogging up the browse() queue, used for passively sending files on connection start.
//The proc calls procs that sleep for long times.
/proc/getFilesSlow(client/client, list/files, register_asset = TRUE)
var/concurrent_tracker = 1
for(var/file in files)
if (!client)
break
if (register_asset)
register_asset(file, files[file])
if (concurrent_tracker >= ASSET_CACHE_PRELOAD_CONCURRENT)
concurrent_tracker = 1
send_asset(client, file)
else
concurrent_tracker++
send_asset(client, file, verify=FALSE)
stoplag(0) //queuing calls like this too quickly can cause issues in some client versions
//This proc "registers" an asset, it adds it to the cache for further use, you cannot touch it from this point on or you'll fuck things up.
//if it's an icon or something be careful, you'll have to copy it before further use.
/proc/register_asset(asset_name, asset)
SSassets.cache[asset_name] = asset
//Generated names do not include file extention.
//Used mainly for code that deals with assets in a generic way
//The same asset will always lead to the same asset name
/proc/generate_asset_name(file)
return "asset.[md5(fcopy_rsc(file))]"
//These datums are used to populate the asset cache, the proc "register()" does this.
//all of our asset datums, used for referring to these later
GLOBAL_LIST_EMPTY(asset_datums)
//get an assetdatum or make a new one
/proc/get_asset_datum(type)
return GLOB.asset_datums[type] || new type()
/datum/asset
var/_abstract = /datum/asset
/datum/asset/New()
GLOB.asset_datums[type] = src
register()
/datum/asset/proc/register()
return
/datum/asset/proc/send(client)
return
//If you don't need anything complicated.
/datum/asset/simple
_abstract = /datum/asset/simple
var/assets = list()
var/verify = FALSE
/datum/asset/simple/register()
for(var/asset_name in assets)
register_asset(asset_name, assets[asset_name])
/datum/asset/simple/send(client)
send_asset_list(client,assets,verify)
// For registering or sending multiple others at once
/datum/asset/group
_abstract = /datum/asset/group
var/list/children
/datum/asset/group/register()
for(var/type in children)
get_asset_datum(type)
/datum/asset/group/send(client/C)
for(var/type in children)
var/datum/asset/A = get_asset_datum(type)
A.send(C)
// spritesheet implementation - coalesces various icons into a single .png file
// and uses CSS to select icons out of that file - saves on transferring some
// 1400-odd individual PNG files
#define SPR_SIZE 1
#define SPR_IDX 2
#define SPRSZ_COUNT 1
#define SPRSZ_ICON 2
#define SPRSZ_STRIPPED 3
/datum/asset/spritesheet
_abstract = /datum/asset/spritesheet
var/name
var/list/sizes = list() // "32x32" -> list(10, icon/normal, icon/stripped)
var/list/sprites = list() // "foo_bar" -> list("32x32", 5)
var/verify = FALSE
/datum/asset/spritesheet/register()
if (!name)
CRASH("spritesheet [type] cannot register without a name")
ensure_stripped()
var/res_name = "spritesheet_[name].css"
var/fname = "data/spritesheets/[res_name]"
fdel(fname)
text2file(generate_css(), fname)
register_asset(res_name, fcopy_rsc(fname))
fdel(fname)
for(var/size_id in sizes)
var/size = sizes[size_id]
register_asset("[name]_[size_id].png", size[SPRSZ_STRIPPED])
/datum/asset/spritesheet/send(client/C)
if (!name)
return
var/all = list("spritesheet_[name].css")
for(var/size_id in sizes)
all += "[name]_[size_id].png"
send_asset_list(C, all, verify)
/datum/asset/spritesheet/proc/ensure_stripped(sizes_to_strip = sizes)
for(var/size_id in sizes_to_strip)
var/size = sizes[size_id]
if (size[SPRSZ_STRIPPED])
continue
// save flattened version
var/fname = "data/spritesheets/[name]_[size_id].png"
fcopy(size[SPRSZ_ICON], fname)
var/error = rustg_dmi_strip_metadata(fname)
if(length(error))
stack_trace("Failed to strip [name]_[size_id].png: [error]")
size[SPRSZ_STRIPPED] = icon(fname)
fdel(fname)
/datum/asset/spritesheet/proc/generate_css()
var/list/out = list()
for (var/size_id in sizes)
var/size = sizes[size_id]
var/icon/tiny = size[SPRSZ_ICON]
out += ".[name][size_id]{display:inline-block;width:[tiny.Width()]px;height:[tiny.Height()]px;background:url('[name]_[size_id].png') no-repeat;}"
for (var/sprite_id in sprites)
var/sprite = sprites[sprite_id]
var/size_id = sprite[SPR_SIZE]
var/idx = sprite[SPR_IDX]
var/size = sizes[size_id]
var/icon/tiny = size[SPRSZ_ICON]
var/icon/big = size[SPRSZ_STRIPPED]
var/per_line = big.Width() / tiny.Width()
var/x = (idx % per_line) * tiny.Width()
var/y = round(idx / per_line) * tiny.Height()
out += ".[name][size_id].[sprite_id]{background-position:-[x]px -[y]px;}"
return out.Join("\n")
/datum/asset/spritesheet/proc/Insert(sprite_name, icon/I, icon_state="", dir=SOUTH, frame=1, moving=FALSE)
I = icon(I, icon_state=icon_state, dir=dir, frame=frame, moving=moving)
if (!I || !length(icon_states(I))) // that direction or state doesn't exist
return
var/size_id = "[I.Width()]x[I.Height()]"
var/size = sizes[size_id]
if (sprites[sprite_name])
CRASH("duplicate sprite \"[sprite_name]\" in sheet [name] ([type])")
if (size)
var/position = size[SPRSZ_COUNT]++
var/icon/sheet = size[SPRSZ_ICON]
size[SPRSZ_STRIPPED] = null
sheet.Insert(I, icon_state=sprite_name)
sprites[sprite_name] = list(size_id, position)
else
sizes[size_id] = size = list(1, I, null)
sprites[sprite_name] = list(size_id, 0)
/datum/asset/spritesheet/proc/InsertAll(prefix, icon/I, list/directions)
if (length(prefix))
prefix = "[prefix]-"
if (!directions)
directions = list(SOUTH)
for (var/icon_state_name in icon_states(I))
for (var/direction in directions)
var/prefix2 = (directions.len > 1) ? "[dir2text(direction)]-" : ""
Insert("[prefix][prefix2][icon_state_name]", I, icon_state=icon_state_name, dir=direction)
/datum/asset/spritesheet/proc/css_tag()
return {"<link rel="stylesheet" href="spritesheet_[name].css" />"}
/datum/asset/spritesheet/proc/icon_tag(sprite_name)
var/sprite = sprites[sprite_name]
if (!sprite)
return null
var/size_id = sprite[SPR_SIZE]
return {"<span class="[name][size_id] [sprite_name]"></span>"}
/datum/asset/spritesheet/proc/icon_class_name(sprite_name)
var/sprite = sprites[sprite_name]
if (!sprite)
return null
var/size_id = sprite[SPR_SIZE]
return {"[name][size_id] [sprite_name]"}
#undef SPR_SIZE
#undef SPR_IDX
#undef SPRSZ_COUNT
#undef SPRSZ_ICON
#undef SPRSZ_STRIPPED
/datum/asset/spritesheet/simple
_abstract = /datum/asset/spritesheet/simple
var/list/assets
/datum/asset/spritesheet/simple/register()
for (var/key in assets)
Insert(key, assets[key])
..()
//Generates assets based on iconstates of a single icon
/datum/asset/simple/icon_states
_abstract = /datum/asset/simple/icon_states
var/icon
var/list/directions = list(SOUTH)
var/frame = 1
var/movement_states = FALSE
var/prefix = "default" //asset_name = "[prefix].[icon_state_name].png"
var/generic_icon_names = FALSE //generate icon filenames using generate_asset_name() instead the above format
verify = FALSE
/datum/asset/simple/icon_states/register(_icon = icon)
for(var/icon_state_name in icon_states(_icon))
for(var/direction in directions)
var/asset = icon(_icon, icon_state_name, direction, frame, movement_states)
if (!asset)
continue
asset = fcopy_rsc(asset) //dedupe
var/prefix2 = (directions.len > 1) ? "[dir2text(direction)]." : ""
var/asset_name = sanitize_filename("[prefix].[prefix2][icon_state_name].png")
if (generic_icon_names)
asset_name = "[generate_asset_name(asset)].png"
register_asset(asset_name, asset)
/datum/asset/simple/icon_states/multiple_icons
_abstract = /datum/asset/simple/icon_states/multiple_icons
var/list/icons
/datum/asset/simple/icon_states/multiple_icons/register()
for(var/i in icons)
..(i)
//DEFINITIONS FOR ASSET DATUMS START HERE.
/* uncomment this and delete the tgui def bellow this for the new tgui
/datum/asset/simple/tgui
assets = list(
// tgui
"tgui.bundle.js" = 'tgui/packages/tgui/public/tgui.bundle.js',
"tgui.bundle.css" = 'tgui/packages/tgui/public/tgui.bundle.css',
)
*/
/datum/asset/simple/tgui
assets = list(
// Old TGUI
"tgui.css" = 'tgui/assets/tgui.css',
"tgui.js" = 'tgui/assets/tgui.js',
// tgui-next
"tgui-main.html" = 'tgui-next/packages/tgui/public/tgui-main.html',
"tgui-fallback.html" = 'tgui-next/packages/tgui/public/tgui-fallback.html',
"tgui.bundle.js" = 'tgui-next/packages/tgui/public/tgui.bundle.js',
"tgui.bundle.css" = 'tgui-next/packages/tgui/public/tgui.bundle.css',
// Old TGUI compatability
"tgui-fallback.html" = 'tgui-next/packages/tgui/public/tgui-fallback.html',
"shim-html5shiv.js" = 'tgui-next/packages/tgui/public/shim-html5shiv.js',
"shim-ie8.js" = 'tgui-next/packages/tgui/public/shim-ie8.js',
"shim-dom4.js" = 'tgui-next/packages/tgui/public/shim-dom4.js',
@@ -434,8 +57,16 @@ GLOBAL_LIST_EMPTY(asset_datums)
"smmon_4.gif" = 'icons/program_icons/smmon_4.gif',
"smmon_5.gif" = 'icons/program_icons/smmon_5.gif',
"smmon_6.gif" = 'icons/program_icons/smmon_6.gif'
//"borg_mon.gif" = 'icons/program_icons/borg_mon.gif'
)
/* uncomment if you're porting the new ntnet app
/datum/asset/simple/radar_assets
assets = list(
"ntosradarbackground.png" = 'icons/UI_Icons/tgui/ntosradar_background.png',
"ntosradarpointer.png" = 'icons/UI_Icons/tgui/ntosradar_pointer.png',
"ntosradarpointerS.png" = 'icons/UI_Icons/tgui/ntosradar_pointer_S.png'
)
*/
/datum/asset/spritesheet/simple/pda
name = "pda"
assets = list(
@@ -464,6 +95,7 @@ GLOBAL_LIST_EMPTY(asset_datums)
"refresh" = 'icons/pda_icons/pda_refresh.png',
"scanner" = 'icons/pda_icons/pda_scanner.png',
"signaler" = 'icons/pda_icons/pda_signaler.png',
//"skills" = 'icons/pda_icons/pda_skills.png',
"status" = 'icons/pda_icons/pda_status.png',
"dronephone" = 'icons/pda_icons/pda_dronephone.png',
"emoji" = 'icons/pda_icons/pda_emoji.png'
@@ -483,51 +115,10 @@ GLOBAL_LIST_EMPTY(asset_datums)
"stamp-cap" = 'icons/stamp_icons/large_stamp-cap.png',
"stamp-qm" = 'icons/stamp_icons/large_stamp-qm.png',
"stamp-law" = 'icons/stamp_icons/large_stamp-law.png'
)
/datum/asset/spritesheet/simple/minesweeper
name = "minesweeper"
assets = list(
"1" = 'icons/UI_Icons/minesweeper_tiles/one.png',
"2" = 'icons/UI_Icons/minesweeper_tiles/two.png',
"3" = 'icons/UI_Icons/minesweeper_tiles/three.png',
"4" = 'icons/UI_Icons/minesweeper_tiles/four.png',
"5" = 'icons/UI_Icons/minesweeper_tiles/five.png',
"6" = 'icons/UI_Icons/minesweeper_tiles/six.png',
"7" = 'icons/UI_Icons/minesweeper_tiles/seven.png',
"8" = 'icons/UI_Icons/minesweeper_tiles/eight.png',
"empty" = 'icons/UI_Icons/minesweeper_tiles/empty.png',
"flag" = 'icons/UI_Icons/minesweeper_tiles/flag.png',
"hidden" = 'icons/UI_Icons/minesweeper_tiles/hidden.png',
"mine" = 'icons/UI_Icons/minesweeper_tiles/mine.png',
"minehit" = 'icons/UI_Icons/minesweeper_tiles/minehit.png'
)
/datum/asset/spritesheet/simple/pills
name = "pills"
assets = list(
"pill1" = 'icons/UI_Icons/Pills/pill1.png',
"pill2" = 'icons/UI_Icons/Pills/pill2.png',
"pill3" = 'icons/UI_Icons/Pills/pill3.png',
"pill4" = 'icons/UI_Icons/Pills/pill4.png',
"pill5" = 'icons/UI_Icons/Pills/pill5.png',
"pill6" = 'icons/UI_Icons/Pills/pill6.png',
"pill7" = 'icons/UI_Icons/Pills/pill7.png',
"pill8" = 'icons/UI_Icons/Pills/pill8.png',
"pill9" = 'icons/UI_Icons/Pills/pill9.png',
"pill10" = 'icons/UI_Icons/Pills/pill10.png',
"pill11" = 'icons/UI_Icons/Pills/pill11.png',
"pill12" = 'icons/UI_Icons/Pills/pill12.png',
"pill13" = 'icons/UI_Icons/Pills/pill13.png',
"pill14" = 'icons/UI_Icons/Pills/pill14.png',
"pill15" = 'icons/UI_Icons/Pills/pill15.png',
"pill16" = 'icons/UI_Icons/Pills/pill16.png',
"pill17" = 'icons/UI_Icons/Pills/pill17.png',
"pill18" = 'icons/UI_Icons/Pills/pill18.png',
"pill19" = 'icons/UI_Icons/Pills/pill19.png',
"pill20" = 'icons/UI_Icons/Pills/pill20.png',
"pill21" = 'icons/UI_Icons/Pills/pill21.png',
"pill22" = 'icons/UI_Icons/Pills/pill22.png',
//"stamp-chap" = 'icons/stamp_icons/large_stamp-chap.png',
//"stamp-mime" = 'icons/stamp_icons/large_stamp-mime.png',
//"stamp-centcom" = 'icons/stamp_icons/large_stamp-centcom.png',
//"stamp-syndicate" = 'icons/stamp_icons/large_stamp-syndicate.png'
)
/datum/asset/simple/IRV
@@ -573,23 +164,20 @@ GLOBAL_LIST_EMPTY(asset_datums)
)
/datum/asset/simple/jquery
verify = FALSE
assets = list(
"jquery.min.js" = 'code/modules/goonchat/browserassets/js/jquery.min.js',
)
/datum/asset/simple/goonchat
verify = FALSE
assets = list(
"json2.min.js" = 'code/modules/goonchat/browserassets/js/json2.min.js',
"browserOutput.js" = 'code/modules/goonchat/browserassets/js/browserOutput.js',
"browserOutput.css" = 'code/modules/goonchat/browserassets/css/browserOutput.css',
"browserOutput_dark.css" = 'code/modules/goonchat/browserassets/css/browserOutput_dark.css',
"browserOutput_dark.css" = 'code/modules/goonchat/browserassets/css/browserOutput_dark.css', //dark theme, cit specific
"browserOutput_light.css" = 'code/modules/goonchat/browserassets/css/browserOutput_light.css'
)
/datum/asset/simple/fontawesome
verify = FALSE
assets = list(
"fa-regular-400.eot" = 'html/font-awesome/webfonts/fa-regular-400.eot',
"fa-regular-400.woff" = 'html/font-awesome/webfonts/fa-regular-400.woff',
@@ -630,6 +218,90 @@ GLOBAL_LIST_EMPTY(asset_datums)
"none_button.png" = 'html/none_button.png',
)
/datum/asset/spritesheet/simple/minesweeper
name = "minesweeper"
assets = list(
"1" = 'icons/UI_Icons/minesweeper_tiles/one.png',
"2" = 'icons/UI_Icons/minesweeper_tiles/two.png',
"3" = 'icons/UI_Icons/minesweeper_tiles/three.png',
"4" = 'icons/UI_Icons/minesweeper_tiles/four.png',
"5" = 'icons/UI_Icons/minesweeper_tiles/five.png',
"6" = 'icons/UI_Icons/minesweeper_tiles/six.png',
"7" = 'icons/UI_Icons/minesweeper_tiles/seven.png',
"8" = 'icons/UI_Icons/minesweeper_tiles/eight.png',
"empty" = 'icons/UI_Icons/minesweeper_tiles/empty.png',
"flag" = 'icons/UI_Icons/minesweeper_tiles/flag.png',
"hidden" = 'icons/UI_Icons/minesweeper_tiles/hidden.png',
"mine" = 'icons/UI_Icons/minesweeper_tiles/mine.png',
"minehit" = 'icons/UI_Icons/minesweeper_tiles/minehit.png'
)
/* Port the app game thing
/datum/asset/simple/arcade
assets = list(
"boss1.gif" = 'icons/UI_Icons/Arcade/boss1.gif',
"boss2.gif" = 'icons/UI_Icons/Arcade/boss2.gif',
"boss3.gif" = 'icons/UI_Icons/Arcade/boss3.gif',
"boss4.gif" = 'icons/UI_Icons/Arcade/boss4.gif',
"boss5.gif" = 'icons/UI_Icons/Arcade/boss5.gif',
"boss6.gif" = 'icons/UI_Icons/Arcade/boss6.gif',
)
*/
/*
/datum/asset/spritesheet/simple/achievements
name ="achievements"
assets = list(
"default" = 'icons/UI_Icons/Achievements/default.png',
"basemisc" = 'icons/UI_Icons/Achievements/basemisc.png',
"baseboss" = 'icons/UI_Icons/Achievements/baseboss.png',
"baseskill" = 'icons/UI_Icons/Achievements/baseskill.png',
"bbgum" = 'icons/UI_Icons/Achievements/Boss/bbgum.png',
"colossus" = 'icons/UI_Icons/Achievements/Boss/colossus.png',
"hierophant" = 'icons/UI_Icons/Achievements/Boss/hierophant.png',
"legion" = 'icons/UI_Icons/Achievements/Boss/legion.png',
"miner" = 'icons/UI_Icons/Achievements/Boss/miner.png',
"swarmer" = 'icons/UI_Icons/Achievements/Boss/swarmer.png',
"tendril" = 'icons/UI_Icons/Achievements/Boss/tendril.png',
"featofstrength" = 'icons/UI_Icons/Achievements/Misc/featofstrength.png',
"helbital" = 'icons/UI_Icons/Achievements/Misc/helbital.png',
"jackpot" = 'icons/UI_Icons/Achievements/Misc/jackpot.png',
"meteors" = 'icons/UI_Icons/Achievements/Misc/meteors.png',
"timewaste" = 'icons/UI_Icons/Achievements/Misc/timewaste.png',
"upgrade" = 'icons/UI_Icons/Achievements/Misc/upgrade.png',
"clownking" = 'icons/UI_Icons/Achievements/Misc/clownking.png',
"clownthanks" = 'icons/UI_Icons/Achievements/Misc/clownthanks.png',
"rule8" = 'icons/UI_Icons/Achievements/Misc/rule8.png',
"snail" = 'icons/UI_Icons/Achievements/Misc/snail.png',
"mining" = 'icons/UI_Icons/Achievements/Skills/mining.png',
)
*/
/datum/asset/spritesheet/simple/pills
name ="pills"
assets = list(
"pill1" = 'icons/UI_Icons/Pills/pill1.png',
"pill2" = 'icons/UI_Icons/Pills/pill2.png',
"pill3" = 'icons/UI_Icons/Pills/pill3.png',
"pill4" = 'icons/UI_Icons/Pills/pill4.png',
"pill5" = 'icons/UI_Icons/Pills/pill5.png',
"pill6" = 'icons/UI_Icons/Pills/pill6.png',
"pill7" = 'icons/UI_Icons/Pills/pill7.png',
"pill8" = 'icons/UI_Icons/Pills/pill8.png',
"pill9" = 'icons/UI_Icons/Pills/pill9.png',
"pill10" = 'icons/UI_Icons/Pills/pill10.png',
"pill11" = 'icons/UI_Icons/Pills/pill11.png',
"pill12" = 'icons/UI_Icons/Pills/pill12.png',
"pill13" = 'icons/UI_Icons/Pills/pill13.png',
"pill14" = 'icons/UI_Icons/Pills/pill14.png',
"pill15" = 'icons/UI_Icons/Pills/pill15.png',
"pill16" = 'icons/UI_Icons/Pills/pill16.png',
"pill17" = 'icons/UI_Icons/Pills/pill17.png',
"pill18" = 'icons/UI_Icons/Pills/pill18.png',
"pill19" = 'icons/UI_Icons/Pills/pill19.png',
"pill20" = 'icons/UI_Icons/Pills/pill20.png',
"pill21" = 'icons/UI_Icons/Pills/pill21.png',
"pill22" = 'icons/UI_Icons/Pills/pill22.png',
)
//this exists purely to avoid meta by pre-loading all language icons.
/datum/asset/language/register()
for(var/path in typesof(/datum/language))
@@ -640,7 +312,7 @@ GLOBAL_LIST_EMPTY(asset_datums)
/datum/asset/spritesheet/pipes
name = "pipes"
/datum/asset/spritesheet/pipes/register()
/datum/asset/spritesheet/pipes/register() //we do not have chempipes
for (var/each in list('icons/obj/atmospherics/pipes/pipe_item.dmi', 'icons/obj/atmospherics/pipes/disposal.dmi', 'icons/obj/atmospherics/pipes/transit_tube.dmi'))
InsertAll("", each, GLOB.alldirs)
..()
@@ -650,7 +322,7 @@ GLOBAL_LIST_EMPTY(asset_datums)
name = "design"
/datum/asset/spritesheet/research_designs/register()
for (var/path in subtypesof(/datum/design))
for(var/path in subtypesof(/datum/design))
var/datum/design/D = path
var/icon_file
@@ -743,4 +415,9 @@ GLOBAL_LIST_EMPTY(asset_datums)
"dna_discovered.gif" = 'html/dna_discovered.gif',
"dna_undiscovered.gif" = 'html/dna_undiscovered.gif',
"dna_extra.gif" = 'html/dna_extra.gif'
)
)
/datum/asset/simple/vv
assets = list(
"view_variables.css" = 'html/admin/view_variables.css'
)
@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
</head>
<body>
<script>
//this is used over window.location because window.location has a character limit in IE.
function sendbyond(text) {
var xhr = new XMLHttpRequest();
xhr.open('GET', '?'+text, true);
xhr.send(null);
}
var xhr = new XMLHttpRequest();
xhr.open('GET', 'asset_data.json', true);
xhr.responseType = 'text';
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
var status = xhr.status;
if (status >= 200 && status < 400) {
sendbyond('asset_cache_preload_data=' + encodeURIComponent(xhr.responseText));
}
}
};
xhr.send(null);
</script>
</body>
</html>
@@ -167,6 +167,7 @@ GLOBAL_LIST_INIT(meta_gas_fusions, meta_gas_fusion_list())
if(!ispath(path))
path = gas_id2path(path) //a lot of these strings can't have embedded expressions (especially for mappers), so support for IDs needs to stick around
set_moles(path, text2num(gas[id]))
archive()
return 1
/datum/gas_mixture/react(datum/holder)
@@ -36,7 +36,7 @@
/obj/item/electronics/airalarm
name = "air alarm electronics"
icon_state = "airalarm_electronics"
custom_price = 50
custom_price = PRICE_CHEAP
/obj/item/wallframe/airalarm
name = "air alarm frame"
@@ -67,6 +67,7 @@
desc = "A machine that monitors atmosphere levels. Goes off if the area is dangerous."
icon = 'icons/obj/monitors.dmi'
icon_state = "alarm0"
plane = ABOVE_WALL_PLANE
use_power = IDLE_POWER_USE
idle_power_usage = 4
active_power_usage = 8
@@ -17,6 +17,7 @@
active_power_usage = 0
power_channel = ENVIRON
layer = GAS_PIPE_HIDDEN_LAYER //under wires
plane = ABOVE_WALL_PLANE
resistance_flags = FIRE_PROOF
max_integrity = 200
obj_flags = CAN_BE_HIT | ON_BLUEPRINTS
@@ -7,6 +7,7 @@
name = "circulator/heat exchanger"
desc = "A gas circulator pump and heat exchanger."
icon_state = "circ-off-0"
plane = GAME_PLANE
var/active = FALSE
@@ -31,7 +31,7 @@
var/turf/T = loc
if(level == 2 || (istype(T) && !T.intact))
showpipe = TRUE
plane = GAME_PLANE
plane = ABOVE_WALL_PLANE
else
showpipe = FALSE
plane = FLOOR_PLANE
@@ -161,6 +161,9 @@
to_chat(user, "<span class='danger'>Access denied.</span>")
return UI_CLOSE
/obj/machinery/atmospherics/components/attack_ghost(mob/dead/observer/O)
. = ..()
atmosanalyzer_scan(airs, O, src, FALSE)
// Tool acts
@@ -6,6 +6,7 @@
max_integrity = 350
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 30, "acid" = 30)
layer = ABOVE_WINDOW_LAYER
plane = GAME_PLANE
state_open = FALSE
circuit = /obj/item/circuitboard/machine/cryo_tube
pipe_flags = PIPING_ONE_PER_TURF | PIPING_DEFAULT_LAYER_ONLY
@@ -15,8 +16,8 @@
var/volume = 100
var/efficiency = 1
var/sleep_factor = 0.00125
var/unconscious_factor = 0.001
var/base_knockout = 30 SECONDS
var/knockout_factor = 1
var/heat_capacity = 20000
var/conduction_coefficient = 0.3
@@ -53,10 +54,9 @@
var/C
for(var/obj/item/stock_parts/matter_bin/M in component_parts)
C += M.rating
// 2 bins total, so C ranges from 2 to 8.
efficiency = initial(efficiency) * C
sleep_factor = initial(sleep_factor) * C
unconscious_factor = initial(unconscious_factor) * C
knockout_factor = initial(knockout_factor) / max(1, (C * 0.33))
heat_capacity = initial(heat_capacity) / C
conduction_coefficient = initial(conduction_coefficient) * C
@@ -188,8 +188,10 @@
if(air1.total_moles())
if(mob_occupant.bodytemperature < T0C) // Sleepytime. Why? More cryo magic.
mob_occupant.Sleeping((mob_occupant.bodytemperature * sleep_factor) * 2000)
mob_occupant.Unconscious((mob_occupant.bodytemperature * unconscious_factor) * 2000)
// temperature factor goes from 1 to about 2.5
var/amount = max(1, (4 * log(T0C - mob_occupant.bodytemperature)) - 20) * knockout_factor * base_knockout
mob_occupant.Sleeping(amount)
mob_occupant.Unconscious(amount)
if(beaker)
if(reagent_transfer == 0) // Magically transfer reagents. Because cryo magic.
beaker.reagents.trans_to(occupant, 1, efficiency * 0.25) // Transfer reagents.
@@ -7,6 +7,7 @@
max_integrity = 800
density = TRUE
layer = ABOVE_WINDOW_LAYER
plane = GAME_PLANE
pipe_flags = PIPING_ONE_PER_TURF
var/volume = 10000 //in liters
var/gas_type = 0
@@ -9,6 +9,7 @@
max_integrity = 300
armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 0, "bio" = 100, "rad" = 100, "fire" = 80, "acid" = 30)
layer = OBJ_LAYER
plane = GAME_PLANE
circuit = /obj/item/circuitboard/machine/thermomachine
ui_x = 300
ui_y = 230
@@ -111,3 +111,10 @@
pipe_color = paint_color
update_node_icon()
return TRUE
/obj/machinery/atmospherics/pipe/attack_ghost(mob/dead/observer/O)
. = ..()
if(parent)
atmosanalyzer_scan(parent.air, O, src, FALSE)
else
to_chat(O, "<span class='warning'>[src] doesn't have a pipenet, which is probably a bug.</span>")
@@ -146,10 +146,14 @@
/obj/machinery/portable_atmospherics/analyzer_act(mob/living/user, obj/item/I)
atmosanalyzer_scan(air_contents, user, src)
/obj/machinery/portable_atmospherics/attacked_by(obj/item/I, mob/user)
/obj/machinery/portable_atmospherics/attacked_by(obj/item/I, mob/user, attackchain_flags = NONE, damage_multiplier = 1)
if(I.force < 10 && !(stat & BROKEN))
take_damage(0)
else
investigate_log("was smacked with \a [I] by [key_name(user)].", INVESTIGATE_ATMOS)
add_fingerprint(user)
..()
/obj/machinery/portable_atmospherics/attack_ghost(mob/dead/observer/O)
. = ..()
atmosanalyzer_scan(air_contents, O, src, FALSE)
+17 -12
View File
@@ -7,7 +7,7 @@
#define AMMO_DROP_LIFETIME 300
#define CTF_REQUIRED_PLAYERS 4
/obj/item/twohanded/ctf
/obj/item/ctf
name = "banner"
icon = 'icons/obj/items_and_weapons.dmi'
icon_state = "banner"
@@ -16,6 +16,7 @@
righthand_file = 'icons/mob/inhands/equipment/banners_righthand.dmi'
desc = "A banner with Nanotrasen's logo on it."
slowdown = 2
item_flags = SLOWS_WHILE_IN_HAND
throw_speed = 0
throw_range = 1
force = 200
@@ -28,16 +29,20 @@
var/obj/effect/ctf/flag_reset/reset
var/reset_path = /obj/effect/ctf/flag_reset
/obj/item/twohanded/ctf/Destroy()
/obj/item/ctf/Destroy()
QDEL_NULL(reset)
return ..()
/obj/item/twohanded/ctf/Initialize()
/obj/item/ctf/Initialize()
. = ..()
if(!reset)
reset = new reset_path(get_turf(src))
/obj/item/twohanded/ctf/process()
/obj/item/ctf/ComponentInitialize()
. = ..()
AddComponent(/datum/component/two_handed)
/obj/item/ctf/process()
if(is_ctf_target(loc)) //don't reset from someone's hands.
return PROCESS_KILL
if(world.time > reset_cooldown)
@@ -49,7 +54,7 @@
STOP_PROCESSING(SSobj, src)
//ATTACK HAND IGNORING PARENT RETURN VALUE
/obj/item/twohanded/ctf/attack_hand(mob/living/user)
/obj/item/ctf/attack_hand(mob/living/user)
if(!is_ctf_target(user) && !anyonecanpickup)
to_chat(user, "Non players shouldn't be moving the flag!")
return
@@ -73,7 +78,7 @@
STOP_PROCESSING(SSobj, src)
..()
/obj/item/twohanded/ctf/dropped(mob/user)
/obj/item/ctf/dropped(mob/user)
..()
user.anchored = FALSE
user.status_flags |= CANPUSH
@@ -86,7 +91,7 @@
anchored = TRUE
/obj/item/twohanded/ctf/red
/obj/item/ctf/red
name = "red flag"
icon_state = "banner-red"
item_state = "banner-red"
@@ -95,7 +100,7 @@
reset_path = /obj/effect/ctf/flag_reset/red
/obj/item/twohanded/ctf/blue
/obj/item/ctf/blue
name = "blue flag"
icon_state = "banner-blue"
item_state = "banner-blue"
@@ -276,8 +281,8 @@
attack_ghost(ghost)
/obj/machinery/capture_the_flag/attackby(obj/item/I, mob/user, params)
if(istype(I, /obj/item/twohanded/ctf))
var/obj/item/twohanded/ctf/flag = I
if(istype(I, /obj/item/ctf))
var/obj/item/ctf/flag = I
if(flag.team != src.team)
user.transferItemToLoc(flag, get_turf(flag.reset), TRUE)
points++
@@ -294,7 +299,7 @@
if(istype(mob_area, /area/ctf))
to_chat(M, "<span class='narsie [team_span]'>[team] team wins!</span>")
to_chat(M, "<span class='userdanger'>Teams have been cleared. Click on the machines to vote to begin another round.</span>")
for(var/obj/item/twohanded/ctf/W in M)
for(var/obj/item/ctf/W in M)
M.dropItemToGround(W)
M.dust()
for(var/obj/machinery/control_point/control in GLOB.machines)
@@ -335,7 +340,7 @@
var/list/ctf_object_typecache = typecacheof(list(
/obj/machinery,
/obj/effect/ctf,
/obj/item/twohanded/ctf
/obj/item/ctf
))
for(var/atm in A)
if (isturf(A) || ismob(A) || isarea(A))
+1 -1
View File
@@ -661,5 +661,5 @@
/datum/outfit/lavaknight/captain
name ="Cydonian Knight Captain"
l_pocket = /obj/item/twohanded/dualsaber/hypereutactic
l_pocket = /obj/item/dualsaber/hypereutactic
id = /obj/item/card/id/knight/captain
@@ -189,6 +189,8 @@
if(!ishuman(user) || !user.mind || (user.mind in SSticker.mode.wizards))
to_chat(user, "<span class='warning'>You feel the magic of the dice is restricted to ordinary humans! You should leave it alone.</span>")
user.dropItemToGround(src)
return
return ..()
/obj/item/dice/d20/fate/proc/effect(var/mob/living/carbon/human/user,roll)
@@ -1,20 +1,39 @@
/*Cabin areas*/
/area/awaymission/snowforest
name = "Snow Forest"
icon_state = "away"
requires_power = FALSE
dynamic_lighting = DYNAMIC_LIGHTING_ENABLED
/area/awaymission/cabin
name = "Cabin"
icon_state = "away2"
requires_power = TRUE
dynamic_lighting = DYNAMIC_LIGHTING_ENABLED
/area/awaymission/snowforest/lumbermill
/area/awaymission/cabin/snowforest
name = "Snow Forest"
icon_state = "away"
dynamic_lighting = DYNAMIC_LIGHTING_DISABLED
/area/awaymission/cabin/snowforest/sovietsurface
name = "Snow Forest"
icon_state = "awaycontent29"
requires_power = FALSE
/area/awaymission/cabin/lumbermill
name = "Lumbermill"
icon_state = "away3"
requires_power = FALSE
dynamic_lighting = DYNAMIC_LIGHTING_DISABLED
/area/awaymission/cabin/caves/sovietcave
name = "Soviet Bunker"
icon_state = "awaycontent4"
/area/awaymission/cabin/caves
name = "North Snowdin Caves"
icon_state = "awaycontent15"
dynamic_lighting = DYNAMIC_LIGHTING_FORCED
/area/awaymission/cabin/caves/mountain
name = "North Snowdin Mountains"
icon_state = "awaycontent24"
/obj/structure/firepit
name = "firepit"
@@ -92,7 +111,7 @@
egg_type = null
speak = list()
/*Cabin's forest*/
/*Cabin's forest. Removed in the new cabin map since it was buggy and I prefer manual placement.*/
/datum/mapGenerator/snowy
modules = list(/datum/mapGeneratorModule/bottomlayer/snow, \
/datum/mapGeneratorModule/snow/pineTrees, \
@@ -136,4 +155,4 @@
endTurfX = 159
endTurfY = 157
startTurfX = 37
startTurfY = 35
startTurfY = 35
@@ -475,43 +475,26 @@
/obj/effect/spawner/lootdrop/snowdin/dungeonlite
name = "dungeon lite"
loot = list(/obj/item/melee/classic_baton = 11,
/obj/item/melee/classic_baton/telescopic = 12,
/obj/item/book/granter/spell/smoke = 10,
loot = list(/obj/item/book/granter/spell/smoke = 10,
/obj/item/book/granter/spell/blind = 10,
/obj/item/storage/firstaid/regular = 45,
/obj/item/storage/firstaid/toxin = 35,
/obj/item/storage/firstaid/brute = 27,
/obj/item/storage/firstaid/fire = 27,
/obj/item/storage/toolbox/syndicate = 12,
/obj/item/grenade/plastic/c4 = 7,
/obj/item/grenade/clusterbuster/smoke = 15,
/obj/item/clothing/under/chameleon = 13,
/obj/item/clothing/shoes/chameleon/noslip = 10,
/obj/item/borg/upgrade/ddrill = 3)
/obj/effect/spawner/lootdrop/snowdin/dungeonmid
name = "dungeon mid"
loot = list(/obj/item/defibrillator/compact = 6,
/obj/item/storage/firstaid/tactical = 35,
/obj/item/shield/energy = 6,
/obj/item/shield/riot/tele = 12,
/obj/item/dnainjector/lasereyesmut = 7,
/obj/item/gun/magic/wand/fireball/inert = 3,
loot = list(/obj/item/shield/riot = 12,
/obj/item/pneumatic_cannon = 15,
/obj/item/melee/transforming/energy/sword = 7,
/obj/item/book/granter/spell/knock = 15,
/obj/item/book/granter/spell/summonitem = 20,
/obj/item/book/granter/spell/forcewall = 17,
/obj/item/storage/backpack/holding = 12,
/obj/item/grenade/spawnergrenade/manhacks = 6,
/obj/item/grenade/spawnergrenade/spesscarp = 7,
/obj/item/grenade/clusterbuster/inferno = 3,
/obj/item/stack/sheet/mineral/diamond{amount = 15} = 10,
/obj/item/stack/sheet/mineral/uranium{amount = 15} = 10,
/obj/item/stack/sheet/mineral/plasma{amount = 15} = 10,
/obj/item/stack/sheet/mineral/gold{amount = 15} = 10,
/obj/item/book/granter/spell/barnyard = 4,
/obj/item/pickaxe/drill/diamonddrill = 6,
/obj/item/borg/upgrade/vtec = 7,
/obj/item/borg/upgrade/disablercooler = 7)
@@ -519,21 +502,12 @@
/obj/effect/spawner/lootdrop/snowdin/dungeonheavy
name = "dungeon heavy"
loot = list(/obj/item/twohanded/singularityhammer = 25,
/obj/item/twohanded/mjollnir = 10,
/obj/item/twohanded/fireaxe = 25,
loot = list(/obj/item/fireaxe = 25,
/obj/item/organ/brain/alien = 17,
/obj/item/twohanded/dualsaber = 15,
/obj/item/organ/heart/demon = 7,
/obj/item/gun/ballistic/automatic/c20r/unrestricted = 16,
/obj/item/gun/magic/wand/resurrection/inert = 15,
/obj/item/gun/magic/wand/resurrection = 10,
/obj/item/uplink/old = 2,
/obj/item/book/granter/spell/charge = 12,
/obj/item/grenade/clusterbuster/spawner_manhacks = 15,
/obj/item/book/granter/spell/fireball = 10,
/obj/item/organ/heart/cursed = 7,
/obj/item/book/granter/spell/forcewall = 17,
/obj/item/gun/magic/wand/fireball/inert = 3,
/obj/item/pickaxe/drill/jackhammer = 30,
/obj/item/borg/upgrade/syndicate = 13,
/obj/item/borg/upgrade/selfrepair = 17)
/obj/effect/spawner/lootdrop/snowdin/dungeonmisc
@@ -544,7 +518,7 @@
loot = list(/obj/item/stack/sheet/mineral/snow{amount = 25} = 10,
/obj/item/toy/snowball = 15,
/obj/item/shovel = 10,
/obj/item/twohanded/spear = 8,
/obj/item/spear = 8,
)
//special items//--
@@ -1,51 +0,0 @@
//Spacebattle Areas
/area/awaymission/spacebattle
name = "Space Battle"
icon_state = "awaycontent1"
requires_power = FALSE
/area/awaymission/spacebattle/cruiser
name = "Nanotrasen Cruiser"
icon_state = "awaycontent2"
/area/awaymission/spacebattle/syndicate1
name = "Syndicate Assault Ship 1"
icon_state = "awaycontent3"
/area/awaymission/spacebattle/syndicate2
name = "Syndicate Assault Ship 2"
icon_state = "awaycontent4"
/area/awaymission/spacebattle/syndicate3
name = "Syndicate Assault Ship 3"
icon_state = "awaycontent5"
/area/awaymission/spacebattle/syndicate4
name = "Syndicate War Sphere 1"
icon_state = "awaycontent6"
/area/awaymission/spacebattle/syndicate5
name = "Syndicate War Sphere 2"
icon_state = "awaycontent7"
/area/awaymission/spacebattle/syndicate6
name = "Syndicate War Sphere 3"
icon_state = "awaycontent8"
/area/awaymission/spacebattle/syndicate7
name = "Syndicate Fighter"
icon_state = "awaycontent9"
/area/awaymission/spacebattle/secret
name = "Hidden Chamber"
icon_state = "awaycontent10"
/mob/living/simple_animal/hostile/syndicate/ranged/spacebattle
loot = list(/obj/effect/mob_spawn/human/corpse/syndicatesoldier,
/obj/item/gun/ballistic/automatic/c20r,
/obj/item/shield/energy)
/mob/living/simple_animal/hostile/syndicate/melee/spacebattle
deathmessage = "falls limp as they release their grip from the energy weapons, activating their self-destruct function!"
loot = list(/obj/effect/mob_spawn/human/corpse/syndicatesoldier)
+3 -3
View File
@@ -31,7 +31,7 @@
description = "CentCom's security forces are going through budget cuts. You will be paid if you ship a set of spears."
reward = 1000
required_count = 5
wanted_types = list(/obj/item/twohanded/spear)
wanted_types = list(/obj/item/spear)
/datum/bounty/item/assistant/toolbox
name = "Toolboxes"
@@ -134,7 +134,7 @@
description = "Central Command is looking to commission a new BirdBoat-class station. You've been ordered to supply the potted plants."
reward = 2000
required_count = 8
wanted_types = list(/obj/item/twohanded/required/kirbyplants)
wanted_types = list(/obj/item/kirbyplants)
// /datum/bounty/item/assistant/earmuffs
// name = "Earmuffs"
@@ -160,7 +160,7 @@
name = "Chainsaw"
description = "The chef at CentCom is having trouble butchering her animals. She requests one chainsaw, please."
reward = 2500
wanted_types = list(/obj/item/twohanded/required/chainsaw)
wanted_types = list(/obj/item/chainsaw)
/datum/bounty/item/assistant/ied
name = "IED"
+1 -1
View File
@@ -121,7 +121,7 @@
/datum/bounty/item/medical/defibrillator
name = "New defibillators"
description = "After years of storge our defib units have become liabilities. Please send us some new ones."
description = "After years of storage our defibrillation units have worn out. Please send us some new ones."
reward = 2250
required_count = 5
wanted_types = list(/obj/item/defibrillator)
+1 -1
View File
@@ -22,7 +22,7 @@
name = "Bone Axe"
description = "Station 12 has had their fire axes stolen by marauding clowns. Ship them a bone axe as a replacement."
reward = 3500
wanted_types = list(/obj/item/twohanded/fireaxe/boneaxe)
wanted_types = list(/obj/item/fireaxe/boneaxe)
/datum/bounty/item/mining/bone_armor
name = "Bone Armor"
+4 -4
View File
@@ -119,7 +119,7 @@
/datum/bounty/item/science/noneactive_reactivearmor
name = "Reactive Armor Shells"
description = "Do to the breakthroughs in anomalies, we can not keep up in making reactive armor shells, can you send us a few?"
description = "Due to the breakthroughs in anomalies, we can not keep up in making reactive armor shells, can you send us a few?"
reward = 2000
required_count = 5
wanted_types = list(/obj/item/reactive_armour_shell, /obj/item/clothing/suit/armor/reactive)
@@ -138,14 +138,14 @@
/datum/bounty/item/science/anomaly_neutralizer
name = "Anomaly Neutralizers"
description = "An idea for a long time was to use an unstable Supermatter Shard to help create the breeding grounds for an unstable part of space to harvest any anomalies we want. It worked a little too well and now were out of anomaly neutralizers please send us a baker's dozen."
description = "An idea for a long time was to use an unstable Supermatter Shard to help create the breeding grounds for an unstable part of space to harvest any anomalies we want. It worked a little too well and now we're out of anomaly neutralizers, please send us a baker's dozen."
reward = 2500
required_count = 13
wanted_types = list(/obj/item/anomaly_neutralizer)
/datum/bounty/item/science/integrated_circuit_printer
name = "Integrated Circuit Printer"
description = "due to a paperwork error, a newly made integrated circuit manufacturer line is missing three of its printers needed to operate. Until the paper work is corrected we are outsourcing this problem, so please send us three integrated circuit printers."
description = "Due to a paperwork error, a newly made integrated circuit manufacturer line is missing three of its printers needed to operate. Until the paper work is corrected we are outsourcing this problem, so please send us three integrated circuit printers."
reward = 2000
required_count = 3
wanted_types = list(/obj/item/integrated_circuit_printer)
@@ -159,7 +159,7 @@
/datum/bounty/item/science/nanite_trash
name = "Nanite Based Gear"
description = "CC wants to make nanite based gear available to a new wing of devolvement but lacks the hand held tools to get it full up and running. Please send us any you have."
description = "CC wants to make nanite based gear available to a new wing of development but lacks the hand held tools to get it fully up and running. Please send us any you have."
reward = 2500
required_count = 20 //Its just metal
wanted_types = list( /obj/item/nanite_remote, /obj/item/nanite_remote/comm, /obj/item/nanite_scanner)
+28 -5
View File
@@ -18,6 +18,7 @@
var/obj/item/radio/headset/radio
/// var that tracks message cooldown
var/message_cooldown
var/list/loaded_coupons
light_color = "#E2853D"//orange
@@ -119,6 +120,7 @@
var/list/data = list()
data["requestonly"] = requestonly
data["supplies"] = list()
data["emagged"] = obj_flags & EMAGGED
for(var/pack in SSshuttle.supply_packs)
var/datum/supply_pack/P = SSshuttle.supply_packs[pack]
if(!data["supplies"][P.group])
@@ -133,7 +135,10 @@
"cost" = P.cost,
"id" = pack,
"desc" = P.desc || P.name, // If there is a description, use it. Otherwise use the pack's name.
"access" = P.access
"private_goody" = P.goody == PACK_GOODY_PRIVATE,
"goody" = P.goody == PACK_GOODY_PUBLIC,
"access" = P.access,
"can_private_buy" = P.can_private_buy
))
return data
@@ -195,9 +200,10 @@
rank = "Silicon"
var/datum/bank_account/account
if(self_paid && ishuman(usr))
var/mob/living/carbon/human/H = usr
var/obj/item/card/id/id_card = H.get_idcard(TRUE)
if(self_paid)
if(!pack.can_private_buy && !(obj_flags & EMAGGED))
return
var/obj/item/card/id/id_card = usr.get_idcard(TRUE)
if(!istype(id_card))
say("No ID card detected.")
return
@@ -212,8 +218,22 @@
if(isnull(reason) || ..())
return
if(pack.goody == PACK_GOODY_PRIVATE && !self_paid)
playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE)
say("ERROR: Private small crates may only be purchased by private accounts.")
return
var/obj/item/coupon/applied_coupon
for(var/i in loaded_coupons)
var/obj/item/coupon/coupon_check = i
if(pack.type == coupon_check.discounted_pack)
say("Coupon found! [round(coupon_check.discount_pct_off * 100)]% off applied!")
coupon_check.moveToNullspace()
applied_coupon = coupon_check
break
var/turf/T = get_turf(src)
var/datum/supply_order/SO = new(pack, name, rank, ckey, reason, account)
var/datum/supply_order/SO = new(pack, name, rank, ckey, reason, account, applied_coupon)
SO.generateRequisition(T)
if(requestonly && !self_paid)
SSshuttle.requestlist += SO
@@ -226,6 +246,9 @@
var/id = text2num(params["id"])
for(var/datum/supply_order/SO in SSshuttle.shoppinglist)
if(SO.id == id)
if(SO.applied_coupon)
say("Coupon refunded.")
SO.applied_coupon.forceMove(get_turf(src))
SSshuttle.shoppinglist -= SO
. = TRUE
break
+50
View File
@@ -0,0 +1,50 @@
#define COUPON_OMEN "omen"
/obj/item/coupon
name = "coupon"
desc = "It doesn't matter if you didn't want it before, what matters now is that you've got a coupon for it!"
icon_state = "data_1"
icon = 'icons/obj/card.dmi'
item_flags = NOBLUDGEON
w_class = WEIGHT_CLASS_TINY
var/datum/supply_pack/discounted_pack
var/discount_pct_off = 0.05
var/obj/machinery/computer/cargo/inserted_console
/// Choose what our prize is :D
/obj/item/coupon/proc/generate()
discounted_pack = pick(subtypesof(/datum/supply_pack/goody))
var/list/chances = list("0.10" = 4, "0.15" = 8, "0.20" = 10, "0.25" = 8, "0.50" = 4, COUPON_OMEN = 1)
discount_pct_off = pickweight(chances)
if(discount_pct_off == COUPON_OMEN)
name = "coupon - fuck you"
desc = "The small text reads, 'You will be slaughtered'... That doesn't sound right, does it?"
if(ismob(loc))
var/mob/M = loc
to_chat(M, "<span class='warning'>The coupon reads '<b>fuck you</b>' in large, bold text... is- is that a prize, or?</span>")
M.AddComponent(/datum/component/omen, TRUE, src)
else
discount_pct_off = text2num(discount_pct_off)
name = "coupon - [round(discount_pct_off * 100)]% off [initial(discounted_pack.name)]"
/obj/item/coupon/attack_obj(obj/O, mob/living/user)
if(!istype(O, /obj/machinery/computer/cargo))
return ..()
if(discount_pct_off == COUPON_OMEN)
to_chat(user, "<span class='warning'>\The [O] validates the coupon as authentic, but refuses to accept it...</span>")
O.say("Coupon fulfillment already in progress...")
return
inserted_console = O
LAZYADD(inserted_console.loaded_coupons, src)
inserted_console.say("Coupon for [initial(discounted_pack.name)] applied!")
forceMove(inserted_console)
/obj/item/coupon/Destroy()
if(inserted_console)
LAZYREMOVE(inserted_console.loaded_coupons, src)
inserted_console = null
. = ..()
#undef COUPON_OMEN
+1 -1
View File
@@ -68,7 +68,7 @@ Credit dupes that require a lot of manual work shouldn't be removed, unless they
var/unit_name = "" // Unit name. Only used in "Received [total_amount] [name]s [message]." message
var/message = ""
var/cost = 100 // Cost of item, in cargo credits. Must not alow for infinite price dupes, see above.
var/k_elasticity = 1/300 //coefficient used in marginal price calculation that roughly corresponds to the inverse of price elasticity, or "quantity elasticity" - CIT EDIT 1/30 - > 0
var/k_elasticity = 1/100 //coefficient used in marginal price calculation that roughly corresponds to the inverse of price elasticity, or "quantity elasticity" - CIT EDIT 1/30 - > 100
var/list/export_types = list() // Type of the exported object. If none, the export datum is considered base type.
var/include_subtypes = TRUE // Set to FALSE to make the datum apply only to a strict type.
var/list/exclude_types = list() // Types excluded from export
+1
View File
@@ -1,5 +1,6 @@
/datum/export/gear
include_subtypes = FALSE
k_elasticity = 0 //We always want clothing/gear
//blanket
/datum/export/gear/hat
+4 -4
View File
@@ -111,7 +111,7 @@
include_subtypes = FALSE
/datum/export/large/am_control_unit
cost = 4000
cost = 2000
unit_name = "antimatter control unit"
export_types = list(/obj/machinery/power/am_control_unit)
@@ -169,10 +169,10 @@
/datum/export/large/gas_canister/get_cost(obj/O)
var/obj/machinery/portable_atmospherics/canister/C = O
var/worth = 10
worth += C.air_contents.get_moles(/datum/gas/bz)*4
worth += C.air_contents.get_moles(/datum/gas/bz)*3
worth += C.air_contents.get_moles(/datum/gas/stimulum)*25
worth += C.air_contents.get_moles(/datum/gas/hypernoblium)*1000
worth += C.air_contents.get_moles(/datum/gas/miasma)*4
worth += C.air_contents.get_moles(/datum/gas/miasma)*2
worth += C.air_contents.get_moles(/datum/gas/tritium)*7
worth += C.air_contents.get_moles(/datum/gas/pluoxium)*6
worth += C.air_contents.get_moles(/datum/gas/nitryl)*30
@@ -301,7 +301,7 @@
export_types = list(/obj/mecha/combat/durand)
/datum/export/large/mech/phazon
cost = 35000 //Little over half do to needing a core
cost = 35000 //Little over half due to needing a core
unit_name = "working phazon"
export_types = list(/obj/mecha/combat/phazon)
@@ -2,15 +2,14 @@
/datum/export/robotics
include_subtypes = FALSE
k_elasticity = 0 //ALWAYS worth selling upgrades
k_elasticity = 1/50
/datum/export/implant
include_subtypes = FALSE
k_elasticity = 0 //ALWAYS worth selling upgrades
k_elasticity = 1/50
/datum/export/organs
include_subtypes = TRUE
k_elasticity = 0 //ALWAYS worth selling orgains
/datum/export/implant/autodoc
cost = 150
@@ -60,11 +59,6 @@
unit_name = "thrusters set implant"
export_types = list(/obj/item/organ/cyberimp/chest/thrusters)
/datum/export/implant/thrusters
cost = 150
unit_name = "thrusters set implant"
export_types = list(/obj/item/organ/cyberimp/chest/thrusters)
/datum/export/implant/arm
cost = 200
unit_name = "arm set implant"

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