diff --git a/.github/workflows/tgs_test.yml b/.github/workflows/tgs_test.yml index 4925019bc6a02..6a9316f493a12 100644 --- a/.github/workflows/tgs_test.yml +++ b/.github/workflows/tgs_test.yml @@ -50,7 +50,7 @@ jobs: env: Database__DatabaseType: Sqlite Database__ConnectionString: Data Source=TGS_TGTest.sqlite3;Mode=ReadWriteCreate - General__ConfigVersion: 4.1.0 + General__ConfigVersion: 5.0.0 General__ApiPort: ${{ env.TGS_API_PORT }} General__SetupWizardMode: Never ports: @@ -59,7 +59,7 @@ jobs: - name: Setup dotnet uses: actions/setup-dotnet@v2 with: - dotnet-version: 7.0.x + dotnet-version: 8.0.x - name: Checkout Repository uses: actions/checkout@v3 diff --git a/code/__DEFINES/ai/monkey.dm b/code/__DEFINES/ai/monkey.dm index fc4af74d9f080..0e8c44e40fa83 100644 --- a/code/__DEFINES/ai/monkey.dm +++ b/code/__DEFINES/ai/monkey.dm @@ -15,3 +15,4 @@ #define BB_MONKEY_TARGET_MONKEYS "BB_monkey_target_monkeys" #define BB_MONKEY_DISPOSING "BB_monkey_disposing" #define BB_MONKEY_RECRUIT_COOLDOWN "BB_monkey_recruit_cooldown" +#define BB_RESISTING "BB_resisting" diff --git a/code/__DEFINES/antagonists.dm b/code/__DEFINES/antagonists.dm index 2085f528ff33c..3bdd0a812dfcd 100644 --- a/code/__DEFINES/antagonists.dm +++ b/code/__DEFINES/antagonists.dm @@ -11,7 +11,7 @@ #define NUKE_RESULT_HIJACK_NO_DISK 10 /// Min players requireed for nukes to declare war -#define CHALLENGE_MIN_PLAYERS 50 +#define CHALLENGE_MIN_PLAYERS 25 // MASSMETA EDIT //fugitive end results #define FUGITIVE_RESULT_BADASS_HUNTER 0 diff --git a/code/__DEFINES/dcs/signals/signals_material_container.dm b/code/__DEFINES/dcs/signals/signals_material_container.dm index f33567a2739da..2c77be55c315d 100644 --- a/code/__DEFINES/dcs/signals/signals_material_container.dm +++ b/code/__DEFINES/dcs/signals/signals_material_container.dm @@ -2,10 +2,14 @@ /// Called from datum/component/material_container/proc/can_hold_material() : (mat) #define COMSIG_MATCONTAINER_MAT_CHECK "matcontainer_mat_check" #define MATCONTAINER_ALLOW_MAT (1<<0) -/// Called from datum/component/material_container/proc/user_insert() : (held_item, user) +/// Called from datum/component/material_container/proc/user_insert() : (target_item, user) #define COMSIG_MATCONTAINER_PRE_USER_INSERT "matcontainer_pre_user_insert" #define MATCONTAINER_BLOCK_INSERT (1<<1) -/// Called from datum/component/material_container/proc/insert_item() : (target, last_inserted_id, mats_consumed, material_amount, context) +/// Called from datum/component/material_container/proc/insert_item() : (item, primary_mat, mats_consumed, material_amount, context) #define COMSIG_MATCONTAINER_ITEM_CONSUMED "matcontainer_item_consumed" -/// Called from datum/component/material_container/proc/retrieve_sheets() : (sheets, context) +/// Called from datum/component/material_container/proc/retrieve_sheets() : (new_sheets, context) #define COMSIG_MATCONTAINER_SHEETS_RETRIEVED "matcontainer_sheets_retrieved" + +//mat container signals but from the ore silo's perspective +/// Called from /obj/machinery/ore_silo/on_item_consumed() : (container, item_inserted, last_inserted_id, mats_consumed, amount_inserted) +#define COMSIG_SILO_ITEM_CONSUMED "silo_item_consumed" diff --git a/code/__DEFINES/dcs/signals/signals_moveloop.dm b/code/__DEFINES/dcs/signals/signals_moveloop.dm index 38ab63a596984..8a354f8bfbb1d 100644 --- a/code/__DEFINES/dcs/signals/signals_moveloop.dm +++ b/code/__DEFINES/dcs/signals/signals_moveloop.dm @@ -9,3 +9,5 @@ #define COMSIG_MOVELOOP_POSTPROCESS "moveloop_postprocess" //from [/datum/move_loop/has_target/jps/recalculate_path] (): #define COMSIG_MOVELOOP_JPS_REPATH "moveloop_jps_repath" +///from [/datum/move_loop/has_target/jps/on_finish_pathing] +#define COMSIG_MOVELOOP_JPS_FINISHED_PATHING "moveloop_jps_finished_pathing" diff --git a/code/__DEFINES/wall_dents.dm b/code/__DEFINES/wall_dents.dm index 1e4f813849ef6..555f98671c079 100644 --- a/code/__DEFINES/wall_dents.dm +++ b/code/__DEFINES/wall_dents.dm @@ -1,2 +1,3 @@ #define WALL_DENT_HIT 1 #define WALL_DENT_SHOT 2 +#define MAX_DENT_DECALS 15 diff --git a/code/__DEFINES/wires.dm b/code/__DEFINES/wires.dm index 633dcc360c2c8..2b4c528abc212 100644 --- a/code/__DEFINES/wires.dm +++ b/code/__DEFINES/wires.dm @@ -64,12 +64,6 @@ #define WIRE_ZAP1 "High Voltage Circuit 1" #define WIRE_ZAP2 "High Voltage Circuit 2" #define WIRE_OVERCLOCK "Overclock" -#define WIRE_VALVE "Valve" -#define WIRE_SHIELDING "Shielding" -#define WIRE_REGULATOR_MIN "Minimize Regulator Pressure" -#define WIRE_REGULATOR_MAX "Maximize Regulator Pressure" -#define WIRE_TANK_EJECT "Eject Tank" -#define WIRE_REACTION_SUPPRESSION "Reaction Suppression" // Wire states for the AI #define AI_WIRE_NORMAL 0 diff --git a/code/controllers/subsystem/movement/movement_types.dm b/code/controllers/subsystem/movement/movement_types.dm index de8e4531236cf..bf4b6a4302917 100644 --- a/code/controllers/subsystem/movement/movement_types.dm +++ b/code/controllers/subsystem/movement/movement_types.dm @@ -323,6 +323,7 @@ turf/avoid, skip_first, subsystem, + diagonal_handling, priority, flags, datum/extra_info, @@ -343,6 +344,7 @@ simulated_only, avoid, skip_first, + diagonal_handling, initial_path) /datum/move_loop/has_target/jps @@ -360,6 +362,8 @@ var/turf/avoid ///Should we skip the first step? This is the tile we're currently on, which breaks some things var/skip_first + ///Whether we replace diagonal movements with cardinal movements or follow through with them + var/diagonal_handling ///A list for the path we're currently following var/list/movement_path ///Cooldown for repathing, prevents spam @@ -373,7 +377,7 @@ . = ..() on_finish_callbacks += CALLBACK(src, PROC_REF(on_finish_pathing)) -/datum/move_loop/has_target/jps/setup(delay, timeout, atom/chasing, repath_delay, max_path_length, minimum_distance, list/access, simulated_only, turf/avoid, skip_first, list/initial_path) +/datum/move_loop/has_target/jps/setup(delay, timeout, atom/chasing, repath_delay, max_path_length, minimum_distance, list/access, simulated_only, turf/avoid, skip_first, diagonal_handling, list/initial_path) . = ..() if(!.) return @@ -384,6 +388,7 @@ src.simulated_only = simulated_only src.avoid = avoid src.skip_first = skip_first + src.diagonal_handling = diagonal_handling movement_path = initial_path?.Copy() /datum/move_loop/has_target/jps/compare_loops(datum/move_loop/loop_type, priority, flags, extra_info, delay, timeout, atom/chasing, repath_delay, max_path_length, minimum_distance, list/access, simulated_only, turf/avoid, skip_first, initial_path) @@ -410,7 +415,7 @@ if(!COOLDOWN_FINISHED(src, repath_cooldown)) return COOLDOWN_START(src, repath_cooldown, repath_delay) - if(SSpathfinder.pathfind(moving, target, max_path_length, minimum_distance, access, simulated_only, avoid, skip_first, on_finish = on_finish_callbacks)) + if(SSpathfinder.pathfind(moving, target, max_path_length, minimum_distance, access, simulated_only, avoid, skip_first, diagonal_handling, on_finish = on_finish_callbacks)) is_pathing = TRUE SEND_SIGNAL(src, COMSIG_MOVELOOP_JPS_REPATH) @@ -418,6 +423,7 @@ /datum/move_loop/has_target/jps/proc/on_finish_pathing(list/path) movement_path = path is_pathing = FALSE + SEND_SIGNAL(src, COMSIG_MOVELOOP_JPS_FINISHED_PATHING, path) /datum/move_loop/has_target/jps/move() if(!length(movement_path)) diff --git a/code/controllers/subsystem/persistence/_persistence.dm b/code/controllers/subsystem/persistence/_persistence.dm index 8d00c77812170..8f865e5d09871 100644 --- a/code/controllers/subsystem/persistence/_persistence.dm +++ b/code/controllers/subsystem/persistence/_persistence.dm @@ -20,8 +20,23 @@ SUBSYSTEM_DEF(persistence) var/list/blocked_maps = list() var/list/saved_trophies = list() var/list/picture_logging_information = list() - var/list/obj/structure/sign/picture_frame/photo_frames - var/list/obj/item/storage/photo_album/photo_albums + + /// A json_database linking to data/photo_frames.json. + /// Schema is persistence_id => array of photo names. + var/datum/json_database/photo_frames_database + + /// A lazy list of every picture frame that is going to be loaded with persistent photos. + /// Will be null'd once the persistence system initializes, and never read from again. + var/list/obj/structure/sign/picture_frame/queued_photo_frames + + /// A json_database linking to data/photo_albums.json. + /// Schema is persistence_id => array of photo names. + var/datum/json_database/photo_albums_database + + /// A lazy list of every photo album that is going to be loaded with persistent photos. + /// Will be null'd once the persistence system initializes, and never read from again. + var/list/obj/item/storage/photo_album/queued_photo_albums + var/rounds_since_engine_exploded = 0 var/delam_highscore = 0 var/tram_hits_this_round = 0 @@ -47,7 +62,6 @@ SUBSYSTEM_DEF(persistence) save_prisoner_tattoos() collect_trophies() collect_maps() - save_photo_persistence() //THIS IS PERSISTENCE, NOT THE LOGGING PORTION. save_randomized_recipes() save_scars() save_custom_outfits() diff --git a/code/controllers/subsystem/persistence/photo_albums.dm b/code/controllers/subsystem/persistence/photo_albums.dm index 3aee856b2c21c..90c8ec31ac146 100644 --- a/code/controllers/subsystem/persistence/photo_albums.dm +++ b/code/controllers/subsystem/persistence/photo_albums.dm @@ -1,15 +1,3 @@ -///Loads up the photo album source file. -/datum/controller/subsystem/persistence/proc/get_photo_albums() - var/album_path = file("data/photo_albums.json") - if(fexists(album_path)) - return json_decode(file2text(album_path)) - -///Loads up the photo frames source file. -/datum/controller/subsystem/persistence/proc/get_photo_frames() - var/frame_path = file("data/photo_frames.json") - if(fexists(frame_path)) - return json_decode(file2text(frame_path)) - /// Removes the identifier of a persistent photo frame from the json. /datum/controller/subsystem/persistence/proc/remove_photo_frames(identifier) var/frame_path = file("data/photo_frames.json") @@ -25,62 +13,23 @@ ///Loads photo albums, and populates them; also loads and applies frames to picture frames. /datum/controller/subsystem/persistence/proc/load_photo_persistence() - var/album_path = file("data/photo_albums.json") - var/frame_path = file("data/photo_frames.json") - if(fexists(album_path)) - var/list/json = json_decode(file2text(album_path)) - if(json.len) - for(var/i in photo_albums) - var/obj/item/storage/photo_album/A = i - if(!A.persistence_id) - continue - if(json[A.persistence_id]) - A.populate_from_id_list(json[A.persistence_id]) - - if(fexists(frame_path)) - var/list/json = json_decode(file2text(frame_path)) - if(json.len) - for(var/i in photo_frames) - var/obj/structure/sign/picture_frame/PF = i - if(!PF.persistence_id) - continue - if(json[PF.persistence_id]) - PF.load_from_id(json[PF.persistence_id]) - -///Saves the contents of photo albums and the picture frames. -/datum/controller/subsystem/persistence/proc/save_photo_persistence() - var/album_path = file("data/photo_albums.json") - var/frame_path = file("data/photo_frames.json") - - var/list/frame_json = list() - var/list/album_json = list() - - if(fexists(album_path)) - album_json = json_decode(file2text(album_path)) - fdel(album_path) - - for(var/i in photo_albums) - var/obj/item/storage/photo_album/A = i - if(!istype(A) || !A.persistence_id) + photo_albums_database = new("data/photo_albums.json") + for (var/obj/item/storage/photo_album/album as anything in queued_photo_albums) + if (isnull(album.persistence_id)) continue - var/list/L = A.get_picture_id_list() - album_json[A.persistence_id] = L - - album_json = json_encode(album_json) - - WRITE_FILE(album_path, album_json) - if(fexists(frame_path)) - frame_json = json_decode(file2text(frame_path)) - fdel(frame_path) + var/album_data = photo_albums_database.get_key(album.persistence_id) + if (!isnull(album_data)) + album.populate_from_id_list(album_data) - for(var/i in photo_frames) - var/obj/structure/sign/picture_frame/F = i - if(!istype(F) || !F.persistence_id) + photo_frames_database = new("data/photo_frames.json") + for (var/obj/structure/sign/picture_frame/frame as anything in queued_photo_frames) + if (isnull(frame.persistence_id)) continue - frame_json[F.persistence_id] = F.get_photo_id() - frame_json = json_encode(frame_json) - - WRITE_FILE(frame_path, frame_json) + var/frame_data = photo_frames_database.get_key(frame.persistence_id) + if (!isnull(frame_data)) + frame.load_from_id(frame_data) + queued_photo_albums = null + queued_photo_frames = null diff --git a/code/datums/ai/_ai_controller.dm b/code/datums/ai/_ai_controller.dm index 6bbdabfc50dfe..04dc7901c857f 100644 --- a/code/datums/ai/_ai_controller.dm +++ b/code/datums/ai/_ai_controller.dm @@ -175,6 +175,7 @@ multiple modular subtrees with behaviors ///Runs any actions that are currently running /datum/ai_controller/process(seconds_per_tick) + if(!able_to_run()) SSmove_manager.stop_looping(pawn) //stop moving return //this should remove them from processing in the future through event-based stuff. 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 435b9cb1ef3d0..8ba9624c21edc 100644 --- a/code/datums/ai/basic_mobs/basic_ai_behaviors/targeting.dm +++ b/code/datums/ai/basic_mobs/basic_ai_behaviors/targeting.dm @@ -1,11 +1,12 @@ +/// List of objects that AIs will treat as targets +GLOBAL_LIST_EMPTY_TYPED(hostile_machines, /atom) + /datum/ai_behavior/find_potential_targets action_cooldown = 2 SECONDS /// How far can we see stuff? var/vision_range = 9 /// Blackboard key for aggro range, uses vision range if not specified var/aggro_range_key = BB_AGGRO_RANGE - /// Static typecache list of potentially dangerous objs - var/static/list/hostile_machines = typecacheof(list(/obj/machinery/porta_turret, /obj/vehicle/sealed/mecha)) /datum/ai_behavior/find_potential_targets/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targeting_strategy_key, hiding_location_key) . = ..() @@ -26,9 +27,9 @@ var/list/potential_targets = hearers(aggro_range, get_turf(controller.pawn)) - living_mob //Remove self, so we don't suicide - for(var/HM in typecache_filter_list(range(aggro_range, living_mob), hostile_machines)) //Can we see any hostile machines? - if(can_see(living_mob, HM, aggro_range)) - potential_targets += HM + for (var/atom/hostile_machine as anything in GLOB.hostile_machines) + if (can_see(living_mob, hostile_machine, aggro_range)) + potential_targets += hostile_machine if(!potential_targets.len) finish_action(controller, succeeded = FALSE) diff --git a/code/datums/ai/generic/generic_behaviors.dm b/code/datums/ai/generic/generic_behaviors.dm index 4f61a26165945..43e37f66e8c8a 100644 --- a/code/datums/ai/generic/generic_behaviors.dm +++ b/code/datums/ai/generic/generic_behaviors.dm @@ -2,6 +2,7 @@ /datum/ai_behavior/resist/perform(seconds_per_tick, datum/ai_controller/controller) . = ..() var/mob/living/living_pawn = controller.pawn + living_pawn.ai_controller.set_blackboard_key(BB_RESISTING, TRUE) living_pawn.execute_resist() finish_action(controller, TRUE) diff --git a/code/datums/ai/monkey/monkey_controller.dm b/code/datums/ai/monkey/monkey_controller.dm index 781bfb70a85e5..8be754f8d4b57 100644 --- a/code/datums/ai/monkey/monkey_controller.dm +++ b/code/datums/ai/monkey/monkey_controller.dm @@ -26,9 +26,22 @@ have ways of interacting with a specific mob and control it. BB_MONKEY_GUN_NEURONS_ACTIVATED = FALSE, BB_MONKEY_GUN_WORKED = TRUE, BB_SONG_LINES = MONKEY_SONG, + BB_RESISTING = FALSE, ) idle_behavior = /datum/idle_behavior/idle_monkey +/datum/ai_controller/monkey/process(seconds_per_tick) + + var/mob/living/living_pawn = src.pawn + + if(!length(living_pawn.do_afters) && living_pawn.ai_controller.blackboard[BB_RESISTING]) + living_pawn.ai_controller.set_blackboard_key(BB_RESISTING, FALSE) + + if(living_pawn.ai_controller.blackboard[BB_RESISTING]) + return + + . = ..() + /datum/ai_controller/monkey/New(atom/new_pawn) var/static/list/control_examine = list( ORGAN_SLOT_EYES = span_monkey("eyes have a primal look in them."), @@ -91,7 +104,7 @@ have ways of interacting with a specific mob and control it. /datum/ai_controller/monkey/able_to_run() var/mob/living/living_pawn = pawn - if(IS_DEAD_OR_INCAP(living_pawn)) + if(living_pawn.incapacitated(IGNORE_RESTRAINTS | IGNORE_GRAB | IGNORE_STASIS) || living_pawn.stat > CONSCIOUS) return FALSE return ..() diff --git a/code/datums/ai/movement/ai_movement_jps.dm b/code/datums/ai/movement/ai_movement_jps.dm index 31fd90ccf53c2..3644869140d7e 100644 --- a/code/datums/ai/movement/ai_movement_jps.dm +++ b/code/datums/ai/movement/ai_movement_jps.dm @@ -4,13 +4,15 @@ /datum/ai_movement/jps max_pathing_attempts = 20 var/maximum_length = AI_MAX_PATH_LENGTH + ///how we deal with diagonal movement, whether we try to avoid them or follow through with them + var/diagonal_flags = DIAGONAL_REMOVE_CLUNKY /datum/ai_movement/jps/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance) . = ..() var/atom/movable/moving = controller.pawn var/delay = controller.movement_delay - var/datum/move_loop/loop = SSmove_manager.jps_move(moving, + var/datum/move_loop/has_target/jps/loop = SSmove_manager.jps_move(moving, current_movement_target, delay, repath_delay = 0.5 SECONDS, @@ -18,6 +20,7 @@ minimum_distance = controller.get_minimum_distance(), access = controller.get_access(), subsystem = SSai_movement, + diagonal_handling = diagonal_flags, extra_info = controller, ) @@ -25,6 +28,8 @@ RegisterSignal(loop, COMSIG_MOVELOOP_POSTPROCESS, PROC_REF(post_move)) RegisterSignal(loop, COMSIG_MOVELOOP_JPS_REPATH, PROC_REF(repath_incoming)) + return loop + /datum/ai_movement/jps/proc/repath_incoming(datum/move_loop/has_target/jps/source) SIGNAL_HANDLER var/datum/ai_controller/controller = source.extra_info @@ -35,3 +40,11 @@ /datum/ai_movement/jps/bot max_pathing_attempts = 25 maximum_length = AI_BOT_PATH_LENGTH + diagonal_flags = DIAGONAL_REMOVE_ALL + +/datum/ai_movement/jps/bot/start_moving_towards(datum/ai_controller/controller, atom/current_movement_target, min_distance) + var/datum/move_loop/loop = ..() + var/atom/our_pawn = controller.pawn + if(isnull(our_pawn)) + return + our_pawn.RegisterSignal(loop, COMSIG_MOVELOOP_JPS_FINISHED_PATHING, TYPE_PROC_REF(/mob/living/basic/bot, generate_bot_path)) diff --git a/code/datums/components/energized.dm b/code/datums/components/energized.dm index 97ff670113f99..d637bc2eb767a 100644 --- a/code/datums/components/energized.dm +++ b/code/datums/components/energized.dm @@ -76,7 +76,7 @@ if(prob(100 - toast_prob)) if(prob(25)) do_sparks(1, FALSE, source) - playsound(src, SFX_SPARKS, 40, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) + playsound(parent, SFX_SPARKS, 40, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) source.audible_message(span_danger("[parent] makes an electric crackle...")) return FALSE @@ -116,10 +116,10 @@ header = "Electrifying!", ) do_sparks(4, FALSE, source) - playsound(src, SFX_SPARKS, 75, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) + playsound(parent, SFX_SPARKS, 75, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) source.audible_message(span_danger("[parent] makes a loud electric crackle!")) to_chat(future_tram_victim, span_userdanger("You hear a loud electric crackle!")) - future_tram_victim.electrocute_act(15, src, 1) + future_tram_victim.electrocute_act(15, parent, 1) return TRUE #undef NORMAL_TOAST_PROB diff --git a/code/datums/components/material/remote_materials.dm b/code/datums/components/material/remote_materials.dm index 376157903a6ce..33d85a5376f70 100644 --- a/code/datums/components/material/remote_materials.dm +++ b/code/datums/components/material/remote_materials.dm @@ -19,15 +19,24 @@ handles linking back and forth. var/allow_standalone ///Local size of container when silo = null var/local_size = INFINITY - ///Flags used when converting inserted materials into their component materials. + ///Flags used for the local material container(exceptions for item insert & intent flags) var/mat_container_flags = NONE - -/datum/component/remote_materials/Initialize(mapload, allow_standalone = TRUE, force_connect = FALSE, mat_container_flags = NONE) + ///List of signals to hook onto the local container + var/list/mat_container_signals + +/datum/component/remote_materials/Initialize( + mapload, + allow_standalone = TRUE, + force_connect = FALSE, + mat_container_flags = NONE, + list/mat_container_signals = null +) if (!isatom(parent)) return COMPONENT_INCOMPATIBLE src.allow_standalone = allow_standalone src.mat_container_flags = mat_container_flags + src.mat_container_signals = mat_container_signals RegisterSignal(parent, COMSIG_ATOM_TOOL_ACT(TOOL_MULTITOOL), PROC_REF(OnMultitool)) @@ -51,6 +60,8 @@ handles linking back and forth. * only if allow_standalone = TRUE, else you a null mat_container */ /datum/component/remote_materials/proc/_PrepareStorage(connect_to_silo) + PRIVATE_PROC(TRUE) + if (connect_to_silo) silo = GLOB.ore_silo_default if (silo) @@ -69,6 +80,8 @@ handles linking back and forth. return ..() /datum/component/remote_materials/proc/_MakeLocal() + PRIVATE_PROC(TRUE) + silo = null var/static/list/allowed_mats = list( @@ -90,6 +103,7 @@ handles linking back and forth. allowed_mats, \ local_size, \ mat_container_flags, \ + container_signals = mat_container_signals, \ allowed_items = /obj/item/stack \ ) @@ -268,7 +282,7 @@ handles linking back and forth. * * obj/item/weapon - the item you are trying to insert * * multiplier - the multiplier applied on the materials consumed */ -/datum/component/remote_materials/proc/insert_item(obj/item/weapon, multiplier) +/datum/component/remote_materials/proc/insert_item(obj/item/weapon, multiplier = 1) if(!_can_use_resource(FALSE)) return MATERIAL_INSERT_ITEM_FAILURE diff --git a/code/datums/components/wall_mounted.dm b/code/datums/components/wall_mounted.dm index 00482592f2b3f..a10fb2468334d 100644 --- a/code/datums/components/wall_mounted.dm +++ b/code/datums/components/wall_mounted.dm @@ -19,7 +19,7 @@ ADD_TRAIT(parent, TRAIT_WALLMOUNTED, REF(src)) RegisterSignal(hanging_wall_turf, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine)) RegisterSignal(hanging_wall_turf, COMSIG_TURF_CHANGE, PROC_REF(on_turf_changing)) - RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(drop_wallmount)) + RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(on_move)) RegisterSignal(parent, COMSIG_QDELETING, PROC_REF(on_linked_destroyed)) /datum/component/wall_mounted/UnregisterFromParent() @@ -51,6 +51,18 @@ if (ispath(path, /turf/open)) drop_wallmount() + +/** + * If we get dragged from our wall (by a singulo for instance) we should deconstruct + */ +/datum/component/wall_mounted/proc/on_move(datum/source, atom/old_loc, dir, forced, list/old_locs) + SIGNAL_HANDLER + // If we're having our lighting messed with we're likely to get dragged about + // That shouldn't lead to a decon + if(HAS_TRAIT(parent, TRAIT_LIGHTING_DEBUGGED)) + return + drop_wallmount() + /** * Handles the dropping of the linked object. This is done via deconstruction, as that should be the most sane way to handle it for most objects. * Except for intercoms, which are handled by creating a new wallframe intercom, as they're apparently items. @@ -68,6 +80,7 @@ if(!QDELING(src)) qdel(src) //Well, we fell off the wall, so we're done here. + /** * Checks object direction and then verifies if there's a wall in that direction. Finally, applies a wall_mounted component to the object. * diff --git a/code/datums/elements/hostile_machine.dm b/code/datums/elements/hostile_machine.dm new file mode 100644 index 0000000000000..0a5f19287bb24 --- /dev/null +++ b/code/datums/elements/hostile_machine.dm @@ -0,0 +1,15 @@ +/// AIs will attack this as a potential target if they see it +/datum/element/hostile_machine + element_flags = ELEMENT_DETACH_ON_HOST_DESTROY + +/datum/element/hostile_machine/Attach(datum/target) + . = ..() + + if (!isatom(target)) + return ELEMENT_INCOMPATIBLE + + GLOB.hostile_machines += target + +/datum/element/hostile_machine/Detach(datum/source) + GLOB.hostile_machines -= source + return ..() diff --git a/code/datums/elements/immerse.dm b/code/datums/elements/immerse.dm index 900c096aa6538..d4171588c31d2 100644 --- a/code/datums/elements/immerse.dm +++ b/code/datums/elements/immerse.dm @@ -96,8 +96,8 @@ /datum/element/immerse/proc/stop_immersion(turf/source) SIGNAL_HANDLER UnregisterSignal(source, list(COMSIG_ATOM_ABSTRACT_ENTERED, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON, COMSIG_ATOM_ABSTRACT_EXITED)) - for(var/atom/movable/movable as anything in attached_turfs_and_movables[source]) - remove_from_element(source, movable) + for(var/datum/weakref/movable as anything in attached_turfs_and_movables[source]) + remove_from_element(source, movable.resolve()) attached_turfs_and_movables -= source /** @@ -122,7 +122,7 @@ try_immerse(movable, buckled) RegisterSignal(movable, COMSIG_QDELETING, PROC_REF(on_movable_qdel)) - LAZYADD(attached_turfs_and_movables[source], movable) + LAZYADD(attached_turfs_and_movables[source], WEAKREF(movable)) ADD_TRAIT(movable, TRAIT_IMMERSED, ELEMENT_TRAIT(src)) /datum/element/immerse/proc/on_movable_qdel(atom/movable/source) @@ -170,7 +170,7 @@ movable.vis_contents |= vis_overlay - LAZYSET(immersed_movables, movable, vis_overlay) + LAZYSET(immersed_movables, WEAKREF(movable), vis_overlay) ///Initializes and caches a new visual overlay given parameters such as width, height and whether it should appear fully underwater. /datum/element/immerse/proc/generate_vis_overlay(width, height, is_below_water) @@ -212,11 +212,11 @@ ///This proc removes the vis_overlay, the keep together trait and some signals from the movable. /datum/element/immerse/proc/remove_immerse_overlay(atom/movable/movable) - var/atom/movable/immerse_overlay/vis_overlay = LAZYACCESS(immersed_movables, movable) + var/atom/movable/immerse_overlay/vis_overlay = LAZYACCESS(immersed_movables, WEAKREF(movable)) if(!vis_overlay) return movable.vis_contents -= vis_overlay - LAZYREMOVE(immersed_movables, movable) + LAZYREMOVE(immersed_movables, WEAKREF(movable)) if(HAS_TRAIT(movable, TRAIT_UNIQUE_IMMERSE)) UnregisterSignal(movable, list(COMSIG_ATOM_SPIN_ANIMATION, COMSIG_LIVING_POST_UPDATE_TRANSFORM)) qdel(vis_overlay) @@ -298,8 +298,8 @@ if(!(exited.loc in attached_turfs_and_movables)) remove_from_element(source, exited) else - LAZYREMOVE(attached_turfs_and_movables[source], exited) - LAZYADD(attached_turfs_and_movables[exited.loc], exited) + LAZYREMOVE(attached_turfs_and_movables[source], WEAKREF(exited)) + LAZYADD(attached_turfs_and_movables[exited.loc], WEAKREF(exited)) ///Remove any signal, overlay, trait given to the movable and reference to it within the element. /datum/element/immerse/proc/remove_from_element(turf/source, atom/movable/movable) @@ -311,7 +311,7 @@ UnregisterSignal(movable, list(COMSIG_LIVING_SET_BUCKLED, COMSIG_QDELETING)) REMOVE_TRAIT(movable, TRAIT_IMMERSED, ELEMENT_TRAIT(src)) - LAZYREMOVE(attached_turfs_and_movables[source], movable) + LAZYREMOVE(attached_turfs_and_movables[source], WEAKREF(movable)) /// A band-aid to keep the (unique) visual overlay from scaling and rotating along with its owner. I'm sorry. /datum/element/immerse/proc/on_update_transform(mob/living/source, resize, new_lying_angle, is_opposite_angle) @@ -320,7 +320,7 @@ new_transform.Scale(1/source.current_size) new_transform.Turn(-new_lying_angle) - var/atom/movable/immerse_overlay/vis_overlay = immersed_movables[source] + var/atom/movable/immerse_overlay/vis_overlay = immersed_movables[WEAKREF(source)] if(is_opposite_angle) vis_overlay.transform = new_transform vis_overlay.adjust_living_overlay_offset(source) @@ -361,7 +361,7 @@ ///Spin the overlay in the opposite direction so it doesn't look like it's spinning at all. /datum/element/immerse/proc/on_spin_animation(atom/source, speed, loops, segments, segment) SIGNAL_HANDLER - var/atom/movable/immerse_overlay/vis_overlay = immersed_movables[source] + var/atom/movable/immerse_overlay/vis_overlay = immersed_movables[WEAKREF(source)] vis_overlay.do_spin_animation(speed, loops, segments, -segment) ///We need to make sure to remove hard refs from the element when deleted. diff --git a/code/datums/json_database.dm b/code/datums/json_database.dm new file mode 100644 index 0000000000000..ea3ff354b48ce --- /dev/null +++ b/code/datums/json_database.dm @@ -0,0 +1,128 @@ +/// Represents a json file being used as a database in the data/ folder. +/// Changes made here will save back to the associated file, with recovery. +/// Will defer writes until later if multiple happen in the same tick. +/// Do not add an extra cache on top of this. This IS your cache. +/datum/json_database + VAR_PRIVATE + filepath + backup_filepath + + cached_data + save_queued = FALSE + + static/existing_json_database = list() + +/datum/json_database/New(filepath) + if (IsAdminAdvancedProcCall()) + to_chat(usr, "json_database creation, linking to [html_encode(filepath)], was blocked.", confidential = TRUE) + return + + ASSERT(isnull(existing_json_database[filepath]), "[filepath] already has an associated json_database. You must expose it somehow and use that instead of making a new one.") + + existing_json_database[filepath] = TRUE + + src.filepath = filepath + backup_filepath = "[filepath].savebac" + + if (fexists(filepath)) + cached_data = safe_json_decode(file2text(filepath)) + if (isnull(cached_data)) + var/scenario = "[filepath] existed, but did not have valid JSON" + + if (fexists(backup_filepath)) + load_backup(scenario) + else + stack_trace("[scenario]. No backup could be found.") + cached_data = list() + else + if (fexists(backup_filepath)) + load_backup("[filepath] didn't exist") + else + cached_data = list() + +/datum/json_database/Destroy() + if (save_queued) + save() + + existing_json_database -= filepath + + return ..() + +/// Returns the cached data. +/// Be careful on holding onto this data for too long, as it can mutate when other stuff changes it. +/// Do not mutate it yourself. +/datum/json_database/proc/get() + return cached_data + +/// Returns the data with the given key. +/// For arrays, this is a number. +/// Be careful on holding onto this data for too long, as it can mutate when other stuff changes it. +/// Do not mutate it yourself. +/datum/json_database/proc/get_key(key) + return cached_data[key] + +/// Sets the data at the key to the value, and queues a save. +/datum/json_database/proc/set_key(key, value) + cached_data[key] = value + queue_save() + +/// Removes the data at the given item, and queues a save. +/// For dictionaries, this can be the key. +/// For arrays, this can be the value. +/datum/json_database/proc/remove(item) + UNTYPED_LIST_REMOVE(cached_data, item) + queue_save() + +/// Inserts the data at the end of what is assumed to be an array, and queues a save. +/datum/json_database/proc/insert(value) + UNTYPED_LIST_ADD(cached_data, value) + queue_save() + +/// Replaces the cache with the new data completely, and queues a save. +/// Do not touch the new data after passing it in. +/datum/json_database/proc/replace(list/new_data) + cached_data = new_data + queue_save() + +/datum/json_database/proc/queue_save() + PRIVATE_PROC(TRUE) + + if (save_queued) + return + + addtimer(CALLBACK(src, PROC_REF(save)), 0) + +/datum/json_database/proc/save() + PRIVATE_PROC(TRUE) + + save_queued = FALSE + + if (fexists(filepath)) + rustg_file_write(file2text(filepath), backup_filepath) + + rustg_file_write(json_encode(cached_data, JSON_PRETTY_PRINT), filepath) + + ASSERT(!isnull(safe_json_decode(file2text(filepath))), "JSON written to [filepath] was not valid. Backup will be preserved.") + + fdel(backup_filepath) + +/datum/json_database/proc/load_backup(scenario) + PRIVATE_PROC(TRUE) + + var/cached_contents = file2text(backup_filepath) + var/list/backed_up_data = safe_json_decode(cached_contents) + + if (isnull(backed_up_data)) + stack_trace("[scenario]. Backup existed, but also did not have valid JSON.") + cached_data = list() + else + stack_trace("[scenario]. Backup existed and was used instead. The JSON file has been updated.") + cached_data = backed_up_data + rustg_file_write(cached_contents, filepath) + +/datum/json_database/vv_edit_var(var_name, var_value) + switch (var_name) + if (nameof(filepath), nameof(backup_filepath)) + return FALSE + else + return ..() diff --git a/code/datums/quirks/positive_quirks/tagger.dm b/code/datums/quirks/positive_quirks/tagger.dm index 4b0f48a1ca893..c2640081a6d96 100644 --- a/code/datums/quirks/positive_quirks/tagger.dm +++ b/code/datums/quirks/positive_quirks/tagger.dm @@ -1,10 +1,10 @@ /datum/quirk/item_quirk/tagger name = "Tagger" - desc = "You're an experienced artist. People will actually be impressed by your graffiti, and you can get twice as many uses out of drawing supplies." + desc = "You're an experienced artist. People will actually be impressed by your graffiti, and you can get twice as many uses out of drawing supplies in half the time." icon = FA_ICON_SPRAY_CAN value = 4 mob_trait = TRAIT_TAGGER - gain_text = span_notice("You know how to tag walls efficiently.") + gain_text = span_notice("You know how to tag walls efficiently and quickly.") lose_text = span_danger("You forget how to tag walls properly.") medical_record_text = "Patient was recently seen for possible paint huffing incident." mail_goodies = list( diff --git a/code/datums/station_traits/negative_traits.dm b/code/datums/station_traits/negative_traits.dm index 154d82cca74a5..b9d11b210e4ed 100644 --- a/code/datums/station_traits/negative_traits.dm +++ b/code/datums/station_traits/negative_traits.dm @@ -17,6 +17,30 @@ /datum/station_trait/distant_supply_lines/on_round_start() SSeconomy.pack_price_modifier *= 1.2 +///A negative trait that stops mail from arriving (or the inverse if on holiday). It also enables a specific shuttle loan situation. +/datum/station_trait/mail_blocked + name = "Postal workers strike" + trait_type = STATION_TRAIT_NEGATIVE + weight = 2 + show_in_report = TRUE + report_message = "Due to an ongoing strike announced by the postal workers union, mail won't be delivered this shift." + +/datum/station_trait/mail_blocked/on_round_start() + //This is either a holiday or sunday... well then, let's flip the situation. + if(SSeconomy.mail_blocked) + name = "Postal system overtime" + report_message = "Despite being a day off, the postal system is working overtime today. Mail will be delivered this shift." + else + var/datum/round_event_control/shuttle_loan/our_event = locate() in SSevents.control + our_event.unavailable_situations -= /datum/shuttle_loan_situation/mail_strike + SSeconomy.mail_blocked = !SSeconomy.mail_blocked + +/datum/station_trait/mail_blocked/hangover/revert() + var/datum/round_event_control/shuttle_loan/our_event = locate() in SSevents.control + our_event.unavailable_situations |= /datum/shuttle_loan_situation/mail_strike + SSeconomy.mail_blocked = !SSeconomy.mail_blocked + return ..() + ///A negative trait that reduces the amount of products available from vending machines throughout the station. /datum/station_trait/vending_shortage name = "Vending products shortage" diff --git a/code/datums/wires/canister.dm b/code/datums/wires/canister.dm deleted file mode 100644 index c692ff22872bf..0000000000000 --- a/code/datums/wires/canister.dm +++ /dev/null @@ -1,33 +0,0 @@ -/datum/wires/canister - holder_type = /obj/machinery/portable_atmospherics/canister - proper_name = "Canister" - -/datum/wires/canister/New(atom/holder) - wires = list(WIRE_VALVE, WIRE_SHIELDING, WIRE_REGULATOR_MIN, WIRE_REGULATOR_MAX, WIRE_TANK_EJECT, WIRE_REACTION_SUPPRESSION) - ..() - -/datum/wires/canister/on_pulse(wire) - var/obj/machinery/portable_atmospherics/canister/the_canister = holder - if(!the_canister.internal_cell) - return - switch(wire) - if(WIRE_VALVE) - the_canister.toggle_valve(usr, wire_pulsed = TRUE) - if(WIRE_SHIELDING) - the_canister.toggle_shielding(usr, wire_pulsed = TRUE) - if(WIRE_TANK_EJECT) - the_canister.eject_tank(usr, wire_pulsed = TRUE) - if(WIRE_REGULATOR_MIN) - the_canister.release_pressure = CAN_MIN_RELEASE_PRESSURE - the_canister.investigate_log("was set to [the_canister.release_pressure] kPa by [key_name(usr)] via wire pulse.", INVESTIGATE_ATMOS) - if(WIRE_REGULATOR_MAX) - the_canister.release_pressure = CAN_MAX_RELEASE_PRESSURE - the_canister.investigate_log("was set to [the_canister.release_pressure] kPa by [key_name(usr)] via wire pulse.", INVESTIGATE_ATMOS) - if(WIRE_REACTION_SUPPRESSION) - the_canister.toggle_reaction_suppression(usr, wire_pulsed = TRUE) - -/datum/wires/canister/can_reveal_wires(mob/user) - if(HAS_TRAIT(user, TRAIT_KNOW_ENGI_WIRES)) - return TRUE - - return ..() diff --git a/code/game/machinery/porta_turret/portable_turret.dm b/code/game/machinery/porta_turret/portable_turret.dm index 5a7e1da998424..76b1b73ba53f3 100644 --- a/code/game/machinery/porta_turret/portable_turret.dm +++ b/code/game/machinery/porta_turret/portable_turret.dm @@ -133,6 +133,8 @@ DEFINE_BITFIELD(turret_flags, list( if(!has_cover) INVOKE_ASYNC(src, PROC_REF(popUp)) + AddElement(/datum/element/hostile_machine) + /obj/machinery/porta_turret/proc/toggle_on(set_to) var/current = on if (!isnull(set_to)) diff --git a/code/game/objects/effects/spawners/random/contraband.dm b/code/game/objects/effects/spawners/random/contraband.dm index f5bf0f452a35c..ca5acbdbe6767 100644 --- a/code/game/objects/effects/spawners/random/contraband.dm +++ b/code/game/objects/effects/spawners/random/contraband.dm @@ -141,3 +141,59 @@ /obj/item/restraints/legcuffs/beartrap/prearmed = 5, //not really a landmine, but still a good threat /obj/effect/mine/shrapnel = 5, ) + +/obj/effect/spawner/random/contraband/grenades + name = "grenades spawner" + loot = list( + /obj/item/grenade/chem_grenade/metalfoam, + /obj/item/grenade/chem_grenade/cleaner, + /obj/effect/spawner/random/entertainment/colorful_grenades, + /obj/item/grenade/smokebomb, + /obj/item/grenade/chem_grenade/antiweed, + /obj/item/grenade/spawnergrenade/syndiesoap, + /obj/effect/spawner/random/contraband/grenades/dangerous, + ) + +/obj/effect/spawner/random/contraband/grenades/dangerous + name = "dangerous grenades spawner" + loot = list( + /obj/item/grenade/flashbang = 3, + /obj/item/grenade/chem_grenade/teargas = 2, + /obj/item/grenade/iedcasing/spawned = 2, + /obj/item/grenade/empgrenade = 2, + /obj/item/grenade/antigravity = 2, + /obj/effect/spawner/random/contraband/grenades/cluster = 1, + /obj/effect/spawner/random/contraband/grenades/lethal = 1, + ) + +/obj/effect/spawner/random/contraband/grenades/cluster + name = "clusterbusters spawner" + loot = list( + /obj/item/grenade/clusterbuster/smoke = 4, + /obj/item/grenade/clusterbuster/metalfoam = 4, + /obj/item/grenade/clusterbuster/cleaner = 4, + /obj/item/grenade/clusterbuster = 3, + /obj/item/grenade/clusterbuster/teargas = 3, + /obj/item/grenade/clusterbuster/antiweed = 3, + /obj/item/grenade/clusterbuster/soap = 2, + /obj/item/grenade/clusterbuster/emp = 1, + /obj/item/grenade/clusterbuster/spawner_spesscarp = 1, + /obj/item/grenade/clusterbuster/facid = 1, + /obj/item/grenade/clusterbuster/inferno = 1, + /obj/item/grenade/clusterbuster/clf3 = 1, + ) + +/obj/effect/spawner/random/contraband/grenades/lethal + name = "lethal grenades spawner" + loot = list( + /obj/item/grenade/chem_grenade/incendiary = 3, + /obj/item/grenade/chem_grenade/facid = 3, + /obj/item/grenade/chem_grenade/ez_clean = 3, + /obj/item/grenade/chem_grenade/clf3 = 2, + /obj/item/grenade/gluon = 2, + /obj/item/grenade/chem_grenade/holy = 2, + /obj/item/grenade/spawnergrenade/spesscarp = 1, + /obj/item/grenade/spawnergrenade/cat = 1, + /obj/item/grenade/frag = 1, + /obj/item/grenade/chem_grenade/bioterrorfoam = 1, + ) diff --git a/code/game/objects/effects/spawners/random/entertainment.dm b/code/game/objects/effects/spawners/random/entertainment.dm index 1c635f07a5aae..0a61cd1ab1fbb 100644 --- a/code/game/objects/effects/spawners/random/entertainment.dm +++ b/code/game/objects/effects/spawners/random/entertainment.dm @@ -281,3 +281,12 @@ /obj/item/toy/plush/ratplush = 2, /obj/item/toy/plush/narplush = 2, ) + +/obj/effect/spawner/random/entertainment/colorful_grenades + name = "colorful/glitter grenades spawner" + loot = list( + /obj/item/grenade/chem_grenade/glitter/pink, + /obj/item/grenade/chem_grenade/glitter/blue, + /obj/item/grenade/chem_grenade/glitter/white, + /obj/item/grenade/chem_grenade/colorful + ) diff --git a/code/game/objects/effects/spawners/random/food_or_drink.dm b/code/game/objects/effects/spawners/random/food_or_drink.dm index b2639b2df68d0..192914b6e3db6 100644 --- a/code/game/objects/effects/spawners/random/food_or_drink.dm +++ b/code/game/objects/effects/spawners/random/food_or_drink.dm @@ -323,3 +323,17 @@ /obj/item/food/donut/jelly/slimejelly/matcha = 2, /obj/item/food/donut/jelly/slimejelly/trumpet = 2, ) + +/obj/effect/spawner/random/food_or_drink/any_snack_or_beverage + name = "any snack or beverage spawner" + icon_state = "slime_jelly_donut" + loot = list( + /obj/effect/spawner/random/food_or_drink/snack = 6, + /obj/effect/spawner/random/food_or_drink/refreshing_beverage = 6, + /obj/effect/spawner/random/food_or_drink/donuts = 5, + /obj/effect/spawner/random/food_or_drink/donkpockets_single = 5, + /obj/effect/spawner/random/food_or_drink/booze = 4, + /obj/effect/spawner/random/food_or_drink/snack/lizard = 4, + /obj/effect/spawner/random/food_or_drink/jelly_donuts = 3, + /obj/effect/spawner/random/food_or_drink/slime_jelly_donuts = 1, + ) diff --git a/code/game/objects/items/crayons.dm b/code/game/objects/items/crayons.dm index 5ba709ef324ba..3090842b65ac8 100644 --- a/code/game/objects/items/crayons.dm +++ b/code/game/objects/items/crayons.dm @@ -14,6 +14,7 @@ #define AVAILABLE_SPRAYCAN_SPACE 8 // enough to fill one radial menu page +#define DRAW_TIME 5 SECONDS #define INFINITE_CHARGES -1 /* @@ -504,11 +505,13 @@ audible_message(span_notice("You hear spraying.")) playsound(user.loc, 'sound/effects/spray.ogg', 5, TRUE, 5) - var/wait_time = 50 + var/wait_time = DRAW_TIME if(paint_mode == PAINT_LARGE_HORIZONTAL) wait_time *= 3 + if(istagger) + wait_time *= 0.5 - if(!instant && !do_after(user, 50, target = target)) + if(!instant && !do_after(user, wait_time, target = target)) return if(!use_charges(user, cost)) @@ -762,6 +765,30 @@ /datum/component/slapcrafting,\ slapcraft_recipes = slapcraft_recipe_list,\ ) + register_context() + register_item_context() + +/obj/item/toy/crayon/spraycan/add_context(atom/source, list/context, obj/item/held_item, mob/living/user) + . = ..() + + if(!user.can_perform_action(src, NEED_DEXTERITY|NEED_HANDS)) + return . + + if(has_cap) + context[SCREENTIP_CONTEXT_ALT_LMB] = "Toggle cap" + + return CONTEXTUAL_SCREENTIP_SET + +/obj/item/toy/crayon/spraycan/add_item_context(datum/source, list/context, atom/target, mob/living/user) + . = ..() + + if(!user.can_perform_action(src, NEED_DEXTERITY|NEED_HANDS)) + return . + + context[SCREENTIP_CONTEXT_LMB] = "Paint" + context[SCREENTIP_CONTEXT_RMB] = "Copy color" + + return CONTEXTUAL_SCREENTIP_SET /obj/item/toy/crayon/spraycan/isValidSurface(surface) return (isfloorturf(surface) || iswallturf(surface)) @@ -1040,3 +1067,4 @@ #undef PAINT_LARGE_HORIZONTAL_ICON #undef INFINITE_CHARGES +#undef DRAW_TIME diff --git a/code/game/objects/items/mail.dm b/code/game/objects/items/mail.dm index 3c75a708f9c4c..61c0beed6efcf 100644 --- a/code/game/objects/items/mail.dm +++ b/code/game/objects/items/mail.dm @@ -152,14 +152,18 @@ /obj/item/mail/examine_more(mob/user) . = ..() - var/list/msg = list(span_notice("You notice the postmarking on the front of the mail...")) + if(!postmarked) + . += span_info("This mail has no postmarking of any sort...") + else + . += span_notice("You notice the postmarking on the front of the mail...") var/datum/mind/recipient = recipient_ref.resolve() if(recipient) - msg += "\t[span_info("Certified NT mail for [recipient].")]" + . += span_info("[postmarked ? "Certified NT" : "Uncertfieid"] mail for [recipient].") + else if(postmarked) + . += span_info("Certified mail for [GLOB.station_name].") else - msg += "\t[span_info("Certified mail for [GLOB.station_name].")]" - msg += "\t[span_info("Distribute by hand or via destination tagger using the certified NT disposal system.")]" - return msg + . += span_info("This is a dead letter mail with no recipient.") + . += span_info("Distribute by hand or via destination tagger using the certified NT disposal system.") /// Accepts a mind to initialize goodies for a piece of mail. /obj/item/mail/proc/initialize_for_recipient(datum/mind/recipient) @@ -249,6 +253,8 @@ lid_x = -26 lid_y = 2 paint_jobs = null + ///if it'll show the nt mark on the crate + var/postmarked = TRUE /obj/structure/closet/crate/mail/update_icon_state() . = ..() @@ -259,6 +265,11 @@ else icon_state = "[base_icon_state]sealed" +/obj/structure/closet/crate/mail/update_overlays() + . = ..() + if(postmarked) + . += "mail_nt" + /// Fills this mail crate with N pieces of mail, where N is the lower of the amount var passed, and the maximum capacity of this crate. If N is larger than the number of alive human players, the excess will be junkmail. /obj/structure/closet/crate/mail/proc/populate(amount) var/mail_count = min(amount, storage_capacity) @@ -304,6 +315,19 @@ . = ..() populate(INFINITY) +///Used in the mail strike shuttle loan event +/obj/structure/closet/crate/mail/full/mail_strike + desc = "A post crate from somewhere else. It has no NT logo on it." + postmarked = FALSE + +/obj/structure/closet/crate/mail/full/mail_strike/populate(amount) + var/strike_mail_to_spawn = rand(1, storage_capacity-1) + for(var/i in 1 to strike_mail_to_spawn) + if(prob(95)) + new /obj/item/mail/mail_strike(src) + else + new /obj/item/mail/traitor/mail_strike(src) + return ..(storage_capacity - strike_mail_to_spawn) /// Opened mail crate /obj/structure/closet/crate/mail/preopen @@ -381,7 +405,8 @@ playsound(loc, 'sound/items/poster_ripped.ogg', vol = 50, vary = TRUE) for(var/obj/item/stuff as anything in contents) // Mail and envelope actually can have more than 1 item. if(user.put_in_hands(stuff) && armed) - log_bomber(user, "opened armed mail made by [made_by_cached_name] ([made_by_cached_ckey]), activating", stuff) + var/whomst = made_by_cached_name ? "[made_by_cached_name] ([made_by_cached_ckey])" : "no one in particular" + log_bomber(user, "opened armed mail made by [whomst], activating", stuff) INVOKE_ASYNC(stuff, TYPE_PROC_REF(/obj/item, attack_self), user) qdel(src) return TRUE @@ -412,6 +437,55 @@ after_unwrap(user) return TRUE +///Generic mail used in the mail strike shuttle loan event +/obj/item/mail/mail_strike + name = "dead mail" + desc = "An unmarked parcel of unknown origins, effectively undeliverable." + postmarked = FALSE + generic_goodies = list( + /obj/effect/spawner/random/entertainment/money_medium = 2, + /obj/effect/spawner/random/contraband = 2, + /obj/effect/spawner/random/entertainment/money_large = 1, + /obj/effect/spawner/random/entertainment/coin = 1, + /obj/effect/spawner/random/food_or_drink/any_snack_or_beverage = 1, + /obj/effect/spawner/random/entertainment/drugs = 1, + /obj/effect/spawner/random/contraband/grenades = 1, + ) + +/obj/item/mail/mail_strike/Initialize(mapload) + if(prob(35)) + stamped = FALSE + if(prob(35)) + name = "dead envelope" + icon_state = "mail_large" + goodie_count = 2 + stamp_max = 2 + stamp_offset_y = 5 + . = ..() + color = pick(COLOR_SILVER, COLOR_DARK, COLOR_DRIED_TAN, COLOR_ORANGE_BROWN, COLOR_BROWN, COLOR_SYNDIE_RED) + for(var/goodie in 1 to goodie_count) + var/target_good = pick_weight(generic_goodies) + new target_good(src) + +///Also found in the mail strike shuttle loan. It contains a random grenade that'll be triggered when unwrapped +/obj/item/mail/traitor/mail_strike + name = "dead mail" + desc = "An unmarked parcel of unknown origins, effectively undeliverable." + postmarked = FALSE + +/obj/item/mail/traitor/mail_strike/Initialize(mapload) + if(prob(35)) + stamped = FALSE + if(prob(35)) + name = "dead envelope" + icon_state = "mail_large" + goodie_count = 2 + stamp_max = 2 + stamp_offset_y = 5 + . = ..() + color = pick(COLOR_SILVER, COLOR_DARK, COLOR_DRIED_TAN, COLOR_ORANGE_BROWN, COLOR_BROWN, COLOR_SYNDIE_RED) + new /obj/effect/spawner/random/contraband/grenades/dangerous(src) + /obj/item/storage/mail_counterfeit_device name = "GLA-2 mail counterfeit device" desc = "Device that actually able to counterfeit NT's mail. This device also able to place a trap inside of mail for malicious actions. Trap will \"activate\" any item inside of mail. Also it might be used for contraband purposes. Integrated micro-computer will give you great configuration optionality for your needs." diff --git a/code/game/objects/items/syndie_spraycan.dm b/code/game/objects/items/syndie_spraycan.dm index deab6229a28fe..02a576a2cb97d 100644 --- a/code/game/objects/items/syndie_spraycan.dm +++ b/code/game/objects/items/syndie_spraycan.dm @@ -1,3 +1,5 @@ +#define SYNDIE_DRAW_TIME 3 SECONDS + // Extending the existing spraycan item was more trouble than it was worth, I don't want or need this to be able to draw arbitrary shapes. /obj/item/traitor_spraycan name = "seditious spraycan" @@ -79,7 +81,12 @@ /obj/item/traitor_spraycan/proc/try_draw_step(start_output, mob/living/user, atom/target) drawing_rune = TRUE user.balloon_alert(user, "[start_output]") - if (!do_after(user, 3 SECONDS, target)) + var/wait_time = SYNDIE_DRAW_TIME + + if(HAS_TRAIT(user, TRAIT_TAGGER)) + wait_time *= 0.5 + + if(!do_after(user, wait_time, target)) user.balloon_alert(user, "interrupted!") drawing_rune = FALSE return FALSE @@ -220,6 +227,7 @@ return ..() +#undef SYNDIE_DRAW_TIME #undef RUNE_STAGE_COLOURED #undef RUNE_STAGE_COMPLETE #undef RUNE_STAGE_OUTLINE diff --git a/code/game/turfs/closed/walls.dm b/code/game/turfs/closed/walls.dm index d4ff7b38ef4ba..e660e95b72e11 100644 --- a/code/game/turfs/closed/walls.dm +++ b/code/game/turfs/closed/walls.dm @@ -1,4 +1,3 @@ -#define MAX_DENT_DECALS 15 #define LEANING_OFFSET 11 /turf/closed/wall @@ -387,5 +386,4 @@ . = ..() SEND_SIGNAL(gone, COMSIG_LIVING_WALL_EXITED, src) -#undef MAX_DENT_DECALS #undef LEANING_OFFSET diff --git a/code/modules/antagonists/heretic/magic/void_phase.dm b/code/modules/antagonists/heretic/magic/void_phase.dm index a3f16a2aeaf18..350ca0f29c100 100644 --- a/code/modules/antagonists/heretic/magic/void_phase.dm +++ b/code/modules/antagonists/heretic/magic/void_phase.dm @@ -1,6 +1,6 @@ /datum/action/cooldown/spell/pointed/void_phase name = "Void Phase" - desc = "Let's you blink to your pointed destination, causes 3x3 aoe damage bubble \ + desc = "Lets you blink to your pointed destination, causes 3x3 aoe damage bubble \ around your pointed destination and your current location. \ It has a minimum range of 3 tiles and a maximum range of 9 tiles." background_icon_state = "bg_heretic" diff --git a/code/modules/antagonists/nukeop/equipment/nuclear_challenge.dm b/code/modules/antagonists/nukeop/equipment/nuclear_challenge.dm index 734025e9a370e..b2114a8317039 100644 --- a/code/modules/antagonists/nukeop/equipment/nuclear_challenge.dm +++ b/code/modules/antagonists/nukeop/equipment/nuclear_challenge.dm @@ -1,6 +1,11 @@ #define CHALLENGE_TELECRYSTALS 280 #define CHALLENGE_TIME_LIMIT (5 MINUTES) #define CHALLENGE_SHUTTLE_DELAY (25 MINUTES) // 25 minutes, so the ops have at least 5 minutes before the shuttle is callable. +// MASSMETA EDIT START +#define WAR_TC_MIN 125 +#define WAR_TC_MAX 280 +#define WAR_MAX_PAYOUT 50 +// MASSMETA EDIT END GLOBAL_LIST_EMPTY(jam_on_wardec) @@ -22,7 +27,11 @@ GLOBAL_LIST_EMPTY(jam_on_wardec) return declaring_war = TRUE - var/are_you_sure = tgui_alert(user, "Consult your team carefully before you declare war on [station_name()]. Are you sure you want to alert the enemy crew? You have [DisplayTimeText(CHALLENGE_TIME_LIMIT - world.time - SSticker.round_start_time)] to decide.", "Declare war?", list("Yes", "No")) +// MASSMETA EDIT START + var/are_you_sure = tgui_alert(user, "Consult your team carefully before you declare war on [station_name()]]. Are you sure you want to alert the enemy crew? You will get \ + [round(WAR_TC_MIN + (WAR_TC_MAX - WAR_TC_MIN) * (1 - (WAR_MAX_PAYOUT - min(WAR_MAX_PAYOUT, GLOB.joined_player_list.len)) / (WAR_MAX_PAYOUT - CHALLENGE_MIN_PLAYERS)), 1)] \ + extra telecystals. You have [DisplayTimeText(CHALLENGE_TIME_LIMIT - world.time - SSticker.round_start_time)] to decide", "Declare war?", list("Yes", "No")) +// MASSMETA EDIT END declaring_war = FALSE if(!check_allowed(user)) @@ -114,7 +123,7 @@ GLOBAL_LIST_EMPTY(jam_on_wardec) continue uplinks += uplink - var/tc_to_distribute = CHALLENGE_TELECRYSTALS + var/tc_to_distribute = round(WAR_TC_MIN + (WAR_TC_MAX - WAR_TC_MIN) * (1 - (WAR_MAX_PAYOUT - min(WAR_MAX_PAYOUT, GLOB.joined_player_list.len)) / (WAR_MAX_PAYOUT - CHALLENGE_MIN_PLAYERS)), 1) // MASSMETA EDIT var/tc_per_nukie = round(tc_to_distribute / (length(orphans)+length(uplinks))) for (var/datum/component/uplink/uplink in uplinks) @@ -197,3 +206,8 @@ GLOBAL_LIST_EMPTY(jam_on_wardec) #undef CHALLENGE_TELECRYSTALS #undef CHALLENGE_TIME_LIMIT #undef CHALLENGE_SHUTTLE_DELAY +// MASSMETA EDIT START +#undef WAR_TC_MIN +#undef WAR_TC_MAX +#undef WAR_MAX_PAYOUT +// MASSMETA EDIT END diff --git a/code/modules/asset_cache/asset_list.dm b/code/modules/asset_cache/asset_list.dm index 7e5785250a5b0..e4a00ffb8bf38 100644 --- a/code/modules/asset_cache/asset_list.dm +++ b/code/modules/asset_cache/asset_list.dm @@ -165,7 +165,7 @@ GLOBAL_LIST_EMPTY(asset_datums) if (isnull(should_refresh)) // `fexists` seems to always fail on static-time - should_refresh = !fexists("[ASSET_CROSS_ROUND_CACHE_DIRECTORY]/spritesheet.[name].css") + should_refresh = !fexists(css_cache_filename()) || !fexists(data_cache_filename()) return should_refresh @@ -296,8 +296,17 @@ GLOBAL_LIST_EMPTY(asset_datums) return out.Join("\n") +/datum/asset/spritesheet/proc/css_cache_filename() + return "[ASSET_CROSS_ROUND_CACHE_DIRECTORY]/spritesheet.[name].css" + +/datum/asset/spritesheet/proc/data_cache_filename() + return "[ASSET_CROSS_ROUND_CACHE_DIRECTORY]/spritesheet.[name].json" + /datum/asset/spritesheet/proc/read_from_cache() - var/replaced_css = file2text("[ASSET_CROSS_ROUND_CACHE_DIRECTORY]/spritesheet.[name].css") + return read_css_from_cache() && read_data_from_cache() + +/datum/asset/spritesheet/proc/read_css_from_cache() + var/replaced_css = file2text(css_cache_filename()) var/regex/find_background_urls = regex(@"background:url\('%(.+?)%'\)", "g") while (find_background_urls.Find(replaced_css)) @@ -319,6 +328,14 @@ GLOBAL_LIST_EMPTY(asset_datums) return TRUE +/datum/asset/spritesheet/proc/read_data_from_cache() + var/json = json_decode(file2text(data_cache_filename())) + + if (islist(json["sprites"])) + sprites = json["sprites"] + + return TRUE + /datum/asset/spritesheet/proc/send_from_cache(client/client) if (isnull(cached_spritesheets_needed)) stack_trace("cached_spritesheets_needed was null when sending assets from [type] from cache") @@ -334,6 +351,10 @@ GLOBAL_LIST_EMPTY(asset_datums) return SSassets.transport.get_asset_url(asset) /datum/asset/spritesheet/proc/write_to_cache() + write_css_to_cache() + write_data_to_cache() + +/datum/asset/spritesheet/proc/write_css_to_cache() for (var/size_id in sizes) fcopy(SSassets.cache["[name]_[size_id].png"].resource, "[ASSET_CROSS_ROUND_CACHE_DIRECTORY]/spritesheet.[name]_[size_id].png") @@ -341,7 +362,12 @@ GLOBAL_LIST_EMPTY(asset_datums) var/mock_css = generate_css() generating_cache = FALSE - rustg_file_write(mock_css, "[ASSET_CROSS_ROUND_CACHE_DIRECTORY]/spritesheet.[name].css") + rustg_file_write(mock_css, css_cache_filename()) + +/datum/asset/spritesheet/proc/write_data_to_cache() + rustg_file_write(json_encode(list( + "sprites" = sprites, + )), data_cache_filename()) /datum/asset/spritesheet/proc/get_cached_url_mappings() var/list/mappings = list() diff --git a/code/modules/atmospherics/machinery/portable/canister.dm b/code/modules/atmospherics/machinery/portable/canister.dm index bef8eb7300d87..18fe9817e8115 100644 --- a/code/modules/atmospherics/machinery/portable/canister.dm +++ b/code/modules/atmospherics/machinery/portable/canister.dm @@ -38,12 +38,6 @@ var/obj/item/stock_parts/cell/internal_cell ///used while processing to update appearance only when its pressure state changes var/current_pressure_state - /// An assembly attached to the canister - var/obj/item/assembly_holder/rig = null - /// The person who attached an assembly to this canister, usually for ignition purposes in tandem with valve wire pulse - var/mob/last_rigger = "" - //overlay of attached assemblies - var/mutable_appearance/assemblies_overlay /datum/armor/portable_atmospherics_canister melee = 50 @@ -60,8 +54,6 @@ if(mapload) internal_cell = new /obj/item/stock_parts/cell/high(src) - set_wires(new /datum/wires/canister(src)) - if(existing_mixture) air_contents.copy_from(existing_mixture) else @@ -81,17 +73,6 @@ AddComponent(/datum/component/gas_leaker, leak_rate=0.01) register_context() -/obj/machinery/portable_atmospherics/canister/Destroy() - QDEL_NULL(wires) - return ..() - -/obj/machinery/portable_atmospherics/canister/Exited(atom/movable/gone) - if(gone == rig) - rig = null - last_rigger = null - cut_overlays(assemblies_overlay) - return ..() - /obj/machinery/portable_atmospherics/canister/interact(mob/user) . = ..() if(!allowed(user)) @@ -101,14 +82,10 @@ /obj/machinery/portable_atmospherics/canister/add_context(atom/source, list/context, obj/item/held_item, mob/user) . = ..() - if(rig) - context[SCREENTIP_CONTEXT_RMB] = "Remove rigged device" if(holding) context[SCREENTIP_CONTEXT_ALT_LMB] = "Remove tank" if(!held_item) return CONTEXTUAL_SCREENTIP_SET - if(istype(held_item, /obj/item/assembly_holder)) - context[SCREENTIP_CONTEXT_LMB] = "Rig" if(istype(held_item, /obj/item/stock_parts/cell)) context[SCREENTIP_CONTEXT_LMB] = "Insert cell" switch(held_item.tool_behaviour) @@ -132,11 +109,6 @@ . += span_notice("Warning, no cell installed, use a screwdriver to open the hatch and insert one.") if(panel_open) . += span_notice("Hatch open, close it with a screwdriver.") - if(get_dist(user, src) <= 2) - if(rig) - . += span_warning("There is some kind of device rigged to the canister!") - else - . += span_notice("It looks like you could rig a device to the canister.") // Please keep the canister types sorted // Basic canister per gas below here @@ -411,50 +383,8 @@ balloon_alert(user, "you install the cell") internal_cell = active_cell return TRUE - if(is_wire_tool(item) && panel_open) - wires.interact(user) - return - if(istype(item, /obj/item/assembly_holder)) - if(rig) - balloon_alert(user, "another device is in the way!") - return ..() - var/obj/item/assembly_holder/holder = item - - user.balloon_alert_to_viewers("attaching rig...") - add_fingerprint(user) - if(!do_after(user, 2 SECONDS, target = src) || !user.transferItemToLoc(holder, src)) - return - rig = holder - holder.master = src - holder.on_attach() - assemblies_overlay = holder - assemblies_overlay.layer = 3.3 - assemblies_overlay.pixel_y -= 5 - add_overlay(assemblies_overlay) - log_bomber(user, "attached [holder.name] to ", src) - last_rigger = user - user.balloon_alert_to_viewers("attached rig") - return - return ..() -/obj/machinery/portable_atmospherics/canister/attack_hand_secondary(mob/user, list/modifiers) - if(!rig) - return - // mousetrap rigs only make sense if you can set them off, can't step on them - // If you see a mousetrap-rigged fuel tank, just leave it alone - rig.on_found() - if(QDELETED(src)) - return - user.balloon_alert_to_viewers("detaching rig...") - if(!do_after(user, 2 SECONDS, target = src)) - return - user.balloon_alert_to_viewers("detached rig") - user.log_message("detached [rig] from [src].", LOG_GAME) - if(!user.put_in_hands(rig)) - rig.forceMove(get_turf(user)) - return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN - /obj/machinery/portable_atmospherics/canister/screwdriver_act(mob/living/user, obj/item/screwdriver) if(default_deconstruction_screwdriver(user, icon_state, icon_state, screwdriver)) update_appearance() @@ -723,39 +653,47 @@ update_appearance() - /// Opens/closes the canister valve /obj/machinery/portable_atmospherics/canister/proc/toggle_valve(mob/user, wire_pulsed = FALSE) - var/logmsg - var/admin_msg - var/danger = FALSE - var/n = 0 valve_open = !valve_open - if(valve_open) - SSair.start_processing_machine(src) - logmsg = "Valve was opened by [key_name(user)] [wire_pulsed ? "via wire pulse" : ""], starting a transfer into \the [holding || "air"].
" - if(!holding) - var/list/gaseslog = list() //list for logging all gases in canister - for(var/id in air_contents.gases) - var/gas = air_contents.gases[id] - gaseslog[gas[GAS_META][META_GAS_NAME]] = gas[MOLES] //adds gases to gaseslog - if(!gas[GAS_META][META_GAS_DANGER]) - continue - if(gas[MOLES] > (gas[GAS_META][META_GAS_MOLES_VISIBLE] || MOLES_GAS_VISIBLE)) //if moles_visible is undefined, default to default visibility - danger = TRUE //at least 1 danger gas - logmsg = "[key_name(user)] opened a canister [wire_pulsed ? "via wire pulse" : ""] that contains the following:" - admin_msg = "[ADMIN_LOOKUPFLW(user)] opened a canister [wire_pulsed ? "via wire pulse" : ""] that contains the following at [ADMIN_VERBOSEJMP(src)]:" - for(var/name in gaseslog) - n = n + 1 - logmsg += "\n[name]: [gaseslog[name]] moles." - if(n <= 5) //the first five gases added - admin_msg += "\n[name]: [gaseslog[name]] moles." - if(n == 5 && length(gaseslog) > 5) //message added if more than 5 gases - admin_msg += "\nToo many gases to log. Check investigate log." - if(danger) //sent to admin's chat if contains dangerous gases - message_admins(admin_msg) - else - logmsg = "valve was closed by [key_name(user)] [wire_pulsed ? "via wire pulse" : ""], stopping the transfer into \the [holding || "air"].
" + if(!valve_open) + var/logmsg = "valve was closed by [key_name(user)] [wire_pulsed ? "via wire pulse" : ""], stopping the transfer into \the [holding || "air"].
" + investigate_log(logmsg, INVESTIGATE_ATMOS) + release_log += logmsg + return + + SSair.start_processing_machine(src) + if(holding) + var/logmsg = "Valve was opened by [key_name(user)] [wire_pulsed ? "via wire pulse" : ""], starting a transfer into \the [holding || "air"].
" + investigate_log(logmsg, INVESTIGATE_ATMOS) + release_log += logmsg + return + + // Go over the gases in canister, pull all their info and mark the spooky ones + var/list/output = list() + output += "[key_name(user)] opened a canister [wire_pulsed ? "via wire pulse" : ""] that contains the following:" + var/list/admin_output = list() + admin_output += "[ADMIN_LOOKUPFLW(user)] opened a canister [wire_pulsed ? "via wire pulse" : ""] that contains the following at [ADMIN_VERBOSEJMP(src)]:" + var/list/gases = air_contents.gases + var/danger = FALSE + for(var/gas_index in 1 to length(gases)) + var/list/gas_info = gases[gases[gas_index]] + var/list/meta = gas_info[GAS_META] + var/name = meta[META_GAS_NAME] + var/moles = gas_info[MOLES] + + output += "[name]: [moles] moles." + if(gas_index <= 5) //the first five gases added + admin_output += "[name]: [moles] moles." + else if(gas_index == 6) // anddd the warning + admin_output += "Too many gases to log. Check investigate log." + //if moles_visible is undefined, default to default visibility + if(meta[META_GAS_DANGER] && moles > (meta[META_GAS_MOLES_VISIBLE] || MOLES_GAS_VISIBLE)) + danger = TRUE + + if(danger) //sent to admin's chat if contains dangerous gases + message_admins(admin_output.Join("\n")) + var/logmsg = output.Join("\n") investigate_log(logmsg, INVESTIGATE_ATMOS) release_log += logmsg @@ -769,12 +707,13 @@ /// Ejects tank from canister, if any /obj/machinery/portable_atmospherics/canister/proc/eject_tank(mob/user, wire_pulsed = FALSE) - if(holding) - if(valve_open) - message_admins("[ADMIN_LOOKUPFLW(user)] removed [holding] from [src] with valve still open [wire_pulsed ? "via wire pulse" : ""] at [ADMIN_VERBOSEJMP(src)] releasing contents into the [span_boldannounce("air")].") - user.investigate_log("removed the [holding] [wire_pulsed ? "via wire pulse" : ""], leaving the valve open and transferring into the [span_boldannounce("air")].", INVESTIGATE_ATMOS) - replace_tank(user, FALSE) - return TRUE + if(!holding) + return FALSE + if(valve_open) + message_admins("[ADMIN_LOOKUPFLW(user)] removed [holding] from [src] with valve still open [wire_pulsed ? "via wire pulse" : ""] at [ADMIN_VERBOSEJMP(src)] releasing contents into the [span_boldannounce("air")].") + user.investigate_log("removed the [holding] [wire_pulsed ? "via wire pulse" : ""], leaving the valve open and transferring into the [span_boldannounce("air")].", INVESTIGATE_ATMOS) + replace_tank(user, FALSE) + return TRUE /// Turns hyper-noblium crystal reaction suppression in the canister on or off /obj/machinery/portable_atmospherics/canister/proc/toggle_reaction_suppression(mob/user, wire_pulsed = FALSE) diff --git a/code/modules/events/shuttle_loan/shuttle_loan_datum.dm b/code/modules/events/shuttle_loan/shuttle_loan_datum.dm index b0c8775cba25c..706274dfd85bf 100644 --- a/code/modules/events/shuttle_loan/shuttle_loan_datum.dm +++ b/code/modules/events/shuttle_loan/shuttle_loan_datum.dm @@ -7,7 +7,7 @@ /// What the shuttle says about it. var/shuttle_transit_text = "Unset transit text" /// Supply points earned for taking the deal. - var/bonus_points = 10000 + var/bonus_points = CARGO_CRATE_VALUE * 50 /// Response for taking the deal. var/thanks_msg = "The cargo shuttle should return in five minutes. Have some supply points for your trouble." /// Small description of the loan for easier log reading. @@ -21,7 +21,7 @@ bonus_points *= 1.15 /// Spawns paths added to `spawn_list`, and passes empty shuttle turfs so you can spawn more complicated things like dead bodies. -/datum/shuttle_loan_situation/proc/spawn_items(list/spawn_list, list/empty_shuttle_turfs) +/datum/shuttle_loan_situation/proc/spawn_items(list/spawn_list, list/empty_shuttle_turfs, list/blocked_shutte_turfs) SHOULD_CALL_PARENT(FALSE) CRASH("Unimplemented get_spawned_items() on [src.type].") @@ -31,7 +31,7 @@ shuttle_transit_text = "Virus samples incoming." logging_desc = "Virus shuttle" -/datum/shuttle_loan_situation/antidote/spawn_items(list/spawn_list, list/empty_shuttle_turfs) +/datum/shuttle_loan_situation/antidote/spawn_items(list/spawn_list, list/empty_shuttle_turfs, list/blocked_shutte_turfs) var/obj/effect/mob_spawn/corpse/human/assistant/infected_assistant = pick(list( /obj/effect/mob_spawn/corpse/human/assistant/beesease_infection, /obj/effect/mob_spawn/corpse/human/assistant/brainrot_infection, @@ -58,7 +58,7 @@ bonus_points = 0 logging_desc = "Resupply packages" -/datum/shuttle_loan_situation/department_resupply/spawn_items(list/spawn_list, list/empty_shuttle_turfs) +/datum/shuttle_loan_situation/department_resupply/spawn_items(list/spawn_list, list/empty_shuttle_turfs, list/blocked_shutte_turfs) var/list/crate_types = list( /datum/supply_pack/emergency/equipment, /datum/supply_pack/security/supplies, @@ -84,7 +84,7 @@ shuttle_transit_text = "Syndicate hijack team incoming." logging_desc = "Syndicate boarding party" -/datum/shuttle_loan_situation/syndiehijacking/spawn_items(list/spawn_list, list/empty_shuttle_turfs) +/datum/shuttle_loan_situation/syndiehijacking/spawn_items(list/spawn_list, list/empty_shuttle_turfs, list/blocked_shutte_turfs) var/datum/supply_pack/pack = SSshuttle.supply_packs[/datum/supply_pack/imports/specialops] pack.generate(pick_n_take(empty_shuttle_turfs)) @@ -99,10 +99,10 @@ sender = "CentCom Janitorial Division" announcement_text = "One of our freighters carrying a bee shipment has been attacked by eco-terrorists. Can you clean up the mess for us?" shuttle_transit_text = "Biohazard cleanup incoming." - bonus_points = 20000 //Toxin bees can be unbeelievably lethal + bonus_points = CARGO_CRATE_VALUE * 100 //Toxin bees can be unbeelievably lethal logging_desc = "Shuttle full of bees" -/datum/shuttle_loan_situation/lots_of_bees/spawn_items(list/spawn_list, list/empty_shuttle_turfs) +/datum/shuttle_loan_situation/lots_of_bees/spawn_items(list/spawn_list, list/empty_shuttle_turfs, list/blocked_shutte_turfs) var/datum/supply_pack/pack = SSshuttle.supply_packs[/datum/supply_pack/organic/hydroponics/beekeeping_fullkit] pack.generate(pick_n_take(empty_shuttle_turfs)) @@ -135,10 +135,10 @@ announcement_text = "We have discovered an active Syndicate bomb near our VIP shuttle's fuel lines. If you feel up to the task, we will pay you for defusing it." shuttle_transit_text = "Live explosive ordnance incoming. Exercise extreme caution." thanks_msg = "Live explosive ordnance incoming via supply shuttle. Evacuating cargo bay is recommended." - bonus_points = 45000 //If you mess up, people die and the shuttle gets turned into swiss cheese + bonus_points = CARGO_CRATE_VALUE * 225 //If you mess up, people die and the shuttle gets turned into swiss cheese logging_desc = "Shuttle with a ticking bomb" -/datum/shuttle_loan_situation/jc_a_bomb/spawn_items(list/spawn_list, list/empty_shuttle_turfs) +/datum/shuttle_loan_situation/jc_a_bomb/spawn_items(list/spawn_list, list/empty_shuttle_turfs, list/blocked_shutte_turfs) spawn_list.Add(/obj/machinery/syndicatebomb/shuttle_loan) if(prob(95)) spawn_list.Add(/obj/item/paper/fluff/cargo/bomb) @@ -153,7 +153,7 @@ bonus_points = 0 //Payout is made when the stamped papers are returned logging_desc = "Paperwork shipment" -/datum/shuttle_loan_situation/papers_please/spawn_items(list/spawn_list, list/empty_shuttle_turfs) +/datum/shuttle_loan_situation/papers_please/spawn_items(list/spawn_list, list/empty_shuttle_turfs, list/blocked_shutte_turfs) spawn_list += subtypesof(/obj/item/paperwork) - typesof(/obj/item/paperwork/photocopy) - typesof(/obj/item/paperwork/ancient) /datum/shuttle_loan_situation/pizza_delivery @@ -164,7 +164,7 @@ bonus_points = 0 logging_desc = "Pizza delivery" -/datum/shuttle_loan_situation/pizza_delivery/spawn_items(list/spawn_list, list/empty_shuttle_turfs) +/datum/shuttle_loan_situation/pizza_delivery/spawn_items(list/spawn_list, list/empty_shuttle_turfs, list/blocked_shutte_turfs) var/naughtypizza = list(/obj/item/pizzabox/bomb, /obj/item/pizzabox/margherita/robo) //oh look another blacklist, for pizza nonetheless! var/nicepizza = list(/obj/item/pizzabox/margherita, /obj/item/pizzabox/meat, /obj/item/pizzabox/vegetable, /obj/item/pizzabox/mushroom) for(var/i in 1 to 6) @@ -176,7 +176,7 @@ shuttle_transit_text = "Partying Russians incoming." logging_desc = "Russian party squad" -/datum/shuttle_loan_situation/russian_party/spawn_items(list/spawn_list, list/empty_shuttle_turfs) +/datum/shuttle_loan_situation/russian_party/spawn_items(list/spawn_list, list/empty_shuttle_turfs, list/blocked_shutte_turfs) var/datum/supply_pack/pack = SSshuttle.supply_packs[/datum/supply_pack/service/party] pack.generate(pick_n_take(empty_shuttle_turfs)) @@ -194,7 +194,7 @@ shuttle_transit_text = "Spider Clan gift incoming." logging_desc = "Shuttle full of spiders" -/datum/shuttle_loan_situation/spider_gift/spawn_items(list/spawn_list, list/empty_shuttle_turfs) +/datum/shuttle_loan_situation/spider_gift/spawn_items(list/spawn_list, list/empty_shuttle_turfs, list/blocked_shutte_turfs) var/datum/supply_pack/pack = SSshuttle.supply_packs[/datum/supply_pack/imports/specialops] pack.generate(pick_n_take(empty_shuttle_turfs)) @@ -213,3 +213,82 @@ for(var/i in 1 to 5) var/turf/web_turf = pick_n_take(empty_shuttle_turfs) new /obj/structure/spider/stickyweb(web_turf) + +#define DENT_WALL "dent" +#define CHANGE_WALL "change" +#define DISMANTLE_WALL "dismantle" + +#define BREAK_TILE "break" +#define PLATING_TILE "plating" +#define RUST_TILE "rust" + +/** + * A special shuttle loan situation enabled by the 'mail blocked' station trait. + * It sends back a lot of mail to the station, at the cost of wrecking the cargo shuttle a little. + */ +/datum/shuttle_loan_situation/mail_strike + sender = "Spinward Mail Workers Union" + announcement_text = "The Mail Workers Union wants to borrow your cargo shuttle to employ \"advanced union strike tactics\" with. Payment is strictly in mails." + bonus_points = 0 + thanks_msg = "The cargo shuttle should return in five minutes." + shuttle_transit_text = "Nothing stops the mail." + logging_desc = "Shuttle full of shady mail" + +/datum/shuttle_loan_situation/mail_strike/spawn_items(list/spawn_list, list/empty_shuttle_turfs, list/blocked_shutte_turfs) + for(var/i in 1 to rand(7, 12)) + var/turf/closed/wall/wall = pick_n_take(blocked_shutte_turfs) + if(!istype(wall)) + continue + var/static/list/wall_bad_stuff = list(DENT_WALL = 85, CHANGE_WALL = 13, DISMANTLE_WALL = 2) + var/static/list/possible_new_walls = list( + /turf/closed/wall/mineral/sandstone, + /turf/closed/wall/mineral/wood, + /turf/closed/wall/mineral/iron, + /turf/closed/wall/metal_foam_base, + /turf/closed/wall/r_wall, + ) + var/damage_done = pick_weight(wall_bad_stuff) + switch(damage_done) + if(DENT_WALL) + for(var/dent in 1 to rand(1, MAX_DENT_DECALS)) + wall.add_dent(prob(90) ? WALL_DENT_SHOT : WALL_DENT_HIT) + if(CHANGE_WALL) + wall.ChangeTurf(pick(possible_new_walls - wall.type)) + if(prob(25)) + for(var/dent in 1 to rand(1, MAX_DENT_DECALS)) + wall.add_dent(prob(90) ? WALL_DENT_SHOT : WALL_DENT_HIT) + if(DISMANTLE_WALL) + wall.dismantle_wall() + + for(var/i in 1 to rand(7, 12)) + var/turf/open/floor/floor = pick_n_take(empty_shuttle_turfs) + if(!istype(floor)) + continue + var/static/list/floor_bad_stuff = list(BREAK_TILE = 65, PLATING_TILE = 25, RUST_TILE = 10) + var/damage_done = pick_weight(floor_bad_stuff) + switch(damage_done) + if(BREAK_TILE) + if(prob(50)) + floor.break_tile() + else + floor.burn_tile() + if(PLATING_TILE) + if(prob(25)) + floor.remove_tile() + else + floor.make_plating() + if(RUST_TILE) + floor.ChangeTurf(/turf/open/floor/plating/rust) + if(prob(25)) + spawn_list += pick(/obj/effect/gibspawner/robot, /obj/effect/gibspawner/human) + + for(var/i in 1 to rand(4, 7)) + spawn_list += /obj/structure/closet/crate/mail/full/mail_strike + +#undef BREAK_TILE +#undef PLATING_TILE +#undef RUST_TILE + +#undef DENT_WALL +#undef CHANGE_WALL +#undef DISMANTLE_WALL diff --git a/code/modules/events/shuttle_loan/shuttle_loan_event.dm b/code/modules/events/shuttle_loan/shuttle_loan_event.dm index 6eeddea021407..99e70e394953b 100644 --- a/code/modules/events/shuttle_loan/shuttle_loan_event.dm +++ b/code/modules/events/shuttle_loan/shuttle_loan_event.dm @@ -8,7 +8,8 @@ description = "If cargo accepts the offer, fills the shuttle with loot and/or enemies." ///The types of loan events already run (and to be excluded if the event triggers). admin_setup = list(/datum/event_admin_setup/listed_options/shuttle_loan) - var/list/run_situations = list() + ///A list of normally unavailable (or already run) situations datums + var/list/unavailable_situations = list(/datum/shuttle_loan_situation/mail_strike) /datum/round_event_control/shuttle_loan/can_spawn_event(players_amt, allow_magic = FALSE) . = ..() @@ -28,14 +29,14 @@ var/datum/round_event_control/shuttle_loan/loan_control = control //by this point if situation is admin picked, it is a type, not an instance. if(!situation) - var/list/valid_situations = subtypesof(/datum/shuttle_loan_situation) - loan_control.run_situations + var/list/valid_situations = subtypesof(/datum/shuttle_loan_situation) - loan_control.unavailable_situations if(!valid_situations.len) //If we somehow run out of loans (fking campbell), they all become available again - loan_control.run_situations.Cut() + loan_control.unavailable_situations.Cut() valid_situations = subtypesof(/datum/shuttle_loan_situation) situation = pick(valid_situations) - loan_control.run_situations.Add(situation) + loan_control.unavailable_situations.Add(situation) situation = new situation() /datum/round_event/shuttle_loan/announce(fake) @@ -76,10 +77,12 @@ //get empty turfs var/list/empty_shuttle_turfs = list() + var/list/blocked_shutte_turfs = list() var/list/area/shuttle/shuttle_areas = SSshuttle.supply.shuttle_areas for(var/area/shuttle/shuttle_area as anything in shuttle_areas) for(var/turf/open/floor/shuttle_turf in shuttle_area) if(shuttle_turf.is_blocked_turf()) + blocked_shutte_turfs += shuttle_turf continue empty_shuttle_turfs += shuttle_turf if(!empty_shuttle_turfs.len) @@ -87,7 +90,7 @@ //let the situation spawn its items var/list/spawn_list = list() - situation.spawn_items(spawn_list, empty_shuttle_turfs) + situation.spawn_items(spawn_list, empty_shuttle_turfs, blocked_shutte_turfs) var/false_positive = 0 while(spawn_list.len && empty_shuttle_turfs.len) @@ -103,7 +106,7 @@ /datum/event_admin_setup/listed_options/shuttle_loan/get_list() var/datum/round_event_control/shuttle_loan/loan_event = event_control - var/list/valid_situations = subtypesof(/datum/shuttle_loan_situation) - loan_event.run_situations + var/list/valid_situations = subtypesof(/datum/shuttle_loan_situation) - loan_event.unavailable_situations return valid_situations /datum/event_admin_setup/listed_options/shuttle_loan/apply_to_event(datum/round_event/shuttle_loan/event) diff --git a/code/modules/jobs/job_types/chaplain/chaplain_vorpal_scythe.dm b/code/modules/jobs/job_types/chaplain/chaplain_vorpal_scythe.dm index 0dffeda57811a..74b1cdcf627d2 100644 --- a/code/modules/jobs/job_types/chaplain/chaplain_vorpal_scythe.dm +++ b/code/modules/jobs/job_types/chaplain/chaplain_vorpal_scythe.dm @@ -139,7 +139,7 @@ If the scythe isn't empowered when you sheath it, you take a heap of damage and var/death_knell_speed_mod = 1 - potential_reaping.visible_message(span_danger("[user] begins to raise [src] arove [potential_reaping]'s [head_name]."), span_userdanger("[user] begins to raise [src], aiming to slice off your [head_name]!")) + potential_reaping.visible_message(span_danger("[user] begins to raise [src] above [potential_reaping]'s [head_name]."), span_userdanger("[user] begins to raise [src], aiming to slice off your [head_name]!")) if(potential_reaping.stat >= UNCONSCIOUS || HAS_TRAIT(potential_reaping, TRAIT_INCAPACITATED)) //if the victim is incapacitated (due to paralysis, a stun, being in staminacrit, etc.), critted, unconscious, or dead, it's much easier to properly behead death_knell_speed_mod *= 0.5 if(potential_reaping.stat != DEAD && potential_reaping.has_status_effect(/datum/status_effect/jitter)) //jittering will make it harder to perform the death knell, even if they're still @@ -149,6 +149,8 @@ If the scythe isn't empowered when you sheath it, you take a heap of damage and if(ispodperson(potential_reaping) || ismonkey(potential_reaping)) //And if they're a podperson or monkey, they can just die. death_knell_speed_mod *= 0.5 + log_combat(user, potential_reaping, "prepared to use [src] to decapitate") + if(do_after(user, 15 SECONDS * death_knell_speed_mod, target = potential_reaping)) playsound(get_turf(potential_reaping), 'sound/weapons/bladeslice.ogg', 250, TRUE) reaped_head.dismember() @@ -165,6 +167,7 @@ If the scythe isn't empowered when you sheath it, you take a heap of damage and ) scythe_empowerment(potential_empowerment) + log_combat(user, potential_reaping, "used [src] to decapitate") if(HAS_MIND_TRAIT(user, TRAIT_MORBID)) //You feel good about yourself, pal? user.add_mood_event("morbid_dismemberment", /datum/mood_event/morbid_dismemberment) diff --git a/code/modules/mining/machine_redemption.dm b/code/modules/mining/machine_redemption.dm index 2d542445f8a6c..cfa8203615e7e 100644 --- a/code/modules/mining/machine_redemption.dm +++ b/code/modules/mining/machine_redemption.dm @@ -52,9 +52,21 @@ if(!GLOB.autounlock_techwebs[/datum/techweb/autounlocking/smelter]) GLOB.autounlock_techwebs[/datum/techweb/autounlocking/smelter] = new /datum/techweb/autounlocking/smelter stored_research = GLOB.autounlock_techwebs[/datum/techweb/autounlocking/smelter] - materials = AddComponent(/datum/component/remote_materials, mapload) - RegisterSignal(src, COMSIG_MATCONTAINER_ITEM_CONSUMED, TYPE_PROC_REF(/obj/machinery/mineral/ore_redemption, redeem_points)) + //mat_container_signals is for reedeming points from local storage if silo is not required + var/list/local_signals = null + if(!requires_silo) + local_signals = list( + COMSIG_MATCONTAINER_ITEM_CONSUMED = TYPE_PROC_REF(/obj/machinery/mineral/ore_redemption, local_redeem_points) + ) + materials = AddComponent( \ + /datum/component/remote_materials, \ + mapload, \ + mat_container_signals = local_signals \ + ) + + //for reedeming points from items inserted into ore silo + RegisterSignal(src, COMSIG_SILO_ITEM_CONSUMED, TYPE_PROC_REF(/obj/machinery/mineral/ore_redemption, silo_redeem_points)) /obj/machinery/mineral/ore_redemption/Destroy() stored_research = null @@ -67,7 +79,12 @@ . += span_notice("Alt-click to rotate the input and output direction.") -/obj/machinery/mineral/ore_redemption/proc/redeem_points(obj/machinery/mineral/ore_redemption/machine, container, obj/item/stack/ore/gathered_ore) +/obj/machinery/mineral/ore_redemption/proc/silo_redeem_points(obj/machinery/mineral/ore_redemption/machine, container, obj/item/stack/ore/gathered_ore) + SIGNAL_HANDLER + + local_redeem_points(container, gathered_ore) + +/obj/machinery/mineral/ore_redemption/proc/local_redeem_points(container, obj/item/stack/ore/gathered_ore) SIGNAL_HANDLER if(istype(gathered_ore) && gathered_ore.refined_type) @@ -156,7 +173,7 @@ if(isnull(gathered_ore.refined_type)) continue - if(materials.mat_container.insert_item(gathered_ore, ore_multiplier, context = src) <= 0) + if(materials.insert_item(gathered_ore, ore_multiplier) <= 0) unload_mineral(gathered_ore) //if rejected unload SEND_SIGNAL(src, COMSIG_ORM_COLLECTED_ORE) @@ -289,21 +306,26 @@ var/datum/component/material_container/mat_container = materials.mat_container switch(action) if("Claim") + //requires silo but silo not in range + if(requires_silo && !materials.check_z_level()) + return FALSE + + //no ID var/obj/item/card/id/user_id_card if(isliving(usr)) var/mob/living/user = usr user_id_card = user.get_idcard(TRUE) - if(!materials.check_z_level() && (requires_silo || !user_id_card.registered_account.replaceable)) - return TRUE + if(isnull(user_id_card)) + to_chat(usr, span_warning("No valid ID detected.")) + return FALSE + + //we have points if(points) - if(user_id_card) - user_id_card.registered_account.mining_points += points - points = 0 - else - to_chat(usr, span_warning("No valid ID detected.")) - else - to_chat(usr, span_warning("No points to claim.")) - return TRUE + user_id_card.registered_account.mining_points += points + points = 0 + return TRUE + + return FALSE if("Release") if(!mat_container) return diff --git a/code/modules/mining/machine_silo.dm b/code/modules/mining/machine_silo.dm index 61e340d3540ef..dcc85dc1c1cc5 100644 --- a/code/modules/mining/machine_silo.dm +++ b/code/modules/mining/machine_silo.dm @@ -64,7 +64,7 @@ GLOBAL_LIST_EMPTY(silo_access_logs) silo_log(context, "deposited", amount_inserted, item_inserted.name, mats_consumed) - SEND_SIGNAL(context, COMSIG_MATCONTAINER_ITEM_CONSUMED, container, item_inserted, last_inserted_id, mats_consumed, amount_inserted) + SEND_SIGNAL(context, COMSIG_SILO_ITEM_CONSUMED, container, item_inserted, last_inserted_id, mats_consumed, amount_inserted) /obj/machinery/ore_silo/proc/log_sheets_ejected(datum/component/material_container/container, obj/item/stack/sheet/sheets, atom/context) SIGNAL_HANDLER diff --git a/code/modules/mob/living/basic/bots/_bots.dm b/code/modules/mob/living/basic/bots/_bots.dm index 81d69ce7701c4..5151ec116fc5f 100644 --- a/code/modules/mob/living/basic/bots/_bots.dm +++ b/code/modules/mob/living/basic/bots/_bots.dm @@ -69,6 +69,14 @@ GLOBAL_LIST_INIT(command_strings, list( var/obj/item/card/id/access_card ///The trim type that will grant additional acces var/datum/id_trim/additional_access + ///file the path icon is stored in + var/path_image_icon = 'icons/mob/silicon/aibots.dmi' + ///state of the path icon + var/path_image_icon_state = "path_indicator" + ///what color this path icon will use + var/path_image_color = "#FFFFFF" + ///list of all layed path icons + var/list/current_pathed_turfs = list() ///The type of data HUD the bot uses. Diagnostic by default. var/data_hud_type = DATA_HUD_DIAGNOSTIC_BASIC @@ -91,8 +99,9 @@ GLOBAL_LIST_INIT(command_strings, list( /mob/living/basic/bot/Initialize(mapload) . = ..() - AddElement(/datum/element/relay_attackers) + AddElement(/datum/element/relay_attackers) + RegisterSignal(src, COMSIG_MOVABLE_MOVED, PROC_REF(handle_loop_movement)) RegisterSignal(src, COMSIG_ATOM_WAS_ATTACKED, PROC_REF(after_attacked)) RegisterSignal(src, COMSIG_MOB_TRIED_ACCESS, PROC_REF(attempt_access)) ADD_TRAIT(src, TRAIT_NO_GLIDE, INNATE_TRAIT) @@ -185,6 +194,7 @@ GLOBAL_LIST_INIT(command_strings, list( /mob/living/basic/bot/Destroy() GLOB.bots_list -= src calling_ai_ref = null + clear_path_hud() QDEL_NULL(paicard) QDEL_NULL(pa_system) QDEL_NULL(internal_radio) @@ -261,10 +271,10 @@ GLOBAL_LIST_INIT(command_strings, list( fully_replace_character_name(real_name, new_name) /mob/living/basic/bot/proc/check_access(mob/living/user, obj/item/card/id) - if(!istype(user)) // Non-living mobs shouldn't be manipulating bots (like observes using the botkeeper UI). - return FALSE if(user.has_unlimited_silicon_privilege || isAdminGhostAI(user)) // Silicon and Admins always have access. return TRUE + if(!istype(user)) // Non-living mobs shouldn't be manipulating bots (like observes using the botkeeper UI). + return FALSE if(!length(maints_access_required)) // No requirements to access it. return TRUE if(bot_access_flags & BOT_CONTROL_PANEL_OPEN) // Unlocked. @@ -540,6 +550,7 @@ GLOBAL_LIST_INIT(command_strings, list( access_card.set_access(initial_access) diag_hud_set_botstat() diag_hud_set_botmode() + clear_path_hud() if(bypass_ai_reset || isnull(calling_ai_ref)) return var/mob/living/ai_caller = calling_ai_ref.resolve() @@ -593,9 +604,9 @@ GLOBAL_LIST_INIT(command_strings, list( // Actions received from TGUI /mob/living/basic/bot/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) . = ..() - if(. || !isliving(ui.user)) + if(.) return - var/mob/living/the_user = ui.user + var/mob/the_user = ui.user if(!check_access(the_user)) balloon_alert(the_user, "access denied!") return @@ -747,6 +758,7 @@ GLOBAL_LIST_INIT(command_strings, list( speed = 2 diag_hud_set_botmode() + clear_path_hud() /mob/living/basic/bot/Logout() . = ..() diff --git a/code/modules/mob/living/basic/bots/bot_hud.dm b/code/modules/mob/living/basic/bots/bot_hud.dm index d9b429a32bf47..4ed97fe32f775 100644 --- a/code/modules/mob/living/basic/bots/bot_hud.dm +++ b/code/modules/mob/living/basic/bots/bot_hud.dm @@ -37,3 +37,81 @@ holder.icon_state = "hudmove" else holder.icon_state = "" + +///proc that handles drawing and transforming the bot's path onto diagnostic huds +/mob/living/basic/bot/proc/generate_bot_path(datum/move_loop/has_target/jps/source) + SIGNAL_HANDLER + + UnregisterSignal(src, COMSIG_MOVELOOP_JPS_FINISHED_PATHING) + + if(isnull(ai_controller)) + return + + clear_path_hud() + + var/list/path_images = active_hud_list[DIAG_PATH_HUD] + QDEL_LIST(path_images) + + var/list/path_huds_watching_me = list(GLOB.huds[DATA_HUD_DIAGNOSTIC_ADVANCED]) + + var/atom/move_target = ai_controller.current_movement_target + if(move_target != ai_controller.blackboard[BB_BEACON_TARGET]) + return + + var/list/our_path = source.movement_path + if(!length(our_path)) + return + + for(var/datum/atom_hud/hud as anything in path_huds_watching_me) + hud.remove_atom_from_hud(src) + + for(var/index in 1 to our_path.len) + if(index == 1 || index == our_path.len) + continue + var/turf/current_turf = our_path[index] + var/turf/previous_turf = our_path[index - 1] + + var/turf/next_turf = our_path[index + 1] + var/next_direction = get_dir(previous_turf, next_turf) + var/previous_direction = get_dir(current_turf, previous_turf) + + var/image/path_display = image(icon = path_image_icon, loc = current_turf, icon_state = path_image_icon_state, layer = GAME_PLANE, dir = next_direction) + + if((ISDIAGONALDIR(next_direction) && (previous_direction & (NORTH|SOUTH)))) + var/turn_value = (next_direction == SOUTHWEST || next_direction == NORTHEAST) ? 90 : -90 + path_display.transform = path_display.transform.Turn(turn_value) + path_display.transform = path_display.transform.Scale(1, -1) + + path_display.color = path_image_color + path_images += path_display + current_pathed_turfs[current_turf] = path_display + + for(var/datum/atom_hud/hud as anything in path_huds_watching_me) + hud.add_atom_to_hud(src) + +///proc that handles moving along the bot's drawn path +/mob/living/basic/bot/proc/handle_loop_movement(atom/movable/source) + SIGNAL_HANDLER + + if(client || !length(current_pathed_turfs) || isnull(ai_controller)) + return + + var/atom/move_target = ai_controller.current_movement_target + + if(move_target != ai_controller.blackboard[BB_BEACON_TARGET]) + clear_path_hud() + + var/turf/our_turf = get_turf(src) + var/image/target_image = current_pathed_turfs[our_turf] + if(target_image) + animate(target_image, alpha = 0, time = 0.3 SECONDS) + current_pathed_turfs -= our_turf + return + +///proc that handles deleting the bot's drawn path when needed +/mob/living/basic/bot/proc/clear_path_hud() + for(var/turf/index as anything in current_pathed_turfs) + var/image/our_image = current_pathed_turfs[index] + animate(our_image, alpha = 0, time = 0.3 SECONDS) + current_pathed_turfs -= index + diff --git a/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm b/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm index 204e6f7ba6236..c19ba69c5ce44 100644 --- a/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm +++ b/code/modules/mob/living/basic/bots/cleanbot/cleanbot.dm @@ -19,6 +19,7 @@ additional_access = /datum/id_trim/job/janitor possessed_message = "You are a cleanbot! Clean the station to the best of your ability!" ai_controller = /datum/ai_controller/basic_controller/bot/cleanbot + path_image_color = "#993299" ///the bucket used to build us. var/obj/item/reagent_containers/cup/bucket/build_bucket ///Flags indicating what kind of cleanables we should scan for to set as our target to clean. diff --git a/code/modules/mob/living/basic/bots/cleanbot/cleanbot_ai.dm b/code/modules/mob/living/basic/bots/cleanbot/cleanbot_ai.dm index 9d1367ee07595..8007f56f78979 100644 --- a/code/modules/mob/living/basic/bots/cleanbot/cleanbot_ai.dm +++ b/code/modules/mob/living/basic/bots/cleanbot/cleanbot_ai.dm @@ -21,7 +21,7 @@ /datum/ai_planning_subtree/acid_spray, /datum/ai_planning_subtree/use_mob_ability/foam_area, /datum/ai_planning_subtree/salute_authority, - /datum/ai_planning_subtree/find_patrol_beacon, + /datum/ai_planning_subtree/find_patrol_beacon/cleanbot, ) reset_keys = list( BB_ACTIVE_PET_COMMAND, @@ -37,6 +37,7 @@ BB_CLEANABLE_DRAWINGS = CLEANBOT_CLEAN_DRAWINGS, BB_HUNTABLE_TRASH = CLEANBOT_CLEAN_TRASH, ) + ai_traits = PAUSE_DURING_DO_AFTER /datum/ai_planning_subtree/pet_planning/cleanbot/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) var/mob/living/basic/bot/bot_pawn = controller.pawn @@ -49,12 +50,8 @@ /datum/ai_planning_subtree/cleaning_subtree /datum/ai_planning_subtree/cleaning_subtree/SelectBehaviors(datum/ai_controller/basic_controller/bot/cleanbot/controller, seconds_per_tick) - var/mob/living/basic/bot/cleanbot/bot_pawn = controller.pawn - - if(LAZYLEN(bot_pawn.do_afters)) - return SUBTREE_RETURN_FINISH_PLANNING - if(controller.reachable_key(BB_CLEAN_TARGET, BOT_CLEAN_PATH_LIMIT)) + controller.clear_blackboard_key(BB_BEACON_TARGET) controller.queue_behavior(/datum/ai_behavior/execute_clean, BB_CLEAN_TARGET) return SUBTREE_RETURN_FINISH_PLANNING @@ -62,6 +59,7 @@ final_hunt_list += controller.blackboard[BB_CLEANABLE_DECALS] var/list/flag_list = controller.clean_flags + var/mob/living/basic/bot/cleanbot/bot_pawn = controller.pawn for(var/list_key in flag_list) if(!(bot_pawn.janitor_mode_flags & flag_list[list_key])) continue @@ -70,7 +68,7 @@ controller.queue_behavior(/datum/ai_behavior/find_and_set/in_list/clean_targets, BB_CLEAN_TARGET, final_hunt_list) /datum/ai_behavior/find_and_set/in_list/clean_targets - action_cooldown = 2 SECONDS + action_cooldown = 1 SECONDS /datum/ai_behavior/find_and_set/in_list/clean_targets/search_tactic(datum/ai_controller/controller, locate_paths, search_range) var/list/found = typecache_filter_list(oview(search_range, controller.pawn), locate_paths) @@ -185,6 +183,13 @@ return human_target return null +/datum/ai_planning_subtree/find_patrol_beacon/cleanbot + +/datum/ai_planning_subtree/find_patrol_beacon/cleanbot/SelectBehaviors(datum/ai_controller/basic_controller/bot/controller, seconds_per_tick) + if(controller.blackboard_key_exists(BB_CLEAN_TARGET)) + return + return ..() + /datum/pet_command/point_targeting/clean command_name = "Clean" command_desc = "Command a cleanbot to clean the mess." diff --git a/code/modules/mob/living/basic/bots/medbot/medbot.dm b/code/modules/mob/living/basic/bots/medbot/medbot.dm index bb21555666548..d2d7c8464777d 100644 --- a/code/modules/mob/living/basic/bots/medbot/medbot.dm +++ b/code/modules/mob/living/basic/bots/medbot/medbot.dm @@ -25,6 +25,7 @@ additional_access = /datum/id_trim/job/paramedic announcement_type = /datum/action/cooldown/bot_announcement/medbot + path_image_color = "#d9d9f4" ///anouncements when we find a target to heal var/static/list/wait_announcements = list( @@ -146,8 +147,10 @@ ) RegisterSignal(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, PROC_REF(pre_attack)) + if(!HAS_TRAIT(SSstation, STATION_TRAIT_MEDBOT_MANIA) || !mapload || !is_station_level(z)) - return + return INITIALIZE_HINT_LATELOAD + skin = "advanced" update_appearance(UPDATE_OVERLAYS) damage_type_healer = HEAL_ALL_DAMAGE diff --git a/code/modules/mob/living/basic/bots/medbot/medbot_ai.dm b/code/modules/mob/living/basic/bots/medbot/medbot_ai.dm index 54315deda72e4..8d8e3efbfe330 100644 --- a/code/modules/mob/living/basic/bots/medbot/medbot_ai.dm +++ b/code/modules/mob/living/basic/bots/medbot/medbot_ai.dm @@ -192,15 +192,15 @@ . = ..() var/mob/living/living_target = controller.blackboard[target_key] if(QDELETED(living_target)) - finish_action(controller, FALSE) + finish_action(controller, FALSE, target_key) return var/datum/action/cooldown/bot_announcement/announcement = controller.blackboard[BB_ANNOUNCE_ABILITY] if(QDELETED(announcement)) - finish_action(controller, FALSE) + finish_action(controller, FALSE, target_key) return var/text_to_announce = "Medical emergency! [living_target] is in critical condition at [get_area(living_target)]!" announcement.announce(text_to_announce, controller.blackboard[BB_RADIO_CHANNEL]) - finish_action(controller, TRUE) + finish_action(controller, TRUE, target_key) /datum/ai_behavior/announce_patient/finish_action(datum/ai_controller/controller, succeeded, target_key) . = ..() diff --git a/code/modules/mob/living/simple_animal/slime/slime.dm b/code/modules/mob/living/simple_animal/slime/slime.dm index 6a03e3cb522c3..d9fbfe8dfb341 100644 --- a/code/modules/mob/living/simple_animal/slime/slime.dm +++ b/code/modules/mob/living/simple_animal/slime/slime.dm @@ -354,7 +354,7 @@ for(var/datum/surgery/operations as anything in surgeries) if(operations.next_step(user, modifiers)) return TRUE - if(istype(attacking_item, /obj/item/stack/sheet/mineral/plasma) && !stat) //Let's you feed slimes plasma. + if(istype(attacking_item, /obj/item/stack/sheet/mineral/plasma) && !stat) //Lets you feed slimes plasma. add_friendship(user, 1) to_chat(user, span_notice("You feed the slime the plasma. It chirps happily.")) var/obj/item/stack/sheet/mineral/plasma/sheet = attacking_item diff --git a/code/modules/photography/photos/album.dm b/code/modules/photography/photos/album.dm index 35d7f27017cdb..f8f8a8fb0625f 100644 --- a/code/modules/photography/photos/album.dm +++ b/code/modules/photography/photos/album.dm @@ -9,6 +9,7 @@ inhand_icon_state = "album" lefthand_file = 'icons/mob/inhands/items/books_lefthand.dmi' righthand_file = 'icons/mob/inhands/items/books_righthand.dmi' + storage_type = /datum/storage/photo_album resistance_flags = FLAMMABLE w_class = WEIGHT_CLASS_SMALL flags_1 = PREVENT_CONTENTS_EXPLOSION_1 @@ -16,13 +17,11 @@ /obj/item/storage/photo_album/Initialize(mapload) . = ..() - atom_storage.set_holdable(list(/obj/item/photo)) - atom_storage.max_total_storage = 42 - atom_storage.max_slots = 21 - LAZYADD(SSpersistence.photo_albums, src) + if (!SSpersistence.initialized) + LAZYADD(SSpersistence.queued_photo_albums, src) /obj/item/storage/photo_album/Destroy() - LAZYREMOVE(SSpersistence.photo_albums, src) + LAZYREMOVE(SSpersistence.queued_photo_albums, src) return ..() /obj/item/storage/photo_album/proc/get_picture_id_list() @@ -41,9 +40,9 @@ //Manual loading, DO NOT USE FOR HARDCODED/MAPPED IN ALBUMS. This is for if an album needs to be loaded mid-round from an ID. /obj/item/storage/photo_album/proc/persistence_load() - var/list/data = SSpersistence.get_photo_albums() - if(data[persistence_id]) - populate_from_id_list(data[persistence_id]) + var/list/data = SSpersistence.photo_albums_database.get_key(persistence_id) + if (!isnull(data)) + populate_from_id_list(data) /obj/item/storage/photo_album/proc/populate_from_id_list(list/ids) var/list/current_ids = get_picture_id_list() @@ -55,6 +54,27 @@ if(!atom_storage?.attempt_insert(P, override = TRUE)) qdel(P) +/datum/storage/photo_album + max_total_storage = 42 + max_slots = 21 + +/datum/storage/photo_album/New(atom/parent, max_slots, max_specific_storage, max_total_storage, numerical_stacking, allow_quick_gather, allow_quick_empty, collection_mode, attack_hand_interact) + . = ..() + set_holdable(list(/obj/item/photo)) + +/datum/storage/photo_album/proc/save_everything() + var/obj/item/storage/photo_album/album = parent.resolve() + ASSERT(istype(album)) + SSpersistence.photo_albums_database.set_key(album.persistence_id, album.get_picture_id_list()) + +/datum/storage/photo_album/handle_enter(datum/source, obj/item/arrived) + . = ..() + save_everything() + +/datum/storage/photo_album/handle_exit(datum/source, obj/item/gone) + . = ..() + save_everything() + /obj/item/storage/photo_album/hos name = "photo album (Head of Security)" icon_state = "album_blue" diff --git a/code/modules/photography/photos/frame.dm b/code/modules/photography/photos/frame.dm index f2d77553ea16a..4fbe3e034d88c 100644 --- a/code/modules/photography/photos/frame.dm +++ b/code/modules/photography/photos/frame.dm @@ -56,7 +56,7 @@ var/obj/structure/sign/picture_frame/PF = O PF.copy_overlays(src) if(displayed) - PF.framed = displayed + PF.set_and_save_framed(displayed) if(contents.len) var/obj/item/I = pick(contents) I.forceMove(PF) @@ -70,27 +70,19 @@ resistance_flags = FLAMMABLE var/obj/item/photo/framed var/persistence_id - var/del_id_on_destroy = FALSE var/art_value = OK_ART var/can_decon = TRUE -#define FRAME_DEFINE(id) /obj/structure/sign/picture_frame/##id/persistence_id = #id - -//Put default persistent frame defines here! - -#undef FRAME_DEFINE - /obj/structure/sign/picture_frame/Initialize(mapload, dir, building) . = ..() AddElement(/datum/element/art, art_value) - LAZYADD(SSpersistence.photo_frames, src) + if (!SSpersistence.initialized) + LAZYADD(SSpersistence.queued_photo_frames, src) if(dir) setDir(dir) /obj/structure/sign/picture_frame/Destroy() - LAZYREMOVE(SSpersistence.photo_frames, src) - if(persistence_id && del_id_on_destroy) - SSpersistence.remove_photo_frames(persistence_id) + LAZYREMOVE(SSpersistence.queued_photo_frames, src) return ..() /obj/structure/sign/picture_frame/proc/get_photo_id() @@ -99,9 +91,9 @@ //Manual loading, DO NOT USE FOR HARDCODED/MAPPED IN ALBUMS. This is for if an album needs to be loaded mid-round from an ID. /obj/structure/sign/picture_frame/proc/persistence_load() - var/list/data = SSpersistence.get_photo_frames() - if(data[persistence_id]) - load_from_id(data[persistence_id]) + var/list/data = SSpersistence.photo_frames_database.get_key(persistence_id) + if(!isnull(data)) + load_from_id(data) /obj/structure/sign/picture_frame/proc/load_from_id(id) var/obj/item/photo/old/P = load_photo_from_disk(id) @@ -113,6 +105,15 @@ framed = P update_appearance() +/// Given a photo (or null), will change the contained picture, and queue a persistent save. +/obj/structure/sign/picture_frame/proc/set_and_save_framed(obj/item/photo/photo) + framed = photo + + if (isnull(persistence_id)) + return + + SSpersistence.photo_frames_database.set_key(persistence_id, photo?.picture?.id) + /obj/structure/sign/picture_frame/examine(mob/user) . = ..() if(in_range(src, user)) @@ -141,7 +142,7 @@ tool.play_tool_sound(src) framed.forceMove(drop_location()) user.visible_message(span_warning("[user] cuts away [framed] from [src]!")) - framed = null + set_and_save_framed(null) update_appearance() return ITEM_INTERACT_SUCCESS @@ -155,7 +156,7 @@ var/obj/item/photo/P = I if(!user.transferItemToLoc(P, src)) return - framed = P + set_and_save_framed(P) update_appearance() return TRUE ..() @@ -177,7 +178,7 @@ var/obj/item/wallframe/picture/F = new /obj/item/wallframe/picture(loc) if(framed) F.displayed = framed - framed = null + set_and_save_framed(null) if(contents.len) var/obj/item/I = pick(contents) I.forceMove(F) @@ -277,7 +278,6 @@ /obj/structure/sign/picture_frame/portrait/bar persistence_id = "frame_bar" - del_id_on_destroy = TRUE ///Generates a persistence id unique to the current map. Every bar should feel a little bit different after all. /obj/structure/sign/picture_frame/portrait/bar/Initialize(mapload) diff --git a/code/modules/power/turbine/turbine.dm b/code/modules/power/turbine/turbine.dm index 2ca8b1df7f2ed..06466318025a3 100644 --- a/code/modules/power/turbine/turbine.dm +++ b/code/modules/power/turbine/turbine.dm @@ -512,7 +512,9 @@ /obj/machinery/power/turbine/core_rotor/deactivate_parts() if(all_parts_connected) power_off() + compressor?.rotor = null compressor = null + turbine?.rotor = null turbine = null all_parts_connected = FALSE disconnect_from_network() diff --git a/code/modules/reagents/chemistry/equilibrium.dm b/code/modules/reagents/chemistry/equilibrium.dm index c9d6226930cbe..ffa8e0c32d482 100644 --- a/code/modules/reagents/chemistry/equilibrium.dm +++ b/code/modules/reagents/chemistry/equilibrium.dm @@ -341,7 +341,7 @@ var/total_step_added = 0 for(var/datum/reagent/product as anything in reaction.results) //create the products - step_add = holder.add_reagent(product, delta_chem_factor * reaction.results[product], null, cached_temp, purity, override_base_ph = TRUE, no_react = TRUE) + step_add = holder.add_reagent(product, delta_chem_factor * reaction.results[product], null, cached_temp, purity, override_base_ph = TRUE) if(!step_add) to_delete = TRUE return diff --git a/code/modules/reagents/chemistry/holder/holder.dm b/code/modules/reagents/chemistry/holder/holder.dm index 867245e9c2524..5872f5db1aeee 100644 --- a/code/modules/reagents/chemistry/holder/holder.dm +++ b/code/modules/reagents/chemistry/holder/holder.dm @@ -194,7 +194,7 @@ set_temperature(reagtemp) SEND_SIGNAL(src, COMSIG_REAGENTS_NEW_REAGENT, new_reagent, amount, reagtemp, data, no_react) - if(!no_react && !is_reacting) + if(!no_react) handle_reactions() return amount diff --git a/code/modules/reagents/chemistry/machinery/chem_heater.dm b/code/modules/reagents/chemistry/machinery/chem_heater.dm index f0113153c92d3..c31c099c2550c 100644 --- a/code/modules/reagents/chemistry/machinery/chem_heater.dm +++ b/code/modules/reagents/chemistry/machinery/chem_heater.dm @@ -146,12 +146,14 @@ //keep constant with the chemical acclimator please beaker.reagents.adjust_thermal_energy((target_temperature - beaker.reagents.chem_temp) * heater_coefficient * seconds_per_tick * SPECIFIC_HEAT_DEFAULT * beaker.reagents.total_volume * randomness) - if(!beaker.reagents.is_reacting) - beaker.reagents.handle_reactions() + beaker.reagents.handle_reactions() //use power use_power(active_power_usage * seconds_per_tick) + //show changes to ui immediatly for responsivenes + SStgui.update_uis(src) + /obj/machinery/chem_heater/wrench_act(mob/living/user, obj/item/tool) . = ITEM_INTERACT_BLOCKING if(default_unfasten_wrench(user, tool) == SUCCESSFUL_UNFASTEN) @@ -176,8 +178,10 @@ var/obj/item/reagent_containers/injector = held_item injector.afterattack(beaker, user, proximity_flag = TRUE) return TRUE + if(is_reagent_container(held_item) && held_item.is_open_container()) - replace_beaker(user, held_item) + if(replace_beaker(user, held_item)) + ui_interact(user) balloon_alert(user, "beaker added!") return TRUE diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm index 8f27f4637fb48..d38b165901838 100644 --- a/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -1007,7 +1007,7 @@ /datum/reagent/mercury/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) . = ..() - if(!HAS_TRAIT(src, TRAIT_IMMOBILIZED) && !isspaceturf(affected_mob.loc)) + if(!HAS_TRAIT(src, TRAIT_IMMOBILIZED) && isturf(affected_mob.loc) && !isgroundlessturf(affected_mob.loc)) step(affected_mob, pick(GLOB.cardinals)) if(SPT_PROB(3.5, seconds_per_tick)) affected_mob.emote(pick("twitch","drool","moan")) @@ -1119,7 +1119,7 @@ /datum/reagent/lithium/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) . = ..() - if(!HAS_TRAIT(affected_mob, TRAIT_IMMOBILIZED) && !isspaceturf(affected_mob.loc) && isturf(affected_mob.loc)) + if(!HAS_TRAIT(affected_mob, TRAIT_IMMOBILIZED) && isturf(affected_mob.loc) && !isgroundlessturf(affected_mob.loc)) step(affected_mob, pick(GLOB.cardinals)) if(SPT_PROB(2.5, seconds_per_tick)) affected_mob.emote(pick("twitch","drool","moan")) diff --git a/code/modules/reagents/chemistry/recipes.dm b/code/modules/reagents/chemistry/recipes.dm index 26fb0f472ddc5..c7732e0908a0d 100644 --- a/code/modules/reagents/chemistry/recipes.dm +++ b/code/modules/reagents/chemistry/recipes.dm @@ -60,21 +60,6 @@ ///A bitflag var for tagging reagents for the reagent loopup functon var/reaction_tags = NONE -/datum/chemical_reaction/New() - . = ..() - SSticker.OnRoundstart(CALLBACK(src, PROC_REF(update_info))) - -/** - * Updates information during the roundstart - * - * This proc is mainly used by explosives but can be used anywhere else - * You should generally use the special reactions in [/datum/chemical_reaction/randomized] - * But for simple variable edits, like changing the temperature or adding/subtracting required reagents it is better to use this. - */ -/datum/chemical_reaction/proc/update_info() - return - - ///REACTION PROCS /** diff --git a/code/modules/reagents/chemistry/recipes/pyrotechnics.dm b/code/modules/reagents/chemistry/recipes/pyrotechnics.dm index 78942431bb8ba..8e74b0ad6f869 100644 --- a/code/modules/reagents/chemistry/recipes/pyrotechnics.dm +++ b/code/modules/reagents/chemistry/recipes/pyrotechnics.dm @@ -77,8 +77,9 @@ required_temp = 450 strengthdiv = 3 -/datum/chemical_reaction/reagent_explosion/tatp/update_info() - required_temp = 450 + rand(-49, 49) //this gets loaded only on round start +/datum/chemical_reaction/reagent_explosion/tatp/New() + . = ..() + required_temp = 450 + rand(-49, 49) /datum/chemical_reaction/reagent_explosion/tatp/on_reaction(datum/reagents/holder, datum/equilibrium/reaction, created_volume) if(holder.has_reagent(/datum/reagent/exotic_stabilizer, created_volume / 50)) // we like exotic stabilizer @@ -93,12 +94,12 @@ /datum/chemical_reaction/reagent_explosion/tatp_explosion/on_reaction(datum/reagents/holder, datum/equilibrium/reaction, created_volume) var/strengthdiv_adjust = created_volume / ( 2100 / initial(strengthdiv)) - strengthdiv = max(initial(strengthdiv) - strengthdiv_adjust + 1.5 ,1.5) //Slightly better than nitroglycerin - . = ..() - return + strengthdiv = max(initial(strengthdiv) - strengthdiv_adjust + 1.5, 1.5) //Slightly better than nitroglycerin + return ..() -/datum/chemical_reaction/reagent_explosion/tatp_explosion/update_info() - required_temp = 550 + rand(-49,49) +/datum/chemical_reaction/reagent_explosion/tatp_explosion/New() + . = ..() + required_temp = 550 + rand(-49, 49) /datum/chemical_reaction/reagent_explosion/penthrite_explosion_epinephrine required_reagents = list(/datum/reagent/medicine/c2/penthrite = 1, /datum/reagent/medicine/epinephrine = 1) diff --git a/code/modules/surgery/eye_surgery.dm b/code/modules/surgery/eye_surgery.dm index 7b6d7844ee6c4..748dab1f4ec85 100644 --- a/code/modules/surgery/eye_surgery.dm +++ b/code/modules/surgery/eye_surgery.dm @@ -22,10 +22,7 @@ /datum/surgery/eye_surgery/can_start(mob/user, mob/living/carbon/target) var/obj/item/organ/internal/eyes/target_eyes = target.get_organ_slot(ORGAN_SLOT_EYES) - if(!target_eyes) - to_chat(user, span_warning("It's hard to do surgery on someone's eyes when [target.p_they()] [target.p_do()]n't have any.")) - return FALSE - return TRUE + return !isnull(target_eyes) /datum/surgery_step/fix_eyes/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery) display_results( diff --git a/code/modules/vehicles/mecha/_mecha.dm b/code/modules/vehicles/mecha/_mecha.dm index 208bd8d68c28d..d971b82a8566c 100644 --- a/code/modules/vehicles/mecha/_mecha.dm +++ b/code/modules/vehicles/mecha/_mecha.dm @@ -267,6 +267,7 @@ equip_by_category[key] -= path AddElement(/datum/element/falling_hazard, damage = 80, wound_bonus = 10, hardhat_safety = FALSE, crushes = TRUE) + AddElement(/datum/element/hostile_machine) /obj/vehicle/sealed/mecha/Destroy() for(var/ejectee in occupants) diff --git a/html/changelogs/AutoChangeLog-pr-80328.yml b/html/changelogs/AutoChangeLog-pr-80328.yml new file mode 100644 index 0000000000000..8bbebbd7078eb --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-80328.yml @@ -0,0 +1,4 @@ +author: "Diamond-74" +delete-after: True +changes: + - bugfix: "ai can now tell if it is in a do_after for resisting and will not interrupt it. monkeys also now don't freeze up when aggressively grabbed and will resist out of those and cuffs." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-80519.yml b/html/changelogs/AutoChangeLog-pr-80519.yml new file mode 100644 index 0000000000000..7c94d9b4fc812 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-80519.yml @@ -0,0 +1,4 @@ +author: "Mothblocks" +delete-after: True +changes: + - refactor: "Photo albums and photo frames are now more resilient to data loss, especially when a server crashes." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-80561.yml b/html/changelogs/AutoChangeLog-pr-80561.yml deleted file mode 100644 index 379d31e8d41d3..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-80561.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Melbert" -delete-after: True -changes: - - bugfix: "Fixed some occasions in which heartbeat SFX will continue on revival for longer than expected" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-80562.yml b/html/changelogs/AutoChangeLog-pr-80562.yml deleted file mode 100644 index c366dbea77bfb..0000000000000 --- a/html/changelogs/AutoChangeLog-pr-80562.yml +++ /dev/null @@ -1,4 +0,0 @@ -author: "Ryll/Shaps" -delete-after: True -changes: - - balance: "Yawns are less likely to propagate" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-80567.yml b/html/changelogs/AutoChangeLog-pr-80567.yml new file mode 100644 index 0000000000000..920d7749b54c9 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-80567.yml @@ -0,0 +1,5 @@ +author: "timothymtorres" +delete-after: True +changes: + - rscadd: "Add 50% graffiti speed boost to tagger quirk" + - bugfix: "Fix time duration of large graffiti not applying properly" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-80573.yml b/html/changelogs/AutoChangeLog-pr-80573.yml new file mode 100644 index 0000000000000..423125e03c911 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-80573.yml @@ -0,0 +1,4 @@ +author: "SyncIt21" +delete-after: True +changes: + - bugfix: "Off station ORM's can redeem points again." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-80600.yml b/html/changelogs/AutoChangeLog-pr-80600.yml new file mode 100644 index 0000000000000..ae331f1d10aaf --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-80600.yml @@ -0,0 +1,6 @@ +author: "SyncIt21" +delete-after: True +changes: + - bugfix: "reaction chamber open its UI when inserting/removing beakers from it" + - bugfix: "reaction chamber triggers new reactions & updates UI more often during heating" + - bugfix: "instant and normal reactions now get triggered more often so for e.g. more plastic sheets from polymer reactions or more reactions occur when there are multiple reagents present" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-80601.yml b/html/changelogs/AutoChangeLog-pr-80601.yml new file mode 100644 index 0000000000000..ab7644fe8a7b7 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-80601.yml @@ -0,0 +1,4 @@ +author: "Mothblocks" +delete-after: True +changes: + - bugfix: "Fixed a lot of missing/broken images, such as emojis, language icons, commendation hearts, icons in R&D menu, etc." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-80609.yml b/html/changelogs/AutoChangeLog-pr-80609.yml new file mode 100644 index 0000000000000..34fc5ff768221 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-80609.yml @@ -0,0 +1,4 @@ +author: "LemonInTheDark" +delete-after: True +changes: + - bugfix: "Dear mappers, the light debugger tool no longer deletes dragged wall lights" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-80612.yml b/html/changelogs/AutoChangeLog-pr-80612.yml new file mode 100644 index 0000000000000..ba3924d539c3e --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-80612.yml @@ -0,0 +1,4 @@ +author: "vinylspiders" +delete-after: True +changes: + - spellcheck: "changes some let's to lets" \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-80613.yml b/html/changelogs/AutoChangeLog-pr-80613.yml new file mode 100644 index 0000000000000..e5d82edfcecec --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-80613.yml @@ -0,0 +1,4 @@ +author: "Mothblocks" +delete-after: True +changes: + - bugfix: "Fixed \"was shocked by /datum/component/energized\" message." \ No newline at end of file diff --git a/html/changelogs/AutoChangeLog-pr-80614.yml b/html/changelogs/AutoChangeLog-pr-80614.yml new file mode 100644 index 0000000000000..cb847b2ceeec7 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-80614.yml @@ -0,0 +1,4 @@ +author: "jlsnow301" +delete-after: True +changes: + - bugfix: "NTOS Messenger should clear on enter now" \ No newline at end of file diff --git a/html/changelogs/archive/2023-12.yml b/html/changelogs/archive/2023-12.yml index b2cc28dc011f4..16c8643b7e784 100644 --- a/html/changelogs/archive/2023-12.yml +++ b/html/changelogs/archive/2023-12.yml @@ -854,3 +854,34 @@ vinylspiders: - bugfix: fixes a potential mob hard del with cardboard boxes - bugfix: fixes a hard del with thrown items +2023-12-27: + Melbert: + - bugfix: Fixed some occasions in which heartbeat SFX will continue on revival for + longer than expected + Mothblocks: + - bugfix: Fixed the surgery menu spamming chat messages when on the eyes section + of a player with no eyes. + Ryll/Shaps: + - balance: Yawns are less likely to propagate + jlsnow301: + - bugfix: NTOS Messenger should search as you type now + timothymtorres: + - qol: Add UI screentips to spraycans +2023-12-28: + Ben10Omintrix: + - rscadd: basic bots can now display their paths on huds + - bugfix: medbots can research healing again + Ghommie: + - rscadd: Added a "postal workers strike" negative station trait. In the case of + holidays and sunday though, it'll be a "postal system overtime" instead. + LemonInTheDark: + - rscdel: Removes the wires from canisters. It's a cool idea, but cheap controlled + maxcaps are bad actually + SyncIt21: + - bugfix: mercury & lithium will no longer make you randomly move outside of cryotubes + or in ground less turfs (space, water, lava etc) + necromanceranne: + - spellcheck: Fixes a typo in chat message when starting a Death Knell with the + Vorpal Scythe + - admin: Adds logging for the death knell, both when starts and when it is completed + successfully. diff --git a/icons/obj/storage/crates.dmi b/icons/obj/storage/crates.dmi index 274af66c820a4..9bc8f4d2c27e9 100644 Binary files a/icons/obj/storage/crates.dmi and b/icons/obj/storage/crates.dmi differ diff --git a/tgstation.dme b/tgstation.dme index d22f46afb66ca..d16555c199341 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -765,6 +765,7 @@ #include "code\datums\hotkeys_help.dm" #include "code\datums\http.dm" #include "code\datums\hud.dm" +#include "code\datums\json_database.dm" #include "code\datums\json_savefile.dm" #include "code\datums\lazy_template.dm" #include "code\datums\map_config.dm" @@ -1404,6 +1405,7 @@ #include "code\datums\elements\haunted.dm" #include "code\datums\elements\high_fiver.dm" #include "code\datums\elements\honkspam.dm" +#include "code\datums\elements\hostile_machine.dm" #include "code\datums\elements\human_biter.dm" #include "code\datums\elements\immerse.dm" #include "code\datums\elements\item_fov.dm" @@ -1813,7 +1815,6 @@ #include "code\datums\wires\airlock.dm" #include "code\datums\wires\apc.dm" #include "code\datums\wires\autolathe.dm" -#include "code\datums\wires\canister.dm" #include "code\datums\wires\conveyor.dm" #include "code\datums\wires\ecto_sniffer.dm" #include "code\datums\wires\emitter.dm" diff --git a/tgui/packages/tgui/components/Input.tsx b/tgui/packages/tgui/components/Input.tsx index c515627b8c12f..d9b517f1cb529 100644 --- a/tgui/packages/tgui/components/Input.tsx +++ b/tgui/packages/tgui/components/Input.tsx @@ -77,22 +77,33 @@ export const Input = (props: Props) => { } }; + /** Focuses the input on mount */ useEffect(() => { + if (!autoFocus && !autoSelect) return; + const input = inputRef.current; if (!input) return; - input.value = toInputValue(value); - if (autoFocus || autoSelect) { - setTimeout(() => { - input.focus(); + setTimeout(() => { + input.focus(); - if (autoSelect) { - input.select(); - } - }, 1); - } + if (autoSelect) { + input.select(); + } + }, 1); }, []); + /** Updates the initial value on props change */ + useEffect(() => { + const input = inputRef.current; + if (!input) return; + + const newValue = toInputValue(value); + if (input.value === newValue) return; + + input.value = newValue; + }, [value]); + return ( { width="220px" placeholder="Search by name or job..." value={searchUser} - onChange={(_, value) => setSearchUser(value)} + onInput={(_, value) => setSearchUser(value)} /> diff --git a/tools/tgs_test/Program.cs b/tools/tgs_test/Program.cs index b8967dbc156da..9b018be951676 100644 --- a/tools/tgs_test/Program.cs +++ b/tools/tgs_test/Program.cs @@ -11,6 +11,7 @@ using Tgstation.Server.Api.Models; using Tgstation.Server.Api.Models.Response; using Tgstation.Server.Client; +using Tgstation.Server.Common.Extensions; using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.Serialization; @@ -202,10 +203,14 @@ default); Console.WriteLine("Installing BYOND..."); - var byondInstallJob = await instanceClient.Byond.SetActiveVersion( - new ByondVersionRequest + var byondInstallJob = await instanceClient.Engine.SetActiveVersion( + new EngineVersionRequest { - Version = targetByondVersion + EngineVersion = new EngineVersion + { + Version = targetByondVersion, + Engine = EngineType.Byond, + } }, null, default); diff --git a/tools/tgs_test/Tgstation.TgsTest.csproj b/tools/tgs_test/Tgstation.TgsTest.csproj index 0a641c269ab2e..967fbd4295160 100644 --- a/tools/tgs_test/Tgstation.TgsTest.csproj +++ b/tools/tgs_test/Tgstation.TgsTest.csproj @@ -1,16 +1,17 @@ - 1.0.0 + 2.0.0 Exe - net7.0 + net8.0 enable enable - - + + +