Merge branch 'master' into upstream-2026-06-23

This commit is contained in:
Roxy
2026-06-23 18:01:04 -04:00
56 changed files with 128721 additions and 209 deletions
File diff suppressed because it is too large Load Diff
+23
View File
@@ -0,0 +1,23 @@
{
"version": 1,
"map_name": "Pubby Station",
"map_path": "map_files/PubbyStation",
"map_file": "PubbyStation.dmm",
"shuttles": {
"emergency": "emergency_pubby",
"whiteship": "whiteship_pubby",
"ferry": "ferry_fancy",
"cargo": "cargo_box"
},
"job_changes": {
"Cook": {
"additional_cqc_areas": ["/area/station/service/bar/atrium"]
},
"Chaplain": {
"has_monastery": 1
},
"Prisoner": {
"spawn_positions": 2
}
}
}
@@ -31,6 +31,8 @@
/// Whether the menu is in the middle of refreshing the preview
var/refreshing = TRUE
///BUBBER VAR: Optional display labels for color groups, keyed by color index.
var/list/color_labels
/**
* Whether the menu is currently locked down to prevent abuse from players.
@@ -102,12 +104,14 @@
/datum/greyscale_modify_menu/ui_data(mob/user)
var/list/data = list()
data["greyscale_config"] = "[config.name]"
data["full_color_string"] = split_colors?.Join("") //BUBBER ADDITION
var/list/color_data = list()
data["colors"] = color_data
for(var/i in 1 to config.expected_colors)
color_data += list(list(
"index" = i,
"label" = color_labels?["[i]"] || color_labels?[i], //BUBBER ADDITION
"value" = split_colors[i]
))
+5 -1
View File
@@ -407,7 +407,11 @@ GLOBAL_LIST_INIT(all_loadout_categories, init_loadout_categories())
formatted_item["reskins"] = get_reskin_options()
formatted_item["icon"] = ui_icon
formatted_item["icon_state"] = ui_icon_state
formatted_item["ckey_whitelist"] = ckeywhitelist // BUBBER EDIT ADDITION: Filter ckey-locked items
//BUBBER EDIT ADDITION START - Dynamic Uniforms + ckey lock filter
if(initial(item_path.greyscale_component_style_type))
add_component_style_preview_to_ui_data(formatted_item)
formatted_item["ckey_whitelist"] = ckeywhitelist
//BUBBER EDIT ADDITION END
return formatted_item
+5
View File
@@ -204,6 +204,11 @@
if(greyscale_colors)
vended_item.set_greyscale(colors=greyscale_colors)
//BUBBER ADDITION START - dynamic uniforms
var/list/component_style_details = greyscale_component_style_vend_details?[REF(user)]
if(component_style_details && vended_item.greyscale_component_style_type)
vended_item.apply_greyscale_component_style(component_style_details)
//BUBBER ADDITION END
if(IsReachableBy(user) && user.put_in_hands(vended_item))
to_chat(user, span_notice("You take [item_record.name] out of the slot."))
vended_item.do_pickup_animation(user, src)
+18 -1
View File
@@ -44,11 +44,22 @@
)
var/atom/printed = record.product_path
//BUBBER ADDITION START - Dynamic uniforms
var/component_preview_applied = FALSE
if(ispath(printed, /obj/item))
var/obj/item/printed_item = printed
if(initial(printed_item.greyscale_component_style_type))
static_record["image"] = get_component_style_vending_preview_image(printed_item)
component_preview_applied = TRUE
//BUBBER ADDITION END
// If it's not GAGS and has no innate colors we have to care about, we use DMIcon
if(ispath(printed, /atom) \
//BUBBER EDIT START - dynamic uniform
if(!component_preview_applied \
&& ispath(printed, /atom) \
&& (!initial(printed.greyscale_config) || !initial(printed.greyscale_colors)) \
&& !initial(printed.color) \
)
//BUBBER EDIT END
static_record["icon"] = initial(printed.icon)
static_record["icon_state"] = initial(printed.icon_state)
@@ -147,6 +158,12 @@
if(!istype(product))
return FALSE
var/atom/fake_atom = product.product_path
//BUBBER ADDITION START - Dynamic uniforms
if(ispath(fake_atom, /obj/item))
var/obj/item/fake_item = fake_atom
if(initial(fake_item.greyscale_component_style_type))
return open_component_style_vending_menu(product, params, ui.user)
//BUBBER ADDITION END
var/config = initial(fake_atom.greyscale_config)
if(!config)
return FALSE
+5
View File
@@ -72,6 +72,11 @@ map kilostation
webmap_url https://webmap.affectedarc07.co.uk/maps/bubber/kilostation/
endmap
map PubbyStation
votable
webmap_url https://webmap.affectedarc07.co.uk/maps/fulp/pubbystation/
endmap
map moonstation
votable
webmap_url https://webmap.affectedarc07.co.uk/maps/bubber/moonstation/
@@ -0,0 +1,4 @@
author: "Robwo"
delete-after: True
changes:
- map: "Risk of Pubby Station Returns"
@@ -0,0 +1,5 @@
author: "KateAnbolho, vinylspiders"
delete-after: True
changes:
- rscadd: "The VOSKHOD Modular Suit, and the systems needed to create it."
- rscadd: "Depowered VOSKHOD armor can be sourced from \"questionable\" sources."
@@ -0,0 +1,5 @@
author: "UvvU"
delete-after: True
changes:
- rscadd: "Added dynamic uniform support"
- rscadd: "Added black varients for the sec huds, belt, and gloves"
@@ -0,0 +1,5 @@
author: "nikothedude"
delete-after: True
changes:
- rscadd: "Re-adds the modular laser weapons to the game, with the rifle being found in a traitor's uplink, and the carbine being crewsided."
- balance: "Tweaked the modular weapons to be less powerful overall."
Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 462 B

After

Width:  |  Height:  |  Size: 509 B

@@ -266,7 +266,7 @@ SUBSYSTEM_DEF(area_spawn)
/// See code/__DEFINES/~skyrat_defines/automapper.dm
var/mode = AREA_SPAWN_MODE_OPEN
/// Map blacklist, this is used to determine what maps we should not spawn on.
var/list/blacklisted_stations = list("Void Raptor", "Ouroboros", "Runtime Station", "Minimal Runtime Station", "MultiZ Debug", "Gateway Test", "Blueshift", "Biodome", "Moon Station", "Box Station", "NorthStar")
var/list/blacklisted_stations = list("Void Raptor", "Ouroboros", "Runtime Station", "Minimal Runtime Station", "MultiZ Debug", "Gateway Test", "Blueshift", "Biodome", "Moon Station", "Box Station", "NorthStar", "Pubby Station")
/// If failing to find a suitable area is OK, then this should be TRUE or CI will fail.
/// Should probably be true if the target_areas are random, such as ruins.
var/optional = FALSE
@@ -1,11 +1,11 @@
/datum/outfit/centcom/ert/security
l_hand = /obj/item/gun/energy/modular_laser_rifle/carbine
l_hand = /obj/item/gun/energy/e_gun/nuclear
/datum/outfit/centcom/ert/medic
r_hand = /obj/item/gun/energy/modular_laser_rifle/carbine
r_hand = /obj/item/gun/energy/e_gun/nuclear
/datum/outfit/centcom/ert/engineer
l_hand = /obj/item/gun/energy/modular_laser_rifle/carbine
l_hand = /obj/item/gun/energy/e_gun/nuclear
/datum/outfit/centcom/ert/commander
l_hand = /obj/item/gun/energy/modular_laser_rifle
@@ -12,7 +12,7 @@
/obj/item/gun/energy/modular_laser_rifle
name = "\improper Hyeseong modular laser rifle"
desc = "A popular energy weapon system that can be reconfigured into many different variants on the fly. \
Seen commonly amongst the Marsians who produce the weapon, with many different shapes and sizes to fit \
Seen commonly amongst the Martians who produce the weapon, with many different shapes and sizes to fit \
the wide variety of modders the planet is home to."
base_icon_state = "hyeseong"
icon = 'modular_skyrat/modules/modular_weapons/icons/obj/company_and_or_faction_based/saibasan/guns48x.dmi'
@@ -22,6 +22,7 @@
inhand_icon_state = "hyeseong_kill"
worn_icon = 'modular_skyrat/modules/modular_weapons/icons/mob/company_and_or_faction_based/saibasan/guns_worn.dmi'
worn_icon_state = "hyeseong_kill"
selfcharge = FALSE
cell_type = /obj/item/stock_parts/power_store/cell/hyeseong_internal_cell
modifystate = FALSE
ammo_type = list(/obj/item/ammo_casing/energy/cybersun_big_kill)
@@ -29,6 +30,7 @@
ammo_x_offset = 0
selfcharge = 1
charge_delay = 15
charge_sections = 3
shaded_charge = TRUE
slot_flags = ITEM_SLOT_BACK
obj_flags = UNIQUE_RENAME
@@ -69,7 +71,7 @@
/// Keeps track of our soulcatcher component
var/datum/component/carrier/soulcatcher/tracked_soulcatcher
/// What is this gun's extended examine, we only have to do this because the carbine is a subtype
var/expanded_examine_text = "The Hyeseong rifle is the first line of man-portable Marsian weapons platforms \
var/expanded_examine_text = "The Hyeseong rifle is the first line of man-portable Martian weapons platforms \
from Cybersun Industries. Like her younger sister weapon, the Hoshi carbine, CI used funding aid provided \
by TerraGov to develop a portable weapon fueled by a proprietary generator rumored to be fueled by superstable plasma. \
A rugged and hefty weapon, the Hyeseong stars in applications anywhere from medium to long ranges, though struggling \
@@ -261,12 +263,11 @@
inhand_icon_state = "hoshi_kill"
worn_icon_state = "hoshi_kill"
base_icon_state = "hoshi"
charge_sections = 3
cell_type = /obj/item/stock_parts/power_store/cell
cell_type = /obj/item/stock_parts/power_store/cell/hyeseong_internal_cell
ammo_type = list(/obj/item/ammo_casing/energy/cybersun_small_hellfire)
slot_flags = ITEM_SLOT_BACK | ITEM_SLOT_BELT
SET_BASE_PIXEL(0, 0)
w_class = WEIGHT_CLASS_NORMAL
w_class = WEIGHT_CLASS_BULKY
weapon_weight = WEAPON_MEDIUM
weapon_mode_options = list(
/datum/laser_weapon_mode/hellfire,
@@ -275,11 +276,11 @@
/datum/laser_weapon_mode/shotgun_small,
/datum/laser_weapon_mode/trickshot_disabler,
)
default_selected_mode = "Incinerate"
default_selected_mode = "kill"
speech_json_file = SHORT_MOD_LASER_SPEECH
expanded_examine_text = "The Hoshi carbine is the latest line of man-portable Marsian weapons platforms from \
expanded_examine_text = "The Hoshi carbine is the latest line of man-portable Martian weapons platforms from \
Cybersun Industries. Like her older sister weapon, the Hyeseong rifle, CI used funding aid provided by TerraGov \
to develop a portable weapon fueled by a proprietary generator rumored to be fueled by superstable plasma. A \
to develop a portable weapon using the same modular weapon system as the Hyeseong, but unforunately lacking the plasma generator. A \
lithe and mobile weapon, the Hoshi stars in close-quarters battle, trickshots, and area-of-effect blasts; though \
ineffective at ranged combat. Her onboard machine intelligence, at first devised to support the operator and \
manage the internal reactor, was originally shipped with a more energetic personality-- since influenced by 'negligence' \
@@ -53,7 +53,7 @@
var/datum/component/scope/scope_component
/datum/laser_weapon_mode/marksman/apply_to_weapon(obj/item/gun/energy/applied_gun)
scope_component = applied_gun.AddComponent(/datum/component/scope, 3)
scope_component = applied_gun.AddComponent(/datum/component/scope, 2)
/datum/laser_weapon_mode/marksman/remove_from_weapon(obj/item/gun/energy/applied_gun)
QDEL_NULL(scope_component)
@@ -110,11 +110,11 @@
// Hellfire mode for the small laser
/datum/laser_weapon_mode/hellfire
name = "Incinerate"
name = "kill"
casing = /obj/item/ammo_casing/energy/cybersun_small_hellfire
weapon_icon_state = "kill"
charge_sections = 3
shot_delay = 0.4 SECONDS
shot_delay = 0.5 SECONDS
json_speech_string = "incinerate"
gun_runetext_color = "#cd4456"
@@ -131,15 +131,15 @@
// And also so the visuals work :3
casing = /obj/item/ammo_casing/energy/cybersun_small_blade
weapon_icon_state = "blade"
charge_sections = 2
charge_sections = 3
json_speech_string = "blade"
gun_runetext_color = "#f8d860"
/datum/laser_weapon_mode/sword/apply_to_weapon(obj/item/gun/energy/modular_laser_rifle/applied_gun)
playsound(src, 'sound/items/unsheath.ogg', 25, TRUE)
applied_gun.force = 20
applied_gun.force = 15
applied_gun.sharpness = SHARP_EDGED
applied_gun.exposed_wound_bonus = 15
applied_gun.exposed_wound_bonus = 10
applied_gun.wound_bonus = 5
applied_gun.disabled_for_other_reasons = TRUE
applied_gun.attack_verb_continuous = list("slashes", "cuts")
@@ -63,7 +63,7 @@
range = 6
impact_effect_type = /obj/effect/temp_visual/impact_effect/green_laser
light_color = COLOR_PALE_GREEN
pass_flags = PASSTABLE | PASSGRILLE // His ass does NOT pass through glass!
pass_flags = PASSTABLE // His ass does NOT pass through glass!
/// What type of casing should we put inside the bullet to act as shrapnel later
var/casing_to_spawn = /obj/item/grenade/c980payload/plasma_grenade
@@ -91,7 +91,7 @@
exposed_wound_bonus = 55 // Lasers have a wound bonus of 40, this is a bit higher
wound_bonus = -50 // However we do not very much against armor
range = 2
pass_flags = PASSTABLE | PASSGRILLE // His ass does NOT pass through glass!
pass_flags = PASSTABLE // His ass does NOT pass through glass!
weak_against_armour = TRUE
impact_effect_type = /obj/effect/temp_visual/impact_effect/green_laser
light_color = COLOR_PALE_GREEN
@@ -124,15 +124,14 @@
/obj/item/ammo_casing/energy/cybersun_small_hellfire
projectile_type = /obj/projectile/beam/cybersun_laser/hellfire
e_cost = LASER_SHOTS(10, STANDARD_CELL_CHARGE)
e_cost = LASER_SHOTS(7, STANDARD_CELL_CHARGE)
select_name = "Incinerate"
fire_sound = 'modular_zubbers/sound/weapons/incinerate.ogg'
/obj/projectile/beam/cybersun_laser/hellfire
icon_state = "hellfire"
damage = 30
damage = 25
impact_effect_type = /obj/effect/temp_visual/impact_effect/red_laser
speed = 1.6
wound_bonus = 0
light_color = COLOR_SOFT_RED
@@ -143,12 +142,15 @@
e_cost = LASER_SHOTS(10, STANDARD_CELL_CHARGE)
select_name = "Disable"
harmful = FALSE
fire_sound = 'sound/items/weapons/taser2.ogg'
impact_sound = 'sound/items/weapons/sear_disabler.ogg'
/obj/projectile/beam/cybersun_laser/disable_bounce
icon_state = "disable_bounce"
damage = 0
stamina = 30
stamina = 20
impact_effect_type = /obj/effect/temp_visual/impact_effect/blue_laser
impact_sound = 'sound/items/weapons/sear_disabler.ogg'
light_color = COLOR_BRIGHT_BLUE
ricochet_auto_aim_angle = 30
ricochet_auto_aim_range = 5
@@ -172,7 +174,7 @@
/obj/projectile/beam/cybersun_laser/flare
name = "plasma flare"
icon_state = "flare"
damage = 30
damage = 40
speed = 0.5
range = 6
impact_effect_type = /obj/effect/temp_visual/impact_effect/green_laser
@@ -220,7 +222,7 @@
/obj/item/ammo_casing/energy/cybersun_small_shotgun
projectile_type = /obj/projectile/beam/cybersun_laser/granata_shrapnel/shotgun_pellet
e_cost = LASER_SHOTS(10, STANDARD_CELL_CHARGE)
e_cost = LASER_SHOTS(7, STANDARD_CELL_CHARGE)
pellets = 3
variance = 20
select_name = "Shotgun"
@@ -45,3 +45,10 @@
if(LAZYLEN(the_piece.magazine.stored_ammo))
return FALSE
return ..()
/obj/item/crafting_conversion_kit/voskhod_refit
name = "\improper Voskhod depowered armor MOD refit kit"
desc = "A metallic case of various tubes, sensors and spare materials required to reuse Voskhod's components in the making of a next-generation MODed version."
force = 10
icon = 'modular_zubbers/icons/misc/refitkit.dmi'
icon_state = "refit_kit"
@@ -32,7 +32,7 @@
flash_protect = FLASH_PROTECTION_WELDER
visor_flags_inv = HIDEMASK|HIDEEYES|HIDEFACE|HIDEFACIALHAIR
visor_flags = STOPSPRESSUREDAMAGE
slowdown = 0
slowdown = 1
/obj/item/clothing/head/helmet/space/hev_suit/nri/captain
name = "\improper VOSKHOD-2 powered combat armor helmet"
@@ -48,3 +48,5 @@
name = "\improper VOSKHOD-IN powered combat armor helmet"
desc = "A composite tungsten-plasteel helmet with a lead-lined ballistic nylon inner padding, complete with a deployable airtight polycarbonate visor and respirator system. 'НРИ - Оборонная Коллегия' is imprinted on the back."
icon_state = "nri_engineer"
@@ -125,6 +125,11 @@
/obj/item/mod/control/pre_equipped/frontline/surplus
theme = /datum/mod_theme/frontline/surplus
applied_modules = list(
/obj/item/mod/module/storage,
/obj/item/mod/module/status_readout/operational,
/obj/item/mod/module/flashlight,
)
/datum/supply_pack/imports/surplus_nri_modsuit
name = "Surplus Combat MODsuit Crate"
@@ -234,159 +239,4 @@
to alert anyone nearby that someone has, in fact, died. This specific unit has a clock and operational ID readout."
display_time = TRUE
death_sound = 'modular_skyrat/modules/novaya_ert/sound/flatline.ogg'
///Blatant copy of the adrenaline boost module.
/obj/item/mod/module/auto_doc
name = "MOD automatic paramedical module"
desc = "The reverse-engineered and redesigned medical assistance system, previously used by the now decommissioned VOSKHOD combat armor. \
The technology it uses is very similar to the one of Spider Clan, yet Innovations and Defense Collegium reject any similarities. \
Using a built-in storage of chemical compounds and miniature chemical mixer, it's capable of injecting its user with simple painkillers and coagulants, \
assisting them with their restoration, as long as they don't overdose themselves. However, this system heavily relies on some rarely combat-available chemical compounds to prepare its injections, \
mainly Cryptobiolin, which appear in the user's bloodstream from time to time, and its trivial damage assesment systems are inadequate for complete restoration purposes."
icon_state = "adrenaline_boost"
module_type = MODULE_TOGGLE
incompatible_modules = list(/obj/item/mod/module/adrenaline_boost, /obj/item/mod/module/auto_doc)
cooldown_time = null
complexity = 4
use_energy_cost = DEFAULT_CHARGE_DRAIN * 20
/// What reagent we need to refill?
var/reagent_required = /datum/reagent/cryptobiolin
/// How much of a reagent we need to refill a single boost.
var/reagent_required_amount = 20
/// Maximum amount of reagents this module can hold.
var/reagent_max_amount = 120
/// Health threshold above which the module won't heal.
var/health_threshold = 80
/// Cooldown betwen each treatment.
var/heal_cooldown = 30 SECONDS
/// Timer for the cooldown.
COOLDOWN_DECLARE(heal_timer)
/// Creates chemical container for chemicals and fills it with chemicals. Chemception.
/obj/item/mod/module/auto_doc/Initialize(mapload)
. = ..()
create_reagents(reagent_max_amount)
reagents.add_reagent(reagent_required, reagent_max_amount)
/obj/item/mod/module/auto_doc/on_activation()
. = ..()
if(!.)
return
RegisterSignal(mod.wearer, COMSIG_LIVING_HEALTH_UPDATE, PROC_REF(on_use))
drain_power(use_energy_cost)
/// Heals damage (in fact, injects chems) based on the damage received and certain other variables (a single one), i.e. having more than X amount of health, not having enough needed chemicals or so on.
/obj/item/mod/module/auto_doc/on_use()
if(!COOLDOWN_FINISHED(src, heal_timer))
return FALSE
if(!check_power(use_energy_cost))
balloon_alert(mod.wearer, "not enough charge!")
SEND_SIGNAL(src, COMSIG_MODULE_DEACTIVATED)
return FALSE
if(!(allow_flags & MODULE_ALLOW_PHASEOUT) && istype(mod.wearer.loc, /obj/effect/dummy/phased_mob))
to_chat(mod.wearer, span_warning("You cannot activate this right now."))
return FALSE
if(SEND_SIGNAL(src, COMSIG_MODULE_TRIGGERED) & MOD_ABORT_USE)
return FALSE
if(!reagents.has_reagent(reagent_required, reagent_required_amount))
balloon_alert(mod.wearer, "not enough chems!")
SEND_SIGNAL(src, COMSIG_MODULE_DEACTIVATED)
return FALSE
if(mod.wearer.health > health_threshold)
return
var/new_bruteloss = mod.wearer.get_brute_loss()
var/new_fireloss = mod.wearer.get_fire_loss()
var/new_toxloss = mod.wearer.get_tox_loss()
var/new_stamloss = mod.wearer.get_stamina_loss()
playsound(mod.wearer, 'modular_skyrat/modules/hev_suit/sound/hev/hiss.ogg', 100)
if(new_bruteloss)
mod.wearer.reagents.add_reagent(/datum/reagent/medicine/mine_salve, 10)
mod.wearer.reagents.add_reagent(/datum/reagent/medicine/sal_acid, 5)
to_chat(mod.wearer, span_warning("Brute treatment administered. Overdose risks present on further use, consult your first-aid analyzer."))
if(new_fireloss)
mod.wearer.reagents.add_reagent(/datum/reagent/medicine/mine_salve, 10)
mod.wearer.reagents.add_reagent(/datum/reagent/medicine/oxandrolone, 5)
to_chat(mod.wearer, span_warning("Burn treatment administered. Overdose risks present on further use, consult your first-aid analyzer."))
if(new_toxloss)
mod.wearer.reagents.add_reagent(/datum/reagent/medicine/mine_salve, 10)
mod.wearer.reagents.add_reagent(/datum/reagent/medicine/pen_acid, 5)
to_chat(mod.wearer, span_warning("Toxin treatment administered. Overdose risks present on further use, consult your first-aid analyzer."))
if(new_stamloss)
mod.wearer.reagents.add_reagent(/datum/reagent/medicine/mine_salve, 10)
mod.wearer.reagents.add_reagent(/datum/reagent/medicine/stimulants, 10)
to_chat(mod.wearer, span_warning("Combat stimulants administered. Overdose risks present on further use, consult your first-aid analyzer."))
mod.wearer.reagents.add_reagent(/datum/reagent/medicine/coagulant, 5)
reagents.remove_reagent(reagent_required, reagent_required_amount)
drain_power(use_energy_cost*10)
///Debuff so it's "balanced", as well as a cooldown.
addtimer(CALLBACK(src, PROC_REF(boost_aftereffects), mod.wearer), 45 SECONDS)
COOLDOWN_START(src, heal_timer, heal_cooldown)
/// Refills MODsuit module with the needed chemicals. That's all it does.
/obj/item/mod/module/auto_doc/proc/charge_boost(obj/item/attacking_item, mob/user)
/// Oh, and it also doesn't work if it's (the chemical container) closed.
if(!attacking_item.is_open_container())
return FALSE
/// And if it's already full (:flushed:)
if(reagents.has_reagent(reagent_required, reagent_max_amount))
balloon_alert(mod.wearer, "already full!")
return FALSE
/// And if the reagent's wrong.
if(!attacking_item.reagents.trans_to(src, reagent_required_amount, target_id = reagent_required))
return FALSE
/// And if you got to that point without screwing up then it awards you with being refilled.
balloon_alert(mod.wearer, "charge reloaded")
return TRUE
/obj/item/mod/module/auto_doc/on_deactivation(display_message = TRUE, deleting = FALSE)
. = ..()
UnregisterSignal(mod.wearer, COMSIG_LIVING_HEALTH_UPDATE)
/obj/item/mod/module/auto_doc/on_install()
. = ..()
RegisterSignal(mod, COMSIG_ATOM_ITEM_INTERACTION, PROC_REF(on_item_interact))
/obj/item/mod/module/auto_doc/on_uninstall(deleting)
. = ..()
UnregisterSignal(mod, COMSIG_ATOM_ATTACKBY)
/obj/item/mod/module/auto_doc/attackby(obj/item/attacking_item, mob/user, params)
if(charge_boost(attacking_item, user))
return TRUE
return ..()
/obj/item/mod/module/auto_doc/proc/on_item_interact(datum/source, mob/user, obj/item/thing, params)
SIGNAL_HANDLER
if(charge_boost(thing, user))
return COMPONENT_NO_AFTERATTACK
return NONE
/// Drawbacks to make this module less self-sufficient and so it feels "balanced" (there's no balance).
/obj/item/mod/module/auto_doc/proc/boost_aftereffects(mob/affected_mob)
if(!affected_mob)
return
mod.wearer.reagents.add_reagent(/datum/reagent/cryptobiolin, 10)
mod.wearer.reagents.add_reagent(/datum/reagent/drug/maint/sludge, 5)
to_chat(affected_mob, span_danger("Your head starts slightly spinning, and your chest hurts."))
/// Not exactly a MODsuit thing but it's needed for the refills huh?
/obj/item/reagent_containers/cup/glass/waterbottle/large/cryptobiolin
name = "bottle of cryptobiolin"
desc = "Nothing screams budget cuts like bottled suit fluid."
list_reagents = list(/datum/reagent/cryptobiolin = 100)
removable = TRUE
+13
View File
@@ -0,0 +1,13 @@
/// Tracks selected core components for component-style GAGS loadout items.
#define INFO_GREYSCALE_COMPONENT_CORES "greyscale_component_cores"
/// Tracks selected optional components for component-style GAGS loadout items.
#define INFO_GREYSCALE_COMPONENT_ACCESSORIES "greyscale_component_accessories"
/// Accessory applies to every option in a core component slot.
#define COMPONENT_CORE_ALL "all"
/// Accessory applies only to the named core option.
#define COMPONENT_CORE_EXACT "exact"
/// Accessory applies to the named core option and any option descended from it.
#define COMPONENT_CORE_BRANCH "branch"
/// Accessory applies to a named group defined on the core component.
#define COMPONENT_CORE_GROUP "group"
@@ -8,6 +8,47 @@
icon_file = 'modular_zubbers/icons/mob/clothing/under/captain.dmi'
json_config = 'modular_zubbers/code/datums/greyscale/json_configs/cso.json'
/datum/greyscale_config/security_uniform
name = "Security Uniform"
icon_file = 'modular_zubbers/icons/mob/clothing/under/security.dmi'
greyscale_component_style_type = /datum/greyscale_component_style/security_uniform
greyscale_component_config_states = list("recolorable_security_uniform", "rsecurity")
/datum/greyscale_config/security_uniform/New()
InitializeGreyscaleComponentStyleConfig()
/datum/greyscale_config/security_uniform/Refresh(loadFromDisk = FALSE)
return RefreshGreyscaleComponentStyleConfig(loadFromDisk)
/datum/greyscale_config/security_uniform/worn
name = "Security Uniform (Worn)"
icon_file = 'modular_zubbers/icons/mob/clothing/under/security.dmi'
/datum/greyscale_config/security_uniform/worn/digi
name = "Security Uniform (Worn, Digi)"
icon_file = 'modular_zubbers/icons/mob/clothing/under/security.dmi'
/datum/greyscale_config/security_cap
name = "Security Cap"
icon_file = 'modular_zubbers/icons/obj/clothing/head/hats.dmi'
greyscale_component_style_type = /datum/greyscale_component_style/security_cap
greyscale_component_config_states = list("security_cap")
/datum/greyscale_config/security_cap/New()
InitializeGreyscaleComponentStyleConfig()
/datum/greyscale_config/security_cap/Refresh(loadFromDisk = FALSE)
return RefreshGreyscaleComponentStyleConfig(loadFromDisk)
/datum/greyscale_config/security_cap/worn
name = "Security Cap (Worn)"
icon_file = 'modular_zubbers/icons/mob/clothing/head/hats.dmi'
greyscale_component_config_state_overrides = list(
"security_cap" = "security_capg",
"security_cap_accs_front" = "security_capg_accs_backing",
"security_cap_accs_logo" = "security_capg_accs_logo",
)
/datum/greyscale_config/gi/worn/digi
name = "Gi (Worn, Digi)"
icon_file = 'modular_skyrat/master_files/icons/mob/clothing/under/costume_digi.dmi'
@@ -0,0 +1,17 @@
/datum/market_item/misc/voskhod_refit
name = "Voskhod refit kit"
desc = "A shipping, holding, medical, security and many other fee-free case of thingamabobs one can use to turn their ancient steel-plated space suit into a less ancient modular space suit."
item = /obj/item/crafting_conversion_kit/voskhod_refit
price_min = CARGO_CRATE_VALUE * 4
price_max = CARGO_CRATE_VALUE * 5.75
stock_max = 3
availability_prob = 25
/datum/market_item/misc/autodoc
name = "CIN Auto-Doc MODule"
desc = "A 'legally' aquired auto-doc MODule, taken from some of the latest CIN equipment. This thing uses Cryptobiolin to heal you, but it's leaky as hell."
item = /obj/item/mod/module/auto_doc
price_min = CARGO_CRATE_VALUE * 3
price_max = CARGO_CRATE_VALUE * 4.5
stock_max = 3
availability_prob = 20
@@ -49,3 +49,13 @@
)
crate_name = "Battle Rifle Crate"
order_flags = ORDER_CONTRABAND
/datum/supply_pack/imports/depowerarmor
name = "Depowered VOSKHOD Armor Set"
desc = "We've gotten in some of the PSC's 'finest' sets of armor, these have seen much better days. But hey, we know you guys love this shit!"
cost = 3500
contains = list(
/obj/item/clothing/head/helmet/space/voskhod,
/obj/item/clothing/suit/space/voskhod,
)
order_flags = ORDER_CONTRABAND
@@ -108,3 +108,7 @@
/datum/supply_pack/company_import/psc_surplus/misc/cabin
item_type = /obj/item/survivalcapsule/cabin
cost = PAYCHECK_COMMAND * 2
/datum/supply_pack/company_import/psc_surplus/misc/restorationkit
item_type = /obj/item/crafting_conversion_kit/voskhod_refit
cost = CARGO_CRATE_VALUE * 6
@@ -0,0 +1,9 @@
/datum/supply_pack/imports/hoshi
name = "Hoshi Modular Carbine crate"
desc = "This crate includes a licenesed Hoshi Modular Carbine, one of the newest addition to the Terran's armories."
cost = CARGO_CRATE_VALUE * 10
contains = list(
/obj/item/gun/energy/modular_laser_rifle/carbine,
)
access_view = ACCESS_WEAPONS
@@ -21,7 +21,6 @@
)
//This makes the Security ammo crate use the cool advanced ammo boxes instead of the old ones
/datum/supply_pack/security/secmed_technician
name = "Security Medic Kit Crate - Technician"
crate_name = "security medic crate"
@@ -218,3 +217,13 @@
/obj/item/gun/ballistic/shotgun/riot/sol,
/obj/item/gun/ballistic/shotgun/riot/sol,
)
/datum/supply_pack/security/armory/hoshis
name = "Hoshi Modular Laser Carbine crate"
desc = "This crate includes 3 of Terra's newest addition to their armories, produced by... 'Saibasan'. These modular weapons have multiple fire modes that can be switched to, and an onboard AI."
cost = CARGO_CRATE_VALUE * 20
contains = list(
/obj/item/gun/energy/modular_laser_rifle/carbine,
/obj/item/gun/energy/modular_laser_rifle/carbine,
/obj/item/gun/energy/modular_laser_rifle/carbine,
)
@@ -46,6 +46,75 @@
icon_state = "secsoft_v"
armor_type = /datum/armor/cosmetic_sec
/datum/greyscale_component_style/security_cap
output_icon_state = "security_cap"
fallback_icon_state = "security_cap"
default_accessories = list("security_cap_accs_front", "security_cap_accs_logo")
core_components = list(
list(
"name" = "Cap",
"key" = "cap",
"default" = "security",
"options" = list(
"security" = list(
"name" = "Security cap",
"state" = "security_cap",
"color_id" = 1,
"color_label" = "Main",
"default_color" = "#A53429",
),
),
),
)
accessories = list(
"Backing" = list(
"state" = "security_cap_accs_front",
"color_id" = 2,
"color_label" = "Backing",
"default_color" = "#3F6E9E",
),
"Logo" = list(
"state" = "security_cap_accs_logo",
"color_id" = 3,
"color_label" = "Logo",
"default_color" = "#FFFFFF",
),
)
/obj/item/clothing/head/soft/sec/recolorable
name = "security cap"
desc = "It's a robust baseball hat, this one seems to have been custom ordered."
icon = 'modular_zubbers/icons/obj/clothing/head/hats.dmi'
worn_icon = 'modular_zubbers/icons/mob/clothing/head/hats.dmi'
icon_state = "security_cap"
post_init_icon_state = "security_cap"
soft_type = "security_cap"
soft_suffix = ""
greyscale_config = /datum/greyscale_config/security_cap
greyscale_config_worn = /datum/greyscale_config/security_cap/worn
greyscale_colors = "#A53429#3F6E9E#FFFFFF"
flags_1 = parent_type::flags_1 | IS_PLAYER_COLORABLE_1
greyscale_component_style_type = /datum/greyscale_component_style/security_cap
greyscale_component_icon_file = 'modular_zubbers/icons/obj/clothing/head/hats.dmi'
greyscale_component_worn_icon_file = 'modular_zubbers/icons/mob/clothing/head/hats.dmi'
greyscale_component_worn_state_overrides = list(
"security_cap" = "security_capg",
"security_cap_accs_front" = "security_capg_accs_backing",
"security_cap_accs_logo" = "security_capg_accs_logo",
)
greyscale_component_accessories = list("security_cap_accs_front", "security_cap_accs_logo")
/obj/item/clothing/head/soft/sec/recolorable/Initialize(mapload)
initialize_greyscale_component_style()
return ..()
/obj/item/clothing/head/soft/sec/recolorable/update_greyscale()
. = ..()
update_greyscale_component_icons()
/obj/item/clothing/head/soft/sec/recolorable/flip(mob/user)
balloon_alert(user, "can't be flipped!")
/obj/item/clothing/head/sec/viro/beanie
name = "security beanie"
desc = "A beanie for security purposes"
@@ -92,6 +92,196 @@
can_adjust = TRUE
supports_variations_flags = CLOTHING_DIGITIGRADE_VARIATION
/datum/greyscale_component_style/security_uniform
output_icon_state = "rsecurity"
fallback_icon_state = "security_shirt"
default_accessories = list("security_shirt_accs_tie")
core_components = list(
list(
"name" = "Shirt",
"key" = "shirt",
"default" = "security",
"groups" = list(
"tie_compatible" = list("security", "viro", "suit"),
"arm_trim_compatible" = list("security", "viro", "turtleneck"),
),
"options" = list(
"security" = list(
"name" = "Security shirt",
"state" = "security_shirt",
"color_id" = 1,
"color_label" = "Shirt",
"default_color" = "#BA3B2E",
),
"viro" = list(
"name" = "Virosec shirt",
"state" = "security_shirt_viro",
"parent" = "security",
"color_id" = 1,
"color_label" = "Shirt",
"default_color" = "#BA3B2E",
),
"suit" = list(
"name" = "Security suit",
"state" = "security_shirt_suit",
"parent" = "security",
"color_id" = 1,
"color_label" = "Shirt",
"default_color" = "#BA3B2E",
),
"turtleneck" = list(
"name" = "Security turtleneck",
"state" = "security_shirt_turtleneck",
"parent" = "security",
"color_id" = 1,
"color_label" = "Shirt",
"default_color" = "#BA3B2E",
),
),
),
list(
"name" = "Pants",
"key" = "pants",
"default" = "security",
"options" = list(
"security" = list(
"name" = "Security pants",
"state" = "security_pants",
"color_id" = 2,
"color_label" = "Pants",
"default_color" = "#4B4C51",
),
"skirt" = list(
"name" = "Security skirt",
"state" = "security_pants_skirt",
"parent" = "security",
"color_id" = 2,
"color_label" = "Pants",
"default_color" = "#4B4C51",
),
"viro" = list(
"name" = "Virosec pants",
"state" = "security_pants_viro",
"parent" = "security",
"color_id" = 2,
"color_label" = "Pants",
"default_color" = "#4B4C51",
),
),
),
)
accessories = list(
"Tie" = list(
"state" = "security_shirt_accs_tie",
"applies_to" = list("shirt" = list(COMPONENT_CORE_GROUP = "tie_compatible")),
"color_id" = 3,
"color_label" = "Tie",
"default_color" = "#2D2D33",
),
"Arm trim one" = list(
"state" = "security_shirt_accs_arm_trim_one",
"applies_to" = list("shirt" = list(COMPONENT_CORE_EXACT = list("security", "viro"))),
"color_id" = 4,
"color_label" = "Arm trim one",
"default_color" = "#39393F",
),
"Arm trim two" = list(
"state" = "security_shirt_accs_arm_trim_two",
"applies_to" = list("shirt" = list(COMPONENT_CORE_GROUP = "arm_trim_compatible")),
"color_id" = 13,
"color_label" = "Arm trim two",
"default_color" = "#46464D",
),
"Arm trim three" = list(
"state" = "security_shirt_accs_arm_trim_three",
"applies_to" = list("shirt" = list(COMPONENT_CORE_GROUP = "arm_trim_compatible")),
"color_id" = 12,
"color_label" = "Arm trim three",
"default_color" = "#D0D0D0",
),
"Epaulettes" = list(
"state" = "security_shirt_accss_epaulettes",
"applies_to" = list("shirt" = list(COMPONENT_CORE_BRANCH = "security")),
"color_id" = 11,
"color_label" = "Epaulettes",
"default_color" = "#39393F",
),
"Viro shirt trim" = list(
"state" = "security_shirt_accs_trim_viro",
"applies_to" = list("shirt" = list("viro" = COMPONENT_CORE_EXACT)),
"color_id" = 8,
"color_label" = "Viro shirt trim",
"default_color" = "#46464D",
),
"Suit inner shirt" = list(
"state" = "security_shirt_suit_accs_inner",
"applies_to" = list("shirt" = list("suit" = COMPONENT_CORE_EXACT)),
"color_id" = 9,
"color_label" = "Suit inner shirt",
"default_color" = "#EBEBEB",
),
"Buckle" = list(
"state" = "security_pants_accs_buckle",
"applies_to" = list("pants" = COMPONENT_CORE_ALL),
"color_id" = 10,
"color_label" = "Buckle",
"default_color" = "#B5BBCF",
),
"Skirt outer trim" = list(
"state" = "security_pants_skirt_accs_outer_trim",
"applies_to" = list("pants" = list("skirt" = COMPONENT_CORE_EXACT)),
"color_id" = 5,
"color_label" = "Skirt outer trim",
"default_color" = "#46464D",
),
"Skirt inner trim" = list(
"state" = "security_pants_skirt_accs_inner_trim",
"applies_to" = list("pants" = list("skirt" = COMPONENT_CORE_EXACT)),
"color_id" = 6,
"color_label" = "Skirt inner trim",
"default_color" = "#46464D",
),
"Viro pants trim" = list(
"state" = "security_pants_viro_accs_trim",
"applies_to" = list("pants" = list("viro" = COMPONENT_CORE_EXACT)),
"color_id" = 7,
"color_label" = "Viro pants trim",
"default_color" = "#7D2A25",
),
)
/obj/item/clothing/under/rank/security/officer/recolorable
name = "security uniform"
desc = "A tactical security jumpsuit for officers. This one seems to have a few custom modifications."
icon = 'icons/map_icons/clothing/under/_under.dmi'
worn_icon = 'modular_zubbers/icons/mob/clothing/under/security.dmi'
worn_icon_digi = 'modular_zubbers/icons/mob/clothing/under/security_digi.dmi'
icon_state = "/obj/item/clothing/under/rank/security/officer/recolorable"
post_init_icon_state = "recolorable_security_uniform"
greyscale_config = /datum/greyscale_config/security_uniform
greyscale_config_worn = /datum/greyscale_config/security_uniform/worn
greyscale_config_worn_digi = /datum/greyscale_config/security_uniform/worn/digi
greyscale_colors = "#BA3B2E#4B4C51#2D2D33#39393F#46464D#46464D#7D2A25#46464D#EBEBEB#B5BBCF#39393F#D0D0D0#46464D"
flags_1 = parent_type::flags_1 | IS_PLAYER_COLORABLE_1
can_adjust = TRUE
supports_variations_flags = CLOTHING_DIGITIGRADE_VARIATION
greyscale_component_style_type = /datum/greyscale_component_style/security_uniform
greyscale_component_worn_icon_file = 'modular_zubbers/icons/mob/clothing/under/security.dmi'
greyscale_component_worn_digi_icon_file = 'modular_zubbers/icons/mob/clothing/under/security_digi.dmi'
greyscale_component_digi_fallback_icon_file = 'modular_zubbers/icons/mob/clothing/under/security.dmi'
greyscale_component_accessories = list("security_shirt_accs_tie")
/obj/item/clothing/under/rank/security/officer/recolorable/Initialize(mapload)
initialize_greyscale_component_style()
return ..()
/obj/item/clothing/under/rank/security/officer/recolorable/update_greyscale()
. = ..()
update_greyscale_component_icons()
icon = 'icons/obj/clothing/under/security.dmi'
icon_state = "rsecurity"
inhand_icon_state = "r_suit"
// Virosec, sprites by axietheaxolotl
/obj/item/clothing/under/rank/security/viro/officer/
@@ -0,0 +1,866 @@
/datum/greyscale_component_style
/// Marker used when loadout storage needs to preserve an intentionally empty accessory selection.
var/static/no_accessories_marker = "__component_style_no_accessories"
/datum/greyscale_component_style
/// Icon state inserted into generated icon files.
var/output_icon_state
/// Fallback component state to use if every selected component is missing.
var/fallback_icon_state
/// Suffix checked first for digitigrade component states.
var/digi_suffix = "_digi"
/// Ordered list of core component definitions.
var/list/core_components = list()
/// Optional component definitions, keyed by display name.
var/list/accessories = list()
/// Optional accessory component states enabled by default.
var/list/default_accessories = list()
/// Suffix checked first for adjusted/casual component states.
var/adjusted_suffix = "_d"
/datum/greyscale_component_style/proc/default_core_selection() as /list
var/list/selection = list()
for(var/list/core_component as anything in core_components)
selection[core_component["key"]] = core_component["default"]
return selection
/datum/greyscale_component_style/proc/sanitize_core_selection(list/selected_cores) as /list
var/list/sanitized = list()
for(var/list/core_component as anything in core_components)
var/core_key = core_component["key"]
var/selected_state = selected_cores?[core_key] || core_component["default"]
if(!core_option_exists(core_component, selected_state))
selected_state = core_component["default"]
sanitized[core_key] = selected_state
return sanitized
/datum/greyscale_component_style/proc/sanitize_accessories(list/selected_accessories, list/selected_cores) as /list
var/list/sanitized = list()
if(!islist(selected_accessories) || (no_accessories_marker in selected_accessories))
return sanitized
for(var/accessory in selected_accessories)
if(!accessory_allowed(accessory, selected_cores))
continue
sanitized += accessory
return sanitized
/datum/greyscale_component_style/proc/core_option_exists(list/core_component, option_state)
var/list/options = core_component["options"]
return islist(options?[option_state])
/datum/greyscale_component_style/proc/core_component_for_key(core_key) as /list
for(var/list/core_component as anything in core_components)
if(core_component["key"] == core_key)
return core_component
return null
/datum/greyscale_component_style/proc/core_option_data(list/core_component, option_id) as /list
var/list/options = core_component?["options"]
return options?[option_id]
/datum/greyscale_component_style/proc/core_option_state(list/core_component, option_id)
var/list/option_data = core_option_data(core_component, option_id)
return option_data?["state"]
/datum/greyscale_component_style/proc/core_group_options(list/core_component, group_id) as /list
var/list/groups = core_component?["groups"]
var/list/group_options = groups?[group_id]
return islist(group_options) ? group_options : null
/datum/greyscale_component_style/proc/core_option_matches(list/core_component, selected_option, target_option, match_mode)
if(match_mode == COMPONENT_CORE_ALL)
return TRUE
if(match_mode == COMPONENT_CORE_EXACT)
return selected_option == target_option
if(match_mode != COMPONENT_CORE_BRANCH)
return FALSE
var/current_option = selected_option
while(current_option)
if(current_option == target_option)
return TRUE
var/list/option_data = core_option_data(core_component, current_option)
current_option = option_data?["parent"]
return FALSE
/datum/greyscale_component_style/proc/core_rule_targets(list/core_component, rule_value, use_groups = FALSE) as /list
if(islist(rule_value))
return rule_value
if(use_groups)
return core_group_options(core_component, rule_value) || list()
return list(rule_value)
/datum/greyscale_component_style/proc/core_apply_rule_matches(list/core_component, selected_option, rule_mode, rule_value)
var/list/targets = core_rule_targets(core_component, rule_value, rule_mode == COMPONENT_CORE_GROUP)
var/match_mode = (rule_mode == COMPONENT_CORE_GROUP) ? COMPONENT_CORE_EXACT : rule_mode
for(var/target_option in targets)
if(core_option_matches(core_component, selected_option, target_option, match_mode))
return TRUE
return FALSE
/datum/greyscale_component_style/proc/core_ui_data(list/selected_cores) as /list
var/list/core_data = list()
for(var/list/core_component as anything in core_components)
var/core_key = core_component["key"]
core_data += list(list(
"name" = core_component["name"],
"key" = core_key,
"options" = option_ui_data(core_component["options"], selected_cores[core_key]),
))
return core_data
/datum/greyscale_component_style/proc/option_ui_data(list/options, selected_value) as /list
var/list/style_options = list()
for(var/option_id in options)
var/list/option_data = options[option_id]
style_options += list(list(
"name" = option_data["name"] || option_id,
"value" = option_id,
"selected" = (option_id == selected_value),
))
return style_options
/datum/greyscale_component_style/proc/accessory_ui_data(list/selected_accessories, list/selected_cores) as /list
var/list/style_options = list()
for(var/accessory_name in accessories)
var/list/accessory_rules = accessories[accessory_name]
var/accessory_state = accessory_rules["state"]
if(!accessory_allowed(accessory_state, selected_cores))
continue
style_options += list(list(
"name" = accessory_name,
"value" = accessory_state,
"selected" = (accessory_state in selected_accessories),
))
return style_options
/datum/greyscale_component_style/proc/accessory_allowed(accessory_state, list/selected_cores)
var/list/accessory_rules = accessory_rules_for_state(accessory_state)
if(isnull(accessory_rules))
return FALSE
var/list/apply_rules = accessory_rules["applies_to"]
if(!length(apply_rules))
return TRUE
for(var/core_key in apply_rules)
var/list/core_component = core_component_for_key(core_key)
if(!core_component)
return FALSE
var/selected_option = selected_cores?[core_key]
if(!selected_option)
return FALSE
var/apply_rule = apply_rules[core_key]
if(apply_rule == COMPONENT_CORE_ALL)
continue
var/matched_core = FALSE
if(islist(apply_rule))
for(var/rule_key in apply_rule)
if(rule_key in list(COMPONENT_CORE_EXACT, COMPONENT_CORE_BRANCH, COMPONENT_CORE_GROUP))
if(core_apply_rule_matches(core_component, selected_option, rule_key, apply_rule[rule_key]))
matched_core = TRUE
break
continue
if(core_apply_rule_matches(core_component, selected_option, apply_rule[rule_key] || COMPONENT_CORE_EXACT, rule_key))
matched_core = TRUE
break
else
matched_core = core_option_matches(core_component, selected_option, apply_rule, COMPONENT_CORE_EXACT)
if(!matched_core)
return FALSE
return TRUE
/datum/greyscale_component_style/proc/accessory_rules_for_state(accessory_state) as /list
for(var/accessory_name in accessories)
var/list/accessory_rules = accessories[accessory_name]
if(accessory_rules["state"] == accessory_state)
return accessory_rules
return null
/datum/greyscale_component_style/proc/active_components(list/selected_cores, list/selected_accessories) as /list
var/list/components = list()
for(var/list/core_component as anything in core_components)
var/component_state = core_option_state(core_component, selected_cores?[core_component["key"]])
if(component_state)
components += component_state
if(islist(selected_accessories))
for(var/accessory_state in selected_accessories)
if(!accessory_state)
continue
components += accessory_state
return components
/datum/greyscale_component_style/proc/color_id_for_component(component_state)
var/list/component_rules = component_rules_for_state(component_state)
return component_rules?["color_id"]
/datum/greyscale_component_style/proc/component_rules_for_state(component_state) as /list
for(var/list/core_component as anything in core_components)
var/list/options = core_component["options"]
for(var/option_id in options)
var/list/option_data = options[option_id]
if(option_data["state"] == component_state)
return option_data
for(var/accessory_name in accessories)
var/list/accessory_rules = accessories[accessory_name]
if(accessory_rules["state"] == component_state)
return accessory_rules
return null
/datum/greyscale_component_style/proc/active_color_ids(list/selected_cores, list/selected_accessories) as /list
var/list/active_color_ids = list()
for(var/component in active_components(selected_cores, selected_accessories))
var/color_id = color_id_for_component(component)
if(!color_id || (color_id in active_color_ids))
continue
active_color_ids += color_id
return active_color_ids
/datum/greyscale_component_style/proc/active_color_labels(list/selected_cores, list/selected_accessories) as /list
var/list/labels = list()
for(var/component_state in active_components(selected_cores, selected_accessories))
var/list/component_rules = component_rules_for_state(component_state)
var/color_id = component_rules?["color_id"]
var/color_label = component_rules?["color_label"]
if(!color_id || !color_label)
continue
labels["[color_id]"] = color_label
return labels
/datum/greyscale_component_style/proc/default_color_list(list/fallback_colors, expected_colors = 0) as /list
var/list/colors = islist(fallback_colors) ? fallback_colors.Copy() : list()
while(length(colors) < expected_colors)
colors += null
for(var/list/core_component as anything in core_components)
var/list/options = core_component["options"]
for(var/option_id in options)
apply_component_default_color(colors, options[option_id])
for(var/accessory_name in accessories)
apply_component_default_color(colors, accessories[accessory_name])
for(var/i in 1 to length(colors))
if(!colors[i])
colors[i] = rgb(100, 100, 100)
return colors
/datum/greyscale_component_style/proc/apply_component_default_color(list/colors, list/component_rules)
var/color_id = component_rules?["color_id"]
var/default_color = component_rules?["default_color"]
if(!color_id || !default_color)
return
while(length(colors) < color_id)
colors += null
colors[color_id] = default_color
/datum/greyscale_component_style/proc/greyscale_config_data(list/output_states, list/state_overrides) as /list
var/list/layers = greyscale_config_layers(state_overrides)
var/list/config_data = list()
if(!length(output_states))
output_states = list(output_icon_state)
for(var/output_state in output_states)
if(isnull(output_state))
continue
config_data[output_state] = layers
return config_data
/datum/greyscale_component_style/proc/greyscale_config_layers(list/state_overrides) as /list
var/list/layers = list()
var/list/seen_states = list()
for(var/component_state in greyscale_config_component_states())
if(!component_state || seen_states[component_state])
continue
seen_states[component_state] = TRUE
var/list/component_rules = component_rules_for_state(component_state)
var/layer_state = islist(state_overrides) ? (state_overrides[component_state] || component_state) : component_state
var/list/layer_data = list(
"type" = "icon_state",
"icon_state" = layer_state,
"blend_mode" = "overlay",
)
var/color_id = component_rules?["color_id"]
if(color_id)
layer_data["color_ids"] = list(color_id)
layers += list(layer_data)
return layers
/datum/greyscale_component_style/proc/greyscale_config_component_states() as /list
var/list/component_states = list()
for(var/list/core_component as anything in core_components)
var/list/options = core_component["options"]
for(var/option_id in options)
component_states += options[option_id]["state"]
for(var/accessory_name in accessories)
component_states += accessories[accessory_name]["state"]
return component_states
/datum/greyscale_config
/// Optional dynamic component-style definition used to generate this config without a hand-written JSON file.
var/greyscale_component_style_type
/// Icon states this generated config should expose in its output bundle.
var/list/greyscale_component_config_states
/// Optional logical component state to real icon state mapping for this config's icon file.
var/list/greyscale_component_config_state_overrides
/datum/greyscale_config/proc/InitializeGreyscaleComponentStyleConfig()
if(!icon_file)
stack_trace("Greyscale component-style config object [DebugName()] is missing an icon file, make sure `icon_file` has been assigned a value.")
string_icon_file = "[icon_file]"
if(!name)
stack_trace("Greyscale component-style config object [DebugName()] is missing a name, make sure `name` has been assigned a value.")
if(!greyscale_component_style_type)
stack_trace("Greyscale component-style config object [DebugName()] is missing a component style type.")
/datum/greyscale_config/proc/RefreshGreyscaleComponentStyleConfig(loadFromDisk = FALSE)
if(loadFromDisk)
var/changed = FALSE
icon_file = file(string_icon_file)
var/icon_hash = rustg_hash_file("md5", string_icon_file)
if(icon_file_hash != icon_hash)
icon_file_hash = icon_hash
changed = TRUE
for(var/datum/greyscale_layer/layer as anything in flat_all_layers)
if(layer.DiskRefresh())
changed = TRUE
if(!changed)
return FALSE
var/style_type = greyscale_component_style_type
var/datum/greyscale_component_style/component_style = new style_type
var/list/raw = component_style.greyscale_config_data(greyscale_component_config_states, greyscale_component_config_state_overrides)
raw_json_string = json_encode(raw)
qdel(component_style)
ReadIconStateConfiguration(raw)
if(!length(icon_states))
CRASH("The component-style greyscale configuration [DebugName()] doesn't have any icon states.")
icon_cache = list()
ReadMetadata()
SEND_SIGNAL(src, COMSIG_GREYSCALE_CONFIG_REFRESHED)
return TRUE
/datum/greyscale_component_style/proc/build_icon(icon_file, list/colors, list/selected_cores, list/selected_accessories, use_digi = FALSE, digi_fallback_icon_file = null, only_dir = null, list/state_overrides = null, use_adjusted = FALSE)
var/icon/final_icon = icon('icons/effects/effects.dmi', "nothing")
final_icon.Scale(32, 32)
if(!icon_file)
return final_icon
var/icon/built_icon
for(var/component in active_components(selected_cores, selected_accessories))
if(!component)
continue
var/layer_file = icon_file
var/state_to_use = component
if(islist(state_overrides))
state_to_use = state_overrides[component] || component
var/list/candidate_states = list()
if(use_digi && use_adjusted)
candidate_states += "[state_to_use][digi_suffix][adjusted_suffix]"
if(use_adjusted)
candidate_states += "[state_to_use][adjusted_suffix]"
if(use_digi)
candidate_states += "[state_to_use][digi_suffix]"
candidate_states += state_to_use
var/list/candidate_files = list(layer_file)
if(use_digi && digi_fallback_icon_file)
candidate_files += digi_fallback_icon_file
var/found_component_state = FALSE
for(var/candidate_state in candidate_states)
for(var/candidate_file in candidate_files)
if(!icon_exists(candidate_file, candidate_state))
continue
layer_file = candidate_file
state_to_use = candidate_state
found_component_state = TRUE
break
if(found_component_state)
break
if(!found_component_state)
continue
var/icon/component_icon = isnull(only_dir) ? icon(layer_file, state_to_use) : icon(layer_file, state_to_use, only_dir)
var/color_id = color_id_for_component(component)
if(color_id && length(colors) >= color_id && colors[color_id])
component_icon.Blend(colors[color_id], ICON_MULTIPLY)
if(isnull(built_icon))
built_icon = component_icon
else
built_icon.Blend(component_icon, ICON_OVERLAY)
if(isnull(built_icon) && fallback_icon_state)
built_icon = isnull(only_dir) ? icon(icon_file, fallback_icon_state) : icon(icon_file, fallback_icon_state, only_dir)
if(!isnull(built_icon))
var/generated_icon_state = use_adjusted ? "[output_icon_state][adjusted_suffix]" : output_icon_state
final_icon.Insert(built_icon, generated_icon_state)
return final_icon
/obj/item
/// Optional component-style GAGS definition used for layered, selectable components.
var/greyscale_component_style_type
/// Selected core option ids, keyed by core component id.
var/list/greyscale_component_cores
/// Selected optional component states.
var/list/greyscale_component_accessories
/// Object icon file used for component-style GAGS composition.
var/greyscale_component_icon_file
/// Worn icon file used for component-style GAGS composition.
var/greyscale_component_worn_icon_file
/// Digitigrade worn icon file used for component-style GAGS composition.
var/greyscale_component_worn_digi_icon_file
/// Optional non-digi worn icon fallback for missing digitigrade component states.
var/greyscale_component_digi_fallback_icon_file
/// Whether component-style worn icons should use adjusted/casual component states.
var/greyscale_component_use_adjusted = FALSE
/// Optional logical component state to worn icon state mapping.
var/list/greyscale_component_worn_state_overrides
/// Optional logical component state to digitigrade worn icon state mapping.
var/list/greyscale_component_worn_digi_state_overrides
/obj/item/proc/get_greyscale_component_style() as /datum/greyscale_component_style
if(!greyscale_component_style_type)
return null
return new greyscale_component_style_type
/obj/item/proc/initialize_greyscale_component_style()
var/datum/greyscale_component_style/component_style = get_greyscale_component_style()
if(!component_style)
return
greyscale_component_cores = component_style.sanitize_core_selection(greyscale_component_cores)
var/list/accessories_to_apply = isnull(greyscale_component_accessories) ? component_style.default_accessories : greyscale_component_accessories
greyscale_component_accessories = component_style.sanitize_accessories(accessories_to_apply, greyscale_component_cores)
qdel(component_style)
/obj/item/proc/apply_greyscale_component_style(list/item_details)
var/datum/greyscale_component_style/component_style = get_greyscale_component_style()
if(!component_style)
return
greyscale_component_cores = component_style.sanitize_core_selection(item_details?[INFO_GREYSCALE_COMPONENT_CORES] || greyscale_component_cores)
var/list/accessories_to_apply = item_details?[INFO_GREYSCALE_COMPONENT_ACCESSORIES]
if(isnull(accessories_to_apply))
accessories_to_apply = isnull(greyscale_component_accessories) ? component_style.default_accessories : greyscale_component_accessories
greyscale_component_accessories = component_style.sanitize_accessories(accessories_to_apply, greyscale_component_cores)
qdel(component_style)
update_greyscale()
/obj/item/proc/update_greyscale_component_icons()
var/datum/greyscale_component_style/component_style = get_greyscale_component_style()
if(!component_style || !greyscale_colors)
qdel(component_style)
return
var/list/colors = SSgreyscale.ParseColorString(greyscale_colors)
icon_state = component_style.output_icon_state
worn_icon_state = greyscale_component_use_adjusted ? "[component_style.output_icon_state][component_style.adjusted_suffix]" : component_style.output_icon_state
greyscale_config_worn = null
greyscale_config_worn_digi = null
if(greyscale_component_icon_file)
icon = component_style.build_icon(greyscale_component_icon_file, colors, greyscale_component_cores, greyscale_component_accessories)
if(greyscale_component_worn_icon_file)
worn_icon = component_style.build_icon(greyscale_component_worn_icon_file, colors, greyscale_component_cores, greyscale_component_accessories, state_overrides = greyscale_component_worn_state_overrides, use_adjusted = greyscale_component_use_adjusted)
if(greyscale_component_worn_digi_icon_file)
worn_icon_digi = component_style.build_icon(greyscale_component_worn_digi_icon_file, colors, greyscale_component_cores, greyscale_component_accessories, use_digi = TRUE, digi_fallback_icon_file = greyscale_component_digi_fallback_icon_file, state_overrides = greyscale_component_worn_digi_state_overrides || greyscale_component_worn_state_overrides, use_adjusted = greyscale_component_use_adjusted)
qdel(component_style)
var/obj/item/clothing/worn_clothing = src
if(!istype(worn_clothing))
return
var/mob/living/carbon/human/wearer = worn_clothing.loc
if(!istype(wearer))
return
wearer.update_clothing(worn_clothing.slot_flags)
/obj/item/clothing/under/adjust_to_normal()
. = ..()
if(!greyscale_component_style_type)
return
greyscale_component_use_adjusted = FALSE
update_greyscale_component_icons()
/obj/item/clothing/under/adjust_to_alt()
. = ..()
if(!greyscale_component_style_type)
return
greyscale_component_use_adjusted = TRUE
update_greyscale_component_icons()
/datum/loadout_item/proc/uses_greyscale_component_style()
return !!initial(item_path.greyscale_component_style_type)
/datum/loadout_item
/// Optional icon file used for component-style color previews instead of the item's worn preview.
var/greyscale_component_preview_icon_file
/// Optional logical component state to preview icon state mapping.
var/list/greyscale_component_preview_state_overrides
/// Opens a component-style GAGS editing menu when the loadout item supports it.
/datum/loadout_item/proc/set_component_style_item_color(datum/preference_middleware/loadout/manager, mob/user)
if(manager.menu)
return FALSE
var/list/loadout = manager.get_current_loadout()
var/list/allowed_configs = list()
if(initial(item_path.greyscale_config_worn))
allowed_configs += "[initial(item_path.greyscale_config_worn)]"
else if(initial(item_path.greyscale_config))
allowed_configs += "[initial(item_path.greyscale_config)]"
var/list/selected_cores = loadout?[item_path]?[INFO_GREYSCALE_COMPONENT_CORES]
if(isnull(selected_cores))
selected_cores = initial(item_path.greyscale_component_cores)
var/list/selected_accessories = loadout?[item_path]?[INFO_GREYSCALE_COMPONENT_ACCESSORIES]
if(islist(selected_accessories) && (/datum/greyscale_component_style::no_accessories_marker in selected_accessories))
selected_accessories = list()
var/starting_config = initial(item_path.greyscale_config_worn) || initial(item_path.greyscale_config)
var/starting_colors = loadout?[item_path]?[INFO_GREYSCALE] || initial(item_path.greyscale_colors)
var/datum/greyscale_config/selected_config = SSgreyscale.configurations["[starting_config]"]
if(selected_config && length(SSgreyscale.ParseColorString(starting_colors)) != selected_config.expected_colors)
starting_colors = initial(item_path.greyscale_colors)
var/preview_icon_file = greyscale_component_preview_icon_file || initial(item_path.greyscale_component_worn_icon_file) || initial(item_path.greyscale_component_icon_file)
var/list/preview_state_overrides = greyscale_component_preview_state_overrides
if(!preview_state_overrides && preview_icon_file == initial(item_path.greyscale_component_worn_icon_file))
preview_state_overrides = initial(item_path.greyscale_component_worn_state_overrides)
var/datum/greyscale_modify_menu/component_style/menu = new(
manager,
user,
allowed_configs,
CALLBACK(src, PROC_REF(set_slot_greyscale), manager),
starting_icon_state = initial(item_path.icon_state),
starting_config = starting_config,
starting_colors = starting_colors,
component_style_type = initial(item_path.greyscale_component_style_type),
selected_cores = selected_cores,
selected_accessories = selected_accessories,
preview_icon_file = preview_icon_file,
preview_digi_fallback_icon_file = initial(item_path.greyscale_component_digi_fallback_icon_file),
preview_state_overrides = preview_state_overrides,
default_colors = initial(item_path.greyscale_colors),
default_cores = initial(item_path.greyscale_component_cores),
default_accessories = null,
)
manager.register_greyscale_menu(menu)
menu.ui_interact(user)
return TRUE
/// Saves component-style GAGS color and component state.
/datum/loadout_item/proc/set_component_style_slot_greyscale(datum/preference_middleware/loadout/manager, datum/greyscale_modify_menu/open_menu)
var/datum/greyscale_modify_menu/component_style/component_menu = open_menu
if(!istype(component_menu))
return FALSE
var/list/loadout = manager.get_current_loadout()
if(!loadout?[item_path])
return FALSE
var/list/colors = component_menu.split_colors
if(!colors)
return FALSE
loadout[item_path][INFO_GREYSCALE] = colors.Join("")
loadout[item_path][INFO_GREYSCALE_COMPONENT_CORES] = component_menu.selected_cores.Copy()
loadout[item_path][INFO_GREYSCALE_COMPONENT_ACCESSORIES] = length(component_menu.selected_accessories) ? component_menu.selected_accessories.Copy() : list(/datum/greyscale_component_style::no_accessories_marker)
manager.save_current_loadout(loadout)
return TRUE
/datum/loadout_item/proc/apply_component_style_to_equipped_item(obj/item/equipped_item, list/item_details)
if(equipped_item?.greyscale_component_style_type)
equipped_item.apply_greyscale_component_style(item_details)
/proc/get_component_style_item_preview_image(obj/item/item_path)
if(item_path == /obj/item/clothing/head/soft/sec/recolorable)
var/icon/preview_icon = icon('icons/obj/clothing/head/hats.dmi', "secsoft")
return icon2base64(preview_icon)
var/component_style_type = initial(item_path.greyscale_component_style_type)
if(!component_style_type)
return null
var/datum/greyscale_component_style/component_style = new component_style_type
var/list/default_cores = component_style.sanitize_core_selection(initial(item_path.greyscale_component_cores))
var/list/default_accessories = component_style.default_accessories
default_accessories = component_style.sanitize_accessories(default_accessories, default_cores)
var/list/default_colors = component_style.default_color_list(SSgreyscale.ParseColorString(initial(item_path.greyscale_colors)))
var/preview_icon_file = initial(item_path.greyscale_component_worn_icon_file) || initial(item_path.greyscale_component_icon_file)
var/list/preview_state_overrides
if(preview_icon_file == initial(item_path.greyscale_component_worn_icon_file))
preview_state_overrides = initial(item_path.greyscale_component_worn_state_overrides)
var/icon/south_preview_icon = component_style.build_icon(preview_icon_file, default_colors, default_cores, default_accessories, only_dir = SOUTH, state_overrides = preview_state_overrides)
var/icon/single_frame_preview = icon(south_preview_icon, component_style.output_icon_state, SOUTH, 1)
qdel(component_style)
return icon2base64(single_frame_preview)
/datum/loadout_item/proc/get_component_style_preview_image()
return get_component_style_item_preview_image(item_path)
/datum/loadout_item/proc/add_component_style_preview_to_ui_data(list/formatted_item)
if(!uses_greyscale_component_style())
return
formatted_item["image"] = get_component_style_preview_image()
/obj/machinery/vending/proc/get_component_style_vending_preview_image(obj/item/item_path)
return get_component_style_item_preview_image(item_path)
/obj/machinery/vending
/// Temporary component-style selections keyed by vending user ref while the normal vend path creates the item.
var/list/greyscale_component_style_vend_details
/obj/machinery/vending/proc/open_component_style_vending_menu(datum/data/vending_product/product, list/params, mob/user)
var/obj/item/item_path = product.product_path
var/list/allowed_configs = list()
if(initial(item_path.greyscale_config_worn))
allowed_configs += "[initial(item_path.greyscale_config_worn)]"
else if(initial(item_path.greyscale_config))
allowed_configs += "[initial(item_path.greyscale_config)]"
var/preview_icon_file = initial(item_path.greyscale_component_worn_icon_file) || initial(item_path.greyscale_component_icon_file)
var/list/preview_state_overrides
if(preview_icon_file == initial(item_path.greyscale_component_worn_icon_file))
preview_state_overrides = initial(item_path.greyscale_component_worn_state_overrides)
var/datum/greyscale_modify_menu/component_style/menu = new(
src,
user,
allowed_configs,
CALLBACK(src, PROC_REF(_vend_component_style), params, user),
starting_icon_state = initial(item_path.icon_state),
starting_config = initial(item_path.greyscale_config_worn) || initial(item_path.greyscale_config),
starting_colors = initial(item_path.greyscale_colors),
component_style_type = initial(item_path.greyscale_component_style_type),
selected_cores = initial(item_path.greyscale_component_cores),
selected_accessories = initial(item_path.greyscale_component_accessories),
preview_icon_file = preview_icon_file,
preview_digi_fallback_icon_file = initial(item_path.greyscale_component_digi_fallback_icon_file),
preview_state_overrides = preview_state_overrides,
default_colors = initial(item_path.greyscale_colors),
default_cores = initial(item_path.greyscale_component_cores),
default_accessories = initial(item_path.greyscale_component_accessories),
)
menu.ui_interact(user)
return TRUE
/obj/machinery/vending/proc/_vend_component_style(list/params, mob/user, datum/greyscale_modify_menu/menu)
PRIVATE_PROC(TRUE)
var/datum/greyscale_modify_menu/component_style/component_menu = menu
if(user != menu.user || !istype(component_menu))
return
var/list/component_details = list(
INFO_GREYSCALE_COMPONENT_CORES = component_menu.selected_cores.Copy(),
INFO_GREYSCALE_COMPONENT_ACCESSORIES = length(component_menu.selected_accessories) ? component_menu.selected_accessories.Copy() : list(/datum/greyscale_component_style::no_accessories_marker),
)
if(!greyscale_component_style_vend_details)
greyscale_component_style_vend_details = list()
var/user_ref = REF(user)
greyscale_component_style_vend_details[user_ref] = component_details
vend(params, user, component_menu.split_colors)
greyscale_component_style_vend_details -= user_ref
if(!length(greyscale_component_style_vend_details))
greyscale_component_style_vend_details = null
/datum/greyscale_modify_menu/component_style
var/datum/greyscale_component_style/component_style
var/component_style_type
var/list/selected_cores
var/list/selected_accessories
var/preview_icon_file
var/preview_use_digi = FALSE
var/preview_digi_fallback_icon_file
var/list/preview_state_overrides
var/list/default_colors
var/list/default_cores
var/list/default_accessories
/datum/greyscale_modify_menu/component_style/New(datum/target, client/user, list/allowed_configs, datum/callback/apply_callback, starting_icon_state = "", starting_config, starting_colors, unlocked = FALSE, component_style_type = null, list/selected_cores = null, list/selected_accessories = null, preview_icon_file = null, preview_use_digi = FALSE, preview_digi_fallback_icon_file = null, list/preview_state_overrides = null, default_colors = null, list/default_cores = null, list/default_accessories = null)
src.component_style_type = component_style_type || /datum/greyscale_component_style
var/style_type = src.component_style_type
src.component_style = new style_type
src.selected_cores = component_style.sanitize_core_selection(selected_cores)
if(isnull(selected_accessories))
selected_accessories = component_style.default_accessories
src.selected_accessories = component_style.sanitize_accessories(selected_accessories, src.selected_cores)
src.preview_icon_file = preview_icon_file
src.preview_use_digi = preview_use_digi
src.preview_digi_fallback_icon_file = preview_digi_fallback_icon_file
src.preview_state_overrides = preview_state_overrides
src.default_colors = component_style.default_color_list(SSgreyscale.ParseColorString(default_colors || starting_colors))
src.default_cores = component_style.sanitize_core_selection(default_cores)
if(isnull(default_accessories))
default_accessories = component_style.default_accessories
src.default_accessories = component_style.sanitize_accessories(default_accessories, src.default_cores)
sprite_data = list(
"icon_states" = list(component_style.output_icon_state),
"finished" = null,
"steps" = list(),
"time_spent" = 0,
)
update_component_color_labels()
return ..()
/datum/greyscale_modify_menu/component_style/Destroy()
QDEL_NULL(component_style)
selected_cores = null
selected_accessories = null
preview_state_overrides = null
default_colors = null
default_cores = null
default_accessories = null
return ..()
/datum/greyscale_modify_menu/component_style/ReadColorsFromString(colorString)
if(..())
return TRUE
normalize_split_colors()
return TRUE
/datum/greyscale_modify_menu/component_style/ui_data(mob/user)
normalize_split_colors()
var/list/data = list()
data["greyscale_config"] = "[config.name]"
data["full_color_string"] = split_colors.Join("")
var/list/active_color_ids = component_style.active_color_ids(selected_cores, selected_accessories)
var/list/active_colors = list()
for(var/i in 1 to config.expected_colors)
if(!(i in active_color_ids))
continue
active_colors += list(list(
"index" = i,
"label" = color_labels?["[i]"],
"value" = (length(split_colors) >= i) ? split_colors[i] : rgb(100, 100, 100),
))
data["colors"] = active_colors
data["generate_full_preview"] = generate_full_preview
data["unlocked"] = unlocked
data["refreshing"] = refreshing
data["monitoring_files"] = !!(config.datum_flags & DF_ISPROCESSING)
data["sprites_dir"] = dir2text(sprite_dir)
data["icon_state"] = icon_state
data["sprites"] = sprite_data || list(
"icon_states" = list(component_style.output_icon_state),
"finished" = null,
"steps" = list(),
"time_spent" = 0,
)
data["hide_full_color_string"] = FALSE
data["component_style"] = list(
"core_components" = component_style.core_ui_data(selected_cores),
"accessories" = component_style.accessory_ui_data(selected_accessories, selected_cores),
)
return data
/datum/greyscale_modify_menu/component_style/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
switch(action)
if("component_style_set_core")
var/core_component = params["component"]
var/new_style = params["value"]
if(!set_core_component(core_component, new_style))
return TRUE
queue_refresh()
return TRUE
if("component_style_toggle_accessory")
var/accessory = params["accessory"]
if(!component_style.accessory_allowed(accessory, selected_cores))
return TRUE
if(accessory in selected_accessories)
selected_accessories -= accessory
else
selected_accessories += accessory
update_component_color_labels()
queue_refresh()
return TRUE
if("random_all_colors")
for(var/color_id in component_style.active_color_ids(selected_cores, selected_accessories))
randomize_color(color_id)
queue_refresh()
return TRUE
if("component_style_reset_color")
var/color_id = text2num(params["color_index"])
if(color_id && length(default_colors) >= color_id)
normalize_split_colors()
split_colors[color_id] = default_colors[color_id]
queue_refresh()
return TRUE
if("component_style_reset_all")
selected_cores = default_cores.Copy()
selected_accessories = default_accessories.Copy()
split_colors = islist(default_colors) ? default_colors.Copy() : list()
normalize_split_colors()
update_component_color_labels()
queue_refresh()
return TRUE
return ..()
/datum/greyscale_modify_menu/component_style/proc/set_core_component(core_key, new_style)
for(var/list/core_component as anything in component_style.core_components)
if(core_component["key"] != core_key)
continue
if(!component_style.core_option_exists(core_component, new_style))
return FALSE
selected_cores[core_key] = new_style
prune_invalid_accessories()
update_component_color_labels()
return TRUE
return FALSE
/datum/greyscale_modify_menu/component_style/proc/prune_invalid_accessories()
selected_accessories = component_style.sanitize_accessories(selected_accessories, selected_cores)
/datum/greyscale_modify_menu/component_style/proc/update_component_color_labels()
color_labels = component_style.active_color_labels(selected_cores, selected_accessories)
/datum/greyscale_modify_menu/component_style/proc/normalize_split_colors()
if(!config)
return
if(!islist(split_colors))
split_colors = list()
if(length(split_colors) > config.expected_colors)
split_colors.Cut(config.expected_colors + 1)
while(length(split_colors) < config.expected_colors)
var/next_color_index = length(split_colors) + 1
split_colors += (islist(default_colors) && length(default_colors) >= next_color_index) ? default_colors[next_color_index] : rgb(100, 100, 100)
/datum/greyscale_modify_menu/component_style/refresh_preview()
normalize_split_colors()
var/list/used_colors = split_colors.Copy(1, config.expected_colors + 1)
sprite_data = list()
sprite_data["icon_states"] = list(component_style.output_icon_state)
icon_state = component_style.output_icon_state
var/time_spent = TICK_USAGE
var/icon/preview_icon = component_style.build_icon(preview_icon_file, used_colors, selected_cores, selected_accessories, preview_use_digi, preview_digi_fallback_icon_file, only_dir = sprite_dir, state_overrides = preview_state_overrides)
var/image/finished = image(preview_icon, icon_state = icon_state)
time_spent = TICK_USAGE - time_spent
sprite_data["time_spent"] = TICK_DELTA_TO_MS(time_spent)
sprite_data["finished"] = icon2html(finished, user, dir = sprite_dir, sourceonly = TRUE)
sprite_data["steps"] = list()
refreshing = FALSE
@@ -76,6 +76,22 @@
item_path = /obj/item/clothing/head/soft/sec
restricted_roles = list(ALL_JOBS_SEC)
/datum/loadout_item/head/security_cap/recolorable
name = "Recolorable Security Cap"
item_path = /obj/item/clothing/head/soft/sec/recolorable
restricted_roles = list(ALL_JOBS_SEC)
greyscale_component_preview_icon_file = 'modular_zubbers/icons/obj/clothing/head/hats.dmi'
/datum/loadout_item/head/security_cap/recolorable/set_item_color(datum/preference_middleware/loadout/manager, mob/user)
return set_component_style_item_color(manager, user)
/datum/loadout_item/head/security_cap/recolorable/set_slot_greyscale(datum/preference_middleware/loadout/manager, datum/greyscale_modify_menu/open_menu)
return set_component_style_slot_greyscale(manager, open_menu)
/datum/loadout_item/head/security_cap/recolorable/on_equip_item(obj/item/equipped_item, list/item_details, mob/living/carbon/human/equipper, datum/outfit/outfit, visuals_only = FALSE)
. = ..()
apply_component_style_to_equipped_item(equipped_item, item_details)
/datum/loadout_item/head/security_cap/veteran
name = "Veteran's Soft Security Cap"
item_path = /obj/item/clothing/head/soft/veteran
@@ -80,6 +80,21 @@
item_path = /obj/item/clothing/under/rank/security/officer/redsec
restricted_roles = list(ALL_JOBS_SEC)
/datum/loadout_item/uniform/security/officer/recolorable
name = "Recolorable Security Uniform"
item_path = /obj/item/clothing/under/rank/security/officer/recolorable
restricted_roles = list(ALL_JOBS_SEC)
/datum/loadout_item/uniform/security/officer/recolorable/set_item_color(datum/preference_middleware/loadout/manager, mob/user)
return set_component_style_item_color(manager, user)
/datum/loadout_item/uniform/security/officer/recolorable/set_slot_greyscale(datum/preference_middleware/loadout/manager, datum/greyscale_modify_menu/open_menu)
return set_component_style_slot_greyscale(manager, open_menu)
/datum/loadout_item/uniform/security/officer/recolorable/on_equip_item(obj/item/equipped_item, list/item_details, mob/living/carbon/human/equipper, datum/outfit/outfit, visuals_only = FALSE)
. = ..()
apply_component_style_to_equipped_item(equipped_item, item_details)
/datum/loadout_item/uniform/security/peacekeeper/armadyne
name = "Armadyne Corporate Uniform"
item_path =/obj/item/clothing/under/rank/security/peacekeeper/armadyne
@@ -165,3 +165,73 @@
#undef LUSTWISH_GAUNTLET_UNSEAL
#undef LUSTWISH_BOOT_SEAL
#undef LUSTWISH_BOOT_UNSEAL
// VOSKHOD MOD, ported from Nova. By Vinylspiders
/datum/mod_theme/voskhod
name = "refitted voskhod"
desc = "An upgraded set of aging VOSKHOD armor, brought up to more modern MODular standards."
extended_desc = "While Modular Suits are still somewhat of a rare sight within the PSC, many client nations within the CIN itself have needed to refit aging suits of VOSKHOD suits into something much more modern. This package provides said modernized alternative, with MODular suit technology baked in. Often done with a specialized fabircation system, the same effects can be done with somewhat rare aftermarket parts kits, and quite a bit of elbow grease."
default_skin = "voskhod"
armor_type = /datum/armor/mod_theme_voskhod
complexity_max = DEFAULT_MAX_COMPLEXITY //Five of which is occupied by the in-builts, thus it's closer to 10
charge_drain = DEFAULT_CHARGE_DRAIN * 1.5
allowed_suit_storage = list(
/obj/item/flashlight,
/obj/item/tank/internals,
/obj/item/ammo_box,
/obj/item/ammo_casing,
/obj/item/restraints/handcuffs,
/obj/item/assembly/flash,
/obj/item/melee/baton,
/obj/item/knife/combat,
/obj/item/shield/riot,
/obj/item/gun,
)
variants = list(
"voskhod" = list(
MOD_ICON_OVERRIDE = 'modular_zubbers/icons/obj/clothing/modsuit/mod_clothing.dmi',
MOD_WORN_ICON_OVERRIDE = 'modular_zubbers/icons/mob/clothing/modsuit/mod_clothing.dmi',
/obj/item/clothing/head/mod = list(
UNSEALED_LAYER = HEAD_LAYER,
UNSEALED_CLOTHING = SNUG_FIT,
SEALED_CLOTHING = THICKMATERIAL|STOPSPRESSUREDAMAGE,
SEALED_INVISIBILITY = HIDEFACIALHAIR|HIDEMASK|HIDEEARS|HIDEEYES|HIDEFACE|HIDEHAIR|HIDESNOUT,
SEALED_COVER = HEADCOVERSMOUTH|HEADCOVERSEYES|PEPPERPROOF,
UNSEALED_MESSAGE = HELMET_UNSEAL_MESSAGE,
SEALED_MESSAGE = HELMET_SEAL_MESSAGE,
),
/obj/item/clothing/suit/mod = list(
UNSEALED_CLOTHING = THICKMATERIAL,
SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
SEALED_INVISIBILITY = HIDEJUMPSUIT|HIDETAIL,
UNSEALED_MESSAGE = CHESTPLATE_UNSEAL_MESSAGE,
SEALED_MESSAGE = CHESTPLATE_SEAL_MESSAGE,
),
/obj/item/clothing/gloves/mod = list(
UNSEALED_CLOTHING = THICKMATERIAL,
SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
CAN_OVERSLOT = TRUE,
UNSEALED_MESSAGE = GAUNTLET_UNSEAL_MESSAGE,
SEALED_MESSAGE = GAUNTLET_SEAL_MESSAGE,
),
/obj/item/clothing/shoes/mod = list(
UNSEALED_CLOTHING = THICKMATERIAL,
SEALED_CLOTHING = STOPSPRESSUREDAMAGE,
CAN_OVERSLOT = TRUE,
UNSEALED_MESSAGE = BOOT_UNSEAL_MESSAGE,
SEALED_MESSAGE = BOOT_SEAL_MESSAGE,
),
),
)
/datum/armor/mod_theme_voskhod
melee = 30
bullet = 30
laser = 30
energy = 30
bomb = 30
bio = 30
fire = 80
acid = 85
wound = 20
@@ -35,3 +35,18 @@
/obj/item/mod/module/hat_stabilizer,
)
return ..()
/obj/item/mod/control/pre_equipped/voskhod
applied_cell = /obj/item/stock_parts/power_store/cell/high
theme = /datum/mod_theme/voskhod
applied_modules = list(
/obj/item/mod/module/storage,
/obj/item/mod/module/flashlight,
/obj/item/mod/module/auto_doc,
/obj/item/mod/module/status_readout/operational,
)
default_pins = list(
/obj/item/mod/module/flashlight,
/obj/item/mod/module/auto_doc,
)
@@ -13,6 +13,21 @@
icon_state = "security"
worn_icon_state = "security"
/obj/item/storage/belt/security/setup_reskins()
AddComponent(/datum/component/reskinable_item, /datum/atom_skin/sec_belt, infinite = FALSE)
/datum/atom_skin/sec_belt
abstract_type = /datum/atom_skin/sec_belt
/datum/atom_skin/sec_belt/standard
preview_name = "Standard"
/datum/atom_skin/sec_belt/black
preview_name = "Black"
new_icon = 'modular_zubbers/icons/obj/clothing/belts/belts.dmi'
new_worn_icon = 'modular_zubbers/icons/mob/clothing/belt.dmi'
new_icon_state = "security_black"
///Enables you to quickdraw weapons from security holsters
/datum/storage/security/open_storage(datum/source, mob/user)
var/atom/resolve_parent = parent
@@ -51,6 +66,21 @@
icon_state = "sunhudsec"
glass_colour_type = /datum/client_colour/glass_colour/red
/obj/item/clothing/glasses/hud/security/sunglasses/setup_reskins()
AddComponent(/datum/component/reskinable_item, /datum/atom_skin/sec_hud_sunglasses, infinite = FALSE)
/datum/atom_skin/sec_hud_sunglasses
abstract_type = /datum/atom_skin/sec_hud_sunglasses
/datum/atom_skin/sec_hud_sunglasses/standard
preview_name = "Standard"
/datum/atom_skin/sec_hud_sunglasses/sunglasses
preview_name = "Sunglasses"
new_icon = 'icons/obj/clothing/glasses.dmi'
new_worn_icon = 'icons/mob/clothing/eyes.dmi'
new_icon_state = "sun"
/obj/item/clothing/glasses/hud/security/sunglasses/eyepatch
icon_state = "hudpatch"
base_icon_state = "hudpatch"
@@ -203,16 +233,37 @@
worn_icon = 'icons/mob/clothing/hands.dmi'
icon_state = "sec"
/obj/item/clothing/gloves/color/black/security/setup_reskins()
AddComponent(/datum/component/reskinable_item, /datum/atom_skin/sec_gloves, infinite = FALSE)
/obj/item/clothing/gloves/tackler/security //Can't just overwrite tackler, as there's a ton of subtypes that we'd then need to account for. This is easier. MUCH easier.
icon = 'icons/obj/clothing/gloves.dmi'
worn_icon = 'icons/mob/clothing/hands.dmi'
icon_state = "gorilla"
/obj/item/clothing/gloves/tackler/security/setup_reskins()
AddComponent(/datum/component/reskinable_item, /datum/atom_skin/sec_gloves, infinite = FALSE)
/obj/item/clothing/gloves/tackler/combat
icon = 'icons/obj/clothing/gloves.dmi'
worn_icon = 'icons/mob/clothing/hands.dmi'
icon_state = "gorilla"
/obj/item/clothing/gloves/tackler/combat/setup_reskins()
AddComponent(/datum/component/reskinable_item, /datum/atom_skin/sec_gloves, infinite = FALSE)
/datum/atom_skin/sec_gloves
abstract_type = /datum/atom_skin/sec_gloves
/datum/atom_skin/sec_gloves/standard
preview_name = "Standard"
/datum/atom_skin/sec_gloves/plain_black
preview_name = "Plain Black"
new_icon = 'modular_skyrat/master_files/icons/obj/clothing/gloves.dmi'
new_worn_icon = 'modular_skyrat/master_files/icons/mob/clothing/hands.dmi'
new_icon_state = "combat"
/obj/item/clothing/gloves/kaza_ruk/sec
icon = 'modular_skyrat/master_files/icons/obj/clothing/gloves.dmi'
worn_icon = 'modular_skyrat/master_files/icons/mob/clothing/hands.dmi'
@@ -646,12 +697,6 @@
inhand_icon_state = "security"
worn_icon_state = "security"
/obj/item/storage/belt/security/redsec/Initialize(mapload)
. = ..()
var/list/reskin_components = GetComponents(/datum/component/reskinable_item)
for(var/datum/component/reskinable_item/reskin_component as anything in reskin_components)
qdel(reskin_component)
/obj/item/storage/belt/holster
desc = "A rather plain but still cool looking holster that can hold a handgun, and some ammo."
@@ -10,6 +10,12 @@
cost = 20
*/
/datum/uplink_item/dangerous/Hyeseong
name = "Hyeseong Modular Laser Rifle"
desc = "One of the newest Cybersun products. This weapon can switch modes on the fly, from a lethal laser to a shotgun to even a plasma grenade. These are rare, and don't come cheap. Luckily for you, it also can slowly recharge itself."
item = /obj/item/gun/energy/modular_laser_rifle
cost = 10
/datum/uplink_item/dangerous/stetchkin
name = "Stetchkin APS Machine Pistol kit"
desc = "A burst-fire weapon dating all the way back to the first Soviet Union, reproduced and found uncommonly among Syndicate agents."
@@ -29,6 +29,7 @@
/obj/item/clothing/head/security_kepi = 6,
/obj/item/clothing/head/beret/sec/peacekeeper/armadyne = 6,
/obj/item/clothing/head/soft/sec = 6,
/obj/item/clothing/head/soft/sec/recolorable = 6,
/obj/item/clothing/head/security_beanie = 6,
/obj/item/clothing/head/costume/ushanka/sec = 10,
/obj/item/clothing/head/playbunnyears/security = 6,
@@ -59,6 +60,7 @@
/obj/item/clothing/neck/security_cape = 6,
/obj/item/clothing/neck/security_cape/armplate = 6,
/obj/item/clothing/under/rank/security/officer = 6,
/obj/item/clothing/under/rank/security/officer/recolorable = 6,
/obj/item/clothing/under/rank/security/peacekeeper/skirt = 6,
/obj/item/clothing/under/rank/security/peacekeeper/miniskirt = 6,
/obj/item/clothing/under/rank/security/officer/blueshirt = 6,
Binary file not shown.

After

Width:  |  Height:  |  Size: 581 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 677 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 15 KiB

@@ -0,0 +1,198 @@
/obj/item/clothing/head/helmet/space/voskhod
name = "\proper Voskhod-P depowered combat helmet"
desc = "A composite graphene-plasteel helmet with a ballistic nylon inner padding, complete with a deployable airtight polycarbonate visor and respirator system. <br>\
This particular unit's rebreathers have been salvaged off; unable to resynthesize any more breathable air for the user."
icon = 'modular_skyrat/master_files/icons/obj/clothing/head/helmet.dmi'
worn_icon = 'modular_skyrat/master_files/icons/mob/clothing/head/helmet.dmi'
worn_icon_digi = 'modular_skyrat/master_files/icons/mob/clothing/head_muzzled.dmi'
worn_icon_teshari = 'modular_skyrat/master_files/icons/mob/clothing/species/teshari/head.dmi'
icon_state = "nri_soldier"
armor_type = /datum/armor/hev_suit_nri
supports_variations_flags = NONE
/obj/item/clothing/suit/space/voskhod
name = "\proper Voskhod-P depowered combat armor"
desc = "A hybrid set of space-resistant armor built on a modified mass-produced Nomex-Aerogel flight suit, polyurea coated durathread-lined light plasteel plates hinder mobility as little as possible.\
These 'paralyzed', marketable variations of the suit come with most of their main features removed: from the infamous wound-tending systems, to the less appreciated death alarms. It's more effective, lightweight plates have been removed with just armor-grade steels and ceramics, slowing the suits down signifigantly."
icon = 'modular_skyrat/master_files/icons/obj/clothing/suits/spacesuit.dmi'
worn_icon = 'modular_skyrat/master_files/icons/mob/clothing/suits/spacesuit.dmi'
worn_icon_digi = 'modular_skyrat/master_files/icons/mob/clothing/suits/spacesuit_digi.dmi'
worn_icon_teshari = 'modular_skyrat/master_files/icons/mob/clothing/species/teshari/suit.dmi'
icon_state = "nri_soldier"
armor_type = /datum/armor/hev_suit_nri
allowed = list(/obj/item/gun, /obj/item/melee/baton, /obj/item/melee/energy/sword/saber, /obj/item/restraints/handcuffs, /obj/item/tank/internals)
supports_variations_flags = NONE //It's already huge enough to look like it can work with digis
slowdown = 1
/datum/crafting_recipe/voskhod_to_mod
name = "Depowered Voskhod-To-Refurbished Voskhod MOD Conversion"
desc = "While this is usually done on a specialised automated workbench, you can tinker with the suit manually for a longer while to achieve the same result."
result = /obj/effect/spawner/random/voskhod_refit
reqs = list(
/obj/item/clothing/suit/space/voskhod = 1,
/obj/item/clothing/head/helmet/space/voskhod = 1,
/obj/item/crafting_conversion_kit/voskhod_refit = 1,
/obj/item/storage/backpack/industrial/cin_surplus = 1,
/obj/item/mod/core = 1,
/obj/item/stock_parts/power_store/cell/high = 1,
/obj/item/stack/sheet/plasteel = 10,
/obj/item/stack/cable_coil = 15,
/obj/item/assembly/health = 1,
)
tool_behaviors = list(TOOL_WELDER, TOOL_MULTITOOL)
time = 30 SECONDS
category = CAT_CLOTHING
crafting_flags = parent_type::crafting_flags | CRAFT_SKIP_MATERIALS_PARITY
/obj/effect/spawner/random/voskhod_refit
name = "converted MODskhod spawner"
icon = 'modular_zubbers/icons/obj/clothing/modsuit/mod_clothing.dmi'
icon_state = "voskhod-chestplate-sealed"
spawn_all_loot = TRUE
spawn_loot_count = 1
loot = list(/obj/item/mod/control/pre_equipped/voskhod)
custom_materials = list(/datum/material/alloy/plasteel = SHEET_MATERIAL_AMOUNT * 10, /datum/material/iron = SHEET_MATERIAL_AMOUNT * 1.95, /datum/material/glass = SHEET_MATERIAL_AMOUNT * 1.45)
/obj/item/mod/module/auto_doc
name = "MOD automatic paramedical module"
desc = "The reverse-engineered and redesigned medical assistance system, previously used by the now decommissioned Voskhod combat armor. \
The technology it uses is very similar to the one of the N-URSEI suites, yet miniaturised and lacking self-synthesis capabilities. \
Using a built-in storage of chemical compounds and a miniature chemical mixer, it's capable of injecting its user with a plethora of drugs, \
assisting them with their restoration. However, this system heavily relies on some rarely combat-available chemical compounds to prepare its injections, \
mainly cryptobiolin , which appear in the user's bloodstream from time to time, and its trivial damage assessment systems are prone to kicking in only when you're moderately wounded."
icon_state = "adrenaline_boost"
module_type = MODULE_TOGGLE
incompatible_modules = list(
/obj/item/mod/module/adrenaline_boost,
/obj/item/mod/module/auto_doc,
/obj/item/mod/module/ash_accretion,
)
complexity = 4
removable = TRUE
use_energy_cost = DEFAULT_CHARGE_DRAIN * 20
/// Reagent used as 'fuel'
var/reagent_required = /datum/reagent/cryptobiolin
/// How much of a reagent we need to refill a single boost.
var/reagent_required_amount = 20
/// Maximum amount of reagents this module can hold.
var/reagent_max_amount = 120
/// Flat health threshold above which the module won't heal.
var/health_threshold = 80
/// Cooldown betwen each treatment.
var/general_cooldown = 25 SECONDS
/// Timer for the healing cooldown.
COOLDOWN_DECLARE(heal_timer)
/// Timer for the stamina damage cooldown.
COOLDOWN_DECLARE(stamina_timer)
/// Timer for the blood-refilling cooldown.
COOLDOWN_DECLARE(blood_timer)
/obj/item/mod/module/auto_doc/Initialize(mapload)
. = ..()
create_reagents(reagent_max_amount)
/obj/item/mod/module/auto_doc/on_active_process(seconds_per_tick)
if(!reagents.has_reagent(reagent_required, reagent_required_amount))
balloon_alert(mod.wearer, "not enough chems!")
deactivate()
return FALSE
var/new_oxyloss = mod.wearer.get_oxy_loss()
var/new_bruteloss = mod.wearer.get_brute_loss()
var/new_fireloss = mod.wearer.get_fire_loss()
var/new_toxloss = mod.wearer.get_tox_loss()
if(mod.wearer.get_blood_volume() < BLOOD_VOLUME_OKAY && reagents.total_volume >= reagent_required_amount * 0.5 * seconds_per_tick)
if(!COOLDOWN_FINISHED(src, blood_timer))
return FALSE
mod.wearer.reagents.add_reagent(/datum/reagent/blood, 25, list("viruses"=null,"blood_DNA"=null,"blood_type"=mod.wearer.dna.blood_type,"resistances"=null,"trace_chem"=null))
mod.wearer.reagents.add_reagent(/datum/reagent/medicine/coagulant, 2.5 * seconds_per_tick)
mod.wearer.playsound_local(mod, 'sound/items/hypospray.ogg', 25, TRUE)
reagents.remove_reagent(reagent_required, reagent_required_amount * 0.5 * seconds_per_tick)
to_chat(mod.wearer, span_warning("Blood infused."))
drain_power(use_energy_cost * 10 * seconds_per_tick)
addtimer(CALLBACK(src, PROC_REF(heal_aftereffects), mod.wearer), 60 SECONDS, TIMER_STOPPABLE|TIMER_DELETE_ME)
COOLDOWN_START(src, blood_timer, general_cooldown)
if(mod.wearer.health < health_threshold)
if(!COOLDOWN_FINISHED(src, heal_timer))
return FALSE
if(new_oxyloss && reagents.total_volume >= reagent_required_amount * 0.5 * seconds_per_tick)
mod.wearer.reagents.add_reagent(/datum/reagent/medicine/salbutamol, 2.5 * seconds_per_tick)
mod.wearer.playsound_local(mod, 'sound/items/internals/internals_on.ogg', 25, TRUE)
reagents.remove_reagent(reagent_required, reagent_required_amount * 0.5 * seconds_per_tick)
to_chat(mod.wearer, span_warning("Blood oxygen saturated."))
if(new_bruteloss && reagents.total_volume >= reagent_required_amount * 1 * seconds_per_tick)
mod.wearer.reagents.add_reagent(/datum/reagent/medicine/sal_acid, 2.5 * seconds_per_tick)
mod.wearer.reagents.add_reagent(/datum/reagent/medicine/mine_salve, 2.5 * seconds_per_tick)
mod.wearer.playsound_local(mod, 'sound/effects/spray2.ogg', 25, TRUE)
reagents.remove_reagent(reagent_required, reagent_required_amount * 1 * seconds_per_tick)
to_chat(mod.wearer, span_warning("Wound treatment administered."))
if(new_fireloss && reagents.total_volume >= reagent_required_amount * 1 * seconds_per_tick)
mod.wearer.reagents.add_reagent(/datum/reagent/medicine/oxandrolone, 2.5 * seconds_per_tick)
mod.wearer.reagents.add_reagent(/datum/reagent/medicine/mine_salve, 2.5 * seconds_per_tick)
mod.wearer.playsound_local(mod, 'sound/effects/spray2.ogg', 25, TRUE)
reagents.remove_reagent(reagent_required, reagent_required_amount * 1 * seconds_per_tick)
to_chat(mod.wearer, span_warning("Ointment applied."))
if(new_toxloss && reagents.total_volume >= reagent_required_amount * 0.5 * seconds_per_tick)
mod.wearer.reagents.add_reagent(/datum/reagent/medicine/pen_acid, 2.5 * seconds_per_tick)
mod.wearer.playsound_local(mod, 'sound/items/hypospray.ogg', 25, TRUE)
reagents.remove_reagent(reagent_required, reagent_required_amount * 0.5 * seconds_per_tick)
to_chat(mod.wearer, span_warning("Antitoxin administered."))
drain_power(use_energy_cost * 15 * seconds_per_tick)
addtimer(CALLBACK(src, PROC_REF(heal_aftereffects), mod.wearer), 60 SECONDS)
COOLDOWN_START(src, heal_timer, general_cooldown)
/obj/item/mod/module/auto_doc/emp_act(severity)
. = ..()
on_emp(src, severity, .)
/obj/item/mod/module/auto_doc/proc/on_emp(datum/source, severity, protection)
SIGNAL_HANDLER
if(protection & EMP_PROTECT_SELF)
return
heal_aftereffects(mod.wearer, TRUE)
/// Refills the module with needed chemicals, assuming the container isn't closed or the module isn't full.
/obj/item/mod/module/auto_doc/proc/charge_boost(obj/item/attacking_item, mob/user)
if(!attacking_item.is_open_container())
return FALSE
if(reagents.has_reagent(reagent_required, reagent_max_amount))
balloon_alert(mod.wearer, "already full!")
return FALSE
if(!attacking_item.reagents.trans_to(src, reagent_required_amount, target_id = reagent_required))
return FALSE
balloon_alert(mod.wearer, "charge reloaded!")
return TRUE
/obj/item/mod/module/auto_doc/on_install()
. = ..()
RegisterSignal(mod, COMSIG_ATOM_ITEM_INTERACTION, PROC_REF(try_refill))
RegisterSignal(mod, COMSIG_ATOM_EMP_ACT, PROC_REF(on_emp))
/obj/item/mod/module/auto_doc/on_uninstall(deleting = FALSE)
. = ..()
UnregisterSignal(mod, COMSIG_ATOM_ITEM_INTERACTION)
UnregisterSignal(mod, COMSIG_ATOM_EMP_ACT)
/obj/item/mod/module/auto_doc/proc/try_refill(source, mob/user, obj/item/attacking_item)
SIGNAL_HANDLER
if(charge_boost(attacking_item))
return COMPONENT_NO_AFTERATTACK
return NONE
/// With a certain chance, triggers a spontaneous injection of cryptobiolin into the user's bloodstream; suit design's rather ancient and prone to mishaps.
/obj/item/mod/module/auto_doc/proc/heal_aftereffects(mob/affected_mob, forced)
if(!affected_mob)
return
var/fault_chance = (reagents.maximum_volume/(reagents.total_volume ? reagents.total_volume : 20))*5 // 5% at max cryptobiolin , 20% at low-to-none cryptobiolin
if(prob(fault_chance) || forced == TRUE)
reagents.trans_to(affected_mob, min(15,reagents.total_volume))
balloon_alert(affected_mob, "reagent canister leak!")
affected_mob.playsound_local(mod, 'sound/effects/spray3.ogg', 25, TRUE)
/obj/item/reagent_containers/cup/glass/waterbottle/large/cryptobiolin
name = "bottle of 'Medical Reagents'"
desc = "Nothing screams 'Budget cuts' like a plastic bottle of autodoc refills."
list_reagents = list(/datum/reagent/cryptobiolin = 100)
+12
View File
@@ -95,6 +95,18 @@
"Close-quarters mode engaged, lethal."
],
"flare": [
"Ignite the flames! Now!",
"Incendiary, incendiary!",
"Fabricating 'Balrog' energy flares; this'll be sick.",
"Fabricating 'Balrog' energy flares; can you feel the fire?",
"Firestarter, twisted firestarter!",
"Apocalyptic meltdown!",
"Incendiary weapons are banned in numerous sectors; makes it kind of risqué..",
"Dragon breath!"
],
"operator": [
"Operator",
"Handler"
+3 -3
View File
@@ -79,9 +79,9 @@
"incinerate": [
"Superheating lens, time to cauterize!",
"Charging Hellfire capacitators, let's toast em!",
"Hellfire capacitators charging. Don't stop 'till I'm empty!",
"Hellfire! Bring it on, bastards!",
"Charging lethal energy capacitators, let's toast em!",
"Lethal capacitators charging. Don't stop 'till I'm empty!",
"Lethal! Bring it on, bastards!",
"Heh-heh, can you feel the heat?",
"Gunslinger!"
],
+5
View File
@@ -9071,6 +9071,7 @@
#include "modular_skyrat\modules\xenos_skyrat_redo\code\xeno_types\warrior.dm"
#include "modular_zubbers\code\__DEFINES\bloodsucker_defines.dm"
#include "modular_zubbers\code\__DEFINES\guardian_defines.dm"
#include "modular_zubbers\code\__DEFINES\loadout.dm"
#include "modular_zubbers\code\__DEFINES\maps.dm"
#include "modular_zubbers\code\__DEFINES\moonstation_defines.dm"
#include "modular_zubbers\code\__DEFINES\projectiles.dm"
@@ -9581,9 +9582,11 @@
#include "modular_zubbers\code\modules\cargo\bounties\blacksmith.dm"
#include "modular_zubbers\code\modules\cargo\bounties\prisoner.dm"
#include "modular_zubbers\code\modules\cargo\exports\weapons.dm"
#include "modular_zubbers\code\modules\cargo\markets\market_items\misc.dm"
#include "modular_zubbers\code\modules\cargo\packs\contraband.dm"
#include "modular_zubbers\code\modules\cargo\packs\general.dm"
#include "modular_zubbers\code\modules\cargo\packs\goodies.dm"
#include "modular_zubbers\code\modules\cargo\packs\imports.dm"
#include "modular_zubbers\code\modules\cargo\packs\medical.dm"
#include "modular_zubbers\code\modules\cargo\packs\minerals.dm"
#include "modular_zubbers\code\modules\cargo\packs\security.dm"
@@ -9808,6 +9811,7 @@
#include "modular_zubbers\code\modules\general_sprite_overrides\turfs\walls.dm"
#include "modular_zubbers\code\modules\ghost_wavedefence\wave_def.dm"
#include "modular_zubbers\code\modules\gladiator\code\game\objects\items\gladiator_items.dm"
#include "modular_zubbers\code\modules\greyscale\component_style.dm"
#include "modular_zubbers\code\modules\hydroponics\gene_modder.dm"
#include "modular_zubbers\code\modules\hydroponics\plant_genes.dm"
#include "modular_zubbers\code\modules\hydroponics\grown\rocks.dm"
@@ -10462,4 +10466,5 @@
#include "modular_zubbers\modules\taur_mechanics\taur_body.dm"
#include "modular_zubbers\modules\taur_mechanics\taur_clothing_offset.dm"
#include "modular_zubbers\modules\wood_walls\code\new_floors.dm"
#include "modular_zubbers\sound\novaya_ert\code\mod_suit.dm"
// END_INCLUDE
@@ -18,6 +18,7 @@ import { Window } from '../layouts';
type ColorEntry = {
index: number;
label?: string; //BUBBER ADDITION - dynamic uniforms
value: string;
};
@@ -37,6 +38,11 @@ type SpriteEntry = {
type GreyscaleMenuData = {
greyscale_config: string;
colors: ColorEntry[];
//BUBBER ADDITION START - dynamic uniforms
full_color_string?: string;
hide_full_color_string?: boolean;
component_style?: ComponentStyleData;
//BUBBER ADDITION END - dynamic uniforms
sprites: SpriteData;
generate_full_preview: boolean;
unlocked: boolean;
@@ -46,6 +52,25 @@ type GreyscaleMenuData = {
refreshing: boolean;
};
//BUBBER ADDITION START - dynamic uniforms
type ComponentStyleOption = {
name: string;
value: string;
selected: boolean;
};
type ComponentStyleCore = {
name: string;
key: string;
options: ComponentStyleOption[];
};
type ComponentStyleData = {
core_components: ComponentStyleCore[];
accessories: ComponentStyleOption[];
};
//BUBBER ADDITION END - dynamic uniforms
enum Direction {
North = 'north',
NorthEast = 'northeast',
@@ -86,6 +111,68 @@ const ConfigDisplay = (props) => {
</Section>
);
};
//BUBBER ADDITION START - dynamic uniforms
const ComponentStyleDisplay = (props) => {
const { act, data } = useBackend<GreyscaleMenuData>();
const style = data.component_style;
if (!style) {
return null;
}
return (
<Section title="Style">
<LabeledList>
{style.core_components.map((component) => (
<LabeledList.Item key={component.key} label={component.name}>
<Flex wrap>
{component.options.map((item) => (
<Flex.Item
key={`${component.key}-${item.value}`}
mr={0.5}
mb={0.5}
>
<Button.Checkbox
checked={item.selected}
onClick={() =>
act('component_style_set_core', {
component: component.key,
value: item.value,
})
}
>
{item.name}
</Button.Checkbox>
</Flex.Item>
))}
</Flex>
</LabeledList.Item>
))}
{!!style.accessories.length && (
<LabeledList.Item label="Accessories">
<Flex wrap>
{style.accessories.map((item) => (
<Flex.Item key={item.value} mr={0.5} mb={0.5}>
<Button.Checkbox
checked={item.selected}
onClick={() =>
act('component_style_toggle_accessory', {
accessory: item.value,
})
}
>
{item.name}
</Button.Checkbox>
</Flex.Item>
))}
</Flex>
</LabeledList.Item>
)}
</LabeledList>
</Section>
);
};
//BUBBER ADDITION END - dynamic uniforms
const ColorDisplay = (props) => {
const { act, data } = useBackend<GreyscaleMenuData>();
@@ -93,23 +180,37 @@ const ColorDisplay = (props) => {
return (
<Section title="Colors">
<LabeledList>
<LabeledList.Item label="Full Color String">
<Button
icon="dice"
onClick={() => act('random_all_colors')}
tooltip="Randomizes all color groups."
/>
<Input
value={colors.map((item) => item.value).join('')}
onBlur={(value) =>
act('recolor_from_string', { color_string: value })
}
/>
</LabeledList.Item>
{/* BUBBER EDIT START - dynamic uniforms */}
{!data.hide_full_color_string && (
<LabeledList.Item label="Full Color String">
<Button
icon="dice"
onClick={() => act('random_all_colors')}
tooltip="Randomizes all color groups."
/>
{!!data.component_style && (
<Button
icon="undo"
onClick={() => act('component_style_reset_all')}
tooltip="Resets colors and style options to their defaults."
/>
)}
<Input
value={
data.full_color_string ||
colors.map((item) => item.value).join('')
}
onBlur={(value) =>
act('recolor_from_string', { color_string: value })
}
/>
</LabeledList.Item>
)}
{/* BUBBER EDIT END - dynamic uniforms */}
{colors.map((item) => (
<LabeledList.Item
key={`colorgroup${item.index}${item.value}`}
label={`Color Group ${item.index}`}
label={item.label || `Color Group ${item.index}`} //BUBBER EDIT
color={item.value}
>
<ColorBox color={item.value} />{' '}
@@ -123,6 +224,19 @@ const ColorDisplay = (props) => {
onClick={() => act('random_color', { color_index: item.index })}
tooltip="Randomizes the color for this color group."
/>
{/* BUBBER EDIT START - dynamic uniforms */}
{!!data.component_style && (
<Button
icon="undo"
onClick={() =>
act('component_style_reset_color', {
color_index: item.index,
})
}
tooltip="Resets this color group to its default."
/>
)}
{/* BUBBER EDIT END - dynamic uniforms */}
<Input
value={item.value}
width={7}
@@ -284,6 +398,9 @@ export const GreyscaleModifyMenu = (props) => {
<Window title="Color Configuration" width={325} height={800}>
<Window.Content scrollable>
<ConfigDisplay />
{/* BUBBER ADDITION START - dynamic uniforms */}
<ComponentStyleDisplay />
{/* BUBBER ADDITION END - dynamic uniforms */}
<ColorDisplay />
<IconStatesDisplay />
<Flex direction="column">
@@ -21,6 +21,20 @@ export function ItemIcon(props: Props) {
const { item, scale = 3 } = props;
const icon_to_use = item.icon;
const icon_state_to_use = item.icon_state;
//BUBBER EDIT START - dyamic uniforms
if (item.image) {
return (
<img
src={`data:image/png;base64,${item.image}`}
style={{
width: `${32 * scale}px`,
height: `${32 * scale}px`,
imageRendering: 'pixelated',
}}
/>
);
}
//BUBBER EDIT END - dynamic uniforms
if (!icon_to_use || !icon_state_to_use) {
return (
@@ -67,6 +81,7 @@ export function ItemDisplay(props: DisplayProps) {
style={{ textTransform: 'capitalize', zIndex: '1' }}
tooltip={item.name}
tooltipPosition={'bottom'}
base64={item.image} //BUBBER EDIT ADDITION: Dynamic uniform icons
dmIcon={item.icon}
dmIconState={item.icon_state}
onClick={() =>
@@ -36,6 +36,7 @@ export type LoadoutItem = {
path: typePath;
icon: DmIconFile | null;
icon_state: DmIconState | null;
image?: string;
buttons: LoadoutButton[];
reskins: ReskinOption[] | null;
information: LoadoutTooltip[];