Merge conflict fix

This commit is contained in:
Artur
2020-05-13 12:37:18 +03:00
985 changed files with 295527 additions and 16121 deletions
+1 -1
View File
@@ -171,7 +171,7 @@
if(!..())
return 0
var/mob/M = target
M.ghostize(1)
M.ghostize(can_reenter_corpse = TRUE, voluntary = TRUE)
/datum/action/proc/OnUpdatedIcon()
UpdateButtonIcon()
+1 -1
View File
@@ -253,7 +253,7 @@
var/datum/ai_laws/lawtype
var/list/law_weights = CONFIG_GET(keyed_list/law_weight)
while(!lawtype && law_weights.len)
var/possible_id = pickweightAllowZero(law_weights)
var/possible_id = pickweight(law_weights, 0)
lawtype = lawid_to_type(possible_id)
if(!lawtype)
law_weights -= possible_id
+2 -1
View File
@@ -91,7 +91,6 @@
trauma = _trauma
owner = trauma.owner
copy_known_languages_from(owner, TRUE)
setup_friend()
@@ -150,6 +149,8 @@
friend_talk(message)
/mob/camera/imaginary_friend/Hear(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode, atom/movable/source)
if (client?.prefs.chat_on_map && (client.prefs.see_chat_non_mob || ismob(speaker)))
create_chat_message(speaker, message_language, raw_message, spans, message_mode)
to_chat(src, compose_message(speaker, message_language, raw_message, radio_freq, spans, message_mode, FALSE, source))
/mob/camera/imaginary_friend/proc/friend_talk(message)
+4 -10
View File
@@ -26,21 +26,15 @@
scan_desc = "extensive damage to the brain's language center"
gain_text = "<span class='warning'>You have trouble forming words in your head...</span>"
lose_text = "<span class='notice'>You suddenly remember how languages work.</span>"
var/datum/language_holder/prev_language
var/datum/language_holder/mob_language
/datum/brain_trauma/severe/aphasia/on_gain()
mob_language = owner.get_language_holder()
prev_language = mob_language.copy()
mob_language.remove_all_languages()
mob_language.grant_language(/datum/language/aphasia)
owner.add_blocked_language(subtypesof(/datum/language/) - /datum/language/aphasia, LANGUAGE_APHASIA)
owner.grant_language(/datum/language/aphasia, TRUE, TRUE, LANGUAGE_APHASIA)
..()
/datum/brain_trauma/severe/aphasia/on_lose()
mob_language.remove_language(/datum/language/aphasia)
mob_language.copy_known_languages_from(prev_language) //this will also preserve languages learned during the trauma
QDEL_NULL(prev_language)
mob_language = null
owner.remove_blocked_language(subtypesof(/datum/language/), LANGUAGE_APHASIA)
owner.remove_language(/datum/language/aphasia, TRUE, TRUE, LANGUAGE_APHASIA)
..()
/datum/brain_trauma/severe/blindness
+236
View File
@@ -0,0 +1,236 @@
#define CHAT_MESSAGE_SPAWN_TIME 0.2 SECONDS
#define CHAT_MESSAGE_LIFESPAN 5 SECONDS
#define CHAT_MESSAGE_EOL_FADE 0.7 SECONDS
#define CHAT_MESSAGE_EXP_DECAY 0.7 // Messages decay at pow(factor, idx in stack)
#define CHAT_MESSAGE_HEIGHT_DECAY 0.9 // Increase message decay based on the height of the message
#define CHAT_MESSAGE_APPROX_LHEIGHT 11 // Approximate height in pixels of an 'average' line, used for height decay
#define CHAT_MESSAGE_WIDTH 96 // pixels
#define CHAT_MESSAGE_MAX_LENGTH 110 // characters
#define WXH_TO_HEIGHT(x) text2num(copytext((x), findtextEx((x), "x") + 1)) // thanks lummox
/**
* # Chat Message Overlay
*
* Datum for generating a message overlay on the map
*/
/datum/chatmessage
/// The visual element of the chat messsage
var/image/message
/// The location in which the message is appearing
var/atom/message_loc
/// The client who heard this message
var/client/owned_by
/// Contains the scheduled destruction time
var/scheduled_destruction
/// Contains the approximate amount of lines for height decay
var/approx_lines
/**
* Constructs a chat message overlay
*
* Arguments:
* * text - The text content of the overlay
* * target - The target atom to display the overlay at
* * owner - The mob that owns this overlay, only this mob will be able to view it
* * extra_classes - Extra classes to apply to the span that holds the text
* * lifespan - The lifespan of the message in deciseconds
*/
/datum/chatmessage/New(text, atom/target, mob/owner, list/extra_classes = null, lifespan = CHAT_MESSAGE_LIFESPAN)
. = ..()
if (!istype(target))
CRASH("Invalid target given for chatmessage")
if(QDELETED(owner) || !istype(owner) || !owner.client)
stack_trace("/datum/chatmessage created with [isnull(owner) ? "null" : "invalid"] mob owner")
qdel(src)
return
INVOKE_ASYNC(src, .proc/generate_image, text, target, owner, extra_classes, lifespan)
/datum/chatmessage/Destroy()
if (owned_by)
if (owned_by.seen_messages)
LAZYREMOVEASSOC(owned_by.seen_messages, message_loc, src)
owned_by.images.Remove(message)
owned_by = null
message_loc = null
message = null
return ..()
/**
* Generates a chat message image representation
*
* Arguments:
* * text - The text content of the overlay
* * target - The target atom to display the overlay at
* * owner - The mob that owns this overlay, only this mob will be able to view it
* * extra_classes - Extra classes to apply to the span that holds the text
* * lifespan - The lifespan of the message in deciseconds
*/
/datum/chatmessage/proc/generate_image(text, atom/target, mob/owner, list/extra_classes, lifespan)
// Register client who owns this message
owned_by = owner.client
RegisterSignal(owned_by, COMSIG_PARENT_QDELETING, .proc/qdel, src)
// Clip message
var/maxlen = owned_by.prefs.max_chat_length
if (length_char(text) > maxlen)
text = copytext_char(text, 1, maxlen + 1) + "..." // BYOND index moment
// Calculate target color if not already present
if (!target.chat_color || target.chat_color_name != target.name)
target.chat_color = colorize_string(target.name)
target.chat_color_darkened = colorize_string(target.name, 0.85, 0.85)
target.chat_color_name = target.name
// Get rid of any URL schemes that might cause BYOND to automatically wrap something in an anchor tag
var/static/regex/url_scheme = new(@"[A-Za-z][A-Za-z0-9+-\.]*:\/\/", "g")
text = replacetext(text, url_scheme, "")
// Reject whitespace
var/static/regex/whitespace = new(@"^\s*$")
if (whitespace.Find(text))
qdel(src)
return
// Non mobs speakers can be small
if (!ismob(target))
extra_classes |= "small"
// Append radio icon if from a virtual speaker
if (extra_classes.Find("virtual-speaker"))
var/image/r_icon = image('icons/UI_Icons/chat/chat_icons.dmi', icon_state = "radio")
text = "\icon[r_icon]&nbsp;" + text
// We dim italicized text to make it more distinguishable from regular text
var/tgt_color = extra_classes.Find("italics") ? target.chat_color_darkened : target.chat_color
// Approximate text height
// Note we have to replace HTML encoded metacharacters otherwise MeasureText will return a zero height
// BYOND Bug #2563917
// Construct text
var/static/regex/html_metachars = new(@"&[A-Za-z]{1,7};", "g")
var/complete_text = "<span class='center maptext [extra_classes != null ? extra_classes.Join(" ") : ""]' style='color: [tgt_color]'>[text]</span>"
var/mheight = WXH_TO_HEIGHT(owned_by.MeasureText(replacetext(complete_text, html_metachars, "m"), null, CHAT_MESSAGE_WIDTH))
approx_lines = max(1, mheight / CHAT_MESSAGE_APPROX_LHEIGHT)
// Translate any existing messages upwards, apply exponential decay factors to timers
message_loc = target
if (owned_by.seen_messages)
var/idx = 1
var/combined_height = approx_lines
for(var/msg in owned_by.seen_messages[message_loc])
var/datum/chatmessage/m = msg
animate(m.message, pixel_y = m.message.pixel_y + mheight, time = CHAT_MESSAGE_SPAWN_TIME)
combined_height += m.approx_lines
var/sched_remaining = m.scheduled_destruction - world.time
if (sched_remaining > CHAT_MESSAGE_SPAWN_TIME)
var/remaining_time = (sched_remaining) * (CHAT_MESSAGE_EXP_DECAY ** idx++) * (CHAT_MESSAGE_HEIGHT_DECAY ** combined_height)
m.scheduled_destruction = world.time + remaining_time
addtimer(CALLBACK(m, .proc/end_of_life), remaining_time, TIMER_UNIQUE|TIMER_OVERRIDE)
// Build message image
message = image(loc = message_loc, layer = CHAT_LAYER)
message.plane = GAME_PLANE
message.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA | KEEP_APART
message.alpha = 0
message.pixel_y = owner.bound_height * 0.95
message.maptext_width = CHAT_MESSAGE_WIDTH
message.maptext_height = mheight
message.maptext_x = (CHAT_MESSAGE_WIDTH - owner.bound_width) * -0.5
message.maptext = complete_text
// View the message
LAZYADDASSOC(owned_by.seen_messages, message_loc, src)
owned_by.images |= message
animate(message, alpha = 255, time = CHAT_MESSAGE_SPAWN_TIME)
// Prepare for destruction
scheduled_destruction = world.time + (lifespan - CHAT_MESSAGE_EOL_FADE)
addtimer(CALLBACK(src, .proc/end_of_life), lifespan - CHAT_MESSAGE_EOL_FADE, TIMER_UNIQUE|TIMER_OVERRIDE)
/**
* Applies final animations to overlay CHAT_MESSAGE_EOL_FADE deciseconds prior to message deletion
*/
/datum/chatmessage/proc/end_of_life(fadetime = CHAT_MESSAGE_EOL_FADE)
animate(message, alpha = 0, time = fadetime, flags = ANIMATION_PARALLEL)
QDEL_IN(src, fadetime)
/**
* Creates a message overlay at a defined location for a given speaker
*
* Arguments:
* * speaker - The atom who is saying this message
* * message_language - The language that the message is said in
* * raw_message - The text content of the message
* * spans - Additional classes to be added to the message
* * message_mode - Bitflags relating to the mode of the message
*/
/mob/proc/create_chat_message(atom/movable/speaker, datum/language/message_language, raw_message, list/spans, message_mode)
// Ensure the list we are using, if present, is a copy so we don't modify the list provided to us
spans = spans?.Copy()
// Check for virtual speakers (aka hearing a message through a radio)
var/atom/movable/originalSpeaker = speaker
if (istype(speaker, /atom/movable/virtualspeaker))
var/atom/movable/virtualspeaker/v = speaker
speaker = v.source
spans |= "virtual-speaker"
// Ignore virtual speaker (most often radio messages) from ourself
if (originalSpeaker != src && speaker == src)
return
// Display visual above source
new /datum/chatmessage(lang_treat(speaker, message_language, raw_message, spans, null, TRUE), speaker, src, spans)
// Tweak these defines to change the available color ranges
#define CM_COLOR_SAT_MIN 0.6
#define CM_COLOR_SAT_MAX 0.7
#define CM_COLOR_LUM_MIN 0.65
#define CM_COLOR_LUM_MAX 0.75
/**
* Gets a color for a name, will return the same color for a given string consistently within a round.atom
*
* Note that this proc aims to produce pastel-ish colors using the HSL colorspace. These seem to be favorable for displaying on the map.
*
* Arguments:
* * name - The name to generate a color for
* * sat_shift - A value between 0 and 1 that will be multiplied against the saturation
* * lum_shift - A value between 0 and 1 that will be multiplied against the luminescence
*/
/datum/chatmessage/proc/colorize_string(name, sat_shift = 1, lum_shift = 1)
// seed to help randomness
var/static/rseed = rand(1,26)
// get hsl using the selected 6 characters of the md5 hash
var/hash = copytext(md5(name + GLOB.round_id), rseed, rseed + 6)
var/h = hex2num(copytext(hash, 1, 3)) * (360 / 255)
var/s = (hex2num(copytext(hash, 3, 5)) >> 2) * ((CM_COLOR_SAT_MAX - CM_COLOR_SAT_MIN) / 63) + CM_COLOR_SAT_MIN
var/l = (hex2num(copytext(hash, 5, 7)) >> 2) * ((CM_COLOR_LUM_MAX - CM_COLOR_LUM_MIN) / 63) + CM_COLOR_LUM_MIN
// adjust for shifts
s *= clamp(sat_shift, 0, 1)
l *= clamp(lum_shift, 0, 1)
// convert to rgb
var/h_int = round(h/60) // mapping each section of H to 60 degree sections
var/c = (1 - abs(2 * l - 1)) * s
var/x = c * (1 - abs((h / 60) % 2 - 1))
var/m = l - c * 0.5
x = (x + m) * 255
c = (c + m) * 255
m *= 255
switch(h_int)
if(0)
return "#[num2hex(c, 2)][num2hex(x, 2)][num2hex(m, 2)]"
if(1)
return "#[num2hex(x, 2)][num2hex(c, 2)][num2hex(m, 2)]"
if(2)
return "#[num2hex(m, 2)][num2hex(c, 2)][num2hex(x, 2)]"
if(3)
return "#[num2hex(m, 2)][num2hex(x, 2)][num2hex(c, 2)]"
if(4)
return "#[num2hex(x, 2)][num2hex(m, 2)][num2hex(c, 2)]"
if(5)
return "#[num2hex(c, 2)][num2hex(m, 2)][num2hex(x, 2)]"
-95
View File
@@ -1,95 +0,0 @@
/datum/component/archaeology
dupe_mode = COMPONENT_DUPE_UNIQUE
var/list/archdrops = list(/obj/item/bikehorn = list(ARCH_PROB = 100, ARCH_MAXDROP = 1)) // honk~
var/prob2drop
var/dug
var/datum/callback/callback
/datum/component/archaeology/Initialize(list/_archdrops = list(), datum/callback/_callback)
archdrops = _archdrops
for(var/i in archdrops)
if(isnull(archdrops[i][ARCH_MAXDROP]))
archdrops[i][ARCH_MAXDROP] = 1
stack_trace("ARCHAEOLOGY WARNING: [parent] contained a null max_drop value in [i].")
if(isnull(archdrops[i][ARCH_PROB]))
archdrops[i][ARCH_PROB] = 100
stack_trace("ARCHAEOLOGY WARNING: [parent] contained a null probability value in [i].")
callback = _callback
RegisterSignal(parent, COMSIG_PARENT_ATTACKBY,.proc/Dig)
RegisterSignal(parent, COMSIG_ATOM_EX_ACT, .proc/BombDig)
RegisterSignal(parent, COMSIG_ATOM_SING_PULL, .proc/SingDig)
/datum/component/archaeology/InheritComponent(datum/component/archaeology/A, i_am_original)
var/list/other_archdrops = A.archdrops
var/list/_archdrops = archdrops
for(var/I in other_archdrops)
_archdrops[I] += other_archdrops[I]
/datum/component/archaeology/proc/Dig(datum/source, obj/item/I, mob/living/user)
if(dug)
to_chat(user, "<span class='notice'>Looks like someone has dug here already.</span>")
return
if(!isturf(user.loc))
return
if(I.tool_behaviour == TOOL_SHOVEL || I.tool_behaviour == TOOL_MINING)
to_chat(user, "<span class='notice'>You start digging...</span>")
if(I.use_tool(parent, user, 40, volume=50))
to_chat(user, "<span class='notice'>You dig a hole.</span>")
gets_dug()
dug = TRUE
SSblackbox.record_feedback("tally", "pick_used_mining", 1, I.type)
return COMPONENT_NO_AFTERATTACK
/datum/component/archaeology/proc/gets_dug()
if(dug)
return
else
var/turf/open/OT = get_turf(parent)
for(var/thing in archdrops)
var/maxtodrop = archdrops[thing][ARCH_MAXDROP]
for(var/i in 1 to maxtodrop)
if(prob(archdrops[thing][ARCH_PROB])) // can't win them all!
new thing(OT)
if(isopenturf(OT))
if(OT.postdig_icon_change)
if(istype(OT, /turf/open/floor/plating/asteroid/) && !OT.postdig_icon)
var/turf/open/floor/plating/asteroid/AOT = parent
AOT.icon_plating = "[AOT.environment_type]_dug"
AOT.icon_state = "[AOT.environment_type]_dug"
else
if(isplatingturf(OT))
var/turf/open/floor/plating/POT = parent
POT.icon_plating = "[POT.postdig_icon]"
POT.icon_state = "[OT.postdig_icon]"
if(OT.slowdown) //Things like snow slow you down until you dig them.
OT.slowdown = 0
dug = TRUE
if(callback)
callback.Invoke()
/datum/component/archaeology/proc/SingDig(datum/source, S, current_size)
switch(current_size)
if(STAGE_THREE)
if(prob(30))
gets_dug()
if(STAGE_FOUR)
if(prob(50))
gets_dug()
else
if(current_size >= STAGE_FIVE && prob(70))
gets_dug()
/datum/component/archaeology/proc/BombDig(datum/source, severity, target)
switch(severity)
if(3)
return
if(2)
if(prob(20))
gets_dug()
if(1)
gets_dug()
+1 -1
View File
@@ -4,7 +4,7 @@
var/list/bounce_signals = list(COMSIG_MOVABLE_IMPACT, COMSIG_ITEM_HIT_REACT, COMSIG_ITEM_ATTACK)
/datum/component/bouncy/Initialize(_bouncy_mod, list/_bounce_signals)
if(!ismovableatom(parent))
if(!ismovable(parent))
return COMPONENT_INCOMPATIBLE
if(_bouncy_mod)
bouncy_mod = _bouncy_mod
+2 -2
View File
@@ -55,7 +55,7 @@
log_combat(user, H, "starts slicing the throat of")
playsound(H.loc, butcher_sound, 50, TRUE, -1)
if(do_mob(user, H, CLAMP(500 / source.force, 30, 100)) && H.Adjacent(source))
if(do_mob(user, H, clamp(500 / source.force, 30, 100)) && H.Adjacent(source))
if(H.has_status_effect(/datum/status_effect/neck_slice))
user.show_message("<span class='warning'>[H]'s neck has already been already cut, you can't make the bleeding any worse!</span>", 1, \
"<span class='warning'>Their neck has already been already cut, you can't make the bleeding any worse!</span>")
@@ -65,7 +65,7 @@
"<span class='userdanger'>[user] slits your throat...</span>")
log_combat(user, H, "finishes slicing the throat of")
H.apply_damage(source.force, BRUTE, BODY_ZONE_HEAD)
H.bleed_rate = CLAMP(H.bleed_rate + 20, 0, 30)
H.bleed_rate = clamp(H.bleed_rate + 20, 0, 30)
H.apply_status_effect(/datum/status_effect/neck_slice)
/datum/component/butchering/proc/Butcher(mob/living/butcher, mob/living/meat)
@@ -23,3 +23,4 @@
*/
/datum/crafting_recipe/proc/check_requirements(mob/user, list/collected_requirements)
return TRUE
@@ -327,3 +327,16 @@
/obj/item/stack/cable_coil = 10)
time = 100 //Takes awhile to put all the garlics on the coil and knot it.
category = CAT_CLOTHING
/datum/crafting_recipe/gripperoffbrand
name = "Improvised Gripper Gloves"
reqs = list(
/obj/item/clothing/gloves/fingerless = 1,
// /obj/item/stack/sticky_tape = 1
/obj/item/stack/cable_coil = 5,
/obj/item/stack/sheet/cloth = 2,
)
result = /obj/item/clothing/gloves/tackler/offbrand
category = CAT_CLOTHING
tools = list(TOOL_WIRECUTTER)
time = 20
@@ -115,6 +115,33 @@
//Tools & Storage//
///////////////////
/datum/crafting_recipe/upgraded_gauze
name = "Improved Gauze"
result = /obj/item/stack/medical/gauze/adv/one
time = 1
reqs = list(/obj/item/stack/medical/gauze = 1,
/datum/reagent/space_cleaner/sterilizine = 10)
category = CAT_MISC
subcategory = CAT_TOOL
/datum/crafting_recipe/bruise_pack
name = "Bruise Pack"
result = /obj/item/stack/medical/bruise_pack/one
time = 1
reqs = list(/obj/item/stack/medical/gauze = 1,
/datum/reagent/medicine/styptic_powder = 10)
category = CAT_MISC
subcategory = CAT_TOOL
/datum/crafting_recipe/burn_pack
name = "Burn Ointment"
result = /obj/item/stack/medical/ointment/one
time = 1
reqs = list(/obj/item/stack/medical/gauze = 1,
/datum/reagent/medicine/silver_sulfadiazine = 10)
category = CAT_MISC
subcategory = CAT_TOOL
/datum/crafting_recipe/ghettojetpack
name = "Improvised Jetpack"
result = /obj/item/tank/jetpack/improvised
@@ -233,6 +260,15 @@
subcategory = CAT_TOOL
category = CAT_MISC
/datum/crafting_recipe/electrochromatic_kit
name = "Electrochromatic Kit"
result = /obj/item/electronics/electrochromatic_kit
reqs = list(/obj/item/stack/sheet/metal = 1,
/obj/item/stack/cable_coil = 1)
time = 5
subcategory = CAT_TOOL
category = CAT_MISC
////////////
//Vehicles//
////////////
@@ -377,15 +413,6 @@
subcategory = CAT_MISCELLANEOUS
category = CAT_MISC
/datum/crafting_recipe/paperwork
name = "Filed Paper Work"
result = /obj/item/folder/paperwork_correct
time = 10 //Takes time for people to file and complete paper work!
tools = list(/obj/item/pen)
reqs = list(/obj/item/folder/paperwork = 1)
subcategory = CAT_MISCELLANEOUS
category = CAT_MISC
/datum/crafting_recipe/coconut_bong
name = "Coconut Bong"
result = /obj/item/bong/coconut
+1 -1
View File
@@ -3,7 +3,7 @@
var/list/say_lines
/datum/component/edit_complainer/Initialize(list/text)
if(!ismovableatom(parent))
if(!ismovable(parent))
return COMPONENT_INCOMPATIBLE
var/static/list/default_lines = list(
+1 -1
View File
@@ -13,7 +13,7 @@
RegisterSignal(parent, COMSIG_PARENT_ATTACKBY, .proc/explodable_attack)
RegisterSignal(parent, COMSIG_TRY_STORAGE_INSERT, .proc/explodable_insert_item)
RegisterSignal(parent, COMSIG_ATOM_EX_ACT, .proc/detonate)
if(ismovableatom(parent))
if(ismovable(parent))
RegisterSignal(parent, COMSIG_MOVABLE_IMPACT, .proc/explodable_impact)
RegisterSignal(parent, COMSIG_MOVABLE_BUMP, .proc/explodable_bump)
if(isitem(parent))
+1 -1
View File
@@ -54,7 +54,7 @@
/datum/fantasy_affix/pyromantic/apply(datum/component/fantasy/comp, newName)
var/obj/item/master = comp.parent
comp.appliedComponents += master.AddComponent(/datum/component/igniter, CLAMP(comp.quality, 1, 10))
comp.appliedComponents += master.AddComponent(/datum/component/igniter, clamp(comp.quality, 1, 10))
return "pyromantic [newName]"
/datum/fantasy_affix/vampiric
+88
View File
@@ -0,0 +1,88 @@
/**
* Identification components
*/
/datum/component/identification
/// General flags for how we should work.
var/identification_flags = NONE
/// General flags for what we should do.
var/identification_effect_flags = NONE
/// General flags for how we can be identified.
var/identification_method_flags = NONE
/// If this is set, show this on examine to the examiner if they know how to use it.
var/additional_examine_text = "<span class='notice'>You seem to know more about this item than others..</span>"
/// Added to deconstructive analyzer say on success if set
var/deconstructor_reveal_text = "item operation instructions"
/datum/component/identification/Initialize(id_flags, id_effect_flags, id_method_flags)
if(!isobj(parent))
return COMPONENT_INCOMPATIBLE
. = ..()
if(. & COMPONENT_INCOMPATIBLE)
return
identification_flags = id_flags
identification_effect_flags = id_effect_flags
identification_method_flags = id_method_flags
/datum/component/identification/RegisterWithParent()
RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/on_examine)
if(identification_effect_flags & ID_COMPONENT_EFFECT_NO_ACTIONS)
RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/on_equip)
if(identification_method_flags & ID_COMPONENT_IDENTIFY_WITH_DECONSTRUCTOR)
RegisterSignal(parent, COMSIG_ITEM_DECONSTRUCTOR_DEEPSCAN, .proc/on_deconstructor_deepscan)
/datum/component/identification/UnregisterFromParent()
var/list/unregister = list(COMSIG_PARENT_EXAMINE)
if(identification_effect_flags & ID_COMPONENT_EFFECT_NO_ACTIONS)
unregister += COMSIG_ITEM_EQUIPPED
if(identification_method_flags & ID_COMPONENT_IDENTIFY_WITH_DECONSTRUCTOR)
unregister += COMSIG_ITEM_DECONSTRUCTOR_DEEPSCAN
UnregisterSignal(parent, unregister)
/datum/component/identification/proc/on_examine(datum/source, mob/user, list/returnlist)
if(check_knowledge(user) != ID_COMPONENT_KNOWLEDGE_FULL)
return
if(!additional_examine_text)
return
returnlist += additional_examine_text
/datum/component/identification/vv_edit_var(var_name, var_value)
// since i care SOOO much about memory optimization, we only register signals we need to
// so when someone vv's us, we should probably make sure we have the ones we need to with an update.
if((var_value == NAMEOF(src, identification_flags)) || (var_value == NAMEOF(src, identification_effect_flags)) || (var_value == NAMEOF(src, identification_method_flags)))
UnregisterFromParent()
. = ..()
if((var_value == NAMEOF(src, identification_flags)) || (var_value == NAMEOF(src, identification_effect_flags)) || (var_value == NAMEOF(src, identification_method_flags)))
RegisterWithParent()
/datum/component/identification/proc/on_equip(datum/source, mob/user)
if(check_knowledge(user) == ID_COMPONENT_KNOWLEDGE_FULL)
return
if(identification_method_flags & ID_COMPONENT_EFFECT_NO_ACTIONS)
return COMPONENT_NO_GRANT_ACTIONS
/datum/component/identification/proc/check_knowledge(mob/user)
return ID_COMPONENT_KNOWLEDGE_NONE
/datum/component/identification/proc/on_identify(mob/user)
if(identification_flags & ID_COMPONENT_DEL_ON_IDENTIFY)
qdel(src)
/datum/component/identification/proc/on_deconstructor_deepscan(datum/source, obj/machinery/rnd/destructive_analyzer/analyzer, mob/user, list/information = list())
if((identification_method_flags & ID_COMPONENT_IDENTIFY_WITH_DECONSTRUCTOR) && !(identification_flags & ID_COMPONENT_DECONSTRUCTOR_DEEPSCANNED))
identification_flags |= ID_COMPONENT_DECONSTRUCTOR_DEEPSCANNED
on_identify(user)
if(deconstructor_reveal_text)
information += deconstructor_reveal_text
return COMPONENT_DEEPSCAN_UNCOVERED_INFORMATION
/**
* Identification component subtype - Syndicate
*
* Checks if the user is a traitor.
*/
/datum/component/identification/syndicate
/datum/component/identification/syndicate/check_knowledge(mob/user)
. = ..()
if(user?.mind?.has_antag_datum(/datum/antagonist/traitor))
. = max(., ID_COMPONENT_KNOWLEDGE_FULL)
+1 -1
View File
@@ -13,7 +13,7 @@
expire_time = world.time + expire_in
QDEL_IN(src, expire_in)
if(!ismovableatom(parent))
if(!ismovable(parent))
return COMPONENT_INCOMPATIBLE
RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean)
RegisterSignal(parent, COMSIG_MOVABLE_BUCKLE, .proc/try_infect_buckle)
+20 -3
View File
@@ -1,13 +1,18 @@
/datum/component/knockback
/// distance the atom will be thrown
var/throw_distance
/// whether this can throw anchored targets (tables, etc)
var/throw_anchored
/// whether this is a gentle throw (default false means people thrown into walls are stunned / take damage)
var/throw_gentle
/datum/component/knockback/Initialize(throw_distance=1)
/datum/component/knockback/Initialize(throw_distance=1, throw_gentle=FALSE)
if(!isitem(parent) && !ishostile(parent) && !isgun(parent) && !ismachinery(parent) && !isstructure(parent))
return COMPONENT_INCOMPATIBLE
src.throw_distance = throw_distance
src.throw_anchored = throw_anchored
src.throw_gentle = throw_gentle
/datum/component/knockback/RegisterWithParent()
. = ..()
@@ -22,19 +27,31 @@
. = ..()
UnregisterSignal(parent, list(COMSIG_ITEM_AFTERATTACK, COMSIG_HOSTILE_ATTACKINGTARGET, COMSIG_PROJECTILE_ON_HIT))
/// triggered after an item attacks something
/datum/component/knockback/proc/item_afterattack(obj/item/source, atom/target, mob/user, proximity_flag, click_parameters)
if(!proximity_flag)
return
do_knockback(target, user, get_dir(source, target))
/// triggered after a hostile simplemob attacks something
/datum/component/knockback/proc/hostile_attackingtarget(mob/living/simple_animal/hostile/attacker, atom/target)
do_knockback(target, attacker, get_dir(attacker, target))
/// triggered after a projectile hits something
/datum/component/knockback/proc/projectile_hit(atom/fired_from, atom/movable/firer, atom/target, Angle)
do_knockback(target, null, angle2dir(Angle))
/**
* Throw a target in a direction
*
* Arguments:
* * target - Target atom to throw
* * thrower - Thing that caused this atom to be thrown
* * throw_dir - Direction to throw the atom
*/
/datum/component/knockback/proc/do_knockback(atom/target, mob/thrower, throw_dir)
if(!ismovableatom(target) || throw_dir == null)
if(!ismovable(target) || throw_dir == null)
return
var/atom/movable/throwee = target
if(throwee.anchored && !throw_anchored)
@@ -43,4 +60,4 @@
throw_dir = turn(throw_dir, 180)
throw_distance *= -1
var/atom/throw_target = get_edge_target_turf(throwee, throw_dir)
throwee.safe_throw_at(throw_target, throw_distance, 1, thrower)
throwee.safe_throw_at(throw_target, throw_distance, 1, thrower) //, gentle = throw_gentle)
+1 -1
View File
@@ -2,7 +2,7 @@
if(!isatom(parent))
return COMPONENT_INCOMPATIBLE
RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/examine)
if(ismovableatom(parent))
if(ismovable(parent))
RegisterSignal(parent, COMSIG_MOVABLE_CROSSED, .proc/crossed_react)
RegisterSignal(parent, COMSIG_MOVABLE_UNCROSSED, .proc/uncrossed_react)
for(var/i in get_turf(parent))
+3 -2
View File
@@ -14,8 +14,8 @@
var/x = target.x
var/y = target.y
var/z = target.z
var/turf/southwest = locate(CLAMP(x - (direction & WEST ? range : 0), 1, world.maxx), CLAMP(y - (direction & SOUTH ? range : 0), 1, world.maxy), CLAMP(z, 1, world.maxz))
var/turf/northeast = locate(CLAMP(x + (direction & EAST ? range : 0), 1, world.maxx), CLAMP(y + (direction & NORTH ? range : 0), 1, world.maxy), CLAMP(z, 1, world.maxz))
var/turf/southwest = locate(clamp(x - (direction & WEST ? range : 0), 1, world.maxx), clamp(y - (direction & SOUTH ? range : 0), 1, world.maxy), clamp(z, 1, world.maxz))
var/turf/northeast = locate(clamp(x + (direction & EAST ? range : 0), 1, world.maxx), clamp(y + (direction & NORTH ? range : 0), 1, world.maxy), clamp(z, 1, world.maxz))
//holder.vis_contents += block(southwest, northeast) // This doesnt work because of beta bug memes
for(var/i in block(southwest, northeast))
holder.vis_contents += i
@@ -39,4 +39,5 @@
/obj/effect/abstract/mirage_holder
name = "Mirage holder"
anchored = TRUE
plane = PLANE_SPACE
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+58 -10
View File
@@ -1,5 +1,7 @@
#define SLIGHT_INSANITY_PEN 1
#define MINOR_INSANITY_PEN 5
#define MAJOR_INSANITY_PEN 10
#define MOOD_INSANITY_MALUS 0.0054 // per point of sanity below SANITY_DISTURBED, a 40% debuff to skills at rock bottom depression.
/datum/component/mood
var/mood //Real happiness
@@ -22,7 +24,6 @@
RegisterSignal(parent, COMSIG_CLEAR_MOOD_EVENT, .proc/clear_event)
RegisterSignal(parent, COMSIG_MODIFY_SANITY, .proc/modify_sanity)
RegisterSignal(parent, COMSIG_LIVING_REVIVE, .proc/on_revive)
RegisterSignal(parent, COMSIG_MOB_HUD_CREATED, .proc/modify_hud)
var/mob/living/owner = parent
if(owner.hud_used)
@@ -150,7 +151,7 @@
if(8)
setSanity(sanity+0.25, maximum=SANITY_GREAT)
if(9)
setSanity(sanity+0.4, maximum=SANITY_GREAT)
setSanity(sanity+0.4, maximum=SANITY_AMAZING)
HandleNutrition(owner)
@@ -166,6 +167,7 @@
else if(sanity > maximum && amount > sanity - 0.5)
amount = sanity - 0.5
var/old_sanity = sanity
// Disturbed stops you from getting any more sane
if(HAS_TRAIT(master, TRAIT_UNSTABLE))
sanity = min(amount,sanity)
@@ -175,28 +177,34 @@
switch(sanity)
if(-INFINITY to SANITY_CRAZY)
setInsanityEffect(MAJOR_INSANITY_PEN)
master.add_movespeed_modifier(MOVESPEED_ID_SANITY, TRUE, 100, override=TRUE, multiplicative_slowdown=1.5) //Did we change something ? movetypes is runtiming, movetypes=(~FLYING))
master.add_movespeed_modifier(/datum/movespeed_modifier/sanity/insane)
sanity_level = 6
if(SANITY_CRAZY to SANITY_UNSTABLE)
setInsanityEffect(MINOR_INSANITY_PEN)
master.add_movespeed_modifier(MOVESPEED_ID_SANITY, TRUE, 100, override=TRUE, multiplicative_slowdown=1)//, movetypes=(~FLYING))
master.add_movespeed_modifier(/datum/movespeed_modifier/sanity/crazy)
sanity_level = 5
if(SANITY_UNSTABLE to SANITY_DISTURBED)
setInsanityEffect(0)
master.add_movespeed_modifier(MOVESPEED_ID_SANITY, TRUE, 100, override=TRUE, multiplicative_slowdown=0.5)//, movetypes=(~FLYING))
setInsanityEffect(SLIGHT_INSANITY_PEN)
master.add_movespeed_modifier(/datum/movespeed_modifier/sanity/disturbed)
sanity_level = 4
if(SANITY_DISTURBED to SANITY_NEUTRAL)
setInsanityEffect(0)
master.remove_movespeed_modifier(MOVESPEED_ID_SANITY, TRUE)
master.remove_movespeed_modifier(MOVESPEED_ID_SANITY)
sanity_level = 3
if(SANITY_NEUTRAL+1 to SANITY_GREAT+1) //shitty hack but +1 to prevent it from responding to super small differences
setInsanityEffect(0)
master.remove_movespeed_modifier(MOVESPEED_ID_SANITY, TRUE)
master.remove_movespeed_modifier(MOVESPEED_ID_SANITY)
sanity_level = 2
if(SANITY_GREAT+1 to INFINITY)
setInsanityEffect(0)
master.remove_movespeed_modifier(MOVESPEED_ID_SANITY, TRUE)
master.remove_movespeed_modifier(MOVESPEED_ID_SANITY)
sanity_level = 1
if(old_sanity > 1 && sanity == 1)
RegisterSignal(master, COMSIG_MOB_SKILL_GET_AFFINITY, .proc/on_get_skill_affinity)
else if(old_sanity == 1 && sanity > 1)
UnregisterSignal(master, COMSIG_MOB_SKILL_GET_AFFINITY)
//update_mood_icon()
/datum/component/mood/proc/setInsanityEffect(newval)//More code so that the previous proc works
@@ -204,9 +212,14 @@
return
//var/mob/living/master = parent
//master.crit_threshold = (master.crit_threshold - insanity_effect) + newval
if(!insanity_effect && newval)
RegisterSignal(parent, COMSIG_MOB_ACTION_SKILL_MOD, .proc/on_mob_action_skill_mod)
RegisterSignal(parent, COMSIG_MOB_ITEM_ACTION_SKILLS_MOD, .proc/on_item_action_skills_mod)
else if(insanity_effect && !newval)
UnregisterSignal(parent, list(COMSIG_MOB_ACTION_SKILL_MOD, COMSIG_MOB_ITEM_ACTION_SKILLS_MOD))
insanity_effect = newval
/datum/component/mood/proc/modify_sanity(datum/source, amount, minimum = -INFINITY, maximum = INFINITY)
/datum/component/mood/proc/modify_sanity(datum/source, amount, minimum = SANITY_INSANE, maximum = SANITY_AMAZING)
setSanity(sanity + amount, minimum, maximum)
/datum/component/mood/proc/add_event(datum/source, category, type, param) //Category will override any events in the same category, should be unique unless the event is based on the same thing like hunger.
@@ -288,5 +301,40 @@
remove_temp_moods()
setSanity(initial(sanity))
/datum/component/mood/proc/on_mob_action_skill_mod(mob/source, list/skill_args, list/mod_values)
var/datum/skill/S = GLOB.skill_datums[skill_args[ACTION_SKILL_MOD_SKILL]]
if(!(S.skill_flags & SKILL_USE_MOOD))
return
var/debuff = 1 - (SANITY_DISTURBED - sanity) * MOOD_INSANITY_MALUS
mod_values[MOD_VALUES_SKILL_MOD] *= skill_args[ACTION_SKILL_MOD_IS_MULTI] ? debuff : 1/debuff
/datum/component/mood/proc/on_item_action_skills_mod(mob/source, list/skill_args, list/mod_values)
if(skill_args[ITEM_SKILLS_MOD_BAD_FLAGS] & SKILL_USE_MOOD)
return
var/divisor = mod_values[MOD_VALUES_ITEM_SKILLS_DIV]
if(!divisor)
return
var/obj/item/I = skill_args[ITEM_SKILLS_MOD_ITEM]
var/list/L = mod_values[MOD_VALUES_ITEM_SKILLS_CHECKED]
var/skills_len = length(L)
var/affected_skills = skills_len
for(var/k in L)
var/datum/skill/S = k
var/our_flags = I.used_skills[S.type]|S.skill_flags
if(!(our_flags & SKILL_USE_MOOD))
affected_skills--
if(!affected_skills)
return
var/debuff = 1 - (SANITY_DISTURBED - sanity) * MOOD_INSANITY_MALUS * (affected_skills/skills_len)
mod_values[MOD_VALUES_ITEM_SKILLS_SUM] *= skill_args[ITEM_SKILLS_MOD_IS_MULTI] ? debuff : 1/debuff
/datum/component/mood/proc/on_get_skill_affinity(mob/source, skill_path, list/return_value)
var/datum/skill/S = GLOB.skill_datums[skill_path]
if(!S || !(S.skill_flags & SKILL_TRAIN_MOOD))
return
return_value[1] *= SKILL_AFFINITY_MOOD_BONUS
#undef SLIGHT_INSANITY_PEN
#undef MINOR_INSANITY_PEN
#undef MAJOR_INSANITY_PEN
#undef MOOD_INSANITY_MALUS
+6 -6
View File
@@ -12,7 +12,7 @@
var/list/datum/nanite_program/programs = list()
var/max_programs = NANITE_PROGRAM_LIMIT
var/list/datum/nanite_program/protocol/protocols = list() ///Separate list of protocol programs, to avoid looping through the whole programs list when cheking for conflicts
var/list/datum/nanite_program/protocol/protocols = list() ///Separate list of protocol programs, to avoid looping through the whole programs list when checking for conflicts
var/start_time = 0 ///Timestamp to when the nanites were first inserted in the host
var/stealth = FALSE //if TRUE, does not appear on HUDs and health scans
var/diagnostics = TRUE //if TRUE, displays program list when scanned by nanite scanners
@@ -175,7 +175,7 @@
return (nanite_volume > 0)
/datum/component/nanites/proc/adjust_nanites(datum/source, amount)
nanite_volume = CLAMP(nanite_volume + amount, 0, max_nanites)
nanite_volume = clamp(nanite_volume + amount, 0, max_nanites)
if(nanite_volume <= 0) //oops we ran out
qdel(src)
@@ -187,7 +187,7 @@
if(remove || stealth)
return //bye icon
var/nanite_percent = (nanite_volume / max_nanites) * 100
nanite_percent = CLAMP(CEILING(nanite_percent, 10), 10, 100)
nanite_percent = clamp(CEILING(nanite_percent, 10), 10, 100)
holder.icon_state = "nanites[nanite_percent]"
/datum/component/nanites/proc/on_emp(datum/source, severity)
@@ -249,13 +249,13 @@
return FALSE
/datum/component/nanites/proc/set_volume(datum/source, amount)
nanite_volume = CLAMP(amount, 0, max_nanites)
nanite_volume = clamp(amount, 0, max_nanites)
/datum/component/nanites/proc/set_max_volume(datum/source, amount)
max_nanites = max(1, max_nanites)
/datum/component/nanites/proc/set_cloud(datum/source, amount)
cloud_id = CLAMP(amount, 0, 100)
cloud_id = clamp(amount, 0, 100)
/datum/component/nanites/proc/set_cloud_sync(datum/source, method)
switch(method)
@@ -267,7 +267,7 @@
cloud_active = TRUE
/datum/component/nanites/proc/set_safety(datum/source, amount)
safety_threshold = CLAMP(amount, 0, max_nanites)
safety_threshold = clamp(amount, 0, max_nanites)
/datum/component/nanites/proc/set_regen(datum/source, amount)
regen_rate = amount
+4 -4
View File
@@ -22,14 +22,14 @@
/datum/component/orbiter/RegisterWithParent()
. = ..()
var/atom/target = parent
while(ismovableatom(target))
while(ismovable(target))
RegisterSignal(target, COMSIG_MOVABLE_MOVED, .proc/move_react)
target = target.loc
/datum/component/orbiter/UnregisterFromParent()
. = ..()
var/atom/target = parent
while(ismovableatom(target))
while(ismovable(target))
UnregisterSignal(target, COMSIG_MOVABLE_MOVED)
target = target.loc
@@ -111,12 +111,12 @@
// These are prety rarely activated, how often are you following something in a bag?
if(oldloc && !isturf(oldloc)) // We used to be registered to it, probably
var/atom/target = oldloc
while(ismovableatom(target))
while(ismovable(target))
UnregisterSignal(target, COMSIG_MOVABLE_MOVED)
target = target.loc
if(orbited?.loc && orbited.loc != newturf) // We want to know when anything holding us moves too
var/atom/target = orbited.loc
while(ismovableatom(target))
while(ismovable(target))
RegisterSignal(target, COMSIG_MOVABLE_MOVED, .proc/move_react, TRUE)
target = target.loc
+16 -4
View File
@@ -20,16 +20,21 @@
var/ride_check_ridden_incapacitated = FALSE
var/list/offhands = list() // keyed list containing all the current riding offsets associated by mob
var/del_on_unbuckle_all = FALSE
/datum/component/riding/Initialize()
if(!ismovableatom(parent))
if(!ismovable(parent))
return COMPONENT_INCOMPATIBLE
RegisterSignal(parent, COMSIG_MOVABLE_BUCKLE, .proc/vehicle_mob_buckle)
RegisterSignal(parent, COMSIG_MOVABLE_UNBUCKLE, .proc/vehicle_mob_unbuckle)
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/vehicle_moved)
/datum/component/riding/proc/vehicle_mob_unbuckle(datum/source, mob/living/M, force = FALSE)
var/atom/movable/AM = parent
restore_position(M)
unequip_buckle_inhands(M)
if(del_on_unbuckle_all && !AM.has_buckled_mobs())
qdel(src)
/datum/component/riding/proc/vehicle_mob_buckle(datum/source, mob/living/M, force = FALSE)
handle_vehicle_offsets()
@@ -194,26 +199,28 @@
///////Yes, I said humans. No, this won't end well...//////////
/datum/component/riding/human
del_on_unbuckle_all = TRUE
var/fireman_carrying = FALSE
/datum/component/riding/human/Initialize()
. = ..()
directional_vehicle_layers = list(TEXT_NORTH = MOB_LOWER_LAYER, TEXT_SOUTH = MOB_UPPER_LAYER, TEXT_EAST = MOB_UPPER_LAYER, TEXT_WEST = MOB_UPPER_LAYER)
RegisterSignal(parent, COMSIG_HUMAN_MELEE_UNARMED_ATTACK, .proc/on_host_unarmed_melee)
/datum/component/riding/human/vehicle_mob_unbuckle(datum/source, mob/living/M, force = FALSE)
. = ..()
var/mob/living/carbon/human/H = parent
if(!length(H.buckled_mobs))
H.remove_movespeed_modifier(MOVESPEED_ID_HUMAN_CARRYING)
H.remove_movespeed_modifier(/datum/movespeed_modifier/human_carry)
if(!fireman_carrying)
M.Daze(25)
REMOVE_TRAIT(M, TRAIT_MOBILITY_NOUSE, src)
return ..()
/datum/component/riding/human/vehicle_mob_buckle(datum/source, mob/living/M, force = FALSE)
. = ..()
var/mob/living/carbon/human/H = parent
if(length(H.buckled_mobs))
H.add_movespeed_modifier(MOVESPEED_ID_HUMAN_CARRYING, multiplicative_slowdown = fireman_carrying? FIREMAN_CARRY_SLOWDOWN : PIGGYBACK_CARRY_SLOWDOWN)
H.add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/human_carry, TRUE, fireman_carrying? FIREMAN_CARRY_SLOWDOWN : PIGGYBACK_CARRY_SLOWDOWN)
if(fireman_carrying)
ADD_TRAIT(M, TRAIT_MOBILITY_NOUSE, src)
@@ -260,6 +267,11 @@
user.visible_message("<span class='warning'>[AM] pushes [user] off of [AM.p_them()]!</span>")
/datum/component/riding/cyborg
del_on_unbuckle_all = TRUE
/datum/component/riding/cyborg/Initialize()
. = ..()
directional_vehicle_layers = list(TEXT_NORTH = MOB_LOWER_LAYER, TEXT_SOUTH = MOB_UPPER_LAYER, TEXT_EAST = MOB_UPPER_LAYER, TEXT_WEST = MOB_UPPER_LAYER)
/datum/component/riding/cyborg/ride_check(mob/user)
var/atom/movable/AM = parent
+1 -1
View File
@@ -14,7 +14,7 @@
var/default_rotation_direction = ROTATION_CLOCKWISE
/datum/component/simple_rotation/Initialize(rotation_flags = NONE ,can_user_rotate,can_be_rotated,after_rotation)
if(!ismovableatom(parent))
if(!ismovable(parent))
return COMPONENT_INCOMPATIBLE
//throw if no rotation direction is specificed ?
+191
View File
@@ -0,0 +1,191 @@
/datum/component/shielded
dupe_mode = COMPONENT_DUPE_ALLOWED
can_transfer = TRUE
var/charges = 3
var/max_charges = 3
var/recharge_delay = 20 SECONDS //How long after we've been attacked before we can start recharging.
var/recharge_rate = 1 //How quickly the shield recharges once it starts charging. Can be a decimal. set to zero to disable.
var/last_time_used //Last time the shield attempted to stop an attack.
var/accepted_slots
var/shield_state = "shield-old" //the state of the shield overlay.
var/broken_state //null by default.
var/recharge_sound = 'sound/magic/charge.ogg'
var/recharge_end_sound = 'sound/machines/ding.ogg'
var/mob/living/holder //who is currently benefiting from the shield.
var/dissipating = FALSE //Is this shield meant to dissipate over time instead of recharging.
var/del_on_overload = FALSE //will delete itself once it has no charges left.
/datum/component/shielded/Initialize(current, max = 3, delay = 20 SECONDS, rate = 1, slots, state = "shield-old", broken, \
sound = 'sound/magic/charge.ogg', end_sound = 'sound/machines/ding.ogg', diss = FALSE, del_overload = FALSE)
var/isitem = isitem(parent)
if(!isitem && !isliving(parent))
return COMPONENT_INCOMPATIBLE
max_charges = max
charges = !isnull(current) ? current : max_charges
recharge_delay = delay
recharge_rate = rate
accepted_slots = slots
shield_state = state
broken_state = broken
recharge_sound = sound
recharge_end_sound = end_sound
dissipating = diss
del_on_overload = del_overload
if(dissipating && recharge_rate > 0)
recharge_rate = -recharge_rate
if(recharge_delay && recharge_rate && (charges < max_charges || dissipating))
START_PROCESSING(SSdcs, src)
/datum/component/shielded/RegisterWithParent()
. = ..()
if(isitem(parent))
RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/on_equip)
RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/on_drop)
else //it's a mob
var/mob/living/L = parent
RegisterSignal(L, COMSIG_LIVING_RUN_BLOCK, .proc/living_block)
holder = L
var/to_add = charges >= 1 ? shield_state : broken_state
if(to_add)
var/mutable_appearance/M = mutable_appearance('icons/effects/effects.dmi', to_add)
M.layer = (L.layer > MOB_LAYER ? L.layer : MOB_LAYER) + 0.01
holder.add_overlay(M, TRUE)
/datum/component/shielded/UnregisterFromParent()
. = ..()
if(parent != holder) //not a mob, thus an item.
UnregisterSignal(parent, list(COMSIG_ITEM_RUN_BLOCK,COMSIG_ITEM_CHECK_BLOCK,COMSIG_ITEM_EQUIPPED,COMSIG_ITEM_DROPPED))
if(holder)
UnregisterSignal(holder, list(COMSIG_LIVING_RUN_BLOCK, COMSIG_LIVING_GET_BLOCKING_ITEMS))
var/to_remove = charges >= 1 ? shield_state : broken_state
if(to_remove)
holder.cut_overlay(mutable_appearance('icons/effects/effects.dmi', to_remove), TRUE)
holder = null
/datum/component/shielded/process()
if(world.time < last_time_used && !dissipating)
return
var/old_charges = charges
charges = clamp(charges + recharge_rate, 0, max_charges)
if(round(old_charges) >= round(charges)) //only send outputs if it effectively gained at least one charge
return
var/sound = recharge_sound
if(dissipating ? !charges : charges == max_charges )
STOP_PROCESSING(SSdcs, src)
sound = recharge_end_sound
if(parent && sound)
playsound(parent, sound, 50, 1)
if(charges < 1 && del_on_overload)
if(holder)
holder.visible_message("[holder]'s shield overloads!")
qdel(src)
return
if(holder && (old_charges < 1 && charges >= 1) || (!del_on_overload && old_charges >= 1 && charges < 1))
update_shield_overlay(charges < 1)
/datum/component/shielded/proc/adjust_charges(amount)
var/old_charges = charges
charges = clamp(charges + amount, 0, max_charges)
if(recharge_delay && recharge_rate && (dissipating ? !charges : charges == max_charges))
STOP_PROCESSING(SSdcs, src)
if(charges < 1 && del_on_overload)
if(holder)
holder.visible_message("[holder]'s shield overloads!")
qdel(src)
return
if(holder && (old_charges < 1 && charges >= 1) || (!del_on_overload && old_charges >= 1 && charges < 1))
update_shield_overlay(charges < 1)
/datum/component/shielded/proc/update_shield_overlay(broken)
if(!holder)
return
var/to_remove = broken ? shield_state : broken_state
var/to_add = broken ? broken_state : shield_state
if(to_remove)
holder.cut_overlay(mutable_appearance('icons/effects/effects.dmi', to_remove), TRUE)
if(to_add)
var/mutable_appearance/M = mutable_appearance('icons/effects/effects.dmi', to_add)
M.layer = (holder.layer > MOB_LAYER ? holder.layer : MOB_LAYER) + 0.01
holder.add_overlay(M, TRUE)
/datum/component/shielded/proc/on_equip(obj/item/source, mob/living/equipper, slot)
if(!(accepted_slots & slotdefine2slotbit(slot)))
return
holder = equipper
RegisterSignal(parent, COMSIG_ITEM_RUN_BLOCK, .proc/on_run_block)
RegisterSignal(parent, COMSIG_ITEM_CHECK_BLOCK, .proc/on_check_block)
RegisterSignal(equipper, COMSIG_LIVING_GET_BLOCKING_ITEMS, .proc/include_shield)
var/to_add = charges >= 1 ? shield_state : broken_state
if(to_add)
var/mutable_appearance/M = mutable_appearance('icons/effects/effects.dmi', to_add)
M.layer = (holder.layer > MOB_LAYER ? holder.layer : MOB_LAYER) + 0.01
equipper.add_overlay(M, TRUE)
/datum/component/shielded/proc/on_drop(obj/item/source, mob/dropper)
if(holder == dropper)
UnregisterSignal(holder, COMSIG_LIVING_GET_BLOCKING_ITEMS)
UnregisterSignal(parent, list(COMSIG_ITEM_RUN_BLOCK, COMSIG_ITEM_CHECK_BLOCK))
var/to_remove = charges >= 1 ? shield_state : broken_state
if(to_remove)
holder.cut_overlay(mutable_appearance('icons/effects/effects.dmi', to_remove), TRUE)
holder = null
/datum/component/shielded/proc/include_shield(mob/source, list/items)
items += parent
/datum/component/shielded/proc/on_run_block(obj/item/source, mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
if(block_return[BLOCK_RETURN_NORMAL_BLOCK_CHANCE] >= 100) //already blocked by another shielded item, don't do anything.
block_return[BLOCK_RETURN_BLOCK_CAPACITY] += round(charges)
return BLOCK_NONE
last_time_used = world.time + recharge_delay
if(charges < 1)
return BLOCK_NONE
var/datum/effect_system/spark_spread/s = new
s.set_up(2, 1, source)
s.start()
owner.visible_message("<span class='danger'>[holder]'s shields deflect [attack_text] in a shower of sparks!</span>")
charges--
var/rounded_charges = round(charges)
if(recharge_delay && recharge_rate && !dissipating)
START_PROCESSING(SSdcs, src)
if(charges < 1)
owner.visible_message("[holder]'s shield overloads!")
if(del_on_overload)
qdel(src)
else
update_shield_overlay(TRUE)
block_return[BLOCK_RETURN_NORMAL_BLOCK_CHANCE] = 100
block_return[BLOCK_RETURN_BLOCK_CAPACITY] += rounded_charges
return BLOCK_SUCCESS | BLOCK_PHYSICAL_EXTERNAL
/datum/component/shielded/proc/on_check_block(obj/item/source, mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
if(charges >= 1)
block_return[BLOCK_RETURN_NORMAL_BLOCK_CHANCE] = 100
block_return[BLOCK_RETURN_BLOCK_CAPACITY] += round(charges)
/datum/component/shielded/proc/living_block(mob/living/source, real_attack, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list)
if(!real_attack)
if(charges >= 1)
return_list[BLOCK_RETURN_NORMAL_BLOCK_CHANCE] = 100
return_list[BLOCK_RETURN_BLOCK_CAPACITY] = round(charges)
return
last_time_used = world.time + recharge_delay
if(charges < 1)
return BLOCK_NONE
var/datum/effect_system/spark_spread/s = new
s.set_up(2, 1, source)
s.start()
source.visible_message("<span class='danger'>[source]'s shields deflect [attack_text] in a shower of sparks!</span>")
charges--
var/rounded_charges = round(charges)
if(recharge_delay && recharge_rate && !dissipating)
START_PROCESSING(SSdcs, src)
if(charges < 1)
source.visible_message("[source]'s shield overloads!")
if(del_on_overload)
qdel(src)
else
update_shield_overlay(TRUE)
return_list[BLOCK_RETURN_NORMAL_BLOCK_CHANCE] = 100
return_list[BLOCK_RETURN_BLOCK_CAPACITY] += rounded_charges
return BLOCK_SUCCESS | BLOCK_PHYSICAL_EXTERNAL
+2 -2
View File
@@ -14,7 +14,7 @@
parent_atom.opacity = 0
if(isliving(parent_atom))
var/mob/living/L = parent_atom
L.add_movespeed_modifier(MOVESPEED_ID_SHRINK_RAY, update=TRUE, priority=100, multiplicative_slowdown=4)
L.add_movespeed_modifier(/datum/movespeed_modifier/shrink_ray)
if(iscarbon(L))
var/mob/living/carbon/C = L
C.unequip_everything()
@@ -35,7 +35,7 @@
parent_atom.opacity = oldopac
if(isliving(parent_atom))
var/mob/living/L = parent_atom
L.remove_movespeed_modifier(MOVESPEED_ID_SHRINK_RAY)
L.remove_movespeed_modifier(/datum/movespeed_modifier/shrink_ray)
if(ishuman(L))
var/mob/living/carbon/human/H = L
H.physiology.damage_resistance += 100
+1 -1
View File
@@ -16,7 +16,7 @@
if(!isatom(parent))
return COMPONENT_INCOMPATIBLE
RegisterSignal(parent, list(COMSIG_ATOM_ENTERED, COMSIG_ATOM_BLOB_ACT, COMSIG_ATOM_HULK_ATTACK, COMSIG_PARENT_ATTACKBY), .proc/play_squeak)
if(ismovableatom(parent))
if(ismovable(parent))
RegisterSignal(parent, list(COMSIG_MOVABLE_BUMP, COMSIG_MOVABLE_IMPACT), .proc/play_squeak)
RegisterSignal(parent, list(COMSIG_MOVABLE_CROSSED, COMSIG_ITEM_WEARERCROSSED), .proc/play_squeak_crossed)
RegisterSignal(parent, COMSIG_MOVABLE_DISPOSING, .proc/disposing_react)
+1 -1
View File
@@ -5,7 +5,7 @@
var/allow_death = FALSE
/datum/component/stationloving/Initialize(inform_admins = FALSE, allow_death = FALSE)
if(!ismovableatom(parent))
if(!ismovable(parent))
return COMPONENT_INCOMPATIBLE
RegisterSignal(parent, list(COMSIG_MOVABLE_Z_CHANGED), .proc/check_in_bounds)
RegisterSignal(parent, list(COMSIG_MOVABLE_SECLUDED_LOCATION), .proc/relocate)
@@ -56,7 +56,8 @@
/obj/item/scalpel, /obj/item/reagent_containers/syringe, /obj/item/dnainjector,
/obj/item/reagent_containers/hypospray/medipen, /obj/item/reagent_containers/dropper,
/obj/item/implanter, /obj/item/screwdriver, /obj/item/weldingtool/mini,
/obj/item/firing_pin, /obj/item/gun/ballistic/automatic/pistol, /obj/item/gun/ballistic/automatic/magrifle/pistol
/obj/item/firing_pin, /obj/item/gun/ballistic/automatic/pistol, /obj/item/gun/ballistic/automatic/magrifle/pistol,
/obj/item/toy/plush/snakeplushie
))
/datum/component/storage/concrete/pockets/shoes/clown/Initialize()
+12 -12
View File
@@ -15,6 +15,7 @@
var/datum/component/storage/concrete/master //If not null, all actions act on master and this is just an access point.
var/list/can_hold //if this is set, only things in this typecache will fit.
var/list/can_hold_extra //if this is set, it will also be able to hold these.
var/list/cant_hold //if this is set, anything in this typecache will not be able to fit.
var/list/mob/is_using //lazy list of mobs looking at the contents of this storage.
@@ -493,26 +494,25 @@
if(M && !stop_messages)
host.add_fingerprint(M)
return FALSE
if(length(can_hold))
if(!is_type_in_typecache(I, can_hold))
if(!length(can_hold_extra) || !is_type_in_typecache(I, can_hold_extra))
if(length(can_hold) && !is_type_in_typecache(I, can_hold))
if(!stop_messages)
to_chat(M, "<span class='warning'>[host] cannot hold [I]!</span>")
return FALSE
if(is_type_in_typecache(I, cant_hold)) //Check for specific items which this container can't hold.
if(!stop_messages)
to_chat(M, "<span class='warning'>[host] cannot hold [I]!</span>")
return FALSE
// STORAGE LIMITS
if(is_type_in_typecache(I, cant_hold)) //Check for specific items which this container can't hold.
if(!stop_messages)
to_chat(M, "<span class='warning'>[host] cannot hold [I]!</span>")
return FALSE
if(storage_flags & STORAGE_LIMIT_MAX_W_CLASS && I.w_class > max_w_class)
if(!stop_messages)
to_chat(M, "<span class='warning'>[I] is too long for [host]!</span>")
return FALSE
// STORAGE LIMITS
if(storage_flags & STORAGE_LIMIT_MAX_ITEMS)
if(real_location.contents.len >= max_items)
if(!stop_messages)
to_chat(M, "<span class='warning'>[host] has too many things in it, make some space!</span>")
return FALSE //Storage item is full
if(storage_flags & STORAGE_LIMIT_MAX_W_CLASS)
if(I.w_class > max_w_class)
if(!stop_messages)
to_chat(M, "<span class='warning'>[I] is too long for [host]!</span>")
return FALSE
if(storage_flags & STORAGE_LIMIT_COMBINED_W_CLASS)
var/sum_w_class = I.w_class
for(var/obj/item/_I in real_location)
+4 -4
View File
@@ -27,8 +27,8 @@
numbered_contents = _process_numerical_display()
adjusted_contents = numbered_contents.len
var/columns = CLAMP(max_items, 1, maxcolumns ? maxcolumns : screen_max_columns)
var/rows = CLAMP(CEILING(adjusted_contents / columns, 1), 1, screen_max_rows)
var/columns = clamp(max_items, 1, maxcolumns ? maxcolumns : screen_max_columns)
var/rows = clamp(CEILING(adjusted_contents / columns, 1), 1, screen_max_rows)
// First, boxes.
ui_boxes = get_ui_boxes()
@@ -105,7 +105,7 @@
// after this point we are sure we can somehow fit all items into our max number of rows.
// determine rows
var/rows = CLAMP(CEILING(min_pixels / horizontal_pixels, 1), 1, screen_max_rows)
var/rows = clamp(CEILING(min_pixels / horizontal_pixels, 1), 1, screen_max_rows)
var/overrun = FALSE
if(used > our_volume)
@@ -198,7 +198,7 @@
M.active_storage.ui_hide(M)
M.active_storage = src
LAZYOR(is_using, M)
if(volumetric_ui())
if(!M.client?.prefs?.no_tetris_storage && volumetric_ui())
//new volumetric ui bay-style
M.client.screen |= orient2hud_volumetric(M, maxallowedscreensize)
else
+491
View File
@@ -0,0 +1,491 @@
#define MAX_TABLE_MESSES 8 // how many things can we knock off a table at once?
/**
*#tackle.dm
*
* For when you want to throw a person at something and have fun stuff happen
*
* This component is made for carbon mobs (really, humans), and allows its parent to throw themselves and perform tackles. This is done by enabling throw mode, then clicking on your
* intended target with an empty hand. You will then launch toward your target. If you hit a carbon, you'll roll to see how hard you hit them. If you hit a solid non-mob, you'll
* roll to see how badly you just messed yourself up. If, along your journey, you hit a table, you'll slam onto it and send up to MAX_TABLE_MESSES (8) /obj/items on the table flying,
* and take a bit of extra damage and stun for each thing launched.
*
* There are 2 """skill rolls""" involved here, which are handled and explained in sack() and rollTackle() (for roll 1, carbons), and splat() (for roll 2, walls and solid objects)
*/
/datum/component/tackler
dupe_mode = COMPONENT_DUPE_UNIQUE
///How much stamina it takes to launch a tackle
var/stamina_cost
///Launching a tackle calls Knockdown on you for this long, so this is your cooldown. Once you stand back up, you can tackle again.
var/base_knockdown
///Your max range for how far you can tackle.
var/range
///How fast you sail through the air. Standard tackles are 1 speed, but gloves that throw you faster come at a cost: higher speeds make it more likely you'll be badly injured if you fly into a non-mob obstacle.
var/speed
///A flat modifier to your roll against your target, as described in [rollTackle()][/datum/component/tackler/proc/rollTackle]. Slightly misleading, skills aren't relevant here, this is a matter of what type of gloves (or whatever) is granting you the ability to tackle.
var/skill_mod
///Some gloves, generally ones that increase mobility, may have a minimum distance to fly. Rocket gloves are especially dangerous with this, be sure you'll hit your target or have a clear background if you miss, or else!
var/min_distance
///The throwdatum we're currently dealing with, if we need it
var/datum/thrownthing/tackle
/datum/component/tackler/Initialize(stamina_cost = 25, base_knockdown = 1 SECONDS, range = 4, speed = 1, skill_mod = 0, min_distance = min_distance)
if(!iscarbon(parent))
return COMPONENT_INCOMPATIBLE
src.stamina_cost = stamina_cost
src.base_knockdown = base_knockdown
src.range = range
src.speed = speed
src.skill_mod = skill_mod
src.min_distance = min_distance
var/mob/living/carbon/P = parent
to_chat(P, "<span class='notice'>You are now able to launch tackles! You can do so by activating throw intent, and clicking on your target with an empty hand.</span>")
P.tackling = TRUE
addtimer(CALLBACK(src, .proc/resetTackle), base_knockdown, TIMER_STOPPABLE)
/datum/component/tackler/Destroy()
var/mob/living/carbon/P = parent
to_chat(P, "<span class='notice'>You can no longer tackle.</span>")
P.tackling = FALSE
..()
/datum/component/tackler/RegisterWithParent()
RegisterSignal(parent, COMSIG_MOB_CLICKON, .proc/checkTackle)
RegisterSignal(parent, COMSIG_MOVABLE_IMPACT, .proc/sack)
RegisterSignal(parent, COMSIG_MOVABLE_POST_THROW, .proc/registerTackle)
/datum/component/tackler/UnregisterFromParent()
UnregisterSignal(parent, list(COMSIG_MOB_CLICKON, COMSIG_MOVABLE_IMPACT, COMSIG_MOVABLE_MOVED, COMSIG_MOVABLE_POST_THROW))
///Store the thrownthing datum for later use
/datum/component/tackler/proc/registerTackle(mob/living/carbon/user, datum/thrownthing/TT)
tackle = TT
///See if we can tackle or not. If we can, leap!
/datum/component/tackler/proc/checkTackle(mob/living/carbon/user, atom/A, params)
if(!user.in_throw_mode || user.get_active_held_item() || user.pulling || user.buckling)
return
if(HAS_TRAIT(user, TRAIT_HULK))
to_chat(user, "<span class='warning'>You're too angry to remember how to tackle!</span>")
return
if(user.restrained())
to_chat(user, "<span class='warning'>You need free use of your hands to tackle!</span>")
return
if(!(user.mobility_flags & MOBILITY_STAND))
to_chat(user, "<span class='warning'>You must be standing to tackle!</span>")
return
if(user.tackling)
to_chat(user, "<span class='warning'>You're not ready to tackle!</span>")
return
if(user.has_status_effect(STATUS_EFFECT_TASED)) // can't tackle if you just got tased
to_chat(user, "<span class='warning'>You can't tackle while tased!</span>")
return
user.face_atom(A)
var/list/modifiers = params2list(params)
if(modifiers["alt"] || modifiers["shift"] || modifiers["ctrl"] || modifiers["middle"])
return
user.tackling = TRUE
RegisterSignal(user, COMSIG_MOVABLE_MOVED, .proc/checkObstacle)
playsound(user, 'sound/weapons/thudswoosh.ogg', 40, TRUE, -1)
var/leap_word = iscatperson(user) ? "pounce" : "leap" ///If cat, "pounce" instead of "leap".
if(can_see(user, A, 7))
user.visible_message("<span class='warning'>[user] [leap_word]s at [A]!</span>", "<span class='danger'>You [leap_word] at [A]!</span>")
else
user.visible_message("<span class='warning'>[user] [leap_word]s!</span>", "<span class='danger'>You [leap_word]!</span>")
if(get_dist(user, A) < min_distance)
A = get_ranged_target_turf(user, get_dir(user, A), min_distance) //TODO: this only works in cardinals/diagonals, make it work with in-betweens too!
user.Knockdown(base_knockdown, TRUE, TRUE)
user.adjustStaminaLoss(stamina_cost)
user.throw_at(A, range, speed, user, FALSE)
user.toggle_throw_mode()
addtimer(CALLBACK(src, .proc/resetTackle), base_knockdown, TIMER_STOPPABLE)
return(COMSIG_MOB_CANCEL_CLICKON)
/**
* sack()
*
* sack() is called when you actually smack into something, assuming we're mid-tackle. First it deals with smacking into non-carbons, in two cases:
* * If it's a non-carbon mob, we don't care, get out of here and do normal thrown-into-mob stuff
* * Else, if it's something dense (walls, machinery, structures, most things other than the floor), go to splat() and get ready for some high grade shit
*
* If it's a carbon we hit, we'll call rollTackle() which rolls a die and calculates modifiers for both the tackler and target, then gives us a number. Negatives favor the target, while positives favor the tackler.
* Check [rollTackle()][/datum/component/tackler/proc/rollTackle] for a more thorough explanation on the modifiers at play.
*
* Then, we figure out what effect we want, and we get to work! Note that with standard gripper gloves and no modifiers, the range of rolls is (-3, 3). The results are as follows, based on what we rolled:
* * -inf to -5: Seriously botched tackle, tackler suffers a concussion, brute damage, and a 3 second paralyze, target suffers nothing
* * -4 to -2: weak tackle, tackler gets 3 second knockdown, target gets shove slowdown but is otherwise fine
* * -1 to 0: decent tackle, tackler gets up a bit quicker than the target
* * 1: solid tackle, tackler has more of an advantage getting up quicker
* * 2 to 4: expert tackle, tackler has sizeable advantage and lands on their feet with a free passive grab
* * 5 to inf: MONSTER tackle, tackler gets up immediately and gets a free aggressive grab, target takes sizeable stamina damage from the hit and is paralyzed for one and a half seconds and knocked down for three seconds
*
* Finally, we return a bitflag to [COMSIG_MOVABLE_IMPACT] that forces the hitpush to false so that we don't knock them away.
*/
/datum/component/tackler/proc/sack(mob/living/carbon/user, atom/hit)
if(!user.tackling || !tackle)
return
if(!iscarbon(hit))
if(hit.density)
return splat(user, hit)
return
var/mob/living/carbon/target = hit
var/tackle_word = iscatperson(user) ? "pounce" : "tackle" ///If cat, "pounce" instead of "tackle".
var/roll = rollTackle(target)
user.tackling = FALSE
switch(roll)
if(-INFINITY to -5)
user.visible_message("<span class='danger'>[user] botches [user.p_their()] [tackle_word] and slams [user.p_their()] head into [target], knocking [user.p_them()]self silly!</span>", "<span class='userdanger'>You botch your [tackle_word] and slam your head into [target], knocking yourself silly!</span>", target)
to_chat(target, "<span class='userdanger'>[user] botches [user.p_their()] [tackle_word] and slams [user.p_their()] head into you, knocking [user.p_them()]self silly!</span>")
user.Paralyze(30)
var/obj/item/bodypart/head/hed = user.get_bodypart(BODY_ZONE_HEAD)
if(hed)
hed.receive_damage(brute=20, updating_health=TRUE)
user.gain_trauma(/datum/brain_trauma/mild/concussion)
if(-4 to -2) // glancing blow at best
user.visible_message("<span class='warning'>[user] lands a weak [tackle_word] on [target], briefly knocking [target.p_them()] off-balance!</span>", "<span class='userdanger'>You land a weak [tackle_word] on [target], briefly knocking [target.p_them()] off-balance!</span>", target)
to_chat(target, "<span class='userdanger'>[user] lands a weak [tackle_word] on you, briefly knocking you off-balance!</span>")
user.Knockdown(30)
target.apply_status_effect(STATUS_EFFECT_TASED_WEAK, 6 SECONDS)
if(-1 to 0) // decent hit, both parties are about equally inconvenienced
user.visible_message("<span class='warning'>[user] lands a passable [tackle_word] on [target], sending them both tumbling!</span>", "<span class='userdanger'>You land a passable [tackle_word] on [target], sending you both tumbling!</span>", target)
to_chat(target, "<span class='userdanger'>[user] lands a passable [tackle_word] on you, sending you both tumbling!</span>")
target.adjustStaminaLoss(stamina_cost)
target.Paralyze(5)
user.Knockdown(20)
target.Knockdown(25)
if(1 to 2) // solid hit, tackler has a slight advantage
user.visible_message("<span class='warning'>[user] lands a solid [tackle_word] on [target], knocking them both down hard!</span>", "<span class='userdanger'>You land a solid [tackle_word] on [target], knocking you both down hard!</span>", target)
to_chat(target, "<span class='userdanger'>[user] lands a solid [tackle_word] on you, knocking you both down hard!</span>")
target.adjustStaminaLoss(30)
target.Paralyze(5)
user.Knockdown(10)
target.Knockdown(20)
if(3 to 4) // really good hit, the target is definitely worse off here. Without positive modifiers, this is as good a tackle as you can land
user.visible_message("<span class='warning'>[user] lands an expert [tackle_word] on [target], knocking [target.p_them()] down hard while landing on [user.p_their()] feet with a passive grip!</span>", "<span class='userdanger'>You land an expert [tackle_word] on [target], knocking [target.p_them()] down hard while landing on your feet with a passive grip!</span>", target)
to_chat(target, "<span class='userdanger'>[user] lands an expert [tackle_word] on you, knocking you down hard and maintaining a passive grab!</span>")
user.SetKnockdown(0)
user.set_resting(FALSE, TRUE, FALSE)
user.forceMove(get_turf(target))
target.adjustStaminaLoss(40)
target.Paralyze(5)
target.Knockdown(30)
if(ishuman(target) && iscarbon(user))
target.grabbedby(user)
if(5 to INFINITY) // absolutely BODIED
user.visible_message("<span class='warning'>[user] lands a monster [tackle_word] on [target], knocking [target.p_them()] senseless and applying an aggressive pin!</span>", "<span class='userdanger'>You land a monster [tackle_word] on [target], knocking [target.p_them()] senseless and applying an aggressive pin!</span>", target)
to_chat(target, "<span class='userdanger'>[user] lands a monster [tackle_word] on you, knocking you senseless and aggressively pinning you!</span>")
user.SetKnockdown(0)
user.set_resting(FALSE, TRUE, FALSE)
user.forceMove(get_turf(target))
target.adjustStaminaLoss(40)
target.Paralyze(5)
target.Knockdown(30)
if(ishuman(target) && iscarbon(user))
target.grabbedby(user)
target.grippedby(user, instant = TRUE)
SEND_SIGNAL(user, COMSIG_CARBON_TACKLED, roll)
return COMPONENT_MOVABLE_IMPACT_FLIP_HITPUSH
/**
* rollTackle()
*
* This handles all of the modifiers for the actual carbon-on-carbon tackling, and gets its own proc because of how many there are (with plenty more in mind!)
*
* The base roll is between (-3, 3), with negative numbers favoring the target, and positive numbers favoring the tackler. The target and the tackler are both assessed for
* how easy they are to knock over, with clumsiness and dwarfiness being strong maluses for each, and gigantism giving a bonus for each. These numbers and ideas
* are absolutely subject to change.
* In addition, after subtracting the defender's mod and adding the attacker's mod to the roll, the component's base (skill) mod is added as well. Some sources of tackles
* are better at taking people down, like the bruiser and rocket gloves, while the dolphin gloves have a malus in exchange for better mobility.
*/
/datum/component/tackler/proc/rollTackle(mob/living/carbon/target)
var/defense_mod = 0
var/attack_mod = 0
// DE-FENSE
if(target.drunkenness > 60) // drunks are easier to knock off balance
defense_mod -= 3
else if(target.drunkenness > 30)
defense_mod -= 1
if(HAS_TRAIT(target, TRAIT_CLUMSY))
defense_mod -= 2
if(HAS_TRAIT(target, TRAIT_FAT)) // chonkers are harder to knock over
defense_mod += 1
//if(HAS_TRAIT(target, TRAIT_GRABWEAKNESS)) Todo, port the pushover trait
//defense_mod -= 2
if(HAS_TRAIT(target, TRAIT_DWARF))
defense_mod -= 2
if(HAS_TRAIT(target, TRAIT_GIANT))
defense_mod += 2
if(ishuman(target))
var/mob/living/carbon/human/T = target
var/suit_slot = T.get_item_by_slot(ITEM_SLOT_OCLOTHING)
if(isnull(T.wear_suit) && isnull(T.w_uniform)) // who honestly puts all of their effort into tackling a naked guy?
defense_mod += 2
if(suit_slot && (istype(suit_slot,/obj/item/clothing/suit/space/hardsuit)))
defense_mod += 1
if(T.is_shove_knockdown_blocked()) // riot armor and such
defense_mod += 5
if(T.is_holding_item_of_type(/obj/item/shield))
defense_mod += 2
if(islizard(T))
if(!T.getorganslot(ORGAN_SLOT_TAIL)) // lizards without tails are off-balance
defense_mod -= 1
else if(T.dna.species.is_wagging_tail()) // lizard tail wagging is robust and can swat away assailants!
defense_mod += 1
// OF-FENSE
var/mob/living/carbon/sacker = parent
if(sacker.drunkenness > 60) // you're far too drunk to hold back!
attack_mod += 1
else if(sacker.drunkenness > 30) // if you're only a bit drunk though, you're just sloppy
attack_mod -= 1
if(HAS_TRAIT(sacker, TRAIT_CLUMSY))
attack_mod -= 2
if(HAS_TRAIT(sacker, TRAIT_DWARF))
attack_mod -= 2
if(HAS_TRAIT(sacker, TRAIT_GIANT))
attack_mod += 2
if(ishuman(target))
var/mob/living/carbon/human/S = sacker
var/suit_slot = S.get_item_by_slot(ITEM_SLOT_OCLOTHING)
if(suit_slot && (istype(suit_slot,/obj/item/clothing/suit/armor/riot))) // tackling in riot armor is more effective, but tiring
attack_mod += 2
sacker.adjustStaminaLoss(20)
var/r = rand(-3, 3) - defense_mod + attack_mod + skill_mod
return r
/**
* splat()
*
* This is where we handle diving into dense atoms, generally with effects ranging from bad to REALLY bad. This works as a percentile roll that is modified in two steps as detailed below. The higher
* the roll, the more severe the result.
*
* Mod 1: Speed
* * Base tackle speed is 1, which is what normal gripper gloves use. For other sources with higher speed tackles, like dolphin and ESPECIALLY rocket gloves, we obey Newton's laws and hit things harder.
* * For every unit of speed above 1, move the lower bound of the roll up by 15. Unlike Mod 2, this only serves to raise the lower bound, so it can't be directly counteracted by anything you can control.
*
* Mod 2: Misc
* -Flat modifiers, these take whatever you rolled and add/subtract to it, with the end result capped between the minimum from Mod 1 and 100. Note that since we can't roll higher than 100 to start with,
* wearing a helmet should be enough to remove any chance of permanently paralyzing yourself and dramatically lessen knocking yourself unconscious, even with rocket gloves. Will expand on maybe
* * Wearing a helmet: -6
* * Wearing armor: -6
* * Clumsy: +6
*
* Effects: Below are the outcomes based off your roll, in order of increasing severity
* * 1-63: Knocked down for a few seconds and a bit of brute and stamina damage
* * 64-83: Knocked silly, gain some confusion as well as the above
* * 84-93: Cranial trauma, get a concussion and more confusion, plus more damage
* * 94-98: Knocked unconscious, significant chance to get a random mild brain trauma, as well as a fair amount of damage
* * 99-100: Break your spinal cord, get paralyzed, take a bunch of damage too. Very unlucky!
*/
/datum/component/tackler/proc/splat(mob/living/carbon/user, atom/hit)
if(istype(hit, /obj/structure/window))
var/obj/structure/window/W = hit
splatWindow(user, W)
if(QDELETED(W))
return COMPONENT_MOVABLE_IMPACT_NEVERMIND
return
var/oopsie_mod = 0
var/danger_zone = (speed - 1) * 15 // for every extra speed we have over 1, take away 15 of the safest chance
danger_zone = max(min(danger_zone, 100), 1)
if(ishuman(user))
var/mob/living/carbon/human/S = user
var/head_slot = S.get_item_by_slot(ITEM_SLOT_HEAD)
var/suit_slot = S.get_item_by_slot(ITEM_SLOT_OCLOTHING)
if(head_slot && (istype(head_slot,/obj/item/clothing/head/helmet) || istype(head_slot,/obj/item/clothing/head/hardhat)))
oopsie_mod -= 6
if(suit_slot && (istype(suit_slot,/obj/item/clothing/suit/armor/)))
oopsie_mod -= 6
if(HAS_TRAIT(user, TRAIT_CLUMSY))
oopsie_mod += 6 //honk!
var/oopsie = rand(danger_zone, 100)
if(oopsie >= 94 && oopsie_mod < 0) // good job avoiding getting paralyzed! gold star!
to_chat(user, "<span class='usernotice'>You're really glad you're wearing protection!</span>")
oopsie += oopsie_mod
switch(oopsie)
if(99 to INFINITY)
// can you imagine standing around minding your own business when all of the sudden some guy fucking launches himself into a wall at full speed and irreparably paralyzes himself?
user.visible_message("<span class='danger'>[user] slams face-first into [hit] at an awkward angle, severing [user.p_their()] spinal column with a sickening crack! Holy shit!</span>", "<span class='userdanger'>You slam face-first into [hit] at an awkward angle, severing your spinal column with a sickening crack! Holy shit!</span>")
user.adjustStaminaLoss(30)
user.adjustBruteLoss(30)
playsound(user, 'sound/effects/blobattack.ogg', 60, TRUE)
playsound(user, 'sound/effects/splat.ogg', 70, TRUE)
user.emote("scream")
user.gain_trauma(/datum/brain_trauma/severe/paralysis/paraplegic) // oopsie indeed!
shake_camera(user, 7, 7)
user.overlay_fullscreen("flash", /obj/screen/fullscreen/flash)
user.clear_fullscreen("flash", 4.5)
if(94 to 98)
user.visible_message("<span class='danger'>[user] slams face-first into [hit] with a concerning squish, immediately going limp!</span>", "<span class='userdanger'>You slam face-first into [hit], and immediately lose consciousness!</span>")
user.adjustStaminaLoss(100)
user.adjustBruteLoss(30)
user.Unconscious(100)
user.gain_trauma_type(BRAIN_TRAUMA_MILD)
user.playsound_local(get_turf(user), 'sound/weapons/flashbang.ogg', 100, TRUE, 8, 0.9)
shake_camera(user, 6, 6)
user.overlay_fullscreen("flash", /obj/screen/fullscreen/flash)
user.clear_fullscreen("flash", 3.5)
if(84 to 93)
user.visible_message("<span class='danger'>[user] slams head-first into [hit], suffering major cranial trauma!</span>", "<span class='userdanger'>You slam head-first into [hit], and the world explodes around you!</span>")
user.adjustStaminaLoss(30)
user.adjustBruteLoss(30)
user.confused += 15
if(prob(80))
user.gain_trauma(/datum/brain_trauma/mild/concussion)
user.playsound_local(get_turf(user), 'sound/weapons/flashbang.ogg', 100, TRUE, 8, 0.9)
user.DefaultCombatKnockdown(40)
shake_camera(user, 5, 5)
user.overlay_fullscreen("flash", /obj/screen/fullscreen/flash)
user.clear_fullscreen("flash", 2.5)
if(64 to 83)
user.visible_message("<span class='danger'>[user] slams hard into [hit], knocking [user.p_them()] senseless!</span>", "<span class='userdanger'>You slam hard into [hit], knocking yourself senseless!</span>")
user.adjustStaminaLoss(30)
user.adjustBruteLoss(10)
user.confused += 10
user.DefaultCombatKnockdown(30)
shake_camera(user, 3, 4)
if(1 to 63)
user.visible_message("<span class='danger'>[user] slams into [hit]!</span>", "<span class='userdanger'>You slam into [hit]!</span>")
user.adjustStaminaLoss(20)
user.adjustBruteLoss(10)
user.DefaultCombatKnockdown(30)
shake_camera(user, 2, 2)
playsound(user, 'sound/weapons/smash.ogg', 70, TRUE)
/datum/component/tackler/proc/resetTackle()
var/mob/living/carbon/P = parent
P.tackling = FALSE
QDEL_NULL(tackle)
UnregisterSignal(parent, COMSIG_MOVABLE_MOVED)
///A special case for splatting for handling windows
/datum/component/tackler/proc/splatWindow(mob/living/carbon/user, obj/structure/window/W)
playsound(user, "sound/effects/Glasshit.ogg", 140, TRUE)
if(W.type in list(/obj/structure/window, /obj/structure/window/fulltile, /obj/structure/window/unanchored, /obj/structure/window/fulltile/unanchored)) // boring unreinforced windows
for(var/i = 0, i < speed, i++)
var/obj/item/shard/shard = new /obj/item/shard(get_turf(user))
//shard.embedding = list(embed_chance = 100, ignore_throwspeed_threshold = TRUE, impact_pain_mult=3, pain_chance=5)
//shard.AddElement(/datum/element/embed, shard.embedding)
user.hitby(shard, skipcatch = TRUE, hitpush = FALSE)
//shard.embedding = list()
//shard.AddElement(/datum/element/embed, shard.embedding)
W.obj_destruction()
user.adjustStaminaLoss(10 * speed)
user.DefaultCombatKnockdown(40)
user.Paralyze(5)
user.visible_message("<span class='danger'>[user] slams into [W] and shatters it, shredding [user.p_them()]self with glass!</span>", "<span class='userdanger'>You slam into [W] and shatter it, shredding yourself with glass!</span>")
else
user.visible_message("<span class='danger'>[user] slams into [W] like a bug, then slowly slides off it!</span>", "<span class='userdanger'>You slam into [W] like a bug, then slowly slide off it!</span>")
user.Paralyze(2)
user.DefaultCombatKnockdown(20)
W.take_damage(20 * speed)
user.adjustStaminaLoss(10 * speed)
user.adjustBruteLoss(5 * speed)
/datum/component/tackler/proc/delayedSmash(obj/structure/window/W)
if(W)
W.obj_destruction()
playsound(W, "shatter", 70, TRUE)
///Check to see if we hit a table, and if so, make a big mess!
/datum/component/tackler/proc/checkObstacle(mob/living/carbon/owner)
if(!owner.tackling)
return
var/turf/T = get_turf(owner)
var/obj/structure/table/kevved = locate(/obj/structure/table) in T.contents
if(!kevved)
return
var/list/messes = list()
// we split the mess-making into two parts (check what we're gonna send flying, intermission for dealing with the tackler, then actually send stuff flying) for the benefit of making sure the face-slam text
// comes before the list of stuff that goes flying, but can still adjust text + damage to how much of a mess it made
for(var/obj/item/I in T.contents)
if(!I.anchored)
messes += I
if(messes.len >= MAX_TABLE_MESSES)
break
/// for telling HOW big of a mess we just made
var/HOW_big_of_a_miss_did_we_just_make = ""
if(messes.len)
if(messes.len < MAX_TABLE_MESSES / 4)
HOW_big_of_a_miss_did_we_just_make = ", making a mess"
else if(messes.len < MAX_TABLE_MESSES / 2)
HOW_big_of_a_miss_did_we_just_make = ", making a big mess"
else if(messes.len < MAX_TABLE_MESSES)
HOW_big_of_a_miss_did_we_just_make = ", making a giant mess"
else
HOW_big_of_a_miss_did_we_just_make = ", making a ginormous mess!" // an extra exclamation point!! for emphasis!!!
owner.visible_message("<span class='danger'>[owner] trips over [kevved] and slams into it face-first[HOW_big_of_a_miss_did_we_just_make]!</span>", "<span class='userdanger'>You trip over [kevved] and slam into it face-first[HOW_big_of_a_miss_did_we_just_make]!</span>")
owner.adjustStaminaLoss(20 + messes.len * 2)
owner.adjustBruteLoss(10 + messes.len)
owner.Paralyze(2 * messes.len)
owner.DefaultCombatKnockdown(20 + 5 * messes.len) // 2 seconds of knockdown after the paralyze
for(var/obj/item/I in messes)
var/dist = rand(1, 3)
var/sp = 2
if(prob(25 * (src.speed - 1))) // if our tackle speed is higher than 1, with chance (speed - 1 * 25%), throw the thing at our tackle speed + 1
sp = speed + 1
I.throw_at(get_ranged_target_turf(I, pick(GLOB.alldirs), range = dist), range = dist, speed = sp)
I.visible_message("<span class='danger'>[I] goes flying[sp > 3 ? " dangerously fast" : ""]!</span>") // standard embed speed
playsound(owner, 'sound/weapons/smash.ogg', 70, TRUE)
tackle.finalize(hit=TRUE)
resetTackle()
#undef MAX_TABLE_MESSES
+1 -1
View File
@@ -181,7 +181,7 @@
/datum/component/wet_floor/proc/_do_add_wet(type, duration_minimum, duration_add, duration_maximum)
var/time = 0
if(LAZYACCESS(time_left_list, "[type]"))
time = CLAMP(LAZYACCESS(time_left_list, "[type]") + duration_add, duration_minimum, duration_maximum)
time = clamp(LAZYACCESS(time_left_list, "[type]") + duration_add, duration_minimum, duration_maximum)
else
time = min(duration_minimum, duration_maximum)
LAZYSET(time_left_list, "[type]", time)
+1 -1
View File
@@ -42,7 +42,7 @@
addtimer(CALLBACK(src, .proc/charge), charge_rate)
/datum/action/innate/dash/proc/charge()
current_charges = CLAMP(current_charges + 1, 0, max_charges)
current_charges = clamp(current_charges + 1, 0, max_charges)
holder.update_action_buttons_icon()
if(recharge_sound)
playsound(dashing_item, recharge_sound, 50, 1)
+4 -3
View File
@@ -29,7 +29,8 @@
VV_DROPDOWN_OPTION(VV_HK_MARK, "Mark Object")
VV_DROPDOWN_OPTION(VV_HK_DELETE, "Delete")
VV_DROPDOWN_OPTION(VV_HK_EXPOSE, "Show VV To Player")
// VV_DROPDOWN_OPTION(VV_HK_MODIFY_TRAITS, "Modify Traits")
VV_DROPDOWN_OPTION(VV_HK_ADDCOMPONENT, "Add Component/Element")
VV_DROPDOWN_OPTION(VV_HK_MODIFY_TRAITS, "Modify Traits")
//This proc is only called if everything topic-wise is verified. The only verifications that should happen here is things like permission checks!
//href_list is a reference, modifying it in these procs WILL change the rest of the proc in topic.dm of admin/view_variables!
@@ -37,8 +38,8 @@
/datum/proc/vv_do_topic(list/href_list)
if(!usr || !usr.client || !usr.client.holder || !check_rights(NONE))
return FALSE //This is VV, not to be called by anything else.
// if(href_list[VV_HK_MODIFY_TRAITS])
// usr.client.holder.modify_traits(src)
if(href_list[VV_HK_MODIFY_TRAITS])
usr.client.holder.modify_traits(src)
return TRUE
/datum/proc/vv_get_header()
+3 -3
View File
@@ -246,10 +246,10 @@
else
visibility_flags &= ~HIDDEN_SCANNER
SetSpread(CLAMP(2 ** (properties["transmittable"] - symptoms.len), DISEASE_SPREAD_BLOOD, DISEASE_SPREAD_AIRBORNE))
SetSpread(clamp(2 ** (properties["transmittable"] - symptoms.len), DISEASE_SPREAD_BLOOD, DISEASE_SPREAD_AIRBORNE))
permeability_mod = max(CEILING(0.4 * properties["transmittable"], 1), 1)
cure_chance = 15 - CLAMP(properties["resistance"], -5, 5) // can be between 10 and 20
cure_chance = 15 - clamp(properties["resistance"], -5, 5) // can be between 10 and 20
stage_prob = max(properties["stage_rate"], 2)
SetSeverity(properties["severity"])
GenerateCure(properties)
@@ -304,7 +304,7 @@
// Will generate a random cure, the less resistance the symptoms have, the harder the cure.
/datum/disease/advance/proc/GenerateCure()
if(properties && properties.len)
var/res = CLAMP(properties["resistance"] - (symptoms.len / 2), 1, advance_cures.len)
var/res = clamp(properties["resistance"] - (symptoms.len / 2), 1, advance_cures.len)
if(res == oldres)
return
cures = list(pick(advance_cures[res]))
@@ -58,7 +58,7 @@
var/mob/living/carbon/C = M
if(prob(10))
if(trauma_heal_severe)
C.cure_trauma_type(resilience = TRAUMA_RESILIENCE_LOBOTOMY)
C.cure_trauma_type(resilience = TRAUMA_RESILIENCE_SURGERY)
else
C.cure_trauma_type(resilience = TRAUMA_RESILIENCE_BASIC)
@@ -30,7 +30,6 @@ Bonus
symptom_delay_max = 120
var/scramble_language = FALSE
var/datum/language/current_language
var/datum/language_holder/original_language
threshold_desc = list(
"Transmission 14" = "The host's language center of the brain is damaged, leading to complete inability to speak or understand any language.",
"Stage Speed 7" = "Changes voice more often.",
@@ -48,9 +47,6 @@ Bonus
symptom_delay_max = 85
if(A.properties["transmittable"] >= 14) //random language
scramble_language = TRUE
var/mob/living/M = A.affected_mob
var/datum/language_holder/mob_language = M.get_language_holder()
original_language = mob_language.copy()
/datum/symptom/voice_change/Activate(datum/disease/advance/A)
if(!..())
@@ -64,12 +60,10 @@ Bonus
if(ishuman(M))
var/mob/living/carbon/human/H = M
H.SetSpecialVoice(H.dna.species.random_name(H.gender))
if(scramble_language)
H.remove_language(current_language)
if(scramble_language && !current_language) // Last part prevents rerolling language with small amounts of cure.
current_language = pick(subtypesof(/datum/language) - /datum/language/common)
H.grant_language(current_language)
var/datum/language_holder/mob_language = H.get_language_holder()
mob_language.only_speaks_language = current_language
H.add_blocked_language(subtypesof(/datum/language) - current_language, LANGUAGE_VOICECHANGE)
H.grant_language(current_language, TRUE, TRUE, LANGUAGE_VOICECHANGE)
/datum/symptom/voice_change/End(datum/disease/advance/A)
..()
@@ -77,7 +71,5 @@ Bonus
var/mob/living/carbon/human/H = A.affected_mob
H.UnsetSpecialVoice()
if(scramble_language)
var/mob/living/M = A.affected_mob
M.copy_known_languages_from(original_language, TRUE)
current_language = null
QDEL_NULL(original_language)
A.affected_mob.remove_blocked_language(subtypesof(/datum/language), LANGUAGE_VOICECHANGE)
A.affected_mob.remove_all_languages(LANGUAGE_VOICECHANGE) // In case someone managed to get more than one anyway.
+8 -3
View File
@@ -315,7 +315,7 @@
unique_enzymes = generate_unique_enzymes()
uni_identity = generate_uni_identity()
generate_dna_blocks()
features = random_features(species?.id)
features = random_features(species?.id, holder?.gender)
/datum/dna/stored //subtype used by brain mob's stored_dna
@@ -361,6 +361,11 @@
var/datum/species/old_species = dna.species
dna.species = new_race
dna.species.on_species_gain(src, old_species, pref_load)
if(ishuman(src))
qdel(language_holder)
var/species_holder = initial(mrace.species_language_holder)
language_holder = new species_holder(src)
update_atom_languages()
/mob/living/carbon/human/set_species(datum/species/mrace, icon_update = TRUE, pref_load = FALSE)
..()
@@ -675,6 +680,6 @@
var/danger = CONFIG_GET(number/threshold_body_size_slowdown)
if(features["body_size"] < danger)
var/slowdown = 1 + round(danger/features["body_size"], 0.1) * CONFIG_GET(number/body_size_slowdown_multiplier)
holder.add_movespeed_modifier(MOVESPEED_ID_SMALL_STRIDE, TRUE, 100, NONE, TRUE, slowdown, ALL, FLOATING|CRAWLING)
holder.add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/small_stride, TRUE, slowdown)
else if(old_size < danger)
holder.remove_movespeed_modifier(MOVESPEED_ID_SMALL_STRIDE)
holder.remove_movespeed_modifier(/datum/movespeed_modifier/small_stride)
+1 -1
View File
@@ -1,6 +1,6 @@
/datum/element/cleaning/Attach(datum/target)
. = ..()
if(!ismovableatom(target))
if(!ismovable(target))
return ELEMENT_INCOMPATIBLE
RegisterSignal(target, COMSIG_MOVABLE_MOVED, .proc/Clean)
+6 -14
View File
@@ -1,28 +1,20 @@
/datum/element/dusts_on_leaving_area
element_flags = ELEMENT_DETACH | ELEMENT_BESPOKE
id_arg_index = 2
var/list/attached_mobs = list()
var/list/area_types = list()
/datum/element/dusts_on_leaving_area/Attach(datum/target,types)
. = ..()
if(!ismob(target))
return ELEMENT_INCOMPATIBLE
attached_mobs += target
area_types = types
START_PROCESSING(SSprocessing,src)
RegisterSignal(target,COMSIG_ENTER_AREA,.proc/check_dust)
/datum/element/dusts_on_leaving_area/Detach(mob/M)
. = ..()
if(M in attached_mobs)
attached_mobs -= M
if(!attached_mobs.len)
STOP_PROCESSING(SSprocessing,src)
UnregisterSignal(M,COMSIG_ENTER_AREA)
/datum/element/dusts_on_leaving_area/process()
for(var/m in attached_mobs)
var/mob/M = m
var/area/A = get_area(M)
if(!(A.type in area_types))
M.dust(force = TRUE)
Detach(M)
/datum/element/dusts_on_leaving_area/proc/check_dust(datum/source, area/A)
var/mob/M = source
if(istype(M) && !(A.type in area_types))
M.dust(force = TRUE)
+1 -1
View File
@@ -10,7 +10,7 @@
/datum/element/firestacker/Attach(datum/target, amount)
. = ..()
if(!ismovableatom(target))
if(!ismovable(target))
return ELEMENT_INCOMPATIBLE
src.amount = amount
+23 -11
View File
@@ -5,12 +5,16 @@ GLOBAL_LIST_EMPTY(mobs_with_editable_flavor_text) //et tu, hacky code
id_arg_index = 3
var/flavor_name = "Flavor Text"
var/list/texts_by_atom = list()
var/addendum = "This can also be used for OOC notes and preferences!"
var/addendum = ""
var/always_show = FALSE
var/max_len = MAX_FLAVOR_LEN
var/can_edit = TRUE
/// For preference/DNA saving/loading. Null to prevent. Prefs are only loaded from obviously if it exists in preferences.features.
var/save_key
/// Do not attempt to render a preview on examine. If this is on, it will display as \[flavor_name\]
var/examine_no_preview = FALSE
/datum/element/flavor_text/Attach(datum/target, text = "", _name = "Flavor Text", _addendum, _max_len = MAX_FLAVOR_LEN, _always_show = FALSE, _edit = TRUE)
/datum/element/flavor_text/Attach(datum/target, text = "", _name = "Flavor Text", _addendum, _max_len = MAX_FLAVOR_LEN, _always_show = FALSE, _edit = TRUE, _save_key, _examine_no_preview = FALSE)
. = ..()
if(. == ELEMENT_INCOMPATIBLE || !isatom(target)) //no reason why this shouldn't work on atoms too.
@@ -25,6 +29,8 @@ GLOBAL_LIST_EMPTY(mobs_with_editable_flavor_text) //et tu, hacky code
addendum = _addendum
always_show = _always_show
can_edit = _edit
save_key = _save_key
examine_no_preview = _examine_no_preview
RegisterSignal(target, COMSIG_PARENT_EXAMINE, .proc/show_flavor)
@@ -33,9 +39,12 @@ GLOBAL_LIST_EMPTY(mobs_with_editable_flavor_text) //et tu, hacky code
LAZYOR(GLOB.mobs_with_editable_flavor_text[M], src)
M.verbs |= /mob/proc/manage_flavor_tests
if(save_key && ishuman(target))
RegisterSignal(target, COMSIG_HUMAN_PREFS_COPIED_TO, .proc/update_prefs_flavor_text)
/datum/element/flavor_text/Detach(atom/A)
. = ..()
UnregisterSignal(A, COMSIG_PARENT_EXAMINE)
UnregisterSignal(A, list(COMSIG_PARENT_EXAMINE, COMSIG_HUMAN_PREFS_COPIED_TO))
texts_by_atom -= A
if(can_edit && ismob(A))
var/mob/M = A
@@ -58,6 +67,9 @@ GLOBAL_LIST_EMPTY(mobs_with_editable_flavor_text) //et tu, hacky code
var/text = texts_by_atom[target]
if(!text)
return
if(examine_no_preview)
examine_list += "<span class='notice'><a href='?src=[REF(src)];show_flavor=[REF(target)]'>\[[flavor_name]\]</a></span>"
return
var/msg = replacetext(text, "\n", " ")
if(length_char(msg) <= 40)
examine_list += "<span class='notice'>[msg]</span>"
@@ -112,13 +124,17 @@ GLOBAL_LIST_EMPTY(mobs_with_editable_flavor_text) //et tu, hacky code
return TRUE
return FALSE
/datum/element/flavor_text/proc/update_prefs_flavor_text(mob/living/carbon/human/H, datum/preferences/P, icon_updates = TRUE, roundstart_checks = TRUE)
if(P.features.Find(save_key))
texts_by_atom[H] = P.features[save_key]
//subtypes with additional hooks for DNA and preferences.
/datum/element/flavor_text/carbon
//list of antagonists etcetera that should have nothing to do with people's snowflakes.
var/static/list/i_dont_even_know_who_you_are = typecacheof(list(/datum/antagonist/abductor, /datum/antagonist/ert,
/datum/antagonist/nukeop, /datum/antagonist/wizard))
/datum/element/flavor_text/carbon/Attach(datum/target, text = "", _name = "Flavor Text", _addendum, _max_len = MAX_FLAVOR_LEN, _always_show = FALSE, _edit = TRUE)
/datum/element/flavor_text/carbon/Attach(datum/target, text = "", _name = "Flavor Text", _addendum, _max_len = MAX_FLAVOR_LEN, _always_show = FALSE, _edit = TRUE, _save_key = "flavor_text", _examine_no_preview = FALSE)
if(!iscarbon(target))
return ELEMENT_INCOMPATIBLE
. = ..()
@@ -127,7 +143,6 @@ GLOBAL_LIST_EMPTY(mobs_with_editable_flavor_text) //et tu, hacky code
RegisterSignal(target, COMSIG_CARBON_IDENTITY_TRANSFERRED_TO, .proc/update_dna_flavor_text)
RegisterSignal(target, COMSIG_MOB_ANTAG_ON_GAIN, .proc/on_antag_gain)
if(ishuman(target))
RegisterSignal(target, COMSIG_HUMAN_PREFS_COPIED_TO, .proc/update_prefs_flavor_text)
RegisterSignal(target, COMSIG_HUMAN_HARDSET_DNA, .proc/update_dna_flavor_text)
RegisterSignal(target, COMSIG_HUMAN_ON_RANDOMIZE, .proc/unset_flavor)
@@ -136,15 +151,12 @@ GLOBAL_LIST_EMPTY(mobs_with_editable_flavor_text) //et tu, hacky code
UnregisterSignal(C, list(COMSIG_CARBON_IDENTITY_TRANSFERRED_TO, COMSIG_MOB_ANTAG_ON_GAIN, COMSIG_HUMAN_PREFS_COPIED_TO, COMSIG_HUMAN_HARDSET_DNA, COMSIG_HUMAN_ON_RANDOMIZE))
/datum/element/flavor_text/carbon/proc/update_dna_flavor_text(mob/living/carbon/C)
texts_by_atom[C] = C.dna.features["flavor_text"]
/datum/element/flavor_text/carbon/proc/update_prefs_flavor_text(mob/living/carbon/human/H, datum/preferences/P, icon_updates = TRUE, roundstart_checks = TRUE)
texts_by_atom[H] = P.features["flavor_text"]
texts_by_atom[C] = C.dna.features[save_key]
/datum/element/flavor_text/carbon/set_flavor(mob/living/carbon/user)
. = ..()
if(. && user.dna)
user.dna.features["flavor_text"] = texts_by_atom[user]
user.dna.features[save_key] = texts_by_atom[user]
/datum/element/flavor_text/carbon/proc/unset_flavor(mob/living/carbon/user)
texts_by_atom[user] = ""
@@ -153,4 +165,4 @@ GLOBAL_LIST_EMPTY(mobs_with_editable_flavor_text) //et tu, hacky code
if(is_type_in_typecache(antag, i_dont_even_know_who_you_are))
texts_by_atom[user] = ""
if(user.dna)
user.dna.features["flavor_text"] = ""
user.dna.features[save_key] = ""
+1 -1
View File
@@ -164,7 +164,7 @@
release()
/obj/item/clothing/head/mob_holder/mob_can_equip(mob/living/M, mob/living/equipper, slot, disable_warning = FALSE, bypass_equip_delay_self = FALSE)
if(!ishuman(M)) //monkeys holding monkeys holding monkeys...
if(M == held_mob || !ishuman(M)) //monkeys holding monkeys holding monkeys...
return FALSE
return ..()
+215
View File
@@ -0,0 +1,215 @@
/*
* Scavenging element. Its scope shouldn't elude your imagination.
* Basically loot piles that can be searched through for some items.
* In my opinion, these are more engaging than normal maintenance loot spawners.
* The loot doesn't have to be strictly made of items and objects, you could also use it to invoke some "events"
* such as mice, rats, an halloween spook, persistent relics, traps, etcetera, go wild.
*/
/datum/element/scavenging
element_flags = ELEMENT_BESPOKE|ELEMENT_DETACH
id_arg_index = 3
var/list/loot_left_per_atom = list() //loot left per attached atom.
var/list/loot_table //pickweight list of available loot.
var/list/unique_loot //limited loot, once the associated value reaches zero, its key is removed from loot_table
var/scavenge_time = 12 SECONDS //how much time it takes
var/can_use_hands = TRUE //bare handed scavenge time multiplier. If set to zero, only tools are usable.
var/list/tool_types //which tool types the player can use instead of scavenging by hand, associated value is their speed.
var/del_atom_on_depletion = FALSE //Will the atom be deleted when there is no loot left?
var/list/search_texts = list("starts searching through", "start searching through", "You hear rummaging...")
var/loot_restriction = NO_LOOT_RESTRICTION
var/maximum_loot_per_player = 1 //only relevant if there is a restriction.
var/list/scavenger_restriction_list //used for restrictions.
var/mean_loot_weight = 0
var/list/progress_per_atom = list() //seconds of ditched progress per atom, used to resume the work instead of starting over.
var/static/list/players_busy_scavenging = list() //players already busy scavenging.
/datum/element/scavenging/Attach(atom/target, amount = 5, list/loot, list/unique, time = 12 SECONDS, hands = TRUE, list/tools, list/texts, \
del_deplete = FALSE, restriction = NO_LOOT_RESTRICTION, max_per_player = 1)
. = ..()
if(. == ELEMENT_INCOMPATIBLE || !length(loot) || !amount || !istype(target) || isarea(target))
return ELEMENT_INCOMPATIBLE
loot_left_per_atom[target] = amount
if(!loot_table)
loot_table = loot
for(var/A in loot_table) //tally the list weights
mean_loot_weight += loot_table[A]
mean_loot_weight /= length(loot_table)
if(!unique_loot)
unique_loot = unique || list()
scavenge_time = time
can_use_hands = hands
tool_types = tools
if(texts)
search_texts = texts
del_atom_on_depletion = del_deplete
loot_restriction = restriction
maximum_loot_per_player = max_per_player
if(can_use_hands)
RegisterSignal(target, list(COMSIG_ATOM_ATTACK_HAND, COMSIG_ATOM_ATTACK_PAW), .proc/scavenge_barehanded)
if(tool_types)
RegisterSignal(target, COMSIG_PARENT_ATTACKBY, .proc/scavenge_tool)
RegisterSignal(target, COMSIG_PARENT_EXAMINE, .proc/on_examine)
/datum/element/scavenging/Detach(atom/target)
. = ..()
loot_left_per_atom -= target
progress_per_atom -= target
if(maximum_loot_per_player == LOOT_RESTRICTION_MIND_PILE || maximum_loot_per_player == LOOT_RESTRICTION_CKEY_PILE)
maximum_loot_per_player -= target
UnregisterSignal(target, list(COMSIG_ATOM_ATTACK_HAND, COMSIG_PARENT_ATTACKBY, COMSIG_PARENT_EXAMINE))
/datum/element/scavenging/proc/on_examine(atom/source, mob/user, list/examine_list)
var/methods = tool_types.Copy()
if(can_use_hands)
methods += list("bare handed")
if(!length(methods))
return
var/text = english_list(methods, "", " or ")
examine_list += "<span class='notice'>Looks like [source.p_they()] can be scavenged [length(tool_types) ? "with" : ""][length(methods == 1) ? "" : "either "][length(tool_types) ? "a " : ""][text]</span>"
/datum/element/scavenging/proc/scavenge_barehanded(atom/source, mob/user)
scavenge(source, user, 1)
return COMPONENT_NO_ATTACK_HAND
/datum/element/scavenging/proc/scavenge_tool(atom/source, obj/item/I, mob/user, params)
if(user.a_intent == INTENT_HARM || !I.tool_behaviour) //Robust trash disposal techniques!
return
var/speed_multi = tool_types[I.tool_behaviour]
if(!speed_multi)
return
scavenge(source, user, speed_multi)
return COMPONENT_NO_AFTERATTACK
/// This proc has to be asynced (cough cough, do_after) in order to return the comsig values in time to stop the attack chain.
/datum/element/scavenging/proc/scavenge(atom/source, mob/user, speed_multi = 1)
set waitfor = FALSE
if(players_busy_scavenging[user])
return
players_busy_scavenging[user] = TRUE
var/progress_done = progress_per_atom[source]
var/len_messages = length(search_texts)
var/msg_first_person
if(len_messages >= 2)
msg_first_person = "<span class='notice'>You [progress_done ? ", resume a ditched task and " : ""][search_texts[2]] [source].</span>"
var/msg_blind
if(len_messages >= 3)
msg_blind = "<span class='italic'>[search_texts[3]]</span>"
user.visible_message("<span class='notice'>[user] [search_texts[1]] [source].</span>", msg_first_person, msg_blind)
if(do_after(user, scavenge_time * speed_multi, TRUE, source, TRUE, CALLBACK(src, .proc/set_progress, source, world.time), resume_time = progress_done * speed_multi))
spawn_loot(source, user)
players_busy_scavenging -= user
/datum/element/scavenging/proc/set_progress(atom/source, start_time)
progress_per_atom[source] = world.time - start_time
return TRUE
/datum/element/scavenging/proc/spawn_loot(atom/source, mob/user)
progress_per_atom -= source
var/loot = pickweight(loot_table)
var/special = TRUE
var/free = FALSE
if(!loot_left_per_atom[source])
to_chat(user, "<span class='warning'>Looks likes there is nothing worth of interest left in [source], what a shame...</span>")
return
var/num_times = 0
switch(loot_restriction)
if(LOOT_RESTRICTION_MIND)
num_times = LAZYACCESS(scavenger_restriction_list, user.mind)
if(LOOT_RESTRICTION_CKEY)
num_times = LAZYACCESS(scavenger_restriction_list, user.ckey)
if(LOOT_RESTRICTION_MIND_PILE)
var/list/L = LAZYACCESS(scavenger_restriction_list, source)
if(L)
num_times = LAZYACCESS(L, user.mind)
if(LOOT_RESTRICTION_CKEY_PILE)
var/list/L = LAZYACCESS(scavenger_restriction_list, source)
if(L)
num_times = LAZYACCESS(L, user.ckey)
if(num_times >= maximum_loot_per_player)
to_chat(user, "<span class='warning'>You can't find anything else vaguely useful in [source]. Another set of eyes might, however.</span>")
return
switch(loot) // TODO: datumize these out.
if(SCAVENGING_FOUND_NOTHING)
to_chat(user, "<span class='notice'>You found nothing, better luck next time.</span>")
free = TRUE //doesn't consume the loot pile.
if(SCAVENGING_SPAWN_MOUSE)
var/nasty_rodent = pick("mouse", "rodent", "squeaky critter", "stupid pest", "annoying cable chewer", "nasty, ugly, evil, disease-ridden rodent")
to_chat(user, "<span class='notice'>You found something in [source]... no wait, that's just another [nasty_rodent].</span>")
new /mob/living/simple_animal/mouse(source.loc)
if(SCAVENGING_SPAWN_MICE)
user.visible_message("<span class='notice'>A small gang of mice emerges from [source].</span>", \
"<span class='notice'>You found something in [source]... no wait, that's just another- <b>no wait, that's a lot of damn mice.</b></span>")
for(var/i in 1 to rand(4, 6))
new /mob/living/simple_animal/mouse(source.loc)
if(SCAVENGING_SPAWN_TOM)
if(GLOB.tom_existed) //There can only be one.
to_chat(user, "<span class='notice'>You found nothing, better luck next time.</span>")
free = TRUE
else
to_chat(user, "<span class='notice'>You found something in [source]... no wait, that's Tom, the mouse! What is he doing here?</span>")
new /mob/living/simple_animal/mouse/brown/Tom(source.loc)
else
special = FALSE
if(!special) //generic loot. Nothing too strange like more loot spawners anyway.
var/atom/A = new loot(source.loc)
if(isitem(A) && !user.get_active_held_item())
user.put_in_hands(A)
var/rarity_append = "."
switch(loot_table[loot]/mean_loot_weight*100)
if(0 to 1)
rarity_append = "! <b>AMAZING!</b>"
if(1 to 2)
rarity_append = "! Woah!"
if(2 to 5)
rarity_append = ". Rare!"
if(5 to 10)
rarity_append = ". Great."
if(10 to 25)
rarity_append = ". Nice."
if(20 to 50)
rarity_append = ". Not bad."
to_chat(user, "You found something in [source]... it's \a [A][rarity_append]")
if(unique_loot[loot])
var/loot_left = --unique_loot[loot]
if(!loot_left)
loot_table -= loot
unique_loot -= loot
mean_loot_weight = 0
for(var/A in loot_table) //re-tally the list weights
mean_loot_weight += loot_table[A]
mean_loot_weight /= length(loot_table)
if(free)
return
--loot_left_per_atom[source]
if(del_atom_on_depletion && !loot_left_per_atom[source])
source.visible_message("<span class='warning'>[source] has been looted clean.</span>")
qdel(source)
return
if(!loot_restriction)
return
LAZYINITLIST(scavenger_restriction_list)
switch(loot_restriction)
if(LOOT_RESTRICTION_MIND)
scavenger_restriction_list[user.mind]++
if(LOOT_RESTRICTION_CKEY)
scavenger_restriction_list[user.ckey]++
if(LOOT_RESTRICTION_MIND_PILE)
LAZYINITLIST(scavenger_restriction_list[source])
var/list/L = scavenger_restriction_list[source]
L[user.mind]++
if(LOOT_RESTRICTION_CKEY_PILE)
LAZYINITLIST(scavenger_restriction_list[source])
var/list/L = scavenger_restriction_list[source]
L[user.ckey]++
+31
View File
@@ -0,0 +1,31 @@
/datum/element/snailcrawl
element_flags = ELEMENT_DETACH
/datum/element/snailcrawl/Attach(datum/target)
. = ..()
if(!ismovable(target))
return ELEMENT_INCOMPATIBLE
var/P
if(iscarbon(target))
P = .proc/snail_crawl
else
P = .proc/lubricate
RegisterSignal(target, COMSIG_MOVABLE_MOVED, P)
/datum/element/snailcrawl/Detach(mob/living/carbon/target)
. = ..()
UnregisterSignal(target, COMSIG_MOVABLE_MOVED)
if(istype(target))
target.remove_movespeed_modifier(/datum/movespeed_modifier/snail_crawl)
/datum/element/snailcrawl/proc/snail_crawl(mob/living/carbon/snail)
if(snail.resting && !snail.buckled && lubricate(snail))
snail.add_movespeed_modifier(/datum/movespeed_modifier/snail_crawl)
else
snail.remove_movespeed_modifier(/datum/movespeed_modifier/snail_crawl)
/datum/element/snailcrawl/proc/lubricate(atom/movable/snail)
var/turf/open/OT = get_turf(snail)
if(istype(OT))
OT.MakeSlippery(TURF_WET_LUBE, 20)
return TRUE
+28
View File
@@ -0,0 +1,28 @@
#define SHORT 5/7
#define TALL 7/5
/datum/element/squish
element_flags = ELEMENT_DETACH
/datum/element/squish/Attach(datum/target, duration)
. = ..()
if(!iscarbon(target))
return ELEMENT_INCOMPATIBLE
var/mob/living/carbon/C = target
var/was_lying = (C.lying != 0)
addtimer(CALLBACK(src, .proc/Detach, C, was_lying), duration)
C.transform = C.transform.Scale(TALL, SHORT)
/datum/element/squish/Detach(mob/living/carbon/C, was_lying)
. = ..()
if(istype(C))
var/is_lying = (C.lying != 0)
if(was_lying == is_lying)
C.transform = C.transform.Scale(SHORT, TALL)
else
C.transform = C.transform.Scale(TALL, SHORT)
#undef SHORT
#undef TALL
+6 -4
View File
@@ -76,7 +76,9 @@ GLOBAL_LIST_EMPTY(explosions)
//I would make this not ex_act the thing that triggered the explosion,
//but everything that explodes gives us their loc or a get_turf()
//and somethings expect us to ex_act them so they can qdel()
stoplag() //tldr, let the calling proc call qdel(src) before we explode
//stoplag() //tldr, let the calling proc call qdel(src) before we explode
// no - use sleep. stoplag() results in quirky things like explosions taking too long to process and hanging mid-air for no reason.
sleep(0)
EX_PREPROCESS_EXIT_CHECK
@@ -121,14 +123,14 @@ GLOBAL_LIST_EMPTY(explosions)
if(dist <= round(max_range + world.view - 2, 1))
M.playsound_local(epicenter, null, 100, 1, frequency, falloff = 5, S = explosion_sound)
if(baseshakeamount > 0)
shake_camera(M, 25, CLAMP(baseshakeamount, 0, 10))
shake_camera(M, 25, clamp(baseshakeamount, 0, 10))
// You hear a far explosion if you're outside the blast radius. Small bombs shouldn't be heard all over the station.
else if(dist <= far_dist)
var/far_volume = CLAMP(far_dist, 30, 50) // Volume is based on explosion size and dist
var/far_volume = clamp(far_dist, 30, 50) // Volume is based on explosion size and dist
far_volume += (dist <= far_dist * 0.5 ? 50 : 0) // add 50 volume if the mob is pretty close to the explosion
M.playsound_local(epicenter, null, far_volume, 1, frequency, falloff = 5, S = far_explosion_sound)
if(baseshakeamount > 0)
shake_camera(M, 10, CLAMP(baseshakeamount*0.25, 0, 2.5))
shake_camera(M, 10, clamp(baseshakeamount*0.25, 0, 2.5))
EX_PREPROCESS_CHECK_TICK
//postpone processing for a bit
-163
View File
@@ -1,163 +0,0 @@
//used for holding information about unique properties of maps
//feed it json files that match the datum layout
//defaults to box
// -Cyberboss
/datum/map_config
// Metadata
var/config_filename = "_maps/boxstation.json"
var/defaulted = TRUE // set to FALSE by LoadConfig() succeeding
// Config from maps.txt
var/config_max_users = 0
var/config_min_users = 0
var/voteweight = 1
var/max_round_search_span = 0 //If this is nonzero, then if the map has been played more than max_rounds_played within the search span (max determined by define in persistence.dm), this map won't be available.
var/max_rounds_played = 0
// Config actually from the JSON - should default to Box
var/map_name = "Box Station"
var/map_path = "map_files/BoxStation"
var/map_file = "BoxStation.dmm"
var/traits = null
var/space_ruin_levels = 2
var/space_empty_levels = 1
var/minetype = "lavaland"
var/maptype = MAP_TYPE_STATION //This should be used to adjust ingame behavior depending on the specific type of map being played. For instance, if an overmap were added, it'd be appropriate for it to only generate with a MAP_TYPE_SHIP
var/announcertype = "standard" //Determines the announcer the map uses. standard uses the default announcer, classic, but has a random chance to use other similarly-themed announcers, like medibot
var/allow_custom_shuttles = TRUE
var/shuttles = list(
"cargo" = "cargo_box",
"ferry" = "ferry_fancy",
"whiteship" = "whiteship_box",
"emergency" = "emergency_box")
var/year_offset = 540 //The offset of ingame year from the actual IRL year. You know you want to make a map that takes place in the 90's. Don't lie.
/proc/load_map_config(filename = "data/next_map.json", default_to_box, delete_after, error_if_missing = TRUE)
var/datum/map_config/config = new
if (default_to_box)
return config
if (!config.LoadConfig(filename, error_if_missing))
qdel(config)
config = new /datum/map_config // Fall back to Box
if (delete_after)
fdel(filename)
return config
#define CHECK_EXISTS(X) if(!istext(json[X])) { log_world("[##X] missing from json!"); return; }
/datum/map_config/proc/LoadConfig(filename, error_if_missing)
if(!fexists(filename))
if(error_if_missing)
log_world("map_config not found: [filename]")
return
var/json = file(filename)
if(!json)
log_world("Could not open map_config: [filename]")
return
json = file2text(json)
if(!json)
log_world("map_config is not text: [filename]")
return
json = json_decode(json)
if(!json)
log_world("map_config is not json: [filename]")
return
config_filename = filename
CHECK_EXISTS("map_name")
map_name = json["map_name"]
CHECK_EXISTS("map_path")
map_path = json["map_path"]
map_file = json["map_file"]
// "map_file": "BoxStation.dmm"
if (istext(map_file))
if (!fexists("_maps/[map_path]/[map_file]"))
log_world("Map file ([map_path]/[map_file]) does not exist!")
return
// "map_file": ["Lower.dmm", "Upper.dmm"]
else if (islist(map_file))
for (var/file in map_file)
if (!fexists("_maps/[map_path]/[file]"))
log_world("Map file ([map_path]/[file]) does not exist!")
return
else
log_world("map_file missing from json!")
return
if (islist(json["shuttles"]))
var/list/L = json["shuttles"]
for(var/key in L)
var/value = L[key]
shuttles[key] = value
else if ("shuttles" in json)
log_world("map_config shuttles is not a list!")
return
traits = json["traits"]
// "traits": [{"Linkage": "Cross"}, {"Space Ruins": true}]
if (islist(traits))
// "Station" is set by default, but it's assumed if you're setting
// traits you want to customize which level is cross-linked
for (var/level in traits)
if (!(ZTRAIT_STATION in level))
level[ZTRAIT_STATION] = TRUE
// "traits": null or absent -> default
else if (!isnull(traits))
log_world("map_config traits is not a list!")
return
var/temp = json["space_ruin_levels"]
if (isnum(temp))
space_ruin_levels = temp
else if (!isnull(temp))
log_world("map_config space_ruin_levels is not a number!")
return
temp = json["space_empty_levels"]
if (isnum(temp))
space_empty_levels = temp
else if (!isnull(temp))
log_world("map_config space_empty_levels is not a number!")
return
temp = json["year_offset"]
if (isnum(temp))
year_offset = temp
else if (!isnull(temp))
log_world("map_config year_offset is not a number!")
return
if ("minetype" in json)
minetype = json["minetype"]
if ("maptype" in json)
maptype = json["maptype"]
if ("announcertype" in json)
announcertype = json["announcertype"]
allow_custom_shuttles = json["allow_custom_shuttles"] != FALSE
defaulted = FALSE
return TRUE
#undef CHECK_EXISTS
/datum/map_config/proc/GetFullMapPaths()
if (istext(map_file))
return list("_maps/[map_path]/[map_file]")
. = list()
for (var/file in map_file)
. += "_maps/[map_path]/[file]"
/datum/map_config/proc/MakeNextMap()
return config_filename == "data/next_map.json" || fcopy(config_filename, "data/next_map.json")
+3 -3
View File
@@ -116,7 +116,7 @@
"<span class='userdanger'>[A] slams your chest! You can't breathe!</span>")
playsound(get_turf(A), 'sound/effects/hit_punch.ogg', 50, 1, -1)
if(D.losebreath <= 10)
D.losebreath = CLAMP(D.losebreath + 5, 0, 10)
D.losebreath = clamp(D.losebreath + 5, 0, 10)
D.adjustOxyLoss(damage + 5)
log_combat(A, D, "quickchoked")
return TRUE
@@ -128,7 +128,7 @@
playsound(get_turf(A), 'sound/effects/hit_punch.ogg', 50, 1, -1)
D.apply_damage(damage, BRUTE)
if(D.silent <= 10)
D.silent = CLAMP(D.silent + 10, 0, 10)
D.silent = clamp(D.silent + 10, 0, 10)
log_combat(A, D, "neck chopped")
return TRUE
@@ -187,7 +187,7 @@
if(damage >= stunthreshold)
D.visible_message("<span class='warning'>[D] sputters and recoils in pain!</span>", "<span class='userdanger'>You recoil in pain as you are jabbed in a nerve!</span>")
D.drop_all_held_items()
return TRUE
//Krav Maga Gloves
+2 -2
View File
@@ -72,7 +72,7 @@ Unless you know what you're doing, only use the first three numbers. They're in
/datum/material/uranium/on_applied(atom/source, amount, material_flags)
. = ..()
source.AddComponent(/datum/component/radioactive, amount / 20, source, 0) //half-life of 0 because we keep on going.
source.AddComponent(/datum/component/radioactive, amount / 60, source, 0) //half-life of 0 because we keep on going.
/datum/material/uranium/on_removed(atom/source, material_flags)
. = ..()
@@ -92,7 +92,7 @@ Unless you know what you're doing, only use the first three numbers. They're in
/datum/material/plasma/on_applied(atom/source, amount, material_flags)
. = ..()
if(ismovableatom(source))
if(ismovable(source))
source.AddElement(/datum/element/firestacker, amount=1)
source.AddComponent(/datum/component/explodable, 0, 0, amount / 2500, amount / 1250)
+7 -5
View File
@@ -63,7 +63,11 @@
var/force_escaped = FALSE // Set by Into The Sunset command of the shuttle manipulator
var/list/learned_recipes //List of learned recipe TYPES.
/// Our skill holder.
var/datum/skill_holder/skill_holder
/datum/mind/New(var/key)
skill_holder = new(src)
src.key = key
soulOwner = src
martial_art = default_martial_art
@@ -76,12 +80,12 @@
if(antag_datum.delete_on_mind_deletion)
qdel(i)
antag_datums = null
QDEL_NULL(skill_holder)
return ..()
/datum/mind/proc/get_language_holder()
if(!language_holder)
var/datum/language_holder/L = current.get_language_holder(shadow=FALSE)
language_holder = L.copy(src)
language_holder = new (src)
return language_holder
@@ -96,9 +100,6 @@
if(iscarbon(current))
var/mob/living/carbon/C = current
C.disable_intentional_combat_mode(TRUE)
if(!language_holder)
var/datum/language_holder/mob_holder = new_character.get_language_holder(shadow = FALSE)
language_holder = mob_holder.copy(src)
if(key)
if(new_character.key != key) //if we're transferring into a body with a key associated which is not ours
@@ -126,6 +127,7 @@
transfer_martial_arts(new_character)
if(active || force_key_move)
new_character.key = key //now transfer the key to link the client to our new body
current.update_atom_languages()
//CIT CHANGE - makes arousal update when transfering bodies
if(isliving(new_character)) //New humans and such are by default enabled arousal. Let's always use the new mind's prefs.
+4
View File
@@ -81,6 +81,7 @@
/datum/mutation/human/dwarfism/on_acquiring(mob/living/carbon/human/owner)
if(..())
return
ADD_TRAIT(owner, TRAIT_DWARF, GENETIC_MUTATION)
owner.transform = owner.transform.Scale(1, 0.8)
passtable_on(owner, GENETIC_MUTATION)
owner.visible_message("<span class='danger'>[owner] suddenly shrinks!</span>", "<span class='notice'>Everything around you seems to grow..</span>")
@@ -88,6 +89,7 @@
/datum/mutation/human/dwarfism/on_losing(mob/living/carbon/human/owner)
if(..())
return
REMOVE_TRAIT(owner, TRAIT_DWARF, GENETIC_MUTATION)
owner.transform = owner.transform.Scale(1, 1.25)
passtable_off(owner, GENETIC_MUTATION)
owner.visible_message("<span class='danger'>[owner] suddenly grows!</span>", "<span class='notice'>Everything around you seems to shrink..</span>")
@@ -339,6 +341,7 @@
/datum/mutation/human/gigantism/on_acquiring(mob/living/carbon/human/owner)
if(..())
return
ADD_TRAIT(owner, TRAIT_GIANT, GENETIC_MUTATION)
owner.resize = 1.25
owner.update_transform()
owner.visible_message("<span class='danger'>[owner] suddenly grows!</span>", "<span class='notice'>Everything around you seems to shrink..</span>")
@@ -346,6 +349,7 @@
/datum/mutation/human/gigantism/on_losing(mob/living/carbon/human/owner)
if(..())
return
REMOVE_TRAIT(owner, TRAIT_GIANT, GENETIC_MUTATION)
owner.resize = 0.8
owner.update_transform()
owner.visible_message("<span class='danger'>[owner] suddenly shrinks!</span>", "<span class='notice'>Everything around you seems to grow..</span>")
+4 -4
View File
@@ -277,10 +277,10 @@
/datum/mutation/human/stoner/on_acquiring(mob/living/carbon/human/owner)
..()
owner.grant_language(/datum/language/beachbum)
owner.remove_language(/datum/language/common)
owner.grant_language(/datum/language/beachbum, TRUE, TRUE, LANGUAGE_STONER)
owner.add_blocked_language(subtypesof(/datum/language) - /datum/language/beachbum, LANGUAGE_STONER)
/datum/mutation/human/stoner/on_losing(mob/living/carbon/human/owner)
..()
owner.grant_language(/datum/language/common)
owner.remove_language(/datum/language/beachbum)
owner.remove_language(/datum/language/beachbum, TRUE, TRUE, LANGUAGE_STONER)
owner.remove_blocked_language(subtypesof(/datum/language) - /datum/language/beachbum, LANGUAGE_STONER)
+1 -1
View File
@@ -20,7 +20,7 @@
return sqrt(((b.x - a.x) ** 2) + ((b.y - a.y) ** 2))
/proc/angle_between_points(datum/point/a, datum/point/b)
return ATAN2((b.y - a.y), (b.x - a.x))
return arctan((b.y - a.y), (b.x - a.x))
/datum/position //For positions with map x/y/z and pixel x/y so you don't have to return lists. Could use addition/subtraction in the future I guess.
var/x = 0
+1 -1
View File
@@ -38,7 +38,7 @@
if (user.client)
user.client.images += bar
progress = CLAMP(progress, 0, goal)
progress = clamp(progress, 0, goal)
bar.icon_state = "prog_bar_[round(((progress / goal) * 100), 5)]"
if (!shown)
user.client.images += bar
+112
View File
@@ -0,0 +1,112 @@
// Hey! Listen! Update \config\iceruinblacklist.txt with your new ruins!
/datum/map_template/ruin/icemoon
prefix = "_maps/RandomRuins/IceRuins/"
allow_duplicates = FALSE
cost = 5
// above ground only
/datum/map_template/ruin/icemoon/lust
name = "Ruin of Lust"
id = "lust"
description = "Not exactly what you expected."
suffix = "icemoon_surface_lust.dmm"
/datum/map_template/ruin/icemoon/asteroid
name = "Asteroid Site"
id = "asteroidsite"
description = "Surprised to see us here?"
suffix = "icemoon_surface_asteroid.dmm"
/datum/map_template/ruin/icemoon/hotsprings
name = "Hot Springs"
id = "hotsprings"
description = "Just relax and take a dip, nothing will go wrong, I swear!"
suffix = "icemoon_surface_hotsprings.dmm"
/datum/map_template/ruin/icemoon/engioutpost
name = "Engineer Outpost"
id = "engioutpost"
description = "Blown up by an unfortunate accident."
suffix = "icemoon_surface_engioutpost.dmm"
/datum/map_template/ruin/icemoon/fountain
name = "Fountain Hall"
id = "fountain"
description = "The fountain has a warning on the side. DANGER: May have undeclared side effects that only become obvious when implemented."
prefix = "_maps/RandomRuins/AnywhereRuins/"
suffix = "fountain_hall.dmm"
// above and below ground together
/datum/map_template/ruin/icemoon/mining_site
name = "Mining Site"
id = "miningsite"
description = "Ruins of a site where people once mined with primitive tools for ore."
suffix = "icemoon_surface_mining_site.dmm"
always_place = TRUE
always_spawn_with = list(/datum/map_template/ruin/icemoon/underground/mining_site_below = PLACE_BELOW)
/datum/map_template/ruin/icemoon/underground/mining_site_below
name = "Mining Site Underground"
id = "miningsite-underground"
description = "Who knew ladders could be so useful?"
suffix = "icemoon_underground_mining_site.dmm"
unpickable = TRUE
// below ground only
/datum/map_template/ruin/icemoon/underground
name = "underground ruin"
/datum/map_template/ruin/icemoon/underground/abandonedvillage
name = "Abandoned Village"
id = "abandonedvillage"
description = "Who knows what lies within?"
suffix = "icemoon_underground_abandoned_village.dmm"
/datum/map_template/ruin/icemoon/underground/library
name = "Buried Library"
id = "buriedlibrary"
description = "A once grand library, now lost to the confines of the Ice Moon."
suffix = "icemoon_underground_library.dmm"
/datum/map_template/ruin/icemoon/underground/wrath
name = "Ruin of Wrath"
id = "wrath"
description = "You'll fight and fight and just keep fighting."
suffix = "icemoon_underground_wrath.dmm"
/datum/map_template/ruin/icemoon/underground/lavaland
name = "Lavaland Site"
id = "lavalandsite"
description = "I guess we never really left you huh?"
suffix = "icemoon_underground_lavaland.dmm"
/datum/map_template/ruin/icemoon/underground/puzzle
name = "Ancient Puzzle"
id = "puzzle"
description = "Mystery to be solved."
suffix = "icemoon_underground_puzzle.dmm"
/datum/map_template/ruin/icemoon/underground/bathhouse
name = "Bath House"
id = "bathhouse"
description = "A taste of paradise, locked in the hell of the Ice Moon."
suffix = "icemoon_underground_bathhouse.dmm"
/datum/map_template/ruin/icemoon/underground/wendigo_cave
name = "Wendigo Cave"
id = "wendigocave"
description = "Into the jaws of the beast."
suffix = "icemoon_underground_wendigo_cave.dmm"
/datum/map_template/ruin/icemoon/underground/free_golem
name = "Free Golem Ship"
id = "golem-ship"
description = "Lumbering humanoids, made out of precious metals, move inside this ship. They frequently leave to mine more minerals, which they somehow turn into more of them. \
Seem very intent on research and individual liberty, and also geology-based naming?"
prefix = "_maps/RandomRuins/AnywhereRuins/"
suffix = "golem_ship.dmm"
allow_duplicates = FALSE
+4 -2
View File
@@ -67,7 +67,8 @@
description = "Lumbering humanoids, made out of precious metals, move inside this ship. They frequently leave to mine more minerals, which they somehow turn into more of them. \
Seem very intent on research and individual liberty, and also geology based naming?"
cost = 20
suffix = "lavaland_surface_golem_ship.dmm"
prefix = "_maps/RandomRuins/AnywhereRuins/"
suffix = "golem_ship.dmm"
allow_duplicates = FALSE
/datum/map_template/ruin/lavaland/animal_hospital
@@ -175,7 +176,8 @@
name = "Fountain Hall"
id = "fountain"
description = "The fountain has a warning on the side. DANGER: May have undeclared side effects that only become obvious when implemented."
suffix = "lavaland_surface_fountain_hall.dmm"
prefix = "_maps/RandomRuins/AnywhereRuins/"
suffix = "fountain_hall.dmm"
cost = 5
allow_duplicates = FALSE
+4 -5
View File
@@ -23,14 +23,14 @@
mappath = "[prefix][shuttle_id].dmm"
. = ..()
/datum/map_template/shuttle/preload_size(path, cache)
/datum/map_template/shuttle/preload_size(path = mappath, force_cache = FALSE)
. = ..(path, TRUE) // Done this way because we still want to know if someone actualy wanted to cache the map
if(!cached_map)
return
discover_port_offset()
if(!cache)
if(!cached_map)
cached_map = null
/datum/map_template/shuttle/proc/discover_port_offset()
@@ -53,12 +53,11 @@
++xcrd
--ycrd
/datum/map_template/shuttle/load(turf/T, centered, register=TRUE)
/datum/map_template/shuttle/load(turf/T, centered = FALSE, orientation = SOUTH, annihilate = default_annihilate, force_cache = FALSE, rotate_placement_to_orientation = FALSE, register = TRUE)
. = ..()
if(!.)
return
var/list/turfs = block( locate(.[MAP_MINX], .[MAP_MINY], .[MAP_MINZ]),
locate(.[MAP_MAXX], .[MAP_MAXY], .[MAP_MAXZ]))
var/list/turfs = get_last_loaded_turf_block()
for(var/i in 1 to turfs.len)
var/turf/place = turfs[i]
if(istype(place, /turf/open/space)) // This assumes all shuttles are loaded in a single spot then moved to their real destination.
+16
View File
@@ -0,0 +1,16 @@
// yeah yeah verbs suck whatever I suck at this fix this someone please - kevinz000
/mob/verb/check_skills()
set name = "Check Skills"
set category = "IC"
set desc = "Check your skills (if you have any..)"
if(!mind)
to_chat(usr, "<span class='warning'>How do you check the skills of [(usr == src)? "yourself when you are" : "something"] without a mind?</span>")
return
if(!mind.skill_holder)
to_chat(usr, "<span class='warning'>How do you check the skills of [(usr == src)? "yourself when you are" : "something"] without the capability for skills? (PROBABLY A BUG, PRESS F1.)</span>")
return
var/datum/browser/B = new(usr, "skilldisplay_[REF(src)]", "Skills of [src]")
B.set_content(mind.skill_holder.html_readout())
B.open()
+208
View File
@@ -0,0 +1,208 @@
GLOBAL_LIST_INIT_TYPED(skill_datums, /datum/skill, init_skill_datums())
/proc/init_skill_datums()
. = list()
for(var/path in subtypesof(/datum/skill))
var/datum/skill/S = path
if(initial(S.abstract_type) == path)
continue
S = new path
.[S.type] = S
/proc/sanitize_skill_value(path, value)
var/datum/skill/S = GLOB.skill_datums[path]
// don't check, if we runtime let it happen.
return S.sanitize_value(value)
/proc/is_skill_value_greater(path, existing, new_value)
var/datum/skill/S = GLOB.skill_datums[path]
// don't check, if we runtime let it happen.
return S.is_value_greater(existing, new_value)
/**
* Skill datums
*/
/datum/skill
/// Our name
var/name
/// Our description
var/desc
/// Color of the name as shown in the html readout
var/name_color = "#F0F0F0" // White on dark surface.
/// Our progression type
var/progression_type
/// Abstract type
var/abstract_type = /datum/skill
/// skill threshold used in generic skill modifiers calculations.
var/list/competency_thresholds = list(0, 0, 0)
/// Multiplier of the difference of the holder skill value and the selected threshold.
var/list/competency_mults = list(0, 0, 0)
/// In which way this skil can affect or be affected through actions.
var/skill_flags = SKILL_USE_MOOD|SKILL_TRAIN_MOOD
/**
* Ensures what someone's setting as a value for this skill is valid.
*/
/datum/skill/proc/sanitize_value(new_value)
return new_value
/**
* Sets the new value of this skill in the holder skills list.
* As well as possible feedback messages or secondary effects on value change, that's on you.
*/
/datum/skill/proc/set_skill_value(datum/skill_holder/H, value, mob/owner)
H.skills[type] = value
/**
* Checks if a value is greater
*/
/datum/skill/proc/is_value_greater(existing, new_value)
if(!existing)
return TRUE
return new_value > existing
/**
* Standard value "render"
*/
/datum/skill/proc/standard_render_value(value, level)
return value
// Just saying, the choice to use different sub-parent-types is to force coders to resolve issues as I won't be implementing custom procs to grab skill levels in a certain context.
// Aka: So people don't forget to change checks if they change a skill's progression type.
/datum/skill/binary
abstract_type = /datum/skill/binary
progression_type = SKILL_PROGRESSION_BINARY
competency_thresholds = list(FALSE, TRUE, TRUE)
competency_mults = list(0.5, 0.5, 0.5)
/datum/skill/binary/sanitize_value(new_value)
return new_value? TRUE : FALSE
/datum/skill/binary/standard_render_value(value, level)
return value? "Yes" : "No"
/datum/skill/numerical
abstract_type = /datum/skill/numerical
progression_type = SKILL_PROGRESSION_NUMERICAL
/// Max value of this skill
var/max_value = 100
/// Min value of this skill
var/min_value = 0
/// Display as a percent in standard_render_value?
var/display_as_percent = FALSE
/datum/skill/numerical/sanitize_value(new_value)
return clamp(new_value, min_value, max_value)
/datum/skill/numerical/standard_render_value(value, level)
return display_as_percent? "[round(value/max_value/100, 0.01)]%" : "[value] / [max_value]"
/datum/skill/enum
abstract_type = /datum/skill/enum
progression_type = SKILL_PROGRESSION_ENUM
/// Valid values for the skill
var/list/valid_values = list()
/datum/skill/enum/sanitize_value(new_value)
if(new_value in valid_values)
return new_value
/**
* Classing r p g styled skills, tiered by lvl, and current/nextlvl experience.
*/
/datum/skill/level
abstract_type = /datum/skill/level
progression_type = SKILL_PROGRESSION_LEVEL
var/standard_xp_lvl_up = STD_XP_LVL_UP //the standard required to level up. def: 100
var/xp_lvl_multiplier = STD_XP_LVL_MULTI //standard required level up exp multiplier. def: 2 (100, 200, 400, 800 etc.)
var/max_levels = STD_MAX_LVL
var/level_up_method = STANDARD_LEVEL_UP //how levels are calculated.
var/list/levels = list() //level thresholds, if associative, these will be preceded by tiers such as "novice" or "trained"
var/associative = FALSE //See above.
var/unskilled_tier = "Unskilled" //Only relevant for associative experience levels
//Builds the levels list.
/datum/skill/level/New()
. = ..()
var/max_assoc = ""
var/max_assoc_start = 1
for(var/lvl in 1 to max_levels)
var/value
switch(level_up_method)
if(STANDARD_LEVEL_UP)
value = XP_LEVEL(standard_xp_lvl_up, xp_lvl_multiplier, lvl)
if(DWARFY_LEVEL_UP)
value = DORF_XP_LEVEL(standard_xp_lvl_up, xp_lvl_multiplier, lvl)
value = round(value, 1)
if(!associative)
levels += value
continue
if(max_assoc)
levels["[max_assoc] +[max_assoc_start++]"] = value
continue
var/key = LAZYACCESS(levels, lvl)
if(!key)
if(lvl == 1) //You dun goof it.
stack_trace("Skill datum [src] was set to have an associative levels list despite the latter having no key value.")
associative = FALSE
levels += value
continue
max_assoc = levels[lvl-1]
levels["[max_assoc] +[max_assoc_start++]"] = value
levels[key] = value
/datum/skill/level/sanitize_value(new_value)
return max(new_value, 0)
/datum/skill/level/set_skill_value(datum/skill_holder/H, value, datum/mind/M, silent = FALSE)
H.skills[type] = value
var/new_level
for(var/k in levels)
if(value < (associative ? levels[k] : k))
break
new_level++
var/old_level = LAZYACCESS(H.skill_levels, type)
LAZYSET(H.skill_levels, type, new_level)
. = new_level - old_level
if(silent || !(M?.current))
return
if(. > 0)
to_chat(M.current, "<span class='nicegreen'>I feel like I've become more proficient at [name]!</span>")
else if(. < 0)
to_chat(M.current, "<span class='warning'>I feel like I've become worse at [name]!</span>")
/datum/skill/level/standard_render_value(value, level)
var/current_lvl = associative ? (!level ? unskilled_tier : levels[level]) : level
var/current_lvl_xp_sum = 0
if(level)
current_lvl_xp_sum = associative ? levels[levels[level]] : levels[level]
var/next_index = max(max_levels, level+1)
var/next_lvl_xp = associative ? levels[levels[next_index]] : levels[next_index]
if(next_lvl_xp > current_lvl_xp_sum)
next_lvl_xp -= current_lvl_xp_sum
return "[associative ? current_lvl : "Lvl. [current_lvl]"] ([value - current_lvl_xp_sum]/[next_lvl_xp])[level == max_levels ? " \[MAX!\]" : ""]"
/datum/skill/level/job
levels = list("Basic", "Trained", "Experienced", "Master")
competency_thresholds = list(JOB_SKILL_TRAINED, JOB_SKILL_EXPERT, JOB_SKILL_MASTER)
competency_mults = list(0.15, 0.1, 0.1)
associative = TRUE
//quite the reference, no?
/datum/skill/level/dwarfy
abstract_type = /datum/skill/level/dwarfy
standard_xp_lvl_up = DORF_XP_LVL_UP
xp_lvl_multiplier = DORF_XP_LVL_MULTI
max_levels = DORF_MAX_LVL
level_up_method = DWARFY_LEVEL_UP
levels = list("Novice", "Adequate", "Competent", "Skilled",
"Proficient", "Talented", "Adept", "Expert",
"Professional", "Accomplished", "Great", "Master",
"High Master", "Grand Master", "Legendary")
competency_thresholds = list(DORF_SKILL_COMPETENT, DORF_SKILL_EXPERT, DORF_SKILL_MASTER)
competency_mults = list(0.15, 0.1, 0.08)
associative = TRUE
unskilled_tier = "Dabbling"
+171
View File
@@ -0,0 +1,171 @@
/**
* Skill holder datums
*/
/datum/skill_holder
var/datum/mind/owner
/// Our list of skills and values. Lazylist. Associative. Keys are datum typepaths to the skill.
var/list/skills
/// Same as [skills] but affinities, which are multiplied to increase amount when gaining skills.
var/list/skill_affinities
/// Let's say we want to get a specific skill "level" without looping through a proc everytime.
/// Only supported by skills with tiers or levels.
var/list/skill_levels
/datum/skill_holder/New(datum/mind/M)
. = ..()
owner = M
/**
* Grabs the value of a skill.
*/
/datum/skill_holder/proc/get_skill_value(skill)
if(!ispath(skill))
CRASH("Invalid get_skill_value call. Use typepaths.") //until a time when we somehow need text ids for dynamic skills, I'm enforcing this.
if(!skills)
return null
return skills[skill]
/**
* Grabs the level of a skill. Only supported by skills with tiers or levels.
*/
/datum/skill_holder/proc/get_skill_level(skill)
if(!ispath(skill, /datum/skill))
CRASH("Invalid get_skill_value call. Use skill typepaths.") //until a time when we somehow need text ids for dynamic skills, I'm enforcing this.
if(!skill_levels)
return 0
return skill_levels[skill]
/**
* Grabs our affinity for a skill. !!This is a multiplier!!
*/
/datum/skill_holder/proc/get_skill_affinity(skill)
. = 1
if(!ispath(skill, /datum/skill))
CRASH("Invalid get_skill_affinity call. Use skill typepaths.") //until a time when we somehow need text ids for dynamic skills, I'm enforcing this.
var/affinity = LAZYACCESS(skill_affinities, skill)
if(!isnull(affinity))
. = affinity
var/list/wrapped = list(.)
SEND_SIGNAL(owner.current, COMSIG_MOB_SKILL_GET_AFFINITY, skill, wrapped)
. = wrapped[1]
/**
* Sets the value of a skill.
*/
/datum/skill_holder/proc/set_skill_value(skill, value, silent = FALSE)
if(!ispath(skill, /datum/skill))
CRASH("Invalid set_skill_value call. Use skill typepaths.") //until a time when we somehow need text ids for dynamic skills, I'm enforcing this.
var/datum/skill/S = GLOB.skill_datums[skill]
value = S.sanitize_value(value)
if(!isnull(value))
LAZYINITLIST(skills)
S.set_skill_value(src, value, owner, silent)
return TRUE
return FALSE
/**
* Boosts a skill to a value if not aobve
*/
/datum/skill_holder/proc/boost_skill_value_to(skill, value, silent = FALSE)
var/current = get_skill_value(skill)
if(!is_skill_value_greater(skill, current, value))
return FALSE
set_skill_value(skill, value, silent)
return TRUE
/**
* Automatic skill increase, multiplied by skill affinity if existing.
* Only works if skill is numerical.
*/
/datum/skill_holder/proc/auto_gain_experience(skill, value, maximum, silent = FALSE)
if(!ispath(skill, /datum/skill))
CRASH("Invalid set_skill_value call. Use skill typepaths.")
var/datum/skill/S = GLOB.skill_datums[skill]
if(S.progression_type != SKILL_PROGRESSION_NUMERICAL && S.progression_type != SKILL_PROGRESSION_LEVEL)
CRASH("You cannot auto increment a non numerical(experience skill!")
var/current = get_skill_value(skill)
var/affinity = get_skill_affinity(skill)
var/target_value = current + (value * affinity)
if(maximum)
target_value = max(target_value, maximum)
if(target_value == maximum) //no more experience to gain, early return.
return
boost_skill_value_to(skill, target_value, silent)
/**
* Generic value modifier proc that uses one skill.
* Args:
* * value : the value to modify, may be a delay, damage, probability.
* * threshold : The difficulty of the action, in short. Refer to __DEFINES/skills/defines.dm for the defines.
* * modifier_is_multiplier : wheter the modifier is a multiplier or a divisor.
*/
/datum/skill_holder/proc/action_skills_mod(skill, value, threshold, modifier_is_multiplier = TRUE)
var/mod
var/datum/skill/S = GLOB.skill_datums[skill]
if(!S)
return
switch(S.progression_type)
if(SKILL_PROGRESSION_LEVEL)
mod = LAZYACCESS(skill_levels, S.type)
else
mod = LAZYACCESS(skills, S.type)
mod = (1+(mod-S.competency_thresholds[threshold])*S.competency_mults[threshold])
var/list/comsig_values = list(mod)
SEND_SIGNAL(owner.current, COMSIG_MOB_ACTION_SKILL_MOD, args, comsig_values)
mod = comsig_values[MOD_VALUES_SKILL_MOD]
. = modifier_is_multiplier ? value*mod : value/mod
/**
* Generic value modifier proc that uses several skills, intended for items.
* Args:
* * item/I : the item used in this action. its used_skills list variable contains the skills exercised with it.
* * value : the value to modify, may be a delay, damage, probability.
* * flags : the required flags that each skill (either in I.used_skills or the skill datum skill_flags) must have to influence
* * the value.
* * bad_flags : the opposite of the above, skills that must not be present to impact the value.
* * modifier_is_multiplier : wheter the modifier is a multiplier or a divisor.
*/
/datum/skill_holder/proc/item_action_skills_mod(obj/item/I, value, flags = NONE, bad_flags = NONE, modifier_is_multiplier = TRUE)
. = value
var/sum = 0
var/divisor = 0
var/list/checked_skills
for(var/k in I.used_skills)
var/datum/skill/S = GLOB.skill_datums[k]
if(!S)
continue
var/our_flags = (I.used_skills[k]|S.skill_flags)
if((flags && !(our_flags & flags)) || (bad_flags && our_flags & bad_flags))
continue
var/mod
switch(S.progression_type)
if(SKILL_PROGRESSION_LEVEL)
mod = LAZYACCESS(skill_levels, S.type)
else
mod = LAZYACCESS(skills, S.type)
sum += 1+(mod - S.competency_thresholds[I.skill_difficulty])*S.competency_mults[I.skill_difficulty]
LAZYADD(checked_skills, S)
var/list/comsig_values = list(sum, divisor, checked_skills)
SEND_SIGNAL(owner.current, COMSIG_MOB_ITEM_ACTION_SKILLS_MOD, args, comsig_values)
sum = comsig_values[MOD_VALUES_ITEM_SKILLS_SUM]
divisor = comsig_values[MOD_VALUES_ITEM_SKILLS_DIV]
if(divisor)
. = modifier_is_multiplier ? value*(sum/divisor) : value/(sum/divisor)
/**
* Generates a HTML readout of our skills.
* Port to tgui-next when?
*/
/datum/skill_holder/proc/html_readout()
var/list/out = list("<center><h1>Skills</h1></center><hr>")
out += "<table style=\"width:100%\"><tr><th><b>Skill</b><th><b>Value</b></tr>"
for(var/path in skills)
var/datum/skill/S = GLOB.skill_datums[path]
out += "<tr><td><font color='[S.name_color]'>[S.name]</font></td><td>[S.standard_render_value(skills[path], LAZYACCESS(skill_levels, path) || 0)]</td></tr>"
out += "</table>"
return out.Join("")
+6
View File
@@ -0,0 +1,6 @@
/datum/skill/level/job/wiring
name = "Wiring"
desc = "How proficient and knowledged you are at wiring beyond laying cables on the floor."
name_color = COLOR_PALE_ORANGE
competency_thresholds = list(JOB_SKILL_BASIC, JOB_SKILL_EXPERT, JOB_SKILL_MASTER)
skill_flags = SKILL_USE_MOOD|SKILL_TRAIN_MOOD|SKILL_USE_TOOL|SKILL_TRAINING_TOOL
+5
View File
@@ -0,0 +1,5 @@
/datum/skill/numerical/surgery
name = "Surgery"
desc = "How proficient you are at doing surgery."
name_color = COLOR_PALE_BLUE_GRAY
competency_mults = list(0.025, 0.025, 0.025) // 60% surgery speed up at max value of 100.
+27
View File
@@ -550,3 +550,30 @@
else if(isanimal(L))
var/mob/living/simple_animal/SM = L
SM.adjustHealth(-3.5, forced = TRUE)
/obj/screen/alert/status_effect/regenerative_core
name = "Reinforcing Tendrils"
desc = "You can move faster than your broken body could normally handle!"
icon_state = "regenerative_core"
name = "Regenerative Core Tendrils"
/datum/status_effect/regenerative_core
id = "Regenerative Core"
duration = 1 MINUTES
status_type = STATUS_EFFECT_REPLACE
alert_type = /obj/screen/alert/status_effect/regenerative_core
/datum/status_effect/regenerative_core/on_apply()
. = ..()
ADD_TRAIT(owner, TRAIT_IGNOREDAMAGESLOWDOWN, "regenerative_core")
owner.adjustBruteLoss(-25)
if(!AmBloodsucker(owner)) //use your coffin you lazy bastard
owner.adjustFireLoss(-25)
owner.remove_CC()
owner.bodytemperature = BODYTEMP_NORMAL
return TRUE
/datum/status_effect/regenerative_core/on_remove()
. = ..()
REMOVE_TRAIT(owner, TRAIT_IGNOREDAMAGESLOWDOWN, "regenerative_core")
owner.updatehealth()
+32 -79
View File
@@ -120,12 +120,12 @@
/datum/status_effect/mesmerize/on_creation(mob/living/new_owner, set_duration)
. = ..()
ADD_TRAIT(owner, TRAIT_MUTE, "mesmerize")
owner.add_movespeed_modifier("[STATUS_EFFECT_MESMERIZE]_[id]", TRUE, priority = 64, override = TRUE, multiplicative_slowdown = 5, blacklisted_movetypes = FALSE? NONE : CRAWLING)
owner.add_movespeed_modifier(/datum/movespeed_modifier/status_effect/mesmerize)
/datum/status_effect/mesmerize/on_remove()
. = ..()
REMOVE_TRAIT(owner, TRAIT_MUTE, "mesmerize")
owner.remove_movespeed_modifier("[STATUS_EFFECT_MESMERIZE]_[id]")
owner.remove_movespeed_modifier(/datum/movespeed_modifier/status_effect/mesmerize)
/datum/status_effect/mesmerize/on_creation(mob/living/new_owner, set_duration)
if(isnum(set_duration))
@@ -141,9 +141,7 @@
/datum/status_effect/electrode
id = "tased"
alert_type = null
var/slowdown = 1.5
var/slowdown_priority = 50 //to make sure the stronger effect overrides
var/affect_crawl = FALSE
var/movespeed_mod = /datum/movespeed_modifier/status_effect/tased
var/nextmove_modifier = 1
var/stamdmg_per_ds = 0 //a 20 duration would do 20 stamdmg, disablers do 24 or something
var/last_tick = 0 //fastprocess processing speed is a goddamn sham, don't trust it.
@@ -155,12 +153,12 @@
last_tick = world.time
if(iscarbon(owner))
var/mob/living/carbon/C = owner
C.add_movespeed_modifier("[MOVESPEED_ID_TASED_STATUS]_[id]", TRUE, priority = slowdown_priority, override = TRUE, multiplicative_slowdown = slowdown, blacklisted_movetypes = affect_crawl? NONE : CRAWLING)
C.add_movespeed_modifier(movespeed_mod)
/datum/status_effect/electrode/on_remove()
if(iscarbon(owner))
var/mob/living/carbon/C = owner
C.remove_movespeed_modifier("[MOVESPEED_ID_TASED_STATUS]_[id]")
C.remove_movespeed_modifier(movespeed_mod)
. = ..()
/datum/status_effect/electrode/tick()
@@ -178,8 +176,7 @@
/datum/status_effect/electrode/no_combat_mode
id = "tased_strong"
slowdown = 8
slowdown_priority = 100
movespeed_mod = /datum/movespeed_modifier/status_effect/tased/no_combat_mode
nextmove_modifier = 2
blocks_combatmode = TRUE
stamdmg_per_ds = 1
@@ -336,7 +333,7 @@
if(prob(severity * 0.15))
to_chat(owner, "<span class='sevtug[span_part]'>\"[text2ratvar(pick(mania_messages))]\"</span>")
owner.playsound_local(get_turf(motor), hum, severity, 1)
owner.adjust_drugginess(CLAMP(max(severity * 0.075, 1), 0, max(0, 50 - owner.druggy))) //7.5% of severity per second, minimum 1
owner.adjust_drugginess(clamp(max(severity * 0.075, 1), 0, max(0, 50 - owner.druggy))) //7.5% of severity per second, minimum 1
if(owner.hallucination < 50)
owner.hallucination = min(owner.hallucination + max(severity * 0.075, 1), 50) //7.5% of severity per second, minimum 1
if(owner.dizziness < 50)
@@ -394,78 +391,34 @@
owner.underlays -= marked_underlay //if this is being called, we should have an owner at this point.
..()
/datum/status_effect/saw_bleed
/datum/status_effect/stacking/saw_bleed
id = "saw_bleed"
duration = -1 //removed under specific conditions
tick_interval = 6
alert_type = null
var/mutable_appearance/bleed_overlay
var/mutable_appearance/bleed_underlay
var/bleed_amount = 3
var/bleed_buildup = 3
var/delay_before_decay = 5
delay_before_decay = 5
stack_threshold = 10
max_stacks = 10
overlay_file = 'icons/effects/bleed.dmi'
underlay_file = 'icons/effects/bleed.dmi'
overlay_state = "bleed"
underlay_state = "bleed"
var/bleed_damage = 200
var/needs_to_bleed = FALSE
/datum/status_effect/saw_bleed/Destroy()
if(owner)
owner.cut_overlay(bleed_overlay)
owner.underlays -= bleed_underlay
QDEL_NULL(bleed_overlay)
return ..()
/datum/status_effect/stacking/saw_bleed/fadeout_effect()
new /obj/effect/temp_visual/bleed(get_turf(owner))
/datum/status_effect/saw_bleed/on_apply()
if(owner.stat == DEAD)
return FALSE
bleed_overlay = mutable_appearance('icons/effects/bleed.dmi', "bleed[bleed_amount]")
bleed_underlay = mutable_appearance('icons/effects/bleed.dmi', "bleed[bleed_amount]")
var/icon/I = icon(owner.icon, owner.icon_state, owner.dir)
var/icon_height = I.Height()
bleed_overlay.pixel_x = -owner.pixel_x
bleed_overlay.pixel_y = FLOOR(icon_height * 0.25, 1)
bleed_overlay.transform = matrix() * (icon_height/world.icon_size) //scale the bleed overlay's size based on the target's icon size
bleed_underlay.pixel_x = -owner.pixel_x
bleed_underlay.transform = matrix() * (icon_height/world.icon_size) * 3
bleed_underlay.alpha = 40
owner.add_overlay(bleed_overlay)
owner.underlays += bleed_underlay
return ..()
/datum/status_effect/stacking/saw_bleed/threshold_cross_effect()
owner.adjustBruteLoss(bleed_damage)
var/turf/T = get_turf(owner)
new /obj/effect/temp_visual/bleed/explode(T)
for(var/d in GLOB.alldirs)
new /obj/effect/temp_visual/dir_setting/bloodsplatter(T, d)
playsound(T, "desceration", 100, TRUE, -1)
/datum/status_effect/saw_bleed/tick()
if(owner.stat == DEAD)
qdel(src)
else
add_bleed(-1)
/datum/status_effect/saw_bleed/proc/add_bleed(amount)
owner.cut_overlay(bleed_overlay)
owner.underlays -= bleed_underlay
bleed_amount += amount
if(bleed_amount)
if(bleed_amount >= 10)
needs_to_bleed = TRUE
qdel(src)
else
if(amount > 0)
tick_interval += delay_before_decay
bleed_overlay.icon_state = "bleed[bleed_amount]"
bleed_underlay.icon_state = "bleed[bleed_amount]"
owner.add_overlay(bleed_overlay)
owner.underlays += bleed_underlay
else
qdel(src)
/datum/status_effect/saw_bleed/on_remove()
. = ..()
if(needs_to_bleed)
var/turf/T = get_turf(owner)
new /obj/effect/temp_visual/bleed/explode(T)
for(var/d in GLOB.alldirs)
new /obj/effect/temp_visual/dir_setting/bloodsplatter(T, d)
playsound(T, "desceration", 200, 1, -1)
owner.adjustBruteLoss(bleed_damage)
else
new /obj/effect/temp_visual/bleed(get_turf(owner))
/datum/status_effect/stacking/saw_bleed/bloodletting
id = "bloodletting"
stack_threshold = 7
max_stacks = 7
bleed_damage = 20
/datum/status_effect/neck_slice
id = "neck_slice"
@@ -594,7 +547,7 @@
old_health = owner.health
if(!old_oxyloss)
old_oxyloss = owner.getOxyLoss()
var/health_difference = old_health - owner.health - CLAMP(owner.getOxyLoss() - old_oxyloss,0, owner.getOxyLoss())
var/health_difference = old_health - owner.health - clamp(owner.getOxyLoss() - old_oxyloss,0, owner.getOxyLoss())
if(!health_difference)
return
owner.visible_message("<span class='warning'>The light in [owner]'s eyes dims as [owner.p_theyre()] harmed!</span>", \
@@ -650,11 +603,11 @@
if(isnum(set_duration))
duration = set_duration
. = ..()
owner.add_movespeed_modifier(MOVESPEED_ID_ELECTROSTAFF, multiplicative_slowdown = 1, movetypes = GROUND)
owner.add_movespeed_modifier(/datum/movespeed_modifier/status_effect/electrostaff)
/datum/status_effect/electrostaff/on_remove()
. = ..()
owner.remove_movespeed_modifier(MOVESPEED_ID_ELECTROSTAFF)
owner.remove_movespeed_modifier(/datum/movespeed_modifier/status_effect/electrostaff)
//GOLEM GANG
+134 -3
View File
@@ -7,7 +7,6 @@
var/duration = -1 //How long the status effect lasts in DECISECONDS. Enter -1 for an effect that never ends unless removed through some means.
var/tick_interval = 10 //How many deciseconds between ticks, approximately. Leave at 10 for every second.
var/mob/living/owner //The mob affected by the status effect.
var/status_type = STATUS_EFFECT_UNIQUE //How many of the effect can be on one mob, and what happens when you try to add another
var/on_remove_on_mob_delete = FALSE //if we call on_remove() when the mob is deleted
var/examine_text //If defined, this text will appear when the mob is examined - to use he, she etc. use "SUBJECTPRONOUN" and replace it in the examines themselves
var/alert_type = /obj/screen/alert/status_effect //the alert thrown by the status effect, contains name and description
@@ -16,6 +15,8 @@
/// If this is TRUE, the user will have sprint forcefully disabled while this is active.
var/blocks_sprint = FALSE
var/obj/screen/alert/status_effect/linked_alert = null //the alert itself, if it exists
/// How many of the effect can be on one mob, and what happens when you try to add another
var/status_type = STATUS_EFFECT_UNIQUE
/datum/status_effect/New(list/arguments)
on_creation(arglist(arguments))
@@ -67,6 +68,9 @@
/datum/status_effect/proc/tick() //Called every tick.
/datum/status_effect/proc/before_remove() //! Called before being removed; returning FALSE will cancel removal
return TRUE
/datum/status_effect/proc/on_remove() //Called whenever the buff expires or is removed; do note that at the point this is called, it is out of the owner's status_effects but owner is not yet null
SHOULD_CALL_PARENT(TRUE)
REMOVE_TRAIT(owner, TRAIT_COMBAT_MODE_LOCKED, src)
@@ -123,12 +127,13 @@
S1 = new effect(arguments)
. = S1
/mob/living/proc/remove_status_effect(effect) //removes all of a given status effect from this mob, returning TRUE if at least one was removed
/mob/living/proc/remove_status_effect(effect, ...) //removes all of a given status effect from this mob, returning TRUE if at least one was removed
. = FALSE
var/list/arguments = args.Copy(2)
if(status_effects)
var/datum/status_effect/S1 = effect
for(var/datum/status_effect/S in status_effects)
if(initial(S1.id) == S.id)
if(initial(S1.id) == S.id && S.before_remove(arguments))
qdel(S)
. = TRUE
@@ -147,3 +152,129 @@
for(var/datum/status_effect/S in status_effects)
if(initial(S1.id) == S.id)
. += S
//////////////////////
// STACKING EFFECTS //
//////////////////////
/datum/status_effect/stacking
id = "stacking_base"
duration = -1 //removed under specific conditions
alert_type = null
var/stacks = 0 //how many stacks are accumulated, also is # of stacks that target will have when first applied
var/delay_before_decay //deciseconds until ticks start occuring, which removes stacks (first stack will be removed at this time plus tick_interval)
tick_interval = 10 //deciseconds between decays once decay starts
var/stack_decay = 1 //how many stacks are lost per tick (decay trigger)
var/stack_threshold //special effects trigger when stacks reach this amount
var/max_stacks //stacks cannot exceed this amount
var/consumed_on_threshold = TRUE //if status should be removed once threshold is crossed
var/threshold_crossed = FALSE //set to true once the threshold is crossed, false once it falls back below
var/overlay_file
var/underlay_file
var/overlay_state // states in .dmi must be given a name followed by a number which corresponds to a number of stacks. put the state name without the number in these state vars
var/underlay_state // the number is concatonated onto the string based on the number of stacks to get the correct state name
var/mutable_appearance/status_overlay
var/mutable_appearance/status_underlay
/datum/status_effect/stacking/proc/threshold_cross_effect() //what happens when threshold is crossed
/datum/status_effect/stacking/proc/stacks_consumed_effect() //runs if status is deleted due to threshold being crossed
/datum/status_effect/stacking/proc/fadeout_effect() //runs if status is deleted due to being under one stack
/datum/status_effect/stacking/proc/stack_decay_effect() //runs every time tick() causes stacks to decay
/datum/status_effect/stacking/proc/on_threshold_cross()
threshold_cross_effect()
if(consumed_on_threshold)
stacks_consumed_effect()
qdel(src)
/datum/status_effect/stacking/proc/on_threshold_drop()
/datum/status_effect/stacking/proc/can_have_status()
return owner.stat != DEAD
/datum/status_effect/stacking/proc/can_gain_stacks()
return owner.stat != DEAD
/datum/status_effect/stacking/tick()
if(!can_have_status())
qdel(src)
else
add_stacks(-stack_decay)
stack_decay_effect()
/datum/status_effect/stacking/proc/add_stacks(stacks_added)
if(stacks_added > 0 && !can_gain_stacks())
return FALSE
owner.cut_overlay(status_overlay)
owner.underlays -= status_underlay
stacks += stacks_added
if(stacks > 0)
if(stacks >= stack_threshold && !threshold_crossed) //threshold_crossed check prevents threshold effect from occuring if changing from above threshold to still above threshold
threshold_crossed = TRUE
on_threshold_cross()
if(consumed_on_threshold)
return
else if(stacks < stack_threshold && threshold_crossed)
threshold_crossed = FALSE //resets threshold effect if we fall below threshold so threshold effect can trigger again
on_threshold_drop()
if(stacks_added > 0)
tick_interval += delay_before_decay //refreshes time until decay
stacks = min(stacks, max_stacks)
status_overlay.icon_state = "[overlay_state][stacks]"
status_underlay.icon_state = "[underlay_state][stacks]"
owner.add_overlay(status_overlay)
owner.underlays += status_underlay
else
fadeout_effect()
qdel(src) //deletes status if stacks fall under one
/datum/status_effect/stacking/on_creation(mob/living/new_owner, stacks_to_apply)
. = ..()
if(.)
add_stacks(stacks_to_apply)
/datum/status_effect/stacking/on_apply()
if(!can_have_status())
return FALSE
status_overlay = mutable_appearance(overlay_file, "[overlay_state][stacks]")
status_underlay = mutable_appearance(underlay_file, "[underlay_state][stacks]")
var/icon/I = icon(owner.icon, owner.icon_state, owner.dir)
var/icon_height = I.Height()
status_overlay.pixel_x = -owner.pixel_x
status_overlay.pixel_y = FLOOR(icon_height * 0.25, 1)
status_overlay.transform = matrix() * (icon_height/world.icon_size) //scale the status's overlay size based on the target's icon size
status_underlay.pixel_x = -owner.pixel_x
status_underlay.transform = matrix() * (icon_height/world.icon_size) * 3
status_underlay.alpha = 40
owner.add_overlay(status_overlay)
owner.underlays += status_underlay
return ..()
/datum/status_effect/stacking/Destroy()
if(owner)
owner.cut_overlay(status_overlay)
owner.underlays -= status_underlay
QDEL_NULL(status_overlay)
return ..()
/// Status effect from multiple sources, when all sources are removed, so is the effect
/datum/status_effect/grouped
status_type = STATUS_EFFECT_MULTIPLE //! Adds itself to sources and destroys itself if one exists already, there are never multiple
var/list/sources = list()
/datum/status_effect/grouped/on_creation(mob/living/new_owner, source)
var/datum/status_effect/grouped/existing = new_owner.has_status_effect(type)
if(existing)
existing.sources |= source
qdel(src)
return FALSE
else
sources |= source
return ..()
/datum/status_effect/grouped/before_remove(source)
sources -= source
return !length(sources)
+108 -26
View File
@@ -1,49 +1,96 @@
//The effects of weather occur across an entire z-level. For instance, lavaland has periodic ash storms that scorch most unprotected creatures.
/**
* Causes weather to occur on a z level in certain area types
*
* The effects of weather occur across an entire z-level. For instance, lavaland has periodic ash storms that scorch most unprotected creatures.
* Weather always occurs on different z levels at different times, regardless of weather type.
* Can have custom durations, targets, and can automatically protect indoor areas.
*
*/
/datum/weather
/// name of weather
var/name = "space wind"
/// description of weather
var/desc = "Heavy gusts of wind blanket the area, periodically knocking down anyone caught in the open."
var/telegraph_message = "<span class='warning'>The wind begins to pick up.</span>" //The message displayed in chat to foreshadow the weather's beginning
var/telegraph_duration = 300 //In deciseconds, how long from the beginning of the telegraph until the weather begins
var/telegraph_sound //The sound file played to everyone on an affected z-level
var/telegraph_overlay //The overlay applied to all tiles on the z-level
/// The message displayed in chat to foreshadow the weather's beginning
var/telegraph_message = "<span class='warning'>The wind begins to pick up.</span>"
var/weather_message = "<span class='userdanger'>The wind begins to blow ferociously!</span>" //Displayed in chat once the weather begins in earnest
var/weather_duration = 1200 //In deciseconds, how long the weather lasts once it begins
var/weather_duration_lower = 1200 //See above - this is the lowest possible duration
var/weather_duration_upper = 1500 //See above - this is the highest possible duration
/// In deciseconds, how long from the beginning of the telegraph until the weather begins
var/telegraph_duration = 300
/// The sound file played to everyone on an affected z-level
var/telegraph_sound
/// The overlay applied to all tiles on the z-level
var/telegraph_overlay
/// Displayed in chat once the weather begins in earnest
var/weather_message = "<span class='userdanger'>The wind begins to blow ferociously!</span>"
///In deciseconds, how long the weather lasts once it begins
var/weather_duration = 1200
///See above - this is the lowest possible duration
var/weather_duration_lower = 1200
///See above - this is the highest possible duration
var/weather_duration_upper = 1500
/// Looping sound while weather is occuring
var/weather_sound
/// Area overlay while the weather is occuring
var/weather_overlay
/// Color to apply to the area while weather is occuring
var/weather_color = null
var/end_message = "<span class='danger'>The wind relents its assault.</span>" //Displayed once the weather is over
var/end_duration = 300 //In deciseconds, how long the "wind-down" graphic will appear before vanishing entirely
/// Displayed once the weather is over
var/end_message = "<span class='danger'>The wind relents its assault.</span>"
/// In deciseconds, how long the "wind-down" graphic will appear before vanishing entirely
var/end_duration = 300
/// Sound that plays while weather is ending
var/end_sound
/// Area overlay while weather is ending
var/end_overlay
var/area_type = /area/space //Types of area to affect
var/list/impacted_areas = list() //Areas to be affected by the weather, calculated when the weather begins
var/list/protected_areas = list()//Areas that are protected and excluded from the affected areas.
var/impacted_z_levels // The list of z-levels that this weather is actively affecting
/// Types of area to affect
var/area_type = /area/space
/// TRUE value protects areas with outdoors marked as false, regardless of area type
var/protect_indoors = FALSE
/// Areas to be affected by the weather, calculated when the weather begins
var/list/impacted_areas = list()
var/overlay_layer = AREA_LAYER //Since it's above everything else, this is the layer used by default. TURF_LAYER is below mobs and walls if you need to use that.
var/aesthetic = FALSE //If the weather has no purpose other than looks
var/immunity_type = "storm" //Used by mobs to prevent them from being affected by the weather
/// Areas that are protected and excluded from the affected areas.
var/list/protected_areas = list()
/// The list of z-levels that this weather is actively affecting
var/impacted_z_levels
var/stage = END_STAGE //The stage of the weather, from 1-4
/// Since it's above everything else, this is the layer used by default. TURF_LAYER is below mobs and walls if you need to use that.
var/overlay_layer = AREA_LAYER
/// Plane for the overlay
var/overlay_plane = BLACKNESS_PLANE
/// If the weather has no purpose but aesthetics.
var/aesthetic = FALSE
/// Used by mobs to prevent them from being affected by the weather
var/immunity_type = "storm"
// These are read by the weather subsystem and used to determine when and where to run the weather.
var/probability = 0 // Weight amongst other eligible weather. If zero, will never happen randomly.
var/target_trait = ZTRAIT_STATION // The z-level trait to affect when run randomly or when not overridden.
/// The stage of the weather, from 1-4
var/stage = END_STAGE
/// Weight amongst other eligible weather. if zero, will never happen randomly.
var/probability = 0
/// The z-level trait to affect when run randomly or when not overridden.
var/target_trait = ZTRAIT_STATION
/// Whether a barometer can predict when the weather will happen
var/barometer_predictable = FALSE
var/next_hit_time = 0 //For barometers to know when the next storm will hit
/// For barometers to know when the next storm will hit
var/next_hit_time = 0
/datum/weather/New(z_levels)
..()
impacted_z_levels = z_levels
/**
* Telegraphs the beginning of the weather on the impacted z levels
*
* Sends sounds and details to mobs in the area
* Calculates duration and hit areas, and makes a callback for the actual weather to start
*
*/
/datum/weather/proc/telegraph()
if(stage == STARTUP_STAGE)
return
@@ -58,6 +105,8 @@
affectareas -= get_areas(V)
for(var/V in affectareas)
var/area/A = V
if(protect_indoors && !A.outdoors)
continue
if(A.z in impacted_z_levels)
impacted_areas |= A
weather_duration = rand(weather_duration_lower, weather_duration_upper)
@@ -72,6 +121,13 @@
SEND_SOUND(M, sound(telegraph_sound))
addtimer(CALLBACK(src, .proc/start), telegraph_duration)
/**
* Starts the actual weather and effects from it
*
* Updates area overlays and sends sounds and messages to mobs to notify them
* Begins dealing effects from weather to mobs in the area
*
*/
/datum/weather/proc/start()
if(stage >= MAIN_STAGE)
return
@@ -86,6 +142,13 @@
SEND_SOUND(M, sound(weather_sound))
addtimer(CALLBACK(src, .proc/wind_down), weather_duration)
/**
* Weather enters the winding down phase, stops effects
*
* Updates areas to be in the winding down phase
* Sends sounds and messages to mobs to notify them
*
*/
/datum/weather/proc/wind_down()
if(stage >= WIND_DOWN_STAGE)
return
@@ -100,6 +163,13 @@
SEND_SOUND(M, sound(end_sound))
addtimer(CALLBACK(src, .proc/end), end_duration)
/**
* Fully ends the weather
*
* Effects no longer occur and area overlays are removed
* Removes weather from processing completely
*
*/
/datum/weather/proc/end()
if(stage == END_STAGE)
return 1
@@ -115,7 +185,11 @@
if(can_weather_act(L))
weather_act(L)
/datum/weather/proc/can_weather_act(mob/living/L) //Can this weather impact a mob?
/**
* Returns TRUE if the living mob can be affected by the weather
*
*/
/datum/weather/proc/can_weather_act(mob/living/L)
var/turf/mob_turf = get_turf(L)
if(mob_turf && !(mob_turf.z in impacted_z_levels))
return
@@ -123,11 +197,19 @@
return
if(!(get_area(L) in impacted_areas))
return
return 1
return TRUE
/datum/weather/proc/weather_act(mob/living/L) //What effect does this weather have on the hapless mob?
/**
* Affects the mob with whatever the weather does
*
*/
/datum/weather/proc/weather_act(mob/living/L)
return
/**
* Updates the overlays on impacted areas
*
*/
/datum/weather/proc/update_areas()
for(var/V in impacted_areas)
var/area/N = V
@@ -17,8 +17,9 @@
end_message = "<span class='boldannounce'>The downpour gradually slows to a light shower. It should be safe outside now.</span>"
end_sound = 'sound/ambience/acidrain_end.ogg'
area_type = /area/lavaland/surface/outdoors
target_trait = ZTRAIT_MINING
area_type = /area
protect_indoors = TRUE
target_trait = ZTRAIT_ACIDRAIN
immunity_type = "acid" // temp
@@ -16,8 +16,9 @@
end_duration = 300
end_overlay = "light_ash"
area_type = /area/lavaland/surface/outdoors
target_trait = ZTRAIT_MINING
area_type = /area
protect_indoors = TRUE
target_trait = ZTRAIT_ASHSTORM
immunity_type = "ash"
@@ -15,8 +15,9 @@
end_duration = 100
end_message = "<span class='boldannounce'>The snowfall dies down, it should be safe to go outside again.</span>"
area_type = /area/awaymission/snowdin/outside
target_trait = ZTRAIT_AWAY
area_type = /area
protect_indoors = TRUE
target_trait = ZTRAIT_SNOWSTORM
immunity_type = "snow"
+35 -6
View File
@@ -33,6 +33,9 @@
var/list/assemblies = list() // List of attached assemblies.
var/randomize = 0 // If every instance of these wires should be random.
// Prevents wires from showing up in station blueprints
var/req_knowledge = INFINITY //wiring skill level on which the functions are revealed.
var/req_skill = JOB_SKILL_BASIC //used in user's cutting/pulsing/mending speed calculations.
var/list/current_users //list of untrained people currently interacting with this set of wires.
/datum/wires/New(atom/holder)
..()
@@ -130,8 +133,22 @@
cut_wires += wire
on_cut(wire, mend = FALSE)
/datum/wires/proc/cut_color(color)
/datum/wires/proc/cut_color(color, mob/living/user)
LAZYINITLIST(current_users)
if(current_users[user])
return FALSE
if(req_skill && user?.mind)
var/level_diff = req_skill - user.mind.skill_holder.get_skill_level(/datum/skill/level/job/wiring)
if(level_diff > 0)
LAZYSET(current_users, user, TRUE)
to_chat(user, "<span class='notice'>You begin cutting [holder]'s [color] wire...</span>")
if(!do_after(user, 1.5 SECONDS * level_diff, target = holder) || !interactable(user))
LAZYREMOVE(current_users, user)
return FALSE
LAZYREMOVE(current_users, user)
to_chat(user, "<span class='notice'>You cut [holder]'s [color] wire.</span>")
cut(get_wire(color))
return TRUE
/datum/wires/proc/cut_random()
cut(wires[rand(1, wires.len)])
@@ -146,7 +163,21 @@
on_pulse(wire, user)
/datum/wires/proc/pulse_color(color, mob/living/user)
LAZYINITLIST(current_users)
if(current_users[user])
return FALSE
if(req_skill && user?.mind)
var/level_diff = req_skill - user.mind.skill_holder.get_skill_level(/datum/skill/level/job/wiring)
if(level_diff > 0)
LAZYSET(current_users, user, TRUE)
to_chat(user, "<span class='notice'>You begin pulsing [holder]'s [color] wire...</span>")
if(!do_after(user, 1.5 SECONDS * level_diff, target = holder) || !interactable(user))
LAZYREMOVE(current_users, user)
return FALSE
LAZYREMOVE(current_users, user)
to_chat(user, "<span class='notice'>You pulse [holder]'s [color] wire.</span>")
pulse(get_wire(color), user)
return TRUE
/datum/wires/proc/pulse_assembly(obj/item/assembly/S)
for(var/color in assemblies)
@@ -224,7 +255,7 @@
var/reveal_wires = FALSE
// Admin ghost can see a purpose of each wire.
if(IsAdminGhost(user))
if(IsAdminGhost(user) || user.mind.skill_holder.get_skill_level(/datum/skill/level/job/wiring) >= req_knowledge)
reveal_wires = TRUE
// Same for anyone with an abductor multitool.
@@ -259,18 +290,16 @@
if("cut")
I = L.is_holding_tool_quality(TOOL_WIRECUTTER)
if(I || IsAdminGhost(usr))
if(I && holder)
if(cut_color(target_wire, L) && I && holder)
I.play_tool_sound(holder, 20)
cut_color(target_wire)
. = TRUE
else
to_chat(L, "<span class='warning'>You need wirecutters!</span>")
if("pulse")
I = L.is_holding_tool_quality(TOOL_MULTITOOL)
if(I || IsAdminGhost(usr))
if(I && holder)
if(pulse_color(target_wire, L) && I && holder)
I.play_tool_sound(holder, 20)
pulse_color(target_wire, L)
. = TRUE
else
to_chat(L, "<span class='warning'>You need a multitool!</span>")
+1
View File
@@ -1,6 +1,7 @@
/datum/wires/airalarm
holder_type = /obj/machinery/airalarm
proper_name = "Air Alarm"
req_knowledge = JOB_SKILL_MASTER
/datum/wires/airalarm/New(atom/holder)
wires = list(
+4 -2
View File
@@ -1,6 +1,7 @@
/datum/wires/airlock
holder_type = /obj/machinery/door/airlock
proper_name = "Generic Airlock"
req_skill = JOB_SKILL_UNTRAINED //Training wheel, as per request.
var/wiretype
/datum/wires/airlock/secure
@@ -52,10 +53,11 @@
/datum/wires/airlock/interactable(mob/user)
var/obj/machinery/door/airlock/A = holder
if(!A.panel_open)
return FALSE
if(!A.hasSiliconAccessInArea(user) && A.isElectrified() && A.shock(user, 100))
return FALSE
if(A.panel_open)
return TRUE
return TRUE
/datum/wires/airlock/get_status()
var/obj/machinery/door/airlock/A = holder
+1
View File
@@ -1,6 +1,7 @@
/datum/wires/apc
holder_type = /obj/machinery/power/apc
proper_name = "APC"
req_knowledge = JOB_SKILL_MASTER
/datum/wires/apc/New(atom/holder)
wires = list(
+1
View File
@@ -1,6 +1,7 @@
/datum/wires/autolathe
holder_type = /obj/machinery/autolathe
proper_name = "Autolathe"
req_knowledge = JOB_SKILL_EXPERT
/datum/wires/autolathe/New(atom/holder)
wires = list(
-48
View File
@@ -1,48 +0,0 @@
/datum/wires/autoylathe
holder_type = /obj/machinery/autoylathe
proper_name = "Autoylathe"
/datum/wires/autoylathe/New(atom/holder)
wires = list(
WIRE_HACK, WIRE_DISABLE,
WIRE_SHOCK, WIRE_ZAP
)
add_duds(6)
..()
/datum/wires/autoylathe/interactable(mob/user)
var/obj/machinery/autoylathe/A = holder
if(A.panel_open)
return TRUE
/datum/wires/autoylathe/get_status()
var/obj/machinery/autoylathe/A = holder
var/list/status = list()
status += "The red light is [A.disabled ? "on" : "off"]."
status += "The blue light is [A.hacked ? "on" : "off"]."
return status
/datum/wires/autoylathe/on_pulse(wire)
var/obj/machinery/autoylathe/A = holder
switch(wire)
if(WIRE_HACK)
A.adjust_hacked(!A.hacked)
addtimer(CALLBACK(A, /obj/machinery/autoylathe.proc/reset, wire), 60)
if(WIRE_SHOCK)
A.shocked = !A.shocked
addtimer(CALLBACK(A, /obj/machinery/autoylathe.proc/reset, wire), 60)
if(WIRE_DISABLE)
A.disabled = !A.disabled
addtimer(CALLBACK(A, /obj/machinery/autoylathe.proc/reset, wire), 60)
/datum/wires/autoylathe/on_cut(wire, mend)
var/obj/machinery/autoylathe/A = holder
switch(wire)
if(WIRE_HACK)
A.adjust_hacked(!mend)
if(WIRE_HACK)
A.shocked = !mend
if(WIRE_DISABLE)
A.disabled = !mend
if(WIRE_ZAP)
A.shock(usr, 50)
+2
View File
@@ -1,6 +1,8 @@
/datum/wires/emitter
holder_type = /obj/machinery/power/emitter
req_knowledge = JOB_SKILL_TRAINED
req_skill = JOB_SKILL_UNTRAINED
/datum/wires/emitter/New(atom/holder)
wires = list(WIRE_ZAP,WIRE_HACK)
+2
View File
@@ -15,6 +15,7 @@
/datum/wires/explosive/c4
holder_type = /obj/item/grenade/plastic/c4
randomize = TRUE //Same behaviour since no wire actually disarms it
req_skill = JOB_SKILL_UNTRAINED
/datum/wires/explosive/c4/interactable(mob/user)
var/obj/item/grenade/plastic/c4/P = holder
@@ -29,6 +30,7 @@
/datum/wires/explosive/pizza
holder_type = /obj/item/pizzabox
randomize = TRUE
req_skill = JOB_SKILL_MASTER
/datum/wires/explosive/pizza/New(atom/holder)
wires = list(
+2
View File
@@ -1,6 +1,8 @@
/datum/wires/microwave
holder_type = /obj/machinery/microwave
proper_name = "Microwave"
req_knowledge = JOB_SKILL_TRAINED
req_skill = JOB_SKILL_UNTRAINED
/datum/wires/microwave/New(atom/holder)
wires = list(
+1
View File
@@ -1,6 +1,7 @@
/datum/wires/mulebot
holder_type = /mob/living/simple_animal/bot/mulebot
randomize = TRUE
req_knowledge = JOB_SKILL_MASTER
/datum/wires/mulebot/New(atom/holder)
wires = list(
@@ -1,6 +1,8 @@
/datum/wires/particle_accelerator/control_box
holder_type = /obj/machinery/particle_accelerator/control_box
proper_name = "Particle Accelerator"
req_knowledge = JOB_SKILL_EXPERT
req_skill = JOB_SKILL_TRAINED
/datum/wires/particle_accelerator/control_box/New(atom/holder)
wires = list(
+1
View File
@@ -1,6 +1,7 @@
/datum/wires/rnd
holder_type = /obj/machinery/rnd
randomize = TRUE
req_knowledge = JOB_SKILL_EXPERT
/datum/wires/rnd/New(atom/holder)
wires = list(
+2
View File
@@ -1,6 +1,8 @@
/datum/wires/radio
holder_type = /obj/item/radio
proper_name = "Radio"
req_knowledge = JOB_SKILL_TRAINED
req_skill = JOB_SKILL_UNTRAINED
/datum/wires/radio/New(atom/holder)
wires = list(
+2
View File
@@ -1,6 +1,8 @@
/datum/wires/robot
holder_type = /mob/living/silicon/robot
randomize = TRUE
req_knowledge = JOB_SKILL_MASTER
req_skill = JOB_SKILL_TRAINED
/datum/wires/robot/New(atom/holder)
wires = list(
+2
View File
@@ -1,6 +1,8 @@
/datum/wires/suit_storage_unit
holder_type = /obj/machinery/suit_storage_unit
proper_name = "Suit Storage Unit"
req_knowledge = JOB_SKILL_TRAINED
req_skill = JOB_SKILL_UNTRAINED
/datum/wires/suit_storage_unit/New(atom/holder)
wires = list(
+2
View File
@@ -1,6 +1,8 @@
/datum/wires/syndicatebomb
holder_type = /obj/machinery/syndicatebomb
randomize = TRUE
req_skill = JOB_SKILL_EXPERT //good luck, wannabe hero.
/datum/wires/syndicatebomb/New(atom/holder)
wires = list(
+1
View File
@@ -2,6 +2,7 @@
/datum/wires/tesla_coil
randomize = 1 //Only one wire don't need blueprints
holder_type = /obj/machinery/power/tesla_coil
req_knowledge = JOB_SKILL_TRAINED
/datum/wires/tesla_coil/New(atom/holder)
wires = list(WIRE_ZAP)
+1
View File
@@ -1,6 +1,7 @@
/datum/wires/vending
holder_type = /obj/machinery/vending
proper_name = "Vending Unit"
req_knowledge = JOB_SKILL_EXPERT
/datum/wires/vending/New(atom/holder)
wires = list(
+1 -1
View File
@@ -169,7 +169,7 @@
.["real_mode"] = SSticker.mode.name
// Key-authed callers may know the truth behind the "secret"
.["security_level"] = get_security_level()
.["security_level"] = NUM2SECLEVEL(GLOB.security_level)
.["round_duration"] = SSticker ? round((world.time-SSticker.round_start_time)/10) : 0
// Amount of world's ticks in seconds, useful for calculating round duration