Merge remote-tracking branch 'upstream/master' into snaxi2

This commit is contained in:
Detective Google
2020-05-22 13:52:56 -05:00
551 changed files with 9610 additions and 5762 deletions
+1 -1
View File
@@ -8,11 +8,11 @@
icon_icon = 'icons/mob/actions/actions_spells.dmi'
/datum/action/item_action/ninjaboost
check_flags = NONE
name = "Adrenaline Boost"
desc = "Inject a secret chemical that will counteract all movement-impairing effect."
button_icon_state = "repulse"
icon_icon = 'icons/mob/actions/actions_spells.dmi'
required_mobility_flags = NONE
/datum/action/item_action/ninjapulse
name = "EM Burst (25E)"
+1 -1
View File
@@ -44,7 +44,7 @@
return
if(world.time > next_check && world.time > next_scare)
next_check = world.time + 50
var/list/seen_atoms = view(7, owner)
var/list/seen_atoms = owner.fov_view(7)
if(LAZYLEN(trigger_objs))
for(var/obj/O in seen_atoms)
+11 -5
View File
@@ -35,7 +35,7 @@
* * 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)
/datum/chatmessage/New(text, atom/target, mob/owner, list/extra_classes = list(), lifespan = CHAT_MESSAGE_LIFESPAN)
. = ..()
if (!istype(target))
CRASH("Invalid target given for chatmessage")
@@ -55,6 +55,12 @@
message = null
return ..()
/**
* Calls qdel on the chatmessage when its parent is deleted, used to register qdel signal
*/
/datum/chatmessage/proc/on_parent_qdel()
qdel(src)
/**
* Generates a chat message image representation
*
@@ -68,7 +74,7 @@
/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)
RegisterSignal(owned_by, COMSIG_PARENT_QDELETING, .proc/on_parent_qdel)
// Clip message
var/maxlen = owned_by.prefs.max_chat_length
@@ -108,7 +114,7 @@
// 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/complete_text = "<span class='center maptext [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)
@@ -129,7 +135,7 @@
// Build message image
message = image(loc = message_loc, layer = CHAT_LAYER)
message.plane = GAME_PLANE
message.plane = CHAT_PLANE
message.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA | KEEP_APART
message.alpha = 0
message.pixel_y = owner.bound_height * 0.95
@@ -166,7 +172,7 @@
*/
/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()
spans = spans ? spans.Copy() : list()
// Check for virtual speakers (aka hearing a message through a radio)
var/atom/movable/originalSpeaker = speaker
+56
View File
@@ -0,0 +1,56 @@
#define BAD_ART 12.5
#define GOOD_ART 25
#define GREAT_ART 50
/datum/component/art
var/impressiveness = 0
/datum/component/art/Initialize(impress)
impressiveness = impress
if(isobj(parent))
RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/on_obj_examine)
else
RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/on_other_examine)
if(isstructure(parent))
RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, .proc/on_attack_hand)
if(isitem(parent))
RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, .proc/apply_moodlet)
/datum/component/art/proc/apply_moodlet(mob/M, impress)
M.visible_message("<span class='notice'>[M] stops and looks intently at [parent].</span>", \
"<span class='notice'>You stop to take in [parent].</span>")
switch(impress)
if (0 to BAD_ART)
SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "artbad", /datum/mood_event/artbad)
if (BAD_ART to GOOD_ART)
SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "artok", /datum/mood_event/artok)
if (GOOD_ART to GREAT_ART)
SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "artgood", /datum/mood_event/artgood)
if(GREAT_ART to INFINITY)
SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "artgreat", /datum/mood_event/artgreat)
/datum/component/art/proc/on_other_examine(datum/source, mob/M)
apply_moodlet(M, impressiveness)
/datum/component/art/proc/on_obj_examine(datum/source, mob/M)
var/obj/O = parent
apply_moodlet(M, impressiveness *(O.obj_integrity/O.max_integrity))
/datum/component/art/proc/on_attack_hand(datum/source, mob/M)
to_chat(M, "<span class='notice'>You start examining [parent]...</span>")
if(!do_after(M, 20, target = parent))
return
on_obj_examine(source, M)
/datum/component/art/rev
/datum/component/art/rev/apply_moodlet(mob/M, impress)
M.visible_message("<span class='notice'>[M] stops to inspect [parent].</span>", \
"<span class='notice'>You take in [parent], inspecting the fine craftsmanship of the proletariat.</span>")
if(M.mind && M.mind.has_antag_datum(/datum/antagonist/rev))
SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "artgreat", /datum/mood_event/artgreat)
else
SEND_SIGNAL(M, COMSIG_ADD_MOOD_EVENT, "artbad", /datum/mood_event/artbad)
@@ -74,7 +74,7 @@
//////////////////////Lens//////////////////////////
//Six Steps //
//Sells for 1800 cr, takes 15 glass shets //
//Sells for 1600 cr, takes 15 glass shets //
//Usefull for selling and later crafting //
////////////////////////////////////////////////////
@@ -146,14 +146,14 @@
/obj/item/glasswork/glass_base/glass_lens_part5
name = "Unpolished glass lens"
desc = "A small unpolished glass lens. Could be polished with some silk."
desc = "A small unpolished glass lens. Could be polished with some cloth."
icon = 'icons/obj/glass_ware.dmi'
icon_state = "glass_optics"
next_step = /obj/item/glasswork/glass_base/glass_lens_part6
/obj/item/glasswork/glass_base/glass_lens_part5/attackby(obj/item/I, mob/user, params)
..()
if(istype(I, /obj/item/stack/sheet/silk))
if(istype(I, /obj/item/stack/sheet/cloth))
if(do_after(user,10, target = src))
new next_step(user.loc, 1)
qdel(src)
@@ -324,7 +324,7 @@
//////////////////////Tea Plates////////////////////
//Three Steps //
//Sells for 1200 cr, takes 5 glass shets //
//Sells for 1000 cr, takes 5 glass shets //
//Usefull for selling and chemical things //
////////////////////////////////////////////////////
@@ -370,20 +370,20 @@
/obj/item/glasswork/glass_base/tea_plate3
name = "Disk of glass"
desc = "A disk of glass that can be cant be used for much. Needs to be polished with some silk."
desc = "A disk of glass that can be cant be used for much. Needs to be polished with some cloth."
icon_state = "glass_base_half"
next_step = /obj/item/tea_plate
/obj/item/glasswork/glass_base/tea_plate3/attackby(obj/item/I, mob/user, params)
..()
if(istype(I, /obj/item/stack/sheet/silk))
if(istype(I, /obj/item/stack/sheet/cloth))
if(do_after(user,10, target = src))
new next_step(user.loc, 1)
qdel(src)
//////////////////////Tea Cup///////////////////////
//Four Steps //
//Sells for 1800 cr, takes 6 glass shets //
//Sells for 1600 cr, takes 6 glass shets //
//Usefull for selling and chemical things //
////////////////////////////////////////////////////
@@ -429,13 +429,13 @@
/obj/item/glasswork/glass_base/tea_cup3
name = "Disk of glass"
desc = "A bowl of glass that can be cant be used for much. Needs to be polished with some silk."
desc = "A bowl of glass that can be cant be used for much. Needs to be polished with some cloth."
icon_state = "glass_base_half"
next_step = /obj/item/glasswork/glass_base/tea_cup4
/obj/item/glasswork/glass_base/cup3/attackby(obj/item/I, mob/user, params)
..()
if(istype(I, /obj/item/stack/sheet/silk))
if(istype(I, /obj/item/stack/sheet/cloth))
if(do_after(user,10, target = src))
new next_step(user.loc, 1)
qdel(src)
@@ -1,21 +1,20 @@
//Gun crafting parts til they can be moved elsewhere
// PARTS //
/obj/item/weaponcrafting
icon = 'icons/obj/improvised.dmi'
/obj/item/weaponcrafting/receiver
name = "modular receiver"
desc = "A prototype modular receiver and trigger assembly for a firearm."
icon = 'icons/obj/improvised.dmi'
icon_state = "receiver"
/obj/item/weaponcrafting/stock
name = "rifle stock"
desc = "A classic rifle stock that doubles as a grip, roughly carved out of wood."
icon = 'icons/obj/improvised.dmi'
icon_state = "riflestock"
/obj/item/weaponcrafting/silkstring
name = "silkstring"
desc = "A long piece of silk with some resemblance to cable coil."
icon = 'icons/obj/improvised.dmi'
icon_state = "silkstring"
/obj/item/weaponcrafting/durathread_string
name = "durathread string"
desc = "A long piece of durathread with some resemblance to cable coil."
icon_state = "durastring"
@@ -39,14 +39,14 @@
time = 10
reqs = list(/obj/item/paper = 20)
category = CAT_CLOTHING
/datum/crafting_recipe/armwraps
name = "Armwraps"
result = /obj/item/clothing/gloves/fingerless/pugilist
time = 60
tools = list(TOOL_WIRECUTTER)
reqs = list(/obj/item/stack/sheet/cloth = 4,
/obj/item/stack/sheet/silk = 2,
/obj/item/stack/sheet/durathread = 2,
/obj/item/stack/sheet/leather = 2)
category = CAT_CLOTHING
@@ -157,7 +157,7 @@
/datum/crafting_recipe/goldenbox
name = "Gold Plated Toolbox"
result = /obj/item/storage/toolbox/gold_fake
tools = list(/obj/item/stock_parts/cell/upgraded/plus)
tools = list(/obj/item/stock_parts/cell/high)
reqs = list(/obj/item/stack/sheet/cardboard = 1, //so we dont null items in crafting
/obj/item/stack/cable_coil = 10,
/obj/item/stack/sheet/mineral/gold = 1,
@@ -184,7 +184,7 @@
/datum/crafting_recipe/bronze_driver
name = "Bronze Plated Screwdriver"
tools = list(/obj/item/stock_parts/cell/upgraded/plus)
tools = list(/obj/item/stock_parts/cell/high)
result = /obj/item/screwdriver/bronze
reqs = list(/obj/item/screwdriver = 1,
/obj/item/stack/cable_coil = 10,
@@ -196,7 +196,7 @@
/datum/crafting_recipe/bronze_welder
name = "Bronze Plated Welding Tool"
tools = list(/obj/item/stock_parts/cell/upgraded/plus)
tools = list(/obj/item/stock_parts/cell/high)
result = /obj/item/weldingtool/bronze
reqs = list(/obj/item/weldingtool = 1,
/obj/item/stack/cable_coil = 10,
@@ -208,7 +208,7 @@
/datum/crafting_recipe/bronze_wirecutters
name = "Bronze Plated Wirecutters"
tools = list(/obj/item/stock_parts/cell/upgraded/plus)
tools = list(/obj/item/stock_parts/cell/high)
result = /obj/item/wirecutters/bronze
reqs = list(/obj/item/wirecutters = 1,
/obj/item/stack/cable_coil = 10,
@@ -220,7 +220,7 @@
/datum/crafting_recipe/bronze_crowbar
name = "Bronze Plated Crowbar"
tools = list(/obj/item/stock_parts/cell/upgraded/plus)
tools = list(/obj/item/stock_parts/cell/high)
result = /obj/item/crowbar/bronze
reqs = list(/obj/item/crowbar = 1,
/obj/item/stack/cable_coil = 10,
@@ -232,7 +232,7 @@
/datum/crafting_recipe/bronze_wrench
name = "Bronze Plated Wrench"
tools = list(/obj/item/stock_parts/cell/upgraded/plus)
tools = list(/obj/item/stock_parts/cell/high)
result = /obj/item/wrench/bronze
reqs = list(/obj/item/wrench = 1,
/obj/item/stack/cable_coil = 10,
@@ -192,7 +192,7 @@
result = /obj/item/gun/ballistic/bow/pipe
reqs = list(/obj/item/pipe = 5,
/obj/item/stack/sheet/plastic = 15,
/obj/item/weaponcrafting/silkstring = 5)
/obj/item/weaponcrafting/durathread_string = 5)
time = 450
category = CAT_WEAPONRY
subcategory = CAT_WEAPON
@@ -281,7 +281,7 @@
result = /obj/item/ammo_casing/caseless/arrow/wood
time = 30
reqs = list(/obj/item/stack/sheet/mineral/wood = 1,
/obj/item/stack/sheet/silk = 1,
/obj/item/stack/sheet/durathread = 1,
/obj/item/stack/rods = 1) // 1 metal sheet = 2 rods = 2 arrows
category = CAT_WEAPONRY
subcategory = CAT_AMMO
+354
View File
@@ -0,0 +1,354 @@
#define CENTERED_RENDER_SOURCE(img, atom, FoV) \
atom.render_target = atom.render_target || ref(atom);\
img.render_source = atom.render_target;\
if(atom.icon){\
var/_cached_sizes = FoV.width_n_height_offsets[atom.icon];\
if(!_cached_sizes){\
var/icon/_I = icon(atom.icon);\
var/list/L = list();\
L += (_I.Width() - world.icon_size)/2;\
L += (_I.Height() - world.icon_size)/2;\
_cached_sizes = FoV.width_n_height_offsets[atom.icon] = L\
}\
img.pixel_x = _cached_sizes[1];\
img.pixel_y = _cached_sizes[2];\
img.loc = atom\
}
#define REGISTER_NESTED_LOCS(source, list, comsig, proc) \
for(var/k in get_nested_locs(source)){\
var/atom/_A = k;\
RegisterSignal(_A, comsig, proc);\
list += _A\
}
#define UNREGISTER_NESTED_LOCS(list, comsig, index) \
for(var/k in index to length(list)){\
var/atom/_A = list[k];\
UnregisterSignal(_A, comsig);\
list -= _A\
}
/**
* Field of Vision component. Does totally what you probably think it does,
* ergo preventing players from seeing what's behind them.
*/
/datum/component/field_of_vision
can_transfer = TRUE
/**
* That special invisible, almost neigh indestructible movable
* that holds both shadow cone mask and image and follows the player around.
*/
var/atom/movable/fov_holder/fov
///The current screen size this field of vision is meant to fit for.
var/current_fov_size = list(15, 15)
///How much is the cone rotated clockwise, purely backend. Please use rotate_shadow_cone() if you must.
var/angle = 0
/// Used to scale the shadow cone when rotating it to fit over the edges of the screen.
var/rot_scale = 1
/// The inner angle of this cone, right hardset to 90, 180, or 270 degrees, until someone figures out a way to make it dynamic.
var/shadow_angle = FOV_90_DEGREES
/// The mask portion of the cone, placed on a * render target plane so while not visible it still applies the filter.
var/image/shadow_mask
/// The visual portion of the cone, placed on the highest layer of the wall plane
var/image/visual_shadow
/**
* An image whose render_source is kept up to date to prevent the mob (or the topmost movable holding it) from being hidden by the mask.
* Will make it use vis_contents instead once a few byonds bugs with images and vis contents are fixed.
*/
var/image/owner_mask
/**
* A circle image used to somewhat uncover the adjacent portion of the shadow cone, making mobs and objects behind us somewhat visible.
* The owner mask is still required for those mob going over the default 32x32 px size btw.
*/
var/image/adj_mask
/// A list of nested locations the mob is in, to ensure the above image works correctly.
var/list/nested_locs = list()
/**
* A static list of offsets based on icon width and height, because render sources are centered unlike most other visuals,
* and that gives us some problems when the icon is larger or smaller than world.icon_size
*/
var/static/list/width_n_height_offsets = list()
/datum/component/field_of_vision/Initialize(fov_type = FOV_90_DEGREES, _angle = 0)
if(!ismob(parent))
return COMPONENT_INCOMPATIBLE
angle = _angle
shadow_angle = fov_type
/datum/component/field_of_vision/RegisterWithParent()
. = ..()
var/mob/M = parent
if(M.client)
generate_fov_holder(M, angle)
RegisterSignal(M, COMSIG_MOB_CLIENT_LOGIN, .proc/on_mob_login)
RegisterSignal(M, COMSIG_MOB_CLIENT_LOGOUT, .proc/on_mob_logout)
RegisterSignal(M, COMSIG_MOB_GET_VISIBLE_MESSAGE, .proc/on_visible_message)
RegisterSignal(M, COMSIG_MOB_EXAMINATE, .proc/on_examinate)
RegisterSignal(M, COMSIG_MOB_FOV_VIEW, .proc/on_fov_view)
RegisterSignal(M, COMSIG_MOB_CLIENT_CHANGE_VIEW, .proc/on_change_view)
RegisterSignal(M, COMSIG_MOB_RESET_PERSPECTIVE, .proc/on_reset_perspective)
RegisterSignal(M, COMSIG_MOB_FOV_VIEWER, .proc/is_viewer)
/datum/component/field_of_vision/UnregisterFromParent()
. = ..()
var/mob/M = parent
if(!QDELETED(fov))
if(M.client)
UnregisterSignal(M, list(COMSIG_ATOM_DIR_CHANGE, COMSIG_MOVABLE_MOVED, COMSIG_MOB_DEATH, COMSIG_LIVING_REVIVE))
M.client.images -= owner_mask
M.client.images -= shadow_mask
M.client.images -= visual_shadow
M.client.images -= adj_mask
qdel(fov, TRUE) // Forced.
fov = null
QDEL_NULL(owner_mask)
QDEL_NULL(adj_mask)
if(length(nested_locs))
UNREGISTER_NESTED_LOCS(nested_locs, COMSIG_MOVABLE_MOVED, 1)
UnregisterSignal(M, list(COMSIG_MOB_CLIENT_LOGIN, COMSIG_MOB_CLIENT_LOGOUT,
COMSIG_MOB_GET_VISIBLE_MESSAGE, COMSIG_MOB_EXAMINATE,
COMSIG_MOB_FOV_VIEW, COMSIG_MOB_RESET_PERSPECTIVE,
COMSIG_MOB_CLIENT_CHANGE_VIEW, COMSIG_MOB_FOV_VIEWER))
/**
* Generates the holder and images (if not generated yet) and adds them to client.images.
* Run when the component is registered to a player mob, or upon login.
*/
/datum/component/field_of_vision/proc/generate_fov_holder(mob/M, _angle = 0)
if(QDELETED(fov))
fov = new(get_turf(M))
fov.icon_state = "[shadow_angle]"
fov.dir = M.dir
shadow_mask = image('icons/misc/field_of_vision.dmi', fov, "[shadow_angle]", FIELD_OF_VISION_LAYER)
shadow_mask.plane = FIELD_OF_VISION_PLANE
visual_shadow = image('icons/misc/field_of_vision.dmi', fov, "[shadow_angle]_v", FIELD_OF_VISION_LAYER)
visual_shadow.plane = FIELD_OF_VISION_VISUAL_PLANE
owner_mask = new
owner_mask.appearance_flags = RESET_TRANSFORM
owner_mask.plane = FIELD_OF_VISION_BLOCKER_PLANE
adj_mask = image('icons/misc/field_of_vision.dmi', fov, "adj_mask", FIELD_OF_VISION_LAYER)
adj_mask.appearance_flags = RESET_TRANSFORM
adj_mask.plane = FIELD_OF_VISION_BLOCKER_PLANE
if(_angle)
rotate_shadow_cone(_angle)
fov.alpha = M.stat == DEAD ? 0 : 255
RegisterSignal(M, COMSIG_MOB_DEATH, .proc/hide_fov)
RegisterSignal(M, COMSIG_LIVING_REVIVE, .proc/show_fov)
RegisterSignal(M, COMSIG_ATOM_DIR_CHANGE, .proc/on_dir_change)
RegisterSignal(M, COMSIG_MOVABLE_MOVED, .proc/on_mob_moved)
RegisterSignal(M, COMSIG_ROBOT_UPDATE_ICONS, .proc/manual_centered_render_source)
var/atom/A = M
if(M.loc && !isturf(M.loc))
REGISTER_NESTED_LOCS(M, nested_locs, COMSIG_MOVABLE_MOVED, .proc/on_loc_moved)
A = nested_locs[nested_locs.len]
CENTERED_RENDER_SOURCE(owner_mask, A, src)
M.client.images += shadow_mask
M.client.images += visual_shadow
M.client.images += owner_mask
M.client.images += adj_mask
if(M.client.view != "[current_fov_size[1]]x[current_fov_size[2]]")
resize_fov(current_fov_size, getviewsize(M.client.view))
///Rotates the shadow cone to a certain degree. Backend shenanigans.
/datum/component/field_of_vision/proc/rotate_shadow_cone(new_angle)
var/simple_degrees = SIMPLIFY_DEGREES(new_angle - angle)
var/to_scale = cos(simple_degrees) * sin(simple_degrees)
if(to_scale)
var/old_rot_scale = rot_scale
rot_scale = 1 + to_scale
if(old_rot_scale != rot_scale)
visual_shadow.transform = shadow_mask.transform = shadow_mask.transform.Scale(rot_scale/old_rot_scale)
visual_shadow.transform = shadow_mask.transform = shadow_mask.transform.Turn(fov.transform, simple_degrees)
/**
* Resizes the shadow to match the current screen size.
* Run when the client view size is changed, or if the player has a viewsize different than "15x15" on login/comp registration.
*/
/datum/component/field_of_vision/proc/resize_fov(list/old_view, list/view)
current_fov_size = view
var/old_size = max(old_view[1], old_view[2])
var/new_size = max(view[1], view[2])
if(old_size == new_size) //longest edges are still of the same length.
return
visual_shadow.transform = shadow_mask.transform = shadow_mask.transform.Scale(new_size/old_size)
/datum/component/field_of_vision/proc/on_mob_login(mob/source, client/client)
generate_fov_holder(source, angle)
/datum/component/field_of_vision/proc/on_mob_logout(mob/source, client/client)
UnregisterSignal(source, list(COMSIG_ATOM_DIR_CHANGE, COMSIG_MOVABLE_MOVED, COMSIG_MOB_DEATH,
COMSIG_LIVING_REVIVE, COMSIG_ROBOT_UPDATE_ICONS))
if(length(nested_locs))
UNREGISTER_NESTED_LOCS(nested_locs, COMSIG_MOVABLE_MOVED, 1)
/datum/component/field_of_vision/proc/on_dir_change(mob/source, old_dir, new_dir)
fov.dir = new_dir
///Hides the shadow, other visibility comsig procs will take it into account. Called when the mob dies.
/datum/component/field_of_vision/proc/hide_fov(mob/source)
fov.alpha = 0
/// Shows the shadow. Called when the mob is revived.
/datum/component/field_of_vision/proc/show_fov(mob/source)
fov.alpha = 255
/// Hides the shadow when looking through other items, shows it otherwise.
/datum/component/field_of_vision/proc/on_reset_perspective(mob/source, atom/target)
if(source.client.eye == source || source.client.eye == source.loc)
fov.alpha = 255
else
fov.alpha = 0
/// Called when the client view size is changed.
/datum/component/field_of_vision/proc/on_change_view(mob/source, client, list/old_view, list/view)
resize_fov(old_view, view)
/**
* Called when the owner mob moves around. Used to keep shadow located right behind us,
* As well as modify the owner mask to match the topmost item.
*/
/datum/component/field_of_vision/proc/on_mob_moved(mob/source, atom/oldloc, dir, forced)
var/turf/T
if(!isturf(source.loc)) //Recalculate all nested locations.
UNREGISTER_NESTED_LOCS( nested_locs, COMSIG_MOVABLE_MOVED, 1)
REGISTER_NESTED_LOCS(source, nested_locs, COMSIG_MOVABLE_MOVED, .proc/on_loc_moved)
var/atom/movable/topmost = nested_locs[nested_locs.len]
T = topmost.loc
CENTERED_RENDER_SOURCE(owner_mask, topmost, src)
else
T = source.loc
if(length(nested_locs))
UNREGISTER_NESTED_LOCS(nested_locs, COMSIG_MOVABLE_MOVED, 1)
CENTERED_RENDER_SOURCE(owner_mask, source, src)
if(T)
fov.forceMove(T, harderforce = TRUE)
/// Pretty much like the above, but meant for other movables the mob is stored in (bodybags, boxes, mechs etc).
/datum/component/field_of_vision/proc/on_loc_moved(atom/movable/source, atom/oldloc, dir, forced)
if(isturf(source.loc) && isturf(oldloc)) //This is the case of the topmost movable loc moving around the world, skip.
fov.forceMove(source.loc, harderforce = TRUE)
return
var/atom/movable/prev_topmost = nested_locs[nested_locs.len]
if(prev_topmost != source)
UNREGISTER_NESTED_LOCS(nested_locs, COMSIG_MOVABLE_MOVED, nested_locs.Find(source) + 1)
REGISTER_NESTED_LOCS(source, nested_locs, COMSIG_MOVABLE_MOVED, .proc/on_loc_moved)
var/atom/movable/topmost = nested_locs[nested_locs.len]
if(topmost != prev_topmost)
CENTERED_RENDER_SOURCE(owner_mask, topmost, src)
if(topmost.loc)
fov.forceMove(topmost.loc, harderforce = TRUE)
/// A hacky comsig proc for things that somehow decide to change icon on the go. may make a change_icon_file() proc later but...
/datum/component/field_of_vision/proc/manual_centered_render_source(mob/source, old_icon)
if(!isturf(source.loc))
return
CENTERED_RENDER_SOURCE(owner_mask, source, src)
#undef CENTERED_RENDER_SOURCE
#undef REGISTER_NESTED_LOCS
#undef UNREGISTER_NESTED_LOCS
/**
* Byond doc is not entirely correct on the integrated arctan() proc.
* When both x and y are negative, the output is also negative, cycling clockwise instead of counter-clockwise.
* That's also why I am extensively using the SIMPLIFY_DEGREES macro here.
*
* Overall this is the main macro that calculates wheter a target is within the shadow cone angle or not.
*/
#define FOV_ANGLE_CHECK(mob, target, zero_x_y_statement, success_statement) \
var/turf/T1 = get_turf(target);\
var/turf/T2 = get_turf(mob);\
if(!T1 || !T2){\
zero_x_y_statement\
}\
var/_x = (T1.x - T2.x);\
var/_y = (T1.y - T2.y);\
if(ISINRANGE(_x, -1, 1) && ISINRANGE(_y, -1, 1)){\
zero_x_y_statement\
}\
var/dir = (mob.dir & (EAST|WEST)) || mob.dir;\
var/_degree = -angle;\
var/_half = shadow_angle/2;\
switch(dir){\
if(EAST){\
_degree += 180;\
}\
if(NORTH){\
_degree += 270;\
}\
if(SOUTH){\
_degree += 90;\
}\
}\
var/_min = SIMPLIFY_DEGREES(_degree - _half);\
var/_max = SIMPLIFY_DEGREES(_degree + _half);\
if((_min > _max) ? !ISINRANGE(SIMPLIFY_DEGREES(arctan(_x, _y)), _max, _min) : ISINRANGE(SIMPLIFY_DEGREES(arctan(_x, _y)), _min, _max)){\
success_statement;\
}
/datum/component/field_of_vision/proc/on_examinate(mob/source, atom/target)
if(fov.alpha)
FOV_ANGLE_CHECK(source, target, return, return COMPONENT_DENY_EXAMINATE|COMPONENT_EXAMINATE_BLIND)
/datum/component/field_of_vision/proc/on_visible_message(mob/source, atom/target, message, range, list/ignored_mobs)
if(fov.alpha)
FOV_ANGLE_CHECK(source, target, return, return COMPONENT_NO_VISIBLE_MESSAGE)
/datum/component/field_of_vision/proc/on_fov_view(mob/source, list/atoms)
if(!fov.alpha)
return
for(var/k in atoms)
var/atom/A = k
FOV_ANGLE_CHECK(source, A, continue, atoms -= A)
/datum/component/field_of_vision/proc/is_viewer(mob/source, atom/center, depth, list/viewers_list)
if(fov.alpha)
FOV_ANGLE_CHECK(source, center, return, viewers_list -= source)
#undef FOV_ANGLE_CHECK
/**
* The shadow cone's mask and visual images holder which can't locate inside the mob,
* lest they inherit the mob opacity and cause a lot of hindrance
*/
/atom/movable/fov_holder
name = "field of vision holder"
pixel_x = -224 //the image is about 480x480 px, ergo 15 tiles (480/32) big, and we gotta center it.
pixel_y = -224
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
plane = FIELD_OF_VISION_PLANE
anchored = TRUE
/atom/movable/fov_holder/ConveyorMove()
return
/atom/movable/fov_holder/has_gravity(turf/T)
return FALSE
/atom/movable/fov_holder/ex_act(severity)
return FALSE
/atom/movable/fov_holder/singularity_act()
return
/atom/movable/fov_holder/singularity_pull()
return
/atom/movable/fov_holder/blob_act()
return
/atom/movable/fov_holder/onTransitZ()
return
/// Prevents people from moving these after creation, because they shouldn't be.
/atom/movable/fov_holder/forceMove(atom/destination, no_tp=FALSE, harderforce = FALSE)
if(harderforce)
return ..()
/// Last but not least, these shouldn't be deleted by anything but the component itself
/atom/movable/fov_holder/Destroy(force = FALSE)
if(!force)
return QDEL_HINT_LETMELIVE
return ..()
+97 -67
View File
@@ -1,26 +1,52 @@
///Footstep component. Plays footsteps at parents location when it is appropriate.
/datum/component/footstep
///How many steps the parent has taken since the last time a footstep was played
var/steps = 0
///volume determines the extra volume of the footstep. This is multiplied by the base volume, should there be one.
var/volume
///e_range stands for extra range - aka how far the sound can be heard. This is added to the base value and ignored if there isn't a base value.
var/e_range
///footstep_type is a define which determines what kind of sounds should get chosen.
var/footstep_type
///This can be a list OR a soundfile OR null. Determines whatever sound gets played.
var/footstep_sounds
/datum/component/footstep/Initialize(volume_ = 0.5, e_range_ = -1)
/datum/component/footstep/Initialize(footstep_type_ = FOOTSTEP_MOB_BAREFOOT, volume_ = 0.5, e_range_ = -1)
if(!isliving(parent))
return COMPONENT_INCOMPATIBLE
volume = volume_
e_range = e_range_
RegisterSignal(parent, list(COMSIG_MOVABLE_MOVED), .proc/play_footstep)
footstep_type = footstep_type_
switch(footstep_type)
if(FOOTSTEP_MOB_HUMAN)
if(!ishuman(parent))
return COMPONENT_INCOMPATIBLE
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/play_humanstep)
return
if(FOOTSTEP_MOB_CLAW)
footstep_sounds = GLOB.clawfootstep
if(FOOTSTEP_MOB_BAREFOOT)
footstep_sounds = GLOB.barefootstep
if(FOOTSTEP_MOB_HEAVY)
footstep_sounds = GLOB.heavyfootstep
if(FOOTSTEP_MOB_SHOE)
footstep_sounds = GLOB.footstep
if(FOOTSTEP_MOB_SLIME)
footstep_sounds = 'sound/effects/footstep/slime1.ogg'
if(FOOTSTEP_MOB_CRAWL)
footstep_sounds = 'sound/effects/footstep/crawl1.ogg'
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/play_simplestep) //Note that this doesn't get called for humans.
/datum/component/footstep/proc/play_footstep()
///Prepares a footstep. Determines if it should get played. Returns the turf it should get played on. Note that it is always a /turf/open
/datum/component/footstep/proc/prepare_step()
var/turf/open/T = get_turf(parent)
if(!istype(T))
return
var/mob/living/LM = parent
var/v = volume
var/e = e_range
if(!T.footstep || LM.buckled || !CHECK_MOBILITY(LM, MOBILITY_STAND) || LM.buckled || LM.throwing || (LM.movement_type & (VENTCRAWLING | FLYING)))
if (LM.lying && !LM.buckled && !(!T.footstep || LM.movement_type & (VENTCRAWLING | FLYING))) //play crawling sound if we're lying
playsound(T, 'sound/effects/footstep/crawl1.ogg', 15 * v)
playsound(T, 'sound/effects/footstep/crawl1.ogg', 15 * volume)
return
if(HAS_TRAIT(LM, TRAIT_SILENT_STEP))
@@ -30,79 +56,83 @@
var/mob/living/carbon/C = LM
if(!C.get_bodypart(BODY_ZONE_L_LEG) && !C.get_bodypart(BODY_ZONE_R_LEG))
return
if(ishuman(C) && C.m_intent == MOVE_INTENT_WALK)
v /= 2
e -= 5
if(C.m_intent == MOVE_INTENT_WALK)
return
steps++
if(steps >= 3)
if(steps >= 6)
steps = 0
else
if(steps % 2)
return
if(prob(80) && !LM.has_gravity(T)) // don't need to step as often when you hop around
if(steps != 0 && !LM.has_gravity(T)) // don't need to step as often when you hop around
return
return T
//begin playsound shenanigans//
//for barefooted non-clawed mobs like monkeys
if(isbarefoot(LM))
playsound(T, pick(GLOB.barefootstep[T.barefootstep][1]),
GLOB.barefootstep[T.barefootstep][2] * v,
TRUE,
GLOB.barefootstep[T.barefootstep][3] + e)
/datum/component/footstep/proc/play_simplestep()
var/turf/open/T = prepare_step()
if(!T)
return
//for xenomorphs, dogs, and other clawed mobs
if(isclawfoot(LM))
if(isalienadult(LM)) //xenos are stealthy and get quieter footsteps
v /= 3
e -= 5
playsound(T, pick(GLOB.clawfootstep[T.clawfootstep][1]),
GLOB.clawfootstep[T.clawfootstep][2] * v,
TRUE,
GLOB.clawfootstep[T.clawfootstep][3] + e)
if(isfile(footstep_sounds) || istext(footstep_sounds))
playsound(T, footstep_sounds, volume)
return
//for megafauna and other large and imtimidating mobs such as the bloodminer
if(isheavyfoot(LM))
playsound(T, pick(GLOB.heavyfootstep[T.heavyfootstep][1]),
GLOB.heavyfootstep[T.heavyfootstep][2] * v,
TRUE,
GLOB.heavyfootstep[T.heavyfootstep][3] + e)
var/turf_footstep
switch(footstep_type)
if(FOOTSTEP_MOB_CLAW)
turf_footstep = T.clawfootstep
if(FOOTSTEP_MOB_BAREFOOT)
turf_footstep = T.barefootstep
if(FOOTSTEP_MOB_HEAVY)
turf_footstep = T.heavyfootstep
if(FOOTSTEP_MOB_SHOE)
turf_footstep = T.footstep
if(!turf_footstep)
return
playsound(T, pick(footstep_sounds[turf_footstep][1]), footstep_sounds[turf_footstep][2] * volume, TRUE, footstep_sounds[turf_footstep][3] + e_range)
//for slimes
if(isslime(LM))
playsound(T, 'sound/effects/footstep/slime1.ogg', 15 * v)
/datum/component/footstep/proc/play_humanstep()
var/turf/open/T = prepare_step()
if(!T)
return
//for (simple) humanoid mobs (clowns, russians, pirates, etc.)
if(isshoefoot(LM))
if(!ishuman(LM))
playsound(T, pick(GLOB.footstep[T.footstep][1]),
GLOB.footstep[T.footstep][2] * v,
TRUE,
GLOB.footstep[T.footstep][3] + e)
return
if(ishuman(LM)) //for proper humans, they're special
var/mob/living/carbon/human/H = LM
var/feetCover = (H.wear_suit && (H.wear_suit.body_parts_covered & FEET)) || (H.w_uniform && (H.w_uniform.body_parts_covered & FEET) || (H.shoes && (H.shoes.body_parts_covered & FEET)))
if (H.dna.features["taur"] == "Naga" || H.dna.features["taur"] == "Tentacle") //are we a naga or tentacle taur creature
playsound(T, 'sound/effects/footstep/crawl1.ogg', 15 * v)
var/mob/living/carbon/human/H = parent
var/list/L = GLOB.barefootstep
var/turf_footstep = T.barefootstep
var/special = FALSE
if(H.physiology.footstep_type)
switch(H.physiology.footstep_type)
if(FOOTSTEP_MOB_CLAW)
turf_footstep = T.clawfootstep
L = GLOB.clawfootstep
if(FOOTSTEP_MOB_BAREFOOT)
turf_footstep = T.barefootstep
L = GLOB.barefootstep
if(FOOTSTEP_MOB_HEAVY)
turf_footstep = T.heavyfootstep
L = GLOB.heavyfootstep
if(FOOTSTEP_MOB_SHOE)
turf_footstep = T.footstep
L = GLOB.footstep
if(FOOTSTEP_MOB_SLIME)
playsound(T, 'sound/effects/footstep/slime1.ogg', 50 * volume)
return
if(FOOTSTEP_MOB_CRAWL)
playsound(T, 'sound/effects/footstep/crawl1.ogg', 50 * volume)
return
special = TRUE
else
var/feetCover = (H.wear_suit && (H.wear_suit.body_parts_covered & FEET)) || (H.w_uniform && (H.w_uniform.body_parts_covered & FEET) || (H.shoes && (H.shoes.body_parts_covered & FEET)))
if(feetCover) //are we wearing shoes
playsound(T, pick(GLOB.footstep[T.footstep][1]),
GLOB.footstep[T.footstep][2] * volume,
TRUE,
GLOB.footstep[T.footstep][3] + e_range)
return
if(feetCover) //are we wearing shoes
playsound(T, pick(GLOB.footstep[T.footstep][1]),
GLOB.footstep[T.footstep][2] * v,
TRUE,
GLOB.footstep[T.footstep][3] + e)
if(!feetCover) //are we NOT wearing shoes
playsound(T, pick(GLOB.barefootstep[T.barefootstep][1]),
GLOB.barefootstep[T.barefootstep][2] * v,
TRUE,
GLOB.barefootstep[T.barefootstep][3] + e)
if(!special && H.dna.species.special_step_sounds)
playsound(T, pick(H.dna.species.special_step_sounds), 50, TRUE)
else
playsound(T, pick(L[turf_footstep][1]),
L[turf_footstep][2] * volume,
TRUE,
L[turf_footstep][3] + e_range)
+1 -1
View File
@@ -73,7 +73,7 @@
if(!material_amount)
to_chat(user, "<span class='warning'>[I] does not contain sufficient materials to be accepted by [parent].</span>")
return
if(!has_space(material_amount))
if((!precise_insertion || !GLOB.typecache_stack[I]) && !has_space(material_amount))
to_chat(user, "<span class='warning'>[parent] has not enough space. Please remove materials from [parent] in order to insert more.</span>")
return
user_insert(I, user)
+49 -53
View File
@@ -1,41 +1,52 @@
#define ECSTATIC_SANITY_PEN -1
#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.
#define MOOD_INSANITY_MALUS 0.13 // 13% debuff per sanity_level above the default of 4 (higher is worser), overall a 39% debuff to skills at rock bottom depression.
/datum/component/mood
var/mood //Real happiness
var/sanity = 100 //Current sanity
var/shown_mood //Shown happiness, this is what others can see when they try to examine you, prevents antag checking by noticing traitors are always very happy.
var/mood_level = 5 //To track what stage of moodies they're on
var/sanity_level = 5 //To track what stage of sanity they're on
var/sanity_level = 3 //To track what stage of sanity they're on
var/mood_modifier = 1 //Modifier to allow certain mobs to be less affected by moodlets
var/list/datum/mood_event/mood_events = list()
var/insanity_effect = 0 //is the owner being punished for low mood? If so, how much?
var/obj/screen/mood/screen_obj
var/datum/skill_modifier/bad_mood/malus
var/datum/skill_modifier/great_mood/bonus
var/static/malus_id = 0
var/static/list/free_maluses = list()
/datum/component/mood/Initialize()
if(!isliving(parent))
return COMPONENT_INCOMPATIBLE
START_PROCESSING(SSmood, src)
var/mob/living/owner = parent
if(owner.stat != DEAD)
START_PROCESSING(SSdcs, src)
RegisterSignal(parent, COMSIG_ADD_MOOD_EVENT, .proc/add_event)
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
RegisterSignal(parent, COMSIG_MOB_DEATH, .proc/stop_processing)
if(owner.hud_used)
modify_hud()
var/datum/hud/hud = owner.hud_used
hud.show_hud(hud.hud_version)
/datum/component/mood/Destroy()
STOP_PROCESSING(SSmood, src)
STOP_PROCESSING(SSdcs, src)
unmodify_hud()
return ..()
/datum/component/mood/proc/stop_processing()
STOP_PROCESSING(SSdcs, src)
/datum/component/mood/proc/print_mood(mob/user)
var/msg = "<span class='info'>*---------*\n<EM>Your current mood</EM>\n"
msg += "<span class='notice'>My mental status: </span>" //Long term
@@ -127,7 +138,7 @@
else
screen_obj.icon_state = "mood[mood_level]"
/datum/component/mood/process() //Called on SSmood process
/datum/component/mood/process() //Called on SSdcs process
if(QDELETED(parent)) // workaround to an obnoxious sneaky periodical runtime.
qdel(src)
return
@@ -167,13 +178,13 @@
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)
else
sanity = amount
var/old_sanity_level = sanity_level
switch(sanity)
if(-INFINITY to SANITY_CRAZY)
setInsanityEffect(MAJOR_INSANITY_PEN)
@@ -196,27 +207,43 @@
master.remove_movespeed_modifier(MOVESPEED_ID_SANITY)
sanity_level = 2
if(SANITY_GREAT+1 to INFINITY)
setInsanityEffect(0)
setInsanityEffect(ECSTATIC_SANITY_PEN) //It's not a penalty but w/e
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)
if(sanity_level != old_sanity_level)
if(sanity_level >= 4)
if(!malus)
if(!length(free_maluses))
ADD_SKILL_MODIFIER_BODY(/datum/skill_modifier/bad_mood, malus_id++, master, malus)
else
malus = pick_n_take(free_maluses)
if(master.mind)
master.mind.add_skill_modifier(malus.identifier)
else
malus.RegisterSignal(master, COMSIG_MOB_ON_NEW_MIND, /datum/skill_modifier.proc/on_mob_new_mind, TRUE)
malus.value_mod = malus.level_mod = 1 - (sanity_level - 3) * MOOD_INSANITY_MALUS
else if(malus)
if(master.mind)
master.mind.remove_skill_modifier(malus.identifier)
else
malus.UnregisterSignal(master, COMSIG_MOB_ON_NEW_MIND)
free_maluses += malus
malus = null
//update_mood_icon()
/datum/component/mood/proc/setInsanityEffect(newval)//More code so that the previous proc works
if(newval == insanity_effect)
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))
var/mob/living/L = parent
if(newval == ECSTATIC_SANITY_PEN && !bonus)
ADD_SKILL_MODIFIER_BODY(/datum/skill_modifier/great_mood, null, L, bonus)
else if(bonus)
REMOVE_SKILL_MODIFIER_BODY(/datum/skill_modifier/great_mood, null, L)
bonus = null
insanity_effect = newval
/datum/component/mood/proc/modify_sanity(datum/source, amount, minimum = SANITY_INSANE, maximum = SANITY_AMAZING)
@@ -294,46 +321,15 @@
if(0 to NUTRITION_LEVEL_STARVING)
add_event(null, "nutrition", /datum/mood_event/starving)
///Called when parent is ahealed.
///Called when parent is revived.
/datum/component/mood/proc/on_revive(datum/source, full_heal)
START_PROCESSING(SSdcs, src)
if(!full_heal)
return
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 ECSTATIC_SANITY_PEN
#undef SLIGHT_INSANITY_PEN
#undef MINOR_INSANITY_PEN
#undef MAJOR_INSANITY_PEN
+8 -5
View File
@@ -353,8 +353,12 @@
/datum/component/storage/proc/_remove_and_refresh(datum/source, atom/movable/thing)
_removal_reset(thing)
if(LAZYACCESS(ui_item_blocks, thing))
qdel(ui_item_blocks[thing])
var/obj/screen/storage/volumetric_box/center/C = ui_item_blocks[thing]
for(var/i in can_see_contents()) //runtimes result if mobs can access post deletion.
var/mob/M = i
M.client?.screen -= C.on_screen_objects()
ui_item_blocks -= thing
qdel(C)
refresh_mob_views()
//Call this proc to handle the removal of an item from the storage item. The item will be moved to the new_location target, if that is null it's being deleted
@@ -567,10 +571,9 @@
return
if(rustle_sound)
playsound(parent, "rustle", 50, 1, -5)
for(var/mob/viewing in viewers(user, null))
if(M == viewing)
to_chat(usr, "<span class='notice'>You put [I] [insert_preposition]to [parent].</span>")
else if(in_range(M, viewing)) //If someone is standing close enough, they can tell what it is...
to_chat(user, "<span class='notice'>You put [I] [insert_preposition]to [parent].</span>")
for(var/mob/viewing in fov_viewers(world.view, user)-M)
if(in_range(M, viewing)) //If someone is standing close enough, they can tell what it is...
viewing.show_message("<span class='notice'>[M] puts [I] [insert_preposition]to [parent].</span>", MSG_VISUAL)
else if(I && I.w_class >= 3) //Otherwise they can only see large or normal items from a distance...
viewing.show_message("<span class='notice'>[M] puts [I] [insert_preposition]to [parent].</span>", MSG_VISUAL)
+10 -6
View File
@@ -92,6 +92,8 @@
var/list/volume_by_item = list()
var/list/percentage_by_item = list()
for(var/obj/item/I in contents)
if(QDELETED(I))
continue
volume = I.get_w_volume()
used += volume
volume_by_item[I] = volume
@@ -123,6 +125,7 @@
var/obj/item/I
// start at this pixel from screen_start_x.
var/current_pixel = VOLUMETRIC_STORAGE_EDGE_PADDING
var/first = TRUE
var/row = 1
LAZYINITLIST(ui_item_blocks)
@@ -140,10 +143,10 @@
addrow = TRUE
// now that we have pixels_to_use, place our thing and add it to the returned list.
B.screen_loc = I.screen_loc = "[screen_start_x]:[round(current_pixel + (pixels_to_use * 0.5) + VOLUMETRIC_STORAGE_ITEM_PADDING, 1)],[screen_start_y+row-1]:[screen_pixel_y]"
B.screen_loc = I.screen_loc = "[screen_start_x]:[round(current_pixel + (pixels_to_use * 0.5) + (first? 0 : VOLUMETRIC_STORAGE_ITEM_PADDING), 1)],[screen_start_y+row-1]:[screen_pixel_y]"
// add the used pixels to pixel after we place the object
current_pixel += pixels_to_use + VOLUMETRIC_STORAGE_ITEM_PADDING
current_pixel += pixels_to_use + (first? 0 : VOLUMETRIC_STORAGE_ITEM_PADDING)
first = FALSE //apply padding to everything after this
// set various things
B.set_pixel_size(pixels_to_use)
@@ -163,6 +166,7 @@
// go up a row if needed
if(addrow)
row++
first = TRUE //first in the row, don't apply between-item padding.
current_pixel = VOLUMETRIC_STORAGE_EDGE_PADDING
// Then, continuous section.
@@ -233,7 +237,7 @@
if(!M.client)
return TRUE
UnregisterSignal(M, COMSIG_MOB_CLIENT_LOGOUT)
M.client.screen -= list(ui_boxes, ui_close, ui_left, ui_continuous) + get_ui_item_objects_hide()
M.client.screen -= list(ui_boxes, ui_close, ui_left, ui_continuous) + get_ui_item_objects_hide(M)
if(M.active_storage == src)
M.active_storage = null
LAZYREMOVE(is_using, M)
@@ -249,8 +253,8 @@
/**
* Gets the ui item objects to ui_hide.
*/
/datum/component/storage/proc/get_ui_item_objects_hide()
if(!volumetric_ui())
/datum/component/storage/proc/get_ui_item_objects_hide(mob/M)
if(!volumetric_ui() || M.client?.prefs?.no_tetris_storage)
var/atom/real_location = real_location()
return real_location.contents
else
+7 -1
View File
@@ -145,4 +145,10 @@
return !is_mouth_covered()
/mob/living/carbon/CanSpreadAirborneDisease()
return !((head && (head.flags_cover & HEADCOVERSMOUTH) && (head.armor.getRating("bio") >= 25)) || (wear_mask && (wear_mask.flags_cover & MASKCOVERSMOUTH) && (wear_mask.armor.getRating("bio") >= 25)))
return !((head && (head.flags_cover & HEADCOVERSMOUTH) && (head.armor.getRating("bio") >= 25)) || (wear_mask && (wear_mask.flags_cover & MASKCOVERSMOUTH) && (wear_mask.armor.getRating("bio") >= 25)))
/mob/living/proc/set_shocked()
flags_1 |= SHOCKED_1
/mob/living/proc/reset_shocked()
flags_1 &= ~ SHOCKED_1
+12 -1
View File
@@ -47,8 +47,8 @@
destination.dna.uni_identity = uni_identity
destination.dna.blood_type = blood_type
destination.dna.skin_tone_override = skin_tone_override
destination.set_species(species.type, icon_update=0)
destination.dna.features = features.Copy()
destination.set_species(species.type, icon_update=0)
destination.dna.real_name = real_name
destination.dna.nameless = nameless
destination.dna.custom_species = custom_species
@@ -246,6 +246,17 @@
construct_block(GLOB.mam_body_markings_list.Find(features["mam_body_markings"]), GLOB.mam_body_markings_list.len)
if(DNA_TAUR_BLOCK)
construct_block(GLOB.taur_list.Find(features["taur"]), GLOB.taur_list.len)
if(species.mutant_bodyparts["taur"] && ishuman(holder))
var/datum/sprite_accessory/taur/T = GLOB.taur_list[features["taur"]]
switch(T?.taur_mode)
if(STYLE_HOOF_TAURIC)
H.physiology.footstep_type = FOOTSTEP_MOB_SHOE
if(STYLE_PAW_TAURIC)
H.physiology.footstep_type = FOOTSTEP_MOB_CLAW
if(STYLE_SNEK_TAURIC)
H.physiology.footstep_type = FOOTSTEP_MOB_CRAWL
else
H.physiology.footstep_type = null
//Please use add_mutation or activate_mutation instead
/datum/dna/proc/force_give(datum/mutation/human/HM)
+42
View File
@@ -0,0 +1,42 @@
#define SHORT 4/5
#define TALL 5/4
///Very similar to squish, but for dwarves and shorties
/datum/element/dwarfism
element_flags = ELEMENT_DETACH|ELEMENT_BESPOKE
id_arg_index = 2
var/comsig
var/list/attached_targets = list()
/datum/element/dwarfism/Attach(datum/target, comsig, comsig_target)
. = ..()
if(!isliving(target))
return ELEMENT_INCOMPATIBLE
src.comsig = comsig
var/mob/living/L = target
if(L.lying != 0)
L.transform = L.transform.Scale(SHORT, 1)
else
L.transform = L.transform.Scale(1, SHORT)
attached_targets[target] = comsig_target
RegisterSignal(target, comsig, .proc/check_loss) //Second arg of the signal will be checked against the comsig_target.
/datum/element/dwarfism/proc/check_loss(mob/living/L, comsig_target)
if(attached_targets[L] == comsig_target)
Detach(L)
/datum/element/dwarfism/Detach(mob/living/L)
. = ..()
if(QDELETED(L))
return
if(L.lying != 0)
L.transform = L.transform.Scale(TALL, 1)
else
L.transform = L.transform.Scale(1, TALL)
UnregisterSignal(L, comsig)
attached_targets -= L
#undef SHORT
#undef TALL
+1 -1
View File
@@ -41,7 +41,7 @@
return
//we want to delay the effect to be displayed after the mob is petted, not before.
switch(user.a_intent)
if(INTENT_HARM, INTENT_DISARM)
if(INTENT_HARM)
addtimer(CALLBACK(src, .proc/kick_the_dog, source, user), 1)
if(INTENT_HELP)
addtimer(CALLBACK(src, .proc/pet_the_dog, source, user), 1)
+11 -7
View File
@@ -5,16 +5,18 @@
var/list/testmerge = list()
/datum/getrev/New()
commit = rustg_git_revparse("HEAD")
if(commit)
date = rustg_git_commit_date(commit)
originmastercommit = rustg_git_revparse("origin/master")
/datum/getrev/proc/load_tgs_info()
testmerge = world.TgsTestMerges()
var/datum/tgs_revision_information/revinfo = world.TgsRevision()
if(revinfo)
commit = revinfo.commit
originmastercommit = revinfo.origin_commit
else
commit = rustg_git_revparse("HEAD")
if(commit)
date = rustg_git_commit_date(commit)
originmastercommit = rustg_git_revparse("origin/master")
date = rustg_git_commit_date(commit)
// goes to DD log and config_error.txt
log_world(get_log_message())
@@ -77,7 +79,9 @@
msg += "No commit information"
if(world.TgsAvailable())
var/datum/tgs_version/version = world.TgsVersion()
msg += "Server tools version: [version.raw_parameter]"
msg += "TGS version: [version.raw_parameter]"
var/datum/tgs_version/api_version = world.TgsApiVersion()
msg += "DMAPI version: [api_version.raw_parameter]"
// Game mode odds
msg += "<br><b>Current Informational Settings:</b>"
@@ -121,4 +125,4 @@
if(probabilities[ctag] > 0)
var/percentage = round(probabilities[ctag] / sum * 100, 0.1)
msg += "[ctag] [percentage]%"
to_chat(src, msg.Join("<br>"))
to_chat(src, msg.Join("<br>"))
+1 -1
View File
@@ -67,7 +67,7 @@
var/datum/skill_holder/skill_holder
/datum/mind/New(var/key)
skill_holder = new(src)
skill_holder = new()
src.key = key
soulOwner = src
martial_art = default_martial_art
@@ -248,7 +248,7 @@
description = "<span class='boldwarning'>Something I recently ate was horrifyingly disgusting.</span>\n"
mood_change = -5
timeout = 5 MINUTES
/datum/mood_event/nanite_sadness
description = "<span class='warning robot'>+++++++HAPPINESS SUPPRESSION+++++++</span>\n"
mood_change = -7
@@ -256,3 +256,7 @@
/datum/mood_event/nanite_sadness/add_effects(message)
description = "<span class='warning robot'>+++++++[message]+++++++</span>\n"
/datum/mood_event/artbad
description = "<span class='warning'>I've produced better art than that from my ass.</span>\n"
mood_change = -2
timeout = 1200
@@ -182,3 +182,17 @@
description = "<span class='nicegreen'>What a peculiar emblem. It makes me feel hopeful for my future.</span>\n"
mood_change = 5
/datum/mood_event/artok
description = "<span class='nicegreen'>It's nice to see people are making art around here.</span>\n"
mood_change = 2
timeout = 2 MINUTES
/datum/mood_event/artgood
description = "<span class='nicegreen'>What a thought-provoking piece of art. I'll remember that for a while.</span>\n"
mood_change = 3
timeout = 3 MINUTES
/datum/mood_event/artgreat
description = "<span class='nicegreen'>That work of art was so great it made me believe in the goodness of humanity. Says a lot in a place like this.</span>\n"
mood_change = 4
timeout = 4 MINUTES
+2 -1
View File
@@ -116,7 +116,8 @@
owner.apply_overlay(layer_used)
if(power)
owner.RemoveSpell(power)
qdel(src)
qdel(power)
SEND_SIGNAL(owner, COMSIG_HUMAN_MUTATION_LOSS, src)
return 0
return 1
+1 -2
View File
@@ -82,7 +82,7 @@
if(..())
return
ADD_TRAIT(owner, TRAIT_DWARF, GENETIC_MUTATION)
owner.transform = owner.transform.Scale(1, 0.8)
owner.AddElement(/datum/element/dwarfism, COMSIG_HUMAN_MUTATION_LOSS, src)
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>")
@@ -90,7 +90,6 @@
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>")
+1 -1
View File
@@ -12,5 +12,5 @@
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.set_content(mind.skill_html_readout())
B.open()
+19 -26
View File
@@ -9,19 +9,10 @@ GLOBAL_LIST_INIT_TYPED(skill_datums, /datum/skill, init_skill_datums())
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
@@ -29,16 +20,20 @@ GLOBAL_LIST_INIT_TYPED(skill_datums, /datum/skill, init_skill_datums())
var/desc
/// Color of the name as shown in the html readout
var/name_color = "#F0F0F0" // White on dark surface.
/// Our progression type
/// Our progression type. These are mostly used to skip typechecks overhead, don't go around messing with these.
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
/// List of max levels. Only used in level skills, but present here for helper macros.
var/max_levels = INFINITY
/// skill threshold used in generic skill competency operations.
var/list/competency_thresholds
/// Base multiplier used in skill competency operations.
var/base_multiplier = 1
/// Value added to the base multiplier depending on overall competency compared to maximum value/level.
var/competency_multiplier = 1
/// A list of ways this skill can affect or be affected through actions and skill modifiers.
var/list/skill_traits = list(SKILL_SANITY, SKILL_INTELLIGENCE)
/**
* Ensures what someone's setting as a value for this skill is valid.
@@ -73,8 +68,7 @@ GLOBAL_LIST_INIT_TYPED(skill_datums, /datum/skill, init_skill_datums())
/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)
competency_thresholds = list(THRESHOLD_COMPETENT = FALSE, THRESHOLD_EXPERT = TRUE, THRESHOLD_MASTER = TRUE)
/datum/skill/binary/sanitize_value(new_value)
return new_value? TRUE : FALSE
@@ -85,6 +79,7 @@ GLOBAL_LIST_INIT_TYPED(skill_datums, /datum/skill, init_skill_datums())
/datum/skill/numerical
abstract_type = /datum/skill/numerical
progression_type = SKILL_PROGRESSION_NUMERICAL
competency_thresholds = list(THRESHOLD_COMPETENT = 25, THRESHOLD_EXPERT = 50, THRESHOLD_MASTER = 75)
/// Max value of this skill
var/max_value = 100
/// Min value of this skill
@@ -114,9 +109,9 @@ GLOBAL_LIST_INIT_TYPED(skill_datums, /datum/skill, init_skill_datums())
/datum/skill/level
abstract_type = /datum/skill/level
progression_type = SKILL_PROGRESSION_LEVEL
max_levels = STD_MAX_LVL
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.
@@ -177,18 +172,17 @@ GLOBAL_LIST_INIT_TYPED(skill_datums, /datum/skill, init_skill_datums())
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_index = min(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
abstract_type = /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)
competency_thresholds = list(THRESHOLD_COMPETENT = JOB_SKILL_TRAINED, THRESHOLD_EXPERT = JOB_SKILL_EXPERT, THRESHOLD_MASTER = JOB_SKILL_MASTER)
associative = TRUE
//quite the reference, no?
@@ -202,7 +196,6 @@ GLOBAL_LIST_INIT_TYPED(skill_datums, /datum/skill, init_skill_datums())
"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)
competency_thresholds = list(THRESHOLD_COMPETENT = DORF_SKILL_COMPETENT, THRESHOLD_EXPERT = DORF_SKILL_EXPERT, THRESHOLD_MASTER = DORF_SKILL_MASTER)
associative = TRUE
unskilled_tier = "Dabbling"
+88 -59
View File
@@ -1,8 +1,8 @@
/**
* Skill holder datums
* All procs are tied to the mind, since they are always expected to have a skill holder anyway.
*/
/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.
@@ -10,66 +10,87 @@
/// 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
/// current skill modifiers lists, per value, affinity, level.
var/list/skill_value_mods
var/list/skill_affinity_mods
var/list/skill_level_mods
/// List of all current skill modifiers, so we don't add the same ones twice.
var/list/all_current_skill_modifiers
/// List of original values stored at the time a modifier with the MODIFIER_SKILL_ORIGIN_DIFF enabled was added.
var/list/original_values
var/list/original_affinities
var/list/original_levels
/**
* Grabs the value of a skill.
*/
/datum/skill_holder/proc/get_skill_value(skill)
/datum/mind/proc/get_skill_value(skill, apply_modifiers = TRUE)
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]
if(!skill_holder.skills)
. = 0
else
. = skill_holder.skills[skill] || 0
if(apply_modifiers && skill_holder.skill_value_mods)
var/L = LAZYACCESS(skill_holder.skill_value_mods, skill)
for(var/k in L)
var/datum/skill_modifier/M = GLOB.skill_modifiers[k]
. = M.apply_modifier(., skill, skill_holder, MODIFIER_TARGET_VALUE)
/**
* Grabs the level of a skill. Only supported by skills with tiers or levels.
*/
/datum/skill_holder/proc/get_skill_level(skill)
/datum/mind/proc/get_skill_level(skill, apply_modifiers = TRUE, round = FALSE)
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]
if(!skill_holder.skill_levels)
. = 0
else
. = skill_holder.skill_levels[skill] || 0
if(apply_modifiers && skill_holder.skill_level_mods)
var/L = LAZYACCESS(skill_holder.skill_level_mods, skill)
for(var/k in L)
var/datum/skill_modifier/M = GLOB.skill_modifiers[k]
. = M.apply_modifier(., skill, skill_holder, MODIFIER_TARGET_LEVEL)
. = SANITIZE_SKILL_LEVEL(skill, round ? round(., 1) : .)
/**
* Grabs our affinity for a skill. !!This is a multiplier!!
*/
/datum/skill_holder/proc/get_skill_affinity(skill)
/datum/mind/proc/get_skill_affinity(skill, apply_modifiers = TRUE)
. = 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)
var/affinity = LAZYACCESS(skill_holder.skill_affinities, skill)
if(!isnull(affinity))
. = affinity
var/list/wrapped = list(.)
SEND_SIGNAL(owner.current, COMSIG_MOB_SKILL_GET_AFFINITY, skill, wrapped)
. = wrapped[1]
if(apply_modifiers && skill_holder.skill_affinity_mods)
var/L = LAZYACCESS(skill_holder.skill_affinity_mods, skill)
for(var/k in L)
var/datum/skill_modifier/M = GLOB.skill_modifiers[k]
. = M.apply_modifier(., skill, skill_holder, MODIFIER_TARGET_AFFINITY)
/**
* Sets the value of a skill.
*/
/datum/skill_holder/proc/set_skill_value(skill, value, silent = FALSE)
/datum/mind/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)
LAZYINITLIST(skill_holder.skills)
S.set_skill_value(skill_holder, value, src, 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))
/datum/mind/proc/boost_skill_value_to(skill, value, silent = FALSE, current)
current = current || get_skill_value(skill, FALSE)
if(!IS_SKILL_VALUE_GREATER(skill, current, value))
return FALSE
set_skill_value(skill, value, silent)
return TRUE
@@ -78,20 +99,20 @@
* 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)
/datum/mind/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/current = get_skill_value(skill, FALSE)
var/affinity = get_skill_affinity(skill)
var/target_value = current + (value * affinity)
if(maximum)
target_value = max(target_value, maximum)
target_value = min(target_value, maximum)
if(target_value == maximum) //no more experience to gain, early return.
return
boost_skill_value_to(skill, target_value, silent)
boost_skill_value_to(skill, target_value, silent, current)
/**
* Generic value modifier proc that uses one skill.
@@ -100,22 +121,23 @@
* * 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
/datum/mind/proc/action_skill_mod(skill, value, threshold, modifier_is_multiplier = TRUE)
var/datum/skill/S = GLOB.skill_datums[skill]
if(!S)
return
return value
var/mod = S.base_multiplier
switch(S.progression_type)
if(SKILL_PROGRESSION_LEVEL)
mod = LAZYACCESS(skill_levels, S.type)
var/datum/skill/level/L = S
var/skill_lvl = get_skill_level(L.type)
mod += skill_lvl/(L.max_levels+max(L.competency_thresholds[threshold]-skill_lvl, 0))*L.competency_multiplier
if(SKILL_PROGRESSION_NUMERICAL)
var/datum/skill/numerical/N = S
var/skill_val = get_skill_value(N.type)
mod += skill_val/(N.max_value+max(N.competency_thresholds[threshold]-skill_val, 0))*N.competency_multiplier
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]
var/comp_threshold = S.competency_thresholds[threshold]
mod += (comp_threshold ? (get_skill_value(S.type) / comp_threshold) : get_skill_value(S.type))*S.competency_multiplier
. = modifier_is_multiplier ? value*mod : value/mod
/**
@@ -123,37 +145,42 @@
* 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
* * traits : the required traits each skill (either in I.used_skills or the skill datum skill_traits) must have to influence
* * the value.
* * bad_flags : the opposite of the above, skills that must not be present to impact the value.
* * bad_traits : the opposite of the above.
* * 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)
/datum/mind/proc/item_action_skills_mod(obj/item/I, value, traits, bad_traits, modifier_is_multiplier = TRUE)
. = value
var/sum = 0
var/divisor = 0
var/list/checked_skills
var/one_trait = istext(traits)
var/one_bad_trait = istext(bad_traits)
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))
var/our_traits = S.skill_traits
our_traits |= I.used_skills[k]
if(traits && !(one_trait ? (traits in our_traits) : length(our_traits & traits)))
continue
var/mod
if(bad_traits && (one_bad_trait ? (bad_traits in our_traits) : length(our_traits & bad_traits)))
continue
var/mod = S.base_multiplier
switch(S.progression_type)
if(SKILL_PROGRESSION_LEVEL)
mod = LAZYACCESS(skill_levels, S.type)
var/datum/skill/level/L = S
var/skill_lvl = get_skill_level(L.type)
mod += skill_lvl/(L.max_levels+max(L.competency_thresholds[I.skill_difficulty]-skill_lvl, 0))*L.competency_multiplier
if(SKILL_PROGRESSION_NUMERICAL)
var/datum/skill/numerical/N = S
var/skill_val = get_skill_value(N.type)
mod += skill_val/(N.max_value+max(N.competency_thresholds[I.skill_difficulty]-skill_val, 0))*N.competency_multiplier
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]
var/comp_threshold = S.competency_thresholds[I.skill_difficulty]
mod += (comp_threshold ? (get_skill_value(S.type) / comp_threshold) : get_skill_value(S.type))*S.competency_multiplier
sum += mod
divisor++
if(divisor)
. = modifier_is_multiplier ? value*(sum/divisor) : value/(sum/divisor)
@@ -161,11 +188,13 @@
* Generates a HTML readout of our skills.
* Port to tgui-next when?
*/
/datum/skill_holder/proc/html_readout()
/datum/mind/proc/skill_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)
for(var/path in skill_holder.skills|skill_holder.skill_value_mods|skill_holder.skill_level_mods)
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>"
var/skill_value = get_skill_value(path)
var/skill_level = get_skill_level(path, round = TRUE)
out += "<tr><td><font color='[S.name_color]'>[S.name]</font></td><td>[S.standard_render_value(skill_value, skill_level)]</td></tr>"
out += "</table>"
return out.Join("")
+204
View File
@@ -0,0 +1,204 @@
GLOBAL_LIST_EMPTY_TYPED(skill_modifiers, /datum/skill_modifier)
GLOBAL_LIST_EMPTY(potential_skills_per_mod)
GLOBAL_LIST_EMPTY(potential_mods_per_skill)
/**
* Base skill modifier datum, used to modify a player skills without directly touching their values, levels and affinity
* and cause lots of edge cases. These are fairly simple overall... make a subtype though, don't use this one.
*/
/datum/skill_modifier
/// flags for this skill modifier.
var/modifier_flags = NONE
/// target skills, can be a specific skill typepath or a list of skill traits.
var/target_skills = /datum/skill
/// the GLOB.potential_skills_per_mod key generated on runtime. You shouldn't be var-editing it.
var/target_skills_key
/// The identifier key this skill modifier is associated with.
var/identifier
/// skill affinity modifier, can be a multiplier or addendum, depending on the modifier_flags.
var/affinity_mod = 1
/// skill value modifier, see above.
var/value_mod = 1
/// skill level modifier, see above.
var/level_mod = 1
/// Priority of this skill modifier compared to other ones.
var/priority = MODIFIER_SKILL_PRIORITY_DEF
/datum/skill_modifier/New(id, register = FALSE)
identifier = GET_SKILL_MOD_ID(type, id)
if(id)
var/former_id = identifier
var/dupe = 0
while(GLOB.skill_modifiers[identifier])
identifier = "[former_id][++dupe]"
if(register)
register()
/datum/skill_modifier/proc/register()
if(GLOB.skill_modifiers[identifier])
CRASH("Skill modifier identifier \"[identifier]\" already taken.")
GLOB.skill_modifiers[identifier] = src
if(ispath(target_skills))
target_skills_key = target_skills
var/list/mod_L = GLOB.potential_mods_per_skill[target_skills]
if(!mod_L)
mod_L = GLOB.potential_mods_per_skill[target_skills] = list()
else
BINARY_INSERT(identifier, mod_L, datum/skill_modifier, src, priority, COMPARE_VALUE)
mod_L[identifier] = src
GLOB.potential_skills_per_mod[target_skills_key] = list(target_skills)
else //Should be a list.
var/list/T = target_skills
T = sortTim(target_skills, /proc/cmp_text_asc) //Sort the list contents alphabetically.
target_skills_key = T.Join("-")
var/list/L = GLOB.potential_skills_per_mod[target_skills_key]
if(!L)
L = list()
for(var/path in GLOB.skill_datums)
if(GLOB.skill_datums[path].skill_traits & target_skills)
L += path
GLOB.potential_skills_per_mod[target_skills_key] = L
for(var/path in L)
var/list/mod_L = GLOB.potential_mods_per_skill[path]
if(!mod_L)
mod_L = GLOB.potential_mods_per_skill[path] = list()
else
BINARY_INSERT(identifier, mod_L, datum/skill_modifier, src, priority, COMPARE_VALUE)
mod_L[identifier] = src
/datum/skill_modifier/Destroy()
for(var/path in GLOB.potential_skills_per_mod[target_skills_key])
var/mod_L = GLOB.potential_mods_per_skill[path]
mod_L -= identifier
if(!length(mod_L))
GLOB.potential_mods_per_skill -= path
GLOB.skill_modifiers -= identifier
return ..()
#define ADD_MOD_STEP(L, P, O, G) \
var/__L = L[P];\
if(!__L){\
L[P] = list(id)\
} else {\
L[P] = GLOB.potential_mods_per_skill[P] & (__L + id)\
}\
if(M.modifier_flags & MODIFIER_SKILL_ORIGIN_DIFF){\
LAZYADDASSOC(O, id, "[P]" = G)\
}
/datum/mind/proc/add_skill_modifier(id)
if(LAZYACCESS(skill_holder.all_current_skill_modifiers, id))
return
var/datum/skill_modifier/M = GLOB.skill_modifiers[id]
if(!M)
CRASH("Invalid add_skill_modifier id: [id].")
if(M.modifier_flags & MODIFIER_SKILL_BODYBOUND && !current)
CRASH("Body-bound skill modifier [M] was tried to be added to a mob-less mind.")
if(M.modifier_flags & MODIFIER_SKILL_VALUE)
LAZYINITLIST(skill_holder.skill_value_mods)
if(M.modifier_flags & MODIFIER_SKILL_AFFINITY)
LAZYINITLIST(skill_holder.skill_affinity_mods)
if(M.modifier_flags & MODIFIER_SKILL_LEVEL)
LAZYINITLIST(skill_holder.skill_level_mods)
for(var/path in GLOB.potential_skills_per_mod[M.target_skills_key])
if(M.modifier_flags & MODIFIER_SKILL_VALUE)
ADD_MOD_STEP(skill_holder.skill_value_mods, path, skill_holder.original_values, get_skill_value(path, FALSE))
if(M.modifier_flags & MODIFIER_SKILL_AFFINITY)
ADD_MOD_STEP(skill_holder.skill_affinity_mods, path, skill_holder.original_affinities, get_skill_affinity(path, FALSE))
if(M.modifier_flags & MODIFIER_SKILL_LEVEL)
ADD_MOD_STEP(skill_holder.skill_level_mods, path, skill_holder.original_levels, get_skill_level(path, FALSE))
LAZYSET(skill_holder.all_current_skill_modifiers, id, TRUE)
if(M.modifier_flags & MODIFIER_SKILL_BODYBOUND)
M.RegisterSignal(src, COMSIG_MIND_TRANSFER, /datum/skill_modifier.proc/on_mind_transfer)
M.RegisterSignal(current, COMSIG_MOB_ON_NEW_MIND, /datum/skill_modifier.proc/on_mob_new_mind, TRUE)
RegisterSignal(M, COMSIG_PARENT_PREQDELETED, .proc/on_skill_modifier_deletion)
#undef ADD_MOD_STEP
#define REMOVE_MOD_STEP(L, P, O)\
LAZYREMOVEASSOC(L, P, id);\
if(M.modifier_flags & MODIFIER_SKILL_ORIGIN_DIFF){\
LAZYREMOVEASSOC(O, id, "[P]")\
}
/datum/mind/proc/remove_skill_modifier(id, mind_transfer = FALSE)
if(!LAZYACCESS(skill_holder.all_current_skill_modifiers, id))
return
var/datum/skill_modifier/M = GLOB.skill_modifiers[id]
if(!M)
CRASH("Invalid remove_skill_modifier id: [id].")
if(!skill_holder.skill_value_mods && !skill_holder.skill_affinity_mods && !skill_holder.skill_level_mods)
return
for(var/path in GLOB.potential_skills_per_mod[M.target_skills_key])
if(M.modifier_flags & MODIFIER_SKILL_VALUE && skill_holder.skill_value_mods)
REMOVE_MOD_STEP(skill_holder.skill_value_mods, path, skill_holder.original_values)
if(M.modifier_flags & MODIFIER_SKILL_AFFINITY && skill_holder.skill_affinity_mods)
REMOVE_MOD_STEP(skill_holder.skill_affinity_mods, path, skill_holder.original_affinities)
if(M.modifier_flags & MODIFIER_SKILL_LEVEL && skill_holder.skill_level_mods)
REMOVE_MOD_STEP(skill_holder.skill_level_mods, path, skill_holder.original_levels)
LAZYREMOVE(skill_holder.all_current_skill_modifiers, id)
if(!mind_transfer && M.modifier_flags & MODIFIER_SKILL_BODYBOUND)
M.UnregisterSignal(src, COMSIG_MIND_TRANSFER)
M.UnregisterSignal(current, list(COMSIG_MOB_ON_NEW_MIND))
UnregisterSignal(M, COMSIG_PARENT_PREQDELETED)
#undef REMOVE_MOD_STEP
/datum/mind/proc/on_skill_modifier_deletion(datum/skill_modifier/source)
remove_skill_modifier(source.identifier)
/datum/skill_modifier/proc/apply_modifier(value, skillpath, datum/skill_holder/H, method = MODIFIER_TARGET_VALUE)
. = value
var/mod = value_mod
switch(method)
if(MODIFIER_TARGET_LEVEL)
mod = level_mod
if(MODIFIER_TARGET_AFFINITY)
mod = affinity_mod
if(modifier_flags & MODIFIER_USE_THRESHOLDS && istext(mod))
var/datum/skill/S = GLOB.skill_datums[skillpath]
if(method == MODIFIER_TARGET_VALUE && S.progression_type == SKILL_PROGRESSION_LEVEL)
var/datum/skill/level/L = S
switch(L.level_up_method)
if(STANDARD_LEVEL_UP)
mod = XP_LEVEL(L.standard_xp_lvl_up, L.xp_lvl_multiplier, S.competency_thresholds[mod])
if(DWARFY_LEVEL_UP)
mod = DORF_XP_LEVEL(L.standard_xp_lvl_up, L.xp_lvl_multiplier, S.competency_thresholds[mod])
else
mod = S.competency_thresholds[mod]
var/diff = 0
if(modifier_flags & (MODIFIER_SKILL_VIRTUE|MODIFIER_SKILL_HANDICAP))
if(modifier_flags & MODIFIER_SKILL_VIRTUE)
. = max(., mod)
if(modifier_flags & MODIFIER_SKILL_HANDICAP)
. = min(., mod)
diff = . - mod
else if(modifier_flags & MODIFIER_SKILL_MULT)
. *= mod
else
. += mod
if(modifier_flags & MODIFIER_SKILL_ORIGIN_DIFF)
var/list/to_access = H.original_values
switch(method)
if(MODIFIER_TARGET_LEVEL)
to_access = H.original_levels
if(MODIFIER_TARGET_AFFINITY)
to_access = H.original_affinities
. += value - diff - LAZYACCESS(to_access[identifier], "[skillpath]")
///Body bound modifier signal procs.
/datum/skill_modifier/proc/on_mind_transfer(datum/mind/source, mob/new_character, mob/old_character)
source.remove_skill_modifier(identifier, TRUE)
UnregisterSignal(source, COMSIG_MIND_TRANSFER)
/datum/skill_modifier/proc/on_mob_new_mind(mob/source)
source.mind.add_skill_modifier(identifier)
RegisterSignal(source.mind, COMSIG_MIND_TRANSFER, /datum/skill_modifier.proc/on_mind_transfer)
+1 -2
View File
@@ -2,5 +2,4 @@
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
skill_traits = list(SKILL_SANITY, SKILL_INTELLIGENCE, SKILL_USE_TOOL, SKILL_TRAINING_TOOL)
+1 -1
View File
@@ -2,4 +2,4 @@
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.
competency_multiplier = 1.5 // 60% surgery speed up at max value of 100, considering the base multiplier.
+35
View File
@@ -0,0 +1,35 @@
/// Jobbie skill modifiers.
/datum/skill_modifier/job
modifier_flags = MODIFIER_SKILL_VALUE|MODIFIER_SKILL_VIRTUE|MODIFIER_SKILL_ORIGIN_DIFF
priority = MODIFIER_SKILL_PRIORITY_MAX
/datum/skill_modifier/job/surgery
target_skills = /datum/skill/numerical/surgery
value_mod = STARTING_SKILL_SURGERY_MEDICAL
/datum/skill_modifier/job/affinity
modifier_flags = MODIFIER_SKILL_AFFINITY|MODIFIER_SKILL_VIRTUE
affinity_mod = STARTING_SKILL_AFFINITY_DEF_JOB
/datum/skill_modifier/job/affinity/surgery
target_skills = /datum/skill/numerical/surgery
/datum/skill_modifier/job/affinity/wiring
target_skills = /datum/skill/level/job/wiring
/// Level skill modifiers below.
/datum/skill_modifier/job/level
modifier_flags = MODIFIER_SKILL_VALUE|MODIFIER_SKILL_LEVEL|MODIFIER_SKILL_VIRTUE|MODIFIER_SKILL_ORIGIN_DIFF
level_mod = JOB_SKILL_TRAINED
/datum/skill_modifier/job/level/New(id)
if(level_mod)
value_mod = GET_STANDARD_LVL(level_mod)
..()
/datum/skill_modifier/job/level/wiring
target_skills = /datum/skill/level/job/wiring
/datum/skill_modifier/job/level/wiring/basic
level_mod = JOB_SKILL_BASIC
+8
View File
@@ -0,0 +1,8 @@
/datum/skill_modifier/bad_mood
modifier_flags = MODIFIER_SKILL_VALUE|MODIFIER_SKILL_LEVEL|MODIFIER_SKILL_MULT|MODIFIER_SKILL_BODYBOUND
target_skills = list(SKILL_SANITY)
/datum/skill_modifier/great_mood
modifier_flags = MODIFIER_SKILL_AFFINITY|MODIFIER_SKILL_MULT|MODIFIER_SKILL_BODYBOUND
target_skills = list(SKILL_SANITY)
affinity_mod = 1.2
+13
View File
@@ -0,0 +1,13 @@
/datum/skill_modifier/brain_damage
target_skills = list(SKILL_INTELLIGENCE)
modifier_flags = MODIFIER_SKILL_VALUE|MODIFIER_SKILL_AFFINITY|MODIFIER_SKILL_LEVEL|MODIFIER_SKILL_MULT|MODIFIER_SKILL_BODYBOUND
value_mod = 0.85
level_mod = 0.85
affinity_mod = 0.85
/datum/skill_modifier/heavy_brain_damage
target_skills = list(SKILL_INTELLIGENCE)
modifier_flags = MODIFIER_SKILL_VALUE|MODIFIER_SKILL_AFFINITY|MODIFIER_SKILL_LEVEL|MODIFIER_SKILL_BODYBOUND|MODIFIER_SKILL_HANDICAP|MODIFIER_USE_THRESHOLDS
priority = MODIFIER_SKILL_PRIORITY_LOW
value_mod = THRESHOLD_COMPETENT
level_mod = THRESHOLD_COMPETENT
+2
View File
@@ -120,11 +120,13 @@
/datum/status_effect/mesmerize/on_creation(mob/living/new_owner, set_duration)
. = ..()
ADD_TRAIT(owner, TRAIT_MUTE, "mesmerize")
ADD_TRAIT(owner, TRAIT_COMBAT_MODE_LOCKED, "mesmerize")
owner.add_movespeed_modifier(/datum/movespeed_modifier/status_effect/mesmerize)
/datum/status_effect/mesmerize/on_remove()
. = ..()
REMOVE_TRAIT(owner, TRAIT_MUTE, "mesmerize")
REMOVE_TRAIT(owner, TRAIT_COMBAT_MODE_LOCKED, "mesmerize")
owner.remove_movespeed_modifier(/datum/movespeed_modifier/status_effect/mesmerize)
/datum/status_effect/mesmerize/on_creation(mob/living/new_owner, set_duration)
+20
View File
@@ -0,0 +1,20 @@
/datum/tgs_event_handler/impl/HandleEvent(event_code, ...)
switch(event_code)
if(TGS_EVENT_REBOOT_MODE_CHANGE)
var/list/reboot_mode_lookup = list ("[TGS_REBOOT_MODE_NORMAL]" = "be normal", "[TGS_REBOOT_MODE_SHUTDOWN]" = "shutdown the server", "[TGS_REBOOT_MODE_RESTART]" = "hard restart the server")
var old_reboot_mode = args[2]
var new_reboot_mode = args[3]
message_admins("TGS: Reboot will no longer [reboot_mode_lookup["[old_reboot_mode]"]], it will instead [reboot_mode_lookup["[new_reboot_mode]"]]")
if(TGS_EVENT_PORT_SWAP)
message_admins("TGS: Changing port from [world.port] to [args[2]]")
if(TGS_EVENT_INSTANCE_RENAMED)
message_admins("TGS: Instance renamed to from [world.TgsInstanceName()] to [args[2]]")
if(TGS_EVENT_COMPILE_START)
message_admins("TGS: Deployment started, new game version incoming...")
if(TGS_EVENT_COMPILE_CANCELLED)
message_admins("TGS: Deployment cancelled!")
if(TGS_EVENT_COMPILE_FAILURE)
message_admins("TGS: Deployment failed!")
if(TGS_EVENT_DEPLOYMENT_COMPLETE)
message_admins("TGS: Deployment complete!")
to_chat(world, "<span class='boldannounce'>Server updated, changes will be applied on the next round...</span>")
+3 -3
View File
@@ -138,7 +138,7 @@
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)
var/level_diff = req_skill - user.mind.get_skill_level(/datum/skill/level/job/wiring, round = TRUE)
if(level_diff > 0)
LAZYSET(current_users, user, TRUE)
to_chat(user, "<span class='notice'>You begin cutting [holder]'s [color] wire...</span>")
@@ -167,7 +167,7 @@
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)
var/level_diff = req_skill - user.mind.get_skill_level(/datum/skill/level/job/wiring, round = TRUE)
if(level_diff > 0)
LAZYSET(current_users, user, TRUE)
to_chat(user, "<span class='notice'>You begin pulsing [holder]'s [color] wire...</span>")
@@ -255,7 +255,7 @@
var/reveal_wires = FALSE
// Admin ghost can see a purpose of each wire.
if(IsAdminGhost(user) || user.mind.skill_holder.get_skill_level(/datum/skill/level/job/wiring) >= req_knowledge)
if(IsAdminGhost(user) || user.mind.get_skill_level(/datum/skill/level/job/wiring) >= req_knowledge)
reveal_wires = TRUE
// Same for anyone with an abductor multitool.