diff --git a/_maps/deprecated/Ruins/oldAIsat.dmm b/_maps/deprecated/Ruins/oldAIsat.dmm index 622873e4f7ae..ea8e4ad1d1e0 100644 --- a/_maps/deprecated/Ruins/oldAIsat.dmm +++ b/_maps/deprecated/Ruins/oldAIsat.dmm @@ -564,7 +564,7 @@ "bU" = ( /obj/effect/decal/cleanable/blood, /obj/structure/chair, -/obj/item/clothing/under/rank/centcom/officer, +/obj/item/clothing/under/rank/centcom/official, /obj/item/restraints/handcuffs, /obj/effect/decal/remains/human, /turf/open/floor/plating/airless, diff --git a/_maps/map_files/generic/CentCom.dmm b/_maps/map_files/generic/CentCom.dmm index 24f28ce738c6..86538c6fc45d 100644 --- a/_maps/map_files/generic/CentCom.dmm +++ b/_maps/map_files/generic/CentCom.dmm @@ -680,7 +680,7 @@ "alS" = ( /obj/structure/fans/tiny/invisible, /turf/open/floor/holofloor/hyperspace, -/area/centcom/supplypod/flyMeToTheMoon) +/area/centcom/supplypod/supplypod_temp_holding) "alW" = ( /obj/structure/chair{ dir = 8 @@ -4244,7 +4244,7 @@ /area/centcom/ferry) "aNE" = ( /turf/open/floor/plasteel, -/area/centcom/supplypod/podStorage) +/area/centcom/supplypod/pod_storage) "aNF" = ( /obj/machinery/computer/communications{ dir = 1 @@ -9186,8 +9186,6 @@ /turf/open/floor/plasteel/dark, /area/ctf) "hYc" = ( -/obj/structure/destructible/cult/tome, -/obj/item/book/codex_gigas, /obj/machinery/airalarm/directional/east, /obj/effect/turf_decal/corner/transparent/neutral{ dir = 1 @@ -9199,6 +9197,7 @@ /obj/effect/turf_decal/corner/transparent/neutral{ dir = 8 }, +/obj/machinery/vending/wardrobe/cent_wardrobe, /turf/open/floor/plasteel/dark, /area/centcom/ferry) "hZs" = ( diff --git a/_maps/map_files/generic/blank.dmm b/_maps/map_files/generic/blank.dmm index b8744ca3eca5..b918e3fcaead 100644 --- a/_maps/map_files/generic/blank.dmm +++ b/_maps/map_files/generic/blank.dmm @@ -38,7 +38,7 @@ "N" = ( /obj/structure/fans/tiny/invisible, /turf/open/floor/holofloor/hyperspace, -/area/centcom/supplypod/flyMeToTheMoon) +/area/centcom/supplypod/supplypod_temp_holding) "P" = ( /obj/structure/signpost/salvation{ icon = 'icons/obj/structures.dmi'; diff --git a/check_regex.yaml b/check_regex.yaml index c28639172af2..7e5269c30a78 100644 --- a/check_regex.yaml +++ b/check_regex.yaml @@ -38,7 +38,7 @@ standards: - exactly: [ - 297, + 298, "non-bitwise << uses", '(? 20 || O.pixel_x < -20 || O.pixel_y > 20 || O.pixel_y < -20) + #define isbook(O) (is_type_in_typecache(O, GLOB.book_types)) GLOBAL_LIST_INIT(book_types, typecacheof(list( diff --git a/code/__DEFINES/lag_switch.dm b/code/__DEFINES/lag_switch.dm new file mode 100644 index 000000000000..022880c1a461 --- /dev/null +++ b/code/__DEFINES/lag_switch.dm @@ -0,0 +1,24 @@ +// All of the possible Lag Switch lag mitigation measures +// If you add more do not forget to update MEASURES_AMOUNT accordingly +/// Stops ghosts flying around freely, they can still jump and orbit, staff exempted +#define DISABLE_DEAD_KEYLOOP 1 +/// Stops ghosts using zoom/t-ray verbs and resets their view if zoomed out, staff exempted +#define DISABLE_GHOST_ZOOM_TRAY 2 +/// Disable runechat and enable the bubbles, speaking mobs with TRAIT_BYPASS_MEASURES exempted +#define DISABLE_RUNECHAT 3 +/// Disable icon2html procs from verbs like examine, mobs calling with TRAIT_BYPASS_MEASURES exempted +#define DISABLE_USR_ICON2HTML 4 +/// Prevents anyone from joining the game as anything but observer +#define DISABLE_NON_OBSJOBS 5 +/// Limit IC/dchat spam to one message every x seconds per client, TRAIT_BYPASS_MEASURES exempted +#define SLOWMODE_SAY 6 +/// Disables parallax, as if everyone had disabled their preference, TRAIT_BYPASS_MEASURES exempted +#define DISABLE_PARALLAX 7 +/// Disables footsteps, TRAIT_BYPASS_MEASURES exempted +#define DISABLE_FOOTSTEPS 8 +/// Disables planet deletion +#define DISABLE_PLANETDEL 9 +/// Disables ALL new planet generation, TRAIT_BYPASS_MEASURES exempted +#define DISABLE_PLANETGEN 10 + +#define MEASURES_AMOUNT 10 // The total number of switches defined above diff --git a/code/__DEFINES/obj_flags.dm b/code/__DEFINES/obj_flags.dm index d9c57e5d3efa..dfecc6f8af6b 100644 --- a/code/__DEFINES/obj_flags.dm +++ b/code/__DEFINES/obj_flags.dm @@ -59,3 +59,7 @@ #define ORGAN_VITAL (1<<4) //Currently only the brain #define ORGAN_EDIBLE (1<<5) //is a snack? :D #define ORGAN_SYNTHETIC_EMP (1<<6) //Synthetic organ affected by an EMP. Deteriorates over time. + +/// Flags for the pod_flags var on /obj/structure/closet/supplypod + +#define FIRST_SOUNDS (1<<0) // If it shouldn't play sounds the first time it lands, used for reverse mode diff --git a/code/__DEFINES/role_preferences.dm b/code/__DEFINES/role_preferences.dm index 4603279a7672..361a24697a39 100644 --- a/code/__DEFINES/role_preferences.dm +++ b/code/__DEFINES/role_preferences.dm @@ -60,7 +60,6 @@ GLOBAL_LIST_INIT(special_roles, list( ROLE_NINJA, ROLE_OBSESSED, ROLE_SPACE_DRAGON, - ROLE_MONKEY = /datum/game_mode/monkey, ROLE_REVENANT, ROLE_ABDUCTOR, ROLE_DEVIL = /datum/game_mode/devil, diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index 26d82fba3278..529274a50b39 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -263,6 +263,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_SCOOPABLE "scoopable" //your smooches actually deal damage to their target #define TRAIT_KISS_OF_DEATH "kiss_of_death" +/// This mob overrides certian SSlag_switch measures with this special trait +#define TRAIT_BYPASS_MEASURES "bypass_lagswitch_measures" //non-mob traits /// Used for limb-based paralysis, where replacing the limb will fix it. #define TRAIT_PARALYSIS "paralysis" diff --git a/code/__HELPERS/datums.dm b/code/__HELPERS/datums.dm new file mode 100644 index 000000000000..7cf87c203b73 --- /dev/null +++ b/code/__HELPERS/datums.dm @@ -0,0 +1,9 @@ +///Check if a datum has not been deleted and is a valid source +/proc/is_valid_src(datum/source_datum) + if(istype(source_datum)) + return !QDELETED(source_datum) + return FALSE + +/proc/call_async(datum/source, proc_type, list/arguments) + set waitfor = FALSE + return call(source, proc_type)(arglist(arguments)) diff --git a/code/__HELPERS/icons.dm b/code/__HELPERS/icons.dm index f8cc644c6649..126d93fe352a 100644 --- a/code/__HELPERS/icons.dm +++ b/code/__HELPERS/icons.dm @@ -1244,6 +1244,8 @@ GLOBAL_DATUM_INIT(dummySave, /savefile, new("tmp/dummySave.sav")) //Cache of ico /proc/icon2html(atom/thing, client/target, icon_state, dir = SOUTH, frame = 1, moving = FALSE, sourceonly = FALSE, extra_classes = null) if (!thing) return + if(SSlag_switch.measures[DISABLE_USR_ICON2HTML] && usr && !HAS_TRAIT(usr, TRAIT_BYPASS_MEASURES)) + return var/key var/icon/icon2collapse = thing @@ -1354,6 +1356,8 @@ GLOBAL_DATUM_INIT(dummySave, /savefile, new("tmp/dummySave.sav")) //Cache of ico /proc/costly_icon2html(thing, target, sourceonly = FALSE) if (!thing) return + if(SSlag_switch.measures[DISABLE_USR_ICON2HTML] && usr && !HAS_TRAIT(usr, TRAIT_BYPASS_MEASURES)) + return if (isicon(thing)) return icon2html(thing, target) diff --git a/code/_globalvars/lists/mobs.dm b/code/_globalvars/lists/mobs.dm index ded23733220c..0c28353395d4 100644 --- a/code/_globalvars/lists/mobs.dm +++ b/code/_globalvars/lists/mobs.dm @@ -12,6 +12,7 @@ GLOBAL_LIST_EMPTY(stealthminID) //reference list with IDs that store ckeys, //This is for procs to replace all the goddamn 'in world's that are chilling around the code GLOBAL_LIST_EMPTY(player_list) //all mobs **with clients attached**. +GLOBAL_LIST_EMPTY(keyloop_list) //as above but can be limited to boost performance GLOBAL_LIST_EMPTY(mob_list) //all mobs, including clientless GLOBAL_LIST_EMPTY(mob_directory) //mob_id -> mob GLOBAL_LIST_EMPTY(alive_mob_list) //all alive mobs, including clientless. Excludes /mob/dead/new_player diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm index b08504daae29..3239cb53b8d0 100644 --- a/code/_globalvars/traits.dm +++ b/code/_globalvars/traits.dm @@ -84,6 +84,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_CANNOT_OPEN_PRESENTS" = TRAIT_CANNOT_OPEN_PRESENTS, "TRAIT_PRESENT_VISION" = TRAIT_PRESENT_VISION, "TRAIT_DISK_VERIFIER" = TRAIT_DISK_VERIFIER, + "TRAIT_BYPASS_MEASURES" = TRAIT_BYPASS_MEASURES, "TRAIT_NOMOBSWAP" = TRAIT_NOMOBSWAP, "TRAIT_XRAY_VISION" = TRAIT_XRAY_VISION, "TRAIT_THERMAL_VISION" = TRAIT_THERMAL_VISION, diff --git a/code/_onclick/hud/parallax.dm b/code/_onclick/hud/parallax.dm index 334dabd9198e..36d278adac0d 100644 --- a/code/_onclick/hud/parallax.dm +++ b/code/_onclick/hud/parallax.dm @@ -46,6 +46,10 @@ /datum/hud/proc/apply_parallax_pref(mob/viewmob) var/mob/screenmob = viewmob || mymob + + if (SSlag_switch.measures[DISABLE_PARALLAX] && !HAS_TRAIT(viewmob, TRAIT_BYPASS_MEASURES)) + return FALSE + var/client/C = screenmob.client if(C.prefs) var/pref = C.prefs.parallax diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm index bf9b8d24a05c..41a470aac610 100644 --- a/code/controllers/configuration/entries/general.dm +++ b/code/controllers/configuration/entries/general.dm @@ -325,6 +325,10 @@ /datum/config_entry/flag/maprotation +/datum/config_entry/number/auto_lag_switch_pop //Number of clients at which drastic lag mitigation measures kick in + config_entry_value = null + min_val = 0 + /datum/config_entry/number/soft_popcap config_entry_value = null min_val = 0 diff --git a/code/controllers/subsystem/input.dm b/code/controllers/subsystem/input.dm index 07de18a43c2c..8bdc53089e12 100644 --- a/code/controllers/subsystem/input.dm +++ b/code/controllers/subsystem/input.dm @@ -93,7 +93,5 @@ SUBSYSTEM_DEF(input) user.set_macros() /datum/controller/subsystem/input/fire() - var/list/clients = GLOB.clients // Let's sing the list cache song - for(var/i in 1 to clients.len) - var/client/C = clients[i] - C.keyLoop() + for(var/mob/user as anything in GLOB.keyloop_list) + user.focus?.keyLoop(user.client) diff --git a/code/controllers/subsystem/lag_switch.dm b/code/controllers/subsystem/lag_switch.dm new file mode 100644 index 000000000000..eadf8d219324 --- /dev/null +++ b/code/controllers/subsystem/lag_switch.dm @@ -0,0 +1,156 @@ +/// The subsystem for controlling drastic performance enhancements aimed at reducing server load for a smoother albeit slightly duller gaming experience +SUBSYSTEM_DEF(lag_switch) + name = "Lag Switch" + flags = SS_NO_FIRE + + /// If the lag switch measures should attempt to trigger automatically, TRUE if a config value exists + var/auto_switch = FALSE + /// Amount of connected clients at which the Lag Switch should engage, set via config or admin panel + var/trigger_pop = INFINITY - 1337 + /// List of bools corresponding to code/__DEFINES/lag_switch.dm + var/static/list/measures[MEASURES_AMOUNT] + /// List of measures that toggle automatically + var/list/auto_measures = list(DISABLE_GHOST_ZOOM_TRAY, DISABLE_RUNECHAT, DISABLE_USR_ICON2HTML, DISABLE_PARALLAX, DISABLE_FOOTSTEPS, DISABLE_PLANETDEL) + /// Timer ID for the automatic veto period + var/veto_timer_id + /// Cooldown between say verb uses when slowmode is enabled + var/slowmode_cooldown = 3 SECONDS + +/datum/controller/subsystem/lag_switch/Initialize(start_timeofday) + for(var/i = 1, i <= measures.len, i++) + measures[i] = FALSE + var/auto_switch_pop = CONFIG_GET(number/auto_lag_switch_pop) + if(auto_switch_pop) + auto_switch = TRUE + trigger_pop = auto_switch_pop + RegisterSignal(SSdcs, COMSIG_GLOB_CLIENT_CONNECT, .proc/client_connected) + return ..() + +/datum/controller/subsystem/lag_switch/proc/client_connected(datum/source, client/connected) + SIGNAL_HANDLER + if(TGS_CLIENT_COUNT < trigger_pop) + return + + auto_switch = FALSE + UnregisterSignal(SSdcs, COMSIG_GLOB_CLIENT_CONNECT) + veto_timer_id = addtimer(CALLBACK(src, .proc/set_all_measures, TRUE, TRUE), 20 SECONDS, TIMER_STOPPABLE) + message_admins("Lag Switch population threshold reached. Automatic activation of lag mitigation measures occuring in 20 seconds. (CANCEL)") + log_admin("Lag Switch population threshold reached. Automatic activation of lag mitigation measures occuring in 20 seconds.") + +/// (En/Dis)able automatic triggering of switches based on client count +/datum/controller/subsystem/lag_switch/proc/toggle_auto_enable() + auto_switch = !auto_switch + if(auto_switch) + RegisterSignal(SSdcs, COMSIG_GLOB_CLIENT_CONNECT, .proc/client_connected) + else + UnregisterSignal(SSdcs, COMSIG_GLOB_CLIENT_CONNECT) + +/// Called from an admin chat link +/datum/controller/subsystem/lag_switch/proc/cancel_auto_enable_in_progress() + if(!veto_timer_id) + return FALSE + + deltimer(veto_timer_id) + veto_timer_id = null + return TRUE + +/// Update the slowmode timer length and clear existing ones if reduced +/datum/controller/subsystem/lag_switch/proc/change_slowmode_cooldown(length) + if(!length) + return FALSE + + var/length_secs = length SECONDS + if(length_secs <= 0) + length_secs = 1 // one tick because cooldowns do not like 0 + + if(length_secs < slowmode_cooldown) + for(var/client/C as anything in GLOB.clients) + COOLDOWN_RESET(C, say_slowmode) + + slowmode_cooldown = length_secs + if(measures[SLOWMODE_SAY]) + to_chat(world, span_boldannounce("Slowmode timer has been changed to [length] seconds by an admin.")) + return TRUE + +/// Handle the state change for individual measures +/datum/controller/subsystem/lag_switch/proc/set_measure(measure_key, state) + if(isnull(measure_key) || isnull(state)) + stack_trace("SSlag_switch.set_measure() was called with a null arg") + return FALSE + if(isnull(LAZYACCESS(measures, measure_key))) + stack_trace("SSlag_switch.set_measure() was called with a measure_key not in the list of measures") + return FALSE + if(measures[measure_key] == state) + return TRUE + + measures[measure_key] = state + + switch(measure_key) + if(DISABLE_DEAD_KEYLOOP) + if(state) + for(var/mob/user as anything in GLOB.player_list) + if(user.stat == DEAD && !user.client?.holder) + GLOB.keyloop_list -= user + deadchat_broadcast(span_big("To increase performance Observer freelook is now disabled. Please use Orbit, Teleport, and Jump to look around."), message_type = DEADCHAT_ANNOUNCEMENT) + else + GLOB.keyloop_list |= GLOB.player_list + deadchat_broadcast("Observer freelook has been re-enabled. Enjoy your wooshing.", message_type = DEADCHAT_ANNOUNCEMENT) + if(DISABLE_GHOST_ZOOM_TRAY) + if(state) // if enabling make sure current ghosts are updated + for(var/mob/dead/observer/ghost in GLOB.dead_mob_list) + if(!ghost.client) + continue + if(!ghost.client.holder && ghost.client.view_size.getView() != ghost.client.view_size.default) + ghost.client.view_size.resetToDefault() + if(SLOWMODE_SAY) + if(state) + to_chat(world, span_boldannounce("Slowmode for IC/dead chat has been enabled with [slowmode_cooldown/10] seconds between messages.")) + else + for(var/client/C as anything in GLOB.clients) + COOLDOWN_RESET(C, say_slowmode) + to_chat(world, span_boldannounce("Slowmode for IC/dead chat has been disabled by an admin.")) + if(DISABLE_NON_OBSJOBS) + world.update_status() + if(DISABLE_PARALLAX) + if (state) + to_chat(world, span_boldannounce("Parallax has been disabled for performance concerns.")) + else + to_chat(world, span_boldannounce("Parallax has been re-enabled.")) + + for (var/mob/mob as anything in GLOB.mob_list) + mob.hud_used?.update_parallax_pref() + if(DISABLE_FOOTSTEPS) + if (state) + to_chat(world, span_boldannounce("Footstep sounds have been disabled for performance concerns.")) + else + to_chat(world, span_boldannounce("Footstep sounds have been re-enabled.")) + if(DISABLE_PLANETDEL) + if (state) + to_chat(world, span_boldannounce("Planet deletion and regeneration has been disabled for performance concerns.")) + else + to_chat(world, span_boldannounce("Planet deletion has been re-enabled.")) + if(DISABLE_PLANETGEN) + if (state) + to_chat(world, span_boldannounce("Planet generation has been disabled for performance concerns. You can still dock at already-generated planets.")) + else + to_chat(world, span_boldannounce("Planet generation has been re-enabled.")) + + return TRUE + +/// Helper to loop over all measures for mass changes +/datum/controller/subsystem/lag_switch/proc/set_all_measures(state, automatic = FALSE) + if(isnull(state)) + stack_trace("SSlag_switch.set_all_measures() was called with a null state arg") + return FALSE + + if(automatic) + message_admins("Lag Switch enabling automatic measures now.") + log_admin("Lag Switch enabling automatic measures now.") + veto_timer_id = null + for(var/i = 1, i <= auto_measures.len, i++) + set_measure(auto_measures[i], state) + return TRUE + + for(var/i = 1, i <= measures.len, i++) + set_measure(i, state) + return TRUE diff --git a/code/controllers/subsystem/traumas.dm b/code/controllers/subsystem/traumas.dm index 87628785caf0..ab220b4382b4 100644 --- a/code/controllers/subsystem/traumas.dm +++ b/code/controllers/subsystem/traumas.dm @@ -96,7 +96,7 @@ SUBSYSTEM_DEF(traumas) /obj/item/clothing/under/rank/security/head_of_security/parade/female, //WS Edit - Better Command Uniforms /obj/item/clothing/head/helmet/abductor, /obj/item/clothing/suit/armor/abductor/vest, /obj/item/melee/baton/abductor, /obj/item/storage/belt/military/abductor, /obj/item/gun/energy/alien, /obj/item/abductor/silencer, - /obj/item/abductor/gizmo, /obj/item/clothing/under/rank/centcom/officer, + /obj/item/abductor/gizmo, /obj/item/clothing/under/rank/centcom/official, /obj/item/clothing/suit/space/hardsuit/ert, /obj/item/clothing/suit/space/hardsuit/ert/sec, /obj/item/clothing/suit/space/hardsuit/ert/engi, /obj/item/clothing/suit/space/hardsuit/ert/med, /obj/item/clothing/suit/space/hardsuit/deathsquad, /obj/item/clothing/head/helmet/space/hardsuit/deathsquad, @@ -119,7 +119,7 @@ SUBSYSTEM_DEF(traumas) /obj/item/clothing/under/rank/command/captain, /obj/item/clothing/under/rank/command/head_of_personnel, /obj/item/clothing/under/rank/security/head_of_security, /obj/item/clothing/under/rank/rnd/research_director, /obj/item/clothing/under/rank/medical/chief_medical_officer, /obj/item/clothing/under/rank/engineering/chief_engineer, - /obj/item/clothing/under/rank/centcom/officer, /obj/item/clothing/under/rank/centcom/commander, + /obj/item/clothing/under/rank/centcom/official, /obj/item/clothing/under/rank/centcom/commander, /obj/item/melee/classic_baton/telescopic, /obj/item/card/id/silver, /obj/item/card/id/gold, /obj/item/card/id/captains_spare, /obj/item/card/id/centcom, /obj/machinery/door/airlock/command)), diff --git a/code/datums/chatmessage.dm b/code/datums/chatmessage.dm index 684ec401e290..0b4b33ce5942 100644 --- a/code/datums/chatmessage.dm +++ b/code/datums/chatmessage.dm @@ -212,6 +212,8 @@ * * spans - Additional classes to be added to the message */ /mob/proc/create_chat_message(atom/movable/speaker, datum/language/message_language, raw_message, list/spans, runechat_flags = NONE) + if(SSlag_switch.measures[DISABLE_RUNECHAT] && !HAS_TRAIT(speaker, TRAIT_BYPASS_MEASURES)) + return // Ensure the list we are using, if present, is a copy so we don't modify the list provided to us spans = spans ? spans.Copy() : list() diff --git a/code/datums/components/footstep.dm b/code/datums/components/footstep.dm index d433e03b6934..95099164eec2 100644 --- a/code/datums/components/footstep.dm +++ b/code/datums/components/footstep.dm @@ -1,3 +1,5 @@ +#define SHOULD_DISABLE_FOOTSTEPS(source) ((SSlag_switch.measures[DISABLE_FOOTSTEPS] && !(HAS_TRAIT(source, TRAIT_BYPASS_MEASURES))) || HAS_TRAIT(source, TRAIT_SILENT_FOOTSTEPS)) + ///Footstep component. Plays footsteps at parents location when it is appropriate. /datum/component/footstep ///How many steps the parent has taken since the last time a footstep was played. @@ -71,6 +73,9 @@ /datum/component/footstep/proc/play_simplestep() SIGNAL_HANDLER + if (SHOULD_DISABLE_FOOTSTEPS(parent)) + return + var/turf/open/T = prepare_step() if(!T) return @@ -94,8 +99,9 @@ /datum/component/footstep/proc/play_humanstep() SIGNAL_HANDLER - if(HAS_TRAIT(parent, TRAIT_SILENT_FOOTSTEPS)) + if (SHOULD_DISABLE_FOOTSTEPS(parent)) return + var/turf/open/T = prepare_step() if(!T) return @@ -115,3 +121,5 @@ GLOB.barefootstep[T.barefootstep][2] * volume, TRUE, GLOB.barefootstep[T.barefootstep][3] + e_range, falloff_distance = 1) + +#undef SHOULD_DISABLE_FOOTSTEPS diff --git a/code/datums/components/pellet_cloud.dm b/code/datums/components/pellet_cloud.dm index d0998c41e5b8..b726489ad2a9 100644 --- a/code/datums/components/pellet_cloud.dm +++ b/code/datums/components/pellet_cloud.dm @@ -47,7 +47,7 @@ var/mob/living/shooter /datum/component/pellet_cloud/Initialize(projectile_type=/obj/item/shrapnel, magnitude=5) - if(!isammocasing(parent) && !isgrenade(parent) && !islandmine(parent)) + if(!isammocasing(parent) && !isgrenade(parent) && !islandmine(parent) && !issupplypod(parent)) return COMPONENT_INCOMPATIBLE if(magnitude < 1) @@ -58,7 +58,7 @@ if(isammocasing(parent)) num_pellets = magnitude - else if(isgrenade(parent) || islandmine(parent)) + else if(isgrenade(parent) || islandmine(parent) || issupplypod(parent)) radius = magnitude /datum/component/pellet_cloud/Destroy(force, silent) @@ -77,6 +77,8 @@ RegisterSignal(parent, COMSIG_GRENADE_PRIME, .proc/create_blast_pellets) else if(islandmine(parent)) RegisterSignal(parent, COMSIG_MINE_TRIGGERED, .proc/create_blast_pellets) + else if(issupplypod(parent)) + RegisterSignal(parent, COMSIG_SUPPLYPOD_LANDED, .proc/create_blast_pellets) /datum/component/pellet_cloud/UnregisterFromParent() UnregisterSignal(parent, list(COMSIG_PARENT_PREQDELETED, COMSIG_PELLET_CLOUD_INIT, COMSIG_GRENADE_PRIME, COMSIG_GRENADE_ARMED, COMSIG_MOVABLE_MOVED, COMSIG_MINE_TRIGGERED, COMSIG_ITEM_DROPPED)) diff --git a/code/datums/diseases/transformation.dm b/code/datums/diseases/transformation.dm index a3884dcf6d3c..6d3959753a9e 100644 --- a/code/datums/diseases/transformation.dm +++ b/code/datums/diseases/transformation.dm @@ -92,67 +92,6 @@ new_mob.ghostize(can_reenter_corpse = FALSE) new_mob.key = null -/datum/disease/transformation/jungle_fever - name = "Jungle Fever" - cure_text = "Death." - cures = list(/datum/reagent/medicine/adminordrazine) - spread_text = "Monkey Bites" - spread_flags = DISEASE_SPREAD_SPECIAL - viable_mobtypes = list(/mob/living/carbon/monkey, /mob/living/carbon/human) - permeability_mod = 1 - cure_chance = 1 - disease_flags = CAN_CARRY|CAN_RESIST - desc = "Monkeys with this disease will bite humans, causing humans to mutate into a monkey." - severity = DISEASE_SEVERITY_BIOHAZARD - stage_prob = 4 - visibility_flags = 0 - agent = "Kongey Vibrion M-909" - new_form = /mob/living/carbon/monkey - bantype = ROLE_MONKEY - - - stage1 = list() - stage2 = list() - stage3 = list() - stage4 = list("Your back hurts.", "You breathe through your mouth.", - "You have a craving for bananas.", "Your mind feels clouded.") - stage5 = list("You feel like monkeying around.") - -/datum/disease/transformation/jungle_fever/do_disease_transformation(mob/living/carbon/affected_mob) - if(affected_mob.mind && !is_monkey(affected_mob.mind)) - add_monkey(affected_mob.mind) - if(ishuman(affected_mob)) - var/mob/living/carbon/monkey/M = affected_mob.monkeyize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_KEEPSE) - M.ventcrawler = VENTCRAWLER_ALWAYS - -/datum/disease/transformation/jungle_fever/stage_act() - ..() - switch(stage) - if(2) - if(prob(2)) - to_chat(affected_mob, "Your [pick("back", "arm", "leg", "elbow", "head")] itches.") - if(3) - if(prob(4)) - to_chat(affected_mob, "You feel a stabbing pain in your head.") - affected_mob.confused += 10 - if(4) - if(prob(3)) - affected_mob.say(pick("Eeek, ook ook!", "Eee-eeek!", "Eeee!", "Ungh, ungh."), forced = "jungle fever") - -/datum/disease/transformation/jungle_fever/cure() - remove_monkey(affected_mob.mind) - ..() - -/datum/disease/transformation/jungle_fever/monkeymode - visibility_flags = HIDDEN_SCANNER|HIDDEN_PANDEMIC - disease_flags = CAN_CARRY //no vaccines! no cure! - -/datum/disease/transformation/jungle_fever/monkeymode/after_add() - if(affected_mob && !is_monkey_leader(affected_mob.mind)) - visibility_flags = NONE - - - /datum/disease/transformation/robot name = "Robotic Transformation" diff --git a/code/datums/saymode.dm b/code/datums/saymode.dm index 1bcc94853456..848940d4e9d9 100644 --- a/code/datums/saymode.dm +++ b/code/datums/saymode.dm @@ -124,25 +124,3 @@ AI.holopad_talk(message, language) return FALSE return TRUE - -/datum/saymode/monkey - key = "k" - mode = MODE_MONKEY - -/datum/saymode/monkey/handle_message(mob/living/user, message, datum/language/language) - var/datum/mind = user.mind - if(!mind) - return TRUE - if(is_monkey_leader(mind) || (ismonkey(user) && is_monkey(mind))) - user.log_talk(message, LOG_SAY, tag="monkey") - if(prob(75) && ismonkey(user)) - user.visible_message("\The [user] chimpers.") - var/msg = "\[[is_monkey_leader(mind) ? "Monkey Leader" : "Monkey"]\] [user]: [message]" - for(var/_M in GLOB.mob_list) - var/mob/M = _M - if(M in GLOB.dead_mob_list) - var/link = FOLLOW_LINK(M, user) - to_chat(M, "[link] [msg]") - if((is_monkey_leader(M.mind) || ismonkey(M)) && (M.mind in SSticker.mode.ape_infectees)) - to_chat(M, msg) - return FALSE diff --git a/code/datums/skills/_skill.dm b/code/datums/skills/_skill.dm index 46c3a1d2bc4d..368a1991a015 100644 --- a/code/datums/skills/_skill.dm +++ b/code/datums/skills/_skill.dm @@ -73,9 +73,9 @@ GLOBAL_LIST_INIT(skill_types, subtypesof(/datum/skill)) to_chat(mind.current, "It seems the Professional [title] Association won't send me another status symbol.") return var/obj/structure/closet/supplypod/bluespacepod/pod = new() - pod.landingDelay = 150 + pod.delays = list(POD_TRANSIT = 15, POD_FALLING = 4, POD_OPENING = 30, POD_LEAVING = 30) pod.explosionSize = list(0,0,0,0) to_chat(mind.current, "My legendary skill has attracted the attention of the Professional [title] Association. It seems they are sending me a status symbol to commemorate my abilities.") var/turf/T = get_turf(mind.current) - new /obj/effect/DPtarget(T, pod , new skill_cape_path(T)) + new /obj/effect/pod_landingzone(T, pod , new skill_cape_path(T)) LAZYADD(mind.skills_rewarded, src.type) diff --git a/code/datums/world_topic.dm b/code/datums/world_topic.dm index 07f93bc431fb..3069a050a04d 100644 --- a/code/datums/world_topic.dm +++ b/code/datums/world_topic.dm @@ -152,8 +152,7 @@ .["version"] = GLOB.game_version .["mode"] = GLOB.master_mode .["respawn"] = config ? !CONFIG_GET(flag/norespawn) : FALSE - .["enter"] = GLOB.enter_allowed - .["vote"] = CONFIG_GET(flag/allow_vote_mode) + .["enter"] = !LAZYACCESS(SSlag_switch.measures, DISABLE_NON_OBSJOBS) .["ai"] = CONFIG_GET(flag/allow_ai) .["host"] = world.host ? world.host : null .["round_id"] = GLOB.round_id diff --git a/code/game/area/areas/centcom.dm b/code/game/area/areas/centcom.dm index a41152d29044..8ca63ad47e4f 100644 --- a/code/game/area/areas/centcom.dm +++ b/code/game/area/areas/centcom.dm @@ -28,7 +28,7 @@ /area/centcom/holding name = "Holding Facility" -/area/centcom/supplypod/flyMeToTheMoon +/area/centcom/supplypod/supplypod_temp_holding name = "Supplypod Shipping lane" icon_state = "supplypod_flight" @@ -37,28 +37,43 @@ icon_state = "supplypod" dynamic_lighting = DYNAMIC_LIGHTING_DISABLED -/area/centcom/supplypod/podStorage +/area/centcom/supplypod/pod_storage name = "Supplypod Storage" icon_state = "supplypod_holding" /area/centcom/supplypod/loading name = "Supplypod Loading Facility" icon_state = "supplypod_loading" + var/loading_id = "" + +/area/centcom/supplypod/loading/Initialize() + . = ..() + if(!loading_id) + CRASH("[type] created without a loading_id") + if(GLOB.supplypod_loading_bays[loading_id]) + CRASH("Duplicate loading bay area: [type] ([loading_id])") + GLOB.supplypod_loading_bays[loading_id] = src /area/centcom/supplypod/loading/one name = "Bay #1" + loading_id = "1" /area/centcom/supplypod/loading/two name = "Bay #2" + loading_id = "2" /area/centcom/supplypod/loading/three name = "Bay #3" + loading_id = "3" /area/centcom/supplypod/loading/four name = "Bay #4" + loading_id = "4" /area/centcom/supplypod/loading/ert name = "ERT Bay" + loading_id = "5" + //THUNDERDOME /area/tdome diff --git a/code/game/atoms.dm b/code/game/atoms.dm index 8152f5d8bd87..3e97c843fd13 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -1656,3 +1656,19 @@ /atom/proc/join_player_here(mob/M) // By default, just place the mob on the same turf as the marker or whatever. M.forceMove(get_turf(src)) + +/* +* Used to set something as 'open' if it's being used as a supplypod +* +* Override this if you want an atom to be usable as a supplypod. +*/ +/atom/proc/setOpened() + return + +/* +* Used to set something as 'closed' if it's being used as a supplypod +* +* Override this if you want an atom to be usable as a supplypod. +*/ +/atom/proc/setClosed() + return diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm index 61ae80615592..29333ce332d4 100644 --- a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm +++ b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm @@ -438,69 +438,6 @@ else objective.find_target() -////////////////////////////////////////////// -// // -// MONKEY // -// // -////////////////////////////////////////////// - -/datum/dynamic_ruleset/roundstart/monkey - name = "Monkey" - antag_flag = ROLE_MONKEY - antag_datum = /datum/antagonist/monkey/leader - restricted_roles = list("Cyborg", "AI", "Prisoner") - required_candidates = 1 - weight = 3 - cost = 0 - requirements = list(101,101,101,101,101,101,101,101,101,101) - high_population_requirement = 101 - var/players_per_carrier = 30 - var/monkeys_to_win = 1 - var/escaped_monkeys = 0 - var/datum/team/monkey/monkey_team - -/datum/dynamic_ruleset/roundstart/monkey/pre_execute() - . = ..() - var/carriers_to_make = max(round(mode.roundstart_pop_ready / players_per_carrier, 1), 1) - mode.antags_rolled += carriers_to_make - - for(var/j = 0, j < carriers_to_make, j++) - if (!candidates.len) - break - var/mob/carrier = pick_n_take(candidates) - assigned += carrier.mind - carrier.mind.special_role = "Monkey Leader" - carrier.mind.restricted_roles = restricted_roles - log_game("[key_name(carrier)] has been selected as a Jungle Fever carrier") - return TRUE - -/datum/dynamic_ruleset/roundstart/monkey/execute() - for(var/datum/mind/carrier in assigned) - var/datum/antagonist/monkey/M = add_monkey_leader(carrier) - if(M) - monkey_team = M.monkey_team - return TRUE - -/datum/dynamic_ruleset/roundstart/monkey/proc/check_monkey_victory() - if(SSshuttle.jump_mode != BS_JUMP_COMPLETED) - return FALSE - var/datum/disease/D = new /datum/disease/transformation/jungle_fever() - for(var/mob/living/carbon/monkey/M in GLOB.alive_mob_list) - if (M.HasDisease(D)) - if(M.onCentCom() || M.onSyndieBase()) - escaped_monkeys++ - if(escaped_monkeys >= monkeys_to_win) - return TRUE - else - return FALSE - -// This does not get called. Look into making it work. -/datum/dynamic_ruleset/roundstart/monkey/round_result() - if(check_monkey_victory()) - SSticker.mode_result = "win - monkey win" - else - SSticker.mode_result = "loss - staff stopped the monkeys" - ////////////////////////////////////////////// // // // METEOR // diff --git a/code/game/gamemodes/monkey/monkey.dm b/code/game/gamemodes/monkey/monkey.dm deleted file mode 100644 index 639f0c5c87b2..000000000000 --- a/code/game/gamemodes/monkey/monkey.dm +++ /dev/null @@ -1,130 +0,0 @@ -/datum/game_mode - var/list/ape_infectees = list() - var/list/ape_leaders = list() - -/datum/game_mode/monkey - name = "monkey" - config_tag = "monkey" - report_type = "monkey" - antag_flag = ROLE_MONKEY - false_report_weight = 1 - - required_players = 20 - required_enemies = 1 - recommended_enemies = 1 - - restricted_jobs = list("Prisoner", "Cyborg", "AI") - - announce_span = "Monkey" - announce_text = "One or more crewmembers have been infected with Jungle Fever! Crew: Contain the outbreak. None of the infected monkeys may escape alive to CentCom. Monkeys: Ensure that your kind lives on! Rise up against your captors!" - - var/carriers_to_make = 1 - var/list/carriers = list() - - var/monkeys_to_win = 1 - var/escaped_monkeys = 0 - - var/players_per_carrier = 30 - - var/datum/team/monkey/monkey_team - - - -/datum/game_mode/monkey/pre_setup() - carriers_to_make = max(round(num_players()/players_per_carrier, 1), 1) - - for(var/j = 0, j < carriers_to_make, j++) - if (!antag_candidates.len) - break - var/datum/mind/carrier = pick(antag_candidates) - carriers += carrier - carrier.special_role = "Monkey Leader" - carrier.restricted_roles = restricted_jobs - log_game("[key_name(carrier)] has been selected as a Jungle Fever carrier") - antag_candidates -= carrier - - if(!carriers.len) - setup_error = "No monkey candidates" - return FALSE - return TRUE - -/datum/game_mode/monkey/post_setup() - for(var/datum/mind/carriermind in carriers) - var/datum/antagonist/monkey/M = add_monkey_leader(carriermind, monkey_team) - if(M) - monkey_team = M.monkey_team - return ..() - -/datum/game_mode/monkey/check_finished() - if(SSshuttle.jump_mode == BS_JUMP_COMPLETED) - return TRUE - - if(!round_converted) - for(var/datum/mind/monkey_mind in ape_infectees) - continuous_sanity_checked = TRUE - if(monkey_mind.current && monkey_mind.current.stat != DEAD) - return FALSE - - var/datum/disease/D = new /datum/disease/transformation/jungle_fever() //ugly but unfortunately needed - for(var/mob/living/carbon/human/H in GLOB.alive_mob_list) - if(H.mind && H.client && H.stat != DEAD) - if(H.HasDisease(D)) - return FALSE - - return ..() - -/datum/game_mode/monkey/proc/check_monkey_victory() - if(SSshuttle.jump_mode != BS_JUMP_COMPLETED) - return FALSE - var/datum/disease/D = new /datum/disease/transformation/jungle_fever() - for(var/mob/living/carbon/monkey/M in GLOB.alive_mob_list) - if (M.HasDisease(D)) - if(M.onCentCom() || M.onSyndieBase()) - escaped_monkeys++ - if(escaped_monkeys >= monkeys_to_win) - return TRUE - else - return FALSE - - -/datum/game_mode/monkey/set_round_result() - ..() - if(check_monkey_victory()) - SSticker.mode_result = "win - monkey win" - else - SSticker.mode_result = "loss - staff stopped the monkeys" - -/datum/game_mode/monkey/special_report() - if(check_monkey_victory()) - return "
The monkeys have overthrown their captors! Eeek eeeek!!
" - else - return "
The staff managed to contain the monkey infestation!
" - -/datum/game_mode/monkey/generate_report() - return "Reports of an ancient [pick("retrovirus", "flesh eating bacteria", "disease", "magical curse blamed on viruses", "banana blight")] outbreak that turn humans into monkeys has been reported in your quadrant. Due to strain mutation, such infections are no longer curable by any known means. If an outbreak occurs, ensure the station is quarantined to prevent a largescale outbreak at CentCom." - -/proc/add_monkey_leader(datum/mind/monkey_mind) - if(is_monkey_leader(monkey_mind)) - return FALSE - var/datum/antagonist/monkey/leader/M = monkey_mind.add_antag_datum(/datum/antagonist/monkey/leader) - return M - -/proc/add_monkey(datum/mind/monkey_mind) - if(is_monkey(monkey_mind)) - return FALSE - var/datum/antagonist/monkey/M = monkey_mind.add_antag_datum(/datum/antagonist/monkey) - return M - -/proc/remove_monkey(datum/mind/monkey_mind) - if(!is_monkey(monkey_mind)) - return FALSE - var/datum/antagonist/monkey/M = monkey_mind.has_antag_datum(/datum/antagonist/monkey) - M.on_removal() - return TRUE - -/proc/is_monkey_leader(datum/mind/monkey_mind) - return monkey_mind && monkey_mind.has_antag_datum(/datum/antagonist/monkey/leader) - -/proc/is_monkey(datum/mind/monkey_mind) - return monkey_mind && (monkey_mind.has_antag_datum(/datum/antagonist/monkey) || is_monkey_leader(monkey_mind)) - diff --git a/code/game/machinery/roulette_machine.dm b/code/game/machinery/roulette_machine.dm index 2cc1dd2dafb3..93cc18456bc1 100644 --- a/code/game/machinery/roulette_machine.dm +++ b/code/game/machinery/roulette_machine.dm @@ -415,7 +415,7 @@ new /obj/machinery/roulette(toLaunch) - new /obj/effect/DPtarget(drop_location(), toLaunch) + new /obj/effect/pod_landingzone(drop_location(), toLaunch) qdel(src) #undef ROULETTE_SINGLES_PAYOUT diff --git a/code/game/machinery/telecomms/broadcasting.dm b/code/game/machinery/telecomms/broadcasting.dm index e3e9534a384f..9f2711ebb7a7 100644 --- a/code/game/machinery/telecomms/broadcasting.dm +++ b/code/game/machinery/telecomms/broadcasting.dm @@ -179,7 +179,14 @@ if(radio.last_chatter_time + 1 SECONDS < world.time && source != radio) playsound(radio, "sound/effects/radio_chatter.ogg", 20, FALSE) radio.last_chatter_time = world.time - //WS edit end + if(radio.log) + var/name = data["name"] + var/list/log_details = list() + log_details["name"] = "[name]▸" + log_details["message"] = "\"[html_decode(message)]\"" + log_details["time"] = station_time_timestamp() + radio.loglist.Insert(1, list(log_details)) + radio.log_trim() // From the list of radios, find all mobs who can hear those. var/list/receive = get_mobs_in_radio_ranges(radios) diff --git a/code/game/machinery/wishgranter.dm b/code/game/machinery/wishgranter.dm deleted file mode 100644 index 2cf51ada2f3e..000000000000 --- a/code/game/machinery/wishgranter.dm +++ /dev/null @@ -1,43 +0,0 @@ -/obj/machinery/wish_granter - name = "wish granter" - desc = "You're not so sure about this, anymore..." - icon = 'icons/obj/device.dmi' - icon_state = "syndbeacon" - - use_power = NO_POWER_USE - density = TRUE - - var/charges = 1 - var/insisting = 0 - -/obj/machinery/wish_granter/attack_hand(mob/living/carbon/user) - . = ..() - if(.) - return - if(charges <= 0) - to_chat(user, "The Wish Granter lies silent.") - return - - else if(!ishuman(user)) - to_chat(user, "You feel a dark stirring inside of the Wish Granter, something you want nothing of. Your instincts are better than any man's.") - return - - else if(is_special_character(user)) - to_chat(user, "Even to a heart as dark as yours, you know nothing good will come of this. Something instinctual makes you pull away.") - - else if (!insisting) - to_chat(user, "Your first touch makes the Wish Granter stir, listening to you. Are you really sure you want to do this?") - insisting++ - - else - to_chat(user, "You speak. [pick("I want the sector to disappear","Humanity is corrupt, mankind must be destroyed","I want to be rich", "I want to rule the world","I want immortality.")]. The Wish Granter answers.") - to_chat(user, "Your head pounds for a moment, before your vision clears. You are the avatar of the Wish Granter, and your power is LIMITLESS! And it's all yours. You need to make sure no one can take it from you. No one can know, first.") - - charges-- - insisting = 0 - - user.mind.add_antag_datum(/datum/antagonist/wishgranter) - - to_chat(user, "You have a very bad feeling about this.") - - return diff --git a/code/game/mecha/mech_fabricator.dm b/code/game/mecha/mech_fabricator.dm index c547b9385296..160b6a27ab3d 100644 --- a/code/game/mecha/mech_fabricator.dm +++ b/code/game/mecha/mech_fabricator.dm @@ -12,7 +12,7 @@ var/time_coeff = 1 var/component_coeff = 1 var/datum/techweb/specialized/autounlocking/exofab/stored_research - var/sync = 0 + var/linked_to_server = FALSE //if a server is linked to the exofab var/part_set var/datum/design/being_built var/list/queue = list() @@ -113,11 +113,11 @@ var/output output += "
Mecha Fabricator
" output += "Security protocols: [(obj_flags & EMAGGED)? "Disabled" : "Enabled"]
" + output += "Linked to server: [(linked_to_server == FALSE)? "Unlinked" : "Linked"]
" if (rmat.mat_container) output += "Material Amount: [rmat.format_amount()]" else output += "No material storage connected, please contact the quartermaster." - output += "
Sync with R&D servers
" output += "Main Screen" output += "
" output += "
\ @@ -277,17 +277,6 @@ output += "Process queue | Clear queue" return output -/obj/machinery/mecha_part_fabricator/proc/sync() - for(var/obj/machinery/computer/rdconsole/RDC in oview(7,src)) - RDC.stored_research.copy_research_to(stored_research) - updateUsrDialog() - say("Successfully synchronized with R&D server.") - return - - temp = "Unable to connect to local R&D Database.
Please check your connections and try again.
Return" - updateUsrDialog() - return - /obj/machinery/mecha_part_fabricator/proc/get_resource_cost_w_coeff(datum/design/D, datum/material/resource, roundto = 1) return round(D.materials[resource]*component_coeff, roundto) @@ -412,8 +401,6 @@ if(href_list["clear_queue"]) queue = list() return update_queue_on_page() - if(href_list["sync"]) - sync() if(href_list["part_desc"]) var/T = href_list["part_desc"] for(var/v in stored_research.researched_designs) @@ -471,7 +458,15 @@ if(default_deconstruction_crowbar(W)) return TRUE - return ..() + if(istype(W, /obj/item/multitool)) + var/obj/item/multitool/multi = W + if(multi.buffer && istype(multi.buffer, /obj/machinery/rnd/server) && multi.buffer != src) + var/obj/machinery/rnd/server/server = multi.buffer + stored_research = server.stored_research + visible_message("Linked to [server]!") + linked_to_server = TRUE + else + return ..() /obj/machinery/mecha_part_fabricator/proc/is_insertion_ready(mob/user) diff --git a/code/game/objects/effects/misc.dm b/code/game/objects/effects/misc.dm index f9f7d19d161f..cc7cf2eb3d0b 100644 --- a/code/game/objects/effects/misc.dm +++ b/code/game/objects/effects/misc.dm @@ -40,10 +40,6 @@ density = TRUE layer = FLY_LAYER -/obj/effect/supplypod_selector - icon_state = "supplypod_selector" - layer = FLY_LAYER - //Makes a tile fully lit no matter what /obj/effect/fullbright icon = 'icons/effects/alphacolors.dmi' diff --git a/code/game/objects/items/bodybag.dm b/code/game/objects/items/bodybag.dm index 2013c1e4e77e..faf1f22a651a 100644 --- a/code/game/objects/items/bodybag.dm +++ b/code/game/objects/items/bodybag.dm @@ -5,6 +5,7 @@ icon = 'icons/obj/bodybag.dmi' icon_state = "bodybag_folded" w_class = WEIGHT_CLASS_SMALL + custom_materials = list(/datum/material/plastic = 4000) var/unfoldedbag_path = /obj/structure/closet/body_bag /obj/item/bodybag/attack_self(mob/user) diff --git a/code/game/objects/items/devices/radio/intercom.dm b/code/game/objects/items/devices/radio/intercom.dm index 35d8be6efa55..3c35294f8e3f 100644 --- a/code/game/objects/items/devices/radio/intercom.dm +++ b/code/game/objects/items/devices/radio/intercom.dm @@ -167,6 +167,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/item/radio/intercom, 31) frequency = FREQ_WIDEBAND freqlock = TRUE freerange = TRUE + log = TRUE wallframe = /obj/item/wallframe/intercom/wideband /obj/item/radio/intercom/wideband/Initialize(mapload, ndir, building) diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm index a46f6e2ea55d..2a5a043656c2 100644 --- a/code/game/objects/items/devices/radio/radio.dm +++ b/code/game/objects/items/devices/radio/radio.dm @@ -34,6 +34,8 @@ var/freqlock = FALSE // Frequency lock to stop the user from untuning specialist radios. var/use_command = FALSE // If true, broadcasts will be large and BOLD. var/command = FALSE // If true, use_command can be toggled at will. + var/log = FALSE // If true, the UI will display the voice log for the frequency + var/list/loglist = list() //the voice log // Encryption key handling var/obj/item/encryptionkey/keyslot @@ -140,6 +142,8 @@ data["useCommand"] = use_command data["subspace"] = subspace_transmission data["subspaceSwitchable"] = subspace_switchable + data["chatlog"] = log + data["chatloglist"] = loglist data["headset"] = FALSE return data @@ -372,6 +376,11 @@ on = TRUE return TRUE +/obj/item/radio/proc/log_trim() + if(loglist.len <= 50) + return + loglist.Cut(51) + /////////////////////////////// //////////Borg Radios////////// /////////////////////////////// diff --git a/code/game/objects/items/miscellaneous.dm b/code/game/objects/items/miscellaneous.dm index 8ae9a34e2dee..487d5d2c96ca 100644 --- a/code/game/objects/items/miscellaneous.dm +++ b/code/game/objects/items/miscellaneous.dm @@ -61,7 +61,7 @@ msg = "You hear something crackle in your ears for a moment before a voice speaks. \"Please stand by for a message from Central Command. Message as follows: Item request received. Your package is inbound, please stand back from the landing site. Message ends.\"" to_chat(M, msg) - new /obj/effect/DPtarget(get_turf(src), pod) + new /obj/effect/pod_landingzone(get_turf(src), pod) /obj/item/choice_beacon/hero name = "heroic beacon" diff --git a/code/game/objects/items/storage/uplink_kits.dm b/code/game/objects/items/storage/uplink_kits.dm index b02a516dc13b..6b3658b523df 100644 --- a/code/game/objects/items/storage/uplink_kits.dm +++ b/code/game/objects/items/storage/uplink_kits.dm @@ -534,7 +534,7 @@ new /obj/item/book/granter/spell/mimery_guns(src) /obj/item/storage/box/syndie_kit/centcom_costume/PopulateContents() - new /obj/item/clothing/under/rank/centcom/officer(src) + new /obj/item/clothing/under/rank/centcom/official(src) new /obj/item/clothing/shoes/sneakers/black(src) new /obj/item/clothing/gloves/color/black(src) new /obj/item/radio/headset/headset_cent/empty(src) diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm index 137af446fa15..25ad21d25d20 100644 --- a/code/game/objects/structures/crates_lockers/closets.dm +++ b/code/game/objects/structures/crates_lockers/closets.dm @@ -71,6 +71,8 @@ /obj/structure/closet/update_icon() . = ..() + if (istype(src, /obj/structure/closet/supplypod)) + return layer = opened ? BELOW_OBJ_LAYER : OBJ_LAYER @@ -340,6 +342,11 @@ var/mob/living/L = O if(!issilicon(L)) L.Paralyze(40) + if(istype(src, /obj/structure/closet/supplypod/extractionpod)) + O.forceMove(src) + else + O.forceMove(T) + close() O.forceMove(T) close() else diff --git a/code/game/world.dm b/code/game/world.dm index dcae8e237bd6..8377932887c9 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -289,10 +289,7 @@ GLOBAL_VAR(restart_counter) var/list/features = list() - if(GLOB.master_mode) - features += GLOB.master_mode - - if (!GLOB.enter_allowed) + if(LAZYACCESS(SSlag_switch.measures, DISABLE_NON_OBSJOBS)) features += "closed" var/s = "" diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index 146eb808129f..a530012270ae 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -624,15 +624,12 @@ set category = "Server" set desc="People can't enter" set name="Toggle Entering" - GLOB.enter_allowed = !(GLOB.enter_allowed) - if (!(GLOB.enter_allowed)) - to_chat(world, "New players may no longer enter the game.", confidential = TRUE) - else - to_chat(world, "New players may now enter the game.", confidential = TRUE) - log_admin("[key_name(usr)] toggled new player game entering.") - message_admins("[key_name_admin(usr)] toggled new player game entering.") - world.update_status() - SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Entering", "[GLOB.enter_allowed ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + if(!SSlag_switch.initialized) + return + SSlag_switch.set_measure(DISABLE_NON_OBSJOBS, !SSlag_switch.measures[DISABLE_NON_OBSJOBS]) + log_admin("[key_name(usr)] toggled new player game entering. Lag Switch at index ([DISABLE_NON_OBSJOBS])") + message_admins("[key_name_admin(usr)] toggled new player game entering [SSlag_switch.measures[DISABLE_NON_OBSJOBS] ? "OFF" : "ON"].") + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Toggle Entering", "[!SSlag_switch.measures[DISABLE_NON_OBSJOBS] ? "Enabled" : "Disabled"]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! /datum/admins/proc/toggleAI() set category = "Server" @@ -750,7 +747,7 @@ var/obj/structure/closet/supplypod/centcompod/pod = new() var/atom/A = new chosen(pod) A.flags_1 |= ADMIN_SPAWNED_1 - new /obj/effect/DPtarget(T, pod) + new /obj/effect/pod_landingzone(T, pod) log_admin("[key_name(usr)] pod-spawned [chosen] at [AREACOORD(usr)]") SSblackbox.record_feedback("tally", "admin_verb", 1, "Podspawn Atom") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! @@ -989,3 +986,35 @@ "Admin login: [key_name(src)]") if(string) message_admins("[string]") + +/datum/admins/proc/show_lag_switch_panel() + set category = "Admin.Game" + set name = "Show Lag Switches" + set desc="Display the controls for drastic lag mitigation measures." + + if(!SSlag_switch.initialized) + to_chat(usr, span_notice("The Lag Switch subsystem has not yet been initialized.")) + return + if(!check_rights()) + return + + var/list/dat = list("Lag Switches

Lag (Reduction) Switches

") + dat += "Automatic Trigger: [SSlag_switch.auto_switch ? "On" : "Off"]
" + dat += "Population Threshold: [SSlag_switch.trigger_pop]
" + dat += "Slowmode Cooldown (toggle On/Off below): [SSlag_switch.slowmode_cooldown/10] seconds
" + dat += "
SET ALL MEASURES: ON | OFF
" + dat += "
Disable ghosts zoom and t-ray verbs (except staff): [SSlag_switch.measures[DISABLE_GHOST_ZOOM_TRAY] ? "On" : "Off"]
" + dat += "Disable planet deletion: [SSlag_switch.measures[DISABLE_PLANETDEL] ? "On" : "Off"]
" + dat += "Disable ALL planet GENERATION: [SSlag_switch.measures[DISABLE_PLANETGEN] ? "On" : "Off"]
" + dat += "Disable late joining: [SSlag_switch.measures[DISABLE_NON_OBSJOBS] ? "On" : "Off"]
" + dat += "
============! MAD GHOSTS ZONE !============
" + dat += "Disable deadmob keyLoop (except staff, informs dchat): [SSlag_switch.measures[DISABLE_DEAD_KEYLOOP] ? "On" : "Off"]
" + dat += "==========================================
" + dat += "
Measures below can be bypassed with a special trait
" + dat += "Slowmode say verb (informs world): [SSlag_switch.measures[SLOWMODE_SAY] ? "On" : "Off"]
" + dat += "Disable runechat: [SSlag_switch.measures[DISABLE_RUNECHAT] ? "On" : "Off"] - trait applies to speaker
" + dat += "Disable examine icons: [SSlag_switch.measures[DISABLE_USR_ICON2HTML] ? "On" : "Off"] - trait applies to examiner
" + dat += "Disable parallax: [SSlag_switch.measures[DISABLE_PARALLAX] ? "On" : "Off"] - trait applies to character
" + dat += "Disable footsteps: [SSlag_switch.measures[DISABLE_FOOTSTEPS] ? "On" : "Off"] - trait applies to character
" + dat += "" + usr << browse(dat.Join(), "window=lag_switch_panel;size=420x480") diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index ecaa96a572ec..6f1809098f9d 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -36,6 +36,7 @@ GLOBAL_PROTECT(admin_verbs_admin) /client/proc/invisimin, /*allows our mob to go invisible/visible*/ // /datum/admins/proc/show_traitor_panel, /*interface which shows a mob's mind*/ -Removed due to rare practical use. Moved to debug verbs ~Errorage /datum/admins/proc/show_player_panel, /*shows an interface for individual players, with various links (links require additional flags)*/ + /datum/admins/proc/show_lag_switch_panel, /datum/verbs/menu/Admin/verb/playerpanel, /client/proc/game_panel, /*game panel, allows to change game-mode etc*/ /client/proc/check_ai_laws, /*shows AI and borg laws*/ diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index 3dc626b50287..32840115224c 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -1566,7 +1566,7 @@ R.activate_module(I) if(pod) - new /obj/effect/DPtarget(target, pod) + new /obj/effect/pod_landingzone(target, pod) if (number == 1) log_admin("[key_name(usr)] created a [english_list(paths)]") @@ -1877,6 +1877,58 @@ SSticker.mode.station_goals += G modify_goals() + else if(href_list["change_lag_switch"]) + if(!check_rights(R_ADMIN)) + return + + switch(href_list["change_lag_switch"]) + if("ALL_ON") + SSlag_switch.set_all_measures(TRUE) + log_admin("[key_name(usr)] turned all Lag Switch measures ON.") + message_admins("[key_name_admin(usr)] turned all Lag Switch measures ON.") + if("ALL_OFF") + SSlag_switch.set_all_measures(FALSE) + log_admin("[key_name(usr)] turned all Lag Switch measures OFF.") + message_admins("[key_name_admin(usr)] turned all Lag Switch measures OFF.") + else + var/switch_index = text2num(href_list["change_lag_switch"]) + if(!SSlag_switch.set_measure(switch_index, !LAZYACCESS(SSlag_switch.measures, switch_index))) + to_chat(src, span_danger("Something went wrong when trying to toggle that Lag Switch. Check runtimes for more info."), confidential = TRUE) + else + log_admin("[key_name(usr)] turned a Lag Switch measure at index ([switch_index]) [LAZYACCESS(SSlag_switch.measures, switch_index) ? "ON" : "OFF"]") + message_admins("[key_name_admin(usr)] turned a Lag Switch measure [LAZYACCESS(SSlag_switch.measures, switch_index) ? "ON" : "OFF"]") + + src.show_lag_switch_panel() + + else if(href_list["change_lag_switch_option"]) + if(!check_rights(R_ADMIN)) + return + + switch(href_list["change_lag_switch_option"]) + if("CANCEL") + if(SSlag_switch.cancel_auto_enable_in_progress()) + log_admin("[key_name(usr)] canceled the automatic Lag Switch activation in progress.") + message_admins("[key_name_admin(usr)] canceled the automatic Lag Switch activation in progress.") + return // return here to avoid (re)rendering the panel for this case + if("TOGGLE_AUTO") + SSlag_switch.toggle_auto_enable() + log_admin("[key_name(usr)] toggled automatic Lag Switch activation [SSlag_switch.auto_switch ? "ON" : "OFF"].") + message_admins("[key_name_admin(usr)] toggled automatic Lag Switch activation [SSlag_switch.auto_switch ? "ON" : "OFF"].") + if("NUM") + var/new_num = input("Enter new threshold value:", "Num") as null|num + if(!isnull(new_num)) + SSlag_switch.trigger_pop = new_num + log_admin("[key_name(usr)] set the Lag Switch automatic trigger pop to [new_num].") + message_admins("[key_name_admin(usr)] set the Lag Switch automatic trigger pop to [new_num].") + if("SLOWCOOL") + var/new_num = input("Enter new cooldown in seconds:", "Num") as null|num + if(!isnull(new_num)) + SSlag_switch.change_slowmode_cooldown(new_num) + log_admin("[key_name(usr)] set the Lag Switch slowmode cooldown to [new_num] seconds.") + message_admins("[key_name_admin(usr)] set the Lag Switch slowmode cooldown to [new_num] seconds.") + + src.show_lag_switch_panel() + else if(href_list["viewruntime"]) var/datum/error_viewer/error_viewer = locate(href_list["viewruntime"]) if(!istype(error_viewer)) diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm index 764ca191cb98..a6c339303517 100644 --- a/code/modules/admin/verbs/randomverbs.dm +++ b/code/modules/admin/verbs/randomverbs.dm @@ -933,7 +933,7 @@ alert("ERROR: Incorrect / improper path given.") return new delivery(pod) - new /obj/effect/DPtarget(get_turf(target), pod) + new /obj/effect/pod_landingzone(get_turf(target), pod) if(ADMIN_PUNISHMENT_SUPPLYPOD) var/datum/centcom_podlauncher/plaunch = new(usr) if(!holder) diff --git a/code/modules/antagonists/monkey/monkey.dm b/code/modules/antagonists/monkey/monkey.dm deleted file mode 100644 index ea83998abaac..000000000000 --- a/code/modules/antagonists/monkey/monkey.dm +++ /dev/null @@ -1,213 +0,0 @@ -#define MONKEYS_ESCAPED 1 -#define MONKEYS_LIVED 2 -#define MONKEYS_DIED 3 -#define DISEASE_LIVED 4 - -/datum/antagonist/monkey - name = "Monkey" - job_rank = ROLE_MONKEY - roundend_category = "monkeys" - antagpanel_category = "Monkey" - show_to_ghosts = TRUE - var/datum/team/monkey/monkey_team - var/monkey_only = TRUE - -/datum/antagonist/monkey/can_be_owned(datum/mind/new_owner) - return ..() && (!monkey_only || ismonkey(new_owner.current)) - -/datum/antagonist/monkey/get_team() - return monkey_team - -/datum/antagonist/monkey/on_gain() - . = ..() - SSticker.mode.ape_infectees += owner - owner.special_role = "Infected Monkey" - - var/datum/disease/D = new /datum/disease/transformation/jungle_fever/monkeymode - if(!owner.current.HasDisease(D)) - owner.current.ForceContractDisease(D) - else - QDEL_NULL(D) - -/datum/antagonist/monkey/greet() - to_chat(owner, "You are a monkey now!") - to_chat(owner, "Bite humans to infect them, follow the orders of the monkey leaders, and help fellow monkeys!") - to_chat(owner, "Ensure at least one infected monkey escapes on the Emergency Shuttle!") - to_chat(owner, "As an intelligent monkey, you know how to use technology and how to ventcrawl while wearing things.") - to_chat(owner, "You can use :k to talk to fellow monkeys!") - SEND_SOUND(owner.current, sound('sound/ambience/antag/monkey.ogg')) - -/datum/antagonist/monkey/on_removal() - owner.special_role = null - SSticker.mode.ape_infectees -= owner - - var/datum/disease/transformation/jungle_fever/D = locate() in owner.current.diseases - if(D) - qdel(D) - - . = ..() - -/datum/antagonist/monkey/create_team(datum/team/monkey/new_team) - if(!new_team) - for(var/datum/antagonist/monkey/H in GLOB.antagonists) - if(!H.owner) - continue - if(H.monkey_team) - monkey_team = H.monkey_team - return - monkey_team = new /datum/team/monkey - monkey_team.update_objectives() - return - if(!istype(new_team)) - stack_trace("Wrong team type passed to [type] initialization.") - monkey_team = new_team - -/datum/antagonist/monkey/proc/forge_objectives() - objectives |= monkey_team.objectives - -/datum/antagonist/monkey/admin_remove(mob/admin) - var/mob/living/carbon/monkey/M = owner.current - if(istype(M)) - switch(alert(admin, "Humanize?", "Humanize", "Yes", "No")) - if("Yes") - if(admin == M) - admin = M.humanize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_DEFAULTMSG) - else - M.humanize(TR_KEEPITEMS | TR_KEEPIMPLANTS | TR_KEEPORGANS | TR_KEEPDAMAGE | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_DEFAULTMSG) - if("No") - //nothing - else - return - . = ..() - -/datum/antagonist/monkey/leader - name = "Monkey Leader" - monkey_only = FALSE - -/datum/antagonist/monkey/leader/admin_add(datum/mind/new_owner,mob/admin) - var/mob/living/carbon/human/H = new_owner.current - if(istype(H)) - switch(alert(admin, "Monkeyize?", "Monkeyize", "Yes", "No")) - if("Yes") - if(admin == H) - admin = H.monkeyize() - else - H.monkeyize() - if("No") - //nothing - else - return - new_owner.add_antag_datum(src) - log_admin("[key_name(admin)] made [key_name(new_owner)] a monkey leader!") - message_admins("[key_name_admin(admin)] made [key_name_admin(new_owner)] a monkey leader!") - -/datum/antagonist/monkey/leader/on_gain() - . = ..() - var/obj/item/organ/heart/freedom/F = new - F.Insert(owner.current, drop_if_replaced = FALSE) - SSticker.mode.ape_leaders += owner - owner.special_role = "Monkey Leader" - -/datum/antagonist/monkey/leader/on_removal() - SSticker.mode.ape_leaders -= owner - var/obj/item/organ/heart/H = new - H.Insert(owner.current, drop_if_replaced = FALSE) //replace freedom heart with normal heart - - . = ..() - -/datum/antagonist/monkey/leader/greet() - to_chat(owner, "You are the Jungle Fever patient zero!!") - to_chat(owner, "You have been planted onto this station by the Animal Rights Consortium.") - to_chat(owner, "Soon the disease will transform you into an ape. Afterwards, you will be able spread the infection to others with a bite.") - to_chat(owner, "While your infection strain is undetectable by scanners, any other infectees will show up on medical equipment.") - to_chat(owner, "Your mission will be deemed a success if any of the live infected monkeys reach CentCom.") - to_chat(owner, "As an initial infectee, you will be considered a 'leader' by your fellow monkeys.") - to_chat(owner, "You can use :k to talk to fellow monkeys!") - SEND_SOUND(owner.current, sound('sound/ambience/antag/monkey.ogg')) - -/datum/objective/monkey - explanation_text = "Ensure that infected monkeys escape on the emergency shuttle!" - martyr_compatible = TRUE - var/monkeys_to_win = 1 - var/escaped_monkeys = 0 - -/datum/objective/monkey/check_completion() - var/datum/disease/D = new /datum/disease/transformation/jungle_fever() - for(var/mob/living/carbon/monkey/M in GLOB.alive_mob_list) - if (M.HasDisease(D) && (M.onCentCom() || M.onSyndieBase())) - escaped_monkeys++ - if(escaped_monkeys >= monkeys_to_win) - return TRUE - return FALSE - -/datum/team/monkey - name = "Monkeys" - -/datum/team/monkey/proc/update_objectives() - objectives = list() - var/datum/objective/monkey/O = new() - O.team = src - objectives += O - -/datum/team/monkey/proc/infected_monkeys_alive() - var/datum/disease/D = new /datum/disease/transformation/jungle_fever() - for(var/mob/living/carbon/monkey/M in GLOB.alive_mob_list) - if(M.HasDisease(D)) - return TRUE - return FALSE - -/datum/team/monkey/proc/infected_monkeys_escaped() - var/datum/disease/D = new /datum/disease/transformation/jungle_fever() - for(var/mob/living/carbon/monkey/M in GLOB.alive_mob_list) - if(M.HasDisease(D) && (M.onCentCom() || M.onSyndieBase())) - return TRUE - return FALSE - -/datum/team/monkey/proc/infected_humans_escaped() - var/datum/disease/D = new /datum/disease/transformation/jungle_fever() - for(var/mob/living/carbon/human/M in GLOB.alive_mob_list) - if(M.HasDisease(D) && (M.onCentCom() || M.onSyndieBase())) - return TRUE - return FALSE - -/datum/team/monkey/proc/infected_humans_alive() - var/datum/disease/D = new /datum/disease/transformation/jungle_fever() - for(var/mob/living/carbon/human/M in GLOB.alive_mob_list) - if(M.HasDisease(D)) - return TRUE - return FALSE - -/datum/team/monkey/proc/get_result() - if(infected_monkeys_escaped()) - return MONKEYS_ESCAPED - if(infected_monkeys_alive()) - return MONKEYS_LIVED - if(infected_humans_alive() || infected_humans_escaped()) - return DISEASE_LIVED - return MONKEYS_DIED - -/datum/team/monkey/roundend_report() - var/list/parts = list() - switch(get_result()) - if(MONKEYS_ESCAPED) - parts += "Monkey Major Victory!" - parts += "Central Command and [station_name()] were taken over by the monkeys! Ook ook!" - if(MONKEYS_LIVED) - parts += "Monkey Minor Victory!" - parts += "[station_name()] was taken over by the monkeys! Ook ook!" - if(DISEASE_LIVED) - parts += "Monkey Minor Defeat!" - parts += "All the monkeys died, but the disease lives on! The future is uncertain." - if(MONKEYS_DIED) - parts += "Monkey Major Defeat!" - parts += "All the monkeys died, and Jungle Fever was wiped out!" - var/list/leaders = get_antag_minds(/datum/antagonist/monkey/leader, TRUE) - var/list/monkeys = get_antag_minds(/datum/antagonist/monkey, TRUE) - - if(LAZYLEN(leaders)) - parts += "The monkey leaders were:" - parts += printplayerlist(SSticker.mode.ape_leaders) - if(LAZYLEN(monkeys)) - parts += "The monkeys were:" - parts += printplayerlist(SSticker.mode.ape_infectees) - return "
[parts.Join("
")]
" diff --git a/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm b/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm index c3595010981f..9aaa8b989c0d 100644 --- a/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm +++ b/code/modules/antagonists/nukeop/equipment/nuclearbomb.dm @@ -467,7 +467,7 @@ SSticker.roundend_check_paused = FALSE return - GLOB.enter_allowed = FALSE + SSlag_switch.set_measure(DISABLE_NON_OBSJOBS, TRUE) var/off_station = 0 var/turf/bomb_location = get_turf(src) diff --git a/code/modules/antagonists/traitor/equipment/contractor.dm b/code/modules/antagonists/traitor/equipment/contractor.dm index 121430252ef0..b1d68a719070 100644 --- a/code/modules/antagonists/traitor/equipment/contractor.dm +++ b/code/modules/antagonists/traitor/equipment/contractor.dm @@ -229,7 +229,7 @@ to_chat(partner_mind.current, "\n[user.real_name] is your superior. Follow any, and all orders given by them. You're here to support their mission only.") to_chat(partner_mind.current, "Should they perish, or be otherwise unavailable, you're to assist other active agents in this mission area to the best of your ability.\n\n") - new /obj/effect/DPtarget(free_location, arrival_pod) + new /obj/effect/pod_landingzone(free_location, arrival_pod) /datum/contractor_item/blackout name = "Blackout" diff --git a/code/modules/antagonists/traitor/syndicate_contract.dm b/code/modules/antagonists/traitor/syndicate_contract.dm index 977cab2987dc..17e841acb5e3 100644 --- a/code/modules/antagonists/traitor/syndicate_contract.dm +++ b/code/modules/antagonists/traitor/syndicate_contract.dm @@ -68,7 +68,7 @@ empty_pod.explosionSize = list(0,0,0,1) empty_pod.leavingSound = 'sound/effects/podwoosh.ogg' - new /obj/effect/DPtarget(empty_pod_turf, empty_pod) + new /obj/effect/pod_landingzone(empty_pod_turf, empty_pod) /datum/syndicate_contract/proc/enter_check(datum/source, sent_mob) if (istype(source, /obj/structure/closet/supplypod/extractionpod)) @@ -111,7 +111,7 @@ var/obj/structure/closet/supplypod/extractionpod/pod = source // Handle the pod returning - pod.send_up(pod) + pod.startExitSequence(pod) if (ishuman(M)) var/mob/living/carbon/human/target = M @@ -226,7 +226,7 @@ M.Dizzy(35) M.confused += 20 - new /obj/effect/DPtarget(possible_drop_loc[pod_rand_loc], return_pod) + new /obj/effect/pod_landingzone(possible_drop_loc[pod_rand_loc], return_pod) else to_chat(M, "A million voices echo in your head... \"Seems where you got sent here from won't \ be able to handle our pod... You will die here instead.\"") diff --git a/code/modules/antagonists/wishgranter/wishgranter.dm b/code/modules/antagonists/wishgranter/wishgranter.dm deleted file mode 100644 index 67a6153f7bb2..000000000000 --- a/code/modules/antagonists/wishgranter/wishgranter.dm +++ /dev/null @@ -1,29 +0,0 @@ -/datum/antagonist/wishgranter - name = "Wishgranter Avatar" - show_in_antagpanel = FALSE - show_name_in_check_antagonists = TRUE - hijack_speed = 2 //You literally are here to do nothing else. Might as well be fast about it. - -/datum/antagonist/wishgranter/proc/forge_objectives() - var/datum/objective/hijack/hijack = new - hijack.owner = owner - objectives += hijack - -/datum/antagonist/wishgranter/on_gain() - owner.special_role = "Avatar of the Wish Granter" - forge_objectives() - . = ..() - give_powers() - -/datum/antagonist/wishgranter/greet() - to_chat(owner, "Your inhibitions are swept away, the bonds of loyalty broken, you are free to murder as you please!") - owner.announce_objectives() - -/datum/antagonist/wishgranter/proc/give_powers() - var/mob/living/carbon/human/H = owner.current - if(!istype(H)) - return - H.dna.add_mutation(HULK) - H.dna.add_mutation(XRAY) - H.dna.add_mutation(SPACEMUT) - H.dna.add_mutation(TK) diff --git a/code/modules/asset_cache/asset_list_items.dm b/code/modules/asset_cache/asset_list_items.dm index 505c84db67fd..feb2fd160992 100644 --- a/code/modules/asset_cache/asset_list_items.dm +++ b/code/modules/asset_cache/asset_list_items.dm @@ -438,3 +438,37 @@ "fishing_background_default" = 'icons/ui_icons/fishing/default.png', "fishing_background_lavaland" = 'icons/ui_icons/fishing/lavaland.png' ) + +/datum/asset/spritesheet/supplypods + name = "supplypods" + +/datum/asset/spritesheet/supplypods/register() + for (var/style in 1 to length(GLOB.podstyles)) + var/icon_file = 'icons/obj/supplypods.dmi' + var/states = icon_states(icon_file) + if (style == STYLE_SEETHROUGH) + Insert("pod_asset[style]", icon(icon_file, "seethrough-icon", SOUTH)) + continue + var/base = GLOB.podstyles[style][POD_BASE] + if (!base) + Insert("pod_asset[style]", icon(icon_file, "invisible-icon", SOUTH)) + continue + var/icon/podIcon = icon(icon_file, base, SOUTH) + var/door = GLOB.podstyles[style][POD_DOOR] + if (door) + door = "[base]_door" + if(door in states) + podIcon.Blend(icon(icon_file, door, SOUTH), ICON_OVERLAY) + var/shape = GLOB.podstyles[style][POD_SHAPE] + if (shape == POD_SHAPE_NORML) + var/decal = GLOB.podstyles[style][POD_DECAL] + if (decal) + if(decal in states) + podIcon.Blend(icon(icon_file, decal, SOUTH), ICON_OVERLAY) + var/glow = GLOB.podstyles[style][POD_GLOW] + if (glow) + glow = "pod_glow_[glow]" + if(glow in states) + podIcon.Blend(icon(icon_file, glow, SOUTH), ICON_OVERLAY) + Insert("pod_asset[style]", podIcon) + return ..() diff --git a/code/modules/awaymissions/corpse.dm b/code/modules/awaymissions/corpse.dm index 4c4fac2717e5..0caf1d7c4e3d 100644 --- a/code/modules/awaymissions/corpse.dm +++ b/code/modules/awaymissions/corpse.dm @@ -486,7 +486,7 @@ /datum/outfit/nanotrasenbridgeofficercorpse name = "Bridge Officer Corpse" ears = /obj/item/radio/headset/heads/head_of_personnel - uniform = /obj/item/clothing/under/rank/centcom/officer + uniform = /obj/item/clothing/under/rank/centcom/official suit = /obj/item/clothing/suit/armor/vest/bulletproof shoes = /obj/item/clothing/shoes/sneakers/black glasses = /obj/item/clothing/glasses/sunglasses diff --git a/code/modules/awaymissions/mission_code/wildwest.dm b/code/modules/awaymissions/mission_code/wildwest.dm index e9f8df5c3499..35396ddded18 100644 --- a/code/modules/awaymissions/mission_code/wildwest.dm +++ b/code/modules/awaymissions/mission_code/wildwest.dm @@ -4,116 +4,6 @@ * Meat Grinder */ -//Areas - -/area/awaymission/wildwest/mines - name = "Wild West Mines" - icon_state = "away1" - requires_power = FALSE - -/area/awaymission/wildwest/gov - name = "Wild West Mansion" - icon_state = "away2" - requires_power = FALSE - -/area/awaymission/wildwest/refine - name = "Wild West Refinery" - icon_state = "away3" - requires_power = FALSE - -/area/awaymission/wildwest/vault - name = "Wild West Vault" - icon_state = "away3" - -/area/awaymission/wildwest/vaultdoors - name = "Wild West Vault Doors" // this is to keep the vault area being entirely lit because of requires_power - icon_state = "away2" - requires_power = FALSE - - -////////// wildwest papers - -/obj/item/paper/fluff/awaymissions/wildwest/grinder - default_raw_text = "meat grinder requires sacri" - - -/obj/item/paper/fluff/awaymissions/wildwest/journal/page1 - name = "Planer Saul's Journal: Page 1" - default_raw_text = "We've discovered something floating in space. We can't really tell how old it is, but it is scraped and bent to hell. There object is the size of about a room with double doors that we have yet to break into. It is a lot sturdier than we could have imagined. We have decided to call it 'The Vault' " - -/obj/item/paper/fluff/awaymissions/wildwest/journal/page4 - name = "Planer Saul's Journal: Page 4" - default_raw_text = " The miners in the town have become sick and almost all production has stopped. They, in a fit of delusion, tossed all of their mining equipment into the furnaces. They all claimed the same thing. A voice beckoning them to lay down their arms. Stupid miners." - -/obj/item/paper/fluff/awaymissions/wildwest/journal/page7 - name = "Planer Sauls' Journal: Page 7" - default_raw_text = "The Vault...it just keeps growing and growing. I went on my daily walk through the garden and now it's just right outside the mansion... a few days ago it was only barely visible. But whatever is inside...it's calling to me." - -/obj/item/paper/fluff/awaymissions/wildwest/journal/page8 - name = "Planer Saul's Journal: Page 8" - default_raw_text = "The syndicate have invaded. Their ships appeared out of nowhere and now they likely intend to kill us all and take everything. On the off-chance that the Vault may grant us sanctuary, many of us have decided to force our way inside and bolt the door, taking as many provisions with us as we can carry. In case you find this, send for help immediately and open the Vault. Find us inside." - - -/* - * Wish Granter - */ -/obj/machinery/wish_granter_dark - name = "Wish Granter" - desc = "You're not so sure about this, anymore..." - icon = 'icons/obj/device.dmi' - icon_state = "syndbeacon" - - density = TRUE - use_power = NO_POWER_USE - - var/chargesa = 1 - var/insistinga = 0 - -/obj/machinery/wish_granter_dark/interact(mob/living/carbon/human/user) - if(chargesa <= 0) - to_chat(user, "The Wish Granter lies silent.") - return - - else if(!ishuman(user)) - to_chat(user, "You feel a dark stirring inside of the Wish Granter, something you want nothing of. Your instincts are better than any man's.") - return - - else if(is_special_character(user)) - to_chat(user, "Even to a heart as dark as yours, you know nothing good will come of this. Something instinctual makes you pull away.") - - else if (!insistinga) - to_chat(user, "Your first touch makes the Wish Granter stir, listening to you. Are you really sure you want to do this?") - insistinga++ - - else - chargesa-- - insistinga = 0 - var/wish = input("You want...","Wish") as null|anything in sortList(list("Power","Wealth","Immortality","Peace")) - switch(wish) - if("Power") - to_chat(user, "Your wish is granted, but at a terrible cost...") - to_chat(user, "The Wish Granter punishes you for your selfishness, claiming your soul and warping your body to match the darkness in your heart.") - user.dna.add_mutation(LASEREYES) - user.dna.add_mutation(SPACEMUT) - user.dna.add_mutation(XRAY) - user.set_species(/datum/species/shadow) - if("Wealth") - to_chat(user, "Your wish is granted, but at a terrible cost...") - to_chat(user, "The Wish Granter punishes you for your selfishness, claiming your soul and warping your body to match the darkness in your heart.") - new /obj/structure/closet/syndicate/resources/everything(loc) - user.set_species(/datum/species/shadow) - if("Immortality") - to_chat(user, "Your wish is granted, but at a terrible cost...") - to_chat(user, "The Wish Granter punishes you for your selfishness, claiming your soul and warping your body to match the darkness in your heart.") - add_verb(user, /mob/living/carbon/proc/immortality) - user.set_species(/datum/species/shadow) - if("Peace") - to_chat(user, "Whatever alien sentience that the Wish Granter possesses is satisfied with your wish. There is a distant wailing as the last of the Faithless begin to die, then silence.") - to_chat(user, "You feel as if you just narrowly avoided a terrible fate...") - for(var/mob/living/simple_animal/hostile/faithless/F in GLOB.mob_living_list) - F.death() - - ///////////////Meatgrinder////////////// @@ -155,19 +45,3 @@ s.start() explosion(M, 1, 0, 0, 0) qdel(src) - -/////For the Wishgranter/////////// - -/mob/living/carbon/proc/immortality() //Mob proc so people cant just clone themselves to get rid of the shadowperson race. No hiding your wickedness. - set category = "Immortality" - set name = "Resurrection" - - var/mob/living/carbon/C = usr - if(!C.stat) - to_chat(C, "You're not dead yet!") - return - if(C.has_status_effect(STATUS_EFFECT_WISH_GRANTERS_GIFT)) - to_chat(C, "You're already resurrecting!") - return - C.apply_status_effect(STATUS_EFFECT_WISH_GRANTERS_GIFT) - return 1 diff --git a/code/modules/buildmode/buildmode.dm b/code/modules/buildmode/buildmode.dm index 8ee15ad72e2c..19bb6631c708 100644 --- a/code/modules/buildmode/buildmode.dm +++ b/code/modules/buildmode/buildmode.dm @@ -15,13 +15,15 @@ // Switching management var/switch_state = BM_SWITCHSTATE_NONE - var/switch_width = 5 + var/switch_width = 4 // modeswitch UI var/atom/movable/screen/buildmode/mode/modebutton var/list/modeswitch_buttons = list() // dirswitch UI var/atom/movable/screen/buildmode/bdir/dirbutton var/list/dirswitch_buttons = list() + /// item preview for selected item + var/atom/movable/screen/buildmode/preview_item/preview /datum/buildmode/New(client/c) mode = new /datum/buildmode_mode/basic(src) @@ -44,6 +46,7 @@ /datum/buildmode/Destroy() close_switchstates() + close_preview() holder.player_details.post_login_callbacks -= li_cb holder = null QDEL_NULL(mode) @@ -72,7 +75,7 @@ buttons += new /atom/movable/screen/buildmode/quit(src) // build the lists of switching buttons build_options_grid(subtypesof(/datum/buildmode_mode), modeswitch_buttons, /atom/movable/screen/buildmode/modeswitch) - build_options_grid(list(SOUTH,EAST,WEST,NORTH,NORTHWEST), dirswitch_buttons, /atom/movable/screen/buildmode/dirswitch) + build_options_grid(GLOB.alldirs, dirswitch_buttons, /atom/movable/screen/buildmode/dirswitch) // this creates a nice offset grid for choosing between buildmode options, // because going "click click click ah hell" sucks. @@ -124,10 +127,41 @@ switch_state = BM_SWITCHSTATE_NONE holder.screen -= dirswitch_buttons +/datum/buildmode/proc/preview_selected_item(atom/typepath) + close_preview() + preview = new /atom/movable/screen/buildmode/preview_item(src) + preview.name = initial(typepath.name) + + // Scale the preview if it's bigger than one tile + var/mutable_appearance/preview_overlay = new(typepath) + var/icon/size_check = icon(initial(typepath.icon), icon_state = initial(typepath.icon_state)) + var/scale = 1 + var/width = size_check.Width() + var/height = size_check.Height() + if(width > world.icon_size || height > world.icon_size) + if(width >= height) + scale = world.icon_size / width + else + scale = world.icon_size / height + preview_overlay.transform = preview_overlay.transform.Scale(scale) + preview_overlay.appearance_flags |= TILE_BOUND + preview_overlay.layer = FLOAT_LAYER + preview_overlay.plane = FLOAT_PLANE + preview.add_overlay(preview_overlay) + + holder.screen += preview + +/datum/buildmode/proc/close_preview() + if(isnull(preview)) + return + holder.screen -= preview + QDEL_NULL(preview) + /datum/buildmode/proc/change_mode(newmode) mode.exit_mode(src) QDEL_NULL(mode) close_switchstates() + close_preview() mode = new newmode(src) mode.enter_mode(src) modebutton.update_appearance() diff --git a/code/modules/buildmode/buttons.dm b/code/modules/buildmode/buttons.dm index a1893b4b6232..a40cbcfa7a6d 100644 --- a/code/modules/buildmode/buttons.dm +++ b/code/modules/buildmode/buttons.dm @@ -89,3 +89,8 @@ /atom/movable/screen/buildmode/quit/Click() bd.quit() return 1 + +/atom/movable/screen/buildmode/preview_item + name = "Selected Item" + icon_state = "template" + screen_loc = "NORTH,WEST+4" diff --git a/code/modules/buildmode/submodes/advanced.dm b/code/modules/buildmode/submodes/advanced.dm index de6e84f6a1eb..4fd6f30ca52b 100644 --- a/code/modules/buildmode/submodes/advanced.dm +++ b/code/modules/buildmode/submodes/advanced.dm @@ -1,23 +1,22 @@ /datum/buildmode_mode/advanced key = "advanced" - var/objholder = null + var/atom/objholder = null // FIXME: add logic which adds a button displaying the icon // of the currently selected path -/datum/buildmode_mode/advanced/show_help(client/c) - to_chat(c, "***********************************************************") - to_chat(c, "Right Mouse Button on buildmode button = Set object type") - to_chat(c, "Left Mouse Button + alt on turf/obj = Copy object type") - to_chat(c, "Left Mouse Button on turf/obj = Place objects") - to_chat(c, "Right Mouse Button = Delete objects") - to_chat(c, "
") - to_chat(c, "Use the button in the upper left corner to") - to_chat(c, "change the direction of built objects.") - to_chat(c, "***********************************************************") +/datum/buildmode_mode/advanced/show_help(client/target_client) + to_chat(target_client, span_purple(examine_block( + "[span_bold("Set object type")] -> Right Mouse Button on buildmode button\n\ + [span_bold("Copy object type")] -> Left Mouse Button + Alt on turf/obj\n\ + [span_bold("Place objects")] -> Left Mouse Button on turf/obj\n\ + [span_bold("Delete objects")] -> Right Mouse Button\n\ + \n\ + Use the button in the upper left corner to change the direction of built objects.")) + ) -/datum/buildmode_mode/advanced/change_settings(client/c) - var/target_path = input(c, "Enter typepath:", "Typepath", "/obj/structure/closet") +/datum/buildmode_mode/advanced/change_settings(client/target_client) + var/target_path = input(target_client, "Enter typepath:", "Typepath", "/obj/structure/closet") objholder = text2path(target_path) if(!ispath(objholder)) objholder = pick_closest_path(target_path) @@ -28,8 +27,9 @@ objholder = null alert("That path is not allowed.") return + BM.preview_selected_item(objholder) -/datum/buildmode_mode/advanced/handle_click(client/c, params, obj/object) +/datum/buildmode_mode/advanced/handle_click(client/target_client, params, obj/object) var/list/modifiers = params2list(params) var/left_click = LAZYACCESS(modifiers, LEFT_CLICK) var/right_click = LAZYACCESS(modifiers, RIGHT_CLICK) @@ -38,21 +38,27 @@ if(left_click && alt_click) if (istype(object, /turf) || istype(object, /obj) || istype(object, /mob)) objholder = object.type - to_chat(c, "[initial(object.name)] ([object.type]) selected.") + to_chat(target_client, "[initial(object.name)] ([object.type]) selected.") + BM.preview_selected_item(objholder) else - to_chat(c, "[initial(object.name)] is not a turf, object, or mob! Please select again.") + to_chat(target_client, "[initial(object.name)] is not a turf, object, or mob! Please select again.") else if(left_click) if(ispath(objholder,/turf)) var/turf/T = get_turf(object) - log_admin("Build Mode: [key_name(c)] modified [T] in [AREACOORD(object)] to [objholder]") - T.ChangeTurf(objholder) + log_admin("Build Mode: [key_name(target_client)] modified [T] in [AREACOORD(object)] to [objholder]") + T = T.ChangeTurf(objholder) + T.setDir(BM.build_dir) + else if(ispath(objholder, /obj/effect/turf_decal)) + var/turf/T = get_turf(object) + T.AddElement(/datum/element/decal, initial(objholder.icon), initial(objholder.icon_state), BM.build_dir, FALSE, initial(objholder.color), null, null, initial(objholder.alpha)) + log_admin("Build Mode: [key_name(target_client)] in [AREACOORD(object)] added a [initial(objholder.name)] decal with dir [BM.build_dir] to [T]") else if(!isnull(objholder)) var/obj/A = new objholder (get_turf(object)) A.setDir(BM.build_dir) - log_admin("Build Mode: [key_name(c)] modified [A]'s [COORD(A)] dir to [BM.build_dir]") + log_admin("Build Mode: [key_name(target_client)] modified [A]'s [COORD(A)] dir to [BM.build_dir]") else - to_chat(c, "Select object type first.") + to_chat(target_client, "Select object type first.") else if(right_click) if(isobj(object)) - log_admin("Build Mode: [key_name(c)] deleted [object] at [AREACOORD(object)]") + log_admin("Build Mode: [key_name(target_client)] deleted [object] at [AREACOORD(object)]") qdel(object) diff --git a/code/modules/buildmode/submodes/area_edit.dm b/code/modules/buildmode/submodes/area_edit.dm index 039f2897a888..b0d8925c0c85 100644 --- a/code/modules/buildmode/submodes/area_edit.dm +++ b/code/modules/buildmode/submodes/area_edit.dm @@ -1,5 +1,6 @@ /datum/buildmode_mode/area_edit key = "areaedit" + use_corner_selection = TRUE var/area/storedarea var/image/areaimage @@ -20,18 +21,19 @@ storedarea = null return ..() -/datum/buildmode_mode/area_edit/show_help(client/c) - to_chat(c, "***********************************************************") - to_chat(c, "Left Mouse Button on obj/turf/mob = Paint area") - to_chat(c, "Right Mouse Button on obj/turf/mob = Select area to paint") - to_chat(c, "Right Mouse Button on buildmode button = Create new area") - to_chat(c, "***********************************************************") +/datum/buildmode_mode/area_edit/show_help(client/target_client) + to_chat(target_client, span_purple(examine_block( + "[span_bold("Select corner")] -> Left Mouse Button on obj/turf/mob\n\ + [span_bold("Paint area")] -> Left Mouse Button + Alt on turf/obj/mob\n\ + [span_bold("Select area to paint")] -> Right Mouse Button on obj/turf/mob\n\ + [span_bold("Create new area")] -> Right Mouse Button on buildmode button")) + ) -/datum/buildmode_mode/area_edit/change_settings(client/c) - var/target_path = input(c, "Enter typepath:", "Typepath", "/area") +/datum/buildmode_mode/area_edit/change_settings(client/target_client) + var/target_path = input(target_client, "Enter typepath:", "Typepath", "/area") var/areatype = text2path(target_path) if(ispath(areatype,/area)) - var/areaname = input(c, "Enter area name:", "Area name", "Area") + var/areaname = input(target_client, "Enter area name:", "Area name", "Area") if(!areaname || !length(areaname)) return storedarea = new areatype @@ -42,18 +44,32 @@ storedarea.name = areaname areaimage.loc = storedarea // color our area -/datum/buildmode_mode/area_edit/handle_click(client/c, params, object) +/datum/buildmode_mode/area_edit/handle_click(client/target_client, params, object) var/list/modifiers = params2list(params) if(LAZYACCESS(modifiers, LEFT_CLICK)) if(!storedarea) - to_chat(c, "Configure or select the area you want to paint first!") + to_chat(target_client, "Configure or select the area you want to paint first!") return - var/turf/T = get_turf(object) - if(get_area(T) != storedarea) - log_admin("Build Mode: [key_name(c)] added [AREACOORD(T)] to [storedarea]") - storedarea.contents.Add(T) + if(LAZYACCESS(modifiers, ALT_CLICK)) + var/turf/T = get_turf(object) + if(get_area(T) != storedarea) + log_admin("Build Mode: [key_name(target_client)] added [AREACOORD(T)] to [storedarea]") + storedarea.contents.Add(T) + return + return ..() else if(LAZYACCESS(modifiers, RIGHT_CLICK)) var/turf/T = get_turf(object) storedarea = get_area(T) areaimage.loc = storedarea // color our area + +/datum/buildmode_mode/area_edit/handle_selected_area(client/target_client, params) + var/list/modifiers = params2list(params) + + if(LAZYACCESS(modifiers, LEFT_CLICK)) + var/choice = alert("Are you sure you want to fill area?", "Area Fill Confirmation", "Yes", "No") + if(choice != "Yes") + return + for(var/turf/T in block(get_turf(cornerA),get_turf(cornerB))) + storedarea.contents.Add(T) + log_admin("Build Mode: [key_name(target_client)] set the area of the region from [AREACOORD(cornerA)] through [AREACOORD(cornerB)] to [storedarea].") diff --git a/code/modules/buildmode/submodes/basic.dm b/code/modules/buildmode/submodes/basic.dm index 302ffba04f9f..180331e94ba8 100644 --- a/code/modules/buildmode/submodes/basic.dm +++ b/code/modules/buildmode/submodes/basic.dm @@ -1,18 +1,17 @@ /datum/buildmode_mode/basic key = "basic" -/datum/buildmode_mode/basic/show_help(client/c) - to_chat(c, "***********************************************************") - to_chat(c, "Left Mouse Button = Construct / Upgrade") - to_chat(c, "Right Mouse Button = Deconstruct / Delete / Downgrade") - to_chat(c, "Left Mouse Button + ctrl = R-Window") - to_chat(c, "Left Mouse Button + alt = Airlock") - to_chat(c, "
") - to_chat(c, "Use the button in the upper left corner to") - to_chat(c, "change the direction of built objects.") - to_chat(c, "***********************************************************") +/datum/buildmode_mode/basic/show_help(client/target_client) + to_chat(target_client, span_purple(examine_block( + "[span_bold("Construct / Upgrade")] -> Left Mouse Button\n\ + [span_bold("Deconstruct / Delete / Downgrade")] -> Right Mouse Button\n\ + [span_bold("R-Window")] -> Left Mouse Button + Ctrl\n\ + [span_bold("Airlock")] -> Left Mouse Button + Alt \n\ + \n\ + Use the button in the upper left corner to change the direction of built objects.")) + ) -/datum/buildmode_mode/basic/handle_click(client/c, params, obj/object) +/datum/buildmode_mode/basic/handle_click(client/target_client, params, obj/object) var/list/modifiers = params2list(params) var/left_click = LAZYACCESS(modifiers, LEFT_CLICK) @@ -30,10 +29,10 @@ T.PlaceOnTop(/turf/closed/wall) else if(iswallturf(object)) T.PlaceOnTop(/turf/closed/wall/r_wall) - log_admin("Build Mode: [key_name(c)] built [T] at [AREACOORD(T)]") + log_admin("Build Mode: [key_name(target_client)] built [T] at [AREACOORD(T)]") return else if(right_click) - log_admin("Build Mode: [key_name(c)] deleted [object] at [AREACOORD(object)]") + log_admin("Build Mode: [key_name(target_client)] deleted [object] at [AREACOORD(object)]") if(isturf(object)) var/turf/T = object T.ScrapeAway(flags = CHANGETURF_INHERIT_AIR) @@ -41,13 +40,13 @@ qdel(object) return else if(istype(object,/turf) && alt_click && left_click) - log_admin("Build Mode: [key_name(c)] built an airlock at [AREACOORD(object)]") + log_admin("Build Mode: [key_name(target_client)] built an airlock at [AREACOORD(object)]") new/obj/machinery/door/airlock(get_turf(object)) else if(istype(object,/turf) && ctrl_click && left_click) var/obj/structure/window/reinforced/window - if(BM.build_dir == NORTHWEST) + if(BM.build_dir in GLOB.diagonals) window = new /obj/structure/window/reinforced/fulltile(get_turf(object)) else window = new /obj/structure/window/reinforced(get_turf(object)) - window.setDir(BM.build_dir) - log_admin("Build Mode: [key_name(c)] built a window at [AREACOORD(object)]") + window.setDir(BM.build_dir) + log_admin("Build Mode: [key_name(target_client)] built a window at [AREACOORD(object)]") diff --git a/code/modules/buildmode/submodes/boom.dm b/code/modules/buildmode/submodes/boom.dm index a8460956a0cf..f0837735c641 100644 --- a/code/modules/buildmode/submodes/boom.dm +++ b/code/modules/buildmode/submodes/boom.dm @@ -7,32 +7,33 @@ var/flash = -1 var/flames = -1 -/datum/buildmode_mode/boom/show_help(client/c) - to_chat(c, "***********************************************************") - to_chat(c, "Mouse Button on obj = Kaboom") - to_chat(c, "NOTE: Using the \"Config/Launch Supplypod\" verb allows you to do this in an IC way (i.e., making a cruise missile come down from the sky and explode wherever you click!)") - to_chat(c, "***********************************************************") +/datum/buildmode_mode/boom/show_help(client/target_client) + to_chat(target_client, span_purple(examine_block( + "[span_bold("Set explosion destructiveness")] -> Right Mouse Button on buildmode button\n\ + [span_bold("Kaboom")] -> Mouse Button on obj\n\n\ + [span_warning("NOTE:")] Using the \"Config/Launch Supplypod\" verb allows you to do this in an IC way (i.e., making a cruise missile come down from the sky and explode wherever you click!)")) + ) -/datum/buildmode_mode/boom/change_settings(client/c) - devastation = input(c, "Range of total devastation. -1 to none", text("Input")) as num|null +/datum/buildmode_mode/boom/change_settings(client/target_client) + devastation = input(target_client, "Range of total devastation. -1 to none", text("Input")) as num|null if(devastation == null) devastation = -1 - heavy = input(c, "Range of heavy impact. -1 to none", text("Input")) as num|null + heavy = input(target_client, "Range of heavy impact. -1 to none", text("Input")) as num|null if(heavy == null) heavy = -1 - light = input(c, "Range of light impact. -1 to none", text("Input")) as num|null + light = input(target_client, "Range of light impact. -1 to none", text("Input")) as num|null if(light == null) light = -1 - flash = input(c, "Range of flash. -1 to none", text("Input")) as num|null + flash = input(target_client, "Range of flash. -1 to none", text("Input")) as num|null if(flash == null) flash = -1 - flames = input(c, "Range of flames. -1 to none", text("Input")) as num|null + flames = input(target_client, "Range of flames. -1 to none", text("Input")) as num|null if(flames == null) flames = -1 -/datum/buildmode_mode/boom/handle_click(client/c, params, obj/object) +/datum/buildmode_mode/boom/handle_click(client/target_client, params, obj/object) var/list/modifiers = params2list(params) if(LAZYACCESS(modifiers, LEFT_CLICK)) explosion(object, devastation, heavy, light, flash, FALSE, TRUE, flames) - log_admin("Build Mode: [key_name(c)] caused an explosion(dev=[devastation], hvy=[heavy], lgt=[light], flash=[flash], flames=[flames]) at [AREACOORD(object)]") + log_admin("Build Mode: [key_name(target_client)] caused an explosion(dev=[devastation], hvy=[heavy], lgt=[light], flash=[flash], flames=[flames]) at [AREACOORD(object)]") diff --git a/code/modules/buildmode/submodes/copy.dm b/code/modules/buildmode/submodes/copy.dm index 7f189923b145..4ac7f9ec4796 100644 --- a/code/modules/buildmode/submodes/copy.dm +++ b/code/modules/buildmode/submodes/copy.dm @@ -6,21 +6,21 @@ stored = null return ..() -/datum/buildmode_mode/copy/show_help(client/c) - to_chat(c, "***********************************************************") - to_chat(c, "Left Mouse Button on obj/turf/mob = Spawn a Copy of selected target") - to_chat(c, "Right Mouse Button on obj/mob = Select target to copy") - to_chat(c, "***********************************************************") +/datum/buildmode_mode/copy/show_help(client/target_client) + to_chat(target_client, span_purple(examine_block( + "[span_bold("Spawn a copy of selected target")] -> Left Mouse Button on obj/turf/mob\n\ + [span_bold("Select target to copy")] -> Right Mouse Button on obj/mob")) + ) -/datum/buildmode_mode/copy/handle_click(client/c, params, obj/object) +/datum/buildmode_mode/copy/handle_click(client/target_client, params, obj/object) var/list/modifiers = params2list(params) if(LAZYACCESS(modifiers, LEFT_CLICK)) var/turf/T = get_turf(object) if(stored) DuplicateObject(stored, perfectcopy=1, sameloc=0,newloc=T) - log_admin("Build Mode: [key_name(c)] copied [stored] to [AREACOORD(object)]") + log_admin("Build Mode: [key_name(target_client)] copied [stored] to [AREACOORD(object)]") else if(LAZYACCESS(modifiers, RIGHT_CLICK)) if(ismovable(object)) // No copying turfs for now. - to_chat(c, "[object] set as template.") + to_chat(target_client, "[object] set as template.") stored = object diff --git a/code/modules/buildmode/submodes/delete.dm b/code/modules/buildmode/submodes/delete.dm new file mode 100644 index 000000000000..4ef4fe37156c --- /dev/null +++ b/code/modules/buildmode/submodes/delete.dm @@ -0,0 +1,61 @@ +/datum/buildmode_mode/delete + key = "delete" + +/datum/buildmode_mode/delete/show_help(client/target_client) + to_chat(target_client, span_purple(examine_block( + "[span_bold("Delete an object")] -> Left Mouse Button on obj/turf/mob\n\ + [span_bold("Delete all objects of a type")] -> Right Mouse Button on obj/turf/mob")) + ) +/datum/buildmode_mode/delete/handle_click(client/target_client, params, object) + var/list/pa = params2list(params) + var/left_click = pa.Find("left") + var/right_click = pa.Find("right") + + if(left_click) + if(isturf(object)) + var/turf/T = object + T.ScrapeAway(flags = CHANGETURF_INHERIT_AIR) + else if(isatom(object)) + qdel(object) + + if(right_click) + if(check_rights(R_DEBUG|R_SERVER)) //Prevents buildmoded non-admins from breaking everything. + if(isturf(object)) + return + var/atom/deleting = object + var/action_type = alert("Strict type ([deleting.type]) or type and all subtypes?",,"Strict type","Type and subtypes","Cancel") + if(action_type == "Cancel" || !action_type) + return + + if(alert("Are you really sure you want to delete all instances of type [deleting.type]?",,"Yes","No") != "Yes") + return + + if(alert("Second confirmation required. Delete?",,"Yes","No") != "Yes") + return + + var/O_type = deleting.type + switch(action_type) + if("Strict type") + var/i = 0 + for(var/atom/Obj in world) + if(Obj.type == O_type) + i++ + qdel(Obj) + CHECK_TICK + if(!i) + to_chat(usr, "No instances of this type exist") + return + log_admin("[key_name(usr)] deleted all instances of type [O_type] ([i] instances deleted) ") + message_admins("[key_name(usr)] deleted all instances of type [O_type] ([i] instances deleted) ") + if("Type and subtypes") + var/i = 0 + for(var/Obj in world) + if(istype(Obj,O_type)) + i++ + qdel(Obj) + CHECK_TICK + if(!i) + to_chat(usr, "No instances of this type exist") + return + log_admin("[key_name(usr)] deleted all instances of type or subtype of [O_type] ([i] instances deleted) ") + message_admins("[key_name(usr)] deleted all instances of type or subtype of [O_type] ([i] instances deleted) ") diff --git a/code/modules/buildmode/submodes/fill.dm b/code/modules/buildmode/submodes/fill.dm index c02c51835653..75f4f2d221b7 100644 --- a/code/modules/buildmode/submodes/fill.dm +++ b/code/modules/buildmode/submodes/fill.dm @@ -1,18 +1,19 @@ +#define FILL_WARNING_MIN 150 + /datum/buildmode_mode/fill key = "fill" use_corner_selection = TRUE - var/objholder = null - -/datum/buildmode_mode/fill/show_help(client/c) - to_chat(c, "***********************************************************") - to_chat(c, "Left Mouse Button on turf/obj/mob = Select corner") - to_chat(c, "Left Mouse Button + Alt on turf/obj/mob = Delete region") - to_chat(c, "Right Mouse Button on buildmode button = Select object type") - to_chat(c, "***********************************************************") + var/atom/objholder = null -/datum/buildmode_mode/fill/change_settings(client/c) - var/target_path = input(c, "Enter typepath:" ,"Typepath","/obj/structure/closet") +/datum/buildmode_mode/fill/show_help(client/target_client) + to_chat(target_client, span_purple(examine_block( + "[span_bold("Select corner")] -> Left Mouse Button on turf/obj/mob\n\ + [span_bold("Delete region")] -> Left Mouse Button + Alt on turf/obj/mob\n\ + [span_bold("Select object type")] -> Right Mouse Button on buildmode button")) + ) +/datum/buildmode_mode/fill/change_settings(client/target_client) + var/target_path = input(target_client, "Enter typepath:" ,"Typepath","/obj/structure/closet") objholder = text2path(target_path) if(!ispath(objholder)) objholder = pick_closest_path(target_path) @@ -23,16 +24,17 @@ objholder = null alert("Area paths are not supported for this mode, use the area edit mode instead.") return + BM.preview_selected_item(objholder) deselect_region() -/datum/buildmode_mode/fill/handle_click(client/c, params, obj/object) +/datum/buildmode_mode/fill/handle_click(client/target_client, params, obj/object) if(isnull(objholder)) - to_chat(c, "Select an object type first.") + to_chat(target_client, "Select an object type first.") deselect_region() return ..() -/datum/buildmode_mode/fill/handle_selected_area(client/c, params) +/datum/buildmode_mode/fill/handle_selected_area(client/target_client, params) var/list/modifiers = params2list(params) if(LAZYACCESS(modifiers, LEFT_CLICK)) //rectangular @@ -47,14 +49,26 @@ for(var/beep in deletion_area) var/turf/T = beep T.AfterChange() - log_admin("Build Mode: [key_name(c)] deleted turfs from [AREACOORD(cornerA)] through [AREACOORD(cornerB)]") + log_admin("Build Mode: [key_name(target_client)] deleted turfs from [AREACOORD(cornerA)] through [AREACOORD(cornerB)]") // if there's an analogous proc for this on tg lmk // empty_region(block(get_turf(cornerA),get_turf(cornerB))) else + var/selection_size = abs(cornerA.x - cornerB.x) * abs(cornerA.y - cornerB.y) + + if(selection_size > FILL_WARNING_MIN) // Confirm fill if the number of tiles in the selection is greater than FILL_WARNING_MIN + var/choice = alert("Your selected area is [selection_size] tiles! Continue?", "Large Fill Confirmation", "Yes", "No") + if(choice != "Yes") + return + for(var/turf/T in block(get_turf(cornerA),get_turf(cornerB))) if(ispath(objholder,/turf)) - T.PlaceOnTop(objholder) + T = T.ChangeTurf(objholder) + T.setDir(BM.build_dir) + else if(ispath(objholder, /obj/effect/turf_decal)) + T.AddElement(/datum/element/decal, initial(objholder.icon), initial(objholder.icon_state), BM.build_dir, FALSE, initial(objholder.color), null, null, initial(objholder.alpha)) else var/obj/A = new objholder(T) A.setDir(BM.build_dir) - log_admin("Build Mode: [key_name(c)] with path [objholder], filled the region from [AREACOORD(cornerA)] through [AREACOORD(cornerB)]") + log_admin("Build Mode: [key_name(target_client)] with path [objholder], filled the region from [AREACOORD(cornerA)] through [AREACOORD(cornerB)]") + +#undef FILL_WARNING_MIN diff --git a/code/modules/buildmode/submodes/map_export.dm b/code/modules/buildmode/submodes/map_export.dm index 983801154afa..3684aaca408c 100644 --- a/code/modules/buildmode/submodes/map_export.dm +++ b/code/modules/buildmode/submodes/map_export.dm @@ -7,24 +7,24 @@ var/save_flag = SAVE_ALL var/static/is_running = FALSE -/datum/buildmode_mode/export/change_settings(client/c) +/datum/buildmode_mode/export/change_settings(client/target_client) var/static/list/options = list("Object Saving" = SAVE_OBJECTS, "Mob Saving" = SAVE_MOBS, "Turf Saving" = SAVE_TURFS, "Area Saving" = SAVE_AREAS, "Space Turf Saving" = SAVE_SPACE, "Object Property Saving" = SAVE_OBJECT_PROPERTIES) - var/what_to_change = tgui_input_list(c, "What export setting would you like to toggle?", "Map Exporter", options) + var/what_to_change = tgui_input_list(target_client, "What export setting would you like to toggle?", "Map Exporter", options) save_flag ^= options[what_to_change] - to_chat(c, "[what_to_change] is now [save_flag & options[what_to_change] ? "ENABLED" : "DISABLED"].") + to_chat(target_client, "[what_to_change] is now [save_flag & options[what_to_change] ? "ENABLED" : "DISABLED"].") -/datum/buildmode_mode/export/show_help(client/c) - to_chat(c, "***********************************************************") - to_chat(c, "Left Mouse Button on turf/obj/mob = Select corner") - to_chat(c, "Right Mouse Button on buildmode button = Set export options") - to_chat(c, "***********************************************************") +/datum/buildmode_mode/export/show_help(client/target_client) + to_chat(target_client, "***********************************************************") + to_chat(target_client, "Left Mouse Button on turf/obj/mob = Select corner") + to_chat(target_client, "Right Mouse Button on buildmode button = Set export options") + to_chat(target_client, "***********************************************************") -/datum/buildmode_mode/export/handle_selected_area(client/c, params) +/datum/buildmode_mode/export/handle_selected_area(client/target_client, params) var/list/modifiers = params2list(params) //Ensure the selection is actually done @@ -53,7 +53,7 @@ to_chat(usr, "Saving, please wait...") is_running = TRUE - log_admin("Build Mode: [key_name(c)] is exporting the map area from [AREACOORD(cornerA)] through [AREACOORD(cornerB)]") //I put this before the actual saving of the map because it likely won't log if it crashes the fucking server + log_admin("Build Mode: [key_name(target_client)] is exporting the map area from [AREACOORD(cornerA)] through [AREACOORD(cornerB)]") //I put this before the actual saving of the map because it likely won't log if it crashes the fucking server //oversimplified for readability and understandibility diff --git a/code/modules/buildmode/submodes/outfit.dm b/code/modules/buildmode/submodes/outfit.dm new file mode 100644 index 000000000000..56faf5d507cc --- /dev/null +++ b/code/modules/buildmode/submodes/outfit.dm @@ -0,0 +1,44 @@ +/datum/buildmode_mode/outfit + key = "outfit" + var/datum/outfit/dressuptime + +/datum/buildmode_mode/outfit/Destroy() + dressuptime = null + return ..() + +/datum/buildmode_mode/outfit/show_help(client/target_client) + to_chat(target_client, span_purple(examine_block( + "[span_bold("Select outfit to equip")] -> Right Mouse Button on buildmode button\n\ + [span_bold("Equip the selected outfit")] -> Left Mouse Button on mob/living/carbon/human\n\ + [span_bold("Strip and delete current outfit")] -> Right Mouse Button on mob/living/carbon/human")) + ) + +/datum/buildmode_mode/outfit/Reset() + . = ..() + dressuptime = null + +/datum/buildmode_mode/outfit/change_settings(client/target_client) + dressuptime = target_client.robust_dress_shop() + +/datum/buildmode_mode/outfit/handle_click(client/target_client, params, object) + var/list/pa = params2list(params) + var/left_click = pa.Find("left") + var/right_click = pa.Find("right") + + if(!ishuman(object)) + return + var/mob/living/carbon/human/dollie = object + + if(left_click) + if(isnull(dressuptime)) + to_chat(target_client, "Pick an outfit first.") + return + + for (var/item in dollie.get_equipped_items(TRUE)) + qdel(item) + if(dressuptime != "Naked") + dollie.equipOutfit(dressuptime) + + if(right_click) + for (var/item in dollie.get_equipped_items(TRUE)) + qdel(item) diff --git a/code/modules/buildmode/submodes/proccall.dm b/code/modules/buildmode/submodes/proccall.dm new file mode 100644 index 000000000000..47e7130aa386 --- /dev/null +++ b/code/modules/buildmode/submodes/proccall.dm @@ -0,0 +1,49 @@ +/datum/buildmode_mode/proccall + key = "proccall" + ///The procedure itself, which we will call in the future. For example "qdel" + var/proc_name = null + ///The list of arguments for the procedure. They may not be. They are selected in the same way in the game, and can be a datum, and other types. + var/list/proc_args = null + +/datum/buildmode_mode/proccall/show_help(client/target_client) + to_chat(target_client, span_purple(examine_block( + "[span_bold("Choose procedure and arguments")] -> Right Mouse Button on buildmode button\n\ + [span_bold("Apply procedure on object")] -> Left Mouse Button on machinery")) + ) + +/datum/buildmode_mode/proccall/change_settings(client/target_client) + if(!check_rights_for(target_client, R_DEBUG)) + return + + proc_name = input("Proc name, eg: fake_blood", "Proc:", null) as text|null + if(!proc_name) + return + + proc_args = target_client.get_callproc_args() + if(!proc_args) + return + +/datum/buildmode_mode/proccall/handle_click(client/target_client, params, datum/object as null|area|mob|obj|turf) + if(!proc_name || !proc_args) + tgui_alert(target_client, "Undefined ProcCall or arguments.") + return + + if(!hascall(object, proc_name)) + to_chat(target_client, span_warning("Error: callproc_datum(): type [object.type] has no proc named [proc_name]."), confidential = TRUE) + return + + if(!is_valid_src(object)) + to_chat(target_client, span_warning("Error: callproc_datum(): owner of proc no longer exists."), confidential = TRUE) + return + + + var/msg = "[key_name(target_client)] called [object]'s [proc_name]() with [proc_args.len ? "the arguments [list2params(proc_args)]":"no arguments"]." + log_admin(msg) + message_admins(msg) + admin_ticket_log(object, msg) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Atom ProcCall") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + + var/returnval = WrapAdminProcCall(object, proc_name, proc_args) // Pass the lst as an argument list to the proc + . = target_client.get_callproc_returnval(returnval, proc_name) + if(.) + to_chat(target_client, ., confidential = TRUE) diff --git a/code/modules/buildmode/submodes/throwing.dm b/code/modules/buildmode/submodes/throwing.dm index c2e6a0029c50..0539d2ec4f9f 100644 --- a/code/modules/buildmode/submodes/throwing.dm +++ b/code/modules/buildmode/submodes/throwing.dm @@ -7,21 +7,21 @@ throw_atom = null return ..() -/datum/buildmode_mode/throwing/show_help(client/c) - to_chat(c, "***********************************************************") - to_chat(c, "Left Mouse Button on turf/obj/mob = Select") - to_chat(c, "Right Mouse Button on turf/obj/mob = Throw") - to_chat(c, "***********************************************************") +/datum/buildmode_mode/throwing/show_help(client/target_client) + to_chat(target_client, span_purple(examine_block( + "[span_bold("Select")] -> Left Mouse Button on turf/obj/mob\n\ + [span_bold("Throw")] -> Right Mouse Button on turf/obj/mob")) + ) -/datum/buildmode_mode/throwing/handle_click(client/c, params, obj/object) +/datum/buildmode_mode/throwing/handle_click(client/target_client, params, obj/object) var/list/modifiers = params2list(params) if(LAZYACCESS(modifiers, LEFT_CLICK)) if(isturf(object)) return throw_atom = object - to_chat(c, "Selected object '[throw_atom]'") + to_chat(target_client, "Selected object '[throw_atom]'") if(LAZYACCESS(modifiers, RIGHT_CLICK)) if(throw_atom) - throw_atom.throw_at(object, 10, 1, c.mob) - log_admin("Build Mode: [key_name(c)] threw [throw_atom] at [object] ([AREACOORD(object)])") + throw_atom.throw_at(object, 10, 1, target_client.mob) + log_admin("Build Mode: [key_name(target_client)] threw [throw_atom] at [object] ([AREACOORD(object)])") diff --git a/code/modules/buildmode/submodes/tweakcomps.dm b/code/modules/buildmode/submodes/tweakcomps.dm new file mode 100644 index 000000000000..4072f8dd8f2f --- /dev/null +++ b/code/modules/buildmode/submodes/tweakcomps.dm @@ -0,0 +1,34 @@ +/datum/buildmode_mode/tweakcomps + key = "tweakcomps" + /// This variable is responsible for the rating of the components themselves. Literally tiers of components, where 1 is standard, 4 is bluespace. + var/rating = null + +/datum/buildmode_mode/tweakcomps/show_help(client/target_client) + to_chat(target_client, span_purple(examine_block( + "[span_bold("Choose the rating of the components")] -> Right Mouse Button on buildmode button\n\ + [span_bold("Sets the chosen rating of the components on the machinery")] -> Left Mouse Button on machinery")) + ) + +/datum/buildmode_mode/tweakcomps/change_settings(client/target_client) + var/rating_to_choose = input(target_client, "Enter number of rating", "Number", "1") + rating_to_choose = text2num(rating_to_choose) + if(!isnum(rating_to_choose)) + tgui_alert(target_client, "Input a number.") + return + + rating = rating_to_choose + +/datum/buildmode_mode/tweakcomps/handle_click(client/target_client, params, obj/machinery/object) + if(!ismachinery(object)) + to_chat(target_client, span_warning("This isn't machinery!")) + return + + if(!object.component_parts) + to_chat(target_client, span_warning("This machinery doesn't have components!")) + return + + for(var/obj/item/stock_parts/P in object.component_parts) + P.rating = rating + object.RefreshParts() + + SSblackbox.record_feedback("nested tally", "admin_toggle", 1, list("Machine Upgrade", "[rating]")) //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! diff --git a/code/modules/buildmode/submodes/variable_edit.dm b/code/modules/buildmode/submodes/variable_edit.dm index b03740e653bb..728c909860b5 100644 --- a/code/modules/buildmode/submodes/variable_edit.dm +++ b/code/modules/buildmode/submodes/variable_edit.dm @@ -9,52 +9,52 @@ valueholder = null return ..() -/datum/buildmode_mode/varedit/show_help(client/c) - to_chat(c, "***********************************************************") - to_chat(c, "Right Mouse Button on buildmode button = Select var(type) & value") - to_chat(c, "Left Mouse Button on turf/obj/mob = Set var(type) & value") - to_chat(c, "Right Mouse Button on turf/obj/mob = Reset var's value") - to_chat(c, "***********************************************************") +/datum/buildmode_mode/varedit/show_help(client/target_client) + to_chat(target_client, span_purple(examine_block( + "[span_bold("Select var(type) & value")] -> Right Mouse Button on buildmode button\n\ + [span_bold("Set var(type) & value")] -> Left Mouse Button on turf/obj/mob\n\ + [span_bold("Reset var's value")] -> Right Mouse Button on turf/obj/mob")) + ) /datum/buildmode_mode/varedit/Reset() . = ..() varholder = null valueholder = null -/datum/buildmode_mode/varedit/change_settings(client/c) - varholder = input(c, "Enter variable name:" ,"Name", "name") +/datum/buildmode_mode/varedit/change_settings(client/target_client) + varholder = input(target_client, "Enter variable name:" ,"Name", "name") if(!vv_varname_lockcheck(varholder)) return - var/temp_value = c.vv_get_value() + var/temp_value = target_client.vv_get_value() if(isnull(temp_value["class"])) Reset() - to_chat(c, "Variable unset.") + to_chat(target_client, "Variable unset.") return valueholder = temp_value["value"] -/datum/buildmode_mode/varedit/handle_click(client/c, params, obj/object) +/datum/buildmode_mode/varedit/handle_click(client/target_client, params, obj/object) var/list/modifiers = params2list(params) if(isnull(varholder)) - to_chat(c, "Choose a variable to modify first.") + to_chat(target_client, "Choose a variable to modify first.") return if(LAZYACCESS(modifiers, LEFT_CLICK)) if(object.vars.Find(varholder)) if(object.vv_edit_var(varholder, valueholder) == FALSE) - to_chat(c, "Your edit was rejected by the object.") + to_chat(target_client, "Your edit was rejected by the object.") return - log_admin("Build Mode: [key_name(c)] modified [object.name]'s [varholder] to [valueholder]") + log_admin("Build Mode: [key_name(target_client)] modified [object.name]'s [varholder] to [valueholder]") else - to_chat(c, "[initial(object.name)] does not have a var called '[varholder]'") + to_chat(target_client, "[initial(object.name)] does not have a var called '[varholder]'") if(LAZYACCESS(modifiers, RIGHT_CLICK)) if(object.vars.Find(varholder)) var/reset_value = initial(object.vars[varholder]) if(object.vv_edit_var(varholder, reset_value) == FALSE) - to_chat(c, "Your edit was rejected by the object.") + to_chat(target_client, "Your edit was rejected by the object.") return - log_admin("Build Mode: [key_name(c)] modified [object.name]'s [varholder] to [reset_value]") + log_admin("Build Mode: [key_name(target_client)] modified [object.name]'s [varholder] to [reset_value]") else - to_chat(c, "[initial(object.name)] does not have a var called '[varholder]'") + to_chat(target_client, "[initial(object.name)] does not have a var called '[varholder]'") diff --git a/code/modules/cargo/centcom_podlauncher.dm b/code/modules/cargo/centcom_podlauncher.dm index 3e5938bbaa55..c0c316a1354a 100644 --- a/code/modules/cargo/centcom_podlauncher.dm +++ b/code/modules/cargo/centcom_podlauncher.dm @@ -1,3 +1,10 @@ +#define TAB_POD 0 //Used to check if the UIs built in camera is looking at the pod +#define TAB_BAY 1 //Used to check if the UIs built in camera is looking at the launch bay area + +#define LAUNCH_ALL 0 //Used to check if we're launching everything from the bay area at once +#define LAUNCH_ORDERED 1 //Used to check if we're launching everything from the bay area in order +#define LAUNCH_RANDOM 2 //Used to check if we're launching everything from the bay area randomly + //The Great and Mighty CentCom Pod Launcher - MrDoomBringer //This was originally created as a way to get adminspawned items to the station in an IC manner. It's evolved to contain a few more //features such as item removal, smiting, controllable delivery mobs, and more. @@ -13,19 +20,21 @@ set name = "Config/Launch Supplypod" set desc = "Configure and launch a CentCom supplypod full of whatever your heart desires!" set category = "Admin.Events" - var/datum/centcom_podlauncher/plaunch = new(usr)//create the datum - plaunch.ui_interact(usr)//datum has a tgui component, here we open the window + new /datum/centcom_podlauncher(usr)//create the datum //Variables declared to change how items in the launch bay are picked and launched. (Almost) all of these are changed in the ui_act proc //Some effect groups are choices, while other are booleans. This is because some effects can stack, while others dont (ex: you can stack explosion and quiet, but you cant stack ordered launch and random launch) /datum/centcom_podlauncher - var/static/list/ignored_atoms = typecacheof(list(null, /mob/dead, /obj/effect/landmark, /obj/docking_port, /atom/movable/lighting_object, /obj/effect/particle_effect/sparks, /obj/effect/DPtarget, /obj/effect/supplypod_selector)) + var/static/list/ignored_atoms = typecacheof(list(null, /mob/dead, /obj/effect/landmark, /obj/docking_port, /atom/movable/lighting_object, /obj/effect/particle_effect/sparks, /obj/effect/pod_landingzone, /obj/effect/hallucination/simple/supplypod_selector, /obj/effect/hallucination/simple/dropoff_location)) var/turf/oldTurf //Keeps track of where the user was at if they use the "teleport to centcom" button, so they can go back var/client/holder //client of whoever is using this datum - var/area/bay //What bay we're using to launch shit from. + var/area/centcom/supplypod/loading/bay //What bay we're using to launch shit from. + var/bayNumber //Quick reference to what bay we're in. Usually set to the loading_id variable for the related area type + var/customDropoff = FALSE + var/picking_dropoff_turf = FALSE var/launchClone = FALSE //If true, then we don't actually launch the thing in the bay. Instead we call duplicateObject() and send the result + var/launchChoice = LAUNCH_RANDOM //Determines if we launch all at once (0) , in order (1), or at random(2) var/launchRandomItem = FALSE //If true, lauches a single random item instead of everything on a turf. - var/launchChoice = 1 //Determines if we launch all at once (0) , in order (1), or at random(2) var/explosionChoice = 0 //Determines if there is no explosion (0), custom explosion (1), or just do a maxcap (2) var/damageChoice = 0 //Determines if we do no damage (0), custom amnt of damage (1), or gib + 5000dmg (2) var/launcherActivated = FALSE //check if we've entered "launch mode" (when we click a pod is launched). Used for updating mouse cursor @@ -37,57 +46,126 @@ var/list/orderedArea = list() //Contains an ordered list of turfs in an area (filled in the createOrderedArea() proc), read top-left to bottom-right. Used for the "ordered" launch mode (launchChoice = 1) var/list/turf/acceptableTurfs = list() //Contians a list of turfs (in the "bay" area on centcom) that have items that can be launched. Taken from orderedArea var/list/launchList = list() //Contains whatever is going to be put in the supplypod and fired. Taken from acceptableTurfs - var/obj/effect/supplypod_selector/selector = new() //An effect used for keeping track of what item is going to be launched when in "ordered" mode (launchChoice = 1) + var/obj/effect/hallucination/simple/supplypod_selector/selector //An effect used for keeping track of what item is going to be launched when in "ordered" mode (launchChoice = 1) + var/obj/effect/hallucination/simple/dropoff_location/indicator var/obj/structure/closet/supplypod/centcompod/temp_pod //The temporary pod that is modified by this datum, then cloned. The buildObject() clone of this pod is what is launched -/datum/centcom_podlauncher/New(H)//H can either be a client or a mob due to byondcode(tm) - if (istype(H,/client)) - var/client/C = H - holder = C //if its a client, assign it to holder + // Stuff needed to render the map + var/map_name + var/atom/movable/screen/map_view/cam_screen + var/list/cam_plane_masters + var/atom/movable/screen/background/cam_background + var/tabIndex = 1 + var/renderLighting = FALSE + +/datum/centcom_podlauncher/New(user) //user can either be a client or a mob + if (user) //Prevents runtimes on datums being made without clients + setup(user) + +/datum/centcom_podlauncher/proc/setup(user) //H can either be a client or a mob + if (istype(user,/client)) + var/client/user_client = user + holder = user_client //if its a client, assign it to holder else - var/mob/M = H - holder = M.client //if its a mob, assign the mob's client to holder + var/mob/user_mob = user + holder = user_mob.client //if its a mob, assign the mob's client to holder bay = locate(/area/centcom/supplypod/loading/one) in GLOB.sortedAreas //Locate the default bay (one) from the centcom map - temp_pod = new(locate(/area/centcom/supplypod/podStorage) in GLOB.sortedAreas) //Create a new temp_pod in the podStorage area on centcom (so users are free to look at it and change other variables if needed) + bayNumber = bay.loading_id //Used as quick reference to what bay we're taking items from + var/area/pod_storage_area = locate(/area/centcom/supplypod/pod_storage) in GLOB.sortedAreas + temp_pod = new(pick(get_area_turfs(pod_storage_area))) //Create a new temp_pod in the podStorage area on centcom (so users are free to look at it and change other variables if needed) orderedArea = createOrderedArea(bay) //Order all the turfs in the selected bay (top left to bottom right) to a single list. Used for the "ordered" mode (launchChoice = 1) + selector = new(null, holder.mob) + indicator = new(null, holder.mob) + setDropoff(bay) + initMap() + refreshBay() + ui_interact(holder.mob) + +/datum/centcom_podlauncher/proc/initMap() + if(map_name) + holder.clear_map(map_name) + + map_name = "admin_supplypod_bay_[REF(src)]_map" + // Initialize map objects + cam_screen = new + cam_screen.name = "screen" + cam_screen.assigned_map = map_name + cam_screen.del_on_map_removal = TRUE + cam_screen.screen_loc = "[map_name]:1,1" + cam_plane_masters = list() + for(var/plane in subtypesof(/atom/movable/screen/plane_master)) + var/atom/movable/screen/instance = new plane() + if (!renderLighting && instance.plane == LIGHTING_PLANE) + instance.alpha = 100 + instance.assigned_map = map_name + instance.del_on_map_removal = TRUE + instance.screen_loc = "[map_name]:CENTER" + cam_plane_masters += instance + cam_background = new + cam_background.assigned_map = map_name + cam_background.del_on_map_removal = TRUE + refreshView() + holder.register_map_obj(cam_screen) + for(var/plane in cam_plane_masters) + holder.register_map_obj(plane) + holder.register_map_obj(cam_background) /datum/centcom_podlauncher/ui_state(mob/user) + if (SSticker.current_state >= GAME_STATE_FINISHED) + return GLOB.always_state //Allow the UI to be given to players by admins after roundend return GLOB.admin_state +/datum/centcom_podlauncher/ui_assets(mob/user) + return list( + get_asset_datum(/datum/asset/spritesheet/supplypods), + ) + /datum/centcom_podlauncher/ui_interact(mob/user, datum/tgui/ui) ui = SStgui.try_update_ui(user, src, ui) if(!ui) + // Open UI ui = new(user, src, "CentcomPodLauncher") ui.open() + refreshView() + +/datum/centcom_podlauncher/ui_static_data(mob/user) + var/list/data = list() + data["mapRef"] = map_name + data["defaultSoundVolume"] = initial(temp_pod.soundVolume) //default volume for pods + return data /datum/centcom_podlauncher/ui_data(mob/user) //Sends info about the pod to the UI. var/list/data = list() //*****NOTE*****: Many of these comments are similarly described in supplypod.dm. If you change them here, please consider doing so in the supplypod code as well! - var/B = (istype(bay, /area/centcom/supplypod/loading/one)) ? 1 : (istype(bay, /area/centcom/supplypod/loading/two)) ? 2 : (istype(bay, /area/centcom/supplypod/loading/three)) ? 3 : (istype(bay, /area/centcom/supplypod/loading/four)) ? 4 : (istype(bay, /area/centcom/supplypod/loading/ert)) ? 5 : 0 //top ten THICCEST FUCKING TERNARY CONDITIONALS OF 2036 - data["bay"] = bay //Holds the current bay the user is launching objects from. Bays are specific rooms on the centcom map. - data["bayNumber"] = B //Holds the bay as a number. Useful for comparisons in centcom_podlauncher.ract + bayNumber = bay?.loading_id //Used as quick reference to what bay we're taking items from + data["bayNumber"] = bayNumber //Holds the bay as a number. Useful for comparisons in centcom_podlauncher.ract data["oldArea"] = (oldTurf ? get_area(oldTurf) : null) //Holds the name of the area that the user was in before using the teleportCentcom action + data["picking_dropoff_turf"] = picking_dropoff_turf //If we're picking or have picked a dropoff turf. Only works when pod is in reverse mode + data["customDropoff"] = customDropoff + data["renderLighting"] = renderLighting data["launchClone"] = launchClone //Do we launch the actual items in the bay or just launch clones of them? data["launchRandomItem"] = launchRandomItem //Do we launch a single random item instead of everything on the turf? data["launchChoice"] = launchChoice //Launch turfs all at once (0), ordered (1), or randomly(1) data["explosionChoice"] = explosionChoice //An explosion that occurs when landing. Can be no explosion (0), custom explosion (1), or maxcap (2) data["damageChoice"] = damageChoice //Damage that occurs to any mob under the pod when it lands. Can be no damage (0), custom damage (1), or gib+5000dmg (2) - data["fallDuration"] = temp_pod.fallDuration //How long the pod's falling animation lasts - data["landingDelay"] = temp_pod.landingDelay //How long the pod takes to land after launching - data["openingDelay"] = temp_pod.openingDelay //How long the pod takes to open after landing - data["departureDelay"] = temp_pod.departureDelay //How long the pod takes to leave after opening (if bluespace=true, it deletes. if reversing=true, it flies back to centcom) - data["styleChoice"] = temp_pod.style //Style is a variable that keeps track of what the pod is supposed to look like. It acts as an index to the POD_STYLES list in cargo.dm defines to get the proper icon/name/desc for the pod. + data["delays"] = temp_pod.delays + data["rev_delays"] = temp_pod.reverse_delays + data["custom_rev_delay"] = temp_pod.custom_rev_delay + data["styleChoice"] = temp_pod.style //Style is a variable that keeps track of what the pod is supposed to look like. It acts as an index to the GLOB.podstyles list in cargo.dm defines to get the proper icon/name/desc for the pod. data["effectStun"] = temp_pod.effectStun //If true, stuns anyone under the pod when it launches until it lands, forcing them to get hit by the pod. Devilish! data["effectLimb"] = temp_pod.effectLimb //If true, pops off a limb (if applicable) from anyone caught under the pod when it lands data["effectOrgans"] = temp_pod.effectOrgans //If true, yeets the organs out of any bodies caught under the pod when it lands data["effectBluespace"] = temp_pod.bluespace //If true, the pod deletes (in a shower of sparks) after landing - data["effectStealth"] = temp_pod.effectStealth //If true, a target icon isnt displayed on the turf where the pod will land + data["effectStealth"] = temp_pod.effectStealth //If true, a target icon isn't displayed on the turf where the pod will land data["effectQuiet"] = temp_pod.effectQuiet //The female sniper. If true, the pod makes no noise (including related explosions, opening sounds, etc) data["effectMissile"] = temp_pod.effectMissile //If true, the pod deletes the second it lands. If you give it an explosion, it will act like a missile exploding as it hits the ground data["effectCircle"] = temp_pod.effectCircle //If true, allows the pod to come in at any angle. Bit of a weird feature but whatever its here data["effectBurst"] = effectBurst //IOf true, launches five pods at once (with a very small delay between for added coolness), in a 3x3 area centered around the area data["effectReverse"] = temp_pod.reversing //If true, the pod will not send any items. Instead, after opening, it will close again (picking up items/mobs) and fly back to centcom + data["reverseOptionList"] = temp_pod.reverseOptionList data["effectTarget"] = specificTarget //Launches the pod at the turf of a specific mob target, rather than wherever the user clicked. Useful for smites data["effectName"] = temp_pod.adminNamed //Determines whether or not the pod has been named by an admin. If true, the pod's name will not get overridden when the style of the pod changes (changing the style of the pod normally also changes the name+desc) + data["podName"] = temp_pod.name + data["podDesc"] = temp_pod.desc data["effectAnnounce"] = effectAnnounce data["giveLauncher"] = launcherActivated //If true, the user is in launch mode, and whenever they click a pod will be launched (either at their mouse position or at a specific target) data["numObjects"] = numTurfs //Counts the number of turfs that contain a launchable object in the centcom supplypod bay @@ -95,7 +173,7 @@ data["landingSound"] = temp_pod.landingSound //Admin sound to play when the pod lands data["openingSound"] = temp_pod.openingSound //Admin sound to play when the pod opens data["leavingSound"] = temp_pod.leavingSound //Admin sound to play when the pod leaves - data["soundVolume"] = temp_pod.soundVolume != initial(temp_pod.soundVolume) //Admin sound to play when the pod leaves + data["soundVolume"] = temp_pod.soundVolume //Admin sound to play when the pod leaves return data /datum/centcom_podlauncher/ui_act(action, params) @@ -104,49 +182,72 @@ return switch(action) ////////////////////////////UTILITIES////////////////// - if("bay1") - bay = locate(/area/centcom/supplypod/loading/one) in GLOB.sortedAreas //set the "bay" variable to the corresponding room in centcom - refreshBay() //calls refreshBay() which "recounts" the bay to see what items we can launch (among other things). - . = TRUE - if("bay2") - bay = locate(/area/centcom/supplypod/loading/two) in GLOB.sortedAreas + if("gamePanel") + holder.holder.Game() + SSblackbox.record_feedback("tally", "admin_verb", 1, "Game Panel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + . = TRUE + if("buildMode") + var/mob/holder_mob = holder.mob + if (holder_mob) + togglebuildmode(holder_mob) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Toggle Build Mode") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + . = TRUE + if("loadDataFromPreset") + var/list/savedData = params["payload"] + loadData(savedData) + . = TRUE + if("switchBay") + bayNumber = params["bayNumber"] refreshBay() . = TRUE - if("bay3") - bay = locate(/area/centcom/supplypod/loading/three) in GLOB.sortedAreas - refreshBay() - . = TRUE - if("bay4") - bay = locate(/area/centcom/supplypod/loading/four) in GLOB.sortedAreas - refreshBay() + if("pickDropoffTurf") //Enters a mode that lets you pick the dropoff location for reverse pods + if (picking_dropoff_turf) + picking_dropoff_turf = FALSE + updateCursor() //Update the cursor of the user to a cool looking target icon + return + if (launcherActivated) + launcherActivated = FALSE //We don't want to have launch mode enabled while we're picking a turf + picking_dropoff_turf = TRUE + updateCursor() //Update the cursor of the user to a cool looking target icon . = TRUE - if("bay5") - bay = locate(/area/centcom/supplypod/loading/ert) in GLOB.sortedAreas - refreshBay() + if("clearDropoffTurf") + setDropoff(bay) + customDropoff = FALSE + picking_dropoff_turf = FALSE + updateCursor() . = TRUE - if("teleportCentcom") //Teleports the user to the centcom supply loading facility. + if("teleportDropoff") //Teleports the user to the dropoff point. var/mob/M = holder.mob //We teleport whatever mob the client is attached to at the point of clicking - oldTurf = get_turf(M) //Used for the "teleportBack" action - var/area/A = locate(bay) in GLOB.sortedAreas - var/list/turfs = list() - for(var/turf/T in A) - turfs.Add(T) //Fill a list with turfs in the area - if (!length(turfs)) //If the list is empty, error and cancel - to_chat(M, "Nowhere to jump to!") - return //Only teleport if the list isn't empty - var/turf/T = pick(turfs) - M.forceMove(T) //Perform the actual teleport - log_admin("[key_name(usr)] jumped to [AREACOORD(A)]") - message_admins("[key_name_admin(usr)] jumped to [AREACOORD(A)]") + var/turf/current_location = get_turf(M) + var/list/coordinate_list = temp_pod.reverse_dropoff_coords + var/turf/dropoff_turf = locate(coordinate_list[1], coordinate_list[2], coordinate_list[3]) + if (current_location != dropoff_turf) + oldTurf = current_location + M.forceMove(dropoff_turf) //Perform the actual teleport + log_admin("[key_name(usr)] jumped to [AREACOORD(dropoff_turf)]") + message_admins("[key_name_admin(usr)] jumped to [AREACOORD(dropoff_turf)]") . = TRUE - if("teleportBack") //After teleporting to centcom, this button allows the user to teleport to the last spot they were at. + if("teleportCentcom") //Teleports the user to the centcom supply loading facility. + var/mob/holder_mob = holder.mob //We teleport whatever mob the client is attached to at the point of clicking + var/turf/current_location = get_turf(holder_mob) + var/area/bay_area = bay + if (current_location.loc != bay_area) + oldTurf = current_location + var/turf/teleport_turf = pick(get_area_turfs(bay_area)) + holder_mob.forceMove(teleport_turf) //Perform the actual teleport + if (holder.holder) + log_admin("[key_name(usr)] jumped to [AREACOORD(teleport_turf)]") + message_admins("[key_name_admin(usr)] jumped to [AREACOORD(teleport_turf)]") + . = TRUE + if("teleportBack") //After teleporting to centcom/dropoff, this button allows the user to teleport to the last spot they were at. var/mob/M = holder.mob if (!oldTurf) //If theres no turf to go back to, error and cancel to_chat(M, "Nowhere to jump to!") return M.forceMove(oldTurf) //Perform the actual teleport - log_admin("[key_name(usr)] jumped to [AREACOORD(oldTurf)]") - message_admins("[key_name_admin(usr)] jumped to [AREACOORD(oldTurf)]") + if (holder.holder) + log_admin("[key_name(usr)] jumped to [AREACOORD(oldTurf)]") + message_admins("[key_name_admin(usr)] jumped to [AREACOORD(oldTurf)]") . = TRUE ////////////////////////////LAUNCH STYLE CHANGES////////////////// @@ -154,22 +255,21 @@ launchClone = !launchClone . = TRUE if("launchRandomItem") //Pick random turfs from the supplypod bay at centcom to launch - launchRandomItem = !launchRandomItem + launchRandomItem = TRUE + . = TRUE + if("launchWholeTurf") //Pick random turfs from the supplypod bay at centcom to launch + launchRandomItem = FALSE + . = TRUE + if("launchAll") //Launch turfs (from the orderedArea list) all at once, from the supplypod bay at centcom + launchChoice = LAUNCH_ALL + updateSelector() . = TRUE if("launchOrdered") //Launch turfs (from the orderedArea list) one at a time in order, from the supplypod bay at centcom - if (launchChoice == 1) //launchChoice 1 represents ordered. If we push "ordered" and it already is, then we go to default value - launchChoice = 0 - updateSelector() //Move the selector effect to the next object that will be launched. See variable declarations for more info on the selector effect. - return - launchChoice = 1 + launchChoice = LAUNCH_ORDERED updateSelector() . = TRUE if("launchRandomTurf") //Pick random turfs from the supplypod bay at centcom to launch - if (launchChoice == 2) - launchChoice = 0 - updateSelector() - return - launchChoice = 2 + launchChoice = LAUNCH_RANDOM updateSelector() . = TRUE @@ -182,11 +282,11 @@ var/list/expNames = list("Devastation", "Heavy Damage", "Light Damage", "Flame") //Explosions have a range of different types of damage var/list/boomInput = list() for (var/i=1 to expNames.len) //Gather input from the user for the value of each type of damage - boomInput.Add(input("[expNames[i]] Range", "Enter the [expNames[i]] range of the explosion. WARNING: This ignores the bomb cap!", 0) as null|num) + boomInput.Add(input("Enter the [expNames[i]] range of the explosion. WARNING: This ignores the bomb cap!", "[expNames[i]] Range", 0) as null|num) if (isnull(boomInput[i])) return if (!isnum(boomInput[i])) //If the user doesn't input a number, set that specific explosion value to zero - alert(usr, "That wasnt a number! Value set to default (zero) instead.") + alert(usr, "That wasn't a number! Value set to default (zero) instead.") boomInput = 0 explosionChoice = 1 temp_pod.explosionSize = boomInput @@ -204,11 +304,11 @@ damageChoice = 0 temp_pod.damage = 0 return - var/damageInput = input("How much damage to deal", "Enter the amount of brute damage dealt by getting hit", 0) as null|num + var/damageInput = input("Enter the amount of brute damage dealt by getting hit","How much damage to deal", 0) as null|num if (isnull(damageInput)) return if (!isnum(damageInput)) //Sanitize the input for damage to deal.s - alert(usr, "That wasnt a number! Value set to default (zero) instead.") + alert(usr, "That wasn't a number! Value set to default (zero) instead.") damageInput = 0 damageChoice = 1 temp_pod.damage = damageInput @@ -228,10 +328,10 @@ temp_pod.adminNamed = FALSE temp_pod.setStyle(temp_pod.style) //This resets the name of the pod based on it's current style (see supplypod/setStyle() proc) return - var/nameInput= input("Custom name", "Enter a custom name", POD_STYLES[temp_pod.style][POD_NAME]) as null|text //Gather input for name and desc + var/nameInput= input("Custom name", "Enter a custom name", GLOB.podstyles[temp_pod.style][POD_NAME]) as null|text //Gather input for name and desc if (isnull(nameInput)) return - var/descInput = input("Custom description", "Enter a custom desc", POD_STYLES[temp_pod.style][POD_DESC]) as null|text //The POD_STYLES is used to get the name, desc, or icon state based on the pod's style + var/descInput = input("Custom description", "Enter a custom desc", GLOB.podstyles[temp_pod.style][POD_DESC]) as null|text //The GLOB.podstyles is used to get the name, desc, or icon state based on the pod's style if (isnull(descInput)) return temp_pod.name = nameInput @@ -270,6 +370,14 @@ . = TRUE if("effectReverse") //Toggle: Don't send any items. Instead, after landing, close (taking any objects inside) and go back to the centcom bay it came from temp_pod.reversing = !temp_pod.reversing + if (temp_pod.reversing) + indicator.alpha = 150 + else + indicator.alpha = 0 + . = TRUE + if("reverseOption") + var/reverseOption = params["reverseOption"] + temp_pod.reverseOptionList[reverseOption] = !temp_pod.reverseOptionList[reverseOption] . = TRUE if("effectTarget") //Toggle: Launch at a specific mob (instead of at whatever turf you click on). Used for the supplypod smite if (specificTarget) @@ -284,71 +392,50 @@ . = TRUE ////////////////////////////TIMER DELAYS////////////////// - if("fallDuration") //Change the time it takes the pod to land, after firing - if (temp_pod.fallDuration != initial(temp_pod.fallDuration)) //If the landing delay has already been changed when we push the "change value" button, then set it to default - temp_pod.fallDuration = initial(temp_pod.fallDuration) - return - var/timeInput = input("Enter the duration of the pod's falling animation, in seconds", "Delay Time", initial(temp_pod.fallDuration) * 0.1) as null|num - if (isnull(timeInput)) - return - if (!isnum(timeInput)) //Sanitize input, if it doesnt check out, error and set to default - alert(usr, "That wasnt a number! Value set to default ([initial(temp_pod.fallDuration)*0.1]) instead.") - timeInput = initial(temp_pod.fallDuration) - temp_pod.fallDuration = 10 * timeInput - . = TRUE - if("landingDelay") //Change the time it takes the pod to land, after firing - if (temp_pod.landingDelay != initial(temp_pod.landingDelay)) //If the landing delay has already been changed when we push the "change value" button, then set it to default - temp_pod.landingDelay = initial(temp_pod.landingDelay) - return - var/timeInput = input("Enter the time it takes for the pod to land, in seconds", "Delay Time", initial(temp_pod.landingDelay) * 0.1) as null|num - if (isnull(timeInput)) - return - if (!isnum(timeInput)) //Sanitize input, if it doesnt check out, error and set to default - alert(usr, "That wasnt a number! Value set to default ([initial(temp_pod.landingDelay)*0.1]) instead.") - timeInput = initial(temp_pod.landingDelay) - temp_pod.landingDelay = 10 * timeInput - . = TRUE - if("openingDelay") //Change the time it takes the pod to open it's door (and release its contents) after landing - if (temp_pod.openingDelay != initial(temp_pod.openingDelay)) //If the opening delay has already been changed when we push the "change value" button, then set it to default - temp_pod.openingDelay = initial(temp_pod.openingDelay) - return - var/timeInput = input("Enter the time it takes for the pod to open after landing, in seconds", "Delay Time", initial(temp_pod.openingDelay) * 0.1) as null|num - if (isnull(timeInput)) - return - if (!isnum(timeInput)) //Sanitize input - alert(usr, "That wasnt a number! Value set to default ([initial(temp_pod.openingDelay)*0.1]) instead.") - timeInput = initial(temp_pod.openingDelay) - temp_pod.openingDelay = 10 * timeInput - . = TRUE - if("departureDelay") //Change the time it takes the pod to leave (if bluespace = true it just deletes, if effectReverse = true it goes back to centcom) - if (temp_pod.departureDelay != initial(temp_pod.departureDelay)) //If the departure delay has already been changed when we push the "change value" button, then set it to default - temp_pod.departureDelay = initial(temp_pod.departureDelay) - return - var/timeInput = input("Enter the time it takes for the pod to leave after opening, in seconds", "Delay Time", initial(temp_pod.departureDelay) * 0.1) as null|num - if (isnull(timeInput)) - return - if (!isnum(timeInput)) - alert(usr, "That wasnt a number! Value set to default ([initial(temp_pod.departureDelay)*0.1]) instead.") - timeInput = initial(temp_pod.departureDelay) - temp_pod.departureDelay = 10 * timeInput + if("editTiming") //Change the different timers relating to the pod + var/delay = params["timer"] + var/value = params["value"] + var/reverse = params["reverse"] + if (reverse) + temp_pod.reverse_delays[delay] = value * 10 + else + temp_pod.delays[delay] = value * 10 + . = TRUE + if("resetTiming") + temp_pod.delays = list(POD_TRANSIT = 20, POD_FALLING = 4, POD_OPENING = 30, POD_LEAVING = 30) + temp_pod.reverse_delays = list(POD_TRANSIT = 20, POD_FALLING = 4, POD_OPENING = 30, POD_LEAVING = 30) + . = TRUE + if("toggleRevDelays") + temp_pod.custom_rev_delay = !temp_pod.custom_rev_delay . = TRUE - ////////////////////////////ADMIN SOUNDS////////////////// if("fallingSound") //Admin sound from a local file that plays when the pod lands if ((temp_pod.fallingSound) != initial(temp_pod.fallingSound)) temp_pod.fallingSound = initial(temp_pod.fallingSound) temp_pod.fallingSoundLength = initial(temp_pod.fallingSoundLength) return - var/soundInput = input(holder, "Please pick a sound file to play when the pod lands! NOTICE: Take a note of exactly how long the sound is.", "Pick a Sound File") as null|sound + var/soundInput = input(holder, "Please pick a sound file to play when the pod lands! Sound will start playing and try to end when the pod lands", "Pick a Sound File") as null|sound if (isnull(soundInput)) return - var/timeInput = input(holder, "What is the exact length of the sound file, in seconds. This number will be used to line the sound up so that it finishes right as the pod lands!", "Pick a Sound File", 0.3) as null|num - if (isnull(timeInput)) - return - if (!isnum(timeInput)) - alert(usr, "That wasnt a number! Value set to default ([initial(temp_pod.fallingSoundLength)*0.1]) instead.") + var/sound/tempSound = sound(soundInput) + playsound(holder.mob, tempSound, 1) + var/list/sounds_list = holder.SoundQuery() + var/soundLen = 0 + for (var/playing_sound in sounds_list) + if (isnull(playing_sound)) + stack_trace("client.SoundQuery() Returned a list containing a null sound! Somehow!") + continue + var/sound/found = playing_sound + if (found.file == tempSound.file) + soundLen = found.len + if (!soundLen) + soundLen = input(holder, "Couldn't auto-determine sound file length. What is the exact length of the sound file, in seconds. This number will be used to line the sound up so that it finishes right as the pod lands!", "Pick a Sound File", 0.3) as null|num + if (isnull(soundLen)) + return + if (!isnum(soundLen)) + alert(usr, "That wasn't a number! Value set to default ([initial(temp_pod.fallingSoundLength)*0.1]) instead.") temp_pod.fallingSound = soundInput - temp_pod.fallingSoundLength = 10 * timeInput + temp_pod.fallingSoundLength = 10 * soundLen . = TRUE if("landingSound") //Admin sound from a local file that plays when the pod lands if (!isnull(temp_pod.landingSound)) @@ -387,53 +474,32 @@ temp_pod.soundVolume = soundInput . = TRUE ////////////////////////////STYLE CHANGES////////////////// - //Style is a value that is used to keep track of what the pod is supposed to look like. It can be used with the POD_STYLES list (in cargo.dm defines) + //Style is a value that is used to keep track of what the pod is supposed to look like. It can be used with the GLOB.podstyles list (in cargo.dm defines) //as a way to get the proper icon state, name, and description of the pod. - if("styleStandard") - temp_pod.setStyle(STYLE_STANDARD) - . = TRUE - if("styleBluespace") - temp_pod.setStyle(STYLE_BLUESPACE) - . = TRUE - if("styleSyndie") - temp_pod.setStyle(STYLE_SYNDICATE) - . = TRUE - if("styleBlue") - temp_pod.setStyle(STYLE_BLUE) - . = TRUE - if("styleCult") - temp_pod.setStyle(STYLE_CULT) - . = TRUE - if("styleMissile") - temp_pod.setStyle(STYLE_MISSILE) - . = TRUE - if("styleSMissile") - temp_pod.setStyle(STYLE_RED_MISSILE) - . = TRUE - if("styleBox") - temp_pod.setStyle(STYLE_BOX) + if("tabSwitch") + tabIndex = params["tabIndex"] + refreshView() . = TRUE - if("styleHONK") - temp_pod.setStyle(STYLE_HONK) + if("refreshView") + initMap() + refreshView() . = TRUE - if("styleFruit") - temp_pod.setStyle(STYLE_FRUIT) + if("renderLighting") + renderLighting = !renderLighting . = TRUE - if("styleInvisible") - temp_pod.setStyle(STYLE_INVISIBLE) - . = TRUE - if("styleGondola") - temp_pod.setStyle(STYLE_GONDOLA) - . = TRUE - if("styleSeeThrough") - temp_pod.setStyle(STYLE_SEETHROUGH) + if("setStyle") + var/chosenStyle = params["style"] + temp_pod.setStyle(chosenStyle+1) . = TRUE if("refresh") //Refresh the Pod bay. User should press this if they spawn something new in the centcom bay. Automatically called whenever the user launches a pod refreshBay() . = TRUE if("giveLauncher") //Enters the "Launch Mode". When the launcher is activated, temp_pod is cloned, and the result it filled and launched anywhere the user clicks (unless specificTarget is true) launcherActivated = !launcherActivated - updateCursor(launcherActivated) //Update the cursor of the user to a cool looking target icon + if (picking_dropoff_turf) + picking_dropoff_turf = FALSE //We don't want to have launch mode enabled while we're picking a turf + updateCursor() //Update the cursor of the user to a cool looking target icon + updateSelector() . = TRUE if("clearBay") //Delete all mobs and objs in the selected bay if(alert(usr, "This will delete all objs and mobs in [bay]. Are you sure?", "Confirmation", "Delete that shit", "No") == "Delete that shit") @@ -441,30 +507,59 @@ refreshBay() . = TRUE -/datum/centcom_podlauncher/ui_close() //Uses the destroy() proc. When the user closes the UI, we clean up the temp_pod and supplypod_selector variables. +/datum/centcom_podlauncher/ui_close(mob/user) //Uses the destroy() proc. When the user closes the UI, we clean up the temp_pod and supplypod_selector variables. + QDEL_NULL(temp_pod) + user.client?.clear_map(map_name) + QDEL_NULL(cam_screen) + QDEL_LIST(cam_plane_masters) + QDEL_NULL(cam_background) qdel(src) -/datum/centcom_podlauncher/proc/updateCursor(launching) //Update the moues of the user - if (holder) //Check to see if we have a client - if (launching) //If the launching param is true, we give the user new mouse icons. +/datum/centcom_podlauncher/proc/setupViewPod() + setupView(RANGE_TURFS(2, temp_pod)) + +/datum/centcom_podlauncher/proc/setupViewBay() + var/list/visible_turfs = list() + for(var/turf/bay_turf in bay) + visible_turfs += bay_turf + setupView(visible_turfs) + +/datum/centcom_podlauncher/proc/setupViewDropoff() + var/list/coords_list = temp_pod.reverse_dropoff_coords + var/turf/drop = locate(coords_list[1], coords_list[2], coords_list[3]) + setupView(RANGE_TURFS(3, drop)) + +/datum/centcom_podlauncher/proc/setupView(list/visible_turfs) + var/list/bbox = get_bbox_of_atoms(visible_turfs) + var/size_x = bbox[3] - bbox[1] + 1 + var/size_y = bbox[4] - bbox[2] + 1 + + cam_screen.vis_contents = visible_turfs + cam_background.icon_state = "clear" + cam_background.fill_rect(1, 1, size_x, size_y) + +/datum/centcom_podlauncher/proc/updateCursor(forceClear = FALSE) //Update the mouse of the user + if (!holder) //Can't update the mouse icon if the client doesnt exist! + return + if (!forceClear && (launcherActivated || picking_dropoff_turf)) //If the launching param is true, we give the user new mouse icons. + if(launcherActivated) holder.mouse_up_icon = 'icons/effects/mouse_pointers/supplypod_target.dmi' //Icon for when mouse is released holder.mouse_down_icon = 'icons/effects/mouse_pointers/supplypod_down_target.dmi' //Icon for when mouse is pressed - holder.mouse_override_icon = holder.mouse_up_icon //Icon for idle mouse (same as icon for when released) - holder.mouse_pointer_icon = holder.mouse_override_icon - holder.click_intercept = src //Create a click_intercept so we know where the user is clicking - else - var/mob/M = holder.mob - holder.mouse_up_icon = null - holder.mouse_down_icon = null - holder.mouse_override_icon = null - holder.click_intercept = null - if (M) - M.update_mouse_pointer() //set the moues icons to null, then call update_moues_pointer() which resets them to the correct values based on what the mob is doing (in a mech, holding a spell, etc)() + else if(picking_dropoff_turf) + holder.mouse_up_icon = 'icons/effects/supplypod_pickturf.dmi' //Icon for when mouse is released + holder.mouse_down_icon = 'icons/effects/supplypod_pickturf_down.dmi' //Icon for when mouse is pressed + holder.mouse_pointer_icon = holder.mouse_up_icon //Icon for idle mouse (same as icon for when released) + holder.click_intercept = src //Create a click_intercept so we know where the user is clicking + else + var/mob/holder_mob = holder.mob + holder.mouse_up_icon = null + holder.mouse_down_icon = null + holder.click_intercept = null + holder_mob?.update_mouse_pointer() //set the moues icons to null, then call update_moues_pointer() which resets them to the correct values based on what the mob is doing (in a mech, holding a spell, etc)() /datum/centcom_podlauncher/proc/InterceptClickOn(user,params,atom/target) //Click Intercept so we know where to send pods where the user clicks - var/list/modifiers = params2list(params) - - var/left_click = LAZYACCESS(modifiers, LEFT_CLICK) + var/list/pa = params2list(params) + var/left_click = pa.Find("left") if (launcherActivated) //Clicking on UI elements shouldn't launch a pod if(istype(target,/atom/movable/screen)) @@ -481,11 +576,12 @@ else return //if target is null and we don't have a specific target, cancel if (effectAnnounce) - deadchat_broadcast("A special package is being launched at the station!", turf_target = target, message_type=DEADCHAT_ANNOUNCEMENT) + deadchat_broadcast("A special package is being launched at the station!", turf_target = target) var/list/bouttaDie = list() - for (var/mob/living/M in target) - bouttaDie.Add(M) - supplypod_punish_log(bouttaDie, target) + for (var/mob/living/target_mob in target) + bouttaDie.Add(target_mob) + if (holder.holder) + supplypod_punish_log(bouttaDie) if (!effectBurst) //If we're not using burst mode, just launch normally. launch(target) else @@ -493,95 +589,153 @@ if (isnull(target)) break //if our target gets deleted during this, we stop the show preLaunch() //Same as above - var/LZ = locate(target.x + rand(-1,1), target.y + rand(-1,1), target.z) //Pods are randomly adjacent to (or the same as) the target - if (LZ) //just incase we're on the edge of the map or something that would cause target.x+1 to fail - launch(LZ) //launch the pod at the adjacent turf + var/landingzone = locate(target.x + rand(-1,1), target.y + rand(-1,1), target.z) //Pods are randomly adjacent to (or the same as) the target + if (landingzone) //just incase we're on the edge of the map or something that would cause target.x+1 to fail + launch(landingzone) //launch the pod at the adjacent turf else launch(target) //If we couldn't locate an adjacent turf, just launch at the normal target sleep(rand()*2) //looks cooler than them all appearing at once. Gives the impression of burst fire. + else if (picking_dropoff_turf) + //Clicking on UI elements shouldn't pick a dropoff turf + if(istype(target, /atom/movable/screen)) + return FALSE + + . = TRUE + if(left_click) //When we left click: + var/turf/target_turf = get_turf(target) + setDropoff(target_turf) + customDropoff = TRUE + to_chat(user, " You've selected [target_turf] at [COORD(target_turf)] as your dropoff location.") + +/datum/centcom_podlauncher/proc/refreshView() + switch(tabIndex) + if (TAB_POD) + setupViewPod() + if (TAB_BAY) + setupViewBay() + else + setupViewDropoff() /datum/centcom_podlauncher/proc/refreshBay() //Called whenever the bay is switched, as well as wheneber a pod is launched + bay = GLOB.supplypod_loading_bays[bayNumber] orderedArea = createOrderedArea(bay) //Create an ordered list full of turfs form the bay preLaunch() //Fill acceptable turfs from orderedArea, then fill launchList from acceptableTurfs (see proc for more info) + refreshView() -/datum/centcom_podlauncher/proc/createOrderedArea(area/A) //This assumes the area passed in is a continuous square - if (isnull(A)) //If theres no supplypod bay mapped into centcom, throw an error +/datum/centcom_podlauncher/proc/createOrderedArea(area/area_to_order) //This assumes the area passed in is a continuous square + if (isnull(area_to_order)) //If theres no supplypod bay mapped into centcom, throw an error to_chat(holder.mob, "No /area/centcom/supplypod/loading/one (or /two or /three or /four) in the world! You can make one yourself (then refresh) for now, but yell at a mapper to fix this, today!") CRASH("No /area/centcom/supplypod/loading/one (or /two or /three or /four) has been mapped into the centcom z-level!") orderedArea = list() - if (length(A.contents)) //Go through the area passed into the proc, and figure out the top left and bottom right corners by calculating max and min values - var/startX = A.contents[1].x //Create the four values (we do it off a.contents[1] so they have some sort of arbitrary initial value. They should be overwritten in a few moments) - var/endX = A.contents[1].x - var/startY = A.contents[1].y - var/endY = A.contents[1].y - for (var/turf/T in A) //For each turf in the area, go through and find: - if (T.x < startX) //The turf with the smallest x value. This is our startX - startX = T.x - else if (T.x > endX) //The turf with the largest x value. This is our endX - endX = T.x - else if (T.y > startY) //The turf with the largest Y value. This is our startY - startY = T.y - else if (T.y < endY) //The turf with the smallest Y value. This is our endY - endY = T.y - for (var/i in endY to startY) - for (var/j in startX to endX) - orderedArea.Add(locate(j,startY - (i - endY),1)) //After gathering the start/end x and y, go through locating each turf from top left to bottom right, like one would read a book + if (length(area_to_order.contents)) //Go through the area passed into the proc, and figure out the top left and bottom right corners by calculating max and min values + var/startX = area_to_order.contents[1].x //Create the four values (we do it off a.contents[1] so they have some sort of arbitrary initial value. They should be overwritten in a few moments) + var/endX = area_to_order.contents[1].x + var/startY = area_to_order.contents[1].y + var/endY = area_to_order.contents[1].y + for (var/turf/turf_in_area in area_to_order) //For each turf in the area, go through and find: + if (turf_in_area.x < startX) //The turf with the smallest x value. This is our startX + startX = turf_in_area.x + else if (turf_in_area.x > endX) //The turf with the largest x value. This is our endX + endX = turf_in_area.x + else if (turf_in_area.y > startY) //The turf with the largest Y value. This is our startY + startY = turf_in_area.y + else if (turf_in_area.y < endY) //The turf with the smallest Y value. This is our endY + endY = turf_in_area.y + for (var/vertical in endY to startY) + for (var/horizontal in startX to endX) + orderedArea.Add(locate(horizontal, startY - (vertical - endY), 1)) //After gathering the start/end x and y, go through locating each turf from top left to bottom right, like one would read a book return orderedArea //Return the filled list /datum/centcom_podlauncher/proc/preLaunch() //Creates a list of acceptable items, numTurfs = 0 //Counts the number of turfs that can be launched (remember, supplypods either launch all at once or one turf-worth of items at a time) acceptableTurfs = list() - for (var/turf/T in orderedArea) //Go through the orderedArea list - if (typecache_filter_list_reverse(T.contents, ignored_atoms).len != 0) //if there is something in this turf that isnt in the blacklist, we consider this turf "acceptable" and add it to the acceptableTurfs list - acceptableTurfs.Add(T) //Because orderedArea was an ordered linear list, acceptableTurfs will be as well. + for (var/t in orderedArea) //Go through the orderedArea list + var/turf/unchecked_turf = t + if (iswallturf(unchecked_turf) || typecache_filter_list_reverse(unchecked_turf.contents, ignored_atoms).len != 0) //if there is something in this turf that isn't in the blacklist, we consider this turf "acceptable" and add it to the acceptableTurfs list + acceptableTurfs.Add(unchecked_turf) //Because orderedArea was an ordered linear list, acceptableTurfs will be as well. numTurfs ++ launchList = list() //Anything in launchList will go into the supplypod when it is launched if (length(acceptableTurfs) && !temp_pod.reversing && !temp_pod.effectMissile) //We dont fill the supplypod if acceptableTurfs is empty, if the pod is going in reverse (effectReverse=true), or if the pod is acitng like a missile (effectMissile=true) switch(launchChoice) - if(0) //If we are launching all the turfs at once - for (var/turf/T in acceptableTurfs) - launchList |= typecache_filter_list_reverse(T.contents, ignored_atoms) //We filter any blacklisted atoms and add the rest to the launchList - if(1) //If we are launching one at a time + if(LAUNCH_ALL) //If we are launching all the turfs at once + for (var/t in acceptableTurfs) + var/turf/accepted_turf = t + launchList |= typecache_filter_list_reverse(accepted_turf.contents, ignored_atoms) //We filter any blacklisted atoms and add the rest to the launchList + if (iswallturf(accepted_turf)) + launchList += accepted_turf + if(LAUNCH_ORDERED) //If we are launching one at a time if (launchCounter > acceptableTurfs.len) //Check if the launchCounter, which acts as an index, is too high. If it is, reset it to 1 launchCounter = 1 //Note that the launchCounter index is incremented in the launch() proc - for (var/atom/movable/O in acceptableTurfs[launchCounter].contents) //Go through the acceptableTurfs list based on the launchCounter index - launchList |= typecache_filter_list_reverse(acceptableTurfs[launchCounter].contents, ignored_atoms) //Filter the specicic turf chosen from acceptableTurfs, and add it to the launchList - if(2) //If we are launching randomly - launchList |= typecache_filter_list_reverse(pick_n_take(acceptableTurfs).contents, ignored_atoms) //filter a random turf from the acceptableTurfs list and add it to the launchList + var/turf/next_turf_in_line = acceptableTurfs[launchCounter] + launchList |= typecache_filter_list_reverse(next_turf_in_line.contents, ignored_atoms) //Filter the specicic turf chosen from acceptableTurfs, and add it to the launchList + if (iswallturf(next_turf_in_line)) + launchList += next_turf_in_line + if(LAUNCH_RANDOM) //If we are launching randomly + var/turf/acceptable_turf = pick_n_take(acceptableTurfs) + launchList |= typecache_filter_list_reverse(acceptable_turf.contents, ignored_atoms) //filter a random turf from the acceptableTurfs list and add it to the launchList + if (iswallturf(acceptable_turf)) + launchList += acceptable_turf updateSelector() //Call updateSelector(), which, if we are launching one at a time (launchChoice==2), will move to the next turf that will be launched //UpdateSelector() is here (instead if the if(1) switch block) because it also moves the selector to nullspace (to hide it) if needed -/datum/centcom_podlauncher/proc/launch(turf/A) //Game time started - if (isnull(A)) +/datum/centcom_podlauncher/proc/launch(turf/target_turf) //Game time started + if (isnull(target_turf)) return var/obj/structure/closet/supplypod/centcompod/toLaunch = DuplicateObject(temp_pod) //Duplicate the temp_pod (which we have been varediting or configuring with the UI) and store the result - toLaunch.bay = bay //Bay is currently a nonstatic expression, so it cant go into toLaunch using DuplicateObject toLaunch.update_appearance()//we update_appearance() here so that the door doesnt "flicker on" right after it lands - var/shippingLane = GLOB.areas_by_type[/area/centcom/supplypod/flyMeToTheMoon] + var/shippingLane = GLOB.areas_by_type[/area/centcom/supplypod/supplypod_temp_holding] toLaunch.forceMove(shippingLane) if (launchClone) //We arent launching the actual items from the bay, rather we are creating clones and launching those if(launchRandomItem) - var/atom/movable/O = pick_n_take(launchList) - DuplicateObject(O).forceMove(toLaunch) //Duplicate a single atom/movable from launchList and forceMove it into the supplypod + var/launch_candidate = pick_n_take(launchList) + if(!isnull(launch_candidate)) + if (iswallturf(launch_candidate)) + var/atom/atom_to_launch = launch_candidate + toLaunch.turfs_in_cargo += atom_to_launch.type + else + var/atom/movable/movable_to_launch = launch_candidate + DuplicateObject(movable_to_launch).forceMove(toLaunch) //Duplicate a single atom/movable from launchList and forceMove it into the supplypod else - for (var/atom/movable/O in launchList) - DuplicateObject(O).forceMove(toLaunch) //Duplicate each atom/movable in launchList and forceMove them into the supplypod + for (var/launch_candidate in launchList) + if (isnull(launch_candidate)) + continue + if (iswallturf(launch_candidate)) + var/turf/turf_to_launch = launch_candidate + toLaunch.turfs_in_cargo += turf_to_launch.type + else + var/atom/movable/movable_to_launch = launch_candidate + DuplicateObject(movable_to_launch).forceMove(toLaunch) //Duplicate each atom/movable in launchList and forceMove them into the supplypod else if(launchRandomItem) - var/atom/movable/O = pick_n_take(launchList) - O.forceMove(toLaunch) //and forceMove any atom/moveable into the supplypod + var/atom/random_item = pick_n_take(launchList) + if(!isnull(random_item)) + if (iswallturf(random_item)) + var/turf/wall = random_item + toLaunch.turfs_in_cargo += wall.type + wall.ScrapeAway() + else + var/atom/movable/random_item_movable = random_item + random_item_movable.forceMove(toLaunch) //and forceMove any atom/moveable into the supplypod else - for (var/atom/movable/O in launchList) //If we aren't cloning the objects, just go through the launchList - O.forceMove(toLaunch) //and forceMove any atom/moveable into the supplypod - new /obj/effect/DPtarget(A, toLaunch) //Then, create the DPTarget effect, which will eventually forceMove the temp_pod to it's location + for (var/thing_to_launch in launchList) //If we aren't cloning the objects, just go through the launchList + if (isnull(thing_to_launch)) + continue + if(iswallturf(thing_to_launch)) + var/turf/wall = thing_to_launch + toLaunch.turfs_in_cargo += wall.type + wall.ScrapeAway() + else + var/atom/movable/movable_to_launch = thing_to_launch + movable_to_launch.forceMove(toLaunch) //and forceMove any atom/moveable into the supplypod + new /obj/effect/pod_landingzone(target_turf, toLaunch) //Then, create the DPTarget effect, which will eventually forceMove the temp_pod to it's location if (launchClone) launchCounter++ //We only need to increment launchCounter if we are cloning objects. //If we aren't cloning objects, taking and removing the first item each time from the acceptableTurfs list will inherently iterate through the list in order /datum/centcom_podlauncher/proc/updateSelector() //Ensures that the selector effect will showcase the next item if needed - if (launchChoice == 1 && length(acceptableTurfs) && !temp_pod.reversing && !temp_pod.effectMissile) //We only show the selector if we are taking items from the bay - var/index = launchCounter + 1 //launchCounter acts as an index to the ordered acceptableTurfs list, so adding one will show the next item in the list + if (launchChoice == LAUNCH_ORDERED && length(acceptableTurfs) > 1 && !temp_pod.reversing && !temp_pod.effectMissile) //We only show the selector if we are taking items from the bay + var/index = (launchCounter == 1 ? launchCounter : launchCounter + 1) //launchCounter acts as an index to the ordered acceptableTurfs list, so adding one will show the next item in the list. We don't want to do this for the very first item tho if (index > acceptableTurfs.len) //out of bounds check index = 1 selector.forceMove(acceptableTurfs[index]) //forceMove the selector to the next turf in the ordered acceptableTurfs list @@ -593,31 +747,102 @@ qdel(O) for (var/mob/M in bay.GetAllContents()) qdel(M) + for (var/bayturf in bay) + var/turf/turf_to_clear = bayturf + turf_to_clear.ChangeTurf(/turf/open/floor/plasteel) /datum/centcom_podlauncher/Destroy() //The Destroy() proc. This is called by ui_close proc, or whenever the user leaves the game - updateCursor(FALSE) //Make sure our moues cursor resets to default. False means we are not in launch mode - qdel(temp_pod) //Delete the temp_pod - qdel(selector) //Delete the selector effect + updateCursor(TRUE) //Make sure our mouse cursor resets to default. False means we are not in launch mode + QDEL_NULL(temp_pod) //Delete the temp_pod + QDEL_NULL(selector) //Delete the selector effect + QDEL_NULL(indicator) . = ..() -/datum/centcom_podlauncher/proc/supplypod_punish_log(list/whoDyin, atom/target) +/datum/centcom_podlauncher/proc/supplypod_punish_log(list/whoDyin) var/podString = effectBurst ? "5 pods" : "a pod" var/whomString = "" if (LAZYLEN(whoDyin)) for (var/mob/living/M in whoDyin) - whomString += "[key_name(M) || "nobody"], " - - var/delayString = temp_pod.landingDelay == initial(temp_pod.landingDelay) ? "" : " Delay=[temp_pod.landingDelay*0.1]s" - var/damageString = temp_pod.damage == 0 ? "" : " Dmg=[temp_pod.damage]" - var/explosionString = "" - var/explosion_sum = temp_pod.explosionSize[1] + temp_pod.explosionSize[2] + temp_pod.explosionSize[3] + temp_pod.explosionSize[4] - if (explosion_sum != 0) - explosionString = " Boom=|" - for (var/X in temp_pod.explosionSize) - explosionString += "[X]|" - - var/msg = "launched [podString] towards [whomString] [delayString][damageString][explosionString]" - message_admins("[key_name_admin(usr)] [msg] in [ADMIN_VERBOSEJMP(specificTarget || target)].") + whomString += "[key_name(M)], " + + var/msg = "launched [podString] towards [whomString]" + message_admins("[key_name_admin(usr)] [msg] in [ADMIN_VERBOSEJMP(specificTarget)].") if (length(whoDyin)) for (var/mob/living/M in whoDyin) admin_ticket_log(M, "[key_name_admin(usr)] [msg]") + +/datum/centcom_podlauncher/proc/loadData(list/dataToLoad) + bayNumber = dataToLoad["bayNumber"] + customDropoff = dataToLoad["customDropoff"] + renderLighting = dataToLoad["renderLighting"] + launchClone = dataToLoad["launchClone"] //Do we launch the actual items in the bay or just launch clones of them? + launchRandomItem = dataToLoad["launchRandomItem"] //Do we launch a single random item instead of everything on the turf? + launchChoice = dataToLoad["launchChoice"] //Launch turfs all at once (0), ordered (1), or randomly(1) + explosionChoice = dataToLoad["explosionChoice"] //An explosion that occurs when landing. Can be no explosion (0), custom explosion (1), or maxcap (2) + damageChoice = dataToLoad["damageChoice"] //Damage that occurs to any mob under the pod when it lands. Can be no damage (0), custom damage (1), or gib+5000dmg (2) + temp_pod.delays = dataToLoad["delays"] + temp_pod.reverse_delays = dataToLoad["rev_delays"] + temp_pod.custom_rev_delay = dataToLoad["custom_rev_delay"] + temp_pod.setStyle(dataToLoad["styleChoice"]) //Style is a variable that keeps track of what the pod is supposed to look like. It acts as an index to the GLOB.podstyles list in cargo.dm defines to get the proper icon/name/desc for the pod. + temp_pod.effectStun = dataToLoad["effectStun"]//If true, stuns anyone under the pod when it launches until it lands, forcing them to get hit by the pod. Devilish! + temp_pod.effectLimb = dataToLoad["effectLimb"]//If true, pops off a limb (if applicable) from anyone caught under the pod when it lands + temp_pod.effectOrgans = dataToLoad["effectOrgans"]//If true, yeets the organs out of any bodies caught under the pod when it lands + temp_pod.bluespace = dataToLoad["effectBluespace"] //If true, the pod deletes (in a shower of sparks) after landing + temp_pod.effectStealth = dataToLoad["effectStealth"]//If true, a target icon isn't displayed on the turf where the pod will land + temp_pod.effectQuiet = dataToLoad["effectQuiet"] //The female sniper. If true, the pod makes no noise (including related explosions, opening sounds, etc) + temp_pod.effectMissile = dataToLoad["effectMissile"] //If true, the pod deletes the second it lands. If you give it an explosion, it will act like a missile exploding as it hits the ground + temp_pod.effectCircle = dataToLoad["effectCircle"] //If true, allows the pod to come in at any angle. Bit of a weird feature but whatever its here + effectBurst = dataToLoad["effectBurst"] //IOf true, launches five pods at once (with a very small delay between for added coolness), in a 3x3 area centered around the area + temp_pod.reversing = dataToLoad["effectReverse"] //If true, the pod will not send any items. Instead, after opening, it will close again (picking up items/mobs) and fly back to centcom + temp_pod.reverseOptionList = dataToLoad["reverseOptionList"] + specificTarget = dataToLoad["effectTarget"] //Launches the pod at the turf of a specific mob target, rather than wherever the user clicked. Useful for smites + temp_pod.adminNamed = dataToLoad["effectName"] //Determines whether or not the pod has been named by an admin. If true, the pod's name will not get overridden when the style of the pod changes (changing the style of the pod normally also changes the name+desc) + temp_pod.name = dataToLoad["podName"] + temp_pod.desc = dataToLoad["podDesc"] + effectAnnounce = dataToLoad["effectAnnounce"] + numTurfs = dataToLoad["numObjects"] //Counts the number of turfs that contain a launchable object in the centcom supplypod bay + temp_pod.fallingSound = dataToLoad["fallingSound"]//Admin sound to play as the pod falls + temp_pod.landingSound = dataToLoad["landingSound"]//Admin sound to play when the pod lands + temp_pod.openingSound = dataToLoad["openingSound"]//Admin sound to play when the pod opens + temp_pod.leavingSound = dataToLoad["leavingSound"]//Admin sound to play when the pod leaves + temp_pod.soundVolume = dataToLoad["soundVolume"] //Admin sound to play when the pod leaves + picking_dropoff_turf = FALSE + launcherActivated = FALSE + updateCursor() + refreshView() + +GLOBAL_DATUM_INIT(podlauncher, /datum/centcom_podlauncher, new) +//Proc for admins to enable others to use podlauncher after roundend +/datum/centcom_podlauncher/proc/give_podlauncher(mob/living/user, override) + if (SSticker.current_state < GAME_STATE_FINISHED) + return + if (!istype(user)) + user = override + if (user) + setup(user)//setup the datum + +//Set the dropoff location and indicator to either a specific turf or somewhere in an area +/datum/centcom_podlauncher/proc/setDropoff(target) + var/turf/target_turf + if (isturf(target)) + target_turf = target + else if (isarea(target)) + target_turf = pick(get_area_turfs(target)) + else + CRASH("Improper type passed to setDropoff! Should be /turf or /area") + temp_pod.reverse_dropoff_coords = list(target_turf.x, target_turf.y, target_turf.z) + indicator.forceMove(target_turf) + +/obj/effect/hallucination/simple/supplypod_selector + name = "Supply Selector (Only you can see this)" + image_icon = 'icons/obj/supplypods_32x32.dmi' + image_state = "selector" + image_layer = FLY_LAYER + alpha = 150 + +/obj/effect/hallucination/simple/dropoff_location + name = "Dropoff Location (Only you can see this)" + image_icon = 'icons/obj/supplypods_32x32.dmi' + image_state = "dropoff_indicator" + image_layer = FLY_LAYER + alpha = 0 diff --git a/code/modules/cargo/expressconsole.dm b/code/modules/cargo/expressconsole.dm index 9074a87d7bbe..9f615a5ba3ee 100644 --- a/code/modules/cargo/expressconsole.dm +++ b/code/modules/cargo/expressconsole.dm @@ -244,7 +244,7 @@ name = usr.real_name rank = "Silicon" var/datum/supply_order/SO = new(pack, name, rank, usr.ckey, "") - new /obj/effect/DPtarget(landing_turf, podType, SO) + new /obj/effect/pod_landingzone(landing_turf, podType, SO) update_appearance() // ?????????????????? return TRUE diff --git a/code/modules/cargo/gondolapod.dm b/code/modules/cargo/gondolapod.dm index 72d4d409ccb6..69a9c7d286b4 100644 --- a/code/modules/cargo/gondolapod.dm +++ b/code/modules/cargo/gondolapod.dm @@ -10,9 +10,9 @@ response_harm_simple = "kick" faction = list("gondola") turns_per_move = 10 - icon = 'icons/mob/gondolapod.dmi' - icon_state = "gondolapod" - icon_living = "gondolapod" + icon = 'icons/obj/supplypods.dmi' + icon_state = "gondola" + icon_living = "gondola" pixel_x = -16//2x2 sprite base_pixel_x = -16 pixel_y = -5 @@ -34,11 +34,10 @@ name = linked_pod.name . = ..() -/mob/living/simple_animal/pet/gondola/gondolapod/update_icon_state() +/mob/living/simple_animal/pet/gondola/gondolapod/update_overlays() + . = ..() if(opened) - icon_state = "gondolapod_open" - else - icon_state = "gondolapod" + . += "[icon_state]_open" return ..() /mob/living/simple_animal/pet/gondola/gondolapod/verb/deliver() @@ -64,12 +63,12 @@ else to_chat(src, "A closer look inside yourself reveals... nothing.") -/mob/living/simple_animal/pet/gondola/gondolapod/proc/setOpened() +/mob/living/simple_animal/pet/gondola/gondolapod/setOpened() opened = TRUE update_appearance() - addtimer(CALLBACK(src, .proc/setClosed), 50) + addtimer(CALLBACK(src, /atom/.proc/setClosed), 50) -/mob/living/simple_animal/pet/gondola/gondolapod/proc/setClosed() +/mob/living/simple_animal/pet/gondola/gondolapod/setClosed() opened = FALSE update_appearance() diff --git a/code/modules/cargo/supplypod.dm b/code/modules/cargo/supplypod.dm index f33ade28bfb8..314484a5a668 100644 --- a/code/modules/cargo/supplypod.dm +++ b/code/modules/cargo/supplypod.dm @@ -1,13 +1,12 @@ -//The "BDPtarget" temp visual is created by anything that "launches" a supplypod. It makes two things: a falling droppod animation, and the droppod itself. +//The "pod_landingzone" temp visual is created by anything that "launches" a supplypod. It makes two things: a falling droppod animation, and the droppod itself. //------------------------------------SUPPLY POD-------------------------------------// /obj/structure/closet/supplypod name = "supply pod" //Names and descriptions are normally created with the setStyle() proc during initialization, but we have these default values here as a failsafe desc = "A Nanotrasen supply drop pod." icon = 'icons/obj/supplypods.dmi' - icon_state = "supplypod" - pixel_x = -16 //2x2 sprite - pixel_y = -5 - layer = TABLE_LAYER //So that the crate inside doesn't appear underneath + icon_state = "pod" //This is a common base sprite shared by a number of pods + pixel_x = SUPPLYPOD_X_OFFSET //2x2 sprite + layer = BELOW_OBJ_LAYER //So that the crate inside doesn't appear underneath allow_objects = TRUE allow_dense = TRUE delivery_icon = null @@ -16,12 +15,16 @@ anchored = TRUE //So it cant slide around after landing anchorable = FALSE flags_1 = PREVENT_CONTENTS_EXPLOSION_1 + appearance_flags = KEEP_TOGETHER | PIXEL_SCALE + density = FALSE + ///List of bitflags for supply pods, see: code\__DEFINES\obj_flags.dm + var/pod_flags = NONE //*****NOTE*****: Many of these comments are similarly described in centcom_podlauncher.dm. If you change them here, please consider doing so in the centcom podlauncher code as well! var/adminNamed = FALSE //Determines whether or not the pod has been named by an admin. If true, the pod's name will not get overridden when the style of the pod changes (changing the style of the pod normally also changes the name+desc) var/bluespace = FALSE //If true, the pod deletes (in a shower of sparks) after landing - var/landingDelay = 30 //How long the pod takes to land after launching - var/openingDelay = 30 //How long the pod takes to open after landing - var/departureDelay = 30 //How long the pod takes to leave after opening. If bluespace = TRUE, it deletes. If reversing = TRUE, it flies back to centcom. + var/delays = list(POD_TRANSIT = 30, POD_FALLING = 4, POD_OPENING = 30, POD_LEAVING = 30) + var/reverse_delays = list(POD_TRANSIT = 30, POD_FALLING = 4, POD_OPENING = 30, POD_LEAVING = 30) + var/custom_rev_delay = FALSE var/damage = 0 //Damage that occurs to any mob under the pod when it lands. var/effectStun = FALSE //If true, stuns anyone under the pod when it launches until it lands, forcing them to get hit by the pod. Devilish! var/effectLimb = FALSE //If true, pops off a limb (if applicable) from anyone caught under the pod when it lands @@ -31,9 +34,9 @@ var/effectQuiet = FALSE //The female sniper. If true, the pod makes no noise (including related explosions, opening sounds, etc) var/effectMissile = FALSE //If true, the pod deletes the second it lands. If you give it an explosion, it will act like a missile exploding as it hits the ground var/effectCircle = FALSE //If true, allows the pod to come in at any angle. Bit of a weird feature but whatever its here - var/style = STYLE_STANDARD //Style is a variable that keeps track of what the pod is supposed to look like. It acts as an index to the POD_STYLES list in cargo.dm defines to get the proper icon/name/desc for the pod. + var/style = STYLE_STANDARD //Style is a variable that keeps track of what the pod is supposed to look like. It acts as an index to the GLOB.podstyles list in cargo.dm defines to get the proper icon/name/desc for the pod. var/reversing = FALSE //If true, the pod will not send any items. Instead, after opening, it will close again (picking up items/mobs) and fly back to centcom - var/fallDuration = 4 + var/list/reverse_dropoff_coords //Turf that the reverse pod will drop off it's newly-acquired cargo to var/fallingSoundLength = 11 var/fallingSound = 'sound/weapons/mortar_long_whistle.ogg'//Admin sound to play before the pod lands var/landingSound //Admin sound to play when the pod lands @@ -43,13 +46,21 @@ var/bay //Used specifically for the centcom_podlauncher datum. Holds the current bay the user is launching objects from. Bays are specific rooms on the centcom map. var/list/explosionSize = list(0,0,2,3) var/stay_after_drop = FALSE - var/specialised = TRUE // It's not a general use pod for cargo/admin use + var/specialised = FALSE // It's not a general use pod for cargo/admin use + var/rubble_type //Rubble effect associated with this supplypod + var/decal = "default" //What kind of extra decals we add to the pod to make it look nice + var/door = "pod_door" + var/fin_mask = "topfin" + var/obj/effect/supplypod_rubble/rubble + var/obj/effect/engineglow/glow_effect + var/list/reverseOptionList = list("Mobs"=FALSE,"Objects"=FALSE,"Anchored"=FALSE,"Underfloor"=FALSE,"Wallmounted"=FALSE,"Floors"=FALSE,"Walls"=FALSE) + var/list/turfs_in_cargo = list() /obj/structure/closet/supplypod/bluespacepod style = STYLE_BLUESPACE bluespace = TRUE explosionSize = list(0,0,1,2) - landingDelay = 15 //Slightly quicker than the supplypod + delays = list(POD_TRANSIT = 15, POD_FALLING = 4, POD_OPENING = 30, POD_LEAVING = 30) /obj/structure/closet/supplypod/extractionpod name = "Syndicate Extraction Pod" @@ -58,47 +69,109 @@ style = STYLE_SYNDICATE bluespace = TRUE explosionSize = list(0,0,1,2) - landingDelay = 25 //Longer than others + delays = list(POD_TRANSIT = 25, POD_FALLING = 4, POD_OPENING = 30, POD_LEAVING = 30) /obj/structure/closet/supplypod/centcompod style = STYLE_CENTCOM bluespace = TRUE explosionSize = list(0,0,0,0) - landingDelay = 20 //Very speedy! + delays = list(POD_TRANSIT = 20, POD_FALLING = 4, POD_OPENING = 30, POD_LEAVING = 30) resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF +/obj/structure/closet/supplypod/Initialize(mapload, customStyle = FALSE) + . = ..() + if (!loc) + var/shippingLane = GLOB.areas_by_type[/area/centcom/supplypod/supplypod_temp_holding] //temporary holder for supplypods mid-transit + forceMove(shippingLane) + if (customStyle) + style = customStyle + setStyle(style) //Upon initialization, give the supplypod an iconstate, name, and description based on the "style" variable. This system is important for the centcom_podlauncher to function correctly + +/obj/structure/closet/supplypod/extractionpod/Initialize() + . = ..() + var/turf/picked_turf = pick(GLOB.holdingfacility) + reverse_dropoff_coords = list(picked_turf.x, picked_turf.y, picked_turf.z) -/obj/structure/closet/supplypod/proc/specialisedPod() - return 1 +/obj/structure/closet/supplypod/proc/setStyle(chosenStyle) //Used to give the sprite an icon state, name, and description. + style = chosenStyle + var/base = GLOB.podstyles[chosenStyle][POD_BASE] //GLOB.podstyles is a 2D array we treat as a dictionary. The style represents the verticle index, with the icon state, name, and desc being stored in the horizontal indexes of the 2D array. + icon_state = base + decal = GLOB.podstyles[chosenStyle][POD_DECAL] + rubble_type = GLOB.podstyles[chosenStyle][POD_RUBBLE_TYPE] + if (!adminNamed && !specialised) //We dont want to name it ourselves if it has been specifically named by an admin using the centcom_podlauncher datum + name = GLOB.podstyles[chosenStyle][POD_NAME] + desc = GLOB.podstyles[chosenStyle][POD_DESC] + if (GLOB.podstyles[chosenStyle][POD_DOOR]) + door = "[base]_door" + else + door = FALSE + update_appearance() -/obj/structure/closet/supplypod/extractionpod/specialisedPod(atom/movable/holder) - holder.forceMove(pick(GLOB.holdingfacility)) // land in ninja jail - open_pod(holder, forced = TRUE) +/obj/structure/closet/supplypod/proc/SetReverseIcon() + fin_mask = "bottomfin" + if (GLOB.podstyles[style][POD_SHAPE] == POD_SHAPE_NORML) + icon_state = GLOB.podstyles[style][POD_BASE] + "_reverse" + pixel_x = initial(pixel_x) + transform = matrix() + update_appearance() -/obj/structure/closet/supplypod/Initialize() - . = ..() - setStyle(style, TRUE) //Upon initialization, give the supplypod an iconstate, name, and description based on the "style" variable. This system is important for the centcom_podlauncher to function correctly +/obj/structure/closet/supplypod/proc/backToNonReverseIcon() + fin_mask = initial(fin_mask) + if (GLOB.podstyles[style][POD_SHAPE] == POD_SHAPE_NORML) + icon_state = GLOB.podstyles[style][POD_BASE] + pixel_x = initial(pixel_x) + transform = matrix() + update_appearance() /obj/structure/closet/supplypod/update_overlays() . = ..() - if (style == STYLE_SEETHROUGH || style == STYLE_INVISIBLE) //If we're invisible, we dont bother adding any overlays + if (style == STYLE_INVISIBLE) + return + if (rubble) + . += rubble.getForeground(src) + if (style == STYLE_SEETHROUGH) + for (var/atom/A in contents) + var/mutable_appearance/itemIcon = new(A) + itemIcon.transform = matrix().Translate(-1 * SUPPLYPOD_X_OFFSET, 0) + . += itemIcon + for (var/t in turfs_in_cargo)//T is just a turf's type + var/turf/turf_type = t + var/mutable_appearance/itemIcon = mutable_appearance(initial(turf_type.icon), initial(turf_type.icon_state)) + itemIcon.transform = matrix().Translate(-1 * SUPPLYPOD_X_OFFSET, 0) + . += itemIcon return - else - if (opened) - . += "[icon_state]_open" - else - . += "[icon_state]_door" -/obj/structure/closet/supplypod/proc/setStyle(chosenStyle, duringInit = FALSE) //Used to give the sprite an icon state, name, and description - if (!duringInit && style == chosenStyle) //Check if the input style is already the same as the pod's style. This happens in centcom_podlauncher, and as such we set the style to STYLE_CENTCOM. - setStyle(STYLE_CENTCOM) //We make sure to not check this during initialize() so the standard supplypod works correctly. + if (opened) //We're opened means all we have to worry about is masking a decal if we have one + if (!decal) //We don't have a decal to mask + return + if (!door) //We have a decal but no door, so let's just add the decal + . += decal + return + var/icon/masked_decal = new(icon, decal) //The decal we want to apply + var/icon/door_masker = new(icon, door) //The door shape we want to 'cut out' of the decal + door_masker.MapColors(0,0,0,1, 0,0,0,1, 0,0,0,1, 1,1,1,0, 0,0,0,1) + door_masker.SwapColor("#ffffffff", null) + door_masker.Blend("#000000", ICON_SUBTRACT) + masked_decal.Blend(door_masker, ICON_ADD) + . += masked_decal return - style = chosenStyle - icon_state = POD_STYLES[chosenStyle][POD_ICON_STATE] //POD_STYLES is a 2D array we treat as a dictionary. The style represents the verticle index, with the icon state, name, and desc being stored in the horizontal indexes of the 2D array. - if (!adminNamed && !specialised) //We dont want to name it ourselves if it has been specifically named by an admin using the centcom_podlauncher datum - name = POD_STYLES[chosenStyle][POD_NAME] - desc = POD_STYLES[chosenStyle][POD_DESC] - update_appearance() + //If we're closed + if(!door) //We have no door, lets see if we have a decal. If not, theres nothing we need to do + if(decal) + . += decal + return + else if (GLOB.podstyles[style][POD_SHAPE] != POD_SHAPE_NORML) //If we're not a normal pod shape (aka, if we don't have fins), just add the door without masking + . += door + else + var/icon/masked_door = new(icon, door) //The door we want to apply + var/icon/fin_masker = new(icon, "mask_[fin_mask]") //The fin shape we want to 'cut out' of the door + fin_masker.MapColors(0,0,0,1, 0,0,0,1, 0,0,0,1, 1,1,1,0, 0,0,0,1) + fin_masker.SwapColor("#ffffffff", null) + fin_masker.Blend("#000000", ICON_SUBTRACT) + masked_door.Blend(fin_masker, ICON_ADD) + . += masked_door + if(decal) + . += decal /obj/structure/closet/supplypod/tool_interact(obj/item/W, mob/user) if(bluespace) //We dont want to worry about interacting with bluespace pods, as they are due to delete themselves soon anyways. @@ -115,86 +188,87 @@ /obj/structure/closet/supplypod/toggle(mob/living/user) return -/obj/structure/closet/supplypod/open(mob/living/user, force = TRUE) //Supplypods shouldn't be able to be manually opened under any circumstances +/obj/structure/closet/supplypod/open(mob/living/user, force = TRUE) return -/obj/structure/closet/supplypod/proc/handleReturningClose(atom/movable/holder, returntobay) - opened = FALSE - INVOKE_ASYNC(holder, .proc/setClosed) //Use the INVOKE_ASYNC proc to call setClosed() on whatever the holder may be, without giving the atom/movable base class a setClosed() proc definition - for (var/atom/movable/O in get_turf(holder)) - if ((ismob(O) && !isliving(O)) || (is_type_in_typecache(O, GLOB.blacklisted_cargo_types) && !isliving(O))) //We dont want to take ghosts with us, and we don't want blacklisted items going, but we allow mobs. - continue - O.forceMove(holder) //Put objects inside before we close - var/obj/effect/temp_visual/risingPod = new /obj/effect/DPfall(get_turf(holder), src) //Make a nice animation of flying back up - risingPod.pixel_z = 0 //The initial value of risingPod's pixel_z is 200 because it normally comes down from a high spot - animate(risingPod, pixel_z = 200, time = 10, easing = LINEAR_EASING) //Animate our rising pod - if (returntobay) - holder.forceMove(bay) //Move the pod back to centcom, where it belongs - QDEL_IN(risingPod, 10) - reversing = FALSE //Now that we're done reversing, we set this to false (otherwise we would get stuck in an infinite loop of calling the close proc at the bottom of open() ) - bluespace = TRUE //Make it so that the pod doesn't stay in centcom forever - open_pod(holder, forced = TRUE) - else - reversing = FALSE //Now that we're done reversing, we set this to false (otherwise we would get stuck in an infinite loop of calling the close proc at the bottom of open() ) - bluespace = TRUE //Make it so that the pod doesn't stay in centcom forever - - QDEL_IN(risingPod, 10) - audible_message("The pod hisses, closing quickly and launching itself away from the launch point.", "The ground vibrates, the nearby pod off into the unknown.") - - stay_after_drop = FALSE - specialisedPod(holder) // Do special actions for specialised pods - this is likely if we were already doing manual launches - -/obj/structure/closet/supplypod/proc/preOpen() //Called before the open() proc. Handles anything that occurs right as the pod lands. - var/turf/T = get_turf(src) +/obj/structure/closet/supplypod/proc/handleReturnAfterDeparting(atom/movable/holder = src) + reversing = FALSE //Now that we're done reversing, we set this to false (otherwise we would get stuck in an infinite loop of calling the close proc at the bottom of open_pod() ) + bluespace = TRUE //Make it so that the pod doesn't stay in centcom forever + pod_flags &= ~FIRST_SOUNDS //Make it so we play sounds now + if (!effectQuiet && style != STYLE_SEETHROUGH) + audible_message("The pod hisses, closing and launching itself away from the station.", "The ground vibrates, and you hear the sound of engines firing.") + stay_after_drop = FALSE + holder.pixel_z = initial(holder.pixel_z) + holder.alpha = initial(holder.alpha) + var/shippingLane = GLOB.areas_by_type[/area/centcom/supplypod/supplypod_temp_holding] + forceMove(shippingLane) //Move to the centcom-z-level until the pod_landingzone says we can drop back down again + if (!reverse_dropoff_coords) //If we're centcom-launched, the reverse dropoff turf will be a centcom loading bay. If we're an extraction pod, it should be the ninja jail. Thus, this shouldn't ever really happen. + var/obj/error_landmark = locate(/obj/effect/landmark/error) in GLOB.landmarks_list + var/turf/error_landmark_turf = get_turf(error_landmark) + reverse_dropoff_coords = list(error_landmark_turf.x, error_landmark_turf.y, error_landmark_turf.z) + if (custom_rev_delay) + delays = reverse_delays + backToNonReverseIcon() + var/turf/return_turf = locate(reverse_dropoff_coords[1], reverse_dropoff_coords[2], reverse_dropoff_coords[3]) + new /obj/effect/pod_landingzone(return_turf, src) + +/obj/structure/closet/supplypod/proc/preOpen() //Called before the open_pod() proc. Handles anything that occurs right as the pod lands. + var/turf/turf_underneath = get_turf(src) var/list/B = explosionSize //Mostly because B is more readable than explosionSize :p - if (landingSound) - playsound(get_turf(src), landingSound, soundVolume, FALSE, FALSE) - for (var/mob/living/M in T) - if (effectLimb && iscarbon(M)) //If effectLimb is true (which means we pop limbs off when we hit people): - var/mob/living/carbon/CM = M - for (var/obj/item/bodypart/bodypart in CM.bodyparts) //Look at the bodyparts in our poor mob beneath our pod as it lands - if(bodypart.body_part != HEAD && bodypart.body_part != CHEST)//we dont want to kill him, just teach em a lesson! - if (bodypart.dismemberable) - bodypart.dismember() //Using the power of flextape i've sawed this man's limb in half! - break - if (effectOrgans && iscarbon(M)) //effectOrgans means remove every organ in our mob - var/mob/living/carbon/CM = M - for(var/X in CM.internal_organs) - var/destination = get_edge_target_turf(T, pick(GLOB.alldirs)) //Pick a random direction to toss them in - var/obj/item/organ/O = X - O.Remove(CM) //Note that this isn't the same proc as for lists - O.forceMove(T) //Move the organ outta the body - O.throw_at(destination, 2, 3) //Thow the organ at a random tile 3 spots away - sleep(1) - for (var/obj/item/bodypart/bodypart in CM.bodyparts) //Look at the bodyparts in our poor mob beneath our pod as it lands - var/destination = get_edge_target_turf(T, pick(GLOB.alldirs)) - if (bodypart.dismemberable) - bodypart.dismember() //Using the power of flextape i've sawed this man's bodypart in half! - bodypart.throw_at(destination, 2, 3) + density = TRUE //Density is originally false so the pod doesn't block anything while it's still falling through the air + for (var/mob/living/target_living in turf_underneath) + if (iscarbon(target_living)) //If effectLimb is true (which means we pop limbs off when we hit people): + if (effectLimb) + var/mob/living/carbon/carbon_target_mob = target_living + for (var/bp in carbon_target_mob.bodyparts) //Look at the bodyparts in our poor mob beneath our pod as it lands + var/obj/item/bodypart/bodypart = bp + if(bodypart.body_part != HEAD && bodypart.body_part != CHEST)//we dont want to kill him, just teach em a lesson! + if (bodypart.dismemberable) + bodypart.dismember() //Using the power of flextape i've sawed this man's limb in half! + break + if (effectOrgans) //effectOrgans means remove every organ in our mob + var/mob/living/carbon/carbon_target_mob = target_living + for(var/organ in carbon_target_mob.internal_organs) + var/destination = get_edge_target_turf(turf_underneath, pick(GLOB.alldirs)) //Pick a random direction to toss them in + var/obj/item/organ/organ_to_yeet = organ + organ_to_yeet.Remove(carbon_target_mob) //Note that this isn't the same proc as for lists + organ_to_yeet.forceMove(turf_underneath) //Move the organ outta the body + organ_to_yeet.throw_at(destination, 2, 3) //Thow the organ at a random tile 3 spots away sleep(1) + for (var/bp in carbon_target_mob.bodyparts) //Look at the bodyparts in our poor mob beneath our pod as it lands + var/obj/item/bodypart/bodypart = bp + var/destination = get_edge_target_turf(turf_underneath, pick(GLOB.alldirs)) + if (bodypart.dismemberable) + bodypart.dismember() //Using the power of flextape i've sawed this man's bodypart in half! + bodypart.throw_at(destination, 2, 3) + sleep(1) if (effectGib) //effectGib is on, that means whatever's underneath us better be fucking oof'd on - M.adjustBruteLoss(5000) //THATS A LOT OF DAMAGE (called just in case gib() doesnt work on em) - M.gib() //After adjusting the fuck outta that brute loss we finish the job with some satisfying gibs - M.adjustBruteLoss(damage) + target_living.adjustBruteLoss(5000) //THATS A LOT OF DAMAGE (called just in case gib() doesnt work on em) + if (!QDELETED(target_living)) + target_living.gib() //After adjusting the fuck outta that brute loss we finish the job with some satisfying gibs + else + target_living.adjustBruteLoss(damage) var/explosion_sum = B[1] + B[2] + B[3] + B[4] if (explosion_sum != 0) //If the explosion list isn't all zeroes, call an explosion - explosion(get_turf(src), B[1], B[2], B[3], flame_range = B[4], silent = effectQuiet, ignorecap = istype(src, /obj/structure/closet/supplypod/centcompod)) //less advanced equipment than bluespace pod, so larger explosion when landing - else if (!effectQuiet) //If our explosion list IS all zeroes, we still make a nice explosion sound (unless the effectQuiet var is true) - playsound(src, "explosion", landingSound ? 15 : 80, TRUE) + explosion(turf_underneath, B[1], B[2], B[3], flame_range = B[4], silent = effectQuiet, ignorecap = istype(src, /obj/structure/closet/supplypod/centcompod)) //less advanced equipment than bluespace pod, so larger explosion when landing + else if (!effectQuiet && !(pod_flags & FIRST_SOUNDS)) //If our explosion list IS all zeroes, we still make a nice explosion sound (unless the effectQuiet var is true) + playsound(src, "explosion", landingSound ? soundVolume * 0.25 : soundVolume, TRUE) + if (landingSound) + playsound(turf_underneath, landingSound, soundVolume, FALSE, FALSE) if (effectMissile) //If we are acting like a missile, then right after we land and finish fucking shit up w explosions, we should delete opened = TRUE //We set opened to TRUE to avoid spending time trying to open (due to being deleted) during the Destroy() proc qdel(src) return if (style == STYLE_GONDOLA) //Checks if we are supposed to be a gondola pod. If so, create a gondolapod mob, and move this pod to nullspace. I'd like to give a shout out, to my man oranges - var/mob/living/simple_animal/pet/gondola/gondolapod/benis = new(get_turf(src), src) + var/mob/living/simple_animal/pet/gondola/gondolapod/benis = new(turf_underneath, src) benis.contents |= contents //Move the contents of this supplypod into the gondolapod mob. moveToNullspace() - addtimer(CALLBACK(src, .proc/open, benis), openingDelay) //After the openingDelay passes, we use the open proc from this supplyprod while referencing the contents of the "holder", in this case the gondolapod mob + addtimer(CALLBACK(src, .proc/open_pod, benis), delays[POD_OPENING]) //After the opening delay passes, we use the open proc from this supplyprod while referencing the contents of the "holder", in this case the gondolapod mob else if (style == STYLE_SEETHROUGH) open_pod(src) else - addtimer(CALLBACK(src, .proc/open_pod, src), openingDelay) //After the openingDelay passes, we use the open proc from this supplypod, while referencing this supplypod's contents + addtimer(CALLBACK(src, .proc/open_pod, src), delays[POD_OPENING]) //After the opening delay passes, we use the open proc from this supplypod, while referencing this supplypod's contents /obj/structure/closet/supplypod/proc/open_pod(atom/movable/holder, broken = FALSE, forced = FALSE) //The holder var represents an atom whose contents we will be working with if (!holder) @@ -202,109 +276,286 @@ if (opened) //This is to ensure we don't open something that has already been opened return opened = TRUE - var/turf/T = get_turf(holder) //Get the turf of whoever's contents we're talking about - var/mob/M + holder.setOpened() + var/turf/turf_underneath = get_turf(holder) //Get the turf of whoever's contents we're talking about if (istype(holder, /mob)) //Allows mobs to assume the role of the holder, meaning we look at the mob's contents rather than the supplypod's contents. Typically by this point the supplypod's contents have already been moved over to the mob's contents - M = holder - if (M.key && !forced && !broken) //If we are player controlled, then we shouldnt open unless the opening is manual, or if it is due to being destroyed (represented by the "broken" parameter) + var/mob/holder_as_mob = holder + if (holder_as_mob.key && !forced && !broken) //If we are player controlled, then we shouldn't open unless the opening is manual, or if it is due to being destroyed (represented by the "broken" parameter) return if (openingSound) playsound(get_turf(holder), openingSound, soundVolume, FALSE, FALSE) //Special admin sound to play - INVOKE_ASYNC(holder, .proc/setOpened) //Use the INVOKE_ASYNC proc to call setOpened() on whatever the holder may be, without giving the atom/movable base class a setOpened() proc definition - if (style == STYLE_SEETHROUGH) - update_appearance() - for (var/atom/movable/O in holder.contents) //Go through the contents of the holder - O.forceMove(T) //move everything from the contents of the holder to the turf of the holder - if (!effectQuiet && !openingSound && style != STYLE_SEETHROUGH) //If we aren't being quiet, play the default pod open sound + for (var/turf_type in turfs_in_cargo) + turf_underneath.PlaceOnTop(turf_type) + for (var/cargo in contents) + var/atom/movable/movable_cargo = cargo + movable_cargo.forceMove(turf_underneath) + if (!effectQuiet && !openingSound && style != STYLE_SEETHROUGH && !(pod_flags & FIRST_SOUNDS)) //If we aren't being quiet, play the default pod open sound playsound(get_turf(holder), open_sound, 15, TRUE, -3) if (broken) //If the pod is opening because it's been destroyed, we end here return if (style == STYLE_SEETHROUGH) - depart(src) + startExitSequence(src) else + if (reversing) + addtimer(CALLBACK(src, .proc/SetReverseIcon), delays[POD_LEAVING]/2) //Finish up the pod's duties after a certain amount of time if(!stay_after_drop) // Departing should be handled manually - addtimer(CALLBACK(src, .proc/depart, holder), departureDelay) //Finish up the pod's duties after a certain amount of time + addtimer(CALLBACK(src, .proc/startExitSequence, holder), delays[POD_LEAVING]*(4/5)) //Finish up the pod's duties after a certain amount of time -/obj/structure/closet/supplypod/proc/depart(atom/movable/holder) +/obj/structure/closet/supplypod/proc/startExitSequence(atom/movable/holder) if (leavingSound) playsound(get_turf(holder), leavingSound, soundVolume, FALSE, FALSE) if (reversing) //If we're reversing, we call the close proc. This sends the pod back up to centcom close(holder) else if (bluespace) //If we're a bluespace pod, then delete ourselves (along with our holder, if a seperate holder exists) + deleteRubble() if (!effectQuiet && style != STYLE_INVISIBLE && style != STYLE_SEETHROUGH) do_sparks(5, TRUE, holder) //Create some sparks right before closing qdel(src) //Delete ourselves and the holder if (holder != src) qdel(holder) -/obj/structure/closet/supplypod/centcompod/close(atom/movable/holder) //Closes the supplypod and sends it back to centcom. Should only ever be called if the "reversing" variable is true - handleReturningClose(holder, TRUE) +/obj/structure/closet/supplypod/close(atom/movable/holder) //Closes the supplypod and sends it back to centcom. Should only ever be called if the "reversing" variable is true + if (!holder) + return + take_contents(holder) + playsound(holder, close_sound, soundVolume*0.75, TRUE, -3) + holder.setClosed() + addtimer(CALLBACK(src, .proc/preReturn, holder), delays[POD_LEAVING] * 0.2) //Start to leave a bit after closing for cinematic effect + +/obj/structure/closet/supplypod/take_contents(atom/movable/holder) + var/turf/turf_underneath = holder.drop_location() + for(var/atom_to_check in turf_underneath) + if(atom_to_check != src && !insert(atom_to_check, holder)) // Can't insert that + continue + insert(turf_underneath, holder) + +/obj/structure/closet/supplypod/insert(atom/to_insert, atom/movable/holder) + if(insertion_allowed(to_insert)) + if(isturf(to_insert)) + var/turf/turf_to_insert = to_insert + turfs_in_cargo += turf_to_insert.type + turf_to_insert.ScrapeAway() + else + var/atom/movable/movable_to_insert = to_insert + movable_to_insert.forceMove(holder) + return TRUE + else + return FALSE -/obj/structure/closet/supplypod/extractionpod/close(atom/movable/holder) //handles closing, and returns pod - deletes itself when returned - . = ..() - return +/obj/structure/closet/supplypod/insertion_allowed(atom/to_insert) + if(to_insert.invisibility == INVISIBILITY_ABSTRACT) + return FALSE + if(ismob(to_insert)) + if(!reverseOptionList["Mobs"]) + return FALSE + if(!isliving(to_insert)) //let's not put ghosts or camera mobs inside + return FALSE + var/mob/living/mob_to_insert = to_insert + if(mob_to_insert.anchored || mob_to_insert.incorporeal_move) + return FALSE + mob_to_insert.stop_pulling() + + else if(isobj(to_insert)) + var/obj/obj_to_insert = to_insert + if(istype(obj_to_insert, /obj/structure/closet/supplypod)) + return FALSE + if(istype(obj_to_insert, /obj/effect/supplypod_smoke)) + return FALSE + if(istype(obj_to_insert, /obj/effect/pod_landingzone)) + return FALSE + if(istype(obj_to_insert, /obj/effect/supplypod_rubble)) + return FALSE + /* + if((obj_to_insert.comp_lookup && obj_to_insert.comp_lookup[COMSIG_OBJ_HIDE]) && reverseOptionList["Underfloor"]) + return TRUE + else if ((obj_to_insert.comp_lookup && obj_to_insert.comp_lookup[COMSIG_OBJ_HIDE]) && !reverseOptionList["Underfloor"]) + return FALSE + */ + if(isProbablyWallMounted(obj_to_insert) && reverseOptionList["Wallmounted"]) + return TRUE + else if (isProbablyWallMounted(obj_to_insert) && !reverseOptionList["Wallmounted"]) + return FALSE + if(!obj_to_insert.anchored && reverseOptionList["Unanchored"]) + return TRUE + if(obj_to_insert.anchored && reverseOptionList["Anchored"]) + return TRUE + return FALSE -/obj/structure/closet/supplypod/extractionpod/proc/send_up(atom/movable/holder) - if (!holder) - holder = src + else if (isturf(to_insert)) + if(isfloorturf(to_insert) && reverseOptionList["Floors"]) + return TRUE + if(isfloorturf(to_insert) && !reverseOptionList["Floors"]) + return FALSE + if(isclosedturf(to_insert) && reverseOptionList["Walls"]) + return TRUE + if(isclosedturf(to_insert) && !reverseOptionList["Walls"]) + return FALSE + return FALSE + return TRUE - if (leavingSound) - playsound(get_turf(holder), leavingSound, soundVolume, FALSE, FALSE) +/obj/structure/closet/supplypod/proc/preReturn(atom/movable/holder) + deleteRubble() + animate(holder, alpha = 0, time = 8, easing = QUAD_EASING|EASE_IN, flags = ANIMATION_PARALLEL) + animate(holder, pixel_z = 400, time = 10, easing = QUAD_EASING|EASE_IN, flags = ANIMATION_PARALLEL) //Animate our rising pod - handleReturningClose(holder, FALSE) + addtimer(CALLBACK(src, .proc/handleReturnAfterDeparting, holder), 15) //Finish up the pod's duties after a certain amount of time -/obj/structure/closet/supplypod/proc/setOpened() //Proc exists here, as well as in any atom that can assume the role of a "holder" of a supplypod. Check the open() proc for more details +/obj/structure/closet/supplypod/setOpened() //Proc exists here, as well as in any atom that can assume the role of a "holder" of a supplypod. Check the open_pod() proc for more details + opened = TRUE + density = FALSE + update_icon() + +/obj/structure/closet/supplypod/extractionpod/setOpened() + opened = TRUE + density = TRUE + update_icon() + +/obj/structure/closet/supplypod/setClosed() //Ditto + opened = FALSE + density = TRUE + update_icon() + +/obj/structure/closet/supplypod/proc/tryMakeRubble(turf/T) //Ditto + if (rubble_type == RUBBLE_NONE) + return + if (rubble) + return + if (effectMissile) + return + if (isspaceturf(T) || isclosedturf(T)) + return + rubble = new /obj/effect/supplypod_rubble(T) + rubble.setStyle(rubble_type, src) update_appearance() -/obj/structure/closet/supplypod/proc/setClosed() //Ditto +/obj/structure/closet/supplypod/Moved() + deleteRubble() + return ..() + +/obj/structure/closet/supplypod/proc/deleteRubble() + rubble?.fadeAway() + rubble = null update_appearance() +/obj/structure/closet/supplypod/proc/addGlow() + if (GLOB.podstyles[style][POD_SHAPE] != POD_SHAPE_NORML) + return + glow_effect = new(src) + glow_effect.icon_state = "pod_glow_" + GLOB.podstyles[style][POD_GLOW] + vis_contents += glow_effect + glow_effect.layer = GASFIRE_LAYER + +/obj/structure/closet/supplypod/proc/endGlow() + if(!glow_effect) + return + glow_effect.layer = LOW_ITEM_LAYER + glow_effect.fadeAway(delays[POD_OPENING]) + glow_effect = null + /obj/structure/closet/supplypod/Destroy() - open_pod(holder = src, broken = TRUE) //Lets dump our contents by opening up - . = ..() + open_pod(src, broken = TRUE) //Lets dump our contents by opening up + deleteRubble() + endGlow() + return ..() + +//------------------------------------TEMPORARY_VISUAL-------------------------------------// +/obj/effect/supplypod_smoke //Falling pod smoke + name = "" + icon = 'icons/obj/supplypods_32x32.dmi' + icon_state = "smoke" + desc = "" + layer = PROJECTILE_HIT_THRESHHOLD_LAYER + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + alpha = 0 -//------------------------------------FALLING SUPPLY POD-------------------------------------// -/obj/effect/DPfall //Falling pod +/obj/effect/engineglow //Falling pod smoke name = "" icon = 'icons/obj/supplypods.dmi' - pixel_x = -16 - pixel_y = -5 - pixel_z = 200 - desc = "Get out of the way!" - layer = FLY_LAYER//that wasnt flying, that was falling with style! - icon_state = "" - -/obj/effect/DPfall/Initialize(dropLocation, obj/structure/closet/supplypod/pod) - if (pod.style == STYLE_SEETHROUGH) - pixel_x = -16 - pixel_y = 0 - for (var/atom/movable/O in pod.contents) - var/icon/I = getFlatIcon(O) //im so sorry - add_overlay(I) - else if (pod.style != STYLE_INVISIBLE) //Check to ensure the pod isn't invisible - icon_state = "[pod.icon_state]_falling" - name = pod.name + icon_state = "pod_engineglow" + desc = "" + layer = GASFIRE_LAYER + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + alpha = 255 + +/obj/effect/engineglow/proc/fadeAway(leaveTime) + var/duration = min(leaveTime, 25) + animate(src, alpha=0, time = duration) + QDEL_IN(src, duration + 5) + +/obj/effect/supplypod_smoke/proc/drawSelf(amount) + alpha = max(0, 255-(amount*20)) + +/obj/effect/supplypod_rubble //This is the object that forceMoves the supplypod to it's location + name = "Debris" + desc = "A small crater of rubble. Closer inspection reveals the debris to be made primarily of space-grade metal fragments. You're pretty sure that this will disperse before too long." + icon = 'icons/obj/supplypods.dmi' + layer = PROJECTILE_HIT_THRESHHOLD_LAYER // We want this to go right below the layer of supplypods and supplypod_rubble's forground. + icon_state = "rubble_bg" + anchored = TRUE + pixel_x = SUPPLYPOD_X_OFFSET + var/foreground = "rubble_fg" + var/verticle_offset = 0 + +/obj/effect/supplypod_rubble/proc/getForeground(obj/structure/closet/supplypod/pod) + var/mutable_appearance/rubble_overlay = mutable_appearance('icons/obj/supplypods.dmi', foreground) + rubble_overlay.appearance_flags = KEEP_APART|RESET_TRANSFORM + rubble_overlay.transform = matrix().Translate(SUPPLYPOD_X_OFFSET - pod.pixel_x, verticle_offset) + return rubble_overlay + +/obj/effect/supplypod_rubble/proc/fadeAway() + animate(src, alpha=0, time = 30) + QDEL_IN(src, 35) + +/obj/effect/supplypod_rubble/proc/setStyle(type, obj/structure/closet/supplypod/pod) + if (type == RUBBLE_WIDE) + icon_state += "_wide" + foreground += "_wide" + if (type == RUBBLE_THIN) + icon_state += "_thin" + foreground += "_thin" + if (pod.style == STYLE_BOX) + verticle_offset = -2 + else + verticle_offset = initial(verticle_offset) + pixel_y = verticle_offset + +/obj/effect/pod_landingzone_effect + name = "" + desc = "" + icon = 'icons/obj/supplypods_32x32.dmi' + icon_state = "LZ_Slider" + layer = PROJECTILE_HIT_THRESHHOLD_LAYER + +/obj/effect/pod_landingzone_effect/Initialize(mapload, obj/structure/closet/supplypod/pod) . = ..() + transform = matrix() * 1.5 + animate(src, transform = matrix()*0.01, time = pod.delays[POD_TRANSIT]+pod.delays[POD_FALLING]) -//------------------------------------TEMPORARY_VISUAL-------------------------------------// -/obj/effect/DPtarget //This is the object that forceMoves the supplypod to it's location +/obj/effect/pod_landingzone //This is the object that forceMoves the supplypod to it's location name = "Landing Zone Indicator" desc = "A holographic projection designating the landing zone of something. It's probably best to stand back." - icon = 'icons/mob/actions/actions_items.dmi' - icon_state = "sniper_zoom" + icon = 'icons/obj/supplypods_32x32.dmi' + icon_state = "LZ" layer = PROJECTILE_HIT_THRESHHOLD_LAYER light_range = 2 - var/obj/effect/temp_visual/fallingPod //Temporary "falling pod" that we animate - var/obj/structure/closet/supplypod/pod //The supplyPod that will be landing ontop of this target + anchored = TRUE + alpha = 0 + var/obj/structure/closet/supplypod/pod //The supplyPod that will be landing ontop of this pod_landingzone + var/obj/effect/pod_landingzone_effect/helper + var/list/smoke_effects = new /list(13) /obj/effect/ex_act() return -/obj/effect/DPtarget/Initialize(mapload, podParam, single_order = null) +/obj/effect/pod_landingzone/Initialize(mapload, podParam, single_order = null, clientman) . = ..() if (ispath(podParam)) //We can pass either a path for a pod (as expressconsoles do), or a reference to an instantiated pod (as the centcom_podlauncher does) podParam = new podParam() //If its just a path, instantiate it pod = podParam + if (!pod.effectStealth) + helper = new (drop_location(), pod) + alpha = 255 + animate(src, transform = matrix().Turn(90), time = pod.delays[POD_TRANSIT]+pod.delays[POD_FALLING]) if (single_order) if (istype(single_order, /datum/supply_order)) var/datum/supply_order/SO = single_order @@ -312,46 +563,73 @@ else if (istype(single_order, /atom/movable)) var/atom/movable/O = single_order O.forceMove(pod) - for (var/mob/living/M in pod) //If there are any mobs in the supplypod, we want to forceMove them into the target. This is so that they can see where they are about to land, AND so that they don't get sent to the nullspace error room (as the pod is currently in nullspace) - M.forceMove(src) - if(pod.effectStun) //If effectStun is true, stun any mobs caught on this target until the pod gets a chance to hit them - for (var/mob/living/M in get_turf(src)) - M.Stun(pod.landingDelay+10, ignore_canstun = TRUE)//you aint goin nowhere, kid. - if (pod.effectStealth) //If effectStealth is true we want to be invisible - icon_state = "" - if (pod.fallDuration == initial(pod.fallDuration) && pod.landingDelay + pod.fallDuration < pod.fallingSoundLength) + for (var/mob/living/mob_in_pod in pod) //If there are any mobs in the supplypod, we want to set their view to the pod_landingzone. This is so that they can see where they are about to land + mob_in_pod.reset_perspective(src) + if(pod.effectStun) //If effectStun is true, stun any mobs caught on this pod_landingzone until the pod gets a chance to hit them + for (var/mob/living/target_living in get_turf(src)) + target_living.Stun(pod.delays[POD_TRANSIT]+10, ignore_canstun = TRUE)//you ain't goin nowhere, kid. + if (pod.delays[POD_FALLING] == initial(pod.delays[POD_FALLING]) && pod.delays[POD_TRANSIT] + pod.delays[POD_FALLING] < pod.fallingSoundLength) pod.fallingSoundLength = 3 //The default falling sound is a little long, so if the landing time is shorter than the default falling sound, use a special, shorter default falling sound pod.fallingSound = 'sound/weapons/mortar_whistle.ogg' - var/soundStartTime = pod.landingDelay - pod.fallingSoundLength + pod.fallDuration + var/soundStartTime = pod.delays[POD_TRANSIT] - pod.fallingSoundLength + pod.delays[POD_FALLING] if (soundStartTime < 0) soundStartTime = 1 - if (!pod.effectQuiet) + if (!pod.effectQuiet && !(pod.pod_flags & FIRST_SOUNDS)) addtimer(CALLBACK(src, .proc/playFallingSound), soundStartTime) - addtimer(CALLBACK(src, .proc/beginLaunch, pod.effectCircle), pod.landingDelay) - -/obj/effect/DPtarget/proc/playFallingSound() - playsound(src, pod.fallingSound, pod.soundVolume, TRUE, 6) - -/obj/effect/DPtarget/proc/beginLaunch(effectCircle) //Begin the animation for the pod falling. The effectCircle param determines whether the pod gets to come in from any descent angle - fallingPod = new /obj/effect/DPfall(drop_location(), pod) - var/matrix/M = matrix(fallingPod.transform) //Create a new matrix that we can rotate + addtimer(CALLBACK(src, .proc/beginLaunch, pod.effectCircle), pod.delays[POD_TRANSIT]) + +/obj/effect/pod_landingzone/proc/playFallingSound() + playsound(src, pod.fallingSound, pod.soundVolume, 1, 6) + +/obj/effect/pod_landingzone/proc/beginLaunch(effectCircle) //Begin the animation for the pod falling. The effectCircle param determines whether the pod gets to come in from any descent angle + pod.addGlow() + pod.update_icon() + if (pod.style != STYLE_INVISIBLE) + pod.add_filter("motionblur",1,list("type"="motion_blur", "x"=0, "y"=3)) + pod.forceMove(drop_location()) + for (var/mob/living/M in pod) //Remember earlier (initialization) when we moved mobs into the pod_landingzone so they wouldnt get lost in nullspace? Time to get them out + M.reset_perspective(null) var/angle = effectCircle ? rand(0,360) : rand(70,110) //The angle that we can come in from - fallingPod.pixel_x = cos(angle)*400 //Use some ADVANCED MATHEMATICS to set the animated pod's position to somewhere on the edge of a circle with the center being the target - fallingPod.pixel_z = sin(angle)*400 - var/rotation = Get_Pixel_Angle(fallingPod.pixel_z, fallingPod.pixel_x) //CUSTOM HOMEBREWED proc that is just arctan with extra steps - M.Turn(rotation) //Turn our matrix accordingly - fallingPod.transform = M //Transform the animated pod according to the matrix - M = matrix(pod.transform) //Make another matrix based on the pod - M.Turn(rotation) //Turn the matrix - pod.transform = M //Turn the actual pod (Won't be visible until endLaunch() proc tho) - animate(fallingPod, pixel_z = 0, pixel_x = -16, time = pod.fallDuration, , easing = LINEAR_EASING) //Make the pod fall! At an angle! - addtimer(CALLBACK(src, .proc/endLaunch), pod.fallDuration, TIMER_CLIENT_TIME) //Go onto the last step after a very short falling animation - -/obj/effect/DPtarget/proc/endLaunch() - pod.update_appearance() - pod.forceMove(drop_location()) //The fallingPod animation is over, now's a good time to forceMove the actual pod into position - QDEL_NULL(fallingPod) //Delete the falling pod effect, because at this point its animation is over. We dont use temp_visual because we want to manually delete it as soon as the pod appears - for (var/mob/living/M in src) //Remember earlier (initialization) when we moved mobs into the DPTarget so they wouldnt get lost in nullspace? Time to get them out - M.forceMove(pod) + pod.pixel_x = cos(angle)*32*length(smoke_effects) //Use some ADVANCED MATHEMATICS to set the animated pod's position to somewhere on the edge of a circle with the center being the target + pod.pixel_z = sin(angle)*32*length(smoke_effects) + var/rotation = Get_Pixel_Angle(pod.pixel_z, pod.pixel_x) //CUSTOM HOMEBREWED proc that is just arctan with extra steps + setupSmoke(rotation) + pod.transform = matrix().Turn(rotation) + pod.layer = FLY_LAYER + if (pod.style != STYLE_INVISIBLE) + animate(pod.get_filter("motionblur"), y = 0, time = pod.delays[POD_FALLING], flags = ANIMATION_PARALLEL) + animate(pod, pixel_z = -1 * abs(sin(rotation))*4, pixel_x = SUPPLYPOD_X_OFFSET + (sin(rotation) * 20), time = pod.delays[POD_FALLING], easing = LINEAR_EASING, flags = ANIMATION_PARALLEL) //Make the pod fall! At an angle! + addtimer(CALLBACK(src, .proc/endLaunch), pod.delays[POD_FALLING], TIMER_CLIENT_TIME) //Go onto the last step after a very short falling animation + +/obj/effect/pod_landingzone/proc/setupSmoke(rotation) + if (pod.style == STYLE_INVISIBLE || pod.style == STYLE_SEETHROUGH) + return + for ( var/i in 1 to length(smoke_effects)) + var/obj/effect/supplypod_smoke/smoke_part = new (drop_location()) + if (i == 1) + smoke_part.layer = FLY_LAYER + smoke_part.icon_state = "smoke_start" + smoke_part.transform = matrix().Turn(rotation) + smoke_effects[i] = smoke_part + smoke_part.pixel_x = sin(rotation)*32 * i + smoke_part.pixel_y = abs(cos(rotation))*32 * i + smoke_part.filters += filter(type = "blur", size = 4) + var/time = (pod.delays[POD_FALLING] / length(smoke_effects))*(length(smoke_effects)-i) + addtimer(CALLBACK(smoke_part, /obj/effect/supplypod_smoke/.proc/drawSelf, i), time, TIMER_CLIENT_TIME) //Go onto the last step after a very short falling animation + QDEL_IN(smoke_part, pod.delays[POD_FALLING] + 35) + +/obj/effect/pod_landingzone/proc/drawSmoke() + if (pod.style == STYLE_INVISIBLE || pod.style == STYLE_SEETHROUGH) + return + for (var/obj/effect/supplypod_smoke/smoke_part in smoke_effects) + animate(smoke_part, alpha = 0, time = 20, flags = ANIMATION_PARALLEL) + animate(smoke_part.filters[1], size = 6, time = 15, easing = CUBIC_EASING|EASE_OUT, flags = ANIMATION_PARALLEL) + +/obj/effect/pod_landingzone/proc/endLaunch() + pod.tryMakeRubble(drop_location()) + pod.layer = initial(pod.layer) + pod.endGlow() + QDEL_NULL(helper) pod.preOpen() //Begin supplypod open procedures. Here effects like explosions, damage, and other dangerous (and potentially admin-caused, if the centcom_podlauncher datum was used) memes will take place + drawSmoke() qdel(src) //The target's purpose is complete. It can rest easy now diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm index f6c306a411af..de655ece5f1a 100644 --- a/code/modules/client/client_defines.dm +++ b/code/modules/client/client_defines.dm @@ -31,6 +31,8 @@ ///Internal counter for clients sending external (IRC/Discord) relay messages via ahelp to prevent spamming. Set to a number every time an admin reply is sent, decremented for every client send. var/externalreplyamount = 0 var/ircreplyamount = 0 + ///Tracks say() usage for ic/dchat while slowmode is enabled + COOLDOWN_DECLARE(say_slowmode) ///////// //OTHER// ///////// diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm index 5c5553df2755..44710b0fd6fc 100644 --- a/code/modules/client/client_procs.dm +++ b/code/modules/client/client_procs.dm @@ -469,6 +469,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list( view_size.setZoomMode() fit_viewport() Master.UpdateTickRate() + SEND_GLOBAL_SIGNAL(COMSIG_GLOB_CLIENT_CONNECT, src) ////////////// //DISCONNECT// diff --git a/code/modules/clothing/head/berets.dm b/code/modules/clothing/head/berets.dm index 0e0d04b40f07..2c6139e54169 100644 --- a/code/modules/clothing/head/berets.dm +++ b/code/modules/clothing/head/berets.dm @@ -190,6 +190,8 @@ icon_state = "beret_com" armor = list("melee" = 40, "bullet" = 20, "laser" = 10, "energy" = 10, "rad" = 10, "bio" = 5, "rad" = 5, "fire" = 5, "rad" = 30) +// SolGov + /obj/item/clothing/head/beret/solgov name = "\improper SolGov beret" desc = "A beret with SolGov's emblem emblazoned on it. Colored in SolGov blue." @@ -215,6 +217,8 @@ icon_state = "beret_terragovplain" item_state = "beret_terragovplain" +// Inteq + /obj/item/clothing/head/beret/sec/inteq name = "inteq beret" desc = "A comfortable looking brown beret with a badge of the golden shield of the IRMG. Denotes the wearer as part of the IRMG." @@ -234,6 +238,8 @@ item_state = "inteq_honorable_beret" armor = list("melee" = 40, "bullet" = 50, "laser" = 50, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 90) +// Frontier + /obj/item/clothing/head/beret/sec/frontier name = "\improper Frontiersmen beret" desc = "A scratchy olive green beret, worn by Frontiersmen who want to look good while intimidating freighter crew." @@ -243,3 +249,14 @@ name = "\improper Frontiersmen officer beret" desc = "A scratchy olive green beret emblazoned with the Frontiersmen insignia, worn by Frontiersmen who want to look good while intimidating freighter captains." icon_state = "frontier_officer_beret" + + +// CentCom + +/obj/item/clothing/head/beret/centcom_formal + name = "\improper CentCom Formal Beret" + desc = "Sometimes, a compromise between fashion and defense needs to be made. Thanks to Nanotrasen's most recent nano-fabric durability enhancements, this time, it's not the case." + icon_state = "beret_badge" + greyscale_colors = "#46b946#f2c42e" + armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 50, "bomb" = 100, "bio" = 100, "fire" = 100, "acid" = 90) + strip_delay = 10 SECONDS diff --git a/code/modules/clothing/head/misc.dm b/code/modules/clothing/head/misc.dm index 0ac57271c0b0..9f0a56d1d476 100644 --- a/code/modules/clothing/head/misc.dm +++ b/code/modules/clothing/head/misc.dm @@ -8,6 +8,15 @@ armor = list("melee" = 30, "bullet" = 15, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) strip_delay = 80 +/obj/item/clothing/head/centcom_cap + name = "\improper CentCom commander cap" + icon_state = "centcom_cap" + desc = "Worn by the finest of CentCom commanders. Inside the lining of the cap, lies two faint initials." + item_state = "that" + flags_inv = 0 + armor = list("melee" = 30, "bullet" = 15, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) + strip_delay = (8 SECONDS) + /obj/item/clothing/head/spacepolice name = "space police cap" desc = "A blue cap for patrolling the daily beat." @@ -364,7 +373,7 @@ /obj/item/clothing/head/frenchberet name = "french beret" - desc = "A quality beret, infused with the aroma of chain-smoking, wine-swilling Parisians. You feel less inclined to engage military conflict, for some reason." + desc = "A quality beret, infused with the aroma of chain-smoking, wine-swilling Parisians. You feel less inclined to engage in military conflict, for some reason." icon_state = "beret" dynamic_hair_suffix = "" @@ -444,7 +453,7 @@ /obj/item/clothing/head/coordinator name = "coordinator cap" - desc = "A cap for a party ooordinator, stylish!." + desc = "A cap for a party coordinator, stylish!." icon_state = "capcap" item_state = "that" armor = list("melee" = 25, "bullet" = 15, "laser" = 25, "energy" = 35, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 50, "acid" = 50) diff --git a/code/modules/clothing/masks/gasmask.dm b/code/modules/clothing/masks/gasmask.dm index b3d57ad61aaf..467377f722dd 100644 --- a/code/modules/clothing/masks/gasmask.dm +++ b/code/modules/clothing/masks/gasmask.dm @@ -29,6 +29,14 @@ item_state = "gas_cap" resistance_flags = FIRE_PROOF | ACID_PROOF +/obj/item/clothing/mask/gas/atmos/centcom + name = "\improper CentCom gas mask" + desc = "Oooh, gold and green. Fancy! This should help as you sit in your office." + icon = 'icons/obj/clothing/masks.dmi' + icon_state = "gas_centcom" + item_state = "gas_centcom" + resistance_flags = FIRE_PROOF | ACID_PROOF + // **** Welding gas mask **** /obj/item/clothing/mask/gas/welding diff --git a/code/modules/clothing/outfits/ert.dm b/code/modules/clothing/outfits/ert.dm index 444a38deae55..e3f90d1070b5 100644 --- a/code/modules/clothing/outfits/ert.dm +++ b/code/modules/clothing/outfits/ert.dm @@ -2,7 +2,7 @@ name = "ERT Common" mask = /obj/item/clothing/mask/gas/sechailer - uniform = /obj/item/clothing/under/rank/centcom/officer + uniform = /obj/item/clothing/under/rank/centcom/official shoes = /obj/item/clothing/shoes/combat/swat gloves = /obj/item/clothing/gloves/combat ears = /obj/item/radio/headset/headset_cent/alt @@ -160,7 +160,7 @@ /datum/outfit/centcom/centcom_official name = "CentCom Official" - uniform = /obj/item/clothing/under/rank/centcom/officer + uniform = /obj/item/clothing/under/rank/centcom/official shoes = /obj/item/clothing/shoes/sneakers/black gloves = /obj/item/clothing/gloves/color/black ears = /obj/item/radio/headset/headset_cent diff --git a/code/modules/clothing/outfits/solgov.dm b/code/modules/clothing/outfits/solgov.dm index 56addfb34813..da4c6cfff3b3 100644 --- a/code/modules/clothing/outfits/solgov.dm +++ b/code/modules/clothing/outfits/solgov.dm @@ -65,14 +65,12 @@ ears = /obj/item/radio/headset/solgov/alt gloves = /obj/item/clothing/gloves/combat head = /obj/item/clothing/head/solgov/sonnensoldner - r_pocket = /obj/item/gun/ballistic/automatic/pistol/solgov - l_pocket = /obj/item/ammo_box/magazine/pistol556mm + r_pocket = null + l_pocket = null shoes = /obj/item/clothing/shoes/workboots back = /obj/item/storage/backpack box = /obj/item/storage/box/survival - backpack_contents = list(/obj/item/crowbar/power,\ - /obj/item/melee/baton/loaded=1,\ - /obj/item/ammo_box/magazine/pistol556mm=2) + backpack_contents = list(/obj/item/crowbar/power) /datum/outfit/job/solgov/representative name = "Solarian Representative (SolGov)" @@ -153,7 +151,6 @@ backpack_contents = list( /obj/item/flashlight/seclite=1,\ /obj/item/kitchen/knife/combat/survival=1,\ - /obj/item/mining_voucher=1,\ /obj/item/stack/marker_beacon/ten=1) backpack = /obj/item/storage/backpack/explorer diff --git a/code/modules/clothing/outfits/standard.dm b/code/modules/clothing/outfits/standard.dm index 6fdeb20b53e1..c339816c7e68 100644 --- a/code/modules/clothing/outfits/standard.dm +++ b/code/modules/clothing/outfits/standard.dm @@ -201,7 +201,7 @@ ears = /obj/item/radio/headset/headset_cent/commander glasses = /obj/item/clothing/glasses/eyepatch mask = /obj/item/clothing/mask/cigarette/cigar/cohiba - head = /obj/item/clothing/head/centhat + head = /obj/item/clothing/head/centcom_cap belt = /obj/item/gun/ballistic/revolver/mateba r_pocket = /obj/item/lighter l_pocket = /obj/item/ammo_box/a357 diff --git a/code/modules/clothing/spacesuits/miscellaneous.dm b/code/modules/clothing/spacesuits/miscellaneous.dm index e70ce5a1d6a9..8a74e555469a 100644 --- a/code/modules/clothing/spacesuits/miscellaneous.dm +++ b/code/modules/clothing/spacesuits/miscellaneous.dm @@ -56,9 +56,10 @@ Contains: resistance_flags = FIRE_PROOF | ACID_PROOF /obj/item/clothing/head/helmet/space/beret - name = "officer's beret" + name = "CentCom officer's beret" desc = "An armored beret commonly used by special operations officers. Uses advanced force field technology to protect the head from space." icon_state = "beret_badge" + greyscale_colors = "#397F3F#FFCE5B" dynamic_hair_suffix = "+generic" dynamic_fhair_suffix = "+generic" flags_inv = 0 @@ -68,12 +69,12 @@ Contains: resistance_flags = FIRE_PROOF | ACID_PROOF /obj/item/clothing/suit/space/officer - name = "officer's jacket" - desc = "An armored, space-proof jacket used in special operations." - icon = 'icons/obj/clothing/suits.dmi' - mob_overlay_icon = 'icons/mob/clothing/suit.dmi' - icon_state = "detective" - item_state = "det_suit" + name = "CentCom officer's coat" + desc = "An armored, space-proof coat used in special operations." + icon = 'icons/obj/clothing/suits/armor.dmi' + mob_overlay_icon = 'icons/mob/clothing/suits/armor.dmi' + icon_state = "centcom_coat" + item_state = "centcom" blood_overlay_type = "coat" slowdown = 0 flags_inv = 0 diff --git a/code/modules/clothing/suits/armor.dm b/code/modules/clothing/suits/armor.dm index 4692811baecf..23b489b6a3c3 100644 --- a/code/modules/clothing/suits/armor.dm +++ b/code/modules/clothing/suits/armor.dm @@ -512,3 +512,16 @@ body_parts_covered = CHEST|GROIN|ARMS cold_protection = CHEST|GROIN|ARMS heat_protection = CHEST|GROIN|ARMS + +/obj/item/clothing/suit/toggle/armor/vest/centcom_formal + name = "\improper CentCom formal coat" + desc = "A stylish coat given to CentCom Commanders. Perfect for sending ERTs to suicide missions with style!" + icon_state = "centcom_formal" + item_state = "centcom" + body_parts_covered = CHEST|GROIN|ARMS + armor = list("melee" = 35, "bullet" = 40, "laser" = 40, "energy" = 50, "bomb" = 35, "bio" = 10, "rad" = 10, "fire" = 10, "acid" = 60) + togglename = "buttons" + +/obj/item/clothing/suit/toggle/armor/vest/centcom_formal/Initialize() + . = ..() + allowed = GLOB.security_wintercoat_allowed diff --git a/code/modules/clothing/suits/miscellaneous.dm b/code/modules/clothing/suits/miscellaneous.dm index f5fb5a1ea4d7..1a020e40623c 100644 --- a/code/modules/clothing/suits/miscellaneous.dm +++ b/code/modules/clothing/suits/miscellaneous.dm @@ -554,175 +554,6 @@ item_state = "cheongsam_blue" body_parts_covered = CHEST|GROIN|ARMS|LEGS -// WINTER COATS - -/obj/item/clothing/suit/hooded/wintercoat - name = "winter coat" - desc = "A heavy jacket made from 'synthetic' animal furs." - icon_state = "coatwinter" - item_state = "coatwinter" - body_parts_covered = CHEST|GROIN|ARMS - cold_protection = CHEST|GROIN|ARMS - min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) - allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter) - -/obj/item/clothing/head/hooded/winterhood - name = "winter hood" - desc = "A hood attached to a heavy winter jacket." - icon_state = "winterhood" - body_parts_covered = HEAD - cold_protection = HEAD - min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT - flags_inv = HIDEHAIR|HIDEEARS - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) - -/obj/item/clothing/suit/hooded/wintercoat/captain - name = "captain's winter coat" - icon_state = "coatcaptain" - item_state = "coatcaptain" - armor = list("melee" = 25, "bullet" = 30, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 50) - hoodtype = /obj/item/clothing/head/hooded/winterhood/captain - -/obj/item/clothing/suit/hooded/wintercoat/captain/Initialize() - . = ..() - allowed = GLOB.security_wintercoat_allowed - -/obj/item/clothing/head/hooded/winterhood/captain - icon_state = "winterhood_captain" - armor = list("melee" = 25, "bullet" = 30, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 50) - -/obj/item/clothing/suit/hooded/wintercoat/security - name = "security winter coat" - icon_state = "coatsecurity" - item_state = "coatsecurity" - armor = list("melee" = 25, "bullet" = 15, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 45) - hoodtype = /obj/item/clothing/head/hooded/winterhood/security - -/obj/item/clothing/suit/hooded/wintercoat/security/Initialize() - . = ..() - allowed = GLOB.security_wintercoat_allowed - -/obj/item/clothing/head/hooded/winterhood/security - icon_state = "winterhood_security" - armor = list("melee" = 25, "bullet" = 15, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 45) - -/obj/item/clothing/suit/hooded/wintercoat/medical - name = "medical winter coat" - icon_state = "coatmedical" - item_state = "coatmedical" - allowed = list(/obj/item/analyzer, /obj/item/sensor_device, /obj/item/stack/medical, /obj/item/dnainjector, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/hypospray, /obj/item/healthanalyzer, /obj/item/flashlight/pen, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/pill, /obj/item/storage/pill_bottle, /obj/item/paper, /obj/item/melee/classic_baton/telescopic, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 50, "rad" = 0, "fire" = 0, "acid" = 45) - hoodtype = /obj/item/clothing/head/hooded/winterhood/medical - -/obj/item/clothing/head/hooded/winterhood/medical - icon_state = "winterhood_medical" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 50, "rad" = 0, "fire" = 0, "acid" = 45) - -/obj/item/clothing/suit/hooded/wintercoat/medical/paramedic - name = "paramedic winter coat" - icon_state = "coatparamedic" - item_state = "coatparamedic" - hoodtype = /obj/item/clothing/head/hooded/winterhood/medical/paramedic - -/obj/item/clothing/head/hooded/winterhood/medical/paramedic - icon_state = "winterhood_paramedic" - -/obj/item/clothing/suit/hooded/wintercoat/science - name = "science winter coat" - icon_state = "coatscience" - item_state = "coatscience" - allowed = list(/obj/item/analyzer, /obj/item/stack/medical, /obj/item/dnainjector, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/hypospray, /obj/item/healthanalyzer, /obj/item/flashlight/pen, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/pill, /obj/item/storage/pill_bottle, /obj/item/paper, /obj/item/melee/classic_baton/telescopic, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman) - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - hoodtype = /obj/item/clothing/head/hooded/winterhood/science - -/obj/item/clothing/head/hooded/winterhood/science - icon_state = "winterhood_science" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - -/obj/item/clothing/suit/hooded/wintercoat/engineering - name = "engineering winter coat" - icon_state = "coatengineer" - item_state = "coatengineer" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 20, "fire" = 30, "acid" = 45) - allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/t_scanner, /obj/item/construction/rcd, /obj/item/pipe_dispenser, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter) - hoodtype = /obj/item/clothing/head/hooded/winterhood/engineering - -/obj/item/clothing/head/hooded/winterhood/engineering - icon_state = "winterhood_engineer" - armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 20, "fire" = 30, "acid" = 45) - -/obj/item/clothing/suit/hooded/wintercoat/engineering/atmos - name = "atmospherics winter coat" - icon_state = "coatatmos" - item_state = "coatatmos" - hoodtype = /obj/item/clothing/head/hooded/winterhood/engineering/atmos - -/obj/item/clothing/head/hooded/winterhood/engineering/atmos - icon_state = "winterhood_atmos" - -/obj/item/clothing/suit/hooded/wintercoat/hydro - name = "hydroponics winter coat" - icon_state = "coathydro" - item_state = "coathydro" - allowed = list(/obj/item/reagent_containers/spray/plantbgone, /obj/item/plant_analyzer, /obj/item/seeds, /obj/item/reagent_containers/glass/bottle, /obj/item/cultivator, /obj/item/reagent_containers/spray/pestspray, /obj/item/hatchet, /obj/item/storage/bag/plants, /obj/item/toy, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/storage/fancy/cigarettes, /obj/item/lighter) - hoodtype = /obj/item/clothing/head/hooded/winterhood/hydro - -/obj/item/clothing/head/hooded/winterhood/hydro - icon_state = "winterhood_hydro" - -/obj/item/clothing/suit/hooded/wintercoat/cargo - name = "cargo winter coat" - icon_state = "coatcargo" - item_state = "coatcargo" - hoodtype = /obj/item/clothing/head/hooded/winterhood/cargo - -/obj/item/clothing/head/hooded/winterhood/cargo - icon_state = "winterhood_cargo" - -/obj/item/clothing/suit/hooded/wintercoat/miner - name = "mining winter coat" - icon_state = "coatminer" - item_state = "coatminer" - allowed = list(/obj/item/pickaxe, /obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter) - armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - hoodtype = /obj/item/clothing/head/hooded/winterhood/miner - -/obj/item/clothing/head/hooded/winterhood/miner - icon_state = "winterhood_miner" - armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) - -/obj/item/clothing/suit/hooded/wintercoat/security/inteq - name = "inteq winter coat" - desc = "An armored wintercoat in the colors of the IRMG, the zipper tab is the golden shield of the IRMG." - icon_state = "coatinteq" - item_state = "coatinteq" - hoodtype = /obj/item/clothing/head/hooded/winterhood/security/inteq - supports_variations = KEPORI_VARIATION - -/obj/item/clothing/head/hooded/winterhood/security/inteq - icon_state = "winterhood_inteq" - supports_variations = KEPORI_VARIATION - -/obj/item/clothing/suit/hooded/coat/inteq - name = "inteq hooded coat" - desc = "A hooded coat with a fur trim around the hood, comfy! It has a small 'IRMG' embroidered onto the shoulder." - icon_state = "hoodieinteq" - item_state = "hoodieinteq" - armor = list("melee" = 25, "bullet" = 15, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 45) - hoodtype = /obj/item/clothing/head/hooded/coat/inteq - -/obj/item/clothing/head/hooded/coat/inteq - name = "inteq hood" - desc = "A comfortable looking brown hood." - icon_state = "hoodinteq" - item_state = "hoodinteq" - armor = list("melee" = 25, "bullet" = 15, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 45) - -/obj/item/clothing/suit/hooded/coat/inteq/Initialize() - . = ..() - allowed = GLOB.security_wintercoat_allowed - /obj/item/clothing/head/hooded/ablative name = "ablative hood" desc = "Hood hopefully belonging to an ablative trenchcoat. Includes a visor for cool-o-vision." diff --git a/code/modules/clothing/suits/wintercoats.dm b/code/modules/clothing/suits/wintercoats.dm new file mode 100644 index 000000000000..684e4d46add9 --- /dev/null +++ b/code/modules/clothing/suits/wintercoats.dm @@ -0,0 +1,187 @@ +// WINTER COATS + +/obj/item/clothing/suit/hooded/wintercoat + name = "winter coat" + desc = "A heavy jacket made from 'synthetic' animal furs." + icon_state = "coatwinter" + item_state = "coatwinter" + body_parts_covered = CHEST|GROIN|ARMS + cold_protection = CHEST|GROIN|ARMS + min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) + allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter) + +/obj/item/clothing/head/hooded/winterhood + name = "winter hood" + desc = "A hood attached to a heavy winter jacket." + icon_state = "winterhood" + body_parts_covered = HEAD + cold_protection = HEAD + min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT + flags_inv = HIDEHAIR|HIDEEARS + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 10, "rad" = 0, "fire" = 0, "acid" = 0) + +/obj/item/clothing/suit/hooded/wintercoat/captain + name = "captain's winter coat" + icon_state = "coatcaptain" + item_state = "coatcaptain" + armor = list("melee" = 25, "bullet" = 30, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 50) + hoodtype = /obj/item/clothing/head/hooded/winterhood/captain + +/obj/item/clothing/suit/hooded/wintercoat/captain/Initialize() + . = ..() + allowed = GLOB.security_wintercoat_allowed + +/obj/item/clothing/head/hooded/winterhood/captain + icon_state = "winterhood_captain" + armor = list("melee" = 25, "bullet" = 30, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 50) + +/obj/item/clothing/suit/hooded/wintercoat/security + name = "security winter coat" + icon_state = "coatsecurity" + item_state = "coatsecurity" + armor = list("melee" = 25, "bullet" = 15, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 45) + hoodtype = /obj/item/clothing/head/hooded/winterhood/security + +/obj/item/clothing/suit/hooded/wintercoat/security/Initialize() + . = ..() + allowed = GLOB.security_wintercoat_allowed + +/obj/item/clothing/head/hooded/winterhood/security + icon_state = "winterhood_security" + armor = list("melee" = 25, "bullet" = 15, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 45) + +/obj/item/clothing/suit/hooded/wintercoat/medical + name = "medical winter coat" + icon_state = "coatmedical" + item_state = "coatmedical" + allowed = list(/obj/item/analyzer, /obj/item/sensor_device, /obj/item/stack/medical, /obj/item/dnainjector, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/hypospray, /obj/item/healthanalyzer, /obj/item/flashlight/pen, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/pill, /obj/item/storage/pill_bottle, /obj/item/paper, /obj/item/melee/classic_baton/telescopic, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 50, "rad" = 0, "fire" = 0, "acid" = 45) + hoodtype = /obj/item/clothing/head/hooded/winterhood/medical + +/obj/item/clothing/head/hooded/winterhood/medical + icon_state = "winterhood_medical" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 0, "bio" = 50, "rad" = 0, "fire" = 0, "acid" = 45) + +/obj/item/clothing/suit/hooded/wintercoat/medical/paramedic + name = "paramedic winter coat" + icon_state = "coatparamedic" + item_state = "coatparamedic" + hoodtype = /obj/item/clothing/head/hooded/winterhood/medical/paramedic + +/obj/item/clothing/head/hooded/winterhood/medical/paramedic + icon_state = "winterhood_paramedic" + +/obj/item/clothing/suit/hooded/wintercoat/science + name = "science winter coat" + icon_state = "coatscience" + item_state = "coatscience" + allowed = list(/obj/item/analyzer, /obj/item/stack/medical, /obj/item/dnainjector, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/syringe, /obj/item/reagent_containers/hypospray, /obj/item/healthanalyzer, /obj/item/flashlight/pen, /obj/item/reagent_containers/glass/bottle, /obj/item/reagent_containers/glass/beaker, /obj/item/reagent_containers/pill, /obj/item/storage/pill_bottle, /obj/item/paper, /obj/item/melee/classic_baton/telescopic, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + hoodtype = /obj/item/clothing/head/hooded/winterhood/science + +/obj/item/clothing/head/hooded/winterhood/science + icon_state = "winterhood_science" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0,"energy" = 0, "bomb" = 10, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + +/obj/item/clothing/suit/hooded/wintercoat/engineering + name = "engineering winter coat" + icon_state = "coatengineer" + item_state = "coatengineer" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 20, "fire" = 30, "acid" = 45) + allowed = list(/obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/t_scanner, /obj/item/construction/rcd, /obj/item/pipe_dispenser, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter) + hoodtype = /obj/item/clothing/head/hooded/winterhood/engineering + +/obj/item/clothing/head/hooded/winterhood/engineering + icon_state = "winterhood_engineer" + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 20, "fire" = 30, "acid" = 45) + +/obj/item/clothing/suit/hooded/wintercoat/engineering/atmos + name = "atmospherics winter coat" + icon_state = "coatatmos" + item_state = "coatatmos" + hoodtype = /obj/item/clothing/head/hooded/winterhood/engineering/atmos + +/obj/item/clothing/head/hooded/winterhood/engineering/atmos + icon_state = "winterhood_atmos" + +/obj/item/clothing/suit/hooded/wintercoat/hydro + name = "hydroponics winter coat" + icon_state = "coathydro" + item_state = "coathydro" + allowed = list(/obj/item/reagent_containers/spray/plantbgone, /obj/item/plant_analyzer, /obj/item/seeds, /obj/item/reagent_containers/glass/bottle, /obj/item/cultivator, /obj/item/reagent_containers/spray/pestspray, /obj/item/hatchet, /obj/item/storage/bag/plants, /obj/item/toy, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/storage/fancy/cigarettes, /obj/item/lighter) + hoodtype = /obj/item/clothing/head/hooded/winterhood/hydro + +/obj/item/clothing/head/hooded/winterhood/hydro + icon_state = "winterhood_hydro" + +/obj/item/clothing/suit/hooded/wintercoat/cargo + name = "cargo winter coat" + icon_state = "coatcargo" + item_state = "coatcargo" + hoodtype = /obj/item/clothing/head/hooded/winterhood/cargo + +/obj/item/clothing/head/hooded/winterhood/cargo + icon_state = "winterhood_cargo" + +/obj/item/clothing/suit/hooded/wintercoat/miner + name = "mining winter coat" + icon_state = "coatminer" + item_state = "coatminer" + allowed = list(/obj/item/pickaxe, /obj/item/flashlight, /obj/item/tank/internals/emergency_oxygen, /obj/item/tank/internals/plasmaman, /obj/item/toy, /obj/item/storage/fancy/cigarettes, /obj/item/lighter) + armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + hoodtype = /obj/item/clothing/head/hooded/winterhood/miner + +/obj/item/clothing/head/hooded/winterhood/miner + icon_state = "winterhood_miner" + armor = list("melee" = 10, "bullet" = 0, "laser" = 0, "energy" = 0, "bomb" = 0, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 0) + +// Inteq + +/obj/item/clothing/suit/hooded/wintercoat/security/inteq + name = "inteq winter coat" + desc = "An armored wintercoat in the colors of the IRMG, the zipper tab is the golden shield of the IRMG." + icon_state = "coatinteq" + item_state = "coatinteq" + hoodtype = /obj/item/clothing/head/hooded/winterhood/security/inteq + supports_variations = KEPORI_VARIATION + +/obj/item/clothing/head/hooded/winterhood/security/inteq + icon_state = "winterhood_inteq" + supports_variations = KEPORI_VARIATION + +/obj/item/clothing/suit/hooded/coat/inteq + name = "inteq hooded coat" + desc = "A hooded coat with a fur trim around the hood, comfy! It has a small 'IRMG' embroidered onto the shoulder." + icon_state = "hoodieinteq" + item_state = "hoodieinteq" + armor = list("melee" = 25, "bullet" = 15, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 45) + hoodtype = /obj/item/clothing/head/hooded/coat/inteq + +/obj/item/clothing/head/hooded/coat/inteq + name = "inteq hood" + desc = "A comfortable looking brown hood." + icon_state = "hoodinteq" + item_state = "hoodinteq" + armor = list("melee" = 25, "bullet" = 15, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 0, "acid" = 45) + +/obj/item/clothing/suit/hooded/coat/inteq/Initialize() + . = ..() + allowed = GLOB.security_wintercoat_allowed + +// CentCom +/obj/item/clothing/suit/hooded/wintercoat/centcom + name = "centcom winter coat" + desc = "A luxurious winter coat woven in the bright green and gold colours of Central Command. It has a small pin in the shape of the Nanotrasen logo for a zipper." + icon_state = "coatcentcom" + item_state = "coatcentcom" + armor = list("melee" = 35, "bullet" = 40, "laser" = 40, "energy" = 50, "bomb" = 35, "bio" = 10, "rad" = 10, "fire" = 10, "acid" = 60) + hoodtype = /obj/item/clothing/head/hooded/winterhood/centcom + +/obj/item/clothing/suit/hooded/wintercoat/centcom/Initialize(mapload) + . = ..() + allowed += GLOB.security_wintercoat_allowed + +/obj/item/clothing/head/hooded/winterhood/centcom + icon_state = "winterhood_centcom" + armor = list("melee" = 35, "bullet" = 40, "laser" = 40, "energy" = 50, "bomb" = 35, "bio" = 10, "rad" = 10, "fire" = 10, "acid" = 60) diff --git a/code/modules/clothing/under/jobs/centcom.dm b/code/modules/clothing/under/jobs/centcom.dm index 992f8eb02653..d862b53eca2e 100644 --- a/code/modules/clothing/under/jobs/centcom.dm +++ b/code/modules/clothing/under/jobs/centcom.dm @@ -2,23 +2,51 @@ icon = 'icons/obj/clothing/under/centcom.dmi' mob_overlay_icon = 'icons/mob/clothing/under/centcom.dmi' -/obj/item/clothing/under/rank/centcom/officer - name = "\improper CentCom officer's jumpsuit" - desc = "It's a jumpsuit worn by CentCom Officers." - icon_state = "officer" - item_state = "g_suit" - alt_covers_chest = TRUE - /obj/item/clothing/under/rank/centcom/commander - name = "\improper CentCom officer's jumpsuit" - desc = "It's a jumpsuit worn by CentCom's highest-tier Commanders." + name = "\improper CentCom commander's suit" + desc = "It's a suit worn by CentCom's highest-tier Commanders." icon_state = "centcom" item_state = "dg_suit" +/obj/item/clothing/under/rank/centcom/official + name = "\improper CentCom official's suit" + desc = "A suit worn by CentCom Officials, with a silver belt buckle to indicate their rank from a glance." + icon_state = "official" + item_state = "dg_suit" + /obj/item/clothing/under/rank/centcom/intern name = "\improper CentCom intern's jumpsuit" desc = "It's a jumpsuit worn by those interning for CentCom. The top is styled after a polo shirt for easy identification." icon_state = "intern" - item_state = "g_suit" + item_state = "dg_suit" can_adjust = FALSE +/obj/item/clothing/under/rank/centcom/officer + name = "\improper CentCom turtleneck suit" + desc = "A casual, yet refined green turtleneck, used by CentCom Officers. It has a fragrance of aloe." + icon_state = "officer" + item_state = "dg_suit" + alt_covers_chest = TRUE + +/obj/item/clothing/under/rank/centcom/officer/replica + name = "\improper CentCom turtleneck replica" + desc = "A cheap copy of the CentCom turtleneck! A Donk Co. logo can be seen on the collar." + +/obj/item/clothing/under/rank/centcom/officer_skirt + name = "\improper CentCom turtleneck skirt" + desc = "A skirt version of the CentCom turtleneck, rarer and more sought after than the original." + icon_state = "officer_skirt" + item_state = "dg_suit" + alt_covers_chest = TRUE + body_parts_covered = CHEST|GROIN|ARMS + +/obj/item/clothing/under/rank/centcom/officer_skirt/replica + name = "\improper CentCom turtleneck skirt replica" + desc = "A cheap copy of the CentCom turtleneck skirt! A Donk Co. logo can be seen on the collar." + +/obj/item/clothing/under/rank/centcom/centcom_skirt + name = "\improper CentCom commander's suitskirt" + desc = "It's a suitskirt worn by CentCom's highest-tier Commanders." + icon_state = "centcom_skirt" + item_state = "dg_suit" + body_parts_covered = CHEST|GROIN|ARMS diff --git a/code/modules/events/stray_cargo.dm b/code/modules/events/stray_cargo.dm index 4c740ad924ae..182ea658a7a9 100644 --- a/code/modules/events/stray_cargo.dm +++ b/code/modules/events/stray_cargo.dm @@ -51,7 +51,7 @@ crate.locked = FALSE //Unlock secure crates crate.update_appearance() var/obj/structure/closet/supplypod/pod = make_pod() - new /obj/effect/DPtarget(LZ, pod, crate) + new /obj/effect/pod_landingzone(LZ, pod, crate) ///Handles the creation of the pod, in case it needs to be modified beforehand /datum/round_event/stray_cargo/proc/make_pod() diff --git a/code/modules/holodeck/area_copy.dm b/code/modules/holodeck/area_copy.dm index 92687709e7d9..3ef68c2345d3 100644 --- a/code/modules/holodeck/area_copy.dm +++ b/code/modules/holodeck/area_copy.dm @@ -20,7 +20,7 @@ GLOBAL_LIST_INIT(duplicate_forbidden_vars,list( if(islist(original.vars[V])) var/list/L = original.vars[V] O.vars[V] = L.Copy() - else if(istype(original.vars[V], /datum)) + else if(istype(original.vars[V], /datum) || ismob(original.vars[V])) continue // this would reference the original's object, that will break when it is used or deleted. else O.vars[V] = original.vars[V] @@ -52,8 +52,12 @@ GLOBAL_LIST_INIT(duplicate_forbidden_vars,list( contained_atom.flags_1 |= HOLOGRAM_1 if(M.circuit) M.circuit.flags_1 |= HOLOGRAM_1 - return O + if(ismob(O)) //Overlays are carried over despite disallowing them, if a fix is found remove this. + var/mob/M = O + M.cut_overlays() + M.regenerate_icons() + return O /area/proc/copy_contents_to(area/A , platingRequired = 0, nerf_weapons = 0) //Takes: Area. Optional: If it should copy to areas that don't have plating diff --git a/code/modules/jobs/job_types/captain.dm b/code/modules/jobs/job_types/captain.dm index 550624dfa034..bf8bf6cd8086 100644 --- a/code/modules/jobs/job_types/captain.dm +++ b/code/modules/jobs/job_types/captain.dm @@ -190,7 +190,7 @@ satchel = /obj/item/storage/backpack/satchel/ duffelbag = /obj/item/storage/backpack/duffelbag courierbag = /obj/item/storage/backpack/messenger - backpack_contents = list(/obj/item/gun/ballistic/automatic/pistol/commander=1, /obj/item/clothing/accessory/medal/gold/captain=1, /obj/item/spacecash/bundle/c10000=1) + backpack_contents = list(/obj/item/clothing/accessory/medal/gold/captain=1, /obj/item/spacecash/bundle/c10000=1) /datum/outfit/job/captain/inteq name = "IRMG Vanguard (Inteq)" @@ -210,7 +210,7 @@ accessory = null courierbag = /obj/item/storage/backpack/messenger/inteq - backpack_contents = list(/obj/item/melee/classic_baton/telescopic=1, /obj/item/ammo_box/magazine/co9mm=1, /obj/item/pda/captain) + backpack_contents = list(/obj/item/melee/classic_baton/telescopic=1, /obj/item/pda/captain) /datum/outfit/job/captain/inteq/naked name = "IRMG Vanguard (Inteq) (Naked)" diff --git a/code/modules/jobs/job_types/head_of_security.dm b/code/modules/jobs/job_types/head_of_security.dm index 50f7fb555737..f38c9fd3a901 100644 --- a/code/modules/jobs/job_types/head_of_security.dm +++ b/code/modules/jobs/job_types/head_of_security.dm @@ -38,10 +38,10 @@ gloves = /obj/item/clothing/gloves/color/black head = /obj/item/clothing/head/HoS glasses = /obj/item/clothing/glasses/hud/security/sunglasses - suit_store = /obj/item/gun/energy/e_gun + suit_store = null r_pocket = /obj/item/assembly/flash/handheld l_pocket = /obj/item/restraints/handcuffs - backpack_contents = list(/obj/item/melee/baton/loaded=1) + backpack_contents = list(/obj/item/melee/classic_baton=1) backpack = /obj/item/storage/backpack/security satchel = /obj/item/storage/backpack/satchel/sec @@ -69,7 +69,7 @@ head = /obj/item/clothing/head/warden suit = /obj/item/clothing/suit/armor/vest/syndie id = /obj/item/card/id/syndicate_command/crew_id - backpack_contents = list(/obj/item/melee/baton/loaded=1, /obj/item/storage/box/survival/syndie=1) + backpack_contents = list(/obj/item/melee/classic_baton=1,/obj/item/storage/box/survival/syndie=1) /datum/outfit/job/hos/nanotrasen name = "Head of Security (Nanotrasen)" diff --git a/code/modules/jobs/job_types/security_officer.dm b/code/modules/jobs/job_types/security_officer.dm index df111c73316c..d847e015cde6 100644 --- a/code/modules/jobs/job_types/security_officer.dm +++ b/code/modules/jobs/job_types/security_officer.dm @@ -87,7 +87,7 @@ GLOBAL_LIST_INIT(available_depts, list(SEC_DEPT_ENGINEERING, SEC_DEPT_MEDICAL, S shoes = /obj/item/clothing/shoes/jackboots l_pocket = /obj/item/restraints/handcuffs r_pocket = /obj/item/assembly/flash/handheld - backpack_contents = list(/obj/item/melee/baton/loaded=1, /obj/item/ammo_box/magazine/co9mm=1, /obj/item/gun_voucher=1) //WS edit - security rearming + backpack_contents = null //WS edit - security rearming // SHIPTEST EDIT - security re-disarming. certified whitesands moment. backpack = /obj/item/storage/backpack/security satchel = /obj/item/storage/backpack/satchel/sec @@ -175,7 +175,7 @@ GLOBAL_LIST_INIT(available_depts, list(SEC_DEPT_ENGINEERING, SEC_DEPT_MEDICAL, S gloves = /obj/item/clothing/gloves/tackler/combat/insulated glasses = /obj/item/clothing/glasses/hud/diagnostic - backpack_contents = list(/obj/item/melee/classic_baton=1, /obj/item/gun/ballistic/automatic/pistol/commander=1, /obj/item/restraints/handcuffs=1) + backpack_contents = list(/obj/item/melee/classic_baton=1, /obj/item/restraints/handcuffs=1) /datum/outfit/job/security/inteq name = "IRMG Enforcer (Inteq)" @@ -190,8 +190,10 @@ GLOBAL_LIST_INIT(available_depts, list(SEC_DEPT_ENGINEERING, SEC_DEPT_MEDICAL, S glasses = /obj/item/clothing/glasses/hud/security/sunglasses/inteq gloves = /obj/item/clothing/gloves/combat + backpack = /obj/item/storage/backpack/messenger/inteq + satchel = /obj/item/storage/backpack/messenger/inteq courierbag = /obj/item/storage/backpack/messenger/inteq - backpack_contents = list(/obj/item/melee/baton/loaded=1, /obj/item/ammo_box/magazine/co9mm=1, /obj/item/gun_voucher=1,/obj/item/pda/security) + backpack_contents = list(/obj/item/pda/security) /datum/outfit/job/security/inteq/beluga name = "IRMG Enforcer (Beluga)" @@ -206,6 +208,8 @@ GLOBAL_LIST_INIT(available_depts, list(SEC_DEPT_ENGINEERING, SEC_DEPT_MEDICAL, S glasses = null gloves = /obj/item/clothing/gloves/color/evening + backpack = /obj/item/storage/backpack/messenger/inteq + satchel = /obj/item/storage/backpack/messenger/inteq courierbag = /obj/item/storage/backpack/messenger/inteq backpack_contents = list(/obj/item/pda/security) diff --git a/code/modules/jobs/job_types/shaft_miner.dm b/code/modules/jobs/job_types/shaft_miner.dm index 95b32a807da6..1fade6b2ecf0 100644 --- a/code/modules/jobs/job_types/shaft_miner.dm +++ b/code/modules/jobs/job_types/shaft_miner.dm @@ -27,7 +27,6 @@ backpack_contents = list( /obj/item/flashlight/seclite=1,\ /obj/item/kitchen/knife/combat/survival=1,\ - /obj/item/mining_voucher=1,\ /obj/item/stack/marker_beacon/ten=1) backpack = /obj/item/storage/backpack/explorer @@ -52,7 +51,6 @@ backpack_contents = list( /obj/item/flashlight/seclite=1,\ /obj/item/kitchen/knife/combat/survival=1, - /obj/item/mining_voucher=1, /obj/item/mining_scanner=1, /obj/item/stack/marker_beacon/ten=1) belt = /obj/item/gun/energy/kinetic_accelerator @@ -152,7 +150,6 @@ backpack_contents = list( /obj/item/flashlight/seclite=1, /obj/item/kitchen/knife/combat/survival=1, - /obj/item/mining_voucher=1, /obj/item/mining_scanner=1, /obj/item/wrench=1 ) @@ -161,7 +158,6 @@ backpack_contents = list( /obj/item/flashlight/seclite=1, /obj/item/kitchen/knife/combat/survival=1, - /obj/item/mining_voucher=1, /obj/item/stack/marker_beacon/ten=1, /obj/item/borg/upgrade/modkit/aoe=1 ) diff --git a/code/modules/jobs/job_types/warden.dm b/code/modules/jobs/job_types/warden.dm index d2c05670dfa5..08aeaab16e8d 100644 --- a/code/modules/jobs/job_types/warden.dm +++ b/code/modules/jobs/job_types/warden.dm @@ -36,8 +36,8 @@ glasses = /obj/item/clothing/glasses/hud/security/sunglasses r_pocket = /obj/item/assembly/flash/handheld l_pocket = /obj/item/restraints/handcuffs - suit_store = /obj/item/gun/energy/e_gun/advtaser //WS edit - Readds tasers - backpack_contents = list(/obj/item/melee/baton/loaded=1, /obj/item/ammo_box/magazine/co9mm=1) //WS edit - free lethals + suit_store = null //WS edit - Readds tasers //SHIPTEST EDIT - removes tasers + backpack_contents = list(/obj/item/melee/classic_baton) //WS edit - free lethals // SHIPTEST EDIT - nope backpack = /obj/item/storage/backpack/security satchel = /obj/item/storage/backpack/satchel/sec @@ -95,10 +95,10 @@ dcoat = /obj/item/clothing/suit/hooded/wintercoat/security/inteq shoes = /obj/item/clothing/shoes/combat gloves = /obj/item/clothing/gloves/combat - suit_store = /obj/item/gun/energy/disabler + suit_store = null courierbag = /obj/item/storage/backpack/messenger/inteq - backpack_contents = list(/obj/item/melee/classic_baton/telescopic=1, /obj/item/ammo_box/magazine/co9mm=1, /obj/item/pda/warden) + backpack_contents = list(/obj/item/melee/classic_baton=1, /obj/item/pda/warden) /datum/outfit/job/warden/nanotrasen name = "Warden (Nanotrasen)" diff --git a/code/modules/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm index 2a35e270537a..6597e5282a9f 100644 --- a/code/modules/mob/dead/new_player/new_player.dm +++ b/code/modules/mob/dead/new_player/new_player.dm @@ -220,7 +220,10 @@ ready = PLAYER_NOT_READY return FALSE - var/this_is_like_playing_right = alert(src,"Are you sure you wish to observe? You will [CONFIG_GET(flag/norespawn) ? "not " : "" ]be able to respawn later.","Player Setup","Yes","No") + var/less_input_message + if(SSlag_switch.measures[DISABLE_DEAD_KEYLOOP]) + less_input_message = " - Notice: Observer freelook is currently disabled." + var/this_is_like_playing_right = tgui_alert(src, "Are you sure you wish to observe? You will [CONFIG_GET(flag/norespawn) ? "not " : "" ]be able to respawn later.[less_input_message]", "Player Setup", list("Yes","No")) if(QDELETED(src) || !src.client || this_is_like_playing_right != "Yes") ready = PLAYER_NOT_READY @@ -361,10 +364,11 @@ GLOB.ship_select_tgui.ui_interact(src) /mob/dead/new_player/proc/can_join_round(silent = FALSE) - if(!GLOB.enter_allowed) - if(!silent) - to_chat(usr, "There is an administrative lock on entering the game!") - return FALSE + if(SSlag_switch.measures[DISABLE_NON_OBSJOBS]) + if(silent) + return + to_chat(usr, span_notice("There is an administrative lock on entering the game!")) + return if(!SSticker?.IsRoundInProgress()) if(!silent) diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index 87b241dc8d53..dca421b8736d 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -367,6 +367,8 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp return client.view_size.setDefault(getScreenSize(client.prefs.widescreenpref))//Let's reset so people can't become allseeing gods SStgui.on_transfer(src, mind.current) // Transfer NanoUIs. + if(mind.current.stat == DEAD && SSlag_switch.measures[DISABLE_DEAD_KEYLOOP]) + to_chat(src, span_warning("To leave your body again use the Ghost verb.")) mind.current.key = key mind.current.client.init_verbs() return TRUE @@ -514,6 +516,10 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp set name = "View Range" set desc = "Change your view range." + if(SSlag_switch.measures[DISABLE_GHOST_ZOOM_TRAY] && !client?.holder) + to_chat(usr, span_notice("That verb is currently globally disabled.")) + return + var/max_view = client.prefs.unlock_content ? GHOST_MAX_VIEW_RANGE_MEMBER : GHOST_MAX_VIEW_RANGE_DEFAULT if(client.view_size.getView() == client.view_size.default) var/list/views = list() @@ -528,6 +534,11 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp /mob/dead/observer/verb/add_view_range(input as num) set name = "Add View Range" set hidden = TRUE + + if(SSlag_switch.measures[DISABLE_GHOST_ZOOM_TRAY] && !client?.holder) + to_chat(usr, span_notice("That verb is currently globally disabled.")) + return + var/max_view = client.prefs.unlock_content ? GHOST_MAX_VIEW_RANGE_MEMBER : GHOST_MAX_VIEW_RANGE_DEFAULT if(input) client.rescale_view(input, 0, ((max_view*2)+1) - 15) @@ -927,6 +938,9 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp set desc = "Toggles a view of sub-floor objects" var/static/t_ray_view = FALSE + if(SSlag_switch.measures[DISABLE_GHOST_ZOOM_TRAY] && !client?.holder && !t_ray_view) + to_chat(usr, span_notice("That verb is currently globally disabled.")) + return t_ray_view = !t_ray_view var/list/t_ray_images = list() diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm index 42dd3f9ba1e3..a260f164829f 100644 --- a/code/modules/mob/living/carbon/human/examine.dm +++ b/code/modules/mob/living/carbon/human/examine.dm @@ -1,6 +1,7 @@ /mob/living/carbon/human/examine(mob/user) //this is very slightly better than it was because you can use it more places. still can't do \his[src] though. var/t_He = p_they(TRUE) + var/t_he = p_they() var/t_His = p_their(TRUE) var/t_his = p_their() var/t_him = p_them() @@ -126,7 +127,7 @@ . += "[t_His] soul seems to have been ripped out of [t_his] body. Revival is impossible." . += "" if(getorgan(/obj/item/organ/brain) && !key && !get_ghost(FALSE, TRUE)) - . += "[t_He] [t_is] limp and unresponsive; there are no signs of life and [t_his] soul has departed..." + . += "[t_He] [t_is] limp and unresponsive; there are no signs of life and [t_he] won't be coming back..." else . += "[t_He] [t_is] limp and unresponsive; there are no signs of life..." diff --git a/code/modules/mob/living/carbon/monkey/monkey.dm b/code/modules/mob/living/carbon/monkey/monkey.dm index 1c7f480e0121..6a66c0546f6e 100644 --- a/code/modules/mob/living/carbon/monkey/monkey.dm +++ b/code/modules/mob/living/carbon/monkey/monkey.dm @@ -115,12 +115,6 @@ internal = null return - -/mob/living/carbon/monkey/IsAdvancedToolUser()//Unless its monkey mode monkeys can't use advanced tools - if(mind && is_monkey(mind)) - return TRUE - return FALSE - /mob/living/carbon/monkey/can_use_guns(obj/item/G) if(G.trigger_guard == TRIGGER_GUARD_NONE) to_chat(src, "You are unable to fire this!") diff --git a/code/modules/mob/living/death.dm b/code/modules/mob/living/death.dm index f8233fad9d10..e334655b3848 100644 --- a/code/modules/mob/living/death.dm +++ b/code/modules/mob/living/death.dm @@ -59,6 +59,9 @@ I.on_mob_death(src, gibbed) if(mind && mind.name && mind.active && !istype(T.loc, /area/ctf)) deadchat_broadcast(" has died at [get_area_name(T)].", "[mind.name]", follow_target = src, turf_target = T, message_type=DEADCHAT_DEATHRATTLE) + if(SSlag_switch.measures[DISABLE_DEAD_KEYLOOP] && !client?.holder) + to_chat(src, span_deadsay(span_big("Observer freelook is disabled.\nPlease use Orbit, Teleport, and Jump to look around."))) + ghostize(TRUE) if(mind) mind.store_memory("Time of death: [tod]", 0) remove_from_alive_mob_list() diff --git a/code/modules/mob/living/living_say.dm b/code/modules/mob/living/living_say.dm index ba8983691bdd..f092cef8cac9 100644 --- a/code/modules/mob/living/living_say.dm +++ b/code/modules/mob/living/living_say.dm @@ -128,6 +128,12 @@ GLOBAL_LIST_INIT(department_radio_keys, list( say_dead(original_message) return + if(client && SSlag_switch.measures[SLOWMODE_SAY] && !HAS_TRAIT(src, TRAIT_BYPASS_MEASURES) && !forced && src == usr) + if(!COOLDOWN_FINISHED(client, say_slowmode)) + to_chat(src, span_warning("Message not sent due to slowmode. Please wait [SSlag_switch.slowmode_cooldown/10] seconds between messages.\n\"[message]\"")) + return + COOLDOWN_START(client, say_slowmode, SSlag_switch.slowmode_cooldown) + if(!can_speak_basic(original_message, ignore_spam, forced)) return @@ -287,7 +293,7 @@ GLOBAL_LIST_INIT(department_radio_keys, list( //speech bubble var/list/speech_bubble_recipients = list() for(var/mob/M in listening) - if(M.client && !M.client.prefs.chat_on_map) + if(M.client && (!M.client.prefs.chat_on_map || (SSlag_switch.measures[DISABLE_RUNECHAT] && !HAS_TRAIT(src, TRAIT_BYPASS_MEASURES)))) speech_bubble_recipients.Add(M.client) var/image/I = image('icons/mob/talk.dmi', src, "[bubble_type][say_test(message)]", FLY_LAYER) I.appearance_flags = APPEARANCE_UI_IGNORE_ALPHA diff --git a/code/modules/mob/mob_helpers.dm b/code/modules/mob/mob_helpers.dm index 1a165ccd3f7b..f7ef27f0c298 100644 --- a/code/modules/mob/mob_helpers.dm +++ b/code/modules/mob/mob_helpers.dm @@ -345,11 +345,7 @@ if("apprentice") if(M.mind in SSticker.mode.apprentices) return 2 - if("monkey") - if(isliving(M)) - var/mob/living/L = M - if(L.diseases && (locate(/datum/disease/transformation/jungle_fever) in L.diseases)) - return 2 + return TRUE if(M.mind && LAZYLEN(M.mind.antag_datums)) //they have an antag datum! return TRUE diff --git a/code/modules/mob/mob_lists.dm b/code/modules/mob/mob_lists.dm index bd47d511e1d4..861a87649ebf 100644 --- a/code/modules/mob/mob_lists.dm +++ b/code/modules/mob/mob_lists.dm @@ -42,6 +42,10 @@ /mob/proc/add_to_player_list() SHOULD_CALL_PARENT(TRUE) GLOB.player_list |= src + if(client.holder) + GLOB.keyloop_list |= src + else if(stat != DEAD || !SSlag_switch?.measures[DISABLE_DEAD_KEYLOOP]) + GLOB.keyloop_list |= src if(!SSticker?.mode) return if(stat == DEAD) @@ -53,6 +57,7 @@ /mob/proc/remove_from_player_list() SHOULD_CALL_PARENT(TRUE) GLOB.player_list -= src + GLOB.keyloop_list -= src if(!SSticker?.mode) return if(stat == DEAD) diff --git a/code/modules/mob/mob_say.dm b/code/modules/mob/mob_say.dm index 178ff23d991e..7e70eb0ee159 100644 --- a/code/modules/mob/mob_say.dm +++ b/code/modules/mob/mob_say.dm @@ -69,6 +69,12 @@ to_chat(src, "You cannot talk in deadchat (muted).") return + if(SSlag_switch.measures[SLOWMODE_SAY] && !HAS_TRAIT(src, TRAIT_BYPASS_MEASURES) && src == usr) + if(!COOLDOWN_FINISHED(client, say_slowmode)) + to_chat(src, span_warning("Message not sent due to slowmode. Please wait [SSlag_switch.slowmode_cooldown/10] seconds between messages.\n\"[message]\"")) + return + COOLDOWN_START(client, say_slowmode, SSlag_switch.slowmode_cooldown) + if(src.client.handle_spam_prevention(message,MUTE_DEADCHAT)) return diff --git a/code/modules/overmap/missions/research_mission.dm b/code/modules/overmap/missions/research_mission.dm index c1de1318b5b5..c80686039f8d 100644 --- a/code/modules/overmap/missions/research_mission.dm +++ b/code/modules/overmap/missions/research_mission.dm @@ -56,7 +56,7 @@ if(!over_obj || !scanner.is_operational || scanner_port?.current_ship != servant) return num_current++ - +/* commented out until ion storms aren't literal torture /datum/mission/research/ion name = "Ion storm research mission" desc = "We require data on the behavior of ion storms in the system for an ongoing study. \ @@ -64,7 +64,7 @@ It must be powered to collect the data." value = 3500 objective_type = /datum/overmap/event/emp - +*/ /datum/mission/research/meteor name = "Asteroid field research mission" desc = "We require data on the behavior of asteroid fields in the system for an ongoing study. \ diff --git a/code/modules/overmap/objects/dynamic_datum.dm b/code/modules/overmap/objects/dynamic_datum.dm index 38f44e2d2fe0..4b62f777da73 100644 --- a/code/modules/overmap/objects/dynamic_datum.dm +++ b/code/modules/overmap/objects/dynamic_datum.dm @@ -93,8 +93,8 @@ if(preserve_level) return - if(length(mapzone?.get_mind_mobs())) - return //Dont fuck over stranded people? tbh this shouldn't be called on this condition, instead of bandaiding it inside + if(length(mapzone?.get_mind_mobs()) || SSlag_switch.measures[DISABLE_PLANETDEL]) + return //Dont fuck over stranded people log_shuttle("[src] [REF(src)] UNLOAD") var/list/results = SSovermap.get_unused_overmap_square() @@ -172,6 +172,8 @@ * * visiting shuttle - The docking port of the shuttle visiting the level. */ /datum/overmap/dynamic/proc/load_level() + if(SSlag_switch.measures[DISABLE_PLANETGEN] && !(HAS_TRAIT(usr, TRAIT_BYPASS_MEASURES))) + return FALSE if(mapzone) return TRUE log_shuttle("[src] [REF(src)] LEVEL_INIT") diff --git a/code/modules/overmap/objects/event_datum.dm b/code/modules/overmap/objects/event_datum.dm index f63c2ceae82a..bfed840a1acd 100644 --- a/code/modules/overmap/objects/event_datum.dm +++ b/code/modules/overmap/objects/event_datum.dm @@ -90,7 +90,7 @@ /obj/effect/meteor/irradiated=10, /obj/effect/meteor/tunguska = 1 ) - +/* commented out until ion storms aren't literal torture ///ION STORM - explodes your IPCs /datum/overmap/event/emp name = "ion storm (moderate)" @@ -128,7 +128,7 @@ chance_to_affect = 25 chain_rate = 4 strength = 6 - +*/ ///ELECTRICAL STORM - explodes your computer and IPCs /datum/overmap/event/electric name = "electrical storm (moderate)" @@ -338,9 +338,11 @@ GLOBAL_LIST_INIT(overmap_event_pick_list, list( /datum/overmap/event/electric/minor = 45, /datum/overmap/event/electric = 40, /datum/overmap/event/electric/major = 35, + /* commented out until ion storms aren't literal torture /datum/overmap/event/emp/minor = 45, /datum/overmap/event/emp = 40, /datum/overmap/event/emp/major = 45, + */ /datum/overmap/event/meteor/minor = 45, /datum/overmap/event/meteor = 40, /datum/overmap/event/meteor/major = 35, diff --git a/code/modules/projectiles/guns/ballistic/assault.dm b/code/modules/projectiles/guns/ballistic/assault.dm index 6b0d14616ef7..fff17d1b8e07 100644 --- a/code/modules/projectiles/guns/ballistic/assault.dm +++ b/code/modules/projectiles/guns/ballistic/assault.dm @@ -21,7 +21,7 @@ /obj/item/gun/ballistic/automatic/assualt/ak47/nt name = "\improper NT-SVG" - desc = "An even cheaper version of the already-cheap SVG-67, rechambered for the lightweight 4.6x38mm PDW cartridge. The flimsy folding stock and light construction make for a highly portable rifle lacking in accuracy and stopping power." + desc = "An even cheaper version of the already-cheap SVG-67, rechambered for the lightweight 4.6x30mm PDW cartridge. The flimsy folding stock and light construction make for a highly portable rifle lacking in accuracy and stopping power." icon = 'icons/obj/guns/48x32guns.dmi' fire_sound = 'sound/weapons/gun/rifle/shot.ogg' icon_state = "ak47_nt" diff --git a/code/modules/projectiles/projectile/bullets/rifle.dm b/code/modules/projectiles/projectile/bullets/rifle.dm index 8fe2beeae20c..5143e66ef23a 100644 --- a/code/modules/projectiles/projectile/bullets/rifle.dm +++ b/code/modules/projectiles/projectile/bullets/rifle.dm @@ -5,7 +5,7 @@ damage = 25 armour_penetration = 20 -// 7.62 (Nagant Rifle) +// 7.62x54mmR (Illestren Rifle) /obj/projectile/bullet/a762 name = "7.62x54mmR bullet" @@ -14,8 +14,9 @@ /obj/projectile/bullet/a300 name = ".300 Magnum bullet" - damage = 60 + damage = 40 stamina = 10 + armour_penetration = 40 /obj/projectile/bullet/a762_enchanted name = "enchanted 7.62x54mmR bullet" @@ -34,7 +35,7 @@ /obj/projectile/bullet/aac_300blk name = ".300 Blackout bullet" damage = 30 - dismemberment = 20 + armour_penetration = 20 //7.62x39mm (SVG-67) @@ -50,9 +51,9 @@ damage = 30 armour_penetration = 40 -// 8x58 (SG-whatever) +// 8x58mm caseless (SG-669) /obj/projectile/bullet/a858 name = "8x58mm caseless bullet" - damage = 50 - armour_penetration = 15 + damage = 30 + armour_penetration = 40 diff --git a/code/modules/research/designs/autolathe_designs.dm b/code/modules/research/designs/autolathe_designs.dm index 5e0947d7e378..f7f471a3d860 100644 --- a/code/modules/research/designs/autolathe_designs.dm +++ b/code/modules/research/designs/autolathe_designs.dm @@ -1175,6 +1175,14 @@ build_path = /obj/item/storage/bag/trash category = list("initial","Tools","Tool Designs","Misc") +/datum/design/bodybag + name="Body Bag" + id="bodybag" + build_type = AUTOLATHE | PROTOLATHE + materials = list(/datum/material/plastic = 4000) + build_path = /obj/item/bodybag + category = list("initial","Medical","Misc") + /datum/design/fishing_rod_basic name = "Fishing Rod" id = "fishing rod" diff --git a/code/modules/vending/clothesmate.dm b/code/modules/vending/clothesmate.dm index 17a6ec2da484..13598f2bc13a 100644 --- a/code/modules/vending/clothesmate.dm +++ b/code/modules/vending/clothesmate.dm @@ -116,7 +116,9 @@ /obj/item/clothing/suit/jacket/letterman_syndie = 1, /obj/item/clothing/under/costume/jabroni = 1, /obj/item/clothing/suit/vapeshirt = 1, - /obj/item/clothing/under/costume/geisha = 1) + /obj/item/clothing/under/costume/geisha = 1, + /obj/item/clothing/under/rank/centcom/officer/replica = 1, + /obj/item/clothing/under/rank/centcom/officer_skirt/replica = 1) premium = list( /obj/item/clothing/under/suit/checkered = 1, /obj/item/clothing/suit/jacket/leather = 1, diff --git a/code/modules/vending/wardrobes.dm b/code/modules/vending/wardrobes.dm index 42ecc4ce697e..a774f048f443 100644 --- a/code/modules/vending/wardrobes.dm +++ b/code/modules/vending/wardrobes.dm @@ -512,3 +512,33 @@ /obj/item/vending_refill/wardrobe/det_wardrobe machine_name = "DetDrobe" + + +/obj/machinery/vending/wardrobe/cent_wardrobe + name = "\improper CentDrobe" + desc = "A one-of-a-kind vending machine for all your centcom aesthetic needs!" + icon_state = "centdrobe" + product_ads = "Show those ERTs who's the most stylish in the briefing room!" + vend_reply = "Thank you for using the CentDrobe!" + products = list( + /obj/item/clothing/shoes/laceup = 3, + /obj/item/clothing/shoes/jackboots = 3, + /obj/item/clothing/gloves/combat = 3, + /obj/item/clothing/glasses/sunglasses = 3, + /obj/item/clothing/under/rank/centcom/commander = 3, + /obj/item/clothing/under/rank/centcom/centcom_skirt = 3, + /obj/item/clothing/under/rank/centcom/intern = 3, + /obj/item/clothing/under/rank/centcom/official = 3, + /obj/item/clothing/under/rank/centcom/officer = 3, + /obj/item/clothing/under/rank/centcom/officer_skirt = 3, + /obj/item/clothing/suit/toggle/armor/vest/centcom_formal = 3, + /obj/item/clothing/suit/space/officer = 3, + /obj/item/clothing/suit/hooded/wintercoat/centcom = 3, + /obj/item/clothing/head/centcom_cap = 3, + /obj/item/clothing/head/centhat = 3, + /obj/item/clothing/head/intern = 3, + ) + refill_canister = /obj/item/vending_refill/wardrobe/cent_wardrobe +/obj/item/vending_refill/wardrobe/cent_wardrobe + machine_name = "CentDrobe" + light_color = LIGHT_COLOR_ELECTRIC_GREEN diff --git a/config/config.txt b/config/config.txt index 89dd495066eb..0cdece6b6a16 100644 --- a/config/config.txt +++ b/config/config.txt @@ -338,6 +338,9 @@ NOTE_FRESH_DAYS 91.31055 ## Notes older then this will be completely faded out. NOTE_STALE_DAYS 365.2422 +## Uncomment to allow drastic performence enhancemet measures to turn on automatically once there are equal or more clients than the configured amount (will also prompt admin for veto) +#AUTO_LAG_SWITCH_POP 75 + ##Note: all population caps can be used with each other if desired. ## Uncomment for 'soft' population caps, players will be warned while joining if the living crew exceeds the listed number. diff --git a/html/changelogs/archive/2023-09.yml b/html/changelogs/archive/2023-09.yml index 8eb857771e62..7dac49601dd5 100644 --- a/html/changelogs/archive/2023-09.yml +++ b/html/changelogs/archive/2023-09.yml @@ -44,3 +44,40 @@ Dethstorm: - rscadd: new stuff to wasteplanet_unhonorable - rscadd: new areas for wasteplanet_unhonorable +2023-09-14: + Apogee-dev: + - balance: nerfed some outlier rifle cartridges for consistency + - rscdel: Removed guns and ammo from sec spawn outfits + BarteG44: + - rscadd: Added a voice log for the wideband + Bjarl: + - rscdel: monkey and wishgranter code + MarkSuckerberg: + - admin: Adds lag switch toggles from /tg/, ONLY use them when the lag becomes unbearable! + PositiveEntropy, tf-4: + - rscadd: Adds the CentVend inside Central Command! You're now able to vend Central + Command clothing items for all your commanding needs! + - rscadd: 'Nanotrasen has added a new outfit for Special Ops Officers to enjoy, + instead of a simple leather jacket: The CentCom Officer''s Coat!' + - rscadd: Re-adds the CentCom Official's suit, making it the default clothing option + for CentCom Officials! The turtlenecks have instead been made to be the standard + ERT uniform. + - imageadd: Thanks to a collaboration between the frontier sector and the core sector, + the parade jackets now boast new and varied apperances for all to enjoy, with + a new parade jacket releasing for the Head of Security! + - imageadd: In no short effort than the finest of tailors, the captain's hat, the + centcom hat, the captain's jumpsuit and the captain's carapace now have finer + gold trims and the finest quality leather available, making them more vibrant + for all! + - imageadd: In no short effort of our best tailors, every Central Command outfit + have been either redesigned or reshaded! All of them! + Skrem7: + - spellcheck: Bodies that lack ownership are no longer described as "soulless" + - spellcheck: typo moment in nt-svg rifle ammo type + Zevotech: + - rscadd: exosuit fabricatiors can now be connected to the RND server via multitool + ritorizo: + - rscadd: Body bags in the autolathe. + thgvr: + - admin: Improved admin build mode menu and Drop pods from tgstation + - rscdel: ion storms are removed diff --git a/icons/effects/supplypod_pickturf.dmi b/icons/effects/supplypod_pickturf.dmi new file mode 100644 index 000000000000..3ca1131e1a85 Binary files /dev/null and b/icons/effects/supplypod_pickturf.dmi differ diff --git a/icons/effects/supplypod_pickturf_down.dmi b/icons/effects/supplypod_pickturf_down.dmi new file mode 100644 index 000000000000..113fe47540c3 Binary files /dev/null and b/icons/effects/supplypod_pickturf_down.dmi differ diff --git a/icons/misc/buildmode.dmi b/icons/misc/buildmode.dmi index 83ee2a87815a..3a73559091b2 100644 Binary files a/icons/misc/buildmode.dmi and b/icons/misc/buildmode.dmi differ diff --git a/icons/mob/clothing/head.dmi b/icons/mob/clothing/head.dmi index e4344a57d42f..9ba2f5b219db 100644 Binary files a/icons/mob/clothing/head.dmi and b/icons/mob/clothing/head.dmi differ diff --git a/icons/mob/clothing/mask.dmi b/icons/mob/clothing/mask.dmi index 6345f8e95b7a..90a54af8fa9e 100644 Binary files a/icons/mob/clothing/mask.dmi and b/icons/mob/clothing/mask.dmi differ diff --git a/icons/mob/clothing/suits/armor.dmi b/icons/mob/clothing/suits/armor.dmi index ca683c1bec46..2bd6aa565fb3 100644 Binary files a/icons/mob/clothing/suits/armor.dmi and b/icons/mob/clothing/suits/armor.dmi differ diff --git a/icons/mob/clothing/suits/hooded.dmi b/icons/mob/clothing/suits/hooded.dmi index 18ae544a659d..73e95617cd94 100644 Binary files a/icons/mob/clothing/suits/hooded.dmi and b/icons/mob/clothing/suits/hooded.dmi differ diff --git a/icons/mob/clothing/suits/toggle.dmi b/icons/mob/clothing/suits/toggle.dmi index 73b6b92d0a81..2059afd5bf46 100644 Binary files a/icons/mob/clothing/suits/toggle.dmi and b/icons/mob/clothing/suits/toggle.dmi differ diff --git a/icons/mob/clothing/under/centcom.dmi b/icons/mob/clothing/under/centcom.dmi index f76130f1e58b..3f6098a26600 100644 Binary files a/icons/mob/clothing/under/centcom.dmi and b/icons/mob/clothing/under/centcom.dmi differ diff --git a/icons/obj/clothing/hats.dmi b/icons/obj/clothing/hats.dmi index d71c875e9b3b..83db437408eb 100644 Binary files a/icons/obj/clothing/hats.dmi and b/icons/obj/clothing/hats.dmi differ diff --git a/icons/obj/clothing/masks.dmi b/icons/obj/clothing/masks.dmi index 7e93ef3ede27..89d5ed3328bd 100644 Binary files a/icons/obj/clothing/masks.dmi and b/icons/obj/clothing/masks.dmi differ diff --git a/icons/obj/clothing/suits/armor.dmi b/icons/obj/clothing/suits/armor.dmi index 387a2f40f303..7d5b69ab1fc2 100644 Binary files a/icons/obj/clothing/suits/armor.dmi and b/icons/obj/clothing/suits/armor.dmi differ diff --git a/icons/obj/clothing/suits/hooded.dmi b/icons/obj/clothing/suits/hooded.dmi index 5ced5746e4b7..ffdedb06bc8c 100644 Binary files a/icons/obj/clothing/suits/hooded.dmi and b/icons/obj/clothing/suits/hooded.dmi differ diff --git a/icons/obj/clothing/suits/toggle.dmi b/icons/obj/clothing/suits/toggle.dmi index 5fb84c8759e0..806101d0a455 100644 Binary files a/icons/obj/clothing/suits/toggle.dmi and b/icons/obj/clothing/suits/toggle.dmi differ diff --git a/icons/obj/clothing/under/centcom.dmi b/icons/obj/clothing/under/centcom.dmi index 8ab99ba04f2b..3fd5a370973e 100644 Binary files a/icons/obj/clothing/under/centcom.dmi and b/icons/obj/clothing/under/centcom.dmi differ diff --git a/icons/obj/supplypods.dmi b/icons/obj/supplypods.dmi index d21da6d53ae1..4dfc996f45bc 100644 Binary files a/icons/obj/supplypods.dmi and b/icons/obj/supplypods.dmi differ diff --git a/icons/obj/supplypods_32x32.dmi b/icons/obj/supplypods_32x32.dmi new file mode 100644 index 000000000000..a7607f716f7a Binary files /dev/null and b/icons/obj/supplypods_32x32.dmi differ diff --git a/icons/obj/vending.dmi b/icons/obj/vending.dmi index 97bbe730fac8..06be7b370c0c 100644 Binary files a/icons/obj/vending.dmi and b/icons/obj/vending.dmi differ diff --git a/shiptest.dme b/shiptest.dme index 3b58d8975df4..1303fb845c60 100644 --- a/shiptest.dme +++ b/shiptest.dme @@ -72,6 +72,7 @@ #include "code\__DEFINES\is_helpers.dm" #include "code\__DEFINES\jobs.dm" #include "code\__DEFINES\keybinding.dm" +#include "code\__DEFINES\lag_switch.dm" #include "code\__DEFINES\language.dm" #include "code\__DEFINES\layers.dm" #include "code\__DEFINES\lighting.dm" @@ -157,6 +158,7 @@ #include "code\__HELPERS\cmp.dm" #include "code\__HELPERS\config.dm" #include "code\__HELPERS\dates.dm" +#include "code\__HELPERS\datums.dm" #include "code\__HELPERS\dna.dm" #include "code\__HELPERS\files.dm" #include "code\__HELPERS\filters.dm" @@ -315,6 +317,7 @@ #include "code\controllers\subsystem\input.dm" #include "code\controllers\subsystem\ipintel.dm" #include "code\controllers\subsystem\jukeboxes.dm" +#include "code\controllers\subsystem\lag_switch.dm" #include "code\controllers\subsystem\language.dm" #include "code\controllers\subsystem\lighting.dm" #include "code\controllers\subsystem\machines.dm" @@ -812,7 +815,6 @@ #include "code\game\gamemodes\extended\extended.dm" #include "code\game\gamemodes\meteor\meteor.dm" #include "code\game\gamemodes\meteor\meteors.dm" -#include "code\game\gamemodes\monkey\monkey.dm" #include "code\game\gamemodes\nuclear\nuclear.dm" #include "code\game\gamemodes\sandbox\airlock_maker.dm" #include "code\game\gamemodes\sandbox\h_sandbox.dm" @@ -884,7 +886,6 @@ #include "code\game\machinery\teleporter.dm" #include "code\game\machinery\transformer.dm" #include "code\game\machinery\washing_machine.dm" -#include "code\game\machinery\wishgranter.dm" #include "code\game\machinery\camera\camera.dm" #include "code\game\machinery\camera\camera_assembly.dm" #include "code\game\machinery\camera\motion.dm" @@ -1659,7 +1660,6 @@ #include "code\modules\antagonists\gang\outfits.dm" #include "code\modules\antagonists\greentext\greentext.dm" #include "code\modules\antagonists\magic_servant\servant.dm" -#include "code\modules\antagonists\monkey\monkey.dm" #include "code\modules\antagonists\morph\morph.dm" #include "code\modules\antagonists\morph\morph_antag.dm" #include "code\modules\antagonists\nightmare\nightmare.dm" @@ -1694,7 +1694,6 @@ #include "code\modules\antagonists\traitor\IAA\internal_affairs.dm" #include "code\modules\antagonists\valentines\heartbreaker.dm" #include "code\modules\antagonists\valentines\valentine.dm" -#include "code\modules\antagonists\wishgranter\wishgranter.dm" #include "code\modules\antagonists\wizard\wizard.dm" #include "code\modules\antagonists\wizard\equipment\artefact.dm" #include "code\modules\antagonists\wizard\equipment\soulstone.dm" @@ -1816,9 +1815,13 @@ #include "code\modules\buildmode\submodes\basic.dm" #include "code\modules\buildmode\submodes\boom.dm" #include "code\modules\buildmode\submodes\copy.dm" +#include "code\modules\buildmode\submodes\delete.dm" #include "code\modules\buildmode\submodes\fill.dm" #include "code\modules\buildmode\submodes\map_export.dm" +#include "code\modules\buildmode\submodes\outfit.dm" +#include "code\modules\buildmode\submodes\proccall.dm" #include "code\modules\buildmode\submodes\throwing.dm" +#include "code\modules\buildmode\submodes\tweakcomps.dm" #include "code\modules\buildmode\submodes\variable_edit.dm" #include "code\modules\cargo\bounty.dm" #include "code\modules\cargo\bounty_console.dm" @@ -1962,6 +1965,7 @@ #include "code\modules\clothing\suits\reactive_armour.dm" #include "code\modules\clothing\suits\toggles.dm" #include "code\modules\clothing\suits\utility.dm" +#include "code\modules\clothing\suits\wintercoats.dm" #include "code\modules\clothing\suits\wiz_robe.dm" #include "code\modules\clothing\under\_under.dm" #include "code\modules\clothing\under\accessories.dm" diff --git a/tgui/packages/tgui/interfaces/CentcomPodLauncher.js b/tgui/packages/tgui/interfaces/CentcomPodLauncher.js index 25e2feb743c9..812b987e1766 100644 --- a/tgui/packages/tgui/interfaces/CentcomPodLauncher.js +++ b/tgui/packages/tgui/interfaces/CentcomPodLauncher.js @@ -1,520 +1,1221 @@ +import { toFixed } from 'common/math'; +import { classes } from 'common/react'; +import { storage } from 'common/storage'; import { multiline } from 'common/string'; -import { Fragment } from 'inferno'; -import { useBackend } from '../backend'; -import { Button, LabeledList, NoticeBox, Section } from '../components'; +import { createUuid } from 'common/uuid'; +import { Component, Fragment } from 'inferno'; +import { useBackend, useLocalState } from '../backend'; +import { + Box, + Button, + ByondUi, + Divider, + Input, + Knob, + LabeledControls, + NumberInput, + Section, + Stack, +} from '../components'; import { Window } from '../layouts'; -export const CentcomPodLauncher = () => { +const pod_grey = { + color: 'grey', +}; + +const useCompact = (context) => { + const [compact, setCompact] = useLocalState(context, 'compact', false); + const toggleCompact = () => setCompact(!compact); + return [compact, toggleCompact]; +}; + +export const CentcomPodLauncher = (props, context) => { + const [compact] = useCompact(context); return ( - - - - + + ); }; -// This is more or less a direct port from old tgui, with some slight -// text cleanup. But yes, it actually worked like this. -export const CentcomPodLauncherContent = (props, context) => { +const CentcomPodLauncherContent = (props, context) => { + const [compact] = useCompact(context); + return ( + + + + + + + + + + + + + + + + +
+ +
+
+
+
+ {!compact && ( + + + + )} + + + + + + + + + {!compact && ( + + + + )} + + + + + +
+
+
+
+ ); +}; + +const TABPAGES = [ + { + title: 'View Pod', + component: () => TabPod, + }, + { + title: 'View Bay', + component: () => TabBay, + }, + { + title: 'View Dropoff Location', + component: () => TabDrop, + }, +]; + +const REVERSE_OPTIONS = [ + { + title: 'Mobs', + icon: 'user', + }, + { + title: 'Unanchored\nObjects', + key: 'Unanchored', + icon: 'cube', + }, + { + title: 'Anchored\nObjects', + key: 'Anchored', + icon: 'anchor', + }, + { + title: 'Under-Floor', + key: 'Underfloor', + icon: 'eye-slash', + }, + { + title: 'Wall-Mounted', + key: 'Wallmounted', + icon: 'link', + }, + { + title: 'Floors', + icon: 'border-all', + }, + { + title: 'Walls', + icon: 'square', + }, + { + title: 'Mechs', + key: 'Mecha', + icon: 'truck', + }, +]; + +const DELAYS = [ + { + title: 'Pre', + tooltip: 'Time until pod gets to station', + }, + { + title: 'Fall', + tooltip: 'Duration of pods\nfalling animation', + }, + { + title: 'Open', + tooltip: 'Time it takes pod to open after landing', + }, + { + title: 'Exit', + tooltip: 'Time for pod to\nleave after opening', + }, +]; + +const REV_DELAYS = [ + { + title: 'Pre', + tooltip: 'Time until pod appears above dropoff point', + }, + { + title: 'Fall', + tooltip: 'Duration of pods\nfalling animation', + }, + { + title: 'Open', + tooltip: 'Time it takes pod to open after landing', + }, + { + title: 'Exit', + tooltip: 'Time for pod to\nleave after opening', + }, +]; + +const SOUNDS = [ + { + title: 'Fall', + act: 'fallingSound', + tooltip: 'Plays while pod falls, timed\nto end when pod lands', + }, + { + title: 'Land', + act: 'landingSound', + tooltip: 'Plays after pod lands', + }, + { + title: 'Open', + act: 'openingSound', + tooltip: 'Plays when pod opens', + }, + { + title: 'Exit', + act: 'leavingSound', + tooltip: 'Plays when pod leaves', + }, +]; + +const STYLES = [ + { title: 'Standard' }, + { title: 'Advanced' }, + { title: 'Nanotrasen' }, + { title: 'Syndicate' }, + { title: 'Deathsquad' }, + { title: 'Cultist' }, + { title: 'Missile' }, + { title: 'Syndie Missile' }, + { title: 'Supply Box' }, + { title: 'Clown Pod' }, + { title: 'Fruit' }, + { title: 'Invisible' }, + { title: 'Gondola' }, + { title: 'Seethrough' }, +]; + +const BAYS = [ + { title: '1' }, + { title: '2' }, + { title: '3' }, + { title: '4' }, + { title: 'ERT' }, +]; + +const EFFECTS_LOAD = [ + { + title: 'Launch All Turfs', + icon: 'globe', + choiceNumber: 0, + selected: 'launchChoice', + act: 'launchAll', + }, + { + title: 'Launch Turf Ordered', + icon: 'sort-amount-down-alt', + choiceNumber: 1, + selected: 'launchChoice', + act: 'launchOrdered', + }, + { + title: 'Pick Random Turf', + icon: 'dice', + choiceNumber: 2, + selected: 'launchChoice', + act: 'launchRandomTurf', + }, + { + divider: 1, + }, + { + title: 'Launch Whole Turf', + icon: 'expand', + choiceNumber: 0, + selected: 'launchRandomItem', + act: 'launchWholeTurf', + }, + { + title: 'Pick Random Item', + icon: 'dice', + choiceNumber: 1, + selected: 'launchRandomItem', + act: 'launchRandomItem', + }, + { + divider: 1, + }, + { + title: 'Clone', + icon: 'clone', + soloSelected: 'launchClone', + act: 'launchClone', + }, +]; + +const EFFECTS_NORMAL = [ + { + title: 'Specific Target', + icon: 'user-check', + soloSelected: 'effectTarget', + act: 'effectTarget', + }, + { + title: 'Pod Stays', + icon: 'hand-paper', + choiceNumber: 0, + selected: 'effectBluespace', + act: 'effectBluespace', + }, + { + title: 'Stealth', + icon: 'user-ninja', + soloSelected: 'effectStealth', + act: 'effectStealth', + }, + { + title: 'Quiet', + icon: 'volume-mute', + soloSelected: 'effectQuiet', + act: 'effectQuiet', + }, + { + title: 'Missile Mode', + icon: 'rocket', + soloSelected: 'effectMissile', + act: 'effectMissile', + }, + { + title: 'Burst Launch', + icon: 'certificate', + soloSelected: 'effectBurst', + act: 'effectBurst', + }, + { + title: 'Any Descent Angle', + icon: 'ruler-combined', + soloSelected: 'effectCircle', + act: 'effectCircle', + }, + { + title: 'No Ghost Alert\n(If you dont want to\nentertain bored ghosts)', + icon: 'ghost', + choiceNumber: 0, + selected: 'effectAnnounce', + act: 'effectAnnounce', + }, +]; + +const EFFECTS_HARM = [ + { + title: 'Explosion Custom', + icon: 'bomb', + choiceNumber: 1, + selected: 'explosionChoice', + act: 'explosionCustom', + }, + { + title: 'Adminbus Explosion\nWhat are they gonna do, ban you?', + icon: 'bomb', + choiceNumber: 2, + selected: 'explosionChoice', + act: 'explosionBus', + }, + { + divider: 1, + }, + { + title: 'Custom Damage', + icon: 'skull', + choiceNumber: 1, + selected: 'damageChoice', + act: 'damageCustom', + }, + { + title: 'Gib', + icon: 'skull-crossbones', + choiceNumber: 2, + selected: 'damageChoice', + act: 'damageGib', + }, + { + divider: 1, + }, + { + title: 'Projectile Cloud', + details: true, + icon: 'cloud-meatball', + soloSelected: 'effectShrapnel', + act: 'effectShrapnel', + }, + { + title: 'Stun', + icon: 'sun', + soloSelected: 'effectStun', + act: 'effectStun', + }, + { + title: 'Delimb', + icon: 'socks', + soloSelected: 'effectLimb', + act: 'effectLimb', + }, + { + title: 'Yeet Organs', + icon: 'book-dead', + soloSelected: 'effectOrgans', + act: 'effectOrgans', + }, +]; + +const EFFECTS_ALL = [ + { + list: EFFECTS_LOAD, + label: 'Load From', + alt_label: 'Load', + tooltipPosition: 'right', + }, + { + list: EFFECTS_NORMAL, + label: 'Normal Effects', + tooltipPosition: 'bottom', + }, + { + list: EFFECTS_HARM, + label: 'Harmful Effects', + tooltipPosition: 'bottom', + }, +]; + +const ViewTabHolder = (props, context) => { const { act, data } = useBackend(context); + const [tabPageIndex, setTabPageIndex] = useLocalState( + context, + 'tabPageIndex', + 1 + ); + const { mapRef } = data; + const TabPageComponent = TABPAGES[tabPageIndex].component(); return ( - - - To use this, simply spawn the atoms you want in one of the five Centcom - Supplypod Bays. Items in the bay will then be launched inside your - supplypod, one turf-full at a time! You can optionally use the following - buttons to configure how the supplypod acts. - -
- - -
+ ); +}; + +const TabPod = (props, context) => { + return ( + + Note: You can right click on this +
+ blueprint pod and edit vars directly +
+ ); +}; + +const TabBay = (props, context) => { + const { act, data } = useBackend(context); + return ( + <> + + ))} + + ); +}; + +const Bays = (props, context) => { + const { act, data } = useBackend(context); + const [compact] = useCompact(context); + return ( +
+
+ ); +}; + +const Timing = (props, context) => { + const { act, data } = useBackend(context); + return ( +
+
+ ); +}; + +const DelayHelper = (props, context) => { + const { act, data } = useBackend(context); + const { delay_list, reverse = false } = props; + return ( + + {delay_list.map((delay, i) => ( + + toFixed(value, 2)} + maxValue={10} + color={ + (reverse ? data.rev_delays[i + 1] : data.delays[i + 1]) / 10 > 10 + ? 'orange' + : 'default' } + onDrag={(e, value) => { + act('editTiming', { + timer: '' + (i + 1), + value: Math.max(value, 0), + reverse: reverse, + }); + }} /> - - -
+ + ))} + + ); +}; + +const Sounds = (props, context) => { + const { act, data } = useBackend(context); + return ( +
act('soundVolume')} + /> + } + > + {SOUNDS.map((sound, i) => ( +
); }; diff --git a/tgui/packages/tgui/interfaces/Radio.js b/tgui/packages/tgui/interfaces/Radio.js index 6d3df1ff64fd..1783933bf468 100644 --- a/tgui/packages/tgui/interfaces/Radio.js +++ b/tgui/packages/tgui/interfaces/Radio.js @@ -1,7 +1,15 @@ import { map } from 'common/collections'; import { toFixed } from 'common/math'; import { useBackend } from '../backend'; -import { Box, Button, LabeledList, NumberInput, Section } from '../components'; +import { + Box, + Button, + LabeledList, + NumberInput, + Section, + Divider, + Table, +} from '../components'; import { RADIO_CHANNELS } from '../constants'; import { Window } from '../layouts'; @@ -18,6 +26,8 @@ export const Radio = (props, context) => { useCommand, subspace, subspaceSwitchable, + chatlog, + chatloglist = [], } = data; const tunedChannel = RADIO_CHANNELS.find( (channel) => channel.freq === frequency @@ -28,15 +38,19 @@ export const Radio = (props, context) => { }))(data.channels); // Calculate window height let height = 106; + let width = 360; if (subspace) { if (channels.length > 0) { height += channels.length * 21 + 6; } else { height += 24; } + } else if (chatlog) { + height += 400; + width += 110; } return ( - +
@@ -127,6 +141,31 @@ export const Radio = (props, context) => { )}
+ {!!chatlog && ( +
+ + + Timestamp + Transcript + + + {chatloglist.map((log) => ( + + {log.time} +
+ {log.name} +
+ {log.message}
+ + ))} + +
+ )}
);