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)
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
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()
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
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
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)
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)
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 = []
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
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
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)
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
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
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
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)
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
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)
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)
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
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
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
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
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
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)
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
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()
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
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], )
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))
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