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(generic.Warning.GENERIC, 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(generic.Warning.GENERIC, 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 builtin_cargoexpr(name, args, pos): if len(args) < 1: raise generic.ScriptError(name + "() must have 1 or more parameters", pos) if not isinstance(args[0], StringLiteral ) or args[0].value not in global_constants.cargo_numbers: raise generic.ScriptError( "First argument of " + name + "() must be a string literal that is also in your cargo table", pos) cargotype = global_constants.cargo_numbers[args[0].value] if name == "produce_cargo": return ProduceCargo(cargotype, args[1:], pos) elif name == "accept_cargo": return AcceptCargo(cargotype, args[1:], pos) else: raise AssertionError()
def resolve_townname_id(self): ''' Resolve the reference number to a previous C{town_names} block. @return: Number of the referenced C{town_names} block. ''' if isinstance(self.def_number, expression.Identifier): self.number = actionF.named_numbers.get(self.def_number.value) if self.number is None: raise generic.ScriptError( 'Town names name "{}" is not defined or points to a next town_names node' .format(self.def_number.value), self.pos) else: self.number = self.def_number.value if self.number not in actionF.numbered_numbers: raise generic.ScriptError( 'Town names number "{}" is not defined or points to a next town_names node' .format(self.number), self.pos) return self.number
def __init__(self, block_type, block_name): self.block_type = block_type self.block_name = block_name self.sprite_data = {} if block_name is not None: if block_name.value in SpriteContainer.sprite_blocks: raise generic.ScriptError( "Block with name '{}' is already defined.".format(block_name.value), block_name.pos ) SpriteContainer.sprite_blocks[block_name.value] = self
def validate_func_float(expr1, expr2, pos): if expr1.type() not in (Type.INTEGER, Type.FLOAT) or expr2.type() not in (Type.INTEGER, Type.FLOAT): if expr1.type() == Type.SPRITEGROUP_REF: raise generic.ProcCallSyntaxError(expr1.name, expr1.pos) if expr2.type() == Type.SPRITEGROUP_REF: raise generic.ProcCallSyntaxError(expr2.name, expr2.pos) raise generic.ScriptError( "Binary operator requires both operands to be integers or floats.", pos) # If one is a float, the other must be constant since we can't handle floats at runtime if (expr1.type() == Type.FLOAT and not isinstance(expr2, (ConstantNumeric, ConstantFloat))) or ( expr2.type() == Type.FLOAT and not isinstance(expr1, (ConstantNumeric, ConstantFloat))): raise generic.ScriptError( "Floating-point operations are only possible when both operands are compile-time constants.", pos)
def validate_string(string): """ Check if a given string refers to a string that is translated in the language files and raise an error otherwise. @param string: The string to validate. @type string: L{expression.String} """ if string.name.value not in default_lang.strings: raise generic.ScriptError('Unknown string "{}"'.format(string.name.value), string.pos)
def builtin_tramtype_available(name, args, pos): """ tramtype_available(tramtype_label) builtin function. @return 1 if the roadtype label is available, 0 otherwise. """ if len(args) != 1: raise generic.ScriptError(name + "() must have exactly 1 parameter", pos) label = args[0].reduce() return SpecialCheck((0x11, None), 0, (0, 1), parse_string_to_dword(label), "{}({})".format(name, str(label)), pos = args[0].pos)
def supported_by_action2(self, raise_error): if not self.op.act2_supports: token = " '{}'".format(self.op.token) if self.op.token else "" if raise_error: raise generic.ScriptError( "Operator{} not supported in a switch-block".format(token), self.pos) return False return self.expr1.supported_by_action2( raise_error) and self.expr2.supported_by_action2(raise_error)
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 __init__(self, param_list, sprite_list, name, pos): base_statement.BaseStatement.__init__(self, "font_glyph-block", pos) sprite_container.SpriteContainer.__init__(self, "font_glyph-block", name) if not (2 <= len(param_list) <= 3): raise generic.ScriptError( "font_glyph-block requires 2 or 3 parameters, encountered " + str(len(param_list)), pos) self.font_size = param_list[0] self.base_char = param_list[1] self.image_file = param_list[2].reduce( ) if len(param_list) >= 3 else None if self.image_file is not None and not isinstance( self.image_file, expression.StringLiteral): raise generic.ScriptError( "font_glyph-block parameter 3 'file' must be a string literal", self.image_file.pos) self.sprite_list = sprite_list self.add_sprite_data(self.sprite_list, self.image_file, pos)
def builtin_sound_import(name, args, pos): from nml.actions import action11 if len(args) not in (2, 3): raise generic.ScriptError(name + "() must have 2 or 3 parameters", pos) grfid = parse_string_to_dword(args[0].reduce()) sound_num = args[1].reduce_constant().value volume = args[2].reduce_constant().value if len(args) >= 3 else 100 generic.check_range(volume, 0, 100, "sound volume", pos) return ConstantNumeric(action11.add_sound((grfid, sound_num, volume), pos), pos)
def builtin_hasbit(name, args, pos): """ hasbit(value, bit_num) builtin function. @return C{1} if and only if C{value} has bit C{bit_num} set, C{0} otherwise. """ if len(args) != 2: raise generic.ScriptError(name + "() must have exactly two parameters", pos) return BinOp(nmlop.HASBIT, args[0], args[1], pos)
def resolve_tmp_storage(self): for reg in self.param_registers: if not self.tmp_locations: raise generic.ScriptError( "There are not enough registers available " + "to perform all required computations in switch blocks. " + "Please reduce the complexity of your code.", self.pos) location = self.tmp_locations[0] self.remove_tmp_location(location, False) reg.set_register(location)
def industry_layout_count(name, args, pos, info): if len(args) < 2 or len(args) > 3: raise generic.ScriptError("'{}'() requires between 2 and 3 argument(s), encountered {:d}".format(name, len(args)), pos) grfid = expression.ConstantNumeric(0xFFFFFFFF) if len(args) == 2 else args[2] extra_params = [] extra_params.append( (0x100, grfid) ) extra_params.append( (0x101, expression.BinOp(nmlop.AND, args[1], expression.ConstantNumeric(0xFF)).reduce()) ) return (args[0], extra_params)
def industry_town_count(name, args, pos, info): if len(args) < 1 or len(args) > 2: raise generic.ScriptError("'{}'() requires between 1 and 2 argument(s), encountered {:d}".format(name, len(args)), pos) grfid = expression.ConstantNumeric(0xFFFFFFFF) if len(args) == 1 else args[1] extra_params = [] extra_params.append( (0x100, grfid) ) extra_params.append( (0x101, expression.ConstantNumeric(0x0100)) ) return (args[0], extra_params)
def get_feature(switch_block): feature = next(iter(switch_block.feature_set)) if switch_block.var_range == 0x8A: feature = action2var_variables.varact2parent_scope[feature] if feature is None: raise generic.ScriptError( "Parent scope for this feature not available, feature: " + str(feature), switch_block.pos) return feature
def reduce(self, id_dicts=None, unknown_id_fatal=True): expr = self.expr.reduce(id_dicts) if expr.type() != Type.INTEGER: raise generic.ScriptError( "Not-operator (~) requires an integer argument.", expr.pos) if isinstance(expr, ConstantNumeric): return ConstantNumeric(0xFFFFFFFF ^ expr.value) if isinstance(expr, BinNot): return expr.expr return BinNot(expr)
def builtin_date(name, args, pos): """ date(year, month, day) builtin function. @return Days since 1 jan 1 of the given date. """ days_in_month = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] if len(args) != 3: raise generic.ScriptError("date() requires exactly 3 arguments", pos) from nml import global_constants identifier.ignore_all_invalid_ids = True year = args[0].reduce(global_constants.const_list) identifier.ignore_all_invalid_ids = False try: month = args[1].reduce_constant().value day = args[2].reduce_constant().value except generic.ConstError: raise generic.ScriptError("Month and day parameters of date() should be compile-time constants", pos) generic.check_range(month, 1, 12, "month", args[1].pos) generic.check_range(day, 1, days_in_month[month-1], "day", args[2].pos) if not isinstance(year, ConstantNumeric): if month != 1 or day != 1: raise generic.ScriptError("when the year parameter of date() is not a compile time constant month and day should be 1", pos) #num_days = year*365 + year/4 - year/100 + year/400 part1 = BinOp(nmlop.MUL, year, ConstantNumeric(365)) part2 = BinOp(nmlop.DIV, year, ConstantNumeric(4)) part3 = BinOp(nmlop.DIV, year, ConstantNumeric(100)) part4 = BinOp(nmlop.DIV, year, ConstantNumeric(400)) res = BinOp(nmlop.ADD, part1, part2) res = BinOp(nmlop.SUB, res, part3) res = BinOp(nmlop.ADD, res, part4) return res generic.check_range(year.value, 0, 5000000, "year", year.pos) day_in_year = 0 for i in range(month - 1): day_in_year += days_in_month[i] day_in_year += day if month >= 3 and (year.value % 4 == 0) and ((not year.value % 100 == 0) or (year.value % 400 == 0)): day_in_year += 1 return ConstantNumeric(year.value * 365 + calendar.leapdays(0, year.value) + day_in_year - 1, pos)
def __init__(self, param_list, sprite_list, pos): base_statement.BaseStatement.__init__(self, "alt_sprites-block", pos) if not (3 <= len(param_list) <= 5): raise generic.ScriptError( "alternative_sprites-block requires 3 or 4 parameters, encountered " + str(len(param_list)), pos) self.name = param_list[0] if not isinstance(self.name, expression.Identifier): raise generic.ScriptError( "alternative_sprites parameter 1 'name' must be an identifier", self.name.pos) if isinstance( param_list[1], expression.Identifier) and param_list[1].value in zoom_levels: self.zoom_level = zoom_levels[param_list[1].value] else: raise generic.ScriptError( "value for alternative_sprites parameter 2 'zoom level' is not a valid zoom level", param_list[1].pos) if isinstance( param_list[2], expression.Identifier) and param_list[2].value in bit_depths: self.bit_depth = bit_depths[param_list[2].value] else: raise generic.ScriptError( "value for alternative_sprites parameter 3 'bit depth' is not a valid bit depthl", param_list[2].pos) global any_32bpp_sprites if self.bit_depth == 32: any_32bpp_sprites = True if len(param_list) >= 4: self.image_file = param_list[3].reduce() if not isinstance(self.image_file, expression.StringLiteral): raise generic.ScriptError( "alternative_sprites-block parameter 4 'file' must be a string literal", self.image_file.pos) else: self.image_file = None if len(param_list) >= 5: self.mask_file = param_list[4].reduce() if not isinstance(self.mask_file, expression.StringLiteral): raise generic.ScriptError( "alternative_sprites-block parameter 5 'mask_file' must be a string literal", self.mask_file.pos) if not self.bit_depth == 32: raise generic.ScriptError( "A mask file may only be specified for 32 bpp sprites.", self.mask_file.pos) else: self.mask_file = None self.sprite_list = sprite_list
def __init__(self, param_list, sprite_list, name, pos): base_statement.BaseStatement.__init__(self, "replace-block", pos) sprite_container.SpriteContainer.__init__(self, "replace-block", name) num_params = len(param_list) if not (1 <= num_params <= 2): raise generic.ScriptError( "replace-block requires 1 or 2 parameters, encountered " + str(num_params), pos) self.start_id = param_list[0] if num_params >= 2: self.image_file = param_list[1].reduce() if not isinstance(self.image_file, expression.StringLiteral): raise generic.ScriptError( "replace-block parameter 2 'file' must be a string literal", self.image_file.pos) else: self.image_file = None self.sprite_list = sprite_list self.add_sprite_data(self.sprite_list, self.image_file, pos)
def handle_grflangid(self, data, pos): """ Handle a 'grflangid' pragma. @param data: Data of the pragma. @type data: C{str} """ if self.langid is not None: raise generic.ScriptError("grflangid already set", pos) lang_text = data.strip() value = LANG_NAMES.get(lang_text) if value is None: try: value = int(lang_text, 16) except ValueError: raise generic.ScriptError("Invalid grflangid {!r}".format(lang_text), pos) if value < 0 or value >= 0x7F: raise generic.ScriptError("Invalid grflangid", pos) self.langid = value self.check_expected_plural(None)
def pre_process(self): if len(self.args) not in (1, 2): raise generic.ScriptError( "engine_override expects 1 or 2 parameters", self.pos) if len(self.args) == 1: try: self.source_grfid = expression.Identifier("GRFID").reduce( global_constants.const_list).value assert isinstance(self.source_grfid, int) except generic.ScriptError: raise generic.ScriptError( "GRFID of this grf is required, but no grf-block is defined.", self.pos) else: self.source_grfid = expression.parse_string_to_dword( self.args[0].reduce(global_constants.const_list)) self.grfid = expression.parse_string_to_dword(self.args[-1].reduce( global_constants.const_list))
def builtin_industry_type(name, args, pos): """ industry_type(IND_TYPE_OLD | IND_TYPE_NEW, id) builtin function @return The industry type in the format used by grfs (industry prop 0x16 and var 0x64) """ if len(args) != 2: raise generic.ScriptError(name + "() must have 2 parameters", pos) from nml import global_constants type = args[0].reduce_constant(global_constants.const_list).value if type not in (0, 1): raise generic.ScriptError("First argument of industry_type() must be IND_TYPE_OLD or IND_TYPE_NEW", pos) # Industry ID uses 7 bits (0 .. 6), bit 7 is for old/new id = args[1].reduce_constant(global_constants.const_list).value if not 0 <= id <= 127: raise generic.ScriptError("Second argument 'id' of industry_type() must be in range 0..127", pos) return ConstantNumeric(type << 7 | id)
def __init__(self, name, subtract_in, add_out, again, pos): if not isinstance(name, expression.Identifier): raise generic.ScriptError( "produce parameter 1 'name' should be an identifier.", name.pos) base_statement.BaseStatement.__init__(self, "produce-block", pos, False, False) self.initialize(name, 0x0A) self.subtract_in = subtract_in self.add_out = add_out self.again = again
def convert_palette(pal): ret = 256 * [0] for idx, colour in enumerate(pal): if 0xD7 <= idx <= 0xE2: if idx != colour: raise generic.ScriptError( "Indices 0xD7..0xE2 are not allowed in recolour sprites when the output is in the WIN palette" ) continue ret[palmap_d2w[idx]] = palmap_d2w[colour] return ret
def supported_by_actionD(self, raise_error): if not self.op.actd_supports: if raise_error: if self.op == nmlop.STO_PERM: raise generic.ScriptError( "STORE_PERM is only available in switch-blocks.", self.pos) elif self.op == nmlop.STO_TMP: raise generic.ScriptError( "STORE_TEMP is only available in switch-blocks.", self.pos) #default case token = " '{}'".format(self.op.token) if self.op.token else "" raise generic.ScriptError( "Operator{} not supported in parameter assignment".format( token), self.pos) return False return self.expr1.supported_by_actionD( raise_error) and self.expr2.supported_by_actionD(raise_error)
def reduce_expressions(self, var_feature): 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) if self.default is not None and self.default.value is not None: self.default.value = action2var.reduce_varaction2_expr(self.default.value, var_feature)
def pre_process(self): if None in (self.name, self.desc, self.grfid, self.version, self.min_compatible_version): raise generic.ScriptError( "A GRF-block requires the 'name', 'desc', 'grfid', 'version' and 'min_compatible_version' properties to be set.", self.pos) self.grfid = self.grfid.reduce() global_constants.constant_numbers[ 'GRFID'] = expression.parse_string_to_dword(self.grfid) self.name = self.name.reduce() if not isinstance(self.name, expression.String): raise generic.ScriptError("GRF-name must be a string", self.name.pos) grfstrings.validate_string(self.name) self.desc = self.desc.reduce() if not isinstance(self.desc, expression.String): raise generic.ScriptError("GRF-description must be a string", self.desc.pos) grfstrings.validate_string(self.desc) if self.url is not None: self.url = self.url.reduce() if not isinstance(self.url, expression.String): raise generic.ScriptError("URL must be a string", self.url.pos) grfstrings.validate_string(self.url) self.version = self.version.reduce_constant() self.min_compatible_version = self.min_compatible_version.reduce_constant( ) global param_stats param_num = 0 for param in self.params: param.pre_process(expression.ConstantNumeric(param_num)) param_num = param.num.value + 1 if param_num > param_stats[1]: raise generic.ScriptError( "No free parameters available. Consider assigning <num> manually and combine multiple bool parameters into a single bitmask parameter using <bit>.", self.pos) if param_num > param_stats[0]: param_stats[0] = param_num
def pre_process(self): feature = next(iter(self.feature_set)) # var_feature is really weird for type=BACKWARD/FORWARD. # Expressions in cases will still refer to the origin vehicle. var_feature = action2var_variables.varact2parent_scope[ feature] if self.type.value == "PARENT" else feature for choice in self.choices: choice.reduce_expressions(var_feature) for dep_list in (self.dependent, self.independent): for i, dep in enumerate(dep_list[:]): if dep.is_return: raise generic.ScriptError( "Expected a random_switch identifier after (in)dependent, not a return.", dep.pos) dep_list[i] = dep.value.reduce(global_constants.const_list) # Make sure, all [in]dependencies refer to existing random switch blocks if (not isinstance(dep_list[i], expression.SpriteGroupRef) ) or len(dep_list[i].param_list) > 0: raise generic.ScriptError( "Value for (in)dependent should be an identifier", dep_list[i].pos) spritegroup = action2.resolve_spritegroup(dep_list[i].name) if not isinstance(spritegroup, RandomSwitch): raise generic.ScriptError( "Value of (in)dependent '{}' should refer to a random_switch." .format(dep_list[i].name.value), dep_list[i].pos, ) self.triggers = self.triggers.reduce_constant( global_constants.const_list) if not (0 <= self.triggers.value <= 255): raise generic.ScriptError( "random_switch parameter 4 'triggers' out of range 0..255, encountered " + str(self.triggers.value), self.triggers.pos, ) switch_base_class.pre_process(self)
def get_sprite_number(self): # Layout of sprite number # bit 0 - 13: Sprite number # bit 14 - 15: Recolour mode (normal/transparent/remap) # bit 16 - 29: Palette sprite number # bit 30: Always draw sprite, even in transparent mode # bit 31: This is a custom sprite (from action1), not a TTD sprite if not self.is_set('sprite'): raise generic.ScriptError( "'sprite' must be set for this layout sprite", self.pos) # Make sure that recolouring is set correctly if self.get_param('recolour_mode') == 0 and self.is_set('palette'): raise generic.ScriptError( "'palette' may not be set when 'recolour_mode' is RECOLOUR_NONE." ) elif self.get_param('recolour_mode') != 0 and not self.is_set( 'palette'): raise generic.ScriptError( "'palette' must be set when 'recolour_mode' is not set to RECOLOUR_NONE." ) # Add the constant terms first sprite_num = self.get_param('recolour_mode') << 14 if self.get_param('always_draw'): sprite_num |= 1 << 30 if self.sprite_from_action1: sprite_num |= 1 << 31 # Prepare the return value expr = expression.ConstantNumeric(sprite_num, self.pos) # Add the sprite expr = expression.BinOp(nmlop.ADD, self.get_param('sprite'), expr, self.pos) # Add the palette expr = expression.BinOp( nmlop.ADD, expression.BinOp(nmlop.SHIFT_LEFT, self.get_param('palette'), expression.ConstantNumeric(16, self.pos), self.pos), expr, self.pos) return expr.reduce()