Merge branch 'master' of https://github.com/Citadel-Station-13/Citadel-Station-13 into putnamos-for-real
This commit is contained in:
@@ -38,8 +38,9 @@
|
||||
parent = raw_args[1]
|
||||
var/list/arguments = raw_args.Copy(2)
|
||||
if(Initialize(arglist(arguments)) == COMPONENT_INCOMPATIBLE)
|
||||
stack_trace("Incompatible [type] assigned to a [parent.type]! args: [json_encode(arguments)]")
|
||||
qdel(src, TRUE, TRUE)
|
||||
CRASH("Incompatible [type] assigned to a [parent.type]! args: [json_encode(arguments)]")
|
||||
return
|
||||
|
||||
_JoinParent(parent)
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
AM.visible_message("<span class='boldwarning'>[AM] falls into [parent]!</span>", "<span class='userdanger'>[oblivion_message]</span>")
|
||||
if (isliving(AM))
|
||||
var/mob/living/L = AM
|
||||
L.notransform = TRUE
|
||||
L.mob_transforming = TRUE
|
||||
L.Paralyze(200)
|
||||
|
||||
var/oldtransform = AM.transform
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
/**
|
||||
* Combat mode component. It makes the user face whichever atom the mouse pointer is hovering,
|
||||
* amongst other things designed outside of this file, namely PvP and PvE stuff, hence the name.
|
||||
* Can be toggled on and off by clicking the screen hud object or by pressing the assigned hotkey (default 'C')
|
||||
*/
|
||||
/datum/component/combat_mode
|
||||
var/mode_flags = COMBAT_MODE_INACTIVE
|
||||
var/combatmessagecooldown
|
||||
var/lastmousedir
|
||||
var/obj/screen/combattoggle/hud_icon
|
||||
var/hud_loc
|
||||
|
||||
/datum/component/combat_mode/Initialize(hud_loc = ui_combat_toggle)
|
||||
if(!isliving(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
var/mob/living/L = parent
|
||||
|
||||
src.hud_loc = hud_loc
|
||||
|
||||
RegisterSignal(L, SIGNAL_TRAIT(TRAIT_COMBAT_MODE_LOCKED), .proc/update_combat_lock)
|
||||
RegisterSignal(L, COMSIG_TOGGLE_COMBAT_MODE, .proc/user_toggle_intentional_combat_mode)
|
||||
RegisterSignal(L, COMSIG_DISABLE_COMBAT_MODE, .proc/safe_disable_combat_mode)
|
||||
RegisterSignal(L, COMSIG_ENABLE_COMBAT_MODE, .proc/safe_enable_combat_mode)
|
||||
RegisterSignal(L, COMSIG_MOB_DEATH, .proc/on_death)
|
||||
RegisterSignal(L, COMSIG_MOB_CLIENT_LOGOUT, .proc/on_logout)
|
||||
RegisterSignal(L, COMSIG_MOB_HUD_CREATED, .proc/on_mob_hud_created)
|
||||
RegisterSignal(L, COMSIG_COMBAT_MODE_CHECK, .proc/check_flags)
|
||||
|
||||
update_combat_lock()
|
||||
|
||||
if(L.client)
|
||||
on_mob_hud_created(L)
|
||||
|
||||
/datum/component/combat_mode/Destroy()
|
||||
if(parent)
|
||||
safe_disable_combat_mode(parent)
|
||||
if(hud_icon)
|
||||
QDEL_NULL(hud_icon)
|
||||
return ..()
|
||||
|
||||
/// Creates the hud screen object.
|
||||
/datum/component/combat_mode/proc/on_mob_hud_created(mob/source)
|
||||
hud_icon = new
|
||||
hud_icon.hud = source.hud_used
|
||||
hud_icon.icon = tg_ui_icon_to_cit_ui(source.hud_used.ui_style)
|
||||
hud_icon.screen_loc = hud_loc
|
||||
source.hud_used.static_inventory += hud_icon
|
||||
hud_icon.update_icon()
|
||||
|
||||
/// Combat mode can be locked out, forcibly disabled by a status trait.
|
||||
/datum/component/combat_mode/proc/update_combat_lock()
|
||||
var/locked = HAS_TRAIT(parent, TRAIT_COMBAT_MODE_LOCKED)
|
||||
var/desired = (mode_flags & COMBAT_MODE_TOGGLED)
|
||||
var/actual = (mode_flags & COMBAT_MODE_ACTIVE)
|
||||
if(actual)
|
||||
if(locked)
|
||||
disable_combat_mode(parent, FALSE, TRUE)
|
||||
else if(!desired)
|
||||
disable_combat_mode(parent, TRUE, TRUE)
|
||||
else
|
||||
if(desired && !locked)
|
||||
enable_combat_mode(parent, FALSE, TRUE)
|
||||
|
||||
/// Enables combat mode. Please use 'safe_enable_combat_mode' instead, if you wish to also enable the toggle flag.
|
||||
/datum/component/combat_mode/proc/enable_combat_mode(mob/living/source, silent = TRUE, forced = TRUE, visible = FALSE, locked = FALSE, playsound = FALSE)
|
||||
if(locked)
|
||||
if(hud_icon)
|
||||
hud_icon.combat_on = TRUE
|
||||
hud_icon.update_icon()
|
||||
return
|
||||
if(mode_flags & COMBAT_MODE_ACTIVE)
|
||||
return
|
||||
mode_flags |= COMBAT_MODE_ACTIVE
|
||||
mode_flags &= ~COMBAT_MODE_INACTIVE
|
||||
SEND_SIGNAL(source, COMSIG_LIVING_COMBAT_ENABLED, forced)
|
||||
if(!silent)
|
||||
var/self_message = forced? "<span class='warning'>Your muscles reflexively tighten!</span>" : "<span class='warning'>You drop into a combative stance!</span>"
|
||||
if(visible && (forced || world.time >= combatmessagecooldown))
|
||||
combatmessagecooldown = world.time + 10 SECONDS
|
||||
if(!forced)
|
||||
if(source.a_intent != INTENT_HELP)
|
||||
source.visible_message("<span class='warning'>[source] [source.resting ? "tenses up" : "drops into a combative stance"].</span>", self_message)
|
||||
else
|
||||
source.visible_message("<span class='notice'>[source] [pick("looks","seems","goes")] [pick("alert","attentive","vigilant")].</span>")
|
||||
else
|
||||
source.visible_message("<span class='warning'>[source] drops into a combative stance!</span>", self_message)
|
||||
else
|
||||
to_chat(source, self_message)
|
||||
if(playsound)
|
||||
source.playsound_local(source, 'sound/misc/ui_toggle.ogg', 50, FALSE, pressure_affected = FALSE) //Sound from interbay!
|
||||
RegisterSignal(source, COMSIG_MOB_CLIENT_MOUSEMOVE, .proc/onMouseMove)
|
||||
RegisterSignal(source, COMSIG_MOVABLE_MOVED, .proc/on_move)
|
||||
RegisterSignal(source, COMSIG_MOB_CLIENT_MOVE, .proc/on_client_move)
|
||||
if(hud_icon)
|
||||
hud_icon.combat_on = TRUE
|
||||
hud_icon.update_icon()
|
||||
|
||||
/// Disables combat mode. Please use 'safe_disable_combat_mode' instead, if you wish to also disable the toggle flag.
|
||||
/datum/component/combat_mode/proc/disable_combat_mode(mob/living/source, silent = TRUE, forced = TRUE, visible = FALSE, locked = FALSE, playsound = FALSE)
|
||||
if(locked)
|
||||
if(hud_icon)
|
||||
hud_icon.combat_on = FALSE
|
||||
hud_icon.update_icon()
|
||||
return
|
||||
if(!(mode_flags & COMBAT_MODE_ACTIVE))
|
||||
return
|
||||
mode_flags &= ~COMBAT_MODE_ACTIVE
|
||||
mode_flags |= COMBAT_MODE_INACTIVE
|
||||
SEND_SIGNAL(source, COMSIG_LIVING_COMBAT_DISABLED, forced)
|
||||
if(!silent)
|
||||
var/self_message = forced? "<span class='warning'>Your muscles are forcibly relaxed!</span>" : "<span class='warning'>You relax your stance.</span>"
|
||||
if(visible)
|
||||
source.visible_message("<span class='warning'>[source] relaxes [source.p_their()] stance.</span>", self_message)
|
||||
else
|
||||
to_chat(source, self_message)
|
||||
if(playsound)
|
||||
source.playsound_local(source, 'sound/misc/ui_toggleoff.ogg', 50, FALSE, pressure_affected = FALSE) //Slightly modified version of the toggleon sound!
|
||||
UnregisterSignal(source, list(COMSIG_MOB_CLIENT_MOUSEMOVE, COMSIG_MOVABLE_MOVED, COMSIG_MOB_CLIENT_MOVE))
|
||||
if(hud_icon)
|
||||
hud_icon.combat_on = FALSE
|
||||
hud_icon.update_icon()
|
||||
source.stop_active_blocking()
|
||||
source.end_parry_sequence()
|
||||
|
||||
///Changes the user direction to (try) keep match the pointer.
|
||||
/datum/component/combat_mode/proc/on_move(atom/movable/source, dir, atom/oldloc, forced)
|
||||
var/mob/living/L = source
|
||||
if(mode_flags & COMBAT_MODE_ACTIVE && L.client && lastmousedir && lastmousedir != dir)
|
||||
L.setDir(lastmousedir, ismousemovement = TRUE)
|
||||
|
||||
/// Added movement delay if moving backward.
|
||||
/datum/component/combat_mode/proc/on_client_move(mob/source, client/client, direction, n, oldloc, added_delay)
|
||||
if(oldloc != n && direction == REVERSE_DIR(source.dir))
|
||||
client.move_delay += added_delay*0.5
|
||||
|
||||
///Changes the user direction to (try) match the pointer.
|
||||
/datum/component/combat_mode/proc/onMouseMove(mob/source, object, location, control, params)
|
||||
if(source.client.show_popup_menus)
|
||||
return
|
||||
source.face_atom(object, TRUE)
|
||||
lastmousedir = source.dir
|
||||
|
||||
/// Toggles whether the user is intentionally in combat mode. THIS should be the proc you generally use! Has built in visual/to other player feedback, as well as an audible cue to ourselves.
|
||||
/datum/component/combat_mode/proc/user_toggle_intentional_combat_mode(mob/living/source)
|
||||
if(mode_flags & COMBAT_MODE_TOGGLED)
|
||||
safe_disable_combat_mode(source)
|
||||
else if(source.stat == CONSCIOUS && !(source.combat_flags & COMBAT_FLAG_HARD_STAMCRIT))
|
||||
safe_enable_combat_mode(source)
|
||||
|
||||
/// Enables intentionally being in combat mode. Please try to use the COMSIG_COMBAT_MODE_CHECK signal for feedback when possible.
|
||||
/datum/component/combat_mode/proc/safe_enable_combat_mode(mob/living/source, silent = FALSE, visible = TRUE)
|
||||
if((mode_flags & COMBAT_MODE_TOGGLED) && (mode_flags & COMBAT_MODE_ACTIVE))
|
||||
return TRUE
|
||||
mode_flags |= COMBAT_MODE_TOGGLED
|
||||
enable_combat_mode(source, silent, FALSE, visible, HAS_TRAIT(source, TRAIT_COMBAT_MODE_LOCKED), TRUE)
|
||||
if(source.client)
|
||||
source.client.show_popup_menus = FALSE
|
||||
if(iscarbon(source)) //I dislike this typecheck. It probably should be removed once that spoiled apple is componentized too.
|
||||
var/mob/living/carbon/C = source
|
||||
if(C.voremode)
|
||||
C.disable_vore_mode()
|
||||
return TRUE
|
||||
|
||||
/// Disables intentionally being in combat mode. Please try to use the COMSIG_COMBAT_MODE_CHECK signal for feedback when possible.
|
||||
/datum/component/combat_mode/proc/safe_disable_combat_mode(mob/living/source, silent = FALSE, visible = FALSE)
|
||||
if(!(mode_flags & COMBAT_MODE_TOGGLED) && !(mode_flags & COMBAT_MODE_ACTIVE))
|
||||
return TRUE
|
||||
mode_flags &= ~COMBAT_MODE_TOGGLED
|
||||
disable_combat_mode(source, silent, FALSE, visible, !(mode_flags & COMBAT_MODE_ACTIVE), TRUE)
|
||||
if(source.client)
|
||||
source.client.show_popup_menus = TRUE
|
||||
return TRUE
|
||||
|
||||
/// Returns a field of flags that are contained in both the second arg and our bitfield variable.
|
||||
/datum/component/combat_mode/proc/check_flags(mob/living/source, flags)
|
||||
return mode_flags & (flags)
|
||||
|
||||
/// Disables combat mode upon death.
|
||||
/datum/component/combat_mode/proc/on_death(mob/living/source)
|
||||
safe_disable_combat_mode(source)
|
||||
|
||||
/// Disables combat mode upon logout
|
||||
/datum/component/combat_mode/proc/on_logout(mob/living/source)
|
||||
safe_disable_combat_mode(source)
|
||||
|
||||
/// The screen button.
|
||||
/obj/screen/combattoggle
|
||||
name = "toggle combat mode"
|
||||
icon = 'modular_citadel/icons/ui/screen_midnight.dmi'
|
||||
icon_state = "combat_off"
|
||||
var/mutable_appearance/flashy
|
||||
var/combat_on = FALSE ///Wheter combat mode is enabled or not, so we don't have to store a reference.
|
||||
|
||||
/obj/screen/combattoggle/Click()
|
||||
if(hud && usr == hud.mymob)
|
||||
SEND_SIGNAL(hud.mymob, COMSIG_TOGGLE_COMBAT_MODE)
|
||||
|
||||
/obj/screen/combattoggle/update_icon_state()
|
||||
var/mob/living/user = hud?.mymob
|
||||
if(!user)
|
||||
return
|
||||
if(combat_on)
|
||||
icon_state = "combat"
|
||||
else if(HAS_TRAIT(user, TRAIT_COMBAT_MODE_LOCKED))
|
||||
icon_state = "combat_locked"
|
||||
else
|
||||
icon_state = "combat_off"
|
||||
|
||||
/obj/screen/combattoggle/update_overlays()
|
||||
. = ..()
|
||||
var/mob/living/carbon/user = hud?.mymob
|
||||
if(!(user?.client))
|
||||
return
|
||||
|
||||
if(combat_on)
|
||||
if(!flashy)
|
||||
flashy = mutable_appearance('icons/mob/screen_gen.dmi', "togglefull_flash")
|
||||
flashy.color = user.client.prefs.hud_toggle_color
|
||||
. += flashy //TODO - beg lummox jr for the ability to force mutable appearances or images to be created rendering from their first frame of animation rather than being based entirely around the client's frame count
|
||||
@@ -74,7 +74,7 @@
|
||||
|
||||
//////////////////////Lens//////////////////////////
|
||||
//Six Steps //
|
||||
//Sells for 1800 cr, takes 15 glass shets //
|
||||
//Sells for 1600 cr, takes 15 glass shets //
|
||||
//Usefull for selling and later crafting //
|
||||
////////////////////////////////////////////////////
|
||||
|
||||
@@ -146,14 +146,14 @@
|
||||
|
||||
/obj/item/glasswork/glass_base/glass_lens_part5
|
||||
name = "Unpolished glass lens"
|
||||
desc = "A small unpolished glass lens. Could be polished with some silk."
|
||||
desc = "A small unpolished glass lens. Could be polished with some cloth."
|
||||
icon = 'icons/obj/glass_ware.dmi'
|
||||
icon_state = "glass_optics"
|
||||
next_step = /obj/item/glasswork/glass_base/glass_lens_part6
|
||||
|
||||
/obj/item/glasswork/glass_base/glass_lens_part5/attackby(obj/item/I, mob/user, params)
|
||||
..()
|
||||
if(istype(I, /obj/item/stack/sheet/silk))
|
||||
if(istype(I, /obj/item/stack/sheet/cloth))
|
||||
if(do_after(user,10, target = src))
|
||||
new next_step(user.loc, 1)
|
||||
qdel(src)
|
||||
@@ -324,7 +324,7 @@
|
||||
|
||||
//////////////////////Tea Plates////////////////////
|
||||
//Three Steps //
|
||||
//Sells for 1200 cr, takes 5 glass shets //
|
||||
//Sells for 1000 cr, takes 5 glass shets //
|
||||
//Usefull for selling and chemical things //
|
||||
////////////////////////////////////////////////////
|
||||
|
||||
@@ -370,20 +370,20 @@
|
||||
|
||||
/obj/item/glasswork/glass_base/tea_plate3
|
||||
name = "Disk of glass"
|
||||
desc = "A disk of glass that can be cant be used for much. Needs to be polished with some silk."
|
||||
desc = "A disk of glass that can be cant be used for much. Needs to be polished with some cloth."
|
||||
icon_state = "glass_base_half"
|
||||
next_step = /obj/item/tea_plate
|
||||
|
||||
/obj/item/glasswork/glass_base/tea_plate3/attackby(obj/item/I, mob/user, params)
|
||||
..()
|
||||
if(istype(I, /obj/item/stack/sheet/silk))
|
||||
if(istype(I, /obj/item/stack/sheet/cloth))
|
||||
if(do_after(user,10, target = src))
|
||||
new next_step(user.loc, 1)
|
||||
qdel(src)
|
||||
|
||||
//////////////////////Tea Cup///////////////////////
|
||||
//Four Steps //
|
||||
//Sells for 1800 cr, takes 6 glass shets //
|
||||
//Sells for 1600 cr, takes 6 glass shets //
|
||||
//Usefull for selling and chemical things //
|
||||
////////////////////////////////////////////////////
|
||||
|
||||
@@ -429,13 +429,13 @@
|
||||
|
||||
/obj/item/glasswork/glass_base/tea_cup3
|
||||
name = "Disk of glass"
|
||||
desc = "A bowl of glass that can be cant be used for much. Needs to be polished with some silk."
|
||||
desc = "A bowl of glass that can be cant be used for much. Needs to be polished with some cloth."
|
||||
icon_state = "glass_base_half"
|
||||
next_step = /obj/item/glasswork/glass_base/tea_cup4
|
||||
|
||||
/obj/item/glasswork/glass_base/cup3/attackby(obj/item/I, mob/user, params)
|
||||
..()
|
||||
if(istype(I, /obj/item/stack/sheet/silk))
|
||||
if(istype(I, /obj/item/stack/sheet/cloth))
|
||||
if(do_after(user,10, target = src))
|
||||
new next_step(user.loc, 1)
|
||||
qdel(src)
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
qdel(src)
|
||||
|
||||
/obj/item/glasswork/glasses
|
||||
name = "Hand Made Glasses"
|
||||
desc = "Hande made glasses that have not been polished at all making them useless. Selling them could still be worth a bit of credits."
|
||||
name = "Handmade Glasses"
|
||||
desc = "Handmade glasses that have not been polished at all making them useless. Selling them could still be worth a few credits."
|
||||
icon = 'icons/obj/glass_ware.dmi'
|
||||
icon_state = "frames_2"
|
||||
@@ -1,21 +1,52 @@
|
||||
//Gun crafting parts til they can be moved elsewhere
|
||||
|
||||
// PARTS //
|
||||
|
||||
/obj/item/weaponcrafting/receiver
|
||||
name = "modular receiver"
|
||||
desc = "A prototype modular receiver and trigger assembly for a firearm."
|
||||
/obj/item/weaponcrafting
|
||||
icon = 'icons/obj/improvised.dmi'
|
||||
icon_state = "receiver"
|
||||
|
||||
/obj/item/weaponcrafting/stock
|
||||
name = "rifle stock"
|
||||
desc = "A classic rifle stock that doubles as a grip, roughly carved out of wood."
|
||||
icon = 'icons/obj/improvised.dmi'
|
||||
custom_materials = list(/datum/material/wood = MINERAL_MATERIAL_AMOUNT * 6)
|
||||
icon_state = "riflestock"
|
||||
|
||||
/obj/item/weaponcrafting/silkstring
|
||||
name = "silkstring"
|
||||
desc = "A long piece of silk with some resemblance to cable coil."
|
||||
icon = 'icons/obj/improvised.dmi'
|
||||
icon_state = "silkstring"
|
||||
/obj/item/weaponcrafting/string
|
||||
name = "wound thread"
|
||||
desc = "A long piece of thread with some resemblance to cable coil."
|
||||
icon_state = "durastring"
|
||||
|
||||
////////////////////////////////
|
||||
// IMPROVISED WEAPON PARTS//
|
||||
////////////////////////////////
|
||||
|
||||
/obj/item/weaponcrafting/improvised_parts
|
||||
name = "Debug Improvised Gun Part"
|
||||
desc = "A badly coded gun part. You should report coders if you see this."
|
||||
icon = 'icons/obj/guns/gun_parts.dmi'
|
||||
icon_state = "palette"
|
||||
|
||||
// RECEIVERS
|
||||
|
||||
/obj/item/weaponcrafting/improvised_parts/rifle_receiver
|
||||
name = "rifle receiver"
|
||||
desc = "A crudely constructed receiver to create an improvised bolt-action breechloaded rifle." // removed some text implying that the item had more uses than it does
|
||||
icon_state = "receiver_rifle"
|
||||
w_class = WEIGHT_CLASS_SMALL
|
||||
|
||||
|
||||
/obj/item/weaponcrafting/improvised_parts/shotgun_receiver
|
||||
name = "shotgun reciever"
|
||||
desc = "An improvised receiver to create a break-action breechloaded shotgun." // removed some text implying that the item had more uses than it does
|
||||
icon_state = "receiver_shotgun"
|
||||
w_class = WEIGHT_CLASS_SMALL
|
||||
|
||||
// MISC
|
||||
|
||||
/obj/item/weaponcrafting/improvised_parts/trigger_assembly
|
||||
name = "firearm trigger assembly"
|
||||
desc = "A modular trigger assembly with a firing pin, this can be used to make a whole bunch of improvised firearss."
|
||||
icon_state = "trigger_assembly"
|
||||
w_class = WEIGHT_CLASS_SMALL
|
||||
|
||||
/obj/item/weaponcrafting/improvised_parts/wooden_body
|
||||
name = "wooden firearm body"
|
||||
desc = "A crudely fashioned wooden body to help keep higher calibre improvised weapons from blowing themselves apart."
|
||||
icon_state = "wooden_body"
|
||||
|
||||
@@ -39,14 +39,14 @@
|
||||
time = 10
|
||||
reqs = list(/obj/item/paper = 20)
|
||||
category = CAT_CLOTHING
|
||||
|
||||
|
||||
/datum/crafting_recipe/armwraps
|
||||
name = "Armwraps"
|
||||
result = /obj/item/clothing/gloves/fingerless/pugilist
|
||||
time = 60
|
||||
tools = list(TOOL_WIRECUTTER)
|
||||
reqs = list(/obj/item/stack/sheet/cloth = 4,
|
||||
/obj/item/stack/sheet/silk = 2,
|
||||
/obj/item/stack/sheet/durathread = 2,
|
||||
/obj/item/stack/sheet/leather = 2)
|
||||
category = CAT_CLOTHING
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@
|
||||
time = 150
|
||||
subcategory = CAT_MISCELLANEOUS
|
||||
category = CAT_MISC
|
||||
always_availible = FALSE // Disabled til learned
|
||||
always_availible = FALSE // Disabled until learned
|
||||
|
||||
|
||||
/datum/crafting_recipe/bloodsucker/candelabrum
|
||||
@@ -124,9 +124,9 @@
|
||||
category = CAT_MISC
|
||||
subcategory = CAT_TOOL
|
||||
|
||||
/datum/crafting_recipe/bruise_pack
|
||||
name = "Bruise Pack"
|
||||
result = /obj/item/stack/medical/bruise_pack/one
|
||||
/datum/crafting_recipe/brute_pack
|
||||
name = "Suture Pack"
|
||||
result = /obj/item/stack/medical/suture/one
|
||||
time = 1
|
||||
reqs = list(/obj/item/stack/medical/gauze = 1,
|
||||
/datum/reagent/medicine/styptic_powder = 10)
|
||||
@@ -134,8 +134,8 @@
|
||||
subcategory = CAT_TOOL
|
||||
|
||||
/datum/crafting_recipe/burn_pack
|
||||
name = "Burn Ointment"
|
||||
result = /obj/item/stack/medical/ointment/one
|
||||
name = "Regenerative Mesh"
|
||||
result = /obj/item/stack/medical/mesh/one
|
||||
time = 1
|
||||
reqs = list(/obj/item/stack/medical/gauze = 1,
|
||||
/datum/reagent/medicine/silver_sulfadiazine = 10)
|
||||
@@ -157,7 +157,7 @@
|
||||
/datum/crafting_recipe/goldenbox
|
||||
name = "Gold Plated Toolbox"
|
||||
result = /obj/item/storage/toolbox/gold_fake
|
||||
tools = list(/obj/item/stock_parts/cell/upgraded/plus)
|
||||
tools = list(/obj/item/stock_parts/cell/high)
|
||||
reqs = list(/obj/item/stack/sheet/cardboard = 1, //so we dont null items in crafting
|
||||
/obj/item/stack/cable_coil = 10,
|
||||
/obj/item/stack/sheet/mineral/gold = 1,
|
||||
@@ -184,7 +184,7 @@
|
||||
|
||||
/datum/crafting_recipe/bronze_driver
|
||||
name = "Bronze Plated Screwdriver"
|
||||
tools = list(/obj/item/stock_parts/cell/upgraded/plus)
|
||||
tools = list(/obj/item/stock_parts/cell/high)
|
||||
result = /obj/item/screwdriver/bronze
|
||||
reqs = list(/obj/item/screwdriver = 1,
|
||||
/obj/item/stack/cable_coil = 10,
|
||||
@@ -196,7 +196,7 @@
|
||||
|
||||
/datum/crafting_recipe/bronze_welder
|
||||
name = "Bronze Plated Welding Tool"
|
||||
tools = list(/obj/item/stock_parts/cell/upgraded/plus)
|
||||
tools = list(/obj/item/stock_parts/cell/high)
|
||||
result = /obj/item/weldingtool/bronze
|
||||
reqs = list(/obj/item/weldingtool = 1,
|
||||
/obj/item/stack/cable_coil = 10,
|
||||
@@ -208,7 +208,7 @@
|
||||
|
||||
/datum/crafting_recipe/bronze_wirecutters
|
||||
name = "Bronze Plated Wirecutters"
|
||||
tools = list(/obj/item/stock_parts/cell/upgraded/plus)
|
||||
tools = list(/obj/item/stock_parts/cell/high)
|
||||
result = /obj/item/wirecutters/bronze
|
||||
reqs = list(/obj/item/wirecutters = 1,
|
||||
/obj/item/stack/cable_coil = 10,
|
||||
@@ -220,7 +220,7 @@
|
||||
|
||||
/datum/crafting_recipe/bronze_crowbar
|
||||
name = "Bronze Plated Crowbar"
|
||||
tools = list(/obj/item/stock_parts/cell/upgraded/plus)
|
||||
tools = list(/obj/item/stock_parts/cell/high)
|
||||
result = /obj/item/crowbar/bronze
|
||||
reqs = list(/obj/item/crowbar = 1,
|
||||
/obj/item/stack/cable_coil = 10,
|
||||
@@ -232,7 +232,7 @@
|
||||
|
||||
/datum/crafting_recipe/bronze_wrench
|
||||
name = "Bronze Plated Wrench"
|
||||
tools = list(/obj/item/stock_parts/cell/upgraded/plus)
|
||||
tools = list(/obj/item/stock_parts/cell/high)
|
||||
result = /obj/item/wrench/bronze
|
||||
reqs = list(/obj/item/wrench = 1,
|
||||
/obj/item/stack/cable_coil = 10,
|
||||
@@ -244,7 +244,7 @@
|
||||
|
||||
/datum/crafting_recipe/rcl
|
||||
name = "Makeshift Rapid Cable Layer"
|
||||
result = /obj/item/twohanded/rcl/ghetto
|
||||
result = /obj/item/rcl/ghetto
|
||||
time = 40
|
||||
tools = list(TOOL_WELDER, TOOL_SCREWDRIVER, TOOL_WRENCH)
|
||||
reqs = list(/obj/item/stack/sheet/metal = 15)
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
|
||||
/datum/crafting_recipe/bonespear
|
||||
name = "Bone Spear"
|
||||
result = /obj/item/twohanded/bonespear
|
||||
result = /obj/item/spear/bonespear
|
||||
time = 30
|
||||
reqs = list(/obj/item/stack/sheet/bone = 4,
|
||||
/obj/item/stack/sheet/sinew = 1)
|
||||
@@ -58,7 +58,7 @@
|
||||
|
||||
/datum/crafting_recipe/boneaxe
|
||||
name = "Bone Axe"
|
||||
result = /obj/item/twohanded/fireaxe/boneaxe
|
||||
result = /obj/item/fireaxe/boneaxe
|
||||
time = 50
|
||||
reqs = list(/obj/item/stack/sheet/bone = 6,
|
||||
/obj/item/stack/sheet/sinew = 3)
|
||||
@@ -74,20 +74,20 @@
|
||||
/datum/crafting_recipe/headpike
|
||||
name = "Spike Head (Glass Spear)"
|
||||
time = 65
|
||||
reqs = list(/obj/item/twohanded/spear = 1,
|
||||
reqs = list(/obj/item/spear = 1,
|
||||
/obj/item/bodypart/head = 1)
|
||||
parts = list(/obj/item/bodypart/head = 1,
|
||||
/obj/item/twohanded/spear = 1)
|
||||
/obj/item/spear = 1)
|
||||
result = /obj/structure/headpike
|
||||
category = CAT_PRIMAL
|
||||
|
||||
/datum/crafting_recipe/headpikebone
|
||||
name = "Spike Head (Bone Spear)"
|
||||
time = 65
|
||||
reqs = list(/obj/item/twohanded/bonespear = 1,
|
||||
reqs = list(/obj/item/spear/bonespear = 1,
|
||||
/obj/item/bodypart/head = 1)
|
||||
parts = list(/obj/item/bodypart/head = 1,
|
||||
/obj/item/twohanded/bonespear = 1)
|
||||
/obj/item/spear/bonespear = 1)
|
||||
result = /obj/structure/headpike/bone
|
||||
category = CAT_PRIMAL
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
/datum/crafting_recipe/bone_bow
|
||||
name = "Bone Bow"
|
||||
result = /obj/item/gun/ballistic/bow/ashen
|
||||
time = 200
|
||||
time = 120 // 80+120 = 200
|
||||
always_availible = FALSE
|
||||
reqs = list(/obj/item/stack/sheet/bone = 8,
|
||||
/obj/item/stack/sheet/sinew = 4)
|
||||
@@ -112,8 +112,45 @@
|
||||
/datum/crafting_recipe/bow_tablet
|
||||
name = "Sandstone Bow Making Manual"
|
||||
result = /obj/item/book/granter/crafting_recipe/bone_bow
|
||||
time = 600 //Scribing
|
||||
time = 200 //Scribing // don't care
|
||||
always_availible = FALSE
|
||||
reqs = list(/obj/item/stack/rods = 1,
|
||||
/obj/item/stack/sheet/mineral/sandstone = 4)
|
||||
category = CAT_PRIMAL
|
||||
category = CAT_PRIMAL
|
||||
|
||||
/datum/crafting_recipe/rib
|
||||
name = "Collosal Rib"
|
||||
always_availible = FALSE
|
||||
reqs = list(
|
||||
/obj/item/stack/sheet/bone = 10,
|
||||
/datum/reagent/oil = 5)
|
||||
result = /obj/structure/statue/bone/rib
|
||||
subcategory = CAT_PRIMAL
|
||||
|
||||
/datum/crafting_recipe/skull
|
||||
name = "Skull Carving"
|
||||
always_availible = FALSE
|
||||
reqs = list(
|
||||
/obj/item/stack/sheet/bone = 6,
|
||||
/datum/reagent/oil = 5)
|
||||
result = /obj/structure/statue/bone/skull
|
||||
category = CAT_PRIMAL
|
||||
|
||||
/datum/crafting_recipe/halfskull
|
||||
name = "Cracked Skull Carving"
|
||||
always_availible = FALSE
|
||||
reqs = list(
|
||||
/obj/item/stack/sheet/bone = 3,
|
||||
/datum/reagent/oil = 5)
|
||||
result = /obj/structure/statue/bone/skull/half
|
||||
category = CAT_PRIMAL
|
||||
|
||||
/datum/crafting_recipe/boneshovel
|
||||
name = "Serrated Bone Shovel"
|
||||
always_availible = FALSE
|
||||
reqs = list(
|
||||
/obj/item/stack/sheet/bone = 4,
|
||||
/datum/reagent/oil = 5,
|
||||
/obj/item/shovel/spade = 1)
|
||||
result = /obj/item/shovel/serrated
|
||||
category = CAT_PRIMAL
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
tools = list(TOOL_WELDER, TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
|
||||
time = 50
|
||||
category = CAT_WEAPONRY
|
||||
subcategory = CAT_WEAPON
|
||||
subcategory = CAT_OTHER
|
||||
|
||||
/datum/crafting_recipe/pin_removal/check_requirements(mob/user, list/collected_requirements)
|
||||
var/obj/item/gun/G = collected_requirements[/obj/item/gun][1]
|
||||
@@ -22,7 +22,7 @@
|
||||
/obj/item/shield/riot = 1)
|
||||
time = 40
|
||||
category = CAT_WEAPONRY
|
||||
subcategory = CAT_WEAPON
|
||||
subcategory = CAT_MELEE
|
||||
|
||||
/datum/crafting_recipe/strobeshield/New()
|
||||
..()
|
||||
@@ -38,18 +38,18 @@
|
||||
tools = list(TOOL_WELDER, TOOL_SCREWDRIVER, TOOL_WIRECUTTER)
|
||||
time = 100
|
||||
category = CAT_WEAPONRY
|
||||
subcategory = CAT_WEAPON
|
||||
subcategory = CAT_MELEE
|
||||
|
||||
/datum/crafting_recipe/spear
|
||||
name = "Spear"
|
||||
result = /obj/item/twohanded/spear
|
||||
result = /obj/item/spear
|
||||
reqs = list(/obj/item/restraints/handcuffs/cable = 1,
|
||||
/obj/item/shard = 1,
|
||||
/obj/item/stack/rods = 1)
|
||||
parts = list(/obj/item/shard = 1)
|
||||
time = 40
|
||||
category = CAT_WEAPONRY
|
||||
subcategory = CAT_WEAPON
|
||||
subcategory = CAT_MELEE
|
||||
|
||||
/datum/crafting_recipe/stunprod
|
||||
name = "Stunprod"
|
||||
@@ -59,7 +59,7 @@
|
||||
/obj/item/assembly/igniter = 1)
|
||||
time = 40
|
||||
category = CAT_WEAPONRY
|
||||
subcategory = CAT_WEAPON
|
||||
subcategory = CAT_MELEE
|
||||
|
||||
/datum/crafting_recipe/teleprod
|
||||
name = "Teleprod"
|
||||
@@ -70,7 +70,7 @@
|
||||
/obj/item/stack/ore/bluespace_crystal = 1)
|
||||
time = 40
|
||||
category = CAT_WEAPONRY
|
||||
subcategory = CAT_WEAPON
|
||||
subcategory = CAT_MELEE
|
||||
|
||||
/datum/crafting_recipe/bola
|
||||
name = "Bola"
|
||||
@@ -88,7 +88,7 @@
|
||||
/obj/item/stack/sheet/metal = 1)
|
||||
time = 40
|
||||
category = CAT_WEAPONRY
|
||||
subcategory = CAT_WEAPON
|
||||
subcategory = CAT_MELEE
|
||||
|
||||
/datum/crafting_recipe/tailwhip
|
||||
name = "Liz O' Nine Tails"
|
||||
@@ -97,7 +97,7 @@
|
||||
/obj/item/stack/cable_coil = 1)
|
||||
time = 40
|
||||
category = CAT_WEAPONRY
|
||||
subcategory = CAT_WEAPON
|
||||
subcategory = CAT_MELEE
|
||||
|
||||
/datum/crafting_recipe/catwhip
|
||||
name = "Cat O' Nine Tails"
|
||||
@@ -106,18 +106,18 @@
|
||||
/obj/item/stack/cable_coil = 1)
|
||||
time = 40
|
||||
category = CAT_WEAPONRY
|
||||
subcategory = CAT_WEAPON
|
||||
subcategory = CAT_MELEE
|
||||
|
||||
/datum/crafting_recipe/chainsaw
|
||||
name = "Chainsaw"
|
||||
result = /obj/item/twohanded/required/chainsaw
|
||||
result = /obj/item/chainsaw
|
||||
reqs = list(/obj/item/circular_saw = 1,
|
||||
/obj/item/stack/cable_coil = 3,
|
||||
/obj/item/stack/sheet/plasteel = 5)
|
||||
tools = list(TOOL_WELDER)
|
||||
time = 50
|
||||
category = CAT_WEAPONRY
|
||||
subcategory = CAT_WEAPON
|
||||
subcategory = CAT_MELEE
|
||||
|
||||
//////////////////
|
||||
///BOMB CRAFTING//
|
||||
@@ -134,20 +134,20 @@
|
||||
parts = list(/obj/item/stock_parts/matter_bin = 1, /obj/item/grenade/chem_grenade = 2)
|
||||
time = 30
|
||||
category = CAT_WEAPONRY
|
||||
subcategory = CAT_WEAPON
|
||||
subcategory = CAT_OTHER
|
||||
|
||||
/datum/crafting_recipe/chemical_payload2
|
||||
name = "Chemical Payload (Gibtonite)"
|
||||
result = /obj/item/bombcore/chemical
|
||||
reqs = list(
|
||||
/obj/item/stock_parts/matter_bin = 1,
|
||||
/obj/item/twohanded/required/gibtonite = 1,
|
||||
/obj/item/gibtonite = 1,
|
||||
/obj/item/grenade/chem_grenade = 2
|
||||
)
|
||||
parts = list(/obj/item/stock_parts/matter_bin = 1, /obj/item/grenade/chem_grenade = 2)
|
||||
time = 50
|
||||
category = CAT_WEAPONRY
|
||||
subcategory = CAT_WEAPON
|
||||
subcategory = CAT_OTHER
|
||||
|
||||
/datum/crafting_recipe/molotov
|
||||
name = "Molotov"
|
||||
@@ -169,18 +169,18 @@
|
||||
parts = list(/obj/item/reagent_containers/food/drinks/soda_cans = 1)
|
||||
time = 15
|
||||
category = CAT_WEAPONRY
|
||||
subcategory = CAT_WEAPON
|
||||
subcategory = CAT_OTHER
|
||||
|
||||
/datum/crafting_recipe/lance
|
||||
name = "Explosive Lance (Grenade)"
|
||||
result = /obj/item/twohanded/spear
|
||||
reqs = list(/obj/item/twohanded/spear = 1,
|
||||
result = /obj/item/spear
|
||||
reqs = list(/obj/item/spear = 1,
|
||||
/obj/item/grenade = 1)
|
||||
parts = list(/obj/item/twohanded/spear = 1,
|
||||
parts = list(/obj/item/spear = 1,
|
||||
/obj/item/grenade = 1)
|
||||
time = 15
|
||||
category = CAT_WEAPONRY
|
||||
subcategory = CAT_WEAPON
|
||||
subcategory = CAT_MELEE
|
||||
|
||||
//////////////////
|
||||
///GUNS CRAFTING//
|
||||
@@ -192,8 +192,8 @@
|
||||
result = /obj/item/gun/ballistic/bow/pipe
|
||||
reqs = list(/obj/item/pipe = 5,
|
||||
/obj/item/stack/sheet/plastic = 15,
|
||||
/obj/item/weaponcrafting/silkstring = 5)
|
||||
time = 450
|
||||
/obj/item/weaponcrafting/string = 5)
|
||||
time = 150
|
||||
category = CAT_WEAPONRY
|
||||
subcategory = CAT_WEAPON
|
||||
|
||||
@@ -248,11 +248,13 @@
|
||||
category = CAT_WEAPONRY
|
||||
subcategory = CAT_WEAPON
|
||||
|
||||
/datum/crafting_recipe/ishotgun
|
||||
/datum/crafting_recipe/ishotgun // smaller and more versatile gun requires some better materials
|
||||
name = "Improvised Shotgun"
|
||||
result = /obj/item/gun/ballistic/revolver/doublebarrel/improvised
|
||||
reqs = list(/obj/item/weaponcrafting/receiver = 1,
|
||||
/obj/item/pipe = 1,
|
||||
reqs = list(/obj/item/pipe = 2, // putting a large amount of meaningless timegates by forcing people to turn base resources into upgraded resources kinda sucks
|
||||
/obj/item/weaponcrafting/improvised_parts/shotgun_receiver = 1,
|
||||
/obj/item/weaponcrafting/improvised_parts/trigger_assembly = 1,
|
||||
/obj/item/weaponcrafting/improvised_parts/wooden_body = 1,
|
||||
/obj/item/weaponcrafting/stock = 1,
|
||||
/obj/item/stack/packageWrap = 5)
|
||||
tools = list(TOOL_SCREWDRIVER)
|
||||
@@ -260,11 +262,13 @@
|
||||
category = CAT_WEAPONRY
|
||||
subcategory = CAT_WEAPON
|
||||
|
||||
/datum/crafting_recipe/irifle
|
||||
name = "Improvised Rifle(7.62mm)"
|
||||
/datum/crafting_recipe/irifle // larger and less versatile gun, but a bit easier to make
|
||||
name = "Improvised Rifle (7.62mm)"
|
||||
result = /obj/item/gun/ballistic/shotgun/boltaction/improvised
|
||||
reqs = list(/obj/item/weaponcrafting/receiver = 1,
|
||||
/obj/item/pipe = 2,
|
||||
reqs = list(/obj/item/pipe = 2, // above
|
||||
/obj/item/weaponcrafting/improvised_parts/rifle_receiver = 1,
|
||||
/obj/item/weaponcrafting/improvised_parts/trigger_assembly = 1,
|
||||
/obj/item/weaponcrafting/improvised_parts/wooden_body = 1,
|
||||
/obj/item/weaponcrafting/stock = 1,
|
||||
/obj/item/stack/packageWrap = 5)
|
||||
tools = list(TOOL_SCREWDRIVER)
|
||||
@@ -279,9 +283,9 @@
|
||||
/datum/crafting_recipe/arrow
|
||||
name = "Arrow"
|
||||
result = /obj/item/ammo_casing/caseless/arrow/wood
|
||||
time = 30
|
||||
time = 5 // these only do 15 damage
|
||||
reqs = list(/obj/item/stack/sheet/mineral/wood = 1,
|
||||
/obj/item/stack/sheet/silk = 1,
|
||||
/obj/item/stack/sheet/cloth = 1,
|
||||
/obj/item/stack/rods = 1) // 1 metal sheet = 2 rods = 2 arrows
|
||||
category = CAT_WEAPONRY
|
||||
subcategory = CAT_AMMO
|
||||
@@ -289,7 +293,7 @@
|
||||
/datum/crafting_recipe/bone_arrow
|
||||
name = "Bone Arrow"
|
||||
result = /obj/item/ammo_casing/caseless/arrow/bone
|
||||
time = 30
|
||||
time = 5
|
||||
always_availible = FALSE
|
||||
reqs = list(/obj/item/stack/sheet/bone = 1,
|
||||
/obj/item/stack/sheet/sinew = 1,
|
||||
@@ -301,7 +305,7 @@
|
||||
name = "Ashen Arrow"
|
||||
result = /obj/item/ammo_casing/caseless/arrow/ash
|
||||
tools = list(TOOL_WELDER)
|
||||
time = 30
|
||||
time = 10 // 1.5 seconds minimum per actually worthwhile arrow excluding interface lag
|
||||
always_availible = FALSE
|
||||
reqs = list(/obj/item/ammo_casing/caseless/arrow/wood = 1)
|
||||
category = CAT_WEAPONRY
|
||||
@@ -394,3 +398,40 @@
|
||||
time = 5
|
||||
category = CAT_WEAPONRY
|
||||
subcategory = CAT_AMMO
|
||||
|
||||
////////////////////
|
||||
// PARTS CRAFTING //
|
||||
////////////////////
|
||||
|
||||
// RECEIVERS
|
||||
|
||||
/datum/crafting_recipe/rifle_receiver
|
||||
name = "Improvised Rifle Receiver"
|
||||
result = /obj/item/weaponcrafting/improvised_parts/rifle_receiver
|
||||
reqs = list(/obj/item/stack/sheet/metal = 15) // you can carry multiple shotguns
|
||||
tools = list(TOOL_SCREWDRIVER, TOOL_WELDER)
|
||||
time = 25
|
||||
category = CAT_WEAPONRY
|
||||
subcategory = CAT_PARTS
|
||||
|
||||
/datum/crafting_recipe/shotgun_receiver
|
||||
name = "Improvised Shotgun Receiver"
|
||||
result = /obj/item/weaponcrafting/improvised_parts/shotgun_receiver
|
||||
reqs = list(/obj/item/stack/sheet/metal = 15,
|
||||
/obj/item/stack/sheet/plasteel = 1) // requires access or hacking since shotgun is better
|
||||
tools = list(TOOL_SCREWDRIVER, TOOL_WELDER)
|
||||
time = 25
|
||||
category = CAT_WEAPONRY
|
||||
subcategory = CAT_PARTS
|
||||
|
||||
// MISC
|
||||
|
||||
/datum/crafting_recipe/trigger_assembly
|
||||
name = "Trigger Assembly"
|
||||
result = /obj/item/weaponcrafting/improvised_parts/trigger_assembly
|
||||
reqs = list(/obj/item/stack/sheet/metal = 3,
|
||||
/obj/item/assembly/igniter = 1)
|
||||
tools = list(TOOL_SCREWDRIVER, TOOL_WELDER)
|
||||
time = 25
|
||||
category = CAT_WEAPONRY
|
||||
subcategory = CAT_PARTS
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
/datum/component/decal
|
||||
dupe_mode = COMPONENT_DUPE_ALLOWED
|
||||
can_transfer = TRUE
|
||||
var/cleanable
|
||||
var/description
|
||||
var/mutable_appearance/pic
|
||||
|
||||
var/first_dir // This only stores the dir arg from init
|
||||
|
||||
/datum/component/decal/Initialize(_icon, _icon_state, _dir, _cleanable=CLEAN_GOD, _color, _layer=TURF_LAYER, _description, _alpha=255)
|
||||
if(!isatom(parent) || !generate_appearance(_icon, _icon_state, _dir, _layer, _color, _alpha))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
first_dir = _dir
|
||||
description = _description
|
||||
cleanable = _cleanable
|
||||
|
||||
apply()
|
||||
|
||||
/datum/component/decal/RegisterWithParent()
|
||||
. = ..()
|
||||
if(first_dir)
|
||||
RegisterSignal(parent, COMSIG_ATOM_DIR_CHANGE, .proc/rotate_react)
|
||||
if(cleanable)
|
||||
RegisterSignal(parent, COMSIG_COMPONENT_CLEAN_ACT, .proc/clean_react)
|
||||
if(description)
|
||||
RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/examine)
|
||||
|
||||
/datum/component/decal/UnregisterFromParent()
|
||||
. = ..()
|
||||
UnregisterSignal(parent, list(COMSIG_ATOM_DIR_CHANGE, COMSIG_COMPONENT_CLEAN_ACT, COMSIG_PARENT_EXAMINE))
|
||||
|
||||
/datum/component/decal/Destroy()
|
||||
remove()
|
||||
return ..()
|
||||
|
||||
/datum/component/decal/PreTransfer()
|
||||
remove()
|
||||
|
||||
/datum/component/decal/PostTransfer()
|
||||
remove()
|
||||
apply()
|
||||
|
||||
/datum/component/decal/proc/generate_appearance(_icon, _icon_state, _dir, _layer, _color, _alpha)
|
||||
if(!_icon || !_icon_state)
|
||||
return FALSE
|
||||
// It has to be made from an image or dir breaks because of a byond bug
|
||||
var/temp_image = image(_icon, null, _icon_state, _layer, _dir)
|
||||
pic = new(temp_image)
|
||||
pic.color = _color
|
||||
pic.alpha = _alpha
|
||||
return TRUE
|
||||
|
||||
/datum/component/decal/proc/apply(atom/thing)
|
||||
var/atom/master = thing || parent
|
||||
master.add_overlay(pic, TRUE)
|
||||
if(isitem(master))
|
||||
addtimer(CALLBACK(master, /obj/item/.proc/update_slot_icon), 0, TIMER_UNIQUE)
|
||||
|
||||
/datum/component/decal/proc/remove(atom/thing)
|
||||
var/atom/master = thing || parent
|
||||
master.cut_overlay(pic, TRUE)
|
||||
if(isitem(master))
|
||||
addtimer(CALLBACK(master, /obj/item/.proc/update_slot_icon), 0, TIMER_UNIQUE)
|
||||
|
||||
/datum/component/decal/proc/rotate_react(datum/source, old_dir, new_dir)
|
||||
if(old_dir == new_dir)
|
||||
return
|
||||
remove()
|
||||
pic.dir = turn(pic.dir, dir2angle(old_dir) - dir2angle(new_dir))
|
||||
apply()
|
||||
|
||||
/datum/component/decal/proc/clean_react(datum/source, strength)
|
||||
if(strength >= cleanable)
|
||||
qdel(src)
|
||||
|
||||
/datum/component/decal/proc/examine(datum/source, mob/user, list/examine_list)
|
||||
examine_list += description
|
||||
@@ -1,13 +0,0 @@
|
||||
/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/proc/get_examine_name(datum/source, mob/user, list/override)
|
||||
var/atom/A = parent
|
||||
|
||||
return COMPONENT_EXNAME_CHANGED
|
||||
@@ -0,0 +1,244 @@
|
||||
/*!
|
||||
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.reaction(eater, INGEST, fraction)
|
||||
owner.reagents.trans_to(eater, bite_consumption)
|
||||
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)
|
||||
@@ -0,0 +1,363 @@
|
||||
/*
|
||||
This component is responsible for handling individual instances of embedded objects. The embeddable element is what allows an item to be embeddable and stores its embedding stats,
|
||||
and when it impacts and meets the requirements to stick into something, it instantiates an embedded component. Once the item falls out, the component is destroyed, while the
|
||||
element survives to embed another day.
|
||||
|
||||
There are 2 different things that can be embedded presently: carbons, and closed turfs (see: walls)
|
||||
|
||||
- Carbon embedding has all the classical embedding behavior, and tracks more events and signals. The main behaviors and hooks to look for are:
|
||||
-- Every process tick, there is a chance to randomly proc pain, controlled by pain_chance. There may also be a chance for the object to fall out randomly, per fall_chance
|
||||
-- Every time the mob moves, there is a chance to proc jostling pain, controlled by jostle_chance (and only 50% as likely if the mob is walking or crawling)
|
||||
-- Various signals hooking into carbon topic() and the embed removal surgery in order to handle removals.
|
||||
|
||||
- Turf embedding is much simpler. All we do here is draw an overlay of the item's inhand on the turf, hide the item, and create an HTML link in the turf's inspect
|
||||
that allows you to rip the item out. There's nothing dynamic about this, so far less checks.
|
||||
|
||||
|
||||
In addition, there are 2 cases of embedding: embedding, and sticking
|
||||
|
||||
- Embedding involves harmful and dangerous embeds, whether they cause brute damage, stamina damage, or a mix. This is the default behavior for embeddings, for when something is "pointy"
|
||||
|
||||
- Sticking occurs when an item should not cause any harm while embedding (imagine throwing a sticky ball of tape at someone, rather than a shuriken). An item is considered "sticky"
|
||||
when it has 0 for both pain multiplier and jostle pain multiplier. It's a bit arbitrary, but fairly straightforward.
|
||||
|
||||
Stickables differ from embeds in the following ways:
|
||||
-- Text descriptors use phrasing like "X is stuck to Y" rather than "X is embedded in Y"
|
||||
-- There is no slicing sound on impact
|
||||
-- All damage checks and bloodloss are skipped for carbons
|
||||
-- Pointy objects create sparks when embedding into a turf
|
||||
|
||||
*/
|
||||
|
||||
|
||||
/datum/component/embedded
|
||||
dupe_mode = COMPONENT_DUPE_ALLOWED
|
||||
var/obj/item/bodypart/limb
|
||||
var/obj/item/weapon
|
||||
|
||||
// all of this stuff is explained in _DEFINES/combat.dm
|
||||
var/embed_chance // not like we really need it once we're already stuck in but hey
|
||||
var/fall_chance
|
||||
var/pain_chance
|
||||
var/pain_mult
|
||||
var/impact_pain_mult
|
||||
var/remove_pain_mult
|
||||
var/rip_time
|
||||
var/ignore_throwspeed_threshold
|
||||
var/jostle_chance
|
||||
var/jostle_pain_mult
|
||||
var/pain_stam_pct
|
||||
var/embed_chance_turf_mod
|
||||
|
||||
///if both our pain multiplier and jostle pain multiplier are 0, we're harmless and can omit most of the damage related stuff
|
||||
var/harmful
|
||||
var/mutable_appearance/overlay
|
||||
|
||||
/datum/component/embedded/Initialize(obj/item/I,
|
||||
datum/thrownthing/throwingdatum,
|
||||
obj/item/bodypart/part,
|
||||
embed_chance = EMBED_CHANCE,
|
||||
fall_chance = EMBEDDED_ITEM_FALLOUT,
|
||||
pain_chance = EMBEDDED_PAIN_CHANCE,
|
||||
pain_mult = EMBEDDED_PAIN_MULTIPLIER,
|
||||
remove_pain_mult = EMBEDDED_UNSAFE_REMOVAL_PAIN_MULTIPLIER,
|
||||
impact_pain_mult = EMBEDDED_IMPACT_PAIN_MULTIPLIER,
|
||||
rip_time = EMBEDDED_UNSAFE_REMOVAL_TIME,
|
||||
ignore_throwspeed_threshold = FALSE,
|
||||
jostle_chance = EMBEDDED_JOSTLE_CHANCE,
|
||||
jostle_pain_mult = EMBEDDED_JOSTLE_PAIN_MULTIPLIER,
|
||||
pain_stam_pct = EMBEDDED_PAIN_STAM_PCT,
|
||||
embed_chance_turf_mod = EMBED_CHANCE_TURF_MOD)
|
||||
|
||||
if((!iscarbon(parent) && !isclosedturf(parent)) || !isitem(I))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
if(part)
|
||||
limb = part
|
||||
src.embed_chance = embed_chance
|
||||
src.fall_chance = fall_chance
|
||||
src.pain_chance = pain_chance
|
||||
src.pain_mult = pain_mult
|
||||
src.remove_pain_mult = remove_pain_mult
|
||||
src.rip_time = rip_time
|
||||
src.impact_pain_mult = impact_pain_mult
|
||||
src.ignore_throwspeed_threshold = ignore_throwspeed_threshold
|
||||
src.jostle_chance = jostle_chance
|
||||
src.jostle_pain_mult = jostle_pain_mult
|
||||
src.pain_stam_pct = pain_stam_pct
|
||||
src.embed_chance_turf_mod = embed_chance_turf_mod
|
||||
|
||||
src.weapon = I
|
||||
|
||||
if(!weapon.isEmbedHarmless())
|
||||
harmful = TRUE
|
||||
|
||||
weapon.embedded(parent)
|
||||
|
||||
if(iscarbon(parent))
|
||||
initCarbon()
|
||||
else if(isclosedturf(parent))
|
||||
initTurf(throwingdatum)
|
||||
|
||||
/datum/component/embedded/RegisterWithParent()
|
||||
if(iscarbon(parent))
|
||||
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/jostleCheck)
|
||||
RegisterSignal(parent, COMSIG_CARBON_EMBED_RIP, .proc/ripOutCarbon)
|
||||
RegisterSignal(parent, COMSIG_CARBON_EMBED_REMOVAL, .proc/safeRemoveCarbon)
|
||||
else if(isclosedturf(parent))
|
||||
RegisterSignal(parent, COMSIG_PARENT_EXAMINE, .proc/examineTurf)
|
||||
RegisterSignal(parent, COMSIG_PARENT_QDELETING, .proc/itemMoved)
|
||||
|
||||
/datum/component/embedded/UnregisterFromParent()
|
||||
UnregisterSignal(parent, list(COMSIG_MOVABLE_MOVED, COMSIG_CARBON_EMBED_RIP, COMSIG_CARBON_EMBED_REMOVAL, COMSIG_PARENT_EXAMINE))
|
||||
|
||||
/datum/component/embedded/process()
|
||||
if(iscarbon(parent))
|
||||
processCarbon()
|
||||
|
||||
/datum/component/embedded/Destroy()
|
||||
if(weapon)
|
||||
UnregisterSignal(weapon, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING))
|
||||
if(overlay)
|
||||
var/atom/A = parent
|
||||
A.cut_overlay(overlay, TRUE)
|
||||
qdel(overlay)
|
||||
|
||||
return ..()
|
||||
|
||||
////////////////////////////////////////
|
||||
/////////////HUMAN PROCS////////////////
|
||||
////////////////////////////////////////
|
||||
|
||||
/// Set up an instance of embedding for a carbon. This is basically an extension of Initialize() so not much to say
|
||||
/datum/component/embedded/proc/initCarbon()
|
||||
START_PROCESSING(SSdcs, src)
|
||||
var/mob/living/carbon/victim = parent
|
||||
if(!istype(limb))
|
||||
limb = pick(victim.bodyparts)
|
||||
|
||||
limb.embedded_objects |= weapon // on the inside... on the inside...
|
||||
weapon.forceMove(victim)
|
||||
RegisterSignal(weapon, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING), .proc/byeItemCarbon)
|
||||
|
||||
if(harmful)
|
||||
victim.visible_message("<span class='danger'>[weapon] embeds itself in [victim]'s [limb.name]!</span>",ignored_mobs=victim)
|
||||
to_chat(victim, "<span class='userdanger'>[weapon] embeds itself in your [limb.name]!</span>")
|
||||
victim.throw_alert("embeddedobject", /obj/screen/alert/embeddedobject)
|
||||
playsound(victim,'sound/weapons/bladeslice.ogg', 40)
|
||||
weapon.add_mob_blood(victim)//it embedded itself in you, of course it's bloody!
|
||||
var/damage = weapon.w_class * impact_pain_mult
|
||||
limb.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage)
|
||||
SEND_SIGNAL(victim, COMSIG_ADD_MOOD_EVENT, "embedded", /datum/mood_event/embedded)
|
||||
else
|
||||
victim.visible_message("<span class='danger'>[weapon] sticks itself to [victim]'s [limb.name]!</span>",ignored_mobs=victim)
|
||||
to_chat(victim, "<span class='userdanger'>[weapon] sticks itself to your [limb.name]!</span>")
|
||||
|
||||
/// Called every time a carbon with a harmful embed moves, rolling a chance for the item to cause pain. The chance is halved if the carbon is crawling or walking.
|
||||
/datum/component/embedded/proc/jostleCheck()
|
||||
var/mob/living/carbon/victim = parent
|
||||
|
||||
var/chance = jostle_chance
|
||||
if(victim.m_intent == MOVE_INTENT_WALK || !(victim.mobility_flags & MOBILITY_STAND))
|
||||
chance *= 0.5
|
||||
|
||||
if(harmful && prob(chance))
|
||||
var/damage = weapon.w_class * jostle_pain_mult
|
||||
limb.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage)
|
||||
to_chat(victim, "<span class='userdanger'>[weapon] embedded in your [limb.name] jostles and stings!</span>")
|
||||
|
||||
|
||||
/// Called when then item randomly falls out of a carbon. This handles the damage and descriptors, then calls safe_remove()
|
||||
/datum/component/embedded/proc/fallOutCarbon()
|
||||
var/mob/living/carbon/victim = parent
|
||||
|
||||
if(harmful)
|
||||
var/damage = weapon.w_class * remove_pain_mult
|
||||
limb.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage)
|
||||
victim.visible_message("<span class='danger'>[weapon] falls out of [victim.name]'s [limb.name]!</span>", ignored_mobs=victim)
|
||||
to_chat(victim, "<span class='userdanger'>[weapon] falls out of your [limb.name]!</span>")
|
||||
else
|
||||
victim.visible_message("<span class='danger'>[weapon] falls off of [victim.name]'s [limb.name]!</span>", ignored_mobs=victim)
|
||||
to_chat(victim, "<span class='userdanger'>[weapon] falls off of your [limb.name]!</span>")
|
||||
|
||||
safeRemoveCarbon()
|
||||
|
||||
|
||||
/// Called when a carbon with an object embedded/stuck to them inspects themselves and clicks the appropriate link to begin ripping the item out. This handles the ripping attempt, descriptors, and dealing damage, then calls safe_remove()
|
||||
/datum/component/embedded/proc/ripOutCarbon(datum/source, obj/item/I, obj/item/bodypart/limb)
|
||||
if(I != weapon || src.limb != limb)
|
||||
return
|
||||
|
||||
var/mob/living/carbon/victim = parent
|
||||
var/time_taken = rip_time * weapon.w_class
|
||||
|
||||
victim.visible_message("<span class='warning'>[victim] attempts to remove [weapon] from [victim.p_their()] [limb.name].</span>","<span class='notice'>You attempt to remove [weapon] from your [limb.name]... (It will take [DisplayTimeText(time_taken)].)</span>")
|
||||
if(do_after(victim, time_taken, target = victim))
|
||||
if(!weapon || !limb || weapon.loc != victim || !(weapon in limb.embedded_objects))
|
||||
qdel(src)
|
||||
return
|
||||
|
||||
if(harmful)
|
||||
var/damage = weapon.w_class * remove_pain_mult
|
||||
limb.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage) //It hurts to rip it out, get surgery you dingus.
|
||||
victim.emote("scream")
|
||||
victim.visible_message("<span class='notice'>[victim] successfully rips [weapon] out of [victim.p_their()] [limb.name]!</span>", "<span class='notice'>You successfully remove [weapon] from your [limb.name].</span>")
|
||||
else
|
||||
victim.visible_message("<span class='notice'>[victim] successfully rips [weapon] off of [victim.p_their()] [limb.name]!</span>", "<span class='notice'>You successfully remove [weapon] from your [limb.name].</span>")
|
||||
|
||||
safeRemoveCarbon(TRUE)
|
||||
|
||||
|
||||
/// This proc handles the final step and actual removal of an embedded/stuck item from a carbon, whether or not it was actually removed safely.
|
||||
/// Pass TRUE for to_hands if we want it to go to the victim's hands when they pull it out
|
||||
/datum/component/embedded/proc/safeRemoveCarbon(to_hands)
|
||||
var/mob/living/carbon/victim = parent
|
||||
limb.embedded_objects -= weapon
|
||||
|
||||
UnregisterSignal(weapon, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING)) // have to unhook these here so they don't also register as having disappeared
|
||||
|
||||
if(!weapon)
|
||||
if(!victim.has_embedded_objects())
|
||||
victim.clear_alert("embeddedobject")
|
||||
SEND_SIGNAL(victim, COMSIG_CLEAR_MOOD_EVENT, "embedded")
|
||||
qdel(src)
|
||||
return
|
||||
|
||||
if(weapon.unembedded()) // if it deleted itself
|
||||
weapon = null
|
||||
if(!victim.has_embedded_objects())
|
||||
victim.clear_alert("embeddedobject")
|
||||
SEND_SIGNAL(victim, COMSIG_CLEAR_MOOD_EVENT, "embedded")
|
||||
qdel(src)
|
||||
return
|
||||
|
||||
if(to_hands)
|
||||
victim.put_in_hands(weapon)
|
||||
else
|
||||
weapon.forceMove(get_turf(victim))
|
||||
|
||||
if(!victim.has_embedded_objects())
|
||||
victim.clear_alert("embeddedobject")
|
||||
SEND_SIGNAL(victim, COMSIG_CLEAR_MOOD_EVENT, "embedded")
|
||||
qdel(src)
|
||||
|
||||
|
||||
/// Something deleted or moved our weapon while it was embedded, how rude!
|
||||
/datum/component/embedded/proc/byeItemCarbon()
|
||||
var/mob/living/carbon/victim = parent
|
||||
limb.embedded_objects -= weapon
|
||||
UnregisterSignal(weapon, list(COMSIG_MOVABLE_MOVED, COMSIG_PARENT_QDELETING))
|
||||
|
||||
if(victim)
|
||||
to_chat(victim, "<span class='userdanger'>\The [weapon] that was embedded in your [limb.name] disappears!</span>")
|
||||
if(!victim.has_embedded_objects())
|
||||
victim.clear_alert("embeddedobject")
|
||||
SEND_SIGNAL(victim, COMSIG_CLEAR_MOOD_EVENT, "embedded")
|
||||
weapon = null
|
||||
qdel(src)
|
||||
|
||||
|
||||
/// Items embedded/stuck to carbons both check whether they randomly fall out (if applicable), as well as if the target mob and limb still exists.
|
||||
/// Items harmfully embedded in carbons have an additional check for random pain (if applicable)
|
||||
/datum/component/embedded/proc/processCarbon()
|
||||
var/mob/living/carbon/victim = parent
|
||||
|
||||
if(!victim || !limb) // in case the victim and/or their limbs exploded (say, due to a sticky bomb)
|
||||
weapon.forceMove(get_turf(weapon))
|
||||
qdel(src)
|
||||
|
||||
if(victim.stat == DEAD)
|
||||
return
|
||||
|
||||
var/damage = weapon.w_class * pain_mult
|
||||
var/chance = pain_chance
|
||||
if(pain_stam_pct && IS_STAMCRIT(victim)) //if it's a less-lethal embed, give them a break if they're already stamcritted
|
||||
chance *= 0.3
|
||||
damage *= 0.7
|
||||
|
||||
if(harmful && prob(chance))
|
||||
limb.receive_damage(brute=(1-pain_stam_pct) * damage, stamina=pain_stam_pct * damage)
|
||||
to_chat(victim, "<span class='userdanger'>[weapon] embedded in your [limb.name] hurts!</span>")
|
||||
|
||||
if(prob(fall_chance))
|
||||
fallOutCarbon()
|
||||
|
||||
|
||||
|
||||
////////////////////////////////////////
|
||||
//////////////TURF PROCS////////////////
|
||||
////////////////////////////////////////
|
||||
|
||||
/// Turfs are much lower maintenance, since we don't care if they're in pain, but since they don't bleed or scream, we draw an overlay to show their status.
|
||||
/// The only difference pointy/sticky items make here is text descriptors and pointy objects making a spark shower on impact.
|
||||
/datum/component/embedded/proc/initTurf(datum/thrownthing/throwingdatum)
|
||||
var/turf/closed/hit = parent
|
||||
|
||||
// we can't store the item IN the turf (cause turfs are just kinda... there), so we fake it by making the item invisible and bailing if it moves due to a blast
|
||||
weapon.forceMove(hit)
|
||||
weapon.invisibility = INVISIBILITY_ABSTRACT
|
||||
RegisterSignal(weapon, COMSIG_MOVABLE_MOVED, .proc/itemMoved)
|
||||
|
||||
var/pixelX = rand(-2, 2)
|
||||
var/pixelY = rand(-1, 3) // bias this upwards since in-hands are usually on the lower end of the sprite
|
||||
|
||||
switch(throwingdatum.init_dir)
|
||||
if(NORTH)
|
||||
pixelY -= 2
|
||||
if(SOUTH)
|
||||
pixelY += 2
|
||||
if(WEST)
|
||||
pixelX += 2
|
||||
if(EAST)
|
||||
pixelX -= 2
|
||||
|
||||
if(throwingdatum.init_dir in list(NORTH, WEST, NORTHWEST, SOUTHWEST))
|
||||
overlay = mutable_appearance(icon=weapon.righthand_file,icon_state=weapon.item_state)
|
||||
else
|
||||
overlay = mutable_appearance(icon=weapon.lefthand_file,icon_state=weapon.item_state)
|
||||
|
||||
var/matrix/M = matrix()
|
||||
M.Translate(pixelX, pixelY)
|
||||
overlay.transform = M
|
||||
hit.add_overlay(overlay, TRUE)
|
||||
|
||||
if(harmful)
|
||||
hit.visible_message("<span class='danger'>[weapon] embeds itself in [hit]!</span>")
|
||||
playsound(hit,'sound/weapons/bladeslice.ogg', 70)
|
||||
|
||||
var/datum/effect_system/spark_spread/sparks = new
|
||||
sparks.set_up(1, 1, parent)
|
||||
sparks.attach(parent)
|
||||
sparks.start()
|
||||
else
|
||||
hit.visible_message("<span class='danger'>[weapon] sticks itself to [hit]!</span>")
|
||||
|
||||
|
||||
/datum/component/embedded/proc/examineTurf(datum/source, mob/user, list/examine_list)
|
||||
if(harmful)
|
||||
examine_list += "\t <a href='?src=[REF(src)];embedded_object=[REF(weapon)]' class='warning'>There is \a [weapon] embedded in [parent]!</a>"
|
||||
else
|
||||
examine_list += "\t <a href='?src=[REF(src)];embedded_object=[REF(weapon)]' class='warning'>There is \a [weapon] stuck to [parent]!</a>"
|
||||
|
||||
|
||||
/// Someone is ripping out the item from the turf by hand
|
||||
/datum/component/embedded/Topic(datum/source, href_list)
|
||||
var/mob/living/us = usr
|
||||
if(in_range(us, parent) && locate(href_list["embedded_object"]) == weapon)
|
||||
if(harmful)
|
||||
us.visible_message("<span class='notice'>[us] begins unwedging [weapon] from [parent].</span>", "<span class='notice'>You begin unwedging [weapon] from [parent]...</span>")
|
||||
else
|
||||
us.visible_message("<span class='notice'>[us] begins unsticking [weapon] from [parent].</span>", "<span class='notice'>You begin unsticking [weapon] from [parent]...</span>")
|
||||
|
||||
if(do_after(us, 30, target = parent))
|
||||
us.put_in_hands(weapon)
|
||||
weapon.unembedded()
|
||||
qdel(src)
|
||||
|
||||
|
||||
/// This proc handles if something knocked the invisible item loose from the turf somehow (probably an explosion). Just make it visible and say it fell loose, then get outta here.
|
||||
/datum/component/embedded/proc/itemMoved()
|
||||
weapon.invisibility = initial(weapon.invisibility)
|
||||
weapon.visible_message("<span class='notice'>[weapon] falls loose from [parent].</span>")
|
||||
weapon.unembedded()
|
||||
qdel(src)
|
||||
@@ -1,11 +0,0 @@
|
||||
/datum/component/empprotection
|
||||
var/flags = NONE
|
||||
|
||||
/datum/component/empprotection/Initialize(_flags)
|
||||
if(!istype(parent, /atom))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
flags = _flags
|
||||
RegisterSignal(parent, list(COMSIG_ATOM_EMP_ACT), .proc/getEmpFlags)
|
||||
|
||||
/datum/component/empprotection/proc/getEmpFlags(datum/source, severity)
|
||||
return flags
|
||||
@@ -9,6 +9,7 @@
|
||||
var/originalName
|
||||
var/list/affixes
|
||||
var/list/appliedComponents
|
||||
var/list/appliedElements
|
||||
|
||||
var/static/list/affixListing
|
||||
|
||||
@@ -22,6 +23,7 @@
|
||||
|
||||
src.affixes = affixes
|
||||
appliedComponents = list()
|
||||
appliedElements = list()
|
||||
randomAffixes()
|
||||
|
||||
/datum/component/fantasy/Destroy()
|
||||
@@ -118,6 +120,8 @@
|
||||
affix.remove(src)
|
||||
for(var/i in appliedComponents)
|
||||
qdel(i)
|
||||
for(var/i in appliedElements)
|
||||
master._RemoveElement(i)
|
||||
|
||||
master.force = max(0, master.force - quality)
|
||||
master.throwforce = max(0, master.throwforce - quality)
|
||||
|
||||
@@ -45,7 +45,8 @@
|
||||
|
||||
/datum/fantasy_affix/tactical/apply(datum/component/fantasy/comp, newName)
|
||||
var/obj/item/master = comp.parent
|
||||
comp.appliedComponents += master.AddComponent(/datum/component/tactical)
|
||||
master.AddElement(/datum/element/tactical)
|
||||
comp.appliedElements += list(/datum/element/tactical)
|
||||
return "tactical [newName]"
|
||||
|
||||
/datum/fantasy_affix/pyromantic
|
||||
|
||||
@@ -144,7 +144,7 @@
|
||||
var/obj/item/projectile/picked_projectiletype = pickweight(weighted_projectile_types)
|
||||
|
||||
var/obj/item/master = comp.parent
|
||||
comp.appliedComponents += master.AddComponent(/datum/component/shrapnel, picked_projectiletype)
|
||||
comp.appliedComponents += master.AddComponent(/datum/component/mirv, picked_projectiletype)
|
||||
return "[newName] of [initial(picked_projectiletype.name)] shrapnel"
|
||||
|
||||
/datum/fantasy_affix/strength
|
||||
|
||||
@@ -0,0 +1,354 @@
|
||||
#define CENTERED_RENDER_SOURCE(img, atom, FoV) \
|
||||
atom.render_target = atom.render_target || ref(atom);\
|
||||
img.render_source = atom.render_target;\
|
||||
if(atom.icon){\
|
||||
var/_cached_sizes = FoV.width_n_height_offsets[atom.icon];\
|
||||
if(!_cached_sizes){\
|
||||
var/icon/_I = icon(atom.icon);\
|
||||
var/list/L = list();\
|
||||
L += (_I.Width() - world.icon_size)/2;\
|
||||
L += (_I.Height() - world.icon_size)/2;\
|
||||
_cached_sizes = FoV.width_n_height_offsets[atom.icon] = L\
|
||||
}\
|
||||
img.pixel_x = _cached_sizes[1];\
|
||||
img.pixel_y = _cached_sizes[2];\
|
||||
img.loc = atom\
|
||||
}
|
||||
|
||||
#define REGISTER_NESTED_LOCS(source, list, comsig, proc) \
|
||||
for(var/k in get_nested_locs(source)){\
|
||||
var/atom/_A = k;\
|
||||
RegisterSignal(_A, comsig, proc);\
|
||||
list += _A\
|
||||
}
|
||||
|
||||
#define UNREGISTER_NESTED_LOCS(list, comsig, index) \
|
||||
for(var/k in index to length(list)){\
|
||||
var/atom/_A = list[k];\
|
||||
UnregisterSignal(_A, comsig);\
|
||||
list -= _A\
|
||||
}
|
||||
|
||||
/**
|
||||
* Field of Vision component. Does totally what you probably think it does,
|
||||
* ergo preventing players from seeing what's behind them.
|
||||
*/
|
||||
/datum/component/field_of_vision
|
||||
can_transfer = TRUE
|
||||
|
||||
/**
|
||||
* That special invisible, almost neigh indestructible movable
|
||||
* that holds both shadow cone mask and image and follows the player around.
|
||||
*/
|
||||
var/atom/movable/fov_holder/fov
|
||||
///The current screen size this field of vision is meant to fit for.
|
||||
var/current_fov_size = list(15, 15)
|
||||
///How much is the cone rotated clockwise, purely backend. Please use rotate_shadow_cone() if you must.
|
||||
var/angle = 0
|
||||
/// Used to scale the shadow cone when rotating it to fit over the edges of the screen.
|
||||
var/rot_scale = 1
|
||||
/// The inner angle of this cone, right hardset to 90, 180, or 270 degrees, until someone figures out a way to make it dynamic.
|
||||
var/shadow_angle = FOV_90_DEGREES
|
||||
/// The mask portion of the cone, placed on a * render target plane so while not visible it still applies the filter.
|
||||
var/image/shadow_mask
|
||||
/// The visual portion of the cone, placed on the highest layer of the wall plane
|
||||
var/image/visual_shadow
|
||||
/**
|
||||
* An image whose render_source is kept up to date to prevent the mob (or the topmost movable holding it) from being hidden by the mask.
|
||||
* Will make it use vis_contents instead once a few byonds bugs with images and vis contents are fixed.
|
||||
*/
|
||||
var/image/owner_mask
|
||||
/**
|
||||
* A circle image used to somewhat uncover the adjacent portion of the shadow cone, making mobs and objects behind us somewhat visible.
|
||||
* The owner mask is still required for those mob going over the default 32x32 px size btw.
|
||||
*/
|
||||
var/image/adj_mask
|
||||
/// A list of nested locations the mob is in, to ensure the above image works correctly.
|
||||
var/list/nested_locs = list()
|
||||
/**
|
||||
* A static list of offsets based on icon width and height, because render sources are centered unlike most other visuals,
|
||||
* and that gives us some problems when the icon is larger or smaller than world.icon_size
|
||||
*/
|
||||
var/static/list/width_n_height_offsets = list()
|
||||
|
||||
/datum/component/field_of_vision/Initialize(fov_type = FOV_90_DEGREES, _angle = 0)
|
||||
if(!ismob(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
angle = _angle
|
||||
shadow_angle = fov_type
|
||||
|
||||
/datum/component/field_of_vision/RegisterWithParent()
|
||||
. = ..()
|
||||
var/mob/M = parent
|
||||
if(M.client)
|
||||
generate_fov_holder(M, angle)
|
||||
RegisterSignal(M, COMSIG_MOB_CLIENT_LOGIN, .proc/on_mob_login)
|
||||
RegisterSignal(M, COMSIG_MOB_CLIENT_LOGOUT, .proc/on_mob_logout)
|
||||
RegisterSignal(M, COMSIG_MOB_GET_VISIBLE_MESSAGE, .proc/on_visible_message)
|
||||
RegisterSignal(M, COMSIG_MOB_EXAMINATE, .proc/on_examinate)
|
||||
RegisterSignal(M, COMSIG_MOB_FOV_VIEW, .proc/on_fov_view)
|
||||
RegisterSignal(M, COMSIG_MOB_CLIENT_CHANGE_VIEW, .proc/on_change_view)
|
||||
RegisterSignal(M, COMSIG_MOB_RESET_PERSPECTIVE, .proc/on_reset_perspective)
|
||||
RegisterSignal(M, COMSIG_MOB_FOV_VIEWER, .proc/is_viewer)
|
||||
|
||||
/datum/component/field_of_vision/UnregisterFromParent()
|
||||
. = ..()
|
||||
var/mob/M = parent
|
||||
if(!QDELETED(fov))
|
||||
if(M.client)
|
||||
UnregisterSignal(M, list(COMSIG_ATOM_DIR_CHANGE, COMSIG_MOVABLE_MOVED, COMSIG_MOB_DEATH, COMSIG_LIVING_REVIVE))
|
||||
M.client.images -= owner_mask
|
||||
M.client.images -= shadow_mask
|
||||
M.client.images -= visual_shadow
|
||||
M.client.images -= adj_mask
|
||||
qdel(fov, TRUE) // Forced.
|
||||
fov = null
|
||||
QDEL_NULL(owner_mask)
|
||||
QDEL_NULL(adj_mask)
|
||||
if(length(nested_locs))
|
||||
UNREGISTER_NESTED_LOCS(nested_locs, COMSIG_MOVABLE_MOVED, 1)
|
||||
UnregisterSignal(M, list(COMSIG_MOB_CLIENT_LOGIN, COMSIG_MOB_CLIENT_LOGOUT,
|
||||
COMSIG_MOB_GET_VISIBLE_MESSAGE, COMSIG_MOB_EXAMINATE,
|
||||
COMSIG_MOB_FOV_VIEW, COMSIG_MOB_RESET_PERSPECTIVE,
|
||||
COMSIG_MOB_CLIENT_CHANGE_VIEW, COMSIG_MOB_FOV_VIEWER))
|
||||
|
||||
/**
|
||||
* Generates the holder and images (if not generated yet) and adds them to client.images.
|
||||
* Run when the component is registered to a player mob, or upon login.
|
||||
*/
|
||||
/datum/component/field_of_vision/proc/generate_fov_holder(mob/M, _angle = 0)
|
||||
if(QDELETED(fov))
|
||||
fov = new(get_turf(M))
|
||||
fov.icon_state = "[shadow_angle]"
|
||||
fov.dir = M.dir
|
||||
shadow_mask = image('icons/misc/field_of_vision.dmi', fov, "[shadow_angle]", FIELD_OF_VISION_LAYER)
|
||||
shadow_mask.plane = FIELD_OF_VISION_PLANE
|
||||
visual_shadow = image('icons/misc/field_of_vision.dmi', fov, "[shadow_angle]_v", FIELD_OF_VISION_LAYER)
|
||||
visual_shadow.plane = FIELD_OF_VISION_VISUAL_PLANE
|
||||
owner_mask = new
|
||||
owner_mask.appearance_flags = RESET_TRANSFORM
|
||||
owner_mask.plane = FIELD_OF_VISION_BLOCKER_PLANE
|
||||
adj_mask = image('icons/misc/field_of_vision.dmi', fov, "adj_mask", FIELD_OF_VISION_LAYER)
|
||||
adj_mask.appearance_flags = RESET_TRANSFORM
|
||||
adj_mask.plane = FIELD_OF_VISION_BLOCKER_PLANE
|
||||
if(_angle)
|
||||
rotate_shadow_cone(_angle)
|
||||
fov.alpha = M.stat == DEAD ? 0 : 255
|
||||
RegisterSignal(M, COMSIG_MOB_DEATH, .proc/hide_fov)
|
||||
RegisterSignal(M, COMSIG_LIVING_REVIVE, .proc/show_fov)
|
||||
RegisterSignal(M, COMSIG_ATOM_DIR_CHANGE, .proc/on_dir_change)
|
||||
RegisterSignal(M, COMSIG_MOVABLE_MOVED, .proc/on_mob_moved)
|
||||
RegisterSignal(M, COMSIG_ROBOT_UPDATE_ICONS, .proc/manual_centered_render_source)
|
||||
var/atom/A = M
|
||||
if(M.loc && !isturf(M.loc))
|
||||
REGISTER_NESTED_LOCS(M, nested_locs, COMSIG_MOVABLE_MOVED, .proc/on_loc_moved)
|
||||
A = nested_locs[nested_locs.len]
|
||||
CENTERED_RENDER_SOURCE(owner_mask, A, src)
|
||||
M.client.images += shadow_mask
|
||||
M.client.images += visual_shadow
|
||||
M.client.images += owner_mask
|
||||
M.client.images += adj_mask
|
||||
if(M.client.view != "[current_fov_size[1]]x[current_fov_size[2]]")
|
||||
resize_fov(current_fov_size, getviewsize(M.client.view))
|
||||
|
||||
///Rotates the shadow cone to a certain degree. Backend shenanigans.
|
||||
/datum/component/field_of_vision/proc/rotate_shadow_cone(new_angle)
|
||||
var/simple_degrees = SIMPLIFY_DEGREES(new_angle - angle)
|
||||
var/to_scale = cos(simple_degrees) * sin(simple_degrees)
|
||||
if(to_scale)
|
||||
var/old_rot_scale = rot_scale
|
||||
rot_scale = 1 + to_scale
|
||||
if(old_rot_scale != rot_scale)
|
||||
visual_shadow.transform = shadow_mask.transform = shadow_mask.transform.Scale(rot_scale/old_rot_scale)
|
||||
visual_shadow.transform = shadow_mask.transform = shadow_mask.transform.Turn(fov.transform, simple_degrees)
|
||||
|
||||
/**
|
||||
* Resizes the shadow to match the current screen size.
|
||||
* Run when the client view size is changed, or if the player has a viewsize different than "15x15" on login/comp registration.
|
||||
*/
|
||||
/datum/component/field_of_vision/proc/resize_fov(list/old_view, list/view)
|
||||
current_fov_size = view
|
||||
var/old_size = max(old_view[1], old_view[2])
|
||||
var/new_size = max(view[1], view[2])
|
||||
if(old_size == new_size) //longest edges are still of the same length.
|
||||
return
|
||||
visual_shadow.transform = shadow_mask.transform = shadow_mask.transform.Scale(new_size/old_size)
|
||||
|
||||
/datum/component/field_of_vision/proc/on_mob_login(mob/source, client/client)
|
||||
generate_fov_holder(source, angle)
|
||||
|
||||
/datum/component/field_of_vision/proc/on_mob_logout(mob/source, client/client)
|
||||
UnregisterSignal(source, list(COMSIG_ATOM_DIR_CHANGE, COMSIG_MOVABLE_MOVED, COMSIG_MOB_DEATH,
|
||||
COMSIG_LIVING_REVIVE, COMSIG_ROBOT_UPDATE_ICONS))
|
||||
if(length(nested_locs))
|
||||
UNREGISTER_NESTED_LOCS(nested_locs, COMSIG_MOVABLE_MOVED, 1)
|
||||
|
||||
/datum/component/field_of_vision/proc/on_dir_change(mob/source, old_dir, new_dir)
|
||||
fov.dir = new_dir
|
||||
|
||||
///Hides the shadow, other visibility comsig procs will take it into account. Called when the mob dies.
|
||||
/datum/component/field_of_vision/proc/hide_fov(mob/source)
|
||||
fov.alpha = 0
|
||||
|
||||
/// Shows the shadow. Called when the mob is revived.
|
||||
/datum/component/field_of_vision/proc/show_fov(mob/source)
|
||||
fov.alpha = 255
|
||||
|
||||
/// Hides the shadow when looking through other items, shows it otherwise.
|
||||
/datum/component/field_of_vision/proc/on_reset_perspective(mob/source, atom/target)
|
||||
if(source.client.eye == source || source.client.eye == source.loc)
|
||||
fov.alpha = 255
|
||||
else
|
||||
fov.alpha = 0
|
||||
|
||||
/// Called when the client view size is changed.
|
||||
/datum/component/field_of_vision/proc/on_change_view(mob/source, client, list/old_view, list/view)
|
||||
resize_fov(old_view, view)
|
||||
|
||||
/**
|
||||
* Called when the owner mob moves around. Used to keep shadow located right behind us,
|
||||
* As well as modify the owner mask to match the topmost item.
|
||||
*/
|
||||
/datum/component/field_of_vision/proc/on_mob_moved(mob/source, atom/oldloc, dir, forced)
|
||||
var/turf/T
|
||||
if(!isturf(source.loc)) //Recalculate all nested locations.
|
||||
UNREGISTER_NESTED_LOCS( nested_locs, COMSIG_MOVABLE_MOVED, 1)
|
||||
REGISTER_NESTED_LOCS(source, nested_locs, COMSIG_MOVABLE_MOVED, .proc/on_loc_moved)
|
||||
var/atom/movable/topmost = nested_locs[nested_locs.len]
|
||||
T = topmost.loc
|
||||
CENTERED_RENDER_SOURCE(owner_mask, topmost, src)
|
||||
else
|
||||
T = source.loc
|
||||
if(length(nested_locs))
|
||||
UNREGISTER_NESTED_LOCS(nested_locs, COMSIG_MOVABLE_MOVED, 1)
|
||||
CENTERED_RENDER_SOURCE(owner_mask, source, src)
|
||||
if(T)
|
||||
fov.forceMove(T, harderforce = TRUE)
|
||||
|
||||
/// Pretty much like the above, but meant for other movables the mob is stored in (bodybags, boxes, mechs etc).
|
||||
/datum/component/field_of_vision/proc/on_loc_moved(atom/movable/source, atom/oldloc, dir, forced)
|
||||
if(isturf(source.loc) && isturf(oldloc)) //This is the case of the topmost movable loc moving around the world, skip.
|
||||
fov.forceMove(source.loc, harderforce = TRUE)
|
||||
return
|
||||
var/atom/movable/prev_topmost = nested_locs[nested_locs.len]
|
||||
if(prev_topmost != source)
|
||||
UNREGISTER_NESTED_LOCS(nested_locs, COMSIG_MOVABLE_MOVED, nested_locs.Find(source) + 1)
|
||||
REGISTER_NESTED_LOCS(source, nested_locs, COMSIG_MOVABLE_MOVED, .proc/on_loc_moved)
|
||||
var/atom/movable/topmost = nested_locs[nested_locs.len]
|
||||
if(topmost != prev_topmost)
|
||||
CENTERED_RENDER_SOURCE(owner_mask, topmost, src)
|
||||
if(topmost.loc)
|
||||
fov.forceMove(topmost.loc, harderforce = TRUE)
|
||||
|
||||
/// A hacky comsig proc for things that somehow decide to change icon on the go. may make a change_icon_file() proc later but...
|
||||
/datum/component/field_of_vision/proc/manual_centered_render_source(mob/source, old_icon)
|
||||
if(!isturf(source.loc))
|
||||
return
|
||||
CENTERED_RENDER_SOURCE(owner_mask, source, src)
|
||||
|
||||
#undef CENTERED_RENDER_SOURCE
|
||||
#undef REGISTER_NESTED_LOCS
|
||||
#undef UNREGISTER_NESTED_LOCS
|
||||
|
||||
/**
|
||||
* Byond doc is not entirely correct on the integrated arctan() proc.
|
||||
* When both x and y are negative, the output is also negative, cycling clockwise instead of counter-clockwise.
|
||||
* That's also why I am extensively using the SIMPLIFY_DEGREES macro here.
|
||||
*
|
||||
* Overall this is the main macro that calculates wheter a target is within the shadow cone angle or not.
|
||||
*/
|
||||
#define FOV_ANGLE_CHECK(mob, target, zero_x_y_statement, success_statement) \
|
||||
var/turf/T1 = get_turf(target);\
|
||||
var/turf/T2 = get_turf(mob);\
|
||||
if(!T1 || !T2){\
|
||||
zero_x_y_statement\
|
||||
}\
|
||||
var/_x = (T1.x - T2.x);\
|
||||
var/_y = (T1.y - T2.y);\
|
||||
if(ISINRANGE(_x, -1, 1) && ISINRANGE(_y, -1, 1)){\
|
||||
zero_x_y_statement\
|
||||
}\
|
||||
var/dir = (mob.dir & (EAST|WEST)) || mob.dir;\
|
||||
var/_degree = -angle;\
|
||||
var/_half = shadow_angle/2;\
|
||||
switch(dir){\
|
||||
if(EAST){\
|
||||
_degree += 180;\
|
||||
}\
|
||||
if(NORTH){\
|
||||
_degree += 270;\
|
||||
}\
|
||||
if(SOUTH){\
|
||||
_degree += 90;\
|
||||
}\
|
||||
}\
|
||||
var/_min = SIMPLIFY_DEGREES(_degree - _half);\
|
||||
var/_max = SIMPLIFY_DEGREES(_degree + _half);\
|
||||
if((_min > _max) ? !ISINRANGE(SIMPLIFY_DEGREES(arctan(_x, _y)), _max, _min) : ISINRANGE(SIMPLIFY_DEGREES(arctan(_x, _y)), _min, _max)){\
|
||||
success_statement;\
|
||||
}
|
||||
|
||||
/datum/component/field_of_vision/proc/on_examinate(mob/source, atom/target)
|
||||
if(fov.alpha)
|
||||
FOV_ANGLE_CHECK(source, target, return, return COMPONENT_DENY_EXAMINATE|COMPONENT_EXAMINATE_BLIND)
|
||||
|
||||
/datum/component/field_of_vision/proc/on_visible_message(mob/source, atom/target, message, range, list/ignored_mobs)
|
||||
if(fov.alpha)
|
||||
FOV_ANGLE_CHECK(source, target, return, return COMPONENT_NO_VISIBLE_MESSAGE)
|
||||
|
||||
/datum/component/field_of_vision/proc/on_fov_view(mob/source, list/atoms)
|
||||
if(!fov.alpha)
|
||||
return
|
||||
for(var/k in atoms)
|
||||
var/atom/A = k
|
||||
FOV_ANGLE_CHECK(source, A, continue, atoms -= A)
|
||||
|
||||
/datum/component/field_of_vision/proc/is_viewer(mob/source, atom/center, depth, list/viewers_list)
|
||||
if(fov.alpha)
|
||||
FOV_ANGLE_CHECK(source, center, return, viewers_list -= source)
|
||||
|
||||
#undef FOV_ANGLE_CHECK
|
||||
|
||||
/**
|
||||
* The shadow cone's mask and visual images holder which can't locate inside the mob,
|
||||
* lest they inherit the mob opacity and cause a lot of hindrance
|
||||
*/
|
||||
/atom/movable/fov_holder
|
||||
name = "field of vision holder"
|
||||
pixel_x = -224 //the image is about 480x480 px, ergo 15 tiles (480/32) big, and we gotta center it.
|
||||
pixel_y = -224
|
||||
mouse_opacity = MOUSE_OPACITY_TRANSPARENT
|
||||
plane = FIELD_OF_VISION_PLANE
|
||||
anchored = TRUE
|
||||
|
||||
/atom/movable/fov_holder/ConveyorMove()
|
||||
return
|
||||
|
||||
/atom/movable/fov_holder/has_gravity(turf/T)
|
||||
return FALSE
|
||||
|
||||
/atom/movable/fov_holder/ex_act(severity)
|
||||
return FALSE
|
||||
|
||||
/atom/movable/fov_holder/singularity_act()
|
||||
return
|
||||
|
||||
/atom/movable/fov_holder/singularity_pull()
|
||||
return
|
||||
|
||||
/atom/movable/fov_holder/blob_act()
|
||||
return
|
||||
|
||||
/atom/movable/fov_holder/onTransitZ()
|
||||
return
|
||||
|
||||
/// Prevents people from moving these after creation, because they shouldn't be.
|
||||
/atom/movable/fov_holder/forceMove(atom/destination, no_tp=FALSE, harderforce = FALSE)
|
||||
if(harderforce)
|
||||
return ..()
|
||||
|
||||
/// Last but not least, these shouldn't be deleted by anything but the component itself
|
||||
/atom/movable/fov_holder/Destroy(force = FALSE)
|
||||
if(!force)
|
||||
return QDEL_HINT_LETMELIVE
|
||||
return ..()
|
||||
@@ -1,26 +1,52 @@
|
||||
///Footstep component. Plays footsteps at parents location when it is appropriate.
|
||||
/datum/component/footstep
|
||||
///How many steps the parent has taken since the last time a footstep was played
|
||||
var/steps = 0
|
||||
///volume determines the extra volume of the footstep. This is multiplied by the base volume, should there be one.
|
||||
var/volume
|
||||
///e_range stands for extra range - aka how far the sound can be heard. This is added to the base value and ignored if there isn't a base value.
|
||||
var/e_range
|
||||
///footstep_type is a define which determines what kind of sounds should get chosen.
|
||||
var/footstep_type
|
||||
///This can be a list OR a soundfile OR null. Determines whatever sound gets played.
|
||||
var/footstep_sounds
|
||||
|
||||
/datum/component/footstep/Initialize(volume_ = 0.5, e_range_ = -1)
|
||||
/datum/component/footstep/Initialize(footstep_type_ = FOOTSTEP_MOB_BAREFOOT, volume_ = 0.5, e_range_ = -1)
|
||||
if(!isliving(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
volume = volume_
|
||||
e_range = e_range_
|
||||
RegisterSignal(parent, list(COMSIG_MOVABLE_MOVED), .proc/play_footstep)
|
||||
footstep_type = footstep_type_
|
||||
switch(footstep_type)
|
||||
if(FOOTSTEP_MOB_HUMAN)
|
||||
if(!ishuman(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/play_humanstep)
|
||||
return
|
||||
if(FOOTSTEP_MOB_CLAW)
|
||||
footstep_sounds = GLOB.clawfootstep
|
||||
if(FOOTSTEP_MOB_BAREFOOT)
|
||||
footstep_sounds = GLOB.barefootstep
|
||||
if(FOOTSTEP_MOB_HEAVY)
|
||||
footstep_sounds = GLOB.heavyfootstep
|
||||
if(FOOTSTEP_MOB_SHOE)
|
||||
footstep_sounds = GLOB.footstep
|
||||
if(FOOTSTEP_MOB_SLIME)
|
||||
footstep_sounds = 'sound/effects/footstep/slime1.ogg'
|
||||
if(FOOTSTEP_MOB_CRAWL)
|
||||
footstep_sounds = 'sound/effects/footstep/crawl1.ogg'
|
||||
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/play_simplestep) //Note that this doesn't get called for humans.
|
||||
|
||||
/datum/component/footstep/proc/play_footstep()
|
||||
///Prepares a footstep. Determines if it should get played. Returns the turf it should get played on. Note that it is always a /turf/open
|
||||
/datum/component/footstep/proc/prepare_step()
|
||||
var/turf/open/T = get_turf(parent)
|
||||
if(!istype(T))
|
||||
return
|
||||
|
||||
var/mob/living/LM = parent
|
||||
var/v = volume
|
||||
var/e = e_range
|
||||
if(!T.footstep || LM.buckled || !CHECK_MOBILITY(LM, MOBILITY_STAND) || LM.buckled || LM.throwing || (LM.movement_type & (VENTCRAWLING | FLYING)))
|
||||
if (LM.lying && !LM.buckled && !(!T.footstep || LM.movement_type & (VENTCRAWLING | FLYING))) //play crawling sound if we're lying
|
||||
playsound(T, 'sound/effects/footstep/crawl1.ogg', 15 * v)
|
||||
playsound(T, 'sound/effects/footstep/crawl1.ogg', 15 * volume)
|
||||
return
|
||||
|
||||
if(HAS_TRAIT(LM, TRAIT_SILENT_STEP))
|
||||
@@ -30,79 +56,83 @@
|
||||
var/mob/living/carbon/C = LM
|
||||
if(!C.get_bodypart(BODY_ZONE_L_LEG) && !C.get_bodypart(BODY_ZONE_R_LEG))
|
||||
return
|
||||
if(ishuman(C) && C.m_intent == MOVE_INTENT_WALK)
|
||||
v /= 2
|
||||
e -= 5
|
||||
if(C.m_intent == MOVE_INTENT_WALK)
|
||||
return
|
||||
steps++
|
||||
|
||||
if(steps >= 3)
|
||||
if(steps >= 6)
|
||||
steps = 0
|
||||
|
||||
else
|
||||
if(steps % 2)
|
||||
return
|
||||
|
||||
if(prob(80) && !LM.has_gravity(T)) // don't need to step as often when you hop around
|
||||
if(steps != 0 && !LM.has_gravity(T)) // don't need to step as often when you hop around
|
||||
return
|
||||
return T
|
||||
|
||||
//begin playsound shenanigans//
|
||||
|
||||
//for barefooted non-clawed mobs like monkeys
|
||||
if(isbarefoot(LM))
|
||||
playsound(T, pick(GLOB.barefootstep[T.barefootstep][1]),
|
||||
GLOB.barefootstep[T.barefootstep][2] * v,
|
||||
TRUE,
|
||||
GLOB.barefootstep[T.barefootstep][3] + e)
|
||||
/datum/component/footstep/proc/play_simplestep()
|
||||
var/turf/open/T = prepare_step()
|
||||
if(!T)
|
||||
return
|
||||
|
||||
//for xenomorphs, dogs, and other clawed mobs
|
||||
if(isclawfoot(LM))
|
||||
if(isalienadult(LM)) //xenos are stealthy and get quieter footsteps
|
||||
v /= 3
|
||||
e -= 5
|
||||
|
||||
playsound(T, pick(GLOB.clawfootstep[T.clawfootstep][1]),
|
||||
GLOB.clawfootstep[T.clawfootstep][2] * v,
|
||||
TRUE,
|
||||
GLOB.clawfootstep[T.clawfootstep][3] + e)
|
||||
if(isfile(footstep_sounds) || istext(footstep_sounds))
|
||||
playsound(T, footstep_sounds, volume)
|
||||
return
|
||||
|
||||
//for megafauna and other large and imtimidating mobs such as the bloodminer
|
||||
if(isheavyfoot(LM))
|
||||
playsound(T, pick(GLOB.heavyfootstep[T.heavyfootstep][1]),
|
||||
GLOB.heavyfootstep[T.heavyfootstep][2] * v,
|
||||
TRUE,
|
||||
GLOB.heavyfootstep[T.heavyfootstep][3] + e)
|
||||
var/turf_footstep
|
||||
switch(footstep_type)
|
||||
if(FOOTSTEP_MOB_CLAW)
|
||||
turf_footstep = T.clawfootstep
|
||||
if(FOOTSTEP_MOB_BAREFOOT)
|
||||
turf_footstep = T.barefootstep
|
||||
if(FOOTSTEP_MOB_HEAVY)
|
||||
turf_footstep = T.heavyfootstep
|
||||
if(FOOTSTEP_MOB_SHOE)
|
||||
turf_footstep = T.footstep
|
||||
if(!turf_footstep)
|
||||
return
|
||||
playsound(T, pick(footstep_sounds[turf_footstep][1]), footstep_sounds[turf_footstep][2] * volume, TRUE, footstep_sounds[turf_footstep][3] + e_range)
|
||||
|
||||
//for slimes
|
||||
if(isslime(LM))
|
||||
playsound(T, 'sound/effects/footstep/slime1.ogg', 15 * v)
|
||||
/datum/component/footstep/proc/play_humanstep()
|
||||
var/turf/open/T = prepare_step()
|
||||
if(!T)
|
||||
return
|
||||
|
||||
//for (simple) humanoid mobs (clowns, russians, pirates, etc.)
|
||||
if(isshoefoot(LM))
|
||||
if(!ishuman(LM))
|
||||
playsound(T, pick(GLOB.footstep[T.footstep][1]),
|
||||
GLOB.footstep[T.footstep][2] * v,
|
||||
TRUE,
|
||||
GLOB.footstep[T.footstep][3] + e)
|
||||
return
|
||||
if(ishuman(LM)) //for proper humans, they're special
|
||||
var/mob/living/carbon/human/H = LM
|
||||
var/feetCover = (H.wear_suit && (H.wear_suit.body_parts_covered & FEET)) || (H.w_uniform && (H.w_uniform.body_parts_covered & FEET) || (H.shoes && (H.shoes.body_parts_covered & FEET)))
|
||||
|
||||
if (H.dna.features["taur"] == "Naga" || H.dna.features["taur"] == "Tentacle") //are we a naga or tentacle taur creature
|
||||
playsound(T, 'sound/effects/footstep/crawl1.ogg', 15 * v)
|
||||
var/mob/living/carbon/human/H = parent
|
||||
var/list/L = GLOB.barefootstep
|
||||
var/turf_footstep = T.barefootstep
|
||||
var/special = FALSE
|
||||
if(H.physiology.footstep_type)
|
||||
switch(H.physiology.footstep_type)
|
||||
if(FOOTSTEP_MOB_CLAW)
|
||||
turf_footstep = T.clawfootstep
|
||||
L = GLOB.clawfootstep
|
||||
if(FOOTSTEP_MOB_BAREFOOT)
|
||||
turf_footstep = T.barefootstep
|
||||
L = GLOB.barefootstep
|
||||
if(FOOTSTEP_MOB_HEAVY)
|
||||
turf_footstep = T.heavyfootstep
|
||||
L = GLOB.heavyfootstep
|
||||
if(FOOTSTEP_MOB_SHOE)
|
||||
turf_footstep = T.footstep
|
||||
L = GLOB.footstep
|
||||
if(FOOTSTEP_MOB_SLIME)
|
||||
playsound(T, 'sound/effects/footstep/slime1.ogg', 50 * volume)
|
||||
return
|
||||
if(FOOTSTEP_MOB_CRAWL)
|
||||
playsound(T, 'sound/effects/footstep/crawl1.ogg', 50 * volume)
|
||||
return
|
||||
special = TRUE
|
||||
else
|
||||
var/feetCover = (H.wear_suit && (H.wear_suit.body_parts_covered & FEET)) || (H.w_uniform && (H.w_uniform.body_parts_covered & FEET) || (H.shoes && (H.shoes.body_parts_covered & FEET)))
|
||||
if(feetCover) //are we wearing shoes
|
||||
playsound(T, pick(GLOB.footstep[T.footstep][1]),
|
||||
GLOB.footstep[T.footstep][2] * volume,
|
||||
TRUE,
|
||||
GLOB.footstep[T.footstep][3] + e_range)
|
||||
return
|
||||
|
||||
if(feetCover) //are we wearing shoes
|
||||
playsound(T, pick(GLOB.footstep[T.footstep][1]),
|
||||
GLOB.footstep[T.footstep][2] * v,
|
||||
TRUE,
|
||||
GLOB.footstep[T.footstep][3] + e)
|
||||
|
||||
if(!feetCover) //are we NOT wearing shoes
|
||||
playsound(T, pick(GLOB.barefootstep[T.barefootstep][1]),
|
||||
GLOB.barefootstep[T.barefootstep][2] * v,
|
||||
TRUE,
|
||||
GLOB.barefootstep[T.barefootstep][3] + e)
|
||||
if(!special && H.dna.species.special_step_sounds)
|
||||
playsound(T, pick(H.dna.species.special_step_sounds), 50, TRUE)
|
||||
else
|
||||
playsound(T, pick(L[turf_footstep][1]),
|
||||
L[turf_footstep][2] * volume,
|
||||
TRUE,
|
||||
L[turf_footstep][3] + e_range)
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
/datum/component/forced_gravity
|
||||
var/gravity
|
||||
var/ignore_space = FALSE //If forced gravity should also work on space turfs
|
||||
|
||||
/datum/component/forced_gravity/Initialize(forced_value = 1)
|
||||
if(!isatom(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
RegisterSignal(COMSIG_ATOM_HAS_GRAVITY, .proc/gravity_check)
|
||||
if(isturf(parent))
|
||||
RegisterSignal(COMSIG_TURF_HAS_GRAVITY, .proc/turf_gravity_check)
|
||||
|
||||
gravity = forced_value
|
||||
|
||||
/datum/component/forced_gravity/proc/gravity_check(datum/source, turf/location, list/gravs)
|
||||
if(!ignore_space && isspaceturf(location))
|
||||
return
|
||||
gravs += gravity
|
||||
|
||||
/datum/component/forced_gravity/proc/turf_gravity_check(datum/source, atom/checker, list/gravs)
|
||||
return gravity_check(parent, gravs)
|
||||
@@ -47,14 +47,11 @@
|
||||
if(icon_state)
|
||||
lock_icon_state = icon_state
|
||||
generate_lock_visuals()
|
||||
var/mob/M = parent
|
||||
LAZYOR(M.mousemove_intercept_objects, src)
|
||||
RegisterSignal(parent, COMSIG_MOB_CLIENT_MOUSEMOVE, .proc/onMouseMove)
|
||||
START_PROCESSING(SSfastprocess, src)
|
||||
|
||||
/datum/component/lockon_aiming/Destroy()
|
||||
var/mob/M = parent
|
||||
clear_visuals()
|
||||
LAZYREMOVE(M.mousemove_intercept_objects, src)
|
||||
STOP_PROCESSING(SSfastprocess, src)
|
||||
return ..()
|
||||
|
||||
@@ -120,7 +117,7 @@
|
||||
return
|
||||
LAZYREMOVE(immune_weakrefs, A.weak_reference)
|
||||
|
||||
/datum/component/lockon_aiming/onMouseMove(object,location,control,params)
|
||||
/datum/component/lockon_aiming/proc/onMouseMove(object,location,control,params)
|
||||
var/mob/M = parent
|
||||
if(!istype(M) || !M.client)
|
||||
return
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
if(!material_amount)
|
||||
to_chat(user, "<span class='warning'>[I] does not contain sufficient materials to be accepted by [parent].</span>")
|
||||
return
|
||||
if(!has_space(material_amount))
|
||||
if((!precise_insertion || !GLOB.typecache_stack[I.type]) && !has_space(material_amount))
|
||||
to_chat(user, "<span class='warning'>[parent] has not enough space. Please remove materials from [parent] in order to insert more.</span>")
|
||||
return
|
||||
user_insert(I, user)
|
||||
|
||||
@@ -1,36 +1,37 @@
|
||||
|
||||
/datum/component/shrapnel
|
||||
/datum/component/mirv
|
||||
var/projectile_type
|
||||
var/radius // shoots a projectile for every turf on this radius from the hit target
|
||||
var/override_projectile_range
|
||||
|
||||
/datum/component/shrapnel/Initialize(projectile_type, radius=1, override_projectile_range)
|
||||
if(!isgun(parent) && !ismachinery(parent) && !isstructure(parent))
|
||||
/datum/component/mirv/Initialize(projectile_type, radius=1, override_projectile_range)
|
||||
if(!isgun(parent) && !ismachinery(parent) && !isstructure(parent) && !isgrenade(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
src.projectile_type = projectile_type
|
||||
src.radius = radius
|
||||
src.override_projectile_range = override_projectile_range
|
||||
if(isgrenade(parent))
|
||||
parent.AddComponent(/datum/component/pellet_cloud, projectile_type=projectile_type)
|
||||
|
||||
/datum/component/shrapnel/RegisterWithParent()
|
||||
/datum/component/mirv/RegisterWithParent()
|
||||
. = ..()
|
||||
if(ismachinery(parent) || isstructure(parent) || isgun(parent)) // turrets, etc
|
||||
RegisterSignal(parent, COMSIG_PROJECTILE_ON_HIT, .proc/projectile_hit)
|
||||
|
||||
/datum/component/shrapnel/UnregisterFromParent()
|
||||
/datum/component/mirv/UnregisterFromParent()
|
||||
. = ..()
|
||||
UnregisterSignal(parent, list(COMSIG_PROJECTILE_ON_HIT))
|
||||
|
||||
/datum/component/shrapnel/proc/projectile_hit(atom/fired_from, atom/movable/firer, atom/target, Angle)
|
||||
/datum/component/mirv/proc/projectile_hit(atom/fired_from, atom/movable/firer, atom/target, Angle)
|
||||
do_shrapnel(firer, target)
|
||||
|
||||
/datum/component/shrapnel/proc/do_shrapnel(mob/firer, atom/target)
|
||||
/datum/component/mirv/proc/do_shrapnel(mob/firer, atom/target)
|
||||
if(radius < 1)
|
||||
return
|
||||
var/turf/target_turf = get_turf(target)
|
||||
for(var/turf/shootat_turf in RANGE_TURFS(radius, target) - RANGE_TURFS(radius-1, target))
|
||||
var/obj/item/projectile/P = new projectile_type(target_turf)
|
||||
|
||||
var/obj/item/projectile/P = new projectile_type(target_turf)
|
||||
//Shooting Code:
|
||||
P.range = radius+1
|
||||
if(override_projectile_range)
|
||||
@@ -1,41 +1,52 @@
|
||||
#define ECSTATIC_SANITY_PEN -1
|
||||
#define SLIGHT_INSANITY_PEN 1
|
||||
#define MINOR_INSANITY_PEN 5
|
||||
#define MAJOR_INSANITY_PEN 10
|
||||
#define MOOD_INSANITY_MALUS 0.0054 // per point of sanity below SANITY_DISTURBED, a 40% debuff to skills at rock bottom depression.
|
||||
#define MOOD_INSANITY_MALUS 0.13 // 13% debuff per sanity_level above the default of 4 (higher is worser), overall a 39% debuff to skills at rock bottom depression.
|
||||
|
||||
/datum/component/mood
|
||||
var/mood //Real happiness
|
||||
var/sanity = 100 //Current sanity
|
||||
var/shown_mood //Shown happiness, this is what others can see when they try to examine you, prevents antag checking by noticing traitors are always very happy.
|
||||
var/mood_level = 5 //To track what stage of moodies they're on
|
||||
var/sanity_level = 5 //To track what stage of sanity they're on
|
||||
var/sanity_level = 3 //To track what stage of sanity they're on
|
||||
var/mood_modifier = 1 //Modifier to allow certain mobs to be less affected by moodlets
|
||||
var/list/datum/mood_event/mood_events = list()
|
||||
var/insanity_effect = 0 //is the owner being punished for low mood? If so, how much?
|
||||
var/obj/screen/mood/screen_obj
|
||||
var/datum/skill_modifier/bad_mood/malus
|
||||
var/datum/skill_modifier/great_mood/bonus
|
||||
var/static/malus_id = 0
|
||||
var/static/list/free_maluses = list()
|
||||
|
||||
/datum/component/mood/Initialize()
|
||||
if(!isliving(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
START_PROCESSING(SSmood, src)
|
||||
var/mob/living/owner = parent
|
||||
if(owner.stat != DEAD)
|
||||
START_PROCESSING(SSobj, src)
|
||||
|
||||
RegisterSignal(parent, COMSIG_ADD_MOOD_EVENT, .proc/add_event)
|
||||
RegisterSignal(parent, COMSIG_CLEAR_MOOD_EVENT, .proc/clear_event)
|
||||
RegisterSignal(parent, COMSIG_MODIFY_SANITY, .proc/modify_sanity)
|
||||
RegisterSignal(parent, COMSIG_LIVING_REVIVE, .proc/on_revive)
|
||||
RegisterSignal(parent, COMSIG_MOB_HUD_CREATED, .proc/modify_hud)
|
||||
var/mob/living/owner = parent
|
||||
RegisterSignal(parent, COMSIG_MOB_DEATH, .proc/stop_processing)
|
||||
|
||||
if(owner.hud_used)
|
||||
modify_hud()
|
||||
var/datum/hud/hud = owner.hud_used
|
||||
hud.show_hud(hud.hud_version)
|
||||
|
||||
/datum/component/mood/Destroy()
|
||||
STOP_PROCESSING(SSmood, src)
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
unmodify_hud()
|
||||
return ..()
|
||||
|
||||
/datum/component/mood/proc/stop_processing()
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
|
||||
/datum/component/mood/proc/print_mood(mob/user)
|
||||
var/msg = "<span class='info'>*---------*\n<EM>Your current mood</EM>\n"
|
||||
msg += "<span class='notice'>My mental status: </span>" //Long term
|
||||
@@ -127,7 +138,7 @@
|
||||
else
|
||||
screen_obj.icon_state = "mood[mood_level]"
|
||||
|
||||
/datum/component/mood/process() //Called on SSmood process
|
||||
/datum/component/mood/process() //Called on SSobj process
|
||||
if(QDELETED(parent)) // workaround to an obnoxious sneaky periodical runtime.
|
||||
qdel(src)
|
||||
return
|
||||
@@ -167,13 +178,13 @@
|
||||
else if(sanity > maximum && amount > sanity - 0.5)
|
||||
amount = sanity - 0.5
|
||||
|
||||
var/old_sanity = sanity
|
||||
// Disturbed stops you from getting any more sane
|
||||
if(HAS_TRAIT(master, TRAIT_UNSTABLE))
|
||||
sanity = min(amount,sanity)
|
||||
else
|
||||
sanity = amount
|
||||
|
||||
var/old_sanity_level = sanity_level
|
||||
switch(sanity)
|
||||
if(-INFINITY to SANITY_CRAZY)
|
||||
setInsanityEffect(MAJOR_INSANITY_PEN)
|
||||
@@ -196,27 +207,43 @@
|
||||
master.remove_movespeed_modifier(MOVESPEED_ID_SANITY)
|
||||
sanity_level = 2
|
||||
if(SANITY_GREAT+1 to INFINITY)
|
||||
setInsanityEffect(0)
|
||||
setInsanityEffect(ECSTATIC_SANITY_PEN) //It's not a penalty but w/e
|
||||
master.remove_movespeed_modifier(MOVESPEED_ID_SANITY)
|
||||
sanity_level = 1
|
||||
|
||||
if(old_sanity > 1 && sanity == 1)
|
||||
RegisterSignal(master, COMSIG_MOB_SKILL_GET_AFFINITY, .proc/on_get_skill_affinity)
|
||||
else if(old_sanity == 1 && sanity > 1)
|
||||
UnregisterSignal(master, COMSIG_MOB_SKILL_GET_AFFINITY)
|
||||
if(sanity_level != old_sanity_level)
|
||||
if(sanity_level >= 4)
|
||||
if(!malus)
|
||||
if(!length(free_maluses))
|
||||
ADD_SKILL_MODIFIER_BODY(/datum/skill_modifier/bad_mood, malus_id++, master, malus)
|
||||
else
|
||||
malus = pick_n_take(free_maluses)
|
||||
if(master.mind)
|
||||
master.mind.add_skill_modifier(malus.identifier)
|
||||
else
|
||||
malus.RegisterSignal(master, COMSIG_MOB_ON_NEW_MIND, /datum/skill_modifier.proc/on_mob_new_mind, TRUE)
|
||||
malus.value_mod = malus.level_mod = 1 - (sanity_level - 3) * MOOD_INSANITY_MALUS
|
||||
else if(malus)
|
||||
if(master.mind)
|
||||
master.mind.remove_skill_modifier(malus.identifier)
|
||||
else
|
||||
malus.UnregisterSignal(master, COMSIG_MOB_ON_NEW_MIND)
|
||||
free_maluses += malus
|
||||
malus = null
|
||||
|
||||
//update_mood_icon()
|
||||
|
||||
/datum/component/mood/proc/setInsanityEffect(newval)//More code so that the previous proc works
|
||||
if(newval == insanity_effect)
|
||||
return
|
||||
//var/mob/living/master = parent
|
||||
//master.crit_threshold = (master.crit_threshold - insanity_effect) + newval
|
||||
if(!insanity_effect && newval)
|
||||
RegisterSignal(parent, COMSIG_MOB_ACTION_SKILL_MOD, .proc/on_mob_action_skill_mod)
|
||||
RegisterSignal(parent, COMSIG_MOB_ITEM_ACTION_SKILLS_MOD, .proc/on_item_action_skills_mod)
|
||||
else if(insanity_effect && !newval)
|
||||
UnregisterSignal(parent, list(COMSIG_MOB_ACTION_SKILL_MOD, COMSIG_MOB_ITEM_ACTION_SKILLS_MOD))
|
||||
|
||||
var/mob/living/L = parent
|
||||
if(newval == ECSTATIC_SANITY_PEN && !bonus)
|
||||
ADD_SKILL_MODIFIER_BODY(/datum/skill_modifier/great_mood, null, L, bonus)
|
||||
else if(bonus)
|
||||
REMOVE_SKILL_MODIFIER_BODY(/datum/skill_modifier/great_mood, null, L)
|
||||
bonus = null
|
||||
|
||||
insanity_effect = newval
|
||||
|
||||
/datum/component/mood/proc/modify_sanity(datum/source, amount, minimum = SANITY_INSANE, maximum = SANITY_AMAZING)
|
||||
@@ -294,46 +321,37 @@
|
||||
if(0 to NUTRITION_LEVEL_STARVING)
|
||||
add_event(null, "nutrition", /datum/mood_event/starving)
|
||||
|
||||
///Called when parent is ahealed.
|
||||
/datum/component/mood/proc/update_beauty(area/A)
|
||||
if(A.outdoors) //if we're outside, we don't care.
|
||||
clear_event(null, "area_beauty")
|
||||
return FALSE
|
||||
if(HAS_TRAIT(parent, TRAIT_SNOB))
|
||||
switch(A.beauty)
|
||||
if(-INFINITY to BEAUTY_LEVEL_HORRID)
|
||||
add_event(null, "area_beauty", /datum/mood_event/horridroom)
|
||||
return
|
||||
if(BEAUTY_LEVEL_HORRID to BEAUTY_LEVEL_BAD)
|
||||
add_event(null, "area_beauty", /datum/mood_event/badroom)
|
||||
return
|
||||
switch(A.beauty)
|
||||
if(-INFINITY to BEAUTY_LEVEL_DECENT)
|
||||
clear_event(null, "area_beauty")
|
||||
if(BEAUTY_LEVEL_DECENT to BEAUTY_LEVEL_GOOD)
|
||||
add_event(null, "area_beauty", /datum/mood_event/decentroom)
|
||||
if(BEAUTY_LEVEL_GOOD to BEAUTY_LEVEL_GREAT)
|
||||
add_event(null, "area_beauty", /datum/mood_event/goodroom)
|
||||
if(BEAUTY_LEVEL_GREAT to INFINITY)
|
||||
add_event(null, "area_beauty", /datum/mood_event/greatroom)
|
||||
|
||||
///Called when parent is revived.
|
||||
/datum/component/mood/proc/on_revive(datum/source, full_heal)
|
||||
START_PROCESSING(SSobj, src)
|
||||
if(!full_heal)
|
||||
return
|
||||
remove_temp_moods()
|
||||
setSanity(initial(sanity))
|
||||
|
||||
/datum/component/mood/proc/on_mob_action_skill_mod(mob/source, list/skill_args, list/mod_values)
|
||||
var/datum/skill/S = GLOB.skill_datums[skill_args[ACTION_SKILL_MOD_SKILL]]
|
||||
if(!(S.skill_flags & SKILL_USE_MOOD))
|
||||
return
|
||||
var/debuff = 1 - (SANITY_DISTURBED - sanity) * MOOD_INSANITY_MALUS
|
||||
mod_values[MOD_VALUES_SKILL_MOD] *= skill_args[ACTION_SKILL_MOD_IS_MULTI] ? debuff : 1/debuff
|
||||
|
||||
/datum/component/mood/proc/on_item_action_skills_mod(mob/source, list/skill_args, list/mod_values)
|
||||
if(skill_args[ITEM_SKILLS_MOD_BAD_FLAGS] & SKILL_USE_MOOD)
|
||||
return
|
||||
var/divisor = mod_values[MOD_VALUES_ITEM_SKILLS_DIV]
|
||||
if(!divisor)
|
||||
return
|
||||
var/obj/item/I = skill_args[ITEM_SKILLS_MOD_ITEM]
|
||||
var/list/L = mod_values[MOD_VALUES_ITEM_SKILLS_CHECKED]
|
||||
var/skills_len = length(L)
|
||||
var/affected_skills = skills_len
|
||||
for(var/k in L)
|
||||
var/datum/skill/S = k
|
||||
var/our_flags = I.used_skills[S.type]|S.skill_flags
|
||||
if(!(our_flags & SKILL_USE_MOOD))
|
||||
affected_skills--
|
||||
if(!affected_skills)
|
||||
return
|
||||
var/debuff = 1 - (SANITY_DISTURBED - sanity) * MOOD_INSANITY_MALUS * (affected_skills/skills_len)
|
||||
mod_values[MOD_VALUES_ITEM_SKILLS_SUM] *= skill_args[ITEM_SKILLS_MOD_IS_MULTI] ? debuff : 1/debuff
|
||||
|
||||
/datum/component/mood/proc/on_get_skill_affinity(mob/source, skill_path, list/return_value)
|
||||
var/datum/skill/S = GLOB.skill_datums[skill_path]
|
||||
if(!S || !(S.skill_flags & SKILL_TRAIN_MOOD))
|
||||
return
|
||||
return_value[1] *= SKILL_AFFINITY_MOOD_BONUS
|
||||
|
||||
#undef ECSTATIC_SANITY_PEN
|
||||
#undef SLIGHT_INSANITY_PEN
|
||||
#undef MINOR_INSANITY_PEN
|
||||
#undef MAJOR_INSANITY_PEN
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* omen.dm: For when you want someone to have a really bad day
|
||||
*
|
||||
* When you attach an omen component to someone, they start running the risk of all sorts of bad environmental injuries, like nearby vending machines randomly falling on you,
|
||||
* or hitting your head really hard when you slip and fall, or... well, for now those two are all I have. More will come.
|
||||
*
|
||||
* Omens are removed once the victim is either maimed by one of the possible injuries, or if they receive a blessing (read: bashing with a bible) from the chaplain.
|
||||
*/
|
||||
/datum/component/omen
|
||||
dupe_mode = COMPONENT_DUPE_UNIQUE
|
||||
|
||||
/// Whatever's causing the omen, if there is one. Destroying the vessel won't stop the omen, but we destroy the vessel (if one exists) upon the omen ending
|
||||
var/obj/vessel
|
||||
|
||||
/datum/component/omen/Initialize(silent=FALSE, vessel)
|
||||
if(!isliving(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
var/mob/person = parent
|
||||
if(!silent)
|
||||
to_chat(person, "<span class='warning'>You get a bad feeling...</span>")
|
||||
src.vessel = vessel
|
||||
|
||||
/datum/component/omen/Destroy(force, silent)
|
||||
if(vessel)
|
||||
vessel.visible_message("<span class='warning'>[vessel] burns up in a sinister flash, taking an evil energy with it...</span>")
|
||||
vessel = null
|
||||
return ..()
|
||||
|
||||
/datum/component/omen/RegisterWithParent()
|
||||
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/check_accident)
|
||||
RegisterSignal(parent, COMSIG_LIVING_STATUS_KNOCKDOWN, .proc/check_slip)
|
||||
RegisterSignal(parent, COMSIG_ADD_MOOD_EVENT, .proc/check_bless)
|
||||
|
||||
/datum/component/omen/UnregisterFromParent()
|
||||
UnregisterSignal(parent, list(COMSIG_LIVING_STATUS_KNOCKDOWN, COMSIG_MOVABLE_MOVED, COMSIG_ADD_MOOD_EVENT))
|
||||
|
||||
/**
|
||||
* check_accident() is called each step we take
|
||||
*
|
||||
* While we're walking around, roll to see if there's any environmental hazards (currently only vending machines) on one of the adjacent tiles we can trigger.
|
||||
* We do the prob() at the beginning to A. add some tension for /when/ it will strike, and B. (more importantly) ameliorate the fact that we're checking up to 5 turfs's contents each time
|
||||
*/
|
||||
/datum/component/omen/proc/check_accident(atom/movable/our_guy)
|
||||
if(!prob(15))
|
||||
return
|
||||
for(var/t in get_adjacent_open_turfs(our_guy))
|
||||
var/turf/the_turf = t
|
||||
for(var/obj/machinery/vending/darth_vendor in the_turf)
|
||||
if(darth_vendor.tiltable)
|
||||
darth_vendor.tilt(our_guy)
|
||||
qdel(src)
|
||||
return
|
||||
|
||||
/// If we get knocked down, see if we have a really bad slip and bash our head hard
|
||||
/datum/component/omen/proc/check_slip(mob/living/our_guy, amount)
|
||||
if(amount <= 0 || prob(50)) // 50% chance to bonk our head
|
||||
return
|
||||
|
||||
var/obj/item/bodypart/the_head = our_guy.get_bodypart(BODY_ZONE_HEAD)
|
||||
if(!the_head)
|
||||
return
|
||||
|
||||
playsound(get_turf(our_guy), "sound/effects/tableheadsmash.ogg", 90, TRUE)
|
||||
our_guy.visible_message("<span class='danger'>[our_guy] hits [our_guy.p_their()] head really badly falling down!</span>", "<span class='userdanger'>You hit your head really badly falling down!</span>")
|
||||
the_head.receive_damage(75)
|
||||
our_guy.adjustOrganLoss(ORGAN_SLOT_BRAIN, 100)
|
||||
qdel(src)
|
||||
|
||||
/// Hijack the mood system to see if we get the blessing mood event to cancel the omen
|
||||
/datum/component/omen/proc/check_bless(mob/living/our_guy, category)
|
||||
if(category != "blessing")
|
||||
return
|
||||
to_chat(our_guy, "<span class='nicegreen'>You feel a horrible omen lifted off your shoulders!</span>")
|
||||
qdel(src)
|
||||
@@ -62,7 +62,9 @@
|
||||
orbiters[orbiter] = TRUE
|
||||
orbiter.orbiting = src
|
||||
RegisterSignal(orbiter, COMSIG_MOVABLE_MOVED, .proc/orbiter_move_react)
|
||||
|
||||
var/matrix/initial_transform = matrix(orbiter.transform)
|
||||
orbiters[orbiter] = initial_transform
|
||||
|
||||
// Head first!
|
||||
if(pre_rotation)
|
||||
@@ -79,8 +81,6 @@
|
||||
|
||||
orbiter.SpinAnimation(rotation_speed, -1, clockwise, rotation_segments, parallel = FALSE)
|
||||
|
||||
//we stack the orbits up client side, so we can assign this back to normal server side without it breaking the orbit
|
||||
orbiter.transform = initial_transform
|
||||
orbiter.forceMove(get_turf(parent))
|
||||
to_chat(orbiter, "<span class='notice'>Now orbiting [parent].</span>")
|
||||
|
||||
@@ -89,6 +89,8 @@
|
||||
return
|
||||
UnregisterSignal(orbiter, COMSIG_MOVABLE_MOVED)
|
||||
orbiter.SpinAnimation(0, 0)
|
||||
if(istype(orbiters[orbiter],/matrix)) //This is ugly.
|
||||
orbiter.transform = orbiters[orbiter]
|
||||
orbiters -= orbiter
|
||||
orbiter.stop_orbit(src)
|
||||
orbiter.orbiting = null
|
||||
|
||||
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
* This component is used when you want to create a bunch of shrapnel or projectiles (say, shrapnel from a fragmentation grenade, or buckshot from a shotgun) from a central point,
|
||||
* without necessarily printing a separate message for every single impact. This component should be instantiated right when you need it (like the moment of firing), then activated
|
||||
* by signal.
|
||||
*
|
||||
* Pellet cloud currently works on two classes of sources: directed (ammo casings), and circular (grenades, landmines).
|
||||
* -Directed: This means you're shooting multiple pellets, like buckshot. If an ammo casing is defined as having multiple pellets, it will automatically create a pellet cloud
|
||||
* and call COMSIG_PELLET_CLOUD_INIT (see [/obj/item/ammo_casing/proc/fire_casing]). Thus, the only projectiles fired will be the ones fired here.
|
||||
* The magnitude var controls how many pellets are created.
|
||||
* -Circular: This results in a big spray of shrapnel flying all around the detonation point when the grenade fires COMSIG_GRENADE_PRIME or landmine triggers COMSIG_MINE_TRIGGERED.
|
||||
* The magnitude var controls how big the detonation radius is (the bigger the magnitude, the more shrapnel is created). Grenades can be covered with bodies to reduce shrapnel output.
|
||||
*
|
||||
* Once all of the fired projectiles either hit a target or disappear due to ranging out/whatever else, we resolve the list of all the things we hit and print aggregate messages so we get
|
||||
* one "You're hit by 6 buckshot pellets" vs 6x "You're hit by the buckshot blah blah" messages.
|
||||
*
|
||||
* Note that this is how all guns handle shooting ammo casings with multiple pellets, in case such a thing comes up.
|
||||
*/
|
||||
|
||||
/datum/component/pellet_cloud
|
||||
/// What's the projectile path of the shrapnel we're shooting?
|
||||
var/projectile_type
|
||||
|
||||
/// How many shrapnel projectiles are we responsible for tracking? May be reduced for grenades if someone dives on top of it. Defined by ammo casing for casings, derived from magnitude otherwise
|
||||
var/num_pellets
|
||||
/// For grenades/landmines, how big is the radius of turfs we're targeting? Note this does not effect the projectiles range, only how many we generate
|
||||
var/radius = 4
|
||||
|
||||
/// The list of pellets we're responsible for tracking, once these are all accounted for, we finalize.
|
||||
var/list/pellets = list()
|
||||
/// An associated list with the atom hit as the key and how many pellets they've eaten for the value, for printing aggregate messages
|
||||
var/list/targets_hit = list()
|
||||
/// For grenades, any /mob/living's the grenade is moved onto, see [/datum/component/pellet_cloud/proc/handle_martyrs()]
|
||||
var/list/bodies
|
||||
/// For grenades, tracking people who die covering a grenade for achievement purposes, see [/datum/component/pellet_cloud/proc/handle_martyrs()]
|
||||
var/list/purple_hearts
|
||||
|
||||
/// For grenades, tracking how many pellets are removed due to martyrs and how many pellets are added due to the last person to touch it being on top of it
|
||||
var/pellet_delta = 0
|
||||
/// how many pellets ranged out without hitting anything
|
||||
var/terminated
|
||||
/// how many pellets impacted something
|
||||
var/hits
|
||||
/// If the parent tried deleting and we're not done yet, we send it to nullspace then delete it after
|
||||
var/queued_delete = FALSE
|
||||
|
||||
/// for if we're an ammo casing being fired
|
||||
var/mob/living/shooter
|
||||
|
||||
/datum/component/pellet_cloud/Initialize(projectile_type=/obj/item/shrapnel, magnitude=5)
|
||||
if(!isammocasing(parent) && !isgrenade(parent) && !islandmine(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
if(magnitude < 1)
|
||||
stack_trace("Invalid magnitude [magnitude] < 1 on pellet_cloud, parent: [parent]")
|
||||
magnitude = 1
|
||||
|
||||
src.projectile_type = projectile_type
|
||||
|
||||
if(isammocasing(parent))
|
||||
num_pellets = magnitude
|
||||
else if(isgrenade(parent) || islandmine(parent))
|
||||
radius = magnitude
|
||||
|
||||
/datum/component/pellet_cloud/Destroy(force, silent)
|
||||
purple_hearts = null
|
||||
pellets = null
|
||||
targets_hit = null
|
||||
bodies = null
|
||||
return ..()
|
||||
|
||||
/datum/component/pellet_cloud/RegisterWithParent()
|
||||
RegisterSignal(parent, COMSIG_PARENT_PREQDELETED, .proc/nullspace_parent)
|
||||
if(isammocasing(parent))
|
||||
RegisterSignal(parent, COMSIG_PELLET_CLOUD_INIT, .proc/create_casing_pellets)
|
||||
else if(isgrenade(parent))
|
||||
RegisterSignal(parent, COMSIG_GRENADE_ARMED, .proc/grenade_armed)
|
||||
RegisterSignal(parent, COMSIG_GRENADE_PRIME, .proc/create_blast_pellets)
|
||||
else if(islandmine(parent))
|
||||
RegisterSignal(parent, COMSIG_MINE_TRIGGERED, .proc/create_blast_pellets)
|
||||
|
||||
/datum/component/pellet_cloud/UnregisterFromParent()
|
||||
UnregisterSignal(parent, list(COMSIG_PARENT_PREQDELETED, COMSIG_PELLET_CLOUD_INIT, COMSIG_GRENADE_PRIME, COMSIG_GRENADE_ARMED, COMSIG_MOVABLE_MOVED, COMSIG_MOVABLE_UNCROSSED, COMSIG_MINE_TRIGGERED, COMSIG_ITEM_DROPPED))
|
||||
|
||||
/**
|
||||
* create_casing_pellets() is for directed pellet clouds for ammo casings that have multiple pellets (buckshot and scatter lasers for instance)
|
||||
*
|
||||
* Honestly this is mostly just a rehash of [/obj/item/ammo_casing/proc/fire_casing()] for pellet counts > 1, except this lets us tamper with the pellets and hook onto them for tracking purposes.
|
||||
* The arguments really don't matter, this proc is triggered by COMSIG_PELLET_CLOUD_INIT which is only for this really, it's just a big mess of the state vars we need for doing the stuff over here.
|
||||
*/
|
||||
/datum/component/pellet_cloud/proc/create_casing_pellets(obj/item/ammo_casing/shell, atom/target, mob/living/user, fired_from, randomspread, spread, zone_override, params, distro)
|
||||
shooter = user
|
||||
var/targloc = get_turf(target)
|
||||
if(!zone_override)
|
||||
zone_override = shooter.zone_selected
|
||||
|
||||
for(var/i in 1 to num_pellets)
|
||||
shell.ready_proj(target, user, SUPPRESSED_VERY, zone_override, fired_from)
|
||||
if(distro)
|
||||
if(randomspread)
|
||||
spread = round((rand() - 0.5) * distro)
|
||||
else //Smart spread
|
||||
spread = round((i / num_pellets - 0.5) * distro)
|
||||
|
||||
RegisterSignal(shell.BB, COMSIG_PROJECTILE_SELF_ON_HIT, .proc/pellet_hit)
|
||||
RegisterSignal(shell.BB, list(COMSIG_PROJECTILE_RANGE_OUT, COMSIG_PARENT_QDELETING), .proc/pellet_range)
|
||||
pellets += shell.BB
|
||||
if(!shell.throw_proj(target, targloc, shooter, params, spread))
|
||||
return
|
||||
if(i != num_pellets)
|
||||
shell.newshot()
|
||||
|
||||
/**
|
||||
* create_blast_pellets() is for when we have a central point we want to shred the surroundings of with a ring of shrapnel, namely frag grenades and landmines.
|
||||
*
|
||||
* Note that grenades have extra handling for someone throwing themselves/being thrown on top of it, while landmines do not (obviously, it's a landmine!). See [/datum/component/pellet_cloud/proc/handle_martyrs()]
|
||||
*/
|
||||
/datum/component/pellet_cloud/proc/create_blast_pellets(obj/O, mob/living/lanced_by)
|
||||
var/atom/A = parent
|
||||
|
||||
if(isgrenade(parent)) // handle_martyrs can reduce the radius and thus the number of pellets we produce if someone dives on top of a frag grenade
|
||||
handle_martyrs(lanced_by) // note that we can modify radius in this proc
|
||||
|
||||
if(radius < 1)
|
||||
return
|
||||
|
||||
var/list/all_the_turfs_were_gonna_lacerate = RANGE_TURFS(radius, A) - RANGE_TURFS(radius-1, A)
|
||||
num_pellets = all_the_turfs_were_gonna_lacerate.len + pellet_delta
|
||||
|
||||
for(var/T in all_the_turfs_were_gonna_lacerate)
|
||||
var/turf/shootat_turf = T
|
||||
pew(shootat_turf)
|
||||
|
||||
/**
|
||||
* handle_martyrs() is used for grenades that shoot shrapnel to check if anyone threw themselves/were thrown on top of the grenade, thus absorbing a good chunk of the shrapnel
|
||||
*
|
||||
* Between the time the grenade is armed and the actual detonation, we set var/list/bodies to the list of mobs currently on the new tile, as if the grenade landed on top of them, tracking if any of them move off the tile and removing them from the "under" list
|
||||
* Once the grenade detonates, handle_martyrs() is called and gets all the new mobs on the tile, and add the ones not in var/list/bodies to var/list/martyrs
|
||||
* We then iterate through the martyrs and reduce the shrapnel magnitude for each mob on top of it, shredding each of them with some of the shrapnel they helped absorb. This can snuff out all of the shrapnel if there's enough bodies
|
||||
*
|
||||
* Note we track anyone who's alive and client'd when they get shredded in var/list/purple_hearts, for achievement checking later
|
||||
*/
|
||||
/datum/component/pellet_cloud/proc/handle_martyrs(mob/living/lanced_by)
|
||||
var/magnitude_absorbed
|
||||
var/list/martyrs = list()
|
||||
|
||||
var/self_harm_radius_mult = 3
|
||||
|
||||
if(lanced_by && prob(60))
|
||||
to_chat(lanced_by, "<span class='userdanger'>Your plan to whack someone with a grenade on a stick backfires on you, literally!</span>")
|
||||
self_harm_radius_mult = 1 // we'll still give the guy who got hit some extra shredding, but not 3*radius
|
||||
pellet_delta += radius
|
||||
for(var/i in 1 to radius)
|
||||
pew(lanced_by) // thought you could be tricky and lance someone with no ill effects!!
|
||||
|
||||
for(var/mob/living/body in get_turf(parent))
|
||||
if(body == shooter)
|
||||
pellet_delta += radius * self_harm_radius_mult
|
||||
for(var/i in 1 to radius * self_harm_radius_mult)
|
||||
pew(body) // free shrapnel if it goes off in your hand, and it doesn't even count towards the absorbed. fun!
|
||||
else if(!(body in bodies))
|
||||
martyrs += body // promoted from a corpse to a hero
|
||||
|
||||
for(var/M in martyrs)
|
||||
var/mob/living/martyr = M
|
||||
if(radius > 4)
|
||||
martyr.visible_message("<b><span class='danger'>[martyr] heroically covers \the [parent] with [martyr.p_their()] body, absorbing a load of the shrapnel!</span></b>", "<span class='userdanger'>You heroically cover \the [parent] with your body, absorbing a load of the shrapnel!</span>")
|
||||
magnitude_absorbed += round(radius * 0.5)
|
||||
else if(radius >= 2)
|
||||
martyr.visible_message("<b><span class='danger'>[martyr] heroically covers \the [parent] with [martyr.p_their()] body, absorbing some of the shrapnel!</span></b>", "<span class='userdanger'>You heroically cover \the [parent] with your body, absorbing some of the shrapnel!</span>")
|
||||
magnitude_absorbed += 2
|
||||
else
|
||||
martyr.visible_message("<b><span class='danger'>[martyr] heroically covers \the [parent] with [martyr.p_their()] body, snuffing out the shrapnel!</span></b>", "<span class='userdanger'>You heroically cover \the [parent] with your body, snuffing out the shrapnel!</span>")
|
||||
magnitude_absorbed = radius
|
||||
|
||||
var/pellets_absorbed = (radius ** 2) - ((radius - magnitude_absorbed - 1) ** 2)
|
||||
radius -= magnitude_absorbed
|
||||
pellet_delta -= round(pellets_absorbed * 0.5)
|
||||
|
||||
if(martyr.stat != DEAD && martyr.client)
|
||||
LAZYADD(purple_hearts, martyr)
|
||||
RegisterSignal(martyr, COMSIG_PARENT_QDELETING, .proc/on_target_qdel, override=TRUE)
|
||||
|
||||
for(var/i in 1 to round(pellets_absorbed * 0.5))
|
||||
pew(martyr)
|
||||
|
||||
if(radius < 1)
|
||||
break
|
||||
|
||||
///One of our pellets hit something, record what it was and check if we're done (terminated == num_pellets)
|
||||
/datum/component/pellet_cloud/proc/pellet_hit(obj/item/projectile/P, atom/movable/firer, atom/target, Angle)
|
||||
pellets -= P
|
||||
terminated++
|
||||
hits++
|
||||
targets_hit[target]++
|
||||
if(targets_hit[target] == 1)
|
||||
RegisterSignal(target, COMSIG_PARENT_QDELETING, .proc/on_target_qdel, override=TRUE)
|
||||
UnregisterSignal(P, list(COMSIG_PARENT_QDELETING, COMSIG_PROJECTILE_RANGE_OUT, COMSIG_PROJECTILE_SELF_ON_HIT))
|
||||
if(terminated == num_pellets)
|
||||
finalize()
|
||||
|
||||
///One of our pellets disappeared due to hitting their max range (or just somehow got qdel'd), remove it from our list and check if we're done (terminated == num_pellets)
|
||||
/datum/component/pellet_cloud/proc/pellet_range(obj/item/projectile/P)
|
||||
pellets -= P
|
||||
terminated++
|
||||
UnregisterSignal(P, list(COMSIG_PARENT_QDELETING, COMSIG_PROJECTILE_RANGE_OUT, COMSIG_PROJECTILE_SELF_ON_HIT))
|
||||
if(terminated == num_pellets)
|
||||
finalize()
|
||||
|
||||
/// Minor convenience function for creating each shrapnel piece with circle explosions, mostly stolen from the MIRV component
|
||||
/datum/component/pellet_cloud/proc/pew(atom/target, spread=0)
|
||||
var/obj/item/projectile/P = new projectile_type(get_turf(parent))
|
||||
|
||||
//Shooting Code:
|
||||
P.spread = spread
|
||||
P.original = target
|
||||
P.fired_from = parent
|
||||
P.firer = parent // don't hit ourself that would be really annoying
|
||||
P.permutated += parent // don't hit the target we hit already with the flak
|
||||
P.suppressed = SUPPRESSED_VERY // set the projectiles to make no message so we can do our own aggregate message
|
||||
P.preparePixelProjectile(target, parent)
|
||||
RegisterSignal(P, COMSIG_PROJECTILE_SELF_ON_HIT, .proc/pellet_hit)
|
||||
RegisterSignal(P, list(COMSIG_PROJECTILE_RANGE_OUT, COMSIG_PARENT_QDELETING), .proc/pellet_range)
|
||||
pellets += P
|
||||
P.fire()
|
||||
|
||||
///All of our pellets are accounted for, time to go target by target and tell them how many things they got hit by.
|
||||
/datum/component/pellet_cloud/proc/finalize()
|
||||
var/obj/item/projectile/P = projectile_type
|
||||
var/proj_name = initial(P.name)
|
||||
|
||||
for(var/atom/target in targets_hit)
|
||||
var/num_hits = targets_hit[target]
|
||||
UnregisterSignal(target, COMSIG_PARENT_QDELETING)
|
||||
if(num_hits > 1)
|
||||
target.visible_message("<span class='danger'>[target] is hit by [num_hits] [proj_name]s!</span>", null, null, COMBAT_MESSAGE_RANGE, target)
|
||||
to_chat(target, "<span class='userdanger'>You're hit by [num_hits] [proj_name]s!</span>")
|
||||
else
|
||||
target.visible_message("<span class='danger'>[target] is hit by a [proj_name]!</span>", null, null, COMBAT_MESSAGE_RANGE, target)
|
||||
to_chat(target, "<span class='userdanger'>You're hit by a [proj_name]!</span>")
|
||||
|
||||
UnregisterSignal(parent, COMSIG_PARENT_PREQDELETED)
|
||||
if(queued_delete)
|
||||
qdel(parent)
|
||||
qdel(src)
|
||||
|
||||
/// Look alive, we're armed! Now we start watching to see if anyone's covering us
|
||||
/datum/component/pellet_cloud/proc/grenade_armed(obj/item/nade)
|
||||
if(ismob(nade.loc))
|
||||
shooter = nade.loc
|
||||
LAZYINITLIST(bodies)
|
||||
RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/grenade_dropped)
|
||||
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/grenade_moved)
|
||||
RegisterSignal(parent, COMSIG_MOVABLE_UNCROSSED, .proc/grenade_uncrossed)
|
||||
|
||||
/// Someone dropped the grenade, so set them to the shooter in case they're on top of it when it goes off
|
||||
/datum/component/pellet_cloud/proc/grenade_dropped(obj/item/nade, mob/living/slick_willy)
|
||||
shooter = slick_willy
|
||||
grenade_moved()
|
||||
|
||||
/// Our grenade has moved, reset var/list/bodies so we're "on top" of any mobs currently on the tile
|
||||
/datum/component/pellet_cloud/proc/grenade_moved()
|
||||
LAZYCLEARLIST(bodies)
|
||||
for(var/mob/living/L in get_turf(parent))
|
||||
RegisterSignal(L, COMSIG_PARENT_QDELETING, .proc/on_target_qdel, override=TRUE)
|
||||
bodies += L
|
||||
|
||||
/// Someone who was originally "under" the grenade has moved off the tile and is now eligible for being a martyr and "covering" it
|
||||
/datum/component/pellet_cloud/proc/grenade_uncrossed(datum/source, atom/movable/AM)
|
||||
bodies -= AM
|
||||
|
||||
/// Our grenade or landmine or caseless shell or whatever tried deleting itself, so we intervene and nullspace it until we're done here
|
||||
/datum/component/pellet_cloud/proc/nullspace_parent()
|
||||
var/atom/movable/AM = parent
|
||||
AM.moveToNullspace()
|
||||
queued_delete = TRUE
|
||||
return TRUE
|
||||
|
||||
/// Someone who was originally "under" the grenade has moved off the tile and is now eligible for being a martyr and "covering" it
|
||||
/datum/component/pellet_cloud/proc/on_target_qdel(atom/target)
|
||||
UnregisterSignal(target, COMSIG_PARENT_QDELETING)
|
||||
targets_hit -= target
|
||||
bodies -= target
|
||||
purple_hearts -= target
|
||||
@@ -20,7 +20,7 @@
|
||||
valid_slots = _valid_slots
|
||||
|
||||
/datum/component/wearertargeting/phantomthief/proc/handlefilterstuff(mob/living/user, was_forced = FALSE)
|
||||
if(!(user.combat_flags & COMBAT_FLAG_COMBAT_ACTIVE))
|
||||
if(!SEND_SIGNAL(user, COMSIG_COMBAT_MODE_CHECK, COMBAT_MODE_ACTIVE))
|
||||
user.remove_filter("phantomthief")
|
||||
else
|
||||
user.add_filter("phantomthief", 4, list(type = "drop_shadow", x = filter_x, y = filter_y, size = filter_size, color = filter_color))
|
||||
|
||||
@@ -69,8 +69,9 @@
|
||||
out += "[out ? " and it " : "[master] "]seems to be glowing a bit."
|
||||
if(RAD_AMOUNT_HIGH to INFINITY) //At this level the object can contaminate other objects
|
||||
out += "[out ? " and it " : "[master] "]hurts to look at."
|
||||
else
|
||||
out += "."
|
||||
if(!LAZYLEN(out))
|
||||
return
|
||||
out += "."
|
||||
examine_list += out.Join()
|
||||
|
||||
/datum/component/radioactive/proc/rad_attack(datum/source, atom/movable/target, mob/living/user)
|
||||
|
||||
@@ -23,18 +23,12 @@
|
||||
|
||||
if(can_user_rotate)
|
||||
src.can_user_rotate = can_user_rotate
|
||||
else
|
||||
src.can_user_rotate = CALLBACK(src,.proc/default_can_user_rotate)
|
||||
|
||||
if(can_be_rotated)
|
||||
src.can_be_rotated = can_be_rotated
|
||||
else
|
||||
src.can_be_rotated = CALLBACK(src,.proc/default_can_be_rotated)
|
||||
|
||||
if(after_rotation)
|
||||
src.after_rotation = after_rotation
|
||||
else
|
||||
src.after_rotation = CALLBACK(src,.proc/default_after_rotation)
|
||||
|
||||
//Try Clockwise,counter,flip in order
|
||||
if(src.rotation_flags & ROTATION_FLIP)
|
||||
@@ -103,14 +97,34 @@
|
||||
examine_list += "<span class='notice'>Alt-click to rotate it clockwise.</span>"
|
||||
|
||||
/datum/component/simple_rotation/proc/HandRot(datum/source, mob/user, rotation = default_rotation_direction)
|
||||
if(!can_be_rotated.Invoke(user, rotation) || !can_user_rotate.Invoke(user, rotation))
|
||||
return
|
||||
if(can_be_rotated)
|
||||
if(!can_be_rotated.Invoke(user, default_rotation_direction))
|
||||
return
|
||||
else
|
||||
if(!default_can_be_rotated(user, default_rotation_direction))
|
||||
return
|
||||
if(can_user_rotate)
|
||||
if(!can_user_rotate.Invoke(user, default_rotation_direction))
|
||||
return
|
||||
else
|
||||
if(!default_can_user_rotate(user, default_rotation_direction))
|
||||
return
|
||||
BaseRot(user, rotation)
|
||||
return TRUE
|
||||
|
||||
/datum/component/simple_rotation/proc/WrenchRot(datum/source, obj/item/I, mob/living/user)
|
||||
if(!can_be_rotated.Invoke(user,default_rotation_direction) || !can_user_rotate.Invoke(user,default_rotation_direction))
|
||||
return
|
||||
if(can_be_rotated)
|
||||
if(!can_be_rotated.Invoke(user, default_rotation_direction))
|
||||
return
|
||||
else
|
||||
if(!default_can_be_rotated(user, default_rotation_direction))
|
||||
return
|
||||
if(can_user_rotate)
|
||||
if(!can_user_rotate.Invoke(user, default_rotation_direction))
|
||||
return
|
||||
else
|
||||
if(!default_can_user_rotate(user, default_rotation_direction))
|
||||
return
|
||||
if(istype(I,/obj/item/wrench))
|
||||
BaseRot(user,default_rotation_direction)
|
||||
return COMPONENT_NO_AFTERATTACK
|
||||
@@ -126,7 +140,10 @@
|
||||
if(ROTATION_FLIP)
|
||||
rot_degree = 180
|
||||
AM.setDir(turn(AM.dir,rot_degree))
|
||||
after_rotation.Invoke(user,rotation_type)
|
||||
if(after_rotation)
|
||||
after_rotation.Invoke(user, rotation_type)
|
||||
else
|
||||
default_after_rotation(user, rotation_type)
|
||||
|
||||
/datum/component/simple_rotation/proc/default_can_user_rotate(mob/living/user, rotation_type)
|
||||
if(!istype(user) || !user.canUseTopic(parent, BE_CLOSE, NO_DEXTERY))
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
var/list/faction = list("mining")
|
||||
|
||||
/datum/component/spawner/Initialize(_mob_types, _spawn_time, _faction, _spawn_text, _max_mobs)
|
||||
if(!isatom(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
if(_spawn_time)
|
||||
spawn_time=_spawn_time
|
||||
if(_mob_types)
|
||||
@@ -19,20 +21,25 @@
|
||||
if(_max_mobs)
|
||||
max_mobs=_max_mobs
|
||||
|
||||
RegisterSignal(parent, list(COMSIG_PARENT_QDELETING), .proc/stop_spawning)
|
||||
RegisterSignal(parent, COMSIG_PARENT_QDELETING, .proc/stop_spawning)
|
||||
RegisterSignal(parent, COMSIG_OBJ_ATTACK_GENERIC, .proc/on_attack_generic)
|
||||
START_PROCESSING(SSprocessing, src)
|
||||
|
||||
/datum/component/spawner/process()
|
||||
try_spawn_mob()
|
||||
|
||||
|
||||
/datum/component/spawner/proc/stop_spawning(force, hint)
|
||||
/datum/component/spawner/proc/stop_spawning(datum/source, force, hint)
|
||||
STOP_PROCESSING(SSprocessing, src)
|
||||
for(var/mob/living/simple_animal/L in spawned_mobs)
|
||||
if(L.nest == src)
|
||||
L.nest = null
|
||||
spawned_mobs = null
|
||||
|
||||
// Stopping clientless simple mobs' from indiscriminately bashing their own spawners due DestroySurroundings() et similars.
|
||||
/datum/component/spawner/proc/on_attack_generic(datum/source, mob/user, damage_amount, damage_type, damage_flag, sound_effect, armor_penetration)
|
||||
if(!user.client && ((user.faction & faction) || (user in spawned_mobs)))
|
||||
return COMPONENT_STOP_GENERIC_ATTACK
|
||||
|
||||
/datum/component/spawner/proc/try_spawn_mob()
|
||||
var/atom/P = parent
|
||||
if(spawned_mobs.len >= max_mobs)
|
||||
|
||||
@@ -136,9 +136,7 @@
|
||||
var/mob/M = parent.loc
|
||||
I.dropped(M)
|
||||
if(new_location)
|
||||
//Reset the items values
|
||||
_removal_reset(AM)
|
||||
AM.forceMove(new_location)
|
||||
AM.forceMove(new_location) // exited comsig will handle removal reset.
|
||||
//We don't want to call this if the item is being destroyed
|
||||
AM.on_exit_storage(src)
|
||||
else
|
||||
|
||||
@@ -34,3 +34,8 @@
|
||||
qdel(A)
|
||||
return
|
||||
. = ..()
|
||||
|
||||
/datum/component/storage/concrete/bluespace/bag_of_holding/can_be_inserted(obj/item/I, stop_messages = FALSE, mob/M)
|
||||
if(I.GetComponent(/datum/component/storage/concrete/bluespace/bag_of_holding))
|
||||
return TRUE
|
||||
return ..()
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
/obj/item/reagent_containers/hypospray/medipen, /obj/item/reagent_containers/dropper,
|
||||
/obj/item/implanter, /obj/item/screwdriver, /obj/item/weldingtool/mini,
|
||||
/obj/item/firing_pin, /obj/item/gun/ballistic/automatic/pistol, /obj/item/gun/ballistic/automatic/magrifle/pistol,
|
||||
/obj/item/toy/plush/snakeplushie
|
||||
/obj/item/toy/plush/snakeplushie, /obj/item/gun/energy/e_gun/mini
|
||||
))
|
||||
|
||||
/datum/component/storage/concrete/pockets/shoes/clown/Initialize()
|
||||
@@ -68,7 +68,7 @@
|
||||
/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, /obj/item/gun/ballistic/automatic/pistol))
|
||||
/obj/item/firing_pin, /obj/item/bikehorn, /obj/item/gun/ballistic/automatic/pistol, /obj/item/gun/energy/e_gun/mini))
|
||||
|
||||
/datum/component/storage/concrete/pockets/pocketprotector
|
||||
max_items = 3
|
||||
|
||||
@@ -351,10 +351,14 @@
|
||||
return master._removal_reset(thing)
|
||||
|
||||
/datum/component/storage/proc/_remove_and_refresh(datum/source, atom/movable/thing)
|
||||
_removal_reset(thing)
|
||||
if(LAZYACCESS(ui_item_blocks, thing))
|
||||
qdel(ui_item_blocks[thing])
|
||||
var/obj/screen/storage/volumetric_box/center/C = ui_item_blocks[thing]
|
||||
for(var/i in can_see_contents()) //runtimes result if mobs can access post deletion.
|
||||
var/mob/M = i
|
||||
M.client?.screen -= C.on_screen_objects()
|
||||
ui_item_blocks -= thing
|
||||
qdel(C)
|
||||
_removal_reset(thing) // THIS NEEDS TO HAPPEN AFTER SO LAYERING DOESN'T BREAK!
|
||||
refresh_mob_views()
|
||||
|
||||
//Call this proc to handle the removal of an item from the storage item. The item will be moved to the new_location target, if that is null it's being deleted
|
||||
@@ -567,10 +571,9 @@
|
||||
return
|
||||
if(rustle_sound)
|
||||
playsound(parent, "rustle", 50, 1, -5)
|
||||
for(var/mob/viewing in viewers(user, null))
|
||||
if(M == viewing)
|
||||
to_chat(usr, "<span class='notice'>You put [I] [insert_preposition]to [parent].</span>")
|
||||
else if(in_range(M, viewing)) //If someone is standing close enough, they can tell what it is...
|
||||
to_chat(user, "<span class='notice'>You put [I] [insert_preposition]to [parent].</span>")
|
||||
for(var/mob/viewing in fov_viewers(world.view, user)-M)
|
||||
if(in_range(M, viewing)) //If someone is standing close enough, they can tell what it is...
|
||||
viewing.show_message("<span class='notice'>[M] puts [I] [insert_preposition]to [parent].</span>", MSG_VISUAL)
|
||||
else if(I && I.w_class >= 3) //Otherwise they can only see large or normal items from a distance...
|
||||
viewing.show_message("<span class='notice'>[M] puts [I] [insert_preposition]to [parent].</span>", MSG_VISUAL)
|
||||
|
||||
@@ -92,6 +92,8 @@
|
||||
var/list/volume_by_item = list()
|
||||
var/list/percentage_by_item = list()
|
||||
for(var/obj/item/I in contents)
|
||||
if(QDELETED(I))
|
||||
continue
|
||||
volume = I.get_w_volume()
|
||||
used += volume
|
||||
volume_by_item[I] = volume
|
||||
@@ -123,6 +125,7 @@
|
||||
var/obj/item/I
|
||||
// start at this pixel from screen_start_x.
|
||||
var/current_pixel = VOLUMETRIC_STORAGE_EDGE_PADDING
|
||||
var/first = TRUE
|
||||
var/row = 1
|
||||
|
||||
LAZYINITLIST(ui_item_blocks)
|
||||
@@ -140,10 +143,10 @@
|
||||
addrow = TRUE
|
||||
|
||||
// now that we have pixels_to_use, place our thing and add it to the returned list.
|
||||
|
||||
B.screen_loc = I.screen_loc = "[screen_start_x]:[round(current_pixel + (pixels_to_use * 0.5) + VOLUMETRIC_STORAGE_ITEM_PADDING, 1)],[screen_start_y+row-1]:[screen_pixel_y]"
|
||||
B.screen_loc = I.screen_loc = "[screen_start_x]:[round(current_pixel + (pixels_to_use * 0.5) + (first? 0 : VOLUMETRIC_STORAGE_ITEM_PADDING), 1)],[screen_start_y+row-1]:[screen_pixel_y]"
|
||||
// add the used pixels to pixel after we place the object
|
||||
current_pixel += pixels_to_use + VOLUMETRIC_STORAGE_ITEM_PADDING
|
||||
current_pixel += pixels_to_use + (first? 0 : VOLUMETRIC_STORAGE_ITEM_PADDING)
|
||||
first = FALSE //apply padding to everything after this
|
||||
|
||||
// set various things
|
||||
B.set_pixel_size(pixels_to_use)
|
||||
@@ -163,6 +166,7 @@
|
||||
// go up a row if needed
|
||||
if(addrow)
|
||||
row++
|
||||
first = TRUE //first in the row, don't apply between-item padding.
|
||||
current_pixel = VOLUMETRIC_STORAGE_EDGE_PADDING
|
||||
|
||||
// Then, continuous section.
|
||||
@@ -233,7 +237,7 @@
|
||||
if(!M.client)
|
||||
return TRUE
|
||||
UnregisterSignal(M, COMSIG_MOB_CLIENT_LOGOUT)
|
||||
M.client.screen -= list(ui_boxes, ui_close, ui_left, ui_continuous) + get_ui_item_objects_hide()
|
||||
M.client.screen -= list(ui_boxes, ui_close, ui_left, ui_continuous) + get_ui_item_objects_hide(M)
|
||||
if(M.active_storage == src)
|
||||
M.active_storage = null
|
||||
LAZYREMOVE(is_using, M)
|
||||
@@ -249,8 +253,8 @@
|
||||
/**
|
||||
* Gets the ui item objects to ui_hide.
|
||||
*/
|
||||
/datum/component/storage/proc/get_ui_item_objects_hide()
|
||||
if(!volumetric_ui())
|
||||
/datum/component/storage/proc/get_ui_item_objects_hide(mob/M)
|
||||
if(!volumetric_ui() || M.client?.prefs?.no_tetris_storage)
|
||||
var/atom/real_location = real_location()
|
||||
return real_location.contents
|
||||
else
|
||||
|
||||
@@ -59,7 +59,7 @@
|
||||
var/mob/living/simple_animal/L = new chosen_mob_type(spawn_location)
|
||||
if(ishostile(L))
|
||||
var/mob/living/simple_animal/hostile/H = L
|
||||
H.friends += summoner // do not attack our summon boy
|
||||
H.friends[summoner]++ // do not attack our summon boy
|
||||
spawned_mobs += L
|
||||
if(faction != null)
|
||||
L.faction = faction
|
||||
|
||||
@@ -11,6 +11,17 @@
|
||||
RegisterSignal(parent, COMSIG_MOVABLE_CROSSED, .proc/join_swarm)
|
||||
RegisterSignal(parent, COMSIG_MOVABLE_UNCROSSED, .proc/leave_swarm)
|
||||
|
||||
/datum/component/swarming/Destroy()
|
||||
if(is_swarming)
|
||||
for(var/A in swarm_members)
|
||||
var/datum/component/swarming/other_swarm = A
|
||||
other_swarm.swarm_members -= src
|
||||
swarm_members -= other_swarm
|
||||
if(!length(other_swarm.swarm_members))
|
||||
other_swarm.unswarm()
|
||||
unswarm()
|
||||
return ..()
|
||||
|
||||
/datum/component/swarming/proc/join_swarm(datum/source, atom/movable/AM)
|
||||
var/datum/component/swarming/other_swarm = AM.GetComponent(/datum/component/swarming)
|
||||
if(!other_swarm)
|
||||
|
||||
@@ -163,24 +163,24 @@
|
||||
if(-4 to -2) // glancing blow at best
|
||||
user.visible_message("<span class='warning'>[user] lands a weak [tackle_word] on [target], briefly knocking [target.p_them()] off-balance!</span>", "<span class='userdanger'>You land a weak [tackle_word] on [target], briefly knocking [target.p_them()] off-balance!</span>", target)
|
||||
to_chat(target, "<span class='userdanger'>[user] lands a weak [tackle_word] on you, briefly knocking you off-balance!</span>")
|
||||
|
||||
user.Knockdown(30)
|
||||
target.adjustStaminaLoss(15)
|
||||
target.apply_status_effect(STATUS_EFFECT_TASED_WEAK, 6 SECONDS)
|
||||
|
||||
if(-1 to 0) // decent hit, both parties are about equally inconvenienced
|
||||
user.visible_message("<span class='warning'>[user] lands a passable [tackle_word] on [target], sending them both tumbling!</span>", "<span class='userdanger'>You land a passable [tackle_word] on [target], sending you both tumbling!</span>", target)
|
||||
to_chat(target, "<span class='userdanger'>[user] lands a passable [tackle_word] on you, sending you both tumbling!</span>")
|
||||
|
||||
target.adjustStaminaLoss(stamina_cost)
|
||||
target.adjustStaminaLoss(stamina_cost * 1.5)
|
||||
target.Paralyze(5)
|
||||
user.Knockdown(20)
|
||||
target.Knockdown(25)
|
||||
target.Knockdown(30)
|
||||
|
||||
if(1 to 2) // solid hit, tackler has a slight advantage
|
||||
user.visible_message("<span class='warning'>[user] lands a solid [tackle_word] on [target], knocking them both down hard!</span>", "<span class='userdanger'>You land a solid [tackle_word] on [target], knocking you both down hard!</span>", target)
|
||||
to_chat(target, "<span class='userdanger'>[user] lands a solid [tackle_word] on you, knocking you both down hard!</span>")
|
||||
|
||||
target.adjustStaminaLoss(30)
|
||||
target.adjustStaminaLoss(40)
|
||||
target.Paralyze(5)
|
||||
user.Knockdown(10)
|
||||
target.Knockdown(20)
|
||||
@@ -192,9 +192,9 @@
|
||||
user.SetKnockdown(0)
|
||||
user.set_resting(FALSE, TRUE, FALSE)
|
||||
user.forceMove(get_turf(target))
|
||||
target.adjustStaminaLoss(40)
|
||||
target.Paralyze(5)
|
||||
target.Knockdown(30)
|
||||
target.adjustStaminaLoss(50)
|
||||
target.Paralyze(3) //Otherwise the victim can just instantly get out of the grab.
|
||||
target.DefaultCombatKnockdown(20) //So they cant get up instantly.
|
||||
if(ishuman(target) && iscarbon(user))
|
||||
target.grabbedby(user)
|
||||
|
||||
@@ -205,9 +205,9 @@
|
||||
user.SetKnockdown(0)
|
||||
user.set_resting(FALSE, TRUE, FALSE)
|
||||
user.forceMove(get_turf(target))
|
||||
target.adjustStaminaLoss(40)
|
||||
target.Paralyze(5)
|
||||
target.Knockdown(30)
|
||||
target.adjustStaminaLoss(65)
|
||||
target.Paralyze(10)
|
||||
target.DefaultCombatKnockdown(20)
|
||||
if(ishuman(target) && iscarbon(user))
|
||||
target.grabbedby(user)
|
||||
target.grippedby(user, instant = TRUE)
|
||||
@@ -354,7 +354,7 @@
|
||||
playsound(user, 'sound/effects/blobattack.ogg', 60, TRUE)
|
||||
playsound(user, 'sound/effects/splat.ogg', 70, TRUE)
|
||||
user.emote("scream")
|
||||
user.gain_trauma(/datum/brain_trauma/severe/paralysis/paraplegic) // oopsie indeed!
|
||||
user.gain_trauma(/datum/brain_trauma/severe/paralysis/spinesnapped) // oopsie indeed!
|
||||
shake_camera(user, 7, 7)
|
||||
user.overlay_fullscreen("flash", /obj/screen/fullscreen/flash)
|
||||
user.clear_fullscreen("flash", 4.5)
|
||||
@@ -415,10 +415,10 @@
|
||||
for(var/i = 0, i < speed, i++)
|
||||
var/obj/item/shard/shard = new /obj/item/shard(get_turf(user))
|
||||
//shard.embedding = list(embed_chance = 100, ignore_throwspeed_threshold = TRUE, impact_pain_mult=3, pain_chance=5)
|
||||
//shard.AddElement(/datum/element/embed, shard.embedding)
|
||||
shard.updateEmbedding()
|
||||
user.hitby(shard, skipcatch = TRUE, hitpush = FALSE)
|
||||
//shard.embedding = list()
|
||||
//shard.AddElement(/datum/element/embed, shard.embedding)
|
||||
shard.embedding = list()
|
||||
shard.updateEmbedding()
|
||||
W.obj_destruction()
|
||||
user.adjustStaminaLoss(10 * speed)
|
||||
user.DefaultCombatKnockdown(40)
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
|
||||
/datum/component/tactical
|
||||
var/allowed_slot
|
||||
|
||||
/datum/component/tactical/Initialize(allowed_slot)
|
||||
if(!isitem(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
src.allowed_slot = allowed_slot
|
||||
|
||||
/datum/component/tactical/RegisterWithParent()
|
||||
. = ..()
|
||||
RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/modify)
|
||||
RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/unmodify)
|
||||
|
||||
/datum/component/tactical/UnregisterFromParent()
|
||||
. = ..()
|
||||
UnregisterSignal(parent, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED))
|
||||
unmodify()
|
||||
|
||||
/datum/component/tactical/Destroy()
|
||||
unmodify()
|
||||
return ..()
|
||||
|
||||
/datum/component/tactical/proc/modify(obj/item/source, mob/user, slot)
|
||||
if(allowed_slot && slot != allowed_slot)
|
||||
unmodify()
|
||||
return
|
||||
|
||||
var/obj/item/master = parent
|
||||
var/image/I = image(icon = master.icon, icon_state = master.icon_state, loc = user)
|
||||
I.copy_overlays(master)
|
||||
I.override = TRUE
|
||||
source.add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/everyone, "sneaking_mission", I)
|
||||
I.layer = ABOVE_MOB_LAYER
|
||||
|
||||
/datum/component/tactical/proc/unmodify(obj/item/source, mob/user)
|
||||
var/obj/item/master = source || parent
|
||||
if(!user)
|
||||
if(!ismob(master.loc))
|
||||
return
|
||||
user = master.loc
|
||||
|
||||
user.remove_alt_appearance("sneaking_mission")
|
||||
@@ -0,0 +1,311 @@
|
||||
/**
|
||||
* Two Handed Component
|
||||
*
|
||||
* When applied to an item it will make it two handed
|
||||
*
|
||||
*/
|
||||
/datum/component/two_handed
|
||||
dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS // Only one of the component can exist on an item
|
||||
var/wielded = FALSE /// Are we holding the two handed item properly
|
||||
var/force_multiplier = 0 /// The multiplier applied to force when wielded, does not work with force_wielded, and force_unwielded
|
||||
var/force_wielded = 0 /// The force of the item when weilded
|
||||
var/force_unwielded = 0 /// The force of the item when unweilded
|
||||
var/wieldsound = FALSE /// Play sound when wielded
|
||||
var/unwieldsound = FALSE /// Play sound when unwielded
|
||||
var/attacksound = FALSE /// Play sound on attack when wielded
|
||||
var/require_twohands = FALSE /// Does it have to be held in both hands
|
||||
var/icon_wielded = FALSE /// The icon that will be used when wielded
|
||||
var/obj/item/offhand/offhand_item = null /// Reference to the offhand created for the item
|
||||
var/sharpened_increase = 0 /// The amount of increase recived from sharpening the item
|
||||
|
||||
/**
|
||||
* Two Handed component
|
||||
*
|
||||
* vars:
|
||||
* * require_twohands (optional) Does the item need both hands to be carried
|
||||
* * wieldsound (optional) The sound to play when wielded
|
||||
* * unwieldsound (optional) The sound to play when unwielded
|
||||
* * attacksound (optional) The sound to play when wielded and attacking
|
||||
* * force_multiplier (optional) The force multiplier when wielded, do not use with force_wielded, and force_unwielded
|
||||
* * force_wielded (optional) The force setting when the item is wielded, do not use with force_multiplier
|
||||
* * force_unwielded (optional) The force setting when the item is unwielded, do not use with force_multiplier
|
||||
* * icon_wielded (optional) The icon to be used when wielded
|
||||
*/
|
||||
/datum/component/two_handed/Initialize(require_twohands=FALSE, wieldsound=FALSE, unwieldsound=FALSE, attacksound=FALSE, \
|
||||
force_multiplier=0, force_wielded=0, force_unwielded=0, icon_wielded=FALSE)
|
||||
if(!isitem(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
|
||||
src.require_twohands = require_twohands
|
||||
src.wieldsound = wieldsound
|
||||
src.unwieldsound = unwieldsound
|
||||
src.attacksound = attacksound
|
||||
src.force_multiplier = force_multiplier
|
||||
src.force_wielded = force_wielded
|
||||
src.force_unwielded = force_unwielded
|
||||
src.icon_wielded = icon_wielded
|
||||
|
||||
// Inherit the new values passed to the component
|
||||
/datum/component/two_handed/InheritComponent(datum/component/two_handed/new_comp, original, require_twohands, wieldsound, unwieldsound, \
|
||||
force_multiplier, force_wielded, force_unwielded, icon_wielded)
|
||||
if(!original)
|
||||
return
|
||||
if(require_twohands)
|
||||
src.require_twohands = require_twohands
|
||||
if(wieldsound)
|
||||
src.wieldsound = wieldsound
|
||||
if(unwieldsound)
|
||||
src.unwieldsound = unwieldsound
|
||||
if(attacksound)
|
||||
src.attacksound = attacksound
|
||||
if(force_multiplier)
|
||||
src.force_multiplier = force_multiplier
|
||||
if(force_wielded)
|
||||
src.force_wielded = force_wielded
|
||||
if(force_unwielded)
|
||||
src.force_unwielded = force_unwielded
|
||||
if(icon_wielded)
|
||||
src.icon_wielded = icon_wielded
|
||||
|
||||
// register signals withthe parent item
|
||||
/datum/component/two_handed/RegisterWithParent()
|
||||
RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/on_equip)
|
||||
RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/on_drop)
|
||||
RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, .proc/on_attack_self)
|
||||
RegisterSignal(parent, COMSIG_ITEM_ATTACK, .proc/on_attack)
|
||||
RegisterSignal(parent, COMSIG_ATOM_UPDATE_ICON, .proc/on_update_icon)
|
||||
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/on_moved)
|
||||
RegisterSignal(parent, COMSIG_ITEM_SHARPEN_ACT, .proc/on_sharpen)
|
||||
|
||||
// Remove all siginals registered to the parent item
|
||||
/datum/component/two_handed/UnregisterFromParent()
|
||||
UnregisterSignal(parent, list(COMSIG_ITEM_EQUIPPED,
|
||||
COMSIG_ITEM_DROPPED,
|
||||
COMSIG_ITEM_ATTACK_SELF,
|
||||
COMSIG_ITEM_ATTACK,
|
||||
COMSIG_ATOM_UPDATE_ICON,
|
||||
COMSIG_MOVABLE_MOVED,
|
||||
COMSIG_ITEM_SHARPEN_ACT))
|
||||
|
||||
/// Triggered on equip of the item containing the component
|
||||
/datum/component/two_handed/proc/on_equip(datum/source, mob/user, slot)
|
||||
if(require_twohands && slot == SLOT_HANDS) // force equip the item
|
||||
wield(user)
|
||||
if(!user.is_holding(parent) && wielded && !require_twohands)
|
||||
unwield(user)
|
||||
|
||||
/// Triggered on drop of item containing the component
|
||||
/datum/component/two_handed/proc/on_drop(datum/source, mob/user)
|
||||
if(require_twohands)
|
||||
unwield(user, show_message=TRUE)
|
||||
if(wielded)
|
||||
unwield(user)
|
||||
if(source == offhand_item && !QDELETED(src))
|
||||
qdel(src)
|
||||
|
||||
/// Triggered on attack self of the item containing the component
|
||||
/datum/component/two_handed/proc/on_attack_self(datum/source, mob/user)
|
||||
if(wielded)
|
||||
unwield(user)
|
||||
else
|
||||
wield(user)
|
||||
|
||||
/**
|
||||
* Wield the two handed item in both hands
|
||||
*
|
||||
* vars:
|
||||
* * user The mob/living/carbon that is wielding the item
|
||||
*/
|
||||
/datum/component/two_handed/proc/wield(mob/living/carbon/user)
|
||||
if(wielded)
|
||||
return
|
||||
if(ismonkey(user))
|
||||
to_chat(user, "<span class='warning'>It's too heavy for you to wield fully.</span>")
|
||||
return
|
||||
if(user.get_inactive_held_item())
|
||||
if(require_twohands)
|
||||
to_chat(user, "<span class='notice'>[parent] is too cumbersome to carry in one hand!</span>")
|
||||
user.dropItemToGround(parent, force=TRUE)
|
||||
else
|
||||
to_chat(user, "<span class='warning'>You need your other hand to be empty!</span>")
|
||||
return
|
||||
if(user.get_num_arms() < 2)
|
||||
if(require_twohands)
|
||||
user.dropItemToGround(parent, force=TRUE)
|
||||
to_chat(user, "<span class='warning'>You don't have enough intact hands.</span>")
|
||||
return
|
||||
|
||||
// wield update status
|
||||
if(SEND_SIGNAL(parent, COMSIG_TWOHANDED_WIELD, user) & COMPONENT_TWOHANDED_BLOCK_WIELD)
|
||||
return // blocked wield from item
|
||||
wielded = TRUE
|
||||
RegisterSignal(user, COMSIG_MOB_SWAP_HANDS, .proc/on_swap_hands)
|
||||
|
||||
// update item stats and name
|
||||
var/obj/item/parent_item = parent
|
||||
if(force_multiplier)
|
||||
parent_item.force *= force_multiplier
|
||||
else if(force_wielded)
|
||||
parent_item.force = force_wielded
|
||||
if(sharpened_increase)
|
||||
parent_item.force += sharpened_increase
|
||||
parent_item.name = "[parent_item.name] (Wielded)"
|
||||
parent_item.update_icon()
|
||||
|
||||
if(iscyborg(user))
|
||||
to_chat(user, "<span class='notice'>You dedicate your module to [parent].</span>")
|
||||
else
|
||||
to_chat(user, "<span class='notice'>You grab [parent] with both hands.</span>")
|
||||
|
||||
// Play sound if one is set
|
||||
if(wieldsound)
|
||||
playsound(parent_item.loc, wieldsound, 50, TRUE)
|
||||
|
||||
// Let's reserve the other hand
|
||||
offhand_item = new(user)
|
||||
offhand_item.name = "[parent_item.name] - offhand"
|
||||
offhand_item.desc = "Your second grip on [parent_item]."
|
||||
offhand_item.wielded = TRUE
|
||||
RegisterSignal(offhand_item, COMSIG_ITEM_DROPPED, .proc/on_drop)
|
||||
user.put_in_inactive_hand(offhand_item)
|
||||
|
||||
/**
|
||||
* Unwield the two handed item
|
||||
*
|
||||
* vars:
|
||||
* * user The mob/living/carbon that is unwielding the item
|
||||
* * show_message (option) show a message to chat on unwield
|
||||
*/
|
||||
/datum/component/two_handed/proc/unwield(mob/living/carbon/user, show_message=TRUE)
|
||||
if(!wielded || !user)
|
||||
return
|
||||
|
||||
// wield update status
|
||||
wielded = FALSE
|
||||
UnregisterSignal(user, COMSIG_MOB_SWAP_HANDS)
|
||||
SEND_SIGNAL(parent, COMSIG_TWOHANDED_UNWIELD, user)
|
||||
|
||||
// update item stats
|
||||
var/obj/item/parent_item = parent
|
||||
if(sharpened_increase)
|
||||
parent_item.force -= sharpened_increase
|
||||
if(force_multiplier)
|
||||
parent_item.force /= force_multiplier
|
||||
else if(force_unwielded)
|
||||
parent_item.force = force_unwielded
|
||||
|
||||
// update the items name to remove the wielded status
|
||||
var/sf = findtext(parent_item.name, " (Wielded)", -10) // 10 == length(" (Wielded)")
|
||||
if(sf)
|
||||
parent_item.name = copytext(parent_item.name, 1, sf)
|
||||
else
|
||||
parent_item.name = "[initial(parent_item.name)]"
|
||||
|
||||
// Update icons
|
||||
parent_item.update_icon()
|
||||
if(user.get_item_by_slot(ITEM_SLOT_BACK) == parent)
|
||||
user.update_inv_back()
|
||||
else
|
||||
user.update_inv_hands()
|
||||
|
||||
// if the item requires two handed drop the item on unwield
|
||||
if(require_twohands)
|
||||
user.dropItemToGround(parent, force=TRUE)
|
||||
|
||||
// Show message if requested
|
||||
if(show_message)
|
||||
if(iscyborg(user))
|
||||
to_chat(user, "<span class='notice'>You free up your module.</span>")
|
||||
else if(require_twohands)
|
||||
to_chat(user, "<span class='notice'>You drop [parent].</span>")
|
||||
else
|
||||
to_chat(user, "<span class='notice'>You are now carrying [parent] with one hand.</span>")
|
||||
|
||||
// Play sound if set
|
||||
if(unwieldsound)
|
||||
playsound(parent_item.loc, unwieldsound, 50, TRUE)
|
||||
|
||||
// Remove the object in the offhand
|
||||
if(offhand_item)
|
||||
UnregisterSignal(offhand_item, COMSIG_ITEM_DROPPED)
|
||||
qdel(offhand_item)
|
||||
// Clear any old refrence to an item that should be gone now
|
||||
offhand_item = null
|
||||
|
||||
/**
|
||||
* on_attack triggers on attack with the parent item
|
||||
*/
|
||||
/datum/component/two_handed/proc/on_attack(obj/item/source, mob/living/target, mob/living/user)
|
||||
if(wielded && attacksound)
|
||||
var/obj/item/parent_item = parent
|
||||
playsound(parent_item.loc, attacksound, 50, TRUE)
|
||||
|
||||
/**
|
||||
* on_update_icon triggers on call to update parent items icon
|
||||
*
|
||||
* Updates the icon using icon_wielded if set
|
||||
*/
|
||||
/datum/component/two_handed/proc/on_update_icon(datum/source)
|
||||
if(icon_wielded && wielded)
|
||||
var/obj/item/parent_item = parent
|
||||
if(parent_item)
|
||||
parent_item.icon_state = icon_wielded
|
||||
return COMSIG_ATOM_NO_UPDATE_ICON_STATE
|
||||
|
||||
/**
|
||||
* on_moved Triggers on item moved
|
||||
*/
|
||||
/datum/component/two_handed/proc/on_moved(datum/source, mob/user, dir)
|
||||
unwield(user)
|
||||
|
||||
/**
|
||||
* on_swap_hands Triggers on swapping hands, blocks swap if the other hand is busy
|
||||
*/
|
||||
/datum/component/two_handed/proc/on_swap_hands(mob/user, obj/item/held_item)
|
||||
if(!held_item)
|
||||
return
|
||||
if(held_item == parent)
|
||||
return COMPONENT_BLOCK_SWAP
|
||||
|
||||
/**
|
||||
* on_sharpen Triggers on usage of a sharpening stone on the item
|
||||
*/
|
||||
/datum/component/two_handed/proc/on_sharpen(obj/item/item, amount, max_amount)
|
||||
if(!item)
|
||||
return COMPONENT_BLOCK_SHARPEN_BLOCKED
|
||||
if(sharpened_increase)
|
||||
return COMPONENT_BLOCK_SHARPEN_ALREADY
|
||||
var/wielded_val = 0
|
||||
if(force_multiplier)
|
||||
var/obj/item/parent_item = parent
|
||||
if(wielded)
|
||||
wielded_val = parent_item.force
|
||||
else
|
||||
wielded_val = parent_item.force * force_multiplier
|
||||
else
|
||||
wielded_val = force_wielded
|
||||
if(wielded_val > max_amount)
|
||||
return COMPONENT_BLOCK_SHARPEN_MAXED
|
||||
sharpened_increase = min(amount, (max_amount - wielded_val))
|
||||
return COMPONENT_BLOCK_SHARPEN_APPLIED
|
||||
|
||||
/**
|
||||
* The offhand dummy item for two handed items
|
||||
*
|
||||
*/
|
||||
/obj/item/offhand
|
||||
name = "offhand"
|
||||
icon_state = "offhand"
|
||||
w_class = WEIGHT_CLASS_HUGE
|
||||
item_flags = ABSTRACT
|
||||
resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
|
||||
var/wielded = FALSE // Off Hand tracking of wielded status
|
||||
|
||||
/obj/item/offhand/Destroy()
|
||||
wielded = FALSE
|
||||
return ..()
|
||||
|
||||
/obj/item/offhand/equipped(mob/user, slot)
|
||||
. = ..()
|
||||
if(wielded && !user.is_holding(src) && !QDELETED(src))
|
||||
qdel(src)
|
||||
Reference in New Issue
Block a user