diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index e8bebbc1100..f5c86f91eb9 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -953,6 +953,14 @@ ///sent from mecha action buttons to the mecha they're linked to #define COMSIG_MECHA_ACTION_TRIGGER "mecha_action_activate" +///sent from clicking while you have no equipment selected. Sent before cooldown and adjacency checks, so you can use this for infinite range things if you want. +#define COMSIG_MECHA_MELEE_CLICK "mecha_action_melee_click" + /// Prevents click from happening. + #define COMPONENT_CANCEL_MELEE_CLICK (1<<0) +///sent from clicking while you have equipment selected. +#define COMSIG_MECHA_EQUIPMENT_CLICK "mecha_action_equipment_click" + /// Prevents click from happening. + #define COMPONENT_CANCEL_EQUIPMENT_CLICK (1<<0) // /mob/living/carbon/human signals diff --git a/code/__DEFINES/devices.dm b/code/__DEFINES/devices.dm index 412e5d416d7..d28e16ff433 100644 --- a/code/__DEFINES/devices.dm +++ b/code/__DEFINES/devices.dm @@ -18,3 +18,16 @@ // Used by PDA and cartridge code to reduce repetitiveness of spritesheets #define PDAIMG(what) {""} + +//N-spect scanner defines +#define INSPECTOR_PRINT_SOUND_MODE_NORMAL 1 +#define INSPECTOR_PRINT_SOUND_MODE_CLASSIC 2 +#define INSPECTOR_PRINT_SOUND_MODE_HONK 3 +#define INSPECTOR_PRINT_SOUND_MODE_FAFAFOGGY 4 +#define BANANIUM_CLOWN_INSPECTOR_PRINT_SOUND_MODE_LAST 4 +#define CLOWN_INSPECTOR_PRINT_SOUND_MODE_LAST 4 +#define INSPECTOR_POWER_USAGE_HONK 15 +#define INSPECTOR_POWER_USAGE_NORMAL 5 +#define INSPECTOR_TIME_MODE_SLOW 1 +#define INSPECTOR_TIME_MODE_FAST 2 +#define INSPECTOR_TIME_MODE_HONK 3 diff --git a/code/__DEFINES/mecha.dm b/code/__DEFINES/mecha.dm index 4689591960b..9acc9bfa9b9 100644 --- a/code/__DEFINES/mecha.dm +++ b/code/__DEFINES/mecha.dm @@ -13,6 +13,8 @@ #define HAS_LIGHTS (1<<6) #define QUIET_STEPS (1<<7) #define QUIET_TURNS (1<<8) +///blocks using equipment and melee attacking. +#define CANNOT_INTERACT (1<<9) #define MECHA_MELEE (1 << 0) #define MECHA_RANGED (1 << 1) diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm index b4295b522d4..5ace6d01a99 100644 --- a/code/__DEFINES/misc.dm +++ b/code/__DEFINES/misc.dm @@ -435,6 +435,7 @@ GLOBAL_LIST_INIT(pda_styles, sortList(list(MONO, VT, ORBITRON, SHARE))) #define BONE_SCAR_FILE "wounds/bone_scar_desc.json" #define SCAR_LOC_FILE "wounds/scar_loc.json" #define EXODRONE_FILE "exodrone.json" +#define CLOWN_NONSENSE_FILE "clown_nonsense.json" //Fullscreen overlay resolution in tiles. #define FULLSCREEN_OVERLAY_RESOLUTION_X 15 diff --git a/code/__DEFINES/vehicles.dm b/code/__DEFINES/vehicles.dm index eff970e61b8..3a2e5fc26ec 100644 --- a/code/__DEFINES/vehicles.dm +++ b/code/__DEFINES/vehicles.dm @@ -1,8 +1,18 @@ -//Vehicle control flags +//Vehicle control flags. control flags describe access to actions in a vehicle. -#define VEHICLE_CONTROL_PERMISSION 1 -#define VEHICLE_CONTROL_DRIVE 2 -#define VEHICLE_CONTROL_KIDNAPPED 4 //Can't leave vehicle voluntarily, has to resist. +///controls the vehicles movement +#define VEHICLE_CONTROL_DRIVE (1<<0) +///Can't leave vehicle voluntarily, has to resist. +#define VEHICLE_CONTROL_KIDNAPPED (1<<1) +///melee attacks/shoves a vehicle may have +#define VEHICLE_CONTROL_MELEE (1<<2) +///using equipment/weapons on the vehicle +#define VEHICLE_CONTROL_EQUIPMENT (1<<3) +///changing around settings and the like. +#define VEHICLE_CONTROL_SETTINGS (1<<4) + +///ez define for giving a single pilot mech all the flags it needs. +#define FULL_MECHA_CONTROL ALL //Ridden vehicle flags diff --git a/code/_onclick/hud/fullscreen.dm b/code/_onclick/hud/fullscreen.dm index 284632838ae..973645fba39 100644 --- a/code/_onclick/hud/fullscreen.dm +++ b/code/_onclick/hud/fullscreen.dm @@ -111,6 +111,10 @@ layer = CURSE_LAYER plane = FULLSCREEN_PLANE +/atom/movable/screen/fullscreen/ivanov_display + icon_state = "ivanov" + alpha = 180 + /atom/movable/screen/fullscreen/impaired icon_state = "impairedoverlay" diff --git a/code/datums/components/crafting/recipes.dm b/code/datums/components/crafting/recipes.dm index 7dc84d84e2b..963a82434a7 100644 --- a/code/datums/components/crafting/recipes.dm +++ b/code/datums/components/crafting/recipes.dm @@ -1299,6 +1299,17 @@ /datum/reagent/consumable/ice = 10 ) category = CAT_CHEMISTRY + +/** + * Recipe used for upgrading fake N-spect scanners to bananium HONK-spect scanners + */ +/datum/crafting_recipe/clown_scanner_upgrade + name = "Bananium HONK-spect scanner" + result = /obj/item/inspector/clown/bananium + reqs = list(/obj/item/inspector/clown = 1, /obj/item/stack/sticky_tape = 3, /obj/item/stack/sheet/mineral/bananium = 5) //the chainsaw of prank tools + tool_paths = list(/obj/item/bikehorn) + time = 40 SECONDS + category = CAT_MISC #undef CRAFTING_MACHINERY_CONSUME #undef CRAFTING_MACHINERY_USE diff --git a/code/game/objects/items/documents.dm b/code/game/objects/items/documents.dm index 54367e75bd1..deff2b6e50b 100644 --- a/code/game/objects/items/documents.dm +++ b/code/game/objects/items/documents.dm @@ -57,6 +57,11 @@ to_chat(user, "You forge the official seal with a [C.crayon_color] crayon. No one will notice... right?") update_appearance() +/** + * # N-spect scanner + * + * Creates reports for area inspection bounties. + */ /obj/item/inspector name = "\improper N-spect scanner" desc = "Central Command-issued inspection device. Performs inspections according to Nanotrasen protocols when activated, then \ @@ -69,18 +74,124 @@ w_class = WEIGHT_CLASS_TINY throw_range = 1 throw_speed = 1 + ///How long it takes to print on time each mode, ordered NORMAL, FAST, HONK + var/list/time_list = list(5 SECONDS, 1 SECONDS, 0.1 SECONDS) + ///Which print time mode we're on. + var/time_mode = INSPECTOR_TIME_MODE_SLOW + ///determines the sound that plays when printing a report + var/print_sound_mode = INSPECTOR_PRINT_SOUND_MODE_NORMAL + ///Power cell used to power the scanner. Paths g + var/obj/item/stock_parts/cell/cell = /obj/item/stock_parts/cell/crap + ///Cell cover status + var/cell_cover_open = FALSE + ///Power used per print in cell units + var/power_per_print = INSPECTOR_POWER_USAGE_NORMAL + ///Power used to say an error message + var/power_to_speak = 1 + +/obj/item/inspector/Initialize() + . = ..() + if(ispath(cell)) + cell = new cell(src) + +// Clean up the cell on destroy +/obj/item/clothing/suit/space/Destroy() + if(cell) + QDEL_NULL(cell) + return ..() + +// Clean up the cell on destroy +/obj/item/inspector/handle_atom_del(atom/A) + if(A == cell) + cell = null + return ..() + +// support for items that interact with the cell +/obj/item/inspector/get_cell() + return cell /obj/item/inspector/attack_self(mob/user) . = ..() - if(do_after(user, 5 SECONDS, target = user, progress=TRUE)) - print_report() + if(do_after(user, time_list[time_mode], target = user, progress=TRUE)) + print_report(user) -///Prints out a report for bounty purposes, and plays a short audio blip. -/obj/item/inspector/proc/print_report() - // Create our report +/obj/item/inspector/crowbar_act(mob/living/user, obj/item/tool) + . = ..() + if(user.combat_mode) + return + cell_cover_open = !cell_cover_open + balloon_alert(user, "You [cell_cover_open ? "open" : "close"] the cell cover on \the [src].") + return TRUE + + +/obj/item/inspector/attackby(obj/item/I, mob/user, params) + if(cell_cover_open && istype(I, /obj/item/stock_parts/cell)) + if(cell) + to_chat(user, "[src] already has a cell installed.") + return + if(user.transferItemToLoc(I, src)) + cell = I + to_chat(user, "You successfully install \the [cell] into [src].") + return + return ..() + +/obj/item/inspector/CtrlClick(mob/living/user) + if(!user.canUseTopic(src, BE_CLOSE, NO_DEXTERITY, FALSE, !iscyborg(user)) || !cell_cover_open || !cell) + return ..() + user.visible_message("[user] removes \the [cell] from [src]!", \ + "You remove [cell].") + cell.add_fingerprint(user) + user.put_in_hands(cell) + cell = null + + +/obj/item/inspector/examine(mob/user) + . = ..() + if(!cell_cover_open) + . += "Its cell cover is closed. It looks like it could be pried out, but doing so would require an appropriate tool." + return + . += "It's cell cover is open, exposing the cell slot. It looks like it could be pried in, but doing so would require an appropriate tool." + if(!cell) + . += "The slot for a cell is empty." + else + . += "\The [cell] is firmly in place. Ctrl-click with an empty hand to remove it." + +/** + * Create our report + * + * Arguments: + */ +/obj/item/inspector/proc/create_slip() var/obj/item/paper/report/slip = new(get_turf(src)) slip.generate_report(get_area(src)) - playsound(src, 'sound/machines/high_tech_confirm.ogg', 50, FALSE) + +/** + * Prints out a report for bounty purposes, and plays a short audio blip. + * + * Arguments: +*/ +/obj/item/inspector/proc/print_report(mob/user) + if(!cell) + to_chat(user, "\The [src] doesn't seem to be on... It feels quite light. Perhaps it lacks a power cell?") + return + if(cell.charge == 0) + to_chat(user, "\The [src] doesn't seem to be on... Perhaps it ran out of power?") + return + if(!cell.use(power_per_print)) + if(cell.use(power_to_speak)) + say("ERROR! POWER CELL CHARGE LEVEL TOO LOW TO PRINT REPORT!") + return + + create_slip() + switch(print_sound_mode) + if(INSPECTOR_PRINT_SOUND_MODE_NORMAL) + playsound(src, 'sound/machines/high_tech_confirm.ogg', 50, FALSE) + if(INSPECTOR_PRINT_SOUND_MODE_CLASSIC) + playsound(src, 'sound/items/biddledeep.ogg', 50, FALSE) + if(INSPECTOR_PRINT_SOUND_MODE_HONK) + playsound(src, 'sound/items/bikehorn.ogg', 50, FALSE) + if(INSPECTOR_PRINT_SOUND_MODE_FAFAFOGGY) + playsound(src, pick(list('sound/items/robofafafoggy.ogg', 'sound/items/robofafafoggy2.ogg')), 50, FALSE) /obj/item/paper/report name = "encrypted station inspection" @@ -115,3 +226,223 @@ . += "Wait a minute, this isn't an encrypted inspection report! You should throw it away." else . += "Wait a minute, this thing's blank! You should throw it away." + +/** + * # Fake N-spect scanner + * + * A clown variant of the N-spect scanner + * + * This prints fake reports with garbage in them, + * can be set to print them instantly with a screwdriver. + * By default it plays the old "woody" scanning sound, scanning sounds can be cycled by clicking with a multitool. + * Can be crafted into a bananium HONK-spect scanner + */ +/obj/item/inspector/clown + ///will only cycle through modes with numbers lower than this + var/max_mode = CLOWN_INSPECTOR_PRINT_SOUND_MODE_LAST + ///names of modes, ordered first to last + var/list/mode_names = list("normal", "classic", "honk", "fafafoggy") + +/obj/item/inspector/clown/attack(mob/living/M, mob/living/user) + . = ..() + print_report(user) + +/obj/item/inspector/clown/screwdriver_act(mob/living/user, obj/item/tool) + if(!cell_cover_open) + return ..() + cycle_print_time(user) + return TRUE + +/obj/item/inspector/clown/attackby(obj/item/I, mob/user, params) + if(cell_cover_open && istype(I, /obj/item/kitchen/fork)) + cycle_sound(user) + return + return ..() + +/obj/item/inspector/clown/examine(mob/user) + . = ..() + if(cell_cover_open) + . += "Two weird settings dials are visible within the battery compartment." + +/obj/item/inspector/clown/examine_more(mob/user) + if(!cell_cover_open) + return ..() + . = list("Both setting dials are flush with the surface of the battery compartment, and seem to be impossible to move with bare hands.") + . += "\tThe first dial is labeled \"SPEED\" and looks a bit like a screw head." + . += "\tThe second dial is labeled \"SOUND\". It has four small holes in it. Perhaps it can be turned with a fork?" + . += "\tA small bananium part labeled \"ADVANCED WATER CHIP 23000000\" is visible within the battery compartment. It looks completely unlike normal modern electronics, disturbing it would be rather unwise." + + +/obj/item/inspector/clown/proc/cycle_print_time(mob/user) + var/message + if(time_mode == INSPECTOR_TIME_MODE_FAST) + time_mode = INSPECTOR_TIME_MODE_SLOW + message = "SLOW." + else + time_mode = INSPECTOR_TIME_MODE_FAST + message = "LIGHTNING FAST." + + balloon_alert(user, "You turn the screw-like dial, setting the device's scanning speed to [message]") + +/obj/item/inspector/clown/proc/cycle_sound(mob/user) + print_sound_mode++ + if(print_sound_mode > max_mode) + print_sound_mode = INSPECTOR_PRINT_SOUND_MODE_NORMAL + balloon_alert(user, "You turn the dial with holes in it, setting the device's bleep setting to [mode_names[print_sound_mode]] mode.") + +/obj/item/inspector/clown/create_slip() + var/obj/item/paper/fake_report/slip = new(get_turf(src)) + slip.generate_report(get_area(src)) + +/** + * # Bananium HONK-spect scanner + * + * An upgraded version of the fake N-spect scanner + * + * Can print things way faster, at full power the reports printed by this will destroy + * themselves and leave water behind when folding is attempted by someone who isn't an + * origami master. Printing at full power costs INSPECTOR_POWER_USAGE_HONK cell units + * instead of INSPECTOR_POWER_USAGE_NORMAL cell units. + */ +/obj/item/inspector/clown/bananium + name = "\improper Bananium HONK-spect scanner" + desc = "Honkmother-blessed inspection device. Performs inspections according to Clown protocols when activated, then \ + prints a clowncrypted report regarding the maintenance of the station. Hard to replace." + icon = 'icons/obj/tools.dmi' + icon_state = "bananium_inspector" + w_class = WEIGHT_CLASS_SMALL + max_mode = BANANIUM_CLOWN_INSPECTOR_PRINT_SOUND_MODE_LAST + ///How many more times can we print? + var/paper_charges = 32 + ///Max value of paper_charges + var/max_paper_charges = 32 + ///How much charges are restored per paper consumed + var/charges_per_paper = 1 + +/obj/item/inspector/clown/bananium/proc/check_settings_legality() + if(print_sound_mode == INSPECTOR_PRINT_SOUND_MODE_NORMAL && time_mode == INSPECTOR_TIME_MODE_HONK) + if(cell.use(power_to_speak)) + say("Setting combination forbidden by Geneva convention revision CCXXIII selected, reverting to defaults") + time_mode = INSPECTOR_TIME_MODE_SLOW + print_sound_mode = INSPECTOR_PRINT_SOUND_MODE_NORMAL + power_per_print = INSPECTOR_POWER_USAGE_NORMAL + +/obj/item/inspector/clown/bananium/screwdriver_act(mob/living/user, obj/item/tool) + . = ..() + check_settings_legality() + return TRUE + +/obj/item/inspector/clown/bananium/attackby(obj/item/I, mob/user, params) + . = ..() + if(cell_cover_open) + check_settings_legality() + if(istype(I, /obj/item/paper/fake_report) || paper_charges >= max_paper_charges) + to_chat(user, "\The [src] refuses to consume \the [I]!") + return + if(istype(I, /obj/item/paper)) + to_chat(user, "\The [src] consumes \the [I]!") + paper_charges = min(paper_charges + charges_per_paper, max_paper_charges) + qdel(I) + +/obj/item/inspector/clown/bananium/Initialize() + . = ..() + playsound(src, 'sound/effects/angryboat.ogg', 150, FALSE) + +/obj/item/inspector/clown/bananium/create_slip() + if(time_mode == INSPECTOR_TIME_MODE_HONK) + var/obj/item/paper/fake_report/water/slip = new(get_turf(src)) + slip.generate_report(get_area(src)) + return + return ..() + +/obj/item/inspector/clown/bananium/print_report(mob/user) + if(time_mode != INSPECTOR_TIME_MODE_HONK) + return ..() + if(paper_charges == 0) + if(cell.use(power_to_speak)) + say("ERROR! OUT OF PAPER! MAXIMUM PRINTING SPEED UNAVAIBLE! SWITCH TO A SLOWER SPEED TO OR PROVIDE PAPER!") + else + to_chat(user, "\The [src] doesn't seem to be on... Perhaps it ran out of power?") + return + paper_charges-- + return ..() + +/obj/item/inspector/clown/bananium/cycle_print_time(mob/user) + var/message + switch(time_mode) + if(INSPECTOR_TIME_MODE_HONK) + power_per_print = INSPECTOR_POWER_USAGE_NORMAL + time_mode = INSPECTOR_TIME_MODE_SLOW + message = "SLOW." + if(INSPECTOR_TIME_MODE_SLOW) + time_mode = INSPECTOR_TIME_MODE_FAST + message = "LIGHTNING FAST." + else + time_mode = INSPECTOR_TIME_MODE_HONK + power_per_print = INSPECTOR_POWER_USAGE_HONK + message = "HONK!" + balloon_alert(user, "You turn the screw-like dial, setting the device's scanning speed to [message]") + +/** + * Reports printed by fake N-spect scanner + * + * Not valid for the bounty. + */ +/obj/item/paper/fake_report + name = "encrypted station inspection" + desc = "Contains no information about the station's current status." + icon = 'icons/obj/bureaucracy.dmi' + icon_state = "slip" + show_written_words = FALSE + ///What area the inspector scanned when the report was made. Used to generate the examine text of the report + var/area/scanned_area + +/obj/item/paper/fake_report/proc/generate_report(area/scan_area) + scanned_area = scan_area + icon_state = "slipfull" + + var/list/new_info = list() + for(var/i in 1 to rand(23, 123)) + var/roll = rand(0, 1000) + switch(roll) + if(0 to 900) + new_info += pick_list_replacements(CLOWN_NONSENSE_FILE, "honk") + if(901 to 999) + new_info += pick_list_replacements(CLOWN_NONSENSE_FILE, "non-honk-clown-words") + if(1000) + new_info += pick_list_replacements(CLOWN_NONSENSE_FILE, "rare") + info += new_info.Join() + +/obj/item/paper/fake_report/examine(mob/user) + . = ..() + if(scanned_area?.name) + . += "\The [src] contains no data on [scanned_area.name]." + else if(scanned_area) + . += "\The [src] contains no data on a vague area on station, you should throw it away." + else if(info) + . += "Wait a minute, this isn't an encrypted inspection report! You should throw it away." + else + . += "Wait a minute, this thing's blank! You should throw it away." + +/** + * # Fake report made of water + * + * Fake report but it turns into water under certain circumstances. + * + * If someone who isn't an origami master tries to fold it into a paper plane, it will make the floor it's on wet and disappear. + * If it is ground, it will turn into 5u water. + */ +/obj/item/paper/fake_report/water + grind_results = list(/datum/reagent/water = 5) + +/obj/item/paper/fake_report/water/AltClick(mob/living/user, obj/item/I) + if(!user.canUseTopic(src, BE_CLOSE, NO_DEXTERITY, FALSE, TRUE)) + return + var/datum/action/innate/origami/origami_action = locate() in user.actions + if(origami_action?.active) //Origami masters can fold water + make_plane(user, I, /obj/item/paperplane/syndicate) + else if(do_after(user, 1 SECONDS, target = src, progress=TRUE)) + var/turf/open/target = get_turf(src) + target.MakeSlippery(TURF_WET_WATER, min_wet_time = 10 SECONDS, wet_time_to_add = 5 SECONDS) + to_chat(user, "As you try to fold [src] into the shape of a plane, it disintegrates into water!") + qdel(src) diff --git a/code/modules/cargo/blackmarket/blackmarket_items/tools.dm b/code/modules/cargo/blackmarket/blackmarket_items/tools.dm index 0b2cb16aa82..65543d1fd0b 100644 --- a/code/modules/cargo/blackmarket/blackmarket_items/tools.dm +++ b/code/modules/cargo/blackmarket/blackmarket_items/tools.dm @@ -80,3 +80,19 @@ price_max = 200 stock_max = 3 availability_prob = 50 + +/** + * # Fake N-spect scanner black market entry + */ +/datum/blackmarket_item/tool/fake_scanner + name = "Clowny N-spect scanner" + desc = "This UPGRADED N-spect scanner can play FIVE HIGH-QUALITY SOUNDS (fork required for sound adjustment not included) and print reports \ + LIGHTNING FAST (screwdriver necessary to activate maximum speed not included). We make no claims as to the usefulness of the reports printed by this. \ + Any and all implied warranties are void if the device is touched, moved, kicked, thrown or modified with bananium sheets. Batteries included. \ + Crowbar necessary to change batteries and adjust settings not included." + item = /obj/item/inspector/clown + + price_min = 230 + price_max = 323 + stock_max = 2 + availability_prob = 50 diff --git a/code/modules/paperwork/paperplane.dm b/code/modules/paperwork/paperplane.dm index 01140868b01..42ee665ab6e 100644 --- a/code/modules/paperwork/paperplane.dm +++ b/code/modules/paperwork/paperplane.dm @@ -123,14 +123,24 @@ if(!Carbon.iscopy && !Carbon.copied) to_chat(user, "Take off the carbon copy first.") return - to_chat(user, "You fold [src] into the shape of a plane!") - user.temporarilyRemoveItemFromInventory(src) - var/obj/item/paperplane/plane_type = /obj/item/paperplane //Origami Master var/datum/action/innate/origami/origami_action = locate() in user.actions if(origami_action?.active) - plane_type = /obj/item/paperplane/syndicate + make_plane(user, I, /obj/item/paperplane/syndicate) + else + make_plane(user, I, /obj/item/paperplane) +/** + * Paper plane folding + * + * Arguments: + * * mob/living/user - who's folding + * * obj/item/I - what's being folded + * * obj/item/paperplane/plane_type - what it will be folded into (path) + */ +/obj/item/paper/proc/make_plane(mob/living/user, obj/item/I, obj/item/paperplane/plane_type = /obj/item/paperplane) + to_chat(user, "You fold [src] into the shape of a plane!") + user.temporarilyRemoveItemFromInventory(src) I = new plane_type(loc, src) if(user.Adjacent(I)) user.put_in_hands(I) diff --git a/code/modules/research/designs/mecha_designs.dm b/code/modules/research/designs/mecha_designs.dm index d1de52f6bdb..bb7e24562ad 100644 --- a/code/modules/research/designs/mecha_designs.dm +++ b/code/modules/research/designs/mecha_designs.dm @@ -149,6 +149,30 @@ category = list("Exosuit Modules") departmental_flags = DEPARTMENTAL_FLAG_SCIENCE +/datum/design/board/savannah_ivanov_main + name = "\"Savannah-Ivanov\" Central Control module" + desc = "Allows for the construction of a \"Savannah-Ivanov\" Central Control module." + id = "savannah_ivanov_main" + build_path = /obj/item/circuitboard/mecha/savannah_ivanov/main + category = list("Exosuit Modules") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/board/savannah_ivanov_peri + name = "\"Savannah-Ivanov\" Peripherals Control module" + desc = "Allows for the construction of a \"Savannah-Ivanov\" Peripheral Control module." + id = "savannah_ivanov_peri" + build_path = /obj/item/circuitboard/mecha/savannah_ivanov/peripherals + category = list("Exosuit Modules") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE + +/datum/design/board/savannah_ivanov_targ + name = "\"Savannah-Ivanov\" Weapons & Targeting Control module" + desc = "Allows for the construction of a \"Savannah-Ivanov\" Weapons & Targeting Control module." + id = "savannah_ivanov_targ" + build_path = /obj/item/circuitboard/mecha/savannah_ivanov/targeting + category = list("Exosuit Modules") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE + //////////////////////////////////////// /////////// Mecha Equpment ///////////// //////////////////////////////////////// diff --git a/code/modules/research/designs/mechfabricator_designs.dm b/code/modules/research/designs/mechfabricator_designs.dm index c63f8b42343..4dca862e83e 100644 --- a/code/modules/research/designs/mechfabricator_designs.dm +++ b/code/modules/research/designs/mechfabricator_designs.dm @@ -465,6 +465,124 @@ construction_time = 300 category = list("Phazon") +//Savannah-Ivanov +/datum/design/savannah_ivanov_chassis + name = "Exosuit Chassis (\"Savannah-Ivanov\")" + id = "savannah_ivanov_chassis" + build_type = MECHFAB + build_path = /obj/item/mecha_parts/chassis/savannah_ivanov + materials = list(/datum/material/iron=20000) + construction_time = 100 + category = list("Savannah-Ivanov") + +/datum/design/savannah_ivanov_torso + name = "Exosuit Torso (\"Savannah-Ivanov\")" + id = "savannah_ivanov_torso" + build_type = MECHFAB + build_path = /obj/item/mecha_parts/part/savannah_ivanov_torso + materials = list(/datum/material/iron=20000,/datum/material/glass = 7500) + construction_time = 200 + category = list("Savannah-Ivanov") + +/datum/design/savannah_ivanov_head + name = "Exosuit Head (\"Savannah-Ivanov\")" + id = "savannah_ivanov_head" + build_type = MECHFAB + build_path = /obj/item/mecha_parts/part/savannah_ivanov_head + materials = list(/datum/material/iron=6000,/datum/material/glass = 10000) + construction_time = 100 + category = list("Savannah-Ivanov") + +/datum/design/savannah_ivanov_left_arm + name = "Exosuit Left Arm (\"Savannah-Ivanov\")" + id = "savannah_ivanov_left_arm" + build_type = MECHFAB + build_path = /obj/item/mecha_parts/part/savannah_ivanov_left_arm + materials = list(/datum/material/iron=15000) + construction_time = 150 + category = list("Savannah-Ivanov") + +/datum/design/savannah_ivanov_right_arm + name = "Exosuit Right Arm (\"Savannah-Ivanov\")" + id = "savannah_ivanov_right_arm" + build_type = MECHFAB + build_path = /obj/item/mecha_parts/part/savannah_ivanov_right_arm + materials = list(/datum/material/iron=15000) + construction_time = 150 + category = list("Savannah-Ivanov") + +/datum/design/savannah_ivanov_chassis + name = "Exosuit Chassis (\"Savannah-Ivanov\")" + id = "savannah_ivanov_chassis" + build_type = MECHFAB + build_path = /obj/item/mecha_parts/chassis/savannah_ivanov + materials = list(/datum/material/iron=25000) + construction_time = 100 + category = list("Savannah-Ivanov") + +/datum/design/savannah_ivanov_torso + name = "Exosuit Torso (\"Savannah-Ivanov\")" + id = "savannah_ivanov_torso" + build_type = MECHFAB + build_path = /obj/item/mecha_parts/part/savannah_ivanov_torso + materials = list(/datum/material/iron=25000, /datum/material/glass = 10000,/datum/material/silver=10000) + construction_time = 300 + category = list("Savannah-Ivanov") + +/datum/design/savannah_ivanov_head + name = "Exosuit Head (\"Savannah-Ivanov\")" + id = "savannah_ivanov_head" + build_type = MECHFAB + build_path = /obj/item/mecha_parts/part/savannah_ivanov_head + materials = list(/datum/material/iron=10000,/datum/material/glass = 15000,/datum/material/silver=2000) + construction_time = 200 + category = list("Savannah-Ivanov") + +/datum/design/savannah_ivanov_left_arm + name = "Exosuit Left Arm (\"Savannah-Ivanov\")" + id = "savannah_ivanov_left_arm" + build_type = MECHFAB + build_path = /obj/item/mecha_parts/part/savannah_ivanov_left_arm + materials = list(/datum/material/iron=10000,/datum/material/silver=4000) + construction_time = 200 + category = list("Savannah-Ivanov") + +/datum/design/savannah_ivanov_right_arm + name = "Exosuit Right Arm (\"Savannah-Ivanov\")" + id = "savannah_ivanov_right_arm" + build_type = MECHFAB + build_path = /obj/item/mecha_parts/part/savannah_ivanov_right_arm + materials = list(/datum/material/iron=10000,/datum/material/silver=4000) + construction_time = 200 + category = list("Savannah-Ivanov") + +/datum/design/savannah_ivanov_left_leg + name = "Exosuit Left Leg (\"Savannah-Ivanov\")" + id = "savannah_ivanov_left_leg" + build_type = MECHFAB + build_path = /obj/item/mecha_parts/part/savannah_ivanov_left_leg + materials = list(/datum/material/iron=15000,/datum/material/silver=4000) + construction_time = 200 + category = list("Savannah-Ivanov") + +/datum/design/savannah_ivanov_right_leg + name = "Exosuit Right Leg (\"Savannah-Ivanov\")" + id = "savannah_ivanov_right_leg" + build_type = MECHFAB + build_path = /obj/item/mecha_parts/part/savannah_ivanov_right_leg + materials = list(/datum/material/iron=15000,/datum/material/silver=4000) + construction_time = 200 + category = list("Savannah-Ivanov") + +/datum/design/savannah_ivanov_armor + name = "Exosuit Armor (\"Savannah-Ivanov\")" + id = "savannah_ivanov_armor" + build_type = MECHFAB + build_path = /obj/item/mecha_parts/part/savannah_ivanov_armor + materials = list(/datum/material/iron=30000,/datum/material/uranium=25000,/datum/material/titanium=20000) + construction_time = 600 + category = list("Savannah-Ivanov") + //Clarke /datum/design/clarke_chassis name = "Exosuit Chassis (\"Clarke\")" diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm index 6906df97d64..5adb906aa60 100644 --- a/code/modules/research/techweb/all_nodes.dm +++ b/code/modules/research/techweb/all_nodes.dm @@ -1523,6 +1523,26 @@ ) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) +/datum/techweb_node/savannah_ivanov + id = "mecha_savannah_ivanov" + display_name = "EXOSUIT: Savannah-Ivanov" + description = "Savannah-Ivanov exosuit designs" + prereq_ids = list("adv_mecha", "weaponry", "exp_tools") + design_ids = list( + "savannah_ivanov_armor", + "savannah_ivanov_chassis", + "savannah_ivanov_head", + "savannah_ivanov_left_arm", + "savannah_ivanov_left_leg", + "savannah_ivanov_main", + "savannah_ivanov_peri", + "savannah_ivanov_right_arm", + "savannah_ivanov_right_leg", + "savannah_ivanov_targ", + "savannah_ivanov_torso", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) + /datum/techweb_node/adv_mecha_tools id = "adv_mecha_tools" display_name = "Advanced Exosuit Equipment" diff --git a/code/modules/vehicles/_vehicle.dm b/code/modules/vehicles/_vehicle.dm index e2f03958421..0a8014b50f4 100644 --- a/code/modules/vehicles/_vehicle.dm +++ b/code/modules/vehicles/_vehicle.dm @@ -23,7 +23,7 @@ var/key_type ///The inserted key, needed on some vehicles to start the engine var/obj/item/key/inserted_key - /// Whether the vehicle os currently able to move + /// Whether the vehicle is currently able to move var/canmove = TRUE var/list/autogrant_actions_passenger //plain list of typepaths var/list/autogrant_actions_controller //assoc list "[bitflag]" = list(typepaths) @@ -102,9 +102,10 @@ /obj/vehicle/proc/auto_assign_occupant_flags(mob/M) //override for each type that needs it. Default is assign driver if drivers is not at max. if(driver_amount() < max_drivers) - add_control_flags(M, VEHICLE_CONTROL_DRIVE|VEHICLE_CONTROL_PERMISSION) + add_control_flags(M, VEHICLE_CONTROL_DRIVE) /obj/vehicle/proc/remove_occupant(mob/M) + SHOULD_CALL_PARENT(TRUE) if(!istype(M)) return FALSE remove_control_flags(M, ALL) diff --git a/code/modules/vehicles/cars/clowncar.dm b/code/modules/vehicles/cars/clowncar.dm index f2ce09ad06f..f9f637d7ba3 100644 --- a/code/modules/vehicles/cars/clowncar.dm +++ b/code/modules/vehicles/cars/clowncar.dm @@ -40,7 +40,7 @@ if(ishuman(M)) var/mob/living/carbon/human/H = M if(H.mind?.assigned_role == "Clown") //Ensures only clowns can drive the car. (Including more at once) - add_control_flags(H, VEHICLE_CONTROL_DRIVE|VEHICLE_CONTROL_PERMISSION) + add_control_flags(H, VEHICLE_CONTROL_DRIVE) RegisterSignal(H, COMSIG_MOB_CLICKON, .proc/fire_cannon_at) M.log_message("has entered [src] as a possible driver", LOG_ATTACK) return diff --git a/code/modules/vehicles/mecha/_mecha.dm b/code/modules/vehicles/mecha/_mecha.dm index 40a14174886..b351be06f37 100644 --- a/code/modules/vehicles/mecha/_mecha.dm +++ b/code/modules/vehicles/mecha/_mecha.dm @@ -151,8 +151,8 @@ ///Cooldown between using smoke var/smoke_cooldown = 10 SECONDS - ///Bool for if the mech is currently phasing - var/phasing = FALSE + ///check for phasing, if it is set to text (to describe how it is phasing: "flying", "phasing") it will let the mech walk through walls. + var/phasing = "" ///Power we use every time we phaze through something var/phasing_energy_drain = 200 ///icon_state for flick() when phazing @@ -252,15 +252,21 @@ /obj/vehicle/sealed/mecha/update_icon_state() - if((mecha_flags & SILICON_PILOT) && silicon_icon_state) - icon_state = silicon_icon_state - return ..() - if(LAZYLEN(occupants)) - icon_state = base_icon_state - return ..() - icon_state = "[base_icon_state]-open" + icon_state = get_mecha_occupancy_state() return ..() +//override this proc if you need to split up mecha control between multiple people (see savannah_ivanov.dm) +/obj/vehicle/sealed/mecha/auto_assign_occupant_flags(mob/M) + if(driver_amount() < max_drivers) + add_control_flags(M, FULL_MECHA_CONTROL) + +/obj/vehicle/sealed/mecha/proc/get_mecha_occupancy_state() + if((mecha_flags & SILICON_PILOT) && silicon_icon_state) + return silicon_icon_state + if(LAZYLEN(occupants)) + return base_icon_state + return "[base_icon_state]-open" + /obj/vehicle/sealed/mecha/CanPassThrough(atom/blocker, turf/target, blocker_opinion) if(!phasing || get_charge() <= phasing_energy_drain || throwing) return ..() @@ -515,18 +521,18 @@ SIGNAL_HANDLER if(!isturf(target) && !isturf(target.loc)) // Prevents inventory from being drilled return - if(completely_disabled || is_currently_ejecting) + if(completely_disabled || is_currently_ejecting || (mecha_flags & CANNOT_INTERACT)) return var/list/modifiers = params2list(params) if(isAI(user) == !LAZYACCESS(modifiers, MIDDLE_CLICK))//BASICALLY if a human uses MMB, or an AI doesn't, then do nothing. return if(phasing) - to_chat(occupants, "[icon2html(src, occupants)]Unable to interact with objects while phasing.") + balloon_alert(user, "not while [phasing]!") return if(user.incapacitated()) return if(construction_state) - to_chat(occupants, "[icon2html(src, occupants)]Maintenance protocols in effect.") + balloon_alert(user, "end maintenance first!") return if(!get_charge()) return @@ -539,22 +545,37 @@ target = pick(view(3,target)) var/mob/living/livinguser = user if(selected) + if(!(livinguser in return_controllers_with_flag(VEHICLE_CONTROL_EQUIPMENT))) + balloon_alert(user, "wrong seat for equipment!") + return if(!Adjacent(target) && (selected.range & MECHA_RANGED)) if(HAS_TRAIT(livinguser, TRAIT_PACIFISM) && selected.harmful) to_chat(livinguser, "You don't want to harm other living beings!") return + if(SEND_SIGNAL(src, COMSIG_MECHA_EQUIPMENT_CLICK, livinguser, target) & COMPONENT_CANCEL_EQUIPMENT_CLICK) + return INVOKE_ASYNC(selected, /obj/item/mecha_parts/mecha_equipment.proc/action, user, target, params) return if((selected.range & MECHA_MELEE) && Adjacent(target)) if(isliving(target) && selected.harmful && HAS_TRAIT(livinguser, TRAIT_PACIFISM)) to_chat(livinguser, "You don't want to harm other living beings!") return + if(SEND_SIGNAL(src, COMSIG_MECHA_EQUIPMENT_CLICK, livinguser, target) & COMPONENT_CANCEL_EQUIPMENT_CLICK) + return INVOKE_ASYNC(selected, /obj/item/mecha_parts/mecha_equipment.proc/action, user, target, params) return - if(TIMER_COOLDOWN_CHECK(src, COOLDOWN_MECHA_MELEE_ATTACK) || !Adjacent(target)) + if(!(livinguser in return_controllers_with_flag(VEHICLE_CONTROL_MELEE))) + to_chat(livinguser, "You're in the wrong seat to interact with your hands.") + return + var/on_cooldown = TIMER_COOLDOWN_CHECK(src, COOLDOWN_MECHA_MELEE_ATTACK) + var/adjacent = Adjacent(target) + if(SEND_SIGNAL(src, COMSIG_MECHA_MELEE_CLICK, livinguser, target, on_cooldown, adjacent) & COMPONENT_CANCEL_MELEE_CLICK) + return + if(on_cooldown || !adjacent) return if(internal_damage & MECHA_INT_CONTROL_LOST) target = pick(oview(1,src)) + target.mech_melee_attack(src, user) TIMER_COOLDOWN_START(src, COOLDOWN_MECHA_MELEE_ATTACK, melee_cooldown) @@ -604,9 +625,10 @@ return FALSE /obj/vehicle/sealed/mecha/relaymove(mob/living/user, direction) - if(canmove) - vehicle_move(direction) - return TRUE + . = TRUE + if(!canmove || !(user in return_drivers())) + return + vehicle_move(direction) /obj/vehicle/sealed/mecha/vehicle_move(direction, forcerotate = FALSE) if(!COOLDOWN_FINISHED(src, cooldown_vehicle_move)) @@ -691,7 +713,7 @@ . = ..() if(phasing) //Theres only one cause for phasing canpass fails addtimer(VARSET_CALLBACK(src, movedelay, TRUE), movedelay*3) - to_chat(occupants, "[icon2html(src, occupants)]A dull, universal force is preventing you from phasing here!") + to_chat(occupants, "[icon2html(src, occupants)]A dull, universal force is preventing you from [phasing] here!") spark_system.start() return if(.) //mech was thrown/door/whatever @@ -934,11 +956,11 @@ /obj/vehicle/sealed/mecha/generate_actions() initialize_passenger_action_type(/datum/action/vehicle/sealed/mecha/mech_eject) - initialize_passenger_action_type(/datum/action/vehicle/sealed/mecha/mech_toggle_internals) - initialize_passenger_action_type(/datum/action/vehicle/sealed/mecha/mech_cycle_equip) - initialize_passenger_action_type(/datum/action/vehicle/sealed/mecha/mech_toggle_lights) - initialize_passenger_action_type(/datum/action/vehicle/sealed/mecha/mech_view_stats) - initialize_passenger_action_type(/datum/action/vehicle/sealed/mecha/strafe) + initialize_controller_action_type(/datum/action/vehicle/sealed/mecha/mech_toggle_internals, VEHICLE_CONTROL_SETTINGS) + initialize_controller_action_type(/datum/action/vehicle/sealed/mecha/mech_cycle_equip, VEHICLE_CONTROL_EQUIPMENT) + initialize_controller_action_type(/datum/action/vehicle/sealed/mecha/mech_toggle_lights, VEHICLE_CONTROL_SETTINGS) + initialize_controller_action_type(/datum/action/vehicle/sealed/mecha/mech_view_stats, VEHICLE_CONTROL_SETTINGS) + initialize_controller_action_type(/datum/action/vehicle/sealed/mecha/strafe, VEHICLE_CONTROL_DRIVE) /obj/vehicle/sealed/mecha/proc/moved_inside(mob/living/newoccupant) if(!(newoccupant?.client)) diff --git a/code/modules/vehicles/mecha/combat/savannah_ivanov.dm b/code/modules/vehicles/mecha/combat/savannah_ivanov.dm new file mode 100644 index 00000000000..f51175b9803 --- /dev/null +++ b/code/modules/vehicles/mecha/combat/savannah_ivanov.dm @@ -0,0 +1,393 @@ +///how much time between charge_level going up by 1 +#define SKYFALL_SINGLE_CHARGE_TIME 2 SECONDS +///enough charge level to take off, basically done charging +#define SKYFALL_CHARGELEVEL_LAUNCH 5 + +///the first half of the leap, where the mech is flying upwards. +#define SKYFALL_LEAP_ARCING_UP 1 +///the second half of the leap, where the mech is flying downwards. +#define SKYFALL_LEAP_ARCING_DOWN 1 +///how much time you're in the air +#define TOTAL_SKYFALL_LEAP_TIME 3 SECONDS + +/** + * ## Savannah-Ivanov! + * + * A two person mecha that delegates moving to the driver and shooting to the pilot. + * ...Hilarious, right? + */ +/obj/vehicle/sealed/mecha/combat/savannah_ivanov + name = "\improper Savannah-Ivanov" + desc = "An insanely overbulked mecha that handily crushes single-pilot opponents. The price is that you need two pilots to use it." + icon = 'icons/mecha/coop_mech.dmi' + base_icon_state = "savannah_ivanov" + icon_state = "savannah_ivanov_0_0" + movedelay = 3 + dir_in = 2 //Facing South. + max_integrity = 450 //really tanky, like damn + deflect_chance = 25 + armor = list(MELEE = 45, BULLET = 40, LASER = 30, ENERGY = 30, BOMB = 40, BIO = 0, RAD = 80, FIRE = 100, ACID = 100) + max_temperature = 30000 + infra_luminosity = 3 + wreckage = /obj/structure/mecha_wreckage/savannah_ivanov + internal_damage_threshold = 25 + max_occupants = 2 + //no tax on flying, since the power cost is in the leap itself. + phasing_energy_drain = 0 + ///skyfall ability cooldown + COOLDOWN_DECLARE(skyfall_cooldown) + ///cooldown time between skyfall uses + var/skyfall_cooldown_time = 1 MINUTES + ///skyfall builds up in charges every 2 seconds, when it reaches 5 charges the ability actually starts + var/skyfall_charge_level = 0 + + ///ivanov strike ability cooldown + COOLDOWN_DECLARE(strike_cooldown) + ///cooldown time between strike uses + var/strike_cooldown_time = 40 SECONDS + ///toggled by ivanov strike, TRUE when signals are hooked to intercept clicks. + var/aiming_ivanov = FALSE + ///how many rockets can we send with ivanov strike + var/rockets_left = 0 + +/obj/vehicle/sealed/mecha/combat/savannah_ivanov/get_mecha_occupancy_state() + var/driver_present = driver_amount() != 0 + var/gunner_present = return_amount_of_controllers_with_flag(VEHICLE_CONTROL_EQUIPMENT) > 0 + var/leap_state = skyfall_charge_level > 2 ? "leap_" : "" + return "[base_icon_state]_[leap_state][gunner_present]_[driver_present]" + +/obj/vehicle/sealed/mecha/combat/savannah_ivanov/auto_assign_occupant_flags(mob/new_occupant) + if(driver_amount() < max_drivers) //movement + add_control_flags(new_occupant, VEHICLE_CONTROL_DRIVE|VEHICLE_CONTROL_SETTINGS) + else //weapons + add_control_flags(new_occupant, VEHICLE_CONTROL_MELEE|VEHICLE_CONTROL_EQUIPMENT) + +/obj/vehicle/sealed/mecha/combat/savannah_ivanov/generate_actions() + initialize_passenger_action_type(/datum/action/vehicle/sealed/mecha/swap_seat) + . = ..() + initialize_controller_action_type(/datum/action/vehicle/sealed/mecha/skyfall, VEHICLE_CONTROL_DRIVE) + initialize_controller_action_type(/datum/action/vehicle/sealed/mecha/ivanov_strike, VEHICLE_CONTROL_EQUIPMENT) + +/obj/vehicle/sealed/mecha/combat/savannah_ivanov/remove_occupant(mob/getting_out) + //gunner getting out ends any ivanov aiming + if(aiming_ivanov && (getting_out in return_controllers_with_flag(VEHICLE_CONTROL_EQUIPMENT))) + end_missile_targeting(getting_out) + . = ..() + +/** + * ## begin_skyfall_charge + * + * Proc called by the mecha's ability that starts the skyfall loop + */ +/obj/vehicle/sealed/mecha/combat/savannah_ivanov/proc/begin_skyfall_charge(mob/pilot) + balloon_alert(pilot, "charging skyfall...") + INVOKE_ASYNC(src, .proc/skyfall_charge_loop, pilot) + +/** + * ## skyfall_charge_loop + * + * The actual skyfall loop itself. Repeatedly calls itself after a do_after, so any interruptions will call abort_skyfall and end the loop + * the other way the loop ends is if charge level (var it's ticking up) gets to SKYFALL_CHARGELEVEL_LAUNCH, in which case it ends the loop and does the ability. + * arguments: + * * pilot: mob that activated the skyfall ability + */ +/obj/vehicle/sealed/mecha/combat/savannah_ivanov/proc/skyfall_charge_loop(mob/living/pilot) + if(!pilot || !(pilot in return_drivers()) || !do_after(pilot, SKYFALL_SINGLE_CHARGE_TIME, target = src)) + abort_skyfall(pilot) + return + skyfall_charge_level++ + switch(skyfall_charge_level) + if(1) + visible_message("[src] clicks and whirrs for a moment, with a low hum emerging from the legs.") + playsound(src, 'sound/items/rped.ogg', 50, TRUE) + if(2) + visible_message("[src] begins to shake, the sounds of electricity growing louder.") + Shake(5, 5, SKYFALL_SINGLE_CHARGE_TIME-1) // -1 gives space between the animates, so they don't interrupt eachother + if(3) + visible_message("[src] assumes a pose as it rattles violently.") + Shake(7, 7, SKYFALL_SINGLE_CHARGE_TIME-1) // -1 gives space between the animates, so they don't interrupt eachother + spark_system.start() + update_icon_state() + if(4) + visible_message("[src] sparks and shutters as it finalizes preparation.") + playsound(src, 'sound/mecha/skyfall_power_up.ogg', 50, TRUE) + Shake(10, 10, SKYFALL_SINGLE_CHARGE_TIME-1) // -1 gives space between the animates, so they don't interrupt eachother + spark_system.start() + if(SKYFALL_CHARGELEVEL_LAUNCH) + visible_message("[src] leaps into the air!") + playsound(src, 'sound/weapons/gun/general/rocket_launch.ogg', 50, TRUE) + if(skyfall_charge_level != SKYFALL_CHARGELEVEL_LAUNCH) + INVOKE_ASYNC(src, .proc/skyfall_charge_loop, pilot) + return + COOLDOWN_START(src, skyfall_cooldown, skyfall_cooldown_time) + var/datum/action/vehicle/sealed/mecha/savannah_action = occupant_actions[pilot][/datum/action/vehicle/sealed/mecha/skyfall] + savannah_action.button_icon_state = "mech_savannah_cooldown" + savannah_action.UpdateButtonIcon() + addtimer(CALLBACK(savannah_action, /datum/action/vehicle/sealed/mecha/skyfall.proc/reset_button_icon), skyfall_cooldown_time) + for(var/mob/living/shaken in range(7, src)) + shake_camera(shaken, 3, 3) + + var/turf/launch_turf = get_turf(src) + new /obj/effect/hotspot(launch_turf) + launch_turf.hotspot_expose(700, 50, 1) + new /obj/effect/skyfall_landingzone(launch_turf, src) + resistance_flags |= INDESTRUCTIBLE //not while jumping at least + mecha_flags |= QUIET_STEPS|QUIET_TURNS|CANNOT_INTERACT + phasing = "flying" + movedelay = 1 + density = FALSE + layer = FLY_LAYER + animate(src, alpha = 0, time = 8, easing = QUAD_EASING|EASE_IN, flags = ANIMATION_PARALLEL) + animate(src, pixel_z = 400, time = 10, easing = QUAD_EASING|EASE_IN, flags = ANIMATION_PARALLEL) //Animate our rising mech (just like pods hehe) + addtimer(CALLBACK(src, .proc/begin_landing, pilot), 2 SECONDS) + +/** + * ## begin_landing + * + * Called by skyfall_charge_loop after some time if it reaches full charge level. + * it's just the animations of the mecha coming down + another timer for the final landing effect + * arguments: + * * pilot: mob that activated the skyfall ability + */ +/obj/vehicle/sealed/mecha/combat/savannah_ivanov/proc/begin_landing(mob/living/pilot) + animate(src, pixel_z = 0, time = 10, easing = QUAD_EASING|EASE_IN, flags = ANIMATION_PARALLEL) + animate(src, alpha = 255, time = 8, easing = QUAD_EASING|EASE_IN, flags = ANIMATION_PARALLEL) + addtimer(CALLBACK(src, .proc/land, pilot), 10) + +/** + * ## begin_landing + * + * Called by skyfall_charge_loop after some time if it reaches full charge level. + * it's just the animations of the mecha coming down + another timer for the final landing effect + * arguments: + * * pilot: mob that activated the skyfall ability + */ +/obj/vehicle/sealed/mecha/combat/savannah_ivanov/proc/land(mob/living/pilot) + visible_message("[src] lands from above!") + playsound(src, 'sound/effects/explosion1.ogg', 50, 1) + resistance_flags &= ~INDESTRUCTIBLE + mecha_flags &= ~(QUIET_STEPS|QUIET_TURNS|CANNOT_INTERACT) + phasing = initial(phasing) + movedelay = initial(movedelay) + density = TRUE + layer = initial(layer) + skyfall_charge_level = 0 + update_icon_state() + for(var/mob/living/shaken in range(7, src)) + shake_camera(shaken, 5, 5) + var/turf/landed_on = get_turf(src) + for(var/thing in range(1, src)) + if(isopenturf(thing)) + var/turf/open/floor/crushed_tile = thing + crushed_tile.break_tile() + if(isclosedturf(thing) && thing == landed_on) + var/turf/closed/crushed_wall = thing + crushed_wall.ScrapeAway() + if(isobj(thing)) + var/obj/crushed_object = thing + if(crushed_object == src) + continue + crushed_object.take_damage(150) //same as a hulk punch, makes sense to me + if(isliving(thing)) + var/mob/living/crushed_victim = thing + if(crushed_victim in occupants) + continue + if(crushed_victim in landed_on) + to_chat(crushed_victim, "[src] crashes down on you from above!") + if(crushed_victim.stat != CONSCIOUS) + crushed_victim.gib(FALSE, FALSE, FALSE) + else + crushed_victim.adjustBruteLoss(80) + else + to_chat(crushed_victim, "The tremors from [src] landing sends you flying!") + var/fly_away_direction = get_dir(src, crushed_victim) + crushed_victim.throw_at(get_edge_target_turf(crushed_victim, fly_away_direction), 4, 3) + crushed_victim.adjustBruteLoss(15) + +/** + * ## abort_skyfall + * + * Called by skyfall_charge_loop if the charging is interrupted. + * Applies cooldown and resets charge level + * arguments: + * * pilot: mob that failed the skyfall ability + */ +/obj/vehicle/sealed/mecha/combat/savannah_ivanov/proc/abort_skyfall(mob/pilot) + if(pilot) + balloon_alert(pilot, "skyfall aborted") + COOLDOWN_START(src, skyfall_cooldown, skyfall_charge_level * 10 SECONDS) //so aborting skyfall later in the process imposes a longer cooldown + skyfall_charge_level = 0 + update_icon_state() + +/** + * ## start_missile_targeting + * + * Called by the ivanov strike datum action, hooks signals into clicking to call drop_missile + * Plus other flavor like the overlay + * arguments: + * * gunner: mob that activated the ivanov strike + * * silent: whether to send feedback messages. + */ +/obj/vehicle/sealed/mecha/combat/savannah_ivanov/proc/start_missile_targeting(mob/gunner) + balloon_alert(gunner, "missile mode on (click to target)") + aiming_ivanov = TRUE + rockets_left = 3 + RegisterSignal(src, COMSIG_MECHA_MELEE_CLICK, .proc/on_melee_click) + RegisterSignal(src, COMSIG_MECHA_EQUIPMENT_CLICK, .proc/on_equipment_click) + gunner.client.mouse_override_icon = 'icons/effects/mouse_pointers/supplypod_down_target.dmi' + gunner.update_mouse_pointer() + gunner.overlay_fullscreen("ivanov", /atom/movable/screen/fullscreen/ivanov_display, 1) + SEND_SOUND(gunner, 'sound/machines/terminal_on.ogg') //spammable so I don't want to make it audible to anyone else + +/** + * ## end_missile_targeting + * + * Called by the ivanov strike datum action or other actions that would end targetting + * Unhooks signals into clicking to call drop_missile plus other flavor like the overlay + * arguments: + * * gunner: mob that activated the ivanov strike + * * silent: whether to send feedback messages. + */ +/obj/vehicle/sealed/mecha/combat/savannah_ivanov/proc/end_missile_targeting(mob/gunner) + aiming_ivanov = FALSE + rockets_left = 0 + UnregisterSignal(src, list(COMSIG_MECHA_MELEE_CLICK, COMSIG_MECHA_EQUIPMENT_CLICK)) + gunner.client.mouse_override_icon = null + gunner.update_mouse_pointer() + gunner.clear_fullscreen("ivanov") + +///signal called from clicking with no equipment +/obj/vehicle/sealed/mecha/combat/savannah_ivanov/proc/on_melee_click(datum/source, mob/living/pilot, atom/target, on_cooldown, is_adjacent) + SIGNAL_HANDLER + if(!target) + return + drop_missile(pilot, get_turf(target)) + +///signal called from clicking with equipment +/obj/vehicle/sealed/mecha/combat/savannah_ivanov/proc/on_equipment_click(datum/source, mob/living/pilot, atom/target) + SIGNAL_HANDLER + if(!target) + return + drop_missile(pilot, get_turf(target)) + +/** + * ## drop_missile + * + * Called via intercepted clicks when the missile ability is active + * Spawns a droppod and starts the cooldown of the missile strike ability + * arguments: + * * gunner: mob that activated the ivanov strike + * * target_turf: turf of the atom that was clicked on + */ +/obj/vehicle/sealed/mecha/combat/savannah_ivanov/proc/drop_missile(mob/gunner, turf/target_turf) + rockets_left -= 1 + if(rockets_left <= 0) + end_missile_targeting(gunner) + SEND_SOUND(gunner, 'sound/machines/triple_beep.ogg') + COOLDOWN_START(src, strike_cooldown, strike_cooldown_time) + podspawn(list( + "target" = target_turf, + "style" = STYLE_MISSILE, + "effectMissile" = TRUE, + "explosionSize" = list(0,0,1,2) + )) + var/datum/action/vehicle/sealed/mecha/strike_action = occupant_actions[gunner][/datum/action/vehicle/sealed/mecha/ivanov_strike] + strike_action.button_icon_state = "mech_ivanov_cooldown" + strike_action.UpdateButtonIcon() + addtimer(CALLBACK(strike_action, /datum/action/vehicle/sealed/mecha/ivanov_strike.proc/reset_button_icon), strike_cooldown_time) + + +///Savannah Skyfall +/datum/action/vehicle/sealed/mecha/skyfall + name = "Savannah Skyfall" + button_icon_state = "mech_savannah" + +/datum/action/vehicle/sealed/mecha/skyfall/Trigger() + if(!owner || !chassis || !(owner in chassis.occupants)) + return + var/obj/vehicle/sealed/mecha/combat/savannah_ivanov/savannah_mecha = chassis + if(savannah_mecha.phasing) + to_chat(owner, "You're already airborne!") + return + if(!COOLDOWN_FINISHED(savannah_mecha, skyfall_cooldown)) + var/timeleft = COOLDOWN_TIMELEFT(savannah_mecha, skyfall_cooldown) + to_chat(owner, "You need to wait [DisplayTimeText(timeleft, 1)] before attempting to Skyfall.") + return + if(savannah_mecha.skyfall_charge_level) + savannah_mecha.abort_skyfall(owner) + return + savannah_mecha.begin_skyfall_charge(owner) + +/** + * ## reset_button_icon + * + * called after an addtimer when the cooldown is finished with the skyfall, resets the icon + */ +/datum/action/vehicle/sealed/mecha/skyfall/proc/reset_button_icon() + button_icon_state = "mech_savannah" + UpdateButtonIcon() + +/datum/action/vehicle/sealed/mecha/ivanov_strike + name = "Ivanov Strike" + button_icon_state = "mech_ivanov" + +/datum/action/vehicle/sealed/mecha/ivanov_strike/Trigger() + if(!owner || !chassis || !(owner in chassis.occupants)) + return + var/obj/vehicle/sealed/mecha/combat/savannah_ivanov/ivanov_mecha = chassis + if(!COOLDOWN_FINISHED(ivanov_mecha, strike_cooldown)) + var/timeleft = COOLDOWN_TIMELEFT(ivanov_mecha, strike_cooldown) + to_chat(owner, "You need to wait [DisplayTimeText(timeleft, 1)] before firing another Ivanov Strike.") + return + if(ivanov_mecha.aiming_ivanov) + ivanov_mecha.end_missile_targeting(owner) + else + ivanov_mecha.start_missile_targeting(owner) + +/** + * ## reset_button_icon + * + * called after an addtimer when the cooldown is finished with the ivanov strike, resets the icon + */ +/datum/action/vehicle/sealed/mecha/ivanov_strike/proc/reset_button_icon() + button_icon_state = "mech_ivanov" + UpdateButtonIcon() + +//misc effects + +///a simple indicator of where the skyfall is going to land. +/obj/effect/skyfall_landingzone + name = "Landing Zone Indicator" + desc = "A holographic projection designating the landing zone of something. It's probably best to stand back." + icon = 'icons/mob/telegraphing/telegraph_96x96.dmi' + icon_state = "target_largebox" + layer = BELOW_MOB_LAYER + pixel_x = -32 + pixel_y = -32 + alpha = 0 + ///reference to mecha following + var/obj/vehicle/sealed/mecha/combat/mecha + +/obj/effect/skyfall_landingzone/Initialize(mapload, obj/vehicle/sealed/mecha/combat/mecha) + . = ..() + src.mecha = mecha + animate(src, alpha = 255, TOTAL_SKYFALL_LEAP_TIME/2, easing = CIRCULAR_EASING|EASE_OUT) + RegisterSignal(mecha, COMSIG_MOVABLE_MOVED, .proc/follow) + QDEL_IN(src, TOTAL_SKYFALL_LEAP_TIME) //when the animations land + +/obj/effect/skyfall_landingzone/Destroy(force) + . = ..() + UnregisterSignal(mecha, COMSIG_MOVABLE_MOVED) + mecha = null + +///called when the mecha moves +/obj/effect/skyfall_landingzone/proc/follow(datum/source_mecha) + SIGNAL_HANDLER + forceMove(get_turf(source_mecha)) + +#undef SKYFALL_SINGLE_CHARGE_TIME +#undef SKYFALL_CHARGELEVEL_LAUNCH + +#undef SKYFALL_LEAP_ARCING_UP +#undef SKYFALL_LEAP_ARCING_DOWN +#undef TOTAL_SKYFALL_LEAP_TIME diff --git a/code/modules/vehicles/mecha/mech_fabricator.dm b/code/modules/vehicles/mecha/mech_fabricator.dm index 47d17130875..edc21af1d13 100644 --- a/code/modules/vehicles/mecha/mech_fabricator.dm +++ b/code/modules/vehicles/mecha/mech_fabricator.dm @@ -53,6 +53,7 @@ "Durand", "H.O.N.K", "Phazon", + "Savannah-Ivanov", "Exosuit Equipment", "Exosuit Ammunition", "Cyborg Upgrade Modules", diff --git a/code/modules/vehicles/mecha/mecha_actions.dm b/code/modules/vehicles/mecha/mecha_actions.dm index d3ba5d6f0f3..7497b8c87fb 100644 --- a/code/modules/vehicles/mecha/mecha_actions.dm +++ b/code/modules/vehicles/mecha/mecha_actions.dm @@ -33,6 +33,7 @@ /datum/action/vehicle/sealed/mecha/mech_toggle_internals/Trigger() if(!owner || !chassis || !(owner in chassis.occupants)) return + chassis.use_internal_tank = !chassis.use_internal_tank button_icon_state = "mech_internals_[chassis.use_internal_tank ? "on" : "off"]" chassis.balloon_alert(owner, "taking air from [chassis.use_internal_tank ? "internal airtank" : "environment"]") @@ -88,6 +89,7 @@ /datum/action/vehicle/sealed/mecha/mech_toggle_lights/Trigger() if(!owner || !chassis || !(owner in chassis.occupants)) return + if(!(chassis.mecha_flags & HAS_LIGHTS)) chassis.balloon_alert(owner, "the mech lights are broken!") return @@ -108,6 +110,7 @@ /datum/action/vehicle/sealed/mecha/mech_view_stats/Trigger() if(!owner || !chassis || !(owner in chassis.occupants)) return + var/datum/browser/popup = new(owner , "exosuit") popup.set_content(chassis.get_stats_html(owner)) popup.open() @@ -124,8 +127,13 @@ chassis.toggle_strafe() /obj/vehicle/sealed/mecha/AltClick(mob/living/user) - if((user in occupants) && user.canUseTopic(src)) - toggle_strafe() + if(!(user in occupants) || !user.canUseTopic(src)) + return + if(!(user in return_controllers_with_flag(VEHICLE_CONTROL_DRIVE))) + to_chat(user, "You're in the wrong seat to control movement.") + return + + toggle_strafe() /obj/vehicle/sealed/mecha/proc/toggle_strafe() if(!(mecha_flags & CANSTRAFE)) @@ -215,14 +223,14 @@ return var/new_damtype switch(chassis.damtype) - if("tox") - new_damtype = "brute" + if(TOX) + new_damtype = BRUTE chassis.balloon_alert(owner, "your punches will now deal brute damage") - if("brute") - new_damtype = "fire" + if(BRUTE) + new_damtype = BURN chassis.balloon_alert(owner, "your punches will now deal burn damage") - if("fire") - new_damtype = "tox" + if(BURN) + new_damtype = TOX chassis.balloon_alert(owner,"your punches will now deal toxin damage") chassis.damtype = new_damtype button_icon_state = "mech_damtype_[new_damtype]" @@ -236,7 +244,37 @@ /datum/action/vehicle/sealed/mecha/mech_toggle_phasing/Trigger() if(!owner || !chassis || !(owner in chassis.occupants)) return - chassis.phasing = !chassis.phasing + chassis.phasing = chassis.phasing ? "" : "phasing" button_icon_state = "mech_phasing_[chassis.phasing ? "on" : "off"]" chassis.balloon_alert(owner, "[chassis.phasing ? "enabled" : "disabled"] phasing") UpdateButtonIcon() + +///swap seats, for two person mecha +/datum/action/vehicle/sealed/mecha/swap_seat + name = "Switch Seats" + button_icon_state = "mech_seat_swap" + +/datum/action/vehicle/sealed/mecha/swap_seat/Trigger() + if(!owner || !chassis || !(owner in chassis.occupants)) + return + + if(chassis.occupants.len == chassis.max_occupants) + chassis.balloon_alert(owner, "other seat occupied!") + return + var/list/drivers = chassis.return_drivers() + chassis.balloon_alert(owner, "moving to other seat...") + chassis.is_currently_ejecting = TRUE + if(!do_after(owner, chassis.has_gravity() ? chassis.exit_delay : 0 , target = chassis)) + chassis.balloon_alert(owner, "interrupted!") + chassis.is_currently_ejecting = FALSE + return + chassis.is_currently_ejecting = FALSE + if(owner in drivers) + chassis.balloon_alert(owner, "controlling gunner seat") + chassis.remove_control_flags(owner, VEHICLE_CONTROL_DRIVE|VEHICLE_CONTROL_SETTINGS) + chassis.add_control_flags(owner, VEHICLE_CONTROL_MELEE|VEHICLE_CONTROL_EQUIPMENT) + else + chassis.balloon_alert(owner, "controlling pilot seat") + chassis.remove_control_flags(owner, VEHICLE_CONTROL_MELEE|VEHICLE_CONTROL_EQUIPMENT) + chassis.add_control_flags(owner, VEHICLE_CONTROL_DRIVE|VEHICLE_CONTROL_SETTINGS) + chassis.update_icon_state() diff --git a/code/modules/vehicles/mecha/mecha_construction_paths.dm b/code/modules/vehicles/mecha/mecha_construction_paths.dm index b8476b9cd9e..5520cbef901 100644 --- a/code/modules/vehicles/mecha/mecha_construction_paths.dm +++ b/code/modules/vehicles/mecha/mecha_construction_paths.dm @@ -1202,6 +1202,148 @@ "You slowly place the bluespace anomaly core into its socket and close its chamber.") return TRUE +//savannah_ivanov + +/datum/component/construction/unordered/mecha_chassis/savannah_ivanov + result = /datum/component/construction/mecha/savannah_ivanov + steps = list( + /obj/item/mecha_parts/part/savannah_ivanov_torso, + /obj/item/mecha_parts/part/savannah_ivanov_head, + /obj/item/mecha_parts/part/savannah_ivanov_left_arm, + /obj/item/mecha_parts/part/savannah_ivanov_right_arm, + /obj/item/mecha_parts/part/savannah_ivanov_left_leg, + /obj/item/mecha_parts/part/savannah_ivanov_right_leg + ) + +/datum/component/construction/mecha/savannah_ivanov + result = /obj/vehicle/sealed/mecha/combat/savannah_ivanov + base_icon = "savannah_ivanov" + + circuit_control = /obj/item/circuitboard/mecha/savannah_ivanov/main + circuit_periph = /obj/item/circuitboard/mecha/savannah_ivanov/peripherals + circuit_weapon = /obj/item/circuitboard/mecha/savannah_ivanov/targeting + + inner_plating = /obj/item/stack/sheet/plasteel + inner_plating_amount = 10 + + outer_plating = /obj/item/mecha_parts/part/savannah_ivanov_armor + outer_plating_amount = 1 + +/datum/component/construction/mecha/savannah_ivanov/custom_action(obj/item/I, mob/living/user, diff) + . = ..() + if(!.) + return FALSE + + switch(index) + if(1) + user.visible_message("[user] connects [parent] hydraulic systems.", "You connect [parent] hydraulic systems.") + if(2) + if(diff==FORWARD) + user.visible_message("[user] activates [parent] hydraulic systems.", "You activate [parent] hydraulic systems.") + else + user.visible_message("[user] disconnects [parent] hydraulic systems.", "You disconnect [parent] hydraulic systems.") + if(3) + if(diff==FORWARD) + user.visible_message("[user] adds the wiring to [parent].", "You add the wiring to [parent].") + else + user.visible_message("[user] deactivates [parent] hydraulic systems.", "You deactivate [parent] hydraulic systems.") + if(4) + if(diff==FORWARD) + user.visible_message("[user] adjusts the wiring of [parent].", "You adjust the wiring of [parent].") + else + user.visible_message("[user] removes the wiring from [parent].", "You remove the wiring from [parent].") + if(5) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] disconnects the wiring of [parent].", "You disconnect the wiring of [parent].") + if(6) + if(diff==FORWARD) + user.visible_message("[user] secures the mainboard.", "You secure the mainboard.") + else + user.visible_message("[user] removes the central control module from [parent].", "You remove the central computer mainboard from [parent].") + if(7) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the mainboard.", "You unfasten the mainboard.") + if(8) + if(diff==FORWARD) + user.visible_message("[user] secures the ivanov peripherals control module.", "You secure the ivanov peripherals control module.") + else + user.visible_message("[user] removes the ivanov peripherals control module from [parent].", "You remove the ivanov peripherals control module from [parent].") + if(9) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the ivanov peripherals control module.", "You unfasten the ivanov peripherals control module.") + if(10) + if(diff==FORWARD) + user.visible_message("[user] secures the ivanov weapon control module.", "You secure the ivanov weapon control module.") + else + user.visible_message("[user] removes the ivanov weapon control module from [parent].", "You remove the ivanov weapon control module from [parent].") + if(11) + if(diff==FORWARD) + user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") + else + user.visible_message("[user] unfastens the ivanov weapon control module.", "You unfasten the ivanov weapon control module.") + if(12) + if(diff==FORWARD) + user.visible_message("[user] secures the scanner module.", "You secure the scanner module.") + else + user.visible_message("[user] removes the scanner module from [parent].", "You remove the scanner module from [parent].") + if(13) + if(diff==FORWARD) + user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") + else + user.visible_message("[user] unfastens the scanner module.", "You unfasten the scanner module.") + if(14) + if(diff==FORWARD) + user.visible_message("[user] secures the capacitor.", "You secure the capacitor.") + else + user.visible_message("[user] removes the capacitor from [parent].", "You remove the capacitor from [parent].") + if(15) + if(diff==FORWARD) + user.visible_message("[user] installs [I] into [parent].", "You install [I] into [parent].") + else + user.visible_message("[user] unfastens the capacitor.", "You unfasten the capacitor.") + if(16) + if(diff==FORWARD) + user.visible_message("[user] secures the power cell.", "You secure the power cell.") + else + user.visible_message("[user] pries the power cell from [parent].", "You pry the power cell from [parent].") + if(17) + if(diff==FORWARD) + user.visible_message("[user] installs the internal armor layer to [parent].", "You install the internal armor layer to [parent].") + else + user.visible_message("[user] unfastens the power cell.", "You unfasten the power cell.") + if(18) + if(diff==FORWARD) + user.visible_message("[user] secures the internal armor layer.", "You secure the internal armor layer.") + else + user.visible_message("[user] pries internal armor layer from [parent].", "You pry internal armor layer from [parent].") + if(19) + if(diff==FORWARD) + user.visible_message("[user] welds the internal armor layer to [parent].", "You weld the internal armor layer to [parent].") + else + user.visible_message("[user] unfastens the internal armor layer.", "You unfasten the internal armor layer.") + if(20) + if(diff==FORWARD) + user.visible_message("[user] installs [I] to [parent].", "You install [I] to [parent].") + else + user.visible_message("[user] cuts the internal armor layer from [parent].", "You cut the internal armor layer from [parent].") + if(21) + if(diff==FORWARD) + user.visible_message("[user] secures Savannah-Ivanov Armor Plates.", "You secure Savannah-Ivanov Armor Plates.") + else + user.visible_message("[user] pries Savannah-Ivanov Armor Plates from [parent].", "You pry Savannah-Ivanov Armor Plates from [parent].") + if(22) + if(diff==FORWARD) + user.visible_message("[user] welds Savannah-Ivanov Armor Plates to [parent].", "You weld Savannah-Ivanov Armor Plates to [parent].") + else + user.visible_message("[user] unfastens Savannah-Ivanov Armor Plates.", "You unfasten Savannah-Ivanov Armor Plates.") + return TRUE + //ODYSSEUS /datum/component/construction/unordered/mecha_chassis/odysseus diff --git a/code/modules/vehicles/mecha/mecha_parts.dm b/code/modules/vehicles/mecha/mecha_parts.dm index 2a9a1306bb9..9d6fb629b15 100644 --- a/code/modules/vehicles/mecha/mecha_parts.dm +++ b/code/modules/vehicles/mecha/mecha_parts.dm @@ -291,6 +291,46 @@ desc="Phazon armor plates. They are layered with plasma to protect the pilot from the stress of phasing and have unusual properties." icon_state = "phazon_armor" +// Savannah-Ivanov + +/obj/item/mecha_parts/chassis/savannah_ivanov + name = "\improper Savannah-Ivanov chassis" + construct_type = /datum/component/construction/unordered/mecha_chassis/savannah_ivanov + +/obj/item/mecha_parts/part/savannah_ivanov_torso + name="\improper Savannah-Ivanov torso" + desc="A Savannah-Ivanov torso part. It's missing a huge chunk of space..." + icon_state = "savannah_ivanov_harness" + +/obj/item/mecha_parts/part/savannah_ivanov_head + name="\improper Savannah-Ivanov head" + desc="A Savannah-Ivanov head. It's sensors have been adjusted to support graceful landings." + icon_state = "savannah_ivanov_head" + +/obj/item/mecha_parts/part/savannah_ivanov_left_arm + name="\improper Savannah-Ivanov left arm" + desc="A Savannah-Ivanov left arm. Hidden rocket fabrication included in the wrists." + icon_state = "savannah_ivanov_l_arm" + +/obj/item/mecha_parts/part/savannah_ivanov_right_arm + name="\improper Savannah-Ivanov right arm" + desc="A Savannah-Ivanov left arm. Hidden rocket fabrication included in the wrists." + icon_state = "savannah_ivanov_r_arm" + +/obj/item/mecha_parts/part/savannah_ivanov_left_leg + name="\improper Savannah-Ivanov left leg" + desc="A Savannah-Ivanov left leg. In production they were designed to carry more than two passengers, so the leaping functionality was added as to not waste potential." + icon_state = "savannah_ivanov_l_leg" + +/obj/item/mecha_parts/part/savannah_ivanov_right_leg + name="\improper Savannah-Ivanov right leg" + desc="A Savannah-Ivanov left leg. In production they were designed to carry more than two passengers, so the leaping functionality was added as to not waste potential." + icon_state = "savannah_ivanov_r_leg" + +/obj/item/mecha_parts/part/savannah_ivanov_armor + name="Savannah-Ivanov armor" + desc="Savannah-Ivanov armor plates. They are uniquely shaped and reinforced to deal with the stresses of two pilots, grandiose leaps, and missiles." + icon_state = "savannah_ivanov_armor" ///////// Circuitboards @@ -379,3 +419,15 @@ /obj/item/circuitboard/mecha/clarke/main name = "Clarke Central Control module (Exosuit Board)" icon_state = "mainboard" + +/obj/item/circuitboard/mecha/savannah_ivanov/peripherals + name = "Savannah Peripherals Control module (Exosuit Board)" + icon_state = "mcontroller" + +/obj/item/circuitboard/mecha/savannah_ivanov/targeting + name = "Ivanov Weapon Control and Targeting module (Exosuit Board)" + icon_state = "mcontroller" + +/obj/item/circuitboard/mecha/savannah_ivanov/main + name = "Savannah-Ivanov Combination Control Lock module (Exosuit Board)" + icon_state = "mainboard" diff --git a/code/modules/vehicles/mecha/mecha_wreckage.dm b/code/modules/vehicles/mecha/mecha_wreckage.dm index 5d7f776c466..87afa82a3d9 100644 --- a/code/modules/vehicles/mecha/mecha_wreckage.dm +++ b/code/modules/vehicles/mecha/mecha_wreckage.dm @@ -207,7 +207,17 @@ /obj/item/mecha_parts/part/phazon_left_leg, /obj/item/mecha_parts/part/phazon_right_leg) - +/obj/structure/mecha_wreckage/savannah_ivanov + name = "\improper Savannah-Ivanov wreckage" + icon = 'icons/mecha/coop_mech.dmi' + icon_state = "savannah_ivanov-broken" + parts = list( + /obj/item/mecha_parts/part/savannah_ivanov_torso, + /obj/item/mecha_parts/part/savannah_ivanov_head, + /obj/item/mecha_parts/part/savannah_ivanov_left_arm, + /obj/item/mecha_parts/part/savannah_ivanov_right_arm, + /obj/item/mecha_parts/part/savannah_ivanov_left_leg, + /obj/item/mecha_parts/part/savannah_ivanov_right_leg) /obj/structure/mecha_wreckage/odysseus name = "\improper Odysseus wreckage" diff --git a/code/modules/vehicles/vehicle_actions.dm b/code/modules/vehicles/vehicle_actions.dm index 90427c9fad0..6b317d0e5be 100644 --- a/code/modules/vehicles/vehicle_actions.dm +++ b/code/modules/vehicles/vehicle_actions.dm @@ -1,7 +1,23 @@ //VEHICLE DEFAULT HANDLING + +/** + * ## generate_actions + * + * You override this with initialize_passenger_action_type and initialize_controller_action_type calls + * To give passengers actions when they enter the vehicle. + * Read the documentation on the aforementioned procs to learn the difference + */ /obj/vehicle/proc/generate_actions() return +/** + * ## generate_action_type + * + * A small proc to properly set up each action path. + * args: + * * actiontype: typepath of the action the proc sets up. + * returns created and set up action instance + */ /obj/vehicle/proc/generate_action_type(actiontype) var/datum/action/vehicle/A = new actiontype if(!istype(A)) @@ -9,45 +25,97 @@ A.vehicle_target = src return A +/** + * ## initialize_passenger_action_type + * + * Gives any passenger that enters the mech this action. + * They will lose it when they disembark. + * args: + * * actiontype: typepath of the action you want to give occupants. + */ /obj/vehicle/proc/initialize_passenger_action_type(actiontype) autogrant_actions_passenger += actiontype for(var/i in occupants) grant_passenger_actions(i) //refresh +/** + * ## initialize_controller_action_type + * + * Gives any passenger that enters the mech this action... IF they have the correct vehicle control flag. + * This is used so passengers cannot press buttons only drivers should have, for example. + * args: + * * actiontype: typepath of the action you want to give occupants. + */ /obj/vehicle/proc/initialize_controller_action_type(actiontype, control_flag) LAZYINITLIST(autogrant_actions_controller["[control_flag]"]) autogrant_actions_controller["[control_flag]"] += actiontype for(var/i in occupants) grant_controller_actions(i) //refresh -/obj/vehicle/proc/grant_action_type_to_mob(actiontype, mob/m) - if(isnull(LAZYACCESS(occupants, m)) || !actiontype) +/** + * ## grant_action_type_to_mob + * + * As on the tin, it does all the annoying small stuff and sanity needed + * to GRANT an action to a mob. + * args: + * * actiontype: typepath of the action you want to give to grant_to. + * * grant_to: the mob we're giving actiontype to + * returns TRUE if successfully granted + */ +/obj/vehicle/proc/grant_action_type_to_mob(actiontype, mob/grant_to) + if(isnull(LAZYACCESS(occupants, grant_to)) || !actiontype) return FALSE - LAZYINITLIST(occupant_actions[m]) - if(occupant_actions[m][actiontype]) + LAZYINITLIST(occupant_actions[grant_to]) + if(occupant_actions[grant_to][actiontype]) return TRUE var/datum/action/action = generate_action_type(actiontype) - action.Grant(m) - occupant_actions[m][action.type] = action + action.Grant(grant_to) + occupant_actions[grant_to][action.type] = action return TRUE -/obj/vehicle/proc/remove_action_type_from_mob(actiontype, mob/m) - if(isnull(LAZYACCESS(occupants, m)) || !actiontype) +/** + * ## remove_action_type_from_mob + * + * As on the tin, it does all the annoying small stuff and sanity needed + * to REMOVE an action to a mob. + * args: + * * actiontype: typepath of the action you want to give to grant_to. + * * take_from: the mob we're taking actiontype to + * returns TRUE if successfully removed + */ +/obj/vehicle/proc/remove_action_type_from_mob(actiontype, mob/take_from) + if(isnull(LAZYACCESS(occupants, take_from)) || !actiontype) return FALSE - LAZYINITLIST(occupant_actions[m]) - if(occupant_actions[m][actiontype]) - var/datum/action/action = occupant_actions[m][actiontype] - action.Remove(m) - occupant_actions[m] -= actiontype + LAZYINITLIST(occupant_actions[take_from]) + if(occupant_actions[take_from][actiontype]) + var/datum/action/action = occupant_actions[take_from][actiontype] + action.Remove(take_from) + occupant_actions[take_from] -= actiontype return TRUE -/obj/vehicle/proc/grant_passenger_actions(mob/M) +/** + * ## grant_passenger_actions + * + * Called on every passenger that enters the vehicle, goes through the list of actions it needs to give... + * and does that. + * args: + * * grant_to: mob that needs to get every action the vehicle grants + */ +/obj/vehicle/proc/grant_passenger_actions(mob/grant_to) for(var/v in autogrant_actions_passenger) - grant_action_type_to_mob(v, M) + grant_action_type_to_mob(v, grant_to) -/obj/vehicle/proc/remove_passenger_actions(mob/M) +/** + * ## remove_passenger_actions + * + * Called on every passenger that exits the vehicle, goes through the list of actions it needs to remove... + * and does that. + * args: + * * take_from: mob that needs to get every action the vehicle grants + */ +/obj/vehicle/proc/remove_passenger_actions(mob/take_from) for(var/v in autogrant_actions_passenger) - remove_action_type_from_mob(v, M) + remove_action_type_from_mob(v, take_from) /obj/vehicle/proc/grant_controller_actions(mob/M) if(!istype(M) || isnull(LAZYACCESS(occupants, M))) diff --git a/icons/hud/screen_full.dmi b/icons/hud/screen_full.dmi index 7bf58d5e2af..89be83c9b93 100644 Binary files a/icons/hud/screen_full.dmi and b/icons/hud/screen_full.dmi differ diff --git a/icons/mecha/coop_mech.dmi b/icons/mecha/coop_mech.dmi new file mode 100644 index 00000000000..2cd91ceb55c Binary files /dev/null and b/icons/mecha/coop_mech.dmi differ diff --git a/icons/mecha/mech_construct.dmi b/icons/mecha/mech_construct.dmi index a809eff594b..c25eb971f2e 100644 Binary files a/icons/mecha/mech_construct.dmi and b/icons/mecha/mech_construct.dmi differ diff --git a/icons/mecha/mech_construction.dmi b/icons/mecha/mech_construction.dmi index 558e06be01f..c0f59412335 100644 Binary files a/icons/mecha/mech_construction.dmi and b/icons/mecha/mech_construction.dmi differ diff --git a/icons/mob/actions/actions_mecha.dmi b/icons/mob/actions/actions_mecha.dmi index 72bbbad9708..943e882ac94 100644 Binary files a/icons/mob/actions/actions_mecha.dmi and b/icons/mob/actions/actions_mecha.dmi differ diff --git a/icons/mob/telegraphing/telegraph_96x96.dmi b/icons/mob/telegraphing/telegraph_96x96.dmi new file mode 100644 index 00000000000..b6264812fac Binary files /dev/null and b/icons/mob/telegraphing/telegraph_96x96.dmi differ diff --git a/icons/obj/tools.dmi b/icons/obj/tools.dmi index 63b4603fd0f..5cc32e7af07 100644 Binary files a/icons/obj/tools.dmi and b/icons/obj/tools.dmi differ diff --git a/sound/effects/angryboat.ogg b/sound/effects/angryboat.ogg new file mode 100644 index 00000000000..0cf6d1723fc Binary files /dev/null and b/sound/effects/angryboat.ogg differ diff --git a/sound/items/robofafafoggy.ogg b/sound/items/robofafafoggy.ogg new file mode 100644 index 00000000000..16d8af77fc5 Binary files /dev/null and b/sound/items/robofafafoggy.ogg differ diff --git a/sound/items/robofafafoggy2.ogg b/sound/items/robofafafoggy2.ogg new file mode 100644 index 00000000000..11bea909b6d Binary files /dev/null and b/sound/items/robofafafoggy2.ogg differ diff --git a/sound/mecha/skyfall_power_up.ogg b/sound/mecha/skyfall_power_up.ogg new file mode 100644 index 00000000000..4199c0a6129 Binary files /dev/null and b/sound/mecha/skyfall_power_up.ogg differ diff --git a/strings/clown_nonsense.json b/strings/clown_nonsense.json new file mode 100644 index 00000000000..bb565efc24d --- /dev/null +++ b/strings/clown_nonsense.json @@ -0,0 +1,38 @@ +{ + "honk": [ + "honk", + "HONK", + "HONK!", + "HONK!!!", + "HENK", + "HOOOOOOONK" + ], + + "rare": [ + "HOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONK!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!", + "damn", + ":))))))))))))))))))))))))", + " :) ", + "Hink", + "HAHAHAA HEEHEEHEE", + " Laugh sound. ", + "Extreme Ruckus at the Party" + ], + + "non-honk-clown-words": [ + "bananas", + "chapiteau", + "entrée", + "pranking", + "peel", + "banana peel", + "party", + "juggling", + "... ", + "cat", + "balagan", + "pie", + "slip" + ] + +} diff --git a/tgstation.dme b/tgstation.dme index ab1a4152b77..b59c4621ff4 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -3543,6 +3543,7 @@ #include "code\modules\vehicles\mecha\combat\marauder.dm" #include "code\modules\vehicles\mecha\combat\phazon.dm" #include "code\modules\vehicles\mecha\combat\reticence.dm" +#include "code\modules\vehicles\mecha\combat\savannah_ivanov.dm" #include "code\modules\vehicles\mecha\equipment\mecha_equipment.dm" #include "code\modules\vehicles\mecha\equipment\tools\medical_tools.dm" #include "code\modules\vehicles\mecha\equipment\tools\mining_tools.dm"