diff --git a/_maps/RandomZLevels/museum.dmm b/_maps/RandomZLevels/museum.dmm index f19ee06ddb56c..7429c0b45382e 100644 --- a/_maps/RandomZLevels/museum.dmm +++ b/_maps/RandomZLevels/museum.dmm @@ -3147,7 +3147,6 @@ /obj/structure/transport/linear/tram/slow, /obj/structure/thermoplastic, /obj/effect/spawner/random/structure/closet_empty/crate/with_loot, -/obj/effect/spawner/random/maintenance/five, /turf/open/chasm/true/no_smooth, /area/awaymission/museum) "Cc" = ( diff --git a/_maps/map_files/Birdshot/birdshot.dmm b/_maps/map_files/Birdshot/birdshot.dmm index 09cd9472764a6..192a6571d8a55 100644 --- a/_maps/map_files/Birdshot/birdshot.dmm +++ b/_maps/map_files/Birdshot/birdshot.dmm @@ -19620,13 +19620,9 @@ /obj/effect/turf_decal/siding/wood{ dir = 9 }, -/obj/structure/rack{ - icon = 'icons/obj/fluff/general.dmi'; - icon_state = "minibar"; - name = "skeletal minibar" - }, /obj/item/storage/fancy/candle_box, /obj/machinery/light_switch/directional/west, +/obj/structure/rack/skeletal, /turf/open/floor/iron/grimy, /area/station/service/library) "hei" = ( diff --git a/_maps/map_files/MetaStation/MetaStation.dmm b/_maps/map_files/MetaStation/MetaStation.dmm index 03dfa234b14be..6b8edaaba1092 100644 --- a/_maps/map_files/MetaStation/MetaStation.dmm +++ b/_maps/map_files/MetaStation/MetaStation.dmm @@ -24819,13 +24819,9 @@ /turf/open/floor/iron/white, /area/station/science/ordnance/storage) "iWB" = ( -/obj/structure/rack{ - icon = 'icons/obj/fluff/general.dmi'; - icon_state = "minibar"; - name = "skeletal minibar" - }, /obj/item/storage/fancy/candle_box, /obj/machinery/light/small/directional/east, +/obj/structure/rack/skeletal, /turf/open/floor/engine/cult, /area/station/service/library) "iWD" = ( @@ -56637,10 +56633,6 @@ "uhI" = ( /obj/structure/cable, /obj/effect/landmark/event_spawn, -/obj/machinery/requests_console/directional/north{ - department = "Security"; - name = "Security Requests Console" - }, /turf/open/floor/iron, /area/station/security/checkpoint/customs) "uhP" = ( diff --git a/_maps/shuttles/emergency_monastery.dmm b/_maps/shuttles/emergency_monastery.dmm index 13236bfabbbab..080f2f2d4e18a 100644 --- a/_maps/shuttles/emergency_monastery.dmm +++ b/_maps/shuttles/emergency_monastery.dmm @@ -2433,16 +2433,12 @@ /turf/open/floor/carpet, /area/shuttle/escape) "Ju" = ( -/obj/structure/rack{ - icon = 'icons/obj/fluff/general.dmi'; - icon_state = "minibar"; - name = "skeletal minibar" - }, /obj/item/book/codex_gigas, /obj/machinery/camera/directional/south{ c_tag = "Monastery Archives Aft"; network = list("ss13","monastery") }, +/obj/structure/rack/skeletal, /turf/open/floor/iron/dark, /area/shuttle/escape) "Jv" = ( diff --git a/_maps/shuttles/pirate_dutchman.dmm b/_maps/shuttles/pirate_dutchman.dmm index 86b85f7b70edc..fb36638173ed1 100644 --- a/_maps/shuttles/pirate_dutchman.dmm +++ b/_maps/shuttles/pirate_dutchman.dmm @@ -464,15 +464,11 @@ /obj/effect/turf_decal/siding/wood{ dir = 8 }, -/obj/structure/rack{ - icon = 'icons/obj/fluff/general.dmi'; - icon_state = "minibar"; - name = "skeletal minibar" - }, /obj/item/food/grown/sugarcane, /obj/item/food/grown/sugarcane, /obj/item/food/grown/sugarcane, /obj/item/reagent_containers/cup/bucket/wooden, +/obj/structure/rack/skeletal, /turf/open/floor/wood/airless, /area/shuttle/pirate/flying_dutchman) "vT" = ( @@ -550,17 +546,13 @@ dir = 10 }, /obj/machinery/light/floor, -/obj/structure/rack{ - icon = 'icons/obj/fluff/general.dmi'; - icon_state = "minibar"; - name = "skeletal minibar" - }, /obj/item/reagent_containers/condiment/milk{ pixel_x = -5 }, /obj/item/reagent_containers/condiment/milk{ pixel_x = 5 }, +/obj/structure/rack/skeletal, /turf/open/floor/wood/airless, /area/shuttle/pirate/flying_dutchman) "zE" = ( diff --git a/_maps/virtual_domains/pirates.dmm b/_maps/virtual_domains/pirates.dmm index f48c9ff7eaea1..1d330adcc4ddf 100644 --- a/_maps/virtual_domains/pirates.dmm +++ b/_maps/virtual_domains/pirates.dmm @@ -474,14 +474,10 @@ /area/virtual_domain/fullbright) "AF" = ( /obj/effect/mapping_helpers/burnt_floor, -/obj/structure/rack{ - icon = 'icons/obj/fluff/general.dmi'; - icon_state = "minibar"; - name = "skeletal minibar" - }, /obj/item/storage/bag/money/dutchmen{ pixel_y = 13 }, +/obj/structure/rack/skeletal, /turf/open/floor/wood/parquet, /area/virtual_domain) "AP" = ( diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm index 1813198b77415..cd1c636c283ca 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm @@ -243,3 +243,6 @@ /// from /mob/proc/slip(): (knockdown_amonut, obj/slipped_on, lube_flags [mobs.dm], paralyze, force_drop) #define COMSIG_MOB_SLIPPED "mob_slipped" + +/// from /mob/proc/key_down(): (key, client/client, full_key) +#define COMSIG_MOB_KEYDOWN "mob_key_down" diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index 5946f76b7a109..ee78e8fcb9fc3 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -341,6 +341,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_DEL_ON_SPACE_DUMP "del_on_hyperspace_leave" /// We can walk up or around cliffs, or at least we don't fall off of it #define TRAIT_CLIFF_WALKER "cliff_walker" +/// This means the user is currently holding/wearing a "tactical camouflage" item (like a potted plant). +#define TRAIT_TACTICALLY_CAMOUFLAGED "tactically_camouflaged" /// Gets double arcade prizes #define TRAIT_GAMERGOD "gamer-god" #define TRAIT_GIANT "giant" @@ -357,6 +359,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_NO_EXTINGUISH "no_extinguish" /// Indicates if the mob is currently speaking with sign language #define TRAIT_SIGN_LANG "sign_language" +/// Trait given to mobs to indicate that they can catch papers thrown at them midair without trying, +/// and make syndicate airplanes when folding paper up. +#define TRAIT_PAPER_MASTER "paper_master" /// This mob is able to use sign language over the radio. #define TRAIT_CAN_SIGN_ON_COMMS "can_sign_on_comms" /// nobody can use martial arts on this mob @@ -634,6 +639,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai /// Used by the honkspam element to avoid spamming the sound. Amusing considering its name. #define TRAIT_HONKSPAMMING "trait_honkspamming" +/// Required by the waddling element since there are multiple sources of it. +#define TRAIT_WADDLING "trait_waddling" ///Used for managing KEEP_TOGETHER in [/atom/var/appearance_flags] #define TRAIT_KEEP_TOGETHER "keep-together" diff --git a/code/__DEFINES/traits/sources.dm b/code/__DEFINES/traits/sources.dm index f009b6ccf0f6c..ae3d7ae24be24 100644 --- a/code/__DEFINES/traits/sources.dm +++ b/code/__DEFINES/traits/sources.dm @@ -41,6 +41,9 @@ /// Trait from light debugging #define LIGHT_DEBUG_TRAIT "light-debug" +/// Trait given by an Action datum +#define ACTION_TRAIT "action" + #define CLOTHING_TRAIT "clothing" #define HELMET_TRAIT "helmet" /// inherited from the mask diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index 4bc72354a8625..64b4b7202c1b6 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -64,6 +64,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_UNIQUE_IMMERSE" = TRAIT_UNIQUE_IMMERSE, "TRAIT_VOIDSTORM_IMMUNE" = TRAIT_VOIDSTORM_IMMUNE, "TRAIT_WAS_RENAMED" = TRAIT_WAS_RENAMED, + "TRAIT_WADDLING" = TRAIT_WADDLING, "TRAIT_WEATHER_IMMUNE" = TRAIT_WEATHER_IMMUNE, "TRAIT_CHASM_STOPPER" = TRAIT_CHASM_STOPPER, ), @@ -399,6 +400,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_SHOCKIMMUNE" = TRAIT_SHOCKIMMUNE, "TRAIT_SHOVE_KNOCKDOWN_BLOCKED" = TRAIT_SHOVE_KNOCKDOWN_BLOCKED, "TRAIT_SIGN_LANG" = TRAIT_SIGN_LANG, + "TRAIT_PAPER_MASTER" = TRAIT_PAPER_MASTER, "TRAIT_SILENT_FOOTSTEPS" = TRAIT_SILENT_FOOTSTEPS, "TRAIT_SIXTHSENSE" = TRAIT_SIXTHSENSE, "TRAIT_SKITTISH" = TRAIT_SKITTISH, @@ -428,6 +430,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_TACKLING_FRAIL_ATTACKER" = TRAIT_TACKLING_FRAIL_ATTACKER, "TRAIT_TACKLING_TAILED_DEFENDER" = TRAIT_TACKLING_TAILED_DEFENDER, "TRAIT_TACKLING_WINGED_ATTACKER" = TRAIT_TACKLING_WINGED_ATTACKER, + "TRAIT_TACTICALLY_CAMOUFLAGED" = TRAIT_TACTICALLY_CAMOUFLAGED, "TRAIT_TAGGER" = TRAIT_TAGGER, "TRAIT_TEMPORARY_BODY" = TRAIT_TEMPORARY_BODY, "TRAIT_TENACIOUS" = TRAIT_TENACIOUS, diff --git a/code/_globalvars/traits/admin_tooling.dm b/code/_globalvars/traits/admin_tooling.dm index b81719bc808c3..84b5bd6d7d7c2 100644 --- a/code/_globalvars/traits/admin_tooling.dm +++ b/code/_globalvars/traits/admin_tooling.dm @@ -188,6 +188,7 @@ GLOBAL_LIST_INIT(admin_visible_traits, list( "TRAIT_SHIFTY_EYES" = TRAIT_SHIFTY_EYES, "TRAIT_SHOCKIMMUNE" = TRAIT_SHOCKIMMUNE, "TRAIT_SIGN_LANG" = TRAIT_SIGN_LANG, + "TRAIT_PAPER_MASTER" = TRAIT_PAPER_MASTER, "TRAIT_SILENT_FOOTSTEPS" = TRAIT_SILENT_FOOTSTEPS, "TRAIT_SIXTHSENSE" = TRAIT_SIXTHSENSE, "TRAIT_SKITTISH" = TRAIT_SKITTISH, diff --git a/code/_onclick/hud/action_button.dm b/code/_onclick/hud/action_button.dm index 533e8f1e8dd48..92defc661016c 100644 --- a/code/_onclick/hud/action_button.dm +++ b/code/_onclick/hud/action_button.dm @@ -18,6 +18,8 @@ /// A weakref of the last thing we hovered over /// God I hate how dragging works var/datum/weakref/last_hovored_ref + /// overlay for keybind maptext + var/mutable_appearance/keybind_maptext /atom/movable/screen/movable/action_button/Destroy() if(our_hud) @@ -48,6 +50,9 @@ return FALSE var/list/modifiers = params2list(params) + if(LAZYACCESS(modifiers, ALT_CLICK)) + begin_creating_bind(usr) + return TRUE if(LAZYACCESS(modifiers, SHIFT_CLICK)) var/datum/hud/our_hud = usr.hud_used our_hud.position_action(src, SCRN_OBJ_DEFAULT) @@ -61,6 +66,14 @@ linked_action.Trigger(trigger_flags = trigger_flags) return TRUE +/atom/movable/screen/movable/action_button/proc/begin_creating_bind(mob/user) + if(!isnull(linked_action.full_key)) + linked_action.full_key = null + linked_action.update_button_status(src) + return + linked_action.full_key = tgui_input_keycombo(user, "Please bind a key for this action.") + linked_action.update_button_status(src) + // Entered and Exited won't fire while you're dragging something, because you're still "holding" it // Very much byond logic, but I want nice behavior, so we fake it with drag /atom/movable/screen/movable/action_button/MouseDrag(atom/over_object, src_location, over_location, src_control, over_control, params) @@ -149,6 +162,15 @@ return user.client.prefs.action_buttons_screen_locs -= "[name]_[id]" +/atom/movable/screen/movable/action_button/proc/update_keybind_maptext(key) + cut_overlay(keybind_maptext) + if(!key) + return + keybind_maptext = new + keybind_maptext.maptext = MAPTEXT("[key]") + keybind_maptext.transform = keybind_maptext.transform.Translate(-4, length(key) > 1 ? -6 : 2) //with modifiers, its placed lower so cooldown is visible + add_overlay(keybind_maptext) + /** * This is a silly proc used in hud code code to determine what icon and icon state we should be using * for hud elements (such as action buttons) that don't have their own icon and icon state set. @@ -241,7 +263,7 @@ action.HideFrom(src) /atom/movable/screen/button_palette - desc = "Drag buttons to move them
Shift-click any button to reset it
Alt-click this to reset all buttons" + desc = "Drag buttons to move them
Shift-click any button to reset it
Alt-click any button to begin binding it to a key
Alt-click this to reset all buttons" icon = 'icons/hud/64x16_actions.dmi' icon_state = "screen_gen_palette" screen_loc = ui_action_palette diff --git a/code/controllers/subsystem/atoms.dm b/code/controllers/subsystem/atoms.dm index 916bbed6e4e1d..71eaed14ea23f 100644 --- a/code/controllers/subsystem/atoms.dm +++ b/code/controllers/subsystem/atoms.dm @@ -1,4 +1,3 @@ -#define SUBSYSTEM_INIT_SOURCE "subsystem init" SUBSYSTEM_DEF(atoms) name = "Atoms" init_order = INIT_ORDER_ATOMS @@ -41,11 +40,16 @@ SUBSYSTEM_DEF(atoms) if(initialized == INITIALIZATION_INSSATOMS) return - set_tracked_initalized(INITIALIZATION_INNEW_MAPLOAD, SUBSYSTEM_INIT_SOURCE) + // Generate a unique mapload source for this run of InitializeAtoms + var/static/uid = 0 + uid = (uid + 1) % (SHORT_REAL_LIMIT - 1) + var/source = "subsystem init [uid]" + set_tracked_initalized(INITIALIZATION_INNEW_MAPLOAD, source) // This may look a bit odd, but if the actual atom creation runtimes for some reason, we absolutely need to set initialized BACK - CreateAtoms(atoms, atoms_to_return) - clear_tracked_initalize(SUBSYSTEM_INIT_SOURCE) + CreateAtoms(atoms, atoms_to_return, source) + clear_tracked_initalize(source) + SSicon_smooth.free_deferred(source) if(late_loaders.len) for(var/I in 1 to late_loaders.len) @@ -72,7 +76,7 @@ SUBSYSTEM_DEF(atoms) #endif /// Actually creates the list of atoms. Exists soley so a runtime in the creation logic doesn't cause initalized to totally break -/datum/controller/subsystem/atoms/proc/CreateAtoms(list/atoms, list/atoms_to_return = null) +/datum/controller/subsystem/atoms/proc/CreateAtoms(list/atoms, list/atoms_to_return = null, mapload_source = null) if (atoms_to_return) LAZYINITLIST(created_atoms) @@ -90,7 +94,12 @@ SUBSYSTEM_DEF(atoms) for(var/I in 1 to atoms.len) var/atom/A = atoms[I] if(!(A.flags_1 & INITIALIZED_1)) - CHECK_TICK + // Unrolled CHECK_TICK setup to let us enable/disable mapload based off source + if(TICK_CHECK) + clear_tracked_initalize(mapload_source) + stoplag() + if(mapload_source) + set_tracked_initalized(INITIALIZATION_INNEW_MAPLOAD, mapload_source) PROFILE_INIT_ATOM_BEGIN() InitAtom(A, TRUE, mapload_arg) PROFILE_INIT_ATOM_END(A) @@ -107,7 +116,11 @@ SUBSYSTEM_DEF(atoms) #ifdef TESTING ++count #endif - CHECK_TICK + if(TICK_CHECK) + clear_tracked_initalize(mapload_source) + stoplag() + if(mapload_source) + set_tracked_initalized(INITIALIZATION_INNEW_MAPLOAD, mapload_source) testing("Initialized [count] atoms") @@ -117,6 +130,13 @@ SUBSYSTEM_DEF(atoms) /datum/controller/subsystem/atoms/proc/map_loader_stop(source) clear_tracked_initalize(source) +/// Returns the source currently modifying SSatom's init behavior +/datum/controller/subsystem/atoms/proc/get_initialized_source() + var/state_length = length(initialized_state) + if(!state_length) + return null + return initialized_state[state_length][1] + /// Use this to set initialized to prevent error states where the old initialized is overriden, and we end up losing all context /// Accepts a state and a source, the most recent state is used, sources exist to prevent overriding old values accidentially /datum/controller/subsystem/atoms/proc/set_tracked_initalized(state, source) @@ -199,5 +219,3 @@ SUBSYSTEM_DEF(atoms) var/initlog = InitLog() if(initlog) text2file(initlog, "[GLOB.log_directory]/initialize.log") - -#undef SUBSYSTEM_INIT_SOURCE diff --git a/code/controllers/subsystem/icon_smooth.dm b/code/controllers/subsystem/icon_smooth.dm index 4d5f3069eca6f..9e5e9ffa99de6 100644 --- a/code/controllers/subsystem/icon_smooth.dm +++ b/code/controllers/subsystem/icon_smooth.dm @@ -9,6 +9,7 @@ SUBSYSTEM_DEF(icon_smooth) var/list/blueprint_queue = list() var/list/smooth_queue = list() var/list/deferred = list() + var/list/deferred_by_source = list() /datum/controller/subsystem/icon_smooth/fire() // We do not want to smooth icons of atoms whose neighbors are not initialized yet, @@ -61,16 +62,30 @@ SUBSYSTEM_DEF(icon_smooth) return SS_INIT_SUCCESS +/// Releases a pool of delayed smooth attempts from a particular source +/datum/controller/subsystem/icon_smooth/proc/free_deferred(source_to_free) + smooth_queue += deferred_by_source[source_to_free] + deferred_by_source -= source_to_free + if(!can_fire) + can_fire = TRUE /datum/controller/subsystem/icon_smooth/proc/add_to_queue(atom/thing) if(thing.smoothing_flags & SMOOTH_QUEUED) return thing.smoothing_flags |= SMOOTH_QUEUED + // If we're currently locked into mapload BY something + // Then put us in a deferred list that we release when this mapload run is finished + if(initialized && length(SSatoms.initialized_state) && SSatoms.initialized == INITIALIZATION_INNEW_MAPLOAD) + var/source = SSatoms.get_initialized_source() + LAZYADD(deferred_by_source[source], thing) + return smooth_queue += thing if(!can_fire) can_fire = TRUE /datum/controller/subsystem/icon_smooth/proc/remove_from_queues(atom/thing) + // Lack of removal from deferred_by_source is safe because the lack of SMOOTH_QUEUED will just free it anyway + // Hopefully this'll never cause a harddel (dies) thing.smoothing_flags &= ~SMOOTH_QUEUED smooth_queue -= thing if(blueprint_queue) diff --git a/code/controllers/subsystem/timer.dm b/code/controllers/subsystem/timer.dm index 80ee843bf8b94..bc894de9beee8 100644 --- a/code/controllers/subsystem/timer.dm +++ b/code/controllers/subsystem/timer.dm @@ -4,8 +4,6 @@ #define BUCKET_POS(timer) (((ROUND_UP((timer.timeToRun - timer.timer_subsystem.head_offset) / world.tick_lag)+1) % BUCKET_LEN) || BUCKET_LEN) /// Gets the maximum time at which timers will be invoked from buckets, used for deferring to secondary queue #define TIMER_MAX(timer_ss) (timer_ss.head_offset + TICKS2DS(BUCKET_LEN + timer_ss.practical_offset - 1)) -/// Max float with integer precision -#define TIMER_ID_MAX (2**24) /** * # Timer Subsystem @@ -731,4 +729,3 @@ SUBSYSTEM_DEF(timer) #undef BUCKET_LEN #undef BUCKET_POS #undef TIMER_MAX -#undef TIMER_ID_MAX diff --git a/code/datums/actions/action.dm b/code/datums/actions/action.dm index 75c9cf59aebf2..39e69ba9fa8fd 100644 --- a/code/datums/actions/action.dm +++ b/code/datums/actions/action.dm @@ -46,6 +46,10 @@ var/overlay_icon = 'icons/mob/actions/backgrounds.dmi' /// This is the icon state for any FOREGROUND overlay icons on the button (such as borders) var/overlay_icon_state + + /// full key we are bound to + var/full_key + /// Toggles whether this action is usable or not var/action_disabled = FALSE @@ -110,6 +114,7 @@ RegisterSignals(owner, list(SIGNAL_ADDTRAIT(TRAIT_MAGICALLY_PHASED), SIGNAL_REMOVETRAIT(TRAIT_MAGICALLY_PHASED)), PROC_REF(update_status_on_signal)) if(owner_has_control) + RegisterSignal(grant_to, COMSIG_MOB_KEYDOWN, PROC_REF(keydown), override = TRUE) GiveAction(grant_to) /// Remove the passed mob from being owner of our action @@ -122,6 +127,7 @@ HideFrom(hud.mymob) LAZYREMOVE(remove_from?.actions, src) // We aren't always properly inserted into the viewers list, gotta make sure that action's cleared viewers = list() + UnregisterSignal(remove_from, COMSIG_MOB_KEYDOWN) if(isnull(owner)) return @@ -312,6 +318,7 @@ * force - whether an update is forced regardless of existing status */ /datum/action/proc/update_button_status(atom/movable/screen/movable/action_button/current_button, force = FALSE) + current_button.update_keybind_maptext(full_key) if(IsAvailable()) current_button.color = rgb(255,255,255,255) else @@ -411,3 +418,14 @@ /// Checks if our action is actively selected. Used for selecting icons primarily. /datum/action/proc/is_action_active(atom/movable/screen/movable/action_button/current_button) return FALSE + +/datum/action/proc/keydown(mob/source, key, client/client, full_key) + SIGNAL_HANDLER + if(isnull(full_key) || full_key != src.full_key) + return + if(istype(source)) + if(source.next_click > world.time) + return + else + source.next_click = world.time + CLICK_CD_RANGE + INVOKE_ASYNC(src, PROC_REF(Trigger)) diff --git a/code/datums/actions/mobs/assume_form.dm b/code/datums/actions/mobs/assume_form.dm index a335d0e745dc5..03a1a38d3c84a 100644 --- a/code/datums/actions/mobs/assume_form.dm +++ b/code/datums/actions/mobs/assume_form.dm @@ -64,7 +64,7 @@ // important: do this at the very end because we might have SIGNAL_ADDTRAIT for this on the mob that's dependent on the above logic SEND_SIGNAL(owner, COMSIG_ACTION_DISGUISED_APPEARANCE, target_atom) - ADD_TRAIT(owner, TRAIT_DISGUISED, REF(src)) + ADD_TRAIT(owner, TRAIT_DISGUISED, ACTION_TRAIT) /// Resets the appearances of the mob to the default. /datum/action/cooldown/mob_cooldown/assume_form/proc/reset_appearances() @@ -85,4 +85,4 @@ owner.cut_overlays() // important: do this very end because we might have SIGNAL_REMOVETRAIT for this on the mob that's dependent on the above logic - REMOVE_TRAIT(owner, TRAIT_DISGUISED, REF(src)) + REMOVE_TRAIT(owner, TRAIT_DISGUISED, ACTION_TRAIT) diff --git a/code/datums/actions/mobs/lava_swoop.dm b/code/datums/actions/mobs/lava_swoop.dm index b86a710fbf227..a6c8282fd10c0 100644 --- a/code/datums/actions/mobs/lava_swoop.dm +++ b/code/datums/actions/mobs/lava_swoop.dm @@ -14,11 +14,11 @@ /datum/action/cooldown/mob_cooldown/lava_swoop/Grant(mob/M) . = ..() - M.add_traits(list(TRAIT_LAVA_IMMUNE, TRAIT_NOFIRE), REF(src)) + M.add_traits(list(TRAIT_LAVA_IMMUNE, TRAIT_NOFIRE), ACTION_TRAIT) /datum/action/cooldown/mob_cooldown/lava_swoop/Remove(mob/M) . = ..() - M.remove_traits(list(TRAIT_LAVA_IMMUNE, TRAIT_NOFIRE), REF(src)) + M.remove_traits(list(TRAIT_LAVA_IMMUNE, TRAIT_NOFIRE), ACTION_TRAIT) /datum/action/cooldown/mob_cooldown/lava_swoop/Activate(atom/target_atom) disable_cooldown_actions() diff --git a/code/datums/actions/mobs/sign_language.dm b/code/datums/actions/mobs/sign_language.dm index da375fd1f8577..20c1157f32652 100644 --- a/code/datums/actions/mobs/sign_language.dm +++ b/code/datums/actions/mobs/sign_language.dm @@ -42,17 +42,17 @@ SIGNAL_ADDTRAIT(TRAIT_MUTE), SIGNAL_REMOVETRAIT(TRAIT_MUTE) )) - REMOVE_TRAIT(grant_to, TRAIT_SIGN_LANG, TRAIT_GENERIC) + REMOVE_TRAIT(grant_to, TRAIT_SIGN_LANG, ACTION_TRAIT) /datum/action/innate/sign_language/Activate() active = TRUE - ADD_TRAIT(owner, TRAIT_SIGN_LANG, TRAIT_GENERIC) + ADD_TRAIT(owner, TRAIT_SIGN_LANG, ACTION_TRAIT) to_chat(owner, span_green("You are now communicating with sign language.")) build_all_button_icons(UPDATE_BUTTON_BACKGROUND) /datum/action/innate/sign_language/Deactivate() active = FALSE - REMOVE_TRAIT(owner, TRAIT_SIGN_LANG, TRAIT_GENERIC) + REMOVE_TRAIT(owner, TRAIT_SIGN_LANG, ACTION_TRAIT) to_chat(owner, span_green("You have stopped using sign language.")) build_all_button_icons(UPDATE_BUTTON_BACKGROUND) diff --git a/code/datums/actions/mobs/sneak.dm b/code/datums/actions/mobs/sneak.dm index 738bb7b70cf5d..521181fa19b15 100644 --- a/code/datums/actions/mobs/sneak.dm +++ b/code/datums/actions/mobs/sneak.dm @@ -16,7 +16,7 @@ /datum/action/cooldown/mob_cooldown/sneak/Remove(mob/living/remove_from) if(HAS_TRAIT(remove_from, TRAIT_SNEAK)) remove_from.alpha = initial(remove_from.alpha) - REMOVE_TRAIT(remove_from, TRAIT_SNEAK, name) + REMOVE_TRAIT(remove_from, TRAIT_SNEAK, ACTION_TRAIT) return ..() @@ -26,11 +26,11 @@ // Otherwise we get permanent invisbility exploits. animate(owner, alpha = initial(owner.alpha), time = animation_time) owner.balloon_alert(owner, "you reveal yourself") - REMOVE_TRAIT(owner, TRAIT_SNEAK, name) + REMOVE_TRAIT(owner, TRAIT_SNEAK, ACTION_TRAIT) else animate(owner, alpha = sneak_alpha, time = animation_time) owner.balloon_alert(owner, "you blend into the environment") - ADD_TRAIT(owner, TRAIT_SNEAK, name) + ADD_TRAIT(owner, TRAIT_SNEAK, ACTION_TRAIT) return TRUE diff --git a/code/datums/ai/objects/vending_machines/vending_machine_controller.dm b/code/datums/ai/objects/vending_machines/vending_machine_controller.dm index cd779b7d691f0..50523db946ca6 100644 --- a/code/datums/ai/objects/vending_machines/vending_machine_controller.dm +++ b/code/datums/ai/objects/vending_machines/vending_machine_controller.dm @@ -14,7 +14,7 @@ return AI_CONTROLLER_INCOMPATIBLE var/obj/machinery/vending/vendor_pawn = new_pawn vendor_pawn.tiltable = FALSE //Not manually tiltable by hitting it anymore. We are now aggressively doing it ourselves. - vendor_pawn.AddElement(/datum/element/waddling) + vendor_pawn.AddElementTrait(TRAIT_WADDLING, REF(src), /datum/element/waddling) vendor_pawn.AddElement(/datum/element/footstep, FOOTSTEP_OBJ_MACHINE, 1, -6, sound_vary = TRUE) vendor_pawn.squish_damage = 15 return ..() //Run parent at end @@ -22,7 +22,7 @@ /datum/ai_controller/vending_machine/UnpossessPawn(destroy) var/obj/machinery/vending/vendor_pawn = pawn vendor_pawn.tiltable = TRUE - vendor_pawn.RemoveElement(/datum/element/waddling) + REMOVE_TRAIT(vendor_pawn, TRAIT_WADDLING, REF(src)) vendor_pawn.squish_damage = initial(vendor_pawn.squish_damage) RemoveElement(/datum/element/footstep, FOOTSTEP_OBJ_MACHINE, 1, -6, sound_vary = TRUE) return ..() //Run parent at end diff --git a/code/datums/components/tactical.dm b/code/datums/components/tactical.dm index e8e5492694928..e0f131258e732 100644 --- a/code/datums/components/tactical.dm +++ b/code/datums/components/tactical.dm @@ -1,5 +1,8 @@ +///A simple component that replacess the user's appearance with that of the parent item when equipped. /datum/component/tactical + ///The allowed slot(s) for the effect. var/allowed_slot + ///A cached of where the item is currently equipped. var/current_slot /datum/component/tactical/Initialize(allowed_slot) @@ -11,50 +14,63 @@ /datum/component/tactical/RegisterWithParent() RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, PROC_REF(modify)) RegisterSignal(parent, COMSIG_ITEM_DROPPED, PROC_REF(unmodify)) + RegisterSignal(parent, COMSIG_ATOM_UPDATED_ICON, PROC_REF(tactical_update)) + var/obj/item/item = parent + if(ismob(item.loc)) + var/mob/holder = item.loc + modify(item, holder, holder.get_slot_by_item(item)) /datum/component/tactical/UnregisterFromParent() - UnregisterSignal(parent, list(COMSIG_ITEM_EQUIPPED, COMSIG_ITEM_DROPPED)) + UnregisterSignal(parent, list( + COMSIG_ITEM_EQUIPPED, + COMSIG_ITEM_DROPPED, + COMSIG_ATOM_UPDATED_ICON, + )) unmodify() /datum/component/tactical/Destroy() unmodify() return ..() -/datum/component/tactical/proc/on_z_move(datum/source) - SIGNAL_HANDLER - var/obj/item/master = parent - if(!ismob(master.loc)) - return - var/old_slot = current_slot - unmodify(master, master.loc) - modify(master, master.loc, old_slot) - /datum/component/tactical/proc/modify(obj/item/source, mob/user, slot) SIGNAL_HANDLER if(allowed_slot && !(slot & allowed_slot)) - unmodify() + if(current_slot) + unmodify(source, user) return + if(current_slot) //If the current slot is set, this means the icon was updated or the item changed z-levels. + user.remove_alt_appearance("sneaking_mission[REF(src)]") + else + RegisterSignal(parent, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(tactical_update)) + current_slot = slot var/obj/item/master = parent - var/image/I = image(icon = master.icon, icon_state = master.icon_state, loc = user) - I.copy_overlays(master) - I.override = TRUE - source.add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/everyone, "sneaking_mission", I) - I.layer = ABOVE_MOB_LAYER - RegisterSignal(parent, COMSIG_MOVABLE_Z_CHANGED, PROC_REF(on_z_move)) + var/image/image = image(master, loc = user) + image.copy_overlays(master) + image.override = TRUE + image.layer = ABOVE_MOB_LAYER + image.plane = FLOAT_PLANE + source.add_alt_appearance(/datum/atom_hud/alternate_appearance/basic/everyone, "sneaking_mission[REF(src)]", image) /datum/component/tactical/proc/unmodify(obj/item/source, mob/user) SIGNAL_HANDLER - var/obj/item/master = source || parent + var/obj/item/master = parent if(!user) if(!ismob(master.loc)) return user = master.loc - user.remove_alt_appearance("sneaking_mission") + user.remove_alt_appearance("sneaking_mission[REF(src)]") current_slot = null UnregisterSignal(parent, COMSIG_MOVABLE_Z_CHANGED) + +/datum/component/tactical/proc/tactical_update(datum/source) + SIGNAL_HANDLER + var/obj/item/master = parent + if(!ismob(master.loc)) + return + modify(master, master.loc, current_slot) diff --git a/code/datums/elements/_element.dm b/code/datums/elements/_element.dm index bcafc83497cfc..b2e021ad2be17 100644 --- a/code/datums/elements/_element.dm +++ b/code/datums/elements/_element.dm @@ -28,7 +28,7 @@ if(element_flags & ELEMENT_DETACH_ON_HOST_DESTROY) RegisterSignal(target, COMSIG_QDELETING, PROC_REF(OnTargetDelete), override = TRUE) -/datum/element/proc/OnTargetDelete(datum/source, force) +/datum/element/proc/OnTargetDelete(datum/source) SIGNAL_HANDLER Detach(source) @@ -75,3 +75,29 @@ ele.Detach(arglist(arguments)) else ele.Detach(src) + +/** + * Used to manage (typically non_bespoke) elements with multiple sources through traits + * so we don't have to make them a components again. + * The element will be later removed once all trait sources are gone, there's no need of a + * "RemoveElementTrait" counterpart. + */ +/datum/proc/AddElementTrait(trait, source, datum/element/eletype, ...) + if(!ispath(eletype, /datum/element)) + CRASH("AddElementTrait called, but [eletype] is not of a /datum/element path") + ADD_TRAIT(src, trait, source) + if(HAS_TRAIT_NOT_FROM(src, trait, source)) + return + var/list/arguments = list(eletype) + /// 3 is the length of fixed args of this proc, any further one is passed down to AddElement. + if(length(args) > 3) + arguments += args.Copy(4) + /// We actually pass down a copy of the arguments since it's manipulated by the end of the proc. + _AddElement(arguments.Copy()) + var/datum/ele = SSdcs.GetElement(arguments) + ele.RegisterSignal(src, SIGNAL_REMOVETRAIT(trait), TYPE_PROC_REF(/datum/element, _detach_on_trait_removed)) + +/datum/element/proc/_detach_on_trait_removed(datum/source, trait) + SIGNAL_HANDLER + Detach(source) + UnregisterSignal(source, SIGNAL_REMOVETRAIT(trait)) diff --git a/code/datums/elements/waddling.dm b/code/datums/elements/waddling.dm index c51a1759768b5..e63d0329bb630 100644 --- a/code/datums/elements/waddling.dm +++ b/code/datums/elements/waddling.dm @@ -4,6 +4,8 @@ . = ..() if(!ismovable(target)) return ELEMENT_INCOMPATIBLE + if(!HAS_TRAIT(target, TRAIT_WADDLING)) + stack_trace("[type] added to [target] without adding TRAIT_WADDLING first. Please use AddElementTrait instead.") RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(Waddle)) /datum/element/waddling/Detach(datum/source) diff --git a/code/datums/spawners_menu.dm b/code/datums/spawners_menu.dm index 06b6dbc76a857..c6b64cc3a7ad3 100644 --- a/code/datums/spawners_menu.dm +++ b/code/datums/spawners_menu.dm @@ -30,9 +30,9 @@ this["important_warning"] = "" this["amount_left"] = 0 for(var/spawner_obj in GLOB.mob_spawners[spawner]) + var/obj/effect/mob_spawn/ghost_role/mob_spawner = spawner_obj if(!this["desc"]) if(istype(spawner_obj, /obj/effect/mob_spawn)) - var/obj/effect/mob_spawn/ghost_role/mob_spawner = spawner_obj if(!mob_spawner.allow_spawn(user, silent = TRUE)) continue this["you_are_text"] = mob_spawner.you_are_text @@ -41,8 +41,9 @@ else var/obj/object = spawner_obj this["desc"] = object.desc - this["amount_left"] += 1 - if(this["amount_left"] > 0) + this["amount_left"] += mob_spawner.uses + this["infinite"] += mob_spawner.infinite_use + if(this["amount_left"] > 0 || this["infinite"]) data["spawners"] += list(this) for(var/mob_type in GLOB.joinable_mobs) var/list/this = list() diff --git a/code/game/objects/items/cardboard_cutouts.dm b/code/game/objects/items/cardboard_cutouts.dm index f58ea90211d1e..97f9c29491eeb 100644 --- a/code/game/objects/items/cardboard_cutouts.dm +++ b/code/game/objects/items/cardboard_cutouts.dm @@ -14,11 +14,19 @@ var/deceptive = FALSE /// What cutout datum we spawn at the start? Uses the name, not the path. var/starting_cutout + /// Reference to the tactical component that should be deleted when the cutout is toppled. + var/datum/component/tactical/tacticool /obj/item/cardboard_cutout/Initialize(mapload) . = ..() if(starting_cutout) return INITIALIZE_HINT_LATELOAD + if(!pushed_over) + AddComponent(/datum/component/tactical) + +/obj/item/cardboard_cutout/Destroy() + tacticool = null + return ..() /obj/item/cardboard_cutout/LateInitialize() ASSERT(!isnull(starting_cutout)) @@ -33,6 +41,8 @@ ASSERT(!isnull(cutout), "No cutout found with name [starting_cutout]") cutout.apply(src) + if(!pushed_over) + tacticool = AddComponent(/datum/component/tactical) //ATTACK HAND IGNORING PARENT RETURN VALUE /obj/item/cardboard_cutout/attack_hand(mob/living/user, list/modifiers) @@ -42,12 +52,22 @@ playsound(src, 'sound/weapons/genhit.ogg', 50, TRUE) push_over() +/obj/item/cardboard_cutout/equipped(mob/living/user, slot) + . = ..() + //Because of the tactical element, the user won't tilt left and right, but it'll still hop. + user.AddElementTrait(TRAIT_WADDLING, REF(src), /datum/element/waddling) + +/obj/item/cardboard_cutout/dropped(mob/living/user) + . = ..() + REMOVE_TRAIT(user, TRAIT_WADDLING, REF(src)) + /obj/item/cardboard_cutout/proc/push_over() appearance = initial(appearance) desc = "[initial(desc)] It's been pushed over." icon_state = "cutout_pushed_over" remove_atom_colour(FIXED_COLOUR_PRIORITY) pushed_over = TRUE + QDEL_NULL(tacticool) /obj/item/cardboard_cutout/attack_self(mob/living/user) if(!pushed_over) @@ -57,6 +77,7 @@ icon = initial(icon) icon_state = initial(icon_state) //This resets a cutout to its blank state - this is intentional to allow for resetting pushed_over = FALSE + tacticool = AddComponent(/datum/component/tactical) /obj/item/cardboard_cutout/attackby(obj/item/I, mob/living/user, params) if(istype(I, /obj/item/toy/crayon)) @@ -100,7 +121,7 @@ for (var/datum/cardboard_cutout/cutout_subtype as anything in subtypesof(/datum/cardboard_cutout)) var/datum/cardboard_cutout/cutout = get_cardboard_cutout_instance(cutout_subtype) appearances_by_name[cutout.name] = cutout - possible_appearances[cutout.name] = image(icon = cutout.applied_appearance) + possible_appearances[cutout.name] = image(icon = cutout.preview_appearance) var/new_appearance = show_radial_menu(user, src, possible_appearances, custom_check = CALLBACK(src, PROC_REF(check_menu), user, crayon), radius = 36, require_near = TRUE) if(!new_appearance) @@ -144,19 +165,16 @@ return FALSE return TRUE -// Cutouts always face forward -/obj/item/cardboard_cutout/setDir(newdir) - SHOULD_CALL_PARENT(FALSE) - return - /obj/item/cardboard_cutout/adaptive //Purchased by Syndicate agents, these cutouts are indistinguishable from normal cutouts but aren't discolored when their appearance is changed deceptive = TRUE /datum/cardboard_cutout /// Name of the cutout, used for radial selection and the global list. var/name = "Boardjak" - /// The appearance we apply to the cardboard cutout. - var/mutable_appearance/applied_appearance = null + /// The appearance of the cardboard cutout that we show in the radial menu. + var/mutable_appearance/preview_appearance + /// A flat appearance, with only one direction, that we apply to the cardboard cutout. + var/image/applied_appearance /// The base name we actually give to to the cardboard cutout. Can be overridden in get_name(). var/applied_name = "boardjak" /// The desc we give to the cardboard cutout. @@ -179,9 +197,9 @@ /datum/cardboard_cutout/New() . = ..() if(direct_icon) - applied_appearance = mutable_appearance(direct_icon, direct_icon_state) + preview_appearance = mutable_appearance(direct_icon, direct_icon_state) else - applied_appearance = get_dynamic_human_appearance(outfit, species, mob_spawner, l_hand, r_hand, animated = FALSE) + preview_appearance = get_dynamic_human_appearance(outfit, species, mob_spawner, l_hand, r_hand, animated = FALSE) /// This proc returns the name that the cardboard cutout item will use. /datum/cardboard_cutout/proc/get_name() @@ -189,9 +207,14 @@ /// This proc sets the cardboard cutout item's vars. /datum/cardboard_cutout/proc/apply(obj/item/cardboard_cutout/cutouts) + if(isnull(applied_appearance)) + applied_appearance = image(fcopy_rsc(getFlatIcon(preview_appearance, no_anim = TRUE))) + applied_appearance.plane = cutouts.plane + applied_appearance.layer = cutouts.layer cutouts.appearance = applied_appearance cutouts.name = get_name() cutouts.desc = applied_desc + cutouts.update_appearance() //forces an update on the tactical comp's appearance. /datum/cardboard_cutout/assistant name = "Assistant" diff --git a/code/game/objects/items/granters/oragami.dm b/code/game/objects/items/granters/oragami.dm index 0b7d6d9261570..0691349756cf9 100644 --- a/code/game/objects/items/granters/oragami.dm +++ b/code/game/objects/items/granters/oragami.dm @@ -21,11 +21,13 @@ check_flags = NONE /datum/action/innate/origami/Activate() + ADD_TRAIT(owner, TRAIT_PAPER_MASTER, ACTION_TRAIT) to_chat(owner, span_notice("You will now fold origami planes.")) active = TRUE build_all_button_icons(UPDATE_BUTTON_ICON) /datum/action/innate/origami/Deactivate() + REMOVE_TRAIT(owner, TRAIT_PAPER_MASTER, ACTION_TRAIT) to_chat(owner, span_notice("You will no longer fold origami planes.")) active = FALSE build_all_button_icons(UPDATE_BUTTON_ICON) diff --git a/code/game/objects/items/knives.dm b/code/game/objects/items/knives.dm index 315862d2d79ff..8b4808848c245 100644 --- a/code/game/objects/items/knives.dm +++ b/code/game/objects/items/knives.dm @@ -144,7 +144,6 @@ user.apply_damage(5, BRUTE, BODY_ZONE_HEAD) playsound(user, 'sound/weapons/slice.ogg', 50, TRUE) user.visible_message(span_danger("[user] accidentally cuts [user.p_them()]self while pulling [src] out of [user.p_them()] teeth! What a doofus!"), span_userdanger("You accidentally cut your mouth with [src]!")) - . = ..() /obj/item/knife/combat/equipped(mob/living/user, slot, initial = FALSE) . = ..() diff --git a/code/game/objects/items/stacks/golem_food/golem_status_effects.dm b/code/game/objects/items/stacks/golem_food/golem_status_effects.dm index 31600b03354c7..514ab36ed66d3 100644 --- a/code/game/objects/items/stacks/golem_food/golem_status_effects.dm +++ b/code/game/objects/items/stacks/golem_food/golem_status_effects.dm @@ -404,7 +404,7 @@ . = ..() if (!.) return - owner.AddElement(/datum/element/waddling) + owner.AddElementTrait(TRAIT_WADDLING, TRAIT_STATUS_EFFECT(id), /datum/element/waddling) ADD_TRAIT(owner, TRAIT_NO_SLIP_WATER, TRAIT_STATUS_EFFECT(id)) slipperiness = owner.AddComponent(\ /datum/component/slippery,\ @@ -418,8 +418,7 @@ return owner.body_position == LYING_DOWN /datum/status_effect/golem/bananium/on_remove() - REMOVE_TRAIT(owner, TRAIT_NO_SLIP_WATER, TRAIT_STATUS_EFFECT(id)) - owner.RemoveElement(/datum/element/waddling) + owner.remove_traits(owner, list(TRAIT_WADDLING, TRAIT_NO_SLIP_WATER), TRAIT_STATUS_EFFECT(id)) QDEL_NULL(slipperiness) return ..() diff --git a/code/game/objects/items/tanks/tanks.dm b/code/game/objects/items/tanks/tanks.dm index 6c11c43550718..a670a966805e7 100644 --- a/code/game/objects/items/tanks/tanks.dm +++ b/code/game/objects/items/tanks/tanks.dm @@ -440,6 +440,10 @@ if(LAZYLEN(assembly.assemblies) == igniter_count) return + + if(isitem(loc)) // we are in a storage item + balloon_alert(user, "can't reach!") + return if((src in user.get_equipped_items(include_pockets = TRUE, include_accessories = TRUE)) && !user.canUnEquip(src)) balloon_alert(user, "it's stuck!") @@ -456,6 +460,7 @@ tank_assembly = assembly //Tell the tank about its assembly part assembly.master = src //Tell the assembly about its new owner assembly.on_attach() + w_class = WEIGHT_CLASS_BULKY balloon_alert(user, "bomb assembled") update_appearance(UPDATE_OVERLAYS) @@ -469,6 +474,7 @@ user.put_in_hands(tank_assembly) tank_assembly.master = null tank_assembly = null + w_class = initial(w_class) update_appearance(UPDATE_OVERLAYS) /// Ignites the contents of the tank. Called when receiving a signal if the tank is welded and has an igniter attached. diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm index a0b8f9d79dcef..debd4d81bc367 100644 --- a/code/game/objects/structures/tables_racks.dm +++ b/code/game/objects/structures/tables_racks.dm @@ -825,6 +825,12 @@ pass_flags_self = LETPASSTHROW //You can throw objects over this, despite it's density. max_integrity = 20 +/obj/structure/rack/skeletal + name = "skeletal minibar" + desc = "Rattle me boozes!" + icon = 'icons/obj/fluff/general.dmi' + icon_state = "minibar" + /obj/structure/rack/Initialize(mapload) . = ..() AddElement(/datum/element/climbable) diff --git a/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer.dm b/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer.dm index 44695b6104730..989156867491f 100644 --- a/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer.dm +++ b/code/modules/atmospherics/machinery/components/gas_recipe_machines/crystallizer.dm @@ -55,7 +55,7 @@ if(TOOL_SCREWDRIVER) context[SCREENTIP_CONTEXT_LMB] = "[panel_open ? "Close" : "Open"] panel" if(TOOL_WRENCH) - context[SCREENTIP_CONTEXT_LMB] = "Rotate" + context[SCREENTIP_CONTEXT_RMB] = "Rotate" return CONTEXTUAL_SCREENTIP_SET /obj/machinery/atmospherics/components/binary/crystallizer/attackby(obj/item/I, mob/user, params) diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm index 96ad34b315868..3d930ce898904 100644 --- a/code/modules/client/preferences_savefile.dm +++ b/code/modules/client/preferences_savefile.dm @@ -213,7 +213,7 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car //Sanitize lastchangelog = sanitize_text(lastchangelog, initial(lastchangelog)) default_slot = sanitize_integer(default_slot, 1, max_save_slots, initial(default_slot)) - toggles = sanitize_integer(toggles, 0, (2**24)-1, initial(toggles)) + toggles = sanitize_integer(toggles, 0, SHORT_REAL_LIMIT-1, initial(toggles)) be_special = sanitize_be_special(SANITIZE_LIST(be_special)) key_bindings = sanitize_keybindings(key_bindings) favorite_outfits = SANITIZE_LIST(favorite_outfits) diff --git a/code/modules/clothing/shoes/clown.dm b/code/modules/clothing/shoes/clown.dm index dea346b8613b2..aff47fde7fabe 100644 --- a/code/modules/clothing/shoes/clown.dm +++ b/code/modules/clothing/shoes/clown.dm @@ -20,13 +20,13 @@ . = ..() if(slot & ITEM_SLOT_FEET) if(enabled_waddle) - user.AddElement(/datum/element/waddling) + user.AddElementTrait(TRAIT_WADDLING, SHOES_TRAIT, /datum/element/waddling) if(is_clown_job(user.mind?.assigned_role)) user.add_mood_event("clownshoes", /datum/mood_event/clownshoes) /obj/item/clothing/shoes/clown_shoes/dropped(mob/living/user) . = ..() - user.RemoveElement(/datum/element/waddling) + REMOVE_TRAIT(user, TRAIT_WADDLING, SHOES_TRAIT) if(is_clown_job(user.mind?.assigned_role)) user.clear_mood_event("clownshoes") diff --git a/code/modules/clothing/shoes/costume.dm b/code/modules/clothing/shoes/costume.dm index 1a3e9b0b2beb7..1e15f25e1f630 100644 --- a/code/modules/clothing/shoes/costume.dm +++ b/code/modules/clothing/shoes/costume.dm @@ -128,8 +128,8 @@ /obj/item/clothing/shoes/ducky_shoes/equipped(mob/living/user, slot) . = ..() if(slot & ITEM_SLOT_FEET) - user.AddElement(/datum/element/waddling) + user.AddElementTrait(TRAIT_WADDLING, SHOES_TRAIT, /datum/element/waddling) /obj/item/clothing/shoes/ducky_shoes/dropped(mob/living/user) . = ..() - user.RemoveElement(/datum/element/waddling) + REMOVE_TRAIT(user, TRAIT_WADDLING, SHOES_TRAIT) diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_martian.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_martian.dm index fb4f29284b87b..02bb9ae7bf82c 100644 --- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_martian.dm +++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_martian.dm @@ -168,7 +168,7 @@ /obj/item/food/onion_slice = 1, /datum/reagent/consumable/nutriment/soup/teriyaki = 4, ) - result = /obj/item/food/salad/bibimbap + result = /obj/item/food/salad/bulgogi_noodles category = CAT_MARTIAN /datum/crafting_recipe/food/yakisoba_katsu diff --git a/code/modules/keybindings/bindings_atom.dm b/code/modules/keybindings/bindings_atom.dm index e9e38489a4df3..e99e3714c6b60 100644 --- a/code/modules/keybindings/bindings_atom.dm +++ b/code/modules/keybindings/bindings_atom.dm @@ -11,6 +11,9 @@ // If we're not movin anywhere, we aren't movin anywhere // Safe because nothing adds to movement_dir after this moment if(!movement_dir) + // No input == our removal would have done nothing + // So we can safely forget about it + user.next_move_dir_sub = NONE return FALSE if(user.next_move_dir_sub) diff --git a/code/modules/keybindings/bindings_client.dm b/code/modules/keybindings/bindings_client.dm index 0a8cc20b38fe5..4a72fb9123da8 100644 --- a/code/modules/keybindings/bindings_client.dm +++ b/code/modules/keybindings/bindings_client.dm @@ -75,11 +75,10 @@ if(kb.can_use(src) && kb.down(src) && keycount >= MAX_COMMANDS_PER_KEY) break - holder?.key_down(_key, src) - mob.focus?.key_down(_key, src) + holder?.key_down(_key, src, full_key) + mob.focus?.key_down(_key, src, full_key) mob.update_mouse_pointer() - /client/verb/keyUp(_key as text) set instant = TRUE set hidden = TRUE diff --git a/code/modules/keybindings/setup.dm b/code/modules/keybindings/setup.dm index ef87e12d90103..d239c48d9ceee 100644 --- a/code/modules/keybindings/setup.dm +++ b/code/modules/keybindings/setup.dm @@ -1,6 +1,7 @@ // Set a client's focus to an object and override these procs on that object to let it handle keypresses -/datum/proc/key_down(key, client/user) // Called when a key is pressed down initially +/datum/proc/key_down(key, client/user, full_key) // Called when a key is pressed down initially + SHOULD_CALL_PARENT(TRUE) return /datum/proc/key_up(key, client/user) // Called when a key is released return diff --git a/code/modules/mob/living/basic/clown/clown.dm b/code/modules/mob/living/basic/clown/clown.dm index 1a713ec04f884..88c2b9496a929 100644 --- a/code/modules/mob/living/basic/clown/clown.dm +++ b/code/modules/mob/living/basic/clown/clown.dm @@ -49,7 +49,7 @@ ai_controller.set_blackboard_key(BB_BASIC_MOB_SPEAK_LINES, emotes) //im not putting dynamic humans or whatever its called here because this is the base path of nonhuman clownstrosities if(waddles) - AddElement(/datum/element/waddling) + AddElementTrait(TRAIT_WADDLING, INNATE_TRAIT, /datum/element/waddling) if(length(loot)) loot = string_list(loot) AddElement(/datum/element/death_drops, loot) diff --git a/code/modules/mob/living/basic/farm_animals/pony.dm b/code/modules/mob/living/basic/farm_animals/pony.dm index df8f3a1fd4eec..7795ec630e6d5 100644 --- a/code/modules/mob/living/basic/farm_animals/pony.dm +++ b/code/modules/mob/living/basic/farm_animals/pony.dm @@ -40,7 +40,7 @@ AddElement(/datum/element/pet_bonus, "whickers.") AddElement(/datum/element/ai_retaliate) AddElement(/datum/element/ai_flee_while_injured) - AddElement(/datum/element/waddling) + AddElementTrait(TRAIT_WADDLING, INNATE_TRAIT, /datum/element/waddling) AddComponent(/datum/component/tameable, food_types = list(/obj/item/food/grown/apple), tame_chance = 25, bonus_tame_chance = 15, after_tame = CALLBACK(src, PROC_REF(tamed)), unique = unique_tamer) /mob/living/basic/pony/proc/tamed(mob/living/tamer) diff --git a/code/modules/mob/living/basic/pets/penguin.dm b/code/modules/mob/living/basic/pets/penguin.dm index 671c2cf30c130..e8e2a038c0ea9 100644 --- a/code/modules/mob/living/basic/pets/penguin.dm +++ b/code/modules/mob/living/basic/pets/penguin.dm @@ -23,7 +23,7 @@ AddElement(/datum/element/ai_retaliate) AddElement(/datum/element/ai_flee_while_injured) AddElement(/datum/element/pet_bonus, "honks happily!") - AddElement(/datum/element/waddling) + AddElementTrait(TRAIT_WADDLING, INNATE_TRAIT, /datum/element/waddling) if(!can_lay_eggs) return AddComponent(\ diff --git a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.dm b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.dm index 3602d5e8a991c..be83d3e058f91 100644 --- a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.dm +++ b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat.dm @@ -52,7 +52,7 @@ RegisterSignal(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(pre_attack)) RegisterSignal(src, COMSIG_MOB_LOGIN, PROC_REF(on_login)) - AddElement(/datum/element/waddling) + AddElementTrait(TRAIT_WADDLING, INNATE_TRAIT, /datum/element/waddling) AddElement(/datum/element/ai_retaliate) AddElement(/datum/element/door_pryer, pry_time = 5 SECONDS, interaction_key = REGALRAT_INTERACTION) AddComponent(\ diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 0301b59e8a0a7..0085d0b2fb693 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -1647,3 +1647,7 @@ set name = "View Skills" mind?.print_levels(src) + +/mob/key_down(key, client/client, full_key) + ..() + SEND_SIGNAL(src, COMSIG_MOB_KEYDOWN, key, client, full_key) diff --git a/code/modules/mob/mob_defines.dm b/code/modules/mob/mob_defines.dm index 245afb869323f..aaf654e6dba2b 100644 --- a/code/modules/mob/mob_defines.dm +++ b/code/modules/mob/mob_defines.dm @@ -200,4 +200,3 @@ var/active_typing_indicator ///the icon currently used for the thinking indicator's bubble var/active_thinking_indicator - diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm index c54d0fd7c33d0..2403ca2da53c3 100644 --- a/code/modules/mob/mob_movement.dm +++ b/code/modules/mob/mob_movement.dm @@ -47,8 +47,8 @@ /client/Move(new_loc, direct) if(world.time < move_delay) //do not move anything ahead of this check please return FALSE - next_move_dir_add = 0 - next_move_dir_sub = 0 + next_move_dir_add = NONE + next_move_dir_sub = NONE var/old_move_delay = move_delay move_delay = world.time + world.tick_lag //this is here because Move() can now be called mutiple times per tick if(!direct || !new_loc) diff --git a/code/modules/mod/modules/modules_service.dm b/code/modules/mod/modules/modules_service.dm index 5381a26e88bd4..e6e4a01c66475 100644 --- a/code/modules/mod/modules/modules_service.dm +++ b/code/modules/mod/modules/modules_service.dm @@ -70,13 +70,13 @@ /obj/item/mod/module/waddle/on_suit_activation() mod.boots.AddComponent(/datum/component/squeak, list('sound/effects/footstep/clownstep1.ogg'=1,'sound/effects/footstep/clownstep2.ogg'=1), 50, falloff_exponent = 20) //die off quick please - mod.wearer.AddElement(/datum/element/waddling) + mod.wearer.AddElementTrait(TRAIT_WADDLING, MOD_TRAIT, /datum/element/waddling) if(is_clown_job(mod.wearer.mind?.assigned_role)) mod.wearer.add_mood_event("clownshoes", /datum/mood_event/clownshoes) /obj/item/mod/module/waddle/on_suit_deactivation(deleting = FALSE) if(!deleting) qdel(mod.boots.GetComponent(/datum/component/squeak)) - mod.wearer.RemoveElement(/datum/element/waddling) + REMOVE_TRAIT(mod.wearer, TRAIT_WADDLING, MOD_TRAIT) if(is_clown_job(mod.wearer.mind?.assigned_role)) mod.wearer.clear_mood_event("clownshoes") diff --git a/code/modules/modular_computers/file_system/programs/radar.dm b/code/modules/modular_computers/file_system/programs/radar.dm index b506777f3de7a..34771cde63b20 100644 --- a/code/modules/modular_computers/file_system/programs/radar.dm +++ b/code/modules/modular_computers/file_system/programs/radar.dm @@ -149,16 +149,13 @@ return RADAR_NOT_TRACKABLE var/turf/here = get_turf(computer) var/turf/there = get_turf(signal) - if(!here || !there) - return RADAR_NOT_TRACKABLE //I was still getting a runtime even after the above check while scanning, so fuck it - if(there.z != here.z && (!is_station_level(here.z) || !is_station_level(there.z))) + if(isnull(here) || isnull(there) || !is_valid_z_level(here, there)) return RADAR_NOT_TRACKABLE var/trackable_signal = SEND_SIGNAL(computer, COMSIG_MODULAR_COMPUTER_RADAR_TRACKABLE, signal, here, there) - switch(trackable_signal) - if(COMPONENT_RADAR_TRACK_ANYWAY) - return RADAR_TRACKABLE_ANYWAY - if(COMPONENT_RADAR_DONT_TRACK) - return RADAR_NOT_TRACKABLE + if(trackable_signal & COMPONENT_RADAR_TRACK_ANYWAY) + return RADAR_TRACKABLE_ANYWAY + if(trackable_signal & COMPONENT_RADAR_DONT_TRACK) + return RADAR_NOT_TRACKABLE return RADAR_TRACKABLE /** @@ -268,15 +265,16 @@ /datum/computer_file/program/radar/lifeline/trackable(mob/living/carbon/human/humanoid) . = ..() - if(. == RADAR_TRACKABLE_ANYWAY) - return RADAR_TRACKABLE_ANYWAY - if(!humanoid || !istype(humanoid)) + if(. != RADAR_TRACKABLE) + return . + if(!istype(humanoid)) return RADAR_NOT_TRACKABLE if(!istype(humanoid.w_uniform, /obj/item/clothing/under)) return RADAR_NOT_TRACKABLE var/obj/item/clothing/under/uniform = humanoid.w_uniform - if(uniform.has_sensor && uniform.sensor_mode >= SENSOR_COORDS) // Suit sensors must be on maximum - return RADAR_TRACKABLE + if(!uniform.has_sensor || uniform.sensor_mode < SENSOR_COORDS) // Suit sensors must be on maximum + return RADAR_NOT_TRACKABLE + return . ///Tracks all janitor equipment /datum/computer_file/program/radar/custodial_locator diff --git a/code/modules/paperwork/carbonpaper.dm b/code/modules/paperwork/carbonpaper.dm index b2f985dc357fd..1dfe4ea782160 100644 --- a/code/modules/paperwork/carbonpaper.dm +++ b/code/modules/paperwork/carbonpaper.dm @@ -20,6 +20,12 @@ return . += span_notice("Right-click to tear off the carbon-copy (you must use both hands).") +/obj/item/paper/carbon/AltClick(mob/living/user) + if(!copied) + to_chat(user, span_notice("Take off the carbon copy first.")) + return + return ..() + /obj/item/paper/carbon/proc/removecopy(mob/living/user) if(copied) to_chat(user, span_notice("There are no more carbon copies attached to this paper!")) diff --git a/code/modules/paperwork/paper.dm b/code/modules/paperwork/paper.dm index 82517f72494ca..1c1daaad51140 100644 --- a/code/modules/paperwork/paper.dm +++ b/code/modules/paperwork/paper.dm @@ -319,6 +319,7 @@ /obj/item/paper/examine(mob/user) . = ..() + . += span_notice("Alt-click [src] to fold it into a paper plane.") if(!in_range(user, src) && !isobserver(user)) . += span_warning("You're too far away to read it!") return @@ -358,6 +359,31 @@ return TRUE return ..() +/obj/item/paper/AltClick(mob/living/user) + . = ..() + if(!user.can_perform_action(src, NEED_DEXTERITY|NEED_HANDS)) + return + if(HAS_TRAIT(user, TRAIT_PAPER_MASTER)) + return make_plane(user, /obj/item/paperplane/syndicate) + return make_plane(user, /obj/item/paperplane) + + +/** + * Paper plane folding + * Makes a paperplane depending on args and returns it. + * + * Arguments: + * * mob/living/user - who's folding + * * obj/item/paperplane/plane_type - what it will be folded into (path) + */ +/obj/item/paper/proc/make_plane(mob/living/user, obj/item/paperplane/plane_type = /obj/item/paperplane) + balloon_alert(user, "folded into a plane") + user.temporarilyRemoveItemFromInventory(src) + var/obj/item/paperplane/new_plane = new plane_type(loc, src) + if(user.Adjacent(new_plane)) + user.put_in_hands(new_plane) + return new_plane + /obj/item/proc/burn_paper_product_attackby_check(obj/item/attacking_item, mob/living/user, bypass_clumsy = FALSE) //can't be put on fire! if((resistance_flags & FIRE_PROOF) || !(resistance_flags & FLAMMABLE)) diff --git a/code/modules/paperwork/paperplane.dm b/code/modules/paperwork/paperplane.dm index d3688ff2156ac..0b9c4feccb18c 100644 --- a/code/modules/paperwork/paperplane.dm +++ b/code/modules/paperwork/paperplane.dm @@ -3,6 +3,7 @@ desc = "Paper, folded in the shape of a plane." icon = 'icons/obj/service/bureaucracy.dmi' icon_state = "paperplane" + base_icon_state = "paperplane" custom_fire_overlay = "paperplane_onfire" throw_range = 7 throw_speed = 1 @@ -11,43 +12,45 @@ resistance_flags = FLAMMABLE max_integrity = 50 - var/hit_probability = 2 //% - var/obj/item/paper/internalPaper + ///The chance of hitting a mob in the eye when thrown, in percentage. + var/hit_probability = 2 + ///Reference to the paper that's folded up in this paperplane, which we return when unfolded. + var/obj/item/paper/internal_paper /obj/item/paperplane/syndicate desc = "Paper, masterfully folded in the shape of a plane." - throwforce = 20 //same as throwing stars, but no chance of embedding. - hit_probability = 100 //guaranteed to cause eye damage when it hits a mob. + throwforce = 20 + hit_probability = 100 -/obj/item/paperplane/Initialize(mapload, obj/item/paper/newPaper) +/obj/item/paperplane/Initialize(mapload, obj/item/paper/paper_made_of) . = ..() pixel_x = base_pixel_x + rand(-9, 9) pixel_y = base_pixel_y + rand(-8, 8) - if(newPaper) - internalPaper = newPaper - flags_1 = newPaper.flags_1 - color = newPaper.color - newPaper.forceMove(src) + if(paper_made_of) + internal_paper = paper_made_of + flags_1 = paper_made_of.flags_1 + color = paper_made_of.color + paper_made_of.forceMove(src) else - internalPaper = new(src) - if(internalPaper.icon_state == "cpaper" || internalPaper.icon_state == "cpaper_words") - icon_state = "paperplane_carbon" // It's the purple carbon copy. Use the purple paper plane - update_appearance() + internal_paper = new(src) + if(istype(internal_paper, /obj/item/paper/carbon_copy)) + icon_state = "[base_icon_state]_carbon" + update_appearance(UPDATE_ICON) /obj/item/paperplane/Exited(atom/movable/gone, direction) . = ..() - if (internalPaper == gone) - internalPaper = null + if (internal_paper == gone) + internal_paper = null if(!QDELETED(src)) qdel(src) /obj/item/paperplane/Destroy() - internalPaper = null + internal_paper = null return ..() /obj/item/paperplane/suicide_act(mob/living/user) var/obj/item/organ/internal/eyes/eyes = user.get_organ_slot(ORGAN_SLOT_EYES) - user.Stun(200) + user.Stun(20 SECONDS) user.visible_message(span_suicide("[user] jams [src] in [user.p_their()] nose. It looks like [user.p_theyre()] trying to commit suicide!")) user.adjust_eye_blur(12 SECONDS) if(eyes) @@ -57,8 +60,8 @@ /obj/item/paperplane/update_overlays() . = ..() - for(var/stamp in internalPaper.stamp_cache) - . += "paperplane_[stamp]" + for(var/stamp in internal_paper.stamp_cache) + . += "[base_icon_state]_[stamp]" /obj/item/paperplane/attack_self(mob/user) balloon_alert(user, "unfolded") @@ -66,82 +69,45 @@ var/atom/location = drop_location() // Need to keep a reference to the internal paper // when we move it out of the plane, our ref gets set to null - var/obj/item/paper/internal_paper = internalPaper - internal_paper.forceMove(location) + var/obj/item/paper/released_paper = internal_paper + released_paper.forceMove(location) // This will as a side effect, qdel the paper plane, making the user's hands empty - user.put_in_hands(internal_paper) + user.put_in_hands(released_paper) -/obj/item/paperplane/attackby(obj/item/P, mob/living/carbon/human/user, params) - if(burn_paper_product_attackby_check(P, user)) +/obj/item/paperplane/attackby(obj/item/attacking_item, mob/user, params) + if(burn_paper_product_attackby_check(attacking_item, user)) return - if(istype(P, /obj/item/pen) || istype(P, /obj/item/toy/crayon)) + if(istype(attacking_item, /obj/item/pen) || istype(attacking_item, /obj/item/toy/crayon)) to_chat(user, span_warning("You should unfold [src] before changing it!")) return - - else if(istype(P, /obj/item/stamp)) //we don't randomize stamps on a paperplane - internalPaper.attackby(P, user) //spoofed attack to update internal paper. + else if(istype(attacking_item, /obj/item/stamp)) //we don't randomize stamps on a paperplane + internal_paper.attackby(attacking_item, user) //spoofed attack to update internal paper. update_appearance() add_fingerprint(user) return - return ..() - -/obj/item/paperplane/throw_at(atom/target, range, speed, mob/thrower, spin=FALSE, diagonals_first = FALSE, datum/callback/callback, gentle, quickstart = TRUE) - . = ..(target, range, speed, thrower, FALSE, diagonals_first, callback, quickstart = quickstart) - /obj/item/paperplane/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum) - if(iscarbon(hit_atom)) - var/mob/living/carbon/C = hit_atom - if(C.can_catch_item(TRUE)) - var/datum/action/innate/origami/origami_action = locate() in C.actions - if(origami_action?.active) //if they're a master of origami and have the ability turned on, force throwmode on so they'll automatically catch the plane. - C.throw_mode_on(THROW_MODE_TOGGLE) - - if(..() || !ishuman(hit_atom))//if the plane is caught or it hits a nonhuman - return - var/mob/living/carbon/human/H = hit_atom - var/obj/item/organ/internal/eyes/eyes = H.get_organ_slot(ORGAN_SLOT_EYES) - if(prob(hit_probability)) - if(H.is_eyes_covered()) - return - visible_message(span_danger("\The [src] hits [H] in the eye[eyes ? "" : " socket"]!")) - H.adjust_eye_blur(12 SECONDS) - eyes?.apply_organ_damage(rand(6,8)) - H.Paralyze(40) - H.emote("scream") + if(iscarbon(hit_atom) && HAS_TRAIT(hit_atom, TRAIT_PAPER_MASTER)) + var/mob/living/carbon/hit_carbon = hit_atom + if(hit_carbon.can_catch_item(TRUE)) + hit_carbon.throw_mode_on(THROW_MODE_TOGGLE) -/obj/item/paper/examine(mob/user) . = ..() - . += span_notice("Alt-click [src] to fold it into a paper plane.") - -/obj/item/paper/AltClick(mob/living/user, obj/item/I) - if(!user.can_perform_action(src, NEED_DEXTERITY|NEED_HANDS)) + if(. || !ishuman(hit_atom)) //if the plane is caught or it hits a nonhuman return - if(istype(src, /obj/item/paper/carbon)) - var/obj/item/paper/carbon/Carbon = src - if(!Carbon.copied) - to_chat(user, span_notice("Take off the carbon copy first.")) - return - //Origami Master - var/datum/action/innate/origami/origami_action = locate() in user.actions - if(origami_action?.active) - make_plane(user, I, /obj/item/paperplane/syndicate) - else - make_plane(user, I, /obj/item/paperplane) + var/mob/living/carbon/human/hit_human = hit_atom + var/obj/item/organ/internal/eyes/eyes = hit_human.get_organ_slot(ORGAN_SLOT_EYES) + if(!prob(hit_probability)) + return + if(hit_human.is_eyes_covered()) + return + visible_message(span_danger("\The [src] hits [hit_human] in the eye[eyes ? "" : " socket"]!")) + hit_human.adjust_eye_blur(12 SECONDS) + eyes?.apply_organ_damage(rand(6, 8)) + hit_human.Paralyze(4 SECONDS) + hit_human.emote("scream") -/** - * Paper plane folding - * - * Arguments: - * * mob/living/user - who's folding - * * obj/item/I - what's being folded - * * obj/item/paperplane/plane_type - what it will be folded into (path) - */ -/obj/item/paper/proc/make_plane(mob/living/user, obj/item/I, obj/item/paperplane/plane_type = /obj/item/paperplane) - balloon_alert(user, "folded into a plane") - user.temporarilyRemoveItemFromInventory(src) - I = new plane_type(loc, src) - if(user.Adjacent(I)) - user.put_in_hands(I) +/obj/item/paperplane/throw_at(atom/target, range, speed, mob/thrower, spin=FALSE, diagonals_first = FALSE, datum/callback/callback, gentle, quickstart = TRUE) + return ..(target, range, speed, thrower, FALSE, diagonals_first, callback, quickstart = quickstart) diff --git a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm index aface2dce34b5..d20f2b786277d 100644 --- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm @@ -273,9 +273,9 @@ . = ..() var/need_mob_update if(affected_mob.getFireLoss() > 25) - need_mob_update = affected_mob.adjustFireLoss(-4 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) //Twice as effective as AIURI for severe burns + need_mob_update = affected_mob.adjustFireLoss(-4 * REM * seconds_per_tick * normalise_creation_purity(), updating_health = FALSE, required_bodytype = affected_bodytype) //Twice as effective as AIURI for severe burns else - need_mob_update = affected_mob.adjustFireLoss(-0.5 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) //But only a quarter as effective for more minor ones + need_mob_update = affected_mob.adjustFireLoss(-0.5 * REM * seconds_per_tick * normalise_creation_purity(), updating_health = FALSE, required_bodytype = affected_bodytype) //But only a quarter as effective for more minor ones if(need_mob_update) return UPDATE_MOB_HEALTH @@ -535,9 +535,9 @@ . = ..() var/need_mob_update if(affected_mob.getBruteLoss() > 25) - need_mob_update = affected_mob.adjustBruteLoss(-4 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update = affected_mob.adjustBruteLoss(-4 * REM * seconds_per_tick * normalise_creation_purity(), updating_health = FALSE, required_bodytype = affected_bodytype) else - need_mob_update = affected_mob.adjustBruteLoss(-0.5 * REM * seconds_per_tick, updating_health = FALSE, required_bodytype = affected_bodytype) + need_mob_update = affected_mob.adjustBruteLoss(-0.5 * REM * seconds_per_tick * normalise_creation_purity(), updating_health = FALSE, required_bodytype = affected_bodytype) if(need_mob_update) return UPDATE_MOB_HEALTH @@ -1235,7 +1235,7 @@ need_mob_update = affected_mob.adjustBruteLoss(-5 * REM * seconds_per_tick, updating_health = FALSE) //A ton of healing - this is a 50 telecrystal investment. need_mob_update += affected_mob.adjustFireLoss(-5 * REM * seconds_per_tick, updating_health = FALSE) need_mob_update += affected_mob.adjustOxyLoss(-15 * REM * seconds_per_tick, updating_health = FALSE) - need_mob_update += affected_mob.adjustToxLoss(-5 * REM * seconds_per_tick, updating_health = FALSE) + need_mob_update += affected_mob.adjustToxLoss(-5 * REM * seconds_per_tick, updating_health = FALSE, forced = TRUE, required_biotype = affected_biotype) need_mob_update += affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, -15 * REM * seconds_per_tick) if(need_mob_update) return UPDATE_MOB_HEALTH diff --git a/code/modules/surgery/organs/internal/ears/_ears.dm b/code/modules/surgery/organs/internal/ears/_ears.dm index ad48645e89366..f538f8789b600 100644 --- a/code/modules/surgery/organs/internal/ears/_ears.dm +++ b/code/modules/surgery/organs/internal/ears/_ears.dm @@ -89,15 +89,13 @@ /obj/item/organ/internal/ears/penguin/on_mob_insert(mob/living/carbon/human/ear_owner) . = ..() - if(istype(ear_owner)) - to_chat(ear_owner, span_notice("You suddenly feel like you've lost your balance.")) - ear_owner.AddElement(/datum/element/waddling) + to_chat(ear_owner, span_notice("You suddenly feel like you've lost your balance.")) + ear_owner.AddElementTrait(TRAIT_WADDLING, ORGAN_TRAIT, /datum/element/waddling) /obj/item/organ/internal/ears/penguin/on_mob_remove(mob/living/carbon/human/ear_owner) . = ..() - if(istype(ear_owner)) - to_chat(ear_owner, span_notice("Your sense of balance comes back to you.")) - ear_owner.RemoveElement(/datum/element/waddling) + to_chat(ear_owner, span_notice("Your sense of balance comes back to you.")) + REMOVE_TRAIT(ear_owner, TRAIT_WADDLING, ORGAN_TRAIT) /obj/item/organ/internal/ears/cybernetic name = "basic cybernetic ears" diff --git a/code/modules/tgui_input/keycombo.dm b/code/modules/tgui_input/keycombo.dm new file mode 100644 index 0000000000000..948dbaea234a8 --- /dev/null +++ b/code/modules/tgui_input/keycombo.dm @@ -0,0 +1,126 @@ +/** + * Creates a TGUI window with a key input. Returns the user's response as a full key with modifiers, eg ShiftK. + * + * This proc should be used to create windows for key entry that the caller will wait for a response from. + * If tgui fancy chat is turned off: Will return a normal input. + * + * Arguments: + * * user - The user to show the number input to. + * * message - The content of the number input, shown in the body of the TGUI window. + * * title - The title of the number input modal, shown on the top of the TGUI window. + * * default - The default (or current) key, shown as a placeholder. + */ +/proc/tgui_input_keycombo(mob/user = usr, message, title = "Key Input", default = 0, timeout = 0, ui_state = GLOB.always_state) + if (!istype(user)) + if (istype(user, /client)) + var/client/client = user + user = client.mob + else + return null + + if (isnull(user.client)) + return null + + // Client does NOT have tgui_input on: Returns regular input + if(!user.client.prefs.read_preference(/datum/preference/toggle/tgui_input)) + var/input_key = input(user, message, title + "(Modifiers are TGUI only, sorry!)", default) as null|text + return input_key[1] + var/datum/tgui_input_keycombo/key_input = new(user, message, title, default, timeout, ui_state) + key_input.ui_interact(user) + key_input.wait() + if (key_input) + . = key_input.entry + qdel(key_input) + +/** + * # tgui_input_keycombo + * + * Datum used for instantiating and using a TGUI-controlled key input that prompts the user with + * a message and listens for key presses. + */ +/datum/tgui_input_keycombo + /// Boolean field describing if the tgui_input_number was closed by the user. + var/closed + /// The default (or current) value, shown as a default. Users can press reset with this. + var/default + /// The entry that the user has return_typed in. + var/entry + /// The prompt's body, if any, of the TGUI window. + var/message + /// The time at which the number input was created, for displaying timeout progress. + var/start_time + /// The lifespan of the number input, after which the window will close and delete itself. + var/timeout + /// The title of the TGUI window + var/title + /// The TGUI UI state that will be returned in ui_state(). Default: always_state + var/datum/ui_state/state + +/datum/tgui_input_keycombo/New(mob/user, message, title, default, timeout, ui_state) + src.default = default + src.message = message + src.title = title + src.state = ui_state + if (timeout) + src.timeout = timeout + start_time = world.time + QDEL_IN(src, timeout) + +/datum/tgui_input_keycombo/Destroy(force) + SStgui.close_uis(src) + state = null + return ..() + +/** + * Waits for a user's response to the tgui_input_keycombo's prompt before returning. Returns early if + * the window was closed by the user. + */ +/datum/tgui_input_keycombo/proc/wait() + while (!entry && !closed && !QDELETED(src)) + stoplag(1) + +/datum/tgui_input_keycombo/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "KeyComboModal") + ui.open() + +/datum/tgui_input_keycombo/ui_close(mob/user) + . = ..() + closed = TRUE + +/datum/tgui_input_keycombo/ui_state(mob/user) + return state + +/datum/tgui_input_keycombo/ui_static_data(mob/user) + var/list/data = list() + data["init_value"] = default // Default is a reserved keyword + data["large_buttons"] = user.client.prefs.read_preference(/datum/preference/toggle/tgui_input_large) + data["message"] = message + data["swapped_buttons"] = user.client.prefs.read_preference(/datum/preference/toggle/tgui_input_swapped) + data["title"] = title + return data + +/datum/tgui_input_keycombo/ui_data(mob/user) + var/list/data = list() + if(timeout) + data["timeout"] = CLAMP01((timeout - (world.time - start_time) - 1 SECONDS) / (timeout - 1 SECONDS)) + return data + +/datum/tgui_input_keycombo/ui_act(action, list/params) + . = ..() + if (.) + return + switch(action) + if("submit") + set_entry(params["entry"]) + closed = TRUE + SStgui.close_uis(src) + return TRUE + if("cancel") + closed = TRUE + SStgui.close_uis(src) + return TRUE + +/datum/tgui_input_keycombo/proc/set_entry(entry) + src.entry = entry diff --git a/code/modules/vehicles/cars/clowncar.dm b/code/modules/vehicles/cars/clowncar.dm index 032a054a77c9a..37f6eb7efa501 100644 --- a/code/modules/vehicles/cars/clowncar.dm +++ b/code/modules/vehicles/cars/clowncar.dm @@ -179,7 +179,7 @@ to_chat(user, span_danger("You scramble [src]'s child safety lock, and a panel with six colorful buttons appears!")) initialize_controller_action_type(/datum/action/vehicle/sealed/roll_the_dice, VEHICLE_CONTROL_DRIVE) initialize_controller_action_type(/datum/action/vehicle/sealed/cannon, VEHICLE_CONTROL_DRIVE) - AddElement(/datum/element/waddling) + AddElementTrait(TRAIT_WADDLING, INNATE_TRAIT, /datum/element/waddling) return TRUE /obj/vehicle/sealed/car/clowncar/atom_destruction(damage_flag) diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm index 47bb966555686..3c544552d512a 100644 --- a/code/modules/vending/_vending.dm +++ b/code/modules/vending/_vending.dm @@ -236,6 +236,11 @@ voice = vendor_voice_by_type[type] if(build_inv) //non-constructable vending machine + ///Non-constructible vending machines do not have a refill canister to populate its products list from, + ///Which apparently is still needed in the case we use product categories instead. + if(product_categories) + for(var/list/category as anything in product_categories) + products |= category["products"] build_inventories() slogan_list = splittext(product_slogans, ";") diff --git a/html/changelogs/AutoChangeLog-pr-81409.yml b/html/changelogs/AutoChangeLog-pr-81409.yml deleted file mode 100644 index f3f1949b1ad00..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-81409.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Thunder12345" -delete-after: True -changes: - - qol: "The bitrunning quantum console UI now lists domains in tabs by difficulty." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-81413.yml b/html/changelogs/AutoChangeLog-pr-81413.yml deleted file mode 100644 index b64eee4e917c7..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-81413.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Melbert" -delete-after: True -changes: - - rscadd: "Wizards have a new mobility option available, the Telegram Scepter. The ability to travel anywhere you can see at the point of a wand... but at a price?" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-81435.yml b/html/changelogs/AutoChangeLog-pr-81435.yml deleted file mode 100644 index 0ef4705974e09..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-81435.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Ghommie" -delete-after: True -changes: - - rscadd: "Added three new 'special' bedsheets. One of them is quite rare and made from gondola hide." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-81452.yml b/html/changelogs/AutoChangeLog-pr-81452.yml deleted file mode 100644 index d501307c8ba40..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-81452.yml +++ /dev/null @@ -1,5 +0,0 @@ -author: "mogeoko" -delete-after: True -changes: - - bugfix: "Thermomachines now reconnect to pipes on multitool's act." - - bugfix: "Multi-deck connectors won't connect pipes not located in front/top/bottom of it." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-81459.yml b/html/changelogs/AutoChangeLog-pr-81459.yml deleted file mode 100644 index 39addd9cbe676..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-81459.yml +++ /dev/null @@ -1,6 +0,0 @@ -author: "SyncIt21" -delete-after: True -changes: - - qol: "adds examines & screentips for ore box" - - code_imp: "cleans up some procs for ore box" - - spellcheck: "corrected description & ui notice of ore box to specify it can hold boulders too" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-81472.yml b/html/changelogs/AutoChangeLog-pr-81472.yml deleted file mode 100644 index 0ca1b4ccb0f35..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-81472.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "kawaiinick" -delete-after: True -changes: - - rscadd: "Your mouth now fits combat or survival knives(it's totally safe)" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-81481.yml b/html/changelogs/AutoChangeLog-pr-81481.yml deleted file mode 100644 index d076280f163ec..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-81481.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Sylphet" -delete-after: True -changes: - - bugfix: "cargo lockboxes update iconstates correctly now" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-81482.yml b/html/changelogs/AutoChangeLog-pr-81482.yml deleted file mode 100644 index 7306b7c28b4de..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-81482.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "00-Steven" -delete-after: True -changes: - - bugfix: "Space cats CAN into space. (They're back to surviving being in unsuitable atmos.)" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-81483.yml b/html/changelogs/AutoChangeLog-pr-81483.yml deleted file mode 100644 index 8c82b8396d588..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-81483.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "IndieanaJones" -delete-after: True -changes: - - bugfix: "Tank spider corpses should no longer be conditionally invisible" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-81485.yml b/html/changelogs/AutoChangeLog-pr-81485.yml deleted file mode 100644 index bac62bcf734ae..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-81485.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "IndieanaJones" -delete-after: True -changes: - - bugfix: "Blob Zombies now render their blob heads correctly again." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-81487.yml b/html/changelogs/AutoChangeLog-pr-81487.yml deleted file mode 100644 index db13b1e0f97da..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-81487.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "SyncIt21" -delete-after: True -changes: - - bugfix: "Indestructible items like the pai card don't teleport to the ore silo when you insert them into silo linked machine & also displays a message saying it was rejected." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-81492.yml b/html/changelogs/AutoChangeLog-pr-81492.yml deleted file mode 100644 index d2aeff28762c2..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-81492.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "13spacemen" -delete-after: True -changes: - - code_imp: "Removed unused global lists and sprite accessories related to tail spines" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-81493.yml b/html/changelogs/AutoChangeLog-pr-81493.yml deleted file mode 100644 index 4f7d4b4b89c23..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-81493.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "13spacemen" -delete-after: True -changes: - - code_imp: "removed redundant check for plasmamen in survival box code" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-81523.yml b/html/changelogs/AutoChangeLog-pr-81523.yml new file mode 100644 index 0000000000000..b0279df0d02b2 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-81523.yml @@ -0,0 +1,4 @@ +author: "Rhials" +delete-after: True +changes: + - bugfix: "Removes the double-newscaster from the arrivals sec post." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-81526.yml b/html/changelogs/AutoChangeLog-pr-81526.yml new file mode 100644 index 0000000000000..7a601ac9ac82f --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-81526.yml @@ -0,0 +1,4 @@ +author: "LemonInTheDark" +delete-after: True +changes: + - bugfix: "Maps loaded in after roundstart will no longer have broken smoothing" \ No newline at end of file diff --git a/html/changelogs/archive/2024-02.yml b/html/changelogs/archive/2024-02.yml index 6afd8b08c3696..28735b510d5b6 100644 --- a/html/changelogs/archive/2024-02.yml +++ b/html/changelogs/archive/2024-02.yml @@ -446,3 +446,61 @@ - rscadd: In an attempt to stop the greytide, NanoTrasen has increased security's baton energy output. This has, through testing, done nothing but make the device spark more than it used to. +2024-02-17: + 00-Steven: + - bugfix: Space cats CAN into space. (They're back to surviving being in unsuitable + atmos.) + 13spacemen: + - code_imp: Removed unused global lists and sprite accessories related to tail spines + - code_imp: removed redundant check for plasmamen in survival box code + Ghommie: + - rscadd: Added three new 'special' bedsheets. One of them is quite rare and made + from gondola hide. + IndieanaJones: + - bugfix: Tank spider corpses should no longer be conditionally invisible + - bugfix: Blob Zombies now render their blob heads correctly again. + MLGTASTICa: + - balance: Oxandrolone now scales with purity. Its default purity is 100% + - balance: Salicylic acid now scales with purity. Its default purity is 100% + Melbert: + - rscadd: Wizards have a new mobility option available, the Telegram Scepter. The + ability to travel anywhere you can see at the point of a wand... but at a price? + Sylphet: + - bugfix: cargo lockboxes update iconstates correctly now + SyncIt21: + - bugfix: Indestructible items like the pai card don't teleport to the ore silo + when you insert them into silo linked machine & also displays a message saying + it was rejected. + - qol: adds examines & screentips for ore box + - code_imp: cleans up some procs for ore box + - spellcheck: corrected description & ui notice of ore box to specify it can hold + boulders too + Thunder12345: + - qol: The bitrunning quantum console UI now lists domains in tabs by difficulty. + destrucktoid: + - bugfix: Restorative Nanites now heal Slimepeople just as well as they do other + people. + kawaiinick: + - rscadd: Your mouth now fits combat or survival knives(it's totally safe) + mc-oofert: + - balance: assemblybombs are bulky + - qol: you may altclick action buttons to bind them to a key + mogeoko: + - bugfix: Thermomachines now reconnect to pipes on multitool's act. + - bugfix: Multi-deck connectors won't connect pipes not located in front/top/bottom + of it. +2024-02-18: + 1393F: + - bugfix: The crystallizer screentip for rotating it has been updated with the correct + button + Ghommie: + - rscadd: Players holding cardboard cutouts will now assume their appearance, just + like for potted plants. + GoldenAlpharex: + - bugfix: The Spawners menu now accurately displays the amount of uses left in each + spawner option, taking into account individual spawners that either allow more + than one use, or an infinite amount of uses. + Melbert: + - bugfix: Lifeline can no longer track mobs with suit sensors off + Roastglue: + - bugfix: Crafting Bulgogi no longer makes Bibimbap. It instead makes Bulgogi. diff --git a/tgstation.dme b/tgstation.dme index 0a32dc4496121..45c11123587cd 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -5799,6 +5799,7 @@ #include "code\modules\tgui\states\zlevel.dm" #include "code\modules\tgui_input\alert.dm" #include "code\modules\tgui_input\checkboxes.dm" +#include "code\modules\tgui_input\keycombo.dm" #include "code\modules\tgui_input\list.dm" #include "code\modules\tgui_input\number.dm" #include "code\modules\tgui_input\text.dm" diff --git a/tgui/packages/tgui/interfaces/KeyComboModal.tsx b/tgui/packages/tgui/interfaces/KeyComboModal.tsx new file mode 100644 index 0000000000000..e0b598764f156 --- /dev/null +++ b/tgui/packages/tgui/interfaces/KeyComboModal.tsx @@ -0,0 +1,147 @@ +import { KEY } from 'common/keys'; +import { useState } from 'react'; + +import { useBackend, useLocalState } from '../backend'; +import { Autofocus, Box, Button, Section, Stack } from '../components'; +import { Window } from '../layouts'; +import { InputButtons } from './common/InputButtons'; +import { Loader } from './common/Loader'; + +type KeyInputData = { + init_value: string; + large_buttons: boolean; + message: string; + timeout: number; + title: string; +}; + +const isStandardKey = (event: React.KeyboardEvent): boolean => { + return ( + event.key !== KEY.Alt && + event.key !== KEY.Control && + event.key !== KEY.Shift && + event.key !== KEY.Escape + ); +}; + +const KEY_CODE_TO_BYOND: Record = { + DEL: 'Delete', + DOWN: 'South', + END: 'Southwest', + HOME: 'Northwest', + INSERT: 'Insert', + LEFT: 'West', + PAGEDOWN: 'Southeast', + PAGEUP: 'Northeast', + RIGHT: 'East', + SPACEBAR: 'Space', + UP: 'North', +}; + +const DOM_KEY_LOCATION_NUMPAD = 3; + +const formatKeyboardEvent = ( + event: React.KeyboardEvent, +): string => { + let text = ''; + + if (event.altKey) { + text += 'Alt'; + } + + if (event.ctrlKey) { + text += 'Ctrl'; + } + + if (event.shiftKey) { + text += 'Shift'; + } + + if (event.location === DOM_KEY_LOCATION_NUMPAD) { + text += 'Numpad'; + } + + if (isStandardKey(event)) { + const key = event.key.toUpperCase(); + text += KEY_CODE_TO_BYOND[key] || key; + } + + return text; +}; + +export const KeyComboModal = (props) => { + const { act, data } = useBackend(); + const { init_value, large_buttons, message = '', title, timeout } = data; + const [input, setInput] = useState(init_value); + const [binding, setBinding] = useLocalState('binding', true); + + const setValue = (value: string) => { + if (value === input) { + return; + } + setInput(value); + }; + + // Dynamically changes the window height based on the message. + const windowHeight = + 130 + + (message.length > 30 ? Math.ceil(message.length / 3) : 0) + + (message.length && large_buttons ? 5 : 0); + + return ( + + {timeout && } + { + if (!binding) { + if (event.key === KEY.Enter) { + act('submit', { entry: input }); + } + if (event.key === KEY.Escape) { + act('cancel'); + } + return; + } + + event.preventDefault(); + + if (isStandardKey(event)) { + setValue(formatKeyboardEvent(event)); + setBinding(false); + return; + } else if (event.key === KEY.Escape) { + setValue(init_value); + setBinding(false); + return; + } + }} + > +
+ + + + {message} + + +
+
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/SpawnersMenu.tsx b/tgui/packages/tgui/interfaces/SpawnersMenu.tsx index 50366a22b2ff9..12f06cfdb62b4 100644 --- a/tgui/packages/tgui/interfaces/SpawnersMenu.tsx +++ b/tgui/packages/tgui/interfaces/SpawnersMenu.tsx @@ -10,6 +10,7 @@ type SpawnersMenuContext = { type spawner = { name: string; amount_left: number; + infinite: boolean; desc?: string; you_are_text?: string; flavor_text?: string; @@ -31,9 +32,15 @@ export const SpawnersMenu = (props) => { title={capitalizeAll(spawner.name)} buttons={ - - {spawner.amount_left} left - + {spawner.infinite ? ( + + Infinite + + ) : ( + + {spawner.amount_left} left + + )}