diff --git a/code/__DEFINES/shuttles.dm b/code/__DEFINES/shuttles.dm index 5380268e979..c67d9e1a832 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 8ca1e485263..3e4007e1178 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -357,6 +357,10 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_HYPERSPACED "hyperspaced" ///Gives the movable free hyperspace movement without being pulled during shuttle transit #define TRAIT_FREE_HYPERSPACE_MOVEMENT "free_hyperspace_movement" +///Lets the movable move freely in the soft-cordon area of transit space, which would otherwise teleport them away just before they got to see the true cordon +#define TRAIT_FREE_HYPERSPACE_SOFTCORDON_MOVEMENT "free_hyperspace_softcordon_movement" +///Deletes the object upon being dumped into space, usually from exiting hyperspace. Useful if you're spawning in a lot of stuff for hyperspace events that dont need to flood the entire game +#define TRAIT_DEL_ON_SPACE_DUMP "del_on_hyperspace_leave" /// Gets double arcade prizes #define TRAIT_GAMERGOD "gamer-god" #define TRAIT_GIANT "giant" diff --git a/code/controllers/subsystem/shuttle.dm b/code/controllers/subsystem/shuttle.dm index d38b32571c6..1ae6965ddfd 100644 --- a/code/controllers/subsystem/shuttle.dm +++ b/code/controllers/subsystem/shuttle.dm @@ -202,8 +202,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 c3244f9538b..70216bf9f80 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_OUTSIDE_CONTROL) + 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)) @@ -80,7 +93,7 @@ ///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)) 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 0218991f0c2..d247117f3a1 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 00000000000..a5b3ffdf7a4 --- /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/atoms.dm b/code/game/atoms.dm index 32164125c15..42f1b1db80c 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -245,7 +245,7 @@ flags_1 |= INITIALIZED_1 if(loc) - SEND_SIGNAL(loc, COMSIG_ATOM_INITIALIZED_ON, src) /// Sends a signal that the new atom `src`, has been created at `loc` + SEND_SIGNAL(loc, COMSIG_ATOM_INITIALIZED_ON, src, mapload) /// Sends a signal that the new atom `src`, has been created at `loc` SET_PLANE_IMPLICIT(src, plane) diff --git a/code/game/objects/items/devices/transfer_valve.dm b/code/game/objects/items/devices/transfer_valve.dm index 7dee7378e6f..feeedc5a823 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 82fba11d0e2..3bceaa827f0 100644 --- a/code/game/objects/structures/lattice.dm +++ b/code/game/objects/structures/lattice.dm @@ -17,7 +17,6 @@ var/number_of_mats = 1 var/build_material = /obj/item/stack/rods - /datum/armor/structure_lattice melee = 50 fire = 80 diff --git a/code/game/turfs/open/space/transit.dm b/code/game/turfs/open/space/transit.dm index b4c713de751..f754438c2f6 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_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_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)) + 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 && !istype(enterer, /obj/docking_port)) + 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 fcd1d0cb716..9662f38c498 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -50,6 +50,7 @@ GLOBAL_PROTECT(admin_verbs_admin) /datum/admins/proc/trophy_manager, /datum/admins/proc/view_all_circuits, /datum/verbs/menu/Admin/verb/playerpanel, /* It isn't /datum/admin but it fits no less */ + /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 00000000000..a24aa456f23 --- /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 0c6685b1aae..4184f315e21 100644 --- a/code/modules/awaymissions/cordon.dm +++ b/code/modules/awaymissions/cordon.dm @@ -42,6 +42,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 fff31893d0e..7a69c54193d 100644 --- a/code/modules/mapping/space_management/space_reservation.dm +++ b/code/modules/mapping/space_management/space_reservation.dm @@ -94,7 +94,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) ..() @@ -106,6 +106,13 @@ dump_in_space(enterer) +///Only dump if we don't have the hyperspace cordon movement exemption trait +/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) + /datum/turf_reservation/proc/Reserve(width, height, zlevel) src.width = width src.height = height 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 ddeccd93103..93174f7970a 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/carp.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/carp.dm @@ -99,9 +99,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) @@ -163,6 +164,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 */ @@ -261,3 +272,25 @@ obj_damage = 15 #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/mob/living/basic/space_fauna/carp/carp_controllers.dm b/code/modules/mob/living/basic/space_fauna/carp/carp_controllers.dm index 75c915c16a1..f4c446d088b 100644 --- a/code/modules/mob/living/basic/space_fauna/carp/carp_controllers.dm +++ b/code/modules/mob/living/basic/space_fauna/carp/carp_controllers.dm @@ -71,3 +71,26 @@ /datum/ai_planning_subtree/basic_melee_attack_subtree/magicarp, /datum/ai_planning_subtree/carp_migration, ) + +/** + * 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_TARGETTING_DATUM = new /datum/targetting_datum/basic/ignore_faction(), + BB_PET_TARGETTING_DATUM = new /datum/targetting_datum/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/carp, + /datum/ai_planning_subtree/carp_migration, + ) diff --git a/code/modules/projectiles/projectile/energy/_energy.dm b/code/modules/projectiles/projectile/energy/_energy.dm index 8527041e860..6b715fdb74f 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 ac28d380518..b787ed4215a 100644 --- a/code/modules/shuttle/emergency.dm +++ b/code/modules/shuttle/emergency.dm @@ -311,6 +311,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 @@ -462,6 +467,7 @@ send2adminchat("Server", "The Emergency Shuttle has docked with the station.") priority_announce("[SSshuttle.emergency] has docked with the station. You have [timeLeft(600)] minutes to board the Emergency Shuttle.", null, ANNOUNCER_SHUTTLEDOCK, "Priority") ShuttleDBStuff() + addtimer(CALLBACK(src, PROC_REF(announce_shuttle_events)), 20 SECONDS) if(SHUTTLE_DOCKED) @@ -515,6 +521,10 @@ priority_announce("The Emergency Shuttle has left the station. Estimate [timeLeft(600)] minutes until the shuttle docks at Central Command.", null, ANNOUNCER_SHUTTLELEFT, "Priority") INVOKE_ASYNC(SSticker, TYPE_PROC_REF(/datum/controller/subsystem/ticker, poll_hearts)) bolt_all_doors() //SKYRAT EDIT ADDITION + //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) @@ -542,6 +552,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) @@ -573,6 +585,16 @@ setTimer(SSshuttle.emergency_escape_time) priority_announce("The Emergency Shuttle is preparing for direct jump. Estimate [timeLeft(600)] minutes until the shuttle docks at Central Command.", null, null, "Priority") +///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 a0ed6160c45..da26466c807 100644 --- a/code/modules/shuttle/shuttle.dm +++ b/code/modules/shuttle/shuttle.dm @@ -476,6 +476,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() var/admin_forced = FALSE //SKYRAT EDIT ADDITION @@ -842,6 +844,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() @@ -1152,13 +1155,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 @@ -1177,6 +1184,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 00000000000..d73fba2282b --- /dev/null +++ b/code/modules/shuttle/shuttle_events/_shuttle_events.dm @@ -0,0 +1,138 @@ +///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 FALSE + + if(!LAZYLEN(spawning_list) && self_destruct_when_empty) + return SHUTTLE_EVENT_CLEAR + + 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 00000000000..ac040bd66e6 --- /dev/null +++ b/code/modules/shuttle/shuttle_events/carp.dm @@ -0,0 +1,60 @@ +///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 + +/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 + +///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 00000000000..d1ded58cbad --- /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 00000000000..c0644f6e491 --- /dev/null +++ b/code/modules/shuttle/shuttle_events/misc.dm @@ -0,0 +1,39 @@ +///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 + +///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 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 00000000000..24513da5b98 --- /dev/null +++ b/code/modules/shuttle/shuttle_events/player_controlled.dm @@ -0,0 +1,68 @@ +///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 + ..() + +/datum/shuttle_event/simple_spawner/player_controlled/proc/try_grant_ghost_control(spawn_type) + var/list/candidates = poll_ghost_candidates(ghost_alert_string + " (Warning: you will not be able to return to your body!)", role_type, FALSE, 10 SECONDS) + var/mob/dead/observer/candidate = pick(candidates) + 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 + + 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 + + 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.Add(pick_weight(spawning_list_copy)) diff --git a/icons/mob/simple/carp.dmi b/icons/mob/simple/carp.dmi index 02aadf91498..54b9cb7bee4 100644 Binary files a/icons/mob/simple/carp.dmi and b/icons/mob/simple/carp.dmi differ diff --git a/tgstation.dme b/tgstation.dme index a7ddf7d1d4f..0a199081dc6 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -2472,6 +2472,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" @@ -4947,6 +4948,11 @@ #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\spatial_grid\cell_tracker.dm" #include "code\modules\spells\spell.dm" #include "code\modules\spells\spell_types\madness_curse.dm"