From ce20d829eb4ad93eb2664211bde336bd0bde2803 Mon Sep 17 00:00:00 2001 From: "paul.mattes" Date: Sat, 31 Jan 2026 12:31:55 +0100 Subject: [PATCH 1/2] added bundle filtering feature --- LANGUAGE_SPEC.md | 43 + README.md | 1 + doc/03_signals_and_types.md | 20 + doc/05_entities.md | 35 + doc/07_advanced_concepts.md | 22 + doc/ENTITY_REFERENCE.md | 2568 +++++++++++------ doc/generate_entity_docs.py | 1054 ++++--- dsl_compiler/src/common/constants.py | 2 +- dsl_compiler/src/common/entity_data.py | 24 + dsl_compiler/src/ir/builder.py | 52 + dsl_compiler/src/layout/connection_planner.py | 15 +- dsl_compiler/src/layout/entity_placer.py | 7 + .../src/lowering/expression_lowerer.py | 82 +- .../src/lowering/tests/test_bundles.py | 159 + dsl_compiler/src/semantic/analyzer.py | 71 +- example_programs/04_binary_clock.facto | 2 +- .../18_comprehensive_entities.facto | 9 + pyproject.toml | 2 +- 18 files changed, 2719 insertions(+), 1449 deletions(-) diff --git a/LANGUAGE_SPEC.md b/LANGUAGE_SPEC.md index 6c50733..c861e8a 100644 --- a/LANGUAGE_SPEC.md +++ b/LANGUAGE_SPEC.md @@ -384,6 +384,49 @@ Signal allBelow1000 = all(levels) < 1000; # True (all values match) - `any()` compiles to a decider combinator outputting `signal-anything` - `all()` compiles to a decider combinator outputting `signal-everything` +#### Bundle Filtering + +Filter a bundle to include only signals that pass a comparison using the conditional output syntax: + +```facto +Bundle requested_items = { ("signal-A", 10), ("signal-B", 5), ("signal-C", -3) }; + +# Keep only signals > 0 (preserving their values) +Bundle positive_items = (requested_items > 0) : requested_items; +# Result: signal-A=10, signal-B=5 (signal-C=-3 is filtered out) + +# Filter with different comparison operators +Bundle above_threshold = (requested_items >= 8) : requested_items; # Only signal-A=10 +Bundle non_zero = (requested_items != 0) : requested_items; # All signals (none are zero) +``` + +**Syntax:** `(bundle COMPARISON scalar) : output` + +The comparison evaluates each signal in the bundle against the scalar value. Only signals that pass the comparison are included in the output. + +**Output Modes:** + +1. **Preserve values** (output is a bundle): The original signal values are preserved + ```facto + Bundle filtered = (items > 0) : items; # copy_count_from_input = true + ``` + +2. **Constant output** (output is an integer): Each matching signal outputs the constant value + ```facto + Bundle counts = (items > 0) : 1; # Each matching signal outputs 1 + ``` + +**Factorio Output:** Compiles to a single decider combinator: +- Input signal: `signal-each` (evaluates each signal independently) +- Comparison: The specified operator and scalar value +- Output signal: `signal-each` (outputs matching signals) +- Output mode: `copy_count_from_input` (true for bundle output, false for constant) + +**Common Use Cases:** +- Filtering inventory requests to only positive quantities +- Separating signals by range (e.g., high vs low values) +- Counting signals that meet a condition + #### Bundle Use Cases Bundles are ideal for: diff --git a/README.md b/README.md index 9f59fe7..2deb679 100644 --- a/README.md +++ b/README.md @@ -145,6 +145,7 @@ Bundle resources = { ("iron-plate", 100), ("copper-plate", 80), ("coal", 50) }; Bundle doubled = resources * 2; # Double all values Signal iron = resources["iron-plate"]; # Access a single signal Signal anyLow = any(resources) < 20; # True if any resource is below 20 +Bundle positive = (resources > 0) : resources; # Filter to only positive values ``` Bundles are powerful for building generic circuits like balanced train loaders that work regardless of what items you're loading. diff --git a/doc/03_signals_and_types.md b/doc/03_signals_and_types.md index f4592cb..c1b56c0 100644 --- a/doc/03_signals_and_types.md +++ b/doc/03_signals_and_types.md @@ -440,6 +440,26 @@ Signal allBelow1000 = all(levels) < 1000; # True - `any()` compiles to `signal-anything` - `all()` compiles to `signal-everything` +### Bundle Filtering + +Filter a bundle to keep only signals that pass a comparison: + +```facto +Bundle requests = { ("signal-A", 10), ("signal-B", 5), ("signal-C", -3) }; + +# Keep only positive values (preserving their counts) +Bundle pending = (requests > 0) : requests; +# Result: signal-A=10, signal-B=5 (signal-C is filtered out) + +# Output constant 1 for each matching signal +Bundle counts = (requests > 0) : 1; +# Result: signal-A=1, signal-B=1 +``` + +This compiles to a single decider combinator using `signal-each`. The syntax is `(bundle COMPARISON scalar) : output`, where output can be: +- A bundle: preserves original signal values +- An integer: outputs that constant for each matching signal + --- ## For Loops diff --git a/doc/05_entities.md b/doc/05_entities.md index 7f3d307..9df0d75 100644 --- a/doc/05_entities.md +++ b/doc/05_entities.md @@ -133,6 +133,41 @@ The `.output` property returns a **Bundle** — all signals the entity outputs. - Balancing item distribution - Controlling production based on storage +### Wiring Signals to Entities: `.input` + +To wire signals to an entity's circuit input, use `.input`: + +```facto +Bundle resources = { ("iron-plate", 100), ("copper-plate", 80) }; +Entity selector = place("selector-combinator", 0, 0, { + operation: "count", + count_signal: "signal-C" +}); +selector.input = resources; # Wire resources to selector's input +``` + +The `.input` property creates a wire connection from a signal source to the entity: + +- **For dual-connector entities** (arithmetic, decider, selector combinators): Wires connect to the **input side** of the combinator +- **For single-connector entities** (lamps, chests, inserters): Wires connect to the entity's circuit connection + +**Common uses:** +- Providing input signals to combinators +- Connecting signal sources to entities for circuit control +- Building circuits where explicit wiring is needed + +**Example with selector combinator:** +```facto +# Count unique signals +Bundle items = chest.output; +Entity counter = place("selector-combinator", 0, 0, { + operation: "count", + count_signal: "signal-C" +}); +counter.input = items; # Wire chest contents to selector +Signal count = counter.output["signal-C"]; # Read the count +``` + ### Balanced Loader Example ```facto diff --git a/doc/07_advanced_concepts.md b/doc/07_advanced_concepts.md index 7af879d..6cb7562 100644 --- a/doc/07_advanced_concepts.md +++ b/doc/07_advanced_concepts.md @@ -427,6 +427,28 @@ Signal c = data["signal-C"]; Signal weighted = (a * 3 + b * 2 + c * 1) / 6; ``` +### Bundle Filtering + +Filter bundles to process only signals meeting a condition: + +```facto +Bundle inventory = { + ("iron-plate", 500), + ("copper-plate", 200), + ("coal", -50), # Negative = deficit + ("stone", 0) +}; + +# Get only items we have (positive counts) +Bundle have = (inventory > 0) : inventory; + +# Get deficit signals as positive values for requesting +Bundle deficits = (inventory < 0) : (inventory * -1); + +# Count how many item types meet a threshold +Bundle above_threshold = (inventory > 100) : 1; +``` + --- ## For Loop Patterns diff --git a/doc/ENTITY_REFERENCE.md b/doc/ENTITY_REFERENCE.md index ea025f9..a34038e 100644 --- a/doc/ENTITY_REFERENCE.md +++ b/doc/ENTITY_REFERENCE.md @@ -1,6 +1,6 @@ # Entity Reference for Facto -**Generated:** 2025-12-21 +**Generated:** 2026-01-29 **Draftsman version:** 3.2.0 This is the **complete reference** for all entities available in the DSL. @@ -9,6 +9,7 @@ Each entity lists its prototypes, circuit I/O capabilities, and all settable pro ## Table of Contents - [Using Entities in the DSL](#using-entities-in-the-dsl) +- [Reading Entity Outputs](#reading-entity-outputs) - [Enum Reference](#enum-reference) - [Combinators](#combinators) - [Lamps & Displays](#lamps--displays) @@ -23,10 +24,22 @@ Each entity lists its prototypes, circuit I/O capabilities, and all settable pro - [Robots & Logistics](#robots--logistics) - [Space](#space) - [Misc](#misc) -- [Uncategorized Entities](#uncategorized-entities) +- [Other Entities](#other-entities) ## Using Entities in the DSL +### How the Compiler Handles Entities + +When you use `place()` in Facto, the compiler creates a corresponding +[Draftsman](https://github.com/redruin1/factorio-draftsman) entity object. +Properties specified in the placement object (the `{...}` part) are passed directly +to Draftsman as Python attributes during entity construction. The compiler validates +that property names and types match what Draftsman expects for that entity class. + +Circuit-controlled properties (like `entity.enable = expression`) are handled differently: +the compiler generates the necessary combinator logic and wire connections to implement +the circuit behavior, then sets the appropriate control properties on the entity. + ### Placement Syntax ```facto @@ -35,20 +48,21 @@ Entity name = place("prototype-name", x, y, {prop1: value1, prop2: value2}); ### Setting Properties -**At placement time** (in the property dictionary): +**At placement time:** ```facto Entity lamp = place("small-lamp", 0, 0, {use_colors: 1, color_mode: 1}); ``` -**After placement** (for circuit-controlled values): +**After placement (circuit-controlled):** ```facto -lamp.enable = signal > 0; # Control based on circuit signal -lamp.r = red_value; # Dynamic RGB control +lamp.enable = signal > 0; +lamp.r = red_value; ``` -### Reading Entity Outputs +## Reading Entity Outputs + +Most entities can output circuit signals. Access them using `.output`: -**Access entity circuit outputs** using `entity.output`: ```facto Entity combinator = place("arithmetic-combinator", 0, 0, {...}); Signal result = combinator.output; # Read the combinator's output signal @@ -56,6 +70,29 @@ Signal result = combinator.output; # Read the combinator's output signal This is particularly useful for reading values from combinators, sensors, and other entities that produce circuit signals. +## Wiring Signals to Entity Inputs + +To wire signals to an entity's circuit input, use `.input`: + +```facto +Bundle resources = { ("iron-plate", 100), ("copper-plate", 80) }; +Entity selector = place("selector-combinator", 0, 0, { + operation: "count", + count_signal: "signal-C" +}); +selector.input = resources; # Wire resources to selector's input +``` + +The `.input` property creates a wire connection from a signal source to the entity: + +- **For dual-connector entities** (arithmetic, decider, selector combinators): Wires connect to the **input side** of the combinator +- **For single-connector entities** (lamps, chests, inserters): Wires connect to the entity's circuit connection + +This is useful for: +- Providing input signals to combinators +- Connecting signal sources to entities for circuit control +- Building circuits where explicit wiring is needed + ### Enum Properties Enum properties accept **integer values**. See the [Enum Reference](#enum-reference) for all values. @@ -73,10 +110,12 @@ Entity lamp = place("small-lamp", 0, 0, {use_colors: 1, always_on: 1}); ## Enum Reference -When setting enum properties in the DSL, use the **integer value**. -This section lists all enums used by entity properties. +When setting enum properties in the DSL, use the **integer value** for IntEnums, +or the **string value** for Literal types. + +### Integer Enums -### LampColorMode +#### LampColorMode | DSL Value | Enum Name | |-----------|-----------| @@ -84,7 +123,7 @@ This section lists all enums used by entity properties. | `1` | COMPONENTS | | `2` | PACKED_RGB | -### Direction +#### Direction | DSL Value | Enum Name | |-----------|-----------| @@ -105,7 +144,7 @@ This section lists all enums used by entity properties. | `14` | NORTHWEST | | `15` | NORTHNORTHWEST | -### InserterModeOfOperation +#### InserterModeOfOperation | DSL Value | Enum Name | |-----------|-----------| @@ -115,14 +154,14 @@ This section lists all enums used by entity properties. | `3` | NONE | | `4` | SET_STACK_SIZE | -### InserterReadMode +#### InserterReadMode | DSL Value | Enum Name | |-----------|-----------| | `0` | PULSE | | `1` | HOLD | -### BeltReadMode +#### BeltReadMode | DSL Value | Enum Name | |-----------|-----------| @@ -130,14 +169,14 @@ This section lists all enums used by entity properties. | `1` | HOLD | | `2` | HOLD_ALL_BELTS | -### FilterMode +#### FilterMode | DSL Value | Enum Name | |-----------|-----------| | `0` | WHITELIST | | `1` | BLACKLIST | -### LogisticModeOfOperation +#### LogisticModeOfOperation | DSL Value | Enum Name | |-----------|-----------| @@ -145,14 +184,14 @@ This section lists all enums used by entity properties. | `1` | SET_REQUESTS | | `2` | NONE | -### MiningDrillReadMode +#### MiningDrillReadMode | DSL Value | Enum Name | |-----------|-----------| | `0` | UNDER_DRILL | | `1` | TOTAL_PATCH | -### SiloReadMode +#### SiloReadMode | DSL Value | Enum Name | |-----------|-----------| @@ -168,34 +207,132 @@ This section lists all enums used by entity properties. | `1` | LOGISTICS | | `2` | MISSING_REQUESTS | +### String Enums (Literal Types) + +These properties accept string values. Use the exact string shown. + +#### ArithmeticOperation + +| Valid Values | +|-------------| +| `"*"` | +| `"/"` | +| `"+"` | +| `"-"` | +| `"%"` | +| `"^"` | +| `"<<"` | +| `">>"` | +| `"AND"` | +| `"OR"` | +| `"XOR"` | + +#### FilterMode + +| Valid Values | +|-------------| +| `"whitelist"` | +| `"blacklist"` | + +#### IOType + +| Valid Values | +|-------------| +| `"input"` | +| `"output"` | + +#### InfinityMode + +| Valid Values | +|-------------| +| `"at-least"` | +| `"at-most"` | +| `"exactly"` | +| `"add"` | +| `"remove"` | + +#### PlaybackMode + +| Valid Values | +|-------------| +| `"local"` | +| `"surface"` | +| `"global"` | + +#### QualityID + +| Valid Values | +|-------------| +| `"normal"` | +| `"uncommon"` | +| `"rare"` | +| `"epic"` | +| `"legendary"` | +| `"quality-unknown"` | + +#### SelectorOperation + +| Valid Values | +|-------------| +| `"select"` | +| `"count"` | +| `"random"` | +| `"stack-size"` | +| `"rocket-capacity"` | +| `"quality-filter"` | +| `"quality-transfer"` | + +#### SplitterPriority + +| Valid Values | +|-------------| +| `"left"` | +| `"none"` | +| `"right"` | + +#### SpoilPriority + +| Valid Values | +|-------------| +| `"spoiled-first"` | +| `"fresh-first"` | + ## Combinators ### ArithmeticCombinator **Description:** An arithmetic combinator. Peforms a mathematical or bitwise operation on circuit signals. +**Draftsman Source:** [ArithmeticCombinator class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/arithmetic_combinator.py) + **Prototypes:** `"arithmetic-combinator"` -**Connection Type:** Dual circuit connection (separate input and output sides) +**Connection Type:** Dual circuit (separate input/output sides) -#### Circuit Signal I/O +#### Reading Entity Output -**Signal Outputs:** +Use `entity.output` to read: **Computed arithmetic result** -| Signal Property | Signal Type | Description | Enable With | -|-----------------|-------------|-------------|-------------| -| `output_signal` | Result value | Signal to output the combinator result on | Always active | +```facto +Entity e = place("arithmetic-combinator", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` #### DSL Examples ```facto -# Note: Combinators are typically generated by the compiler -Signal result = input * 2 + offset; # Creates ArithmeticCombinator(s) +# Note: Arithmetic combinators are usually auto-generated +Signal result = input * 2 + offset; # Creates combinator(s) + +# Manual placement if needed +Entity arith = place("arithmetic-combinator", 0, 0, { + operation: "+" +}); ``` #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| @@ -204,32 +341,51 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = | `first_operand_wires` | Complex (see draftsman docs) ⚠️ | (factory) | | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `operation` | One of: *, /, +, -, %, ^, <<, >>, AND, OR, XOR | "*" | | +| `operation` | One of: `"*"`, `"/"`, `"+"`, `"-"`, `"%"`, `"^"`, `"<<"`, `">>"`, `"AND"`, `"OR"`, `"XOR"` ([ArithmeticOperation](#arithmeticoperation)) | "*" | `"*"` | | `player_description` | String | "" | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | | `second_operand` | Integer | 0 | | | `second_operand_wires` | Complex (see draftsman docs) ⚠️ | (factory) | | +#### Signal Configuration + +Properties for configuring which signals the entity uses: + +| Property | Type | Description | +|----------|------|-------------| +| `output_signal` | String (signal name, e.g. `"signal-A"`) | The output signal of the ``ArithmeticCombinator``. Cannot be... | + --- ### DeciderCombinator **Description:** A decider combinator. Makes comparisons based on circuit network inputs. +**Draftsman Source:** [DeciderCombinator class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/decider_combinator.py) + **Prototypes:** `"decider-combinator"` -**Connection Type:** Dual circuit connection (separate input and output sides) +**Connection Type:** Dual circuit (separate input/output sides) + +#### Reading Entity Output + +Use `entity.output` to read: **Conditional output signals** + +```facto +Entity e = place("decider-combinator", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` #### DSL Examples ```facto -# Note: Combinators are typically generated by the compiler -Signal flag = (count > 100) : 1; # Creates DeciderCombinator +# Note: Decider combinators are usually auto-generated +Signal flag = (count > 100) : 1; # Creates decider ``` #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| @@ -239,7 +395,7 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = | `name` | String (entity prototype name) | (factory) | | | `outputs` | List (complex) ⚠️ | (factory) | | | `player_description` | String | "" | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- @@ -247,29 +403,40 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = **Description:** A combinator that holds a number of constant signals that can be output to the circuit network. +**Draftsman Source:** [ConstantCombinator class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/constant_combinator.py) + **Prototypes:** `"constant-combinator"` **Connection Type:** Single circuit connection +#### Reading Entity Output + +Use `entity.output` to read: **Constant signal values** + +```facto +Entity e = place("constant-combinator", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` + #### DSL Examples ```facto -# Note: Constant combinators are typically generated by the compiler -Signal constant = 42; # Creates ConstantCombinator +# Note: Constants are usually auto-generated +Signal constant = 42; # Creates constant combinator ``` #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| | `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | -| `enabled` | Boolean (0/1) | true | `1` | +| `enabled` | Boolean (`0` or `1`) | 1 | `1` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | | `player_description` | String | "" | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | | `sections` | List (complex) ⚠️ | (factory) | | --- @@ -278,29 +445,36 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = **Description:** (Factorio 2.0) +**Draftsman Source:** [SelectorCombinator class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/selector_combinator.py) + **Prototypes:** `"selector-combinator"` -**Connection Type:** Dual circuit connection (separate input and output sides) +**Connection Type:** Dual circuit (separate input/output sides) -#### Circuit Signal I/O +#### Reading Entity Output -**Signal Inputs:** +Use `entity.output` to read: **Selected/filtered signals** -| Signal Property | Signal Type | Description | Enable With | -|-----------------|-------------|-------------|-------------| -| `index_signal` | Integer | Signal for selector index input | Always active | -| `quality_source_signal` | Any signal | Signal to read quality from | Always active | +```facto +Entity e = place("selector-combinator", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` -**Signal Outputs:** +#### DSL Examples -| Signal Property | Signal Type | Description | Enable With | -|-----------------|-------------|-------------|-------------| -| `count_signal` | Integer | Signal to output the count result | Always active | -| `quality_destination_signal` | Quality level | Signal to output quality result | Always active | +```facto +# Selector in count mode +Entity counter = place("selector-combinator", 0, 0, { + operation: "count", + count_signal: "signal-C" +}); +# Reading output +Bundle result = counter.output; +``` #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| @@ -308,14 +482,25 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = | `index_constant` | Integer | 0 | | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `operation` | String (see type for valid values) | "select" | | +| `operation` | One of: `"select"`, `"count"`, `"random"`, `"stack-size"`, `"rocket-capacity"`, `"quality-filter"`, `"quality-transfer"` ([SelectorOperation](#selectoroperation)) | "select" | `"select"` | | `player_description` | String | "" | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | | `quality_filter` | Complex (see draftsman docs) ⚠️ | (factory) | | -| `quality_source_static` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality_source_static` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | | `random_update_interval` | Integer | 0 | | -| `select_max` | Boolean (0/1) | true | `1` | -| `select_quality_from_signal` | Boolean (0/1) | false | `1` | +| `select_max` | Boolean (`0` or `1`) | 1 | `1` | +| `select_quality_from_signal` | Boolean (`0` or `1`) | 0 | `1` | + +#### Signal Configuration + +Properties for configuring which signals the entity uses: + +| Property | Type | Description | +|----------|------|-------------| +| `count_signal` | String (signal name, e.g. `"signal-A"`) | What signal to output the sum total number of unique signals... | +| `index_signal` | String (signal name, e.g. `"signal-A"`) | Which input signal to pull the index value from in order to ... | +| `quality_destination_signal` | String (signal name, e.g. `"signal-A"`) | The destination signal(s) to output with the read quality va... | +| `quality_source_signal` | String (signal name, e.g. `"signal-A"`) | The input signal type to pull the quality from dynamically, ... | --- @@ -325,23 +510,12 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = **Description:** An entity that illuminates an area. +**Draftsman Source:** [Lamp class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/lamp.py) + **Prototypes:** `"small-lamp"` **Connection Type:** Single circuit connection -#### Circuit Signal I/O - -**Signal Inputs:** - -| Signal Property | Signal Type | Description | Enable With | -|-----------------|-------------|-------------|-------------| -| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` | -| `logistic_condition` | Any signal | Logistic network signal for enable/disable | `connect_to_logistic_network: 1` | -| `red_signal` | Integer (0-255) | Red color component (COMPONENTS mode) | `use_colors: 1, color_mode: 1` | -| `green_signal` | Integer (0-255) | Green color component (COMPONENTS mode) | `use_colors: 1, color_mode: 1` | -| `blue_signal` | Integer (0-255) | Blue color component (COMPONENTS mode) | `use_colors: 1, color_mode: 1` | -| `rgb_signal` | Packed RGB integer | Combined RGB value (PACKED_RGB mode) | `use_colors: 1, color_mode: 2` | - #### DSL Examples ```facto @@ -349,28 +523,41 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = Entity lamp = place("small-lamp", 0, 0); lamp.enable = signal > 0; -# RGB colored lamp +# RGB colored lamp (color_mode: 1 = COMPONENTS) Entity rgb_lamp = place("small-lamp", 2, 0, {use_colors: 1, color_mode: 1}); -rgb_lamp.r = red_signal; -rgb_lamp.g = green_signal; -rgb_lamp.b = blue_signal; +rgb_lamp.r = red_value; +rgb_lamp.g = green_value; +rgb_lamp.b = blue_value; ``` #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `always_on` | Boolean (0/1) | false | `1` | -| `circuit_enabled` | Boolean (0/1) | false | `1` | -| `color` | Color {r: 0-255, g: 0-255, b: 0-255} | (factory) | `{r: 255, g: 0, b: 0}` | +| `always_on` | Boolean (`0` or `1`) | 0 | `1` | +| `circuit_enabled` | Boolean (`0` or `1`) | 0 | `1` | +| `color` | Object `{r, g, b}` (0-255 each) | (factory) | `{r: 255, g: 0, b: 0}` | | `color_mode` | Integer ([LampColorMode](#lampcolormode)) | 0 | `0 # COLOR_MAPPING` | -| `connect_to_logistic_network` | Boolean (0/1) | false | `1` | +| `connect_to_logistic_network` | Boolean (`0` or `1`) | 0 | `1` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | -| `use_colors` | Boolean (0/1) | false | `1` | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | +| `use_colors` | Boolean (`0` or `1`) | 0 | `1` | + +#### Signal Configuration + +Properties for configuring which signals the entity uses: + +| Property | Type | Description | +|----------|------|-------------| +| `blue_signal` | String (signal name, e.g. `"signal-A"`) | The signal to pull the blue color component from, if color_m... | +| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... | +| `green_signal` | String (signal name, e.g. `"signal-A"`) | The signal to pull the green color component from, if color_... | +| `logistic_condition` | Condition (use `.enable = expr`) | The logistic condition that must be passed in order for this... | +| `red_signal` | String (signal name, e.g. `"signal-A"`) | | +| `rgb_signal` | String (signal name, e.g. `"signal-A"`) | .. versionadded:: 3.0.0 (Factorio 2.0) | --- @@ -378,25 +565,36 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = **Description:** (Factorio 2.0) +**Draftsman Source:** [DisplayPanel class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/display_panel.py) + **Prototypes:** `"display-panel"` **Connection Type:** Single circuit connection +#### Reading Entity Output + +Use `entity.output` to read: **Circuit network signals** + +```facto +Entity e = place("display-panel", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` + #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `always_show_in_alt_mode` | Boolean (0/1) | false | `1` | +| `always_show_in_alt_mode` | Boolean (`0` or `1`) | 0 | `1` | | `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | -| `icon` | String (signal name) | None | | +| `icon` | String (signal name, e.g. `"signal-A"`) | None | | | `item_requests` | List (complex) ⚠️ | (factory) | | | `messages` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | | `player_description` | String | "" | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | -| `show_in_chart` | Boolean (0/1) | false | `1` | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | +| `show_in_chart` | Boolean (`0` or `1`) | 0 | `1` | | `text` | String | "" | | --- @@ -407,56 +605,68 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = **Description:** An entity with a swinging arm that can move items between machines. +**Draftsman Source:** [Inserter class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/inserter.py) + **Prototypes:** `"bulk-inserter"`, `"fast-inserter"`, `"inserter"`, `"burner-inserter"`, `"long-handed-inserter"`, `"stack-inserter"` **Connection Type:** Single circuit connection -#### Circuit Signal I/O +#### Reading Entity Output -**Signal Inputs:** +Use `entity.output` to read: **Items in hand or filter status** -| Signal Property | Signal Type | Description | Enable With | -|-----------------|-------------|-------------|-------------| -| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` | -| `logistic_condition` | Any signal | Logistic network signal for enable/disable | `connect_to_logistic_network: 1` | -| `stack_size_control_signal` | Integer signal | Sets inserter stack size from signal value | `circuit_set_stack_size: 1` | +```facto +Entity e = place("bulk-inserter", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` -**Content Outputs:** +**Enable properties:** -- Inserter hand contents (items being moved, enable with `read_hand_contents: 1`) +- `read_hand_contents`: Read items in hand +- `circuit_set_filters`: Control via filters #### DSL Examples ```facto # Inserter that enables when chest has items Entity inserter = place("inserter", 0, 0, {direction: 4}); -inserter.enable = chest_contents > 50; +inserter.enable = chest.output > 50; ``` #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `circuit_enabled` | Boolean (0/1) | false | `1` | -| `circuit_set_filters` | Boolean (0/1) | false | `1` | -| `circuit_set_stack_size` | Boolean (0/1) | false | `1` | -| `connect_to_logistic_network` | Boolean (0/1) | false | `1` | +| `circuit_enabled` | Boolean (`0` or `1`) | 0 | `1` | +| `circuit_set_filters` | Boolean (`0` or `1`) | 0 | `1` | +| `circuit_set_stack_size` | Boolean (`0` or `1`) | 0 | `1` | +| `connect_to_logistic_network` | Boolean (`0` or `1`) | 0 | `1` | | `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | -| `drop_position_offset` | Vector {x, y} ⚠️ | (factory) | | -| `filter_mode` | One of: whitelist, blacklist | "whitelist" | | +| `drop_position_offset` | Vector `{x, y}` ⚠️ | (factory) | | +| `filter_mode` | One of: `"whitelist"`, `"blacklist"` ([FilterMode](#filtermode)) | "whitelist" | `"whitelist"` | | `filters` | List (complex) ⚠️ | (factory) | | | `item_requests` | List (complex) ⚠️ | (factory) | | | `mode_of_operation` | Integer ([InserterModeOfOperation](#insertermodeofoperation)) | 0 | `0 # ENABLE_DISABLE` | | `name` | String (entity prototype name) | (factory) | | | `override_stack_size` | Integer | None | | -| `pickup_position_offset` | Vector {x, y} ⚠️ | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | -| `read_hand_contents` | Boolean (0/1) | false | `1` | +| `pickup_position_offset` | Vector `{x, y}` ⚠️ | (factory) | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | +| `read_hand_contents` | Boolean (`0` or `1`) | 0 | `1` | | `read_mode` | Integer ([InserterReadMode](#inserterreadmode)) | 0 | `0 # PULSE` | -| `spoil_priority` | One of: spoiled-first, fresh-first, None | None | | -| `use_filters` | Boolean (0/1) | false | `1` | +| `spoil_priority` | One of: `"spoiled-first"`, `"fresh-first"` ([SpoilPriority](#spoilpriority)) | None | `"spoiled-first"` | +| `use_filters` | Boolean (`0` or `1`) | 0 | `1` | + +#### Signal Configuration + +Properties for configuring which signals the entity uses: + +| Property | Type | Description | +|----------|------|-------------| +| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... | +| `logistic_condition` | Condition (use `.enable = expr`) | The logistic condition that must be passed in order for this... | +| `stack_size_control_signal` | String (signal name, e.g. `"signal-A"`) | What circuit network signal should indicate the current stac... | --- @@ -466,67 +676,72 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = **Description:** An entity that transports items. +**Draftsman Source:** [TransportBelt class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/transport_belt.py) + **Prototypes:** `"express-transport-belt"`, `"transport-belt"`, `"fast-transport-belt"`, `"turbo-transport-belt"` **Connection Type:** Single circuit connection -#### Circuit Signal I/O - -**Signal Inputs:** +#### Reading Entity Output -| Signal Property | Signal Type | Description | Enable With | -|-----------------|-------------|-------------|-------------| -| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` | -| `logistic_condition` | Any signal | Logistic network signal for enable/disable | `connect_to_logistic_network: 1` | - -**Content Outputs:** - -- Item contents (all items in entity, enable with `read_contents: 1`) - -#### DSL Examples +Use `entity.output` to read: **Items on the belt** ```facto -# Belt that stops when storage is full -Entity belt = place("transport-belt", 0, 0, {direction: 4}); -belt.enable = storage_count < 1000; +Entity e = place("express-transport-belt", 0, 0); +Bundle signals = e.output; # Returns all output signals ``` +**Enable properties:** + +- `read_contents`: Read belt contents + #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `circuit_enabled` | Boolean (0/1) | false | `1` | -| `connect_to_logistic_network` | Boolean (0/1) | false | `1` | +| `circuit_enabled` | Boolean (`0` or `1`) | 0 | `1` | +| `connect_to_logistic_network` | Boolean (`0` or `1`) | 0 | `1` | | `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | -| `read_contents` | Boolean (0/1) | false | `1` | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | +| `read_contents` | Boolean (`0` or `1`) | 0 | `1` | | `read_mode` | Integer ([BeltReadMode](#beltreadmode)) | 0 | `0 # PULSE` | +#### Signal Configuration + +Properties for configuring which signals the entity uses: + +| Property | Type | Description | +|----------|------|-------------| +| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... | +| `logistic_condition` | Condition (use `.enable = expr`) | The logistic condition that must be passed in order for this... | + --- ### UndergroundBelt **Description:** A transport belt that transfers items underneath other entities. +**Draftsman Source:** [UndergroundBelt class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/underground_belt.py) + **Prototypes:** `"underground-belt"`, `"turbo-underground-belt"`, `"fast-underground-belt"`, `"express-underground-belt"` **Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| | `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | -| `io_type` | One of: input, output, None | "input" | | +| `io_type` | One of: `"input"`, `"output"` ([IOType](#iotype)) | "input" | `"input"` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- @@ -534,30 +749,41 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = **Description:** An entity that evenly splits a set of input belts between a set of output belts. +**Draftsman Source:** [Splitter class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/splitter.py) + **Prototypes:** `"turbo-splitter"`, `"express-splitter"`, `"fast-splitter"`, `"splitter"` **Connection Type:** Single circuit connection +#### Reading Entity Output + +Use `entity.output` to read: **Circuit network signals** + +```facto +Entity e = place("turbo-splitter", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` + #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| | `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | -| `filter` | String (signal name) | None | | -| `input_left_condition` | Condition (set via .enable) ⚠️ | (factory) | | -| `input_priority` | One of: left, none, right | "none" | | -| `input_right_condition` | Condition (set via .enable) ⚠️ | (factory) | | +| `filter` | String (signal name, e.g. `"signal-A"`) | None | | +| `input_left_condition` | Condition (use `.enable = expr`) ⚠️ | (factory) | | +| `input_priority` | One of: `"left"`, `"none"`, `"right"` ([SplitterPriority](#splitterpriority)) | "none" | `"left"` | +| `input_right_condition` | Condition (use `.enable = expr`) ⚠️ | (factory) | | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `output_left_condition` | Condition (set via .enable) ⚠️ | (factory) | | -| `output_priority` | One of: left, none, right | "none" | | -| `output_right_condition` | Condition (set via .enable) ⚠️ | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | -| `set_filter` | Boolean (0/1) | false | `1` | -| `set_input_side` | Boolean (0/1) | false | `1` | -| `set_output_side` | Boolean (0/1) | false | `1` | +| `output_left_condition` | Condition (use `.enable = expr`) ⚠️ | (factory) | | +| `output_priority` | One of: `"left"`, `"none"`, `"right"` ([SplitterPriority](#splitterpriority)) | "none" | `"left"` | +| `output_right_condition` | Condition (use `.enable = expr`) ⚠️ | (factory) | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | +| `set_filter` | Boolean (`0` or `1`) | 0 | `1` | +| `set_input_side` | Boolean (`0` or `1`) | 0 | `1` | +| `set_output_side` | Boolean (`0` or `1`) | 0 | `1` | --- @@ -565,23 +791,25 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = **Description:** An entity that inserts items directly from a belt to an inventory or vise-versa. +**Draftsman Source:** [Loader class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/loader.py) + **Prototypes:** `"loader"`, `"fast-loader"`, `"turbo-loader"`, `"express-loader"` **Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| | `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `filters` | List (complex) ⚠️ | (factory) | | -| `io_type` | One of: input, output, None | "input" | | +| `io_type` | One of: `"input"`, `"output"` ([IOType](#iotype)) | "input" | `"input"` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | -| `use_filters` | Boolean (0/1) | false | `1` | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | +| `use_filters` | Boolean (`0` or `1`) | 0 | `1` | --- @@ -589,20 +817,22 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = **Description:** A belt object that can transfer items over any distance, regardless of constraint, as long as the two are paired together. +**Draftsman Source:** [LinkedBelt class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/linked_belt.py) + **Prototypes:** `"linked-belt"` **Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| | `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- @@ -612,97 +842,121 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = **Description:** A stop for making train schedules for locomotives. +**Draftsman Source:** [TrainStop class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/train_stop.py) + **Prototypes:** `"train-stop"` **Connection Type:** Single circuit connection -#### Circuit Signal I/O +#### Reading Entity Output -**Signal Inputs:** +Use `entity.output` to read: **Train ID, count, and cargo** -| Signal Property | Signal Type | Description | Enable With | -|-----------------|-------------|-------------|-------------| -| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` | -| `logistic_condition` | Any signal | Logistic network signal for enable/disable | `connect_to_logistic_network: 1` | -| `trains_limit_signal` | Integer | Sets train limit from signal value | `signal_limits_trains: 1` | -| `priority_signal` | Integer | Sets station priority from signal value | `set_priority: 1` | +```facto +Entity e = place("train-stop", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` -**Signal Outputs:** +**Enable properties:** -| Signal Property | Signal Type | Description | Enable With | -|-----------------|-------------|-------------|-------------| -| `train_stopped_signal` | Train ID | Outputs ID of stopped train | `read_stopped_train: 1` | -| `trains_count_signal` | Integer | Outputs count of trains en route | `read_trains_count: 1` | +- `read_stopped_train`: Read stopped train ID +- `read_trains_count`: Read incoming trains count +- `read_from_train`: Read train cargo #### DSL Examples ```facto # Train station with circuit control Entity station = place("train-stop", 0, 0, {station: "Iron Pickup"}); -station.enable = has_cargo > 0; +station.enable = cargo.output > 0; +# Read train info +Bundle train_info = station.output; ``` #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `circuit_enabled` | Boolean (0/1) | false | `1` | -| `color` | Color {r: 0-255, g: 0-255, b: 0-255} | (factory) | `{r: 255, g: 0, b: 0}` | -| `connect_to_logistic_network` | Boolean (0/1) | false | `1` | +| `circuit_enabled` | Boolean (`0` or `1`) | 0 | `1` | +| `color` | Object `{r, g, b}` (0-255 each) | (factory) | `{r: 255, g: 0, b: 0}` | +| `connect_to_logistic_network` | Boolean (`0` or `1`) | 0 | `1` | | `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `manual_trains_limit` | Integer | None | | | `name` | String (entity prototype name) | (factory) | | | `priority` | Integer | 50 | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | -| `read_from_train` | Boolean (0/1) | false | `1` | -| `read_stopped_train` | Boolean (0/1) | false | `1` | -| `read_trains_count` | Boolean (0/1) | false | `1` | -| `send_to_train` | Boolean (0/1) | true | `1` | -| `set_priority` | Boolean (0/1) | false | `1` | -| `signal_limits_trains` | Boolean (0/1) | false | `1` | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | +| `read_from_train` | Boolean (`0` or `1`) | 0 | `1` | +| `read_stopped_train` | Boolean (`0` or `1`) | 0 | `1` | +| `read_trains_count` | Boolean (`0` or `1`) | 0 | `1` | +| `send_to_train` | Boolean (`0` or `1`) | 1 | `1` | +| `set_priority` | Boolean (`0` or `1`) | 0 | `1` | +| `signal_limits_trains` | Boolean (`0` or `1`) | 0 | `1` | | `station` | String | "" | `"Station Name"` | +#### Signal Configuration + +Properties for configuring which signals the entity uses: + +| Property | Type | Description | +|----------|------|-------------| +| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... | +| `logistic_condition` | Condition (use `.enable = expr`) | The logistic condition that must be passed in order for this... | +| `priority_signal` | String (signal name, e.g. `"signal-A"`) | Which signal to read the dynamic priority of this train stop... | +| `train_stopped_signal` | String (signal name, e.g. `"signal-A"`) | What signal to output the unique train ID if a train is curr... | +| `trains_count_signal` | String (signal name, e.g. `"signal-A"`) | What signal to use to output the current number of trains th... | +| `trains_limit_signal` | String (signal name, e.g. `"signal-A"`) | What signal to read to limit the number of trains that can u... | + --- ### RailSignal **Description:** A rail signal that determines whether or not trains can pass along their rail block. +**Draftsman Source:** [RailSignal class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/rail_signal.py) + **Prototypes:** `"rail-signal"` **Connection Type:** Single circuit connection -#### Circuit Signal I/O +#### Reading Entity Output -**Signal Inputs:** +Use `entity.output` to read: **Signal state (red/yellow/green)** -| Signal Property | Signal Type | Description | Enable With | -|-----------------|-------------|-------------|-------------| -| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` | +```facto +Entity e = place("rail-signal", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` -**Signal Outputs:** +**Enable properties:** -| Signal Property | Signal Type | Description | Enable With | -|-----------------|-------------|-------------|-------------| -| `red_output_signal` | 1 when red | Outputs 1 when rail signal shows red | `read_signal: 1` | -| `yellow_output_signal` | 1 when yellow | Outputs 1 when rail signal shows yellow | `read_signal: 1` | -| `green_output_signal` | 1 when green | Outputs 1 when rail signal shows green | `read_signal: 1` | +- `read_signal`: Read signal state #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `circuit_enabled` | Boolean (0/1) | false | `1` | +| `circuit_enabled` | Boolean (`0` or `1`) | 0 | `1` | | `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | -| `read_signal` | Boolean (0/1) | true | `1` | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | +| `read_signal` | Boolean (`0` or `1`) | 1 | `1` | + +#### Signal Configuration + +Properties for configuring which signals the entity uses: + +| Property | Type | Description | +|----------|------|-------------| +| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... | +| `green_output_signal` | String (signal name, e.g. `"signal-A"`) | The green output signal. Sent with a unit value when the rai... | +| `red_output_signal` | String (signal name, e.g. `"signal-A"`) | The red output signal. Sent with a unit value when the rail ... | +| `yellow_output_signal` | String (signal name, e.g. `"signal-A"`) | The yellow output signal. Sent with a unit value when the ra... | --- @@ -710,31 +964,46 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = **Description:** A rail signal that determines access of a current rail block based on a forward rail block. +**Draftsman Source:** [RailChainSignal class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/rail_chain_signal.py) + **Prototypes:** `"rail-chain-signal"` **Connection Type:** Single circuit connection -#### Circuit Signal I/O +#### Reading Entity Output + +Use `entity.output` to read: **Signal state (red/yellow/green/blue)** -**Signal Outputs:** +```facto +Entity e = place("rail-chain-signal", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` -| Signal Property | Signal Type | Description | Enable With | -|-----------------|-------------|-------------|-------------| -| `red_output_signal` | 1 when red | Outputs 1 when rail signal shows red | `read_signal: 1` | -| `yellow_output_signal` | 1 when yellow | Outputs 1 when rail signal shows yellow | `read_signal: 1` | -| `green_output_signal` | 1 when green | Outputs 1 when rail signal shows green | `read_signal: 1` | -| `blue_output_signal` | 1 when blue | Outputs 1 when chain signal reserved | Always active | +**Enable properties:** + +- `read_signal`: Read signal state #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| | `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | + +#### Signal Configuration + +Properties for configuring which signals the entity uses: + +| Property | Type | Description | +|----------|------|-------------| +| `blue_output_signal` | String (signal name, e.g. `"signal-A"`) | The blue output signal. Sent with a unit value when the rail... | +| `green_output_signal` | String (signal name, e.g. `"signal-A"`) | The green output signal. Sent with a unit value when the rai... | +| `red_output_signal` | String (signal name, e.g. `"signal-A"`) | The red output signal. Sent with a unit value when the rail ... | +| `yellow_output_signal` | String (signal name, e.g. `"signal-A"`) | The yellow output signal. Sent with a unit value when the ra... | --- @@ -742,23 +1011,25 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = **Description:** A train car that moves other wagons around using a fuel. +**Draftsman Source:** [Locomotive class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/locomotive.py) + **Prototypes:** `"locomotive"` **Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `color` | Color {r: 0-255, g: 0-255, b: 0-255} | Color(r=0.9176470588235294, g=0.06666666666666667, b=0, a=0.4980392156862745) | `{r: 255, g: 0, b: 0}` | -| `enable_logistics_while_moving` | Boolean (0/1) | true | `1` | +| `color` | Object `{r, g, b}` (0-255 each) | Color(r=0.9176470588235294, g=0.06666666666666667, b=0, a=0.4980392156862745) | `{r: 255, g: 0, b: 0}` | +| `enable_logistics_while_moving` | Boolean (`0` or `1`) | 1 | `1` | | `equipment` | List (complex) ⚠️ | (factory) | | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `orientation` | Float (0.0-1.0) | | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `orientation` | Complex (see draftsman docs) ⚠️ | | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- @@ -766,23 +1037,25 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = **Description:** A train wagon that holds items as cargo. +**Draftsman Source:** [CargoWagon class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/cargo_wagon.py) + **Prototypes:** `"cargo-wagon"` **Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `enable_logistics_while_moving` | Boolean (0/1) | true | `1` | +| `enable_logistics_while_moving` | Boolean (`0` or `1`) | 1 | `1` | | `equipment` | List (complex) ⚠️ | (factory) | | | `inventory` | Complex (see draftsman docs) ⚠️ | (factory) | | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `orientation` | Float (0.0-1.0) | | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `orientation` | Complex (see draftsman docs) ⚠️ | | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- @@ -790,22 +1063,24 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = **Description:** A train wagon that holds a fluid as cargo. +**Draftsman Source:** [FluidWagon class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/fluid_wagon.py) + **Prototypes:** `"fluid-wagon"` **Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `enable_logistics_while_moving` | Boolean (0/1) | true | `1` | +| `enable_logistics_while_moving` | Boolean (`0` or `1`) | 1 | `1` | | `equipment` | List (complex) ⚠️ | (factory) | | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `orientation` | Float (0.0-1.0) | | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `orientation` | Complex (see draftsman docs) ⚠️ | | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- @@ -813,1777 +1088,2171 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = **Description:** An artillery train car. +**Draftsman Source:** [ArtilleryWagon class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/artillery_wagon.py) + **Prototypes:** `"artillery-wagon"` **Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `auto_target` | Boolean (0/1) | true | `1` | -| `enable_logistics_while_moving` | Boolean (0/1) | true | `1` | +| `auto_target` | Boolean (`0` or `1`) | 1 | `1` | +| `enable_logistics_while_moving` | Boolean (`0` or `1`) | 1 | `1` | | `equipment` | List (complex) ⚠️ | (factory) | | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `orientation` | Float (0.0-1.0) | | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `orientation` | Complex (see draftsman docs) ⚠️ | | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- -### StraightRail - -**Description:** A piece of rail track that moves in the 8 cardinal directions. +## Production -**Prototypes:** `"straight-rail"` +### AssemblingMachine -**Connection Type:** Single circuit connection +**Description:** A machine that takes input items and produces output items. Includes assembling machines, chemical plants, oil refineries, and centrifuges, but does not include :py:class:`.RocketSilo`. -#### Settable Properties +**Draftsman Source:** [AssemblingMachine class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/assembling_machine.py) -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +**Prototypes:** `"centrifuge"`, `"biochamber"`, `"oil-refinery"`, `"foundry"`, `"captive-biter-spawner"`, `"assembling-machine-1"`, `"electromagnetic-plant"`, `"crusher"`, `"cryogenic-plant"`, `"assembling-machine-2"`, ... (12 total) -| Property | Type | Default | Example | -|----------|------|---------|---------| -| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | -| `item_requests` | List (complex) ⚠️ | (factory) | | -| `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +**Connection Type:** Single circuit connection ---- +#### Reading Entity Output -### CurvedRailA +Use `entity.output` to read: **Recipe finished pulse, working status** -**Description:** Curved rails which connect straight rails to half-diagonal rails. +```facto +Entity e = place("centrifuge", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` -**Prototypes:** `"curved-rail-a"` +**Enable properties:** -**Connection Type:** Single circuit connection +- `read_recipe_finished`: Pulse when recipe completes +- `read_working`: Output working status #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| +| `circuit_enabled` | Boolean (`0` or `1`) | 0 | `1` | +| `circuit_set_recipe` | Boolean (`0` or `1`) | 0 | `1` | +| `connect_to_logistic_network` | Boolean (`0` or `1`) | 0 | `1` | | `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | +| `include_in_crafting` | Boolean (`0` or `1`) | 1 | `1` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | - ---- +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | +| `read_contents` | Boolean (`0` or `1`) | 0 | `1` | +| `read_recipe_finished` | Boolean (`0` or `1`) | 0 | `1` | +| `read_working` | Boolean (`0` or `1`) | 0 | `1` | +| `recipe` | String (recipe name) | None | `"iron-gear-wheel"` | +| `recipe_quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | -### CurvedRailB +#### Signal Configuration -**Description:** Curved rails which connect half-diagonal rails to diagonal rails. +Properties for configuring which signals the entity uses: -**Prototypes:** `"curved-rail-b"` +| Property | Type | Description | +|----------|------|-------------| +| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... | +| `logistic_condition` | Condition (use `.enable = expr`) | The logistic condition that must be passed in order for this... | +| `recipe_finished_signal` | String (signal name, e.g. `"signal-A"`) | What signal to pulse when the crafting cycle completes. Only... | +| `working_signal` | String (signal name, e.g. `"signal-A"`) | .. versionadded:: 3.0.0 (Factorio 2.0) | -**Connection Type:** Single circuit connection +--- -#### Settable Properties +### Furnace -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +**Description:** An entity that automatically determines it's recipe from it's input items. Obviously includes regular furnaces, but can also include other machines like recyclers. -| Property | Type | Default | Example | -|----------|------|---------|---------| -| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | -| `item_requests` | List (complex) ⚠️ | (factory) | | -| `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +**Draftsman Source:** [Furnace class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/furnace.py) ---- +**Prototypes:** `"recycler"`, `"stone-furnace"`, `"steel-furnace"`, `"electric-furnace"` -### HalfDiagonalRail +**Connection Type:** Single circuit connection -**Description:** (Factorio 2.0) +#### Reading Entity Output -**Prototypes:** `"half-diagonal-rail"` +Use `entity.output` to read: **Circuit network signals** -**Connection Type:** Single circuit connection +```facto +Entity e = place("recycler", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| +| `circuit_enabled` | Boolean (`0` or `1`) | 0 | `1` | +| `circuit_set_recipe` | Boolean (`0` or `1`) | 0 | `1` | +| `connect_to_logistic_network` | Boolean (`0` or `1`) | 0 | `1` | | `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | +| `include_in_crafting` | Boolean (`0` or `1`) | 1 | `1` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | +| `read_contents` | Boolean (`0` or `1`) | 0 | `1` | +| `read_recipe_finished` | Boolean (`0` or `1`) | 0 | `1` | +| `read_working` | Boolean (`0` or `1`) | 0 | `1` | ---- +#### Signal Configuration -### RailRamp +Properties for configuring which signals the entity uses: -**Description:** (Factorio 2.0) +| Property | Type | Description | +|----------|------|-------------| +| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... | +| `logistic_condition` | Condition (use `.enable = expr`) | The logistic condition that must be passed in order for this... | +| `recipe_finished_signal` | String (signal name, e.g. `"signal-A"`) | What signal to pulse when the crafting cycle completes. Only... | +| `working_signal` | String (signal name, e.g. `"signal-A"`) | .. versionadded:: 3.0.0 (Factorio 2.0) | -**Prototypes:** `"rail-ramp"`, `"dummy-rail-ramp"` +--- -**Connection Type:** Single circuit connection +### MiningDrill -#### Settable Properties +**Description:** An entity that extracts resources (item or fluid) from the environment. -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +**Draftsman Source:** [MiningDrill class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/mining_drill.py) -| Property | Type | Default | Example | -|----------|------|---------|---------| -| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | -| `item_requests` | List (complex) ⚠️ | (factory) | | -| `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +**Prototypes:** `"electric-mining-drill"`, `"big-mining-drill"`, `"pumpjack"`, `"burner-mining-drill"` ---- +**Connection Type:** Single circuit connection -### RailSupport +#### Reading Entity Output -**Description:** (Factorio 2.0) +Use `entity.output` to read: **Resources under the drill** -**Prototypes:** `"dummy-rail-support"`, `"rail-support"` +```facto +Entity e = place("electric-mining-drill", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` -**Connection Type:** Single circuit connection +**Enable properties:** + +- `read_resources`: Read resource amounts #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| +| `circuit_enabled` | Boolean (`0` or `1`) | 0 | `1` | +| `connect_to_logistic_network` | Boolean (`0` or `1`) | 0 | `1` | | `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | +| `read_mode` | Integer ([MiningDrillReadMode](#miningdrillreadmode)) | 0 | `0 # UNDER_DRILL` | +| `read_resources` | Boolean (`0` or `1`) | 1 | `1` | + +#### Signal Configuration + +Properties for configuring which signals the entity uses: + +| Property | Type | Description | +|----------|------|-------------| +| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... | +| `logistic_condition` | Condition (use `.enable = expr`) | The logistic condition that must be passed in order for this... | --- -### ElevatedStraightRail +### Lab -**Description:** (Factorio 2.0) +**Description:** An entity that consumes items and produces research. -**Prototypes:** `"dummy-elevated-straight-rail"`, `"elevated-straight-rail"` +**Draftsman Source:** [Lab class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/lab.py) + +**Prototypes:** `"lab"`, `"biolab"` **Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- -### ElevatedCurvedRailA +### RocketSilo -**Description:** (Factorio 2.0) +**Description:** An entity that launches rockets, usually to move items between surfaces or space platforms. -**Prototypes:** `"dummy-elevated-curved-rail-a"`, `"elevated-curved-rail-a"` +**Draftsman Source:** [RocketSilo class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/rocket_silo.py) + +**Prototypes:** `"rocket-silo"` **Connection Type:** Single circuit connection +#### Reading Entity Output + +Use `entity.output` to read: **Circuit network signals** + +```facto +Entity e = place("rocket-silo", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` + #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| +| `auto_launch` | Boolean (`0` or `1`) | 0 | `1` | | `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | +| `read_items_mode` | Integer ([SiloReadMode](#siloreadmode)) | 1 | `0 # NONE` | +| `recipe` | String (recipe name) | "rocket-part" | `"iron-gear-wheel"` | +| `recipe_quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | +| `transitional_request_index` | Integer | 0 | | +| `use_transitional_requests` | Boolean (`0` or `1`) | 0 | `1` | --- -### ElevatedCurvedRailB +### Beacon -**Description:** (Factorio 2.0) +**Description:** An entity designed to apply module effects to other machines in its radius. -**Prototypes:** `"dummy-elevated-curved-rail-b"`, `"elevated-curved-rail-b"` +**Draftsman Source:** [Beacon class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/beacon.py) + +**Prototypes:** `"beacon"` **Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- -### ElevatedHalfDiagonalRail +### Boiler -**Description:** (Factorio 2.0) +**Description:** An entity that uses a fuel to convert a fluid (usually water) to another fluid (usually steam). -**Prototypes:** `"dummy-elevated-half-diagonal-rail"`, `"elevated-half-diagonal-rail"` +**Draftsman Source:** [Boiler class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/boiler.py) + +**Prototypes:** `"boiler"`, `"heat-exchanger"` **Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| | `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- -### LegacyStraightRail +### Generator -**Description:** An old, Factorio 1.0 straight rail entity. +**Description:** An entity that converts a fluid (usually steam) to electricity. -**Prototypes:** `"legacy-straight-rail"` +**Draftsman Source:** [Generator class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/generator.py) + +**Prototypes:** `"steam-engine"`, `"steam-turbine"` **Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| | `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- -### LegacyCurvedRail +### Reactor -**Description:** An old, Factorio 1.0 curved rail entity. +**Description:** An entity that converts a fuel into thermal energy. -**Prototypes:** `"legacy-curved-rail"` +**Draftsman Source:** [Reactor class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/reactor.py) + +**Prototypes:** `"heating-tower"`, `"nuclear-reactor"` **Connection Type:** Single circuit connection +#### Reading Entity Output + +Use `entity.output` to read: **Circuit network signals** + +```facto +Entity e = place("heating-tower", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` + #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | +| `read_burner_fuel` | Boolean (`0` or `1`) | 0 | `1` | +| `read_temperature` | Boolean (`0` or `1`) | 0 | `1` | +| `temperature_signal` | String (signal name, e.g. `"signal-A"`) | (factory) | `"signal-A"` | --- -## Production +## Storage -### AssemblingMachine +### Container -**Description:** A machine that takes input items and produces output items. Includes assembling machines, chemical plants, oil refineries, and centrifuges, but does not include :py:class:`.RocketSilo`. +**Description:** An entity that holds items. + +**Draftsman Source:** [Container class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/container.py) -**Prototypes:** `"centrifuge"`, `"biochamber"`, `"oil-refinery"`, `"foundry"`, `"captive-biter-spawner"`, `"assembling-machine-1"`, `"electromagnetic-plant"`, `"crusher"`, `"cryogenic-plant"`, `"assembling-machine-2"`, `"assembling-machine-3"`, `"chemical-plant"` +**Prototypes:** `"factorio-logo-22tiles"`, `"bottomless-chest"`, `"red-chest"`, `"crash-site-chest-2"`, `"factorio-logo-16tiles"`, `"iron-chest"`, `"blue-chest"`, `"crash-site-chest-1"`, `"steel-chest"`, `"factorio-logo-11tiles"`, ... (11 total) **Connection Type:** Single circuit connection -#### Circuit Signal I/O +#### Reading Entity Output -**Signal Inputs:** +Use `entity.output` to read: **Item contents of the container** -| Signal Property | Signal Type | Description | Enable With | -|-----------------|-------------|-------------|-------------| -| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` | -| `logistic_condition` | Any signal | Logistic network signal for enable/disable | `connect_to_logistic_network: 1` | +```facto +Entity e = place("factorio-logo-22tiles", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` -**Signal Outputs:** +**Enable properties:** -| Signal Property | Signal Type | Description | Enable With | -|-----------------|-------------|-------------|-------------| -| `recipe_finished_signal` | Pulse (1) | Pulses when recipe completes | `read_recipe_finished: 1` | -| `working_signal` | 1 when working | Outputs 1 while machine is crafting | `read_working: 1` | +- `read_contents`: Enable reading container contents #### DSL Examples ```facto -# Assembler controlled by circuit -Entity assembler = place("assembling-machine-1", 0, 0, {recipe: "iron-gear-wheel"}); -assembler.enable = iron_count > 100; +# Read chest contents +Entity chest = place("iron-chest", 0, 0); +Bundle contents = chest.output; +Signal iron = contents["iron-plate"]; ``` #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `circuit_enabled` | Boolean (0/1) | false | `1` | -| `circuit_set_recipe` | Boolean (0/1) | false | `1` | -| `connect_to_logistic_network` | Boolean (0/1) | false | `1` | -| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | -| `include_in_crafting` | Boolean (0/1) | true | `1` | +| `bar` | Integer | None | | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | -| `read_contents` | Boolean (0/1) | false | `1` | -| `read_recipe_finished` | Boolean (0/1) | false | `1` | -| `read_working` | Boolean (0/1) | false | `1` | -| `recipe` | String (recipe name) | None | `"iron-gear-wheel"` | -| `recipe_quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- -### Furnace +### LogisticPassiveContainer -**Description:** An entity that automatically determines it's recipe from it's input items. Obviously includes regular furnaces, but can also include other machines like recyclers. +**Description:** A logistics container that provides it's contents to the logistic network when needed by the network. -**Prototypes:** `"recycler"`, `"stone-furnace"`, `"steel-furnace"`, `"electric-furnace"` +**Draftsman Source:** [LogisticPassiveContainer class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/logistic_passive_container.py) -**Connection Type:** Single circuit connection - -#### Circuit Signal I/O +**Prototypes:** `"passive-provider-chest"` -**Signal Inputs:** +**Connection Type:** Single circuit connection -| Signal Property | Signal Type | Description | Enable With | -|-----------------|-------------|-------------|-------------| -| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` | -| `logistic_condition` | Any signal | Logistic network signal for enable/disable | `connect_to_logistic_network: 1` | +#### Reading Entity Output -**Signal Outputs:** +Use `entity.output` to read: **Circuit network signals** -| Signal Property | Signal Type | Description | Enable With | -|-----------------|-------------|-------------|-------------| -| `recipe_finished_signal` | Pulse (1) | Pulses when recipe completes | `read_recipe_finished: 1` | -| `working_signal` | 1 when working | Outputs 1 while machine is crafting | `read_working: 1` | +```facto +Entity e = place("passive-provider-chest", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `circuit_enabled` | Boolean (0/1) | false | `1` | -| `circuit_set_recipe` | Boolean (0/1) | false | `1` | -| `connect_to_logistic_network` | Boolean (0/1) | false | `1` | -| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | -| `include_in_crafting` | Boolean (0/1) | true | `1` | +| `bar` | Integer | None | | +| `circuit_enabled` | Boolean (`0` or `1`) | 0 | `1` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | -| `read_contents` | Boolean (0/1) | false | `1` | -| `read_recipe_finished` | Boolean (0/1) | false | `1` | -| `read_working` | Boolean (0/1) | false | `1` | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | +| `read_contents` | Boolean (`0` or `1`) | 1 | `1` | + +#### Signal Configuration + +Properties for configuring which signals the entity uses: + +| Property | Type | Description | +|----------|------|-------------| +| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... | --- -### MiningDrill +### LogisticActiveContainer -**Description:** An entity that extracts resources (item or fluid) from the environment. +**Description:** A logistics container that immediately provides it's contents to the logistic network. -**Prototypes:** `"electric-mining-drill"`, `"big-mining-drill"`, `"pumpjack"`, `"burner-mining-drill"` +**Draftsman Source:** [LogisticActiveContainer class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/logistic_active_container.py) + +**Prototypes:** `"active-provider-chest"` **Connection Type:** Single circuit connection -#### Circuit Signal I/O +#### Reading Entity Output + +Use `entity.output` to read: **Circuit network signals** + +```facto +Entity e = place("active-provider-chest", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` + +#### Settable Properties + +Set at placement: `place("name", x, y, {prop: value})` + +| Property | Type | Default | Example | +|----------|------|---------|---------| +| `bar` | Integer | None | | +| `item_requests` | List (complex) ⚠️ | (factory) | | +| `name` | String (entity prototype name) | (factory) | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | +| `read_contents` | Boolean (`0` or `1`) | 1 | `1` | + +#### Signal Configuration + +Properties for configuring which signals the entity uses: + +| Property | Type | Description | +|----------|------|-------------| +| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... | + +--- + +### LogisticStorageContainer + +**Description:** A logistics container that stores items not currently being used in the logistic network. + +**Draftsman Source:** [LogisticStorageContainer class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/logistic_storage_container.py) + +**Prototypes:** `"storage-chest"` -**Signal Inputs:** +**Connection Type:** Single circuit connection -| Signal Property | Signal Type | Description | Enable With | -|-----------------|-------------|-------------|-------------| -| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` | -| `logistic_condition` | Any signal | Logistic network signal for enable/disable | `connect_to_logistic_network: 1` | +#### Reading Entity Output -**Content Outputs:** +Use `entity.output` to read: **Circuit network signals** -- Resource amounts (resources under entity, enable with `read_resources: 1`) +```facto +Entity e = place("storage-chest", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `circuit_enabled` | Boolean (0/1) | false | `1` | -| `connect_to_logistic_network` | Boolean (0/1) | false | `1` | -| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | +| `bar` | Integer | None | | +| `circuit_enabled` | Boolean (`0` or `1`) | 0 | `1` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | -| `read_mode` | Integer ([MiningDrillReadMode](#miningdrillreadmode)) | 0 | `0 # UNDER_DRILL` | -| `read_resources` | Boolean (0/1) | true | `1` | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | +| `read_contents` | Boolean (`0` or `1`) | 1 | `1` | +| `request_from_buffers` | Boolean (`0` or `1`) | 1 | `1` | +| `requests_enabled` | Boolean (`0` or `1`) | 1 | `1` | +| `sections` | List (complex) ⚠️ | (factory) | | +| `trash_not_requested` | Boolean (`0` or `1`) | 0 | `1` | + +#### Signal Configuration + +Properties for configuring which signals the entity uses: + +| Property | Type | Description | +|----------|------|-------------| +| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... | --- -### Lab +### LogisticRequestContainer -**Description:** An entity that consumes items and produces research. +**Description:** A logistics container that requests items with a primary priority. -**Prototypes:** `"lab"`, `"biolab"` +**Draftsman Source:** [LogisticRequestContainer class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/logistic_request_container.py) + +**Prototypes:** `"requester-chest"` **Connection Type:** Single circuit connection +#### Reading Entity Output + +Use `entity.output` to read: **Circuit network signals** + +```facto +Entity e = place("requester-chest", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` + #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| +| `bar` | Integer | None | | +| `circuit_enabled` | Boolean (`0` or `1`) | 0 | `1` | | `item_requests` | List (complex) ⚠️ | (factory) | | +| `mode_of_operation` | Integer ([LogisticModeOfOperation](#logisticmodeofoperation)) | 0 | `0 # SEND_CONTENTS` | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | +| `request_from_buffers` | Boolean (`0` or `1`) | 0 | `1` | +| `requests_enabled` | Boolean (`0` or `1`) | 1 | `1` | +| `sections` | List (complex) ⚠️ | (factory) | | +| `trash_not_requested` | Boolean (`0` or `1`) | 0 | `1` | + +#### Signal Configuration + +Properties for configuring which signals the entity uses: + +| Property | Type | Description | +|----------|------|-------------| +| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... | --- -### RocketSilo +### LogisticBufferContainer -**Description:** An entity that launches rockets, usually to move items between surfaces or space platforms. +**Description:** A logistics container that requests items on a secondary priority. -**Prototypes:** `"rocket-silo"` +**Draftsman Source:** [LogisticBufferContainer class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/logistic_buffer_container.py) + +**Prototypes:** `"buffer-chest"` **Connection Type:** Single circuit connection +#### Reading Entity Output + +Use `entity.output` to read: **Circuit network signals** + +```facto +Entity e = place("buffer-chest", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` + #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `auto_launch` | Boolean (0/1) | false | `1` | -| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | +| `bar` | Integer | None | | +| `circuit_enabled` | Boolean (`0` or `1`) | 0 | `1` | | `item_requests` | List (complex) ⚠️ | (factory) | | +| `mode_of_operation` | Integer ([LogisticModeOfOperation](#logisticmodeofoperation)) | 0 | `0 # SEND_CONTENTS` | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | -| `read_items_mode` | Integer ([SiloReadMode](#siloreadmode)) | 1 | `0 # NONE` | -| `recipe` | String (recipe name) | "rocket-part" | `"iron-gear-wheel"` | -| `recipe_quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | -| `transitional_request_index` | Integer | 0 | | -| `use_transitional_requests` | Boolean (0/1) | false | `1` | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | +| `request_from_buffers` | Boolean (`0` or `1`) | 1 | `1` | +| `requests_enabled` | Boolean (`0` or `1`) | 1 | `1` | +| `sections` | List (complex) ⚠️ | (factory) | | +| `trash_not_requested` | Boolean (`0` or `1`) | 0 | `1` | + +#### Signal Configuration + +Properties for configuring which signals the entity uses: + +| Property | Type | Description | +|----------|------|-------------| +| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... | --- -### Beacon +## Power -**Description:** An entity designed to apply module effects to other machines in its radius. +### ElectricPole -**Prototypes:** `"beacon"` +**Description:** An entity used to distribute electrical energy as a network. + +**Draftsman Source:** [ElectricPole class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/electric_pole.py) + +**Prototypes:** `"substation"`, `"medium-electric-pole"`, `"big-electric-pole"`, `"small-electric-pole"` **Connection Type:** Single circuit connection +#### Reading Entity Output + +Use `entity.output` to read: **Circuit network signals** + +```facto +Entity e = place("substation", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` + #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- -### Boiler +### PowerSwitch -**Description:** An entity that uses a fuel to convert a fluid (usually water) to another fluid (usually steam). +**Description:** An entity that connects or disconnects a power network. -**Prototypes:** `"boiler"`, `"heat-exchanger"` +**Draftsman Source:** [PowerSwitch class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/power_switch.py) + +**Prototypes:** `"power-switch"` **Connection Type:** Single circuit connection +#### Reading Entity Output + +Use `entity.output` to read: **Circuit network signals** + +```facto +Entity e = place("power-switch", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` + #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | +| `connect_to_logistic_network` | Boolean (`0` or `1`) | 0 | `1` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | +| `switch_state` | Boolean (`0` or `1`) | 0 | `1` | + +#### Signal Configuration + +Properties for configuring which signals the entity uses: + +| Property | Type | Description | +|----------|------|-------------| +| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... | +| `logistic_condition` | Condition (use `.enable = expr`) | The logistic condition that must be passed in order for this... | --- -### Generator +### Accumulator -**Description:** An entity that converts a fluid (usually steam) to electricity. +**Description:** An entity that stores electricity for periods of high demand. -**Prototypes:** `"steam-engine"`, `"steam-turbine"` +**Draftsman Source:** [Accumulator class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/accumulator.py) + +**Prototypes:** `"accumulator"` **Connection Type:** Single circuit connection +#### Reading Entity Output + +Use `entity.output` to read: **Charge level percentage** + +```facto +Entity e = place("accumulator", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` + #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | + +#### Signal Configuration + +Properties for configuring which signals the entity uses: + +| Property | Type | Description | +|----------|------|-------------| +| `output_signal` | String (signal name, e.g. `"signal-A"`) | The signal used to output this accumulator's charge level, i... | --- -### BurnerGenerator +### SolarPanel -**Description:** A electrical generator that turns burnable fuel directly into electrical energy. +**Description:** An entity that produces electricity depending on the presence of the sun. -**Prototypes:** `"burner-generator"` +**Draftsman Source:** [SolarPanel class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/solar_panel.py) + +**Prototypes:** `"solar-panel"` **Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- -### Reactor +## Fluids -**Description:** An entity that converts a fuel into thermal energy. +### Pump -**Prototypes:** `"heating-tower"`, `"nuclear-reactor"` +**Description:** An entity that aids fluid transfer through pipes. + +**Draftsman Source:** [Pump class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/pump.py) + +**Prototypes:** `"pump"` **Connection Type:** Single circuit connection +#### Reading Entity Output + +Use `entity.output` to read: **Circuit network signals** + +```facto +Entity e = place("pump", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` + #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| +| `connect_to_logistic_network` | Boolean (`0` or `1`) | 0 | `1` | +| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | -| `read_burner_fuel` | Boolean (0/1) | false | `1` | -| `read_temperature` | Boolean (0/1) | false | `1` | -| `temperature_signal` | String (signal name) | (factory) | `"signal-A"` | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | + +#### Signal Configuration + +Properties for configuring which signals the entity uses: + +| Property | Type | Description | +|----------|------|-------------| +| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... | +| `logistic_condition` | Condition (use `.enable = expr`) | The logistic condition that must be passed in order for this... | --- -### FusionReactor +### StorageTank -**Description:** (Factorio 2.0) +**Description:** An entity that stores a fluid. -**Prototypes:** `"fusion-reactor"` +**Draftsman Source:** [StorageTank class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/storage_tank.py) + +**Prototypes:** `"storage-tank"` **Connection Type:** Single circuit connection +#### Reading Entity Output + +Use `entity.output` to read: **Fluid level in the tank** + +```facto +Entity e = place("storage-tank", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` + #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| | `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- -### FusionGenerator +### OffshorePump -**Description:** (Factorio 2.0) +**Description:** An entity that pumps a fluid from the environment. -**Prototypes:** `"fusion-generator"` +**Draftsman Source:** [OffshorePump class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/offshore_pump.py) + +**Prototypes:** `"offshore-pump"` **Connection Type:** Single circuit connection +#### Reading Entity Output + +Use `entity.output` to read: **Circuit network signals** + +```facto +Entity e = place("offshore-pump", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` + #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| +| `connect_to_logistic_network` | Boolean (`0` or `1`) | 0 | `1` | | `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | + +#### Signal Configuration + +Properties for configuring which signals the entity uses: + +| Property | Type | Description | +|----------|------|-------------| +| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... | +| `logistic_condition` | Condition (use `.enable = expr`) | The logistic condition that must be passed in order for this... | --- -### LightningAttractor +### Pipe -**Description:** (Factorio 2.0) +**Description:** A structure that transports a fluid across a surface. -**Prototypes:** `"lightning-rod"`, `"fulgoran-ruin-attractor"`, `"lightning-collector"` +**Draftsman Source:** [Pipe class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/pipe.py) + +**Prototypes:** `"pipe"` **Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- -### AgriculturalTower +## Combat -**Description:** (Factorio 2.0) +### Radar -**Prototypes:** `"agricultural-tower"` +**Description:** An entity that reveals and scans neighbouring chunks. + +**Draftsman Source:** [Radar class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/radar.py) + +**Prototypes:** `"radar"` **Connection Type:** Single circuit connection -#### Circuit Signal I/O +#### Reading Entity Output -**Signal Inputs:** +Use `entity.output` to read: **Circuit network signals** -| Signal Property | Signal Type | Description | Enable With | -|-----------------|-------------|-------------|-------------| -| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` | -| `logistic_condition` | Any signal | Logistic network signal for enable/disable | `connect_to_logistic_network: 1` | +```facto +Entity e = place("radar", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `circuit_enabled` | Boolean (0/1) | false | `1` | -| `connect_to_logistic_network` | Boolean (0/1) | false | `1` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | -| `read_contents` | Boolean (0/1) | false | `1` | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- -## Storage +### ArtilleryTurret -### Container +**Description:** A turret which can only target enemy structures and uses artillery ammunition. -**Description:** An entity that holds items. +**Draftsman Source:** [ArtilleryTurret class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/artillery_turret.py) -**Prototypes:** `"factorio-logo-22tiles"`, `"bottomless-chest"`, `"red-chest"`, `"crash-site-chest-2"`, `"factorio-logo-16tiles"`, `"iron-chest"`, `"blue-chest"`, `"crash-site-chest-1"`, `"steel-chest"`, `"factorio-logo-11tiles"`, `"wooden-chest"` +**Prototypes:** `"artillery-turret"` **Connection Type:** Single circuit connection +#### Reading Entity Output + +Use `entity.output` to read: **Circuit network signals** + +```facto +Entity e = place("artillery-turret", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` + #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `bar` | Integer | None | | +| `auto_target` | Boolean (`0` or `1`) | 1 | `1` | +| `circuit_enabled` | Boolean (`0` or `1`) | 0 | `1` | +| `connect_to_logistic_network` | Boolean (`0` or `1`) | 0 | `1` | +| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | +| `read_ammo` | Boolean (`0` or `1`) | 1 | `1` | + +#### Signal Configuration + +Properties for configuring which signals the entity uses: + +| Property | Type | Description | +|----------|------|-------------| +| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... | +| `logistic_condition` | Condition (use `.enable = expr`) | The logistic condition that must be passed in order for this... | --- -### LogisticPassiveContainer +### AmmoTurret -**Description:** A logistics container that provides it's contents to the logistic network when needed by the network. +**Description:** An entity that automatically targets and attacks other forces within range. Consumes item-based ammunition. -**Prototypes:** `"passive-provider-chest"` +**Draftsman Source:** [AmmoTurret class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/ammo_turret.py) + +**Prototypes:** `"rocket-turret"`, `"gun-turret"`, `"railgun-turret"` **Connection Type:** Single circuit connection -#### Circuit Signal I/O +#### Reading Entity Output -**Signal Inputs:** +Use `entity.output` to read: **Circuit network signals** -| Signal Property | Signal Type | Description | Enable With | -|-----------------|-------------|-------------|-------------| -| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` | +```facto +Entity e = place("rocket-turret", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `bar` | Integer | None | | -| `circuit_enabled` | Boolean (0/1) | false | `1` | +| `circuit_enabled` | Boolean (`0` or `1`) | 0 | `1` | +| `connect_to_logistic_network` | Boolean (`0` or `1`) | 0 | `1` | +| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | +| `ignore_unlisted_targets_condition` | Condition (use `.enable = expr`) ⚠️ | (factory) | | +| `ignore_unprioritized` | Boolean (`0` or `1`) | 0 | `1` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | -| `read_contents` | Boolean (0/1) | true | `1` | +| `priority_list` | List (complex) ⚠️ | (factory) | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | +| `read_ammo` | Boolean (`0` or `1`) | 1 | `1` | +| `set_ignore_unprioritized` | Boolean (`0` or `1`) | 0 | `1` | +| `set_priority_list` | Boolean (`0` or `1`) | 0 | `1` | + +#### Signal Configuration + +Properties for configuring which signals the entity uses: + +| Property | Type | Description | +|----------|------|-------------| +| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... | +| `logistic_condition` | Condition (use `.enable = expr`) | The logistic condition that must be passed in order for this... | --- -### LogisticActiveContainer +### ElectricTurret -**Description:** A logistics container that immediately provides it's contents to the logistic network. +**Description:** An entity that automatically targets and attacks other forces within range. Uses electricity as ammunition. -**Prototypes:** `"active-provider-chest"` +**Draftsman Source:** [ElectricTurret class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/electric_turret.py) + +**Prototypes:** `"tesla-turret"`, `"laser-turret"` **Connection Type:** Single circuit connection -#### Circuit Signal I/O +#### Reading Entity Output -**Signal Inputs:** +Use `entity.output` to read: **Circuit network signals** -| Signal Property | Signal Type | Description | Enable With | -|-----------------|-------------|-------------|-------------| -| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` | +```facto +Entity e = place("tesla-turret", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `bar` | Integer | None | | +| `circuit_enabled` | Boolean (`0` or `1`) | 0 | `1` | +| `connect_to_logistic_network` | Boolean (`0` or `1`) | 0 | `1` | +| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | +| `ignore_unlisted_targets_condition` | Condition (use `.enable = expr`) ⚠️ | (factory) | | +| `ignore_unprioritized` | Boolean (`0` or `1`) | 0 | `1` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | -| `read_contents` | Boolean (0/1) | true | `1` | +| `priority_list` | List (complex) ⚠️ | (factory) | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | +| `read_ammo` | Boolean (`0` or `1`) | 1 | `1` | +| `set_ignore_unprioritized` | Boolean (`0` or `1`) | 0 | `1` | +| `set_priority_list` | Boolean (`0` or `1`) | 0 | `1` | + +#### Signal Configuration + +Properties for configuring which signals the entity uses: + +| Property | Type | Description | +|----------|------|-------------| +| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... | +| `logistic_condition` | Condition (use `.enable = expr`) | The logistic condition that must be passed in order for this... | --- -### LogisticStorageContainer +### FluidTurret -**Description:** A logistics container that stores items not currently being used in the logistic network. +**Description:** An entity that automatically targets and attacks other forces within range. Uses fluids as ammunition. -**Prototypes:** `"storage-chest"` +**Draftsman Source:** [FluidTurret class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/fluid_turret.py) + +**Prototypes:** `"flamethrower-turret"` **Connection Type:** Single circuit connection -#### Circuit Signal I/O +#### Reading Entity Output -**Signal Inputs:** +Use `entity.output` to read: **Circuit network signals** -| Signal Property | Signal Type | Description | Enable With | -|-----------------|-------------|-------------|-------------| -| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` | +```facto +Entity e = place("flamethrower-turret", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `bar` | Integer | None | | -| `circuit_enabled` | Boolean (0/1) | false | `1` | +| `circuit_enabled` | Boolean (`0` or `1`) | 0 | `1` | +| `connect_to_logistic_network` | Boolean (`0` or `1`) | 0 | `1` | +| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | +| `ignore_unlisted_targets_condition` | Condition (use `.enable = expr`) ⚠️ | (factory) | | +| `ignore_unprioritized` | Boolean (`0` or `1`) | 0 | `1` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | -| `read_contents` | Boolean (0/1) | true | `1` | -| `request_from_buffers` | Boolean (0/1) | true | `1` | -| `requests_enabled` | Boolean (0/1) | true | `1` | -| `sections` | List (complex) ⚠️ | (factory) | | -| `trash_not_requested` | Boolean (0/1) | false | `1` | +| `priority_list` | List (complex) ⚠️ | (factory) | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | +| `read_ammo` | Boolean (`0` or `1`) | 1 | `1` | +| `set_ignore_unprioritized` | Boolean (`0` or `1`) | 0 | `1` | +| `set_priority_list` | Boolean (`0` or `1`) | 0 | `1` | + +#### Signal Configuration + +Properties for configuring which signals the entity uses: + +| Property | Type | Description | +|----------|------|-------------| +| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... | +| `logistic_condition` | Condition (use `.enable = expr`) | The logistic condition that must be passed in order for this... | --- -### LogisticRequestContainer +### Wall -**Description:** A logistics container that requests items with a primary priority. +**Description:** A static barrier that acts as protection for structures. -**Prototypes:** `"requester-chest"` +**Draftsman Source:** [Wall class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/wall.py) + +**Prototypes:** `"stone-wall"` **Connection Type:** Single circuit connection -#### Circuit Signal I/O +#### Reading Entity Output -**Signal Inputs:** +Use `entity.output` to read: **Circuit network signals** -| Signal Property | Signal Type | Description | Enable With | -|-----------------|-------------|-------------|-------------| -| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` | +```facto +Entity e = place("stone-wall", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `bar` | Integer | None | | -| `circuit_enabled` | Boolean (0/1) | false | `1` | +| `circuit_enabled` | Boolean (`0` or `1`) | 1 | `1` | +| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | -| `mode_of_operation` | Integer ([LogisticModeOfOperation](#logisticmodeofoperation)) | 0 | `0 # SEND_CONTENTS` | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | -| `request_from_buffers` | Boolean (0/1) | false | `1` | -| `requests_enabled` | Boolean (0/1) | true | `1` | -| `sections` | List (complex) ⚠️ | (factory) | | -| `trash_not_requested` | Boolean (0/1) | false | `1` | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | +| `read_gate` | Boolean (`0` or `1`) | 0 | `1` | + +#### Signal Configuration + +Properties for configuring which signals the entity uses: + +| Property | Type | Description | +|----------|------|-------------| +| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... | +| `output_signal` | String (signal name, e.g. `"signal-A"`) | | --- -### LogisticBufferContainer +### Gate -**Description:** A logistics container that requests items on a secondary priority. +**Description:** A wall that opens near the player. -**Prototypes:** `"buffer-chest"` +**Draftsman Source:** [Gate class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/gate.py) + +**Prototypes:** `"gate"` **Connection Type:** Single circuit connection -#### Circuit Signal I/O +#### Settable Properties + +Set at placement: `place("name", x, y, {prop: value})` + +| Property | Type | Default | Example | +|----------|------|---------|---------| +| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | +| `item_requests` | List (complex) ⚠️ | (factory) | | +| `name` | String (entity prototype name) | (factory) | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | -**Signal Inputs:** +--- -| Signal Property | Signal Type | Description | Enable With | -|-----------------|-------------|-------------|-------------| -| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` | +### LandMine + +**Description:** An entity that explodes when in proximity to another force. + +**Draftsman Source:** [LandMine class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/land_mine.py) + +**Prototypes:** `"land-mine"` + +**Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `bar` | Integer | None | | -| `circuit_enabled` | Boolean (0/1) | false | `1` | | `item_requests` | List (complex) ⚠️ | (factory) | | -| `mode_of_operation` | Integer ([LogisticModeOfOperation](#logisticmodeofoperation)) | 0 | `0 # SEND_CONTENTS` | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | -| `request_from_buffers` | Boolean (0/1) | true | `1` | -| `requests_enabled` | Boolean (0/1) | true | `1` | -| `sections` | List (complex) ⚠️ | (factory) | | -| `trash_not_requested` | Boolean (0/1) | false | `1` | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- -### LinkedContainer +## Robots & Logistics -**Description:** An entity that allows sharing it's contents with any other ``LinkedContainer`` with the same ``link_id``. +### Roboport -**Prototypes:** `"linked-chest"` +**Description:** An entity that acts as a node in a logistics network. + +**Draftsman Source:** [Roboport class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/roboport.py) + +**Prototypes:** `"roboport"` **Connection Type:** Single circuit connection +#### Reading Entity Output + +Use `entity.output` to read: **Robot counts and logistics info** + +```facto +Entity e = place("roboport", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` + +**Enable properties:** + +- `read_logistics`: Read logistic robot counts +- `read_robot_stats`: Read robot statistics + #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `bar` | Integer | None | | +| `available_construction_signal` | String (signal name, e.g. `"signal-A"`) | (factory) | `"signal-A"` | +| `available_logistic_signal` | String (signal name, e.g. `"signal-A"`) | (factory) | `"signal-A"` | | `item_requests` | List (complex) ⚠️ | (factory) | | -| `link_id` | Integer | 0 | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | +| `read_items_mode` | Integer (see enum reference) | 1 | | +| `read_robot_stats` | Boolean (`0` or `1`) | 0 | `1` | +| `request_from_buffers` | Boolean (`0` or `1`) | 1 | `1` | +| `requests_enabled` | Boolean (`0` or `1`) | 1 | `1` | +| `roboport_count_signal` | String (signal name, e.g. `"signal-A"`) | (factory) | `"signal-A"` | +| `sections` | List (complex) ⚠️ | (factory) | | +| `total_construction_signal` | String (signal name, e.g. `"signal-A"`) | (factory) | `"signal-A"` | +| `total_logistic_signal` | String (signal name, e.g. `"signal-A"`) | (factory) | `"signal-A"` | +| `trash_not_requested` | Boolean (`0` or `1`) | 0 | `1` | --- -## Power +## Space -### ElectricPole +### SpacePlatformHub -**Description:** An entity used to distribute electrical energy as a network. +**Description:** (Factorio 2.0) -**Prototypes:** `"substation"`, `"medium-electric-pole"`, `"big-electric-pole"`, `"small-electric-pole"` +**Draftsman Source:** [SpacePlatformHub class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/space_platform_hub.py) + +**Prototypes:** `"space-platform-hub"` **Connection Type:** Single circuit connection +#### Reading Entity Output + +Use `entity.output` to read: **Circuit network signals** + +```facto +Entity e = place("space-platform-hub", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` + #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| +| `damage_taken_signal` | String (signal name, e.g. `"signal-A"`) | (factory) | `"signal-A"` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | +| `read_contents` | Boolean (`0` or `1`) | 1 | `1` | +| `read_damage_taken` | Boolean (`0` or `1`) | 0 | `1` | +| `read_moving_from` | Boolean (`0` or `1`) | 0 | `1` | +| `read_moving_to` | Boolean (`0` or `1`) | 0 | `1` | +| `read_speed` | Boolean (`0` or `1`) | 0 | `1` | +| `request_from_buffers` | Boolean (`0` or `1`) | 1 | `1` | +| `request_missing_construction_materials` | Boolean (`0` or `1`) | 1 | `1` | +| `requests_enabled` | Boolean (`0` or `1`) | 1 | `1` | +| `sections` | List (complex) ⚠️ | (factory) | | +| `send_to_platform` | Boolean (`0` or `1`) | 1 | `1` | +| `speed_signal` | String (signal name, e.g. `"signal-A"`) | (factory) | `"signal-A"` | +| `trash_not_requested` | Boolean (`0` or `1`) | 0 | `1` | --- -### PowerSwitch +### CargoLandingPad -**Description:** An entity that connects or disconnects a power network. +**Description:** (Factorio 2.0) -**Prototypes:** `"power-switch"` +**Draftsman Source:** [CargoLandingPad class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/cargo_landing_pad.py) + +**Prototypes:** `"cargo-landing-pad"` **Connection Type:** Single circuit connection -#### Circuit Signal I/O +#### Reading Entity Output -**Signal Inputs:** +Use `entity.output` to read: **Circuit network signals** -| Signal Property | Signal Type | Description | Enable With | -|-----------------|-------------|-------------|-------------| -| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` | -| `logistic_condition` | Any signal | Logistic network signal for enable/disable | `connect_to_logistic_network: 1` | +```facto +Entity e = place("cargo-landing-pad", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `connect_to_logistic_network` | Boolean (0/1) | false | `1` | | `item_requests` | List (complex) ⚠️ | (factory) | | +| `mode_of_operation` | Integer ([LogisticModeOfOperation](#logisticmodeofoperation)) | 0 | `0 # SEND_CONTENTS` | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | -| `switch_state` | Boolean (0/1) | false | `1` | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | +| `request_from_buffers` | Boolean (`0` or `1`) | 1 | `1` | +| `requests_enabled` | Boolean (`0` or `1`) | 1 | `1` | +| `sections` | List (complex) ⚠️ | (factory) | | +| `trash_not_requested` | Boolean (`0` or `1`) | 0 | `1` | --- -### Accumulator +### AsteroidCollector -**Description:** An entity that stores electricity for periods of high demand. +**Description:** (Factorio 2.0) -**Prototypes:** `"accumulator"` +**Draftsman Source:** [AsteroidCollector class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/asteroid_collector.py) + +**Prototypes:** `"asteroid-collector"` **Connection Type:** Single circuit connection -#### Circuit Signal I/O +#### Reading Entity Output -**Signal Outputs:** +Use `entity.output` to read: **Circuit network signals** -| Signal Property | Signal Type | Description | Enable With | -|-----------------|-------------|-------------|-------------| -| `output_signal` | Result value | Signal to output the combinator result on | Always active | +```facto +Entity e = place("asteroid-collector", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| +| `chunk_filter` | List (complex) ⚠️ | (factory) | | +| `circuit_enabled` | Boolean (`0` or `1`) | 0 | `1` | +| `circuit_set_filters` | Boolean (`0` or `1`) | 0 | `1` | +| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | +| `read_contents` | Boolean (`0` or `1`) | 0 | `1` | +| `read_hands` | Boolean (`0` or `1`) | 1 | `1` | +| `result_inventory` | Complex (see draftsman docs) ⚠️ | (factory) | | + +#### Signal Configuration + +Properties for configuring which signals the entity uses: + +| Property | Type | Description | +|----------|------|-------------| +| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... | --- -### SolarPanel +### CargoBay -**Description:** An entity that produces electricity depending on the presence of the sun. +**Description:** (Factorio 2.0) -**Prototypes:** `"solar-panel"` +**Draftsman Source:** [CargoBay class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/cargo_bay.py) + +**Prototypes:** `"cargo-bay"` **Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- -### ElectricEnergyInterface +### Thruster -**Description:** An entity that interfaces with an electrical grid. +**Description:** (Factorio 2.0) -**Prototypes:** `"electric-energy-interface"`, `"hidden-electric-energy-interface"` +**Draftsman Source:** [Thruster class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/thruster.py) + +**Prototypes:** `"thruster"` **Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `buffer_size` | Complex (see draftsman docs) ⚠️ | (factory) | | -| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `power_production` | Complex (see draftsman docs) ⚠️ | (factory) | | -| `power_usage` | Complex (see draftsman docs) ⚠️ | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- -## Fluids +## Misc -### Pump +### ProgrammableSpeaker -**Description:** An entity that aids fluid transfer through pipes. +**Description:** An entity that makes sounds that can be controlled by circuit network signals. -**Prototypes:** `"pump"` +**Draftsman Source:** [ProgrammableSpeaker class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/programmable_speaker.py) + +**Prototypes:** `"programmable-speaker"` **Connection Type:** Single circuit connection -#### Circuit Signal I/O +#### Reading Entity Output -**Signal Inputs:** +Use `entity.output` to read: **Circuit network signals** -| Signal Property | Signal Type | Description | Enable With | -|-----------------|-------------|-------------|-------------| -| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` | -| `logistic_condition` | Any signal | Logistic network signal for enable/disable | `connect_to_logistic_network: 1` | +```facto +Entity e = place("programmable-speaker", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `connect_to_logistic_network` | Boolean (0/1) | false | `1` | -| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | +| `alert_icon` | String (signal name, e.g. `"signal-A"`) | None | | +| `alert_message` | Complex (see draftsman docs) ⚠️ | "" | | +| `allow_polyphony` | Boolean (`0` or `1`) | 0 | `1` | +| `circuit_enabled` | Boolean (`0` or `1`) | 0 | `1` | +| `instrument_id` | Integer | 0 | | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `note_id` | Integer | 0 | | +| `playback_mode` | One of: `"local"`, `"surface"`, `"global"` ([PlaybackMode](#playbackmode)) | "local" | `"local"` | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | +| `show_alert` | Boolean (`0` or `1`) | 0 | `1` | +| `show_alert_on_map` | Boolean (`0` or `1`) | 1 | `1` | +| `signal_value_is_pitch` | Boolean (`0` or `1`) | 0 | `1` | +| `stop_playing_sounds` | Boolean (`0` or `1`) | 0 | `1` | +| `volume` | Number | 1.0 | | +| `volume_controlled_by_signal` | Boolean (`0` or `1`) | 0 | `1` | +| `volume_signal` | String (signal name, e.g. `"signal-A"`) | None | `"signal-A"` | + +#### Signal Configuration + +Properties for configuring which signals the entity uses: + +| Property | Type | Description | +|----------|------|-------------| +| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... | --- -### StorageTank +### Car -**Description:** An entity that stores a fluid. +**Description:** (Factorio 2.0) -**Prototypes:** `"storage-tank"` +**Draftsman Source:** [Car class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/car.py) + +**Prototypes:** `"car"`, `"tank"` **Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | +| `ammo_inventory` | Complex (see draftsman docs) ⚠️ | (factory) | | +| `driver_is_main_gunner` | Boolean (`0` or `1`) | 0 | `1` | +| `enable_logistics_while_moving` | Boolean (`0` or `1`) | 1 | `1` | +| `equipment` | List (complex) ⚠️ | (factory) | | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `orientation` | Complex (see draftsman docs) ⚠️ | | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | +| `selected_gun_index` | Integer | 0 | | +| `trunk_inventory` | Complex (see draftsman docs) ⚠️ | (factory) | | --- -### OffshorePump +### SpiderVehicle -**Description:** An entity that pumps a fluid from the environment. +**Description:** (Factorio 2.0) + +**Draftsman Source:** [SpiderVehicle class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/spider_vehicle.py) + +**Prototypes:** `"spidertron"` + +**Connection Type:** Single circuit connection + +#### Settable Properties + +Set at placement: `place("name", x, y, {prop: value})` + +| Property | Type | Default | Example | +|----------|------|---------|---------| +| `ammo_inventory` | Complex (see draftsman docs) ⚠️ | (factory) | | +| `auto_target_with_gunner` | Boolean (`0` or `1`) | 0 | `1` | +| `auto_target_without_gunner` | Boolean (`0` or `1`) | 1 | `1` | +| `color` | Object `{r, g, b}` (0-255 each) | (factory) | `{r: 255, g: 0, b: 0}` | +| `driver_is_main_gunner` | Boolean (`0` or `1`) | 0 | `1` | +| `enable_logistics_while_moving` | Boolean (`0` or `1`) | 1 | `1` | +| `equipment` | List (complex) ⚠️ | (factory) | | +| `item_requests` | List (complex) ⚠️ | (factory) | | +| `name` | String (entity prototype name) | (factory) | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | +| `request_from_buffers` | Boolean (`0` or `1`) | 1 | `1` | +| `requests_enabled` | Boolean (`0` or `1`) | 1 | `1` | +| `sections` | List (complex) ⚠️ | (factory) | | +| `selected_gun_index` | Integer | 0 | | +| `trash_not_requested` | Boolean (`0` or `1`) | 0 | `1` | +| `trunk_inventory` | Complex (see draftsman docs) ⚠️ | (factory) | | + +--- -**Prototypes:** `"offshore-pump"` +### HeatPipe -**Connection Type:** Single circuit connection +**Description:** An entity used to transfer thermal energy. -#### Circuit Signal I/O +**Draftsman Source:** [HeatPipe class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/heat_pipe.py) -**Signal Inputs:** +**Prototypes:** `"heat-pipe"` -| Signal Property | Signal Type | Description | Enable With | -|-----------------|-------------|-------------|-------------| -| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` | -| `logistic_condition` | Any signal | Logistic network signal for enable/disable | `connect_to_logistic_network: 1` | +**Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `connect_to_logistic_network` | Boolean (0/1) | false | `1` | -| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- -### Pipe +### HeatInterface -**Description:** A structure that transports a fluid across a surface. +**Description:** An entity that interacts with a heat network. -**Prototypes:** `"pipe"` +**Draftsman Source:** [HeatInterface class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/heat_interface.py) + +**Prototypes:** `"heat-interface"` **Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| | `item_requests` | List (complex) ⚠️ | (factory) | | +| `mode` | One of: `"at-least"`, `"at-most"`, `"exactly"`, `"add"`, `"remove"` ([InfinityMode](#infinitymode)) | "at-least" | `"at-least"` | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | +| `temperature` | Number | 0.0 | | --- -### InfinityPipe +## Other Entities -**Description:** An entity used to create an infinite amount of any fluid at any temperature. +Additional entities not in the main categories: -**Prototypes:** `"infinity-pipe"` +### AgriculturalTower + +**Description:** (Factorio 2.0) + +**Draftsman Source:** [AgriculturalTower class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/agricultural_tower.py) + +**Prototypes:** `"agricultural-tower"` **Connection Type:** Single circuit connection +#### Reading Entity Output + +Use `entity.output` to read: **Circuit network signals** + +```facto +Entity e = place("agricultural-tower", 0, 0); +Bundle signals = e.output; # Returns all output signals +``` + #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `fluid_name` | Condition (set via .enable) ⚠️ | None | | +| `circuit_enabled` | Boolean (`0` or `1`) | 0 | `1` | +| `connect_to_logistic_network` | Boolean (`0` or `1`) | 0 | `1` | | `item_requests` | List (complex) ⚠️ | (factory) | | -| `mode` | One of: at-least, at-most, exactly, add, remove | "at-least" | | | `name` | String (entity prototype name) | (factory) | | -| `percentage` | Complex (see draftsman docs) ⚠️ | 0.0 | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | -| `temperature` | Integer | 0 | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | +| `read_contents` | Boolean (`0` or `1`) | 0 | `1` | + +#### Signal Configuration + +Properties for configuring which signals the entity uses: + +| Property | Type | Description | +|----------|------|-------------| +| `circuit_condition` | Condition (use `.enable = expr`) | The circuit condition that must be passed in order for this ... | +| `logistic_condition` | Condition (use `.enable = expr`) | The logistic condition that must be passed in order for this... | --- -## Combat +### BurnerGenerator -### Radar +**Description:** A electrical generator that turns burnable fuel directly into electrical energy. -**Description:** An entity that reveals and scans neighbouring chunks. +**Draftsman Source:** [BurnerGenerator class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/burner_generator.py) -**Prototypes:** `"radar"` +**Prototypes:** `"burner-generator"` **Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| +| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- -### ArtilleryTurret - -**Description:** A turret which can only target enemy structures and uses artillery ammunition. - -**Prototypes:** `"artillery-turret"` +### CurvedRailA -**Connection Type:** Single circuit connection +**Description:** Curved rails which connect straight rails to half-diagonal rails. -#### Circuit Signal I/O +**Draftsman Source:** [CurvedRailA class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/curved_rail_a.py) -**Signal Inputs:** +**Prototypes:** `"curved-rail-a"` -| Signal Property | Signal Type | Description | Enable With | -|-----------------|-------------|-------------|-------------| -| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` | -| `logistic_condition` | Any signal | Logistic network signal for enable/disable | `connect_to_logistic_network: 1` | +**Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `auto_target` | Boolean (0/1) | true | `1` | -| `circuit_enabled` | Boolean (0/1) | false | `1` | -| `connect_to_logistic_network` | Boolean (0/1) | false | `1` | | `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | -| `read_ammo` | Boolean (0/1) | true | `1` | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- -### AmmoTurret - -**Description:** An entity that automatically targets and attacks other forces within range. Consumes item-based ammunition. - -**Prototypes:** `"rocket-turret"`, `"gun-turret"`, `"railgun-turret"` +### CurvedRailB -**Connection Type:** Single circuit connection +**Description:** Curved rails which connect half-diagonal rails to diagonal rails. -#### Circuit Signal I/O +**Draftsman Source:** [CurvedRailB class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/curved_rail_b.py) -**Signal Inputs:** +**Prototypes:** `"curved-rail-b"` -| Signal Property | Signal Type | Description | Enable With | -|-----------------|-------------|-------------|-------------| -| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` | -| `logistic_condition` | Any signal | Logistic network signal for enable/disable | `connect_to_logistic_network: 1` | +**Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `circuit_enabled` | Boolean (0/1) | false | `1` | -| `connect_to_logistic_network` | Boolean (0/1) | false | `1` | | `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | -| `ignore_unlisted_targets_condition` | Condition (set via .enable) ⚠️ | (factory) | | -| `ignore_unprioritized` | Boolean (0/1) | false | `1` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `priority_list` | List (complex) ⚠️ | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | -| `read_ammo` | Boolean (0/1) | true | `1` | -| `set_ignore_unprioritized` | Boolean (0/1) | false | `1` | -| `set_priority_list` | Boolean (0/1) | false | `1` | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- -### ElectricTurret - -**Description:** An entity that automatically targets and attacks other forces within range. Uses electricity as ammunition. - -**Prototypes:** `"tesla-turret"`, `"laser-turret"` +### ElectricEnergyInterface -**Connection Type:** Single circuit connection +**Description:** An entity that interfaces with an electrical grid. -#### Circuit Signal I/O +**Draftsman Source:** [ElectricEnergyInterface class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/electric_energy_interface.py) -**Signal Inputs:** +**Prototypes:** `"electric-energy-interface"`, `"hidden-electric-energy-interface"` -| Signal Property | Signal Type | Description | Enable With | -|-----------------|-------------|-------------|-------------| -| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` | -| `logistic_condition` | Any signal | Logistic network signal for enable/disable | `connect_to_logistic_network: 1` | +**Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `circuit_enabled` | Boolean (0/1) | false | `1` | -| `connect_to_logistic_network` | Boolean (0/1) | false | `1` | +| `buffer_size` | Number | (factory) | | | `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | -| `ignore_unlisted_targets_condition` | Condition (set via .enable) ⚠️ | (factory) | | -| `ignore_unprioritized` | Boolean (0/1) | false | `1` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `priority_list` | List (complex) ⚠️ | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | -| `read_ammo` | Boolean (0/1) | true | `1` | -| `set_ignore_unprioritized` | Boolean (0/1) | false | `1` | -| `set_priority_list` | Boolean (0/1) | false | `1` | +| `power_production` | Number | (factory) | | +| `power_usage` | Number | (factory) | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- -### FluidTurret - -**Description:** An entity that automatically targets and attacks other forces within range. Uses fluids as ammunition. - -**Prototypes:** `"flamethrower-turret"` +### ElevatedCurvedRailA -**Connection Type:** Single circuit connection +**Description:** (Factorio 2.0) -#### Circuit Signal I/O +**Draftsman Source:** [ElevatedCurvedRailA class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/elevated_curved_rail_a.py) -**Signal Inputs:** +**Prototypes:** `"dummy-elevated-curved-rail-a"`, `"elevated-curved-rail-a"` -| Signal Property | Signal Type | Description | Enable With | -|-----------------|-------------|-------------|-------------| -| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` | -| `logistic_condition` | Any signal | Logistic network signal for enable/disable | `connect_to_logistic_network: 1` | +**Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `circuit_enabled` | Boolean (0/1) | false | `1` | -| `connect_to_logistic_network` | Boolean (0/1) | false | `1` | | `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | -| `ignore_unlisted_targets_condition` | Condition (set via .enable) ⚠️ | (factory) | | -| `ignore_unprioritized` | Boolean (0/1) | false | `1` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `priority_list` | List (complex) ⚠️ | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | -| `read_ammo` | Boolean (0/1) | true | `1` | -| `set_ignore_unprioritized` | Boolean (0/1) | false | `1` | -| `set_priority_list` | Boolean (0/1) | false | `1` | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- -### Wall - -**Description:** A static barrier that acts as protection for structures. - -**Prototypes:** `"stone-wall"` - -**Connection Type:** Single circuit connection - -#### Circuit Signal I/O +### ElevatedCurvedRailB -**Signal Inputs:** +**Description:** (Factorio 2.0) -| Signal Property | Signal Type | Description | Enable With | -|-----------------|-------------|-------------|-------------| -| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` | +**Draftsman Source:** [ElevatedCurvedRailB class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/elevated_curved_rail_b.py) -**Signal Outputs:** +**Prototypes:** `"dummy-elevated-curved-rail-b"`, `"elevated-curved-rail-b"` -| Signal Property | Signal Type | Description | Enable With | -|-----------------|-------------|-------------|-------------| -| `output_signal` | Result value | Signal to output the combinator result on | Always active | +**Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `circuit_enabled` | Boolean (0/1) | true | `1` | | `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | -| `read_gate` | Boolean (0/1) | false | `1` | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- -### Gate +### ElevatedHalfDiagonalRail -**Description:** A wall that opens near the player. +**Description:** (Factorio 2.0) -**Prototypes:** `"gate"` +**Draftsman Source:** [ElevatedHalfDiagonalRail class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/elevated_half_diagonal_rail.py) + +**Prototypes:** `"dummy-elevated-half-diagonal-rail"`, `"elevated-half-diagonal-rail"` **Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| | `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- -## Robots & Logistics +### ElevatedStraightRail -### Roboport +**Description:** (Factorio 2.0) -**Description:** An entity that acts as a node in a logistics network. +**Draftsman Source:** [ElevatedStraightRail class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/elevated_straight_rail.py) -**Prototypes:** `"roboport"` +**Prototypes:** `"dummy-elevated-straight-rail"`, `"elevated-straight-rail"` **Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `available_construction_signal` | String (signal name) | (factory) | `"signal-A"` | -| `available_logistic_signal` | String (signal name) | (factory) | `"signal-A"` | +| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | -| `read_items_mode` | Integer ([ReadItemsMode](#readitemsmode)) | 1 | `1 # LOGISTICS` | -| `read_robot_stats` | Boolean (0/1) | false | `1` | -| `request_from_buffers` | Boolean (0/1) | true | `1` | -| `requests_enabled` | Boolean (0/1) | true | `1` | -| `roboport_count_signal` | String (signal name) | (factory) | `"signal-A"` | -| `sections` | List (complex) ⚠️ | (factory) | | -| `total_construction_signal` | String (signal name) | (factory) | `"signal-A"` | -| `total_logistic_signal` | String (signal name) | (factory) | `"signal-A"` | -| `trash_not_requested` | Boolean (0/1) | false | `1` | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- -## Space - -### SpacePlatformHub +### FusionGenerator **Description:** (Factorio 2.0) -**Prototypes:** `"space-platform-hub"` +**Draftsman Source:** [FusionGenerator class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/fusion_generator.py) + +**Prototypes:** `"fusion-generator"` **Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `damage_taken_signal` | String (signal name) | (factory) | `"signal-A"` | +| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | -| `read_contents` | Boolean (0/1) | true | `1` | -| `read_damage_taken` | Boolean (0/1) | false | `1` | -| `read_moving_from` | Boolean (0/1) | false | `1` | -| `read_moving_to` | Boolean (0/1) | false | `1` | -| `read_speed` | Boolean (0/1) | false | `1` | -| `request_from_buffers` | Boolean (0/1) | true | `1` | -| `request_missing_construction_materials` | Boolean (0/1) | true | `1` | -| `requests_enabled` | Boolean (0/1) | true | `1` | -| `sections` | List (complex) ⚠️ | (factory) | | -| `send_to_platform` | Boolean (0/1) | true | `1` | -| `speed_signal` | String (signal name) | (factory) | `"signal-A"` | -| `trash_not_requested` | Boolean (0/1) | false | `1` | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- -### CargoLandingPad +### FusionReactor **Description:** (Factorio 2.0) -**Prototypes:** `"cargo-landing-pad"` +**Draftsman Source:** [FusionReactor class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/fusion_reactor.py) + +**Prototypes:** `"fusion-reactor"` **Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| +| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | -| `mode_of_operation` | Integer ([LogisticModeOfOperation](#logisticmodeofoperation)) | 0 | `0 # SEND_CONTENTS` | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | -| `request_from_buffers` | Boolean (0/1) | true | `1` | -| `requests_enabled` | Boolean (0/1) | true | `1` | -| `sections` | List (complex) ⚠️ | (factory) | | -| `trash_not_requested` | Boolean (0/1) | false | `1` | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- -### AsteroidCollector +### HalfDiagonalRail **Description:** (Factorio 2.0) -**Prototypes:** `"asteroid-collector"` - -**Connection Type:** Single circuit connection - -#### Circuit Signal I/O +**Draftsman Source:** [HalfDiagonalRail class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/half_diagonal_rail.py) -**Signal Inputs:** +**Prototypes:** `"half-diagonal-rail"` -| Signal Property | Signal Type | Description | Enable With | -|-----------------|-------------|-------------|-------------| -| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` | +**Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `chunk_filter` | List (complex) ⚠️ | (factory) | | -| `circuit_enabled` | Boolean (0/1) | false | `1` | -| `circuit_set_filters` | Boolean (0/1) | false | `1` | | `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | -| `read_contents` | Boolean (0/1) | false | `1` | -| `read_hands` | Boolean (0/1) | true | `1` | -| `result_inventory` | Complex (see draftsman docs) ⚠️ | (factory) | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- -### CargoBay +### InfinityContainer -**Description:** (Factorio 2.0) +**Description:** An entity used to create an infinite amount of any item. -**Prototypes:** `"cargo-bay"` +**Draftsman Source:** [InfinityContainer class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/infinity_container.py) + +**Prototypes:** `"infinity-chest"` **Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| +| `bar` | Integer | None | | +| `filters` | List (complex) ⚠️ | (factory) | | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | +| `remove_unfiltered_items` | Boolean (`0` or `1`) | 0 | `1` | --- -### Thruster +### InfinityPipe -**Description:** (Factorio 2.0) +**Description:** An entity used to create an infinite amount of any fluid at any temperature. -**Prototypes:** `"thruster"` +**Draftsman Source:** [InfinityPipe class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/infinity_pipe.py) + +**Prototypes:** `"infinity-pipe"` **Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| +| `fluid_name` | Condition (use `.enable = expr`) ⚠️ | None | | | `item_requests` | List (complex) ⚠️ | (factory) | | +| `mode` | One of: `"at-least"`, `"at-most"`, `"exactly"`, `"add"`, `"remove"` ([InfinityMode](#infinitymode)) | "at-least" | `"at-least"` | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `percentage` | Number | 0.0 | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | +| `temperature` | Integer | 0 | | --- -## Misc +### LegacyCurvedRail -### ProgrammableSpeaker +**Description:** An old, Factorio 1.0 curved rail entity. -**Description:** An entity that makes sounds that can be controlled by circuit network signals. +**Draftsman Source:** [LegacyCurvedRail class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/legacy_curved_rail.py) -**Prototypes:** `"programmable-speaker"` +**Prototypes:** `"legacy-curved-rail"` **Connection Type:** Single circuit connection -#### Circuit Signal I/O - -**Signal Inputs:** - -| Signal Property | Signal Type | Description | Enable With | -|-----------------|-------------|-------------|-------------| -| `circuit_condition` | Any signal | Signal used in enable/disable condition | `circuit_enabled: 1` | -| `signal_value_is_pitch` | Pitch value | Signal value controls note pitch | Always active | - #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `alert_icon` | String (signal name) | None | | -| `alert_message` | Complex (see draftsman docs) ⚠️ | "" | | -| `allow_polyphony` | Boolean (0/1) | false | `1` | -| `circuit_enabled` | Boolean (0/1) | false | `1` | -| `instrument_id` | Integer | 0 | | +| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `note_id` | Integer | 0 | | -| `playback_mode` | One of: local, surface, global | "local" | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | -| `show_alert` | Boolean (0/1) | false | `1` | -| `show_alert_on_map` | Boolean (0/1) | true | `1` | -| `stop_playing_sounds` | Boolean (0/1) | false | `1` | -| `volume` | Complex (see draftsman docs) ⚠️ | 1.0 | | -| `volume_controlled_by_signal` | Boolean (0/1) | false | `1` | -| `volume_signal` | String (signal name) | None | `"signal-A"` | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- -### Car +### LegacyStraightRail -**Description:** (Factorio 2.0) +**Description:** An old, Factorio 1.0 straight rail entity. -**Prototypes:** `"car"`, `"tank"` +**Draftsman Source:** [LegacyStraightRail class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/legacy_straight_rail.py) + +**Prototypes:** `"legacy-straight-rail"` **Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `ammo_inventory` | Complex (see draftsman docs) ⚠️ | (factory) | | -| `driver_is_main_gunner` | Boolean (0/1) | false | `1` | -| `enable_logistics_while_moving` | Boolean (0/1) | true | `1` | -| `equipment` | List (complex) ⚠️ | (factory) | | +| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `orientation` | Float (0.0-1.0) | | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | -| `selected_gun_index` | Integer | 0 | | -| `trunk_inventory` | Complex (see draftsman docs) ⚠️ | (factory) | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- -### SpiderVehicle +### LightningAttractor **Description:** (Factorio 2.0) -**Prototypes:** `"spidertron"` +**Draftsman Source:** [LightningAttractor class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/lightning_attractor.py) + +**Prototypes:** `"lightning-rod"`, `"fulgoran-ruin-attractor"`, `"lightning-collector"` **Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `ammo_inventory` | Complex (see draftsman docs) ⚠️ | (factory) | | -| `auto_target_with_gunner` | Boolean (0/1) | false | `1` | -| `auto_target_without_gunner` | Boolean (0/1) | true | `1` | -| `color` | Color {r: 0-255, g: 0-255, b: 0-255} | (factory) | `{r: 255, g: 0, b: 0}` | -| `driver_is_main_gunner` | Boolean (0/1) | false | `1` | -| `enable_logistics_while_moving` | Boolean (0/1) | true | `1` | -| `equipment` | List (complex) ⚠️ | (factory) | | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | -| `request_from_buffers` | Boolean (0/1) | true | `1` | -| `requests_enabled` | Boolean (0/1) | true | `1` | -| `sections` | List (complex) ⚠️ | (factory) | | -| `selected_gun_index` | Integer | 0 | | -| `trash_not_requested` | Boolean (0/1) | false | `1` | -| `trunk_inventory` | Complex (see draftsman docs) ⚠️ | (factory) | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- -### HeatPipe +### LinkedContainer -**Description:** An entity used to transfer thermal energy. +**Description:** An entity that allows sharing it's contents with any other ``LinkedContainer`` with the same ``link_id``. -**Prototypes:** `"heat-pipe"` +**Draftsman Source:** [LinkedContainer class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/linked_container.py) + +**Prototypes:** `"linked-chest"` **Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| +| `bar` | Integer | None | | | `item_requests` | List (complex) ⚠️ | (factory) | | +| `link_id` | Integer | 0 | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- -### HeatInterface +### PlayerPort -**Description:** An entity that interacts with a heat network. +**Description:** A constructable respawn point typically used in scenarios. -**Prototypes:** `"heat-interface"` +**Draftsman Source:** [PlayerPort class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/player_port.py) **Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| | `item_requests` | List (complex) ⚠️ | (factory) | | -| `mode` | One of: at-least, at-most, exactly, add, remove | "at-least" | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | -| `temperature` | Complex (see draftsman docs) ⚠️ | 0.0 | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- -### SimpleEntityWithOwner +### RailRamp -**Description:** A generic entity owned by some other entity. +**Description:** (Factorio 2.0) -**Prototypes:** `"simple-entity-with-owner"` +**Draftsman Source:** [RailRamp class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/rail_ramp.py) + +**Prototypes:** `"rail-ramp"`, `"dummy-rail-ramp"` **Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| | `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | -| `variation` | Integer | 1 | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- -### SimpleEntityWithForce +### RailSupport -**Description:** A generic entity associated with friends or foes. +**Description:** (Factorio 2.0) -**Prototypes:** `"simple-entity-with-force"` +**Draftsman Source:** [RailSupport class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/rail_support.py) + +**Prototypes:** `"dummy-rail-support"`, `"rail-support"` **Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| | `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | -| `variation` | Integer | 1 | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- -## Uncategorized Entities - -The following entities are available but not yet categorized. -They still have full documentation below. +### SimpleEntityWithForce -### InfinityContainer +**Description:** A generic entity associated with friends or foes. -**Description:** An entity used to create an infinite amount of any item. +**Draftsman Source:** [SimpleEntityWithForce class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/simple_entity_with_force.py) -**Prototypes:** `"infinity-chest"` +**Prototypes:** `"simple-entity-with-force"` **Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| -| `bar` | Integer | None | | -| `filters` | List (complex) ⚠️ | (factory) | | +| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | -| `remove_unfiltered_items` | Boolean (0/1) | false | `1` | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | +| `variation` | Integer | 1 | | --- -### LandMine +### SimpleEntityWithOwner -**Description:** An entity that explodes when in proximity to another force. +**Description:** A generic entity owned by some other entity. -**Prototypes:** `"land-mine"` +**Draftsman Source:** [SimpleEntityWithOwner class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/simple_entity_with_owner.py) + +**Prototypes:** `"simple-entity-with-owner"` **Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| +| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | +| `variation` | Integer | 1 | | --- -### PlayerPort +### StraightRail -**Description:** A constructable respawn point typically used in scenarios. +**Description:** A piece of rail track that moves in the 8 cardinal directions. + +**Draftsman Source:** [StraightRail class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/straight_rail.py) + +**Prototypes:** `"straight-rail"` **Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| +| `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- @@ -2591,20 +3260,22 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = **Description:** A pipe that transports fluids underneath other entities. +**Draftsman Source:** [UndergroundPipe class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/underground_pipe.py) + **Prototypes:** `"pipe-to-ground"` **Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| | `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- @@ -2612,34 +3283,21 @@ Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = **Description:** A pipe that may or may not admit fluid to pass through it based on some threshold. +**Draftsman Source:** [Valve class](https://github.com/redruin1/factorio-draftsman/blob/main/draftsman/prototypes/valve.py) + **Prototypes:** `"top-up-valve"`, `"overflow-valve"`, `"one-way-valve"` **Connection Type:** Single circuit connection #### Settable Properties -Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value` +Set at placement: `place("name", x, y, {prop: value})` | Property | Type | Default | Example | |----------|------|---------|---------| | `direction` | Integer ([Direction](#direction)) | 0 | `0 # NORTH` | | `item_requests` | List (complex) ⚠️ | (factory) | | | `name` | String (entity prototype name) | (factory) | | -| `quality` | String (normal/uncommon/rare/epic/legendary) | "normal" | | +| `quality` | One of: `"normal"`, `"uncommon"`, `"rare"`, `"epic"`, `"legendary"`, `"quality-unknown"` ([QualityID](#qualityid)) | "normal" | `"normal"` | --- - -## Notes on Complex Property Types - -Some properties marked with ⚠️ have complex types that may not be directly settable -in the current DSL syntax. These typically include: - -| Type | Description | Workaround | -|------|-------------|------------| -| List | Arrays of items/filters | May require special syntax | -| Condition | Circuit conditions | Use `.enable = signal > value` syntax | -| Vector | Position offsets | Use `{x: value, y: value}` | -| Complex | Other structured data | See draftsman documentation | - -For full details on complex types, refer to the -[Draftsman documentation](https://factorio-draftsman.readthedocs.io/en/latest/). diff --git a/doc/generate_entity_docs.py b/doc/generate_entity_docs.py index ca697c0..ceeb801 100644 --- a/doc/generate_entity_docs.py +++ b/doc/generate_entity_docs.py @@ -4,14 +4,18 @@ This script creates educational documentation that helps users understand: 1. What properties can be set on each entity (both at placement and dynamically) -2. How to set enum values (with the actual integer values) -3. What circuit input/output signals each entity supports +2. How to set enum/literal values (with all valid options) +3. What circuit outputs each entity can produce via `.output` 4. DSL syntax examples for each entity type All information is extracted dynamically from the Draftsman library. Usage: + # Generate fresh documentation python doc/generate_entity_docs.py -o doc/ENTITY_REFERENCE.md + + # Update existing documentation (targeted changes only) + python doc/generate_entity_docs.py --update doc/ENTITY_REFERENCE.md """ from __future__ import annotations @@ -23,7 +27,7 @@ from dataclasses import dataclass, field from datetime import datetime from enum import Enum, IntEnum -from typing import Any +from typing import Any, Literal, get_args, get_origin import attrs @@ -39,15 +43,25 @@ sys.exit(1) +# ============================================================================= +# Data Classes +# ============================================================================= + + @dataclass class EnumInfo: + """Information about an enum or Literal type.""" + name: str - members: dict[str, Any] + members: dict[str, Any] # member_name -> value description: str = "" + is_literal: bool = False # True for Literal[] types, False for IntEnum @dataclass class PropertyInfo: + """Information about an entity property.""" + name: str type_name: str # Original Python type dsl_type: str # DSL-friendly type description @@ -56,33 +70,36 @@ class PropertyInfo: description: str is_enum: bool = False enum_info: EnumInfo | None = None + literal_values: tuple | None = None # For Literal types example_value: str = "" - is_dsl_supported: bool = True # Whether this type is directly settable in DSL - is_signal_property: bool = False # Whether this is a signal I/O property + is_dsl_supported: bool = True + is_signal_property: bool = False # e.g. count_signal, output_signal @dataclass -class SignalIOEntry: - """Represents a single signal input or output.""" +class EntityOutputInfo: + """Information about entity circuit outputs via .output property.""" - property_name: str - direction: str # "input" or "output" - signal_type: str - description: str - enable_property: str | None = None + supports_output: bool = False + output_type: str = "Bundle" # What .output returns + output_signals: list[str] = field(default_factory=list) # What signals it can output + enable_properties: dict[str, str] = field(default_factory=dict) # prop -> description + description: str = "" @dataclass class CircuitIOInfo: + """Information about entity circuit connections.""" + has_circuit_connection: bool = True has_dual_connection: bool = False - signal_inputs: list[SignalIOEntry] = field(default_factory=list) - signal_outputs: list[SignalIOEntry] = field(default_factory=list) - content_outputs: list[str] = field(default_factory=list) # Generic outputs like "item contents" + output_info: EntityOutputInfo = field(default_factory=EntityOutputInfo) @dataclass class EntityInfo: + """Complete information about an entity class.""" + class_name: str cls: type description: str @@ -93,7 +110,13 @@ class EntityInfo: dsl_examples: list[str] = field(default_factory=list) -def get_all_enums() -> dict[str, EnumInfo]: +# ============================================================================= +# Enum and Literal Type Detection +# ============================================================================= + + +def get_all_int_enums() -> dict[str, EnumInfo]: + """Get all IntEnum types from draftsman.constants.""" enums = {} for name in dir(constants): if name.startswith("_"): @@ -111,24 +134,115 @@ def get_all_enums() -> dict[str, EnumInfo]: if hasattr(val, "__int__"): members[member_name] = int(val) else: - members[member_name] = str(val) # type: ignore[assignment] + members[member_name] = str(val) except (ValueError, TypeError): - members[member_name] = str(member.value) # type: ignore[assignment] - enums[name] = EnumInfo(name=name, members=members) + members[member_name] = str(member.value) + enums[name] = EnumInfo(name=name, members=members, is_literal=False) return enums -ALL_ENUMS = get_all_enums() +def get_literal_values_from_type(type_hint: Any) -> tuple | None: + """Extract values from a Literal type annotation.""" + origin = get_origin(type_hint) + if origin is Literal: + return get_args(type_hint) + # Handle Optional[Literal[...]] and similar + if origin is type(None) or str(origin) == "typing.Union": + for arg in get_args(type_hint): + if get_origin(arg) is Literal: + return get_args(arg) + return None + + +def get_all_literal_types() -> dict[str, EnumInfo]: + """Get all unique Literal types used in entity properties.""" + literal_types = {} + + # Well-known Literal types to name them nicely + KNOWN_LITERALS = { + ("normal", "uncommon", "rare", "epic", "legendary", "quality-unknown"): "QualityID", + ("*", "/", "+", "-", "%", "^", "<<", ">>", "AND", "OR", "XOR"): "ArithmeticOperation", + ( + "select", + "count", + "random", + "stack-size", + "rocket-capacity", + "quality-filter", + "quality-transfer", + ): "SelectorOperation", + ("at-least", "at-most", "exactly", "add", "remove"): "InfinityMode", + ("whitelist", "blacklist"): "FilterMode", + ("spoiled-first", "fresh-first"): "SpoilPriority", + ("input", "output"): "IOType", + ("local", "surface", "global"): "PlaybackMode", + ("left", "none", "right"): "SplitterPriority", + } + + for name in dir(entity_module): + obj = getattr(entity_module, name) + if not isinstance(obj, type) or not issubclass(obj, Entity) or obj is Entity: + continue + if not attrs.has(obj): + continue + + for fld in attrs.fields(obj): + if fld.type is None: + continue + literal_vals = get_literal_values_from_type(fld.type) + if literal_vals: + # Filter out None values + filtered_vals = tuple(v for v in literal_vals if v is not None) + if not filtered_vals: + continue + + # Look for a known name + known_name = KNOWN_LITERALS.get(filtered_vals) + if known_name and known_name not in literal_types: + members = {str(v): v for v in filtered_vals} + literal_types[known_name] = EnumInfo( + name=known_name, members=members, is_literal=True + ) + + return literal_types + + +ALL_INT_ENUMS = get_all_int_enums() +ALL_LITERAL_TYPES = get_all_literal_types() def find_enum_for_type(type_hint: Any) -> EnumInfo | None: + """Find an IntEnum that matches this type hint.""" type_str = str(type_hint) if type_hint else "" - for enum_name, enum_info in ALL_ENUMS.items(): + for enum_name, enum_info in ALL_INT_ENUMS.items(): if enum_name in type_str: return enum_info return None +def find_literal_for_type(type_hint: Any) -> tuple[tuple | None, EnumInfo | None]: + """Find Literal values and corresponding EnumInfo for a type.""" + literal_vals = get_literal_values_from_type(type_hint) + if not literal_vals: + return None, None + + filtered_vals = tuple(v for v in literal_vals if v is not None) + if not filtered_vals: + return None, None + + # Look for matching EnumInfo + for enum_info in ALL_LITERAL_TYPES.values(): + if set(enum_info.members.keys()) == {str(v) for v in filtered_vals}: + return filtered_vals, enum_info + + return filtered_vals, None + + +# ============================================================================= +# Skip lists and signal definitions +# ============================================================================= + + SKIP_PROPS = { "_parent", "_connections", @@ -141,206 +255,170 @@ def find_enum_for_type(type_hint: Any) -> EnumInfo | None: "mirror", } -# Detailed signal I/O definitions -# Format: property_name -> (direction, signal_type, description, enable_property) -# direction: "input" or "output" -# signal_type: what kind of signal value is read/written -# description: what this signal does -# enable_property: the boolean property that enables this (or more complex string) -SIGNAL_IO_DEFINITIONS = { - # === COMMON INPUT SIGNALS === - "circuit_condition": ( - "input", - "Any signal", - "Signal used in enable/disable condition", - "circuit_enabled: 1", - ), - "logistic_condition": ( - "input", - "Any signal", - "Logistic network signal for enable/disable", - "connect_to_logistic_network: 1", - ), - "stack_size_control_signal": ( - "input", - "Integer signal", - "Sets inserter stack size from signal value", - "circuit_set_stack_size: 1", - ), - # === LAMP SIGNALS === - # For COMPONENTS mode (color_mode: 1), use red_signal/green_signal/blue_signal - # For PACKED_RGB mode (color_mode: 2), use rgb_signal - "red_signal": ( - "input", - "Integer (0-255)", - "Red color component (COMPONENTS mode)", - "use_colors: 1, color_mode: 1", - ), - "green_signal": ( - "input", - "Integer (0-255)", - "Green color component (COMPONENTS mode)", - "use_colors: 1, color_mode: 1", - ), - "blue_signal": ( - "input", - "Integer (0-255)", - "Blue color component (COMPONENTS mode)", - "use_colors: 1, color_mode: 1", - ), - "rgb_signal": ( - "input", - "Packed RGB integer", - "Combined RGB value (PACKED_RGB mode)", - "use_colors: 1, color_mode: 2", - ), - # === RAIL SIGNAL OUTPUTS === - "red_output_signal": ( - "output", - "1 when red", - "Outputs 1 when rail signal shows red", - "read_signal: 1", - ), - "yellow_output_signal": ( - "output", - "1 when yellow", - "Outputs 1 when rail signal shows yellow", - "read_signal: 1", - ), - "green_output_signal": ( - "output", - "1 when green", - "Outputs 1 when rail signal shows green", - "read_signal: 1", - ), - "blue_output_signal": ("output", "1 when blue", "Outputs 1 when chain signal reserved", None), - # === TRAIN STOP SIGNALS === - "train_stopped_signal": ( - "output", - "Train ID", - "Outputs ID of stopped train", - "read_stopped_train: 1", - ), - "trains_limit_signal": ( - "input", - "Integer", - "Sets train limit from signal value", - "signal_limits_trains: 1", - ), - "trains_count_signal": ( - "output", - "Integer", - "Outputs count of trains en route", - "read_trains_count: 1", - ), - "priority_signal": ( - "input", - "Integer", - "Sets station priority from signal value", - "set_priority: 1", - ), - # === CRAFTING MACHINE SIGNALS === - "recipe_finished_signal": ( - "output", - "Pulse (1)", - "Pulses when recipe completes", - "read_recipe_finished: 1", - ), - "working_signal": ( - "output", - "1 when working", - "Outputs 1 while machine is crafting", - "read_working: 1", - ), - # === COMBINATOR SIGNALS === - "output_signal": ("output", "Result value", "Signal to output the combinator result on", None), - "index_signal": ("input", "Integer", "Signal for selector index input", None), - "count_signal": ("output", "Integer", "Signal to output the count result", None), - "quality_source_signal": ("input", "Any signal", "Signal to read quality from", None), - "quality_destination_signal": ( - "output", - "Quality level", - "Signal to output quality result", - None, - ), - # === ROBOPORT SIGNALS === - "available_logistic_robots_signal": ( - "output", - "Integer", - "Count of idle logistic robots", - "read_logistics: 1", - ), - "total_logistic_robots_signal": ( - "output", - "Integer", - "Total logistic robots in network", - "read_logistics: 1", - ), - "available_construction_robots_signal": ( - "output", - "Integer", - "Count of idle construction robots", - "read_logistics: 1", - ), - "total_construction_robots_signal": ( - "output", - "Integer", - "Total construction robots", - "read_logistics: 1", - ), - # === ACCUMULATOR SIGNALS === - # Note: accumulator uses "output_signal" but that's also used by combinators - # === SPEAKER SIGNALS === - "signal_value_is_pitch": ("input", "Pitch value", "Signal value controls note pitch", None), - # === MINING DRILL SIGNALS === - # read_resources outputs resource signals based on what's under the drill - # === ASTEROID COLLECTOR SIGNALS === - "status_signal": ("output", "Status code", "Current collector status", "read_status"), - "storage_signal": ("output", "Item contents", "Items stored in collector", "read_contents"), +# Properties that indicate signal I/O (usually for circuit configuration) +SIGNAL_IO_PROPERTIES = { + # Combinator signals + "output_signal", + "index_signal", + "count_signal", + "quality_source_signal", + "quality_destination_signal", + # Rail signal outputs + "red_output_signal", + "yellow_output_signal", + "green_output_signal", + "blue_output_signal", + # Train stop + "train_stopped_signal", + "trains_limit_signal", + "trains_count_signal", + "priority_signal", + # Recipe/working signals + "recipe_finished_signal", + "working_signal", + # Roboport + "available_logistic_robots_signal", + "total_logistic_robots_signal", + "available_construction_robots_signal", + "total_construction_robots_signal", + # Lamp RGB + "red_signal", + "green_signal", + "blue_signal", + "rgb_signal", + # Circuit condition + "circuit_condition", + "logistic_condition", + # Stack control + "stack_size_control_signal", + # Others + "status_signal", + "storage_signal", } -# Properties that enable signal reading/writing (bool properties) -SIGNAL_ENABLE_PROPERTIES = { - "read_contents": "Enables outputting contents to circuit network", - "read_hand_contents": "Enables outputting items in inserter hand", - "read_resources": "Enables outputting resource amounts under entity", - "read_signal": "Enables outputting rail signal state", - "read_from_train": "Enables reading train cargo contents", - "send_to_train": "Enables sending signals to train for schedule control", - "read_stopped_train": "Enables outputting stopped train ID", - "read_trains_count": "Enables outputting count of trains en route", - "read_recipe_finished": "Enables recipe finished pulse signal", - "read_working": "Enables outputting working status", - "read_logistics": "Enables outputting robot counts", - "read_robot_stats": "Enables outputting robot statistics", - "circuit_enabled": "Enables circuit condition control", - "connect_to_logistic_network": "Enables logistic network condition control", - "circuit_set_filters": "Enables setting filters from circuit signals", - "circuit_set_stack_size": "Enables setting stack size from signal", - "circuit_set_recipe": "Enables setting recipe from circuit signals", - "signal_limits_trains": "Enables setting train limit from signal", - "set_priority": "Enables setting station priority from signal", - "use_colors": "Enables color control from circuit signals", - "read_status": "Enables outputting entity status", - "read_ammo": "Enables outputting ammo count", +# Entity class name -> output description mapping +ENTITY_OUTPUT_DESCRIPTIONS = { + "Container": { + "supports_output": True, + "description": "Item contents of the container", + "enable_properties": {"read_contents": "Enable reading container contents"}, + }, + "StorageTank": { + "supports_output": True, + "description": "Fluid level in the tank", + "enable_properties": {}, # Always outputs + }, + "Inserter": { + "supports_output": True, + "description": "Items in hand or filter status", + "enable_properties": { + "read_hand_contents": "Read items in hand", + "circuit_set_filters": "Control via filters", + }, + }, + "MiningDrill": { + "supports_output": True, + "description": "Resources under the drill", + "enable_properties": {"read_resources": "Read resource amounts"}, + }, + "TransportBelt": { + "supports_output": True, + "description": "Items on the belt", + "enable_properties": {"read_contents": "Read belt contents"}, + }, + "ArithmeticCombinator": { + "supports_output": True, + "description": "Computed arithmetic result", + "enable_properties": {}, # Always outputs + }, + "DeciderCombinator": { + "supports_output": True, + "description": "Conditional output signals", + "enable_properties": {}, # Always outputs + }, + "SelectorCombinator": { + "supports_output": True, + "description": "Selected/filtered signals", + "enable_properties": {}, # Always outputs + }, + "ConstantCombinator": { + "supports_output": True, + "description": "Constant signal values", + "enable_properties": {}, # Always outputs + }, + "Accumulator": { + "supports_output": True, + "description": "Charge level percentage", + "enable_properties": {}, + }, + "Roboport": { + "supports_output": True, + "description": "Robot counts and logistics info", + "enable_properties": { + "read_logistics": "Read logistic robot counts", + "read_robot_stats": "Read robot statistics", + }, + }, + "TrainStop": { + "supports_output": True, + "description": "Train ID, count, and cargo", + "enable_properties": { + "read_stopped_train": "Read stopped train ID", + "read_trains_count": "Read incoming trains count", + "read_from_train": "Read train cargo", + }, + }, + "RailSignal": { + "supports_output": True, + "description": "Signal state (red/yellow/green)", + "enable_properties": {"read_signal": "Read signal state"}, + }, + "RailChainSignal": { + "supports_output": True, + "description": "Signal state (red/yellow/green/blue)", + "enable_properties": {"read_signal": "Read signal state"}, + }, + "Lamp": { + "supports_output": False, + "description": "", + "enable_properties": {}, + }, + "AssemblingMachine": { + "supports_output": True, + "description": "Recipe finished pulse, working status", + "enable_properties": { + "read_recipe_finished": "Pulse when recipe completes", + "read_working": "Output working status", + }, + }, } -# Set of property names that are signal I/O and should be excluded from settable properties -SIGNAL_PROPERTY_NAMES = set(SIGNAL_IO_DEFINITIONS.keys()) + +# ============================================================================= +# Type conversion helpers +# ============================================================================= -# DSL-friendly type mappings -# Map Python/Draftsman types to user-friendly DSL types -def get_dsl_type(type_str: str, prop_name: str) -> tuple[str, bool]: - """ - Convert a Python type string to a DSL-friendly type description. - Returns (dsl_type, is_supported) tuple. - """ +def get_dsl_type( + type_str: str, prop_name: str, literal_vals: tuple | None, enum_info: EnumInfo | None +) -> tuple[str, bool]: + """Convert Python type to DSL-friendly description. Returns (type_str, is_supported).""" type_lower = type_str.lower() + # Literal types - show possible values + if literal_vals: + if enum_info: + values_str = ", ".join(f'`"{v}"`' for v in literal_vals) + return ( + f"One of: {values_str} ([{enum_info.name}](#{enum_info.name.lower()}))", + True, + ) + values_str = ", ".join(f'`"{v}"`' for v in literal_vals) + return f"One of: {values_str}", True + # Boolean types if "bool" in type_lower: - return "Boolean (0/1)", True + return "Boolean (`0` or `1`)", True # String types if prop_name in ("station", "player_description", "text"): @@ -353,64 +431,46 @@ def get_dsl_type(type_str: str, prop_name: str) -> tuple[str, bool]: # Integer types if "int" in type_lower and "annotated" in type_lower: return "Integer", True - if prop_name in ("priority", "index_constant", "random_update_interval"): + if prop_name in ("priority", "index_constant", "random_update_interval", "bar"): return "Integer", True # Direction enum if "direction" in type_lower: - return "Integer (0-15, see Direction enum)", True + return "Integer (`0`-`15`, see [Direction](#direction))", True # Color type if "color" in type_lower and "mode" not in prop_name: - return "Color {r: 0-255, g: 0-255, b: 0-255}", True + return "Object `{r, g, b}` (0-255 each)", True - # Signal ID types + # Signal ID types - important for entity configuration if "signalid" in type_lower: - return "String (signal name)", True + return 'String (signal name, e.g. `"signal-A"`)', True # Quality if "quality" in type_lower and "literal" in type_lower: - return "String (normal/uncommon/rare/epic/legendary)", True - - # Literal string types (extract values) - if "literal[" in type_lower: - # Try to extract the literal values - import re - - match = re.search(r"literal\[([^\]]+)\]", type_str, re.IGNORECASE) - if match: - values = match.group(1).replace("'", "").replace('"', "") - if len(values) < 60: - return f"One of: {values}", True - return "String (see type for valid values)", True + return "String ([QualityID](#qualityid))", True # Enum types if " dict[str, str]: + """Extract property docstrings from class source code.""" docstrings = {} for klass in cls.__mro__: if klass in (object,) or not hasattr(klass, "__module__"): @@ -440,14 +500,21 @@ def get_docstrings_from_source(cls: type) -> dict[str, str]: return docstrings -def get_example_value(prop_name: str, prop_type: Any, enum_info: EnumInfo | None) -> str: +def get_example_value( + prop_name: str, prop_type: Any, enum_info: EnumInfo | None, literal_vals: tuple | None +) -> str: + """Generate an example value for a property.""" + if literal_vals and len(literal_vals) > 0: + return f'"{literal_vals[0]}"' if enum_info: first_member = next(iter(enum_info.members.items())) return f"{first_member[1]} # {first_member[0]}" + type_str = str(prop_type).lower() if prop_type else "" + if "bool" in type_str: return "1" - elif "signal" in prop_name.lower(): + elif "signal" in prop_name.lower() and "signalid" in type_str: return '"signal-A"' elif "color" in prop_name.lower() and "mode" not in prop_name.lower(): return "{r: 255, g: 0, b: 0}" @@ -457,18 +524,28 @@ def get_example_value(prop_name: str, prop_type: Any, enum_info: EnumInfo | None return '"Station Name"' elif prop_name == "recipe": return '"iron-gear-wheel"' + return "" +# ============================================================================= +# Entity information gathering +# ============================================================================= + + def get_entity_properties(cls: type) -> list[PropertyInfo]: + """Extract all settable properties from an entity class.""" properties: list[PropertyInfo] = [] if not attrs.has(cls): return properties + docstrings = get_docstrings_from_source(cls) + for fld in attrs.fields(cls): name = fld.name if name in SKIP_PROPS or name.startswith("_"): continue + type_str = str(fld.type) if fld.type else "Any" type_str = re.sub(r"typing\.", "", type_str) type_str = re.sub(r"draftsman\.\w+\.", "", type_str) @@ -476,32 +553,36 @@ def get_entity_properties(cls: type) -> list[PropertyInfo]: if len(type_str) > 80: type_str = type_str[:77] + "..." - # Get DSL-friendly type - dsl_type, is_supported = get_dsl_type(type_str, name) + # Check for enums and literals + enum_info = find_enum_for_type(fld.type) + literal_vals, literal_enum = find_literal_for_type(fld.type) - # Check if this is a signal property - is_signal_prop = name in SIGNAL_PROPERTY_NAMES + # Use literal enum if available + if literal_enum: + enum_info = literal_enum - enum_info = find_enum_for_type(fld.type) - is_enum = enum_info is not None + # Get DSL type + dsl_type, is_supported = get_dsl_type(type_str, name, literal_vals, enum_info) - # Override DSL type for enums - if is_enum and enum_info: + # Override for enums + if enum_info and not enum_info.is_literal: dsl_type = f"Integer ([{enum_info.name}](#{enum_info.name.lower()}))" is_supported = True + # Default value if fld.default is attrs.NOTHING: default = "required" - elif isinstance(fld.default, attrs.Factory): # type: ignore[arg-type] + elif isinstance(fld.default, attrs.Factory): default = "(factory)" elif fld.default is None: default = "None" elif isinstance(fld.default, bool): - default = "true" if fld.default else "false" + default = "1" if fld.default else "0" elif isinstance(fld.default, str): default = f'"{fld.default}"' else: default = str(fld.default) + prop = PropertyInfo( name=name, type_name=type_str, @@ -509,71 +590,51 @@ def get_entity_properties(cls: type) -> list[PropertyInfo]: python_type=fld.type, default_value=default, description=docstrings.get(name, ""), - is_enum=is_enum, + is_enum=enum_info is not None, enum_info=enum_info, - example_value=get_example_value(name, fld.type, enum_info), + literal_values=literal_vals, + example_value=get_example_value(name, fld.type, enum_info, literal_vals), is_dsl_supported=is_supported, - is_signal_property=is_signal_prop, + is_signal_property=name in SIGNAL_IO_PROPERTIES, ) properties.append(prop) - # Sort alphabetically for consistency + properties.sort(key=lambda p: p.name) return properties def get_circuit_io_info(cls: type) -> CircuitIOInfo: - """Extract circuit signal I/O information from an entity class.""" + """Extract circuit I/O information from an entity class.""" info = CircuitIOInfo() - mixin_names = [c.__name__ for c in cls.__mro__ if "Mixin" in c.__name__] # Check for dual connection (combinators) if cls.__name__ in ("ArithmeticCombinator", "DeciderCombinator", "SelectorCombinator"): info.has_dual_connection = True - # Get all property names from the entity - prop_names = set() - if attrs.has(cls): - for fld in attrs.fields(cls): - prop_names.add(fld.name) - - # Check for specific signal properties defined in SIGNAL_IO_DEFINITIONS - for prop_name, ( - direction, - signal_type, - description, - enable_prop, - ) in SIGNAL_IO_DEFINITIONS.items(): - if prop_name in prop_names: - entry = SignalIOEntry( - property_name=prop_name, - direction=direction, - signal_type=signal_type, - description=description, - enable_property=enable_prop, - ) - if direction == "input": - info.signal_inputs.append(entry) - else: - info.signal_outputs.append(entry) - - # Check for generic content output capabilities based on mixins - if "CircuitReadContentsMixin" in mixin_names and "read_contents" in prop_names: - info.content_outputs.append( - "Item contents (all items in entity, enable with `read_contents: 1`)" - ) - if "CircuitReadHandMixin" in mixin_names and "read_hand_contents" in prop_names: - info.content_outputs.append( - "Inserter hand contents (items being moved, enable with `read_hand_contents: 1`)" - ) - if "CircuitReadResourceMixin" in mixin_names and "read_resources" in prop_names: - info.content_outputs.append( - "Resource amounts (resources under entity, enable with `read_resources: 1`)" + # Get output info from class name + class_name = cls.__name__ + if class_name in ENTITY_OUTPUT_DESCRIPTIONS: + output_desc = ENTITY_OUTPUT_DESCRIPTIONS[class_name] + info.output_info = EntityOutputInfo( + supports_output=output_desc["supports_output"], + description=output_desc["description"], + enable_properties=output_desc.get("enable_properties", {}), ) + else: + # Check mixins for output capability + mixin_names = [c.__name__ for c in cls.__mro__ if "Mixin" in c.__name__] + if any("CircuitRead" in m or "CircuitConnect" in m for m in mixin_names): + info.output_info = EntityOutputInfo( + supports_output=True, + description="Circuit network signals", + enable_properties={}, + ) return info def get_all_entity_classes() -> dict[str, type]: + """Get all entity classes from draftsman.""" classes = {} for name in dir(entity_module): obj = getattr(entity_module, name) @@ -588,21 +649,23 @@ def get_all_entity_classes() -> dict[str, type]: def get_entity_prototypes(cls: type) -> list[str]: - """Get ALL prototypes for this entity class.""" + """Get all prototypes for an entity class.""" try: instance = cls() if hasattr(instance, "similar_entities"): - return list(instance.similar_entities) # No limit! + return list(instance.similar_entities) return [instance.name] if hasattr(instance, "name") and instance.name else [] except Exception: return [] def get_mixins(cls: type) -> list[str]: + """Get mixin class names.""" return [c.__name__ for c in cls.__mro__ if "Mixin" in c.__name__] def get_entity_description(cls: type) -> str: + """Get entity class description.""" doc = cls.__doc__ or "" lines = doc.strip().split("\n") result: list[str] = [] @@ -613,61 +676,92 @@ def get_entity_description(cls: type) -> str: if line: result.append(line) desc = " ".join(result) if result else "No description available." - # Clean up sphinx directives desc = re.sub(r"\.\.\s+\w+::\s*\S*", "", desc) return desc.strip() def get_dsl_examples(cls: type, class_name: str) -> list[str]: + """Get DSL code examples for an entity.""" examples = { "Lamp": [ "# Basic lamp controlled by circuit", 'Entity lamp = place("small-lamp", 0, 0);', "lamp.enable = signal > 0;", "", - "# RGB colored lamp", + "# RGB colored lamp (color_mode: 1 = COMPONENTS)", 'Entity rgb_lamp = place("small-lamp", 2, 0, {use_colors: 1, color_mode: 1});', - "rgb_lamp.r = red_signal;", - "rgb_lamp.g = green_signal;", - "rgb_lamp.b = blue_signal;", + "rgb_lamp.r = red_value;", + "rgb_lamp.g = green_value;", + "rgb_lamp.b = blue_value;", + ], + "SelectorCombinator": [ + "# Selector in count mode", + 'Entity counter = place("selector-combinator", 0, 0, {', + ' operation: "count",', + ' count_signal: "signal-C"', + "});", + "# Reading output", + "Bundle result = counter.output;", ], "Inserter": [ "# Inserter that enables when chest has items", 'Entity inserter = place("inserter", 0, 0, {direction: 4});', - "inserter.enable = chest_contents > 50;", - ], - "TransportBelt": [ - "# Belt that stops when storage is full", - 'Entity belt = place("transport-belt", 0, 0, {direction: 4});', - "belt.enable = storage_count < 1000;", - ], - "TrainStop": [ - "# Train station with circuit control", - 'Entity station = place("train-stop", 0, 0, {station: "Iron Pickup"});', - "station.enable = has_cargo > 0;", + "inserter.enable = chest.output > 50;", ], - "AssemblingMachine": [ - "# Assembler controlled by circuit", - 'Entity assembler = place("assembling-machine-1", 0, 0, {recipe: "iron-gear-wheel"});', - "assembler.enable = iron_count > 100;", + "Container": [ + "# Read chest contents", + 'Entity chest = place("iron-chest", 0, 0);', + "Bundle contents = chest.output;", + 'Signal iron = contents["iron-plate"];', ], "ArithmeticCombinator": [ - "# Note: Combinators are typically generated by the compiler", - "Signal result = input * 2 + offset; # Creates ArithmeticCombinator(s)", + "# Note: Arithmetic combinators are usually auto-generated", + "Signal result = input * 2 + offset; # Creates combinator(s)", + "", + "# Manual placement if needed", + 'Entity arith = place("arithmetic-combinator", 0, 0, {', + ' operation: "+"', + "});", ], "DeciderCombinator": [ - "# Note: Combinators are typically generated by the compiler", - "Signal flag = (count > 100) : 1; # Creates DeciderCombinator", + "# Note: Decider combinators are usually auto-generated", + "Signal flag = (count > 100) : 1; # Creates decider", ], "ConstantCombinator": [ - "# Note: Constant combinators are typically generated by the compiler", - "Signal constant = 42; # Creates ConstantCombinator", + "# Note: Constants are usually auto-generated", + "Signal constant = 42; # Creates constant combinator", + ], + "TrainStop": [ + "# Train station with circuit control", + 'Entity station = place("train-stop", 0, 0, {station: "Iron Pickup"});', + "station.enable = cargo.output > 0;", + "# Read train info", + "Bundle train_info = station.output;", ], } return examples.get(class_name, []) +def get_draftsman_source_link(cls: type) -> str: + """Generate a GitHub link to the draftsman source file for this entity.""" + try: + source_file = inspect.getfile(cls) + # Extract relative path from draftsman package + if "draftsman" in source_file: + # Get path after 'draftsman/' + parts = source_file.split("draftsman/") + if len(parts) > 1: + rel_path = "draftsman/" + parts[-1] + # Link to forked repo + github_url = f"https://github.com/redruin1/factorio-draftsman/blob/main/{rel_path}" + return github_url + except (TypeError, OSError): + pass + return "" + + def gather_entity_info(cls: type, class_name: str) -> EntityInfo: + """Gather all information about an entity class.""" return EntityInfo( class_name=class_name, cls=cls, @@ -680,17 +774,23 @@ def gather_entity_info(cls: type, class_name: str) -> EntityInfo: ) +# ============================================================================= +# Document generation +# ============================================================================= + + def generate_enum_reference() -> list[str]: + """Generate the enum reference section.""" lines = [ "## Enum Reference", "", - "When setting enum properties in the DSL, use the **integer value**.", - "This section lists all enums used by entity properties.", + "When setting enum properties in the DSL, use the **integer value** for IntEnums,", + "or the **string value** for Literal types.", "", ] - # List ALL enums that are actually used by entities - relevant_enums = [ + # IntEnums - only include relevant ones + relevant_int_enums = [ "LampColorMode", "Direction", "InserterModeOfOperation", @@ -700,17 +800,18 @@ def generate_enum_reference() -> list[str]: "LogisticModeOfOperation", "MiningDrillReadMode", "SiloReadMode", - "SpaceConnectionReadMode", - "AsteroidCollectorStatus", ] - for enum_name in relevant_enums: - if enum_name not in ALL_ENUMS: + lines.append("### Integer Enums") + lines.append("") + + for enum_name in relevant_int_enums: + if enum_name not in ALL_INT_ENUMS: continue - enum_info = ALL_ENUMS[enum_name] + enum_info = ALL_INT_ENUMS[enum_name] lines.extend( [ - f'### {enum_name}', + f'#### {enum_name}', "", "| DSL Value | Enum Name |", "|-----------|-----------|", @@ -719,80 +820,78 @@ def generate_enum_reference() -> list[str]: for member_name, value in enum_info.members.items(): lines.append(f"| `{value}` | {member_name} |") lines.append("") - return lines - -def format_type_with_enum_link(prop: PropertyInfo) -> str: - """Format type string with enum link if applicable.""" - if prop.is_enum and prop.enum_info: - return f"{prop.type_name} ([{prop.enum_info.name}](#{prop.enum_info.name.lower()}))" - return prop.type_name + # Literal types + lines.append("### String Enums (Literal Types)") + lines.append("") + lines.append("These properties accept string values. Use the exact string shown.") + lines.append("") + for enum_name, enum_info in sorted(ALL_LITERAL_TYPES.items()): + lines.extend( + [ + f'#### {enum_name}', + "", + "| Valid Values |", + "|-------------|", + ] + ) + for value in enum_info.members: + lines.append(f'| `"{value}"` |') + lines.append("") -# Track unsupported properties across all entities -UNSUPPORTED_PROPERTIES: set[tuple[str, str]] = set() # (property_name, dsl_type) + return lines def generate_entity_section(entity: EntityInfo) -> list[str]: + """Generate documentation section for a single entity.""" lines = [f"### {entity.class_name}", ""] # Description lines.append(f"**Description:** {entity.description}") lines.append("") - # ALL prototypes + # Draftsman source link + source_link = get_draftsman_source_link(entity.cls) + if source_link: + lines.append(f"**Draftsman Source:** [{entity.class_name} class]({source_link})") + lines.append("") + + # Prototypes if entity.prototypes: - proto_str = ", ".join(f'`"{p}"`' for p in entity.prototypes) + proto_str = ", ".join(f'`"{p}"`' for p in entity.prototypes[:10]) + if len(entity.prototypes) > 10: + proto_str += f", ... ({len(entity.prototypes)} total)" lines.extend([f"**Prototypes:** {proto_str}", ""]) - # Circuit connection type + # Circuit connection info io = entity.circuit_io if io.has_dual_connection: - lines.append( - "**Connection Type:** Dual circuit connection (separate input and output sides)" - ) + lines.append("**Connection Type:** Dual circuit (separate input/output sides)") else: lines.append("**Connection Type:** Single circuit connection") lines.append("") - # === Circuit Signal I/O Section === - has_signal_io = io.signal_inputs or io.signal_outputs or io.content_outputs - if has_signal_io: - lines.append("#### Circuit Signal I/O") + # === Entity Output Section === + output_info = io.output_info + if output_info.supports_output: + lines.append("#### Reading Entity Output") + lines.append("") + lines.append(f"Use `entity.output` to read: **{output_info.description}**") + lines.append("") + lines.append("```facto") + lines.append( + f'Entity e = place("{entity.prototypes[0] if entity.prototypes else entity.class_name.lower()}", 0, 0);' + ) + lines.append("Bundle signals = e.output; # Returns all output signals") + lines.append("```") lines.append("") - # Signal Inputs Table - if io.signal_inputs: - lines.append("**Signal Inputs:**") - lines.append("") - lines.append("| Signal Property | Signal Type | Description | Enable With |") - lines.append("|-----------------|-------------|-------------|-------------|") - for sig in io.signal_inputs: - enable = f"`{sig.enable_property}`" if sig.enable_property else "Always active" - lines.append( - f"| `{sig.property_name}` | {sig.signal_type} | {sig.description} | {enable} |" - ) - lines.append("") - - # Signal Outputs Table - if io.signal_outputs: - lines.append("**Signal Outputs:**") - lines.append("") - lines.append("| Signal Property | Signal Type | Description | Enable With |") - lines.append("|-----------------|-------------|-------------|-------------|") - for sig in io.signal_outputs: - enable = f"`{sig.enable_property}`" if sig.enable_property else "Always active" - lines.append( - f"| `{sig.property_name}` | {sig.signal_type} | {sig.description} | {enable} |" - ) - lines.append("") - - # Content outputs (generic item/resource outputs) - if io.content_outputs: - lines.append("**Content Outputs:**") + if output_info.enable_properties: + lines.append("**Enable properties:**") lines.append("") - for content in io.content_outputs: - lines.append(f"- {content}") + for prop, desc in output_info.enable_properties.items(): + lines.append(f"- `{prop}`: {desc}") lines.append("") # DSL Examples @@ -802,36 +901,44 @@ def generate_entity_section(entity: EntityInfo) -> list[str]: lines.extend(["```", ""]) # === Settable Properties Section === - # Filter out signal properties (they're in the Circuit Signal I/O section) settable_props = [p for p in entity.properties if not p.is_signal_property] if settable_props: lines.append("#### Settable Properties") lines.append("") - lines.append( - 'Set at placement: `place("name", x, y, {prop: value})` or after: `entity.prop = value`' - ) + lines.append('Set at placement: `place("name", x, y, {prop: value})`') lines.append("") lines.append("| Property | Type | Default | Example |") lines.append("|----------|------|---------|---------|") + for prop in settable_props: - # Use DSL type, not Python type type_str = prop.dsl_type - example = f"`{prop.example_value}`" if prop.example_value else "" - - # Track unsupported properties if not prop.is_dsl_supported: - UNSUPPORTED_PROPERTIES.add((prop.name, prop.dsl_type)) - type_str = f"{prop.dsl_type} ⚠️" # Mark as potentially unsupported - + type_str = f"{prop.dsl_type} ⚠️" + example = f"`{prop.example_value}`" if prop.example_value else "" lines.append(f"| `{prop.name}` | {type_str} | {prop.default_value} | {example} |") lines.append("") + # Signal configuration properties + signal_props = [p for p in entity.properties if p.is_signal_property] + if signal_props: + lines.append("#### Signal Configuration") + lines.append("") + lines.append("Properties for configuring which signals the entity uses:") + lines.append("") + lines.append("| Property | Type | Description |") + lines.append("|----------|------|-------------|") + for prop in signal_props: + lines.append( + f"| `{prop.name}` | {prop.dsl_type} | {prop.description[:60]}{'...' if len(prop.description) > 60 else ''} |" + ) + lines.append("") + lines.extend(["---", ""]) return lines -# Entity categorization - will add uncategorized entities to "Other" automatically +# Entity categorization ENTITY_CATEGORIES = { "Combinators": [ "ArithmeticCombinator", @@ -847,7 +954,6 @@ def generate_entity_section(entity: EntityInfo) -> list[str]: "Splitter", "Loader", "LinkedBelt", - "LaneLaneSplitter", ], "Train System": [ "TrainStop", @@ -857,18 +963,6 @@ def generate_entity_section(entity: EntityInfo) -> list[str]: "CargoWagon", "FluidWagon", "ArtilleryWagon", - "StraightRail", - "CurvedRailA", - "CurvedRailB", - "HalfDiagonalRail", - "RailRamp", - "RailSupport", - "ElevatedStraightRail", - "ElevatedCurvedRailA", - "ElevatedCurvedRailB", - "ElevatedHalfDiagonalRail", - "LegacyStraightRail", - "LegacyCurvedRail", ], "Production": [ "AssemblingMachine", @@ -879,13 +973,7 @@ def generate_entity_section(entity: EntityInfo) -> list[str]: "Beacon", "Boiler", "Generator", - "BurnerGenerator", "Reactor", - "FusionReactor", - "FusionGenerator", - "LightningAttractor", - "LightningRod", - "AgriculturalTower", ], "Storage": [ "Container", @@ -894,16 +982,14 @@ def generate_entity_section(entity: EntityInfo) -> list[str]: "LogisticStorageContainer", "LogisticRequestContainer", "LogisticBufferContainer", - "LinkedContainer", ], "Power": [ "ElectricPole", "PowerSwitch", "Accumulator", "SolarPanel", - "ElectricEnergyInterface", ], - "Fluids": ["Pump", "StorageTank", "OffshorePump", "Pipe", "PipeToGround", "InfinityPipe"], + "Fluids": ["Pump", "StorageTank", "OffshorePump", "Pipe", "PipeToGround"], "Combat": [ "Radar", "ArtilleryTurret", @@ -912,9 +998,9 @@ def generate_entity_section(entity: EntityInfo) -> list[str]: "FluidTurret", "Wall", "Gate", - "Landmine", + "LandMine", ], - "Robots & Logistics": ["Roboport", "ConstructionRobot", "LogisticRobot"], + "Robots & Logistics": ["Roboport"], "Space": ["SpacePlatformHub", "CargoLandingPad", "AsteroidCollector", "CargoBay", "Thruster"], "Misc": [ "ProgrammableSpeaker", @@ -922,13 +1008,12 @@ def generate_entity_section(entity: EntityInfo) -> list[str]: "SpiderVehicle", "HeatPipe", "HeatInterface", - "SimpleEntityWithOwner", - "SimpleEntityWithForce", ], } def generate_documentation() -> str: + """Generate complete documentation.""" lines = [ "# Entity Reference for Facto", "", @@ -936,19 +1021,19 @@ def generate_documentation() -> str: f"**Draftsman version:** {draftsman.__version__}", "", "This is the **complete reference** for all entities available in the DSL.", - "Each entity lists its prototypes, circuit I/O capabilities, and all settable properties.", "", "## Table of Contents", "", "- [Using Entities in the DSL](#using-entities-in-the-dsl)", + "- [Reading Entity Outputs](#reading-entity-outputs)", "- [Enum Reference](#enum-reference)", ] - # Add category links + # Category links for category in ENTITY_CATEGORIES: anchor = category.lower().replace(" ", "-").replace("&", "").replace(" ", "-") lines.append(f"- [{category}](#{anchor})") - lines.append("- [Uncategorized Entities](#uncategorized-entities)") + lines.append("- [Other Entities](#other-entities)") lines.append("") # Usage section @@ -956,6 +1041,18 @@ def generate_documentation() -> str: [ "## Using Entities in the DSL", "", + "### How the Compiler Handles Entities", + "", + "When you use `place()` in Facto, the compiler creates a corresponding", + "[Draftsman](https://github.com/Snagnar/factorio-draftsman) entity object.", + "Properties specified in the placement object (the `{...}` part) are passed directly", + "to Draftsman as Python attributes during entity construction. The compiler validates", + "that property names and types match what Draftsman expects for that entity class.", + "", + "Circuit-controlled properties (like `entity.enable = expression`) are handled differently:", + "the compiler generates the necessary combinator logic and wire connections to implement", + "the circuit behavior, then sets the appropriate control properties on the entity.", + "", "### Placement Syntax", "", "```facto", @@ -964,32 +1061,58 @@ def generate_documentation() -> str: "", "### Setting Properties", "", - "**At placement time** (in the property dictionary):", + "**At placement time:**", "```facto", 'Entity lamp = place("small-lamp", 0, 0, {use_colors: 1, color_mode: 1});', "```", "", - "**After placement** (for circuit-controlled values):", + "**After placement (circuit-controlled):**", "```facto", - "lamp.enable = signal > 0; # Control based on circuit signal", - "lamp.r = red_value; # Dynamic RGB control", + "lamp.enable = signal > 0;", + "lamp.r = red_value;", "```", "", - "### Enum Properties", + ] + ) + + # Reading Entity Outputs - NEW SECTION + lines.extend( + [ + "## Reading Entity Outputs", "", - "Enum properties accept **integer values**. See the [Enum Reference](#enum-reference) for all values.", + "Most entities can output circuit signals. Access them using `.output`:", "", "```facto", - 'Entity lamp = place("small-lamp", 0, 0, {color_mode: 1}); # 1 = COMPONENTS', - "```", + "# Read all signals from a container", + 'Entity chest = place("iron-chest", 0, 0, {read_contents: 1});', + "Bundle contents = chest.output;", "", - "### Boolean Properties", + "# Extract a specific signal", + 'Signal iron_count = contents["iron-plate"];', "", - "Boolean properties accept `1` (true) or `0` (false):", - "```facto", - 'Entity lamp = place("small-lamp", 0, 0, {use_colors: 1, always_on: 1});', + "# Use in calculations", + "Signal need_more = (iron_count < 100) : 1;", "```", "", + "### Output Types by Entity", + "", + "| Entity Type | What `.output` Returns | Enable Property |", + "|-------------|------------------------|-----------------|", + "| Combinators | Computed result signals | (always active) |", + "| Containers | Item counts | `read_contents: 1` |", + "| Storage Tanks | Fluid level | (always active) |", + "| Inserters | Hand contents | `read_hand_contents: 1` |", + "| Belts | Belt contents | `read_contents: 1` |", + "| Mining Drills | Resource amounts | `read_resources: 1` |", + "| Train Stops | Train ID, count | `read_stopped_train: 1`, etc. |", + "| Rail Signals | Signal state | `read_signal: 1` |", + "| Roboports | Robot counts | `read_logistics: 1` |", + "", + "### Note on Combinators", + "", + "For combinators (arithmetic, decider, selector), the `.output` reads from the", + "**output side** of the combinator, which is the result of its computation.", + "", ] ) @@ -998,13 +1121,10 @@ def generate_documentation() -> str: # Get all entity classes entity_classes = get_all_entity_classes() - - # Track which entities are categorized categorized = set() # Generate sections by category for category, class_names in ENTITY_CATEGORIES.items(): - # Only include category if it has any entities category_entities = [name for name in class_names if name in entity_classes] if not category_entities: continue @@ -1019,60 +1139,42 @@ def generate_documentation() -> str: entity = gather_entity_info(cls, class_name) lines.extend(generate_entity_section(entity)) - # Generate sections for ALL uncategorized entities (no truncation!) + # Uncategorized entities uncategorized = [name for name in sorted(entity_classes.keys()) if name not in categorized] if uncategorized: lines.extend( [ - "## Uncategorized Entities", + "## Other Entities", "", - "The following entities are available but not yet categorized.", - "They still have full documentation below.", + "Additional entities not in the main categories:", "", ] ) - for class_name in uncategorized: cls = entity_classes[class_name] entity = gather_entity_info(cls, class_name) lines.extend(generate_entity_section(entity)) - # Add notes section about unsupported/complex types - if UNSUPPORTED_PROPERTIES: - lines.extend( - [ - "## Notes on Complex Property Types", - "", - "Some properties marked with ⚠️ have complex types that may not be directly settable", - "in the current DSL syntax. These typically include:", - "", - "| Type | Description | Workaround |", - "|------|-------------|------------|", - "| List | Arrays of items/filters | May require special syntax |", - "| Condition | Circuit conditions | Use `.enable = signal > value` syntax |", - "| Vector | Position offsets | Use `{x: value, y: value}` |", - "| Complex | Other structured data | See draftsman documentation |", - "", - "For full details on complex types, refer to the ", - "[Draftsman documentation](https://factorio-draftsman.readthedocs.io/en/latest/).", - "", - ] - ) - return "\n".join(lines) def main(): - parser = argparse.ArgumentParser( - description="Generate comprehensive entity documentation for the Facto" - ) + parser = argparse.ArgumentParser(description="Generate entity documentation for the Facto") parser.add_argument("--output", "-o", help="Output file path (default: stdout)") + parser.add_argument("--update", help="Update existing file (targeted changes)") args = parser.parse_args() print("Gathering entity information...", file=sys.stderr) content = generate_documentation() - if args.output: + if args.update: + # TODO: Implement targeted update mode + # For now, just regenerate + print("Note: Full regeneration mode (targeted update not yet implemented)", file=sys.stderr) + with open(args.update, "w", encoding="utf-8") as f: + f.write(content) + print(f"Documentation written to: {args.update}", file=sys.stderr) + elif args.output: with open(args.output, "w", encoding="utf-8") as f: f.write(content) print(f"Documentation written to: {args.output}", file=sys.stderr) diff --git a/dsl_compiler/src/common/constants.py b/dsl_compiler/src/common/constants.py index e007d68..c3c0e7e 100644 --- a/dsl_compiler/src/common/constants.py +++ b/dsl_compiler/src/common/constants.py @@ -45,7 +45,7 @@ class CompilerConfig: """ # Layout Optimization - layout_solver_time_limit: int = 20 + layout_solver_time_limit: int = 30 max_layout_coordinate: int = 200 acceptable_layout_violations: int = 1 # Per-stage violation limits: (strict, relaxed_span, larger_area, both_relaxed, very_relaxed) diff --git a/dsl_compiler/src/common/entity_data.py b/dsl_compiler/src/common/entity_data.py index 7b3b0dd..5cc3d43 100644 --- a/dsl_compiler/src/common/entity_data.py +++ b/dsl_compiler/src/common/entity_data.py @@ -1,13 +1,36 @@ """Entity data extraction from draftsman.""" import math +from functools import lru_cache from draftsman.data import entities as entity_data +from draftsman.entity import new_entity class EntityDataHelper: """Helper to extract entity information from draftsman data.""" + @staticmethod + @lru_cache(maxsize=128) + def is_dual_circuit_connectable(prototype: str) -> bool: + """Check if an entity type has separate input/output circuit connectors. + + Entities like arithmetic-combinator, decider-combinator, and selector-combinator + have dual circuit connectors (input side and output side). When connecting wires + to these entities, you must specify which side (input/output) the wire connects to. + + Args: + prototype: Entity prototype name (e.g., "selector-combinator") + + Returns: + True if the entity has dual circuit connectors, False otherwise + """ + try: + entity = new_entity(prototype) + return getattr(entity, "dual_circuit_connectable", False) + except Exception: + return False + @staticmethod def get_footprint(prototype: str) -> tuple[int, int]: """Get entity footprint size dynamically from draftsman. @@ -54,3 +77,4 @@ def get_alignment(prototype: str) -> int: get_entity_footprint = EntityDataHelper.get_footprint get_entity_alignment = EntityDataHelper.get_alignment +is_dual_circuit_connectable = EntityDataHelper.is_dual_circuit_connectable diff --git a/dsl_compiler/src/ir/builder.py b/dsl_compiler/src/ir/builder.py index 21db367..a205dec 100644 --- a/dsl_compiler/src/ir/builder.py +++ b/dsl_compiler/src/ir/builder.py @@ -356,6 +356,58 @@ def bundle_arithmetic( self.add_operation(arith_op) return BundleRef(bundle.signal_types.copy(), node_id, source_ast=source_ast) + def bundle_decider( + self, + op: str, + bundle: BundleRef, + compare_value: ValueRef, + copy_count_from_input: bool = True, + output_value: int = 1, + source_ast: ASTNode | None = None, + ) -> BundleRef: + """Create a decider combinator using signal-each for bundle filtering. + + Compares each signal in the input bundle against compare_value. + Outputs only the signals that pass the comparison. + + Args: + op: Comparison operator (>, <, ==, !=, >=, <=) + bundle: Input bundle to filter + compare_value: Value to compare each signal against (scalar) + copy_count_from_input: If True, output matching signals with their counts. + If False, output constant value for each matching signal. + output_value: The constant to output when copy_count_from_input=False. + source_ast: Source AST for debugging + + Returns: + BundleRef pointing to the decider's output (filtered bundle) + """ + from dsl_compiler.src.ir.nodes import IRDecider + + node_id = self.next_id("bundle_decider") + + # Create decider with signal-each as both left operand and output + decider = IRDecider(node_id, "signal-each", source_ast) + decider.test_op = op + decider.left = SignalRef("signal-each", bundle.source_id) + decider.right = compare_value + decider.copy_count_from_input = copy_count_from_input + decider.output_value = output_value + + # If the compare_value is a signal (not a constant), we need wire separation + # to prevent the scalar signal from being processed by "each" + if isinstance(compare_value, SignalRef): + decider.debug_metadata["needs_wire_separation"] = True + decider.debug_metadata["scalar_signal_id"] = compare_value.source_id + + self.add_operation(decider) + + return BundleRef( + bundle.signal_types.copy(), + node_id, + source_ast=source_ast, + ) + def get_ir(self) -> list[IRNode]: """Return a copy of the currently built IR operations.""" diff --git a/dsl_compiler/src/layout/connection_planner.py b/dsl_compiler/src/layout/connection_planner.py index 1619d36..579b461 100644 --- a/dsl_compiler/src/layout/connection_planner.py +++ b/dsl_compiler/src/layout/connection_planner.py @@ -8,6 +8,7 @@ from dsl_compiler.src.common.constants import DEFAULT_CONFIG, CompilerConfig from dsl_compiler.src.common.diagnostics import ProgramDiagnostics +from dsl_compiler.src.common.entity_data import is_dual_circuit_connectable from dsl_compiler.src.ir.builder import BundleRef, SignalRef from .layout_plan import LayoutPlan, WireConnection @@ -1069,23 +1070,23 @@ def _log_unresolved_conflicts(self) -> None: def _get_connection_side(self, entity_id: str, is_source: bool) -> str | None: """Determine if entity needs 'input'/'output' side specified. + Entities with dual circuit connectors (like arithmetic-combinator, decider-combinator, + selector-combinator) have separate input and output sides. When wiring these entities, + we need to specify which side to connect to. + Args: entity_id: Entity to check is_source: True if this entity is producing the signal Returns: - 'output' for source combinators, 'input' for sink combinators, None otherwise + 'output' for source side of dual-connectable entities, + 'input' for sink side, None otherwise """ placement = self.layout_plan.get_placement(entity_id) if not placement: return None - combinator_types = { - "arithmetic-combinator", - "decider-combinator", - } - - if placement.entity_type in combinator_types: + if is_dual_circuit_connectable(placement.entity_type): return "output" if is_source else "input" return None diff --git a/dsl_compiler/src/layout/entity_placer.py b/dsl_compiler/src/layout/entity_placer.py index de5b788..c097ca3 100644 --- a/dsl_compiler/src/layout/entity_placer.py +++ b/dsl_compiler/src/layout/entity_placer.py @@ -538,6 +538,13 @@ def _place_entity_prop_write(self, op: IREntityPropWrite) -> None: "type": "signal", "signal_ref": op.value, } + elif isinstance(op.value, BundleRef): + # Bundle reference - register as signal sink to create wire connection + self._add_signal_sink(op.value, op.entity_id) + placement.properties["property_writes"][op.property_name] = { + "type": "bundle", + "bundle_ref": op.value, + } elif isinstance(op.value, int): placement.properties["property_writes"][op.property_name] = { "type": "constant", diff --git a/dsl_compiler/src/lowering/expression_lowerer.py b/dsl_compiler/src/lowering/expression_lowerer.py index 93363f3..b889e2c 100644 --- a/dsl_compiler/src/lowering/expression_lowerer.py +++ b/dsl_compiler/src/lowering/expression_lowerer.py @@ -642,16 +642,23 @@ def _fold_binary_constant(self, op: str, left: int, right: int, node: Expr) -> i """ return ConstantFolder.fold_binary_operation(op, left, right, node, self.diagnostics) - def lower_output_spec_expr(self, expr: OutputSpecExpr) -> SignalRef: + def lower_output_spec_expr(self, expr: OutputSpecExpr) -> ValueRef: """Lower output specifier expression to decider with copy-count-from-input. (condition) : output_value The condition must be a comparison or logical AND/OR of comparisons. When true, outputs the output_value instead of constant 1. + + Special case: Bundle filter pattern (bundle CMP scalar) : bundle + Uses signal-each decider to filter the bundle to only matching signals. """ condition = expr.condition + # Check for bundle filter pattern FIRST: (bundle CMP scalar) : output + if self._is_bundle_filter_pattern(expr): + return self._lower_bundle_filter_output_spec(expr) + # Check if this is a compound condition (AND/OR of comparisons) if isinstance(condition, BinaryOp) and condition.op in ("&&", "||", "and", "or"): return self._lower_compound_output_spec(expr) @@ -879,6 +886,79 @@ def _lower_compound_output_spec(self, expr: OutputSpecExpr) -> SignalRef: self._attach_expr_context(result.source_id, expr) return result + # ------------------------------------------------------------------------- + # Bundle filter output spec: (bundle CMP scalar) : bundle + # ------------------------------------------------------------------------- + + def _is_bundle_filter_pattern(self, expr: OutputSpecExpr) -> bool: + """Check if expression is a bundle filter pattern: (bundle CMP scalar) : output. + + Returns True if: + - Condition is a binary comparison (>, <, ==, etc.) + - Left operand of comparison is a Bundle + """ + condition = expr.condition + if not isinstance(condition, BinaryOp): + return False + if condition.op not in COMPARISON_OPS: + return False + left_type = self.semantic.get_expr_type(condition.left) + return isinstance(left_type, BundleValue) + + def _lower_bundle_filter_output_spec(self, expr: OutputSpecExpr) -> BundleRef: + """Lower (bundle CMP scalar) : output to signal-each decider. + + Creates a decider combinator that: + - Uses signal-each as the condition input + - Compares each signal against the scalar + - Outputs only matching signals + + If output is a bundle: copy_count_from_input=True (preserve values) + If output is an integer: copy_count_from_input=False (constant per signal) + """ + comparison = expr.condition + assert isinstance(comparison, BinaryOp) + + # Lower the bundle (left operand of comparison) + bundle_ref = self.lower_expr(comparison.left) + if not isinstance(bundle_ref, BundleRef): + self._error("Expected bundle in bundle filter condition", comparison.left) + return BundleRef(set(), "error") + + # Lower the scalar (right operand of comparison) + scalar_ref = self.lower_expr(comparison.right) + + # Determine output mode from the output value type + output_type = self.semantic.get_expr_type(expr.output_value) + + if isinstance(output_type, BundleValue): + # Output is a bundle: use copy_count_from_input mode + # This preserves the original signal values + result = self.ir_builder.bundle_decider( + op=comparison.op, + bundle=bundle_ref, + compare_value=scalar_ref, + copy_count_from_input=True, + source_ast=expr, + ) + else: + # Output is a constant: output that constant for each matching signal + output_const = ConstantFolder.extract_constant_int(expr.output_value, self.diagnostics) + if output_const is None: + output_const = 1 # Default to 1 if not a constant + + result = self.ir_builder.bundle_decider( + op=comparison.op, + bundle=bundle_ref, + compare_value=scalar_ref, + copy_count_from_input=False, + output_value=output_const, + source_ast=expr, + ) + + self._attach_expr_context(result.source_id, expr) + return result + def lower_unary_op(self, expr: UnaryOp) -> ValueRef: operand_type = self.semantic.get_expr_type(expr.expr) result_type = self.semantic.get_expr_type(expr) diff --git a/dsl_compiler/src/lowering/tests/test_bundles.py b/dsl_compiler/src/lowering/tests/test_bundles.py index c218d13..32fd8be 100644 --- a/dsl_compiler/src/lowering/tests/test_bundles.py +++ b/dsl_compiler/src/lowering/tests/test_bundles.py @@ -304,3 +304,162 @@ def test_bundle_all_comparison_creates_decider(self, parser, analyzer, diagnosti None, ) assert all_decider is not None + + +class TestBundleFilter: + """Test bundle filter pattern: (bundle > 0) : bundle. + + This pattern filters a bundle to include only signals that pass + the comparison, using a decider combinator with signal-each. + """ + + @pytest.fixture + def parser(self): + return DSLParser() + + @pytest.fixture + def diagnostics(self): + return ProgramDiagnostics() + + @pytest.fixture + def analyzer(self, diagnostics): + return SemanticAnalyzer(diagnostics) + + def test_bundle_filter_parses(self, parser): + """Bundle filter syntax should parse correctly.""" + code = """ + Bundle b = { ("signal-A", 10), ("signal-B", -5) }; + Bundle filtered = (b > 0) : b; + """ + program = parser.parse(code) + assert len(program.statements) == 2 + + def test_bundle_filter_type_inference(self, parser, diagnostics, analyzer): + """Bundle filter should return BundleValue type.""" + code = """ + Bundle b = { ("signal-A", 10), ("signal-B", -5) }; + Bundle filtered = (b > 0) : b; + """ + program = parser.parse(code) + analyzer.visit(program) + + assert not diagnostics.has_errors(), diagnostics.get_messages() + symbol = analyzer.symbol_table.lookup("filtered") + assert symbol is not None + assert isinstance(symbol.value_type, BundleValue) + + def test_bundle_filter_with_different_operators(self, parser, diagnostics, analyzer): + """Bundle filter should work with various comparison operators.""" + operators = [">", "<", ">=", "<=", "==", "!="] + for op in operators: + code = f""" + Bundle b = {{ ("signal-A", 10) }}; + Bundle result = (b {op} 5) : b; + """ + program = parser.parse(code) + diagnostics_local = ProgramDiagnostics() + analyzer_local = SemanticAnalyzer(diagnostics_local) + analyzer_local.visit(program) + + assert not diagnostics_local.has_errors(), ( + f"Failed for operator {op}: {diagnostics_local.get_messages()}" + ) + + def test_bundle_filter_creates_each_decider(self, parser, analyzer, diagnostics): + """Bundle filter should create decider with signal-each input and output.""" + code = """ + Bundle b = { ("signal-A", 10), ("signal-B", -5) }; + Bundle filtered = (b > 0) : b; + """ + program = parser.parse(code) + analyzer.visit(program) + ir_operations, lower_diags, _ = lower_program(program, analyzer) + + assert not lower_diags.has_errors(), lower_diags.get_messages() + + # Should have decider combinator for filtering + deciders = [op for op in ir_operations if isinstance(op, IRDecider)] + assert len(deciders) >= 1 + + # Find the bundle filter decider (uses signal-each as both input and output) + filter_decider = next( + ( + d + for d in deciders + if ( + isinstance(d.left, SignalRef) + and d.left.signal_type == "signal-each" + and d.output_type == "signal-each" + ) + ), + None, + ) + assert filter_decider is not None, "No decider with signal-each input and output found" + assert filter_decider.copy_count_from_input is True, ( + "Bundle filter should preserve input values" + ) + + def test_bundle_filter_with_constant_output(self, parser, analyzer, diagnostics): + """Bundle filter with constant output should set copy_count_from_input=False.""" + code = """ + Bundle b = { ("signal-A", 10), ("signal-B", -5) }; + Bundle counts = (b > 0) : 1; + """ + program = parser.parse(code) + analyzer.visit(program) + ir_operations, lower_diags, _ = lower_program(program, analyzer) + + assert not lower_diags.has_errors(), lower_diags.get_messages() + + # Find the decider + deciders = [op for op in ir_operations if isinstance(op, IRDecider)] + filter_decider = next( + (d for d in deciders if d.output_type == "signal-each"), + None, + ) + assert filter_decider is not None + assert filter_decider.copy_count_from_input is False, ( + "Constant output should not copy from input" + ) + + def test_bundle_filter_comparison_operator_preserved(self, parser, analyzer, diagnostics): + """Bundle filter should preserve the comparison operator.""" + code = """ + Bundle b = { ("signal-A", 10), ("signal-B", -5) }; + Bundle filtered = (b >= 0) : b; + """ + program = parser.parse(code) + analyzer.visit(program) + ir_operations, lower_diags, _ = lower_program(program, analyzer) + + assert not lower_diags.has_errors(), lower_diags.get_messages() + + deciders = [op for op in ir_operations if isinstance(op, IRDecider)] + filter_decider = next( + (d for d in deciders if d.output_type == "signal-each"), + None, + ) + assert filter_decider is not None + assert filter_decider.test_op == ">=", f"Expected >= but got {filter_decider.test_op}" + + def test_bundle_filter_with_comparison_value(self, parser, analyzer, diagnostics): + """Bundle filter should preserve comparison value.""" + code = """ + Bundle b = { ("signal-A", 10), ("signal-B", -5) }; + Bundle filtered = (b > 5) : b; + """ + program = parser.parse(code) + analyzer.visit(program) + ir_operations, lower_diags, _ = lower_program(program, analyzer) + + assert not lower_diags.has_errors(), lower_diags.get_messages() + + deciders = [op for op in ir_operations if isinstance(op, IRDecider)] + filter_decider = next( + (d for d in deciders if d.output_type == "signal-each"), + None, + ) + assert filter_decider is not None + assert filter_decider.right == 5, ( + f"Expected comparison value 5 but got {filter_decider.right}" + ) diff --git a/dsl_compiler/src/semantic/analyzer.py b/dsl_compiler/src/semantic/analyzer.py index 71745c8..e42d600 100644 --- a/dsl_compiler/src/semantic/analyzer.py +++ b/dsl_compiler/src/semantic/analyzer.py @@ -727,13 +727,16 @@ def infer_binary_op_type(self, expr: BinaryOp) -> ValueInfo: # Handle Bundle operations: Bundle OP Signal/int -> Bundle if isinstance(left_type, BundleValue): if expr.op in self.COMPARISON_OPS: - # Bundle comparisons should use any() or all() instead - self.diagnostics.error( - "Cannot compare Bundle directly. Use any(bundle) or all(bundle) for comparisons.", - stage="semantic", - node=expr, - ) - return SignalValue(signal_type=self.allocate_implicit_type()) + # Bundle comparison: (bundle > N) produces a signal-each comparison + # This must be used with output specifier to get filtered bundle: + # (bundle > N) : bundle + # Returns SignalValue (comparison result) to allow use in OutputSpecExpr + # Mark it as a bundle comparison for later detection + signal_type = self.allocate_implicit_type() + result = SignalValue(signal_type=signal_type, is_comparison_result=True) + # Store bundle comparison metadata on the expression for lowering + expr._bundle_comparison_source = left_type # type: ignore[attr-defined] + return result # Bundle arithmetic: result is a Bundle with same signal types if isinstance(right_type, (SignalValue, IntValue)): @@ -813,7 +816,16 @@ def _infer_output_spec_type(self, expr: OutputSpecExpr) -> ValueInfo: """Infer type for output specifier expression. The result type is determined by the output_value, not the condition. + + Special case: Bundle filter pattern (bundle > N) : bundle + - Condition is a bundle comparison + - Output is a bundle (typically the same bundle) + - Result is a filtered bundle """ + # Check for bundle filter pattern FIRST: (bundle CMP scalar) : bundle + if self._is_bundle_filter_pattern(expr): + return self._infer_bundle_filter_type(expr) + # Validate that condition is a comparison if not self._is_comparison_expr(expr.condition): # Generate a helpful error message @@ -851,6 +863,51 @@ def _infer_output_spec_type(self, expr: OutputSpecExpr) -> ValueInfo: return output_type + def _is_bundle_filter_pattern(self, expr: OutputSpecExpr) -> bool: + """Check if expression is a bundle filter pattern: (bundle CMP scalar) : output. + + Returns True if: + - Condition is a binary comparison (>, <, ==, etc.) + - Left operand of comparison is a Bundle + """ + if not isinstance(expr.condition, BinaryOp): + return False + if expr.condition.op not in self.COMPARISON_OPS: + return False + left_type = self.get_expr_type(expr.condition.left) + return isinstance(left_type, BundleValue) + + def _infer_bundle_filter_type(self, expr: OutputSpecExpr) -> ValueInfo: + """Infer type for bundle filter pattern: (bundle CMP scalar) : output. + + Returns: + - BundleValue if output is a bundle (filter with copy count) + - BundleValue if output is integer (filter with constant output per signal) + """ + condition = expr.condition + assert isinstance(condition, BinaryOp) + + # Get the source bundle type + source_bundle_type = self.get_expr_type(condition.left) + assert isinstance(source_bundle_type, BundleValue) + + # Analyze the comparison right operand (should be scalar) + right_type = self.get_expr_type(condition.right) + if isinstance(right_type, BundleValue): + self.diagnostics.error( + "Bundle filter comparison requires scalar (Signal or int) on right side.\n" + " Got: Bundle\n" + " Hint: Use a specific signal or constant: (bundle > 0) : bundle", + stage="semantic", + node=expr, + ) + + # Analyze the output value + self.get_expr_type(expr.output_value) + + # Result is always a bundle - either copy or constant output per matching signal + return BundleValue(signal_types=source_bundle_type.signal_types.copy()) + def _is_comparison_expr(self, expr: Expr) -> bool: """Check if expression is a valid decider condition. diff --git a/example_programs/04_binary_clock.facto b/example_programs/04_binary_clock.facto index 4631e16..ef30769 100644 --- a/example_programs/04_binary_clock.facto +++ b/example_programs/04_binary_clock.facto @@ -2,7 +2,7 @@ Memory counter; -int num_lamps = 16; +int num_lamps = 30; counter.write((counter.read() + 1) % (2 ** num_lamps)); for i in 0..num_lamps { diff --git a/example_programs/18_comprehensive_entities.facto b/example_programs/18_comprehensive_entities.facto index 3380930..2980d08 100644 --- a/example_programs/18_comprehensive_entities.facto +++ b/example_programs/18_comprehensive_entities.facto @@ -22,6 +22,15 @@ Signal quotient = counter_value / 3; Signal condition = input_signal > 10; Signal gate_output = condition * counter_value; +# Selector combinator (Factorio 2.0) - count unique signals +Bundle sample_items = { ("iron-plate", 100), ("copper-plate", 50), ("coal", 25) }; +Entity signal_counter = place("selector-combinator", 60, 0, { + operation: "count", + count_signal: "signal-C" +}); +signal_counter.input = sample_items; # Wire bundle to selector input +Signal unique_count = signal_counter.output["signal-C"]; # Read count output + # ============================================================================= # POWER INFRASTRUCTURE # ============================================================================= diff --git a/pyproject.toml b/pyproject.toml index e270097..e41a231 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ classifiers = [ dependencies = [ "lark>=1.3.0", - "factorio-draftsman>=3.2.0", + "factorio-draftsman @ git+https://github.com/Snagnar/factorio-draftsman.git@main", "click>=8.3.0", "numpy>=2.3.5", "ortools>=9.14.6206", From 0580c4b3c4ad91e7ad4483e50791f69e846d0aef Mon Sep 17 00:00:00 2001 From: "paul.mattes" Date: Sat, 31 Jan 2026 16:42:32 +0100 Subject: [PATCH 2/2] fixed unit tests --- doc/05_entities.md | 3 +- .../src/layout/integer_layout_solver.py | 46 +++++++++++++++---- dsl_compiler/src/semantic/analyzer.py | 30 ++++++++++++ 3 files changed, 70 insertions(+), 9 deletions(-) diff --git a/doc/05_entities.md b/doc/05_entities.md index 9df0d75..a718c1c 100644 --- a/doc/05_entities.md +++ b/doc/05_entities.md @@ -159,8 +159,9 @@ The `.input` property creates a wire connection from a signal source to the enti **Example with selector combinator:** ```facto # Count unique signals +Entity chest = place("steel-chest", 0, 0, {read_contents: 1}); Bundle items = chest.output; -Entity counter = place("selector-combinator", 0, 0, { +Entity counter = place("selector-combinator", 1, 0, { operation: "count", count_signal: "signal-C" }); diff --git a/dsl_compiler/src/layout/integer_layout_solver.py b/dsl_compiler/src/layout/integer_layout_solver.py index 304e824..f1718a2 100644 --- a/dsl_compiler/src/layout/integer_layout_solver.py +++ b/dsl_compiler/src/layout/integer_layout_solver.py @@ -787,6 +787,7 @@ def _add_edge_layout_constraints(self, model: cp_model.CpModel, positions: dict) - All inputs share Y = Y_input_line (a variable) - All outputs share Y = Y_output_line (a variable) - All intermediates have Y strictly between these lines + - Fixed-position entities also constrain intermediate placement - Optimizer minimizes bounding box, naturally placing: * Y_input_line at minimum feasible Y * Y_output_line at maximum feasible Y @@ -799,28 +800,47 @@ def _add_edge_layout_constraints(self, model: cp_model.CpModel, positions: dict) model: CP-SAT constraint model positions: Dict mapping entity_id to (x, y) variables """ - # Categorize entities (skip user-fixed entities) + # Categorize entities - track both fixed and non-fixed separately input_entities = [] output_entities = [] intermediate_entities = [] + fixed_input_y_values: list[int] = [] + fixed_output_y_values: list[int] = [] - for entity_id in self.entity_ids: - if entity_id in self.fixed_positions: - continue + def is_input_entity(p: EntityPlacement) -> bool: + """Check if placement is an input (via properties or role).""" + return p.properties.get("is_input") or p.role == "input" + def is_output_entity(p: EntityPlacement) -> bool: + """Check if placement is an output (via properties or role).""" + return p.properties.get("is_output") or p.role == "output" + + for entity_id in self.entity_ids: placement = self.entity_placements.get(entity_id) if not placement: continue - if placement.properties.get("is_input"): + if entity_id in self.fixed_positions: + # Track fixed positions for boundary constraints + fixed_y = self.fixed_positions[entity_id][1] + fixed_height = self.footprints.get(entity_id, (1, 1))[1] + if is_input_entity(placement): + fixed_input_y_values.append(fixed_y + fixed_height) # Bottom edge + elif is_output_entity(placement): + fixed_output_y_values.append(fixed_y + fixed_height) # Bottom edge + continue + + if is_input_entity(placement): input_entities.append(entity_id) - elif placement.properties.get("is_output"): + elif is_output_entity(placement): output_entities.append(entity_id) else: intermediate_entities.append(entity_id) - # If no inputs or outputs, no edge constraints needed - if not input_entities and not output_entities: + # If no inputs or outputs (fixed or non-fixed), no edge constraints needed + has_inputs = input_entities or fixed_input_y_values + has_outputs = output_entities or fixed_output_y_values + if not has_inputs and not has_outputs: self.diagnostics.info("No edge layout constraints (no inputs/outputs marked)") return @@ -880,6 +900,7 @@ def _add_edge_layout_constraints(self, model: cp_model.CpModel, positions: dict) model.Add(y == Y_output_line) # Constrain intermediates to be between input and output lines + # This includes both non-fixed input/output lines AND fixed positions for entity_id in intermediate_entities: _, y = positions[entity_id] height = self.footprints.get(entity_id, (1, 1))[1] @@ -890,6 +911,15 @@ def _add_edge_layout_constraints(self, model: cp_model.CpModel, positions: dict) if Y_output_line is not None: model.Add(y + height <= Y_output_line) + # Also constrain based on fixed-position outputs + # Intermediates must be below fixed outputs (higher Y values) + for fixed_output_bottom in fixed_output_y_values: + model.Add(y >= fixed_output_bottom) + self.diagnostics.info( + f"Edge layout: intermediate {entity_id} must have y >= {fixed_output_bottom} " + "(below fixed output)" + ) + def _create_objective( self, model: cp_model.CpModel, diff --git a/dsl_compiler/src/semantic/analyzer.py b/dsl_compiler/src/semantic/analyzer.py index e42d600..a0067cd 100644 --- a/dsl_compiler/src/semantic/analyzer.py +++ b/dsl_compiler/src/semantic/analyzer.py @@ -1022,6 +1022,19 @@ def visit_DeclStmt(self, node: DeclStmt) -> None: if simplified is not None: node.value = simplified + # Check for naked bundle comparisons - these must use any()/all() or filter syntax + if self._is_naked_bundle_comparison(node.value): + self.diagnostics.error( + f"Cannot assign bundle comparison directly to '{node.name}'.\n" + " Bundle comparisons must use one of:\n" + " - any(bundle) > N (true if any signal matches)\n" + " - all(bundle) > N (true if all signals match)\n" + " - (bundle > N) : bundle (filter syntax)", + stage="semantic", + node=node, + ) + return + value_type = self.get_expr_type(node.value) if node.type_name == "Signal" and isinstance(value_type, IntValue): @@ -1054,6 +1067,23 @@ def visit_DeclStmt(self, node: DeclStmt) -> None: self._register_signal_metadata(node.name, node, value_type, node.type_name) + def _is_naked_bundle_comparison(self, expr: Expr) -> bool: + """Check if expression is a bundle comparison not wrapped in filter syntax. + + A bundle comparison (bundle > N) is only valid when: + 1. Used with any()/all() functions for boolean results + 2. Used in filter syntax: (bundle > N) : bundle + + A "naked" bundle comparison is one used directly without these wrappers. + """ + if not isinstance(expr, BinaryOp): + return False + if expr.op not in self.COMPARISON_OPS: + return False + # Check if left operand is a bundle + left_type = self.get_expr_type(expr.left) + return isinstance(left_type, BundleValue) + def _type_name_to_symbol_type(self, type_name: str) -> SymbolType: """Convert type name to symbol type.""" if type_name == "Entity":