diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index f71cb0f33cf..bd9347f7094 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -214,6 +214,7 @@ #include "mapping.dm" #include "mapping_nearstation_test.dm" #include "market.dm" +#include "mecha_build.dm" #include "mecha_damage.dm" #include "medical_wounds.dm" #include "merge_type.dm" diff --git a/code/modules/unit_tests/mecha_build.dm b/code/modules/unit_tests/mecha_build.dm new file mode 100644 index 00000000000..812974f12cc --- /dev/null +++ b/code/modules/unit_tests/mecha_build.dm @@ -0,0 +1,39 @@ +/datum/unit_test/mecha_construction_icons + +/datum/unit_test/mecha_construction_icons/Run() + for(var/chassis_type in subtypesof(/obj/item/mecha_parts/chassis)) + var/obj/item/mecha_parts/chassis/chassis = allocate(chassis_type) + var/datum/component/construction/unordered/mecha_chassis/chassis_comp = chassis.GetComponent(/datum/component/construction/unordered/mecha_chassis) + if(isnull(chassis_comp)) + TEST_FAIL("[chassis_type]: Mecha chassis without a construction component") + continue + chassis_comp.spawn_result() + + var/datum/component/construction/mecha/construction_comp = chassis.GetComponent(/datum/component/construction/mecha) + if(isnull(construction_comp)) + TEST_FAIL("[chassis_type]: Finished chassis without a mech construction component") + continue + if(!QDELETED(chassis_comp)) + TEST_FAIL("[chassis_type]: Chassis construction component was not deleted after applying the mecha construction component") + continue + if(isnull(construction_comp.base_icon)) + continue // apparently valid, for construction which don't have *any* icon states + + var/list/all_chassis_icon_states = icon_states_fast(chassis.icon) + var/list/step_icon_states = list() + for(var/list/step_data as anything in construction_comp.steps) + var/icon_state = step_data["icon_state"] + if(isnull(icon_state)) + continue // valid, it just means the step doesn't change the icon + + if(!(icon_state in all_chassis_icon_states)) + TEST_FAIL("[chassis_type]: Mecha construction step has invalid icon_state '[icon_state]'") + continue + + step_icon_states += icon_state + + for(var/icon_state in all_chassis_icon_states - step_icon_states) + // little extra logic here to avoid false positives like finding "mech" in "darkmech" + if(!findtext("test-[icon_state]", "test-[construction_comp.base_icon]")) + continue + TEST_FAIL("[chassis_type]: Mecha construction has an unused icon state '[icon_state]'") diff --git a/code/modules/vehicles/mecha/mecha_construction_paths.dm b/code/modules/vehicles/mecha/mecha_construction_paths.dm index 35e394f5062..d680dc6f6ae 100644 --- a/code/modules/vehicles/mecha/mecha_construction_paths.dm +++ b/code/modules/vehicles/mecha/mecha_construction_paths.dm @@ -1,7 +1,4 @@ - -//////////////////////////////// -///// Construction datums ////// -//////////////////////////////// +/// Mecha construction /datum/component/construction/mecha var/base_icon @@ -42,17 +39,35 @@ // Override if the mech needs an entirely custom process (See HONK mech) // Otherwise override specific steps as needed (Ripley, Clarke, Phazon) /datum/component/construction/mecha/proc/get_steps() - return get_frame_steps() + get_circuit_steps() + (circuit_weapon ? get_circuit_weapon_steps() : list()) + get_stockpart_steps() + get_inner_plating_steps() + get_outer_plating_steps() + var/list/all_steps = \ + get_frame_steps() + \ + get_circuit_steps() + \ + get_circuit_weapon_steps() + \ + get_stockpart_steps() + \ + get_inner_plating_steps() + \ + get_outer_plating_steps() -/datum/component/construction/mecha/update_parent(step_index) - steps = get_steps() - ..() - // By default, each step in mech construction has a single icon_state: - // "[base_icon][index - 1]" - // For example, Ripley's step 1 icon_state is "ripley0" - var/atom/parent_atom = parent - if(!steps[index]["icon_state"] && base_icon) - parent_atom.icon_state = "[base_icon][index - 1]" + // If you don't set a construction icon state, one will automatically be assigned + // based on the index of the step in the step list + // + // If you do set a custom icon state, it will not be overridden, but the state will still increment. + // + // You can use skip_state to prevent a step from increasing the index, + // useful for steps which don't affect the mech's appearance at all + // or for steps which have custom icon states that don't follow the normal pattern. + var/state = 0 + for(var/list/step_data as anything in all_steps) + if(step_data["skip_state"]) + continue + + step_data["icon_state"] ||= "[base_icon][state]" + state += 1 + + return all_steps + +/datum/component/construction/mecha/Initialize() + steps ||= get_steps() + return ..() /datum/component/construction/unordered/mecha_chassis/custom_action(obj/item/I, mob/living/user, typepath) . = user.transferItemToLoc(I, parent) @@ -67,7 +82,7 @@ parent_atom.icon = 'icons/mob/rideables/mech_construction.dmi' parent_atom.set_density(TRUE) parent_atom.cut_overlays() - ..() + return ..() // Default proc for the first steps of mech construction. /datum/component/construction/mecha/proc/get_frame_steps() @@ -82,7 +97,7 @@ "back_key" = TOOL_WRENCH, "desc" = "The hydraulic systems are connected, and can be activated with a screwdriver.", "forward_message" = "activated the hydraulic systems", - "backward_message" = "disconnected the hydraulic systems" + "backward_message" = "disconnected the hydraulic systems", ), list( "key" = /obj/item/stack/cable_coil, @@ -90,14 +105,14 @@ "back_key" = TOOL_SCREWDRIVER, "desc" = "The hydraulic systems are active, and the frame can be wired.", "forward_message" = "added wiring", - "backward_message" = "deactivated the hydraulic systems" + "backward_message" = "deactivated the hydraulic systems", ), list( "key" = TOOL_WIRECUTTER, "back_key" = TOOL_SCREWDRIVER, "desc" = "The wiring is added, and can be adjusted with wirecutters.", "forward_message" = "adjusted wiring", - "backward_message" = "removed wiring" + "backward_message" = "removed wiring", ) ) @@ -111,14 +126,14 @@ "back_key" = TOOL_SCREWDRIVER, "desc" = "The wiring is adjusted, and the central control module slot has opened.", "forward_message" = "added central control module", - "backward_message" = "disconnected wiring" + "backward_message" = "disconnected wiring", ), list( "key" = TOOL_SCREWDRIVER, "back_key" = TOOL_CROWBAR, "desc" = "Central control module is installed, and can be screwed into place.", "forward_message" = "secured central control module", - "backward_message" = "removed central control module" + "backward_message" = "removed central control module", ), list( "key" = circuit_periph, @@ -126,38 +141,42 @@ "back_key" = TOOL_SCREWDRIVER, "desc" = "Central control module is secured, and the peripheral control module slot has opened.", "forward_message" = "added peripheral control module", - "backward_message" = "unsecured central control module" + "backward_message" = "unsecured central control module", ), list( "key" = TOOL_SCREWDRIVER, "back_key" = TOOL_CROWBAR, "desc" = "Peripheral control module is installed, and can be screwed into place.", "forward_message" = "secured peripheral control module", - "backward_message" = "removed peripheral control module" + "backward_message" = "removed peripheral control module", ) ) // Default proc for weapon circuitboard steps // Used by combat mechs /datum/component/construction/mecha/proc/get_circuit_weapon_steps() + if(!circuit_weapon) + return list() + return list( list( "key" = circuit_weapon, "action" = ITEM_DELETE, "back_key" = TOOL_SCREWDRIVER, - "desc" = "Peripherals control module is secured, and the weapon control module slot has opened.", + "desc" = "Peripherals control module is secured, and the weapon control module slot has opened.", "forward_message" = "added weapon control module", - "backward_message" = "unsecured peripheral control module" + "backward_message" = "unsecured peripheral control module", ), list( "key" = TOOL_SCREWDRIVER, "back_key" = TOOL_CROWBAR, "desc" = "Weapon control module is installed, and can be screwed into place.", "forward_message" = "secured weapon control module", - "backward_message" = "removed weapon control module" + "backward_message" = "removed weapon control module", ) ) + // Default proc for stock part installation // Third set of steps by default /datum/component/construction/mecha/proc/get_stockpart_steps() @@ -171,14 +190,14 @@ "back_key" = TOOL_SCREWDRIVER, "desc" = prevstep_text, "forward_message" = "added scanning module", - "backward_message" = backward_text + "backward_message" = backward_text, ), list( "key" = TOOL_SCREWDRIVER, "back_key" = TOOL_CROWBAR, "desc" = "Scanning module is installed, and can be screwed into place.", "forward_message" = "secured scanning module", - "backward_message" = "removed scanning module" + "backward_message" = "removed scanning module", ), list( "key" = /obj/item/stock_parts/capacitor, @@ -186,14 +205,14 @@ "back_key" = TOOL_SCREWDRIVER, "desc" = "Scanning module is secured, the capacitor can be added.", "forward_message" = "added capacitor", - "backward_message" = "unscecured scanning module" + "backward_message" = "unscecured scanning module", ), list( "key" = TOOL_SCREWDRIVER, "back_key" = TOOL_CROWBAR, "desc" = "Capacitor is installed, and can be screwed into place.", "forward_message" = "secured capacitor", - "backward_message" = "removed capacitor" + "backward_message" = "removed capacitor", ), list( "key" = /obj/item/stock_parts/servo, @@ -201,14 +220,14 @@ "back_key" = TOOL_SCREWDRIVER, "desc" = "Scanning module is secured, the servo can be added.", "forward_message" = "added servo", - "backward_message" = "unsecured capacitor" + "backward_message" = "unsecured capacitor", ), list( "key" = TOOL_SCREWDRIVER, "back_key" = TOOL_CROWBAR, "desc" = "Servo is installed, and can be screwed into place.", "forward_message" = "secured servo", - "backward_message" = "removed servo" + "backward_message" = "removed servo", ), list( "key" = /obj/item/stock_parts/power_store/cell, @@ -216,14 +235,14 @@ "back_key" = TOOL_SCREWDRIVER, "desc" = "Servo is secured, and the power cell can be added.", "forward_message" = "added power cell", - "backward_message" = "unsecured servo" + "backward_message" = "unsecured servo", ), list( "key" = TOOL_SCREWDRIVER, "back_key" = TOOL_CROWBAR, "desc" = "The power cell is installed, and can be screwed into place.", "forward_message" = "secured power cell", - "backward_message" = "removed power cell" + "backward_message" = "removed power cell", ) ) @@ -239,7 +258,7 @@ "back_key" = TOOL_SCREWDRIVER, "desc" = "The power cell is secured, [inner_plating_amount] sheets of [initial(inner_plating.name)] can be used as inner plating.", "forward_message" = "installed internal armor layer", - "backward_message" = "unsecured power cell" + "backward_message" = "unsecured power cell", ) ) else @@ -250,7 +269,7 @@ "back_key" = TOOL_SCREWDRIVER, "desc" = "The power cell is secured, [initial(inner_plating.name)] can be used as inner plating.", "forward_message" = "installed internal armor layer", - "backward_message" = "unsecured power cell" + "backward_message" = "unsecured power cell", ) ) @@ -260,14 +279,14 @@ "back_key" = TOOL_CROWBAR, "desc" = "Inner plating is installed, and can be wrenched into place.", "forward_message" = "secured internal armor layer", - "backward_message" = "pried off internal armor layer" + "backward_message" = "pried off internal armor layer", ), list( "key" = TOOL_WELDER, "back_key" = TOOL_WRENCH, "desc" = "Inner plating is wrenched, and can be welded.", "forward_message" = "welded internal armor layer", - "backward_message" = "unfastened internal armor layer" + "backward_message" = "unfastened internal armor layer", ) ) @@ -283,7 +302,7 @@ "back_key" = TOOL_WELDER, "desc" = "Inner plating is welded, [outer_plating_amount] sheets of [initial(outer_plating.name)] can be used as external armor.", "forward_message" = "installed external armor layer", - "backward_message" = "cut off internal armor layer" + "backward_message" = "cut off internal armor layer", ) ) else @@ -294,7 +313,7 @@ "back_key" = TOOL_WELDER, "desc" = "Inner plating is welded, [initial(outer_plating.name)] can be used as external armor.", "forward_message" = "installed external armor layer", - "backward_message" = "cut off internal armor layer" + "backward_message" = "cut off internal armor layer", ) ) @@ -304,14 +323,14 @@ "back_key" = TOOL_CROWBAR, "desc" = "External armor is installed, and can be wrenched into place.", "forward_message" = "secured external armor layer", - "backward_message" = "pried off external armor layer" + "backward_message" = "pried off external armor layer", ), list( "key" = TOOL_WELDER, "back_key" = TOOL_WRENCH, "desc" = "External armor is wrenched, and can be welded.", "forward_message" = "welded external armor layer", - "backward_message" = "unfastened external armor layer" + "backward_message" = "unfastened external armor layer", ) ) @@ -322,8 +341,15 @@ if(diff == FORWARD && steps[index]["forward_message"]) user.balloon_alert_to_viewers(steps[index]["forward_message"]) + var/list/next_step = index + 1 == steps.len ? null : steps[index + 1] + if(next_step?["desc"]) + to_chat(user, span_smallnoticeital(next_step["desc"])) + else if(steps[index]["backward_message"]) user.balloon_alert_to_viewers(steps[index]["backward_message"]) + var/list/last_step = index <= 1 ? null : steps[index - 1] + if(last_step?["desc"]) + to_chat(user, span_smallnoticeital(last_step["desc"])) return TRUE @@ -352,22 +378,22 @@ outer_plating_amount = 10 /datum/component/construction/mecha/ripley/get_outer_plating_steps() + // we yoink the first step of adding plating and modify the flavor a bit + var/list/first_step = ..()[1] + first_step["desc"] = "Plating is welded, and 10 rods can be used to install the cockpit." + first_step["forward_message"] = "installed cockpit" + first_step["backward_message"] = "cut off plating" + + // then we add our own second step for welding the cockpit in place return list( - list( - "key" = /obj/item/stack/rods, - "amount" = 10, - "back_key" = TOOL_WELDER, - "desc" = "Outer plating is welded, and 10 rods can be used to install the cockpit.", - "forward_message" = "installed cockpit", - "backward_message" = "cut off outer armor layer" - ), + first_step, list( "key" = TOOL_WELDER, "back_key" = TOOL_WIRECUTTER, "desc" = "Cockpit wire screen is installed, and can be welded.", "forward_message" = "welded cockpit", - "backward_message" = "cut off cockpit" - ), + "backward_message" = "cut off cockpit", + ) ) //GYGAX @@ -584,7 +610,7 @@ var/atom/parent_atom = parent parent_atom.icon = 'icons/mob/rideables/mech_construct.dmi' parent_atom.icon_state = "honker_chassis" - ..() + return ..() /datum/component/construction/mecha/honker/custom_action(obj/item/I, mob/living/user, diff) if(istype(I, /obj/item/bikehorn)) @@ -646,6 +672,18 @@ outer_plating = /obj/item/mecha_parts/part/phazon_armor outer_plating_amount = 1 + var/obj/item/required_core = /obj/item/assembly/signaler/anomaly/ectoplasm + +/datum/component/construction/mecha/phazon/custom_action(obj/item/I, mob/living/user, diff) + if(!..()) + return FALSE + + if(istype(I, /obj/item/assembly/signaler/anomaly) && !istype(I, required_core)) + to_chat(user, span_warning("The anomaly core socket only accepts \a [initial(required_core.name)]!")) + return FALSE + + return TRUE + /datum/component/construction/mecha/phazon/get_stockpart_steps() return list( list( @@ -654,14 +692,14 @@ "back_key" = TOOL_SCREWDRIVER, "desc" = "Weapon control module is secured, and the scanning module can be added.", "forward_message" = "added scanning module", - "backward_message" = "unsecured weapon control module" + "backward_message" = "unsecured weapon control module", ), list( "key" = TOOL_SCREWDRIVER, "back_key" = TOOL_CROWBAR, "desc" = "Scanning module is installed, and can be screwed into place.", "forward_message" = "secured scanning module", - "backward_message" = "removed scanning module" + "backward_message" = "removed scanning module", ), list( "key" = /obj/item/stock_parts/capacitor, @@ -669,14 +707,14 @@ "back_key" = TOOL_SCREWDRIVER, "desc" = "Scanning module is secured, and the capacitor can be added.", "forward_message" = "added capacitor", - "backward_message" = "unsecured scanning module" + "backward_message" = "unsecured scanning module", ), list( "key" = TOOL_SCREWDRIVER, "back_key" = TOOL_CROWBAR, "desc" = "Capacitor is installed, and can be screwed into place.", "forward_message" = "secured capacitor", - "backward_message" = "removed capacitor" + "backward_message" = "removed capacitor", ), list( "key" = /obj/item/stock_parts/servo, @@ -684,14 +722,14 @@ "back_key" = TOOL_SCREWDRIVER, "desc" = "Capacitor is secured, the servo can be added.", "forward_message" = "added servo", - "backward_message" = "unsecured capacitor" + "backward_message" = "unsecured capacitor", ), list( "key" = TOOL_SCREWDRIVER, "back_key" = TOOL_CROWBAR, "desc" = "Servo is installed, and can be screwed into place.", "forward_message" = "secured servo", - "backward_message" = "removed servo" + "backward_message" = "removed servo", ), list( "key" = /obj/item/stack/ore/bluespace_crystal, @@ -699,7 +737,7 @@ "back_key" = TOOL_SCREWDRIVER, "desc" = "Servo is secured, and the bluespace crystal can be added.", "forward_message" = "added bluespace crystal", - "backward_message" = "unsecured servo" + "backward_message" = "unsecured servo", ), list( "key" = /obj/item/stack/cable_coil, @@ -707,14 +745,16 @@ "back_key" = TOOL_CROWBAR, "desc" = "The bluespace crystal is installed, and can be wired to the mech systems.", "forward_message" = "connected bluespace crystal", - "backward_message" = "removed bluespace crystal" + "backward_message" = "removed bluespace crystal", + "icon_state" = "phazon19", ), list( "key" = TOOL_SCREWDRIVER, "back_key" = TOOL_WIRECUTTER, "desc" = "The bluespace crystal is connected, and the system can be engaged with a screwdriver.", "forward_message" = "engaded bluespace crystal", - "backward_message" = "disconnected bluespace crystal" + "backward_message" = "disconnected bluespace crystal", + "icon_state" = "phazon20", ), list( "key" = /obj/item/stock_parts/power_store/cell, @@ -722,52 +762,29 @@ "back_key" = TOOL_SCREWDRIVER, "desc" = "The bluespace crystal is engaged, and the power cell can be added.", "forward_message" = "added power cell", - "backward_message" = "disengaged bluespace crystal" + "backward_message" = "disengaged bluespace crystal", + "icon_state" = "phazon21", ), list( "key" = TOOL_SCREWDRIVER, "back_key" = TOOL_CROWBAR, - "desc" = "The power cell is installed, and can be screwed into place.",, + "desc" = "The power cell is installed, and can be screwed into place.", "forward_message" = "secured power cell", "backward_message" = "removed power cell", - "icon_state" = "phazon19" - // This is the point where a step icon is skipped, so "icon_state" had to be set manually starting from here. + "icon_state" = "phazon21", ) ) /datum/component/construction/mecha/phazon/get_outer_plating_steps() - return list( + return ..() + list( list( - "key" = outer_plating, - "amount" = 1, + "key" = required_core, "action" = ITEM_DELETE, "back_key" = TOOL_WELDER, - "desc" = "Internal armor is welded, [initial(outer_plating.name)] can be used as external armor.", - "forward_message" = "added external armor layer", - "backward_message" = "cut off internal armor layer" - ), - list( - "key" = TOOL_WRENCH, - "back_key" = TOOL_CROWBAR, - "desc" = "External armor is installed, and can be wrenched into place.", - "forward_message" = "secured external armor layer", - "backward_message" = "pried off external armor" - ), - list( - "key" = TOOL_WELDER, - "back_key" = TOOL_WRENCH, - "desc" = "External armor is wrenched, and can be welded.", - "forward_message" = "welded external armor", - "backward_message" = "unfastened external armor layer" - ), - list( - "key" = /obj/item/assembly/signaler/anomaly/ectoplasm, - "action" = ITEM_DELETE, - "back_key" = TOOL_WELDER, - "desc" = "The external armor is welded, and the ectoplasm anomaly core socket is open.", - "icon_state" = "phazon26", - "forward_message" = "inserted ectoplasm anomaly core", - "backward_message" = "cut off external armor" + "desc" = "The external armor is welded, and the [initial(required_core.name)] socket is open.", + "forward_message" = "inserted [initial(required_core.name)]", + "backward_message" = "cut off external armor", + "skip_state" = TRUE, ) ) diff --git a/code/modules/vehicles/mecha/mecha_parts.dm b/code/modules/vehicles/mecha/mecha_parts.dm index e0a31c762da..e04d7bc2d14 100644 --- a/code/modules/vehicles/mecha/mecha_parts.dm +++ b/code/modules/vehicles/mecha/mecha_parts.dm @@ -255,12 +255,6 @@ name = "\improper Phazon chassis" construct_type = /datum/component/construction/unordered/mecha_chassis/phazon -/obj/item/mecha_parts/chassis/phazon/item_interaction(mob/living/user, obj/item/tool, list/modifiers) - if(istype(tool, /obj/item/assembly/signaler/anomaly) && !istype(tool, /obj/item/assembly/signaler/anomaly/ectoplasm)) - to_chat(user, "The anomaly core socket only accepts ectoplasm anomaly cores!") - return ITEM_INTERACT_BLOCKING - return ..() - /obj/item/mecha_parts/part/phazon_torso name="\improper Phazon torso" desc="A Phazon torso part. The socket for the ectoplasmic core that powers the exosuit's unique phase drives is located in the middle." diff --git a/icons/mob/rideables/mech_construction.dmi b/icons/mob/rideables/mech_construction.dmi index f26dbe17fd0..bee082ad6f9 100644 Binary files a/icons/mob/rideables/mech_construction.dmi and b/icons/mob/rideables/mech_construction.dmi differ