Skip to content

Commit

Permalink
version 1.0.3
Browse files Browse the repository at this point in the history
see changelog
  • Loading branch information
redruin1 committed Mar 13, 2023
1 parent 351ca51 commit 8ec3b00
Show file tree
Hide file tree
Showing 105 changed files with 1,675 additions and 309 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,11 @@ update(verbose=True, path="some/path") # equivalent to 'draftsman-update -v -p s
Both `mod-info.json` and `mod-settings.dat` are recognized by `draftsman-update`, so you can also just change the settings in either of those and the loading process will adjust as well.

## TODO
* Investigate `deal` and improve user experience with errors and warnings
* Add warnings for placement constraints on rails, rail signals and train stops
* Add constraints on `UpgradePlanner` and `DeconstructionPlanner`
* `Blueprint.schedules` convenience functions
* More doctests
* Write test cases for `dump_format`
* Add plaintext representations of Entity JSON objects for all entities in addition to blueprintables
* Update modding documentation guide to reflect 2.0 changes
* Reevaluate the diamond diagrams for inherited `Entity` subclass
Expand All @@ -153,4 +153,6 @@ Both `mod-info.json` and `mod-settings.dat` are recognized by `draftsman-update`
* RailPlanner (specify rail paths via turtle-like commands)
* Custom `data.raw` extraction and formatting?
* Maybe integrate defaults for more succinct blueprint strings?
* Unify entity validation into one monolithic thing
* Investigate more performant alternatives to `schema` (validir? requires cython, currently we're pure python)
* Look into Lua (or other language) bindings via backport to C/Cython
16 changes: 15 additions & 1 deletion changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# Changelog

## 1.0.3
* Updated `factorio-data` to version `1.1.76` (latest stable)
* Updated `compatibility/defines.lua` to `1.1.76` (latest stable)
* Merged penguincounter's pull request:
* Fixed logistics requester and buffer chest signatures not having the correct `circuit_mode_of_operation` key
* Added a `dump_format()` method to entities that outputs a user friendly description of all of the possible key/value entries in the exported blueprint dict
* Still needs to be done for Blueprintable; ideally there would be a `Exportable` parent class that would implement the madness
* Also need to investigate faster validation options since schema is pretty slow; maybe we can unify and improve speed at the same time
* Added a `get_format()` method intended to get a readable formatted string (that can easily be autogenerated at the top of each entity in the documentation!)
* Changed `_exports` dict to be both more user readable and only defined on a per class basis instead of a per instance basis (so memory usage should be down)
* Prepped `env.py` for when Lupa version 2.0 goes live (which will resolve #50)
* Fixed `"Mining_Drones_Harder"` mod not loading because of stray "__MACOSX" folder defined alongside (#55)
* Fixed `"FactorioExtended-Plus-Logistics"` not loading due to internal file titled `__init__.lua` (#56)
* Fixed `env.extract_entities().categorize_entities()` to `get` flags instead of assuming they exist (`"flags"` set is common but optional)

## 1.0.2
* Added `UpgradePlanner` and `DeconstructionPlanner` (#40)
* Created an abstract class `Blueprintable` which now implements `Blueprint`, `BlueprintBook`, `UpgradePlanner`, and `DeconstructionPlanner` to increase code reuse
Expand Down Expand Up @@ -44,7 +59,6 @@
* Split the `signatures.CONTROL_BEHAVIOR` into many sub implementations, which should improve both safety and (hopefully) performance
* Fixed #24, #25, #27, #32, #33, and #34


## 0.9.7
* Merged louga31's pull request
* Rewrite of the `_shift_key_indices` in `EntityList` to make it faster using list comprehension
Expand Down
4 changes: 2 additions & 2 deletions draftsman/_factorio_version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# _factorio_version.py

__factorio_version__ = "1.1.61.0"
__factorio_version_info__ = (1, 1, 61, 0)
__factorio_version__ = "1.1.76.0"
__factorio_version_info__ = (1, 1, 76, 0)
4 changes: 2 additions & 2 deletions draftsman/_version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# version.py

__version__ = "1.0.2"
__version_info__ = (1, 0, 2)
__version__ = "1.0.3"
__version_info__ = (1, 0, 3)
2 changes: 1 addition & 1 deletion draftsman/classes/blueprintbook.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
{
"blueprint_book": {
"item": "blueprint-book", # The associated item with this structure
"label": str, # A user given name for this deconstruction planner
"label": str, # A user given name for this blueprint book planner
"label_color": { # The overall color of the label
"r": float[0.0, 1.0] or int[0, 255], # red
"g": float[0.0, 1.0] or int[0, 255], # green
Expand Down
172 changes: 121 additions & 51 deletions draftsman/classes/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
from draftsman import utils

import copy
import json
from typing import Union, Callable
from schema import Schema
import six


Expand All @@ -28,6 +30,67 @@ class Entity(EntityLike):
implemented in :py:mod:`draftsman.prototypes`.
"""

# A dictionary containing all of the valid keys used in exported blueprint
# strings.
# Updated on a per Entity and Mixin basis; so ``Entity._exports`` will
# differ from ``ConstantCombinator._exports``.
# TODO: this needs to be some kind of recursive structure so that the data
# for things like "control_behavior" aren't displayed flat
_exports = {
"name": {
"format": "str",
"description": "Name of the entity",
"required": True,
},
"position": {
"format": "{'x': float, 'y': float}",
"description": "Position of the entity in the blueprint/world",
"required": True,
"transform": (lambda self, _: getattr(self, "global_position").to_dict()),
},
"tags": {
"format": "{...}",
"description": (
"Any custom data associated with the entity; used for modded data "
"primarily"
),
"required": lambda x: x, # Only optional if empty
},
}

@classmethod
def dump_format(cls):
# type: () -> dict
"""
Dumps a JSON-serializable representation of the Entity's ``exports``
dictionary, primarily for user interpretation.
:returns: A JSON dictionary containing the names of each valid key, a
short description of their purpose, and whether or not they're
considered optional.
"""
return {
name: {
"format": export["format"],
"description": export["description"],
"optional": False if export["required"] is True else True,
}
for name, export in cls._exports.items()
} # pragma: no coverage

@classmethod
def get_format(cls):
# type: () -> str
"""
Produces a pretty string representation of ``meth:dump_format``. Work in
progress.
:returns: A formatted string that can be output to stdout or file.
"""
return json.dumps(cls.dump_format(), indent=4) # pragma: no coverage

# =========================================================================

def __init__(self, name, similar_entities, tile_position=[0, 0], **kwargs):
# type: (str, list[str], Union[list, dict], **dict) -> None
"""
Expand All @@ -50,9 +113,7 @@ def __init__(self, name, similar_entities, tile_position=[0, 0], **kwargs):
"""
# Init EntityLike
super(Entity, self).__init__()
# Create a set of keywords that transfer in to_dict function
# Since some things we want to keep internal without sending to to_dict
self.exports = dict()

# For user convinience, keep track of all the unused arguments, and
# issue a warning if the user provided one that was not used.
self.unused_args = kwargs
Expand All @@ -68,7 +129,6 @@ def __init__(self, name, similar_entities, tile_position=[0, 0], **kwargs):
)
)
self._name = six.text_type(name)
self._add_export("name")

# Entity type
self._type = entities.raw[self.name]["type"]
Expand Down Expand Up @@ -131,16 +191,12 @@ def __init__(self, name, similar_entities, tile_position=[0, 0], **kwargs):
self.unused_args.pop("position")
else:
self.tile_position = tile_position
self._add_export(
"global_position", None, lambda k, v: ("position", v.to_dict())
)

# Entity tags
self.tags = {}
if "tags" in kwargs:
self.tags = kwargs["tags"]
self.unused_args.pop("tags")
self._add_export("tags", lambda x: x)

# Remove entity_number if we're importing from a dict
self.unused_args.pop("entity_number", None)
Expand Down Expand Up @@ -513,20 +569,34 @@ def to_dict(self):
:returns: The exported JSON-dict representation of the Entity.
"""
# Only add the keys in the exports dictionary
# out = {}
# for name, funcs in self.exports.items():
# value = getattr(self, name)
# criterion = funcs[0]
# formatter = funcs[1]
# # Does the value match the criteria to be included?
# if criterion is None or criterion(value):
# if formatter is not None:
# # Normalize key/value pair
# k, v = formatter(name, value)
# else:
# k, v = name, value
# out[k] = v

# return out

out = {}
for name, funcs in self.exports.items():
value = getattr(self, name)
criterion = funcs[0]
formatter = funcs[1]
# Does the value match the criteria to be included?
if criterion is None or criterion(value):
if formatter is not None:
# Normalize key/value pair
k, v = formatter(name, value)
else:
k, v = name, value
out[k] = v
for name, export in self.__class__._exports.items():
transform = export.get("transform", None)
if transform is not None:
value = transform(self, name)
else:
value = getattr(self, name)

required = export.get("required", None)
# print(required)
if required is True or required and required(value):
out[name] = value

return out

Expand Down Expand Up @@ -662,36 +732,36 @@ def merge_circuit_connection(self, side, color, point, other):
# (make sure to make a copy in case the original data gets deleted)
self.tags = copy.deepcopy(other.tags)

def _add_export(self, name, criterion=None, formatter=None):
# type: (str, Callable, Callable) -> None
"""
Adds a key to ``exports`` with an optional criteria and formatting
function.
We can't just convert the entire entity to a dict, because there are a
number of keys (for technical or space reasons) that we dont want to
add to the dictionary. Instead, we keep track of the keys we do want
(``exports``) and add those if they're present in the Entity object.
However, some items that are present in Entity might be initialized to
``None`` or otherwise redundant values, which would just take up space
in the output dict. Hence, we can also provide a criteria function that
takes a single argument, the value of the element in the `Entity`. If
the function returns ``True``, the key is added to the output dictionary.
If the function is ``None``, the key is always added.
This function also supports an optional ``formatter`` function that
takes two arguments, the ``key`` and ``value`` pair and returns a tuple
of the two in the same order. This allows to perform any modification to
the key or value before being added to the output dict.
:param name: The name of the attribute that you would like to keep.
:param criterion: Function that determines whether or not the attribute
should be added.
:param formatter: Function that determines the output format of the
key-value pair in the output dictionary.
"""
self.exports[name] = [criterion, formatter]
# def _add_export(self, name, criterion=None, formatter=None):
# # type: (str, Callable, Callable) -> None
# """
# Adds a key to ``exports`` with an optional criteria and formatting
# function.

# We can't just convert the entire entity to a dict, because there are a
# number of keys (for technical or space reasons) that we dont want to
# add to the dictionary. Instead, we keep track of the keys we do want
# (``exports``) and add those if they're present in the Entity object.

# However, some items that are present in Entity might be initialized to
# ``None`` or otherwise redundant values, which would just take up space
# in the output dict. Hence, we can also provide a criteria function that
# takes a single argument, the value of the element in the `Entity`. If
# the function returns ``True``, the key is added to the output dictionary.
# If the function is ``None``, the key is always added.

# This function also supports an optional ``formatter`` function that
# takes two arguments, the ``key`` and ``value`` pair and returns a tuple
# of the two in the same order. This allows to perform any modification to
# the key or value before being added to the output dict.

# :param name: The name of the attribute that you would like to keep.
# :param criterion: Function that determines whether or not the attribute
# should be added.
# :param formatter: Function that determines the output format of the
# key-value pair in the output dictionary.
# """
# self.exports[name] = [criterion, formatter]

def __repr__(self): # pragma: no coverage
# type: () -> str
Expand Down
2 changes: 2 additions & 0 deletions draftsman/classes/mixins/circuit_condition.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ class CircuitConditionMixin(object): # (ControlBehaviorMixin)
value of some signal exceeds some constant.
"""

_exports = {}

def set_circuit_condition(self, a=None, cmp="<", b=0):
# type: (str, str, Union[str, int]) -> None
"""
Expand Down
10 changes: 9 additions & 1 deletion draftsman/classes/mixins/circuit_connectable.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ class CircuitConnectableMixin(object):
Enables the Entity to be connected to circuit networks.
"""

_exports = {
"connections": {
"format": "TODO",
"description": "All circuit and copper wire connections",
"required": lambda x: len(x) != 0,
}
}

def __init__(self, name, similar_entities, **kwargs):
# type: (str, list[str], **dict) -> None
super(CircuitConnectableMixin, self).__init__(name, similar_entities, **kwargs)
Expand All @@ -39,7 +47,7 @@ def __init__(self, name, similar_entities, **kwargs):
if "connections" in kwargs:
self.connections = kwargs["connections"]
self.unused_args.pop("connections")
self._add_export("connections", lambda x: len(x) != 0)
# self._add_export("connections", lambda x: len(x) != 0)

# =========================================================================

Expand Down
2 changes: 2 additions & 0 deletions draftsman/classes/mixins/circuit_read_contents.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ class CircuitReadContentsMixin(object): # (ControlBehaviorMixin)
| :py:class:`~draftsman.classes.mixins.circuit_read_resource.CircuitReadResourceMixin`
"""

_exports = {}

@property
def read_contents(self):
# type: () -> bool
Expand Down
2 changes: 2 additions & 0 deletions draftsman/classes/mixins/circuit_read_hand.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ class CircuitReadHandMixin(object): # (ControlBehaviorMixin)
| :py:class:`~draftsman.classes.mixins.circuit_read_resource.CircuitReadResourceMixin`
"""

_exports = {}

@property
def read_hand_contents(self):
# type: () -> bool
Expand Down
2 changes: 2 additions & 0 deletions draftsman/classes/mixins/circuit_read_resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ class CircuitReadResourceMixin(object): # (ControlBehaviorMixin)
| :py:class:`~draftsman.classes.mixins.circuit_read_hand.CircuitReadHandMixin`
"""

_exports = {}

@property
def read_resources(self):
# type: () -> bool
Expand Down
10 changes: 9 additions & 1 deletion draftsman/classes/mixins/color.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ class ColorMixin(object):
Gives the entity an editable color.
"""

_exports = {
"color": {
"format": "{'r': r, 'g': g, 'b': b, 'a': a}",
"description": "The color property of the entity if colorable",
"required": lambda x: x is not None,
}
}

def __init__(self, name, similar_entities, **kwargs):
# type: (str, list[str], **dict) -> None
super(ColorMixin, self).__init__(name, similar_entities, **kwargs)
Expand All @@ -29,7 +37,7 @@ def __init__(self, name, similar_entities, **kwargs):
if "color" in kwargs:
self.color = kwargs["color"]
self.unused_args.pop("color")
self._add_export("color", lambda x: x is not None)
# self._add_export("color", lambda x: x is not None)

@property
def color(self):
Expand Down
Loading

0 comments on commit 8ec3b00

Please sign in to comment.