Exemple #1
0
 def pre_process(self):
     generic.print_warning(
         "Consider using the new produce() syntax for '{}'".format(
             self.name), self.name.pos)
     for i, param in enumerate(self.param_list):
         self.param_list[i] = action2var.reduce_varaction2_expr(param, 0x0A)
     produce_base_class.pre_process(self)
Exemple #2
0
    def make_actions(self):
        """
        Construct new actionF instances to store all pieces of this part, if needed.

        @return: Action F that should be defined before, and the processed part.
        @rtype: C{list} of L{ActionF}, L{TownNamesPart}
        """
        new_pieces = []
        for piece in self.pieces:
            piece.pre_process()
            if piece.probability.value == 0:
                generic.print_warning(
                    "Dropping town name piece with 0 probability.", piece.pos)
            else:
                new_pieces.append(piece)

        self.pieces = new_pieces

        actFs = self.move_pieces()
        if len(self.pieces) == 0:
            raise generic.ScriptError(
                "Expected names and/or town_name references in the part.",
                self.pos)
        if len(self.pieces) > 255:
            raise generic.ScriptError(
                "Too many values in a part, found {:d}, maximum is 255".format(
                    len(self.pieces)), self.pos)

        return actFs, self
Exemple #3
0
def read_lang_files(lang_dir, default_lang_file):
    """
    Read the language files containing the translations for string constants
    used in the NML specification.

    @param lang_dir: Name of the directory containing the language files.
    @type  lang_dir: C{str}

    @param default_lang_file: Filename of the language file that has the
                              default translation which will be used as
                              fallback for other languages.
    @type  default_lang_file: C{str}
    """
    global DEFAULT_LANGNAME

    DEFAULT_LANGNAME = default_lang_file
    if not os.path.exists(lang_dir + os.sep + default_lang_file):
        generic.print_warning(
            'Default language file "{}" doesn\'t exist'.format(os.path.join(lang_dir, default_lang_file))
        )
        return
    parse_file(lang_dir + os.sep + default_lang_file, True)
    for filename in glob.glob(lang_dir + os.sep + "*.lng"):
        if filename.endswith(default_lang_file):
            continue
        parse_file(filename, False)
    langs.sort()
Exemple #4
0
def validate_prop_info_list(prop_info_list, pos_list, feature):
    """
    Perform various checks on a list of properties in a property-block
        - make sure that properties that should appear first (substitute type) appear first

    @param prop_info_list: List of dictionaries with property information
    @type prop_info_list: C{list} of C{dict}

    @param pos_list: List containing position information (order matches prop_info_list)
    @type pos_list: C{list} of L{Position}

    @param feature: Feature of the associated item
    @type feature: C{int}
    """
    global properties
    first_warnings = [(info, pos_list[i])
                      for i, info in enumerate(prop_info_list)
                      if 'first' in info and i != 0]
    for info, pos in first_warnings:
        for prop_name, prop_info in list(properties[feature].items()):
            if info == prop_info or (isinstance(prop_info, list)
                                     and info in prop_info):
                generic.print_warning(
                    "Property '{}' should be set before all other properties and graphics."
                    .format(prop_name), pos)
                break
Exemple #5
0
    def optimise(self):
        if self.optimised:
            return self.optimised is not self

        # Constant condition, pick the right result
        if isinstance(self.expr, expression.ConstantNumeric):
            for r in self.body.ranges[:]:
                if r.min.value <= self.expr.value <= r.max.value:
                    self.optimised = r.result.value if r.result.value else self.expr
            if not self.optimised and self.body.default:
                self.optimised = self.body.default.value if self.body.default.value else self.expr

        # Default result only
        if not self.optimised and self.expr.is_read_only(
        ) and self.body.default and len(self.body.ranges) == 0:
            self.optimised = self.body.default.value

        # If we return an expression, just rewrite ourself to keep the correct scope
        # so a return action with the wrong scope doesn't need to be created later
        if (self.optimised
                and not isinstance(self.optimised, expression.ConstantNumeric)
                and not isinstance(self.optimised, expression.SpriteGroupRef)):
            self.expr = self.optimised
            self.body.ranges = []
            self.body.default = SwitchValue(None, True, self.optimised.pos)
            self.optimised = self

        if self.optimised:
            generic.print_warning(
                "Block '{}' returns a constant, optimising.".format(
                    self.name.value), self.pos)
            return self.optimised is not self

        self.optimised = self  # Prevent multiple run on the same non optimisable Switch
        return False
Exemple #6
0
    def pre_process(self):
        self.value = self.value.reduce(global_constants.const_list)

        self.param = self.param.reduce(global_constants.const_list,
                                       unknown_id_fatal=False)
        if isinstance(self.param, expression.SpecialParameter):
            if not self.param.can_assign():
                raise generic.ScriptError(
                    "Trying to assign a value to the read-only variable '{}'".
                    format(self.param.name), self.param.pos)
        elif isinstance(self.param, expression.Identifier):
            if global_constants.identifier_refcount[self.param.value] == 0:
                generic.print_warning(
                    generic.Warning.OPTIMISATION,
                    "Named parameter '{}' is not referenced, ignoring.".format(
                        self.param.value),
                    self.param.pos,
                )
                return
            num = action6.free_parameters.pop_unique(self.pos)
            global_constants.named_parameters[self.param.value] = num
        elif not isinstance(self.param, expression.Parameter):
            raise generic.ScriptError(
                "Left side of an assignment must be a parameter.",
                self.param.pos)
Exemple #7
0
    def t_line_directive2(self, t):
        r'\#\s+\d+\s+".*"\s*(\d+\s*)*\r?\n'
        # Format: # lineno filename flags
        # See: https://gcc.gnu.org/onlinedocs/cpp/Preprocessor-Output.html
        m = line_directive2_pat.match(t.value)
        assert m is not None
        line, fname, flags = m.groups()
        line = int(line, 10)
        flags = [int(f, 10) for f in flags.split(" ")
                 if f != ""] if flags is not None else []

        if 1 in flags:
            # File is being included, add current file/line to stack
            self.includes.append(self.lexer.lineno)
        elif 2 in flags:
            # End of include, new file should be equal to the one on top of the stack
            if self.includes and self.includes[-1].filename == fname:
                self.includes.pop()
            else:
                # But of course user input can never be trusted
                generic.print_warning(
                    "Information about included files is inconsistent, position information for errors may be wrong."
                )

        self.set_position(fname, line)
        self.increment_lines(t.value.count("\n") - 1)
Exemple #8
0
 def reduce_expressions(self, var_feature, extra_dicts=None):
     if extra_dicts is None:
         extra_dicts = []
     for r in self.ranges[:]:
         if r.min is r.max and isinstance(
                 r.min, expression.Identifier) and r.min.value == "default":
             if self.default is not None:
                 raise generic.ScriptError(
                     "Switch-block has more than one default, which is impossible.",
                     r.result.pos)
             self.default = r.result
             self.ranges.remove(r)
         else:
             r.reduce_expressions(var_feature, extra_dicts)
     if self.default is not None:
         if self.default.value is not None:
             self.default.value = action2var.reduce_varaction2_expr(
                 self.default.value, var_feature, extra_dicts)
         if len(self.ranges) != 0:
             if any(self.default.value != r.result.value
                    for r in self.ranges):
                 return
             generic.print_warning(
                 "Switch-Block ranges are the same as default, optimising.",
                 self.default.pos)
             self.ranges = []
Exemple #9
0
def read_extra_commands(custom_tags_file):
    """
    @param custom_tags_file: Filename of the custom tags file.
    @type  custom_tags_file: C{str}
    """
    if not os.access(custom_tags_file, os.R_OK):
        # Failed to open custom_tags.txt, ignore this
        return
    line_no = 0
    with open(generic.find_file(custom_tags_file), "r", encoding="utf-8") as fh:
        for line in fh:
            line_no += 1
            line = line.strip()
            if len(line) == 0 or line[0] == "#":
                continue

            i = line.find(":")
            if i == -1:
                raise generic.ScriptError("Line has no ':' delimiter.", generic.LinePosition(custom_tags_file, line_no))
            name = line[:i].strip()
            value = line[i + 1 :]
            if name in commands:
                generic.print_warning(
                    'Overwriting existing tag "' + name + '".', generic.LinePosition(custom_tags_file, line_no)
                )
            commands[name] = {"unicode": value}
            if is_ascii_string(value):
                commands[name]["ascii"] = value
Exemple #10
0
def parse_sprite_data(sprite_container):
    """
    @param sprite_container: AST node that contains the sprite data
    @type sprite_container: L{SpriteContainer}

    @return: List of real sprite actions
    @rtype: C{list} of L{BaseAction}
    """
    all_sprite_data = sprite_container.get_all_sprite_data()
    action_list = []
    first = True

    for sprite_data in all_sprite_data:
        sprite_list, default_file, default_mask_file, pos, zoom_level, bit_depth = sprite_data
        new_sprite_list = parse_sprite_list(sprite_list, default_file,
                                            default_mask_file, [pos])
        if not first and len(new_sprite_list) != len(action_list):
            msg = "Expected {:d} alternative sprites for {} '{}', got {:d}."
            msg = msg.format(len(action_list), sprite_container.block_type,
                             sprite_container.block_name.value,
                             len(new_sprite_list))
            raise generic.ScriptError(msg, sprite_container.pos)

        for i, sprite in enumerate(new_sprite_list):
            sprite.zoom_level = zoom_level
            sprite.bit_depth = bit_depth
            if (bit_depth == 8 and isinstance(sprite, RealSprite)
                    and (not sprite.is_empty)
                    and sprite.mask_file is not None):
                raise generic.ScriptError(
                    "Mask file may only be specified for 32bpp sprites.",
                    sprite.mask_file.pos)
            if first:
                if isinstance(sprite, RealSprite):
                    action_list.append(RealSpriteAction())
                else:
                    assert isinstance(sprite, RecolourSprite)
                    action_list.append(RecolourSpriteAction(sprite))
            else:
                # Not the first sprite, so an alternative sprite
                if isinstance(sprite, RecolourSprite) or isinstance(
                        action_list[i], RecolourSpriteAction):
                    raise generic.ScriptError(
                        "Alternative sprites may only be provided for and contain real sprites, not recolour sprites.",
                        sprite_container.pos,
                    )
                if action_list[i].sprite_list[
                        0].is_empty and not sprite.is_empty:
                    # if the first sprite is empty, all others are ignored
                    generic.print_warning(
                        "Alternative sprites for an empty real sprite are ignored.",
                        sprite_container.pos)
            if isinstance(sprite, RealSprite):
                action_list[i].add_sprite(sprite)
        first = False

    if len(action_list) != 0:
        action_list[-1].last = True
    return action_list
Exemple #11
0
 def __init__(self, num, pos=None, by_user=False):
     Expression.__init__(self, pos)
     self.num = num
     if by_user and isinstance(
             num, ConstantNumeric) and not (0 <= num.value <= 63):
         generic.print_warning(
             "Accessing parameters out of the range 0..63 is not supported and may lead to unexpected behaviour.",
             pos)
Exemple #12
0
def get_property_info_list(feature, name):
    """
    Find information on a single property, based on feature and name/number

    @param feature: Feature of the associated item
    @type feature: C{int}

    @param name: Name (or number) of the property
    @type name: L{Identifier} or L{ConstantNumeric}

    @return: A list of dictionaries with property information
    @rtype: C{list} of C{dict}
    """
    # Validate feature
    assert feature in range(0, len(properties))  # guaranteed by item
    if properties[feature] is None:
        raise generic.ScriptError(
            "Setting properties for feature '{}' is not possible, no properties are defined."
            .format(general.feature_name(feature)),
            name.pos,
        )

    if isinstance(name, expression.Identifier):
        prop_name = name.value
        if prop_name not in properties[feature]:
            raise generic.ScriptError("Unknown property name: " + prop_name,
                                      name.pos)
        prop_info_list = properties[feature][prop_name]
        if not isinstance(prop_info_list, list):
            prop_info_list = [prop_info_list]
    elif isinstance(name, expression.ConstantNumeric):
        for prop_info_list in properties[feature].values():
            if not isinstance(prop_info_list, list):
                prop_info_list = [prop_info_list]
            # Only non-compound properties may be set by number
            if len(prop_info_list) == 1 and "num" in prop_info_list[
                    0] and prop_info_list[0]["num"] == name.value:
                break
        else:
            raise generic.ScriptError("Unknown property number: " + str(name),
                                      name.pos)
    else:
        raise AssertionError()

    for prop_info in prop_info_list:
        if "replaced_by" in prop_info:
            generic.print_warning(
                generic.Warning.DEPRECATION,
                "'{}' is deprecated, consider using '{}' instead".format(
                    prop_name, prop_info["replaced_by"]),
                name.pos,
            )
        if "warning" in prop_info:
            generic.print_warning(generic.Warning.GENERIC,
                                  prop_info["warning"], name.pos)
    return prop_info_list
Exemple #13
0
 def optimise(self):
     if self.optimised:
         return True
     if len(self.choices) == 1:
         generic.print_warning(
             "Block '{}' returns a constant, optimising.".format(
                 self.name.value), self.pos)
         self.optimised = self.choices[0].result.value
         return True
     return False
Exemple #14
0
 def register_names(self):
     generic.OnlyOnce.enforce(self, "cargo table")
     for i, cargo in enumerate(self.cargo_list):
         if isinstance(cargo, expression.Identifier):
             self.cargo_list[i] = expression.StringLiteral(cargo.value, cargo.pos)
         expression.parse_string_to_dword(
             self.cargo_list[i]
         )  # we don't care about the result, only validate the input
         if self.cargo_list[i].value in global_constants.cargo_numbers:
             generic.print_warning("Duplicate entry in cargo table: {}".format(self.cargo_list[i].value), cargo.pos)
         else:
             global_constants.cargo_numbers[self.cargo_list[i].value] = i
Exemple #15
0
def param_desc_actions(root, params):
    num_params = 0
    for param_desc in params:
        num_params += len(param_desc.setting_list)
    root.subnodes.append(BinaryNode("NPAR", 1, num_params))
    param_root = BranchNode("PARA")
    param_num = 0
    setting_num = 0
    for param_desc in params:
        if param_desc.num is not None:
            param_num = param_desc.num.value
        for setting in param_desc.setting_list:
            setting_node = BranchNode(setting_num)
            if setting.name_string is not None:
                setting_node.subnodes.append(
                    TextNode("NAME", setting.name_string))
            if setting.desc_string is not None:
                setting_node.subnodes.append(
                    TextNode("DESC", setting.desc_string))
            if setting.type == "int":
                setting_node.subnodes.append(BinaryNode("MASK", 1, param_num))
                min_val = setting.min_val.uvalue if setting.min_val is not None else 0
                max_val = setting.max_val.uvalue if setting.max_val is not None else 0xFFFFFFFF
                def_val = setting.def_val.uvalue if setting.def_val is not None else 0
                if min_val > max_val or def_val < min_val or def_val > max_val:
                    generic.print_warning(
                        generic.Warning.GENERIC,
                        "Limits for GRF parameter {} are incoherent, ignoring."
                        .format(param_num),
                    )
                    min_val = 0
                    max_val = 0xFFFFFFFF
                setting_node.subnodes.append(LimitNode(min_val, max_val))
                if len(setting.val_names) > 0:
                    value_names_node = BranchNode("VALU")
                    for set_val_pair in setting.val_names:
                        value_names_node.subnodes.append(
                            TextNode(set_val_pair[0], set_val_pair[1]))
                    setting_node.subnodes.append(value_names_node)
            else:
                assert setting.type == "bool"
                setting_node.subnodes.append(BinaryNode("TYPE", 1, 1))
                bit = setting.bit_num.value if setting.bit_num is not None else 0
                setting_node.subnodes.append(SettingMaskNode(
                    param_num, bit, 1))
            if setting.def_val is not None:
                setting_node.subnodes.append(
                    BinaryNode("DFLT", 4, setting.def_val.value))
            param_root.subnodes.append(setting_node)
            setting_num += 1
        param_num += 1
    if len(param_root.subnodes) > 0:
        root.subnodes.append(param_root)
Exemple #16
0
        def prepare_act2_output(self):
            """
            Prepare this node for outputting.
            This sets the feature and makes sure it is correct.

            @return: True iff parsing of this node is needed
            @rtype: C{bool}
            """
            if not cls_is_referenced:
                return True
            if not self._prepared:
                self._prepared = True
                # copy, since we're going to modify
                ref_nodes = self._referencing_nodes.copy()
                for node in ref_nodes:
                    used = node.prepare_act2_output()
                    if not used:
                        node._remove_reference(self)

                # now determine the feature
                if self._has_explicit_feature():
                    # by this time, feature should be set
                    assert len(self.feature_set) == 1
                    for n in self._referencing_nodes:
                        if n.feature_set != self.feature_set:
                            msg = "Cannot refer to block '{}' with feature '{}', expected feature is '{}'"
                            msg = msg.format(
                                self.name.value,
                                general.feature_name(
                                    next(iter(self.feature_set))),
                                general.feature_name(
                                    n.feature_set.difference(
                                        self.feature_set).pop()),
                            )
                            raise generic.ScriptError(msg, n.pos)

                elif len(self._referencing_nodes) != 0:
                    for n in self._referencing_nodes:
                        # Add the features from all calling blocks to the set
                        self.feature_set.update(n.feature_set)

                if len(self._referencing_nodes) == 0 and (
                        not self.optimised or self.optimised is self):
                    # if we can be 'not used', there ought to be a way to refer to this block
                    assert self.name is not None
                    generic.print_warning(
                        generic.Warning.OPTIMISATION,
                        "Block '{}' is not referenced, ignoring.".format(
                            self.name.value),
                        self.pos,
                    )

            return len(self._referencing_nodes) != 0
Exemple #17
0
 def pre_process(self):
     generic.print_warning(
         generic.Warning.DEPRECATION,
         "Consider using the new produce() syntax for '{}'".format(
             self.name),
         self.name.pos,
     )
     var_scope = action2var.get_scope(0x0A)
     for i, param in enumerate(self.param_list):
         self.param_list[i] = action2var.reduce_varaction2_expr(
             param, var_scope)
     produce_base_class.pre_process(self)
Exemple #18
0
 def add_sprite_data(self, sprite_list, default_file, pos, zoom_level=0, bit_depth=8, default_mask_file=None):
     assert zoom_level in range(0, 6)
     assert bit_depth in (8, 32)
     key = (zoom_level, bit_depth)
     if key in self.sprite_data:
         msg = (
             "Sprites are already defined for {} '{}' for this zoom "
             + "level / bit depth combination. This data will be overridden."
         )
         msg = msg.format(self.block_type, self.block_name.value)
         generic.print_warning(msg, pos)
     self.sprite_data[key] = (sprite_list, default_file, default_mask_file, pos)
Exemple #19
0
def parse_var(name, info, pos):
    if 'replaced_by' in info:
        generic.print_warning(
            "'{}' is deprecated, consider using '{}' instead".format(
                name, info['replaced_by']), pos)
    param = expression.ConstantNumeric(
        info['param']) if 'param' in info else None
    res = expression.Variable(
        expression.ConstantNumeric(info['var']),
        expression.ConstantNumeric(info['start']),
        expression.ConstantNumeric((1 << info['size']) - 1), param, pos)
    if 'value_function' in info:
        return info['value_function'](res, info)
    return res
Exemple #20
0
def parse_var(name, info, pos):
    if "replaced_by" in info:
        generic.print_warning("'{}' is deprecated, consider using '{}' instead".format(name, info["replaced_by"]), pos)
    param = expression.ConstantNumeric(info["param"]) if "param" in info else None
    res = expression.Variable(
        expression.ConstantNumeric(info["var"]),
        expression.ConstantNumeric(info["start"]),
        expression.ConstantNumeric((1 << info["size"]) - 1),
        param,
        pos,
    )
    if "value_function" in info:
        return info["value_function"](res, info)
    return res
Exemple #21
0
def get_property_info_list(feature, name):
    """
    Find information on a single property, based on feature and name/number

    @param feature: Feature of the associated item
    @type feature: C{int}

    @param name: Name (or number) of the property
    @type name: L{Identifier} or L{ConstantNumeric}

    @return: A list of dictionaries with property information
    @rtype: C{list} of C{dict}
    """
    global properties

    #Validate feature
    assert feature in range(0, len(properties))  #guaranteed by item
    if properties[feature] is None:
        raise generic.ScriptError(
            "Setting properties for feature '{}' is not possible, no properties are defined."
            .format(general.feature_name(feature)), name.pos)

    if isinstance(name, expression.Identifier):
        if not name.value in properties[feature]:
            raise generic.ScriptError("Unknown property name: " + name.value,
                                      name.pos)
        prop_info_list = properties[feature][name.value]
        if not isinstance(prop_info_list, list):
            prop_info_list = [prop_info_list]
    elif isinstance(name, expression.ConstantNumeric):
        for p in properties[feature]:
            prop_info_list = properties[feature][p]
            if not isinstance(prop_info_list, list):
                prop_info_list = [prop_info_list]
            # Only non-compound properties may be set by number
            if len(prop_info_list) == 1 and 'num' in prop_info_list[
                    0] and prop_info_list[0]['num'] == name.value:
                break
        else:
            raise generic.ScriptError("Unknown property number: " + str(name),
                                      name.pos)
    else:
        assert False

    for prop_info in prop_info_list:
        if 'warning' in prop_info:
            generic.print_warning(prop_info['warning'], name.pos)
    return prop_info_list
Exemple #22
0
def parse_action5(replaces):
    real_sprite_list = real_sprite.parse_sprite_data(replaces)
    num_sprites = len(real_sprite_list)

    if replaces.type.value not in action5_table:
        raise generic.ScriptError(
            replaces.type.value + " is not a valid sprite replacement type",
            replaces.type.pos)
    type_id, num_required, block_type = action5_table[replaces.type.value]
    offset = None

    if block_type == Action5BlockType.FIXED:
        if num_sprites < num_required:
            msg = "Invalid sprite count for sprite replacement type '{}', expected {:d}, got {:d}"
            msg = msg.format(replaces.type, num_required, num_sprites)
            raise generic.ScriptError(msg, replaces.pos)

        elif num_sprites > num_required:
            msg = (
                "Too many sprites specified for sprite replacement type '{}',"
                " expected {:d}, got {:d}, extra sprites may be ignored"
            ).format(replaces.type, num_required, num_sprites)
            generic.print_warning(msg, replaces.pos)

        if replaces.offset != 0:
            msg = "replacenew parameter 'offset' must be zero for sprite replacement type '{}'".format(
                replaces.type)
            raise generic.ScriptError(msg, replaces.pos)

    elif block_type == Action5BlockType.ANY:
        if replaces.offset != 0:
            msg = "replacenew parameter 'offset' must be zero for sprite replacement type '{}'".format(
                replaces.type)
            raise generic.ScriptError(msg, replaces.pos)

    elif block_type == Action5BlockType.OFFSET:
        if num_sprites + replaces.offset > num_required:
            msg = "Exceeding the limit of {:d} sprites for sprite replacement type '{}', extra sprites may be ignored"
            msg = msg.format(num_required, replaces.type)
            generic.print_warning(msg, replaces.pos)

        if replaces.offset != 0 or num_sprites != num_required:
            offset = replaces.offset
    else:
        assert 0

    return [Action5(type_id, num_sprites, offset)] + real_sprite_list
Exemple #23
0
    def optimise(self):
        if self.optimised:
            return self.optimised is not self

        # Triggers have side-effects, and can't be skipped.
        # Scope for expressions can be different in referencing location, so don't optimise them.
        if (self.triggers.value == 0 and len(self.choices) == 1
                and (isinstance(self.choices[0].result.value,
                                expression.ConstantNumeric)
                     or isinstance(self.choices[0].result.value,
                                   expression.SpriteGroupRef))):
            generic.print_warning(
                "Block '{}' returns a constant, optimising.".format(
                    self.name.value), self.pos)
            self.optimised = self.choices[0].result.value
            return True

        self.optimised = self  # Prevent multiple run on the same non optimisable RandomSwitch
        return False
Exemple #24
0
 def preprocess_ternaryop(self, expr):
     assert isinstance(expr, expression.TernaryOp)
     guard = expression.Boolean(expr.guard).reduce()
     self.parse(guard)
     if isinstance(expr.expr1, expression.ConstantNumeric) and isinstance(
             expr.expr2, expression.ConstantNumeric):
         # This can be done more efficiently as (guard)*(expr1-expr2) + expr2
         self.var_list.append(nmlop.MUL)
         diff_var = VarAction2Var(0x1A, 0,
                                  expr.expr1.value - expr.expr2.value)
         diff_var.comment = "expr1 - expr2"
         self.var_list.append(diff_var)
         self.var_list.append(nmlop.ADD)
         # Add var sizes, +2 for the operators
         self.var_list_size += 2 + diff_var.get_size()
         return expr.expr2
     else:
         if not expr.is_read_only() and guard.is_read_only():
             generic.print_warning(
                 generic.Warning.GENERIC,
                 "Ternary operator may have unexpected side effects",
                 expr.pos)
         guard_var = VarAction2StoreTempVar()
         guard_var.comment = "guard"
         inverted_guard_var = VarAction2StoreTempVar()
         inverted_guard_var.comment = "!guard"
         self.var_list.append(nmlop.STO_TMP)
         self.var_list.append(guard_var)
         self.var_list.append(nmlop.XOR)
         var = VarAction2Var(0x1A, 0, 1)
         self.var_list.append(var)
         self.var_list.append(nmlop.STO_TMP)
         self.var_list.append(inverted_guard_var)
         self.var_list.append(nmlop.VAL2)
         # the +4 is for the 4 operators added above (STO_TMP, XOR, STO_TMP, VAL2)
         self.var_list_size += 4 + guard_var.get_size(
         ) + inverted_guard_var.get_size() + var.get_size()
         expr1 = nmlop.MUL(expr.expr1, VarAction2LoadTempVar(guard_var))
         expr2 = nmlop.MUL(expr.expr2,
                           VarAction2LoadTempVar(inverted_guard_var))
         return nmlop.ADD(expr1, expr2)
Exemple #25
0
 def optimise(self):
     if self.optimised:
         return True
     if isinstance(self.expr, expression.ConstantNumeric):
         for r in self.body.ranges[:]:
             if r.min.value <= self.expr.value <= r.max.value:
                 generic.print_warning(
                     "Block '{}' returns a constant, optimising.".format(
                         self.name.value), self.pos)
                 self.optimised = r.result.value
                 return True
     if (self.expr.is_read_only() and self.body.default is not None
             and self.body.default.value is not None
             and (len(self.body.ranges) == 0
                  or isinstance(self.expr, expression.ConstantNumeric))):
         generic.print_warning(
             "Block '{}' returns a constant, optimising.".format(
                 self.name.value), self.pos)
         self.optimised = self.body.default.value
         return True
     return False
Exemple #26
0
    def print_single_sprite(self, sprite_info):
        assert sprite_info.file is not None or sprite_info.mask_file is not None

        # Position for warning messages
        pos_warning = None
        if sprite_info.mask_file is not None:
            pos_warning = sprite_info.mask_file.pos
        elif sprite_info.file is not None:
            pos_warning = sprite_info.file.pos

        size_x, size_y, xoffset, yoffset, compressed_data, info_byte, crop_rect, warnings = self.encoder.get(
            sprite_info)

        for w in warnings:
            generic.print_warning(w, pos_warning)

        self.sprite_output.start_sprite(len(compressed_data) + 18)
        self.wsprite_header(size_x, size_y, len(compressed_data), xoffset,
                            yoffset, info_byte, sprite_info.zoom_level)
        self.sprite_output.print_data(compressed_data)
        self.sprite_output.end_sprite()
Exemple #27
0
def get_real_action2s(spritegroup, feature):
    loaded_list = []
    loading_list = []
    actions = []

    if feature not in action2.features_sprite_group:
        raise generic.ScriptError("Sprite groups that combine sprite sets are not supported for feature '{}'.".format(general.feature_name(feature)), spritegroup.pos)

    # First make sure that all referenced real sprites are put in a single action1
    spriteset_list = []
    for view in spritegroup.spriteview_list:
        spriteset_list.extend([action2.resolve_spritegroup(sg_ref.name) for sg_ref in view.spriteset_list])
    actions.extend(action1.add_to_action1(spriteset_list, feature, spritegroup.pos))

    view_names = sorted([view.name.value for view in spritegroup.spriteview_list])
    if feature in (0x00, 0x01, 0x02, 0x03):
        if view_names != sorted(['loading', 'loaded']):
            raise generic.ScriptError("Expected a 'loading' and a 'loaded' (list of) sprite set(s).", spritegroup.pos)
    elif feature in (0x05, 0x0B, 0x0D, 0x10):
        msg = "Sprite groups for feature {:02X} will not be supported in the future, as they are no longer needed. Directly refer to sprite sets instead."
        msg = msg.format(feature)
        generic.print_warning(msg, spritegroup.pos)
        if view_names != ['default']:
            raise generic.ScriptError("Expected only a 'default' (list of) sprite set(s).", spritegroup.pos)

    for view in spritegroup.spriteview_list:
        if len(view.spriteset_list) == 0:
            raise generic.ScriptError("Expected at least one sprite set, encountered 0.", view.pos)
        for set_ref in view.spriteset_list:
            spriteset = action2.resolve_spritegroup(set_ref.name)
            action1_index = action1.get_action1_index(spriteset)
            if view.name.value == 'loading': loading_list.append(action1_index)
            else: loaded_list.append(action1_index)

    actions.append(Action2Real(feature, spritegroup.name.value + " - feature {:02X}".format(feature), spritegroup.pos, loaded_list, loading_list))
    spritegroup.set_action2(actions[-1], feature)
    return actions
Exemple #28
0
def validate_prop_info_list(prop_info_list, pos_list, feature):
    """
    Perform various checks on a list of properties in a property-block
        - make sure that properties that should appear first (substitute type) appear first
        - make sure that required properties are set

    @param prop_info_list: List of dictionaries with property information
    @type prop_info_list: C{list} of C{dict}

    @param pos_list: List containing position information (order matches prop_info_list)
    @type pos_list: C{list} of L{Position}

    @param feature: Feature of the associated item
    @type feature: C{int}
    """
    first_warnings = [(info, pos_list[i])
                      for i, info in enumerate(prop_info_list)
                      if "first" in info and i != 0]
    for prop_name, prop_info in properties[feature].items():
        if not isinstance(prop_info, list):
            prop_info = [prop_info]
        for info, pos in first_warnings:
            if info in prop_info:
                generic.print_warning(
                    generic.Warning.GENERIC,
                    "Property '{}' should be set before all other properties and graphics."
                    .format(prop_name),
                    pos,
                )
                break
        for info in prop_info:
            if "required" in info and info not in prop_info_list:
                generic.print_error(
                    "Property '{}' is not set. Item will be invalid.".format(
                        prop_name),
                    pos_list[-1],
                )
Exemple #29
0
def parse_file(filename, default):
    """
    Read and parse a single language file.

    @param filename: The filename of the file to parse.
    @type  filename: C{str}

    @param default: True iff this is the default language.
    @type  default: C{bool}
    """
    lang = Language(False)
    try:
        with open(generic.find_file(filename), "r", encoding="utf-8") as fh:
            for idx, line in enumerate(fh):
                pos = generic.LinePosition(filename, idx + 1)
                line = line.rstrip("\n\r").lstrip("\uFEFF")
                # The default language is processed twice here. Once as fallback langauge
                # and once as normal language.
                if default:
                    default_lang.handle_string(line, pos)
                lang.handle_string(line, pos)
    except UnicodeDecodeError:
        pos = generic.LanguageFilePosition(filename)
        if default:
            raise generic.ScriptError("The default language file contains non-utf8 characters.", pos)
        generic.print_warning("Language file contains non-utf8 characters. Ignoring (part of) the contents.", pos)
    except generic.ScriptError as err:
        if default:
            raise
        generic.print_warning(err.value, err.pos)
    else:
        if lang.langid is None:
            generic.print_warning(
                "Language file does not contain a ##grflangid pragma", generic.LanguageFilePosition(filename)
            )
        else:
            for lng in langs:
                if lng[0] == lang.langid:
                    msg = "Language file has the same ##grflangid (with number {:d}) as another language file".format(
                        lang.langid
                    )
                    raise generic.ScriptError(msg, generic.LanguageFilePosition(filename))
            langs.append((lang.langid, lang))
Exemple #30
0
def parse_graphics_block_single_id(graphics_block, feature, id, is_livery_override, house_tile = None, house_north_tile_id = None):
    action6.free_parameters.save()
    prepend_action_list = []
    action_list = []
    act6 = action6.Action6()
    act3 = create_action3(feature, id, action_list, act6, is_livery_override)

    cargo_gfx = {}
    seen_callbacks = set()
    callbacks = []
    livery_override = None # Used for rotor graphics

    for graphics in graphics_block.graphics_list:
        cargo_id = graphics.cargo_id
        if isinstance(cargo_id, expression.Identifier):
            cb_name = cargo_id.value
            cb_table = action3_callbacks.callbacks[feature]
            if cb_name in cb_table:
                if cb_name in seen_callbacks:
                    raise generic.ScriptError("Callback '{}' is defined multiple times.".format(cb_name), cargo_id.pos)
                seen_callbacks.add(cb_name)

                info_list = cb_table[cb_name]
                if not isinstance(info_list, list):
                    info_list = [info_list]

                for info in info_list:
                    if 'deprecate_message' in info:
                        generic.print_warning(info['deprecate_message'], cargo_id.pos)
                    if house_tile is not None and 'tiles' in info and house_tile not in info['tiles']:
                        continue

                    if info['type'] == 'cargo':
                        # Not a callback, but an alias for a certain cargo type
                        if info['num'] in cargo_gfx:
                            raise generic.ScriptError("Graphics for '{}' are defined multiple times.".format(cb_name), cargo_id.pos)
                        cargo_gfx[info['num']] = graphics.result.value
                    elif info['type'] == 'cb':
                        callbacks.append( (info, graphics.result.value) )
                    elif info['type'] == 'override':
                        assert livery_override is None
                        livery_override = graphics.result.value
                    else:
                        assert False
                continue

        # Not a callback, so it must be a 'normal' cargo (vehicles/stations only)
        cargo_id = cargo_id.reduce_constant(global_constants.const_list)
        # Raise the error only now, to let the 'unknown identifier' take precedence
        if feature >= 5: raise generic.ScriptError("Associating graphics with a specific cargo is possible only for vehicles and stations.", cargo_id.pos)
        if cargo_id.value in cargo_gfx:
            raise generic.ScriptError("Graphics for cargo {:d} are defined multiple times.".format(cargo_id.value), cargo_id.pos)
        cargo_gfx[cargo_id.value] = graphics.result.value

    if graphics_block.default_graphics is not None:
        if 'default' not in action3_callbacks.callbacks[feature]:
            raise generic.ScriptError("Default graphics may not be defined for this feature (0x{:02X}).".format(feature), graphics_block.default_graphics.pos)
        if None in cargo_gfx:
            raise generic.ScriptError("Default graphics are defined twice.", graphics_block.default_graphics.pos)
        cargo_gfx[None] = graphics_block.default_graphics.value

    # An in-between varaction2 is always needed for houses
    if len(callbacks) != 0 or feature == 0x07:
        cb_flags = 0
        # Determine the default value
        if None not in cargo_gfx:
            cargo_gfx[None] = expression.SpriteGroupRef(expression.Identifier('CB_FAILED', None), [], None)
        default_val = cargo_gfx[None]

        cb_mapping = {}
        cb_buy_mapping = {}
        # Special case for vehicle cb 36, maps var10 values to spritegroups
        cb36_mapping = {}
        cb36_buy_mapping = {}
        # Sspecial case for industry production CB, maps var18 values to spritegroups
        prod_cb_mapping = {}

        for cb_info, gfx in callbacks:
            if 'flag_bit' in cb_info:
                # Set a bit in the CB flags property
                cb_flags |= 1 << cb_info['flag_bit']

            value_function = cb_info.get('value_function', None)
            mapping_val = (gfx, value_function)

            # See action3_callbacks for info on possible values
            purchase = cb_info.get('purchase', 0)
            if isinstance(purchase, str):
                # Not in purchase list, if separate purchase CB is set
                purchase = 0 if purchase in seen_callbacks else 1
            # Explicit purchase CBs will need a purchase cargo, even if not needed for graphics
            if purchase == 2 and 0xFF not in cargo_gfx:
                cargo_gfx[0xFF] = default_val

            num = cb_info['num']
            if num == 0x36:
                if purchase != 2: cb36_mapping[cb_info['var10']] = mapping_val
                if purchase != 0: cb36_buy_mapping[cb_info['var10']] = mapping_val
            elif feature == 0x0A and num == 0x00:
                # Industry production CB
                assert purchase == 0
                prod_cb_mapping[cb_info['var18']] = mapping_val
            else:
                if purchase != 2: cb_mapping[num] = mapping_val
                if purchase != 0: cb_buy_mapping[num] = mapping_val

        if cb_flags != 0:
            prepend_action_list.extend(action0.get_callback_flags_actions(feature, id, cb_flags))

        # Handle CB 36
        if len(cb36_mapping) != 0:
            expr = expression.Variable(expression.ConstantNumeric(0x10), mask = expression.ConstantNumeric(0xFF))
            actions, cb36_ref = create_cb_choice_varaction2(feature, expr, cb36_mapping, default_val, graphics_block.pos)
            prepend_action_list.extend(actions)
            cb_mapping[0x36] = (cb36_ref, None)
        if len(cb36_buy_mapping) != 0:
            expr = expression.Variable(expression.ConstantNumeric(0x10), mask = expression.ConstantNumeric(0xFF))
            actions, cb36_ref = create_cb_choice_varaction2(feature, expr, cb36_buy_mapping, default_val, graphics_block.pos)
            prepend_action_list.extend(actions)
            cb_buy_mapping[0x36] = (cb36_ref, None)
        if len(prod_cb_mapping) != 0:
            expr = expression.Variable(expression.ConstantNumeric(0x18), mask = expression.ConstantNumeric(0xFF))
            actions, cb_ref = create_cb_choice_varaction2(feature, expr, prod_cb_mapping, default_val, graphics_block.pos)
            prepend_action_list.extend(actions)
            cb_mapping[0x00] = (cb_ref, None)

        for cargo in sorted(cargo_gfx, key=lambda x: -1 if x is None else x):
            mapping = cb_buy_mapping if cargo == 0xFF else cb_mapping
            if len(mapping) == 0 and feature != 0x07:
                # No callbacks here, so move along
                # Except for houses, where we need to store some stuff in a register
                continue
            if cargo_gfx[cargo] != default_val:
                # There are cargo-specific graphics, be sure to handle those
                # Unhandled callbacks should chain to the default, though
                mapping = mapping.copy()
                mapping[0x00] = (cargo_gfx[cargo], None)

            expr = expression.Variable(expression.ConstantNumeric(0x0C), mask = expression.ConstantNumeric(0xFFFF))
            if feature == 0x07:
                # Store relative x/y, item id (of the north tile) and house tile (HOUSE_TILE_XX constant) in register FF
                # Format: 0xIIHHYYXX: II: item ID, HH: house tile, YY: relative y, XX: relative x
                lowbytes_dict = {
                    'n' : 0x000000,
                    'e' : 0x010100,
                    'w' : 0x020001,
                    's' : 0x030101,
                }
                lowbytes = expression.ConstantNumeric(lowbytes_dict[house_tile])
                highbyte = expression.BinOp(nmlop.SHIFT_LEFT, house_north_tile_id, expression.ConstantNumeric(24))
                register_FF = expression.BinOp(nmlop.OR, lowbytes, highbyte, lowbytes.pos).reduce()
                register_FF = expression.BinOp(nmlop.STO_TMP, register_FF, expression.ConstantNumeric(0xFF))
                expr = expression.BinOp(nmlop.VAL2, register_FF, expr, register_FF.pos)

                if len(mapping) == 0:
                    # mapping must not be empty
                    mapping[0x00] = (default_val, None)

            actions, cb_ref = create_cb_choice_varaction2(feature, expr, mapping, default_val, graphics_block.pos)
            prepend_action_list.extend(actions)
            cargo_gfx[cargo] = cb_ref

    # Make sure to sort to make the order well-defined
    offset = 7 if feature <= 3 else 5
    for cargo_id in sorted(cg for cg in cargo_gfx if cg is not None):
        result, comment = action2var.parse_result(cargo_gfx[cargo_id], action_list, act6, offset + 1, act3, None, 0x89)
        act3.cid_mappings.append( (cargo_id, result, comment) )
        offset += 3

    if None in cargo_gfx:
        result, comment = action2var.parse_result(cargo_gfx[None], action_list, act6, offset, act3, None, 0x89)
        act3.def_cid = result
        act3.default_comment = comment
    else:
        act3.def_cid = None
        act3.default_comment = ''

    if livery_override is not None:
        act6livery = action6.Action6()
        # Add any extra actions before the main action3 (TTDP requirement)
        act3livery = create_action3(feature, id, action_list, act6livery, True)
        offset = 7 if feature <= 3 else 5
        result, comment = action2var.parse_result(livery_override, action_list, act6livery, offset, act3livery, None, 0x89)
        act3livery.def_cid = result
        act3livery.default_comment = comment

    if len(act6.modifications) > 0: action_list.append(act6)
    action_list.append(act3)
    if livery_override is not None:
        if len(act6livery.modifications) > 0: action_list.append(act6livery)
        action_list.append(act3livery)
    action6.free_parameters.restore()

    return prepend_action_list + action_list