Force LF line endings with gitattributes and convert repo (#52266)

Co-authored-by: Aleksej Komarov <stylemistake@gmail.com>
This commit is contained in:
jdawg1290
2020-07-16 03:02:40 +03:00
committed by GitHub
co-authored by Aleksej Komarov
parent 39e8bd721f
commit 62676e72a8
1133 changed files with 266492 additions and 266454 deletions
+9 -9
View File
@@ -1,9 +1,9 @@
# Datum Component System (DCS)
## Concept
Loosely adapted from /vg/. This is an entity component system for adding behaviours to datums when inheritance doesn't quite cut it. By using signals and events instead of direct inheritance, you can inject behaviours without hacky overloads. It requires a different method of thinking, but is not hard to use correctly. If a behaviour can have application across more than one thing. Make it generic, make it a component. Atom/mob/obj event? Give it a signal, and forward it's arguments with a `SendSignal()` call. Now every component that want's to can also know about this happening.
See [this thread](https://tgstation13.org/phpBB/viewtopic.php?f=5&t=22674) for an introduction to the system as a whole.
### See/Define signals and their arguments in [__DEFINES\components.dm](../../__DEFINES/components.dm)
# Datum Component System (DCS)
## Concept
Loosely adapted from /vg/. This is an entity component system for adding behaviours to datums when inheritance doesn't quite cut it. By using signals and events instead of direct inheritance, you can inject behaviours without hacky overloads. It requires a different method of thinking, but is not hard to use correctly. If a behaviour can have application across more than one thing. Make it generic, make it a component. Atom/mob/obj event? Give it a signal, and forward it's arguments with a `SendSignal()` call. Now every component that want's to can also know about this happening.
See [this thread](https://tgstation13.org/phpBB/viewtopic.php?f=5&t=22674) for an introduction to the system as a whole.
### See/Define signals and their arguments in [__DEFINES\components.dm](../../__DEFINES/components.dm)
File diff suppressed because it is too large Load Diff
+62 -62
View File
@@ -1,62 +1,62 @@
GLOBAL_LIST_INIT(creamable, typecacheof(list(
/mob/living/carbon/human,
/mob/living/carbon/monkey,
/mob/living/simple_animal/pet/dog/corgi,
/mob/living/silicon/ai)))
/**
* Creamed component
*
* For when you have pie on your face
*/
/datum/component/creamed
dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
var/mutable_appearance/creamface
/datum/component/creamed/Initialize()
if(!is_type_in_typecache(parent, GLOB.creamable))
return COMPONENT_INCOMPATIBLE
creamface = mutable_appearance('icons/effects/creampie.dmi')
if(ishuman(parent))
var/mob/living/carbon/human/H = parent
if(H.dna.species.limbs_id == "lizard")
creamface.icon_state = "creampie_lizard"
else
creamface.icon_state = "creampie_human"
SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "creampie", /datum/mood_event/creampie)
else if(ismonkey(parent))
creamface.icon_state = "creampie_monkey"
else if(iscorgi(parent))
creamface.icon_state = "creampie_corgi"
else if(isAI(parent))
creamface.icon_state = "creampie_ai"
var/atom/A = parent
A.add_overlay(creamface)
/datum/component/creamed/Destroy(force, silent)
var/atom/A = parent
A.cut_overlay(creamface)
qdel(creamface)
if(ishuman(A))
SEND_SIGNAL(A, COMSIG_CLEAR_MOOD_EVENT, "creampie")
return ..()
/datum/component/creamed/RegisterWithParent()
RegisterSignal(parent, list(
COMSIG_COMPONENT_CLEAN_ACT,
COMSIG_COMPONENT_CLEAN_FACE_ACT),
.proc/clean_up)
/datum/component/creamed/UnregisterFromParent()
UnregisterSignal(parent, list(
COMSIG_COMPONENT_CLEAN_ACT,
COMSIG_COMPONENT_CLEAN_FACE_ACT))
///Callback to remove pieface
/datum/component/creamed/proc/clean_up(datum/source, strength)
if(strength >= CLEAN_WEAK)
qdel(src)
GLOBAL_LIST_INIT(creamable, typecacheof(list(
/mob/living/carbon/human,
/mob/living/carbon/monkey,
/mob/living/simple_animal/pet/dog/corgi,
/mob/living/silicon/ai)))
/**
* Creamed component
*
* For when you have pie on your face
*/
/datum/component/creamed
dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
var/mutable_appearance/creamface
/datum/component/creamed/Initialize()
if(!is_type_in_typecache(parent, GLOB.creamable))
return COMPONENT_INCOMPATIBLE
creamface = mutable_appearance('icons/effects/creampie.dmi')
if(ishuman(parent))
var/mob/living/carbon/human/H = parent
if(H.dna.species.limbs_id == "lizard")
creamface.icon_state = "creampie_lizard"
else
creamface.icon_state = "creampie_human"
SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "creampie", /datum/mood_event/creampie)
else if(ismonkey(parent))
creamface.icon_state = "creampie_monkey"
else if(iscorgi(parent))
creamface.icon_state = "creampie_corgi"
else if(isAI(parent))
creamface.icon_state = "creampie_ai"
var/atom/A = parent
A.add_overlay(creamface)
/datum/component/creamed/Destroy(force, silent)
var/atom/A = parent
A.cut_overlay(creamface)
qdel(creamface)
if(ishuman(A))
SEND_SIGNAL(A, COMSIG_CLEAR_MOOD_EVENT, "creampie")
return ..()
/datum/component/creamed/RegisterWithParent()
RegisterSignal(parent, list(
COMSIG_COMPONENT_CLEAN_ACT,
COMSIG_COMPONENT_CLEAN_FACE_ACT),
.proc/clean_up)
/datum/component/creamed/UnregisterFromParent()
UnregisterSignal(parent, list(
COMSIG_COMPONENT_CLEAN_ACT,
COMSIG_COMPONENT_CLEAN_FACE_ACT))
///Callback to remove pieface
/datum/component/creamed/proc/clean_up(datum/source, strength)
if(strength >= CLEAN_WEAK)
qdel(src)
+39 -39
View File
@@ -1,39 +1,39 @@
/datum/component/decal/blood
dupe_mode = COMPONENT_DUPE_UNIQUE
/datum/component/decal/blood/Initialize(_icon, _icon_state, _dir, _cleanable=CLEAN_STRENGTH_BLOOD, _color, _layer=ABOVE_OBJ_LAYER)
if(!isitem(parent))
return COMPONENT_INCOMPATIBLE
. = ..()
RegisterSignal(parent, COMSIG_ATOM_GET_EXAMINE_NAME, .proc/get_examine_name)
/datum/component/decal/blood/generate_appearance(_icon, _icon_state, _dir, _layer, _color)
var/obj/item/I = parent
if(!_icon)
_icon = 'icons/effects/blood.dmi'
if(!_icon_state)
_icon_state = "itemblood"
var/icon = initial(I.icon)
var/icon_state = initial(I.icon_state)
if(!icon || !icon_state)
// It's something which takes on the look of other items, probably
icon = I.icon
icon_state = I.icon_state
var/static/list/blood_splatter_appearances = list()
//try to find a pre-processed blood-splatter. otherwise, make a new one
var/index = "[REF(icon)]-[icon_state]"
pic = blood_splatter_appearances[index]
if(!pic)
var/icon/blood_splatter_icon = icon(initial(I.icon), initial(I.icon_state), , 1) //we only want to apply blood-splatters to the initial icon_state for each object
blood_splatter_icon.Blend("#fff", ICON_ADD) //fills the icon_state with white (except where it's transparent)
blood_splatter_icon.Blend(icon(_icon, _icon_state), ICON_MULTIPLY) //adds blood and the remaining white areas become transparant
pic = mutable_appearance(blood_splatter_icon, initial(I.icon_state))
blood_splatter_appearances[index] = pic
return TRUE
/datum/component/decal/blood/proc/get_examine_name(datum/source, mob/user, list/override)
var/atom/A = parent
override[EXAMINE_POSITION_ARTICLE] = A.gender == PLURAL? "some" : "a"
override[EXAMINE_POSITION_BEFORE] = " blood-stained "
return COMPONENT_EXNAME_CHANGED
/datum/component/decal/blood
dupe_mode = COMPONENT_DUPE_UNIQUE
/datum/component/decal/blood/Initialize(_icon, _icon_state, _dir, _cleanable=CLEAN_STRENGTH_BLOOD, _color, _layer=ABOVE_OBJ_LAYER)
if(!isitem(parent))
return COMPONENT_INCOMPATIBLE
. = ..()
RegisterSignal(parent, COMSIG_ATOM_GET_EXAMINE_NAME, .proc/get_examine_name)
/datum/component/decal/blood/generate_appearance(_icon, _icon_state, _dir, _layer, _color)
var/obj/item/I = parent
if(!_icon)
_icon = 'icons/effects/blood.dmi'
if(!_icon_state)
_icon_state = "itemblood"
var/icon = initial(I.icon)
var/icon_state = initial(I.icon_state)
if(!icon || !icon_state)
// It's something which takes on the look of other items, probably
icon = I.icon
icon_state = I.icon_state
var/static/list/blood_splatter_appearances = list()
//try to find a pre-processed blood-splatter. otherwise, make a new one
var/index = "[REF(icon)]-[icon_state]"
pic = blood_splatter_appearances[index]
if(!pic)
var/icon/blood_splatter_icon = icon(initial(I.icon), initial(I.icon_state), , 1) //we only want to apply blood-splatters to the initial icon_state for each object
blood_splatter_icon.Blend("#fff", ICON_ADD) //fills the icon_state with white (except where it's transparent)
blood_splatter_icon.Blend(icon(_icon, _icon_state), ICON_MULTIPLY) //adds blood and the remaining white areas become transparant
pic = mutable_appearance(blood_splatter_icon, initial(I.icon_state))
blood_splatter_appearances[index] = pic
return TRUE
/datum/component/decal/blood/proc/get_examine_name(datum/source, mob/user, list/override)
var/atom/A = parent
override[EXAMINE_POSITION_ARTICLE] = A.gender == PLURAL? "some" : "a"
override[EXAMINE_POSITION_BEFORE] = " blood-stained "
return COMPONENT_EXNAME_CHANGED
+247 -247
View File
@@ -1,247 +1,247 @@
/*!
This component makes it possible to make things edible. What this means is that you can take a bite or force someone to take a bite (in the case of items).
These items take a specific time to eat, and can do most of the things our original food items could.
Behavior that's still missing from this component that original food items had that should either be put into seperate components or somewhere else:
Components:
Drying component (jerky etc)
Customizable component (custom pizzas etc)
Processable component (Slicing and cooking behavior essentialy, making it go from item A to B when conditions are met.)
Dunkable component (Dunking things into reagent containers to absorb a specific amount of reagents)
Misc:
Something for cakes (You can store things inside)
*/
/datum/component/edible
///Amount of reagents taken per bite
var/bite_consumption = 2
///Amount of bites taken so far
var/bitecount = 0
///Flags for food
var/food_flags = NONE
///Bitfield of the types of this food
var/foodtypes = NONE
///Amount of seconds it takes to eat this food
var/eat_time = 30
///Defines how much it lowers someones satiety (Need to eat, essentialy)
var/junkiness = 0
///Message to send when eating
var/list/eatverbs
///Callback to be ran for when you take a bite of something
var/datum/callback/after_eat
///Last time we checked for food likes
var/last_check_time
/datum/component/edible/Initialize(list/initial_reagents, food_flags = NONE, foodtypes = NONE, volume = 50, eat_time = 30, list/tastes, list/eatverbs = list("bite","chew","nibble","gnaw","gobble","chomp"), bite_consumption = 2, datum/callback/after_eat)
if(!isatom(parent))
return COMPONENT_INCOMPATIBLE
RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/examine)
RegisterSignal(parent, COMSIG_ATOM_ATTACK_ANIMAL, .proc/UseByAnimal)
if(isitem(parent))
RegisterSignal(parent, COMSIG_ITEM_ATTACK, .proc/UseFromHand)
else if(isturf(parent))
RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, .proc/TryToEatTurf)
src.bite_consumption = bite_consumption
src.food_flags = food_flags
src.foodtypes = foodtypes
src.eat_time = eat_time
src.eatverbs = eatverbs
src.junkiness = junkiness
src.after_eat = after_eat
var/atom/owner = parent
owner.create_reagents(volume, INJECTABLE)
if(initial_reagents)
for(var/rid in initial_reagents)
var/amount = initial_reagents[rid]
if(tastes && tastes.len && (rid == /datum/reagent/consumable/nutriment || rid == /datum/reagent/consumable/nutriment/vitamin))
owner.reagents.add_reagent(rid, amount, tastes.Copy())
else
owner.reagents.add_reagent(rid, amount)
/datum/component/edible/proc/examine(datum/source, mob/user, list/examine_list)
if(!(food_flags & FOOD_IN_CONTAINER))
switch (bitecount)
if (0)
return
if(1)
examine_list += "[parent] was bitten by someone!"
if(2,3)
examine_list += "[parent] was bitten [bitecount] times!"
else
examine_list += "[parent] was bitten multiple times!"
/datum/component/edible/proc/UseFromHand(obj/item/source, mob/living/M, mob/living/user)
return TryToEat(M, user)
/datum/component/edible/proc/TryToEatTurf(datum/source, mob/user)
return TryToEat(user, user)
///All the checks for the act of eating itself and
/datum/component/edible/proc/TryToEat(mob/living/eater, mob/living/feeder)
set waitfor = FALSE
var/atom/owner = parent
if(feeder.a_intent == INTENT_HARM)
return
if(!owner.reagents.total_volume)//Shouldn't be needed but it checks to see if it has anything left in it.
to_chat(feeder, "<span class='warning'>None of [owner] left, oh no!</span>")
if(isturf(parent))
var/turf/T = parent
T.ScrapeAway(1, CHANGETURF_INHERIT_AIR)
else
qdel(parent)
return
if(!CanConsume(eater, feeder))
return
var/fullness = eater.nutrition + 10 //The theoretical fullness of the person eating if they were to eat this
for(var/datum/reagent/consumable/C in eater.reagents.reagent_list) //we add the nutrition value of what we're currently digesting
fullness += C.nutriment_factor * C.volume / C.metabolization_rate
. = COMPONENT_ITEM_NO_ATTACK //Point of no return I suppose
if(eater == feeder)//If you're eating it yourself.
if(!do_mob(feeder, eater, eat_time)) //Gotta pass the minimal eat time
return
var/eatverb = pick(eatverbs)
if(junkiness && eater.satiety < -150 && eater.nutrition > NUTRITION_LEVEL_STARVING + 50 && !HAS_TRAIT(eater, TRAIT_VORACIOUS))
to_chat(eater, "<span class='warning'>You don't feel like eating any more junk food at the moment!</span>")
return
else if(fullness <= 50)
eater.visible_message("<span class='notice'>[eater] hungrily [eatverb]s \the [parent], gobbling it down!</span>", "<span class='notice'>You hungrily [eatverb] \the [parent], gobbling it down!</span>")
else if(fullness > 50 && fullness < 150)
eater.visible_message("<span class='notice'>[eater] hungrily [eatverb]s \the [parent].</span>", "<span class='notice'>You hungrily [eatverb] \the [parent].</span>")
else if(fullness > 150 && fullness < 500)
eater.visible_message("<span class='notice'>[eater] [eatverb]s \the [parent].</span>", "<span class='notice'>You [eatverb] \the [parent].</span>")
else if(fullness > 500 && fullness < 600)
eater.visible_message("<span class='notice'>[eater] unwillingly [eatverb]s a bit of \the [parent].</span>", "<span class='notice'>You unwillingly [eatverb] a bit of \the [parent].</span>")
else if(fullness > (600 * (1 + eater.overeatduration / 2000))) // The more you eat - the more you can eat
eater.visible_message("<span class='warning'>[eater] cannot force any more of \the [parent] to go down [eater.p_their()] throat!</span>", "<span class='warning'>You cannot force any more of \the [parent] to go down your throat!</span>")
return
else //If you're feeding it to someone else.
if(isbrain(eater))
to_chat(feeder, "<span class='warning'>[eater] doesn't seem to have a mouth!</span>")
return
if(fullness <= (600 * (1 + eater.overeatduration / 1000)))
eater.visible_message("<span class='danger'>[feeder] attempts to feed [eater] [parent].</span>", \
"<span class='userdanger'>[feeder] attempts to feed you [parent].</span>")
else
eater.visible_message("<span class='warning'>[feeder] cannot force any more of [parent] down [eater]'s throat!</span>", \
"<span class='warning'>[feeder] cannot force any more of [parent] down your throat!</span>")
return
if(!do_mob(feeder, eater)) //Wait 3 seconds before you can feed
return
log_combat(feeder, eater, "fed", owner.reagents.log_list())
eater.visible_message("<span class='danger'>[feeder] forces [eater] to eat [parent]!</span>", \
"<span class='userdanger'>[feeder] forces you to eat [parent]!</span>")
TakeBite(eater, feeder)
///This function lets the eater take a bite and transfers the reagents to the eater.
/datum/component/edible/proc/TakeBite(mob/living/eater, mob/living/feeder)
var/atom/owner = parent
if(!owner?.reagents)
return FALSE
if(eater.satiety > -200)
eater.satiety -= junkiness
playsound(eater.loc,'sound/items/eatfood.ogg', rand(10,50), TRUE)
if(owner.reagents.total_volume)
SEND_SIGNAL(parent, COMSIG_FOOD_EATEN, eater, feeder)
var/fraction = min(bite_consumption / owner.reagents.total_volume, 1)
owner.reagents.trans_to(eater, bite_consumption, transfered_by = feeder, method = INGEST)
bitecount++
On_Consume(eater)
checkLiked(fraction, eater)
//Invoke our after eat callback if it is valid
if(after_eat)
after_eat.Invoke(eater, feeder)
return TRUE
///Checks whether or not the eater can actually consume the food
/datum/component/edible/proc/CanConsume(mob/living/eater, mob/living/feeder)
if(!iscarbon(eater))
return FALSE
var/mob/living/carbon/C = eater
var/covered = ""
if(C.is_mouth_covered(head_only = 1))
covered = "headgear"
else if(C.is_mouth_covered(mask_only = 1))
covered = "mask"
if(covered)
var/who = (isnull(feeder) || eater == feeder) ? "your" : "[eater.p_their()]"
to_chat(feeder, "<span class='warning'>You have to remove [who] [covered] first!</span>")
return FALSE
return TRUE
///Check foodtypes to see if we should send a moodlet
/datum/component/edible/proc/checkLiked(var/fraction, mob/M)
if(last_check_time + 50 > world.time)
return FALSE
if(!ishuman(M))
return FALSE
var/mob/living/carbon/human/H = M
if(HAS_TRAIT(H, TRAIT_AGEUSIA) && foodtypes & H.dna.species.toxic_food)
to_chat(H, "<span class='warning'>You don't feel so good...</span>")
H.adjust_disgust(25 + 30 * fraction)
else
if(foodtypes & H.dna.species.toxic_food)
to_chat(H,"<span class='warning'>What the hell was that thing?!</span>")
H.adjust_disgust(25 + 30 * fraction)
SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "toxic_food", /datum/mood_event/disgusting_food)
else if(foodtypes & H.dna.species.disliked_food)
to_chat(H,"<span class='notice'>That didn't taste very good...</span>")
H.adjust_disgust(11 + 15 * fraction)
SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "gross_food", /datum/mood_event/gross_food)
else if(foodtypes & H.dna.species.liked_food)
to_chat(H,"<span class='notice'>I love this taste!</span>")
H.adjust_disgust(-5 + -2.5 * fraction)
SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "fav_food", /datum/mood_event/favorite_food)
if((foodtypes & BREAKFAST) && world.time - SSticker.round_start_time < STOP_SERVING_BREAKFAST)
SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "breakfast", /datum/mood_event/breakfast)
last_check_time = world.time
///Delete the item when it is fully eaten
/datum/component/edible/proc/On_Consume(mob/living/eater)
var/atom/owner = parent
if(!eater)
return
if(!owner.reagents.total_volume)
if(isturf(parent))
var/turf/T = parent
T.ScrapeAway(1, CHANGETURF_INHERIT_AIR)
else
qdel(parent)
///Ability to feed food to puppers
/datum/component/edible/proc/UseByAnimal(datum/source, mob/user)
var/atom/owner = parent
if(!isdog(user))
return
var/mob/living/L = user
if(bitecount == 0 || prob(50))
L.emote("me", 1, "nibbles away at \the [parent]")
bitecount++
. = COMPONENT_ITEM_NO_ATTACK
L.taste(owner.reagents) // why should carbons get all the fun?
if(bitecount >= 5)
var/sattisfaction_text = pick("burps from enjoyment", "yaps for more", "woofs twice", "looks at the area where \the [parent] was")
if(sattisfaction_text)
L.emote("me", 1, "[sattisfaction_text]")
qdel(parent)
/*!
This component makes it possible to make things edible. What this means is that you can take a bite or force someone to take a bite (in the case of items).
These items take a specific time to eat, and can do most of the things our original food items could.
Behavior that's still missing from this component that original food items had that should either be put into seperate components or somewhere else:
Components:
Drying component (jerky etc)
Customizable component (custom pizzas etc)
Processable component (Slicing and cooking behavior essentialy, making it go from item A to B when conditions are met.)
Dunkable component (Dunking things into reagent containers to absorb a specific amount of reagents)
Misc:
Something for cakes (You can store things inside)
*/
/datum/component/edible
///Amount of reagents taken per bite
var/bite_consumption = 2
///Amount of bites taken so far
var/bitecount = 0
///Flags for food
var/food_flags = NONE
///Bitfield of the types of this food
var/foodtypes = NONE
///Amount of seconds it takes to eat this food
var/eat_time = 30
///Defines how much it lowers someones satiety (Need to eat, essentialy)
var/junkiness = 0
///Message to send when eating
var/list/eatverbs
///Callback to be ran for when you take a bite of something
var/datum/callback/after_eat
///Last time we checked for food likes
var/last_check_time
/datum/component/edible/Initialize(list/initial_reagents, food_flags = NONE, foodtypes = NONE, volume = 50, eat_time = 30, list/tastes, list/eatverbs = list("bite","chew","nibble","gnaw","gobble","chomp"), bite_consumption = 2, datum/callback/after_eat)
if(!isatom(parent))
return COMPONENT_INCOMPATIBLE
RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/examine)
RegisterSignal(parent, COMSIG_ATOM_ATTACK_ANIMAL, .proc/UseByAnimal)
if(isitem(parent))
RegisterSignal(parent, COMSIG_ITEM_ATTACK, .proc/UseFromHand)
else if(isturf(parent))
RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, .proc/TryToEatTurf)
src.bite_consumption = bite_consumption
src.food_flags = food_flags
src.foodtypes = foodtypes
src.eat_time = eat_time
src.eatverbs = eatverbs
src.junkiness = junkiness
src.after_eat = after_eat
var/atom/owner = parent
owner.create_reagents(volume, INJECTABLE)
if(initial_reagents)
for(var/rid in initial_reagents)
var/amount = initial_reagents[rid]
if(tastes && tastes.len && (rid == /datum/reagent/consumable/nutriment || rid == /datum/reagent/consumable/nutriment/vitamin))
owner.reagents.add_reagent(rid, amount, tastes.Copy())
else
owner.reagents.add_reagent(rid, amount)
/datum/component/edible/proc/examine(datum/source, mob/user, list/examine_list)
if(!(food_flags & FOOD_IN_CONTAINER))
switch (bitecount)
if (0)
return
if(1)
examine_list += "[parent] was bitten by someone!"
if(2,3)
examine_list += "[parent] was bitten [bitecount] times!"
else
examine_list += "[parent] was bitten multiple times!"
/datum/component/edible/proc/UseFromHand(obj/item/source, mob/living/M, mob/living/user)
return TryToEat(M, user)
/datum/component/edible/proc/TryToEatTurf(datum/source, mob/user)
return TryToEat(user, user)
///All the checks for the act of eating itself and
/datum/component/edible/proc/TryToEat(mob/living/eater, mob/living/feeder)
set waitfor = FALSE
var/atom/owner = parent
if(feeder.a_intent == INTENT_HARM)
return
if(!owner.reagents.total_volume)//Shouldn't be needed but it checks to see if it has anything left in it.
to_chat(feeder, "<span class='warning'>None of [owner] left, oh no!</span>")
if(isturf(parent))
var/turf/T = parent
T.ScrapeAway(1, CHANGETURF_INHERIT_AIR)
else
qdel(parent)
return
if(!CanConsume(eater, feeder))
return
var/fullness = eater.nutrition + 10 //The theoretical fullness of the person eating if they were to eat this
for(var/datum/reagent/consumable/C in eater.reagents.reagent_list) //we add the nutrition value of what we're currently digesting
fullness += C.nutriment_factor * C.volume / C.metabolization_rate
. = COMPONENT_ITEM_NO_ATTACK //Point of no return I suppose
if(eater == feeder)//If you're eating it yourself.
if(!do_mob(feeder, eater, eat_time)) //Gotta pass the minimal eat time
return
var/eatverb = pick(eatverbs)
if(junkiness && eater.satiety < -150 && eater.nutrition > NUTRITION_LEVEL_STARVING + 50 && !HAS_TRAIT(eater, TRAIT_VORACIOUS))
to_chat(eater, "<span class='warning'>You don't feel like eating any more junk food at the moment!</span>")
return
else if(fullness <= 50)
eater.visible_message("<span class='notice'>[eater] hungrily [eatverb]s \the [parent], gobbling it down!</span>", "<span class='notice'>You hungrily [eatverb] \the [parent], gobbling it down!</span>")
else if(fullness > 50 && fullness < 150)
eater.visible_message("<span class='notice'>[eater] hungrily [eatverb]s \the [parent].</span>", "<span class='notice'>You hungrily [eatverb] \the [parent].</span>")
else if(fullness > 150 && fullness < 500)
eater.visible_message("<span class='notice'>[eater] [eatverb]s \the [parent].</span>", "<span class='notice'>You [eatverb] \the [parent].</span>")
else if(fullness > 500 && fullness < 600)
eater.visible_message("<span class='notice'>[eater] unwillingly [eatverb]s a bit of \the [parent].</span>", "<span class='notice'>You unwillingly [eatverb] a bit of \the [parent].</span>")
else if(fullness > (600 * (1 + eater.overeatduration / 2000))) // The more you eat - the more you can eat
eater.visible_message("<span class='warning'>[eater] cannot force any more of \the [parent] to go down [eater.p_their()] throat!</span>", "<span class='warning'>You cannot force any more of \the [parent] to go down your throat!</span>")
return
else //If you're feeding it to someone else.
if(isbrain(eater))
to_chat(feeder, "<span class='warning'>[eater] doesn't seem to have a mouth!</span>")
return
if(fullness <= (600 * (1 + eater.overeatduration / 1000)))
eater.visible_message("<span class='danger'>[feeder] attempts to feed [eater] [parent].</span>", \
"<span class='userdanger'>[feeder] attempts to feed you [parent].</span>")
else
eater.visible_message("<span class='warning'>[feeder] cannot force any more of [parent] down [eater]'s throat!</span>", \
"<span class='warning'>[feeder] cannot force any more of [parent] down your throat!</span>")
return
if(!do_mob(feeder, eater)) //Wait 3 seconds before you can feed
return
log_combat(feeder, eater, "fed", owner.reagents.log_list())
eater.visible_message("<span class='danger'>[feeder] forces [eater] to eat [parent]!</span>", \
"<span class='userdanger'>[feeder] forces you to eat [parent]!</span>")
TakeBite(eater, feeder)
///This function lets the eater take a bite and transfers the reagents to the eater.
/datum/component/edible/proc/TakeBite(mob/living/eater, mob/living/feeder)
var/atom/owner = parent
if(!owner?.reagents)
return FALSE
if(eater.satiety > -200)
eater.satiety -= junkiness
playsound(eater.loc,'sound/items/eatfood.ogg', rand(10,50), TRUE)
if(owner.reagents.total_volume)
SEND_SIGNAL(parent, COMSIG_FOOD_EATEN, eater, feeder)
var/fraction = min(bite_consumption / owner.reagents.total_volume, 1)
owner.reagents.trans_to(eater, bite_consumption, transfered_by = feeder, method = INGEST)
bitecount++
On_Consume(eater)
checkLiked(fraction, eater)
//Invoke our after eat callback if it is valid
if(after_eat)
after_eat.Invoke(eater, feeder)
return TRUE
///Checks whether or not the eater can actually consume the food
/datum/component/edible/proc/CanConsume(mob/living/eater, mob/living/feeder)
if(!iscarbon(eater))
return FALSE
var/mob/living/carbon/C = eater
var/covered = ""
if(C.is_mouth_covered(head_only = 1))
covered = "headgear"
else if(C.is_mouth_covered(mask_only = 1))
covered = "mask"
if(covered)
var/who = (isnull(feeder) || eater == feeder) ? "your" : "[eater.p_their()]"
to_chat(feeder, "<span class='warning'>You have to remove [who] [covered] first!</span>")
return FALSE
return TRUE
///Check foodtypes to see if we should send a moodlet
/datum/component/edible/proc/checkLiked(var/fraction, mob/M)
if(last_check_time + 50 > world.time)
return FALSE
if(!ishuman(M))
return FALSE
var/mob/living/carbon/human/H = M
if(HAS_TRAIT(H, TRAIT_AGEUSIA) && foodtypes & H.dna.species.toxic_food)
to_chat(H, "<span class='warning'>You don't feel so good...</span>")
H.adjust_disgust(25 + 30 * fraction)
else
if(foodtypes & H.dna.species.toxic_food)
to_chat(H,"<span class='warning'>What the hell was that thing?!</span>")
H.adjust_disgust(25 + 30 * fraction)
SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "toxic_food", /datum/mood_event/disgusting_food)
else if(foodtypes & H.dna.species.disliked_food)
to_chat(H,"<span class='notice'>That didn't taste very good...</span>")
H.adjust_disgust(11 + 15 * fraction)
SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "gross_food", /datum/mood_event/gross_food)
else if(foodtypes & H.dna.species.liked_food)
to_chat(H,"<span class='notice'>I love this taste!</span>")
H.adjust_disgust(-5 + -2.5 * fraction)
SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "fav_food", /datum/mood_event/favorite_food)
if((foodtypes & BREAKFAST) && world.time - SSticker.round_start_time < STOP_SERVING_BREAKFAST)
SEND_SIGNAL(H, COMSIG_ADD_MOOD_EVENT, "breakfast", /datum/mood_event/breakfast)
last_check_time = world.time
///Delete the item when it is fully eaten
/datum/component/edible/proc/On_Consume(mob/living/eater)
var/atom/owner = parent
if(!eater)
return
if(!owner.reagents.total_volume)
if(isturf(parent))
var/turf/T = parent
T.ScrapeAway(1, CHANGETURF_INHERIT_AIR)
else
qdel(parent)
///Ability to feed food to puppers
/datum/component/edible/proc/UseByAnimal(datum/source, mob/user)
var/atom/owner = parent
if(!isdog(user))
return
var/mob/living/L = user
if(bitecount == 0 || prob(50))
L.emote("me", 1, "nibbles away at \the [parent]")
bitecount++
. = COMPONENT_ITEM_NO_ATTACK
L.taste(owner.reagents) // why should carbons get all the fun?
if(bitecount >= 5)
var/sattisfaction_text = pick("burps from enjoyment", "yaps for more", "woofs twice", "looks at the area where \the [parent] was")
if(sattisfaction_text)
L.emote("me", 1, "[sattisfaction_text]")
qdel(parent)
+184 -184
View File
@@ -1,184 +1,184 @@
/datum/component/forensics
dupe_mode = COMPONENT_DUPE_UNIQUE
can_transfer = TRUE
var/list/fingerprints //assoc print = print
var/list/hiddenprints //assoc ckey = realname/gloves/ckey
var/list/blood_DNA //assoc dna = bloodtype
var/list/fibers //assoc print = print
/datum/component/forensics/InheritComponent(datum/component/forensics/F, original) //Use of | and |= being different here is INTENTIONAL.
fingerprints = fingerprints | F.fingerprints
hiddenprints = hiddenprints | F.hiddenprints
blood_DNA = blood_DNA | F.blood_DNA
fibers = fibers | F.fibers
check_blood()
return ..()
/datum/component/forensics/Initialize(new_fingerprints, new_hiddenprints, new_blood_DNA, new_fibers)
if(!isatom(parent))
return COMPONENT_INCOMPATIBLE
fingerprints = new_fingerprints
hiddenprints = new_hiddenprints
blood_DNA = new_blood_DNA
fibers = new_fibers
check_blood()
/datum/component/forensics/RegisterWithParent()
check_blood()
RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_act)
/datum/component/forensics/UnregisterFromParent()
UnregisterSignal(parent, list(COMSIG_COMPONENT_CLEAN_ACT))
/datum/component/forensics/PostTransfer()
if(!isatom(parent))
return COMPONENT_INCOMPATIBLE
/datum/component/forensics/proc/wipe_fingerprints()
fingerprints = null
return TRUE
/datum/component/forensics/proc/wipe_hiddenprints()
return //no.
/datum/component/forensics/proc/wipe_blood_DNA()
blood_DNA = null
if(isitem(parent))
qdel(parent.GetComponent(/datum/component/decal/blood))
return TRUE
/datum/component/forensics/proc/wipe_fibers()
fibers = null
return TRUE
/datum/component/forensics/proc/clean_act(datum/source, strength)
if(strength >= CLEAN_STRENGTH_FINGERPRINTS)
wipe_fingerprints()
if(strength >= CLEAN_STRENGTH_BLOOD)
wipe_blood_DNA()
if(strength >= CLEAN_STRENGTH_FIBERS)
wipe_fibers()
/datum/component/forensics/proc/add_fingerprint_list(list/_fingerprints) //list(text)
if(!length(_fingerprints))
return
LAZYINITLIST(fingerprints)
for(var/i in _fingerprints) //We use an associative list, make sure we don't just merge a non-associative list into ours.
fingerprints[i] = i
return TRUE
/datum/component/forensics/proc/add_fingerprint(mob/living/M, ignoregloves = FALSE)
if(!isliving(M))
if(!iscameramob(M))
return
if(isaicamera(M))
var/mob/camera/ai_eye/ai_camera = M
if(!ai_camera.ai)
return
M = ai_camera.ai
add_hiddenprint(M)
if(ishuman(M))
var/mob/living/carbon/human/H = M
add_fibers(H)
if(H.gloves) //Check if the gloves (if any) hide fingerprints
var/obj/item/clothing/gloves/G = H.gloves
if(G.transfer_prints)
ignoregloves = TRUE
if(!ignoregloves)
H.gloves.add_fingerprint(H, TRUE) //ignoregloves = 1 to avoid infinite loop.
return
var/full_print = md5(H.dna.uni_identity)
LAZYSET(fingerprints, full_print, full_print)
return TRUE
/datum/component/forensics/proc/add_fiber_list(list/_fibertext) //list(text)
if(!length(_fibertext))
return
LAZYINITLIST(fibers)
for(var/i in _fibertext) //We use an associative list, make sure we don't just merge a non-associative list into ours.
fibers[i] = i
return TRUE
/datum/component/forensics/proc/add_fibers(mob/living/carbon/human/M)
var/fibertext
var/item_multiplier = isitem(src)?1.2:1
if(M.wear_suit)
fibertext = "Material from \a [M.wear_suit]."
if(prob(10*item_multiplier) && !LAZYACCESS(fibers, fibertext))
LAZYSET(fibers, fibertext, fibertext)
if(!(M.wear_suit.body_parts_covered & CHEST))
if(M.w_uniform)
fibertext = "Fibers from \a [M.w_uniform]."
if(prob(12*item_multiplier) && !LAZYACCESS(fibers, fibertext)) //Wearing a suit means less of the uniform exposed.
LAZYSET(fibers, fibertext, fibertext)
if(!(M.wear_suit.body_parts_covered & HANDS))
if(M.gloves)
fibertext = "Material from a pair of [M.gloves.name]."
if(prob(20*item_multiplier) && !LAZYACCESS(fibers, fibertext))
LAZYSET(fibers, fibertext, fibertext)
else if(M.w_uniform)
fibertext = "Fibers from \a [M.w_uniform]."
if(prob(15*item_multiplier) && !LAZYACCESS(fibers, fibertext))
// "Added fibertext: [fibertext]"
LAZYSET(fibers, fibertext, fibertext)
if(M.gloves)
fibertext = "Material from a pair of [M.gloves.name]."
if(prob(20*item_multiplier) && !LAZYACCESS(fibers, fibertext))
LAZYSET(fibers, fibertext, fibertext)
else if(M.gloves)
fibertext = "Material from a pair of [M.gloves.name]."
if(prob(20*item_multiplier) && !LAZYACCESS(fibers, fibertext))
LAZYSET(fibers, fibertext, fibertext)
return TRUE
/datum/component/forensics/proc/add_hiddenprint_list(list/_hiddenprints) //list(ckey = text)
if(!length(_hiddenprints))
return
LAZYINITLIST(hiddenprints)
for(var/i in _hiddenprints) //We use an associative list, make sure we don't just merge a non-associative list into ours.
hiddenprints[i] = _hiddenprints[i]
return TRUE
/datum/component/forensics/proc/add_hiddenprint(mob/M)
if(!isliving(M))
if(!iscameramob(M))
return
if(isaicamera(M))
var/mob/camera/ai_eye/ai_camera = M
if(!ai_camera.ai)
return
M = ai_camera.ai
if(!M.key)
return
var/hasgloves = ""
if(ishuman(M))
var/mob/living/carbon/human/H = M
if(H.gloves)
hasgloves = "(gloves)"
var/current_time = time_stamp()
if(!LAZYACCESS(hiddenprints, M.key))
LAZYSET(hiddenprints, M.key, "First: [M.real_name]\[[current_time]\][hasgloves]. Ckey: [M.ckey]")
else
var/laststamppos = findtext(LAZYACCESS(hiddenprints, M.key), " Last: ")
if(laststamppos)
LAZYSET(hiddenprints, M.key, copytext(hiddenprints[M.key], 1, laststamppos))
hiddenprints[M.key] += " Last: [M.real_name]\[[current_time]\][hasgloves]. Ckey: [M.ckey]" //made sure to be existing by if(!LAZYACCESS);else
var/atom/A = parent
A.fingerprintslast = M.ckey
return TRUE
/datum/component/forensics/proc/add_blood_DNA(list/dna) //list(dna_enzymes = type)
if(!length(dna))
return
LAZYINITLIST(blood_DNA)
for(var/i in dna)
blood_DNA[i] = dna[i]
check_blood()
return TRUE
/datum/component/forensics/proc/check_blood()
if(!isitem(parent))
return
if(!length(blood_DNA))
return
parent.LoadComponent(/datum/component/decal/blood)
/datum/component/forensics
dupe_mode = COMPONENT_DUPE_UNIQUE
can_transfer = TRUE
var/list/fingerprints //assoc print = print
var/list/hiddenprints //assoc ckey = realname/gloves/ckey
var/list/blood_DNA //assoc dna = bloodtype
var/list/fibers //assoc print = print
/datum/component/forensics/InheritComponent(datum/component/forensics/F, original) //Use of | and |= being different here is INTENTIONAL.
fingerprints = fingerprints | F.fingerprints
hiddenprints = hiddenprints | F.hiddenprints
blood_DNA = blood_DNA | F.blood_DNA
fibers = fibers | F.fibers
check_blood()
return ..()
/datum/component/forensics/Initialize(new_fingerprints, new_hiddenprints, new_blood_DNA, new_fibers)
if(!isatom(parent))
return COMPONENT_INCOMPATIBLE
fingerprints = new_fingerprints
hiddenprints = new_hiddenprints
blood_DNA = new_blood_DNA
fibers = new_fibers
check_blood()
/datum/component/forensics/RegisterWithParent()
check_blood()
RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_act)
/datum/component/forensics/UnregisterFromParent()
UnregisterSignal(parent, list(COMSIG_COMPONENT_CLEAN_ACT))
/datum/component/forensics/PostTransfer()
if(!isatom(parent))
return COMPONENT_INCOMPATIBLE
/datum/component/forensics/proc/wipe_fingerprints()
fingerprints = null
return TRUE
/datum/component/forensics/proc/wipe_hiddenprints()
return //no.
/datum/component/forensics/proc/wipe_blood_DNA()
blood_DNA = null
if(isitem(parent))
qdel(parent.GetComponent(/datum/component/decal/blood))
return TRUE
/datum/component/forensics/proc/wipe_fibers()
fibers = null
return TRUE
/datum/component/forensics/proc/clean_act(datum/source, strength)
if(strength >= CLEAN_STRENGTH_FINGERPRINTS)
wipe_fingerprints()
if(strength >= CLEAN_STRENGTH_BLOOD)
wipe_blood_DNA()
if(strength >= CLEAN_STRENGTH_FIBERS)
wipe_fibers()
/datum/component/forensics/proc/add_fingerprint_list(list/_fingerprints) //list(text)
if(!length(_fingerprints))
return
LAZYINITLIST(fingerprints)
for(var/i in _fingerprints) //We use an associative list, make sure we don't just merge a non-associative list into ours.
fingerprints[i] = i
return TRUE
/datum/component/forensics/proc/add_fingerprint(mob/living/M, ignoregloves = FALSE)
if(!isliving(M))
if(!iscameramob(M))
return
if(isaicamera(M))
var/mob/camera/ai_eye/ai_camera = M
if(!ai_camera.ai)
return
M = ai_camera.ai
add_hiddenprint(M)
if(ishuman(M))
var/mob/living/carbon/human/H = M
add_fibers(H)
if(H.gloves) //Check if the gloves (if any) hide fingerprints
var/obj/item/clothing/gloves/G = H.gloves
if(G.transfer_prints)
ignoregloves = TRUE
if(!ignoregloves)
H.gloves.add_fingerprint(H, TRUE) //ignoregloves = 1 to avoid infinite loop.
return
var/full_print = md5(H.dna.uni_identity)
LAZYSET(fingerprints, full_print, full_print)
return TRUE
/datum/component/forensics/proc/add_fiber_list(list/_fibertext) //list(text)
if(!length(_fibertext))
return
LAZYINITLIST(fibers)
for(var/i in _fibertext) //We use an associative list, make sure we don't just merge a non-associative list into ours.
fibers[i] = i
return TRUE
/datum/component/forensics/proc/add_fibers(mob/living/carbon/human/M)
var/fibertext
var/item_multiplier = isitem(src)?1.2:1
if(M.wear_suit)
fibertext = "Material from \a [M.wear_suit]."
if(prob(10*item_multiplier) && !LAZYACCESS(fibers, fibertext))
LAZYSET(fibers, fibertext, fibertext)
if(!(M.wear_suit.body_parts_covered & CHEST))
if(M.w_uniform)
fibertext = "Fibers from \a [M.w_uniform]."
if(prob(12*item_multiplier) && !LAZYACCESS(fibers, fibertext)) //Wearing a suit means less of the uniform exposed.
LAZYSET(fibers, fibertext, fibertext)
if(!(M.wear_suit.body_parts_covered & HANDS))
if(M.gloves)
fibertext = "Material from a pair of [M.gloves.name]."
if(prob(20*item_multiplier) && !LAZYACCESS(fibers, fibertext))
LAZYSET(fibers, fibertext, fibertext)
else if(M.w_uniform)
fibertext = "Fibers from \a [M.w_uniform]."
if(prob(15*item_multiplier) && !LAZYACCESS(fibers, fibertext))
// "Added fibertext: [fibertext]"
LAZYSET(fibers, fibertext, fibertext)
if(M.gloves)
fibertext = "Material from a pair of [M.gloves.name]."
if(prob(20*item_multiplier) && !LAZYACCESS(fibers, fibertext))
LAZYSET(fibers, fibertext, fibertext)
else if(M.gloves)
fibertext = "Material from a pair of [M.gloves.name]."
if(prob(20*item_multiplier) && !LAZYACCESS(fibers, fibertext))
LAZYSET(fibers, fibertext, fibertext)
return TRUE
/datum/component/forensics/proc/add_hiddenprint_list(list/_hiddenprints) //list(ckey = text)
if(!length(_hiddenprints))
return
LAZYINITLIST(hiddenprints)
for(var/i in _hiddenprints) //We use an associative list, make sure we don't just merge a non-associative list into ours.
hiddenprints[i] = _hiddenprints[i]
return TRUE
/datum/component/forensics/proc/add_hiddenprint(mob/M)
if(!isliving(M))
if(!iscameramob(M))
return
if(isaicamera(M))
var/mob/camera/ai_eye/ai_camera = M
if(!ai_camera.ai)
return
M = ai_camera.ai
if(!M.key)
return
var/hasgloves = ""
if(ishuman(M))
var/mob/living/carbon/human/H = M
if(H.gloves)
hasgloves = "(gloves)"
var/current_time = time_stamp()
if(!LAZYACCESS(hiddenprints, M.key))
LAZYSET(hiddenprints, M.key, "First: [M.real_name]\[[current_time]\][hasgloves]. Ckey: [M.ckey]")
else
var/laststamppos = findtext(LAZYACCESS(hiddenprints, M.key), " Last: ")
if(laststamppos)
LAZYSET(hiddenprints, M.key, copytext(hiddenprints[M.key], 1, laststamppos))
hiddenprints[M.key] += " Last: [M.real_name]\[[current_time]\][hasgloves]. Ckey: [M.ckey]" //made sure to be existing by if(!LAZYACCESS);else
var/atom/A = parent
A.fingerprintslast = M.ckey
return TRUE
/datum/component/forensics/proc/add_blood_DNA(list/dna) //list(dna_enzymes = type)
if(!length(dna))
return
LAZYINITLIST(blood_DNA)
for(var/i in dna)
blood_DNA[i] = dna[i]
check_blood()
return TRUE
/datum/component/forensics/proc/check_blood()
if(!isitem(parent))
return
if(!length(blood_DNA))
return
parent.LoadComponent(/datum/component/decal/blood)
+74 -74
View File
@@ -1,74 +1,74 @@
/datum/component/jousting
var/current_direction = NONE
var/max_tile_charge = 5
var/min_tile_charge = 2 //tiles before this code gets into effect.
var/current_tile_charge = 0
var/movement_reset_tolerance = 2 //deciseconds
var/unmounted_damage_boost_per_tile = 0
var/unmounted_knockdown_chance_per_tile = 0
var/unmounted_knockdown_time = 0
var/mounted_damage_boost_per_tile = 2
var/mounted_knockdown_chance_per_tile = 20
var/mounted_knockdown_time = 20
var/requires_mob_riding = TRUE //whether this only works if the attacker is riding a mob, rather than anything they can buckle to.
var/requires_mount = TRUE //kinda defeats the point of jousting if you're not mounted but whatever.
var/mob/current_holder
var/current_timerid
/datum/component/jousting/Initialize()
if(!isitem(parent))
return COMPONENT_INCOMPATIBLE
RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/on_equip)
RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/on_drop)
RegisterSignal(parent, COMSIG_ITEM_ATTACK, .proc/on_attack)
/datum/component/jousting/proc/on_equip(datum/source, mob/user, slot)
RegisterSignal(user, COMSIG_MOVABLE_MOVED, .proc/mob_move, TRUE)
current_holder = user
/datum/component/jousting/proc/on_drop(datum/source, mob/user)
UnregisterSignal(user, COMSIG_MOVABLE_MOVED)
current_holder = null
current_direction = NONE
current_tile_charge = 0
/datum/component/jousting/proc/on_attack(datum/source, mob/living/target, mob/user)
if(user != current_holder)
return
var/current = current_tile_charge
var/obj/item/I = parent
var/target_buckled = target.buckled ? TRUE : FALSE //we don't need the reference of what they're buckled to, just whether they are.
if((requires_mount && ((requires_mob_riding && !ismob(user.buckled)) || (!user.buckled))) || !current_direction || (current_tile_charge < min_tile_charge))
return
var/turf/target_turf = get_step(user, current_direction)
if(target in range(1, target_turf))
var/knockdown_chance = (target_buckled? mounted_knockdown_chance_per_tile : unmounted_knockdown_chance_per_tile) * current
var/knockdown_time = (target_buckled? mounted_knockdown_time : unmounted_knockdown_time)
var/damage = (target_buckled? mounted_damage_boost_per_tile : unmounted_damage_boost_per_tile) * current
var/sharp = I.get_sharpness()
var/msg
if(damage)
msg += "[user] [sharp? "impales" : "slams into"] [target] [sharp? "on" : "with"] their [parent]"
target.apply_damage(damage, BRUTE, user.zone_selected, 0)
if(prob(knockdown_chance))
msg += " and knocks [target] [target_buckled? "off of [target.buckled]" : "down"]"
if(target_buckled)
target.buckled.unbuckle_mob(target)
target.Paralyze(knockdown_time)
if(length(msg))
user.visible_message("<span class='danger'>[msg]!</span>")
/datum/component/jousting/proc/mob_move(datum/source, newloc, dir)
if(!current_holder || (requires_mount && ((requires_mob_riding && !ismob(current_holder.buckled)) || (!current_holder.buckled))))
return
if(dir != current_direction)
current_tile_charge = 0
current_direction = dir
if(current_tile_charge < max_tile_charge)
current_tile_charge++
if(current_timerid)
deltimer(current_timerid)
current_timerid = addtimer(CALLBACK(src, .proc/reset_charge), movement_reset_tolerance, TIMER_STOPPABLE)
/datum/component/jousting/proc/reset_charge()
current_tile_charge = 0
/datum/component/jousting
var/current_direction = NONE
var/max_tile_charge = 5
var/min_tile_charge = 2 //tiles before this code gets into effect.
var/current_tile_charge = 0
var/movement_reset_tolerance = 2 //deciseconds
var/unmounted_damage_boost_per_tile = 0
var/unmounted_knockdown_chance_per_tile = 0
var/unmounted_knockdown_time = 0
var/mounted_damage_boost_per_tile = 2
var/mounted_knockdown_chance_per_tile = 20
var/mounted_knockdown_time = 20
var/requires_mob_riding = TRUE //whether this only works if the attacker is riding a mob, rather than anything they can buckle to.
var/requires_mount = TRUE //kinda defeats the point of jousting if you're not mounted but whatever.
var/mob/current_holder
var/current_timerid
/datum/component/jousting/Initialize()
if(!isitem(parent))
return COMPONENT_INCOMPATIBLE
RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/on_equip)
RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/on_drop)
RegisterSignal(parent, COMSIG_ITEM_ATTACK, .proc/on_attack)
/datum/component/jousting/proc/on_equip(datum/source, mob/user, slot)
RegisterSignal(user, COMSIG_MOVABLE_MOVED, .proc/mob_move, TRUE)
current_holder = user
/datum/component/jousting/proc/on_drop(datum/source, mob/user)
UnregisterSignal(user, COMSIG_MOVABLE_MOVED)
current_holder = null
current_direction = NONE
current_tile_charge = 0
/datum/component/jousting/proc/on_attack(datum/source, mob/living/target, mob/user)
if(user != current_holder)
return
var/current = current_tile_charge
var/obj/item/I = parent
var/target_buckled = target.buckled ? TRUE : FALSE //we don't need the reference of what they're buckled to, just whether they are.
if((requires_mount && ((requires_mob_riding && !ismob(user.buckled)) || (!user.buckled))) || !current_direction || (current_tile_charge < min_tile_charge))
return
var/turf/target_turf = get_step(user, current_direction)
if(target in range(1, target_turf))
var/knockdown_chance = (target_buckled? mounted_knockdown_chance_per_tile : unmounted_knockdown_chance_per_tile) * current
var/knockdown_time = (target_buckled? mounted_knockdown_time : unmounted_knockdown_time)
var/damage = (target_buckled? mounted_damage_boost_per_tile : unmounted_damage_boost_per_tile) * current
var/sharp = I.get_sharpness()
var/msg
if(damage)
msg += "[user] [sharp? "impales" : "slams into"] [target] [sharp? "on" : "with"] their [parent]"
target.apply_damage(damage, BRUTE, user.zone_selected, 0)
if(prob(knockdown_chance))
msg += " and knocks [target] [target_buckled? "off of [target.buckled]" : "down"]"
if(target_buckled)
target.buckled.unbuckle_mob(target)
target.Paralyze(knockdown_time)
if(length(msg))
user.visible_message("<span class='danger'>[msg]!</span>")
/datum/component/jousting/proc/mob_move(datum/source, newloc, dir)
if(!current_holder || (requires_mount && ((requires_mob_riding && !ismob(current_holder.buckled)) || (!current_holder.buckled))))
return
if(dir != current_direction)
current_tile_charge = 0
current_direction = dir
if(current_tile_charge < max_tile_charge)
current_tile_charge++
if(current_timerid)
deltimer(current_timerid)
current_timerid = addtimer(CALLBACK(src, .proc/reset_charge), movement_reset_tolerance, TIMER_STOPPABLE)
/datum/component/jousting/proc/reset_charge()
current_tile_charge = 0
+214 -214
View File
@@ -1,214 +1,214 @@
#define LOCKON_AIMING_MAX_CURSOR_RADIUS 7
#define LOCKON_IGNORE_RESULT "ignore_my_result"
#define LOCKON_RANGING_BREAK_CHECK if(current_ranging_id != this_id){return LOCKON_IGNORE_RESULT}
/datum/component/lockon_aiming
dupe_mode = COMPONENT_DUPE_ALLOWED
var/lock_icon = 'icons/mob/cameramob.dmi'
var/lock_icon_state = "marker"
var/mutable_appearance/lock_appearance
var/list/image/lock_images
var/list/target_typecache
var/list/immune_weakrefs //list(weakref = TRUE)
var/mob_stat_check = TRUE //if a potential target is a mob make sure it's conscious!
var/lock_amount = 1
var/lock_cursor_range = 5
var/list/locked_weakrefs
var/update_disabled = FALSE
var/current_ranging_id = 0
var/list/last_location
var/datum/callback/on_lock
var/datum/callback/can_target_callback
/datum/component/lockon_aiming/Initialize(range, list/typecache, amount, list/immune, datum/callback/when_locked, icon, icon_state, datum/callback/target_callback)
if(!ismob(parent))
return COMPONENT_INCOMPATIBLE
if(target_callback)
can_target_callback = target_callback
else
can_target_callback = CALLBACK(src, .proc/can_target)
if(range)
lock_cursor_range = range
if(typecache)
target_typecache = typecache
if(amount)
lock_amount = amount
immune_weakrefs = list(WEAKREF(parent) = TRUE) //Manually take this out if you want..
if(immune)
for(var/i in immune)
if(isweakref(i))
immune_weakrefs[i] = TRUE
else if(isatom(i))
immune_weakrefs[WEAKREF(i)] = TRUE
if(when_locked)
on_lock = when_locked
if(icon)
lock_icon = icon
if(icon_state)
lock_icon_state = icon_state
generate_lock_visuals()
START_PROCESSING(SSfastprocess, src)
/datum/component/lockon_aiming/Destroy()
clear_visuals()
STOP_PROCESSING(SSfastprocess, src)
return ..()
/datum/component/lockon_aiming/proc/show_visuals()
LAZYINITLIST(lock_images)
var/mob/M = parent
if(!M.client)
return
for(var/i in locked_weakrefs)
var/datum/weakref/R = i
var/atom/A = R.resolve()
if(!A)
continue //It'll be cleared by processing.
var/image/I = new
I.appearance = lock_appearance
I.loc = A
M.client.images |= I
lock_images |= I
/datum/component/lockon_aiming/proc/clear_visuals()
var/mob/M = parent
if(!M.client)
return
if(!lock_images)
return
for(var/i in lock_images)
M.client.images -= i
qdel(i)
lock_images.Cut()
/datum/component/lockon_aiming/proc/refresh_visuals()
clear_visuals()
show_visuals()
/datum/component/lockon_aiming/proc/generate_lock_visuals()
lock_appearance = mutable_appearance(icon = lock_icon, icon_state = lock_icon_state, layer = FLOAT_LAYER)
/datum/component/lockon_aiming/proc/unlock_all(refresh_vis = TRUE)
LAZYCLEARLIST(locked_weakrefs)
if(refresh_vis)
refresh_visuals()
/datum/component/lockon_aiming/proc/unlock(atom/A, refresh_vis = TRUE)
if(!A.weak_reference)
return
LAZYREMOVE(locked_weakrefs, A.weak_reference)
if(refresh_vis)
refresh_visuals()
/datum/component/lockon_aiming/proc/lock(atom/A, refresh_vis = TRUE)
LAZYOR(locked_weakrefs, WEAKREF(A))
if(refresh_vis)
refresh_visuals()
/datum/component/lockon_aiming/proc/add_immune_atom(atom/A)
var/datum/weakref/R = WEAKREF(A)
if(immune_weakrefs && (immune_weakrefs[R]))
return
LAZYSET(immune_weakrefs, R, TRUE)
/datum/component/lockon_aiming/proc/remove_immune_atom(atom/A)
if(!A.weak_reference || !immune_weakrefs) //if A doesn't have a weakref how did it get on the immunity list?
return
LAZYREMOVE(immune_weakrefs, A.weak_reference)
/datum/component/lockon_aiming/process()
if(update_disabled)
return
if(!last_location)
return
var/changed = FALSE
for(var/i in locked_weakrefs)
var/datum/weakref/R = i
if(istype(R))
var/atom/thing = R.resolve()
if(!istype(thing) || (get_dist(thing, locate(last_location[1], last_location[2], last_location[3])) > lock_cursor_range))
unlock(R)
changed = TRUE
else
unlock(R)
changed = TRUE
if(changed)
autolock()
/datum/component/lockon_aiming/proc/autolock()
var/mob/M = parent
if(!M.client)
return FALSE
var/datum/position/current = mouse_absolute_datum_map_position_from_client(M.client)
var/turf/target = current.return_turf()
var/list/atom/targets = get_nearest(target, target_typecache, lock_amount, lock_cursor_range)
if(targets == LOCKON_IGNORE_RESULT)
return
unlock_all(FALSE)
for(var/i in targets)
if(immune_weakrefs[WEAKREF(i)])
continue
lock(i, FALSE)
refresh_visuals()
on_lock.Invoke(locked_weakrefs)
/datum/component/lockon_aiming/proc/can_target(atom/A)
var/mob/M = A
return is_type_in_typecache(A, target_typecache) && !(ismob(A) && mob_stat_check && M.stat != CONSCIOUS) && !immune_weakrefs[WEAKREF(A)]
/datum/component/lockon_aiming/proc/get_nearest(turf/T, list/typecache, amount, range)
current_ranging_id++
var/this_id = current_ranging_id
var/list/L = list()
var/turf/center = get_turf(T)
if(amount < 1 || range < 0 || !istype(center) || !islist(typecache))
return
if(range == 0)
return typecache_filter_list(T.contents + T, typecache)
var/x = 0
var/y = 0
var/cd = 0
while(cd <= range)
x = center.x - cd + 1
y = center.y + cd
LOCKON_RANGING_BREAK_CHECK
for(x in x to center.x + cd)
T = locate(x, y, center.z)
if(T)
L |= special_list_filter(T.contents, can_target_callback)
if(L.len >= amount)
L.Cut(amount+1)
return L
LOCKON_RANGING_BREAK_CHECK
y = center.y + cd - 1
x = center.x + cd
for(y in center.y - cd to y)
T = locate(x, y, center.z)
if(T)
L |= special_list_filter(T.contents, can_target_callback)
if(L.len >= amount)
L.Cut(amount+1)
return L
LOCKON_RANGING_BREAK_CHECK
y = center.y - cd
x = center.x + cd - 1
for(x in center.x - cd to x)
T = locate(x, y, center.z)
if(T)
L |= special_list_filter(T.contents, can_target_callback)
if(L.len >= amount)
L.Cut(amount+1)
return L
LOCKON_RANGING_BREAK_CHECK
y = center.y - cd + 1
x = center.x - cd
for(y in y to center.y + cd)
T = locate(x, y, center.z)
if(T)
L |= special_list_filter(T.contents, can_target_callback)
if(L.len >= amount)
L.Cut(amount+1)
return L
LOCKON_RANGING_BREAK_CHECK
cd++
CHECK_TICK
#define LOCKON_AIMING_MAX_CURSOR_RADIUS 7
#define LOCKON_IGNORE_RESULT "ignore_my_result"
#define LOCKON_RANGING_BREAK_CHECK if(current_ranging_id != this_id){return LOCKON_IGNORE_RESULT}
/datum/component/lockon_aiming
dupe_mode = COMPONENT_DUPE_ALLOWED
var/lock_icon = 'icons/mob/cameramob.dmi'
var/lock_icon_state = "marker"
var/mutable_appearance/lock_appearance
var/list/image/lock_images
var/list/target_typecache
var/list/immune_weakrefs //list(weakref = TRUE)
var/mob_stat_check = TRUE //if a potential target is a mob make sure it's conscious!
var/lock_amount = 1
var/lock_cursor_range = 5
var/list/locked_weakrefs
var/update_disabled = FALSE
var/current_ranging_id = 0
var/list/last_location
var/datum/callback/on_lock
var/datum/callback/can_target_callback
/datum/component/lockon_aiming/Initialize(range, list/typecache, amount, list/immune, datum/callback/when_locked, icon, icon_state, datum/callback/target_callback)
if(!ismob(parent))
return COMPONENT_INCOMPATIBLE
if(target_callback)
can_target_callback = target_callback
else
can_target_callback = CALLBACK(src, .proc/can_target)
if(range)
lock_cursor_range = range
if(typecache)
target_typecache = typecache
if(amount)
lock_amount = amount
immune_weakrefs = list(WEAKREF(parent) = TRUE) //Manually take this out if you want..
if(immune)
for(var/i in immune)
if(isweakref(i))
immune_weakrefs[i] = TRUE
else if(isatom(i))
immune_weakrefs[WEAKREF(i)] = TRUE
if(when_locked)
on_lock = when_locked
if(icon)
lock_icon = icon
if(icon_state)
lock_icon_state = icon_state
generate_lock_visuals()
START_PROCESSING(SSfastprocess, src)
/datum/component/lockon_aiming/Destroy()
clear_visuals()
STOP_PROCESSING(SSfastprocess, src)
return ..()
/datum/component/lockon_aiming/proc/show_visuals()
LAZYINITLIST(lock_images)
var/mob/M = parent
if(!M.client)
return
for(var/i in locked_weakrefs)
var/datum/weakref/R = i
var/atom/A = R.resolve()
if(!A)
continue //It'll be cleared by processing.
var/image/I = new
I.appearance = lock_appearance
I.loc = A
M.client.images |= I
lock_images |= I
/datum/component/lockon_aiming/proc/clear_visuals()
var/mob/M = parent
if(!M.client)
return
if(!lock_images)
return
for(var/i in lock_images)
M.client.images -= i
qdel(i)
lock_images.Cut()
/datum/component/lockon_aiming/proc/refresh_visuals()
clear_visuals()
show_visuals()
/datum/component/lockon_aiming/proc/generate_lock_visuals()
lock_appearance = mutable_appearance(icon = lock_icon, icon_state = lock_icon_state, layer = FLOAT_LAYER)
/datum/component/lockon_aiming/proc/unlock_all(refresh_vis = TRUE)
LAZYCLEARLIST(locked_weakrefs)
if(refresh_vis)
refresh_visuals()
/datum/component/lockon_aiming/proc/unlock(atom/A, refresh_vis = TRUE)
if(!A.weak_reference)
return
LAZYREMOVE(locked_weakrefs, A.weak_reference)
if(refresh_vis)
refresh_visuals()
/datum/component/lockon_aiming/proc/lock(atom/A, refresh_vis = TRUE)
LAZYOR(locked_weakrefs, WEAKREF(A))
if(refresh_vis)
refresh_visuals()
/datum/component/lockon_aiming/proc/add_immune_atom(atom/A)
var/datum/weakref/R = WEAKREF(A)
if(immune_weakrefs && (immune_weakrefs[R]))
return
LAZYSET(immune_weakrefs, R, TRUE)
/datum/component/lockon_aiming/proc/remove_immune_atom(atom/A)
if(!A.weak_reference || !immune_weakrefs) //if A doesn't have a weakref how did it get on the immunity list?
return
LAZYREMOVE(immune_weakrefs, A.weak_reference)
/datum/component/lockon_aiming/process()
if(update_disabled)
return
if(!last_location)
return
var/changed = FALSE
for(var/i in locked_weakrefs)
var/datum/weakref/R = i
if(istype(R))
var/atom/thing = R.resolve()
if(!istype(thing) || (get_dist(thing, locate(last_location[1], last_location[2], last_location[3])) > lock_cursor_range))
unlock(R)
changed = TRUE
else
unlock(R)
changed = TRUE
if(changed)
autolock()
/datum/component/lockon_aiming/proc/autolock()
var/mob/M = parent
if(!M.client)
return FALSE
var/datum/position/current = mouse_absolute_datum_map_position_from_client(M.client)
var/turf/target = current.return_turf()
var/list/atom/targets = get_nearest(target, target_typecache, lock_amount, lock_cursor_range)
if(targets == LOCKON_IGNORE_RESULT)
return
unlock_all(FALSE)
for(var/i in targets)
if(immune_weakrefs[WEAKREF(i)])
continue
lock(i, FALSE)
refresh_visuals()
on_lock.Invoke(locked_weakrefs)
/datum/component/lockon_aiming/proc/can_target(atom/A)
var/mob/M = A
return is_type_in_typecache(A, target_typecache) && !(ismob(A) && mob_stat_check && M.stat != CONSCIOUS) && !immune_weakrefs[WEAKREF(A)]
/datum/component/lockon_aiming/proc/get_nearest(turf/T, list/typecache, amount, range)
current_ranging_id++
var/this_id = current_ranging_id
var/list/L = list()
var/turf/center = get_turf(T)
if(amount < 1 || range < 0 || !istype(center) || !islist(typecache))
return
if(range == 0)
return typecache_filter_list(T.contents + T, typecache)
var/x = 0
var/y = 0
var/cd = 0
while(cd <= range)
x = center.x - cd + 1
y = center.y + cd
LOCKON_RANGING_BREAK_CHECK
for(x in x to center.x + cd)
T = locate(x, y, center.z)
if(T)
L |= special_list_filter(T.contents, can_target_callback)
if(L.len >= amount)
L.Cut(amount+1)
return L
LOCKON_RANGING_BREAK_CHECK
y = center.y + cd - 1
x = center.x + cd
for(y in center.y - cd to y)
T = locate(x, y, center.z)
if(T)
L |= special_list_filter(T.contents, can_target_callback)
if(L.len >= amount)
L.Cut(amount+1)
return L
LOCKON_RANGING_BREAK_CHECK
y = center.y - cd
x = center.x + cd - 1
for(x in center.x - cd to x)
T = locate(x, y, center.z)
if(T)
L |= special_list_filter(T.contents, can_target_callback)
if(L.len >= amount)
L.Cut(amount+1)
return L
LOCKON_RANGING_BREAK_CHECK
y = center.y - cd + 1
x = center.x - cd
for(y in y to center.y + cd)
T = locate(x, y, center.z)
if(T)
L |= special_list_filter(T.contents, can_target_callback)
if(L.len >= amount)
L.Cut(amount+1)
return L
LOCKON_RANGING_BREAK_CHECK
cd++
CHECK_TICK
+381 -381
View File
@@ -1,381 +1,381 @@
/datum/component/nanites
dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
var/mob/living/host_mob
var/nanite_volume = 100 //amount of nanites in the system, used as fuel for nanite programs
var/max_nanites = 500 //maximum amount of nanites in the system
var/regen_rate = 0.5 //nanites generated per second
var/safety_threshold = 50 //how low nanites will get before they stop processing/triggering
var/cloud_id = 0 //0 if not connected to the cloud, 1-100 to set a determined cloud backup to draw from
var/cloud_active = TRUE //if false, won't sync to the cloud
var/next_sync = 0
var/list/datum/nanite_program/programs = list()
var/max_programs = NANITE_PROGRAM_LIMIT
var/list/datum/nanite_program/protocol/protocols = list() ///Separate list of protocol programs, to avoid looping through the whole programs list when cheking for conflicts
var/start_time = 0 ///Timestamp to when the nanites were first inserted in the host
var/stealth = FALSE //if TRUE, does not appear on HUDs and health scans
var/diagnostics = TRUE //if TRUE, displays program list when scanned by nanite scanners
/datum/component/nanites/Initialize(amount = 100, cloud = 0)
if(!isliving(parent) && !istype(parent, /datum/nanite_cloud_backup))
return COMPONENT_INCOMPATIBLE
nanite_volume = amount
cloud_id = cloud
//Nanites without hosts are non-interactive through normal means
if(isliving(parent))
host_mob = parent
if(!(host_mob.mob_biotypes & (MOB_ORGANIC|MOB_UNDEAD))) //Shouldn't happen, but this avoids HUD runtimes in case a silicon gets them somehow.
return COMPONENT_INCOMPATIBLE
start_time = world.time
host_mob.hud_set_nanite_indicator()
START_PROCESSING(SSnanites, src)
if(cloud_id && cloud_active)
cloud_sync()
/datum/component/nanites/RegisterWithParent()
RegisterSignal(parent, COMSIG_HAS_NANITES, .proc/confirm_nanites)
RegisterSignal(parent, COMSIG_NANITE_IS_STEALTHY, .proc/check_stealth)
RegisterSignal(parent, COMSIG_NANITE_DELETE, .proc/delete_nanites)
RegisterSignal(parent, COMSIG_NANITE_UI_DATA, .proc/nanite_ui_data)
RegisterSignal(parent, COMSIG_NANITE_GET_PROGRAMS, .proc/get_programs)
RegisterSignal(parent, COMSIG_NANITE_SET_VOLUME, .proc/set_volume)
RegisterSignal(parent, COMSIG_NANITE_ADJUST_VOLUME, .proc/adjust_nanites)
RegisterSignal(parent, COMSIG_NANITE_SET_MAX_VOLUME, .proc/set_max_volume)
RegisterSignal(parent, COMSIG_NANITE_SET_CLOUD, .proc/set_cloud)
RegisterSignal(parent, COMSIG_NANITE_SET_CLOUD_SYNC, .proc/set_cloud_sync)
RegisterSignal(parent, COMSIG_NANITE_SET_SAFETY, .proc/set_safety)
RegisterSignal(parent, COMSIG_NANITE_SET_REGEN, .proc/set_regen)
RegisterSignal(parent, COMSIG_NANITE_ADD_PROGRAM, .proc/add_program)
RegisterSignal(parent, COMSIG_NANITE_SCAN, .proc/nanite_scan)
RegisterSignal(parent, COMSIG_NANITE_SYNC, .proc/sync)
if(isliving(parent))
RegisterSignal(parent, COMSIG_ATOM_EMP_ACT, .proc/on_emp)
RegisterSignal(parent, COMSIG_MOB_DEATH, .proc/on_death)
RegisterSignal(parent, COMSIG_MOB_ALLOWED, .proc/check_access)
RegisterSignal(parent, COMSIG_LIVING_ELECTROCUTE_ACT, .proc/on_shock)
RegisterSignal(parent, COMSIG_LIVING_MINOR_SHOCK, .proc/on_minor_shock)
RegisterSignal(parent, COMSIG_SPECIES_GAIN, .proc/check_viable_biotype)
RegisterSignal(parent, COMSIG_NANITE_SIGNAL, .proc/receive_signal)
RegisterSignal(parent, COMSIG_NANITE_COMM_SIGNAL, .proc/receive_comm_signal)
/datum/component/nanites/UnregisterFromParent()
UnregisterSignal(parent, list(COMSIG_HAS_NANITES,
COMSIG_NANITE_IS_STEALTHY,
COMSIG_NANITE_DELETE,
COMSIG_NANITE_UI_DATA,
COMSIG_NANITE_GET_PROGRAMS,
COMSIG_NANITE_SET_VOLUME,
COMSIG_NANITE_ADJUST_VOLUME,
COMSIG_NANITE_SET_MAX_VOLUME,
COMSIG_NANITE_SET_CLOUD,
COMSIG_NANITE_SET_CLOUD_SYNC,
COMSIG_NANITE_SET_SAFETY,
COMSIG_NANITE_SET_REGEN,
COMSIG_NANITE_ADD_PROGRAM,
COMSIG_NANITE_SCAN,
COMSIG_NANITE_SYNC,
COMSIG_ATOM_EMP_ACT,
COMSIG_MOB_DEATH,
COMSIG_MOB_ALLOWED,
COMSIG_LIVING_ELECTROCUTE_ACT,
COMSIG_LIVING_MINOR_SHOCK,
COMSIG_MOVABLE_HEAR,
COMSIG_SPECIES_GAIN,
COMSIG_NANITE_SIGNAL,
COMSIG_NANITE_COMM_SIGNAL))
/datum/component/nanites/Destroy()
STOP_PROCESSING(SSnanites, src)
QDEL_LIST(programs)
if(host_mob)
set_nanite_bar(TRUE)
host_mob.hud_set_nanite_indicator()
host_mob = null
return ..()
/datum/component/nanites/InheritComponent(datum/component/nanites/new_nanites, i_am_original, amount, cloud)
if(new_nanites)
adjust_nanites(null, new_nanites.nanite_volume)
else
adjust_nanites(null, amount) //just add to the nanite volume
/datum/component/nanites/process()
if(!IS_IN_STASIS(host_mob))
adjust_nanites(null, regen_rate)
add_research()
for(var/X in programs)
var/datum/nanite_program/NP = X
NP.on_process()
if(cloud_id && cloud_active && world.time > next_sync)
cloud_sync()
next_sync = world.time + NANITE_SYNC_DELAY
set_nanite_bar()
/datum/component/nanites/proc/delete_nanites()
qdel(src)
//Syncs the nanite component to another, making it so programs are the same with the same programming (except activation status)
/datum/component/nanites/proc/sync(datum/signal_source, datum/component/nanites/source, full_overwrite = TRUE, copy_activation = FALSE)
var/list/programs_to_remove = programs.Copy()
var/list/programs_to_add = source.programs.Copy()
for(var/X in programs)
var/datum/nanite_program/NP = X
for(var/Y in programs_to_add)
var/datum/nanite_program/SNP = Y
if(NP.type == SNP.type)
programs_to_remove -= NP
programs_to_add -= SNP
SNP.copy_programming(NP, copy_activation)
break
if(full_overwrite)
for(var/X in programs_to_remove)
qdel(X)
for(var/X in programs_to_add)
var/datum/nanite_program/SNP = X
add_program(null, SNP.copy())
/datum/component/nanites/proc/cloud_sync()
if(cloud_id)
var/datum/nanite_cloud_backup/backup = SSnanites.get_cloud_backup(cloud_id)
if(backup)
var/datum/component/nanites/cloud_copy = backup.nanites
if(cloud_copy)
sync(null, cloud_copy)
return
//Without cloud syncing nanites can accumulate errors and/or defects
if(prob(8) && programs.len)
var/datum/nanite_program/NP = pick(programs)
NP.software_error()
/datum/component/nanites/proc/add_program(datum/source, datum/nanite_program/new_program, datum/nanite_program/source_program)
for(var/X in programs)
var/datum/nanite_program/NP = X
if(NP.unique && NP.type == new_program.type)
qdel(NP)
if(programs.len >= max_programs)
return COMPONENT_PROGRAM_NOT_INSTALLED
if(source_program)
source_program.copy_programming(new_program)
programs += new_program
new_program.on_add(src)
return COMPONENT_PROGRAM_INSTALLED
/datum/component/nanites/proc/consume_nanites(amount, force = FALSE)
if(!force && safety_threshold && (nanite_volume - amount < safety_threshold))
return FALSE
adjust_nanites(null, -amount)
return (nanite_volume > 0)
/datum/component/nanites/proc/adjust_nanites(datum/source, amount)
nanite_volume = clamp(nanite_volume + amount, 0, max_nanites)
if(nanite_volume <= 0) //oops we ran out
qdel(src)
/datum/component/nanites/proc/set_nanite_bar(remove = FALSE)
var/image/holder = host_mob.hud_list[DIAG_NANITE_FULL_HUD]
var/icon/I = icon(host_mob.icon, host_mob.icon_state, host_mob.dir)
holder.pixel_y = I.Height() - world.icon_size
holder.icon_state = null
if(remove || stealth)
return //bye icon
var/nanite_percent = (nanite_volume / max_nanites) * 100
nanite_percent = clamp(CEILING(nanite_percent, 10), 10, 100)
holder.icon_state = "nanites[nanite_percent]"
/datum/component/nanites/proc/on_emp(datum/source, severity)
nanite_volume *= (rand(60, 90) * 0.01) //Lose 10-40% of nanites
adjust_nanites(null, -(rand(5, 50))) //Lose 5-50 flat nanite volume
if(prob(40/severity))
cloud_id = 0
for(var/X in programs)
var/datum/nanite_program/NP = X
NP.on_emp(severity)
/datum/component/nanites/proc/on_shock(datum/source, shock_damage, siemens_coeff = 1, flags = NONE)
if(flags & SHOCK_ILLUSION || shock_damage < 1)
return
if(!HAS_TRAIT_NOT_FROM(host_mob, TRAIT_SHOCKIMMUNE, "nanites"))//Another shock protection must protect nanites too, but nanites protect only host
nanite_volume *= (rand(45, 80) * 0.01) //Lose 20-55% of nanites
adjust_nanites(null, -(rand(5, 50))) //Lose 5-50 flat nanite volume
for(var/X in programs)
var/datum/nanite_program/NP = X
NP.on_shock(shock_damage)
/datum/component/nanites/proc/on_minor_shock(datum/source)
adjust_nanites(null, -(rand(5, 15))) //Lose 5-15 flat nanite volume
for(var/X in programs)
var/datum/nanite_program/NP = X
NP.on_minor_shock()
/datum/component/nanites/proc/check_stealth(datum/source)
return stealth
/datum/component/nanites/proc/on_death(datum/source, gibbed)
for(var/X in programs)
var/datum/nanite_program/NP = X
NP.on_death(gibbed)
/datum/component/nanites/proc/receive_signal(datum/source, code, source = "an unidentified source")
for(var/X in programs)
var/datum/nanite_program/NP = X
NP.receive_signal(code, source)
/datum/component/nanites/proc/receive_comm_signal(datum/source, comm_code, comm_message, comm_source = "an unidentified source")
for(var/X in programs)
if(istype(X, /datum/nanite_program/comm))
var/datum/nanite_program/comm/NP = X
NP.receive_comm_signal(comm_code, comm_message, comm_source)
/datum/component/nanites/proc/check_viable_biotype()
if(!(host_mob.mob_biotypes & (MOB_ORGANIC|MOB_UNDEAD)))
qdel(src) //bodytype no longer sustains nanites
/datum/component/nanites/proc/check_access(datum/source, obj/O)
for(var/datum/nanite_program/access/access_program in programs)
if(access_program.activated)
return O.check_access_list(access_program.access)
else
return FALSE
return FALSE
/datum/component/nanites/proc/set_volume(datum/source, amount)
nanite_volume = clamp(amount, 0, max_nanites)
/datum/component/nanites/proc/set_max_volume(datum/source, amount)
max_nanites = max(1, max_nanites)
/datum/component/nanites/proc/set_cloud(datum/source, amount)
cloud_id = clamp(amount, 0, 100)
/datum/component/nanites/proc/set_cloud_sync(datum/source, method)
switch(method)
if(NANITE_CLOUD_TOGGLE)
cloud_active = !cloud_active
if(NANITE_CLOUD_DISABLE)
cloud_active = FALSE
if(NANITE_CLOUD_ENABLE)
cloud_active = TRUE
/datum/component/nanites/proc/set_safety(datum/source, amount)
safety_threshold = clamp(amount, 0, max_nanites)
/datum/component/nanites/proc/set_regen(datum/source, amount)
regen_rate = amount
/datum/component/nanites/proc/confirm_nanites()
return TRUE //yup i exist
/datum/component/nanites/proc/get_data(list/nanite_data)
nanite_data["nanite_volume"] = nanite_volume
nanite_data["max_nanites"] = max_nanites
nanite_data["cloud_id"] = cloud_id
nanite_data["regen_rate"] = regen_rate
nanite_data["safety_threshold"] = safety_threshold
nanite_data["stealth"] = stealth
/datum/component/nanites/proc/get_programs(datum/source, list/nanite_programs)
nanite_programs |= programs
/datum/component/nanites/proc/add_research()
var/research_value = NANITE_BASE_RESEARCH
if(!ishuman(host_mob))
if(!iscarbon(host_mob))
research_value *= 0.4
else
research_value *= 0.8
if(!host_mob.client)
research_value *= 0.5
if(host_mob.stat == DEAD)
research_value *= 0.75
SSresearch.science_tech.add_point_list(list(TECHWEB_POINT_TYPE_NANITES = research_value))
/datum/component/nanites/proc/nanite_scan(datum/source, mob/user, full_scan)
if(!full_scan)
if(!stealth)
to_chat(user, "<span class='notice'><b>Nanites Detected</b></span>")
to_chat(user, "<span class='notice'>Saturation: [nanite_volume]/[max_nanites]</span>")
return TRUE
else
to_chat(user, "<span class='info'>NANITES DETECTED</span>")
to_chat(user, "<span class='info'>================</span>")
to_chat(user, "<span class='info'>Saturation: [nanite_volume]/[max_nanites]</span>")
to_chat(user, "<span class='info'>Safety Threshold: [safety_threshold]</span>")
to_chat(user, "<span class='info'>Cloud ID: [cloud_id ? cloud_id : "None"]</span>")
to_chat(user, "<span class='info'>Cloud Sync: [cloud_active ? "Active" : "Disabled"]</span>")
to_chat(user, "<span class='info'>================</span>")
to_chat(user, "<span class='info'>Program List:</span>")
if(!diagnostics)
to_chat(user, "<span class='alert'>Diagnostics Disabled</span>")
else
for(var/X in programs)
var/datum/nanite_program/NP = X
to_chat(user, "<span class='info'><b>[NP.name]</b> | [NP.activated ? "Active" : "Inactive"]</span>")
return TRUE
/datum/component/nanites/proc/nanite_ui_data(datum/source, list/data, scan_level)
data["has_nanites"] = TRUE
data["nanite_volume"] = nanite_volume
data["regen_rate"] = regen_rate
data["safety_threshold"] = safety_threshold
data["cloud_id"] = cloud_id
data["cloud_active"] = cloud_active
var/list/mob_programs = list()
var/id = 1
for(var/X in programs)
var/datum/nanite_program/P = X
var/list/mob_program = list()
mob_program["name"] = P.name
mob_program["desc"] = P.desc
mob_program["id"] = id
if(scan_level >= 2)
mob_program["activated"] = P.activated
mob_program["use_rate"] = P.use_rate
mob_program["can_trigger"] = P.can_trigger
mob_program["trigger_cost"] = P.trigger_cost
mob_program["trigger_cooldown"] = P.trigger_cooldown / 10
if(scan_level >= 3)
mob_program["timer_restart"] = P.timer_restart / 10
mob_program["timer_shutdown"] = P.timer_shutdown / 10
mob_program["timer_trigger"] = P.timer_trigger / 10
mob_program["timer_trigger_delay"] = P.timer_trigger_delay / 10
var/list/extra_settings = P.get_extra_settings_frontend()
mob_program["extra_settings"] = extra_settings
if(LAZYLEN(extra_settings))
mob_program["has_extra_settings"] = TRUE
else
mob_program["has_extra_settings"] = FALSE
if(scan_level >= 4)
mob_program["activation_code"] = P.activation_code
mob_program["deactivation_code"] = P.deactivation_code
mob_program["kill_code"] = P.kill_code
mob_program["trigger_code"] = P.trigger_code
var/list/rules = list()
var/rule_id = 1
for(var/Z in P.rules)
var/datum/nanite_rule/nanite_rule = Z
var/list/rule = list()
rule["display"] = nanite_rule.display()
rule["program_id"] = id
rule["id"] = rule_id
rules += list(rule)
rule_id++
mob_program["rules"] = rules
if(LAZYLEN(rules))
mob_program["has_rules"] = TRUE
id++
mob_programs += list(mob_program)
data["mob_programs"] = mob_programs
/datum/component/nanites
dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
var/mob/living/host_mob
var/nanite_volume = 100 //amount of nanites in the system, used as fuel for nanite programs
var/max_nanites = 500 //maximum amount of nanites in the system
var/regen_rate = 0.5 //nanites generated per second
var/safety_threshold = 50 //how low nanites will get before they stop processing/triggering
var/cloud_id = 0 //0 if not connected to the cloud, 1-100 to set a determined cloud backup to draw from
var/cloud_active = TRUE //if false, won't sync to the cloud
var/next_sync = 0
var/list/datum/nanite_program/programs = list()
var/max_programs = NANITE_PROGRAM_LIMIT
var/list/datum/nanite_program/protocol/protocols = list() ///Separate list of protocol programs, to avoid looping through the whole programs list when cheking for conflicts
var/start_time = 0 ///Timestamp to when the nanites were first inserted in the host
var/stealth = FALSE //if TRUE, does not appear on HUDs and health scans
var/diagnostics = TRUE //if TRUE, displays program list when scanned by nanite scanners
/datum/component/nanites/Initialize(amount = 100, cloud = 0)
if(!isliving(parent) && !istype(parent, /datum/nanite_cloud_backup))
return COMPONENT_INCOMPATIBLE
nanite_volume = amount
cloud_id = cloud
//Nanites without hosts are non-interactive through normal means
if(isliving(parent))
host_mob = parent
if(!(host_mob.mob_biotypes & (MOB_ORGANIC|MOB_UNDEAD))) //Shouldn't happen, but this avoids HUD runtimes in case a silicon gets them somehow.
return COMPONENT_INCOMPATIBLE
start_time = world.time
host_mob.hud_set_nanite_indicator()
START_PROCESSING(SSnanites, src)
if(cloud_id && cloud_active)
cloud_sync()
/datum/component/nanites/RegisterWithParent()
RegisterSignal(parent, COMSIG_HAS_NANITES, .proc/confirm_nanites)
RegisterSignal(parent, COMSIG_NANITE_IS_STEALTHY, .proc/check_stealth)
RegisterSignal(parent, COMSIG_NANITE_DELETE, .proc/delete_nanites)
RegisterSignal(parent, COMSIG_NANITE_UI_DATA, .proc/nanite_ui_data)
RegisterSignal(parent, COMSIG_NANITE_GET_PROGRAMS, .proc/get_programs)
RegisterSignal(parent, COMSIG_NANITE_SET_VOLUME, .proc/set_volume)
RegisterSignal(parent, COMSIG_NANITE_ADJUST_VOLUME, .proc/adjust_nanites)
RegisterSignal(parent, COMSIG_NANITE_SET_MAX_VOLUME, .proc/set_max_volume)
RegisterSignal(parent, COMSIG_NANITE_SET_CLOUD, .proc/set_cloud)
RegisterSignal(parent, COMSIG_NANITE_SET_CLOUD_SYNC, .proc/set_cloud_sync)
RegisterSignal(parent, COMSIG_NANITE_SET_SAFETY, .proc/set_safety)
RegisterSignal(parent, COMSIG_NANITE_SET_REGEN, .proc/set_regen)
RegisterSignal(parent, COMSIG_NANITE_ADD_PROGRAM, .proc/add_program)
RegisterSignal(parent, COMSIG_NANITE_SCAN, .proc/nanite_scan)
RegisterSignal(parent, COMSIG_NANITE_SYNC, .proc/sync)
if(isliving(parent))
RegisterSignal(parent, COMSIG_ATOM_EMP_ACT, .proc/on_emp)
RegisterSignal(parent, COMSIG_MOB_DEATH, .proc/on_death)
RegisterSignal(parent, COMSIG_MOB_ALLOWED, .proc/check_access)
RegisterSignal(parent, COMSIG_LIVING_ELECTROCUTE_ACT, .proc/on_shock)
RegisterSignal(parent, COMSIG_LIVING_MINOR_SHOCK, .proc/on_minor_shock)
RegisterSignal(parent, COMSIG_SPECIES_GAIN, .proc/check_viable_biotype)
RegisterSignal(parent, COMSIG_NANITE_SIGNAL, .proc/receive_signal)
RegisterSignal(parent, COMSIG_NANITE_COMM_SIGNAL, .proc/receive_comm_signal)
/datum/component/nanites/UnregisterFromParent()
UnregisterSignal(parent, list(COMSIG_HAS_NANITES,
COMSIG_NANITE_IS_STEALTHY,
COMSIG_NANITE_DELETE,
COMSIG_NANITE_UI_DATA,
COMSIG_NANITE_GET_PROGRAMS,
COMSIG_NANITE_SET_VOLUME,
COMSIG_NANITE_ADJUST_VOLUME,
COMSIG_NANITE_SET_MAX_VOLUME,
COMSIG_NANITE_SET_CLOUD,
COMSIG_NANITE_SET_CLOUD_SYNC,
COMSIG_NANITE_SET_SAFETY,
COMSIG_NANITE_SET_REGEN,
COMSIG_NANITE_ADD_PROGRAM,
COMSIG_NANITE_SCAN,
COMSIG_NANITE_SYNC,
COMSIG_ATOM_EMP_ACT,
COMSIG_MOB_DEATH,
COMSIG_MOB_ALLOWED,
COMSIG_LIVING_ELECTROCUTE_ACT,
COMSIG_LIVING_MINOR_SHOCK,
COMSIG_MOVABLE_HEAR,
COMSIG_SPECIES_GAIN,
COMSIG_NANITE_SIGNAL,
COMSIG_NANITE_COMM_SIGNAL))
/datum/component/nanites/Destroy()
STOP_PROCESSING(SSnanites, src)
QDEL_LIST(programs)
if(host_mob)
set_nanite_bar(TRUE)
host_mob.hud_set_nanite_indicator()
host_mob = null
return ..()
/datum/component/nanites/InheritComponent(datum/component/nanites/new_nanites, i_am_original, amount, cloud)
if(new_nanites)
adjust_nanites(null, new_nanites.nanite_volume)
else
adjust_nanites(null, amount) //just add to the nanite volume
/datum/component/nanites/process()
if(!IS_IN_STASIS(host_mob))
adjust_nanites(null, regen_rate)
add_research()
for(var/X in programs)
var/datum/nanite_program/NP = X
NP.on_process()
if(cloud_id && cloud_active && world.time > next_sync)
cloud_sync()
next_sync = world.time + NANITE_SYNC_DELAY
set_nanite_bar()
/datum/component/nanites/proc/delete_nanites()
qdel(src)
//Syncs the nanite component to another, making it so programs are the same with the same programming (except activation status)
/datum/component/nanites/proc/sync(datum/signal_source, datum/component/nanites/source, full_overwrite = TRUE, copy_activation = FALSE)
var/list/programs_to_remove = programs.Copy()
var/list/programs_to_add = source.programs.Copy()
for(var/X in programs)
var/datum/nanite_program/NP = X
for(var/Y in programs_to_add)
var/datum/nanite_program/SNP = Y
if(NP.type == SNP.type)
programs_to_remove -= NP
programs_to_add -= SNP
SNP.copy_programming(NP, copy_activation)
break
if(full_overwrite)
for(var/X in programs_to_remove)
qdel(X)
for(var/X in programs_to_add)
var/datum/nanite_program/SNP = X
add_program(null, SNP.copy())
/datum/component/nanites/proc/cloud_sync()
if(cloud_id)
var/datum/nanite_cloud_backup/backup = SSnanites.get_cloud_backup(cloud_id)
if(backup)
var/datum/component/nanites/cloud_copy = backup.nanites
if(cloud_copy)
sync(null, cloud_copy)
return
//Without cloud syncing nanites can accumulate errors and/or defects
if(prob(8) && programs.len)
var/datum/nanite_program/NP = pick(programs)
NP.software_error()
/datum/component/nanites/proc/add_program(datum/source, datum/nanite_program/new_program, datum/nanite_program/source_program)
for(var/X in programs)
var/datum/nanite_program/NP = X
if(NP.unique && NP.type == new_program.type)
qdel(NP)
if(programs.len >= max_programs)
return COMPONENT_PROGRAM_NOT_INSTALLED
if(source_program)
source_program.copy_programming(new_program)
programs += new_program
new_program.on_add(src)
return COMPONENT_PROGRAM_INSTALLED
/datum/component/nanites/proc/consume_nanites(amount, force = FALSE)
if(!force && safety_threshold && (nanite_volume - amount < safety_threshold))
return FALSE
adjust_nanites(null, -amount)
return (nanite_volume > 0)
/datum/component/nanites/proc/adjust_nanites(datum/source, amount)
nanite_volume = clamp(nanite_volume + amount, 0, max_nanites)
if(nanite_volume <= 0) //oops we ran out
qdel(src)
/datum/component/nanites/proc/set_nanite_bar(remove = FALSE)
var/image/holder = host_mob.hud_list[DIAG_NANITE_FULL_HUD]
var/icon/I = icon(host_mob.icon, host_mob.icon_state, host_mob.dir)
holder.pixel_y = I.Height() - world.icon_size
holder.icon_state = null
if(remove || stealth)
return //bye icon
var/nanite_percent = (nanite_volume / max_nanites) * 100
nanite_percent = clamp(CEILING(nanite_percent, 10), 10, 100)
holder.icon_state = "nanites[nanite_percent]"
/datum/component/nanites/proc/on_emp(datum/source, severity)
nanite_volume *= (rand(60, 90) * 0.01) //Lose 10-40% of nanites
adjust_nanites(null, -(rand(5, 50))) //Lose 5-50 flat nanite volume
if(prob(40/severity))
cloud_id = 0
for(var/X in programs)
var/datum/nanite_program/NP = X
NP.on_emp(severity)
/datum/component/nanites/proc/on_shock(datum/source, shock_damage, siemens_coeff = 1, flags = NONE)
if(flags & SHOCK_ILLUSION || shock_damage < 1)
return
if(!HAS_TRAIT_NOT_FROM(host_mob, TRAIT_SHOCKIMMUNE, "nanites"))//Another shock protection must protect nanites too, but nanites protect only host
nanite_volume *= (rand(45, 80) * 0.01) //Lose 20-55% of nanites
adjust_nanites(null, -(rand(5, 50))) //Lose 5-50 flat nanite volume
for(var/X in programs)
var/datum/nanite_program/NP = X
NP.on_shock(shock_damage)
/datum/component/nanites/proc/on_minor_shock(datum/source)
adjust_nanites(null, -(rand(5, 15))) //Lose 5-15 flat nanite volume
for(var/X in programs)
var/datum/nanite_program/NP = X
NP.on_minor_shock()
/datum/component/nanites/proc/check_stealth(datum/source)
return stealth
/datum/component/nanites/proc/on_death(datum/source, gibbed)
for(var/X in programs)
var/datum/nanite_program/NP = X
NP.on_death(gibbed)
/datum/component/nanites/proc/receive_signal(datum/source, code, source = "an unidentified source")
for(var/X in programs)
var/datum/nanite_program/NP = X
NP.receive_signal(code, source)
/datum/component/nanites/proc/receive_comm_signal(datum/source, comm_code, comm_message, comm_source = "an unidentified source")
for(var/X in programs)
if(istype(X, /datum/nanite_program/comm))
var/datum/nanite_program/comm/NP = X
NP.receive_comm_signal(comm_code, comm_message, comm_source)
/datum/component/nanites/proc/check_viable_biotype()
if(!(host_mob.mob_biotypes & (MOB_ORGANIC|MOB_UNDEAD)))
qdel(src) //bodytype no longer sustains nanites
/datum/component/nanites/proc/check_access(datum/source, obj/O)
for(var/datum/nanite_program/access/access_program in programs)
if(access_program.activated)
return O.check_access_list(access_program.access)
else
return FALSE
return FALSE
/datum/component/nanites/proc/set_volume(datum/source, amount)
nanite_volume = clamp(amount, 0, max_nanites)
/datum/component/nanites/proc/set_max_volume(datum/source, amount)
max_nanites = max(1, max_nanites)
/datum/component/nanites/proc/set_cloud(datum/source, amount)
cloud_id = clamp(amount, 0, 100)
/datum/component/nanites/proc/set_cloud_sync(datum/source, method)
switch(method)
if(NANITE_CLOUD_TOGGLE)
cloud_active = !cloud_active
if(NANITE_CLOUD_DISABLE)
cloud_active = FALSE
if(NANITE_CLOUD_ENABLE)
cloud_active = TRUE
/datum/component/nanites/proc/set_safety(datum/source, amount)
safety_threshold = clamp(amount, 0, max_nanites)
/datum/component/nanites/proc/set_regen(datum/source, amount)
regen_rate = amount
/datum/component/nanites/proc/confirm_nanites()
return TRUE //yup i exist
/datum/component/nanites/proc/get_data(list/nanite_data)
nanite_data["nanite_volume"] = nanite_volume
nanite_data["max_nanites"] = max_nanites
nanite_data["cloud_id"] = cloud_id
nanite_data["regen_rate"] = regen_rate
nanite_data["safety_threshold"] = safety_threshold
nanite_data["stealth"] = stealth
/datum/component/nanites/proc/get_programs(datum/source, list/nanite_programs)
nanite_programs |= programs
/datum/component/nanites/proc/add_research()
var/research_value = NANITE_BASE_RESEARCH
if(!ishuman(host_mob))
if(!iscarbon(host_mob))
research_value *= 0.4
else
research_value *= 0.8
if(!host_mob.client)
research_value *= 0.5
if(host_mob.stat == DEAD)
research_value *= 0.75
SSresearch.science_tech.add_point_list(list(TECHWEB_POINT_TYPE_NANITES = research_value))
/datum/component/nanites/proc/nanite_scan(datum/source, mob/user, full_scan)
if(!full_scan)
if(!stealth)
to_chat(user, "<span class='notice'><b>Nanites Detected</b></span>")
to_chat(user, "<span class='notice'>Saturation: [nanite_volume]/[max_nanites]</span>")
return TRUE
else
to_chat(user, "<span class='info'>NANITES DETECTED</span>")
to_chat(user, "<span class='info'>================</span>")
to_chat(user, "<span class='info'>Saturation: [nanite_volume]/[max_nanites]</span>")
to_chat(user, "<span class='info'>Safety Threshold: [safety_threshold]</span>")
to_chat(user, "<span class='info'>Cloud ID: [cloud_id ? cloud_id : "None"]</span>")
to_chat(user, "<span class='info'>Cloud Sync: [cloud_active ? "Active" : "Disabled"]</span>")
to_chat(user, "<span class='info'>================</span>")
to_chat(user, "<span class='info'>Program List:</span>")
if(!diagnostics)
to_chat(user, "<span class='alert'>Diagnostics Disabled</span>")
else
for(var/X in programs)
var/datum/nanite_program/NP = X
to_chat(user, "<span class='info'><b>[NP.name]</b> | [NP.activated ? "Active" : "Inactive"]</span>")
return TRUE
/datum/component/nanites/proc/nanite_ui_data(datum/source, list/data, scan_level)
data["has_nanites"] = TRUE
data["nanite_volume"] = nanite_volume
data["regen_rate"] = regen_rate
data["safety_threshold"] = safety_threshold
data["cloud_id"] = cloud_id
data["cloud_active"] = cloud_active
var/list/mob_programs = list()
var/id = 1
for(var/X in programs)
var/datum/nanite_program/P = X
var/list/mob_program = list()
mob_program["name"] = P.name
mob_program["desc"] = P.desc
mob_program["id"] = id
if(scan_level >= 2)
mob_program["activated"] = P.activated
mob_program["use_rate"] = P.use_rate
mob_program["can_trigger"] = P.can_trigger
mob_program["trigger_cost"] = P.trigger_cost
mob_program["trigger_cooldown"] = P.trigger_cooldown / 10
if(scan_level >= 3)
mob_program["timer_restart"] = P.timer_restart / 10
mob_program["timer_shutdown"] = P.timer_shutdown / 10
mob_program["timer_trigger"] = P.timer_trigger / 10
mob_program["timer_trigger_delay"] = P.timer_trigger_delay / 10
var/list/extra_settings = P.get_extra_settings_frontend()
mob_program["extra_settings"] = extra_settings
if(LAZYLEN(extra_settings))
mob_program["has_extra_settings"] = TRUE
else
mob_program["has_extra_settings"] = FALSE
if(scan_level >= 4)
mob_program["activation_code"] = P.activation_code
mob_program["deactivation_code"] = P.deactivation_code
mob_program["kill_code"] = P.kill_code
mob_program["trigger_code"] = P.trigger_code
var/list/rules = list()
var/rule_id = 1
for(var/Z in P.rules)
var/datum/nanite_rule/nanite_rule = Z
var/list/rule = list()
rule["display"] = nanite_rule.display()
rule["program_id"] = id
rule["id"] = rule_id
rules += list(rule)
rule_id++
mob_program["rules"] = rules
if(LAZYLEN(rules))
mob_program["has_rules"] = TRUE
id++
mob_programs += list(mob_program)
data["mob_programs"] = mob_programs
+66 -66
View File
@@ -1,66 +1,66 @@
//Thing meant for allowing datums and objects to access an NTnet network datum.
/datum/proc/ntnet_receive(datum/netdata/data)
return
/datum/proc/ntnet_receive_broadcast(datum/netdata/data)
return
/datum/proc/ntnet_send(datum/netdata/data, netid)
var/datum/component/ntnet_interface/NIC = GetComponent(/datum/component/ntnet_interface)
if(!NIC)
return FALSE
return NIC.__network_send(data, netid)
/datum/component/ntnet_interface
var/hardware_id //text. this is the true ID. do not change this. stuff like ID forgery can be done manually.
var/network_name = "" //text
var/list/networks_connected_by_id = list() //id = datum/ntnet
var/differentiate_broadcast = TRUE //If false, broadcasts go to ntnet_receive. NOT RECOMMENDED.
/datum/component/ntnet_interface/Initialize(force_name = "NTNet Device", autoconnect_station_network = TRUE) //Don't force ID unless you know what you're doing!
hardware_id = "[SSnetworks.get_next_HID()]"
network_name = force_name
if(!SSnetworks.register_interface(src))
. = COMPONENT_INCOMPATIBLE
CRASH("Unable to register NTNet interface. Interface deleted.")
if(autoconnect_station_network)
register_connection(SSnetworks.station_network)
/datum/component/ntnet_interface/Destroy()
unregister_all_connections()
SSnetworks.unregister_interface(src)
return ..()
/datum/component/ntnet_interface/proc/__network_receive(datum/netdata/data) //Do not directly proccall!
SEND_SIGNAL(parent, COMSIG_COMPONENT_NTNET_RECEIVE, data)
if(differentiate_broadcast && data.broadcast)
parent.ntnet_receive_broadcast(data)
else
parent.ntnet_receive(data)
/datum/component/ntnet_interface/proc/__network_send(datum/netdata/data, netid) //Do not directly proccall!
if(netid)
if(networks_connected_by_id[netid])
var/datum/ntnet/net = networks_connected_by_id[netid]
return net.process_data_transmit(src, data)
return FALSE
for(var/i in networks_connected_by_id)
var/datum/ntnet/net = networks_connected_by_id[i]
net.process_data_transmit(src, data)
return TRUE
/datum/component/ntnet_interface/proc/register_connection(datum/ntnet/net)
if(net.interface_connect(src))
networks_connected_by_id[net.network_id] = net
return TRUE
/datum/component/ntnet_interface/proc/unregister_all_connections()
for(var/i in networks_connected_by_id)
unregister_connection(networks_connected_by_id[i])
return TRUE
/datum/component/ntnet_interface/proc/unregister_connection(datum/ntnet/net)
net.interface_disconnect(src)
networks_connected_by_id -= net.network_id
return TRUE
//Thing meant for allowing datums and objects to access an NTnet network datum.
/datum/proc/ntnet_receive(datum/netdata/data)
return
/datum/proc/ntnet_receive_broadcast(datum/netdata/data)
return
/datum/proc/ntnet_send(datum/netdata/data, netid)
var/datum/component/ntnet_interface/NIC = GetComponent(/datum/component/ntnet_interface)
if(!NIC)
return FALSE
return NIC.__network_send(data, netid)
/datum/component/ntnet_interface
var/hardware_id //text. this is the true ID. do not change this. stuff like ID forgery can be done manually.
var/network_name = "" //text
var/list/networks_connected_by_id = list() //id = datum/ntnet
var/differentiate_broadcast = TRUE //If false, broadcasts go to ntnet_receive. NOT RECOMMENDED.
/datum/component/ntnet_interface/Initialize(force_name = "NTNet Device", autoconnect_station_network = TRUE) //Don't force ID unless you know what you're doing!
hardware_id = "[SSnetworks.get_next_HID()]"
network_name = force_name
if(!SSnetworks.register_interface(src))
. = COMPONENT_INCOMPATIBLE
CRASH("Unable to register NTNet interface. Interface deleted.")
if(autoconnect_station_network)
register_connection(SSnetworks.station_network)
/datum/component/ntnet_interface/Destroy()
unregister_all_connections()
SSnetworks.unregister_interface(src)
return ..()
/datum/component/ntnet_interface/proc/__network_receive(datum/netdata/data) //Do not directly proccall!
SEND_SIGNAL(parent, COMSIG_COMPONENT_NTNET_RECEIVE, data)
if(differentiate_broadcast && data.broadcast)
parent.ntnet_receive_broadcast(data)
else
parent.ntnet_receive(data)
/datum/component/ntnet_interface/proc/__network_send(datum/netdata/data, netid) //Do not directly proccall!
if(netid)
if(networks_connected_by_id[netid])
var/datum/ntnet/net = networks_connected_by_id[netid]
return net.process_data_transmit(src, data)
return FALSE
for(var/i in networks_connected_by_id)
var/datum/ntnet/net = networks_connected_by_id[i]
net.process_data_transmit(src, data)
return TRUE
/datum/component/ntnet_interface/proc/register_connection(datum/ntnet/net)
if(net.interface_connect(src))
networks_connected_by_id[net.network_id] = net
return TRUE
/datum/component/ntnet_interface/proc/unregister_all_connections()
for(var/i in networks_connected_by_id)
unregister_connection(networks_connected_by_id[i])
return TRUE
/datum/component/ntnet_interface/proc/unregister_connection(datum/ntnet/net)
net.interface_disconnect(src)
networks_connected_by_id -= net.network_id
return TRUE
+395 -395
View File
@@ -1,395 +1,395 @@
/datum/component/riding
var/last_vehicle_move = 0 //used for move delays
var/last_move_diagonal = FALSE
var/vehicle_move_delay = 2 //tick delay between movements, lower = faster, higher = slower
var/keytype
var/slowed = FALSE
var/slowvalue = 1
var/list/riding_offsets = list() //position_of_user = list(dir = list(px, py)), or RIDING_OFFSET_ALL for a generic one.
var/list/directional_vehicle_layers = list() //["[DIRECTION]"] = layer. Don't set it for a direction for default, set a direction to null for no change.
var/list/directional_vehicle_offsets = list() //same as above but instead of layer you have a list(px, py)
var/list/allowed_turf_typecache
var/list/forbid_turf_typecache //allow typecache for only certain turfs, forbid to allow all but those. allow only certain turfs will take precedence.
var/allow_one_away_from_valid_turf = TRUE //allow moving one tile away from a valid turf but not more.
var/override_allow_spacemove = FALSE
var/drive_verb = "drive"
var/ride_check_rider_incapacitated = FALSE
var/ride_check_rider_restrained = FALSE
var/ride_check_ridden_incapacitated = FALSE
var/ride_check_ridden_restrained = FALSE
var/del_on_unbuckle_all = FALSE
/// If the "vehicle" is a mob, respect MOBILITY_MOVE on said mob.
var/respect_mob_mobility = TRUE
/datum/component/riding/Initialize()
if(!ismovable(parent))
return COMPONENT_INCOMPATIBLE
RegisterSignal(parent, COMSIG_MOVABLE_BUCKLE, .proc/vehicle_mob_buckle)
RegisterSignal(parent, COMSIG_MOVABLE_UNBUCKLE, .proc/vehicle_mob_unbuckle)
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/vehicle_moved)
/datum/component/riding/proc/vehicle_mob_unbuckle(datum/source, mob/living/M, force = FALSE)
var/atom/movable/AM = parent
restore_position(M)
unequip_buckle_inhands(M)
if(del_on_unbuckle_all && !AM.has_buckled_mobs())
qdel(src)
/datum/component/riding/proc/vehicle_mob_buckle(datum/source, mob/living/M, force = FALSE)
handle_vehicle_offsets()
/datum/component/riding/proc/handle_vehicle_layer()
var/atom/movable/AM = parent
var/static/list/defaults = list(TEXT_NORTH = OBJ_LAYER, TEXT_SOUTH = ABOVE_MOB_LAYER, TEXT_EAST = ABOVE_MOB_LAYER, TEXT_WEST = ABOVE_MOB_LAYER)
. = defaults["[AM.dir]"]
if(directional_vehicle_layers["[AM.dir]"])
. = directional_vehicle_layers["[AM.dir]"]
if(isnull(.)) //you can set it to null to not change it.
. = AM.layer
AM.layer = .
/datum/component/riding/proc/set_vehicle_dir_layer(dir, layer)
directional_vehicle_layers["[dir]"] = layer
/datum/component/riding/proc/vehicle_moved(datum/source)
var/atom/movable/AM = parent
for(var/i in AM.buckled_mobs)
ride_check(i)
handle_vehicle_offsets()
handle_vehicle_layer()
/datum/component/riding/proc/ride_check(mob/living/M)
var/atom/movable/AM = parent
var/mob/AMM = AM
var/kick_us_off
if((ride_check_rider_restrained && M.restrained(TRUE)) || (ride_check_rider_incapacitated && M.incapacitated(TRUE, TRUE)))
kick_us_off = TRUE
if(kick_us_off || (istype(AMM) && ((ride_check_ridden_restrained && AMM.restrained(TRUE)) || (ride_check_ridden_incapacitated && AMM.incapacitated(TRUE, TRUE)))))
M.visible_message("<span class='warning'>[M] falls off of [AM]!</span>", \
"<span class='warning'>You fall off of [AM]!</span>")
AM.unbuckle_mob(M)
return TRUE
/datum/component/riding/proc/force_dismount(mob/living/M, gentle = FALSE)
var/atom/movable/AM = parent
AM.unbuckle_mob(M)
if(isanimal(AM) || iscyborg(AM))
var/turf/target = get_edge_target_turf(AM, AM.dir)
var/turf/targetm = get_step(get_turf(AM), AM.dir)
M.Move(targetm)
if(gentle)
M.visible_message("<span class='warning'>[M] is thrown clear of [AM]!</span>", \
"<span class='warning'>You're thrown clear of [AM]!</span>")
M.throw_at(target, 8, 3, AM, gentle = TRUE)
else
M.visible_message("<span class='warning'>[M] is thrown violently from [AM]!</span>", \
"<span class='warning'>You're thrown violently from [AM]!</span>")
M.throw_at(target, 14, 5, AM, gentle = FALSE)
M.Knockdown(3 SECONDS)
/datum/component/riding/proc/handle_vehicle_offsets()
var/atom/movable/AM = parent
var/AM_dir = "[AM.dir]"
var/passindex = 0
if(AM.has_buckled_mobs())
for(var/m in AM.buckled_mobs)
passindex++
var/mob/living/buckled_mob = m
var/list/offsets = get_offsets(passindex)
var/rider_dir = get_rider_dir(passindex)
buckled_mob.setDir(rider_dir)
dir_loop:
for(var/offsetdir in offsets)
if(offsetdir == AM_dir)
var/list/diroffsets = offsets[offsetdir]
buckled_mob.pixel_x = diroffsets[1]
if(diroffsets.len >= 2)
buckled_mob.pixel_y = diroffsets[2]
if(diroffsets.len == 3)
buckled_mob.layer = diroffsets[3]
break dir_loop
var/list/static/default_vehicle_pixel_offsets = list(TEXT_NORTH = list(0, 0), TEXT_SOUTH = list(0, 0), TEXT_EAST = list(0, 0), TEXT_WEST = list(0, 0))
var/px = default_vehicle_pixel_offsets[AM_dir]
var/py = default_vehicle_pixel_offsets[AM_dir]
if(directional_vehicle_offsets[AM_dir])
if(isnull(directional_vehicle_offsets[AM_dir]))
px = AM.pixel_x
py = AM.pixel_y
else
px = directional_vehicle_offsets[AM_dir][1]
py = directional_vehicle_offsets[AM_dir][2]
AM.pixel_x = px
AM.pixel_y = py
/datum/component/riding/proc/set_vehicle_dir_offsets(dir, x, y)
directional_vehicle_offsets["[dir]"] = list(x, y)
//Override this to set your vehicle's various pixel offsets
/datum/component/riding/proc/get_offsets(pass_index) // list(dir = x, y, layer)
. = list(TEXT_NORTH = list(0, 0), TEXT_SOUTH = list(0, 0), TEXT_EAST = list(0, 0), TEXT_WEST = list(0, 0))
if(riding_offsets["[pass_index]"])
. = riding_offsets["[pass_index]"]
else if(riding_offsets["[RIDING_OFFSET_ALL]"])
. = riding_offsets["[RIDING_OFFSET_ALL]"]
/datum/component/riding/proc/set_riding_offsets(index, list/offsets)
if(!islist(offsets))
return FALSE
riding_offsets["[index]"] = offsets
//Override this to set the passengers/riders dir based on which passenger they are.
//ie: rider facing the vehicle's dir, but passenger 2 facing backwards, etc.
/datum/component/riding/proc/get_rider_dir(pass_index)
var/atom/movable/AM = parent
return AM.dir
//KEYS
/datum/component/riding/proc/keycheck(mob/user)
return !keytype || user.is_holding_item_of_type(keytype)
//BUCKLE HOOKS
/datum/component/riding/proc/restore_position(mob/living/buckled_mob)
if(buckled_mob)
buckled_mob.pixel_x = 0
buckled_mob.pixel_y = 0
if(buckled_mob.client)
buckled_mob.client.view_size.resetToDefault()
//MOVEMENT
/datum/component/riding/proc/turf_check(turf/next, turf/current)
if(allowed_turf_typecache && !allowed_turf_typecache[next.type])
return (allow_one_away_from_valid_turf && allowed_turf_typecache[current.type])
else if(forbid_turf_typecache && forbid_turf_typecache[next.type])
return (allow_one_away_from_valid_turf && !forbid_turf_typecache[current.type])
return TRUE
/datum/component/riding/proc/handle_ride(mob/user, direction)
var/atom/movable/AM = parent
if(user.incapacitated())
Unbuckle(user)
return
if(world.time < last_vehicle_move + ((last_move_diagonal? 2 : 1) * vehicle_move_delay))
return
last_vehicle_move = world.time
if(keycheck(user))
var/turf/next = get_step(AM, direction)
var/turf/current = get_turf(AM)
if(!istype(next) || !istype(current))
return //not happening.
if(!turf_check(next, current))
to_chat(user, "<span class='warning'>Your \the [AM] can not go onto [next]!</span>")
return
if(!Process_Spacemove(direction) || !isturf(AM.loc))
return
if(isliving(AM) && respect_mob_mobility)
var/mob/living/M = AM
if(!(M.mobility_flags & MOBILITY_MOVE))
return
step(AM, direction)
if((direction & (direction - 1)) && (AM.loc == next)) //moved diagonally
last_move_diagonal = TRUE
else
last_move_diagonal = FALSE
handle_vehicle_layer()
handle_vehicle_offsets()
else
to_chat(user, "<span class='warning'>You'll need a special item in one of your hands to [drive_verb] [AM].</span>")
/datum/component/riding/proc/Unbuckle(atom/movable/M)
addtimer(CALLBACK(parent, /atom/movable/.proc/unbuckle_mob, M), 0, TIMER_UNIQUE)
/datum/component/riding/proc/Process_Spacemove(direction)
var/atom/movable/AM = parent
return override_allow_spacemove || AM.has_gravity()
/datum/component/riding/proc/account_limbs(mob/living/M)
if(M.get_num_legs() < 2 && !slowed)
vehicle_move_delay = vehicle_move_delay + slowvalue
slowed = TRUE
else if(slowed)
vehicle_move_delay = vehicle_move_delay - slowvalue
slowed = FALSE
///////Yes, I said humans. No, this won't end well...//////////
/datum/component/riding/human
del_on_unbuckle_all = TRUE
/datum/component/riding/human/Initialize()
. = ..()
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)
unequip_buckle_inhands(parent)
var/mob/living/carbon/human/H = parent
H.remove_movespeed_modifier(/datum/movespeed_modifier/human_carry)
. = ..()
/datum/component/riding/human/vehicle_mob_buckle(datum/source, mob/living/M, force = FALSE)
. = ..()
var/mob/living/carbon/human/H = parent
H.add_movespeed_modifier(/datum/movespeed_modifier/human_carry)
/datum/component/riding/human/proc/on_host_unarmed_melee(atom/target)
var/mob/living/carbon/human/H = parent
if(H.a_intent == INTENT_DISARM && (target in H.buckled_mobs))
force_dismount(target)
/datum/component/riding/human/handle_vehicle_layer()
var/atom/movable/AM = parent
if(AM.buckled_mobs && AM.buckled_mobs.len)
for(var/mob/M in AM.buckled_mobs) //ensure proper layering of piggyback and carry, sometimes weird offsets get applied
M.layer = MOB_LAYER
if(!AM.buckle_lying)
if(AM.dir == SOUTH)
AM.layer = ABOVE_MOB_LAYER
else
AM.layer = OBJ_LAYER
else
if(AM.dir == NORTH)
AM.layer = OBJ_LAYER
else
AM.layer = ABOVE_MOB_LAYER
else
AM.layer = MOB_LAYER
/datum/component/riding/human/get_offsets(pass_index)
var/mob/living/carbon/human/H = parent
if(H.buckle_lying)
return list(TEXT_NORTH = list(0, 6), TEXT_SOUTH = list(0, 6), TEXT_EAST = list(0, 6), TEXT_WEST = list(0, 6))
else
return list(TEXT_NORTH = list(0, 6), TEXT_SOUTH = list(0, 6), TEXT_EAST = list(-6, 4), TEXT_WEST = list( 6, 4))
/datum/component/riding/human/force_dismount(mob/living/user)
var/atom/movable/AM = parent
AM.unbuckle_mob(user)
user.Paralyze(60)
user.visible_message("<span class='warning'>[AM] pushes [user] off of [AM.p_them()]!</span>", \
"<span class='warning'>[AM] pushes you off of [AM.p_them()]!</span>")
/datum/component/riding/cyborg
del_on_unbuckle_all = TRUE
/datum/component/riding/cyborg/ride_check(mob/user)
var/atom/movable/AM = parent
if(user.incapacitated())
var/kick = TRUE
if(iscyborg(AM))
var/mob/living/silicon/robot/R = AM
if(R.module && R.module.ride_allow_incapacitated)
kick = FALSE
if(kick)
to_chat(user, "<span class='userdanger'>You fall off of [AM]!</span>")
Unbuckle(user)
return
if(iscarbon(user))
var/mob/living/carbon/carbonuser = user
if(!carbonuser.get_num_arms())
Unbuckle(user)
to_chat(user, "<span class='warning'>You can't grab onto [AM] with no hands!</span>")
return
/datum/component/riding/cyborg/handle_vehicle_layer()
var/atom/movable/AM = parent
if(AM.buckled_mobs && AM.buckled_mobs.len)
if(AM.dir == SOUTH)
AM.layer = ABOVE_MOB_LAYER
else
AM.layer = OBJ_LAYER
else
AM.layer = MOB_LAYER
/datum/component/riding/cyborg/get_offsets(pass_index) // list(dir = x, y, layer)
return list(TEXT_NORTH = list(0, 4), TEXT_SOUTH = list(0, 4), TEXT_EAST = list(-6, 3), TEXT_WEST = list( 6, 3))
/datum/component/riding/cyborg/handle_vehicle_offsets()
var/atom/movable/AM = parent
if(AM.has_buckled_mobs())
for(var/mob/living/M in AM.buckled_mobs)
M.setDir(AM.dir)
if(iscyborg(AM))
var/mob/living/silicon/robot/R = AM
if(istype(R.module))
M.pixel_x = R.module.ride_offset_x[dir2text(AM.dir)]
M.pixel_y = R.module.ride_offset_y[dir2text(AM.dir)]
else
..()
/datum/component/riding/proc/equip_buckle_inhands(mob/living/carbon/human/user, amount_required = 1, riding_target_override = null)
var/atom/movable/AM = parent
var/amount_equipped = 0
for(var/amount_needed = amount_required, amount_needed > 0, amount_needed--)
var/obj/item/riding_offhand/inhand = new /obj/item/riding_offhand(user)
if(!riding_target_override)
inhand.rider = user
else
inhand.rider = riding_target_override
inhand.parent = AM
for(var/obj/item/I in user.held_items) // yes i know this sucks but these are ABSTRACT++ dumbness and i'm not adding a whole new flag for these two meme items
if((I.obj_flags & HAND_ITEM))
qdel(I)
if(user.put_in_hands(inhand, TRUE))
amount_equipped++
else
break
if(amount_equipped >= amount_required)
return TRUE
else
unequip_buckle_inhands(user)
return FALSE
/datum/component/riding/proc/unequip_buckle_inhands(mob/living/carbon/user)
var/atom/movable/AM = parent
for(var/obj/item/riding_offhand/O in user.contents)
if(O.parent != AM)
CRASH("RIDING OFFHAND ON WRONG MOB")
if(O.selfdeleting)
continue
else
qdel(O)
return TRUE
/obj/item/riding_offhand
name = "offhand"
icon = 'icons/obj/items_and_weapons.dmi'
icon_state = "offhand"
w_class = WEIGHT_CLASS_HUGE
item_flags = ABSTRACT | DROPDEL | NOBLUDGEON
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
var/mob/living/carbon/rider
var/mob/living/parent
var/selfdeleting = FALSE
/obj/item/riding_offhand/dropped()
selfdeleting = TRUE
. = ..()
/obj/item/riding_offhand/equipped()
if(loc != rider && loc != parent)
selfdeleting = TRUE
qdel(src)
. = ..()
/obj/item/riding_offhand/Destroy()
var/atom/movable/AM = parent
if(selfdeleting)
if(rider in AM.buckled_mobs)
AM.unbuckle_mob(rider)
. = ..()
/obj/item/riding_offhand/on_thrown(mob/living/carbon/user, atom/target)
if(rider == user)
return //Piggyback user.
user.unbuckle_mob(rider)
if(HAS_TRAIT(user, TRAIT_PACIFISM))
to_chat(user, "<span class='notice'>You gently let go of [rider].</span>")
return
return rider
/datum/component/riding
var/last_vehicle_move = 0 //used for move delays
var/last_move_diagonal = FALSE
var/vehicle_move_delay = 2 //tick delay between movements, lower = faster, higher = slower
var/keytype
var/slowed = FALSE
var/slowvalue = 1
var/list/riding_offsets = list() //position_of_user = list(dir = list(px, py)), or RIDING_OFFSET_ALL for a generic one.
var/list/directional_vehicle_layers = list() //["[DIRECTION]"] = layer. Don't set it for a direction for default, set a direction to null for no change.
var/list/directional_vehicle_offsets = list() //same as above but instead of layer you have a list(px, py)
var/list/allowed_turf_typecache
var/list/forbid_turf_typecache //allow typecache for only certain turfs, forbid to allow all but those. allow only certain turfs will take precedence.
var/allow_one_away_from_valid_turf = TRUE //allow moving one tile away from a valid turf but not more.
var/override_allow_spacemove = FALSE
var/drive_verb = "drive"
var/ride_check_rider_incapacitated = FALSE
var/ride_check_rider_restrained = FALSE
var/ride_check_ridden_incapacitated = FALSE
var/ride_check_ridden_restrained = FALSE
var/del_on_unbuckle_all = FALSE
/// If the "vehicle" is a mob, respect MOBILITY_MOVE on said mob.
var/respect_mob_mobility = TRUE
/datum/component/riding/Initialize()
if(!ismovable(parent))
return COMPONENT_INCOMPATIBLE
RegisterSignal(parent, COMSIG_MOVABLE_BUCKLE, .proc/vehicle_mob_buckle)
RegisterSignal(parent, COMSIG_MOVABLE_UNBUCKLE, .proc/vehicle_mob_unbuckle)
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/vehicle_moved)
/datum/component/riding/proc/vehicle_mob_unbuckle(datum/source, mob/living/M, force = FALSE)
var/atom/movable/AM = parent
restore_position(M)
unequip_buckle_inhands(M)
if(del_on_unbuckle_all && !AM.has_buckled_mobs())
qdel(src)
/datum/component/riding/proc/vehicle_mob_buckle(datum/source, mob/living/M, force = FALSE)
handle_vehicle_offsets()
/datum/component/riding/proc/handle_vehicle_layer()
var/atom/movable/AM = parent
var/static/list/defaults = list(TEXT_NORTH = OBJ_LAYER, TEXT_SOUTH = ABOVE_MOB_LAYER, TEXT_EAST = ABOVE_MOB_LAYER, TEXT_WEST = ABOVE_MOB_LAYER)
. = defaults["[AM.dir]"]
if(directional_vehicle_layers["[AM.dir]"])
. = directional_vehicle_layers["[AM.dir]"]
if(isnull(.)) //you can set it to null to not change it.
. = AM.layer
AM.layer = .
/datum/component/riding/proc/set_vehicle_dir_layer(dir, layer)
directional_vehicle_layers["[dir]"] = layer
/datum/component/riding/proc/vehicle_moved(datum/source)
var/atom/movable/AM = parent
for(var/i in AM.buckled_mobs)
ride_check(i)
handle_vehicle_offsets()
handle_vehicle_layer()
/datum/component/riding/proc/ride_check(mob/living/M)
var/atom/movable/AM = parent
var/mob/AMM = AM
var/kick_us_off
if((ride_check_rider_restrained && M.restrained(TRUE)) || (ride_check_rider_incapacitated && M.incapacitated(TRUE, TRUE)))
kick_us_off = TRUE
if(kick_us_off || (istype(AMM) && ((ride_check_ridden_restrained && AMM.restrained(TRUE)) || (ride_check_ridden_incapacitated && AMM.incapacitated(TRUE, TRUE)))))
M.visible_message("<span class='warning'>[M] falls off of [AM]!</span>", \
"<span class='warning'>You fall off of [AM]!</span>")
AM.unbuckle_mob(M)
return TRUE
/datum/component/riding/proc/force_dismount(mob/living/M, gentle = FALSE)
var/atom/movable/AM = parent
AM.unbuckle_mob(M)
if(isanimal(AM) || iscyborg(AM))
var/turf/target = get_edge_target_turf(AM, AM.dir)
var/turf/targetm = get_step(get_turf(AM), AM.dir)
M.Move(targetm)
if(gentle)
M.visible_message("<span class='warning'>[M] is thrown clear of [AM]!</span>", \
"<span class='warning'>You're thrown clear of [AM]!</span>")
M.throw_at(target, 8, 3, AM, gentle = TRUE)
else
M.visible_message("<span class='warning'>[M] is thrown violently from [AM]!</span>", \
"<span class='warning'>You're thrown violently from [AM]!</span>")
M.throw_at(target, 14, 5, AM, gentle = FALSE)
M.Knockdown(3 SECONDS)
/datum/component/riding/proc/handle_vehicle_offsets()
var/atom/movable/AM = parent
var/AM_dir = "[AM.dir]"
var/passindex = 0
if(AM.has_buckled_mobs())
for(var/m in AM.buckled_mobs)
passindex++
var/mob/living/buckled_mob = m
var/list/offsets = get_offsets(passindex)
var/rider_dir = get_rider_dir(passindex)
buckled_mob.setDir(rider_dir)
dir_loop:
for(var/offsetdir in offsets)
if(offsetdir == AM_dir)
var/list/diroffsets = offsets[offsetdir]
buckled_mob.pixel_x = diroffsets[1]
if(diroffsets.len >= 2)
buckled_mob.pixel_y = diroffsets[2]
if(diroffsets.len == 3)
buckled_mob.layer = diroffsets[3]
break dir_loop
var/list/static/default_vehicle_pixel_offsets = list(TEXT_NORTH = list(0, 0), TEXT_SOUTH = list(0, 0), TEXT_EAST = list(0, 0), TEXT_WEST = list(0, 0))
var/px = default_vehicle_pixel_offsets[AM_dir]
var/py = default_vehicle_pixel_offsets[AM_dir]
if(directional_vehicle_offsets[AM_dir])
if(isnull(directional_vehicle_offsets[AM_dir]))
px = AM.pixel_x
py = AM.pixel_y
else
px = directional_vehicle_offsets[AM_dir][1]
py = directional_vehicle_offsets[AM_dir][2]
AM.pixel_x = px
AM.pixel_y = py
/datum/component/riding/proc/set_vehicle_dir_offsets(dir, x, y)
directional_vehicle_offsets["[dir]"] = list(x, y)
//Override this to set your vehicle's various pixel offsets
/datum/component/riding/proc/get_offsets(pass_index) // list(dir = x, y, layer)
. = list(TEXT_NORTH = list(0, 0), TEXT_SOUTH = list(0, 0), TEXT_EAST = list(0, 0), TEXT_WEST = list(0, 0))
if(riding_offsets["[pass_index]"])
. = riding_offsets["[pass_index]"]
else if(riding_offsets["[RIDING_OFFSET_ALL]"])
. = riding_offsets["[RIDING_OFFSET_ALL]"]
/datum/component/riding/proc/set_riding_offsets(index, list/offsets)
if(!islist(offsets))
return FALSE
riding_offsets["[index]"] = offsets
//Override this to set the passengers/riders dir based on which passenger they are.
//ie: rider facing the vehicle's dir, but passenger 2 facing backwards, etc.
/datum/component/riding/proc/get_rider_dir(pass_index)
var/atom/movable/AM = parent
return AM.dir
//KEYS
/datum/component/riding/proc/keycheck(mob/user)
return !keytype || user.is_holding_item_of_type(keytype)
//BUCKLE HOOKS
/datum/component/riding/proc/restore_position(mob/living/buckled_mob)
if(buckled_mob)
buckled_mob.pixel_x = 0
buckled_mob.pixel_y = 0
if(buckled_mob.client)
buckled_mob.client.view_size.resetToDefault()
//MOVEMENT
/datum/component/riding/proc/turf_check(turf/next, turf/current)
if(allowed_turf_typecache && !allowed_turf_typecache[next.type])
return (allow_one_away_from_valid_turf && allowed_turf_typecache[current.type])
else if(forbid_turf_typecache && forbid_turf_typecache[next.type])
return (allow_one_away_from_valid_turf && !forbid_turf_typecache[current.type])
return TRUE
/datum/component/riding/proc/handle_ride(mob/user, direction)
var/atom/movable/AM = parent
if(user.incapacitated())
Unbuckle(user)
return
if(world.time < last_vehicle_move + ((last_move_diagonal? 2 : 1) * vehicle_move_delay))
return
last_vehicle_move = world.time
if(keycheck(user))
var/turf/next = get_step(AM, direction)
var/turf/current = get_turf(AM)
if(!istype(next) || !istype(current))
return //not happening.
if(!turf_check(next, current))
to_chat(user, "<span class='warning'>Your \the [AM] can not go onto [next]!</span>")
return
if(!Process_Spacemove(direction) || !isturf(AM.loc))
return
if(isliving(AM) && respect_mob_mobility)
var/mob/living/M = AM
if(!(M.mobility_flags & MOBILITY_MOVE))
return
step(AM, direction)
if((direction & (direction - 1)) && (AM.loc == next)) //moved diagonally
last_move_diagonal = TRUE
else
last_move_diagonal = FALSE
handle_vehicle_layer()
handle_vehicle_offsets()
else
to_chat(user, "<span class='warning'>You'll need a special item in one of your hands to [drive_verb] [AM].</span>")
/datum/component/riding/proc/Unbuckle(atom/movable/M)
addtimer(CALLBACK(parent, /atom/movable/.proc/unbuckle_mob, M), 0, TIMER_UNIQUE)
/datum/component/riding/proc/Process_Spacemove(direction)
var/atom/movable/AM = parent
return override_allow_spacemove || AM.has_gravity()
/datum/component/riding/proc/account_limbs(mob/living/M)
if(M.get_num_legs() < 2 && !slowed)
vehicle_move_delay = vehicle_move_delay + slowvalue
slowed = TRUE
else if(slowed)
vehicle_move_delay = vehicle_move_delay - slowvalue
slowed = FALSE
///////Yes, I said humans. No, this won't end well...//////////
/datum/component/riding/human
del_on_unbuckle_all = TRUE
/datum/component/riding/human/Initialize()
. = ..()
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)
unequip_buckle_inhands(parent)
var/mob/living/carbon/human/H = parent
H.remove_movespeed_modifier(/datum/movespeed_modifier/human_carry)
. = ..()
/datum/component/riding/human/vehicle_mob_buckle(datum/source, mob/living/M, force = FALSE)
. = ..()
var/mob/living/carbon/human/H = parent
H.add_movespeed_modifier(/datum/movespeed_modifier/human_carry)
/datum/component/riding/human/proc/on_host_unarmed_melee(atom/target)
var/mob/living/carbon/human/H = parent
if(H.a_intent == INTENT_DISARM && (target in H.buckled_mobs))
force_dismount(target)
/datum/component/riding/human/handle_vehicle_layer()
var/atom/movable/AM = parent
if(AM.buckled_mobs && AM.buckled_mobs.len)
for(var/mob/M in AM.buckled_mobs) //ensure proper layering of piggyback and carry, sometimes weird offsets get applied
M.layer = MOB_LAYER
if(!AM.buckle_lying)
if(AM.dir == SOUTH)
AM.layer = ABOVE_MOB_LAYER
else
AM.layer = OBJ_LAYER
else
if(AM.dir == NORTH)
AM.layer = OBJ_LAYER
else
AM.layer = ABOVE_MOB_LAYER
else
AM.layer = MOB_LAYER
/datum/component/riding/human/get_offsets(pass_index)
var/mob/living/carbon/human/H = parent
if(H.buckle_lying)
return list(TEXT_NORTH = list(0, 6), TEXT_SOUTH = list(0, 6), TEXT_EAST = list(0, 6), TEXT_WEST = list(0, 6))
else
return list(TEXT_NORTH = list(0, 6), TEXT_SOUTH = list(0, 6), TEXT_EAST = list(-6, 4), TEXT_WEST = list( 6, 4))
/datum/component/riding/human/force_dismount(mob/living/user)
var/atom/movable/AM = parent
AM.unbuckle_mob(user)
user.Paralyze(60)
user.visible_message("<span class='warning'>[AM] pushes [user] off of [AM.p_them()]!</span>", \
"<span class='warning'>[AM] pushes you off of [AM.p_them()]!</span>")
/datum/component/riding/cyborg
del_on_unbuckle_all = TRUE
/datum/component/riding/cyborg/ride_check(mob/user)
var/atom/movable/AM = parent
if(user.incapacitated())
var/kick = TRUE
if(iscyborg(AM))
var/mob/living/silicon/robot/R = AM
if(R.module && R.module.ride_allow_incapacitated)
kick = FALSE
if(kick)
to_chat(user, "<span class='userdanger'>You fall off of [AM]!</span>")
Unbuckle(user)
return
if(iscarbon(user))
var/mob/living/carbon/carbonuser = user
if(!carbonuser.get_num_arms())
Unbuckle(user)
to_chat(user, "<span class='warning'>You can't grab onto [AM] with no hands!</span>")
return
/datum/component/riding/cyborg/handle_vehicle_layer()
var/atom/movable/AM = parent
if(AM.buckled_mobs && AM.buckled_mobs.len)
if(AM.dir == SOUTH)
AM.layer = ABOVE_MOB_LAYER
else
AM.layer = OBJ_LAYER
else
AM.layer = MOB_LAYER
/datum/component/riding/cyborg/get_offsets(pass_index) // list(dir = x, y, layer)
return list(TEXT_NORTH = list(0, 4), TEXT_SOUTH = list(0, 4), TEXT_EAST = list(-6, 3), TEXT_WEST = list( 6, 3))
/datum/component/riding/cyborg/handle_vehicle_offsets()
var/atom/movable/AM = parent
if(AM.has_buckled_mobs())
for(var/mob/living/M in AM.buckled_mobs)
M.setDir(AM.dir)
if(iscyborg(AM))
var/mob/living/silicon/robot/R = AM
if(istype(R.module))
M.pixel_x = R.module.ride_offset_x[dir2text(AM.dir)]
M.pixel_y = R.module.ride_offset_y[dir2text(AM.dir)]
else
..()
/datum/component/riding/proc/equip_buckle_inhands(mob/living/carbon/human/user, amount_required = 1, riding_target_override = null)
var/atom/movable/AM = parent
var/amount_equipped = 0
for(var/amount_needed = amount_required, amount_needed > 0, amount_needed--)
var/obj/item/riding_offhand/inhand = new /obj/item/riding_offhand(user)
if(!riding_target_override)
inhand.rider = user
else
inhand.rider = riding_target_override
inhand.parent = AM
for(var/obj/item/I in user.held_items) // yes i know this sucks but these are ABSTRACT++ dumbness and i'm not adding a whole new flag for these two meme items
if((I.obj_flags & HAND_ITEM))
qdel(I)
if(user.put_in_hands(inhand, TRUE))
amount_equipped++
else
break
if(amount_equipped >= amount_required)
return TRUE
else
unequip_buckle_inhands(user)
return FALSE
/datum/component/riding/proc/unequip_buckle_inhands(mob/living/carbon/user)
var/atom/movable/AM = parent
for(var/obj/item/riding_offhand/O in user.contents)
if(O.parent != AM)
CRASH("RIDING OFFHAND ON WRONG MOB")
if(O.selfdeleting)
continue
else
qdel(O)
return TRUE
/obj/item/riding_offhand
name = "offhand"
icon = 'icons/obj/items_and_weapons.dmi'
icon_state = "offhand"
w_class = WEIGHT_CLASS_HUGE
item_flags = ABSTRACT | DROPDEL | NOBLUDGEON
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
var/mob/living/carbon/rider
var/mob/living/parent
var/selfdeleting = FALSE
/obj/item/riding_offhand/dropped()
selfdeleting = TRUE
. = ..()
/obj/item/riding_offhand/equipped()
if(loc != rider && loc != parent)
selfdeleting = TRUE
qdel(src)
. = ..()
/obj/item/riding_offhand/Destroy()
var/atom/movable/AM = parent
if(selfdeleting)
if(rider in AM.buckled_mobs)
AM.unbuckle_mob(rider)
. = ..()
/obj/item/riding_offhand/on_thrown(mob/living/carbon/user, atom/target)
if(rider == user)
return //Piggyback user.
user.unbuckle_mob(rider)
if(HAS_TRAIT(user, TRAIT_PACIFISM))
to_chat(user, "<span class='notice'>You gently let go of [rider].</span>")
return
return rider
+36 -36
View File
@@ -1,36 +1,36 @@
/datum/component/slippery
var/force_drop_items = FALSE
var/knockdown_time = 0
var/paralyze_time = 0
var/lube_flags
var/datum/callback/callback
/datum/component/slippery/Initialize(_knockdown, _lube_flags = NONE, datum/callback/_callback, _paralyze, _force_drop = FALSE)
knockdown_time = max(_knockdown, 0)
paralyze_time = max(_paralyze, 0)
force_drop_items = _force_drop
lube_flags = _lube_flags
callback = _callback
RegisterSignal(parent, list(COMSIG_MOVABLE_CROSSED, COMSIG_ATOM_ENTERED), .proc/Slip)
RegisterSignal(parent, COMSIG_ITEM_WEARERCROSSED, .proc/Slip_on_wearer)
/datum/component/slippery/proc/Slip(datum/source, atom/movable/AM)
var/mob/victim = AM
if(istype(victim) && !victim.is_flying() && victim.slip(knockdown_time, parent, lube_flags, paralyze_time, force_drop_items) && callback)
callback.Invoke(victim)
/datum/component/slippery/proc/Slip_on_wearer(datum/source, atom/movable/AM, mob/living/crossed)
if(!(crossed.mobility_flags & MOBILITY_STAND) && !crossed.buckle_lying)
Slip(source, AM)
/datum/component/slippery/clowning //used for making the clown PDA only slip if the clown is wearing his shoes and the elusive banana-skin belt
/datum/component/slippery/clowning/Slip_on_wearer(datum/source, atom/movable/AM, mob/living/crossed)
var/obj/item/I = crossed.get_item_by_slot(ITEM_SLOT_FEET)
if(!(crossed.mobility_flags & MOBILITY_STAND) && !crossed.buckle_lying)
if(istype(I, /obj/item/clothing/shoes/clown_shoes))
Slip(source, AM)
else
to_chat(crossed,"<span class='warning'>[parent] failed to slip anyone. Perhaps I shouldn't have abandoned my legacy...</span>")
/datum/component/slippery
var/force_drop_items = FALSE
var/knockdown_time = 0
var/paralyze_time = 0
var/lube_flags
var/datum/callback/callback
/datum/component/slippery/Initialize(_knockdown, _lube_flags = NONE, datum/callback/_callback, _paralyze, _force_drop = FALSE)
knockdown_time = max(_knockdown, 0)
paralyze_time = max(_paralyze, 0)
force_drop_items = _force_drop
lube_flags = _lube_flags
callback = _callback
RegisterSignal(parent, list(COMSIG_MOVABLE_CROSSED, COMSIG_ATOM_ENTERED), .proc/Slip)
RegisterSignal(parent, COMSIG_ITEM_WEARERCROSSED, .proc/Slip_on_wearer)
/datum/component/slippery/proc/Slip(datum/source, atom/movable/AM)
var/mob/victim = AM
if(istype(victim) && !victim.is_flying() && victim.slip(knockdown_time, parent, lube_flags, paralyze_time, force_drop_items) && callback)
callback.Invoke(victim)
/datum/component/slippery/proc/Slip_on_wearer(datum/source, atom/movable/AM, mob/living/crossed)
if(!(crossed.mobility_flags & MOBILITY_STAND) && !crossed.buckle_lying)
Slip(source, AM)
/datum/component/slippery/clowning //used for making the clown PDA only slip if the clown is wearing his shoes and the elusive banana-skin belt
/datum/component/slippery/clowning/Slip_on_wearer(datum/source, atom/movable/AM, mob/living/crossed)
var/obj/item/I = crossed.get_item_by_slot(ITEM_SLOT_FEET)
if(!(crossed.mobility_flags & MOBILITY_STAND) && !crossed.buckle_lying)
if(istype(I, /obj/item/clothing/shoes/clown_shoes))
Slip(source, AM)
else
to_chat(crossed,"<span class='warning'>[parent] failed to slip anyone. Perhaps I shouldn't have abandoned my legacy...</span>")
+57 -57
View File
@@ -1,57 +1,57 @@
// This component is for forcing strange things into your pocket that fall out if you fall down
// Yes this exists purely for the spaghetti meme
/datum/component/spill
can_transfer = TRUE
var/preexisting_item_flags
var/list/droptext
var/list/dropsound
// droptext is an arglist for visible_message
// dropsound is a list of potential sounds that gets picked from
/datum/component/spill/Initialize(list/_droptext, list/_dropsound)
if(!isitem(parent))
return COMPONENT_INCOMPATIBLE
if(_droptext && !islist(_droptext))
_droptext = list(_droptext)
droptext = _droptext
if(_dropsound && !islist(_dropsound))
_dropsound = list(_dropsound)
dropsound = _dropsound
/datum/component/spill/PostTransfer()
if(!isitem(parent))
return COMPONENT_INCOMPATIBLE
/datum/component/spill/RegisterWithParent()
RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/equip_react)
RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/drop_react)
var/obj/item/master = parent
preexisting_item_flags = master.item_flags
master.item_flags |= ITEM_SLOT_POCKETS
/datum/component/spill/UnregisterFromParent()
UnregisterSignal(parent, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED))
var/obj/item/master = parent
if(!(preexisting_item_flags & ITEM_SLOT_POCKETS))
master.item_flags &= ~ITEM_SLOT_POCKETS
/datum/component/spill/proc/equip_react(obj/item/source, mob/equipper, slot)
if(slot == ITEM_SLOT_LPOCKET || slot == ITEM_SLOT_RPOCKET)
RegisterSignal(equipper, COMSIG_LIVING_STATUS_KNOCKDOWN, .proc/knockdown_react, TRUE)
else
UnregisterSignal(equipper, COMSIG_LIVING_STATUS_KNOCKDOWN)
/datum/component/spill/proc/drop_react(obj/item/source, mob/dropper)
UnregisterSignal(dropper, COMSIG_LIVING_STATUS_KNOCKDOWN)
/datum/component/spill/proc/knockdown_react(mob/living/fool)
var/obj/item/master = parent
fool.dropItemToGround(master)
if(droptext)
fool.visible_message(arglist(droptext))
if(dropsound)
playsound(master, pick(dropsound), 30)
// This component is for forcing strange things into your pocket that fall out if you fall down
// Yes this exists purely for the spaghetti meme
/datum/component/spill
can_transfer = TRUE
var/preexisting_item_flags
var/list/droptext
var/list/dropsound
// droptext is an arglist for visible_message
// dropsound is a list of potential sounds that gets picked from
/datum/component/spill/Initialize(list/_droptext, list/_dropsound)
if(!isitem(parent))
return COMPONENT_INCOMPATIBLE
if(_droptext && !islist(_droptext))
_droptext = list(_droptext)
droptext = _droptext
if(_dropsound && !islist(_dropsound))
_dropsound = list(_dropsound)
dropsound = _dropsound
/datum/component/spill/PostTransfer()
if(!isitem(parent))
return COMPONENT_INCOMPATIBLE
/datum/component/spill/RegisterWithParent()
RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/equip_react)
RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/drop_react)
var/obj/item/master = parent
preexisting_item_flags = master.item_flags
master.item_flags |= ITEM_SLOT_POCKETS
/datum/component/spill/UnregisterFromParent()
UnregisterSignal(parent, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED))
var/obj/item/master = parent
if(!(preexisting_item_flags & ITEM_SLOT_POCKETS))
master.item_flags &= ~ITEM_SLOT_POCKETS
/datum/component/spill/proc/equip_react(obj/item/source, mob/equipper, slot)
if(slot == ITEM_SLOT_LPOCKET || slot == ITEM_SLOT_RPOCKET)
RegisterSignal(equipper, COMSIG_LIVING_STATUS_KNOCKDOWN, .proc/knockdown_react, TRUE)
else
UnregisterSignal(equipper, COMSIG_LIVING_STATUS_KNOCKDOWN)
/datum/component/spill/proc/drop_react(obj/item/source, mob/dropper)
UnregisterSignal(dropper, COMSIG_LIVING_STATUS_KNOCKDOWN)
/datum/component/spill/proc/knockdown_react(mob/living/fool)
var/obj/item/master = parent
fool.dropItemToGround(master)
if(droptext)
fool.visible_message(arglist(droptext))
if(dropsound)
playsound(master, pick(dropsound), 30)
@@ -1,201 +1,201 @@
// External storage-related logic:
// /mob/proc/ClickOn() in /_onclick/click.dm - clicking items in storages
// /mob/living/Move() in /modules/mob/living/living.dm - hiding storage boxes on mob movement
/datum/component/storage/concrete
can_transfer = TRUE
var/drop_all_on_deconstruct = TRUE
var/drop_all_on_destroy = FALSE
var/transfer_contents_on_component_transfer = FALSE
var/list/datum/component/storage/slaves = list()
var/list/_contents_limbo // Where objects go to live mid transfer
var/list/_user_limbo // The last users before the component started moving
/datum/component/storage/concrete/Initialize()
. = ..()
RegisterSignal(parent, COMSIG_ATOM_CONTENTS_DEL, .proc/on_contents_del)
RegisterSignal(parent, COMSIG_OBJ_DECONSTRUCT, .proc/on_deconstruct)
/datum/component/storage/concrete/Destroy()
var/atom/real_location = real_location()
for(var/atom/_A in real_location)
_A.mouse_opacity = initial(_A.mouse_opacity)
if(drop_all_on_destroy)
do_quick_empty()
for(var/i in slaves)
var/datum/component/storage/slave = i
slave.change_master(null)
QDEL_LIST(_contents_limbo)
_user_limbo = null
return ..()
/datum/component/storage/concrete/master()
return src
/datum/component/storage/concrete/real_location()
return parent
/datum/component/storage/concrete/PreTransfer()
if(is_using)
_user_limbo = is_using.Copy()
close_all()
if(transfer_contents_on_component_transfer)
_contents_limbo = list()
for(var/atom/movable/AM in parent)
_contents_limbo += AM
AM.moveToNullspace()
/datum/component/storage/concrete/PostTransfer()
if(!isatom(parent))
return COMPONENT_INCOMPATIBLE
if(transfer_contents_on_component_transfer)
for(var/i in _contents_limbo)
var/atom/movable/AM = i
AM.forceMove(parent)
_contents_limbo = null
if(_user_limbo)
for(var/i in _user_limbo)
show_to(i)
_user_limbo = null
/datum/component/storage/concrete/_insert_physical_item(obj/item/I, override = FALSE)
. = TRUE
var/atom/real_location = real_location()
if(I.loc != real_location)
I.forceMove(real_location)
refresh_mob_views()
/datum/component/storage/concrete/refresh_mob_views()
. = ..()
for(var/i in slaves)
var/datum/component/storage/slave = i
slave.refresh_mob_views()
/datum/component/storage/concrete/emp_act(datum/source, severity)
if(emp_shielded)
return
var/atom/real_location = real_location()
for(var/i in real_location)
var/atom/A = i
A.emp_act(severity)
/datum/component/storage/concrete/proc/on_slave_link(datum/component/storage/S)
if(S == src)
return FALSE
slaves += S
return TRUE
/datum/component/storage/concrete/proc/on_slave_unlink(datum/component/storage/S)
slaves -= S
return FALSE
/datum/component/storage/concrete/proc/on_contents_del(datum/source, atom/A)
var/atom/real_location = parent
if(A in real_location)
usr = null
remove_from_storage(A, null)
/datum/component/storage/concrete/proc/on_deconstruct(datum/source, disassembled)
if(drop_all_on_deconstruct)
do_quick_empty()
/datum/component/storage/concrete/can_see_contents()
. = ..()
for(var/i in slaves)
var/datum/component/storage/slave = i
. |= slave.can_see_contents()
//Resets screen loc and other vars of something being removed from storage.
/datum/component/storage/concrete/_removal_reset(atom/movable/thing)
thing.layer = initial(thing.layer)
thing.plane = initial(thing.plane)
thing.mouse_opacity = initial(thing.mouse_opacity)
if(thing.maptext)
thing.maptext = ""
/datum/component/storage/concrete/remove_from_storage(atom/movable/AM, atom/new_location)
//Cache this as it should be reusable down the bottom, will not apply if anyone adds a sleep to dropped
//or moving objects, things that should never happen
var/atom/parent = src.parent
var/list/seeing_mobs = can_see_contents()
for(var/mob/M in seeing_mobs)
M.client.screen -= AM
if(ismob(parent.loc) && isitem(AM))
var/obj/item/I = AM
var/mob/M = parent.loc
I.dropped(M, TRUE)
I.item_flags &= ~IN_STORAGE
if(new_location)
//Reset the items values
_removal_reset(AM)
AM.forceMove(new_location)
//We don't want to call this if the item is being destroyed
AM.on_exit_storage(src)
else
//Being destroyed, just move to nullspace now (so it's not in contents for the icon update)
AM.moveToNullspace()
refresh_mob_views()
if(isobj(parent))
var/obj/O = parent
O.update_icon()
return TRUE
/datum/component/storage/concrete/proc/slave_can_insert_object(datum/component/storage/slave, obj/item/I, stop_messages = FALSE, mob/M)
return TRUE
/datum/component/storage/concrete/proc/handle_item_insertion_from_slave(datum/component/storage/slave, obj/item/I, prevent_warning = FALSE, M)
. = handle_item_insertion(I, prevent_warning, M, slave)
if(. && !prevent_warning)
slave.mob_item_insertion_feedback(usr, M, I)
/datum/component/storage/concrete/handle_item_insertion(obj/item/I, prevent_warning = FALSE, mob/M, datum/component/storage/remote) //Remote is null or the slave datum
var/datum/component/storage/concrete/master = master()
var/atom/parent = src.parent
var/moved = FALSE
if(!istype(I))
return FALSE
if(M)
if(!M.temporarilyRemoveItemFromInventory(I))
return FALSE
else
moved = TRUE //At this point if the proc fails we need to manually move the object back to the turf/mob/whatever.
if(I.pulledby)
I.pulledby.stop_pulling()
if(silent)
prevent_warning = TRUE
if(!_insert_physical_item(I))
if(moved)
if(M)
if(!M.put_in_active_hand(I))
I.forceMove(parent.drop_location())
else
I.forceMove(parent.drop_location())
return FALSE
I.on_enter_storage(master)
I.item_flags |= IN_STORAGE
refresh_mob_views()
I.mouse_opacity = MOUSE_OPACITY_OPAQUE //So you can click on the area around the item to equip it, instead of having to pixel hunt
if(M)
if(M.client && M.active_storage != src)
M.client.screen -= I
if(M.observers && M.observers.len)
for(var/i in M.observers)
var/mob/dead/observe = i
if(observe.client && observe.active_storage != src)
observe.client.screen -= I
if(!remote)
parent.add_fingerprint(M)
if(!prevent_warning)
mob_item_insertion_feedback(usr, M, I)
update_icon()
return TRUE
/datum/component/storage/concrete/update_icon()
if(isobj(parent))
var/obj/O = parent
O.update_icon()
for(var/i in slaves)
var/datum/component/storage/slave = i
slave.update_icon()
// External storage-related logic:
// /mob/proc/ClickOn() in /_onclick/click.dm - clicking items in storages
// /mob/living/Move() in /modules/mob/living/living.dm - hiding storage boxes on mob movement
/datum/component/storage/concrete
can_transfer = TRUE
var/drop_all_on_deconstruct = TRUE
var/drop_all_on_destroy = FALSE
var/transfer_contents_on_component_transfer = FALSE
var/list/datum/component/storage/slaves = list()
var/list/_contents_limbo // Where objects go to live mid transfer
var/list/_user_limbo // The last users before the component started moving
/datum/component/storage/concrete/Initialize()
. = ..()
RegisterSignal(parent, COMSIG_ATOM_CONTENTS_DEL, .proc/on_contents_del)
RegisterSignal(parent, COMSIG_OBJ_DECONSTRUCT, .proc/on_deconstruct)
/datum/component/storage/concrete/Destroy()
var/atom/real_location = real_location()
for(var/atom/_A in real_location)
_A.mouse_opacity = initial(_A.mouse_opacity)
if(drop_all_on_destroy)
do_quick_empty()
for(var/i in slaves)
var/datum/component/storage/slave = i
slave.change_master(null)
QDEL_LIST(_contents_limbo)
_user_limbo = null
return ..()
/datum/component/storage/concrete/master()
return src
/datum/component/storage/concrete/real_location()
return parent
/datum/component/storage/concrete/PreTransfer()
if(is_using)
_user_limbo = is_using.Copy()
close_all()
if(transfer_contents_on_component_transfer)
_contents_limbo = list()
for(var/atom/movable/AM in parent)
_contents_limbo += AM
AM.moveToNullspace()
/datum/component/storage/concrete/PostTransfer()
if(!isatom(parent))
return COMPONENT_INCOMPATIBLE
if(transfer_contents_on_component_transfer)
for(var/i in _contents_limbo)
var/atom/movable/AM = i
AM.forceMove(parent)
_contents_limbo = null
if(_user_limbo)
for(var/i in _user_limbo)
show_to(i)
_user_limbo = null
/datum/component/storage/concrete/_insert_physical_item(obj/item/I, override = FALSE)
. = TRUE
var/atom/real_location = real_location()
if(I.loc != real_location)
I.forceMove(real_location)
refresh_mob_views()
/datum/component/storage/concrete/refresh_mob_views()
. = ..()
for(var/i in slaves)
var/datum/component/storage/slave = i
slave.refresh_mob_views()
/datum/component/storage/concrete/emp_act(datum/source, severity)
if(emp_shielded)
return
var/atom/real_location = real_location()
for(var/i in real_location)
var/atom/A = i
A.emp_act(severity)
/datum/component/storage/concrete/proc/on_slave_link(datum/component/storage/S)
if(S == src)
return FALSE
slaves += S
return TRUE
/datum/component/storage/concrete/proc/on_slave_unlink(datum/component/storage/S)
slaves -= S
return FALSE
/datum/component/storage/concrete/proc/on_contents_del(datum/source, atom/A)
var/atom/real_location = parent
if(A in real_location)
usr = null
remove_from_storage(A, null)
/datum/component/storage/concrete/proc/on_deconstruct(datum/source, disassembled)
if(drop_all_on_deconstruct)
do_quick_empty()
/datum/component/storage/concrete/can_see_contents()
. = ..()
for(var/i in slaves)
var/datum/component/storage/slave = i
. |= slave.can_see_contents()
//Resets screen loc and other vars of something being removed from storage.
/datum/component/storage/concrete/_removal_reset(atom/movable/thing)
thing.layer = initial(thing.layer)
thing.plane = initial(thing.plane)
thing.mouse_opacity = initial(thing.mouse_opacity)
if(thing.maptext)
thing.maptext = ""
/datum/component/storage/concrete/remove_from_storage(atom/movable/AM, atom/new_location)
//Cache this as it should be reusable down the bottom, will not apply if anyone adds a sleep to dropped
//or moving objects, things that should never happen
var/atom/parent = src.parent
var/list/seeing_mobs = can_see_contents()
for(var/mob/M in seeing_mobs)
M.client.screen -= AM
if(ismob(parent.loc) && isitem(AM))
var/obj/item/I = AM
var/mob/M = parent.loc
I.dropped(M, TRUE)
I.item_flags &= ~IN_STORAGE
if(new_location)
//Reset the items values
_removal_reset(AM)
AM.forceMove(new_location)
//We don't want to call this if the item is being destroyed
AM.on_exit_storage(src)
else
//Being destroyed, just move to nullspace now (so it's not in contents for the icon update)
AM.moveToNullspace()
refresh_mob_views()
if(isobj(parent))
var/obj/O = parent
O.update_icon()
return TRUE
/datum/component/storage/concrete/proc/slave_can_insert_object(datum/component/storage/slave, obj/item/I, stop_messages = FALSE, mob/M)
return TRUE
/datum/component/storage/concrete/proc/handle_item_insertion_from_slave(datum/component/storage/slave, obj/item/I, prevent_warning = FALSE, M)
. = handle_item_insertion(I, prevent_warning, M, slave)
if(. && !prevent_warning)
slave.mob_item_insertion_feedback(usr, M, I)
/datum/component/storage/concrete/handle_item_insertion(obj/item/I, prevent_warning = FALSE, mob/M, datum/component/storage/remote) //Remote is null or the slave datum
var/datum/component/storage/concrete/master = master()
var/atom/parent = src.parent
var/moved = FALSE
if(!istype(I))
return FALSE
if(M)
if(!M.temporarilyRemoveItemFromInventory(I))
return FALSE
else
moved = TRUE //At this point if the proc fails we need to manually move the object back to the turf/mob/whatever.
if(I.pulledby)
I.pulledby.stop_pulling()
if(silent)
prevent_warning = TRUE
if(!_insert_physical_item(I))
if(moved)
if(M)
if(!M.put_in_active_hand(I))
I.forceMove(parent.drop_location())
else
I.forceMove(parent.drop_location())
return FALSE
I.on_enter_storage(master)
I.item_flags |= IN_STORAGE
refresh_mob_views()
I.mouse_opacity = MOUSE_OPACITY_OPAQUE //So you can click on the area around the item to equip it, instead of having to pixel hunt
if(M)
if(M.client && M.active_storage != src)
M.client.screen -= I
if(M.observers && M.observers.len)
for(var/i in M.observers)
var/mob/dead/observe = i
if(observe.client && observe.active_storage != src)
observe.client.screen -= I
if(!remote)
parent.add_fingerprint(M)
if(!prevent_warning)
mob_item_insertion_feedback(usr, M, I)
update_icon()
return TRUE
/datum/component/storage/concrete/update_icon()
if(isobj(parent))
var/obj/O = parent
O.update_icon()
for(var/i in slaves)
var/datum/component/storage/slave = i
slave.update_icon()
@@ -1,23 +1,23 @@
/datum/component/storage/concrete/bluespace/bag_of_holding/handle_item_insertion(obj/item/W, prevent_warning = FALSE, mob/living/user)
var/atom/A = parent
if(A == W) //don't put yourself into yourself.
return
var/list/obj/item/storage/backpack/holding/matching = typecache_filter_list(W.GetAllContents(), typecacheof(/obj/item/storage/backpack/holding))
matching -= A
if(istype(W, /obj/item/storage/backpack/holding) || matching.len)
var/safety = alert(user, "Doing this will have extremely dire consequences for the station and its crew. Be sure you know what you're doing.", "Put in [A.name]?", "Proceed", "Abort")
if(safety != "Proceed" || QDELETED(A) || QDELETED(W) || QDELETED(user) || !user.canUseTopic(A, BE_CLOSE, iscarbon(user)))
return
var/turf/loccheck = get_turf(A)
to_chat(user, "<span class='danger'>The Bluespace interfaces of the two devices catastrophically malfunction!</span>")
qdel(W)
playsound(loccheck,'sound/effects/supermatter.ogg', 200, TRUE)
message_admins("[ADMIN_LOOKUPFLW(user)] detonated a bag of holding at [ADMIN_VERBOSEJMP(loccheck)].")
log_game("[key_name(user)] detonated a bag of holding at [loc_name(loccheck)].")
user.gib(TRUE, TRUE, TRUE)
new/obj/singularity/boh_tear(loccheck)
qdel(A)
return
. = ..()
/datum/component/storage/concrete/bluespace/bag_of_holding/handle_item_insertion(obj/item/W, prevent_warning = FALSE, mob/living/user)
var/atom/A = parent
if(A == W) //don't put yourself into yourself.
return
var/list/obj/item/storage/backpack/holding/matching = typecache_filter_list(W.GetAllContents(), typecacheof(/obj/item/storage/backpack/holding))
matching -= A
if(istype(W, /obj/item/storage/backpack/holding) || matching.len)
var/safety = alert(user, "Doing this will have extremely dire consequences for the station and its crew. Be sure you know what you're doing.", "Put in [A.name]?", "Proceed", "Abort")
if(safety != "Proceed" || QDELETED(A) || QDELETED(W) || QDELETED(user) || !user.canUseTopic(A, BE_CLOSE, iscarbon(user)))
return
var/turf/loccheck = get_turf(A)
to_chat(user, "<span class='danger'>The Bluespace interfaces of the two devices catastrophically malfunction!</span>")
qdel(W)
playsound(loccheck,'sound/effects/supermatter.ogg', 200, TRUE)
message_admins("[ADMIN_LOOKUPFLW(user)] detonated a bag of holding at [ADMIN_VERBOSEJMP(loccheck)].")
log_game("[key_name(user)] detonated a bag of holding at [loc_name(loccheck)].")
user.gib(TRUE, TRUE, TRUE)
new/obj/singularity/boh_tear(loccheck)
qdel(A)
return
. = ..()
@@ -1,22 +1,22 @@
/datum/component/storage/concrete/bluespace
var/dumping_range = 8
var/dumping_sound = 'sound/items/pshoom.ogg'
var/alt_sound = 'sound/items/pshoom_2.ogg'
/datum/component/storage/concrete/bluespace/dump_content_at(atom/dest, mob/M)
var/atom/A = parent
if(A.Adjacent(M))
var/atom/dumping_location = dest.get_dumping_location()
var/turf/bagT = get_turf(parent)
var/turf/destT = get_turf(dumping_location)
if(destT && bagT && bagT.z == destT.z && get_dist(M, dumping_location) < dumping_range)
if(dumping_location.storage_contents_dump_act(src, M))
if(alt_sound && prob(1))
playsound(src, alt_sound, 40, TRUE)
else
playsound(src, dumping_sound, 40, TRUE)
M.Beam(dumping_location, icon_state="rped_upgrade", time=5)
return TRUE
to_chat(M, "<span class='hear'>The [A.name] buzzes.</span>")
playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE)
return FALSE
/datum/component/storage/concrete/bluespace
var/dumping_range = 8
var/dumping_sound = 'sound/items/pshoom.ogg'
var/alt_sound = 'sound/items/pshoom_2.ogg'
/datum/component/storage/concrete/bluespace/dump_content_at(atom/dest, mob/M)
var/atom/A = parent
if(A.Adjacent(M))
var/atom/dumping_location = dest.get_dumping_location()
var/turf/bagT = get_turf(parent)
var/turf/destT = get_turf(dumping_location)
if(destT && bagT && bagT.z == destT.z && get_dist(M, dumping_location) < dumping_range)
if(dumping_location.storage_contents_dump_act(src, M))
if(alt_sound && prob(1))
playsound(src, alt_sound, 40, TRUE)
else
playsound(src, dumping_sound, 40, TRUE)
M.Beam(dumping_location, icon_state="rped_upgrade", time=5)
return TRUE
to_chat(M, "<span class='hear'>The [A.name] buzzes.</span>")
playsound(src, 'sound/machines/buzz-sigh.ogg', 50, FALSE)
return FALSE
@@ -1,18 +1,18 @@
/datum/component/storage/concrete/implant
max_w_class = WEIGHT_CLASS_NORMAL
max_combined_w_class = 6
max_items = 2
drop_all_on_destroy = TRUE
drop_all_on_deconstruct = TRUE
silent = TRUE
allow_big_nesting = TRUE
/datum/component/storage/concrete/implant/Initialize()
. = ..()
set_holdable(null, list(/obj/item/disk/nuclear))
/datum/component/storage/concrete/implant/InheritComponent(datum/component/storage/concrete/implant/I, original)
if(!istype(I))
return ..()
max_combined_w_class += I.max_combined_w_class
max_items += I.max_items
/datum/component/storage/concrete/implant
max_w_class = WEIGHT_CLASS_NORMAL
max_combined_w_class = 6
max_items = 2
drop_all_on_destroy = TRUE
drop_all_on_deconstruct = TRUE
silent = TRUE
allow_big_nesting = TRUE
/datum/component/storage/concrete/implant/Initialize()
. = ..()
set_holdable(null, list(/obj/item/disk/nuclear))
/datum/component/storage/concrete/implant/InheritComponent(datum/component/storage/concrete/implant/I, original)
if(!istype(I))
return ..()
max_combined_w_class += I.max_combined_w_class
max_items += I.max_items
@@ -1,93 +1,93 @@
/datum/component/storage/concrete/pockets
max_items = 2
max_w_class = WEIGHT_CLASS_SMALL
max_combined_w_class = 50
rustle_sound = FALSE
/datum/component/storage/concrete/pockets/handle_item_insertion(obj/item/I, prevent_warning, mob/user)
. = ..()
if(. && silent && !prevent_warning)
if(quickdraw)
to_chat(user, "<span class='notice'>You discreetly slip [I] into [parent]. Alt-click [parent] to remove it.</span>")
else
to_chat(user, "<span class='notice'>You discreetly slip [I] into [parent].</span>")
/datum/component/storage/concrete/pockets/small
max_items = 1
max_w_class = WEIGHT_CLASS_SMALL
attack_hand_interact = FALSE
/datum/component/storage/concrete/pockets/tiny
max_items = 1
max_w_class = WEIGHT_CLASS_TINY
attack_hand_interact = FALSE
/datum/component/storage/concrete/pockets/small/fedora/Initialize()
. = ..()
var/static/list/exception_cache = typecacheof(list(
/obj/item/katana, /obj/item/toy/katana, /obj/item/nullrod/claymore/katana,
/obj/item/energy_katana, /obj/item/gun/ballistic/automatic/tommygun
))
exception_hold = exception_cache
/datum/component/storage/concrete/pockets/small/fedora/detective
attack_hand_interact = TRUE // so the detectives would discover pockets in their hats
/datum/component/storage/concrete/pockets/shoes
attack_hand_interact = FALSE
quickdraw = TRUE
silent = TRUE
/datum/component/storage/concrete/pockets/shoes/Initialize()
. = ..()
set_holdable(list(
/obj/item/kitchen/knife, /obj/item/switchblade, /obj/item/pen,
/obj/item/scalpel, /obj/item/reagent_containers/syringe, /obj/item/dnainjector,
/obj/item/reagent_containers/hypospray/medipen, /obj/item/reagent_containers/dropper,
/obj/item/implanter, /obj/item/screwdriver, /obj/item/weldingtool/mini,
/obj/item/firing_pin
),
list(/obj/item/screwdriver/power)
)
/datum/component/storage/concrete/pockets/shoes/clown/Initialize()
. = ..()
set_holdable(list(
/obj/item/kitchen/knife, /obj/item/switchblade, /obj/item/pen,
/obj/item/scalpel, /obj/item/reagent_containers/syringe, /obj/item/dnainjector,
/obj/item/reagent_containers/hypospray/medipen, /obj/item/reagent_containers/dropper,
/obj/item/implanter, /obj/item/screwdriver, /obj/item/weldingtool/mini,
/obj/item/firing_pin, /obj/item/bikehorn),
list(/obj/item/screwdriver/power)
)
/datum/component/storage/concrete/pockets/pocketprotector
max_items = 3
max_w_class = WEIGHT_CLASS_TINY
var/atom/original_parent
/datum/component/storage/concrete/pockets/pocketprotector/Initialize()
original_parent = parent
. = ..()
set_holdable(list( //Same items as a PDA
/obj/item/pen,
/obj/item/toy/crayon,
/obj/item/lipstick,
/obj/item/flashlight/pen,
/obj/item/clothing/mask/cigarette)
)
/datum/component/storage/concrete/pockets/pocketprotector/real_location()
// if the component is reparented to a jumpsuit, the items still go in the protector
return original_parent
/datum/component/storage/concrete/pockets/helmet
quickdraw = TRUE
max_combined_w_class = 6
/datum/component/storage/concrete/pockets/helmet/Initialize()
. = ..()
set_holdable(list(/obj/item/reagent_containers/food/drinks/bottle/vodka,
/obj/item/reagent_containers/food/drinks/bottle/molotov,
/obj/item/reagent_containers/food/drinks/drinkingglass,
/obj/item/ammo_box/a762))
/datum/component/storage/concrete/pockets
max_items = 2
max_w_class = WEIGHT_CLASS_SMALL
max_combined_w_class = 50
rustle_sound = FALSE
/datum/component/storage/concrete/pockets/handle_item_insertion(obj/item/I, prevent_warning, mob/user)
. = ..()
if(. && silent && !prevent_warning)
if(quickdraw)
to_chat(user, "<span class='notice'>You discreetly slip [I] into [parent]. Alt-click [parent] to remove it.</span>")
else
to_chat(user, "<span class='notice'>You discreetly slip [I] into [parent].</span>")
/datum/component/storage/concrete/pockets/small
max_items = 1
max_w_class = WEIGHT_CLASS_SMALL
attack_hand_interact = FALSE
/datum/component/storage/concrete/pockets/tiny
max_items = 1
max_w_class = WEIGHT_CLASS_TINY
attack_hand_interact = FALSE
/datum/component/storage/concrete/pockets/small/fedora/Initialize()
. = ..()
var/static/list/exception_cache = typecacheof(list(
/obj/item/katana, /obj/item/toy/katana, /obj/item/nullrod/claymore/katana,
/obj/item/energy_katana, /obj/item/gun/ballistic/automatic/tommygun
))
exception_hold = exception_cache
/datum/component/storage/concrete/pockets/small/fedora/detective
attack_hand_interact = TRUE // so the detectives would discover pockets in their hats
/datum/component/storage/concrete/pockets/shoes
attack_hand_interact = FALSE
quickdraw = TRUE
silent = TRUE
/datum/component/storage/concrete/pockets/shoes/Initialize()
. = ..()
set_holdable(list(
/obj/item/kitchen/knife, /obj/item/switchblade, /obj/item/pen,
/obj/item/scalpel, /obj/item/reagent_containers/syringe, /obj/item/dnainjector,
/obj/item/reagent_containers/hypospray/medipen, /obj/item/reagent_containers/dropper,
/obj/item/implanter, /obj/item/screwdriver, /obj/item/weldingtool/mini,
/obj/item/firing_pin
),
list(/obj/item/screwdriver/power)
)
/datum/component/storage/concrete/pockets/shoes/clown/Initialize()
. = ..()
set_holdable(list(
/obj/item/kitchen/knife, /obj/item/switchblade, /obj/item/pen,
/obj/item/scalpel, /obj/item/reagent_containers/syringe, /obj/item/dnainjector,
/obj/item/reagent_containers/hypospray/medipen, /obj/item/reagent_containers/dropper,
/obj/item/implanter, /obj/item/screwdriver, /obj/item/weldingtool/mini,
/obj/item/firing_pin, /obj/item/bikehorn),
list(/obj/item/screwdriver/power)
)
/datum/component/storage/concrete/pockets/pocketprotector
max_items = 3
max_w_class = WEIGHT_CLASS_TINY
var/atom/original_parent
/datum/component/storage/concrete/pockets/pocketprotector/Initialize()
original_parent = parent
. = ..()
set_holdable(list( //Same items as a PDA
/obj/item/pen,
/obj/item/toy/crayon,
/obj/item/lipstick,
/obj/item/flashlight/pen,
/obj/item/clothing/mask/cigarette)
)
/datum/component/storage/concrete/pockets/pocketprotector/real_location()
// if the component is reparented to a jumpsuit, the items still go in the protector
return original_parent
/datum/component/storage/concrete/pockets/helmet
quickdraw = TRUE
max_combined_w_class = 6
/datum/component/storage/concrete/pockets/helmet/Initialize()
. = ..()
set_holdable(list(/obj/item/reagent_containers/food/drinks/bottle/vodka,
/obj/item/reagent_containers/food/drinks/bottle/molotov,
/obj/item/reagent_containers/food/drinks/drinkingglass,
/obj/item/ammo_box/a762))
+33 -33
View File
@@ -1,33 +1,33 @@
/datum/component/storage/concrete/rped
collection_mode = COLLECT_EVERYTHING
allow_quick_gather = TRUE
allow_quick_empty = TRUE
click_gather = TRUE
max_w_class = WEIGHT_CLASS_NORMAL
max_combined_w_class = 100
max_items = 50
display_numerical_stacking = TRUE
/datum/component/storage/concrete/rped/can_be_inserted(obj/item/I, stop_messages, mob/M)
. = ..()
if(!I.get_part_rating())
if (!stop_messages)
to_chat(M, "<span class='warning'>[parent] only accepts machine parts!</span>")
return FALSE
/datum/component/storage/concrete/bluespace/rped
collection_mode = COLLECT_EVERYTHING
allow_quick_gather = TRUE
allow_quick_empty = TRUE
click_gather = TRUE
max_w_class = WEIGHT_CLASS_BULKY // can fit vending refills
max_combined_w_class = 800
max_items = 400
display_numerical_stacking = TRUE
/datum/component/storage/concrete/bluespace/rped/can_be_inserted(obj/item/I, stop_messages, mob/M)
. = ..()
if(!I.get_part_rating())
if (!stop_messages)
to_chat(M, "<span class='warning'>[parent] only accepts machine parts!</span>")
return FALSE
/datum/component/storage/concrete/rped
collection_mode = COLLECT_EVERYTHING
allow_quick_gather = TRUE
allow_quick_empty = TRUE
click_gather = TRUE
max_w_class = WEIGHT_CLASS_NORMAL
max_combined_w_class = 100
max_items = 50
display_numerical_stacking = TRUE
/datum/component/storage/concrete/rped/can_be_inserted(obj/item/I, stop_messages, mob/M)
. = ..()
if(!I.get_part_rating())
if (!stop_messages)
to_chat(M, "<span class='warning'>[parent] only accepts machine parts!</span>")
return FALSE
/datum/component/storage/concrete/bluespace/rped
collection_mode = COLLECT_EVERYTHING
allow_quick_gather = TRUE
allow_quick_empty = TRUE
click_gather = TRUE
max_w_class = WEIGHT_CLASS_BULKY // can fit vending refills
max_combined_w_class = 800
max_items = 400
display_numerical_stacking = TRUE
/datum/component/storage/concrete/bluespace/rped/can_be_inserted(obj/item/I, stop_messages, mob/M)
. = ..()
if(!I.get_part_rating())
if (!stop_messages)
to_chat(M, "<span class='warning'>[parent] only accepts machine parts!</span>")
return FALSE
@@ -1,67 +1,67 @@
//Stack-only storage.
/datum/component/storage/concrete/stack
display_numerical_stacking = TRUE
var/max_combined_stack_amount = 300
max_w_class = WEIGHT_CLASS_NORMAL
max_combined_w_class = WEIGHT_CLASS_NORMAL * 14
/datum/component/storage/concrete/stack/proc/total_stack_amount()
. = 0
var/atom/real_location = real_location()
for(var/i in real_location)
var/obj/item/stack/S = i
if(!istype(S))
continue
. += S.amount
/datum/component/storage/concrete/stack/proc/remaining_space()
return max(0, max_combined_stack_amount - total_stack_amount())
//emptying procs do not need modification as stacks automatically merge.
/datum/component/storage/concrete/stack/_insert_physical_item(obj/item/I, override = FALSE)
if(!istype(I, /obj/item/stack))
if(override)
return ..()
return FALSE
var/atom/real_location = real_location()
var/obj/item/stack/S = I
var/can_insert = min(S.amount, remaining_space())
if(!can_insert)
return FALSE
for(var/i in real_location) //combine.
if(QDELETED(I))
return
var/obj/item/stack/_S = i
if(!istype(_S))
continue
if(_S.merge_type == S.merge_type)
_S.add(can_insert)
S.use(can_insert, TRUE)
return TRUE
return ..(S.change_stack(null, can_insert), override)
/datum/component/storage/concrete/stack/remove_from_storage(obj/item/I, atom/new_location)
var/atom/real_location = real_location()
var/obj/item/stack/S = I
if(!istype(S))
return ..()
if(S.amount > S.max_amount)
var/overrun = S.amount - S.max_amount
S.amount = S.max_amount
var/obj/item/stack/temp = new S.type(real_location, overrun)
handle_item_insertion(temp)
return ..(S, new_location)
/datum/component/storage/concrete/stack/_process_numerical_display()
var/atom/real_location = real_location()
. = list()
for(var/i in real_location)
var/obj/item/stack/I = i
if(!istype(I) || QDELETED(I)) //We're specialized stack storage, just ignore non stacks.
continue
if(!.[I.merge_type])
.[I.merge_type] = new /datum/numbered_display(I, I.amount)
else
var/datum/numbered_display/ND = .[I.merge_type]
ND.number += I.amount
//Stack-only storage.
/datum/component/storage/concrete/stack
display_numerical_stacking = TRUE
var/max_combined_stack_amount = 300
max_w_class = WEIGHT_CLASS_NORMAL
max_combined_w_class = WEIGHT_CLASS_NORMAL * 14
/datum/component/storage/concrete/stack/proc/total_stack_amount()
. = 0
var/atom/real_location = real_location()
for(var/i in real_location)
var/obj/item/stack/S = i
if(!istype(S))
continue
. += S.amount
/datum/component/storage/concrete/stack/proc/remaining_space()
return max(0, max_combined_stack_amount - total_stack_amount())
//emptying procs do not need modification as stacks automatically merge.
/datum/component/storage/concrete/stack/_insert_physical_item(obj/item/I, override = FALSE)
if(!istype(I, /obj/item/stack))
if(override)
return ..()
return FALSE
var/atom/real_location = real_location()
var/obj/item/stack/S = I
var/can_insert = min(S.amount, remaining_space())
if(!can_insert)
return FALSE
for(var/i in real_location) //combine.
if(QDELETED(I))
return
var/obj/item/stack/_S = i
if(!istype(_S))
continue
if(_S.merge_type == S.merge_type)
_S.add(can_insert)
S.use(can_insert, TRUE)
return TRUE
return ..(S.change_stack(null, can_insert), override)
/datum/component/storage/concrete/stack/remove_from_storage(obj/item/I, atom/new_location)
var/atom/real_location = real_location()
var/obj/item/stack/S = I
if(!istype(S))
return ..()
if(S.amount > S.max_amount)
var/overrun = S.amount - S.max_amount
S.amount = S.max_amount
var/obj/item/stack/temp = new S.type(real_location, overrun)
handle_item_insertion(temp)
return ..(S, new_location)
/datum/component/storage/concrete/stack/_process_numerical_display()
var/atom/real_location = real_location()
. = list()
for(var/i in real_location)
var/obj/item/stack/I = i
if(!istype(I) || QDELETED(I)) //We're specialized stack storage, just ignore non stacks.
continue
if(!.[I.merge_type])
.[I.merge_type] = new /datum/numbered_display(I, I.amount)
else
var/datum/numbered_display/ND = .[I.merge_type]
ND.number += I.amount
File diff suppressed because it is too large Load Diff
+206 -206
View File
@@ -1,206 +1,206 @@
/datum/component/wet_floor
dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
can_transfer = TRUE
var/highest_strength = TURF_DRY
var/lube_flags = NONE //why do we have this?
var/list/time_left_list //In deciseconds.
var/static/mutable_appearance/permafrost_overlay = mutable_appearance('icons/effects/water.dmi', "ice_floor")
var/static/mutable_appearance/ice_overlay = mutable_appearance('icons/turf/overlays.dmi', "snowfloor")
var/static/mutable_appearance/water_overlay = mutable_appearance('icons/effects/water.dmi', "wet_floor_static")
var/static/mutable_appearance/generic_turf_overlay = mutable_appearance('icons/effects/water.dmi', "wet_static")
var/current_overlay
var/permanent = FALSE
var/last_process = 0
/datum/component/wet_floor/InheritComponent(datum/newcomp, orig, strength, duration_minimum, duration_add, duration_maximum, _permanent)
if(!newcomp) //We are getting passed the arguments of a would-be new component, but not a new component
add_wet(arglist(args.Copy(3)))
else //We are being passed in a full blown component
var/datum/component/wet_floor/WF = newcomp //Lets make an assumption
if(WF.gc()) //See if it's even valid, still. Also does LAZYLEN and stuff for us.
CRASH("Wet floor component tried to inherit another, but the other was able to garbage collect while being inherited! What a waste of time!")
for(var/i in WF.time_left_list)
add_wet(text2num(i), WF.time_left_list[i])
/datum/component/wet_floor/Initialize(strength, duration_minimum, duration_add, duration_maximum, _permanent = FALSE)
if(!isopenturf(parent))
return COMPONENT_INCOMPATIBLE
add_wet(strength, duration_minimum, duration_add, duration_maximum)
permanent = _permanent
if(!permanent)
START_PROCESSING(SSwet_floors, src)
addtimer(CALLBACK(src, .proc/gc, TRUE), 1) //GC after initialization.
last_process = world.time
/datum/component/wet_floor/RegisterWithParent()
RegisterSignal(parent, COMSIG_TURF_IS_WET, .proc/is_wet)
RegisterSignal(parent, COMSIG_TURF_MAKE_DRY, .proc/dry)
/datum/component/wet_floor/UnregisterFromParent()
UnregisterSignal(parent, list(COMSIG_TURF_IS_WET, COMSIG_TURF_MAKE_DRY))
/datum/component/wet_floor/Destroy()
STOP_PROCESSING(SSwet_floors, src)
var/turf/T = parent
qdel(T.GetComponent(/datum/component/slippery))
if(istype(T)) //If this is false there is so many things wrong with it.
T.cut_overlay(current_overlay)
else
stack_trace("Warning: Wet floor component wasn't on a turf when being destroyed! This is really bad!")
return ..()
/datum/component/wet_floor/proc/update_overlay()
var/intended
if(!istype(parent, /turf/open/floor))
intended = generic_turf_overlay
else
switch(highest_strength)
if(TURF_WET_PERMAFROST)
intended = permafrost_overlay
if(TURF_WET_ICE)
intended = ice_overlay
else
intended = water_overlay
if(current_overlay != intended)
var/turf/T = parent
T.cut_overlay(current_overlay)
T.add_overlay(intended)
current_overlay = intended
/datum/component/wet_floor/proc/AfterSlip(mob/living/L)
if(highest_strength == TURF_WET_LUBE)
L.confused = max(L.confused, 8)
/datum/component/wet_floor/proc/update_flags()
var/intensity
lube_flags = NONE
switch(highest_strength)
if(TURF_WET_WATER)
intensity = 60
lube_flags = NO_SLIP_WHEN_WALKING
if(TURF_WET_LUBE)
intensity = 80
lube_flags = SLIDE | GALOSHES_DONT_HELP
if(TURF_WET_ICE)
intensity = 120
lube_flags = SLIDE | GALOSHES_DONT_HELP
if(TURF_WET_PERMAFROST)
intensity = 120
lube_flags = SLIDE_ICE | GALOSHES_DONT_HELP
if(TURF_WET_SUPERLUBE)
intensity = 120
lube_flags = SLIDE | GALOSHES_DONT_HELP | SLIP_WHEN_CRAWLING
else
qdel(parent.GetComponent(/datum/component/slippery))
return
parent.LoadComponent(/datum/component/slippery, intensity, lube_flags, CALLBACK(src, .proc/AfterSlip))
/datum/component/wet_floor/proc/dry(datum/source, strength = TURF_WET_WATER, immediate = FALSE, duration_decrease = INFINITY)
for(var/i in time_left_list)
if(text2num(i) <= strength)
time_left_list[i] = max(0, time_left_list[i] - duration_decrease)
if(immediate)
check()
/datum/component/wet_floor/proc/max_time_left()
. = 0
for(var/i in time_left_list)
. = max(., time_left_list[i])
/datum/component/wet_floor/process()
var/turf/open/T = parent
var/diff = world.time - last_process
var/decrease = 0
var/t = T.GetTemperature()
switch(t)
if(-INFINITY to T0C)
add_wet(TURF_WET_ICE, max_time_left()) //Water freezes into ice!
if(T0C to T0C + 100)
decrease = ((T.air.temperature - T0C) / SSwet_floors.temperature_coeff) * (diff / SSwet_floors.time_ratio)
if(T0C + 100 to INFINITY)
decrease = INFINITY
decrease = max(0, decrease)
if((is_wet() & TURF_WET_ICE) && t > T0C) //Ice melts into water!
for(var/obj/O in T.contents)
if(O.obj_flags & FROZEN)
O.make_unfrozen()
add_wet(TURF_WET_WATER, max_time_left())
dry(null, TURF_WET_ICE)
dry(null, ALL, FALSE, decrease)
check()
last_process = world.time
/datum/component/wet_floor/proc/update_strength()
highest_strength = 0 //Not bitflag.
for(var/i in time_left_list)
highest_strength = max(highest_strength, text2num(i))
/datum/component/wet_floor/proc/is_wet()
. = 0
for(var/i in time_left_list)
. |= text2num(i)
/datum/component/wet_floor/PreTransfer()
var/turf/O = parent
O.cut_overlay(current_overlay)
//That turf is no longer slippery, we're out of here
//Slippery components don't transfer due to callbacks
qdel(O.GetComponent(/datum/component/slippery))
/datum/component/wet_floor/PostTransfer()
if(!isopenturf(parent))
return COMPONENT_INCOMPATIBLE
var/turf/T = parent
T.add_overlay(current_overlay)
//Make sure to add/update any slippery component on the new turf (update_flags calls LoadComponent)
update_flags()
//NB it's possible we get deleted after this, due to inherit
/datum/component/wet_floor/proc/add_wet(type, duration_minimum = 0, duration_add = 0, duration_maximum = MAXIMUM_WET_TIME, _permanent = FALSE)
var/static/list/allowed_types = list(TURF_WET_WATER, TURF_WET_LUBE, TURF_WET_ICE, TURF_WET_PERMAFROST, TURF_WET_SUPERLUBE)
if(duration_minimum <= 0 || !type)
return FALSE
if(type in allowed_types)
return _do_add_wet(type, duration_minimum, duration_add, duration_maximum)
else
. = NONE
for(var/i in allowed_types)
if(!(type & i))
continue
. |= _do_add_wet(i, duration_minimum, duration_add, duration_maximum)
if(_permanent)
permanent = TRUE
STOP_PROCESSING(SSwet_floors, src)
/datum/component/wet_floor/proc/_do_add_wet(type, duration_minimum, duration_add, duration_maximum)
var/time = 0
if(LAZYACCESS(time_left_list, "[type]"))
time = clamp(LAZYACCESS(time_left_list, "[type]") + duration_add, duration_minimum, duration_maximum)
else
time = min(duration_minimum, duration_maximum)
LAZYSET(time_left_list, "[type]", time)
check(TRUE)
return TRUE
/datum/component/wet_floor/proc/gc(on_init = FALSE)
if(!LAZYLEN(time_left_list))
if(on_init)
var/turf/T = parent
stack_trace("Warning: Wet floor component gc'd right after initialization! What a waste of time and CPU! Type = [T? T.type : "ERROR - NO PARENT"], Location = [istype(T)? AREACOORD(T) : "ERROR - INVALID PARENT"].")
qdel(src)
return TRUE
return FALSE
/datum/component/wet_floor/proc/check(force_update = FALSE)
var/changed = FALSE
for(var/i in time_left_list)
if(time_left_list[i] <= 0)
time_left_list -= i
changed = TRUE
if(changed || force_update)
update_strength()
update_overlay()
update_flags()
gc()
/datum/component/wet_floor
dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS
can_transfer = TRUE
var/highest_strength = TURF_DRY
var/lube_flags = NONE //why do we have this?
var/list/time_left_list //In deciseconds.
var/static/mutable_appearance/permafrost_overlay = mutable_appearance('icons/effects/water.dmi', "ice_floor")
var/static/mutable_appearance/ice_overlay = mutable_appearance('icons/turf/overlays.dmi', "snowfloor")
var/static/mutable_appearance/water_overlay = mutable_appearance('icons/effects/water.dmi', "wet_floor_static")
var/static/mutable_appearance/generic_turf_overlay = mutable_appearance('icons/effects/water.dmi', "wet_static")
var/current_overlay
var/permanent = FALSE
var/last_process = 0
/datum/component/wet_floor/InheritComponent(datum/newcomp, orig, strength, duration_minimum, duration_add, duration_maximum, _permanent)
if(!newcomp) //We are getting passed the arguments of a would-be new component, but not a new component
add_wet(arglist(args.Copy(3)))
else //We are being passed in a full blown component
var/datum/component/wet_floor/WF = newcomp //Lets make an assumption
if(WF.gc()) //See if it's even valid, still. Also does LAZYLEN and stuff for us.
CRASH("Wet floor component tried to inherit another, but the other was able to garbage collect while being inherited! What a waste of time!")
for(var/i in WF.time_left_list)
add_wet(text2num(i), WF.time_left_list[i])
/datum/component/wet_floor/Initialize(strength, duration_minimum, duration_add, duration_maximum, _permanent = FALSE)
if(!isopenturf(parent))
return COMPONENT_INCOMPATIBLE
add_wet(strength, duration_minimum, duration_add, duration_maximum)
permanent = _permanent
if(!permanent)
START_PROCESSING(SSwet_floors, src)
addtimer(CALLBACK(src, .proc/gc, TRUE), 1) //GC after initialization.
last_process = world.time
/datum/component/wet_floor/RegisterWithParent()
RegisterSignal(parent, COMSIG_TURF_IS_WET, .proc/is_wet)
RegisterSignal(parent, COMSIG_TURF_MAKE_DRY, .proc/dry)
/datum/component/wet_floor/UnregisterFromParent()
UnregisterSignal(parent, list(COMSIG_TURF_IS_WET, COMSIG_TURF_MAKE_DRY))
/datum/component/wet_floor/Destroy()
STOP_PROCESSING(SSwet_floors, src)
var/turf/T = parent
qdel(T.GetComponent(/datum/component/slippery))
if(istype(T)) //If this is false there is so many things wrong with it.
T.cut_overlay(current_overlay)
else
stack_trace("Warning: Wet floor component wasn't on a turf when being destroyed! This is really bad!")
return ..()
/datum/component/wet_floor/proc/update_overlay()
var/intended
if(!istype(parent, /turf/open/floor))
intended = generic_turf_overlay
else
switch(highest_strength)
if(TURF_WET_PERMAFROST)
intended = permafrost_overlay
if(TURF_WET_ICE)
intended = ice_overlay
else
intended = water_overlay
if(current_overlay != intended)
var/turf/T = parent
T.cut_overlay(current_overlay)
T.add_overlay(intended)
current_overlay = intended
/datum/component/wet_floor/proc/AfterSlip(mob/living/L)
if(highest_strength == TURF_WET_LUBE)
L.confused = max(L.confused, 8)
/datum/component/wet_floor/proc/update_flags()
var/intensity
lube_flags = NONE
switch(highest_strength)
if(TURF_WET_WATER)
intensity = 60
lube_flags = NO_SLIP_WHEN_WALKING
if(TURF_WET_LUBE)
intensity = 80
lube_flags = SLIDE | GALOSHES_DONT_HELP
if(TURF_WET_ICE)
intensity = 120
lube_flags = SLIDE | GALOSHES_DONT_HELP
if(TURF_WET_PERMAFROST)
intensity = 120
lube_flags = SLIDE_ICE | GALOSHES_DONT_HELP
if(TURF_WET_SUPERLUBE)
intensity = 120
lube_flags = SLIDE | GALOSHES_DONT_HELP | SLIP_WHEN_CRAWLING
else
qdel(parent.GetComponent(/datum/component/slippery))
return
parent.LoadComponent(/datum/component/slippery, intensity, lube_flags, CALLBACK(src, .proc/AfterSlip))
/datum/component/wet_floor/proc/dry(datum/source, strength = TURF_WET_WATER, immediate = FALSE, duration_decrease = INFINITY)
for(var/i in time_left_list)
if(text2num(i) <= strength)
time_left_list[i] = max(0, time_left_list[i] - duration_decrease)
if(immediate)
check()
/datum/component/wet_floor/proc/max_time_left()
. = 0
for(var/i in time_left_list)
. = max(., time_left_list[i])
/datum/component/wet_floor/process()
var/turf/open/T = parent
var/diff = world.time - last_process
var/decrease = 0
var/t = T.GetTemperature()
switch(t)
if(-INFINITY to T0C)
add_wet(TURF_WET_ICE, max_time_left()) //Water freezes into ice!
if(T0C to T0C + 100)
decrease = ((T.air.temperature - T0C) / SSwet_floors.temperature_coeff) * (diff / SSwet_floors.time_ratio)
if(T0C + 100 to INFINITY)
decrease = INFINITY
decrease = max(0, decrease)
if((is_wet() & TURF_WET_ICE) && t > T0C) //Ice melts into water!
for(var/obj/O in T.contents)
if(O.obj_flags & FROZEN)
O.make_unfrozen()
add_wet(TURF_WET_WATER, max_time_left())
dry(null, TURF_WET_ICE)
dry(null, ALL, FALSE, decrease)
check()
last_process = world.time
/datum/component/wet_floor/proc/update_strength()
highest_strength = 0 //Not bitflag.
for(var/i in time_left_list)
highest_strength = max(highest_strength, text2num(i))
/datum/component/wet_floor/proc/is_wet()
. = 0
for(var/i in time_left_list)
. |= text2num(i)
/datum/component/wet_floor/PreTransfer()
var/turf/O = parent
O.cut_overlay(current_overlay)
//That turf is no longer slippery, we're out of here
//Slippery components don't transfer due to callbacks
qdel(O.GetComponent(/datum/component/slippery))
/datum/component/wet_floor/PostTransfer()
if(!isopenturf(parent))
return COMPONENT_INCOMPATIBLE
var/turf/T = parent
T.add_overlay(current_overlay)
//Make sure to add/update any slippery component on the new turf (update_flags calls LoadComponent)
update_flags()
//NB it's possible we get deleted after this, due to inherit
/datum/component/wet_floor/proc/add_wet(type, duration_minimum = 0, duration_add = 0, duration_maximum = MAXIMUM_WET_TIME, _permanent = FALSE)
var/static/list/allowed_types = list(TURF_WET_WATER, TURF_WET_LUBE, TURF_WET_ICE, TURF_WET_PERMAFROST, TURF_WET_SUPERLUBE)
if(duration_minimum <= 0 || !type)
return FALSE
if(type in allowed_types)
return _do_add_wet(type, duration_minimum, duration_add, duration_maximum)
else
. = NONE
for(var/i in allowed_types)
if(!(type & i))
continue
. |= _do_add_wet(i, duration_minimum, duration_add, duration_maximum)
if(_permanent)
permanent = TRUE
STOP_PROCESSING(SSwet_floors, src)
/datum/component/wet_floor/proc/_do_add_wet(type, duration_minimum, duration_add, duration_maximum)
var/time = 0
if(LAZYACCESS(time_left_list, "[type]"))
time = clamp(LAZYACCESS(time_left_list, "[type]") + duration_add, duration_minimum, duration_maximum)
else
time = min(duration_minimum, duration_maximum)
LAZYSET(time_left_list, "[type]", time)
check(TRUE)
return TRUE
/datum/component/wet_floor/proc/gc(on_init = FALSE)
if(!LAZYLEN(time_left_list))
if(on_init)
var/turf/T = parent
stack_trace("Warning: Wet floor component gc'd right after initialization! What a waste of time and CPU! Type = [T? T.type : "ERROR - NO PARENT"], Location = [istype(T)? AREACOORD(T) : "ERROR - INVALID PARENT"].")
qdel(src)
return TRUE
return FALSE
/datum/component/wet_floor/proc/check(force_update = FALSE)
var/changed = FALSE
for(var/i in time_left_list)
if(time_left_list[i] <= 0)
time_left_list -= i
changed = TRUE
if(changed || force_update)
update_strength()
update_overlay()
update_flags()
gc()