diff --git a/code/__DEFINES/shuttles.dm b/code/__DEFINES/shuttles.dm index 5380268e979c..c67d9e1a8327 100644 --- a/code/__DEFINES/shuttles.dm +++ b/code/__DEFINES/shuttles.dm @@ -94,3 +94,13 @@ #define SHUTTLE_UNLOCK_BUBBLEGUM "bubblegum" #define SHUTTLE_UNLOCK_MEDISIM "holodeck" #define SHUTTLE_UNLOCK_NARNAR "narnar" + +//Shuttle Events + +///Self destruct if this is returned in process +#define SHUTTLE_EVENT_CLEAR 2 + +///spawned stuff should float by the window and not hit the shuttle +#define SHUTTLE_EVENT_MISS_SHUTTLE 1 << 0 +///spawned stuff should hit the shuttle +#define SHUTTLE_EVENT_HIT_SHUTTLE 1 << 1 diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index 5d7ebb88be40..7ee56e1c372d 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -644,6 +644,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_LAVA_STOPPED "lava_stopped" ///Chasms will be safe to cross while they've this trait. #define TRAIT_CHASM_STOPPED "chasm_stopped" +/// The effects of hyperspace drift are blocked when the tile has this trait +#define TRAIT_HYPERSPACE_STOPPED "hyperspace_stopped" ///Turf slowdown will be ignored when this trait is added to a turf. #define TRAIT_TURF_IGNORE_SLOWDOWN "turf_ignore_slowdown" ///Mobs won't slip on a wet turf while it has this trait diff --git a/code/controllers/subsystem/shuttle.dm b/code/controllers/subsystem/shuttle.dm index 3a8ccad63adb..5f7dc3f52b89 100644 --- a/code/controllers/subsystem/shuttle.dm +++ b/code/controllers/subsystem/shuttle.dm @@ -198,8 +198,8 @@ SUBSYSTEM_DEF(shuttle) if(!thing) mobile_docking_ports.Remove(thing) continue - var/obj/docking_port/mobile/P = thing - P.check() + var/obj/docking_port/mobile/port = thing + port.check() for(var/thing in transit_docking_ports) var/obj/docking_port/stationary/transit/T = thing if(!T.owner) diff --git a/code/datums/components/shuttle_cling.dm b/code/datums/components/shuttle_cling.dm index 57c79e15193a..f686d7103bc8 100644 --- a/code/datums/components/shuttle_cling.dm +++ b/code/datums/components/shuttle_cling.dm @@ -38,15 +38,25 @@ ADD_TRAIT(parent, TRAIT_HYPERSPACED, src) RegisterSignals(parent, list(COMSIG_MOVABLE_MOVED, COMSIG_MOVABLE_UNBUCKLE, COMSIG_ATOM_NO_LONGER_PULLED), PROC_REF(update_state)) + RegisterSignal(parent, SIGNAL_REMOVETRAIT(TRAIT_FREE_HYPERSPACE_MOVEMENT), PROC_REF(initialize_loop)) + RegisterSignal(parent, SIGNAL_ADDTRAIT(TRAIT_FREE_HYPERSPACE_MOVEMENT), PROC_REF(clear_loop)) //Items have this cool thing where they're first put on the floor if you grab them from storage, and then into your hand, which isn't caught by movement signals that well if(isitem(parent)) RegisterSignal(parent, COMSIG_ITEM_PICKUP, PROC_REF(do_remove)) - hyperloop = SSmove_manager.move(moving = parent, direction = direction, delay = not_clinging_move_delay, subsystem = SShyperspace_drift, priority = MOVEMENT_ABOVE_SPACE_PRIORITY, flags = MOVEMENT_LOOP_NO_DIR_UPDATE|MOVEMENT_LOOP_DRAGGING) + if(!HAS_TRAIT(parent, TRAIT_FREE_HYPERSPACE_MOVEMENT)) + initialize_loop() update_state(parent) //otherwise we'll get moved 1 tile before we can correct ourselves, which isnt super bad but just looks jank +/datum/component/shuttle_cling/proc/initialize_loop() + hyperloop = SSmove_manager.move(moving = parent, direction = direction, delay = not_clinging_move_delay, subsystem = SShyperspace_drift, priority = MOVEMENT_ABOVE_SPACE_PRIORITY, flags = MOVEMENT_LOOP_NO_DIR_UPDATE|MOVEMENT_LOOP_OUTSIDE_CONTROL) + update_state() + +/datum/component/shuttle_cling/proc/clear_loop() + QDEL_NULL(hyperloop) + ///Check if we're in hyperspace and our state in hyperspace /datum/component/shuttle_cling/proc/update_state() SIGNAL_HANDLER @@ -55,6 +65,9 @@ qdel(src) return + if(!hyperloop) + return + var/should_loop = FALSE switch(is_holding_on(parent)) @@ -72,6 +85,10 @@ if(ALL_GOOD) should_loop = FALSE + // the hyperloop can get reset to null from the above procs + if(!hyperloop) + return + //Do pause/unpause/nothing for the hyperloop if(should_loop && hyperloop.paused) hyperloop.resume_loop() @@ -80,12 +97,16 @@ ///Check if we're "holding on" to the shuttle /datum/component/shuttle_cling/proc/is_holding_on(atom/movable/movee) - if(movee.pulledby || !isturf(movee.loc)) + if(movee.pulledby || !isturf(movee.loc) || HAS_TRAIT(movee, TRAIT_FREE_HYPERSPACE_MOVEMENT)) return ALL_GOOD if(!isliving(movee)) + if(HAS_TRAIT(movee, TRAIT_FORCED_GRAVITY)) // nothing can block the singularity + return SUPER_NOT_HOLDING_ON + if(is_tile_solid(get_step(movee, direction))) //something is blocking us so do the cool drift return CLINGING + return SUPER_NOT_HOLDING_ON var/mob/living/living = movee @@ -108,7 +129,7 @@ ///Are we on a hyperspace tile? There's some special bullshit with lattices so we just wrap this check /datum/component/shuttle_cling/proc/is_on_hyperspace(atom/movable/clinger) - if(istype(clinger.loc, hyperspace_type) && !(locate(/obj/structure/lattice) in clinger.loc)) + if(istype(clinger.loc, hyperspace_type) && !HAS_TRAIT(clinger.loc, TRAIT_HYPERSPACE_STOPPED)) return TRUE return FALSE diff --git a/code/datums/greyscale/config_types/greyscale_configs/greyscale_mobs.dm b/code/datums/greyscale/config_types/greyscale_configs/greyscale_mobs.dm index f8a0f4a062c6..bbcb44f91754 100644 --- a/code/datums/greyscale/config_types/greyscale_configs/greyscale_mobs.dm +++ b/code/datums/greyscale/config_types/greyscale_configs/greyscale_mobs.dm @@ -7,6 +7,11 @@ icon_file = 'icons/mob/simple/carp.dmi' json_config = 'code/datums/greyscale/json_configs/carp.json' +/datum/greyscale_config/carp_friend + name = "Friend Carp" + icon_file = 'icons/mob/simple/carp.dmi' + json_config = 'code/datums/greyscale/json_configs/carp_friend.json' + /datum/greyscale_config/carp_magic name = "Magicarp" icon_file = 'icons/mob/simple/carp.dmi' diff --git a/code/datums/greyscale/json_configs/carp_friend.json b/code/datums/greyscale/json_configs/carp_friend.json new file mode 100644 index 000000000000..a5b3ffdf7a47 --- /dev/null +++ b/code/datums/greyscale/json_configs/carp_friend.json @@ -0,0 +1,23 @@ +{ + "base_friend": [ + { + "type": "icon_state", + "icon_state": "base_friend", + "blend_mode": "overlay", + "color_ids": [ 1 ] + }, + { + "type": "icon_state", + "icon_state": "base_friend_mouth", + "blend_mode": "overlay" + } + ], + "base_friend_dead": [ + { + "type": "icon_state", + "icon_state": "base_friend_dead", + "blend_mode": "overlay", + "color_ids": [ 1 ] + } + ] +} diff --git a/code/game/objects/items/devices/transfer_valve.dm b/code/game/objects/items/devices/transfer_valve.dm index 7dee7378e6f1..feeedc5a823b 100644 --- a/code/game/objects/items/devices/transfer_valve.dm +++ b/code/game/objects/items/devices/transfer_valve.dm @@ -292,3 +292,11 @@ */ /obj/item/transfer_valve/proc/ready() return tank_one && tank_two + +/obj/item/transfer_valve/fake/Initialize(mapload) + . = ..() + + tank_one = new /obj/item/tank/internals/plasma (src) + tank_two = new /obj/item/tank/internals/oxygen (src) + + update_appearance() diff --git a/code/game/objects/structures/lattice.dm b/code/game/objects/structures/lattice.dm index 50f31d76346f..ff71f06497e9 100644 --- a/code/game/objects/structures/lattice.dm +++ b/code/game/objects/structures/lattice.dm @@ -16,7 +16,7 @@ canSmoothWith = SMOOTH_GROUP_LATTICE + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_OPEN_FLOOR var/number_of_mats = 1 var/build_material = /obj/item/stack/rods - var/list/give_turf_traits = list(TRAIT_CHASM_STOPPED) + var/list/give_turf_traits = list(TRAIT_CHASM_STOPPED, TRAIT_HYPERSPACE_STOPPED) /obj/structure/lattice/Initialize(mapload) . = ..() @@ -24,7 +24,6 @@ give_turf_traits = string_list(give_turf_traits) AddElement(/datum/element/give_turf_traits, give_turf_traits) - /datum/armor/structure_lattice melee = 50 fire = 80 @@ -100,7 +99,7 @@ smoothing_groups = SMOOTH_GROUP_CATWALK + SMOOTH_GROUP_LATTICE + SMOOTH_GROUP_OPEN_FLOOR canSmoothWith = SMOOTH_GROUP_CATWALK obj_flags = CAN_BE_HIT | BLOCK_Z_OUT_DOWN | BLOCK_Z_IN_UP - give_turf_traits = list(TRAIT_TURF_IGNORE_SLOWDOWN, TRAIT_LAVA_STOPPED, TRAIT_CHASM_STOPPED) + give_turf_traits = list(TRAIT_TURF_IGNORE_SLOWDOWN, TRAIT_LAVA_STOPPED, TRAIT_CHASM_STOPPED, TRAIT_HYPERSPACE_STOPPED) /obj/structure/lattice/catwalk/Initialize(mapload) . = ..() @@ -155,7 +154,7 @@ canSmoothWith = SMOOTH_GROUP_LATTICE obj_flags = CAN_BE_HIT | BLOCK_Z_OUT_DOWN | BLOCK_Z_IN_UP resistance_flags = FIRE_PROOF | LAVA_PROOF - give_turf_traits = list(TRAIT_LAVA_STOPPED, TRAIT_CHASM_STOPPED) + give_turf_traits = list(TRAIT_LAVA_STOPPED, TRAIT_CHASM_STOPPED, TRAIT_HYPERSPACE_STOPPED) /obj/structure/lattice/lava/deconstruction_hints(mob/user) return span_notice("The rods look like they could be cut, but the heat treatment will shatter off. There's space for a tile.") diff --git a/code/game/turfs/open/space/transit.dm b/code/game/turfs/open/space/transit.dm index 6c58d4872947..8876080830bf 100644 --- a/code/game/turfs/open/space/transit.dm +++ b/code/game/turfs/open/space/transit.dm @@ -11,10 +11,13 @@ . = ..() update_appearance() RegisterSignal(src, COMSIG_TURF_RESERVATION_RELEASED, PROC_REF(launch_contents)) + RegisterSignal(src, COMSIG_ATOM_ENTERED, PROC_REF(initialize_drifting)) + RegisterSignal(src, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON, PROC_REF(initialize_drifting_but_from_initialize)) /turf/open/space/transit/Destroy() //Signals are NOT removed from turfs upon replacement, and we get replaced ALOT, so unregister our signal - UnregisterSignal(src, COMSIG_TURF_RESERVATION_RELEASED) + UnregisterSignal(src, list(COMSIG_TURF_RESERVATION_RELEASED, COMSIG_ATOM_ENTERED, COMSIG_ATOM_AFTER_SUCCESSFUL_INITIALIZED_ON)) + return ..() /turf/open/space/transit/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir) @@ -30,11 +33,17 @@ icon_state = "speedspace_ns_[get_transit_state(src)]" return ..() -/turf/open/space/transit/Entered(atom/movable/arrived, atom/old_loc, list/atom/old_locs) - . = ..() +/turf/open/space/transit/proc/initialize_drifting(atom/entered, atom/movable/enterer) + SIGNAL_HANDLER - if(!HAS_TRAIT(arrived, TRAIT_HYPERSPACED) && !HAS_TRAIT(arrived, TRAIT_FREE_HYPERSPACE_MOVEMENT)) - arrived.AddComponent(/datum/component/shuttle_cling, turn(dir, 180), old_loc) + if(enterer && !HAS_TRAIT(enterer, TRAIT_HYPERSPACED) && !HAS_TRAIT(src, TRAIT_HYPERSPACE_STOPPED)) + enterer.AddComponent(/datum/component/shuttle_cling, turn(dir, 180)) + +/turf/open/space/transit/proc/initialize_drifting_but_from_initialize(atom/movable/location, atom/movable/enterer, mapload) + SIGNAL_HANDLER + + if(!mapload && !enterer.anchored) + INVOKE_ASYNC(src, PROC_REF(initialize_drifting), src, enterer) /turf/open/space/transit/Exited(atom/movable/gone, direction) . = ..() @@ -52,6 +61,10 @@ ///Dump a movable in a random valid spacetile /proc/dump_in_space(atom/movable/dumpee) + if(HAS_TRAIT(dumpee, TRAIT_DEL_ON_SPACE_DUMP)) + qdel(dumpee) + return + var/max = world.maxx-TRANSITIONEDGE var/min = 1+TRANSITIONEDGE diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index a6c087f2b074..5f61e2bc7035 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -48,6 +48,7 @@ GLOBAL_PROTECT(admin_verbs_admin) /datum/admins/proc/open_artifactpanel, /datum/verbs/menu/Admin/verb/playerpanel, /* It isn't /datum/admin but it fits no less */ /datum/admins/proc/kick_player_by_ckey, //MONKESTATION ADDITION - kick a player by their ckey + /datum/admins/proc/change_shuttle_events, //allows us to change the shuttle events // Client procs /client/proc/admin_call_shuttle, /*allows us to call the emergency shuttle*/ /client/proc/admin_cancel_shuttle, /*allows us to cancel the emergency shuttle, sending it back to centcom*/ diff --git a/code/modules/admin/verbs/change_shuttle_events.dm b/code/modules/admin/verbs/change_shuttle_events.dm new file mode 100644 index 000000000000..a24aa456f237 --- /dev/null +++ b/code/modules/admin/verbs/change_shuttle_events.dm @@ -0,0 +1,42 @@ +///Manipulate the events that are gonna run/are running on the escape shuttle +/datum/admins/proc/change_shuttle_events() + set category = "Admin.Events" + set name = "Change Shuttle Events" + set desc = "Allows you to change the events on a shuttle." + + if (!istype(src, /datum/admins)) + src = usr.client.holder + if (!istype(src, /datum/admins)) + to_chat(usr, "Error: you are not an admin!", confidential = TRUE) + return + + //At least for now, just letting admins modify the emergency shuttle is fine + var/obj/docking_port/mobile/port = SSshuttle.emergency + + if(!port) + to_chat(usr, span_admin("Uh oh, couldn't find the escape shuttle!")) + + var/list/options = list("Clear"="Clear") + + //Grab the active events so we know which ones we can Add or Remove + var/list/active = list() + for(var/datum/shuttle_event/event in port.event_list) + active[event.type] = event + + for(var/datum/shuttle_event/event as anything in subtypesof(/datum/shuttle_event)) + options[((event in active) ? "(Remove)" : "(Add)") + initial(event.name)] = event + + //Throw up an ugly menu with the shuttle events and the options to add or remove them, or clear them all + var/result = input(usr, "Choose an event to add/remove", "Shuttle Events") as null|anything in sort_list(options) + + if(result == "Clear") + port.event_list.Cut() + log_admin("[key_name_admin(usr)] has cleared the shuttle events on: [port]") + else if(options[result]) + var/typepath = options[result] + if(typepath in active) + port.event_list.Remove(active[options[result]]) + log_admin("[key_name_admin(usr)] has removed '[active[result]]' from [port].") + else + port.event_list.Add(new typepath (port)) + log_admin("[key_name_admin(usr)] has added '[typepath]' to [port].") diff --git a/code/modules/awaymissions/cordon.dm b/code/modules/awaymissions/cordon.dm index 177fc85ba37d..285d0d49e105 100644 --- a/code/modules/awaymissions/cordon.dm +++ b/code/modules/awaymissions/cordon.dm @@ -46,6 +46,12 @@ /turf/cordon/Adjacent(atom/neighbor, atom/target, atom/movable/mover) return FALSE +/turf/cordon/Bumped(atom/movable/bumped_atom) + . = ..() + + if(HAS_TRAIT(bumped_atom, TRAIT_FREE_HYPERSPACE_SOFTCORDON_MOVEMENT)) //we could feasibly reach the border, so just dont + dump_in_space(bumped_atom) + /// Area used in conjuction with the cordon turf to create a fully functioning world border. /area/misc/cordon name = "CORDON" diff --git a/code/modules/mapping/space_management/space_reservation.dm b/code/modules/mapping/space_management/space_reservation.dm index 8c246699b4eb..b4e933d87d3c 100644 --- a/code/modules/mapping/space_management/space_reservation.dm +++ b/code/modules/mapping/space_management/space_reservation.dm @@ -115,7 +115,7 @@ /datum/turf_reservation/transit/make_repel(turf/pre_cordon_turf) ..() - RegisterSignal(pre_cordon_turf, COMSIG_ATOM_ENTERED, PROC_REF(space_dump)) + RegisterSignal(pre_cordon_turf, COMSIG_ATOM_ENTERED, PROC_REF(space_dump_soft)) /datum/turf_reservation/transit/stop_repel(turf/pre_cordon_turf) ..() @@ -127,6 +127,11 @@ dump_in_space(enterer) +/datum/turf_reservation/transit/proc/space_dump_soft(atom/source, atom/movable/enterer) + SIGNAL_HANDLER + + if(!HAS_TRAIT(enterer, TRAIT_FREE_HYPERSPACE_SOFTCORDON_MOVEMENT)) + space_dump(source, enterer) /// Internal proc which handles reserving the area for the reservation. /datum/turf_reservation/proc/_reserve_area(width, height, zlevel) diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp.dm b/code/modules/mob/living/basic/space_fauna/carp/carp.dm index b42000f27701..24fb0b582642 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/carp.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/carp.dm @@ -100,9 +100,10 @@ ) /mob/living/basic/carp/Initialize(mapload, mob/tamer) + ADD_TRAIT(src, TRAIT_FREE_HYPERSPACE_MOVEMENT, INNATE_TRAIT) //Need to set before init cause if we init in hyperspace we get dragged before the trait can be added . = ..() apply_colour() - add_traits(list(TRAIT_HEALS_FROM_CARP_RIFTS, TRAIT_SPACEWALK, TRAIT_FREE_HYPERSPACE_MOVEMENT), INNATE_TRAIT) + add_traits(list(TRAIT_HEALS_FROM_CARP_RIFTS, TRAIT_SPACEWALK), INNATE_TRAIT) if (cell_line) AddElement(/datum/element/swabable, cell_line, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 5) @@ -165,6 +166,16 @@ ai_controller.set_blackboard_key(BB_CARP_MIGRATION_PATH, actual_points) +/mob/living/basic/carp/death(gibbed) + . = ..() + + REMOVE_TRAIT(src, TRAIT_FREE_HYPERSPACE_MOVEMENT, INNATE_TRAIT) + +/mob/living/basic/carp/revive(full_heal_flags, excess_healing, force_grab_ghost) + . = ..() + + ADD_TRAIT(src, TRAIT_FREE_HYPERSPACE_MOVEMENT, INNATE_TRAIT) + /** * Holographic carp from the holodeck */ @@ -259,3 +270,25 @@ new_overlays += disk_overlay #undef RARE_CAYENNE_CHANCE + +///Wild carp that just vibe ya know +/mob/living/basic/carp/passive + name = "passive carp" + desc = "A timid, sucker-bearing creature that resembles a fish. " + + icon_state = "base_friend" + icon_living = "base_friend" + icon_dead = "base_friend_dead" + greyscale_config = /datum/greyscale_config/carp_friend + + attack_verb_continuous = "suckers" + attack_verb_simple = "suck" + + melee_damage_lower = 4 + melee_damage_upper = 4 + ai_controller = /datum/ai_controller/basic_controller/carp/passive + +/mob/living/basic/carp/passive/Initialize(mapload) + . = ..() + AddElement(/datum/element/ai_retaliate) + AddElement(/datum/element/pet_bonus, "bloops happily!") diff --git a/code/modules/power/singularity/singularity.dm b/code/modules/power/singularity/singularity.dm index 603dc1ffe091..d61573491c51 100644 --- a/code/modules/power/singularity/singularity.dm +++ b/code/modules/power/singularity/singularity.dm @@ -48,6 +48,7 @@ /// What the game tells ghosts when you make one var/ghost_notification_message = "IT'S LOOSE" + pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE | PASSCLOSEDTURF | PASSMACHINE | PASSSTRUCTURE | PASSDOORS flags_1 = SUPERMATTER_IGNORES_1 resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF | FREEZE_PROOF | SHUTTLE_CRUSH_PROOF obj_flags = CAN_BE_HIT | DANGEROUS_POSSESSION @@ -470,3 +471,7 @@ /obj/singularity/deadchat_controlled/Initialize(mapload, starting_energy) . = ..() deadchat_plays(mode = DEMOCRACY_MODE) + +/// Special singularity that spawns for shuttle events only +/obj/singularity/shuttle_event + anchored = FALSE diff --git a/code/modules/projectiles/projectile/energy/_energy.dm b/code/modules/projectiles/projectile/energy/_energy.dm index 8527041e8600..6b715fdb74f5 100644 --- a/code/modules/projectiles/projectile/energy/_energy.dm +++ b/code/modules/projectiles/projectile/energy/_energy.dm @@ -6,3 +6,9 @@ armor_flag = ENERGY reflectable = REFLECT_NORMAL impact_effect_type = /obj/effect/temp_visual/impact_effect/energy + +/obj/projectile/energy/Initialize(mapload) + . = ..() + + ADD_TRAIT(src, TRAIT_FREE_HYPERSPACE_MOVEMENT, INNATE_TRAIT) + ADD_TRAIT(src, TRAIT_FREE_HYPERSPACE_MOVEMENT, TRAIT_FREE_HYPERSPACE_SOFTCORDON_MOVEMENT) diff --git a/code/modules/shuttle/emergency.dm b/code/modules/shuttle/emergency.dm index 3ef25b22e219..97e1a1b0791e 100644 --- a/code/modules/shuttle/emergency.dm +++ b/code/modules/shuttle/emergency.dm @@ -313,6 +313,11 @@ var/sound_played = 0 //If the launch sound has been sent to all players on the shuttle itself var/hijack_status = NOT_BEGUN +/obj/docking_port/mobile/emergency/Initialize(mapload) + . = ..() + + setup_shuttle_events() + /obj/docking_port/mobile/emergency/canDock(obj/docking_port/stationary/S) return SHUTTLE_CAN_DOCK //If the emergency shuttle can't move, the whole game breaks, so it will force itself to land even if it has to crush a few departments in the process @@ -480,6 +485,7 @@ color_override = "orange", ) ShuttleDBStuff() + addtimer(CALLBACK(src, PROC_REF(announce_shuttle_events)), 20 SECONDS) if(SHUTTLE_DOCKED) @@ -536,6 +542,10 @@ color_override = "orange", ) INVOKE_ASYNC(SSticker, TYPE_PROC_REF(/datum/controller/subsystem/ticker, poll_hearts)) + //Tell the events we're starting, so they can time their spawns or do some other stuff + for(var/datum/shuttle_event/event as anything in event_list) + event.start_up_event(SSshuttle.emergency_escape_time * engine_coeff) + SSmapping.mapvote() //If no map vote has been run yet, start one. if(SHUTTLE_STRANDED, SHUTTLE_DISABLED) @@ -563,6 +573,8 @@ if(istype(M, /obj/docking_port/mobile/pod)) M.parallax_slowdown() + process_events() + if(time_left <= 0) //move each escape pod to its corresponding escape dock for(var/obj/docking_port/mobile/port as anything in SSshuttle.mobile_docking_ports) @@ -596,6 +608,16 @@ color_override = "orange", ) +///Generate a list of events to run during the departure +/obj/docking_port/mobile/emergency/proc/setup_shuttle_events() + var/list/names = list() + for(var/datum/shuttle_event/event as anything in subtypesof(/datum/shuttle_event)) + if(prob(initial(event.event_probability))) + event_list.Add(new event(src)) + names += initial(event.name) + if(LAZYLEN(names)) + log_game("[capitalize(name)] has selected the following shuttle events: [english_list(names)].") + /obj/docking_port/mobile/monastery name = "monastery pod" shuttle_id = "mining_common" //set so mining can call it down diff --git a/code/modules/shuttle/shuttle.dm b/code/modules/shuttle/shuttle.dm index 769de2efb336..fdf008c26344 100644 --- a/code/modules/shuttle/shuttle.dm +++ b/code/modules/shuttle/shuttle.dm @@ -488,6 +488,8 @@ ///if this shuttle can move docking ports other than the one it is docked at var/can_move_docking_ports = FALSE var/list/hidden_turfs = list() + ///List of shuttle events that can run or are running + var/list/datum/shuttle_event/event_list = list() #define WORLDMAXX_CUTOFF (world.maxx + 1) #define WORLDMAXY_CUTOFF (world.maxx + 1) @@ -843,6 +845,7 @@ //used by shuttle subsystem to check timers /obj/docking_port/mobile/proc/check() check_effects() + //process_events() if you were to add events to non-escape shuttles, uncomment this if(mode == SHUTTLE_IGNITING) check_transit_zone() @@ -1148,13 +1151,17 @@ return FALSE return ..() - //Called when emergency shuttle leaves the station /obj/docking_port/mobile/proc/on_emergency_launch() if(launch_status == UNLAUNCHED) //Pods will not launch from the mine/planet, and other ships won't launch unless we tell them to. launch_status = ENDGAME_LAUNCHED enterTransit() +///Let people know shits about to go down +/obj/docking_port/mobile/proc/announce_shuttle_events() + for(var/datum/shuttle_event/event as anything in event_list) + notify_ghosts("The [name] has selected: [event.name]") + /obj/docking_port/mobile/emergency/on_emergency_launch() return @@ -1173,6 +1180,15 @@ /obj/docking_port/mobile/emergency/on_emergency_dock() return +///Process all the shuttle events for every shuttle tick we get +/obj/docking_port/mobile/proc/process_events() + var/list/removees + for(var/datum/shuttle_event/event as anything in event_list) + if(event.event_process() == SHUTTLE_EVENT_CLEAR) //if we return SHUTTLE_EVENT_CLEAR, we clean them up + LAZYADD(removees, event) + for(var/item in removees) + event_list.Remove(item) + #ifdef TESTING #undef DOCKING_PORT_HIGHLIGHT #endif diff --git a/code/modules/shuttle/shuttle_events/_shuttle_events.dm b/code/modules/shuttle/shuttle_events/_shuttle_events.dm new file mode 100644 index 000000000000..6736428c02aa --- /dev/null +++ b/code/modules/shuttle/shuttle_events/_shuttle_events.dm @@ -0,0 +1,140 @@ +///An event that can run during shuttle flight, and will run for the duration of it (configurable) +/datum/shuttle_event + ///How we're announced to ghosts and stuff + var/name = "The concept of a shuttle event" + ///probability of this event to run from 0 to 100 + var/event_probability = 0 + ///Track if we're allowed to run, gets turned to TRUE when the activation timer hits + VAR_PRIVATE/active = FALSE + ///fraction of the escape timer at which we activate, 0 means we start running immediately + ///(so if activation timer is 0.2 and shuttle takes 3 minutes to get going, it will activate in 36 seconds) + ///We only care about the timer from the moment of launch, any speed changed afterwards are not worth dealing with + var/activation_fraction = 0 + ///when do we activate? + VAR_PRIVATE/activate_at + ///Our reference to the docking port and thus the shuttle + var/obj/docking_port/mobile/port + +/datum/shuttle_event/New(obj/docking_port/mobile/port) + . = ..() + + src.port = port + +/datum/shuttle_event/proc/start_up_event(evacuation_duration) + activate_at = world.time + evacuation_duration * activation_fraction + +///We got activated +/datum/shuttle_event/proc/activate() + return + +///Process with the SShutle subsystem. Return SHUTTLE_EVENT_CLEAR to self-destruct +/datum/shuttle_event/proc/event_process() + . = TRUE + + if(!active) + if(world.time < activate_at) + return FALSE + active = TRUE + . = activate() + +///Spawns objects, mobs, whatever with all the necessary code to make it hit and/or miss the shuttle +/datum/shuttle_event/simple_spawner + ///behaviour of spawning objects, if we spawn + var/spawning_flags = SHUTTLE_EVENT_MISS_SHUTTLE | SHUTTLE_EVENT_HIT_SHUTTLE + ///List of valid spawning turfs, generated from generate_spawning_turfs(), that will HIT the shuttle + var/list/turf/spawning_turfs_hit + ///List of valid spawning turfs, generated from generate_spawning_turfs(), that will MISS the shuttle + var/list/turf/spawning_turfs_miss + ///Chance, from 0 to 100, for something to spawn + var/spawn_probability_per_process = 0 + ///Increment if you want more stuff to spawn at once + var/spawns_per_spawn = 1 + ///weighted list with spawnable movables + var/list/spawning_list = list() + ///If set to TRUE, every time an object is spawned their weight is decreased untill they are removed + var/remove_from_list_when_spawned = FALSE + ///If set to true, we'll delete ourselves if we cant spawn anything anymore. Useful in conjunction with remove_from_list_when_spawned + var/self_destruct_when_empty = FALSE + +/datum/shuttle_event/simple_spawner/start_up_event(evacuation_duration) + ..() + + generate_spawning_turfs(port.return_coords(), spawning_flags, port.preferred_direction) + +///Bounding coords are list(x0, y0, x1, y1) where x0 and y0 are top-left +/datum/shuttle_event/simple_spawner/proc/generate_spawning_turfs(list/bounding_coords, spawning_behaviour, direction) + spawning_turfs_hit = list() //turfs that will drift its contents to miss the shuttle + spawning_turfs_miss = list() //turfs that will drift its contents to hit the shuttle + var/list/step_dir //vector, either -1, 0 or 1. once we get a corner (lets say top right), in which direction do we 'walk' to get the full side? (this case to the right, so (1, 0) + var/list/target_corner //Top left or bottom right corner + var/list/spawn_offset //bounding_coords is ONLY the shuttle, not the space around it, so offset spawn_tiles or stuff spawns on the walls of the shuttle + + switch(direction) + if(NORTH) //we're travelling north (so people get pushed south) + step_dir = list(1, 0) + target_corner = list(bounding_coords[1], bounding_coords[2]) + spawn_offset = list(0, SHUTTLE_TRANSIT_BORDER) + if(SOUTH) + step_dir = list(-1, 0) + target_corner = list(bounding_coords[3], bounding_coords[4]) + spawn_offset = list(0, -SHUTTLE_TRANSIT_BORDER) + if(EAST) + step_dir = list(0, 1) + target_corner = list(bounding_coords[3], bounding_coords[4]) + spawn_offset = list(SHUTTLE_TRANSIT_BORDER, 0) + if(WEST) + step_dir = list(0, -1) + target_corner = list(bounding_coords[1], bounding_coords[2]) + spawn_offset = list(-SHUTTLE_TRANSIT_BORDER, 0) + + if(spawning_behaviour & SHUTTLE_EVENT_HIT_SHUTTLE) + ///so we get either the horizontal width or vertical width, which would both equal the amount of spawn tiles + var/tile_amount = abs((direction == NORTH || SOUTH) ? bounding_coords[1] - bounding_coords[3] : bounding_coords[2] - bounding_coords[4]) + for(var/i in 0 to tile_amount) + var/list/target_coords = list(target_corner[1] + step_dir[1] * i + spawn_offset[1], target_corner[2] + step_dir[2] * i + spawn_offset[2]) + spawning_turfs_hit.Add(locate(target_coords[1], target_coords[2], port.z)) + if(spawning_behaviour & SHUTTLE_EVENT_MISS_SHUTTLE) + for(var/i in 1 to SHUTTLE_TRANSIT_BORDER) + //Get the corner tile, and move away from the shuttle and towards the cordon + spawning_turfs_miss.Add(locate(target_corner[1] - step_dir[1] * i + spawn_offset[1], target_corner[2] - step_dir[2] * i + spawn_offset[2], port.z)) + var/corner_delta = list(bounding_coords[3] - bounding_coords[1], bounding_coords[2] - bounding_coords[4]) + //Get the corner tile, but jump over the shuttle and then continue unto the cordon + spawning_turfs_miss.Add(locate(target_corner[1] + corner_delta[1] * step_dir[1] + step_dir[1] * i + spawn_offset[1], target_corner[2] + corner_delta[2] * step_dir[2] + step_dir[2] * i + spawn_offset[2], port.z)) + + +/datum/shuttle_event/simple_spawner/event_process() + . = ..() + + if(!.) + return + + if(!LAZYLEN(spawning_list)) + if(self_destruct_when_empty) + return SHUTTLE_EVENT_CLEAR + return + + if(prob(spawn_probability_per_process)) + for(var/i in 1 to spawns_per_spawn) + spawn_movable(get_type_to_spawn()) + +///Pick a random turf from the valid turfs we got. Overwrite if you need some custom picking +/datum/shuttle_event/simple_spawner/proc/get_spawn_turf() + RETURN_TYPE(/turf) + return pick(spawning_turfs_hit + spawning_turfs_miss) + +///Spawn stuff! if you're not using this, don't use the simple_spawner subtype +/datum/shuttle_event/simple_spawner/proc/spawn_movable(spawn_type) + post_spawn(new spawn_type (get_spawn_turf())) + +///Not technically a getter if remove_from_list_when_spawned=TRUE. Otherwise, this returns the type we're going to spawn and throw at the shuttle +/datum/shuttle_event/simple_spawner/proc/get_type_to_spawn() + . = pick_weight(spawning_list) + if(remove_from_list_when_spawned) //if we have this enabled, we decrease the pickweight by 1 till it runs out + spawning_list[.] -= 1 + if(spawning_list[.] < 1) + spawning_list.Remove(.) + +///Do any post-spawn edits you need to do +/datum/shuttle_event/simple_spawner/proc/post_spawn(atom/movable/spawnee) + ADD_TRAIT(spawnee, TRAIT_FREE_HYPERSPACE_SOFTCORDON_MOVEMENT, REF(src)) //Lets us spawn and move further away from the shuttle without being teleported into space + ADD_TRAIT(spawnee, TRAIT_DEL_ON_SPACE_DUMP, REF(src)) //if we hit the cordon, we get deleted. If the shuttle can make you, it can qdel you diff --git a/code/modules/shuttle/shuttle_events/carp.dm b/code/modules/shuttle/shuttle_events/carp.dm new file mode 100644 index 000000000000..18529f1c0288 --- /dev/null +++ b/code/modules/shuttle/shuttle_events/carp.dm @@ -0,0 +1,62 @@ +///CARPTIDE! CARPTIDE! CARPTIDE! A swarm of carp will pass by and through the shuttle, including consequences of carp going through the shuttle +/datum/shuttle_event/simple_spawner/carp + name = "Carp Nest! (Very Dangerous!)" + event_probability = 0.4 + activation_fraction = 0.2 + + spawning_list = list(/mob/living/basic/carp = 12, /mob/living/basic/carp/mega = 3) + spawning_flags = SHUTTLE_EVENT_HIT_SHUTTLE | SHUTTLE_EVENT_MISS_SHUTTLE + spawn_probability_per_process = 20 + + remove_from_list_when_spawned = TRUE + self_destruct_when_empty = TRUE + +/datum/shuttle_event/simple_spawner/carp/post_spawn(mob/living/basic/carp/carpee) + . = ..() + //Give the carp the goal to migrate in a straight line so they dont just idle in hyperspace + carpee.migrate_to(list(WEAKREF(get_edge_target_turf(carpee.loc, angle2dir(dir2angle(port.preferred_direction) - 180))))) + +///CARPTIDE! CARPTIDE! CARPTIDE! Magical carp will attack the shuttle! +/datum/shuttle_event/simple_spawner/carp/magic + name = "Magical Carp Nest! (Very Dangerous!)" + event_probability = 0 + activation_fraction = 0.2 + + spawning_list = list(/mob/living/basic/carp/magic = 12, /mob/living/basic/carp/magic/chaos = 1) + spawning_flags = SHUTTLE_EVENT_HIT_SHUTTLE | SHUTTLE_EVENT_MISS_SHUTTLE + spawn_probability_per_process = 20 + + remove_from_list_when_spawned = TRUE + self_destruct_when_empty = TRUE + +///Spawn a bunch of friendly carp to view from inside the shuttle! May occassionally pass through and nibble some windows, but are otherwise pretty harmless +/datum/shuttle_event/simple_spawner/carp/friendly + name = "Passive Carp Nest! (Mostly Harmless!)" + event_probability = 3 + activation_fraction = 0.1 + + spawning_list = list(/mob/living/basic/carp/passive = 1) + spawning_flags = SHUTTLE_EVENT_HIT_SHUTTLE | SHUTTLE_EVENT_MISS_SHUTTLE + spawns_per_spawn = 2 + spawn_probability_per_process = 100 + + remove_from_list_when_spawned = FALSE + + ///Chance we hit the shuttle, instead of flying past it (most carp will go through anyway, and we dont want this to be too annoying to take away from the majesty) + var/hit_the_shuttle_chance = 1 + +/datum/shuttle_event/simple_spawner/carp/friendly/get_spawn_turf() + return prob(hit_the_shuttle_chance) ? pick(spawning_turfs_hit) : pick(spawning_turfs_miss) + +///Same as /friendly, but we only go through the shuttle, MUHAHAHAHAHAHA!! They dont actually harm anyone, but itll be a clusterfuck of confusion +/datum/shuttle_event/simple_spawner/carp/friendly_but_no_personal_space + name = "Comfortable Carp Nest going through the shuttle! (Extremely annoying and confusing!)" + event_probability = 0 + activation_fraction = 0.5 + + spawning_list = list(/mob/living/basic/carp/passive = 1) + spawning_flags = SHUTTLE_EVENT_HIT_SHUTTLE + spawns_per_spawn = 2 + spawn_probability_per_process = 100 + + remove_from_list_when_spawned = FALSE diff --git a/code/modules/shuttle/shuttle_events/meteors.dm b/code/modules/shuttle/shuttle_events/meteors.dm new file mode 100644 index 000000000000..d1ded58cbada --- /dev/null +++ b/code/modules/shuttle/shuttle_events/meteors.dm @@ -0,0 +1,42 @@ +/datum/shuttle_event/simple_spawner/meteor + spawning_list = list(/obj/effect/meteor) + +/datum/shuttle_event/simple_spawner/meteor/post_spawn(atom/movable/spawnee) + ADD_TRAIT(spawnee, TRAIT_FREE_HYPERSPACE_MOVEMENT, src) + ..() + +/datum/shuttle_event/simple_spawner/meteor/spawn_movable(spawn_type) + var/turf/spawn_turf = get_spawn_turf() + //invert the dir cause we shoot in the opposite direction we're flying + if(ispath(spawn_type, /obj/effect/meteor)) + post_spawn(new spawn_type (spawn_turf, get_edge_target_turf(spawn_turf, angle2dir(dir2angle(port.preferred_direction) - 180)))) + else //if you want to spawn some random garbage inbetween, go wild + post_spawn(new spawn_type (get_spawn_turf())) + +///Very weak meteors, but may very rarely actually hit the shuttle! +/datum/shuttle_event/simple_spawner/meteor/dust + name = "Dust Meteors! (Mostly Safe)" + event_probability = 2 + activation_fraction = 0.1 + + spawn_probability_per_process = 100 + spawns_per_spawn = 5 + spawning_list = list(/obj/effect/meteor/dust = 1, /obj/effect/meteor/sand = 1) + spawning_flags = SHUTTLE_EVENT_MISS_SHUTTLE | SHUTTLE_EVENT_HIT_SHUTTLE + ///We can, occassionally, hit the shuttle, but we dont do a lot of damage and should only do so pretty rarely + var/hit_the_shuttle_chance = 1 + +/datum/shuttle_event/simple_spawner/meteor/dust/get_spawn_turf() + return prob(hit_the_shuttle_chance) ? pick(spawning_turfs_hit) : pick(spawning_turfs_miss) + +///Okay this spawns a lot of really bad meteors, but they never hit the shuttle so it's perfectly safe (unless you go outside lol) +/datum/shuttle_event/simple_spawner/meteor/safe + name = "Various Meteors! (Safe)" + event_probability = 5 + activation_fraction = 0.1 + + spawn_probability_per_process = 100 + spawns_per_spawn = 6 + spawning_flags = SHUTTLE_EVENT_MISS_SHUTTLE + spawning_list = list(/obj/effect/meteor/medium = 10, /obj/effect/meteor/big = 5, /obj/effect/meteor/flaming = 3, /obj/effect/meteor/cluster = 1, + /obj/effect/meteor/irradiated = 3, /obj/effect/meteor/bluespace = 2) diff --git a/code/modules/shuttle/shuttle_events/misc.dm b/code/modules/shuttle/shuttle_events/misc.dm new file mode 100644 index 000000000000..f65eddadf7cb --- /dev/null +++ b/code/modules/shuttle/shuttle_events/misc.dm @@ -0,0 +1,61 @@ +///thats amoreeeeee +/datum/shuttle_event/simple_spawner/italian + name = "Italian Storm! (Mama Mia!)" + event_probability = 0.05 + + spawns_per_spawn = 5 + spawning_flags = SHUTTLE_EVENT_MISS_SHUTTLE | SHUTTLE_EVENT_HIT_SHUTTLE + spawn_probability_per_process = 100 + spawning_list = list(/obj/item/food/spaghetti/boiledspaghetti = 5, /obj/item/food/meatball = 1, /obj/item/food/spaghetti/pastatomato = 2, + /obj/item/food/spaghetti/meatballspaghetti = 2, /obj/item/food/pizza/margherita = 1) + +///We do a little bit of tomfoolery +/datum/shuttle_event/simple_spawner/fake_ttv + name = "Fake TTV (Harmless!)" + event_probability = 0.5 + activation_fraction = 0.1 + + spawning_list = list(/obj/item/transfer_valve/fake = 1) + spawning_flags = SHUTTLE_EVENT_HIT_SHUTTLE + spawn_probability_per_process = 5 + + remove_from_list_when_spawned = TRUE + self_destruct_when_empty = TRUE + +///Just spawn random maint garbage +/datum/shuttle_event/simple_spawner/maintenance + name = "Maintenance Debris (Harmless!)" + event_probability = 3 + activation_fraction = 0.1 + + spawning_list = list() + spawning_flags = SHUTTLE_EVENT_HIT_SHUTTLE | SHUTTLE_EVENT_MISS_SHUTTLE + spawn_probability_per_process = 100 + spawns_per_spawn = 2 + +/datum/shuttle_event/simple_spawner/maintenance/get_type_to_spawn() + var/list/spawn_list = GLOB.maintenance_loot + while(islist(spawn_list)) + spawn_list = pick_weight(spawn_list) + return spawn_list + +///Sensors indicate that a black hole's gravitational field is affecting the region of space we were headed through +/datum/shuttle_event/simple_spawner/black_hole + name = "Black Hole (Oh no!)" + event_probability = 0 // only admin spawnable + spawn_probability_per_process = 10 + activation_fraction = 0.35 + spawning_flags = SHUTTLE_EVENT_HIT_SHUTTLE + spawning_list = list(/obj/singularity/shuttle_event = 1) + // only spawn it once + remove_from_list_when_spawned = TRUE + self_destruct_when_empty = TRUE + +///Kobayashi Maru version +/datum/shuttle_event/simple_spawner/black_hole/adminbus + name = "Black Holes (OH GOD!)" + event_probability = 0 + spawn_probability_per_process = 50 + activation_fraction = 0.2 + spawning_list = list(/obj/singularity/shuttle_event = 10) + remove_from_list_when_spawned = TRUE diff --git a/code/modules/shuttle/shuttle_events/player_controlled.dm b/code/modules/shuttle/shuttle_events/player_controlled.dm new file mode 100644 index 000000000000..fb4d6f6945c5 --- /dev/null +++ b/code/modules/shuttle/shuttle_events/player_controlled.dm @@ -0,0 +1,77 @@ +///Mobs spawned with this one are automatically player controlled, if possible +/datum/shuttle_event/simple_spawner/player_controlled + spawning_list = list(/mob/living/basic/carp) + + ///If we cant find a ghost, do we spawn them anyway? Otherwise they go in the garbage bin + var/spawn_anyway_if_no_player = FALSE + + var/ghost_alert_string = "Would you like to be shot at the shuttle?" + + var/role_type = ROLE_SENTIENCE + +/datum/shuttle_event/simple_spawner/player_controlled/spawn_movable(spawn_type) + if(ispath(spawn_type, /mob/living)) + INVOKE_ASYNC(src, PROC_REF(try_grant_ghost_control), spawn_type) + else + ..() + +/// Attempt to grant control of a mob to ghosts before spawning it in. if spawn_anyway_if_no_player = TRUE, we spawn the mob even if there's no ghosts +/datum/shuttle_event/simple_spawner/player_controlled/proc/try_grant_ghost_control(spawn_type) + var/list/candidates = SSpolling.poll_ghost_candidates( + ghost_alert_string + " (Warning: you will not be able to return to your body!)", + check_jobban = role_type, + poll_time = 10 SECONDS, + pic_source = spawn_type, + role_name_text = "shot at shuttle" + ) + var/mob/dead/observer/candidate = length(candidates) ? pick(candidates) : null + if(candidate || spawn_anyway_if_no_player) + var/mob/living/new_mob = new spawn_type (get_turf(get_spawn_turf())) + if(candidate) + new_mob.ckey = candidate.ckey + post_spawn(new_mob) + +///BACK FOR REVENGE!!! +/datum/shuttle_event/simple_spawner/player_controlled/alien_queen + name = "ALIEN QUEEN! (Kinda dangerous!)" + spawning_list = list(/mob/living/carbon/alien/adult/royal/queen = 1, /obj/vehicle/sealed/mecha/working/ripley = 1) + spawning_flags = SHUTTLE_EVENT_HIT_SHUTTLE + + event_probability = 0.2 + spawn_probability_per_process = 10 + activation_fraction = 0.5 + + spawn_anyway_if_no_player = FALSE + ghost_alert_string = "Would you like to be an alien queen shot at the shuttle?" + remove_from_list_when_spawned = TRUE + self_destruct_when_empty = TRUE + + role_type = ROLE_ALIEN + +///Spawns three player controlled carp!! Deadchats final chance to wreak havoc, probably really not that dangerous if even one person has a laser gun +/datum/shuttle_event/simple_spawner/player_controlled/carp + name = "Three player controlled carp! (Little dangerous!)" + spawning_list = list(/mob/living/basic/carp = 10, /mob/living/basic/carp/mega = 2, /mob/living/basic/carp/magic = 2, /mob/living/basic/carp/magic/chaos = 1) + spawning_flags = SHUTTLE_EVENT_HIT_SHUTTLE + + event_probability = 1 + spawn_probability_per_process = 10 + activation_fraction = 0.4 + + spawn_anyway_if_no_player = TRUE + ghost_alert_string = "Would you like to be a space carp to pester the emergency shuttle?" + remove_from_list_when_spawned = TRUE + self_destruct_when_empty = TRUE + + role_type = ROLE_SENTIENCE + + ///how many carp can we spawn max? + var/max_carp_spawns = 3 + +/datum/shuttle_event/simple_spawner/player_controlled/carp/New(obj/docking_port/mobile/port) + . = ..() + + var/list/spawning_list_copy = spawning_list.Copy() + spawning_list.Cut() + for(var/i in 1 to max_carp_spawns) + spawning_list[pick_weight(spawning_list_copy)] += 1 diff --git a/code/modules/shuttle/shuttle_events/turbulence.dm b/code/modules/shuttle/shuttle_events/turbulence.dm new file mode 100644 index 000000000000..bbc136397c2a --- /dev/null +++ b/code/modules/shuttle/shuttle_events/turbulence.dm @@ -0,0 +1,48 @@ +/// Repeat the "buckle in or fall over" event a couple times +/datum/shuttle_event/turbulence + name = "Turbulence" + event_probability = 5 + activation_fraction = 0.1 + /// Minimum time to wait between periods of turbulence + var/minimum_interval = 20 SECONDS + /// Maximum time to wait between periods of turbulence + var/maximum_interval = 50 SECONDS + /// Time until we should shake again + COOLDOWN_DECLARE(turbulence_cooldown) + /// How long do we give people to get buckled? + var/warning_interval = 2 SECONDS + +/datum/shuttle_event/turbulence/activate() + . = ..() + minor_announce("Please note, we are entering an area of subspace turbulence. For your own safety, \ + please fasten your belts and remain seated until the vehicle comes to a complete stop.", + title = "Emergency Shuttle", alert = TRUE) + COOLDOWN_START(src, turbulence_cooldown, rand(5 SECONDS, 20 SECONDS)) // Reduced interval after the announcement + +/datum/shuttle_event/turbulence/event_process() + . = ..() + if (!.) + return + if (!COOLDOWN_FINISHED(src, turbulence_cooldown)) + return + COOLDOWN_START(src, turbulence_cooldown, rand(minimum_interval, maximum_interval)) + shake() + addtimer(CALLBACK(src, PROC_REF(knock_down)), warning_interval, TIMER_DELETE_ME) + +/// Warn players to get buckled +/datum/shuttle_event/turbulence/proc/shake() + var/list/mobs = mobs_in_area_type(list(/area/shuttle/escape)) + for(var/mob/living/mob as anything in mobs) + var/shake_intensity = mob.buckled ? 0.25 : 1 + if(mob.client) + shake_camera(mob, 3 SECONDS, shake_intensity) + +/// Knock them down +/datum/shuttle_event/turbulence/proc/knock_down() + if (SSshuttle.emergency.mode != SHUTTLE_ESCAPE) + return // They docked + var/list/mobs = mobs_in_area_type(list(/area/shuttle/escape)) // Not very efficient but check again in case someone was outdoors + for(var/mob/living/mob as anything in mobs) + if(mob.buckled) + continue + mob.Paralyze(3 SECONDS, ignore_canstun = TRUE) diff --git a/icons/mob/simple/carp.dmi b/icons/mob/simple/carp.dmi index 42baa703400f..d58245094799 100644 Binary files a/icons/mob/simple/carp.dmi and b/icons/mob/simple/carp.dmi differ diff --git a/monkestation/code/modules/blueshift/mobs/misc_pets.dm b/monkestation/code/modules/blueshift/mobs/misc_pets.dm index 7e5b7efafc4c..27e23366db2b 100644 --- a/monkestation/code/modules/blueshift/mobs/misc_pets.dm +++ b/monkestation/code/modules/blueshift/mobs/misc_pets.dm @@ -29,44 +29,3 @@ unsuitable_atmos_damage = 0 minimum_survivable_temperature = 0 maximum_survivable_temperature = 30000 - -///Wild carp that just vibe ya know -/mob/living/basic/carp/passive - name = "passive carp" - desc = "A timid, sucker-bearing creature that resembles a fish. " - - attack_verb_continuous = "suckers" - attack_verb_simple = "suck" - - melee_damage_lower = 4 - melee_damage_upper = 4 - ai_controller = /datum/ai_controller/basic_controller/carp/passive - -/mob/living/basic/carp/passive/Initialize(mapload) - . = ..() - AddElement(/datum/element/ai_retaliate) - AddElement(/datum/element/pet_bonus, "bloops happily!") - -/** - * Carp which bites back, but doesn't look for targets and doesnt do as much damage - * Still migrate and stuff - */ -/datum/ai_controller/basic_controller/carp/passive - blackboard = list( - BB_BASIC_MOB_STOP_FLEEING = TRUE, - BB_TARGETING_STRATEGY = /datum/targeting_strategy/basic, - BB_PET_TARGETING_STRATEGY = /datum/targeting_strategy/basic/not_friends, - ) - ai_traits = STOP_MOVING_WHEN_PULLED - planning_subtrees = list( - /datum/ai_planning_subtree/pet_planning, - /datum/ai_planning_subtree/simple_find_nearest_target_to_flee, - /datum/ai_planning_subtree/make_carp_rift/panic_teleport, - /datum/ai_planning_subtree/flee_target, - /datum/ai_planning_subtree/find_food, - /datum/ai_planning_subtree/attack_obstacle_in_path/carp, - /datum/ai_planning_subtree/shortcut_to_target_through_carp_rift, - /datum/ai_planning_subtree/make_carp_rift/aggressive_teleport, - /datum/ai_planning_subtree/basic_melee_attack_subtree, - /datum/ai_planning_subtree/carp_migration, - ) diff --git a/tgstation.dme b/tgstation.dme index 4fd36bb85a04..1d43beb90ebe 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -2606,6 +2606,7 @@ #include "code\modules\admin\verbs\atmosdebug.dm" #include "code\modules\admin\verbs\beakerpanel.dm" #include "code\modules\admin\verbs\borgpanel.dm" +#include "code\modules\admin\verbs\change_shuttle_events.dm" #include "code\modules\admin\verbs\cinematic.dm" #include "code\modules\admin\verbs\color_blind_test.dm" #include "code\modules\admin\verbs\commandreport.dm" @@ -5262,6 +5263,12 @@ #include "code\modules\shuttle\supply.dm" #include "code\modules\shuttle\syndicate.dm" #include "code\modules\shuttle\white_ship.dm" +#include "code\modules\shuttle\shuttle_events\_shuttle_events.dm" +#include "code\modules\shuttle\shuttle_events\carp.dm" +#include "code\modules\shuttle\shuttle_events\meteors.dm" +#include "code\modules\shuttle\shuttle_events\misc.dm" +#include "code\modules\shuttle\shuttle_events\player_controlled.dm" +#include "code\modules\shuttle\shuttle_events\turbulence.dm" #include "code\modules\spatial_grid\cell_tracker.dm" #include "code\modules\spells\spell.dm" #include "code\modules\spells\spell_types\madness_curse.dm"