diff --git a/code/__DEFINES/crafting.dm b/code/__DEFINES/crafting.dm index cb7930e9d1f..e71004f7f53 100644 --- a/code/__DEFINES/crafting.dm +++ b/code/__DEFINES/crafting.dm @@ -6,6 +6,8 @@ #define CRAFTING_MACHINERY_USE 0 ///If the structure is only "used" i.e. it checks to see if it's nearby and allows crafting, but doesn't delete it #define CRAFTING_STRUCTURE_USE 0 +///If the ingredient is only "used" i.e. it checks to see if it's nearby and allows crafting, but doesn't delete it +#define CRAFTING_INGREDIENT_USE 0 //stack recipe placement check types /// Checks if there is an object of the result type in any of the cardinal directions diff --git a/code/__DEFINES/dcs/signals/signals_area.dm b/code/__DEFINES/dcs/signals/signals_area.dm index b136f46467f..5636a4c35a0 100644 --- a/code/__DEFINES/dcs/signals/signals_area.dm +++ b/code/__DEFINES/dcs/signals/signals_area.dm @@ -38,3 +38,9 @@ ///From base of area/update_beauty() #define COMSIG_AREA_BEAUTY_UPDATED "area_beauty_updated" + +/// From base of turf/change_area(area/old_area) +#define COMSIG_AREA_TURF_ADDED "area_turf_added" + +/// From base of turf/change_area(area/new_area) +#define COMSIG_AREA_TURF_REMOVED "area_turf_removed" diff --git a/code/__DEFINES/dcs/signals/signals_lattice.dm b/code/__DEFINES/dcs/signals/signals_lattice.dm new file mode 100644 index 00000000000..fe45ad5cf52 --- /dev/null +++ b/code/__DEFINES/dcs/signals/signals_lattice.dm @@ -0,0 +1,4 @@ +//From base of /obj/structure/lattice/proc/replace_with_catwalk() : (list/post_replacement_callbacks) +/// `post_replacement_callbacks` is a list that signal handlers can mutate to append `/datum/callback` objects. +/// They will be called with the new catwalk after it has been initialized. +#define COMSIG_LATTICE_PRE_REPLACE_WITH_CATWALK "lattice_pre_replace_with_catwalk" diff --git a/code/__DEFINES/dcs/signals/signals_shuttle.dm b/code/__DEFINES/dcs/signals/signals_shuttle.dm index 336117fb868..69aa13c3900 100644 --- a/code/__DEFINES/dcs/signals/signals_shuttle.dm +++ b/code/__DEFINES/dcs/signals/signals_shuttle.dm @@ -1,3 +1,4 @@ + // Shuttle signals. this file is empty because shuttle code is ancient, feel free to // add more signals where its appropriate to have them @@ -5,3 +6,6 @@ #define COMSIG_SHUTTLE_SHOULD_MOVE "shuttle_should_move" /// Return this when the shuttle move should be blocked. #define BLOCK_SHUTTLE_MOVE (1<<0) + +//from base of /proc/expand_shuttle() : (list/turfs) +#define COMSIG_SHUTTLE_EXPANDED "shuttle_expanded" diff --git a/code/__DEFINES/dcs/signals/signals_turf.dm b/code/__DEFINES/dcs/signals/signals_turf.dm index 321fb503cbf..fb16d8c6b35 100644 --- a/code/__DEFINES/dcs/signals/signals_turf.dm +++ b/code/__DEFINES/dcs/signals/signals_turf.dm @@ -43,3 +43,14 @@ ///Called when turf no longer blocks light from passing through #define COMSIG_TURF_NO_LONGER_BLOCK_LIGHT "turf_no_longer_block_light" + +///from /turf/proc/attempt_lattice_replacement() : (list/post_successful_replacement_callbacks) +/// `post_successful_replacement_callbacks` is a list that signal handlers can mutate to append `/datum/callback` objects. +/// They will be called with the new catwalk if it is actually created. +#define COMSIG_TURF_ATTEMPT_LATTICE_REPLACEMENT "turf_replaced_with_lattice" + +///from /turf/proc/change_area() : (area/old_area) +#define COMSIG_TURF_AREA_CHANGED "turf_area_changed" + +///from /proc/create_shuttle() and /obj/docking_port/mobile/proc/add_turf() : (obj/docking_port/mobile/shuttle) +#define COMSIG_TURF_ADDED_TO_SHUTTLE "turf_added_to_shuttle" diff --git a/code/__DEFINES/research/techweb_nodes.dm b/code/__DEFINES/research/techweb_nodes.dm index f27225f1fed..c44a3f4b71a 100644 --- a/code/__DEFINES/research/techweb_nodes.dm +++ b/code/__DEFINES/research/techweb_nodes.dm @@ -107,6 +107,7 @@ #define TECHWEB_NODE_SANITATION "sanitation" #define TECHWEB_NODE_SEC_EQUIP "sec_equip" #define TECHWEB_NODE_SELECTION "selection" +#define TECHWEB_NODE_SHUTTLE_ENG "shuttle_eng" #define TECHWEB_NODE_SPEC_ENG "spec_eng" #define TECHWEB_NODE_STICKY_ADVANCED "sticky_advanced" #define TECHWEB_NODE_SURGERY "surgery" diff --git a/code/__DEFINES/shuttles.dm b/code/__DEFINES/shuttles.dm index 12f15ab1e68..8a31acfc087 100644 --- a/code/__DEFINES/shuttles.dm +++ b/code/__DEFINES/shuttles.dm @@ -130,3 +130,21 @@ #define HIJACK_STAGE_3 3 #define HIJACK_STAGE_4 4 #define HIJACK_COMPLETED 5 + +// Custom shuttle construction errors +#define ORIGIN_NOT_ON_SHUTTLE 1 << 0 + +// The following two errors occur in mutually exclusive contexts, so they can share the same bitflag +#define TOO_MANY_SHUTTLES 1 << 1 +#define FRAME_NOT_ADJACENT_TO_LINKED_SHUTTLE 1 << 1 + +#define ABOVE_MAX_SHUTTLE_SIZE 1 << 2 +#define CUSTOM_AREA_NOT_COMPLETELY_CONTAINED 1 << 3 +#define INTERSECTS_NON_WHITELISTED_AREA 1 << 4 + +// Custom shuttle engine defines +#define CUSTOM_ENGINE_COEFF_MIN 0.5 +#define CUSTOM_ENGINE_COEFF_MAX 10 + +#define CUSTOM_ENGINE_POWER_MULTIPLIER 25 +#define CUSTOM_ENGINE_POWER_TURF_COUNT_OFFSET 5 diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index db594d235c8..1c081f14c54 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -1438,4 +1438,18 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai /// Prevents fishing achievement from being granted by catching one of these #define TRAIT_NO_FISHING_ACHIEVEMENT "no_fishing_achievement" +/** + * This trait is given to turfs that have had shuttle frame parts built on them, but are not yet part of a shuttle. + * When attempting custom shuttle creation, a flood fill algorithm + * checks for turfs with this trait to determine the turfs + * that will constitute the created shuttle. + */ +#define TRAIT_SHUTTLE_CONSTRUCTION_TURF "shuttle_construction_turf" + +// Trait given to areas with a shuttle construction turf in them +#define TRAIT_HAS_SHUTTLE_CONSTRUCTION_TURF "has_shuttle_construction_turf" + +///A trait given to users as a mutex to prevent repeated unresolved attempts to christen a shuttle +#define TRAIT_ATTEMPTING_CHRISTENING "attempting_christening" + // END TRAIT DEFINES diff --git a/code/__DEFINES/turfs.dm b/code/__DEFINES/turfs.dm index ac4f62f10ee..f66edbed8ad 100644 --- a/code/__DEFINES/turfs.dm +++ b/code/__DEFINES/turfs.dm @@ -5,6 +5,7 @@ #define CHANGETURF_INHERIT_AIR (1<<4) // Inherit air from previous turf. Implies CHANGETURF_IGNORE_AIR #define CHANGETURF_RECALC_ADJACENT (1<<5) //Immediately recalc adjacent atmos turfs instead of queuing. #define CHANGETURF_TRAPDOOR_INDUCED (1<<6) // Caused by a trapdoor, for trapdoor to know that this changeturf was caused by itself +#define CHANGETURF_GENERATE_SHUTTLE_CEILING (1<<7) // Generate a shuttle ceiling on the above turf #define IS_OPAQUE_TURF(turf) (turf.directional_opacity == ALL_CARDINALS) diff --git a/code/__HELPERS/areas.dm b/code/__HELPERS/areas.dm index 8e818e0e7f4..2395bcbde95 100644 --- a/code/__HELPERS/areas.dm +++ b/code/__HELPERS/areas.dm @@ -1,4 +1,6 @@ #define BP_MAX_ROOM_SIZE 300 +#define EXTRA_ROOM_CHECK_SKIP 1 +#define EXTRA_ROOM_CHECK_FAIL 2 GLOBAL_LIST_INIT(typecache_powerfailure_safe_areas, typecacheof(list( /area/station/engineering/main, @@ -13,8 +15,9 @@ GLOBAL_LIST_INIT(typecache_powerfailure_safe_areas, typecacheof(list( // Returns an associative list of turf|dirs pairs // The dirs are connected turfs in the same space // break_if_found is a typecache of turf/area types to return false if found +// extra_check is an optional callback to invoke on each turf checked, and can specify whether to skip processing the turf or return false // Please keep this proc type agnostic. If you need to restrict it do it elsewhere or add an arg. -/proc/detect_room(turf/origin, list/break_if_found = list(), max_size=INFINITY) +/proc/detect_room(turf/origin, list/break_if_found = list(), max_size=INFINITY, datum/callback/extra_check) if(origin.blocks_air) return list(origin) @@ -35,6 +38,11 @@ GLOBAL_LIST_INIT(typecache_powerfailure_safe_areas, typecacheof(list( continue checked_turfs[sourceT] |= dir checked_turfs[checkT] |= REVERSE_DIR(dir) + switch(extra_check?.Invoke(checkT)) + if(EXTRA_ROOM_CHECK_SKIP) + continue + if(EXTRA_ROOM_CHECK_FAIL) + return FALSE .[sourceT] |= dir .[checkT] |= REVERSE_DIR(dir) if(break_if_found[checkT.type] || break_if_found[checkT.loc.type]) @@ -81,6 +89,28 @@ GLOBAL_LIST_INIT(typecache_powerfailure_safe_areas, typecacheof(list( return counter += 1 //increment by one so the next loop will start at the next position in the list +/proc/set_turfs_to_area(list/turf/turfs, area/new_area, list/area/affected_areas = list()) + for(var/turf/the_turf as anything in turfs) + var/area/old_area = the_turf.loc + + //keep rack of all areas affected by turf changes + affected_areas[old_area.name] = old_area + + //move the turf to its new area and unregister it from the old one + the_turf.change_area(old_area, new_area) + + //inform atoms on the turf that their area has changed + for(var/atom/stuff as anything in the_turf) + //unregister the stuff from its old area + SEND_SIGNAL(stuff, COMSIG_EXIT_AREA, old_area) + + //register the stuff to its new area. special exception for apc as its not registered to this signal + if(istype(stuff, /obj/machinery/power/apc)) + var/obj/machinery/power/apc/area_apc = stuff + area_apc.assign_to_area() + else + SEND_SIGNAL(stuff, COMSIG_ENTER_AREA, new_area) + /proc/create_area(mob/creator, new_area_type = /area) // Passed into the above proc as list/break_if_found var/static/list/area_or_turf_fail_types = typecacheof(list( @@ -135,8 +165,10 @@ GLOBAL_LIST_INIT(typecache_powerfailure_safe_areas, typecacheof(list( if(!str) return newA = new area_choice + newA.AddComponent(/datum/component/custom_area) newA.setup(str) newA.default_gravity = oldA.default_gravity + GLOB.custom_areas[newA] = TRUE require_area_resort() //new area registered. resort the names else newA = area_choice @@ -151,26 +183,7 @@ GLOBAL_LIST_INIT(typecache_powerfailure_safe_areas, typecacheof(list( * we use this to keep track of what areas are affected by the blueprints & what machinery of these areas needs to be reconfigured accordingly */ var/list/area/affected_areas = list() - for(var/turf/the_turf as anything in turfs) - var/area/old_area = the_turf.loc - - //keep rack of all areas affected by turf changes - affected_areas[old_area.name] = old_area - - //move the turf to its new area and unregister it from the old one - the_turf.change_area(old_area, newA) - - //inform atoms on the turf that their area has changed - for(var/atom/stuff as anything in the_turf) - //unregister the stuff from its old area - SEND_SIGNAL(stuff, COMSIG_EXIT_AREA, old_area) - - //register the stuff to its new area. special exception for apc as its not registered to this signal - if(istype(stuff, /obj/machinery/power/apc)) - var/obj/machinery/power/apc/area_apc = stuff - area_apc.assign_to_area() - else - SEND_SIGNAL(stuff, COMSIG_ENTER_AREA, newA) + set_turfs_to_area(turfs, newA, affected_areas) newA.reg_in_areas_in_z() diff --git a/code/__HELPERS/pronouns.dm b/code/__HELPERS/pronouns.dm index 28bb4c11662..6568909c964 100644 --- a/code/__HELPERS/pronouns.dm +++ b/code/__HELPERS/pronouns.dm @@ -57,6 +57,9 @@ /datum/proc/p_es(temp_gender) return "es" +/datum/proc/p_themselves(temp_gender) + return "itself" + /datum/proc/plural_s(pluralize) switch(copytext_char(pluralize, -2)) if ("ss") @@ -178,6 +181,19 @@ if(temp_gender != PLURAL && temp_gender != NEUTER) return "es" +/client/p_themselves(temp_gender) + if(!temp_gender) + temp_gender = gender + switch(temp_gender) + if(FEMALE) + return "herself" + if(MALE) + return "himself" + if(PLURAL) + return "themselves" + else + return "itself" + //mobs(and atoms but atoms don't really matter write your own proc overrides) also have gender! /mob/p_they(temp_gender) if(!temp_gender) @@ -271,6 +287,19 @@ if(temp_gender != PLURAL) return "es" +/mob/p_themselves(temp_gender) + if(!temp_gender) + temp_gender = gender + switch(temp_gender) + if(FEMALE) + return "herself" + if(MALE) + return "himself" + if(PLURAL) + return "themselves" + else + return "itself" + //humans need special handling, because they can have their gender hidden /mob/living/carbon/human/p_they(temp_gender) var/obscured = check_obscured_slots() @@ -342,6 +371,13 @@ temp_gender = PLURAL return ..() +/mob/living/carbon/human/p_themselves(temp_gender) + var/obscured = check_obscured_slots() + var/skipface = (wear_mask && (wear_mask.flags_inv & HIDEFACE)) || (head && (head.flags_inv & HIDEFACE)) + if((obscured & ITEM_SLOT_ICLOTHING) && skipface) + temp_gender = PLURAL + return ..() + //clothing need special handling due to pairs of items, ie gloves vs a singular glove, shoes, ect. /obj/item/clothing/p_they(temp_gender) if(!temp_gender) @@ -411,6 +447,19 @@ if(temp_gender != PLURAL) return "es" +/obj/item/clothing/p_themselves(temp_gender) + if(!temp_gender) + temp_gender = gender + switch(temp_gender) + if(FEMALE) + return "herself" + if(MALE) + return "himself" + if(PLURAL) + return "themselves" + else + return "itself" + /datum/mind/p_they(temp_gender) return current?.p_they(temp_gender) || ..() @@ -440,3 +489,6 @@ /datum/mind/p_es(temp_gender) return current?.p_es(temp_gender) || ..() + +/datum/mind/p_themselves(temp_gender) + return current?.p_themselves(temp_gender) || ..() diff --git a/code/__HELPERS/spatial_info.dm b/code/__HELPERS/spatial_info.dm index 050c569cbdf..3125f380902 100644 --- a/code/__HELPERS/spatial_info.dm +++ b/code/__HELPERS/spatial_info.dm @@ -443,6 +443,16 @@ ) list_clear_nulls(.) +///Returns a list of all turfs that are adjacent to the center atom's turf, clear the list of nulls at the end. +/proc/get_adjacent_turfs(atom/center) + . = list( + get_step(center, NORTH), + get_step(center, SOUTH), + get_step(center, EAST), + get_step(center, WEST) + ) + list_clear_nulls(.) + ///Checks if the mob provided (must_be_alone) is alone in an area /proc/alone_in_area(area/the_area, mob/must_be_alone, check_type = /mob/living/carbon) var/area/our_area = get_area(the_area) diff --git a/code/__HELPERS/string_lists.dm b/code/__HELPERS/string_lists.dm index 99ce28fba1d..eae730213f7 100644 --- a/code/__HELPERS/string_lists.dm +++ b/code/__HELPERS/string_lists.dm @@ -22,6 +22,7 @@ GLOBAL_LIST_EMPTY(string_lists) stack_trace("The baseturfs list of [baseturf_holder] at [baseturf_holder.x], [baseturf_holder.y], [baseturf_holder.x] is [length(values)], it should never be this long, investigate. I've set baseturfs to a flashing wall as a visual queue") baseturf_holder.ChangeTurf(/turf/closed/indestructible/baseturfs_ded, list(/turf/closed/indestructible/baseturfs_ded), flags = CHANGETURF_FORCEOP) return string_list(list(/turf/closed/indestructible/baseturfs_ded)) //I want this reported god damn it + return string_list(values) /turf/closed/indestructible/baseturfs_ded diff --git a/code/_globalvars/lists/mapping.dm b/code/_globalvars/lists/mapping.dm index 78680d4b216..35a18fffdd3 100644 --- a/code/_globalvars/lists/mapping.dm +++ b/code/_globalvars/lists/mapping.dm @@ -147,6 +147,8 @@ GLOBAL_LIST_EMPTY(areas) GLOBAL_LIST_EMPTY(sortedAreas) /// An association from typepath to area instance. Only includes areas with `unique` set. GLOBAL_LIST_EMPTY_TYPED(areas_by_type, /area) +/// A list of player-created areas. +GLOBAL_LIST_EMPTY_TYPED(custom_areas, /area) GLOBAL_LIST_EMPTY(all_abstract_markers) diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index 820cf66fa69..9151dcb5bc7 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -88,6 +88,9 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_WAS_RENAMED" = TRAIT_WAS_RENAMED, "TRAIT_WEATHER_IMMUNE" = TRAIT_WEATHER_IMMUNE, ), + /area = list( + "TRAIT_HAS_SHUTTLE_CONSTRUCTION_TURF" = TRAIT_HAS_SHUTTLE_CONSTRUCTION_TURF, + ), /datum/controller/subsystem/economy = list( "TRAIT_MARKET_CRASHING" = TRAIT_MARKET_CRASHING, ), @@ -157,6 +160,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_ANTIMAGIC" = TRAIT_ANTIMAGIC, "TRAIT_ANTIMAGIC_NO_SELFBLOCK" = TRAIT_ANTIMAGIC_NO_SELFBLOCK, "TRAIT_ANXIOUS" = TRAIT_ANXIOUS, + "TRAIT_ATTEMPTING_CHRISTENING" = TRAIT_ATTEMPTING_CHRISTENING, "TRAIT_BADDNA" = TRAIT_BADDNA, "TRAIT_BADTOUCH" = TRAIT_BADTOUCH, "TRAIT_BALD" = TRAIT_BALD, @@ -740,6 +744,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_IMMERSE_STOPPED" = TRAIT_IMMERSE_STOPPED, "TRAIT_LAVA_STOPPED" = TRAIT_LAVA_STOPPED, "TRAIT_NO_TERRAFORM" = TRAIT_NO_TERRAFORM, + "TRAIT_SHUTTLE_CONSTRUCTION_TURF" = TRAIT_SHUTTLE_CONSTRUCTION_TURF, "TRAIT_SPINNING_WEB_TURF" = TRAIT_SPINNING_WEB_TURF, "TRAIT_TURF_IGNORE_SLIPPERY" = TRAIT_TURF_IGNORE_SLIPPERY, "TRAIT_TURF_IGNORE_SLOWDOWN" = TRAIT_TURF_IGNORE_SLOWDOWN, diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm index e2dd97935e0..0a9cd40bddd 100644 --- a/code/controllers/configuration/entries/game_options.dm +++ b/code/controllers/configuration/entries/game_options.dm @@ -494,3 +494,12 @@ integer = FALSE default = 1 min_val = 0.05 + + +//Custom Shuttles +//Shuttle size limiter +/datum/config_entry/number/max_shuttle_count + default = 6 + +/datum/config_entry/number/max_shuttle_size + default = 250 diff --git a/code/controllers/subsystem/shuttle.dm b/code/controllers/subsystem/shuttle.dm index 71d9ed0189e..060e50e4159 100644 --- a/code/controllers/subsystem/shuttle.dm +++ b/code/controllers/subsystem/shuttle.dm @@ -17,6 +17,8 @@ SUBSYSTEM_DEF(shuttle) var/list/mobile_docking_ports = list() /// A list of all the stationary docking ports. var/list/stationary_docking_ports = list() + /// A list of all the custom shuttles. + var/list/custom_shuttles = list() /// A list of all the beacons that can be docked to. var/list/beacon_list = list() /// A list of all the transit docking ports. diff --git a/code/datums/components/connect_containers.dm b/code/datums/components/connect_containers.dm index 22efc634359..59eabcf6658 100644 --- a/code/datums/components/connect_containers.dm +++ b/code/datums/components/connect_containers.dm @@ -33,7 +33,7 @@ /datum/component/connect_containers/proc/set_tracked(atom/movable/new_tracked) if(tracked) UnregisterSignal(tracked, list(COMSIG_MOVABLE_MOVED, COMSIG_QDELETING)) - unregister_signals(tracked.loc) + unregister_signals(tracked) tracked = new_tracked if(!tracked) return diff --git a/code/datums/components/connect_inventory.dm b/code/datums/components/connect_inventory.dm new file mode 100644 index 00000000000..c2e688171b4 --- /dev/null +++ b/code/datums/components/connect_inventory.dm @@ -0,0 +1,59 @@ +/// Behaves similar to connect_loc_behalf, but hooks into signals on items in the user's inventory +/datum/component/connect_inventory + dupe_mode = COMPONENT_DUPE_UNIQUE + + var/list/connections + + var/mob/living/tracked + + var/allowed_slots + +/datum/component/connect_inventory/Initialize(mob/living/tracked, connections, allowed_slots = ALL) + . = ..() + if(!istype(tracked)) + return COMPONENT_INCOMPATIBLE + src.connections = connections + src.tracked = tracked + src.allowed_slots = allowed_slots + +/datum/component/connect_inventory/RegisterWithParent() + RegisterSignal(tracked, COMSIG_MOB_EQUIPPED_ITEM, PROC_REF(on_equipped_item)) + RegisterSignal(tracked, COMSIG_QDELETING, PROC_REF(handle_tracked_qdel)) + update_signals() + +/datum/component/connect_inventory/UnregisterFromParent() + unregister_signals() + UnregisterSignal(tracked, list(COMSIG_MOB_EQUIPPED_ITEM, COMSIG_MOB_UNEQUIPPED_ITEM)) + +/datum/component/connect_inventory/proc/handle_tracked_qdel() + SIGNAL_HANDLER + qdel(src) + +/datum/component/connect_inventory/proc/update_signals() + unregister_signals() + + for(var/obj/item/item as anything in tracked.get_equipped_items(INCLUDE_POCKETS | INCLUDE_HELD)) + if(!(allowed_slots & tracked.get_slot_by_item(item))) + continue + RegisterSignal(item, COMSIG_ITEM_DROPPED, PROC_REF(on_unequipped_item)) + for(var/signal in connections) + parent.RegisterSignal(item, signal, connections[signal]) + +/datum/component/connect_inventory/proc/unregister_signals() + for(var/obj/item/item as anything in tracked.get_equipped_items(INCLUDE_POCKETS | INCLUDE_HELD)) + UnregisterSignal(item, COMSIG_ITEM_DROPPED) + parent.UnregisterSignal(item, connections) + +/datum/component/connect_inventory/proc/on_equipped_item(datum/source, obj/item/equipped, slot) + SIGNAL_HANDLER + if(!(allowed_slots & slot)) + return + // This handler has to be registered on the component itself because users may have their own COMSIG_ITEM_DROPPED handler for the equipped item + RegisterSignal(equipped, COMSIG_ITEM_DROPPED, PROC_REF(on_unequipped_item)) + for(var/signal in connections) + parent.RegisterSignal(equipped, signal, connections[signal]) + +/datum/component/connect_inventory/proc/on_unequipped_item(obj/item/unequipped) + SIGNAL_HANDLER + UnregisterSignal(unequipped, COMSIG_ITEM_DROPPED) + parent.UnregisterSignal(unequipped, connections) diff --git a/code/datums/components/connect_range.dm b/code/datums/components/connect_range.dm index af8ec247eb2..2a9225523d3 100644 --- a/code/datums/components/connect_range.dm +++ b/code/datums/components/connect_range.dm @@ -75,14 +75,13 @@ turfs = list() return - if(ismovable(target.loc)) + var/loc_is_movable = ismovable(target.loc) + + if(loc_is_movable) if(!works_in_containers) unregister_signals(old_loc, turfs) turfs = list() return - //Keep track of possible movement of all movables the target is in. - for(var/atom/movable/container as anything in get_nested_locs(target)) - RegisterSignal(container, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved)) //Only register/unregister turf signals if it's moved to a new turf. if(current_turf == get_turf(old_loc)) @@ -91,6 +90,10 @@ var/list/old_turfs = turfs turfs = RANGE_TURFS(range, current_turf) unregister_signals(old_loc, old_turfs - turfs) + if(loc_is_movable) + //Keep track of possible movement of all movables the target is in. + for(var/atom/movable/container as anything in get_nested_locs(target)) + RegisterSignal(container, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved)) for(var/turf/target_turf as anything in turfs - old_turfs) for(var/signal in connections) parent.RegisterSignal(target_turf, signal, connections[signal]) diff --git a/code/datums/components/crafting/tools.dm b/code/datums/components/crafting/tools.dm index d1d303daf80..a1e1fd1ab76 100644 --- a/code/datums/components/crafting/tools.dm +++ b/code/datums/components/crafting/tools.dm @@ -78,3 +78,34 @@ reqs = list(/obj/item/stack/sheet/bone = 1) time = 2 SECONDS category = CAT_TOOLS + +/datum/crafting_recipe/shuttle_blueprints + name = "Crude Shuttle Blueprints" + result = /obj/item/shuttle_blueprints/crude + reqs = list( + /obj/item/paper = 1, + /obj/item/toy/crayon = CRAFTING_INGREDIENT_USE, + ) + steps = list( + "You must use either a a blue crayon, a rainbow crayon, or a spray can.", + "The crayon or spray can you use must have at least 10 uses remaining." + ) + time = 10 SECONDS + category = CAT_TOOLS + +/datum/crafting_recipe/shuttle_blueprints/check_requirements(mob/user, list/collected_requirements) + var/list/crayons = collected_requirements[/obj/item/toy/crayon] + for(var/obj/item/toy/crayon/crayon as anything in crayons) + if(!is_type_in_list(crayon, list(/obj/item/toy/crayon/blue, /obj/item/toy/crayon/rainbow, /obj/item/toy/crayon/spraycan))) + continue + if(!crayon.check_empty(user, 10)) + return TRUE + +/datum/crafting_recipe/shuttle_blueprints/on_craft_completion(mob/user, atom/result) + var/static/list/valid_types = list(/obj/item/toy/crayon/blue, /obj/item/toy/crayon/rainbow, /obj/item/toy/crayon/spraycan) + for(var/valid_type in valid_types) + var/obj/item/toy/crayon/crayon = locate(valid_type) in range(1) + if(!crayon) + continue + if(crayon.use_charges(user, 10)) + return diff --git a/code/datums/components/custom_area.dm b/code/datums/components/custom_area.dm new file mode 100644 index 00000000000..f46a168bb86 --- /dev/null +++ b/code/datums/components/custom_area.dm @@ -0,0 +1,23 @@ +/** + * Container for data necessary to track custom areas. + * Currently this is just the areas this area was created on top of, in case the area is used to create a custom shuttle. + */ +/datum/component/custom_area + var/list/previous_areas = list() + +/datum/component/custom_area/Initialize(...) + if(!isarea(parent)) + return COMPONENT_INCOMPATIBLE + +/datum/component/custom_area/RegisterWithParent() + RegisterSignal(parent, COMSIG_AREA_TURF_ADDED, PROC_REF(on_turf_added)) + RegisterSignal(parent, COMSIG_AREA_TURF_REMOVED, PROC_REF(on_turf_removed)) + +/datum/component/custom_area/UnregisterFromParent() + UnregisterSignal(parent, list(COMSIG_AREA_TURF_ADDED, COMSIG_AREA_TURF_REMOVED)) + +/datum/component/custom_area/proc/on_turf_added(area/source, turf/turf, area/old_area) + previous_areas[turf] = old_area + +/datum/component/custom_area/proc/on_turf_removed(area/source, turf/turf) + previous_areas -= turf diff --git a/code/datums/elements/shuttle_construction_lattice.dm b/code/datums/elements/shuttle_construction_lattice.dm new file mode 100644 index 00000000000..0c89b7fe110 --- /dev/null +++ b/code/datums/elements/shuttle_construction_lattice.dm @@ -0,0 +1,55 @@ +/// Element used to specify that a lattice is part of an incomplete shuttle frame +/datum/element/shuttle_construction_lattice + element_flags = ELEMENT_DETACH_ON_HOST_DESTROY + var/list/lattices_by_turf = list() + +/datum/element/shuttle_construction_lattice/Attach(obj/structure/lattice/target) + . = ..() + if(!istype(target)) + return ELEMENT_INCOMPATIBLE + var/turf/target_turf = target.loc + if(!istype(target_turf)) + return ELEMENT_INCOMPATIBLE + target_turf.AddElementTrait(TRAIT_SHUTTLE_CONSTRUCTION_TURF, REF(target), eletype = /datum/element/shuttle_construction_turf) + lattices_by_turf[target_turf] = target + RegisterSignal(target, COMSIG_ATOM_EXAMINE, PROC_REF(on_examined)) + RegisterSignal(target, COMSIG_MOVABLE_MOVED, PROC_REF(on_moved)) + RegisterSignal(target, COMSIG_LATTICE_PRE_REPLACE_WITH_CATWALK, PROC_REF(on_replacing_with_catwalk)) + RegisterSignal(target_turf, COMSIG_TURF_ADDED_TO_SHUTTLE, PROC_REF(on_turf_added_to_shuttle)) + +/datum/element/shuttle_construction_lattice/Detach(obj/source, ...) + . = ..() + var/turf/source_turf = source.loc + if(istype(source_turf)) + REMOVE_TRAIT(source_turf, TRAIT_SHUTTLE_CONSTRUCTION_TURF, REF(source)) + lattices_by_turf -= source_turf + UnregisterSignal(source_turf, COMSIG_TURF_ADDED_TO_SHUTTLE) + UnregisterSignal(source, list(COMSIG_ATOM_EXAMINE, COMSIG_MOVABLE_MOVED, COMSIG_LATTICE_PRE_REPLACE_WITH_CATWALK)) + +/datum/element/shuttle_construction_lattice/proc/on_examined(obj/source, mob/user, list/examine_list) + SIGNAL_HANDLER + examine_list += span_notice("Cutting this [source.name] will ruin the treatment that makes it suitable for shuttle construction.") + +/datum/element/shuttle_construction_lattice/proc/on_moved(obj/source, atom/old_loc) + SIGNAL_HANDLER + var/trait_source = REF(source) + if(isturf(old_loc)) + REMOVE_TRAIT(old_loc, TRAIT_SHUTTLE_CONSTRUCTION_TURF, trait_source) + UnregisterSignal(old_loc, COMSIG_TURF_ADDED_TO_SHUTTLE) + lattices_by_turf -= old_loc + var/turf/new_turf = source.loc + if(istype(new_turf)) + new_turf.AddElementTrait(TRAIT_SHUTTLE_CONSTRUCTION_TURF, trait_source, /datum/element/shuttle_construction_turf) + RegisterSignal(new_turf, COMSIG_TURF_ADDED_TO_SHUTTLE, PROC_REF(on_turf_added_to_shuttle)) + lattices_by_turf[new_turf] = source + +/datum/element/shuttle_construction_lattice/proc/on_replacing_with_catwalk(obj/source, list/callbacks) + SIGNAL_HANDLER + callbacks += CALLBACK(src, PROC_REF(register_catwalk)) + +/datum/element/shuttle_construction_lattice/proc/register_catwalk(obj/structure/lattice/catwalk/new_catwalk) + new_catwalk.AddElement(/datum/element/shuttle_construction_lattice) + +/datum/element/shuttle_construction_lattice/proc/on_turf_added_to_shuttle(turf/source) + var/obj/structure/lattice/turf_lattice = lattices_by_turf[source] + turf_lattice?.RemoveElement(/datum/element/shuttle_construction_lattice) diff --git a/code/datums/elements/shuttle_construction_turf.dm b/code/datums/elements/shuttle_construction_turf.dm new file mode 100644 index 00000000000..3c345e5dbf6 --- /dev/null +++ b/code/datums/elements/shuttle_construction_turf.dm @@ -0,0 +1,43 @@ +/// Element used to track the conditions for a turf being part of an incomplete shuttle frame +/datum/element/shuttle_construction_turf + element_flags = ELEMENT_DETACH_ON_HOST_DESTROY + +/datum/element/shuttle_construction_turf/Attach(turf/target) + . = ..() + if(!istype(target)) + return ELEMENT_INCOMPATIBLE + RegisterSignal(target, COMSIG_TURF_CHANGE, PROC_REF(pre_turf_changed)) + RegisterSignal(target, COMSIG_TURF_ATTEMPT_LATTICE_REPLACEMENT, PROC_REF(pre_lattice_replacement)) + RegisterSignal(target, COMSIG_TURF_ADDED_TO_SHUTTLE, PROC_REF(on_turF_added_to_shuttle)) + ADD_TRAIT((get_area(target)), TRAIT_HAS_SHUTTLE_CONSTRUCTION_TURF, REF(target)) + +/datum/element/shuttle_construction_turf/Detach(turf/source, ...) + . = ..() + UnregisterSignal(source, list(COMSIG_TURF_CHANGE, COMSIG_TURF_ATTEMPT_LATTICE_REPLACEMENT, COMSIG_TURF_ADDED_TO_SHUTTLE, SIGNAL_REMOVETRAIT(TRAIT_SHUTTLE_CONSTRUCTION_TURF))) + REMOVE_TRAIT((get_area(source)), TRAIT_HAS_SHUTTLE_CONSTRUCTION_TURF, REF(source)) + +///Changing or destroying the turf detaches the element, also we need to reapply the traits since they don't get passed down. +/datum/element/shuttle_construction_turf/proc/pre_turf_changed(turf/source, path, list/new_baseturfs, flags, list/post_change_callbacks) + SIGNAL_HANDLER + var/list/old_trait_sources = GET_TRAIT_SOURCES(source, TRAIT_SHUTTLE_CONSTRUCTION_TURF) + old_trait_sources = old_trait_sources.Copy() + post_change_callbacks += CALLBACK(src, PROC_REF(post_turf_changed), old_trait_sources) + +/datum/element/shuttle_construction_turf/proc/pre_lattice_replacement(turf/source, list/post_successful_replacement_callbacks) + SIGNAL_HANDLER + post_successful_replacement_callbacks += CALLBACK(src, PROC_REF(register_lattice)) + +/datum/element/shuttle_construction_turf/proc/post_turf_changed(list/trait_sources, turf/new_turf) + if(isfloorturf(new_turf) || iswallturf(new_turf)) + trait_sources |= ELEMENT_TRAIT(type) + else + trait_sources -= ELEMENT_TRAIT(type) + if(length(trait_sources)) + for(var/source in trait_sources) + new_turf.AddElementTrait(TRAIT_SHUTTLE_CONSTRUCTION_TURF, source, type) + +/datum/element/shuttle_construction_turf/proc/register_lattice(obj/structure/lattice/new_lattice) + new_lattice.AddElement(/datum/element/shuttle_construction_lattice) + +/datum/element/shuttle_construction_turf/proc/on_turF_added_to_shuttle(turf/source) + REMOVE_TRAIT(source, TRAIT_SHUTTLE_CONSTRUCTION_TURF, ELEMENT_TRAIT(type)) diff --git a/code/datums/shuttles/emergency.dm b/code/datums/shuttles/emergency.dm index 70161d2622b..75e1d56f6e1 100644 --- a/code/datums/shuttles/emergency.dm +++ b/code/datums/shuttles/emergency.dm @@ -53,12 +53,6 @@ who_can_purchase = list(ACCESS_CAPTAIN, ACCESS_CE) occupancy_limit = "Flexible" -/datum/map_template/shuttle/emergency/construction/post_load() - . = ..() - //enable buying engines from cargo - var/datum/supply_pack/P = SSshuttle.supply_packs[/datum/supply_pack/engineering/shuttle_engine] - P.special_enabled = TRUE - /datum/map_template/shuttle/emergency/asteroid suffix = "asteroid" name = "Asteroid Station Emergency Shuttle" diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm index de2b9ebb6c2..5ef0f578120 100644 --- a/code/game/area/areas.dm +++ b/code/game/area/areas.dm @@ -355,6 +355,8 @@ GLOBAL_LIST_EMPTY(teleportlocs) //just for sanity sake cause why not if(!isnull(GLOB.areas)) GLOB.areas -= src + if(!isnull(GLOB.custom_areas)) + GLOB.custom_areas -= src //machinery cleanup STOP_PROCESSING(SSobj, src) QDEL_NULL(alarm_manager) diff --git a/code/game/area/areas/shuttles.dm b/code/game/area/areas/shuttles.dm index f93f0ca4c5a..c82f04e7027 100644 --- a/code/game/area/areas/shuttles.dm +++ b/code/game/area/areas/shuttles.dm @@ -21,10 +21,17 @@ . = ..() if(ispath(added_layer, /turf/open/floor/plating)) new_baseturfs.Add(/turf/baseturf_skipover/shuttle) + . |= CHANGETURF_GENERATE_SHUTTLE_CEILING else if(ispath(new_baseturfs[1], /turf/open/floor/plating)) new_baseturfs.Insert(1, /turf/baseturf_skipover/shuttle) + . |= CHANGETURF_GENERATE_SHUTTLE_CEILING -////////////////////////////Multi-area shuttles//////////////////////////// +////////////////////////////Custom Shuttles//////////////////////////// + +/area/shuttle/custom + requires_power = TRUE + +////////////////////////////Multi-area shuttles////////////////////////////// ////////////////////////////Syndicate infiltrator//////////////////////////// diff --git a/code/game/objects/items/circuitboards/computer_circuitboards.dm b/code/game/objects/items/circuitboards/computer_circuitboards.dm index 82d96329540..e77e36a0dac 100644 --- a/code/game/objects/items/circuitboards/computer_circuitboards.dm +++ b/code/game/objects/items/circuitboards/computer_circuitboards.dm @@ -650,3 +650,24 @@ /obj/item/circuitboard/computer/exodrone_console name = "Exploration Drone Control Console" build_path = /obj/machinery/computer/exodrone_control_console + +/obj/item/circuitboard/computer/shuttle + var/shuttle_id + +/obj/item/circuitboard/computer/shuttle/configure_machine(obj/machinery/machine) + var/obj/docking_port/mobile/custom/shuttle = shuttle_id ? SSshuttle.getShuttle(shuttle_id) : SSshuttle.get_containing_shuttle(machine) + if(!shuttle) + var/on_shuttle_frame = HAS_TRAIT((get_turf(machine)), TRAIT_SHUTTLE_CONSTRUCTION_TURF) + machine.say(on_shuttle_frame ? "Console will automatically link on shuttle completion." : "No shuttle available for linking.") + else if(!istype(shuttle)) + machine.say("Cannot link to this kind of shuttle!") + else + machine.connect_to_shuttle(TRUE, shuttle) + +/obj/item/circuitboard/computer/shuttle/flight_control + name = "Shuttle Flight Control (Computer Board)" + build_path = /obj/machinery/computer/shuttle/custom_shuttle + +/obj/item/circuitboard/computer/shuttle/docker + name = "Shuttle Navigation Computer (Computer Board)" + build_path = /obj/machinery/computer/camera_advanced/shuttle_docker/custom diff --git a/code/game/objects/items/robot/items/storage.dm b/code/game/objects/items/robot/items/storage.dm index 4d90c28de6d..e0310d62584 100644 --- a/code/game/objects/items/robot/items/storage.dm +++ b/code/game/objects/items/robot/items/storage.dm @@ -251,10 +251,11 @@ ///Apparatus to allow Engineering/Sabo borgs to manipulate any material sheets. /obj/item/borg/apparatus/sheet_manipulator name = "material manipulation apparatus" - desc = "An apparatus for carrying, deploying, and manipulating sheets of material. The device can also carry custom floor tiles." + desc = "An apparatus for carrying, deploying, and manipulating sheets of material. The device can also carry custom floor tiles and shuttle frame rods." icon_state = "borg_stack_apparatus" storable = list(/obj/item/stack/sheet, - /obj/item/stack/tile) + /obj/item/stack/tile, + /obj/item/stack/rods/shuttle) /obj/item/borg/apparatus/sheet_manipulator/Initialize(mapload) update_appearance() diff --git a/code/game/objects/items/robot/robot_upgrades.dm b/code/game/objects/items/robot/robot_upgrades.dm index f448ec0ed59..a6702259316 100644 --- a/code/game/objects/items/robot/robot_upgrades.dm +++ b/code/game/objects/items/robot/robot_upgrades.dm @@ -803,6 +803,16 @@ items_to_add = list(/obj/item/storage/bag/plants/cyborg, /obj/item/borg/cyborg_omnitool/botany, /obj/item/plant_analyzer) +/obj/item/borg/upgrade/shuttle_blueprints + name = "Engineering Cyborg Shuttle Blueprint Database" + desc = "An upgrade to the engineering model cyborg allowing for the construction and expansion of shuttles." + icon_state = "module_engineer" + require_model = TRUE + model_type = list(/obj/item/robot_model/engineering, /obj/item/robot_model/saboteur) + model_flags = BORG_MODEL_ENGINEERING + + items_to_add = list(/obj/item/shuttle_blueprints/borg) + ///This isn't an upgrade or part of the same path, but I'm gonna just stick it here because it's a tool used on cyborgs. //A reusable tool that can bring borgs back to life. They gotta be repaired first, though. diff --git a/code/game/objects/items/stacks/rods.dm b/code/game/objects/items/stacks/rods.dm index 6523a52308c..127f39bfb81 100644 --- a/code/game/objects/items/stacks/rods.dm +++ b/code/game/objects/items/stacks/rods.dm @@ -145,3 +145,22 @@ GLOBAL_LIST_INIT(rod_recipes, list ( \ /obj/item/stack/rods/lava/thirty amount = 30 + +/obj/item/stack/rods/shuttle + name = "shuttle frame rods" + desc = "Treated, specialized iron rods suitable for the construction of shuttle frames or the expansion of existing shuttles." + singular_name = "shuttle frame rod" + mats_per_unit = list(/datum/material/iron=HALF_SHEET_MATERIAL_AMOUNT, /datum/material/titanium=SMALL_MATERIAL_AMOUNT) + merge_type = /obj/item/stack/rods/shuttle + +/obj/item/stack/rods/shuttle/five + amount = 5 + +/obj/item/stack/rods/shuttle/ten + amount = 10 + +/obj/item/stack/rods/shuttle/twentyfive + amount = 25 + +/obj/item/stack/rods/shuttle/fifty + amount = 50 diff --git a/code/game/objects/items/stacks/sheets/mineral.dm b/code/game/objects/items/stacks/sheets/mineral.dm index 4b9990ea04e..14a9b5dcfb7 100644 --- a/code/game/objects/items/stacks/sheets/mineral.dm +++ b/code/game/objects/items/stacks/sheets/mineral.dm @@ -296,6 +296,26 @@ GLOBAL_LIST_INIT(titanium_recipes, list ( \ . = ..() . += GLOB.titanium_recipes +/obj/item/stack/sheet/mineral/titanium/attackby(obj/item/W, mob/user, params) + add_fingerprint(user) + if(istype(W, /obj/item/stack/rods)) + var/obj/item/stack/rods/old_rods = W + if(old_rods.merge_type != /obj/item/stack/rods) + to_chat(user, span_warning("You can't craft shuttle frame rods with this type of rod!")) + if (old_rods.get_amount() >= 5 && get_amount() >= 1) + var/obj/item/stack/rods/shuttle/five/new_rods = new (get_turf(user)) + if(!QDELETED(new_rods)) + new_rods.add_fingerprint(user) + var/replace = user.get_inactive_held_item() == src + old_rods.use(5) + use(1) + if(QDELETED(src) && replace && !QDELETED(new_rods)) + user.put_in_hands(new_rods) + else + to_chat(user, span_warning("You need five rods and one sheet of titanium to make shuttle frame rods!")) + return + return ..() + /obj/item/stack/sheet/mineral/titanium/fifty amount = 50 diff --git a/code/game/objects/structures/lattice.dm b/code/game/objects/structures/lattice.dm index f22ef3f7e2b..40bc738e2fb 100644 --- a/code/game/objects/structures/lattice.dm +++ b/code/game/objects/structures/lattice.dm @@ -76,16 +76,14 @@ /obj/structure/lattice/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, list/rcd_data) if(rcd_data["[RCD_DESIGN_MODE]"] == RCD_TURF) var/design_structure = rcd_data["[RCD_DESIGN_PATH]"] - if(design_structure == /turf/open/floor/plating) + if(design_structure == /turf/open/floor/plating/rcd) var/turf/T = src.loc if(isgroundlessturf(T)) T.place_on_top(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) qdel(src) return TRUE if(design_structure == /obj/structure/lattice/catwalk) - var/turf/turf = loc - qdel(src) - new /obj/structure/lattice/catwalk(turf) + replace_with_catwalk() return TRUE return FALSE @@ -93,6 +91,15 @@ if(current_size >= STAGE_FOUR) deconstruct() +/obj/structure/lattice/proc/replace_with_catwalk() + var/list/post_replacement_callbacks = list() + SEND_SIGNAL(src, COMSIG_LATTICE_PRE_REPLACE_WITH_CATWALK, post_replacement_callbacks) + var/turf/turf = loc + qdel(src) + var/new_catwalk = new /obj/structure/lattice/catwalk(turf) + for(var/datum/callback/callback as anything in post_replacement_callbacks) + callback.Invoke(new_catwalk) + /obj/structure/lattice/catwalk name = "catwalk" desc = "A catwalk for easier EVA maneuvering and cable placement." diff --git a/code/game/shuttle_engines.dm b/code/game/shuttle_engines.dm index 726b7274834..f8d2a9db058 100644 --- a/code/game/shuttle_engines.dm +++ b/code/game/shuttle_engines.dm @@ -26,6 +26,8 @@ ///The mobile ship we are connected to. var/datum/weakref/connected_ship_ref + var/static/list/connections = list(COMSIG_TURF_ADDED_TO_SHUTTLE = PROC_REF(on_turf_added_to_shuttle)) + /datum/armor/power_shuttle_engine melee = 100 bullet = 10 @@ -35,6 +37,7 @@ /obj/machinery/power/shuttle_engine/Initialize(mapload) . = ..() + AddComponent(/datum/component/simple_rotation) register_context() /obj/machinery/power/shuttle_engine/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock) @@ -83,6 +86,7 @@ port.engine_list -= src port.current_engine_power -= initial(engine_power) connected_ship_ref = null + RemoveElement(/datum/element/connect_loc, connections) //Ugh this is a lot of copypasta from emitters, welding need some boilerplate reduction /obj/machinery/power/shuttle_engine/can_be_unfasten_wrench(mob/user, silent) @@ -97,11 +101,17 @@ if(. == SUCCESSFUL_UNFASTEN) if(anchored) connect_to_shuttle(port = SSshuttle.get_containing_shuttle(src)) //connect to a new ship, if needed + if(!connected_ship_ref?.resolve()) + AddElement(/datum/element/connect_loc, connections) engine_state = ENGINE_WRENCHED else unsync_ship() //not part of the ship anymore engine_state = ENGINE_UNWRENCHED +/obj/machinery/power/shuttle_engine/proc/on_turf_added_to_shuttle(turf/source, obj/docking_port/mobile/port) + SIGNAL_HANDLER + connect_to_shuttle(port = port) + /obj/machinery/power/shuttle_engine/wrench_act(mob/living/user, obj/item/tool) . = ..() default_unfasten_wrench(user, tool) diff --git a/code/game/turfs/baseturfs.dm b/code/game/turfs/baseturfs.dm index 29a288b1b59..832b7c1def5 100644 --- a/code/game/turfs/baseturfs.dm +++ b/code/game/turfs/baseturfs.dm @@ -171,3 +171,13 @@ var/floor_position = baseturfs.Find(floor) if(floor_position != 0) insert_baseturf(floor_position + 1, roof) + +/// Places a baseturf below a searched for baseturf. +/turf/proc/stack_below_baseturf(search_type, stack_type) + if(!islist(baseturfs)) + baseturfs = list(baseturfs) + var/search_position = baseturfs.Find(search_type) + if(search_position != 0) + insert_baseturf(search_position, stack_type) + else if(type == search_type) + insert_baseturf(turf_type = stack_type) diff --git a/code/game/turfs/change_turf.dm b/code/game/turfs/change_turf.dm index dc2c257ad38..9e00c034678 100644 --- a/code/game/turfs/change_turf.dm +++ b/code/game/turfs/change_turf.dm @@ -128,6 +128,16 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list( if(!(flags & CHANGETURF_DEFER_CHANGE)) new_turf.AfterChange(flags, old_type) + if(flags & CHANGETURF_GENERATE_SHUTTLE_CEILING) + var/turf/above = get_step_multiz(src, UP) + if(above) + if(!(istype(above, /turf/open/floor/engine/hull/ceiling) || above.depth_to_find_baseturf(/turf/open/floor/engine/hull/ceiling))) + if(istype(above, /turf/open/openspace) || istype(above, /turf/open/space/openspace)) + above.place_on_top(/turf/open/floor/engine/hull/ceiling) + else + above.stack_ontop_of_baseturf(/turf/open/openspace, /turf/open/floor/engine/hull/ceiling) + above.stack_ontop_of_baseturf(/turf/open/space/openspace, /turf/open/floor/engine/hull/ceiling) + new_turf.blueprint_data = old_bp new_turf.rcd_memory = old_rcd_memory new_turf.explosion_throw_details = old_explosion_throw_details @@ -284,8 +294,12 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list( /// Attempts to replace a tile with lattice. Amount is the amount of tiles to scrape away. /turf/proc/attempt_lattice_replacement(amount = 2) if(lattice_underneath) + var/list/successful_replacement_callbacks = list() + SEND_SIGNAL(src, COMSIG_TURF_ATTEMPT_LATTICE_REPLACEMENT, successful_replacement_callbacks) var/turf/new_turf = ScrapeAway(amount, flags = CHANGETURF_INHERIT_AIR) if(!istype(new_turf, /turf/open/floor)) - new /obj/structure/lattice(src) + var/new_lattice = new /obj/structure/lattice(src) + for(var/datum/callback/callback as anything in successful_replacement_callbacks) + callback.Invoke(new_lattice) else ScrapeAway(amount, flags = CHANGETURF_INHERIT_AIR) diff --git a/code/game/turfs/open/_open.dm b/code/game/turfs/open/_open.dm index 8f99f494eeb..6e0d91dbcad 100644 --- a/code/game/turfs/open/_open.dm +++ b/code/game/turfs/open/_open.dm @@ -434,10 +434,9 @@ if(catwalk_bait) if(used_rods.use(1)) - qdel(catwalk_bait) to_chat(user, span_notice("You construct a catwalk.")) playsound(src, 'sound/items/weapons/genhit.ogg', 50, TRUE) - new /obj/structure/lattice/catwalk(src) + catwalk_bait.replace_with_catwalk() else to_chat(user, span_warning("You need two rods to build a catwalk!")) return @@ -445,7 +444,9 @@ if(used_rods.use(1)) to_chat(user, span_notice("You construct a lattice.")) playsound(src, 'sound/items/weapons/genhit.ogg', 50, TRUE) - new /obj/structure/lattice(src) + var/obj/structure/lattice/new_lattice = new (src) + if(istype(used_rods, /obj/item/stack/rods/shuttle) && !istype(loc, /area/shuttle)) + new_lattice.AddElement(/datum/element/shuttle_construction_lattice) else to_chat(user, span_warning("You need one rod to build a lattice.")) @@ -462,6 +463,8 @@ playsound(src, 'sound/items/weapons/genhit.ogg', 50, TRUE) var/turf/open/floor/plating/new_plating = place_on_top(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR) + if(istype(loc, /area/shuttle)) + new_plating.insert_baseturf(turf_type = /turf/baseturf_skipover/shuttle) if(lattice) qdel(lattice) else diff --git a/code/game/turfs/open/floor/hull.dm b/code/game/turfs/open/floor/hull.dm index 6836c29b960..2aec1f97fd3 100644 --- a/code/game/turfs/open/floor/hull.dm +++ b/code/game/turfs/open/floor/hull.dm @@ -8,7 +8,6 @@ /turf/open/floor/engine/hull/ceiling name = "shuttle ceiling plating" - var/old_turf_type /turf/open/floor/engine/hull/ceiling/Initialize(mapload) . = ..() @@ -20,10 +19,6 @@ ///datum/unit_test/mapping_nearstation_test.dm SHOULD fail this case automatically //this is just here so the mapper responsible can easily see where the issues are directly on the map. -/turf/open/floor/engine/hull/ceiling/AfterChange(flags, oldType) - . = ..() - old_turf_type = oldType - /turf/open/floor/engine/hull/reinforced name = "exterior reinforced hull plating" desc = "Extremely sturdy exterior hull plating that separates you from the uncaring vacuum of space." diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm index 7e839e9d709..c5a2f8fe423 100644 --- a/code/game/turfs/turf.dm +++ b/code/game/turfs/turf.dm @@ -244,6 +244,9 @@ GLOBAL_LIST_EMPTY(station_turfs) old_area.turfs_to_uncontain_by_zlevel[z] += src new_area.turfs_by_zlevel[z] += src new_area.contents += src + SEND_SIGNAL(src, COMSIG_TURF_AREA_CHANGED, old_area) + SEND_SIGNAL(new_area, COMSIG_AREA_TURF_ADDED, src, old_area) + SEND_SIGNAL(old_area, COMSIG_AREA_TURF_REMOVED, src, new_area) //changes to make after turf has moved on_change_area(old_area, new_area) diff --git a/code/modules/cargo/goodies.dm b/code/modules/cargo/goodies.dm index ee48e48e403..3b3aec6680c 100644 --- a/code/modules/cargo/goodies.dm +++ b/code/modules/cargo/goodies.dm @@ -366,3 +366,16 @@ desc = "Many people consider mice to be vermin, or dirty lab animals for experimentation, or a culinary delicacy. That's why we're not asking any questions, here." cost = PAYCHECK_CREW * 1.5 contains = list(/obj/item/pet_carrier/small/mouse) + +/datum/supply_pack/goody/shuttle_construction_kit + name = "Shuttle Construction Starter Kit" + desc = "Contains a set of shuttle blueprints, and the circuitboards necessary for constructing your own shuttle. \ + Well at least the ones you can't source yourself without Science's help." + cost = PAYCHECK_COMMAND * 12 //You assistants with shipwrighting ambitions can do a couple bounties, can't you? + access_view = ACCESS_AUX_BASE //Engineers have it, QM can give it to whoever, and scientists can just research the tech. + contains = list( + /obj/item/shuttle_blueprints, + /obj/item/circuitboard/computer/shuttle/flight_control, + /obj/item/circuitboard/computer/shuttle/docker, + /obj/item/circuitboard/machine/engine/propulsion = 2, + ) diff --git a/code/modules/cargo/packs/engineering.dm b/code/modules/cargo/packs/engineering.dm index dd376ec2017..0173fd69e7f 100644 --- a/code/modules/cargo/packs/engineering.dm +++ b/code/modules/cargo/packs/engineering.dm @@ -106,7 +106,6 @@ contains = list(/obj/machinery/power/shuttle_engine/propulsion/burst/cargo) crate_name = "shuttle engine crate" crate_type = /obj/structure/closet/crate/secure/engineering - special = TRUE /datum/supply_pack/engineering/tools name = "Toolbox Crate" diff --git a/code/modules/hallucination/_hallucination.dm b/code/modules/hallucination/_hallucination.dm index b3f33fc3191..df3bbe74757 100644 --- a/code/modules/hallucination/_hallucination.dm +++ b/code/modules/hallucination/_hallucination.dm @@ -107,6 +107,8 @@ var/image_layer = MOB_LAYER /// The plane of the image var/image_plane = GAME_PLANE + /// Should this image holder persist if there are no seers for it? + var/persist_without_seers = FALSE /obj/effect/client_image_holder/Initialize(mapload, list/mobs_which_see_us) . = ..() @@ -121,10 +123,7 @@ who_sees_us = list() for(var/mob/seer as anything in mobs_which_see_us) - RegisterSignal(seer, COMSIG_MOB_LOGIN, PROC_REF(show_image_to)) - RegisterSignal(seer, COMSIG_QDELETING, PROC_REF(remove_seer)) - who_sees_us += seer - show_image_to(seer) + add_seer(seer) /obj/effect/client_image_holder/Destroy(force) if(shown_image) @@ -141,6 +140,12 @@ return SET_PLANE(shown_image, PLANE_TO_TRUE(shown_image.plane), new_turf) +/obj/effect/client_image_holder/proc/add_seer(mob/new_seer) + RegisterSignal(new_seer, COMSIG_MOB_LOGIN, PROC_REF(show_image_to)) + RegisterSignal(new_seer, COMSIG_QDELETING, PROC_REF(remove_seer)) + who_sees_us += new_seer + show_image_to(new_seer) + /// Signal proc to clean up references if people who see us are deleted. /obj/effect/client_image_holder/proc/remove_seer(mob/source) SIGNAL_HANDLER @@ -150,7 +155,7 @@ who_sees_us -= source // No reason to exist, anymore - if(!QDELETED(src) && !length(who_sees_us)) + if(!QDELETED(src) && !length(who_sees_us) && !persist_without_seers) qdel(src) /// Generates the image which we take on. diff --git a/code/modules/research/designs/comp_board_designs.dm b/code/modules/research/designs/comp_board_designs.dm index 6db2a6bb24e..50ed6e43f61 100644 --- a/code/modules/research/designs/comp_board_designs.dm +++ b/code/modules/research/designs/comp_board_designs.dm @@ -395,3 +395,19 @@ RND_CATEGORY_COMPUTER + RND_SUBCATEGORY_COMPUTER_RECORDS ) departmental_flags = DEPARTMENT_BITFLAG_SERVICE + +/datum/design/board/shuttle + category = list("Computer Boards", "Shuttle Machinery") + departmental_flags = DEPARTMENT_BITFLAG_SCIENCE | DEPARTMENT_BITFLAG_ENGINEERING | DEPARTMENT_BITFLAG_CARGO + +/datum/design/board/shuttle/flight_control + name = "Computer Design (Shuttle Flight Controls)" + desc = "Allows for the construction of circuit boards used to build a console that enables shuttle flight" + id = "shuttle_control" + build_path = /obj/item/circuitboard/computer/shuttle/flight_control + +/datum/design/board/shuttle/shuttle_docker + name = "Computer Design (Shuttle Navigation Computer)" + desc = "Allows for the construction of circuit boards used to build a console that enables the targetting of custom flight locations" + id = "shuttle_docker" + build_path = /obj/item/circuitboard/computer/shuttle/docker diff --git a/code/modules/research/designs/machine_designs.dm b/code/modules/research/designs/machine_designs.dm index 6364025924b..5fb67b7356e 100644 --- a/code/modules/research/designs/machine_designs.dm +++ b/code/modules/research/designs/machine_designs.dm @@ -1357,3 +1357,13 @@ RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_CARGO ) departmental_flags = DEPARTMENT_BITFLAG_CARGO | DEPARTMENT_BITFLAG_ENGINEERING + +/datum/design/board/propulsion_engine + name = "Propulsion Engine Board" + desc = "The circuit for a propulsion engine." + id = "propulsion_engine" + build_path = /obj/item/circuitboard/machine/engine/propulsion + category = list( + RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_ENGINEERING + ) + departmental_flags = DEPARTMENT_BITFLAG_CARGO | DEPARTMENT_BITFLAG_SCIENCE | DEPARTMENT_BITFLAG_ENGINEERING diff --git a/code/modules/research/designs/mechfabricator_designs.dm b/code/modules/research/designs/mechfabricator_designs.dm index c06eb40204e..7c288770240 100644 --- a/code/modules/research/designs/mechfabricator_designs.dm +++ b/code/modules/research/designs/mechfabricator_designs.dm @@ -1705,6 +1705,20 @@ RND_CATEGORY_MECHFAB_CYBORG_MODULES + RND_SUBCATEGORY_MECHFAB_CYBORG_MODULES_SERVICE ) +/datum/design/borg_upgrade_shuttle_blueprints + name = "Engineering Shuttle Blueprints" + id = "borg_upgrade_engineering_shuttle_blueprints" + build_type = MECHFAB + build_path = /obj/item/borg/upgrade/shuttle_blueprints + materials = list( + /datum/material/iron = SHEET_MATERIAL_AMOUNT*5, + /datum/material/glass = SHEET_MATERIAL_AMOUNT*2.5, + ) + construction_time = 4 SECONDS + category = list( + RND_CATEGORY_MECHFAB_CYBORG_MODULES + RND_SUBCATEGORY_MECHFAB_CYBORG_MODULES_ENGINEERING + ) + /datum/design/borg_upgrade_expand name = "Expand Module" id = "borg_upgrade_expand" diff --git a/code/modules/research/designs/misc_designs.dm b/code/modules/research/designs/misc_designs.dm index c7441285fc3..6221444ab9d 100644 --- a/code/modules/research/designs/misc_designs.dm +++ b/code/modules/research/designs/misc_designs.dm @@ -1116,6 +1116,17 @@ ) departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING | DEPARTMENT_BITFLAG_SCIENCE | DEPARTMENT_BITFLAG_CARGO +/datum/design/shuttle_rods + name = "Shuttle Frame Rods" + id = "shuttlerods" + build_type = PROTOLATHE + materials = list(/datum/material/iron =HALF_SHEET_MATERIAL_AMOUNT, /datum/material/titanium = SMALL_MATERIAL_AMOUNT) + build_path = /obj/item/stack/rods/shuttle + category = list( + RND_CATEGORY_CONSTRUCTION + RND_SUBCATEGORY_CONSTRUCTION_MATERIALS + ) + departmental_flags = DEPARTMENT_BITFLAG_CARGO | DEPARTMENT_BITFLAG_SCIENCE | DEPARTMENT_BITFLAG_ENGINEERING + // Experimental designs /datum/design/polymorph_belt diff --git a/code/modules/research/designs/tool_designs.dm b/code/modules/research/designs/tool_designs.dm index 901bc304dc1..fdd96513848 100644 --- a/code/modules/research/designs/tool_designs.dm +++ b/code/modules/research/designs/tool_designs.dm @@ -425,3 +425,13 @@ RND_CATEGORY_TOOLS + RND_SUBCATEGORY_TOOLS_ENGINEERING_ADVANCED ) departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING + +/datum/design/shuttle_blueprints + name = "Shuttle Blueprints" + desc = "Blueprints suitable for constructing shuttles" + id = "shuttle_blueprints" + build_type = PROTOLATHE + materials = list(/datum/material/plastic = HALF_SHEET_MATERIAL_AMOUNT) + build_path = /obj/item/shuttle_blueprints + category = list(RND_CATEGORY_TOOLS + RND_SUBCATEGORY_TOOLS_ENGINEERING) + departmental_flags = DEPARTMENT_BITFLAG_CARGO | DEPARTMENT_BITFLAG_ENGINEERING | DEPARTMENT_BITFLAG_SCIENCE diff --git a/code/modules/research/techweb/nodes/engi_nodes.dm b/code/modules/research/techweb/nodes/engi_nodes.dm index 75c9459771c..69f653d8db8 100644 --- a/code/modules/research/techweb/nodes/engi_nodes.dm +++ b/code/modules/research/techweb/nodes/engi_nodes.dm @@ -182,6 +182,22 @@ research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_1_POINTS) announce_channels = list(RADIO_CHANNEL_ENGINEERING) +/datum/techweb_node/shuttle_engineering + id = TECHWEB_NODE_SHUTTLE_ENG + display_name = "Shuttle Engineering" + description = "Materials and equipment for constructing shuttles" + prereq_ids = list(TECHWEB_NODE_ENERGY_MANIPULATION, TECHWEB_NODE_APPLIED_BLUESPACE) + design_ids = list( + "borg_upgrade_engineering_shuttle_blueprints", + "propulsion_engine", + "shuttle_blueprints", + "shuttle_control", + "shuttle_docker", + "shuttlerods", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = TECHWEB_TIER_1_POINTS) + announce_channels = list(RADIO_CHANNEL_ENGINEERING, RADIO_CHANNEL_SCIENCE, RADIO_CHANNEL_SUPPLY) + /datum/techweb_node/holographics id = TECHWEB_NODE_HOLOGRAPHICS display_name = "Holographics" diff --git a/code/modules/shuttle/mobile_port/mobile_port.dm b/code/modules/shuttle/mobile_port/mobile_port.dm index 8679c1f7cf7..b98528d2e08 100644 --- a/code/modules/shuttle/mobile_port/mobile_port.dm +++ b/code/modules/shuttle/mobile_port/mobile_port.dm @@ -56,7 +56,12 @@ ///List of shuttle events that can run or are running var/list/datum/shuttle_event/event_list = list() -/obj/docking_port/mobile/Initialize(mapload) + var/list/underlying_areas_by_turf = list() + + ///How many turfs this shuttle has. Used to check against max shuttle size when expanding expandable shuttles. + var/turf_count = 0 + +/obj/docking_port/mobile/Initialize(mapload, list/areas) . = ..() if(!shuttle_id) @@ -71,12 +76,17 @@ shuttle_id = "[tmp_id]_[counter]" name = "[tmp_name] [counter]" - var/list/all_turfs = return_ordered_turfs(x, y, z, dir) - for(var/i in 1 to all_turfs.len) - var/turf/curT = all_turfs[i] - var/area/cur_area = curT.loc - if(istype(cur_area, area_type)) - shuttle_areas[cur_area] = TRUE + if(areas) + for(var/area/area as anything in areas) + shuttle_areas[area] = TRUE + else + var/list/all_turfs = return_ordered_turfs(x, y, z, dir) + for(var/i in 1 to all_turfs.len) + var/turf/curT = all_turfs[i] + var/area/cur_area = curT.loc + if(istype(cur_area, area_type)) + turf_count++ + shuttle_areas[cur_area] = TRUE #ifdef TESTING highlight("#0f0") @@ -111,28 +121,28 @@ if(!length(shuttle_areas)) CRASH("Attempted to calculate a docking port's information without a template before it was assigned any areas!") // no template given, use shuttle_areas to calculate width and height - var/min_x = -1 - var/min_y = -1 - var/max_x = WORLDMAXX_CUTOFF - var/max_y = WORLDMAXY_CUTOFF + var/min_x = WORLDMAXX_CUTOFF + var/min_y = WORLDMAXY_CUTOFF + var/max_x = -1 + var/max_y = -1 for(var/area/shuttle_area as anything in shuttle_areas) for (var/list/zlevel_turfs as anything in shuttle_area.get_zlevel_turf_lists()) for(var/turf/turf as anything in zlevel_turfs) - min_x = max(turf.x, min_x) - max_x = min(turf.x, max_x) - min_y = max(turf.y, min_y) - max_y = min(turf.y, max_y) + min_x = min(turf.x, min_x) + max_x = max(turf.x, max_x) + min_y = min(turf.y, min_y) + max_y = max(turf.y, max_y) CHECK_TICK - if(min_x == -1 || max_x == WORLDMAXX_CUTOFF) + if(min_x == WORLDMAXX_CUTOFF || max_x == -1) CRASH("Failed to locate shuttle boundaries when iterating through shuttle areas, somehow.") - if(min_y == -1 || max_y == WORLDMAXY_CUTOFF) + if(min_y == WORLDMAXY_CUTOFF || max_y == -1) CRASH("Failed to locate shuttle boundaries when iterating through shuttle areas, somehow.") width = (max_x - min_x) + 1 height = (max_y - min_y) + 1 - port_x_offset = min_x - x - port_y_offset = min_y - y + port_x_offset = x - min_x + 1 + port_y_offset = y - min_y + 1 if(dir in list(EAST, WEST)) src.width = height @@ -157,13 +167,19 @@ #undef WORLDMAXX_CUTOFF #undef WORLDMAXY_CUTOFF +/obj/docking_port/mobile/is_in_shuttle_bounds(atom/A) + . = ..() + if(. && !shuttle_areas[get_area(A)]) + return FALSE + /** * Actions to be taken after shuttle is loaded but before it has been moved out of transit z-level to its final location * * Arguments: * * replace - TRUE if this shuttle is replacing an existing one. FALSE by default. + * * custom - TRUE if this shuttle should be added to the custom shuttle list. FALSE by default. */ -/obj/docking_port/mobile/register(replace = FALSE) +/obj/docking_port/mobile/register(replace = FALSE, custom = FALSE) . = ..() if(!shuttle_id) shuttle_id = "shuttle" @@ -185,6 +201,9 @@ SSshuttle.mobile_docking_ports += src + if(custom) + SSshuttle.custom_shuttles += src + /** * Actions to be taken after shuttle is loaded and has been moved to its final location * @@ -197,6 +216,7 @@ /obj/docking_port/mobile/unregister() . = ..() SSshuttle.mobile_docking_ports -= src + SSshuttle.custom_shuttles -= src diff --git a/code/modules/shuttle/mobile_port/shuttle_move.dm b/code/modules/shuttle/mobile_port/shuttle_move.dm index b7e125826dc..5c748bc2cfd 100644 --- a/code/modules/shuttle/mobile_port/shuttle_move.dm +++ b/code/modules/shuttle/mobile_port/shuttle_move.dm @@ -17,11 +17,11 @@ var/obj/docking_port/stationary/old_dock = get_docked() - // The area that gets placed under where the shuttle moved from - var/underlying_area_type = SHUTTLE_DEFAULT_UNDERLYING_AREA + // The area that gets placed under shuttle turfs that do not have their own area to place down + var/fallback_area_type = SHUTTLE_DEFAULT_UNDERLYING_AREA if(old_dock) //Dock overwrites - underlying_area_type = old_dock.area_type + fallback_area_type = old_dock.area_type /************************************************************************************************************** Both lists are associative with a turf:bitflag structure. (new_turfs bitflag space unused currently) @@ -33,11 +33,11 @@ CHECK_TICK /**************************************************************************************************************/ - // The underlying old area is the area assumed to be under the shuttle's starting location + // The fallback area is the area for shuttle turfs that have no area underneath them // If it no longer/has never existed it will be created - var/area/underlying_old_area = GLOB.areas_by_type[underlying_area_type] - if(!underlying_old_area) - underlying_old_area = new underlying_area_type(null) + var/area/fallback_area = GLOB.areas_by_type[fallback_area_type] + if(!fallback_area) + fallback_area = new fallback_area_type(null) var/rotation = 0 if(new_dock.dir != dir) //Even when the dirs are the same rotation is coming out as not 0 for some reason @@ -51,8 +51,9 @@ var/list/moved_atoms = list() //Everything not a turf that gets moved in the shuttle var/list/areas_to_move = list() //unique assoc list of areas on turfs being moved + var/list/underlying_areas = list() //unique assoc list of areas beneath turfs being moved - . = preflight_check(old_turfs, new_turfs, areas_to_move, rotation) + . = preflight_check(old_turfs, new_turfs, areas_to_move, underlying_areas, rotation) if(.) remove_ripples() return @@ -81,11 +82,11 @@ // Moving to the new location will trample the ripples there at the exact // same time any mobs there are trampled, to avoid any discrepancy where // the ripples go away before it is safe. - takeoff(old_turfs, new_turfs, moved_atoms, rotation, movement_direction, old_dock, underlying_old_area) + takeoff(old_turfs, new_turfs, moved_atoms, rotation, movement_direction, old_dock, fallback_area) CHECK_TICK - cleanup_runway(new_dock, old_turfs, new_turfs, areas_to_move, moved_atoms, rotation, movement_direction, underlying_old_area) + cleanup_runway(new_dock, old_turfs, new_turfs, areas_to_move, underlying_areas, moved_atoms, rotation, movement_direction, fallback_area) CHECK_TICK @@ -103,7 +104,7 @@ remove_ripples() return DOCKING_SUCCESS -/obj/docking_port/mobile/proc/preflight_check(list/old_turfs, list/new_turfs, list/areas_to_move, rotation) +/obj/docking_port/mobile/proc/preflight_check(list/old_turfs, list/new_turfs, list/areas_to_move, list/underlying_areas, rotation) for(var/i in 1 to length(old_turfs)) CHECK_TICK var/turf/oldT = old_turfs[i] @@ -126,11 +127,14 @@ move_mode = newT.toShuttleMove(oldT, move_mode, src) //turfs if(move_mode & MOVE_AREA) + var/area/underlying_area = underlying_areas_by_turf[oldT] + if(underlying_area) + underlying_areas[underlying_area] = TRUE areas_to_move[old_area] = TRUE old_turfs[oldT] = move_mode -/obj/docking_port/mobile/proc/takeoff(list/old_turfs, list/new_turfs, list/moved_atoms, rotation, movement_direction, old_dock, area/underlying_old_area) +/obj/docking_port/mobile/proc/takeoff(list/old_turfs, list/new_turfs, list/moved_atoms, rotation, movement_direction, old_dock, area/fallback_area) for(var/i in 1 to old_turfs.len) var/turf/oldT = old_turfs[i] var/turf/newT = new_turfs[i] @@ -141,7 +145,7 @@ if(move_mode & MOVE_AREA) var/area/shuttle_area = oldT.loc - shuttle_area.onShuttleMove(oldT, newT, underlying_old_area) //areas + shuttle_area.onShuttleMove(oldT, newT, src, fallback_area) //areas if(move_mode & MOVE_CONTENTS) for(var/k in oldT) @@ -152,8 +156,12 @@ moved_atoms[moving_atom] = oldT -/obj/docking_port/mobile/proc/cleanup_runway(obj/docking_port/stationary/new_dock, list/old_turfs, list/new_turfs, list/areas_to_move, list/moved_atoms, rotation, movement_direction, area/underlying_old_area) - underlying_old_area.afterShuttleMove(0) +/obj/docking_port/mobile/proc/cleanup_runway(obj/docking_port/stationary/new_dock, list/old_turfs, list/new_turfs, list/areas_to_move, list/underlying_areas, list/moved_atoms, rotation, movement_direction, area/fallback_area) + fallback_area.afterShuttleMove(0) + for(var/i in 1 to underlying_areas.len) + CHECK_TICK + var/area/underlying_area = underlying_areas[i] + underlying_area.afterShuttleMove(0) // Parallax handling // This needs to be done before the atom after move @@ -167,24 +175,28 @@ for(var/i in 1 to old_turfs.len) CHECK_TICK - if(!(old_turfs[old_turfs[i]] & MOVE_TURF)) - continue var/turf/old_turf = old_turfs[i] + var/old_move_mode = old_turfs[old_turf] + var/turf/old_ceiling = get_step_multiz(old_turf, UP) + if(old_ceiling) // check if a ceiling was generated previously + // remove old ceiling + if(istype(old_ceiling, /turf/open/floor/engine/hull/ceiling)) + old_ceiling.ScrapeAway() + else + old_ceiling.remove_baseturfs_from_typecache(list(/turf/open/floor/engine/hull/ceiling = TRUE)) + if(!(old_move_mode & MOVE_TURF)) + continue var/turf/new_turf = new_turfs[i] new_turf.afterShuttleMove(old_turf, rotation) //turfs var/turf/new_ceiling = get_step_multiz(new_turf, UP) // check if a ceiling is needed if(new_ceiling) // generate ceiling - if(istype(new_ceiling, /turf/open/openspace)) // why is this needed? because we have 2 different typepaths for openspace - new_ceiling.ChangeTurf(/turf/open/floor/engine/hull/ceiling, list(/turf/open/openspace)) - else if (istype(new_ceiling, /turf/open/space/openspace)) - new_ceiling.ChangeTurf(/turf/open/floor/engine/hull/ceiling, list(/turf/open/space/openspace)) - var/turf/old_ceiling = get_step_multiz(old_turf, UP) - if(old_ceiling && istype(old_ceiling, /turf/open/floor/engine/hull/ceiling)) // check if a ceiling was generated previously - // remove old ceiling - var/turf/open/floor/engine/hull/ceiling/old_shuttle_ceiling = old_ceiling - old_shuttle_ceiling.ChangeTurf(old_shuttle_ceiling.old_turf_type) - + if(!(istype(new_ceiling, /turf/open/floor/engine/hull/ceiling) || new_ceiling.depth_to_find_baseturf(/turf/open/floor/engine/hull/ceiling))) + if(istype(new_ceiling, /turf/open/openspace) || istype(new_ceiling, /turf/open/space/openspace)) + new_ceiling.place_on_top(/turf/open/floor/engine/hull/ceiling) + else + new_ceiling.stack_ontop_of_baseturf(/turf/open/openspace, /turf/open/floor/engine/hull/ceiling) + new_ceiling.stack_ontop_of_baseturf(/turf/open/space/openspace, /turf/open/floor/engine/hull/ceiling) for(var/i in 1 to moved_atoms.len) CHECK_TICK var/atom/movable/moved_object = moved_atoms[i] @@ -195,13 +207,18 @@ // lateShuttleMove (There had better be a really good reason for additional stages beyond this) - underlying_old_area.lateShuttleMove() + fallback_area.lateShuttleMove() for(var/i in 1 to areas_to_move.len) CHECK_TICK var/area/internal_area = areas_to_move[i] internal_area.lateShuttleMove() + for(var/i in 1 to underlying_areas.len) + CHECK_TICK + var/area/underlying_area = underlying_areas[i] + underlying_area.lateShuttleMove() + for(var/i in 1 to old_turfs.len) CHECK_TICK if(!(old_turfs[old_turfs[i]] & MOVE_CONTENTS | MOVE_TURF)) diff --git a/code/modules/shuttle/mobile_port/shuttle_move_callbacks.dm b/code/modules/shuttle/mobile_port/shuttle_move_callbacks.dm index 4dc5e8142b0..07d9cf65669 100644 --- a/code/modules/shuttle/mobile_port/shuttle_move_callbacks.dm +++ b/code/modules/shuttle/mobile_port/shuttle_move_callbacks.dm @@ -89,6 +89,11 @@ All ShuttleMove procs go here ///////////////////////////////////////////////////////////////////////////////////// +// Return the move_move (based on the old), without any side effects. +// This is for checking what would be moved if src is on a shuttle being moved. +/atom/movable/proc/hypotheticalShuttleMove(rotation, move_mode, obj/docking_port/mobile/moving_dock) + return move_mode + // Called on every atom in shuttle turf contents before anything has been moved // returns the new move_mode (based on the old) // WARNING: Do not leave turf contents in beforeShuttleMove or dock() will runtime @@ -141,16 +146,19 @@ All ShuttleMove procs go here return MOVE_AREA // Called on areas to move their turf between areas -/area/proc/onShuttleMove(turf/oldT, turf/newT, area/underlying_old_area) +/area/proc/onShuttleMove(turf/oldT, turf/newT, obj/docking_port/mobile/shuttle, area/fallback_area) if(newT == oldT) // In case of in place shuttle rotation shenanigans. return TRUE - oldT.change_area(src, underlying_old_area) + var/area/underlying_area = shuttle.underlying_areas_by_turf[oldT] + oldT.change_area(src, underlying_area || fallback_area) + shuttle.underlying_areas_by_turf -= oldT //The old turf has now been given back to the area that turf originaly belonged to var/area/old_dest_area = newT.loc parallax_movedir = old_dest_area.parallax_movedir newT.change_area(old_dest_area, src) + shuttle.underlying_areas_by_turf[newT] = old_dest_area return TRUE // Called on areas after everything has been moved @@ -218,6 +226,11 @@ All ShuttleMove procs go here break +/obj/machinery/camera/hypotheticalShuttleMove(rotation, move_mode, obj/docking_port/mobile/moving_dock) + . = ..() + if(. & MOVE_AREA) + . |= MOVE_CONTENTS + /obj/machinery/camera/beforeShuttleMove(turf/newT, rotation, move_mode, obj/docking_port/mobile/moving_dock) . = ..() if(. & MOVE_AREA) @@ -323,11 +336,21 @@ All ShuttleMove procs go here /************************************Structure move procs************************************/ +/obj/structure/grille/hypotheticalShuttleMove(rotation, move_mode, obj/docking_port/mobile/moving_dock) + . = ..() + if(. & MOVE_AREA) + . |= MOVE_CONTENTS + /obj/structure/grille/beforeShuttleMove(turf/newT, rotation, move_mode, obj/docking_port/mobile/moving_dock) . = ..() if(. & MOVE_AREA) . |= MOVE_CONTENTS +/obj/structure/lattice/hypotheticalShuttleMove(rotation, move_mode, obj/docking_port/mobile/moving_dock) + . = ..() + if(. & MOVE_AREA) + . |= MOVE_CONTENTS + /obj/structure/lattice/beforeShuttleMove(turf/newT, rotation, move_mode, obj/docking_port/mobile/moving_dock) . = ..() if(. & MOVE_AREA) @@ -342,6 +365,11 @@ All ShuttleMove procs go here Connect_cable(TRUE) propagate_if_no_network() +/obj/machinery/power/shuttle_engine/hypotheticalShuttleMove(move_mode) + . = ..() + if(. & MOVE_AREA) + . |= MOVE_CONTENTS + /obj/machinery/power/shuttle_engine/beforeShuttleMove(turf/newT, rotation, move_mode, obj/docking_port/mobile/moving_dock) . = ..() if(. & MOVE_AREA) @@ -365,6 +393,11 @@ All ShuttleMove procs go here /************************************Misc move procs************************************/ +/obj/docking_port/mobile/hypotheticalShuttleMove(rotation, move_mode, obj/docking_port/mobile/moving_dock) + . = ..() + if(moving_dock == src) + . |= MOVE_CONTENTS + /obj/docking_port/mobile/beforeShuttleMove(turf/newT, rotation, move_mode, obj/docking_port/mobile/moving_dock) . = ..() if(moving_dock == src) diff --git a/code/modules/shuttle/mobile_port/variants/assault_pod.dm b/code/modules/shuttle/mobile_port/variants/assault_pod.dm index 75baee5c392..aa1b7ea5259 100644 --- a/code/modules/shuttle/mobile_port/variants/assault_pod.dm +++ b/code/modules/shuttle/mobile_port/variants/assault_pod.dm @@ -7,7 +7,7 @@ return ..() -/obj/docking_port/mobile/assault_pod/initiate_docking(obj/docking_port/stationary/S1) +/obj/docking_port/mobile/assault_pod/initiate_docking(obj/docking_port/stationary/S1, force=FALSE) . = ..() if(!istype(S1, /obj/docking_port/stationary/transit)) playsound(get_turf(src.loc), 'sound/effects/explosion/explosion1.ogg',50,TRUE) diff --git a/code/modules/shuttle/mobile_port/variants/custom/blueprints.dm b/code/modules/shuttle/mobile_port/variants/custom/blueprints.dm new file mode 100644 index 00000000000..5185d268b8e --- /dev/null +++ b/code/modules/shuttle/mobile_port/variants/custom/blueprints.dm @@ -0,0 +1,1051 @@ +GLOBAL_LIST_INIT(shuttle_construction_area_whitelist, list(/area/space, /area/lavaland, /area/icemoon, /area/station/asteroid)) + +/obj/effect/client_image_holder/shuttle_construction_visualization + image_icon = 'icons/effects/alphacolors.dmi' + image_state = "transparent" + image_layer = ABOVE_NORMAL_TURF_LAYER + image_plane = ABOVE_GAME_PLANE + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + persist_without_seers = TRUE + +/datum/proximity_monitor/advanced/shuttle_construction_visualizer + edge_is_a_field = TRUE + var/mob/user + var/list/image_holders = list() + var/obj/item/shuttle_blueprints/parent + +/datum/proximity_monitor/advanced/shuttle_construction_visualizer/New(atom/_host, range, _ignore_if_not_on_turf) + . = ..() + parent = _host + +/datum/proximity_monitor/advanced/shuttle_construction_visualizer/Destroy() + . = ..() + parent = null + QDEL_LIST_ASSOC_VAL(image_holders) + +/datum/proximity_monitor/advanced/shuttle_construction_visualizer/proc/set_user(mob/new_user) + if(user) + for(var/turf in image_holders) + var/obj/effect/client_image_holder/holder = image_holders[turf] + holder.remove_seer(user) + UnregisterSignal(user, list(COMSIG_MOB_LOGIN, COMSIG_MOB_LOGOUT)) + var/client/client = user.client + if(client) + UnregisterSignal(client, list(COMSIG_VIEW_SET, COMSIG_CLIENT_SET_EYE)) + if(new_user) + user = new_user + for(var/turf in image_holders) + var/obj/effect/client_image_holder/holder = image_holders[turf] + holder.add_seer(user) + RegisterSignal(user, COMSIG_MOB_LOGIN, PROC_REF(on_user_login)) + RegisterSignal(user, COMSIG_MOB_LOGOUT, PROC_REF(unregister_client)) + var/client/client = user.client + if(client) + register_client(client) + else + unregister_client() + +/datum/proximity_monitor/advanced/shuttle_construction_visualizer/proc/register_client(client/client) + var/atom/eye = client.eye + if(eye) + set_host(client.eye) + var/list/view_size = getviewsize(client.view) + set_range(CEILING(max(view_size[1], view_size[2])/2, 1)+1) + RegisterSignal(client, COMSIG_VIEW_SET, PROC_REF(on_view_set)) + RegisterSignal(client, COMSIG_CLIENT_SET_EYE, PROC_REF(on_set_eye)) + +/datum/proximity_monitor/advanced/shuttle_construction_visualizer/proc/unregister_client() + SIGNAL_HANDLER + set_host(parent) + set_range(0) + +/datum/proximity_monitor/advanced/shuttle_construction_visualizer/proc/on_user_login(mob/source) + SIGNAL_HANDLER + register_client(source.client) + +/datum/proximity_monitor/advanced/shuttle_construction_visualizer/proc/on_view_set(datum/source, new_view) + SIGNAL_HANDLER + var/list/view_size = getviewsize(new_view) + set_range(CEILING(max(view_size[1], view_size[2])/2, 1)+1) + +/datum/proximity_monitor/advanced/shuttle_construction_visualizer/proc/on_set_eye(datum/source, atom/old_eye, atom/new_eye) + SIGNAL_HANDLER + set_host(new_eye) + +/datum/proximity_monitor/advanced/shuttle_construction_visualizer/setup_field_turf(turf/target) + var/obj/effect/client_image_holder/shuttle_construction_visualization/holder = new(target, list()) + image_holders[target] = holder + evaluate_turf_overlay(holder, target) + RegisterSignals(target, list( + COMSIG_TURF_CHANGE, + COMSIG_TURF_AREA_CHANGED, + SIGNAL_ADDTRAIT(TRAIT_SHUTTLE_CONSTRUCTION_TURF), + SIGNAL_REMOVETRAIT(TRAIT_SHUTTLE_CONSTRUCTION_TURF) + ), PROC_REF(on_turf_updated)) + if(user) + holder.add_seer(user) + +/datum/proximity_monitor/advanced/shuttle_construction_visualizer/cleanup_field_turf(turf/target) + qdel(image_holders[target]) + image_holders -= target + UnregisterSignal(target, list( + COMSIG_TURF_CHANGE, + COMSIG_TURF_AREA_CHANGED, + SIGNAL_ADDTRAIT(TRAIT_SHUTTLE_CONSTRUCTION_TURF), + SIGNAL_REMOVETRAIT(TRAIT_SHUTTLE_CONSTRUCTION_TURF) + )) + +/datum/proximity_monitor/advanced/shuttle_construction_visualizer/proc/on_turf_updated(turf/source) + SIGNAL_HANDLER + evaluate_turf_overlay(image_holders[source], source) + +/datum/proximity_monitor/advanced/shuttle_construction_visualizer/proc/evaluate_turf_overlay(obj/effect/client_image_holder/holder, turf/target) + var/area/turf_area = target.loc + if(HAS_TRAIT(target, TRAIT_SHUTTLE_CONSTRUCTION_TURF)) + if(is_type_in_list(turf_area, GLOB.shuttle_construction_area_whitelist) || GLOB.custom_areas[turf_area]) + holder.image_state = "green" + else + holder.image_state = "red" + else + if(GLOB.custom_areas[turf_area] && HAS_TRAIT(turf_area, TRAIT_HAS_SHUTTLE_CONSTRUCTION_TURF)) + holder.image_state = "red" + else + holder.image_state = "transparent" + holder.regenerate_image() + +/obj/item/shuttle_blueprints + name = "shuttle blueprints" + desc = "A blank sheet of synthetic engineering-grade paper." + icon = 'icons/obj/scrolls.dmi' + icon_state = "shuttle_blueprints0" + base_icon_state = "shuttle_blueprints" + inhand_icon_state = "blueprints" + attack_verb_continuous = list("attacks", "baps", "hits") + attack_verb_simple = list("attack", "bap", "hit") + interaction_flags_atom = parent_type::interaction_flags_atom | INTERACT_ATOM_ALLOW_USER_LOCATION | INTERACT_ATOM_IGNORE_MOBILITY + + var/base_desc = "A blank sheet of synthetic engineering-grade paper." + var/linked_desc = "A sheet of synthetic engineering-grade paper with shuttle schematics printed on it." + + //A weakref to the mobile docking port of the shuttle these blueprints are linked to, if any. + var/datum/weakref/shuttle_ref + + //Whether the holder can visualize shuttle frames (and any turfs preventing them from becoming complete shuttles) + var/visualize_frame_turfs = FALSE + + var/offset_x = 0 + var/offset_y = 0 + + var/datum/proximity_monitor/advanced/shuttle_construction_visualizer/prox_monitor + +/obj/item/shuttle_blueprints/Initialize(mapload) + . = ..() + prox_monitor = new(src, 0, FALSE) + update_appearance() + +/obj/item/shuttle_blueprints/equipped(mob/user, slot, initial) + . = ..() + var/static/list/connections = list(COMSIG_ITEM_PRE_ATTACK = PROC_REF(christen_check)) + if(slot == ITEM_SLOT_HANDS) + AddComponent(/datum/component/connect_inventory, user, connections, allowed_slots = ITEM_SLOT_HANDS) + +/obj/item/shuttle_blueprints/dropped(mob/user, silent) + . = ..() + stop_visualizing(user) + qdel(GetComponent(/datum/component/connect_inventory)) + +/obj/item/shuttle_blueprints/proc/christen_check(obj/item/reagent_containers/cup/glass/bottle/source, atom/attacked, mob/living/user) + SIGNAL_HANDLER + var/obj/docking_port/mobile/custom/shuttle = shuttle_ref?.resolve() + if(!istype(shuttle)) + return + if(!iswallturf(attacked)) + return + if(!istype(source)) + return + if(SSshuttle.get_containing_shuttle(attacked) != shuttle) + return + if(HAS_TRAIT_FROM(user, TRAIT_ATTEMPTING_CHRISTENING, REF(shuttle))) + return + . = COMPONENT_CANCEL_ATTACK_CHAIN + if(shuttle.master_blueprint?.resolve() != src) + to_chat(user, span_warning("Only the master blueprint for \the [shuttle] grants you the right to rechristen [shuttle.p_them()]!")) + return + var/turf/user_turf = get_turf(user) + if(user_turf && isshuttleturf(user_turf)) + to_chat(user, span_warning("You can't rechristen \the [shuttle] from inside of [shuttle.p_them()]!")) + return + if(!source.isGlass) + to_chat(user, span_warning("You can't break [source] against [attacked]!")) + return + if(source.reagents.total_volume < CHEMICAL_QUANTISATION_LEVEL) + to_chat(user, span_warning("You should put some christening fluid in [source]!")) + return + INVOKE_ASYNC(src, PROC_REF(christen), user, shuttle, attacked, user.active_hand_index) + +/obj/item/shuttle_blueprints/proc/christen(mob/living/user, obj/docking_port/mobile/custom/shuttle, atom/attacked, hand) + var/trait_source = REF(shuttle) + ADD_TRAIT(user, TRAIT_ATTEMPTING_CHRISTENING, trait_source) + var/new_name = reject_bad_name(tgui_input_text(user, "What would you like to rechristen \the [shuttle] as?", "Shuttle Rechristening", max_length = 128), allow_numbers = TRUE, strict = TRUE, cap_after_symbols = FALSE) + if(QDELETED(user)) + return + REMOVE_TRAIT(user, TRAIT_ATTEMPTING_CHRISTENING, trait_source) + if(!new_name) + user.balloon_alert(user, "cancelled") + return + new_name = apply_text_macros(new_name) + var/obj/item/hitting_implement = (locate(/obj/item/reagent_containers/cup/glass/bottle) in user.held_items) || user.get_item_for_held_index(hand) + if(!user.CanReach(attacked, hitting_implement)) + user.balloon_alert(user, "out of range!") + return + var/obj/item/reagent_containers/cup/glass/bottle/bottle = hitting_implement + bottle = istype(bottle) && bottle.isGlass && bottle + var/shuttle_exists = !QDELETED(shuttle) + if(!shuttle_exists) + to_chat(user, span_warning("Wasn't there supposed to be a shuttle here?")) + var/has_blueprints = !QDELETED(src) && (src in user.gather_belongings()) + var/turf/attacked_turf = attacked + var/is_closed_turf = isclosedturf(attacked_turf) + var/is_shuttle_turf = isshuttleturf(attacked_turf) + var/off_shuttle = SSshuttle.get_containing_shuttle(get_turf(user)) != shuttle + var/has_bottle = istype(bottle) + var/bottle_empty = bottle && bottle.reagents.total_volume < CHEMICAL_QUANTISATION_LEVEL + var/is_clumsy = HAS_TRAIT(user, TRAIT_CLUMSY) + var/clumsy_check = is_clumsy && prob(25) + if(!(has_blueprints && is_closed_turf && is_shuttle_turf && off_shuttle && has_bottle && !clumsy_check && !bottle_empty)) + var/msg = "[user] tries to christen the shuttle with [hitting_implement ? "[hitting_implement.get_examine_name()]" : "[user.p_their()] empty fist"]" + var/self_msg = "You try to christen the shuttle with [hitting_implement ? "[hitting_implement.get_examine_name()]" : "your empty fist"]" + if(!is_closed_turf || clumsy_check) + msg += ", but miss[user.p_es()], hitting [user.p_themselves()] instead" + self_msg += ", but miss, hitting yourself instead" + msg += "!" + self_msg += "!" + if(is_clumsy) + msg += " What a klutz!" + var/old_combat_mode = user.combat_mode + user.set_combat_mode(TRUE) + // Stupid code to bypass pacifism checks, since unintentional hits like this one shouldn't be blocked by pacifism + var/list/pacifism_sources = GET_TRAIT_SOURCES(user, TRAIT_PACIFISM) + REMOVE_TRAIT(user, TRAIT_PACIFISM, pacifism_sources) + if(hitting_implement) + hitting_implement.attack(user, user) + bottle?.smash(user, user) // Because smashing a bottle isn't part of its main melee attack chain + else + user.resolve_unarmed_attack(user) + if(length(pacifism_sources)) + ADD_TRAIT(user, TRAIT_PACIFISM, pacifism_sources) + user.set_combat_mode(old_combat_mode) + else + if(!shuttle_exists) + msg += ", but there was no shuttle to christen!" + self_msg += ", but there was no shuttle to christen!" + else if(!off_shuttle) + msg += ", but you're suppossed to do that outside of [shuttle.p_them()]!" + self_msg += ", but you're suppossed to do that outside of [shuttle.p_them()]!" + else if(!is_shuttle_turf) + msg += ", but miss[user.p_es()], hitting [attacked] instead!" + self_msg += ", but miss, hitting [attacked] instead!" + else if(!has_bottle) + if(hitting_implement) + msg += " - which isn't a glass bottle!" + self_msg += " - which isn't a glass bottle!" + else + msg += "! That's not how you're supposed to do it!" + self_msg += "! Aren't you supposed to use a glass bottle?" + else if(bottle_empty) + msg += ", but there's no christening fluid in [bottle]!" + self_msg += ", but there's no christening fluid in [bottle]!" + else if(!has_blueprints) + msg += ", but [user.p_have()] no right to do so!" + msg += ", but have no right to do so!" + user.do_attack_animation(attacked, used_item = hitting_implement) + if(hitting_implement && !bottle) + if(hitting_implement.force) + playsound(hitting_implement, 'sound/items/weapons/smash.ogg', hitting_implement.get_clamped_volume(), TRUE, hitting_implement.stealthy_audio ? SILENCED_SOUND_EXTRARANGE : -1, falloff_distance = 0) + else + playsound(hitting_implement, 'sound/items/weapons/tap.ogg', hitting_implement.get_clamped_volume(), TRUE, -1) + else if(bottle) + bottle.smash(attacked, user) + else + playsound(user, 'sound/effects/bang.ogg', 50, TRUE) + user.visible_message(span_warning(msg), span_warning(self_msg)) + else + user.visible_message( + span_notice("[user] christens the shuttle as \the [new_name] with [hitting_implement.get_examine_name()]\ + [(bottle.reagents.total_volume < 30) ? "" : ", though the dearth of christening fluid makes for an unimpressive display"]."), + span_notice("You christen the shuttle as \the [new_name] with [hitting_implement.get_examine_name()].") + ) + user.do_attack_animation(attacked, used_item = bottle) + bottle.smash(attacked, user) + shuttle.name = new_name + rename_area(shuttle.default_area, new_name) + +/obj/item/shuttle_blueprints/proc/start_visualizing(mob/user) + visualize_frame_turfs = TRUE + RegisterSignal(user, SIGNAL_ADDTRAIT(TRAIT_USER_SCOPED), PROC_REF(stop_visualizing)) + prox_monitor.set_user(user) + prox_monitor.recalculate_field() + +/obj/item/shuttle_blueprints/proc/stop_visualizing(mob/user) + SIGNAL_HANDLER + visualize_frame_turfs = FALSE + UnregisterSignal(user, SIGNAL_ADDTRAIT(TRAIT_USER_SCOPED)) + prox_monitor.set_user(null) + +/obj/item/shuttle_blueprints/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "ShuttleBlueprints", name) + ui.open() + RegisterSignal(user, COMSIG_ENTER_AREA, PROC_REF(on_user_enter_area)) + +/obj/item/shuttle_blueprints/ui_close(mob/user) + . = ..() + UnregisterSignal(user, COMSIG_ENTER_AREA) + +/obj/item/shuttle_blueprints/proc/on_user_enter_area(mob/source, area/new_area) + var/obj/docking_port/mobile/custom/shuttle = shuttle_ref?.resolve() + if(shuttle && shuttle.default_area == new_area) + update_static_data(source) + +/obj/item/shuttle_blueprints/ui_state(mob/user) + return GLOB.hands_state + +/obj/item/shuttle_blueprints/ui_static_data(mob/user) + var/list/data = list() + var/obj/docking_port/mobile/custom/shuttle = shuttle_ref?.resolve() + if(get_area(user) == shuttle?.default_area && length(shuttle?.shuttle_areas) > 1) + var/list/neighboring_areas = list() + var/room_turfs = detect_room(get_turf(user), max_size = CONFIG_GET(number/max_shuttle_size), extra_check = CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(custom_shuttle_room_check), shuttle, neighboring_areas)) + if(room_turfs) + data["apcInMergeRegion"] = shuttle.default_area.apc && room_turfs[get_turf(shuttle.default_area.apc)] + var/list/area_refs = list() + for(var/area/neighboring_area in neighboring_areas) + area_refs[REF(neighboring_area)] = neighboring_area.name + data["neighboringAreas"] = area_refs + return data + +/obj/item/shuttle_blueprints/ui_data(mob/user) + var/data = list() + var/turf/current_turf = get_turf(user) + var/obj/docking_port/mobile/custom/linked_shuttle = shuttle_ref?.resolve() + var/linked_to_shuttle = istype(linked_shuttle) + data["linkedShuttle"] = linked_to_shuttle && shuttle_ref.reference + data["visualizing"] = visualize_frame_turfs + data["onShuttleFrame"] = HAS_TRAIT(current_turf, TRAIT_SHUTTLE_CONSTRUCTION_TURF) + if(!linked_to_shuttle) + var/obj/docking_port/mobile/custom/loc_shuttle = SSshuttle.get_containing_shuttle(current_turf) + data["tooManyShuttles"] = length(SSshuttle.custom_shuttles) >= CONFIG_GET(number/max_shuttle_count) + data["onCustomShuttle"] = istype(loc_shuttle) + data["masterExists"] = loc_shuttle?.master_blueprint?.resolve() + else + var/obj/item/shuttle_blueprints/master = linked_shuttle.master_blueprint?.resolve() + data["masterExists"] = master + data["isMaster"] = master == src + var/area/current_area = get_area(current_turf) + var/area/default_area = linked_shuttle.default_area + data["onShuttle"] = linked_shuttle.shuttle_areas[current_area] + data["inDefaultArea"] = default_area == current_area + data["currentArea"] = list(name = current_area.name, ref = REF(current_area)) + data["defaultApc"] = !!default_area.apc + var/list/apcs = list() + for(var/area/area as anything in linked_shuttle.shuttle_areas - default_area) + apcs[REF(area)] = !!area.apc + data["apcs"] = apcs + data["idle"] = linked_shuttle.mode == SHUTTLE_IDLE + return data + +/obj/item/shuttle_blueprints/proc/link_to_shuttle(obj/docking_port/mobile/custom/shuttle, is_master = FALSE) + shuttle_ref = WEAKREF(shuttle) + if(is_master) + shuttle.master_blueprint = WEAKREF(src) + RegisterSignal(shuttle, COMSIG_QDELETING, PROC_REF(on_shuttle_deleted)) + update_appearance() + +/obj/item/shuttle_blueprints/proc/on_shuttle_deleted() + SIGNAL_HANDLER + unlink(removing = TRUE) + +/obj/item/shuttle_blueprints/proc/unlink(removing = FALSE) + var/obj/docking_port/mobile/custom/shuttle = shuttle_ref.resolve() + if(!QDELETED(shuttle)) + UnregisterSignal(shuttle, COMSIG_QDELETING) + shuttle_ref = null + update_appearance() + +/obj/item/shuttle_blueprints/update_name(updates) + . = ..() + var/obj/docking_port/mobile/shuttle = shuttle_ref?.resolve() + if(shuttle) + name = get_linked_name(shuttle) + else + name = initial(name) + +/obj/item/shuttle_blueprints/proc/get_linked_name(obj/docking_port/mobile/shuttle) + return "\improper [shuttle.name] blueprints" + +/obj/item/shuttle_blueprints/update_desc(updates) + . = ..() + desc = shuttle_ref?.resolve() ? linked_desc : base_desc + +/obj/item/shuttle_blueprints/update_icon_state() + . = ..() + icon_state = "[base_icon_state][!!shuttle_ref]" + +/obj/item/shuttle_blueprints/vv_edit_var(vname, vval) + . = ..() + if(!.) + return + if(vname == NAMEOF(src, desc)) + if(shuttle_ref?.resolve()) + linked_desc = vval + else + base_desc = vval + update_desc() + +/obj/item/shuttle_blueprints/examine(mob/user) + . = ..() + . += get_shuttle_tip() + +/obj/item/shuttle_blueprints/proc/get_shuttle_tip() + . = list() + if(!shuttle_ref) + . += span_notice("It can be used to construct a custom shuttle.") + return + var/obj/docking_port/mobile/custom/shuttle = shuttle_ref.resolve() + if(!shuttle) + . += span_notice("It has the plans for a shuttle that no longer exists. It can be reused to construct a new shuttle.") + else + . += span_notice("It has the plans for \the [shuttle] on it, and can be used to expand [shuttle.p_them()] or modify [shuttle.p_their()] areas.") + if(shuttle.master_blueprint.resolve() == src) + . += span_notice("This is the master blueprint for \the [shuttle]. You can copy it to a blank set of blueprints, or to an engineering cyborg with a shuttle database module installed.") + +/obj/item/shuttle_blueprints/interact_with_atom(atom/interacting_with, mob/living/user, list/modifiers) + . = ..() + var/obj/docking_port/mobile/custom/shuttle = shuttle_ref?.resolve() + if(!istype(shuttle)) + return + if(shuttle.master_blueprint?.resolve() != src) + return + if(istype(interacting_with, /obj/item/shuttle_blueprints)) + var/obj/item/shuttle_blueprints/other_blueprints = interacting_with + var/obj/docking_port/mobile/other_shuttle = other_blueprints.shuttle_ref?.resolve() + if(istype(other_shuttle)) + return + balloon_alert(user, "copying blueprints...") + if(!do_after(user, 5 SECONDS, other_blueprints)) + balloon_alert(user, "interrupted!") + return ITEM_INTERACT_FAILURE + other_blueprints.link_to_shuttle(shuttle) + balloon_alert(user, "copied") + return ITEM_INTERACT_SUCCESS + if(istype(interacting_with, /mob/living/silicon/robot)) + var/mob/living/silicon/robot/borg = interacting_with + var/obj/item/shuttle_blueprints/borg/other_blueprints = (locate() in borg.model.modules) || (locate() in borg.held_items) + if(!other_blueprints) + return + if(other_blueprints.shuttles.Find(shuttle_ref)) + balloon_alert(user, "already has these blueprints!") + balloon_alert(user, "copying blueprints...") + if(!do_after(user, 5 SECONDS, borg)) + balloon_alert(user, "interrupted") + return ITEM_INTERACT_FAILURE + if(QDELETED(other_blueprints)) + return ITEM_INTERACT_FAILURE + other_blueprints.shuttles |= shuttle_ref + balloon_alert(user, "copied") + return ITEM_INTERACT_SUCCESS + +/obj/item/shuttle_blueprints/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(.) + return + switch(action) + if("toggleVisualization") + if(visualize_frame_turfs) + stop_visualizing(usr) + else + start_visualizing(usr) + return TRUE + if("tryBuildShuttle") + var/shuttle_dir = params["dir"] + if(!(shuttle_dir in GLOB.cardinals)) + return TRUE + var/list/shuttle_turfs = list() + var/list/shuttle_areas = list() + var/turf/shuttle_origin = get_turf(usr) + var/check_status = shuttle_build_check(shuttle_origin, shuttle_turfs, shuttle_areas) + if(check_status & ORIGIN_NOT_ON_SHUTTLE) + balloon_alert(usr, "not on shuttle frame!") + return TRUE + if(check_status & TOO_MANY_SHUTTLES) + balloon_alert(usr, "too many shuttles exist!") + return TRUE + if(check_status & ABOVE_MAX_SHUTTLE_SIZE) + balloon_alert(usr, "frame too big!") + return TRUE + if(check_status & CUSTOM_AREA_NOT_COMPLETELY_CONTAINED) + balloon_alert(usr, "frame must completely enclose custom areas!") + return TRUE + if(check_status & INTERSECTS_NON_WHITELISTED_AREA) + balloon_alert(usr, "frame overlaps disallowed areas!") + return TRUE + var/obj/docking_port/mobile/custom/shuttle = create_shuttle( + usr, + shuttle_origin, + shuttle_turfs, + shuttle_areas, + shuttle_dir, + name = "\improper Unnamed Shuttle", + id = "custom_[length(SSshuttle.custom_shuttles)+1]" + ) + link_to_shuttle(shuttle, TRUE) + return TRUE + if("tryLinkShuttle") + if(shuttle_ref?.resolve()) + balloon_alert(usr, "already linked!") + return TRUE + var/obj/docking_port/mobile/custom/shuttle = SSshuttle.get_containing_shuttle(usr) + if(!shuttle) + balloon_alert(usr, "not on shuttle!") + return TRUE + if(!istype(shuttle)) + balloon_alert(usr, "incompatible shuttle type!") + return TRUE + var/obj/item/shuttle_blueprints/master = shuttle.master_blueprint?.resolve() + if(master && (master != src)) + balloon_alert(usr, "master blueprint already exists!") + return TRUE + link_to_shuttle(shuttle, TRUE) + return TRUE + if("promoteToMaster") + var/obj/docking_port/mobile/custom/shuttle = shuttle_ref?.resolve() + if(!shuttle) + balloon_alert(usr, "not linked!") + return TRUE + var/obj/item/shuttle_blueprints/master = shuttle.master_blueprint?.resolve() + if(master) + balloon_alert(usr, "master blueprint already exists!") + return TRUE + shuttle.master_blueprint = WEAKREF(src) + return TRUE + if("createNewArea") + var/obj/docking_port/mobile/custom/shuttle = shuttle_ref?.resolve() + if(!shuttle) + balloon_alert(usr, "not linked!") + return TRUE + var/area_name = params["name"] + if(!area_name) + balloon_alert(usr, "no name given!") + return TRUE + var/area/current_area = get_area(usr) + var/area/default_area = shuttle.default_area + if(current_area != default_area) + balloon_alert(usr, "must be in default area!") + return TRUE + var/list/turfs = detect_room(get_turf(usr), max_size = CONFIG_GET(number/max_shuttle_size), extra_check = CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(custom_shuttle_room_check), shuttle, null)) + if(!length(turfs)) + balloon_alert(usr, "invalid room!") + return TRUE + var/area/shuttle/custom/new_area = new() + new_area.name = area_name + shuttle.shuttle_areas[new_area] = TRUE + set_turfs_to_area(turfs, new_area) + new_area.reg_in_areas_in_z() + new_area.create_area_lighting_objects() + for(var/obj/machinery/door/firedoor/firelock as anything in default_area.firedoors) + firelock.CalculateAffectingAreas() + new_area.power_change() + return TRUE + if("releaseArea") + var/obj/docking_port/mobile/custom/shuttle = shuttle_ref?.resolve() + if(!shuttle) + balloon_alert(usr, "not linked!") + return TRUE + var/area/current_area = get_area(usr) + if(!shuttle.shuttle_areas[current_area]) + balloon_alert(usr, "not on shuttle!") + return TRUE + var/area/default_area = shuttle.default_area + if(current_area == default_area) + balloon_alert(usr, "can't release default area!") + return TRUE + var/obj/machinery/power/apc/current_area_apc = current_area.apc + var/obj/machinery/power/apc/default_area_apc = default_area.apc + if(current_area_apc && default_area_apc) + balloon_alert(usr, "remove the apc first!") + return TRUE + var/list/turfs = current_area.get_turfs_by_zlevel(shuttle.z) + set_turfs_to_area(turfs, default_area) + for(var/obj/machinery/door/firedoor/firelock as anything in default_area.firedoors) + firelock.CalculateAffectingAreas() + shuttle.shuttle_areas -= current_area + qdel(current_area) + return TRUE + if("mergeIntoArea") + var/obj/docking_port/mobile/custom/shuttle = shuttle_ref?.resolve() + if(!shuttle) + balloon_alert(usr, "not linked!") + return TRUE + var/area/current_area = get_area(usr) + var/area/default_area = shuttle.default_area + if(current_area != default_area) + balloon_alert(usr, "must be in default area!") + return TRUE + var/area/merge_area = locate(params["area"]) + if(!istype(merge_area)) + return TRUE + if(!shuttle.shuttle_areas[merge_area]) + return TRUE + if(merge_area == default_area) + return TRUE + var/list/actual_adjacent_areas = list() + var/list/turfs = detect_room(get_turf(usr), max_size = CONFIG_GET(number/max_shuttle_size), extra_check = CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(custom_shuttle_room_check), shuttle, actual_adjacent_areas)) + if(!length(turfs)) + balloon_alert(usr, "invalid room!") + return TRUE + if(!actual_adjacent_areas[merge_area]) + balloon_alert(usr, "selected area not connected to room!") + return TRUE + if(merge_area.apc && default_area.apc && turfs[get_turf(default_area.apc)]) + balloon_alert(usr, "remove the apc first!") + return TRUE + set_turfs_to_area(turfs, merge_area) + for(var/obj/machinery/door/firedoor/firelock as anything in merge_area.firedoors + default_area.firedoors) + firelock.CalculateAffectingAreas() + return TRUE + if("renameArea") + var/obj/docking_port/mobile/custom/shuttle = shuttle_ref?.resolve() + if(!shuttle) + balloon_alert(usr, "not linked!") + return TRUE + var/area/current_area = get_area(usr) + if(!shuttle.shuttle_areas[current_area]) + balloon_alert(usr, "not on shuttle!") + return TRUE + var/area/default_area = shuttle.default_area + if(current_area == default_area) + balloon_alert(usr, "can't rename default area!") + return TRUE + var/new_name = params["name"] + if(!new_name) + balloon_alert(usr, "no name given!") + return TRUE + rename_area(current_area, new_name) + return TRUE + if("expandWithFrame") + var/obj/docking_port/mobile/custom/shuttle = shuttle_ref?.resolve() + if(!shuttle) + balloon_alert(usr, "not linked!") + return TRUE + var/list/turfs = list() + var/list/areas = list() + var/turf/origin = get_turf(usr) + var/check_status = shuttle_expand_check(origin, shuttle, turfs, areas) + if(check_status & ORIGIN_NOT_ON_SHUTTLE) + balloon_alert(usr, "not on shuttle frame!") + return TRUE + if(check_status & FRAME_NOT_ADJACENT_TO_LINKED_SHUTTLE) + balloon_alert(usr, "not connected to linked shuttle!") + return TRUE + if(check_status & ABOVE_MAX_SHUTTLE_SIZE) + balloon_alert(usr, "frame too big!") + return TRUE + if(check_status & CUSTOM_AREA_NOT_COMPLETELY_CONTAINED) + balloon_alert(usr, "frame must completely enclose custom areas!") + return TRUE + if(check_status & INTERSECTS_NON_WHITELISTED_AREA) + balloon_alert(usr, "frame overlaps disallowed areas!") + return TRUE + expand_shuttle(usr, shuttle, turfs, areas) + return TRUE + if("cleanupEmptyTurfs") + var/obj/docking_port/mobile/custom/shuttle = shuttle_ref?.resolve() + if(!shuttle) + balloon_alert(usr, "not linked!") + return TRUE + var/obj/item/shuttle_blueprints/master = shuttle.master_blueprint?.resolve() + if(master && master != src) + balloon_alert(usr, "not master blueprints!") + var/shuttle_z = shuttle.z + var/bounds_need_recalculation + var/docking_port_needs_relocated + var/list/bounds = shuttle.return_coords() + var/x0 = bounds[1] + var/y0 = bounds[2] + var/x1 = bounds[3] + var/y1 = bounds[4] + for(var/area/area as anything in shuttle.shuttle_areas) + var/list/turfs = area.get_turfs_by_zlevel(shuttle_z) + turfs = turfs.Copy() + for(var/turf/turf as anything in turfs) + var/move_mode = turf.fromShuttleMove(move_mode = MOVE_AREA) + if(move_mode & (MOVE_TURF | MOVE_CONTENTS)) + continue + for(var/atom/movable/movable as anything in turf.contents) + //CHECK_TICK + if(movable.loc != turf) + continue + if(movable == shuttle) + continue + move_mode = movable.hypotheticalShuttleMove(0, move_mode, shuttle) + if(move_mode & (MOVE_TURF | MOVE_CONTENTS)) + continue + if(shuttle.loc == turf) + docking_port_needs_relocated = TRUE + bounds_need_recalculation = TRUE + var/area/new_area = shuttle.underlying_areas_by_turf[turf] + if(!istype(new_area)) + new_area = GLOB.areas_by_type[SHUTTLE_DEFAULT_UNDERLYING_AREA] + if(!istype(new_area)) + new_area = new SHUTTLE_DEFAULT_UNDERLYING_AREA(null) + shuttle.underlying_areas_by_turf -= turf + shuttle.turf_count-- + turfs -= turf + turf.change_area(area, new_area) + if(bounds_need_recalculation) + continue + if(turf.x == x0 || turf.x == x1 || turf.y == y0 || turf.y == y1) + bounds_need_recalculation = TRUE + if((area != shuttle.default_area) && !length(turfs)) + shuttle.shuttle_areas -= area + qdel(area) + if(!shuttle.turf_count) + qdel(shuttle.default_area) + qdel(shuttle) + return + if(docking_port_needs_relocated) + shuttle.forceMove(pick(shuttle.underlying_areas_by_turf)) + if(bounds_need_recalculation) + QDEL_NULL(shuttle.assigned_transit) + shuttle.calculate_docking_port_information() + +/obj/item/shuttle_blueprints/crude + name = "crude shuttle blueprints" + desc = "This is just a sheet of paper thoroughly covered in what could either be crayon or spraypaint." + icon_state = "shuttle_blueprints_crude0" + base_icon_state = "shuttle_blueprints_crude" + base_desc = "This is just a sheet of paper thoroughly covered in what could either be crayon or spraypaint." + linked_desc = "This is just a crude doodle of a shuttle drawn on a background of what could either be crayon or spraypaint." + +/obj/item/shuttle_blueprints/borg + name = "shuttle blueprint database" + desc = "A module designed to store the plans for one or more shuttles." + icon = 'icons/obj/items_cyborg.dmi' + icon_state = "shuttle_database0" + base_icon_state = "shuttle_database" + damtype = BURN // In case fantasy affixes or adminbus end up making this actually capable of hurting someone. + attack_verb_continuous = list("attacks", "scans", "analyzes") + attack_verb_simple = list("attack", "scan", "analyze") + base_desc = "A module designed to store the plans for one or more shuttles." + linked_desc = "A module designed to store the plans for one or more shuttles." + var/list/shuttles = list() + +/obj/item/shuttle_blueprints/borg/get_shuttle_tip() + . = list() + if(!shuttle_ref) + if(!length(shuttles)) + . += span_notice("It does not have any shuttle plans stored.") + else + . += span_notice("It does not currently have a shuttle plan loaded.") + . += span_notice("In this mode, it can be used to construct a custom shuttle.") + return + var/obj/docking_port/mobile/custom/shuttle = shuttle_ref.resolve() + if(!shuttle) + . += span_notice("The currently loaded plans are for a shuttle that no longer exists. It will default to shuttle construction mode.") + else + . += span_notice("It has the plans for \the [shuttle] currently loaded, and can be used to expand [shuttle.p_them()] or modify [shuttle.p_their()] areas.") + if(shuttle.master_blueprint.resolve() == src) + . += span_notice("This is the master blueprint for \the [shuttle]. You can copy it to a blank set of blueprints, or to another engineering cyborg with a shuttle database module installed.") + +/obj/item/shuttle_blueprints/borg/unlink(removing) + if(removing) + shuttles -= shuttle_ref + ..() + +/obj/item/shuttle_blueprints/borg/ui_data(mob/user) + var/list/data = ..() + var/list/shuttle_data = list() + var/list/shuttle_name_count = list() + for(var/datum/weakref/shuttle_weakref as anything in shuttles) + var/obj/docking_port/mobile/shuttle = shuttle_weakref.resolve() + if(!shuttle) + continue + var/shuttle_name = shuttle.name + if(!shuttle_name_count[shuttle_name]) + shuttle_name_count[shuttle_name] = 0 + var/ref = shuttle_weakref.reference + shuttle_data[ref] = shuttle_name + if(shuttle_name_count[shuttle_name]++ > 1) + shuttle_data[ref] += " ([shuttle_name_count[shuttle_name]])" + if(length(shuttle_data)) + data["shuttles"] = shuttle_data + return data + +/obj/item/shuttle_blueprints/borg/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(.) + return + switch(action) + if("switchShuttle") + var/ref = params["ref"] + var/obj/docking_port/mobile/shuttle = locate(ref) + if(!shuttle) + return TRUE + if(shuttles.Find(WEAKREF(shuttle))) + link_to_shuttle(shuttle) + return TRUE + if("unsetShuttle") + unlink() + return TRUE + +/obj/item/shuttle_blueprints/borg/link_to_shuttle(obj/docking_port/mobile/custom/shuttle, is_master) + . = ..() + shuttles |= WEAKREF(shuttle) + +/obj/item/shuttle_blueprints/borg/get_linked_name(obj/docking_port/mobile/shuttle) + name = "shuttle blueprint database ([shuttle.name])" + +/proc/custom_shuttle_room_check(obj/docking_port/mobile/custom/shuttle, list/neighboring_areas = list(), turf/check_turf) + if(SSshuttle.get_containing_shuttle(check_turf) != shuttle) + return EXTRA_ROOM_CHECK_SKIP + var/area/check_area = check_turf.loc + if(check_area != shuttle.default_area) + neighboring_areas[check_area] = TRUE + return EXTRA_ROOM_CHECK_SKIP + var/move_mode = MOVE_AREA + move_mode = check_turf.fromShuttleMove(move_mode = move_mode) + if(move_mode & (MOVE_CONTENTS | MOVE_TURF)) + return + for(var/atom/movable/movable as anything in check_turf.contents) + CHECK_TICK + if(movable.loc != check_turf) + continue + move_mode = movable.hypotheticalShuttleMove(0, move_mode, shuttle) + if(!(move_mode & (MOVE_CONTENTS | MOVE_TURF))) + return EXTRA_ROOM_CHECK_SKIP + +/proc/get_connected_shuttle_frame_turfs(turf/starting_turf, max_size = INFINITY, list/adjacent_shuttles) + . = list() + var/list/check_turfs = list(starting_turf) + while(length(check_turfs)) + var/turf/check_turf = check_turfs[1] + check_turfs.Cut(1,2) + .[check_turf] = TRUE + if(length(.) > max_size) + return + for(var/check_dir in GLOB.cardinals) + var/turf/neighbor = get_step(check_turf, check_dir) + if(neighbor) + if(HAS_TRAIT(neighbor, TRAIT_SHUTTLE_CONSTRUCTION_TURF) && !.[neighbor]) + check_turfs[neighbor] = TRUE + else if(islist(adjacent_shuttles)) + var/obj/docking_port/mobile/custom/shuttle = SSshuttle.get_containing_shuttle(neighbor) + if(istype(shuttle)) + adjacent_shuttles |= shuttle + +/proc/shuttle_build_check(turf/origin, list/turfs, list/areas) + var/z = origin.z + var/skip_flood_fill = !!length(turfs) + if(skip_flood_fill && !(turfs[origin])) + . |= ORIGIN_NOT_ON_SHUTTLE + if(length(SSshuttle.custom_shuttles) >= CONFIG_GET(number/max_shuttle_count)) + . |= TOO_MANY_SHUTTLES + var/max_turfs = CONFIG_GET(number/max_shuttle_size) + if(!skip_flood_fill) + turfs += get_connected_shuttle_frame_turfs(origin, max_turfs) + var/turf_count = length(turfs) + if(!skip_flood_fill && !(turfs[origin])) + . |= ORIGIN_NOT_ON_SHUTTLE + if(turf_count > max_turfs) + . |= ABOVE_MAX_SHUTTLE_SIZE + . |= shuttle_area_check(turfs.Copy(), areas, z) + +/proc/shuttle_expand_check(turf/origin, obj/docking_port/mobile/shuttle, list/turfs, list/areas) + var/z = origin.z + var/skip_flood_fill = !!length(turfs) + if(skip_flood_fill && !(turfs[origin])) + . |= ORIGIN_NOT_ON_SHUTTLE + var/max_turfs = CONFIG_GET(number/max_shuttle_size) - shuttle.turf_count + var/list/adjacent_shuttles = list() + if(!skip_flood_fill) + turfs += get_connected_shuttle_frame_turfs(origin, max_turfs, adjacent_shuttles) + var/turf_count = length(turfs) + if(!skip_flood_fill && !(turfs[origin])) + . |= ORIGIN_NOT_ON_SHUTTLE + if(turf_count > max_turfs) + . |= ABOVE_MAX_SHUTTLE_SIZE + if(!adjacent_shuttles.Find(shuttle)) + . |= FRAME_NOT_ADJACENT_TO_LINKED_SHUTTLE + . |= shuttle_area_check(turfs.Copy(), areas, z) + +/* + * Check to see if the following conditions are met: + * 1. Any custom areas that overlap with the the region defined by `turf` are contained entirely within that region + * 2. All other turfs in the region are within whitelisted areas + */ +/proc/shuttle_area_check(list/turfs, list/areas, z) + for(var/area/custom_area as anything in GLOB.custom_areas) + var/list/area_turfs = custom_area.get_turfs_by_zlevel(z) + var/turf_count = length(area_turfs) + if(!turf_count) + continue + var/list/turfs_not_in_frame = area_turfs - turfs + var/turfs_not_in_frame_count = length(turfs_not_in_frame) + if(turfs_not_in_frame_count == turf_count) + continue + areas[custom_area] = area_turfs - turfs_not_in_frame + if(turfs_not_in_frame_count) + . |= CUSTOM_AREA_NOT_COMPLETELY_CONTAINED + turfs -= area_turfs + while(length(turfs)) + var/turf/checked_turf = pick(turfs) + var/area/checked_area = checked_turf.loc + var/list/area_turfs = checked_area.get_turfs_by_zlevel(z) + if(!is_type_in_list(checked_area, GLOB.shuttle_construction_area_whitelist)) + . |= INTERSECTS_NON_WHITELISTED_AREA + turfs -= area_turfs + +/proc/convert_areas_to_shuttle_areas(list/turfs, list/in_areas, list/out_areas, list/underlying_areas, area_type = /area/shuttle/custom) + for(var/area/area as anything in in_areas) + var/area/new_area = new area_type() + new_area.setup(area.name) + out_areas += new_area + var/list/area_turfs = in_areas[area] + var/datum/component/custom_area/custom_area = area.GetComponent(/datum/component/custom_area) + if(custom_area) + underlying_areas += (custom_area.previous_areas & area_turfs) + set_turfs_to_area(area_turfs, new_area) + turfs -= area_turfs + new_area.reg_in_areas_in_z() + new_area.create_area_lighting_objects() + new_area.power_change() + for(var/obj/machinery/door/firedoor/firelock as anything in area.firedoors) + firelock.CalculateAffectingAreas() + if(!area.has_contained_turfs()) + qdel(area) + +/proc/create_shuttle(mob/user, turf/origin, list/turfs, list/areas, shuttle_dir, port_dir = NORTH, area_type = /area/shuttle/custom, docking_port_type = /obj/docking_port/mobile/custom, obj/docking_port/stationary/dock_at, name, id, replace, custom = TRUE, force) + if(!ispath(docking_port_type, /obj/docking_port/mobile)) + CRASH("docking_port_type must be /obj/docking_port/mobile or a subpath") + if(!ispath(area_type, /area/shuttle)) + CRASH("area_type must be /area/shuttle or a subpath") + var/list/default_area_turfs = turfs.Copy() + // Convert each custom area into a shuttle area, then remove the affected turfs from the list of turfs to add to the default area + var/list/shuttle_areas = list() + var/list/underlying_areas = list() + convert_areas_to_shuttle_areas(default_area_turfs, areas, shuttle_areas, underlying_areas, area_type) + for(var/turf/turf as anything in default_area_turfs) + underlying_areas[turf] = turf.loc + + // Merge the remaining frame turfs into a default shuttle area + var/list/affected_areas = list() + var/area/default_area = new area_type() + default_area.setup(name) + set_turfs_to_area(default_area_turfs, default_area, affected_areas) + default_area.reg_in_areas_in_z() + default_area.create_area_lighting_objects() + default_area.power_change() + for(var/area_name in affected_areas) + var/area/merged_area = affected_areas[area_name] + for(var/obj/machinery/door/firedoor/firelock as anything in merged_area.firedoors) + firelock.CalculateAffectingAreas() + if(!merged_area.has_contained_turfs()) + qdel(merged_area) + shuttle_areas.Insert(1, default_area) + + var/obj/docking_port/mobile/mobile_port = new docking_port_type(origin, shuttle_areas) + mobile_port.underlying_areas_by_turf += underlying_areas + mobile_port.name = name + mobile_port.shuttle_id = id + mobile_port.port_direction = REVERSE_DIR(shuttle_dir) + mobile_port.dir = port_dir + mobile_port.calculate_docking_port_information() + mobile_port.turf_count = length(turfs) + + for(var/turf/turf as anything in turfs) + turf.stack_below_baseturf(/turf/open/floor/plating, /turf/baseturf_skipover/shuttle) + SEND_SIGNAL(turf, COMSIG_TURF_ADDED_TO_SHUTTLE, mobile_port) + if(!turf.depth_to_find_baseturf(/turf/baseturf_skipover/shuttle)) + continue + var/turf/new_ceiling = get_step_multiz(turf, UP) // check if a ceiling is needed + if(new_ceiling) + // generate ceiling + if(!(istype(new_ceiling, /turf/open/floor/engine/hull/ceiling) || new_ceiling.depth_to_find_baseturf(/turf/open/floor/engine/hull/ceiling))) + if(istype(new_ceiling, /turf/open/openspace) || istype(new_ceiling, /turf/open/space/openspace)) + new_ceiling.place_on_top(/turf/open/floor/engine/hull/ceiling) + else + new_ceiling.stack_ontop_of_baseturf(/turf/open/openspace, /turf/open/floor/engine/hull/ceiling) + new_ceiling.stack_ontop_of_baseturf(/turf/open/space/openspace, /turf/open/floor/engine/hull/ceiling) + + if(!istype(dock_at)) + dock_at = new(origin) + var/area/origin_area = get_area(origin) + dock_at.name = origin_area.name + dock_at.dir = port_dir + + mobile_port.register(replace, custom) + + message_admins("[key_name(user)] has created a shuttle at [ADMIN_VERBOSEJMP(origin)].") + log_shuttle("[key_name(user)] has created a shuttle at [get_area(origin)].") + + return mobile_port + +/proc/expand_shuttle(mob/user, obj/docking_port/mobile/shuttle, list/turfs, list/areas) + var/list/default_area_turfs = turfs.Copy() + // Convert each custom area into a shuttle area, then remove the affected turfs from the list of turfs to add to the default area + var/list/shuttle_areas = list() + var/list/underlying_areas = list() + convert_areas_to_shuttle_areas(default_area_turfs, areas, shuttle_areas, underlying_areas, shuttle.area_type) + for(var/turf/turf as anything in default_area_turfs) + underlying_areas[turf] = turf.loc + + var/list/affected_areas = list() + var/area/default_area = shuttle.shuttle_areas[1] + set_turfs_to_area(default_area_turfs, default_area, affected_areas) + default_area.power_change() + + for(var/area_name in affected_areas) + var/area/merged_area = affected_areas[area_name] + for(var/obj/machinery/door/firedoor/firelock as anything in merged_area.firedoors) + firelock.CalculateAffectingAreas() + if(!merged_area.has_contained_turfs()) + qdel(merged_area) + + for(var/area/shuttle_area as anything in shuttle_areas) + shuttle.shuttle_areas[shuttle_area] = TRUE + + var/list/bounds = shuttle.return_coords() + var/x0 = bounds[1] + var/y0 = bounds[2] + var/x1 = bounds[3] + var/y1 = bounds[4] + var/bounds_need_recalculation + for(var/turf/turf as anything in turfs) + turf.stack_below_baseturf(/turf/open/floor/plating, /turf/baseturf_skipover/shuttle) + SEND_SIGNAL(turf, COMSIG_TURF_ADDED_TO_SHUTTLE, shuttle) + if(turf.depth_to_find_baseturf(/turf/baseturf_skipover/shuttle)) + var/turf/new_ceiling = get_step_multiz(turf, UP) // check if a ceiling is needed + if(new_ceiling) + // generate ceiling + if(!(istype(new_ceiling, /turf/open/floor/engine/hull/ceiling) || new_ceiling.depth_to_find_baseturf(/turf/open/floor/engine/hull/ceiling))) + if(istype(new_ceiling, /turf/open/openspace) || istype(new_ceiling, /turf/open/space/openspace)) + new_ceiling.place_on_top(/turf/open/floor/engine/hull/ceiling) + else + new_ceiling.stack_ontop_of_baseturf(/turf/open/openspace, /turf/open/floor/engine/hull/ceiling) + new_ceiling.stack_ontop_of_baseturf(/turf/open/space/openspace, /turf/open/floor/engine/hull/ceiling) + if(bounds_need_recalculation) + continue + if(!(ISINRANGE(turf.x, x0, x1) && ISINRANGE(turf.y, y0, y1))) + bounds_need_recalculation = TRUE + + shuttle.turf_count += length(turfs) + shuttle.underlying_areas_by_turf += underlying_areas + SEND_SIGNAL(shuttle, COMSIG_SHUTTLE_EXPANDED, turfs) + if(bounds_need_recalculation) + QDEL_NULL(shuttle.assigned_transit) + shuttle.calculate_docking_port_information() + shuttle.initiate_docking(shuttle.get_docked(), force = TRUE) + + message_admins("[key_name(user)] has expanded [shuttle] at [ADMIN_VERBOSEJMP(user)].") + log_shuttle("[key_name(user)] expanded [shuttle] at [get_area(user)].") diff --git a/code/modules/shuttle/mobile_port/variants/custom/custom.dm b/code/modules/shuttle/mobile_port/variants/custom/custom.dm new file mode 100644 index 00000000000..1a5a1df71a1 --- /dev/null +++ b/code/modules/shuttle/mobile_port/variants/custom/custom.dm @@ -0,0 +1,19 @@ +/obj/docking_port/mobile/custom + name = "custom shuttle" + shuttle_id = "custom" + var/datum/weakref/master_blueprint + var/area/default_area + var/datum/weakref/control_console + var/datum/weakref/navigation_console + +/obj/docking_port/mobile/custom/Initialize(mapload, list/areas) + . = ..() + default_area = areas[1] + +/obj/docking_port/mobile/custom/canMove() + return ..() && (current_engine_power > 0) + +/obj/docking_port/mobile/custom/get_engine_coeff() + var/thrust_ratio = (current_engine_power * CUSTOM_ENGINE_POWER_MULTIPLIER)/(turf_count + CUSTOM_ENGINE_POWER_TURF_COUNT_OFFSET) + var/calculated_multiplier = 2*(1-(NUM_E ** -thrust_ratio)) + return clamp(1/calculated_multiplier, CUSTOM_ENGINE_COEFF_MIN, CUSTOM_ENGINE_COEFF_MAX) diff --git a/code/modules/shuttle/mobile_port/variants/custom/custom_consoles.dm b/code/modules/shuttle/mobile_port/variants/custom/custom_consoles.dm new file mode 100644 index 00000000000..c3ec5a05414 --- /dev/null +++ b/code/modules/shuttle/mobile_port/variants/custom/custom_consoles.dm @@ -0,0 +1,174 @@ +GLOBAL_LIST_INIT(custom_shuttle_station_area_whitelist, list(/area/station/asteroid)) + +/obj/machinery/computer/shuttle/custom_shuttle + desc = "A shuttle control computer." + icon_screen = "shuttle" + icon_keyboard = "tech_key" + shuttleId = "" + light_color = LIGHT_COLOR_CYAN + req_access = list() + interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON + possible_destinations = "whiteship_home;" + var/static/list/connections = list(COMSIG_TURF_ADDED_TO_SHUTTLE = PROC_REF(on_loc_added_to_shuttle)) + +/obj/machinery/computer/shuttle/custom_shuttle/on_construction(mob/user) + circuit.configure_machine(src) + if(!shuttleId) + AddElement(/datum/element/connect_loc, connections) + +/obj/machinery/computer/shuttle/custom_shuttle/proc/on_loc_added_to_shuttle(turf/source, obj/docking_port/mobile/custom/port) + SIGNAL_HANDLER + if(!istype(port)) + say("Cannot link to this kind of shuttle!") + else + if(connect_to_shuttle(TRUE, port)) + RemoveElement(/datum/element/connect_loc, connections) + +/obj/machinery/computer/shuttle/custom_shuttle/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock) + var/obj/docking_port/mobile/custom/custom_port = port + if(istype(custom_port)) + if(custom_port.control_console?.resolve()) + say("Control console already present!") + return FALSE + . = ..() + if(!.) + return + if(istype(custom_port)) + custom_port.control_console = WEAKREF(src) + name = "[port.name] console" + +/obj/machinery/computer/shuttle/custom_shuttle/proc/linkShuttle(new_id) + if(shuttleId=="") + shuttleId = new_id + possible_destinations = "whiteship_home;shuttle[new_id]_custom;" + return TRUE + return FALSE + + +//docking cam +/obj/machinery/computer/camera_advanced/shuttle_docker/custom + lock_override = NONE + jump_to_ports = list("whiteship_home" = 1) + designate_time = 100 + circuit = /obj/item/circuitboard/computer/shuttle/docker + var/static/list/connections = list(COMSIG_TURF_ADDED_TO_SHUTTLE = PROC_REF(on_loc_added_to_shuttle)) + +/obj/machinery/computer/camera_advanced/shuttle_docker/custom/on_construction(mob/user) + circuit.configure_machine(src) + if(!shuttleId) + AddElement(/datum/element/connect_loc, connections) + +/obj/machinery/computer/camera_advanced/shuttle_docker/custom/proc/on_loc_added_to_shuttle(turf/source, obj/docking_port/mobile/custom/port) + SIGNAL_HANDLER + if(!istype(port)) + say("Cannot link to this kind of shuttle!") + else + if(connect_to_shuttle(TRUE, port)) + RemoveElement(/datum/element/connect_loc, connections) + +/obj/machinery/computer/camera_advanced/shuttle_docker/custom/Initialize(mapload) + . = ..() + GLOB.jam_on_wardec += src + +/obj/machinery/computer/camera_advanced/shuttle_docker/custom/Destroy() + GLOB.jam_on_wardec -= src + return ..() + +/obj/machinery/computer/camera_advanced/shuttle_docker/custom/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock) + if(shuttleId) //We normally should only be connecting newly-created consoles to shuttles, but just in case... + var/obj/docking_port/mobile/old_shuttle = SSshuttle.getShuttle(shuttleId) + if(old_shuttle) + UnregisterSignal(old_shuttle, COMSIG_SHUTTLE_EXPANDED) + var/obj/docking_port/mobile/custom/custom_port = port + if(istype(custom_port)) + if(custom_port.navigation_console?.resolve()) + say("Navigation console already present!") + return FALSE + . = ..() + if(!.) + return + name = "[port.name] navigation computer" + if(istype(custom_port)) + custom_port.navigation_console = WEAKREF(src) + RegisterSignal(port, COMSIG_SHUTTLE_EXPANDED, PROC_REF(on_shuttle_expanded)) + recalculate_eye_view(port) + +/obj/machinery/computer/camera_advanced/shuttle_docker/custom/proc/on_shuttle_expanded(obj/docking_port/mobile/source, list/turfs) + SIGNAL_HANDLER + recalculate_eye_view(source) + if(my_port) + var/here_x = source.x + var/here_y = source.y + var/there_x = my_port.x + var/there_y = my_port.y + var/target_z = my_port.z + var/rotation = angle2dir_cardinal(dir2angle(my_port.dir) - dir2angle(source.dir)) + var/docked = my_port.get_docked() == source + for(var/turf/turf as anything in turfs) + var/turf/checked_turf + if(docked) + checked_turf = turf + else + var/offset_x = turf.x - here_x + var/offset_y = turf.y - here_y + var/target_x = there_x + var/target_y = there_y + switch(rotation) + if(NORTH) + target_x += offset_x + target_y += offset_y + if(SOUTH) + target_x -= offset_x + target_y -= offset_y + if(EAST) + target_x -= offset_y + target_y += offset_x + if(WEST) + target_x += offset_y + target_y -= offset_x + checked_turf = locate(target_x, target_y, target_z) + if(checkLandingTurf(checked_turf) != SHUTTLE_DOCKER_LANDING_CLEAR) + if(docked) + my_port.unregister() + my_port.delete_after = TRUE + my_port.shuttle_id = null + my_port.name = "Old [my_port.name]" + my_port = null + else + QDEL_NULL(my_port) + break + +/obj/machinery/computer/camera_advanced/shuttle_docker/custom/proc/recalculate_eye_view(obj/docking_port/mobile/shuttle) + var/bigger_shuttle_dimension = max(shuttle.width, shuttle.height) + var/list/viewsize = getviewsize(world.view) + var/smaller_view_dimension = min(viewsize[1], viewsize[2]) + var/new_view_range = max(bigger_shuttle_dimension - smaller_view_dimension, 0) + if(new_view_range != view_range) + view_range = new_view_range + +/obj/machinery/computer/camera_advanced/shuttle_docker/custom/placeLandingSpot() + if(!shuttleId) + return //Only way this would happen is if someone else delinks the console while in use somehow + var/obj/docking_port/mobile/M = SSshuttle.getShuttle(shuttleId) + if(M?.mode != SHUTTLE_IDLE) + to_chat(usr, "You cannot target locations while in transit.") + return + ..() + +/obj/machinery/computer/camera_advanced/shuttle_docker/custom/checkLandingTurf(turf/T, list/overlappers) + . = ..() + var/area/area = get_area(T) + if(!is_type_in_list(area, GLOB.custom_shuttle_station_area_whitelist) && is_type_in_list(area, GLOB.the_station_areas)) + return SHUTTLE_DOCKER_BLOCKED + +/obj/machinery/computer/camera_advanced/shuttle_docker/custom/attack_hand(mob/user) + if(!shuttleId) + to_chat(user, "You must link the console to a shuttle first.") + return + return ..() + +/obj/machinery/computer/camera_advanced/shuttle_docker/custom/proc/linkShuttle(new_id) + if(shuttleId=="") + shuttleId = new_id + return TRUE + return FALSE diff --git a/code/modules/shuttle/mobile_port/variants/supply.dm b/code/modules/shuttle/mobile_port/variants/supply.dm index c45d4285523..54f7548aff2 100644 --- a/code/modules/shuttle/mobile_port/variants/supply.dm +++ b/code/modules/shuttle/mobile_port/variants/supply.dm @@ -119,7 +119,7 @@ GLOBAL_LIST_INIT(blacklisted_cargo_types, typecacheof(list( message_admins("Blacklisted item found on in-transit Cargo Shuttle. See cargo logs for more details.") SSshuttle.centcom_message = "Contraband found on Cargo Shuttle. This has been returned via drop pod." -/obj/docking_port/mobile/supply/initiate_docking() +/obj/docking_port/mobile/supply/initiate_docking(obj/docking_port/stationary/new_dock, force=FALSE) if(getDockedId() == "cargo_away") // Buy when we leave home. buy() create_mail() diff --git a/code/modules/shuttle/shuttle_consoles/navigation_computer.dm b/code/modules/shuttle/shuttle_consoles/navigation_computer.dm index 3e9bf0cbe3b..033c6309aae 100644 --- a/code/modules/shuttle/shuttle_consoles/navigation_computer.dm +++ b/code/modules/shuttle/shuttle_consoles/navigation_computer.dm @@ -24,7 +24,7 @@ var/x_offset = 0 ///y offset for where the camera eye will spawn. Starts from the shuttle's docking port var/y_offset = 0 - var/list/whitelist_turfs = list(/turf/open/space, /turf/open/floor/plating, /turf/open/lava, /turf/open/openspace) + var/list/whitelist_turfs = list(/turf/open/space, /turf/open/floor/plating, /turf/open/lava, /turf/open/openspace, /turf/open/misc) var/see_hidden = FALSE var/designate_time = 0 var/turf/designating_target_loc diff --git a/code/modules/shuttle/shuttle_consoles/shuttle_console.dm b/code/modules/shuttle/shuttle_consoles/shuttle_console.dm index e269e360ba4..cf5ccb2c1c4 100644 --- a/code/modules/shuttle/shuttle_consoles/shuttle_console.dm +++ b/code/modules/shuttle/shuttle_consoles/shuttle_console.dm @@ -239,6 +239,7 @@ possible_destinations = replacetext(replacetextEx(possible_destinations, "[shuttleId]_custom", ""), ";;", ";") shuttleId = port.shuttle_id possible_destinations += ";[port.shuttle_id]_custom" + return TRUE #undef SHUTTLE_CONSOLE_ACCESSDENIED #undef SHUTTLE_CONSOLE_ENDGAME diff --git a/config/game_options.txt b/config/game_options.txt index 93f060c3759..97799dd8656 100644 --- a/config/game_options.txt +++ b/config/game_options.txt @@ -560,3 +560,7 @@ MAX_POSITIVE_QUIRKS 6 # If the value is lower than 1, it'll tend to even out the odds # If higher than 1, it'll lean toward common spawns even more. RANDOM_LOOT_WEIGHT_MODIFIER 1 + +## Custom shuttle spam prevention. Changine these numbers allows you to change the maxsize and amount of custom shuttles. +MAX_SHUTTLE_COUNT 6 +MAX_SHUTTLE_SIZE 300 diff --git a/icons/mob/actions/actions_shuttle.dmi b/icons/mob/actions/actions_shuttle.dmi new file mode 100644 index 00000000000..ef35786a2b2 Binary files /dev/null and b/icons/mob/actions/actions_shuttle.dmi differ diff --git a/icons/obj/items_cyborg.dmi b/icons/obj/items_cyborg.dmi index e3014997c04..17a65770979 100644 Binary files a/icons/obj/items_cyborg.dmi and b/icons/obj/items_cyborg.dmi differ diff --git a/icons/obj/scrolls.dmi b/icons/obj/scrolls.dmi index ae488cab2f7..84805f239d9 100644 Binary files a/icons/obj/scrolls.dmi and b/icons/obj/scrolls.dmi differ diff --git a/tgstation.dme b/tgstation.dme index 27a7d558e7d..5b6e4d22c1f 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -330,6 +330,7 @@ #include "code\__DEFINES\dcs\signals\signals_janitor.dm" #include "code\__DEFINES\dcs\signals\signals_key.dm" #include "code\__DEFINES\dcs\signals\signals_ladder.dm" +#include "code\__DEFINES\dcs\signals\signals_lattice.dm" #include "code\__DEFINES\dcs\signals\signals_lazy_templates.dm" #include "code\__DEFINES\dcs\signals\signals_leash.dm" #include "code\__DEFINES\dcs\signals\signals_lift.dm" @@ -1098,6 +1099,7 @@ #include "code\datums\components\combo_attacks.dm" #include "code\datums\components\combustible_flooder.dm" #include "code\datums\components\connect_containers.dm" +#include "code\datums\components\connect_inventory.dm" #include "code\datums\components\connect_loc_behalf.dm" #include "code\datums\components\connect_mob_behalf.dm" #include "code\datums\components\connect_range.dm" @@ -1110,6 +1112,7 @@ #include "code\datums\components\cult_ritual_item.dm" #include "code\datums\components\curse_of_hunger.dm" #include "code\datums\components\curse_of_polymorph.dm" +#include "code\datums\components\custom_area.dm" #include "code\datums\components\customizable_reagent_holder.dm" #include "code\datums\components\damage_aura.dm" #include "code\datums\components\damage_chain.dm" @@ -1575,6 +1578,8 @@ #include "code\datums\elements\selfknockback.dm" #include "code\datums\elements\series.dm" #include "code\datums\elements\shiny_bait.dm" +#include "code\datums\elements\shuttle_construction_lattice.dm" +#include "code\datums\elements\shuttle_construction_turf.dm" #include "code\datums\elements\sideway_movement.dm" #include "code\datums\elements\simple_flying.dm" #include "code\datums\elements\skill_reward.dm" @@ -5981,6 +5986,9 @@ #include "code\modules\shuttle\mobile_port\variants\ferry.dm" #include "code\modules\shuttle\mobile_port\variants\infiltrator.dm" #include "code\modules\shuttle\mobile_port\variants\supply.dm" +#include "code\modules\shuttle\mobile_port\variants\custom\blueprints.dm" +#include "code\modules\shuttle\mobile_port\variants\custom\custom.dm" +#include "code\modules\shuttle\mobile_port\variants\custom\custom_consoles.dm" #include "code\modules\shuttle\mobile_port\variants\emergency\emergency.dm" #include "code\modules\shuttle\mobile_port\variants\emergency\emergency_console.dm" #include "code\modules\shuttle\mobile_port\variants\emergency\emergency_types.dm" diff --git a/tgui/packages/tgui/constants.ts b/tgui/packages/tgui/constants.ts index 0d58cb5cbe3..1b55c62c945 100644 --- a/tgui/packages/tgui/constants.ts +++ b/tgui/packages/tgui/constants.ts @@ -69,6 +69,21 @@ export const CSS_COLORS = [ 'yellow', ] as const; +export enum Direction { + NONE = 0, + NORTH = 1, + SOUTH = 2, + EAST = 4, + WEST = 8, + NORTHEAST = NORTH | EAST, + NORTHWEST = NORTH | WEST, + SOUTHEAST = SOUTH | EAST, + SOUTHWEST = SOUTH | WEST, + VERTICAL = NORTH | SOUTH, + HORIZONTAL = EAST | WEST, + ALL = NORTH | SOUTH | EAST | WEST, +} + export type CssColor = (typeof CSS_COLORS)[number]; /* IF YOU CHANGE THIS KEEP IT IN SYNC WITH CHAT CSS */ diff --git a/tgui/packages/tgui/interfaces/ShuttleBlueprints.tsx b/tgui/packages/tgui/interfaces/ShuttleBlueprints.tsx new file mode 100644 index 00000000000..c2bcca2e852 --- /dev/null +++ b/tgui/packages/tgui/interfaces/ShuttleBlueprints.tsx @@ -0,0 +1,422 @@ +import { ReactNode, useState } from 'react'; +import { + Box, + Button, + Dropdown, + Input, + Section, + Stack, + Tooltip, +} from 'tgui-core/components'; +import { BooleanLike } from 'tgui-core/react'; + +import { useBackend } from '../backend'; +import { Direction } from '../constants'; +import { Window } from '../layouts'; + +type AreaData = { name: string; ref: string }; + +type VisualizationToggleProps = { visualizing: BooleanLike }; + +type ShuttleConstructionUnieuqData = { + linkedShuttle: 0; + tooManyShuttles: BooleanLike; + onCustomShuttle: BooleanLike; +}; + +type ShuttleConfigurationUniqueData = { + linkedShuttle: string; + onShuttle: BooleanLike; + inDefaultArea: BooleanLike; + currentArea: AreaData; + defaultApc: BooleanLike; + apcInMergeRegion: BooleanLike; + apcs: Record; + neighboringAreas: Record; + idle: BooleanLike; +}; + +type ShuttleBlueprintsData = { + shuttles?: Record; + visualizing: BooleanLike; + onShuttleFrame: BooleanLike; + masterExists: BooleanLike; + isMaster: BooleanLike; +} & (ShuttleConstructionUnieuqData | ShuttleConfigurationUniqueData); + +type DirectionPadProps = { + title: string; + tooltip?: ReactNode; + enabledDirections: Direction; + selectedDirection: Direction; + onSelect: (direction: Direction) => void; +}; + +const directionData: [Direction, string][] = [ + [Direction.NORTH, 'up'], + [Direction.SOUTH, 'down'], + [Direction.EAST, 'right'], + [Direction.WEST, 'left'], +]; + +const DirectionPad = (props: DirectionPadProps) => { + const { title, tooltip, enabledDirections, selectedDirection, onSelect } = + props; + const [north, south, east, west] = directionData.map( + ([direction, icon_suffix], i) => ( + +