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