Merge remote-tracking branch 'citadel/master' into combat_rework
This commit is contained in:
@@ -186,6 +186,8 @@
|
||||
|
||||
/datum/action/item_action/New(Target)
|
||||
..()
|
||||
if(button_icon_state)
|
||||
use_target_appearance = FALSE
|
||||
var/obj/item/I = target
|
||||
LAZYINITLIST(I.actions)
|
||||
I.actions += src
|
||||
@@ -345,6 +347,7 @@
|
||||
/datum/action/item_action/clock/quickbind
|
||||
name = "Quickbind"
|
||||
desc = "If you're seeing this, file a bug report."
|
||||
use_target_appearance = FALSE
|
||||
var/scripture_index = 0 //the index of the scripture we're associated with
|
||||
|
||||
/datum/action/item_action/toggle_helmet_flashlight
|
||||
|
||||
@@ -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)"
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
if(prob(2))
|
||||
switch(rand(1,2))
|
||||
if(1)
|
||||
to_chat(owner, "<i>...[lowertext(hypnotic_phrase)]...</i>")
|
||||
to_chat(owner, "<span class='hypnophrase'><i>...[lowertext(hypnotic_phrase)]...</i></span>")
|
||||
if(2)
|
||||
new /datum/hallucination/chat(owner, TRUE, FALSE, "<span class='hypnophrase'>[hypnotic_phrase]</span>")
|
||||
|
||||
|
||||
@@ -91,7 +91,6 @@
|
||||
|
||||
trauma = _trauma
|
||||
owner = trauma.owner
|
||||
copy_known_languages_from(owner, TRUE)
|
||||
|
||||
setup_friend()
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -271,3 +265,37 @@
|
||||
..()
|
||||
if(prob(1) && !owner.has_status_effect(/datum/status_effect/trance))
|
||||
owner.apply_status_effect(/datum/status_effect/trance, rand(100,300), FALSE)
|
||||
|
||||
/datum/brain_trauma/severe/hypnotic_trigger
|
||||
name = "Hypnotic Trigger"
|
||||
desc = "Patient has a trigger phrase set in their subconscious that will trigger a suggestible trance-like state."
|
||||
scan_desc = "oneiric feedback loop"
|
||||
gain_text = "<span class='warning'>You feel odd, like you just forgot something important.</span>"
|
||||
lose_text = "<span class='notice'>You feel like a weight was lifted from your mind.</span>"
|
||||
random_gain = FALSE
|
||||
var/trigger_phrase = "Nanotrasen"
|
||||
|
||||
/datum/brain_trauma/severe/hypnotic_trigger/New(phrase)
|
||||
..()
|
||||
if(phrase)
|
||||
trigger_phrase = phrase
|
||||
|
||||
/datum/brain_trauma/severe/hypnotic_trigger/on_lose() //hypnosis must be cleared separately, but brain surgery should get rid of both anyway
|
||||
..()
|
||||
owner.remove_status_effect(/datum/status_effect/trance)
|
||||
|
||||
/datum/brain_trauma/severe/hypnotic_trigger/handle_hearing(datum/source, list/hearing_args)
|
||||
if(!owner.can_hear())
|
||||
return
|
||||
if(owner == hearing_args[HEARING_SPEAKER])
|
||||
return
|
||||
|
||||
var/regex/reg = new("(\\b[REGEX_QUOTE(trigger_phrase)]\\b)","ig")
|
||||
|
||||
if(findtext(hearing_args[HEARING_RAW_MESSAGE], reg))
|
||||
addtimer(CALLBACK(src, .proc/hypnotrigger), 10) //to react AFTER the chat message
|
||||
hearing_args[HEARING_RAW_MESSAGE] = reg.Replace(hearing_args[HEARING_RAW_MESSAGE], "<span class='hypnophrase'>*********</span>")
|
||||
|
||||
/datum/brain_trauma/severe/hypnotic_trigger/proc/hypnotrigger()
|
||||
to_chat(owner, "<span class='warning'>The words trigger something deep within you, and you feel your consciousness slipping away...</span>")
|
||||
owner.apply_status_effect(/datum/status_effect/trance, rand(100,300), FALSE)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -209,10 +209,9 @@
|
||||
var/obj/item/reagent_containers/food/food_result = I
|
||||
var/total_quality = 0
|
||||
var/total_items = 0
|
||||
for(var/obj/item/ingredient in parts)
|
||||
var/obj/item/reagent_containers/food/food_ingredient = ingredient
|
||||
for(var/obj/item/reagent_containers/food/ingredient in parts)
|
||||
total_items += 1
|
||||
total_quality += food_ingredient.food_quality
|
||||
total_quality += ingredient.food_quality
|
||||
if(total_items == 0)
|
||||
food_result.adjust_food_quality(50)
|
||||
else
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -117,7 +117,7 @@
|
||||
|
||||
/datum/crafting_recipe/upgraded_gauze
|
||||
name = "Improved Gauze"
|
||||
result = /obj/item/stack/medical/gauze/adv
|
||||
result = /obj/item/stack/medical/gauze/adv/one
|
||||
time = 1
|
||||
reqs = list(/obj/item/stack/medical/gauze = 1,
|
||||
/datum/reagent/space_cleaner/sterilizine = 10)
|
||||
@@ -126,7 +126,7 @@
|
||||
|
||||
/datum/crafting_recipe/bruise_pack
|
||||
name = "Bruise Pack"
|
||||
result = /obj/item/stack/medical/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)
|
||||
@@ -134,8 +134,8 @@
|
||||
subcategory = CAT_TOOL
|
||||
|
||||
/datum/crafting_recipe/burn_pack
|
||||
name = "Brun Ointment"
|
||||
result = /obj/item/stack/medical/ointment
|
||||
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)
|
||||
@@ -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
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
/datum/component/empprotection
|
||||
var/flags = NONE
|
||||
|
||||
/datum/component/empprotection/Initialize(_flags)
|
||||
if(!istype(parent, /atom))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
flags = _flags
|
||||
RegisterSignal(parent, list(COMSIG_ATOM_EMP_ACT), .proc/getEmpFlags)
|
||||
|
||||
/datum/component/empprotection/proc/getEmpFlags(datum/source, severity)
|
||||
return flags
|
||||
@@ -9,6 +9,7 @@
|
||||
var/originalName
|
||||
var/list/affixes
|
||||
var/list/appliedComponents
|
||||
var/list/appliedElements
|
||||
|
||||
var/static/list/affixListing
|
||||
|
||||
@@ -22,6 +23,7 @@
|
||||
|
||||
src.affixes = affixes
|
||||
appliedComponents = list()
|
||||
appliedElements = list()
|
||||
randomAffixes()
|
||||
|
||||
/datum/component/fantasy/Destroy()
|
||||
@@ -118,6 +120,8 @@
|
||||
affix.remove(src)
|
||||
for(var/i in appliedComponents)
|
||||
qdel(i)
|
||||
for(var/i in appliedElements)
|
||||
master._RemoveElement(i)
|
||||
|
||||
master.force = max(0, master.force - quality)
|
||||
master.throwforce = max(0, master.throwforce - quality)
|
||||
|
||||
@@ -45,7 +45,8 @@
|
||||
|
||||
/datum/fantasy_affix/tactical/apply(datum/component/fantasy/comp, newName)
|
||||
var/obj/item/master = comp.parent
|
||||
comp.appliedComponents += master.AddComponent(/datum/component/tactical)
|
||||
master.AddElement(/datum/element/tactical)
|
||||
comp.appliedElements += list(/datum/element/tactical)
|
||||
return "tactical [newName]"
|
||||
|
||||
/datum/fantasy_affix/pyromantic
|
||||
|
||||
@@ -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 ..()
|
||||
@@ -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,20 +0,0 @@
|
||||
/datum/component/forced_gravity
|
||||
var/gravity
|
||||
var/ignore_space = FALSE //If forced gravity should also work on space turfs
|
||||
|
||||
/datum/component/forced_gravity/Initialize(forced_value = 1)
|
||||
if(!isatom(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
RegisterSignal(COMSIG_ATOM_HAS_GRAVITY, .proc/gravity_check)
|
||||
if(isturf(parent))
|
||||
RegisterSignal(COMSIG_TURF_HAS_GRAVITY, .proc/turf_gravity_check)
|
||||
|
||||
gravity = forced_value
|
||||
|
||||
/datum/component/forced_gravity/proc/gravity_check(datum/source, turf/location, list/gravs)
|
||||
if(!ignore_space && isspaceturf(location))
|
||||
return
|
||||
gravs += gravity
|
||||
|
||||
/datum/component/forced_gravity/proc/turf_gravity_check(datum/source, atom/checker, list/gravs)
|
||||
return gravity_check(parent, gravs)
|
||||
@@ -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)
|
||||
|
||||
@@ -1,40 +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.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
|
||||
@@ -126,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
|
||||
@@ -150,7 +162,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)
|
||||
|
||||
@@ -172,6 +184,7 @@
|
||||
else
|
||||
sanity = amount
|
||||
|
||||
var/old_sanity_level = sanity_level
|
||||
switch(sanity)
|
||||
if(-INFINITY to SANITY_CRAZY)
|
||||
setInsanityEffect(MAJOR_INSANITY_PEN)
|
||||
@@ -182,7 +195,7 @@
|
||||
master.add_movespeed_modifier(/datum/movespeed_modifier/sanity/crazy)
|
||||
sanity_level = 5
|
||||
if(SANITY_UNSTABLE to SANITY_DISTURBED)
|
||||
setInsanityEffect(0)
|
||||
setInsanityEffect(SLIGHT_INSANITY_PEN)
|
||||
master.add_movespeed_modifier(/datum/movespeed_modifier/sanity/disturbed)
|
||||
sanity_level = 4
|
||||
if(SANITY_DISTURBED to SANITY_NEUTRAL)
|
||||
@@ -194,19 +207,46 @@
|
||||
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(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
|
||||
|
||||
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 = -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.
|
||||
@@ -281,12 +321,16 @@
|
||||
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))
|
||||
|
||||
#undef ECSTATIC_SANITY_PEN
|
||||
#undef SLIGHT_INSANITY_PEN
|
||||
#undef MINOR_INSANITY_PEN
|
||||
#undef MAJOR_INSANITY_PEN
|
||||
#undef MOOD_INSANITY_MALUS
|
||||
|
||||
@@ -208,13 +208,13 @@
|
||||
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(/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)
|
||||
. = ..()
|
||||
|
||||
@@ -23,18 +23,12 @@
|
||||
|
||||
if(can_user_rotate)
|
||||
src.can_user_rotate = can_user_rotate
|
||||
else
|
||||
src.can_user_rotate = CALLBACK(src,.proc/default_can_user_rotate)
|
||||
|
||||
if(can_be_rotated)
|
||||
src.can_be_rotated = can_be_rotated
|
||||
else
|
||||
src.can_be_rotated = CALLBACK(src,.proc/default_can_be_rotated)
|
||||
|
||||
if(after_rotation)
|
||||
src.after_rotation = after_rotation
|
||||
else
|
||||
src.after_rotation = CALLBACK(src,.proc/default_after_rotation)
|
||||
|
||||
//Try Clockwise,counter,flip in order
|
||||
if(src.rotation_flags & ROTATION_FLIP)
|
||||
@@ -103,14 +97,34 @@
|
||||
examine_list += "<span class='notice'>Alt-click to rotate it clockwise.</span>"
|
||||
|
||||
/datum/component/simple_rotation/proc/HandRot(datum/source, mob/user, rotation = default_rotation_direction)
|
||||
if(!can_be_rotated.Invoke(user, rotation) || !can_user_rotate.Invoke(user, rotation))
|
||||
return
|
||||
if(can_be_rotated)
|
||||
if(!can_be_rotated.Invoke(user, default_rotation_direction))
|
||||
return
|
||||
else
|
||||
if(!default_can_be_rotated(user, default_rotation_direction))
|
||||
return
|
||||
if(can_user_rotate)
|
||||
if(!can_user_rotate.Invoke(user, default_rotation_direction))
|
||||
return
|
||||
else
|
||||
if(!default_can_user_rotate(user, default_rotation_direction))
|
||||
return
|
||||
BaseRot(user, rotation)
|
||||
return TRUE
|
||||
|
||||
/datum/component/simple_rotation/proc/WrenchRot(datum/source, obj/item/I, mob/living/user)
|
||||
if(!can_be_rotated.Invoke(user,default_rotation_direction) || !can_user_rotate.Invoke(user,default_rotation_direction))
|
||||
return
|
||||
if(can_be_rotated)
|
||||
if(!can_be_rotated.Invoke(user, default_rotation_direction))
|
||||
return
|
||||
else
|
||||
if(!default_can_be_rotated(user, default_rotation_direction))
|
||||
return
|
||||
if(can_user_rotate)
|
||||
if(!can_user_rotate.Invoke(user, default_rotation_direction))
|
||||
return
|
||||
else
|
||||
if(!default_can_user_rotate(user, default_rotation_direction))
|
||||
return
|
||||
if(istype(I,/obj/item/wrench))
|
||||
BaseRot(user,default_rotation_direction)
|
||||
return COMPONENT_NO_AFTERATTACK
|
||||
@@ -126,7 +140,10 @@
|
||||
if(ROTATION_FLIP)
|
||||
rot_degree = 180
|
||||
AM.setDir(turn(AM.dir,rot_degree))
|
||||
after_rotation.Invoke(user,rotation_type)
|
||||
if(after_rotation)
|
||||
after_rotation.Invoke(user, rotation_type)
|
||||
else
|
||||
default_after_rotation(user, rotation_type)
|
||||
|
||||
/datum/component/simple_rotation/proc/default_can_user_rotate(mob/living/user, rotation_type)
|
||||
if(!istype(user) || !user.canUseTopic(parent, BE_CLOSE, NO_DEXTERY))
|
||||
|
||||
@@ -136,9 +136,7 @@
|
||||
var/mob/M = parent.loc
|
||||
I.dropped(M)
|
||||
if(new_location)
|
||||
//Reset the items values
|
||||
_removal_reset(AM)
|
||||
AM.forceMove(new_location)
|
||||
AM.forceMove(new_location) // exited comsig will handle removal reset.
|
||||
//We don't want to call this if the item is being destroyed
|
||||
AM.on_exit_storage(src)
|
||||
else
|
||||
|
||||
@@ -351,10 +351,14 @@
|
||||
return master._removal_reset(thing)
|
||||
|
||||
/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)
|
||||
_removal_reset(thing) // THIS NEEDS TO HAPPEN AFTER SO LAYERING DOESN'T BREAK!
|
||||
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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
|
||||
/datum/component/tactical
|
||||
var/allowed_slot
|
||||
|
||||
/datum/component/tactical/Initialize(allowed_slot)
|
||||
if(!isitem(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
src.allowed_slot = allowed_slot
|
||||
|
||||
/datum/component/tactical/RegisterWithParent()
|
||||
. = ..()
|
||||
RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/modify)
|
||||
RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/unmodify)
|
||||
|
||||
/datum/component/tactical/UnregisterFromParent()
|
||||
. = ..()
|
||||
UnregisterSignal(parent, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED))
|
||||
unmodify()
|
||||
|
||||
/datum/component/tactical/Destroy()
|
||||
unmodify()
|
||||
return ..()
|
||||
|
||||
/datum/component/tactical/proc/modify(obj/item/source, mob/user, slot)
|
||||
if(allowed_slot && slot != allowed_slot)
|
||||
unmodify()
|
||||
return
|
||||
|
||||
var/obj/item/master = parent
|
||||
var/image/I = image(icon = master.icon, icon_state = master.icon_state, loc = user)
|
||||
I.copy_overlays(master)
|
||||
I.override = TRUE
|
||||
source.add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/everyone, "sneaking_mission", I)
|
||||
I.layer = ABOVE_MOB_LAYER
|
||||
|
||||
/datum/component/tactical/proc/unmodify(obj/item/source, mob/user)
|
||||
var/obj/item/master = source || parent
|
||||
if(!user)
|
||||
if(!ismob(master.loc))
|
||||
return
|
||||
user = master.loc
|
||||
|
||||
user.remove_alt_appearance("sneaking_mission")
|
||||
@@ -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
|
||||
@@ -126,7 +126,7 @@
|
||||
var/datum/reagent/R = E
|
||||
M.reagents.remove_reagent(R.type, actual_power)
|
||||
if(food_conversion)
|
||||
M.nutrition += 0.3
|
||||
M.adjust_nutrition(0.3)
|
||||
if(prob(2))
|
||||
to_chat(M, "<span class='notice'>You feel a mild warmth as your blood purifies itself.</span>")
|
||||
return 1
|
||||
@@ -164,7 +164,7 @@
|
||||
C.reagents.metabolize(C, can_overdose=TRUE)
|
||||
C.overeatduration = max(C.overeatduration - 2, 0)
|
||||
var/lost_nutrition = 9 - (reduced_hunger * 5)
|
||||
C.nutrition = max(C.nutrition - (lost_nutrition * HUNGER_FACTOR), 0) //Hunger depletes at 10x the normal speed
|
||||
C.adjust_nutrition(-lost_nutrition * HUNGER_FACTOR) //Hunger depletes at 10x the normal speed
|
||||
if(prob(2))
|
||||
to_chat(C, "<span class='notice'>You feel an odd gurgle in your stomach, as if it was working much faster than normal.</span>")
|
||||
return 1
|
||||
@@ -454,7 +454,7 @@
|
||||
"Transmission 6" = "Additionally heals cellular damage and toxin lovers.",
|
||||
"Resistance 7" = "Increases healing speed.",
|
||||
)
|
||||
|
||||
|
||||
/datum/symptom/heal/radiation/Start(datum/disease/advance/A)
|
||||
if(!..())
|
||||
return
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -50,4 +50,4 @@ Bonus
|
||||
else
|
||||
to_chat(M, "<span class='warning'><i>[pick("So hungry...", "You'd kill someone for a bite of food...", "Hunger cramps seize you...")]</i></span>")
|
||||
M.overeatduration = max(M.overeatduration - 100, 0)
|
||||
M.nutrition = max(M.nutrition - 100, 0)
|
||||
M.adjust_nutrition(-100)
|
||||
@@ -52,7 +52,7 @@
|
||||
if(prob(3))
|
||||
to_chat(affected_mob, "<span class='warning'><i>[pick("Your stomach silently rumbles...", "Your stomach seizes up and falls limp, muscles dead and lifeless.", "You could eat a crayon")]</i></span>")
|
||||
affected_mob.overeatduration = max(affected_mob.overeatduration - 100, 0)
|
||||
affected_mob.nutrition = max(affected_mob.nutrition - 100, 0)
|
||||
affected_mob.adjust_nutrition(-100)
|
||||
if(prob(15))
|
||||
to_chat(affected_mob, "<span class='danger'>[pick("You feel uncomfortably hot...", "You feel like unzipping your jumpsuit", "You feel like taking off some clothes...")]</span>")
|
||||
affected_mob.adjust_bodytemperature(40)
|
||||
|
||||
+21
-4
@@ -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
|
||||
@@ -152,10 +152,11 @@
|
||||
return .
|
||||
|
||||
/datum/dna/proc/generate_dna_blocks()
|
||||
var/bonus
|
||||
var/list/mutations_temp = GLOB.good_mutations + GLOB.bad_mutations + GLOB.not_good_mutations
|
||||
if(species && species.inert_mutation)
|
||||
bonus = GET_INITIALIZED_MUTATION(species.inert_mutation)
|
||||
var/list/mutations_temp = GLOB.good_mutations + GLOB.bad_mutations + GLOB.not_good_mutations + bonus
|
||||
var/bonus = GET_INITIALIZED_MUTATION(species.inert_mutation)
|
||||
if(bonus)
|
||||
mutations_temp += bonus
|
||||
if(!LAZYLEN(mutations_temp))
|
||||
return
|
||||
mutation_index.Cut()
|
||||
@@ -245,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)
|
||||
@@ -355,6 +367,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)
|
||||
..()
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,18 @@
|
||||
/datum/element/empprotection
|
||||
element_flags = ELEMENT_DETACH | ELEMENT_BESPOKE
|
||||
id_arg_index = 2
|
||||
var/flags = NONE
|
||||
|
||||
/datum/element/empprotection/Attach(datum/target, _flags)
|
||||
. = ..()
|
||||
if(. == ELEMENT_INCOMPATIBLE || !isatom(target))
|
||||
return ELEMENT_INCOMPATIBLE
|
||||
flags = _flags
|
||||
RegisterSignal(target, COMSIG_ATOM_EMP_ACT, .proc/getEmpFlags)
|
||||
|
||||
/datum/element/empprotection/Detach(atom/target)
|
||||
UnregisterSignal(target, COMSIG_ATOM_EMP_ACT)
|
||||
return ..()
|
||||
|
||||
/datum/element/empprotection/proc/getEmpFlags(datum/source, severity)
|
||||
return flags
|
||||
@@ -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] = ""
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/datum/element/forced_gravity
|
||||
element_flags = ELEMENT_BESPOKE
|
||||
id_arg_index = 2
|
||||
var/gravity
|
||||
var/ignore_space
|
||||
|
||||
/datum/element/forced_gravity/Attach(datum/target, gravity=1, ignore_space=FALSE)
|
||||
. = ..()
|
||||
if(!isatom(target))
|
||||
return ELEMENT_INCOMPATIBLE
|
||||
|
||||
src.gravity = gravity
|
||||
src.ignore_space = ignore_space
|
||||
|
||||
RegisterSignal(target, COMSIG_ATOM_HAS_GRAVITY, .proc/gravity_check)
|
||||
if(isturf(target))
|
||||
RegisterSignal(target, COMSIG_TURF_HAS_GRAVITY, .proc/turf_gravity_check)
|
||||
|
||||
/datum/element/forced_gravity/Detach(datum/source, force)
|
||||
. = ..()
|
||||
var/static/list/signals_b_gone = list(COMSIG_ATOM_HAS_GRAVITY, COMSIG_TURF_HAS_GRAVITY)
|
||||
UnregisterSignal(source, signals_b_gone)
|
||||
|
||||
/datum/element/forced_gravity/proc/gravity_check(datum/source, turf/location, list/gravs)
|
||||
if(!ignore_space && isspaceturf(location))
|
||||
return
|
||||
gravs += gravity
|
||||
|
||||
/datum/element/forced_gravity/proc/turf_gravity_check(datum/source, atom/checker, list/gravs)
|
||||
return gravity_check(null, source, gravs)
|
||||
@@ -0,0 +1,37 @@
|
||||
/datum/element/tactical
|
||||
element_flags = ELEMENT_BESPOKE|ELEMENT_DETACH
|
||||
id_arg_index = 2
|
||||
var/allowed_slot
|
||||
|
||||
/datum/element/tactical/Attach(datum/target, allowed_slot)
|
||||
. = ..()
|
||||
if(. == ELEMENT_INCOMPATIBLE || !isitem(target))
|
||||
return ELEMENT_INCOMPATIBLE
|
||||
|
||||
src.allowed_slot = allowed_slot
|
||||
RegisterSignal(target, COMSIG_ITEM_EQUIPPED, .proc/modify)
|
||||
RegisterSignal(target, COMSIG_ITEM_DROPPED, .proc/unmodify)
|
||||
|
||||
/datum/element/tactical/Detach(datum/target)
|
||||
UnregisterSignal(target, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED))
|
||||
unmodify()
|
||||
return ..()
|
||||
|
||||
/datum/element/tactical/proc/modify(obj/item/source, mob/user, slot)
|
||||
if(allowed_slot && slot != allowed_slot)
|
||||
unmodify()
|
||||
return
|
||||
|
||||
var/image/I = image(icon = source.icon, icon_state = source.icon_state, loc = user)
|
||||
I.copy_overlays(source)
|
||||
I.override = TRUE
|
||||
source.add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/everyone, "sneaking_mission", I)
|
||||
I.layer = ABOVE_MOB_LAYER
|
||||
|
||||
/datum/element/tactical/proc/unmodify(obj/item/source, mob/user)
|
||||
if(!user)
|
||||
if(!ismob(source.loc))
|
||||
return
|
||||
user = source.loc
|
||||
|
||||
user.remove_alt_appearance("sneaking_mission")
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>"))
|
||||
|
||||
@@ -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)
|
||||
. = ..()
|
||||
|
||||
+4
-6
@@ -67,7 +67,7 @@
|
||||
var/datum/skill_holder/skill_holder
|
||||
|
||||
/datum/mind/New(var/key)
|
||||
skill_holder = new
|
||||
skill_holder = new()
|
||||
src.key = key
|
||||
soulOwner = src
|
||||
martial_art = default_martial_art
|
||||
@@ -80,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
|
||||
|
||||
@@ -100,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
|
||||
@@ -130,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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>")
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -189,6 +189,12 @@
|
||||
x += mpx * (multiplier)
|
||||
y += mpy * (multiplier)
|
||||
|
||||
/datum/point/vector/proc/pixel_increment(pixels = 32, update_iteration = TRUE, realistic_iteration = FALSE)
|
||||
if(update_iteration)
|
||||
iteration += realistic_iteration? round(pixels / speed) : 1
|
||||
x += sin(angle) * pixels
|
||||
y += cos(angle) * pixels
|
||||
|
||||
/datum/point/vector/proc/return_vector_after_increments(amount = 7, multiplier = 1, force_simulate = FALSE)
|
||||
var/datum/point/vector/v = copy_to()
|
||||
if(force_simulate)
|
||||
|
||||
@@ -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()
|
||||
|
||||
+124
-18
@@ -1,4 +1,4 @@
|
||||
GLOBAL_LIST_INIT(skill_datums, init_skill_datums())
|
||||
GLOBAL_LIST_INIT_TYPED(skill_datums, /datum/skill, init_skill_datums())
|
||||
|
||||
/proc/init_skill_datums()
|
||||
. = list()
|
||||
@@ -9,31 +9,31 @@ GLOBAL_LIST_INIT(skill_datums, init_skill_datums())
|
||||
S = new path
|
||||
.[S.type] = S
|
||||
|
||||
/proc/get_skill_datum(path)
|
||||
return GLOB.skill_datums[path]
|
||||
|
||||
/proc/sanitize_skill_value(path, value)
|
||||
var/datum/skill/S = get_skill_datum(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 = get_skill_datum(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
|
||||
/// Our progression type
|
||||
/// Color of the name as shown in the html readout
|
||||
var/name_color = "#F0F0F0" // White on dark surface.
|
||||
/// 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
|
||||
/// 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.
|
||||
@@ -41,6 +41,13 @@ GLOBAL_LIST_INIT(skill_datums, init_skill_datums())
|
||||
/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
|
||||
*/
|
||||
@@ -52,7 +59,7 @@ GLOBAL_LIST_INIT(skill_datums, init_skill_datums())
|
||||
/**
|
||||
* Standard value "render"
|
||||
*/
|
||||
/datum/skill/proc/standard_render_value(value)
|
||||
/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.
|
||||
@@ -61,16 +68,18 @@ GLOBAL_LIST_INIT(skill_datums, init_skill_datums())
|
||||
/datum/skill/binary
|
||||
abstract_type = /datum/skill/binary
|
||||
progression_type = SKILL_PROGRESSION_BINARY
|
||||
competency_thresholds = list(THRESHOLD_COMPETENT = FALSE, THRESHOLD_EXPERT = TRUE, THRESHOLD_MASTER = TRUE)
|
||||
|
||||
/datum/skill/binary/sanitize_value(new_value)
|
||||
return new_value? TRUE : FALSE
|
||||
|
||||
/datum/skill/binary/standard_render_value(value)
|
||||
/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
|
||||
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
|
||||
@@ -81,7 +90,7 @@ GLOBAL_LIST_INIT(skill_datums, init_skill_datums())
|
||||
/datum/skill/numerical/sanitize_value(new_value)
|
||||
return clamp(new_value, min_value, max_value)
|
||||
|
||||
/datum/skill/numerical/standard_render_value(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
|
||||
@@ -93,3 +102,100 @@ GLOBAL_LIST_INIT(skill_datums, init_skill_datums())
|
||||
/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
|
||||
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/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 = 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(THRESHOLD_COMPETENT = JOB_SKILL_TRAINED, THRESHOLD_EXPERT = JOB_SKILL_EXPERT, THRESHOLD_MASTER = JOB_SKILL_MASTER)
|
||||
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(THRESHOLD_COMPETENT = DORF_SKILL_COMPETENT, THRESHOLD_EXPERT = DORF_SKILL_EXPERT, THRESHOLD_MASTER = DORF_SKILL_MASTER)
|
||||
associative = TRUE
|
||||
unskilled_tier = "Dabbling"
|
||||
|
||||
@@ -1,78 +1,200 @@
|
||||
/**
|
||||
* Skill holder datums
|
||||
* All procs are tied to the mind, since they are always expected to have a skill holder anyway.
|
||||
*/
|
||||
/datum/skill_holder
|
||||
/// 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
|
||||
/// 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 LAZYACCESS(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/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_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)
|
||||
if(!ispath(skill))
|
||||
CRASH("Invalid get_skill_affinity call. Use typepaths.") //until a time when we somehow need text ids for dynamic skills, I'm enforcing this.
|
||||
if(!skills)
|
||||
return 1
|
||||
var/affinity = LAZYACCESS(skill_affinities, skill)
|
||||
if(isnull(affinity))
|
||||
return 1
|
||||
return affinity
|
||||
/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_holder.skill_affinities, skill)
|
||||
if(!isnull(affinity))
|
||||
. = affinity
|
||||
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)
|
||||
if(!ispath(skill))
|
||||
CRASH("Invalid set_skill_value call. Use typepaths.") //until a time when we somehow need text ids for dynamic skills, I'm enforcing this.
|
||||
LAZYINITLIST(skills)
|
||||
value = sanitize_skill_value(skill, value)
|
||||
/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))
|
||||
LAZYSET(skills, skill, value)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
if(!ispath(skill, /datum/skill/numerical))
|
||||
CRASH("You cannot auto increment a non numerical skill!")
|
||||
var/current = get_skill_value(skill)
|
||||
/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, FALSE)
|
||||
var/affinity = get_skill_affinity(skill)
|
||||
boost_skill_value_to(skill, current + (value * affinity))
|
||||
var/target_value = current + (value * affinity)
|
||||
if(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, current)
|
||||
|
||||
/**
|
||||
* 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/mind/proc/action_skill_mod(skill, value, threshold, modifier_is_multiplier = TRUE)
|
||||
var/datum/skill/S = GLOB.skill_datums[skill]
|
||||
if(!S)
|
||||
return value
|
||||
var/mod = S.base_multiplier
|
||||
switch(S.progression_type)
|
||||
if(SKILL_PROGRESSION_LEVEL)
|
||||
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
|
||||
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
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* * traits : the required traits each skill (either in I.used_skills or the skill datum skill_traits) must have to influence
|
||||
* * the value.
|
||||
* * bad_traits : the opposite of the above.
|
||||
* * modifier_is_multiplier : wheter the modifier is a multiplier or a divisor.
|
||||
*/
|
||||
/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/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_traits = S.skill_traits
|
||||
our_traits |= I.used_skills[k]
|
||||
if(traits && !(one_trait ? (traits in our_traits) : length(our_traits & traits)))
|
||||
continue
|
||||
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)
|
||||
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
|
||||
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)
|
||||
|
||||
/**
|
||||
* 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>[S.name]</td><td>[S.standard_render_value(skills[path])]</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("")
|
||||
|
||||
@@ -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)
|
||||
@@ -0,0 +1,5 @@
|
||||
/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
|
||||
skill_traits = list(SKILL_SANITY, SKILL_INTELLIGENCE, SKILL_USE_TOOL, SKILL_TRAINING_TOOL)
|
||||
@@ -1,3 +1,5 @@
|
||||
/datum/skill/numerical/surgery
|
||||
name = "Surgery"
|
||||
desc = "How proficient you are at doing surgery."
|
||||
name_color = COLOR_PALE_BLUE_GRAY
|
||||
competency_multiplier = 1.5 // 60% surgery speed up at max value of 100, considering the base multiplier.
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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>")
|
||||
@@ -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.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>")
|
||||
if(!do_after(user, 0.75 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.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>")
|
||||
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.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,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(
|
||||
|
||||
@@ -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,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,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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,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,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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,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(
|
||||
|
||||
Reference in New Issue
Block a user