From 35084b7d7c8e5fb380d6c6f4b45d35d245fe9667 Mon Sep 17 00:00:00 2001 From: fira Date: Wed, 12 Jun 2024 08:11:28 +0000 Subject: [PATCH] Requisitions Line Tutorial (#5909) # About the pull request Adds a tutorial/simulator for the Requisitions Line. Players are asked by NPCs to vend them several items. There are 3 training NPCs with pre-made requests and highlights, after which the tutorial enters an endless mode for training against increasingly complex requests. This still has some rough edges, notably that it can ask randomly for non-obvious names much like in real game such as "Frag" refering to HEDPs. A new player might need for the NPC to repeat the order with a different name before getting it right. Also it might be worth finding a way to do highlighting in the vendor UI for this... All items dispensed in the tutorial are cloned as props and will be missing some overlays and be non interactible, because it's the simplest way i found so the player doesn't explode everything. # Explain why it's good for the game Gives people an opportunity to approach a simple yet potentially stressful activity. Line rush can be tough when starting the game. This only contains a tutorial for the line though, other reqs system will need to be made separately # Testing Photographs and Procedure Ran through it a few times testing for unintended interactions Older video of initial implement: https://cdn.discordapp.com/attachments/964684928161808384/1178420842347102218/reqline-tutorial.mp4?ex=65f747ee&is=65e4d2ee&hm=797dfdfe2693878f4dc69fb976060a285944023935b2e0b5216670102c37366a& # Changelog :cl: add: Added a Tutorial for tending to the Requisitions Line /:cl: --------- Co-authored-by: John Doe --- code/__DEFINES/vendors.dm | 2 + code/datums/tutorial/marine/reqs_line.dm | 482 ++++++++++++++++++++++ code/game/machinery/vending/cm_vending.dm | 4 +- code/game/objects/prop.dm | 23 ++ code/game/objects/structures/blocker.dm | 4 +- code/modules/gear_presets/other.dm | 20 + code/modules/mob/mob.dm | 2 +- colonialmarines.dme | 1 + icons/misc/tutorial.dmi | Bin 1318 -> 1756 bytes maps/tutorial/tutorial_reqs_line.dmm | 195 +++++++++ 10 files changed, 730 insertions(+), 3 deletions(-) create mode 100644 code/datums/tutorial/marine/reqs_line.dm create mode 100644 maps/tutorial/tutorial_reqs_line.dmm diff --git a/code/__DEFINES/vendors.dm b/code/__DEFINES/vendors.dm index dc78f7caa4d3..70e30edddf24 100644 --- a/code/__DEFINES/vendors.dm +++ b/code/__DEFINES/vendors.dm @@ -72,6 +72,8 @@ /// Vendors with this flag will fill retroactively based on latejoining players, /// and expect a scale multiplier instead of amount of items #define VEND_STOCK_DYNAMIC (1<<10) +/// Vends props looking like the items instead of the actual items. Basically for tutorials. +#define VEND_PROPS (1<<11) // Redemption Tokens #define VEND_TOKEN_ENGINEER "Engineer" diff --git a/code/datums/tutorial/marine/reqs_line.dm b/code/datums/tutorial/marine/reqs_line.dm new file mode 100644 index 000000000000..ba2d7ddade9d --- /dev/null +++ b/code/datums/tutorial/marine/reqs_line.dm @@ -0,0 +1,482 @@ +/* List of Reqs Line tutorial stages */ +#define TUTORIAL_REQS_LINE_STAGE_STARTING 0 //! Reqs Tutorial Stage 0: Get in position +#define TUTORIAL_REQS_LINE_STAGE_ATTACHIES 1 //! Reqs Tutorial Stage 1: Give me some attachies +#define TUTORIAL_REQS_LINE_STAGE_GEARBOX 2 //! Reqs Tutorial Stage 2: I would like an HPR +#define TUTORIAL_REQS_LINE_STAGE_MIXED 3 //! Reqs Tutorial Stage 3: Multiple items. Also toss something over.. +#define TUTORIAL_REQS_LINE_STAGE_SURVIVAL 4 //! Reqs Tutorial Stage 4: SURVIVAL MODE, random requests in a loop + +/// How fast to increase difficulty in survival mode (amount of items/agents) +#define TUTORIAL_REQS_LINE_SURVIVAL_DIFFICULTY (1/3) + +/// Simulates the Requisitions Line experience for newcomers +/datum/tutorial/marine/reqs_line + name = "Marine - Requistions Line" + desc = "Learn how to tend to the requisitions line as a Cargo Technician." + icon_state = "cargotech" + tutorial_id = "requisitions_line" + tutorial_template = /datum/map_template/tutorial/reqs_line + + /// Current step of the tutorial we're at + var/stage = TUTORIAL_REQS_LINE_STAGE_STARTING + /// Current "remind" timer after which the agent will remind player of its request + var/remind_timer + /// Current "hint" timer after which we display visual cues like highlights + var/hint_timer + + /// List of line 'agents', aka the dummies requesting items, sorted by line order + /// During normal stages there is one per stage (except for Forgot stage), + /// During Survival mode there would usually be two: one at the counter, and one moving. + /// The agents are mapped to a list of item types requested. + var/list/mob/living/carbon/human/agents = list() + + /// Active agent currently at the line + var/mob/living/carbon/human/active_agent + /// Specifically for [TUTORIAL_REQS_LINE_STAGE_MIXED], the agent that forgot an item + var/mob/living/carbon/human/loser_agent + + /// Cooldown of confusion if an incorrect item is presented + COOLDOWN_DECLARE(confused_cooldown) + /// Crutch for confusion feedback to work with vending new()ing directly to table - only act surprised once per item type + var/list/confused_types = list() + + /// Max amount of agents per survival wave + var/max_survival_agents = 5 + /// Current survival wave + var/survival_wave = 0 + /// Difficulty factor per survival wave, increasing both the amount of agents and requested items + var/survival_difficulty = 1 + /// Max factor of additional items requested per agent in survival mode. 0.5 = 50% added maximum + var/survival_request_random_factor = 0.5 + + /* + * REQUESTS LISTS + * Maps item types to their names, used for building the requests + */ + var/static/shopping_catalogue = list( + /* ATTACHIES */ + /obj/item/attachable/extended_barrel = list("EB", "EB", "Extended", "Extended Barrel", "Extendo", "Ext Barrel"), + /obj/item/attachable/magnetic_harness = list("MH", "MH", "Magharn", "Mag Harn", "Mag Harness", "Harness", "Magnetic Harness"), + /obj/item/attachable/reddot = list("RDS", "Red Dot", "S5", "Red Dot Sight", "reddot"), + /obj/item/attachable/reflex = list("Reflex", "S6", "Reflex Sight", "S6"), + /obj/item/attachable/scope = list("S8", "S8", "4x", "4x sight", "4x scope", "S8 scope"), + /obj/item/attachable/angledgrip = list("AG", "agrip", "Agrip", "Angled", "angled grip"), + /obj/item/attachable/gyro = list("Gyro"), + /obj/item/attachable/lasersight = list("Laser", "Laser sight", "LS"), + /obj/item/attachable/attached_gun/shotgun = list("U7", "Underbarrel", "Underbarrel Shotgun", "Mini Shotgun"), + /obj/item/attachable/verticalgrip = list("VG", "Vert Grip", "Vertical Grip"), + /obj/item/attachable/stock/rifle = list("Solid Stock", "M41 stock", "M41 Solid Stock"), + /obj/item/attachable/stock/shotgun = list("M37 Stock", "Wooden stock"), + /* GEAR */ + /obj/item/weapon/gun/rifle/m41a = list("M41", "M41", "M41A", "Mk2", "M4 rifle"), + /obj/item/weapon/gun/shotgun/pump = list("M37", "shotgun"), + /obj/item/storage/box/guncase/mou53 = list("MOU", "MOU53", "MOU-53", "Mouse"), + /obj/item/storage/box/guncase/lmg = list("HPR", "HPR kit", "heavy pulse rifle"), + /obj/item/storage/box/guncase/m41aMK1 = list("MK1", "M41 Mk1", "MK1 Kit", "M41A MK1 Kit"), + /obj/item/storage/box/guncase/m56d = list("M56D", "HMG", "M56"), + /obj/item/storage/box/guncase/m2c = list("M2C"), + /obj/item/storage/box/guncase/flamer = list("Flamer", "Flamer kit", "Incinerator"), + /obj/item/storage/box/guncase/m79 = list("GL", "Grenade launcher", "M79", "M79 Grenade launcher"), + /obj/item/clothing/accessory/storage/black_vest = list("Black webbing", "Black webbing vest"), + /obj/item/clothing/accessory/storage/black_vest/brown_vest = list("Brown webbing", "Brown webbing vest"), + /obj/item/clothing/accessory/storage/webbing = list("Webbing", "normal webbing", "web"), + /obj/item/storage/backpack/marine/engineerpack/flamethrower/kit = list("pyro pack", "pyro backpack", "g4-1 pack", "flamer backpack"), + /obj/item/storage/backpack/marine/satchel/rto = list("phone pack", "phone backpack", "radio pack"), + /obj/item/storage/backpack/general_belt = list("G8", "G8 belt"), + /obj/item/storage/pouch/magazine/large = list("Large mag pouch", "Large magazine pouch"), + /obj/item/storage/pouch/shotgun/large = list("Shotgun pouch", "Shotgun shells pouch", "Shells pouch", "Large shells pouch"), + /obj/item/storage/box/m94/signal = list("Signal flares", "box of signals", "CAS flares"), + /obj/item/device/motiondetector = list("MD", "Motion Detector"), + /obj/item/device/binoculars = list("Binos", "Binoculars"), + /obj/item/device/binoculars/range/designator = list("LD", "Designator", "Laser Designator", "Tac Binos"), + /obj/item/pamphlet/skill/jtac = list("JTAC Pamphlet"), + /* Explosives */ + /obj/item/explosive/grenade/high_explosive = list("M40 HEDP", "HEDP"), + /obj/item/explosive/grenade/incendiary = list("M40 HIDP", "Incendiary nade", "Incendiary grenade", "HIDP", "Fire grenade"), + /obj/item/explosive/plastic = list("C4", "C4", "plastic", "plastic explosives"), + /obj/item/explosive/plastic/breaching_charge = list("Breaching", "breach charge", "breaching charge"), + /* AMMO */ + /obj/item/ammo_magazine/rifle/extended = list("Extended", "MK2 Extended", "Extended MK2 Mags"), + /obj/item/ammo_magazine/smg/m39/ap = list("M39 AP", "M39 AP", "SMG AP"), + /obj/item/ammo_magazine/smartgun = list("Smartgun drum", "SG drum"), + ) + +/datum/tutorial/marine/reqs_line/Destroy(force) + STOP_PROCESSING(SSfastobj, src) + kill_timers() + active_agent = null + loser_agent = null + QDEL_LIST(agents) + return ..() + +/datum/tutorial/marine/reqs_line/init_map() + var/obj/structure/machinery/cm_vending/sorted/attachments/blend/tutorial/attachies_vendor = new(loc_from_corner(2, 7)) + add_to_tracking_atoms(attachies_vendor) + var/obj/structure/machinery/cm_vending/sorted/cargo_guns/cargo/blend/tutorial/guns_vendor = new(loc_from_corner(3, 7)) + add_to_tracking_atoms(guns_vendor) + var/obj/structure/machinery/cm_vending/sorted/cargo_ammo/cargo/blend/tutorial/ammo_vendor = new(loc_from_corner(4, 7)) + add_to_tracking_atoms(ammo_vendor) + restock_vendors() + + var/turf/asker_turf = loc_from_corner(1, 6) + RegisterSignal(asker_turf, COMSIG_TURF_ENTERED, PROC_REF(a_new_challenger_appears)) + var/turf/trade_turf = loc_from_corner(2, 6) + RegisterSignal(trade_turf, COMSIG_TURF_ENTERED, PROC_REF(item_offered)) + var/turf/loser_turf = loc_from_corner(0, 6) + RegisterSignal(loser_turf, COMSIG_TURF_ENTERED, PROC_REF(loser_got_the_item)) + + // Crutch to be able to detect items that are spawned directly on the table + RegisterSignal(attachies_vendor, COMSIG_VENDOR_SUCCESSFUL_VEND, PROC_REF(scan_table_for_items)) + RegisterSignal(guns_vendor, COMSIG_VENDOR_SUCCESSFUL_VEND, PROC_REF(scan_table_for_items)) + RegisterSignal(ammo_vendor, COMSIG_VENDOR_SUCCESSFUL_VEND, PROC_REF(scan_table_for_items)) + +/datum/tutorial/marine/reqs_line/init_mob() + . = ..() + arm_equipment(tutorial_mob, /datum/equipment_preset/uscm_ship/cargo) + // Remove their radio from CT preset + var/mob/living/carbon/human/user = tutorial_mob + var/obj/item/device/radio/headset/headset = user.wear_l_ear + user.drop_inv_item_on_ground(headset) + QDEL_NULL(headset) + +/// Refills all the vendors on stage updates so the player shouldn't run out of stock +/datum/tutorial/marine/reqs_line/proc/restock_vendors() + TUTORIAL_ATOM_FROM_TRACKING(/obj/structure/machinery/cm_vending/sorted/attachments/blend/tutorial, attachies_vendor) + restock_one_vendor(attachies_vendor) + TUTORIAL_ATOM_FROM_TRACKING(/obj/structure/machinery/cm_vending/sorted/cargo_guns/cargo/blend/tutorial, cargo_vendor) + restock_one_vendor(cargo_vendor) + TUTORIAL_ATOM_FROM_TRACKING(/obj/structure/machinery/cm_vending/sorted/cargo_ammo/cargo/blend/tutorial, ammo_vendor) + restock_one_vendor(ammo_vendor) + +/// Refills a specific vendor to 99 items across the board +/datum/tutorial/marine/reqs_line/proc/restock_one_vendor(obj/structure/machinery/cm_vending/vendor) + for(var/list/vendspec in vendor.listed_products) + if(vendspec[2] >= 0) + vendspec[2] = 99 + +/datum/tutorial/marine/reqs_line/process(delta_time) + if(stage == TUTORIAL_REQS_LINE_STAGE_SURVIVAL && !length(agents)) + spawn_survival_wave() + + for(var/mob/living/carbon/human/agent as anything in agents) + if(agent == loser_agent) + continue + if(agent == active_agent) + agent.face_dir(EAST) + else if(!agent_step(agent, NORTH)) + agent_step(agent, EAST) + +/// Makes agents move on processing tick if they can. They check surroundings to ensure proper movement flow. +/datum/tutorial/marine/reqs_line/proc/agent_step(mob/living/carbon/human/agent, dir) + var/turf/target_turf = get_step(agent, dir) + if(target_turf.density) + return FALSE + if(locate(/mob/living) in target_turf) + return FALSE + // Don't try to step back through the turnstile + if(dir == EAST && locate(/obj/structure/machinery/line_nexter) in target_turf) + return FALSE + . = TRUE + agent.Move(target_turf, dir) + +/// Creates a new agent with the given request list to queue in the line +/datum/tutorial/marine/reqs_line/proc/spawn_agent(list/request = list(), name_prefix) + var/turf/target_turf = loc_from_corner(1, 2) + var/mob/living/carbon/human/dummy/agent = new(target_turf) + var/mob_name = "[name_prefix][random_name(agent.gender)]" + agent.change_real_name(agent, mob_name) + arm_equipment(agent, /datum/equipment_preset/uscm/tutorial_rifleman) + agents[agent] = request + RegisterSignal(agent, COMSIG_PARENT_QDELETING, PROC_REF(clean_agent)) + +/// Called to generate a new survival wave of agents +/datum/tutorial/marine/reqs_line/proc/spawn_survival_wave() + survival_wave++ + message_to_player("Wave [survival_wave]") + var/agents_to_spawn = min(max_survival_agents, 1 + survival_wave * TUTORIAL_REQS_LINE_SURVIVAL_DIFFICULTY * survival_difficulty) + for(var/agent_i in 1 to agents_to_spawn) + var/items_requested = 1 + survival_wave * survival_difficulty * 0.5 + items_requested *= (1 + survival_request_random_factor * rand()) + spawn_survival_agent(round(items_requested)) + +/// Called to generate a single agent and request +/datum/tutorial/marine/reqs_line/proc/spawn_survival_agent(items_to_request) + var/list/request = list() + var/list/catalogue = list() + // We make a custom catalogue copy to increase weighting of already requested items; + // this avoids getting huge lists too quickly + for(var/typepath in shopping_catalogue) + catalogue += typepath + for(var/i in 1 to items_to_request) + request += pick(catalogue) + if(i < 6) // Only telescope catalogues the first 5 times as the chances compound + catalogue += request + spawn_agent(request, "Lv [survival_wave]. ") + +/// Called when an agent presents at the line window and needs to make a request +/datum/tutorial/marine/reqs_line/proc/a_new_challenger_appears(turf/source, mob/living/carbon/human/challenger) + SIGNAL_HANDLER + if(!(challenger in agents)) // Bob Vancelave NOT allowed + return + active_agent = challenger + confused_types.Cut() + var/list/request = agents[challenger] + if(!length(request)) + make_agent_leave() + return + restock_vendors() + var/speech = verbalize_request(request) + var/greeting = pick("Hello! ", "hi, ", "hey, ", "Good day. ", "I need ", "Please give me ", "", "") // Yes, no greeting is a greeting option for real world accuracy + var/trailing = pick("", "", ", please.", " - please and thank you", ", thanks.", ", hurry") + challenger.say("[greeting][speech][trailing]") // Pleasantries for the first exchange only + remind_timer = addtimer(CALLBACK(src, PROC_REF(remind_request)), 15 SECONDS, TIMER_STOPPABLE) + hint_timer = addtimer(CALLBACK(src, PROC_REF(send_hints)), 3 SECONDS, TIMER_STOPPABLE) + +/// Called when we need to remind the user of what we want served +/datum/tutorial/marine/reqs_line/proc/remind_request() + var/list/request = agents[active_agent] + var/speech = verbalize_request(request) + active_agent.say(speech) + remind_timer = addtimer(CALLBACK(src, PROC_REF(remind_request)), 15 SECONDS, TIMER_STOPPABLE) + +/// Transforms the list of required items by the agent into a string request for the user +/datum/tutorial/marine/reqs_line/proc/verbalize_request(list/original_request) + var/list/request = shuffle(original_request) + var/output_string = "" + var/counts = list() // Assoc list of how many are needed of each item + for(var/item in request) + if(item in counts) + counts[item]++ + else + counts[item] = 1 + var/first = TRUE + for(var/item in counts) + var/list/info = shopping_catalogue[item] + var/word = pick(info) // Pick one of the coded in designations for the item + if(!first) // Join list with commas + output_string += ", " + first = FALSE + if(counts[item] > 1) + output_string += "[counts[item]] " + output_string += word + return output_string + +/// Triggered when an object is put on the table. The agent evaluates if that's something they want and reacts appropriately. +/datum/tutorial/marine/reqs_line/proc/item_offered(turf/source, obj/item/item) + SIGNAL_HANDLER + if(!active_agent) + return + var/list/request = agents[active_agent] + + var/item_type = item.type + if(istype(item, /obj/item/prop/replacer)) + var/obj/item/prop/replacer/prop = item + item_type = prop.original_type + + if(!(item_type in request)) // Wrong item + if(item_type in confused_types) + return + confused_types |= item_type + if(COOLDOWN_FINISHED(src, confused_cooldown)) + COOLDOWN_START(src, confused_cooldown, 5 SECONDS) + active_agent.say("Huh?") + QDEL_IN(item, 30 SECONDS) + return + + request -= item_type + agent_pick_up(active_agent, item) + + // If there's nothing left to pick up, we leave + if(loser_agent) + return // Still blocking the way. Wait here. + if(!length(request)) + make_agent_leave(success = TRUE) + +/// Re-scan the table/trade turf for any present items. We have to do this because items vended to the table do not move onto it. +/datum/tutorial/marine/reqs_line/proc/scan_table_for_items(datum/source) + SIGNAL_HANDLER + var/turf/trade_turf = loc_from_corner(2, 6) + for(var/obj/item/item in trade_turf) + item_offered(source, item) + +/datum/tutorial/marine/reqs_line/proc/agent_pick_up(mob/agent, obj/item/item) + // Actually pick the item up for the animation + agent.put_in_hands(item, drop_on_fail = FALSE) + playsound(agent, "rustle", 30) + // Now delete it + agent.temp_drop_inv_item(item) + qdel(item) + +/datum/tutorial/marine/reqs_line/proc/make_agent_leave(success = FALSE) + switch(stage) + if(TUTORIAL_REQS_LINE_STAGE_ATTACHIES) + INVOKE_ASYNC(src, PROC_REF(continue_stage_gearbox)) + if(TUTORIAL_REQS_LINE_STAGE_GEARBOX) + INVOKE_ASYNC(src, PROC_REF(continue_stage_mixed)) + if(TUTORIAL_REQS_LINE_STAGE_MIXED) + INVOKE_ASYNC(src, PROC_REF(continue_stage_survival)) + // Wave handling for Survival is in process + clean_items() + kill_timers() + + if(!active_agent) + return // Nani? + + if(success && prob(80)) + var/speech = pick("Thanks!", "Thanks", "Thanks bro", "Thank you.", "Bye", "Nice.") + active_agent.say(speech) + + // Immediately step the agent through the turnstile and towards exit + var/turf/target_turf = get_step(active_agent, WEST) + active_agent.Move(target_turf, WEST) + active_agent = null + +/// Cleanup when an agent reaches the exit +/datum/tutorial/marine/reqs_line/proc/clean_agent(datum/source) + SIGNAL_HANDLER + agents -= source + if(active_agent == source) + active_agent = null + +/// Cleanup the table and ground contents when an agent leaves the line +/datum/tutorial/marine/reqs_line/proc/clean_items() + var/turf/trade_turf = loc_from_corner(2, 6) + for(var/obj/item/item in trade_turf) + qdel(item) + var/turf/forgot_turf = loc_from_corner(0, 6) + for(var/obj/item/item in forgot_turf) + qdel(item) + +/// Kills active timers to reset state +/datum/tutorial/marine/reqs_line/proc/kill_timers() + if(remind_timer) + deltimer(remind_timer) + remind_timer = null + if(hint_timer) // User was just that fast + deltimer(hint_timer) + hint_timer = null + +/// Displays appropriate hints for the user based on tutorial stage +/datum/tutorial/marine/reqs_line/proc/send_hints() + switch(stage) + if(TUTORIAL_REQS_LINE_STAGE_ATTACHIES) + TUTORIAL_ATOM_FROM_TRACKING(/obj/structure/machinery/cm_vending/sorted/attachments/blend/tutorial, attachies_vendor) + add_highlight(attachies_vendor) + message_to_player("This marine wants 'attachies' for their weapon. You can find them in the leftmost vendor.") + update_objective("Serve the marine's request using the vendors.") + if(TUTORIAL_REQS_LINE_STAGE_GEARBOX) + TUTORIAL_ATOM_FROM_TRACKING(/obj/structure/machinery/cm_vending/sorted/cargo_guns/cargo/blend/tutorial, gear_vendor) + add_highlight(gear_vendor) + message_to_player("This marine wants items from the gear vendor in the middle. You can use the search function at top right to find things more easily.") + update_objective("Serve the marine's request using the middle vendor. This one has a lot of things, you might want to use the search bar.") + if(TUTORIAL_REQS_LINE_STAGE_MIXED) + TUTORIAL_ATOM_FROM_TRACKING(/obj/structure/machinery/cm_vending/sorted/cargo_ammo/cargo/blend/tutorial, ammo_vendor) + add_highlight(ammo_vendor) + add_highlight(loser_agent) + loser_agent.say("Wait! I really NEED a Mk2 Extended Mag. Throw me one!") + message_to_player("Seems the marine wanted ammo too. Grab some and high-toss it over to him, with [retrieve_bind("toggle_high_throw_mode")].") + update_objective("Get the M41 Extended magazine and perform a high toss to give it to the forgetful marine.") + +/datum/tutorial/marine/reqs_line/start_tutorial(mob/starting_mob) + . = ..() + if(!.) + return + + init_mob() + START_PROCESSING(SSfastobj, src) + + message_to_player("Welcome to Requisitions Line tutorial. Come in and have a seat.") + update_objective("Reach the line window to begin!") + var/turf/target_turf = loc_from_corner(3, 6) + RegisterSignal(target_turf, COMSIG_TURF_ENTERED, PROC_REF(user_in_position)) + + +/// Called when the player is in position to start handling the line +/datum/tutorial/marine/reqs_line/proc/user_in_position(turf/source, atom/movable/mover) + SIGNAL_HANDLER + if(mover != tutorial_mob) + return + UnregisterSignal(source, COMSIG_TURF_ENTERED) + stage = TUTORIAL_REQS_LINE_STAGE_ATTACHIES + var/list/request = list(/obj/item/attachable/magnetic_harness, /obj/item/attachable/extended_barrel) + spawn_agent(request) + +/datum/tutorial/marine/reqs_line/proc/continue_stage_gearbox() + TUTORIAL_ATOM_FROM_TRACKING(/obj/structure/machinery/cm_vending/sorted/attachments/blend/tutorial, attachies_vendor) + remove_highlight(attachies_vendor) + message_to_player("Success!") + stage = TUTORIAL_REQS_LINE_STAGE_GEARBOX + var/list/request = list(/obj/item/storage/box/guncase/lmg, /obj/item/explosive/grenade/high_explosive) + spawn_agent(request) + +/datum/tutorial/marine/reqs_line/proc/continue_stage_mixed() + loser_agent = active_agent + TUTORIAL_ATOM_FROM_TRACKING(/obj/structure/machinery/cm_vending/sorted/cargo_guns/cargo/blend/tutorial, gear_vendor) + remove_highlight(gear_vendor) + message_to_player("Success!") + stage = TUTORIAL_REQS_LINE_STAGE_MIXED + var/list/request = list(/obj/item/attachable/gyro, /obj/item/storage/box/guncase/m41aMK1, /obj/item/explosive/grenade/high_explosive) + spawn_agent(request) + +/datum/tutorial/marine/reqs_line/proc/loser_got_the_item(turf/source, atom/movable/passer) + SIGNAL_HANDLER + var/obj/item/prop/replacer/prop = passer + if(!istype(prop)) + return + if(prop.original_type != /obj/item/ammo_magazine/rifle/extended) + return + qdel(prop) + loser_agent.say("Nice.") + remove_highlight(loser_agent) + TUTORIAL_ATOM_FROM_TRACKING(/obj/structure/machinery/cm_vending/sorted/cargo_ammo/cargo/blend/tutorial, ammo_vendor) + remove_highlight(ammo_vendor) + + loser_agent = null // Resumes moving + // Unstucks the guy at line to move on too if he's already been served + if(active_agent) + var/list/request = agents[active_agent] + if(!length(request)) + make_agent_leave(TRUE) + +/datum/tutorial/marine/reqs_line/proc/continue_stage_survival() + mark_completed() + message_to_player("Success! You have completed the tutorial!") + update_objective("You have finished the tutorial! But there's more if you want to practice.") + addtimer(CALLBACK(src, PROC_REF(message_to_player), "You may stay to practice with random orders, or quit with the button at top left."), 3 SECONDS) + addtimer(CALLBACK(src, PROC_REF(engage_survival_mode)), 12 SECONDS) + +/datum/tutorial/marine/reqs_line/proc/engage_survival_mode() + update_objective("Keep practicing with increasingly complex orders, or leave at any time with the button at top left.") + stage = TUTORIAL_REQS_LINE_STAGE_SURVIVAL + +/datum/map_template/tutorial/reqs_line + name = "Reqs Line Tutorial (8x11)" + mappath = "maps/tutorial/tutorial_reqs_line.dmm" + width = 8 + height = 11 + +/* === ITEMS USED IN THE TUTORIAL === */ + +/// Deletes dummies coming onto it, purely and simply +/obj/effect/landmark/tutorial/reqs_line_cleaner/Crossed(atom/movable/passer) + if(istype(passer, /mob/living/carbon/human)) + qdel(passer) + +/* === VENDORS USED IN THE TUTORIAL === */ + +/obj/structure/machinery/cm_vending/sorted/cargo_guns/cargo/blend/tutorial + vend_flags = VEND_CLUTTER_PROTECTION | VEND_LIMITED_INVENTORY | VEND_LOAD_AMMO_BOXES | VEND_PROPS + +/obj/structure/machinery/cm_vending/sorted/cargo_ammo/cargo/blend/tutorial + vend_flags = VEND_CLUTTER_PROTECTION | VEND_LIMITED_INVENTORY | VEND_LOAD_AMMO_BOXES | VEND_PROPS + +/obj/structure/machinery/cm_vending/sorted/attachments/blend/tutorial + vend_flags = VEND_CLUTTER_PROTECTION | VEND_LIMITED_INVENTORY | VEND_LOAD_AMMO_BOXES | VEND_PROPS + + +#undef TUTORIAL_REQS_LINE_STAGE_STARTING +#undef TUTORIAL_REQS_LINE_STAGE_ATTACHIES +#undef TUTORIAL_REQS_LINE_STAGE_GEARBOX +#undef TUTORIAL_REQS_LINE_STAGE_MIXED +#undef TUTORIAL_REQS_LINE_STAGE_SURVIVAL + +#undef TUTORIAL_REQS_LINE_SURVIVAL_DIFFICULTY diff --git a/code/game/machinery/vending/cm_vending.dm b/code/game/machinery/vending/cm_vending.dm index 6c55ce8c7946..76a2b6df8e45 100644 --- a/code/game/machinery/vending/cm_vending.dm +++ b/code/game/machinery/vending/cm_vending.dm @@ -1384,7 +1384,9 @@ GLOBAL_LIST_INIT(cm_vending_gear_corresponding_types_list, list( /obj/structure/machinery/cm_vending/proc/vendor_successful_vend_one(prod_type, mob/living/carbon/human/user, turf/target_turf, insignas_override, stack_amount) var/obj/item/new_item - if(ispath(prod_type, /obj/item)) + if(vend_flags & VEND_PROPS) + new_item = new /obj/item/prop/replacer(target_turf, prod_type) + else if(ispath(prod_type, /obj/item)) if(ispath(prod_type, /obj/item/weapon/gun)) new_item = new prod_type(target_turf, TRUE) else diff --git a/code/game/objects/prop.dm b/code/game/objects/prop.dm index ac94e8ab03b4..cc941b19ed97 100644 --- a/code/game/objects/prop.dm +++ b/code/game/objects/prop.dm @@ -2,6 +2,29 @@ name = "prop" desc = "Some kind of prop." +/// A prop that acts as a replacement for another item, mimicking their looks. +/// Mainly used in Reqs Tutorial to provide the full item selections without side effects. +/obj/item/prop/replacer + /// The type that this object is taking the place of + var/original_type + +/obj/item/prop/replacer/Initialize(mapload, obj/original_type) + if(!original_type) + return INITIALIZE_HINT_QDEL + . = ..() + src.original_type = original_type + var/obj/created_type = new original_type // Instancing this for the sake of assigning its appearance to the prop and nothing else + name = initial(original_type.name) + icon = initial(original_type.icon) + icon_state = initial(original_type.icon_state) + desc = initial(original_type.desc) + if(ispath(original_type, /obj/item)) + var/obj/item/item_type = original_type + item_state = initial(item_type.item_state) + + appearance = created_type.appearance + qdel(created_type) + /obj/item/prop/laz_top name = "lazertop" icon = 'icons/obj/structures/props/server_equipment.dmi' diff --git a/code/game/objects/structures/blocker.dm b/code/game/objects/structures/blocker.dm index 33f79d7e9d32..aa438aafac64 100644 --- a/code/game/objects/structures/blocker.dm +++ b/code/game/objects/structures/blocker.dm @@ -126,8 +126,10 @@ visible = TRUE -// for fuel pump since it's a large sprite. +/obj/structure/blocker/forcefield/human/bulletproof/get_projectile_hit_boolean() + return TRUE +// for fuel pump since it's a large sprite. /obj/structure/blocker/fuelpump name = "\improper Fuel Pump" desc = "It is a machine that pumps fuel around the ship." diff --git a/code/modules/gear_presets/other.dm b/code/modules/gear_presets/other.dm index 99b8bf634eed..f51a5bfe5f76 100644 --- a/code/modules/gear_presets/other.dm +++ b/code/modules/gear_presets/other.dm @@ -969,3 +969,23 @@ /datum/equipment_preset/tutorial/fed underfed = FALSE + + +/datum/equipment_preset/uscm/tutorial_rifleman + name = "Tutorial Rifleman" + flags = EQUIPMENT_PRESET_EXTRA + assignment = JOB_SQUAD_MARINE + rank = JOB_SQUAD_MARINE + paygrade = "ME2" + role_comm_title = "RFN" + skills = /datum/skills/pfc/crafty + minimap_icon = "private" + +/datum/equipment_preset/uscm/tutorial_rifleman/load_gear(mob/living/carbon/human/new_human) + new_human.equip_to_slot_or_del(new /obj/item/clothing/under/marine(new_human), WEAR_BODY) + new_human.equip_to_slot_or_del(new /obj/item/clothing/head/helmet/marine(new_human), WEAR_HEAD) + new_human.equip_to_slot_or_del(new /obj/item/clothing/suit/storage/marine/medium(new_human), WEAR_JACKET) + new_human.equip_to_slot_or_del(new /obj/item/storage/backpack/marine(new_human), WEAR_BACK) + new_human.equip_to_slot_or_del(new /obj/item/clothing/gloves/marine(new_human), WEAR_HANDS) + new_human.equip_to_slot_or_del(new /obj/item/clothing/shoes/marine/knife(new_human), WEAR_FEET) + diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 13408be2096e..8a792943e345 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -662,7 +662,7 @@ note dizziness decrements automatically in the mob's Life() proc. // facing verbs /mob/proc/canface() - if(client.moving) return 0 + if(client?.moving) return 0 if(stat==2) return 0 if(anchored) return 0 if(monkeyizing) return 0 diff --git a/colonialmarines.dme b/colonialmarines.dme index ce7c050d6dba..670f064a06ed 100644 --- a/colonialmarines.dme +++ b/colonialmarines.dme @@ -691,6 +691,7 @@ #include "code\datums\tutorial\marine\_marine.dm" #include "code\datums\tutorial\marine\basic_marine.dm" #include "code\datums\tutorial\marine\medical_basic.dm" +#include "code\datums\tutorial\marine\reqs_line.dm" #include "code\datums\tutorial\ss13\_ss13.dm" #include "code\datums\tutorial\ss13\basic_ss13.dm" #include "code\datums\tutorial\ss13\intents.dm" diff --git a/icons/misc/tutorial.dmi b/icons/misc/tutorial.dmi index 31c9f72d385370a96058be82316431ac1d5b84f1..aee42db11bd71a537eae02bbc4a2f1875bdd8b42 100644 GIT binary patch delta 1442 zcmV;T1zq~43fv8lBLV?dktM``BOe<-a&nr5bg-n7a%-AnR!tcd5;rm~8yXuUCn2<_ z!W$bGn|y4=nS15Nq=92#4+sk~JY+vIEL%%F$i1YfnR|qCS9fDhz_y@`dSYp2ksKNs zKR7Ozh;my~Mm0W9VOL69QbwnsuMiLrdT(GuJ~t*LBEYGDVOUJ4oQh>ET0pNxP20004WQchCeo1O_ z1}>G#3a)-GV7~wWE4Dioj$9`7*RN@!ctw}dbVWeW zZrr?e^VaPfA^=3HXuuJqa3?Zhipt&eFR3^!*|?G2o4P@CyNbztHaK=EA~)soxX$t&9zRe>-Cd{EDo~rfx#3h~l%h z4TpfBsB%h&Uer^vI*wpxav3|9%klFVzpSPtMbdR0+LW3c33et6TgJ-p^B4k*;8bS|DWN0JC5c#aMi> z^kC6Y_+2~$e+#MzpU!3YRYHPp>HvroDEux4u$JMlK!Ii1WZ6mpiVs(ca$a77EiV^W z9u{N%)wM?@$N>~U2Bk-9s|mo$`noCS4a3OG=KA_d0`Pck15@Y6#@gdCfR$pQzn~QG zCIQ$efqHJ4(nbwtwM3wRwq;`c%AW~p)1(ry`X1QFckV>Uo-ki$6_58)lGQ3cJ`QrK3 z6n^p>f7&J~^Yaw`A)}7t(8;e%3wVY0BY@ZA0k5jt+uPOe6R5Kz0PI$))!qLAc=L95 zclYg^$+3jEHRpGy<`R;i{6Gp62&~2!91tG^)EmtfY>oP40%IO1@ED87BcO@-ljDg?9iMt0f9`M*lMnfdq(gUFv#K8f-8S>dh7j{*EjlEs+93APl@*jo}7e{K?#Nc zHNWN$UWWCM_2Wkea`&H4-G5IH+6SlYe;^zd;77BMT>*9?0yh5KuU|$K00FY*2u3^tWYd5T zATPcgA)(hhBWJx{7<RbXG{%Mx@tD;?8Y-#ZS wfwpyVk^Jr2Uw{zcM}R~P&cV0>ycj(G52VjF;~N~}iU0rr07*qoM6N<$g7Y(pm;e9( delta 1001 zcmVAktM`Az`(#ia&mHOnj0D$BPSuWr@|W>7Y_&vGCX8yW|1{M zPp6=-7wa6r00001bW%=J06^y0W&i*Hc$1U?ZGVqTk+OoTp9|Q906lUp$Kd;nuK)l8 zY)M2xR9J<@m5FlGFc5}i$*Igy3N+Lu_{h;CAuZ_vO`$~|u#w#M{ojXwl@v-Gb!;;X z>-d`0e*Sk?QW$$^9_QR+?akxD7mpCU&c?<@$L*KG51s_RkS>4cDOtzmw*ve}By^rV z-+$cPeEzKCiWicTN#P)LUTnSGdim;w3xG(U3>-%auNUS_QEYeL;MjH*VQ1;zaitLM zcDrx8-QCcU!p_p~0P6t8wJdo(347k6JkbfqFBpnO}_P)l?{lF8RloHp#ug-)t>ElS^ z#NO8sFpr0x9|VE#4cE=)w2$C^f6%AB&jNhhZ2Fd316ztpHAs&nR_!o*kdA0Zb>O z<%Tk%mkQu)2KCmG*;y69CKDE+l>#k{=w;Q8W();PDwdbqX)U?EE;`I?oqurbXh zq%d{@Xc3_kE_=AQjJ2UC5Okq6PJhI=k)f2< z1`3SUt>PIzt_>MIw27w<8l{xMgE7qkxynjq4E^xXLPD+-;#8)n1Z@a;ULw?J(xBTA zRGAx+PYn7sCa4kwMT{Gh<+?FI<+m9Kwv{*Lwz&YU^4t=XHg`9uW8+35CeKxF7HQ}f z69Hjp;~mY$8=W_gXO#UN6@P$w&?27J0!1Ud_EIRyqWnD=`=MUO*94S{)ptLdb|Xum z9ig58Tk2l~Remq^BEG1p;LlR2GP8g}tD1l!TLq|Q3XEb8lht`?OHwP1cdN2t^C%_W}T@HoziOb`Qwxr**@Bl^*2=M1XPus9S@d XcaOVO5ma+w00000NkvXXu0mjfi}>Oc diff --git a/maps/tutorial/tutorial_reqs_line.dmm b/maps/tutorial/tutorial_reqs_line.dmm new file mode 100644 index 000000000000..ca010dd68fca --- /dev/null +++ b/maps/tutorial/tutorial_reqs_line.dmm @@ -0,0 +1,195 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/turf/closed/wall/almayer/outer, +/area/misc/tutorial) +"c" = ( +/obj/structure/machinery/door/airlock/almayer/marine/requisitions{ + dir = 2; + no_panel = 1; + not_weldable = 1; + req_one_access = null + }, +/turf/open/floor/almayer, +/area/misc/tutorial) +"l" = ( +/obj/structure/surface/table/almayer, +/turf/open/floor/almayer{ + dir = 8; + icon_state = "green" + }, +/area/misc/tutorial) +"m" = ( +/obj/structure/surface/rack, +/turf/open/floor/almayer{ + dir = 8; + icon_state = "green" + }, +/area/misc/tutorial) +"n" = ( +/obj/structure/surface/table/almayer{ + breakable = 0; + throwpass = 0; + climbable = 0; + indestructible = 1; + density = 0 + }, +/obj/structure/blocker/forcefield/human/bulletproof{ + visible = 0 + }, +/turf/open/floor/almayer, +/area/misc/tutorial) +"o" = ( +/turf/closed/wall/almayer/outer, +/area/misc/tutorial) +"w" = ( +/obj/structure/window/framed/almayer/hull, +/turf/open/floor/almayer, +/area/misc/tutorial) +"x" = ( +/obj/structure/barricade/handrail{ + dir = 8 + }, +/turf/open/floor/almayer{ + dir = 4; + icon_state = "green" + }, +/area/misc/tutorial) +"y" = ( +/obj/structure/machinery/line_nexter, +/turf/open/floor/almayer{ + dir = 4; + icon_state = "green" + }, +/area/misc/tutorial) +"E" = ( +/obj/structure/bed/chair/office/dark{ + dir = 8 + }, +/turf/open/floor/almayer, +/area/misc/tutorial) +"J" = ( +/obj/item/toy/bikehorn/rubberducky{ + name = "Quackers"; + desc = "You feel as though this rubber duck has been here for a long time. It's Mr. Quackers! He loves you!" + }, +/obj/effect/landmark/tutorial/reqs_line_cleaner, +/turf/open/floor/almayer, +/area/misc/tutorial) +"K" = ( +/turf/open/floor/almayer, +/area/misc/tutorial) +"N" = ( +/obj/effect/landmark/tutorial_bottom_left, +/turf/open/floor/almayer, +/area/misc/tutorial) +"X" = ( +/obj/structure/sign/ROsign{ + layer = 3 + }, +/turf/closed/wall/almayer, +/area/misc/tutorial) + +(1,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +"} +(2,1,1) = {" +a +K +K +K +K +K +K +K +a +N +a +"} +(3,1,1) = {" +a +J +X +y +x +x +x +x +a +K +a +"} +(4,1,1) = {" +a +a +K +n +w +o +a +a +a +c +a +"} +(5,1,1) = {" +a +a +K +E +l +l +m +m +K +K +a +"} +(6,1,1) = {" +a +a +K +K +K +K +K +K +K +K +a +"} +(7,1,1) = {" +a +a +a +K +K +K +K +K +K +K +a +"} +(8,1,1) = {" +a +a +a +a +a +a +a +a +a +a +a +"}