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

This commit is contained in:
keronshb
2022-02-22 17:10:25 -05:00
1045 changed files with 68632 additions and 58299 deletions
+1
View File
@@ -224,6 +224,7 @@
else
to_chat(owner, "<span class='userdanger'>You feel your heart lurching in your chest...</span>")
owner.adjustOxyLoss(8)
else
/datum/brain_trauma/severe/discoordination
name = "Discoordination"
+62 -35
View File
@@ -4,7 +4,7 @@
var/window_id // window_id is used as the window name for browse and onclose
var/width = 0
var/height = 0
var/atom/ref = null
var/datum/weakref/ref = null
var/window_options = "can_close=1;can_minimize=1;can_maximize=0;can_resize=1;titlebar=1;" // window option is set using window_id
var/stylesheets[0]
var/scripts[0]
@@ -16,8 +16,8 @@
/datum/browser/New(nuser, nwindow_id, ntitle = 0, nwidth = 0, nheight = 0, atom/nref = null)
user = nuser
RegisterSignal(user, COMSIG_PARENT_QDELETING, .proc/user_deleted)
window_id = nwindow_id
if (ntitle)
title = format_text(ntitle)
@@ -26,7 +26,11 @@
if (nheight)
height = nheight
if (nref)
ref = nref
ref = WEAKREF(nref)
/datum/browser/proc/user_deleted(datum/source)
SIGNAL_HANDLER
user = null
/datum/browser/proc/add_head_content(nhead_content)
head_content = nhead_content
@@ -35,7 +39,7 @@
window_options = nwindow_options
/datum/browser/proc/add_stylesheet(name, file)
if(istype(name, /datum/asset/spritesheet))
if (istype(name, /datum/asset/spritesheet))
var/datum/asset/spritesheet/sheet = name
stylesheets["spritesheet_[sheet.name].css"] = "data/spritesheets/[sheet.name]"
else
@@ -94,27 +98,32 @@
"}
/datum/browser/proc/open(use_onclose = TRUE)
if(isnull(window_id)) //null check because this can potentially nuke goonchat
if(isnull(window_id)) //null check because this can potentially nuke goonchat
WARNING("Browser [title] tried to open with a null ID")
to_chat(user, "<span class='userdanger'>The [title] browser you tried to open failed a sanity check! Please report this on github!</span>")
to_chat(user, span_userdanger("The [title] browser you tried to open failed a sanity check! Please report this on github!"))
return
var/window_size = ""
if(width && height)
if (width && height)
window_size = "size=[width]x[height];"
common_asset.send(user)
if(stylesheets.len)
if (stylesheets.len)
SSassets.transport.send_assets(user, stylesheets)
if(scripts.len)
if (scripts.len)
SSassets.transport.send_assets(user, scripts)
user << browse(get_content(), "window=[window_id];[window_size][window_options]")
if(use_onclose)
if (use_onclose)
setup_onclose()
/datum/browser/proc/setup_onclose()
set waitfor = 0 //winexists sleeps, so we don't need to.
for (var/i in 1 to 10)
if (user && winexists(user, window_id))
onclose(user, window_id, ref)
if (user?.client && winexists(user, window_id))
var/atom/send_ref
if(ref)
send_ref = ref.resolve()
if(!send_ref)
ref = null
onclose(user, window_id, send_ref)
break
/datum/browser/proc/close()
@@ -153,11 +162,35 @@
opentime = 0
close()
//designed as a drop in replacement for alert(); functions the same. (outside of needing User specified)
/proc/tgalert(mob/User, Message, Title, Button1="Ok", Button2, Button3, StealFocus = 1, Timeout = 6000)
/**
* **DEPRECATED: USE tgui_alert(...) INSTEAD**
*
* Designed as a drop in replacement for alert(); functions the same. (outside of needing User specified)
* Arguments:
* * User - The user to show the alert to.
* * Message - The textual body of the alert.
* * Title - The title of the alert's window.
* * Button1 - The first button option.
* * Button2 - The second button option.
* * Button3 - The third button option.
* * StealFocus - Boolean operator controlling if the alert will steal the user's window focus.
* * Timeout - The timeout of the window, after which no responses will be valid.
*/
/proc/tgalert(mob/User, Message, Title, Button1="Ok", Button2, Button3, StealFocus = TRUE, Timeout = 6000)
if (!User)
User = usr
switch(askuser(User, Message, Title, Button1, Button2, Button3, StealFocus, Timeout))
if (!istype(User))
if (istype(User, /client))
var/client/client = User
User = client.mob
else
return
// Get user's response using a modal
var/datum/browser/modal/alert/A = new(User, Message, Title, Button1, Button2, Button3, StealFocus, Timeout)
A.open()
A.wait()
switch(A.selectedbutton)
if (1)
return Button1
if (2)
@@ -197,8 +230,8 @@
.=..()
opentime = 0
/datum/browser/modal/open(use_onclose = 1)
set waitfor = 0
/datum/browser/modal/open(use_onclose)
set waitfor = FALSE
opentime = world.time
if (stealfocus)
@@ -277,7 +310,7 @@
opentime = 0
close()
/proc/presentpicker(var/mob/User,Message, Title, Button1="Ok", Button2, Button3, StealFocus = 1,Timeout = 6000,list/values, inputtype = "checkbox", width, height, slidecolor)
/proc/presentpicker(mob/User,Message, Title, Button1="Ok", Button2, Button3, StealFocus = 1,Timeout = 6000,list/values, inputtype = "checkbox", width, height, slidecolor)
if (!istype(User))
if (istype(User, /client/))
var/client/C = User
@@ -290,7 +323,7 @@
if (A.selectedbutton)
return list("button" = A.selectedbutton, "values" = A.valueslist)
/proc/input_bitfield(var/mob/User, title, bitfield, current_value, nwidth = 350, nheight = 350, nslidecolor, allowed_edit_list = null)
/proc/input_bitfield(mob/User, title, bitfield, current_value, nwidth = 350, nheight = 350, nslidecolor, allowed_edit_list = null)
if (!User || !(bitfield in GLOB.bitfields))
return
var/list/pickerlist = list()
@@ -401,7 +434,7 @@
opentime = 0
close()
/proc/presentpreflikepicker(var/mob/User,Message, Title, Button1="Ok", Button2, Button3, StealFocus = 1,Timeout = 6000,list/settings, width, height, slidecolor)
/proc/presentpreflikepicker(mob/User,Message, Title, Button1="Ok", Button2, Button3, StealFocus = 1,Timeout = 6000,list/settings, width, height, slidecolor)
if (!istype(User))
if (istype(User, /client/))
var/client/C = User
@@ -414,12 +447,6 @@
if (A.selectedbutton)
return list("button" = A.selectedbutton, "settings" = A.settings)
// This will allow you to show an icon in the browse window
// This is added to mob so that it can be used without a reference to the browser object
// There is probably a better place for this...
/mob/proc/browse_rsc_icon(icon, icon_state, dir = -1)
// Registers the on-close verb for a browse window (client/verb/.windowclose)
// this will be called when the close-button of a window is pressed.
//
@@ -427,8 +454,8 @@
// e.g. canisters, timers, etc.
//
// windowid should be the specified window name
// e.g. code is : user << browse(text, "window=fred")
// then use : onclose(user, "fred")
// e.g. code is : user << browse(text, "window=fred")
// then use : onclose(user, "fred")
//
// Optionally, specify the "ref" parameter as the controlled atom (usually src)
// to pass a "close=1" parameter to the atom's Topic() proc for special handling.
@@ -451,18 +478,18 @@
// otherwise, just reset the client mob's machine var.
//
/client/verb/windowclose(atomref as text)
set hidden = TRUE // hide this verb from the user's panel
set name = ".windowclose" // no autocomplete on cmd line
set hidden = TRUE // hide this verb from the user's panel
set name = ".windowclose" // no autocomplete on cmd line
if(atomref!="null") // if passed a real atomref
var/hsrc = locate(atomref) // find the reffed atom
if(atomref!="null") // if passed a real atomref
var/hsrc = locate(atomref) // find the reffed atom
var/href = "close=1"
if(hsrc)
usr = src.mob
src.Topic(href, params2list(href), hsrc) // this will direct to the atom's
return // Topic() proc via client.Topic()
src.Topic(href, params2list(href), hsrc) // this will direct to the atom's
return // Topic() proc via client.Topic()
// no atomref specified (or not found)
// so just reset the user mob's machine var
if(src && src.mob)
if(src?.mob)
src.mob.unset_machine()
+32
View File
@@ -0,0 +1,32 @@
/datum/changelog
var/static/list/changelog_items = list()
/datum/changelog/ui_state()
return GLOB.always_state
/datum/changelog/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if (!ui)
ui = new(user, src, "Changelog")
ui.open()
/datum/changelog/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(.)
return
if(action == "get_month")
var/datum/asset/changelog_item/changelog_item = changelog_items[params["date"]]
if (!changelog_item)
changelog_item = new /datum/asset/changelog_item(params["date"])
changelog_items[params["date"]] = changelog_item
return ui.send_asset(changelog_item)
/datum/changelog/ui_static_data()
var/list/data = list( "dates" = list() )
var/regex/ymlRegex = regex(@"\.yml", "g")
for(var/archive_file in flist("[global.config.directory]/../html/changelogs/archive/"))
var/archive_date = ymlRegex.Replace(archive_file, "")
data["dates"] = list(archive_date) + data["dates"]
return data
+2 -4
View File
@@ -18,8 +18,6 @@
#define CHAT_LAYER_Z_STEP 0.0001
/// The number of z-layer 'slices' usable by the chat message layering
#define CHAT_LAYER_MAX_Z (CHAT_LAYER_MAX - CHAT_LAYER) / CHAT_LAYER_Z_STEP
/// Macro from Lummox used to get height from a MeasureText proc
#define WXH_TO_HEIGHT(x) text2num(copytext(x, findtextEx(x, "x") + 1))
/**
* # Chat Message Overlay
@@ -125,7 +123,7 @@
// Append radio icon if from a virtual speaker
if (extra_classes.Find("virtual-speaker"))
var/image/r_icon = image('icons/UI_Icons/chat/chat_icons.dmi', icon_state = "radio")
var/image/r_icon = image('icons/ui_icons/chat/chat_icons.dmi', icon_state = "radio")
text = "\icon[r_icon]&nbsp;" + text
// We dim italicized text to make it more distinguishable from regular text
@@ -165,7 +163,7 @@
message.maptext_width = CHAT_MESSAGE_WIDTH
message.maptext_height = mheight
message.maptext_x = (CHAT_MESSAGE_WIDTH - owner.bound_width) * -0.5
message.maptext = complete_text
message.maptext = MAPTEXT(complete_text)
// View the message
LAZYADDASSOC(owned_by.seen_messages, message_loc, src)
+1 -1
View File
@@ -101,7 +101,7 @@
if(!C)
return
watching += C
M.overlay_fullscreen("cinematic",/atom/movable/screen/fullscreen/cinematic_backdrop)
M.overlay_fullscreen("cinematic",/atom/movable/screen/fullscreen/tiled/cinematic_backdrop)
C.screen += screen
//Sound helper
@@ -0,0 +1,77 @@
///Allows you to set a theme for a set of areas without tying them to looping sounds explicitly
/datum/component/area_sound_manager
///area -> looping sound type
var/list/area_to_looping_type = list()
///Current sound loop
var/datum/looping_sound/our_loop
///A list of "acceptable" z levels to be on. If you leave this, we're gonna delete ourselves
var/list/accepted_zs
///The timer id of our current start delay, if it exists
var/timerid
/datum/component/area_sound_manager/Initialize(area_loop_pairs, change_on, remove_on, acceptable_zs)
if(!ismovable(parent))
return
area_to_looping_type = area_loop_pairs
accepted_zs = acceptable_zs
change_the_track()
RegisterSignal(parent, COMSIG_MOVABLE_MOVED, .proc/react_to_move)
RegisterSignal(parent, COMSIG_MOVABLE_Z_CHANGED, .proc/react_to_z_move)
RegisterSignal(parent, change_on, .proc/handle_change)
RegisterSignal(parent, remove_on, .proc/handle_removal)
/datum/component/area_sound_manager/Destroy(force, silent)
QDEL_NULL(our_loop)
. = ..()
/datum/component/area_sound_manager/proc/react_to_move(datum/source, atom/oldloc, dir, forced)
SIGNAL_HANDLER
var/list/loop_lookup = area_to_looping_type
if(loop_lookup[get_area(oldloc)] == loop_lookup[get_area(parent)])
return
change_the_track(TRUE)
/datum/component/area_sound_manager/proc/react_to_z_move(datum/source, old_z, new_z)
SIGNAL_HANDLER
if(!length(accepted_zs) || (new_z in accepted_zs))
return
qdel(src)
/datum/component/area_sound_manager/proc/handle_removal(datum/source)
SIGNAL_HANDLER
qdel(src)
/datum/component/area_sound_manager/proc/handle_change(datum/source)
SIGNAL_HANDLER
change_the_track()
/datum/component/area_sound_manager/proc/change_the_track(skip_start = FALSE)
var/time_remaining = 0
if(our_loop)
var/our_id = our_loop.timerid || timerid
if(our_id)
time_remaining = timeleft(our_id, SSsound_loops) || 0
//Time left will sometimes return negative values, just ignore them and start a new sound loop now
time_remaining = max(time_remaining, 0)
QDEL_NULL(our_loop)
var/area/our_area = get_area(parent)
var/new_loop_type = area_to_looping_type[our_area]
if(!new_loop_type)
return
our_loop = new new_loop_type(parent, FALSE, TRUE, skip_start)
//If we're still playing, wait a bit before changing the sound so we don't double up
if(time_remaining)
timerid = addtimer(CALLBACK(src, .proc/start_looping_sound), time_remaining, TIMER_UNIQUE | TIMER_CLIENT_TIME | TIMER_STOPPABLE | TIMER_NO_HASH_WAIT | TIMER_DELETE_ME, SSsound_loops)
return
timerid = null
our_loop.start()
/datum/component/area_sound_manager/proc/start_looping_sound()
timerid = null
if(our_loop)
our_loop.start()
@@ -66,7 +66,7 @@
/datum/component/storage/concrete/_insert_physical_item(obj/item/I, override = FALSE)
. = TRUE
var/atom/real_location = real_location()
if(I.loc != real_location && real_location)
if(real_location && I.loc != real_location)
I.forceMove(real_location)
refresh_mob_views()
@@ -95,16 +95,19 @@
return FALSE
/datum/component/storage/concrete/proc/on_contents_del(datum/source, atom/A)
SIGNAL_HANDLER
var/atom/real_location = parent
if(A in real_location)
usr = null
remove_from_storage(A, null)
/datum/component/storage/concrete/proc/on_deconstruct(datum/source, disassembled)
SIGNAL_HANDLER
if(drop_all_on_deconstruct)
do_quick_empty()
/datum/component/storage/concrete/proc/on_break(datum/source, damage_flag)
SIGNAL_HANDLER
if(drop_all_on_break)
do_quick_empty()
if(unlock_on_break)
@@ -131,14 +134,16 @@
var/list/seeing_mobs = can_see_contents()
for(var/mob/M in seeing_mobs)
M.client.screen -= AM
if(ismob(parent.loc) && isitem(AM))
var/obj/item/I = AM
var/mob/M = parent.loc
I.dropped(M)
I.item_flags &= ~IN_STORAGE
I.remove_outline()
if(isitem(AM))
var/obj/item/removed_item = AM
removed_item.item_flags &= ~IN_STORAGE
if(ismob(parent.loc))
var/mob/carrying_mob = parent.loc
removed_item.dropped(carrying_mob, TRUE)
if(new_location)
AM.forceMove(new_location) // exited comsig will handle removal reset.
//Reset the items values
_removal_reset(AM)
AM.forceMove(new_location)
//We don't want to call this if the item is being destroyed
AM.on_exit_storage(src)
else
@@ -147,7 +152,7 @@
refresh_mob_views()
if(isobj(parent))
var/obj/O = parent
O.update_icon()
O.update_appearance()
return TRUE
/datum/component/storage/concrete/proc/slave_can_insert_object(datum/component/storage/slave, obj/item/I, stop_messages = FALSE, mob/M)
@@ -158,7 +163,7 @@
if(. && !prevent_warning)
slave.mob_item_insertion_feedback(usr, M, I)
/datum/component/storage/concrete/handle_item_insertion(obj/item/I, prevent_warning = FALSE, mob/M, datum/component/storage/remote) //Remote is null or the slave datum
/datum/component/storage/concrete/handle_item_insertion(obj/item/I, prevent_warning = FALSE, mob/M, datum/component/storage/remote) //Remote is null or the slave datum
var/datum/component/storage/concrete/master = master()
var/atom/parent = src.parent
var/moved = FALSE
@@ -168,7 +173,7 @@
if(!M.temporarilyRemoveItemFromInventory(I))
return FALSE
else
moved = TRUE //At this point if the proc fails we need to manually move the object back to the turf/mob/whatever.
moved = TRUE //At this point if the proc fails we need to manually move the object back to the turf/mob/whatever.
if(I.pulledby)
I.pulledby.stop_pulling()
if(silent)
@@ -203,7 +208,7 @@
/datum/component/storage/concrete/update_icon()
if(isobj(parent))
var/obj/O = parent
O.update_icon()
O.update_appearance()
for(var/i in slaves)
var/datum/component/storage/slave = i
slave.update_icon()
+29 -29
View File
@@ -418,38 +418,38 @@
return TRUE
/datum/component/storage/proc/mousedrop_onto(datum/source, atom/over_object, mob/M)
SIGNAL_HANDLER
set waitfor = FALSE
. = COMPONENT_NO_MOUSEDROP
if(!ismob(M))
return
if(!over_object)
return
if(ismecha(M.loc)) // stops inventory actions in a mech
return
if(M.incapacitated() || !M.canUseStorage())
return
var/atom/A = parent
if(ismob(M)) //all the check for item manipulation are in other places, you can safely open any storages as anything and its not buggy, i checked
A.add_fingerprint(M)
if(istype(A, /obj/item))
var/obj/item/I = A
I.remove_outline() //Removes the outline when we drag
if(!over_object)
return FALSE
if(ismecha(M.loc)) // stops inventory actions in a mech
return FALSE
// this must come before the screen objects only block, dunno why it wasn't before
if(over_object == M)
user_show_to_mob(M, trigger_on_found = TRUE)
return
if(isrevenant(M))
RevenantThrow(over_object, M, source)
return
if(!M.incapacitated())
if(!istype(over_object, /atom/movable/screen))
dump_content_at(over_object, M)
return
if(A.loc != M)
return
playsound(A, "rustle", 50, 1, -5)
A.do_jiggle()
if(istype(over_object, /atom/movable/screen/inventory/hand))
var/atom/movable/screen/inventory/hand/H = over_object
M.putItemFromInventoryInHandIfPossible(A, H.held_index)
return
A.add_fingerprint(M)
A.add_fingerprint(M)
// this must come before the screen objects only block, dunno why it wasn't before
if(over_object == M)
user_show_to_mob(M, trigger_on_found = TRUE)
if(isrevenant(M))
INVOKE_ASYNC(GLOBAL_PROC, .proc/RevenantThrow, over_object, M, source)
return
if(!istype(over_object, /atom/movable/screen))
INVOKE_ASYNC(src, .proc/dump_content_at, over_object, M)
return
if(A.loc != M)
return
playsound(A, "rustle", 50, TRUE, -5)
A.do_jiggle()
if(istype(over_object, /atom/movable/screen/inventory/hand))
var/atom/movable/screen/inventory/hand/H = over_object
M.putItemFromInventoryInHandIfPossible(A, H.held_index)
return
A.add_fingerprint(M)
/datum/component/storage/proc/user_show_to_mob(mob/M, force = FALSE, trigger_on_found = FALSE)
var/atom/A = parent
+3 -3
View File
@@ -370,7 +370,7 @@
user.emote("scream")
user.gain_trauma(/datum/brain_trauma/severe/paralysis/spinesnapped) // oopsie indeed!
shake_camera(user, 7, 7)
user.overlay_fullscreen("flash", /atom/movable/screen/fullscreen/flash)
user.overlay_fullscreen("flash", /atom/movable/screen/fullscreen/tiled/flash)
user.clear_fullscreen("flash", 4.5)
if(94 to 98)
@@ -381,7 +381,7 @@
user.gain_trauma_type(BRAIN_TRAUMA_MILD)
user.playsound_local(get_turf(user), 'sound/weapons/flashbang.ogg', 100, TRUE, 8, 0.9)
shake_camera(user, 6, 6)
user.overlay_fullscreen("flash", /atom/movable/screen/fullscreen/flash)
user.overlay_fullscreen("flash", /atom/movable/screen/fullscreen/tiled/flash)
user.clear_fullscreen("flash", 3.5)
if(84 to 93)
@@ -394,7 +394,7 @@
user.playsound_local(get_turf(user), 'sound/weapons/flashbang.ogg', 100, TRUE, 8, 0.9)
user.DefaultCombatKnockdown(40)
shake_camera(user, 5, 5)
user.overlay_fullscreen("flash", /atom/movable/screen/fullscreen/flash)
user.overlay_fullscreen("flash", /atom/movable/screen/fullscreen/tiled/flash)
user.clear_fullscreen("flash", 2.5)
if(64 to 83)
+92 -58
View File
@@ -1,4 +1,5 @@
GLOBAL_LIST_EMPTY(uplinks)
#define PEN_ROTATIONS 2
/**
* Uplinks
@@ -17,7 +18,7 @@ GLOBAL_LIST_EMPTY(uplinks)
var/telecrystals
var/selected_cat
var/owner = null
var/datum/game_mode/gamemode
var/uplink_flag
var/datum/uplink_purchase_log/purchase_log
var/list/uplink_items
var/hidden_crystals = 0
@@ -26,11 +27,11 @@ GLOBAL_LIST_EMPTY(uplinks)
var/failsafe_code
var/compact_mode = FALSE
var/debug = FALSE
var/saved_player_population = 0
var/list/filters = list()
///Instructions on how to access the uplink based on location
var/unlock_text
var/list/previous_attempts
/datum/component/uplink/Initialize(_owner, _lockable = TRUE, _enabled = FALSE, datum/game_mode/_gamemode, starting_tc = 20, datum/traitor_class/traitor_class)
/datum/component/uplink/Initialize(_owner, _lockable = TRUE, _enabled = FALSE, uplink_flag = UPLINK_TRAITORS, starting_tc = TELECRYSTALS_DEFAULT)
if(!isitem(parent))
return COMPONENT_INCOMPATIBLE
@@ -44,16 +45,13 @@ GLOBAL_LIST_EMPTY(uplinks)
RegisterSignal(parent, COMSIG_IMPLANT_EXISTING_UPLINK, .proc/new_implant)
else if(istype(parent, /obj/item/pda))
RegisterSignal(parent, COMSIG_PDA_CHANGE_RINGTONE, .proc/new_ringtone)
// RegisterSignal(parent, COMSIG_PDA_CHECK_DETONATE, .proc/check_detonate)
else if(istype(parent, /obj/item/radio))
RegisterSignal(parent, COMSIG_RADIO_NEW_FREQUENCY, .proc/new_frequency)
else if(istype(parent, /obj/item/pen))
RegisterSignal(parent, COMSIG_PEN_ROTATED, .proc/pen_rotation)
GLOB.uplinks += src
if(istype(traitor_class))
filters = traitor_class.uplink_filters
starting_tc = traitor_class.TC
uplink_items = get_uplink_items(gamemode, TRUE, allow_restricted, filters)
GLOB.uplinks |= src
if(_owner)
owner = _owner
@@ -64,44 +62,58 @@ GLOBAL_LIST_EMPTY(uplinks)
purchase_log = new(owner, src)
lockable = _lockable
active = _enabled
gamemode = _gamemode
src.uplink_flag = uplink_flag
update_items()
telecrystals = starting_tc
if(!lockable)
active = TRUE
locked = FALSE
saved_player_population = GLOB.joined_player_list.len
previous_attempts = list()
/datum/component/uplink/InheritComponent(datum/component/uplink/U)
lockable |= U.lockable
active |= U.active
if(!gamemode)
gamemode = U.gamemode
uplink_flag |= U.uplink_flag
telecrystals += U.telecrystals
if(purchase_log && U.purchase_log)
purchase_log.MergeWithAndDel(U.purchase_log)
/datum/component/uplink/Destroy()
GLOB.uplinks -= src
gamemode = null
purchase_log = null
return ..()
/datum/component/uplink/proc/update_items()
var/updated_items
updated_items = get_uplink_items(uplink_flag, TRUE, allow_restricted)
update_sales(updated_items)
uplink_items = updated_items
/datum/component/uplink/proc/update_sales(updated_items)
var/discount_categories = list("Discounted Gear", "Discounted Team Gear", "Limited Stock Team Gear")
if (uplink_items == null)
return
for (var/category in discount_categories) // Makes sure discounted items aren't renewed or replaced
if (uplink_items[category] != null && updated_items[category] != null)
updated_items[category] = uplink_items[category]
/datum/component/uplink/proc/LoadTC(mob/user, obj/item/stack/telecrystal/TC, silent = FALSE)
if(!silent)
to_chat(user, "<span class='notice'>You slot [TC] into [parent] and charge its internal uplink.</span>")
to_chat(user, span_notice("You slot [TC] into [parent] and charge its internal uplink."))
var/amt = TC.amount
telecrystals += amt
TC.use(amt)
/datum/component/uplink/proc/set_gamemode(_gamemode)
gamemode = _gamemode
uplink_items = get_uplink_items(gamemode, TRUE, allow_restricted)
// log_uplink("[key_name(user)] loaded [amt] telecrystals into [parent]'s uplink")
/datum/component/uplink/proc/OnAttackBy(datum/source, obj/item/I, mob/user)
SIGNAL_HANDLER
if(!active)
return //no hitting everyone/everything just to try to slot tcs in!
return //no hitting everyone/everything just to try to slot tcs in!
if(istype(I, /obj/item/stack/telecrystal))
LoadTC(user, I)
// CIT SPECIFIC: STEALING from unlocked uplink.
if(active)
if(I.GetComponent(/datum/component/uplink))
var/datum/component/uplink/hidden_uplink = I.GetComponent(/datum/component/uplink)
@@ -118,31 +130,26 @@ GLOBAL_LIST_EMPTY(uplinks)
var/cost = UI.refund_amount || UI.cost
if(I.type == path && UI.refundable && I.check_uplink_validity())
telecrystals += cost
purchase_log.total_spent -= cost
to_chat(user, "<span class='notice'>[I] refunded.</span>")
// log_uplink("[key_name(user)] refunded [UI] for [cost] telecrystals using [parent]'s uplink")
if(purchase_log)
purchase_log.total_spent -= cost
to_chat(user, span_notice("[I] refunded."))
qdel(I)
return
/datum/component/uplink/proc/interact(datum/source, mob/user)
SIGNAL_HANDLER
if(locked)
return
active = TRUE
update_items()
if(user)
//update the saved population
var/previous_player_population = saved_player_population
saved_player_population = GLOB.joined_player_list.len
//if population has changed, update uplink items
if(saved_player_population != previous_player_population)
//make sure discounts are not rerolled
var/old_discounts = uplink_items["Discounted Gear"]
uplink_items = get_uplink_items(gamemode, FALSE, allow_restricted, filters)
if(old_discounts)
uplink_items["Discounted Gear"] = old_discounts
ui_interact(user)
INVOKE_ASYNC(src, .proc/ui_interact, user)
// an unlocked uplink blocks also opening the PDA or headset menu
return COMPONENT_NO_INTERACT
/datum/component/uplink/ui_state(mob/user)
if(istype(parent, /obj/item/implant/uplink))
return GLOB.not_incapacitated_state
@@ -178,15 +185,10 @@ GLOBAL_LIST_EMPTY(uplinks)
var/datum/uplink_item/I = uplink_items[category][item]
if(I.limited_stock == 0)
continue
if(I.restricted_roles.len)
var/is_inaccessible = TRUE
for(var/R in I.restricted_roles)
if(R == user.mind.assigned_role || debug)
is_inaccessible = FALSE
if(is_inaccessible)
if(length(I.restricted_roles))
if(!debug && !(user.mind.assigned_role in I.restricted_roles))
continue
/*
if(I.restricted_species) //catpeople specfic gloves.
if(I.restricted_species)
if(ishuman(user))
var/is_inaccessible = TRUE
var/mob/living/carbon/human/H = user
@@ -196,7 +198,6 @@ GLOBAL_LIST_EMPTY(uplinks)
break
if(is_inaccessible)
continue
*/
cat["items"] += list(list(
"name" = I.name,
"cost" = I.cost,
@@ -255,25 +256,41 @@ GLOBAL_LIST_EMPTY(uplinks)
// Implant signal responses
/datum/component/uplink/proc/implant_activation()
SIGNAL_HANDLER
var/obj/item/implant/implant = parent
locked = FALSE
interact(null, implant.imp_in)
/datum/component/uplink/proc/implanting(datum/source, list/arguments)
SIGNAL_HANDLER
var/mob/user = arguments[2]
owner = "[user.key]"
owner = user?.key
if(owner && !purchase_log)
LAZYINITLIST(GLOB.uplink_purchase_logs_by_key)
if(GLOB.uplink_purchase_logs_by_key[owner])
purchase_log = GLOB.uplink_purchase_logs_by_key[owner]
else
purchase_log = new(owner, src)
/datum/component/uplink/proc/old_implant(datum/source, list/arguments, obj/item/implant/new_implant)
SIGNAL_HANDLER
// It kinda has to be weird like this until implants are components
return SEND_SIGNAL(new_implant, COMSIG_IMPLANT_EXISTING_UPLINK, src)
/datum/component/uplink/proc/new_implant(datum/source, datum/component/uplink/uplink)
SIGNAL_HANDLER
uplink.telecrystals += telecrystals
return COMPONENT_DELETE_NEW_IMPLANT
// PDA signal responses
/datum/component/uplink/proc/new_ringtone(datum/source, mob/living/user, new_ring_text)
SIGNAL_HANDLER
var/obj/item/pda/master = parent
if(trim(lowertext(new_ring_text)) != trim(lowertext(unlock_code)))
if(trim(lowertext(new_ring_text)) == trim(lowertext(failsafe_code)))
@@ -282,14 +299,21 @@ GLOBAL_LIST_EMPTY(uplinks)
return
locked = FALSE
interact(null, user)
to_chat(user, "<span class='hear'>The PDA softly beeps.</span>")
to_chat(user, span_hear("The PDA softly beeps."))
user << browse(null, "window=pda")
master.mode = 0
return COMPONENT_STOP_RINGTONE_CHANGE
/datum/component/uplink/proc/check_detonate()
SIGNAL_HANDLER
// return COMPONENT_PDA_NO_DETONATE
// Radio signal responses
/datum/component/uplink/proc/new_frequency(datum/source, list/arguments)
SIGNAL_HANDLER
var/obj/item/radio/master = parent
var/frequency = arguments[1]
if(frequency != unlock_code)
@@ -303,15 +327,22 @@ GLOBAL_LIST_EMPTY(uplinks)
// Pen signal responses
/datum/component/uplink/proc/pen_rotation(datum/source, degrees, mob/living/carbon/user)
SIGNAL_HANDLER
var/obj/item/pen/master = parent
if(degrees != unlock_code)
if(degrees == failsafe_code) //Getting failsafes on pens is risky business
failsafe()
return
locked = FALSE
master.degrees = 0
interact(null, user)
to_chat(user, "<span class='warning'>Your pen makes a clicking noise, before quickly rotating back to 0 degrees!</span>")
previous_attempts += degrees
if(length(previous_attempts) > PEN_ROTATIONS)
popleft(previous_attempts)
if(compare_list(previous_attempts, unlock_code))
locked = FALSE
previous_attempts.Cut()
master.degrees = 0
interact(null, user)
to_chat(user, span_warning("Your pen makes a clicking noise, before quickly rotating back to 0 degrees!"))
else if(compare_list(previous_attempts, failsafe_code))
failsafe(user)
/datum/component/uplink/proc/setup_unlock_code()
unlock_code = generate_code()
@@ -321,15 +352,18 @@ GLOBAL_LIST_EMPTY(uplinks)
else if(istype(parent,/obj/item/radio))
unlock_note = "<B>Radio Frequency:</B> [format_frequency(unlock_code)] ([P.name])."
else if(istype(parent,/obj/item/pen))
unlock_note = "<B>Uplink Degrees:</B> [unlock_code] ([P.name])."
unlock_note = "<B>Uplink Degrees:</B> [english_list(unlock_code)] ([P.name])."
/datum/component/uplink/proc/generate_code()
if(istype(parent,/obj/item/pda))
return "[rand(100,999)] [pick(GLOB.phonetic_alphabet)]"
else if(istype(parent,/obj/item/radio))
return sanitize_frequency(rand(MIN_FREQ, MAX_FREQ))
return return_unused_frequency()
else if(istype(parent,/obj/item/pen))
return rand(1, 360)
var/list/L = list()
for(var/i in 1 to PEN_ROTATIONS)
L += rand(1, 360)
return L
/datum/component/uplink/proc/failsafe(mob/living/carbon/user)
if(!parent)
@@ -339,5 +373,5 @@ GLOBAL_LIST_EMPTY(uplinks)
return
message_admins("[ADMIN_LOOKUPFLW(user)] has triggered an uplink failsafe explosion at [AREACOORD(T)] The owner of the uplink was [ADMIN_LOOKUPFLW(owner)].")
log_game("[key_name(user)] triggered an uplink failsafe explosion. The owner of the uplink was [key_name(owner)].")
explosion(T,1,2,3)
explosion(parent, devastation_range = 1, heavy_impact_range = 2, light_impact_range = 3)
qdel(parent) //Alternatively could brick the uplink.
+1 -1
View File
@@ -106,7 +106,7 @@
active_timers = null
for(var/thing in timers)
var/datum/timedevent/timer = thing
if (timer.spent)
if (timer.spent && !(timer.flags & TIMER_DELETE_ME))
continue
qdel(timer)
+1 -1
View File
@@ -30,7 +30,7 @@
return
switch(stage)
if(2 || 3) //Pretend to be a cold and give time to spread.
if(2, 3) //Pretend to be a cold and give time to spread.
if(prob(8))
affected_mob.emote("sneeze")
if(prob(8))
+1 -1
View File
@@ -168,7 +168,7 @@
L.visible_message("<span class='warning'>[held_mob] escapes from [L]!</span>", "<span class='warning'>[held_mob] escapes your grip!</span>")
release()
/obj/item/clothing/head/mob_holder/mob_can_equip(mob/living/M, mob/living/equipper, slot, disable_warning = FALSE, bypass_equip_delay_self = FALSE)
/obj/item/clothing/head/mob_holder/mob_can_equip(M, equipper, slot, disable_warning, bypass_equip_delay_self)
if(M == held_mob || !ishuman(M)) //monkeys holding monkeys holding monkeys...
return FALSE
return ..()
+517
View File
@@ -0,0 +1,517 @@
/// An element for atoms that, when dragged and dropped onto a mob, opens a strip panel.
/datum/element/strippable
element_flags = ELEMENT_BESPOKE | ELEMENT_DETACH
id_arg_index = 2
/// An assoc list of keys to /datum/strippable_item
var/list/items
/// A proc path that returns TRUE/FALSE if we should show the strip panel for this entity.
/// If it does not exist, the strip menu will always show.
/// Will be called with (mob/user).
var/should_strip_proc_path
/// An existing strip menus
var/list/strip_menus
/datum/element/strippable/Attach(datum/target, list/items, should_strip_proc_path)
. = ..()
if (!isatom(target))
return ELEMENT_INCOMPATIBLE
RegisterSignal(target, COMSIG_MOUSEDROP_ONTO, .proc/mouse_drop_onto)
src.items = items
src.should_strip_proc_path = should_strip_proc_path
/datum/element/strippable/Detach(datum/source)
. = ..()
UnregisterSignal(source, COMSIG_MOUSEDROP_ONTO)
if (!isnull(strip_menus))
qdel(strip_menus[source])
strip_menus -= source
/datum/element/strippable/proc/mouse_drop_onto(datum/source, atom/over, mob/user)
SIGNAL_HANDLER
if (user == source)
return
if (over != user)
return
// Cyborgs buckle people by dragging them onto them, unless in combat mode.
if (iscyborg(user))
var/mob/living/silicon/robot/cyborg_user = user
if (cyborg_user.a_intent == INTENT_HARM)
return
if (!isnull(should_strip_proc_path) && !call(source, should_strip_proc_path)(user))
return
var/datum/strip_menu/strip_menu
if (isnull(strip_menu))
strip_menu = new(source, src)
LAZYSET(strip_menus, source, strip_menu)
INVOKE_ASYNC(strip_menu, /datum/.proc/ui_interact, user)
/// A representation of an item that can be stripped down
/datum/strippable_item
/// The STRIPPABLE_ITEM_* key
var/key
/// Should we warn about dangerous clothing?
var/warn_dangerous_clothing = TRUE
/// Gets the item from the given source.
/datum/strippable_item/proc/get_item(atom/source)
/// Tries to equip the item onto the given source.
/// Returns TRUE/FALSE depending on if it is allowed.
/// This should be used for checking if an item CAN be equipped.
/// It should not perform the equipping itself.
/datum/strippable_item/proc/try_equip(atom/source, obj/item/equipping, mob/user)
if (HAS_TRAIT(equipping, TRAIT_NODROP))
to_chat(user, span_warning("You can't put [equipping] on [source], it's stuck to your hand!"))
return FALSE
return TRUE
/// Start the equipping process. This is the proc you should yield in.
/// Returns TRUE/FALSE depending on if it is allowed.
/datum/strippable_item/proc/start_equip(atom/source, obj/item/equipping, mob/user)
if (warn_dangerous_clothing && isclothing(source))
source.visible_message(
span_notice("[user] tries to put [equipping] on [source]."),
span_notice("[user] tries to put [equipping] on you."),
ignored_mobs = user,
)
if(ishuman(source))
var/mob/living/carbon/human/victim_human = source
if(victim_human.key && !victim_human.client) // AKA braindead
if(victim_human.stat <= SOFT_CRIT && LAZYLEN(victim_human.afk_thefts) <= AFK_THEFT_MAX_MESSAGES)
var/list/new_entry = list(list(user.name, "tried equipping you with [equipping]", world.time))
LAZYADD(victim_human.afk_thefts, new_entry)
to_chat(user, span_notice("You try to put [equipping] on [source]..."))
var/log = "[key_name(source)] is having [equipping] put on them by [key_name(user)]"
user.log_message(log, LOG_ATTACK, color="red")
source.log_message(log, LOG_VICTIM, color="red", log_globally=FALSE)
return TRUE
/// The proc that places the item on the source. This should not yield.
/datum/strippable_item/proc/finish_equip(atom/source, obj/item/equipping, mob/user)
SHOULD_NOT_SLEEP(TRUE)
return TRUE
/// Tries to unequip the item from the given source.
/// Returns TRUE/FALSE depending on if it is allowed.
/// This should be used for checking if it CAN be unequipped.
/// It should not perform the unequipping itself.
/datum/strippable_item/proc/try_unequip(atom/source, mob/user)
SHOULD_NOT_SLEEP(TRUE)
var/obj/item/item = get_item(source)
if (isnull(item))
return FALSE
if (ismob(source))
var/mob/mob_source = source
if (!item.canStrip(user, mob_source))
return FALSE
return TRUE
/// Start the unequipping process. This is the proc you should yield in.
/// Returns TRUE/FALSE depending on if it is allowed.
/datum/strippable_item/proc/start_unequip(atom/source, mob/user)
var/obj/item/item = get_item(source)
if (isnull(item))
return FALSE
source.visible_message(
span_warning("[user] tries to remove [source]'s [item.name]."),
span_userdanger("[user] tries to remove your [item.name]."),
ignored_mobs = user,
)
to_chat(user, span_danger("You try to remove [source]'s [item]..."))
user.log_message("[key_name(source)] is being stripped of [item] by [key_name(user)]", LOG_ATTACK, color="red")
source.log_message("[key_name(source)] is being stripped of [item] by [key_name(user)]", LOG_VICTIM, color="red", log_globally=FALSE)
item.add_fingerprint(source)
if(ishuman(source))
var/mob/living/carbon/human/victim_human = source
if(victim_human.key && !victim_human.client) // AKA braindead
if(victim_human.stat <= SOFT_CRIT && LAZYLEN(victim_human.afk_thefts) <= AFK_THEFT_MAX_MESSAGES)
var/list/new_entry = list(list(user.name, "tried unequipping your [item.name]", world.time))
LAZYADD(victim_human.afk_thefts, new_entry)
return TRUE
/// The proc that unequips the item from the source. This should not yield.
/datum/strippable_item/proc/finish_unequip(atom/source, mob/user)
/// Returns a STRIPPABLE_OBSCURING_* define to report on whether or not this is obscured.
/datum/strippable_item/proc/get_obscuring(atom/source)
SHOULD_NOT_SLEEP(TRUE)
return STRIPPABLE_OBSCURING_NONE
/// Returns the ID of this item's strippable action.
/// Return `null` if there is no alternate action.
/// Any return value of this must be in StripMenu.
/datum/strippable_item/proc/get_alternate_action(atom/source, mob/user)
if(get_obscuring(source) == STRIPPABLE_OBSCURING_COMPLETELY)
return FALSE
return null
/// Performs an alternative action on this strippable_item.
/// `has_alternate_action` needs to be TRUE.
/datum/strippable_item/proc/alternate_action(atom/source, mob/user)
if(get_obscuring(source) == STRIPPABLE_OBSCURING_COMPLETELY)
return null
return TRUE
/// Returns whether or not this item should show.
/datum/strippable_item/proc/should_show(atom/source, mob/user)
return TRUE
/// A preset for equipping items onto mob slots
/datum/strippable_item/mob_item_slot
/// The ITEM_SLOT_* to equip to.
var/item_slot
/datum/strippable_item/mob_item_slot/get_item(atom/source)
if (!ismob(source))
return null
var/mob/mob_source = source
return mob_source.get_item_by_slot(item_slot)
/datum/strippable_item/mob_item_slot/try_equip(atom/source, obj/item/equipping, mob/user)
. = ..()
if (!.)
return
if (!ismob(source))
return FALSE
if (!equipping.mob_can_equip(
source,
user,
item_slot,
disable_warning = TRUE,
bypass_equip_delay_self = TRUE,
))
to_chat(user, span_warning("\The [equipping] doesn't fit in that place!"))
return FALSE
return TRUE
/datum/strippable_item/mob_item_slot/start_equip(atom/source, obj/item/equipping, mob/user)
. = ..()
if (!.)
return
if (!ismob(source))
return FALSE
if (!do_mob(user, source, get_equip_delay(equipping)))
return FALSE
if(get_obscuring(source) == STRIPPABLE_OBSCURING_COMPLETELY)
return FALSE
if (!equipping.mob_can_equip(
source,
user,
item_slot,
disable_warning = TRUE,
bypass_equip_delay_self = TRUE,
))
return FALSE
if (!user.temporarilyRemoveItemFromInventory(equipping))
return FALSE
return TRUE
/datum/strippable_item/mob_item_slot/finish_equip(atom/source, obj/item/equipping, mob/user)
if(!..())
return FALSE
if (!ismob(source))
return FALSE
var/mob/mob_source = source
mob_source.equip_to_slot(equipping, item_slot)
/datum/strippable_item/mob_item_slot/get_obscuring(atom/source)
if (iscarbon(source))
var/mob/living/carbon/carbon_source = source
return (item_slot in carbon_source.check_obscured_slots()) \
? STRIPPABLE_OBSCURING_COMPLETELY \
: STRIPPABLE_OBSCURING_NONE
return FALSE
/datum/strippable_item/mob_item_slot/start_unequip(atom/source, mob/user)
. = ..()
if (!.)
return
return start_unequip_mob(get_item(source), source, user)
/datum/strippable_item/mob_item_slot/finish_unequip(atom/source, mob/user)
var/obj/item/item = get_item(source)
if (isnull(item))
return FALSE
if (!ismob(source))
return FALSE
return finish_unequip_mob(item, source, user)
/// Returns the delay of equipping this item to a mob
/datum/strippable_item/mob_item_slot/proc/get_equip_delay(obj/item/equipping)
return equipping.equip_delay_other
/// A utility function for `/datum/strippable_item`s to start unequipping an item from a mob.
/proc/start_unequip_mob(obj/item/item, mob/source, mob/user, strip_delay)
if (!do_mob(user, source, strip_delay || item.strip_delay, ignorehelditem = TRUE))
return FALSE
return TRUE
/// A utility function for `/datum/strippable_item`s to finish unequipping an item from a mob.
/proc/finish_unequip_mob(obj/item/item, mob/source, mob/user)
if (!item.doStrip(user, source))
return FALSE
user.log_message("[key_name(source)] has been stripped of [item] by [key_name(user)]", LOG_ATTACK, color="red")
source.log_message("[key_name(source)] has been stripped of [item] by [key_name(user)]", LOG_VICTIM, color="red", log_globally=FALSE)
// Updates speed in case stripped speed affecting item
source.update_equipment_speed_mods()
/// A representation of the stripping UI
/datum/strip_menu
/// The owner who has the element /datum/element/strippable
var/atom/movable/owner
/// The strippable element itself
var/datum/element/strippable/strippable
/// A lazy list of user mobs to a list of strip menu keys that they're interacting with
var/list/interactions
/datum/strip_menu/New(atom/movable/owner, datum/element/strippable/strippable)
. = ..()
src.owner = owner
src.strippable = strippable
/datum/strip_menu/Destroy()
owner = null
strippable = null
return ..()
/datum/strip_menu/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if (!ui)
ui = new(user, src, "StripMenu")
ui.open()
/datum/strip_menu/ui_assets(mob/user)
return list(
get_asset_datum(/datum/asset/simple/inventory),
)
/datum/strip_menu/ui_data(mob/user)
var/list/data = list()
var/list/items = list()
for (var/strippable_key in strippable.items)
var/datum/strippable_item/item_data = strippable.items[strippable_key]
if (!item_data.should_show(owner, user))
continue
var/list/result
if(strippable_key in LAZYACCESS(interactions, user))
LAZYSET(result, "interacting", TRUE)
var/obscuring = item_data.get_obscuring(owner)
if (obscuring != STRIPPABLE_OBSCURING_NONE)
LAZYSET(result, "obscured", obscuring)
items[strippable_key] = result
continue
var/obj/item/item = item_data.get_item(owner)
if (isnull(item))
items[strippable_key] = result
continue
LAZYINITLIST(result)
result["icon"] = icon2base64(icon(item.icon, item.icon_state, SOUTH, 1))
result["name"] = item.name
result["alternate"] = item_data.get_alternate_action(owner, user)
items[strippable_key] = result
data["items"] = items
// While most `\the`s are implicit, this one is not.
// In this case, `\The` would otherwise be used.
// This doesn't match with what it's used for, which is to say "Stripping the alien drone",
// as opposed to "Stripping The alien drone".
// Human names will still show without "the", as they are proper nouns.
data["name"] = "\the [owner]"
/// Customize the strip menu
data["long_strip_menu"] = user.client.prefs.long_strip_menu
return data
/datum/strip_menu/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if (.)
return
. = TRUE
var/mob/user = usr
switch (action)
if ("use")
var/key = params["key"]
var/datum/strippable_item/strippable_item = strippable.items[key]
if (isnull(strippable_item))
return
if (!strippable_item.should_show(owner, user))
return
if (strippable_item.get_obscuring(owner) == STRIPPABLE_OBSCURING_COMPLETELY)
return
var/item = strippable_item.get_item(owner)
if (isnull(item))
var/obj/item/held_item = user.get_active_held_item()
if (isnull(held_item))
return
if (strippable_item.try_equip(owner, held_item, user))
LAZYORASSOCLIST(interactions, user, key)
// Yielding call
var/should_finish = strippable_item.start_equip(owner, held_item, user)
LAZYREMOVEASSOC(interactions, user, key)
if (!should_finish)
return
if (QDELETED(src) || QDELETED(owner))
return
// They equipped an item in the meantime
if (!isnull(strippable_item.get_item(owner)))
return
if (!user.Adjacent(owner))
return
strippable_item.finish_equip(owner, held_item, user)
else if (strippable_item.try_unequip(owner, user))
LAZYORASSOCLIST(interactions, user, key)
var/should_unequip = strippable_item.start_unequip(owner, user)
LAZYREMOVEASSOC(interactions, user, key)
// Yielding call
if (!should_unequip)
return
if (QDELETED(src) || QDELETED(owner))
return
// They changed the item in the meantime
if (strippable_item.get_item(owner) != item)
return
if (!user.Adjacent(owner))
return
strippable_item.finish_unequip(owner, user)
if ("alt")
var/key = params["key"]
var/datum/strippable_item/strippable_item = strippable.items[key]
if (isnull(strippable_item))
return
if (!strippable_item.should_show(owner, user))
return
if (strippable_item.get_obscuring(owner) == STRIPPABLE_OBSCURING_COMPLETELY)
return
var/item = strippable_item.get_item(owner)
if (isnull(item))
return
if (isnull(strippable_item.get_alternate_action(owner, user)))
return
LAZYORASSOCLIST(interactions, user, key)
// Potentially yielding
strippable_item.alternate_action(owner, user)
LAZYREMOVEASSOC(interactions, user, key)
/datum/strip_menu/ui_host(mob/user)
return owner
/datum/strip_menu/ui_state(mob/user)
return GLOB.always_state
/datum/strip_menu/ui_status(mob/user, datum/ui_state/state)
. = ..()
if(isliving(user))
var/mob/living/living_user = user
if (
living_user.stat == CONSCIOUS \
&& living_user.Adjacent(owner)
)
return UI_INTERACTIVE
if(IsAdminGhost(user))
return UI_INTERACTIVE
if(user.Adjacent(owner))
return UI_UPDATE
else
return UI_DISABLED
/// Creates an assoc list of keys to /datum/strippable_item
/proc/create_strippable_list(types)
var/list/strippable_items = list()
for (var/strippable_type in types)
var/datum/strippable_item/strippable_item = new strippable_type
strippable_items[strippable_item.key] = strippable_item
return strippable_items
+44
View File
@@ -0,0 +1,44 @@
///This element just handles creating and destroying an area sound manager that's hooked into weather stuff
/datum/element/weather_listener
element_flags = ELEMENT_BESPOKE
id_arg_index = 2
var/weather_type
//What events to change the track on
var/list/sound_change_signals
//The weather type we're working with
var/weather_trait
//The playlist of sounds to draw from. Pass by ref
var/list/playlist
/datum/element/weather_listener/Attach(datum/target, w_type, trait, weather_playlist)
. = ..()
if(!weather_type)
weather_type = w_type
sound_change_signals = list(
COMSIG_WEATHER_TELEGRAPH(weather_type),
COMSIG_WEATHER_START(weather_type),
COMSIG_WEATHER_WINDDOWN(weather_type),
COMSIG_WEATHER_END(weather_type)
)
weather_trait = trait
playlist = weather_playlist
RegisterSignal(target, COMSIG_MOVABLE_Z_CHANGED, .proc/handle_z_level_change, override = TRUE)
RegisterSignal(target, COMSIG_MOB_CLIENT_LOGOUT, .proc/handle_logout, override = TRUE)
/datum/element/weather_listener/Detach(datum/source)
. = ..()
UnregisterSignal(source, COMSIG_MOVABLE_Z_CHANGED, COMSIG_MOB_CLIENT_LOGOUT)
/datum/element/weather_listener/proc/handle_z_level_change(datum/source, old_z, new_z)
SIGNAL_HANDLER
var/list/fitting_z_levels = SSmapping.levels_by_trait(weather_trait)
if(!(new_z in fitting_z_levels))
return
var/datum/component/our_comp = source.AddComponent(/datum/component/area_sound_manager, playlist, list(), COMSIG_MOB_CLIENT_LOGOUT, fitting_z_levels)
our_comp.RegisterSignal(SSdcs, sound_change_signals, /datum/component/area_sound_manager/proc/handle_change)
/datum/element/weather_listener/proc/handle_logout(datum/source, client/this_is_a_null_ref)
SIGNAL_HANDLER
source.RemoveElement(/datum/element/weather_listener, weather_type, weather_trait, playlist)
-132
View File
@@ -1,132 +0,0 @@
/* HUD DATUMS */
GLOBAL_LIST_EMPTY(all_huds)
//GLOBAL HUD LIST
GLOBAL_LIST_INIT(huds, list(
DATA_HUD_SECURITY_BASIC = new/datum/atom_hud/data/human/security/basic(),
DATA_HUD_SECURITY_ADVANCED = new/datum/atom_hud/data/human/security/advanced(),
DATA_HUD_MEDICAL_BASIC = new/datum/atom_hud/data/human/medical/basic(),
DATA_HUD_MEDICAL_ADVANCED = new/datum/atom_hud/data/human/medical/advanced(),
DATA_HUD_DIAGNOSTIC_BASIC = new/datum/atom_hud/data/diagnostic/basic(),
DATA_HUD_DIAGNOSTIC_ADVANCED = new/datum/atom_hud/data/diagnostic/advanced(),
DATA_HUD_ABDUCTOR = new/datum/atom_hud/abductor(),
DATA_HUD_SENTIENT_DISEASE = new/datum/atom_hud/sentient_disease(),
DATA_HUD_AI_DETECT = new/datum/atom_hud/ai_detector(),
ANTAG_HUD_CULT = new/datum/atom_hud/antag(),
ANTAG_HUD_REV = new/datum/atom_hud/antag(),
ANTAG_HUD_OPS = new/datum/atom_hud/antag(),
ANTAG_HUD_WIZ = new/datum/atom_hud/antag(),
ANTAG_HUD_SHADOW = new/datum/atom_hud/antag(),
ANTAG_HUD_TRAITOR = new/datum/atom_hud/antag/hidden(),
ANTAG_HUD_NINJA = new/datum/atom_hud/antag/hidden(),
ANTAG_HUD_CHANGELING = new/datum/atom_hud/antag/hidden(),
ANTAG_HUD_ABDUCTOR = new/datum/atom_hud/antag/hidden(),
ANTAG_HUD_DEVIL = new/datum/atom_hud/antag(),
ANTAG_HUD_SINTOUCHED = new/datum/atom_hud/antag/hidden(),
ANTAG_HUD_SOULLESS = new/datum/atom_hud/antag/hidden(),
ANTAG_HUD_CLOCKWORK = new/datum/atom_hud/antag(),
ANTAG_HUD_BROTHER = new/datum/atom_hud/antag/hidden(),
ANTAG_HUD_BLOODSUCKER = new/datum/atom_hud/antag/bloodsucker(),
ANTAG_HUD_FUGITIVE = new/datum/atom_hud/antag(),
ANTAG_HUD_HERETIC = new/datum/atom_hud/antag/hidden()
))
/datum/atom_hud
var/list/atom/hudatoms = list() //list of all atoms which display this hud
var/list/hudusers = list() //list with all mobs who can see the hud
var/list/hud_icons = list() //these will be the indexes for the atom's hud_list
var/list/next_time_allowed = list() //mobs associated with the next time this hud can be added to them
var/list/queued_to_see = list() //mobs that have triggered the cooldown and are queued to see the hud, but do not yet
/datum/atom_hud/New()
GLOB.all_huds += src
/datum/atom_hud/Destroy()
for(var/v in hudusers)
remove_hud_from(v)
for(var/v in hudatoms)
remove_from_hud(v)
GLOB.all_huds -= src
return ..()
/datum/atom_hud/proc/remove_hud_from(mob/M)
if(!M || !hudusers[M])
return
if (!--hudusers[M])
hudusers -= M
if(queued_to_see[M])
queued_to_see -= M
else
for(var/atom/A in hudatoms)
remove_from_single_hud(M, A)
/datum/atom_hud/proc/remove_from_hud(atom/A)
if(!A)
return FALSE
for(var/mob/M in hudusers)
remove_from_single_hud(M, A)
hudatoms -= A
return TRUE
/datum/atom_hud/proc/remove_from_single_hud(mob/M, atom/A) //unsafe, no sanity apart from client
if(!M || !M.client || !A)
return
for(var/i in hud_icons)
M.client.images -= A.hud_list[i]
/datum/atom_hud/proc/add_hud_to(mob/M)
if(!M)
return
if(!hudusers[M])
hudusers[M] = 1
if(next_time_allowed[M] > world.time)
if(!queued_to_see[M])
addtimer(CALLBACK(src, .proc/show_hud_images_after_cooldown, M), next_time_allowed[M] - world.time)
queued_to_see[M] = TRUE
else
next_time_allowed[M] = world.time + ADD_HUD_TO_COOLDOWN
for(var/atom/A in hudatoms)
add_to_single_hud(M, A)
else
hudusers[M]++
/datum/atom_hud/proc/show_hud_images_after_cooldown(M)
if(queued_to_see[M])
queued_to_see -= M
next_time_allowed[M] = world.time + ADD_HUD_TO_COOLDOWN
for(var/atom/A in hudatoms)
add_to_single_hud(M, A)
/datum/atom_hud/proc/add_to_hud(atom/A)
if(!A)
return FALSE
hudatoms |= A
for(var/mob/M in hudusers)
if(!queued_to_see[M])
add_to_single_hud(M, A)
return TRUE
/datum/atom_hud/proc/add_to_single_hud(mob/M, atom/A) //unsafe, no sanity apart from client
if(!M || !M.client || !A)
return
for(var/i in hud_icons)
if(A.hud_list[i])
M.client.images |= A.hud_list[i]
//MOB PROCS
/mob/proc/reload_huds()
for(var/datum/atom_hud/hud in GLOB.all_huds)
if(hud && hud.hudusers[src])
for(var/atom/A in hud.hudatoms)
hud.add_to_single_hud(src, A)
/mob/dead/new_player/reload_huds()
return
/mob/proc/add_click_catcher()
client.screen += client.void
/mob/dead/new_player/add_click_catcher()
return
+47 -34
View File
@@ -1,21 +1,21 @@
/*
output_atoms (list of atoms) The destination(s) for the sounds
output_atoms (list of atoms) The destination(s) for the sounds
mid_sounds (list or soundfile) Since this can be either a list or a single soundfile you can have random sounds. May contain further lists but must contain a soundfile at the end.
mid_length (num) The length to wait between playing mid_sounds
mid_sounds (list or soundfile) Since this can be either a list or a single soundfile you can have random sounds. May contain further lists but must contain a soundfile at the end.
mid_length (num) The length to wait between playing mid_sounds
start_sound (soundfile) Played before starting the mid_sounds loop
start_length (num) How long to wait before starting the main loop after playing start_sound
start_sound (soundfile) Played before starting the mid_sounds loop
start_length (num) How long to wait before starting the main loop after playing start_sound
end_sound (soundfile) The sound played after the main loop has concluded
end_sound (soundfile) The sound played after the main loop has concluded
chance (num) Chance per loop to play a mid_sound
volume (num) Sound output volume
max_loops (num) The max amount of loops to run for.
direct (bool) If true plays directly to provided atoms instead of from them
chance (num) Chance per loop to play a mid_sound
volume (num) Sound output volume
max_loops (num) The max amount of loops to run for.
direct (bool) If true plays directly to provided atoms instead of from them
*/
/datum/looping_sound
var/list/atom/output_atoms
var/atom/parent
var/mid_sounds
var/mid_length
///Override for volume of start sound
@@ -34,38 +34,46 @@
var/falloff_exponent
var/timerid
var/falloff_distance
var/skip_starting_sounds = FALSE
var/loop_started = FALSE
/datum/looping_sound/New(list/_output_atoms=list(), start_immediately=FALSE, _direct=FALSE)
/datum/looping_sound/New(_parent, start_immediately=FALSE, _direct=FALSE, _skip_starting_sounds = FALSE)
if(!mid_sounds)
WARNING("A looping sound datum was created without sounds to play.")
return
output_atoms = _output_atoms
set_parent(_parent)
direct = _direct
skip_starting_sounds = _skip_starting_sounds
if(start_immediately)
start()
/datum/looping_sound/Destroy()
stop()
output_atoms = null
stop(TRUE)
return ..()
/datum/looping_sound/proc/start(atom/add_thing)
if(add_thing)
output_atoms |= add_thing
/datum/looping_sound/proc/start(on_behalf_of)
if(on_behalf_of)
set_parent(on_behalf_of)
if(timerid)
return
on_start()
/datum/looping_sound/proc/stop(atom/remove_thing)
if(remove_thing)
output_atoms -= remove_thing
/datum/looping_sound/proc/stop(null_parent)
if(null_parent)
set_parent(null)
if(!timerid)
return
on_stop()
deltimer(timerid)
deltimer(timerid, SSsound_loops)
timerid = null
loop_started = FALSE
/datum/looping_sound/proc/start_sound_loop()
loop_started = TRUE
sound_loop()
timerid = addtimer(CALLBACK(src, .proc/sound_loop, world.time), mid_length, TIMER_CLIENT_TIME | TIMER_STOPPABLE | TIMER_LOOP | TIMER_DELETE_ME, SSsound_loops)
/datum/looping_sound/proc/sound_loop(starttime)
if(max_loops && world.time >= starttime + mid_length * max_loops)
@@ -73,21 +81,15 @@
return
if(!chance || prob(chance))
play(get_sound(starttime))
if(!timerid)
timerid = addtimer(CALLBACK(src, .proc/sound_loop, world.time), mid_length, TIMER_CLIENT_TIME | TIMER_STOPPABLE | TIMER_LOOP)
/datum/looping_sound/proc/play(soundfile, volume_override)
var/list/atoms_cache = output_atoms
var/sound/S = sound(soundfile)
if(direct)
S.channel = SSsounds.random_available_channel()
S.volume = volume_override || volume //Use volume as fallback if theres no override
for(var/i in 1 to atoms_cache.len)
var/atom/thing = atoms_cache[i]
if(direct)
SEND_SOUND(thing, S)
else
playsound(thing, S, volume, vary, extra_range, falloff_exponent = falloff_exponent, falloff_distance = falloff_distance)
SEND_SOUND(parent, S)
else
playsound(parent, S, volume, vary, extra_range, falloff_exponent = falloff_exponent, falloff_distance = falloff_distance)
/datum/looping_sound/proc/get_sound(starttime, _mid_sounds)
. = _mid_sounds || mid_sounds
@@ -96,11 +98,22 @@
/datum/looping_sound/proc/on_start()
var/start_wait = 0
if(start_sound)
if(start_sound && !skip_starting_sounds)
play(start_sound, start_volume)
start_wait = start_length
addtimer(CALLBACK(src, .proc/sound_loop), start_wait, TIMER_CLIENT_TIME)
timerid = addtimer(CALLBACK(src, .proc/start_sound_loop), start_wait, TIMER_CLIENT_TIME | TIMER_DELETE_ME | TIMER_STOPPABLE, SSsound_loops)
/datum/looping_sound/proc/on_stop()
if(end_sound)
if(end_sound && loop_started)
play(end_sound, end_volume)
/datum/looping_sound/proc/set_parent(new_parent)
if(parent)
UnregisterSignal(parent, COMSIG_PARENT_QDELETING)
parent = new_parent
if(parent)
RegisterSignal(parent, COMSIG_PARENT_QDELETING, .proc/handle_parent_del)
/datum/looping_sound/proc/handle_parent_del(datum/source)
SIGNAL_HANDLER
set_parent(null)
+1 -1
View File
@@ -1,4 +1,4 @@
#define RAD_GEIGER_LOW 100 // Geiger counter sound thresholds
#define RAD_GEIGER_LOW 100 // Geiger counter sound thresholds
#define RAD_GEIGER_MEDIUM 500
#define RAD_GEIGER_HIGH 1000
@@ -7,7 +7,7 @@
/mob/living/simple_animal/hostile/asteroid/basilisk/watcher/random = 40, /obj/structure/spawner/lavaland = 2, \
/mob/living/simple_animal/hostile/asteroid/hivelord/legion/random = 30, /obj/structure/spawner/lavaland/legion = 3, \
SPAWN_MEGAFAUNA = 4, /mob/living/simple_animal/hostile/asteroid/goldgrub = 10)
flora_spawn_list = list(/obj/structure/flora/ash/leaf_shroom = 2 , /obj/structure/flora/ash/cap_shroom = 2 , /obj/structure/flora/ash/stem_shroom = 2 , /obj/structure/flora/ash/cacti = 1, /obj/structure/flora/ash/tall_shroom = 2)
flora_spawn_list = list(/obj/structure/flora/ash/leaf_shroom = 2 , /obj/structure/flora/ash/cap_shroom = 2 , /obj/structure/flora/ash/stem_shroom = 2 , /obj/structure/flora/ash/cacti = 1, /obj/structure/flora/ash/tall_shroom = 2, /obj/structure/flora/ashtree = 1)
feature_spawn_list = list(/obj/structure/geyser/random = 1)
initial_closed_chance = 45
+1 -1
View File
@@ -120,7 +120,7 @@ Simple datum which is instanced once per type and is used for every object of sa
return
I.hitsound = item_sound_override
I.usesound = item_sound_override
I.throwhitsound = item_sound_override
I.mob_throw_hit_sound = item_sound_override
// I.mob_throw_hit_sound = item_sound_override
// I.equip_sound = item_sound_override
// I.pickup_sound = item_sound_override
+40 -37
View File
@@ -229,7 +229,6 @@
/datum/mind/proc/has_antag_datum(datum_type, check_subtypes = TRUE)
if(!datum_type)
return
. = FALSE
for(var/a in antag_datums)
var/datum/antagonist/A = a
if(check_subtypes && istype(A, datum_type))
@@ -296,11 +295,17 @@
remove_rev()
SSticker.mode.update_cult_icons_removed(src)
/datum/mind/proc/equip_traitor(datum/traitor_class/traitor_class, silent = FALSE, datum/antagonist/uplink_owner)
/**
* ## give_uplink
*
* A mind proc for giving anyone an uplink.
* arguments:
* * silent: if this should send a message to the mind getting the uplink. traitors do not use this silence, but the silence var on their antag datum.
* * antag_datum: the antag datum of the uplink owner, for storing it in antag memory. optional!
*/
/datum/mind/proc/equip_traitor(silent = FALSE, datum/antagonist/antag_datum)
if(!current)
return
if(!traitor_class)
traitor_class = GLOB.traitor_classes[TRAITOR_HUMAN]
var/mob/living/carbon/human/traitor_mob = current
if (!istype(traitor_mob))
return
@@ -314,16 +319,9 @@
P = locate() in PDA
if (!P) // If we couldn't find a pen in the PDA, or we didn't even have a PDA, do it the old way
P = locate() in all_contents
if(!P) // I do not have a pen.
var/obj/item/pen/inowhaveapen
if(istype(traitor_mob.back,/obj/item/storage)) //ok buddy you better have a backpack!
inowhaveapen = new /obj/item/pen(traitor_mob.back)
else
inowhaveapen = new /obj/item/pen(traitor_mob.loc)
traitor_mob.put_in_hands(inowhaveapen) // I hope you don't have arms and your traitor pen gets stolen for all this trouble you've caused.
P = inowhaveapen
var/obj/item/uplink_loc
var/implant = FALSE
if(traitor_mob.client && traitor_mob.client.prefs)
switch(traitor_mob.client.prefs.uplink_spawn_loc)
@@ -341,33 +339,38 @@
uplink_loc = P
if(UPLINK_PEN)
uplink_loc = P
if(!uplink_loc)
uplink_loc = PDA
if(!uplink_loc)
uplink_loc = R
if(UPLINK_IMPLANT)
implant = TRUE
if (!uplink_loc)
if(!silent)
to_chat(traitor_mob, "Unfortunately, [traitor_class.employer] wasn't able to get you an Uplink.")
. = 0
else
. = uplink_loc
var/datum/component/uplink/U = uplink_loc.AddComponent(/datum/component/uplink, traitor_mob.key,traitor_class)
if(!U)
CRASH("Uplink creation failed.")
U.setup_unlock_code()
if(!silent)
if(uplink_loc == R)
to_chat(traitor_mob, "[traitor_class.employer] has cunningly disguised a Syndicate Uplink as your [R.name]. Simply dial the frequency [format_frequency(U.unlock_code)] to unlock its hidden features.")
else if(uplink_loc == PDA)
to_chat(traitor_mob, "[traitor_class.employer] has cunningly disguised a Syndicate Uplink as your [PDA.name]. Simply enter the code \"[U.unlock_code]\" into the ringtone select to unlock its hidden features.")
else if(uplink_loc == P)
to_chat(traitor_mob, "[traitor_class.employer] has cunningly disguised a Syndicate Uplink as your [P.name]. Simply twist the top of the pen [U.unlock_code] from its starting position to unlock its hidden features.")
if(!uplink_loc) // We've looked everywhere, let's just implant you
implant = TRUE
if(uplink_owner)
uplink_owner.antag_memory += U.unlock_note + "<br>"
else
traitor_mob.mind.store_memory(U.unlock_note)
if(implant)
var/obj/item/implant/uplink/starting/new_implant = new(traitor_mob)
new_implant.implant(traitor_mob, null, silent = TRUE)
if(!silent)
to_chat(traitor_mob, span_boldnotice("Your Syndicate Uplink has been cunningly implanted in you, for a small TC fee. Simply trigger the uplink to access it."))
return new_implant
. = uplink_loc
var/unlock_text
var/datum/component/uplink/new_uplink = uplink_loc.AddComponent(/datum/component/uplink, traitor_mob.key)
if(!new_uplink)
CRASH("Uplink creation failed.")
new_uplink.setup_unlock_code()
if(uplink_loc == R)
unlock_text = "Your Uplink is cunningly disguised as your [R.name]. Simply dial the frequency [format_frequency(new_uplink.unlock_code)] to unlock its hidden features."
else if(uplink_loc == PDA)
unlock_text = "Your Uplink is cunningly disguised as your [PDA.name]. Simply enter the code \"[new_uplink.unlock_code]\" into the ringtone select to unlock its hidden features."
else if(uplink_loc == P)
unlock_text = "Your Uplink is cunningly disguised as your [P.name]. Simply twist the top of the pen [english_list(new_uplink.unlock_code)] from its starting position to unlock its hidden features."
new_uplink.unlock_text = unlock_text
if(!silent)
to_chat(traitor_mob, span_boldnotice(unlock_text))
if(!antag_datum)
traitor_mob.mind.store_memory(new_uplink.unlock_note)
return
antag_datum.antag_memory += new_uplink.unlock_note + "<br>"
//Link a new mobs mind to the creator of said mob. They will join any team they are currently on, and will only switch teams when their creator does.
@@ -294,3 +294,39 @@
description = "<span class='boldwarning'>I hate when my shoes come untied!</span>\n"
mood_change = -3
timeout = 1 MINUTES
/datum/mood_event/high_five_alone
description = "<span class='boldwarning'>I tried getting a high-five with no one around, how embarassing!</span>\n"
mood_change = -2
timeout = 1 MINUTES
/datum/mood_event/high_five_full_hand
description = "<span class='boldwarning'>Oh God, I don't even know how to high-five correctly...</span>\n"
mood_change = -1
timeout = 45 SECONDS
/datum/mood_event/left_hanging
description = "<span class='boldwarning'>But everyone loves high fives! Maybe people just... hate me?</span>\n"
mood_change = -2
timeout = 1.5 MINUTES
/datum/mood_event/too_slow
description = "<span class='boldwarning'>NO! HOW COULD I BE.... TOO SLOW???</span>\n"
mood_change = -2 // multiplied by how many people saw it happen, up to 8, so potentially massive. the ULTIMATE prank carries a lot of weight
timeout = 2 MINUTES
/datum/mood_event/too_slow/add_effects(param)
var/people_laughing_at_you = 1 // start with 1 in case they're on the same tile or something
for(var/mob/living/carbon/iter_carbon in oview(owner, 7))
if(iter_carbon.stat == CONSCIOUS)
people_laughing_at_you++
if(people_laughing_at_you > 7)
break
mood_change *= people_laughing_at_you
return ..()
/datum/mood_event/sacrifice_bad
description = "<span class='warning'>Those darn savages!</span>\n"
mood_change = -5
timeout = 2 MINUTES
@@ -205,3 +205,23 @@
/datum/mood_event/cleared_stomach
description = "<span class='nicegreen'>Feels nice to get that out of the way!</span>\n"
mood_change = 3
/datum/mood_event/high_five
description = "<span class='nicegreen'>I love getting high fives!</span>\n"
mood_change = 2
timeout = 45 SECONDS
/datum/mood_event/high_ten
description = "<span class='nicegreen'>AMAZING! A HIGH-TEN!</span>\n"
mood_change = 3
timeout = 45 SECONDS
/datum/mood_event/down_low
description = "<span class='nicegreen'>HA! What a rube, they never stood a chance...</span>\n"
mood_change = 4
timeout = 1.5 MINUTES
/datum/mood_event/sacrifice_good
description = "<span class='nicegreen'>The gods are pleased with this offering!</span>\n"
mood_change = 5
timeout = 3 MINUTES
@@ -13,6 +13,7 @@
return
ADD_TRAIT(owner, TRAIT_RESISTCOLD, "cold_resistance")
ADD_TRAIT(owner, TRAIT_RESISTLOWPRESSURE, "cold_resistance")
ADD_TRAIT(owner, TRAIT_LOWPRESSURECOOLING, "cold_resistance")
owner.add_filter("space_glow", 2, list("type" = "outline", "color" = "#ffe46bd8", "size" = 1))
addtimer(CALLBACK(src, .proc/glow_loop, owner), rand(1,19))
@@ -27,5 +28,6 @@
return
REMOVE_TRAIT(owner, TRAIT_RESISTCOLD, "cold_resistance")
REMOVE_TRAIT(owner, TRAIT_RESISTLOWPRESSURE, "cold_resistance")
REMOVE_TRAIT(owner, TRAIT_LOWPRESSURECOOLING, "cold_resistance")
owner.remove_filter("space_glow")
+8 -6
View File
@@ -771,7 +771,7 @@
/datum/status_effect/necropolis_curse/proc/apply_curse(set_curse)
curse_flags |= set_curse
if(curse_flags & CURSE_BLINDING)
owner.overlay_fullscreen("curse", /atom/movable/screen/fullscreen/curse, 1)
owner.overlay_fullscreen("curse", /atom/movable/screen/fullscreen/scaled/curse, 1)
/datum/status_effect/necropolis_curse/proc/remove_curse(remove_curse)
if(remove_curse & CURSE_BLINDING)
@@ -929,11 +929,13 @@
/atom/movable/screen/alert/status_effect/strandling/Click(location, control, params)
. = ..()
to_chat(mob_viewer, "<span class='notice'>You attempt to remove the durathread strand from around your neck.</span>")
if(do_after(mob_viewer, 35, null, mob_viewer))
if(isliving(mob_viewer))
var/mob/living/L = mob_viewer
to_chat(mob_viewer, "<span class='notice'>You successfully remove the durathread strand.</span>")
if(usr != owner)
return
to_chat(owner, "<span class='notice'>You attempt to remove the durathread strand from around your neck.</span>")
if(do_after(owner, 35, null, owner))
if(isliving(owner))
var/mob/living/L = owner
to_chat(owner, "<span class='notice'>You successfully remove the durathread strand.</span>")
L.remove_status_effect(STATUS_EFFECT_CHOKINGSTRAND)
+86
View File
@@ -83,3 +83,89 @@
/datum/status_effect/throat_soothed/on_remove()
REMOVE_TRAIT(owner, TRAIT_SOOTHED_THROAT, "[STATUS_EFFECT_TRAIT]_[id]")
return ..()
// this status effect is used to negotiate the high-fiving capabilities of all concerned parties
/datum/status_effect/offering
id = "offering"
duration = -1
tick_interval = -1
status_type = STATUS_EFFECT_UNIQUE
alert_type = null
/// The people who were offered this item at the start
var/list/possible_takers
/// The actual item being offered
var/obj/item/offered_item
/// The type of alert given to people when offered, in case you need to override some behavior (like for high-fives)
var/give_alert_type = /atom/movable/screen/alert/give
/datum/status_effect/offering/on_creation(mob/living/new_owner, obj/item/offer, give_alert_override)
. = ..()
if(!.)
return
offered_item = offer
if(give_alert_override)
give_alert_type = give_alert_override
for(var/mob/living/carbon/possible_taker in orange(1, owner))
if(!owner.CanReach(possible_taker) || IS_DEAD_OR_INCAP(possible_taker) || !possible_taker.can_hold_items())
continue
register_candidate(possible_taker)
if(!possible_takers) // no one around
qdel(src)
return
RegisterSignal(owner, COMSIG_MOVABLE_MOVED, .proc/check_owner_in_range)
RegisterSignal(offered_item, list(COMSIG_PARENT_QDELETING, COMSIG_ITEM_DROPPED), .proc/dropped_item)
//RegisterSignal(owner, COMSIG_PARENT_EXAMINE_MORE, .proc/check_fake_out)
/datum/status_effect/offering/Destroy()
for(var/i in possible_takers)
var/mob/living/carbon/removed_taker = i
remove_candidate(removed_taker)
LAZYCLEARLIST(possible_takers)
return ..()
/// Hook up the specified carbon mob to be offered the item in question, give them the alert and signals and all
/datum/status_effect/offering/proc/register_candidate(mob/living/carbon/possible_candidate)
var/atom/movable/screen/alert/give/G = possible_candidate.throw_alert("[owner]", give_alert_type)
if(!G)
return
LAZYADD(possible_takers, possible_candidate)
RegisterSignal(possible_candidate, COMSIG_MOVABLE_MOVED, .proc/check_taker_in_range)
G.setup(possible_candidate, owner, offered_item)
/// Remove the alert and signals for the specified carbon mob. Automatically removes the status effect when we lost the last taker
/datum/status_effect/offering/proc/remove_candidate(mob/living/carbon/removed_candidate)
removed_candidate.clear_alert("[owner]")
LAZYREMOVE(possible_takers, removed_candidate)
UnregisterSignal(removed_candidate, COMSIG_MOVABLE_MOVED)
if(!possible_takers && !QDELING(src))
qdel(src)
/// One of our possible takers moved, see if they left us hanging
/datum/status_effect/offering/proc/check_taker_in_range(mob/living/carbon/taker)
SIGNAL_HANDLER
if(owner.CanReach(taker) && !IS_DEAD_OR_INCAP(taker))
return
remove_candidate(taker)
/// The offerer moved, see if anyone is out of range now
/datum/status_effect/offering/proc/check_owner_in_range(mob/living/carbon/source)
SIGNAL_HANDLER
for(var/i in possible_takers)
var/mob/living/carbon/checking_taker = i
if(!istype(checking_taker) || !owner.CanReach(checking_taker) || IS_DEAD_OR_INCAP(checking_taker))
remove_candidate(checking_taker)
/// We lost the item, give it up
/datum/status_effect/offering/proc/dropped_item(obj/item/source)
SIGNAL_HANDLER
qdel(src)
/datum/status_effect/offering/secret_handshake
id = "secret_handshake"
give_alert_type = /atom/movable/screen/alert/give/secret_handshake
+6 -5
View File
@@ -25,18 +25,19 @@
/datum/status_effect/proc/on_creation(mob/living/new_owner, ...)
if(new_owner)
owner = new_owner
if(owner)
LAZYADD(owner.status_effects, src)
if(!owner || !on_apply())
if(QDELETED(owner) || !on_apply())
qdel(src)
return
if(owner)
LAZYADD(owner.status_effects, src)
if(duration != -1)
duration = world.time + duration
next_tick = world.time + tick_interval
if(alert_type)
var/atom/movable/screen/alert/status_effect/A = owner.throw_alert(id, alert_type)
A.attached_effect = src //so the alert can reference us, if it needs to
linked_alert = A //so we can reference the alert, if we need to
if(istype(A))
A?.attached_effect = src //so the alert can reference us, if it needs to
linked_alert = A //so we can reference the alert, if we need to
START_PROCESSING(SSstatus_effects, src)
return TRUE
+25 -32
View File
@@ -1,3 +1,5 @@
//A reference to this list is passed into area sound managers, and it's modified in a manner that preserves that reference in ash_storm.dm
GLOBAL_LIST_EMPTY(ash_storm_sounds)
//Ash storms happen frequently on lavaland. They heavily obscure vision, and cause high fire damage to anyone caught outside.
/datum/weather/ash_storm
name = "ash storm"
@@ -25,55 +27,41 @@
probability = 90
barometer_predictable = TRUE
var/datum/looping_sound/active_outside_ashstorm/sound_ao = new(list(), FALSE, TRUE)
var/datum/looping_sound/active_inside_ashstorm/sound_ai = new(list(), FALSE, TRUE)
var/datum/looping_sound/weak_outside_ashstorm/sound_wo = new(list(), FALSE, TRUE)
var/datum/looping_sound/weak_inside_ashstorm/sound_wi = new(list(), FALSE, TRUE)
var/list/weak_sounds = list()
var/list/strong_sounds = list()
/datum/weather/ash_storm/telegraph()
. = ..()
var/list/inside_areas = list()
var/list/outside_areas = list()
var/list/eligible_areas = list()
for (var/z in impacted_z_levels)
eligible_areas += SSmapping.areas_in_z["[z]"]
for(var/i in 1 to eligible_areas.len)
var/area/place = eligible_areas[i]
if(place.outdoors)
outside_areas += place
weak_sounds[place] = /datum/looping_sound/weak_outside_ashstorm
strong_sounds[place] = /datum/looping_sound/active_outside_ashstorm
else
inside_areas += place
weak_sounds[place] = /datum/looping_sound/weak_inside_ashstorm
strong_sounds[place] = /datum/looping_sound/active_inside_ashstorm
CHECK_TICK
sound_ao.output_atoms = outside_areas
sound_ai.output_atoms = inside_areas
sound_wo.output_atoms = outside_areas
sound_wi.output_atoms = inside_areas
sound_wo.start()
sound_wi.start()
//We modify this list instead of setting it to weak/stron sounds in order to preserve things that hold a reference to it
//It's essentially a playlist for a bunch of components that chose what sound to loop based on the area a player is in
GLOB.ash_storm_sounds += weak_sounds
return ..()
/datum/weather/ash_storm/start()
. = ..()
sound_wo.stop()
sound_wi.stop()
sound_ao.start()
sound_ai.start()
GLOB.ash_storm_sounds -= weak_sounds
GLOB.ash_storm_sounds += strong_sounds
return ..()
/datum/weather/ash_storm/wind_down()
. = ..()
sound_ao.stop()
sound_ai.stop()
sound_wo.start()
sound_wi.start()
GLOB.ash_storm_sounds -= strong_sounds
GLOB.ash_storm_sounds += weak_sounds
return ..()
/datum/weather/ash_storm/end()
. = ..()
sound_wo.stop()
sound_wi.stop()
GLOB.ash_storm_sounds -= weak_sounds
return ..()
/datum/weather/ash_storm/proc/is_ash_immune(atom/L)
while (L && !isturf(L))
@@ -88,6 +76,11 @@
var/mob/living/the_mob = L
if("ash" in the_mob.weather_immunities)
return TRUE
// if(istype(L, /obj/structure/closet))
// var/obj/structure/closet/the_locker = L
// if(the_locker.weather_protection)
// if("ash" in the_locker.weather_protection)
// return TRUE
L = L.loc //Check parent items immunities (recurses up to the turf)
return FALSE //RIP you
+10 -2
View File
@@ -119,12 +119,20 @@
victim.bleed(blood_bled, TRUE)
if(14 to 19)
victim.visible_message("<span class='smalldanger'>[victim] spits out a string of blood from the blow to [victim.p_their()] chest!</span>", "<span class='danger'>You spit out a string of blood from the blow to your chest!</span>", vision_distance=COMBAT_MESSAGE_RANGE)
new /obj/effect/temp_visual/dir_setting/bloodsplatter(victim.loc, victim.dir)
if(ishuman(victim))
var/mob/living/carbon/human/H = victim
new /obj/effect/temp_visual/dir_setting/bloodsplatter(victim.loc, victim.dir, H.dna.species.exotic_blood_color)
else
new /obj/effect/temp_visual/dir_setting/bloodsplatter(victim.loc, victim.dir)
victim.bleed(blood_bled)
if(20 to INFINITY)
victim.visible_message("<span class='danger'>[victim] chokes up a spray of blood from the blow to [victim.p_their()] chest!</span>", "<span class='danger'><b>You choke up on a spray of blood from the blow to your chest!</b></span>", vision_distance=COMBAT_MESSAGE_RANGE)
victim.bleed(blood_bled)
new /obj/effect/temp_visual/dir_setting/bloodsplatter(victim.loc, victim.dir)
if(ishuman(victim))
var/mob/living/carbon/human/H = victim
new /obj/effect/temp_visual/dir_setting/bloodsplatter(victim.loc, victim.dir, H.dna.species.exotic_blood_color)
else
new /obj/effect/temp_visual/dir_setting/bloodsplatter(victim.loc, victim.dir)
victim.add_splatter_floor(get_step(victim.loc, victim.dir))
+10 -2
View File
@@ -39,12 +39,20 @@
victim.bleed(blood_bled, TRUE)
if(14 to 19)
victim.visible_message("<span class='smalldanger'>A small stream of blood spurts from the hole in [victim]'s [limb.name]!</span>", "<span class='danger'>You spit out a string of blood from the blow to your [limb.name]!</span>", vision_distance=COMBAT_MESSAGE_RANGE)
new /obj/effect/temp_visual/dir_setting/bloodsplatter(victim.loc, victim.dir)
if(ishuman(victim))
var/mob/living/carbon/human/H = victim
new /obj/effect/temp_visual/dir_setting/bloodsplatter(victim.loc, victim.dir, H.dna.species.exotic_blood_color)
else
new /obj/effect/temp_visual/dir_setting/bloodsplatter(victim.loc, victim.dir)
victim.bleed(blood_bled)
if(20 to INFINITY)
victim.visible_message("<span class='danger'>A spray of blood streams from the gash in [victim]'s [limb.name]!</span>", "<span class='danger'><b>You choke up on a spray of blood from the blow to your [limb.name]!</b></span>", vision_distance=COMBAT_MESSAGE_RANGE)
victim.bleed(blood_bled)
new /obj/effect/temp_visual/dir_setting/bloodsplatter(victim.loc, victim.dir)
if(ishuman(victim))
var/mob/living/carbon/human/H = victim
new /obj/effect/temp_visual/dir_setting/bloodsplatter(victim.loc, victim.dir, H.dna.species.exotic_blood_color)
else
new /obj/effect/temp_visual/dir_setting/bloodsplatter(victim.loc, victim.dir)
victim.add_splatter_floor(get_step(victim.loc, victim.dir))
/datum/wound/pierce/handle_process()