Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master'
Browse files Browse the repository at this point in the history
  • Loading branch information
SyncIt21 committed Jan 1, 2025
2 parents 21ff04d + 91cbc92 commit c74d558
Show file tree
Hide file tree
Showing 32 changed files with 726 additions and 154 deletions.
2 changes: 2 additions & 0 deletions code/__DEFINES/bodyparts.dm
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,5 @@
// Color priorities for bodyparts
#define LIMB_COLOR_HULK 10
#define LIMB_COLOR_CARP_INFUSION 20
/// Base priority for atom colors, gets atom priorities added to it
#define LIMB_COLOR_ATOM_COLOR 30
2 changes: 2 additions & 0 deletions code/__DEFINES/colors.dm
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,8 @@ GLOBAL_LIST_INIT(heretic_path_to_color, list(

// Lowest priority
#define EYE_COLOR_ORGAN_PRIORITY 1
/// Base priority for atom colors, gets atom priorities added to it
#define EYE_COLOR_ATOM_COLOR_PRIORITY 2
#define EYE_COLOR_SPECIES_PRIORITY 10
#define EYE_COLOR_WEED_PRIORITY 20
#define EYE_COLOR_CULT_PRIORITY 30
3 changes: 1 addition & 2 deletions code/controllers/subsystem/persistence/trophy_fishes.dm
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,9 @@
fish.set_custom_materials(mat_list)
fish.persistence_load(data)
fish.name = data[PERSISTENCE_FISH_NAME]
mount.catcher_name = data[PERSISTENCE_FISH_CATCHER]
mount.catch_date = data[PERSISTENCE_FISH_CATCH_DATE]
fish.set_status(FISH_DEAD, silent = TRUE)
mount.add_fish(fish, from_persistence = TRUE, catcher = data[PERSISTENCE_FISH_CATCHER])
mount.catch_date = data[PERSISTENCE_FISH_CATCH_DATE]

/datum/controller/subsystem/persistence/proc/save_trophy_fish(obj/structure/fish_mount/mount)
var/obj/item/fish/fish = mount.mounted_fish
Expand Down
2 changes: 1 addition & 1 deletion code/controllers/subsystem/polling.dm
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ SUBSYSTEM_DEF(polling)
act_never = "[custom_link_style_start]<a href='byond://?src=[REF(poll_alert_button)];never=1'[custom_link_style_end]>\[Never For This Round\]</a>"

if(!duplicate_message_check(alert_poll)) //Only notify people once. They'll notice if there are multiple and we don't want to spam people.
SEND_SOUND(candidate_mob, 'sound/announcer/notice/notice2.ogg')
SEND_SOUND(candidate_mob, sound('sound/misc/prompt.ogg', volume = 70))
var/surrounding_icon
if(chat_text_border_icon)
var/image/surrounding_image
Expand Down
9 changes: 8 additions & 1 deletion code/datums/ai_laws/ai_laws.dm
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,15 @@ GLOBAL_VAR(round_default_lawset)
/proc/pick_weighted_lawset()
var/datum/ai_laws/lawtype
var/list/law_weights = CONFIG_GET(keyed_list/law_weight)
var/list/specified_law_ids = CONFIG_GET(keyed_list/specified_laws)

if(HAS_TRAIT(SSstation, STATION_TRAIT_UNIQUE_AI))
law_weights -= AI_LAWS_ASIMOV
switch(CONFIG_GET(number/default_laws))
if(CONFIG_ASIMOV)
law_weights -= AI_LAWS_ASIMOV
if(CONFIG_CUSTOM)
law_weights -= specified_law_ids

while(!lawtype && law_weights.len)
var/possible_id = pick_weight(law_weights)
lawtype = lawid_to_type(possible_id)
Expand Down
2 changes: 1 addition & 1 deletion code/datums/elements/decals/blood.dm
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
var/icon/icon_for_size = icon(icon, icon_state)
var/scale_factor_x = icon_for_size.Width()/ICON_SIZE_X
var/scale_factor_y = icon_for_size.Height()/ICON_SIZE_Y
var/mutable_appearance/blood_splatter = mutable_appearance('icons/effects/blood.dmi', "itemblood", appearance_flags = RESET_COLOR) //MA of the blood that we apply
var/mutable_appearance/blood_splatter = mutable_appearance('icons/effects/blood.dmi', "itemblood", appearance_flags = KEEP_APART|RESET_COLOR) //MA of the blood that we apply
blood_splatter.transform = blood_splatter.transform.Scale(scale_factor_x, scale_factor_y)
blood_splatter.blend_mode = BLEND_INSET_OVERLAY
blood_splatter.color = _color
Expand Down
6 changes: 6 additions & 0 deletions code/game/objects/effects/spawners/random/trash.dm
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@
/obj/effect/spawner/random/entertainment/cigar = 1,
/obj/item/stack/ore/gold = 1,
)
/obj/effect/spawner/random/trash/deluxe_garbage/Initialize(mapload)
if(mapload)
var/turf/location = get_turf(loc)
if(location.initial_gas_mix != OPENTURF_DEFAULT_ATMOS && location.initial_gas_mix != OPENTURF_DIRTY_ATMOS)
loot -= /mob/living/basic/mouse
return ..()

/obj/effect/spawner/random/trash/cigbutt
name = "cigarette butt spawner"
Expand Down
38 changes: 8 additions & 30 deletions code/game/objects/items/crayons.dm
Original file line number Diff line number Diff line change
Expand Up @@ -829,9 +829,7 @@
if(isbodypart(target))
var/obj/item/bodypart/limb = target
if(IS_ROBOTIC_LIMB(limb))
context[SCREENTIP_CONTEXT_CTRL_LMB] = "Restyle robotic limb"
else
context[SCREENTIP_CONTEXT_CTRL_LMB] = "Copy color"
context[SCREENTIP_CONTEXT_LMB] = "Restyle robotic limb"

return CONTEXTUAL_SCREENTIP_SET

Expand Down Expand Up @@ -887,16 +885,17 @@
return ..()

/obj/item/toy/crayon/spraycan/use_on(atom/target, mob/user, list/modifiers)
if (LAZYACCESS(modifiers, CTRL_CLICK))
return ctrl_interact(target, user)

if(is_capped)
balloon_alert(user, "take the cap off first!")
return ITEM_INTERACT_BLOCKING

if(check_empty(user))
return ITEM_INTERACT_BLOCKING

if (isbodypart(target))
if (color_limb(target, user))
return ITEM_INTERACT_SUCCESS

if(iscarbon(target))
if(pre_noise || post_noise)
playsound(user.loc, 'sound/effects/spray.ogg', 25, TRUE, 5)
Expand Down Expand Up @@ -994,30 +993,9 @@
user.visible_message(span_notice("[user] coats [target] with spray paint!"), span_notice("You coat [target] with spray paint."))
return ITEM_INTERACT_SUCCESS

/obj/item/toy/crayon/spraycan/proc/ctrl_interact(atom/interacting_with, mob/living/user)
if(is_capped)
if(!interacting_with.color)
// let's be generous and assume if they're trying to match something with no color, while capped,
// we shouldn't be blocking further interactions
return NONE
balloon_alert(user, "take the cap off first!")
return ITEM_INTERACT_BLOCKING

if(check_empty(user))
return ITEM_INTERACT_BLOCKING

if(!isbodypart(interacting_with) || !actually_paints)
if(interacting_with.color)
paint_color = interacting_with.color
balloon_alert(user, "matched colour of target")
update_appearance()
return ITEM_INTERACT_BLOCKING
balloon_alert(user, "can't match those colours!")
return ITEM_INTERACT_BLOCKING

var/obj/item/bodypart/limb = interacting_with
/obj/item/toy/crayon/spraycan/proc/color_limb(obj/item/bodypart/limb, mob/living/user)
if(!IS_ROBOTIC_LIMB(limb))
return ITEM_INTERACT_BLOCKING
return FALSE

var/list/skins = list()
var/static/list/style_list_icons = list(
Expand All @@ -1036,7 +1014,7 @@
if(choice && (use_charges(user, 5, requires_full = FALSE)))
playsound(user.loc, 'sound/effects/spray.ogg', 5, TRUE, 5)
limb.change_appearance(style_list_icons[choice], greyscale = FALSE)
return ITEM_INTERACT_SUCCESS
return TRUE

/obj/item/toy/crayon/spraycan/click_alt(mob/user)
if(!has_cap)
Expand Down
198 changes: 198 additions & 0 deletions code/game/objects/items/forensicsspoofer.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
/obj/item/forensics_spoofer
name = /obj/item/detective_scanner::name
desc = "Used to adjacently scan objects and biomass for fibers and fingerprints. Can replicate the findings."
icon = /obj/item/detective_scanner::icon
icon_state = /obj/item/detective_scanner::icon_state
w_class = WEIGHT_CLASS_SMALL
inhand_icon_state = /obj/item/detective_scanner::inhand_icon_state
worn_icon_state = /obj/item/detective_scanner::worn_icon_state
lefthand_file = /obj/item/detective_scanner::lefthand_file
righthand_file = /obj/item/detective_scanner::righthand_file
obj_flags = CONDUCTS_ELECTRICITY
item_flags = NOBLUDGEON
slot_flags = ITEM_SLOT_BELT
/// stored fibers in memory
var/list/fibers = list()
/// stored fingerprints in memory
var/list/fingerprints = list()
/// chosen fiber to add to target
var/chosen_fiber
/// chosen fingerprint to add to target
var/chosen_fingerprint
/// max storage for fibers/fingerprints seperate for each
var/max_storage = 5
/// do we scan for new material? if false will tamper
var/scan_mode = TRUE
/// do we make forensics scanner messages and sounds
var/silent_mode = FALSE
/// tamper cooldown time so people dont spam it on every single wall and thing ever
var/tamper_cooldown_time = 1 SECONDS
COOLDOWN_DECLARE(tamper_cooldown)

/obj/item/forensics_spoofer/Initialize(mapload)
. = ..()
// most things have add_fingerprint in their item interaction because lol lmao
// tl;dr cut off the chain before anything fires so we dont add user fingerprints to target
RegisterSignal(src, COMSIG_ITEM_INTERACTING_WITH_ATOM, PROC_REF(do_interact))

/obj/item/forensics_spoofer/attack_self_secondary(mob/user, modifiers)
. = ..()
if(.)
return
scan_mode = !scan_mode
balloon_alert(user, "now [scan_mode ? "scanning" : "applying"]")
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN

// ok due to shenanigans basically every item interact adds your fingerprints to it which isnt ideal so we have this
/obj/item/forensics_spoofer/proc/do_interact(datum/source, mob/living/user, atom/interacting_with, list/modifiers)
SIGNAL_HANDLER
if(scan_mode)
INVOKE_ASYNC(src, PROC_REF(scan), interacting_with, user)
else
tamper(interacting_with, user, do_fibers = !isnull(chosen_fiber))
return ITEM_INTERACT_SUCCESS

/obj/item/forensics_spoofer/proc/do_fake_scan(atom/target, mob/user)
if(silent_mode)
return
playsound(src, SFX_INDUSTRIAL_SCAN, 20, TRUE, -2, TRUE, FALSE)
user.visible_message(
span_notice("\The [user] points the [name] at \the [target] and performs a forensic scan.")
)

/obj/item/forensics_spoofer/proc/clear_values(list/the_list)
for(var/key in the_list)
the_list[key] = ""

/obj/item/forensics_spoofer/proc/scan(atom/target, mob/living/user)
do_fake_scan(target, user)
if(isnull(target.forensics))
target.balloon_alert(user, "nothing!")
return ITEM_INTERACT_FAILURE
var/list/new_fibers = LAZYCOPY(target.forensics.fibers) - fibers
var/list/new_prints = LAZYCOPY(target.forensics.fingerprints) - fingerprints
var/new_len = length(new_fibers) + length(new_prints)
balloon_alert(user, "[new_len ? new_len : "no"] new prints/fibers")
if(new_len)
var/list/message = list(span_bold("Scan results (Unstored Only):"))
for(var/text in new_fibers)
message += span_notice("Fiber: [text]")
if(length(fibers) > max_storage)
message += span_boldwarning("Fiber storage full.")
for(var/text in new_prints)
message += span_notice("Fingerprint: [text]")
if(length(fingerprints) > max_storage)
message += span_boldwarning("Fingerprint storage full.")
to_chat(user, boxed_message(jointext(message, "\n")), type = MESSAGE_TYPE_INFO)
if(length(fingerprints) < max_storage)
while(length(fingerprints) + length(new_prints) > max_storage)
var/to_remove = tgui_input_list(user, "Too many prints, cancel to discard all", "What to discard", new_fibers)
if(isnull(to_remove))
return ITEM_INTERACT_FAILURE
new_prints -= to_remove
clear_values(new_prints)
fingerprints += new_prints
for(var/fingerprint in fingerprints)
fingerprints[fingerprint] = get_name_from_fingerprint(fingerprint)
if(length(fibers) < max_storage)
while(length(fibers) + length(new_fibers) > max_storage)
var/to_remove = tgui_input_list(user, "Too many prints, cancel to discard all", "What to discard", new_fibers)
if(isnull(to_remove))
return ITEM_INTERACT_FAILURE
new_fibers -= to_remove
clear_values(new_fibers)
fibers += new_fibers
return ITEM_INTERACT_SUCCESS

/obj/item/forensics_spoofer/proc/tamper(atom/target, mob/living/user, do_fibers = FALSE)
do_fake_scan(target, user)
if((!do_fibers && isnull(chosen_fingerprint)) || (do_fibers && isnull(chosen_fiber)))
balloon_alert(user, "no [do_fibers ? "fiber" : "fingerprint"] selected!") // we CAN automatically select it but if they dont have it selected then they likely didnt know of it in the first place so they learn it now
return ITEM_INTERACT_FAILURE
if(!COOLDOWN_FINISHED(src, tamper_cooldown))
balloon_alert(user, "please wait!")
return ITEM_INTERACT_FAILURE
if(!isnull(target.forensics) && LAZYFIND(do_fibers ? target.forensics.fibers : target.forensics.fingerprints, do_fibers ? chosen_fiber : chosen_fingerprint))
balloon_alert(user, "already present!")
return ITEM_INTERACT_FAILURE

if(do_fibers)
target.add_fiber_list(list(chosen_fiber))
user.log_message("has tampered with the fingerprints/fibers of [src]. Added [chosen_fiber]", LOG_ATTACK)
else
target.add_fingerprint_list(list(chosen_fingerprint))
user.log_message("has tampered with the fingerprints/fibers of [src]. Added [chosen_fingerprint]", LOG_ATTACK)

target.balloon_alert(user, "[do_fibers ? "fiber" : "fingerprint"] added")
target.add_hiddenprint(user)
COOLDOWN_START(src, tamper_cooldown, tamper_cooldown_time)

return ITEM_INTERACT_SUCCESS

/obj/item/forensics_spoofer/proc/get_name_from_fingerprint(fingerprint)
. = "Unknown"
for(var/datum/record/crew/player_record as anything in GLOB.manifest.general)
if(player_record.fingerprint != fingerprint)
continue
return player_record.name

/obj/item/forensics_spoofer/ui_state(mob/user)
return GLOB.hands_state

/obj/item/forensics_spoofer/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "ForensicsSpoofer", name)
ui.open()

/obj/item/forensics_spoofer/ui_static_data(mob/user)
. = list(
"max_storage" = max_storage,
)

/obj/item/forensics_spoofer/ui_data(mob/user)
return list(
"scanmode" = scan_mode,
"silent" = silent_mode,
"fibers" = fibers,
"fingerprints" = fingerprints,
"chosen_fiber" = chosen_fiber,
"chosen_fingerprint" = chosen_fingerprint,
)

/obj/item/forensics_spoofer/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(.)
return
if(!isnull(params["chosen"])) //fiber/print actions
var/chosen = params["chosen"]
switch(action)
if("delete")
if(chosen in fibers)
if(chosen_fiber == chosen)
chosen_fiber = null
fibers -= chosen
else
if(chosen_fingerprint == chosen)
chosen_fingerprint = null
fingerprints -= chosen
return TRUE
if("choose")
var/is_fiber = !!(chosen in fibers)
chosen_fiber = is_fiber ? chosen : null
chosen_fingerprint = is_fiber ? null : chosen
return TRUE
if("make_note")
if(chosen in fibers)
fibers[chosen] = params["note"]
else
fingerprints[chosen] = params["note"]
return TRUE
else
switch(action)
if("scanmode")
scan_mode = !scan_mode
return TRUE
if("stealth")
silent_mode = !silent_mode
return TRUE
16 changes: 16 additions & 0 deletions code/game/objects/items/storage/uplink_kits.dm
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,22 @@
new /obj/item/gun/ballistic/rifle/rebarxbow/syndie(src)
new /obj/item/storage/bag/rebar_quiver/syndicate(src)

/obj/item/paper/syndicate_forensics_spoofer
name = "Forensics Spoofer Guide"
default_raw_text = {"
<b>Forensics Spoofer Info:</b><br>
The spoofer has two modes: <b>SCAN</b> which scans for fingerprints and fibers, and <b>APPLY</b> which applies the currently chosen fingerprint/fiber to your target.<br>
The spoofer can only store 5 fingerprints and 5 fibers, and may not store or report fibers/prints already stored. Additionally, it taps into the stations network to associate scanned fingerprints with names.<br>
The spoofer will make the same sounds and sights as a forensics scanner, when <b>silent mode</b> is <b>off</b>.<br>
"}

/obj/item/storage/box/syndie_kit/forensics_spoofer
name = "forensics spoofing kit"

/obj/item/storage/box/syndie_kit/forensics_spoofer/PopulateContents()
new /obj/item/forensics_spoofer(src)
new /obj/item/paper/syndicate_forensics_spoofer(src)

/obj/item/storage/box/syndie_kit/origami_bundle
name = "origami kit"
desc = "A box full of a number of rather masterfully engineered paper planes and a manual on \"The Art of Origami\"."
Expand Down
9 changes: 4 additions & 5 deletions code/game/turfs/baseturfs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -112,17 +112,16 @@
/// Replaces all instances of needle_type in baseturfs with replacement_type
/turf/proc/replace_baseturf(needle_type, replacement_type)
if (islist(baseturfs))
var/list/new_baseturfs
var/list/new_baseturfs = baseturfs.Copy()

while (TRUE)
var/found_index = baseturfs.Find(needle_type)
for(var/base_i in 1 to length(new_baseturfs))
var/found_index = new_baseturfs.Find(needle_type)
if (found_index == 0)
break

new_baseturfs ||= baseturfs.Copy()
new_baseturfs[found_index] = replacement_type

if (!isnull(new_baseturfs))
if (length(new_baseturfs))
baseturfs = baseturfs_string_list(new_baseturfs, src)
else if (baseturfs == needle_type)
baseturfs = replacement_type
Expand Down
Loading

0 comments on commit c74d558

Please sign in to comment.