Module:TestModule

From Pacific Drive Wiki

Documentation for this module may be created at Module:TestModule/doc

local p = {}

p.consts = {
  bg_color = '#191714',
  canvas_padding = 20,
  icon_edge_thickness = 6,
  label_font_size = 14,
  node_size = {-1, -1},
  node_padding = {8, 16, 8, 16},
  node_icon_size = 64,
  node_icon_padding = 16,
  node_icon_container_size = -1,
  node_spacing = {-1, -1},
  wikitext_template_icon = '[[File:%s|frameless|%spx|link=]]',
  wikitext_template_link = '[[%s|<span class="fab-station-ui__link" style="position: absolute; top: 0; left: 0; width: 100%%; height: 100%%;"/>]]',
  tab_order = {
    'garage',
    'refine',
    'survival_tools',
    'lights',
    'car_body',
    'wheels',
    'expansion_racks',
    'car_storage',
    'fuel',
    'battery',
    'utilities',
    'engines',
  },
}
p.consts.node_icon_container_size = p.consts.node_icon_size + p.consts.node_icon_padding * 2 + p.consts.icon_edge_thickness * 2
p.consts.node_size = {
  p.consts.node_icon_container_size + p.consts.node_padding[2] + p.consts.node_padding[4],
  p.consts.node_icon_container_size + p.consts.node_padding[1] + p.consts.node_padding[3] + 40
}
p.consts.node_spacing = {
  p.consts.node_size[1] * 0.5,
  p.consts.node_size[2] * 0.3,
}

-- slot notation is {row, col}
-- nodes are recorded left to right, top to bottom
-- edges are recorded by source node, left to right, top to bottom
p.tabs = {
  battery = {
    canvas_slots = {3, 6},
    edges = {
      {4, 1},
      {4, 5},
      {5, 2},
      {5, 6},
      {5, 10},
      {6, 7},
      {6, 11},
      {7, 3},
      {7, 8},
      {8, 9},
      {8, 12},
    },
    nodes = {
      -- 1
      {
        id = 'plasma_charger',
        icon = 'PLACEHOLDER_PROTOTYPE.png',
        link_to = 'Plasma Charger',
        label = 'Plasma Charger',
        slot = {1, 2},
      },
      -- 2
      {
        id = 'hydro_generator',
        icon = 'RAIN-GENERATOR.png',
        link_to = 'Hydro Generator',
        label = 'Hydro Generator',
        slot = {1, 3},
      },
      -- 3
      {
        id = 'leak_resistant_battery',
        icon = 'PLACEHOLDER_PROTOTYPE.png',
        link_to = 'Leak-Resistant Battery',
        label = 'Leak-Resistant Battery',
        slot = {1, 5},
      },
      -- 4
      {
        id = 'battery_jumper',
        icon = 'PLACEHOLDER_PROTOTYPE.png',
        link_to = 'Battery Jumper',
        label = 'Battery Jumper',
        slot = {2, 1},
      },
      -- 5
      {
        id = 'lightning_rod',
        icon = 'ELEC-GENERATOR.png',
        link_to = 'Lightning Rod',
        label = 'Lightning Rod',
        slot = {2, 2},
      },
      -- 6
      {
        id = 'side_battery',
        icon = 'SIDE-BATTERY.png',
        link_to = 'Side Battery',
        label = 'Side Battery',
        slot = {2, 3},
      },
      -- 7
      {
        id = 'lead_acid_battery',
        icon = 'BACKSEAT-BATTERY.png',
        link_to = 'Lead-Acid Battery',
        label = 'Lead-Acid Battery',
        slot = {2, 4},
      },
      -- 8
      {
        id = 'high_capacity_battery',
        icon = 'EXPANDED-BACKSEAT-BATTERY.png',
        link_to = 'High-Capacity Battery',
        label = 'High-Capacity Battery',
        slot = {2, 5},
      },
      -- 9
      {
        id = 'xl_roof_battery',
        icon = 'ROOF-BATTERY.png',
        link_to = 'XL Roof Battery',
        label = 'XL Roof Battery',
        slot = {2, 6},
      },
      -- 10
      {
        id = 'mini_turbine',
        icon = 'Mini_Turbine_Icon.png',
        link_to = 'Mini Turbine',
        label = 'Mini Turbine',
        slot = {3, 3},
      },
      -- 11
      {
        id = 'solar_panel',
        icon = 'SOLAR-GENERATOR.png',
        link_to = 'Solar Panel',
        label = 'Solar Panel',
        slot = {3, 4},
      },
      -- 12
      {
        id = 'anchor_energy_converter',
        icon = 'BATTERY-GOBBLER.png',
        link_to = 'Anchor Energy Converter',
        label = 'Anchor Energy Converter',
        slot = {3, 6},
      },
    },
  },
  car_body = {
    canvas_slots = {7, 4},
    edges = {
      {2, 3},
      {5, 1},
      {5, 2},
      {7, 8},
      {8, 4},
      {8, 5},
      {8, 6},
      {11, 12},
      {12, 9},
      {12, 13},
      {12, 17},
      {13, 10},
      {13, 14},
      {15, 16},
      {16, 18},
      {16, 19},
      {16, 20},
      {19, 21},
      {19, 22},
    },
    nodes = {
      -- 1
      {
        id = 'anti_corrosive_bumper',
        icon = 'ACID-BUMPER.png',
        link_to = 'Anti-Corrosive Bumper',
        label = 'Anti-Corrosive Bumper',
        slot = {1, 1},
      },
      -- 2
      {
        id = 'olympium_bumper',
        icon = 'ACID-BUMPER.png',
        link_to = 'Olympium Bumper',
        label = 'Olympium Bumper',
        slot = {1, 2},
      },
      -- 3
      {
        id = 'powered_bumper',
        icon = 'POWERED-BUMPER.png',
        link_to = 'Powered Bumper',
        label = 'Powered Bumper',
        slot = {1, 3},
      },
      -- 4
      {
        id = 'lead_plated_bumper',
        icon = 'RADS-BUMPER.png',
        link_to = 'Lead-Plated Bumper',
        label = 'Lead-Plated Bumper',
        slot = {2, 1},
      },
      -- 5
      {
        id = 'armored_bumper',
        icon = 'HEAL-BUMPER.png',
        link_to = 'Armored Bumper',
        label = 'Armored Bumper',
        slot = {2, 2},
      },
      -- 6
      {
        id = 'insulated_bumper',
        icon = 'ELE-BUMPER.png',
        link_to = 'Insulated Bumper',
        label = 'Insulated Bumper',
        slot = {2, 3},
      },
      -- 7
      {
        id = 'crude_bumper',
        icon = 'Crude_Bumper.png',
        link_to = 'Crude Bumper',
        label = 'Crude Bumper',
        slot = {3, 1},
      },
      -- 8
      {
        id = 'steel_bumper',
        icon = 'Steel_Bumper.png',
        link_to = 'Steel Bumper',
        label = 'Steel Bumper',
        slot = {3, 2},
      },
      -- 9
      {
        id = 'lead_plated_panel',
        icon = 'RADS-PANEL.png',
        link_to = 'Lead-Plated Panel',
        label = 'Lead-Plated Panel',
        slot = {3, 3},
      },
      -- 10
      {
        id = 'anti_corrosive_panel',
        icon = 'POISON-PANEL.png',
        link_to = 'Anti-Corrosive Panel',
        label = 'Anti-Corrosive Panel',
        slot = {3, 4},
      },
      -- 11
      {
        id = 'crude_panel',
        icon = 'Crude_Panel.png',
        link_to = 'Crude Panel',
        label = 'Crude Panel',
        slot = {4, 1},
      },
      -- 12
      {
        id = 'steel_panel',
        icon = 'Steel_Panel.png',
        link_to = 'Steel Panel',
        label = 'Steel Panel',
        slot = {4, 2},
      },
      -- 13
      {
        id = 'armored_panel',
        icon = 'HEAL-PANEL.png',
        link_to = 'Armored Panel',
        label = 'Armored Panel',
        slot = {4, 3},
      },
      -- 14
      {
        id = 'olympium_panel',
        icon = 'PHYS-PANEL.png',
        link_to = 'Olympium Panel',
        label = 'Olympium Panel',
        slot = {4, 4},
      },
      -- 15
      {
        id = 'crude_door',
        icon = 'Crude_Door.png',
        link_to = 'Crude Door',
        label = 'Crude Door',
        slot = {5, 1},
      },
      -- 16
      {
        id = 'steel_door',
        icon = 'Steel_Door.png',
        link_to = 'Steel Door',
        label = 'Steel Door',
        slot = {5, 2},
      },
      -- 17
      {
        id = 'insulated_panel',
        icon = 'ELEC-PANEL.png',
        link_to = 'Insulated Panel',
        label = 'Insulated Panel',
        slot = {5, 3},
      },
      -- 18
      {
        id = 'insulated_door',
        icon = 'CAGED-STEEL-DOOR.png',
        link_to = 'Insulated Door',
        label = 'Insulated Door',
        slot = {6, 1},
      },
      -- 19
      {
        id = 'armored_door',
        icon = 'PLATED-DOOR.png',
        link_to = 'Armored Door',
        label = 'Armored Door',
        slot = {6, 2},
      },
      -- 20
      {
        id = 'lead_plated_door',
        icon = 'LEAD-SHIELD-DOOR.png',
        link_to = 'Lead-Plated Door',
        label = 'Lead-Plated Door',
        slot = {6, 3},
      },
      -- 21
      {
        id = 'olympium_door',
        icon = 'REINFORCED-DOOR.png',
        link_to = 'Olympium Door',
        label = 'Olympium Door',
        slot = {7, 2},
      },
      -- 22
      {
        id = 'anti_corrosive_door',
        icon = 'STEEL-FIBRE-DOOR.png',
        link_to = 'Anti-Corrosive Door',
        label = 'Anti-Corrosive Door',
        slot = {7, 3},
      },
    },
  },
  car_storage = {
    canvas_slots = {2, 3},
    edges = {
      {2, 1},
      {2, 3},
      {3, 4},
    },
    nodes = {
      -- 1
      {
        id = 'xl_roof_storage',
        icon = 'ROOF-STORAGE.png',
        link_to = 'XL Roof Storage',
        label = 'XL Roof Storage',
        slot = {1, 2},
      },
      -- 2
      {
        id = 'side_storage',
        icon = 'SIDE-STORAGE.png',
        link_to = 'Side Storage',
        label = 'Side Storage',
        slot = {2, 1},
      },
      -- 3
      {
        id = 'a_trunk_in_the_trunk',
        icon = 'STEEL-SHEET_Inverted.png',
        link_to = 'A Trunk In The Trunk',
        label = 'A Trunk in the Trunk',
        slot = {2, 2},
      },
      -- 4
      {
        id = 'augmented_trunk_storage',
        icon = 'TRUNK-T3.png',
        link_to = 'Augmented Trunk Storage',
        label = 'Augmented Trunk Storage',
        slot = {2, 3},
      },
    },
  },
  engines = {
    canvas_slots = {2, 3},
    edges = {
      {2, 3},
      {3, 1},
      {3, 4},
    },
    nodes = {
      -- 1
      {
        id = 'amp_engine',
        icon = 'AMP-Icon.png',
        link_to = 'AMP Engine',
        label = 'AMP Engine',
        slot = {1, 3},
      },
      -- 2
      {
        id = 'carbureted_engine',
        icon = 'Carbureted_Icon.png',
        link_to = 'Carbureted Engine',
        label = 'Carbureted Engine',
        slot = {2, 1},
      },
      -- 3
      {
        id = 'turbolight_engine',
        icon = 'Turbolight_Icon.png',
        link_to = 'Turbolight Engine',
        label = 'Turbolight Engine',
        slot = {2, 2},
      },
      -- 4
      {
        id = 'lim_chipped_engine',
        icon = 'LIM-Chipped-Icon.png',
        link_to = 'LIM-Chipped Engine',
        label = 'LIM-Chipped Engine',
        slot = {2, 3},
      },
    },
  },
  expansion_racks = {
    canvas_slots = {3, 5},
    edges = {
      {1, 2},
      {3, 4},
      {4, 1},
      {4, 5},
      {5, 6},
      {5, 7},
      {7, 8},
    },
    nodes = {
      -- 1
      {
        id = 'seat_rack_1',
        icon = 'PLACEHOLDER_PROTOTYPE.png',
        link_to = 'Seat Rack',
        label = 'Seat Rack',
        slot = {1, 3},
      },
      -- 2
      {
        id = 'seat_rack_2',
        icon = 'PLACEHOLDER_PROTOTYPE.png',
        link_to = 'Seat Rack',
        label = 'Seat Rack',
        slot = {1, 4},
      },
      -- 3
      {
        id = 'side_rack_1',
        icon = 'PLACEHOLDER_PROTOTYPE.png',
        link_to = 'Side Rack',
        label = 'Side Rack',
        slot = {2, 1},
      },
      -- 4
      {
        id = 'side_rack_2',
        icon = 'PLACEHOLDER_PROTOTYPE.png',
        link_to = 'Side Rack',
        label = 'Side Rack',
        slot = {2, 2},
      },
      -- 5
      {
        id = 'side_rack_3',
        icon = 'PLACEHOLDER_PROTOTYPE.png',
        link_to = 'Side Rack',
        label = 'Side Rack',
        slot = {2, 3},
      },
      -- 6
      {
        id = 'side_rack_4',
        icon = 'PLACEHOLDER_PROTOTYPE.png',
        link_to = 'Side Rack',
        label = 'Side Rack',
        slot = {2, 4},
      },
      -- 7
      {
        id = 'roof_rack_1',
        icon = 'PLACEHOLDER_PROTOTYPE.png',
        link_to = 'Roof Rack',
        label = 'Roof Rack',
        slot = {3, 4},
      },
      -- 8
      {
        id = 'roof_rack_2',
        icon = 'PLACEHOLDER_PROTOTYPE.png',
        link_to = 'Roof Rack',
        label = 'Roof Rack',
        slot = {3, 5},
      },
    },
  },
  fuel = {
    canvas_slots = {3, 5},
    edges = {
      {2, 3},
      {3, 4},
      {3, 7},
      {4, 1},
      {4, 5},
      {5, 6},
    },
    nodes = {
      -- 1
      {
        id = 'leak_resistant_fuel_tank',
        icon = 'LEAK-RESIST-FUEL.png',
        link_to = 'Leak-Resistant Fuel Tank',
        label = 'Leak-Resistant Fuel Tank',
        slot = {1, 4},
      },
      -- 2
      {
        id = 'large_fuel_can',
        icon = 'PLACEHOLDER_PROTOTYPE.png',
        link_to = 'Large Fuel Can',
        label = 'Large Fuel Can',
        slot = {2, 1},
      },
      -- 3
      {
        id = 'side_fuel_tank',
        icon = 'SIDE-FUEL.png',
        link_to = 'Side Fuel Tank',
        label = 'Side Fuel Tank',
        slot = {2, 2},
      },
      -- 4
      {
        id = 'backseat_tank',
        icon = 'BACKSEAT-FUEL.png',
        link_to = 'Backseat Tank',
        label = 'Backseat Tank',
        slot = {2, 3},
      },
      -- 5
      {
        id = 'backseat_tank',
        icon = 'EXPANDED-BACKSEAT-FUEL.png',
        link_to = 'Expanded Backseat Tank',
        label = 'Expanded Backseat Tank',
        slot = {2, 4},
      },
      -- 6
      {
        id = 'gas_reservoir',
        icon = 'ROOF-FUEL.png',
        link_to = 'Gas Reservoir',
        label = 'Gas Reservoir',
        slot = {2, 5},
      },
      -- 7
      {
        id = 'fuel_synthesizer',
        icon = 'FUEL-GENERATOR.png',
        link_to = 'Fuel Synthesizer',
        label = 'Fuel Synthesizer',
        slot = {3, 3},
      },
    },
  },
  -- Scanning Antenna, Matter Deconstructor, Expanded Locker, Pneumatic Locker, Parts Lockers,
  -- Outfitting Station, Vargas Auto-Pac-Vac, Rapid Refill Pump, Experimental Charging Station,
  -- Improved Antenna, Advanced Antenna, Incident Board, Junction Re-Stabilizer, Junction Bypass,
  -- Matter Regenerator, Investigator Module, Analysis Module, Advanced Workbench,
  -- Expanded Matter Regenerator, Repair Station, Detailing Station, F.A.X. Machine,
  -- Athletic Stimulation Station, Deco-Vend, Fish Tank, Smart Matter Deconstructor
  garage = {
    canvas_slots = {8, 6},
    edges = {},
    nodes = {},
  },
  lights = {
    canvas_slots = {6, 4},
    edges = {
      {3, 1},
      {3, 4},
      {5, 2},
      {5, 6},
      {6, 3},
      {8, 9},
      {9, 7},
      {9, 10},
      {9, 13},
      {11, 12},
      {12, 14},
    },
    nodes = {
      -- 1
      {
        id = 'biolantern',
        icon = 'Biolantern.png',
        no_icon_padding = true,
        link_to = 'Biolantern',
        label = 'Biolantern',
        slot = {1, 3},
      },
      -- 2
      {
        id = 'crude_flashlight',
        icon = 'Crude_Flashlight.png',
        no_icon_padding = true,
        link_to = 'Crude Flashlight',
        label = 'Crude Flashlight',
        slot = {2, 2},
      },
      -- 3
      {
        id = 'bioflare',
        icon = 'Bioflare.png',
        no_icon_padding = true,
        link_to = 'Bioflare',
        label = 'Bioflare',
        slot = {2, 3},
      },
      -- 4
      {
        id = 'bioflare',
        icon = 'Bioflare-Gun.png',
        no_icon_padding = true,
        link_to = 'Bioflare Gun',
        label = 'Bioflare Gun',
        slot = {2, 4},
      },
      -- 5
      {
        id = 'relightable_flare',
        icon = 'Relightable_Flare.png',
        no_icon_padding = true,
        link_to = 'Relightable Flare',
        label = 'Relightable Flare',
        slot = {3, 1},
      },
      -- 6
      {
        id = 'flare_gun',
        icon = 'Flare_Gun.png',
        no_icon_padding = true,
        link_to = 'Flare Gun',
        label = 'Flare Gun',
        slot = {3, 2},
      },
      -- 7
      {
        id = 'insulated_headlight',
        icon = 'Insulated_Headlight_Icon.png',
        link_to = 'Insulated Headlight',
        label = 'Insulated Headlight',
        slot = {3, 3},
      },
      -- 8
      {
        id = 'crude_headlight',
        icon = 'Crude_Headlight_Icon.png',
        link_to = 'Crude Headlight',
        label = 'Crude Headlight',
        slot = {4, 1},
      },
      -- 9
      {
        id = 'headlight',
        icon = 'Headlight_Icon.png',
        link_to = 'Headlight',
        label = 'Headlight',
        slot = {4, 2},
      },
      -- 10
      {
        id = 'reinforced_headlight',
        icon = 'PHYS-LIGHT.png',
        link_to = 'Reinforced Headlight',
        label = 'Reinforced Headlight',
        slot = {4, 3},
      },
      -- 11
      {
        id = 'side_floodlights',
        icon = 'SIDE-LIGHT.png',
        link_to = 'Side Floodlights',
        label = 'Side Floodlights',
        slot = {5, 1},
      },
      -- 12
      {
        id = 'roof_floodlights',
        icon = 'ROOF-ARRAY.png',
        link_to = 'Roof Floodlights',
        label = 'Roof Floodlights',
        slot = {5, 2},
      },
      -- 13
      {
        id = 'bio_headlight',
        icon = 'BIO-LIGHT.png',
        link_to = 'Bio Headlight',
        label = 'Bio Headlight',
        slot = {5, 3},
      },
      -- 14
      {
        id = 'auto_tracking_spotlight',
        icon = 'ROOF-LIGHT.png',
        link_to = 'Auto Tracking Spotlight',
        label = 'Auto Tracking Spotlight',
        slot = {6, 3},
      },
    },
  },
  refine = {
    canvas_slots = {1, 6},
    edges = {
      {1, 2},
      {2, 3},
      {3, 4},
      {4, 5},
      {5, 6},
    },
    nodes = {
      -- 1
      {
        id = 'gear',
        icon = 'GEAR.png',
        link_to = 'Gear',
        label = 'Gear',
        slot = {1, 1},
      },
      -- 2
      {
        id = 'bulb',
        icon = 'BULB.png',
        link_to = 'Bulb',
        label = 'Bulb',
        slot = {1, 2},
      },
      -- 3
      {
        id = 'steel_sheet',
        icon = 'STEEL-SHEET_Inverted.png',
        link_to = 'Steel Sheet',
        label = 'Steel Sheet',
        slot = {1, 3},
      },
      -- 4
      {
        id = 'circuit_board',
        icon = 'BREADBOARD.png',
        link_to = 'Circuit Board',
        label = 'Circuit Board',
        slot = {1, 4},
      },
      -- 5
      {
        id = 'carbonfiberglass',
        icon = 'CARBON-FIBREGLASS.png',
        link_to = 'Carbonfiberglass',
        label = 'Carbonfiberglass',
        slot = {1, 5},
      },
      -- 6
      {
        id = 'lim_chip',
        icon = 'LIM-CHIP.png',
        link_to = 'LIM Chip',
        label = 'LIM Chip',
        slot = {1, 6},
      },
    },
  },
  survival_tools = {
    canvas_slots = {6, 3},
    edges = {
      {2, 1},
      {4, 2},
      {4, 5},
      {5, 3},
      {7, 8},
      {7, 11},
      {8, 6},
      {8, 9},
      {10, 13},
      {10, 14},
      {11, 12},
      {14, 15},
    },
    nodes = {
      -- 1
      {
        id = 'olympium_torch',
        icon = 'PLACEHOLDER_PROTOTYPE.png',
        link_to = 'Olympium Torch',
        label = 'Olympium Torch',
        slot = {1, 3},
      },
      -- 2
      {
        id = 'blowtorch',
        icon = 'PLACEHOLDER_PROTOTYPE.png',
        link_to = 'Blowtorch',
        label = 'Blowtorch',
        slot = {2, 2},
      },
      -- 3
      {
        id = 'electricians_kit',
        icon = 'Electricians_Kit.png',
        link_to = 'Electrician\'s Kit',
        label = 'Electrician\'s Kit',
        slot = {2, 3},
      },
      -- 4
      {
        id = 'repair_putty',
        icon = 'REPAIR-PUTTY.png',
        link_to = 'Repair Putty',
        label = 'Repair Putty',
        slot = {3, 1},
      },
      -- 5
      {
        id = 'light_replacement_kit',
        icon = 'Light_Replacement_Kit.png',
        link_to = 'Light Replacement Kit',
        label = 'Light Replacement Kit',
        slot = {3, 2},
      },
      -- 6
      {
        id = 'thermal_vacuum',
        icon = 'Thermal_Vacuum.png',
        link_to = 'Thermal Vacuum',
        label = 'Thermal Vacuum',
        slot = {3, 3},
      },
      -- 7
      {
        id = 'scrapper',
        icon = 'Scrapper.png',
        link_to = 'Scrapper',
        label = 'Scrapper',
        slot = {4, 1},
      },
      -- 8
      {
        id = 'hand_vac',
        icon = 'Hand-Vac.png',
        link_to = 'Hand-Vac',
        label = 'Hand-Vac',
        slot = {4, 2},
      },
      -- 9
      {
        id = 'plasma_scrapper',
        icon = 'Plasma_Scrapper.png',
        link_to = 'Plasma Scrapper',
        label = 'Plasma Scrapper',
        slot = {4, 3},
      },
      -- 10
      {
        id = 'prybar',
        icon = 'Prybar.png',
        link_to = 'Prybar',
        label = 'Prybar',
        slot = {5, 1},
      },
      -- 11
      {
        id = 'liberator',
        icon = 'Liberator.png',
        link_to = 'Liberator',
        label = 'Liberator',
        slot = {5, 2},
      },
      -- 12
      {
        id = 'liberator_mk2',
        icon = 'PLACEHOLDER_PROTOTYPE.png',
        link_to = 'Liberator Mk. 2',
        label = 'Liberator Mk. 2',
        slot = {5, 3},
      },
      -- 13
      {
        id = 'anchor_radar',
        icon = 'Anchor_Radar.png',
        link_to = 'Anchor Radar',
        label = 'Anchor Radar',
        slot = {6, 1},
      },
      -- 14
      {
        id = 'impact_hammer',
        icon = 'Impact_Hammer.png',
        link_to = 'Impact Hammer',
        label = 'Impact Hammer',
        slot = {6, 2},
      },
      -- 15
      {
        id = 'magnetic_hammer',
        icon = 'Magnetic_Hammer.png',
        link_to = 'Magnetic Hammer',
        label = 'Magnetic Hammer',
        slot = {6, 3},
      },
    },
  },
  utilities = {
    canvas_slots = {3, 5},
    edges = {
      {4, 1},
      {4, 5},
      {4, 9},
      {5, 6},
      {5, 10},
      {6, 2},
      {6, 7},
      {6, 11},
      {7, 3},
      {7, 8},
      {7, 12},
    },
    nodes = {
      -- 1
      {
        id = 'the_auto_parker',
        icon = 'PARKING-MANOUVER.png',
        link_to = 'The Auto Parker',
        label = 'The Auto Parker',
        slot = {1, 2},
      },
      -- 2
      {
        id = 'the_lazarus_device',
        icon = 'LIFESAVER.png',
        link_to = 'The Lazarus Device',
        label = 'The Lazarus Device',
        slot = {1, 4},
      },
      -- 3
      {
        id = 'chrono_dilator',
        icon = 'TIME-MANOUVER.png',
        link_to = 'Chrono Dilator',
        label = 'Chrono Dilator',
        slot = {1, 5},
      },
      -- 4
      {
        id = 'limpulse_emitter',
        icon = 'PHYS-WAVE-SHIELD.png',
        link_to = 'Limpulse Emitter',
        label = 'Limpulse Emitter',
        slot = {2, 1},
      },
      -- 5
      {
        id = 'jump_jacks',
        icon = 'JUMP-MANOUVER.png',
        link_to = 'Jump Jacks',
        label = 'Jump Jacks',
        slot = {2, 2},
      },
      -- 6
      {
        id = 'ion_shield',
        icon = 'ROOF-RAD-SHIELD.png',
        link_to = 'Ion Shield',
        label = 'Ion Shield',
        slot = {2, 3},
      },
      -- 7
      {
        id = 'mobile_workbench',
        icon = 'BENCH-T1.png',
        link_to = 'Mobile Workbench',
        label = 'Mobile Workbench',
        slot = {2, 4},
      },
      -- 8
      {
        id = 'magnetic_bumper',
        icon = 'MAGNET-BUMPER.png',
        link_to = 'Magnetic Bumper',
        label = 'Magnetic Bumper',
        slot = {2, 5},
      },
      -- 9
      {
        id = 'resource_radar',
        icon = 'RESOURCE-SCANNER.png',
        link_to = 'Resource Radar',
        label = 'Resource Radar',
        slot = {3, 2},
      },
      -- 10
      {
        id = 'juke_jets',
        icon = 'SIDE-THRUSTERS-MANOUVER.png',
        link_to = 'Juke Jets',
        label = 'Juke Jets',
        slot = {3, 3},
      },
      -- 11
      {
        id = 'nitro_boost',
        icon = 'BOOST-MANOUVER.png',
        link_to = 'Nitro Boost',
        label = 'Nitro Boost',
        slot = {3, 4},
      },
      -- 12
      {
        id = 'anti_grav_emitter',
        icon = 'GRAVITY-MANOUVER.png',
        link_to = 'Anti-Grav Emitter',
        label = 'Anti-Grav Emitter',
        slot = {3, 5},
      },
    },
  },
  wheels = {
    canvas_slots = {3, 5},
    edges = {
      {2, 3},
      {3, 4},
      {4, 1},
      {4, 5},
      {4, 7},
      {5, 6},
    },
    nodes = {
      -- 1
      {
        id = 'puncture_proof_tire',
        icon = 'PUNCTURE-RESISTANT-TIRE.png',
        link_to = 'Puncture-Proof Tire',
        label = 'Puncture-Proof Tire',
        slot = {1, 4},
      },
      -- 2
      {
        id = 'spare_tire',
        icon = 'Spare_Tire_Icon.png',
        link_to = 'Spare Tire',
        label = 'Spare Tire',
        slot = {2, 1},
      },
      -- 3
      {
        id = 'summer_tire',
        icon = 'Summer_Tire_Icon.png',
        link_to = 'Summer Tire',
        label = 'Summer Tire',
        slot = {2, 2},
      },
      -- 4
      {
        id = 'offroad_tire',
        icon = 'OFFROAD-TIRE.png',
        link_to = 'Offroad Tire',
        label = 'Offroad Tire',
        slot = {2, 3},
      },
      -- 5
      {
        id = 'all_terrain_tire',
        icon = 'ADAPTIVE-TIRE.png',
        link_to = 'All-Terrain Tire',
        label = 'All-Terrain Tire',
        slot = {2, 4},
      },
      -- 6
      {
        id = 'power_grip_tire',
        icon = 'POWER-GRIP-TIRE.png',
        link_to = 'Power Grip Tires',
        label = 'Power Grip Tires',
        slot = {2, 5},
      },
      -- 7
      {
        id = 'paddle_tire',
        icon = 'WATER-TIRE.png',
        link_to = 'Paddle Tire',
        label = 'Paddle Tire',
        slot = {3, 4},
      },
    },
  },
}

function css_px( n )
  return string.format('%spx', n)
end

-- returns inverted order because cols produce x, rows produce y
function get_canvas_size( tab )
  return {
    p.consts.canvas_padding * 2 + tab.canvas_slots[2] * p.consts.node_size[2] + (tab.canvas_slots[2] - 1) * p.consts.node_spacing[2],
    p.consts.canvas_padding * 2 + tab.canvas_slots[1] * p.consts.node_size[1] + (tab.canvas_slots[1] - 1) * p.consts.node_spacing[1],
  }
end

-- returns inverted order because cols produce x, rows produce y
function get_node_anchor_pos( node_slot )
  return {
    p.consts.canvas_padding + (node_slot[2] - 1) * (p.consts.node_size[2] + p.consts.node_spacing[2]),
    p.consts.canvas_padding + (node_slot[1] - 1) * (p.consts.node_size[1] + p.consts.node_spacing[1]),
  }
end

-- does not return inverted order because it's already been inverted by get_node_anchor_pos
function get_node_graph_anchor_pos( node_slot )
  local anchor_pos = get_node_anchor_pos(node_slot)
  local offset_x = p.consts.node_size[1] / 2
  local offset_y = offset_x - p.consts.node_padding[1] - p.consts.icon_edge_thickness
  return { anchor_pos[1] + offset_x, anchor_pos[2] + offset_y }
end

function render_node_label( html_ref, node )
  return html_ref
    :tag('div')
    :css({
      ['font-family'] = 'var(--font-family-monospace)',
      ['font-size'] = css_px(p.consts.label_font_size),
      ['line-height'] = '1em',
      ['color'] = 'white',
      ['width'] = css_px(p.consts.node_icon_container_size),
      ['text-align'] = 'end'
    })
    :wikitext(node.label)
    :done()
end

function render_node_icon( html_ref, node )
  return html_ref
    :tag('div')
    :css({
      ['position'] = 'relative',
      ['border'] = '6px double white',
      ['width'] = css_px(p.consts.node_icon_container_size),
      ['height'] = css_px(p.consts.node_icon_container_size),
      ['padding'] = node.no_icon_padding and '0px' or css_px(p.consts.node_icon_padding),
    })
    :wikitext(string.format(p.consts.wikitext_template_link, node.link_to))
    :newline()
    :wikitext(string.format(p.consts.wikitext_template_icon, node.icon, p.consts.node_icon_size + (node.no_icon_padding and p.consts.node_icon_padding * 2 or 0)))
    :done()
end

function render_node( html_ref, node )
  local res = html_ref
  local anchor_pos = get_node_anchor_pos(node.slot)
  local padding = p.consts.node_padding
  res = html_ref
    :tag('div')
    :css({
      ['position'] = 'absolute',
      ['padding'] = string.format('%spx %spx %spx %spx', padding[1], padding[2], padding[3], padding[4]),
      ['width'] = css_px(p.consts.node_size[1]),
      ['height'] = css_px(p.consts.node_size[2]),
      ['background-color'] = p.consts.bg_color,
      ['transform'] = string.format('translate(%spx, %spx)', anchor_pos[1], anchor_pos[2])
    })
  res = render_node_icon(res, node)
  res = render_node_label(res, node)
  return res:done()
end

function render_edge( html_ref, node_slot_1, node_slot_2 )
  local pos_1 = get_node_graph_anchor_pos(node_slot_1)
  local pos_2 = get_node_graph_anchor_pos(node_slot_2)
  local delta_x = pos_2[1] - pos_1[1]
  local delta_y = pos_2[2] - pos_1[2]
  local edge_len = (delta_x ^ 2 + delta_y ^ 2) ^ 0.5
  local center_tgt = { pos_1[1] + delta_x / 2, pos_1[2] + delta_y / 2 }
  local center_orig = { edge_len / 2, 0 }
  local translate_by = { center_tgt[1] - center_orig[1], center_tgt[2] - center_orig[2] }
  local rotate_by = math.atan(delta_y / delta_x)
  
  return html_ref
    :tag('div')
    :css({
      ['position'] = 'absolute',
      ['width'] = css_px(edge_len),
      ['height'] = css_px(p.consts.icon_edge_thickness),
      ['background-color'] = 'white',
      ['transform'] = string.format('translate(%spx, %spx) rotate(%srad)', translate_by[1], translate_by[2], rotate_by)
    })
    :done()
end

function p.main(frame)
  local tab_name = frame.args.tab
  local tab = p.tabs[tab_name]
  local tab_canvas_size = get_canvas_size(tab)
  local viewport_size = {
    tab_canvas_size[1] + 10,
    math.min(tab_canvas_size[2], 390) + 10
  }
  
  local html_ref = mw.html.create('div')
    :css({
      ['max-width'] = '100%',
      ['width'] = css_px(viewport_size[1]),
      ['height'] = css_px(viewport_size[2]),
      ['overflow'] = 'auto'
    })
    :tag('div')
    :css({
      ['position'] = 'relative',
      ['width'] = css_px(tab_canvas_size[1]),
      ['height'] = css_px(tab_canvas_size[2]),
      ['background-color'] = p.consts.bg_color
    })
  for k, v in pairs(tab.edges) do
    html_ref = render_edge(html_ref, tab.nodes[v[1]].slot, tab.nodes[v[2]].slot)
  end
  for k, v in pairs(tab.nodes) do
    html_ref = render_node(html_ref, v)
  end
  return tostring(html_ref:allDone())
end

return p