Compare commits
5 Commits
e8dd73bcb0
...
config
| Author | SHA1 | Date | |
|---|---|---|---|
| c44936d1fe | |||
| 68c1345660 | |||
| dbf334c829 | |||
| f225c1330e | |||
| fba98c928f |
@@ -1,122 +1,36 @@
|
|||||||
[[module]]
|
# modules.toml
|
||||||
id = "armor_plate"
|
#
|
||||||
unlock_at_station_level = -1
|
# First real-content iteration: module ids and surface masks are the designed
|
||||||
surface_mask = ["OO"]
|
# content; stats, materials, and threat costs are placeholders until the
|
||||||
materials = [{item = "armor_plate_module", amount = 1}]
|
# recipe and balancing passes.
|
||||||
player_production_level = 1
|
#
|
||||||
production_time_seconds = 3
|
# Surface mask footprint ladder — footprints gate which hulls can mount a
|
||||||
fill_color = "#808080"
|
# module, purely through geometry (see ships.toml for the matching hull
|
||||||
glyph = "A"
|
# grids):
|
||||||
|
#
|
||||||
[module.health]
|
# 1x1 laser_cannon_s, salvager, repair_tool fits every hull, incl. drones
|
||||||
added_hp_formula = "40"
|
# 1x2 maneuvering_thrusters, sensor_booster,
|
||||||
|
# armor_plates frigate and up
|
||||||
|
# 1x3 afterburner frigate and up (eats most of a frigate)
|
||||||
|
# L-shape weapon_stabilizer, weapon_primer,
|
||||||
|
# weapon_upgrade frigate and up
|
||||||
|
# 2x2 laser_cannon_m, drone_bay cruiser and up (no 2x2 area on s hulls)
|
||||||
|
# 3x3 laser_cannon_l battleship and up (no 3x3 area on m hulls)
|
||||||
|
# 2x6 drone_hangar carrier only
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Weapons
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
[[module]]
|
[[module]]
|
||||||
id = "sensor_booster"
|
id = "laser_cannon_s"
|
||||||
unlock_at_station_level = -1
|
unlock_at_station_level = -1
|
||||||
surface_mask = ["O"]
|
surface_mask = ["O"]
|
||||||
materials = [{item = "sensor_booster_module", amount = 1}]
|
materials = [{item = "laser_cannon_s_module", amount = 1}]
|
||||||
player_production_level = 1
|
|
||||||
production_time_seconds = 2
|
|
||||||
fill_color = "#40A0FF"
|
|
||||||
glyph = "S"
|
|
||||||
|
|
||||||
[module.sensor]
|
|
||||||
added_sensor_range_m_formula = "50"
|
|
||||||
|
|
||||||
|
|
||||||
[[module]]
|
|
||||||
id = "manuvering_thrusters"
|
|
||||||
unlock_at_station_level = -1
|
|
||||||
surface_mask = ["O"]
|
|
||||||
materials = [{item = "manuvering_thrusters_module", amount = 1}]
|
|
||||||
player_production_level = 1
|
|
||||||
production_time_seconds = 2
|
|
||||||
fill_color = "#40A0FF"
|
|
||||||
glyph = "Mt"
|
|
||||||
|
|
||||||
[module.movement]
|
|
||||||
multiplied_speed_mps_formula = "1.2"
|
|
||||||
added_maneuvering_acceleration_mpss_formula = "10"
|
|
||||||
|
|
||||||
|
|
||||||
[[module]]
|
|
||||||
id = "afterburner"
|
|
||||||
unlock_at_station_level = -1
|
|
||||||
surface_mask = ["O"]
|
|
||||||
materials = [{item = "afterburner_module", amount = 1}]
|
|
||||||
player_production_level = 1
|
|
||||||
production_time_seconds = 2
|
|
||||||
fill_color = "#40A0FF"
|
|
||||||
glyph = "Ab"
|
|
||||||
|
|
||||||
[module.movement]
|
|
||||||
multiplied_speed_mps_formula = "1.6"
|
|
||||||
added_main_acceleration_mpss_formula = "60"
|
|
||||||
|
|
||||||
|
|
||||||
[[module]]
|
|
||||||
id = "weapon_upgrade"
|
|
||||||
unlock_at_station_level = -1
|
|
||||||
surface_mask = [
|
|
||||||
"OO",
|
|
||||||
"O ",
|
|
||||||
]
|
|
||||||
materials = [{item = "weapon_upgrade_module", amount = 1}]
|
|
||||||
player_production_level = 1
|
|
||||||
production_time_seconds = 4
|
|
||||||
fill_color = "#FF4040"
|
|
||||||
glyph = "Wu"
|
|
||||||
|
|
||||||
[module.weapon]
|
|
||||||
multiplied_damage_formula = "1.2"
|
|
||||||
|
|
||||||
|
|
||||||
[[module]]
|
|
||||||
id = "weapon_primer"
|
|
||||||
unlock_at_station_level = -1
|
|
||||||
surface_mask = [
|
|
||||||
"OO",
|
|
||||||
"O ",
|
|
||||||
]
|
|
||||||
materials = [{item = "weapon_primer_module", amount = 1}]
|
|
||||||
player_production_level = 1
|
|
||||||
production_time_seconds = 4
|
|
||||||
fill_color = "#FF4040"
|
|
||||||
glyph = "Wp"
|
|
||||||
|
|
||||||
[module.weapon]
|
|
||||||
multiplied_attack_rate_hz_formula = "1.2"
|
|
||||||
|
|
||||||
|
|
||||||
[[module]]
|
|
||||||
id = "weapon_stabilizer"
|
|
||||||
unlock_at_station_level = -1
|
|
||||||
surface_mask = [
|
|
||||||
"OO",
|
|
||||||
"O ",
|
|
||||||
]
|
|
||||||
materials = [{item = "weapon_stabilizer_module", amount = 1}]
|
|
||||||
player_production_level = 1
|
|
||||||
production_time_seconds = 4
|
|
||||||
fill_color = "#FF4040"
|
|
||||||
glyph = "Ws"
|
|
||||||
|
|
||||||
[module.weapon]
|
|
||||||
multiplied_attack_range_m_formula = "1.5"
|
|
||||||
multiplied_attack_rate_hz_formula = "0.8"
|
|
||||||
|
|
||||||
|
|
||||||
[[module]]
|
|
||||||
id = "laser_cannon_xs"
|
|
||||||
unlock_at_station_level = -1
|
|
||||||
surface_mask = ["O"]
|
|
||||||
materials = [{item = "iron_ore", amount = 1}]
|
|
||||||
player_production_level = 1
|
player_production_level = 1
|
||||||
production_time_seconds = 0.5
|
production_time_seconds = 0.5
|
||||||
fill_color = "#FF8040"
|
fill_color = "#FF8040"
|
||||||
glyph = "L"
|
glyph = "Ls"
|
||||||
|
|
||||||
[module.weapon]
|
[module.weapon]
|
||||||
damage_formula = "2"
|
damage_formula = "2"
|
||||||
@@ -125,16 +39,16 @@ attack_rate_hz_formula = "2.0"
|
|||||||
|
|
||||||
|
|
||||||
[[module]]
|
[[module]]
|
||||||
id = "laser_cannon_s"
|
id = "laser_cannon_m"
|
||||||
unlock_at_station_level = -1
|
unlock_at_station_level = -1
|
||||||
surface_mask = [
|
surface_mask = [
|
||||||
"OO",
|
"OO",
|
||||||
"OO"]
|
"OO"]
|
||||||
materials = [{item = "laser_cannon_s_module", amount = 1}]
|
materials = [{item = "laser_cannon_m_module", amount = 1}]
|
||||||
player_production_level = 1
|
player_production_level = 1
|
||||||
production_time_seconds = 0.5
|
production_time_seconds = 2
|
||||||
fill_color = "#FF8040"
|
fill_color = "#FF8040"
|
||||||
glyph = "L"
|
glyph = "Lm"
|
||||||
|
|
||||||
[module.weapon]
|
[module.weapon]
|
||||||
damage_formula = "10"
|
damage_formula = "10"
|
||||||
@@ -142,6 +56,28 @@ attack_range_m_formula = "70"
|
|||||||
attack_rate_hz_formula = "1.5"
|
attack_rate_hz_formula = "1.5"
|
||||||
|
|
||||||
|
|
||||||
|
[[module]]
|
||||||
|
id = "laser_cannon_l"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
surface_mask = [
|
||||||
|
"OOO",
|
||||||
|
"OOO",
|
||||||
|
"OOO"]
|
||||||
|
materials = [{item = "laser_cannon_l_module", amount = 1}]
|
||||||
|
player_production_level = 1
|
||||||
|
production_time_seconds = 8
|
||||||
|
fill_color = "#FF8040"
|
||||||
|
glyph = "Ll"
|
||||||
|
|
||||||
|
[module.weapon]
|
||||||
|
damage_formula = "40"
|
||||||
|
attack_range_m_formula = "100"
|
||||||
|
attack_rate_hz_formula = "0.8"
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Utility tools
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
[[module]]
|
[[module]]
|
||||||
id = "salvager"
|
id = "salvager"
|
||||||
unlock_at_station_level = -1
|
unlock_at_station_level = -1
|
||||||
@@ -171,3 +107,154 @@ glyph = "Rp"
|
|||||||
[module.repair]
|
[module.repair]
|
||||||
repair_rate_hz_formula = "5 + x"
|
repair_rate_hz_formula = "5 + x"
|
||||||
repair_range_m_formula = "800"
|
repair_range_m_formula = "800"
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Propulsion
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
[[module]]
|
||||||
|
id = "afterburner"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
surface_mask = ["OOO"]
|
||||||
|
materials = [{item = "afterburner_module", amount = 1}]
|
||||||
|
player_production_level = 1
|
||||||
|
production_time_seconds = 2
|
||||||
|
fill_color = "#40A0FF"
|
||||||
|
glyph = "Ab"
|
||||||
|
|
||||||
|
[module.movement]
|
||||||
|
multiplied_speed_mps_formula = "1.6"
|
||||||
|
added_main_acceleration_mpss_formula = "60"
|
||||||
|
|
||||||
|
|
||||||
|
[[module]]
|
||||||
|
id = "maneuvering_thrusters"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
surface_mask = ["OO"]
|
||||||
|
materials = [{item = "maneuvering_thrusters_module", amount = 1}]
|
||||||
|
player_production_level = 1
|
||||||
|
production_time_seconds = 2
|
||||||
|
fill_color = "#40A0FF"
|
||||||
|
glyph = "Mt"
|
||||||
|
|
||||||
|
[module.movement]
|
||||||
|
multiplied_speed_mps_formula = "1.2"
|
||||||
|
added_maneuvering_acceleration_mpss_formula = "10"
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Defense & sensors
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
[[module]]
|
||||||
|
id = "armor_plates"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
surface_mask = ["OO"]
|
||||||
|
materials = [{item = "armor_plates_module", amount = 1}]
|
||||||
|
player_production_level = 1
|
||||||
|
production_time_seconds = 3
|
||||||
|
fill_color = "#808080"
|
||||||
|
glyph = "A"
|
||||||
|
|
||||||
|
[module.health]
|
||||||
|
added_hp_formula = "40"
|
||||||
|
|
||||||
|
|
||||||
|
[[module]]
|
||||||
|
id = "sensor_booster"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
surface_mask = ["OO"]
|
||||||
|
materials = [{item = "sensor_booster_module", amount = 1}]
|
||||||
|
player_production_level = 1
|
||||||
|
production_time_seconds = 2
|
||||||
|
fill_color = "#40A0FF"
|
||||||
|
glyph = "S"
|
||||||
|
|
||||||
|
[module.sensor]
|
||||||
|
added_sensor_range_m_formula = "50"
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Weapon modifiers
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
[[module]]
|
||||||
|
id = "weapon_upgrade"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
surface_mask = [
|
||||||
|
"OO",
|
||||||
|
"OX",
|
||||||
|
]
|
||||||
|
materials = [{item = "weapon_upgrade_module", amount = 1}]
|
||||||
|
player_production_level = 1
|
||||||
|
production_time_seconds = 4
|
||||||
|
fill_color = "#FF4040"
|
||||||
|
glyph = "Wu"
|
||||||
|
|
||||||
|
[module.weapon]
|
||||||
|
multiplied_damage_formula = "1.2"
|
||||||
|
|
||||||
|
|
||||||
|
[[module]]
|
||||||
|
id = "weapon_primer"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
surface_mask = [
|
||||||
|
"OO",
|
||||||
|
"OX",
|
||||||
|
]
|
||||||
|
materials = [{item = "weapon_primer_module", amount = 1}]
|
||||||
|
player_production_level = 1
|
||||||
|
production_time_seconds = 4
|
||||||
|
fill_color = "#FF4040"
|
||||||
|
glyph = "Wp"
|
||||||
|
|
||||||
|
[module.weapon]
|
||||||
|
multiplied_attack_rate_hz_formula = "1.2"
|
||||||
|
|
||||||
|
|
||||||
|
[[module]]
|
||||||
|
id = "weapon_stabilizer"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
surface_mask = [
|
||||||
|
"OO",
|
||||||
|
"OX",
|
||||||
|
]
|
||||||
|
materials = [{item = "weapon_stabilizer_module", amount = 1}]
|
||||||
|
player_production_level = 1
|
||||||
|
production_time_seconds = 4
|
||||||
|
fill_color = "#FF4040"
|
||||||
|
glyph = "Ws"
|
||||||
|
|
||||||
|
[module.weapon]
|
||||||
|
multiplied_attack_range_m_formula = "1.5"
|
||||||
|
multiplied_attack_rate_hz_formula = "0.8"
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Drone modules
|
||||||
|
#
|
||||||
|
# Footprint-only placeholders: the drone launching capability is not
|
||||||
|
# implemented yet, so these modules define no capability section.
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
[[module]]
|
||||||
|
id = "drone_bay"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
surface_mask = [
|
||||||
|
"OO",
|
||||||
|
"OO"]
|
||||||
|
materials = [{item = "drone_bay_module", amount = 1}]
|
||||||
|
player_production_level = 1
|
||||||
|
production_time_seconds = 5
|
||||||
|
fill_color = "#CC66FF"
|
||||||
|
glyph = "Db"
|
||||||
|
|
||||||
|
|
||||||
|
[[module]]
|
||||||
|
id = "drone_hangar"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
surface_mask = [
|
||||||
|
"OOOOOO",
|
||||||
|
"OOOOOO"]
|
||||||
|
materials = [{item = "drone_hangar_module", amount = 1}]
|
||||||
|
player_production_level = 1
|
||||||
|
production_time_seconds = 20
|
||||||
|
fill_color = "#9933CC"
|
||||||
|
glyph = "Dh"
|
||||||
|
|||||||
@@ -1,3 +1,29 @@
|
|||||||
|
# recipes.toml
|
||||||
|
#
|
||||||
|
# First real-content iteration of the production tree. Quantities and
|
||||||
|
# durations are a first guess; the balancing pass will tune them and assign
|
||||||
|
# real unlock_at_station_level values (everything is unlocked for now so the
|
||||||
|
# full tree is testable).
|
||||||
|
#
|
||||||
|
# Input chain per game phase — each phase adds exactly one new base input:
|
||||||
|
#
|
||||||
|
# early iron_ore + copper_ore -> ingots -> copper_wire, steel_plate,
|
||||||
|
# circuit_board
|
||||||
|
# mid + titanium_ore -> titanium_frame; assembler-made
|
||||||
|
# mechanical_parts, targeting_unit,
|
||||||
|
# drive_unit
|
||||||
|
# late + advanced_alloy -> reinforced_plating, capital_core.
|
||||||
|
# advanced_alloy CANNOT be mined; it only
|
||||||
|
# comes from reprocessing salvaged scrap,
|
||||||
|
# so capital production requires combat.
|
||||||
|
#
|
||||||
|
# Run tools/verify_recipes.py after editing to check that every consumed
|
||||||
|
# item has a producer and every item has a visuals.toml entry.
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Mining (tier 0)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
[[recipe]]
|
[[recipe]]
|
||||||
id = "mine_iron_ore"
|
id = "mine_iron_ore"
|
||||||
building = "miner"
|
building = "miner"
|
||||||
@@ -12,6 +38,18 @@ inputs = []
|
|||||||
outputs = [{item = "copper_ore", amount = 1}]
|
outputs = [{item = "copper_ore", amount = 1}]
|
||||||
duration_seconds = 1.5
|
duration_seconds = 1.5
|
||||||
|
|
||||||
|
# Titanium is the midgame ore: mined three times slower than iron.
|
||||||
|
[[recipe]]
|
||||||
|
id = "mine_titanium_ore"
|
||||||
|
building = "miner"
|
||||||
|
inputs = []
|
||||||
|
outputs = [{item = "titanium_ore", amount = 1}]
|
||||||
|
duration_seconds = 3.0
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Smelting (tier 1)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
[[recipe]]
|
[[recipe]]
|
||||||
id = "iron_ingot"
|
id = "iron_ingot"
|
||||||
building = "smelter"
|
building = "smelter"
|
||||||
@@ -27,54 +65,17 @@ outputs = [{item = "copper_ingot", amount = 1}]
|
|||||||
duration_seconds = 2.5
|
duration_seconds = 2.5
|
||||||
|
|
||||||
[[recipe]]
|
[[recipe]]
|
||||||
id = "circuit_board"
|
id = "titanium_ingot"
|
||||||
building = "assembler"
|
building = "smelter"
|
||||||
inputs = [{item = "iron_ingot", amount = 1}, {item = "copper_ingot", amount = 2}]
|
inputs = [{item = "titanium_ore", amount = 3}]
|
||||||
outputs = [{item = "circuit_board", amount = 1}]
|
outputs = [{item = "titanium_ingot", amount = 1}]
|
||||||
duration_seconds = 2.0
|
|
||||||
|
|
||||||
[[recipe]]
|
|
||||||
id = "drone_hull"
|
|
||||||
building = "assembler"
|
|
||||||
inputs = [{item = "iron_ingot", amount = 5}, {item = "circuit_board", amount = 1}]
|
|
||||||
outputs = [{item = "drone_hull", amount = 1}]
|
|
||||||
duration_seconds = 4.0
|
duration_seconds = 4.0
|
||||||
|
|
||||||
[[recipe]]
|
# -----------------------------------------------------------------------------
|
||||||
id = "laser_cannon_xs_module"
|
# Reprocessing
|
||||||
building = "assembler"
|
#
|
||||||
inputs = [{item = "iron_ingot", amount = 2}, {item = "circuit_board", amount = 1}]
|
# The only source of advanced_alloy: salvaged scrap from destroyed ships.
|
||||||
outputs = [{item = "laser_cannon_xs_module", amount = 1}]
|
# -----------------------------------------------------------------------------
|
||||||
duration_seconds = 3.0
|
|
||||||
|
|
||||||
[[recipe]]
|
|
||||||
id = "laser_cannon_s_module"
|
|
||||||
building = "assembler"
|
|
||||||
inputs = [{item = "iron_ingot", amount = 4}, {item = "circuit_board", amount = 2}]
|
|
||||||
outputs = [{item = "laser_cannon_s_module", amount = 1}]
|
|
||||||
duration_seconds = 6.0
|
|
||||||
|
|
||||||
[[recipe]]
|
|
||||||
id = "salvager_module"
|
|
||||||
building = "assembler"
|
|
||||||
inputs = [{item = "iron_ingot", amount = 1}, {item = "circuit_board", amount = 1}]
|
|
||||||
outputs = [{item = "salvager_module", amount = 1}]
|
|
||||||
duration_seconds = 6.0
|
|
||||||
|
|
||||||
[[recipe]]
|
|
||||||
id = "repair_tool_module"
|
|
||||||
building = "assembler"
|
|
||||||
inputs = [{item = "iron_ingot", amount = 1}, {item = "circuit_board", amount = 2}]
|
|
||||||
outputs = [{item = "repair_tool_module", amount = 1}]
|
|
||||||
duration_seconds = 6.0
|
|
||||||
|
|
||||||
[[recipe]]
|
|
||||||
id = "building_blocks"
|
|
||||||
unlock_at_station_level = -1
|
|
||||||
building = "assembler"
|
|
||||||
inputs = [{item = "iron_ingot", amount = 4}]
|
|
||||||
outputs = [{item = "building_block", amount = 10}]
|
|
||||||
duration_seconds = 4.0
|
|
||||||
|
|
||||||
[[recipe]]
|
[[recipe]]
|
||||||
id = "reprocessing_cycle"
|
id = "reprocessing_cycle"
|
||||||
@@ -85,15 +86,354 @@ duration_seconds = 3.0
|
|||||||
[[recipe.outputs]]
|
[[recipe.outputs]]
|
||||||
item = "iron_ingot"
|
item = "iron_ingot"
|
||||||
amount = 2
|
amount = 2
|
||||||
probability = 0.6
|
probability = 0.45
|
||||||
|
|
||||||
[[recipe.outputs]]
|
[[recipe.outputs]]
|
||||||
item = "circuit_board"
|
item = "copper_ingot"
|
||||||
amount = 1
|
amount = 1
|
||||||
probability = 0.3
|
probability = 0.25
|
||||||
|
|
||||||
|
[[recipe.outputs]]
|
||||||
|
item = "titanium_ingot"
|
||||||
|
amount = 1
|
||||||
|
probability = 0.15
|
||||||
|
|
||||||
[[recipe.outputs]]
|
[[recipe.outputs]]
|
||||||
item = "advanced_alloy"
|
item = "advanced_alloy"
|
||||||
amount = 1
|
amount = 1
|
||||||
probability = 0.1
|
probability = 0.15
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Basic components (tier 2, early game)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
[[recipe]]
|
||||||
|
id = "copper_wire"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
building = "assembler"
|
||||||
|
inputs = [{item = "copper_ingot", amount = 1}]
|
||||||
|
outputs = [{item = "copper_wire", amount = 2}]
|
||||||
|
duration_seconds = 1.5
|
||||||
|
|
||||||
|
[[recipe]]
|
||||||
|
id = "steel_plate"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
building = "assembler"
|
||||||
|
inputs = [{item = "iron_ingot", amount = 2}]
|
||||||
|
outputs = [{item = "steel_plate", amount = 1}]
|
||||||
|
duration_seconds = 2.0
|
||||||
|
|
||||||
|
[[recipe]]
|
||||||
|
id = "circuit_board"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
building = "assembler"
|
||||||
|
inputs = [{item = "iron_ingot", amount = 1}, {item = "copper_wire", amount = 2}]
|
||||||
|
outputs = [{item = "circuit_board", amount = 1}]
|
||||||
|
duration_seconds = 2.0
|
||||||
|
|
||||||
|
[[recipe]]
|
||||||
|
id = "building_blocks"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
building = "assembler"
|
||||||
|
inputs = [{item = "iron_ingot", amount = 4}]
|
||||||
|
outputs = [{item = "building_block", amount = 10}]
|
||||||
|
duration_seconds = 4.0
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Advanced components (tier 3, midgame)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
[[recipe]]
|
||||||
|
id = "mechanical_parts"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
building = "assembler"
|
||||||
|
inputs = [{item = "steel_plate", amount = 1}, {item = "iron_ingot", amount = 1}]
|
||||||
|
outputs = [{item = "mechanical_parts", amount = 2}]
|
||||||
|
duration_seconds = 2.5
|
||||||
|
|
||||||
|
[[recipe]]
|
||||||
|
id = "targeting_unit"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
building = "assembler"
|
||||||
|
inputs = [{item = "circuit_board", amount = 2}, {item = "copper_wire", amount = 1}]
|
||||||
|
outputs = [{item = "targeting_unit", amount = 1}]
|
||||||
|
duration_seconds = 3.0
|
||||||
|
|
||||||
|
[[recipe]]
|
||||||
|
id = "drive_unit"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
building = "assembler"
|
||||||
|
inputs = [
|
||||||
|
{item = "steel_plate", amount = 1},
|
||||||
|
{item = "mechanical_parts", amount = 1},
|
||||||
|
{item = "circuit_board", amount = 1},
|
||||||
|
]
|
||||||
|
outputs = [{item = "drive_unit", amount = 1}]
|
||||||
|
duration_seconds = 4.0
|
||||||
|
|
||||||
|
[[recipe]]
|
||||||
|
id = "titanium_frame"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
building = "assembler"
|
||||||
|
inputs = [{item = "titanium_ingot", amount = 2}, {item = "steel_plate", amount = 1}]
|
||||||
|
outputs = [{item = "titanium_frame", amount = 1}]
|
||||||
|
duration_seconds = 4.0
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Capital components (tier 4, lategame — gated on advanced_alloy)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
[[recipe]]
|
||||||
|
id = "reinforced_plating"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
building = "assembler"
|
||||||
|
inputs = [{item = "steel_plate", amount = 2}, {item = "advanced_alloy", amount = 1}]
|
||||||
|
outputs = [{item = "reinforced_plating", amount = 1}]
|
||||||
|
duration_seconds = 5.0
|
||||||
|
|
||||||
|
[[recipe]]
|
||||||
|
id = "capital_core"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
building = "assembler"
|
||||||
|
inputs = [
|
||||||
|
{item = "targeting_unit", amount = 1},
|
||||||
|
{item = "drive_unit", amount = 1},
|
||||||
|
{item = "advanced_alloy", amount = 2},
|
||||||
|
]
|
||||||
|
outputs = [{item = "capital_core", amount = 1}]
|
||||||
|
duration_seconds = 8.0
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Module items — early game
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
[[recipe]]
|
||||||
|
id = "laser_cannon_s_module"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
building = "assembler"
|
||||||
|
inputs = [{item = "iron_ingot", amount = 2}, {item = "circuit_board", amount = 1}]
|
||||||
|
outputs = [{item = "laser_cannon_s_module", amount = 1}]
|
||||||
|
duration_seconds = 3.0
|
||||||
|
|
||||||
|
[[recipe]]
|
||||||
|
id = "salvager_module"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
building = "assembler"
|
||||||
|
inputs = [{item = "steel_plate", amount = 1}, {item = "circuit_board", amount = 1}]
|
||||||
|
outputs = [{item = "salvager_module", amount = 1}]
|
||||||
|
duration_seconds = 4.0
|
||||||
|
|
||||||
|
[[recipe]]
|
||||||
|
id = "repair_tool_module"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
building = "assembler"
|
||||||
|
inputs = [{item = "circuit_board", amount = 2}, {item = "copper_wire", amount = 1}]
|
||||||
|
outputs = [{item = "repair_tool_module", amount = 1}]
|
||||||
|
duration_seconds = 4.0
|
||||||
|
|
||||||
|
[[recipe]]
|
||||||
|
id = "armor_plates_module"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
building = "assembler"
|
||||||
|
inputs = [{item = "steel_plate", amount = 2}]
|
||||||
|
outputs = [{item = "armor_plates_module", amount = 1}]
|
||||||
|
duration_seconds = 3.0
|
||||||
|
|
||||||
|
[[recipe]]
|
||||||
|
id = "sensor_booster_module"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
building = "assembler"
|
||||||
|
inputs = [{item = "circuit_board", amount = 1}, {item = "copper_wire", amount = 2}]
|
||||||
|
outputs = [{item = "sensor_booster_module", amount = 1}]
|
||||||
|
duration_seconds = 3.0
|
||||||
|
|
||||||
|
[[recipe]]
|
||||||
|
id = "maneuvering_thrusters_module"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
building = "assembler"
|
||||||
|
inputs = [{item = "mechanical_parts", amount = 1}, {item = "copper_wire", amount = 1}]
|
||||||
|
outputs = [{item = "maneuvering_thrusters_module", amount = 1}]
|
||||||
|
duration_seconds = 3.0
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Module items — midgame
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
[[recipe]]
|
||||||
|
id = "afterburner_module"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
building = "assembler"
|
||||||
|
inputs = [{item = "drive_unit", amount = 1}, {item = "steel_plate", amount = 1}]
|
||||||
|
outputs = [{item = "afterburner_module", amount = 1}]
|
||||||
|
duration_seconds = 4.0
|
||||||
|
|
||||||
|
[[recipe]]
|
||||||
|
id = "weapon_upgrade_module"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
building = "assembler"
|
||||||
|
inputs = [{item = "targeting_unit", amount = 1}, {item = "steel_plate", amount = 1}]
|
||||||
|
outputs = [{item = "weapon_upgrade_module", amount = 1}]
|
||||||
|
duration_seconds = 4.0
|
||||||
|
|
||||||
|
[[recipe]]
|
||||||
|
id = "weapon_primer_module"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
building = "assembler"
|
||||||
|
inputs = [{item = "targeting_unit", amount = 1}, {item = "copper_wire", amount = 2}]
|
||||||
|
outputs = [{item = "weapon_primer_module", amount = 1}]
|
||||||
|
duration_seconds = 4.0
|
||||||
|
|
||||||
|
[[recipe]]
|
||||||
|
id = "weapon_stabilizer_module"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
building = "assembler"
|
||||||
|
inputs = [{item = "targeting_unit", amount = 1}, {item = "mechanical_parts", amount = 1}]
|
||||||
|
outputs = [{item = "weapon_stabilizer_module", amount = 1}]
|
||||||
|
duration_seconds = 4.0
|
||||||
|
|
||||||
|
[[recipe]]
|
||||||
|
id = "laser_cannon_m_module"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
building = "assembler"
|
||||||
|
inputs = [{item = "targeting_unit", amount = 1}, {item = "titanium_frame", amount = 1}]
|
||||||
|
outputs = [{item = "laser_cannon_m_module", amount = 1}]
|
||||||
|
duration_seconds = 6.0
|
||||||
|
|
||||||
|
[[recipe]]
|
||||||
|
id = "drone_bay_module"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
building = "assembler"
|
||||||
|
inputs = [
|
||||||
|
{item = "titanium_frame", amount = 1},
|
||||||
|
{item = "mechanical_parts", amount = 1},
|
||||||
|
{item = "circuit_board", amount = 1},
|
||||||
|
]
|
||||||
|
outputs = [{item = "drone_bay_module", amount = 1}]
|
||||||
|
duration_seconds = 6.0
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Module items — lategame
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
[[recipe]]
|
||||||
|
id = "laser_cannon_l_module"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
building = "assembler"
|
||||||
|
inputs = [
|
||||||
|
{item = "targeting_unit", amount = 2},
|
||||||
|
{item = "reinforced_plating", amount = 2},
|
||||||
|
{item = "titanium_frame", amount = 1},
|
||||||
|
]
|
||||||
|
outputs = [{item = "laser_cannon_l_module", amount = 1}]
|
||||||
|
duration_seconds = 12.0
|
||||||
|
|
||||||
|
[[recipe]]
|
||||||
|
id = "drone_hangar_module"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
building = "assembler"
|
||||||
|
inputs = [
|
||||||
|
{item = "capital_core", amount = 1},
|
||||||
|
{item = "titanium_frame", amount = 2},
|
||||||
|
{item = "reinforced_plating", amount = 2},
|
||||||
|
]
|
||||||
|
outputs = [{item = "drone_hangar_module", amount = 1}]
|
||||||
|
duration_seconds = 20.0
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Ship hulls
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
[[recipe]]
|
||||||
|
id = "drone_hull"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
building = "assembler"
|
||||||
|
inputs = [{item = "iron_ingot", amount = 5}, {item = "circuit_board", amount = 1}]
|
||||||
|
outputs = [{item = "drone_hull", amount = 1}]
|
||||||
|
duration_seconds = 4.0
|
||||||
|
|
||||||
|
[[recipe]]
|
||||||
|
id = "frigate_hull"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
building = "assembler"
|
||||||
|
inputs = [
|
||||||
|
{item = "steel_plate", amount = 3},
|
||||||
|
{item = "mechanical_parts", amount = 1},
|
||||||
|
{item = "circuit_board", amount = 1},
|
||||||
|
]
|
||||||
|
outputs = [{item = "frigate_hull", amount = 1}]
|
||||||
|
duration_seconds = 8.0
|
||||||
|
|
||||||
|
[[recipe]]
|
||||||
|
id = "destroyer_hull"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
building = "assembler"
|
||||||
|
inputs = [
|
||||||
|
{item = "steel_plate", amount = 5},
|
||||||
|
{item = "mechanical_parts", amount = 2},
|
||||||
|
{item = "circuit_board", amount = 1},
|
||||||
|
]
|
||||||
|
outputs = [{item = "destroyer_hull", amount = 1}]
|
||||||
|
duration_seconds = 10.0
|
||||||
|
|
||||||
|
[[recipe]]
|
||||||
|
id = "cruiser_hull"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
building = "assembler"
|
||||||
|
inputs = [
|
||||||
|
{item = "titanium_frame", amount = 2},
|
||||||
|
{item = "steel_plate", amount = 4},
|
||||||
|
{item = "drive_unit", amount = 1},
|
||||||
|
]
|
||||||
|
outputs = [{item = "cruiser_hull", amount = 1}]
|
||||||
|
duration_seconds = 15.0
|
||||||
|
|
||||||
|
[[recipe]]
|
||||||
|
id = "battlecruiser_hull"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
building = "assembler"
|
||||||
|
inputs = [
|
||||||
|
{item = "titanium_frame", amount = 3},
|
||||||
|
{item = "steel_plate", amount = 6},
|
||||||
|
{item = "drive_unit", amount = 1},
|
||||||
|
{item = "targeting_unit", amount = 1},
|
||||||
|
]
|
||||||
|
outputs = [{item = "battlecruiser_hull", amount = 1}]
|
||||||
|
duration_seconds = 20.0
|
||||||
|
|
||||||
|
[[recipe]]
|
||||||
|
id = "battleship_hull"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
building = "assembler"
|
||||||
|
inputs = [
|
||||||
|
{item = "titanium_frame", amount = 4},
|
||||||
|
{item = "reinforced_plating", amount = 2},
|
||||||
|
{item = "drive_unit", amount = 2},
|
||||||
|
]
|
||||||
|
outputs = [{item = "battleship_hull", amount = 1}]
|
||||||
|
duration_seconds = 30.0
|
||||||
|
|
||||||
|
[[recipe]]
|
||||||
|
id = "dreadnought_hull"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
building = "assembler"
|
||||||
|
inputs = [
|
||||||
|
{item = "capital_core", amount = 1},
|
||||||
|
{item = "titanium_frame", amount = 6},
|
||||||
|
{item = "reinforced_plating", amount = 4},
|
||||||
|
{item = "drive_unit", amount = 2},
|
||||||
|
]
|
||||||
|
outputs = [{item = "dreadnought_hull", amount = 1}]
|
||||||
|
duration_seconds = 60.0
|
||||||
|
|
||||||
|
[[recipe]]
|
||||||
|
id = "carrier_hull"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
building = "assembler"
|
||||||
|
inputs = [
|
||||||
|
{item = "capital_core", amount = 1},
|
||||||
|
{item = "titanium_frame", amount = 5},
|
||||||
|
{item = "reinforced_plating", amount = 3},
|
||||||
|
{item = "drive_unit", amount = 2},
|
||||||
|
]
|
||||||
|
outputs = [{item = "carrier_hull", amount = 1}]
|
||||||
|
duration_seconds = 60.0
|
||||||
|
|||||||
@@ -1,8 +1,25 @@
|
|||||||
|
# ships.toml
|
||||||
|
#
|
||||||
|
# First real-content iteration: ship ids and layout grids are the designed
|
||||||
|
# content; stats, materials, and production times are placeholders until the
|
||||||
|
# recipe and balancing passes.
|
||||||
|
#
|
||||||
|
# Size classes:
|
||||||
|
# xs drone 1 cell — exactly one 1x1 module
|
||||||
|
# s frigate, destroyer no 2x2 area anywhere: only 1x1/1x2/1x3/L modules fit
|
||||||
|
# m cruiser, battlecruiser 2x2 areas (m guns, drone bays) but no 3x3 area
|
||||||
|
# l battleship four m guns, or exactly one 3x3 l gun at heavy
|
||||||
|
# opportunity cost
|
||||||
|
# xl dreadnought, carrier dreadnought fits three l guns but no drone
|
||||||
|
# hangar; carrier fits one drone hangar (2x6)
|
||||||
|
# but no l gun (its deck rows are broken up
|
||||||
|
# by elevator shafts)
|
||||||
|
|
||||||
[[ship]]
|
[[ship]]
|
||||||
id = "drone"
|
id = "drone"
|
||||||
unlock_at_station_level = -1
|
unlock_at_station_level = -1
|
||||||
layout = ["O"]
|
layout = ["O"]
|
||||||
default_modules = [{type = "laser_cannon_xs", x = 0, y = 0, rotation = "east"}]
|
default_modules = [{type = "laser_cannon_s", x = 0, y = 0, rotation = "east"}]
|
||||||
|
|
||||||
[ship.schematic]
|
[ship.schematic]
|
||||||
materials = [{item = "iron_ore", amount = 1}]
|
materials = [{item = "iron_ore", amount = 1}]
|
||||||
@@ -24,3 +41,255 @@ sensor_range_m_formula = "150"
|
|||||||
|
|
||||||
[ship.loot]
|
[ship.loot]
|
||||||
scrap_drop = 2
|
scrap_drop = 2
|
||||||
|
|
||||||
|
|
||||||
|
# Frigate — 5 cells in a plus shape. Holds a couple of small guns plus at
|
||||||
|
# most one 1x2 support (every 1x2 placement crosses the center cell), or one
|
||||||
|
# L-shaped weapon modifier, or an afterburner spanning the full center line.
|
||||||
|
[[ship]]
|
||||||
|
id = "frigate"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
layout = [
|
||||||
|
"XOX",
|
||||||
|
"OOO",
|
||||||
|
"XOX",
|
||||||
|
]
|
||||||
|
|
||||||
|
[ship.schematic]
|
||||||
|
materials = [{item = "frigate_hull", amount = 1}]
|
||||||
|
player_production_level = 1
|
||||||
|
production_time_seconds = 10
|
||||||
|
|
||||||
|
[ship.health]
|
||||||
|
hp_formula = "30"
|
||||||
|
|
||||||
|
[ship.movement]
|
||||||
|
speed_mps_formula = "30"
|
||||||
|
main_acceleration_mpss_formula = "50"
|
||||||
|
maneuvering_acceleration_mpss_formula = "25"
|
||||||
|
angular_acceleration_radpss_formula = "8"
|
||||||
|
max_rotation_speed_radps_formula = "4"
|
||||||
|
|
||||||
|
[ship.sensor]
|
||||||
|
sensor_range_m_formula = "200"
|
||||||
|
|
||||||
|
[ship.loot]
|
||||||
|
scrap_drop = 5
|
||||||
|
|
||||||
|
|
||||||
|
# Destroyer — 8 cells: a long gun deck with three turret bumps on top.
|
||||||
|
# Still no 2x2 area, so it packs more small guns than a frigate but can never
|
||||||
|
# mount medium hardware.
|
||||||
|
[[ship]]
|
||||||
|
id = "destroyer"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
layout = [
|
||||||
|
"OXOXO",
|
||||||
|
"OOOOO",
|
||||||
|
]
|
||||||
|
|
||||||
|
[ship.schematic]
|
||||||
|
materials = [{item = "destroyer_hull", amount = 1}]
|
||||||
|
player_production_level = 1
|
||||||
|
production_time_seconds = 15
|
||||||
|
|
||||||
|
[ship.health]
|
||||||
|
hp_formula = "50"
|
||||||
|
|
||||||
|
[ship.movement]
|
||||||
|
speed_mps_formula = "25"
|
||||||
|
main_acceleration_mpss_formula = "40"
|
||||||
|
maneuvering_acceleration_mpss_formula = "20"
|
||||||
|
angular_acceleration_radpss_formula = "6"
|
||||||
|
max_rotation_speed_radps_formula = "3"
|
||||||
|
|
||||||
|
[ship.sensor]
|
||||||
|
sensor_range_m_formula = "220"
|
||||||
|
|
||||||
|
[ship.loot]
|
||||||
|
scrap_drop = 8
|
||||||
|
|
||||||
|
|
||||||
|
# Cruiser — 12 cells with notched corners. Fits at most two 2x2 m guns
|
||||||
|
# (stacked through the middle), leaving the four side cells for small
|
||||||
|
# supports; no 3x3 area exists for an l gun.
|
||||||
|
[[ship]]
|
||||||
|
id = "cruiser"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
layout = [
|
||||||
|
"XOOX",
|
||||||
|
"OOOO",
|
||||||
|
"OOOO",
|
||||||
|
"XOOX",
|
||||||
|
]
|
||||||
|
|
||||||
|
[ship.schematic]
|
||||||
|
materials = [{item = "cruiser_hull", amount = 1}]
|
||||||
|
player_production_level = 1
|
||||||
|
production_time_seconds = 25
|
||||||
|
|
||||||
|
[ship.health]
|
||||||
|
hp_formula = "120"
|
||||||
|
|
||||||
|
[ship.movement]
|
||||||
|
speed_mps_formula = "20"
|
||||||
|
main_acceleration_mpss_formula = "30"
|
||||||
|
maneuvering_acceleration_mpss_formula = "15"
|
||||||
|
angular_acceleration_radpss_formula = "4"
|
||||||
|
max_rotation_speed_radps_formula = "2"
|
||||||
|
|
||||||
|
[ship.sensor]
|
||||||
|
sensor_range_m_formula = "250"
|
||||||
|
|
||||||
|
[ship.loot]
|
||||||
|
scrap_drop = 15
|
||||||
|
|
||||||
|
|
||||||
|
# Battlecruiser — 16 cells: a wide bow split into two gun cheeks, tapering
|
||||||
|
# toward the stern. Fits three 2x2 m guns (two in the cheeks, one through
|
||||||
|
# the middle) with small support slots left over; the split bow and tapered
|
||||||
|
# stern leave no 3x3 area for an l gun and no 2x6 area for a drone hangar.
|
||||||
|
[[ship]]
|
||||||
|
id = "battlecruiser"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
layout = [
|
||||||
|
"OOXXOO",
|
||||||
|
"OOOOOO",
|
||||||
|
"XOOOOX",
|
||||||
|
"XXOOXX",
|
||||||
|
]
|
||||||
|
|
||||||
|
[ship.schematic]
|
||||||
|
materials = [{item = "battlecruiser_hull", amount = 1}]
|
||||||
|
player_production_level = 1
|
||||||
|
production_time_seconds = 35
|
||||||
|
|
||||||
|
[ship.health]
|
||||||
|
hp_formula = "180"
|
||||||
|
|
||||||
|
[ship.movement]
|
||||||
|
speed_mps_formula = "18"
|
||||||
|
main_acceleration_mpss_formula = "25"
|
||||||
|
maneuvering_acceleration_mpss_formula = "12"
|
||||||
|
angular_acceleration_radpss_formula = "3"
|
||||||
|
max_rotation_speed_radps_formula = "1.5"
|
||||||
|
|
||||||
|
[ship.sensor]
|
||||||
|
sensor_range_m_formula = "260"
|
||||||
|
|
||||||
|
[ship.loot]
|
||||||
|
scrap_drop = 20
|
||||||
|
|
||||||
|
|
||||||
|
# Battleship — 24 cells: a broadside hull with notched flanks on every other
|
||||||
|
# row. Fits four 2x2 m guns (two per gun deck) with the bow, stern, and flank
|
||||||
|
# cells left for supports. All 3x3 placements crowd the center columns, so at
|
||||||
|
# most ONE l gun fits — and mounting it blocks every m gun mount, leaving
|
||||||
|
# only narrow support strips. The notched rows are never adjacent-and-full,
|
||||||
|
# so no 2x6 drone hangar fits.
|
||||||
|
[[ship]]
|
||||||
|
id = "battleship"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
layout = [
|
||||||
|
"XOOOOX",
|
||||||
|
"OOOOOO",
|
||||||
|
"XOOOOX",
|
||||||
|
"OOOOOO",
|
||||||
|
"XOOOOX",
|
||||||
|
]
|
||||||
|
|
||||||
|
[ship.schematic]
|
||||||
|
materials = [{item = "battleship_hull", amount = 1}]
|
||||||
|
player_production_level = 1
|
||||||
|
production_time_seconds = 60
|
||||||
|
|
||||||
|
[ship.health]
|
||||||
|
hp_formula = "350"
|
||||||
|
|
||||||
|
[ship.movement]
|
||||||
|
speed_mps_formula = "14"
|
||||||
|
main_acceleration_mpss_formula = "18"
|
||||||
|
maneuvering_acceleration_mpss_formula = "8"
|
||||||
|
angular_acceleration_radpss_formula = "2"
|
||||||
|
max_rotation_speed_radps_formula = "1"
|
||||||
|
|
||||||
|
[ship.sensor]
|
||||||
|
sensor_range_m_formula = "280"
|
||||||
|
|
||||||
|
[ship.loot]
|
||||||
|
scrap_drop = 35
|
||||||
|
|
||||||
|
|
||||||
|
# Dreadnought — 36 cells: the main battery deck is split into three 3x3 gun
|
||||||
|
# slots by structural spacer columns, so exactly three l guns fit side by
|
||||||
|
# side (or m guns / supports in unused slots). The spacers cap every
|
||||||
|
# horizontal run at 5 cells, so the 2x6 drone hangar can never fit — carriers
|
||||||
|
# stay the only hangar hull. Bow and stern strips hold supports.
|
||||||
|
[[ship]]
|
||||||
|
id = "dreadnought"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
layout = [
|
||||||
|
"XXXOOOOOXXX",
|
||||||
|
"OOOXOOOXOOO",
|
||||||
|
"OOOXOOOXOOO",
|
||||||
|
"OOOXOOOXOOO",
|
||||||
|
"XXOOXXXOOXX",
|
||||||
|
]
|
||||||
|
|
||||||
|
[ship.schematic]
|
||||||
|
materials = [{item = "dreadnought_hull", amount = 1}]
|
||||||
|
player_production_level = 1
|
||||||
|
production_time_seconds = 120
|
||||||
|
|
||||||
|
[ship.health]
|
||||||
|
hp_formula = "800"
|
||||||
|
|
||||||
|
[ship.movement]
|
||||||
|
speed_mps_formula = "8"
|
||||||
|
main_acceleration_mpss_formula = "10"
|
||||||
|
maneuvering_acceleration_mpss_formula = "5"
|
||||||
|
angular_acceleration_radpss_formula = "1"
|
||||||
|
max_rotation_speed_radps_formula = "0.5"
|
||||||
|
|
||||||
|
[ship.sensor]
|
||||||
|
sensor_range_m_formula = "300"
|
||||||
|
|
||||||
|
[ship.loot]
|
||||||
|
scrap_drop = 60
|
||||||
|
|
||||||
|
|
||||||
|
# Carrier — 37 cells: the top flight deck (rows 0-1) is the only place wide
|
||||||
|
# enough for the 2x6 drone hangar, and exactly one fits. The middle deck row
|
||||||
|
# is broken up by elevator shafts (the X cells) so no 3x3 l gun can ever fit;
|
||||||
|
# the lower decks hold supports and 2x2 point-defense m guns.
|
||||||
|
[[ship]]
|
||||||
|
id = "carrier"
|
||||||
|
unlock_at_station_level = -1
|
||||||
|
layout = [
|
||||||
|
"XOOOOOOOOX",
|
||||||
|
"OOOOOOOOOO",
|
||||||
|
"OOXOOXOOXO",
|
||||||
|
"XOOOOOOOOX",
|
||||||
|
"XXXOOOOXXX",
|
||||||
|
]
|
||||||
|
|
||||||
|
[ship.schematic]
|
||||||
|
materials = [{item = "carrier_hull", amount = 1}]
|
||||||
|
player_production_level = 1
|
||||||
|
production_time_seconds = 120
|
||||||
|
|
||||||
|
[ship.health]
|
||||||
|
hp_formula = "700"
|
||||||
|
|
||||||
|
[ship.movement]
|
||||||
|
speed_mps_formula = "9"
|
||||||
|
main_acceleration_mpss_formula = "10"
|
||||||
|
maneuvering_acceleration_mpss_formula = "5"
|
||||||
|
angular_acceleration_radpss_formula = "1"
|
||||||
|
max_rotation_speed_radps_formula = "0.5"
|
||||||
|
|
||||||
|
[ship.sensor]
|
||||||
|
sensor_range_m_formula = "350"
|
||||||
|
|
||||||
|
[ship.loot]
|
||||||
|
scrap_drop = 60
|
||||||
|
|||||||
@@ -106,6 +106,8 @@ glyph = "E"
|
|||||||
# drawn around it. One section per ItemType.
|
# drawn around it. One section per ItemType.
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# --- ores ---
|
||||||
|
|
||||||
[items.iron_ore]
|
[items.iron_ore]
|
||||||
fill = "#8a5a4a"
|
fill = "#8a5a4a"
|
||||||
outline = "#201010"
|
outline = "#201010"
|
||||||
@@ -114,6 +116,12 @@ outline = "#201010"
|
|||||||
fill = "#c47a3a"
|
fill = "#c47a3a"
|
||||||
outline = "#3a1a0a"
|
outline = "#3a1a0a"
|
||||||
|
|
||||||
|
[items.titanium_ore]
|
||||||
|
fill = "#9aa3ad"
|
||||||
|
outline = "#2a2e33"
|
||||||
|
|
||||||
|
# --- ingots ---
|
||||||
|
|
||||||
[items.iron_ingot]
|
[items.iron_ingot]
|
||||||
fill = "#b0b0b8"
|
fill = "#b0b0b8"
|
||||||
outline = "#202028"
|
outline = "#202028"
|
||||||
@@ -122,30 +130,80 @@ outline = "#202028"
|
|||||||
fill = "#d48a4a"
|
fill = "#d48a4a"
|
||||||
outline = "#402010"
|
outline = "#402010"
|
||||||
|
|
||||||
[items.circuit_board]
|
[items.titanium_ingot]
|
||||||
fill = "#2ea35a"
|
fill = "#c8d2dc"
|
||||||
outline = "#0a2a14"
|
outline = "#3a4048"
|
||||||
|
|
||||||
[items.advanced_alloy]
|
# --- salvage loop ---
|
||||||
fill = "#a06acc"
|
|
||||||
outline = "#201030"
|
|
||||||
|
|
||||||
[items.building_block]
|
|
||||||
fill = "#c8b070"
|
|
||||||
outline = "#302810"
|
|
||||||
|
|
||||||
[items.scrap]
|
[items.scrap]
|
||||||
fill = "#7a7268"
|
fill = "#7a7268"
|
||||||
outline = "#201a14"
|
outline = "#201a14"
|
||||||
|
|
||||||
[items.drone_hull]
|
[items.advanced_alloy]
|
||||||
fill = "#1b1b1b"
|
fill = "#a06acc"
|
||||||
outline = "#1402b3"
|
outline = "#201030"
|
||||||
|
|
||||||
[items.laser_cannon_xs_module]
|
# --- basic components ---
|
||||||
|
|
||||||
|
[items.copper_wire]
|
||||||
|
fill = "#e09a50"
|
||||||
|
outline = "#3a2008"
|
||||||
|
|
||||||
|
[items.steel_plate]
|
||||||
|
fill = "#8a92a0"
|
||||||
|
outline = "#22262c"
|
||||||
|
|
||||||
|
[items.circuit_board]
|
||||||
|
fill = "#2ea35a"
|
||||||
|
outline = "#0a2a14"
|
||||||
|
|
||||||
|
[items.building_block]
|
||||||
|
fill = "#c8b070"
|
||||||
|
outline = "#302810"
|
||||||
|
|
||||||
|
# --- advanced components ---
|
||||||
|
|
||||||
|
[items.mechanical_parts]
|
||||||
|
fill = "#6f7a66"
|
||||||
|
outline = "#1c2018"
|
||||||
|
|
||||||
|
[items.targeting_unit]
|
||||||
|
fill = "#3a9e8c"
|
||||||
|
outline = "#0c2824"
|
||||||
|
|
||||||
|
[items.drive_unit]
|
||||||
|
fill = "#4a6ad0"
|
||||||
|
outline = "#101a38"
|
||||||
|
|
||||||
|
[items.titanium_frame]
|
||||||
|
fill = "#b8c4d4"
|
||||||
|
outline = "#343c48"
|
||||||
|
|
||||||
|
# --- capital components ---
|
||||||
|
|
||||||
|
[items.reinforced_plating]
|
||||||
|
fill = "#8a6ad0"
|
||||||
|
outline = "#1c1038"
|
||||||
|
|
||||||
|
[items.capital_core]
|
||||||
|
fill = "#b040d0"
|
||||||
|
outline = "#280c30"
|
||||||
|
|
||||||
|
# --- module items ---
|
||||||
|
|
||||||
|
[items.laser_cannon_s_module]
|
||||||
fill = "#691313"
|
fill = "#691313"
|
||||||
outline = "#f3ff4f"
|
outline = "#f3ff4f"
|
||||||
|
|
||||||
|
[items.laser_cannon_m_module]
|
||||||
|
fill = "#892020"
|
||||||
|
outline = "#f3ff4f"
|
||||||
|
|
||||||
|
[items.laser_cannon_l_module]
|
||||||
|
fill = "#a92d2d"
|
||||||
|
outline = "#f3ff4f"
|
||||||
|
|
||||||
[items.salvager_module]
|
[items.salvager_module]
|
||||||
fill = "#b2cfdd"
|
fill = "#b2cfdd"
|
||||||
outline = "#236137"
|
outline = "#236137"
|
||||||
@@ -154,6 +212,76 @@ outline = "#236137"
|
|||||||
fill = "#2e9ba3"
|
fill = "#2e9ba3"
|
||||||
outline = "#689275"
|
outline = "#689275"
|
||||||
|
|
||||||
|
[items.armor_plates_module]
|
||||||
|
fill = "#808080"
|
||||||
|
outline = "#202020"
|
||||||
|
|
||||||
|
[items.sensor_booster_module]
|
||||||
|
fill = "#40a0ff"
|
||||||
|
outline = "#102840"
|
||||||
|
|
||||||
|
[items.maneuvering_thrusters_module]
|
||||||
|
fill = "#5090e0"
|
||||||
|
outline = "#142438"
|
||||||
|
|
||||||
|
[items.afterburner_module]
|
||||||
|
fill = "#6080c0"
|
||||||
|
outline = "#182030"
|
||||||
|
|
||||||
|
[items.weapon_upgrade_module]
|
||||||
|
fill = "#ff4040"
|
||||||
|
outline = "#401010"
|
||||||
|
|
||||||
|
[items.weapon_primer_module]
|
||||||
|
fill = "#e03838"
|
||||||
|
outline = "#380e0e"
|
||||||
|
|
||||||
|
[items.weapon_stabilizer_module]
|
||||||
|
fill = "#c03030"
|
||||||
|
outline = "#300c0c"
|
||||||
|
|
||||||
|
[items.drone_bay_module]
|
||||||
|
fill = "#cc66ff"
|
||||||
|
outline = "#331040"
|
||||||
|
|
||||||
|
[items.drone_hangar_module]
|
||||||
|
fill = "#9933cc"
|
||||||
|
outline = "#260c33"
|
||||||
|
|
||||||
|
# --- ship hulls (outline matches the ship's fleet color in [ships.*]) ---
|
||||||
|
|
||||||
|
[items.drone_hull]
|
||||||
|
fill = "#1b1b1b"
|
||||||
|
outline = "#3366ff"
|
||||||
|
|
||||||
|
[items.frigate_hull]
|
||||||
|
fill = "#1b1b1b"
|
||||||
|
outline = "#44aaff"
|
||||||
|
|
||||||
|
[items.destroyer_hull]
|
||||||
|
fill = "#1b1b1b"
|
||||||
|
outline = "#33ccaa"
|
||||||
|
|
||||||
|
[items.cruiser_hull]
|
||||||
|
fill = "#1b1b1b"
|
||||||
|
outline = "#66cc33"
|
||||||
|
|
||||||
|
[items.battlecruiser_hull]
|
||||||
|
fill = "#1b1b1b"
|
||||||
|
outline = "#cccc33"
|
||||||
|
|
||||||
|
[items.battleship_hull]
|
||||||
|
fill = "#1b1b1b"
|
||||||
|
outline = "#ff9933"
|
||||||
|
|
||||||
|
[items.dreadnought_hull]
|
||||||
|
fill = "#1b1b1b"
|
||||||
|
outline = "#ff5533"
|
||||||
|
|
||||||
|
[items.carrier_hull]
|
||||||
|
fill = "#1b1b1b"
|
||||||
|
outline = "#cc66ff"
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Ships
|
# Ships
|
||||||
#
|
#
|
||||||
@@ -164,6 +292,34 @@ outline = "#689275"
|
|||||||
fill = "#3366ff"
|
fill = "#3366ff"
|
||||||
outline = "#ffffff"
|
outline = "#ffffff"
|
||||||
|
|
||||||
|
[ships.frigate]
|
||||||
|
fill = "#44aaff"
|
||||||
|
outline = "#ffffff"
|
||||||
|
|
||||||
|
[ships.destroyer]
|
||||||
|
fill = "#33ccaa"
|
||||||
|
outline = "#ffffff"
|
||||||
|
|
||||||
|
[ships.cruiser]
|
||||||
|
fill = "#66cc33"
|
||||||
|
outline = "#ffffff"
|
||||||
|
|
||||||
|
[ships.battlecruiser]
|
||||||
|
fill = "#cccc33"
|
||||||
|
outline = "#ffffff"
|
||||||
|
|
||||||
|
[ships.battleship]
|
||||||
|
fill = "#ff9933"
|
||||||
|
outline = "#ffffff"
|
||||||
|
|
||||||
|
[ships.dreadnought]
|
||||||
|
fill = "#ff5533"
|
||||||
|
outline = "#ffffff"
|
||||||
|
|
||||||
|
[ships.carrier]
|
||||||
|
fill = "#cc66ff"
|
||||||
|
outline = "#ffffff"
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Laser beams (REQ-SHP-FIRING-BEAM)
|
# Laser beams (REQ-SHP-FIRING-BEAM)
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ enemy_buffer_width_tiles = 10
|
|||||||
level = 1
|
level = 1
|
||||||
count = 5
|
count = 5
|
||||||
modules = [
|
modules = [
|
||||||
{type = "laser_cannon_xs", x = 1, y = 1, rotation = "east"},
|
{type = "laser_cannon_s", x = 1, y = 1, rotation = "east"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[arena.team]]
|
[[arena.team]]
|
||||||
@@ -22,7 +22,7 @@ enemy_buffer_width_tiles = 10
|
|||||||
level = 1
|
level = 1
|
||||||
count = 2
|
count = 2
|
||||||
modules = [
|
modules = [
|
||||||
{type = "laser_cannon_xs", x = 1, y = 1, rotation = "east"},
|
{type = "laser_cannon_s", x = 1, y = 1, rotation = "east"},
|
||||||
{type = "weapon_stabilizer", x = 1, y = 1, rotation = "east"},
|
{type = "weapon_stabilizer", x = 1, y = 1, rotation = "east"},
|
||||||
{type = "weapon_stabilizer", x = 1, y = 1, rotation = "east"},
|
{type = "weapon_stabilizer", x = 1, y = 1, rotation = "east"},
|
||||||
{type = "weapon_upgrade", x = 1, y = 1, rotation = "east"},
|
{type = "weapon_upgrade", x = 1, y = 1, rotation = "east"},
|
||||||
@@ -44,7 +44,7 @@ enemy_buffer_width_tiles = 10
|
|||||||
level = 1
|
level = 1
|
||||||
count = 5
|
count = 5
|
||||||
modules = [
|
modules = [
|
||||||
{type = "laser_cannon_xs", x = 1, y = 1, rotation = "east"},
|
{type = "laser_cannon_s", x = 1, y = 1, rotation = "east"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[arena.team]]
|
[[arena.team]]
|
||||||
@@ -54,7 +54,7 @@ enemy_buffer_width_tiles = 10
|
|||||||
level = 1
|
level = 1
|
||||||
count = 3
|
count = 3
|
||||||
modules = [
|
modules = [
|
||||||
{type = "laser_cannon_xs", x = 1, y = 1, rotation = "east"},
|
{type = "laser_cannon_s", x = 1, y = 1, rotation = "east"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[arena.team.ship]]
|
[[arena.team.ship]]
|
||||||
@@ -79,7 +79,7 @@ enemy_buffer_width_tiles = 15
|
|||||||
level = 1
|
level = 1
|
||||||
count = 3
|
count = 3
|
||||||
modules = [
|
modules = [
|
||||||
{type = "laser_cannon_xs", x = 1, y = 1, rotation = "east"},
|
{type = "laser_cannon_s", x = 1, y = 1, rotation = "east"},
|
||||||
]
|
]
|
||||||
[[arena.team.station]]
|
[[arena.team.station]]
|
||||||
type = "player_station"
|
type = "player_station"
|
||||||
@@ -99,5 +99,5 @@ enemy_buffer_width_tiles = 15
|
|||||||
level = 1
|
level = 1
|
||||||
count = 8
|
count = 8
|
||||||
modules = [
|
modules = [
|
||||||
{type = "laser_cannon_xs", x = 1, y = 1, rotation = "east"},
|
{type = "laser_cannon_s", x = 1, y = 1, rotation = "east"},
|
||||||
]
|
]
|
||||||
|
|||||||
194
docs/content_design.md
Normal file
194
docs/content_design.md
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
# Content Design — Ships & Modules
|
||||||
|
|
||||||
|
First real-content iterations (June 2026). Pass 1 defined ship hull grids and
|
||||||
|
module surface masks; pass 2 defined the production tree (recipes). Stats and
|
||||||
|
threat costs in the config files are still placeholders for the balancing
|
||||||
|
pass.
|
||||||
|
|
||||||
|
## Design principle: footprint gating
|
||||||
|
|
||||||
|
Which module fits on which hull is controlled purely by geometry — no
|
||||||
|
explicit allow-lists. Each hull grid is shaped so that it physically cannot
|
||||||
|
contain the footprint of modules from a larger size class. This keeps the
|
||||||
|
rules transparent to the player ("it doesn't fit because there is no room")
|
||||||
|
and makes them trivially moddable through the config files alone.
|
||||||
|
|
||||||
|
### Module footprint ladder
|
||||||
|
|
||||||
|
| Footprint | Modules | Smallest hull that fits it |
|
||||||
|
|-----------|---------|----------------------------|
|
||||||
|
| 1x1 | laser_cannon_s, salvager, repair_tool | drone |
|
||||||
|
| 1x2 | maneuvering_thrusters, sensor_booster, armor_plates | frigate |
|
||||||
|
| 1x3 | afterburner | frigate (eats most of it) |
|
||||||
|
| L-shape (3 cells) | weapon_stabilizer, weapon_primer, weapon_upgrade | frigate |
|
||||||
|
| 2x2 | laser_cannon_m, drone_bay | cruiser |
|
||||||
|
| 3x3 | laser_cannon_l | battleship |
|
||||||
|
| 2x6 | drone_hangar | carrier (only) |
|
||||||
|
|
||||||
|
### Hull grids
|
||||||
|
|
||||||
|
`O` = buildable cell, `X` = hull structure (not buildable).
|
||||||
|
|
||||||
|
**drone (xs, 1 cell)** — exactly one 1x1 module: a small gun, a salvager, or
|
||||||
|
a repair tool. This is what makes drone roles swappable.
|
||||||
|
|
||||||
|
O
|
||||||
|
|
||||||
|
**frigate (s, 5 cells)** — plus shape. Every 1x2 placement crosses the center
|
||||||
|
cell, so at most ONE 1x2 support fits; alternatively one L-shaped weapon
|
||||||
|
modifier or one afterburner through the center line. Gun-boat with one or two
|
||||||
|
support modules, as intended.
|
||||||
|
|
||||||
|
XOX
|
||||||
|
OOO
|
||||||
|
XOX
|
||||||
|
|
||||||
|
**destroyer (s, 8 cells)** — gun deck with three turret bumps. More cells
|
||||||
|
than the frigate (more small guns), but still no 2x2 area anywhere, so medium
|
||||||
|
hardware can never be mounted.
|
||||||
|
|
||||||
|
OXOXO
|
||||||
|
OOOOO
|
||||||
|
|
||||||
|
**cruiser (m, 12 cells)** — notched corners. Fits at most two 2x2 m guns
|
||||||
|
(stacked through the middle), leaving the side cells for supports. No 3x3
|
||||||
|
area.
|
||||||
|
|
||||||
|
XOOX
|
||||||
|
OOOO
|
||||||
|
OOOO
|
||||||
|
XOOX
|
||||||
|
|
||||||
|
**battlecruiser (m, 16 cells)** — split bow with two gun cheeks, tapered
|
||||||
|
stern. Fits three 2x2 m guns — one more than the cruiser — with small support
|
||||||
|
slots left over. The bow split and stern taper prevent any 3x3 area (no l
|
||||||
|
gun) and any 2x6 area (no drone hangar).
|
||||||
|
|
||||||
|
OOXXOO
|
||||||
|
OOOOOO
|
||||||
|
XOOOOX
|
||||||
|
XXOOXX
|
||||||
|
|
||||||
|
**battleship (l, 24 cells)** — broadside hull with notched flanks on every
|
||||||
|
other row. Fits four 2x2 m guns (two per gun deck) — one more than the
|
||||||
|
battlecruiser — with bow, stern, and flank cells for supports. All 3x3
|
||||||
|
placements crowd the center columns, so at most ONE l gun fits: mounted
|
||||||
|
center it blocks every m gun mount (pure support strips remain), mounted
|
||||||
|
offset it still allows two m guns. The notched rows are never adjacent-and-
|
||||||
|
full, so no 2x6 drone hangar fits.
|
||||||
|
|
||||||
|
XOOOOX
|
||||||
|
OOOOOO
|
||||||
|
XOOOOX
|
||||||
|
OOOOOO
|
||||||
|
XOOOOX
|
||||||
|
|
||||||
|
**dreadnought (xl, 36 cells)** — the main battery deck is split into three
|
||||||
|
3x3 gun slots by structural spacer columns, so exactly three l guns fit side
|
||||||
|
by side (or m guns / supports in unused slots), plus bow/stern strips for
|
||||||
|
supports. The spacers cap every horizontal run at 5 cells, so the 2x6 drone
|
||||||
|
hangar can never fit — the carrier stays the only hangar hull.
|
||||||
|
|
||||||
|
XXXOOOOOXXX
|
||||||
|
OOOXOOOXOOO
|
||||||
|
OOOXOOOXOOO
|
||||||
|
OOOXOOOXOOO
|
||||||
|
XXOOXXXOOXX
|
||||||
|
|
||||||
|
**carrier (xl, 37 cells)** — the top flight deck (rows 0–1) is the only
|
||||||
|
region wide enough for the 2x6 drone hangar, and exactly one fits. The middle
|
||||||
|
deck row is broken up by elevator shafts (X cells placed so every 3-column
|
||||||
|
window hits one), which is what prevents any 3x3 l gun from ever fitting.
|
||||||
|
Lower decks hold supports and 2x2 point-defense m guns.
|
||||||
|
|
||||||
|
XOOOOOOOOX
|
||||||
|
OOOOOOOOOO
|
||||||
|
OOXOOXOOXO
|
||||||
|
XOOOOOOOOX
|
||||||
|
XXXOOOOXXX
|
||||||
|
|
||||||
|
### Verified gating matrix
|
||||||
|
|
||||||
|
Checked programmatically against the configs (all four mask rotations,
|
||||||
|
all placements) with `tools/verify_layouts.py` — re-run it after editing
|
||||||
|
layout grids or surface masks:
|
||||||
|
|
||||||
|
python dota_factory/tools/verify_layouts.py
|
||||||
|
|
||||||
|
| Footprint | drone | frigate | destroyer | cruiser | battlecruiser | battleship | dreadnought | carrier |
|
||||||
|
|-----------|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
|
||||||
|
| 1x1 | x | x | x | x | x | x | x | x |
|
||||||
|
| 1x2 | | x | x | x | x | x | x | x |
|
||||||
|
| 1x3 | | x | x | x | x | x | x | x |
|
||||||
|
| L-shape | | x | x | x | x | x | x | x |
|
||||||
|
| 2x2 | | | | x | x | x | x | x |
|
||||||
|
| 3x3 | | | | | | x | x | |
|
||||||
|
| 2x6 | | | | | | | | x |
|
||||||
|
|
||||||
|
Maximum simultaneous (disjoint) placements: m guns — cruiser 2,
|
||||||
|
battlecruiser 3, battleship 4; l guns — battleship 1, dreadnought 3;
|
||||||
|
drone hangar — carrier 1.
|
||||||
|
|
||||||
|
## Production tree
|
||||||
|
|
||||||
|
Design principle: each game phase adds exactly one new base input chain, so
|
||||||
|
factory complexity ramps alongside ship size.
|
||||||
|
|
||||||
|
| Phase | New input | How acquired | Unlocks |
|
||||||
|
|-------|-----------|--------------|---------|
|
||||||
|
| early | iron_ore, copper_ore | mined | drone, frigate, destroyer; small guns and basic supports |
|
||||||
|
| mid | titanium_ore | mined (3x slower than iron) | cruiser, battlecruiser; m guns, drone bay, weapon modifiers |
|
||||||
|
| late | advanced_alloy | ONLY from reprocessing salvaged scrap | battleship, dreadnought, carrier; l guns, drone hangar |
|
||||||
|
|
||||||
|
The advanced_alloy gate is the core loop hook: capital ship production
|
||||||
|
requires fighting (salvaging scrap from kills and reprocessing it), not just
|
||||||
|
mining. The reprocessing plant turns 5 scrap into iron/copper/titanium ingots
|
||||||
|
or advanced_alloy probabilistically.
|
||||||
|
|
||||||
|
Intermediate components, by tier:
|
||||||
|
|
||||||
|
- **Tier 2 (early):** copper_wire (copper), steel_plate (iron), circuit_board
|
||||||
|
(iron + wire), building_block (iron).
|
||||||
|
- **Tier 3 (mid):** mechanical_parts (steel + iron), targeting_unit (circuits
|
||||||
|
+ wire), drive_unit (steel + mechanical_parts + circuit), titanium_frame
|
||||||
|
(titanium + steel).
|
||||||
|
- **Tier 4 (late):** reinforced_plating (steel + advanced_alloy),
|
||||||
|
capital_core (targeting_unit + drive_unit + 2 advanced_alloy).
|
||||||
|
|
||||||
|
Hulls and modules consume intermediates of their tier: early items are built
|
||||||
|
from tier-2 parts, midgame items require tier-3 parts (deeper chains, more
|
||||||
|
assemblers), capital items require tier-4 parts (and therefore combat). Hull
|
||||||
|
items are named `<ship>_hull`; module items `<module>_module`. Every item has
|
||||||
|
an `[items.*]` entry in visuals.toml; hull item outlines match the ship's
|
||||||
|
fleet color from `[ships.*]`.
|
||||||
|
|
||||||
|
Consistency is checked by `tools/verify_recipes.py` — re-run it after editing
|
||||||
|
recipes, ship/module materials, or visuals:
|
||||||
|
|
||||||
|
python dota_factory/tools/verify_recipes.py
|
||||||
|
|
||||||
|
It verifies every consumed item has a producer, every item has a visuals
|
||||||
|
entry, flags orphaned items, and prints which items are reprocessing-only
|
||||||
|
(currently exactly advanced_alloy).
|
||||||
|
|
||||||
|
## Deliberate placeholders / open questions for later passes
|
||||||
|
|
||||||
|
- All new hulls have `threat.cost_formula = "0"` so enemy waves do not spawn
|
||||||
|
them yet (WaveSystem treats any ship with positive threat cost as wave-
|
||||||
|
eligible, regardless of unlock level). The balancing pass should set real
|
||||||
|
threat costs together with `default_modules` loadouts so waves spawn them
|
||||||
|
armed.
|
||||||
|
- All new hulls and all assembler recipes are `unlock_at_station_level = -1`
|
||||||
|
(available from the start) to make testing easy; the balancing pass should
|
||||||
|
stagger these so mid/lategame recipes drop as schematics from enemy defence
|
||||||
|
stations.
|
||||||
|
- Recipe quantities and durations are a first guess, deliberately roughly
|
||||||
|
tiered (capital hulls ~60 s, drones 4 s); the balancing pass tunes them.
|
||||||
|
- `drone_bay` and `drone_hangar` are footprint-only placeholders: the drone
|
||||||
|
launching capability does not exist in the simulation yet, so they define
|
||||||
|
no capability section.
|
||||||
|
- Renames in this pass: `laser_cannon_xs` → `laser_cannon_s` (the old 2x2
|
||||||
|
`laser_cannon_s` became `laser_cannon_m`), `armor_plate` → `armor_plates`,
|
||||||
|
`manuvering_thrusters` → `maneuvering_thrusters` (typo fix). Test data
|
||||||
|
under `bin/test/data/config` intentionally still uses the old ids — it is
|
||||||
|
an independent fixture set.
|
||||||
170
tools/verify_layouts.py
Normal file
170
tools/verify_layouts.py
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Verify which module surface masks fit which ship layout grids.
|
||||||
|
|
||||||
|
Reads ships.toml and modules.toml, then checks every module footprint
|
||||||
|
against every ship layout the same way the game does (all four mask
|
||||||
|
rotations, every placement position). Prints three reports:
|
||||||
|
|
||||||
|
1. Fit matrix — can the footprint be placed on the hull at all?
|
||||||
|
2. Max simultaneous — how many disjoint copies of a footprint fit at
|
||||||
|
once (only computed for footprints of 4+ cells;
|
||||||
|
smaller ones would be slow and uninteresting).
|
||||||
|
3. Cell counts — buildable cells per hull.
|
||||||
|
|
||||||
|
Use it after editing layout grids or surface masks to confirm the
|
||||||
|
footprint-gating rules in docs/content_design.md still hold, e.g. that
|
||||||
|
no 2x2 area exists on s-class hulls or that the drone hangar fits the
|
||||||
|
carrier only.
|
||||||
|
|
||||||
|
Usage (from the repository root or anywhere else):
|
||||||
|
|
||||||
|
python dota_factory/tools/verify_layouts.py
|
||||||
|
python dota_factory/tools/verify_layouts.py --config-dir path/to/config
|
||||||
|
|
||||||
|
By default the config directory is resolved relative to this script
|
||||||
|
(../bin/app/data/config). Requires the 'toml' package on Python < 3.11
|
||||||
|
(pip install --user toml); on 3.11+ the standard tomllib is used.
|
||||||
|
|
||||||
|
The script is informational only — it always exits 0. Read the matrix
|
||||||
|
and compare it against the intended gating in docs/content_design.md.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def load_toml(path):
|
||||||
|
try:
|
||||||
|
import tomllib
|
||||||
|
with open(path, "rb") as fh:
|
||||||
|
return tomllib.load(fh)
|
||||||
|
except ImportError:
|
||||||
|
import toml
|
||||||
|
return toml.load(path)
|
||||||
|
|
||||||
|
|
||||||
|
def grid_cells(rows):
|
||||||
|
"""Set of (x, y) for every 'O' cell in a list of layout/mask strings."""
|
||||||
|
return {(x, y)
|
||||||
|
for y, row in enumerate(rows)
|
||||||
|
for x, ch in enumerate(row)
|
||||||
|
if ch == "O"}
|
||||||
|
|
||||||
|
|
||||||
|
def rotate_cw(shape):
|
||||||
|
height = max(y for x, y in shape) + 1
|
||||||
|
return {(height - 1 - y, x) for x, y in shape}
|
||||||
|
|
||||||
|
|
||||||
|
def normalize(shape):
|
||||||
|
min_x = min(x for x, y in shape)
|
||||||
|
min_y = min(y for x, y in shape)
|
||||||
|
return frozenset((x - min_x, y - min_y) for x, y in shape)
|
||||||
|
|
||||||
|
|
||||||
|
def orientations(shape):
|
||||||
|
"""All distinct 90-degree rotations of a shape, normalized to (0, 0)."""
|
||||||
|
result = []
|
||||||
|
current = shape
|
||||||
|
for _ in range(4):
|
||||||
|
norm = normalize(current)
|
||||||
|
if norm not in result:
|
||||||
|
result.append(norm)
|
||||||
|
current = rotate_cw(current)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def placements(layout, shape):
|
||||||
|
"""Every position (in any rotation) where shape fits fully on layout."""
|
||||||
|
found = []
|
||||||
|
layout_w = max(x for x, y in layout) + 1
|
||||||
|
layout_h = max(y for x, y in layout) + 1
|
||||||
|
for orient in orientations(shape):
|
||||||
|
shape_w = max(x for x, y in orient) + 1
|
||||||
|
shape_h = max(y for x, y in orient) + 1
|
||||||
|
for off_x in range(layout_w - shape_w + 1):
|
||||||
|
for off_y in range(layout_h - shape_h + 1):
|
||||||
|
cells = frozenset((x + off_x, y + off_y) for x, y in orient)
|
||||||
|
if cells <= layout and cells not in found:
|
||||||
|
found.append(cells)
|
||||||
|
return found
|
||||||
|
|
||||||
|
|
||||||
|
def max_disjoint(layout, shape):
|
||||||
|
"""Maximum number of non-overlapping placements of shape on layout."""
|
||||||
|
options = placements(layout, shape)
|
||||||
|
best = [0]
|
||||||
|
|
||||||
|
def recurse(start_index, used, count):
|
||||||
|
if count > best[0]:
|
||||||
|
best[0] = count
|
||||||
|
for i in range(start_index, len(options)):
|
||||||
|
if not (options[i] & used):
|
||||||
|
recurse(i + 1, used | options[i], count + 1)
|
||||||
|
|
||||||
|
recurse(0, frozenset(), 0)
|
||||||
|
return best[0]
|
||||||
|
|
||||||
|
|
||||||
|
def shape_label(shape):
|
||||||
|
width = max(x for x, y in shape) + 1
|
||||||
|
height = max(y for x, y in shape) + 1
|
||||||
|
if len(shape) == width * height:
|
||||||
|
return "{}x{}".format(width, height)
|
||||||
|
return "{}x{}-{}c".format(width, height, len(shape))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
default_dir = os.path.normpath(os.path.join(
|
||||||
|
os.path.dirname(os.path.abspath(__file__)),
|
||||||
|
"..", "bin", "app", "data", "config"))
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Check module surface masks against ship layout grids.")
|
||||||
|
parser.add_argument("--config-dir", default=default_dir,
|
||||||
|
help="directory containing ships.toml and modules.toml"
|
||||||
|
" (default: %(default)s)")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
ships = load_toml(os.path.join(args.config_dir, "ships.toml"))["ship"]
|
||||||
|
modules = load_toml(os.path.join(args.config_dir, "modules.toml"))["module"]
|
||||||
|
|
||||||
|
layouts = [(s["id"], grid_cells(s["layout"])) for s in ships]
|
||||||
|
|
||||||
|
# Group modules that share the same footprint (up to rotation).
|
||||||
|
footprint_modules = {} # canonical shape -> [module ids]
|
||||||
|
for module in modules:
|
||||||
|
shape = grid_cells(module["surface_mask"])
|
||||||
|
canonical = min(orientations(shape), key=sorted)
|
||||||
|
footprint_modules.setdefault(canonical, []).append(module["id"])
|
||||||
|
footprints = sorted(footprint_modules.items(), key=lambda e: len(e[0]))
|
||||||
|
|
||||||
|
column_header = "".join("{:>8}".format(ship_id[:7])
|
||||||
|
for ship_id, _ in layouts)
|
||||||
|
|
||||||
|
print("Fit matrix (YES = at least one placement exists)")
|
||||||
|
print("{:48}{}".format("footprint (modules)", column_header))
|
||||||
|
for shape, module_ids in footprints:
|
||||||
|
name = "{:8}{}".format(shape_label(shape), ", ".join(module_ids))
|
||||||
|
row = "".join("{:>8}".format("YES" if placements(layout, shape) else "-")
|
||||||
|
for _, layout in layouts)
|
||||||
|
print("{:48}{}".format(name[:47], row))
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("Max simultaneous (disjoint) placements, footprints of 4+ cells")
|
||||||
|
print("{:48}{}".format("footprint (modules)", column_header))
|
||||||
|
for shape, module_ids in footprints:
|
||||||
|
if len(shape) < 4:
|
||||||
|
continue
|
||||||
|
name = "{:8}{}".format(shape_label(shape), ", ".join(module_ids))
|
||||||
|
row = "".join("{:>8}".format(max_disjoint(layout, shape))
|
||||||
|
for _, layout in layouts)
|
||||||
|
print("{:48}{}".format(name[:47], row))
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("Buildable cells per hull")
|
||||||
|
for ship_id, layout in layouts:
|
||||||
|
print("{:16}{}".format(ship_id, len(layout)))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
132
tools/verify_recipes.py
Normal file
132
tools/verify_recipes.py
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Verify the recipe tree is closed and consistent.
|
||||||
|
|
||||||
|
Reads recipes.toml, ships.toml, modules.toml, and visuals.toml, then checks:
|
||||||
|
|
||||||
|
1. Producers — every item consumed anywhere (recipe inputs, ship hull
|
||||||
|
materials, module materials) is produced by some recipe. 'scrap' is
|
||||||
|
exempt: it drops from destroyed ships.
|
||||||
|
2. Visuals — every item that exists in the economy has an [items.*]
|
||||||
|
entry in visuals.toml, and visuals.toml has no entries for items
|
||||||
|
that no longer exist.
|
||||||
|
3. Orphans — items that are produced but never consumed (warning only;
|
||||||
|
'building_block' is exempt: the HQ consumes it).
|
||||||
|
|
||||||
|
It also prints which items are obtainable ONLY through reprocessing —
|
||||||
|
the combat-gated materials — so changes to that gate are visible.
|
||||||
|
|
||||||
|
Usage (from the repository root or anywhere else):
|
||||||
|
|
||||||
|
python dota_factory/tools/verify_recipes.py
|
||||||
|
python dota_factory/tools/verify_recipes.py --config-dir path/to/config
|
||||||
|
|
||||||
|
By default the config directory is resolved relative to this script
|
||||||
|
(../bin/app/data/config). Requires the 'toml' package on Python < 3.11
|
||||||
|
(pip install --user toml); on 3.11+ the standard tomllib is used.
|
||||||
|
|
||||||
|
Exits 1 if a producer or visuals check fails, 0 otherwise (warnings do
|
||||||
|
not affect the exit code).
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
WORLD_SOURCED_ITEMS = {"scrap"} # dropped by destroyed ships
|
||||||
|
IMPLICITLY_CONSUMED_ITEMS = {"building_block"} # consumed by the HQ
|
||||||
|
|
||||||
|
|
||||||
|
def load_toml(path):
|
||||||
|
try:
|
||||||
|
import tomllib
|
||||||
|
with open(path, "rb") as fh:
|
||||||
|
return tomllib.load(fh)
|
||||||
|
except ImportError:
|
||||||
|
import toml
|
||||||
|
return toml.load(path)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
default_dir = os.path.normpath(os.path.join(
|
||||||
|
os.path.dirname(os.path.abspath(__file__)),
|
||||||
|
"..", "bin", "app", "data", "config"))
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Check recipe tree consistency across the config files.")
|
||||||
|
parser.add_argument("--config-dir", default=default_dir,
|
||||||
|
help="directory containing the config toml files"
|
||||||
|
" (default: %(default)s)")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
recipes = load_toml(os.path.join(args.config_dir, "recipes.toml"))["recipe"]
|
||||||
|
ships = load_toml(os.path.join(args.config_dir, "ships.toml"))["ship"]
|
||||||
|
modules = load_toml(os.path.join(args.config_dir, "modules.toml"))["module"]
|
||||||
|
visuals = load_toml(os.path.join(args.config_dir, "visuals.toml"))
|
||||||
|
|
||||||
|
produced = {} # item id -> [producer descriptions]
|
||||||
|
consumed = {} # item id -> [consumer descriptions]
|
||||||
|
|
||||||
|
for recipe in recipes:
|
||||||
|
for output in recipe.get("outputs", []):
|
||||||
|
produced.setdefault(output["item"], []).append(
|
||||||
|
"recipe '{}'".format(recipe["id"]))
|
||||||
|
for inp in recipe.get("inputs", []):
|
||||||
|
consumed.setdefault(inp["item"], []).append(
|
||||||
|
"recipe '{}'".format(recipe["id"]))
|
||||||
|
|
||||||
|
for ship in ships:
|
||||||
|
for material in ship["schematic"]["materials"]:
|
||||||
|
consumed.setdefault(material["item"], []).append(
|
||||||
|
"ship '{}'".format(ship["id"]))
|
||||||
|
|
||||||
|
for module in modules:
|
||||||
|
for material in module["materials"]:
|
||||||
|
consumed.setdefault(material["item"], []).append(
|
||||||
|
"module '{}'".format(module["id"]))
|
||||||
|
|
||||||
|
all_items = set(produced) | set(consumed) | WORLD_SOURCED_ITEMS
|
||||||
|
visual_items = set(visuals.get("items", {}))
|
||||||
|
|
||||||
|
errors = []
|
||||||
|
warnings = []
|
||||||
|
|
||||||
|
for item in sorted(consumed):
|
||||||
|
if item not in produced and item not in WORLD_SOURCED_ITEMS:
|
||||||
|
errors.append("no producer for '{}' (consumed by {})".format(
|
||||||
|
item, ", ".join(sorted(set(consumed[item])))))
|
||||||
|
|
||||||
|
for item in sorted(all_items - visual_items):
|
||||||
|
errors.append("no [items.{}] entry in visuals.toml".format(item))
|
||||||
|
for item in sorted(visual_items - all_items):
|
||||||
|
warnings.append("visuals.toml entry [items.{}] matches no known item"
|
||||||
|
.format(item))
|
||||||
|
|
||||||
|
for item in sorted(produced):
|
||||||
|
if item not in consumed and item not in IMPLICITLY_CONSUMED_ITEMS:
|
||||||
|
warnings.append("'{}' is produced but never consumed (by {})"
|
||||||
|
.format(item, ", ".join(sorted(set(produced[item])))))
|
||||||
|
|
||||||
|
reprocessing_only = sorted(
|
||||||
|
item for item, producers in produced.items()
|
||||||
|
if all("reprocessing" in p for p in producers))
|
||||||
|
|
||||||
|
print("{} items, {} recipes, {} ships, {} modules".format(
|
||||||
|
len(all_items), len(recipes), len(ships), len(modules)))
|
||||||
|
print("obtainable only via reprocessing: {}".format(
|
||||||
|
", ".join(reprocessing_only) if reprocessing_only else "(none)"))
|
||||||
|
print()
|
||||||
|
|
||||||
|
for warning in warnings:
|
||||||
|
print("WARNING: {}".format(warning))
|
||||||
|
for error in errors:
|
||||||
|
print("ERROR: {}".format(error))
|
||||||
|
if not errors and not warnings:
|
||||||
|
print("all checks passed")
|
||||||
|
elif not errors:
|
||||||
|
print("no errors")
|
||||||
|
|
||||||
|
return 1 if errors else 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
Reference in New Issue
Block a user