From e9abe8dcd2f94bcea32369b53b091a334958de6e Mon Sep 17 00:00:00 2001 From: John Willard <53777086+JohnFulpWillard@users.noreply.github.com> Date: Wed, 1 May 2024 13:03:13 +0000 Subject: [PATCH 01/87] Fixes monkeys falling over through windoors (#82963) ## About The Pull Request Monkey knockover is now on a new mob-bump signal instead of being on crossed, fixing some consistency issues and getting rid of our need for snowflake "can bump into mob" check. We should try to get rid of all our instances of Crossed and remove its use to prevent this bug from occurring in the future. ## Why It's Good For The Game Closes https://github.com/tgstation/tgstation/issues/66983 ## Changelog :cl: fix: Monkeys can no longer be knocked over by walking into a windoor on combat mode while they're behind it. /:cl: --- .../signals/signals_mob/signals_mob_living.dm | 2 ++ .../events/immovable_rod/immovable_rod.dm | 5 ----- .../carbon/human/species_types/monkeys.dm | 17 +++++------------ code/modules/mob/living/living.dm | 1 + 4 files changed, 8 insertions(+), 17 deletions(-) diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm index cc9e4d2e9666a..5a328a62ef796 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_living.dm @@ -146,6 +146,8 @@ #define COMPONENT_LIVING_BLOCK_PRE_MOB_BUMP (1<<0) ///From base of mob/living/MobBump() (mob/living) #define COMSIG_LIVING_MOB_BUMP "living_mob_bump" +///From base of mob/living/MobBump() (mob/living) +#define COMSIG_LIVING_MOB_BUMPED "living_mob_bumped" ///From base of mob/living/Bump() (turf/closed) #define COMSIG_LIVING_WALL_BUMP "living_wall_bump" ///From base of turf/closed/Exited() (turf/closed) diff --git a/code/modules/events/immovable_rod/immovable_rod.dm b/code/modules/events/immovable_rod/immovable_rod.dm index 285a99cd31dd7..e9d2995218d56 100644 --- a/code/modules/events/immovable_rod/immovable_rod.dm +++ b/code/modules/events/immovable_rod/immovable_rod.dm @@ -76,11 +76,6 @@ if(istype(ghost)) ghost.ManualFollow(src) -/obj/effect/immovablerod/proc/on_entered_over_movable(datum/source, atom/movable/atom_crossed_over) - SIGNAL_HANDLER - if((atom_crossed_over.density || isliving(atom_crossed_over)) && !QDELETED(atom_crossed_over)) - Bump(atom_crossed_over) - /obj/effect/immovablerod/proc/on_entering_atom(datum/source, atom/destination, atom/old_loc, list/atom/old_locs) SIGNAL_HANDLER if(destination.density && isturf(destination)) diff --git a/code/modules/mob/living/carbon/human/species_types/monkeys.dm b/code/modules/mob/living/carbon/human/species_types/monkeys.dm index 159824f2ef33f..cbd2df699ea1a 100644 --- a/code/modules/mob/living/carbon/human/species_types/monkeys.dm +++ b/code/modules/mob/living/carbon/human/species_types/monkeys.dm @@ -168,24 +168,17 @@ /obj/item/organ/internal/brain/primate/on_mob_insert(mob/living/carbon/primate) . = ..() - RegisterSignal(primate, COMSIG_MOVABLE_CROSS, PROC_REF(on_crossed)) + RegisterSignal(primate, COMSIG_LIVING_MOB_BUMPED, PROC_REF(on_mob_bump)) /obj/item/organ/internal/brain/primate/on_mob_remove(mob/living/carbon/primate) . = ..() - UnregisterSignal(primate, COMSIG_MOVABLE_CROSS) + UnregisterSignal(primate, COMSIG_LIVING_MOB_BUMPED) -/obj/item/organ/internal/brain/primate/proc/on_crossed(datum/source, atom/movable/crossed) +/obj/item/organ/internal/brain/primate/proc/on_mob_bump(mob/source, mob/living/crossing_mob) SIGNAL_HANDLER - if(!tripping) + if(!tripping || !crossing_mob.combat_mode) return - if(IS_DEAD_OR_INCAP(owner) || !isliving(crossed)) - return - var/mob/living/in_the_way_mob = crossed - if(iscarbon(in_the_way_mob) && !in_the_way_mob.combat_mode) - return - if(in_the_way_mob.pass_flags & PASSMOB) - return - in_the_way_mob.knockOver(owner) + crossing_mob.knockOver(owner) /obj/item/organ/internal/brain/primate/get_attacking_limb(mob/living/carbon/human/target) if(!HAS_TRAIT(owner, TRAIT_ADVANCEDTOOLUSER)) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 5981fbb390300..d77d7409561ef 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -156,6 +156,7 @@ return TRUE SEND_SIGNAL(src, COMSIG_LIVING_MOB_BUMP, M) + SEND_SIGNAL(M, COMSIG_LIVING_MOB_BUMPED, src) //Even if we don't push/swap places, we "touched" them, so spread fire spreadFire(M) From 641b60926d97d8dee318197f137998656982c553 Mon Sep 17 00:00:00 2001 From: orange man <61334995+comfyorange@users.noreply.github.com> Date: Thu, 2 May 2024 01:03:34 +1200 Subject: [PATCH 02/87] Automatic changelog for PR #82963 [ci skip] --- html/changelogs/AutoChangeLog-pr-82963.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-82963.yml diff --git a/html/changelogs/AutoChangeLog-pr-82963.yml b/html/changelogs/AutoChangeLog-pr-82963.yml new file mode 100644 index 0000000000000..bdbe508a6a04b --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-82963.yml @@ -0,0 +1,4 @@ +author: "JohnFulpWillard" +delete-after: True +changes: + - bugfix: "Monkeys can no longer be knocked over by walking into a windoor on combat mode while they're behind it." \ No newline at end of file From b34f833df96ca77881e11126318ca4b1cb618310 Mon Sep 17 00:00:00 2001 From: John Willard <53777086+JohnFulpWillard@users.noreply.github.com> Date: Wed, 1 May 2024 13:04:38 +0000 Subject: [PATCH 03/87] Makes pod doors re-constructible (#82939) ## About The Pull Request Adds the ability to reconstruct pod doors once deconstruction has been started, and adds a pre-deconstructed pod door to the list of plasteel crafting recipes. Also adds context tips and some minor extra feedback during deconstruction steps. ## Why It's Good For The Game Closes https://github.com/tgstation/tgstation/issues/68800 Door construction like airlocks are all possible this slap-crafting rather than stuffing all of it in the crafting menu, I thought it would be nice to have pod doors available there as well. ## Changelog :cl: fix: You can now re-construct pod doors that had deconstruction started. qol: You can now make pod door assemblies using plasteel in-hand. /:cl: --- code/__DEFINES/construction/structures.dm | 4 +- code/game/machinery/doors/poddoor.dm | 89 +++++++++++++++---- code/game/machinery/doors/shutters.dm | 3 + .../items/stacks/sheets/sheet_types.dm | 1 + 4 files changed, 81 insertions(+), 16 deletions(-) diff --git a/code/__DEFINES/construction/structures.dm b/code/__DEFINES/construction/structures.dm index 1b4ea8aa11297..e48dc078ca4bc 100644 --- a/code/__DEFINES/construction/structures.dm +++ b/code/__DEFINES/construction/structures.dm @@ -48,9 +48,11 @@ #define AIRLOCK_ASSEMBLY_NEEDS_ELECTRONICS 1 #define AIRLOCK_ASSEMBLY_NEEDS_SCREWDRIVER 2 -//blast door (de)construction states +///The blast door is missing wires, first step of construction. #define BLASTDOOR_NEEDS_WIRES 0 +///The blast door needs electronics, second step of construction. #define BLASTDOOR_NEEDS_ELECTRONICS 1 +///The blast door is fully constructed. #define BLASTDOOR_FINISHED 2 //floodlights because apparently we use defines now diff --git a/code/game/machinery/doors/poddoor.dm b/code/game/machinery/doors/poddoor.dm index ea8d758666e7d..48e0cb195d7f3 100644 --- a/code/game/machinery/doors/poddoor.dm +++ b/code/game/machinery/doors/poddoor.dm @@ -35,6 +35,72 @@ /obj/machinery/door/poddoor/get_save_vars() return ..() + NAMEOF(src, id) +/obj/machinery/door/poddoor/examine(mob/user) + . = ..() + if(panel_open) + if(deconstruction == BLASTDOOR_FINISHED) + . += span_notice("The maintenance panel is opened and the electronics could be pried out.") + . += span_notice("\The [src] could be calibrated to a blast door controller ID with a multitool.") + else if(deconstruction == BLASTDOOR_NEEDS_ELECTRONICS) + . += span_notice("The electronics are missing and there are some wires sticking out.") + else if(deconstruction == BLASTDOOR_NEEDS_WIRES) + . += span_notice("The wires have been removed and it's ready to be sliced apart.") + +/obj/machinery/door/poddoor/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = ..() + if(isnull(held_item)) + return NONE + if(deconstruction == BLASTDOOR_NEEDS_WIRES && istype(held_item, /obj/item/stack/cable_coil)) + context[SCREENTIP_CONTEXT_LMB] = "Wire assembly" + return CONTEXTUAL_SCREENTIP_SET + if(deconstruction == BLASTDOOR_NEEDS_ELECTRONICS && istype(held_item, /obj/item/electronics/airlock)) + context[SCREENTIP_CONTEXT_LMB] = "Add electronics" + return CONTEXTUAL_SCREENTIP_SET + //we do not check for special effects like if they can actually perform the action because they will be told they can't do it when they try, + //with feedback on what they have to do in order to do so. + switch(held_item.tool_behaviour) + if(TOOL_SCREWDRIVER) + context[SCREENTIP_CONTEXT_LMB] = "Open panel" + return CONTEXTUAL_SCREENTIP_SET + if(TOOL_MULTITOOL) + context[SCREENTIP_CONTEXT_LMB] = "Calibrate ID" + return CONTEXTUAL_SCREENTIP_SET + if(TOOL_CROWBAR) + context[SCREENTIP_CONTEXT_LMB] = "Remove electronics" + return CONTEXTUAL_SCREENTIP_SET + if(TOOL_WIRECUTTER) + context[SCREENTIP_CONTEXT_LMB] = "Remove wires" + return CONTEXTUAL_SCREENTIP_SET + if(TOOL_WELDER) + context[SCREENTIP_CONTEXT_LMB] = "Disassemble" + return CONTEXTUAL_SCREENTIP_SET + +/obj/machinery/door/poddoor/item_interaction(mob/living/user, obj/item/tool, list/modifiers) + if(deconstruction == BLASTDOOR_NEEDS_WIRES && istype(tool, /obj/item/stack/cable_coil)) + var/obj/item/stack/cable_coil/coil = tool + var/datum/crafting_recipe/recipe = locate(recipe_type) in GLOB.crafting_recipes + var/amount_needed = recipe.reqs[/obj/item/stack/cable_coil] + if(coil.get_amount() < amount_needed) + balloon_alert(user, "not enough cable!") + return ITEM_INTERACT_SUCCESS + balloon_alert(user, "adding cables...") + if(!do_after(user, 5 SECONDS, src)) + return ITEM_INTERACT_SUCCESS + coil.use(amount_needed) + deconstruction = BLASTDOOR_NEEDS_ELECTRONICS + balloon_alert(user, "cables added") + return ITEM_INTERACT_SUCCESS + + if(deconstruction == BLASTDOOR_NEEDS_ELECTRONICS && istype(tool, /obj/item/electronics/airlock)) + balloon_alert(user, "adding electronics...") + if(!do_after(user, 10 SECONDS, src)) + return ITEM_INTERACT_SUCCESS + qdel(tool) + balloon_alert(user, "electronics added") + deconstruction = BLASTDOOR_FINISHED + return ITEM_INTERACT_SUCCESS + return NONE + /obj/machinery/door/poddoor/screwdriver_act(mob/living/user, obj/item/tool) . = ..() if (density) @@ -49,7 +115,8 @@ balloon_alert(user, "open the door first!") return ITEM_INTERACT_SUCCESS if (!panel_open) - return + balloon_alert(user, "open the panel first!") + return ITEM_INTERACT_SUCCESS if (deconstruction != BLASTDOOR_FINISHED) return var/change_id = tgui_input_number(user, "Set the door controllers ID (Current: [id])", "Door Controller ID", isnum(id) ? id : null, 100) @@ -69,7 +136,8 @@ balloon_alert(user, "open the door first!") return ITEM_INTERACT_SUCCESS if (!panel_open) - return + balloon_alert(user, "open the panel first!") + return ITEM_INTERACT_SUCCESS if (deconstruction != BLASTDOOR_FINISHED) return balloon_alert(user, "removing airlock electronics...") @@ -86,7 +154,8 @@ balloon_alert(user, "open the door first!") return ITEM_INTERACT_SUCCESS if (!panel_open) - return + balloon_alert(user, "open the panel first!") + return ITEM_INTERACT_SUCCESS if (deconstruction != BLASTDOOR_NEEDS_ELECTRONICS) return balloon_alert(user, "removing internal cables...") @@ -104,7 +173,8 @@ balloon_alert(user, "open the door first!") return ITEM_INTERACT_SUCCESS if (!panel_open) - return + balloon_alert(user, "open the panel first!") + return ITEM_INTERACT_SUCCESS if (deconstruction != BLASTDOOR_NEEDS_WIRES) return balloon_alert(user, "tearing apart...") //You're tearing me apart, Lisa! @@ -116,17 +186,6 @@ qdel(src) return ITEM_INTERACT_SUCCESS -/obj/machinery/door/poddoor/examine(mob/user) - . = ..() - if(panel_open) - if(deconstruction == BLASTDOOR_FINISHED) - . += span_notice("The maintenance panel is opened and the electronics could be pried out.") - . += span_notice("\The [src] could be calibrated to a blast door controller ID with a multitool.") - else if(deconstruction == BLASTDOOR_NEEDS_ELECTRONICS) - . += span_notice("The electronics are missing and there are some wires sticking out.") - else if(deconstruction == BLASTDOOR_NEEDS_WIRES) - . += span_notice("The wires have been removed and it's ready to be sliced apart.") - /obj/machinery/door/poddoor/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock) id = "[port.shuttle_id]_[id]" diff --git a/code/game/machinery/doors/shutters.dm b/code/game/machinery/doors/shutters.dm index eca8d88da4baf..0df6024ca827a 100644 --- a/code/game/machinery/doors/shutters.dm +++ b/code/game/machinery/doors/shutters.dm @@ -16,6 +16,9 @@ density = FALSE opacity = FALSE +/obj/machinery/door/poddoor/shutters/preopen/deconstructed + deconstruction = BLASTDOOR_NEEDS_WIRES + /obj/machinery/door/poddoor/shutters/indestructible name = "hardened shutters" resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm index f00767217a493..b5d4f596a56a1 100644 --- a/code/game/objects/items/stacks/sheets/sheet_types.dm +++ b/code/game/objects/items/stacks/sheets/sheet_types.dm @@ -256,6 +256,7 @@ GLOBAL_LIST_INIT(plasteel_recipes, list ( \ new/datum/stack_recipe("AI core", /obj/structure/ai_core, 4, time = 5 SECONDS, one_per_turf = TRUE, check_density = FALSE, category = CAT_ROBOT), new/datum/stack_recipe("bomb assembly", /obj/machinery/syndicatebomb/empty, 10, time = 5 SECONDS, check_density = FALSE, category = CAT_CHEMISTRY), new/datum/stack_recipe("Large Gas Tank", /obj/structure/tank_frame, 4, time=1 SECONDS, one_per_turf=TRUE, check_density = FALSE, category = CAT_ATMOSPHERIC), + new/datum/stack_recipe("shutter assembly", /obj/machinery/door/poddoor/shutters/preopen/deconstructed, 5, time = 5 SECONDS, one_per_turf = TRUE, check_density = FALSE, category = CAT_DOORS), null, new /datum/stack_recipe_list("airlock assemblies", list( \ new/datum/stack_recipe("high security airlock assembly", /obj/structure/door_assembly/door_assembly_highsecurity, 4, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_DOORS), From 65a36f5b96d7826938bc77bd6bd7f3ee574af703 Mon Sep 17 00:00:00 2001 From: orange man <61334995+comfyorange@users.noreply.github.com> Date: Thu, 2 May 2024 01:04:58 +1200 Subject: [PATCH 04/87] Automatic changelog for PR #82939 [ci skip] --- html/changelogs/AutoChangeLog-pr-82939.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-82939.yml diff --git a/html/changelogs/AutoChangeLog-pr-82939.yml b/html/changelogs/AutoChangeLog-pr-82939.yml new file mode 100644 index 0000000000000..291878922ab75 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-82939.yml @@ -0,0 +1,5 @@ +author: "JohnFulpWillard" +delete-after: True +changes: + - bugfix: "You can now re-construct pod doors that had deconstruction started." + - qol: "You can now make pod door assemblies using plasteel in-hand." \ No newline at end of file From ec89592301722f6b7ac62fe807efab8f297d32b6 Mon Sep 17 00:00:00 2001 From: John Willard <53777086+JohnFulpWillard@users.noreply.github.com> Date: Wed, 1 May 2024 14:59:17 +0000 Subject: [PATCH 05/87] Debug uplinks shows all categories and doesnt lock (#82967) ## About The Pull Request Using debug uplinks once again shows you all categories of role/species restricted items. I also made it not show the shop as being locked since they are meant to always be able to buy things per; https://github.com/tgstation/tgstation/blob/fae4ec5aa1fdd51364b2ffdd49d54ab22c2dc38f/code/modules/antagonists/traitor/uplink_handler.dm#L90-L110 ## Why It's Good For The Game Closes https://github.com/tgstation/tgstation/issues/66743 ## Changelog :cl: admin: Debug uplinks now shows all categories and won't lock upon buying items like his grace and the syndicate balloon. /:cl: --- code/datums/components/uplink.dm | 2 +- code/modules/uplink/uplink_items.dm | 2 +- tgui/packages/tgui/interfaces/Uplink/index.tsx | 8 +++++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/code/datums/components/uplink.dm b/code/datums/components/uplink.dm index 163de4867d575..d62414a862b24 100644 --- a/code/datums/components/uplink.dm +++ b/code/datums/components/uplink.dm @@ -266,7 +266,7 @@ /datum/component/uplink/ui_assets(mob/user) return list( - get_asset_datum(/datum/asset/json/uplink) + get_asset_datum(/datum/asset/json/uplink), ) /datum/component/uplink/ui_act(action, params, datum/tgui/ui, datum/ui_state/state) diff --git a/code/modules/uplink/uplink_items.dm b/code/modules/uplink/uplink_items.dm index f0f7b0accaf5e..e17488bc96a6d 100644 --- a/code/modules/uplink/uplink_items.dm +++ b/code/modules/uplink/uplink_items.dm @@ -71,7 +71,7 @@ /// If this uplink item is only available to certain roles. Roles are dependent on the frequency chip or stored ID. var/list/restricted_roles = list() /// The species able to purchase this uplink item. - var/restricted_species = list() + var/list/restricted_species = list() /// The minimum amount of progression needed for this item to be added to uplinks. var/progression_minimum = 0 /// Whether this purchase is visible in the purchase log. diff --git a/tgui/packages/tgui/interfaces/Uplink/index.tsx b/tgui/packages/tgui/interfaces/Uplink/index.tsx index a8b2e7d477b2a..4949fc94ee7f5 100644 --- a/tgui/packages/tgui/interfaces/Uplink/index.tsx +++ b/tgui/packages/tgui/interfaces/Uplink/index.tsx @@ -137,13 +137,15 @@ export class Uplink extends Component<{}, UplinkState> { uplinkData.items = uplinkData.items.filter((value) => { if ( value.restricted_roles.length > 0 && - !value.restricted_roles.includes(uplinkRole) + !value.restricted_roles.includes(uplinkRole) && + !data.debug ) { return false; } if ( value.restricted_species.length > 0 && - !value.restricted_species.includes(uplinkSpecies) + !value.restricted_species.includes(uplinkSpecies) && + !data.debug ) { return false; } @@ -455,7 +457,7 @@ export class Uplink extends Component<{}, UplinkState> { } }} /> - {(shop_locked && ( + {(shop_locked && !data.debug && ( Date: Wed, 1 May 2024 10:01:24 -0500 Subject: [PATCH 06/87] Give more control over conveyor switch circuits (#82857) ## About The Pull Request This adds more signals to the conveyor switch circuits, specifically for going forward, stopping, or reversing. One-way conveyor switches will not get the reverse signal (as shown by the bottom right switch in the video). https://github.com/tgstation/tgstation/assets/16826524/66f287de-8413-41df-a69c-51b9ab8d7a02 ## Why It's Good For The Game This makes it far easier to make contraptions such as sorting mechanisms using circuits without the need to toggle multiple times just to get the conveyor direction into the state you need it in. ## Changelog :cl: qol: conveyor switches now have direction-specific input signals /:cl: --- code/modules/recycling/conveyor.dm | 34 ++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/code/modules/recycling/conveyor.dm b/code/modules/recycling/conveyor.dm index 8271298a0e638..36d73552159c2 100644 --- a/code/modules/recycling/conveyor.dm +++ b/code/modules/recycling/conveyor.dm @@ -590,12 +590,18 @@ GLOBAL_LIST_EMPTY(conveyors_by_id) desc = "Allows to control connected conveyor belts." circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL + /// Direction input ports. + var/datum/port/input/stop + var/datum/port/input/active + var/datum/port/input/reverse /// The current direction of the conveyor attached to the component. var/datum/port/output/direction /// The switch this conveyor switch component is attached to. var/obj/machinery/conveyor_switch/attached_switch /obj/item/circuit_component/conveyor_switch/populate_ports() + active = add_input_port("Activate", PORT_TYPE_SIGNAL, trigger = PROC_REF(activate)) + stop = add_input_port("Stop", PORT_TYPE_SIGNAL, trigger = PROC_REF(stop)) direction = add_output_port("Conveyor Direction", PORT_TYPE_NUMBER) /obj/item/circuit_component/conveyor_switch/get_ui_notices() @@ -606,6 +612,8 @@ GLOBAL_LIST_EMPTY(conveyors_by_id) . = ..() if(istype(shell, /obj/machinery/conveyor_switch)) attached_switch = shell + if(!attached_switch.oneway) + reverse = add_input_port("Reverse", PORT_TYPE_SIGNAL, trigger = PROC_REF(reverse)) /obj/item/circuit_component/conveyor_switch/unregister_usb_parent(atom/movable/shell) attached_switch = null @@ -617,15 +625,33 @@ GLOBAL_LIST_EMPTY(conveyors_by_id) INVOKE_ASYNC(src, PROC_REF(update_conveyors), port) +/obj/item/circuit_component/conveyor_switch/proc/on_switch_changed() + attached_switch.update_appearance() + attached_switch.update_linked_conveyors() + attached_switch.update_linked_switches() + direction.set_output(attached_switch.position) + +/obj/item/circuit_component/conveyor_switch/proc/activate() + SIGNAL_HANDLER + attached_switch.position = CONVEYOR_FORWARD + INVOKE_ASYNC(src, PROC_REF(on_switch_changed)) + +/obj/item/circuit_component/conveyor_switch/proc/stop() + SIGNAL_HANDLER + attached_switch.position = CONVEYOR_OFF + INVOKE_ASYNC(src, PROC_REF(on_switch_changed)) + +/obj/item/circuit_component/conveyor_switch/proc/reverse() + SIGNAL_HANDLER + attached_switch.position = CONVEYOR_BACKWARDS + INVOKE_ASYNC(src, PROC_REF(on_switch_changed)) + /obj/item/circuit_component/conveyor_switch/proc/update_conveyors(datum/port/input/port) if(!attached_switch) return attached_switch.update_position() - attached_switch.update_appearance() - attached_switch.update_linked_conveyors() - attached_switch.update_linked_switches() - direction.set_output(attached_switch.position) + INVOKE_ASYNC(src, PROC_REF(on_switch_changed)) #undef CONVEYOR_BACKWARDS #undef CONVEYOR_OFF From ee40f4f1f02c09dff9b7d60bceb30a9c0e9f4afe Mon Sep 17 00:00:00 2001 From: orange man <61334995+comfyorange@users.noreply.github.com> Date: Thu, 2 May 2024 03:12:55 +1200 Subject: [PATCH 07/87] Automatic changelog for PR #82967 [ci skip] --- html/changelogs/AutoChangeLog-pr-82967.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-82967.yml diff --git a/html/changelogs/AutoChangeLog-pr-82967.yml b/html/changelogs/AutoChangeLog-pr-82967.yml new file mode 100644 index 0000000000000..c068527e6cb51 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-82967.yml @@ -0,0 +1,4 @@ +author: "JohnFulpWillard" +delete-after: True +changes: + - admin: "Debug uplinks now shows all categories and won't lock upon buying items like his grace and the syndicate balloon." \ No newline at end of file From 0108294084d1efe0b9f132d5f7a6e35b4af73c47 Mon Sep 17 00:00:00 2001 From: orange man <61334995+comfyorange@users.noreply.github.com> Date: Thu, 2 May 2024 03:13:12 +1200 Subject: [PATCH 08/87] Automatic changelog for PR #82857 [ci skip] --- html/changelogs/AutoChangeLog-pr-82857.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-82857.yml diff --git a/html/changelogs/AutoChangeLog-pr-82857.yml b/html/changelogs/AutoChangeLog-pr-82857.yml new file mode 100644 index 0000000000000..8ea50a3077d60 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-82857.yml @@ -0,0 +1,4 @@ +author: "Echriser" +delete-after: True +changes: + - qol: "conveyor switches now have direction-specific input signals" \ No newline at end of file From 575c8f53a25bfcacc9f1c9af1faeb2a5f98a90bf Mon Sep 17 00:00:00 2001 From: John Willard <53777086+JohnFulpWillard@users.noreply.github.com> Date: Wed, 1 May 2024 15:39:59 +0000 Subject: [PATCH 09/87] Fixes cyborg tracking not always showing up (#82975) ## About The Pull Request Silicons moving with their builtincamera doesnt automatically update the camera net, so for some time after borgs move, they wont show up to the AI to track. It's a minor but annoying issue that this fixes. ## Why It's Good For The Game Fixes a minor issue with the new ai tracking stuff. ## Changelog :cl: fix: AIs can now track Silicon as long as their built-in camera is online. /:cl: --- code/modules/mob/living/living.dm | 4 ++-- code/modules/mob/living/silicon/silicon.dm | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index d77d7409561ef..5f5889ae0a3b4 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -1290,9 +1290,9 @@ return FALSE if(invisibility || alpha <= 50)//cloaked return FALSE - if(!isturf(src.loc)) //The reason why we don't just use get_turf is because they could be in a closet, disposals, or a vehicle. + if(!isturf(loc)) //The reason why we don't just use get_turf is because they could be in a closet, disposals, or a vehicle. return FALSE - var/turf/T = src.loc + var/turf/T = loc if(is_centcom_level(T.z)) //dont detect mobs on centcom return FALSE if(is_away_level(T.z)) diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm index 5c47c4e5bb829..95e2e6ed13379 100644 --- a/code/modules/mob/living/silicon/silicon.dm +++ b/code/modules/mob/living/silicon/silicon.dm @@ -480,3 +480,11 @@ stack_trace("Silicon [src] ( [type] ) was somehow missing their integrated tablet. Please make a bug report.") create_modularInterface() modularInterface.imprint_id(name = newname) + +/mob/living/silicon/can_track(mob/living/user) + //if their camera is online, it's safe to assume they are in cameranets + //since it takes a while for camera vis to update, this lets us bypass that so AIs can always see their borgs, + //without making cameras constantly update every time a borg moves. + if(builtInCamera && builtInCamera.can_use()) + return TRUE + return ..() From 6fc7681b181fa3a2315ecb2e17ba3610b3eaebfc Mon Sep 17 00:00:00 2001 From: orange man <61334995+comfyorange@users.noreply.github.com> Date: Thu, 2 May 2024 03:46:59 +1200 Subject: [PATCH 10/87] Automatic changelog for PR #82975 [ci skip] --- html/changelogs/AutoChangeLog-pr-82975.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-82975.yml diff --git a/html/changelogs/AutoChangeLog-pr-82975.yml b/html/changelogs/AutoChangeLog-pr-82975.yml new file mode 100644 index 0000000000000..d563b5306d215 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-82975.yml @@ -0,0 +1,4 @@ +author: "JohnFulpWillard" +delete-after: True +changes: + - bugfix: "AIs can now track Silicon as long as their built-in camera is online." \ No newline at end of file From 4731db993357b218079295af1ae9471ed4641d1a Mon Sep 17 00:00:00 2001 From: Pickle-Coding <58013024+Pickle-Coding@users.noreply.github.com> Date: Wed, 1 May 2024 18:41:12 +0100 Subject: [PATCH 11/87] [NO GBP]Fixes hypercharged slime core cells and circuit gun cells starting with an insignificant amount of charge. (#82977) Scales the hypercharged slime core and wiremod gun cell maximum charge and charge rate by the STANDARD_CELL_CHARGE and STANDARD_CELL_RATE defines. Fixes their scale. Closes #82907 ## Changelog :cl: fix: Fixes hypercharged slime core cells and circuit guns having 1,000 times less energy than intended. /:cl: --- code/modules/research/xenobiology/crossbreeding/_misc.dm | 4 ++-- code/modules/wiremod/shell/gun.dm | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/code/modules/research/xenobiology/crossbreeding/_misc.dm b/code/modules/research/xenobiology/crossbreeding/_misc.dm index 7b85a878ec39b..0e6a37ce3d0ba 100644 --- a/code/modules/research/xenobiology/crossbreeding/_misc.dm +++ b/code/modules/research/xenobiology/crossbreeding/_misc.dm @@ -93,8 +93,8 @@ Slimecrossing Items icon_state = "yellow slime extract" rating = 7 custom_materials = null - maxcharge = 50000 - chargerate = 2500 + maxcharge = 50 * STANDARD_CELL_CHARGE + chargerate = 2.5 * STANDARD_CELL_RATE charge_light_type = null connector_type = "slimecore" diff --git a/code/modules/wiremod/shell/gun.dm b/code/modules/wiremod/shell/gun.dm index 9f196e6c1fcce..283815fb3346b 100644 --- a/code/modules/wiremod/shell/gun.dm +++ b/code/modules/wiremod/shell/gun.dm @@ -30,7 +30,7 @@ range = 7 /obj/item/stock_parts/cell/emproof/wiremod_gun - maxcharge = 100 + maxcharge = 0.1 * STANDARD_CELL_CHARGE /obj/item/gun/energy/wiremod_gun/Initialize(mapload) . = ..() From fd7701ccdf0597bae36f35d68b01846074b77eaf Mon Sep 17 00:00:00 2001 From: orange man <61334995+comfyorange@users.noreply.github.com> Date: Thu, 2 May 2024 05:41:33 +1200 Subject: [PATCH 12/87] Automatic changelog for PR #82977 [ci skip] --- html/changelogs/AutoChangeLog-pr-82977.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-82977.yml diff --git a/html/changelogs/AutoChangeLog-pr-82977.yml b/html/changelogs/AutoChangeLog-pr-82977.yml new file mode 100644 index 0000000000000..3bdf3f73bd6a1 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-82977.yml @@ -0,0 +1,4 @@ +author: "Pickle-Coding" +delete-after: True +changes: + - bugfix: "Fixes hypercharged slime core cells and circuit guns having 1,000 times less energy than intended." \ No newline at end of file From a9dfdd84d09a263afe1ba88ac8d9863011fe01d2 Mon Sep 17 00:00:00 2001 From: John Willard <53777086+JohnFulpWillard@users.noreply.github.com> Date: Wed, 1 May 2024 17:46:05 +0000 Subject: [PATCH 13/87] Fixes a way to get a very long gold number (#82976) ## About The Pull Request Fixes this ![image](https://github.com/tgstation/tgstation/assets/53777086/091991e4-6470-4ffb-a9b7-a2899adc594d) Which occurs when you spam flee, since it unrestrictedly halves your gold. ## Why It's Good For The Game another arcade issue gone. ## Changelog :cl: fix: Constantly fleeing in Battle Arcade will no longer give you a very large amount of decimals due to halving your gold every time. /:cl: --- code/game/machinery/computer/arcade/battle.dm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/code/game/machinery/computer/arcade/battle.dm b/code/game/machinery/computer/arcade/battle.dm index 63d2808c773e0..b97dac9e15dad 100644 --- a/code/game/machinery/computer/arcade/battle.dm +++ b/code/game/machinery/computer/arcade/battle.dm @@ -531,7 +531,8 @@ return player_turn = TRUE ui_panel = UI_PANEL_WORLD_MAP - player_gold /= 2 + if(player_gold) + player_gold = max(round(player_gold /= 2, 1), 0) return TRUE //they pressed something but it wasn't in the menu, we'll be nice and give them back their turn anyway. player_turn = TRUE From 5aae4a3ddc9a8a2bd163bbada0f79bfe4834c7f2 Mon Sep 17 00:00:00 2001 From: FlufflesTheDog Date: Wed, 1 May 2024 10:47:07 -0700 Subject: [PATCH 14/87] Wheelchair qol (#82968) ## About The Pull Request Lets you pick up wheelchairs while resting, and lets you place wheelchairs on adjacent tiles, much like medical roller beds. ## Why It's Good For The Game Makes life just a little bit easier. Barely being able to move is already enough of a downside. I think allowing the little bit of extra independence being able to pick up your own chair (without just finding or making a basic chair first) offers is justifiable. And being able to place the chair a tile away just saves needing to pixel hunt or shuffle around to buckle yourself. ## Changelog :cl: Fluffles qol: you can pick up wheelchairs while on the ground qol: you can place wheelchairs a tile away from you, like roller beds /:cl: --- code/game/objects/items/bodybag.dm | 11 +++++------ code/game/objects/structures/beds_chairs/bed.dm | 11 +++++------ code/modules/vehicles/wheelchair.dm | 8 +++++++- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/code/game/objects/items/bodybag.dm b/code/game/objects/items/bodybag.dm index 7e67b23c3b716..19d3d273337c4 100644 --- a/code/game/objects/items/bodybag.dm +++ b/code/game/objects/items/bodybag.dm @@ -14,12 +14,11 @@ else deploy_bodybag(user, get_turf(src)) -/obj/item/bodybag/afterattack(atom/target, mob/user, proximity) - . = ..() - if(proximity) - if(isopenturf(target)) - deploy_bodybag(user, target) - +/obj/item/bodybag/interact_with_atom(atom/interacting_with, mob/living/user, flags) + if(isopenturf(interacting_with)) + deploy_bodybag(user, interacting_with) + return ITEM_INTERACT_SUCCESS + return NONE /obj/item/bodybag/attempt_pickup(mob/user) // can't pick ourselves up if we are inside of the bodybag, else very weird things may happen if(contains(user)) diff --git a/code/game/objects/structures/beds_chairs/bed.dm b/code/game/objects/structures/beds_chairs/bed.dm index 0e3845d2efa7f..e037043cc91a5 100644 --- a/code/game/objects/structures/beds_chairs/bed.dm +++ b/code/game/objects/structures/beds_chairs/bed.dm @@ -218,13 +218,12 @@ /obj/item/emergency_bed/attack_self(mob/user) deploy_bed(user, user.loc) -/obj/item/emergency_bed/afterattack(obj/target, mob/user, proximity) - . = ..() - if(!proximity) - return +/obj/item/emergency_bed/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) + if(isopenturf(interacting_with)) + deploy_bed(user, interacting_with) + return ITEM_INTERACT_SUCCESS + return NONE - if(isopenturf(target)) - deploy_bed(user, target) /obj/item/emergency_bed/proc/deploy_bed(mob/user, atom/location) var/obj/structure/bed/medical/emergency/deployed = new /obj/structure/bed/medical/emergency(location) diff --git a/code/modules/vehicles/wheelchair.dm b/code/modules/vehicles/wheelchair.dm index 92fcb995f7643..d40b57276c0e4 100644 --- a/code/modules/vehicles/wheelchair.dm +++ b/code/modules/vehicles/wheelchair.dm @@ -126,7 +126,7 @@ . = ..() if(over_object != usr || !Adjacent(usr) || !foldabletype) return FALSE - if(!ishuman(usr) || !usr.can_perform_action(src)) + if(!ishuman(usr) || !usr.can_perform_action(src, ALLOW_RESTING)) return FALSE if(has_buckled_mobs()) return FALSE @@ -138,6 +138,12 @@ /obj/item/wheelchair/attack_self(mob/user) //Deploys wheelchair on in-hand use deploy_wheelchair(user, user.loc) +/obj/item/wheelchair/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) + if(isopenturf(interacting_with)) + deploy_wheelchair(user, interacting_with) + return ITEM_INTERACT_SUCCESS + return NONE + /obj/item/wheelchair/proc/deploy_wheelchair(mob/user, atom/location) var/obj/vehicle/ridden/wheelchair/wheelchair_unfolded = new unfolded_type(location) wheelchair_unfolded.add_fingerprint(user) From 24bc322fa6b7d16999af642b939e642de5e602c7 Mon Sep 17 00:00:00 2001 From: Watermelon914 <37270891+Watermelon914@users.noreply.github.com> Date: Wed, 1 May 2024 17:53:38 +0000 Subject: [PATCH 15/87] Fixed the ipintel subsystem not initializing (#82936) ## About The Pull Request Subsystem had the SS_INIT_NO_NEED inside of its subsystem flags. This is not a flag and has a value of 3, which includes the SS_NO_INIT and SS_NO_FIRE flags, which stopped it from initializing. The subsystem has things it needs to do in init, so I've replaced this flag with SS_OK_TO_FAIL_INIT so that it can pass unit tests, but still be okay to fail. ## Why It's Good For The Game Fixes the ipintel subsystem not working. ## Changelog :cl: fix: Fixed the ipintel subsystem not working. /:cl: --------- Co-authored-by: Watermelon914 <3052169-Watermelon914@users.noreply.gitlab.com> --- code/controllers/subsystem/ipintel.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/controllers/subsystem/ipintel.dm b/code/controllers/subsystem/ipintel.dm index 3f1c738b09c0e..ba4cda13a43d9 100644 --- a/code/controllers/subsystem/ipintel.dm +++ b/code/controllers/subsystem/ipintel.dm @@ -1,7 +1,7 @@ SUBSYSTEM_DEF(ipintel) name = "XKeyScore" init_order = INIT_ORDER_XKEYSCORE - flags = SS_INIT_NO_NEED|SS_NO_FIRE + flags = SS_OK_TO_FAIL_INIT|SS_NO_FIRE /// The threshold for probability to be considered a VPN and/or bad IP var/probability_threshold /// The email used in conjuction with https://check.getipintel.net/check.php From 7d578e979ec24576278f306459863f8b99171849 Mon Sep 17 00:00:00 2001 From: orange man <61334995+comfyorange@users.noreply.github.com> Date: Thu, 2 May 2024 05:54:46 +1200 Subject: [PATCH 16/87] Automatic changelog for PR #82976 [ci skip] --- html/changelogs/AutoChangeLog-pr-82976.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-82976.yml diff --git a/html/changelogs/AutoChangeLog-pr-82976.yml b/html/changelogs/AutoChangeLog-pr-82976.yml new file mode 100644 index 0000000000000..3005ebe9b06ca --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-82976.yml @@ -0,0 +1,4 @@ +author: "JohnFulpWillard" +delete-after: True +changes: + - bugfix: "Constantly fleeing in Battle Arcade will no longer give you a very large amount of decimals due to halving your gold every time." \ No newline at end of file From 362a84946bac49d78b463fa3de228c9e663fab25 Mon Sep 17 00:00:00 2001 From: orange man <61334995+comfyorange@users.noreply.github.com> Date: Thu, 2 May 2024 05:54:49 +1200 Subject: [PATCH 17/87] Automatic changelog for PR #82968 [ci skip] --- html/changelogs/AutoChangeLog-pr-82968.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-82968.yml diff --git a/html/changelogs/AutoChangeLog-pr-82968.yml b/html/changelogs/AutoChangeLog-pr-82968.yml new file mode 100644 index 0000000000000..986653310c815 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-82968.yml @@ -0,0 +1,5 @@ +author: "Fluffles" +delete-after: True +changes: + - qol: "you can pick up wheelchairs while on the ground" + - qol: "you can place wheelchairs a tile away from you, like roller beds" \ No newline at end of file From 8d69f6eb683da053c068c4bc6110aeb5640b8a8b Mon Sep 17 00:00:00 2001 From: orange man <61334995+comfyorange@users.noreply.github.com> Date: Thu, 2 May 2024 05:58:37 +1200 Subject: [PATCH 18/87] Automatic changelog for PR #82936 [ci skip] --- html/changelogs/AutoChangeLog-pr-82936.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-82936.yml diff --git a/html/changelogs/AutoChangeLog-pr-82936.yml b/html/changelogs/AutoChangeLog-pr-82936.yml new file mode 100644 index 0000000000000..f8ea2d4556cd1 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-82936.yml @@ -0,0 +1,4 @@ +author: "Watermelon914" +delete-after: True +changes: + - bugfix: "Fixed the ipintel subsystem not working." \ No newline at end of file From 1ccdaa84b87f58966289ed672c58ccc0770cc927 Mon Sep 17 00:00:00 2001 From: kawoppi <94711066+kawoppi@users.noreply.github.com> Date: Thu, 2 May 2024 00:26:40 +0200 Subject: [PATCH 19/87] civilian bounty control terminal displays bounty description and payout of new bounty options (#82987) ## About The Pull Request The green buttons for selecting one of three bounties now also display the description and payout. https://github.com/tgstation/tgstation/assets/94711066/53d7fb02-28be-4138-b3d8-9f7a4dd6a356 Fixes #81867 Sorta brings it back to how it used to look, I think? The payout was still in the code, just not being displayed. But I looked through the file history and could find NO EVIDENCE of the bounty console displaying the description in the past so I guess I hallucinated that. ## Why It's Good For The Game It's nice to be able to see the payout before you're locked into a bounty for 5 minutes. Also more chances to read the flavortext and it makes the buttons feel less barren. With some bounties it also gives a better idea of how to even get the thing before committing to it. ## Changelog :cl: fix: the civilian bounty control terminal displays the payout of new bounty options again qol: the civilian bounty control terminal displays the description of new bounty options /:cl: --- code/game/machinery/civilian_bounties.dm | 3 + .../tgui/interfaces/CivCargoHoldTerminal.jsx | 75 ++++++++++++------- 2 files changed, 53 insertions(+), 25 deletions(-) diff --git a/code/game/machinery/civilian_bounties.dm b/code/game/machinery/civilian_bounties.dm index 1cb7ee1477ef2..792c1ba22eb31 100644 --- a/code/game/machinery/civilian_bounties.dm +++ b/code/game/machinery/civilian_bounties.dm @@ -184,6 +184,9 @@ data["id_bounty_names"] = list(inserted_scan_id.registered_account.bounties[1].name, inserted_scan_id.registered_account.bounties[2].name, inserted_scan_id.registered_account.bounties[3].name) + data["id_bounty_infos"] = list(inserted_scan_id.registered_account.bounties[1].description, + inserted_scan_id.registered_account.bounties[2].description, + inserted_scan_id.registered_account.bounties[3].description) data["id_bounty_values"] = list(inserted_scan_id.registered_account.bounties[1].reward * (CIV_BOUNTY_SPLIT/100), inserted_scan_id.registered_account.bounties[2].reward * (CIV_BOUNTY_SPLIT/100), inserted_scan_id.registered_account.bounties[3].reward * (CIV_BOUNTY_SPLIT/100)) diff --git a/tgui/packages/tgui/interfaces/CivCargoHoldTerminal.jsx b/tgui/packages/tgui/interfaces/CivCargoHoldTerminal.jsx index 386f3c5721a0c..438030e39472c 100644 --- a/tgui/packages/tgui/interfaces/CivCargoHoldTerminal.jsx +++ b/tgui/packages/tgui/interfaces/CivCargoHoldTerminal.jsx @@ -96,41 +96,66 @@ const BountyTextBox = (props) => { const BountyPickBox = (props) => { const { act, data } = useBackend(); - const { id_bounty_names, id_bounty_values } = data; + const { id_bounty_names, id_bounty_infos, id_bounty_values } = data; return (
- + - + - +
); }; + +const BountyPickButton = (props) => { + return ( + + ); +}; From 64506c5a3cc3540ccb7bffbefb301ab68bb8bd84 Mon Sep 17 00:00:00 2001 From: nikothedude <59709059+nikothedude@users.noreply.github.com> Date: Wed, 1 May 2024 18:26:50 -0400 Subject: [PATCH 20/87] Fixes jousting bypassing pacifism + Some signal doc (#82986) ## About The Pull Request Closes https://github.com/tgstation/tgstation/issues/82983 Title. Jousting now uses a shiny new post-attack signal, only sent if the attack is actually executed. ## Why It's Good For The Game bgus bad ## Changelog :cl: nikothedude fix: Jousting no longer bypasses pacifism /:cl: --- code/__DEFINES/dcs/signals/signals_object.dm | 3 +++ code/_onclick/item_attack.dm | 2 ++ code/datums/components/jousting.dm | 6 +++--- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm index 43e3a09a0287d..f628df0e964b4 100644 --- a/code/__DEFINES/dcs/signals/signals_object.dm +++ b/code/__DEFINES/dcs/signals/signals_object.dm @@ -448,7 +448,10 @@ /// Prevents click from happening. #define COMPONENT_CANCEL_EQUIPMENT_CLICK (1<<0) +///from base of /obj/item/attack(): (mob/living, mob/living, params) #define COMSIG_ITEM_ATTACK "item_attack" +///from base of /obj/item/attack(): (mob/living, mob/living, params) +#define COMSIG_ITEM_POST_ATTACK "item_post_attack" // called only if the attack was executed ///from base of obj/item/attack_self(): (/mob) #define COMSIG_ITEM_ATTACK_SELF "item_attack_self" //from base of obj/item/attack_self_secondary(): (/mob) diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm index be4a481e6773c..9fa8fbf7ff1a2 100644 --- a/code/_onclick/item_attack.dm +++ b/code/_onclick/item_attack.dm @@ -236,6 +236,8 @@ user.do_attack_animation(target_mob) target_mob.attacked_by(src, user) + SEND_SIGNAL(src, COMSIG_ITEM_POST_ATTACK, target_mob, user, params) + log_combat(user, target_mob, "attacked", src.name, "(COMBAT MODE: [uppertext(user.combat_mode)]) (DAMTYPE: [uppertext(damtype)])") add_fingerprint(user) diff --git a/code/datums/components/jousting.dm b/code/datums/components/jousting.dm index 56b65c6f758b5..9c3dab3c8fbdd 100644 --- a/code/datums/components/jousting.dm +++ b/code/datums/components/jousting.dm @@ -44,7 +44,7 @@ RegisterSignal(parent, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(on_equip)) RegisterSignal(parent, COMSIG_ITEM_DROPPED, PROC_REF(on_drop)) - RegisterSignal(parent, COMSIG_ITEM_ATTACK, PROC_REF(on_attack)) + RegisterSignal(parent, COMSIG_ITEM_POST_ATTACK, PROC_REF(on_successful_attack)) RegisterSignal(parent, COMSIG_TRANSFORMING_ON_TRANSFORM, PROC_REF(on_transform)) /datum/component/jousting/UnregisterFromParent() @@ -53,7 +53,7 @@ COMSIG_ATOM_EXAMINE, COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED, - COMSIG_ITEM_ATTACK, + COMSIG_ITEM_POST_ATTACK, COMSIG_TRANSFORMING_ON_TRANSFORM, )) @@ -94,7 +94,7 @@ * We deduct the minimum tile charge from the current tile charge to get what will actually be buffed * So your charge will only get benefits from each extra tile after the minimum (and before the maximum). */ -/datum/component/jousting/proc/on_attack(datum/source, mob/living/target, mob/user) +/datum/component/jousting/proc/on_successful_attack(datum/source, mob/living/target, mob/user) SIGNAL_HANDLER if(user != current_holder || !user.buckled) return From ed5e14c0eecd92522ba13c5e81802fa031f40ce9 Mon Sep 17 00:00:00 2001 From: orange man <61334995+comfyorange@users.noreply.github.com> Date: Thu, 2 May 2024 10:27:03 +1200 Subject: [PATCH 21/87] Automatic changelog for PR #82987 [ci skip] --- html/changelogs/AutoChangeLog-pr-82987.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-82987.yml diff --git a/html/changelogs/AutoChangeLog-pr-82987.yml b/html/changelogs/AutoChangeLog-pr-82987.yml new file mode 100644 index 0000000000000..39e5d297ce3d6 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-82987.yml @@ -0,0 +1,5 @@ +author: "kawoppi" +delete-after: True +changes: + - bugfix: "the civilian bounty control terminal displays the payout of new bounty options again" + - qol: "the civilian bounty control terminal displays the description of new bounty options" \ No newline at end of file From 906f4cdf49b025f149e66be339e424d1aa8df255 Mon Sep 17 00:00:00 2001 From: orange man <61334995+comfyorange@users.noreply.github.com> Date: Thu, 2 May 2024 10:27:11 +1200 Subject: [PATCH 22/87] Automatic changelog for PR #82986 [ci skip] --- html/changelogs/AutoChangeLog-pr-82986.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-82986.yml diff --git a/html/changelogs/AutoChangeLog-pr-82986.yml b/html/changelogs/AutoChangeLog-pr-82986.yml new file mode 100644 index 0000000000000..eecba8d4bafc0 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-82986.yml @@ -0,0 +1,4 @@ +author: "nikothedude" +delete-after: True +changes: + - bugfix: "Jousting no longer bypasses pacifism" \ No newline at end of file From d1cadb24f9a648fb81261b75f81ec86d1952b39d Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Wed, 1 May 2024 17:55:01 -0500 Subject: [PATCH 23/87] Vote clean up and admin additions (#82981) ## About The Pull Request - Fixes `vote_delay` not being a thing. I broke this two years ago but there's no bug report associated. - Admins can now reset the vote delay (to let people vote again instantly) - Admins can now end the current vote immediately (rather than cancelling) - Custom multi and custom single combined into one vote ## Why It's Good For The Game Makes voting a bit easier to use, both for admins and for coders adding new votes. ![image](https://github.com/tgstation/tgstation/assets/51863163/40b8857c-76b7-4a58-82bc-1b82640d550a) ## Changelog :cl: Melbert admin: Custom Single and Custom Multi votes are now combined into one vote admin: Admins can now end votes instantly, rather than cancelling them admin: Admins can now reset the vote cooldown fix: Vote cooldown actually applies now /:cl: --- code/__DEFINES/subsystems.dm | 3 + .../configuration/entries/general.dm | 4 +- code/controllers/subsystem/vote.dm | 119 ++++++++++--- code/datums/votes/_vote_datum.dm | 49 +++--- code/datums/votes/custom_vote.dm | 57 +++++-- code/datums/votes/map_vote.dm | 38 ++--- code/datums/votes/restart_vote.dm | 35 ++-- code/datums/votes/rock_the_vote.dm | 42 ++--- tgui/packages/tgui/interfaces/VotePanel.tsx | 159 ++++++++++++------ 9 files changed, 307 insertions(+), 199 deletions(-) diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm index 031244d3d76c1..8d460c3aecb6f 100644 --- a/code/__DEFINES/subsystems.dm +++ b/code/__DEFINES/subsystems.dm @@ -346,3 +346,6 @@ #define VOTE_WINNER_METHOD_WEIGHTED_RANDOM "Weighted Random" /// There is no winner for this vote. #define VOTE_WINNER_METHOD_NONE "None" + +/// Returned by [/datum/vote/proc/can_be_initiated] to denote the vote is valid and can be initiated. +#define VOTE_AVAILABLE "Vote Available" diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm index 5292e47ecd654..98a386b7c8b82 100644 --- a/code/controllers/configuration/entries/general.dm +++ b/code/controllers/configuration/entries/general.dm @@ -184,13 +184,13 @@ /// minimum time between voting sessions (deciseconds, 10 minute default) /datum/config_entry/number/vote_delay - default = 6000 + default = 10 MINUTES integer = FALSE min_val = 0 /// length of voting period (deciseconds, default 1 minute) /datum/config_entry/number/vote_period - default = 600 + default = 1 MINUTES integer = FALSE min_val = 0 diff --git a/code/controllers/subsystem/vote.dm b/code/controllers/subsystem/vote.dm index 410db36988dd8..b7c26acf375ee 100644 --- a/code/controllers/subsystem/vote.dm +++ b/code/controllers/subsystem/vote.dm @@ -18,6 +18,8 @@ SUBSYSTEM_DEF(vote) var/list/voted = list() /// A list of all ckeys currently voting for the current vote. var/list/voting = list() + /// World.time we started our last vote + var/last_vote_time = -INFINITY /datum/controller/subsystem/vote/Initialize() for(var/vote_type in subtypesof(/datum/vote)) @@ -30,16 +32,20 @@ SUBSYSTEM_DEF(vote) return SS_INIT_SUCCESS - // Called by master_controller /datum/controller/subsystem/vote/fire() if(!current_vote) return current_vote.time_remaining = round((current_vote.started_time + CONFIG_GET(number/vote_period) - world.time) / 10) if(current_vote.time_remaining < 0) - process_vote_result() - SStgui.close_uis(src) - reset() + end_vote() + +/// Ends the current vote. +/datum/controller/subsystem/vote/proc/end_vote() + ASSERT(current_vote) + process_vote_result() + SStgui.close_uis(src) + reset() /// Resets all of our vars after votes conclude / are cancelled. /datum/controller/subsystem/vote/proc/reset() @@ -168,24 +174,10 @@ SUBSYSTEM_DEF(vote) * * vote_type - The type of vote to initiate. Can be a [/datum/vote] typepath, a [/datum/vote] instance, or the name of a vote datum. * * vote_initiator_name - The ckey (if player initiated) or name that initiated a vote. Ex: "UristMcAdmin", "the server" * * vote_initiator - If a person / mob initiated the vote, this is the mob that did it - * * forced - Whether we're forcing the vote to go through regardless of existing votes or other circumstances. Note: If the vote is admin created, forced becomes true regardless. + * * forced - Whether we're forcing the vote to go through regardless of existing votes or other circumstances. */ /datum/controller/subsystem/vote/proc/initiate_vote(vote_type, vote_initiator_name, mob/vote_initiator, forced = FALSE) - - // Even if it's forced we can't vote before we're set up - if(!MC_RUNNING(init_stage)) - if(vote_initiator) - to_chat(vote_initiator, span_warning("You cannot start vote now, the server is not done initializing.")) - return FALSE - - // Check if we have unlimited voting power. - // Admin started (or forced) voted will go through even if there's an ongoing vote, - // if voting is on cooldown, or regardless if a vote is config disabled (in some cases) - var/unlimited_vote_power = forced || !!GLOB.admin_datums[vote_initiator?.ckey] - - if(current_vote && !unlimited_vote_power) - if(vote_initiator) - to_chat(vote_initiator, span_warning("There is already a vote in progress! Please wait for it to finish.")) + if(!can_vote_start(vote_initiator, forced)) return FALSE // Get our actual datum @@ -212,7 +204,7 @@ SUBSYSTEM_DEF(vote) return FALSE // Vote can't be initiated in our circumstances? No vote - if(!to_vote.can_be_initiated(vote_initiator, unlimited_vote_power)) + if(to_vote.can_be_initiated(forced) != VOTE_AVAILABLE) return FALSE // Okay, we're ready to actually create a vote - @@ -223,8 +215,12 @@ SUBSYSTEM_DEF(vote) if(!to_vote.create_vote(vote_initiator)) return FALSE + if(!vote_initiator_name && vote_initiator) + vote_initiator_name = vote_initiator.key + // Okay, the vote's happening now, for real. Set it up. current_vote = to_vote + last_vote_time = world.time var/duration = CONFIG_GET(number/vote_period) var/to_display = current_vote.initiate_vote(vote_initiator_name, duration) @@ -248,6 +244,36 @@ SUBSYSTEM_DEF(vote) return TRUE +/** + * Checks if we can start a vote. + * + * * vote_initiator - The mob that initiated the vote. + * * forced - Whether we're forcing the vote to go through regardless of existing votes or other circumstances. + * + * Returns TRUE if we can start a vote, FALSE if we can't. + */ +/datum/controller/subsystem/vote/proc/can_vote_start(mob/vote_initiator, forced) + // Even if it's forced we can't vote before we're set up + if(!MC_RUNNING(init_stage)) + if(vote_initiator) + to_chat(vote_initiator, span_warning("You cannot start a vote now, the server is not done initializing.")) + return FALSE + + if(forced) + return TRUE + + var/next_allowed_time = last_vote_time + CONFIG_GET(number/vote_delay) + if(next_allowed_time > world.time) + if(vote_initiator) + to_chat(vote_initiator, span_warning("A vote was initiated recently. You must wait [DisplayTimeText(next_allowed_time - world.time)] before a new vote can be started!")) + return FALSE + + if(current_vote) + if(vote_initiator) + to_chat(vote_initiator, span_warning("There is already a vote in progress! Please wait for it to finish.")) + return FALSE + + return TRUE /datum/controller/subsystem/vote/ui_state() return GLOB.always_state @@ -282,11 +308,12 @@ SUBSYSTEM_DEF(vote) if(!istype(vote)) continue + var/can_vote = vote.can_be_initiated(is_lower_admin) var/list/vote_data = list( "name" = vote_name, - "canBeInitiated" = vote.can_be_initiated(forced = is_lower_admin), + "canBeInitiated" = can_vote == VOTE_AVAILABLE, "config" = vote.is_config_enabled(), - "message" = vote.message, + "message" = can_vote == VOTE_AVAILABLE ? vote.default_message : can_vote, ) if(vote == current_vote) @@ -310,7 +337,13 @@ SUBSYSTEM_DEF(vote) all_vote_data += list(vote_data) data["possibleVotes"] = all_vote_data + data["LastVoteTime"] = last_vote_time - world.time + + return data +/datum/controller/subsystem/vote/ui_static_data(mob/user) + var/list/data = list() + data["VoteCD"] = CONFIG_GET(number/vote_delay) return data /datum/controller/subsystem/vote/ui_act(action, params) @@ -323,19 +356,37 @@ SUBSYSTEM_DEF(vote) switch(action) if("cancel") if(!voter.client?.holder) + message_admins("[key_name(voter)] tried to cancel the current vote while having no admin holder, \ + this is potentially a malicious exploit and worth noting.") return voter.log_message("cancelled a vote.", LOG_ADMIN) message_admins("[key_name_admin(voter)] has cancelled the current vote.") + SStgui.close_uis(src) reset() return TRUE + if("endNow") + if(!voter.client?.holder) + message_admins("[key_name(voter)] tried to end the current vote while having no admin holder, \ + this is potentially a malicious exploit and worth noting.") + return + + voter.log_message("ended the current vote early", LOG_ADMIN) + message_admins("[key_name_admin(voter)] has ended the current vote.") + end_vote() + return TRUE + if("toggleVote") var/datum/vote/selected = possible_votes[params["voteName"]] if(!istype(selected)) return + if(!check_rights_for(voter.client, R_ADMIN)) + message_admins("[key_name(voter)] tried to toggle vote availability while having improper rights, \ + this is potentially a malicious exploit and worth noting.") + return - return selected.toggle_votable(voter) + return selected.toggle_votable() if("callVote") var/datum/vote/selected = possible_votes[params["voteName"]] @@ -344,7 +395,12 @@ SUBSYSTEM_DEF(vote) // Whether the user actually can initiate this vote is checked in initiate_vote, // meaning you can't spoof initiate a vote you're not supposed to be able to - return initiate_vote(selected, voter.key, voter) + return initiate_vote( + vote_type = selected, + vote_initiator_name = voter.key, + vote_initiator = voter, + forced = !!GLOB.admin_datums[voter.ckey], + ) if("voteSingle") return submit_single_vote(voter, params["voteOption"]) @@ -352,6 +408,15 @@ SUBSYSTEM_DEF(vote) if("voteMulti") return submit_multi_vote(voter, params["voteOption"]) + if("resetCooldown") + if(!voter.client.holder) + message_admins("[key_name(voter)] tried to reset the vote cooldown while having no admin holder, \ + this is potentially a malicious exploit and worth noting.") + return + + last_vote_time = -INFINITY + return TRUE + /datum/controller/subsystem/vote/ui_close(mob/user) voting -= user.client?.ckey @@ -360,6 +425,10 @@ SUBSYSTEM_DEF(vote) set category = "OOC" set name = "Vote" + if(!SSvote.initialized) + to_chat(usr, span_notice("Voting is not set up yet!")) + return + SSvote.ui_interact(usr) /// Datum action given to mobs that allows players to vote on the current vote. diff --git a/code/datums/votes/_vote_datum.dm b/code/datums/votes/_vote_datum.dm index 3f821a7129ada..76833f73ff5b0 100644 --- a/code/datums/votes/_vote_datum.dm +++ b/code/datums/votes/_vote_datum.dm @@ -15,25 +15,25 @@ var/list/default_choices /// Does the name of this vote contain the word "vote"? var/contains_vote_in_name = FALSE - /// What message do we want to pass to the player-side vote panel as a tooltip? - var/message = "Click to initiate a vote." + /// What message do we show as the tooltip of this vote if the vote can be initiated? + var/default_message = "Click to initiate a vote." + /// The counting method we use for votes. + var/count_method = VOTE_COUNT_METHOD_SINGLE + /// The method for selecting a winner. + var/winner_method = VOTE_WINNER_METHOD_SIMPLE + /// Should we show details about the number of votes submitted for each option? + var/display_statistics = TRUE // Internal values used when tracking ongoing votes. // Don't mess with these, change the above values / override procs for subtypes. /// An assoc list of [all choices] to [number of votes in the current running vote]. - var/list/choices = list() + VAR_FINAL/list/choices = list() /// A assoc list of [ckey] to [what they voted for in the current running vote]. - var/list/choices_by_ckey = list() + VAR_FINAL/list/choices_by_ckey = list() /// The world time this vote was started. - var/started_time + VAR_FINAL/started_time = -1 /// The time remaining in this vote's run. - var/time_remaining - /// The counting method we use for votes. - var/count_method = VOTE_COUNT_METHOD_SINGLE - /// The method for selecting a winner. - var/winner_method = VOTE_WINNER_METHOD_SIMPLE - /// Should we show details about the number of votes submitted for each option? - var/display_statistics = TRUE + VAR_FINAL/time_remaining = -1 /** * Used to determine if this vote is a possible @@ -55,14 +55,13 @@ choices.Cut() choices_by_ckey.Cut() started_time = null - time_remaining = null + time_remaining = -1 /** * If this vote has a config associated, toggles it between enabled and disabled. - * Returns TRUE on a successful toggle, FALSE otherwise */ -/datum/vote/proc/toggle_votable(mob/toggler) - return FALSE +/datum/vote/proc/toggle_votable() + return /** * If this vote has a config associated, returns its value (True or False, usually). @@ -74,20 +73,18 @@ /** * Checks if the passed mob can initiate this vote. * - * Return TRUE if the mob can begin the vote, allowing anyone to actually vote on it. - * Return FALSE if the mob cannot initiate the vote. + * * forced - if being invoked by someone who is an admin + * + * Return VOTE_AVAILABLE if the mob can initiate the vote. + * Return a string with the reason why the mob can't initiate the vote. */ -/datum/vote/proc/can_be_initiated(mob/by_who, forced = FALSE) +/datum/vote/proc/can_be_initiated(forced = FALSE) SHOULD_CALL_PARENT(TRUE) - if(started_time) - var/next_allowed_time = (started_time + CONFIG_GET(number/vote_delay)) - if(next_allowed_time > world.time && !forced) - message = "A vote was initiated recently. You must wait [DisplayTimeText(next_allowed_time - world.time)] before a new vote can be started!" - return FALSE + if(!forced && !is_config_enabled()) + return "This vote is currently disabled by the server configuration." - message = initial(message) - return TRUE + return VOTE_AVAILABLE /** * Called prior to the vote being initiated. diff --git a/code/datums/votes/custom_vote.dm b/code/datums/votes/custom_vote.dm index d67eb0281c6c6..78eb6d3b876d8 100644 --- a/code/datums/votes/custom_vote.dm +++ b/code/datums/votes/custom_vote.dm @@ -1,14 +1,9 @@ /// The max amount of options someone can have in a custom vote. #define MAX_CUSTOM_VOTE_OPTIONS 10 -/datum/vote/custom_vote/single - name = "Custom Standard" - message = "Click here to start a custom vote (one selection per voter)" - -/datum/vote/custom_vote/multi - name = "Custom Multi" - message = "Click here to start a custom multi vote (multiple selections per voter)" - count_method = VOTE_COUNT_METHOD_MULTI +/datum/vote/custom_vote + name = "Custom" + default_message = "Click here to start a custom vote." // Custom votes ares always accessible. /datum/vote/custom_vote/is_accessible_vote() @@ -17,23 +12,45 @@ /datum/vote/custom_vote/reset() default_choices = null override_question = null + count_method = VOTE_COUNT_METHOD_SINGLE return ..() -/datum/vote/custom_vote/can_be_initiated(mob/by_who, forced = FALSE) +/datum/vote/custom_vote/can_be_initiated(forced) . = ..() - if(!.) - return FALSE + if(. != VOTE_AVAILABLE) + return . + if(forced) + return . // Custom votes can only be created if they're forced to be made. // (Either an admin makes it, or otherwise.) - return forced + return "Only admins can create custom votes." /datum/vote/custom_vote/create_vote(mob/vote_creator) + var/custom_count_method = tgui_input_list( + user = vote_creator, + message = "Single or multiple choice?", + title = "Choice Method", + items = list("Single", "Multiple"), + default = "Single", + ) + switch(custom_count_method) + if("Single") + count_method = VOTE_COUNT_METHOD_SINGLE + if("Multiple") + count_method = VOTE_COUNT_METHOD_MULTI + if(null) + return FALSE + else + stack_trace("Got '[custom_count_method]' in create_vote() for custom voting.") + to_chat(vote_creator, span_boldwarning("Unknown choice method. Contact a coder.")) + return FALSE + var/custom_win_method = tgui_input_list( - vote_creator, - "How should the vote winner be determined?", - "Winner Method", - list("Simple", "Weighted Random", "No Winner"), + user = vote_creator, + message = "How should the vote winner be determined?", + title = "Winner Method", + items = list("Simple", "Weighted Random", "No Winner"), default = "Simple", ) switch(custom_win_method) @@ -43,7 +60,10 @@ winner_method = VOTE_WINNER_METHOD_WEIGHTED_RANDOM if("No Winner") winner_method = VOTE_WINNER_METHOD_NONE + if(null) + return FALSE else + stack_trace("Got '[custom_win_method]' in create_vote() for custom voting.") to_chat(vote_creator, span_boldwarning("Unknown winner method. Contact a coder.")) return FALSE @@ -54,7 +74,7 @@ list("Yes", "No"), ) - if(display_stats == null) + if(isnull(display_stats)) return FALSE display_statistics = display_stats == "Yes" @@ -74,6 +94,9 @@ if(!length(default_choices)) return FALSE + // Sanity for all the tgui input stalling we are doing + if(isnull(vote_creator.client?.holder)) + return FALSE return ..() diff --git a/code/datums/votes/map_vote.dm b/code/datums/votes/map_vote.dm index a07d87846f050..9b149e812795d 100644 --- a/code/datums/votes/map_vote.dm +++ b/code/datums/votes/map_vote.dm @@ -1,6 +1,6 @@ /datum/vote/map_vote name = "Map" - message = "Vote for next round's map!" + default_message = "Vote for next round's map!" count_method = VOTE_COUNT_METHOD_SINGLE winner_method = VOTE_WINNER_METHOD_WEIGHTED_RANDOM display_statistics = FALSE @@ -30,44 +30,26 @@ SSmapping.map_voted = TRUE // voted by not voting, very sad. return FALSE -/datum/vote/map_vote/toggle_votable(mob/toggler) - if(!toggler) - CRASH("[type] wasn't passed a \"toggler\" mob to toggle_votable.") - if(!check_rights_for(toggler.client, R_ADMIN)) - return FALSE - +/datum/vote/map_vote/toggle_votable() CONFIG_SET(flag/allow_vote_map, !CONFIG_GET(flag/allow_vote_map)) - return TRUE /datum/vote/map_vote/is_config_enabled() return CONFIG_GET(flag/allow_vote_map) -/datum/vote/map_vote/can_be_initiated(mob/by_who, forced = FALSE) +/datum/vote/map_vote/can_be_initiated(forced) . = ..() - if(!.) - return FALSE - + if(. != VOTE_AVAILABLE) + return . if(forced) - return TRUE - + return VOTE_AVAILABLE var/number_of_choices = length(check_population()) if(number_of_choices < 2) - message = "There [number_of_choices == 1 ? "is only one map" : "are no maps"] to choose from." - return FALSE - + return "There [number_of_choices == 1 ? "is only one map" : "are no maps"] to choose from." if(SSmapping.map_vote_rocked) - return TRUE - - if(!CONFIG_GET(flag/allow_vote_map)) - message = "Map voting is disabled by server configuration settings." - return FALSE - + return VOTE_AVAILABLE if(SSmapping.map_voted) - message = "The next map has already been selected." - return FALSE - - message = initial(message) - return TRUE + return "The next map has already been selected." + return VOTE_AVAILABLE /// Before we create a vote, remove all maps from our choices that are outside of our population range. /// Note that this can result in zero remaining choices for our vote, which is not ideal (but ultimately okay). diff --git a/code/datums/votes/restart_vote.dm b/code/datums/votes/restart_vote.dm index 987d5b87eb363..3c74d7e518e28 100644 --- a/code/datums/votes/restart_vote.dm +++ b/code/datums/votes/restart_vote.dm @@ -7,7 +7,8 @@ CHOICE_RESTART, CHOICE_CONTINUE, ) - message = "Vote to restart the ongoing round." + default_message = "Vote to restart the ongoing round. \ + Only works if there are no non-AFK admins online." /// This proc checks to see if any admins are online for the purposes of this vote to see if it can pass. Returns TRUE if there are valid admins online (Has +SERVER and is not AFK), FALSE otherwise. /datum/vote/restart_vote/proc/admins_present() @@ -19,36 +20,24 @@ return FALSE -/datum/vote/restart_vote/toggle_votable(mob/toggler) - if(!toggler) - CRASH("[type] wasn't passed a \"toggler\" mob to toggle_votable.") - - if(!check_rights_for(toggler.client, R_ADMIN)) - return FALSE - +/datum/vote/restart_vote/toggle_votable() CONFIG_SET(flag/allow_vote_restart, !CONFIG_GET(flag/allow_vote_restart)) - return TRUE /datum/vote/restart_vote/is_config_enabled() return CONFIG_GET(flag/allow_vote_restart) -/datum/vote/restart_vote/can_be_initiated(mob/by_who, forced) +/datum/vote/restart_vote/create_vote(mob/vote_creator) . = ..() if(!.) - return FALSE - - if(!forced && !CONFIG_GET(flag/allow_vote_restart)) - message = "Restart voting is disabled by server configuration settings." - return FALSE - - // We still want players to be able to vote to restart even if valid admins are online. Let's update the message just so that the player is aware of this fact. - // We don't want to lock-out the vote though, so we'll return TRUE. - if(admins_present()) - message = "Regardless of the results of this vote, the round will not automatically restart because an admin is online." - return TRUE + return + if(!admins_present()) + return + async_alert_about_admins(vote_creator) - message = initial(message) - return TRUE +/datum/vote/restart_vote/proc/async_alert_about_admins(mob/vote_creator) + set waitfor = FALSE + tgui_alert(vote_creator, "Note: Regardless of the results of this vote, \ + the round will not automatically restart because an active admin is online.") /datum/vote/restart_vote/get_vote_result(list/non_voters) if(!CONFIG_GET(flag/default_no_vote)) diff --git a/code/datums/votes/rock_the_vote.dm b/code/datums/votes/rock_the_vote.dm index 6cffeb5ad6702..6c7ac4ff2572e 100644 --- a/code/datums/votes/rock_the_vote.dm +++ b/code/datums/votes/rock_the_vote.dm @@ -10,58 +10,40 @@ CHOICE_TO_ROCK, CHOICE_NOT_TO_ROCK, ) - message = "Override the current map vote." + default_message = "Override the current map vote." /// The number of times we have rocked the vote thus far. var/rocking_votes = 0 -/datum/vote/rock_the_vote/toggle_votable(mob/toggler) - if(!toggler) - CRASH("[type] wasn't passed a \"toggler\" mob to toggle_votable.") - if(!check_rights_for(toggler.client, R_ADMIN)) - return FALSE - +/datum/vote/rock_the_vote/toggle_votable() CONFIG_SET(flag/allow_rock_the_vote, !CONFIG_GET(flag/allow_rock_the_vote)) - return TRUE /datum/vote/rock_the_vote/is_config_enabled() return CONFIG_GET(flag/allow_rock_the_vote) -/datum/vote/rock_the_vote/can_be_initiated(mob/by_who, forced) +/datum/vote/rock_the_vote/can_be_initiated(forced) . = ..() - - if(!.) - return FALSE - - if(!forced && !CONFIG_GET(flag/allow_rock_the_vote)) - message = "Rocking the vote is disabled by this server's configuration settings." - return FALSE + if(. != VOTE_AVAILABLE) + return . if(SSticker.current_state == GAME_STATE_FINISHED) - message = "The game is finished, no map votes can be initiated." - return FALSE + return "The game is finished, no map votes can be initiated." if(rocking_votes >= CONFIG_GET(number/max_rocking_votes)) - message = "The maximum number of times to rock the vote has been reached." - return FALSE + return "The maximum number of times to rock the vote has been reached." if(SSmapping.map_vote_rocked) - message = "The vote has already been rocked! Initiate a map vote!" - return FALSE + return "The vote has already been rocked! Initiate a map vote!" if(!SSmapping.map_voted) - message = "Rocking the vote is disabled because no map has been voted on yet!" - return FALSE + return "Rocking the vote is disabled because no map has been voted on yet!" if(SSmapping.map_force_chosen) - message = "Rocking the vote is disabled because an admin has forcibly set the map!" - return FALSE + return "Rocking the vote is disabled because an admin has forcibly set the map!" if(EMERGENCY_ESCAPED_OR_ENDGAMED && SSmapping.map_voted) - message = "The emergency shuttle has already left the station and the next map has already been chosen!" - return FALSE + return "The emergency shuttle has already left the station and the next map has already been chosen!" - message = initial(message) - return TRUE + return VOTE_AVAILABLE /datum/vote/rock_the_vote/finalize_vote(winning_option) rocking_votes++ diff --git a/tgui/packages/tgui/interfaces/VotePanel.tsx b/tgui/packages/tgui/interfaces/VotePanel.tsx index 07da85e6028d2..2cb9c91ff8249 100644 --- a/tgui/packages/tgui/interfaces/VotePanel.tsx +++ b/tgui/packages/tgui/interfaces/VotePanel.tsx @@ -5,6 +5,7 @@ import { Box, Button, Collapsible, + Dimmer, Icon, LabeledList, NoticeBox, @@ -59,11 +60,13 @@ type Data = { possibleVotes: Vote[]; user: UserData; voting: string[]; + LastVoteTime: number; + VoteCD: number; }; export const VotePanel = (props) => { - const { data } = useBackend(); - const { currentVote, user } = data; + const { act, data } = useBackend(); + const { currentVote, user, LastVoteTime, VoteCD } = data; /** * Adds the voting type to title if there is an ongoing vote. @@ -81,7 +84,19 @@ export const VotePanel = (props) => { -
+
act('resetCooldown')} + /> + ) + } + > {!!user.isLowerAdmin && currentVote && }
@@ -93,51 +108,86 @@ export const VotePanel = (props) => { ); }; +const VoteOptionDimmer = (props) => { + const { data } = useBackend(); + const { LastVoteTime, VoteCD } = data; + + return ( + + + + Vote Cooldown + + {Math.floor((VoteCD + LastVoteTime) / 10)}s + + + ); +}; + /** * The create vote options menu. Only upper admins can disable voting. * @returns A section visible to everyone with vote options. */ const VoteOptions = (props) => { const { act, data } = useBackend(); - const { possibleVotes, user } = data; + const { possibleVotes, user, LastVoteTime, VoteCD } = data; return ( - - {possibleVotes.map((option) => ( - - {!!user.isLowerAdmin && option.config !== VoteConfig.None && ( - - act('toggleVote', { - voteName: option.name, - }) - } - /> - )} -
); @@ -153,11 +203,11 @@ const VotersList = (props) => { return ( -
+
{data.voting.map((voter) => { return {voter}; })} @@ -275,13 +325,26 @@ const TimePanel = (props) => { {currentVote?.timeRemaining || 0}s {!!user.isLowerAdmin && ( - + + + + + + + + )}
From 2f5ade08bc82c6ce3ad2d501b4b352a85796d16d Mon Sep 17 00:00:00 2001 From: orange man <61334995+comfyorange@users.noreply.github.com> Date: Thu, 2 May 2024 10:55:24 +1200 Subject: [PATCH 24/87] Automatic changelog for PR #82981 [ci skip] --- html/changelogs/AutoChangeLog-pr-82981.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-82981.yml diff --git a/html/changelogs/AutoChangeLog-pr-82981.yml b/html/changelogs/AutoChangeLog-pr-82981.yml new file mode 100644 index 0000000000000..b19efb8134b65 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-82981.yml @@ -0,0 +1,7 @@ +author: "Melbert" +delete-after: True +changes: + - admin: "Custom Single and Custom Multi votes are now combined into one vote" + - admin: "Admins can now end votes instantly, rather than cancelling them" + - admin: "Admins can now reset the vote cooldown" + - bugfix: "Vote cooldown actually applies now" \ No newline at end of file From 5f44545da81ba36f873cba772e9815ed037cb507 Mon Sep 17 00:00:00 2001 From: san7890 Date: Wed, 1 May 2024 17:14:18 -0600 Subject: [PATCH 25/87] Moves "sprite accessories" (e.g. Hair, Undergarments, Mutant Bits) from `GLOB` to a datasystem (#82847) This is just a revitalization of #80275. ## About The Pull Request On the tin, basically demotes everything related to setting up and storing these bulky lists generated from reading `/datum/sprite_accessory` subtypes from living in a global space that will instead be in a compartmentalized subsystem for accesses. Also a lot of code modernization and micro-improvements (unquantifiable) ## Why It's Good For The Game Same exact expected results, just accessed in a different way. ![image](https://github.com/tgstation/tgstation/assets/34697715/14627773-c9fb-45bd-8ce0-dee33cdd1d27) There's a few reasons why I want this to happen. * The `GLOB` space is too clogged. There are at least a thousand variables on `GLOB`, and it's extremely painful to access stuff on production/local through view variables when you're debugging stuff like this. It's also painful when there is stuff that _should_ live on `GLOB` that you might want to see in VV/Debugger but are forced to either have to scroll a mile to find what you want or wait a long while for it to load. The less bulky lists we have of stored initialized datums, the better. * `make_datum_reference_lists()` is a consequence of wack stuff like this where we're reliant on certain things being initialized in the `GLOB` portion of world initialization _before_ subsystems/static variables load - most of these datum lists in the aforementioned proc doesn't _really_ need to be ready to go before `world.New()` for example. We'll sadly have to abuse `PreInit()` for now, but it really is something that has to be ready to go due the critical dependence that stuff like Preferences has on it. * We don't have to have the procs live in a global namespace either. Instead of passing in `GLOB.XList` or `DSstorage.XList` every single time, we can instead just move the proc setup on the subsystem and use `XList` in a more native fashion. * It's easier to find what you need. To me, it's a lot nicer to ctrl+click the DS and go to the variables to find something I'm looking for instead of having to scavenge around for any footprint/trace of the global I want to look for. This is more trivial than the other two, but that's something I like to think about when I go to bed. I also had to refactor a bit of the code to accommodate the limitations of the new DS system, but it should be a lot cleaner anyways. ## Changelog Not relevant --- Also nothing should have broken but it's a good thing we have screenshot unit tests to prove me wrong. --- code/__HELPERS/global_lists.dm | 42 ----- code/__HELPERS/mobs.dm | 38 ++--- code/_globalvars/lists/flavor_misc.dm | 42 ----- .../subsystem/sprite_accessories.dm | 154 ++++++++++++++++++ code/datums/dna.dm | 103 ++++++------ code/datums/quirks/neutral_quirks/bald.dm | 2 +- code/datums/sprite_accessories.dm | 29 ---- code/game/objects/items/cosmetics.dm | 4 +- code/game/objects/items/dyespray.dm | 2 +- code/game/objects/structures/dresser.dm | 6 +- code/game/objects/structures/mannequin.dm | 12 +- code/game/objects/structures/mirror.dm | 6 +- code/modules/client/preferences/clothing.dm | 12 +- .../preferences/species_features/basic.dm | 14 +- .../preferences/species_features/felinid.dm | 4 +- .../preferences/species_features/lizard.dm | 22 +-- .../preferences/species_features/monkey.dm | 2 +- .../preferences/species_features/moth.dm | 12 +- .../species_features/mushperson.dm | 2 +- .../preferences/species_features/pod.dm | 6 +- code/modules/clothing/head/wig.dm | 10 +- .../restaurant/customers/_customer.dm | 2 +- code/modules/hydroponics/grown/replicapod.dm | 2 +- code/modules/mining/lavaland/tendril_loot.dm | 2 +- code/modules/mob/dead/observer/observer.dm | 4 +- .../mob/living/basic/space_fauna/ghost.dm | 4 +- .../mob/living/carbon/human/_species.dm | 15 +- code/modules/mob/living/carbon/human/dummy.dm | 26 +-- .../human/species_types/lizardpeople.dm | 2 +- .../carbon/human/species_types/mothmen.dm | 2 +- .../carbon/human/species_types/mushpeople.dm | 2 +- .../chemistry/reagents/other_reagents.dm | 4 +- .../surgery/bodyparts/head_hair_and_lips.dm | 8 +- .../organs/external/_external_organ.dm | 10 +- .../modules/surgery/organs/external/spines.dm | 2 +- code/modules/surgery/organs/external/tails.dm | 8 +- .../organs/external/wings/functional_wings.dm | 4 +- .../organs/external/wings/moth_wings.dm | 2 +- tgstation.dme | 1 + 39 files changed, 333 insertions(+), 291 deletions(-) create mode 100644 code/controllers/subsystem/sprite_accessories.dm diff --git a/code/__HELPERS/global_lists.dm b/code/__HELPERS/global_lists.dm index 83294d26180e7..84299fc3021fb 100644 --- a/code/__HELPERS/global_lists.dm +++ b/code/__HELPERS/global_lists.dm @@ -2,37 +2,6 @@ /////Initial Building///// ////////////////////////// -/proc/init_sprite_accessories() - //hair - init_sprite_accessory_subtypes(/datum/sprite_accessory/hair, GLOB.hairstyles_list, GLOB.hairstyles_male_list, GLOB.hairstyles_female_list) - //facial hair - init_sprite_accessory_subtypes(/datum/sprite_accessory/facial_hair, GLOB.facial_hairstyles_list, GLOB.facial_hairstyles_male_list, GLOB.facial_hairstyles_female_list) - //underwear - init_sprite_accessory_subtypes(/datum/sprite_accessory/underwear, GLOB.underwear_list, GLOB.underwear_m, GLOB.underwear_f) - //undershirt - init_sprite_accessory_subtypes(/datum/sprite_accessory/undershirt, GLOB.undershirt_list, GLOB.undershirt_m, GLOB.undershirt_f) - //socks - init_sprite_accessory_subtypes(/datum/sprite_accessory/socks, GLOB.socks_list) - //bodypart accessories (blizzard intensifies) - init_sprite_accessory_subtypes(/datum/sprite_accessory/body_markings, GLOB.body_markings_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/human, GLOB.tails_list_human, add_blank = TRUE) - init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/lizard, GLOB.tails_list_lizard, add_blank = TRUE) - init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/monkey, GLOB.tails_list_monkey, add_blank = FALSE) - init_sprite_accessory_subtypes(/datum/sprite_accessory/snouts, GLOB.snouts_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/horns,GLOB.horns_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/ears, GLOB.ears_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/wings, GLOB.wings_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/wings_open, GLOB.wings_open_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/frills, GLOB.frills_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/spines, GLOB.spines_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/tail_spines, GLOB.tail_spines_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/legs, GLOB.legs_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/caps, GLOB.caps_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_wings, GLOB.moth_wings_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_antennae, GLOB.moth_antennae_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_markings, GLOB.moth_markings_list) - init_sprite_accessory_subtypes(/datum/sprite_accessory/pod_hair, GLOB.pod_hair_list) - /// Inits GLOB.species_list. Not using GLOBAL_LIST_INIT b/c it depends on GLOB.string_lists /proc/init_species_list() for(var/species_path in subtypesof(/datum/species)) @@ -48,21 +17,10 @@ sort_list(surgeries, GLOBAL_PROC_REF(cmp_typepaths_asc)) return surgeries -/// Hair Gradients - Initialise all /datum/sprite_accessory/hair_gradient into an list indexed by gradient-style name -/proc/init_hair_gradients() - for(var/path in subtypesof(/datum/sprite_accessory/gradient)) - var/datum/sprite_accessory/gradient/gradient = new path() - if(gradient.gradient_category & GRADIENT_APPLIES_TO_HAIR) - GLOB.hair_gradients_list[gradient.name] = gradient - if(gradient.gradient_category & GRADIENT_APPLIES_TO_FACIAL_HAIR) - GLOB.facial_hair_gradients_list[gradient.name] = gradient - /// Legacy procs that really should be replaced with proper _INIT macros /proc/make_datum_reference_lists() // I tried to eliminate this proc but I couldn't untangle their init-order interdependencies -Dominion/Cyberboss - init_sprite_accessories() init_species_list() - init_hair_gradients() init_keybindings() GLOB.emote_list = init_emote_list() // WHY DOES THIS NEED TO GO HERE? IT JUST INITS DATUMS init_crafting_recipes() diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm index 68965248115cd..9bc3beb3a1b39 100644 --- a/code/__HELPERS/mobs.dm +++ b/code/__HELPERS/mobs.dm @@ -29,31 +29,31 @@ return COLOR_BLACK /proc/random_underwear(gender) - if(!GLOB.underwear_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/underwear, GLOB.underwear_list, GLOB.underwear_m, GLOB.underwear_f) + if(length(SSaccessories.underwear_list) == 0) + CRASH("No underwear to choose from!") switch(gender) if(MALE) - return pick(GLOB.underwear_m) + return pick(SSaccessories.underwear_m) if(FEMALE) - return pick(GLOB.underwear_f) + return pick(SSaccessories.underwear_f) else - return pick(GLOB.underwear_list) + return pick(SSaccessories.underwear_list) /proc/random_undershirt(gender) - if(!GLOB.undershirt_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/undershirt, GLOB.undershirt_list, GLOB.undershirt_m, GLOB.undershirt_f) + if(length(SSaccessories.undershirt_list) == 0) + CRASH("No undershirts to choose from!") switch(gender) if(MALE) - return pick(GLOB.undershirt_m) + return pick(SSaccessories.undershirt_m) if(FEMALE) - return pick(GLOB.undershirt_f) + return pick(SSaccessories.undershirt_f) else - return pick(GLOB.undershirt_list) + return pick(SSaccessories.undershirt_list) /proc/random_socks() - if(!GLOB.socks_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/socks, GLOB.socks_list) - return pick(GLOB.socks_list) + if(length(SSaccessories.socks_list) == 0) + CRASH("No socks to choose from!") + return pick(SSaccessories.socks_list) /proc/random_backpack() return pick(GLOB.backpacklist) @@ -61,20 +61,20 @@ /proc/random_hairstyle(gender) switch(gender) if(MALE) - return pick(GLOB.hairstyles_male_list) + return pick(SSaccessories.hairstyles_male_list) if(FEMALE) - return pick(GLOB.hairstyles_female_list) + return pick(SSaccessories.hairstyles_female_list) else - return pick(GLOB.hairstyles_list) + return pick(SSaccessories.hairstyles_list) /proc/random_facial_hairstyle(gender) switch(gender) if(MALE) - return pick(GLOB.facial_hairstyles_male_list) + return pick(SSaccessories.facial_hairstyles_male_list) if(FEMALE) - return pick(GLOB.facial_hairstyles_female_list) + return pick(SSaccessories.facial_hairstyles_female_list) else - return pick(GLOB.facial_hairstyles_list) + return pick(SSaccessories.facial_hairstyles_list) /proc/random_unique_name(gender, attempts_to_find_unique_name=10) for(var/i in 1 to attempts_to_find_unique_name) diff --git a/code/_globalvars/lists/flavor_misc.dm b/code/_globalvars/lists/flavor_misc.dm index ce4d847928988..e7c6368d4d5fd 100644 --- a/code/_globalvars/lists/flavor_misc.dm +++ b/code/_globalvars/lists/flavor_misc.dm @@ -1,45 +1,3 @@ -//Preferences stuff - //Hairstyles -GLOBAL_LIST_EMPTY(hairstyles_list) //stores /datum/sprite_accessory/hair indexed by name -GLOBAL_LIST_EMPTY(hairstyles_male_list) //stores only hair names -GLOBAL_LIST_EMPTY(hairstyles_female_list) //stores only hair names -GLOBAL_LIST_EMPTY(facial_hairstyles_list) //stores /datum/sprite_accessory/facial_hair indexed by name -GLOBAL_LIST_EMPTY(facial_hairstyles_male_list) //stores only hair names -GLOBAL_LIST_EMPTY(facial_hairstyles_female_list) //stores only hair names -GLOBAL_LIST_EMPTY(hair_gradients_list) //stores /datum/sprite_accessory/hair_gradient indexed by name -GLOBAL_LIST_EMPTY(facial_hair_gradients_list) //stores /datum/sprite_accessory/facial_hair_gradient indexed by name - //Underwear -GLOBAL_LIST_EMPTY(underwear_list) //stores /datum/sprite_accessory/underwear indexed by name -GLOBAL_LIST_EMPTY(underwear_m) //stores only underwear name -GLOBAL_LIST_EMPTY(underwear_f) //stores only underwear name - //Undershirts -GLOBAL_LIST_EMPTY(undershirt_list) //stores /datum/sprite_accessory/undershirt indexed by name -GLOBAL_LIST_EMPTY(undershirt_m) //stores only undershirt name -GLOBAL_LIST_EMPTY(undershirt_f) //stores only undershirt name - //Socks -GLOBAL_LIST_EMPTY(socks_list) //stores /datum/sprite_accessory/socks indexed by name - //Lizard Bits (all datum lists indexed by name) -GLOBAL_LIST_EMPTY(body_markings_list) -GLOBAL_LIST_EMPTY(snouts_list) -GLOBAL_LIST_EMPTY(horns_list) -GLOBAL_LIST_EMPTY(frills_list) -GLOBAL_LIST_EMPTY(spines_list) -GLOBAL_LIST_EMPTY(tail_spines_list) -GLOBAL_LIST_EMPTY(legs_list) - - //Mutant Human bits -GLOBAL_LIST_EMPTY(tails_list_human) -GLOBAL_LIST_EMPTY(tails_list_lizard) -GLOBAL_LIST_EMPTY(tails_list_monkey) -GLOBAL_LIST_EMPTY(ears_list) -GLOBAL_LIST_EMPTY(wings_list) -GLOBAL_LIST_EMPTY(wings_open_list) -GLOBAL_LIST_EMPTY(moth_wings_list) -GLOBAL_LIST_EMPTY(moth_antennae_list) -GLOBAL_LIST_EMPTY(moth_markings_list) -GLOBAL_LIST_EMPTY(caps_list) -GLOBAL_LIST_EMPTY(pod_hair_list) - GLOBAL_LIST_INIT(color_list_ethereal, list( "Blue" = "#3399ff", "Bright Yellow" = "#ffff99", diff --git a/code/controllers/subsystem/sprite_accessories.dm b/code/controllers/subsystem/sprite_accessories.dm new file mode 100644 index 0000000000000..ec5934ac8e8d4 --- /dev/null +++ b/code/controllers/subsystem/sprite_accessories.dm @@ -0,0 +1,154 @@ +/// The non gender specific list that we get from init_sprite_accessory_subtypes() +#define DEFAULT_SPRITE_LIST "default_sprites" +/// The male specific list that we get from init_sprite_accessory_subtypes() +#define MALE_SPRITE_LIST "male_sprites" +/// The female specific list that we get from init_sprite_accessory_subtypes() +#define FEMALE_SPRITE_LIST "female_sprites" + +/// subsystem that just holds lists of sprite accessories for accession in generating said sprites. +/// A sprite accessory is something that we add to a human sprite to make them look different. This is hair, facial hair, underwear, mutant bits, etc. +SUBSYSTEM_DEF(accessories) // just 'accessories' for brevity + name = "Sprite Accessories" + flags = SS_NO_FIRE | SS_NO_INIT + + //Hairstyles + var/list/hairstyles_list //! stores /datum/sprite_accessory/hair indexed by name + var/list/hairstyles_male_list //! stores only hair names + var/list/hairstyles_female_list //! stores only hair names + var/list/facial_hairstyles_list //! stores /datum/sprite_accessory/facial_hair indexed by name + var/list/facial_hairstyles_male_list //! stores only hair names + var/list/facial_hairstyles_female_list //! stores only hair names + var/list/hair_gradients_list //! stores /datum/sprite_accessory/hair_gradient indexed by name + var/list/facial_hair_gradients_list //! stores /datum/sprite_accessory/facial_hair_gradient indexed by name + + //Underwear + var/list/underwear_list //! stores /datum/sprite_accessory/underwear indexed by name + var/list/underwear_m //! stores only underwear name + var/list/underwear_f //! stores only underwear name + + //Undershirts + var/list/undershirt_list //! stores /datum/sprite_accessory/undershirt indexed by name + var/list/undershirt_m //! stores only undershirt name + var/list/undershirt_f //! stores only undershirt name + + //Socks + var/list/socks_list //! stores /datum/sprite_accessory/socks indexed by name + + //Lizard Bits (all datum lists indexed by name) + var/list/body_markings_list + var/list/snouts_list + var/list/horns_list + var/list/frills_list + var/list/spines_list + var/list/legs_list + var/list/tail_spines_list + + //Mutant Human bits + var/list/tails_list_human + var/list/tails_list_lizard + var/list/tails_list_monkey + var/list/ears_list + var/list/wings_list + var/list/wings_open_list + var/list/moth_wings_list + var/list/moth_antennae_list + var/list/moth_markings_list + var/list/caps_list + var/list/pod_hair_list + +/datum/controller/subsystem/accessories/PreInit() // this stuff NEEDS to be set up before GLOB for preferences and stuff to work so this must go here. sorry + setup_lists() + init_hair_gradients() + +/// Sets up all of the lists for later utilization in the round and building sprites. +/// In an ideal world we could tack everything that just needed `DEFAULT_SPRITE_LIST` into static variables on the top, but due to the initialization order +/// where this subsystem will initialize BEFORE statics, it's just not feasible since this all needs to be ready for actual subsystems to use. +/// Sorry. +/datum/controller/subsystem/accessories/proc/setup_lists() + var/hair_lists = init_sprite_accessory_subtypes(/datum/sprite_accessory/hair) + hairstyles_list = hair_lists[DEFAULT_SPRITE_LIST] + hairstyles_male_list = hair_lists[MALE_SPRITE_LIST] + hairstyles_female_list = hair_lists[FEMALE_SPRITE_LIST] + + var/facial_hair_lists = init_sprite_accessory_subtypes(/datum/sprite_accessory/facial_hair) + facial_hairstyles_list = facial_hair_lists[DEFAULT_SPRITE_LIST] + facial_hairstyles_male_list = facial_hair_lists[MALE_SPRITE_LIST] + facial_hairstyles_female_list = facial_hair_lists[FEMALE_SPRITE_LIST] + + var/underwear_lists = init_sprite_accessory_subtypes(/datum/sprite_accessory/underwear) + underwear_list = underwear_lists[DEFAULT_SPRITE_LIST] + underwear_m = underwear_lists[MALE_SPRITE_LIST] + underwear_f = underwear_lists[FEMALE_SPRITE_LIST] + + var/undershirt_lists = init_sprite_accessory_subtypes(/datum/sprite_accessory/undershirt) + undershirt_list = undershirt_lists[DEFAULT_SPRITE_LIST] + undershirt_m = undershirt_lists[MALE_SPRITE_LIST] + undershirt_f = undershirt_lists[FEMALE_SPRITE_LIST] + + socks_list = init_sprite_accessory_subtypes(/datum/sprite_accessory/socks)[DEFAULT_SPRITE_LIST] + + body_markings_list = init_sprite_accessory_subtypes(/datum/sprite_accessory/body_markings)[DEFAULT_SPRITE_LIST] + tails_list_human = init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/human, add_blank = TRUE)[DEFAULT_SPRITE_LIST] + tails_list_lizard = init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/lizard, add_blank = TRUE)[DEFAULT_SPRITE_LIST] + tails_list_monkey = init_sprite_accessory_subtypes(/datum/sprite_accessory/tails/monkey, add_blank = TRUE)[DEFAULT_SPRITE_LIST] + snouts_list = init_sprite_accessory_subtypes(/datum/sprite_accessory/snouts)[DEFAULT_SPRITE_LIST] + horns_list = init_sprite_accessory_subtypes(/datum/sprite_accessory/horns)[DEFAULT_SPRITE_LIST] + ears_list = init_sprite_accessory_subtypes(/datum/sprite_accessory/ears)[DEFAULT_SPRITE_LIST] + wings_list = init_sprite_accessory_subtypes(/datum/sprite_accessory/wings)[DEFAULT_SPRITE_LIST] + wings_open_list = init_sprite_accessory_subtypes(/datum/sprite_accessory/wings_open)[DEFAULT_SPRITE_LIST] + frills_list = init_sprite_accessory_subtypes(/datum/sprite_accessory/frills)[DEFAULT_SPRITE_LIST] + spines_list = init_sprite_accessory_subtypes(/datum/sprite_accessory/spines)[DEFAULT_SPRITE_LIST] + tail_spines_list = init_sprite_accessory_subtypes(/datum/sprite_accessory/tail_spines)[DEFAULT_SPRITE_LIST] + legs_list = init_sprite_accessory_subtypes(/datum/sprite_accessory/legs)[DEFAULT_SPRITE_LIST] + caps_list = init_sprite_accessory_subtypes(/datum/sprite_accessory/caps)[DEFAULT_SPRITE_LIST] + moth_wings_list = init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_wings)[DEFAULT_SPRITE_LIST] + moth_antennae_list = init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_antennae)[DEFAULT_SPRITE_LIST] + moth_markings_list = init_sprite_accessory_subtypes(/datum/sprite_accessory/moth_markings)[DEFAULT_SPRITE_LIST] + pod_hair_list = init_sprite_accessory_subtypes(/datum/sprite_accessory/pod_hair)[DEFAULT_SPRITE_LIST] + +/// This proc just intializes all /datum/sprite_accessory/hair_gradient into an list indexed by gradient-style name +/datum/controller/subsystem/accessories/proc/init_hair_gradients() + hair_gradients_list = list() + facial_hair_gradients_list = list() + for(var/path in subtypesof(/datum/sprite_accessory/gradient)) + var/datum/sprite_accessory/gradient/gradient = new path + if(gradient.gradient_category & GRADIENT_APPLIES_TO_HAIR) + hair_gradients_list[gradient.name] = gradient + if(gradient.gradient_category & GRADIENT_APPLIES_TO_FACIAL_HAIR) + facial_hair_gradients_list[gradient.name] = gradient + +/// This reads the applicable sprite accessory datum's subtypes and adds it to the subsystem's list of sprite accessories. +/// The boolean `add_blank` argument just adds a "None" option to the list of sprite accessories, like if a felinid doesn't want a tail or something, typically good for gated-off things. +/datum/controller/subsystem/accessories/proc/init_sprite_accessory_subtypes(prototype, add_blank = FALSE) + RETURN_TYPE(/list) + var/returnable_list = list( + DEFAULT_SPRITE_LIST = list(), + MALE_SPRITE_LIST = list(), + FEMALE_SPRITE_LIST = list(), + ) + + for(var/path in subtypesof(prototype)) + var/datum/sprite_accessory/accessory = new path + + if(accessory.icon_state) + returnable_list[DEFAULT_SPRITE_LIST][accessory.name] = accessory + else + returnable_list[DEFAULT_SPRITE_LIST] += accessory.name + + switch(accessory.gender) + if(MALE) + returnable_list[MALE_SPRITE_LIST] += accessory.name + if(FEMALE) + returnable_list[FEMALE_SPRITE_LIST] += accessory.name + else + returnable_list[MALE_SPRITE_LIST] += accessory.name + returnable_list[FEMALE_SPRITE_LIST] += accessory.name + + if(add_blank) + returnable_list[DEFAULT_SPRITE_LIST][SPRITE_ACCESSORY_NONE] = new /datum/sprite_accessory/blank + + return returnable_list + +#undef DEFAULT_SPRITE_LIST +#undef MALE_SPRITE_LIST +#undef FEMALE_SPRITE_LIST diff --git a/code/datums/dna.dm b/code/datums/dna.dm index 7d69353e02ffc..ab5407bc78ccf 100644 --- a/code/datums/dna.dm +++ b/code/datums/dna.dm @@ -179,13 +179,12 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block()) L[DNA_GENDER_BLOCK] = construct_block(G_PLURAL, GENDERS) if(ishuman(holder)) var/mob/living/carbon/human/H = holder - if(!GLOB.hairstyles_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/hair,GLOB.hairstyles_list, GLOB.hairstyles_male_list, GLOB.hairstyles_female_list) - L[DNA_HAIRSTYLE_BLOCK] = construct_block(GLOB.hairstyles_list.Find(H.hairstyle), GLOB.hairstyles_list.len) + if(length(SSaccessories.hairstyles_list) == 0 || length(SSaccessories.facial_hairstyles_list) == 0) + CRASH("SSaccessories lists are empty, this is bad!") + + L[DNA_HAIRSTYLE_BLOCK] = construct_block(SSaccessories.hairstyles_list.Find(H.hairstyle), length(SSaccessories.hairstyles_list)) L[DNA_HAIR_COLOR_BLOCK] = sanitize_hexcolor(H.hair_color, include_crunch = FALSE) - if(!GLOB.facial_hairstyles_list.len) - init_sprite_accessory_subtypes(/datum/sprite_accessory/facial_hair, GLOB.facial_hairstyles_list, GLOB.facial_hairstyles_male_list, GLOB.facial_hairstyles_female_list) - L[DNA_FACIAL_HAIRSTYLE_BLOCK] = construct_block(GLOB.facial_hairstyles_list.Find(H.facial_hairstyle), GLOB.facial_hairstyles_list.len) + L[DNA_FACIAL_HAIRSTYLE_BLOCK] = construct_block(SSaccessories.facial_hairstyles_list.Find(H.facial_hairstyle), length(SSaccessories.facial_hairstyles_list)) L[DNA_FACIAL_HAIR_COLOR_BLOCK] = sanitize_hexcolor(H.facial_hair_color, include_crunch = FALSE) L[DNA_SKIN_TONE_BLOCK] = construct_block(GLOB.skin_tones.Find(H.skin_tone), GLOB.skin_tones.len) L[DNA_EYE_COLOR_LEFT_BLOCK] = sanitize_hexcolor(H.eye_color_left, include_crunch = FALSE) @@ -203,33 +202,33 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block()) if(features["ethcolor"]) L[DNA_ETHEREAL_COLOR_BLOCK] = sanitize_hexcolor(features["ethcolor"], include_crunch = FALSE) if(features["body_markings"]) - L[DNA_LIZARD_MARKINGS_BLOCK] = construct_block(GLOB.body_markings_list.Find(features["body_markings"]), GLOB.body_markings_list.len) + L[DNA_LIZARD_MARKINGS_BLOCK] = construct_block(SSaccessories.body_markings_list.Find(features["body_markings"]), length(SSaccessories.body_markings_list)) if(features["tail_cat"]) - L[DNA_TAIL_BLOCK] = construct_block(GLOB.tails_list_human.Find(features["tail_cat"]), GLOB.tails_list_human.len) + L[DNA_TAIL_BLOCK] = construct_block(SSaccessories.tails_list_human.Find(features["tail_cat"]), length(SSaccessories.tails_list_human)) if(features["tail_lizard"]) - L[DNA_LIZARD_TAIL_BLOCK] = construct_block(GLOB.tails_list_lizard.Find(features["tail_lizard"]), GLOB.tails_list_lizard.len) + L[DNA_LIZARD_TAIL_BLOCK] = construct_block(SSaccessories.tails_list_lizard.Find(features["tail_lizard"]), length(SSaccessories.tails_list_lizard)) if(features["tail_monkey"]) - L[DNA_MONKEY_TAIL_BLOCK] = construct_block(GLOB.tails_list_monkey.Find(features["tail_monkey"]), GLOB.tails_list_monkey.len) + L[DNA_MONKEY_TAIL_BLOCK] = construct_block(SSaccessories.tails_list_monkey.Find(features["tail_monkey"]), length(SSaccessories.tails_list_monkey)) if(features["snout"]) - L[DNA_SNOUT_BLOCK] = construct_block(GLOB.snouts_list.Find(features["snout"]), GLOB.snouts_list.len) + L[DNA_SNOUT_BLOCK] = construct_block(SSaccessories.snouts_list.Find(features["snout"]), length(SSaccessories.snouts_list)) if(features["horns"]) - L[DNA_HORNS_BLOCK] = construct_block(GLOB.horns_list.Find(features["horns"]), GLOB.horns_list.len) + L[DNA_HORNS_BLOCK] = construct_block(SSaccessories.horns_list.Find(features["horns"]), length(SSaccessories.horns_list)) if(features["frills"]) - L[DNA_FRILLS_BLOCK] = construct_block(GLOB.frills_list.Find(features["frills"]), GLOB.frills_list.len) + L[DNA_FRILLS_BLOCK] = construct_block(SSaccessories.frills_list.Find(features["frills"]), length(SSaccessories.frills_list)) if(features["spines"]) - L[DNA_SPINES_BLOCK] = construct_block(GLOB.spines_list.Find(features["spines"]), GLOB.spines_list.len) + L[DNA_SPINES_BLOCK] = construct_block(SSaccessories.spines_list.Find(features["spines"]), length(SSaccessories.spines_list)) if(features["ears"]) - L[DNA_EARS_BLOCK] = construct_block(GLOB.ears_list.Find(features["ears"]), GLOB.ears_list.len) + L[DNA_EARS_BLOCK] = construct_block(SSaccessories.ears_list.Find(features["ears"]), length(SSaccessories.ears_list)) if(features["moth_wings"] != "Burnt Off") - L[DNA_MOTH_WINGS_BLOCK] = construct_block(GLOB.moth_wings_list.Find(features["moth_wings"]), GLOB.moth_wings_list.len) + L[DNA_MOTH_WINGS_BLOCK] = construct_block(SSaccessories.moth_wings_list.Find(features["moth_wings"]), length(SSaccessories.moth_wings_list)) if(features["moth_antennae"] != "Burnt Off") - L[DNA_MOTH_ANTENNAE_BLOCK] = construct_block(GLOB.moth_antennae_list.Find(features["moth_antennae"]), GLOB.moth_antennae_list.len) + L[DNA_MOTH_ANTENNAE_BLOCK] = construct_block(SSaccessories.moth_antennae_list.Find(features["moth_antennae"]), length(SSaccessories.moth_antennae_list)) if(features["moth_markings"]) - L[DNA_MOTH_MARKINGS_BLOCK] = construct_block(GLOB.moth_markings_list.Find(features["moth_markings"]), GLOB.moth_markings_list.len) + L[DNA_MOTH_MARKINGS_BLOCK] = construct_block(SSaccessories.moth_markings_list.Find(features["moth_markings"]), length(SSaccessories.moth_markings_list)) if(features["caps"]) - L[DNA_MUSHROOM_CAPS_BLOCK] = construct_block(GLOB.caps_list.Find(features["caps"]), GLOB.caps_list.len) + L[DNA_MUSHROOM_CAPS_BLOCK] = construct_block(SSaccessories.caps_list.Find(features["caps"]), length(SSaccessories.caps_list)) if(features["pod_hair"]) - L[DNA_POD_HAIR_BLOCK] = construct_block(GLOB.pod_hair_list.Find(features["pod_hair"]), GLOB.pod_hair_list.len) + L[DNA_POD_HAIR_BLOCK] = construct_block(SSaccessories.pod_hair_list.Find(features["pod_hair"]), length(SSaccessories.pod_hair_list)) for(var/blocknum in 1 to DNA_FEATURE_BLOCKS) . += L[blocknum] || random_string(GET_UI_BLOCK_LEN(blocknum), GLOB.hex_characters) @@ -326,9 +325,9 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block()) else set_uni_identity_block(blocknumber, construct_block(G_PLURAL, GENDERS)) if(DNA_FACIAL_HAIRSTYLE_BLOCK) - set_uni_identity_block(blocknumber, construct_block(GLOB.facial_hairstyles_list.Find(H.facial_hairstyle), GLOB.facial_hairstyles_list.len)) + set_uni_identity_block(blocknumber, construct_block(SSaccessories.facial_hairstyles_list.Find(H.facial_hairstyle), length(SSaccessories.facial_hairstyles_list))) if(DNA_HAIRSTYLE_BLOCK) - set_uni_identity_block(blocknumber, construct_block(GLOB.hairstyles_list.Find(H.hairstyle), GLOB.hairstyles_list.len)) + set_uni_identity_block(blocknumber, construct_block(SSaccessories.hairstyles_list.Find(H.hairstyle), length(SSaccessories.hairstyles_list))) /datum/dna/proc/update_uf_block(blocknumber) if(!blocknumber) @@ -341,33 +340,33 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block()) if(DNA_ETHEREAL_COLOR_BLOCK) set_uni_feature_block(blocknumber, sanitize_hexcolor(features["ethcolor"], include_crunch = FALSE)) if(DNA_LIZARD_MARKINGS_BLOCK) - set_uni_feature_block(blocknumber, construct_block(GLOB.body_markings_list.Find(features["body_markings"]), GLOB.body_markings_list.len)) + set_uni_feature_block(blocknumber, construct_block(SSaccessories.body_markings_list.Find(features["body_markings"]), length(SSaccessories.body_markings_list))) if(DNA_TAIL_BLOCK) - set_uni_feature_block(blocknumber, construct_block(GLOB.tails_list_human.Find(features["tail_cat"]), GLOB.tails_list_human.len)) + set_uni_feature_block(blocknumber, construct_block(SSaccessories.tails_list_human.Find(features["tail_cat"]), length(SSaccessories.tails_list_human))) if(DNA_LIZARD_TAIL_BLOCK) - set_uni_feature_block(blocknumber, construct_block(GLOB.tails_list_lizard.Find(features["tail_lizard"]), GLOB.tails_list_lizard.len)) + set_uni_feature_block(blocknumber, construct_block(SSaccessories.tails_list_lizard.Find(features["tail_lizard"]), length(SSaccessories.tails_list_lizard))) if(DNA_MONKEY_TAIL_BLOCK) - set_uni_feature_block(blocknumber, construct_block(GLOB.tails_list_monkey.Find(features["tail_monkey"]), GLOB.tails_list_monkey.len)) + set_uni_feature_block(blocknumber, construct_block(SSaccessories.tails_list_monkey.Find(features["tail_monkey"]), length(SSaccessories.tails_list_monkey))) if(DNA_SNOUT_BLOCK) - set_uni_feature_block(blocknumber, construct_block(GLOB.snouts_list.Find(features["snout"]), GLOB.snouts_list.len)) + set_uni_feature_block(blocknumber, construct_block(SSaccessories.snouts_list.Find(features["snout"]), length(SSaccessories.snouts_list))) if(DNA_HORNS_BLOCK) - set_uni_feature_block(blocknumber, construct_block(GLOB.horns_list.Find(features["horns"]), GLOB.horns_list.len)) + set_uni_feature_block(blocknumber, construct_block(SSaccessories.horns_list.Find(features["horns"]), length(SSaccessories.horns_list))) if(DNA_FRILLS_BLOCK) - set_uni_feature_block(blocknumber, construct_block(GLOB.frills_list.Find(features["frills"]), GLOB.frills_list.len)) + set_uni_feature_block(blocknumber, construct_block(SSaccessories.frills_list.Find(features["frills"]), length(SSaccessories.frills_list))) if(DNA_SPINES_BLOCK) - set_uni_feature_block(blocknumber, construct_block(GLOB.spines_list.Find(features["spines"]), GLOB.spines_list.len)) + set_uni_feature_block(blocknumber, construct_block(SSaccessories.spines_list.Find(features["spines"]), length(SSaccessories.spines_list))) if(DNA_EARS_BLOCK) - set_uni_feature_block(blocknumber, construct_block(GLOB.ears_list.Find(features["ears"]), GLOB.ears_list.len)) + set_uni_feature_block(blocknumber, construct_block(SSaccessories.ears_list.Find(features["ears"]), length(SSaccessories.ears_list))) if(DNA_MOTH_WINGS_BLOCK) - set_uni_feature_block(blocknumber, construct_block(GLOB.moth_wings_list.Find(features["moth_wings"]), GLOB.moth_wings_list.len)) + set_uni_feature_block(blocknumber, construct_block(SSaccessories.moth_wings_list.Find(features["moth_wings"]), length(SSaccessories.moth_wings_list))) if(DNA_MOTH_ANTENNAE_BLOCK) - set_uni_feature_block(blocknumber, construct_block(GLOB.moth_antennae_list.Find(features["moth_antennae"]), GLOB.moth_antennae_list.len)) + set_uni_feature_block(blocknumber, construct_block(SSaccessories.moth_antennae_list.Find(features["moth_antennae"]), length(SSaccessories.moth_antennae_list))) if(DNA_MOTH_MARKINGS_BLOCK) - set_uni_feature_block(blocknumber, construct_block(GLOB.moth_markings_list.Find(features["moth_markings"]), GLOB.moth_markings_list.len)) + set_uni_feature_block(blocknumber, construct_block(SSaccessories.moth_markings_list.Find(features["moth_markings"]), length(SSaccessories.moth_markings_list))) if(DNA_MUSHROOM_CAPS_BLOCK) - set_uni_feature_block(blocknumber, construct_block(GLOB.caps_list.Find(features["caps"]), GLOB.caps_list.len)) + set_uni_feature_block(blocknumber, construct_block(SSaccessories.caps_list.Find(features["caps"]), length(SSaccessories.caps_list))) if(DNA_POD_HAIR_BLOCK) - set_uni_feature_block(blocknumber, construct_block(GLOB.pod_hair_list.Find(features["pod_hair"]), GLOB.pod_hair_list.len)) + set_uni_feature_block(blocknumber, construct_block(SSaccessories.pod_hair_list.Find(features["pod_hair"]), length(SSaccessories.pod_hair_list))) //Please use add_mutation or activate_mutation instead /datum/dna/proc/force_give(datum/mutation/human/human_mutation) @@ -624,12 +623,12 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block()) if(HAS_TRAIT(src, TRAIT_SHAVED)) set_facial_hairstyle("Shaved", update = FALSE) else - var/style = GLOB.facial_hairstyles_list[deconstruct_block(get_uni_identity_block(structure, DNA_FACIAL_HAIRSTYLE_BLOCK), GLOB.facial_hairstyles_list.len)] + var/style = SSaccessories.facial_hairstyles_list[deconstruct_block(get_uni_identity_block(structure, DNA_FACIAL_HAIRSTYLE_BLOCK), length(SSaccessories.facial_hairstyles_list))] set_facial_hairstyle(style, update = FALSE) if(HAS_TRAIT(src, TRAIT_BALD)) set_hairstyle("Bald", update = FALSE) else - var/style = GLOB.hairstyles_list[deconstruct_block(get_uni_identity_block(structure, DNA_HAIRSTYLE_BLOCK), GLOB.hairstyles_list.len)] + var/style = SSaccessories.hairstyles_list[deconstruct_block(get_uni_identity_block(structure, DNA_HAIRSTYLE_BLOCK), length(SSaccessories.hairstyles_list))] set_hairstyle(style, update = FALSE) var/features = dna.unique_features if(dna.features["mcolor"]) @@ -637,37 +636,37 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block()) if(dna.features["ethcolor"]) dna.features["ethcolor"] = sanitize_hexcolor(get_uni_feature_block(features, DNA_ETHEREAL_COLOR_BLOCK)) if(dna.features["body_markings"]) - dna.features["body_markings"] = GLOB.body_markings_list[deconstruct_block(get_uni_feature_block(features, DNA_LIZARD_MARKINGS_BLOCK), GLOB.body_markings_list.len)] + dna.features["body_markings"] = SSaccessories.body_markings_list[deconstruct_block(get_uni_feature_block(features, DNA_LIZARD_MARKINGS_BLOCK), length(SSaccessories.body_markings_list))] if(dna.features["snout"]) - dna.features["snout"] = GLOB.snouts_list[deconstruct_block(get_uni_feature_block(features, DNA_SNOUT_BLOCK), GLOB.snouts_list.len)] + dna.features["snout"] = SSaccessories.snouts_list[deconstruct_block(get_uni_feature_block(features, DNA_SNOUT_BLOCK), length(SSaccessories.snouts_list))] if(dna.features["horns"]) - dna.features["horns"] = GLOB.horns_list[deconstruct_block(get_uni_feature_block(features, DNA_HORNS_BLOCK), GLOB.horns_list.len)] + dna.features["horns"] = SSaccessories.horns_list[deconstruct_block(get_uni_feature_block(features, DNA_HORNS_BLOCK), length(SSaccessories.horns_list))] if(dna.features["frills"]) - dna.features["frills"] = GLOB.frills_list[deconstruct_block(get_uni_feature_block(features, DNA_FRILLS_BLOCK), GLOB.frills_list.len)] + dna.features["frills"] = SSaccessories.frills_list[deconstruct_block(get_uni_feature_block(features, DNA_FRILLS_BLOCK), length(SSaccessories.frills_list))] if(dna.features["spines"]) - dna.features["spines"] = GLOB.spines_list[deconstruct_block(get_uni_feature_block(features, DNA_SPINES_BLOCK), GLOB.spines_list.len)] + dna.features["spines"] = SSaccessories.spines_list[deconstruct_block(get_uni_feature_block(features, DNA_SPINES_BLOCK), length(SSaccessories.spines_list))] if(dna.features["tail_cat"]) - dna.features["tail_cat"] = GLOB.tails_list_human[deconstruct_block(get_uni_feature_block(features, DNA_TAIL_BLOCK), GLOB.tails_list_human.len)] + dna.features["tail_cat"] = SSaccessories.tails_list_human[deconstruct_block(get_uni_feature_block(features, DNA_TAIL_BLOCK), length(SSaccessories.tails_list_human))] if(dna.features["tail_lizard"]) - dna.features["tail_lizard"] = GLOB.tails_list_lizard[deconstruct_block(get_uni_feature_block(features, DNA_LIZARD_TAIL_BLOCK), GLOB.tails_list_lizard.len)] + dna.features["tail_lizard"] = SSaccessories.tails_list_lizard[deconstruct_block(get_uni_feature_block(features, DNA_LIZARD_TAIL_BLOCK), length(SSaccessories.tails_list_lizard))] if(dna.features["tail_monkey"]) - dna.features["tail_monkey"] = GLOB.tails_list_monkey[deconstruct_block(get_uni_feature_block(features, DNA_MONKEY_TAIL_BLOCK), GLOB.tails_list_monkey.len)] + dna.features["tail_monkey"] = SSaccessories.tails_list_monkey[deconstruct_block(get_uni_feature_block(features, DNA_MONKEY_TAIL_BLOCK), length(SSaccessories.tails_list_monkey))] if(dna.features["ears"]) - dna.features["ears"] = GLOB.ears_list[deconstruct_block(get_uni_feature_block(features, DNA_EARS_BLOCK), GLOB.ears_list.len)] + dna.features["ears"] = SSaccessories.ears_list[deconstruct_block(get_uni_feature_block(features, DNA_EARS_BLOCK), length(SSaccessories.ears_list))] if(dna.features["moth_wings"]) - var/genetic_value = GLOB.moth_wings_list[deconstruct_block(get_uni_feature_block(features, DNA_MOTH_WINGS_BLOCK), GLOB.moth_wings_list.len)] + var/genetic_value = SSaccessories.moth_wings_list[deconstruct_block(get_uni_feature_block(features, DNA_MOTH_WINGS_BLOCK), length(SSaccessories.moth_wings_list))] dna.features["original_moth_wings"] = genetic_value dna.features["moth_wings"] = genetic_value if(dna.features["moth_antennae"]) - var/genetic_value = GLOB.moth_antennae_list[deconstruct_block(get_uni_feature_block(features, DNA_MOTH_ANTENNAE_BLOCK), GLOB.moth_antennae_list.len)] + var/genetic_value = SSaccessories.moth_antennae_list[deconstruct_block(get_uni_feature_block(features, DNA_MOTH_ANTENNAE_BLOCK), length(SSaccessories.moth_antennae_list))] dna.features["original_moth_antennae"] = genetic_value dna.features["moth_antennae"] = genetic_value if(dna.features["moth_markings"]) - dna.features["moth_markings"] = GLOB.moth_markings_list[deconstruct_block(get_uni_feature_block(features, DNA_MOTH_MARKINGS_BLOCK), GLOB.moth_markings_list.len)] + dna.features["moth_markings"] = SSaccessories.moth_markings_list[deconstruct_block(get_uni_feature_block(features, DNA_MOTH_MARKINGS_BLOCK), length(SSaccessories.moth_markings_list))] if(dna.features["caps"]) - dna.features["caps"] = GLOB.caps_list[deconstruct_block(get_uni_feature_block(features, DNA_MUSHROOM_CAPS_BLOCK), GLOB.caps_list.len)] + dna.features["caps"] = SSaccessories.caps_list[deconstruct_block(get_uni_feature_block(features, DNA_MUSHROOM_CAPS_BLOCK), length(SSaccessories.caps_list))] if(dna.features["pod_hair"]) - dna.features["pod_hair"] = GLOB.pod_hair_list[deconstruct_block(get_uni_feature_block(features, DNA_POD_HAIR_BLOCK), GLOB.pod_hair_list.len)] + dna.features["pod_hair"] = SSaccessories.pod_hair_list[deconstruct_block(get_uni_feature_block(features, DNA_POD_HAIR_BLOCK), length(SSaccessories.pod_hair_list))] for(var/obj/item/organ/external/external_organ in organs) external_organ.mutate_feature(features, src) diff --git a/code/datums/quirks/neutral_quirks/bald.dm b/code/datums/quirks/neutral_quirks/bald.dm index 8a760f6ceefdb..2844b790ddfd3 100644 --- a/code/datums/quirks/neutral_quirks/bald.dm +++ b/code/datums/quirks/neutral_quirks/bald.dm @@ -21,7 +21,7 @@ /datum/quirk/item_quirk/bald/add_unique(client/client_source) var/obj/item/clothing/head/wig/natural/baldie_wig = new(get_turf(quirk_holder)) if(old_hair == "Bald") - baldie_wig.hairstyle = pick(GLOB.hairstyles_list - "Bald") + baldie_wig.hairstyle = pick(SSaccessories.hairstyles_list - "Bald") else baldie_wig.hairstyle = old_hair diff --git a/code/datums/sprite_accessories.dm b/code/datums/sprite_accessories.dm index 40c17b774b1c2..e6e8b956e6568 100644 --- a/code/datums/sprite_accessories.dm +++ b/code/datums/sprite_accessories.dm @@ -16,35 +16,6 @@ * conversion in savefile.dm */ -/proc/init_sprite_accessory_subtypes(prototype, list/L, list/male, list/female, add_blank)//Roundstart argument builds a specific list for roundstart parts where some parts may be locked - if(!istype(L)) - L = list() - if(!istype(male)) - male = list() - if(!istype(female)) - female = list() - - for(var/path in subtypesof(prototype)) - var/datum/sprite_accessory/D = new path() - - if(D.icon_state) - L[D.name] = D - else - L += D.name - - switch(D.gender) - if(MALE) - male += D.name - if(FEMALE) - female += D.name - else - male += D.name - female += D.name - if(add_blank) - L[SPRITE_ACCESSORY_NONE] = new /datum/sprite_accessory/blank - - return L - /datum/sprite_accessory /// The icon file the accessory is located in. var/icon diff --git a/code/game/objects/items/cosmetics.dm b/code/game/objects/items/cosmetics.dm index a78d9d4fbf0b9..b16cf3a6ef61a 100644 --- a/code/game/objects/items/cosmetics.dm +++ b/code/game/objects/items/cosmetics.dm @@ -217,7 +217,7 @@ return if(!user.can_perform_action(src, FORBID_TELEKINESIS_REACH)) return - var/new_style = tgui_input_list(user, "Select a facial hairstyle", "Grooming", GLOB.facial_hairstyles_list) + var/new_style = tgui_input_list(user, "Select a facial hairstyle", "Grooming", SSaccessories.facial_hairstyles_list) if(isnull(new_style)) return if(!get_location_accessible(human_target, location)) @@ -270,7 +270,7 @@ return if(!user.can_perform_action(src, FORBID_TELEKINESIS_REACH)) return - var/new_style = tgui_input_list(user, "Select a hairstyle", "Grooming", GLOB.hairstyles_list) + var/new_style = tgui_input_list(user, "Select a hairstyle", "Grooming", SSaccessories.hairstyles_list) if(isnull(new_style)) return if(!get_location_accessible(human_target, location)) diff --git a/code/game/objects/items/dyespray.dm b/code/game/objects/items/dyespray.dm index fd198ec1c40e3..b64f15fe69073 100644 --- a/code/game/objects/items/dyespray.dm +++ b/code/game/objects/items/dyespray.dm @@ -27,7 +27,7 @@ if(!beard_or_hair || !user.can_perform_action(src, NEED_DEXTERITY)) return - var/list/choices = beard_or_hair == "Hair" ? GLOB.hair_gradients_list : GLOB.facial_hair_gradients_list + var/list/choices = beard_or_hair == "Hair" ? SSaccessories.hair_gradients_list : SSaccessories.facial_hair_gradients_list var/new_grad_style = tgui_input_list(user, "Choose a color pattern", "Character Preference", choices) if(isnull(new_grad_style)) return diff --git a/code/game/objects/structures/dresser.dm b/code/game/objects/structures/dresser.dm index 4296ac3368443..cf3864b140059 100644 --- a/code/game/objects/structures/dresser.dm +++ b/code/game/objects/structures/dresser.dm @@ -40,7 +40,7 @@ return switch(choice) if("Underwear") - var/new_undies = tgui_input_list(user, "Select your underwear", "Changing", GLOB.underwear_list) + var/new_undies = tgui_input_list(user, "Select your underwear", "Changing", SSaccessories.underwear_list) if(new_undies) dressing_human.underwear = new_undies if("Underwear Color") @@ -48,11 +48,11 @@ if(new_underwear_color) dressing_human.underwear_color = sanitize_hexcolor(new_underwear_color) if("Undershirt") - var/new_undershirt = tgui_input_list(user, "Select your undershirt", "Changing", GLOB.undershirt_list) + var/new_undershirt = tgui_input_list(user, "Select your undershirt", "Changing", SSaccessories.undershirt_list) if(new_undershirt) dressing_human.undershirt = new_undershirt if("Socks") - var/new_socks = tgui_input_list(user, "Select your socks", "Changing", GLOB.socks_list) + var/new_socks = tgui_input_list(user, "Select your socks", "Changing", SSaccessories.socks_list) if(new_socks) dressing_human.socks = new_socks diff --git a/code/game/objects/structures/mannequin.dm b/code/game/objects/structures/mannequin.dm index bdb5344d7fe10..a47802273d5a0 100644 --- a/code/game/objects/structures/mannequin.dm +++ b/code/game/objects/structures/mannequin.dm @@ -95,19 +95,19 @@ var/mutable_appearance/pedestal = mutable_appearance(icon, "pedestal_[material]") pedestal.pixel_y = -3 . += pedestal - var/datum/sprite_accessory/underwear/underwear = GLOB.underwear_list[underwear_name] + var/datum/sprite_accessory/underwear/underwear = SSaccessories.underwear_list[underwear_name] if(underwear) if(body_type == FEMALE && underwear.gender == MALE) . += wear_female_version(underwear.icon_state, underwear.icon, BODY_LAYER, FEMALE_UNIFORM_FULL) else . += mutable_appearance(underwear.icon, underwear.icon_state, -BODY_LAYER) - var/datum/sprite_accessory/undershirt/undershirt = GLOB.undershirt_list[undershirt_name] + var/datum/sprite_accessory/undershirt/undershirt = SSaccessories.undershirt_list[undershirt_name] if(undershirt) if(body_type == FEMALE) . += wear_female_version(undershirt.icon_state, undershirt.icon, BODY_LAYER) else . += mutable_appearance(undershirt.icon, undershirt.icon_state, -BODY_LAYER) - var/datum/sprite_accessory/socks/socks = GLOB.socks_list[socks_name] + var/datum/sprite_accessory/socks/socks = SSaccessories.socks_list[socks_name] if(socks) . += mutable_appearance(socks.icon, socks.icon_state, -BODY_LAYER) for(var/slot_flag in worn_items) @@ -168,15 +168,15 @@ return switch(choice) if("Underwear") - var/new_undies = tgui_input_list(user, "Select the mannequin's underwear", "Changing", GLOB.underwear_list) + var/new_undies = tgui_input_list(user, "Select the mannequin's underwear", "Changing", SSaccessories.underwear_list) if(new_undies) underwear_name = new_undies if("Undershirt") - var/new_undershirt = tgui_input_list(user, "Select the mannequin's undershirt", "Changing", GLOB.undershirt_list) + var/new_undershirt = tgui_input_list(user, "Select the mannequin's undershirt", "Changing", SSaccessories.undershirt_list) if(new_undershirt) undershirt_name = new_undershirt if("Socks") - var/new_socks = tgui_input_list(user, "Select the mannequin's socks", "Changing", GLOB.socks_list) + var/new_socks = tgui_input_list(user, "Select the mannequin's socks", "Changing", SSaccessories.socks_list) if(new_socks) socks_name = new_socks update_appearance() diff --git a/code/game/objects/structures/mirror.dm b/code/game/objects/structures/mirror.dm index f15cabd707ec8..7ea2330281413 100644 --- a/code/game/objects/structures/mirror.dm +++ b/code/game/objects/structures/mirror.dm @@ -118,7 +118,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror/broken, 28) beard_dresser.set_facial_hairstyle("Shaved", update = TRUE) return TRUE - var/new_style = tgui_input_list(beard_dresser, "Select a facial hairstyle", "Grooming", GLOB.facial_hairstyles_list) + var/new_style = tgui_input_list(beard_dresser, "Select a facial hairstyle", "Grooming", SSaccessories.facial_hairstyles_list) if(isnull(new_style)) return TRUE @@ -131,7 +131,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror/broken, 28) beard_dresser.set_facial_hairstyle(new_style, update = TRUE) /obj/structure/mirror/proc/change_hair(mob/living/carbon/human/hairdresser) - var/new_style = tgui_input_list(hairdresser, "Select a hairstyle", "Grooming", GLOB.hairstyles_list) + var/new_style = tgui_input_list(hairdresser, "Select a hairstyle", "Grooming", SSaccessories.hairstyles_list) if(isnull(new_style)) return TRUE if(HAS_TRAIT(hairdresser, TRAIT_BALD)) @@ -331,7 +331,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror/broken, 28) selectable_races = sort_list(selectable_races) /obj/structure/mirror/magic/change_beard(mob/living/carbon/human/beard_dresser) // magical mirrors do nothing but give you the damn beard - var/new_style = tgui_input_list(beard_dresser, "Select a facial hairstyle", "Grooming", GLOB.facial_hairstyles_list) + var/new_style = tgui_input_list(beard_dresser, "Select a facial hairstyle", "Grooming", SSaccessories.facial_hairstyles_list) if(isnull(new_style)) return TRUE beard_dresser.set_facial_hairstyle(new_style, update = TRUE) diff --git a/code/modules/client/preferences/clothing.dm b/code/modules/client/preferences/clothing.dm index cf200ad1ffd65..002a7f8c13a33 100644 --- a/code/modules/client/preferences/clothing.dm +++ b/code/modules/client/preferences/clothing.dm @@ -95,7 +95,7 @@ should_generate_icons = TRUE /datum/preference/choiced/socks/init_possible_values() - return assoc_to_keys_features(GLOB.socks_list) + return assoc_to_keys_features(SSaccessories.socks_list) /datum/preference/choiced/socks/icon_for(value) var/static/icon/lower_half @@ -105,7 +105,7 @@ lower_half.Blend(icon('icons/mob/human/bodyparts_greyscale.dmi', "human_r_leg"), ICON_OVERLAY) lower_half.Blend(icon('icons/mob/human/bodyparts_greyscale.dmi', "human_l_leg"), ICON_OVERLAY) - return generate_underwear_icon(GLOB.socks_list[value], lower_half) + return generate_underwear_icon(SSaccessories.socks_list[value], lower_half) /datum/preference/choiced/socks/apply_to_human(mob/living/carbon/human/target, value) target.socks = value @@ -119,7 +119,7 @@ should_generate_icons = TRUE /datum/preference/choiced/undershirt/init_possible_values() - return assoc_to_keys_features(GLOB.undershirt_list) + return assoc_to_keys_features(SSaccessories.undershirt_list) /datum/preference/choiced/undershirt/icon_for(value) var/static/icon/body @@ -135,7 +135,7 @@ var/icon/icon_with_undershirt = icon(body) if (value != "Nude") - var/datum/sprite_accessory/accessory = GLOB.undershirt_list[value] + var/datum/sprite_accessory/accessory = SSaccessories.undershirt_list[value] icon_with_undershirt.Blend(icon('icons/mob/clothing/underwear.dmi', accessory.icon_state), ICON_OVERLAY) icon_with_undershirt.Crop(9, 9, 23, 23) @@ -154,7 +154,7 @@ should_generate_icons = TRUE /datum/preference/choiced/underwear/init_possible_values() - return assoc_to_keys_features(GLOB.underwear_list) + return assoc_to_keys_features(SSaccessories.underwear_list) /datum/preference/choiced/underwear/icon_for(value) var/static/icon/lower_half @@ -165,7 +165,7 @@ lower_half.Blend(icon('icons/mob/human/bodyparts_greyscale.dmi', "human_r_leg"), ICON_OVERLAY) lower_half.Blend(icon('icons/mob/human/bodyparts_greyscale.dmi', "human_l_leg"), ICON_OVERLAY) - return generate_underwear_icon(GLOB.underwear_list[value], lower_half, COLOR_ALMOST_BLACK) + return generate_underwear_icon(SSaccessories.underwear_list[value], lower_half, COLOR_ALMOST_BLACK) /datum/preference/choiced/underwear/apply_to_human(mob/living/carbon/human/target, value) target.underwear = value diff --git a/code/modules/client/preferences/species_features/basic.dm b/code/modules/client/preferences/species_features/basic.dm index abf4ea0e44e20..3f101ad9e44a5 100644 --- a/code/modules/client/preferences/species_features/basic.dm +++ b/code/modules/client/preferences/species_features/basic.dm @@ -7,7 +7,7 @@ var/icon/final_icon = new(head_icon) if (!isnull(sprite_accessory)) ASSERT(istype(sprite_accessory)) - + var/icon/head_accessory_icon = icon(sprite_accessory.icon, sprite_accessory.icon_state) if(y_offset) head_accessory_icon.Shift(NORTH, y_offset) @@ -61,10 +61,10 @@ relevant_head_flag = HEAD_FACIAL_HAIR /datum/preference/choiced/facial_hairstyle/init_possible_values() - return assoc_to_keys_features(GLOB.facial_hairstyles_list) + return assoc_to_keys_features(SSaccessories.facial_hairstyles_list) /datum/preference/choiced/facial_hairstyle/icon_for(value) - return generate_icon_with_head_accessory(GLOB.facial_hairstyles_list[value]) + return generate_icon_with_head_accessory(SSaccessories.facial_hairstyles_list[value]) /datum/preference/choiced/facial_hairstyle/apply_to_human(mob/living/carbon/human/target, value) target.set_facial_hairstyle(value, update = FALSE) @@ -94,7 +94,7 @@ relevant_head_flag = HEAD_FACIAL_HAIR /datum/preference/choiced/facial_hair_gradient/init_possible_values() - return assoc_to_keys_features(GLOB.facial_hair_gradients_list) + return assoc_to_keys_features(SSaccessories.facial_hair_gradients_list) /datum/preference/choiced/facial_hair_gradient/apply_to_human(mob/living/carbon/human/target, value) target.set_facial_hair_gradient_style(new_style = value, update = FALSE) @@ -137,10 +137,10 @@ relevant_head_flag = HEAD_HAIR /datum/preference/choiced/hairstyle/init_possible_values() - return assoc_to_keys_features(GLOB.hairstyles_list) + return assoc_to_keys_features(SSaccessories.hairstyles_list) /datum/preference/choiced/hairstyle/icon_for(value) - var/datum/sprite_accessory/hair/hairstyle = GLOB.hairstyles_list[value] + var/datum/sprite_accessory/hair/hairstyle = SSaccessories.hairstyles_list[value] return generate_icon_with_head_accessory(hairstyle, hairstyle?.y_offset) /datum/preference/choiced/hairstyle/apply_to_human(mob/living/carbon/human/target, value) @@ -161,7 +161,7 @@ relevant_head_flag = HEAD_HAIR /datum/preference/choiced/hair_gradient/init_possible_values() - return assoc_to_keys_features(GLOB.hair_gradients_list) + return assoc_to_keys_features(SSaccessories.hair_gradients_list) /datum/preference/choiced/hair_gradient/apply_to_human(mob/living/carbon/human/target, value) target.set_hair_gradient_style(new_style = value, update = FALSE) diff --git a/code/modules/client/preferences/species_features/felinid.dm b/code/modules/client/preferences/species_features/felinid.dm index b9f3c7bfa337b..a6d43736cf46c 100644 --- a/code/modules/client/preferences/species_features/felinid.dm +++ b/code/modules/client/preferences/species_features/felinid.dm @@ -6,7 +6,7 @@ relevant_external_organ = /obj/item/organ/external/tail/cat /datum/preference/choiced/tail_human/init_possible_values() - return assoc_to_keys_features(GLOB.tails_list_human) + return assoc_to_keys_features(SSaccessories.tails_list_human) /datum/preference/choiced/tail_human/apply_to_human(mob/living/carbon/human/target, value) target.dna.features["tail_cat"] = value @@ -23,7 +23,7 @@ relevant_mutant_bodypart = "ears" /datum/preference/choiced/ears/init_possible_values() - return assoc_to_keys_features(GLOB.ears_list) + return assoc_to_keys_features(SSaccessories.ears_list) /datum/preference/choiced/ears/apply_to_human(mob/living/carbon/human/target, value) target.dna.features["ears"] = value diff --git a/code/modules/client/preferences/species_features/lizard.dm b/code/modules/client/preferences/species_features/lizard.dm index 4ce09715483ad..bee57300ec4a4 100644 --- a/code/modules/client/preferences/species_features/lizard.dm +++ b/code/modules/client/preferences/species_features/lizard.dm @@ -32,10 +32,10 @@ relevant_mutant_bodypart = "body_markings" /datum/preference/choiced/lizard_body_markings/init_possible_values() - return assoc_to_keys_features(GLOB.body_markings_list) + return assoc_to_keys_features(SSaccessories.body_markings_list) /datum/preference/choiced/lizard_body_markings/icon_for(value) - var/datum/sprite_accessory/sprite_accessory = GLOB.body_markings_list[value] + var/datum/sprite_accessory/sprite_accessory = SSaccessories.body_markings_list[value] var/icon/final_icon = icon('icons/mob/human/species/lizard/bodyparts.dmi', "lizard_chest_m") @@ -65,10 +65,10 @@ should_generate_icons = TRUE /datum/preference/choiced/lizard_frills/init_possible_values() - return assoc_to_keys_features(GLOB.frills_list) + return assoc_to_keys_features(SSaccessories.frills_list) /datum/preference/choiced/lizard_frills/icon_for(value) - return generate_lizard_side_shot(GLOB.frills_list[value], "frills") + return generate_lizard_side_shot(SSaccessories.frills_list[value], "frills") /datum/preference/choiced/lizard_frills/apply_to_human(mob/living/carbon/human/target, value) target.dna.features["frills"] = value @@ -81,10 +81,10 @@ should_generate_icons = TRUE /datum/preference/choiced/lizard_horns/init_possible_values() - return assoc_to_keys_features(GLOB.horns_list) + return assoc_to_keys_features(SSaccessories.horns_list) /datum/preference/choiced/lizard_horns/icon_for(value) - return generate_lizard_side_shot(GLOB.horns_list[value], "horns") + return generate_lizard_side_shot(SSaccessories.horns_list[value], "horns") /datum/preference/choiced/lizard_horns/apply_to_human(mob/living/carbon/human/target, value) target.dna.features["horns"] = value @@ -96,7 +96,7 @@ relevant_mutant_bodypart = "legs" /datum/preference/choiced/lizard_legs/init_possible_values() - return assoc_to_keys_features(GLOB.legs_list) + return assoc_to_keys_features(SSaccessories.legs_list) /datum/preference/choiced/lizard_legs/apply_to_human(mob/living/carbon/human/target, value) target.dna.features["legs"] = value @@ -109,10 +109,10 @@ should_generate_icons = TRUE /datum/preference/choiced/lizard_snout/init_possible_values() - return assoc_to_keys_features(GLOB.snouts_list) + return assoc_to_keys_features(SSaccessories.snouts_list) /datum/preference/choiced/lizard_snout/icon_for(value) - return generate_lizard_side_shot(GLOB.snouts_list[value], "snout", include_snout = FALSE) + return generate_lizard_side_shot(SSaccessories.snouts_list[value], "snout", include_snout = FALSE) /datum/preference/choiced/lizard_snout/apply_to_human(mob/living/carbon/human/target, value) target.dna.features["snout"] = value @@ -124,7 +124,7 @@ relevant_mutant_bodypart = "spines" /datum/preference/choiced/lizard_spines/init_possible_values() - return assoc_to_keys_features(GLOB.spines_list) + return assoc_to_keys_features(SSaccessories.spines_list) /datum/preference/choiced/lizard_spines/apply_to_human(mob/living/carbon/human/target, value) target.dna.features["spines"] = value @@ -136,7 +136,7 @@ relevant_external_organ = /obj/item/organ/external/tail/lizard /datum/preference/choiced/lizard_tail/init_possible_values() - return assoc_to_keys_features(GLOB.tails_list_lizard) + return assoc_to_keys_features(SSaccessories.tails_list_lizard) /datum/preference/choiced/lizard_tail/apply_to_human(mob/living/carbon/human/target, value) target.dna.features["tail_lizard"] = value diff --git a/code/modules/client/preferences/species_features/monkey.dm b/code/modules/client/preferences/species_features/monkey.dm index b25d2cdfab8e1..adf9e367723de 100644 --- a/code/modules/client/preferences/species_features/monkey.dm +++ b/code/modules/client/preferences/species_features/monkey.dm @@ -5,7 +5,7 @@ relevant_external_organ = /obj/item/organ/external/tail/monkey /datum/preference/choiced/monkey_tail/init_possible_values() - return assoc_to_keys_features(GLOB.tails_list_monkey) + return assoc_to_keys_features(SSaccessories.tails_list_monkey) /datum/preference/choiced/monkey_tail/apply_to_human(mob/living/carbon/human/target, value) target.dna.features["tail_monkey"] = value diff --git a/code/modules/client/preferences/species_features/moth.dm b/code/modules/client/preferences/species_features/moth.dm index 120a7ea822bec..745e6fb917b8f 100644 --- a/code/modules/client/preferences/species_features/moth.dm +++ b/code/modules/client/preferences/species_features/moth.dm @@ -6,7 +6,7 @@ should_generate_icons = TRUE /datum/preference/choiced/moth_antennae/init_possible_values() - return assoc_to_keys_features(GLOB.moth_antennae_list) + return assoc_to_keys_features(SSaccessories.moth_antennae_list) /datum/preference/choiced/moth_antennae/icon_for(value) var/static/icon/moth_head @@ -16,7 +16,7 @@ moth_head.Blend(icon('icons/mob/human/human_face.dmi', "motheyes_l"), ICON_OVERLAY) moth_head.Blend(icon('icons/mob/human/human_face.dmi', "motheyes_r"), ICON_OVERLAY) - var/datum/sprite_accessory/antennae = GLOB.moth_antennae_list[value] + var/datum/sprite_accessory/antennae = SSaccessories.moth_antennae_list[value] var/icon/icon_with_antennae = new(moth_head) icon_with_antennae.Blend(icon(antennae.icon, "m_moth_antennae_[antennae.icon_state]_FRONT"), ICON_OVERLAY) @@ -37,7 +37,7 @@ relevant_mutant_bodypart = "moth_markings" /datum/preference/choiced/moth_markings/init_possible_values() - return assoc_to_keys_features(GLOB.moth_markings_list) + return assoc_to_keys_features(SSaccessories.moth_markings_list) /datum/preference/choiced/moth_markings/icon_for(value) var/static/list/body_parts = list( @@ -59,7 +59,7 @@ moth_body.Blend(icon('icons/mob/human/human_face.dmi', "motheyes_l"), ICON_OVERLAY) moth_body.Blend(icon('icons/mob/human/human_face.dmi', "motheyes_r"), ICON_OVERLAY) - var/datum/sprite_accessory/markings = GLOB.moth_markings_list[value] + var/datum/sprite_accessory/markings = SSaccessories.moth_markings_list[value] var/icon/icon_with_markings = new(moth_body) if (value != "None") @@ -88,10 +88,10 @@ should_generate_icons = TRUE /datum/preference/choiced/moth_wings/init_possible_values() - return assoc_to_keys_features(GLOB.moth_wings_list) + return assoc_to_keys_features(SSaccessories.moth_wings_list) /datum/preference/choiced/moth_wings/icon_for(value) - var/datum/sprite_accessory/moth_wings = GLOB.moth_wings_list[value] + var/datum/sprite_accessory/moth_wings = SSaccessories.moth_wings_list[value] var/icon/final_icon = icon(moth_wings.icon, "m_moth_wings_[moth_wings.icon_state]_BEHIND") final_icon.Blend(icon(moth_wings.icon, "m_moth_wings_[moth_wings.icon_state]_FRONT"), ICON_OVERLAY) return final_icon diff --git a/code/modules/client/preferences/species_features/mushperson.dm b/code/modules/client/preferences/species_features/mushperson.dm index f5e6a08ac92d2..45bd9c4b72620 100644 --- a/code/modules/client/preferences/species_features/mushperson.dm +++ b/code/modules/client/preferences/species_features/mushperson.dm @@ -5,7 +5,7 @@ relevant_mutant_bodypart = "cap" /datum/preference/choiced/mushroom_cap/init_possible_values() - return assoc_to_keys_features(GLOB.caps_list) + return assoc_to_keys_features(SSaccessories.caps_list) /datum/preference/choiced/mushroom_cap/apply_to_human(mob/living/carbon/human/target, value) target.dna.features["caps"] = value diff --git a/code/modules/client/preferences/species_features/pod.dm b/code/modules/client/preferences/species_features/pod.dm index 5280308fa8930..de3d5221f7a41 100644 --- a/code/modules/client/preferences/species_features/pod.dm +++ b/code/modules/client/preferences/species_features/pod.dm @@ -6,10 +6,10 @@ should_generate_icons = TRUE /datum/preference/choiced/pod_hair/init_possible_values() - return assoc_to_keys_features(GLOB.pod_hair_list) + return assoc_to_keys_features(SSaccessories.pod_hair_list) /datum/preference/choiced/pod_hair/icon_for(value) - var/datum/sprite_accessory/pod_hair = GLOB.pod_hair_list[value] + var/datum/sprite_accessory/pod_hair = SSaccessories.pod_hair_list[value] var/icon/icon_with_hair = icon('icons/mob/human/bodyparts_greyscale.dmi', "pod_head_m") @@ -24,7 +24,7 @@ return icon_with_hair /datum/preference/choiced/pod_hair/create_default_value() - return pick(assoc_to_keys_features(GLOB.pod_hair_list)) + return pick(assoc_to_keys_features(SSaccessories.pod_hair_list)) /datum/preference/choiced/pod_hair/apply_to_human(mob/living/carbon/human/target, value) target.dna.features["pod_hair"] = value diff --git a/code/modules/clothing/head/wig.dm b/code/modules/clothing/head/wig.dm index 5ab2bcad2144c..ecd70742ba889 100644 --- a/code/modules/clothing/head/wig.dm +++ b/code/modules/clothing/head/wig.dm @@ -25,7 +25,7 @@ item_flags &= ~EXAMINE_SKIP /obj/item/clothing/head/wig/update_icon_state() - var/datum/sprite_accessory/hair/hair_style = GLOB.hairstyles_list[hairstyle] + var/datum/sprite_accessory/hair/hair_style = SSaccessories.hairstyles_list[hairstyle] if(hair_style) icon = hair_style.icon icon_state = hair_style.icon_state @@ -36,7 +36,7 @@ if(isinhands) return - var/datum/sprite_accessory/hair/hair = GLOB.hairstyles_list[hairstyle] + var/datum/sprite_accessory/hair/hair = SSaccessories.hairstyles_list[hairstyle] if(!hair) return @@ -49,7 +49,7 @@ hair_overlay.overlays += emissive_blocker(hair_overlay.icon, hair_overlay.icon_state, src, alpha = hair_overlay.alpha) /obj/item/clothing/head/wig/attack_self(mob/user) - var/new_style = tgui_input_list(user, "Select a hairstyle", "Wig Styling", GLOB.hairstyles_list - "Bald") + var/new_style = tgui_input_list(user, "Select a hairstyle", "Wig Styling", SSaccessories.hairstyles_list - "Bald") var/newcolor = adjustablecolor ? input(usr,"","Choose Color",color) as color|null : null if(!user.can_perform_action(src)) return @@ -92,7 +92,7 @@ update_appearance() /obj/item/clothing/head/wig/random/Initialize(mapload) - hairstyle = pick(GLOB.hairstyles_list - "Bald") //Don't want invisible wig + hairstyle = pick(SSaccessories.hairstyles_list - "Bald") //Don't want invisible wig add_atom_colour("#[random_short_color()]", FIXED_COLOUR_PRIORITY) . = ..() @@ -104,7 +104,7 @@ custom_price = PAYCHECK_COMMAND /obj/item/clothing/head/wig/natural/Initialize(mapload) - hairstyle = pick(GLOB.hairstyles_list - "Bald") + hairstyle = pick(SSaccessories.hairstyles_list - "Bald") . = ..() /obj/item/clothing/head/wig/natural/visual_equipped(mob/living/carbon/human/user, slot) diff --git a/code/modules/food_and_drinks/restaurant/customers/_customer.dm b/code/modules/food_and_drinks/restaurant/customers/_customer.dm index 47bb843d755c7..15e4659338d0c 100644 --- a/code/modules/food_and_drinks/restaurant/customers/_customer.dm +++ b/code/modules/food_and_drinks/restaurant/customers/_customer.dm @@ -306,7 +306,7 @@ /datum/customer_data/moth/proc/get_wings(mob/living/basic/robot_customer/customer) var/customer_ref = WEAKREF(customer) if (!LAZYACCESS(wings_chosen, customer_ref)) - LAZYSET(wings_chosen, customer_ref, GLOB.moth_wings_list[pick(GLOB.moth_wings_list)]) + LAZYSET(wings_chosen, customer_ref, SSaccessories.moth_wings_list[pick(SSaccessories.moth_wings_list)]) return wings_chosen[customer_ref] /datum/customer_data/moth/get_underlays(mob/living/basic/robot_customer/customer) diff --git a/code/modules/hydroponics/grown/replicapod.dm b/code/modules/hydroponics/grown/replicapod.dm index c4e3b27673ae4..31c0b7f81df64 100644 --- a/code/modules/hydroponics/grown/replicapod.dm +++ b/code/modules/hydroponics/grown/replicapod.dm @@ -197,7 +197,7 @@ if(!features["mcolor"]) features["mcolor"] = "#59CE00" if(!features["pod_hair"]) - features["pod_hair"] = pick(GLOB.pod_hair_list) + features["pod_hair"] = pick(SSaccessories.pod_hair_list) for(var/V in quirks) new V(podman) diff --git a/code/modules/mining/lavaland/tendril_loot.dm b/code/modules/mining/lavaland/tendril_loot.dm index c8d0f5580d724..8a90f0e548c30 100644 --- a/code/modules/mining/lavaland/tendril_loot.dm +++ b/code/modules/mining/lavaland/tendril_loot.dm @@ -566,7 +566,7 @@ var/list/name2type = list() for(var/obj/item/organ/external/wings/functional/possible_type as anything in wing_types) var/datum/sprite_accessory/accessory = initial(possible_type.sprite_accessory_override) //get the type - accessory = GLOB.wings_list[initial(accessory.name)] //get the singleton instance + accessory = SSaccessories.wings_list[initial(accessory.name)] //get the singleton instance var/image/img = image(icon = accessory.icon, icon_state = "m_wingsopen_[accessory.icon_state]_BEHIND") //Process the HUD elements img.transform *= 0.5 img.pixel_x = -32 diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index 18b55cd6a5f07..f7e4b56cab274 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -221,7 +221,7 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER) if(ghost_accs == GHOST_ACCS_FULL && (icon_state in GLOB.ghost_forms_with_accessories_list)) //check if this form supports accessories and if the client wants to show them if(facial_hairstyle) - var/datum/sprite_accessory/S = GLOB.facial_hairstyles_list[facial_hairstyle] + var/datum/sprite_accessory/S = SSaccessories.facial_hairstyles_list[facial_hairstyle] if(S) facial_hair_overlay = mutable_appearance(S.icon, "[S.icon_state]", -HAIR_LAYER) if(facial_hair_color) @@ -229,7 +229,7 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER) facial_hair_overlay.alpha = 200 add_overlay(facial_hair_overlay) if(hairstyle) - var/datum/sprite_accessory/hair/S = GLOB.hairstyles_list[hairstyle] + var/datum/sprite_accessory/hair/S = SSaccessories.hairstyles_list[hairstyle] if(S) hair_overlay = mutable_appearance(S.icon, "[S.icon_state]", -HAIR_LAYER) if(hair_color) diff --git a/code/modules/mob/living/basic/space_fauna/ghost.dm b/code/modules/mob/living/basic/space_fauna/ghost.dm index 7545f9cfea394..728c5ead9f4a8 100644 --- a/code/modules/mob/living/basic/space_fauna/ghost.dm +++ b/code/modules/mob/living/basic/space_fauna/ghost.dm @@ -71,7 +71,7 @@ ghost_facial_hair_color = ghost_hair_color if(!isnull(ghost_hairstyle) && ghost_hairstyle != "Bald") //Bald hairstyle and the Shaved facial hairstyle lack an associated sprite and will not properly generate hair, and just cause runtimes. - var/datum/sprite_accessory/hair/hair_style = GLOB.hairstyles_list[ghost_hairstyle] //We use the hairstyle name to get the sprite accessory, which we copy the icon_state from. + var/datum/sprite_accessory/hair/hair_style = SSaccessories.hairstyles_list[ghost_hairstyle] //We use the hairstyle name to get the sprite accessory, which we copy the icon_state from. ghost_hair = mutable_appearance('icons/mob/human/human_face.dmi', "[hair_style.icon_state]", -HAIR_LAYER) ghost_hair.alpha = 200 ghost_hair.color = ghost_hair_color @@ -79,7 +79,7 @@ add_overlay(ghost_hair) if(!isnull(ghost_facial_hairstyle) && ghost_facial_hairstyle != "Shaved") - var/datum/sprite_accessory/facial_hair_style = GLOB.facial_hairstyles_list[ghost_facial_hairstyle] + var/datum/sprite_accessory/facial_hair_style = SSaccessories.facial_hairstyles_list[ghost_facial_hairstyle] ghost_facial_hair = mutable_appearance('icons/mob/human/human_face.dmi', "[facial_hair_style.icon_state]", -HAIR_LAYER) ghost_facial_hair.alpha = 200 ghost_facial_hair.color = ghost_facial_hair_color diff --git a/code/modules/mob/living/carbon/human/_species.dm b/code/modules/mob/living/carbon/human/_species.dm index 26b0b1b8c223f..adfe93d318dba 100644 --- a/code/modules/mob/living/carbon/human/_species.dm +++ b/code/modules/mob/living/carbon/human/_species.dm @@ -595,8 +595,9 @@ GLOBAL_LIST_EMPTY(features_by_species) var/obj/item/bodypart/arm/left/left_arm = species_human.get_bodypart(BODY_ZONE_L_ARM) var/obj/item/bodypart/leg/right/right_leg = species_human.get_bodypart(BODY_ZONE_R_LEG) var/obj/item/bodypart/leg/left/left_leg = species_human.get_bodypart(BODY_ZONE_L_LEG) - var/datum/sprite_accessory/markings = GLOB.moth_markings_list[species_human.dna.features["moth_markings"]] + var/datum/sprite_accessory/markings = SSaccessories.moth_markings_list[species_human.dna.features["moth_markings"]] var/mutable_appearance/marking = mutable_appearance(layer = -BODY_LAYER, appearance_flags = KEEP_TOGETHER) + if(noggin && (IS_ORGANIC_LIMB(noggin))) var/mutable_appearance/markings_head_overlay = mutable_appearance(markings.icon, "[markings.icon_state]_head") marking.overlays += markings_head_overlay @@ -626,7 +627,7 @@ GLOBAL_LIST_EMPTY(features_by_species) //Underwear, Undershirts & Socks if(!HAS_TRAIT(species_human, TRAIT_NO_UNDERWEAR)) if(species_human.underwear) - var/datum/sprite_accessory/underwear/underwear = GLOB.underwear_list[species_human.underwear] + var/datum/sprite_accessory/underwear/underwear = SSaccessories.underwear_list[species_human.underwear] var/mutable_appearance/underwear_overlay if(underwear) if(species_human.dna.species.sexes && species_human.physique == FEMALE && (underwear.gender == MALE)) @@ -638,7 +639,7 @@ GLOBAL_LIST_EMPTY(features_by_species) standing += underwear_overlay if(species_human.undershirt) - var/datum/sprite_accessory/undershirt/undershirt = GLOB.undershirt_list[species_human.undershirt] + var/datum/sprite_accessory/undershirt/undershirt = SSaccessories.undershirt_list[species_human.undershirt] if(undershirt) var/mutable_appearance/working_shirt if(species_human.dna.species.sexes && species_human.physique == FEMALE) @@ -648,7 +649,7 @@ GLOBAL_LIST_EMPTY(features_by_species) standing += working_shirt if(species_human.socks && species_human.num_legs >= 2 && !(species_human.bodyshape & BODYSHAPE_DIGITIGRADE)) - var/datum/sprite_accessory/socks/socks = GLOB.socks_list[species_human.socks] + var/datum/sprite_accessory/socks/socks = SSaccessories.socks_list[species_human.socks] if(socks) standing += mutable_appearance(socks.icon, socks.icon_state, -BODY_LAYER) @@ -698,11 +699,11 @@ GLOBAL_LIST_EMPTY(features_by_species) var/datum/sprite_accessory/accessory switch(bodypart) if("ears") - accessory = GLOB.ears_list[source.dna.features["ears"]] + accessory = SSaccessories.ears_list[source.dna.features["ears"]] if("body_markings") - accessory = GLOB.body_markings_list[source.dna.features["body_markings"]] + accessory = SSaccessories.body_markings_list[source.dna.features["body_markings"]] if("legs") - accessory = GLOB.legs_list[source.dna.features["legs"]] + accessory = SSaccessories.legs_list[source.dna.features["legs"]] if(!accessory || accessory.icon_state == "none") continue diff --git a/code/modules/mob/living/carbon/human/dummy.dm b/code/modules/mob/living/carbon/human/dummy.dm index d17661cf5629e..627745cba929e 100644 --- a/code/modules/mob/living/carbon/human/dummy.dm +++ b/code/modules/mob/living/carbon/human/dummy.dm @@ -103,19 +103,19 @@ INITIALIZE_IMMEDIATE(/mob/living/carbon/human/dummy) /proc/create_consistent_human_dna(mob/living/carbon/human/target) target.dna.features["mcolor"] = COLOR_VIBRANT_LIME target.dna.features["ethcolor"] = COLOR_WHITE - target.dna.features["body_markings"] = get_consistent_feature_entry(GLOB.body_markings_list) - target.dna.features["ears"] = get_consistent_feature_entry(GLOB.ears_list) - target.dna.features["frills"] = get_consistent_feature_entry(GLOB.frills_list) - target.dna.features["horns"] = get_consistent_feature_entry(GLOB.horns_list) - target.dna.features["moth_antennae"] = get_consistent_feature_entry(GLOB.moth_antennae_list) - target.dna.features["moth_markings"] = get_consistent_feature_entry(GLOB.moth_markings_list) - target.dna.features["moth_wings"] = get_consistent_feature_entry(GLOB.moth_wings_list) - target.dna.features["snout"] = get_consistent_feature_entry(GLOB.snouts_list) - target.dna.features["spines"] = get_consistent_feature_entry(GLOB.spines_list) - target.dna.features["tail_cat"] = get_consistent_feature_entry(GLOB.tails_list_human) // it's a lie - target.dna.features["tail_lizard"] = get_consistent_feature_entry(GLOB.tails_list_lizard) - target.dna.features["tail_monkey"] = get_consistent_feature_entry(GLOB.tails_list_monkey) - target.dna.features["pod_hair"] = get_consistent_feature_entry(GLOB.pod_hair_list) + target.dna.features["body_markings"] = get_consistent_feature_entry(SSaccessories.body_markings_list) + target.dna.features["ears"] = get_consistent_feature_entry(SSaccessories.ears_list) + target.dna.features["frills"] = get_consistent_feature_entry(SSaccessories.frills_list) + target.dna.features["horns"] = get_consistent_feature_entry(SSaccessories.horns_list) + target.dna.features["moth_antennae"] = get_consistent_feature_entry(SSaccessories.moth_antennae_list) + target.dna.features["moth_markings"] = get_consistent_feature_entry(SSaccessories.moth_markings_list) + target.dna.features["moth_wings"] = get_consistent_feature_entry(SSaccessories.moth_wings_list) + target.dna.features["snout"] = get_consistent_feature_entry(SSaccessories.snouts_list) + target.dna.features["spines"] = get_consistent_feature_entry(SSaccessories.spines_list) + target.dna.features["tail_cat"] = get_consistent_feature_entry(SSaccessories.tails_list_human) // it's a lie + target.dna.features["tail_lizard"] = get_consistent_feature_entry(SSaccessories.tails_list_lizard) + target.dna.features["tail_monkey"] = get_consistent_feature_entry(SSaccessories.tails_list_monkey) + target.dna.features["pod_hair"] = get_consistent_feature_entry(SSaccessories.pod_hair_list) target.dna.initialize_dna(create_mutation_blocks = FALSE, randomize_features = FALSE) // UF and UI are nondeterministic, even though the features are the same some blocks will randomize slightly // In practice this doesn't matter, but this is for the sake of 100%(ish) consistency diff --git a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm index 93167cb689c3e..1b478162de4c6 100644 --- a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm @@ -61,7 +61,7 @@ /datum/species/lizard/randomize_features() var/list/features = ..() - features["body_markings"] = pick(GLOB.body_markings_list) + features["body_markings"] = pick(SSaccessories.body_markings_list) return features /datum/species/lizard/get_scream_sound(mob/living/carbon/human/lizard) diff --git a/code/modules/mob/living/carbon/human/species_types/mothmen.dm b/code/modules/mob/living/carbon/human/species_types/mothmen.dm index 86a9180ed07e3..e52a19b587b9a 100644 --- a/code/modules/mob/living/carbon/human/species_types/mothmen.dm +++ b/code/modules/mob/living/carbon/human/species_types/mothmen.dm @@ -61,7 +61,7 @@ /datum/species/moth/randomize_features() var/list/features = ..() - features["moth_markings"] = pick(GLOB.moth_markings_list) + features["moth_markings"] = pick(SSaccessories.moth_markings_list) return features /datum/species/moth/get_scream_sound(mob/living/carbon/human) diff --git a/code/modules/mob/living/carbon/human/species_types/mushpeople.dm b/code/modules/mob/living/carbon/human/species_types/mushpeople.dm index f3486f1342f68..14d6c1437f0da 100644 --- a/code/modules/mob/living/carbon/human/species_types/mushpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/mushpeople.dm @@ -83,7 +83,7 @@ feature_key = "caps" /datum/bodypart_overlay/mutant/mushroom_cap/get_global_feature_list() - return GLOB.caps_list + return SSaccessories.caps_list /datum/bodypart_overlay/mutant/mushroom_cap/can_draw_on_bodypart(mob/living/carbon/human/human) if((human.head?.flags_inv & HIDEHAIR) || (human.wear_mask?.flags_inv & HIDEHAIR)) diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm index 991cd5d3098bd..9859617ce4271 100644 --- a/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -2201,10 +2201,10 @@ var/mob/living/carbon/human/exposed_human = exposed_mob if(!HAS_TRAIT(exposed_human, TRAIT_SHAVED)) - var/datum/sprite_accessory/facial_hair/picked_beard = pick(GLOB.facial_hairstyles_list) + var/datum/sprite_accessory/facial_hair/picked_beard = pick(SSaccessories.facial_hairstyles_list) exposed_human.set_facial_hairstyle(picked_beard, update = FALSE) if(!HAS_TRAIT(exposed_human, TRAIT_BALD)) - var/datum/sprite_accessory/hair/picked_hair = pick(GLOB.hairstyles_list) + var/datum/sprite_accessory/hair/picked_hair = pick(SSaccessories.hairstyles_list) exposed_human.set_hairstyle(picked_hair, update = TRUE) to_chat(exposed_human, span_notice("Hair starts sprouting from your [HAS_TRAIT(exposed_human, TRAIT_BALD) ? "face" : "scalp"].")) diff --git a/code/modules/surgery/bodyparts/head_hair_and_lips.dm b/code/modules/surgery/bodyparts/head_hair_and_lips.dm index aa0196b187a54..453bd79ee22e9 100644 --- a/code/modules/surgery/bodyparts/head_hair_and_lips.dm +++ b/code/modules/surgery/bodyparts/head_hair_and_lips.dm @@ -84,7 +84,7 @@ var/image/facial_hair_overlay if(!facial_hair_hidden && facial_hairstyle && (head_flags & HEAD_FACIAL_HAIR)) - sprite_accessory = GLOB.facial_hairstyles_list[facial_hairstyle] + sprite_accessory = SSaccessories.facial_hairstyles_list[facial_hairstyle] if(sprite_accessory) //Overlay facial_hair_overlay = image(sprite_accessory.icon, sprite_accessory.icon_state, -HAIR_LAYER, image_dir) @@ -99,12 +99,12 @@ var/facial_hair_gradient_style = LAZYACCESS(gradient_styles, GRADIENT_FACIAL_HAIR_KEY) if(facial_hair_gradient_style) var/facial_hair_gradient_color = LAZYACCESS(gradient_colors, GRADIENT_FACIAL_HAIR_KEY) - var/image/facial_hair_gradient_overlay = get_gradient_overlay(sprite_accessory.icon, sprite_accessory.icon_state, -HAIR_LAYER, GLOB.facial_hair_gradients_list[facial_hair_gradient_style], facial_hair_gradient_color, image_dir) + var/image/facial_hair_gradient_overlay = get_gradient_overlay(sprite_accessory.icon, sprite_accessory.icon_state, -HAIR_LAYER, SSaccessories.facial_hair_gradients_list[facial_hair_gradient_style], facial_hair_gradient_color, image_dir) . += facial_hair_gradient_overlay var/image/hair_overlay if(!(show_debrained && (head_flags & HEAD_DEBRAIN)) && !hair_hidden && hairstyle && (head_flags & HEAD_HAIR)) - var/datum/sprite_accessory/hair/hair_sprite_accessory = GLOB.hairstyles_list[hairstyle] + var/datum/sprite_accessory/hair/hair_sprite_accessory = SSaccessories.hairstyles_list[hairstyle] if(hair_sprite_accessory) //Overlay hair_overlay = image(hair_sprite_accessory.icon, hair_sprite_accessory.icon_state, -HAIR_LAYER, image_dir) @@ -120,7 +120,7 @@ var/hair_gradient_style = LAZYACCESS(gradient_styles, GRADIENT_HAIR_KEY) if(hair_gradient_style) var/hair_gradient_color = LAZYACCESS(gradient_colors, GRADIENT_HAIR_KEY) - var/image/hair_gradient_overlay = get_gradient_overlay(hair_sprite_accessory.icon, hair_sprite_accessory.icon_state, -HAIR_LAYER, GLOB.hair_gradients_list[hair_gradient_style], hair_gradient_color, image_dir) + var/image/hair_gradient_overlay = get_gradient_overlay(hair_sprite_accessory.icon, hair_sprite_accessory.icon_state, -HAIR_LAYER, SSaccessories.hair_gradients_list[hair_gradient_style], hair_gradient_color, image_dir) hair_gradient_overlay.pixel_y = hair_sprite_accessory.y_offset . += hair_gradient_overlay diff --git a/code/modules/surgery/organs/external/_external_organ.dm b/code/modules/surgery/organs/external/_external_organ.dm index b58f08fe97d7e..a054bc741e632 100644 --- a/code/modules/surgery/organs/external/_external_organ.dm +++ b/code/modules/surgery/organs/external/_external_organ.dm @@ -182,7 +182,7 @@ return TRUE /datum/bodypart_overlay/mutant/horns/get_global_feature_list() - return GLOB.horns_list + return SSaccessories.horns_list ///The frills of a lizard (like weird fin ears) /obj/item/organ/external/frills @@ -209,7 +209,7 @@ return FALSE /datum/bodypart_overlay/mutant/frills/get_global_feature_list() - return GLOB.frills_list + return SSaccessories.frills_list ///Guess what part of the lizard this is? /obj/item/organ/external/snout @@ -238,7 +238,7 @@ return FALSE /datum/bodypart_overlay/mutant/snout/get_global_feature_list() - return GLOB.snouts_list + return SSaccessories.snouts_list ///A moth's antennae /obj/item/organ/external/antennae @@ -315,7 +315,7 @@ burn_datum = fetch_sprite_datum(burn_datum) //turn the path into the singleton instance /datum/bodypart_overlay/mutant/antennae/get_global_feature_list() - return GLOB.moth_antennae_list + return SSaccessories.moth_antennae_list /datum/bodypart_overlay/mutant/antennae/get_base_icon_state() return burnt ? burn_datum.icon_state : sprite_datum.icon_state @@ -347,7 +347,7 @@ var/color_inverse_base = 255 /datum/bodypart_overlay/mutant/pod_hair/get_global_feature_list() - return GLOB.pod_hair_list + return SSaccessories.pod_hair_list /datum/bodypart_overlay/mutant/pod_hair/color_image(image/overlay, draw_layer, obj/item/bodypart/limb) if(draw_layer != bitflag_to_layer(color_swapped_layer)) diff --git a/code/modules/surgery/organs/external/spines.dm b/code/modules/surgery/organs/external/spines.dm index 743b7fa8d47f2..86bff9a768939 100644 --- a/code/modules/surgery/organs/external/spines.dm +++ b/code/modules/surgery/organs/external/spines.dm @@ -32,7 +32,7 @@ feature_key = "spines" /datum/bodypart_overlay/mutant/spines/get_global_feature_list() - return GLOB.spines_list + return SSaccessories.spines_list /datum/bodypart_overlay/mutant/spines/can_draw_on_bodypart(mob/living/carbon/human/human) . = ..() diff --git a/code/modules/surgery/organs/external/tails.dm b/code/modules/surgery/organs/external/tails.dm index ab71e3ac9f35f..38b35bce45cb3 100644 --- a/code/modules/surgery/organs/external/tails.dm +++ b/code/modules/surgery/organs/external/tails.dm @@ -151,7 +151,7 @@ wag_flags = WAG_ABLE /datum/bodypart_overlay/mutant/tail/get_global_feature_list() - return GLOB.tails_list_human + return SSaccessories.tails_list_human /obj/item/organ/external/tail/cat/get_butt_sprite() return BUTT_SPRITE_CAT @@ -175,7 +175,7 @@ feature_key = "tail_monkey" /datum/bodypart_overlay/mutant/tail/monkey/get_global_feature_list() - return GLOB.tails_list_monkey + return SSaccessories.tails_list_monkey /obj/item/organ/external/tail/lizard name = "lizard tail" @@ -192,7 +192,7 @@ feature_key = "tail_lizard" /datum/bodypart_overlay/mutant/tail/lizard/get_global_feature_list() - return GLOB.tails_list_lizard + return SSaccessories.tails_list_lizard /obj/item/organ/external/tail/lizard/fake name = "fabricated lizard tail" @@ -208,7 +208,7 @@ var/tail_spine_key = NONE /datum/bodypart_overlay/mutant/tail_spines/get_global_feature_list() - return GLOB.tail_spines_list + return SSaccessories.tail_spines_list /datum/bodypart_overlay/mutant/tail_spines/get_base_icon_state() return (!isnull(tail_spine_key) ? "[tail_spine_key]_" : "") + (wagging ? "wagging_" : "") + sprite_datum.icon_state // Select the wagging state if appropriate diff --git a/code/modules/surgery/organs/external/wings/functional_wings.dm b/code/modules/surgery/organs/external/wings/functional_wings.dm index aacf6f08f6a5c..5f2851b467635 100644 --- a/code/modules/surgery/organs/external/wings/functional_wings.dm +++ b/code/modules/surgery/organs/external/wings/functional_wings.dm @@ -139,9 +139,9 @@ /datum/bodypart_overlay/mutant/wings/functional/get_global_feature_list() if(wings_open) - return GLOB.wings_open_list + return SSaccessories.wings_open_list else - return GLOB.wings_list + return SSaccessories.wings_list ///Update our wingsprite to the open wings variant /datum/bodypart_overlay/mutant/wings/functional/proc/open_wings() diff --git a/code/modules/surgery/organs/external/wings/moth_wings.dm b/code/modules/surgery/organs/external/wings/moth_wings.dm index f4e0f156e703e..11aebf4e8f1b5 100644 --- a/code/modules/surgery/organs/external/wings/moth_wings.dm +++ b/code/modules/surgery/organs/external/wings/moth_wings.dm @@ -84,7 +84,7 @@ burn_datum = fetch_sprite_datum(burn_datum) /datum/bodypart_overlay/mutant/wings/moth/get_global_feature_list() - return GLOB.moth_wings_list + return SSaccessories.moth_wings_list /datum/bodypart_overlay/mutant/wings/moth/can_draw_on_bodypart(mob/living/carbon/human/human) if(!(human.wear_suit?.flags_inv & HIDEMUTWINGS)) diff --git a/tgstation.dme b/tgstation.dme index 3e6731b180179..7e6bf667f0c47 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -690,6 +690,7 @@ #include "code\controllers\subsystem\sounds.dm" #include "code\controllers\subsystem\spatial_gridmap.dm" #include "code\controllers\subsystem\speech_controller.dm" +#include "code\controllers\subsystem\sprite_accessories.dm" #include "code\controllers\subsystem\statpanel.dm" #include "code\controllers\subsystem\stickyban.dm" #include "code\controllers\subsystem\stock_market.dm" From d6b7f5c0191b99c697c1d18213a497d2c9fac346 Mon Sep 17 00:00:00 2001 From: Changelogs Date: Thu, 2 May 2024 00:20:33 +0000 Subject: [PATCH 26/87] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-82857.yml | 4 --- html/changelogs/AutoChangeLog-pr-82936.yml | 4 --- html/changelogs/AutoChangeLog-pr-82939.yml | 5 ---- html/changelogs/AutoChangeLog-pr-82963.yml | 4 --- html/changelogs/AutoChangeLog-pr-82966.yml | 4 --- html/changelogs/AutoChangeLog-pr-82967.yml | 4 --- html/changelogs/AutoChangeLog-pr-82968.yml | 5 ---- html/changelogs/AutoChangeLog-pr-82975.yml | 4 --- html/changelogs/AutoChangeLog-pr-82976.yml | 4 --- html/changelogs/AutoChangeLog-pr-82977.yml | 4 --- html/changelogs/AutoChangeLog-pr-82981.yml | 7 ----- html/changelogs/AutoChangeLog-pr-82986.yml | 4 --- html/changelogs/AutoChangeLog-pr-82987.yml | 5 ---- html/changelogs/archive/2024-05.yml | 35 ++++++++++++++++++++++ 14 files changed, 35 insertions(+), 58 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-82857.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-82936.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-82939.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-82963.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-82966.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-82967.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-82968.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-82975.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-82976.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-82977.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-82981.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-82986.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-82987.yml diff --git a/html/changelogs/AutoChangeLog-pr-82857.yml b/html/changelogs/AutoChangeLog-pr-82857.yml deleted file mode 100644 index 8ea50a3077d60..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-82857.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Echriser" -delete-after: True -changes: - - qol: "conveyor switches now have direction-specific input signals" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-82936.yml b/html/changelogs/AutoChangeLog-pr-82936.yml deleted file mode 100644 index f8ea2d4556cd1..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-82936.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Watermelon914" -delete-after: True -changes: - - bugfix: "Fixed the ipintel subsystem not working." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-82939.yml b/html/changelogs/AutoChangeLog-pr-82939.yml deleted file mode 100644 index 291878922ab75..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-82939.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "JohnFulpWillard" -delete-after: True -changes: - - bugfix: "You can now re-construct pod doors that had deconstruction started." - - qol: "You can now make pod door assemblies using plasteel in-hand." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-82963.yml b/html/changelogs/AutoChangeLog-pr-82963.yml deleted file mode 100644 index bdbe508a6a04b..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-82963.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "JohnFulpWillard" -delete-after: True -changes: - - bugfix: "Monkeys can no longer be knocked over by walking into a windoor on combat mode while they're behind it." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-82966.yml b/html/changelogs/AutoChangeLog-pr-82966.yml deleted file mode 100644 index 8602d867b5faa..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-82966.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "JohnFulpWillard" -delete-after: True -changes: - - bugfix: "Monkey changelings that are disguised as someone can now take off their flesh clothes." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-82967.yml b/html/changelogs/AutoChangeLog-pr-82967.yml deleted file mode 100644 index c068527e6cb51..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-82967.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "JohnFulpWillard" -delete-after: True -changes: - - admin: "Debug uplinks now shows all categories and won't lock upon buying items like his grace and the syndicate balloon." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-82968.yml b/html/changelogs/AutoChangeLog-pr-82968.yml deleted file mode 100644 index 986653310c815..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-82968.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Fluffles" -delete-after: True -changes: - - qol: "you can pick up wheelchairs while on the ground" - - qol: "you can place wheelchairs a tile away from you, like roller beds" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-82975.yml b/html/changelogs/AutoChangeLog-pr-82975.yml deleted file mode 100644 index d563b5306d215..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-82975.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "JohnFulpWillard" -delete-after: True -changes: - - bugfix: "AIs can now track Silicon as long as their built-in camera is online." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-82976.yml b/html/changelogs/AutoChangeLog-pr-82976.yml deleted file mode 100644 index 3005ebe9b06ca..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-82976.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "JohnFulpWillard" -delete-after: True -changes: - - bugfix: "Constantly fleeing in Battle Arcade will no longer give you a very large amount of decimals due to halving your gold every time." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-82977.yml b/html/changelogs/AutoChangeLog-pr-82977.yml deleted file mode 100644 index 3bdf3f73bd6a1..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-82977.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Pickle-Coding" -delete-after: True -changes: - - bugfix: "Fixes hypercharged slime core cells and circuit guns having 1,000 times less energy than intended." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-82981.yml b/html/changelogs/AutoChangeLog-pr-82981.yml deleted file mode 100644 index b19efb8134b65..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-82981.yml +++ /dev/null @@ -1,7 +0,0 @@ -author: "Melbert" -delete-after: True -changes: - - admin: "Custom Single and Custom Multi votes are now combined into one vote" - - admin: "Admins can now end votes instantly, rather than cancelling them" - - admin: "Admins can now reset the vote cooldown" - - bugfix: "Vote cooldown actually applies now" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-82986.yml b/html/changelogs/AutoChangeLog-pr-82986.yml deleted file mode 100644 index eecba8d4bafc0..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-82986.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "nikothedude" -delete-after: True -changes: - - bugfix: "Jousting no longer bypasses pacifism" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-82987.yml b/html/changelogs/AutoChangeLog-pr-82987.yml deleted file mode 100644 index 39e5d297ce3d6..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-82987.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "kawoppi" -delete-after: True -changes: - - bugfix: "the civilian bounty control terminal displays the payout of new bounty options again" - - qol: "the civilian bounty control terminal displays the description of new bounty options" \ No newline at end of file diff --git a/html/changelogs/archive/2024-05.yml b/html/changelogs/archive/2024-05.yml index ae546e168c13f..90ccdc1199d2a 100644 --- a/html/changelogs/archive/2024-05.yml +++ b/html/changelogs/archive/2024-05.yml @@ -39,3 +39,38 @@ necromanceranne: - bugfix: If you are a freerunner, you don't end up faceplanting despite having catlike grace. +2024-05-02: + Echriser: + - qol: conveyor switches now have direction-specific input signals + Fluffles: + - qol: you can pick up wheelchairs while on the ground + - qol: you can place wheelchairs a tile away from you, like roller beds + JohnFulpWillard: + - bugfix: Monkey changelings that are disguised as someone can now take off their + flesh clothes. + - bugfix: You can now re-construct pod doors that had deconstruction started. + - qol: You can now make pod door assemblies using plasteel in-hand. + - bugfix: Monkeys can no longer be knocked over by walking into a windoor on combat + mode while they're behind it. + - admin: Debug uplinks now shows all categories and won't lock upon buying items + like his grace and the syndicate balloon. + - bugfix: Constantly fleeing in Battle Arcade will no longer give you a very large + amount of decimals due to halving your gold every time. + - bugfix: AIs can now track Silicon as long as their built-in camera is online. + Melbert: + - admin: Custom Single and Custom Multi votes are now combined into one vote + - admin: Admins can now end votes instantly, rather than cancelling them + - admin: Admins can now reset the vote cooldown + - bugfix: Vote cooldown actually applies now + Pickle-Coding: + - bugfix: Fixes hypercharged slime core cells and circuit guns having 1,000 times + less energy than intended. + Watermelon914: + - bugfix: Fixed the ipintel subsystem not working. + kawoppi: + - bugfix: the civilian bounty control terminal displays the payout of new bounty + options again + - qol: the civilian bounty control terminal displays the description of new bounty + options + nikothedude: + - bugfix: Jousting no longer bypasses pacifism From 9d2c02e691bc5a0f2f65b2971ac3923b52acfafa Mon Sep 17 00:00:00 2001 From: Gaxeer <44334376+Gaxeer@users.noreply.github.com> Date: Thu, 2 May 2024 04:14:35 +0300 Subject: [PATCH 27/87] make SSevents `frequeny_lower` and `frequency_upper` configurable, fix runtimes when no events were drafted to trigger (#82978) ## About The Pull Request Make events frequency configurable. Fix runtime when no events were drafted to be picked from. ## Why It's Good For The Game No runtimes good. No need to change frequency in code. :cl: fix: fix runtime when no events were drafted to be picked from config: make events frequency configurable /:cl: --------- Co-authored-by: san7890 --- code/__HELPERS/_lists.dm | 18 ++++++++++++------ .../configuration/entries/game_options.dm | 10 ++++++++++ code/controllers/subsystem/events.dm | 11 ++++++++--- config/game_options.txt | 7 +++++++ 4 files changed, 37 insertions(+), 9 deletions(-) diff --git a/code/__HELPERS/_lists.dm b/code/__HELPERS/_lists.dm index 7af30c8d9727c..6811a31284aa4 100644 --- a/code/__HELPERS/_lists.dm +++ b/code/__HELPERS/_lists.dm @@ -417,7 +417,7 @@ **/ /proc/list_clear_nulls(list/list_to_clear) return (list_to_clear.RemoveAll(null) > 0) - + /** * Removes any empty weakrefs from the list @@ -473,17 +473,23 @@ * You should only pass integers in. */ /proc/pick_weight(list/list_to_pick) + if(length(list_to_pick) == 0) + return null + var/total = 0 - var/item - for(item in list_to_pick) + for(var/item in list_to_pick) if(!list_to_pick[item]) list_to_pick[item] = 0 total += list_to_pick[item] total = rand(1, total) - for(item in list_to_pick) - total -= list_to_pick[item] - if(total <= 0 && list_to_pick[item]) + for(var/item in list_to_pick) + var/item_weight = list_to_pick[item] + if(item_weight == 0) + continue + + total -= item_weight + if(total <= 0) return item return null diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm index 368ae3a384035..068f857c8cb31 100644 --- a/code/controllers/configuration/entries/game_options.dm +++ b/code/controllers/configuration/entries/game_options.dm @@ -347,6 +347,16 @@ min_val = 0 integer = FALSE +/datum/config_entry/number/events_frequency_lower + default = 2.5 MINUTES + min_val = 0 + protection = CONFIG_ENTRY_LOCKED + +/datum/config_entry/number/events_frequency_upper + default = 7 MINUTES + min_val = 0 + protection = CONFIG_ENTRY_LOCKED + /datum/config_entry/number/mice_roundstart default = 10 min_val = 0 diff --git a/code/controllers/subsystem/events.dm b/code/controllers/subsystem/events.dm index 362129f130570..50e437195a74a 100644 --- a/code/controllers/subsystem/events.dm +++ b/code/controllers/subsystem/events.dm @@ -23,6 +23,10 @@ SUBSYSTEM_DEF(events) if(!event.typepath || !event.valid_for_map()) continue //don't want this one! leave it for the garbage collector control += event //add it to the list of all events (controls) + + frequency_lower = CONFIG_GET(number/events_frequency_lower) + frequency_upper = CONFIG_GET(number/events_frequency_upper) + reschedule() // Instantiate our holidays list if it hasn't been already if(isnull(GLOB.holidays)) @@ -85,7 +89,8 @@ SUBSYSTEM_DEF(events) event_roster[event_to_check] = event_to_check.weight var/datum/round_event_control/event_to_run = pick_weight(event_roster) - TriggerEvent(event_to_run) + if(event_to_run) + TriggerEvent(event_to_run) ///Does the last pre-flight checks for the passed event, and runs it if the event is ready. /datum/controller/subsystem/events/proc/TriggerEvent(datum/round_event_control/event_to_trigger) @@ -103,8 +108,8 @@ SUBSYSTEM_DEF(events) ///Sets the event frequency bounds back to their initial value. /datum/controller/subsystem/events/proc/resetFrequency() - frequency_lower = initial(frequency_lower) - frequency_upper = initial(frequency_upper) + frequency_lower = CONFIG_GET(number/events_frequency_lower) + frequency_upper = CONFIG_GET(number/events_frequency_upper) /** * HOLIDAYS diff --git a/config/game_options.txt b/config/game_options.txt index f6d321c1b8e13..f7a3407d99bba 100644 --- a/config/game_options.txt +++ b/config/game_options.txt @@ -147,6 +147,13 @@ EVENTS_MIN_TIME_MUL 1 ## Set to 0 to make dangerous events avaliable for all populations. EVENTS_MIN_PLAYERS_MUL 1 +## The lower bound, in deciseconds, for how soon another random event can be scheduled. +## Defaults to 1500 deciseconds or 2.5 minutes +EVENTS_FREQUENCY_LOWER 1500 + +## The upper bound, in deciseconds, for how soon another random event can be scheduled. +## Defaults to 4200 deciseconds or 7 minutes +EVENTS_FREQUENCY_UPPER 4200 ## AI ### From ce4880a11dc47609d5cf71a16f9960c0d8d4f61d Mon Sep 17 00:00:00 2001 From: orange man <61334995+comfyorange@users.noreply.github.com> Date: Thu, 2 May 2024 13:14:55 +1200 Subject: [PATCH 28/87] Automatic changelog for PR #82978 [ci skip] --- html/changelogs/AutoChangeLog-pr-82978.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-82978.yml diff --git a/html/changelogs/AutoChangeLog-pr-82978.yml b/html/changelogs/AutoChangeLog-pr-82978.yml new file mode 100644 index 0000000000000..e9b93d33a3f38 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-82978.yml @@ -0,0 +1,5 @@ +author: "Gaxeer" +delete-after: True +changes: + - bugfix: "fix runtime when no events were drafted to be picked from" + - config: "make events frequency configurable" \ No newline at end of file From 9975eabef6131d5437249670c5cbfcbf6d788a29 Mon Sep 17 00:00:00 2001 From: The Sharkening <95130227+StrangeWeirdKitten@users.noreply.github.com> Date: Wed, 1 May 2024 19:18:33 -0600 Subject: [PATCH 29/87] Stamp papers quickly with a right click (#82993) ## About The Pull Request Right clicking a paper with a stamp will quickly place down the stamp ## Why It's Good For The Game ![fast](https://github.com/tgstation/tgstation/assets/95130227/de3eab11-e8bf-4266-aab6-97c8434663d7) ## Changelog :cl: qol: You can now quickly stamp papers with a right click /:cl: --------- Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com> --- code/modules/paperwork/paper.dm | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm index 605e88f1e483b..5dd5515c47c4c 100644 --- a/code/modules/paperwork/paper.dm +++ b/code/modules/paperwork/paper.dm @@ -441,8 +441,8 @@ // Handle stamping items. if(writing_stats["interaction_mode"] == MODE_STAMPING) if(!user.can_read(src) || user.is_blind()) - //The paper's stampable window area is assumed approx 400x500 - add_stamp(writing_stats["stamp_class"], rand(0, 400), rand(0, 500), rand(0, 360), writing_stats["stamp_icon_state"]) + //The paper's stampable window area is assumed approx 300x400 + add_stamp(writing_stats["stamp_class"], rand(0, 300), rand(0, 400), rand(0, 360), writing_stats["stamp_icon_state"]) user.visible_message(span_notice("[user] blindly stamps [src] with \the [attacking_item]!")) to_chat(user, span_notice("You stamp [src] with \the [attacking_item] the best you can!")) playsound(src, 'sound/items/handling/standard_stamp.ogg', 50, vary = TRUE) @@ -454,6 +454,25 @@ ui_interact(user) return ..() +/// Secondary right click interaction to quickly stamp things +/obj/item/paper/item_interaction_secondary(mob/living/user, obj/item/tool, list/modifiers) + var/list/writing_stats = tool.get_writing_implement_details() + + if(!length(writing_stats)) + return NONE + if(writing_stats["interaction_mode"] != MODE_STAMPING) + return NONE + if(!user.can_read(src) || user.is_blind()) // Just leftclick instead + return NONE + + add_stamp(writing_stats["stamp_class"], rand(1, 300), rand(1, 400), stamp_icon_state = writing_stats["stamp_icon_state"]) + user.visible_message( + span_notice("[user] quickly stamps [src] with [tool] without looking."), + span_notice("You quickly stamp [src] with [tool] without looking."), + ) + playsound(src, 'sound/items/handling/standard_stamp.ogg', 50, vary = TRUE) + + return ITEM_INTERACT_BLOCKING // Stop the UI from opening. /** * Attempts to ui_interact the paper to the given user, with some sanity checking * to make sure the camera still exists via the weakref and that this paper is still From 286a5d1d5b69416195e9b2641c75d702c57556ae Mon Sep 17 00:00:00 2001 From: orange man <61334995+comfyorange@users.noreply.github.com> Date: Thu, 2 May 2024 13:18:53 +1200 Subject: [PATCH 30/87] Automatic changelog for PR #82993 [ci skip] --- html/changelogs/AutoChangeLog-pr-82993.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-82993.yml diff --git a/html/changelogs/AutoChangeLog-pr-82993.yml b/html/changelogs/AutoChangeLog-pr-82993.yml new file mode 100644 index 0000000000000..18966acc72ef9 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-82993.yml @@ -0,0 +1,4 @@ +author: "StrangeWeirdKitten" +delete-after: True +changes: + - qol: "You can now quickly stamp papers with a right click" \ No newline at end of file From 8700c0801b29188d39386c74a905758465dfdc1d Mon Sep 17 00:00:00 2001 From: jimmyl <70376633+mc-oofert@users.noreply.github.com> Date: Thu, 2 May 2024 15:46:41 +0200 Subject: [PATCH 31/87] rad shutter directionals (#82997) --- icons/obj/doors/shutters_radiation.dmi | Bin 1477 -> 18358 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/icons/obj/doors/shutters_radiation.dmi b/icons/obj/doors/shutters_radiation.dmi index 657b613b0ccdef3dfe1bf0c010cf1c6650fdf4fa..2e70b24a4aa2aacbb463e9099d48ccd5a58e86ae 100644 GIT binary patch literal 18358 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 delta 1411 zcmZuxeK^wz9RF=BW@*DR<+AOBJrB*Cu5-$=JzR)IN={z3%1d4*mr%xUW{Xo7b-Eiq~eddDJ*$EBM@+(cpar{@-M~&fh1Rqi*LMLJwM{Eibpy>EAXNZrCHC(^xkiES~TgSED3>4Lz?7V%jZ~*}H zE4@8v2hvJr!djAsD42K8!DkyidJ2Cv4FwV^fq)d4iM=rA#dEw(0BXAVAc6oOMFNBD z`8267+YSs`uj&e0x`3HAa>RIrV^&`Pj2Rne(l?Mmryz?=PU zKcsOU3Ol(nu;zB?0Iunr;SUX~0b zhVO8U#+B2u$V?k$O8QZ)q(@(%4xZ)=Ql|<4* zvfNskl07$|9MAZHhQ)r5n>S&&DcBTsP23|gM57@nX0GWf_6|E-0R-UYO;bJ!>&ioW zP_&EFxh}}zoFT0L&eV;P&okHbRKJ{iI>`p3VB~Fd?eyY@CG$`W!c&Ie@MW3ba7wic zBdH$8{h52(eGJDyv|(Ne$^yA_ce6_Gyqrh+?51R-V7UeJ?^zTDZ!765vV3cK_6WGC z@P&1k=V?={V5|yR6<}rH`F1hBRaz=aZg5Wv+y;Hv5Y^OXYNf@4^$q{4Q-r!y|Z-(nI(XIM<5gCT?awLtrooHBKs}Ga%v7>w= z0gtZG3&GE8)-uAGs-?7|*-2E%cipT;(A}b4yo8u^iY$u2oK0(?o)@jG@Ops`nZb4) z^{WPV6@1Y_@+ozrutoxUnSV?M^R3qXct-0p=|YxN%g=v0t|2-u#%IthzTTa)ur3pG zt+J(@(keYP^f2miX;$0%VZ-88``l;ZbSV%gu(Y0()W)RaDKJZ|;0t&`2 z-ihy@JydWPeeAEL@^JTAP4t9bxe77G;dODc)2Q%UlUF&^x=Qc4>I;>SNn)nv-Nd2= zs`KV#D^E!O(&0{UOK=D#*;mTTa~qfPeyeA*X?<`JqT8Hu2vg4up`AmKkfu23A8jqP z0K1O{o6o?-BU~j8SVl@#*G*;WCpW<#mckfww-v@ugC)6p+oq*S;$N3k0XLHjQ^m>a|bT*su_1q(Ji2u>;57Bj{r8U3emN(oeTv@M&UlZ zb_5BVr^uEWkVEmo5n`&4%>;MT#Sz5QBfzeFF13PC+epY9$vT`U$(ba|)!gJJy7<(J zm--=Sb4{VCgjNb1GOgfjG|9oR)&OhHAJQ{Iwa&nFcGTQf-idT~7ENTFD Date: Fri, 3 May 2024 01:47:00 +1200 Subject: [PATCH 32/87] Automatic changelog for PR #82997 [ci skip] --- html/changelogs/AutoChangeLog-pr-82997.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-82997.yml diff --git a/html/changelogs/AutoChangeLog-pr-82997.yml b/html/changelogs/AutoChangeLog-pr-82997.yml new file mode 100644 index 0000000000000..bba9e4e7b6035 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-82997.yml @@ -0,0 +1,4 @@ +author: "mc-oofert" +delete-after: True +changes: + - image: "added directional sprites for radiation shutters" \ No newline at end of file From 2f660b328ace9d4de950df2a2aa379986178d2cd Mon Sep 17 00:00:00 2001 From: Echriser Date: Thu, 2 May 2024 11:08:59 -0500 Subject: [PATCH 33/87] Conveyors switches now use left and right click (#83001) ## About The Pull Request Replaces current switch behavior from having to spam to get between forward, off, and reversing states to just use left and right click. It also removes the previous toggle behavior from the circuit trigger in favor or the forward, backwards, and stop buttons. Follow up to suggested changes from #82857 ## Why It's Good For The Game No more spamming left click to get to the state you want, nor worrying about what the previous direction was and not knowing which way it's gonna go. ## Changelog :cl: qol: conveyors now use left and right click controls qol: add screentips for conveyor switches del: removes circuit conveyor circuit default trigger behavior in favor in favor of the three state triggers /:cl: --- code/modules/recycling/conveyor.dm | 53 +++++++++++++++++------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/code/modules/recycling/conveyor.dm b/code/modules/recycling/conveyor.dm index 36d73552159c2..6ef15929ecc5e 100644 --- a/code/modules/recycling/conveyor.dm +++ b/code/modules/recycling/conveyor.dm @@ -350,8 +350,6 @@ GLOBAL_LIST_EMPTY(conveyors_by_id) /// The current state of the switch. var/position = CONVEYOR_OFF - /// Last direction setting. - var/last_pos = CONVEYOR_BACKWARDS /// If the switch only operates the conveyor belts in a single direction. var/oneway = FALSE /// If the level points the opposite direction when it's turned on. @@ -372,6 +370,7 @@ GLOBAL_LIST_EMPTY(conveyors_by_id) AddComponent(/datum/component/usb_port, list( /obj/item/circuit_component/conveyor_switch, )) + register_context() /obj/machinery/conveyor_switch/Destroy() LAZYREMOVE(GLOB.conveyors_by_id[id], src) @@ -397,6 +396,27 @@ GLOBAL_LIST_EMPTY(conveyors_by_id) icon_state = "[base_icon_state]-[invert_icon ? "rev" : "fwd"]" return ..() +/obj/machinery/conveyor_switch/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = ..() + if(!held_item) + context[SCREENTIP_CONTEXT_LMB] = "Toggle forwards" + if(!oneway) + context[SCREENTIP_CONTEXT_RMB] = "Toggle backwards" + return CONTEXTUAL_SCREENTIP_SET + if(held_item.tool_behaviour == TOOL_MULTITOOL) + context[SCREENTIP_CONTEXT_LMB] = "Set speed" + context[SCREENTIP_CONTEXT_RMB] = "View wires" + return CONTEXTUAL_SCREENTIP_SET + if(held_item.tool_behaviour == TOOL_SCREWDRIVER) + context[SCREENTIP_CONTEXT_LMB] = "Toggle oneway" + return CONTEXTUAL_SCREENTIP_SET + if(held_item.tool_behaviour == TOOL_CROWBAR) + context[SCREENTIP_CONTEXT_LMB] = "Detach" + return CONTEXTUAL_SCREENTIP_SET + if(held_item.tool_behaviour == TOOL_WRENCH) + context[SCREENTIP_CONTEXT_LMB] = "Invert" + return CONTEXTUAL_SCREENTIP_SET + /// Updates all conveyor belts that are linked to this switch, and tells them to start processing. /obj/machinery/conveyor_switch/proc/update_linked_conveyors() for(var/obj/machinery/conveyor/belt in GLOB.conveyors_by_id[id]) @@ -414,28 +434,29 @@ GLOBAL_LIST_EMPTY(conveyors_by_id) CHECK_TICK /// Updates the switch's `position` and `last_pos` variable. Useful so that the switch can properly cycle between the forwards, backwards and neutral positions. -/obj/machinery/conveyor_switch/proc/update_position() +/obj/machinery/conveyor_switch/proc/update_position(direction) if(position == CONVEYOR_OFF) if(oneway) //is it a oneway switch position = oneway else - if(last_pos < CONVEYOR_OFF) + if(direction == CONVEYOR_FORWARD) position = CONVEYOR_FORWARD - last_pos = CONVEYOR_OFF else position = CONVEYOR_BACKWARDS - last_pos = CONVEYOR_OFF else - last_pos = position position = CONVEYOR_OFF /// Called when a user clicks on this switch with an open hand. -/obj/machinery/conveyor_switch/interact(mob/user) +/obj/machinery/conveyor_switch/attack_hand(mob/living/user, list/modifiers) add_fingerprint(user) - update_position() + if(LAZYACCESS(modifiers, RIGHT_CLICK)) + update_position(CONVEYOR_BACKWARDS) + else + update_position(CONVEYOR_FORWARD) update_appearance() update_linked_conveyors() update_linked_switches() + return TRUE /obj/machinery/conveyor_switch/attackby(obj/item/attacking_item, mob/user, params) if(is_wire_tool(attacking_item)) @@ -588,7 +609,6 @@ GLOBAL_LIST_EMPTY(conveyors_by_id) /obj/item/circuit_component/conveyor_switch display_name = "Conveyor Switch" desc = "Allows to control connected conveyor belts." - circuit_flags = CIRCUIT_FLAG_INPUT_SIGNAL /// Direction input ports. var/datum/port/input/stop @@ -619,12 +639,6 @@ GLOBAL_LIST_EMPTY(conveyors_by_id) attached_switch = null return ..() -/obj/item/circuit_component/conveyor_switch/input_received(datum/port/input/port) - if(!attached_switch) - return - - INVOKE_ASYNC(src, PROC_REF(update_conveyors), port) - /obj/item/circuit_component/conveyor_switch/proc/on_switch_changed() attached_switch.update_appearance() attached_switch.update_linked_conveyors() @@ -646,13 +660,6 @@ GLOBAL_LIST_EMPTY(conveyors_by_id) attached_switch.position = CONVEYOR_BACKWARDS INVOKE_ASYNC(src, PROC_REF(on_switch_changed)) -/obj/item/circuit_component/conveyor_switch/proc/update_conveyors(datum/port/input/port) - if(!attached_switch) - return - - attached_switch.update_position() - INVOKE_ASYNC(src, PROC_REF(on_switch_changed)) - #undef CONVEYOR_BACKWARDS #undef CONVEYOR_OFF #undef CONVEYOR_FORWARD From 6d043f34f65fc138ca27a69cc905754a68233beb Mon Sep 17 00:00:00 2001 From: orange man <61334995+comfyorange@users.noreply.github.com> Date: Fri, 3 May 2024 04:09:21 +1200 Subject: [PATCH 34/87] Automatic changelog for PR #83001 [ci skip] --- html/changelogs/AutoChangeLog-pr-83001.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-83001.yml diff --git a/html/changelogs/AutoChangeLog-pr-83001.yml b/html/changelogs/AutoChangeLog-pr-83001.yml new file mode 100644 index 0000000000000..36e13fbda33e3 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-83001.yml @@ -0,0 +1,6 @@ +author: "Echriser" +delete-after: True +changes: + - qol: "conveyors now use left and right click controls" + - qol: "add screentips for conveyor switches" + - rscdel: "removes circuit conveyor circuit default trigger behavior in favor in favor of the three state triggers" \ No newline at end of file From b5a9c322b704380e58dbce0d475db02a336be498 Mon Sep 17 00:00:00 2001 From: Zenog400 <41298161+Zenog400@users.noreply.github.com> Date: Thu, 2 May 2024 09:28:11 -0700 Subject: [PATCH 35/87] More Machine Blessing Options (#82606) ## About The Pull Request Adds more implants to the pool that the Machine Blessing rite can draw from, and rebalances the drop rates so that you're more likely to get thematically-consistent implants. ## Why It's Good For The Game Currently, the options for what the Machine Blessing rite can give you are kinda garbage, considering that _one_ implant from the rite is the same value as _two_ full species conversions. This adds a few more that are actually useful. Specific implants added: - Combat arm (extremely low drop rate) - Tool arm (higher drop rate) - Reviver implant (lower drop rate) - Anti-drop implant - Anti-stun implant (significantly lower drop rate) - Welding eyes Additionally, slightly lowers the drop rate of the breathing tube implant, because androids don't have lungs ## Changelog :cl: add: increased the size of the pool that Machine Blessing draws from balance: weighted some of the implants that Machine Blessing can give /:cl: --------- Co-authored-by: Jacquerel --- code/modules/religion/rites.dm | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/code/modules/religion/rites.dm b/code/modules/religion/rites.dm index d907191c33ddd..fa7d31c324920 100644 --- a/code/modules/religion/rites.dm +++ b/code/modules/religion/rites.dm @@ -128,13 +128,33 @@ /datum/religion_rites/machine_blessing/invoke_effect(mob/living/user, atom/movable/religious_tool) ..() var/altar_turf = get_turf(religious_tool) - var/blessing = pick( - /obj/item/organ/internal/cyberimp/arm/surgery, + var/arm = list( + /obj/item/organ/internal/cyberimp/arm/combat, + /obj/item/organ/internal/cyberimp/arm/surgery = 1000000, + /obj/item/organ/internal/cyberimp/arm/toolset = 1500000 + ) + var/eyes = list( /obj/item/organ/internal/cyberimp/eyes/hud/diagnostic, /obj/item/organ/internal/cyberimp/eyes/hud/medical, - /obj/item/organ/internal/cyberimp/mouth/breathing_tube, - /obj/item/organ/internal/cyberimp/chest/thrusters, - /obj/item/organ/internal/eyes/robotic/glow, + /obj/item/organ/internal/eyes/robotic/shield = 2, + /obj/item/organ/internal/eyes/robotic/glow + ) + var/chest = list( + /obj/item/organ/internal/cyberimp/chest/reviver, + /obj/item/organ/internal/cyberimp/chest/thrusters = 2 + ) + var/head = list( + /obj/item/organ/internal/cyberimp/brain/anti_drop = 100, + /obj/item/organ/internal/cyberimp/brain/anti_stun = 10 + ) + var/blessing = pick_weight_recursive( + list( + arm = 15, + eyes = 15, + chest = 9, + head = 10, + /obj/item/organ/internal/cyberimp/mouth/breathing_tube = 5, + ) ) new blessing(altar_turf) return TRUE From 71928e9678b921c5a4dcbad524b27feb8bf01dda Mon Sep 17 00:00:00 2001 From: orange man <61334995+comfyorange@users.noreply.github.com> Date: Fri, 3 May 2024 04:28:56 +1200 Subject: [PATCH 36/87] Automatic changelog for PR #82606 [ci skip] --- html/changelogs/AutoChangeLog-pr-82606.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-82606.yml diff --git a/html/changelogs/AutoChangeLog-pr-82606.yml b/html/changelogs/AutoChangeLog-pr-82606.yml new file mode 100644 index 0000000000000..90cf1fc2d1751 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-82606.yml @@ -0,0 +1,5 @@ +author: "Zenog400" +delete-after: True +changes: + - rscadd: "increased the size of the pool that Machine Blessing draws from" + - balance: "weighted some of the implants that Machine Blessing can give" \ No newline at end of file From 504f54104ae4958207d127652322a9a04a326520 Mon Sep 17 00:00:00 2001 From: Changelogs Date: Fri, 3 May 2024 00:22:39 +0000 Subject: [PATCH 37/87] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-82606.yml | 5 ----- html/changelogs/AutoChangeLog-pr-82978.yml | 5 ----- html/changelogs/AutoChangeLog-pr-82993.yml | 4 ---- html/changelogs/AutoChangeLog-pr-82997.yml | 4 ---- html/changelogs/AutoChangeLog-pr-83001.yml | 6 ------ html/changelogs/archive/2024-05.yml | 16 ++++++++++++++++ 6 files changed, 16 insertions(+), 24 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-82606.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-82978.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-82993.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-82997.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-83001.yml diff --git a/html/changelogs/AutoChangeLog-pr-82606.yml b/html/changelogs/AutoChangeLog-pr-82606.yml deleted file mode 100644 index 90cf1fc2d1751..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-82606.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Zenog400" -delete-after: True -changes: - - rscadd: "increased the size of the pool that Machine Blessing draws from" - - balance: "weighted some of the implants that Machine Blessing can give" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-82978.yml b/html/changelogs/AutoChangeLog-pr-82978.yml deleted file mode 100644 index e9b93d33a3f38..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-82978.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Gaxeer" -delete-after: True -changes: - - bugfix: "fix runtime when no events were drafted to be picked from" - - config: "make events frequency configurable" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-82993.yml b/html/changelogs/AutoChangeLog-pr-82993.yml deleted file mode 100644 index 18966acc72ef9..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-82993.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "StrangeWeirdKitten" -delete-after: True -changes: - - qol: "You can now quickly stamp papers with a right click" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-82997.yml b/html/changelogs/AutoChangeLog-pr-82997.yml deleted file mode 100644 index bba9e4e7b6035..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-82997.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "mc-oofert" -delete-after: True -changes: - - image: "added directional sprites for radiation shutters" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-83001.yml b/html/changelogs/AutoChangeLog-pr-83001.yml deleted file mode 100644 index 36e13fbda33e3..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-83001.yml +++ /dev/null @@ -1,6 +0,0 @@ -author: "Echriser" -delete-after: True -changes: - - qol: "conveyors now use left and right click controls" - - qol: "add screentips for conveyor switches" - - rscdel: "removes circuit conveyor circuit default trigger behavior in favor in favor of the three state triggers" \ No newline at end of file diff --git a/html/changelogs/archive/2024-05.yml b/html/changelogs/archive/2024-05.yml index 90ccdc1199d2a..8e615e3cb43ed 100644 --- a/html/changelogs/archive/2024-05.yml +++ b/html/changelogs/archive/2024-05.yml @@ -74,3 +74,19 @@ options nikothedude: - bugfix: Jousting no longer bypasses pacifism +2024-05-03: + Echriser: + - qol: conveyors now use left and right click controls + - qol: add screentips for conveyor switches + - rscdel: removes circuit conveyor circuit default trigger behavior in favor in + favor of the three state triggers + Gaxeer: + - bugfix: fix runtime when no events were drafted to be picked from + - config: make events frequency configurable + StrangeWeirdKitten: + - qol: You can now quickly stamp papers with a right click + Zenog400: + - rscadd: increased the size of the pool that Machine Blessing draws from + - balance: weighted some of the implants that Machine Blessing can give + mc-oofert: + - image: added directional sprites for radiation shutters From c58193910c06ecbe9f979b08d6b208529f1fbdec Mon Sep 17 00:00:00 2001 From: Kyle Spier-Swenson Date: Thu, 2 May 2024 18:56:38 -0700 Subject: [PATCH 38/87] Bump supported client api version to 515 (#83011) --- .github/max_required_byond_client.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/max_required_byond_client.txt b/.github/max_required_byond_client.txt index faf8058076a2a..bd10125cce940 100644 --- a/.github/max_required_byond_client.txt +++ b/.github/max_required_byond_client.txt @@ -5,4 +5,4 @@ # (Requiring clients update to connect to the game server is not something we like to spring on them with no notice, # especially for beta builds where the pager/updater won't let them update without additional configuration.) -514 +515 From ad968fc793d0f1d57bbf0942f9e3127d12c774ee Mon Sep 17 00:00:00 2001 From: Ikalpo Date: Thu, 2 May 2024 18:57:01 -0700 Subject: [PATCH 39/87] 13 year old singularity bug fixed (#83005) ## About The Pull Request Stage 2 singularities have the wrong step size, allowing them to eat the containment field if they happen to be right next to it. (For instance, if they are contained in a 3x3 or 4x4 field) 2 lines of code, and that's fixed. ## Why It's Good For The Game BEFORE https://github.com/tgstation/tgstation/assets/23534908/821f43f6-85ca-482b-a905-614f2b1c8359 AFTER https://github.com/tgstation/tgstation/assets/23534908/4580d23a-41f0-4485-8454-4e991aa38344 ## The Source This was introduced in a googlecode commit in 2011: https://github.com/tgstation/tgstation/blob/337be2c3bcc7a739ffc927ccce9d6aa2b43c114c/code/modules/power/singularity/singularity.dm#L269 No, mport2004, I *don't* think this is right. ## Changelog :cl: fix: Stage 2 singularities should no longer escape containment /:cl: --- code/datums/components/singularity.dm | 2 +- code/modules/power/singularity/singularity.dm | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/code/datums/components/singularity.dm b/code/datums/components/singularity.dm index 56a6723f21f41..14aaedff7172a 100644 --- a/code/datums/components/singularity.dm +++ b/code/datums/components/singularity.dm @@ -286,7 +286,7 @@ if (STAGE_ONE) steps = 1 if (STAGE_TWO) - steps = 3//Yes this is right + steps = 2 if (STAGE_THREE) steps = 3 if (STAGE_FOUR) diff --git a/code/modules/power/singularity/singularity.dm b/code/modules/power/singularity/singularity.dm index d47d75360946a..74b7bfdbcdcb4 100644 --- a/code/modules/power/singularity/singularity.dm +++ b/code/modules/power/singularity/singularity.dm @@ -340,7 +340,7 @@ if(STAGE_ONE) steps = 1 if(STAGE_TWO) - steps = 3//Yes this is right + steps = 2 if(STAGE_THREE) steps = 3 if(STAGE_FOUR) From 52b0d48f3d38908790f495d318775e5e92b76ea5 Mon Sep 17 00:00:00 2001 From: orange man <61334995+comfyorange@users.noreply.github.com> Date: Fri, 3 May 2024 13:57:20 +1200 Subject: [PATCH 40/87] Automatic changelog for PR #83005 [ci skip] --- html/changelogs/AutoChangeLog-pr-83005.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-83005.yml diff --git a/html/changelogs/AutoChangeLog-pr-83005.yml b/html/changelogs/AutoChangeLog-pr-83005.yml new file mode 100644 index 0000000000000..be13b5786e78c --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-83005.yml @@ -0,0 +1,4 @@ +author: "Ikalpo" +delete-after: True +changes: + - bugfix: "Stage 2 singularities should no longer escape containment" \ No newline at end of file From 8da332e50b8a2d92beb2683b2db21d817195805e Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Thu, 2 May 2024 21:02:30 -0500 Subject: [PATCH 41/87] Fix warning on master (#83014) ## Changelog :cl: Melbert fix: New machine god blessings now actually works probably /:cl: --- code/modules/religion/rites.dm | 55 ++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/code/modules/religion/rites.dm b/code/modules/religion/rites.dm index fa7d31c324920..d7d0fa818441c 100644 --- a/code/modules/religion/rites.dm +++ b/code/modules/religion/rites.dm @@ -128,33 +128,36 @@ /datum/religion_rites/machine_blessing/invoke_effect(mob/living/user, atom/movable/religious_tool) ..() var/altar_turf = get_turf(religious_tool) - var/arm = list( - /obj/item/organ/internal/cyberimp/arm/combat, - /obj/item/organ/internal/cyberimp/arm/surgery = 1000000, - /obj/item/organ/internal/cyberimp/arm/toolset = 1500000 - ) - var/eyes = list( - /obj/item/organ/internal/cyberimp/eyes/hud/diagnostic, - /obj/item/organ/internal/cyberimp/eyes/hud/medical, - /obj/item/organ/internal/eyes/robotic/shield = 2, - /obj/item/organ/internal/eyes/robotic/glow - ) - var/chest = list( - /obj/item/organ/internal/cyberimp/chest/reviver, - /obj/item/organ/internal/cyberimp/chest/thrusters = 2 - ) - var/head = list( - /obj/item/organ/internal/cyberimp/brain/anti_drop = 100, - /obj/item/organ/internal/cyberimp/brain/anti_stun = 10 - ) var/blessing = pick_weight_recursive( - list( - arm = 15, - eyes = 15, - chest = 9, - head = 10, - /obj/item/organ/internal/cyberimp/mouth/breathing_tube = 5, - ) + list( + // Arms + list( + /obj/item/organ/internal/cyberimp/arm/combat = 1, + /obj/item/organ/internal/cyberimp/arm/surgery = 1000000, + /obj/item/organ/internal/cyberimp/arm/toolset = 1500000, + ) = 15, + // Eyes + list( + /obj/item/organ/internal/cyberimp/eyes/hud/diagnostic = 1, + /obj/item/organ/internal/cyberimp/eyes/hud/medical = 1, + /obj/item/organ/internal/eyes/robotic/glow = 1, + /obj/item/organ/internal/eyes/robotic/shield = 2, + ) = 15, + // Chest + list( + /obj/item/organ/internal/cyberimp/chest/reviver = 1, + /obj/item/organ/internal/cyberimp/chest/thrusters = 2, + ) = 9, + // Brain / Head + list( + /obj/item/organ/internal/cyberimp/brain/anti_drop = 100, + /obj/item/organ/internal/cyberimp/brain/anti_stun = 10, + ) = 10, + // Misc + list( + /obj/item/organ/internal/cyberimp/mouth/breathing_tube = 1, + ) = 5, + ) ) new blessing(altar_turf) return TRUE From dc202e7642401d22ff962bf5931ac6cd4e3717f4 Mon Sep 17 00:00:00 2001 From: Ben10Omintrix <138636438+Ben10Omintrix@users.noreply.github.com> Date: Fri, 3 May 2024 05:03:19 +0300 Subject: [PATCH 42/87] fixes a few problems in ai targetting (#82998) ## About The Pull Request fixes #82964 , fixes #82866 ## Why It's Good For The Game fixes several problems with ai targetting. this pr resolves civil war that was brewing between several mobs. also fixes a major problem where mobs would only search for targets and not perform any other behaviors. also fixes a small problem where mobs would constantly stop and start while chasing targets ## Changelog :cl: fix: mobs in the same faction will no longer be at odds against one another fix: mobs can now perform behaviors alongside searching for targets fix: mobs will no longer be starting and stopping when chasing targets /:cl: --- code/datums/ai/_ai_controller.dm | 8 ++++++-- code/datums/ai/basic_mobs/basic_ai_behaviors/targeting.dm | 3 ++- .../ai/basic_mobs/basic_subtrees/capricious_retaliate.dm | 1 + .../ai/basic_mobs/basic_subtrees/target_retaliate.dm | 5 +---- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/code/datums/ai/_ai_controller.dm b/code/datums/ai/_ai_controller.dm index 309be50878341..35615c6704c42 100644 --- a/code/datums/ai/_ai_controller.dm +++ b/code/datums/ai/_ai_controller.dm @@ -172,9 +172,13 @@ multiple modular subtrees with behaviors return FALSE return TRUE -/datum/ai_controller/proc/recalculate_idle() +/datum/ai_controller/proc/recalculate_idle(datum/exited) if(ai_status == AI_STATUS_OFF) return + + if(exited && (get_dist(pawn, (islist(exited) ? exited[1] : exited)) <= interesting_dist)) //is our target in between interesting cells? + return + if(should_idle()) set_ai_status(AI_STATUS_IDLE) @@ -187,7 +191,7 @@ multiple modular subtrees with behaviors /datum/ai_controller/proc/on_client_exit(datum/source, datum/exited) SIGNAL_HANDLER - recalculate_idle() + recalculate_idle(exited) /// Sets the AI on or off based on current conditions, call to reset after you've manually disabled it somewhere /datum/ai_controller/proc/reset_ai_status() diff --git a/code/datums/ai/basic_mobs/basic_ai_behaviors/targeting.dm b/code/datums/ai/basic_mobs/basic_ai_behaviors/targeting.dm index f0ee4bb9bba37..4cf04039e8535 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/targeting.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/targeting.dm @@ -7,6 +7,7 @@ GLOBAL_LIST_INIT(target_interested_atoms, typecacheof(list(/mob, /obj/machinery/ /datum/ai_behavior/find_potential_targets action_cooldown = 2 SECONDS + behavior_flags = AI_BEHAVIOR_CAN_PLAN_DURING_EXECUTION /// How far can we see stuff? var/vision_range = 9 /// Blackboard key for aggro range, uses vision range if not specified @@ -34,7 +35,7 @@ GLOBAL_LIST_INIT(target_interested_atoms, typecacheof(list(/mob, /obj/machinery/ // If we're using a field rn, just don't do anything yeah? if(controller.blackboard[BB_FIND_TARGETS_FIELD(type)]) - return + return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED var/list/potential_targets = hearers(aggro_range, get_turf(controller.pawn)) - living_mob //Remove self, so we don't suicide diff --git a/code/datums/ai/basic_mobs/basic_subtrees/capricious_retaliate.dm b/code/datums/ai/basic_mobs/basic_subtrees/capricious_retaliate.dm index a4fc49facc000..52b19036b9a47 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/capricious_retaliate.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/capricious_retaliate.dm @@ -21,6 +21,7 @@ return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_SUCCEEDED pawn.visible_message(span_notice("[pawn] calms down.")) // We can blackboard key this if anyone else actually wants to customise it controller.clear_blackboard_key(BB_BASIC_MOB_RETALIATE_LIST) + controller.clear_blackboard_key(BB_BASIC_MOB_CURRENT_TARGET) controller.CancelActions() // Otherwise they will try and get one last kick in return AI_BEHAVIOR_DELAY diff --git a/code/datums/ai/basic_mobs/basic_subtrees/target_retaliate.dm b/code/datums/ai/basic_mobs/basic_subtrees/target_retaliate.dm index d327b1047cf57..042ccb2310c1a 100644 --- a/code/datums/ai/basic_mobs/basic_subtrees/target_retaliate.dm +++ b/code/datums/ai/basic_mobs/basic_subtrees/target_retaliate.dm @@ -11,7 +11,6 @@ var/check_faction = FALSE /datum/ai_planning_subtree/target_retaliate/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick) - . = ..() controller.queue_behavior(/datum/ai_behavior/target_from_retaliate_list, BB_BASIC_MOB_RETALIATE_LIST, target_key, targeting_strategy_key, hiding_place_key, check_faction) /datum/ai_planning_subtree/target_retaliate/check_faction @@ -35,7 +34,6 @@ var/vision_range = 9 /datum/ai_behavior/target_from_retaliate_list/perform(seconds_per_tick, datum/ai_controller/controller, shitlist_key, target_key, targeting_strategy_key, hiding_location_key, check_faction) - . = ..() var/mob/living/living_mob = controller.pawn var/datum/targeting_strategy/targeting_strategy = GET_TARGETING_STRATEGY(controller.blackboard[targeting_strategy_key]) if(!targeting_strategy) @@ -58,7 +56,6 @@ enemies_list += potential_target if(!length(enemies_list)) - controller.clear_blackboard_key(target_key) return AI_BEHAVIOR_DELAY | AI_BEHAVIOR_FAILED var/atom/new_target = pick_final_target(controller, enemies_list) @@ -75,7 +72,7 @@ /datum/ai_behavior/target_from_retaliate_list/proc/pick_final_target(datum/ai_controller/controller, list/enemies_list) return pick(enemies_list) -/datum/ai_behavior/target_from_retaliate_list/finish_action(datum/ai_controller/controller, succeeded, check_faction) +/datum/ai_behavior/target_from_retaliate_list/finish_action(datum/ai_controller/controller, succeeded, shitlist_key, target_key, targeting_strategy_key, hiding_location_key, check_faction) . = ..() if (succeeded || check_faction) return From 8bbfd225cd4d39e33dd6c8600174265aef8fab67 Mon Sep 17 00:00:00 2001 From: Ryll Ryll <3589655+Ryll-Ryll@users.noreply.github.com> Date: Thu, 2 May 2024 22:03:41 -0400 Subject: [PATCH 43/87] Pacifists can no longer endlessly spam rocket launcher backblasts (#82992) ## About The Pull Request Fixes: #82990 Backblasts from a rocket launcher are often the last thing an untrained person thinks of when they fire a rocket launcher. I did not consider the moral stance of pacifists who nevertheless try to shoot rocket launchers when I implemented rocket launcher backblast, because currently pacifists trying to shoot a loaded rocket launcher can spam backblast as much as they want, even if they aren't able to actually shoot. This PR filters out pacifists before they trigger the backblast, to make sure only actual live shots deliver the hellpayload to their allies behind them. ## Why It's Good For The Game A magical reverse flamethrower with no ammo limit (because at no point is the rocket in the chamber actually expended when a pacifist fails to fire it) is bad. A weapon as destructive as that left solely in the hands of pacifists is even worse. ## Changelog :cl: Ryll/Shaps fix: Pacifists can no longer endlessly spam the backblast functionality of loaded rocket launchers that they cannot actually fire /:cl: --------- Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com> --- code/datums/elements/backblast.dm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/code/datums/elements/backblast.dm b/code/datums/elements/backblast.dm index 8952afbfeaa21..f5e73977159cd 100644 --- a/code/datums/elements/backblast.dm +++ b/code/datums/elements/backblast.dm @@ -38,6 +38,9 @@ /// For firing an actual backblast pellet /datum/element/backblast/proc/pew(obj/item/gun/weapon, mob/living/user, atom/target) + if(HAS_TRAIT(user, TRAIT_PACIFISM)) + return + var/turf/origin = get_turf(weapon) var/backblast_angle = get_angle(target, origin) explosion(weapon, devastation_range = dev_range, heavy_impact_range = heavy_range, light_impact_range = light_range, flame_range = flame_range, adminlog = FALSE, protect_epicenter = TRUE, explosion_direction = backblast_angle, explosion_arc = blast_angle) From f70c83cd697246f6e5be418b5b22fe7b12228c6b Mon Sep 17 00:00:00 2001 From: orange man <61334995+comfyorange@users.noreply.github.com> Date: Fri, 3 May 2024 14:05:27 +1200 Subject: [PATCH 44/87] Automatic changelog for PR #83014 [ci skip] --- html/changelogs/AutoChangeLog-pr-83014.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-83014.yml diff --git a/html/changelogs/AutoChangeLog-pr-83014.yml b/html/changelogs/AutoChangeLog-pr-83014.yml new file mode 100644 index 0000000000000..ebd44714a49a6 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-83014.yml @@ -0,0 +1,4 @@ +author: "Melbert" +delete-after: True +changes: + - bugfix: "New machine god blessings now actually works probably" \ No newline at end of file From 854668cb44749bf8989ae4ea4d3d116b6f7192ee Mon Sep 17 00:00:00 2001 From: orange man <61334995+comfyorange@users.noreply.github.com> Date: Fri, 3 May 2024 14:06:04 +1200 Subject: [PATCH 45/87] Automatic changelog for PR #82998 [ci skip] --- html/changelogs/AutoChangeLog-pr-82998.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-82998.yml diff --git a/html/changelogs/AutoChangeLog-pr-82998.yml b/html/changelogs/AutoChangeLog-pr-82998.yml new file mode 100644 index 0000000000000..696b730fca1ee --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-82998.yml @@ -0,0 +1,6 @@ +author: "Ben10Omintrix" +delete-after: True +changes: + - bugfix: "mobs in the same faction will no longer be at odds against one another" + - bugfix: "mobs can now perform behaviors alongside searching for targets" + - bugfix: "mobs will no longer be starting and stopping when chasing targets" \ No newline at end of file From 3e85d915f4ca30ae978edb8f2efd8614d20023e0 Mon Sep 17 00:00:00 2001 From: orange man <61334995+comfyorange@users.noreply.github.com> Date: Fri, 3 May 2024 14:06:26 +1200 Subject: [PATCH 46/87] Automatic changelog for PR #82992 [ci skip] --- html/changelogs/AutoChangeLog-pr-82992.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-82992.yml diff --git a/html/changelogs/AutoChangeLog-pr-82992.yml b/html/changelogs/AutoChangeLog-pr-82992.yml new file mode 100644 index 0000000000000..eb03652dc1470 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-82992.yml @@ -0,0 +1,4 @@ +author: "Ryll/Shaps" +delete-after: True +changes: + - bugfix: "Pacifists can no longer endlessly spam the backblast functionality of loaded rocket launchers that they cannot actually fire" \ No newline at end of file From 79d858a02536d62bf041f87d92e9ba09256ca38c Mon Sep 17 00:00:00 2001 From: Jeremiah <42397676+jlsnow301@users.noreply.github.com> Date: Thu, 2 May 2024 20:09:30 -0700 Subject: [PATCH 47/87] Detective buff: Fixes detective's candy dispenser (#83004) ## About The Pull Request Alt click was broken on detective's hat. Couldn't draw candy corn, oh no! This issue is somewhat rightly caused from the alt click refactor, as its aim was to prevent double interactions from one click. In this case, both withdrawing morsels and opening storage at the same time --- code/datums/storage/storage.dm | 18 ++++++++++++- code/datums/storage/subtypes/pockets.dm | 1 + code/modules/clothing/head/jobs.dm | 35 ++++++++++++++++++++----- 3 files changed, 46 insertions(+), 8 deletions(-) diff --git a/code/datums/storage/storage.dm b/code/datums/storage/storage.dm index b1bacfbceab44..9b661688258f1 100644 --- a/code/datums/storage/storage.dm +++ b/code/datums/storage/storage.dm @@ -113,6 +113,10 @@ /// If TRUE, shows the contents of the storage in open_storage var/display_contents = TRUE + /// Switch this off if you want to handle click_alt in the parent atom + var/click_alt_open = TRUE + + /datum/storage/New( atom/parent, max_slots = src.max_slots, @@ -198,7 +202,7 @@ RegisterSignal(parent, COMSIG_ATOM_ATTACKBY, PROC_REF(on_attackby)) RegisterSignal(parent, COMSIG_ITEM_PRE_ATTACK, PROC_REF(on_preattack)) RegisterSignal(parent, COMSIG_ITEM_ATTACK_SELF, PROC_REF(mass_empty)) - RegisterSignals(parent, list(COMSIG_CLICK_ALT, COMSIG_ATOM_ATTACK_GHOST, COMSIG_ATOM_ATTACK_HAND_SECONDARY), PROC_REF(open_storage_on_signal)) + RegisterSignals(parent, list(COMSIG_ATOM_ATTACK_GHOST, COMSIG_ATOM_ATTACK_HAND_SECONDARY), PROC_REF(open_storage_on_signal)) RegisterSignal(parent, COMSIG_ATOM_ATTACKBY_SECONDARY, PROC_REF(open_storage_attackby_secondary)) RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(close_distance)) RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(update_actions)) @@ -208,6 +212,7 @@ RegisterSignal(parent, COMSIG_OBJ_DECONSTRUCT, PROC_REF(on_deconstruct)) RegisterSignal(parent, COMSIG_ATOM_EMP_ACT, PROC_REF(on_emp_act)) RegisterSignal(parent, COMSIG_ATOM_CONTENTS_WEIGHT_CLASS_CHANGED, PROC_REF(contents_changed_w_class)) + RegisterSignal(parent, COMSIG_CLICK_ALT, PROC_REF(on_click_alt)) /** * Sets where items are physically being stored in the case it shouldn't be on the parent. @@ -919,6 +924,17 @@ GLOBAL_LIST_EMPTY(cached_storage_typecaches) if(display_contents) return COMPONENT_NO_AFTERATTACK + +/// Alt click on the storage item. Default: Open the storage. +/datum/storage/proc/on_click_alt(datum/source, mob/user) + SIGNAL_HANDLER + + if(!click_alt_open) + return + + return open_storage_on_signal(source, user) + + /// Opens the storage to the mob, showing them the contents to their UI. /datum/storage/proc/open_storage(mob/to_show) if(isobserver(to_show)) diff --git a/code/datums/storage/subtypes/pockets.dm b/code/datums/storage/subtypes/pockets.dm index 67a8b2dda7804..2b100d5d3232b 100644 --- a/code/datums/storage/subtypes/pockets.dm +++ b/code/datums/storage/subtypes/pockets.dm @@ -45,6 +45,7 @@ /datum/storage/pockets/small/fedora/detective attack_hand_interact = TRUE // so the detectives would discover pockets in their hats + click_alt_open = FALSE /datum/storage/pockets/chefhat attack_hand_interact = TRUE diff --git a/code/modules/clothing/head/jobs.dm b/code/modules/clothing/head/jobs.dm index 4f5d377ddac00..e0b06a5ab6e2f 100644 --- a/code/modules/clothing/head/jobs.dm +++ b/code/modules/clothing/head/jobs.dm @@ -166,6 +166,8 @@ desc = "An opulent hat that functions as a radio to God. Or as a lightning rod, depending on who you ask." icon_state = "bishopmitre" +#define CANDY_CD_TIME 2 MINUTES + //Detective /obj/item/clothing/head/fedora/det_hat name = "detective's fedora" @@ -174,11 +176,12 @@ icon_state = "detective" inhand_icon_state = "det_hat" interaction_flags_click = NEED_DEXTERITY|NEED_HANDS|ALLOW_RESTING - /// Cooldown for retrieving precious candy corn on alt click - var/candy_cooldown = 0 dog_fashion = /datum/dog_fashion/head/detective - ///Path for the flask that spawns inside their hat roundstart + /// Path for the flask that spawns inside their hat roundstart var/flask_path = /obj/item/reagent_containers/cup/glass/flask/det + /// Cooldown for retrieving precious candy corn with rmb + COOLDOWN_DECLARE(candy_cooldown) + /datum/armor/fedora_det_hat melee = 25 @@ -189,28 +192,46 @@ acid = 50 wound = 5 + /obj/item/clothing/head/fedora/det_hat/Initialize(mapload) . = ..() create_storage(storage_type = /datum/storage/pockets/small/fedora/detective) + register_context() + new flask_path(src) + /obj/item/clothing/head/fedora/det_hat/examine(mob/user) . = ..() . += span_notice("Alt-click to take a candy corn.") + +/obj/item/clothing/head/fedora/det_hat/add_context(atom/source, list/context, obj/item/held_item, mob/user) + . = ..() + + context[SCREENTIP_CONTEXT_ALT_LMB] = "Candy Time" + + return CONTEXTUAL_SCREENTIP_SET + + +/// Now to solve where all these keep coming from /obj/item/clothing/head/fedora/det_hat/click_alt(mob/user) - if(candy_cooldown >= world.time) + if(!COOLDOWN_FINISHED(src, candy_cooldown)) to_chat(user, span_warning("You just took a candy corn! You should wait a couple minutes, lest you burn through your stash.")) return CLICK_ACTION_BLOCKING - var/obj/item/food/candy_corn/CC = new /obj/item/food/candy_corn(src) - user.put_in_hands(CC) + var/obj/item/food/candy_corn/sweets = new /obj/item/food/candy_corn(src) + user.put_in_hands(sweets) to_chat(user, span_notice("You slip a candy corn from your hat.")) - candy_cooldown = world.time+1200 + COOLDOWN_START(src, candy_cooldown, CANDY_CD_TIME) + return CLICK_ACTION_SUCCESS + +#undef CANDY_CD_TIME + /obj/item/clothing/head/fedora/det_hat/minor flask_path = /obj/item/reagent_containers/cup/glass/flask/det/minor From 5ced3d3521202738df7169dd55c52f266c967438 Mon Sep 17 00:00:00 2001 From: orange man <61334995+comfyorange@users.noreply.github.com> Date: Fri, 3 May 2024 15:09:49 +1200 Subject: [PATCH 48/87] Automatic changelog for PR #83004 [ci skip] --- html/changelogs/AutoChangeLog-pr-83004.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-83004.yml diff --git a/html/changelogs/AutoChangeLog-pr-83004.yml b/html/changelogs/AutoChangeLog-pr-83004.yml new file mode 100644 index 0000000000000..c763ac3aa9594 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-83004.yml @@ -0,0 +1,4 @@ +author: "jlsnow301" +delete-after: True +changes: + - bugfix: "Candy corn is once again available to detective fedoras" \ No newline at end of file From 361850adf7df8e1c7f1e65c1ff98ecb0b939c9e9 Mon Sep 17 00:00:00 2001 From: mogeoko <109075486+mogeoko@users.noreply.github.com> Date: Fri, 3 May 2024 06:05:41 +0200 Subject: [PATCH 49/87] [NO GBP] Multiz deck revert (#82996) ## About The Pull Request I've probably did unnecessary things in my old PR and it made a little problem for larvas. Now I revert my mistakes so they can feel great again. Partially reverts #81452, fixes https://github.com/tgstation/tgstation/pull/82996 ## Why It's Good For The Game Larvas can change Z levels using multiz-deck again. Is good or at least I hope so. ## Changelog :cl: mogeoko fix: Ventcrawling mobs can change Z-level using multiz-decks again. /:cl: --- code/modules/atmospherics/machinery/atmosmachinery.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/modules/atmospherics/machinery/atmosmachinery.dm b/code/modules/atmospherics/machinery/atmosmachinery.dm index d152cf09e711f..fa8b833234586 100644 --- a/code/modules/atmospherics/machinery/atmosmachinery.dm +++ b/code/modules/atmospherics/machinery/atmosmachinery.dm @@ -301,8 +301,8 @@ * * given_layer - the piping_layer we are checking */ /obj/machinery/atmospherics/proc/connection_check(obj/machinery/atmospherics/target, given_layer) - //check if the target & src connect in the same direction - if(!((initialize_directions & get_dir(src, target)) && (target.initialize_directions & get_dir(target, src)))) + //if target is not multiz then we have to check if the target & src connect in the same direction + if(!istype(target, /obj/machinery/atmospherics/pipe/multiz) && !((initialize_directions & get_dir(src, target)) && (target.initialize_directions & get_dir(target, src)))) return FALSE //both target & src can't be connected either way From 4a3996d248546ccb5073fd15c0e3da2d1f379116 Mon Sep 17 00:00:00 2001 From: orange man <61334995+comfyorange@users.noreply.github.com> Date: Fri, 3 May 2024 16:06:03 +1200 Subject: [PATCH 50/87] Automatic changelog for PR #82996 [ci skip] --- html/changelogs/AutoChangeLog-pr-82996.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-82996.yml diff --git a/html/changelogs/AutoChangeLog-pr-82996.yml b/html/changelogs/AutoChangeLog-pr-82996.yml new file mode 100644 index 0000000000000..fada9ce1d19f1 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-82996.yml @@ -0,0 +1,4 @@ +author: "mogeoko" +delete-after: True +changes: + - bugfix: "Ventcrawling mobs can change Z-level using multiz-decks again." \ No newline at end of file From e54ecf4b10a6d4b17441ca6fd74d88617967dd41 Mon Sep 17 00:00:00 2001 From: Xander3359 <66163761+Xander3359@users.noreply.github.com> Date: Fri, 3 May 2024 03:35:51 -0400 Subject: [PATCH 51/87] Fix crafting bypassing checks (#82833) ## About The Pull Request Backport from https://github.com/tgstation/TerraGov-Marine-Corps/pull/15701 Fixes crafting bypassing checks. ## Why It's Good For The Game Closes https://github.com/tgstation/tgstation/issues/82826 ## Changelog :cl: fix: You can no longer bypass construction restrictions via the crafting menu /:cl: --- code/__DEFINES/crafting.dm | 22 + code/controllers/subsystem/materials.dm | 12 +- code/datums/components/crafting/_recipes.dm | 10 +- .../datums/components/crafting/atmospheric.dm | 2 +- code/datums/components/crafting/crafting.dm | 116 ++-- code/datums/components/crafting/doors.dm | 4 +- .../components/crafting/melee_weapon.dm | 2 +- code/datums/components/crafting/misc.dm | 2 +- .../components/crafting/ranged_weapon.dm | 12 +- code/datums/components/crafting/structures.dm | 8 +- code/datums/components/crafting/tools.dm | 2 +- .../datums/components/crafting/weapon_ammo.dm | 4 +- code/game/objects/items/stacks/rods.dm | 27 +- .../game/objects/items/stacks/sheets/glass.dm | 34 +- .../objects/items/stacks/sheets/leather.dm | 80 +-- .../objects/items/stacks/sheets/mineral.dm | 64 +-- .../items/stacks/sheets/sheet_types.dm | 498 +++++++++--------- code/game/objects/items/stacks/stack.dm | 23 +- .../game/objects/items/stacks/stack_recipe.dm | 30 +- .../recipes/tablecraft/recipes_bread.dm | 2 +- .../recipes/tablecraft/recipes_cake.dm | 4 +- .../recipes/tablecraft/recipes_pie.dm | 6 +- .../recipes/tablecraft/recipes_sandwich.dm | 2 +- code/modules/mining/ores_coins.dm | 6 +- 24 files changed, 505 insertions(+), 467 deletions(-) diff --git a/code/__DEFINES/crafting.dm b/code/__DEFINES/crafting.dm index 647278aa3ec0b..54dc479aa7306 100644 --- a/code/__DEFINES/crafting.dm +++ b/code/__DEFINES/crafting.dm @@ -7,6 +7,28 @@ ///If the structure is only "used" i.e. it checks to see if it's nearby and allows crafting, but doesn't delete it #define CRAFTING_STRUCTURE_USE 0 +//stack recipe placement check types +/// Checks if there is an object of the result type in any of the cardinal directions +#define STACK_CHECK_CARDINALS (1<<0) +/// Checks if there is an object of the result type within one tile +#define STACK_CHECK_ADJACENT (1<<1) + +//---- Defines for var/crafting_flags +///If this craft must be learned before it becomes available +#define CRAFT_MUST_BE_LEARNED (1<<0) +///Should only one object exist on the same turf? +#define CRAFT_ONE_PER_TURF (1<<1) +/// Setting this to true will effectively set check_direction to true. +#define CRAFT_IS_FULLTILE (1<<2) +/// If this craft should run the direction check, for use when building things like directional windows where you can have more than one per turf +#define CRAFT_CHECK_DIRECTION (1<<3) +/// If the craft requires a floor below +#define CRAFT_ON_SOLID_GROUND (1<<4) +/// If the craft checks that there are objects with density in the same turf when being built +#define CRAFT_CHECK_DENSITY (1<<5) +/// If the created atom will gain custom mat datums +#define CRAFT_APPLIES_MATS (1<<6) + //food/drink crafting defines //When adding new defines, please make sure to also add them to the encompassing list #define CAT_FOOD "Foods" diff --git a/code/controllers/subsystem/materials.dm b/code/controllers/subsystem/materials.dm index b4664323eebfa..3a704d01a82fd 100644 --- a/code/controllers/subsystem/materials.dm +++ b/code/controllers/subsystem/materials.dm @@ -21,15 +21,15 @@ SUBSYSTEM_DEF(materials) var/list/list/material_combos ///List of stackcrafting recipes for materials using base recipes var/list/base_stack_recipes = list( - new /datum/stack_recipe("Chair", /obj/structure/chair/greyscale, one_per_turf = TRUE, on_solid_ground = TRUE, applies_mats = TRUE, category = CAT_FURNITURE), - new /datum/stack_recipe("Toilet", /obj/structure/toilet/greyscale, one_per_turf = TRUE, on_solid_ground = TRUE, applies_mats = TRUE, category = CAT_FURNITURE), - new /datum/stack_recipe("Sink Frame", /obj/structure/sinkframe, one_per_turf = TRUE, on_solid_ground = TRUE, applies_mats = TRUE, category = CAT_FURNITURE), - new /datum/stack_recipe("Material floor tile", /obj/item/stack/tile/material, 1, 4, 20, applies_mats = TRUE, check_density = FALSE, category = CAT_TILES), - new /datum/stack_recipe("Material airlock assembly", /obj/structure/door_assembly/door_assembly_material, 4, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, applies_mats = TRUE, category = CAT_DOORS), + new /datum/stack_recipe("Chair", /obj/structure/chair/greyscale, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND | CRAFT_APPLIES_MATS, category = CAT_FURNITURE), + new /datum/stack_recipe("Toilet", /obj/structure/toilet/greyscale, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND | CRAFT_APPLIES_MATS, category = CAT_FURNITURE), + new /datum/stack_recipe("Sink Frame", /obj/structure/sinkframe, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND | CRAFT_APPLIES_MATS, category = CAT_FURNITURE), + new /datum/stack_recipe("Material floor tile", /obj/item/stack/tile/material, 1, 4, 20, crafting_flags = CRAFT_APPLIES_MATS, category = CAT_TILES), + new /datum/stack_recipe("Material airlock assembly", /obj/structure/door_assembly/door_assembly_material, 4, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND | CRAFT_APPLIES_MATS, category = CAT_DOORS), ) ///List of stackcrafting recipes for materials using rigid recipes var/list/rigid_stack_recipes = list( - new /datum/stack_recipe("Carving block", /obj/structure/carving_block, 5, time = 3 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, applies_mats = TRUE, category = CAT_STRUCTURE), + new /datum/stack_recipe("Carving block", /obj/structure/carving_block, 5, time = 3 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND | CRAFT_APPLIES_MATS, category = CAT_STRUCTURE), ) ///A list of dimensional themes used by the dimensional anomaly and other things, most of which require materials to function. diff --git a/code/datums/components/crafting/_recipes.dm b/code/datums/components/crafting/_recipes.dm index 09121339d3058..31825ac66118a 100644 --- a/code/datums/components/crafting/_recipes.dm +++ b/code/datums/components/crafting/_recipes.dm @@ -23,14 +23,12 @@ var/list/chem_catalysts = list() ///where it shows up in the crafting UI var/category - ///Set to FALSE if it needs to be learned first. - var/always_available = TRUE ///Required machines for the craft, set the assigned value of the typepath to CRAFTING_MACHINERY_CONSUME or CRAFTING_MACHINERY_USE. Lazy associative list: type_path key -> flag value. var/list/machinery ///Required structures for the craft, set the assigned value of the typepath to CRAFTING_STRUCTURE_CONSUME or CRAFTING_STRUCTURE_USE. Lazy associative list: type_path key -> flag value. var/list/structures - ///Should only one object exist on the same turf? - var/one_per_turf = FALSE + /// Bitflag of additional placement checks required to place. (STACK_CHECK_CARDINALS|STACK_CHECK_ADJACENT|STACK_CHECK_TRAM_FORBIDDEN|STACK_CHECK_TRAM_EXCLUSIVE) + var/placement_checks = NONE /// Steps needed to achieve the result var/list/steps /// Whether the result can be crafted with a crafting menu button @@ -44,6 +42,9 @@ /// Allows you to craft so that you don't have to click the craft button many times. var/mass_craftable = FALSE + ///crafting_flags var to hold bool values + var/crafting_flags = CRAFT_CHECK_DENSITY + /datum/crafting_recipe/New() if(!name && result) var/atom/atom_result = result @@ -83,6 +84,7 @@ src.result_amount = stack_recipe.res_amount src.reqs[material] = stack_recipe.req_amount src.category = stack_recipe.category || CAT_MISC + src.placement_checks = stack_recipe.placement_checks /** * Run custom pre-craft checks for this recipe, don't add feedback messages in this because it will spam the client diff --git a/code/datums/components/crafting/atmospheric.dm b/code/datums/components/crafting/atmospheric.dm index cb5bba9ab52b2..b2993012e82b0 100644 --- a/code/datums/components/crafting/atmospheric.dm +++ b/code/datums/components/crafting/atmospheric.dm @@ -25,7 +25,7 @@ /obj/item/assembly/igniter = 1, ) blacklist = list(/obj/item/assembly/igniter/condenser) - one_per_turf = TRUE + crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF time = 2 SECONDS category = CAT_ATMOSPHERIC diff --git a/code/datums/components/crafting/crafting.dm b/code/datums/components/crafting/crafting.dm index 6fc560a293f21..7a56f89c8e924 100644 --- a/code/datums/components/crafting/crafting.dm +++ b/code/datums/components/crafting/crafting.dm @@ -191,42 +191,86 @@ var/list/contents = get_surroundings(crafter, recipe.blacklist) var/send_feedback = 1 - if(check_contents(crafter, recipe, contents)) - if(check_tools(crafter, recipe, contents)) - if(recipe.one_per_turf) - for(var/content in get_turf(crafter)) - if(istype(content, recipe.result)) - return ", object already present." - //If we're a mob we'll try a do_after; non mobs will instead instantly construct the item - if(ismob(crafter) && !do_after(crafter, recipe.time, target = crafter)) - return "." - contents = get_surroundings(crafter, recipe.blacklist) - if(!check_contents(crafter, recipe, contents)) - return ", missing component." - if(!check_tools(crafter, recipe, contents)) - return ", missing tool." - var/list/parts = del_reqs(recipe, crafter) - var/atom/movable/result - if(ispath(recipe.result, /obj/item/stack)) - result = new recipe.result(get_turf(crafter.loc), recipe.result_amount || 1) - else - result = new recipe.result(get_turf(crafter.loc)) - if(result.atom_storage && recipe.delete_contents) - for(var/obj/item/thing in result) - qdel(thing) - var/datum/reagents/holder = locate() in parts - if(holder) //transfer reagents from ingredients to result - if(!ispath(recipe.result, /obj/item/reagent_containers) && result.reagents) - result.reagents.clear_reagents() - holder.trans_to(result.reagents, holder.total_volume, no_react = TRUE) - parts -= holder - qdel(holder) - result.CheckParts(parts, recipe) - if(send_feedback) - SSblackbox.record_feedback("tally", "object_crafted", 1, result.type) - return result //Send the item back to whatever called this proc so it can handle whatever it wants to do with the new item + var/turf/dest_turf = get_turf(crafter) + + if(!check_contents(crafter, recipe, contents)) + return ", missing component." + + if(!check_tools(crafter, recipe, contents)) return ", missing tool." - return ", missing component." + + + + if((recipe.crafting_flags & CRAFT_ONE_PER_TURF) && (locate(recipe.result) in dest_turf)) + return ", already one here!" + + if(recipe.crafting_flags & CRAFT_CHECK_DIRECTION) + if(!valid_build_direction(dest_turf, crafter.dir, is_fulltile = (recipe.crafting_flags & CRAFT_IS_FULLTILE))) + return ", won't fit here!" + + if(recipe.crafting_flags & CRAFT_ON_SOLID_GROUND) + if(isclosedturf(dest_turf)) + return ", cannot be made on a wall!" + + if(is_type_in_typecache(dest_turf, GLOB.turfs_without_ground)) + if(!locate(/obj/structure/thermoplastic) in dest_turf) // for tram construction + return ", must be made on solid ground!" + + if(recipe.crafting_flags & CRAFT_CHECK_DENSITY) + for(var/obj/object in dest_turf) + if(object.density && !(object.obj_flags & IGNORE_DENSITY) || object.obj_flags & BLOCKS_CONSTRUCTION) + return ", something is in the way!" + + if(recipe.placement_checks & STACK_CHECK_CARDINALS) + var/turf/nearby_turf + for(var/direction in GLOB.cardinals) + nearby_turf = get_step(dest_turf, direction) + if(locate(recipe.result) in nearby_turf) + to_chat(crafter, span_warning("\The [recipe.name] must not be built directly adjacent to another!")) + return ", can't be adjacent to another!" + + if(recipe.placement_checks & STACK_CHECK_ADJACENT) + if(locate(recipe.result) in range(1, dest_turf)) + return ", can't be near another!" + + if(recipe.placement_checks & STACK_CHECK_TRAM_FORBIDDEN) + if(locate(/obj/structure/transport/linear/tram) in dest_turf || locate(/obj/structure/thermoplastic) in dest_turf) + return ", can't be on tram!" + + if(recipe.placement_checks & STACK_CHECK_TRAM_EXCLUSIVE) + if(!locate(/obj/structure/transport/linear/tram) in dest_turf) + return ", must be made on a tram!" + + //If we're a mob we'll try a do_after; non mobs will instead instantly construct the item + if(ismob(crafter) && !do_after(crafter, recipe.time, target = crafter)) + return "." + contents = get_surroundings(crafter, recipe.blacklist) + if(!check_contents(crafter, recipe, contents)) + return ", missing component." + if(!check_tools(crafter, recipe, contents)) + return ", missing tool." + var/list/parts = del_reqs(recipe, crafter) + var/atom/movable/result + if(ispath(recipe.result, /obj/item/stack)) + result = new recipe.result(get_turf(crafter.loc), recipe.result_amount || 1) + result.dir = crafter.dir + else + result = new recipe.result(get_turf(crafter.loc)) + result.dir = crafter.dir + if(result.atom_storage && recipe.delete_contents) + for(var/obj/item/thing in result) + qdel(thing) + var/datum/reagents/holder = locate() in parts + if(holder) //transfer reagents from ingredients to result + if(!ispath(recipe.result, /obj/item/reagent_containers) && result.reagents) + result.reagents.clear_reagents() + holder.trans_to(result.reagents, holder.total_volume, no_react = TRUE) + parts -= holder + qdel(holder) + result.CheckParts(parts, recipe) + if(send_feedback) + SSblackbox.record_feedback("tally", "object_crafted", 1, result.type) + return result //Send the item back to whatever called this proc so it can handle whatever it wants to do with the new item /*Del reqs works like this: @@ -369,7 +413,7 @@ qdel(DL) /datum/component/personal_crafting/proc/is_recipe_available(datum/crafting_recipe/recipe, mob/user) - if(!recipe.always_available && !(recipe.type in user?.mind?.learned_recipes)) //User doesn't actually know how to make this. + if((recipe.crafting_flags & CRAFT_MUST_BE_LEARNED) && !(recipe.type in user?.mind?.learned_recipes)) //User doesn't actually know how to make this. return FALSE if (recipe.category == CAT_CULT && !IS_CULTIST(user)) // Skip blood cult recipes if not cultist return FALSE diff --git a/code/datums/components/crafting/doors.dm b/code/datums/components/crafting/doors.dm index e8fcb3fdfd915..d9ed708904e51 100644 --- a/code/datums/components/crafting/doors.dm +++ b/code/datums/components/crafting/doors.dm @@ -9,7 +9,7 @@ tool_behaviors = list(TOOL_SCREWDRIVER, TOOL_MULTITOOL, TOOL_WIRECUTTER, TOOL_WELDER) time = 10 SECONDS category = CAT_DOORS - one_per_turf = TRUE + crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF /datum/crafting_recipe/blast_doors name = "Blast Door" @@ -22,4 +22,4 @@ tool_behaviors = list(TOOL_SCREWDRIVER, TOOL_MULTITOOL, TOOL_WIRECUTTER, TOOL_WELDER) time = 30 SECONDS category = CAT_DOORS - one_per_turf = TRUE + crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF diff --git a/code/datums/components/crafting/melee_weapon.dm b/code/datums/components/crafting/melee_weapon.dm index 869d223a758c9..1c4150585fccc 100644 --- a/code/datums/components/crafting/melee_weapon.dm +++ b/code/datums/components/crafting/melee_weapon.dm @@ -144,7 +144,6 @@ /datum/crafting_recipe/house_edge name = "House Edge" result = /obj/item/house_edge - always_available = FALSE tool_behaviors = list(TOOL_WRENCH, TOOL_SCREWDRIVER, TOOL_WELDER) reqs = list( /obj/item/v8_engine = 1, @@ -157,6 +156,7 @@ ) time = 10 SECONDS category = CAT_WEAPON_MELEE + crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_MUST_BE_LEARNED /datum/crafting_recipe/giant_wrench name = "Big Slappy" diff --git a/code/datums/components/crafting/misc.dm b/code/datums/components/crafting/misc.dm index 264ff98156533..606cf1fc29262 100644 --- a/code/datums/components/crafting/misc.dm +++ b/code/datums/components/crafting/misc.dm @@ -11,8 +11,8 @@ time = 3 SECONDS reqs = list(/obj/item/stack/sheet/bone = 5) result = /obj/item/skeleton_key - always_available = FALSE category = CAT_MISC + crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_MUST_BE_LEARNED /datum/crafting_recipe/coffee_cartridge name = "Bootleg Coffee Cartridge" diff --git a/code/datums/components/crafting/ranged_weapon.dm b/code/datums/components/crafting/ranged_weapon.dm index ace9f2c7c8b0d..0cf681ac2b107 100644 --- a/code/datums/components/crafting/ranged_weapon.dm +++ b/code/datums/components/crafting/ranged_weapon.dm @@ -222,7 +222,6 @@ /datum/crafting_recipe/pipegun_prime name = "Regal Pipegun" - always_available = FALSE result = /obj/item/gun/ballistic/rifle/boltaction/pipegun/prime reqs = list( /obj/item/gun/ballistic/rifle/boltaction/pipegun = 1, @@ -235,10 +234,10 @@ tool_paths = list(/obj/item/clothing/gloves/color/yellow, /obj/item/clothing/mask/gas, /obj/item/melee/baton/security/cattleprod) time = 30 SECONDS //contemplate for a bit category = CAT_WEAPON_RANGED + crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_MUST_BE_LEARNED /datum/crafting_recipe/deagle_prime //When you factor in the makarov (7 tc), the toolbox (1 tc), and the emag (3 tc), this comes to a total of 18 TC or thereabouts. Igorning the 20k pricetag, obviously. name = "Regal Condor" - always_available = FALSE result = /obj/item/gun/ballistic/automatic/pistol/deagle/regal reqs = list( /obj/item/gun/ballistic/automatic/pistol = 1, @@ -258,6 +257,7 @@ ) time = 30 SECONDS category = CAT_WEAPON_RANGED + crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_MUST_BE_LEARNED /datum/crafting_recipe/deagle_prime/New() ..() @@ -265,7 +265,6 @@ /datum/crafting_recipe/deagle_prime_mag name = "Regal Condor Magazine (10mm Reaper)" - always_available = FALSE result = /obj/item/ammo_box/magazine/r10mm reqs = list( /obj/item/stack/sheet/iron = 10, @@ -283,10 +282,10 @@ ) time = 5 SECONDS category = CAT_WEAPON_RANGED + crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_MUST_BE_LEARNED /datum/crafting_recipe/trash_cannon name = "Trash Cannon" - always_available = FALSE tool_behaviors = list(TOOL_WELDER, TOOL_SCREWDRIVER) result = /obj/structure/cannon/trash reqs = list( @@ -297,6 +296,7 @@ /obj/item/storage/toolbox = 1, ) category = CAT_WEAPON_RANGED + crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_MUST_BE_LEARNED /datum/crafting_recipe/laser_musket name = "Laser Musket" @@ -316,7 +316,6 @@ /datum/crafting_recipe/laser_musket_prime name = "Heroic Laser Musket" - always_available = FALSE result = /obj/item/gun/energy/laser/musket/prime reqs = list( /obj/item/gun/energy/laser/musket = 1, @@ -329,6 +328,7 @@ tool_paths = list(/obj/item/clothing/head/cowboy, /obj/item/clothing/shoes/cowboy) time = 30 SECONDS //contemplate for a bit category = CAT_WEAPON_RANGED + crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_MUST_BE_LEARNED /datum/crafting_recipe/smoothbore_disabler name = "Smoothbore Disabler" @@ -347,7 +347,6 @@ /datum/crafting_recipe/smoothbore_disabler_prime name = "Elite Smoothbore Disabler" - always_available = FALSE result = /obj/item/gun/energy/disabler/smoothbore/prime reqs = list( /obj/item/gun/energy/disabler/smoothbore = 1, @@ -358,3 +357,4 @@ tool_behaviors = list(TOOL_SCREWDRIVER) time = 20 SECONDS category = CAT_WEAPON_RANGED + crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_MUST_BE_LEARNED diff --git a/code/datums/components/crafting/structures.dm b/code/datums/components/crafting/structures.dm index 2cbe2c353020b..c4a9b48ec36b6 100644 --- a/code/datums/components/crafting/structures.dm +++ b/code/datums/components/crafting/structures.dm @@ -11,33 +11,33 @@ /datum/crafting_recipe/rib name = "Colossal Rib" - always_available = FALSE reqs = list( /obj/item/stack/sheet/bone = 10, /datum/reagent/fuel/oil = 5, ) result = /obj/structure/statue/bone/rib category = CAT_STRUCTURE + crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_MUST_BE_LEARNED /datum/crafting_recipe/skull name = "Skull Carving" - always_available = FALSE reqs = list( /obj/item/stack/sheet/bone = 6, /datum/reagent/fuel/oil = 5, ) result = /obj/structure/statue/bone/skull category = CAT_STRUCTURE + crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_MUST_BE_LEARNED /datum/crafting_recipe/halfskull name = "Cracked Skull Carving" - always_available = FALSE reqs = list( /obj/item/stack/sheet/bone = 3, /datum/reagent/fuel/oil = 5, ) result = /obj/structure/statue/bone/skull/half category = CAT_STRUCTURE + crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_MUST_BE_LEARNED /datum/crafting_recipe/firecabinet name = "Fire Axe Cabinet" @@ -65,7 +65,6 @@ name = "Syndicate Uplink Beacon" result = /obj/structure/syndicate_uplink_beacon tool_behaviors = list(TOOL_SCREWDRIVER) - always_available = FALSE time = 6 SECONDS reqs = list( /obj/item/stack/sheet/iron = 5, @@ -74,3 +73,4 @@ /obj/item/stack/ore/bluespace_crystal = 1, ) category = CAT_STRUCTURE + crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_MUST_BE_LEARNED diff --git a/code/datums/components/crafting/tools.dm b/code/datums/components/crafting/tools.dm index 8b4b00d006026..d1d303daf8040 100644 --- a/code/datums/components/crafting/tools.dm +++ b/code/datums/components/crafting/tools.dm @@ -19,7 +19,6 @@ /datum/crafting_recipe/boneshovel name = "Serrated Bone Shovel" - always_available = FALSE reqs = list( /obj/item/stack/sheet/bone = 4, /datum/reagent/fuel/oil = 5, @@ -27,6 +26,7 @@ ) result = /obj/item/shovel/serrated category = CAT_TOOLS + crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_MUST_BE_LEARNED /datum/crafting_recipe/lasso name = "Bone Lasso" diff --git a/code/datums/components/crafting/weapon_ammo.dm b/code/datums/components/crafting/weapon_ammo.dm index 44b8055b3ff94..55701dbde5349 100644 --- a/code/datums/components/crafting/weapon_ammo.dm +++ b/code/datums/components/crafting/weapon_ammo.dm @@ -20,8 +20,8 @@ ) tool_behaviors = list(TOOL_WIRECUTTER) time = 0.5 SECONDS - always_available = FALSE category = CAT_WEAPON_AMMO + crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_MUST_BE_LEARNED /datum/crafting_recipe/pulseslug name = "Pulse Slug Shell" @@ -85,10 +85,10 @@ /datum/crafting_recipe/trashball name = "Trashball" - always_available = FALSE result = /obj/item/stack/cannonball/trashball reqs = list( /obj/item/stack/sheet = 5, /datum/reagent/consumable/space_cola = 10, ) category = CAT_WEAPON_AMMO + crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_MUST_BE_LEARNED diff --git a/code/game/objects/items/stacks/rods.dm b/code/game/objects/items/stacks/rods.dm index 064f933573ca3..63625536b74b5 100644 --- a/code/game/objects/items/stacks/rods.dm +++ b/code/game/objects/items/stacks/rods.dm @@ -1,19 +1,20 @@ GLOBAL_LIST_INIT(rod_recipes, list ( \ - new/datum/stack_recipe("grille", /obj/structure/grille, 2, time = 1 SECONDS, one_per_turf = TRUE, on_solid_ground = FALSE, check_density = FALSE, category = CAT_STRUCTURE), \ - new/datum/stack_recipe("table frame", /obj/structure/table_frame, 2, time = 1 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ - new/datum/stack_recipe("scooter frame", /obj/item/scooter_frame, 10, time = 2.5 SECONDS, one_per_turf = FALSE, category = CAT_ENTERTAINMENT), \ - new/datum/stack_recipe("linen bin", /obj/structure/bedsheetbin/empty, 2, time = 0.5 SECONDS, one_per_turf = FALSE, category = CAT_CONTAINERS), \ - new/datum/stack_recipe("railing", /obj/structure/railing, 2, time = 1 SECONDS, check_direction = TRUE, category = CAT_STRUCTURE), \ - new/datum/stack_recipe("railing corner", /obj/structure/railing/corner, 1, time = 1 SECONDS, check_direction = TRUE, category = CAT_STRUCTURE), \ - new/datum/stack_recipe("railing end", /obj/structure/railing/corner/end, 1, time = 1 SECONDS, check_direction = TRUE, category = CAT_STRUCTURE), \ - new/datum/stack_recipe("railing end (flipped)", /obj/structure/railing/corner/end/flip, 1, time = 1 SECONDS, check_direction = TRUE, category = CAT_STRUCTURE), \ - new/datum/stack_recipe("tank holder", /obj/structure/tank_holder, 2, time = 0.5 SECONDS, one_per_turf = TRUE, on_solid_ground = FALSE, check_density = FALSE, category = CAT_FURNITURE), \ - new/datum/stack_recipe("ladder", /obj/structure/ladder/crafted, 15, time = 15 SECONDS, one_per_turf = TRUE, on_solid_ground = FALSE, check_density = FALSE, category = CAT_STRUCTURE), \ + new/datum/stack_recipe("grille", /obj/structure/grille, 2, time = 1 SECONDS, crafting_flags = CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_STRUCTURE), \ + new/datum/stack_recipe("table frame", /obj/structure/table_frame, 2, time = 1 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ + new/datum/stack_recipe("scooter frame", /obj/item/scooter_frame, 10, time = 2.5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY, category = CAT_ENTERTAINMENT), \ + new/datum/stack_recipe("linen bin", /obj/structure/bedsheetbin/empty, 2, time = 0.5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY, category = CAT_CONTAINERS), \ + new/datum/stack_recipe("railing", /obj/structure/railing, 2, time = 1 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_CHECK_DIRECTION, category = CAT_STRUCTURE), \ + new/datum/stack_recipe("railing corner", /obj/structure/railing/corner, 1, time = 1 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_CHECK_DIRECTION, category = CAT_STRUCTURE), \ + new/datum/stack_recipe("railing end", /obj/structure/railing/corner/end, 1, time = 1 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_CHECK_DIRECTION, category = CAT_STRUCTURE), \ + new/datum/stack_recipe("railing end (flipped)", /obj/structure/railing/corner/end/flip, 1, time = 1 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_CHECK_DIRECTION, category = CAT_STRUCTURE), \ + new/datum/stack_recipe("tank holder", /obj/structure/tank_holder, 2, time = 0.5 SECONDS, crafting_flags = CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ + new/datum/stack_recipe("ladder", /obj/structure/ladder/crafted, 15, time = 15 SECONDS, crafting_flags = CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_STRUCTURE), \ new/datum/stack_recipe("catwalk floor tile", /obj/item/stack/tile/catwalk_tile, 1, 4, 20, category = CAT_TILES), \ - new/datum/stack_recipe("stairs frame", /obj/structure/stairs_frame, 10, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_STRUCTURE), \ - new/datum/stack_recipe("white cane", /obj/item/cane/white, 3, time = 1 SECONDS, one_per_turf = FALSE, category = CAT_TOOLS), \ - new/datum/stack_recipe("sharpened iron rod", /obj/item/ammo_casing/rebar, 1, time = 0.2 SECONDS, one_per_turf = FALSE, category = CAT_WEAPON_AMMO), \ + new/datum/stack_recipe("stairs frame", /obj/structure/stairs_frame, 10, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_STRUCTURE), \ + new/datum/stack_recipe("white cane", /obj/item/cane/white, 3, time = 1 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY, category = CAT_TOOLS), \ + new/datum/stack_recipe("sharpened iron rod", /obj/item/ammo_casing/rebar, 1, time = 0.2 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY, category = CAT_WEAPON_AMMO), \ )) + /obj/item/stack/rods name = "iron rod" desc = "Some rods. Can be used for building or something." diff --git a/code/game/objects/items/stacks/sheets/glass.dm b/code/game/objects/items/stacks/sheets/glass.dm index 1f66b80cf55b7..e93b8c4cea59d 100644 --- a/code/game/objects/items/stacks/sheets/glass.dm +++ b/code/game/objects/items/stacks/sheets/glass.dm @@ -9,9 +9,9 @@ * Glass sheets */ GLOBAL_LIST_INIT(glass_recipes, list ( \ - new/datum/stack_recipe("directional window", /obj/structure/window/unanchored, time = 0.5 SECONDS, on_solid_ground = TRUE, check_direction = TRUE, category = CAT_WINDOWS), \ - new/datum/stack_recipe("fulltile window", /obj/structure/window/fulltile/unanchored, 2, time = 1 SECONDS, on_solid_ground = TRUE, is_fulltile = TRUE, category = CAT_WINDOWS), \ - new/datum/stack_recipe("glass shard", /obj/item/shard, time = 0, on_solid_ground = TRUE, category = CAT_MISC), \ + new/datum/stack_recipe("directional window", /obj/structure/window/unanchored, time = 0.5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ON_SOLID_GROUND | CRAFT_CHECK_DIRECTION, category = CAT_WINDOWS), \ + new/datum/stack_recipe("fulltile window", /obj/structure/window/fulltile/unanchored, 2, time = 1 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ON_SOLID_GROUND | CRAFT_IS_FULLTILE, category = CAT_WINDOWS), \ + new/datum/stack_recipe("glass shard", /obj/item/shard, time = 0, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ON_SOLID_GROUND, category = CAT_MISC), \ new/datum/stack_recipe("glass tile", /obj/item/stack/tile/glass, 1, 4, 20, category = CAT_TILES) \ )) @@ -82,9 +82,9 @@ GLOBAL_LIST_INIT(glass_recipes, list ( \ return ..() GLOBAL_LIST_INIT(pglass_recipes, list ( \ - new/datum/stack_recipe("directional window", /obj/structure/window/plasma/unanchored, time = 0.5 SECONDS, on_solid_ground = TRUE, check_direction = TRUE, category = CAT_WINDOWS), \ - new/datum/stack_recipe("fulltile window", /obj/structure/window/plasma/fulltile/unanchored, 2, time = 2 SECONDS, on_solid_ground = TRUE, is_fulltile = TRUE, category = CAT_WINDOWS), \ - new/datum/stack_recipe("plasma glass shard", /obj/item/shard/plasma, time = 20, on_solid_ground = TRUE, category = CAT_MISC), \ + new/datum/stack_recipe("directional window", /obj/structure/window/plasma/unanchored, time = 0.5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ON_SOLID_GROUND | CRAFT_CHECK_DIRECTION, category = CAT_WINDOWS), \ + new/datum/stack_recipe("fulltile window", /obj/structure/window/plasma/fulltile/unanchored, 2, time = 2 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ON_SOLID_GROUND | CRAFT_IS_FULLTILE, category = CAT_WINDOWS), \ + new/datum/stack_recipe("plasma glass shard", /obj/item/shard/plasma, time = 20, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ON_SOLID_GROUND, category = CAT_MISC), \ new/datum/stack_recipe("plasma glass tile", /obj/item/stack/tile/glass/plasma, 1, 4, 20, category = CAT_TILES) \ )) @@ -138,11 +138,11 @@ GLOBAL_LIST_INIT(pglass_recipes, list ( \ * Reinforced glass sheets */ GLOBAL_LIST_INIT(reinforced_glass_recipes, list ( \ - new/datum/stack_recipe("windoor frame", /obj/structure/windoor_assembly, 5, time = 0, on_solid_ground = TRUE, check_direction = TRUE, category = CAT_WINDOWS), \ + new/datum/stack_recipe("windoor frame", /obj/structure/windoor_assembly, 5, time = 0, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ON_SOLID_GROUND | CRAFT_CHECK_DIRECTION, category = CAT_WINDOWS), \ null, \ - new/datum/stack_recipe("directional reinforced window", /obj/structure/window/reinforced/unanchored, time = 0.5 SECONDS, on_solid_ground = TRUE, check_direction = TRUE, category = CAT_WINDOWS), \ - new/datum/stack_recipe("fulltile reinforced window", /obj/structure/window/reinforced/fulltile/unanchored, 2, time = 2 SECONDS, on_solid_ground = TRUE, is_fulltile = TRUE, category = CAT_WINDOWS), \ - new/datum/stack_recipe("glass shard", /obj/item/shard, time = 10, on_solid_ground = TRUE, category = CAT_MISC), \ + new/datum/stack_recipe("directional reinforced window", /obj/structure/window/reinforced/unanchored, time = 0.5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ON_SOLID_GROUND | CRAFT_CHECK_DIRECTION, category = CAT_WINDOWS), \ + new/datum/stack_recipe("fulltile reinforced window", /obj/structure/window/reinforced/fulltile/unanchored, 2, time = 2 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ON_SOLID_GROUND | CRAFT_IS_FULLTILE, category = CAT_WINDOWS), \ + new/datum/stack_recipe("glass shard", /obj/item/shard, time = 10, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ON_SOLID_GROUND, category = CAT_MISC), \ new/datum/stack_recipe("reinforced glass tile", /obj/item/stack/tile/rglass, 1, 4, 20, category = CAT_TILES) \ )) @@ -177,9 +177,9 @@ GLOBAL_LIST_INIT(reinforced_glass_recipes, list ( \ . += GLOB.reinforced_glass_recipes GLOBAL_LIST_INIT(prglass_recipes, list ( \ - new/datum/stack_recipe("directional reinforced window", /obj/structure/window/reinforced/plasma/unanchored, time = 0.5 SECONDS, on_solid_ground = TRUE, check_direction = TRUE, category = CAT_WINDOWS), \ - new/datum/stack_recipe("fulltile reinforced window", /obj/structure/window/reinforced/plasma/fulltile/unanchored, 2, time = 2 SECONDS, on_solid_ground = TRUE, is_fulltile = TRUE, category = CAT_WINDOWS), \ - new/datum/stack_recipe("plasma glass shard", /obj/item/shard/plasma, time = 40, on_solid_ground = TRUE, category = CAT_MISC), \ + new/datum/stack_recipe("directional reinforced window", /obj/structure/window/reinforced/plasma/unanchored, time = 0.5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ON_SOLID_GROUND | CRAFT_CHECK_DIRECTION, category = CAT_WINDOWS), \ + new/datum/stack_recipe("fulltile reinforced window", /obj/structure/window/reinforced/plasma/fulltile/unanchored, 2, time = 2 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ON_SOLID_GROUND | CRAFT_IS_FULLTILE, category = CAT_WINDOWS), \ + new/datum/stack_recipe("plasma glass shard", /obj/item/shard/plasma, time = 40, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ON_SOLID_GROUND, category = CAT_MISC), \ new/datum/stack_recipe("reinforced plasma glass tile", /obj/item/stack/tile/rglass/plasma, 1, 4, 20, category = CAT_TILES) \ )) @@ -212,8 +212,8 @@ GLOBAL_LIST_INIT(prglass_recipes, list ( \ . += GLOB.prglass_recipes GLOBAL_LIST_INIT(titaniumglass_recipes, list( - new/datum/stack_recipe("shuttle window", /obj/structure/window/reinforced/shuttle/unanchored, 2, time = 0.5 SECONDS, on_solid_ground = TRUE, check_direction = TRUE, is_fulltile = TRUE, category = CAT_WINDOWS), \ - new/datum/stack_recipe("titanium glass shard", /obj/item/shard/titanium, time = 40, on_solid_ground = TRUE, category = CAT_MISC) \ + new/datum/stack_recipe("shuttle window", /obj/structure/window/reinforced/shuttle/unanchored, 2, time = 0.5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ON_SOLID_GROUND | CRAFT_CHECK_DIRECTION | CRAFT_IS_FULLTILE, category = CAT_WINDOWS), \ + new/datum/stack_recipe("titanium glass shard", /obj/item/shard/titanium, time = 40, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ON_SOLID_GROUND, category = CAT_MISC) \ )) /obj/item/stack/sheet/titaniumglass @@ -241,8 +241,8 @@ GLOBAL_LIST_INIT(titaniumglass_recipes, list( . += GLOB.titaniumglass_recipes GLOBAL_LIST_INIT(plastitaniumglass_recipes, list( - new/datum/stack_recipe("plastitanium window", /obj/structure/window/reinforced/plasma/plastitanium/unanchored, 2, time = 2 SECONDS, on_solid_ground = TRUE, is_fulltile = TRUE, category = CAT_WINDOWS), \ - new/datum/stack_recipe("plastitanium glass shard", /obj/item/shard/plastitanium, time = 60, on_solid_ground = TRUE, category = CAT_MISC) \ + new/datum/stack_recipe("plastitanium window", /obj/structure/window/reinforced/plasma/plastitanium/unanchored, 2, time = 2 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ON_SOLID_GROUND | CRAFT_IS_FULLTILE, category = CAT_WINDOWS), \ + new/datum/stack_recipe("plastitanium glass shard", /obj/item/shard/plastitanium, time = 60, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ON_SOLID_GROUND, category = CAT_MISC) \ )) /obj/item/stack/sheet/plastitaniumglass diff --git a/code/game/objects/items/stacks/sheets/leather.dm b/code/game/objects/items/stacks/sheets/leather.dm index 851e544ff8a83..2d1636e9e165a 100644 --- a/code/game/objects/items/stacks/sheets/leather.dm +++ b/code/game/objects/items/stacks/sheets/leather.dm @@ -14,8 +14,8 @@ merge_type = /obj/item/stack/sheet/animalhide/human GLOBAL_LIST_INIT(human_recipes, list( \ - new/datum/stack_recipe("bloated human costume", /obj/item/clothing/suit/hooded/bloated_human, 5, check_density = FALSE, category = CAT_CLOTHING), \ - new/datum/stack_recipe("human skin hat", /obj/item/clothing/head/fedora/human_leather, 1, check_density = FALSE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("bloated human costume", /obj/item/clothing/suit/hooded/bloated_human, 5, crafting_flags = NONE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("human skin hat", /obj/item/clothing/head/fedora/human_leather, 1, crafting_flags = NONE, category = CAT_CLOTHING), \ )) /obj/item/stack/sheet/animalhide/human/get_main_recipes() @@ -55,9 +55,9 @@ GLOBAL_LIST_INIT(human_recipes, list( \ amount = 5 GLOBAL_LIST_INIT(gondola_recipes, list ( \ - new/datum/stack_recipe("gondola mask", /obj/item/clothing/mask/gondola, 1, check_density = FALSE, category = CAT_CLOTHING), \ - new/datum/stack_recipe("gondola suit", /obj/item/clothing/under/costume/gondola, 2, check_density = FALSE, category = CAT_CLOTHING), \ - new/datum/stack_recipe("gondola bedsheet", /obj/item/bedsheet/gondola, 1, check_density = FALSE, category = CAT_FURNITURE), \ + new/datum/stack_recipe("gondola mask", /obj/item/clothing/mask/gondola, 1, crafting_flags = NONE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("gondola suit", /obj/item/clothing/under/costume/gondola, 2, crafting_flags = NONE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("gondola bedsheet", /obj/item/bedsheet/gondola, 1, crafting_flags = NONE, category = CAT_FURNITURE), \ )) /obj/item/stack/sheet/animalhide/gondola @@ -73,7 +73,7 @@ GLOBAL_LIST_INIT(gondola_recipes, list ( \ . += GLOB.gondola_recipes GLOBAL_LIST_INIT(corgi_recipes, list ( \ - new/datum/stack_recipe("corgi costume", /obj/item/clothing/suit/hooded/ian_costume, 3, check_density = FALSE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("corgi costume", /obj/item/clothing/suit/hooded/ian_costume, 3, crafting_flags = NONE, category = CAT_CLOTHING), \ )) /obj/item/stack/sheet/animalhide/corgi/get_main_recipes() @@ -100,8 +100,8 @@ GLOBAL_LIST_INIT(corgi_recipes, list ( \ merge_type = /obj/item/stack/sheet/animalhide/monkey GLOBAL_LIST_INIT(monkey_recipes, list ( \ - new/datum/stack_recipe("monkey mask", /obj/item/clothing/mask/gas/monkeymask, 1, check_density = FALSE, category = CAT_CLOTHING), \ - new/datum/stack_recipe("monkey suit", /obj/item/clothing/suit/costume/monkeysuit, 2, check_density = FALSE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("monkey mask", /obj/item/clothing/mask/gas/monkeymask, 1, crafting_flags = NONE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("monkey suit", /obj/item/clothing/suit/costume/monkeysuit, 2, crafting_flags = NONE, category = CAT_CLOTHING), \ )) /obj/item/stack/sheet/animalhide/monkey/get_main_recipes() @@ -131,8 +131,8 @@ GLOBAL_LIST_INIT(monkey_recipes, list ( \ merge_type = /obj/item/stack/sheet/animalhide/xeno GLOBAL_LIST_INIT(xeno_recipes, list ( \ - new/datum/stack_recipe("alien helmet", /obj/item/clothing/head/costume/xenos, 1, check_density = FALSE, category = CAT_CLOTHING), \ - new/datum/stack_recipe("alien suit", /obj/item/clothing/suit/costume/xenos, 2, check_density = FALSE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("alien helmet", /obj/item/clothing/head/costume/xenos, 1, crafting_flags = NONE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("alien suit", /obj/item/clothing/suit/costume/xenos, 2, crafting_flags = NONE, category = CAT_CLOTHING), \ )) /obj/item/stack/sheet/animalhide/xeno/get_main_recipes() @@ -151,11 +151,11 @@ GLOBAL_LIST_INIT(xeno_recipes, list ( \ merge_type = /obj/item/stack/sheet/animalhide/carp GLOBAL_LIST_INIT(carp_recipes, list ( \ - new/datum/stack_recipe("carp costume", /obj/item/clothing/suit/hooded/carp_costume, 4, check_density = FALSE, category = CAT_CLOTHING), \ - new/datum/stack_recipe("carp mask", /obj/item/clothing/mask/gas/carp, 1, check_density = FALSE, category = CAT_CLOTHING), \ - new/datum/stack_recipe("carpskin chair", /obj/structure/chair/comfy/carp, 2, check_density = FALSE, category = CAT_FURNITURE), \ - new/datum/stack_recipe("carpskin suit", /obj/item/clothing/under/suit/carpskin, 3, check_density = FALSE, category = CAT_CLOTHING), \ - new/datum/stack_recipe("carpskin fedora", /obj/item/clothing/head/fedora/carpskin, 2, check_density = FALSE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("carp costume", /obj/item/clothing/suit/hooded/carp_costume, 4, crafting_flags = NONE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("carp mask", /obj/item/clothing/mask/gas/carp, 1, crafting_flags = NONE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("carpskin chair", /obj/structure/chair/comfy/carp, 2, crafting_flags = NONE, category = CAT_FURNITURE), \ + new/datum/stack_recipe("carpskin suit", /obj/item/clothing/under/suit/carpskin, 3, crafting_flags = NONE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("carpskin fedora", /obj/item/clothing/head/fedora/carpskin, 2, crafting_flags = NONE, category = CAT_CLOTHING), \ )) /obj/item/stack/sheet/animalhide/carp/get_main_recipes() @@ -193,33 +193,33 @@ GLOBAL_LIST_INIT(carp_recipes, list ( \ merge_type = /obj/item/stack/sheet/leather GLOBAL_LIST_INIT(leather_recipes, list ( \ - new/datum/stack_recipe("wallet", /obj/item/storage/wallet, 1, check_density = FALSE, category = CAT_CONTAINERS), \ - new/datum/stack_recipe("muzzle", /obj/item/clothing/mask/muzzle, 2, check_density = FALSE, category = CAT_ENTERTAINMENT), \ - new/datum/stack_recipe("basketball", /obj/item/toy/basketball, 20, check_density = FALSE, category = CAT_ENTERTAINMENT), \ - new/datum/stack_recipe("baseball", /obj/item/toy/beach_ball/baseball, 3, check_density = FALSE, category = CAT_ENTERTAINMENT), \ - new/datum/stack_recipe("saddle", /obj/item/goliath_saddle, 5, check_density = FALSE, category = CAT_EQUIPMENT), \ - new/datum/stack_recipe("leather shoes", /obj/item/clothing/shoes/laceup, 2, check_density = FALSE, category = CAT_CLOTHING), \ - new/datum/stack_recipe("cowboy boots", /obj/item/clothing/shoes/cowboy, 2, check_density = FALSE, category = CAT_CLOTHING), \ - new/datum/stack_recipe("botany gloves", /obj/item/clothing/gloves/botanic_leather, 3, check_density = FALSE, category = CAT_CLOTHING), \ - new/datum/stack_recipe("leather satchel", /obj/item/storage/backpack/satchel/leather, 5, check_density = FALSE, category = CAT_CLOTHING), \ - new/datum/stack_recipe("sheriff vest", /obj/item/clothing/accessory/vest_sheriff, 4, check_density = FALSE, category = CAT_CLOTHING), \ - new/datum/stack_recipe("leather jacket", /obj/item/clothing/suit/jacket/leather, 7, check_density = FALSE, category = CAT_CLOTHING), \ - new/datum/stack_recipe("biker jacket", /obj/item/clothing/suit/jacket/leather/biker, 7, check_density = FALSE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("wallet", /obj/item/storage/wallet, 1, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new/datum/stack_recipe("muzzle", /obj/item/clothing/mask/muzzle, 2, crafting_flags = NONE, category = CAT_ENTERTAINMENT), \ + new/datum/stack_recipe("basketball", /obj/item/toy/basketball, 20, crafting_flags = NONE, category = CAT_ENTERTAINMENT), \ + new/datum/stack_recipe("baseball", /obj/item/toy/beach_ball/baseball, 3, crafting_flags = NONE, category = CAT_ENTERTAINMENT), \ + new/datum/stack_recipe("saddle", /obj/item/goliath_saddle, 5, crafting_flags = NONE, category = CAT_EQUIPMENT), \ + new/datum/stack_recipe("leather shoes", /obj/item/clothing/shoes/laceup, 2, crafting_flags = NONE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("cowboy boots", /obj/item/clothing/shoes/cowboy, 2, crafting_flags = NONE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("botany gloves", /obj/item/clothing/gloves/botanic_leather, 3, crafting_flags = NONE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("leather satchel", /obj/item/storage/backpack/satchel/leather, 5, crafting_flags = NONE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("sheriff vest", /obj/item/clothing/accessory/vest_sheriff, 4, crafting_flags = NONE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("leather jacket", /obj/item/clothing/suit/jacket/leather, 7, crafting_flags = NONE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("biker jacket", /obj/item/clothing/suit/jacket/leather/biker, 7, crafting_flags = NONE, category = CAT_CLOTHING), \ new/datum/stack_recipe_list("belts", list( \ - new/datum/stack_recipe("tool belt", /obj/item/storage/belt/utility, 4, check_density = FALSE, category = CAT_CONTAINERS), \ - new/datum/stack_recipe("botanical belt", /obj/item/storage/belt/plant, 2, check_density = FALSE, category = CAT_CONTAINERS), \ - new/datum/stack_recipe("janitorial belt", /obj/item/storage/belt/janitor, 2, check_density = FALSE, category = CAT_CONTAINERS), \ - new/datum/stack_recipe("medical belt", /obj/item/storage/belt/medical, 2, check_density = FALSE, category = CAT_CONTAINERS), \ - new/datum/stack_recipe("security belt", /obj/item/storage/belt/security, 2, check_density = FALSE, category = CAT_CONTAINERS), \ - new/datum/stack_recipe("shoulder holster", /obj/item/storage/belt/holster, 3, check_density = FALSE, category = CAT_CONTAINERS), \ - new/datum/stack_recipe("bandolier", /obj/item/storage/belt/bandolier, 5, check_density = FALSE, category = CAT_CONTAINERS), \ + new/datum/stack_recipe("tool belt", /obj/item/storage/belt/utility, 4, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new/datum/stack_recipe("botanical belt", /obj/item/storage/belt/plant, 2, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new/datum/stack_recipe("janitorial belt", /obj/item/storage/belt/janitor, 2, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new/datum/stack_recipe("medical belt", /obj/item/storage/belt/medical, 2, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new/datum/stack_recipe("security belt", /obj/item/storage/belt/security, 2, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new/datum/stack_recipe("shoulder holster", /obj/item/storage/belt/holster, 3, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new/datum/stack_recipe("bandolier", /obj/item/storage/belt/bandolier, 5, crafting_flags = NONE, category = CAT_CONTAINERS), \ )), new/datum/stack_recipe_list("cowboy hats", list( \ - new/datum/stack_recipe("sheriff hat", /obj/item/clothing/head/cowboy/brown, 2, check_density = FALSE, category = CAT_CLOTHING), \ - new/datum/stack_recipe("desperado hat", /obj/item/clothing/head/cowboy/black, 2, check_density = FALSE, category = CAT_CLOTHING), \ - new/datum/stack_recipe("ten-gallon hat", /obj/item/clothing/head/cowboy/white, 2, check_density = FALSE, category = CAT_CLOTHING), \ - new/datum/stack_recipe("deputy hat", /obj/item/clothing/head/cowboy/red, 2, check_density = FALSE, category = CAT_CLOTHING), \ - new/datum/stack_recipe("drifter hat", /obj/item/clothing/head/cowboy/grey, 2, check_density = FALSE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("sheriff hat", /obj/item/clothing/head/cowboy/brown, 2, crafting_flags = NONE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("desperado hat", /obj/item/clothing/head/cowboy/black, 2, crafting_flags = NONE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("ten-gallon hat", /obj/item/clothing/head/cowboy/white, 2, crafting_flags = NONE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("deputy hat", /obj/item/clothing/head/cowboy/red, 2, crafting_flags = NONE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("drifter hat", /obj/item/clothing/head/cowboy/grey, 2, crafting_flags = NONE, category = CAT_CLOTHING), \ )), )) @@ -263,7 +263,7 @@ GLOBAL_LIST_INIT(leather_recipes, list ( \ merge_type = /obj/item/stack/sheet/sinew/wolf GLOBAL_LIST_INIT(sinew_recipes, list ( \ - new/datum/stack_recipe("sinew restraints", /obj/item/restraints/handcuffs/cable/sinew, 1, check_density = FALSE, category = CAT_EQUIPMENT), \ + new/datum/stack_recipe("sinew restraints", /obj/item/restraints/handcuffs/cable/sinew, 1, crafting_flags = NONE, category = CAT_EQUIPMENT), \ )) /obj/item/stack/sheet/sinew/get_main_recipes() diff --git a/code/game/objects/items/stacks/sheets/mineral.dm b/code/game/objects/items/stacks/sheets/mineral.dm index 1b3d66a7dae92..f6e7d797fd95c 100644 --- a/code/game/objects/items/stacks/sheets/mineral.dm +++ b/code/game/objects/items/stacks/sheets/mineral.dm @@ -24,8 +24,8 @@ Mineral Sheets */ GLOBAL_LIST_INIT(sandstone_recipes, list ( \ - new/datum/stack_recipe("sandstone door", /obj/structure/mineral_door/sandstone, 10, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, applies_mats = TRUE, category = CAT_DOORS), \ - new/datum/stack_recipe("Breakdown into sand", /obj/item/stack/ore/glass, 1, one_per_turf = FALSE, on_solid_ground = TRUE, category = CAT_MISC) \ + new/datum/stack_recipe("sandstone door", /obj/structure/mineral_door/sandstone, 10, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND | CRAFT_APPLIES_MATS, category = CAT_DOORS), \ + new/datum/stack_recipe("Breakdown into sand", /obj/item/stack/ore/glass, 1, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ON_SOLID_GROUND, category = CAT_MISC) \ )) /obj/item/stack/sheet/mineral/sandstone @@ -62,7 +62,7 @@ GLOBAL_LIST_INIT(sandstone_recipes, list ( \ merge_type = /obj/item/stack/sheet/mineral/sandbags GLOBAL_LIST_INIT(sandbag_recipes, list ( \ - new/datum/stack_recipe("sandbags", /obj/structure/barricade/sandbags, 1, time = 3 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_STRUCTURE), \ + new/datum/stack_recipe("sandbags", /obj/structure/barricade/sandbags, 1, time = 3 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_STRUCTURE), \ )) /obj/item/stack/sheet/mineral/sandbags/get_main_recipes() @@ -105,8 +105,8 @@ GLOBAL_LIST_INIT(sandbag_recipes, list ( \ walltype = /turf/closed/wall/mineral/diamond GLOBAL_LIST_INIT(diamond_recipes, list ( \ - new/datum/stack_recipe("diamond door", /obj/structure/mineral_door/transparent/diamond, 10, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, applies_mats = TRUE, category = CAT_DOORS), \ - new/datum/stack_recipe("diamond tile", /obj/item/stack/tile/mineral/diamond, 1, 4, 20, check_density = FALSE, category = CAT_TILES), \ + new/datum/stack_recipe("diamond door", /obj/structure/mineral_door/transparent/diamond, 10, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND | CRAFT_APPLIES_MATS, category = CAT_DOORS), \ + new/datum/stack_recipe("diamond tile", /obj/item/stack/tile/mineral/diamond, 1, 4, 20, crafting_flags = NONE, category = CAT_TILES), \ )) /obj/item/stack/sheet/mineral/diamond/get_main_recipes() @@ -130,8 +130,8 @@ GLOBAL_LIST_INIT(diamond_recipes, list ( \ walltype = /turf/closed/wall/mineral/uranium GLOBAL_LIST_INIT(uranium_recipes, list ( \ - new/datum/stack_recipe("uranium door", /obj/structure/mineral_door/uranium, 10, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, applies_mats = TRUE, category = CAT_DOORS), \ - new/datum/stack_recipe("uranium tile", /obj/item/stack/tile/mineral/uranium, 1, 4, 20, check_density = FALSE, category = CAT_TILES), \ + new/datum/stack_recipe("uranium door", /obj/structure/mineral_door/uranium, 10, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND | CRAFT_APPLIES_MATS, category = CAT_DOORS), \ + new/datum/stack_recipe("uranium tile", /obj/item/stack/tile/mineral/uranium, 1, 4, 20, crafting_flags = NONE, category = CAT_TILES), \ )) /obj/item/stack/sheet/mineral/uranium/get_main_recipes() @@ -167,8 +167,8 @@ GLOBAL_LIST_INIT(uranium_recipes, list ( \ return TOXLOSS//dont you kids know that stuff is toxic? GLOBAL_LIST_INIT(plasma_recipes, list ( \ - new/datum/stack_recipe("plasma door", /obj/structure/mineral_door/transparent/plasma, 10, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, applies_mats = TRUE, category = CAT_DOORS), \ - new/datum/stack_recipe("plasma tile", /obj/item/stack/tile/mineral/plasma, 1, 4, 20, check_density = FALSE, category = CAT_TILES), \ + new/datum/stack_recipe("plasma door", /obj/structure/mineral_door/transparent/plasma, 10, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND | CRAFT_APPLIES_MATS, category = CAT_DOORS), \ + new/datum/stack_recipe("plasma tile", /obj/item/stack/tile/mineral/plasma, 1, 4, 20, crafting_flags = NONE, category = CAT_TILES), \ )) /obj/item/stack/sheet/mineral/plasma/get_main_recipes() @@ -198,10 +198,10 @@ GLOBAL_LIST_INIT(plasma_recipes, list ( \ walltype = /turf/closed/wall/mineral/gold GLOBAL_LIST_INIT(gold_recipes, list ( \ - new/datum/stack_recipe("golden door", /obj/structure/mineral_door/gold, 10, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, applies_mats = TRUE, category = CAT_DOORS), \ - new/datum/stack_recipe("gold tile", /obj/item/stack/tile/mineral/gold, 1, 4, 20, check_density = FALSE, category = CAT_TILES), \ - new/datum/stack_recipe("blank plaque", /obj/item/plaque, 1, check_density = FALSE, category = CAT_FURNITURE), \ - new/datum/stack_recipe("Simple Crown", /obj/item/clothing/head/costume/crown, 5, check_density = FALSE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("golden door", /obj/structure/mineral_door/gold, 10, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND | CRAFT_APPLIES_MATS, category = CAT_DOORS), \ + new/datum/stack_recipe("gold tile", /obj/item/stack/tile/mineral/gold, 1, 4, 20, crafting_flags = NONE, category = CAT_TILES), \ + new/datum/stack_recipe("blank plaque", /obj/item/plaque, 1, crafting_flags = NONE, category = CAT_FURNITURE), \ + new/datum/stack_recipe("Simple Crown", /obj/item/clothing/head/costume/crown, 5, crafting_flags = NONE, category = CAT_CLOTHING), \ )) /obj/item/stack/sheet/mineral/gold/get_main_recipes() @@ -226,8 +226,8 @@ GLOBAL_LIST_INIT(gold_recipes, list ( \ walltype = /turf/closed/wall/mineral/silver GLOBAL_LIST_INIT(silver_recipes, list ( \ - new/datum/stack_recipe("silver door", /obj/structure/mineral_door/silver, 10, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, applies_mats = TRUE, category = CAT_DOORS), \ - new/datum/stack_recipe("silver tile", /obj/item/stack/tile/mineral/silver, 1, 4, 20, check_density = FALSE, category = CAT_TILES), \ + new/datum/stack_recipe("silver door", /obj/structure/mineral_door/silver, 10, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND | CRAFT_APPLIES_MATS, category = CAT_DOORS), \ + new/datum/stack_recipe("silver tile", /obj/item/stack/tile/mineral/silver, 1, 4, 20, crafting_flags = NONE, category = CAT_TILES), \ )) /obj/item/stack/sheet/mineral/silver/get_main_recipes() @@ -251,7 +251,7 @@ GLOBAL_LIST_INIT(silver_recipes, list ( \ walltype = /turf/closed/wall/mineral/bananium GLOBAL_LIST_INIT(bananium_recipes, list ( \ - new/datum/stack_recipe("bananium tile", /obj/item/stack/tile/mineral/bananium, 1, 4, 20, check_density = FALSE, category = CAT_TILES), \ + new/datum/stack_recipe("bananium tile", /obj/item/stack/tile/mineral/bananium, 1, 4, 20, crafting_flags = NONE, category = CAT_TILES), \ )) /obj/item/stack/sheet/mineral/bananium/get_main_recipes() @@ -282,8 +282,8 @@ GLOBAL_LIST_INIT(bananium_recipes, list ( \ walltype = /turf/closed/wall/mineral/titanium GLOBAL_LIST_INIT(titanium_recipes, list ( \ - new/datum/stack_recipe("Titanium tile", /obj/item/stack/tile/mineral/titanium, 1, 4, 20, check_density = FALSE, category = CAT_TILES), \ - new/datum/stack_recipe("Shuttle seat", /obj/structure/chair/comfy/shuttle, 2, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ + new/datum/stack_recipe("Titanium tile", /obj/item/stack/tile/mineral/titanium, 1, 4, 20, crafting_flags = NONE, category = CAT_TILES), \ + new/datum/stack_recipe("Shuttle seat", /obj/structure/chair/comfy/shuttle, 2, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ )) /obj/item/stack/sheet/mineral/titanium/get_main_recipes() @@ -315,7 +315,7 @@ GLOBAL_LIST_INIT(titanium_recipes, list ( \ walltype = /turf/closed/wall/mineral/plastitanium GLOBAL_LIST_INIT(plastitanium_recipes, list ( \ - new/datum/stack_recipe("plastitanium tile", /obj/item/stack/tile/mineral/plastitanium, 1, 4, 20, check_density = FALSE, category = CAT_TILES), \ + new/datum/stack_recipe("plastitanium tile", /obj/item/stack/tile/mineral/plastitanium, 1, 4, 20, crafting_flags = NONE, category = CAT_TILES), \ )) /obj/item/stack/sheet/mineral/plastitanium/get_main_recipes() @@ -341,10 +341,10 @@ GLOBAL_LIST_INIT(plastitanium_recipes, list ( \ material_type = /datum/material/snow GLOBAL_LIST_INIT(snow_recipes, list ( \ - new/datum/stack_recipe("snow wall", /turf/closed/wall/mineral/snow, 5, time = 4 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_STRUCTURE), \ - new/datum/stack_recipe("snowman", /obj/structure/statue/snow/snowman, 5, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_ENTERTAINMENT), \ - new/datum/stack_recipe("snowball", /obj/item/toy/snowball, 1, check_density = FALSE, category = CAT_WEAPON_RANGED), \ - new/datum/stack_recipe("snow tile", /obj/item/stack/tile/mineral/snow, 1, 4, 20, check_density = FALSE, category = CAT_TILES), \ + new/datum/stack_recipe("snow wall", /turf/closed/wall/mineral/snow, 5, time = 4 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_STRUCTURE), \ + new/datum/stack_recipe("snowman", /obj/structure/statue/snow/snowman, 5, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_ENTERTAINMENT), \ + new/datum/stack_recipe("snowball", /obj/item/toy/snowball, 1, crafting_flags = NONE, category = CAT_WEAPON_RANGED), \ + new/datum/stack_recipe("snow tile", /obj/item/stack/tile/mineral/snow, 1, 4, 20, crafting_flags = NONE, category = CAT_TILES), \ )) /obj/item/stack/sheet/mineral/snow/Initialize(mapload, new_amount, merge, list/mat_override, mat_amt) @@ -421,12 +421,12 @@ GLOBAL_LIST_INIT(adamantine_recipes, list( walltype = /turf/closed/wall/mineral/abductor GLOBAL_LIST_INIT(abductor_recipes, list ( \ - new/datum/stack_recipe("alien bed", /obj/structure/bed/abductor, 2, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ - new/datum/stack_recipe("alien locker", /obj/structure/closet/abductor, 2, time = 2 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ - new/datum/stack_recipe("alien table frame", /obj/structure/table_frame/abductor, 1, time = 2 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ - new/datum/stack_recipe("alien airlock assembly", /obj/structure/door_assembly/door_assembly_abductor, 4, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_DOORS), \ + new/datum/stack_recipe("alien bed", /obj/structure/bed/abductor, 2, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ + new/datum/stack_recipe("alien locker", /obj/structure/closet/abductor, 2, time = 2 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ + new/datum/stack_recipe("alien table frame", /obj/structure/table_frame/abductor, 1, time = 2 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ + new/datum/stack_recipe("alien airlock assembly", /obj/structure/door_assembly/door_assembly_abductor, 4, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_DOORS), \ null, \ - new/datum/stack_recipe("alien floor tile", /obj/item/stack/tile/mineral/abductor, 1, 4, 20, check_density = FALSE, category = CAT_TILES), \ + new/datum/stack_recipe("alien floor tile", /obj/item/stack/tile/mineral/abductor, 1, 4, 20, crafting_flags = NONE, category = CAT_TILES), \ )) /obj/item/stack/sheet/mineral/abductor/get_main_recipes() @@ -469,10 +469,10 @@ GLOBAL_LIST_INIT(abductor_recipes, list ( \ //Metal Hydrogen GLOBAL_LIST_INIT(metalhydrogen_recipes, list( - new /datum/stack_recipe("incomplete servant golem shell", /obj/item/golem_shell/servant, req_amount=20, res_amount=1, check_density = FALSE, category = CAT_ROBOT), - new /datum/stack_recipe("ancient armor", /obj/item/clothing/suit/armor/elder_atmosian, req_amount = 5, res_amount = 1, check_density = FALSE, category = CAT_CLOTHING), - new /datum/stack_recipe("ancient helmet", /obj/item/clothing/head/helmet/elder_atmosian, req_amount = 3, res_amount = 1, check_density = FALSE, category = CAT_CLOTHING), - new /datum/stack_recipe("metallic hydrogen axe", /obj/item/fireaxe/metal_h2_axe, req_amount = 15, res_amount = 1, check_density = FALSE, category = CAT_WEAPON_MELEE), + new /datum/stack_recipe("incomplete servant golem shell", /obj/item/golem_shell/servant, req_amount=20, res_amount=1, crafting_flags = NONE, category = CAT_ROBOT), + new /datum/stack_recipe("ancient armor", /obj/item/clothing/suit/armor/elder_atmosian, req_amount = 5, res_amount = 1, crafting_flags = NONE, category = CAT_CLOTHING), + new /datum/stack_recipe("ancient helmet", /obj/item/clothing/head/helmet/elder_atmosian, req_amount = 3, res_amount = 1, crafting_flags = NONE, category = CAT_CLOTHING), + new /datum/stack_recipe("metallic hydrogen axe", /obj/item/fireaxe/metal_h2_axe, req_amount = 15, res_amount = 1, crafting_flags = NONE, category = CAT_WEAPON_MELEE), )) /obj/item/stack/sheet/mineral/metal_hydrogen diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm index b5d4f596a56a1..36582f3ad5ac1 100644 --- a/code/game/objects/items/stacks/sheets/sheet_types.dm +++ b/code/game/objects/items/stacks/sheets/sheet_types.dm @@ -15,127 +15,127 @@ * Iron */ GLOBAL_LIST_INIT(metal_recipes, list ( \ - new/datum/stack_recipe("stool", /obj/structure/chair/stool, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ - new/datum/stack_recipe("bar stool", /obj/structure/chair/stool/bar, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ - new/datum/stack_recipe("bed", /obj/structure/bed, 2, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ - new/datum/stack_recipe("double bed", /obj/structure/bed/double, 4, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ + new/datum/stack_recipe("stool", /obj/structure/chair/stool, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ + new/datum/stack_recipe("bar stool", /obj/structure/chair/stool/bar, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ + new/datum/stack_recipe("bed", /obj/structure/bed, 2, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ + new/datum/stack_recipe("double bed", /obj/structure/bed/double, 4, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ null, \ new/datum/stack_recipe_list("office chairs", list( \ - new/datum/stack_recipe("dark office chair", /obj/structure/chair/office, 5, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ - new/datum/stack_recipe("light office chair", /obj/structure/chair/office/light, 5, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ + new/datum/stack_recipe("dark office chair", /obj/structure/chair/office, 5, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ + new/datum/stack_recipe("light office chair", /obj/structure/chair/office/light, 5, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ )), \ new/datum/stack_recipe_list("comfy chairs", list( \ - new/datum/stack_recipe("beige comfy chair", /obj/structure/chair/comfy/beige, 2, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ - new/datum/stack_recipe("black comfy chair", /obj/structure/chair/comfy/black, 2, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ - new/datum/stack_recipe("brown comfy chair", /obj/structure/chair/comfy/brown, 2, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ - new/datum/stack_recipe("lime comfy chair", /obj/structure/chair/comfy/lime, 2, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ - new/datum/stack_recipe("teal comfy chair", /obj/structure/chair/comfy/teal, 2, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ + new/datum/stack_recipe("beige comfy chair", /obj/structure/chair/comfy/beige, 2, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ + new/datum/stack_recipe("black comfy chair", /obj/structure/chair/comfy/black, 2, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ + new/datum/stack_recipe("brown comfy chair", /obj/structure/chair/comfy/brown, 2, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ + new/datum/stack_recipe("lime comfy chair", /obj/structure/chair/comfy/lime, 2, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ + new/datum/stack_recipe("teal comfy chair", /obj/structure/chair/comfy/teal, 2, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ )), \ new/datum/stack_recipe_list("sofas", list( - new /datum/stack_recipe("sofa (middle)", /obj/structure/chair/sofa/middle, 1, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), - new /datum/stack_recipe("sofa (left)", /obj/structure/chair/sofa/left, 1, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), - new /datum/stack_recipe("sofa (right)", /obj/structure/chair/sofa/right, 1, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), - new /datum/stack_recipe("sofa (corner)", /obj/structure/chair/sofa/corner, 1, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE) + new /datum/stack_recipe("sofa (middle)", /obj/structure/chair/sofa/middle, 1, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), + new /datum/stack_recipe("sofa (left)", /obj/structure/chair/sofa/left, 1, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), + new /datum/stack_recipe("sofa (right)", /obj/structure/chair/sofa/right, 1, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), + new /datum/stack_recipe("sofa (corner)", /obj/structure/chair/sofa/corner, 1, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE) )), \ new/datum/stack_recipe_list("corporate sofas", list( \ - new /datum/stack_recipe("sofa (middle)", /obj/structure/chair/sofa/corp, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ - new /datum/stack_recipe("sofa (left)", /obj/structure/chair/sofa/corp/left, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ - new /datum/stack_recipe("sofa (right)", /obj/structure/chair/sofa/corp/right, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ - new /datum/stack_recipe("sofa (corner)", /obj/structure/chair/sofa/corp/corner, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ + new /datum/stack_recipe("sofa (middle)", /obj/structure/chair/sofa/corp, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ + new /datum/stack_recipe("sofa (left)", /obj/structure/chair/sofa/corp/left, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ + new /datum/stack_recipe("sofa (right)", /obj/structure/chair/sofa/corp/right, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ + new /datum/stack_recipe("sofa (corner)", /obj/structure/chair/sofa/corp/corner, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ )), \ new /datum/stack_recipe_list("benches", list( \ - new /datum/stack_recipe("bench (middle)", /obj/structure/chair/sofa/bench, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ - new /datum/stack_recipe("bench (left)", /obj/structure/chair/sofa/bench/left, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ - new /datum/stack_recipe("bench (right)", /obj/structure/chair/sofa/bench/right, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ - new /datum/stack_recipe("bench (corner)", /obj/structure/chair/sofa/bench/corner, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ - new /datum/stack_recipe("tram bench (solo)", /obj/structure/chair/sofa/bench/tram/solo, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ - new /datum/stack_recipe("tram bench (middle)", /obj/structure/chair/sofa/bench/tram, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ - new /datum/stack_recipe("tram bench (left)", /obj/structure/chair/sofa/bench/tram/left, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ - new /datum/stack_recipe("tram bench (right)", /obj/structure/chair/sofa/bench/tram/right, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ - new /datum/stack_recipe("tram bench (corner)", /obj/structure/chair/sofa/bench/tram/corner, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ + new /datum/stack_recipe("bench (middle)", /obj/structure/chair/sofa/bench, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ + new /datum/stack_recipe("bench (left)", /obj/structure/chair/sofa/bench/left, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ + new /datum/stack_recipe("bench (right)", /obj/structure/chair/sofa/bench/right, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ + new /datum/stack_recipe("bench (corner)", /obj/structure/chair/sofa/bench/corner, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ + new /datum/stack_recipe("tram bench (solo)", /obj/structure/chair/sofa/bench/tram/solo, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ + new /datum/stack_recipe("tram bench (middle)", /obj/structure/chair/sofa/bench/tram, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ + new /datum/stack_recipe("tram bench (left)", /obj/structure/chair/sofa/bench/tram/left, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ + new /datum/stack_recipe("tram bench (right)", /obj/structure/chair/sofa/bench/tram/right, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ + new /datum/stack_recipe("tram bench (corner)", /obj/structure/chair/sofa/bench/tram/corner, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ )), \ new /datum/stack_recipe_list("chess pieces", list( \ - new /datum/stack_recipe("White Pawn", /obj/structure/chess/whitepawn, 2, time = 1 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_ENTERTAINMENT), \ - new /datum/stack_recipe("White Rook", /obj/structure/chess/whiterook, 2, time = 1 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_ENTERTAINMENT), \ - new /datum/stack_recipe("White Knight", /obj/structure/chess/whiteknight, 2, time = 1 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_ENTERTAINMENT), \ - new /datum/stack_recipe("White Bishop", /obj/structure/chess/whitebishop, 2, time = 1 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_ENTERTAINMENT), \ - new /datum/stack_recipe("White Queen", /obj/structure/chess/whitequeen, 2, time = 1 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_ENTERTAINMENT), \ - new /datum/stack_recipe("White King", /obj/structure/chess/whiteking, 2, time = 1 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_ENTERTAINMENT), \ - new /datum/stack_recipe("Black Pawn", /obj/structure/chess/blackpawn, 2, time = 1 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_ENTERTAINMENT), \ - new /datum/stack_recipe("Black Rook", /obj/structure/chess/blackrook, 2, time = 1 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_ENTERTAINMENT), \ - new /datum/stack_recipe("Black Knight", /obj/structure/chess/blackknight, 2, time = 1 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_ENTERTAINMENT), \ - new /datum/stack_recipe("Black Bishop", /obj/structure/chess/blackbishop, 2, time = 1 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_ENTERTAINMENT), \ - new /datum/stack_recipe("Black Queen", /obj/structure/chess/blackqueen, 2, time = 1 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_ENTERTAINMENT), \ - new /datum/stack_recipe("Black King", /obj/structure/chess/blackking, 2, time = 1 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_ENTERTAINMENT), \ + new /datum/stack_recipe("White Pawn", /obj/structure/chess/whitepawn, 2, time = 1 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_ENTERTAINMENT), \ + new /datum/stack_recipe("White Rook", /obj/structure/chess/whiterook, 2, time = 1 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_ENTERTAINMENT), \ + new /datum/stack_recipe("White Knight", /obj/structure/chess/whiteknight, 2, time = 1 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_ENTERTAINMENT), \ + new /datum/stack_recipe("White Bishop", /obj/structure/chess/whitebishop, 2, time = 1 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_ENTERTAINMENT), \ + new /datum/stack_recipe("White Queen", /obj/structure/chess/whitequeen, 2, time = 1 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_ENTERTAINMENT), \ + new /datum/stack_recipe("White King", /obj/structure/chess/whiteking, 2, time = 1 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_ENTERTAINMENT), \ + new /datum/stack_recipe("Black Pawn", /obj/structure/chess/blackpawn, 2, time = 1 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_ENTERTAINMENT), \ + new /datum/stack_recipe("Black Rook", /obj/structure/chess/blackrook, 2, time = 1 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_ENTERTAINMENT), \ + new /datum/stack_recipe("Black Knight", /obj/structure/chess/blackknight, 2, time = 1 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_ENTERTAINMENT), \ + new /datum/stack_recipe("Black Bishop", /obj/structure/chess/blackbishop, 2, time = 1 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_ENTERTAINMENT), \ + new /datum/stack_recipe("Black Queen", /obj/structure/chess/blackqueen, 2, time = 1 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_ENTERTAINMENT), \ + new /datum/stack_recipe("Black King", /obj/structure/chess/blackking, 2, time = 1 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_ENTERTAINMENT), \ )), new /datum/stack_recipe_list("checkers pieces", list( \ - new /datum/stack_recipe("White Checker Man", /obj/structure/chess/checker/whiteman, 2, time = 1 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_ENTERTAINMENT), \ - new /datum/stack_recipe("White Checker King", /obj/structure/chess/checker/whiteking, 2, time = 1 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_ENTERTAINMENT), \ - new /datum/stack_recipe("Black Checker Man", /obj/structure/chess/checker/blackman, 2, time = 1 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_ENTERTAINMENT), \ - new /datum/stack_recipe("Black Checker King", /obj/structure/chess/checker/blackking, 2, time = 1 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_ENTERTAINMENT), \ + new /datum/stack_recipe("White Checker Man", /obj/structure/chess/checker/whiteman, 2, time = 1 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_ENTERTAINMENT), \ + new /datum/stack_recipe("White Checker King", /obj/structure/chess/checker/whiteking, 2, time = 1 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_ENTERTAINMENT), \ + new /datum/stack_recipe("Black Checker Man", /obj/structure/chess/checker/blackman, 2, time = 1 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_ENTERTAINMENT), \ + new /datum/stack_recipe("Black Checker King", /obj/structure/chess/checker/blackking, 2, time = 1 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_ENTERTAINMENT), \ )), null, \ new/datum/stack_recipe("rack parts", /obj/item/rack_parts, category = CAT_FURNITURE), \ - new/datum/stack_recipe("closet", /obj/structure/closet, 2, time = 1.5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ + new/datum/stack_recipe("closet", /obj/structure/closet, 2, time = 1.5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ null, \ - new/datum/stack_recipe("atmos canister", /obj/machinery/portable_atmospherics/canister, 10, time = 3 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_ATMOSPHERIC), \ - new/datum/stack_recipe("pipe", /obj/item/pipe/quaternary/pipe/crafted, 1, time = 4 SECONDS, check_density = FALSE, category = CAT_ATMOSPHERIC), \ + new/datum/stack_recipe("atmos canister", /obj/machinery/portable_atmospherics/canister, 10, time = 3 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_ATMOSPHERIC), \ + new/datum/stack_recipe("pipe", /obj/item/pipe/quaternary/pipe/crafted, 1, time = 4 SECONDS, crafting_flags = NONE, category = CAT_ATMOSPHERIC), \ null, \ new/datum/stack_recipe("floor tile", /obj/item/stack/tile/iron/base, 1, 4, 20, category = CAT_TILES), \ new/datum/stack_recipe("iron rod", /obj/item/stack/rods, 1, 2, 60, category = CAT_MISC), \ null, \ - new/datum/stack_recipe("wall girders (anchored)", /obj/structure/girder, 2, time = 4 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, placement_checks = STACK_CHECK_TRAM_FORBIDDEN, trait_booster = TRAIT_QUICK_BUILD, trait_modifier = 0.75, category = CAT_STRUCTURE), \ + new/datum/stack_recipe("wall girders (anchored)", /obj/structure/girder, 2, time = 4 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, placement_checks = STACK_CHECK_TRAM_FORBIDDEN, trait_booster = TRAIT_QUICK_BUILD, trait_modifier = 0.75, category = CAT_STRUCTURE), \ null, \ null, \ - new/datum/stack_recipe("computer frame", /obj/structure/frame/computer, 5, time = 2.5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_EQUIPMENT), \ - new/datum/stack_recipe("modular console", /obj/machinery/modular_computer, 10, time = 2.5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_EQUIPMENT), \ - new/datum/stack_recipe("machine frame", /obj/structure/frame/machine, 5, time = 2.5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_EQUIPMENT), \ + new/datum/stack_recipe("computer frame", /obj/structure/frame/computer, 5, time = 2.5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_EQUIPMENT), \ + new/datum/stack_recipe("modular console", /obj/machinery/modular_computer, 10, time = 2.5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_EQUIPMENT), \ + new/datum/stack_recipe("machine frame", /obj/structure/frame/machine, 5, time = 2.5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_EQUIPMENT), \ null, \ new /datum/stack_recipe_list("airlock assemblies", list( \ - new /datum/stack_recipe("standard airlock assembly", /obj/structure/door_assembly, 4, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_DOORS), \ - new /datum/stack_recipe("public airlock assembly", /obj/structure/door_assembly/door_assembly_public, 4, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_DOORS), \ - new /datum/stack_recipe("command airlock assembly", /obj/structure/door_assembly/door_assembly_com, 4, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_DOORS), \ - new /datum/stack_recipe("security airlock assembly", /obj/structure/door_assembly/door_assembly_sec, 4, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_DOORS), \ - new /datum/stack_recipe("engineering airlock assembly", /obj/structure/door_assembly/door_assembly_eng, 4, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_DOORS), \ - new /datum/stack_recipe("mining airlock assembly", /obj/structure/door_assembly/door_assembly_min, 4, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_DOORS), \ - new /datum/stack_recipe("atmospherics airlock assembly", /obj/structure/door_assembly/door_assembly_atmo, 4, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_DOORS), \ - new /datum/stack_recipe("research airlock assembly", /obj/structure/door_assembly/door_assembly_research, 4, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_DOORS), \ - new /datum/stack_recipe("freezer airlock assembly", /obj/structure/door_assembly/door_assembly_fre, 4, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_DOORS), \ - new /datum/stack_recipe("science airlock assembly", /obj/structure/door_assembly/door_assembly_science, 4, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_DOORS), \ - new /datum/stack_recipe("medical airlock assembly", /obj/structure/door_assembly/door_assembly_med, 4, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_DOORS), \ - new /datum/stack_recipe("hydroponics airlock assembly", /obj/structure/door_assembly/door_assembly_hydro, 4, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_DOORS), \ - new /datum/stack_recipe("virology airlock assembly", /obj/structure/door_assembly/door_assembly_viro, 4, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_DOORS), \ - new /datum/stack_recipe("maintenance airlock assembly", /obj/structure/door_assembly/door_assembly_mai, 4, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_DOORS), \ - new /datum/stack_recipe("external airlock assembly", /obj/structure/door_assembly/door_assembly_ext, 4, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_DOORS), \ - new /datum/stack_recipe("external maintenance airlock assembly", /obj/structure/door_assembly/door_assembly_extmai, 4, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_DOORS), \ - new /datum/stack_recipe("airtight hatch assembly", /obj/structure/door_assembly/door_assembly_hatch, 4, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_DOORS), \ - new /datum/stack_recipe("maintenance hatch assembly", /obj/structure/door_assembly/door_assembly_mhatch, 4, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_DOORS), \ + new /datum/stack_recipe("standard airlock assembly", /obj/structure/door_assembly, 4, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_DOORS), \ + new /datum/stack_recipe("public airlock assembly", /obj/structure/door_assembly/door_assembly_public, 4, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_DOORS), \ + new /datum/stack_recipe("command airlock assembly", /obj/structure/door_assembly/door_assembly_com, 4, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_DOORS), \ + new /datum/stack_recipe("security airlock assembly", /obj/structure/door_assembly/door_assembly_sec, 4, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_DOORS), \ + new /datum/stack_recipe("engineering airlock assembly", /obj/structure/door_assembly/door_assembly_eng, 4, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_DOORS), \ + new /datum/stack_recipe("mining airlock assembly", /obj/structure/door_assembly/door_assembly_min, 4, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_DOORS), \ + new /datum/stack_recipe("atmospherics airlock assembly", /obj/structure/door_assembly/door_assembly_atmo, 4, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_DOORS), \ + new /datum/stack_recipe("research airlock assembly", /obj/structure/door_assembly/door_assembly_research, 4, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_DOORS), \ + new /datum/stack_recipe("freezer airlock assembly", /obj/structure/door_assembly/door_assembly_fre, 4, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_DOORS), \ + new /datum/stack_recipe("science airlock assembly", /obj/structure/door_assembly/door_assembly_science, 4, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_DOORS), \ + new /datum/stack_recipe("medical airlock assembly", /obj/structure/door_assembly/door_assembly_med, 4, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_DOORS), \ + new /datum/stack_recipe("hydroponics airlock assembly", /obj/structure/door_assembly/door_assembly_hydro, 4, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_DOORS), \ + new /datum/stack_recipe("virology airlock assembly", /obj/structure/door_assembly/door_assembly_viro, 4, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_DOORS), \ + new /datum/stack_recipe("maintenance airlock assembly", /obj/structure/door_assembly/door_assembly_mai, 4, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_DOORS), \ + new /datum/stack_recipe("external airlock assembly", /obj/structure/door_assembly/door_assembly_ext, 4, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_DOORS), \ + new /datum/stack_recipe("external maintenance airlock assembly", /obj/structure/door_assembly/door_assembly_extmai, 4, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_DOORS), \ + new /datum/stack_recipe("airtight hatch assembly", /obj/structure/door_assembly/door_assembly_hatch, 4, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_DOORS), \ + new /datum/stack_recipe("maintenance hatch assembly", /obj/structure/door_assembly/door_assembly_mhatch, 4, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_DOORS), \ )), \ null, \ - new/datum/stack_recipe("firelock frame", /obj/structure/firelock_frame, 3, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_DOORS), \ - new/datum/stack_recipe("turret frame", /obj/machinery/porta_turret_construct, 5, time = 2.5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_EQUIPMENT), \ - new/datum/stack_recipe("meatspike frame", /obj/structure/kitchenspike_frame, 5, time = 2.5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_EQUIPMENT), \ - new/datum/stack_recipe("reflector frame", /obj/structure/reflector, 5, time = 2.5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_EQUIPMENT), \ + new/datum/stack_recipe("firelock frame", /obj/structure/firelock_frame, 3, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_DOORS), \ + new/datum/stack_recipe("turret frame", /obj/machinery/porta_turret_construct, 5, time = 2.5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_EQUIPMENT), \ + new/datum/stack_recipe("meatspike frame", /obj/structure/kitchenspike_frame, 5, time = 2.5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_EQUIPMENT), \ + new/datum/stack_recipe("reflector frame", /obj/structure/reflector, 5, time = 2.5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_EQUIPMENT), \ null, \ - new/datum/stack_recipe("grenade casing", /obj/item/grenade/chem_grenade, check_density = FALSE, category = CAT_CHEMISTRY), \ - new/datum/stack_recipe("light fixture frame", /obj/item/wallframe/light_fixture, 2, check_density = FALSE, category = CAT_EQUIPMENT), \ - new/datum/stack_recipe("small light fixture frame", /obj/item/wallframe/light_fixture/small, 1, check_density = FALSE, category = CAT_EQUIPMENT), \ + new/datum/stack_recipe("grenade casing", /obj/item/grenade/chem_grenade, crafting_flags = NONE, category = CAT_CHEMISTRY), \ + new/datum/stack_recipe("light fixture frame", /obj/item/wallframe/light_fixture, 2, crafting_flags = NONE, category = CAT_EQUIPMENT), \ + new/datum/stack_recipe("small light fixture frame", /obj/item/wallframe/light_fixture/small, 1, crafting_flags = NONE, category = CAT_EQUIPMENT), \ null, \ - new/datum/stack_recipe("apc frame", /obj/item/wallframe/apc, 2, check_density = FALSE, category = CAT_EQUIPMENT), \ - new/datum/stack_recipe("air alarm frame", /obj/item/wallframe/airalarm, 2, check_density = FALSE, category = CAT_EQUIPMENT), \ - new/datum/stack_recipe("fire alarm frame", /obj/item/wallframe/firealarm, 2, check_density = FALSE, category = CAT_EQUIPMENT), \ - new/datum/stack_recipe("extinguisher cabinet frame", /obj/item/wallframe/extinguisher_cabinet, 2, check_density = FALSE, category = CAT_EQUIPMENT), \ - new/datum/stack_recipe("button frame", /obj/item/wallframe/button, 1, check_density = FALSE, category = CAT_EQUIPMENT), \ + new/datum/stack_recipe("apc frame", /obj/item/wallframe/apc, 2, crafting_flags = NONE, category = CAT_EQUIPMENT), \ + new/datum/stack_recipe("air alarm frame", /obj/item/wallframe/airalarm, 2, crafting_flags = NONE, category = CAT_EQUIPMENT), \ + new/datum/stack_recipe("fire alarm frame", /obj/item/wallframe/firealarm, 2, crafting_flags = NONE, category = CAT_EQUIPMENT), \ + new/datum/stack_recipe("extinguisher cabinet frame", /obj/item/wallframe/extinguisher_cabinet, 2, crafting_flags = NONE, category = CAT_EQUIPMENT), \ + new/datum/stack_recipe("button frame", /obj/item/wallframe/button, 1, crafting_flags = NONE, category = CAT_EQUIPMENT), \ null, \ - new/datum/stack_recipe("iron door", /obj/structure/mineral_door/iron, 20, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, applies_mats = TRUE, category = CAT_DOORS), \ - new/datum/stack_recipe("filing cabinet", /obj/structure/filingcabinet, 2, time = 10 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ - new/datum/stack_recipe("desk bell", /obj/structure/desk_bell, 2, time = 3 SECONDS, check_density = FALSE, category = CAT_FURNITURE), \ - new/datum/stack_recipe("floodlight frame", /obj/structure/floodlight_frame, 5, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_EQUIPMENT), \ - new/datum/stack_recipe("voting box", /obj/structure/votebox, 15, time = 5 SECONDS, check_density = FALSE, category = CAT_ENTERTAINMENT), \ - new/datum/stack_recipe("pestle", /obj/item/pestle, 1, time = 5 SECONDS, check_density = FALSE, category = CAT_CHEMISTRY), \ - new/datum/stack_recipe("hygienebot assembly", /obj/item/bot_assembly/hygienebot, 2, time = 5 SECONDS, check_density = FALSE, category = CAT_ROBOT), \ - new/datum/stack_recipe("shower frame", /obj/structure/showerframe, 2, time = 2 SECONDS, check_density = FALSE, category = CAT_FURNITURE), \ - new/datum/stack_recipe("urinal", /obj/item/wallframe/urinal, 2, time = 1 SECONDS, check_density = FALSE, category = CAT_FURNITURE) + new/datum/stack_recipe("iron door", /obj/structure/mineral_door/iron, 20, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND | CRAFT_APPLIES_MATS, category = CAT_DOORS), \ + new/datum/stack_recipe("filing cabinet", /obj/structure/filingcabinet, 2, time = 10 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ + new/datum/stack_recipe("desk bell", /obj/structure/desk_bell, 2, time = 3 SECONDS, crafting_flags = NONE, category = CAT_FURNITURE), \ + new/datum/stack_recipe("floodlight frame", /obj/structure/floodlight_frame, 5, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_EQUIPMENT), \ + new/datum/stack_recipe("voting box", /obj/structure/votebox, 15, time = 5 SECONDS, crafting_flags = NONE, category = CAT_ENTERTAINMENT), \ + new/datum/stack_recipe("pestle", /obj/item/pestle, 1, time = 5 SECONDS, crafting_flags = NONE, category = CAT_CHEMISTRY), \ + new/datum/stack_recipe("hygienebot assembly", /obj/item/bot_assembly/hygienebot, 2, time = 5 SECONDS, crafting_flags = NONE, category = CAT_ROBOT), \ + new/datum/stack_recipe("shower frame", /obj/structure/showerframe, 2, time = 2 SECONDS, crafting_flags = NONE, category = CAT_FURNITURE), \ + new/datum/stack_recipe("urinal", /obj/item/wallframe/urinal, 2, time = 1 SECONDS, crafting_flags = NONE, category = CAT_FURNITURE) )) /obj/item/stack/sheet/iron @@ -253,14 +253,14 @@ GLOBAL_LIST_INIT(metal_recipes, list ( \ * Plasteel */ GLOBAL_LIST_INIT(plasteel_recipes, list ( \ - new/datum/stack_recipe("AI core", /obj/structure/ai_core, 4, time = 5 SECONDS, one_per_turf = TRUE, check_density = FALSE, category = CAT_ROBOT), - new/datum/stack_recipe("bomb assembly", /obj/machinery/syndicatebomb/empty, 10, time = 5 SECONDS, check_density = FALSE, category = CAT_CHEMISTRY), - new/datum/stack_recipe("Large Gas Tank", /obj/structure/tank_frame, 4, time=1 SECONDS, one_per_turf=TRUE, check_density = FALSE, category = CAT_ATMOSPHERIC), - new/datum/stack_recipe("shutter assembly", /obj/machinery/door/poddoor/shutters/preopen/deconstructed, 5, time = 5 SECONDS, one_per_turf = TRUE, check_density = FALSE, category = CAT_DOORS), + new/datum/stack_recipe("AI core", /obj/structure/ai_core, 4, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF, category = CAT_ROBOT), + new/datum/stack_recipe("bomb assembly", /obj/machinery/syndicatebomb/empty, 10, time = 5 SECONDS, crafting_flags = NONE, category = CAT_CHEMISTRY), + new/datum/stack_recipe("Large Gas Tank", /obj/structure/tank_frame, 4, time=1 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF, category = CAT_ATMOSPHERIC), + new/datum/stack_recipe("shutter assembly", /obj/machinery/door/poddoor/shutters/preopen/deconstructed, 5, time = 5 SECONDS, crafting_flags = CRAFT_ONE_PER_TURF, category = CAT_DOORS), null, new /datum/stack_recipe_list("airlock assemblies", list( \ - new/datum/stack_recipe("high security airlock assembly", /obj/structure/door_assembly/door_assembly_highsecurity, 4, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_DOORS), - new/datum/stack_recipe("vault door assembly", /obj/structure/door_assembly/door_assembly_vault, 6, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_DOORS), + new/datum/stack_recipe("high security airlock assembly", /obj/structure/door_assembly/door_assembly_highsecurity, 4, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_DOORS), + new/datum/stack_recipe("vault door assembly", /obj/structure/door_assembly/door_assembly_vault, 6, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_DOORS), )), \ )) @@ -301,49 +301,49 @@ GLOBAL_LIST_INIT(plasteel_recipes, list ( \ * Wood */ GLOBAL_LIST_INIT(wood_recipes, list ( \ - new/datum/stack_recipe("wooden sandals", /obj/item/clothing/shoes/sandal, 1, check_density = FALSE, category = CAT_CLOTHING), \ - new/datum/stack_recipe("wood floor tile", /obj/item/stack/tile/wood, 1, 4, 20, check_density = FALSE, category = CAT_TILES), \ - new/datum/stack_recipe("wood table frame", /obj/structure/table_frame/wood, 2, time = 1 SECONDS, check_density = FALSE, category = CAT_FURNITURE), \ - new/datum/stack_recipe("rolling pin", /obj/item/kitchen/rollingpin, 2, time = 3 SECONDS, check_density = FALSE, category = CAT_TOOLS), \ - new/datum/stack_recipe("wooden chair", /obj/structure/chair/wood/, 3, time = 1 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ - new/datum/stack_recipe("winged wooden chair", /obj/structure/chair/wood/wings, 3, time = 1 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ - new/datum/stack_recipe("wooden barricade", /obj/structure/barricade/wooden, 5, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_STRUCTURE), \ - new/datum/stack_recipe("wooden door", /obj/structure/mineral_door/wood, 10, time = 2 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_DOORS), \ - new/datum/stack_recipe("wooden stairs frame", /obj/structure/stairs_frame/wood, 10, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_STRUCTURE), \ - new/datum/stack_recipe("wooden fence", /obj/structure/railing/wooden_fence, 2, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_STRUCTURE), \ - new/datum/stack_recipe("cat house", /obj/structure/cat_house, 5, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_STRUCTURE), \ - new/datum/stack_recipe("coffin", /obj/structure/closet/crate/coffin, 5, time = 1.5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ - new/datum/stack_recipe("book case", /obj/structure/bookcase, 4, time = 1.5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ - new/datum/stack_recipe("drying rack", /obj/machinery/smartfridge/drying_rack, 10, time = 1.5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_TOOLS), \ - new/datum/stack_recipe("wooden barrel", /obj/structure/fermenting_barrel, 8, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_CONTAINERS), \ - new/datum/stack_recipe("dog bed", /obj/structure/bed/dogbed, 10, time = 1 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ - new/datum/stack_recipe("dresser", /obj/structure/dresser, 10, time = 1.5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ - new/datum/stack_recipe("picture frame", /obj/item/wallframe/picture, 1, time = 1 SECONDS, check_density = FALSE, category = CAT_ENTERTAINMENT),\ - new/datum/stack_recipe("painting frame", /obj/item/wallframe/painting, 1, time = 1 SECONDS, check_density = FALSE, category = CAT_ENTERTAINMENT),\ - new/datum/stack_recipe("display case chassis", /obj/structure/displaycase_chassis, 5, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ - new/datum/stack_recipe("wooden buckler", /obj/item/shield/buckler, 20, time = 4 SECONDS, check_density = FALSE, category = CAT_EQUIPMENT), \ - new/datum/stack_recipe("apiary", /obj/structure/beebox, 40, time = 5 SECONDS, check_density = FALSE, category = CAT_TOOLS),\ - new/datum/stack_recipe("mannequin", /obj/structure/mannequin/wood, 25, time = 5 SECONDS, one_per_turf = TRUE, category = CAT_ENTERTAINMENT), \ - new/datum/stack_recipe("tiki mask", /obj/item/clothing/mask/gas/tiki_mask, 2, check_density = FALSE, category = CAT_CLOTHING), \ - new/datum/stack_recipe("smoking pipe", /obj/item/clothing/mask/cigarette/pipe, 2, check_density = FALSE, category = CAT_CLOTHING), \ - new/datum/stack_recipe("honey frame", /obj/item/honey_frame, 5, time = 1 SECONDS, check_density = FALSE, category = CAT_TOOLS),\ - new/datum/stack_recipe("wooden bucket", /obj/item/reagent_containers/cup/bucket/wooden, 3, time = 1 SECONDS, check_density = FALSE, category = CAT_CONTAINERS),\ - new/datum/stack_recipe("rake", /obj/item/cultivator/rake, 5, time = 1 SECONDS, check_density = FALSE, category = CAT_TOOLS),\ - new/datum/stack_recipe("ore box", /obj/structure/ore_box, 4, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_CONTAINERS),\ - new/datum/stack_recipe("wooden crate", /obj/structure/closet/crate/wooden, 6, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE),\ - new/datum/stack_recipe("baseball bat", /obj/item/melee/baseball_bat, 5, time = 1.5 SECONDS, check_density = FALSE, category = CAT_WEAPON_MELEE),\ - new/datum/stack_recipe("loom", /obj/structure/loom, 10, time = 1.5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_TOOLS), \ - new/datum/stack_recipe("mortar", /obj/item/reagent_containers/cup/mortar, 3, check_density = FALSE, category = CAT_CHEMISTRY), \ - new/datum/stack_recipe("firebrand", /obj/item/match/firebrand, 2, time = 10 SECONDS, check_density = FALSE, category = CAT_TOOLS), \ - new/datum/stack_recipe("bonfire", /obj/structure/bonfire, 10, time = 6 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_TOOLS), \ - new/datum/stack_recipe("easel", /obj/structure/easel, 5, time = 1 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_ENTERTAINMENT), \ - new/datum/stack_recipe("noticeboard", /obj/item/wallframe/noticeboard, 1, time = 1 SECONDS, one_per_turf = FALSE, on_solid_ground = FALSE, check_density = FALSE, category = CAT_FURNITURE), \ - new/datum/stack_recipe("test tube rack", /obj/item/storage/test_tube_rack, 1, time = 1 SECONDS, check_density = FALSE, category = CAT_CHEMISTRY), \ + new/datum/stack_recipe("wooden sandals", /obj/item/clothing/shoes/sandal, 1, crafting_flags = NONE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("wood floor tile", /obj/item/stack/tile/wood, 1, 4, 20, crafting_flags = NONE, category = CAT_TILES), \ + new/datum/stack_recipe("wood table frame", /obj/structure/table_frame/wood, 2, time = 1 SECONDS, crafting_flags = NONE, category = CAT_FURNITURE), \ + new/datum/stack_recipe("rolling pin", /obj/item/kitchen/rollingpin, 2, time = 3 SECONDS, crafting_flags = NONE, category = CAT_TOOLS), \ + new/datum/stack_recipe("wooden chair", /obj/structure/chair/wood/, 3, time = 1 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ + new/datum/stack_recipe("winged wooden chair", /obj/structure/chair/wood/wings, 3, time = 1 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ + new/datum/stack_recipe("wooden barricade", /obj/structure/barricade/wooden, 5, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_STRUCTURE), \ + new/datum/stack_recipe("wooden door", /obj/structure/mineral_door/wood, 10, time = 2 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_DOORS), \ + new/datum/stack_recipe("wooden stairs frame", /obj/structure/stairs_frame/wood, 10, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_STRUCTURE), \ + new/datum/stack_recipe("wooden fence", /obj/structure/railing/wooden_fence, 2, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_STRUCTURE), \ + new/datum/stack_recipe("cat house", /obj/structure/cat_house, 5, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_STRUCTURE), \ + new/datum/stack_recipe("coffin", /obj/structure/closet/crate/coffin, 5, time = 1.5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ + new/datum/stack_recipe("book case", /obj/structure/bookcase, 4, time = 1.5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ + new/datum/stack_recipe("drying rack", /obj/machinery/smartfridge/drying_rack, 10, time = 1.5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_TOOLS), \ + new/datum/stack_recipe("wooden barrel", /obj/structure/fermenting_barrel, 8, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_CONTAINERS), \ + new/datum/stack_recipe("dog bed", /obj/structure/bed/dogbed, 10, time = 1 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ + new/datum/stack_recipe("dresser", /obj/structure/dresser, 10, time = 1.5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ + new/datum/stack_recipe("picture frame", /obj/item/wallframe/picture, 1, time = 1 SECONDS, crafting_flags = NONE, category = CAT_ENTERTAINMENT),\ + new/datum/stack_recipe("painting frame", /obj/item/wallframe/painting, 1, time = 1 SECONDS, crafting_flags = NONE, category = CAT_ENTERTAINMENT),\ + new/datum/stack_recipe("display case chassis", /obj/structure/displaycase_chassis, 5, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ + new/datum/stack_recipe("wooden buckler", /obj/item/shield/buckler, 20, time = 4 SECONDS, crafting_flags = NONE, category = CAT_EQUIPMENT), \ + new/datum/stack_recipe("apiary", /obj/structure/beebox, 40, time = 5 SECONDS, crafting_flags = NONE, category = CAT_TOOLS),\ + new/datum/stack_recipe("mannequin", /obj/structure/mannequin/wood, 25, time = 5 SECONDS, crafting_flags = CRAFT_ONE_PER_TURF, category = CAT_ENTERTAINMENT), \ + new/datum/stack_recipe("tiki mask", /obj/item/clothing/mask/gas/tiki_mask, 2, crafting_flags = NONE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("smoking pipe", /obj/item/clothing/mask/cigarette/pipe, 2, crafting_flags = NONE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("honey frame", /obj/item/honey_frame, 5, time = 1 SECONDS, crafting_flags = NONE, category = CAT_TOOLS),\ + new/datum/stack_recipe("wooden bucket", /obj/item/reagent_containers/cup/bucket/wooden, 3, time = 1 SECONDS, crafting_flags = NONE, category = CAT_CONTAINERS),\ + new/datum/stack_recipe("rake", /obj/item/cultivator/rake, 5, time = 1 SECONDS, crafting_flags = NONE, category = CAT_TOOLS),\ + new/datum/stack_recipe("ore box", /obj/structure/ore_box, 4, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_CONTAINERS),\ + new/datum/stack_recipe("wooden crate", /obj/structure/closet/crate/wooden, 6, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE),\ + new/datum/stack_recipe("baseball bat", /obj/item/melee/baseball_bat, 5, time = 1.5 SECONDS, crafting_flags = NONE, category = CAT_WEAPON_MELEE),\ + new/datum/stack_recipe("loom", /obj/structure/loom, 10, time = 1.5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_TOOLS), \ + new/datum/stack_recipe("mortar", /obj/item/reagent_containers/cup/mortar, 3, crafting_flags = NONE, category = CAT_CHEMISTRY), \ + new/datum/stack_recipe("firebrand", /obj/item/match/firebrand, 2, time = 10 SECONDS, crafting_flags = NONE, category = CAT_TOOLS), \ + new/datum/stack_recipe("bonfire", /obj/structure/bonfire, 10, time = 6 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_TOOLS), \ + new/datum/stack_recipe("easel", /obj/structure/easel, 5, time = 1 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_ENTERTAINMENT), \ + new/datum/stack_recipe("noticeboard", /obj/item/wallframe/noticeboard, 1, time = 1 SECONDS, crafting_flags = NONE, category = CAT_FURNITURE), \ + new/datum/stack_recipe("test tube rack", /obj/item/storage/test_tube_rack, 1, time = 1 SECONDS, crafting_flags = NONE, category = CAT_CHEMISTRY), \ null, \ new/datum/stack_recipe_list("pews", list( - new /datum/stack_recipe("pew (middle)", /obj/structure/chair/pew, 3, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), - new /datum/stack_recipe("pew (left)", /obj/structure/chair/pew/left, 3, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), - new /datum/stack_recipe("pew (right)", /obj/structure/chair/pew/right, 3, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE) + new /datum/stack_recipe("pew (middle)", /obj/structure/chair/pew, 3, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), + new /datum/stack_recipe("pew (left)", /obj/structure/chair/pew/left, 3, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), + new /datum/stack_recipe("pew (right)", /obj/structure/chair/pew/right, 3, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE) )), null, \ )) @@ -380,19 +380,19 @@ GLOBAL_LIST_INIT(wood_recipes, list ( \ */ GLOBAL_LIST_INIT(bamboo_recipes, list ( \ - new/datum/stack_recipe("punji sticks trap", /obj/structure/punji_sticks, 5, time = 3 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_EQUIPMENT), \ - new/datum/stack_recipe("bamboo spear", /obj/item/spear/bamboospear, 25, time = 9 SECONDS, check_density = FALSE, category = CAT_WEAPON_MELEE), \ - new/datum/stack_recipe("blow gun", /obj/item/gun/syringe/blowgun, 10, time = 7 SECONDS, check_density = FALSE, category = CAT_WEAPON_RANGED), \ - new/datum/stack_recipe("crude syringe", /obj/item/reagent_containers/syringe/crude, 5, time = 1 SECONDS, check_density = FALSE, category = CAT_CHEMISTRY), \ - new/datum/stack_recipe("rice hat", /obj/item/clothing/head/costume/rice_hat, 10, time = 7 SECONDS, check_density = FALSE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("punji sticks trap", /obj/structure/punji_sticks, 5, time = 3 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_EQUIPMENT), \ + new/datum/stack_recipe("bamboo spear", /obj/item/spear/bamboospear, 25, time = 9 SECONDS, crafting_flags = NONE, category = CAT_WEAPON_MELEE), \ + new/datum/stack_recipe("blow gun", /obj/item/gun/syringe/blowgun, 10, time = 7 SECONDS, crafting_flags = NONE, category = CAT_WEAPON_RANGED), \ + new/datum/stack_recipe("crude syringe", /obj/item/reagent_containers/syringe/crude, 5, time = 1 SECONDS, crafting_flags = NONE, category = CAT_CHEMISTRY), \ + new/datum/stack_recipe("rice hat", /obj/item/clothing/head/costume/rice_hat, 10, time = 7 SECONDS, crafting_flags = NONE, category = CAT_CLOTHING), \ null, \ - new/datum/stack_recipe("bamboo stool", /obj/structure/chair/stool/bamboo, 2, time = 1 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ - new/datum/stack_recipe("bamboo mat piece", /obj/item/stack/tile/bamboo, 1, 4, 20, check_density = FALSE, category = CAT_TILES), \ + new/datum/stack_recipe("bamboo stool", /obj/structure/chair/stool/bamboo, 2, time = 1 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ + new/datum/stack_recipe("bamboo mat piece", /obj/item/stack/tile/bamboo, 1, 4, 20, crafting_flags = NONE, category = CAT_TILES), \ null, \ new/datum/stack_recipe_list("bamboo benches", list( - new /datum/stack_recipe("bamboo bench (middle)", /obj/structure/chair/sofa/bamboo, 3, time = 1 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), - new /datum/stack_recipe("bamboo bench (left)", /obj/structure/chair/sofa/bamboo/left, 3, time = 1 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), - new /datum/stack_recipe("bamboo bench (right)", /obj/structure/chair/sofa/bamboo/right, 3, time = 1 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE) + new /datum/stack_recipe("bamboo bench (middle)", /obj/structure/chair/sofa/bamboo, 3, time = 1 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), + new /datum/stack_recipe("bamboo bench (left)", /obj/structure/chair/sofa/bamboo/left, 3, time = 1 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), + new /datum/stack_recipe("bamboo bench (right)", /obj/structure/chair/sofa/bamboo/right, 3, time = 1 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE) )), \ )) @@ -427,41 +427,41 @@ GLOBAL_LIST_INIT(bamboo_recipes, list ( \ * Cloth */ GLOBAL_LIST_INIT(cloth_recipes, list ( \ - new/datum/stack_recipe("white jumpskirt", /obj/item/clothing/under/color/jumpskirt/white, 3, check_density = FALSE, category = CAT_CLOTHING), /*Ladies first*/ \ - new/datum/stack_recipe("white jumpsuit", /obj/item/clothing/under/color/white, 3, check_density = FALSE, category = CAT_CLOTHING), \ - new/datum/stack_recipe("white shoes", /obj/item/clothing/shoes/sneakers/white, 2, check_density = FALSE, category = CAT_CLOTHING), \ - new/datum/stack_recipe("white scarf", /obj/item/clothing/neck/scarf, 1, check_density = FALSE, category = CAT_CLOTHING), \ - new/datum/stack_recipe("white bandana", /obj/item/clothing/mask/bandana/white, 2, check_density = FALSE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("white jumpskirt", /obj/item/clothing/under/color/jumpskirt/white, 3, crafting_flags = NONE, category = CAT_CLOTHING), /*Ladies first*/ \ + new/datum/stack_recipe("white jumpsuit", /obj/item/clothing/under/color/white, 3, crafting_flags = NONE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("white shoes", /obj/item/clothing/shoes/sneakers/white, 2, crafting_flags = NONE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("white scarf", /obj/item/clothing/neck/scarf, 1, crafting_flags = NONE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("white bandana", /obj/item/clothing/mask/bandana/white, 2, crafting_flags = NONE, category = CAT_CLOTHING), \ null, \ - new/datum/stack_recipe("backpack", /obj/item/storage/backpack, 4, check_density = FALSE, category = CAT_CONTAINERS), \ - new/datum/stack_recipe("satchel", /obj/item/storage/backpack/satchel, 4, check_density = FALSE, category = CAT_CONTAINERS), \ - new/datum/stack_recipe("messenger bag", /obj/item/storage/backpack/messenger, 4, check_density = FALSE, category = CAT_CONTAINERS), \ - new/datum/stack_recipe("duffel bag", /obj/item/storage/backpack/duffelbag, 6, check_density = FALSE, category = CAT_CONTAINERS), \ + new/datum/stack_recipe("backpack", /obj/item/storage/backpack, 4, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new/datum/stack_recipe("satchel", /obj/item/storage/backpack/satchel, 4, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new/datum/stack_recipe("messenger bag", /obj/item/storage/backpack/messenger, 4, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new/datum/stack_recipe("duffel bag", /obj/item/storage/backpack/duffelbag, 6, crafting_flags = NONE, category = CAT_CONTAINERS), \ null, \ - new/datum/stack_recipe("plant bag", /obj/item/storage/bag/plants, 4, check_density = FALSE, category = CAT_CONTAINERS), \ - new/datum/stack_recipe("book bag", /obj/item/storage/bag/books, 4, check_density = FALSE, category = CAT_CONTAINERS), \ - new/datum/stack_recipe("mining satchel", /obj/item/storage/bag/ore, 4, check_density = FALSE, category = CAT_CONTAINERS), \ - new/datum/stack_recipe("chemistry bag", /obj/item/storage/bag/chemistry, 4, check_density = FALSE, category = CAT_CONTAINERS), \ - new/datum/stack_recipe("bio bag", /obj/item/storage/bag/bio, 4, check_density = FALSE, category = CAT_CONTAINERS), \ - new/datum/stack_recipe("science bag", /obj/item/storage/bag/xeno, 4, check_density = FALSE, category = CAT_CONTAINERS), \ - new/datum/stack_recipe("construction bag", /obj/item/storage/bag/construction, 4, check_density = FALSE, category = CAT_CONTAINERS), \ + new/datum/stack_recipe("plant bag", /obj/item/storage/bag/plants, 4, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new/datum/stack_recipe("book bag", /obj/item/storage/bag/books, 4, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new/datum/stack_recipe("mining satchel", /obj/item/storage/bag/ore, 4, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new/datum/stack_recipe("chemistry bag", /obj/item/storage/bag/chemistry, 4, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new/datum/stack_recipe("bio bag", /obj/item/storage/bag/bio, 4, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new/datum/stack_recipe("science bag", /obj/item/storage/bag/xeno, 4, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new/datum/stack_recipe("construction bag", /obj/item/storage/bag/construction, 4, crafting_flags = NONE, category = CAT_CONTAINERS), \ null, \ - new/datum/stack_recipe("improvised gauze", /obj/item/stack/medical/gauze/improvised, 1, 2, 6, check_density = FALSE, category = CAT_TOOLS), \ - new/datum/stack_recipe("rag", /obj/item/reagent_containers/cup/rag, 1, check_density = FALSE, category = CAT_CHEMISTRY), \ - new/datum/stack_recipe("bedsheet", /obj/item/bedsheet, 3, check_density = FALSE, category = CAT_FURNITURE), \ - new/datum/stack_recipe("double bedsheet", /obj/item/bedsheet/double, 6, check_density = FALSE, category = CAT_FURNITURE), \ - new/datum/stack_recipe("empty sandbag", /obj/item/emptysandbag, 4, check_density = FALSE, category = CAT_CONTAINERS), \ + new/datum/stack_recipe("improvised gauze", /obj/item/stack/medical/gauze/improvised, 1, 2, 6, crafting_flags = NONE, category = CAT_TOOLS), \ + new/datum/stack_recipe("rag", /obj/item/reagent_containers/cup/rag, 1, crafting_flags = NONE, category = CAT_CHEMISTRY), \ + new/datum/stack_recipe("bedsheet", /obj/item/bedsheet, 3, crafting_flags = NONE, category = CAT_FURNITURE), \ + new/datum/stack_recipe("double bedsheet", /obj/item/bedsheet/double, 6, crafting_flags = NONE, category = CAT_FURNITURE), \ + new/datum/stack_recipe("empty sandbag", /obj/item/emptysandbag, 4, crafting_flags = NONE, category = CAT_CONTAINERS), \ null, \ - new/datum/stack_recipe("fingerless gloves", /obj/item/clothing/gloves/fingerless, 1, check_density = FALSE, category = CAT_CLOTHING), \ - new/datum/stack_recipe("white gloves", /obj/item/clothing/gloves/color/white, 3, check_density = FALSE, category = CAT_CLOTHING), \ - new/datum/stack_recipe("white softcap", /obj/item/clothing/head/soft/mime, 2, check_density = FALSE, category = CAT_CLOTHING), \ - new/datum/stack_recipe("white beanie", /obj/item/clothing/head/beanie, 2, check_density = FALSE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("fingerless gloves", /obj/item/clothing/gloves/fingerless, 1, crafting_flags = NONE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("white gloves", /obj/item/clothing/gloves/color/white, 3, crafting_flags = NONE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("white softcap", /obj/item/clothing/head/soft/mime, 2, crafting_flags = NONE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("white beanie", /obj/item/clothing/head/beanie, 2, crafting_flags = NONE, category = CAT_CLOTHING), \ null, \ - new/datum/stack_recipe("blindfold", /obj/item/clothing/glasses/blindfold, 2, check_density = FALSE, category = CAT_ENTERTAINMENT), \ + new/datum/stack_recipe("blindfold", /obj/item/clothing/glasses/blindfold, 2, crafting_flags = NONE, category = CAT_ENTERTAINMENT), \ null, \ - new/datum/stack_recipe("19x19 canvas", /obj/item/canvas/nineteen_nineteen, 3, check_density = FALSE, category = CAT_ENTERTAINMENT), \ - new/datum/stack_recipe("23x19 canvas", /obj/item/canvas/twentythree_nineteen, 4, check_density = FALSE, category = CAT_ENTERTAINMENT), \ - new/datum/stack_recipe("23x23 canvas", /obj/item/canvas/twentythree_twentythree, 5, check_density = FALSE, category = CAT_ENTERTAINMENT), \ + new/datum/stack_recipe("19x19 canvas", /obj/item/canvas/nineteen_nineteen, 3, crafting_flags = NONE, category = CAT_ENTERTAINMENT), \ + new/datum/stack_recipe("23x19 canvas", /obj/item/canvas/twentythree_nineteen, 4, crafting_flags = NONE, category = CAT_ENTERTAINMENT), \ + new/datum/stack_recipe("23x23 canvas", /obj/item/canvas/twentythree_twentythree, 5, crafting_flags = NONE, category = CAT_ENTERTAINMENT), \ new/datum/stack_recipe("pillow", /obj/item/pillow, 3, category = CAT_FURNITURE), \ )) @@ -490,10 +490,10 @@ GLOBAL_LIST_INIT(cloth_recipes, list ( \ amount = 5 GLOBAL_LIST_INIT(durathread_recipes, list ( \ - new/datum/stack_recipe("durathread jumpsuit", /obj/item/clothing/under/misc/durathread, 4, time = 4 SECONDS, check_density = FALSE, category = CAT_CLOTHING), - new/datum/stack_recipe("durathread beret", /obj/item/clothing/head/beret/durathread, 2, time = 4 SECONDS, check_density = FALSE, category = CAT_CLOTHING), \ - new/datum/stack_recipe("durathread beanie", /obj/item/clothing/head/beanie/durathread, 2, time = 4 SECONDS, check_density = FALSE, category = CAT_CLOTHING), \ - new/datum/stack_recipe("durathread bandana", /obj/item/clothing/mask/bandana/durathread, 1, time = 2.5 SECONDS, check_density = FALSE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("durathread jumpsuit", /obj/item/clothing/under/misc/durathread, 4, time = 4 SECONDS, crafting_flags = NONE, category = CAT_CLOTHING), + new/datum/stack_recipe("durathread beret", /obj/item/clothing/head/beret/durathread, 2, time = 4 SECONDS, crafting_flags = NONE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("durathread beanie", /obj/item/clothing/head/beanie/durathread, 2, time = 4 SECONDS, crafting_flags = NONE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("durathread bandana", /obj/item/clothing/mask/bandana/durathread, 1, time = 2.5 SECONDS, crafting_flags = NONE, category = CAT_CLOTHING), \ )) /obj/item/stack/sheet/durathread @@ -564,61 +564,61 @@ GLOBAL_LIST_INIT(durathread_recipes, list ( \ * Cardboard */ GLOBAL_LIST_INIT(cardboard_recipes, list ( \ - new/datum/stack_recipe("box", /obj/item/storage/box, check_density = FALSE, category = CAT_CONTAINERS), \ - new/datum/stack_recipe("cardborg suit", /obj/item/clothing/suit/costume/cardborg, 3, check_density = FALSE, category = CAT_CLOTHING), \ - new/datum/stack_recipe("cardborg helmet", /obj/item/clothing/head/costume/cardborg, check_density = FALSE, category = CAT_CLOTHING), \ - new/datum/stack_recipe("large box", /obj/structure/closet/cardboard, 4, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_CONTAINERS), \ - new/datum/stack_recipe("cardboard cutout", /obj/item/cardboard_cutout, 5, check_density = FALSE, category = CAT_ENTERTAINMENT), \ + new/datum/stack_recipe("box", /obj/item/storage/box, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new/datum/stack_recipe("cardborg suit", /obj/item/clothing/suit/costume/cardborg, 3, crafting_flags = NONE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("cardborg helmet", /obj/item/clothing/head/costume/cardborg, crafting_flags = NONE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("large box", /obj/structure/closet/cardboard, 4, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_CONTAINERS), \ + new/datum/stack_recipe("cardboard cutout", /obj/item/cardboard_cutout, 5, crafting_flags = NONE, category = CAT_ENTERTAINMENT), \ null, \ - new/datum/stack_recipe("pizza box", /obj/item/pizzabox, check_density = FALSE, category = CAT_CONTAINERS), \ - new/datum/stack_recipe("folder", /obj/item/folder, check_density = FALSE, category = CAT_CONTAINERS), \ + new/datum/stack_recipe("pizza box", /obj/item/pizzabox, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new/datum/stack_recipe("folder", /obj/item/folder, crafting_flags = NONE, category = CAT_CONTAINERS), \ null, \ //TO-DO: Find a proper way to just change the illustration on the box. Code isn't the issue, input is. new/datum/stack_recipe_list("fancy boxes", list( - new /datum/stack_recipe("donut box", /obj/item/storage/fancy/donut_box, check_density = FALSE, category = CAT_CONTAINERS), \ - new /datum/stack_recipe("egg box", /obj/item/storage/fancy/egg_box, check_density = FALSE, category = CAT_CONTAINERS), \ - new /datum/stack_recipe("donk-pockets box", /obj/item/storage/box/donkpockets, check_density = FALSE, category = CAT_CONTAINERS), \ - new /datum/stack_recipe("donk-pockets spicy box", /obj/item/storage/box/donkpockets/donkpocketspicy, check_density = FALSE, category = CAT_CONTAINERS), \ - new /datum/stack_recipe("donk-pockets teriyaki box", /obj/item/storage/box/donkpockets/donkpocketteriyaki, check_density = FALSE, category = CAT_CONTAINERS), \ - new /datum/stack_recipe("donk-pockets pizza box", /obj/item/storage/box/donkpockets/donkpocketpizza, check_density = FALSE, category = CAT_CONTAINERS), \ - new /datum/stack_recipe("donk-pockets berry box", /obj/item/storage/box/donkpockets/donkpocketberry, check_density = FALSE, category = CAT_CONTAINERS), \ - new /datum/stack_recipe("donk-pockets honk box", /obj/item/storage/box/donkpockets/donkpockethonk, check_density = FALSE, category = CAT_CONTAINERS), \ - new /datum/stack_recipe("monkey cube box", /obj/item/storage/box/monkeycubes, check_density = FALSE, category = CAT_CONTAINERS), - new /datum/stack_recipe("nugget box", /obj/item/storage/fancy/nugget_box, check_density = FALSE, category = CAT_CONTAINERS), \ + new /datum/stack_recipe("donut box", /obj/item/storage/fancy/donut_box, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new /datum/stack_recipe("egg box", /obj/item/storage/fancy/egg_box, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new /datum/stack_recipe("donk-pockets box", /obj/item/storage/box/donkpockets, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new /datum/stack_recipe("donk-pockets spicy box", /obj/item/storage/box/donkpockets/donkpocketspicy, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new /datum/stack_recipe("donk-pockets teriyaki box", /obj/item/storage/box/donkpockets/donkpocketteriyaki, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new /datum/stack_recipe("donk-pockets pizza box", /obj/item/storage/box/donkpockets/donkpocketpizza, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new /datum/stack_recipe("donk-pockets berry box", /obj/item/storage/box/donkpockets/donkpocketberry, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new /datum/stack_recipe("donk-pockets honk box", /obj/item/storage/box/donkpockets/donkpockethonk, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new /datum/stack_recipe("monkey cube box", /obj/item/storage/box/monkeycubes, crafting_flags = NONE, category = CAT_CONTAINERS), + new /datum/stack_recipe("nugget box", /obj/item/storage/fancy/nugget_box, crafting_flags = NONE, category = CAT_CONTAINERS), \ null, \ - new /datum/stack_recipe("lethal ammo box", /obj/item/storage/box/lethalshot, check_density = FALSE, category = CAT_CONTAINERS), \ - new /datum/stack_recipe("rubber shot ammo box", /obj/item/storage/box/rubbershot, check_density = FALSE, category = CAT_CONTAINERS), \ - new /datum/stack_recipe("bean bag ammo box", /obj/item/storage/box/beanbag, check_density = FALSE, category = CAT_CONTAINERS), \ - new /datum/stack_recipe("flashbang box", /obj/item/storage/box/flashbangs, check_density = FALSE, category = CAT_CONTAINERS), \ - new /datum/stack_recipe("flashes box", /obj/item/storage/box/flashes, check_density = FALSE, category = CAT_CONTAINERS), \ - new /datum/stack_recipe("handcuffs box", /obj/item/storage/box/handcuffs, check_density = FALSE, category = CAT_CONTAINERS), \ - new /datum/stack_recipe("ID card box", /obj/item/storage/box/ids, check_density = FALSE, category = CAT_CONTAINERS), \ - new /datum/stack_recipe("PDA box", /obj/item/storage/box/pdas, check_density = FALSE, category = CAT_CONTAINERS), \ + new /datum/stack_recipe("lethal ammo box", /obj/item/storage/box/lethalshot, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new /datum/stack_recipe("rubber shot ammo box", /obj/item/storage/box/rubbershot, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new /datum/stack_recipe("bean bag ammo box", /obj/item/storage/box/beanbag, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new /datum/stack_recipe("flashbang box", /obj/item/storage/box/flashbangs, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new /datum/stack_recipe("flashes box", /obj/item/storage/box/flashes, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new /datum/stack_recipe("handcuffs box", /obj/item/storage/box/handcuffs, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new /datum/stack_recipe("ID card box", /obj/item/storage/box/ids, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new /datum/stack_recipe("PDA box", /obj/item/storage/box/pdas, crafting_flags = NONE, category = CAT_CONTAINERS), \ null, \ - new /datum/stack_recipe("pillbottle box", /obj/item/storage/box/pillbottles, check_density = FALSE, category = CAT_CONTAINERS), \ - new /datum/stack_recipe("beaker box", /obj/item/storage/box/beakers, check_density = FALSE, category = CAT_CONTAINERS), \ - new /datum/stack_recipe("syringe box", /obj/item/storage/box/syringes, check_density = FALSE, category = CAT_CONTAINERS), \ - new /datum/stack_recipe("latex gloves box", /obj/item/storage/box/gloves, check_density = FALSE, category = CAT_CONTAINERS), \ - new /datum/stack_recipe("sterile masks box", /obj/item/storage/box/masks, check_density = FALSE, category = CAT_CONTAINERS), \ - new /datum/stack_recipe("body bag box", /obj/item/storage/box/bodybags, check_density = FALSE, category = CAT_CONTAINERS), \ - new /datum/stack_recipe("perscription glasses box", /obj/item/storage/box/rxglasses, check_density = FALSE, category = CAT_CONTAINERS), \ - new /datum/stack_recipe("medipen box", /obj/item/storage/box/medipens, check_density = FALSE, category = CAT_CONTAINERS), \ - new /datum/stack_recipe("oxygen tank box", /obj/item/storage/box/emergencytank, check_density = FALSE, category = CAT_CONTAINERS), \ - new /datum/stack_recipe("extended oxygen tank box", /obj/item/storage/box/engitank, check_density = FALSE, category = CAT_CONTAINERS), \ + new /datum/stack_recipe("pillbottle box", /obj/item/storage/box/pillbottles, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new /datum/stack_recipe("beaker box", /obj/item/storage/box/beakers, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new /datum/stack_recipe("syringe box", /obj/item/storage/box/syringes, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new /datum/stack_recipe("latex gloves box", /obj/item/storage/box/gloves, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new /datum/stack_recipe("sterile masks box", /obj/item/storage/box/masks, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new /datum/stack_recipe("body bag box", /obj/item/storage/box/bodybags, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new /datum/stack_recipe("perscription glasses box", /obj/item/storage/box/rxglasses, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new /datum/stack_recipe("medipen box", /obj/item/storage/box/medipens, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new /datum/stack_recipe("oxygen tank box", /obj/item/storage/box/emergencytank, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new /datum/stack_recipe("extended oxygen tank box", /obj/item/storage/box/engitank, crafting_flags = NONE, category = CAT_CONTAINERS), \ null, \ - new /datum/stack_recipe("survival box", /obj/item/storage/box/survival/crafted, check_density = FALSE, category = CAT_CONTAINERS), \ - new /datum/stack_recipe("extended tank survival box", /obj/item/storage/box/survival/engineer/crafted, check_density = FALSE, category = CAT_CONTAINERS), \ - new /datum/stack_recipe("disk box", /obj/item/storage/box/disks, check_density = FALSE, category = CAT_CONTAINERS), \ - new /datum/stack_recipe("light tubes box", /obj/item/storage/box/lights/tubes, check_density = FALSE, category = CAT_CONTAINERS), \ - new /datum/stack_recipe("light bulbs box", /obj/item/storage/box/lights/bulbs, check_density = FALSE, category = CAT_CONTAINERS), \ - new /datum/stack_recipe("mixed lights box", /obj/item/storage/box/lights/mixed, check_density = FALSE, category = CAT_CONTAINERS), \ - new /datum/stack_recipe("mouse traps box", /obj/item/storage/box/mousetraps, check_density = FALSE, category = CAT_CONTAINERS), \ - new /datum/stack_recipe("candle box", /obj/item/storage/fancy/candle_box, check_density = FALSE, category = CAT_CONTAINERS), \ - new /datum/stack_recipe("bandage box", /obj/item/storage/box/bandages, check_density = FALSE, category = CAT_CONTAINERS) + new /datum/stack_recipe("survival box", /obj/item/storage/box/survival/crafted, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new /datum/stack_recipe("extended tank survival box", /obj/item/storage/box/survival/engineer/crafted, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new /datum/stack_recipe("disk box", /obj/item/storage/box/disks, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new /datum/stack_recipe("light tubes box", /obj/item/storage/box/lights/tubes, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new /datum/stack_recipe("light bulbs box", /obj/item/storage/box/lights/bulbs, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new /datum/stack_recipe("mixed lights box", /obj/item/storage/box/lights/mixed, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new /datum/stack_recipe("mouse traps box", /obj/item/storage/box/mousetraps, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new /datum/stack_recipe("candle box", /obj/item/storage/fancy/candle_box, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new /datum/stack_recipe("bandage box", /obj/item/storage/box/bandages, crafting_flags = NONE, category = CAT_CONTAINERS) )), null, \ @@ -676,18 +676,18 @@ GLOBAL_LIST_INIT(cardboard_recipes, list ( \ */ GLOBAL_LIST_INIT(bronze_recipes, list ( \ - new/datum/stack_recipe("wall gear", /obj/structure/girder/bronze, 2, time = 2 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_STRUCTURE), \ + new/datum/stack_recipe("wall gear", /obj/structure/girder/bronze, 2, time = 2 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_STRUCTURE), \ null, - new/datum/stack_recipe("directional bronze window", /obj/structure/window/bronze/unanchored, time = 0, on_solid_ground = TRUE, check_direction = TRUE, category = CAT_WINDOWS), \ - new/datum/stack_recipe("fulltile bronze window", /obj/structure/window/bronze/fulltile/unanchored, 2, time = 0, on_solid_ground = TRUE, is_fulltile = TRUE, category = CAT_WINDOWS), \ - new/datum/stack_recipe("pinion airlock assembly", /obj/structure/door_assembly/door_assembly_bronze, 4, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_DOORS), \ - new/datum/stack_recipe("bronze pinion airlock assembly", /obj/structure/door_assembly/door_assembly_bronze/seethru, 4, time = 5 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_DOORS), \ - new/datum/stack_recipe("bronze floor tile", /obj/item/stack/tile/bronze, 1, 4, 20, check_density = FALSE, category = CAT_TILES), \ - new/datum/stack_recipe("bronze hat", /obj/item/clothing/head/costume/bronze, check_density = FALSE, category = CAT_CLOTHING), \ - new/datum/stack_recipe("bronze suit", /obj/item/clothing/suit/costume/bronze, check_density = FALSE, category = CAT_CLOTHING), \ - new/datum/stack_recipe("bronze boots", /obj/item/clothing/shoes/bronze, check_density = FALSE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("directional bronze window", /obj/structure/window/bronze/unanchored, time = 0, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ON_SOLID_GROUND | CRAFT_CHECK_DIRECTION, category = CAT_WINDOWS), \ + new/datum/stack_recipe("fulltile bronze window", /obj/structure/window/bronze/fulltile/unanchored, 2, time = 0, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ON_SOLID_GROUND | CRAFT_IS_FULLTILE, category = CAT_WINDOWS), \ + new/datum/stack_recipe("pinion airlock assembly", /obj/structure/door_assembly/door_assembly_bronze, 4, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_DOORS), \ + new/datum/stack_recipe("bronze pinion airlock assembly", /obj/structure/door_assembly/door_assembly_bronze/seethru, 4, time = 5 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_DOORS), \ + new/datum/stack_recipe("bronze floor tile", /obj/item/stack/tile/bronze, 1, 4, 20, crafting_flags = NONE, category = CAT_TILES), \ + new/datum/stack_recipe("bronze hat", /obj/item/clothing/head/costume/bronze, crafting_flags = NONE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("bronze suit", /obj/item/clothing/suit/costume/bronze, crafting_flags = NONE, category = CAT_CLOTHING), \ + new/datum/stack_recipe("bronze boots", /obj/item/clothing/shoes/bronze, crafting_flags = NONE, category = CAT_CLOTHING), \ null, - new/datum/stack_recipe("bronze chair", /obj/structure/chair/bronze, 1, time = 0, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_FURNITURE), \ + new/datum/stack_recipe("bronze chair", /obj/structure/chair/bronze, 1, time = 0, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_FURNITURE), \ )) /obj/item/stack/sheet/bronze @@ -784,19 +784,19 @@ GLOBAL_LIST_INIT(bronze_recipes, list ( \ slapcraft_recipes = slapcraft_recipe_list,\ ) GLOBAL_LIST_INIT(plastic_recipes, list( - new /datum/stack_recipe("plastic floor tile", /obj/item/stack/tile/plastic, 1, 4, 20, time = 2 SECONDS, check_density = FALSE, category = CAT_TILES), \ - new /datum/stack_recipe("light tram tile", /obj/item/stack/thermoplastic/light, 1, 4, 20, time = 2 SECONDS, check_density = FALSE, category = CAT_TILES), \ - new /datum/stack_recipe("dark tram tile", /obj/item/stack/thermoplastic, 1, 4, 20, time = 2 SECONDS, check_density = FALSE, category = CAT_TILES), \ - new /datum/stack_recipe("folding plastic chair", /obj/structure/chair/plastic, 2, check_density = FALSE, category = CAT_FURNITURE), \ - new /datum/stack_recipe("plastic flaps", /obj/structure/plasticflaps, 5, one_per_turf = TRUE, on_solid_ground = TRUE, time = 4 SECONDS, category = CAT_FURNITURE), \ - new /datum/stack_recipe("water bottle", /obj/item/reagent_containers/cup/glass/waterbottle/empty, check_density = FALSE, category = CAT_CONTAINERS), \ - new /datum/stack_recipe("large water bottle", /obj/item/reagent_containers/cup/glass/waterbottle/large/empty, 3, check_density = FALSE, category = CAT_CONTAINERS), \ - new /datum/stack_recipe("colo cups", /obj/item/reagent_containers/cup/glass/colocup, 1, check_density = FALSE, category = CAT_CONTAINERS), \ - new /datum/stack_recipe("mannequin", /obj/structure/mannequin/plastic, 25, time = 5 SECONDS, one_per_turf = TRUE, check_density = FALSE, category = CAT_ENTERTAINMENT), \ - new /datum/stack_recipe("wet floor sign", /obj/item/clothing/suit/caution, 2, check_density = FALSE, category = CAT_EQUIPMENT), \ - new /datum/stack_recipe("warning cone", /obj/item/clothing/head/cone, 2, check_density = FALSE, category = CAT_EQUIPMENT), \ - new /datum/stack_recipe("blank wall sign", /obj/item/sign, 1, check_density = FALSE, category = CAT_FURNITURE), \ - new /datum/stack_recipe("rebellion mask", /obj/item/clothing/mask/rebellion, 1, check_density = FALSE, category = CAT_CLOTHING))) + new /datum/stack_recipe("plastic floor tile", /obj/item/stack/tile/plastic, 1, 4, 20, time = 2 SECONDS, crafting_flags = NONE, category = CAT_TILES), \ + new /datum/stack_recipe("light tram tile", /obj/item/stack/thermoplastic/light, 1, 4, 20, time = 2 SECONDS, crafting_flags = NONE, category = CAT_TILES), \ + new /datum/stack_recipe("dark tram tile", /obj/item/stack/thermoplastic, 1, 4, 20, time = 2 SECONDS, crafting_flags = NONE, category = CAT_TILES), \ + new /datum/stack_recipe("folding plastic chair", /obj/structure/chair/plastic, 2, crafting_flags = NONE, category = CAT_FURNITURE), \ + new /datum/stack_recipe("plastic flaps", /obj/structure/plasticflaps, 5, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, time = 4 SECONDS, category = CAT_FURNITURE), \ + new /datum/stack_recipe("water bottle", /obj/item/reagent_containers/cup/glass/waterbottle/empty, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new /datum/stack_recipe("large water bottle", /obj/item/reagent_containers/cup/glass/waterbottle/large/empty, 3, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new /datum/stack_recipe("colo cups", /obj/item/reagent_containers/cup/glass/colocup, 1, crafting_flags = NONE, category = CAT_CONTAINERS), \ + new /datum/stack_recipe("mannequin", /obj/structure/mannequin/plastic, 25, time = 5 SECONDS, crafting_flags = CRAFT_ONE_PER_TURF, category = CAT_ENTERTAINMENT), \ + new /datum/stack_recipe("wet floor sign", /obj/item/clothing/suit/caution, 2, crafting_flags = NONE, category = CAT_EQUIPMENT), \ + new /datum/stack_recipe("warning cone", /obj/item/clothing/head/cone, 2, crafting_flags = NONE, category = CAT_EQUIPMENT), \ + new /datum/stack_recipe("blank wall sign", /obj/item/sign, 1, crafting_flags = NONE, category = CAT_FURNITURE), \ + new /datum/stack_recipe("rebellion mask", /obj/item/clothing/mask/rebellion, 1, crafting_flags = NONE, category = CAT_CLOTHING))) /obj/item/stack/sheet/plastic name = "plastic" @@ -820,8 +820,8 @@ GLOBAL_LIST_INIT(plastic_recipes, list( . += GLOB.plastic_recipes GLOBAL_LIST_INIT(paperframe_recipes, list( -new /datum/stack_recipe("paper frame separator", /obj/structure/window/paperframe, 2, one_per_turf = TRUE, on_solid_ground = TRUE, is_fulltile = TRUE, time = 1 SECONDS), \ -new /datum/stack_recipe("paper frame door", /obj/structure/mineral_door/paperframe, 3, one_per_turf = TRUE, on_solid_ground = TRUE, time = 1 SECONDS ))) +new /datum/stack_recipe("paper frame separator", /obj/structure/window/paperframe, 2, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND | CRAFT_IS_FULLTILE, time = 1 SECONDS), \ +new /datum/stack_recipe("paper frame door", /obj/structure/mineral_door/paperframe, 3, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, time = 1 SECONDS ))) /obj/item/stack/sheet/paperframes name = "paper frames" diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm index 18a95cd77d378..4258465f3a07e 100644 --- a/code/game/objects/items/stacks/stack.dm +++ b/code/game/objects/items/stacks/stack.dm @@ -1,9 +1,3 @@ -//stack recipe placement check types -/// Checks if there is an object of the result type in any of the cardinal directions -#define STACK_CHECK_CARDINALS (1<<0) -/// Checks if there is an object of the result type within one tile -#define STACK_CHECK_ADJACENT (1<<1) - /* Stack type objects! * Contains: * Stacks @@ -423,7 +417,7 @@ return var/turf/created_turf = covered_turf.place_on_top(recipe.result_type, flags = CHANGETURF_INHERIT_AIR) builder.balloon_alert(builder, "placed [ispath(recipe.result_type, /turf/open) ? "floor" : "wall"]") - if(recipe.applies_mats && LAZYLEN(mats_per_unit)) + if((recipe.crafting_flags & CRAFT_APPLIES_MATS) && LAZYLEN(mats_per_unit)) created_turf.set_custom_materials(mats_per_unit, recipe.req_amount / recipe.res_amount) else @@ -439,7 +433,7 @@ builder.investigate_log("crafted [recipe.title]", INVESTIGATE_CRAFTING) // Apply mat datums - if(recipe.applies_mats && LAZYLEN(mats_per_unit)) + if((recipe.crafting_flags & CRAFT_APPLIES_MATS) && LAZYLEN(mats_per_unit)) if(isstack(created)) var/obj/item/stack/crafted_stack = created crafted_stack.set_mats_per_unit(mats_per_unit, recipe.req_amount / recipe.res_amount) @@ -484,16 +478,16 @@ return FALSE var/turf/dest_turf = get_turf(builder) - if(recipe.one_per_turf && (locate(recipe.result_type) in dest_turf)) + if((recipe.crafting_flags & CRAFT_ONE_PER_TURF) && (locate(recipe.result_type) in dest_turf)) builder.balloon_alert(builder, "already one here!") return FALSE - if(recipe.check_direction) - if(!valid_build_direction(dest_turf, builder.dir, is_fulltile = recipe.is_fulltile)) + if(recipe.crafting_flags & CRAFT_CHECK_DIRECTION) + if(!valid_build_direction(dest_turf, builder.dir, is_fulltile = (recipe.crafting_flags & CRAFT_IS_FULLTILE))) builder.balloon_alert(builder, "won't fit here!") return FALSE - if(recipe.on_solid_ground) + if(recipe.crafting_flags & CRAFT_ON_SOLID_GROUND) if(isclosedturf(dest_turf)) builder.balloon_alert(builder, "cannot be made on a wall!") return FALSE @@ -503,7 +497,7 @@ builder.balloon_alert(builder, "must be made on solid ground!") return FALSE - if(recipe.check_density) + if(recipe.crafting_flags & CRAFT_CHECK_DENSITY) for(var/obj/object in dest_turf) if(object.density && !(object.obj_flags & IGNORE_DENSITY) || object.obj_flags & BLOCKS_CONSTRUCTION) builder.balloon_alert(builder, "something is in the way!") @@ -742,6 +736,3 @@ add_hiddenprint_list(GET_ATOM_HIDDENPRINTS(from)) fingerprintslast = from.fingerprintslast //TODO bloody overlay - -#undef STACK_CHECK_CARDINALS -#undef STACK_CHECK_ADJACENT diff --git a/code/game/objects/items/stacks/stack_recipe.dm b/code/game/objects/items/stacks/stack_recipe.dm index bfdc3c8ca5717..4a150f4f2abce 100644 --- a/code/game/objects/items/stacks/stack_recipe.dm +++ b/code/game/objects/items/stacks/stack_recipe.dm @@ -15,21 +15,8 @@ var/max_res_amount = 1 /// How long it takes to make var/time = 0 - /// If only one of the resulting atom is allowed per turf - var/one_per_turf = FALSE - /// If the atom is fulltile, as in a fulltile window. This is used for the direction check to prevent fulltile windows from being able to be built over directional stuff. - /// Setting this to true will effectively set check_direction to true. - var/is_fulltile = FALSE - /// If this atom should run the direction check, for use when building things like directional windows where you can have more than one per turf - var/check_direction = FALSE - /// If the atom requires a floor below - var/on_solid_ground = FALSE - /// If the atom checks that there are objects with density in the same turf when being built. TRUE by default - var/check_density = TRUE /// Bitflag of additional placement checks required to place. (STACK_CHECK_CARDINALS|STACK_CHECK_ADJACENT|STACK_CHECK_TRAM_FORBIDDEN|STACK_CHECK_TRAM_EXCLUSIVE) var/placement_checks = NONE - /// If TRUE, the created atom will gain custom mat datums - var/applies_mats = FALSE /// What trait, if any, boosts the construction speed of this item var/trait_booster /// How much the trait above, if supplied, boosts the construct speed of this item @@ -37,6 +24,9 @@ /// Category for general crafting menu var/category + ///crafting_flags var to hold bool values + var/crafting_flags = CRAFT_CHECK_DENSITY + /datum/stack_recipe/New( title, result_type, @@ -44,13 +34,8 @@ res_amount = 1, max_res_amount = 1, time = 0, - one_per_turf = FALSE, - on_solid_ground = FALSE, - is_fulltile = FALSE, - check_direction = FALSE, - check_density = TRUE, + crafting_flags = CRAFT_CHECK_DENSITY, placement_checks = NONE, - applies_mats = FALSE, trait_booster, trait_modifier = 1, category, @@ -62,13 +47,7 @@ src.res_amount = res_amount src.max_res_amount = max_res_amount src.time = time - src.one_per_turf = one_per_turf - src.on_solid_ground = on_solid_ground - src.is_fulltile = is_fulltile - src.check_direction = check_direction || is_fulltile - src.check_density = check_density src.placement_checks = placement_checks - src.applies_mats = applies_mats src.trait_booster = trait_booster src.trait_modifier = trait_modifier src.category = src.category || category || CAT_MISC @@ -88,7 +67,6 @@ on_solid_ground = FALSE, window_checks = FALSE, placement_checks = NONE, - applies_mats = FALSE, trait_booster, trait_modifier = 1, desc, diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_bread.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_bread.dm index c05446d35218e..8f78cb01ebc23 100644 --- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_bread.dm +++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_bread.dm @@ -150,7 +150,7 @@ ) result = /obj/item/food/croissant/throwing category = CAT_BREAD - always_available = FALSE + crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_MUST_BE_LEARNED /datum/crafting_recipe/food/breaddog name = "Living dog/bread hybrid" diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_cake.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_cake.dm index b34cc5f36e965..4b56874eb7c6b 100644 --- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_cake.dm +++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_cake.dm @@ -184,7 +184,6 @@ /datum/crafting_recipe/food/clowncake name = "clown cake" - always_available = FALSE reqs = list( /obj/item/food/cake/plain = 1, /obj/item/food/sundae = 2, @@ -192,16 +191,17 @@ ) result = /obj/item/food/cake/clown_cake category = CAT_CAKE + crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_MUST_BE_LEARNED /datum/crafting_recipe/food/vanillacake name = "vanilla cake" - always_available = FALSE reqs = list( /obj/item/food/cake/plain = 1, /obj/item/food/grown/vanillapod = 2 ) result = /obj/item/food/cake/vanilla_cake category = CAT_CAKE + crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_MUST_BE_LEARNED /datum/crafting_recipe/food/trumpetcake name = "Spaceman's Cake" diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_pie.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_pie.dm index a9f1ad23d8e26..c0c99bbe6523b 100644 --- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_pie.dm +++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_pie.dm @@ -132,7 +132,6 @@ /datum/crafting_recipe/food/mimetart name = "Mime tart" - always_available = FALSE reqs = list( /datum/reagent/consumable/milk = 5, /datum/reagent/consumable/sugar = 5, @@ -141,10 +140,10 @@ ) result = /obj/item/food/pie/mimetart category = CAT_PIE + crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_MUST_BE_LEARNED /datum/crafting_recipe/food/berrytart name = "Berry tart" - always_available = FALSE reqs = list( /datum/reagent/consumable/milk = 5, /datum/reagent/consumable/sugar = 5, @@ -153,10 +152,10 @@ ) result = /obj/item/food/pie/berrytart category = CAT_PIE + crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_MUST_BE_LEARNED /datum/crafting_recipe/food/cocolavatart name = "Chocolate Lava tart" - always_available = FALSE reqs = list( /datum/reagent/consumable/milk = 5, /datum/reagent/consumable/sugar = 5, @@ -166,6 +165,7 @@ ) result = /obj/item/food/pie/cocolavatart category = CAT_PIE + crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_MUST_BE_LEARNED /datum/crafting_recipe/food/blumpkinpie name = "Blumpkin pie" diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_sandwich.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_sandwich.dm index b0c44629e1853..7761a57fcfdbd 100644 --- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_sandwich.dm +++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_sandwich.dm @@ -118,7 +118,6 @@ /datum/crafting_recipe/food/death_sandwich name = "Death Sandwich" - always_available = FALSE reqs = list( /obj/item/food/breadslice/plain = 2, /obj/item/food/salami = 4, @@ -127,6 +126,7 @@ ) result = /obj/item/food/sandwich/death category = CAT_SANDWICH + crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_MUST_BE_LEARNED /datum/crafting_recipe/food/toast_sandwich name = "Toast Sandwich" diff --git a/code/modules/mining/ores_coins.dm b/code/modules/mining/ores_coins.dm index e94b8d981ffeb..65644b00cc87a 100644 --- a/code/modules/mining/ores_coins.dm +++ b/code/modules/mining/ores_coins.dm @@ -107,9 +107,9 @@ merge_type = /obj/item/stack/ore/glass GLOBAL_LIST_INIT(sand_recipes, list(\ - new /datum/stack_recipe("pile of dirt", /obj/machinery/hydroponics/soil, 3, time = 1 SECONDS, one_per_turf = TRUE, on_solid_ground = TRUE, category = CAT_TOOLS), \ - new /datum/stack_recipe("sandstone", /obj/item/stack/sheet/mineral/sandstone, 1, 1, 50, check_density = FALSE, category = CAT_MISC),\ - new /datum/stack_recipe("aesthetic volcanic floor tile", /obj/item/stack/tile/basalt, 2, 1, 50, check_density = FALSE, category = CAT_TILES)\ + new /datum/stack_recipe("pile of dirt", /obj/machinery/hydroponics/soil, 3, time = 1 SECONDS, crafting_flags = CRAFT_CHECK_DENSITY | CRAFT_ONE_PER_TURF | CRAFT_ON_SOLID_GROUND, category = CAT_TOOLS), \ + new /datum/stack_recipe("sandstone", /obj/item/stack/sheet/mineral/sandstone, 1, 1, 50, crafting_flags = NONE, category = CAT_MISC),\ + new /datum/stack_recipe("aesthetic volcanic floor tile", /obj/item/stack/tile/basalt, 2, 1, 50, crafting_flags = NONE, category = CAT_TILES)\ )) /obj/item/stack/ore/glass/Initialize(mapload, new_amount, merge, list/mat_override, mat_amt) From c5e534ecc4e2056d4837d9fe627f0597079e46b6 Mon Sep 17 00:00:00 2001 From: orange man <61334995+comfyorange@users.noreply.github.com> Date: Fri, 3 May 2024 19:36:15 +1200 Subject: [PATCH 52/87] Automatic changelog for PR #82833 [ci skip] --- html/changelogs/AutoChangeLog-pr-82833.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-82833.yml diff --git a/html/changelogs/AutoChangeLog-pr-82833.yml b/html/changelogs/AutoChangeLog-pr-82833.yml new file mode 100644 index 0000000000000..b9e08edc7e89e --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-82833.yml @@ -0,0 +1,4 @@ +author: "Xander3359" +delete-after: True +changes: + - bugfix: "You can no longer bypass construction restrictions via the crafting menu" \ No newline at end of file From 15fe98edfa00fdbfe2f65c6015ababc738635f42 Mon Sep 17 00:00:00 2001 From: Ghom <42542238+Ghommie@users.noreply.github.com> Date: Fri, 3 May 2024 13:23:52 +0200 Subject: [PATCH 53/87] Adds a station trait that brightens up IceBox's surface. (#82945) ## About The Pull Request This PR adds a positive trait that brings lights to the outdoors surface areas of Icebox. ## Why It's Good For The Game Building upon the features of the station traits. This is how it'd look: ![It's Always Sunny On IceBoxStation](https://github.com/tgstation/tgstation/assets/42542238/af4238e9-c731-41c9-9eaf-8fcb04d6fe4f) ## Changelog :cl: add: Adds an Icebox-specific station trait that brightens outdoors areas on the surface level. /:cl: --- code/__DEFINES/traits/declarations.dm | 1 + code/_globalvars/traits/_traits.dm | 1 + code/datums/station_traits/positive_traits.dm | 9 +++++++++ code/game/area/areas/mining.dm | 5 +++++ 4 files changed, 16 insertions(+) diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index f2cb9069fb64f..a3218bd5db733 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -950,6 +950,7 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define STATION_TRAIT_BIGGER_PODS "station_trait_bigger_pods" #define STATION_TRAIT_BIRTHDAY "station_trait_birthday" #define STATION_TRAIT_BOTS_GLITCHED "station_trait_bot_glitch" +#define STATION_TRAIT_BRIGHT_DAY "station_trait_bright_day" #define STATION_TRAIT_CARP_INFESTATION "station_trait_carp_infestation" #define STATION_TRAIT_CYBERNETIC_REVOLUTION "station_trait_cybernetic_revolution" #define STATION_TRAIT_EMPTY_MAINT "station_trait_empty_maint" diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index dd0c89c2211c8..91ea4d2be0033 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -79,6 +79,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "STATION_TRAIT_BIGGER_PODS" = STATION_TRAIT_BIGGER_PODS, "STATION_TRAIT_BIRTHDAY" = STATION_TRAIT_BIRTHDAY, "STATION_TRAIT_BOTS_GLITCHED" = STATION_TRAIT_BOTS_GLITCHED, + "STATION_TRAIT_BRIGHT_DAY" = STATION_TRAIT_BRIGHT_DAY, "STATION_TRAIT_CARP_INFESTATION" = STATION_TRAIT_CARP_INFESTATION, "STATION_TRAIT_CYBERNETIC_REVOLUTION" = STATION_TRAIT_CYBERNETIC_REVOLUTION, "STATION_TRAIT_EMPTY_MAINT" = STATION_TRAIT_EMPTY_MAINT, diff --git a/code/datums/station_traits/positive_traits.dm b/code/datums/station_traits/positive_traits.dm index bff2d532570f9..8398e02139d74 100644 --- a/code/datums/station_traits/positive_traits.dm +++ b/code/datums/station_traits/positive_traits.dm @@ -292,6 +292,15 @@ weight_multiplier = 3 max_occurrences_modifier = 10 //lotta cows +/datum/station_trait/bright_day + name = "Bright Day" + report_message = "The stars shine bright and the clouds are scarcer than usual. It's a bright day here on the Ice Moon's surface." + trait_type = STATION_TRAIT_POSITIVE + weight = 5 + show_in_report = TRUE + trait_flags = STATION_TRAIT_PLANETARY + trait_to_give = STATION_TRAIT_BRIGHT_DAY + /datum/station_trait/shuttle_sale name = "Shuttle Firesale" report_message = "The Nanotrasen Emergency Dispatch team is celebrating a record number of shuttle calls in the recent quarter. Some of your emergency shuttle options have been discounted!" diff --git a/code/game/area/areas/mining.dm b/code/game/area/areas/mining.dm index 1582f7390cf04..38855de366f9f 100644 --- a/code/game/area/areas/mining.dm +++ b/code/game/area/areas/mining.dm @@ -209,6 +209,11 @@ name = "Icemoon Wastes" outdoors = TRUE +/area/icemoon/surface/outdoors/Initialize(mapload) + if(HAS_TRAIT(SSstation, STATION_TRAIT_BRIGHT_DAY)) + base_lighting_alpha = 145 + return ..() + /area/icemoon/surface/outdoors/nospawn // this is the area you use for stuff to not spawn, but if you still want weather. /area/icemoon/surface/outdoors/nospawn/New() // unless you roll forested trait lol From 70eefcd71d4086e0dfbe33658e096253d8668c66 Mon Sep 17 00:00:00 2001 From: orange man <61334995+comfyorange@users.noreply.github.com> Date: Fri, 3 May 2024 23:24:11 +1200 Subject: [PATCH 54/87] Automatic changelog for PR #82945 [ci skip] --- html/changelogs/AutoChangeLog-pr-82945.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-82945.yml diff --git a/html/changelogs/AutoChangeLog-pr-82945.yml b/html/changelogs/AutoChangeLog-pr-82945.yml new file mode 100644 index 0000000000000..a1485366ae2f4 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-82945.yml @@ -0,0 +1,4 @@ +author: "Ghommie" +delete-after: True +changes: + - rscadd: "Adds an Icebox-specific station trait that brightens outdoors areas on the surface level." \ No newline at end of file From 7778471269a40250f59dd2620fa84bd447a956cd Mon Sep 17 00:00:00 2001 From: cnleth <113535457+cnleth@users.noreply.github.com> Date: Fri, 3 May 2024 14:32:27 +0300 Subject: [PATCH 55/87] Fire ant colonies created by burning actually contain fire ants (#83002) ## About The Pull Request Fire ant colonies created by burning regular ants now give you fire ants when scooped up There were 2 lines of code clearing the ants' reagent when they're burned. They're not needed because ants use `decal_reagent` Tested and it works without these 2 lines ![fireants](https://github.com/tgstation/tgstation/assets/113535457/aee4ec28-a767-4dfe-b870-2a222848ae3a) ## Why It's Good For The Game Fixes #82864 ## Changelog :cl: fix: Fire ant colonies created by burning regular ants will now contain fire ants as their reagent /:cl: --- code/game/objects/effects/decals/cleanable/misc.dm | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/code/game/objects/effects/decals/cleanable/misc.dm b/code/game/objects/effects/decals/cleanable/misc.dm index f21bfc4788426..0af1e402ee1dd 100644 --- a/code/game/objects/effects/decals/cleanable/misc.dm +++ b/code/game/objects/effects/decals/cleanable/misc.dm @@ -412,9 +412,7 @@ . += emissive_appearance(icon, "[icon_state]_light", src, alpha = src.alpha) /obj/effect/decal/cleanable/ants/fire_act(exposed_temperature, exposed_volume) - var/obj/effect/decal/cleanable/ants/fire/fire_ants = new(loc) - fire_ants.reagents.clear_reagents() - reagents.trans_to(fire_ants, fire_ants.reagents.maximum_volume) + new /obj/effect/decal/cleanable/ants/fire(loc) qdel(src) /obj/effect/decal/cleanable/ants/fire From f112369547a7da6fdafd69c1d43baf0fc6f76f77 Mon Sep 17 00:00:00 2001 From: orange man <61334995+comfyorange@users.noreply.github.com> Date: Fri, 3 May 2024 23:32:46 +1200 Subject: [PATCH 56/87] Automatic changelog for PR #83002 [ci skip] --- html/changelogs/AutoChangeLog-pr-83002.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-83002.yml diff --git a/html/changelogs/AutoChangeLog-pr-83002.yml b/html/changelogs/AutoChangeLog-pr-83002.yml new file mode 100644 index 0000000000000..114603f123725 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-83002.yml @@ -0,0 +1,4 @@ +author: "cnleth" +delete-after: True +changes: + - bugfix: "Fire ant colonies created by burning regular ants will now contain fire ants as their reagent" \ No newline at end of file From 82d5a883252e6f4eec35146bf0365518b49bba5c Mon Sep 17 00:00:00 2001 From: oranges Date: Sat, 4 May 2024 06:35:20 +1200 Subject: [PATCH 57/87] General IP intel tweaks (#82904) Co-authored-by: Zephyr <12817816+ZephyrTFA@users.noreply.github.com> Co-authored-by: ZephyrTFA Co-authored-by: Kyle Spier-Swenson --- code/__DEFINES/admin_verb.dm | 2 +- .../configuration/entries/general.dm | 14 +- code/controllers/subsystem/ipintel.dm | 130 +++++++++--------- code/modules/client/client_procs.dm | 2 +- 4 files changed, 72 insertions(+), 76 deletions(-) diff --git a/code/__DEFINES/admin_verb.dm b/code/__DEFINES/admin_verb.dm index 7e811c121961c..04806e098b2c4 100644 --- a/code/__DEFINES/admin_verb.dm +++ b/code/__DEFINES/admin_verb.dm @@ -87,7 +87,7 @@ _ADMIN_VERB(verb_path_name, verb_permissions, verb_name, verb_desc, verb_categor #define ADMIN_CATEGORY_OBJECT "Object" #define ADMIN_CATEGORY_MAPPING "Mapping" #define ADMIN_CATEGORY_PROFILE "Profile" -#define ADMIN_CATEGORY_IPINTEL "IPIntel" +#define ADMIN_CATEGORY_IPINTEL "Admin.IPIntel" // Visibility flags #define ADMIN_VERB_VISIBLITY_FLAG_MAPPING_DEBUG "Map-Debug" diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm index 98a386b7c8b82..69b3bbcad64f6 100644 --- a/code/controllers/configuration/entries/general.dm +++ b/code/controllers/configuration/entries/general.dm @@ -453,7 +453,7 @@ /datum/config_entry/string/ipintel_email /datum/config_entry/string/ipintel_email/ValidateAndSet(str_val) - return str_val != "ch@nge.me" && ..() + return str_val != "ch@nge.me" && (!length(str_val) || findtext(str_val, "@")) && ..() /datum/config_entry/number/ipintel_rating_bad default = 1 @@ -462,25 +462,25 @@ max_val = 1 /datum/config_entry/flag/ipintel_reject_rate_limited - default = TRUE + default = FALSE /datum/config_entry/flag/ipintel_reject_bad - default = TRUE + default = FALSE /datum/config_entry/flag/ipintel_reject_unknown default = FALSE /datum/config_entry/number/ipintel_rate_minute default = 15 - -/datum/config_entry/number/ipintel_rate_day - default = 500 + min_val = 0 /datum/config_entry/number/ipintel_cache_length default = 7 + min_val = 0 /datum/config_entry/number/ipintel_exempt_playtime_living - default = 0 + default = 5 + min_val = 0 /datum/config_entry/flag/aggressive_changelog diff --git a/code/controllers/subsystem/ipintel.dm b/code/controllers/subsystem/ipintel.dm index ba4cda13a43d9..9bbaf320139d5 100644 --- a/code/controllers/subsystem/ipintel.dm +++ b/code/controllers/subsystem/ipintel.dm @@ -1,26 +1,14 @@ SUBSYSTEM_DEF(ipintel) name = "XKeyScore" init_order = INIT_ORDER_XKEYSCORE - flags = SS_OK_TO_FAIL_INIT|SS_NO_FIRE + flags = SS_NO_INIT|SS_NO_FIRE /// The threshold for probability to be considered a VPN and/or bad IP var/probability_threshold - /// The email used in conjuction with https://check.getipintel.net/check.php - var/contact_email - /// Maximum number of queries per minute - var/max_queries_per_minute - /// Maximum number of queries per day - var/max_queries_per_day - /// Query base - var/query_base - /// The length of time (days) to cache IP intel - var/ipintel_cache_length - /// The living playtime (minutes) for players to be exempt from IPIntel checks - var/exempt_living_playtime /// Cache for previously queried IP addresses and those stored in the database var/list/datum/ip_intel/cached_queries = list() /// The store for rate limiting - var/list/rate_limits + var/list/rate_limit_minute /// The ip intel for a given address /datum/ip_intel @@ -30,47 +18,39 @@ SUBSYSTEM_DEF(ipintel) var/address var/date -/datum/controller/subsystem/ipintel/Initialize() +/datum/controller/subsystem/ipintel/OnConfigLoad() var/list/fail_messages = list() - - probability_threshold = CONFIG_GET(number/ipintel_rating_bad) - if(probability_threshold < 0 || probability_threshold > 1) - fail_messages += list("invalid probability threshold") - - contact_email = CONFIG_GET(string/ipintel_email) - if(isnull(contact_email) || !findtext(contact_email, "@")) - fail_messages += list("invalid contact email") - - var/max_queries_per_minute = CONFIG_GET(number/ipintel_rate_minute) - var/max_queries_per_day = CONFIG_GET(number/ipintel_rate_day) - if(max_queries_per_minute < 0 || max_queries_per_day < 0) - fail_messages += list("invalid rate limits") - - var/query_base = CONFIG_GET(string/ipintel_base) - if(isnull(query_base)) - fail_messages += list("invalid query base") - - var/ipintel_cache_length = CONFIG_GET(number/ipintel_cache_length) - if(ipintel_cache_length < 0) - fail_messages += list("invalid cache length") - - var/exempt_living_playtime = CONFIG_GET(number/ipintel_exempt_playtime_living) - if(exempt_living_playtime < 0) - fail_messages += list("invalid exempt living playtime") - + + var/contact_email = CONFIG_GET(string/ipintel_email) + + if(!length(contact_email)) + fail_messages += "No contact email" + + if(!findtext(contact_email, "@")) + fail_messages += "Invalid contact email" + + if(!length(CONFIG_GET(string/ipintel_base))) + fail_messages += "Invalid query base" + + if (!CONFIG_GET(flag/sql_enabled)) + fail_messages += "The database is not enabled" + if(length(fail_messages)) message_admins("IPIntel: Initialization failed check logs!") - logger.Log(LOG_CATEGORY_GAME_ACCESS, "IPIntel failed to initialize.", list( + logger.Log(LOG_CATEGORY_GAME_ACCESS, "IPIntel is not enabled because the configs are not valid.", list( "fail_messages" = fail_messages, )) - return SS_INIT_FAILURE - - return SS_INIT_SUCCESS /datum/controller/subsystem/ipintel/stat_entry(msg) - return "[..()] | D: [max_queries_per_day - rate_limits[IPINTEL_RATE_LIMIT_DAY]] | M: [max_queries_per_minute - rate_limits[IPINTEL_RATE_LIMIT_MINUTE]]" + return "[..()] | M: [CONFIG_GET(number/ipintel_rate_minute) - rate_limit_minute]" + + +/datum/controller/subsystem/ipintel/proc/is_enabled() + return length(CONFIG_GET(string/ipintel_email)) && length(CONFIG_GET(string/ipintel_base)) && CONFIG_GET(flag/sql_enabled) /datum/controller/subsystem/ipintel/proc/get_address_intel_state(address, probability_override) + if (!is_enabled()) + return IPINTEL_GOOD_IP var/datum/ip_intel/intel = query_address(address) if(isnull(intel)) stack_trace("query_address did not return an ip intel response") @@ -81,7 +61,7 @@ SUBSYSTEM_DEF(ipintel) if(!(intel.query_status in list("success", "cached"))) return IPINTEL_UNKNOWN_QUERY_ERROR - var/check_probability = probability_override || probability_threshold + var/check_probability = probability_override || CONFIG_GET(number/ipintel_rating_bad) if(intel.result >= check_probability) return IPINTEL_BAD_IP return IPINTEL_GOOD_IP @@ -92,34 +72,32 @@ SUBSYSTEM_DEF(ipintel) if(minute_key != expected_minute_key) minute_key = expected_minute_key - rate_limits[IPINTEL_RATE_LIMIT_MINUTE] = 0 + rate_limit_minute = 0 - if(rate_limits[IPINTEL_RATE_LIMIT_MINUTE] >= max_queries_per_minute) + if(rate_limit_minute >= CONFIG_GET(number/ipintel_rate_minute)) return IPINTEL_RATE_LIMITED_MINUTE - if(rate_limits[IPINTEL_RATE_LIMIT_DAY] >= max_queries_per_day) - return IPINTEL_RATE_LIMITED_DAY return FALSE /datum/controller/subsystem/ipintel/proc/query_address(address, allow_cached = TRUE) + if (!is_enabled()) + return if(allow_cached && fetch_cached_ip_intel(address)) return cached_queries[address] var/is_rate_limited = is_rate_limited() if(is_rate_limited) return is_rate_limited - if(!initialized) - return IPINTEL_UNKNOWN_INTERNAL_ERROR - - rate_limits[IPINTEL_RATE_LIMIT_MINUTE] += 1 - rate_limits[IPINTEL_RATE_LIMIT_DAY] += 1 + rate_limit_minute += 1 - var/query_base = "https://[src.query_base]/check.php?ip=" - var/query = "[query_base][address]&contact=[contact_email]&flags=b&format=json" + var/query_base = "https://[CONFIG_GET(string/ipintel_base)]/check.php?ip=" + var/query = "[query_base][address]&contact=[CONFIG_GET(string/ipintel_email)]&flags=b&format=json" var/datum/http_request/request = new request.prepare(RUSTG_HTTP_METHOD_GET, query) request.execute_blocking() var/datum/http_response/response = request.into_response() - var/list/data = response.body + var/list/data = json_decode(response.body) + // Log the response + logger.Log(LOG_CATEGORY_DEBUG, "ip check response body", data) var/datum/ip_intel/intel = new intel.query_status = data["status"] @@ -133,6 +111,9 @@ SUBSYSTEM_DEF(ipintel) return intel /datum/controller/subsystem/ipintel/proc/add_intel_to_database(datum/ip_intel/intel) + set waitfor = FALSE //no need to make the client connection wait for this step. + if (!SSdbcore.Connect()) + return var/datum/db_query/query = SSdbcore.NewQuery( "INSERT INTO [format_table_name("ipintel")] ( \ ip, \ @@ -150,13 +131,17 @@ SUBSYSTEM_DEF(ipintel) qdel(query) /datum/controller/subsystem/ipintel/proc/fetch_cached_ip_intel(address) - var/date_restrictor - if(ipintel_cache_length > 0) - date_restrictor = " AND date > DATE_SUB(NOW(), INTERVAL [ipintel_cache_length] DAY)" + if (!SSdbcore.Connect()) + return + var/ipintel_cache_length = CONFIG_GET(number/ipintel_cache_length) + var/date_restrictor = "" + var/sql_args = list("address" = address) + if(ipintel_cache_length > 1) + date_restrictor = " AND date > DATE_SUB(NOW(), INTERVAL :ipintel_cache_length DAY)" + sql_args["ipintel_cache_length"] = ipintel_cache_length var/datum/db_query/query = SSdbcore.NewQuery( - "SELECT * FROM [format_table_name("ipintel")] WHERE ip = INET_ATON(:address)[date_restrictor]", list( - "address" = address - ) + "SELECT * FROM [format_table_name("ipintel")] WHERE ip = INET_ATON(:address)[date_restrictor]", + sql_args ) query.warn_execute() query.sync() @@ -178,6 +163,9 @@ SUBSYSTEM_DEF(ipintel) return TRUE /datum/controller/subsystem/ipintel/proc/is_exempt(client/player) + if(player.holder || GLOB.deadmins[player.ckey]) + return TRUE + var/exempt_living_playtime = CONFIG_GET(number/ipintel_exempt_playtime_living) if(exempt_living_playtime > 0) var/list/play_records = player.prefs.exp if (!play_records.len) @@ -199,9 +187,13 @@ SUBSYSTEM_DEF(ipintel) qdel(query) return FALSE query.NextRow() - return !!query.item // if they have a row, they are whitelisted + . = !!query.item // if they have a row, they are whitelisted + qdel(query) + ADMIN_VERB(ipintel_allow, R_BAN, "Whitelist Player VPN", "Allow a player to connect even if they are using a VPN.", ADMIN_CATEGORY_IPINTEL, ckey as text) + if (!SSipintel.is_enabled()) + to_chat(user, "The ipintel system is not currently enabled but you can still edit the whitelists") if(SSipintel.is_whitelisted(ckey)) to_chat(user, "Player is already whitelisted.") return @@ -224,6 +216,8 @@ ADMIN_VERB(ipintel_allow, R_BAN, "Whitelist Player VPN", "Allow a player to conn message_admins("IPINTEL: [key_name_admin(user)] has whitelisted '[ckey]'") ADMIN_VERB(ipintel_revoke, R_BAN, "Revoke Player VPN Whitelist", "Revoke a player's VPN whitelist.", ADMIN_CATEGORY_IPINTEL, ckey as text) + if (!SSipintel.is_enabled()) + to_chat(user, "The ipintel system is not currently enabled but you can still edit the whitelists") if(!SSipintel.is_whitelisted(ckey)) to_chat(user, "Player is not whitelisted.") return @@ -238,6 +232,8 @@ ADMIN_VERB(ipintel_revoke, R_BAN, "Revoke Player VPN Whitelist", "Revoke a playe message_admins("IPINTEL: [key_name_admin(user)] has revoked the VPN whitelist for '[ckey]'") /client/proc/check_ip_intel() + if (!SSipintel.is_enabled()) + return if(SSipintel.is_exempt(src) || SSipintel.is_whitelisted(ckey)) return @@ -275,7 +271,7 @@ ADMIN_VERB(ipintel_revoke, R_BAN, "Revoke Player VPN Whitelist", "Revoke a playe if(!connection_rejected) return - + var/list/contact_where = list() var/forum_url = CONFIG_GET(string/forumurl) if(forum_url) diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index 2c8c63579e2f5..8e51fa917f4da 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -502,7 +502,6 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( "[key_name(src)] (IP: [address], ID: [computer_id]) is a new BYOND account [account_age] day[(account_age == 1?"":"s")] old, created on [account_join_date].[new_player_alert_role ? " <@&[new_player_alert_role]>" : ""]" ) scream_about_watchlists(src) - check_ip_intel() validate_key_in_db() // If we aren't already generating a ban cache, fire off a build request // This way hopefully any users of request_ban_cache will never need to yield @@ -532,6 +531,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( to_chat(src, span_warning("Unable to access asset cache browser, if you are using a custom skin file, please allow DS to download the updated version, if you are not, then make a bug report. This is not a critical issue but can cause issues with resource downloading, as it is impossible to know when extra resources arrived to you.")) update_ambience_pref() + check_ip_intel() //This is down here because of the browse() calls in tooltip/New() if(!tooltips) From d176704f2b676fd6d7a1121942369f4c14275931 Mon Sep 17 00:00:00 2001 From: Zephyr <12817816+ZephyrTFA@users.noreply.github.com> Date: Fri, 3 May 2024 15:37:04 -0400 Subject: [PATCH 58/87] text2num moment for ipintel (#83032) --- code/controllers/subsystem/ipintel.dm | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/code/controllers/subsystem/ipintel.dm b/code/controllers/subsystem/ipintel.dm index 9bbaf320139d5..0f162e2a8616f 100644 --- a/code/controllers/subsystem/ipintel.dm +++ b/code/controllers/subsystem/ipintel.dm @@ -20,21 +20,21 @@ SUBSYSTEM_DEF(ipintel) /datum/controller/subsystem/ipintel/OnConfigLoad() var/list/fail_messages = list() - + var/contact_email = CONFIG_GET(string/ipintel_email) - + if(!length(contact_email)) fail_messages += "No contact email" - + if(!findtext(contact_email, "@")) fail_messages += "Invalid contact email" if(!length(CONFIG_GET(string/ipintel_base))) fail_messages += "Invalid query base" - + if (!CONFIG_GET(flag/sql_enabled)) fail_messages += "The database is not enabled" - + if(length(fail_messages)) message_admins("IPIntel: Initialization failed check logs!") logger.Log(LOG_CATEGORY_GAME_ACCESS, "IPIntel is not enabled because the configs are not valid.", list( @@ -104,6 +104,8 @@ SUBSYSTEM_DEF(ipintel) if(intel.query_status != "success") return intel intel.result = data["result"] + if(istext(intel.result)) + intel.result = text2num(intel.result) intel.date = SQLtime() intel.address = address cached_queries[address] = intel @@ -140,7 +142,7 @@ SUBSYSTEM_DEF(ipintel) date_restrictor = " AND date > DATE_SUB(NOW(), INTERVAL :ipintel_cache_length DAY)" sql_args["ipintel_cache_length"] = ipintel_cache_length var/datum/db_query/query = SSdbcore.NewQuery( - "SELECT * FROM [format_table_name("ipintel")] WHERE ip = INET_ATON(:address)[date_restrictor]", + "SELECT * FROM [format_table_name("ipintel")] WHERE ip = INET_ATON(:address)[date_restrictor]", sql_args ) query.warn_execute() @@ -158,6 +160,8 @@ SUBSYSTEM_DEF(ipintel) var/datum/ip_intel/intel = new intel.query_status = "cached" intel.result = data["intel"] + if(istext(intel.result)) + intel.result = text2num(intel.result) intel.date = data["date"] intel.address = address return TRUE @@ -189,7 +193,7 @@ SUBSYSTEM_DEF(ipintel) query.NextRow() . = !!query.item // if they have a row, they are whitelisted qdel(query) - + ADMIN_VERB(ipintel_allow, R_BAN, "Whitelist Player VPN", "Allow a player to connect even if they are using a VPN.", ADMIN_CATEGORY_IPINTEL, ckey as text) if (!SSipintel.is_enabled()) @@ -271,7 +275,7 @@ ADMIN_VERB(ipintel_revoke, R_BAN, "Revoke Player VPN Whitelist", "Revoke a playe if(!connection_rejected) return - + var/list/contact_where = list() var/forum_url = CONFIG_GET(string/forumurl) if(forum_url) From 7ca738d168eaf341d215994e77b2e9650d9cdfc3 Mon Sep 17 00:00:00 2001 From: AnturK Date: Fri, 3 May 2024 21:37:38 +0200 Subject: [PATCH 59/87] Fixes CI not checking warnings. (#83015) Yeah. --- .github/workflows/run_integration_tests.yml | 2 +- tools/build/build.js | 2 +- tools/build/lib/byond.js | 20 ++++++++++++++------ 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/.github/workflows/run_integration_tests.yml b/.github/workflows/run_integration_tests.yml index 42954b571b6cc..90233292ecb4c 100644 --- a/.github/workflows/run_integration_tests.yml +++ b/.github/workflows/run_integration_tests.yml @@ -57,7 +57,7 @@ jobs: run: | bash tools/ci/install_byond.sh source $HOME/BYOND/byond/bin/byondsetup - tools/build/build --ci dm -DCIBUILDING -DANSICOLORS -WError -NWTG0001 + tools/build/build --ci dm -DCIBUILDING -DANSICOLORS -Werror -ITG0001 -I"loop_checks" - name: Run Tests run: | source $HOME/BYOND/byond/bin/byondsetup diff --git a/tools/build/build.js b/tools/build/build.js index d8207e55310d3..8362e88e7abd9 100644 --- a/tools/build/build.js +++ b/tools/build/build.js @@ -86,7 +86,7 @@ export const WarningParameter = new Juke.Parameter({ export const NoWarningParameter = new Juke.Parameter({ type: 'string[]', - alias: 'NW', + alias: 'I', }); export const CutterTarget = new Juke.Target({ diff --git a/tools/build/lib/byond.js b/tools/build/lib/byond.js index e4809e3e9a7a7..8b6abcf8fe831 100644 --- a/tools/build/lib/byond.js +++ b/tools/build/lib/byond.js @@ -166,13 +166,21 @@ export const DreamMaker = async (dmeFile, options = {}) => { await testDmVersion(dmPath); testOutputFile(`${dmeBaseName}.dmb`); testOutputFile(`${dmeBaseName}.rsc`); + const runWithWarningChecks = async (dmPath, args) => { const execReturn = await Juke.exec(dmPath, args); - const ignoredWarningCodes = options.ignoreWarningCodes ?? []; - const reg = ignoredWarningCodes.length > 0 ? new RegExp(`\d+:warning: (?!(${ignoredWarningCodes.join('|')}))`) : /\d+:warning: /; - if (options.warningsAsErrors && execReturn.combined.match(reg)) { - Juke.logger.error(`Compile warnings treated as errors`); - throw new Juke.ExitCode(2); + if(options.warningsAsErrors){ + const ignoredWarningCodes = options.ignoreWarningCodes ?? []; + if(ignoredWarningCodes.length > 0 ){ + Juke.logger.info('Ignored warning codes:', ignoredWarningCodes.join(', ')); + } + const base_regex = '\\d+:warning( \\([a-z_]*\\))?:' + const with_ignores = `\\d+:warning( \\([a-z_]*\\))?:(?!(${ignoredWarningCodes.map(x => `.*${x}.*$`).join('|')}))` + const reg = ignoredWarningCodes.length > 0 ? new RegExp(with_ignores, "m") : new RegExp(base_regex,"m") + if (options.warningsAsErrors && execReturn.combined.match(reg)) { + Juke.logger.error(`Compile warnings treated as errors`); + throw new Juke.ExitCode(2); + } } return execReturn; } @@ -180,8 +188,8 @@ export const DreamMaker = async (dmeFile, options = {}) => { const { defines } = options; if (defines && defines.length > 0) { Juke.logger.info('Using defines:', defines.join(', ')); - } + await runWithWarningChecks(dmPath, [...defines.map(def => `-D${def}`), dmeFile]); }; From 28540f275beaece0e51c1772bad358639017e894 Mon Sep 17 00:00:00 2001 From: Profakos Date: Fri, 3 May 2024 22:10:10 +0200 Subject: [PATCH 60/87] Bepis techs can be found in bitrunning crates, removed from vendors (#82872) ## About The Pull Request This PR adds Bepis disks to the main rewards of the Bitrunning crate in addition to the minerals and domain specific rewards. It also removes it from the Bepis vending machine. Once per domain, if its difficult was Medium or higher, and the completion score was A or S, and if there are still any leftover locked Bepis tech nodes, a Bepis disk will spawn.
Original PR text If domain has a reward value greater than one, it has 10% chance per reward value to drop. No disk is gained if they ran out of techs to unlock. Most of the domains have a reward value of three, so by the law of great numbers they probably get one disk per every three domains run, which should be one disk every 20-30 minutes. At least, if I am correct that domain runs take about five to ten minutes, and the server cooldown is three minutes. If I am incorrect, I can edit the drop chance as needed, of course. Edit: As I have underestimated how fast Bitrunners can be, I have decreased the chance to be 5% per reward tier.
## Why It's Good For The Game - Bepis disks are expensive, and bitrunners need to spend almost all their NP on gear and abilities - Downloading secret research data, is flavourful, fitting for invading the forgotten nooks of cyberspace. - This will allow Bepis tech to be actually used in rounds ## Changelog :cl: balance: Bitrunners can now earn Bepis disks, once per medium domain or above, if they scored at least an A. del: Bitrunners can not buy Bepis disks from their vendors. /:cl: --- code/modules/bitrunning/orders/bepis.dm | 4 -- code/modules/bitrunning/server/loot.dm | 54 ++++++++++++++----- .../virtual_domain/virtual_domain.dm | 2 + 3 files changed, 44 insertions(+), 16 deletions(-) diff --git a/code/modules/bitrunning/orders/bepis.dm b/code/modules/bitrunning/orders/bepis.dm index 286e9817f3c52..4b7253bdaf24a 100644 --- a/code/modules/bitrunning/orders/bepis.dm +++ b/code/modules/bitrunning/orders/bepis.dm @@ -17,7 +17,3 @@ /datum/orderable_item/bepis/sprayoncan item_path = /obj/item/toy/sprayoncan cost_per_order = 750 - -/datum/orderable_item/bepis/pristine - item_path = /obj/item/disk/design_disk/bepis/remove_tech - cost_per_order = 1000 diff --git a/code/modules/bitrunning/server/loot.dm b/code/modules/bitrunning/server/loot.dm index cb4902abfe3ab..aa7b99d6e922a 100644 --- a/code/modules/bitrunning/server/loot.dm +++ b/code/modules/bitrunning/server/loot.dm @@ -1,3 +1,9 @@ +#define GRADE_D "D" +#define GRADE_C "C" +#define GRADE_B "B" +#define GRADE_A "A" +#define GRADE_S "S" + /// Handles calculating rewards based on number of players, parts, threats, etc /obj/machinery/quantum_server/proc/calculate_rewards() var/rewards_base = 0.8 @@ -28,8 +34,11 @@ var/bonus = calculate_rewards() + var/time_difference = world.time - generated_domain.start_time + var/grade = grade_completion(time_difference) + var/obj/item/paper/certificate = new() - certificate.add_raw_text(get_completion_certificate()) + certificate.add_raw_text(get_completion_certificate(time_difference, grade)) certificate.name = "certificate of domain completion" certificate.update_appearance() @@ -37,6 +46,10 @@ reward_cache.manifest = certificate reward_cache.update_appearance() + if(can_generate_tech_disk(grade)) + new /obj/item/disk/design_disk/bepis/remove_tech(reward_cache) + generated_domain.disk_reward_spawned = TRUE + chosen_forge.start_to_spawn(reward_cache) return TRUE @@ -50,7 +63,7 @@ return TRUE /// Returns the markdown text containing domain completion information -/obj/machinery/quantum_server/proc/get_completion_certificate() +/obj/machinery/quantum_server/proc/get_completion_certificate(time_difference, grade) var/base_points = generated_domain.reward_points if(domain_randomized) base_points -= 1 @@ -59,11 +72,9 @@ var/domain_threats = length(spawned_threat_refs) - var/time_difference = world.time - generated_domain.start_time - var/completion_time = "### Completion Time: [DisplayTimeText(time_difference)]\n" - var/grade = "\n---\n\n# Rating: [grade_completion(time_difference)]" + var/completion_grade = "\n---\n\n# Rating: [grade]" var/text = "# Certificate of Domain Completion\n\n---\n\n" @@ -75,7 +86,7 @@ if(bonuses <= 1) text += completion_time - text += grade + text += completion_grade return text text += "### Bonuses\n" @@ -94,10 +105,24 @@ text += "- **Components:** + [servo_rating]\n" text += completion_time - text += grade + text += completion_grade return text +/// Checks if the players should get a bepis reward +/obj/machinery/quantum_server/proc/can_generate_tech_disk(grade) + if(generated_domain.disk_reward_spawned) + return FALSE + + if(!LAZYLEN(SSresearch.techweb_nodes_experimental)) + return FALSE + + var/static/list/passing_grades = list() + if(!passing_grades.len) + passing_grades = list(GRADE_A,GRADE_S) + + return generated_domain.difficulty >= BITRUNNER_DIFFICULTY_MEDIUM && (grade in passing_grades) + /// Grades the player's run based on several factors /obj/machinery/quantum_server/proc/grade_completion(completion_time) var/score = length(spawned_threat_refs) * 5 @@ -124,13 +149,18 @@ switch(score) if(1 to 4) - return "D" + return GRADE_D if(5 to 7) - return "C" + return GRADE_C if(8 to 10) - return "B" + return GRADE_B if(11 to 13) - return "A" + return GRADE_A else - return "S" + return GRADE_S +#undef GRADE_D +#undef GRADE_C +#undef GRADE_B +#undef GRADE_A +#undef GRADE_S diff --git a/code/modules/bitrunning/virtual_domain/virtual_domain.dm b/code/modules/bitrunning/virtual_domain/virtual_domain.dm index b316bb97cae1e..21898daad72d7 100644 --- a/code/modules/bitrunning/virtual_domain/virtual_domain.dm +++ b/code/modules/bitrunning/virtual_domain/virtual_domain.dm @@ -44,6 +44,8 @@ var/start_time /// This map is specifically for unit tests. Shouldn't display in game var/test_only = FALSE + /// Has this domain been beaten with high enough score to spawn a tech disk? + var/disk_reward_spawned = FALSE /// Sends a point to any loot signals on the map /datum/lazy_template/virtual_domain/proc/add_points(points_to_add) From 0ca5dcb61b2ab41bb051b391adedfcf430923d70 Mon Sep 17 00:00:00 2001 From: orange man <61334995+comfyorange@users.noreply.github.com> Date: Sat, 4 May 2024 08:10:39 +1200 Subject: [PATCH 61/87] Automatic changelog for PR #82872 [ci skip] --- html/changelogs/AutoChangeLog-pr-82872.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-82872.yml diff --git a/html/changelogs/AutoChangeLog-pr-82872.yml b/html/changelogs/AutoChangeLog-pr-82872.yml new file mode 100644 index 0000000000000..8df2569edf5ba --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-82872.yml @@ -0,0 +1,5 @@ +author: "Profakos" +delete-after: True +changes: + - balance: "Bitrunners can now earn Bepis disks, once per medium domain or above, if they scored at least an A." + - rscdel: "Bitrunners can not buy Bepis disks from their vendors." \ No newline at end of file From cc1df2405ee0ffb14b9e877beaf0d5baf5f2888a Mon Sep 17 00:00:00 2001 From: Watermelon914 <37270891+Watermelon914@users.noreply.github.com> Date: Fri, 3 May 2024 20:15:35 +0000 Subject: [PATCH 62/87] Fixes lua breaking when registering signals on turfs (#83018) ## About The Pull Request Signals don't get removed on turfs when they're deleted. This fixes that so that it is reflected in lua as well. ## Why It's Good For The Game Lua bugfixes ## Changelog :cl: fix: Fixed lua scripts breaking when turfs with registered signals get deleted. /:cl: --------- Co-authored-by: Watermelon914 <3052169-Watermelon914@users.noreply.gitlab.com> --- lua/SS13_base.lua | 8 +++++++- lua/handler_group.lua | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lua/SS13_base.lua b/lua/SS13_base.lua index f49908f094261..ea04c8c6503dd 100644 --- a/lua/SS13_base.lua +++ b/lua/SS13_base.lua @@ -100,7 +100,8 @@ function SS13.register_signal(datum, signal, func) callback:call_proc("RegisterSignal", datum, signal, "Invoke") local path = { "__SS13_signal_handlers", datumWeakRef, signal, callbackWeakRef, "func" } callback.vars.arguments = { path } - if not __SS13_signal_handlers[datumWeakRef]._cleanup then + -- Turfs don't remove their signals on deletion. + if not __SS13_signal_handlers[datumWeakRef]._cleanup and not SS13.istype(datum, "/turf") then local cleanupCallback = SS13.new("/datum/callback", SS13.state, "call_function_return_first") local cleanupPath = { "__SS13_signal_handlers", datumWeakRef, "_cleanup"} cleanupCallback.vars.arguments = { cleanupPath } @@ -134,6 +135,11 @@ function SS13.unregister_signal(datum, signal, callback) local callbackWeakRef = dm.global_proc("WEAKREF", handler_callback) if not SS13.istype(datum, "/datum/weakref") then handler_callback:call_proc("UnregisterSignal", datum, signal) + else + local actualDatum = datum:call_proc("hard_resolve") + if SS13.is_valid(actualDatum) then + handler_callback:call_proc("UnregisterSignal", actualDatum, signal) + end end SS13.stop_tracking(handler_callback) end diff --git a/lua/handler_group.lua b/lua/handler_group.lua index fff63ad18e427..0246d33c74488 100644 --- a/lua/handler_group.lua +++ b/lua/handler_group.lua @@ -14,7 +14,7 @@ function HandlerGroup:register_signal(datum, signal, func) if not callback then return end - table.insert(self.registered, { datum = datum, signal = signal, callback = callback }) + table.insert(self.registered, { datum = dm.global_proc("WEAKREF", datum), signal = signal, callback = callback }) end -- Clears all the signals that have been registered on this HandlerGroup From fdbd2b8480f75945d718baf57dabac9622d8ed71 Mon Sep 17 00:00:00 2001 From: orange man <61334995+comfyorange@users.noreply.github.com> Date: Sat, 4 May 2024 08:15:54 +1200 Subject: [PATCH 63/87] Automatic changelog for PR #83018 [ci skip] --- html/changelogs/AutoChangeLog-pr-83018.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-83018.yml diff --git a/html/changelogs/AutoChangeLog-pr-83018.yml b/html/changelogs/AutoChangeLog-pr-83018.yml new file mode 100644 index 0000000000000..d8692ba435c6e --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-83018.yml @@ -0,0 +1,4 @@ +author: "Watermelon914" +delete-after: True +changes: + - bugfix: "Fixed lua scripts breaking when turfs with registered signals get deleted." \ No newline at end of file From 053ae55c3a07ff628223f6b5473f3f5ef787ae55 Mon Sep 17 00:00:00 2001 From: Jeremiah <42397676+jlsnow301@users.noreply.github.com> Date: Fri, 3 May 2024 17:12:26 -0700 Subject: [PATCH 64/87] Adds debug dna disk to runtimestation (#83013) ## About The Pull Request Title || Puts it next to the dna scanner ## Why It's Good For The Game Easier debugging genetics / dna ui --- _maps/map_files/debug/runtimestation.dmm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/_maps/map_files/debug/runtimestation.dmm b/_maps/map_files/debug/runtimestation.dmm index 1c5a88860bea5..584ba1eb7ee9d 100644 --- a/_maps/map_files/debug/runtimestation.dmm +++ b/_maps/map_files/debug/runtimestation.dmm @@ -683,6 +683,10 @@ /obj/effect/turf_decal/tile/blue{ dir = 8 }, +/obj/item/disk/data/debug{ + pixel_y = 9; + pixel_x = 7 + }, /turf/open/floor/iron/white/corner, /area/station/medical/medbay) "cL" = ( From 89c711a3ac9172dc84e6608491ad340bc96d5d5c Mon Sep 17 00:00:00 2001 From: Changelogs Date: Sat, 4 May 2024 00:20:12 +0000 Subject: [PATCH 65/87] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-82833.yml | 4 --- html/changelogs/AutoChangeLog-pr-82872.yml | 5 ---- html/changelogs/AutoChangeLog-pr-82945.yml | 4 --- html/changelogs/AutoChangeLog-pr-82992.yml | 4 --- html/changelogs/AutoChangeLog-pr-82996.yml | 4 --- html/changelogs/AutoChangeLog-pr-82998.yml | 6 ----- html/changelogs/AutoChangeLog-pr-83002.yml | 4 --- html/changelogs/AutoChangeLog-pr-83004.yml | 4 --- html/changelogs/AutoChangeLog-pr-83005.yml | 4 --- html/changelogs/AutoChangeLog-pr-83014.yml | 4 --- html/changelogs/AutoChangeLog-pr-83018.yml | 4 --- html/changelogs/archive/2024-05.yml | 30 ++++++++++++++++++++++ 12 files changed, 30 insertions(+), 47 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-82833.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-82872.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-82945.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-82992.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-82996.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-82998.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-83002.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-83004.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-83005.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-83014.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-83018.yml diff --git a/html/changelogs/AutoChangeLog-pr-82833.yml b/html/changelogs/AutoChangeLog-pr-82833.yml deleted file mode 100644 index b9e08edc7e89e..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-82833.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Xander3359" -delete-after: True -changes: - - bugfix: "You can no longer bypass construction restrictions via the crafting menu" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-82872.yml b/html/changelogs/AutoChangeLog-pr-82872.yml deleted file mode 100644 index 8df2569edf5ba..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-82872.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Profakos" -delete-after: True -changes: - - balance: "Bitrunners can now earn Bepis disks, once per medium domain or above, if they scored at least an A." - - rscdel: "Bitrunners can not buy Bepis disks from their vendors." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-82945.yml b/html/changelogs/AutoChangeLog-pr-82945.yml deleted file mode 100644 index a1485366ae2f4..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-82945.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Ghommie" -delete-after: True -changes: - - rscadd: "Adds an Icebox-specific station trait that brightens outdoors areas on the surface level." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-82992.yml b/html/changelogs/AutoChangeLog-pr-82992.yml deleted file mode 100644 index eb03652dc1470..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-82992.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Ryll/Shaps" -delete-after: True -changes: - - bugfix: "Pacifists can no longer endlessly spam the backblast functionality of loaded rocket launchers that they cannot actually fire" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-82996.yml b/html/changelogs/AutoChangeLog-pr-82996.yml deleted file mode 100644 index fada9ce1d19f1..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-82996.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "mogeoko" -delete-after: True -changes: - - bugfix: "Ventcrawling mobs can change Z-level using multiz-decks again." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-82998.yml b/html/changelogs/AutoChangeLog-pr-82998.yml deleted file mode 100644 index 696b730fca1ee..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-82998.yml +++ /dev/null @@ -1,6 +0,0 @@ -author: "Ben10Omintrix" -delete-after: True -changes: - - bugfix: "mobs in the same faction will no longer be at odds against one another" - - bugfix: "mobs can now perform behaviors alongside searching for targets" - - bugfix: "mobs will no longer be starting and stopping when chasing targets" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-83002.yml b/html/changelogs/AutoChangeLog-pr-83002.yml deleted file mode 100644 index 114603f123725..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-83002.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "cnleth" -delete-after: True -changes: - - bugfix: "Fire ant colonies created by burning regular ants will now contain fire ants as their reagent" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-83004.yml b/html/changelogs/AutoChangeLog-pr-83004.yml deleted file mode 100644 index c763ac3aa9594..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-83004.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "jlsnow301" -delete-after: True -changes: - - bugfix: "Candy corn is once again available to detective fedoras" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-83005.yml b/html/changelogs/AutoChangeLog-pr-83005.yml deleted file mode 100644 index be13b5786e78c..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-83005.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Ikalpo" -delete-after: True -changes: - - bugfix: "Stage 2 singularities should no longer escape containment" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-83014.yml b/html/changelogs/AutoChangeLog-pr-83014.yml deleted file mode 100644 index ebd44714a49a6..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-83014.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Melbert" -delete-after: True -changes: - - bugfix: "New machine god blessings now actually works probably" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-83018.yml b/html/changelogs/AutoChangeLog-pr-83018.yml deleted file mode 100644 index d8692ba435c6e..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-83018.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Watermelon914" -delete-after: True -changes: - - bugfix: "Fixed lua scripts breaking when turfs with registered signals get deleted." \ No newline at end of file diff --git a/html/changelogs/archive/2024-05.yml b/html/changelogs/archive/2024-05.yml index 8e615e3cb43ed..304b3ad1b73f3 100644 --- a/html/changelogs/archive/2024-05.yml +++ b/html/changelogs/archive/2024-05.yml @@ -90,3 +90,33 @@ - balance: weighted some of the implants that Machine Blessing can give mc-oofert: - image: added directional sprites for radiation shutters +2024-05-04: + Ben10Omintrix: + - bugfix: mobs in the same faction will no longer be at odds against one another + - bugfix: mobs can now perform behaviors alongside searching for targets + - bugfix: mobs will no longer be starting and stopping when chasing targets + Ghommie: + - rscadd: Adds an Icebox-specific station trait that brightens outdoors areas on + the surface level. + Ikalpo: + - bugfix: Stage 2 singularities should no longer escape containment + Melbert: + - bugfix: New machine god blessings now actually works probably + Profakos: + - balance: Bitrunners can now earn Bepis disks, once per medium domain or above, + if they scored at least an A. + - rscdel: Bitrunners can not buy Bepis disks from their vendors. + Ryll/Shaps: + - bugfix: Pacifists can no longer endlessly spam the backblast functionality of + loaded rocket launchers that they cannot actually fire + Watermelon914: + - bugfix: Fixed lua scripts breaking when turfs with registered signals get deleted. + Xander3359: + - bugfix: You can no longer bypass construction restrictions via the crafting menu + cnleth: + - bugfix: Fire ant colonies created by burning regular ants will now contain fire + ants as their reagent + jlsnow301: + - bugfix: Candy corn is once again available to detective fedoras + mogeoko: + - bugfix: Ventcrawling mobs can change Z-level using multiz-decks again. From b8af2429c816262f64189d7796110ff75b4620b3 Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Sat, 4 May 2024 13:16:04 -0500 Subject: [PATCH 66/87] Fix vote `can_be_initiated` mutating the active choices (#83029) ## About The Pull Request Closes #83020 This proc, called *every single ui_data tick*, was mutating the vote's list of choices. Grahhh impure procs grahhh https://github.com/tgstation/tgstation/blob/f112369547a7da6fdafd69c1d43baf0fc6f76f77/code/datums/votes/map_vote.dm#L57-L60 Weirdly, I have no idea how this *ever* worked, even prior to my PR, because I didn't touch this or any consuming code. It was called in the same place, same args, etc. prior to my PR. So I have no idea. ## Changelog :cl: Melbert fix: Map vote should work better /:cl: --- code/datums/votes/map_vote.dm | 45 +++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/code/datums/votes/map_vote.dm b/code/datums/votes/map_vote.dm index 9b149e812795d..abe452ce4fedf 100644 --- a/code/datums/votes/map_vote.dm +++ b/code/datums/votes/map_vote.dm @@ -21,15 +21,23 @@ /datum/vote/map_vote/create_vote() . = ..() - check_population(should_key_choices = FALSE) + if(!.) + return FALSE + + choices -= get_choices_invalid_for_population() if(length(choices) == 1) // Only one choice, no need to vote. Let's just auto-rotate it to the only remaining map because it would just happen anyways. - var/de_facto_winner = choices[1] - var/datum/map_config/change_me_out = global.config.maplist[de_facto_winner] - SSmapping.changemap(change_me_out) - to_chat(world, span_boldannounce("The map vote has been skipped because there is only one map left to vote for. The map has been changed to [change_me_out.map_name].")) - SSmapping.map_voted = TRUE // voted by not voting, very sad. + var/datum/map_config/change_me_out = global.config.maplist[choices[1]] + finalize_vote(choices[1])// voted by not voting, very sad. + to_chat(world, span_boldannounce("The map vote has been skipped because there is only one map left to vote for. \ + The map has been changed to [change_me_out.map_name].")) + return FALSE + if(length(choices) == 0) + to_chat(world, span_boldannounce("A map vote was called, but there are no maps to vote for! \ + Players, complain to the admins. Admins, complain to the coders.")) return FALSE + return TRUE + /datum/vote/map_vote/toggle_votable() CONFIG_SET(flag/allow_vote_map, !CONFIG_GET(flag/allow_vote_map)) @@ -42,38 +50,33 @@ return . if(forced) return VOTE_AVAILABLE - var/number_of_choices = length(check_population()) - if(number_of_choices < 2) - return "There [number_of_choices == 1 ? "is only one map" : "are no maps"] to choose from." + var/num_choices = length(default_choices - get_choices_invalid_for_population()) + if(num_choices <= 1) + return "There [num_choices == 1 ? "is only one map" : "are no maps"] to choose from." if(SSmapping.map_vote_rocked) return VOTE_AVAILABLE if(SSmapping.map_voted) return "The next map has already been selected." return VOTE_AVAILABLE -/// Before we create a vote, remove all maps from our choices that are outside of our population range. -/// Note that this can result in zero remaining choices for our vote, which is not ideal (but ultimately okay). -/// Argument should_key_choices is TRUE, pass as FALSE in a context where choices are already keyed in a list. -/datum/vote/map_vote/proc/check_population(should_key_choices = TRUE) - if(should_key_choices) - for(var/key in default_choices) - choices[key] = 0 - +/// Returns a list of all map options that are invalid for the current population. +/datum/vote/map_vote/proc/get_choices_invalid_for_population() var/filter_threshold = 0 if(SSticker.HasRoundStarted()) filter_threshold = get_active_player_count(alive_check = FALSE, afk_check = TRUE, human_check = FALSE) else filter_threshold = GLOB.clients.len - for(var/map in choices) + var/list/invalid_choices = list() + for(var/map in default_choices) var/datum/map_config/possible_config = config.maplist[map] if(possible_config.config_min_users > 0 && filter_threshold < possible_config.config_min_users) - choices -= map + invalid_choices += map else if(possible_config.config_max_users > 0 && filter_threshold > possible_config.config_max_users) - choices -= map + invalid_choices += map - return choices + return invalid_choices /datum/vote/map_vote/get_vote_result(list/non_voters) // Even if we have default no vote off, From dcc2718a122294a123f73bf6a267c1f73ab0a301 Mon Sep 17 00:00:00 2001 From: Isratosh Date: Sat, 4 May 2024 12:16:17 -0600 Subject: [PATCH 67/87] Cargo scanner no longer kidnaps people (#83026) ## About The Pull Request Fixes #83022 The universal cargo scanner calls `sell_object(dry_run = TRUE)` to determine the price without actually selling anything, but an early return for this case was not included when the proc was overridden for the `/datum/export/pirate` type, allowing anybody with a cargo scanner to participate in illegal human trafficking. The automatic return timer had incorrect args causing a runtime error which I've also fixed, humans are returned after 6 minutes. Kidnapping code desperately needs a unification because now there are 3 ways to end up in the holding facility (progression traitor kidnapping side objective, traitor contractor, pirates) and they all have copy pasted code but some is different for no reason. This pirate code does not handle your belongings and you can take anything out of the holding facility or get your stuff stolen while in there, unlike the other 2 methods which hold your belongings for safe return later. ## Why It's Good For The Game Fixes a very funny but very lame oversight and also allows for the safe return of our friends kidnapped by pirates. ## Changelog :cl: fix: If kidnapped and ransomed by pirates, you will now properly return to the station automatically after 6 minutes. fix: You can no longer be kidnapped and held for ransom by cargo technicians posing as pirates. /:cl: --- code/modules/antagonists/pirate/pirate_shuttle_equipment.dm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/code/modules/antagonists/pirate/pirate_shuttle_equipment.dm b/code/modules/antagonists/pirate/pirate_shuttle_equipment.dm index eaa1de48b5865..16cad321853d9 100644 --- a/code/modules/antagonists/pirate/pirate_shuttle_equipment.dm +++ b/code/modules/antagonists/pirate/pirate_shuttle_equipment.dm @@ -428,7 +428,7 @@ /datum/export/pirate/ransom/sell_object(mob/living/carbon/human/sold_item, datum/export_report/report, dry_run = TRUE, apply_elastic = TRUE) . = ..() - if(. == EXPORT_NOT_SOLD) + if(. == EXPORT_NOT_SOLD || dry_run) return var/turf/picked_turf = pick(GLOB.holdingfacility) sold_item.forceMove(picked_turf) @@ -439,7 +439,7 @@ sold_item.flash_act() sold_item.adjust_confusion(10 SECONDS) sold_item.adjust_dizzy(10 SECONDS) - addtimer(src, CALLBACK(src, PROC_REF(send_back_to_station), sold_item), COME_BACK_FROM_CAPTURE_TIME) + addtimer(CALLBACK(src, PROC_REF(send_back_to_station), sold_item), COME_BACK_FROM_CAPTURE_TIME) to_chat(sold_item, span_hypnophrase("A million voices echo in your head... \"Yaarrr, thanks for the booty, landlubber. \ You will be ransomed back to your station, so it's only a matter of time before we ship you back...")) From 02e09efb458631ebff88dc73fc618b8584e7b3b2 Mon Sep 17 00:00:00 2001 From: larentoun <31931237+larentoun@users.noreply.github.com> Date: Sat, 4 May 2024 21:16:30 +0300 Subject: [PATCH 68/87] Fix missing crafting flags (#83025) ## About The Pull Request fixes https://github.com/tgstation/tgstation/issues/83023 ## Changelog :cl: fix: Materials are now correctly applied to crafted items (chairs, toilets, etc) /:cl: --- code/game/objects/items/stacks/stack_recipe.dm | 1 + 1 file changed, 1 insertion(+) diff --git a/code/game/objects/items/stacks/stack_recipe.dm b/code/game/objects/items/stacks/stack_recipe.dm index 4a150f4f2abce..eabaa706d8879 100644 --- a/code/game/objects/items/stacks/stack_recipe.dm +++ b/code/game/objects/items/stacks/stack_recipe.dm @@ -47,6 +47,7 @@ src.res_amount = res_amount src.max_res_amount = max_res_amount src.time = time + src.crafting_flags = crafting_flags src.placement_checks = placement_checks src.trait_booster = trait_booster src.trait_modifier = trait_modifier From d258536686d115fd99d6d040506e4ab0faeca0ec Mon Sep 17 00:00:00 2001 From: orange man <61334995+comfyorange@users.noreply.github.com> Date: Sun, 5 May 2024 06:16:35 +1200 Subject: [PATCH 69/87] Automatic changelog for PR #83026 [ci skip] --- html/changelogs/AutoChangeLog-pr-83026.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-83026.yml diff --git a/html/changelogs/AutoChangeLog-pr-83026.yml b/html/changelogs/AutoChangeLog-pr-83026.yml new file mode 100644 index 0000000000000..97760a11c77da --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-83026.yml @@ -0,0 +1,5 @@ +author: "Isratosh" +delete-after: True +changes: + - bugfix: "If kidnapped and ransomed by pirates, you will now properly return to the station automatically after 6 minutes." + - bugfix: "You can no longer be kidnapped and held for ransom by cargo technicians posing as pirates." \ No newline at end of file From 6e57b94657575b151faae472ae7d80fc82bdb040 Mon Sep 17 00:00:00 2001 From: Jacquerel Date: Sat, 4 May 2024 19:18:33 +0100 Subject: [PATCH 70/87] Blood Brothers should start with objectives (#83030) ## About The Pull Request Fixes #82064 Fixes a couple of different bugs with Blood Brothers. - Delegates creating the objectives to the team rather than the ruleset, so ones created via the traitor panel will also have objectives. - Creates the objectives after the team has a member mind, so it doesn't runtime when trying to give that mob the equipment needed to steal a supermatter sliver. - Creates the objectives before the first Blood Brother is assigned the antag datum, so that they will correctly be given the starting objectives. - Check the maximum number of brothers that can be recruited when deciding how many objectives to generate rather than the current number of members (which would always be 1). ## Changelog :cl: fix: Blood Brothers should spawn knowing what their objectives are. fix: Teams of 3 Blood Brothers will once more have an additional objective. /:cl: --- .../subsystem/dynamic/dynamic_rulesets_roundstart.dm | 5 +---- code/modules/antagonists/brother/brother.dm | 6 ++++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/code/controllers/subsystem/dynamic/dynamic_rulesets_roundstart.dm b/code/controllers/subsystem/dynamic/dynamic_rulesets_roundstart.dm index 51ecd59925a4d..1f315391a8f2c 100644 --- a/code/controllers/subsystem/dynamic/dynamic_rulesets_roundstart.dm +++ b/code/controllers/subsystem/dynamic/dynamic_rulesets_roundstart.dm @@ -135,10 +135,7 @@ GLOBAL_VAR_INIT(revolutionary_win, FALSE) /datum/dynamic_ruleset/roundstart/traitorbro/execute() for (var/datum/mind/mind in assigned) - var/datum/team/brother_team/team = new - team.add_member(mind) - team.forge_brother_objectives() - mind.add_antag_datum(/datum/antagonist/brother, team) + new /datum/team/brother_team(mind) GLOB.pre_setup_antags -= mind return TRUE diff --git a/code/modules/antagonists/brother/brother.dm b/code/modules/antagonists/brother/brother.dm index 49d0ab4ad2300..ca36585a1a1c4 100644 --- a/code/modules/antagonists/brother/brother.dm +++ b/code/modules/antagonists/brother/brother.dm @@ -167,13 +167,15 @@ member_name = "blood brother" var/brothers_left = 2 -/datum/team/brother_team/New() +/datum/team/brother_team/New(starting_members) . = ..() if (prob(10)) brothers_left += 1 /datum/team/brother_team/add_member(datum/mind/new_member) . = ..() + if (!length(objectives)) + forge_brother_objectives() if (!new_member.has_antag_datum(/datum/antagonist/brother)) add_brother(new_member.current) @@ -217,7 +219,7 @@ add_objective(new /datum/objective/convert_brother) var/is_hijacker = prob(10) - for(var/i = 1 to max(1, CONFIG_GET(number/brother_objectives_amount) + (members.len > 2) - is_hijacker)) + for(var/i = 1 to max(1, CONFIG_GET(number/brother_objectives_amount) + (brothers_left > 2) - is_hijacker)) forge_single_objective() if(is_hijacker) if(!locate(/datum/objective/hijack) in objectives) From 0cc5cfb178eb63af939a5ceb31f18968a2baac7d Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Sat, 4 May 2024 13:21:26 -0500 Subject: [PATCH 71/87] Random Name Generation refactor, generate random names based on languages (for species without name lists, like Felinids and Podpeople) (#83021) ## About The Pull Request This PR moves random name generation for species onto their languages. What does this mean? - For species with a predefined name list, such as Lizards and Moths, nothing. - For species without predefined name lists, such as Felinids, their names will now be randomly generated from their language's syllables. ![image](https://github.com/tgstation/tgstation/assets/51863163/dddce7a6-5882-4f97-b817-c8922033c8d2) ![image](https://github.com/tgstation/tgstation/assets/51863163/e34e03e9-bcca-45ff-84e4-239e606cd24f) (In the prefs menu:) ![image](https://github.com/tgstation/tgstation/assets/51863163/eb6ccf9b-8b1c-4637-b46e-66cab9c8aac0) Why? - Well, we actually had some dead code that did this. All I did was fix it up and re-enable it. - Generates some pretty believable in-universe names for various languages that are lacking name lists. Obviously defined lists would be preferred, but until they are added, at least. - Moves some stuff off of species, which is always nice. - Also hopefully makes it a tad easier to work with name generation. There's now a standard framework for getting a random name for a mob, and for getting a random name based on a species. Misc: - Adds a generic `species_prototype` global, uses it in a lot of places in prefs code. - Makes `GLOB.species_list` init via the global defines - Deletes Language SS - Alphabetizes some instances of admin tooling using the list of all species IDs - Docs language stuff - Deletes random_skin_tone, it does pretty much nothin ## Changelog :cl: Melbert refactor: Random Name Generation has been refactored. Report any instances of people having weird (or "Unknown") names. qol: Felinids, Slimepeople, Podpeople, and some other species without defined namelists now automatically generate names based on their primary language(s). qol: More non-human names can be generated in codewords (and other misc. areas) than just lizard names. /:cl: --- code/__HELPERS/global_lists.dm | 8 - code/__HELPERS/mobs.dm | 44 ----- code/__HELPERS/names.dm | 94 +++++++--- code/_globalvars/lists/mobs.dm | 51 +++++- code/_globalvars/lists/names.dm | 4 +- code/controllers/subsystem/language.dm | 17 -- code/datums/brain_damage/imaginary_friend.dm | 4 +- .../diseases/advance/symptoms/voice_change.dm | 2 +- code/datums/dna.dm | 10 +- .../machinery/computer/records/security.dm | 6 +- code/game/objects/items/cardboard_cutouts.dm | 2 +- code/game/objects/items/debug_items.dm | 3 +- code/game/objects/structures/headpike.dm | 2 +- code/modules/admin/create_mob.dm | 4 +- code/modules/admin/verbs/anonymousnames.dm | 3 +- code/modules/admin/verbs/secrets.dm | 3 +- .../modules/client/preferences/_preference.dm | 2 +- code/modules/client/preferences/clothing.dm | 2 +- code/modules/client/preferences/names.dm | 15 +- code/modules/client/preferences/species.dm | 4 +- .../preferences/species_features/mutants.dm | 2 +- .../client/preferences/underwear_color.dm | 2 +- code/modules/jobs/job_types/_job.dm | 20 ++- code/modules/language/_language.dm | 164 ++++++++++++++++++ ...language_holder.dm => _language_holder.dm} | 0 ...nguage_manuals.dm => _language_manuals.dm} | 0 .../{language_menu.dm => _language_menu.dm} | 0 code/modules/language/aphasia.dm | 1 + code/modules/language/beachbum.dm | 2 +- code/modules/language/buzzwords.dm | 1 + code/modules/language/calcic.dm | 12 ++ code/modules/language/codespeak.dm | 1 + code/modules/language/common.dm | 95 +++++----- code/modules/language/draconic.dm | 20 +++ code/modules/language/drone.dm | 1 + code/modules/language/language.dm | 107 ------------ code/modules/language/machine.dm | 11 +- code/modules/language/moffic.dm | 16 ++ code/modules/language/monkey.dm | 9 + code/modules/language/mushroom.dm | 2 + code/modules/language/nekomimetic.dm | 13 ++ code/modules/language/piratespeak.dm | 1 + code/modules/language/shadowtongue.dm | 2 + code/modules/language/slime.dm | 3 +- code/modules/language/sylvan.dm | 2 + code/modules/language/terrum.dm | 19 +- code/modules/language/voltaic.dm | 18 ++ code/modules/language/xenocommon.dm | 1 + code/modules/mapping/mapping_helpers.dm | 3 +- code/modules/mob/dead/observer/observer.dm | 19 +- .../basic/space_fauna/revenant/_revenant.dm | 6 +- .../mob/living/carbon/human/_species.dm | 32 +--- code/modules/mob/living/carbon/human/human.dm | 4 +- .../mob/living/carbon/human/human_helpers.dm | 2 +- .../carbon/human/species_types/ethereal.dm | 8 - .../carbon/human/species_types/golems.dm | 9 - .../human/species_types/lizardpeople.dm | 12 -- .../carbon/human/species_types/monkeys.dm | 3 - .../carbon/human/species_types/mothmen.dm | 11 -- .../carbon/human/species_types/plasmamen.dm | 11 -- code/modules/mob/living/living_say.dm | 6 +- .../mob_spawn/ghost_roles/golem_roles.dm | 3 +- .../mob_spawn/ghost_roles/mining_roles.dm | 6 +- code/modules/mob_spawn/mob_spawn.dm | 2 +- code/modules/shuttle/emergency.dm | 3 +- code/modules/surgery/plastic_surgery.dm | 6 +- code/modules/unit_tests/preference_species.dm | 4 +- tgstation.dme | 9 +- 68 files changed, 532 insertions(+), 432 deletions(-) delete mode 100644 code/controllers/subsystem/language.dm create mode 100644 code/modules/language/_language.dm rename code/modules/language/{language_holder.dm => _language_holder.dm} (100%) rename code/modules/language/{language_manuals.dm => _language_manuals.dm} (100%) rename code/modules/language/{language_menu.dm => _language_menu.dm} (100%) delete mode 100644 code/modules/language/language.dm diff --git a/code/__HELPERS/global_lists.dm b/code/__HELPERS/global_lists.dm index 84299fc3021fb..bd991f29d014a 100644 --- a/code/__HELPERS/global_lists.dm +++ b/code/__HELPERS/global_lists.dm @@ -2,13 +2,6 @@ /////Initial Building///// ////////////////////////// -/// Inits GLOB.species_list. Not using GLOBAL_LIST_INIT b/c it depends on GLOB.string_lists -/proc/init_species_list() - for(var/species_path in subtypesof(/datum/species)) - var/datum/species/species = new species_path() - GLOB.species_list[species.id] = species_path - sort_list(GLOB.species_list, GLOBAL_PROC_REF(cmp_typepaths_asc)) - /// Inits GLOB.surgeries /proc/init_surgeries() var/surgeries = list() @@ -20,7 +13,6 @@ /// Legacy procs that really should be replaced with proper _INIT macros /proc/make_datum_reference_lists() // I tried to eliminate this proc but I couldn't untangle their init-order interdependencies -Dominion/Cyberboss - init_species_list() init_keybindings() GLOB.emote_list = init_emote_list() // WHY DOES THIS NEED TO GO HERE? IT JUST INITS DATUMS init_crafting_recipes() diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm index 9bc3beb3a1b39..084e51167eaa6 100644 --- a/code/__HELPERS/mobs.dm +++ b/code/__HELPERS/mobs.dm @@ -76,47 +76,6 @@ else return pick(SSaccessories.facial_hairstyles_list) -/proc/random_unique_name(gender, attempts_to_find_unique_name=10) - for(var/i in 1 to attempts_to_find_unique_name) - if(gender == FEMALE) - . = capitalize(pick(GLOB.first_names_female)) + " " + capitalize(pick(GLOB.last_names)) - else - . = capitalize(pick(GLOB.first_names_male)) + " " + capitalize(pick(GLOB.last_names)) - - if(!findname(.)) - break - -/proc/random_unique_lizard_name(gender, attempts_to_find_unique_name=10) - for(var/i in 1 to attempts_to_find_unique_name) - . = capitalize(lizard_name(gender)) - - if(!findname(.)) - break - -/proc/random_unique_plasmaman_name(attempts_to_find_unique_name=10) - for(var/i in 1 to attempts_to_find_unique_name) - . = capitalize(plasmaman_name()) - - if(!findname(.)) - break - -/proc/random_unique_ethereal_name(attempts_to_find_unique_name=10) - for(var/i in 1 to attempts_to_find_unique_name) - . = capitalize(ethereal_name()) - - if(!findname(.)) - break - -/proc/random_unique_moth_name(attempts_to_find_unique_name=10) - for(var/i in 1 to attempts_to_find_unique_name) - . = capitalize(pick(GLOB.moth_first)) + " " + capitalize(pick(GLOB.moth_last)) - - if(!findname(.)) - break - -/proc/random_skin_tone() - return pick(GLOB.skin_tones) - GLOBAL_LIST_INIT(skin_tones, sort_list(list( "albino", "caucasian1", @@ -155,9 +114,6 @@ GLOBAL_LIST_INIT(skin_tone_names, list( "mixed4" = "Macadamia", )) -/// An assoc list of species IDs to type paths -GLOBAL_LIST_EMPTY(species_list) - /proc/age2agedescription(age) switch(age) if(0 to 1) diff --git a/code/__HELPERS/names.dm b/code/__HELPERS/names.dm index 12c34d3a705ca..3a82c8dc1a66c 100644 --- a/code/__HELPERS/names.dm +++ b/code/__HELPERS/names.dm @@ -1,20 +1,75 @@ -/proc/lizard_name(gender) - if(gender == MALE) - return "[pick(GLOB.lizard_names_male)]-[pick(GLOB.lizard_names_male)]" - else - return "[pick(GLOB.lizard_names_female)]-[pick(GLOB.lizard_names_female)]" +/** + * Generate a random name based off of one of the roundstart languages + * + * * gender - What gender to pick from. Picks between male, female if not provided. + * * unique - If the name should be unique, IE, avoid picking names that mobs already have. + * * list/language_weights - A list of language weights to pick from. + * If not provided, it will default to a list of roundstart languages, with common being the most likely. + */ +/proc/generate_random_name(gender, unique, list/language_weights) + if(isnull(language_weights)) + language_weights = list() + for(var/lang_type in GLOB.uncommon_roundstart_languages) + language_weights[lang_type] = 1 + language_weights[/datum/language/common] = 20 + + var/datum/language/picked = GLOB.language_datum_instances[pick_weight(language_weights)] + if(unique) + return picked.get_random_unique_name(gender) + return picked.get_random_name(gender) + +/** + * Generate a random name based off of a species + * This will pick a name from the species language, and avoid picking common if there are alternatives + * + * * gender - What gender to pick from. Picks between male, female if not provided. + * * unique - If the name should be unique, IE, avoid picking names that mobs already have. + * * datum/species/species_type - The species to pick from + * * include_all - Makes the generated name a mix of all the languages the species can speak rather than just one of them + * Does this on a per-name basis, IE "Lizard first name, uncommon last name". + */ +/proc/generate_random_name_species_based(gender, unique, datum/species/species_type, include_all = FALSE) + ASSERT(ispath(species_type, /datum/species)) + var/datum/language_holder/holder = GLOB.prototype_language_holders[species_type::species_language_holder] -/proc/ethereal_name() - var/tempname = "[pick(GLOB.ethereal_names)] [random_capital_letter()]" - if(prob(65)) - tempname += random_capital_letter() - return tempname + var/list/languages_to_pick_from = list() + for(var/language in holder.spoken_languages) + languages_to_pick_from[language] = 1 -/proc/plasmaman_name() - return "[pick(GLOB.plasmaman_names)] \Roman[rand(1,99)]" + if(length(languages_to_pick_from) >= 2) + // Basically, if we have alternatives, don't pick common it's boring + languages_to_pick_from -= /datum/language/common -/proc/moth_name() - return "[pick(GLOB.moth_first)] [pick(GLOB.moth_last)]" + if(!include_all || length(languages_to_pick_from) <= 1) + return generate_random_name(gender, unique, languages_to_pick_from) + + var/list/name_parts = list() + for(var/lang_type in shuffle(languages_to_pick_from)) + name_parts += GLOB.language_datum_instances[lang_type].get_random_name(gender, name_count = 1, force_use_syllables = TRUE) + return jointext(name_parts, " ") + +/** + * Generates a random name for the mob based on their gender or species (for humans) + * + * * unique - If the name should be unique, IE, avoid picking names that mobs already have. + */ +/mob/proc/generate_random_mob_name(unique) + return generate_random_name_species_based(gender, unique, /datum/species/human) + +/mob/living/carbon/generate_random_mob_name(unique) + return generate_random_name_species_based(gender, unique, dna?.species?.type || /datum/species/human) + +/mob/living/silicon/generate_random_mob_name(unique) + return generate_random_name(gender, unique, list(/datum/language/machine = 1)) + +/mob/living/basic/drone/generate_random_mob_name(unique) + return generate_random_name(gender, unique, list(/datum/language/machine = 1)) + +/mob/living/basic/bot/generate_random_mob_name(unique) + return generate_random_name(gender, unique, list(/datum/language/machine = 1)) + +/mob/living/simple_animal/bot/generate_random_mob_name(unique) + return generate_random_name(gender, unique, list(/datum/language/machine = 1)) GLOBAL_VAR(command_name) /proc/command_name() @@ -194,16 +249,11 @@ GLOBAL_DATUM(syndicate_code_response_regex, /regex) if(1)//1 and 2 can only be selected once each to prevent more than two specific names/places/etc. switch(rand(1,2))//Mainly to add more options later. if(1) - if(names.len && prob(70)) + if(length(names) && prob(70)) . += pick(names) else - if(prob(10)) - . += pick(lizard_name(MALE),lizard_name(FEMALE)) - else - var/new_name = pick(pick(GLOB.first_names_male,GLOB.first_names_female)) - new_name += " " - new_name += pick(GLOB.last_names) - . += new_name + . += generate_random_name() + if(2) var/datum/job/job = pick(SSjob.joinable_occupations) if(job) diff --git a/code/_globalvars/lists/mobs.dm b/code/_globalvars/lists/mobs.dm index 4e33aa43708a2..692704be0fffd 100644 --- a/code/_globalvars/lists/mobs.dm +++ b/code/_globalvars/lists/mobs.dm @@ -85,11 +85,54 @@ GLOBAL_LIST_EMPTY(revenant_relay_mobs) ///underages who have been reported to security for trying to buy things they shouldn't, so they can't spam GLOBAL_LIST_EMPTY(narcd_underages) +/// List of language prototypes to reference, assoc [type] = prototype +GLOBAL_LIST_INIT_TYPED(language_datum_instances, /datum/language, init_language_prototypes()) +/// List if all language typepaths learnable, IE, those with keys +GLOBAL_LIST_INIT(all_languages, init_all_languages()) +// /List of language prototypes to reference, assoc "name" = typepath +GLOBAL_LIST_INIT(language_types_by_name, init_language_types_by_name()) + +/proc/init_language_prototypes() + var/list/lang_list = list() + for(var/datum/language/lang_type as anything in typesof(/datum/language)) + if(!initial(lang_type.key)) + continue + + lang_list[lang_type] = new lang_type() + return lang_list -GLOBAL_LIST_EMPTY(language_datum_instances) -GLOBAL_LIST_EMPTY(all_languages) -///List of all languages ("name" = type) -GLOBAL_LIST_EMPTY(language_types_by_name) +/proc/init_all_languages() + var/list/lang_list = list() + for(var/datum/language/lang_type as anything in typesof(/datum/language)) + if(!initial(lang_type.key)) + continue + lang_list += lang_type + return lang_list + +/proc/init_language_types_by_name() + var/list/lang_list = list() + for(var/datum/language/lang_type as anything in typesof(/datum/language)) + if(!initial(lang_type.key)) + continue + lang_list[initial(lang_type.name)] = lang_type + return lang_list + +/// An assoc list of species IDs to type paths +GLOBAL_LIST_INIT(species_list, init_species_list()) +/// List of all species prototypes to reference, assoc [type] = prototype +GLOBAL_LIST_INIT_TYPED(species_prototypes, /datum/species, init_species_prototypes()) + +/proc/init_species_list() + var/list/species_list = list() + for(var/datum/species/species_path as anything in subtypesof(/datum/species)) + species_list[initial(species_path.id)] = species_path + return species_list + +/proc/init_species_prototypes() + var/list/species_list = list() + for(var/species_type in subtypesof(/datum/species)) + species_list[species_type] = new species_type() + return species_list GLOBAL_LIST_EMPTY(sentient_disease_instances) diff --git a/code/_globalvars/lists/names.dm b/code/_globalvars/lists/names.dm index c51fbaa9eb7a0..81fe08373b31a 100644 --- a/code/_globalvars/lists/names.dm +++ b/code/_globalvars/lists/names.dm @@ -8,12 +8,12 @@ GLOBAL_LIST_INIT(first_names, world.file2list("strings/names/first.txt")) GLOBAL_LIST_INIT(first_names_male, world.file2list("strings/names/first_male.txt")) GLOBAL_LIST_INIT(first_names_female, world.file2list("strings/names/first_female.txt")) GLOBAL_LIST_INIT(last_names, world.file2list("strings/names/last.txt")) -GLOBAL_LIST_INIT(lizard_names_male, world.file2list("strings/names/lizard_male.txt")) -GLOBAL_LIST_INIT(lizard_names_female, world.file2list("strings/names/lizard_female.txt")) GLOBAL_LIST_INIT(clown_names, world.file2list("strings/names/clown.txt")) GLOBAL_LIST_INIT(mime_names, world.file2list("strings/names/mime.txt")) GLOBAL_LIST_INIT(religion_names, world.file2list("strings/names/religion.txt")) GLOBAL_LIST_INIT(carp_names, world.file2list("strings/names/carp.txt")) +GLOBAL_LIST_INIT(lizard_names_male, world.file2list("strings/names/lizard_male.txt")) +GLOBAL_LIST_INIT(lizard_names_female, world.file2list("strings/names/lizard_female.txt")) GLOBAL_LIST_INIT(golem_names, world.file2list("strings/names/golem.txt")) GLOBAL_LIST_INIT(moth_first, world.file2list("strings/names/moth_first.txt")) GLOBAL_LIST_INIT(moth_last, world.file2list("strings/names/moth_last.txt")) diff --git a/code/controllers/subsystem/language.dm b/code/controllers/subsystem/language.dm deleted file mode 100644 index 88e92e2f93c5f..0000000000000 --- a/code/controllers/subsystem/language.dm +++ /dev/null @@ -1,17 +0,0 @@ -SUBSYSTEM_DEF(language) - name = "Language" - init_order = INIT_ORDER_LANGUAGE - flags = SS_NO_FIRE - -/datum/controller/subsystem/language/Initialize() - for(var/datum/language/language as anything in subtypesof(/datum/language)) - if(!initial(language.key)) - continue - - GLOB.all_languages += language - GLOB.language_types_by_name[initial(language.name)] = language - - var/datum/language/instance = new language - GLOB.language_datum_instances[language] = instance - - return SS_INIT_SUCCESS diff --git a/code/datums/brain_damage/imaginary_friend.dm b/code/datums/brain_damage/imaginary_friend.dm index e3891392d1a76..251414241c92d 100644 --- a/code/datums/brain_damage/imaginary_friend.dm +++ b/code/datums/brain_damage/imaginary_friend.dm @@ -129,8 +129,8 @@ /// Randomise friend name and appearance /mob/camera/imaginary_friend/proc/setup_friend() - var/gender = pick(MALE, FEMALE) - real_name = random_unique_name(gender) + gender = pick(MALE, FEMALE) + real_name = generate_random_name_species_based(gender, FALSE, /datum/species/human) name = real_name human_image = get_flat_human_icon(null, pick(SSjob.joinable_occupations)) Show() diff --git a/code/datums/diseases/advance/symptoms/voice_change.dm b/code/datums/diseases/advance/symptoms/voice_change.dm index 255c2a3f3a7f5..9654365c49d34 100644 --- a/code/datums/diseases/advance/symptoms/voice_change.dm +++ b/code/datums/diseases/advance/symptoms/voice_change.dm @@ -54,7 +54,7 @@ else if(ishuman(M)) var/mob/living/carbon/human/H = M - H.SetSpecialVoice(H.dna.species.random_name(H.gender)) + H.SetSpecialVoice(H.generate_random_mob_name()) if(scramble_language && !current_language) // Last part prevents rerolling language with small amounts of cure. current_language = pick(subtypesof(/datum/language) - /datum/language/common) H.add_blocked_language(subtypesof(/datum/language) - current_language, LANGUAGE_VOICECHANGE) diff --git a/code/datums/dna.dm b/code/datums/dna.dm index ab5407bc78ccf..8f5844aa48093 100644 --- a/code/datums/dna.dm +++ b/code/datums/dna.dm @@ -451,14 +451,8 @@ GLOBAL_LIST_INIT(total_uf_len_by_block, populate_total_uf_len_by_block()) if(create_mutation_blocks) //I hate this generate_dna_blocks() if(randomize_features) - var/static/list/all_species_protoypes - if(isnull(all_species_protoypes)) - all_species_protoypes = list() - for(var/species_path in subtypesof(/datum/species)) - all_species_protoypes += new species_path() - - for(var/datum/species/random_species as anything in all_species_protoypes) - features |= random_species.randomize_features() + for(var/species_type in GLOB.species_prototypes) + features |= GLOB.species_prototypes[species_type].randomize_features() features["mcolor"] = "#[random_color()]" diff --git a/code/game/machinery/computer/records/security.dm b/code/game/machinery/computer/records/security.dm index 0797b4d1890a1..c41779e7384ec 100644 --- a/code/game/machinery/computer/records/security.dm +++ b/code/game/machinery/computer/records/security.dm @@ -49,10 +49,8 @@ if(prob(10/severity)) switch(rand(1,5)) if(1) - if(prob(10)) - target.name = "[pick(lizard_name(MALE),lizard_name(FEMALE))]" - else - target.name = "[pick(pick(GLOB.first_names_male), pick(GLOB.first_names_female))] [pick(GLOB.last_names)]" + target.name = generate_random_name() + if(2) target.gender = pick("Male", "Female", "Other") if(3) diff --git a/code/game/objects/items/cardboard_cutouts.dm b/code/game/objects/items/cardboard_cutouts.dm index 71f3a244a3081..1e6c2b35ede2c 100644 --- a/code/game/objects/items/cardboard_cutouts.dm +++ b/code/game/objects/items/cardboard_cutouts.dm @@ -317,7 +317,7 @@ outfit = /datum/outfit/ashwalker/spear /datum/cardboard_cutout/ash_walker/get_name() - return lizard_name(pick(MALE, FEMALE)) + return generate_random_name_species_based(species_type = /datum/species/lizard) /datum/cardboard_cutout/death_squad name = "Deathsquad Officer" diff --git a/code/game/objects/items/debug_items.dm b/code/game/objects/items/debug_items.dm index 4f6239acbe817..44f53df2c2b2d 100644 --- a/code/game/objects/items/debug_items.dm +++ b/code/game/objects/items/debug_items.dm @@ -21,7 +21,7 @@ /obj/item/debug/human_spawner/attack_self(mob/user) ..() - var/choice = input("Select a species", "Human Spawner", null) in GLOB.species_list + var/choice = input("Select a species", "Human Spawner", null) in sortTim(GLOB.species_list, GLOBAL_PROC_REF(cmp_text_asc)) selected_species = GLOB.species_list[choice] /obj/item/debug/omnitool @@ -168,4 +168,3 @@ var/turf/loc_turf = get_turf(src) for(var/spawn_atom in (choice == "No" ? typesof(path) : subtypesof(path))) new spawn_atom(loc_turf) - diff --git a/code/game/objects/structures/headpike.dm b/code/game/objects/structures/headpike.dm index b4cffdb654d23..fca325744554d 100644 --- a/code/game/objects/structures/headpike.dm +++ b/code/game/objects/structures/headpike.dm @@ -36,7 +36,7 @@ victim = locate() in parts_list if(!victim) //likely a mapspawned one victim = new(src) - victim.real_name = random_unique_name(prob(50)) + victim.real_name = generate_random_name() spear = locate(speartype) in parts_list if(!spear) spear = new speartype(src) diff --git a/code/modules/admin/create_mob.dm b/code/modules/admin/create_mob.dm index fcf9693731e05..509787ffd3a56 100644 --- a/code/modules/admin/create_mob.dm +++ b/code/modules/admin/create_mob.dm @@ -16,7 +16,7 @@ /proc/randomize_human(mob/living/carbon/human/human, randomize_mutations = FALSE) human.gender = human.dna.species.sexes ? pick(MALE, FEMALE, PLURAL, NEUTER) : PLURAL human.physique = human.gender - human.real_name = human.dna?.species.random_name(human.gender) || random_unique_name(human.gender) + human.real_name = human.generate_random_mob_name() human.name = human.get_visible_name() human.set_hairstyle(random_hairstyle(human.gender), update = FALSE) human.set_facial_hairstyle(random_facial_hairstyle(human.gender), update = FALSE) @@ -24,7 +24,7 @@ human.set_facial_haircolor(human.hair_color, update = FALSE) human.eye_color_left = random_eye_color() human.eye_color_right = human.eye_color_left - human.skin_tone = random_skin_tone() + human.skin_tone = pick(GLOB.skin_tones) human.dna.species.randomize_active_underwear_only(human) // Needs to be called towards the end to update all the UIs just set above human.dna.initialize_dna(newblood_type = random_blood_type(), create_mutation_blocks = randomize_mutations, randomize_features = TRUE) diff --git a/code/modules/admin/verbs/anonymousnames.dm b/code/modules/admin/verbs/anonymousnames.dm index 9a71d68637a88..10edb49d99336 100644 --- a/code/modules/admin/verbs/anonymousnames.dm +++ b/code/modules/admin/verbs/anonymousnames.dm @@ -131,8 +131,7 @@ GLOBAL_DATUM(current_anonymous_theme, /datum/anonymous_theme) /datum/anonymous_theme/proc/anonymous_name(mob/target) var/datum/client_interface/client = GET_CLIENT(target) var/species_type = client.prefs.read_preference(/datum/preference/choiced/species) - var/datum/species/species = new species_type - return species.random_name(target.gender,1) + return generate_random_name_species_based(target.gender, TRUE, species_type) /** * anonymous_ai_name: generates a random name, based off of whatever the round's anonymousnames is set to (but for sillycones). diff --git a/code/modules/admin/verbs/secrets.dm b/code/modules/admin/verbs/secrets.dm index b7aa16b6b5fd5..5f5b5f245bb5c 100644 --- a/code/modules/admin/verbs/secrets.dm +++ b/code/modules/admin/verbs/secrets.dm @@ -222,7 +222,7 @@ ADMIN_VERB(secrets, R_NONE, "Secrets", "Abuse harder than you ever have before w if("allspecies") if(!is_funmin) return - var/result = input(holder, "Please choose a new species","Species") as null|anything in GLOB.species_list + var/result = input(holder, "Please choose a new species","Species") as null|anything in sortTim(GLOB.species_list, GLOBAL_PROC_REF(cmp_text_asc)) if(result) SSblackbox.record_feedback("nested tally", "admin_secrets_fun_used", 1, list("Mass Species Change", "[result]")) log_admin("[key_name(holder)] turned all humans into [result]") @@ -724,4 +724,3 @@ ADMIN_VERB(secrets, R_NONE, "Secrets", "Abuse harder than you ever have before w var/datum/antagonist/malf_ai/antag_datum = new antag_datum.give_objectives = keep_generic_objecives assign_admin_objective_and_antag(player, antag_datum) - diff --git a/code/modules/client/preferences/_preference.dm b/code/modules/client/preferences/_preference.dm index 644d57b6d24d1..5fbf5c6953d6a 100644 --- a/code/modules/client/preferences/_preference.dm +++ b/code/modules/client/preferences/_preference.dm @@ -331,7 +331,7 @@ GLOBAL_LIST_INIT(preference_entries_by_key, init_preference_entries_by_key()) ) var/species_type = preferences.read_preference(/datum/preference/choiced/species) - var/datum/species/species = new species_type + var/datum/species/species = GLOB.species_prototypes[species_type] if (!(savefile_key in species.get_features())) return FALSE diff --git a/code/modules/client/preferences/clothing.dm b/code/modules/client/preferences/clothing.dm index 002a7f8c13a33..d0ec072ba472f 100644 --- a/code/modules/client/preferences/clothing.dm +++ b/code/modules/client/preferences/clothing.dm @@ -175,7 +175,7 @@ return FALSE var/species_type = preferences.read_preference(/datum/preference/choiced/species) - var/datum/species/species = new species_type + var/datum/species/species = GLOB.species_prototypes[species_type] return !(TRAIT_NO_UNDERWEAR in species.inherent_traits) /datum/preference/choiced/underwear/compile_constant_data() diff --git a/code/modules/client/preferences/names.dm b/code/modules/client/preferences/names.dm index 476fc7381a28f..9afc8da18c1aa 100644 --- a/code/modules/client/preferences/names.dm +++ b/code/modules/client/preferences/names.dm @@ -45,12 +45,11 @@ target.log_mob_tag("TAG: [target.tag] RENAMED: [key_name(target)]") /datum/preference/name/real_name/create_informed_default_value(datum/preferences/preferences) - var/species_type = preferences.read_preference(/datum/preference/choiced/species) - var/gender = preferences.read_preference(/datum/preference/choiced/gender) - - var/datum/species/species = new species_type - - return species.random_name(gender, unique = TRUE) + return generate_random_name_species_based( + preferences.read_preference(/datum/preference/choiced/gender), + TRUE, + preferences.read_preference(/datum/preference/choiced/species), + ) /datum/preference/name/real_name/deserialize(input, datum/preferences/preferences) input = ..(input) @@ -73,9 +72,7 @@ savefile_key = "human_name" /datum/preference/name/backup_human/create_informed_default_value(datum/preferences/preferences) - var/gender = preferences.read_preference(/datum/preference/choiced/gender) - - return random_unique_name(gender) + return generate_random_name(preferences.read_preference(/datum/preference/choiced/gender)) /datum/preference/name/clown savefile_key = "clown_name" diff --git a/code/modules/client/preferences/species.dm b/code/modules/client/preferences/species.dm index 9e4923d2b11da..1c74d7981b655 100644 --- a/code/modules/client/preferences/species.dm +++ b/code/modules/client/preferences/species.dm @@ -34,7 +34,7 @@ for (var/species_id in get_selectable_species()) var/species_type = GLOB.species_list[species_id] - var/datum/species/species = new species_type() + var/datum/species/species = GLOB.species_prototypes[species_type] data[species_id] = list() data[species_id]["name"] = species.name @@ -47,6 +47,4 @@ data[species_id]["perks"] = species.get_species_perks() data[species_id]["diet"] = species.get_species_diet() - qdel(species) - return data diff --git a/code/modules/client/preferences/species_features/mutants.dm b/code/modules/client/preferences/species_features/mutants.dm index 7ecf25d9abce5..1d18c78ee1ad1 100644 --- a/code/modules/client/preferences/species_features/mutants.dm +++ b/code/modules/client/preferences/species_features/mutants.dm @@ -9,7 +9,7 @@ return FALSE var/species_type = preferences.read_preference(/datum/preference/choiced/species) - var/datum/species/species = new species_type + var/datum/species/species = GLOB.species_prototypes[species_type] return !(TRAIT_FIXED_MUTANT_COLORS in species.inherent_traits) /datum/preference/color/mutant_color/create_default_value() diff --git a/code/modules/client/preferences/underwear_color.dm b/code/modules/client/preferences/underwear_color.dm index 6e64b4423e50a..1304bdaf2da8d 100644 --- a/code/modules/client/preferences/underwear_color.dm +++ b/code/modules/client/preferences/underwear_color.dm @@ -8,7 +8,7 @@ return FALSE var/species_type = preferences.read_preference(/datum/preference/choiced/species) - var/datum/species/species = new species_type + var/datum/species/species = GLOB.species_prototypes[species_type] return !(TRAIT_NO_UNDERWEAR in species.inherent_traits) /datum/preference/color/underwear_color/apply_to_human(mob/living/carbon/human/target, value) diff --git a/code/modules/jobs/job_types/_job.dm b/code/modules/jobs/job_types/_job.dm index 6c5c2f4f03967..9cbd711d98161 100644 --- a/code/modules/jobs/job_types/_job.dm +++ b/code/modules/jobs/job_types/_job.dm @@ -117,7 +117,7 @@ /// RPG job names, for the memes var/rpg_title - /// Alternate titles to register as pointing to this job. + /// Alternate titles to register as pointing to this job. var/list/alternate_titles /// Does this job ignore human authority? @@ -543,11 +543,11 @@ dna.species.roundstart_changed = TRUE apply_pref_name(/datum/preference/name/backup_human, player_client) if(CONFIG_GET(flag/force_random_names)) - var/species_type = player_client.prefs.read_preference(/datum/preference/choiced/species) - var/datum/species/species = new species_type - - var/gender = player_client.prefs.read_preference(/datum/preference/choiced/gender) - real_name = species.random_name(gender, TRUE) + real_name = generate_random_name_species_based( + player_client.prefs.read_preference(/datum/preference/choiced/gender), + TRUE, + player_client.prefs.read_preference(/datum/preference/choiced/species), + ) dna.update_dna_identity() @@ -569,9 +569,11 @@ if(!player_client) return // Disconnected while checking the appearance ban. - var/species_type = player_client.prefs.read_preference(/datum/preference/choiced/species) - var/datum/species/species = new species_type - organic_name = species.random_name(player_client.prefs.read_preference(/datum/preference/choiced/gender), TRUE) + organic_name = generate_random_name_species_based( + player_client.prefs.read_preference(/datum/preference/choiced/gender), + TRUE, + player_client.prefs.read_preference(/datum/preference/choiced/species), + ) else if(!player_client) return // Disconnected while checking the appearance ban. diff --git a/code/modules/language/_language.dm b/code/modules/language/_language.dm new file mode 100644 index 0000000000000..94ea4a6aa4027 --- /dev/null +++ b/code/modules/language/_language.dm @@ -0,0 +1,164 @@ +/// maximum of 50 specific scrambled lines per language +#define SCRAMBLE_CACHE_LEN 50 + +/// Datum based languages. Easily editable and modular. +/datum/language + /// Fluff name of language if any. + var/name = "an unknown language" + /// Short description for 'Check Languages'. + var/desc = "A language." + /// Character used to speak in language + /// If key is null, then the language isn't real or learnable. + var/key + /// Various language flags. + var/flags = NONE + /// Used when scrambling text for a non-speaker. + var/list/syllables + /// List of characters that will randomly be inserted between syllables. + var/list/special_characters + /// Likelihood of making a new sentence after each syllable. + var/sentence_chance = 5 + /// Likelihood of getting a space in the random scramble string + var/space_chance = 55 + /// Spans to apply from this language + var/list/spans + /// Cache of recently scrambled text + /// This allows commonly reused words to not require a full re-scramble every time. + var/list/scramble_cache = list() + /// The language that an atom knows with the highest "default_priority" is selected by default. + var/default_priority = 0 + /// If TRUE, when generating names, we will always use the default human namelist, even if we have syllables set. + /// This is to be used for languages with very outlandish syllable lists (like pirates). + var/always_use_default_namelist = FALSE + /// Icon displayed in the chat window when speaking this language. + /// if you are seeing someone speak popcorn language, then something is wrong. + var/icon = 'icons/misc/language.dmi' + /// Icon state displayed in the chat window when speaking this language. + var/icon_state = "popcorn" + + /// By default, random names picks this many names + var/default_name_count = 2 + /// By default, random names picks this many syllables (min) + var/default_name_syllable_min = 2 + /// By default, random names picks this many syllables (max) + var/default_name_syllable_max = 4 + /// What char to place in between randomly generated names + var/random_name_spacer = " " + +/// Checks whether we should display the language icon to the passed hearer. +/datum/language/proc/display_icon(atom/movable/hearer) + var/understands = hearer.has_language(src.type) + if((flags & LANGUAGE_HIDE_ICON_IF_UNDERSTOOD) && understands) + return FALSE + if((flags & LANGUAGE_HIDE_ICON_IF_NOT_UNDERSTOOD) && !understands) + return FALSE + return TRUE + +/// Returns the icon to display in the chat window when speaking this language. +/datum/language/proc/get_icon() + var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/chat) + return sheet.icon_tag("language-[icon_state]") + +/// Simple helper for getting a default firstname lastname +/datum/language/proc/default_name(gender = NEUTER) + if(gender != MALE) + gender = pick(MALE, FEMALE) + if(gender == FEMALE) + return capitalize(pick(GLOB.first_names_female)) + " " + capitalize(pick(GLOB.last_names)) + return capitalize(pick(GLOB.first_names_male)) + " " + capitalize(pick(GLOB.last_names)) + + +/** + * Generates a random name this language would use. + * + * * gender: What gender to generate from, if neuter / plural coin flips between male and female + * * name_count: How many names to generate in, by default 2, for firstname lastname + * * syllable_count: How many syllables to generate in each name, min + * * syllable_max: How many syllables to generate in each name, max + * * force_use_syllables: If the name should be generated from the syllables list. + * Only used for subtypes which implement custom name lists. Also requires the language has syllables set. + */ +/datum/language/proc/get_random_name( + gender = NEUTER, + name_count = default_name_count, + syllable_min = default_name_syllable_min, + syllable_max = default_name_syllable_max, + force_use_syllables = FALSE, +) + if(gender != MALE) + gender = pick(MALE, FEMALE) + if(!length(syllables) || always_use_default_namelist) + return default_name(gender) + + var/list/full_name = list() + for(var/i in 1 to name_count) + var/new_name = "" + for(var/j in 1 to rand(default_name_syllable_min, default_name_syllable_max)) + new_name += pick_weight_recursive(syllables) + full_name += capitalize(LOWER_TEXT(new_name)) + + return jointext(full_name, random_name_spacer) + +/// Generates a random name, and attempts to ensure it is unique (IE, no other mob in the world has it) +/datum/language/proc/get_random_unique_name(...) + var/result = get_random_name(arglist(args)) + for(var/i in 1 to 10) + if(!findname(result)) + break + result = get_random_name(arglist(args)) + + return result + +/datum/language/proc/check_cache(input) + var/lookup = scramble_cache[input] + if(lookup) + scramble_cache -= input + scramble_cache[input] = lookup + . = lookup + +/datum/language/proc/add_to_cache(input, scrambled_text) + // Add it to cache, cutting old entries if the list is too long + scramble_cache[input] = scrambled_text + if(scramble_cache.len > SCRAMBLE_CACHE_LEN) + scramble_cache.Cut(1, scramble_cache.len-SCRAMBLE_CACHE_LEN-1) + +/datum/language/proc/scramble(input) + + if(!length(syllables)) + return stars(input) + + // If the input is cached already, move it to the end of the cache and return it + var/lookup = check_cache(input) + if(lookup) + return lookup + + var/input_size = length_char(input) + var/scrambled_text = "" + var/capitalize = TRUE + + while(length_char(scrambled_text) < input_size) + var/next = (length(scrambled_text) && length(special_characters) && prob(1)) ? pick(special_characters) : pick_weight_recursive(syllables) + if(capitalize) + next = capitalize(next) + capitalize = FALSE + scrambled_text += next + var/chance = rand(100) + if(chance <= sentence_chance) + scrambled_text += ". " + capitalize = TRUE + else if(chance > sentence_chance && chance <= space_chance) + scrambled_text += " " + + scrambled_text = trim(scrambled_text) + var/ending = copytext_char(scrambled_text, -1) + if(ending == ".") + scrambled_text = copytext_char(scrambled_text, 1, -2) + var/input_ending = copytext_char(input, -1) + if(input_ending in list("!","?",".")) + scrambled_text += input_ending + + add_to_cache(input, scrambled_text) + + return scrambled_text + +#undef SCRAMBLE_CACHE_LEN diff --git a/code/modules/language/language_holder.dm b/code/modules/language/_language_holder.dm similarity index 100% rename from code/modules/language/language_holder.dm rename to code/modules/language/_language_holder.dm diff --git a/code/modules/language/language_manuals.dm b/code/modules/language/_language_manuals.dm similarity index 100% rename from code/modules/language/language_manuals.dm rename to code/modules/language/_language_manuals.dm diff --git a/code/modules/language/language_menu.dm b/code/modules/language/_language_menu.dm similarity index 100% rename from code/modules/language/language_menu.dm rename to code/modules/language/_language_menu.dm diff --git a/code/modules/language/aphasia.dm b/code/modules/language/aphasia.dm index 9d4e317c4d881..2d82b79892ee7 100644 --- a/code/modules/language/aphasia.dm +++ b/code/modules/language/aphasia.dm @@ -7,3 +7,4 @@ space_chance = 20 default_priority = 10 icon_state = "aphasia" + always_use_default_namelist = TRUE // Shouldn't generate names for this anyways diff --git a/code/modules/language/beachbum.dm b/code/modules/language/beachbum.dm index d78be9788f35b..bd319e717ffd0 100644 --- a/code/modules/language/beachbum.dm +++ b/code/modules/language/beachbum.dm @@ -17,5 +17,5 @@ "heavy", "stellar", "excellent", "triumphant", "babe", "four", "tail", "trim", "tube", "wobble", "roll", "gnarly", "epic", ) - icon_state = "beach" + always_use_default_namelist = TRUE diff --git a/code/modules/language/buzzwords.dm b/code/modules/language/buzzwords.dm index c46088c0ad5b9..2ed033bca345b 100644 --- a/code/modules/language/buzzwords.dm +++ b/code/modules/language/buzzwords.dm @@ -8,3 +8,4 @@ ) icon_state = "buzz" default_priority = 90 + always_use_default_namelist = TRUE // Otherwise we get Bzzbzbz Zzzbzbz. diff --git a/code/modules/language/calcic.dm b/code/modules/language/calcic.dm index f4882e1105b95..477e442203bc1 100644 --- a/code/modules/language/calcic.dm +++ b/code/modules/language/calcic.dm @@ -13,4 +13,16 @@ icon_state = "calcic" default_priority = 90 +/datum/language/calcic/get_random_name( + gender = NEUTER, + name_count = default_name_count, + syllable_min = default_name_syllable_min, + syllable_max = default_name_syllable_max, + force_use_syllables = FALSE, +) + if(force_use_syllables) + return ..() + + return "[pick(GLOB.plasmaman_names)] \Roman[rand(1, 99)]" + // Yeah, this goes to skeletons too, since it's basically just skeleton clacking. diff --git a/code/modules/language/codespeak.dm b/code/modules/language/codespeak.dm index 09db7ef511b4b..242095b3bb7fa 100644 --- a/code/modules/language/codespeak.dm +++ b/code/modules/language/codespeak.dm @@ -5,6 +5,7 @@ default_priority = 0 flags = TONGUELESS_SPEECH | LANGUAGE_HIDE_ICON_IF_NOT_UNDERSTOOD icon_state = "codespeak" + always_use_default_namelist = TRUE // No syllables anyways /datum/language/codespeak/scramble(input) var/lookup = check_cache(input) diff --git a/code/modules/language/common.dm b/code/modules/language/common.dm index 2dc7294983c0a..6bad808fef262 100644 --- a/code/modules/language/common.dm +++ b/code/modules/language/common.dm @@ -7,50 +7,51 @@ default_priority = 100 icon_state = "galcom" - -//Syllable Lists -/* - This list really long, mainly because I can't make up my mind about which mandarin syllables should be removed, - and the english syllables had to be duplicated so that there is roughly a 50-50 weighting. - - Sources: - http://www.sttmedia.com/syllablefrequency-english - http://www.chinahighlights.com/travelguide/learning-chinese/pinyin-syllables.htm -*/ -/datum/language/common/syllables = list( - // each sublist has an equal chance of being picked, so each syllable has an equal chance of being english or chinese - list( - "a", "ai", "an", "ang", "ao", "ba", "bai", "ban", "bang", "bao", "bei", "ben", "beng", "bi", "bian", "biao", - "bie", "bin", "bing", "bo", "bu", "ca", "cai", "can", "cang", "cao", "ce", "cei", "cen", "ceng", "cha", "chai", - "chan", "chang", "chao", "che", "chen", "cheng", "chi", "chong", "chou", "chu", "chua", "chuai", "chuan", "chuang", "chui", "chun", - "chuo", "ci", "cong", "cou", "cu", "cuan", "cui", "cun", "cuo", "da", "dai", "dan", "dang", "dao", "de", "dei", - "den", "deng", "di", "dian", "diao", "die", "ding", "diu", "dong", "dou", "du", "duan", "dui", "dun", "duo", "e", - "ei", "en", "er", "fa", "fan", "fang", "fei", "fen", "feng", "fo", "fou", "fu", "ga", "gai", "gan", "gang", - "gao", "ge", "gei", "gen", "geng", "gong", "gou", "gu", "gua", "guai", "guan", "guang", "gui", "gun", "guo", "ha", - "hai", "han", "hang", "hao", "he", "hei", "hen", "heng", "hm", "hng", "hong", "hou", "hu", "hua", "huai", "huan", - "huang", "hui", "hun", "huo", "ji", "jia", "jian", "jiang", "jiao", "jie", "jin", "jing", "jiong", "jiu", "ju", "juan", - "jue", "jun", "ka", "kai", "kan", "kang", "kao", "ke", "kei", "ken", "keng", "kong", "kou", "ku", "kua", "kuai", - "kuan", "kuang", "kui", "kun", "kuo", "la", "lai", "lan", "lang", "lao", "le", "lei", "leng", "li", "lia", "lian", - "liang", "liao", "lie", "lin", "ling", "liu", "long", "lou", "lu", "luan", "lun", "luo", "ma", "mai", "man", "mang", - "mao", "me", "mei", "men", "meng", "mi", "mian", "miao", "mie", "min", "ming", "miu", "mo", "mou", "mu", "na", - "nai", "nan", "nang", "nao", "ne", "nei", "nen", "neng", "ng", "ni", "nian", "niang", "niao", "nie", "nin", "ning", - "niu", "nong", "nou", "nu", "nuan", "nuo", "o", "ou", "pa", "pai", "pan", "pang", "pao", "pei", "pen", "peng", - "pi", "pian", "piao", "pie", "pin", "ping", "po", "pou", "pu", "qi", "qia", "qian", "qiang", "qiao", "qie", "qin", - "qing", "qiong", "qiu", "qu", "quan", "que", "qun", "ran", "rang", "rao", "re", "ren", "reng", "ri", "rong", "rou", - "ru", "rua", "ruan", "rui", "run", "ruo", "sa", "sai", "san", "sang", "sao", "se", "sei", "sen", "seng", "sha", - "shai", "shan", "shang", "shao", "she", "shei", "shen", "sheng", "shi", "shou", "shu", "shua", "shuai", "shuan", "shuang", "shui", - "shun", "shuo", "si", "song", "sou", "su", "suan", "sui", "sun", "suo", "ta", "tai", "tan", "tang", "tao", "te", - "teng", "ti", "tian", "tiao", "tie", "ting", "tong", "tou", "tu", "tuan", "tui", "tun", "tuo", "wa", "wai", "wan", - "wang", "wei", "wen", "weng", "wo", "wu", "xi", "xia", "xian", "xiang", "xiao", "xie", "xin", "xing", "xiong", "xiu", - "xu", "xuan", "xue", "xun", "ya", "yan", "yang", "yao", "ye", "yi", "yin", "ying", "yong", "you", "yu", "yuan", - "yue", "yun", "za", "zai", "zan", "zang", "zao", "ze", "zei", "zen", "zeng", "zha", "zhai", "zhan", "zhang", "zhao", - "zhe", "zhei", "zhen", "zheng", "zhi", "zhong", "zhou", "zhu", "zhua", "zhuai", "zhuan", "zhuang", "zhui", "zhun", "zhuo", "zi", - "zong", "zou", "zuan", "zui", "zun", "zuo", "zu", - ), - list( - "al", "an", "ar", "as", "at", "ea", "ed", "en", "er", "es", "ha", "he", "hi", "in", "is", "it", - "le", "me", "nd", "ne", "ng", "nt", "on", "or", "ou", "re", "se", "st", "te", "th", "ti", "to", - "ve", "wa", "all", "and", "are", "but", "ent", "era", "ere", "eve", "for", "had", "hat", "hen", "her", "hin", - "his", "ing", "ion", "ith", "not", "ome", "oul", "our", "sho", "ted", "ter", "tha", "the", "thi", - ), -) + // Default namelist is the human namelist, and common is the human language, so might as well. + // Feel free to remove this at some point because common can generate some pretty cool names. + always_use_default_namelist = TRUE + /** + * This list really long, mainly because I can't make up my mind about which mandarin syllables should be removed, + * and the english syllables had to be duplicated so that there is roughly a 50-50 weighting. + * + * Sources: + * http://www.sttmedia.com/syllablefrequency-english + * http://www.chinahighlights.com/travelguide/learning-chinese/pinyin-syllables.htm + */ + syllables = list( + // each sublist has an equal chance of being picked, so each syllable has an equal chance of being english or chinese + list( + "a", "ai", "an", "ang", "ao", "ba", "bai", "ban", "bang", "bao", "bei", "ben", "beng", "bi", "bian", "biao", + "bie", "bin", "bing", "bo", "bu", "ca", "cai", "can", "cang", "cao", "ce", "cei", "cen", "ceng", "cha", "chai", + "chan", "chang", "chao", "che", "chen", "cheng", "chi", "chong", "chou", "chu", "chua", "chuai", "chuan", "chuang", "chui", "chun", + "chuo", "ci", "cong", "cou", "cu", "cuan", "cui", "cun", "cuo", "da", "dai", "dan", "dang", "dao", "de", "dei", + "den", "deng", "di", "dian", "diao", "die", "ding", "diu", "dong", "dou", "du", "duan", "dui", "dun", "duo", "e", + "ei", "en", "er", "fa", "fan", "fang", "fei", "fen", "feng", "fo", "fou", "fu", "ga", "gai", "gan", "gang", + "gao", "ge", "gei", "gen", "geng", "gong", "gou", "gu", "gua", "guai", "guan", "guang", "gui", "gun", "guo", "ha", + "hai", "han", "hang", "hao", "he", "hei", "hen", "heng", "hm", "hng", "hong", "hou", "hu", "hua", "huai", "huan", + "huang", "hui", "hun", "huo", "ji", "jia", "jian", "jiang", "jiao", "jie", "jin", "jing", "jiong", "jiu", "ju", "juan", + "jue", "jun", "ka", "kai", "kan", "kang", "kao", "ke", "kei", "ken", "keng", "kong", "kou", "ku", "kua", "kuai", + "kuan", "kuang", "kui", "kun", "kuo", "la", "lai", "lan", "lang", "lao", "le", "lei", "leng", "li", "lia", "lian", + "liang", "liao", "lie", "lin", "ling", "liu", "long", "lou", "lu", "luan", "lun", "luo", "ma", "mai", "man", "mang", + "mao", "me", "mei", "men", "meng", "mi", "mian", "miao", "mie", "min", "ming", "miu", "mo", "mou", "mu", "na", + "nai", "nan", "nang", "nao", "ne", "nei", "nen", "neng", "ng", "ni", "nian", "niang", "niao", "nie", "nin", "ning", + "niu", "nong", "nou", "nu", "nuan", "nuo", "o", "ou", "pa", "pai", "pan", "pang", "pao", "pei", "pen", "peng", + "pi", "pian", "piao", "pie", "pin", "ping", "po", "pou", "pu", "qi", "qia", "qian", "qiang", "qiao", "qie", "qin", + "qing", "qiong", "qiu", "qu", "quan", "que", "qun", "ran", "rang", "rao", "re", "ren", "reng", "ri", "rong", "rou", + "ru", "rua", "ruan", "rui", "run", "ruo", "sa", "sai", "san", "sang", "sao", "se", "sei", "sen", "seng", "sha", + "shai", "shan", "shang", "shao", "she", "shei", "shen", "sheng", "shi", "shou", "shu", "shua", "shuai", "shuan", "shuang", "shui", + "shun", "shuo", "si", "song", "sou", "su", "suan", "sui", "sun", "suo", "ta", "tai", "tan", "tang", "tao", "te", + "teng", "ti", "tian", "tiao", "tie", "ting", "tong", "tou", "tu", "tuan", "tui", "tun", "tuo", "wa", "wai", "wan", + "wang", "wei", "wen", "weng", "wo", "wu", "xi", "xia", "xian", "xiang", "xiao", "xie", "xin", "xing", "xiong", "xiu", + "xu", "xuan", "xue", "xun", "ya", "yan", "yang", "yao", "ye", "yi", "yin", "ying", "yong", "you", "yu", "yuan", + "yue", "yun", "za", "zai", "zan", "zang", "zao", "ze", "zei", "zen", "zeng", "zha", "zhai", "zhan", "zhang", "zhao", + "zhe", "zhei", "zhen", "zheng", "zhi", "zhong", "zhou", "zhu", "zhua", "zhuai", "zhuan", "zhuang", "zhui", "zhun", "zhuo", "zi", + "zong", "zou", "zuan", "zui", "zun", "zuo", "zu", + ), + list( + "al", "an", "ar", "as", "at", "ea", "ed", "en", "er", "es", "ha", "he", "hi", "in", "is", "it", + "le", "me", "nd", "ne", "ng", "nt", "on", "or", "ou", "re", "se", "st", "te", "th", "ti", "to", + "ve", "wa", "all", "and", "are", "but", "ent", "era", "ere", "eve", "for", "had", "hat", "hen", "her", "hin", + "his", "ing", "ion", "ith", "not", "ome", "oul", "our", "sho", "ted", "ter", "tha", "the", "thi", + ), + ) diff --git a/code/modules/language/draconic.dm b/code/modules/language/draconic.dm index f812c8dc1311a..55ebd1ec20267 100644 --- a/code/modules/language/draconic.dm +++ b/code/modules/language/draconic.dm @@ -13,5 +13,25 @@ "ra", "ar", "re", "er", "ri", "ir", "ro", "or", "ru", "ur", "rs", "sr", "a", "a", "e", "e", "i", "i", "o", "o", "u", "u", "s", "s" ) + special_characters = list("-") icon_state = "lizard" default_priority = 90 + default_name_syllable_min = 3 + default_name_syllable_max = 5 + random_name_spacer = "-" + +/datum/language/draconic/get_random_name( + gender = NEUTER, + name_count = default_name_count, + syllable_min = default_name_syllable_min, + syllable_max = default_name_syllable_max, + force_use_syllables = FALSE, +) + if(force_use_syllables) + return ..() + if(gender != MALE) + gender = pick(MALE, FEMALE) + + if(gender == MALE) + return "[pick(GLOB.lizard_names_male)][random_name_spacer][pick(GLOB.lizard_names_male)]" + return "[pick(GLOB.lizard_names_female)][random_name_spacer][pick(GLOB.lizard_names_female)]" diff --git a/code/modules/language/drone.dm b/code/modules/language/drone.dm index 5b47533d45e3a..09fb6546e4a16 100644 --- a/code/modules/language/drone.dm +++ b/code/modules/language/drone.dm @@ -11,3 +11,4 @@ default_priority = 20 icon_state = "drone" + always_use_default_namelist = TRUE // Nonsense language diff --git a/code/modules/language/language.dm b/code/modules/language/language.dm deleted file mode 100644 index 9fc2abf0f5a9f..0000000000000 --- a/code/modules/language/language.dm +++ /dev/null @@ -1,107 +0,0 @@ -#define SCRAMBLE_CACHE_LEN 50 //maximum of 50 specific scrambled lines per language - -/* - Datum based languages. Easily editable and modular. -*/ - -/datum/language - var/name = "an unknown language" // Fluff name of language if any. - var/desc = "A language." // Short description for 'Check Languages'. - var/key // Character used to speak in language - // If key is null, then the language isn't real or learnable. - var/flags // Various language flags. - var/list/syllables // Used when scrambling text for a non-speaker. - var/sentence_chance = 5 // Likelihood of making a new sentence after each syllable. - var/space_chance = 55 // Likelihood of getting a space in the random scramble string - var/list/spans = list() - var/list/scramble_cache = list() - var/default_priority = 0 // the language that an atom knows with the highest "default_priority" is selected by default. - - // if you are seeing someone speak popcorn language, then something is wrong. - var/icon = 'icons/misc/language.dmi' - var/icon_state = "popcorn" - -/datum/language/proc/display_icon(atom/movable/hearer) - var/understands = hearer.has_language(src.type) - if(flags & LANGUAGE_HIDE_ICON_IF_UNDERSTOOD && understands) - return FALSE - if(flags & LANGUAGE_HIDE_ICON_IF_NOT_UNDERSTOOD && !understands) - return FALSE - return TRUE - -/datum/language/proc/get_icon() - var/datum/asset/spritesheet/sheet = get_asset_datum(/datum/asset/spritesheet/chat) - return sheet.icon_tag("language-[icon_state]") - -/datum/language/proc/get_random_name(gender, name_count=2, syllable_count=4, syllable_divisor=2) - if(!syllables || !syllables.len) - if(gender == FEMALE) - return capitalize(pick(GLOB.first_names_female)) + " " + capitalize(pick(GLOB.last_names)) - else - return capitalize(pick(GLOB.first_names_male)) + " " + capitalize(pick(GLOB.last_names)) - - var/full_name = "" - var/new_name = "" - - for(var/i in 0 to name_count) - new_name = "" - var/Y = rand(FLOOR(syllable_count/syllable_divisor, 1), syllable_count) - for(var/x in Y to 0) - new_name += pick_weight_recursive(syllables) - full_name += " [capitalize(LOWER_TEXT(new_name))]" - - return "[trim(full_name)]" - -/datum/language/proc/check_cache(input) - var/lookup = scramble_cache[input] - if(lookup) - scramble_cache -= input - scramble_cache[input] = lookup - . = lookup - -/datum/language/proc/add_to_cache(input, scrambled_text) - // Add it to cache, cutting old entries if the list is too long - scramble_cache[input] = scrambled_text - if(scramble_cache.len > SCRAMBLE_CACHE_LEN) - scramble_cache.Cut(1, scramble_cache.len-SCRAMBLE_CACHE_LEN-1) - -/datum/language/proc/scramble(input) - - if(!syllables || !syllables.len) - return stars(input) - - // If the input is cached already, move it to the end of the cache and return it - var/lookup = check_cache(input) - if(lookup) - return lookup - - var/input_size = length_char(input) - var/scrambled_text = "" - var/capitalize = TRUE - - while(length_char(scrambled_text) < input_size) - var/next = pick_weight_recursive(syllables) - if(capitalize) - next = capitalize(next) - capitalize = FALSE - scrambled_text += next - var/chance = rand(100) - if(chance <= sentence_chance) - scrambled_text += ". " - capitalize = TRUE - else if(chance > sentence_chance && chance <= space_chance) - scrambled_text += " " - - scrambled_text = trim(scrambled_text) - var/ending = copytext_char(scrambled_text, -1) - if(ending == ".") - scrambled_text = copytext_char(scrambled_text, 1, -2) - var/input_ending = copytext_char(input, -1) - if(input_ending in list("!","?",".")) - scrambled_text += input_ending - - add_to_cache(input, scrambled_text) - - return scrambled_text - -#undef SCRAMBLE_CACHE_LEN diff --git a/code/modules/language/machine.dm b/code/modules/language/machine.dm index 36962a712a1b5..4be282a5e2812 100644 --- a/code/modules/language/machine.dm +++ b/code/modules/language/machine.dm @@ -14,7 +14,16 @@ icon_state = "eal" -/datum/language/machine/get_random_name() +/datum/language/machine/get_random_name( + gender = NEUTER, + name_count = 2, + syllable_min = 2, + syllable_max = 4, + unique = FALSE, + force_use_syllables = FALSE, +) + if(force_use_syllables) + return ..() if(prob(70)) return "[pick(GLOB.posibrain_names)]-[rand(100, 999)]" return pick(GLOB.ai_names) diff --git a/code/modules/language/moffic.dm b/code/modules/language/moffic.dm index 1d0aea96697fb..fb8dea63dcc83 100644 --- a/code/modules/language/moffic.dm +++ b/code/modules/language/moffic.dm @@ -13,4 +13,20 @@ icon_state = "moth" default_priority = 90 + default_name_syllable_min = 5 + default_name_syllable_max = 10 + +/datum/language/moffic/get_random_name( + gender = NEUTER, + name_count = default_name_count, + syllable_min = default_name_syllable_min, + syllable_max = default_name_syllable_max, + force_use_syllables = FALSE, +) + if(force_use_syllables) + return ..() + + return "[pick(GLOB.moth_first)] [pick(GLOB.moth_last)]" + + // Fuck guest accounts, and fuck language testing. diff --git a/code/modules/language/monkey.dm b/code/modules/language/monkey.dm index e44f6a6268e25..423e94f22bd8c 100644 --- a/code/modules/language/monkey.dm +++ b/code/modules/language/monkey.dm @@ -7,3 +7,12 @@ default_priority = 80 icon_state = "animal" + +/datum/language/monkey/get_random_name( + gender = NEUTER, + name_count = 2, + syllable_min = 2, + syllable_max = 4, + force_use_syllables = FALSE, +) + return "monkey ([rand(1, 999)])" diff --git a/code/modules/language/mushroom.dm b/code/modules/language/mushroom.dm index 08d494cc04d64..910489fd6dd9e 100644 --- a/code/modules/language/mushroom.dm +++ b/code/modules/language/mushroom.dm @@ -5,3 +5,5 @@ sentence_chance = 0 default_priority = 80 syllables = list("poof", "pff", "pFfF", "piff", "puff", "pooof", "pfffff", "piffpiff", "puffpuff", "poofpoof", "pifpafpofpuf") + default_name_syllable_min = 1 + default_name_syllable_max = 2 diff --git a/code/modules/language/nekomimetic.dm b/code/modules/language/nekomimetic.dm index 82edc2afcb57a..4be943f84417a 100644 --- a/code/modules/language/nekomimetic.dm +++ b/code/modules/language/nekomimetic.dm @@ -12,3 +12,16 @@ ) icon_state = "neko" default_priority = 90 + default_name_syllable_min = 2 + default_name_syllable_max = 2 + +/datum/language/nekomimetic/get_random_name( + gender = NEUTER, + name_count = default_name_count, + syllable_min = default_name_syllable_min, + syllable_max = default_name_syllable_max, + force_use_syllables = FALSE, +) + if(prob(33)) + return default_name(gender) + return ..() diff --git a/code/modules/language/piratespeak.dm b/code/modules/language/piratespeak.dm index 5f6cb4897715d..a2faddb544f7c 100644 --- a/code/modules/language/piratespeak.dm +++ b/code/modules/language/piratespeak.dm @@ -10,3 +10,4 @@ "shiver", "timbers", "matey", "swashbuckler" ) icon_state = "pirate" + always_use_default_namelist = TRUE diff --git a/code/modules/language/shadowtongue.dm b/code/modules/language/shadowtongue.dm index 9c0adb5eea3ff..351589393856b 100644 --- a/code/modules/language/shadowtongue.dm +++ b/code/modules/language/shadowtongue.dm @@ -16,3 +16,5 @@ ) icon_state = "shadow" default_priority = 90 + default_name_syllable_min = 2 + default_name_syllable_max = 3 diff --git a/code/modules/language/slime.dm b/code/modules/language/slime.dm index fcb471774118a..15960898673d6 100644 --- a/code/modules/language/slime.dm +++ b/code/modules/language/slime.dm @@ -2,7 +2,8 @@ name = "Slime" desc = "A melodic and complex language spoken by slimes. Some of the notes are inaudible to humans." key = "k" - syllables = list("qr","qrr","xuq","qil","quum","xuqm","vol","xrim","zaoo","qu-uu","qix","qoo","zix","*","!") + syllables = list("qr","qrr","xuq","qil","quum","xuqm","vol","xrim","zaoo","qu-uu","qix","qoo","zix") + special_characters = list("!","*") default_priority = 70 icon_state = "slime" diff --git a/code/modules/language/sylvan.dm b/code/modules/language/sylvan.dm index 68cb73f9d525a..4f66fb5931c1a 100644 --- a/code/modules/language/sylvan.dm +++ b/code/modules/language/sylvan.dm @@ -13,3 +13,5 @@ ) icon_state = "plant" default_priority = 90 + default_name_syllable_min = 2 + default_name_syllable_max = 3 diff --git a/code/modules/language/terrum.dm b/code/modules/language/terrum.dm index 361106ed16c93..63b527202f4ca 100644 --- a/code/modules/language/terrum.dm +++ b/code/modules/language/terrum.dm @@ -7,8 +7,25 @@ "sha", "vu", "nah", "ha", "yom", "ma", "cha", "ar", "et", "mol", "lua", "ch", "na", "sh", "ni", "yah", "bes", "ol", "hish", "ev", "la", "ot", "la", "khe", "tza", "chak", "hak", "hin", "hok", "lir", "tov", "yef", "yfe", - "cho", "ar", "kas", "kal", "ra", "lom", "im", "'", "'", "'", "'", "bok", + "cho", "ar", "kas", "kal", "ra", "lom", "im", "bok", "erev", "shlo", "lo", "ta", "im", "yom" ) + special_characters = list("'") icon_state = "golem" default_priority = 90 + +/datum/language/terrum/get_random_name( + gender = NEUTER, + name_count = default_name_count, + syllable_min = default_name_syllable_min, + syllable_max = default_name_syllable_max, + force_use_syllables = FALSE, +) + if(force_use_syllables) + return ..() + + var/name = pick(GLOB.golem_names) + // 3% chance to be given a human surname for "lore reasons" + if (prob(3)) + name += " [pick(GLOB.last_names)]" + return name diff --git a/code/modules/language/voltaic.dm b/code/modules/language/voltaic.dm index 40fa9dcb1e826..90ab90dbe48e0 100644 --- a/code/modules/language/voltaic.dm +++ b/code/modules/language/voltaic.dm @@ -12,3 +12,21 @@ ) icon_state = "volt" default_priority = 90 + default_name_syllable_min = 2 + default_name_syllable_max = 3 + + +/datum/language/voltaic/get_random_name( + gender = NEUTER, + name_count = default_name_count, + syllable_min = default_name_syllable_min, + syllable_max = default_name_syllable_max, + force_use_syllables = FALSE, +) + if(force_use_syllables) + return ..() + + var/picked = "[pick(GLOB.ethereal_names)] [random_capital_letter()]" + if(prob(65)) + picked += random_capital_letter() + return picked diff --git a/code/modules/language/xenocommon.dm b/code/modules/language/xenocommon.dm index c5e6366715d8e..f4949b7d73cb4 100644 --- a/code/modules/language/xenocommon.dm +++ b/code/modules/language/xenocommon.dm @@ -6,3 +6,4 @@ default_priority = 50 icon_state = "xeno" + always_use_default_namelist = TRUE // Sssss Ssss? diff --git a/code/modules/mapping/mapping_helpers.dm b/code/modules/mapping/mapping_helpers.dm index a2ffa244679b6..ff46cfc0b64e7 100644 --- a/code/modules/mapping/mapping_helpers.dm +++ b/code/modules/mapping/mapping_helpers.dm @@ -914,8 +914,7 @@ INITIALIZE_IMMEDIATE(/obj/effect/mapping_helpers/no_lava) var/datum/species/new_human_species = GLOB.species_list[species_to_pick] if(new_human_species) new_human.set_species(new_human_species) - new_human_species = new_human.dna.species - new_human.fully_replace_character_name(new_human.real_name, new_human_species.random_name(new_human.gender, TRUE, TRUE)) + new_human.fully_replace_character_name(new_human.real_name, new_human.generate_random_mob_name()) else stack_trace("failed to spawn cadaver with species ID [species_to_pick]") //if it's invalid they'll just be a human, so no need to worry too much aside from yelling at the server owner lol. else diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index f7e4b56cab274..1d2a8d1570f0f 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -89,15 +89,10 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER) gender = body.gender if(body.mind && body.mind.name) - if(body.mind.ghostname) - name = body.mind.ghostname - else - name = body.mind.name + name = body.mind.ghostname || body.mind.name else - if(body.real_name) - name = body.real_name - else - name = random_unique_name(gender) + name = body.real_name || generate_random_mob_name(gender) + mind = body.mind //we don't transfer the mind but we keep a reference to it. @@ -125,8 +120,8 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER) abstract_move(T) - if(!name) //To prevent nameless ghosts - name = random_unique_name(gender) + //To prevent nameless ghosts + name ||= generate_random_mob_name(FALSE) real_name = name if(!fun_verbs) @@ -838,7 +833,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp client.prefs.apply_character_randomization_prefs() var/species_type = client.prefs.read_preference(/datum/preference/choiced/species) - var/datum/species/species = new species_type + var/datum/species/species = GLOB.species_prototypes[species_type] if(species.check_head_flags(HEAD_HAIR)) hairstyle = client.prefs.read_preference(/datum/preference/choiced/hairstyle) hair_color = ghostify_color(client.prefs.read_preference(/datum/preference/color/hair_color)) @@ -847,8 +842,6 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp facial_hairstyle = client.prefs.read_preference(/datum/preference/choiced/facial_hairstyle) facial_hair_color = ghostify_color(client.prefs.read_preference(/datum/preference/color/facial_hair_color)) - qdel(species) - update_appearance() /mob/dead/observer/can_perform_action(atom/movable/target, action_bitflags) diff --git a/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm b/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm index 21943d39d3d1b..d2b5edc58cced 100644 --- a/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm +++ b/code/modules/mob/living/basic/space_fauna/revenant/_revenant.dm @@ -102,7 +102,7 @@ RegisterSignal(src, COMSIG_LIVING_BANED, PROC_REF(on_baned)) RegisterSignal(src, COMSIG_MOVABLE_PRE_MOVE, PROC_REF(on_move)) RegisterSignal(src, COMSIG_LIVING_LIFE, PROC_REF(on_life)) - set_random_revenant_name() + name = generate_random_mob_name() GLOB.revenant_relay_mobs |= src @@ -357,13 +357,13 @@ returnable_list += span_bold("Be sure to read the wiki page to learn more.") return returnable_list -/mob/living/basic/revenant/proc/set_random_revenant_name() +/mob/living/basic/revenant/generate_random_mob_name() var/list/built_name_strings = list() built_name_strings += pick(strings(REVENANT_NAME_FILE, "spirit_type")) built_name_strings += " of " built_name_strings += pick(strings(REVENANT_NAME_FILE, "adverb")) built_name_strings += pick(strings(REVENANT_NAME_FILE, "theme")) - name = built_name_strings.Join("") + return built_name_strings.Join("") /mob/living/basic/revenant/proc/on_baned(obj/item/weapon, mob/living/user) SIGNAL_HANDLER diff --git a/code/modules/mob/living/carbon/human/_species.dm b/code/modules/mob/living/carbon/human/_species.dm index adfe93d318dba..24a35e683e36d 100644 --- a/code/modules/mob/living/carbon/human/_species.dm +++ b/code/modules/mob/living/carbon/human/_species.dm @@ -222,14 +222,12 @@ GLOBAL_LIST_EMPTY(features_by_species) var/list/selectable_species = list() for(var/species_type in subtypesof(/datum/species)) - var/datum/species/species = new species_type + var/datum/species/species = GLOB.species_prototypes[species_type] if(species.check_roundstart_eligible()) selectable_species += species.id - var/datum/language_holder/temp_holder = new species.species_language_holder + var/datum/language_holder/temp_holder = GLOB.prototype_language_holders[species.species_language_holder] for(var/datum/language/spoken_language as anything in temp_holder.understood_languages) GLOB.uncommon_roundstart_languages |= spoken_language - qdel(temp_holder) - qdel(species) GLOB.uncommon_roundstart_languages -= /datum/language/common if(!selectable_species.len) @@ -248,32 +246,6 @@ GLOBAL_LIST_EMPTY(features_by_species) return TRUE return FALSE -/** - * Generates a random name for a carbon. - * - * This generates a random unique name based on a human's species and gender. - * Arguments: - * * gender - The gender that the name should adhere to. Use MALE for male names, use anything else for female names. - * * unique - If true, ensures that this new name is not a duplicate of anyone else's name currently on the station. - * * last_name - Do we use a given last name or pick a random new one? - */ -/datum/species/proc/random_name(gender, unique, last_name) - if(unique) - return random_unique_name(gender) - - var/randname - if(gender == MALE) - randname = pick(GLOB.first_names_male) - else - randname = pick(GLOB.first_names_female) - - if(last_name) - randname += " [last_name]" - else - randname += " [pick(GLOB.last_names)]" - - return randname - /** * Copies some vars and properties over that should be kept when creating a copy of this species. * diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 07135351a91f2..9d3d8c2d5a81b 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -815,7 +815,7 @@ if(href_list[VV_HK_SET_SPECIES]) if(!check_rights(R_SPAWN)) return - var/result = input(usr, "Please choose a new species","Species") as null|anything in GLOB.species_list + var/result = input(usr, "Please choose a new species","Species") as null|anything in sortTim(GLOB.species_list, GLOBAL_PROC_REF(cmp_text_asc)) if(result) var/newtype = GLOB.species_list[result] admin_ticket_log("[key_name_admin(usr)] has modified the bodyparts of [src] to [result]") @@ -1019,7 +1019,7 @@ /mob/living/carbon/human/species/set_species(datum/species/mrace, icon_update, pref_load) . = ..() if(use_random_name) - fully_replace_character_name(real_name, dna.species.random_name()) + fully_replace_character_name(real_name, generate_random_mob_name()) /mob/living/carbon/human/species/abductor race = /datum/species/abductor diff --git a/code/modules/mob/living/carbon/human/human_helpers.dm b/code/modules/mob/living/carbon/human/human_helpers.dm index c7069ad056d4d..3315c5421d376 100644 --- a/code/modules/mob/living/carbon/human/human_helpers.dm +++ b/code/modules/mob/living/carbon/human/human_helpers.dm @@ -259,7 +259,7 @@ if (preference.is_randomizable()) preference.apply_to_human(src, preference.create_random_value(preferences)) - fully_replace_character_name(real_name, dna.species.random_name()) + fully_replace_character_name(real_name, generate_random_mob_name()) /** * Setter for mob height diff --git a/code/modules/mob/living/carbon/human/species_types/ethereal.dm b/code/modules/mob/living/carbon/human/species_types/ethereal.dm index e37e7ca81447f..4c307107f153d 100644 --- a/code/modules/mob/living/carbon/human/species_types/ethereal.dm +++ b/code/modules/mob/living/carbon/human/species_types/ethereal.dm @@ -80,14 +80,6 @@ QDEL_NULL(ethereal_light) return ..() -/datum/species/ethereal/random_name(gender,unique,lastname) - if(unique) - return random_unique_ethereal_name() - - var/randname = ethereal_name() - - return randname - /datum/species/ethereal/randomize_features() var/list/features = ..() features["ethcolor"] = GLOB.color_list_ethereal[pick(GLOB.color_list_ethereal)] diff --git a/code/modules/mob/living/carbon/human/species_types/golems.dm b/code/modules/mob/living/carbon/human/species_types/golems.dm index 4e860014554e8..13471b2872b98 100644 --- a/code/modules/mob/living/carbon/human/species_types/golems.dm +++ b/code/modules/mob/living/carbon/human/species_types/golems.dm @@ -51,15 +51,6 @@ BODY_ZONE_CHEST = /obj/item/bodypart/chest/golem, ) - /// Chance that we will generate a human surname, for lore reasons - var/human_surname_chance = 3 - -/datum/species/golem/random_name(gender,unique,lastname) - var/name = pick(GLOB.golem_names) - if (prob(human_surname_chance)) - name += " [pick(GLOB.last_names)]" - return name - /datum/species/golem/get_physical_attributes() return "Golems are hardy creatures made out of stone, which are thus naturally resistant to many dangers, including asphyxiation, fire, radiation, electricity, and viruses.\ They gain special abilities depending on the type of material consumed, but they need to consume material to keep their body animated." diff --git a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm index 1b478162de4c6..488d76cbd2136 100644 --- a/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm +++ b/code/modules/mob/living/carbon/human/species_types/lizardpeople.dm @@ -47,18 +47,6 @@ /datum/species/lizard/body_temperature_core(mob/living/carbon/human/humi, seconds_per_tick, times_fired) return -/datum/species/lizard/random_name(gender,unique,lastname) - if(unique) - return random_unique_lizard_name(gender) - - var/randname = lizard_name(gender) - - if(lastname) - randname += " [lastname]" - - return randname - - /datum/species/lizard/randomize_features() var/list/features = ..() features["body_markings"] = pick(SSaccessories.body_markings_list) diff --git a/code/modules/mob/living/carbon/human/species_types/monkeys.dm b/code/modules/mob/living/carbon/human/species_types/monkeys.dm index cbd2df699ea1a..ddf0963bb5d24 100644 --- a/code/modules/mob/living/carbon/human/species_types/monkeys.dm +++ b/code/modules/mob/living/carbon/human/species_types/monkeys.dm @@ -41,9 +41,6 @@ payday_modifier = 1.5 ai_controlled_species = TRUE -/datum/species/monkey/random_name(gender,unique,lastname) - return "monkey ([rand(1, 999)])" - /datum/species/monkey/on_species_gain(mob/living/carbon/human/human_who_gained_species, datum/species/old_species, pref_load) . = ..() passtable_on(human_who_gained_species, SPECIES_TRAIT) diff --git a/code/modules/mob/living/carbon/human/species_types/mothmen.dm b/code/modules/mob/living/carbon/human/species_types/mothmen.dm index e52a19b587b9a..54d6fe027e32f 100644 --- a/code/modules/mob/living/carbon/human/species_types/mothmen.dm +++ b/code/modules/mob/living/carbon/human/species_types/mothmen.dm @@ -34,17 +34,6 @@ var/mob/living/carbon/human/H = C handle_mutant_bodyparts(H) -/datum/species/moth/random_name(gender,unique,lastname) - if(unique) - return random_unique_moth_name() - - var/randname = moth_name() - - if(lastname) - randname += " [lastname]" - - return randname - /datum/species/moth/on_species_gain(mob/living/carbon/human/human_who_gained_species, datum/species/old_species, pref_load) . = ..() RegisterSignal(human_who_gained_species, COMSIG_MOB_APPLY_DAMAGE_MODIFIERS, PROC_REF(damage_weakness)) diff --git a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm index 2d053813e5b83..e63e3c39c4885 100644 --- a/code/modules/mob/living/carbon/human/species_types/plasmamen.dm +++ b/code/modules/mob/living/carbon/human/species_types/plasmamen.dm @@ -129,17 +129,6 @@ else give_important_for_life(equipping) -/datum/species/plasmaman/random_name(gender,unique,lastname) - if(unique) - return random_unique_plasmaman_name() - - var/randname = plasmaman_name() - - if(lastname) - randname += " [lastname]" - - return randname - /datum/species/plasmaman/get_scream_sound(mob/living/carbon/human) return pick( 'sound/voice/plasmaman/plasmeme_scream_1.ogg', diff --git a/code/modules/mob/living/living_say.dm b/code/modules/mob/living/living_say.dm index 36a020ce3e759..bbcf77986ac79 100644 --- a/code/modules/mob/living/living_say.dm +++ b/code/modules/mob/living/living_say.dm @@ -208,9 +208,9 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list( spans |= speech_span - if(language) - var/datum/language/L = GLOB.language_datum_instances[language] - spans |= L.spans + var/datum/language/spoken_lang = GLOB.language_datum_instances[language] + if(LAZYLEN(spoken_lang?.spans)) + spans |= spoken_lang.spans if(message_mods[MODE_SING]) var/randomnote = pick("\u2669", "\u266A", "\u266B") diff --git a/code/modules/mob_spawn/ghost_roles/golem_roles.dm b/code/modules/mob_spawn/ghost_roles/golem_roles.dm index b3475e9207f83..5fc643bffa622 100644 --- a/code/modules/mob_spawn/ghost_roles/golem_roles.dm +++ b/code/modules/mob_spawn/ghost_roles/golem_roles.dm @@ -36,8 +36,7 @@ if(forced_name || !iscarbon(spawned_mob)) return ..() - var/datum/species/golem/golem_species = new() - forced_name = golem_species.random_name() + forced_name = generate_random_name_species_based(spawned_mob.gender, TRUE, species_type = /datum/species/golem) return ..() /obj/effect/mob_spawn/ghost_role/human/golem/special(mob/living/new_spawn, mob/mob_possessor) diff --git a/code/modules/mob_spawn/ghost_roles/mining_roles.dm b/code/modules/mob_spawn/ghost_roles/mining_roles.dm index 208a74a3edbf5..53fa001097039 100644 --- a/code/modules/mob_spawn/ghost_roles/mining_roles.dm +++ b/code/modules/mob_spawn/ghost_roles/mining_roles.dm @@ -194,9 +194,9 @@ /obj/structure/ash_walker_eggshell/Destroy() if(!egg) return ..() - var/mob/living/carbon/human/yolk = new /mob/living/carbon/human/(get_turf(src)) - yolk.fully_replace_character_name(null,random_unique_lizard_name(gender)) + var/mob/living/carbon/human/yolk = new(get_turf(src)) yolk.set_species(/datum/species/lizard/ashwalker) + yolk.fully_replace_character_name(null, yolk.generate_random_mob_name(TRUE)) yolk.underwear = "Nude" yolk.equipOutfit(/datum/outfit/ashwalker)//this is an authentic mess we're making yolk.update_body() @@ -235,7 +235,7 @@ /obj/effect/mob_spawn/ghost_role/human/ash_walker/special(mob/living/carbon/human/spawned_human) . = ..() - spawned_human.fully_replace_character_name(null,random_unique_lizard_name(gender)) + spawned_human.fully_replace_character_name(null, spawned_human.generate_random_mob_name(TRUE)) to_chat(spawned_human, "Drag the corpses of men and beasts to your nest. It will absorb them to create more of your kind. Invade the strange structure of the outsiders if you must. Do not cause unnecessary destruction, as littering the wastes with ugly wreckage is certain to not gain you favor. Glory to the Necropolis!") spawned_human.mind.add_antag_datum(/datum/antagonist/ashwalker, team) diff --git a/code/modules/mob_spawn/mob_spawn.dm b/code/modules/mob_spawn/mob_spawn.dm index 086254aae3881..ad8c7e6a03ef0 100644 --- a/code/modules/mob_spawn/mob_spawn.dm +++ b/code/modules/mob_spawn/mob_spawn.dm @@ -78,7 +78,7 @@ if(skin_tone) spawned_human.skin_tone = skin_tone else - spawned_human.skin_tone = random_skin_tone() + spawned_human.skin_tone = pick(GLOB.skin_tones) spawned_human.update_body(is_creating = TRUE) /obj/effect/mob_spawn/proc/name_mob(mob/living/spawned_mob, forced_name) diff --git a/code/modules/shuttle/emergency.dm b/code/modules/shuttle/emergency.dm index 62e13ecb7f9e0..6ac45f9424d99 100644 --- a/code/modules/shuttle/emergency.dm +++ b/code/modules/shuttle/emergency.dm @@ -293,13 +293,12 @@ obj_flags |= EMAGGED SSshuttle.emergency.movement_force = list("KNOCKDOWN" = 60, "THROW" = 20)//YOUR PUNY SEATBELTS can SAVE YOU NOW, MORTAL - var/datum/species/S = new for(var/i in 1 to 10) // the shuttle system doesn't know who these people are, but they // must be important, surely var/obj/item/card/id/ID = new(src) var/datum/job/J = pick(SSjob.joinable_occupations) - ID.registered_name = S.random_name(pick(MALE, FEMALE)) + ID.registered_name = generate_random_name_species_based(species_type = /datum/species/human) ID.assignment = J.title authorized += ID diff --git a/code/modules/surgery/plastic_surgery.dm b/code/modules/surgery/plastic_surgery.dm index 9224c29b2db63..8be13802e7cbb 100644 --- a/code/modules/surgery/plastic_surgery.dm +++ b/code/modules/surgery/plastic_surgery.dm @@ -94,11 +94,11 @@ else user.visible_message(span_warning("You have no picture to base the appearance on, reverting to random appearances.")) for(var/i in 1 to 10) - names += target.dna.species.random_name(target.gender, TRUE) + names += target.generate_random_mob_name(TRUE) else - for(var/_i in 1 to 9) + for(var/j in 1 to 9) names += "Subject [target.gender == MALE ? "i" : "o"]-[pick("a", "b", "c", "d", "e")]-[rand(10000, 99999)]" - names += target.dna.species.random_name(target.gender, TRUE) //give one normal name in case they want to do regular plastic surgery + names += target.generate_random_mob_name(TRUE) //give one normal name in case they want to do regular plastic surgery var/chosen_name = tgui_input_list(user, "New name to assign", "Plastic Surgery", names) if(isnull(chosen_name)) return diff --git a/code/modules/unit_tests/preference_species.dm b/code/modules/unit_tests/preference_species.dm index 8e49f49cdd6a4..8d913cc8fb64d 100644 --- a/code/modules/unit_tests/preference_species.dm +++ b/code/modules/unit_tests/preference_species.dm @@ -12,7 +12,7 @@ for(var/species_id in get_selectable_species()) var/species_type = GLOB.species_list[species_id] - var/datum/species/species = new species_type() + var/datum/species/species = GLOB.species_prototypes[species_type] // Check the species decription. // If it's not overridden, a stack trace will be thrown (and fail the test). @@ -29,5 +29,3 @@ TEST_FAIL("Species [species] ([species_type]) is selectable, but did not properly implement get_species_lore().") else if(!islist(species_lore)) TEST_FAIL("Species [species] ([species_type]) is selectable, but did not properly implement get_species_lore() (Did not return a list).") - - qdel(species) diff --git a/tgstation.dme b/tgstation.dme index 7e6bf667f0c47..c7cea8aae3a54 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -651,7 +651,6 @@ #include "code\controllers\subsystem\ipintel.dm" #include "code\controllers\subsystem\job.dm" #include "code\controllers\subsystem\lag_switch.dm" -#include "code\controllers\subsystem\language.dm" #include "code\controllers\subsystem\library.dm" #include "code\controllers\subsystem\lighting.dm" #include "code\controllers\subsystem\lua.dm" @@ -4333,6 +4332,10 @@ #include "code\modules\keybindings\bindings_client.dm" #include "code\modules\keybindings\focus.dm" #include "code\modules\keybindings\setup.dm" +#include "code\modules\language\_language.dm" +#include "code\modules\language\_language_holder.dm" +#include "code\modules\language\_language_manuals.dm" +#include "code\modules\language\_language_menu.dm" #include "code\modules\language\aphasia.dm" #include "code\modules\language\beachbum.dm" #include "code\modules\language\buzzwords.dm" @@ -4341,10 +4344,6 @@ #include "code\modules\language\common.dm" #include "code\modules\language\draconic.dm" #include "code\modules\language\drone.dm" -#include "code\modules\language\language.dm" -#include "code\modules\language\language_holder.dm" -#include "code\modules\language\language_manuals.dm" -#include "code\modules\language\language_menu.dm" #include "code\modules\language\machine.dm" #include "code\modules\language\moffic.dm" #include "code\modules\language\monkey.dm" From 84b8a3d87da63699aac7b981ac9f536ad3947a38 Mon Sep 17 00:00:00 2001 From: orange man <61334995+comfyorange@users.noreply.github.com> Date: Sun, 5 May 2024 06:23:13 +1200 Subject: [PATCH 72/87] Automatic changelog for PR #83025 [ci skip] --- html/changelogs/AutoChangeLog-pr-83025.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-83025.yml diff --git a/html/changelogs/AutoChangeLog-pr-83025.yml b/html/changelogs/AutoChangeLog-pr-83025.yml new file mode 100644 index 0000000000000..1182b19d5ebde --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-83025.yml @@ -0,0 +1,4 @@ +author: "larentoun" +delete-after: True +changes: + - bugfix: "Materials are now correctly applied to crafted items (chairs, toilets, etc)" \ No newline at end of file From 1a222e7e6fb74c68f29dc7152a22cd5862b666a1 Mon Sep 17 00:00:00 2001 From: orange man <61334995+comfyorange@users.noreply.github.com> Date: Sun, 5 May 2024 06:25:44 +1200 Subject: [PATCH 73/87] Automatic changelog for PR #83030 [ci skip] --- html/changelogs/AutoChangeLog-pr-83030.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-83030.yml diff --git a/html/changelogs/AutoChangeLog-pr-83030.yml b/html/changelogs/AutoChangeLog-pr-83030.yml new file mode 100644 index 0000000000000..5c70f0e277a8a --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-83030.yml @@ -0,0 +1,5 @@ +author: "Jacquerel" +delete-after: True +changes: + - bugfix: "Blood Brothers should spawn knowing what their objectives are." + - bugfix: "Teams of 3 Blood Brothers will once more have an additional objective." \ No newline at end of file From 2b2d12b4b2d0f98c69c0f284efdb43baa3113d09 Mon Sep 17 00:00:00 2001 From: orange man <61334995+comfyorange@users.noreply.github.com> Date: Sun, 5 May 2024 06:25:47 +1200 Subject: [PATCH 74/87] Automatic changelog for PR #83021 [ci skip] --- html/changelogs/AutoChangeLog-pr-83021.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-83021.yml diff --git a/html/changelogs/AutoChangeLog-pr-83021.yml b/html/changelogs/AutoChangeLog-pr-83021.yml new file mode 100644 index 0000000000000..6d9ec5542d374 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-83021.yml @@ -0,0 +1,6 @@ +author: "Melbert" +delete-after: True +changes: + - refactor: "Random Name Generation has been refactored. Report any instances of people having weird (or \"Unknown\") names." + - qol: "Felinids, Slimepeople, Podpeople, and some other species without defined namelists now automatically generate names based on their primary language(s)." + - qol: "More non-human names can be generated in codewords (and other misc. areas) than just lizard names." \ No newline at end of file From 386715ea08556ac369a7c104eeb004458dcb23b0 Mon Sep 17 00:00:00 2001 From: Bloop <13398309+vinylspiders@users.noreply.github.com> Date: Sat, 4 May 2024 14:32:03 -0400 Subject: [PATCH 75/87] Makes this list a little more downstream friendly (aka trailing comma pr) (#83028) ## About The Pull Request That's all. Just makes this easier to add onto with less diffs involved. ## Changelog Nothing worth mentioning --------- Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com> --- code/game/objects/items/stacks/sheets/sheet_types.dm | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm index 36582f3ad5ac1..be6226f49bf8e 100644 --- a/code/game/objects/items/stacks/sheets/sheet_types.dm +++ b/code/game/objects/items/stacks/sheets/sheet_types.dm @@ -774,10 +774,14 @@ GLOBAL_LIST_INIT(bronze_recipes, list ( \ // As bone and sinew have just a little too many recipes for this, we'll just split them up. // Sinew slapcrafting will mostly-sinew recipes, and bones will have mostly-bones recipes. - var/static/list/slapcraft_recipe_list = list(\ - /datum/crafting_recipe/bonedagger, /datum/crafting_recipe/bonespear, /datum/crafting_recipe/boneaxe,\ - /datum/crafting_recipe/bonearmor, /datum/crafting_recipe/skullhelm, /datum/crafting_recipe/bracers - ) + var/static/list/slapcraft_recipe_list = list( + /datum/crafting_recipe/bonearmor, + /datum/crafting_recipe/boneaxe, + /datum/crafting_recipe/bonedagger, + /datum/crafting_recipe/bonespear, + /datum/crafting_recipe/bracers, + /datum/crafting_recipe/skullhelm, + ) AddComponent( /datum/component/slapcrafting,\ From 618bd422da9efa7cd667097906ad432056e065d5 Mon Sep 17 00:00:00 2001 From: Changelogs Date: Sun, 5 May 2024 00:22:42 +0000 Subject: [PATCH 76/87] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-83021.yml | 6 ------ html/changelogs/AutoChangeLog-pr-83025.yml | 4 ---- html/changelogs/AutoChangeLog-pr-83026.yml | 5 ----- html/changelogs/AutoChangeLog-pr-83030.yml | 5 ----- html/changelogs/archive/2024-05.yml | 19 +++++++++++++++++++ 5 files changed, 19 insertions(+), 20 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-83021.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-83025.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-83026.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-83030.yml diff --git a/html/changelogs/AutoChangeLog-pr-83021.yml b/html/changelogs/AutoChangeLog-pr-83021.yml deleted file mode 100644 index 6d9ec5542d374..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-83021.yml +++ /dev/null @@ -1,6 +0,0 @@ -author: "Melbert" -delete-after: True -changes: - - refactor: "Random Name Generation has been refactored. Report any instances of people having weird (or \"Unknown\") names." - - qol: "Felinids, Slimepeople, Podpeople, and some other species without defined namelists now automatically generate names based on their primary language(s)." - - qol: "More non-human names can be generated in codewords (and other misc. areas) than just lizard names." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-83025.yml b/html/changelogs/AutoChangeLog-pr-83025.yml deleted file mode 100644 index 1182b19d5ebde..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-83025.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "larentoun" -delete-after: True -changes: - - bugfix: "Materials are now correctly applied to crafted items (chairs, toilets, etc)" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-83026.yml b/html/changelogs/AutoChangeLog-pr-83026.yml deleted file mode 100644 index 97760a11c77da..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-83026.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Isratosh" -delete-after: True -changes: - - bugfix: "If kidnapped and ransomed by pirates, you will now properly return to the station automatically after 6 minutes." - - bugfix: "You can no longer be kidnapped and held for ransom by cargo technicians posing as pirates." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-83030.yml b/html/changelogs/AutoChangeLog-pr-83030.yml deleted file mode 100644 index 5c70f0e277a8a..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-83030.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "Jacquerel" -delete-after: True -changes: - - bugfix: "Blood Brothers should spawn knowing what their objectives are." - - bugfix: "Teams of 3 Blood Brothers will once more have an additional objective." \ No newline at end of file diff --git a/html/changelogs/archive/2024-05.yml b/html/changelogs/archive/2024-05.yml index 304b3ad1b73f3..0137f16cb44b7 100644 --- a/html/changelogs/archive/2024-05.yml +++ b/html/changelogs/archive/2024-05.yml @@ -120,3 +120,22 @@ - bugfix: Candy corn is once again available to detective fedoras mogeoko: - bugfix: Ventcrawling mobs can change Z-level using multiz-decks again. +2024-05-05: + Isratosh: + - bugfix: If kidnapped and ransomed by pirates, you will now properly return to + the station automatically after 6 minutes. + - bugfix: You can no longer be kidnapped and held for ransom by cargo technicians + posing as pirates. + Jacquerel: + - bugfix: Blood Brothers should spawn knowing what their objectives are. + - bugfix: Teams of 3 Blood Brothers will once more have an additional objective. + Melbert: + - refactor: Random Name Generation has been refactored. Report any instances of + people having weird (or "Unknown") names. + - qol: Felinids, Slimepeople, Podpeople, and some other species without defined + namelists now automatically generate names based on their primary language(s). + - qol: More non-human names can be generated in codewords (and other misc. areas) + than just lizard names. + larentoun: + - bugfix: Materials are now correctly applied to crafted items (chairs, toilets, + etc) From e766f921f6625febca526f39bfcfbe4a6dd7da58 Mon Sep 17 00:00:00 2001 From: Jeremiah <42397676+jlsnow301@users.noreply.github.com> Date: Sat, 4 May 2024 20:33:52 -0700 Subject: [PATCH 77/87] Code cleanup: Sorting (#83017) ## About The Pull Request 1. Removes code duplication 2. Fully documents `sortTim()` 3. Makes a define with default sortTim behavior, straight and to the point for 95% of cases 4. Migrates other sorts into the same file 5. Removes some redundancy where they're reassigning a variable using an in place sorter For the record, we only use timSort ## Why It's Good For The Game More documentation, easier to read, uses `length` over `len`, etc Should be no gameplay effect at all --- code/__HELPERS/hallucinations.dm | 2 +- code/__HELPERS/sorts/InsertSort.dm | 19 ---- code/__HELPERS/sorts/MergeSort.dm | 19 ---- code/__HELPERS/sorts/TimSort.dm | 20 ---- code/__HELPERS/sorts/helpers.dm | 95 +++++++++++++++++++ .../sorts/{__main.dm => sort_instance.dm} | 0 code/controllers/subsystem/dcs.dm | 2 +- code/datums/datum.dm | 2 +- code/modules/admin/verbs/debug.dm | 2 +- code/modules/admin/verbs/light_debug.dm | 2 +- .../malf_ai/malf_ai_module_picker.dm | 2 +- code/modules/antagonists/pirate/pirate.dm | 2 +- code/modules/asset_cache/assets/uplink.dm | 2 +- .../file_system/programs/emojipedia.dm | 2 +- code/modules/research/stock_parts.dm | 2 +- code/modules/unit_tests/unit_test.dm | 2 +- tgstation.dme | 6 +- 17 files changed, 108 insertions(+), 73 deletions(-) delete mode 100644 code/__HELPERS/sorts/InsertSort.dm delete mode 100644 code/__HELPERS/sorts/MergeSort.dm delete mode 100644 code/__HELPERS/sorts/TimSort.dm create mode 100644 code/__HELPERS/sorts/helpers.dm rename code/__HELPERS/sorts/{__main.dm => sort_instance.dm} (100%) diff --git a/code/__HELPERS/hallucinations.dm b/code/__HELPERS/hallucinations.dm index 1eeab08d87691..edd65ee926abf 100644 --- a/code/__HELPERS/hallucinations.dm +++ b/code/__HELPERS/hallucinations.dm @@ -145,7 +145,7 @@ ADMIN_VERB(debug_hallucination_weighted_list_per_type, R_DEBUG, "Show Hallucinat last_type_weight = this_weight // Sort by weight descending, where weight is the values (not the keys). We assoc_to_keys later to get JUST the text - all_weights = sortTim(all_weights, GLOBAL_PROC_REF(cmp_numeric_dsc), associative = TRUE) + sortTim(all_weights, GLOBAL_PROC_REF(cmp_numeric_dsc), associative = TRUE) var/page_style = "" var/page_contents = "[page_style][header][jointext(assoc_to_keys(all_weights), "")]
" diff --git a/code/__HELPERS/sorts/InsertSort.dm b/code/__HELPERS/sorts/InsertSort.dm deleted file mode 100644 index 2d5ca408ce3b3..0000000000000 --- a/code/__HELPERS/sorts/InsertSort.dm +++ /dev/null @@ -1,19 +0,0 @@ -//simple insertion sort - generally faster than merge for runs of 7 or smaller -/proc/sortInsert(list/L, cmp=/proc/cmp_numeric_asc, associative, fromIndex=1, toIndex=0) - if(L && L.len >= 2) - fromIndex = fromIndex % L.len - toIndex = toIndex % (L.len+1) - if(fromIndex <= 0) - fromIndex += L.len - if(toIndex <= 0) - toIndex += L.len + 1 - - var/datum/sort_instance/SI = GLOB.sortInstance - if(!SI) - SI = new - SI.L = L - SI.cmp = cmp - SI.associative = associative - - SI.binarySort(fromIndex, toIndex, fromIndex) - return L diff --git a/code/__HELPERS/sorts/MergeSort.dm b/code/__HELPERS/sorts/MergeSort.dm deleted file mode 100644 index 4692534edffd7..0000000000000 --- a/code/__HELPERS/sorts/MergeSort.dm +++ /dev/null @@ -1,19 +0,0 @@ -//merge-sort - gernerally faster than insert sort, for runs of 7 or larger -/proc/sortMerge(list/L, cmp=/proc/cmp_numeric_asc, associative, fromIndex=1, toIndex) - if(L && L.len >= 2) - fromIndex = fromIndex % L.len - toIndex = toIndex % (L.len+1) - if(fromIndex <= 0) - fromIndex += L.len - if(toIndex <= 0) - toIndex += L.len + 1 - - var/datum/sort_instance/SI = GLOB.sortInstance - if(!SI) - SI = new - SI.L = L - SI.cmp = cmp - SI.associative = associative - - SI.mergeSort(fromIndex, toIndex) - return L diff --git a/code/__HELPERS/sorts/TimSort.dm b/code/__HELPERS/sorts/TimSort.dm deleted file mode 100644 index 44f6d170df402..0000000000000 --- a/code/__HELPERS/sorts/TimSort.dm +++ /dev/null @@ -1,20 +0,0 @@ -//TimSort interface -/proc/sortTim(list/L, cmp=/proc/cmp_numeric_asc, associative, fromIndex=1, toIndex=0) - if(L && L.len >= 2) - fromIndex = fromIndex % L.len - toIndex = toIndex % (L.len+1) - if(fromIndex <= 0) - fromIndex += L.len - if(toIndex <= 0) - toIndex += L.len + 1 - - var/datum/sort_instance/SI = GLOB.sortInstance - if(!SI) - SI = new - - SI.L = L - SI.cmp = cmp - SI.associative = associative - - SI.timSort(fromIndex, toIndex) - return L diff --git a/code/__HELPERS/sorts/helpers.dm b/code/__HELPERS/sorts/helpers.dm new file mode 100644 index 0000000000000..7198286f29fe2 --- /dev/null +++ b/code/__HELPERS/sorts/helpers.dm @@ -0,0 +1,95 @@ +/// Sorts the list in place with timSort, default settings. +#define SORT_TIM(to_sort, associative) if(length(to_sort) >= 2) { \ + var/datum/sort_instance/sorter = GLOB.sortInstance; \ + if (isnull(sorter)) { \ + sorter = new; \ + } \ + sorter.L = to_sort; \ + sorter.cmp = GLOBAL_PROC_REF(cmp_numeric_asc); \ + sorter.associative = associative; \ + sorter.timSort(1, 0); \ +} + + +/// Helper for the sorting procs. Prevents some code duplication. Creates /datum/sort_instance/sorter +#define CREATE_SORT_INSTANCE(to_sort, cmp, associative, fromIndex, toIndex) \ + if(length(to_sort) < 2) { \ + return to_sort; \ + } \ + fromIndex = fromIndex % length(to_sort); \ + toIndex = toIndex % (length(to_sort) + 1); \ + if (fromIndex <= 0) { \ + fromIndex += length(to_sort); \ + } \ + if (toIndex <= 0) { \ + toIndex += length(to_sort) + 1; \ + } \ + var/datum/sort_instance/sorter = GLOB.sortInstance; \ + if (isnull(sorter)) { \ + sorter = new; \ + } \ + sorter.L = to_sort; \ + sorter.cmp = cmp; \ + sorter.associative = associative; + + +/** + * ## Tim Sort + * Hybrid sorting algorithm derived from merge sort and insertion sort. + * + * **Sorts in place**. + * You might not need to get the return value. + * + * @see + * https://en.wikipedia.org/wiki/Timsort + * + * @param {list} to_sort - The list to sort. + * + * @param {proc} cmp - The comparison proc to use. Default: Numeric ascending. + * + * @param {boolean} associative - Whether the list is associative. Default: FALSE. + * + * @param {int} fromIndex - The index to start sorting from. Default: 1. + * + * @param {int} toIndex - The index to stop sorting at. Default: 0. + */ +/proc/sortTim(list/to_sort, cmp = GLOBAL_PROC_REF(cmp_numeric_asc), associative = FALSE, fromIndex = 1, toIndex = 0) as /list + CREATE_SORT_INSTANCE(to_sort, cmp, associative, fromIndex, toIndex) + + sorter.timSort(fromIndex, toIndex) + + return to_sort + + +/** + * ## Merge Sort + * Divide and conquer sorting algorithm. + * + * @see + * - https://en.wikipedia.org/wiki/Merge_sort + */ +/proc/sortMerge(list/to_sort, cmp = GLOBAL_PROC_REF(cmp_numeric_asc), associative = FALSE, fromIndex = 1, toIndex = 0) as /list + CREATE_SORT_INSTANCE(to_sort, cmp, associative, fromIndex, toIndex) + + sorter.mergeSort(fromIndex, toIndex) + + return to_sort + + +/** + * ## Insertion Sort + * Simple sorting algorithm that builds the final sorted list one item at a time. + * + + * @see + * - https://en.wikipedia.org/wiki/Insertion_sort + */ +/proc/sortInsert(list/to_sort, cmp = GLOBAL_PROC_REF(cmp_numeric_asc), associative = FALSE, fromIndex = 1, toIndex = 0) as /list + CREATE_SORT_INSTANCE(to_sort, cmp, associative, fromIndex, toIndex) + + sorter.binarySort(fromIndex, toIndex) + + return to_sort + + +#undef CREATE_SORT_INSTANCE diff --git a/code/__HELPERS/sorts/__main.dm b/code/__HELPERS/sorts/sort_instance.dm similarity index 100% rename from code/__HELPERS/sorts/__main.dm rename to code/__HELPERS/sorts/sort_instance.dm diff --git a/code/controllers/subsystem/dcs.dm b/code/controllers/subsystem/dcs.dm index eefbede308739..a3dcc26af54fb 100644 --- a/code/controllers/subsystem/dcs.dm +++ b/code/controllers/subsystem/dcs.dm @@ -84,7 +84,7 @@ PROCESSING_SUBSYSTEM_DEF(dcs) fullid += REF(key) if(named_arguments) - named_arguments = sortTim(named_arguments, GLOBAL_PROC_REF(cmp_text_asc)) + sortTim(named_arguments, GLOBAL_PROC_REF(cmp_text_asc)) fullid += named_arguments return list2params(fullid) diff --git a/code/datums/datum.dm b/code/datums/datum.dm index 77d787eb85ba0..6288244667e85 100644 --- a/code/datums/datum.dm +++ b/code/datums/datum.dm @@ -328,7 +328,7 @@ ASSERT(isatom(src) || isimage(src)) var/atom/atom_cast = src // filters only work with images or atoms. atom_cast.filters = null - filter_data = sortTim(filter_data, GLOBAL_PROC_REF(cmp_filter_data_priority), TRUE) + sortTim(filter_data, GLOBAL_PROC_REF(cmp_filter_data_priority), TRUE) for(var/filter_raw in filter_data) var/list/data = filter_data[filter_raw] var/list/arguments = data.Copy() diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm index e56eb1e5101a7..20cdf3514598f 100644 --- a/code/modules/admin/verbs/debug.dm +++ b/code/modules/admin/verbs/debug.dm @@ -765,7 +765,7 @@ ADMIN_VERB(reestablish_tts_connection, R_DEBUG, "Re-establish Connection To TTS" var/list/sorted = list() for (var/source in per_source) sorted += list(list("source" = source, "count" = per_source[source])) - sorted = sortTim(sorted, GLOBAL_PROC_REF(cmp_timer_data)) + sortTim(sorted, GLOBAL_PROC_REF(cmp_timer_data)) // Now that everything is sorted, compile them into an HTML output var/output = "" diff --git a/code/modules/admin/verbs/light_debug.dm b/code/modules/admin/verbs/light_debug.dm index ae5b24ff38032..eac81f6ed294b 100644 --- a/code/modules/admin/verbs/light_debug.dm +++ b/code/modules/admin/verbs/light_debug.dm @@ -10,7 +10,7 @@ sum[source.source_atom.type] += 1 total += 1 - sum = sortTim(sum, /proc/cmp_numeric_asc, TRUE) + sortTim(sum, associative = TRUE) var/text = "" for(var/type in sum) text += "[type] = [sum[type]]\n" diff --git a/code/modules/antagonists/malf_ai/malf_ai_module_picker.dm b/code/modules/antagonists/malf_ai/malf_ai_module_picker.dm index 51058f82ac02e..0ac27c14c97bf 100644 --- a/code/modules/antagonists/malf_ai/malf_ai_module_picker.dm +++ b/code/modules/antagonists/malf_ai/malf_ai_module_picker.dm @@ -24,7 +24,7 @@ filtered_modules[AM.category][AM] = AM for(var/category in filtered_modules) - filtered_modules[category] = sortTim(filtered_modules[category], GLOBAL_PROC_REF(cmp_malfmodules_priority)) + sortTim(filtered_modules[category], GLOBAL_PROC_REF(cmp_malfmodules_priority)) return filtered_modules diff --git a/code/modules/antagonists/pirate/pirate.dm b/code/modules/antagonists/pirate/pirate.dm index 15a028b24d740..0fa80f5524776 100644 --- a/code/modules/antagonists/pirate/pirate.dm +++ b/code/modules/antagonists/pirate/pirate.dm @@ -85,7 +85,7 @@ //Lists notable loot. if(!cargo_hold || !cargo_hold.total_report) return "Nothing" - cargo_hold.total_report.total_value = sortTim(cargo_hold.total_report.total_value, cmp = GLOBAL_PROC_REF(cmp_numeric_dsc), associative = TRUE) + sortTim(cargo_hold.total_report.total_value, cmp = GLOBAL_PROC_REF(cmp_numeric_dsc), associative = TRUE) var/count = 0 var/list/loot_texts = list() for(var/datum/export/E in cargo_hold.total_report.total_value) diff --git a/code/modules/asset_cache/assets/uplink.dm b/code/modules/asset_cache/assets/uplink.dm index e283b86c04299..e85ee1b35b5c1 100644 --- a/code/modules/asset_cache/assets/uplink.dm +++ b/code/modules/asset_cache/assets/uplink.dm @@ -9,7 +9,7 @@ var/list/items = list() for(var/datum/uplink_category/category as anything in subtypesof(/datum/uplink_category)) categories += category - categories = sortTim(categories, GLOBAL_PROC_REF(cmp_uplink_category_desc)) + sortTim(categories, GLOBAL_PROC_REF(cmp_uplink_category_desc)) var/list/new_categories = list() for(var/datum/uplink_category/category as anything in categories) diff --git a/code/modules/modular_computers/file_system/programs/emojipedia.dm b/code/modules/modular_computers/file_system/programs/emojipedia.dm index 6e9bf56a381c8..10be69cc34976 100644 --- a/code/modules/modular_computers/file_system/programs/emojipedia.dm +++ b/code/modules/modular_computers/file_system/programs/emojipedia.dm @@ -14,7 +14,7 @@ /datum/computer_file/program/emojipedia/New() . = ..() // Sort the emoji list so it's easier to find things and we don't have to keep sorting on ui_data since the number of emojis can not change in-game. - emoji_list = sortTim(emoji_list, /proc/cmp_text_asc) + sortTim(emoji_list, /proc/cmp_text_asc) /datum/computer_file/program/emojipedia/ui_static_data(mob_user) var/list/data = list() diff --git a/code/modules/research/stock_parts.dm b/code/modules/research/stock_parts.dm index 79d24b9c55ab9..d4cb4afefdaf0 100644 --- a/code/modules/research/stock_parts.dm +++ b/code/modules/research/stock_parts.dm @@ -207,7 +207,7 @@ If you create T5+ please take a pass at mech_fabricator.dm. The parts being good continue part_list += component_part //Sort the parts. This ensures that higher tier items are applied first. - part_list = sortTim(part_list, GLOBAL_PROC_REF(cmp_rped_sort)) + sortTim(part_list, GLOBAL_PROC_REF(cmp_rped_sort)) return part_list /proc/cmp_rped_sort(obj/item/first_item, obj/item/second_item) diff --git a/code/modules/unit_tests/unit_test.dm b/code/modules/unit_tests/unit_test.dm index 332005070d5b6..9a6f89fddc353 100644 --- a/code/modules/unit_tests/unit_test.dm +++ b/code/modules/unit_tests/unit_test.dm @@ -340,7 +340,7 @@ GLOBAL_VAR_INIT(focused_tests, focused_tests()) if(length(focused_tests)) tests_to_run = focused_tests - tests_to_run = sortTim(tests_to_run, GLOBAL_PROC_REF(cmp_unit_test_priority)) + sortTim(tests_to_run, GLOBAL_PROC_REF(cmp_unit_test_priority)) var/list/test_results = list() diff --git a/tgstation.dme b/tgstation.dme index c7cea8aae3a54..997e0fb51054a 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -504,10 +504,8 @@ #include "code\__HELPERS\paths\jps.dm" #include "code\__HELPERS\paths\path.dm" #include "code\__HELPERS\paths\sssp.dm" -#include "code\__HELPERS\sorts\__main.dm" -#include "code\__HELPERS\sorts\InsertSort.dm" -#include "code\__HELPERS\sorts\MergeSort.dm" -#include "code\__HELPERS\sorts\TimSort.dm" +#include "code\__HELPERS\sorts\helpers.dm" +#include "code\__HELPERS\sorts\sort_instance.dm" #include "code\_globalvars\_regexes.dm" #include "code\_globalvars\admin.dm" #include "code\_globalvars\arcade.dm" From 79ca00076b3df177b06711f873ceeb49e85ed425 Mon Sep 17 00:00:00 2001 From: paganiy <126676387+paganiy@users.noreply.github.com> Date: Sun, 5 May 2024 16:24:46 +0300 Subject: [PATCH 78/87] Adminmod now has radiation protect module (#83040) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## About The Pull Request Adminmod now has radprotect module ## Why It's Good For The Game It’s kind of annoying to heal yourself every minute when you’re experimenting with a tritium or a supermatter on a local server, so radprotect module fixes this issue ## Changelog :cl: qol: Admin modsuit now has a radiation protect module /:cl: Co-authored-by: paganiy --- code/modules/mod/mod_types.dm | 1 + 1 file changed, 1 insertion(+) diff --git a/code/modules/mod/mod_types.dm b/code/modules/mod/mod_types.dm index f0bca6b0980d3..2789763e12cd2 100644 --- a/code/modules/mod/mod_types.dm +++ b/code/modules/mod/mod_types.dm @@ -596,6 +596,7 @@ /obj/item/mod/module/storage/bluespace, /obj/item/mod/module/emp_shield/advanced, /obj/item/mod/module/welding, + /obj/item/mod/module/rad_protection, /obj/item/mod/module/stealth/ninja, /obj/item/mod/module/quick_carry/advanced, /obj/item/mod/module/magboot/advanced, From 8e8cfd735315ff584b158c75c66fa4cc0f7322ab Mon Sep 17 00:00:00 2001 From: orange man <61334995+comfyorange@users.noreply.github.com> Date: Mon, 6 May 2024 01:25:04 +1200 Subject: [PATCH 79/87] Automatic changelog for PR #83040 [ci skip] --- html/changelogs/AutoChangeLog-pr-83040.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-83040.yml diff --git a/html/changelogs/AutoChangeLog-pr-83040.yml b/html/changelogs/AutoChangeLog-pr-83040.yml new file mode 100644 index 0000000000000..70dc75d07193a --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-83040.yml @@ -0,0 +1,4 @@ +author: "paganiy" +delete-after: True +changes: + - qol: "Admin modsuit now has a radiation protect module" \ No newline at end of file From 54bcab1b96e576c0eab21c31dc049a6aea3ee351 Mon Sep 17 00:00:00 2001 From: Nick Date: Sun, 5 May 2024 19:50:18 +0200 Subject: [PATCH 80/87] Fixes traditional equipment having no name for its crate (#83051) ## About The Pull Request This pr fixes the missing name when you order traditional equipment then just appearing the name ## Why It's Good For The Game So people know what kind of crate it is at sight ## Changelog :cl: fix: fixes traditional equipment crate name /:cl: --- code/modules/cargo/packs/security.dm | 1 + 1 file changed, 1 insertion(+) diff --git a/code/modules/cargo/packs/security.dm b/code/modules/cargo/packs/security.dm index b8e93f2815c0d..e36f9f84cacf1 100644 --- a/code/modules/cargo/packs/security.dm +++ b/code/modules/cargo/packs/security.dm @@ -169,6 +169,7 @@ /obj/item/clothing/mask/whistle, /obj/item/conversion_kit, ) + crate_name = "traditional equipment crate" discountable = SUPPLY_PACK_RARE_DISCOUNTABLE /// Armory packs From 4b10ed53426bdc1164678090ff4925403281a5b3 Mon Sep 17 00:00:00 2001 From: orange man <61334995+comfyorange@users.noreply.github.com> Date: Mon, 6 May 2024 05:50:39 +1200 Subject: [PATCH 81/87] Automatic changelog for PR #83051 [ci skip] --- html/changelogs/AutoChangeLog-pr-83051.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-83051.yml diff --git a/html/changelogs/AutoChangeLog-pr-83051.yml b/html/changelogs/AutoChangeLog-pr-83051.yml new file mode 100644 index 0000000000000..bb64d12981df0 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-83051.yml @@ -0,0 +1,4 @@ +author: "improvedname" +delete-after: True +changes: + - bugfix: "fixes traditional equipment crate name" \ No newline at end of file From 4fb33f819d5699e04d537f41a96fba902cfa7bbb Mon Sep 17 00:00:00 2001 From: Changelogs Date: Mon, 6 May 2024 00:21:18 +0000 Subject: [PATCH 82/87] Automatic changelog compile [ci skip] --- html/changelogs/AutoChangeLog-pr-83040.yml | 4 ---- html/changelogs/AutoChangeLog-pr-83051.yml | 4 ---- html/changelogs/archive/2024-05.yml | 5 +++++ 3 files changed, 5 insertions(+), 8 deletions(-) delete mode 100644 html/changelogs/AutoChangeLog-pr-83040.yml delete mode 100644 html/changelogs/AutoChangeLog-pr-83051.yml diff --git a/html/changelogs/AutoChangeLog-pr-83040.yml b/html/changelogs/AutoChangeLog-pr-83040.yml deleted file mode 100644 index 70dc75d07193a..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-83040.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "paganiy" -delete-after: True -changes: - - qol: "Admin modsuit now has a radiation protect module" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-83051.yml b/html/changelogs/AutoChangeLog-pr-83051.yml deleted file mode 100644 index bb64d12981df0..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-83051.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "improvedname" -delete-after: True -changes: - - bugfix: "fixes traditional equipment crate name" \ No newline at end of file diff --git a/html/changelogs/archive/2024-05.yml b/html/changelogs/archive/2024-05.yml index 0137f16cb44b7..249d2e84cee3d 100644 --- a/html/changelogs/archive/2024-05.yml +++ b/html/changelogs/archive/2024-05.yml @@ -139,3 +139,8 @@ larentoun: - bugfix: Materials are now correctly applied to crafted items (chairs, toilets, etc) +2024-05-06: + improvedname: + - bugfix: fixes traditional equipment crate name + paganiy: + - qol: Admin modsuit now has a radiation protect module From f29af7af9b8d58c9f402ef89e5a34745f48a381f Mon Sep 17 00:00:00 2001 From: Kyle Spier-Swenson Date: Sun, 5 May 2024 18:53:06 -0700 Subject: [PATCH 83/87] fix ipintel caching to db not working. (#83046) --- code/controllers/subsystem/ipintel.dm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/code/controllers/subsystem/ipintel.dm b/code/controllers/subsystem/ipintel.dm index 0f162e2a8616f..f49ad56fca54f 100644 --- a/code/controllers/subsystem/ipintel.dm +++ b/code/controllers/subsystem/ipintel.dm @@ -119,10 +119,10 @@ SUBSYSTEM_DEF(ipintel) var/datum/db_query/query = SSdbcore.NewQuery( "INSERT INTO [format_table_name("ipintel")] ( \ ip, \ - intel, \ + intel \ ) VALUES ( \ - INET_ATON(:address) \ - :result, \ + INET_ATON(:address), \ + :result \ )", list( "address" = intel.address, "result" = intel.result, From 16d221f86d43cfc320173e1df0645887d45c5c14 Mon Sep 17 00:00:00 2001 From: oranges Date: Mon, 6 May 2024 14:19:35 +1200 Subject: [PATCH 84/87] Fix a few mistakes I made (#82887) Co-authored-by: Ghom <42542238+Ghommie@users.noreply.github.com> --- SQL/database_changelog.md | 14 ++++++-------- SQL/tgstation_schema_prefixed.sql | 13 +++++++++++++ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/SQL/database_changelog.md b/SQL/database_changelog.md index 31b516f2af74e..8ae4c7d42647b 100644 --- a/SQL/database_changelog.md +++ b/SQL/database_changelog.md @@ -16,17 +16,15 @@ INSERT INTO `SS13_schema_revision` (`major`, `minor`) VALUES (5, 27); In any query remember to add a prefix to the table names if you use one. ----------------------------------------------------- Version 5.27, 26 April 2024, by zephyrtfa -Add the ip intel table +Add the ip intel whitelist table ```sql -DROP TABLE IF EXISTS `ipintel`; +DROP TABLE IF EXISTS `ipintel_whitelist`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; -CREATE TABLE `ipintel` ( - `ip` int(10) unsigned NOT NULL, - `date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - `intel` double NOT NULL DEFAULT '0', - PRIMARY KEY (`ip`), - KEY `idx_ipintel` (`ip`,`intel`,`date`) +CREATE TABLE `ipintel_whitelist` ( + `ckey` varchar(32) NOT NULL, + `admin_ckey` varchar(32) NOT NULL, + PRIMARY KEY (`ckey`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; ``` diff --git a/SQL/tgstation_schema_prefixed.sql b/SQL/tgstation_schema_prefixed.sql index 597281f2c2076..fb9a6cbe10fa4 100644 --- a/SQL/tgstation_schema_prefixed.sql +++ b/SQL/tgstation_schema_prefixed.sql @@ -214,6 +214,19 @@ CREATE TABLE `SS13_ipintel` ( KEY `idx_ipintel` (`ip`,`intel`,`date`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; /*!40101 SET character_set_client = @saved_cs_client */; +-- +-- Table structure for table `ipintel_whitelist` +-- + +DROP TABLE IF EXISTS `SS13_ipintel_whitelist`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `SS13_ipintel_whitelist` ( + `ckey` varchar(32) NOT NULL, + `admin_ckey` varchar(32) NOT NULL, + PRIMARY KEY (`ckey`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; +/*!40101 SET character_set_client = @saved_cs_client */; -- -- Table structure for table `SS13_legacy_population` From 63b019f5d5adad13cd37393b64a69b654958f938 Mon Sep 17 00:00:00 2001 From: Watermelon914 <37270891+Watermelon914@users.noreply.github.com> Date: Mon, 6 May 2024 02:20:03 +0000 Subject: [PATCH 85/87] Fixes auxtools hanging on server shutdown. (#83050) ## About The Pull Request Does a full shutdown of auxlua when the lua subsystem shuts down. This should unpin the dll file. Compare `AUXTOOLS_SHUTDOWN` code with `AUXTOOLS_FULL_SHUTDOWN`, let me know if I'm mistaken: ### AUXTOOLS_SHUTDOWN https://github.com/willox/auxtools/blob/bc5b2cf019f0f9b18f46b560a0f730d709171b55/auxtools/src/lib.rs#L346 ### AUXTOOLS_FULL_SHUTDOWN https://github.com/willox/auxtools/blob/bc5b2cf019f0f9b18f46b560a0f730d709171b55/auxtools/src/lib.rs#L365 ## Why It's Good For The Game Fixes auxlua keeping the dll pinned when the server is in a process of shutting down. Co-authored-by: Watermelon914 <3052169-Watermelon914@users.noreply.gitlab.com> --- code/controllers/subsystem/lua.dm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/controllers/subsystem/lua.dm b/code/controllers/subsystem/lua.dm index b9ad7dc1644e3..1ab88a01746b7 100644 --- a/code/controllers/subsystem/lua.dm +++ b/code/controllers/subsystem/lua.dm @@ -53,7 +53,7 @@ SUBSYSTEM_DEF(lua) world.SetConfig("env", "LUAU_PATH", jointext(lua_path, ";")) /datum/controller/subsystem/lua/Shutdown() - AUXTOOLS_SHUTDOWN(AUXLUA) + AUXTOOLS_FULL_SHUTDOWN(AUXLUA) /datum/controller/subsystem/lua/proc/queue_resume(datum/lua_state/state, index, arguments) if(!initialized) From 6edd087e173bda1f2558a9341f9377e9e2b8e226 Mon Sep 17 00:00:00 2001 From: "candle :)" <68376391+CandleJaxx@users.noreply.github.com> Date: Sun, 5 May 2024 21:20:34 -0500 Subject: [PATCH 86/87] three more pais (#83038) ## About The Pull Request adds three pai skins, puppy / kitty / spider ![Screenshot_002](https://github.com/tgstation/tgstation/assets/68376391/9c6a65b1-ae05-40a2-97a0-8083e81397d2) ![Screenshot_003](https://github.com/tgstation/tgstation/assets/68376391/2f45d0d2-218c-4caa-8529-26222254fc4f) ![Screenshot_004](https://github.com/tgstation/tgstation/assets/68376391/3b1a0bf7-81d5-465d-acfe-145252b4adb0) ![Screenshot_005](https://github.com/tgstation/tgstation/assets/68376391/b6bf75f9-bc60-4b11-8393-59ae2fed90b6) ![Screenshot_001](https://github.com/tgstation/tgstation/assets/68376391/a80cdf8b-85a1-4305-bcbf-333fe1a39bfc) ![Screenshot_007](https://github.com/tgstation/tgstation/assets/68376391/17c99793-0f95-4724-8a27-3b52449a1487) ![Screenshot_008](https://github.com/tgstation/tgstation/assets/68376391/83f23d0b-3fbf-455e-8181-e118c691eb38) ![Screenshot_009](https://github.com/tgstation/tgstation/assets/68376391/d8cb1bf0-4daa-491d-80d6-1e162302c5cf) ![Screenshot_010](https://github.com/tgstation/tgstation/assets/68376391/3cd34b0a-363b-4739-b30d-a934b3c68782) ![Screenshot_006](https://github.com/tgstation/tgstation/assets/68376391/1a310903-5e6a-496e-b32e-781fe9130c78) ## Why It's Good For The Game bounty request downstream, thought i'd push it up here incase yall wanted it too ## Changelog :cl: add: 'puppy' 'kitten' and 'spider' pai skins /:cl: --- code/modules/pai/pai.dm | 3 +++ icons/mob/silicon/pai.dmi | Bin 64479 -> 66821 bytes 2 files changed, 3 insertions(+) diff --git a/code/modules/pai/pai.dm b/code/modules/pai/pai.dm index 428bfd33fce41..2f050f11a5fb0 100644 --- a/code/modules/pai/pai.dm +++ b/code/modules/pai/pai.dm @@ -119,6 +119,9 @@ "mouse" = TRUE, "rabbit" = TRUE, "repairbot" = TRUE, + "kitten" = TRUE, + "puppy" = TRUE, + "spider" = TRUE, ) /// List of all available card overlays. var/static/list/possible_overlays = list( diff --git a/icons/mob/silicon/pai.dmi b/icons/mob/silicon/pai.dmi index 2be986d411dbef5035d7eeef957fa3f7bc198906..260b175f3ae3f7f85729ac365183b3bd166ce4c0 100644 GIT binary patch literal 66821 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 X^>}jr=);E)@bAuz2ihf?Rxke#+v)5H literal 64479 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 From 0c562fd74299f8ce92a81c0a932b8ec4862189af Mon Sep 17 00:00:00 2001 From: orange man <61334995+comfyorange@users.noreply.github.com> Date: Mon, 6 May 2024 14:27:45 +1200 Subject: [PATCH 87/87] Automatic changelog for PR #83038 [ci skip] --- html/changelogs/AutoChangeLog-pr-83038.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 html/changelogs/AutoChangeLog-pr-83038.yml diff --git a/html/changelogs/AutoChangeLog-pr-83038.yml b/html/changelogs/AutoChangeLog-pr-83038.yml new file mode 100644 index 0000000000000..fa026276635e7 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-83038.yml @@ -0,0 +1,4 @@ +author: "CandleJaxx" +delete-after: True +changes: + - rscadd: "'puppy' 'kitten' and 'spider' pai skins" \ No newline at end of file