nml-0.4.4/0000755000567200056720000000000012643457623013463 5ustar jenkinsjenkins00000000000000nml-0.4.4/nml/0000755000567200056720000000000012643457623014251 5ustar jenkinsjenkins00000000000000nml-0.4.4/nml/generic.py0000644000567200056720000003461012643457545016246 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" # -*- coding: utf-8 -*- import sys, os, time def truncate_int32(value): """ Truncate the given value so it can be stored in exactly 4 bytes. The sign will be kept. Too high or too low values will be cut off, not clamped to the valid range. @param value: The value to truncate. @type value: C{int} @return: The truncated value. @rtype: C{int}. """ #source: http://www.tiac.net/~sw/2010/02/PureSalsa20/index.html return int( (value & 0x7fffFFFF) | -(value & 0x80000000) ) def check_range(value, min_value, max_value, name, pos): """ Check if a value is within a certain range and raise an error if it's not. @param value: The value to check. @type value: C{int} @param min_value: Minimum valid value. @type min_value: C{int} @param max_value: Maximum valid value. @type max_value: C{int} @param name: Name of the variable that is being tested. @type name: C{str} @param pos: Position information from the variable being tested. @type pos: L{Position} """ if not min_value <= value <= max_value: raise RangeError(value, min_value, max_value, name, pos) def greatest_common_divisor(a, b): """ Get the greatest common divisor of two numbers @param a: First number. @type a: C{int} @param b: Second number. @type b: C{int} @return: Greatest common divisor. @rtype: C{int] """ while b != 0: t = b b = a % b a = t return a def reverse_lookup(dic, val): """ Perform reverse lookup of any key that has the provided value. @param dic: Dictionary to perform reverse lookup on. @type dic: C{dict} @param val: Value being searched. @type val: an existing value. @return: A key such that C{dict[key] == val}. @rtype: Type of the matching key. """ for k, v in dic.items(): if v == val: return k raise AssertionError("Value not found in the dictionary.") def build_position(poslist): """ Construct a L{Position} object that takes the other positions as include list. @param poslist: Sequence of positions to report. First entry is the innermost position, last entry is the nml statement that started it all. @type poslist: C{list} of L{Position} @return: Position to attach to an error. @rtype: L{Position} """ if poslist is None or len(poslist) == 0: return None if len(poslist) == 1: return poslist[0] pos = poslist[-1] pos.includes = pos.includes + poslist[:-1] return pos class Position(object): """ Base class representing a position in a file. @ivar filename: Name of the file. @type filename: C{str} @ivar includes: List of file includes @type includes: C{list} of L{Position} """ def __init__(self, filename, includes): self.filename = filename self.includes = includes class LinePosition(Position): """ Line in a file. @ivar line_start: Line number (starting with 1) where the position starts. @type line_start: C{int} """ def __init__(self, filename, line_start, includes = []): Position.__init__(self, filename, includes) self.line_start = line_start def __str__(self): return '"{}", line {:d}'.format(self.filename, self.line_start) class PixelPosition(Position): """ Position of a pixel (in a file with graphics). @ivar xpos: Horizontal position of the pixel. @type xpos: C{int} @ivar ypos: Vertical position of the pixel. @type ypos: C{int} """ def __init__(self, filename, xpos, ypos): Position.__init__(self, filename, []) self.xpos = xpos self.ypos = ypos def __str__(self): return '"{}" at [x: {:d}, y: {:d}]'.format(self.filename, self.xpos, self.ypos) class ImageFilePosition(Position): """ Generic (not position-dependant) error with an image file """ def __init__(self, filename, pos = None): poslist = [] if pos is not None: poslist.append(pos) Position.__init__(self, filename, poslist) def __str__(self): return 'Image file "{}"'.format(self.filename) class LanguageFilePosition(Position): """ Generic (not position-dependant) error with a language file. """ def __init__(self, filename): Position.__init__(self, filename, []) def __str__(self): return 'Language file "{}"'.format(self.filename) class ScriptError(Exception): def __init__(self, value, pos = None): self.value = value self.pos = pos def __str__(self): if self.pos is None: return self.value else: ret = str(self.pos) + ": " + self.value for inc in reversed(self.pos.includes): ret += "\nIncluded from: " + str(inc) return ret class ConstError(ScriptError): """ Error to denote a compile-time integer constant was expected but not found. """ def __init__(self, pos = None): ScriptError.__init__(self, "Expected a compile-time integer constant", pos) class RangeError(ScriptError): def __init__(self, value, min_value, max_value, name, pos = None): ScriptError.__init__(self, name + " out of range " + str(min_value) + ".." + str(max_value) + ", encountered " + str(value), pos) class ImageError(ScriptError): def __init__(self, value, filename, pos = None): ScriptError.__init__(self, value, ImageFilePosition(filename, pos)) class OnlyOnceError(ScriptError): """ An error denoting two elements in a single grf were found, where only one is allowed. """ def __init__(self, typestr, pos = None): """ @param typestr: Description of the type of element encountered. @type typestr: C{str} @param pos: Position of the error, if provided. @type pos: C{None} or L{Position} """ ScriptError.__init__(self, "A grf may contain only one {}.".format(typestr), pos) class OnlyOnce: """ Class to enforce that certain objects / constructs appear only once. """ seen = {} @classmethod def enforce(cls, obj, typestr): """ If this method is called more than once for an object of the exact same class, an OnlyOnceError is raised. """ objtype = obj.__class__ if objtype in cls.seen: raise OnlyOnceError(typestr, obj.pos) cls.seen[objtype] = None @classmethod def clear(cls): cls.seen = {} VERBOSITY_WARNING = 1 # Verbosity level for warnings VERBOSITY_INFO = 2 # Verbosity level for info messages VERBOSITY_PROGRESS = 3 # Verbosity level for progress feedback VERBOSITY_TIMING = 4 # Verbosity level for timing information VERBOSITY_MAX = 4 # Maximum verbosity level """ Verbosity level for console output. """ verbosity_level = VERBOSITY_PROGRESS def set_verbosity(level): global verbosity_level verbosity_level = level def print_eol(msg): """ Clear current line and print message without linefeed. """ if not os.isatty(sys.stdout.fileno()): return print("\r" + msg + "\033[K", end="") """ Current progress message. """ progress_message = None """ Timestamp when the current processing step started. """ progress_start_time = None """ Timestamp of the last incremental progress update. """ progress_update_time = None def hide_progress(): if progress_message is not None: print_eol("") def show_progress(): if progress_message is not None: print_eol(progress_message) def clear_progress(): global progress_message global progress_start_time global progress_update_time hide_progress() if (progress_message is not None) and (verbosity_level >= VERBOSITY_TIMING): print("{} {:.1f} s".format(progress_message, time.clock() - progress_start_time)) progress_message = None progress_start_time = None progress_update_time = None def print_progress(msg, incremental = False): """ Output progess information to the user. @param msg: Progress message. @type msg: C{str} @param incremental: True if this message is updated incrementally (that is, very often). @type incremental: C{bool} """ if verbosity_level < VERBOSITY_PROGRESS: return global progress_message global progress_start_time global progress_update_time if (not incremental) and (progress_message is not None): clear_progress() progress_message = msg if incremental: t = time.clock() if (progress_update_time is not None) and (t - progress_update_time < 1): return progress_update_time = t else: progress_start_time = time.clock() print_eol(msg) def print_info(msg): """ Output a pure informational message to th euser. """ if verbosity_level < VERBOSITY_INFO: return hide_progress() print(" nmlc info: " + msg) show_progress() def print_warning(msg, pos = None): """ Output a warning message to the user. """ if verbosity_level < VERBOSITY_WARNING: return if pos: msg = str(pos) + ": " + msg msg = " nmlc warning: " + msg if (sys.stderr.isatty()) and (os.name == 'posix'): msg = "\033[33m" + msg + "\033[0m" hide_progress() print(msg, file=sys.stderr) show_progress() def print_error(msg): """ Output an error message to the user. """ clear_progress() print("nmlc ERROR: " + msg, file=sys.stderr) def print_dbg(indent, *args): """ Output debug text. @param indent: Indentation to add to the output. @type indent: C{int} @param args: Arguments to print. An additional space is printed between them. @type args: C{Tuple} of C{str} """ hide_progress() print(indent * ' ' + ' '.join(str(arg) for arg in args)) show_progress() """ Paths already resolved to correct paths on the system. The key is the path as specified in the sources. The value is the validated path on the system. """ _paths = dict() def find_file(filepath): """ Verify whether L{filepath} exists. If not, try to find a similar one with a different case. @param filepath: Path to the file to open. @type filepath: C{str} @return: Path name to a file that exists at the file system. @rtype: C{str} """ # Split the filepath in components (directory parts and the filename). drive, filepath = os.path.splitdrive(os.path.normpath(filepath)) # 'splitdrive' above does not remove the leading / of absolute Unix paths. # The 'split' below splits on os.sep, which means that loop below fails for "/". # To prevent that, handle the leading os.sep separately. if filepath.startswith(os.sep): drive = drive + os.sep filepath = filepath[len(os.sep):] components = [] # Path stored in reverse order (filename at index[0]) while filepath != '': filepath, filepart = os.path.split(filepath) components.append(filepart) # Re-build the absolute path. path = drive if path == '': path = os.getcwd() while len(components) > 0: comp = components.pop() childpath = os.path.join(path, comp) if childpath in _paths: path = _paths[childpath] continue if os.access(path, os.R_OK): # Path is readable, compare provided path with the file system. entries = os.listdir(path) + [os.curdir, os.pardir] lcomp = comp.lower() matches = [entry for entry in entries if lcomp == entry.lower()] if len(matches) == 0: raise ScriptError("Path \"{}\" does not exist (even after case conversions)".format(os.path.join(path, comp))) elif len(matches) > 1: raise ScriptError("Path \"{}\" is not unique (case conversion gave {:d} solutions)".format(os.path.join(path, comp), len(matches))) if matches[0] != comp: given_path = os.path.join(path, comp) real_path = os.path.join(path, matches[0]) msg = "Path \"{}\" at the file system does not match path \"{}\" given in the input (case mismatch in the last component)" msg = msg.format(real_path, given_path) print_warning(msg) elif os.access(path, os.X_OK): # Path is only accessible, cannot inspect the file system. matches = [comp] else: raise ScriptError("Path \"{}\" does not exist or is not accessible".format(path)) path = os.path.join(path, matches[0]) if len(components) > 0: _paths[childpath] = path return path cache_root_dir = ".nmlcache" def set_cache_root_dir(dir): cache_root_dir = os.path.abspath(dir) os.makedirs(cache_root_dir, exist_ok=True) def get_cache_file(sources, extension): """ Compose a filename for a cache file. @param sources: List of source files, the cache file depends on / belongs to. @type sources: C{list} or C{tuple} of C{str} or similar. @param extension: File extension for the cache file including leading ".". @type extension: C{str} @return: Filename for cache file. @rtype: C{str} """ result = "" for part in sources: if part is not None: path, name = os.path.split(part) if len(result) == 0: # Make sure that the path does not leave the cache dir path = os.path.normpath(path).replace(os.path.pardir, "__") path = os.path.join(cache_root_dir, path) os.makedirs(path, exist_ok=True) result = os.path.join(path, name) else: # In case of multiple soure files, ignore the path component for all but the first result += "_" + name return result + extension nml-0.4.4/nml/tokens.py0000644000567200056720000002216512643457545016137 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import sys, re import ply.lex as lex from nml import expression, generic reserved = { 'grf' : 'GRF', 'var' : 'VARIABLE', 'param' : 'PARAMETER', 'cargotable' : 'CARGOTABLE', 'railtypetable' : 'RAILTYPETABLE', 'if' : 'IF', 'else' : 'ELSE', 'while' : 'WHILE', # reserved 'item' : 'ITEM', # action 0/3 'property' : 'PROPERTY', 'graphics' : 'GRAPHICS', 'snowline' : 'SNOWLINE', 'basecost' : 'BASECOST', 'template' : 'TEMPLATE', # sprite template for action1 'spriteset' : 'SPRITESET', # action 1 'spritegroup' : 'SPRITEGROUP', # action 2 'switch' : 'SWITCH', # deterministic varaction2 'random_switch' : 'RANDOMSWITCH', # random action2 'produce' : 'PRODUCE', # production action2 'error' : 'ERROR', # action B 'disable_item' : 'DISABLE_ITEM', 'replace' : 'REPLACESPRITE', # action A 'replacenew' : 'REPLACENEWSPRITE', # action 5 'font_glyph' : 'FONTGLYPH', # action 12 'deactivate' : 'DEACTIVATE', # action E 'town_names' : 'TOWN_NAMES', # action F 'string' : 'STRING', 'return' : 'RETURN', 'livery_override' : 'LIVERYOVERRIDE', 'exit' : 'SKIP_ALL', 'tilelayout' : 'TILELAYOUT', 'spritelayout' : 'SPRITELAYOUT', 'alternative_sprites' : 'ALT_SPRITES', 'base_graphics' : 'BASE_GRAPHICS', 'recolour_sprite' : 'RECOLOUR_SPRITE', 'engine_override' : 'ENGINE_OVERRIDE', 'sort' : 'SORT_VEHICLES', } line_directive1_pat = re.compile(r'\#line\s+(\d+)\s*(\r?\n|"(.*)"\r?\n)') line_directive2_pat = re.compile(r'\#\s+(\d+)\s+"(.*)"\s*((?:\d+\s*)*)\r?\n') class NMLLexer(object): """ @ivar lexer: PLY scanner object. @type lexer: L{ply.lex} @ivar includes: Stack of included files. @type includes: C{List} of L{generic.LinePosition} @ivar text: Input text to scan. @type text: C{str} """ # Tokens tokens = list(reserved.values()) + [ 'ID', 'PLUS', 'MINUS', 'TIMES', 'DIVIDE', 'MODULO', 'AND', 'OR', 'XOR', 'LOGICAL_AND', 'LOGICAL_OR', 'LOGICAL_NOT', 'BINARY_NOT', 'EQ', 'LPAREN', 'RPAREN', 'SHIFT_LEFT', 'SHIFT_RIGHT', 'SHIFTU_RIGHT', 'COMP_EQ', 'COMP_NEQ', 'COMP_LE', 'COMP_GE', 'COMP_LT', 'COMP_GT', 'COMMA', 'RANGE', 'LBRACKET', 'RBRACKET', 'LBRACE', 'RBRACE', 'TERNARY_OPEN', 'COLON', 'SEMICOLON', 'STRING_LITERAL', 'NUMBER', 'FLOAT', 'UNIT', ] t_PLUS = r'\+' t_MINUS = r'-' t_TIMES = r'\*' t_MODULO = r'%' t_DIVIDE = r'/' t_AND = r'&' t_OR = r'\|' t_XOR = r'\^' t_LOGICAL_AND = r'&&' t_LOGICAL_OR = r'\|\|' t_LOGICAL_NOT = r'!' t_BINARY_NOT = r'~' t_EQ = r'=' t_LPAREN = r'\(' t_RPAREN = r'\)' t_SHIFT_LEFT = r'<<' t_SHIFT_RIGHT = r'>>' t_SHIFTU_RIGHT = r'>>>' t_COMP_EQ = r'==' t_COMP_NEQ = r'!=' t_COMP_LE = r'<=' t_COMP_GE = r'>=' t_COMP_LT = r'<' t_COMP_GT = r'>' t_COMMA = r',' t_RANGE = r'\.\.' t_LBRACKET = r'\[' t_RBRACKET = r'\]' t_LBRACE = r'{' t_RBRACE = r'}' t_TERNARY_OPEN = r'\?' t_COLON = r':' t_SEMICOLON = r';' def t_FLOAT(self, t): r'\d+\.\d+' t.value = expression.ConstantFloat(float(t.value), t.lineno) return t def t_NUMBER(self, t): r'(0x[0-9a-fA-F]+)|(\d+)' base = 10 if len(t.value) >= 2 and t.value[0:2] == "0x": t.value = t.value[2:] base = 16 t.value = expression.ConstantNumeric(int(t.value, base), t.lineno) return t def t_UNIT(self, t): r'(nfo)|(mph)|(km/h)|(m/s)|(hpI)|(hpM)|(hp)|(kW)|(tons)|(ton)|(kg)|(snow%)' return t def t_ID(self, t): r'[a-zA-Z_][a-zA-Z0-9_]*' if t.value in reserved: # Check for reserved words t.type = reserved[t.value] else: t.type = 'ID' t.value = expression.Identifier(t.value, t.lineno) return t def t_STRING_LITERAL(self, t): r'"([^"\\]|\\.)*"' t.value = expression.StringLiteral(t.value[1:-1], t.lineno) return t # Ignored characters def t_ignore_comment(self, t): r'(/\*(\n|.)*?\*/)|(//.*)' self.increment_lines(t.value.count("\n")) def t_ignore_whitespace(self, t): "[ \t\r]+" pass def t_line_directive1(self, t): r'\#line\s+\d+\s*(\r?\n|".*"\r?\n)' # See: https://gcc.gnu.org/onlinedocs/cpp/Line-Control.html m = line_directive1_pat.match(t.value) assert m is not None fname = self.lexer.lineno.filename if m.group(3) is None else m.group(3) # This type of line directive contains no information about includes, so we have to make some assumptions if self.includes and self.includes[-1].filename == fname: # Filename equal to the one on top of the stack -> end of an include self.includes.pop() elif fname != self.lexer.lineno.filename: # Not an end of include and not the current file -> start of an include self.includes.append(self.lexer.lineno) self.set_position(fname, int(m.group(1), 10)) self.increment_lines(t.value.count('\n') - 1) 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 t_newline(self, t): r'\n+' self.increment_lines(len(t.value)) def t_error(self, t): print(("Illegal character '{}' (character code 0x{:02X}) at {}, column {:d}".format(t.value[0], ord(t.value[0]), t.lexer.lineno, self.find_column(t)))) sys.exit(1) def build(self): """ Initial construction of the scanner. """ self.lexer = lex.lex(module=self) def setup(self, text, fname): """ Setup scanner for scanning an input file. @param text: Input text to scan. @type text: C{str} @param fname: Filename associated with the input text (main input file). @type fname: C{str} """ self.includes = [] self.text = text self.set_position(fname, 1) self.lexer.input(text) def set_position(self, fname, line): """ @note: The lexer.lineno contains a Position object. """ self.lexer.lineno = generic.LinePosition(fname, line, self.includes[:]) def increment_lines(self, count): self.set_position(self.lexer.lineno.filename, self.lexer.lineno.line_start + count) def find_column(self, t): last_cr = self.text.rfind('\n', 0, t.lexpos) if last_cr < 0: last_cr = 0 return t.lexpos - last_cr nml-0.4.4/nml/spritecache.py0000644000567200056720000003014112643457545017117 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import array, json, os from nml import generic keep_orphaned = True class SpriteCache(object): """ Cache for compressed sprites. @ivar cache_filename: Filename of cache data file. @type cache_filename: C{str} @ivar cache_index_filename: Filename of cache index file. @type cache_index_filename: C{str} @ivar cache_time: Date of cache files. The cache is invalid, if the source image files are newer. @type cache_time: C{int} @ivar cached_sprites: Cache contents @type cached_sprites: C{dict} mapping cache keys to cache items. Cache file format description: Format of cache index is JSON (JavaScript Object Notation), which is easily readable by both humans (for debugging) and machines. Format is as follows: A list of dictionaries, each corresponding to a sprite, with the following keys - rgb_file: filename of the 32bpp sprite (string) - rgb_rect: (uncropped) rectangle of the 32bpp sprite (list with 4 elements (x,y,w,h)) - mask_file, mask_rect: same as above, but for 8bpp sprite - mask_pal: palette of mask file, 'DEFAULT' or 'LEGACY'. Not present if 'mask_file' is not present. - crop: List of 4 positive integers, indicating how much to crop if cropping is enabled Order is (left, right, top, bottom), it is not present if cropping is disabled - info: Info byte of the sprite - pixel_stats: Dictionary with statistics about pixels: 'total': Total amount of pixels. 'alpha': Amount of semi-transparent pixels in 32bpp. 'white': Amount of pure-white pixels in 8bpp. 'anim': Amount of animated pixels in 8bpp. - offset: Offset into the cache file for this sprite - size: Length of this sprite in the cache file Either rgb_file/rect, mask_file/rect, or both must be specified, depending on the sprite The cache should contain the sprite data, but not the header (sizes/offsets and such) For easy lookup, this information is stored in a dictionary Tuples are used because these are hashable The key is a (rgb_file, rgb_rect, mask_file, mask_rect, do_crop, palette)-tuple The rectangles are 4-tuples, non-existent items (e.g. no rgb) are None do_crop is a boolean indicating if this sprite has been cropped palette is a string identifier The value that this key maps to is a 6-tuple, containing: - the sprite data (as a byte array) - The 'info' byte of the sprite - The cropping information (see above) (None if 'do_crop' in the key is false) - The pixel_stats dictionary with statistics. - Whether the sprite exists in the old (loaded)cache - whether the sprite is used by the current GRF The cache file itself is simply the binary sprite data with no meta-information or padding. Offsets and sizes for the various sprites are in the cacheindex file. """ def __init__(self, filename): self.cache_filename = filename + ".cache" self.cache_index_filename = filename + ".cacheindex" self.cache_time = 0 self.cached_sprites = {} def get_item(self, cache_key, palette): """ Get item from cache. @param cache_key: Sprite metadata key, as returned by RealSprite.get_cache_key @type cache_key: C{tuple} @param palette: Palette identifier, one of palette.palette_name @type palette: C{str} @return: Cache item @rtype: C{tuple} or C{None} """ if cache_key[2] is not None: key = cache_key + (palette, ) else: key = cache_key + (None, ) return self.cached_sprites.get(key, None) def add_item(self, cache_key, palette, item): """ Add item to cache. @param cache_key: Sprite metadata key, as returned by RealSprite.get_cache_key @type cache_key: C{tuple} @param palette: Palette identifier, one of palette.palette_name @type palette: C{str} @param item: Cache item @type item: C{tuple} """ if cache_key[2] is not None: key = cache_key + (palette, ) else: key = cache_key + (None, ) self.cached_sprites[key] = item def count_orphaned(self): """ Count number of items in cache, which have not been used. @return: Number of unused items. @type: C{int} """ return sum(not item[5] for item in self.cached_sprites.values()) def read_cache(self): """ Read the *.grf.cache[index] files. """ if not (os.access(self.cache_filename, os.R_OK) and os.access(self.cache_index_filename, os.R_OK)): # Cache files don't exist return index_file = open(self.cache_index_filename, 'r') cache_file = open(self.cache_filename, 'rb') cache_data = array.array('B') cache_size = os.fstat(cache_file.fileno()).st_size cache_data.fromfile(cache_file, cache_size) assert cache_size == len(cache_data) self.cache_time = os.path.getmtime(self.cache_filename) source_mtime = dict() try: # Just assert and print a generic message on errors, as the cache data should be correct # Not asserting could lead to errors later on # Also, it doesn't make sense to inform the user about things he shouldn't know about and can't fix sprite_index = json.load(index_file) assert isinstance(sprite_index, list) for sprite in sprite_index: assert isinstance(sprite, dict) # load RGB (32bpp) data rgb_key = (None, None) if 'rgb_file' in sprite and 'rgb_rect' in sprite: assert isinstance(sprite['rgb_file'], str) assert isinstance(sprite['rgb_rect'], list) and len(sprite['rgb_rect']) == 4 assert all(isinstance(num, int) for num in sprite['rgb_rect']) rgb_key = (sprite['rgb_file'], tuple(sprite['rgb_rect'])) # load Mask (8bpp) data mask_key = (None, None) if 'mask_file' in sprite and 'mask_rect' in sprite: assert isinstance(sprite['mask_file'], str) assert isinstance(sprite['mask_rect'], list) and len(sprite['mask_rect']) == 4 assert all(isinstance(num, int) for num in sprite['mask_rect']) mask_key = (sprite['mask_file'], tuple(sprite['mask_rect'])) palette_key = None if 'mask_pal' in sprite: palette_key = sprite['mask_pal'] # Compose key assert any(i is not None for i in rgb_key + mask_key) key = rgb_key + mask_key + ('crop' in sprite, palette_key) assert key not in self.cached_sprites # Read size/offset from cache assert 'offset' in sprite and 'size' in sprite offset, size = sprite['offset'], sprite['size'] assert isinstance(offset, int) and isinstance(size, int) assert offset >= 0 and size > 0 assert offset + size <= cache_size data = cache_data[offset:offset+size] # Read info / cropping data from cache assert 'info' in sprite and isinstance(sprite['info'], int) info = sprite['info'] if 'crop' in sprite: assert isinstance(sprite['crop'], list) and len(sprite['crop']) == 4 assert all(isinstance(num, int) for num in sprite['crop']) crop = tuple(sprite['crop']) else: crop = None if 'pixel_stats' in sprite: assert isinstance(sprite['pixel_stats'], dict) pixel_stats = sprite['pixel_stats'] else: pixel_stats = {} # Compose value value = (data, info, crop, pixel_stats, True, False) # Check if cache item is still valid is_valid = True if rgb_key[0] is not None: mtime = source_mtime.get(rgb_key[0]) if mtime is None: mtime = os.path.getmtime(generic.find_file(rgb_key[0])) source_mtime[rgb_key[0]] = mtime if mtime > self.cache_time: is_valid = False if mask_key[0] is not None: mtime = source_mtime.get(mask_key[0]) if mtime is None: mtime = os.path.getmtime(generic.find_file(mask_key[0])) source_mtime[mask_key[0]] = mtime if mtime > self.cache_time: is_valid = False # Drop items from older spritecache format without palette entry if (mask_key[0] is None) != (palette_key is None): is_valid = False if is_valid: self.cached_sprites[key] = value except: generic.print_warning(self.cache_index_filename + " contains invalid data, ignoring. Please remove the file and file a bug report if this warning keeps appearing") self.cached_sprites = {} # Clear cache index_file.close() cache_file.close() def write_cache(self): """ Write the cache data to the .cache[index] files. """ index_data = [] sprite_data = array.array('B') offset = 0 old_cache_valid = True for key, value in list(self.cached_sprites.items()): # Unpack key/value rgb_file, rgb_rect, mask_file, mask_rect, do_crop, mask_pal = key data, info, crop_rect, pixel_stats, in_old_cache, in_use = value assert do_crop == (crop_rect is not None) assert (mask_file is None) == (mask_pal is None) # If this cache information is exactly the same as the old cache, then we don't bother writing later on if not in_use and not keep_orphaned: old_cache_valid = False continue if not in_old_cache: old_cache_valid = False # Create dictionary with information sprite = {} if rgb_file is not None: sprite['rgb_file'] = rgb_file sprite['rgb_rect'] = tuple(rgb_rect) if mask_file is not None: sprite['mask_file'] = mask_file sprite['mask_rect'] = tuple(mask_rect) sprite['mask_pal'] = mask_pal size = len(data) sprite['offset'] = offset sprite['size'] = size sprite['info'] = info if do_crop: sprite['crop'] = tuple(crop_rect) sprite['pixel_stats'] = pixel_stats index_data.append(sprite) sprite_data.extend(data) offset += size if old_cache_valid: return index_output = json.JSONEncoder(sort_keys = True).encode(index_data) index_file = open(self.cache_index_filename, 'w') index_file.write(index_output) index_file.close() cache_file = open(self.cache_filename, 'wb') sprite_data.tofile(cache_file) cache_file.close() nml-0.4.4/nml/editors/0000755000567200056720000000000012643457623015722 5ustar jenkinsjenkins00000000000000nml-0.4.4/nml/editors/extract_tables.py0000644000567200056720000001264512643457545021313 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import unit, tokens, global_constants from nml.ast import general, switch, basecost from nml.expression import functioncall from nml.actions import action0properties, action2layout, action2var_variables from nml.actions import action3_callbacks, action5, action12, actionB, real_sprite #Create list of blocks, functions and units units = set(unit.units.keys()) keywords = set(tokens.reserved.keys()) functions = set(functioncall.function_table.keys()) layouts = set(action2layout.layout_sprite_types.keys()) #No easy way to get action14 stuff temp1 = units | keywords | functions | layouts | set(["int", "bool"]) block_names_table = sorted(temp1) ##Create list of properties and variables var_tables = [ action2var_variables.varact2_globalvars, action2var_variables.varact2vars_vehicles, action2var_variables.varact2vars_trains, action2var_variables.varact2vars_roadvehs, action2var_variables.varact2vars_ships, action2var_variables.varact2vars_aircraft, action2var_variables.varact2vars60x_vehicles, action2var_variables.varact2vars_base_stations, action2var_variables.varact2vars60x_base_stations, action2var_variables.varact2vars_stations, action2var_variables.varact2vars60x_stations, action2var_variables.varact2vars_canals, action2var_variables.varact2vars_houses, action2var_variables.varact2vars60x_houses, action2var_variables.varact2vars_industrytiles, action2var_variables.varact2vars60x_industrytiles, action2var_variables.varact2vars_industries, action2var_variables.varact2vars60x_industries, action2var_variables.varact2vars_airports, action2var_variables.varact2vars_objects, action2var_variables.varact2vars60x_objects, action2var_variables.varact2vars_railtype, action2var_variables.varact2vars_airporttiles, action2var_variables.varact2vars60x_airporttiles, action2var_variables.varact2vars_towns] variables = set() for d in var_tables: for key in d.keys(): variables.add(key) prop_tables = [ action0properties.general_veh_props, action0properties.properties[0x00], action0properties.properties[0x01], action0properties.properties[0x02], action0properties.properties[0x03], action0properties.properties[0x05], action0properties.properties[0x07], action0properties.properties[0x09], action0properties.properties[0x0A], action0properties.properties[0x0B], action0properties.properties[0x0D], action0properties.properties[0x0F], action0properties.properties[0x10], action0properties.properties[0x11]] properties = set() for d in prop_tables: for key in d.keys(): properties.add(key) dummy = action2layout.Action2LayoutSprite(None, None) layout_sprites = set(dummy.params.keys()) cb_tables = [ action3_callbacks.general_vehicle_cbs, action3_callbacks.callbacks[0x00], action3_callbacks.callbacks[0x01], action3_callbacks.callbacks[0x02], action3_callbacks.callbacks[0x03], action3_callbacks.callbacks[0x04], action3_callbacks.callbacks[0x05], action3_callbacks.callbacks[0x07], action3_callbacks.callbacks[0x09], action3_callbacks.callbacks[0x0A], action3_callbacks.callbacks[0x0B], action3_callbacks.callbacks[0x0D], action3_callbacks.callbacks[0x0F], action3_callbacks.callbacks[0x10], action3_callbacks.callbacks[0x11]] callbacks = set() for d in cb_tables: for key in d.keys(): callbacks.add(key) #No easy way to get action14 stuff act14_vars = set(["grfid", "name", "desc", "version", "min_compatible_version", "type", "bit", "min_value", "max_value", "def_value", "names"]) temp2 = variables | properties | layout_sprites | callbacks | act14_vars variables_names_table = sorted(temp2) #Create list of features features = set(general.feature_ids.keys()) switch_names = set(switch.var_ranges.keys()) temp3 = features | switch_names feature_names_table = sorted(temp3) #Create list of callbacks constants const_tables = [ global_constants.constant_numbers, global_constants.global_parameters, global_constants.misc_grf_bits, global_constants.patch_variables, global_constants.config_flags, global_constants.unified_maglev_var, global_constants.railtype_table] constant_names = set() for d in const_tables: for key in d.keys(): constant_names.add(key) act5_names = set(action5.action5_table.keys()) act12_names = set(action12.font_sizes.keys()) actB_names = set(actionB.default_error_msg.keys()) | set(actionB.error_severity.keys()) sprite_names = set(real_sprite.real_sprite_flags.keys()) cost_names = set(basecost.base_cost_table.keys()) | set(basecost.generic_base_costs) temp4 = constant_names | act5_names | act12_names | actB_names | sprite_names | cost_names callback_names_table = sorted(temp4) nml-0.4.4/nml/editors/kate.py0000644000567200056720000001646312643457545017235 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml.editors import extract_tables output_file="nml_kate.xml" header_text = """\ """ feature_text = """\ """ builtin_text = """\ """ constant_text = """\ """ tail_text = """\ """ def write_file(fname): handle = open(fname, "w") handle.write(header_text) for word in extract_tables.keywords: handle.write(" {} \n".format(word)) handle.write(feature_text) for word in extract_tables.features: handle.write(" {} \n".format(word)) handle.write(builtin_text) for word in extract_tables.functions: handle.write(" {} \n".format(word)) handle.write(constant_text) for word in extract_tables.callback_names_table: handle.write(" {} \n".format(word)) handle.write(tail_text) handle.close() def run(): write_file("nml_kate.xml") nml-0.4.4/nml/editors/__init__.py0000644000567200056720000000124312643457545020036 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" nml-0.4.4/nml/editors/notepadpp.py0000644000567200056720000000766412643457545020306 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml.editors import extract_tables #Define parts of np++ xml file string1 = """\ 000000 { } ( ) , : ; [ ] 1/* 2*/ 0// """ string2 = """\ """ string3 = """\ """ string4 = """\ """ string5="""\ """ #Build np++ xml file def write_file(fname): handle = open(fname, "w") handle.write(string1) handle.write(" ".join(extract_tables.block_names_table)) handle.write(string2) handle.write(" ".join(extract_tables.variables_names_table)) handle.write(string3) handle.write(" ".join(extract_tables.feature_names_table)) handle.write(string4) handle.write(" ".join(extract_tables.callback_names_table)) handle.write(string5) handle.close() def run(): write_file("nml_notepadpp.xml") nml-0.4.4/nml/main.py0000644000567200056720000004732112643457545015561 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import sys, os, codecs, optparse from nml import generic, grfstrings, parser, version_info, output_nml, output_nfo, output_grf, output_dep, palette, spriteencoder, spritecache, global_constants from nml.actions import action2layout, action2var, action8, sprite_count, real_sprite, action4, action0, action1, action2, action6, action7, action11, actionF from nml.ast import grf, alt_sprites try: from PIL import Image except ImportError: try: import Image except ImportError: pass developmode = False # Give 'nice' error message instead of a stack dump. version = version_info.get_nml_version() def parse_cli(argv): """ Parse the command line, and process options. @return: Options, and input filename (if not supplied, use C{sys.stdin}). @rtype: C{tuple} (C{Object}, C{str} or C{None}) """ usage = "Usage: %prog [options] \n" \ "Where is the nml file to parse" opt_parser = optparse.OptionParser(usage=usage, version=version_info.get_cli_version()) opt_parser.set_defaults(debug=False, crop=False, compress=True, outputs=[], start_sprite_num=0, custom_tags="custom_tags.txt", lang_dir="lang", default_lang="english.lng", cache_dir=".nmlcache", forced_palette="ANY", quiet=False, md5_filename=None, keep_orphaned=True, verbosity=generic.verbosity_level) opt_parser.add_option("-d", "--debug", action="store_true", dest="debug", help="write the AST to stdout") opt_parser.add_option("-s", "--stack", action="store_true", dest="stack", help="Dump stack when an error occurs") opt_parser.add_option("--grf", dest="grf_filename", metavar="", help="write the resulting grf to ") opt_parser.add_option("--md5", dest="md5_filename", metavar="", help="Write an md5sum of the resulting grf to ") opt_parser.add_option("--nfo", dest="nfo_filename", metavar="", help="write nfo output to ") opt_parser.add_option("-M", action="store_true", dest="dep_check", help="output a rule suitable for make describing the graphics dependencies of the main grf file (requires input file or --grf)") opt_parser.add_option("--MF", dest="dep_filename", metavar="", help="When used with -M, specifies a file to write the dependencies to") opt_parser.add_option("--MT", dest="depgrf_filename", metavar="", help="target of the rule emitted by dependency generation (requires -M)") opt_parser.add_option("-c", action="store_true", dest="crop", help="crop extraneous transparent blue from real sprites") opt_parser.add_option("-u", action="store_false", dest="compress", help="save uncompressed data in the grf file") opt_parser.add_option("--nml", dest="nml_filename", metavar="", help="write optimized nml to ") opt_parser.add_option("-o", "--output", dest="outputs", action="append", metavar="", help="write output(nfo/grf) to ") opt_parser.add_option("-t", "--custom-tags", dest="custom_tags", metavar="", help="Load custom tags from [default: %default]") opt_parser.add_option("-l", "--lang-dir", dest="lang_dir", metavar="", help="Load language files from directory [default: %default]") opt_parser.add_option("--default-lang", dest="default_lang", metavar="", help="The default language is stored in [default: %default]") opt_parser.add_option("--start-sprite", action="store", type="int", dest="start_sprite_num", metavar="", help="Set the first sprite number to write (do not use except when you output nfo that you want to include in other files)") opt_parser.add_option("-p", "--palette", dest="forced_palette", metavar="", choices = ["DEFAULT", "LEGACY", "DOS", "WIN", "ANY"], help="Force nml to use the palette [default: %default]. Valid values are 'DEFAULT', 'LEGACY', 'ANY'") opt_parser.add_option("--quiet", action="store_true", dest="quiet", help="Disable all warnings. Errors will be printed normally.") opt_parser.add_option("-n", "--no-cache", action="store_true", dest="no_cache", help="Disable caching of sprites in .cache[index] files, which may reduce compilation time.") opt_parser.add_option("--cache-dir", dest="cache_dir", metavar="", help="Cache files are stored in directory [default: %default]") opt_parser.add_option("--clear-orphaned", action="store_false", dest="keep_orphaned", help="Remove unused/orphaned items from cache files.") opt_parser.add_option("--verbosity", type="int", dest="verbosity", metavar="", help="Set the verbosity level for informational output. [default: %default, max: {}]".format(generic.VERBOSITY_MAX)) opts, args = opt_parser.parse_args(argv) generic.set_verbosity(0 if opts.quiet else opts.verbosity) generic.set_cache_root_dir(opts.cache_dir) spritecache.keep_orphaned = opts.keep_orphaned opts.outputfile_given = (opts.grf_filename or opts.nfo_filename or opts.nml_filename or opts.dep_filename or opts.outputs) if not args: if not opts.outputfile_given: opt_parser.print_help() sys.exit(2) input_filename = None # Output filenames, but no input -> use stdin. elif len(args) > 1: opt_parser.error("Error: only a single nml file can be read per run") else: input_filename = args[0] if not os.access(input_filename, os.R_OK): raise generic.ScriptError('Input file "{}" does not exist'.format(input_filename)) return opts, input_filename def main(argv): global developmode opts, input_filename = parse_cli(argv) if opts.stack: developmode = True grfstrings.read_extra_commands(opts.custom_tags) generic.print_progress("Reading lang ...") grfstrings.read_lang_files(opts.lang_dir, opts.default_lang) generic.clear_progress() # We have to do the dependency check first or we might later have # more targets than we asked for outputs = [] if opts.dep_check: # First make sure we have a file to output the dependencies to: dep_filename = opts.dep_filename if dep_filename is None and opts.grf_filename is not None: dep_filename = filename_output_from_input(opts.grf_filename, ".dep") if dep_filename is None and input_filename is not None: dep_filename = filename_output_from_input(input_filename, ".dep") if dep_filename is None: raise generic.ScriptError("-M requires a dependency file either via -MF, an input filename or a valid output via --grf") # Now make sure we have a file which is the target for the dependencies: depgrf_filename = opts.depgrf_filename if depgrf_filename is None and opts.grf_filename is not None: depgrf_filename = opts.grf_filename if depgrf_filename is None and input_filename is not None: depgrf_filename = filename_output_from_input(input_filename, ".grf") if depgrf_filename is None: raise generic.ScriptError("-M requires either a target grf file via -MT, an input filename or a valid output via --grf") # Only append the dependency check to the output targets when we have both, # a target grf and a file to write to if dep_filename is not None and depgrf_filename is not None: outputs.append(output_dep.OutputDEP(dep_filename, depgrf_filename)) if input_filename is None: input = sys.stdin else: input = codecs.open(generic.find_file(input_filename), 'r', 'utf-8') # Only append an output grf name, if no ouput is given, also not implicitly via -M if not opts.outputfile_given and not outputs: opts.grf_filename = filename_output_from_input(input_filename, ".grf") # Translate the 'common' palette names as used by OpenTTD to the traditional ones being within NML's code if opts.forced_palette == 'DOS': opts.forced_palette = 'DEFAULT' elif opts.forced_palette == 'WIN': opts.forced_palette = 'LEGACY' if opts.grf_filename: outputs.append(output_grf.OutputGRF(opts.grf_filename)) if opts.nfo_filename: outputs.append(output_nfo.OutputNFO(opts.nfo_filename, opts.start_sprite_num)) if opts.nml_filename: outputs.append(output_nml.OutputNML(opts.nml_filename)) for output in opts.outputs: outroot, outext = os.path.splitext(output) outext = outext.lower() if outext == '.grf': outputs.append(output_grf.OutputGRF(output)) elif outext == '.nfo': outputs.append(output_nfo.OutputNFO(output, opts.start_sprite_num)) elif outext == '.nml': outputs.append(output_nml.OutputNML(output)) elif outext == '.dep': outputs.append(output_dep.OutputDEP(output, opts.grf_filename)) else: generic.print_error("Unknown output format {}".format(outext)) sys.exit(2) ret = nml(input, input_filename, opts.debug, outputs, opts.start_sprite_num, opts.compress, opts.crop, not opts.no_cache, opts.forced_palette, opts.md5_filename) input.close() sys.exit(ret) def filename_output_from_input(name, ext): return os.path.splitext(name)[0] + ext def nml(inputfile, input_filename, output_debug, outputfiles, start_sprite_num, compress_grf, crop_sprites, enable_cache, forced_palette, md5_filename): """ Compile an NML file. @param inputfile: File handle associated with the input file. @type inputfile: C{File} @param input_filename: Filename of the input file, C{None} if receiving from L{sys.stdin} @type input_filename: C{str} or C{None} @param outputfiles: Output streams to write to. @type outputfiles: C{List} of L{output_base.OutputBase} @param start_sprite_num: Number of the first sprite. @type start_sprite_num: C{int} @param compress_grf: Enable GRF sprite compression. @type compress_grf: C{bool} @param crop_sprites: Enable sprite cropping. @type crop_sprites: C{bool} @param enable_cache: Enable sprite cache. @type enable_cache: C{bool} @param forced_palette: Palette to use for the file. @type forced_palette: C{str} @param md5_filename: Filename to use for writing the md5 sum of the grf file. C{None} if the file should not be written. @type md5_filename: C{str} or C{None} """ generic.OnlyOnce.clear() generic.print_progress("Reading ...") try: script = inputfile.read() except UnicodeDecodeError as ex: raise generic.ScriptError('Input file is not utf-8 encoded: {}'.format(ex)) # Strip a possible BOM script = script.lstrip(str(codecs.BOM_UTF8, "utf-8")) if script.strip() == "": generic.print_error("Empty input file") return 4 generic.print_progress("Init parser ...") nml_parser = parser.NMLParser() if input_filename is None: input_filename = 'input' generic.print_progress("Parsing ...") result = nml_parser.parse(script, input_filename) result.validate([]) if output_debug > 0: result.debug_print(0) for outputfile in outputfiles: if isinstance(outputfile, output_nml.OutputNML): outputfile.open() outputfile.write(str(result)) outputfile.close() generic.print_progress("Preprocessing ...") result.register_names() result.pre_process() tmp_actions = result.get_action_list() generic.print_progress("Generating actions ...") actions = [] for action in tmp_actions: if isinstance(action, action1.SpritesetCollection): actions.extend(action.get_action_list()) else: actions.append(action) actions.extend(action11.get_sound_actions()) generic.print_progress("Assigning Action2 registers ...") action8_index = -1 for i in range(len(actions) - 1, -1, -1): if isinstance(actions[i], (action2var.Action2Var, action2layout.Action2Layout)): actions[i].resolve_tmp_storage() elif isinstance(actions[i], action8.Action8): action8_index = i generic.print_progress("Generating strings ...") if action8_index != -1: lang_actions = [] # Add plural/gender/case tables for lang_pair in grfstrings.langs: lang_id, lang = lang_pair lang_actions.extend(action0.get_language_translation_tables(lang)) # Add global strings lang_actions.extend(action4.get_global_string_actions()) actions = actions[:action8_index + 1] + lang_actions + actions[action8_index + 1:] generic.print_progress("Collecting real sprites ...") # Collect all sprite files, and put them into buckets of same image and mask files sprite_files = dict() for action in actions: if isinstance(action, real_sprite.RealSpriteAction): for sprite in action.sprite_list: if sprite.is_empty: continue sprite.validate_size() file = sprite.file if file is not None: file = file.value mask_file = sprite.mask_file if mask_file is not None: mask_file = mask_file.value key = (file, mask_file) sprite_files.setdefault(key, []).append(sprite) # Check whether we can terminate sprite processing prematurely for # dependency checks skip_sprite_processing = True for outputfile in outputfiles: if isinstance(outputfile, output_dep.OutputDEP): outputfile.open() for f in sprite_files: if f[0] is not None: outputfile.write(f[0]) if f[1] is not None: outputfile.write(f[1]) outputfile.close() skip_sprite_processing &= outputfile.skip_sprite_checks() if skip_sprite_processing: generic.clear_progress() return 0 if not Image and len(sprite_files) > 0: generic.print_error("PIL (python-imaging) wasn't found, no support for using graphics") sys.exit(3) generic.print_progress("Checking palette of source images ...") used_palette = forced_palette last_file = None for f_pair in sprite_files: # Palette is defined by mask_file, if present. Otherwise by the main file. f = f_pair[1] if f is None: f = f_pair[0] try: im = Image.open(generic.find_file(f)) except IOError as ex: raise generic.ImageError(str(ex), f) if im.mode != "P": continue pal = palette.validate_palette(im, f) if forced_palette != "ANY" and pal != forced_palette and not (forced_palette == "DEFAULT" and pal == "LEGACY"): raise generic.ImageError("Image has '{}' palette, but you forced the '{}' palette".format(pal, used_palette), f) if used_palette == "ANY": used_palette = pal elif pal != used_palette: if used_palette in ("LEGACY", "DEFAULT") and pal in ("LEGACY", "DEFAULT"): used_palette = "DEFAULT" else: raise generic.ImageError("Image has '{}' palette, but \"{}\" has the '{}' palette".format(pal, last_file, used_palette), f) last_file = f palette_bytes = {"LEGACY": "W", "DEFAULT": "D", "ANY": "A"} if used_palette in palette_bytes: grf.set_palette_used(palette_bytes[used_palette]) encoder = None for outputfile in outputfiles: outputfile.palette = used_palette # used by RecolourSpriteAction if isinstance(outputfile, output_grf.OutputGRF): if encoder is None: encoder = spriteencoder.SpriteEncoder(compress_grf, crop_sprites, enable_cache, used_palette) outputfile.encoder = encoder generic.clear_progress() # Read all image data, compress, and store in sprite cache if encoder is not None: encoder.open(sprite_files) #If there are any 32bpp sprites hint to openttd that we'd like a 32bpp blitter if alt_sprites.any_32bpp_sprites: grf.set_preferred_blitter("3") generic.print_progress("Linking actions ...") if action8_index != -1: actions = [sprite_count.SpriteCountAction(len(actions))] + actions for idx, action in enumerate(actions): num = start_sprite_num + idx action.prepare_output(num) # Processing finished, print some statistics action0.print_stats() actionF.print_stats() action7.print_stats() action1.print_stats() action2.print_stats() action6.print_stats() grf.print_stats() global_constants.print_stats() action4.print_stats() action11.print_stats() generic.print_progress("Writing output ...") md5 = None for outputfile in outputfiles: if isinstance(outputfile, output_grf.OutputGRF): outputfile.open() for action in actions: action.write(outputfile) outputfile.close() md5 = outputfile.get_md5() if isinstance(outputfile, output_nfo.OutputNFO): outputfile.open() for action in actions: action.write(outputfile) outputfile.close() if md5 is not None and md5_filename is not None: with open(md5_filename, 'w', encoding="utf-8") as f: f.write(md5 + '\n') if encoder is not None: encoder.close() generic.clear_progress() return 0 def run(): try: main(sys.argv[1:]) except generic.ScriptError as ex: generic.print_error(str(ex)) if developmode: raise # Reraise exception in developmode sys.exit(1) except SystemExit as ex: raise except KeyboardInterrupt as ex: generic.print_error('Application forcibly terminated by user.') if developmode: raise # Reraise exception in developmode sys.exit(1) except Exception as ex: # Other/internal error. if developmode: raise # Reraise exception in developmode # User mode: print user friendly error message. ex_msg = str(ex) if len(ex_msg) > 0: ex_msg = '"{}"'.format(ex_msg) traceback = sys.exc_info()[2] # Walk through the traceback object until we get to the point where the exception happened. while traceback.tb_next is not None: traceback = traceback.tb_next lineno = traceback.tb_lineno frame = traceback.tb_frame code = frame.f_code filename = code.co_filename name = code.co_name del traceback # Required according to Python docs. ex_data = {'class' : ex.__class__.__name__, 'version' : version, 'msg' : ex_msg, 'cli' : sys.argv, 'loc' : 'File "{}", line {:d}, in {}'.format(filename, lineno, name)} msg = "nmlc: An internal error has occurred:\n" \ "nmlc-version: {version}\n" \ "Error: ({class}) {msg}.\n" \ "Command: {cli}\n" \ "Location: {loc}\n".format(**ex_data) generic.print_error(msg) sys.exit(1) sys.exit(0) if __name__ == "__main__": run() nml-0.4.4/nml/spriteencoder.py0000644000567200056720000005134212643457545017501 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import array from nml import generic, palette, lz77, spritecache from nml.actions import real_sprite try: from PIL import Image except ImportError: try: import Image except ImportError: pass # Some constants for the 'info' byte INFO_RGB = 1 INFO_ALPHA = 2 INFO_PAL = 4 INFO_TILE = 8 INFO_NOCROP = 0x40 def get_bpp(info): bpp = 0 if (info & INFO_RGB) != 0: bpp += 3 if (info & INFO_ALPHA) != 0: bpp += 1 if (info & INFO_PAL) != 0: bpp += 1 return bpp def has_transparency(info): return (info & (INFO_ALPHA | INFO_PAL)) != 0 def transparency_offset(info): """ Determine which byte within a pixel should be 0 for a pixel to be transparent """ assert has_transparency(info) # There is an alpha or palette component, or both, else no transparency at all # Pixel is transparent if either: # - alpha channel present and 0 # - palette present and 0, and alpha not present # In either case, the pixel is transparent, # if byte 3 (with RGB present) or byte 0 (without RGB) is 0 if (info & INFO_RGB) != 0: return 3 else: return 0 class SpriteEncoder(object): """ Algorithms for cropping and compressing sprites. That is encoding source images into GRF sprites. @ivar compress_grf: Compress sprites. @type compress_grf: C{bool} @ivar crop_sprites: Crop sprites if possible. @type crop_sprites: C{bool} @ivar enable_cache: Read/write cache from/to disk. @type enable_cache: C{bool} @ivar palette: Palette for encoding, see L{palette.palette_name}. @type palette: C{str} @ivar cached_image_files: Currently opened source image files. @type cached_image_files: C{dict} mapping C{str} to C{Image} """ def __init__(self, compress_grf, crop_sprites, enable_cache, palette): self.compress_grf = compress_grf self.crop_sprites = crop_sprites self.enable_cache = enable_cache self.palette = palette self.sprite_cache = spritecache.SpriteCache("") self.cached_image_files = {} def open(self, sprite_files): """ Start the encoder, read caches, and stuff. @param sprite_files: List of sprites per source image file. @type sprite_files: C{dict} that maps (C{tuple} of C{str}) to (C{RealSprite}) """ num_sprites = sum(len(sprite_list) for sprite_list in sprite_files.values()) generic.print_progress("Encoding ...") num_cached = 0 num_dup = 0 num_enc = 0 num_orphaned = 0 count_sprites = 0 for sources, sprite_list in sprite_files.items(): # Iterate over sprites grouped by source image file. # - Open source files only once. (speed) # - Do not keep files around for long. (memory) source_name = "_".join(src for src in sources if src is not None) local_cache = spritecache.SpriteCache(generic.get_cache_file(sources, "")) if self.enable_cache: local_cache.read_cache() for sprite_info in sprite_list: count_sprites += 1 generic.print_progress("Encoding {}/{}: {}".format(count_sprites, num_sprites, source_name), incremental = True) cache_key = sprite_info.get_cache_key(self.crop_sprites) cache_item = local_cache.get_item(cache_key, self.palette) in_use = False in_old_cache = False if cache_item is not None: # Write a sprite from the cached data compressed_data, info_byte, crop_rect, pixel_stats, in_old_cache, in_use = cache_item if in_use: num_dup += 1 else: num_cached += 1 else: size_x, size_y, xoffset, yoffset, compressed_data, info_byte, crop_rect, pixel_stats = self.encode_sprite(sprite_info) num_enc += 1 # Store sprite in cache, unless already up-to-date if not in_use: cache_item = (compressed_data, info_byte, crop_rect, pixel_stats, in_old_cache, True) local_cache.add_item(cache_key, self.palette, cache_item) # Delete all files from dictionary to free memory self.cached_image_files.clear() num_orphaned += local_cache.count_orphaned() # Only write cache if compression is enabled. Uncompressed data is not worth to be cached. if self.enable_cache and self.compress_grf: local_cache.write_cache() # Transfer data to global cache for later usage self.sprite_cache.cached_sprites.update(local_cache.cached_sprites) generic.print_progress("Encoding ...", incremental = True) generic.clear_progress() generic.print_info("{} sprites, {} cached, {} orphaned, {} duplicates, {} newly encoded ({})".format(num_sprites, num_cached, num_orphaned, num_dup, num_enc, "native" if lz77.is_native else "python")) def close(self): """ Close the encoder, validate data, write caches, and stuff. """ pass def get(self, sprite_info): """ Get encoded sprite date, either from cache, or new. @param sprite_info: Sprite meta data @type sprite_info: C{RealSprite} """ # Try finding the file in the cache # open() should have put all sprites into it. cache_key = sprite_info.get_cache_key(self.crop_sprites) cache_item = self.sprite_cache.get_item(cache_key, self.palette) assert cache_item is not None compressed_data, info_byte, crop_rect, pixel_stats, in_old_cache, in_use = cache_item size_x = sprite_info.xsize.value size_y = sprite_info.ysize.value xoffset = sprite_info.xrel.value yoffset = sprite_info.yrel.value if cache_key[-1]: size_x, size_y, xoffset, yoffset = self.recompute_offsets(size_x, size_y, xoffset, yoffset, crop_rect) warnings = [] total = pixel_stats.get('total', 0) if total > 0: if cache_key[0] is not None: image_32_pos = generic.PixelPosition(cache_key[0], cache_key[1][0], cache_key[1][1]) alpha = pixel_stats.get('alpha', 0) if alpha > 0 and (sprite_info.flags.value & real_sprite.FLAG_NOALPHA) != 0: warnings.append("{}: {:d} of {:d} pixels ({:d}%) are semi-transparent".format(str(image_32_pos), alpha, total, alpha * 100 // total)) if cache_key[2] is not None: image_8_pos = generic.PixelPosition(cache_key[2], cache_key[3][0], cache_key[3][1]) white = pixel_stats.get('white', 0) anim = pixel_stats.get('anim', 0) if white > 0 and (sprite_info.flags.value & real_sprite.FLAG_WHITE) == 0: warnings.append("{}: {:d} of {:d} pixels ({:d}%) are pure white".format(str(image_8_pos), white, total, white * 100 // total)) if anim > 0 and (sprite_info.flags.value & real_sprite.FLAG_ANIM) == 0: warnings.append("{}: {:d} of {:d} pixels ({:d}%) are animated".format(str(image_8_pos), anim, total, anim * 100 // total)) return (size_x, size_y, xoffset, yoffset, compressed_data, info_byte, crop_rect, warnings) def open_image_file(self, filename): """ Obtain a handle to an image file @param filename: Name of the file @type filename: C{str} @return: Image file @rtype: L{Image} """ if filename in self.cached_image_files: im = self.cached_image_files[filename] else: im = Image.open(generic.find_file(filename)) self.cached_image_files[filename] = im return im def encode_sprite(self, sprite_info): """ Crop and compress a real sprite. @param sprite_info: Sprite meta data @type sprite_info: C{RealSprite} @return: size_x, size_y, xoffset, yoffset, compressed_data, info_byte, crop_rect, pixel_stats @rtype: C{tuple} """ filename_8bpp = None filename_32bpp = None if sprite_info.bit_depth == 8: filename_8bpp = sprite_info.file else: filename_32bpp = sprite_info.file filename_8bpp = sprite_info.mask_file # Get initial info_byte and dimensions from sprite_info. # These values will be changed depending on cropping and compression. info_byte = INFO_NOCROP if (sprite_info.flags.value & real_sprite.FLAG_NOCROP) != 0 else 0 size_x = sprite_info.xsize.value size_y = sprite_info.ysize.value xoffset = sprite_info.xrel.value yoffset = sprite_info.yrel.value im, mask_im = None, None im_mask_pal = None # Select region of image bounded by x/ypos and x/ysize x = sprite_info.xpos.value y = sprite_info.ypos.value if sprite_info.bit_depth == 8 or sprite_info.mask_pos is None: mask_x, mask_y = x, y else: mask_x = sprite_info.mask_pos[0].value mask_y = sprite_info.mask_pos[1].value pixel_stats = { 'total': size_x * size_y, 'alpha': 0, 'white': 0, 'anim': 0 } # Read and validate image data if filename_32bpp is not None: im = self.open_image_file(filename_32bpp.value) if im.mode not in ("RGB", "RGBA"): pos = generic.build_position(sprite_info.poslist) raise generic.ImageError("32bpp image is not a full colour RGB(A) image.", filename_32bpp.value, pos) info_byte |= INFO_RGB if im.mode == "RGBA": info_byte |= INFO_ALPHA (im_width, im_height) = im.size if x < 0 or y < 0 or x + size_x > im_width or y + size_y > im_height: pos = generic.build_position(sprite_info.poslist) raise generic.ScriptError("Read beyond bounds of image file '{}'".format(filename_32bpp.value), pos) sprite = im.crop((x, y, x + size_x, y + size_y)) rgb_sprite_data = sprite.tobytes() if (info_byte & INFO_ALPHA) != 0: # Check for half-transparent pixels (not valid for ground sprites) pixel_stats['alpha'] = sum(0x00 < p < 0xFF for p in rgb_sprite_data[3::4]) if filename_8bpp is not None: mask_im = self.open_image_file(filename_8bpp.value) if mask_im.mode != "P": pos = generic.build_position(sprite_info.poslist) raise generic.ImageError("8bpp image does not have a palette", filename_8bpp.value, pos) im_mask_pal = palette.validate_palette(mask_im, filename_8bpp.value) info_byte |= INFO_PAL (im_width, im_height) = mask_im.size if mask_x < 0 or mask_y < 0 or mask_x + size_x > im_width or mask_y + size_y > im_height: pos = generic.build_position(sprite_info.poslist) raise generic.ScriptError("Read beyond bounds of image file '{}'".format(filename_8bpp.value), pos) mask_sprite = mask_im.crop((mask_x, mask_y, mask_x + size_x, mask_y + size_y)) mask_sprite_data = self.palconvert(mask_sprite.tobytes(), im_mask_pal) # Check for white pixels; those that cause "artefacts" when shading pixel_stats['white'] = sum(p == 255 for p in mask_sprite_data) # Check for palette animation colours if self.palette == 'DEFAULT': pixel_stats['anim'] = sum(0xE3 <= p <= 0xFE for p in mask_sprite_data) else: pixel_stats['anim'] = sum(0xD9 <= p <= 0xF4 for p in mask_sprite_data) # Compose pixel information in an array of bytes sprite_data = array.array('B') if (info_byte & INFO_RGB) != 0 and (info_byte & INFO_PAL) != 0: mask_data = array.array('B', mask_sprite_data) # Convert to numeric rgb_data = array.array('B', rgb_sprite_data) if (info_byte & INFO_ALPHA) != 0: for i in range(len(mask_sprite_data)): sprite_data.extend(rgb_data[4*i:4*(i+1)]) sprite_data.append(mask_data[i]) else: for i in range(len(mask_sprite_data)): sprite_data.extend(rgb_data[3*i:3*(i+1)]) sprite_data.append(mask_data[i]) elif (info_byte & INFO_RGB) != 0: sprite_data.frombytes(rgb_sprite_data) else: sprite_data.frombytes(mask_sprite_data) bpp = get_bpp(info_byte) assert len(sprite_data) == size_x * size_y * bpp if self.crop_sprites and ((info_byte & INFO_NOCROP) == 0): sprite_data, crop_rect = self.crop_sprite(sprite_data, size_x, size_y, info_byte, bpp) size_x, size_y, xoffset, yoffset = self.recompute_offsets(size_x, size_y, xoffset, yoffset, crop_rect) else: crop_rect = None assert len(sprite_data) == size_x * size_y * bpp compressed_data = self.sprite_compress(sprite_data) # Try tile compression, and see if it results in a smaller file size tile_data = self.sprite_encode_tile(size_x, size_y, sprite_data, info_byte, bpp) if tile_data is not None: tile_compressed_data = self.sprite_compress(tile_data) # Tile compression adds another 4 bytes for the uncompressed chunked data in the header if len(tile_compressed_data) + 4 < len(compressed_data): info_byte |= INFO_TILE data_len = len(tile_data) compressed_data = array.array('B') compressed_data.append(data_len & 0xFF) compressed_data.append((data_len >> 8) & 0xFF) compressed_data.append((data_len >> 16) & 0xFF) compressed_data.append((data_len >> 24) & 0xFF) compressed_data.extend(tile_compressed_data) return (size_x, size_y, xoffset, yoffset, compressed_data, info_byte, crop_rect, pixel_stats) def fakecompress(self, data): i = 0 output = array.array('B') length = len(data) while i < length: n = min(length - i, 127) output.append(n) output.extend(data[i:i+n]) i += n return output def sprite_compress(self, data): if self.compress_grf: stream = lz77.encode(data) else: stream = self.fakecompress(data) return stream def sprite_encode_tile(self, size_x, size_y, data, info, bpp, long_format = False): long_chunk = size_x > 256 # There are basically four different encoding configurations here, # but just two variables. If the width of the sprite is more than # 256, then the chunk could be 'out of bounds' and thus the long # chunk format is used. If the sprite is more than 65536 bytes, # then the offsets might not fit and the long format method is # used. The latter is enabled via recursion if it's needed. if not has_transparency(info): return None trans_offset = transparency_offset(info) max_chunk_len = 0x7fff if long_chunk else 0x7f line_offset_size = 4 if long_format else 2 # Whether to use 2 or 4 bytes in the list of line offsets output = array.array('B', [0] * (line_offset_size * size_y)) for y in range(size_y): # Write offset in the correct place, in little-endian format offset = len(output) output[y*line_offset_size] = offset & 0xFF output[y*line_offset_size + 1] = (offset >> 8) & 0xFF if long_format: output[y*line_offset_size + 2] = (offset >> 16) & 0xFF output[y*line_offset_size + 3] = (offset >> 24) & 0xFF line_start = y*size_x*bpp line_parts = [] x1 = 0 while True: # Skip transparent pixels while x1 < size_x and data[line_start + x1*bpp + trans_offset] == 0: x1 += 1 if x1 == size_x: # End-of-line reached break # Grab as many non-transparent pixels as possible, but not without x2-x1 going out of bounds # Only stop the chunk when encountering 3 consecutive transparent pixels x2 = x1 + 1 while x2 - x1 < max_chunk_len and ( (x2 < size_x and data[line_start + x2*bpp + trans_offset] != 0) or (x2 + 1 < size_x and data[line_start + (x2+1)*bpp + trans_offset] != 0) or (x2 + 2 < size_x and data[line_start + (x2+2)*bpp + trans_offset] != 0)): x2 += 1 line_parts.append((x1, x2)) x1 = x2 if len(line_parts) == 0: # Completely transparent line if long_chunk: output.extend((0, 0x80, 0, 0)) else: output.extend((0x80, 0)) continue for idx, part in enumerate(line_parts): x1, x2 = part last_mask = 0x80 if idx == len(line_parts) - 1 else 0 chunk_len = x2 - x1 if long_chunk: output.extend((chunk_len & 0xFF, (chunk_len >> 8) | last_mask, x1 & 0xFF, x1 >> 8)) else: output.extend((chunk_len | last_mask, x1)) output.extend(data[line_start + x1*bpp : line_start + x2*bpp]) if len(output) > 65535 and not long_format: # Recurse into the long format if that's possible. return self.sprite_encode_tile(size_x, size_y, data, info, bpp, True) return output def recompute_offsets(self, size_x, size_y, xoffset, yoffset, crop_rect): # Recompute sizes and offsets after cropping a sprite left, right, top, bottom = crop_rect size_x -= left + right size_y -= top + bottom xoffset += left yoffset += top return size_x, size_y, xoffset, yoffset def crop_sprite(self, data, size_x, size_y, info, bpp): left, right, top, bottom = 0, 0, 0, 0 if not has_transparency(info): return (data, (left, right, top, bottom)) trans_offset = transparency_offset(info) line_size = size_x * bpp # size (no. of bytes) of a scan line data_size = len(data) #Crop the top of the sprite while size_y > 1 and not any(data[line_size * top + trans_offset : line_size * (top+1) : bpp]): top += 1 size_y -= 1 #Crop the bottom of the sprite while size_y > 1 and not any(data[data_size - line_size * (bottom+1) + trans_offset : data_size - line_size * bottom : bpp]): # Don't use negative indexing, it breaks for the last line (where you'd need index 0) bottom += 1 size_y -= 1 #Modify data by removing top/bottom data = data[line_size * top : data_size - line_size * bottom] #Crop the left of the sprite while size_x > 1 and not any(data[left * bpp + trans_offset : : line_size]): left += 1 size_x -= 1 #Crop the right of the sprite while size_x > 1 and not any(data[line_size - (right+1) * bpp + trans_offset : : line_size]): right += 1 size_x -= 1 #Removing left/right data is not easily done by slicing #Best to create a new array if left + right > 0: new_data = array.array('B') for y in range(0, size_y): a = data[y*line_size + left*bpp : (y+1)*line_size - right*bpp] new_data.extend(a) data = new_data return (data, (left, right, top, bottom)) def palconvert(self, sprite_str, orig_pal): if orig_pal == "LEGACY" and self.palette == "DEFAULT": return sprite_str.translate(real_sprite.translate_w2d) else: return sprite_str nml-0.4.4/nml/output_grf.py0000644000567200056720000001655112643457545017034 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import hashlib, os from nml import generic, output_base, grfstrings, spriteencoder class OutputGRF(output_base.BinaryOutputBase): def __init__(self, filename): output_base.BinaryOutputBase.__init__(self, filename) self.encoder = None self.sprite_output = output_base.BinaryOutputBase(filename + ".sprite.tmp") self.md5 = hashlib.md5() # sprite_num is deliberately off-by-one because it is used as an # id between data and sprite section. For the sprite section an id # of 0 is invalid (means end of sprites), and for a non-NewGRF GRF # the first sprite is a real sprite. self.sprite_num = 1 def open_file(self): # Remove / unlink the file, most useful for linux systems # See also issue #4165 # If the file happens to be in use or non-existant, ignore try: os.unlink(self.filename) except OSError: pass return open(self.filename, 'wb') def get_md5(self): return self.md5.hexdigest() def assemble_file(self, real_file): #add end-of-chunks self.in_sprite = True self.print_dword(0) self.in_sprite = False self.sprite_output.in_sprite = True self.sprite_output.print_dword(0) self.sprite_output.in_sprite = False #add header header = bytearray([0x00, 0x00, ord('G'), ord('R'), ord('F'), 0x82, 0x0D, 0x0A, 0x1A, 0x0A]) size = len(self.file) + 1 header.append(size & 0xFF) header.append((size >> 8) & 0xFF) header.append((size >> 16) & 0xFF) header.append(size >> 24) header.append(0) #no compression header_str = bytes(header) real_file.write(header_str) self.md5.update(header_str) #add data section, and then the sprite section real_file.write(self.file) self.md5.update(self.file) real_file.write(self.sprite_output.file) def open(self): output_base.BinaryOutputBase.open(self) self.sprite_output.open() def close(self): output_base.BinaryOutputBase.close(self) self.sprite_output.discard() def _print_utf8(self, char, stream): for c in chr(char).encode('utf8'): stream.print_byte(c) def print_string(self, value, final_zero = True, force_ascii = False, stream = None): if stream is None: stream = self if not grfstrings.is_ascii_string(value): if force_ascii: raise generic.ScriptError("Expected ascii string but got a unicode string") stream.print_byte(0xC3) stream.print_byte(0x9E) i = 0 while i < len(value): if value[i] == '\\': if value[i+1] in ('\\', '"'): stream.print_byte(ord(value[i+1])) i += 2 elif value[i+1] == 'U': self._print_utf8(int(value[i+2:i+6], 16), stream) i += 6 else: stream.print_byte(int(value[i+1:i+3], 16)) i += 3 else: self._print_utf8(ord(value[i]), stream) i += 1 if final_zero: stream.print_byte(0) def comment(self, msg): pass def start_sprite(self, size, type = 0xFF): if type == 0xFF: output_base.BinaryOutputBase.start_sprite(self, size + 5) self.print_dword(size) self.print_byte(type) elif type == 0xFD: # Real sprite, this means no data is written to the data section # This call is still needed to open 'output mode' assert size == 0 output_base.BinaryOutputBase.start_sprite(self, 9) self.print_dword(4) self.print_byte(0xfd) self.print_dword(self.sprite_num) else: assert False, "Unexpected info byte encountered." def print_sprite(self, sprite_list): """ @param sprite_list: List of non-empty real sprites for various bit depths / zoom levels @type sprite_list: C{list} of L{RealSprite} """ self.start_sprite(0, 0xFD) for sprite in sprite_list: self.print_single_sprite(sprite) self.end_sprite() 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 print_empty_realsprite(self): self.start_sprite(1) self.print_byte(0) self.end_sprite() def wsprite_header(self, size_x, size_y, size, xoffset, yoffset, info, zoom_level): self.sprite_output.print_dword(self.sprite_num) self.sprite_output.print_dword(size + 10) self.sprite_output.print_byte(info) self.sprite_output.print_byte(zoom_level) self.sprite_output.print_word(size_y) self.sprite_output.print_word(size_x) self.sprite_output.print_word(xoffset) self.sprite_output.print_word(yoffset) def print_named_filedata(self, filename): name = os.path.split(filename)[1] size = os.path.getsize(filename) self.start_sprite(0, 0xfd) self.sprite_output.start_sprite(8 + 3 + len(name) + 1 + size) self.sprite_output.print_dword(self.sprite_num) self.sprite_output.print_dword(3 + len(name) + 1 + size) self.sprite_output.print_byte(0xff) self.sprite_output.print_byte(0xff) self.sprite_output.print_byte(len(name)) self.print_string(name, force_ascii = True, final_zero = True, stream = self.sprite_output) # ASCII filenames seems sufficient. fp = open(generic.find_file(filename), 'rb') while True: data = fp.read(1024) if len(data) == 0: break for d in data: self.sprite_output.print_byte(d) fp.close() self.sprite_output.end_sprite(); self.end_sprite() def end_sprite(self): output_base.BinaryOutputBase.end_sprite(self) self.sprite_num += 1 nml-0.4.4/nml/lz77.py0000644000567200056720000000513012643457545015430 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import array def _encode(data): """ GRF compression algorithm. @param data: Uncompressed data. @type data: C{str}, C{bytearray}, C{bytes} or similar. @return: Compressed data. @rtype: C{bytearray} """ stream = data.tostring() position = 0 output = array.array('B') literal_bytes = array.array('B') stream_len = len(stream) while position < stream_len: overlap_len = 0 start_pos = max(0, position - (1 << 11) + 1) # Loop through the lookahead buffer. for i in range(3, min(stream_len - position + 1, 16)): # Set pattern to find the longest match. pattern = stream[position:position+i] # Find the pattern match in the window. result = stream.find(pattern, start_pos, position) # If match failed, we've found the longest. if result < 0: break p = position - result overlap_len = i start_pos = result if overlap_len > 0: if len(literal_bytes) > 0: output.append(len(literal_bytes)) output.extend(literal_bytes) literal_bytes = array.array('B') val = ((-overlap_len) << 3) & 0xFF | (p >> 8) output.append(val) output.append(p & 0xFF) position += overlap_len else: literal_bytes.append(stream[position]) if len(literal_bytes) == 0x80: output.append(0) output.extend(literal_bytes) literal_bytes = array.array('B') position += 1 if len(literal_bytes) > 0: output.append(len(literal_bytes)) output.extend(literal_bytes) return output """ True if the encoding is provided by a native module. Used for verbose information. """ is_native = False try: from nml_lz77 import encode is_native = True except ImportError: encode = _encode nml-0.4.4/nml/global_constants.py0000644000567200056720000015466612643457545020204 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, nmlop constant_numbers = { #climates 'CLIMATE_TEMPERATE' : 0, 'CLIMATE_ARCTIC' : 1, 'CLIMATE_TROPIC' : 2, 'CLIMATE_TROPICAL' : 2, 'CLIMATE_TOYLAND' : 3, 'ABOVE_SNOWLINE' : 11, # Only for house property available_mask 'NO_CLIMATE' : 0x00, 'ALL_CLIMATES' : 0x0F, #never expire 'VEHICLE_NEVER_EXPIRES' : 0xFF, #default_cargo 'DEFAULT_CARGO_FIRST_REFITTABLE' : 0xFF, #cargo classes 'CC_PASSENGERS' : 0, 'CC_MAIL' : 1, 'CC_EXPRESS' : 2, 'CC_ARMOURED' : 3, 'CC_BULK' : 4, 'CC_PIECE_GOODS' : 5, 'CC_LIQUID' : 6, 'CC_REFRIGERATED' : 7, 'CC_HAZARDOUS' : 8, 'CC_COVERED' : 9, 'CC_OVERSIZED' : 10, 'CC_POWDERIZED' : 11, 'CC_NON_POURABLE' : 12, 'CC_NEO_BULK' : 12, 'CC_SPECIAL' : 15, 'NO_CARGO_CLASS' : 0, 'ALL_NORMAL_CARGO_CLASSES' : 0x1FFF, 'ALL_CARGO_CLASSES' : 0x9FFF, #engine class 'ENGINE_CLASS_STEAM' : 0x00, 'ENGINE_CLASS_DIESEL' : 0x08, 'ENGINE_CLASS_ELECTRIC' : 0x28, 'ENGINE_CLASS_MONORAIL' : 0x32, 'ENGINE_CLASS_MAGLEV' : 0x38, #running costs 'RUNNING_COST_STEAM' : 0x4C30, 'RUNNING_COST_DIESEL' : 0x4C36, 'RUNNING_COST_ELECTRIC' : 0x4C3C, 'RUNNING_COST_ROADVEH' : 0x4C48, 'RUNNING_COST_NONE' : 0x0000, #shorten factor 'SHORTEN_TO_8_8' : 0x00, 'SHORTEN_TO_7_8' : 0x01, 'SHORTEN_TO_6_8' : 0x02, 'SHORTEN_TO_5_8' : 0x03, 'SHORTEN_TO_4_8' : 0x04, 'SHORTEN_TO_3_8' : 0x05, 'SHORTEN_TO_2_8' : 0x06, 'SHORTEN_TO_1_8' : 0x07, #vehicle length 'VEHICLE_LENGTH' : 8, #visual effect 'VISUAL_EFFECT_DEFAULT' : 0x00, 'VISUAL_EFFECT_STEAM' : 0x10, 'VISUAL_EFFECT_DIESEL' : 0x20, 'VISUAL_EFFECT_ELECTRIC' : 0x30, 'VISUAL_EFFECT_DISABLE' : 0x40, #effect spawn model 'EFFECT_SPAWN_MODEL_NONE' : 0x40, 'EFFECT_SPAWN_MODEL_STEAM' : 0x41, 'EFFECT_SPAWN_MODEL_DIESEL' : 0x42, 'EFFECT_SPAWN_MODEL_ELECTRIC': 0x43, #visual effect / effect spawn model 'ENABLE_WAGON_POWER' : 0x00, 'DISABLE_WAGON_POWER' : 0x80, #create_effect 'EFFECT_SPRITE_STEAM' : 0xF1, 'EFFECT_SPRITE_DIESEL' : 0xF2, 'EFFECT_SPRITE_ELECTRIC' : 0xF3, 'EFFECT_SPRITE_AIRCRAFT_BREAKDOWN_SMOKE' : 0xFA, 'EFFECT_SPRITE_NONE' : 0xFF, 'CB_RESULT_CREATE_EFFECT_CENTER' : 0x2000, 'CB_RESULT_CREATE_EFFECT_NO_ROTATION' : 0x4000, #train misc flags 'TRAIN_FLAG_TILT' : 0, 'TRAIN_FLAG_2CC' : 1, 'TRAIN_FLAG_MU' : 2, 'TRAIN_FLAG_FLIP' : 3, 'TRAIN_FLAG_AUTOREFIT': 4, 'TRAIN_FLAG_NO_BREAKDOWN_SMOKE': 6, #roadveh misc flags 'ROADVEH_FLAG_TRAM' : 0, 'ROADVEH_FLAG_2CC' : 1, 'ROADVEH_FLAG_AUTOREFIT': 4, 'ROADVEH_FLAG_NO_BREAKDOWN_SMOKE': 6, #ship misc flags 'SHIP_FLAG_2CC' : 1, 'SHIP_FLAG_AUTOREFIT': 4, 'SHIP_FLAG_NO_BREAKDOWN_SMOKE': 6, #aircrafts misc flags 'AIRCRAFT_FLAG_2CC' : 1, 'AIRCRAFT_FLAG_AUTOREFIT': 4, 'AIRCRAFT_FLAG_NO_BREAKDOWN_SMOKE': 6, #for those, who can't tell the difference between a train and an aircraft: 'VEHICLE_FLAG_2CC' : 1, 'VEHICLE_FLAG_AUTOREFIT': 4, 'VEHICLE_FLAG_NO_BREAKDOWN_SMOKE': 6, #Graphic flags for waterfeatures 'WATERFEATURE_ALTERNATIVE_SPRITES' : 0, #IDs for waterfeatures 'WF_WATERCLIFFS' : 0x00, 'WF_LOCKS' : 0x01, 'WF_DIKES' : 0x02, 'WF_CANAL_GUI' : 0x03, 'WF_FLAT_DOCKS' : 0x04, 'WF_RIVER_SLOPE' : 0x05, 'WF_RIVERBANKS' : 0x06, 'WF_RIVER_GUI' : 0x07, 'WF_BUOY' : 0x08, #ai flags 'AI_FLAG_PASSENGER' : 0x01, 'AI_FLAG_CARGO' : 0x00, # Callback results 'CB_RESULT_ATTACH_ALLOW_IF_RAILTYPES' : 0x400, 'CB_RESULT_ATTACH_ALLOW' : 0x401, 'CB_RESULT_ATTACH_DISALLOW' : 0x402, 'CB_RESULT_NO_MORE_ARTICULATED_PARTS' : 0x7FFF, 'CB_RESULT_REVERSED_VEHICLE' : 0x4000, 'CB_RESULT_32_DAYS_TRIGGER' : 0, 'CB_RESULT_32_DAYS_COLOUR_MAPPING' : 1, 'CB_RESULT_COLOUR_MAPPING_ADD_CC' : 0x4000, 'CB_RESULT_AUTOREFIT' : 0x4000, 'CB_RESULT_REFIT_COST_MASK' : 0x3FFF, 'CB_RESULT_NO_SOUND' : 0x7EFF, # Never a valid sound id # 1-based, not 0-based 'SOUND_EVENT_START' : 1, 'SOUND_EVENT_TUNNEL' : 2, 'SOUND_EVENT_BREAKDOWN' : 3, 'SOUND_EVENT_RUNNING' : 4, 'SOUND_EVENT_TOUCHDOWN' : 5, 'SOUND_EVENT_VISUAL_EFFECT' : 6, 'SOUND_EVENT_RUNNING_16' : 7, 'SOUND_EVENT_STOPPED' : 8, 'SOUND_EVENT_LOAD_UNLOAD' : 9, #sound effects 'SOUND_SPLAT' : 0x00, 'SOUND_FACTORY_WHISTLE' : 0x01, 'SOUND_TRAIN' : 0x02, 'SOUND_TRAIN_THROUGH_TUNNEL' : 0x03, 'SOUND_SHIP_HORN' : 0x04, 'SOUND_FERRY_HORN' : 0x05, 'SOUND_PLANE_TAKE_OFF' : 0x06, 'SOUND_JET' : 0x07, 'SOUND_TRAIN_HORN' : 0x08, 'SOUND_MINING_MACHINERY' : 0x09, 'SOUND_ELECTRIC_SPARK' : 0x0A, 'SOUND_STEAM' : 0x0B, 'SOUND_LEVEL_CROSSING' : 0x0C, 'SOUND_VEHICLE_BREAKDOWN' : 0x0D, 'SOUND_TRAIN_BREAKDOWN' : 0x0E, 'SOUND_CRASH' : 0x0F, 'SOUND_EXPLOSION' : 0x10, 'SOUND_BIG_CRASH' : 0x11, 'SOUND_CASHTILL' : 0x12, 'SOUND_BEEP' : 0x13, 'SOUND_MORSE' : 0x14, 'SOUND_SKID_PLANE' : 0x15, 'SOUND_HELICOPTER' : 0x16, 'SOUND_BUS_START_PULL_AWAY' : 0x17, 'SOUND_BUS_START_PULL_AWAY_WITH_HORN' : 0x18, 'SOUND_TRUCK_START' : 0x19, 'SOUND_TRUCK_START_2' : 0x1A, 'SOUND_APPLAUSE' : 0x1B, 'SOUND_OOOOH' : 0x1C, 'SOUND_SPLAT_2' : 0x1D, 'SOUND_SPLAT_3' : 0x1E, 'SOUND_JACKHAMMER' : 0x1F, 'SOUND_CAR_HORN' : 0x20, 'SOUND_CAR_HORN_2' : 0x21, 'SOUND_SHEEP' : 0x22, 'SOUND_COW' : 0x23, 'SOUND_HORSE' : 0x24, 'SOUND_BLACKSMITH_ANVIL' : 0x25, 'SOUND_SAWMILL' : 0x26, 'SOUND_GOOD_YEAR' : 0x27, 'SOUND_BAD_YEAR' : 0x28, 'SOUND_RIP' : 0x29, 'SOUND_EXTRACT_AND_POP' : 0x2A, 'SOUND_COMEDY_HIT' : 0x2B, 'SOUND_MACHINERY' : 0x2C, 'SOUND_RIP_2' : 0x2D, 'SOUND_EXTRACT_AND_POP_2' : 0x2E, 'SOUND_POP' : 0x2F, 'SOUND_CARTOON_SOUND' : 0x30, 'SOUND_EXTRACT' : 0x31, 'SOUND_POP_2' : 0x32, 'SOUND_PLASTIC_MINE' : 0x33, 'SOUND_WIND' : 0x34, 'SOUND_COMEDY_BREAKDOWN' : 0x35, 'SOUND_CARTOON_CRASH' : 0x36, 'SOUND_BALLOON_SQUEAK' : 0x37, 'SOUND_CHAINSAW' : 0x38, 'SOUND_HEAVY_WIND' : 0x39, 'SOUND_COMEDY_BREAKDOWN_2' : 0x3A, 'SOUND_JET_OVERHEAD' : 0x3B, 'SOUND_COMEDY_CAR' : 0x3C, 'SOUND_ANOTHER_JET_OVERHEAD' : 0x3D, 'SOUND_COMEDY_CAR_2' : 0x3E, 'SOUND_COMEDY_CAR_3' : 0x3F, 'SOUND_COMEDY_CAR_START_AND_PULL_AWAY' : 0x40, 'SOUND_MAGLEV' : 0x41, 'SOUND_LOON_BIRD' : 0x42, 'SOUND_LION' : 0x43, 'SOUND_MONKEYS' : 0x44, 'SOUND_PLANE_CRASHING' : 0x45, 'SOUND_PLANE_ENGINE_SPUTTERING' : 0x46, 'SOUND_MAGLEV_2' : 0x47, 'SOUND_DISTANT_BIRD' : 0x48, #sprite ids 'SPRITE_ID_NEW_TRAIN' : 0xFD, 'SPRITE_ID_NEW_ROADVEH' : 0xFF, 'SPRITE_ID_NEW_SHIP' : 0xFF, 'SPRITE_ID_NEW_AIRCRAFT' : 0xFF, 'NEW_CARGO_SPRITE' : 0xFFFF, #aircraft type 'AIRCRAFT_TYPE_HELICOPTER' : 0x00, 'AIRCRAFT_TYPE_SMALL' : 0x02, 'AIRCRAFT_TYPE_LARGE' : 0x03, #ground sprite IDs 'GROUNDSPRITE_CONCRETE' : 1420, 'GROUNDSPRITE_CLEARED' : 3924, 'GROUNDSPRITE_NORMAL' : 3981, 'GROUNDSPRITE_WATER' : 4061, 'GROUNDSPRITE_SNOW_1_4' : 4493, 'GROUNDSPRITE_SNOW_2_4' : 4512, 'GROUNDSPRITE_SNOW_3_4' : 4531, 'GROUNDSPRITE_SNOW_4_4' : 4550, 'GROUNDSPRITE_SNOW' : 4550, 'GROUNDSPRITE_DESERT_1_2' : 4512, 'GROUNDSPRITE_DESERT' : 4550, # general CB for rerandomizing 'CB_RANDOM_TRIGGER' : 0x01, #house flags 'HOUSE_FLAG_NOT_SLOPED' : 1, 'HOUSE_FLAG_ANIMATE' : 5, 'HOUSE_FLAG_CHURCH' : 6, 'HOUSE_FLAG_STADIUM' : 7, 'HOUSE_FLAG_ONLY_SE' : 8, 'HOUSE_FLAG_PROTECTED' : 9, 'HOUSE_FLAG_SYNC_CALLBACK' : 10, 'HOUSE_FLAG_RANDOM_ANIMATION' : 11, #cargo acceptance 'HOUSE_ACCEPT_GOODS' : 0x00, 'HOUSE_ACCEPT_FOOD' : 0x10, # 0x80 / 8 'HOUSE_ACCEPT_FIZZY_DRINKS' : 0x10, # 0x80 / 8 # house sizes 'HOUSE_SIZE_1X1' : 0, 'HOUSE_SIZE_2X1' : 2, 'HOUSE_SIZE_1X2' : 3, 'HOUSE_SIZE_2X2' : 4, # house tiles 'HOUSE_TILE_NORTH' : 0, 'HOUSE_TILE_EAST' : 1, 'HOUSE_TILE_WEST' : 2, 'HOUSE_TILE_SOUTH' : 3, # house callback results 'CB_RESULT_HOUSE_NO_MORE_PRODUCTION': 0x20FF, #town zones 'TOWNZONE_EDGE' : 0, 'TOWNZONE_OUTSKIRT' : 1, 'TOWNZONE_OUTER_SUBURB' : 2, 'TOWNZONE_INNER_SUBURB' : 3, 'TOWNZONE_CENTRE' : 4, 'ALL_TOWNZONES' : 0x1F, #industry tile special flags 'INDTILE_FLAG_RANDOM_ANIMATION' : 0, 'CB_RESULT_IND_PROD_NO_CHANGE' : 0x00, 'CB_RESULT_IND_PROD_HALF' : 0x01, 'CB_RESULT_IND_PROD_DOUBLE' : 0x02, 'CB_RESULT_IND_PROD_CLOSE' : 0x03, 'CB_RESULT_IND_PROD_RANDOM' : 0x04, 'CB_RESULT_IND_PROD_DIVIDE_BY_4' : 0x05, 'CB_RESULT_IND_PROD_DIVIDE_BY_8' : 0x06, 'CB_RESULT_IND_PROD_DIVIDE_BY_16' : 0x07, 'CB_RESULT_IND_PROD_DIVIDE_BY_32' : 0x08, 'CB_RESULT_IND_PROD_MULTIPLY_BY_4' : 0x09, 'CB_RESULT_IND_PROD_MULTIPLY_BY_8' : 0x0A, 'CB_RESULT_IND_PROD_MULTIPLY_BY_16' : 0x0B, 'CB_RESULT_IND_PROD_MULTIPLY_BY_32' : 0x0C, 'CB_RESULT_IND_PROD_DECREMENT_BY_1' : 0x0D, 'CB_RESULT_IND_PROD_INCREMENT_BY_1' : 0x0E, 'CB_RESULT_IND_PROD_SET_BY_0x100' : 0x0F, 'CB_RESULT_IND_DO_NOT_USE_SPECIAL' : 0x00, 'CB_RESULT_IND_USE_SPECIAL' : 0x01, 'CB_RESULT_IND_NO_CONSTRUCTION' : 0x0000, 'CB_RESULT_IND_PROBABILITY_FROM_PROPERTY' : 0x0100, 'CB_RESULT_NO_TEXT' : 0x400, 'CB_RESULT_LOCATION_ALLOW' : 0x400, 'CB_RESULT_LOCATION_DISALLOW' : 0x401, 'CB_RESULT_LOCATION_DISALLOW_ONLY_RAINFOREST' : 0x402, 'CB_RESULT_LOCATION_DISALLOW_ONLY_DESERT' : 0x403, 'CB_RESULT_LOCATION_DISALLOW_ONLY_ABOVE_SNOWLINE' : 0x404, 'CB_RESULT_LOCATION_DISALLOW_ONLY_BELOW_SNOWLINE' : 0x405, 'CB_RESULT_LOCATION_DISALLOW_NOT_ON_OPEN_SEA' : 0x406, 'CB_RESULT_LOCATION_DISALLOW_NOT_ON_CANAL' : 0x407, 'CB_RESULT_LOCATION_DISALLOW_NOT_ON_RIVER' : 0x408, #industry special flags 'IND_FLAG_PLANT_FIELDS_PERIODICALLY' : 0, 'IND_FLAG_CUT_TREES' : 1, 'IND_FLAG_BUILT_ON_WATER' : 2, 'IND_FLAG_ONLY_IN_LARGE_TOWNS' : 3, 'IND_FLAG_ONLY_IN_TOWNS' : 4, 'IND_FLAG_BUILT_NEAR_TOWN' : 5, 'IND_FLAG_PLANT_FIELDS_WHEN_BUILT' : 6, 'IND_FLAG_NO_PRODUCTION_INCREASE' : 7, 'IND_FLAG_BUILT_ONLY_BEFORE_1950' : 8, 'IND_FLAG_BUILT_ONLY_AFTER_1960' : 9, 'IND_FLAG_AI_CREATES_AIR_AND_SHIP_ROUTES' : 10, 'IND_FLAG_MILITARY_AIRPLANE_CAN_EXPLODE' : 11, 'IND_FLAG_MILITARY_HELICOPTER_CAN_EXPLODE' : 12, 'IND_FLAG_CAN_CAUSE_SUBSIDENCE' : 13, 'IND_FLAG_AUTOMATIC_PRODUCTION_MULTIPLIER' : 14, 'IND_FLAG_RANDOM_BITS_IN_PRODUCTION_CALLBACK' : 15, 'IND_FLAG_DO_NOT_FORCE_INSTANCE_AT_MAP_GENERATION' : 16, 'IND_FLAG_ALLOW_CLOSING_LAST_INSTANCE' : 17, #flags for builtin function industry_type(..) 'IND_TYPE_OLD' : 0, 'IND_TYPE_NEW' : 1, #founder for industries (founder variable) 'FOUNDER_GAME' : 16, #object flags 'OBJ_FLAG_ONLY_SE' : 0, 'OBJ_FLAG_IRREMOVABLE' : 1, 'OBJ_FLAG_ANYTHING_REMOVE' : 2, 'OBJ_FLAG_ON_WATER' : 3, 'OBJ_FLAG_REMOVE_IS_INCOME': 4, 'OBJ_FLAG_NO_FOUNDATIONS' : 5, 'OBJ_FLAG_ANIMATED' : 6, 'OBJ_FLAG_ONLY_INGAME' : 7, 'OBJ_FLAG_2CC' : 8, 'OBJ_FLAG_NOT_ON_LAND' : 9, 'OBJ_FLAG_DRAW_WATER' : 10, 'OBJ_FLAG_ALLOW_BRIDGE' : 11, 'OBJ_FLAG_RANDOM_ANIMATION': 12, 'OBJ_FLAG_SCALE_BY_WATER' : 13, #object animation triggers 'OBJ_ANIM_IS_BUILT' : 0, 'OBJ_ANIM_PERIODIC' : 1, 'OBJ_ANIM_SYNC' : 2, # Special values for object var 0x60 'OBJECT_TYPE_OTHER_GRF' : 0xFFFE, 'OBJECT_TYPE_NO_OBJECT' : 0xFFFF, #railtype flags 'RAILTYPE_FLAG_CATENARY' : 0, 'RAILTYPE_FLAG_NO_LEVEL_CROSSING' : 1, # for OpenTTD > r20049 #type of default station graphics used for a railtype 'RAILTYPE_STATION_NORMAL' : 0, 'RAILTYPE_STATION_MONORAIL' : 1, 'RAILTYPE_STATION_MAGLEV' : 2, #ground tile types as returned by railtypes varaction2 0x40 'TILETYPE_NORMAL' : 0x00, 'TILETYPE_DESERT' : 0x01, 'TILETYPE_RAIN_FOREST' : 0x02, 'TILETYPE_SNOW' : 0x04, # Water classes 'WATER_CLASS_NONE' : 0, 'WATER_CLASS_SEA' : 1, 'WATER_CLASS_CANAL' : 2, 'WATER_CLASS_RIVER' : 3, #level crossing status as returned by railtypes varaction2 0x42 'LEVEL_CROSSING_CLOSED' : 1, 'LEVEL_CROSSING_OPEN' : 0, 'LEVEL_CROSSING_NONE' : 0, #acceleration model for trains 'ACC_MODEL_RAIL' : 0, 'ACC_MODEL_MONORAIL' : 1, 'ACC_MODEL_MAGLEV' : 2, #default industry IDs 'INDUSTRYTYPE_COAL_MINE' : 0x00, 'INDUSTRYTYPE_POWER_PLANT' : 0x01, 'INDUSTRYTYPE_SAWMILL' : 0x02, 'INDUSTRYTYPE_FOREST' : 0x03, 'INDUSTRYTYPE_OIL_REFINERY' : 0x04, 'INDUSTRYTYPE_OIL_RIG' : 0x05, 'INDUSTRYTYPE_TEMPERATE_FACTORY' : 0x06, 'INDUSTRYTYPE_PRINTING_WORKS' : 0x07, 'INDUSTRYTYPE_STEEL_MILL' : 0x08, 'INDUSTRYTYPE_TEMPERATE_ARCTIC_FARM' : 0x09, 'INDUSTRYTYPE_COPPER_ORE_MINE' : 0x0A, 'INDUSTRYTYPE_OIL_WELLS' : 0x0B, 'INDUSTRYTYPE_TEMPERATE_BANK' : 0x0C, 'INDUSTRYTYPE_FOOD_PROCESSING_PLANT' : 0x0D, 'INDUSTRYTYPE_PAPER_MILL' : 0x0E, 'INDUSTRYTYPE_GOLD_MINE' : 0x0F, 'INDUSTRYTYPE_TROPICAL_ARCTIC_BANK' : 0x10, 'INDUSTRYTYPE_DIAMOND_MINE' : 0x11, 'INDUSTRYTYPE_IRON_ORE_MINE' : 0x12, 'INDUSTRYTYPE_FRUIT_PLANTATION' : 0x13, 'INDUSTRYTYPE_RUBBER_PLANTATION' : 0x14, 'INDUSTRYTYPE_WATER_WELL' : 0x15, 'INDUSTRYTYPE_WATER_TOWER' : 0x16, 'INDUSTRYTYPE_TROPICAL_FACTORY' : 0x17, 'INDUSTRYTYPE_TROPICAL_FARM' : 0x18, 'INDUSTRYTYPE_LUMBER_MILL' : 0x19, 'INDUSTRYTYPE_CANDYFLOSS_FOREST' : 0x1A, 'INDUSTRYTYPE_SWEETS_FACTORY' : 0x1B, 'INDUSTRYTYPE_BATTERY_FARM' : 0x1C, 'INDUSTRYTYPE_COLA_WELLS' : 0x1D, 'INDUSTRYTYPE_TOY_SHOP' : 0x1E, 'INDUSTRYTYPE_TOY_FACTORY' : 0x1F, 'INDUSTRYTYPE_PLASTIC_FOUNTAIN' : 0x20, 'INDUSTRYTYPE_FIZZY_DRINKS_FACTORY' : 0x21, 'INDUSTRYTYPE_BUBBLE_GENERATOR' : 0x22, 'INDUSTRYTYPE_TOFFE_QUARRY' : 0x23, 'INDUSTRYTYPE_SUGAR_MINE' : 0x24, 'INDUSTRYTYPE_UNKNOWN' : 0xFE, 'INDUSTRYTYPE_TOWN' : 0xFF, #industry life types (industry property 0x0B, life_type) 'IND_LIFE_TYPE_BLACK_HOLE' : 0x00, 'IND_LIFE_TYPE_EXTRACTIVE' : 0x01, 'IND_LIFE_TYPE_ORGANIC' : 0x02, 'IND_LIFE_TYPE_PROCESSING' : 0x04, #traffic side (bool, true = right hand side) 'TRAFFIC_SIDE_LEFT' : 0, 'TRAFFIC_SIDE_RIGHT' : 1, #which platform has loaded this grf 'PLATFORM_TTDPATCH' : 0x00, 'PLATFORM_OPENTTD' : 0x01, #player types (vehicle var 0x43) 'PLAYERTYPE_HUMAN' : 0, 'PLAYERTYPE_AI' : 1, 'PLAYERTYPE_HUMAN_IN_AI' : 2, 'PLAYERTYPE_AI_IN_HUMAN' : 3, # search criteria for house var 0x65 'SEARCH_HOUSE_BY_TYPE' : 0, 'SEARCH_HOUSE_BY_CLASS' : 1, 'SEARCH_HOUSE_BY_GRFID' : 2, #build types (industry var 0xB3) 'BUILDTYPE_UNKNOWN' : 0, 'BUILDTYPE_GAMEPLAY' : 1, 'BUILDTYPE_GENERATION' : 2, 'BUILDTYPE_EDITOR' : 3, # Creation types (several industry[tile] callbacks 'IND_CREATION_GENERATION' : 0, 'IND_CREATION_RANDOM' : 1, 'IND_CREATION_FUND' : 2, 'IND_CREATION_PROSPECT' : 3, #airport types (vehicle var 0x44, base station var airport_type) 'AIRPORTTYPE_SMALL' : 0, 'AIRPORTTYPE_LARGE' : 1, 'AIRPORTTYPE_HELIPORT' : 2, 'AIRPORTTYPE_OILRIG' : 3, #Direction for e.g. airport layouts, vehicle direction 'DIRECTION_NORTH' : 0, 'DIRECTION_NORTHEAST' : 1, 'DIRECTION_EAST' : 2, 'DIRECTION_SOUTHEAST' : 3, 'DIRECTION_SOUTH' : 4, 'DIRECTION_SOUTHWEST' : 5, 'DIRECTION_WEST' : 6, 'DIRECTION_NORTHWEST' : 7, 'CANAL_DIRECTION_NORTH' : 7, 'CANAL_DIRECTION_NORTHEAST' : 0, 'CANAL_DIRECTION_EAST' : 4, 'CANAL_DIRECTION_SOUTHEAST' : 1, 'CANAL_DIRECTION_SOUTH' : 5, 'CANAL_DIRECTION_SOUTHWEST' : 2, 'CANAL_DIRECTION_WEST' : 6, 'CANAL_DIRECTION_NORTHWEST' : 3, 'CORNER_W' : 0, 'CORNER_S' : 1, 'CORNER_E' : 2, 'CORNER_N' : 3, 'IS_STEEP_SLOPE' : 4, 'SLOPE_FLAT' : 0, 'SLOPE_W' : 1, 'SLOPE_S' : 2, 'SLOPE_E' : 4, 'SLOPE_N' : 8, 'SLOPE_NW' : 9, 'SLOPE_SW' : 3, 'SLOPE_SE' : 6, 'SLOPE_NE' : 12, 'SLOPE_EW' : 5, 'SLOPE_NS' : 10, 'SLOPE_NWS' : 11, 'SLOPE_WSE' : 7, 'SLOPE_SEN' : 14, 'SLOPE_ENW' : 13, 'SLOPE_STEEP_W' : 27, 'SLOPE_STEEP_S' : 23, 'SLOPE_STEEP_E' : 30, 'SLOPE_STEEP_N' : 29, #loading stages 'LOADING_STAGE_INITIALIZE' : 0x0000, 'LOADING_STAGE_RESERVE' : 0x0101, 'LOADING_STAGE_ACTIVATE' : 0x0201, 'LOADING_STAGE_TEST' : 0x0401, #palettes 'PALETTE_DOS' : 0, 'PALETTE_DEFAULT' : 0, 'PALETTE_WIN' : 1, 'PALETTE_LEGACY' : 1, #game mode 'GAMEMODE_MENU' : 0, 'GAMEMODE_GAME' : 1, 'GAMEMODE_EDITOR' : 2, #difficulty 'DIFFICULTY_EASY' : 0, 'DIFFICULTY_MEDIUM' : 1, 'DIFFICULTY_HARD' : 2, 'DIFFICULTY_CUSTOM' : 3, #display options 'DISPLAY_TOWN_NAMES' : 0, 'DISPLAY_STATION_NAMES' : 1, 'DISPLAY_SIGNS' : 2, 'DISPLAY_ANIMATION' : 3, 'DISPLAY_FULL_DETAIL' : 5, #map types (ttdp variable 0x13) 'MAP_TYPE_X_BIGGER' : 0, #bit 0 and 1 clear 'MAP_TYPE_RECTANGULAR' : 1, #bit 0 set 'MAP_TYPE_Y_BIGGER' : 2, #bit 0 clear, bit 1 set # Platform types (platform_xx station variables) 'PLATFORM_SAME_STATION' : 0, 'PLATFORM_SAME_SECTION' : 1, 'PLATFORM_SAME_DIRECTION' : 2, # vehicle type (base station var 'had_vehicle_of_type') 'HAD_VEHICLE_OF_TYPE_TRAIN' : 0, 'HAD_VEHICLE_OF_TYPE_BUS' : 1, 'HAD_VEHICLE_OF_TYPE_TRUCK' : 2, 'HAD_VEHICLE_OF_TYPE_AIRCRAFT' : 3, 'HAD_VEHICLE_OF_TYPE_SHIP' : 4, # station facilities (base station var 'facilities') 'FACILITY_TRAIN' : 0, 'FACILITY_TRUCK_STOP' : 1, 'FACILITY_BUS_STOP' : 2, 'FACILITY_AIRPORT' : 3, 'FACILITY_DOCK' : 4, #Random triggers 'TRIGGER_ALL_NEEDED' : 7, 'TRIGGER_VEHICLE_NEW_LOAD' : 0, 'TRIGGER_VEHICLE_SERVICE' : 1, 'TRIGGER_VEHICLE_UNLOAD_ALL' : 2, 'TRIGGER_VEHICLE_ANY_LOAD' : 3, 'TRIGGER_VEHICLE_32_CALLBACK' : 4, 'TRIGGER_STATION_NEW_CARGO' : 0, 'TRIGGER_STATION_NO_MORE_CARGO' : 1, 'TRIGGER_STATION_TRAIN_ARRIVES' : 2, 'TRIGGER_STATION_TRAIN_LEAVES' : 3, 'TRIGGER_STATION_TRAIN_LOADS_UNLOADS' : 4, 'TRIGGER_STATION_TRAIN_RESERVES' : 5, 'TRIGGER_HOUSE_TILELOOP' : 0, 'TRIGGER_HOUSE_TOP_TILELOOP' : 1, 'TRIGGER_INDUSTRYTILE_TILELOOP' : 0, 'TRIGGER_INDUSTRYTILE_256_TICKS' : 1, 'TRIGGER_INDUSTRYTILE_CARGO_DELIVERY' : 2, #Tile classes 'TILE_CLASS_GROUND' : 0x00, 'TILE_CLASS_RAIL' : 0x01, 'TILE_CLASS_ROAD' : 0x02, 'TILE_CLASS_HOUSE' : 0x03, 'TILE_CLASS_TREES' : 0x04, 'TILE_CLASS_STATION' : 0x05, 'TILE_CLASS_WATER' : 0x06, 'TILE_CLASS_VOID' : 0x07, 'TILE_CLASS_INDUSTRY' : 0x08, 'TILE_CLASS_TUNNEL_BRIDGE' : 0x09, 'TILE_CLASS_OBJECTS' : 0x0A, #Land shape flags for industry tiles 'LSF_CANNOT_LOWER_NW_EDGE' : 0, 'LSF_CANNOT_LOWER_NE_EDGE' : 1, 'LSF_CANNOT_LOWER_SW_EDGE' : 2, 'LSF_CANNOT_LOWER_SE_EDGE' : 3, 'LSF_ONLY_ON_FLAT_LAND' : 4, 'LSF_ALLOW_ON_WATER' : 5, # Animation triggers 'ANIM_TRIGGER_INDTILE_CONSTRUCTION_STATE' : 0, 'ANIM_TRIGGER_INDTILE_TILE_LOOP' : 1, 'ANIM_TRIGGER_INDTILE_INDUSTRY_LOOP' : 2, 'ANIM_TRIGGER_INDTILE_RECEIVED_CARGO' : 3, 'ANIM_TRIGGER_INDTILE_DISTRIBUTES_CARGO' : 4, 'ANIM_TRIGGER_OBJ_BUILT' : 0, 'ANIM_TRIGGER_OBJ_TILELOOP' : 1, 'ANIM_TRIGGER_OBJ_256_TICKS' : 2, 'ANIM_TRIGGER_APT_BUILT' : 0, 'ANIM_TRIGGER_APT_TILELOOP' : 1, 'ANIM_TRIGGER_APT_NEW_CARGO' : 2, 'ANIM_TRIGGER_APT_CARGO_TAKEN' : 3, 'ANIM_TRIGGER_APT_250_TICKS' : 4, # Animation looping 'ANIMATION_NON_LOOPING' : 0, 'ANIMATION_LOOPING' : 1, # Animation callback results 'CB_RESULT_STOP_ANIMATION' : 0xFF, # callback 0x25, 0x26 'CB_RESULT_START_ANIMATION' : 0xFE, # callback 0x25 'CB_RESULT_NEXT_FRAME' : 0xFE, # callback 0x26 'CB_RESULT_DO_NOTHING' : 0xFD, # callback 0x25 'CB_RESULT_FOUNDATIONS' : 0x01, # callback 0x30 'CB_RESULT_NO_FOUNDATIONS' : 0x00, # callback 0x30 'CB_RESULT_AUTOSLOPE' : 0x00, # callback 0x3C 'CB_RESULT_NO_AUTOSLOPE' : 0x01, # callback 0x3C # Recolour modes for layout sprites 'RECOLOUR_NONE' : 0, 'RECOLOUR_TRANSPARENT' : 1, 'RECOLOUR_REMAP' : 2, # Possible values for palette 'PALETTE_USE_DEFAULT' : 0, 'PALETTE_TILE_RED_PULSATING' : 771, 'PALETTE_SEL_TILE_RED' : 772, 'PALETTE_SEL_TILE_BLUE' : 773, 'PALETTE_CC_FIRST' : 775, 'PALETTE_CC_DARK_BLUE' : 775, # = first 'PALETTE_CC_PALE_GREEN' : 776, 'PALETTE_CC_PINK' : 777, 'PALETTE_CC_YELLOW' : 778, 'PALETTE_CC_RED' : 778, 'PALETTE_CC_LIGHT_BLUE' : 780, 'PALETTE_CC_GREEN' : 781, 'PALETTE_CC_DARK_GREEN' : 782, 'PALETTE_CC_BLUE' : 783, 'PALETTE_CC_CREAM' : 784, 'PALETTE_CC_MAUVE' : 785, 'PALETTE_CC_PURPLE' : 786, 'PALETTE_CC_ORANGE' : 787, 'PALETTE_CC_BROWN' : 788, 'PALETTE_CC_GREY' : 789, 'PALETTE_CC_WHITE' : 790, 'PALETTE_BARE_LAND' : 791, 'PALETTE_STRUCT_BLUE' : 795, 'PALETTE_STRUCT_BROWN' : 796, 'PALETTE_STRUCT_WHITE' : 797, 'PALETTE_STRUCT_RED' : 798, 'PALETTE_STRUCT_GREEN' : 799, 'PALETTE_STRUCT_CONCRETE' : 800, 'PALETTE_STRUCT_YELLOW' : 801, 'PALETTE_TRANSPARENT' : 802, 'PALETTE_STRUCT_GREY' : 803, 'PALETTE_CRASH' : 804, 'PALETTE_CHURCH_RED' : 1438, 'PALETTE_CHURCH_CREAM' : 1439, # Company colours 'COLOUR_DARK_BLUE' : 0, 'COLOUR_PALE_GREEN' : 1, 'COLOUR_PINK' : 2, 'COLOUR_YELLOW' : 3, 'COLOUR_RED' : 4, 'COLOUR_LIGHT_BLUE' : 5, 'COLOUR_GREEN' : 6, 'COLOUR_DARK_GREEN' : 7, 'COLOUR_BLUE' : 8, 'COLOUR_CREAM' : 9, 'COLOUR_MAUVE' : 10, 'COLOUR_PURPLE' : 11, 'COLOUR_ORANGE' : 12, 'COLOUR_BROWN' : 13, 'COLOUR_GREY' : 14, 'COLOUR_WHITE' : 15, # Town growth effect of cargo 'TOWNGROWTH_PASSENGERS' : 0x00, 'TOWNGROWTH_MAIL' : 0x02, 'TOWNGROWTH_GOODS' : 0x05, 'TOWNGROWTH_WATER' : 0x09, 'TOWNGROWTH_FOOD' : 0x0B, 'TOWNGROWTH_NONE' : 0xFF, # Cargo callbacks 'CARGO_CB_PROFIT' : 0x01, 'CARGO_CB_STATION_RATING' : 0x02, #CMP and UCMP results 'CMP_LESS' : 0, 'CMP_EQUAL' : 1, 'CMP_GREATER' : 2, # TTD Strings 'TTD_STR_CARGO_PLURAL_NOTHING' : 0x000E, 'TTD_STR_CARGO_PLURAL_PASSENGERS' : 0x000F, 'TTD_STR_CARGO_PLURAL_COAL' : 0x0010, 'TTD_STR_CARGO_PLURAL_MAIL' : 0x0011, 'TTD_STR_CARGO_PLURAL_OIL' : 0x0012, 'TTD_STR_CARGO_PLURAL_LIVESTOCK' : 0x0013, 'TTD_STR_CARGO_PLURAL_GOODS' : 0x0014, 'TTD_STR_CARGO_PLURAL_GRAIN' : 0x0015, 'TTD_STR_CARGO_PLURAL_WOOD' : 0x0016, 'TTD_STR_CARGO_PLURAL_IRON_ORE' : 0x0017, 'TTD_STR_CARGO_PLURAL_STEEL' : 0x0018, 'TTD_STR_CARGO_PLURAL_VALUABLES' : 0x0019, 'TTD_STR_CARGO_PLURAL_COPPER_ORE' : 0x001A, 'TTD_STR_CARGO_PLURAL_MAIZE' : 0x001B, 'TTD_STR_CARGO_PLURAL_FRUIT' : 0x001C, 'TTD_STR_CARGO_PLURAL_DIAMONDS' : 0x001D, 'TTD_STR_CARGO_PLURAL_FOOD' : 0x001E, 'TTD_STR_CARGO_PLURAL_PAPER' : 0x001F, 'TTD_STR_CARGO_PLURAL_GOLD' : 0x0020, 'TTD_STR_CARGO_PLURAL_WATER' : 0x0021, 'TTD_STR_CARGO_PLURAL_WHEAT' : 0x0022, 'TTD_STR_CARGO_PLURAL_RUBBER' : 0x0023, 'TTD_STR_CARGO_PLURAL_SUGAR' : 0x0024, 'TTD_STR_CARGO_PLURAL_TOYS' : 0x0025, 'TTD_STR_CARGO_PLURAL_CANDY' : 0x0026, 'TTD_STR_CARGO_PLURAL_COLA' : 0x0027, 'TTD_STR_CARGO_PLURAL_COTTON_CANDY' : 0x0028, 'TTD_STR_CARGO_PLURAL_BUBBLES' : 0x0029, 'TTD_STR_CARGO_PLURAL_TOFFEE' : 0x002A, 'TTD_STR_CARGO_PLURAL_BATTERIES' : 0x002B, 'TTD_STR_CARGO_PLURAL_PLASTIC' : 0x002C, 'TTD_STR_CARGO_PLURAL_FIZZY_DRINKS' : 0x002D, 'TTD_STR_CARGO_SINGULAR_NOTHING' : 0x002E, 'TTD_STR_CARGO_SINGULAR_PASSENGER' : 0x002F, 'TTD_STR_CARGO_SINGULAR_COAL' : 0x0030, 'TTD_STR_CARGO_SINGULAR_MAIL' : 0x0031, 'TTD_STR_CARGO_SINGULAR_OIL' : 0x0032, 'TTD_STR_CARGO_SINGULAR_LIVESTOCK' : 0x0033, 'TTD_STR_CARGO_SINGULAR_GOODS' : 0x0034, 'TTD_STR_CARGO_SINGULAR_GRAIN' : 0x0035, 'TTD_STR_CARGO_SINGULAR_WOOD' : 0x0036, 'TTD_STR_CARGO_SINGULAR_IRON_ORE' : 0x0037, 'TTD_STR_CARGO_SINGULAR_STEEL' : 0x0038, 'TTD_STR_CARGO_SINGULAR_VALUABLES' : 0x0039, 'TTD_STR_CARGO_SINGULAR_COPPER_ORE' : 0x003A, 'TTD_STR_CARGO_SINGULAR_MAIZE' : 0x003B, 'TTD_STR_CARGO_SINGULAR_FRUIT' : 0x003C, 'TTD_STR_CARGO_SINGULAR_DIAMOND' : 0x003D, 'TTD_STR_CARGO_SINGULAR_FOOD' : 0x003E, 'TTD_STR_CARGO_SINGULAR_PAPER' : 0x003F, 'TTD_STR_CARGO_SINGULAR_GOLD' : 0x0040, 'TTD_STR_CARGO_SINGULAR_WATER' : 0x0041, 'TTD_STR_CARGO_SINGULAR_WHEAT' : 0x0042, 'TTD_STR_CARGO_SINGULAR_RUBBER' : 0x0043, 'TTD_STR_CARGO_SINGULAR_SUGAR' : 0x0044, 'TTD_STR_CARGO_SINGULAR_TOY' : 0x0045, 'TTD_STR_CARGO_SINGULAR_CANDY' : 0x0046, 'TTD_STR_CARGO_SINGULAR_COLA' : 0x0047, 'TTD_STR_CARGO_SINGULAR_COTTON_CANDY' : 0x0048, 'TTD_STR_CARGO_SINGULAR_BUBBLE' : 0x0049, 'TTD_STR_CARGO_SINGULAR_TOFFEE' : 0x004A, 'TTD_STR_CARGO_SINGULAR_BATTERY' : 0x004B, 'TTD_STR_CARGO_SINGULAR_PLASTIC' : 0x004C, 'TTD_STR_CARGO_SINGULAR_FIZZY_DRINK' : 0x004D, 'TTD_STR_PASSENGERS' : 0x004F, 'TTD_STR_TONS' : 0x0050, 'TTD_STR_BAGS' : 0x0051, 'TTD_STR_LITERS' : 0x0052, 'TTD_STR_ITEMS' : 0x0053, 'TTD_STR_CRATES' : 0x0054, 'TTD_STR_QUANTITY_NOTHING' : 0x006E, 'TTD_STR_QUANTITY_PASSENGERS' : 0x006F, 'TTD_STR_QUANTITY_COAL' : 0x0070, 'TTD_STR_QUANTITY_MAIL' : 0x0071, 'TTD_STR_QUANTITY_OIL' : 0x0072, 'TTD_STR_QUANTITY_LIVESTOCK' : 0x0073, 'TTD_STR_QUANTITY_GOODS' : 0x0074, 'TTD_STR_QUANTITY_GRAIN' : 0x0075, 'TTD_STR_QUANTITY_WOOD' : 0x0076, 'TTD_STR_QUANTITY_IRON_ORE' : 0x0077, 'TTD_STR_QUANTITY_STEEL' : 0x0078, 'TTD_STR_QUANTITY_VALUABLES' : 0x0079, 'TTD_STR_QUANTITY_COPPER_ORE' : 0x007A, 'TTD_STR_QUANTITY_MAIZE' : 0x007B, 'TTD_STR_QUANTITY_FRUIT' : 0x007C, 'TTD_STR_QUANTITY_DIAMONDS' : 0x007D, 'TTD_STR_QUANTITY_FOOD' : 0x007E, 'TTD_STR_QUANTITY_PAPER' : 0x007F, 'TTD_STR_QUANTITY_GOLD' : 0x0080, 'TTD_STR_QUANTITY_WATER' : 0x0081, 'TTD_STR_QUANTITY_WHEAT' : 0x0082, 'TTD_STR_QUANTITY_RUBBER' : 0x0083, 'TTD_STR_QUANTITY_SUGAR' : 0x0084, 'TTD_STR_QUANTITY_TOYS' : 0x0085, 'TTD_STR_QUANTITY_SWEETS' : 0x0086, 'TTD_STR_QUANTITY_COLA' : 0x0087, 'TTD_STR_QUANTITY_CANDYFLOSS' : 0x0088, 'TTD_STR_QUANTITY_BUBBLES' : 0x0089, 'TTD_STR_QUANTITY_TOFFEE' : 0x008A, 'TTD_STR_QUANTITY_BATTERIES' : 0x008B, 'TTD_STR_QUANTITY_PLASTIC' : 0x008C, 'TTD_STR_QUANTITY_FIZZY_DRINKS' : 0x008D, 'TTD_STR_ABBREV_NOTHING' : 0x008E, 'TTD_STR_ABBREV_PASSENGERS' : 0x008F, 'TTD_STR_ABBREV_COAL' : 0x0090, 'TTD_STR_ABBREV_MAIL' : 0x0091, 'TTD_STR_ABBREV_OIL' : 0x0092, 'TTD_STR_ABBREV_LIVESTOCK' : 0x0093, 'TTD_STR_ABBREV_GOODS' : 0x0094, 'TTD_STR_ABBREV_GRAIN' : 0x0095, 'TTD_STR_ABBREV_WOOD' : 0x0096, 'TTD_STR_ABBREV_IRON_ORE' : 0x0097, 'TTD_STR_ABBREV_STEEL' : 0x0098, 'TTD_STR_ABBREV_VALUABLES' : 0x0099, 'TTD_STR_ABBREV_COPPER_ORE' : 0x009A, 'TTD_STR_ABBREV_MAIZE' : 0x009B, 'TTD_STR_ABBREV_FRUIT' : 0x009C, 'TTD_STR_ABBREV_DIAMONDS' : 0x009D, 'TTD_STR_ABBREV_FOOD' : 0x009E, 'TTD_STR_ABBREV_PAPER' : 0x009F, 'TTD_STR_ABBREV_GOLD' : 0x00A0, 'TTD_STR_ABBREV_WATER' : 0x00A1, 'TTD_STR_ABBREV_WHEAT' : 0x00A2, 'TTD_STR_ABBREV_RUBBER' : 0x00A3, 'TTD_STR_ABBREV_SUGAR' : 0x00A4, 'TTD_STR_ABBREV_TOYS' : 0x00A5, 'TTD_STR_ABBREV_SWEETS' : 0x00A6, 'TTD_STR_ABBREV_COLA' : 0x00A7, 'TTD_STR_ABBREV_CANDYFLOSS' : 0x00A8, 'TTD_STR_ABBREV_BUBBLES' : 0x00A9, 'TTD_STR_ABBREV_TOFFEE' : 0x00AA, 'TTD_STR_ABBREV_BATTERIES' : 0x00AB, 'TTD_STR_ABBREV_PLASTIC' : 0x00AC, 'TTD_STR_ABBREV_FIZZY_DRINKS' : 0x00AD, 'TTD_STR_TOWN_BUILDING_NAME_TALL_OFFICE_BLOCK_1' : 0x200F, 'TTD_STR_TOWN_BUILDING_NAME_OFFICE_BLOCK_1' : 0x2010, 'TTD_STR_TOWN_BUILDING_NAME_SMALL_BLOCK_OF_FLATS_1' : 0x2011, 'TTD_STR_TOWN_BUILDING_NAME_CHURCH_1' : 0x2012, 'TTD_STR_TOWN_BUILDING_NAME_LARGE_OFFICE_BLOCK_1' : 0x2013, 'TTD_STR_TOWN_BUILDING_NAME_TOWN_HOUSES_1' : 0x2014, 'TTD_STR_TOWN_BUILDING_NAME_HOTEL_1' : 0x2015, 'TTD_STR_TOWN_BUILDING_NAME_STATUE_1' : 0x2016, 'TTD_STR_TOWN_BUILDING_NAME_FOUNTAIN_1' : 0x2017, 'TTD_STR_TOWN_BUILDING_NAME_PARK_1' : 0x2018, 'TTD_STR_TOWN_BUILDING_NAME_OFFICE_BLOCK_2' : 0x2019, 'TTD_STR_TOWN_BUILDING_NAME_SHOPS_AND_OFFICES_1' : 0x201A, 'TTD_STR_TOWN_BUILDING_NAME_MODERN_OFFICE_BUILDING_1' : 0x201B, 'TTD_STR_TOWN_BUILDING_NAME_WAREHOUSE_1' : 0x201C, 'TTD_STR_TOWN_BUILDING_NAME_OFFICE_BLOCK_3' : 0x201D, 'TTD_STR_TOWN_BUILDING_NAME_STADIUM_1' : 0x201E, 'TTD_STR_TOWN_BUILDING_NAME_OLD_HOUSES_1' : 0x201F, 'TTD_STR_TOWN_BUILDING_NAME_COTTAGES_1' : 0x2036, 'TTD_STR_TOWN_BUILDING_NAME_HOUSES_1' : 0x2037, 'TTD_STR_TOWN_BUILDING_NAME_FLATS_1' : 0x2038, 'TTD_STR_TOWN_BUILDING_NAME_TALL_OFFICE_BLOCK_2' : 0x2039, 'TTD_STR_TOWN_BUILDING_NAME_SHOPS_AND_OFFICES_2' : 0x203A, 'TTD_STR_TOWN_BUILDING_NAME_SHOPS_AND_OFFICES_3' : 0x203B, 'TTD_STR_TOWN_BUILDING_NAME_THEATER_1' : 0x203C, 'TTD_STR_TOWN_BUILDING_NAME_STADIUM_2' : 0x203D, 'TTD_STR_TOWN_BUILDING_NAME_OFFICES_1' : 0x203E, 'TTD_STR_TOWN_BUILDING_NAME_HOUSES_2' : 0x203F, 'TTD_STR_TOWN_BUILDING_NAME_CINEMA_1' : 0x2040, 'TTD_STR_TOWN_BUILDING_NAME_SHOPPING_MALL_1' : 0x2041, 'TTD_STR_TOWN_BUILDING_NAME_IGLOO_1' : 0x2059, 'TTD_STR_TOWN_BUILDING_NAME_TEPEES_1' : 0x205A, 'TTD_STR_TOWN_BUILDING_NAME_TEAPOT_HOUSE_1' : 0x205B, 'TTD_STR_TOWN_BUILDING_NAME_PIGGY_BANK_1' : 0x205C, 'TTD_STR_INDUSTRY_NAME_COAL_MINE' : 0x4802, 'TTD_STR_INDUSTRY_NAME_POWER_STATION' : 0x4803, 'TTD_STR_INDUSTRY_NAME_SAWMILL' : 0x4804, 'TTD_STR_INDUSTRY_NAME_FOREST' : 0x4805, 'TTD_STR_INDUSTRY_NAME_OIL_REFINERY' : 0x4806, 'TTD_STR_INDUSTRY_NAME_OIL_RIG' : 0x4807, 'TTD_STR_INDUSTRY_NAME_FACTORY' : 0x4808, 'TTD_STR_INDUSTRY_NAME_PRINTING_WORKS' : 0x4809, 'TTD_STR_INDUSTRY_NAME_STEEL_MILL' : 0x480A, 'TTD_STR_INDUSTRY_NAME_FARM' : 0x480B, 'TTD_STR_INDUSTRY_NAME_COPPER_ORE_MINE' : 0x480C, 'TTD_STR_INDUSTRY_NAME_OIL_WELLS' : 0x480D, 'TTD_STR_INDUSTRY_NAME_BANK' : 0x480E, 'TTD_STR_INDUSTRY_NAME_FOOD_PROCESSING_PLANT' : 0x480F, 'TTD_STR_INDUSTRY_NAME_PAPER_MILL' : 0x4810, 'TTD_STR_INDUSTRY_NAME_GOLD_MINE' : 0x4811, 'TTD_STR_INDUSTRY_NAME_BANK_TROPIC_ARCTIC' : 0x4812, 'TTD_STR_INDUSTRY_NAME_DIAMOND_MINE' : 0x4813, 'TTD_STR_INDUSTRY_NAME_IRON_ORE_MINE' : 0x4814, 'TTD_STR_INDUSTRY_NAME_FRUIT_PLANTATION' : 0x4815, 'TTD_STR_INDUSTRY_NAME_RUBBER_PLANTATION' : 0x4816, 'TTD_STR_INDUSTRY_NAME_WATER_SUPPLY' : 0x4817, 'TTD_STR_INDUSTRY_NAME_WATER_TOWER' : 0x4818, 'TTD_STR_INDUSTRY_NAME_FACTORY_2' : 0x4819, 'TTD_STR_INDUSTRY_NAME_FARM_2' : 0x481A, 'TTD_STR_INDUSTRY_NAME_LUMBER_MILL' : 0x481B, 'TTD_STR_INDUSTRY_NAME_COTTON_CANDY_FOREST' : 0x481C, 'TTD_STR_INDUSTRY_NAME_CANDY_FACTORY' : 0x481D, 'TTD_STR_INDUSTRY_NAME_BATTERY_FARM' : 0x481E, 'TTD_STR_INDUSTRY_NAME_COLA_WELLS' : 0x481F, 'TTD_STR_INDUSTRY_NAME_TOY_SHOP' : 0x4820, 'TTD_STR_INDUSTRY_NAME_TOY_FACTORY' : 0x4821, 'TTD_STR_INDUSTRY_NAME_PLASTIC_FOUNTAINS' : 0x4822, 'TTD_STR_INDUSTRY_NAME_FIZZY_DRINK_FACTORY' : 0x4823, 'TTD_STR_INDUSTRY_NAME_BUBBLE_GENERATOR' : 0x4824, 'TTD_STR_INDUSTRY_NAME_TOFFEE_QUARRY' : 0x4825, 'TTD_STR_INDUSTRY_NAME_SUGAR_MINE' : 0x4826, 'TTD_STR_NEWS_INDUSTRY_CONSTRUCTION' : 0x482D, 'TTD_STR_NEWS_INDUSTRY_PLANTED' : 0x482E, 'TTD_STR_NEWS_INDUSTRY_CLOSURE_GENERAL' : 0x4832, 'TTD_STR_NEWS_INDUSTRY_CLOSURE_SUPPLY_PROBLEMS' : 0x4833, 'TTD_STR_NEWS_INDUSTRY_CLOSURE_LACK_OF_TREES' : 0x4834, 'TTD_STR_NEWS_INDUSTRY_PRODUCTION_INCREASE_GENERAL' : 0x4835, 'TTD_STR_NEWS_INDUSTRY_PRODUCTION_INCREASE_COAL' : 0x4836, 'TTD_STR_NEWS_INDUSTRY_PRODUCTION_INCREASE_OIL' : 0x4837, 'TTD_STR_NEWS_INDUSTRY_PRODUCTION_INCREASE_FARM' : 0x4838, 'TTD_STR_NEWS_INDUSTRY_PRODUCTION_DECREASE_GENERAL' : 0x4839, 'TTD_STR_NEWS_INDUSTRY_PRODUCTION_DECREASE_FARM' : 0x483A, 'TTD_STR_ERROR_CAN_T_CONSTRUCT_THIS_INDUSTRY' : 0x4830, 'TTD_STR_ERROR_FOREST_CAN_ONLY_BE_PLANTED' : 0x4831, 'TTD_STR_ERROR_CAN_ONLY_BE_POSITIONED' : 0x483B, } def signextend(param, info): #r = (x ^ m) - m; with m being (1 << (num_bits -1)) m = expression.ConstantNumeric(1 << (info['size'] * 8 - 1)) return expression.BinOp(nmlop.SUB, expression.BinOp(nmlop.XOR, param, m, param.pos), m, param.pos) def global_param_write(info, expr, pos): if not ('writable' in info and info['writable']): raise generic.ScriptError("Target parameter is not writable.", pos) return expression.Parameter(expression.ConstantNumeric(info['num']), pos), expr def global_param_read(info, pos): param = expression.Parameter(expression.ConstantNumeric(info['num']), pos) if info['size'] == 1: mask = expression.ConstantNumeric(0xFF) param = expression.BinOp(nmlop.AND, param, mask) else: assert info['size'] == 4 if 'function' in info: return info['function'](param, info) return param def param_from_info(info, pos): return expression.SpecialParameter(generic.reverse_lookup(global_parameters, info), info, global_param_write, global_param_read, False, pos) global_parameters = { 'climate' : {'num': 0x83, 'size': 1}, 'loading_stage' : {'num': 0x84, 'size': 4}, 'ttdpatch_version' : {'num': 0x8B, 'size': 4}, 'current_palette' : {'num': 0x8D, 'size': 1}, 'traininfo_y_offset' : {'num': 0x8E, 'size': 1, 'writable': 1, 'function': signextend}, 'game_mode' : {'num': 0x92, 'size': 1}, 'ttd_platform' : {'num': 0x9D, 'size': 4}, 'openttd_version' : {'num': 0xA1, 'size': 4}, 'difficulty_level' : {'num': 0xA2, 'size': 4}, 'date_loaded' : {'num': 0xA3, 'size': 4}, 'year_loaded' : {'num': 0xA4, 'size': 4}, } def misc_bit_write(info, expr, pos): param = expression.Parameter(expression.ConstantNumeric(info['param'], pos), pos) #param = (expr != 0) ? param | (1 << bit) : param & ~(1 << bit) expr = expression.BinOp(nmlop.CMP_NEQ, expr, expression.ConstantNumeric(0, pos), pos) or_expr = expression.BinOp(nmlop.OR, param, expression.ConstantNumeric(1 << info['bit'], pos), pos) and_expr = expression.BinOp(nmlop.AND, param, expression.ConstantNumeric(~(1 << info['bit']), pos), pos) expr = expression.TernaryOp(expr, or_expr, and_expr, pos) return (param, expr) def misc_bit_read(info, pos): return expression.BinOp(nmlop.HASBIT, expression.Parameter(expression.ConstantNumeric(info['param'], pos), pos), expression.ConstantNumeric(info['bit'], pos), pos) def misc_grf_bit(info, pos): return expression.SpecialParameter(generic.reverse_lookup(misc_grf_bits, info), info, misc_bit_write, misc_bit_read, True, pos) misc_grf_bits = { 'traffic_side' : {'param': 0x86, 'bit': 4}, 'desert_paved_roads' : {'param': 0x9E, 'bit': 1}, 'train_width_32_px' : {'param': 0x9E, 'bit': 3}, 'second_rocky_tileset' : {'param': 0x9E, 'bit': 6}, } def add_1920(expr, info): """ Create a new expression that adds 1920 to a given expression. @param expr: The expression to add 1920 to. @type expr: L{Expression} @param info: Ignored. @return: A new expression that adds 1920 to the given expression. @rtype: L{Expression} """ return expression.BinOp(nmlop.ADD, expr, expression.ConstantNumeric(1920, expr.pos), expr.pos) def map_exponentiate(expr, info): """ Given a exponent, add an offset to it and compute the exponentiation with base 2. @param expr: The exponent. @type expr: L{Expression} @param info: Table with extra information, most notable 'log_offset', the value we need to add to the given expression before computing the exponentiation. @type info: C{dict} @return: An expression computing 2**(expr + info['log_offset']). @rtype: L{Expression} """ #map (log2(x) - a) to x, i.e. do 1 << (x + a) expr = expression.BinOp(nmlop.ADD, expr, expression.ConstantNumeric(info['log_offset'], expr.pos), expr.pos) return expression.BinOp(nmlop.SHIFT_LEFT, expression.ConstantNumeric(1, expr.pos), expr, expr.pos) def patch_variable_read(info, pos): """ Helper function to read special patch variables. @param info: Generic information about the parameter to read, like parameter number and size. @type info: C{dict} @param pos: Position information in the source file. @type pos: L{Position} or C{None} @return: An expression that reads the special variables. @rtype: L{Expression} """ expr = expression.PatchVariable(info['num'], pos) if info['start'] != 0: expr = expression.BinOp(nmlop.SHIFT_RIGHT, expr, expression.ConstantNumeric(info['start'], pos), pos) if info['size'] != 32: expr = expression.BinOp(nmlop.AND, expr, expression.ConstantNumeric((1 << info['size']) - 1, pos), pos) if 'function' in info: expr = info['function'](expr, info) return expr def patch_variable(info, pos): return expression.SpecialParameter(generic.reverse_lookup(patch_variables, info), info, None, patch_variable_read, False, pos) patch_variables = { 'starting_year' : {'num': 0x0B, 'start': 0, 'size': 32, 'function': add_1920}, 'freight_trains' : {'num': 0x0E, 'start': 0, 'size': 32}, 'plane_speed' : {'num': 0x10, 'start': 0, 'size': 32}, 'base_sprite_2cc' : {'num': 0x11, 'start': 0, 'size': 32}, 'map_type' : {'num': 0x13, 'start': 24, 'size': 2}, 'map_min_edge' : {'num': 0x13, 'start': 20, 'size': 4, 'log_offset': 6, 'function': map_exponentiate}, 'map_max_edge' : {'num': 0x13, 'start': 16, 'size': 4, 'log_offset': 6, 'function': map_exponentiate}, 'map_x_edge' : {'num': 0x13, 'start': 12, 'size': 4, 'log_offset': 6, 'function': map_exponentiate}, 'map_y_edge' : {'num': 0x13, 'start': 8, 'size': 4, 'log_offset': 6, 'function': map_exponentiate}, 'map_size' : {'num': 0x13, 'start': 0, 'size': 8, 'log_offset': 12, 'function': map_exponentiate}, 'max_height_level' : {'num': 0x14, 'start': 0, 'size': 32}, 'base_sprite_foundations' : {'num': 0x15, 'start': 0, 'size': 32}, 'base_sprite_shores' : {'num': 0x16, 'start': 0, 'size': 32}, } def config_flag_read(bit, pos): return expression.SpecialCheck((0x01, r'\70'), 0x85, (0, 1), bit, "PatchFlag({})".format(bit), varsize = 1, pos = pos) def config_flag(info, pos): return expression.SpecialParameter(generic.reverse_lookup(config_flags, info), info, None, config_flag_read, True, pos) config_flags = { 'long_bridges' : 0x0F, 'gradual_loading' : 0x2C, 'bridge_speed_limits' : 0x34, 'newtrains' : 0x37, 'newrvs' : 0x38, 'newships' : 0x39, 'newplanes' : 0x3A, 'signals_on_traffic_side' : 0x3B, 'electrified_railways' : 0x3C, 'newhouses' : 0x59, 'wagon_speed_limits' : 0x5D, 'newindustries' : 0x67, 'temperate_snowline' : 0x6A, 'newcargos' : 0x6B, 'dynamic_engines' : 0x78, 'variable_runningcosts' : 0x7E, } def unified_maglev_read(info, pos): bit0 = expression.BinOp(nmlop.HASBIT, expression.Parameter(expression.ConstantNumeric(0x85), pos), expression.ConstantNumeric(0x32), pos) bit1 = expression.BinOp(nmlop.HASBIT, expression.Parameter(expression.ConstantNumeric(0x85), pos), expression.ConstantNumeric(0x33), pos) shifted_bit1 = expression.BinOp(nmlop.SHIFT_LEFT, bit1, expression.ConstantNumeric(1)) return expression.BinOp(nmlop.OR, shifted_bit1, bit0) def unified_maglev(info, pos): return expression.SpecialParameter(generic.reverse_lookup(unified_maglev_var, info), info, None, unified_maglev_read, False, pos) unified_maglev_var = { 'unified_maglev' : 0, } def setting_from_info(info, pos): return expression.SpecialParameter(generic.reverse_lookup(settings, info), info, global_param_write, global_param_read, False, pos) def item_to_id(item, pos): if not isinstance(item.id, expression.ConstantNumeric): raise generic.ScriptError("Referencing item '{}' with a non-constant id is not possible.".format(item.name), pos) return expression.ConstantNumeric(item.id.value, pos) def param_from_name(info, pos): return expression.Parameter(expression.ConstantNumeric(info), pos) def create_spritegroup_ref(info, pos): return expression.SpriteGroupRef(expression.Identifier(info), [], pos) cargo_numbers = {} is_default_railtype_table = True railtype_table = {'RAIL': 0, 'ELRL': 1, 'MONO': 1, 'MGLV': 2} item_names = {} settings = {} named_parameters = {} spritegroups = {'CB_FAILED': 'CB_FAILED'} const_list = [ constant_numbers, (global_parameters, param_from_info), (misc_grf_bits, misc_grf_bit), (patch_variables, patch_variable), (named_parameters, param_from_name), cargo_numbers, railtype_table, (item_names, item_to_id), (settings, setting_from_info), (config_flags, config_flag), (unified_maglev_var, unified_maglev), (spritegroups, create_spritegroup_ref), ] def print_stats(): """ Print statistics about used ids. """ if len(cargo_numbers) > 0: # Ids FE and FF have special meanings in Action3, so we do not consider them valid ids. generic.print_info("Cargo translation table: {}/{}".format(len(cargo_numbers), 0xFE)) if not is_default_railtype_table: generic.print_info("Railtype translation table: {}/{}".format(len(railtype_table), 0x100)) nml-0.4.4/nml/output_dep.py0000644000567200056720000000233312643457545017017 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" # -*- coding: utf-8 -*- import codecs from nml import output_base class OutputDEP(output_base.TextOutputBase): """ Class for output to a dependency file in makefile format. """ def __init__(self, filename, grf_filename): output_base.TextOutputBase.__init__(self, filename) self.grf_filename = grf_filename def open_file(self): return codecs.open(self.filename, 'w', 'utf-8') def write(self, text): self.file.write(self.grf_filename + ': ' + text + '\n') def skip_sprite_checks(self): return True nml-0.4.4/nml/output_nfo.py0000644000567200056720000001361612643457545017037 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" # -*- coding: utf-8 -*- import codecs, io from nml import generic, grfstrings, output_base from nml.actions import real_sprite zoom_levels = { 0 : 'normal', 1 : 'zi4', 2 : 'zi2', 3 : 'zo2', 4 : 'zo4', 5 : 'zo8', } bit_depths = { 8 : '8bpp', 32 : '32bpp', } class OutputNFO(output_base.SpriteOutputBase): def __init__(self, filename, start_sprite_num): output_base.SpriteOutputBase.__init__(self, filename) self.sprite_num = start_sprite_num def open(self): self.file = io.StringIO() def open_file(self): handle = codecs.open(self.filename, 'w', encoding='utf-8') handle.write('// Automatically generated by GRFCODEC. Do not modify!\n' '// (Info version 32)\n' '// Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>>\n' '// Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C\n' '// Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D%\n' '// Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags\n\n') return handle def print_byte(self, value): value = self.prepare_byte(value) self.file.write("\\b" + str(value) + " ") def print_bytex(self, value, pretty_print = None): value = self.prepare_byte(value) if pretty_print is not None: self.file.write(pretty_print + " ") return self.file.write("{:02X} ".format(value)) def print_word(self, value): value = self.prepare_word(value) self.file.write("\\w{:d} ".format(value)) def print_wordx(self, value): value = self.prepare_word(value) self.file.write("\\wx{:04X} ".format(value)) def print_dword(self, value): value = self.prepare_dword(value) self.file.write("\\d{:d} ".format(value)) def print_dwordx(self, value): value = self.prepare_dword(value) self.file.write("\\dx{:08X} ".format(value)) def print_string(self, value, final_zero = True, force_ascii = False): assert self.in_sprite self.file.write('"') if not grfstrings.is_ascii_string(value): if force_ascii: raise generic.ScriptError("Expected ascii string but got a unicode string") self.file.write('Þ') # b'\xC3\x9E'.decode('utf-8') self.file.write(value.replace('"', '\\"')) self.byte_count += grfstrings.get_string_size(value, final_zero, force_ascii) self.file.write('" ') if final_zero: self.print_bytex(0) # get_string_size already includes the final 0 byte # but print_bytex also increases byte_count, so decrease # it here by one to correct it. self.byte_count -= 1 def print_decimal(self, value): assert self.in_sprite self.file.write(str(value) + " ") def newline(self, msg = "", prefix = "\t"): if msg != "": msg = prefix + "// " + msg self.file.write(msg + "\n") def comment(self, msg): self.file.write("// " + msg + "\n") def start_sprite(self, size, is_real_sprite = False): output_base.SpriteOutputBase.start_sprite(self, size) self.print_decimal(self.sprite_num) self.sprite_num += 1 if not is_real_sprite: self.file.write("* ") self.print_decimal(size) def print_sprite(self, sprite_list): """ @param sprite_list: List of non-empty real sprites for various bit depths / zoom levels @type sprite_list: C{list} of L{RealSprite} """ self.start_sprite(0, True) for i, sprite_info in enumerate(sprite_list): self.file.write(sprite_info.file.value + " ") self.file.write(bit_depths[sprite_info.bit_depth] + " ") self.print_decimal(sprite_info.xpos.value) self.print_decimal(sprite_info.ypos.value) self.print_decimal(sprite_info.xsize.value) self.print_decimal(sprite_info.ysize.value) self.print_decimal(sprite_info.xrel.value) self.print_decimal(sprite_info.yrel.value) self.file.write(zoom_levels[sprite_info.zoom_level] + " ") if (sprite_info.flags.value & real_sprite.FLAG_NOCROP) != 0: self.file.write("nocrop ") if sprite_info.mask_file is not None: self.newline() self.file.write("|\t") self.file.write(sprite_info.mask_file.value) self.file.write(" mask ") mask_x, mask_y = sprite_info.mask_pos if sprite_info.mask_pos is not None else (sprite_info.xpos, sprite_info.ypos) self.print_decimal(mask_x.value) self.print_decimal(mask_y.value) if i + 1 < len(sprite_list): self.newline() self.file.write("|\t") self.end_sprite() def print_empty_realsprite(self): self.start_sprite(1) self.print_bytex(0) self.end_sprite() def print_named_filedata(self, filename): self.start_sprite(0, True) self.file.write("** " + filename) self.end_sprite() nml-0.4.4/nml/unit.py0000644000567200056720000000777712643457545015627 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" # Available units, mapping of unit name to L{Unit} objects. units = {} def get_unit(name): """ Get a unit by name. @param name: Name of the unit. @type name: C{str} @return: The requested unit. @rtype: L{Unit} """ return units[name] class Unit(object): def __init__(self, name, type, convert, ottd_mul, ottd_shift): """ Unit definition. Conversion factor works like this: 1 reference_unit = convert other_unit So nfo_value = property_value / convert * property_specific_conversion_factor To avoid using fractions, rational numbers (as 2-tuples) are used instead. ottd_mul and ottd_shift are the values taken from OpenTTD's src/strings.cpp and are used to calculate the displayed value by OpenTTD. If possible, adjust_values increases or decreases the NFO value so that the desired display value is actually achieved. @ivar name: Name of the unit. @type name: C{str} @ivar type: Kind of unit. @type type: C{str} @ivar convert: Conversion factor of the unit. @type convert: Either C{int} or a rational tuple (C{int}, C{int}) @ivar ottd_mul: OpenTTD multiplication factor for displaying a value of this unit. @type ottd_mul: C{int} @ivar ottd_shift: OpenTTD shift factor for displaying a value of this unit. @type ottd_shift: C{int} """ self.name = name self.type = type self.convert = convert self.ottd_mul = ottd_mul self.ottd_shift = ottd_shift def __str__(self): return self.name def add_unit(name, type, convert, ottd_mul, ottd_shift): """ Construct new unit, and add it to L{units}. @param name: Name of the unit. @type name: C{str} @param type: Kind of unit. @type type: C{str} @param convert: Conversion factor of the unit. @type convert: Either C{int} or a rational tuple (C{int}, C{int}) @param ottd_mul: OpenTTD multiplication factor for displaying a value of this unit. @type ottd_mul: C{int} @param ottd_shift: OpenTTD shift factor for displaying a value of this unit. @type ottd_shift: C{int} """ unit = Unit(name, type, convert, ottd_mul, ottd_shift) units[name] = unit # name type convert mul shift add_unit('nfo', 'nfo', 1, 1, 0) #don't convert, take value literal #Speed (reference: m/s) # name type convert mul shift add_unit( 'mph', 'speed', (3125, 1397), 1, 0) add_unit( 'km/h', 'speed', ( 18, 5), 103, 6) add_unit( 'm/s', 'speed', 1, 1831, 12) #Power (reference: hpI (imperial hp)) # name type convert mul shift add_unit( 'hp', 'power', 1, 1, 0) # Default to imperial hp add_unit( 'kW', 'power', (2211, 2965), 6109, 13) add_unit( 'hpM', 'power', ( 731, 721), 4153, 12) add_unit( 'hpI', 'power', 1, 1, 0) #Weight (reference: ton) # name type convert mul shift add_unit( 'ton', 'weight', 1, 1, 0) add_unit( 'tons', 'weight', 1, 1, 0) add_unit( 'kg', 'weight', 1000, 1000, 0) #Snowline height # name type convert mul shift add_unit('snow%', 'snowline', ( 255, 100), 1, 0) nml-0.4.4/nml/ast/0000755000567200056720000000000012643457623015040 5ustar jenkinsjenkins00000000000000nml-0.4.4/nml/ast/tilelayout.py0000644000567200056720000001261612643457545017616 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, expression, global_constants from nml.actions import action0properties from nml.ast import base_statement, assignment class TileLayout(base_statement.BaseStatement): """ 'tile_layout' AST node. A TileLayout is a list of x,y-offset/tileID pairs. The x and y offsets are from the northernmost tile of the industry/airport. Additionally some extra properties can be stored in the TileLayout, like the orientation of the airport. @ivar name: The name of this layout by which it can be referenced later. @type name: C{str} @ivar tile_prop_list: List of offset/tileID and properties. @type tile_prop_list: C{list} of L{LayoutTile} and L{Assignment} @ivar tile_list: List of tile-offsets/tileIDs. @type tile_list: C{list} of C{LayoutTile} with constant x and y values. @ivar properties: table of all properties. Unknown property names are accepted and ignored. @type properties: C{dict} with C{str} keys and L{ConstantNumeric} values """ def __init__(self, name, tile_list, pos): base_statement.BaseStatement.__init__(self, "tile layout", pos, False, False) self.name = name.value self.tile_prop_list = tile_list self.tile_list = [] self.properties = {} def pre_process(self): for tileprop in self.tile_prop_list: if isinstance(tileprop, assignment.Assignment): name = tileprop.name.value if name in self.properties: raise generic.ScriptError("Duplicate property {} in tile layout".format(name), tileprop.name.pos) self.properties[name] = tileprop.value.reduce_constant(global_constants.const_list) else: assert isinstance(tileprop, LayoutTile) x = tileprop.x.reduce_constant() y = tileprop.y.reduce_constant() tile = tileprop.tiletype.reduce(unknown_id_fatal = False) if isinstance(tile, expression.Identifier) and tile.value == 'clear': tile = expression.ConstantNumeric(0xFF) self.tile_list.append(LayoutTile(x, y, tile)) if self.name in action0properties.tilelayout_names: raise generic.ScriptError("A tile layout with name '{}' has already been defined.".format(self.name), self.pos) action0properties.tilelayout_names[self.name] = self def debug_print(self, indentation): generic.print_dbg(indentation, 'TileLayout') for tile in self.tile_list: generic.print_dbg(indentation + 2, 'At {:d},{:d}:'.format(tile.x, tile.y)) tile.tiletype.debug_print(indentation + 4) def get_action_list(self): return [] def __str__(self): return 'tilelayout {} {{\n\t{}\n}}\n'.format(self.name, '\n\t'.join(str(x) for x in self.tile_prop_list)) def get_size(self): size = 2 for tile in self.tile_list: size += 3 if not isinstance(tile.tiletype, expression.ConstantNumeric): size += 2 return size def write(self, file): for tile in self.tile_list: file.print_bytex(tile.x.value) file.print_bytex(tile.y.value) if isinstance(tile.tiletype, expression.ConstantNumeric): file.print_bytex(tile.tiletype.value) else: if not isinstance(tile.tiletype, expression.Identifier): raise generic.ScriptError("Invalid expression type for layout tile", tile.tiletype.pos) if tile.tiletype.value not in global_constants.item_names: raise generic.ScriptError("Unknown tile name", tile.tiletype.pos) file.print_bytex(0xFE) tile_id = global_constants.item_names[tile.tiletype.value].id if not isinstance(tile_id, expression.ConstantNumeric): raise generic.ScriptError("Tile '{}' cannot be used in a tilelayout, as its ID is not a constant.".format(tile.tiletype.value), tile.tiletype.pos) file.print_wordx(tile_id.value) file.newline() file.print_bytex(0) file.print_bytex(0x80) file.newline() class LayoutTile(object): """ Single tile that is part of a L{TileLayout}. @ivar x: X-offset from the northernmost tile of the industry/airport. @type x: L{Expression} @ivar y: Y-offset from the northernmost tile of the industry/airport. @type y: L{Expression} @ivar tiletype: TileID of the tile to draw on the given offset. @type tiletype: L{Expression} """ def __init__(self, x, y, tiletype): self.x = x self.y = y self.tiletype = tiletype def __str__(self): return '{}, {}: {};'.format(self.x, self.y, self.tiletype) nml-0.4.4/nml/ast/assignment.py0000644000567200056720000000600712643457545017570 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic class Assignment(object): """ Simple storage container for a name / value pair. This class does not enforce any type information. Create a subclass to do this, or hard-code it in the parser. @ivar name: Name of the parameter / property / whatever to be set. @type name: Anything @ivar value: Value to assign to @type value: Anything @ivar pos: Position information of the assignment @type pos: L{Position} """ def __init__(self, name, value, pos): self.name = name self.value = value self.pos = pos def debug_print(self, indentation): generic.print_dbg(indentation, 'Assignment') generic.print_dbg(indentation + 2, 'Name:') self.name.debug_print(indentation + 4) generic.print_dbg(indentation + 2, 'Value:') self.value.debug_print(indentation + 4) def __str__(self): return "{}: {};".format(self.name, self.value) class UnitAssignment(Assignment): """ Storage container where the value can have a unit. @ivar unit: Unit of the value, or not C{None} @type unit: L{Unit} """ def __init__(self, name, value, unit, pos): Assignment.__init__(self, name, value, pos) self.unit = unit def debug_print(self, indentation): Assignment.debug_print(self, indentation) generic.print_dbg(indentation + 2, 'Unit:') if self.unit is None: generic.print_dbg(indentation + 4, 'None') else: generic.print_dbg(indentation + 4, self.unit) def __str__(self): if self.unit is None: return Assignment.__str__(self) else: return "{}: {} {};".format(self.name, self.value, self.unit.name) class Range(object): """ Storage container for a range of values (inclusive). This Contains a minimum value and optionally also a maximum value. If the maximum values is omitted, the minimum is also used as maximum. @ivar min: The minimum value of this range. @type min: L{Expression} @ivar max: The maximum value of this range. @type max: L{Expression} or C{None} """ def __init__(self, min, max): self.min = min self.max = max def __str__(self): if self.max is None: return str(self.min) return "{} .. {}".format(self.min, self.max) nml-0.4.4/nml/ast/skipall.py0000644000567200056720000000226212643457545017056 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml.actions import action7 from nml.ast import base_statement from nml import generic class SkipAll(base_statement.BaseStatement): """ Skip everything after this statement. """ def __init__(self, pos): base_statement.BaseStatement.__init__(self, "exit-statement", pos) def get_action_list(self): return [action7.UnconditionalSkipAction(9, 0)] def debug_print(self, indentation): generic.print_dbg(indentation, 'Skip all') def __str__(self): return "exit;\n" nml-0.4.4/nml/ast/font.py0000644000567200056720000000634012643457545016366 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, expression from nml.actions import action12 from nml.ast import base_statement, sprite_container class FontGlyphBlock(base_statement.BaseStatement, sprite_container.SpriteContainer): """ AST class for a font_glyph block Syntax: font_glyph( @ivar font_size: Size of the font to provide characters for (NORMAL/SMALL/LARGE/MONO) @type font_size: L{Expression} @ivar base_char: First character to replace @type base_char: L{Expression} @ivar image_file: Default file to use for the contained real sprites (none if N/A) @type image_file: L{StringLiteral} or C{None} @ivar sprite_list: List of real sprites @type sprite_list: C{list} of L{RealSprite} """ 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 debug_print(self, indentation): generic.print_dbg(indentation, 'Load font glyphs, starting at', self.base_char) generic.print_dbg(indentation + 2, 'Font size: ', self.font_size) generic.print_dbg(indentation + 2, 'Source: ', self.image_file.value if self.image_file is not None else 'None') generic.print_dbg(indentation + 2, 'Sprites:') for sprite in self.sprite_list: sprite.debug_print(indentation + 4) def get_action_list(self): return action12.parse_action12(self) def __str__(self): name = str(self.block_name) if self.block_name is not None else "" params = [self.font_size, self.base_char] if self.image_file is not None: params.append(self.image_file) ret = "font_glyph {}({}) {{\n".format(name, ", ".join(str(param) for param in params)) for sprite in self.sprite_list: ret += "\t{}\n".format(sprite) ret += "}\n" return ret nml-0.4.4/nml/ast/sprite_container.py0000644000567200056720000000647112643457545020775 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic class SpriteContainer(object): """ Base class for all AST Nodes that contain real sprites Note that this does not inherit from BaseStatement, to (amongst other things) avoid various multiple inheritance issues with Spritesets @ivar block_type: Type of block (e.g. 'spriteset' ,'replace') @type block_type: C{str} @ivar block_name: Block-specific name @type block_name: L{Identifier}, or C{None} if N/A @ivar sprite_data: Mapping of (zoom level, bit-depth) to (sprite list, default file) @type sprite_data: C{dict} that maps (C{tuple} of (C{int}, C{int})) to (C{tuple} of (C{list} of (L{RealSprite}, L{RecolourSprite} or L{TemplateUsage}), L{StringLiteral} or C{None}, L{Position})) """ sprite_blocks = {} 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 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 get_all_sprite_data(self): """ Get all sprite data. Sorting makes sure that the order is consistent, and that the normal zoom, 8bpp sprites appear first. @return: List of 6-tuples (sprite_list, default_file, default_mask_file, position, zoom_level, bit_depth). @rtype: C{list} of C{tuple} of (C{list} of (L{RealSprite}, L{RecolourSprite} or L{TemplateUsage}), L{StringLiteral} or C{None}, L{Position}, C{int}, C{int}) """ return [val + key for key, val in sorted(self.sprite_data.items())] @classmethod def resolve_sprite_block(cls, block_name): if block_name.value in cls.sprite_blocks: return cls.sprite_blocks[block_name.value] raise generic.ScriptError("Undeclared block identifier '{}' encountered".format(block_name.value), block_name.pos) nml-0.4.4/nml/ast/snowline.py0000644000567200056720000001520612643457545017257 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import datetime from nml import generic, expression, nmlop from nml.actions import action0 from nml.ast import base_statement class Snowline(base_statement.BaseStatement): """ Snowline curve throughout the year. @ivar type: Type of snowline. @type type: C{str} @ivar date_heights: Height of the snow line at given days in the year. @type date_heights: C{list} of L{UnitAssignment} """ def __init__(self, line_type, height_data, pos): base_statement.BaseStatement.__init__(self, "snowline-block", pos) if line_type.value not in ('equal', 'linear'): raise generic.ScriptError('Unknown type of snow line (only "equal" and "linear" are supported)', line_type.pos) self.type = line_type.value self.date_heights = height_data def debug_print(self, indentation): generic.print_dbg(indentation, 'Snowline (type={})'.format(self.type)) for dh in self.date_heights: dh.debug_print(indentation + 2) def __str__(self): return 'snowline ({}) {{\n\t{}\n}}\n'.format(str(self.type), '\n\t'.join(str(x) for x in self.date_heights)) def get_action_list(self): return action0.get_snowlinetable_action(compute_table(self)) def compute_table(snowline): """ Compute the table with snowline height for each day of the year. @param snowline: Snowline definition. @type snowline: L{Snowline} @return: Table of 12*32 entries with snowline heights. @rtype: C{str} """ day_table = [None]*365 # Height at each day, starting at day 0 for dh in snowline.date_heights: doy = dh.name.reduce() if not isinstance(doy, expression.ConstantNumeric): raise generic.ScriptError('Day of year is not a compile-time constant', doy.pos) if doy.value < 1 or doy.value > 365: raise generic.ScriptError('Day of the year must be between 1 and 365', doy.pos) height = dh.value.reduce() if isinstance(height, expression.ConstantNumeric) and height.value < 0: raise generic.ScriptError('Height must be at least 0', height.pos) if dh.unit is None: if isinstance(height, expression.ConstantNumeric) and height.value > 255: raise generic.ScriptError('Height must be at most 255', height.pos) else: unit = dh.unit if unit.type != 'snowline': raise generic.ScriptError('Expected a snowline percentage ("snow%")', height.pos) if isinstance(height, expression.ConstantNumeric) and height.value > 100: raise generic.ScriptError('Height must be at most 100 snow%', height.pos) mul, div = unit.convert, 1 if isinstance(mul, tuple): mul, div = mul # Factor out common factors gcd = generic.greatest_common_divisor(mul, div) mul //= gcd div //= gcd if isinstance(height, (expression.ConstantNumeric, expression.ConstantFloat)): # Even if mul == div == 1, we have to round floats and adjust value height = expression.ConstantNumeric(int(float(height.value) * mul / div + 0.5), height.pos) elif mul != div: # Compute (value * mul + div/2) / div height = expression.BinOp(nmlop.MUL, height, expression.ConstantNumeric(mul, height.pos), height.pos) height = expression.BinOp(nmlop.ADD, height, expression.ConstantNumeric(int(div / 2), height.pos), height.pos) height = expression.BinOp(nmlop.DIV, height, expression.ConstantNumeric(div, height.pos), height.pos) # For 'linear' snow-line, only accept integer constants. if snowline.type != 'equal' and not isinstance(height, expression.ConstantNumeric): raise generic.ScriptError('Height is not a compile-time constant', height.pos) day_table[doy.value - 1] = height # Find first specified point. start = 0 while start < 365 and day_table[start] is None: start = start + 1 if start == 365: raise generic.ScriptError('No heights given for the snowline table', snowline.pos) first_point = start while True: # Find second point from start end = start + 1 if end == 365: end = 0 while end != first_point and day_table[end] is None: end = end + 1 if end == 365: end = 0 # Fill the days between start and end (exclusive both border values) startvalue = day_table[start] endvalue = day_table[end] unwrapped_end = end if end < start: unwrapped_end += 365 if snowline.type == 'equal': for day in range(start + 1, unwrapped_end): if day >= 365: day -= 365 day_table[day] = startvalue else: assert snowline.type == 'linear' if start != end: dhd = float(endvalue.value - startvalue.value) / float(unwrapped_end - start) else: assert startvalue.value == endvalue.value dhd = 0 for day in range(start + 1, unwrapped_end): uday = day if uday >= 365: uday -= 365 height = startvalue.value + int(round(dhd * (day - start))) day_table[uday] = expression.ConstantNumeric(height) if end == first_point: # All days done break start = end table = [None] * (12*32) for dy in range(365): today = datetime.date.fromordinal(dy + 1) if day_table[dy]: expr = day_table[dy].reduce() else: expr = None table[(today.month - 1) * 32 + today.day - 1] = expr for idx, d in enumerate(table): if d is None: table[idx] = table[idx - 1] #Second loop is needed because we need make sure the first item is also set. for idx, d in enumerate(table): if d is None: table[idx] = table[idx - 1] return table nml-0.4.4/nml/ast/conditional.py0000644000567200056720000000546712643457545017734 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import global_constants, generic from nml.actions import action7 from nml.ast import base_statement class ConditionalList(base_statement.BaseStatementList): """ Wrapper for a complete if/else if/else if/else block. """ def __init__(self, conditionals): assert len(conditionals) > 0 base_statement.BaseStatementList.__init__(self, "if/else-block", conditionals[0].pos, base_statement.BaseStatementList.LIST_TYPE_SKIP, conditionals, in_item = True) def get_action_list(self): return action7.parse_conditional_block(self) def debug_print(self, indentation): generic.print_dbg(indentation, 'Conditional') base_statement.BaseStatementList.debug_print(self, indentation + 2) def __str__(self): ret = '' ret += ' else '.join([str(stmt) for stmt in self.statements]) ret += '\n' return ret class Conditional(base_statement.BaseStatementList): """ Condition along with the code that has to be executed if the condition evaluates to some value not equal to 0. @ivar expr: The expression where the execution of code in this block depends on. @type expr: L{Expression} """ def __init__(self, expr, block, pos): base_statement.BaseStatementList.__init__(self, "if/else-block", pos, base_statement.BaseStatementList.LIST_TYPE_SKIP, block, in_item = True) self.expr = expr def pre_process(self): if self.expr is not None: self.expr = self.expr.reduce(global_constants.const_list) base_statement.BaseStatementList.pre_process(self) def debug_print(self, indentation): if self.expr is not None: generic.print_dbg(indentation, 'Expression:') self.expr.debug_print(indentation + 2) generic.print_dbg(indentation, 'Block:') base_statement.BaseStatementList.debug_print(self, indentation + 2) def __str__(self): ret = '' if self.expr is not None: ret += 'if ({})'.format(self.expr) ret += ' {\n' ret += base_statement.BaseStatementList.__str__(self) ret += '}\n' return ret nml-0.4.4/nml/ast/general.py0000644000567200056720000000510212643457545017030 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from nml.ast import base_statement feature_ids = { 'FEAT_TRAINS': 0x00, 'FEAT_ROADVEHS': 0x01, 'FEAT_SHIPS': 0x02, 'FEAT_AIRCRAFT': 0x03, 'FEAT_STATIONS': 0x04, 'FEAT_CANALS': 0x05, 'FEAT_BRIDGES': 0x06, 'FEAT_HOUSES': 0x07, 'FEAT_GLOBALVARS': 0x08, 'FEAT_INDUSTRYTILES': 0x09, 'FEAT_INDUSTRIES': 0x0A, 'FEAT_CARGOS': 0x0B, 'FEAT_SOUNDEFFECTS': 0x0C, 'FEAT_AIRPORTS': 0x0D, 'FEAT_SIGNALS': 0x0E, 'FEAT_OBJECTS': 0x0F, 'FEAT_RAILTYPES': 0x10, 'FEAT_AIRPORTTILES': 0x11, } def feature_name(feature): """ Given a feature number, return the identifier as normally used for that feature in nml files. @param feature: The feature number to convert to a string. @type feature: C{int} @return: String with feature as used in nml files. @rtype: C{str} """ for name, num in list(feature_ids.items()): if num == feature: return name assert False, "Invalid feature number" def parse_feature(expr): """ Parse an expression into a valid feature number. @param expr: Expression to parse @type expr: L{Expression} @return: A constant number representing the parsed feature @rtype: L{ConstantNumeric} """ expr = expr.reduce_constant([feature_ids]) if expr.value not in list(feature_ids.values()): raise generic.ScriptError("Invalid feature '{:02X}' encountered.".format(expr.value), expr.pos) return expr class MainScript(base_statement.BaseStatementList): def __init__(self, statements): assert len(statements) > 0 base_statement.BaseStatementList.__init__(self, "main script", statements[0].pos, base_statement.BaseStatementList.LIST_TYPE_NONE, statements, False, False, False, False) def __str__(self): res = "" for stmt in self.statements: res += str(stmt) + '\n' return res nml-0.4.4/nml/ast/sort_vehicles.py0000644000567200056720000000435512643457545020275 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, global_constants from nml.ast import base_statement, general from nml.actions import action0 class SortVehicles(base_statement.BaseStatement): """ AST-node representing a sort-vehicles block. @ivar feature: Feature of the item @type feature: L{ConstantNumeric} @ivar vehid_list: List of vehicle ids. @type vehid_list: L{Array}. """ def __init__(self, params, pos): base_statement.BaseStatement.__init__(self, "sort-block", pos) if len(params) != 2: raise generic.ScriptError("Sort-block requires exactly two parameters, got {:d}".format(len(params)), self.pos) self.feature = general.parse_feature(params[0]) self.vehid_list = params[1] def pre_process(self): self.vehid_list = self.vehid_list.reduce(global_constants.const_list) if not isinstance(self.vehid_list, expression.Array) or not all([isinstance(x, expression.ConstantNumeric) for x in self.vehid_list.values]): raise generic.ScriptError("Second parameter is not an array of one of the items in it could not be reduced to a constant number", self.pos) def debug_print(self, indentation): generic.print_dbg(indentation, 'Sort, feature', hex(self.feature.value)) for id in self.vehid_list.values: generic.print_dbg(indentation + 2, 'Vehicle id:', id) def get_action_list(self): return action0.parse_sort_block(self.feature.value, self.vehid_list.values) def __str__(self): return 'sort({:d}, {});\n'.format(self.feature.value, self.vehid_list) nml-0.4.4/nml/ast/alt_sprites.py0000644000567200056720000001341612643457545017753 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic from nml.ast import base_statement, sprite_container """ Store if there are any 32bpp sprites, if so ask to enable the 32bpp blitter via action14 """ any_32bpp_sprites = False zoom_levels = { 'ZOOM_LEVEL_NORMAL' : 0, 'ZOOM_LEVEL_IN_4X' : 1, 'ZOOM_LEVEL_IN_2X' : 2, 'ZOOM_LEVEL_OUT_2X' : 3, 'ZOOM_LEVEL_OUT_4X' : 4, 'ZOOM_LEVEL_OUT_8X' : 5, } bit_depths = { 'BIT_DEPTH_8BPP' : 8, 'BIT_DEPTH_32BPP' : 32. } class AltSpritesBlock(base_statement.BaseStatement): """ AST Node for alternative graphics. These are normally 32bpp graphics, possible for a higher zoom-level than the default sprites. Syntax: alternative_sprites(name, zoom_level, bit_depth[, image_file]) @ivar name: The name of the replace/font_glyph/replace_new/spriteset/base_graphics-block this block contains alternative graphics for. @type name: L{expression.Identifier} @ivar zoom_level: The zoomlevel these graphics are for. @type zoom_level: C{int} @ivar bit_depth: Bit depth these graphics are for @type bit_depth: C{int} @ivar image_file: Default graphics file for the sprites in this block. @type image_file: L{expression.StringLiteral} or C{None} @ivar mask_file: Default graphics file for the mask sprites in this block. @type mask_file: L{expression.StringLiteral} or C{None} @ivar sprite_list: List of real sprites or templates expanding to real sprites. @type sprite_list: Heterogeneous C{list} of L{RealSprite}, L{TemplateUsage} """ 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 pre_process(self): block = sprite_container.SpriteContainer.resolve_sprite_block(self.name) block.add_sprite_data(self.sprite_list, self.image_file, self.pos, self.zoom_level, self.bit_depth, self.mask_file) def debug_print(self, indentation): generic.print_dbg(indentation, 'Alternative sprites') generic.print_dbg(indentation + 2, 'Replacement for sprite:', self.name) generic.print_dbg(indentation + 2, 'Zoom level:', self.zoom_level) generic.print_dbg(indentation + 2, 'Bit depth:', self.bit_depth) generic.print_dbg(indentation + 2, 'Source:', self.image_file.value if self.image_file is not None else 'None') generic.print_dbg(indentation + 2, 'Mask source:', self.mask_file.value if self.mask_file is not None else 'None') generic.print_dbg(indentation + 2, 'Sprites:') for sprite in self.sprite_list: sprite.debug_print(indentation + 4) def get_action_list(self): return [] def __str__(self): params = [self.name, generic.reverse_lookup(zoom_levels, self.zoom_level), generic.reverse_lookup(bit_depths, self.bit_depth)] if self.image_file is not None: params.append(self.image_file) if self.mask_file is not None: params.append(self.mask_file) ret = "alternative_sprites({}) {{\n".format(", ".join(str(p) for p in params)) for sprite in self.sprite_list: ret += "\t{}\n".format(sprite) ret += "}\n" return ret nml-0.4.4/nml/ast/deactivate.py0000644000567200056720000000303212643457545017524 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml.actions import actionE from nml.ast import base_statement from nml import expression, generic class DeactivateBlock(base_statement.BaseStatement): def __init__(self, grfid_list, pos): base_statement.BaseStatement.__init__(self, "deactivate()", pos) self.grfid_list = grfid_list def pre_process(self): # Parse (string-)expressions to integers self.grfid_list = [expression.parse_string_to_dword(grfid.reduce()) for grfid in self.grfid_list] def debug_print(self, indentation): generic.print_dbg(indentation, 'Deactivate other newgrfs:') for grfid in self.grfid_list: grfid.debug_print(indentation + 2) def get_action_list(self): return actionE.parse_deactivate_block(self) def __str__(self): return 'deactivate({});\n'.format(', '.join(str(grfid) for grfid in self.grfid_list)) nml-0.4.4/nml/ast/grf.py0000644000567200056720000003034012643457545016173 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, grfstrings, global_constants from nml.actions import action8, action14 from nml.ast import base_statement palette_node = None blitter_node = None """ Statistics about registers used for parameters. The 1st field is the largest parameter register used. The 2nd field is the maximum amount of parameter registers available. This is where L{action6.free_parameters} begins. """ param_stats = [0, 0x40] def print_stats(): """ Print statistics about used ids. """ if param_stats[0] > 0: generic.print_info("GRF parameter registers: {}/{}".format(param_stats[0], param_stats[1])) def set_palette_used(pal): """ Set the used palette in the action 14 node, if applicable @param pal: Palette to use @type pal: C{str} of length 1 """ if palette_node: palette_node.pal = pal def set_preferred_blitter(blitter): """ Set the preferred blitter in the action14 node, if applicable @param blitter: The blitter to set @type blitter: C{str} of length 1 """ if blitter_node: blitter_node.blitter = blitter class GRF(base_statement.BaseStatement): """ AST Node for a grf block, that supplies (static) information about the GRF This is equivalent to actions 8 and 14 @ivar name: Name of the GRF (short) @type name: L{Expression}, should be L{String} else user error @ivar desc: Description of the GRF (longer) @type name: L{Expression}, should be L{String} else user error @ivar grfid: Globally unique identifier of the GRF @type grfid: L{Expression}, should be L{StringLiteral} of 4 bytes else user error @ivar version: Version of this GRF @type version: L{Expression} @ivar min_compatible_version: Minimum (older) version of the same GRF that it is compatible with @type min_compatible_version: L{Expression} @ivar params: List of user-configurable GRF parameters @type params: C{list} of L{ParameterDescription} """ def __init__(self, alist, pos): base_statement.BaseStatement.__init__(self, "grf-block", pos, False, False) self.name = None self.desc = None self.url = None self.grfid = None self.version = None self.min_compatible_version = None self.params = [] generic.OnlyOnce.enforce(self, "GRF-block") for assignment in alist: if isinstance(assignment, ParameterDescription): self.params.append(assignment) elif assignment.name.value == "name": self.name = assignment.value elif assignment.name.value == "desc": self.desc = assignment.value elif assignment.name.value == "url": self.url = assignment.value elif assignment.name.value == "grfid": self.grfid = assignment.value elif assignment.name.value == "version": self.version = assignment.value elif assignment.name.value == "min_compatible_version": self.min_compatible_version = assignment.value else: raise generic.ScriptError("Unknown item in GRF-block: " + str(assignment.name), assignment.name.pos) 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 manually and combine multiple bool parameters into a single bitmask parameter using .", self.pos) if param_num > param_stats[0]: param_stats[0] = param_num def debug_print(self, indentation): generic.print_dbg(indentation, 'GRF') generic.print_dbg(indentation + 2, 'grfid:') self.grfid.debug_print(indentation + 4) generic.print_dbg(indentation + 2, 'Name:') self.name.debug_print(indentation + 4) generic.print_dbg(indentation + 2, 'Description:') self.desc.debug_print(indentation + 4) if self.url is not None: generic.print_dbg(indentation + 2, 'URL:') self.url.debug_print(indentation + 4) generic.print_dbg(indentation + 2, 'Version:') self.version.debug_print(indentation + 4) generic.print_dbg(indentation + 2, 'Minimal compatible version:') self.min_compatible_version.debug_print(indentation + 4) def get_action_list(self): global palette_node, blitter_node palette_node = action14.UsedPaletteNode("A") blitter_node = action14.BlitterNode("8") action14_root = action14.BranchNode("INFO") action14.grf_name_desc_actions(action14_root, self.name, self.desc, self.url, self.version, self.min_compatible_version) action14.param_desc_actions(action14_root, self.params) action14_root.subnodes.append(palette_node) action14_root.subnodes.append(blitter_node) return action14.get_actions(action14_root) + [action8.Action8(self.grfid, self.name, self.desc)] def __str__(self): ret = 'grf {\n' ret += '\tgrfid: {};\n'.format(str(self.grfid)) ret += '\tname: {};\n'.format(str(self.name)) ret += '\tdesc: {};\n'.format(str(self.desc)) if self.url is not None: ret += '\turl: {};\n'.format(self.url) ret += '\tversion: {};\n'.format(self.version) ret += '\tmin_compatible_version: {};\n'.format(self.min_compatible_version) for param in self.params: ret += str(param) ret += '}\n' return ret class ParameterSetting(object): def __init__(self, name, value_list): self.name = name self.value_list = value_list self.type = 'int' self.name_string = None self.desc_string = None self.min_val = None self.max_val = None self.def_val = None self.bit_num = None self.val_names = [] self.properties_set = set() def pre_process(self): for set_val in self.value_list: self.set_property(set_val.name.value, set_val.value) def __str__(self): ret = "\t\t{} {{\n".format(self.name) for val in self.value_list: if val.name.value == 'names': ret += "\t\t\tnames: {\n" for name in val.value.values: ret += "\t\t\t\t{}: {};\n".format(name.name, name.value) ret += "\t\t\t};\n" else: ret += "\t\t\t{}: {};\n".format(val.name, val.value) ret += "\t\t}\n" return ret def set_property(self, name, value): """ Set a single parameter property @param name: Name of the property to be set @type name: C{str} @param value: Value of the property (note: may be an array) @type value: L{Expression} """ if name in self.properties_set: raise generic.ScriptError("You cannot set the same property twice in a parameter description block", value.pos) self.properties_set.add(name) if name == 'names': for name_value in value.values: num = name_value.name.reduce_constant().value desc = name_value.value if not isinstance(desc, expression.String): raise generic.ScriptError("setting name description must be a string", desc.pos) self.val_names.append((num, desc)) return value = value.reduce(unknown_id_fatal = False) if name == 'type': if not isinstance(value, expression.Identifier) or (value.value != 'int' and value.value != 'bool'): raise generic.ScriptError("setting-type must be either 'int' or 'bool'", value.pos) self.type = value.value elif name == 'name': if not isinstance(value, expression.String): raise generic.ScriptError("setting-name must be a string", value.pos) self.name_string = value elif name == 'desc': if not isinstance(value, expression.String): raise generic.ScriptError("setting-description must be a string", value.pos) self.desc_string = value elif name == 'bit': if self.type != 'bool': raise generic.ScriptError("setting-bit is only valid for 'bool' settings", value.pos) self.bit_num = value.reduce_constant() elif name == 'min_value': if self.type != 'int': raise generic.ScriptError("setting-min_value is only valid for 'int' settings", value.pos) self.min_val = value.reduce_constant() elif name == 'max_value': if self.type != 'int': raise generic.ScriptError("setting-max_value is only valid for 'int' settings", value.pos) self.max_val = value.reduce_constant() elif name == 'def_value': self.def_val = value.reduce_constant() if self.type == 'bool' and self.def_val.value != 0 and self.def_val.value != 1: raise generic.ScriptError("setting-def_value must be either 0 or 1 for 'bool' settings", value.pos) else: raise generic.ScriptError("Unknown setting-property " + name, value.pos) class ParameterDescription(object): def __init__(self, setting_list, num = None, pos = None): self.setting_list = setting_list self.num = num self.pos = pos def __str__(self): ret = "\tparam" if self.num: ret += " " + str(self.num) ret += " {\n" for setting in self.setting_list: ret += str(setting) ret += "\t}\n" return ret def pre_process(self, num): if self.num is None: self.num = num self.num = self.num.reduce_constant() for setting in self.setting_list: setting.pre_process() for setting in self.setting_list: if setting.type == 'int': if len(self.setting_list) > 1: raise generic.ScriptError("When packing multiple settings in one parameter only bool settings are allowed", self.pos) global_constants.settings[setting.name.value] = {'num': self.num.value, 'size': 4} else: bit = 0 if setting.bit_num is None else setting.bit_num.value global_constants.misc_grf_bits[setting.name.value] = {'param': self.num.value, 'bit': bit} nml-0.4.4/nml/ast/spriteblock.py0000644000567200056720000002566312643457545017752 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, global_constants from nml.actions import action2, action2layout, action2real, real_sprite from nml.ast import base_statement, sprite_container class TemplateDeclaration(base_statement.BaseStatement): def __init__(self, name, param_list, sprite_list, pos): base_statement.BaseStatement.__init__(self, "template declaration", pos, False, False) self.name = name self.param_list = param_list self.sprite_list = sprite_list def pre_process(self): #check that all templates that are referred to exist at this point #This prevents circular dependencies for sprite in self.sprite_list: if isinstance(sprite, real_sprite.TemplateUsage): if sprite.name.value == self.name.value: raise generic.ScriptError("Sprite template '{}' includes itself.".format(sprite.name.value), self.pos) elif sprite.name.value not in real_sprite.sprite_template_map: raise generic.ScriptError("Encountered unknown template identifier: " + sprite.name.value, sprite.pos) #Register template if self.name.value not in real_sprite.sprite_template_map: real_sprite.sprite_template_map[self.name.value] = self else: raise generic.ScriptError("Template named '{}' is already defined, first definition at {}".format(self.name.value, real_sprite.sprite_template_map[self.name.value].pos), self.pos) def get_labels(self): labels = {} offset = 0 for sprite in self.sprite_list: sprite_labels, num_sprites = sprite.get_labels() for lbl, lbl_offset in list(sprite_labels.items()): if lbl in labels: raise generic.ScriptError("Duplicate label encountered; '{}' already exists.".format(lbl), self.pos) labels[lbl] = lbl_offset + offset offset += num_sprites return labels, offset def debug_print(self, indentation): generic.print_dbg(indentation, 'Template declaration:', self.name.value) generic.print_dbg(indentation + 2, 'Parameters:') for param in self.param_list: param.debug_print(indentation + 4) generic.print_dbg(indentation + 2, 'Sprites:') for sprite in self.sprite_list: sprite.debug_print(indentation + 4) def get_action_list(self): return [] def __str__(self): ret = "template {}({}) {{\n".format(str(self.name), ", ".join([str(param) for param in self.param_list])) for sprite in self.sprite_list: ret += "\t{}\n".format(sprite) ret += "}\n" return ret spriteset_base_class = action2.make_sprite_group_class(True, True, False, cls_is_relocatable = True) class SpriteSet(spriteset_base_class, sprite_container.SpriteContainer): def __init__(self, param_list, sprite_list, pos): base_statement.BaseStatement.__init__(self, "spriteset", pos, False, False) if not (1 <= len(param_list) <= 2): raise generic.ScriptError("Spriteset requires 1 or 2 parameters, encountered " + str(len(param_list)), pos) name = param_list[0] if not isinstance(name, expression.Identifier): raise generic.ScriptError("Spriteset parameter 1 'name' should be an identifier", name.pos) sprite_container.SpriteContainer.__init__(self, "spriteset", name) self.initialize(name) if len(param_list) >= 2: self.image_file = param_list[1].reduce() if not isinstance(self.image_file, expression.StringLiteral): raise generic.ScriptError("Spriteset-block parameter 2 'file' must be a string literal", self.image_file.pos) else: self.image_file = None self.sprite_list = sprite_list self.action1_num = None #set number in action1 self.labels = {} #mapping of real sprite labels to offsets self.add_sprite_data(self.sprite_list, self.image_file, pos) def pre_process(self): spriteset_base_class.pre_process(self) offset = 0 for sprite in self.sprite_list: sprite_labels, num_sprites = sprite.get_labels() for lbl, lbl_offset in list(sprite_labels.items()): if lbl in self.labels: raise generic.ScriptError("Duplicate label encountered; '{}' already exists.".format(lbl), self.pos) self.labels[lbl] = lbl_offset + offset offset += num_sprites def collect_references(self): return [] def debug_print(self, indentation): generic.print_dbg(indentation, 'Sprite set:', self.name.value) generic.print_dbg(indentation + 2, 'Source: ', self.image_file.value if self.image_file is not None else 'None') generic.print_dbg(indentation + 2, 'Sprites:') for sprite in self.sprite_list: sprite.debug_print(indentation + 4) def get_action_list(self): # Actions are created when parsing the action2s, not here return [] def __str__(self): filename = (", " + str(self.image_file)) if self.image_file is not None else "" ret = "spriteset({}{}) {{\n".format(self.name, filename) for sprite in self.sprite_list: ret += "\t{}\n".format(str(sprite)) ret += "}\n" return ret spritegroup_base_class = action2.make_sprite_group_class(False, True, False) class SpriteGroup(spritegroup_base_class): def __init__(self, name, spriteview_list, pos = None): base_statement.BaseStatement.__init__(self, "spritegroup", pos, False, False) self.initialize(name) self.spriteview_list = spriteview_list def pre_process(self): for spriteview in self.spriteview_list: spriteview.pre_process() spritegroup_base_class.pre_process(self) def collect_references(self): return [] def debug_print(self, indentation): generic.print_dbg(indentation, 'Sprite group:', self.name.value) for spriteview in self.spriteview_list: spriteview.debug_print(indentation + 2) def get_action_list(self): action_list = [] if self.prepare_act2_output(): for feature in sorted(self.feature_set): action_list.extend(action2real.get_real_action2s(self, feature)) return action_list def __str__(self): ret = "spritegroup {} {{\n".format(self.name) for spriteview in self.spriteview_list: ret += "\t{}\n".format(spriteview) ret += "}\n" return ret class SpriteView(object): def __init__(self, name, spriteset_list, pos): self.name = name self.spriteset_list = spriteset_list self.pos = pos def pre_process(self): self.spriteset_list = [x.reduce(global_constants.const_list) for x in self.spriteset_list] for sg_ref in self.spriteset_list: if not (isinstance(sg_ref, expression.SpriteGroupRef) and action2.resolve_spritegroup(sg_ref.name).is_spriteset()): raise generic.ScriptError("Expected a sprite set reference", sg_ref.pos) if len(sg_ref.param_list) != 0: raise generic.ScriptError("Spritesets referenced from a spritegroup may not have parameters.", sg_ref.pos) def debug_print(self, indentation): generic.print_dbg(indentation, 'Sprite view:', self.name.value) generic.print_dbg(indentation + 2, 'Sprite sets:') for spriteset in self.spriteset_list: spriteset.debug_print(indentation + 4) def __str__(self): return "{}: [{}];".format(self.name, ", ".join([str(spriteset) for spriteset in self.spriteset_list])) spritelayout_base_class = action2.make_sprite_group_class(False, True, False) class SpriteLayout(spritelayout_base_class): def __init__(self, name, param_list, layout_sprite_list, pos = None): base_statement.BaseStatement.__init__(self, "spritelayout", pos, False, False) self.initialize(name, None, len(param_list)) self.param_list = param_list self.register_map = {} # Set during action generation for easier referencing self.layout_sprite_list = layout_sprite_list # Do not reduce expressions here as they may contain variables # And the feature is not known yet def pre_process(self): # Check parameter names seen_names = set() for param in self.param_list: if not isinstance(param, expression.Identifier): raise generic.ScriptError("spritelayout parameter names must be identifiers.", param.pos) if param.value in seen_names: raise generic.ScriptError("Duplicate parameter name '{}' encountered.".format(param.value), param.pos) seen_names.add(param.value) spritelayout_base_class.pre_process(self) def collect_references(self): return [] def debug_print(self, indentation): generic.print_dbg(indentation, 'Sprite layout:', self.name.value) generic.print_dbg(indentation + 2, 'Parameters:') for param in self.param_list: param.debug_print(indentation + 4) generic.print_dbg(indentation + 2, 'Sprites:') for layout_sprite in self.layout_sprite_list: layout_sprite.debug_print(indentation + 4) def __str__(self): params = "" if not self.param_list else "({})".format(", ".join(str(x) for x in self.param_list)) return 'spritelayout {}{} {{\n{}\n}}\n'.format(str(self.name), params, '\n'.join(str(x) for x in self.layout_sprite_list)) def get_action_list(self): action_list = [] if self.prepare_act2_output(): for feature in sorted(self.feature_set): action_list.extend(action2layout.get_layout_action2s(self, feature, self.pos)) return action_list class LayoutSprite(object): def __init__(self, ls_type, param_list, pos): self.type = ls_type self.param_list = param_list self.pos = pos def debug_print(self, indentation): generic.print_dbg(indentation, 'Tile layout sprite of type:', self.type) for layout_param in self.param_list: layout_param.debug_print(indentation + 2) def __str__(self): return '\t{} {{\n\t\t{}\n\t}}'.format(self.type, '\n\t\t'.join(str(layout_param) for layout_param in self.param_list)) nml-0.4.4/nml/ast/disable_item.py0000644000567200056720000000543012643457545020040 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, global_constants from nml.actions import action0 from nml.ast import base_statement, general class DisableItem(base_statement.BaseStatement): """ Class representing a 'disable_item' statement in the AST. @ivar feature: Feature of the items to disable @type feature: L{ConstantNumeric} @ivar first_id: First item ID to disable @type first_id: L{ConstantNumeric}, or C{None} if not set @ivar last_id: Last item ID to disable @type last_id: L{ConstantNumeric}, or C{None} if not set """ def __init__(self, param_list, pos): base_statement.BaseStatement.__init__(self, "disable_item()", pos) if not (1 <= len(param_list) <= 3): raise generic.ScriptError("disable_item() requires between 1 and 3 parameters, encountered {:d}.".format(len(param_list)), pos) self.feature = general.parse_feature(param_list[0]) if len(param_list) > 1: self.first_id = param_list[1].reduce_constant(global_constants.const_list) else: self.first_id = None if len(param_list) > 2: self.last_id = param_list[2].reduce_constant(global_constants.const_list) if self.last_id.value < self.first_id.value: raise generic.ScriptError("Last id to disable may not be lower than the first id.", pos) else: self.last_id = None def debug_print(self, indentation): generic.print_dbg(indentation, 'Disable items, feature=' + str(self.feature.value)) if self.first_id is not None: generic.print_dbg(indentation + 2, 'First ID:') self.first_id.debug_print(indentation + 4) if self.last_id is not None: generic.print_dbg(indentation + 2, 'Last ID:') self.last_id.debug_print(indentation + 4) def __str__(self): ret = str(self.feature) if self.first_id is not None: ret += ', ' + str(self.first_id) if self.last_id is not None: ret += ', ' + str(self.last_id) return 'disable_item({});\n'.format(ret) def get_action_list(self): return action0.get_disable_actions(self) nml-0.4.4/nml/ast/base_statement.py0000644000567200056720000001375212643457545020423 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic class BaseStatement(object): """ Base class for a statement (AST node) in NML. Note: All instance variables (except 'pos') are prefixed with "bs_" to avoid naming conflicts @ivar bs_name: Name of the statement type @type bs_name: C{str} @ivar pos: Position information @type pos: L{Position} @ivar bs_skipable: Whether the statement may be skipped using an if-block (default: True) @type bs_skipable: C{bool} @ivar bs_loopable: Whether the statement may be executed multiple times using a while-block (default: True) @type bs_loopable: C{bool} @pre: bs_skipable or not bs_loopable @ivar bs_in_item: Whether the statement may be part of an item block (default: False) @type bs_in_item: C{bool} @ivar bs_out_item: Whether the statement may appear outside of item blocks (default: True) @type bs_out_item: C{bool} """ def __init__(self, name, pos, skipable = True, loopable = True, in_item = False, out_item = True): assert skipable or not loopable self.bs_name = name self.pos = pos self.bs_skipable = skipable self.bs_loopable = loopable self.bs_in_item = in_item self.bs_out_item = out_item def validate(self, scope_list): """ Validate that this statement is in a valid location @param scope_list: List of nested blocks containing this statement @type scope_list: C{list} of L{BaseStatementList} """ seen_item = False for scope in scope_list: if scope.list_type == BaseStatementList.LIST_TYPE_SKIP: if not self.bs_skipable: raise generic.ScriptError("{} may not appear inside a conditional block.".format(self.bs_name), self.pos) if scope.list_type == BaseStatementList.LIST_TYPE_LOOP: if not self.bs_loopable: raise generic.ScriptError("{} may not appear inside a loop.".format(self.bs_name), self.pos) if scope.list_type == BaseStatementList.LIST_TYPE_ITEM: seen_item = True if not self.bs_in_item: raise generic.ScriptError("{} may not appear inside an item block.".format(self.bs_name), self.pos) if not (seen_item or self.bs_out_item): raise generic.ScriptError("{} must appear inside an item block.".format(self.bs_name), self.pos) def register_names(self): """ Called to register identifiers, that must be available before their definition. """ pass def pre_process(self): """ Called to do any pre-processing before the actual action generation. For example, to remove identifiesr """ pass def debug_print(self, indentation): """ Print all AST information to the standard output @param indentation: Print all lines with at least C{indentation} spaces @type indentation: C{int} """ raise NotImplementedError('debug_print must be implemented in BaseStatement-subclass {:r}'.format(type(self))) def get_action_list(self): """ Generate a list of NFO actions associated with this statement @return: A list of action @rtype: C{list} of L{BaseAction} """ raise NotImplementedError('get_action_list must be implemented in BaseStatement-subclass {:r}'.format(type(self))) def __str__(self): """ Generate a string representing this statement in valid NML-code. @return: An NML string representing this action @rtype: C{str} """ raise NotImplementedError('__str__ must be implemented in BaseStatement-subclass {:r}'.format(type(self))) class BaseStatementList(BaseStatement): """ Base class for anything that contains a list of statements @ivar list_type: Type of this list, used for validation logic @type list_type: C{int}, see constants below @pre list_type in (LIST_TYPE_NONE, LIST_TYPE_SKIP, LIST_TYPE_LOOP, LIST_TYPE_ITEM) @ivar statements: List of sub-statements in this block @type statements: C{list} of L{BaseStatement} """ LIST_TYPE_NONE = 0 LIST_TYPE_SKIP = 1 LIST_TYPE_LOOP = 2 LIST_TYPE_ITEM = 3 def __init__(self, name, pos, list_type, statements, skipable = True, loopable = True, in_item = False, out_item = True): BaseStatement.__init__(self, name, pos, skipable, loopable, in_item, out_item) assert list_type in (self.LIST_TYPE_NONE, self.LIST_TYPE_SKIP, self.LIST_TYPE_LOOP, self.LIST_TYPE_ITEM) self.list_type = list_type self.statements = statements def validate(self, scope_list): new_list = scope_list + [self] for stmt in self.statements: stmt.validate(new_list) def register_names(self): for stmt in self.statements: stmt.register_names() def pre_process(self): for stmt in self.statements: stmt.pre_process() def debug_print(self, indentation): for stmt in self.statements: stmt.debug_print(indentation) def get_action_list(self): action_list = [] for stmt in self.statements: action_list.extend(stmt.get_action_list()) return action_list def __str__(self): res = "" for stmt in self.statements: res += '\t' + str(stmt).replace('\n', '\n\t')[0:-1] return res nml-0.4.4/nml/ast/switch.py0000644000567200056720000003360012643457545016720 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, global_constants from nml.actions import action2, action2var, action2random from nml.ast import base_statement, general var_ranges = { 'SELF' : 0x89, 'PARENT' : 0x8A } # Used by Switch and RandomSwitch switch_base_class = action2.make_sprite_group_class(False, True, True) class Switch(switch_base_class): def __init__(self, param_list, body, pos): base_statement.BaseStatement.__init__(self, "switch-block", pos, False, False) if len(param_list) != 4: raise generic.ScriptError("Switch-block requires 4 parameters, encountered " + str(len(param_list)), pos) if not isinstance(param_list[1], expression.Identifier): raise generic.ScriptError("Switch-block parameter 2 'variable range' must be an identifier.", param_list[1].pos) if param_list[1].value in var_ranges: self.var_range = var_ranges[param_list[1].value] else: raise generic.ScriptError("Unrecognized value for switch parameter 2 'variable range': '{}'".format(param_list[1].value), param_list[1].pos) if not isinstance(param_list[2], expression.Identifier): raise generic.ScriptError("Switch-block parameter 3 'name' must be an identifier.", param_list[2].pos) self.initialize(param_list[2], general.parse_feature(param_list[0]).value) self.expr = param_list[3] self.body = body def pre_process(self): var_feature = action2var.get_feature(self) # Feature of the accessed variables self.expr = action2var.reduce_varaction2_expr(self.expr, var_feature) self.body.reduce_expressions(var_feature) switch_base_class.pre_process(self) def collect_references(self): all_refs = [] for result in [r.result for r in self.body.ranges] + [self.body.default]: if result is not None and isinstance(result.value, expression.SpriteGroupRef): all_refs.append(result.value) return all_refs def debug_print(self, indentation): generic.print_dbg(indentation, 'Switch, Feature = {:d}, name = {}'.format(next(iter(self.feature_set)), self.name.value)) generic.print_dbg(indentation + 2, 'Expression:') self.expr.debug_print(indentation + 4) generic.print_dbg(indentation + 2, 'Body:') self.body.debug_print(indentation + 4) def get_action_list(self): if self.prepare_act2_output(): return action2var.parse_varaction2(self) return [] def __str__(self): var_range = 'SELF' if self.var_range == 0x89 else 'PARENT' return 'switch({}, {}, {}, {}) {{\n{}}}\n'.format(str(next(iter(self.feature_set))), var_range, str(self.name), str(self.expr), str(self.body)) class SwitchBody(object): """ AST-node representing the body of a switch block This contains the various ranges as well as the default value @ivar ranges: List of ranges @type ranges: C{list} of L{SwitchRange} @ivar default: Default result to use if no range matches @type default: L{SwitchValue} or C{None} if N/A """ def __init__(self, ranges, default): self.ranges = ranges self.default = default 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 debug_print(self, indentation): for r in self.ranges: r.debug_print(indentation) if self.default is not None: generic.print_dbg(indentation, 'Default:') self.default.debug_print(indentation + 2) def __str__(self): ret = ''.join('\t{}\n'.format(r) for r in self.ranges) if self.default is not None: ret += '\t{}\n'.format(str(self.default)) return ret class SwitchRange(object): def __init__(self, min, max, result, unit = None): self.min = min self.max = max self.result = result self.unit = unit def reduce_expressions(self, var_feature): self.min = self.min.reduce(global_constants.const_list) self.max = self.max.reduce(global_constants.const_list) if self.result.value is not None: self.result.value = action2var.reduce_varaction2_expr(self.result.value, var_feature) def debug_print(self, indentation): generic.print_dbg(indentation, 'Min:') self.min.debug_print(indentation + 2) generic.print_dbg(indentation, 'Max:') self.max.debug_print(indentation + 2) generic.print_dbg(indentation, 'Result:') self.result.debug_print(indentation + 2) def __str__(self): ret = str(self.min) if not isinstance(self.min, expression.ConstantNumeric) or not isinstance(self.max, expression.ConstantNumeric) or self.max.value != self.min.value: ret += '..' + str(self.max) ret += ': ' + str(self.result) return ret class SwitchValue(object): """ Class representing a single returned value or sprite group in a switch-block Also used for random-switch and graphics blocks @ivar value: Value to return @type value: L{Expression} or C{None} @ivar is_return: Whether the return keyword was present @type is_return: C{bool} @ivar pos: Position information @type pos: L{Position} """ def __init__(self, value, is_return, pos): self.value = value self.is_return = is_return self.pos = pos def debug_print(self, indentation): if self.value is None: assert self.is_return generic.print_dbg(indentation, 'Return computed value') else: generic.print_dbg(indentation, 'Return value:' if self.is_return else 'Go to block:') self.value.debug_print(indentation + 2) def __str__(self): if self.value is None: assert self.is_return return 'return;' elif self.is_return: return 'return {};'.format(self.value) else: return '{};'.format(self.value) class RandomSwitch(switch_base_class): def __init__(self, param_list, choices, pos): base_statement.BaseStatement.__init__(self, "random_switch-block", pos, False, False) if not (3 <= len(param_list) <= 4): raise generic.ScriptError("random_switch requires 3 or 4 parameters, encountered {:d}".format(len(param_list)), pos) #feature feature = general.parse_feature(param_list[0]).value #type self.type = param_list[1] # Extract type name and possible argument if isinstance(self.type, expression.Identifier): self.type_count = None elif isinstance(self.type, expression.FunctionCall): if len(self.type.params) == 0: self.type_count = None elif len(self.type.params) == 1: self.type_count = action2var.reduce_varaction2_expr(self.type.params[0], feature) else: raise generic.ScriptError("Value for random_switch parameter 2 'type' can have only one parameter.", self.type.pos) self.type = self.type.name else: raise generic.ScriptError("random_switch parameter 2 'type' should be an identifier, possibly with a parameter.", self.type.pos) #name if not isinstance(param_list[2], expression.Identifier): raise generic.ScriptError("random_switch parameter 3 'name' should be an identifier", pos) name = param_list[2] #triggers self.triggers = param_list[3] if len(param_list) == 4 else expression.ConstantNumeric(0) #body self.choices = [] self.dependent = [] self.independent = [] for choice in choices: if isinstance(choice.probability, expression.Identifier): if choice.probability.value == 'dependent': self.dependent.append(choice.result) continue elif choice.probability.value == 'independent': self.independent.append(choice.result) continue self.choices.append(choice) if len(self.choices) == 0: raise generic.ScriptError("random_switch requires at least one possible choice", pos) self.initialize(name, feature) self.random_act2 = None # Set during action generation to resolve dependent/independent chains def pre_process(self): for choice in self.choices: choice.reduce_expressions(next(iter(self.feature_set))) 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 collect_references(self): all_refs = [] for choice in self.choices: if isinstance(choice.result.value, expression.SpriteGroupRef): all_refs.append(choice.result.value) return all_refs def debug_print(self, indentation): generic.print_dbg(indentation, 'Random') generic.print_dbg(indentation + 2, 'Feature:', next(iter(self.feature_set))) generic.print_dbg(indentation + 2, 'Type:') self.type.debug_print(indentation + 4) generic.print_dbg(indentation + 2, 'Name:', self.name.value) generic.print_dbg(indentation + 2, 'Triggers:') self.triggers.debug_print(indentation + 4) for dep in self.dependent: generic.print_dbg(indentation + 2, 'Dependent on:') dep.debug_print(indentation + 4) for indep in self.independent: generic.print_dbg(indentation + 2, 'Independent from:') indep.debug_print(indentation + 4) generic.print_dbg(indentation + 2, 'Choices:') for choice in self.choices: choice.debug_print(indentation + 4) def get_action_list(self): if self.prepare_act2_output(): return action2random.parse_randomswitch(self) return [] def __str__(self): ret = 'random_switch({}, {}, {}, {}) {{\n'.format(str(next(iter(self.feature_set))), str(self.type), str(self.name), str(self.triggers)) for dep in self.dependent: ret += 'dependent: {};\n'.format(dep) for indep in self.independent: ret += 'independent: {};\n'.format(indep) for choice in self.choices: ret += str(choice) + '\n' ret += '}\n' return ret class RandomChoice(object): """ Class to hold one of the possible choices in a random_switch @ivar probability: Relative chance for this choice to be chosen @type probability: L{Expression} @ivar result: Result of this choice, either another action2 or a return value @type result: L{SwitchValue} """ def __init__ (self, probability, result): self.probability = probability if result.value is None: raise generic.ScriptError("Returning the computed value is not possible in a random_switch, as there is no computed value.", result.pos) self.result = result def reduce_expressions(self, var_feature): self.probability = self.probability.reduce_constant(global_constants.const_list) if self.probability.value <= 0: raise generic.ScriptError("Random probability must be higher than 0", self.probability.pos) self.result.value = action2var.reduce_varaction2_expr(self.result.value, var_feature) def debug_print(self, indentation): generic.print_dbg(indentation, 'Probability:') self.probability.debug_print(indentation + 2) generic.print_dbg(indentation, 'Result:') self.result.debug_print(indentation + 2) def __str__(self): return '{}: {}'.format(self.probability, self.result) nml-0.4.4/nml/ast/base_graphics.py0000644000567200056720000000711512643457545020213 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic from nml.actions import real_sprite from nml.ast import base_statement, sprite_container class BaseGraphics(base_statement.BaseStatement, sprite_container.SpriteContainer): """ AST node for a 'base_graphics' block. NML syntax: base_graphics [block_name]([[sprite_num ,]default_file]) { ..real sprites.. } @ivar image_file: Default image file to use for sprites. @type image_file: C{None} if not specified, else L{StringLiteral} @ivar sprite_num: Sprite number of the first sprite (if provided explicitly) @type sprite_num: L{Expression} or C{None} @ivar sprite_list: List of real sprites to use @type sprite_list: Heterogeneous C{list} of L{RealSprite}, L{TemplateUsage} """ def __init__(self, param_list, sprite_list, name, pos): base_statement.BaseStatement.__init__(self, "base_graphics-block", pos) sprite_container.SpriteContainer.__init__(self, "base_graphics-block", name) num_params = len(param_list) if not (0 <= num_params <= 2): raise generic.ScriptError("base_graphics-block requires 0 to 2 parameters, encountered {:d}".format(num_params), pos) if num_params >= 2: self.sprite_num = param_list[0].reduce_constant() else: self.sprite_num = None if num_params >= 1: self.image_file = param_list[-1].reduce() if not isinstance(self.image_file, expression.StringLiteral): raise generic.ScriptError("The last base_graphics-block parameter '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 debug_print(self, indentation): generic.print_dbg(indentation, 'base_graphics-block') generic.print_dbg(indentation + 2, 'Source:', self.image_file.value if self.image_file is not None else 'None') if self.block_name: generic.print_dbg(indentation + 2, 'Name:', self.block_name) if self.sprite_num is not None: generic.print_dbg(indentation + 2, 'Sprite number:', self.sprite_num) generic.print_dbg(indentation + 2, 'Sprites:') for sprite in self.sprite_list: sprite.debug_print(indentation + 4) def get_action_list(self): actions = real_sprite.parse_sprite_data(self) actions[0].sprite_num = self.sprite_num return actions def __str__(self): name = str(self.block_name) if self.block_name is not None else "" params = [] if self.sprite_num is None else [self.sprite_num] if self.image_file is not None: params.append(self.image_file) ret = "base_graphics {}({}) {{\n".format(name, ", ".join(str(param) for param in params)) for sprite in self.sprite_list: ret += "\t{}\n".format(sprite) ret += "}\n" return ret nml-0.4.4/nml/ast/cargotable.py0000644000567200056720000000362312643457545017524 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, global_constants, expression from nml.actions import action0 from nml.ast import base_statement class CargoTable(base_statement.BaseStatement): def __init__(self, cargo_list, pos): base_statement.BaseStatement.__init__(self, "cargo table", pos, False, False) self.cargo_list = cargo_list generic.OnlyOnce.enforce(self, "cargo table") for i, cargo in enumerate(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 global_constants.cargo_numbers[self.cargo_list[i].value] = i def debug_print(self, indentation): generic.print_dbg(indentation, 'Cargo table') for cargo in self.cargo_list: generic.print_dbg(indentation, 'Cargo:', cargo.value) def get_action_list(self): return action0.get_cargolist_action(self.cargo_list) def __str__(self): ret = 'cargotable {\n' ret += ', '.join([expression.identifier_to_print(cargo.value) for cargo in self.cargo_list]) ret += '\n}\n' return ret nml-0.4.4/nml/ast/override.py0000644000567200056720000000457312643457545017245 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, expression, global_constants from nml.ast import base_statement from nml.actions import action0 class EngineOverride(base_statement.BaseStatement): """ AST Node for an engine override. @ivar grfid: GRFid of the grf to override the engines from. @type grfid: C{int{ @ivar source_grfid: GRFid of the grf that overrides the engines. @type source_grfid: C{int} """ def __init__(self, args, pos): base_statement.BaseStatement.__init__(self, "engine_override()", pos) self.args = args 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 debug_print(self, indentation): generic.print_dbg(indentation, 'Engine override') generic.print_dbg(indentation, 'Source:', self.source_grfid) generic.print_dbg(indentation, 'Target:', self.grfid) def get_action_list(self): return action0.get_engine_override_action(self) def __str__(self): return "engine_override({});\n".format(', '.join(str(x) for x in self.args)) nml-0.4.4/nml/ast/loop.py0000644000567200056720000000362712643457545016376 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml.actions import action7 from nml.ast import base_statement from nml import global_constants, generic class Loop(base_statement.BaseStatementList): """ AST node for a while-loop. @ivar expr: The conditional to check whether the loop continues. @type expr: L{Expression} """ def __init__(self, expr, block, pos): base_statement.BaseStatementList.__init__(self, "while-loop", pos, base_statement.BaseStatementList.LIST_TYPE_LOOP, block, in_item = True) self.expr = expr def pre_process(self): self.expr = self.expr.reduce(global_constants.const_list) base_statement.BaseStatementList.pre_process(self) def debug_print(self, indentation): generic.print_dbg(indentation, 'While loop') generic.print_dbg(indentation + 2, 'Expression:') self.expr.debug_print(indentation + 4) generic.print_dbg(indentation + 2, 'Block:') base_statement.BaseStatementList.debug_print(self, indentation + 4) def get_action_list(self): return action7.parse_loop_block(self) def __str__(self): ret = 'while({}) {{\n'.format(self.expr) ret += base_statement.BaseStatementList.__str__(self) ret += '}\n' return ret nml-0.4.4/nml/ast/railtypetable.py0000644000567200056720000000547712643457545020273 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, global_constants, expression from nml.ast import assignment from nml.actions import action0 from nml.ast import base_statement class RailtypeTable(base_statement.BaseStatement): def __init__(self, railtype_list, pos): base_statement.BaseStatement.__init__(self, "rail type table", pos, False, False) self.railtype_list = railtype_list generic.OnlyOnce.enforce(self, "rail type table") global_constants.is_default_railtype_table = False global_constants.railtype_table.clear() def register_names(self): for i, railtype in enumerate(self.railtype_list): if isinstance(railtype, assignment.Assignment): name = railtype.name val_list = [] for rt in railtype.value: if isinstance(rt, expression.Identifier): val_list.append(expression.StringLiteral(rt.value, rt.pos)) else: val_list.append(rt) expression.parse_string_to_dword(val_list[-1]) # we don't care about the result, only validate the input self.railtype_list[i] = val_list if len(val_list) > 1 else val_list[0] else: name = railtype if isinstance(railtype, expression.Identifier): self.railtype_list[i] = expression.StringLiteral(railtype.value, railtype.pos) expression.parse_string_to_dword(self.railtype_list[i]) # we don't care about the result, only validate the input global_constants.railtype_table[name.value] = i def pre_process(self): pass def debug_print(self, indentation): generic.print_dbg(indentation, 'Railtype table') for railtype in self.railtype_list: generic.print_dbg(indentation + 2, 'Railtype: ', railtype.value) def get_action_list(self): return action0.get_railtypelist_action(self.railtype_list) def __str__(self): ret = 'railtypetable {\n' ret += ', '.join([expression.identifier_to_print(railtype.value) for railtype in self.railtype_list]) ret += '\n}\n' return ret nml-0.4.4/nml/ast/error.py0000644000567200056720000000766612643457545016565 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, expression from nml.actions import actionB from nml.ast import base_statement class Error(base_statement.BaseStatement): """ An error has occured while parsing the GRF. This can be anything ranging from an imcompatible GRF file that was found or a game setting that is set to the wrong value to a wrong combination of parameters. The action taken by the host depends on the severity level of the error. NML equivalent: error(level, message[, extra_text[, parameter1[, parameter2]]]). @ivar params: Extra expressions whose value can be used in the error string. @type params: C{list} of L{Expression} @ivar severity: Severity level of this error, value between 0 and 3. @type severity: L{Expression} @ivar msg: The string to be used for this error message. This can be either one of the predifined error strings or a custom string from the language file. @type msg: L{Expression} @ivar data: Optional extra message that is inserted in place of the second {STRING}-code of msg. @type data: C{None} or L{String} or L{StringLiteral} """ def __init__(self, param_list, pos): base_statement.BaseStatement.__init__(self, "error()", pos) if not 2 <= len(param_list) <= 5: raise generic.ScriptError("'error' expects between 2 and 5 parameters, got " + str(len(param_list)), self.pos) self.severity = param_list[0] self.msg = param_list[1] self.data = param_list[2] if len(param_list) >= 3 else None self.params = param_list[3:] def pre_process(self): self.severity = self.severity.reduce([actionB.error_severity]) self.msg = self.msg.reduce([actionB.default_error_msg]) if self.data: self.data = self.data.reduce() self.params = [x.reduce() for x in self.params] def debug_print(self, indentation): generic.print_dbg(indentation, 'Error message') generic.print_dbg(indentation + 2, 'Message:') self.msg.debug_print(indentation + 4) generic.print_dbg(indentation + 2, 'Severity:') self.severity.debug_print(indentation + 4) generic.print_dbg(indentation, 'Data: ') if self.data is not None: self.data.debug_print(indentation + 4) if len(self.params) > 0: generic.print_dbg(indentation + 2, 'Param1: ') self.params[0].debug_print(indentation + 4) if len(self.params) > 1: generic.print_dbg(indentation + 2, 'Param2: ') self.params[1].debug_print(indentation + 4) def get_action_list(self): return actionB.parse_error_block(self) def __str__(self): sev = str(self.severity) if isinstance(self.severity, expression.ConstantNumeric): for s in actionB.error_severity: if self.severity.value == actionB.error_severity[s]: sev = s break res = 'error({}, {}'.format(sev, self.msg) if self.data is not None: res += ', {}'.format(self.data) if len(self.params) > 0: res += ', {}'.format(self.params[0]) if len(self.params) > 1: res += ', {}'.format(self.params[1]) res += ');\n' return res nml-0.4.4/nml/ast/__init__.py0000644000567200056720000000124312643457545017154 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" nml-0.4.4/nml/ast/townnames.py0000644000567200056720000004030112643457545017426 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import heapq from nml import expression, generic, grfstrings from nml.actions import actionF from nml.ast import base_statement townname_serial = 1 class TownNames(base_statement.BaseStatement): """ 'town_names' ast node. @ivar name: Name ID of the town_name. @type name: C{None}, L{Identifier}, or L{ConstantNumeric} @ivar id_number: Allocated ID number for this town_name action F node. @type id_number: C{None} or C{int} @ivar style_name: Name of the translated string containing the name of the style, if any. @type style_name: C{None} or L{String} @ivar actFs: Action F instance needed before this one. @type actFs: C{list} of L{ActionF} @ivar parts: Parts of the names. @type parts: C{list} of L{TownNamesPart} @ivar param_list: Stored parameter list. @type param_list: C{list} of (L{TownNamesPart} or L{TownNamesParam}) """ def __init__(self, name, param_list, pos): base_statement.BaseStatement.__init__(self, "town_names-block", pos, False, False) self.name = name self.param_list = param_list self.id_number = None self.style_name = None self.actFs = [] self.parts = [] def debug_print(self, indentation): if isinstance(self.name, str): name_text = "name = " + repr(self.name) if self.id_number is not None: name_text += " (allocated number is 0x{:x})".format(self.id_number) elif self.id_number is not None: name_text = "number = 0x{:x}".format(self.id_number) else: name_text = "(unnamed)" generic.print_dbg(indentation, 'Town name ' + name_text) if self.style_name is not None: generic.print_dbg(indentation, " style name string:", self.style_name) for part in self.parts: generic.print_dbg(indentation, "-name part:") part.debug_print(indentation + 2) def pre_process(self): self.actFs = [] self.parts = [] for param in self.param_list: if isinstance(param, TownNamesPart): actFs, part = param.make_actions() self.actFs.extend(actFs) self.parts.append(part) else: if param.key.value != 'styles': raise generic.ScriptError("Expected 'styles' keyword.", param.pos) if len(param.value.params) > 0: raise generic.ScriptError("Parameters of the 'styles' were not expected.", param.pos) if self.style_name is not None: raise generic.ScriptError("'styles' is already defined.", self.pos) self.style_name = param.value if len(self.parts) == 0: raise generic.ScriptError("Missing name parts in a town_names item.", self.pos) # 'name' is actually a number. # Allocate it now, before the self.prepare_output() call (to prevent names to grab it). if self.name is not None and not isinstance(self.name, expression.Identifier): value = self.name.reduce_constant() if not isinstance(value, expression.ConstantNumeric): raise generic.ScriptError("ID should be an integer number.", self.pos) self.id_number = value.value if self.id_number < 0 or self.id_number > 0x7f: raise generic.ScriptError("ID must be a number between 0 and 0x7f (inclusive)", self.pos) if self.id_number not in actionF.free_numbers: raise generic.ScriptError("town names ID 0x{:x} is already used.".format(self.id_number), self.pos) actionF.free_numbers.remove(self.id_number) def __str__(self): ret = 'town_names' if self.name is not None: ret += '({})'.format(self.name) ret += '{{\n{}}}\n'.format(''.join(str(x) for x in self.param_list)) return ret def get_action_list(self): return self.actFs + [actionF.ActionF(self.name, self.id_number, self.style_name, self.parts, self.pos)] class TownNamesPart(object): """ A class containing a town name part. @ivar pieces: Pieces of the town name part. @type pieces: C{list} of (L{TownNamesEntryDefinition} or L{TownNamesEntryText}) @ivar pos: Position information of the parts block. @type pos: L{Position} @ivar startbit: First bit to use for this part, if defined. @type startbit: C{int} or C{None} @ivar num_bits: Number of bits to use, if defined. @type num_bits: C{int} or C{None} """ def __init__(self, pieces, pos): self.pos = pos self.pieces = pieces self.startbit = None self.num_bits = None 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 move_pieces(self): """ Move pieces to new action F instances to make it fit, if needed. @return: Created action F instances. @rtype: C{list} of L{ActionF} @note: Function may change L{pieces}. """ global townname_serial if len(self.pieces) <= 255: return [] # Trivially correct. # There are too many pieces. number_action_f = (len(self.pieces) + 254) // 255 pow2 = 1 while pow2 < number_action_f: pow2 = pow2 * 2 if pow2 < 255: number_action_f = pow2 heap = [] # Heap of (summed probability, subset-of-pieces) i = 0 while i < number_action_f: # Index 'i' is added to have a unique sorting when lists have equal total probabilities. heapq.heappush(heap, (0, i, [])) i = i + 1 finished_actions = [] # Index 'idx' is added to have a unique sorting when pieces have equal probabilities. rev_pieces = sorted(((p.probability.value, idx, p) for idx, p in enumerate(self.pieces)), reverse = True) for prob, _idx, piece in rev_pieces: while True: sub = heapq.heappop(heap) if len(sub[2]) < 255: break # If a subset already has the max number of parts, consider it finished. finished_actions.append(sub) sub[2].append(piece) sub = (sub[0] + prob, sub[1], sub[2]) heapq.heappush(heap, sub) finished_actions.extend(heap) # To ensure the chances do not get messed up due to one part needing less bits for its # selection, all parts are forced to use the same number of bits. max_prob = max(sub[0] for sub in finished_actions) num_bits = 1 while (1 << num_bits) < max_prob: num_bits = num_bits + 1 # Assign to action F actFs = [] for _prob, _idx, sub in finished_actions: actF_name = expression.Identifier("**townname #{:d}**".format(townname_serial), None) townname_serial = townname_serial + 1 town_part = TownNamesPart(sub, self.pos) town_part.set_numbits(num_bits) actF = actionF.ActionF(actF_name, None, None, [town_part], self.pos) actFs.append(actF) # Remove pieces of 'sub' from self.pieces counts = len(self.pieces), len(sub) sub_set = set(sub) self.pieces = [piece for piece in self.pieces if piece not in sub_set] assert len(self.pieces) == counts[0] - counts[1] self.pieces.append(TownNamesEntryDefinition(actF_name, expression.ConstantNumeric(1), self.pos)) # update self.parts return actFs def assign_bits(self, startbit): """ Assign bits for this piece. @param startbit: First bit free for use. @type startbit: C{int} @return: Number of bits needed for this piece. @rtype: C{int} """ assert len(self.pieces) <= 255 total = sum(piece.probability.value for piece in self.pieces) self.startbit = startbit if self.num_bits is None: n = 1 while total > (1 << n): n = n + 1 self.num_bits = n assert (1 << self.num_bits) >= total return self.num_bits def set_numbits(self, numbits): """ Set the number of bits that this part should use. """ assert self.num_bits is None self.num_bits = numbits def debug_print(self, indentation): total = sum(piece.probability.value for piece in self.pieces) generic.print_dbg(indentation, 'Town names part (total {:d})'.format(total)) for piece in self.pieces: piece.debug_print(indentation + 2, total) def __str__(self): return '{{\n\t{}\n}}\n'.format('\n\t'.join(str(piece) for piece in self.pieces)) def get_length(self): size = 3 # textcount, firstbit, bitcount bytes. size += sum(piece.get_length() for piece in self.pieces) return size def resolve_townname_id(self): ''' Resolve the reference numbers to previous C{town_names} blocks. @return: Set of referenced C{town_names} block numbers. ''' blocks = set() for piece in self.pieces: block = piece.resolve_townname_id() if block is not None: blocks.add(block) return blocks def write(self, file): file.print_bytex(len(self.pieces)) file.print_bytex(self.startbit) file.print_bytex(self.num_bits) for piece in self.pieces: piece.write(file) file.newline() class TownNamesParam(object): """ Class containing a parameter of a town name. Currently known key/values: - 'styles' / string expression """ def __init__(self, key, value, pos): self.key = key self.value = value self.pos = pos def __str__(self): return '{}: {};\n'.format(self.key, self.value) class TownNamesEntryDefinition(object): """ An entry in a part referring to a non-final town name, with a given probability. @ivar def_number: Name or number referring to a previous town_names node. @type def_number: L{Identifier} or L{ConstantNumeric} @ivar number: Actual ID to use. @type number: C{None} or C{int} @ivar probability: Probability of picking this reference. @type probability: C{ConstantNumeric} @ivar pos: Position information of the parts block. @type pos: L{Position} """ def __init__(self, def_number, probability, pos): self.def_number = def_number self.number = None self.probability = probability self.pos = pos def pre_process(self): self.number = None if not isinstance(self.def_number, expression.Identifier): self.def_number = self.def_number.reduce_constant() if not isinstance(self.def_number, expression.ConstantNumeric): raise generic.ScriptError("Reference to other town name ID should be an integer number.", self.pos) if self.def_number.value < 0 or self.def_number.value > 0x7f: raise generic.ScriptError("Reference number out of range (must be between 0 and 0x7f inclusive).", self.pos) self.probability = self.probability.reduce_constant() if not isinstance(self.probability, expression.ConstantNumeric): raise generic.ScriptError("Probability should be an integer number.", self.pos) if self.probability.value < 0 or self.probability.value > 0x7f: raise generic.ScriptError("Probability out of range (must be between 0 and 0x7f inclusive).", self.pos) def debug_print(self, indentation, total): if isinstance(self.def_number, expression.Identifier): name_text = "name '" + self.def_number.value + "'" else: name_text = "number 0x{:x}".format(self.def_number.value) generic.print_dbg(indentation, 'Insert town_name ID {} with probability {:d}/{:d}'.format(name_text, self.probability.value, total)) def __str__(self): return 'town_names({}, {:d}),'.format(str(self.def_number), self.probability.value) def get_length(self): return 2 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 write(self, file): file.print_bytex(self.probability.value | 0x80) file.print_bytex(self.number) class TownNamesEntryText(object): """ An entry in a part, a text-string with a given probability. @ivar pos: Position information of the parts block. @type pos: L{Position} """ def __init__(self, id, text, probability, pos): self.id = id self.text = text self.probability = probability self.pos = pos def pre_process(self): if self.id.value != 'text': raise generic.ScriptError("Expected 'text' prefix.", self.pos) if not isinstance(self.text, expression.StringLiteral): raise generic.ScriptError("Expected string literal for the name.", self.pos) self.probability = self.probability.reduce_constant() if not isinstance(self.probability, expression.ConstantNumeric): raise generic.ScriptError("Probability should be an integer number.", self.pos) if self.probability.value < 0 or self.probability.value > 0x7f: raise generic.ScriptError("Probability out of range (must be between 0 and 0x7f inclusive).", self.pos) def debug_print(self, indentation, total): generic.print_dbg(indentation, 'Text {} with probability {:d}/{:d}'.format(self.text.value, self.probability.value, total)) def __str__(self): return 'text({}, {:d}),'.format(self.text, self.probability.value) def get_length(self): return 1 + grfstrings.get_string_size(self.text.value) # probability, text def resolve_townname_id(self): ''' Resolve the reference number to a previous C{town_names} block. @return: C{None}, as being the block number of a referenced previous C{town_names} block. ''' return None def write(self, file): file.print_bytex(self.probability.value) file.print_string(self.text.value, final_zero = True) nml-0.4.4/nml/ast/basecost.py0000644000567200056720000001717412643457545017232 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, global_constants, nmlop from nml.ast import assignment, base_statement from nml.actions import action0 class BaseCost(base_statement.BaseStatement): """ AST Node for a base costs table. @ivar costs: List of base cost values to set. @type costs: C{list} of L{Assignment} """ def __init__(self, costs, pos): base_statement.BaseStatement.__init__(self, "basecost-block", pos) self.costs = costs def pre_process(self): new_costs = [] for cost in self.costs: cost.value = cost.value.reduce(global_constants.const_list) if isinstance(cost.value, expression.ConstantNumeric): generic.check_range(cost.value.value, -8, 16, 'Base cost value', cost.value.pos) cost.value = expression.BinOp(nmlop.ADD, cost.value, expression.ConstantNumeric(8), cost.value.pos).reduce() if isinstance(cost.name, expression.Identifier): if cost.name.value in base_cost_table: cost.name = expression.ConstantNumeric(base_cost_table[cost.name.value][0]) new_costs.append(cost) elif cost.name.value in generic_base_costs: #create temporary list, so it can be sorted for efficiency tmp_list = [] for num, type in list(base_cost_table.values()): if type == cost.name.value: tmp_list.append(assignment.Assignment(expression.ConstantNumeric(num), cost.value, cost.name.pos)) tmp_list.sort(key=lambda x: x.name.value) new_costs.extend(tmp_list) else: raise generic.ScriptError("Unrecognized base cost identifier '{}' encountered".format(cost.name.value), cost.name.pos) else: cost.name = cost.name.reduce() if isinstance(cost.name, expression.ConstantNumeric): generic.check_range(cost.name.value, 0, len(base_cost_table), 'Base cost number', cost.name.pos) new_costs.append(cost) self.costs = new_costs def debug_print(self, indentation): generic.print_dbg(indentation, 'Base costs') for cost in self.costs: cost.debug_print(indentation + 2) def get_action_list(self): return action0.get_basecost_action(self) def __str__(self): ret = "basecost {\n" for cost in self.costs: ret += "\t{}: {};\n".format(cost.name, cost.value) ret += "}\n" return ret base_cost_table = { 'PR_STATION_VALUE' : (0, ''), 'PR_BUILD_RAIL' : (1, 'PR_CONSTRUCTION'), 'PR_BUILD_ROAD' : (2, 'PR_CONSTRUCTION'), 'PR_BUILD_SIGNALS' : (3, 'PR_CONSTRUCTION'), 'PR_BUILD_BRIDGE' : (4, 'PR_CONSTRUCTION'), 'PR_BUILD_DEPOT_TRAIN' : (5, 'PR_CONSTRUCTION'), 'PR_BUILD_DEPOT_ROAD' : (6, 'PR_CONSTRUCTION'), 'PR_BUILD_DEPOT_SHIP' : (7, 'PR_CONSTRUCTION'), 'PR_BUILD_TUNNEL' : (8, 'PR_CONSTRUCTION'), 'PR_BUILD_STATION_RAIL' : (9, 'PR_CONSTRUCTION'), 'PR_BUILD_STATION_RAIL_LENGTH' : (10, 'PR_CONSTRUCTION'), 'PR_BUILD_STATION_AIRPORT' : (11, 'PR_CONSTRUCTION'), 'PR_BUILD_STATION_BUS' : (12, 'PR_CONSTRUCTION'), 'PR_BUILD_STATION_TRUCK' : (13, 'PR_CONSTRUCTION'), 'PR_BUILD_STATION_DOCK' : (14, 'PR_CONSTRUCTION'), 'PR_BUILD_VEHICLE_TRAIN' : (15, 'PR_BUILD_VEHICLE'), 'PR_BUILD_VEHICLE_WAGON' : (16, 'PR_BUILD_VEHICLE'), 'PR_BUILD_VEHICLE_AIRCRAFT' : (17, 'PR_BUILD_VEHICLE'), 'PR_BUILD_VEHICLE_ROAD' : (18, 'PR_BUILD_VEHICLE'), 'PR_BUILD_VEHICLE_SHIP' : (19, 'PR_BUILD_VEHICLE'), 'PR_BUILD_TREES' : (20, 'PR_CONSTRUCTION'), 'PR_TERRAFORM' : (21, 'PR_CONSTRUCTION'), 'PR_CLEAR_GRASS' : (22, 'PR_CONSTRUCTION'), 'PR_CLEAR_ROUGH' : (23, 'PR_CONSTRUCTION'), 'PR_CLEAR_ROCKS' : (24, 'PR_CONSTRUCTION'), 'PR_CLEAR_FIELDS' : (25, 'PR_CONSTRUCTION'), 'PR_CLEAR_TREES' : (26, 'PR_CONSTRUCTION'), 'PR_CLEAR_RAIL' : (27, 'PR_CONSTRUCTION'), 'PR_CLEAR_SIGNALS' : (28, 'PR_CONSTRUCTION'), 'PR_CLEAR_BRIDGE' : (29, 'PR_CONSTRUCTION'), 'PR_CLEAR_DEPOT_TRAIN' : (30, 'PR_CONSTRUCTION'), 'PR_CLEAR_DEPOT_ROAD' : (31, 'PR_CONSTRUCTION'), 'PR_CLEAR_DEPOT_SHIP' : (32, 'PR_CONSTRUCTION'), 'PR_CLEAR_TUNNEL' : (33, 'PR_CONSTRUCTION'), 'PR_CLEAR_WATER' : (34, 'PR_CONSTRUCTION'), 'PR_CLEAR_STATION_RAIL' : (35, 'PR_CONSTRUCTION'), 'PR_CLEAR_STATION_AIRPORT' : (36, 'PR_CONSTRUCTION'), 'PR_CLEAR_STATION_BUS' : (37, 'PR_CONSTRUCTION'), 'PR_CLEAR_STATION_TRUCK' : (38, 'PR_CONSTRUCTION'), 'PR_CLEAR_STATION_DOCK' : (39, 'PR_CONSTRUCTION'), 'PR_CLEAR_HOUSE' : (40, 'PR_CONSTRUCTION'), 'PR_CLEAR_ROAD' : (41, 'PR_CONSTRUCTION'), 'PR_RUNNING_TRAIN_STEAM' : (42, 'PR_RUNNING'), 'PR_RUNNING_TRAIN_DIESEL' : (43, 'PR_RUNNING'), 'PR_RUNNING_TRAIN_ELECTRIC' : (44, 'PR_RUNNING'), 'PR_RUNNING_AIRCRAFT' : (45, 'PR_RUNNING'), 'PR_RUNNING_ROADVEH' : (46, 'PR_RUNNING'), 'PR_RUNNING_SHIP' : (47, 'PR_RUNNING'), 'PR_BUILD_INDUSTRY' : (48, 'PR_CONSTRUCTION'), 'PR_CLEAR_INDUSTRY' : (49, 'PR_CONSTRUCTION'), 'PR_BUILD_UNMOVABLE' : (50, 'PR_CONSTRUCTION'), 'PR_CLEAR_UNMOVABLE' : (51, 'PR_CONSTRUCTION'), 'PR_BUILD_WAYPOINT_RAIL' : (52, 'PR_CONSTRUCTION'), 'PR_CLEAR_WAYPOINT_RAIL' : (53, 'PR_CONSTRUCTION'), 'PR_BUILD_WAYPOINT_BUOY' : (54, 'PR_CONSTRUCTION'), 'PR_CLEAR_WAYPOINT_BUOY' : (55, 'PR_CONSTRUCTION'), 'PR_TOWN_ACTION' : (56, 'PR_CONSTRUCTION'), 'PR_BUILD_FOUNDATION' : (57, 'PR_CONSTRUCTION'), 'PR_BUILD_INDUSTRY_RAW' : (58, 'PR_CONSTRUCTION'), 'PR_BUILD_TOWN' : (59, 'PR_CONSTRUCTION'), 'PR_BUILD_CANAL' : (60, 'PR_CONSTRUCTION'), 'PR_CLEAR_CANAL' : (61, 'PR_CONSTRUCTION'), 'PR_BUILD_AQUEDUCT' : (62, 'PR_CONSTRUCTION'), 'PR_CLEAR_AQUEDUCT' : (63, 'PR_CONSTRUCTION'), 'PR_BUILD_LOCK' : (64, 'PR_CONSTRUCTION'), 'PR_CLEAR_LOCK' : (65, 'PR_CONSTRUCTION'), 'PR_MAINTENANCE_RAIL' : (66, 'PR_RUNNING'), 'PR_MAINTENANCE_ROAD' : (67, 'PR_RUNNING'), 'PR_MAINTENANCE_CANAL' : (68, 'PR_RUNNING'), 'PR_MAINTENANCE_STATION' : (69, 'PR_RUNNING'), 'PR_MAINTENANCE_AIRPORT' : (70, 'PR_RUNNING'), } generic_base_costs = ['PR_CONSTRUCTION', 'PR_RUNNING', 'PR_BUILD_VEHICLE'] nml-0.4.4/nml/ast/replace.py0000644000567200056720000001466412643457545017043 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, global_constants from nml.actions import actionA, action5 from nml.ast import base_statement, sprite_container class ReplaceSprite(base_statement.BaseStatement, sprite_container.SpriteContainer): """ AST node for a 'replace' block. NML syntax: replace [name] (start_id[, default_file]) { ..real sprites.. } @ivar start_id: First sprite to replace. Extracted from C{param_list} during pre-processing. @type start_id: C{Expression} @ivar image_file: Default image file to use for sprites. @type image_file: C{None} if not specified, else L{StringLiteral} @ivar sprite_list: List of real sprites to use @type sprite_list: Heterogeneous C{list} of L{RealSprite}, L{TemplateUsage} """ 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 pre_process(self): self.start_id = self.start_id.reduce(global_constants.const_list) def debug_print(self, indentation): generic.print_dbg(indentation, 'Replace sprites starting at') self.start_id.debug_print(indentation + 2) generic.print_dbg(indentation + 2, 'Source:', self.image_file.value if self.image_file is not None else 'None') if self.block_name is not None: generic.print_dbg(indentation + 2, 'Name:', self.block_name.value) generic.print_dbg(indentation + 2, 'Sprites:') for sprite in self.sprite_list: sprite.debug_print(indentation + 4) def get_action_list(self): return actionA.parse_actionA(self) def __str__(self): name = str(self.block_name) if self.block_name is not None else "" def_file = "" if self.image_file is None else ", " + str(self.image_file) ret = "replace {}({}{}) {{\n".format(name, self.start_id, def_file) for sprite in self.sprite_list: ret += "\t{}\n".format(sprite) ret += "}\n" return ret class ReplaceNewSprite(base_statement.BaseStatement, sprite_container.SpriteContainer): """ AST node for a 'replacenew' block. NML syntax: replacenew [name](type[, default_file[, offset]]) { ..real sprites.. } @ivar type: Type of sprites to replace. @type type: L{Identifier} @ivar image_file: Default image file to use for sprites. @type image_file: C{None} if not specified, else L{StringLiteral} @ivar offset: Offset into the block of sprites. @type offset: C{int} @ivar sprite_list: List of real sprites to use @type sprite_list: Heterogeneous C{list} of L{RealSprite}, L{TemplateUsage} """ def __init__(self, param_list, sprite_list, name, pos): base_statement.BaseStatement.__init__(self, "replacenew-block", pos) sprite_container.SpriteContainer.__init__(self, "replacenew-block", name) num_params = len(param_list) if not (1 <= num_params <= 3): raise generic.ScriptError("replacenew-block requires 1 to 3 parameters, encountered " + str(num_params), pos) self.type = param_list[0] if not isinstance(self.type, expression.Identifier): raise generic.ScriptError("replacenew parameter 'type' must be an identifier of a sprite replacement type", self.type.pos) if num_params >= 2: self.image_file = param_list[1].reduce() if not isinstance(self.image_file, expression.StringLiteral): raise generic.ScriptError("replacenew-block parameter 2 'file' must be a string literal", self.image_file.pos) else: self.image_file = None if num_params >= 3: self.offset = param_list[2].reduce_constant().value generic.check_range(self.offset, 0, 0xFFFF, "replacenew-block parameter 3 'offset'", param_list[2].pos) else: self.offset = 0 self.sprite_list = sprite_list self.add_sprite_data(self.sprite_list, self.image_file, pos) def debug_print(self, indentation): generic.print_dbg(indentation, 'Replace sprites for new features of type', self.type) generic.print_dbg(indentation + 2, 'Offset: ', self.offset) generic.print_dbg(indentation + 2, 'Source: ', self.image_file.value if self.image_file is not None else 'None') if self.block_name is not None: generic.print_dbg(indentation + 2, 'Name:', self.block_name.value) generic.print_dbg(indentation + 2, 'Sprites:') for sprite in self.sprite_list: sprite.debug_print(indentation + 4) def get_action_list(self): return action5.parse_action5(self) def __str__(self): name = str(self.block_name) if self.block_name is not None else "" params = [self.type] if self.image_file is not None: params.append(self.image_file) if self.offset != 0: params.append(self.offset) ret = "replacenew {}({}) {{\n".format(name, ", ".join(str(param) for param in params)) for sprite in self.sprite_list: ret += "\t{}\n".format(sprite) ret += "}\n" return ret nml-0.4.4/nml/ast/item.py0000644000567200056720000002672312643457545016365 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, global_constants, unit from nml.ast import base_statement, general from nml.actions import action0, action2, action2var, action3 item_feature = None item_id = None item_size = None class Item(base_statement.BaseStatementList): """ AST-node representing an item block @ivar feature: Feature of the item @type feature: L{ConstantNumeric} @ivar name: Name of the item @type name: L{Identifier} or C{None} if N/A. @ivar id: Numeric ID of the item @type id: L{ConstantNumeric} or C{None} if N/A @ivar size: Size, used by houses only @type size: L{ConstantNumeric} or C{None} if N/A """ def __init__(self, params, body, pos): base_statement.BaseStatementList.__init__(self, "item-block", pos, base_statement.BaseStatementList.LIST_TYPE_ITEM, body) if not (1 <= len(params) <= 4): raise generic.ScriptError("Item block requires between 1 and 4 parameters, found {:d}.".format(len(params)), self.pos) self.feature = general.parse_feature(params[0]) if self.feature.value in (0x08, 0x0C, 0x0E): raise generic.ScriptError("Defining item blocks for this feature is not allowed.", self.pos) self.name = params[1] if len(params) >= 2 else None self.id = params[2].reduce_constant(global_constants.const_list) if len(params) >= 3 else None if isinstance(self.id, expression.ConstantNumeric) and self.id.value == -1: self.id = None # id == -1 means default if len(params) >= 4: if self.feature.value != 0x07: raise generic.ScriptError("item-block parameter 4 'size' may only be set for houses", params[3].pos) self.size = params[3].reduce_constant(global_constants.const_list) if self.size.value not in list(action0.house_sizes.keys()): raise generic.ScriptError("item-block parameter 4 'size' does not have a valid value", self.size.pos) else: self.size = None def register_names(self): if self.name: if not isinstance(self.name, expression.Identifier): raise generic.ScriptError("Item parameter 2 'name' should be an identifier", self.pos) if self.name.value in global_constants.item_names: existing_id = global_constants.item_names[self.name.value].id if self.id is not None and existing_id.value != self.id.value: raise generic.ScriptError("Duplicate item with name '{}'. This item has already been assigned to id {:d}, cannot reassign to id {:d}".format(self.name.value, existing_id.value, self.id.value), self.pos) self.id = existing_id # We may have to reserve multiple item IDs for houses num_ids = action0.house_sizes[self.size.value] if self.size is not None else 1 if self.id is None: self.id = expression.ConstantNumeric(action0.get_free_id(self.feature.value, num_ids, self.pos)) elif not action0.check_id_range(self.feature.value, self.id.value, num_ids, self.id.pos): action0.mark_id_used(self.feature.value, self.id.value, num_ids) if self.name is not None: global_constants.item_names[self.name.value] = self base_statement.BaseStatementList.register_names(self) def pre_process(self): global item_feature, item_id, item_size item_id = self.id item_feature = self.feature.value item_size = self.size base_statement.BaseStatementList.pre_process(self) def debug_print(self, indentation): generic.print_dbg(indentation, 'Item, feature', hex(self.feature.value)) base_statement.BaseStatementList.debug_print(self, indentation + 2) def get_action_list(self): global item_feature, item_id, item_size item_id = self.id item_feature = self.feature.value item_size = self.size return base_statement.BaseStatementList.get_action_list(self) def __str__(self): ret = 'item({:d}'.format(self.feature.value) if self.name is not None: ret += ', {}'.format(self.name) ret += ', {}'.format(str(self.id) if self.id is not None else "-1") if self.size is not None: sizes =["1X1", None, "2X1", "1X2", "2X2"] ret += ', HOUSE_SIZE_{}'.format(sizes[self.size.value]) ret += ') {\n' ret += base_statement.BaseStatementList.__str__(self) ret += '}\n' return ret class Property(object): """ AST-node representing a single property. These are only valid insde a PropertyBlock. @ivar name: The name (or number) of this property. @type name: L{Identifier} or L{ConstantNumeric}. @ivar value: The value that will be assigned to this property. @type value: L{Expression}. @ivar unit: The unit of the value. @type unit: L{Unit} @ivar pos: Position information. @type pos: L{Position} """ def __init__(self, name, value, unit, pos): self.pos = pos self.name = name self.value = value self.unit = unit def pre_process(self): self.value = self.value.reduce(global_constants.const_list, unknown_id_fatal = False) def debug_print(self, indentation): generic.print_dbg(indentation, 'Property:', self.name.value) self.value.debug_print(indentation + 2) def __str__(self): unit = '' if self.unit is None else ' ' + str(self.unit) return '\t{}: {}{};'.format(self.name, self.value, unit) class PropertyBlock(base_statement.BaseStatement): """ Block that contains a list of property/value pairs to be assigned to the current item. @ivar prop_list: List of properties. @type prop_list: C{list} of L{Property} """ def __init__(self, prop_list, pos): base_statement.BaseStatement.__init__(self, "property-block", pos, in_item = True, out_item = False) self.prop_list = prop_list def pre_process(self): for prop in self.prop_list: prop.pre_process() def debug_print(self, indentation): generic.print_dbg(indentation, 'Property block:') for prop in self.prop_list: prop.debug_print(indentation + 2) def get_action_list(self): return action0.parse_property_block(self.prop_list, item_feature, item_id, item_size) def __str__(self): ret = 'property {\n' for prop in self.prop_list: ret += '{}\n'.format(prop) ret += '}\n' return ret class LiveryOverride(base_statement.BaseStatement): def __init__(self, wagon_id, graphics_block, pos): base_statement.BaseStatement.__init__(self, "livery override", pos, in_item = True, out_item = False) self.graphics_block = graphics_block self.wagon_id = wagon_id def pre_process(self): self.graphics_block.pre_process() pass def debug_print(self, indentation): generic.print_dbg(indentation, 'Livery override, wagon id:') self.wagon_id.debug_print(indentation + 2) for graphics in self.graphics_block.graphics_list: graphics.debug_print(indentation + 2) generic.print_dbg(indentation + 2, 'Default graphics:', self.graphics_block.default_graphics) def get_action_list(self): wagon_id = self.wagon_id.reduce_constant([(global_constants.item_names, global_constants.item_to_id)]) return action3.parse_graphics_block(self.graphics_block, item_feature, wagon_id, item_size, True) def __str__(self): ret = 'livery_override({}) {{\n'.format(self.wagon_id) for graphics in self.graphics_block.graphics_list: ret += "\t{}\n".format(graphics) if self.graphics_block.default_graphics is not None: ret += '\t{}\n'.format(self.graphics_block.default_graphics) ret += '}\n' return ret graphics_base_class = action2.make_sprite_group_class(False, False, True) class GraphicsBlock(graphics_base_class): def __init__(self, graphics_list, default_graphics, pos): base_statement.BaseStatement.__init__(self, "graphics-block", pos, in_item = True, out_item = False) self.graphics_list = graphics_list self.default_graphics = default_graphics def pre_process(self): for graphics_def in self.graphics_list: graphics_def.reduce_expressions(item_feature) if self.default_graphics is not None: if self.default_graphics.value is None: raise generic.ScriptError("Returning the computed value is not possible in a graphics-block, as there is no computed value.", self.pos) self.default_graphics.value = action2var.reduce_varaction2_expr(self.default_graphics.value, item_feature) # initialize base class and pre_process it as well (in that order) self.initialize(None, item_feature) graphics_base_class.pre_process(self) def collect_references(self): all_refs = [] for result in [g.result for g in self.graphics_list] + ([self.default_graphics] if self.default_graphics is not None else []): if isinstance(result.value, expression.SpriteGroupRef): all_refs.append(result.value) return all_refs def debug_print(self, indentation): generic.print_dbg(indentation, 'Graphics block:') for graphics in self.graphics_list: graphics.debug_print(indentation + 2) if self.default_graphics is not None: generic.print_dbg(indentation + 2, 'Default graphics:') self.default_graphics.debug_print(indentation + 4) def get_action_list(self): if self.prepare_act2_output(): return action3.parse_graphics_block(self, item_feature, item_id, item_size) return [] def __str__(self): ret = 'graphics {\n' for graphics in self.graphics_list: ret += "\t{}\n".format(graphics) if self.default_graphics is not None: ret += '\t{}\n'.format(self.default_graphics) ret += '}\n' return ret class GraphicsDefinition(object): def __init__(self, cargo_id, result, unit = None): self.cargo_id = cargo_id self.result = result self.unit = unit def reduce_expressions(self, var_feature): # Do not reduce cargo-id (yet) if self.result.value is None: raise generic.ScriptError("Returning the computed value is not possible in a graphics-block, as there is no computed value.", self.result.pos) self.result.value = action2var.reduce_varaction2_expr(self.result.value, var_feature) def debug_print(self, indentation): generic.print_dbg(indentation, 'Cargo ID:') self.cargo_id.debug_print(indentation + 2) generic.print_dbg(indentation, 'Result:') self.result.debug_print(indentation + 2) def __str__(self): return '{}: {}'.format(self.cargo_id, self.result) nml-0.4.4/nml/ast/produce.py0000644000567200056720000000611712643457545017063 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic from nml.actions import action2, action2var, action2production from nml.ast import base_statement produce_base_class = action2.make_sprite_group_class(False, True, True) class Produce(produce_base_class): """ AST node for a 'produce'-block, which is basically equivalent to the production callback. Syntax: produce(name, sub1, sub2, sub3, add1, add2[, again]) @ivar param_list: List of parameters supplied to the produce-block. - 0..2: Amounts of cargo to subtract from input - 3..4: Amounts of cargo to add to output - 5: Run the production CB again if nonzero @type param_list: C{list} of L{Expression} """ def __init__(self, param_list, pos): base_statement.BaseStatement.__init__(self, "produce-block", pos, False, False) if not (6 <= len(param_list) <= 7): raise generic.ScriptError("produce-block requires 6 or 7 parameters, encountered {:d}".format(len(param_list)), self.pos) name = param_list[0] if not isinstance(name, expression.Identifier): raise generic.ScriptError("produce parameter 1 'name' should be an identifier.", name.pos) self.initialize(name, 0x0A) self.param_list = param_list[1:] if len(self.param_list) < 6: self.param_list.append(expression.ConstantNumeric(0)) def pre_process(self): 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 collect_references(self): return [] def __str__(self): return 'produce({});\n'.format(', '.join(str(x) for x in [self.name] + self.param_list)) def debug_print(self, indentation): generic.print_dbg(indentation, 'Produce, name =', self.name) generic.print_dbg(indentation + 2, 'Subtract from input:') for expr in self.param_list[0:3]: expr.debug_print(indentation + 4) generic.print_dbg(indentation + 2, 'Add to output:') for expr in self.param_list[3:5]: expr.debug_print(indentation + 4) generic.print_dbg(indentation + 2, 'Again:') self.param_list[5].debug_print(indentation + 4) def get_action_list(self): if self.prepare_act2_output(): return action2production.get_production_actions(self) return [] nml-0.4.4/nml/_lz77.c0000644000567200056720000001332612643457545015367 0ustar jenkinsjenkins00000000000000/* NML is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * NML is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with NML; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ /** * @file * Cython accelerator module. * * Loaded by lz77.py if available, to replace the universal python code if possible. */ #include /** * Resizeable buffer/array. */ typedef struct { char *buf; ///< Plain data. int used; ///< Number of bytes used. int allocated; ///< Number of bytes allocated. } buffer_t; /** * Append a byte to a buffer. * If the buffer is too small, it is enlarged. * @param data Buffer. * @param b Byte to add. */ static inline void append_byte(buffer_t *data, char b) { if (data->used + 1 > data->allocated) { data->allocated += 0x10000; data->buf = realloc(data->buf, data->allocated); } data->buf[data->used++] = b; } /** * Append a byte array to a buffer. * If the buffer is too small, it is enlarged. * @param data Buffer. * @param b Data to add. * @param l Size of \a b. */ static inline void append_bytes(buffer_t *data, const char *b, int l) { if (data->used + l > data->allocated) { data->allocated = (data->used + l + 0xFFFF) & ~0xFFFF; data->buf = realloc(data->buf, data->allocated); } memcpy(data->buf + data->used, b, l); data->used += l; } /** * Locate a byte sequence in a buffer. * @param pat_data Pattern to look for. * @param pat_size Size of \a pat_data. * @param data Data to search in. * @param data__size Size of \a data. * @return Offset of first occurence, or -1 if no match. */ static inline int find(const char *pat_data, int pat_size, const char *data, int data_size) { int i; for (i = 0; i + pat_size <= data_size; ++i) { /* Apparently this loop is optimised quite well by gcc. * Usage of __builtin_bcmp performed terrible, since I didn't manage to get it inlined. * It it worth noting though, that pat_size is very small (<= 16). */ int j = 0; while (j < pat_size && pat_data[j] == data[i + j]) ++j; if (j == pat_size) return i; } return -1; } /** * GRF compression algorithm. * @param input_data Uncompressed data. * @param input_size Size of \a input_data. * @param output Compressed data. */ static void encode(const char *input_data, int input_size, buffer_t *output) { char literal[0x80]; int literal_size = 0; int position = 0; while (position < input_size) { int start_pos = position - (1 << 11) + 1; if (start_pos < 0) start_pos = 0; /* Loop through the lookahead buffer. */ int max_look = input_size - position + 1; if (max_look > 16) max_look = 16; int overlap_pos = 0; int overlap_len = 0; int i; for (i = 3; i < max_look; ++i) { /* Find the pattern match in the window. */ int result = find(input_data + position, i, input_data + start_pos, position - start_pos); /* If match failed, we've found the longest. */ if (result < 0) break; overlap_pos = position - start_pos - result; overlap_len = i; start_pos += result; } if (overlap_len > 0) { if (literal_size > 0) { append_byte(output, literal_size); append_bytes(output, literal, literal_size); literal_size = 0; } int val = 0x80 | (16 - overlap_len) << 3 | overlap_pos >> 8; append_byte(output, val); append_byte(output, overlap_pos & 0xFF); position += overlap_len; } else { literal[literal_size++] = input_data[position]; if (literal_size == sizeof(literal)) { append_byte(output, 0); append_bytes(output, literal, literal_size); literal_size = 0; } position += 1; } } if (literal_size > 0) { append_byte(output, literal_size); append_bytes(output, literal, literal_size); literal_size = 0; } } /** * Interface method to Python. * * @param self Unused. * @param args Uncompressed data as "str", "bytes", "bytearray", or anything providing the buffer interface. * @return Compressed data as "bytes". */ static PyObject *lz77_encode(PyObject *self, PyObject *args) { PyObject *result = NULL; Py_buffer input; if (PyArg_ParseTuple(args, "s*", &input) && input.buf) { buffer_t output = {0}; Py_BEGIN_ALLOW_THREADS encode((const char*)input.buf, input.len, &output); Py_END_ALLOW_THREADS PyBuffer_Release(&input); result = Py_BuildValue("y#", output.buf, output.used); free(output.buf); } return result; } /** * Function table. */ static PyMethodDef lz77Methods[] = { {"encode", lz77_encode, METH_VARARGS, "GRF compression algorithm"}, {NULL, NULL, 0, NULL} }; /** * Module table. */ static struct PyModuleDef lz77module = { PyModuleDef_HEAD_INIT, "nml_lz77", NULL, -1, lz77Methods }; /** * Module intialisation. * Called by Python upon import. */ PyMODINIT_FUNC PyInit_nml_lz77(void) { return PyModule_Create(&lz77module); } nml-0.4.4/nml/__version__.py0000644000567200056720000000007312643457623017104 0ustar jenkinsjenkins00000000000000# this file is autogenerated by setup.py version = "0.4.4" nml-0.4.4/nml/palette.py0000644000567200056720000004713412643457545016275 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic raw_palette_data = [ #DOS palette [ 0, 0, 255, 16, 16, 16, 32, 32, 32, 48, 48, 48, 64, 64, 64, 80, 80, 80, 100, 100, 100, 116, 116, 116, 132, 132, 132, 148, 148, 148, 168, 168, 168, 184, 184, 184, 200, 200, 200, 216, 216, 216, 232, 232, 232, 252, 252, 252, 52, 60, 72, 68, 76, 92, 88, 96, 112, 108, 116, 132, 132, 140, 152, 156, 160, 172, 176, 184, 196, 204, 208, 220, 48, 44, 4, 64, 60, 12, 80, 76, 20, 96, 92, 28, 120, 120, 64, 148, 148, 100, 176, 176, 132, 204, 204, 168, 72, 44, 4, 88, 60, 20, 104, 80, 44, 124, 104, 72, 152, 132, 92, 184, 160, 120, 212, 188, 148, 244, 220, 176, 64, 0, 4, 88, 4, 16, 112, 16, 32, 136, 32, 52, 160, 56, 76, 188, 84, 108, 204, 104, 124, 220, 132, 144, 236, 156, 164, 252, 188, 192, 252, 208, 0, 252, 232, 60, 252, 252, 128, 76, 40, 0, 96, 60, 8, 116, 88, 28, 136, 116, 56, 156, 136, 80, 176, 156, 108, 196, 180, 136, 68, 24, 0, 96, 44, 4, 128, 68, 8, 156, 96, 16, 184, 120, 24, 212, 156, 32, 232, 184, 16, 252, 212, 0, 252, 248, 128, 252, 252, 192, 32, 4, 0, 64, 20, 8, 84, 28, 16, 108, 44, 28, 128, 56, 40, 148, 72, 56, 168, 92, 76, 184, 108, 88, 196, 128, 108, 212, 148, 128, 8, 52, 0, 16, 64, 0, 32, 80, 4, 48, 96, 4, 64, 112, 12, 84, 132, 20, 104, 148, 28, 128, 168, 44, 28, 52, 24, 44, 68, 32, 60, 88, 48, 80, 104, 60, 104, 124, 76, 128, 148, 92, 152, 176, 108, 180, 204, 124, 16, 52, 24, 32, 72, 44, 56, 96, 72, 76, 116, 88, 96, 136, 108, 120, 164, 136, 152, 192, 168, 184, 220, 200, 32, 24, 0, 56, 28, 0, 72, 40, 4, 88, 52, 12, 104, 64, 24, 124, 84, 44, 140, 108, 64, 160, 128, 88, 76, 40, 16, 96, 52, 24, 116, 68, 40, 136, 84, 56, 164, 96, 64, 184, 112, 80, 204, 128, 96, 212, 148, 112, 224, 168, 128, 236, 188, 148, 80, 28, 4, 100, 40, 20, 120, 56, 40, 140, 76, 64, 160, 100, 96, 184, 136, 136, 36, 40, 68, 48, 52, 84, 64, 64, 100, 80, 80, 116, 100, 100, 136, 132, 132, 164, 172, 172, 192, 212, 212, 224, 40, 20, 112, 64, 44, 144, 88, 64, 172, 104, 76, 196, 120, 88, 224, 140, 104, 252, 160, 136, 252, 188, 168, 252, 0, 24, 108, 0, 36, 132, 0, 52, 160, 0, 72, 184, 0, 96, 212, 24, 120, 220, 56, 144, 232, 88, 168, 240, 128, 196, 252, 188, 224, 252, 16, 64, 96, 24, 80, 108, 40, 96, 120, 52, 112, 132, 80, 140, 160, 116, 172, 192, 156, 204, 220, 204, 240, 252, 172, 52, 52, 212, 52, 52, 252, 52, 52, 252, 100, 88, 252, 144, 124, 252, 184, 160, 252, 216, 200, 252, 244, 236, 72, 20, 112, 92, 44, 140, 112, 68, 168, 140, 100, 196, 168, 136, 224, 200, 176, 248, 208, 184, 255, 232, 208, 252, 60, 0, 0, 92, 0, 0, 128, 0, 0, 160, 0, 0, 196, 0, 0, 224, 0, 0, 252, 0, 0, 252, 80, 0, 252, 108, 0, 252, 136, 0, 252, 164, 0, 252, 192, 0, 252, 220, 0, 252, 252, 0, 204, 136, 8, 228, 144, 4, 252, 156, 0, 252, 176, 48, 252, 196, 100, 252, 216, 152, 8, 24, 88, 12, 36, 104, 20, 52, 124, 28, 68, 140, 40, 92, 164, 56, 120, 188, 72, 152, 216, 100, 172, 224, 92, 156, 52, 108, 176, 64, 124, 200, 76, 144, 224, 92, 224, 244, 252, 200, 236, 248, 180, 220, 236, 132, 188, 216, 88, 152, 172, 244, 0, 244, 245, 0, 245, 246, 0, 246, 247, 0, 247, 248, 0, 248, 249, 0, 249, 250, 0, 250, 251, 0, 251, 252, 0, 252, 253, 0, 253, 254, 0, 254, 255, 0, 255, 76, 24, 8, 108, 44, 24, 144, 72, 52, 176, 108, 84, 210, 146, 126, 252, 60, 0, 252, 84, 0, 252, 104, 0, 252, 124, 0, 252, 148, 0, 252, 172, 0, 252, 196, 0, 64, 0, 0, 255, 0, 0, 48, 48, 0, 64, 64, 0, 80, 80, 0, 255, 255, 0, 32, 68, 112, 36, 72, 116, 40, 76, 120, 44, 80, 124, 48, 84, 128, 72, 100, 144, 100, 132, 168, 216, 244, 252, 96, 128, 164, 68, 96, 140, 255, 255, 255 ], #end of DOS palette #Windows palette [ 0, 0, 255, 238, 0, 238, 239, 0, 239, 240, 0, 240, 241, 0, 241, 242, 0, 242, 243, 0, 243, 244, 0, 244, 245, 0, 245, 246, 0, 246, 168, 168, 168, 184, 184, 184, 200, 200, 200, 216, 216, 216, 232, 232, 232, 252, 252, 252, 52, 60, 72, 68, 76, 92, 88, 96, 112, 108, 116, 132, 132, 140, 152, 156, 160, 172, 176, 184, 196, 204, 208, 220, 48, 44, 4, 64, 60, 12, 80, 76, 20, 96, 92, 28, 120, 120, 64, 148, 148, 100, 176, 176, 132, 204, 204, 168, 100, 100, 100, 116, 116, 116, 104, 80, 44, 124, 104, 72, 152, 132, 92, 184, 160, 120, 212, 188, 148, 244, 220, 176, 132, 132, 132, 88, 4, 16, 112, 16, 32, 136, 32, 52, 160, 56, 76, 188, 84, 108, 204, 104, 124, 220, 132, 144, 236, 156, 164, 252, 188, 192, 252, 208, 0, 252, 232, 60, 252, 252, 128, 76, 40, 0, 96, 60, 8, 116, 88, 28, 136, 116, 56, 156, 136, 80, 176, 156, 108, 196, 180, 136, 68, 24, 0, 96, 44, 4, 128, 68, 8, 156, 96, 16, 184, 120, 24, 212, 156, 32, 232, 184, 16, 252, 212, 0, 252, 248, 128, 252, 252, 192, 32, 4, 0, 64, 20, 8, 84, 28, 16, 108, 44, 28, 128, 56, 40, 148, 72, 56, 168, 92, 76, 184, 108, 88, 196, 128, 108, 212, 148, 128, 8, 52, 0, 16, 64, 0, 32, 80, 4, 48, 96, 4, 64, 112, 12, 84, 132, 20, 104, 148, 28, 128, 168, 44, 64, 64, 64, 44, 68, 32, 60, 88, 48, 80, 104, 60, 104, 124, 76, 128, 148, 92, 152, 176, 108, 180, 204, 124, 16, 52, 24, 32, 72, 44, 56, 96, 72, 76, 116, 88, 96, 136, 108, 120, 164, 136, 152, 192, 168, 184, 220, 200, 32, 24, 0, 56, 28, 0, 80, 80, 80, 88, 52, 12, 104, 64, 24, 124, 84, 44, 140, 108, 64, 160, 128, 88, 76, 40, 16, 96, 52, 24, 116, 68, 40, 136, 84, 56, 164, 96, 64, 184, 112, 80, 204, 128, 96, 212, 148, 112, 224, 168, 128, 236, 188, 148, 80, 28, 4, 100, 40, 20, 120, 56, 40, 140, 76, 64, 160, 100, 96, 184, 136, 136, 36, 40, 68, 48, 52, 84, 64, 64, 100, 80, 80, 116, 100, 100, 136, 132, 132, 164, 172, 172, 192, 212, 212, 224, 48, 48, 48, 64, 44, 144, 88, 64, 172, 104, 76, 196, 120, 88, 224, 140, 104, 252, 160, 136, 252, 188, 168, 252, 0, 24, 108, 0, 36, 132, 0, 52, 160, 0, 72, 184, 0, 96, 212, 24, 120, 220, 56, 144, 232, 88, 168, 240, 128, 196, 252, 188, 224, 252, 16, 64, 96, 24, 80, 108, 40, 96, 120, 52, 112, 132, 80, 140, 160, 116, 172, 192, 156, 204, 220, 204, 240, 252, 172, 52, 52, 212, 52, 52, 252, 52, 52, 252, 100, 88, 252, 144, 124, 252, 184, 160, 252, 216, 200, 252, 244, 236, 72, 20, 112, 92, 44, 140, 112, 68, 168, 140, 100, 196, 168, 136, 224, 200, 176, 248, 208, 184, 255, 232, 208, 252, 60, 0, 0, 92, 0, 0, 128, 0, 0, 160, 0, 0, 196, 0, 0, 224, 0, 0, 252, 0, 0, 252, 80, 0, 252, 108, 0, 252, 136, 0, 252, 164, 0, 252, 192, 0, 252, 220, 0, 252, 252, 0, 204, 136, 8, 228, 144, 4, 252, 156, 0, 252, 176, 48, 252, 196, 100, 252, 216, 152, 8, 24, 88, 12, 36, 104, 20, 52, 124, 28, 68, 140, 40, 92, 164, 56, 120, 188, 72, 152, 216, 100, 172, 224, 92, 156, 52, 108, 176, 64, 124, 200, 76, 144, 224, 92, 224, 244, 252, 200, 236, 248, 180, 220, 236, 132, 188, 216, 88, 152, 172, 16, 16, 16, 32, 32, 32, 32, 68, 112, 36, 72, 116, 40, 76, 120, 44, 80, 124, 48, 84, 128, 72, 100, 144, 100, 132, 168, 216, 244, 252, 96, 128, 164, 68, 96, 140, 76, 24, 8, 108, 44, 24, 144, 72, 52, 176, 108, 84, 210, 146, 126, 252, 60, 0, 252, 84, 0, 252, 104, 0, 252, 124, 0, 252, 148, 0, 252, 172, 0, 252, 196, 0, 64, 0, 0, 255, 0, 0, 48, 48, 0, 64, 64, 0, 80, 80, 0, 255, 255, 0, 148, 148, 148, 247, 0, 247, 248, 0, 248, 249, 0, 249, 250, 0, 250, 251, 0, 251, 252, 0, 252, 253, 0, 253, 254, 0, 254, 255, 0, 255, 255, 255, 255 ], #end of Windows palette # DOS Toyland palette [ 0, 0, 255, 16, 16, 16, 32, 32, 32, 48, 48, 48, 64, 64, 64, 80, 80, 80, 100, 100, 100, 116, 116, 116, 132, 132, 132, 148, 148, 148, 168, 168, 168, 184, 184, 184, 200, 200, 200, 216, 216, 216, 232, 232, 232, 252, 252, 252, 52, 60, 72, 68, 76, 92, 88, 96, 112, 108, 116, 132, 132, 140, 152, 156, 160, 172, 176, 184, 196, 204, 208, 220, 48, 44, 4, 64, 60, 12, 80, 76, 20, 96, 92, 28, 120, 120, 64, 148, 148, 100, 176, 176, 132, 204, 204, 168, 72, 44, 4, 88, 60, 20, 104, 80, 44, 124, 104, 72, 152, 132, 92, 184, 160, 120, 212, 188, 148, 244, 220, 176, 64, 0, 4, 88, 4, 16, 112, 16, 32, 136, 32, 52, 160, 56, 76, 188, 84, 108, 204, 104, 124, 220, 132, 144, 236, 156, 164, 252, 188, 192, 252, 208, 0, 252, 232, 60, 252, 252, 128, 76, 40, 0, 96, 60, 8, 116, 88, 28, 136, 116, 56, 156, 136, 80, 176, 156, 108, 196, 180, 136, 68, 24, 0, 96, 44, 4, 128, 68, 8, 156, 96, 16, 184, 120, 24, 212, 156, 32, 232, 184, 16, 252, 212, 0, 252, 248, 128, 252, 252, 192, 32, 4, 0, 64, 20, 8, 84, 28, 16, 108, 44, 28, 128, 56, 40, 148, 72, 56, 168, 92, 76, 184, 108, 88, 196, 128, 108, 212, 148, 128, 8, 52, 0, 16, 64, 0, 32, 80, 4, 48, 96, 4, 64, 112, 12, 84, 132, 20, 104, 148, 28, 128, 168, 44, 28, 52, 24, 44, 68, 32, 60, 88, 48, 80, 104, 60, 104, 124, 76, 128, 148, 92, 152, 176, 108, 180, 204, 124, 16, 52, 24, 32, 72, 44, 56, 96, 72, 76, 116, 88, 96, 136, 108, 120, 164, 136, 152, 192, 168, 184, 220, 200, 32, 24, 0, 56, 28, 0, 72, 40, 4, 88, 52, 12, 104, 64, 24, 124, 84, 44, 140, 108, 64, 160, 128, 88, 76, 40, 16, 96, 52, 24, 116, 68, 40, 136, 84, 56, 164, 96, 64, 184, 112, 80, 204, 128, 96, 212, 148, 112, 224, 168, 128, 236, 188, 148, 80, 28, 4, 100, 40, 20, 120, 56, 40, 140, 76, 64, 160, 100, 96, 184, 136, 136, 36, 40, 68, 48, 52, 84, 64, 64, 100, 80, 80, 116, 100, 100, 136, 132, 132, 164, 172, 172, 192, 212, 212, 224, 40, 20, 112, 64, 44, 144, 88, 64, 172, 104, 76, 196, 120, 88, 224, 140, 104, 252, 160, 136, 252, 188, 168, 252, 0, 24, 108, 0, 36, 132, 0, 52, 160, 0, 72, 184, 0, 96, 212, 24, 120, 220, 56, 144, 232, 88, 168, 240, 128, 196, 252, 188, 224, 252, 16, 64, 96, 24, 80, 108, 40, 96, 120, 52, 112, 132, 80, 140, 160, 116, 172, 192, 156, 204, 220, 204, 240, 252, 172, 52, 52, 212, 52, 52, 252, 52, 52, 252, 100, 88, 252, 144, 124, 252, 184, 160, 252, 216, 200, 252, 244, 236, 72, 20, 112, 92, 44, 140, 112, 68, 168, 140, 100, 196, 168, 136, 224, 200, 176, 248, 208, 184, 255, 232, 208, 252, 60, 0, 0, 92, 0, 0, 128, 0, 0, 160, 0, 0, 196, 0, 0, 224, 0, 0, 252, 0, 0, 252, 80, 0, 252, 108, 0, 252, 136, 0, 252, 164, 0, 252, 192, 0, 252, 220, 0, 252, 252, 0, 204, 136, 8, 228, 144, 4, 252, 156, 0, 252, 176, 48, 252, 196, 100, 252, 216, 152, 8, 24, 88, 12, 36, 104, 20, 52, 124, 28, 68, 140, 40, 92, 164, 56, 120, 188, 72, 152, 216, 100, 172, 224, 92, 156, 52, 108, 176, 64, 124, 200, 76, 144, 224, 92, 224, 244, 252, 200, 236, 248, 180, 220, 236, 132, 188, 216, 88, 152, 172, 244, 0, 244, 245, 0, 245, 246, 0, 246, 247, 0, 247, 248, 0, 248, 249, 0, 249, 250, 0, 250, 251, 0, 251, 252, 0, 252, 253, 0, 253, 254, 0, 254, 255, 0, 255, 76, 24, 8, 108, 44, 24, 144, 72, 52, 176, 108, 84, 210, 146, 126, 252, 60, 0, 252, 84, 0, 252, 104, 0, 252, 124, 0, 252, 148, 0, 252, 172, 0, 252, 196, 0, 64, 0, 0, 255, 0, 0, 48, 48, 0, 64, 64, 0, 80, 80, 0, 255, 255, 0, 28, 108, 124, 32, 112, 128, 36, 116, 132, 40, 120, 136, 44, 124, 140, 92, 164, 184, 116, 180, 196, 216, 244, 252, 112, 176, 192, 88, 160, 180, 255, 255, 255 ], #end of DOS Toyland palette #Windows Toyland palette [ 0, 255, 255, 238, 0, 238, 239, 0, 239, 240, 0, 240, 241, 0, 241, 242, 0, 242, 243, 0, 243, 244, 0, 244, 245, 0, 245, 246, 0, 246, 168, 168, 168, 184, 184, 184, 200, 200, 200, 216, 216, 216, 232, 232, 232, 252, 252, 252, 52, 60, 72, 68, 76, 92, 88, 96, 112, 108, 116, 132, 132, 140, 152, 156, 160, 172, 176, 184, 196, 204, 208, 220, 48, 44, 4, 64, 60, 12, 80, 76, 20, 96, 92, 28, 120, 120, 64, 148, 148, 100, 176, 176, 132, 204, 204, 168, 100, 100, 100, 116, 116, 116, 104, 80, 44, 124, 104, 72, 152, 132, 92, 184, 160, 120, 212, 188, 148, 244, 220, 176, 132, 132, 132, 88, 4, 16, 112, 16, 32, 136, 32, 52, 160, 56, 76, 188, 84, 108, 204, 104, 124, 220, 132, 144, 236, 156, 164, 252, 188, 192, 252, 208, 0, 252, 232, 60, 252, 252, 128, 76, 40, 0, 96, 60, 8, 116, 88, 28, 136, 116, 56, 156, 136, 80, 176, 156, 108, 196, 180, 136, 68, 24, 0, 96, 44, 4, 128, 68, 8, 156, 96, 16, 184, 120, 24, 212, 156, 32, 232, 184, 16, 252, 212, 0, 252, 248, 128, 252, 252, 192, 32, 4, 0, 64, 20, 8, 84, 28, 16, 108, 44, 28, 128, 56, 40, 148, 72, 56, 168, 92, 76, 184, 108, 88, 196, 128, 108, 212, 148, 128, 8, 52, 0, 16, 64, 0, 32, 80, 4, 48, 96, 4, 64, 112, 12, 84, 132, 20, 104, 148, 28, 128, 168, 44, 64, 64, 64, 44, 68, 32, 60, 88, 48, 80, 104, 60, 104, 124, 76, 128, 148, 92, 152, 176, 108, 180, 204, 124, 16, 52, 24, 32, 72, 44, 56, 96, 72, 76, 116, 88, 96, 136, 108, 120, 164, 136, 152, 192, 168, 184, 220, 200, 32, 24, 0, 56, 28, 0, 80, 80, 80, 88, 52, 12, 104, 64, 24, 124, 84, 44, 140, 108, 64, 160, 128, 88, 76, 40, 16, 96, 52, 24, 116, 68, 40, 136, 84, 56, 164, 96, 64, 184, 112, 80, 204, 128, 96, 212, 148, 112, 224, 168, 128, 236, 188, 148, 80, 28, 4, 100, 40, 20, 120, 56, 40, 140, 76, 64, 160, 100, 96, 184, 136, 136, 36, 40, 68, 48, 52, 84, 64, 64, 100, 80, 80, 116, 100, 100, 136, 132, 132, 164, 172, 172, 192, 212, 212, 224, 48, 48, 48, 64, 44, 144, 88, 64, 172, 104, 76, 196, 120, 88, 224, 140, 104, 252, 160, 136, 252, 188, 168, 252, 0, 24, 108, 0, 36, 132, 0, 52, 160, 0, 72, 184, 0, 96, 212, 24, 120, 220, 56, 144, 232, 88, 168, 240, 128, 196, 252, 188, 224, 252, 16, 64, 96, 24, 80, 108, 40, 96, 120, 52, 112, 132, 80, 140, 160, 116, 172, 192, 156, 204, 220, 204, 240, 252, 172, 52, 52, 212, 52, 52, 252, 52, 52, 252, 100, 88, 252, 144, 124, 252, 184, 160, 252, 216, 200, 252, 244, 236, 72, 20, 112, 92, 44, 140, 112, 68, 168, 140, 100, 196, 168, 136, 224, 200, 176, 248, 208, 184, 255, 232, 208, 252, 60, 0, 0, 92, 0, 0, 128, 0, 0, 160, 0, 0, 196, 0, 0, 224, 0, 0, 252, 0, 0, 252, 80, 0, 252, 108, 0, 252, 136, 0, 252, 164, 0, 252, 192, 0, 252, 220, 0, 252, 252, 0, 204, 136, 8, 228, 144, 4, 252, 156, 0, 252, 176, 48, 252, 196, 100, 252, 216, 152, 8, 24, 88, 12, 36, 104, 20, 52, 124, 28, 68, 140, 40, 92, 164, 56, 120, 188, 72, 152, 216, 100, 172, 224, 92, 156, 52, 108, 176, 64, 124, 200, 76, 144, 224, 92, 224, 244, 252, 200, 236, 248, 180, 220, 236, 132, 188, 216, 88, 152, 172, 16, 16, 16, 32, 32, 32, 28, 108, 124, 32, 112, 128, 36, 116, 132, 40, 120, 136, 44, 124, 140, 92, 164, 184, 116, 180, 196, 216, 244, 252, 112, 176, 192, 88, 160, 180, 76, 24, 8, 108, 44, 24, 144, 72, 52, 176, 108, 84, 210, 146, 126, 252, 60, 0, 252, 84, 0, 252, 104, 0, 252, 124, 0, 252, 148, 0, 252, 172, 0, 252, 196, 0, 64, 0, 0, 255, 0, 0, 48, 48, 0, 64, 64, 0, 80, 80, 0, 255, 255, 0, 148, 148, 148, 247, 0, 247, 248, 0, 248, 249, 0, 249, 250, 0, 250, 251, 0, 251, 252, 0, 252, 253, 0, 253, 254, 0, 254, 255, 0, 255, 255, 255, 255 ] #end of Windows Toyland palette ] # Convert palettes to strings for fast comparison. palette_data = [bytes(pal) for pal in raw_palette_data] palette_name = ["DEFAULT", "LEGACY", "DEFAULT_TOYLAND", "LEGACY_TOYLAND"] def validate_palette(image, filename): palette = image.palette.palette if len(palette) != 768: raise generic.ImageError("Invalid palette; does not contain 256 entries.", filename) for i, pal in enumerate(palette_data): if pal != palette: continue return palette_name[i] raise generic.ImageError("Palette is not recognized as a valid palette.", filename) nml-0.4.4/nml/actions/0000755000567200056720000000000012643457623015711 5ustar jenkinsjenkins00000000000000nml-0.4.4/nml/actions/actionF.py0000644000567200056720000001627412643457545017663 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" """ Code for storing and generating action F """ from nml import expression, grfstrings, generic from nml.actions import base_action # Helper functions to allocate townname IDs # # Numbers available are from 0 to 0x7f (inclusive). # These numbers can be in five states: # - free: Number is available for use. # - named: Number is allocated to represent a name. # - safe numbered: Number is allocated by the user, and is safe to refer to. # - unsafe numbered: Number is allocated by the user, and is not safe to refer to (that is, it is below the point of 'prepare_output') # - invisible: Number is allocated by a final town_name, without attaching a name to it. It is not accessible any more. # Instances of the TownNames class have a 'name' attribute, which can be 'None' (for an invisible number), # a string (for a named number), or a (constant numeric) expression (for a safe/unsafe number). # total_numbers = 0x80 free_numbers = set(range(total_numbers)) #: Free numbers. first_free_id = 0 #: All numbers before this are allocated. named_numbers = {} #: Mapping of names to named numbers. Note that these are always safe to refer to. numbered_numbers = set() #: Safe numbers introduced by the user (without name). def get_free_id(): """Allocate a number from the free_numbers.""" global first_free_id while first_free_id not in free_numbers: first_free_id = first_free_id + 1 if first_free_id >= total_numbers: raise generic.ScriptError("Too many town name blocks. Some of these are autogenerated. Please use less town names.") number = first_free_id free_numbers.remove(number) first_free_id = first_free_id + 1 return number town_names_blocks = {} # Mapping of town_names ID number to TownNames instance. def print_stats(): """ Print statistics about used ids. """ num_used = total_numbers - len(free_numbers) if num_used > 0: generic.print_info("Town names: {}/{}".format(num_used, total_numbers)) class ActionF(base_action.BaseAction): """ Town names action. @ivar name: Name ID of the town_name. @type name: C{None}, L{Identifier}, or L{ConstantNumeric} @ivar id_number: Allocated ID number for this town_name action F node. @type id_number: C{None} or C{int} @ivar style_name: Name of the translated string containing the name of the style, if any. @type style_name: C{None} or L{String} @ivar style_names: List translations of L{style_name}, pairs (languageID, text). @type style_names: C{list} of (C{int}, L{Identifier}) @ivar parts: Parts of the names. @type parts: C{list} of L{TownNamesPart} @ivar free_bit: First available bit above the bits used by this block. @type free_bit: C{None} if unset, else C{int} @ivar pos: Position information of the 'town_names' block. @type pos: L{Position} """ def __init__(self, name, id_number, style_name, parts, pos): self.name = name self.id_number = id_number self.style_name = style_name self.style_names = [] self.parts = parts self.free_bit = None self.pos = pos def prepare_output(self, sprite_num): # Resolve references to earlier townname actions blocks = set() for part in self.parts: blocks.update(part.resolve_townname_id()) # Allocate a number for this action F. if self.name is None or isinstance(self.name, expression.Identifier): self.id_number = get_free_id() if isinstance(self.name, expression.Identifier): if self.name.value in named_numbers: raise generic.ScriptError('Cannot define town name "{}", it is already in use'.format(self.name), self.pos) named_numbers[self.name.value] = self.id_number # Add name to the set 'safe' names. else: numbered_numbers.add(self.id_number) # Add number to the set of 'safe' numbers. town_names_blocks[self.id_number] = self # Add self to the available blocks. # Ask descendants for the lowest available bit. if len(blocks) == 0: startbit = 0 # No descendants, all bits are free. else: startbit = max(town_names_blocks[block].free_bit for block in blocks) # Allocate random bits to all parts. for part in self.parts: num_bits = part.assign_bits(startbit) startbit += num_bits self.free_bit = startbit if startbit > 32: raise generic.ScriptError("Not enough random bits for the town name generation ({:d} needed, 32 available)".format(startbit), self.pos) # Pull style names if needed. if self.style_name is not None: grfstrings.validate_string(self.style_name) self.style_names = [(lang_id, grfstrings.get_translation(self.style_name, lang_id)) for lang_id in grfstrings.get_translations(self.style_name)] self.style_names.append( (0x7F, grfstrings.get_translation(self.style_name)) ) self.style_names.sort() if len(self.style_names) == 0: raise generic.ScriptError('Style "{}" defined, but no translations found for it'.format(self.style_name.name.value), self.pos) else: self.style_names = [] # Style names def get_length_styles(self): if len(self.style_names) == 0: return 0 size = 0 for _lang, txt in self.style_names: size += 1 + grfstrings.get_string_size(txt) # Language ID, text return size + 1 # Terminating 0 def write_styles(self, handle): if len(self.style_names) == 0: return for lang, txt in self.style_names: handle.print_bytex(lang) handle.print_string(txt, final_zero = True) handle.newline() handle.print_bytex(0) handle.newline() # Parts def get_length_parts(self): size = 1 # num_parts byte return size + sum(part.get_length() for part in self.parts) def write_parts(self, handle): handle.print_bytex(len(self.parts)) for part in self.parts: part.write(handle) handle.newline() def write(self, handle): handle.start_sprite(2 + self.get_length_styles() + self.get_length_parts()) handle.print_bytex(0xF) handle.print_bytex(self.id_number | (0x80 if len(self.style_names) > 0 else 0)) handle.newline(str(self.name) if self.name is not None else "") self.write_styles(handle) self.write_parts(handle) handle.end_sprite() def skip_action7(self): return False nml-0.4.4/nml/actions/action14.py0000644000567200056720000002270112643457545017712 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import grfstrings from nml.actions import base_action class Action14(base_action.BaseAction): def __init__(self, nodes): self.nodes = nodes def skip_action7(self): return False def write(self, file): size = 2 # final 0-byte for node in self.nodes: size += node.get_size() file.start_sprite(size) file.print_bytex(0x14) for node in self.nodes: node.write(file) file.print_bytex(0) file.end_sprite() def split_action14(node, max_size): if node.get_size() <= max_size: return [node, None] if not isinstance(node, BranchNode): return [None, node] new_node = BranchNode(node.id) rest = BranchNode(node.id) copy_to_rest = False for subnode in node.subnodes: if copy_to_rest: rest.subnodes.append(subnode) continue new_subnode, subnode_rest = split_action14(subnode, max_size - new_node.get_size()) if new_subnode is not None: new_node.subnodes.append(new_subnode) if subnode_rest is not None: rest.subnodes.append(subnode_rest) copy_to_rest = True assert len(rest.subnodes) > 0 if len(new_node.subnodes) == 0: return [None, rest] return [new_node, rest] def get_actions(root): action_list = [] while True: node, root = split_action14(root, 65535) assert node is not None action_list.append(Action14([node])) if root is None: break return action_list class Action14Node(object): def __init__(self, type_string, id): self.type_string = type_string self.id = id def get_size(self): """ How many bytes will be written to the output file by L{write}? @return: The size (in bytes) of this node. """ raise NotImplementedError('get_size must be implemented in Action14Node-subclass {:r}'.format(type(self))) def write(self, file): """ Write this node to the output file. @param file: The file to write the output to. """ raise NotImplementedError('write must be implemented in Action14Node-subclass {:r}'.format(type(self))) def write_type_id(self, file): file.print_string(self.type_string, False, True) if isinstance(self.id, str): file.print_string(self.id, False, True) else: file.print_dword(self.id) class TextNode(Action14Node): def __init__(self, id, string, skip_default_langid = False): Action14Node.__init__(self, "T", id) self.string = string grfstrings.validate_string(self.string) self.skip_default_langid = skip_default_langid def get_size(self): if self.skip_default_langid: size = 0 else: size = 6 + grfstrings.get_string_size(grfstrings.get_translation(self.string)) for lang_id in grfstrings.get_translations(self.string): # 6 is for "T" (1), id (4), langid (1) size += 6 + grfstrings.get_string_size(grfstrings.get_translation(self.string, lang_id)) return size def write(self, file): if not self.skip_default_langid: self.write_type_id(file) file.print_bytex(0x7F) file.print_string(grfstrings.get_translation(self.string)) file.newline() for lang_id in grfstrings.get_translations(self.string): self.write_type_id(file) file.print_bytex(lang_id) file.print_string(grfstrings.get_translation(self.string, lang_id)) file.newline() class BranchNode(Action14Node): def __init__(self, id): Action14Node.__init__(self, "C", id) self.subnodes = [] def get_size(self): size = 6 # "C", id, final 0-byte for node in self.subnodes: size += node.get_size() return size def write(self, file): self.write_type_id(file) file.newline() for node in self.subnodes: node.write(file) file.print_bytex(0) file.newline() class BinaryNode(Action14Node): def __init__(self, id, size, val = None): Action14Node.__init__(self, "B", id) self.size = size self.val = val def get_size(self): return 7 + self.size # "B" (1), id (4), size (2), data (self.size) def write(self, file): self.write_type_id(file) file.print_word(self.size) file.print_varx(self.val, self.size) file.newline() class UsedPaletteNode(BinaryNode): def __init__(self, pal): BinaryNode.__init__(self, "PALS", 1) self.pal = pal def write(self, file): self.write_type_id(file) file.print_word(self.size) file.print_string(self.pal, False, True) file.newline() class BlitterNode(BinaryNode): def __init__(self, blitter): BinaryNode.__init__(self, "BLTR", 1) self.blitter = blitter def write(self, file): self.write_type_id(file) file.print_word(self.size) file.print_string(self.blitter, False, True) file.newline() class SettingMaskNode(BinaryNode): def __init__(self, param_num, first_bit, num_bits): BinaryNode.__init__(self, "MASK", 3) self.param_num = param_num self.first_bit = first_bit self.num_bits = num_bits def write(self, file): self.write_type_id(file) file.print_word(self.size) file.print_byte(self.param_num) file.print_byte(self.first_bit) file.print_byte(self.num_bits) file.newline() class LimitNode(BinaryNode): def __init__(self, min_val, max_val): BinaryNode.__init__(self, "LIMI", 8) self.min_val = min_val self.max_val = max_val def write(self, file): self.write_type_id(file) file.print_word(self.size) file.print_dword(self.min_val) file.print_dword(self.max_val) file.newline() def grf_name_desc_actions(root, name, desc, url, version, min_compatible_version): if len(grfstrings.get_translations(name)) > 0: name_node = TextNode("NAME", name, True) root.subnodes.append(name_node) if len(grfstrings.get_translations(desc)) > 0: desc_node = TextNode("DESC", desc, True) root.subnodes.append(desc_node) if url is not None: desc_node = TextNode("URL_", url) root.subnodes.append(desc_node) version_node = BinaryNode("VRSN", 4, version.value) root.subnodes.append(version_node) min_compatible_version_node = BinaryNode("MINV", 4, min_compatible_version.value) root.subnodes.append(min_compatible_version_node) 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.value if setting.min_val is not None else 0 max_val = setting.max_val.value if setting.max_val is not None else 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 PaletteAction(pal): root = BranchNode("INFO") pal_node = UsedPaletteNode(pal) root.subnodes.append(pal_node) return [Action14([root])] nml-0.4.4/nml/actions/action11.py0000644000567200056720000001000012643457545017674 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" """ Action 11 support classes (sounds). """ import os from nml import generic from nml.actions import base_action, action0 class Action11(base_action.BaseAction): def __init__(self, num_sounds): self.num_sounds = num_sounds def write(self, file): file.start_sprite(3) file.print_bytex(0x11) file.print_word(self.num_sounds) file.end_sprite() class LoadBinaryFile(base_action.BaseAction): ''' * FF 00 ''' def __init__(self, fname, pos): self.fname = fname self.pos = pos self.last = False def prepare_output(self, sprite_num): if not os.access(self.fname, os.R_OK): raise generic.ScriptError("Sound file '{}' does not exist.".format(self.fname), self.pos) size = os.path.getsize(self.fname) if size == 0: raise generic.ScriptError("Expected sound file '{}' to have a non-zero length.".format(self.fname), self.pos) def write(self, file): file.print_named_filedata(self.fname) if self.last: file.newline() class ImportSound(base_action.BaseAction): """ Import a sound from another grf:: * FE 00 @ivar grfid: ID of the other grf. @type grfid: C{int} @ivar number: Sound number to load. @type number: C{int} @ivar pos: Position information @type pos: L{Position} """ def __init__(self, grfid, number, pos): self.grfid = grfid self.number = number self.pos = pos self.last = False def write(self, file): file.start_sprite(8) file.print_bytex(0xfe) file.print_bytex(0) file.print_dwordx(self.grfid) file.print_wordx(self.number) file.end_sprite() if self.last: file.newline() registered_sounds = {} SOUND_OFFSET = 73 # No of original sounds NUM_ANIMATION_SOUNDS = 0x80 - SOUND_OFFSET # Number of custom sound ids, which can be returned by animation callbacks. def print_stats(): """ Print statistics about used ids. """ if len(registered_sounds) > 0: # Currently NML does not optimise the order of sound effects. So we assume NUM_ANIMATION_SOUNDS as the maximum. generic.print_info("Sound effects: {}/{}".format(len(registered_sounds), NUM_ANIMATION_SOUNDS)) def add_sound(args, pos): if args not in registered_sounds: registered_sounds[args] = (len(registered_sounds), pos) return registered_sounds[args][0] + SOUND_OFFSET def get_sound_actions(): """ Get a list of actions that actually includes all sounds in the output file. """ if not registered_sounds: return [] action_list = [] action_list.append(Action11(len(registered_sounds))) volume_list = [] sound_data = [(sound_id, args, pos) for args, (sound_id, pos) in list(registered_sounds.items())] # Sort on first item, i.e. sound ID for i, args, pos in sorted(sound_data): if len(args) == 3: action_list.append(ImportSound(args[0], args[1], pos)) else: action_list.append(LoadBinaryFile(args[0], pos)) if args[-1] != 100: volume_list.append( (i + SOUND_OFFSET, int(args[-1] * 128 / 100)) ) if volume_list: action_list.extend(action0.get_volume_actions(volume_list)) return action_list nml-0.4.4/nml/actions/action2real.py0000644000567200056720000001246212643457545020476 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from nml.actions import action2, action1 from nml.ast import general class Action2Real(action2.Action2): def __init__(self, feature, name, pos, loaded_list, loading_list): action2.Action2.__init__(self, feature, name, pos) self.loaded_list = loaded_list self.loading_list = loading_list def write(self, file): size = 2 + 2 * len(self.loaded_list) + 2 * len(self.loading_list) action2.Action2.write_sprite_start(self, file, size) file.print_byte(len(self.loaded_list)) file.print_byte(len(self.loading_list)) file.newline() for i in self.loaded_list: file.print_word(i) file.newline() for i in self.loading_list: file.print_word(i) file.newline() file.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 make_simple_real_action2(feature, name, pos, action1_index): """ Make a simple real action2, referring to only 1 action1 sprite set @param feature: Feature of the needed action2 @type feature: C{int} @param name: Name of the action2 @type name: C{str} @param pos: Positional context. @type pos: L{Position} @param action1_index: Index of the action1 sprite set to use @type action1_index: C{int} @return: The created real action2 @rtype: L{Action2Real} """ return Action2Real(feature, name, pos, [action1_index], [action1_index] if feature <= 0x03 else []) def create_spriteset_actions(spritegroup): """ Create action2s for directly-referenced sprite sets @param spritegroup: Spritegroup to create the sprite sets for @type spritegroup: L{ASTSpriteGroup} @return: Resulting list of actions @rtype: C{list} of L{BaseAction} """ action_list = [] # Iterate over features first for more efficient action1s for feature in spritegroup.feature_set: if len(spritegroup.used_sprite_sets) != 0 and feature not in action2.features_sprite_group: raise generic.ScriptError("Directly referring to sprite sets is not possible for feature {:02X}".format(feature), spritegroup.pos) for spriteset in spritegroup.used_sprite_sets: if spriteset.has_action2(feature): continue action_list.extend(action1.add_to_action1([spriteset], feature, spritegroup.pos)) real_action2 = make_simple_real_action2(feature, spriteset.name.value + " - feature {:02X}".format(feature), spritegroup.pos, action1.get_action1_index(spriteset)) action_list.append(real_action2) spriteset.set_action2(real_action2, feature) return action_list nml-0.4.4/nml/actions/action2.py0000644000567200056720000004715112643457545017635 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from nml.actions import base_action from nml.ast import base_statement, general import nml total_action2_ids = 0x100 free_action2_ids = list(range(0, total_action2_ids)) """ Statistics about spritegroups. The 1st field of type C{int} contains the largest number of concurrently active spritegroup ids. The 2nd field of type L{Position} contains a positional reference to the last spritegroup of the concurrently active ones. """ spritegroup_stats = (0, None) total_tmp_locations = 0x7F """ Statistics about temporary Action2 registers. The 1st field of type C{int} contains the largest number of concurrently active register ids. The 2nd field of type L{Position} contains a positional reference to the spritegroup. """ a2register_stats = (0, None) def print_stats(): """ Print statistics about used ids. """ if spritegroup_stats[0] > 0: generic.print_info("Concurrent spritegroups: {}/{} ({})".format(spritegroup_stats[0], total_action2_ids, str(spritegroup_stats[1]))) if a2register_stats[0] > 0: generic.print_info("Concurrent Action2 registers: {}/{} ({})".format(a2register_stats[0], total_tmp_locations, str(a2register_stats[1]))) class Action2(base_action.BaseAction): """ Abstract Action2 base class. @ivar name: Name of the action2. @type name: C{str} @ivar feature: Action2 feature byte. @type feature: C{int} @ivar pos: Position reference to source. @type pos: L{Position} @ivar num_refs: Number of references to this action2. @type num_refs: C{int} @ivar id: Number of this action2. @type id: C{int}, or C{None} if no number is allocated yet. @ivar references: All Action2s that are referenced by this Action2. @type references: C{list} of L{Action2Reference} @ivar tmp_locations: List of address in the temporary storage that are free to be used in this varaction2. @type tmp_locations: C{list} of C{int} """ def __init__(self, feature, name, pos): self.feature = feature self.name = name self.pos = pos self.num_refs = 0 self.id = None self.references = [] #0x00 - 0x7F: available to user #0x80 - 0xFE: used by NML #0xFF: Used for some house variables #0x100 - 0x10F: Special meaning (used for some CB results) self.tmp_locations = list(range(0x80, 0x80 + total_tmp_locations)) def prepare_output(self, sprite_num): free_references(self) global spritegroup_stats try: if self.num_refs == 0: self.id = free_action2_ids[0] else: self.id = free_action2_ids.pop() num_used = total_action2_ids - len(free_action2_ids) if num_used > spritegroup_stats[0]: spritegroup_stats = (num_used, self.pos) except IndexError: raise generic.ScriptError("Unable to allocate ID for [random]switch, sprite set/layout/group or produce-block. Try reducing the number of such blocks.", self.pos) def write_sprite_start(self, file, size): assert self.num_refs == 0, "Action2 reference counting has {:d} dangling references.".format(self.num_refs) file.comment("Name: " + self.name) file.start_sprite(size + 3) file.print_bytex(2) file.print_bytex(self.feature) file.print_bytex(self.id) def skip_action7(self): return False def skip_action9(self): return False def skip_needed(self): return False def remove_tmp_location(self, location, force_recursive): """ Recursively remove a location from the list of available temporary storage locations. It is not only removed from the the list of the current Action2Var but also from all Action2Var it calls. If an Action2Var is referenced as a procedure call, the location is always removed recursively, otherwise only if force_recursive is True. @param location: Number of the storage register to remove. @type location: C{int} @param force_recursive: Force removing this location recursively, also for 'chained' action2s. @type force_recursive: C{bool} """ global a2register_stats if location in self.tmp_locations: self.tmp_locations.remove(location) num_used = total_tmp_locations - len(self.tmp_locations) if num_used > a2register_stats[0]: a2register_stats = (num_used, self.pos) for act2_ref in self.references: if force_recursive or act2_ref.is_proc: act2_ref.action2.remove_tmp_location(location, True) class Action2Reference: """ Container class to store information about an action2 reference @ivar action2: The target action2 @type action2: L{Action2} @ivar is_proc: Whether this reference is made because of a procedure call @type is_proc: C{bool} """ def __init__(self, action2, is_proc): self.action2 = action2 self.is_proc = is_proc def add_ref(ref, source_action, reference_as_proc = False): """ Add a reference to a certain action2. This is needed so we can correctly reserve / free action2 IDs later on. To be called when creating the actions from the AST. @param ref: Reference to the sprite group that corresponds to the action2. @type ref: L{SpriteGroupRef} @param source_action: Source action (act2 or act3) that contains the reference. @type source_action: L{Action2} or L{Action3} @param reference_as_proc: True iff the reference source is a procedure call, which needs special precautions for temp registers. @type reference_as_proc: C{bool} """ # Add reference to list of references of the source action act2 = ref.act2 if ref.act2 is not None else resolve_spritegroup(ref.name).get_action2(source_action.feature) source_action.references.append(Action2Reference(act2, reference_as_proc)) act2.num_refs += 1 def free_references(source_action): """ Free all references to other action2s from a certain action 2/3 @param source_action: Action that contains the reference @type source_action: L{Action2} or L{Action3} """ for act2_ref in source_action.references: act2 = act2_ref.action2 act2.num_refs -= 1 if act2.num_refs == 0: free_action2_ids.append(act2.id) # Features using sprite groups directly: vehicles, canals, cargos, railtypes, airports features_sprite_group = [0x00, 0x01, 0x02, 0x03, 0x05, 0x0B, 0x0D, 0x10] # Features using sprite layouts: stations, houses, industry tiles, objects and airport tiles features_sprite_layout = [0x04, 0x07, 0x09, 0x0F, 0x11] # All features that need sprite sets features_sprite_set = features_sprite_group + features_sprite_layout def make_sprite_group_class(cls_is_spriteset, cls_is_referenced, cls_has_explicit_feature, cls_is_relocatable = False): """ Metaclass factory which makes base classes for all nodes 'Action 2 graph' This graph is made up of all blocks that are eventually compiled to Action2, which use the same name space. Spritesets do inherit from this class to make referencing them possible, but they are not part of the refernce graph that is built. @param cls_is_spriteset: Whether this class represents a spriteset @type cls_is_spriteset: C{bool} @param cls_is_referenced: True iff this node can be referenced by other nodes @type cls_is_referenced: C{bool} @param cls_has_explicit_feature: Whether the feature of an instance is explicitly set, or derived from nodes that link to it. @type cls_has_explicit_feature: C{bool} @param cls_is_relocatable: Whether instances of this class can be freely moved around or whether they need to to be converted to nfo code at the same location as they are in the nml code. @type cls_is_relocatable: C{bool} @return: The constructed class @rtype: C{type} """ #without either references or an explicit feature, we have nothing to base our feature on assert cls_is_referenced or cls_has_explicit_feature class ASTSpriteGroup(base_statement.BaseStatement): """ Abstract base class for all AST nodes that represent a sprite group This handles all the relations between the various nodes Child classes should do the following: - Implement their own __init__ method - Call BaseStatement.__init__ - Call initialize, pre_process and perpare_output (in that order) - Implement collect_references - Call set_action2 after generating the corresponding action2 (if applicable) @ivar _referencing_nodes: Set of nodes that refer to this node @type _referencing_nodes: C{set} @ivar _referenced_nodes: Set of nodes that this node refers to @type _referenced_nodes: C{set} @ivar _prepared: True iff prepare_output has already been executed @type _prepared: C{bool} @ivar _action2: Mapping of features to action2s @type _action2: C{dict} that maps C{int} to L{Action2} @ivar feature_set: Set of features that use this node @type feature_set: C{set} of C{int} @ivar name: Name of this node, as declared by the user @type name: L{Identifier} @ivar num_params: Number of parameters that can be (and have to be) passed @type num_params: C{int} @ivar used_sprite_sets: List of sprite sets used by this node @type used_sprite_sets: C{list} of L{SpriteSet} """ def __init__(self): """ Subclasses should implement their own __init__ method. This method should not be called, because calling a method on a meta class can be troublesome. Instead, call initialize(..). """ raise NotImplementedError('__init__ must be implemented in ASTSpriteGroup-subclass {:r}, initialize(..) should be called instead'.format(type(self))) def initialize(self, name = None, feature = None, num_params = 0): """ Initialize this instance. This function is generally, but not necessarily, called from the child class' constructor. Calling it later (during pre-processing) is also possible, as long as it's called before any other actions are done. @param name: Name of this node, as set by the user (if applicable) Should be be set (not None) iff cls_is_referenced is True @type name: L{Identifier} or C{None} if N/A @param feature: Feature of this node, if set by the user. Should be set (not None) iff cls_has_explicit_feature is True @type feature: C{int} or C{None} """ assert not (self._has_explicit_feature() and feature is None) assert not (cls_is_referenced and name is None) self._referencing_nodes = set() self._referenced_nodes = set() self._prepared = False self._action2 = {} self.feature_set = set([feature]) if feature is not None else set() self.name = name self.num_params = num_params self.used_sprite_sets = [] def register_names(self): if cls_is_relocatable and cls_is_referenced: register_spritegroup(self) def pre_process(self): """ Pre-process this node. During this stage, the reference graph is built. """ refs = self.collect_references() for ref in refs: self._add_reference(ref) if (not cls_is_relocatable) and cls_is_referenced: register_spritegroup(self) 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: # 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("Block '{}' is not referenced, ignoring.".format(self.name.value), self.pos) return len(self._referencing_nodes) != 0 def referenced_nodes(self): """ Get the nodes that this node refers to. @note: Make sure to sort this in a deterministic way when the order of items affects the output. @return: A set of nodes @rtype: C{set} of L{ASTSpriteGroup} """ return self._referenced_nodes def referencing_nodes(self): """ Get the nodes that refer to this node. @note: Make sure to sort this in a deterministic way when the order of items affects the output. @return: A set of nodes @rtype: C{set} of L{ASTSpriteGroup} """ return self._referencing_nodes def collect_references(self): """ This function should collect all references to other nodes from this instance. @return: A collection containing all links to other nodes. @rtype: C{iterable} of L{SpriteGroupRef} """ raise NotImplementedError('collect_references must be implemented in ASTSpriteGroup-subclass {:r}'.format(type(self))) def set_action2(self, action2, feature): """ Set this node's resulting action2 @param feature: Feature of the Action2 @type feature: C{int} @param action2: Action2 to set @type action2: L{Action2} """ assert feature not in self._action2 self._action2[feature] = action2 def get_action2(self, feature): """ Get this node's resulting action2 @param feature: Feature of the Action2 @type feature: C{int} @return: Action2 to get @rtype: L{Action2} """ assert feature in self._action2 return self._action2[feature] def has_action2(self, feature): """ Check, if this node already has an action2 for a given feature @param feature: Feature to check @type feature: C{int} @return: True iff there is an action2 for this feature @rtype: C{bool} """ return feature in self._action2 def _add_reference(self, target_ref): """ Add a reference from C{self} to a target with a given name. @param target_ref: Name of the reference target @type target_ref: L{SpriteGroupRef} """ if target_ref.name.value == "CB_FAILED": return target = resolve_spritegroup(target_ref.name) if target.is_spriteset(): assert target.num_params == 0 # Referencing a spriteset directly from graphics/[random]switch # Passing parameters is not possible here if len(target_ref.param_list) != 0: raise generic.ScriptError("Passing parameters to '{}' is only possible from a spritelayout.".format(target_ref.name.value), target_ref.pos) self.used_sprite_sets.append(target) else: if len(target_ref.param_list) != target.num_params: msg = "'{}' expects {:d} parameters, encountered {:d}." msg = msg.format(target_ref.name.value, target.num_params, len(target_ref.param_list)) raise generic.ScriptError(msg, target_ref.pos) self._referenced_nodes.add(target) target._referencing_nodes.add(self) def _remove_reference(self, target): """ Add a reference from C{self} to a target @param target: Existing reference target to be removed @type target: L{ASTSpriteGroup} """ assert target in self._referenced_nodes assert self in target._referencing_nodes self._referenced_nodes.remove(target) target._referencing_nodes.remove(self) #Make metaclass arguments available outside of the class def is_spriteset(self): return cls_is_spriteset def _has_explicit_feature(self): return cls_has_explicit_feature return ASTSpriteGroup #list of all registered sprite sets and sprite groups spritegroup_list = {} def register_spritegroup(spritegroup): """ Register a sprite group, so it can be resolved by name later @param spritegroup: Sprite group to register @type spritegroup: L{ASTSpriteGroup} """ name = spritegroup.name.value if name in spritegroup_list: raise generic.ScriptError("Block with name '{}' has already been defined".format(name), spritegroup.pos) spritegroup_list[name] = spritegroup nml.global_constants.spritegroups[name] = name def resolve_spritegroup(name): """ Resolve a sprite group with a given name @param name: Name of the sprite group. @type name: L{Identifier} @return: The sprite group that the name refers to. @rtype: L{ASTSpriteGroup} """ if name.value not in spritegroup_list: raise generic.ScriptError("Unknown identifier encountered: '{}'".format(name.value), name.pos) return spritegroup_list[name.value] nml-0.4.4/nml/actions/sprite_count.py0000644000567200056720000000172712643457545021013 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" class SpriteCountAction(object): def __init__(self, count): self.count = count def prepare_output(self, sprite_num): assert sprite_num == 0 def write(self, file): file.start_sprite(4) file.print_dword(self.count) file.newline() file.end_sprite() nml-0.4.4/nml/actions/action10.py0000644000567200056720000000207312643457545017706 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml.actions import base_action class Action10(base_action.BaseAction): def __init__(self, label): self.label = label def write(self, file): file.start_sprite(2) file.print_bytex(0x10) file.print_bytex(self.label) file.newline() file.end_sprite() def skip_action9(self): return False def skip_needed(self): return False nml-0.4.4/nml/actions/action12.py0000644000567200056720000000605112643457545017710 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, expression from nml.actions import base_action, real_sprite class Action12(base_action.BaseAction): #sets: list of (font_size, num_char, base_char) tuples def __init__(self, sets): self.sets = sets def write(self, file): # * 12 ( ){n} size = 2 + 4 * len(self.sets) file.start_sprite(size) file.print_bytex(0x12) file.print_byte(len(self.sets)) file.newline() for font_size, num_char, base_char in self.sets: font_size.write(file, 1) file.print_byte(num_char) file.print_word(base_char) file.newline() file.end_sprite() font_sizes = { 'NORMAL' : 0, 'SMALL' : 1, 'LARGE' : 2, 'MONO' : 3, } def parse_action12(font_glyphs): try: font_size = font_glyphs.font_size.reduce_constant([font_sizes]) if isinstance(font_glyphs.base_char, expression.StringLiteral) and len(font_glyphs.base_char.value) == 1: base_char = ord(font_glyphs.base_char.value) else: base_char = font_glyphs.base_char.reduce_constant() except generic.ConstError: raise generic.ScriptError("Parameters of font_glyph have to be compile-time constants", font_glyphs.pos) if font_size.value not in list(font_sizes.values()): raise generic.ScriptError("Invalid value for parameter 'font_size' in font_glyph, valid values are 0, 1, 2", font_size.pos) if not (0 <= base_char.value <= 0xFFFF): raise generic.ScriptError("Invalid value for parameter 'base_char' in font_glyph, valid values are 0-0xFFFF", base_char.pos) real_sprite_list = real_sprite.parse_sprite_data(font_glyphs) char = base_char.value last_char = char + len(real_sprite_list) if last_char > 0xFFFF: raise generic.ScriptError("Character numbers in font_glyph block exceed the allowed range (0-0xFFFF)", font_glyphs.pos) sets = [] while char < last_char: #each set of characters must fit in a single 128-char block, according to specs / TTDP if (char // 128) * 128 != (last_char // 128) * 128: num_in_set = (char // 128 + 1) * 128 - char else: num_in_set = last_char - char sets.append((font_size, num_in_set, char)) char += num_in_set return [Action12(sets)] + real_sprite_list nml-0.4.4/nml/actions/action5.py0000644000567200056720000001277112643457545017640 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from nml.actions import base_action, real_sprite class Action5(base_action.BaseAction): def __init__(self, type, num_sprites, offset): self.type = type self.num_sprites = num_sprites self.offset = offset def prepare_output(self, sprite_num): if self.offset is not None: self.type |= 0x80 def write(self, file): # * 05 [] size = 5 if self.offset is None else 8 file.start_sprite(size) file.print_bytex(0x05) file.print_bytex(self.type) file.print_bytex(0xFF) file.print_word(self.num_sprites) if self.offset is not None: file.print_bytex(0xFF) file.print_word(self.offset) file.newline() file.end_sprite() def skip_action7(self): #skipping with Action7 should work, according to the Action7/9 specs #However, skipping invalid (OpenTTD-only) Action5s in TTDP can only be done using Action9, else an error occurs #To be on the safe side, don't allow skipping with Action7 at all return False class Action5BlockType(object): FIXED = 0, #fixed number of sprites ANY = 1, #any number of sprites OFFSET = 2, #flexible number of sprites, offset may be set action5_table = { 'PRE_SIGNAL' : (0x04, 48, Action5BlockType.FIXED), 'PRE_SIGNAL_SEMAPHORE' : (0x04, 112, Action5BlockType.FIXED), 'PRE_SIGNAL_SEMAPHORE_PBS' : (0x04, 240, Action5BlockType.OFFSET), 'CATENARY' : (0x05, 48, Action5BlockType.OFFSET), 'FOUNDATIONS_SLOPES' : (0x06, 74, Action5BlockType.FIXED), 'FOUNDATIONS_SLOPES_HALFTILES' : (0x06, 90, Action5BlockType.OFFSET), 'TTDP_GUI_25' : (0x07, 73, Action5BlockType.FIXED), 'TTDP_GUI' : (0x07, 93, Action5BlockType.FIXED), 'CANALS' : (0x08, 65, Action5BlockType.OFFSET), 'ONE_WAY_ROAD' : (0x09, 6, Action5BlockType.OFFSET), 'COLOURMAP_2CC' : (0x0A, 256, Action5BlockType.OFFSET), 'TRAMWAY' : (0x0B, 113, Action5BlockType.OFFSET), 'SNOWY_TEMPERATE_TREES' : (0x0C, 133, Action5BlockType.FIXED), 'COAST_TILES' : (0x0D, 16, Action5BlockType.FIXED), 'COAST_TILES_BASEGFX' : (0x0D, 10, Action5BlockType.FIXED), 'COAST_TILES_DIAGONAL' : (0x0D, 18, Action5BlockType.FIXED), 'NEW_SIGNALS' : (0x0E, 0, Action5BlockType.ANY), 'SLOPED_RAILS' : (0x0F, 12, Action5BlockType.OFFSET), 'AIRPORTS' : (0x10, 15, Action5BlockType.OFFSET), 'ROAD_STOPS' : (0x11, 8, Action5BlockType.OFFSET), 'AQUEDUCTS' : (0x12, 8, Action5BlockType.OFFSET), 'AUTORAIL' : (0x13, 55, Action5BlockType.OFFSET), 'FLAGS' : (0x14, 36, Action5BlockType.OFFSET), 'OTTD_GUI' : (0x15, 175, Action5BlockType.OFFSET), 'AIRPORT_PREVIEW' : (0x16, 9, Action5BlockType.OFFSET), 'RAILTYPE_TUNNELS': (0x17, 16, Action5BlockType.OFFSET), 'OTTD_RECOLOUR' : (0x18, 1, Action5BlockType.OFFSET), } 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" msg = msg.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 nml-0.4.4/nml/actions/base_action.py0000644000567200056720000000365212643457545020543 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" class BaseAction(object): def prepare_output(self, sprite_num): """ Called just before L{write}, this function can be used to do some last modifications to the action (like resolving some IDs that can't be resolved earlier). @param sprite_num: The sprite number in the nfo/grf output. @type sprite_num: C{int} """ pass def write(self, file): """ Write this action to the given outputfile. @param file: The outputfile to write the data to. @type file: L{SpriteOutputBase} """ raise NotImplementedError('write is not implemented in {:r}'.format(type(self))) def skip_action7(self): """ Can this action be skipped safely by an action7? @return True iff this action can be skipped by action7. """ return True def skip_action9(self): """ Can this action be skipped safely by an action9? @return True iff this action can be skipped by action9. """ return True def skip_needed(self): """ Do we need to skip this action at all? @return False when it doesn't matter whether this action is skipped or not. """ return True nml-0.4.4/nml/actions/real_sprite.py0000644000567200056720000006106612643457545020610 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, expression from nml.actions import base_action from nml.ast import assignment try: from PIL import Image except ImportError: import Image FLAG_NOCROP = 0x0040 FLAG_NOALPHA = 0x0100 FLAG_WHITE = 0x0200 FLAG_ANIM = 0x0400 real_sprite_flags = { 'CROP' : 0, # Allow cropping 'NOCROP' : FLAG_NOCROP, # Disallow cropping 'ALPHA' : 0, # Allow semi-transparency 'NOALPHA' : FLAG_NOALPHA, # Warn about semi-transparency 'WHITE' : FLAG_WHITE, # Allow pure-white 'NOWHITE' : 0, # Warn about pure-white 'ANIM' : FLAG_ANIM, # Allow anim colours 'NOANIM' : 0, # Warn about anim colours } palmap_d2w = [ 0, 215, 216, 136, 88, 106, 32, 33, # 0..7 40, 245, 10, 11, 12, 13, 14, 15, # 8..15 16, 17, 18, 19, 20, 21, 22, 23, # 16..23 24, 25, 26, 27, 28, 29, 30, 31, # 24..31 53, 54, 34, 35, 36, 37, 38, 39, # 32..39 178, 41, 42, 43, 44, 45, 46, 47, # 40..47 48, 49, 50, 51, 52, 53, 54, 55, # 48..55 56, 57, 58, 59, 60, 61, 62, 63, # 56..63 64, 65, 66, 67, 68, 69, 70, 71, # 64..71 72, 73, 74, 75, 76, 77, 78, 79, # 72..79 80, 81, 82, 83, 84, 85, 86, 87, # 80..87 96, 89, 90, 91, 92, 93, 94, 95, # 88..95 96, 97, 98, 99, 100, 101, 102, 103, # 96..103 104, 105, 53, 107, 108, 109, 110, 111, # 104..111 112, 113, 114, 115, 116, 117, 118, 119, # 112..119 120, 121, 122, 123, 124, 125, 126, 127, # 120..127 128, 129, 130, 131, 132, 133, 134, 135, # 128..135 170, 137, 138, 139, 140, 141, 142, 143, # 136..143 144, 145, 146, 147, 148, 149, 150, 151, # 144..151 152, 153, 154, 155, 156, 157, 158, 159, # 152..159 160, 161, 162, 163, 164, 165, 166, 167, # 160..167 168, 169, 170, 171, 172, 173, 174, 175, # 168..175 176, 177, 178, 179, 180, 181, 182, 183, # 176..183 184, 185, 186, 187, 188, 189, 190, 191, # 184..191 192, 193, 194, 195, 196, 197, 198, 199, # 192..199 200, 201, 202, 203, 204, 205, 206, 207, # 200..207 208, 209, 210, 211, 212, 213, 214, 215, # 208..215 216, 217, 246, 247, 248, 249, 250, 251, # 216..223 252, 253, 254, 227, 228, 229, 230, 231, # 224..231 232, 233, 234, 235, 236, 237, 238, 239, # 232..239 240, 241, 242, 243, 244, 217, 218, 219, # 240..247 220, 221, 222, 223, 224, 225, 226, 255, # 248..255 ] palmap_w2d = [ 0, 1, 2, 3, 4, 5, 6, 7, # 0..7 8, 9, 10, 11, 12, 13, 14, 15, # 8..15 16, 17, 18, 19, 20, 21, 22, 23, # 16..23 24, 25, 26, 27, 28, 29, 30, 31, # 24..31 6, 7, 34, 35, 36, 37, 38, 39, # 32..39 8, 41, 42, 43, 44, 45, 46, 47, # 40..47 48, 49, 50, 51, 52, 53, 54, 55, # 48..55 56, 57, 58, 59, 60, 61, 62, 63, # 56..63 64, 65, 66, 67, 68, 69, 70, 71, # 64..71 72, 73, 74, 75, 76, 77, 78, 79, # 72..79 80, 81, 82, 83, 84, 85, 86, 87, # 80..87 4, 89, 90, 91, 92, 93, 94, 95, # 88..95 96, 97, 98, 99, 100, 101, 102, 103, # 96..103 104, 105, 5, 107, 108, 109, 110, 111, # 104..111 112, 113, 114, 115, 116, 117, 118, 119, # 112..119 120, 121, 122, 123, 124, 125, 126, 127, # 120..127 128, 129, 130, 131, 132, 133, 134, 135, # 128..135 3, 137, 138, 139, 140, 141, 142, 143, # 136..143 144, 145, 146, 147, 148, 149, 150, 151, # 144..151 152, 153, 154, 155, 156, 157, 158, 159, # 152..159 160, 161, 162, 163, 164, 165, 166, 167, # 160..167 168, 169, 170, 171, 172, 173, 174, 175, # 168..175 176, 177, 178, 179, 180, 181, 182, 183, # 176..183 184, 185, 186, 187, 188, 189, 190, 191, # 184..191 192, 193, 194, 195, 196, 197, 198, 199, # 192..199 200, 201, 202, 203, 204, 205, 206, 207, # 200..207 208, 209, 210, 211, 212, 213, 214, 1, # 208..215 2, 245, 246, 247, 248, 249, 250, 251, # 216..223 252, 253, 254, 229, 230, 231, 227, 228, # 224..231 232, 233, 234, 235, 236, 237, 238, 239, # 232..239 240, 241, 242, 243, 244, 9, 218, 219, # 240..247 220, 221, 222, 223, 224, 225, 226, 255, # 248..255 ] translate_w2d = bytearray(v for v in palmap_w2d) translate_d2w = bytearray(v for v in palmap_d2w) 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 class RealSprite(object): """ @ivar param_list: Original parameters from NML source file. @type param_list: List of L{expression.Expression}, or C{None} @ivar label: Optional label from NML source file. @type label: L{expression.Identifier} or C{None} @ivar is_empty: True, if this is an empty sprite with zero dimension. @type is_empty: C{bool} @ivar file: Filename of primary image file. @type file: L{expression.StringLiteral} @ivar xpos: X position of sprite in source image file. @type xpos: L{expression.ConstantNumeric} or C{None} @ivar ypos: Y position of sprite in source image file. @type ypos: L{expression.ConstantNumeric} or C{None} @ivar mask_file: Filename of additional mask image file. @type mask_file: L{expression.StringLiteral} or C{None} @ivar mask_pos: Position of sprite in source mask image file. @type mask_pos: Pair of L{expression.ConstantNumeric}, or C{None} @ivar xsize: X size of sprite in both source image and mask file. @type xsize: L{expression.ConstantNumeric} or C{None} @ivar ysize: Y position of sprite in both source image and mask file. @type ysize: L{expression.ConstantNumeric} or C{None} @ivar xrel: X sprite offset. @type xrel: L{expression.ConstantNumeric} @ivar yrel: Y sprite offset. @type yrel: L{expression.ConstantNumeric} @ivar flags: Cropping/warning flags. @type flags: L{expression.ConstantNumeric} @ivar poslist: Position of creation of the sprite, if available. @type poslist: C{list} of L{Position} """ def __init__(self, param_list = None, label = None, poslist = None): self.param_list = param_list self.label = label self.is_empty = False self.xpos = None self.ypos = None self.xsize = None self.ysize = None if poslist is None: self.poslist = [] else: self.poslist = poslist def debug_print(self, indentation): generic.print_dbg(indentation, 'Real sprite, parameters:') for param in self.param_list: param.debug_print(indentation + 2) def get_labels(self): labels = {} if self.label is not None: labels[self.label.value] = 0 return labels, 1 def expand(self, default_file, default_mask_file, poslist, id_dict): return [parse_real_sprite(self, default_file, default_mask_file, poslist, id_dict)] def check_sprite_size(self): generic.check_range(self.xpos.value, 0, 0x7fffFFFF, "Real sprite paramater 'xpos'", self.xpos.pos) generic.check_range(self.ypos.value, 0, 0x7fffFFFF, "Real sprite paramater 'ypos'", self.ypos.pos) generic.check_range(self.xsize.value, 1, 0xFFFF, "Real sprite paramater 'xsize'", self.xsize.pos) generic.check_range(self.ysize.value, 1, 0xFFFF, "Real sprite paramater 'ysize'", self.ysize.pos) def validate_size(self): """ Check if xpos/ypos/xsize/ysize are already set and if not, set them to 0,0,image_width,image_height. """ if self.xpos is None: im = Image.open(generic.find_file(self.file.value)) self.xpos = expression.ConstantNumeric(0) self.ypos = expression.ConstantNumeric(0) self.xsize = expression.ConstantNumeric(im.size[0]) self.ysize = expression.ConstantNumeric(im.size[1]) self.check_sprite_size() if self.mask_pos is None: self.mask_pos = (self.xpos, self.ypos) def __str__(self): ret = "" if self.label is not None: ret += str(self.label) + ": " ret += "[" ret += ", ".join([str(param) for param in self.param_list]) ret += "]" return ret def get_cache_key(self, crop_sprites): """ Assemble the sprite meta data into a key, able to identify the sprite. @param crop_sprites: Whether to crop sprites, which allow it. @type crop_sprites: C{bool} @return: Key @rtype: C{tuple} """ filename_8bpp = None filename_32bpp = None if self.bit_depth == 8: filename_8bpp = self.file else: filename_32bpp = self.file filename_8bpp = self.mask_file x = self.xpos.value y = self.ypos.value size_x = self.xsize.value size_y = self.ysize.value if self.bit_depth == 8 or self.mask_pos is None: mask_x, mask_y = x, y else: mask_x = self.mask_pos[0].value mask_y = self.mask_pos[1].value rgb_file, rgb_rect = (filename_32bpp.value, (x, y, size_x, size_y)) if filename_32bpp is not None else (None, None) mask_file, mask_rect = (filename_8bpp.value, (mask_x, mask_y, size_x, size_y)) if filename_8bpp is not None else (None, None) do_crop = crop_sprites and ((self.flags.value & FLAG_NOCROP) == 0) return (rgb_file, rgb_rect, mask_file, mask_rect, do_crop) class SpriteAction(base_action.BaseAction): """ @ivar sprite_num: Number of the sprite, or C{None} if not decided yet. @type sprite_num: C{int} or C{None} @ivar last: Whether this sprite action is the last of a series. @type last: C{bool} """ def __init__(self): self.sprite_num = None self.last = False def prepare_output(self, sprite_num): if self.sprite_num is not None and self.sprite_num.value != sprite_num: msg = "Sprite number {:d} given in base_graphics-block, but it doesn't match output sprite number {:d}" msg = msg.format(self.sprite_num.value, sprite_num) raise generic.ScriptError(msg) class RealSpriteAction(SpriteAction): def __init__(self): SpriteAction.__init__(self) self.sprite_list = [] def add_sprite(self, sprite): self.sprite_list.append(sprite) def write(self, file): if len(self.sprite_list) == 0 or self.sprite_list[0].is_empty: file.print_empty_realsprite() else: file.print_sprite([s for s in self.sprite_list if not s.is_empty]) if self.last: file.newline() class RecolourSprite(object): def __init__(self, mapping, label = None, poslist = None): self.mapping = mapping self.label = label if poslist is None: self.poslist = [] else: self.poslist = poslist def debug_print(self, indentation): generic.print_dbg(indentation, 'Recolour sprite, mapping:') for assignment in self.mapping: generic.print_dbg(indentation + 2, '{}: {};'.format(assignment.name, assignment.value)) def get_labels(self): labels = {} if self.label is not None: labels[self.label.value] = 0 return labels, 1 def expand(self, default_file, default_mask_file, poslist, id_dict): # create new struct, needed for template expansion new_mapping = [] for old_assignment in self.mapping: from_min_value = old_assignment.name.min.reduce_constant([id_dict]) from_max_value = from_min_value if old_assignment.name.max is None else old_assignment.name.max.reduce_constant([id_dict]) to_min_value = old_assignment.value.min.reduce_constant([id_dict]) to_max_value = None if old_assignment.value.max is None else old_assignment.value.max.reduce_constant([id_dict]) new_mapping.append(assignment.Assignment(assignment.Range(from_min_value, from_max_value), assignment.Range(to_min_value, to_max_value), old_assignment.pos)) return [RecolourSprite(new_mapping, poslist = poslist)] def __str__(self): ret = "" if self.label is None else str(self.label) + ": " ret += "recolour_sprite {\n" for assignment in self.mapping: ret += '{}: {};'.format(assignment.name, assignment.value) ret += "}" return ret class RecolourSpriteAction(SpriteAction): def __init__(self, sprite): SpriteAction.__init__(self) self.sprite = sprite self.output_table = [] def prepare_output(self, sprite_num): SpriteAction.prepare_output(self, sprite_num) colour_mapping = {} for assignment in self.sprite.mapping: if assignment.value.max is not None and assignment.name.max.value - assignment.name.min.value != assignment.value.max.value - assignment.value.min.value: raise generic.ScriptError("From and to ranges in a recolour block need to have the same size", assignment.pos) for i in range(assignment.name.max.value - assignment.name.min.value + 1): idx = assignment.name.min.value + i val = assignment.value.min.value if assignment.value.max is not None: val += i colour_mapping[idx] = val for i in range(256): if i in colour_mapping: colour = colour_mapping[i] else: colour = i self.output_table.append(colour) def write(self, file): file.start_sprite(257) file.print_bytex(0) if file.palette not in ("DEFAULT", "LEGACY"): raise generic.ScriptError("Recolour sprites are only supported when writing to the DEFAULT (DOS) or LEGACY (WIN) palette. If you don't have any real sprites use the commandline option -p to set a palette.") colour_table = self.output_table if file.palette == "DEFAULT" else convert_palette(self.output_table) for idx, colour in enumerate(colour_table): if idx % 16 == 0: file.newline() file.print_bytex(colour) if self.last: file.newline() file.end_sprite() class TemplateUsage(object): def __init__(self, name, param_list, label, pos): self.name = name self.param_list = param_list self.label = label self.pos = pos def debug_print(self, indentation): generic.print_dbg(indentation, 'Template used:', self.name.value) generic.print_dbg(indentation + 2, 'Parameters:') for param in self.param_list: param.debug_print(indentation + 4) def get_labels(self): # Load labels from the template definition if self.name.value not in sprite_template_map: raise generic.ScriptError("Encountered unknown template identifier: " + self.name.value, self.name.pos) labels, offset = sprite_template_map[self.name.value].get_labels() # Add (possibly) label applied to ourselves if self.label is not None: if self.label.value in labels: raise generic.ScriptError("Duplicate label encountered; '{}' already exists.".format(self.label.value), self.pos) labels[self.label.value] = 0 return labels, offset def expand(self, default_file, default_mask_file, poslist, parameters): if self.name.value not in sprite_template_map: raise generic.ScriptError("Encountered unknown template identifier: " + self.name.value, self.name.pos) template = sprite_template_map[self.name.value] if len(self.param_list) != len(template.param_list): raise generic.ScriptError("Incorrect number of template arguments. Expected " + str(len(template.param_list)) + ", got " + str(len(self.param_list)), self.pos) param_dict = {} for i, param in enumerate(self.param_list): param = param.reduce([real_sprite_flags, parameters]) if not isinstance(param, (expression.ConstantNumeric, expression.StringLiteral)): raise generic.ScriptError("Template parameters should be compile-time constants", param.pos) param_dict[template.param_list[i].value] = param.value return parse_sprite_list(template.sprite_list, default_file, default_mask_file, poslist + [self.pos], param_dict) def __str__(self): return "{}({})".format(self.name, ", ".join(str(param) for param in self.param_list)) def parse_real_sprite(sprite, default_file, default_mask_file, poslist, id_dict): # check the number of parameters num_param = len(sprite.param_list) if num_param == 0: sprite.is_empty = True return sprite elif not (2 <= num_param <= 9): raise generic.ScriptError("Invalid number of arguments for real sprite. Expected 2..9.", sprite.param_list[0].pos) # create new sprite struct, needed for template expansion new_sprite = RealSprite(poslist = poslist + sprite.poslist) param_offset = 0 if num_param >= 6: # xpos, ypos, xsize and ysize are all optional. If not specified they'll default # to 0, 0, image_width, image_height new_sprite.xpos = sprite.param_list[0].reduce_constant([id_dict]) new_sprite.ypos = sprite.param_list[1].reduce_constant([id_dict]) new_sprite.xsize = sprite.param_list[2].reduce_constant([id_dict]) new_sprite.ysize = sprite.param_list[3].reduce_constant([id_dict]) new_sprite.check_sprite_size() param_offset += 4 new_sprite.xrel = sprite.param_list[param_offset].reduce_constant([id_dict]) new_sprite.yrel = sprite.param_list[param_offset + 1].reduce_constant([id_dict]) generic.check_range(new_sprite.xrel.value, -0x8000, 0x7fff, "Real sprite paramater {:d} 'xrel'".format(param_offset + 1), new_sprite.xrel.pos) generic.check_range(new_sprite.yrel.value, -0x8000, 0x7fff, "Real sprite paramater {:d} 'yrel'".format(param_offset + 2), new_sprite.yrel.pos) param_offset += 2 # Next may follow any combination of (flags, filename, mask), but always in that order new_sprite.flags = expression.ConstantNumeric(0) if num_param > param_offset: try: new_sprite.flags = sprite.param_list[param_offset].reduce_constant([real_sprite_flags, id_dict]) param_offset += 1 except generic.ConstError: pass new_sprite.file = default_file if num_param > param_offset and not isinstance(sprite.param_list[param_offset], expression.Array): new_sprite.file = sprite.param_list[param_offset].reduce([id_dict]) param_offset += 1 if not isinstance(new_sprite.file, expression.StringLiteral): raise generic.ScriptError("Real sprite parameter {:d} 'file' should be a string literal".format(param_offset + 1), new_sprite.file.pos) if new_sprite.file is None: raise generic.ScriptError("No image file specified for real sprite", sprite.param_list[0].pos) new_sprite.mask_file = default_mask_file new_sprite.mask_pos = None if num_param > param_offset: mask = sprite.param_list[param_offset] param_offset += 1 # Mask may be either string (file only) or array (empty => no mask, 1 value => file only, 2 => offsets only, 3 => file + offsets) if isinstance(mask, expression.Array): if not (0 <= len(mask.values) <= 3): raise generic.ScriptError("Real sprite mask should be an array with 0 to 3 values, encountered {:d}".format(len(mask.values)), mask.pos) if len(mask.values) == 0: # disable any default mask new_sprite.mask_file = None else: if len(mask.values) & 1: new_sprite.mask_file = mask.values[0].reduce([id_dict]) if not isinstance(new_sprite.mask_file, expression.StringLiteral): raise generic.ScriptError("Real sprite parameter 'mask_file' should be a string literal", new_sprite.file.pos) if len(mask.values) & 2: new_sprite.mask_pos = tuple(mask.values[i].reduce_constant([id_dict]) for i in range(-2, 0)) # Check that there is also a mask specified, else the offsets make no sense if new_sprite.mask_file is None: raise generic.ScriptError("Mask offsets are specified, but there is no mask file set.", new_sprite.mask_pos[0].pos) else: new_sprite.mask_file = mask.reduce([id_dict]) if not isinstance(new_sprite.mask_file, expression.StringLiteral): raise generic.ScriptError("Real sprite parameter {:d} 'mask' should be an array or string literal".format(param_offset + 1), new_sprite.file.pos) if num_param > param_offset: raise generic.ScriptError("Real sprite has too many parameters, the last {:d} parameter(s) cannot be parsed.".format(num_param - param_offset), sprite.param_list[param_offset].pos) return new_sprite sprite_template_map = {} def parse_sprite_list(sprite_list, default_file, default_mask_file, poslist, parameters = {}): real_sprite_list = [] for sprite in sprite_list: real_sprite_list.extend(sprite.expand(default_file, default_mask_file, poslist, parameters)) return real_sprite_list 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 nml-0.4.4/nml/actions/actionE.py0000644000567200056720000000232012643457545017645 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml.actions import base_action class ActionE(base_action.BaseAction): def __init__(self, grfid_list): self.grfid_list = grfid_list def write(self, file): size = 2 + 4 * len(self.grfid_list) file.start_sprite(size) file.print_bytex(0x0E) file.print_byte(len(self.grfid_list)) for grfid in self.grfid_list: file.newline() file.print_dwordx(grfid) file.newline() file.end_sprite() def parse_deactivate_block(block): return [ActionE(block.grfid_list)] nml-0.4.4/nml/actions/action3.py0000644000567200056720000005003712643457545017633 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, expression, global_constants, nmlop from nml.actions import base_action, action0, action2, action2real, action2var, action3_callbacks, action6, actionD class Action3(base_action.BaseAction): """ Class representing a single Action3. @ivar feature: Action3 feature byte. @type feature: C{int} @ivar id: Item ID of the item that this action3 represents. @type id: C{int} @ivar is_livery_override: Whether this action 3 is a livery override @type is_livery_override: C{bool} @ivar cid_mappings: List of mappings that map cargo IDs to Action2s. @type cid_mappings: C{list} of C{tuple}: (C{int}, L{Expression} before prepare_output, C{int} afterwards, C{str}) @ivar def_cid: Default Action2 to use if no cargo ID matches. @type def_cid: C{None} or L{SpriteGroupRef} before prepare_output, C{int} afterwards @ivar references: All Action2s that are referenced by this Action3. @type references: C{list} of L{Action2Reference} """ def __init__(self, feature, id, is_livery_override): self.feature = feature self.id = id self.is_livery_override = is_livery_override self.cid_mappings = [] self.references = [] def prepare_output(self, sprite_num): action2.free_references(self) map_cid = lambda cid: cid.get_action2_id(self.feature) if isinstance(cid, expression.SpriteGroupRef) else cid.value | 0x8000 self.cid_mappings = [(cargo, map_cid(cid), comment) for cargo, cid, comment in self.cid_mappings] if self.def_cid is None: self.def_cid = 0 else: self.def_cid = map_cid(self.def_cid) def write(self, file): size = 7 + 3 * len(self.cid_mappings) if self.feature <= 3: size += 2 # Vehicles use extended byte file.start_sprite(size) file.print_bytex(3) file.print_bytex(self.feature) file.print_bytex(1 if not self.is_livery_override else 0x81) # a single id file.print_varx(self.id, 3 if self.feature <= 3 else 1) file.print_byte(len(self.cid_mappings)) file.newline() for cargo, cid, comment in self.cid_mappings: file.print_bytex(cargo) file.print_wordx(cid) file.newline(comment) file.print_wordx(self.def_cid) file.newline(self.default_comment) file.end_sprite() def skip_action9(self): return False # Make sure all action2s created here get a unique name action2_id = 0 def create_intermediate_varaction2(feature, varact2parser, mapping, default, pos): """ Create a varaction2 based on a parsed expression and a value mapping @param feature: Feature of the varaction2 @type feature: C{int} @param varact2parser: Parser containing a parsed expression @type varact2parser: L{Varaction2Parser} @param mapping: Mapping of various values to sprite groups / return values, with a possible extra function to apply to the return value @type mapping: C{dict} that maps C{int} to C{tuple} of (L{SpriteGroupRef}, C{function}, or C{None}) @param default: Default sprite group if no value matches @type default: L{SpriteGroupRef} @param pos: Positional context. @type pos: L{Position} @return: A tuple containing the action list and a reference to the created action2 @rtype: C{tuple} of (C{list} of L{BaseAction}, L{SpriteGroupRef}) """ global action2_id action_list = varact2parser.extra_actions act6 = action6.Action6() for mod in varact2parser.mods: act6.modify_bytes(mod.param, mod.size, mod.offset + 4) name = expression.Identifier("@action3_{:d}".format(action2_id)) action2_id += 1 varaction2 = action2var.Action2Var(feature, name.value, pos, 0x89) varaction2.var_list = varact2parser.var_list offset = 5 + varact2parser.var_list_size for proc in varact2parser.proc_call_list: action2.add_ref(proc, varaction2, True) for switch_value in sorted(mapping): return_value, ret_value_function = mapping[switch_value] if ret_value_function is None: result, comment = action2var.parse_result(return_value, action_list, act6, offset, varaction2, None, 0x89) else: if isinstance(return_value, expression.SpriteGroupRef): # We need to execute the callback via a procedure call # then return CB_FAILED if the CB failed, # or the CB result (with ret_value_function applied) if successful if return_value.name.value == 'CB_FAILED': result, comment = action2var.parse_result(return_value, action_list, act6, offset, varaction2, None, 0x89) else: extra_actions, result, comment = create_proc_call_varaction2(feature, return_value, ret_value_function, pos) action_list.extend(extra_actions) else: return_value = ret_value_function(return_value).reduce() result, comment = action2var.parse_result(return_value, action_list, act6, offset, varaction2, None, 0x89) varaction2.ranges.append(action2var.VarAction2Range(expression.ConstantNumeric(switch_value), expression.ConstantNumeric(switch_value), result, comment)) offset += 10 result, comment = action2var.parse_result(default, action_list, act6, offset, varaction2, None, 0x89) varaction2.default_result = result varaction2.default_comment = comment return_ref = expression.SpriteGroupRef(name, [], None, varaction2) if len(act6.modifications) > 0: action_list.append(act6) action_list.append(varaction2) return (action_list, return_ref) def create_proc_call_varaction2(feature, proc, ret_value_function, pos): """ Create a varaction2 that executes a procedure call and applies a function on the result @param feature: Feature of the varaction2 @type feature: C{int} @param proc: Procedure to execute @type proc: L{SpriteGroupRef} @param ret_value_function: Function to apply on the result (L{Expression} -> L{Expression}) @type ret_value_function: C{function} @param pos: Positional context. @type pos: L{Position} @return: A list of extra actions, reference to the created action2 and a comment to add @rtype: C{tuple} of (C{list} of L{BaseAction}, L{SpriteGroupRef}, C{str}) """ varact2parser = action2var.Varaction2Parser(feature) varact2parser.parse_proc_call(proc) mapping = {0xFFFF: (expression.SpriteGroupRef(expression.Identifier('CB_FAILED'), [], None), None)} default = ret_value_function(expression.Variable(expression.ConstantNumeric(0x1C))) action_list, result = create_intermediate_varaction2(feature, varact2parser, mapping, default, pos) comment = result.name.value + ';' return (action_list, result, comment) def create_cb_choice_varaction2(feature, expr, mapping, default, pos): """ Create a varaction2 that maps callback numbers to various sprite groups @param feature: Feature of the varaction2 @type feature: C{int} @param expr: Expression to evaluate @type expr: L{Expression} @param mapping: Mapping of various values to sprite groups, with a possible extra function to apply to the return value @type mapping: C{dict} that maps C{int} to C{tuple} of (L{SpriteGroupRef}, C{function}, or C{None}) @param default: Default sprite group if no value matches @type default: L{SpriteGroupRef} @param pos: Positional context. @type pos: L{Position} @return: A tuple containing the action list and a reference to the created action2 @rtype: C{tuple} of (C{list} of L{BaseAction}, L{SpriteGroupRef}) """ varact2parser = action2var.Varaction2Parser(feature) varact2parser.parse_expr(expr) return create_intermediate_varaction2(feature, varact2parser, mapping, default, pos) def create_action3(feature, id, action_list, act6, is_livery_override): # Vehicles use an extended byte size = 2 if feature <= 3 else 1 offset = 4 if feature <= 3 else 3 id, offset = actionD.write_action_value(id, action_list, act6, offset, size) return Action3(feature, id.value, is_livery_override) house_tiles = { 0 : 'n', # 1x1 2 : 'nw', # 2x1 3 : 'ne', # 1x2 4 : 'news', # 2x2 } def parse_graphics_block(graphics_block, feature, id, size, is_livery_override = False): """ Parse a graphics block (or livery override) into a list of actions, mainly action3 @param graphics_block: Graphics-block to parse @type graphics_block: L{GraphicsBlock} @param feature: Feature of the associated item @type feature: C{int} @param id: ID of the associated item @type id: L{Expression} @param size: Size of the associated item (relevant for houses only) @type size: L{ConstantNumeric} or C{None} @param is_livery_override: Whether this is a livery override instead of a normal graphics block @type is_livery_override: C{bool} @return: The resulting list of actions @rtype: L{BaseAction} """ action_list = action2real.create_spriteset_actions(graphics_block) if feature == 0x07: # Multi-tile houses need more work size_bit = size.value if size is not None else 0 for i, tile in enumerate(house_tiles[size_bit]): tile_id = id if i == 0 else expression.BinOp(nmlop.ADD, id, expression.ConstantNumeric(i, id.pos), id.pos).reduce() action_list.extend(parse_graphics_block_single_id(graphics_block, feature, tile_id, is_livery_override, tile, id)) else: action_list.extend(parse_graphics_block_single_id(graphics_block, feature, id, is_livery_override)) return action_list 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 nml-0.4.4/nml/actions/actionD.py0000644000567200056720000004513712643457545017661 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, global_constants, expression, nmlop from nml.actions import base_action, action6 from nml.ast import base_statement import nml class ActionD(base_action.BaseAction): """ ActionD class General procedure: target = param1 op param2 If one of the params is 0xFF, the value of 'data' is read instead. @ivar target: Number of the target parameter @ivar target: L{ConstantNumeric} @ivar param1: Paramter number of the first operand @type param1: L{ConstantNumeric} @ivar op: (Binary) operator to use. @type op: L{Operator} @ivar param2: Paramter number of the second operand @type param2: L{ConstantNumeric} @ivar data: Numerical data that will be used instead of parameter value, if the parameter number is 0xFF. None if n/a. @type data: L{ConstantNumeric} or C{None} """ def __init__(self, target, param1, op, param2, data = None): self.target = target self.param1 = param1 self.op = op self.param2 = param2 self.data = data def write(self, file): size = 5 if self.data is not None: size += 4 #print the statement for easier debugging str1 = "param[{}]".format(self.param1) if self.param1.value != 0xFF else str(self.data) str2 = "param[{}]".format(self.param2) if self.param2.value != 0xFF else str(self.data) str_total = self.op.to_string(str1, str2) if self.op != nmlop.ASSIGN else str1 file.comment("param[{}] = {}".format(self.target, str_total)) file.start_sprite(size) file.print_bytex(0x0D) self.target.write(file, 1) file.print_bytex(self.op.actd_num, self.op.actd_str) self.param1.write(file, 1) self.param2.write(file, 1) if self.data is not None: self.data.write(file, 4) file.newline() file.end_sprite() def skip_action7(self): return False class ParameterAssignment(base_statement.BaseStatement): """ AST-node for a parameter assignment. NML equivalent: param[$num] = $expr; @ivar param: Target expression to assign (must evaluate to a parameter) @type param: L{Expression} @ivar value: Value to assign to this parameter @type value: L{Expression} """ def __init__(self, param, value): base_statement.BaseStatement.__init__(self, "parameter assignment", param.pos) self.param = param self.value = value 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): 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 debug_print(self, indentation): generic.print_dbg(indentation, 'Parameter assignment') self.param.debug_print(indentation + 2) self.value.debug_print(indentation + 2) def get_action_list(self): return parse_actionD(self) def __str__(self): return '{} = {};\n'.format(self.param, self.value) #prevent evaluating common sub-expressions multiple times def parse_subexpression(expr, action_list): if isinstance(expr, expression.ConstantNumeric) or \ (isinstance(expr, expression.Parameter) and isinstance(expr.num, expression.ConstantNumeric)): return expr else: tmp_param, tmp_param_actions = get_tmp_parameter(expr) action_list.extend(tmp_param_actions) return expression.Parameter(expression.ConstantNumeric(tmp_param)) #returns a (param_num, action_list) tuple. def get_tmp_parameter(expr): param = action6.free_parameters.pop(expr.pos) actions = parse_actionD(ParameterAssignment(expression.Parameter(expression.ConstantNumeric(param)), expr)) return (param, actions) def write_action_value(expr, action_list, act6, offset, size): """ Write a single numeric value that is part of an action Possibly use action6 and/or actionD if needed @param expr: Expression to write @type expr: L{Expression} @param action_list: Action list to append any extra actions to @type action_list: C{list} of L{BaseAction} @param act6: Action6 to append any modifications to @type act6: L{Action6} @param offset: Offset to use for the action 6 (if applicable) @type offset: C{int} @param size: Size to use for the action 6 (if applicable) @type size: C{int} @return: 2-tuple containing - - the value to be used when writing the action (0 if overwritten by action 6) - the offset just past this numeric value (=offset + size) @rtype: C{tuple} of (L{ConstantNumeric}, C{int}) """ if isinstance(expr, expression.ConstantNumeric): result = expr elif isinstance(expr, expression.Parameter) and isinstance(expr.num, expression.ConstantNumeric): act6.modify_bytes(expr.num.value, size, offset) result = expression.ConstantNumeric(0) else: tmp_param, tmp_param_actions = get_tmp_parameter(expr) action_list.extend(tmp_param_actions) act6.modify_bytes(tmp_param, size, offset) result = expression.ConstantNumeric(0) return result, offset + size def parse_ternary_op(assignment): assert isinstance(assignment.value, expression.TernaryOp) actions = parse_actionD(ParameterAssignment(assignment.param, assignment.value.expr2)) cond_block = nml.ast.conditional.Conditional(assignment.value.guard, [ParameterAssignment(assignment.param, assignment.value.expr1)], None) actions.extend(nml.ast.conditional.ConditionalList([cond_block]).get_action_list()) return actions def parse_special_check(assignment): check = assignment.value assert isinstance(check, expression.SpecialCheck) actions = parse_actionD(ParameterAssignment(assignment.param, expression.ConstantNumeric(check.results[0]))) value = check.value if check.mask is not None: value &= check.mask value += check.mask << 32 assert check.varsize == 8 else: assert check.varsize <= 4 actions.append(nml.actions.action7.SkipAction(9, check.varnum, check.varsize, check.op, value, 1)) actions.extend(parse_actionD(ParameterAssignment(assignment.param, expression.ConstantNumeric(check.results[1])))) return actions def parse_grm(assignment): assert isinstance(assignment.value, expression.GRMOp) action6.free_parameters.save() action_list = [] act6 = action6.Action6() assert isinstance(assignment.param, expression.Parameter) target = assignment.param.num if isinstance(target, expression.Parameter) and isinstance(target.num, expression.ConstantNumeric): act6.modify_bytes(target.num.value, 1, 1) target = expression.ConstantNumeric(0) elif not isinstance(target, expression.ConstantNumeric): tmp_param, tmp_param_actions = get_tmp_parameter(target) act6.modify_bytes(tmp_param, 1, 1) target = expression.ConstantNumeric(0) action_list.extend(tmp_param_actions) op = nmlop.ASSIGN param1 = assignment.value.op param2 = expression.ConstantNumeric(0xFE) data = expression.ConstantNumeric(0xFF | (assignment.value.feature << 8) | (assignment.value.count << 16)) if len(act6.modifications) > 0: action_list.append(act6) action_list.append(ActionD(target, param1, op, param2, data)) action6.free_parameters.restore() return action_list def parse_hasbit(assignment): assert isinstance(assignment.value, expression.BinOp) and (assignment.value.op == nmlop.HASBIT or assignment.value.op == nmlop.NOTHASBIT) actions = parse_actionD(ParameterAssignment(assignment.param, expression.ConstantNumeric(0))) cond_block = nml.ast.conditional.Conditional(assignment.value, [ParameterAssignment(assignment.param, expression.ConstantNumeric(1))], None) actions.extend(nml.ast.conditional.ConditionalList([cond_block]).get_action_list()) return actions def parse_min_max(assignment): assert isinstance(assignment.value, expression.BinOp) and assignment.value.op in (nmlop.MIN, nmlop.MAX) #min(a, b) ==> a < b ? a : b. #max(a, b) ==> a > b ? a : b. action6.free_parameters.save() action_list = [] expr1 = parse_subexpression(assignment.value.expr1, action_list) expr2 = parse_subexpression(assignment.value.expr2, action_list) guard = expression.BinOp(nmlop.CMP_LT if assignment.value.op == nmlop.MIN else nmlop.CMP_GT, expr1, expr2) action_list.extend(parse_actionD(ParameterAssignment(assignment.param, expression.TernaryOp(guard, expr1, expr2, None)))) action6.free_parameters.restore() return action_list def parse_boolean(assignment): assert isinstance(assignment.value, expression.Boolean) actions = parse_actionD(ParameterAssignment(assignment.param, expression.ConstantNumeric(0))) expr = expression.BinOp(nmlop.CMP_NEQ, assignment.value.expr, expression.ConstantNumeric(0)) cond_block = nml.ast.conditional.Conditional(expr, [ParameterAssignment(assignment.param, expression.ConstantNumeric(1))], None) actions.extend(nml.ast.conditional.ConditionalList([cond_block]).get_action_list()) return actions def transform_bin_op(assignment): op = assignment.value.op expr1 = assignment.value.expr1 expr2 = assignment.value.expr2 extra_actions = [] if op == nmlop.CMP_GE: expr1, expr2 = expr2, expr1 op = nmlop.CMP_LE if op == nmlop.CMP_LE: extra_actions.extend(parse_actionD(ParameterAssignment(assignment.param, expression.BinOp(nmlop.SUB, expr1, expr2)))) op = nmlop.CMP_LT expr1 = assignment.param expr2 = expression.ConstantNumeric(1) if op == nmlop.CMP_GT: expr1, expr2 = expr2, expr1 op = nmlop.CMP_LT if op == nmlop.CMP_LT: extra_actions.extend(parse_actionD(ParameterAssignment(assignment.param, expression.BinOp(nmlop.SUB, expr1, expr2)))) op = nmlop.SHIFTU_LEFT #shift left by negative number = shift right expr1 = assignment.param expr2 = expression.ConstantNumeric(-31) elif op == nmlop.CMP_NEQ: extra_actions.extend(parse_actionD(ParameterAssignment(assignment.param, expression.BinOp(nmlop.SUB, expr1, expr2)))) op = nmlop.DIV # We rely here on the (ondocumented) behavior of both OpenTTD and TTDPatch # that expr/0==expr. What we do is compute A/A, which will result in 1 if # A != 0 and in 0 if A == 0 expr1 = assignment.param expr2 = assignment.param elif op == nmlop.CMP_EQ: # We compute A==B by doing not(A - B) which will result in a value != 0 # if A is equal to B extra_actions.extend(parse_actionD(ParameterAssignment(assignment.param, expression.BinOp(nmlop.SUB, expr1, expr2)))) # Clamp the value to 0/1, see above for details extra_actions.extend(parse_actionD(ParameterAssignment(assignment.param, expression.BinOp(nmlop.DIV, assignment.param, assignment.param)))) op = nmlop.SUB expr1 = expression.ConstantNumeric(1) expr2 = assignment.param if op == nmlop.SHIFT_RIGHT or op == nmlop.SHIFTU_RIGHT: if isinstance(expr2, expression.ConstantNumeric): expr2.value *= -1 else: expr2 = expression.BinOp(nmlop.SUB, expression.ConstantNumeric(0), expr2) op = nmlop.SHIFT_LEFT if op == nmlop.SHIFT_RIGHT else nmlop.SHIFTU_LEFT elif op == nmlop.XOR: #a ^ b ==> (a | b) - (a & b) expr1 = parse_subexpression(expr1, extra_actions) expr2 = parse_subexpression(expr2, extra_actions) tmp_param1, tmp_action_list1 = get_tmp_parameter(expression.BinOp(nmlop.OR, expr1, expr2)) tmp_param2, tmp_action_list2 = get_tmp_parameter(expression.BinOp(nmlop.AND, expr1, expr2)) extra_actions.extend(tmp_action_list1) extra_actions.extend(tmp_action_list2) expr1 = expression.Parameter(expression.ConstantNumeric(tmp_param1)) expr2 = expression.Parameter(expression.ConstantNumeric(tmp_param2)) op = nmlop.SUB return op, expr1, expr2, extra_actions def parse_actionD(assignment): assignment.value.supported_by_actionD(True) if isinstance(assignment.param, expression.SpecialParameter): assignment.param, assignment.value = assignment.param.to_assignment(assignment.value) elif isinstance(assignment.param, expression.Identifier): assignment.param = expression.Parameter(expression.ConstantNumeric(global_constants.named_parameters[assignment.param.value]), assignment.param.pos) assert isinstance(assignment.param, expression.Parameter) if isinstance(assignment.value, expression.SpecialParameter): assignment.value = assignment.value.to_reading() if isinstance(assignment.value, expression.TernaryOp): return parse_ternary_op(assignment) if isinstance(assignment.value, expression.SpecialCheck): return parse_special_check(assignment) if isinstance(assignment.value, expression.GRMOp): return parse_grm(assignment) if isinstance(assignment.value, expression.BinOp): op = assignment.value.op if op == nmlop.HASBIT or op == nmlop.NOTHASBIT: return parse_hasbit(assignment) elif op == nmlop.MIN or op == nmlop.MAX: return parse_min_max(assignment) if isinstance(assignment.value, expression.Boolean): return parse_boolean(assignment) if isinstance(assignment.value, expression.Not): expr = expression.BinOp(nmlop.SUB, expression.ConstantNumeric(1), assignment.value.expr) assignment = ParameterAssignment(assignment.param, expr) if isinstance(assignment.value, expression.BinNot): expr = expression.BinOp(nmlop.SUB, expression.ConstantNumeric(0xFFFFFFFF), assignment.value.expr) assignment = ParameterAssignment(assignment.param, expr) action6.free_parameters.save() action_list = [] act6 = action6.Action6() assert isinstance(assignment.param, expression.Parameter) target = assignment.param.num if isinstance(target, expression.Parameter) and isinstance(target.num, expression.ConstantNumeric): act6.modify_bytes(target.num.value, 1, 1) target = expression.ConstantNumeric(0) elif not isinstance(target, expression.ConstantNumeric): tmp_param, tmp_param_actions = get_tmp_parameter(target) act6.modify_bytes(tmp_param, 1, 1) target = expression.ConstantNumeric(0) action_list.extend(tmp_param_actions) data = None #print assignment.value if isinstance(assignment.value, expression.ConstantNumeric): op = nmlop.ASSIGN param1 = expression.ConstantNumeric(0xFF) param2 = expression.ConstantNumeric(0) data = assignment.value elif isinstance(assignment.value, expression.Parameter): if isinstance(assignment.value.num, expression.ConstantNumeric): op = nmlop.ASSIGN param1 = assignment.value.num else: tmp_param, tmp_param_actions = get_tmp_parameter(assignment.value.num) act6.modify_bytes(tmp_param, 1, 3) action_list.extend(tmp_param_actions) op = nmlop.ASSIGN param1 = expression.ConstantNumeric(0) param2 = expression.ConstantNumeric(0) elif isinstance(assignment.value, expression.OtherGRFParameter): op = nmlop.ASSIGN if isinstance(assignment.value.num, expression.ConstantNumeric): param1 = assignment.value.num else: tmp_param, tmp_param_actions = get_tmp_parameter(assignment.value.num) act6.modify_bytes(tmp_param, 1, 3) action_list.extend(tmp_param_actions) param1 = expression.ConstantNumeric(0) param2 = expression.ConstantNumeric(0xFE) data = expression.ConstantNumeric(expression.parse_string_to_dword(assignment.value.grfid)) elif isinstance(assignment.value, expression.PatchVariable): op = nmlop.ASSIGN param1 = expression.ConstantNumeric(assignment.value.num) param2 = expression.ConstantNumeric(0xFE) data = expression.ConstantNumeric(0xFFFF) elif isinstance(assignment.value, expression.BinOp): op, expr1, expr2, extra_actions = transform_bin_op(assignment) action_list.extend(extra_actions) if isinstance(expr1, expression.ConstantNumeric): param1 = expression.ConstantNumeric(0xFF) data = expr1 elif isinstance(expr1, expression.Parameter) and isinstance(expr1.num, expression.ConstantNumeric): param1 = expr1.num else: tmp_param, tmp_param_actions = get_tmp_parameter(expr1) action_list.extend(tmp_param_actions) param1 = expression.ConstantNumeric(tmp_param) # We can use the data only for one for the parameters. # If the first parameter uses "data" we need a temp parameter for this one if isinstance(expr2, expression.ConstantNumeric) and data is None: param2 = expression.ConstantNumeric(0xFF) data = expr2 elif isinstance(expr2, expression.Parameter) and isinstance(expr2.num, expression.ConstantNumeric): param2 = expr2.num else: tmp_param, tmp_param_actions = get_tmp_parameter(expr2) action_list.extend(tmp_param_actions) param2 = expression.ConstantNumeric(tmp_param) else: raise generic.ScriptError("Invalid expression in argument assignment", assignment.value.pos) if len(act6.modifications) > 0: action_list.append(act6) action_list.append(ActionD(target, param1, op, param2, data)) action6.free_parameters.restore() return action_list nml-0.4.4/nml/actions/action4.py0000644000567200056720000001750112643457545017633 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, grfstrings, generic from nml.actions import base_action, action6, actionD class Action4(base_action.BaseAction): """ Class representing a single action 4. Format: 04 @ivar feature: Feature of this action 4 @type feature: C{int} @ivar lang: Language ID of the text (set as ##grflangid in the .lng file, 0x7F for default) @type lang: C{int} @ivar size: Size of the id, may be 1 (byte), 2 (word) or 3 (ext. byte) @type size: C{int} @ivar id: ID of the first string to write @type id: C{int} @ivar texts: List of strings to write @type texts: C{list} of C{str} """ def __init__(self, feature, lang, size, id, texts): self.feature = feature self.lang = lang self.size = size self.id = id self.texts = texts def prepare_output(self, sprite_num): #To indicate a word value, bit 7 of the lang ID must be set if self.size == 2: self.lang = self.lang | 0x80 def write(self, file): size = 4 + self.size for text in self.texts: size += grfstrings.get_string_size(text) file.start_sprite(size) file.print_bytex(4) file.print_bytex(self.feature) file.print_bytex(self.lang) file.print_bytex(len(self.texts)) file.print_varx(self.id, self.size) for text in self.texts: file.print_string(text) file.newline() file.end_sprite() def skip_action9(self): return False # List of various string ranges that may be used # Attributes: # - random_id: If true, string IDs may be allocated randomly, else the ID has a special meaning and must be assigned (e.g. for vehicles, string ID = vehicle ID) # - ids: List of free IDs, only needed if random_id is true. Whenever an ID is used, it's removed from the list string_ranges = { 0xC4: {'random_id': False}, # Station class names 0xC5: {'random_id': False}, # Station names 0xC9: {'random_id': False}, # House name 0xD0: {'random_id': True, 'total': 0x400, 'ids': list(range(0x3FF, -1, -1))}, # Misc. text ids, used for callbacks and such 0xDC: {'random_id': True, 'total': 0x100, 'ids': list(range(0xFF, -1, -1))}, # Misc. persistent text ids, used to set properties } # Mapping of string identifiers to D0xx/DCxx text ids # This allows outputting strings only once, instead of everywhere they are used used_strings = { 0xD0: {}, 0xDC: {}, } def print_stats(): """ Print statistics about used ids. """ for t, l in string_ranges.items(): if l['random_id']: num_used = l['total'] - len(l['ids']) if num_used > 0: generic.print_info("{:02X}xx strings: {}/{}".format(t, num_used, l['total'])) def get_global_string_actions(): """ Get a list of global string actions i.e. print all D0xx / DCxx texts at once @return: A list of all D0xx / DCxx action4s @rtype: C{list} of L{BaseAction} """ texts = [] actions = [] for string_range, strings in list(used_strings.items()): for feature_name, id in list(strings.items()): feature, string_name = feature_name texts.append( (0x7F, (string_range << 8) | id, grfstrings.get_translation(string_name), feature) ) for lang_id in grfstrings.get_translations(string_name): texts.append( (lang_id, (string_range << 8) | id, grfstrings.get_translation(string_name, lang_id), feature) ) last_lang = -1 last_id = -1 last_feature = -1 # Sort to have a deterministic ordering and to have as much consecutive IDs as possible texts.sort(key=lambda text: (-1 if text[0] == 0x7F else text[0], text[1])) for text in texts: str_lang, str_id, str_text, feature = text # If possible, append strings to the last action 4 instead of creating a new one if str_lang != last_lang or str_id - 1 != last_id or feature != last_feature or len(actions[-1].texts) == 0xFF: actions.append(Action4(feature, str_lang, 2, str_id, [str_text])) else: actions[-1].texts.append(str_text) last_lang = str_lang last_id = str_id last_feature = feature return actions def get_string_action4s(feature, string_range, string, id = None): """ Let a string from the lang files be used in the rest of NML. This may involve adding actions directly, but otherwise an ID is allocated and the string will be written later @param feature: Feature that uses the string @type feature: C{int} @param string_range: String range to use, either a value from L{string_ranges} or C{None} if N/A (item names) @type string_range: C{int} or C{None} @param string: String to parse @type string: L{expression.String} @param id: ID to use for this string, or C{None} if it will be allocated dynamically (random_id is true for the string range) @type id: L{Expression} or C{None} @return: A tuple of two values: - ID of the string (useful if allocated dynamically) - Resulting action list to be appended @rtype: C{tuple} of (C{int}, C{list} of L{BaseAction}) """ grfstrings.validate_string(string) write_action4s = True action6.free_parameters.save() actions = [] mod = None if string_range is not None: size = 2 if string_ranges[string_range]['random_id']: # ID is allocated randomly, we will output the actions later write_action4s = False if (feature, string) in used_strings[string_range]: id_val = used_strings[string_range][(feature, string)] else: id_val = string_ranges[string_range]['ids'].pop() used_strings[string_range][(feature, string)] = id_val else: # ID must be supplied assert id is not None assert isinstance(id, expression.ConstantNumeric) id_val = id.value id_val = id_val | (string_range << 8) else: # Not a string range, so we must have an id assert id is not None size = 3 if feature <= 3 else 1 if isinstance(id, expression.ConstantNumeric): id_val = id.value else: id_val = 0 tmp_param, tmp_param_actions = actionD.get_tmp_parameter(id) actions.extend(tmp_param_actions) # Apply ID via action4 later mod = (tmp_param, 2 if feature <= 3 else 1, 5 if feature <= 3 else 4) if write_action4s: strings = [(lang_id, grfstrings.get_translation(string, lang_id)) for lang_id in grfstrings.get_translations(string)] # Sort the strings for deterministic ordering and prepend the default language strings = [(0x7F, grfstrings.get_translation(string))] + sorted(strings, key=lambda lang_text: lang_text[0]) for lang_id, text in strings: if mod is not None: act6 = action6.Action6() act6.modify_bytes(*mod) actions.append(act6) actions.append(Action4(feature, lang_id, size, id_val, [text])) action6.free_parameters.restore() return (id_val, actions) nml-0.4.4/nml/actions/action2random.py0000644000567200056720000003464212643457545021037 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml.actions import action2, action2var, action2real, action6 from nml import generic, expression, nmlop class Action2Random(action2.Action2): def __init__(self, feature, name, pos, type_byte, count, triggers, randbit, nrand): action2.Action2.__init__(self, feature, name, pos) self.type_byte = type_byte self.count = count self.triggers = triggers self.randbit = randbit self.nrand = nrand self.choices = [] def prepare_output(self, sprite_num): action2.Action2.prepare_output(self, sprite_num) for choice in self.choices: if isinstance(choice.result, expression.SpriteGroupRef): choice.result = choice.result.get_action2_id(self.feature) else: choice.result = choice.result.value | 0x8000 def write(self, file): # [] size = 4 + 2 * self.nrand + (self.count is not None) action2.Action2.write_sprite_start(self, file, size) file.print_bytex(self.type_byte) if self.count is not None: file.print_bytex(self.count) file.print_bytex(self.triggers) file.print_byte(self.randbit) file.print_bytex(self.nrand) file.newline() for choice in self.choices: for i in range(0, choice.prob): file.print_wordx(choice.result) file.comment(choice.comment) file.end_sprite() class RandomAction2Choice(object): def __init__(self, result, prob, comment): self.result = result self.prob = prob self.comment = comment vehicle_random_types = { 'SELF' : {'type': 0x80, 'param': 0, 'first_bit': 0, 'num_bits': 8, 'triggers': True}, 'PARENT' : {'type': 0x83, 'param': 0, 'first_bit': 0, 'num_bits': 8, 'triggers': False}, 'TILE' : {'type': 0x80, 'param': 0, 'first_bit': 0, 'num_bits': 8, 'triggers': False}, 'BACKWARD_SELF' : {'type': 0x84, 'param': 1, 'first_bit': 0, 'num_bits': 8, 'triggers': False, 'value': 0x00}, 'FORWARD_SELF' : {'type': 0x84, 'param': 1, 'first_bit': 0, 'num_bits': 8, 'triggers': False, 'value': 0x40}, 'BACKWARD_ENGINE' : {'type': 0x84, 'param': 1, 'first_bit': 0, 'num_bits': 8, 'triggers': False, 'value': 0x80}, 'BACKWARD_SAMEID' : {'type': 0x84, 'param': 1, 'first_bit': 0, 'num_bits': 8, 'triggers': False, 'value': 0xC0}, } random_types = { 0x00 : vehicle_random_types, 0x01 : vehicle_random_types, 0x02 : vehicle_random_types, 0x03 : vehicle_random_types, 0x04 : {'SELF': {'type': 0x80, 'param': 0, 'first_bit': 0, 'num_bits': 16, 'triggers': True}, 'TILE': {'type': 0x80, 'param': 0, 'first_bit': 16, 'num_bits': 4, 'triggers': True}}, 0x05 : {'SELF': {'type': 0x80, 'param': 0, 'first_bit': 0, 'num_bits': 8, 'triggers': False}}, 0x06 : {}, 0x07 : {'SELF': {'type': 0x80, 'param': 0, 'first_bit': 0, 'num_bits': 8, 'triggers': True}}, 0x08 : {}, 0x09 : {'SELF': {'type': 0x80, 'param': 0, 'first_bit': 0, 'num_bits': 8, 'triggers': True}, 'PARENT': {'type': 0x83, 'first_bit': 0, 'num_bits': 16, 'triggers': True}}, 0x0A : {'SELF': {'type': 0x80, 'param': 0, 'first_bit': 0, 'num_bits': 16, 'triggers': False}}, 0x0B : {}, 0x0C : {}, 0x0D : {}, 0x0E : {}, 0x0F : {'SELF': {'type': 0x80, 'param': 0, 'first_bit': 0, 'num_bits': 8, 'triggers': False}}, 0x10 : {'SELF': {'type': 0x80, 'param': 0, 'first_bit': 0, 'num_bits': 2, 'triggers': False}}, 0x11 : {'SELF': {'type': 0x80, 'param': 0, 'first_bit': 0, 'num_bits': 16, 'triggers': False}, 'TILE': {'type': 0x80, 'first_bit': 16, 'num_bits': 4, 'triggers': False}}, } def parse_randomswitch_type(random_switch): """ Parse the type of a random switch to determine the type and random bits to use. @param random_switch: Random switch to parse the type of @type random_switch: L{RandomSwitch} @return: A tuple containing the following: - The type byte of the resulting random action2. - The value to use as , None if N/A. - Expression to parse in a preceding switch-block, None if N/A. - The first random bit that should be used (often 0) - The number of random bits available @rtype: C{tuple} of (C{int}, C{int} or C{None}, L{Expression} or C{None}, C{int}, C{int}) """ # Extract some stuff we'll often need type_str = random_switch.type.value type_pos = random_switch.type.pos feature_val = next(iter(random_switch.feature_set)) # Validate type name / param combination if type_str not in random_types[feature_val]: raise generic.ScriptError("Invalid combination for random_switch feature {:d} and type '{}'. ".format(feature_val, type_str), type_pos) type_info = random_types[feature_val][type_str] count_expr = None if random_switch.type_count is None: # No param given if type_info['param'] == 1: raise generic.ScriptError("Value '{}' for random_switch parameter 2 'type' requires a parameter.".format(type_str), type_pos) count = None else: # Param given if type_info['param'] == 0: raise generic.ScriptError("Value '{}' for random_switch parameter 2 'type' should not have a parameter.".format(type_str), type_pos) if isinstance(random_switch.type_count, expression.ConstantNumeric) and 1 <= random_switch.type_count.value <= 15: count = random_switch.type_count.value else: count = 0 count_expr = expression.BinOp(nmlop.STO_TMP, random_switch.type_count, expression.ConstantNumeric(0x100), type_pos) count = type_info['value'] | count if random_switch.triggers.value != 0 and not type_info['triggers']: raise generic.ScriptError("Triggers may not be set for random_switch feature {:d} and type '{}'. ".format(feature_val, type_str), type_pos) # Determine type byte and random bits type_byte = type_info['type'] start_bit = type_info['first_bit'] bits_available = type_info['num_bits'] return type_byte, count, count_expr, start_bit, bits_available def lookup_random_action2(sg_ref): """ Lookup a sprite group reference to find the corresponding random action2 @param sg_ref: Reference to random action2 @type sg_ref: L{SpriteGroupRef} @return: Random action2 corresponding to this sprite group, or None if N/A @rtype: L{Action2Random} or C{None} """ spritegroup = action2.resolve_spritegroup(sg_ref.name) act2 = spritegroup.random_act2 assert act2 is None or isinstance(act2, Action2Random) return act2 def parse_randomswitch_dependencies(random_switch, start_bit, bits_available, nrand): """ Handle the dependencies between random chains to determine the random bits to use @param random_switch: Random switch to parse @type random_switch: L{RandomSwitch} @param start_bit: First available random bit @type start_bit: C{int} @param bits_available: Number of random bits available @type bits_available: C{int} @param nrand: Number of random choices to use @type nrand: C{int} @return: A tuple of two values: - The first random bit to use - The number of random choices to use. This may be higher the the original amount passed as paramter @rtype: C{tuple} of (C{int}, C{int}) """ #Dependent random chains act2_to_copy = None for dep in random_switch.dependent: act2 = lookup_random_action2(dep) if act2 is None: continue # May happen if said random switch is not used and therefore not parsed if act2_to_copy is not None: if act2_to_copy.randbit != act2.randbit: msg = "random_switch '{}' cannot be dependent on both '{}' and '{}' as these are independent of each other." msg = msg.format(random_switch.name.value, act2_to_copy.name, act2.name) raise generic.ScriptError(msg, random_switch.pos) if act2_to_copy.nrand != act2.nrand: msg = "random_switch '{}' cannot be dependent on both '{}' and '{}' as they don't use the same amount of random data." msg = msg.format(random_switch.name.value, act2_to_copy.name, act2.name) raise generic.ScriptError(msg, random_switch.pos) else: act2_to_copy = act2 if act2_to_copy is not None: randbit = act2_to_copy.randbit if nrand > act2_to_copy.nrand: msg = "random_switch '{}' cannot be dependent on '{}' as it requires more random data." msg = msg.format(random_switch.name.value, act2_to_copy.name) raise generic.ScriptError(msg, random_switch.pos) nrand = act2_to_copy.nrand else: randbit = -1 #INdependent random chains possible_mask = ((1 << bits_available) - 1) << start_bit for indep in random_switch.independent: act2 = lookup_random_action2(indep) if act2 is None: continue # May happen if said random switch is not used and therefore not parsed possible_mask &= ~((act2.nrand - 1) << act2.randbit) required_mask = nrand - 1 if randbit != -1: #randbit has already been determined. Check that it is suitable if possible_mask & (required_mask << randbit) != (required_mask << randbit): msg = "Combination of dependence on and independence from random_switches is not possible for random_switch '{}'." msg = msg.format(random_switch.name.value) raise generic.ScriptError(msg, random_switch.pos) else: #find a suitable randbit for i in range(start_bit, bits_available + start_bit): if possible_mask & (required_mask << i) == (required_mask << i): randbit = i break else: msg = "Independence of all given random_switches is not possible for random_switch '{}'." msg = msg.format(random_switch.name.value) raise generic.ScriptError(msg, random_switch.pos) return randbit, nrand def parse_randomswitch(random_switch): """ Parse a randomswitch block into actions @param random_switch: RandomSwitch block to parse @type random_switch: L{RandomSwitch} @return: List of actions @rtype: C{list} of L{BaseAction} """ action_list = action2real.create_spriteset_actions(random_switch) feature = next(iter(random_switch.feature_set)) type_byte, count, count_expr, start_bit, bits_available = parse_randomswitch_type(random_switch) total_prob = sum([choice.probability.value for choice in random_switch.choices]) assert total_prob > 0 nrand = 1 while nrand < total_prob: nrand <<= 1 # Verify that enough random data is available if min(1 << bits_available, 0x80) < nrand: msg = "The maximum sum of all random_switch probabilities is {:d}, encountered {:d}." msg = msg.format(min(1 << bits_available, 0x80), total_prob) raise generic.ScriptError(msg, random_switch.pos) randbit, nrand = parse_randomswitch_dependencies(random_switch, start_bit, bits_available, nrand) random_action2 = Action2Random(feature, random_switch.name.value, random_switch.pos, type_byte, count, random_switch.triggers.value, randbit, nrand) random_switch.random_act2 = random_action2 action6.free_parameters.save() act6 = action6.Action6() offset = 8 if count is not None else 7 #divide the 'extra' probabilities in an even manner i = 0 resulting_prob = dict((c, c.probability.value) for c in random_switch.choices) while i < (nrand - total_prob): best_choice = None best_ratio = 0 for choice in random_switch.choices: #float division, so 9 / 10 = 0.9 ratio = choice.probability.value / float(resulting_prob[choice] + 1) if ratio > best_ratio: best_ratio = ratio best_choice = choice assert best_choice is not None resulting_prob[best_choice] += 1 i += 1 for choice in random_switch.choices: res_prob = resulting_prob[choice] result, comment = action2var.parse_result(choice.result.value, action_list, act6, offset, random_action2, None, 0x89, res_prob) offset += res_prob * 2 comment = "({:d}/{:d}) -> ({:d}/{:d}): ".format(choice.probability.value, total_prob, res_prob, nrand) + comment random_action2.choices.append(RandomAction2Choice(result, res_prob, comment)) if len(act6.modifications) > 0: action_list.append(act6) action_list.append(random_action2) if count_expr is None: random_switch.set_action2(random_action2, feature) else: # Create intermediate varaction2 varaction2 = action2var.Action2Var(feature, '{}@registers'.format(random_switch.name.value), random_switch.pos, 0x89) varact2parser = action2var.Varaction2Parser(feature) varact2parser.parse_expr(count_expr) varaction2.var_list = varact2parser.var_list action_list.extend(varact2parser.extra_actions) extra_act6 = action6.Action6() for mod in varact2parser.mods: extra_act6.modify_bytes(mod.param, mod.size, mod.offset + 4) if len(extra_act6.modifications) > 0: action_list.append(extra_act6) ref = expression.SpriteGroupRef(random_switch.name, [], None, random_action2) varaction2.ranges.append(action2var.VarAction2Range(expression.ConstantNumeric(0), expression.ConstantNumeric(0), ref, '')) varaction2.default_result = ref varaction2.default_comment = '' # Add two references (default + range) action2.add_ref(ref, varaction2) action2.add_ref(ref, varaction2) random_switch.set_action2(varaction2, feature) action_list.append(varaction2) action6.free_parameters.restore() return action_list nml-0.4.4/nml/actions/actionA.py0000644000567200056720000000547612643457545017660 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, nmlop from nml.actions import base_action, real_sprite, actionD, action6 class ActionA(base_action.BaseAction): """ Action class for Action A (sprite replacement) @ivar sets: List of sprite collections to be replaced. @type sets: C{list} of (C{int}, C{int})-tuples """ def __init__(self, sets): self.sets = sets def write(self, file): # * 0A [ ]+ size = 2 + 3 * len(self.sets) file.start_sprite(size) file.print_bytex(0x0A) file.print_byte(len(self.sets)) for num, first in self.sets: file.print_byte(num) file.print_word(first) file.newline() file.end_sprite() def parse_actionA(replaces): """ Parse replace-block to ActionA. @param replaces: Replace-block to parse. @type replaces: L{ReplaceSprite} """ action_list = [] action6.free_parameters.save() act6 = action6.Action6() real_sprite_list = real_sprite.parse_sprite_data(replaces) block_list = [] total_sprites = len(real_sprite_list) offset = 2 # Skip 0A and sprite_offset = 0 # Number of sprites already covered by previous [ ]-pairs while total_sprites > 0: this_block = min(total_sprites, 255) # number of sprites in this block total_sprites -= this_block offset += 1 # Skip first_sprite = replaces.start_id # number of first sprite if sprite_offset != 0: first_sprite = expression.BinOp(nmlop.ADD, first_sprite, expression.ConstantNumeric(sprite_offset, first_sprite.pos), first_sprite.pos).reduce() first_sprite, offset = actionD.write_action_value(first_sprite, action_list, act6, offset, 2) block_list.append( (this_block, first_sprite.value) ) sprite_offset += this_block # increase first-sprite for next block if len(act6.modifications) > 0: action_list.append(act6) action6.free_parameters.restore() action_list.append(ActionA(block_list)) action_list.extend(real_sprite_list) return action_list nml-0.4.4/nml/actions/__init__.py0000644000567200056720000000124312643457545020025 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" nml-0.4.4/nml/actions/action2var.py0000644000567200056720000012627012643457545020346 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml.actions import action2, action2real, action2var_variables, action4, action6, actionD from nml import expression, generic, global_constants, nmlop from nml.ast import general class Action2Var(action2.Action2): """ Variational Action2. This is the NFO equivalent of a switch-block in NML. It computes a single integer from one or more variables and picks it's return value based on the result of the computation. The return value can be either a 15bit integer or a reference to another action2. @ivar type_byte: The size (byte, word, double word) and access type (own object or related object). 0x89 (own object, double word) and 0x8A (related object, double word) and the only supported values. @type type_byte: C{int} @ivar ranges: List of return value ranges. Each range contains a minimum and a maximum value and a return value. The list is checked in order, if the result of the computation is between the minimum and maximum (inclusive) of one range the result of that range is returned. The result can be either an integer of another action2. @ivar ranges: C{list} of L{VarAction2Range} """ def __init__(self, feature, name, pos, type_byte): action2.Action2.__init__(self, feature, name, pos) self.type_byte = type_byte self.ranges = [] def resolve_tmp_storage(self): for var in self.var_list: if isinstance(var, VarAction2StoreTempVar): 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) var.set_register(location) def prepare_output(self, sprite_num): action2.Action2.prepare_output(self, sprite_num) for i in range(0, len(self.var_list) - 1, 2): self.var_list[i].shift |= 0x20 for i in range(0, len(self.var_list), 2): if isinstance(self.var_list[i], VarAction2ProcCallVar): self.var_list[i].resolve_parameter(self.feature) for r in self.ranges: if isinstance(r.result, expression.SpriteGroupRef): r.result = r.result.get_action2_id(self.feature) else: r.result = r.result.value | 0x8000 if isinstance(self.default_result, expression.SpriteGroupRef): self.default_result = self.default_result.get_action2_id(self.feature) else: self.default_result = self.default_result.value | 0x8000 def write(self, file): #type_byte, num_ranges, default_result = 4 #2 bytes for the result, 8 bytes for the min/max range. size = 4 + (2 + 8) * len(self.ranges) for var in self.var_list: if isinstance(var, nmlop.Operator): size += 1 else: size += var.get_size() self.write_sprite_start(file, size) file.print_bytex(self.type_byte) file.newline() for var in self.var_list: if isinstance(var, nmlop.Operator): file.print_bytex(var.act2_num, var.act2_str) else: var.write(file, 4) file.newline(var.comment) file.print_byte(len(self.ranges)) file.newline() for r in self.ranges: file.print_wordx(r.result) file.print_varx(r.min.value, 4) file.print_varx(r.max.value, 4) file.newline(r.comment) file.print_wordx(self.default_result) file.comment(self.default_comment) file.end_sprite() class VarAction2Var(object): """ Represents a variable for use in a (advanced) variational action2. @ivar var_num: Number of the variable to use. @type var_num: C{int} @ivar shift: The number of bits to shift the value of the given variable to the right. @type shift: C{int} @ivar mask: Bitmask to use on the value after shifting it. @type mask: C{int} @ivar parameter: Parameter to be used as argument for the variable. @type parameter: C{int} or C{None} @precondition: (0x60 <= var_num <= 0x7F) == (parameter is not None) @ivar add: If not C{None}, add this value to the result. @type add: C{int} or C{None} @ivar div: If not C{None}, divide the result by this. @type add: C{int} or C{None} @ivar mod: If not C{None}, compute (result module mod). @type add: C{int} or C{None} @ivar comment: Textual description of this variable. @type comment: C{basestr} """ def __init__(self, var_num, shift, mask, parameter = None): self.var_num = var_num self.shift = shift self.mask = mask self.parameter = parameter self.add = None self.div = None self.mod = None self.comment = "" def write(self, file, size): file.print_bytex(self.var_num) if self.parameter is not None: file.print_bytex(self.parameter) if self.mod is not None: self.shift |= 0x80 elif self.add is not None or self.div is not None: self.shift |= 0x40 file.print_bytex(self.shift) file.print_varx(self.mask, size) if self.add is not None: file.print_varx(self.add, size) if self.div is not None: file.print_varx(self.div, size) elif self.mod is not None: file.print_varx(self.mod, size) else: #no div or add, just divide by 1 file.print_varx(1, size) def get_size(self): #var number (1) [+ parameter (1)] + shift num (1) + and mask (4) [+ add (4) + div/mod (4)] size = 6 if self.parameter is not None: size += 1 if self.add is not None or self.div is not None or self.mod is not None: size += 8 return size def supported_by_actionD(self, raise_error): assert not raise_error return False # Class for var 7E procedure calls class VarAction2ProcCallVar(VarAction2Var): def __init__(self, sg_ref): VarAction2Var.__init__(self, 0x7E, 0, 0) # Reference to the called action2 self.sg_ref = sg_ref def resolve_parameter(self, feature): self.parameter = self.sg_ref.get_action2_id(feature) def get_size(self): return 7 def write(self, file, size): self.mask = get_mask(size) VarAction2Var.write(self, file, size) # General load and store class for temp parameters # Register is allocated at the store operation class VarAction2StoreTempVar(VarAction2Var): def __init__(self): VarAction2Var.__init__(self, 0x1A, 0, 0) #mask holds the number, it's resolved in Action2Var.resolve_tmp_storage self.load_vars = [] def set_register(self, register): self.mask = register for load_var in self.load_vars: load_var.parameter = register def get_size(self): return 6 def get_mask(size): if size == 1: return 0xFF elif size == 2: return 0xFFFF return 0xFFFFFFFF class VarAction2LoadTempVar(VarAction2Var, expression.Expression): def __init__(self, tmp_var): VarAction2Var.__init__(self, 0x7D, 0, 0) expression.Expression.__init__(self, None) assert isinstance(tmp_var, VarAction2StoreTempVar) tmp_var.load_vars.append(self) def write(self, file, size): self.mask = get_mask(size) VarAction2Var.write(self, file, size) def get_size(self): return 7 def reduce(self, id_dicts = [], unknown_id_fatal = True): return self def supported_by_action2(self, raise_error): return True def supported_by_actionD(self, raise_error): assert not raise_error return False # Temporary load and store classes used for spritelayout parameters # Register is allocated in a separate entity class VarAction2LayoutParam(object): def __init__(self): self.register = None self.store_vars = [] self.load_vars = [] def set_register(self, register): self.register = register for store_var in self.store_vars: store_var.mask = register for load_var in self.load_vars: load_var.parameter = register class VarAction2LoadLayoutParam(VarAction2Var, expression.Expression): def __init__(self, param): VarAction2Var.__init__(self, 0x7D, 0, 0) expression.Expression.__init__(self, None) assert isinstance(param, VarAction2LayoutParam) param.load_vars.append(self) # Register is stored in parameter def write(self, file, size): self.mask = get_mask(size) VarAction2Var.write(self, file, size) def get_size(self): return 7 def reduce(self, id_dicts = [], unknown_id_fatal = True): return self def supported_by_action2(self, raise_error): return True def supported_by_actionD(self, raise_error): assert not raise_error return False class VarAction2StoreLayoutParam(VarAction2Var): def __init__(self, param): VarAction2Var.__init__(self, 0x1A, 0, 0) assert isinstance(param, VarAction2LayoutParam) param.store_vars.append(self) # Register is stored in mask def get_size(self): return 6 class VarAction2Range(object): def __init__(self, min, max, result, comment): self.min = min self.max = max self.result = result self.comment = comment class Modification(object): def __init__(self, param, size, offset): self.param = param self.size = size self.offset = offset class Varaction2Parser(object): def __init__(self, feature): self.feature = feature # Depends on feature and var_range self.extra_actions = [] self.mods = [] self.var_list = [] self.var_list_size = 0 self.proc_call_list = [] def preprocess_binop(self, expr): """ Several nml operators are not directly support by nfo so we have to work around that by implementing those operators in terms of others. @return: A pre-processed version of the expression. @rtype: L{Expression} """ assert isinstance(expr, expression.BinOp) if expr.op == nmlop.CMP_LT: #return value is 0, 1 or 2, we want to map 0 to 1 and the others to 0 expr = expression.BinOp(nmlop.VACT2_CMP, expr.expr1, expr.expr2) #reduce the problem to 0/1 expr = expression.BinOp(nmlop.MIN, expr, expression.ConstantNumeric(1)) #and invert the result expr = expression.BinOp(nmlop.XOR, expr, expression.ConstantNumeric(1)) elif expr.op == nmlop.CMP_GT: #return value is 0, 1 or 2, we want to map 2 to 1 and the others to 0 expr = expression.BinOp(nmlop.VACT2_CMP, expr.expr1, expr.expr2) #subtract one expr = expression.BinOp(nmlop.SUB, expr, expression.ConstantNumeric(1)) #map -1 and 0 to 0 expr = expression.BinOp(nmlop.MAX, expr, expression.ConstantNumeric(0)) elif expr.op == nmlop.CMP_LE: #return value is 0, 1 or 2, we want to map 2 to 0 and the others to 1 expr = expression.BinOp(nmlop.VACT2_CMP, expr.expr1, expr.expr2) #swap 0 and 2 expr = expression.BinOp(nmlop.XOR, expr, expression.ConstantNumeric(2)) #map 1/2 to 1 expr = expression.BinOp(nmlop.MIN, expr, expression.ConstantNumeric(1)) elif expr.op == nmlop.CMP_GE: #return value is 0, 1 or 2, we want to map 1/2 to 1 expr = expression.BinOp(nmlop.VACT2_CMP, expr.expr1, expr.expr2) expr = expression.BinOp(nmlop.MIN, expr, expression.ConstantNumeric(1)) elif expr.op == nmlop.CMP_EQ: #return value is 0, 1 or 2, we want to map 1 to 1, other to 0 expr = expression.BinOp(nmlop.VACT2_CMP, expr.expr1, expr.expr2) expr = expression.BinOp(nmlop.AND, expr, expression.ConstantNumeric(1)) elif expr.op == nmlop.CMP_NEQ: #same as CMP_EQ but invert the result expr = expression.BinOp(nmlop.VACT2_CMP, expr.expr1, expr.expr2) expr = expression.BinOp(nmlop.AND, expr, expression.ConstantNumeric(1)) expr = expression.BinOp(nmlop.XOR, expr, expression.ConstantNumeric(1)) elif expr.op == nmlop.HASBIT: # hasbit(x, n) ==> (x >> n) & 1 expr = expression.BinOp(nmlop.SHIFTU_RIGHT, expr.expr1, expr.expr2) expr = expression.BinOp(nmlop.AND, expr, expression.ConstantNumeric(1)) elif expr.op == nmlop.NOTHASBIT: # !hasbit(x, n) ==> ((x >> n) & 1) ^ 1 expr = expression.BinOp(nmlop.SHIFTU_RIGHT, expr.expr1, expr.expr2) expr = expression.BinOp(nmlop.AND, expr, expression.ConstantNumeric(1)) expr = expression.BinOp(nmlop.XOR, expr, expression.ConstantNumeric(1)) return expr.reduce() 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: 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 = expression.BinOp(nmlop.MUL, expr.expr1, VarAction2LoadTempVar(guard_var)) expr2 = expression.BinOp(nmlop.MUL, expr.expr2, VarAction2LoadTempVar(inverted_guard_var)) return expression.BinOp(nmlop.ADD, expr1, expr2) def preprocess_storageop(self, expr): assert isinstance(expr, expression.StorageOp) max = 0xF if expr.info['perm'] else 0x10F if isinstance(expr.register, expression.ConstantNumeric) and expr.register.value > max: raise generic.ScriptError("Register number must be in range 0..{:d}, encountered {:d}.".format(max, expr.register.value), expr.pos) if expr.info['perm'] and self.feature not in (0x08, 0x0A, 0x0D): raise generic.ScriptError("Persistent storage is not supported for feature '{}'".format(general.feature_name(self.feature)), expr.pos) if expr.info['store']: op = nmlop.STO_PERM if expr.info['perm'] else nmlop.STO_TMP ret = expression.BinOp(op, expr.value, expr.register, expr.pos) else: var_num = 0x7C if expr.info['perm'] else 0x7D ret = expression.Variable(expression.ConstantNumeric(var_num), param=expr.register, pos=expr.pos) if expr.info['perm'] and self.feature == 0x08: # store grfid in register 0x100 for town persistent storage grfid = expression.ConstantNumeric(0xFFFFFFFF if expr.grfid is None else expression.parse_string_to_dword(expr.grfid)) store_op = expression.BinOp(nmlop.STO_TMP, grfid, expression.ConstantNumeric(0x100), expr.pos) ret = expression.BinOp(nmlop.VAL2, store_op, ret, expr.pos) elif expr.grfid is not None: raise generic.ScriptError("Specifying a grfid is only possible for town persistent storage.", expr.pos) return ret def parse_expr_to_constant(self, expr, offset): if isinstance(expr, expression.ConstantNumeric): return expr.value tmp_param, tmp_param_actions = actionD.get_tmp_parameter(expr) self.extra_actions.extend(tmp_param_actions) self.mods.append(Modification(tmp_param, 4, self.var_list_size + offset)) return 0 def parse_variable(self, expr): """ Parse a variable in an expression. @param expr: @type expr: L{expression.Variable} """ if not isinstance(expr.num, expression.ConstantNumeric): raise generic.ScriptError("Variable number must be a constant number", expr.num.pos) if not (expr.param is None or isinstance(expr.param, expression.ConstantNumeric)): raise generic.ScriptError("Variable parameter must be a constant number", expr.param.pos) if len(expr.extra_params) > 0: first_var = len(self.var_list) == 0 backup_op = None value_backup = None if not first_var: backup_op = self.var_list.pop() value_backup = VarAction2StoreTempVar() self.var_list.append(nmlop.STO_TMP) self.var_list.append(value_backup) self.var_list.append(nmlop.VAL2) self.var_list_size += value_backup.get_size() + 1 #Last value == 0, and this is right before we're going to use #the extra parameters. Set them to their correct value here. for extra_param in expr.extra_params: self.parse(extra_param[1]) self.var_list.append(nmlop.STO_TMP) var = VarAction2Var(0x1A, 0, extra_param[0]) self.var_list.append(var) self.var_list.append(nmlop.VAL2) self.var_list_size += var.get_size() + 2 if not first_var: value_loadback = VarAction2LoadTempVar(value_backup) self.var_list.append(value_loadback) self.var_list.append(backup_op) self.var_list_size += value_loadback.get_size() + 1 if expr.param is None: offset = 2 param = None else: offset = 3 param = expr.param.value mask = self.parse_expr_to_constant(expr.mask, offset) var = VarAction2Var(expr.num.value, expr.shift.value, mask, param) if expr.add is not None: var.add = self.parse_expr_to_constant(expr.add, offset + 4) if expr.div is not None: var.div = self.parse_expr_to_constant(expr.div, offset + 8) if expr.mod is not None: var.mod = self.parse_expr_to_constant(expr.mod, offset + 8) self.var_list.append(var) self.var_list_size += var.get_size() def parse_not(self, expr): self.parse_binop(expression.BinOp(nmlop.XOR, expr.expr, expression.ConstantNumeric(1))) def parse_binop(self, expr): if expr.op.act2_num is None: expr.supported_by_action2(True) if isinstance(expr.expr2, (expression.ConstantNumeric, expression.Variable)) or \ isinstance(expr.expr2, (VarAction2LoadTempVar, VarAction2LoadLayoutParam)) or \ (isinstance(expr.expr2, expression.Parameter) and isinstance(expr.expr2.num, expression.ConstantNumeric)) or \ expr.op == nmlop.VAL2: expr2 = expr.expr2 elif expr.expr2.supported_by_actionD(False): tmp_param, tmp_param_actions = actionD.get_tmp_parameter(expr.expr2) self.extra_actions.extend(tmp_param_actions) expr2 = expression.Parameter(expression.ConstantNumeric(tmp_param)) else: #The expression is so complex we need to compute it first, store the #result and load it back later. self.parse(expr.expr2) tmp_var = VarAction2StoreTempVar() self.var_list.append(nmlop.STO_TMP) self.var_list.append(tmp_var) self.var_list.append(nmlop.VAL2) #the +2 is for both operators self.var_list_size += tmp_var.get_size() + 2 expr2 = VarAction2LoadTempVar(tmp_var) #parse expr1 self.parse(expr.expr1) self.var_list.append(expr.op) self.var_list_size += 1 self.parse(expr2) def parse_constant(self, expr): var = VarAction2Var(0x1A, 0, expr.value) self.var_list.append(var) self.var_list_size += var.get_size() def parse_param(self, expr): self.mods.append(Modification(expr.num.value, 4, self.var_list_size + 2)) var = VarAction2Var(0x1A, 0, 0) var.comment = str(expr) self.var_list.append(var) self.var_list_size += var.get_size() def parse_string(self, expr): str_id, actions = action4.get_string_action4s(0, 0xD0, expr) self.extra_actions.extend(actions) self.parse_constant(expression.ConstantNumeric(str_id)) def parse_via_actionD(self, expr): tmp_param, tmp_param_actions = actionD.get_tmp_parameter(expr) self.extra_actions.extend(tmp_param_actions) num = expression.ConstantNumeric(tmp_param) self.parse(expression.Parameter(num)) def parse_proc_call(self, expr): assert isinstance(expr, expression.SpriteGroupRef) var_access = VarAction2ProcCallVar(expr) self.var_list.append(var_access) self.var_list_size += var_access.get_size() self.proc_call_list.append(expr) def parse_expr(self, expr): if isinstance(expr, expression.Array): for expr2 in expr.values: self.parse(expr2) self.var_list.append(nmlop.VAL2) self.var_list_size += 1 # Drop the trailing VAL2 again self.var_list.pop() self.var_list_size -= 1 else: self.parse(expr) def parse(self, expr): #Preprocess the expression if isinstance(expr, expression.SpecialParameter): #do this first, since it may evaluate to a BinOp expr = expr.to_reading() if isinstance(expr, expression.BinOp): expr = self.preprocess_binop(expr) elif isinstance(expr, expression.Boolean): expr = expression.BinOp(nmlop.MINU, expr.expr, expression.ConstantNumeric(1)) elif isinstance(expr, expression.BinNot): expr = expression.BinOp(nmlop.XOR, expr.expr, expression.ConstantNumeric(0xFFFFFFFF)) elif isinstance(expr, expression.TernaryOp) and not expr.supported_by_actionD(False): expr = self.preprocess_ternaryop(expr) elif isinstance(expr, expression.StorageOp): expr = self.preprocess_storageop(expr) #Try to parse the expression to a list of variables+operators if isinstance(expr, expression.ConstantNumeric): self.parse_constant(expr) elif isinstance(expr, expression.Parameter) and isinstance(expr.num, expression.ConstantNumeric): self.parse_param(expr) elif isinstance(expr, expression.Variable): self.parse_variable(expr) elif expr.supported_by_actionD(False): self.parse_via_actionD(expr) elif isinstance(expr, expression.BinOp): self.parse_binop(expr) elif isinstance(expr, expression.Not): self.parse_not(expr) elif isinstance(expr, expression.String): self.parse_string(expr) elif isinstance(expr, (VarAction2LoadTempVar, VarAction2LoadLayoutParam)): self.var_list.append(expr) self.var_list_size += expr.get_size() else: expr.supported_by_action2(True) assert False #supported_by_action2 should have raised the correct error already def parse_var(info, 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_60x_var(name, args, pos, info): if 'param_function' in info: # Special function to extract parameters if there is more than one param, extra_params = info['param_function'](name, args, pos, info) else: # Default function to extract parameters param, extra_params = action2var_variables.default_60xvar(name, args, pos, info) if isinstance(param, expression.ConstantNumeric) and (0 <= param.value <= 255): res = expression.Variable(expression.ConstantNumeric(info['var']), expression.ConstantNumeric(info['start']), \ expression.ConstantNumeric((1 << info['size']) - 1), param, pos) res.extra_params.extend(extra_params) else: # Make use of var 7B to pass non-constant parameters var = expression.Variable(expression.ConstantNumeric(0x7B), expression.ConstantNumeric(info['start']), \ expression.ConstantNumeric((1 << info['size']) - 1), expression.ConstantNumeric(info['var']), pos) var.extra_params.extend(extra_params) # Set the param in the accumulator beforehand res = expression.BinOp(nmlop.VAL2, param, var, pos) if 'value_function' in info: res = info['value_function'](res, info) return res def parse_minmax(value, unit_str, action_list, act6, offset): """ Parse a min or max value in a switch block. @param value: Value to parse @type value: L{Expression} @param unit_str: Unit to use @type unit_str: C{str} or C{None} @param action_list: List to append any extra actions to @type action_list: C{list} of L{BaseAction} @param act6: Action6 to add any modifications to @type act6: L{Action6} @param offset: Current offset to use for action6 @type offset: C{int} @return: A tuple of two values: - The value to use as min/max - Whether the resulting range may need a sanity check @rtype: C{tuple} of (L{ConstantNumeric} or L{SpriteGroupRef}), C{bool} """ if unit_str is not None: raise generic.ScriptError("Using a unit is in switch-ranges is not (temporarily) not supported", value.pos) result, offset = actionD.write_action_value(value, action_list, act6, offset, 4) check_range = isinstance(value, expression.ConstantNumeric) return (result, offset, check_range) return_action_id = 0 def create_return_action(expr, feature, name, var_range): """ Create a varaction2 to return the computed value @param expr: Expression to return @type expr: L{Expression} @param feature: Feature of the switch-block @type feature: C{int} @param name: Name of the new varaction2 @type name: C{str} @return: A tuple of two values: - Action list to prepend - Reference to the created varaction2 @rtype: C{tuple} of (C{list} of L{BaseAction}, L{SpriteGroupRef}) """ varact2parser = Varaction2Parser(feature if var_range == 0x89 else action2var_variables.varact2parent_scope[feature]) varact2parser.parse_expr(expr) action_list = varact2parser.extra_actions extra_act6 = action6.Action6() for mod in varact2parser.mods: extra_act6.modify_bytes(mod.param, mod.size, mod.offset + 4) if len(extra_act6.modifications) > 0: action_list.append(extra_act6) varaction2 = Action2Var(feature, name, expr.pos, var_range) varaction2.var_list = varact2parser.var_list varaction2.default_result = expression.ConstantNumeric(0) # Bogus result, it's the nvar == 0 that matters varaction2.default_comment = 'Return computed value' ref = expression.SpriteGroupRef(expression.Identifier(name), [], None, varaction2) action_list.append(varaction2) return (action_list, ref) failed_cb_results = {} def get_failed_cb_result(feature, action_list, parent_action, pos): """ Get a sprite group reference to use for a failed callback The actions needed are created on first use, then cached in L{failed_cb_results} @param feature: Feature to use @type feature: C{int} @param action_list: Action list to append any extra actions to @type action_list: C{list} of L{BaseAction} @param parent_action: Reference to the action of which this is a result @type parent_action: L{BaseAction} @param pos: Positional context. @type pos: L{Position} @return: Sprite group reference to use @rtype: L{SpriteGroupRef} """ if feature in failed_cb_results: varaction2 = failed_cb_results[feature] else: # Create action2 (+ action1, if needed) # Import here to avoid circular imports from nml.actions import action1, action2layout, action2production, action2real if feature == 0x0A: # Industries -> production action2 act2 = action2production.make_empty_production_action2(pos) elif feature in (0x07, 0x09, 0x0F, 0x11): # Tile layout action2 act2 = action2layout.make_empty_layout_action2(feature, pos) else: # Normal action2 act1_actions, act1_index = action1.make_cb_failure_action1(feature) action_list.extend(act1_actions) act2 = action2real.make_simple_real_action2(feature, "@CB_FAILED_REAL{:02X}".format(feature), pos, act1_index) action_list.append(act2) # Create varaction2, to choose between returning graphics and 0, depending on CB varact2parser = Varaction2Parser(feature) varact2parser.parse_expr(expression.Variable(expression.ConstantNumeric(0x0C), mask=expression.ConstantNumeric(0xFFFF))) varaction2 = Action2Var(feature, "@CB_FAILED{:02X}".format(feature), pos, 0x89) varaction2.var_list = varact2parser.var_list varaction2.ranges.append(VarAction2Range(expression.ConstantNumeric(0), expression.ConstantNumeric(0), expression.ConstantNumeric(0), "graphics callback -> return 0")) varaction2.default_result = expression.SpriteGroupRef(expression.Identifier(act2.name), [], None, act2) varaction2.default_comment = "Non-graphics callback, return graphics result" action2.add_ref(varaction2.default_result, varaction2) action_list.append(varaction2) failed_cb_results[feature] = varaction2 ref = expression.SpriteGroupRef(expression.Identifier(varaction2.name), [], None, varaction2) action2.add_ref(ref, parent_action) return ref def parse_sg_ref_result(result, action_list, parent_action, var_range): """ Parse a result that is a sprite group reference. @param result: Result to parse @type result: L{SpriteGroupRef} @param action_list: List to append any extra actions to @type action_list: C{list} of L{BaseAction} @param parent_action: Reference to the action of which this is a result @type parent_action: L{BaseAction} @param var_range: Variable range to use for variables in the expression @type var_range: C{int} @return: Result to use in the calling varaction2 @rtype: L{SpriteGroupRef} """ if result.name.value == "CB_FAILED": return get_failed_cb_result(parent_action.feature, action_list, parent_action, result.pos) if len(result.param_list) == 0: action2.add_ref(result, parent_action) return result # Result is parametrized # Insert an intermediate varaction2 to store expressions in registers var_feature = parent_action.feature if var_range == 0x89 else action2var_variables.varact2parent_scope[parent_action.feature] varact2parser = Varaction2Parser(var_feature) layout = action2.resolve_spritegroup(result.name) for i, param in enumerate(result.param_list): if i > 0: varact2parser.var_list.append(nmlop.VAL2) varact2parser.var_list_size += 1 varact2parser.parse_expr(reduce_varaction2_expr(param, var_feature)) varact2parser.var_list.append(nmlop.STO_TMP) store_tmp = VarAction2StoreLayoutParam(layout.register_map[parent_action.feature][i]) varact2parser.var_list.append(store_tmp) varact2parser.var_list_size += store_tmp.get_size() + 1 # Add 1 for operator action_list.extend(varact2parser.extra_actions) extra_act6 = action6.Action6() for mod in varact2parser.mods: extra_act6.modify_bytes(mod.param, mod.size, mod.offset + 4) if len(extra_act6.modifications) > 0: action_list.append(extra_act6) global return_action_id name = "@return_action_{:d}".format(return_action_id) varaction2 = Action2Var(parent_action.feature, name, result.pos, var_range) return_action_id += 1 varaction2.var_list = varact2parser.var_list ref = expression.SpriteGroupRef(result.name, [], result.pos) varaction2.ranges.append(VarAction2Range(expression.ConstantNumeric(0), expression.ConstantNumeric(0), ref, result.name.value)) varaction2.default_result = ref varaction2.default_comment = result.name.value # Add the references as procs, to make sure, that any intermediate registers # are freed at the spritelayout and thus not selected to pass parameters # Reference is used twice (range + default) so call add_ref twice action2.add_ref(ref, varaction2, True) action2.add_ref(ref, varaction2, True) ref = expression.SpriteGroupRef(expression.Identifier(name), [], None, varaction2) action_list.append(varaction2) action2.add_ref(ref, parent_action) return ref def parse_result(value, action_list, act6, offset, parent_action, none_result, var_range, repeat_result = 1): """ Parse a result (another switch or CB result) in a switch block. @param value: Value to parse @type value: L{Expression} @param action_list: List to append any extra actions to @type action_list: C{list} of L{BaseAction} @param act6: Action6 to add any modifications to @type act6: L{Action6} @param offset: Current offset to use for action6 @type offset: C{int} @param parent_action: Reference to the action of which this is a result @type parent_action: L{BaseAction} @param none_result: Result to use to return the computed value @type none_result: L{Expression} @param var_range: Variable range to use for variables in the expression @type var_range: C{int} @param repeat_result: Repeat any action6 modifying of the next sprite this many times. @type repeat_result: C{int} @return: A tuple of two values: - The value to use as return value - Comment to add to this value @rtype: C{tuple} of (L{ConstantNumeric} or L{SpriteGroupRef}), C{str} """ if value is None: comment = "return;" assert none_result is not None if isinstance(none_result, expression.SpriteGroupRef): result = parse_sg_ref_result(none_result, action_list, parent_action, var_range) else: result = none_result elif isinstance(value, expression.SpriteGroupRef): result = parse_sg_ref_result(value, action_list, parent_action, var_range) comment = result.name.value + ';' elif isinstance(value, expression.ConstantNumeric): comment = "return {:d};".format(value.value) result = value if not(-16384 <= value.value <= 32767): msg = "Callback results are limited to -16384..16383 (when the result is a signed number) or 0..32767 (unsigned), encountered {:d}." msg = msg.format(value.value) raise generic.ScriptError(msg, value.pos) elif isinstance(value, expression.String): comment = "return {};".format(str(value)) str_id, actions = action4.get_string_action4s(0, 0xD0, value) action_list.extend(actions) result = expression.ConstantNumeric(str_id - 0xD000 + 0x8000) elif value.supported_by_actionD(False): tmp_param, tmp_param_actions = actionD.get_tmp_parameter(expression.BinOp(nmlop.OR, value, expression.ConstantNumeric(0x8000)).reduce()) comment = "return param[{:d}];".format(tmp_param) action_list.extend(tmp_param_actions) for i in range(repeat_result): act6.modify_bytes(tmp_param, 2, offset + 2*i) result = expression.ConstantNumeric(0) else: global return_action_id extra_actions, result = create_return_action(value, parent_action.feature, "@return_action_{:d}".format(return_action_id), var_range) return_action_id += 1 action2.add_ref(result, parent_action) action_list.extend(extra_actions) comment = "return {}".format(value) return (result, comment) 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_varaction2_expr(expr, feature, extra_dicts = []): # 'normal' and 60+x variables to use vars_normal = action2var_variables.varact2vars[feature] vars_60x = action2var_variables.varact2vars60x[feature] # lambda function to convert (value, pos) to a function pointer # since we need the variable name later on, a reverse lookup is needed # TODO pass the function name along to avoid this func60x = lambda value, pos: expression.FunctionPtr(expression.Identifier(generic.reverse_lookup(vars_60x, value), pos), parse_60x_var, value) return expr.reduce(extra_dicts + [(action2var_variables.varact2_globalvars, parse_var), \ (vars_normal, parse_var), \ (vars_60x, func60x)] + \ global_constants.const_list) def parse_varaction2(switch_block): global return_action_id return_action_id = 0 action6.free_parameters.save() act6 = action6.Action6() action_list = action2real.create_spriteset_actions(switch_block) feature = next(iter(switch_block.feature_set)) varaction2 = Action2Var(feature, switch_block.name.value, switch_block.pos, switch_block.var_range) expr = reduce_varaction2_expr(switch_block.expr, get_feature(switch_block)) offset = 4 #first var parser = Varaction2Parser(get_feature(switch_block)) parser.parse_expr(expr) action_list.extend(parser.extra_actions) for mod in parser.mods: act6.modify_bytes(mod.param, mod.size, mod.offset + offset) varaction2.var_list = parser.var_list offset += parser.var_list_size + 1 # +1 for the byte num-ranges none_result = None if any(x is not None and x.value is None for x in [r.result for r in switch_block.body.ranges] + [switch_block.body.default]): # Computed result is returned in at least one result if len(switch_block.body.ranges) == 0: # There is only a default, which is 'return computed result', so we're fine none_result = expression.ConstantNumeric(0) # Return value does not matter else: # Add an extra action to return the computed value extra_actions, none_result = create_return_action(expression.Variable(expression.ConstantNumeric(0x1C)), feature, switch_block.name.value + "@return", 0x89) action_list.extend(extra_actions) used_ranges = [] for r in switch_block.body.ranges: comment = str(r.min) + " .. " + str(r.max) + ": " range_result, range_comment = parse_result(r.result.value, action_list, act6, offset, varaction2, none_result, switch_block.var_range) comment += range_comment offset += 2 # size of result range_min, offset, check_min = parse_minmax(r.min, r.unit, action_list, act6, offset) range_max, offset, check_max = parse_minmax(r.max, r.unit, action_list, act6, offset) range_overlap = False if check_min and check_max: for existing_range in used_ranges: if existing_range[0] <= range_min.value and range_max.value <= existing_range[1]: generic.print_warning("Range overlaps with existing ranges so it'll never be reached", r.min.pos) range_overlap = True break if not range_overlap: used_ranges.append([range_min.value, range_max.value]) used_ranges.sort() i = 0 while i + 1 < len(used_ranges): if used_ranges[i + 1][0] <= used_ranges[i][1] + 1: used_ranges[i][1] = max(used_ranges[i][1], used_ranges[i + 1][1]) used_ranges.pop(i + 1) else: i += 1 if not range_overlap: varaction2.ranges.append(VarAction2Range(range_min, range_max, range_result, comment)) if len(switch_block.body.ranges) == 0 and \ (switch_block.body.default is None or switch_block.body.default.value is not None): # Computed result is not returned, but there are no ranges # Add one range, to avoid the nvar == 0 bear trap offset += 10 varaction2.ranges.append(VarAction2Range(expression.ConstantNumeric(1), expression.ConstantNumeric(0), expression.ConstantNumeric(0), "Bogus range to avoid nvar == 0")) # Handle default result if switch_block.body.default is not None: # there is a default value default_result = switch_block.body.default.value else: # Default to CB_FAILED default_result = expression.SpriteGroupRef(expression.Identifier('CB_FAILED', None), [], None) default, default_comment = parse_result(default_result, action_list, act6, offset, varaction2, none_result, switch_block.var_range) varaction2.default_result = default if switch_block.body.default is None: varaction2.default_comment = 'No default specified -> fail callback' elif switch_block.body.default.value is None: varaction2.default_comment = 'Return computed value' else: varaction2.default_comment = 'default: ' + default_comment if len(act6.modifications) > 0: action_list.append(act6) action_list.append(varaction2) switch_block.set_action2(varaction2, feature) action6.free_parameters.restore() return action_list nml-0.4.4/nml/actions/action6.py0000644000567200056720000000363712643457545017642 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import free_number_list, generic from nml.actions import base_action free_parameters = free_number_list.FreeNumberList(list(range(0x40, 0x80)), "No free parameters available to use for internal computations.", "No unique free parameters available for internal computations.") def print_stats(): """ Print statistics about used ids. """ if free_parameters.stats[0] > 0: generic.print_info("Concurrent ActionD registers: {}/{} ({})".format(free_parameters.stats[0], free_parameters.total_amount, str(free_parameters.stats[1]))) class Action6(base_action.BaseAction): def __init__(self): self.modifications = [] def modify_bytes(self, param, num_bytes, offset): self.modifications.append( (param, num_bytes, offset) ) def write(self, file): size = 2 + 5 * len(self.modifications) file.start_sprite(size) file.print_bytex(6) file.newline() for mod in self.modifications: file.print_bytex(mod[0]) file.print_bytex(mod[1]) file.print_bytex(0xFF) file.print_wordx(mod[2]) file.newline() file.print_bytex(0xFF) file.newline() file.end_sprite() def skip_action7(self): return False nml-0.4.4/nml/actions/action2production.py0000644000567200056720000001340312643457545021735 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml.actions import action2, action2var, action6, actionD from nml import expression, nmlop class Action2Production(action2.Action2): """ Class corresponding to Action2Industries (=production CB) @ivar version: Production CB version. Version 0 uses constants, version 1 uses registers. @type version: C{int} @ivar sub_in: Amounts (v0) or registers (v1) to subtract from incoming cargos. @type sub_in: C{list} of (C{int} or L{VarAction2Var}) @ivar add_out: Amounts (v0) or registers (v1) to add to the output cargos. @type add_out: C{list} of (C{int} or L{VarAction2Var}) @ivar again: Number (v0) or register (v1), production CB will be run again if nonzero. @type again C{int} or L{VarAction2Var} """ def __init__(self, name, pos, version, sub_in, add_out, again): action2.Action2.__init__(self, 0x0A, name, pos) self.version = version assert version == 0 or version == 1 self.sub_in = sub_in assert len(self.sub_in) == 3 self.add_out = add_out assert len(self.add_out) == 2 self.again = again def prepare_output(self, sprite_num): action2.Action2.prepare_output(self, sprite_num) def write(self, file): cargo_size = 2 if self.version == 0 else 1 size = 2 + 5 * cargo_size action2.Action2.write_sprite_start(self, file, size) file.print_bytex(self.version) values = self.sub_in + self.add_out + [self.again] # Read register numbers if needed if self.version == 1: values = [val.parameter for val in values] for val in values[:-1]: file.print_varx(val, cargo_size) file.print_bytex(values[-1]) file.newline() file.end_sprite() def get_production_actions(produce): """ Get the action list that implements the given produce-block in nfo. @param produce: Produce-block to parse. @type produce: L{Produce} """ action_list = [] act6 = action6.Action6() action6.free_parameters.save() result_list = [] varact2parser = action2var.Varaction2Parser(0x0A) if all(x.supported_by_actionD(False) for x in produce.param_list): version = 0 offset = 4 for i, param in enumerate(produce.param_list): result, offset = actionD.write_action_value(param, action_list, act6, offset, 2 if i < 5 else 1) result_list.append(result.value) else: version = 1 for i, param in enumerate(produce.param_list): if isinstance(param, expression.StorageOp) and param.name == 'LOAD_TEMP' and \ isinstance(param.register, expression.ConstantNumeric): # We can load a register directly result_list.append(action2var.VarAction2Var(0x7D, 0, 0xFFFFFFFF, param.register.value)) else: if len(varact2parser.var_list) != 0: varact2parser.var_list.append(nmlop.VAL2) varact2parser.var_list_size += 1 varact2parser.parse_expr(action2var.reduce_varaction2_expr(param, 0x0A)) store_tmp = action2var.VarAction2StoreTempVar() result_list.append(action2var.VarAction2LoadTempVar(store_tmp)) varact2parser.var_list.append(nmlop.STO_TMP) varact2parser.var_list.append(store_tmp) varact2parser.var_list_size += store_tmp.get_size() + 1 # Add 1 for operator if len(act6.modifications) > 0: action_list.append(act6) prod_action = Action2Production(produce.name.value, produce.pos, version, result_list[0:3], result_list[3:5], result_list[5]) action_list.append(prod_action) if len(varact2parser.var_list) == 0: produce.set_action2(prod_action, 0x0A) else: # Create intermediate varaction2 varaction2 = action2var.Action2Var(0x0A, '{}@registers'.format(produce.name.value), produce.pos, 0x89) varaction2.var_list = varact2parser.var_list action_list.extend(varact2parser.extra_actions) extra_act6 = action6.Action6() for mod in varact2parser.mods: extra_act6.modify_bytes(mod.param, mod.size, mod.offset + 4) if len(extra_act6.modifications) > 0: action_list.append(extra_act6) ref = expression.SpriteGroupRef(produce.name, [], None, prod_action) varaction2.ranges.append(action2var.VarAction2Range(expression.ConstantNumeric(0), expression.ConstantNumeric(0), ref, '')) varaction2.default_result = ref varaction2.default_comment = '' # Add two references (default + range) action2.add_ref(ref, varaction2) action2.add_ref(ref, varaction2) produce.set_action2(varaction2, 0x0A) action_list.append(varaction2) action6.free_parameters.restore() return action_list def make_empty_production_action2(pos): """ Make an empty production action2 For use with failed callbacks @param pos: Positional context. @type pos: L{Position} @return: The created production action2 @rtype: L{Action2Production} """ return Action2Production("@CB_FAILED_PROD", pos, 0, [0, 0, 0], [0, 0], 0) nml-0.4.4/nml/actions/actionB.py0000644000567200056720000001012712643457545017646 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, generic, grfstrings from nml.actions import base_action, action6, actionD class ActionB(base_action.BaseAction): def __init__(self, severity, lang, msg, data, extra_params): self.severity = severity self.lang = lang self.msg = msg self.data = data self.extra_params = extra_params def write(self, file): size = 4 if not isinstance(self.msg, int): size += grfstrings.get_string_size(self.msg) if self.data is not None: size += grfstrings.get_string_size(self.data) + len(self.extra_params) file.start_sprite(size) file.print_bytex(0x0B) self.severity.write(file, 1) file.print_bytex(self.lang) if isinstance(self.msg, int): file.print_bytex(self.msg) else: file.print_bytex(0xFF) file.print_string(self.msg) if self.data is not None: file.print_string(self.data) for param in self.extra_params: param.write(file, 1) file.newline() file.end_sprite() def skip_action7(self): return False default_error_msg = { 'REQUIRES_TTDPATCH' : 0, 'REQUIRES_DOS_WINDOWS' : 1, 'USED_WITH' : 2, 'INVALID_PARAMETER' : 3, 'MUST_LOAD_BEFORE' : 4, 'MUST_LOAD_AFTER' : 5, 'REQUIRES_OPENTTD' : 6, } error_severity = { 'NOTICE' : 0, 'WARNING' : 1, 'ERROR' : 2, 'FATAL' : 3, } def parse_error_block(error): action6.free_parameters.save() action_list = [] act6 = action6.Action6() severity = actionD.write_action_value(error.severity, action_list, act6, 1, 1)[0] langs = [0x7F] if isinstance(error.msg, expression.String): custom_msg = True msg_string = error.msg grfstrings.validate_string(msg_string) langs.extend(grfstrings.get_translations(msg_string)) for l in langs: assert l is not None else: custom_msg = False msg = error.msg.reduce_constant().value if error.data is not None: error.data = error.data.reduce() if isinstance(error.data, expression.String): grfstrings.validate_string(error.data) langs.extend(grfstrings.get_translations(error.data)) for l in langs: assert l is not None elif not isinstance(error.data, expression.StringLiteral): raise generic.ScriptError("Error parameter 3 'data' should be the identifier of a custom sting", error.data.pos) params = [] for expr in error.params: if isinstance(expr, expression.Parameter) and isinstance(expr.num, expression.ConstantNumeric): params.append(expr.num) else: tmp_param, tmp_param_actions = actionD.get_tmp_parameter(expr) action_list.extend(tmp_param_actions) params.append(expression.ConstantNumeric(tmp_param)) langs = list(set(langs)) langs.sort() for lang in langs: if custom_msg: msg = grfstrings.get_translation(msg_string, lang) if error.data is None: data = None elif isinstance(error.data, expression.StringLiteral): data = error.data.value else: data = grfstrings.get_translation(error.data, lang) if len(act6.modifications) > 0: action_list.append(act6) action_list.append(ActionB(severity, lang, msg, data, params)) action6.free_parameters.restore() return action_list nml-0.4.4/nml/actions/action0properties.py0000644000567200056720000014374012643457545021751 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import itertools from nml import generic, nmlop from nml.expression import BinOp, ConstantNumeric, ConstantFloat, Array, StringLiteral, Identifier tilelayout_names = {} class BaseAction0Property(object): """ Base class for Action0 properties. """ def write(self, file): """ Write this property to the output given by file. @param file: The outputfile we have to write to. @type file: L{SpriteOutputBase} """ raise NotImplementedError('write is not implemented in {:r}'.format(type(self))) def get_size(self): """ Get the number of bytes that this property will write to the output. @return: The size of this property in bytes. @rtype: C{int} """ raise NotImplementedError('get_size is not implemented in {:r}'.format(type(self))) class Action0Property(BaseAction0Property): """ Simple Action 0 property with a fixed size. @ivar num: Number of the property. @type num: C{int} @ivar values: Value of the property for each id. @type values: C{list} of L{ConstantNumeric} @ivar size: Size of the storage, in bytes. @type size: C{int} """ def __init__(self, num, value, size): self.num = num self.values = value if isinstance(value, list) else [value] self.size = size # Make sure the value fits in the size. # Strings have their own check in parse_property for val in self.values: if not isinstance(val, StringLiteral): biggest = 1 << (8 * size) if val.value >= biggest: raise generic.ScriptError("Action 0 property too large", val.pos) elif val.value < 0 and val.value + (biggest // 2) < 0: raise generic.ScriptError("Action 0 property too small", val.pos) def write(self, file): file.print_bytex(self.num) for val in self.values: val.write(file, self.size) file.newline() def get_size(self): return self.size * len(self.values) + 1 # @var properties: A mapping of features to properties. This is a list # with one item per feature. Entries should be a dictionary of properties, # or C{None} if no properties are defined for that feature. # # Each property is a mapping of property name to its characteristics. # These characteristics are either a dictionary or a list of # dictionaries, the latter can be used to set multiple properties. # First a short summary is given, then the recognized characteristics # are outlined below in more detail. # # Summary: If 'string' or 'string_literal' is set, the value should be a # string or literal string, else the value is a number. 'unit_type' and # 'unit_conversion' are used to convert user-entered values to nfo values. # If some more arithmetic is needed to convert the entered value into an nfo # value, 'value_function' can be used. For even more complicated things, # 'custom_function' can be used to create a special mapping of the value to nfo # properties, else 'num' and 'size' are used to provide a 'normal' action0. # # 'string', if set, means that the value of the property should be a string. # The value of characteristic indicates the string range to use (usually 0xD0 or 0xDC) # If set to None, the string will use the ID of the item (used for vehicle names) # # 'string_literal', if set, indicates that the value of the property should # be a literal (quoted) string. The value of the characteristic is equal to # the required length (usually 4) of said literal string. # # 'unit_type' means that units of the given type (power, speed) can be applied # to this property. A list of units can be found in ../unit.py. The value is then # converted to a certain reference unit (for example m/s for speed) # Leaving this unset means that no units (except 'nfo' which is an identity mapping) # can be applied to this property. # # 'unit_conversion' defines a conversion factor between the value entered by # the user and the resulting value in nfo. This is either an integer or a # rational number entered as a 2-tuple (numerator, denominator). # The entered value (possibly converted # to the appropriate reference unit, # see 'unit_type' above) is multiplied by this factor and then rounded to an # integer to provide the final value. # This parameter is not required and defaults to 1. # # 'value_function' can be used to alter the mapping of nml values to nfo values # it takes one argument (the value) and returns the new value to use # Both the parameter and the return value are expressions that do not have to be # constants # # 'custom_function' can be used to bypass the normal way of converting the # name / value to an Action0Property. This function is normally called with one # argument, which is the value of the property. For houses, there may be multiple # values, passed as a vararg-list. It should return a list of Action0Property. # To pass extra parameters to the function, a dose of lambda calculus can be used. # Consult the code for examples. # # 'test_function' can be used to determine if the property should be set in the # first place. It normally takes one argument (the value) and should return True # if the property is to be set, False if it is to be ignored. Default is True # For houses, there may be multiple values, passed as a vararg-list. # # 'num' is the Action0 property number of the action 0 property, as given by the # nfo specs. If set to -1, no action0 property will be generated. If # 'custom_function' is set, this value is not needed and can be left out. # # 'size' is the size (in bytes) of the resulting action 0 property. Valid # values are 1 (byte), 2 (word) or 4 (dword). For other (or variable) sizes, # 'custom_function' is needed. If 'custom_function' is set or 'num' is equal # to -1, this parameter is not needed and can be left out. # # 'warning' is a string (optional) containing a warning message that will be # shown if a property is used. Use for deprecating properties. # # 'first' (value doesn't matter) if the property should be set first (generally a substitute type) properties = 0x12 * [None] # # Some helper functions that are used for multiple features # def two_byte_property(low_prop, high_prop, low_prop_info = {}, high_prop_info = {}): """ Decode a two byte value into two action 0 properties. @param low_prop: Property number for the low 8 bits of the value. @type low_prop: C{int} @param high_prop: Property number for the high 8 bits of the value. @type high_prop: C{int} @param low_prop_info: Dictionary with additional property information for the low byte. @type low_prop_info: C{dict} @param high_prop_info: Dictionary with additional property information for the low byte. @type high_prop_info: C{dict} @return: Sequence of two dictionaries with property information (low part, high part). @rtype: C{list} of C{dict} """ low_byte_info = {'num': low_prop, 'size': 1, 'value_function': lambda value: BinOp(nmlop.AND, value, ConstantNumeric(0xFF, value.pos), value.pos).reduce()} high_byte_info = {'num': high_prop, 'size': 1, 'value_function': lambda value: BinOp(nmlop.SHIFT_RIGHT, value, ConstantNumeric(8, value.pos), value.pos).reduce()} low_byte_info.update(low_prop_info) high_byte_info.update(high_prop_info) return [low_byte_info, high_byte_info] def animation_info(value, loop_bit=8, max_frame=253): """ Convert animation info array of two elements to an animation info property. The first is 0/1, and defines whether or not the animation loops. The second is the number of frames, at most 253 frames. @param value: Array of animation info. @type value: C{Array} @param loop_bit: Bit the loop information is stored. @type loop_bit: C{int} @param max_frame: Max frames possible. @type max_frame: C{int} @return: Value to use for animation property. @rtype: L{Expression} """ if not isinstance(value, Array) or len(value.values) != 2: raise generic.ScriptError("animation_info must be an array with exactly 2 constant values", value.pos) looping = value.values[0].reduce_constant().value frames = value.values[1].reduce_constant().value if looping not in (0, 1): raise generic.ScriptError("First field of the animation_info array must be either 0 or 1", value.values[0].pos) if frames < 1 or frames > max_frame: raise generic.ScriptError("Second field of the animation_info array must be between 1 and " + str(max_frame), value.values[1].pos) return ConstantNumeric((looping << loop_bit) + frames - 1) def cargo_list(value, max_num_cargos): """ Encode an array of cargo types in a single property value. If less than the maximum number of cargos are given the rest is filled up with 0xFF (=invalid cargo). @param value: Array of cargo types. @type value: C{Array} @param max_num_cargos: The maximum number of cargos in the array. @type max_num_cargos: C{int} @param prop_num: Property number. @type prop_num: C{int} @param prop_size: Property size in bytes. @type prop_size: C{int} """ if not isinstance(value, Array) or len(value.values) > max_num_cargos: raise generic.ScriptError("Cargo list must be an array with no more than {:d} values".format(max_num_cargos), value.pos) cargoes = value.values + [ConstantNumeric(0xFF, value.pos) for _ in range(max_num_cargos - len(value.values))] ret = None for i, cargo in enumerate(cargoes): byte = BinOp(nmlop.AND, cargo, ConstantNumeric(0xFF, cargo.pos), cargo.pos) if i == 0: ret = byte else: byte = BinOp(nmlop.SHIFT_LEFT, byte, ConstantNumeric(i * 8, cargo.pos), cargo.pos) ret = BinOp(nmlop.OR, ret, byte, cargo.pos) return ret.reduce() # # General vehicle properties that apply to feature 0x00 .. 0x03 # general_veh_props = { 'reliability_decay' : {'size': 1, 'num': 0x02}, 'vehicle_life' : {'size': 1, 'num': 0x03}, 'model_life' : {'size': 1, 'num': 0x04}, 'climates_available' : {'size': 1, 'num': 0x06}, 'loading_speed' : {'size': 1, 'num': 0x07}, 'name' : {'num': -1, 'string': None}, } def ottd_display_speed(value, divisor, unit): return int(value.value / divisor) * 10 // 16 * unit.ottd_mul >> unit.ottd_shift class CargotypeListProp(BaseAction0Property): def __init__(self, prop_num, data): # data is a list, each element belongs to an item ID # Each element in the list is a list of cargo types self.prop_num = prop_num self.data = data def write(self, file): file.print_bytex(self.prop_num) for elem in self.data: file.print_byte(len(elem)) for i, val in enumerate(elem): if i % 8 == 0: file.newline() file.print_bytex(val) file.newline() def get_size(self): total_len = 1 # Prop number for elem in self.data: # For each item ID to set, make space for all values + 1 for the length total_len += len(elem) + 1 return total_len def ctt_list(prop_num, *values): # values may have multiple entries, if more than one item ID is set (e.g. multitile houses) # Each value is an expression.Array of cargo types for value in values: if not isinstance(value, Array): raise generic.ScriptError("Value of cargolist property must be an array", value.pos) return [CargotypeListProp(prop_num, [[ctype.reduce_constant().value for ctype in single_item_array.values] for single_item_array in values])] def vehicle_length(value): if isinstance(value, ConstantNumeric): generic.check_range(value.value, 1, 8, "vehicle length", value.pos) return BinOp(nmlop.SUB, ConstantNumeric(8, value.pos), value, value.pos).reduce() def zero_refit_mask(prop_num): # Zero the refit mask, in addition to setting some other refit property return {'size': 4, 'num': prop_num, 'value_function': lambda value: ConstantNumeric(0)} # # Feature 0x00 (Trains) # properties[0x00] = { 'track_type' : {'size': 1, 'num': 0x05}, 'ai_special_flag' : {'size': 1, 'num': 0x08}, 'speed' : {'size': 2, 'num': 0x09, 'unit_type': 'speed', 'unit_conversion': (5000, 1397), 'adjust_value': lambda val, unit: ottd_display_speed(val, 1, unit)}, # 09 doesn't exist 'power' : {'size': 2, 'num': 0x0B, 'unit_type': 'power'}, # 0A doesn't exist 'running_cost_factor' : {'size': 1, 'num': 0x0D}, 'running_cost_base' : {'size': 4, 'num': 0x0E}, # 0F -11 don't exist 'sprite_id' : {'size': 1, 'num': 0x12}, 'dual_headed' : {'size': 1, 'num': 0x13}, 'cargo_capacity' : {'size': 1, 'num': 0x14}, 'default_cargo_type' : {'size': 1, 'num': 0x15}, 'weight' : two_byte_property(0x16, 0x24, {'unit_type': 'weight'}, {'unit_type': 'weight'}), 'cost_factor' : {'size': 1, 'num': 0x17}, 'ai_engine_rank' : {'size': 1, 'num': 0x18}, 'engine_class' : {'size': 1, 'num': 0x19}, # 1A (sort purchase list) is implemented elsewhere 'extra_power_per_wagon' : {'size': 2, 'num': 0x1B, 'unit_type': 'power'}, 'refit_cost' : {'size': 1, 'num': 0x1C}, # 1D (refittable cargo types) is removed, it is zeroed when setting a different refit property # 1E (callback flags) is not set by user 'tractive_effort_coefficient' : {'size': 1, 'num': 0x1F, 'unit_conversion': 255}, 'air_drag_coefficient' : {'size': 1, 'num': 0x20, 'unit_conversion': 255}, 'length' : {'size': 1, 'num': 0x21, 'value_function': vehicle_length}, # 22 has two names, to simplify docs 'visual_effect_and_powered' : {'size': 1, 'num': 0x22}, 'effect_spawn_model_and_powered': {'size': 1, 'num': 0x22}, 'extra_weight_per_wagon' : {'size': 1, 'num': 0x23, 'unit_type': 'weight'}, # 24 is high byte of 16 (weight) 'bitmask_vehicle_info' : {'size': 1, 'num': 0x25}, 'retire_early' : {'size': 1, 'num': 0x26}, 'misc_flags' : {'size': 1, 'num': 0x27}, 'refittable_cargo_classes' : [{'size': 2, 'num': 0x28}, zero_refit_mask(0x1D)], 'non_refittable_cargo_classes' : [{'size': 2, 'num': 0x29}, zero_refit_mask(0x1D)], 'introduction_date' : {'size': 4, 'num': 0x2A}, 'cargo_age_period' : {'size': 2, 'num': 0x2B}, 'cargo_allow_refit' : [{'custom_function': lambda value: ctt_list(0x2C, value)}, zero_refit_mask(0x1D)], 'cargo_disallow_refit' : [{'custom_function': lambda value: ctt_list(0x2D, value)}, zero_refit_mask(0x1D)], } properties[0x00].update(general_veh_props) # # Feature 0x01 (Road Vehicles) # def roadveh_speed_prop(prop_info): # prop 08 value is min(value, 255) prop08_value = lambda value: BinOp(nmlop.MIN, value, ConstantNumeric(0xFF, value.pos), value.pos).reduce() # prop 15 value is (value + 3) / 4 prop15_value = lambda value: BinOp(nmlop.DIV, BinOp(nmlop.ADD, value, ConstantNumeric(3, value.pos), value.pos), ConstantNumeric(4, value.pos), value.pos).reduce() # prop 15 should not be set if value(prop08_value) <= 255. But as we test prop15 and prop15 = 0.25/prop08, test for 64: prop15_test = lambda value: isinstance(value, ConstantNumeric) and value.value >= 0x40 prop08 = {'size': 1, 'num': 0x08, 'value_function': prop08_value} prop15 = {'size': 1, 'num': 0x15, 'value_function': prop15_value, 'test_function': prop15_test} for key in prop_info: prop08[key] = prop15[key] = prop_info[key] return [prop08, prop15] properties[0x01] = { 'speed' : roadveh_speed_prop({'unit_type': 'speed', 'unit_conversion': (10000, 1397), 'adjust_value': lambda val, unit: ottd_display_speed(val, 2, unit)}), 'running_cost_factor' : {'size': 1, 'num': 0x09}, 'running_cost_base' : {'size': 4, 'num': 0x0A}, # 0B -0D don't exist 'sprite_id' : {'size': 1, 'num': 0x0E}, 'cargo_capacity' : {'size': 1, 'num': 0x0F}, 'default_cargo_type' : {'size': 1, 'num': 0x10}, 'cost_factor' : {'size': 1, 'num': 0x11}, 'sound_effect' : {'size': 1, 'num': 0x12}, 'power' : {'size': 1, 'num': 0x13, 'unit_type': 'power', 'unit_conversion': (1, 10)}, 'weight' : {'size': 1, 'num': 0x14, 'unit_type': 'weight', 'unit_conversion': 4}, # 15 is set together with 08 (see above) # 16 (refittable cargo types) is removed, it is zeroed when setting a different refit property # 17 (callback flags) is not set by user 'tractive_effort_coefficient' : {'size': 1, 'num': 0x18, 'unit_conversion': 255}, 'air_drag_coefficient' : {'size': 1, 'num': 0x19, 'unit_conversion': 255}, 'refit_cost' : {'size': 1, 'num': 0x1A}, 'retire_early' : {'size': 1, 'num': 0x1B}, 'misc_flags' : {'size': 1, 'num': 0x1C}, 'refittable_cargo_classes' : [{'size': 2, 'num': 0x1D}, zero_refit_mask(0x16)], 'non_refittable_cargo_classes' : [{'size': 2, 'num': 0x1E}, zero_refit_mask(0x16)], 'introduction_date' : {'size': 4, 'num': 0x1F}, # 20 (sort purchase list) is implemented elsewhere # 21 has two names, to simplify docs 'visual_effect' : {'size': 1, 'num': 0x21}, 'effect_spawn_model' : {'size': 1, 'num': 0x21}, 'cargo_age_period' : {'size': 2, 'num': 0x22}, 'length' : {'size': 1, 'num': 0x23, 'value_function': vehicle_length}, 'cargo_allow_refit' : [{'custom_function': lambda value: ctt_list(0x24, value)}, zero_refit_mask(0x16)], 'cargo_disallow_refit' : [{'custom_function': lambda value: ctt_list(0x25, value)}, zero_refit_mask(0x16)], } properties[0x01].update(general_veh_props) # # Feature 0x02 (Ships) # def speed_fraction(value): # Unit is already converted to 0 .. 255 range when we get here if isinstance(value, ConstantNumeric) and not (0 <= value.value <= 255): # Do not use check_range to provide better error message raise generic.ScriptError("speed fraction must be in range 0 .. 1", value.pos) return BinOp(nmlop.SUB, ConstantNumeric(255, value.pos), value, value.pos).reduce() properties[0x02] = { 'sprite_id' : {'size': 1, 'num': 0x08}, 'is_refittable' : {'size': 1, 'num': 0x09}, 'cost_factor' : {'size': 1, 'num': 0x0A}, 'speed' : {'size': 1, 'num': 0x0B, 'unit_type': 'speed', 'unit_conversion': (10000, 1397), 'adjust_value': lambda val, unit: ottd_display_speed(val, 2, unit)}, 'default_cargo_type' : {'size': 1, 'num': 0x0C}, 'cargo_capacity' : {'size': 2, 'num': 0x0D}, # 0E does not exist 'running_cost_factor' : {'size': 1, 'num': 0x0F}, 'sound_effect' : {'size': 1, 'num': 0x10}, # 11 (refittable cargo types) is removed, it is zeroed when setting a different refit property # 12 (callback flags) is not set by user 'refit_cost' : {'size': 1, 'num': 0x13}, 'ocean_speed_fraction' : {'size': 1, 'num': 0x14, 'unit_conversion': 255, 'value_function': speed_fraction}, 'canal_speed_fraction' : {'size': 1, 'num': 0x15, 'unit_conversion': 255, 'value_function': speed_fraction}, 'retire_early' : {'size': 1, 'num': 0x16}, 'misc_flags' : {'size': 1, 'num': 0x17}, 'refittable_cargo_classes' : [{'size': 2, 'num': 0x18}, zero_refit_mask(0x11)], 'non_refittable_cargo_classes' : [{'size': 2, 'num': 0x19}, zero_refit_mask(0x11)], 'introduction_date' : {'size': 4, 'num': 0x1A}, # 1B (sort purchase list) is implemented elsewhere # 1C has two names, to simplify docs 'visual_effect' : {'size': 1, 'num': 0x1C}, 'effect_spawn_model' : {'size': 1, 'num': 0x1C}, 'cargo_age_period' : {'size': 2, 'num': 0x1D}, 'cargo_allow_refit' : [{'custom_function': lambda value: ctt_list(0x1E, value)}, zero_refit_mask(0x11)], 'cargo_disallow_refit' : [{'custom_function': lambda value: ctt_list(0x1F, value)}, zero_refit_mask(0x11)], } properties[0x02].update(general_veh_props) # # Feature 0x03 (Aircraft) # def aircraft_is_heli(value): if isinstance(value, ConstantNumeric) and not value.value in (0, 2, 3): raise generic.ScriptError("Invalid value for aircraft_type", value.pos) return BinOp(nmlop.AND, value, ConstantNumeric(2, value.pos), value.pos).reduce() def aircraft_is_large(value): return BinOp(nmlop.AND, value, ConstantNumeric(1, value.pos), value.pos).reduce() properties[0x03] = { 'sprite_id' : {'size': 1, 'num': 0x08}, 'aircraft_type' : [{'size': 1, 'num': 0x09, 'value_function': aircraft_is_heli}, {'size': 1, 'num': 0x0A, 'value_function': aircraft_is_large}], 'cost_factor' : {'size': 1, 'num': 0x0B}, 'speed' : {'size': 1, 'num': 0x0C, 'unit_type': 'speed', 'unit_conversion': (701, 2507), 'adjust_value': lambda val, unit: ottd_display_speed(val, 1 / (8 * 1.6), unit)}, 'acceleration' : {'size': 1, 'num': 0x0D}, 'running_cost_factor' : {'size': 1, 'num': 0x0E}, 'passenger_capacity' : {'size': 2, 'num': 0x0F}, # 10 does not exist 'mail_capacity' : {'size': 1, 'num': 0x11}, 'sound_effect' : {'size': 1, 'num': 0x12}, # 13 (refittable cargo types) is removed, it is zeroed when setting a different refit property # 14 (callback flags) is not set by user 'refit_cost' : {'size': 1, 'num': 0x15}, 'retire_early' : {'size': 1, 'num': 0x16}, 'misc_flags' : {'size': 1, 'num': 0x17}, 'refittable_cargo_classes' : [{'size': 2, 'num': 0x18}, zero_refit_mask(0x13)], 'non_refittable_cargo_classes' : [{'size': 2, 'num': 0x19}, zero_refit_mask(0x13)], 'introduction_date' : {'size': 4, 'num': 0x1A}, # 1B (sort purchase list) is implemented elsewhere 'cargo_age_period' : {'size': 2, 'num': 0x1C}, 'cargo_allow_refit' : [{'custom_function': lambda value: ctt_list(0x1D, value)}, zero_refit_mask(0x13)], 'cargo_disallow_refit' : [{'custom_function': lambda value: ctt_list(0x1E, value)}, zero_refit_mask(0x13)], 'range' : {'size': 2, 'num': 0x1F}, } properties[0x03].update(general_veh_props) # TODO: Feature 0x04 # # Feature 0x05 (Canals) # properties[0x05] = { # 08 (callback flags) not set by user 'graphic_flags' : {'size': 1, 'num': 0x09}, } # TODO: Feature 0x06 # # Feature 0x07 (Houses) # def house_prop_0A(value): # User sets an array [min_year, max_year] as property value # House property 0A is set to ((max_year - 1920) << 8) | (min_year - 1920) # With both bytes clamped to the 0 .. 255 range if not isinstance(value, Array) or len(value.values) != 2: raise generic.ScriptError("Availability years must be an array with exactly two values", value.pos) min_year = BinOp(nmlop.SUB, value.values[0], ConstantNumeric(1920, value.pos), value.pos) min_year = BinOp(nmlop.MAX, min_year, ConstantNumeric(0, value.pos), value.pos) min_year = BinOp(nmlop.MIN, min_year, ConstantNumeric(255, value.pos), value.pos) max_year = BinOp(nmlop.SUB, value.values[1], ConstantNumeric(1920, value.pos), value.pos) max_year = BinOp(nmlop.MAX, max_year, ConstantNumeric(0, value.pos), value.pos) max_year = BinOp(nmlop.MIN, max_year, ConstantNumeric(255, value.pos), value.pos) max_year = BinOp(nmlop.SHIFT_LEFT, max_year, ConstantNumeric(8, value.pos), value.pos) return BinOp(nmlop.OR, max_year, min_year, value.pos).reduce() def house_prop_21_22(value, index): # Take one of the values from the years_available array if not isinstance(value, Array) or len(value.values) != 2: raise generic.ScriptError("Availability years must be an array with exactly two values", value.pos) return value.values[index] def house_acceptance(value, index): if not isinstance(value, Array) or len(value.values) > 3: raise generic.ScriptError("accepted_cargos must be an array with no more than 3 values", value.pos) if index < len(value.values): cargo_amount_pair = value.values[index] if not isinstance(cargo_amount_pair, Array) or len(cargo_amount_pair.values) != 2: raise generic.ScriptError("Each element of accepted_cargos must be an array with two elements: cargoid and amount", cargo_amount_pair.pos) return cargo_amount_pair.values[1] else: return ConstantNumeric(0, value.pos) def house_accepted_cargo_types(value): if not isinstance(value, Array) or len(value.values) > 3: raise generic.ScriptError("accepted_cargos must be an array with no more than 3 values", value.pos) cargoes = [] for i in range(3): if i < len(value.values): cargo_amount_pair = value.values[i] if not isinstance(cargo_amount_pair, Array) or len(cargo_amount_pair.values) != 2: raise generic.ScriptError("Each element of accepted_cargos must be an array with two elements: cargoid and amount", cargo_amount_pair.pos) cargoes.append(cargo_amount_pair.values[0]) else: cargoes.append(ConstantNumeric(0xFF, value.pos)) ret = None for i, cargo in enumerate(cargoes): byte = BinOp(nmlop.AND, cargo, ConstantNumeric(0xFF, cargo.pos), cargo.pos) if i == 0: ret = byte else: byte = BinOp(nmlop.SHIFT_LEFT, byte, ConstantNumeric(i * 8, cargo.pos), cargo.pos) ret = BinOp(nmlop.OR, ret, byte, cargo.pos) return ret.reduce() def house_random_colours(value): # User sets array with 4 values (range 0..15) # Output is a dword, each byte being a value from the array if not isinstance(value, Array) or len(value.values) != 4: raise generic.ScriptError("Random colours must be an array with exactly four values", value.pos) ret = None for i, colour in enumerate(value.values): if isinstance(colour, ConstantNumeric): generic.check_range(colour.value, 0, 15, "Random house colours", colour.pos) byte = BinOp(nmlop.AND, colour, ConstantNumeric(0xFF, colour.pos), colour.pos) if i == 0: ret = byte else: byte = BinOp(nmlop.SHIFT_LEFT, byte, ConstantNumeric(i * 8, colour.pos), colour.pos) ret = BinOp(nmlop.OR, ret, byte, colour.pos) return ret.reduce() def house_available_mask(value): # User sets [town_zones, climates] array # Which is mapped to (town_zones | (climates & 0x800) | ((climates & 0xF) << 12)) if not isinstance(value, Array) or len(value.values) != 2: raise generic.ScriptError("availability_mask must be an array with exactly 2 values", value.pos) climates = BinOp(nmlop.AND, value.values[1], ConstantNumeric(0xF, value.pos), value.pos) climates = BinOp(nmlop.SHIFT_LEFT, climates, ConstantNumeric(12, value.pos), value.pos) above_snow = BinOp(nmlop.AND, value.values[1], ConstantNumeric(0x800, value.pos), value.pos) ret = BinOp(nmlop.OR, climates, value.values[0], value.pos) ret = BinOp(nmlop.OR, ret, above_snow, value.pos) return ret.reduce() # List of valid IDs of old house types old_houses = { 0 : set(), # 1x1, see below 2 : set([74, 76, 87]), # 2x1 3 : set([7, 66, 68, 99]), # 1x2 4 : set([20, 32, 40]), # 2x2 } # All houses not part of a multitile-house, are 1x1 houses old_houses[0] = set(range(110)).difference( house + i for house in (itertools.chain(*list(old_houses.values()))) for i in range(4 if house in old_houses[4] else 2) ) def mt_house_old_id(value, num_ids, size_bit): # For substitute / override properties # Set value for tile i (0 .. 3) to (value + i) # Also validate that the size of the old house matches if isinstance(value, ConstantNumeric) and not value.value in old_houses[size_bit]: raise generic.ScriptError("Substitute / override house type must have the same size as the newly defined house.", value.pos) ret = [value] for i in range(1, num_ids): ret.append(BinOp(nmlop.ADD, value, ConstantNumeric(i, value.pos), value.pos).reduce()) return ret def mt_house_prop09(value, num_ids, size_bit): # Only bit 5 should be set for additional tiles # Additionally, correctly set the size bit (0, 2, 3 or 4) for the first tile if isinstance(value, ConstantNumeric) and (value.value & 0x1D) != 0: raise generic.ScriptError("Invalid bits set in house property 'building_flags'.", value.pos) ret = [BinOp(nmlop.OR, value, ConstantNumeric(1 << size_bit, value.pos), value.pos).reduce()] for _i in range(1, num_ids): ret.append(BinOp(nmlop.AND, value, ConstantNumeric(1 << 5, value.pos), value.pos).reduce()) return ret def mt_house_mask(mask, value, num_ids, size_bit): # Mask out the bits not present in the 'mask' parameter for additional tiles ret = [value] for _i in range(1, num_ids): ret.append(BinOp(nmlop.AND, value, ConstantNumeric(mask, value.pos), value.pos).reduce()) return ret def mt_house_zero(value, num_ids, size_bit): return [value] + (num_ids - 1) * [ConstantNumeric(0, value.pos)] def mt_house_same(value, num_ids, size_bit): # Set to the same value for all tiles return num_ids * [value] def mt_house_class(value, num_ids, size_bit): # Set class to 0xFF for additional tiles return [value] + (num_ids - 1) * [ConstantNumeric(0xFF, value.pos)] properties[0x07] = { 'substitute' : {'size': 1, 'num': 0x08, 'multitile_function': mt_house_old_id, 'first': None}, 'building_flags' : two_byte_property(0x09, 0x19, {'multitile_function': mt_house_prop09}, {'multitile_function': lambda *args: mt_house_mask(0xFE, *args)}), 'years_available' : [{'size': 2, 'num': 0x0A, 'multitile_function': mt_house_zero, 'value_function': house_prop_0A}, {'size': 2, 'num': 0x21, 'multitile_function': mt_house_zero, 'value_function': lambda value: house_prop_21_22(value, 0)}, {'size': 2, 'num': 0x22, 'multitile_function': mt_house_zero, 'value_function': lambda value: house_prop_21_22(value, 1)}], 'population' : {'size': 1, 'num': 0x0B, 'multitile_function': mt_house_zero}, 'mail_multiplier' : {'size': 1, 'num': 0x0C, 'multitile_function': mt_house_zero}, 'accepted_cargos' : [{'size': 1, 'num': 0x0D, 'multitile_function': mt_house_same, 'value_function': lambda value: house_acceptance(value, 0)}, {'size': 1, 'num': 0x0E, 'multitile_function': mt_house_same, 'value_function': lambda value: house_acceptance(value, 1)}, {'size': 1, 'num': 0x0F, 'multitile_function': mt_house_same, 'value_function': lambda value: house_acceptance(value, 2)}, {'size': 4, 'num': 0x1E, 'multitile_function': mt_house_same, 'value_function': house_accepted_cargo_types}], 'local_authority_impact' : {'size': 2, 'num': 0x10, 'multitile_function': mt_house_same}, 'removal_cost_multiplier' : {'size': 1, 'num': 0x11, 'multitile_function': mt_house_same}, 'name' : {'size': 2, 'num': 0x12, 'string': 0xDC, 'multitile_function': mt_house_same}, 'availability_mask' : {'size': 2, 'num': 0x13, 'multitile_function': mt_house_zero, 'value_function': house_available_mask}, # prop 14 (callback flags 1) is not set by user 'override' : {'size': 1, 'num': 0x15, 'multitile_function': mt_house_old_id}, 'refresh_multiplier' : {'size': 1, 'num': 0x16, 'multitile_function': mt_house_same}, 'random_colours' : {'size': 4, 'num': 0x17, 'multitile_function': mt_house_same, 'value_function': house_random_colours}, 'probability' : {'size': 1, 'num': 0x18, 'multitile_function': mt_house_zero, 'unit_conversion': 16}, # prop 19 is the high byte of prop 09 'animation_info' : {'size': 1, 'num': 0x1A, 'multitile_function': mt_house_same, 'value_function': lambda value: animation_info(value, 7, 128)}, 'animation_speed' : {'size': 1, 'num': 0x1B, 'multitile_function': mt_house_same}, 'building_class' : {'size': 1, 'num': 0x1C, 'multitile_function': mt_house_class}, # prop 1D (callback flags 2) is not set by user 'minimum_lifetime' : {'size': 1, 'num': 0x1F, 'multitile_function': mt_house_zero}, 'watched_cargo_types' : {'multitile_function': mt_house_same, 'custom_function': lambda *values: ctt_list(0x20, *values)} # prop 21 -22 see above (years_available, prop 0A) } # Feature 0x08 (General Vars) is implemented elsewhere (e.g. basecost, snowline) # # Feature 0x09 (Industry Tiles) # def industrytile_cargos(value): if not isinstance(value, Array) or len(value.values) > 3: raise generic.ScriptError("accepted_cargos must be an array with no more than 3 values", value.pos) prop_num = 0x0A props = [] for cargo_amount_pair in value.values: if not isinstance(cargo_amount_pair, Array) or len(cargo_amount_pair.values) != 2: raise generic.ScriptError("Each element of accepted_cargos must be an array with two elements: cargoid and amount", cargo_amount_pair.pos) cargo_id = cargo_amount_pair.values[0].reduce_constant().value cargo_amount = cargo_amount_pair.values[1].reduce_constant().value props.append(Action0Property(prop_num, ConstantNumeric((cargo_amount << 8) | cargo_id), 2)) prop_num += 1 while prop_num <= 0x0C: props.append(Action0Property(prop_num, ConstantNumeric(0), 2)) prop_num += 1 return props properties[0x09] = { 'substitute' : {'size': 1, 'num': 0x08, 'first': None}, 'override' : {'size': 1, 'num': 0x09}, 'accepted_cargos' : {'custom_function': industrytile_cargos}, # = prop 0A - 0C 'land_shape_flags' : {'size': 1, 'num': 0x0D}, # prop 0E (callback flags) is not set by user 'animation_info' : {'size': 2, 'num': 0x0F, 'value_function': animation_info}, 'animation_speed' : {'size': 1, 'num': 0x10}, 'animation_triggers' : {'size': 1, 'num': 0x11}, 'special_flags' : {'size': 1, 'num': 0x12}, } # # Feature 0x0A (Industries) # class IndustryLayoutProp(BaseAction0Property): def __init__(self, layout_list): self.layout_list = layout_list def write(self, file): file.print_bytex(0x0A) file.print_byte(len(self.layout_list)) # -6 because prop_num, num_layouts and size should not be included file.print_dword(self.get_size() - 6) file.newline() for layout in self.layout_list: layout.write(file) file.newline() def get_size(self): size = 6 for layout in self.layout_list: size += layout.get_size() return size def industry_layouts(value): if not isinstance(value, Array) or not all(isinstance(x, Identifier) for x in value.values): raise generic.ScriptError("layouts must be an array of layout names", value.pos) layouts = [] for name in value.values: if name.value not in tilelayout_names: raise generic.ScriptError("Unknown layout name '{}'".format(name.value), name.pos) layouts.append(tilelayout_names[name.value]) return [IndustryLayoutProp(layouts)] def industry_prod_multiplier(value): if not isinstance(value, Array) or len(value.values) > 2: raise generic.ScriptError("Prod multiplier must be an array of up to two values", value.pos) props = [] for i in range(0, 2): val = value.values[i].reduce_constant() if i < len(value.values) else ConstantNumeric(0) props.append(Action0Property(0x12 + i, val, 1)) return props class RandomSoundsProp(BaseAction0Property): def __init__(self, sound_list): self.sound_list = sound_list def write(self, file): file.print_bytex(0x15) file.print_byte(len(self.sound_list)) for sound in self.sound_list: sound.write(file, 1) file.newline() def get_size(self): return len(self.sound_list) + 2 def random_sounds(value): if not isinstance(value, Array) or not all(isinstance(x, ConstantNumeric) for x in value.values): raise generic.ScriptError("random_sound_effects must be an array with sounds effects", value.pos) return [RandomSoundsProp(value.values)] class ConflictingTypesProp(BaseAction0Property): def __init__(self, types_list): self.types_list = types_list assert len(self.types_list) == 3 def write(self, file): file.print_bytex(0x16) for type in self.types_list: type.write(file, 1) file.newline() def get_size(self): return len(self.types_list) + 1 def industry_conflicting_types(value): if not isinstance(value, Array): raise generic.ScriptError("conflicting_ind_types must be an array of industry types", value.pos) if len(value.values) > 3: raise generic.ScriptError("conflicting_ind_types may have at most three entries", value.pos) types_list = [] for val in value.values: types_list.append(val.reduce_constant()) while len(types_list) < 3: types_list.append(ConstantNumeric(0xFF)) return [ConflictingTypesProp(types_list)] def industry_input_multiplier(value, prop_num): if not isinstance(value, Array) or len(value.values) > 2: raise generic.ScriptError("Input multiplier must be an array of up to two values", value.pos) val1 = value.values[0].reduce() if len(value.values) > 0 else ConstantNumeric(0) val2 = value.values[1].reduce() if len(value.values) > 1 else ConstantNumeric(0) if not isinstance(val1, (ConstantNumeric, ConstantFloat)) or not isinstance(val2, (ConstantNumeric, ConstantFloat)): raise generic.ScriptError("Expected a compile-time constant", value.pos) generic.check_range(val1.value, 0, 256, "input_multiplier", val1.pos) generic.check_range(val2.value, 0, 256, "input_multiplier", val2.pos) mul1 = int(val1.value * 256) mul2 = int(val2.value * 256) return [Action0Property(prop_num, ConstantNumeric(mul1 | (mul2 << 16)), 4)] properties[0x0A] = { 'substitute' : {'size': 1, 'num': 0x08, 'first': None}, 'override' : {'size': 1, 'num': 0x09}, 'layouts' : {'custom_function': industry_layouts}, # = prop 0A 'life_type' : {'size': 1, 'num': 0x0B}, 'closure_msg' : {'size': 2, 'num': 0x0C, 'string': 0xDC}, 'prod_increase_msg' : {'size': 2, 'num': 0x0D, 'string': 0xDC}, 'prod_decrease_msg' : {'size': 2, 'num': 0x0E, 'string': 0xDC}, 'fund_cost_multiplier' : {'size': 1, 'num': 0x0F}, 'prod_cargo_types' : {'size': 2, 'num': 0x10, 'value_function': lambda value: cargo_list(value, 2)}, 'accept_cargo_types' : {'size': 4, 'num': 0x11, 'value_function': lambda value: cargo_list(value, 3)}, 'prod_multiplier' : {'custom_function': industry_prod_multiplier}, # = prop 12,13 'min_cargo_distr' : {'size': 1, 'num': 0x14}, 'random_sound_effects' : {'custom_function': random_sounds}, # = prop 15 'conflicting_ind_types' : {'custom_function': industry_conflicting_types}, # = prop 16 'prob_random' : {'size': 1, 'num': 0x17}, # Obsolete, ambiguous name, use 'prob_map_gen' instead 'prob_map_gen' : {'size': 1, 'num': 0x17}, 'prob_in_game' : {'size': 1, 'num': 0x18}, 'map_colour' : {'size': 1, 'num': 0x19}, 'spec_flags' : {'size': 4, 'num': 0x1A}, 'new_ind_msg' : {'size': 2, 'num': 0x1B, 'string': 0xDC}, 'input_multiplier_1' : {'custom_function': lambda value: industry_input_multiplier(value, 0x1C)}, 'input_multiplier_2' : {'custom_function': lambda value: industry_input_multiplier(value, 0x1D)}, 'input_multiplier_3' : {'custom_function': lambda value: industry_input_multiplier(value, 0x1E)}, 'name' : {'size': 2, 'num': 0x1F, 'string': 0xDC}, 'prospect_chance' : {'size': 4, 'num': 0x20, 'unit_conversion': 0xFFFFFFFF}, # prop 21, 22 (callback flags) are not set by user 'remove_cost_multiplier' : {'size': 4, 'num': 0x23}, 'nearby_station_name' : {'size': 2, 'num': 0x24, 'string': 0xDC}, } # # Feature 0x0B (Cargos) # properties[0x0B] = { 'number' : {'num' : 0x08, 'size' : 1}, 'type_name' : {'num' : 0x09, 'size' : 2, 'string' : 0xDC}, 'unit_name' : {'num' : 0x0A, 'size' : 2, 'string' : 0xDC}, # Properties 0B, 0C are not used by OpenTTD 'type_abbreviation' : {'num' : 0x0D, 'size' : 2, 'string' : 0xDC}, 'sprite' : {'num' : 0x0E, 'size' : 2}, 'weight' : {'num' : 0x0F, 'size' : 1, 'unit_type' : 'weight', 'unit_conversion' : 16}, 'penalty_lowerbound' : {'num' : 0x10, 'size' : 1}, 'single_penalty_length' : {'num' : 0x11, 'size' : 1}, 'price_factor' : {'num' : 0x12, 'size' : 4, 'unit_conversion' : (1 << 21, 10 * 20 * 255)}, # 10 units of cargo across 20 tiles, with time factor = 255 'station_list_colour' : {'num' : 0x13, 'size' : 1}, 'cargo_payment_list_colour' : {'num' : 0x14, 'size' : 1}, 'is_freight' : {'num' : 0x15, 'size' : 1}, 'cargo_classes' : {'num' : 0x16, 'size' : 2}, 'cargo_label' : {'num' : 0x17, 'size' : 4, 'string_literal': 4}, 'town_growth_effect' : {'num' : 0x18, 'size' : 1}, 'town_growth_multiplier' : {'num' : 0x19, 'size' : 2, 'unit_conversion' : 0x100}, # 1A (callback flags) is not set by user 'units_of_cargo' : {'num' : 0x1B, 'size' : 2, 'string' : 0xDC}, 'items_of_cargo' : {'num' : 0x1C, 'size' : 2, 'string' : 0xDC}, 'capacity_multiplier' : {'num' : 0x1D, 'size' : 2, 'unit_conversion' : 0x100}, } # Feature 0x0C (Sound Effects) is implemented differently # # Feature 0x0D (Airports) # def airport_years(value): if not isinstance(value, Array) or len(value.values) != 2: raise generic.ScriptError("Availability years must be an array with exactly two values", value.pos) min_year = value.values[0].reduce_constant() max_year = value.values[1].reduce_constant() return [Action0Property(0x0C, ConstantNumeric(max_year.value << 16 | min_year.value), 4)] class AirportLayoutProp(BaseAction0Property): def __init__(self, layout_list): self.layout_list = layout_list def write(self, file): file.print_bytex(0x0A) file.print_byte(len(self.layout_list)) # -6 because prop_num, num_layouts and size should not be included file.print_dword(self.get_size() - 6) file.newline() for layout in self.layout_list: file.print_bytex(layout.properties['rotation'].value) layout.write(file) file.newline() def get_size(self): size = 6 for layout in self.layout_list: size += layout.get_size() + 1 return size def airport_layouts(value): if not isinstance(value, Array) or not all(isinstance(x, Identifier) for x in value.values): raise generic.ScriptError("layouts must be an array of layout names", value.pos) layouts = [] for name in value.values: if name.value not in tilelayout_names: raise generic.ScriptError("Unknown layout name '{}'".format(name.value), name.pos) layout = tilelayout_names[name.value] if 'rotation' not in layout.properties: raise generic.ScriptError("Airport layouts must have the 'rotation' property", layout.pos) if layout.properties['rotation'].value not in (0, 2, 4, 6): raise generic.ScriptError("Airport layout rotation is not a valid direction.", layout.properties['rotation'].pos) layouts.append(layout) return [AirportLayoutProp(layouts)] properties[0x0D] = { 'override' : {'size': 1, 'num': 0x08, 'first':None}, # 09 does not exist 'layouts' : {'custom_function': airport_layouts}, # = prop 0A # 0B does not exist 'years_available' : {'custom_function': airport_years}, # = prop 0C 'ttd_airport_type' : {'size': 1, 'num': 0x0D}, 'catchment_area' : {'size': 1, 'num': 0x0E}, 'noise_level' : {'size': 1, 'num': 0x0F}, 'name' : {'size': 2, 'num': 0x10, 'string': 0xDC}, 'maintenance_cost' : {'size': 2, 'num': 0x11}, } # Feature 0x0E (Signals) doesn't currently have any action0 # # Feature 0x0F (Objects) # def object_size(value): if not isinstance(value, Array) or len(value.values) != 2: raise generic.ScriptError("Object size must be an array with exactly two values", value.pos) sizex = value.values[0].reduce_constant() sizey = value.values[1].reduce_constant() if sizex.value < 1 or sizex.value > 15 or sizey.value < 1 or sizey.value > 15: raise generic.ScriptError("The size of an object must be at least 1x1 and at most 15x15 tiles", value.pos) return [Action0Property(0x0C, ConstantNumeric(sizey.value << 4 | sizex.value), 1)] properties[0x0F] = { 'class' : {'size': 4, 'num': 0x08, 'first': None, 'string_literal': 4}, # strings might be according to specs be either 0xD0 or 0xD4 'classname' : {'size': 2, 'num': 0x09, 'string': 0xD0}, 'name' : {'size': 2, 'num': 0x0A, 'string': 0xD0}, 'climates_available' : {'size': 1, 'num': 0x0B}, 'size' : {'custom_function': object_size}, # = prop 0C 'build_cost_multiplier' : {'size': 1, 'num': 0x0D}, 'introduction_date' : {'size': 4, 'num': 0x0E}, 'end_of_life_date' : {'size': 4, 'num': 0x0F}, 'object_flags' : {'size': 2, 'num': 0x10}, 'animation_info' : {'size': 2, 'num': 0x11, 'value_function': animation_info}, 'animation_speed' : {'size': 1, 'num': 0x12}, 'animation_triggers' : {'size': 2, 'num': 0x13}, 'remove_cost_multiplier' : {'size': 1, 'num': 0x14}, # 15 (callback flags) is not set by user 'height' : {'size': 1, 'num': 0x16}, 'num_views' : {'size': 1, 'num': 0x17}, 'count_per_map256' : {'size': 1, 'num': 0x18}, } # # Feature 0x10 (Rail Types) # class RailtypeListProp(BaseAction0Property): def __init__(self, prop_num, railtype_list): self.prop_num = prop_num self.railtype_list = railtype_list def write(self, file): file.print_bytex(self.prop_num) file.print_byte(len(self.railtype_list)) for railtype in self.railtype_list: railtype.write(file, 4) file.newline() def get_size(self): return len(self.railtype_list) * 4 + 2 def railtype_list(value, prop_num): if not isinstance(value, Array): raise generic.ScriptError("Railtype list must be an array of literal strings", value.pos) for val in value.values: if not isinstance(val, StringLiteral): raise generic.ScriptError("Railtype list must be an array of literal strings", val.pos) return [RailtypeListProp(prop_num, value.values)] properties[0x10] = { 'label' : {'size': 4, 'num': 0x08, 'string_literal': 4}, # is allocated during reservation stage, setting label first is thus not needed 'toolbar_caption' : {'size': 2, 'num': 0x09, 'string': 0xDC}, 'menu_text' : {'size': 2, 'num': 0x0A, 'string': 0xDC}, 'build_window_caption' : {'size': 2, 'num': 0x0B, 'string': 0xDC}, 'autoreplace_text' : {'size': 2, 'num': 0x0C, 'string': 0xDC}, 'new_engine_text' : {'size': 2, 'num': 0x0D, 'string': 0xDC}, 'compatible_railtype_list' : {'custom_function': lambda x: railtype_list(x, 0x0E)}, 'powered_railtype_list' : {'custom_function': lambda x: railtype_list(x, 0x0F)}, 'railtype_flags' : {'size': 1, 'num': 0x10}, 'curve_speed_multiplier' : {'size': 1, 'num': 0x11}, 'station_graphics' : {'size': 1, 'num': 0x12}, 'construction_cost' : {'size': 2, 'num': 0x13}, 'speed_limit' : {'size': 2, 'num': 0x14, 'unit_type': 'speed', 'unit_conversion': (5000, 1397)}, 'acceleration_model' : {'size': 1, 'num': 0x15}, 'map_colour' : {'size': 1, 'num': 0x16}, 'introduction_date' : {'size': 4, 'num': 0x17}, 'requires_railtype_list' : {'custom_function': lambda x: railtype_list(x, 0x18)}, 'introduces_railtype_list' : {'custom_function': lambda x: railtype_list(x, 0x19)}, 'sort_order' : {'size': 1, 'num': 0x1A}, 'name' : {'size': 2, 'num': 0x1B, 'string': 0xDC}, 'maintenance_cost' : {'size': 2, 'num': 0x1C}, 'alternative_railtype_list': {'custom_function': lambda x: railtype_list(x, 0x1D)}, } # # Feature 0x11 (Airport Tiles) # properties[0x11] = { 'substitute' : {'size': 1, 'num': 0x08, 'first': None}, 'override' : {'size': 1, 'num': 0x09}, # 0A - 0D don't exist (yet?) # 0E (callback flags) is not set by user 'animation_info' : {'size': 2, 'num': 0x0F, 'value_function': animation_info}, 'animation_speed' : {'size': 1, 'num': 0x10}, 'animation_triggers' : {'size': 1, 'num': 0x11}, } nml-0.4.4/nml/actions/action7.py0000644000567200056720000002760312643457545017642 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, nmlop, free_number_list, generic from nml.actions import base_action, action6, actionD, action10 free_labels = free_number_list.FreeNumberList(list(range(0xFF, 0x0F, -1)), "No label available to use for large if-blocks and loops.", "No unique label available to use for large if-blocks and loops.") def print_stats(): """ Print statistics about used ids. """ if free_labels.stats[0] > 0: generic.print_info("Concurrent Action10 labels: {}/{} ({})".format(free_labels.stats[0], free_labels.total_amount, str(free_labels.stats[1]))) class SkipAction(base_action.BaseAction): def __init__(self, action_type, var, varsize, condtype, value, label): self.action_type = action_type self.label = label self.var = var self.varsize = varsize self.condtype = condtype self.value = value self.label = label if self.condtype[0] == 0 or self.condtype[0] == 1: assert self.varsize == 1 def write(self, file): size = 5 + self.varsize file.start_sprite(size) file.print_bytex(self.action_type) file.print_bytex(self.var) file.print_bytex(self.varsize) file.print_bytex(self.condtype[0], self.condtype[1]) if self.varsize == 8: #grfid + mask file.print_dwordx(self.value & 0xFFFFFFFF) file.print_dwordx(self.value >> 32) else: file.print_varx(self.value, self.varsize) file.print_bytex(self.label) file.newline() file.end_sprite() def skip_action7(self): return self.action_type == 7 def skip_action9(self): return self.action_type == 9 or self.label == 0 class UnconditionalSkipAction(SkipAction): def __init__(self, action_type, label): SkipAction.__init__(self, action_type, 0x9A, 1, (0, r'\71'), 0, label) def op_to_cond_op(op): #The operators are reversed as we want to skip if the expression is true #while the nml-syntax wants to execute the block if the expression is true if op == nmlop.CMP_NEQ: return (2, r'\7=') if op == nmlop.CMP_EQ: return (3, r'\7!') if op == nmlop.CMP_GE: return (4, r'\7<') if op == nmlop.CMP_LE: return (5, r'\7>') def parse_conditional(expr): ''' Parse an expression and return enough information to use that expression as a conditional statement. Return value is a tuple with the following elements: - Parameter number (as integer) to use in comparison or None for unconditional skip - List of actions needed to set the given parameter to the correct value - The type of comparison to be done - The value to compare against (as integer) - The size of the value (as integer) ''' if expr is None: return (None, [], (2, r'\7='), 0, 4) if isinstance(expr, expression.BinOp): if expr.op == nmlop.HASBIT or expr.op == nmlop.NOTHASBIT: if isinstance(expr.expr1, expression.Parameter) and isinstance(expr.expr1.num, expression.ConstantNumeric): param = expr.expr1.num.value actions = [] else: param, actions = actionD.get_tmp_parameter(expr.expr1) if isinstance(expr.expr2, expression.ConstantNumeric): bit_num = expr.expr2.value else: if isinstance(expr.expr2, expression.Parameter) and isinstance(expr.expr2.num, expression.ConstantNumeric): param = expr.expr2.num.value else: param, tmp_action_list = actionD.get_tmp_parameter(expr.expr2) actions.extend(tmp_action_list) act6 = action6.Action6() act6.modify_bytes(param, 1, 4) actions.append(act6) bit_num = 0 comp_type = (1, r'\70') if expr.op == nmlop.HASBIT else (0, r'\71') return (param, actions, comp_type, bit_num , 1) elif expr.op in (nmlop.CMP_EQ, nmlop.CMP_NEQ, nmlop.CMP_LE, nmlop.CMP_GE) \ and isinstance(expr.expr2, expression.ConstantNumeric): if isinstance(expr.expr1, expression.Parameter) and isinstance(expr.expr1.num, expression.ConstantNumeric): param = expr.expr1.num.value actions = [] else: param, actions = actionD.get_tmp_parameter(expr.expr1) op = op_to_cond_op(expr.op) return (param, actions, op, expr.expr2.value, 4) if isinstance(expr, expression.Boolean): expr = expr.expr if isinstance(expr, expression.Not): param, actions = actionD.get_tmp_parameter(expr.expr) return (param, actions, (3, r'\7!'), 0, 4) param, actions = actionD.get_tmp_parameter(expr) return (param, actions, (2, r'\7='), 0, 4) def cond_skip_actions(action_list, param, condtype, value, value_size, pos): if len(action_list) == 0: return [] actions = [] start, length = 0, 0 # Whether to allow not-skipping, using action7 or using action9 skip_opts = (True, True, True) # Add a sentinel value to the list to avoid code duplication for action in action_list + [None]: assert any(skip_opts) if action is not None: # Options allowed by the next action act_opts = (not action.skip_needed(), action.skip_action7(), action.skip_action9()) else: # There are no further actions, so be sure to finish the current block act_opts = (False, False, False) # Options still allowed, when including this action in the previous block new_opts = tuple(all(tpl) for tpl in zip(skip_opts, act_opts)) # End this block in two cases: # - There are no options to skip this action and the preceding block in one go # - The existing block (with length > 0) needed no skipping, but this action does if any(new_opts) and not (length > 0 and skip_opts[0] and not new_opts[0]): # Append this action to the existing block length += 1 skip_opts = new_opts continue # We need to create a new block if skip_opts[0]: # We can just choose to not skip the preceeding actions without harm actions.extend(action_list[start:start+length]) else: action_type = 7 if skip_opts[1] else 9 if length < 0x10: # Lengths under 0x10 are handled without labels, to avoid excessive label usage target = length label = None else: target = free_labels.pop(pos) label = action10.Action10(target) actions.append(SkipAction(action_type, param, value_size, condtype, value, target)) actions.extend(action_list[start:start+length]) if label is not None: actions.append(label) start = start + length length = 1 skip_opts = act_opts assert start == len(action_list) return actions recursive_cond_blocks = 0 def parse_conditional_block(cond_list): global recursive_cond_blocks recursive_cond_blocks += 1 if recursive_cond_blocks == 1: # We only save a single state (at toplevel nml-blocks) because # we don't know at the start of the block how many labels we need. # Getting the same label for a block that was already used in a # sub-block would be very bad, since the action7/9 would skip # to the action10 of the sub-block. free_labels.save() blocks = [] for cond in cond_list.statements: if isinstance(cond.expr, expression.ConstantNumeric): if cond.expr.value == 0: continue else: blocks.append({'expr': None, 'statements': cond.statements}) break blocks.append({'expr': cond.expr, 'statements': cond.statements}) if blocks: blocks[-1]['last_block'] = True if len(blocks) == 1 and blocks[0]['expr'] is None: action_list = [] for stmt in blocks[0]['statements']: action_list.extend(stmt.get_action_list()) return action_list action6.free_parameters.save() if len(blocks) > 1: # the skip all parameter is used to skip all blocks after one # of the conditionals was true. We can't always skip directly # to the end of the blocks since action7/action9 can't always # be mixed param_skip_all, action_list = actionD.get_tmp_parameter(expression.ConstantNumeric(0xFFFFFFFF)) else: action_list = [] # use parse_conditional here, we also need to know if all generated # actions (like action6) can be skipped safely for block in blocks: block['param_dst'], block['cond_actions'], block['cond_type'], block['cond_value'], block['cond_value_size'] = parse_conditional(block['expr']) if not 'last_block' in block: block['action_list'] = [actionD.ActionD(expression.ConstantNumeric(param_skip_all), expression.ConstantNumeric(0xFF), nmlop.ASSIGN, expression.ConstantNumeric(0), expression.ConstantNumeric(0))] else: block['action_list'] = [] for stmt in block['statements']: block['action_list'].extend(stmt.get_action_list()) # Main problem: action10 can't be skipped by action9, so we're # nearly forced to use action7, but action7 can't safely skip action6 # Solution: use temporary parameter, set to 0 for not skip, !=0 for skip. # then skip every block of actions (as large as possible) with either # action7 or action9, depending on which of the two works. for i, block in enumerate(blocks): param = block['param_dst'] if i == 0: action_list.extend(block['cond_actions']) else: action_list.extend(cond_skip_actions(block['cond_actions'], param_skip_all, (2, r'\7='), 0, 4, cond_list.pos)) if param is None: param = param_skip_all else: action_list.append(actionD.ActionD(expression.ConstantNumeric(block['param_dst']), expression.ConstantNumeric(block['param_dst']), nmlop.AND, expression.ConstantNumeric(param_skip_all))) action_list.extend(cond_skip_actions(block['action_list'], param, block['cond_type'], block['cond_value'], block['cond_value_size'], cond_list.pos)) if recursive_cond_blocks == 1: free_labels.restore() recursive_cond_blocks -= 1 action6.free_parameters.restore() return action_list def parse_loop_block(loop): global recursive_cond_blocks recursive_cond_blocks += 1 if recursive_cond_blocks == 1: free_labels.save() action6.free_parameters.save() begin_label = free_labels.pop_unique(loop.pos) action_list = [action10.Action10(begin_label)] cond_param, cond_actions, cond_type, cond_value, cond_value_size = parse_conditional(loop.expr) block_actions = [] for stmt in loop.statements: block_actions.extend(stmt.get_action_list()) action_list.extend(cond_actions) block_actions.append(UnconditionalSkipAction(9, begin_label)) action_list.extend(cond_skip_actions(block_actions, cond_param, cond_type, cond_value, cond_value_size, loop.pos)) if recursive_cond_blocks == 1: free_labels.restore() recursive_cond_blocks -= 1 action6.free_parameters.restore() return action_list nml-0.4.4/nml/actions/action8.py0000644000567200056720000000267012643457545017640 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import grfstrings from nml.actions import base_action class Action8(base_action.BaseAction): def __init__(self, grfid, name, description): self.grfid = grfid self.name = name self.description = description def write(self, file): name = grfstrings.get_translation(self.name) desc = grfstrings.get_translation(self.description) size = 6 + grfstrings.get_string_size(name) + grfstrings.get_string_size(desc) file.start_sprite(size) file.print_bytex(8) # Write the grf version file.print_bytex(8) file.print_string(self.grfid.value, False, True) file.print_string(name) file.print_string(desc) file.end_sprite() def skip_action7(self): return False nml-0.4.4/nml/actions/action3_callbacks.py0000644000567200056720000004107312643457545021632 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, nmlop callbacks = 0x12 * [{}] # Possible values for 'purchase': # 0 (or not set): not called from purchase list # 1: called normally and from purchase list # 2: only called from purchase list # 'cbname': as 1) but if 'cbname' is set also, then 'cbname' overrides this # in the purchase list. 'cbname' should have a value of 2 for 'purchase' # Callbacks common to all vehicle types general_vehicle_cbs = { 'default' : {'type': 'cargo', 'num': None}, 'purchase' : {'type': 'cargo', 'num': 0xFF}, 'random_trigger' : {'type': 'cb', 'num': 0x01}, # Almost undocumented, but really neccesary! 'loading_speed' : {'type': 'cb', 'num': 0x36, 'var10': 0x07}, 'cargo_subtype_text' : {'type': 'cb', 'num': 0x19, 'flag_bit': 5}, 'additional_text' : {'type': 'cb', 'num': 0x23, 'purchase': 2}, 'colour_mapping' : {'type': 'cb', 'num': 0x2D, 'flag_bit':6, 'purchase': 'purchase_colour_mapping'}, 'purchase_colour_mapping' : {'type': 'cb', 'num': 0x2D, 'flag_bit':6, 'purchase': 2}, 'start_stop' : {'type': 'cb', 'num': 0x31}, 'every_32_days' : {'type': 'cb', 'num': 0x32}, 'sound_effect' : {'type': 'cb', 'num': 0x33, 'flag_bit': 7}, 'refit_cost' : {'type': 'cb', 'num': 0x15E, 'purchase': 1}, } # Function to convert vehicle length to the actual property value, which is (8 - length) def vehicle_length(value): return expression.BinOp(nmlop.SUB, expression.ConstantNumeric(8, value.pos), value, value.pos) # Trains callbacks[0x00] = { 'visual_effect_and_powered' : {'type': 'cb', 'num': 0x10, 'flag_bit': 0}, 'effect_spawn_model_and_powered' : {'type': 'cb', 'num': 0x10, 'flag_bit': 0}, 'length' : {'type': 'cb', 'num': 0x36, 'var10': 0x21, 'value_function': vehicle_length}, 'cargo_capacity' : [ {'type': 'cb', 'num': 0x15, 'flag_bit': 3}, {'type': 'cb', 'num': 0x36, 'var10': 0x14, 'purchase': 'purchase_cargo_capacity'}], 'purchase_cargo_capacity' : {'type': 'cb', 'num': 0x36, 'var10': 0x14, 'purchase': 2}, 'articulated_part' : {'type': 'cb', 'num': 0x16, 'flag_bit': 4, 'purchase': 1}, # Don't add separate purchase CB here 'can_attach_wagon' : {'type': 'cb', 'num': 0x1D}, 'speed' : {'type': 'cb', 'num': 0x36, 'var10': 0x09, 'purchase': 'purchase_speed'}, 'purchase_speed' : {'type': 'cb', 'num': 0x36, 'var10': 0x09, 'purchase': 2}, 'power' : {'type': 'cb', 'num': 0x36, 'var10': 0x0B, 'purchase': 'purchase_power'}, 'purchase_power' : {'type': 'cb', 'num': 0x36, 'var10': 0x0B, 'purchase': 2}, 'running_cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x0D, 'purchase': 'purchase_running_cost_factor'}, 'purchase_running_cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x0D, 'purchase': 2}, 'weight' : {'type': 'cb', 'num': 0x36, 'var10': 0x16, 'purchase': 'purchase_weight'}, 'purchase_weight' : {'type': 'cb', 'num': 0x36, 'var10': 0x16, 'purchase': 2}, 'cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x17, 'purchase': 2}, 'tractive_effort_coefficient' : {'type': 'cb', 'num': 0x36, 'var10': 0x1F, 'purchase': 'purchase_tractive_effort_coefficient'}, 'purchase_tractive_effort_coefficient' : {'type': 'cb', 'num': 0x36, 'var10': 0x1F, 'purchase': 2}, 'bitmask_vehicle_info' : {'type': 'cb', 'num': 0x36, 'var10': 0x25}, 'cargo_age_period' : {'type': 'cb', 'num': 0x36, 'var10': 0x2B}, 'create_effect' : {'type': 'cb', 'num': 0x160}, } callbacks[0x00].update(general_vehicle_cbs) # Road vehicles callbacks[0x01] = { 'visual_effect' : {'type': 'cb', 'num': 0x10, 'flag_bit': 0}, 'effect_spawn_model' : {'type': 'cb', 'num': 0x10, 'flag_bit': 0}, 'length' : {'type': 'cb', 'num': 0x36, 'var10': 0x23, 'value_function': vehicle_length}, 'cargo_capacity' : [ {'type': 'cb', 'num': 0x15, 'flag_bit': 3}, {'type': 'cb', 'num': 0x36, 'var10': 0x0F, 'purchase': 'purchase_cargo_capacity'}], 'purchase_cargo_capacity' : {'type': 'cb', 'num': 0x36, 'var10': 0x0F, 'purchase': 2}, 'articulated_part' : {'type': 'cb', 'num': 0x16, 'flag_bit': 4, 'purchase': 1}, # Don't add separate purchase CB here 'running_cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x09, 'purchase': 'purchase_running_cost_factor'}, 'purchase_running_cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x09, 'purchase': 2}, 'cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x11, 'purchase': 2}, 'power' : {'type': 'cb', 'num': 0x36, 'var10': 0x13, 'purchase': 'purchase_power'}, 'purchase_power' : {'type': 'cb', 'num': 0x36, 'var10': 0x13, 'purchase': 2}, 'weight' : {'type': 'cb', 'num': 0x36, 'var10': 0x14, 'purchase': 'purchase_weight'}, 'purchase_weight' : {'type': 'cb', 'num': 0x36, 'var10': 0x14, 'purchase': 2}, 'speed' : {'type': 'cb', 'num': 0x36, 'var10': 0x15, 'purchase': 'purchase_speed'}, 'purchase_speed' : {'type': 'cb', 'num': 0x36, 'var10': 0x15, 'purchase': 2}, 'tractive_effort_coefficient' : {'type': 'cb', 'num': 0x36, 'var10': 0x18, 'purchase': 'purchase_tractive_effort_coefficient'}, 'purchase_tractive_effort_coefficient' : {'type': 'cb', 'num': 0x36, 'var10': 0x18, 'purchase': 2}, 'cargo_age_period' : {'type': 'cb', 'num': 0x36, 'var10': 0x22}, 'create_effect' : {'type': 'cb', 'num': 0x160}, } callbacks[0x01].update(general_vehicle_cbs) # Ships callbacks[0x02] = { 'visual_effect' : {'type': 'cb', 'num': 0x10, 'flag_bit': 0}, 'effect_spawn_model' : {'type': 'cb', 'num': 0x10, 'flag_bit': 0}, 'cargo_capacity' : [ {'type': 'cb', 'num': 0x15, 'flag_bit': 3}, {'type': 'cb', 'num': 0x36, 'var10': 0x0D, 'purchase': 'purchase_cargo_capacity'}], 'purchase_cargo_capacity' : {'type': 'cb', 'num': 0x36, 'var10': 0x0D, 'purchase': 2}, 'cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x0A, 'purchase': 2}, 'speed' : {'type': 'cb', 'num': 0x36, 'var10': 0x0B, 'purchase': 'purchase_speed'}, 'purchase_speed' : {'type': 'cb', 'num': 0x36, 'var10': 0x0B, 'purchase': 2}, 'running_cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x0F, 'purchase': 'purchase_running_cost_factor'}, 'purchase_running_cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x0F, 'purchase': 2}, 'cargo_age_period' : {'type': 'cb', 'num': 0x36, 'var10': 0x1D}, 'create_effect' : {'type': 'cb', 'num': 0x160}, } callbacks[0x02].update(general_vehicle_cbs) # Aircraft callbacks[0x03] = { 'passenger_capacity' : [ {'type': 'cb', 'num': 0x15, 'flag_bit': 3}, {'type': 'cb', 'num': 0x36, 'var10': 0x0F, 'purchase': 'purchase_passenger_capacity'}], 'purchase_passenger_capacity' : {'type': 'cb', 'num': 0x36, 'var10': 0x0F, 'purchase': 2}, 'cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x0B, 'purchase': 2}, 'speed' : {'type': 'cb', 'num': 0x36, 'var10': 0x0C, 'purchase': 'purchase_speed'}, 'purchase_speed' : {'type': 'cb', 'num': 0x36, 'var10': 0x0C, 'purchase': 2}, 'running_cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x0E, 'purchase': 'purchase_running_cost_factor'}, 'purchase_running_cost_factor' : {'type': 'cb', 'num': 0x36, 'var10': 0x0E, 'purchase': 2}, 'mail_capacity' : {'type': 'cb', 'num': 0x36, 'var10': 0x11, 'purchase': 'purchase_mail_capacity'}, 'purchase_mail_capacity' : {'type': 'cb', 'num': 0x36, 'var10': 0x11, 'purchase': 2}, 'cargo_age_period' : {'type': 'cb', 'num': 0x36, 'var10': 0x1C}, 'range' : {'type': 'cb', 'num': 0x36, 'var10': 0x1F, 'purchase': 'purchase_range'}, 'purchase_range' : {'type': 'cb', 'num': 0x36, 'var10': 0x1F, 'purchase': 2}, 'rotor' : {'type': 'override'}, } callbacks[0x03].update(general_vehicle_cbs) # Stations (0x04) are not yet fully implemented callbacks[0x04] = { 'default' : {'type': 'cargo', 'num': None}, } # Canals callbacks[0x05] = { 'sprite_offset' : {'type': 'cb', 'num': 0x147, 'flag_bit': 0}, 'default' : {'type': 'cargo', 'num': None}, } # Bridges (0x06) have no action3 # Houses callbacks[0x07] = { 'random_trigger' : {'type': 'cb', 'num': 0x01}, 'construction_check' : {'type': 'cb', 'num': 0x17, 'flag_bit': 0, 'tiles': 'n'}, 'anim_next_frame' : {'type': 'cb', 'num': 0x1A, 'flag_bit': 1}, 'anim_control' : {'type': 'cb', 'num': 0x1B, 'flag_bit': 2}, 'construction_anim' : {'type': 'cb', 'num': 0x1C, 'flag_bit': 3}, 'colour' : {'type': 'cb', 'num': 0x1E, 'flag_bit': 4}, 'cargo_amount_accept' : {'type': 'cb', 'num': 0x1F, 'flag_bit': 5}, 'anim_speed' : {'type': 'cb', 'num': 0x20, 'flag_bit': 6}, 'destruction' : {'type': 'cb', 'num': 0x21, 'flag_bit': 7, 'tiles': 'n'}, 'cargo_type_accept' : {'type': 'cb', 'num': 0x2A, 'flag_bit': 8}, 'cargo_production' : {'type': 'cb', 'num': 0x2E, 'flag_bit': 9}, 'protection' : {'type': 'cb', 'num': 0x143, 'flag_bit': 10}, 'watched_cargo_accepted' : {'type': 'cb', 'num': 0x148}, 'name' : {'type': 'cb', 'num': 0x14D}, 'foundations' : {'type': 'cb', 'num': 0x14E, 'flag_bit': 11}, 'autoslope' : {'type': 'cb', 'num': 0x14F, 'flag_bit': 12}, 'graphics_north' : {'type': 'cb', 'num': 0x00, 'tiles': 'n'}, 'graphics_east' : {'type': 'cb', 'num': 0x00, 'tiles': 'e'}, 'graphics_south' : {'type': 'cb', 'num': 0x00, 'tiles': 's'}, 'graphics_west' : {'type': 'cb', 'num': 0x00, 'tiles': 'w'}, 'default' : {'type': 'cargo', 'num': None}, } # General variables (0x08) have no action3 # Industry tiles callbacks[0x09] = { 'random_trigger' : {'type': 'cb', 'num': 0x01}, 'anim_control' : {'type': 'cb', 'num': 0x25}, 'anim_next_frame' : {'type': 'cb', 'num': 0x26, 'flag_bit': 0}, 'anim_speed' : {'type': 'cb', 'num': 0x27, 'flag_bit': 1}, 'cargo_amount_accept' : {'type': 'cb', 'num': 0x2B, 'flag_bit': 2}, # Should work like the industry CB, i.e. call multiple times 'cargo_type_accept' : {'type': 'cb', 'num': 0x2C, 'flag_bit': 3}, # Should work like the industry CB, i.e. call multiple times 'tile_check' : {'type': 'cb', 'num': 0x2F, 'flag_bit': 4}, 'foundations' : {'type': 'cb', 'num': 0x30, 'flag_bit': 5}, 'autoslope' : {'type': 'cb', 'num': 0x3C, 'flag_bit': 6}, 'default' : {'type': 'cargo', 'num': None}, } # Industries callbacks[0x0A] = { 'construction_probability' : {'type': 'cb', 'num': 0x22, 'flag_bit': 0}, 'produce_cargo_arrival' : {'type': 'cb', 'num': 0x00, 'flag_bit': 1, 'var18': 0}, 'produce_256_ticks' : {'type': 'cb', 'num': 0x00, 'flag_bit': 2, 'var18': 1}, 'location_check' : {'type': 'cb', 'num': 0x28, 'flag_bit': 3}, # We need a way to access all those special variables 'random_prod_change' : {'type': 'cb', 'num': 0x29, 'flag_bit': 4}, 'monthly_prod_change' : {'type': 'cb', 'num': 0x35, 'flag_bit': 5}, 'cargo_subtype_display' : {'type': 'cb', 'num': 0x37, 'flag_bit': 6}, 'extra_text_fund' : {'type': 'cb', 'num': 0x38, 'flag_bit': 7}, 'extra_text_industry' : {'type': 'cb', 'num': 0x3A, 'flag_bit': 8}, 'control_special' : {'type': 'cb', 'num': 0x3B, 'flag_bit': 9}, 'stop_accept_cargo' : {'type': 'cb', 'num': 0x3D, 'flag_bit': 10}, 'colour' : {'type': 'cb', 'num': 0x14A, 'flag_bit': 11}, 'cargo_input' : {'type': 'cb', 'num': 0x14B, 'flag_bit': 12}, 'cargo_output' : {'type': 'cb', 'num': 0x14C, 'flag_bit': 13}, 'build_prod_change' : {'type': 'cb', 'num': 0x15F, 'flag_bit': 14}, 'default' : {'type': 'cargo', 'num': None}, } # Cargos def cargo_profit_value(value): # In NFO, calculation is (amount * price_factor * cb_result) / 8192 # Units of the NML price factor differ by about 41.12, i.e. 1 NML unit = 41 NFO units # That'd make the formula (amount * price_factor * cb_result) / (8192 / 41) # This is almost (error 0.01%) equivalent to the following, which is what this calculation does # (amount * price_factor * (cb_result * 329 / 256)) / 256 # This allows us to report a factor of 256 in the documentation, which makes a lot more sense than 199.804... # Not doing the division here would improve accuracy, but limits the range of the return value too much value = expression.BinOp(nmlop.MUL, value, expression.ConstantNumeric(329), value.pos) return expression.BinOp(nmlop.DIV, value, expression.ConstantNumeric(256), value.pos) callbacks[0x0B] = { 'profit' : {'type': 'cb', 'num': 0x39, 'flag_bit': 0, 'value_function': cargo_profit_value}, 'station_rating' : {'type': 'cb', 'num': 0x145, 'flag_bit': 1}, 'default' : {'type': 'cargo', 'num': None}, } # Sound effects (0x0C) have no item-specific action3 # Airports callbacks[0x0D] = { 'additional_text' : {'type': 'cb', 'num': 0x155}, 'layout_name' : {'type': 'cb', 'num': 0x156}, 'default' : {'type': 'cargo', 'num': None}, } # New signals (0x0E) have no item-specific action3 # Objects callbacks[0x0F] = { 'tile_check' : {'type': 'cb', 'num': 0x157, 'flag_bit': 0, 'purchase': 2}, 'anim_next_frame' : {'type': 'cb', 'num': 0x158, 'flag_bit': 1}, 'anim_control' : {'type': 'cb', 'num': 0x159}, 'anim_speed' : {'type': 'cb', 'num': 0x15A, 'flag_bit': 2}, 'colour' : {'type': 'cb', 'num': 0x15B, 'flag_bit': 3}, 'additional_text' : {'type': 'cb', 'num': 0x15C, 'flag_bit': 4, 'purchase': 2}, 'autoslope' : {'type': 'cb', 'num': 0x15D, 'flag_bit': 5}, 'default' : {'type': 'cargo', 'num': None}, 'purchase' : {'type': 'cargo', 'num': 0xFF}, } # Railtypes callbacks[0x10] = { # No default here, it makes no sense 'gui' : {'type': 'cargo', 'num': 0x00}, 'track_overlay' : {'type': 'cargo', 'num': 0x01}, 'underlay' : {'type': 'cargo', 'num': 0x02}, 'tunnels' : {'type': 'cargo', 'num': 0x03}, 'catenary_wire' : {'type': 'cargo', 'num': 0x04}, 'catenary_pylons' : {'type': 'cargo', 'num': 0x05}, 'bridge_surfaces' : {'type': 'cargo', 'num': 0x06}, 'level_crossings' : {'type': 'cargo', 'num': 0x07}, 'depots' : {'type': 'cargo', 'num': 0x08}, 'fences' : {'type': 'cargo', 'num': 0x09}, 'tunnel_overlay' : {'type': 'cargo', 'num': 0x0A}, 'signals' : {'type': 'cargo', 'num': 0x0B}, } # Airport tiles callbacks[0x11] = { 'foundations' : {'type': 'cb', 'num': 0x150, 'flag_bit': 5}, 'anim_control' : {'type': 'cb', 'num': 0x152}, 'anim_next_frame' : {'type': 'cb', 'num': 0x153, 'flag_bit': 0}, 'anim_speed' : {'type': 'cb', 'num': 0x154, 'flag_bit': 1}, 'default' : {'type': 'cargo', 'num': None}, } nml-0.4.4/nml/actions/action2layout.py0000644000567200056720000005557212643457545021101 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, expression, nmlop from nml.actions import action2, action6, actionD, action1, action2var, real_sprite from nml.ast import general class Action2Layout(action2.Action2): def __init__(self, feature, name, pos, ground_sprite, sprite_list, param_registers): action2.Action2.__init__(self, feature, name, pos) assert ground_sprite.type == Action2LayoutSpriteType.GROUND self.ground_sprite = ground_sprite self.sprite_list = sprite_list self.param_registers = param_registers 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 write(self, file): advanced = any(x.is_advanced_sprite() for x in self.sprite_list + [self.ground_sprite]) size = 5 if advanced: size += self.ground_sprite.get_registers_size() for sprite in self.sprite_list: if sprite.type == Action2LayoutSpriteType.CHILD: size += 7 else: size += 10 if advanced: size += sprite.get_registers_size() if len(self.sprite_list) == 0: size += 9 action2.Action2.write_sprite_start(self, file, size) if advanced: file.print_byte(0x40 | len(self.sprite_list)) else: file.print_byte(len(self.sprite_list)) self.ground_sprite.write_sprite_number(file) if advanced: self.ground_sprite.write_flags(file) self.ground_sprite.write_registers(file) file.newline() if len(self.sprite_list) == 0: file.print_dwordx(0) #sprite number 0 == no sprite for i in range(0, 5): file.print_byte(0) #empty bounding box. Note that number of zeros is 5, not 6 else: for sprite in self.sprite_list: sprite.write_sprite_number(file) if advanced: sprite.write_flags(file) file.print_byte(sprite.get_param('xoffset').value) file.print_byte(sprite.get_param('yoffset').value) if sprite.type == Action2LayoutSpriteType.CHILD: file.print_bytex(0x80) else: #normal building sprite file.print_byte(sprite.get_param('zoffset').value) file.print_byte(sprite.get_param('xextent').value) file.print_byte(sprite.get_param('yextent').value) file.print_byte(sprite.get_param('zextent').value) if advanced: sprite.write_registers(file) file.newline() file.end_sprite() class Action2LayoutSpriteType(object): GROUND = 0 BUILDING = 1 CHILD = 2 #these keywords are used to identify a ground/building/childsprite layout_sprite_types = { 'ground' : Action2LayoutSpriteType.GROUND, 'building' : Action2LayoutSpriteType.BUILDING, 'childsprite' : Action2LayoutSpriteType.CHILD, } class Action2LayoutSprite(object): def __init__(self, feature, type, pos = None, extra_dicts = []): self.feature = feature self.type = type self.pos = pos self.extra_dicts = extra_dicts self.params = { 'sprite' : {'value': None, 'validator': self._validate_sprite}, 'recolour_mode' : {'value': 0, 'validator': self._validate_recolour_mode}, 'palette' : {'value': expression.ConstantNumeric(0), 'validator': self._validate_palette}, 'always_draw' : {'value': 0, 'validator': self._validate_always_draw}, 'xoffset' : {'value': expression.ConstantNumeric(0), 'validator': self._validate_bounding_box}, 'yoffset' : {'value': expression.ConstantNumeric(0), 'validator': self._validate_bounding_box}, 'zoffset' : {'value': expression.ConstantNumeric(0), 'validator': self._validate_bounding_box}, 'xextent' : {'value': expression.ConstantNumeric(16), 'validator': self._validate_bounding_box}, 'yextent' : {'value': expression.ConstantNumeric(16), 'validator': self._validate_bounding_box}, 'zextent' : {'value': expression.ConstantNumeric(16), 'validator': self._validate_bounding_box}, 'hide_sprite' : {'value': None, 'validator': self._validate_hide_sprite}, # Value not used } for i in self.params: self.params[i]['is_set'] = False self.params[i]['register'] = None self.sprite_from_action1 = False self.palette_from_action1 = False def is_advanced_sprite(self): if self.palette_from_action1: return True return len(self.get_all_registers()) != 0 def get_registers_size(self): # Number of registers to write size = len(self.get_all_registers()) # Add 2 for the flags size += 2 return size def write_flags(self, file): flags = 0 if self.get_register('hide_sprite') is not None: flags |= 1 << 0 if self.get_register('sprite') is not None: flags |= 1 << 1 if self.get_register('palette') is not None: flags |= 1 << 2 if self.palette_from_action1: flags |= 1 << 3 # for building sprites: bit 4 => xoffset+yoffset, bit 5 => zoffset (x and y always set totgether) # for child sprites: bit 4 => xoffset, bit 5 => yoffset if self.type == Action2LayoutSpriteType.BUILDING: assert (self.get_register('xoffset') is not None) == (self.get_register('yoffset') is not None) if self.get_register('xoffset') is not None: flags |= 1 << 4 nextreg = 'zoffset' if self.type == Action2LayoutSpriteType.BUILDING else 'yoffset' if self.get_register(nextreg) is not None: flags |= 1 << 5 file.print_wordx(flags) def write_register(self, file, name): register = self.get_register(name)[0] file.print_bytex(register.parameter) def write_registers(self, file): if self.is_set('hide_sprite'): self.write_register(file, 'hide_sprite') if self.get_register('sprite') is not None: self.write_register(file, 'sprite') if self.get_register('palette') is not None: self.write_register(file, 'palette') if self.get_register('xoffset') is not None: self.write_register(file, 'xoffset') if self.get_register('yoffset') is not None: self.write_register(file, 'yoffset') if self.get_register('zoffset') is not None: self.write_register(file, 'zoffset') def write_sprite_number(self, file): num = self.get_sprite_number() if isinstance(num, expression.ConstantNumeric): num.write(file, 4) else: file.print_dwordx(0) 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 add_sprite = False sprite = self.get_param('sprite') if isinstance(sprite, expression.ConstantNumeric): sprite_num |= sprite.value else: add_sprite = True add_palette = False palette = self.get_param('palette') if isinstance(palette, expression.ConstantNumeric): sprite_num |= palette.value << 16 else: add_palette = True expr = expression.ConstantNumeric(sprite_num, sprite.pos) if add_sprite: expr = expression.BinOp(nmlop.ADD, sprite, expr, sprite.pos) if add_palette: expr = expression.BinOp(nmlop.ADD, palette, expr, sprite.pos) return expr.reduce() def get_param(self, name): assert name in self.params return self.params[name]['value'] def is_set(self, name): assert name in self.params return self.params[name]['is_set'] def get_register(self, name): assert name in self.params return self.params[name]['register'] def get_all_registers(self): return [self.get_register(name) for name in sorted(self.params) if self.get_register(name) is not None] def create_register(self, name, value): if isinstance(value, expression.StorageOp) and value.name == "LOAD_TEMP" and isinstance(value.register, expression.ConstantNumeric): store_tmp = None load_tmp = action2var.VarAction2Var(0x7D, 0, 0xFFFFFFFF, value.register.value) else: store_tmp = action2var.VarAction2StoreTempVar() load_tmp = action2var.VarAction2LoadTempVar(store_tmp) self.params[name]['register'] = (load_tmp, store_tmp, value) def set_param(self, name, value): assert isinstance(name, expression.Identifier) assert isinstance(value, expression.Expression) name = name.value if not name in self.params: raise generic.ScriptError("Unknown sprite parameter '{}'".format(name), value.pos) if self.is_set(name): raise generic.ScriptError("Sprite parameter '{}' can be set only once per sprite.".format(name), value.pos) self.params[name]['value'] = self.params[name]['validator'](name, value) self.params[name]['is_set'] = True def resolve_spritegroup_ref(self, sg_ref): """ Resolve a reference to a (sprite/palette) sprite group @param sg_ref: Reference to a sprite group @type sg_ref: L{SpriteGroupRef} @return: Sprite number (index of action1 set) to use @rtype: L{Expression} """ spriteset = action2.resolve_spritegroup(sg_ref.name) if len(sg_ref.param_list) == 0: offset = None elif len(sg_ref.param_list) == 1: id_dicts = [(spriteset.labels, lambda val, pos: expression.ConstantNumeric(val, pos))] offset = action2var.reduce_varaction2_expr(sg_ref.param_list[0], self.feature, self.extra_dicts + id_dicts) if isinstance(offset, expression.ConstantNumeric): generic.check_range(offset.value, 0, len(real_sprite.parse_sprite_data(spriteset)) - 1, "offset within spriteset", sg_ref.pos) else: raise generic.ScriptError("Expected 0 or 1 parameter, got " + str(len(sg_ref.param_list)), sg_ref.pos) num = action1.get_action1_index(spriteset) generic.check_range(num, 0, (1 << 14) - 1, "sprite", sg_ref.pos) return expression.ConstantNumeric(num), offset def _validate_sprite(self, name, value): if isinstance(value, expression.SpriteGroupRef): self.sprite_from_action1 = True val, offset = self.resolve_spritegroup_ref(value) if offset is not None: self.create_register(name, offset) return val else: self.sprite_from_action1 = False if isinstance(value, expression.ConstantNumeric): generic.check_range(value.value, 0, (1 << 14) - 1, "sprite", value.pos) return value self.create_register(name, value) return expression.ConstantNumeric(0) def _validate_recolour_mode(self, name, value): if not isinstance(value, expression.ConstantNumeric): raise generic.ScriptError("Expected a compile-time constant.", value.pos) if not value.value in (0, 1, 2): raise generic.ScriptError("Value of 'recolour_mode' must be RECOLOUR_NONE, RECOLOUR_TRANSPARENT or RECOLOUR_REMAP.") return value.value def _validate_palette(self, name, value): if isinstance(value, expression.SpriteGroupRef): self.palette_from_action1 = True val, offset = self.resolve_spritegroup_ref(value) if offset is not None: self.create_register(name, offset) return val else: if isinstance(value, expression.ConstantNumeric): generic.check_range(value.value, 0, (1 << 14) - 1, "palette", value.pos) self.palette_from_action1 = False return value def _validate_always_draw(self, name, value): if not isinstance(value, expression.ConstantNumeric): raise generic.ScriptError("Expected a compile-time constant number.", value.pos) # Not valid for ground sprites, raise error if self.type == Action2LayoutSpriteType.GROUND: raise generic.ScriptError("'always_draw' may not be set for groundsprites, these are always drawn anyways.", value.pos) if value.value not in (0, 1): raise generic.ScriptError("Value of 'always_draw' should be 0 or 1", value.pos) return value.value def _validate_bounding_box(self, name, value): if self.type == Action2LayoutSpriteType.GROUND: raise generic.ScriptError(name + " can not be set for ground sprites", value.pos) elif self.type == Action2LayoutSpriteType.CHILD: if name not in ('xoffset', 'yoffset'): raise generic.ScriptError(name + " can not be set for child sprites", value.pos) if isinstance(value, expression.ConstantNumeric): generic.check_range(value.value, 0, 255, name, value.pos) return value else: assert self.type == Action2LayoutSpriteType.BUILDING if name in ('xoffset', 'yoffset', 'zoffset'): if isinstance(value, expression.ConstantNumeric): generic.check_range(value.value, -128, 127, name, value.pos) return value else: assert name in ('xextent', 'yextent', 'zextent') if not isinstance(value, expression.ConstantNumeric): raise generic.ScriptError("Value of '{}' must be a compile-time constant number.".format(name), value.pos) generic.check_range(value.value, 0, 255, name, value.pos) return value # Value must be written to a register self.create_register(name, value) if self.type == Action2LayoutSpriteType.BUILDING: # For building sprites, x and y registers are always written together if name == 'xoffset' and self.get_register('yoffset') is None: self.create_register('yoffset', expression.ConstantNumeric(0)) if name == 'yoffset' and self.get_register('xoffset') is None: self.create_register('xoffset', expression.ConstantNumeric(0)) return expression.ConstantNumeric(0) def _validate_hide_sprite(self, name, value): value = expression.Not(value) try: value = value.reduce() except: pass self.create_register(name, value) return None def get_layout_action2s(spritelayout, feature, spr_pos): """ @param spr_pos: Position information of the sprite view. @type spr_pos: L{Position} """ ground_sprite = None building_sprites = [] actions = [] if feature not in action2.features_sprite_layout: raise generic.ScriptError("Sprite layouts are not supported for feature '{}'.".format(general.feature_name(feature))) # Allocate registers param_map = {} param_registers = [] for i, param in enumerate(spritelayout.param_list): reg = action2var.VarAction2LayoutParam() param_registers.append(reg) param_map[param.value] = reg param_map = (param_map, lambda value, pos: action2var.VarAction2LoadLayoutParam(value)) spritelayout.register_map[feature] = param_registers # Reduce all expressions, can't do that earlier as feature is not known all_sprite_sets = [] layout_sprite_list = [] # Create a new structure for layout_sprite in spritelayout.layout_sprite_list: param_list = [] layout_sprite_list.append( (layout_sprite.type, layout_sprite.pos, param_list) ) for param in layout_sprite.param_list: param_val = action2var.reduce_varaction2_expr(param.value, feature, [param_map]) param_list.append( (param.name, param_val) ) if isinstance(param_val, expression.SpriteGroupRef): spriteset = action2.resolve_spritegroup(param_val.name) if not spriteset.is_spriteset(): raise generic.ScriptError("Expected a reference to a spriteset.", param_val.pos) all_sprite_sets.append(spriteset) actions.extend(action1.add_to_action1(all_sprite_sets, feature, spritelayout.pos)) temp_registers = [] for type, pos, param_list in layout_sprite_list: if type.value not in layout_sprite_types: raise generic.ScriptError("Invalid sprite type '{}' encountered. Expected 'ground', 'building', or 'childsprite'.".format(type.value), type.pos) sprite = Action2LayoutSprite(feature, layout_sprite_types[type.value], pos, [param_map]) for name, value in param_list: sprite.set_param(name, value) temp_registers.extend(sprite.get_all_registers()) if sprite.type == Action2LayoutSpriteType.GROUND: if ground_sprite is not None: raise generic.ScriptError("Sprite layout can have no more than one ground sprite", spritelayout.pos) ground_sprite = sprite else: building_sprites.append(sprite) if ground_sprite is None: if len(building_sprites) == 0: #no sprites defined at all, that's not very much. raise generic.ScriptError("Sprite layout requires at least one sprite", spr_pos) #set to 0 for no ground sprite ground_sprite = Action2LayoutSprite(feature, Action2LayoutSpriteType.GROUND) ground_sprite.set_param(expression.Identifier('sprite'), expression.ConstantNumeric(0)) action6.free_parameters.save() act6 = action6.Action6() offset = 4 sprite_num = ground_sprite.get_sprite_number() sprite_num, offset = actionD.write_action_value(sprite_num, actions, act6, offset, 4) offset += ground_sprite.get_registers_size() for sprite in building_sprites: sprite_num = sprite.get_sprite_number() sprite_num, offset = actionD.write_action_value(sprite_num, actions, act6, offset, 4) offset += sprite.get_registers_size() offset += 3 if sprite.type == Action2LayoutSpriteType.CHILD else 6 if len(act6.modifications) > 0: actions.append(act6) layout_action = Action2Layout(feature, spritelayout.name.value + " - feature {:02X}".format(feature), spritelayout.pos, ground_sprite, building_sprites, param_registers) actions.append(layout_action) if temp_registers: varact2parser = action2var.Varaction2Parser(feature) for register_info in temp_registers: reg, expr = register_info[1], register_info[2] if reg is None: continue varact2parser.parse_expr(expr) varact2parser.var_list.append(nmlop.STO_TMP) varact2parser.var_list.append(reg) varact2parser.var_list.append(nmlop.VAL2) varact2parser.var_list_size += reg.get_size() + 2 # Only continue if we actually needed any new registers if temp_registers and varact2parser.var_list: #Remove the last VAL2 operator varact2parser.var_list.pop() varact2parser.var_list_size -= 1 actions.extend(varact2parser.extra_actions) extra_act6 = action6.Action6() for mod in varact2parser.mods: extra_act6.modify_bytes(mod.param, mod.size, mod.offset + 4) if len(extra_act6.modifications) > 0: actions.append(extra_act6) varaction2 = action2var.Action2Var(feature, "{}@registers - feature {:02X}".format(spritelayout.name.value, feature), spritelayout.pos, 0x89) varaction2.var_list = varact2parser.var_list ref = expression.SpriteGroupRef(spritelayout.name, [], None, layout_action) varaction2.ranges.append(action2var.VarAction2Range(expression.ConstantNumeric(0), expression.ConstantNumeric(0), ref, '')) varaction2.default_result = ref varaction2.default_comment = '' # Add two references (default + range) # Make sure that registers allocated here are not used by the spritelayout action2.add_ref(ref, varaction2, True) action2.add_ref(ref, varaction2, True) spritelayout.set_action2(varaction2, feature) actions.append(varaction2) else: spritelayout.set_action2(layout_action, feature) action6.free_parameters.restore() return actions def make_empty_layout_action2(feature, pos): """ Make an empty layout action2 For use with failed callbacks @param feature: Feature of the sprite layout to create @type feature: C{int} @param pos: Positional context. @type pos: L{Position} @return: The created sprite layout action2 @rtype: L{Action2Layout} """ ground_sprite = Action2LayoutSprite(feature, Action2LayoutSpriteType.GROUND) ground_sprite.set_param(expression.Identifier('sprite'), expression.ConstantNumeric(0)) return Action2Layout(feature, "@CB_FAILED_LAYOUT{:02X}".format(feature), pos, ground_sprite, [], []) nml-0.4.4/nml/actions/action2var_variables.py0000644000567200056720000012613112643457545022372 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import expression, nmlop, generic # Use feature 0x12 for towns (accessible via station/house/industry parent scope) varact2vars = 0x13 * [{}] varact2vars60x = 0x13 * [{}] # feature number: 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12 varact2parent_scope = [0x00, 0x01, 0x02, 0x03, 0x12, None, 0x12, 0x12, None, 0x0A, 0x12, None, None, None, None, 0x12, None, None, None] def default_60xvar(name, args, pos, info): """ Function to convert arguments into a variable parameter. This function handles the default case of one argument. @param name: Name of the variable @type name: C{str} @param args: List of passed arguments @type args: C{list} of L{Expression} @param pos: Position information @type pos: L{Position} @param info: Information of the variable, as found in the dictionary @type info: C{dict} @return: A tuple of two values: - Parameter to use for the 60+x variable - List of possible extra parameters that need to be passed via registers @rtype: C{tuple} of (L{Expression}, C{list} C{tuple} of (C{int}, L{Expression})) """ if len(args) != 1: raise generic.ScriptError("'{}'() requires one argument, encountered {:d}".format(name, len(args)), pos) return (args[0], []) # Some commonly used functions that apply some modification to the raw variable value # To pass extra parameters, lambda calculus may be used def value_sign_extend(var, info): #r = (x ^ m) - m; with m being (1 << (num_bits -1)) m = expression.ConstantNumeric(1 << (info['size'] - 1)) return expression.BinOp(nmlop.SUB, expression.BinOp(nmlop.XOR, var, m, var.pos), m, var.pos) def value_mul_div(mul, div): return lambda var, info: expression.BinOp(nmlop.DIV, expression.BinOp(nmlop.MUL, var, expression.ConstantNumeric(mul, var.pos), var.pos), expression.ConstantNumeric(div, var.pos), var.pos) def value_add_constant(const): return lambda var, info: expression.BinOp(nmlop.ADD, var, expression.ConstantNumeric(const, var.pos), var.pos) def value_equals(const): return lambda var, info: expression.BinOp(nmlop.CMP_EQ, var, expression.ConstantNumeric(const, var.pos), var.pos) # Commonly used functions to let a variable accept an (x, y)-offset as parameters def tile_offset(name, args, pos, info, min, max): if len(args) != 2: raise generic.ScriptError("'{}'() requires 2 arguments, encountered {:d}".format(name, len(args)), pos) for arg in args: if isinstance(arg, expression.ConstantNumeric): generic.check_range(arg.value, min, max, "Argument of '{}'".format(name), arg.pos) x = expression.BinOp(nmlop.AND, args[0], expression.ConstantNumeric(0xF), args[0].pos) y = expression.BinOp(nmlop.AND, args[1], expression.ConstantNumeric(0xF), args[1].pos) # Shift y left by four y = expression.BinOp(nmlop.SHIFT_LEFT, y, expression.ConstantNumeric(4), y.pos) param = expression.BinOp(nmlop.ADD, x, y, x.pos) #Make sure to reduce the result return ( param.reduce(), [] ) def signed_tile_offset(name, args, pos, info): return tile_offset(name, args, pos, info, -8, 7) def unsigned_tile_offset(name, args, pos, info): return tile_offset(name, args, pos, info, 0, 15) # # Global variables, usable for all features # varact2_globalvars = { 'current_month' : {'var': 0x02, 'start': 0, 'size': 8}, 'current_day_of_month' : {'var': 0x02, 'start': 8, 'size': 5}, 'is_leapyear' : {'var': 0x02, 'start': 15, 'size': 1}, 'current_day_of_year' : {'var': 0x02, 'start': 16, 'size': 9}, 'traffic_side' : {'var': 0x06, 'start': 0, 'size': 8}, 'animation_counter' : {'var': 0x0A, 'start': 0, 'size': 16}, 'current_callback' : {'var': 0x0C, 'start': 0, 'size': 16}, 'extra_callback_info1' : {'var': 0x10, 'start': 0, 'size': 32}, 'game_mode' : {'var': 0x12, 'start': 0, 'size': 8}, 'extra_callback_info2' : {'var': 0x18, 'start': 0, 'size': 32}, 'display_options' : {'var': 0x1B, 'start': 0, 'size': 6}, 'last_computed_result' : {'var': 0x1C, 'start': 0, 'size': 32}, 'snowline_height' : {'var': 0x20, 'start': 0, 'size': 8}, 'difficulty_level' : {'var': 0x22, 'start': 0, 'size': 8}, 'current_date' : {'var': 0x23, 'start': 0, 'size': 32}, 'current_year' : {'var': 0x24, 'start': 0, 'size': 32}, } # # Vehicles (features 0x00 - 0x03) # A few variables have an implementation that differs per vehicle type # These are to be found below # varact2vars_vehicles = { 'grfid' : {'var': 0x25, 'start': 0, 'size': 32}, 'position_in_consist' : {'var': 0x40, 'start': 0, 'size': 8}, 'position_in_consist_from_end' : {'var': 0x40, 'start': 8, 'size': 8}, 'num_vehs_in_consist' : {'var': 0x40, 'start': 16, 'size': 8, 'value_function': value_add_constant(1)}, # Zero-based, add 1 to make sane 'position_in_vehid_chain' : {'var': 0x41, 'start': 0, 'size': 8}, 'position_in_vehid_chain_from_end' : {'var': 0x41, 'start': 8, 'size': 8}, 'num_vehs_in_vehid_chain' : {'var': 0x41, 'start': 16, 'size': 8}, # One-based, already sane 'cargo_classes_in_consist' : {'var': 0x42, 'start': 0, 'size': 8}, 'most_common_refit' : {'var': 0x42, 'start': 16, 'size': 8}, 'bitmask_consist_info' : {'var': 0x42, 'start': 24, 'size': 8}, 'company_num' : {'var': 0x43, 'start': 0, 'size': 8}, 'company_type' : {'var': 0x43, 'start': 16, 'size': 2}, 'company_colour1' : {'var': 0x43, 'start': 24, 'size': 4}, 'company_colour2' : {'var': 0x43, 'start': 28, 'size': 4}, 'aircraft_height' : {'var': 0x44, 'start': 8, 'size': 8}, 'airport_type' : {'var': 0x44, 'start': 0, 'size': 8}, 'curv_info_prev_cur' : {'var': 0x45, 'start': 0, 'size': 4, 'value_function': value_sign_extend}, 'curv_info_cur_next' : {'var': 0x45, 'start': 8, 'size': 4, 'value_function': value_sign_extend}, 'curv_info_prev_next' : {'var': 0x45, 'start': 16, 'size': 4, 'value_function': value_sign_extend}, 'curv_info' : {'var': 0x45, 'start': 0, 'size': 12, 'value_function': lambda var, info: expression.BinOp(nmlop.AND, var, expression.ConstantNumeric(0x0F0F, var.pos), var.pos).reduce()}, 'motion_counter' : {'var': 0x46, 'start': 8, 'size': 24}, 'cargo_type_in_veh' : {'var': 0x47, 'start': 0, 'size': 8}, 'cargo_unit_weight' : {'var': 0x47, 'start': 8, 'size': 8}, 'cargo_classes' : {'var': 0x47, 'start': 16, 'size': 16}, 'vehicle_is_available' : {'var': 0x48, 'start': 0, 'size': 1}, 'vehicle_is_testing' : {'var': 0x48, 'start': 1, 'size': 1}, 'vehicle_is_offered' : {'var': 0x48, 'start': 2, 'size': 1}, 'build_year' : {'var': 0x49, 'start': 0, 'size': 32}, 'current_railtype' : {'var': 0x4A, 'start': 0, 'size': 8}, 'vehicle_is_potentially_powered' : {'var': 0x4A, 'start': 8, 'size': 1}, 'date_of_last_service' : {'var': 0x4B, 'start': 0, 'size': 32}, 'position_in_articulated_veh' : {'var': 0x4D, 'start': 0, 'size': 8}, 'position_in_articulated_veh_from_end' : {'var': 0x4D, 'start': 8, 'size': 8}, 'waiting_triggers' : {'var': 0x5F, 'start': 0, 'size': 8}, 'random_bits' : {'var': 0x5F, 'start': 8, 'size': 8}, 'direction' : {'var': 0x9F, 'start': 0, 'size': 8}, 'vehicle_is_hidden' : {'var': 0xB2, 'start': 0, 'size': 1}, 'vehicle_is_stopped' : {'var': 0xB2, 'start': 1, 'size': 1}, 'vehicle_is_crashed' : {'var': 0xB2, 'start': 7, 'size': 1}, 'cargo_capacity' : {'var': 0xBA, 'start': 0, 'size': 16}, 'cargo_count' : {'var': 0xBC, 'start': 0, 'size': 16}, 'age_in_days' : {'var': 0xC0, 'start': 0, 'size': 16}, 'max_age_in_days' : {'var': 0xC2, 'start': 0, 'size': 16}, 'vehicle_type_id' : {'var': 0xC6, 'start': 0, 'size': 16}, 'vehicle_is_flipped' : {'var': 0xC8, 'start': 1, 'size': 1}, 'breakdowns_since_last_service' : {'var': 0xCA, 'start': 0, 'size': 8}, 'vehicle_is_broken' : {'var': 0xCB, 'start': 0, 'size': 8, 'value_function': value_equals(1)}, 'reliability' : {'var': 0xCE, 'start': 0, 'size': 16, 'value_function': value_mul_div(101, 0x10000)}, 'cargo_subtype' : {'var': 0xF2, 'start': 0, 'size': 8}, 'vehicle_is_unloading' : {'var': 0xFE, 'start': 1, 'size': 1}, 'vehicle_is_powered' : {'var': 0xFE, 'start': 5, 'size': 1}, 'vehicle_is_not_powered' : {'var': 0xFE, 'start': 6, 'size': 1}, 'vehicle_is_reversed' : {'var': 0xFE, 'start': 8, 'size': 1}, 'built_during_preview' : {'var': 0xFE, 'start': 10, 'size': 1}, } # Vehicle-type-specific variables varact2vars_trains = { #0x4786 / 0x10000 is an approximation of 3.5790976, the conversion factor #for train speed 'max_speed' : {'var': 0x98, 'start': 0, 'size': 16, 'value_function': value_mul_div(0x4786, 0x10000)}, 'current_speed' : {'var': 0xB4, 'start': 0, 'size': 16, 'value_function': value_mul_div(0x4786, 0x10000)}, 'current_max_speed' : {'var': 0x4C, 'start': 0, 'size': 16, 'value_function': value_mul_div(0x4786, 0x10000)}, 'vehicle_is_in_depot' : {'var': 0xE2, 'start': 7, 'size': 1}, } varact2vars_trains.update(varact2vars_vehicles) varact2vars_roadvehs = { #0x23C3 / 0x10000 is an approximation of 7.1581952, the conversion factor #for road vehicle speed 'max_speed' : {'var': 0x98, 'start': 0, 'size': 16, 'value_function': value_mul_div(0x23C3, 0x10000)}, 'current_speed' : {'var': 0xB4, 'start': 0, 'size': 16, 'value_function': value_mul_div(0x23C3, 0x10000)}, 'current_max_speed' : {'var': 0x4C, 'start': 0, 'size': 16, 'value_function': value_mul_div(0x23C3, 0x10000)}, 'vehicle_is_in_depot' : {'var': 0xE2, 'start': 0, 'size': 8, 'value_function': value_equals(0xFE)}, } varact2vars_roadvehs.update(varact2vars_vehicles) varact2vars_ships = { #0x23C3 / 0x10000 is an approximation of 7.1581952, the conversion factor #for ship speed 'max_speed' : {'var': 0x98, 'start': 0, 'size': 16, 'value_function': value_mul_div(0x23C3, 0x10000)}, 'current_speed' : {'var': 0xB4, 'start': 0, 'size': 16, 'value_function': value_mul_div(0x23C3, 0x10000)}, 'current_max_speed' : {'var': 0x4C, 'start': 0, 'size': 16, 'value_function': value_mul_div(0x23C3, 0x10000)}, 'vehicle_is_in_depot' : {'var': 0xE2, 'start': 7, 'size': 1}, } varact2vars_ships.update(varact2vars_vehicles) varact2vars_aircraft = { #0x3939 / 0x1000 is an approximation of 0.279617, the conversion factor #Note that the denominator has one less zero here! #for aircraft speed 'max_speed' : {'var': 0x98, 'start': 0, 'size': 16, 'value_function': value_mul_div(0x3939, 0x1000)}, 'current_speed' : {'var': 0xB4, 'start': 0, 'size': 16, 'value_function': value_mul_div(0x3939, 0x1000)}, 'current_max_speed' : {'var': 0x4C, 'start': 0, 'size': 16, 'value_function': value_mul_div(0x3939, 0x1000)}, 'vehicle_is_in_depot' : {'var': 0xE6, 'start': 0, 'size': 8, 'value_function': value_equals(0)}, } varact2vars_aircraft.update(varact2vars_vehicles) def signed_byte_parameter(name, args, pos, info): # Convert to a signed byte by AND-ing with 0xFF if len(args) != 1: raise generic.ScriptError("{}() requires one argument, encountered {:d}".format(name, len(args)), pos) if isinstance(args[0], expression.ConstantNumeric): generic.check_range(args[0].value, -128, 127, "parameter of {}()".format(name), pos) ret = expression.BinOp(nmlop.AND, args[0], expression.ConstantNumeric(0xFF, pos), pos).reduce() return (ret, []) varact2vars60x_vehicles = { 'count_veh_id' : {'var': 0x60, 'start': 0, 'size': 8}, 'other_veh_curv_info' : {'var': 0x62, 'start': 0, 'size': 4, 'param_function':signed_byte_parameter, 'value_function':value_sign_extend}, 'other_veh_is_hidden' : {'var': 0x62, 'start': 7, 'size': 1, 'param_function':signed_byte_parameter}, 'other_veh_x_offset' : {'var': 0x62, 'start': 8, 'size': 8, 'param_function':signed_byte_parameter, 'value_function':value_sign_extend}, 'other_veh_y_offset' : {'var': 0x62, 'start': 16, 'size': 8, 'param_function':signed_byte_parameter, 'value_function':value_sign_extend}, 'other_veh_z_offset' : {'var': 0x62, 'start': 24, 'size': 8, 'param_function':signed_byte_parameter, 'value_function':value_sign_extend}, } # # Stations (feature 0x04) # # 'Base station' variables are shared between stations and airports varact2vars_base_stations = { # Var 48 doesn't work with newcargos, do not use 'had_vehicle_of_type' : {'var': 0x8A, 'start': 1, 'size': 5}, # Only read bits 1-5 'is_waypoint' : {'var': 0x8A, 'start': 6, 'size': 1}, 'facilities' : {'var': 0xF0, 'start': 0, 'size': 8}, 'airport_type' : {'var': 0xF1, 'start': 0, 'size': 8}, # Variables F2, F3, F6 (roadstop, airport flags) are next to useless # Also, their values are not the same as in TTDP / spec # Therefore, these are not implemented 'build_date' : {'var': 0xFA, 'start': 0, 'size': 16, 'value_function': value_add_constant(701265)} } varact2vars60x_base_stations = { 'cargo_amount_waiting' : {'var': 0x60, 'start': 0, 'size': 32}, 'cargo_time_since_pickup' : {'var': 0x61, 'start': 0, 'size': 32}, 'cargo_rating' : {'var': 0x62, 'start': 0, 'size': 32, 'value_function': value_mul_div(101, 256)}, 'cargo_time_en_route' : {'var': 0x63, 'start': 0, 'size': 32}, 'cargo_last_vehicle_speed' : {'var': 0x64, 'start': 0, 'size': 8}, 'cargo_last_vehicle_age' : {'var': 0x64, 'start': 8, 'size': 8}, 'cargo_accepted' : {'var': 0x65, 'start': 3, 'size': 1}, 'cargo_accepted_ever' : {'var': 0x69, 'start': 0, 'size': 1}, 'cargo_accepted_last_month' : {'var': 0x69, 'start': 1, 'size': 1}, 'cargo_accepted_this_month' : {'var': 0x69, 'start': 2, 'size': 1}, 'cargo_accepted_bigtick' : {'var': 0x69, 'start': 3, 'size': 1}, } varact2vars_stations = { # Vars 40, 41, 46, 47, 49 are implemented as 60+x vars, # except for the 'tile type' part which is always the same anyways 'tile_type' : {'var': 0x40, 'start': 24, 'size': 4}, 'terrain_type' : {'var': 0x42, 'start': 0, 'size': 8}, 'track_type' : {'var': 0x42, 'start': 8, 'size': 8}, 'company_num' : {'var': 0x43, 'start': 0, 'size': 8}, 'company_type' : {'var': 0x43, 'start': 16, 'size': 2}, 'company_colour1' : {'var': 0x43, 'start': 24, 'size': 4}, 'company_colour2' : {'var': 0x43, 'start': 28, 'size': 4}, 'pbs_reserved' : {'var': 0x44, 'start': 0, 'size': 1}, 'pbs_reserved_or_disabled' : {'var': 0x44, 'start': 1, 'size': 1}, 'pbs_enabled' : {'var': 0x44, 'start': 2, 'size': 1}, 'rail_continuation' : {'var': 0x45, 'start': 0, 'size': 8}, 'rail_present' : {'var': 0x45, 'start': 8, 'size': 8}, 'animation_frame' : {'var': 0x4A, 'start': 0, 'size': 8}, } varact2vars_stations.update(varact2vars_base_stations) # Mapping of param values for platform_xx vars to variable numbers mapping_platform_param = { (0, False) : 0x40, (1, False) : 0x41, (0, True) : 0x46, (1, True) : 0x47, (2, False) : 0x49, } def platform_info_param(name, args, pos, info): if len(args) != 1: raise generic.ScriptError("'{}'() requires one argument, encountered {:d}".format(name, len(args)), pos) if (not isinstance(args[0], expression.ConstantNumeric)) or args[0].value not in (0, 1, 2): raise generic.ScriptError("Invalid argument for '{}'(), must be one of PLATFORM_SAME_XXX.".format(name), pos) is_middle = 'middle' in info and info['middle'] if is_middle and args[0].value == 2: raise generic.ScriptError("Invalid argument for '{}'(), PLATFORM_SAME_DIRECTION is not supported here.".format(name), pos) # Temporarily store variable number in the param, this will be fixed in the value_function return (expression.ConstantNumeric(mapping_platform_param[(args[0].value, is_middle)]), []) def platform_info_fix_var(var, info): # Variable to use was temporarily stored in the param # Fix this now var.num = var.param var.param = None return var varact2vars60x_stations = { 'nearby_tile_animation_frame' : {'var': 0x66, 'start': 0, 'size': 32, 'param_function': signed_tile_offset}, 'nearby_tile_slope' : {'var': 0x67, 'start': 0, 'size': 5, 'param_function': signed_tile_offset}, 'nearby_tile_is_water' : {'var': 0x67, 'start': 9, 'size': 1, 'param_function': signed_tile_offset}, 'nearby_tile_terrain_type' : {'var': 0x67, 'start': 10, 'size': 3, 'param_function': signed_tile_offset}, 'nearby_tile_water_class' : {'var': 0x67, 'start': 13, 'size': 2, 'param_function': signed_tile_offset}, 'nearby_tile_height' : {'var': 0x67, 'start': 16, 'size': 8, 'param_function': signed_tile_offset}, 'nearby_tile_class' : {'var': 0x67, 'start': 24, 'size': 4, 'param_function': signed_tile_offset}, 'nearby_tile_is_station' : {'var': 0x68, 'start': 0, 'size': 32, 'param_function': signed_tile_offset, 'value_function': lambda var, info: expression.BinOp(nmlop.CMP_NEQ, var, expression.ConstantNumeric(0xFFFFFFFF, var.pos), var.pos)}, 'nearby_tile_station_id' : {'var': 0x68, 'start': 0, 'size': 8, 'param_function': signed_tile_offset}, 'nearby_tile_same_grf' : {'var': 0x68, 'start': 8, 'size': 2, 'param_function': signed_tile_offset, 'value_function': value_equals(0)}, 'nearby_tile_other_grf' : {'var': 0x68, 'start': 8, 'size': 2, 'param_function': signed_tile_offset, 'value_function': value_equals(1)}, 'nearby_tile_original_gfx' : {'var': 0x68, 'start': 8, 'size': 2, 'param_function': signed_tile_offset, 'value_function': value_equals(2)}, 'nearby_tile_same_station' : {'var': 0x68, 'start': 10, 'size': 1, 'param_function': signed_tile_offset}, 'nearby_tile_perpendicular' : {'var': 0x68, 'start': 11, 'size': 1, 'param_function': signed_tile_offset}, 'nearby_tile_platform_type' : {'var': 0x68, 'start': 12, 'size': 2, 'param_function': signed_tile_offset}, # 'var' will be set in the value_function, depending on parameter 'platform_length' : {'var': 0x00, 'start': 16, 'size': 4, 'param_function': platform_info_param, 'value_function': platform_info_fix_var}, 'platform_count' : {'var': 0x00, 'start': 20, 'size': 4, 'param_function': platform_info_param, 'value_function': platform_info_fix_var}, 'platform_position_from_start' : {'var': 0x00, 'start': 0, 'size': 4, 'param_function': platform_info_param, 'value_function': platform_info_fix_var}, 'platform_position_from_end' : {'var': 0x00, 'start': 4, 'size': 4, 'param_function': platform_info_param, 'value_function': platform_info_fix_var}, 'platform_number_from_start' : {'var': 0x00, 'start': 8, 'size': 4, 'param_function': platform_info_param, 'value_function': platform_info_fix_var}, 'platform_number_from_end' : {'var': 0x00, 'start': 12, 'size': 4, 'param_function': platform_info_param, 'value_function': platform_info_fix_var}, 'platform_position_from_middle' : {'var': 0x00, 'start': 0, 'size': 4, 'param_function': platform_info_param, 'middle': True, # 'middle' is used by platform_info_param 'value_function': lambda var, info: value_sign_extend(platform_info_fix_var(var, info), info)}, 'platform_number_from_middle' : {'var': 0x00, 'start': 4, 'size': 4, 'param_function': platform_info_param, 'middle': True, # 'middle' is used by platform_info_param 'value_function': lambda var, info: value_sign_extend(platform_info_fix_var(var, info), info)}, } varact2vars60x_stations.update(varact2vars60x_base_stations) # # Canals (feature 0x05) # varact2vars_canals = { 'tile_height' : {'var': 0x80, 'start': 0, 'size': 8}, 'terrain_type' : {'var': 0x81, 'start': 0, 'size': 8}, 'dike_map' : {'var': 0x82, 'start': 0, 'size': 8}, 'random_bits' : {'var': 0x83, 'start': 0, 'size': 8}, } # Canals have no 60+X variables # # Bridges (feature 0x06) have no variational action2 # # # Houses (feature 0x07) # def house_same_class(var, info): # Just using var 44 fails for non-north house tiles, as these have no class # Therefore work around it using var 61 # Load ID of the north tile from register FF bits 24..31, and use that as param for var 61 north_tile = expression.Variable(expression.ConstantNumeric(0x7D), expression.ConstantNumeric(24), expression.ConstantNumeric(0xFF), expression.ConstantNumeric(0xFF), var.pos) var61 = expression.Variable(expression.ConstantNumeric(0x7B), expression.ConstantNumeric(info['start']), expression.ConstantNumeric((1 << info['size']) - 1), expression.ConstantNumeric(0x61), var.pos) return expression.BinOp(nmlop.VAL2, north_tile, var61, var.pos) varact2vars_houses = { 'construction_state' : {'var': 0x40, 'start': 0, 'size': 2}, 'pseudo_random_bits' : {'var': 0x40, 'start': 2, 'size': 2}, 'age' : {'var': 0x41, 'start': 0, 'size': 8}, 'town_zone' : {'var': 0x42, 'start': 0, 'size': 8}, 'terrain_type' : {'var': 0x43, 'start': 0, 'size': 8}, 'same_house_count_town' : {'var': 0x44, 'start': 0, 'size': 8}, 'same_house_count_map' : {'var': 0x44, 'start': 8, 'size': 8}, 'same_class_count_town' : {'var': 0xFF, 'start': 16, 'size': 8, 'value_function': house_same_class}, # 'var' is unused 'same_class_count_map' : {'var': 0xFF, 'start': 24, 'size': 8, 'value_function': house_same_class}, # 'var' is unused 'generating_town' : {'var': 0x45, 'start': 0, 'size': 1}, 'animation_frame' : {'var': 0x46, 'start': 0, 'size': 8}, 'x_coordinate' : {'var': 0x47, 'start': 0, 'size': 16}, 'y_coordinate' : {'var': 0x47, 'start': 16, 'size': 16}, 'random_bits' : {'var': 0x5F, 'start': 8, 'size': 8}, 'relative_x' : {'var': 0x7D, 'start': 0, 'size': 8, 'param': 0xFF}, 'relative_y' : {'var': 0x7D, 'start': 8, 'size': 8, 'param': 0xFF}, 'relative_pos' : {'var': 0x7D, 'start': 0, 'size': 16, 'param': 0xFF}, 'house_tile' : {'var': 0x7D, 'start': 16, 'size': 8, 'param': 0xFF}, 'house_type_id' : {'var': 0x7D, 'start': 24, 'size': 8, 'param': 0xFF}, } def cargo_accepted_nearby(name, args, pos, info): # cargo_accepted_nearby(cargo[, xoffset, yoffset]) if len(args) not in (1, 3): raise generic.ScriptError("{}() requires 1 or 3 arguments, encountered {:d}".format(name, len(args)), pos) if len(args) > 1: offsets = args[1:3] for i, offs in enumerate(offsets[:]): if isinstance(offs, expression.ConstantNumeric): generic.check_range(offs.value, -128, 127, "{}-parameter {:d} '{}offset'".format(name, i + 1, "x" if i == 0 else "y"), pos) offsets[i] = expression.BinOp(nmlop.AND, offs, expression.ConstantNumeric(0xFF, pos), pos).reduce() # Register 0x100 should be set to xoffset | (yoffset << 8) reg100 = expression.BinOp(nmlop.OR, expression.BinOp(nmlop.MUL, offsets[1], expression.ConstantNumeric(256, pos), pos), offsets[0], pos).reduce() else: reg100 = expression.ConstantNumeric(0, pos) return (args[0], [(0x100, reg100)]) def nearest_house_matching_criterion(name, args, pos, info): # nearest_house_matching_criterion(radius, criterion) # parameter is radius | (criterion << 6) if len(args) != 2: raise generic.ScriptError("{}() requires 2 arguments, encountered {:d}".format(name, len(args)), pos) if isinstance(args[0], expression.ConstantNumeric): generic.check_range(args[0].value, 1, 63, "{}()-parameter 1 'radius'".format(name), pos) if isinstance(args[1], expression.ConstantNumeric) and args[1].value not in (0, 1, 2): raise generic.ScriptError("Invalid value for {}()-parameter 2 'criterion'".format(name), pos) radius = expression.BinOp(nmlop.AND, args[0], expression.ConstantNumeric(0x3F, pos), pos) criterion = expression.BinOp(nmlop.AND, args[1], expression.ConstantNumeric(0x03, pos), pos) criterion = expression.BinOp(nmlop.MUL, criterion, expression.ConstantNumeric(0x40, pos), pos) retval = expression.BinOp(nmlop.OR, criterion, radius, pos).reduce() return (retval, []) varact2vars60x_houses = { 'old_house_count_town' : {'var': 0x60, 'start': 0, 'size': 8}, 'old_house_count_map' : {'var': 0x60, 'start': 8, 'size': 8}, 'other_house_count_town' : {'var': 0x61, 'start': 0, 'size': 8}, 'other_house_count_map' : {'var': 0x61, 'start': 8, 'size': 8}, 'other_class_count_town' : {'var': 0x61, 'start': 16, 'size': 8}, 'other_class_count_map' : {'var': 0x61, 'start': 24, 'size': 8}, 'nearby_tile_slope' : {'var': 0x62, 'start': 0, 'size': 5, 'param_function': signed_tile_offset}, 'nearby_tile_is_water' : {'var': 0x62, 'start': 9, 'size': 1, 'param_function': signed_tile_offset}, 'nearby_tile_terrain_type' : {'var': 0x62, 'start': 10, 'size': 3, 'param_function': signed_tile_offset}, 'nearby_tile_water_class' : {'var': 0x62, 'start': 13, 'size': 2, 'param_function': signed_tile_offset}, 'nearby_tile_height' : {'var': 0x62, 'start': 16, 'size': 8, 'param_function': signed_tile_offset}, 'nearby_tile_class' : {'var': 0x62, 'start': 24, 'size': 4, 'param_function': signed_tile_offset}, 'nearby_tile_animation_frame' : {'var': 0x63, 'start': 0, 'size': 8, 'param_function': signed_tile_offset}, 'cargo_accepted_nearby_ever' : {'var': 0x64, 'start': 0, 'size': 1, 'param_function': cargo_accepted_nearby}, 'cargo_accepted_nearby_last_month' : {'var': 0x64, 'start': 1, 'size': 1, 'param_function': cargo_accepted_nearby}, 'cargo_accepted_nearby_this_month' : {'var': 0x64, 'start': 2, 'size': 1, 'param_function': cargo_accepted_nearby}, 'cargo_accepted_nearby_last_bigtick' : {'var': 0x64, 'start': 3, 'size': 1, 'param_function': cargo_accepted_nearby}, 'cargo_accepted_nearby_watched' : {'var': 0x64, 'start': 4, 'size': 1, 'param_function': cargo_accepted_nearby}, 'nearest_house_matching_criterion' : {'var': 0x65, 'start': 0, 'size': 8, 'param_function': nearest_house_matching_criterion}, 'nearby_tile_house_id' : {'var': 0x66, 'start': 0, 'size': 16, 'param_function': signed_tile_offset, 'value_function': value_sign_extend}, 'nearby_tile_house_class' : {'var': 0x66, 'start': 16, 'size': 16, 'param_function': signed_tile_offset, 'value_function': value_sign_extend}, 'nearby_tile_house_grfid' : {'var': 0x67, 'start': 0, 'size': 32, 'param_function': signed_tile_offset}, } # # Global variables (feature 0x08) have no variational action2 # # # Industry tiles (feature 0x09) # varact2vars_industrytiles = { 'construction_state' : {'var': 0x40, 'start': 0, 'size': 2}, 'terrain_type' : {'var': 0x41, 'start': 0, 'size': 8}, 'town_zone' : {'var': 0x42, 'start': 0, 'size': 3}, 'relative_x' : {'var': 0x43, 'start': 0, 'size': 8}, 'relative_y' : {'var': 0x43, 'start': 8, 'size': 8}, 'relative_pos' : {'var': 0x43, 'start': 0, 'size': 16}, 'animation_frame' : {'var': 0x44, 'start': 0, 'size': 8}, 'random_bits' : {'var': 0x5F, 'start': 8, 'size': 8}, } varact2vars60x_industrytiles = { 'nearby_tile_slope' : {'var': 0x60, 'start': 0, 'size': 5, 'param_function': signed_tile_offset}, 'nearby_tile_is_same_industry' : {'var': 0x60, 'start': 8, 'size': 1, 'param_function': signed_tile_offset}, 'nearby_tile_is_water' : {'var': 0x60, 'start': 9, 'size': 1, 'param_function': signed_tile_offset}, 'nearby_tile_terrain_type' : {'var': 0x60, 'start': 10, 'size': 3, 'param_function': signed_tile_offset}, 'nearby_tile_water_class' : {'var': 0x60, 'start': 13, 'size': 2, 'param_function': signed_tile_offset}, 'nearby_tile_height' : {'var': 0x60, 'start': 16, 'size': 8, 'param_function': signed_tile_offset}, 'nearby_tile_class' : {'var': 0x60, 'start': 24, 'size': 4, 'param_function': signed_tile_offset}, 'nearby_tile_animation_frame' : {'var': 0x61, 'start': 0, 'size': 8, 'param_function': signed_tile_offset}, 'nearby_tile_industrytile_id' : {'var': 0x62, 'start': 0, 'size': 16, 'param_function': signed_tile_offset}, } # # Industries (feature 0x0A) # varact2vars_industries = { 'waiting_cargo_1' : {'var': 0x40, 'start': 0, 'size': 16}, 'waiting_cargo_2' : {'var': 0x41, 'start': 0, 'size': 16}, 'waiting_cargo_3' : {'var': 0x42, 'start': 0, 'size': 16}, 'water_distance' : {'var': 0x43, 'start': 0, 'size': 32}, 'layout_num' : {'var': 0x44, 'start': 0, 'size': 8}, # bits 0 .. 16 are either useless or already covered by var A7 'founder_type' : {'var': 0x45, 'start': 16, 'size': 2}, 'founder_colour1' : {'var': 0x45, 'start': 24, 'size': 4}, 'founder_colour2' : {'var': 0x45, 'start': 28, 'size': 4}, 'build_date' : {'var': 0x46, 'start': 0, 'size': 32}, 'random_bits' : {'var': 0x5F, 'start': 8, 'size': 16}, 'produced_cargo_waiting_1' : {'var': 0x8A, 'start': 0, 'size': 16}, 'produced_cargo_waiting_2' : {'var': 0x8C, 'start': 0, 'size': 16}, 'production_rate_1' : {'var': 0x8E, 'start': 0, 'size': 8}, 'production_rate_2' : {'var': 0x8F, 'start': 0, 'size': 8}, 'production_level' : {'var': 0x93, 'start': 0, 'size': 8}, 'produced_this_month_1' : {'var': 0x94, 'start': 0, 'size': 16}, 'produced_this_month_2' : {'var': 0x96, 'start': 0, 'size': 16}, 'transported_this_month_1' : {'var': 0x98, 'start': 0, 'size': 16}, 'transported_this_month_2' : {'var': 0x9A, 'start': 0, 'size': 16}, 'transported_last_month_pct_1' : {'var': 0x9C, 'start': 0, 'size': 8, 'value_function': value_mul_div(101, 256)}, 'transported_last_month_pct_2' : {'var': 0x9D, 'start': 0, 'size': 8, 'value_function': value_mul_div(101, 256)}, 'produced_last_month_1' : {'var': 0x9E, 'start': 0, 'size': 16}, 'produced_last_month_2' : {'var': 0xA0, 'start': 0, 'size': 16}, 'transported_last_month_1' : {'var': 0xA2, 'start': 0, 'size': 16}, 'transported_last_month_2' : {'var': 0xA4, 'start': 0, 'size': 16}, 'founder' : {'var': 0xA7, 'start': 0, 'size': 8}, 'colour' : {'var': 0xA8, 'start': 0, 'size': 8}, 'counter' : {'var': 0xAA, 'start': 0, 'size': 16}, 'build_type' : {'var': 0xB3, 'start': 0, 'size': 2}, 'last_accept_date' : {'var': 0xB4, 'start': 0, 'size': 16, 'value_function': value_add_constant(701265)}, } def industry_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 = [(0x100, grfid)] return (args[0], extra_params) 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) varact2vars60x_industries = { 'nearby_tile_industry_tile_id' : {'var': 0x60, 'start': 0, 'size': 16, 'param_function': unsigned_tile_offset}, 'nearby_tile_random_bits' : {'var': 0x61, 'start': 0, 'size': 8, 'param_function': unsigned_tile_offset}, 'nearby_tile_slope' : {'var': 0x62, 'start': 0, 'size': 5, 'param_function': unsigned_tile_offset}, 'nearby_tile_is_water' : {'var': 0x62, 'start': 9, 'size': 1, 'param_function': unsigned_tile_offset}, 'nearby_tile_terrain_type' : {'var': 0x62, 'start': 10, 'size': 3, 'param_function': unsigned_tile_offset}, 'nearby_tile_water_class' : {'var': 0x62, 'start': 13, 'size': 2, 'param_function': unsigned_tile_offset}, 'nearby_tile_height' : {'var': 0x62, 'start': 16, 'size': 8, 'param_function': unsigned_tile_offset}, 'nearby_tile_class' : {'var': 0x62, 'start': 24, 'size': 4, 'param_function': unsigned_tile_offset}, 'nearby_tile_animation_frame' : {'var': 0x63, 'start': 0, 'size': 8, 'param_function': unsigned_tile_offset}, 'town_manhattan_dist' : {'var': 0x65, 'start': 0, 'size': 16, 'param_function': signed_tile_offset}, 'town_zone' : {'var': 0x65, 'start': 16, 'size': 8, 'param_function': signed_tile_offset}, 'town_euclidean_dist' : {'var': 0x66, 'start': 0, 'size': 32, 'param_function': signed_tile_offset}, 'industry_count' : {'var': 0x67, 'start': 16, 'size': 8, 'param_function': industry_count}, 'industry_distance' : {'var': 0x67, 'start': 0, 'size': 16, 'param_function': industry_count}, 'industry_layout_count' : {'var': 0x68, 'start': 16, 'size': 8, 'param_function': industry_layout_count}, 'industry_layout_distance' : {'var': 0x68, 'start': 0, 'size': 16, 'param_function': industry_layout_count}, 'industry_town_count' : {'var': 0x68, 'start': 16, 'size': 8, 'param_function': industry_town_count}, } # # Cargos (feature 0x0B) have no own varaction2 variables # Sounds (feature 0x0C) have no variational action2 # # # Airports (feature 0x0D) # varact2vars_airports = { 'layout' : {'var': 0x40, 'start': 0, 'size': 32}, } varact2vars_airports.update(varact2vars_base_stations) varact2vars60x_airports = varact2vars60x_base_stations # # New Signals (feature 0x0E) are not implemented in OpenTTD # # # Objects (feature 0x0F) # varact2vars_objects = { 'relative_x' : {'var': 0x40, 'start': 0, 'size': 8}, 'relative_y' : {'var': 0x40, 'start': 8, 'size': 8}, 'relative_pos' : {'var': 0x40, 'start': 0, 'size': 16}, 'terrain_type' : {'var': 0x41, 'start': 0, 'size': 3}, 'tile_slope' : {'var': 0x41, 'start': 8, 'size': 5}, 'build_date' : {'var': 0x42, 'start': 0, 'size': 32}, 'animation_frame' : {'var': 0x43, 'start': 0, 'size': 8}, 'company_colour' : {'var': 0x43, 'start': 0, 'size': 8}, 'owner' : {'var': 0x44, 'start': 0, 'size': 8}, 'town_manhattan_dist' : {'var': 0x45, 'start': 0, 'size': 16}, 'town_zone' : {'var': 0x45, 'start': 16, 'size': 8}, 'town_euclidean_dist' : {'var': 0x46, 'start': 0, 'size': 32}, 'view' : {'var': 0x48, 'start': 0, 'size': 8}, 'random_bits' : {'var': 0x5F, 'start': 8, 'size': 8}, } varact2vars60x_objects = { 'nearby_tile_object_type' : {'var': 0x60, 'start': 0, 'size': 16, 'param_function': signed_tile_offset}, 'nearby_tile_object_view' : {'var': 0x60, 'start': 16, 'size': 4, 'param_function': signed_tile_offset}, 'nearby_tile_random_bits' : {'var': 0x61, 'start': 0, 'size': 8, 'param_function': signed_tile_offset}, 'nearby_tile_slope' : {'var': 0x62, 'start': 0, 'size': 5, 'param_function': signed_tile_offset}, 'nearby_tile_is_same_object' : {'var': 0x62, 'start': 8, 'size': 1, 'param_function': signed_tile_offset}, 'nearby_tile_is_water' : {'var': 0x62, 'start': 9, 'size': 1, 'param_function': signed_tile_offset}, 'nearby_tile_terrain_type' : {'var': 0x62, 'start': 10, 'size': 3, 'param_function': signed_tile_offset}, 'nearby_tile_water_class' : {'var': 0x62, 'start': 13, 'size': 2, 'param_function': signed_tile_offset}, 'nearby_tile_height' : {'var': 0x62, 'start': 16, 'size': 8, 'param_function': signed_tile_offset}, 'nearby_tile_class' : {'var': 0x62, 'start': 24, 'size': 4, 'param_function': signed_tile_offset}, 'nearby_tile_animation_frame' : {'var': 0x63, 'start': 0, 'size': 8, 'param_function': signed_tile_offset}, 'object_count' : {'var': 0x64, 'start': 16, 'size': 8, 'param_function': industry_count}, 'object_distance' : {'var': 0x64, 'start': 0, 'size': 16, 'param_function': industry_count}, } # # Railtypes (feature 0x10) # varact2vars_railtype = { 'terrain_type' : {'var': 0x40, 'start': 0, 'size': 8}, 'enhanced_tunnels' : {'var': 0x41, 'start': 0, 'size': 8}, 'level_crossing_status' : {'var': 0x42, 'start': 0, 'size': 8}, 'build_date' : {'var': 0x43, 'start': 0, 'size': 32}, 'town_zone' : {'var': 0x44, 'start': 0, 'size': 8}, 'random_bits' : {'var': 0x5F, 'start': 8, 'size': 2}, } # Railtypes have no 60+x variables # # Airport tiles (feature 0x11) # varact2vars_airporttiles = { 'terrain_type' : {'var': 0x41, 'start': 0, 'size': 8}, 'town_radius_group' : {'var': 0x42, 'start': 0, 'size': 3}, 'relative_x' : {'var': 0x43, 'start': 0, 'size': 8}, 'relative_y' : {'var': 0x43, 'start': 8, 'size': 8}, 'relative_pos' : {'var': 0x43, 'start': 0, 'size': 16}, 'animation_frame' : {'var': 0x44, 'start': 0, 'size': 8}, } varact2vars60x_airporttiles = { 'nearby_tile_slope' : {'var': 0x60, 'start': 0, 'size': 5, 'param_function': signed_tile_offset}, 'nearby_tile_is_same_airport' : {'var': 0x60, 'start': 8, 'size': 1, 'param_function': signed_tile_offset}, 'nearby_tile_is_water' : {'var': 0x60, 'start': 9, 'size': 1, 'param_function': signed_tile_offset}, 'nearby_tile_terrain_type' : {'var': 0x60, 'start': 10, 'size': 3, 'param_function': signed_tile_offset}, 'nearby_tile_water_class' : {'var': 0x60, 'start': 13, 'size': 2, 'param_function': signed_tile_offset}, 'nearby_tile_height' : {'var': 0x60, 'start': 16, 'size': 8, 'param_function': signed_tile_offset}, 'nearby_tile_class' : {'var': 0x60, 'start': 24, 'size': 4, 'param_function': signed_tile_offset}, 'nearby_tile_animation_frame' : {'var': 0x61, 'start': 0, 'size': 8, 'param_function': signed_tile_offset}, 'nearby_tile_airporttile_id' : {'var': 0x62, 'start': 0, 'size': 16, 'param_function': signed_tile_offset}, } # # Towns are not a true feature, but accessible via the parent scope of e.g. industries, stations # varact2vars_towns = { 'is_city' : {'var': 0x40, 'start': 0, 'size': 1}, 'cities_enabled' : {'var': 0x40, 'start': 1, 'size': 1, 'value_function': lambda var, info: expression.Not(var, var.pos)}, 'population' : {'var': 0x82, 'start': 0, 'size': 16}, 'has_church' : {'var': 0x92, 'start': 1, 'size': 1}, 'has_stadium' : {'var': 0x92, 'start': 2, 'size': 1}, 'town_zone_0_radius_square' : {'var': 0x94, 'start': 0, 'size': 16}, 'town_zone_1_radius_square' : {'var': 0x96, 'start': 0, 'size': 16}, 'town_zone_2_radius_square' : {'var': 0x98, 'start': 0, 'size': 16}, 'town_zone_3_radius_square' : {'var': 0x9A, 'start': 0, 'size': 16}, 'town_zone_4_radius_square' : {'var': 0x9C, 'start': 0, 'size': 16}, 'num_houses' : {'var': 0xB6, 'start': 0, 'size': 16}, 'percent_transported_passengers' : {'var': 0xCA, 'start': 0, 'size': 8, 'value_function': value_mul_div(101, 256)}, 'percent_transported_mail' : {'var': 0xCB, 'start': 0, 'size': 8, 'value_function': value_mul_div(101, 256)}, } varact2vars[0x00] = varact2vars_trains varact2vars60x[0x00] = varact2vars60x_vehicles varact2vars[0x01] = varact2vars_roadvehs varact2vars60x[0x01] = varact2vars60x_vehicles varact2vars[0x02] = varact2vars_ships varact2vars60x[0x02] = varact2vars60x_vehicles varact2vars[0x03] = varact2vars_aircraft varact2vars60x[0x03] = varact2vars60x_vehicles varact2vars[0x04] = varact2vars_stations varact2vars60x[0x04] = varact2vars60x_stations varact2vars[0x05] = varact2vars_canals varact2vars[0x07] = varact2vars_houses varact2vars60x[0x07] = varact2vars60x_houses varact2vars[0x09] = varact2vars_industrytiles varact2vars60x[0x09] = varact2vars60x_industrytiles varact2vars[0x0A] = varact2vars_industries varact2vars60x[0x0A] = varact2vars60x_industries varact2vars[0x0D] = varact2vars_airports varact2vars60x[0x0D] = varact2vars60x_airports varact2vars[0x0F] = varact2vars_objects varact2vars60x[0x0F] = varact2vars60x_objects varact2vars[0x10] = varact2vars_railtype varact2vars[0x11] = varact2vars_airporttiles varact2vars60x[0x11] = varact2vars60x_airporttiles varact2vars[0x12] = varact2vars_towns nml-0.4.4/nml/actions/action1.py0000644000567200056720000002206112643457545017625 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from nml.actions import base_action, real_sprite """ Maximum number of sprites per block. This can be increased by switching to extended Action1. """ max_sprite_block_size = 0xFF class Action1(base_action.BaseAction): """ Class representing an Action1 @ivar feature: Feature of this action1 @type feature: C{int} @ivar num_sets: Number of (sprite) sets that follow this action 1. @type num_sets: C{int} @ivar num_ent: Number of sprites per set (e.g. (usually) 8 for vehicles) @type num_ent: C{int} """ def __init__(self, feature, num_sets, num_ent): self.feature = feature self.num_sets = num_sets self.num_ent = num_ent def write(self, file): # * 01 file.start_sprite(6) file.print_bytex(1) file.print_bytex(self.feature) file.print_byte(self.num_sets) file.print_varx(self.num_ent, 3) file.newline() file.end_sprite() class SpritesetCollection(base_action.BaseAction): """ A collection that contains multiple spritesets. All spritesets will be written to the same Action1, so they need to have the same number of sprites. @ivar feature: The feature number the action1 will get. @type feature: C{int} @ivar num_sprites_per_spriteset: The number of sprites in each spriteset. @type num_sprites_per_spriteset: C{int} @ivar spritesets: A mapping from spritesets to indices. This allows for quick lookup of whether a spriteset is already in this collection. The indices are unique integers in the range 0 .. len(spritesets) - 1. @type spritesets: C{dict} mapping L{SpriteSet} to C{int}. """ def __init__(self, feature, num_sprites_per_spriteset): self.feature = feature self.num_sprites_per_spriteset = num_sprites_per_spriteset self.spritesets = {} def skip_action7(self): return False def skip_action9(self): return False def skip_needed(self): return False def can_add(self, spritesets, feature): """ Test whether the given list of spritesets can be added to this collection. @param spritesets: The list of spritesets to test for addition. @type spritesets: C{list} of L{SpriteSet} @param feature: The feature of the given spritesets. @type feature: C{int} @return: True iff the given spritesets can be added to this collection. @rtype: C{bool} """ assert len(spritesets) <= max_sprite_block_size if feature != self.feature: return False for spriteset in spritesets: if len(real_sprite.parse_sprite_data(spriteset)) != self.num_sprites_per_spriteset: return False num_new_sets = sum(1 for x in spritesets if x not in self.spritesets) return len(self.spritesets) + num_new_sets <= max_sprite_block_size def add(self, spritesets): """ Add a list of spritesets to this collection. @param spritesets: The list of spritesets to add. @type spritesets: C{list} of L{SpriteSet} @pre: can_add(spritesets, self.feature). """ assert self.can_add(spritesets, self.feature) for spriteset in spritesets: if spriteset not in self.spritesets: self.spritesets[spriteset] = len(self.spritesets) def get_index(self, spriteset): """ Get the index of the given spriteset in the final action1. @param spriteset: The spriteset to get the index of. @type spriteset: L{SpriteSet}. @pre: The spriteset must have been previously added to this collection via #add. """ assert spriteset in self.spritesets return self.spritesets[spriteset] def get_action_list(self): """ Create a list of actions needed to write this collection to the output. This will generate a single Action1 and as many realsprites as needed. @return: A list of actions needed to represet this collection in a GRF. @rtype: C{list} of L{BaseAction} """ actions = [Action1(self.feature, len(self.spritesets), self.num_sprites_per_spriteset)] for idx in range(len(self.spritesets)): for spriteset, spriteset_offset in list(self.spritesets.items()): if idx == spriteset_offset: actions.extend(real_sprite.parse_sprite_data(spriteset)) break return actions """ Statistics about spritesets. The 1st field of type C{int} contains the largest block of consecutive spritesets. The 2nd field of type L{Position} contains a positional reference to the largest block of consecutive spritesets. """ spriteset_stats = (0, None) def print_stats(): """ Print statistics about used ids. """ if spriteset_stats[0] > 0: # NML uses as many concurrent spritesets as possible to prevent sprite duplication. # So, instead of the actual amount, we rather print the biggest unsplittable block, since that is what matters. generic.print_info("Concurrent spritesets: {}/{} ({})".format(spriteset_stats[0], max_sprite_block_size, str(spriteset_stats[1]))) """ The collection which was previoulsy used. add_to_action1 will try to reuse this collection as long as possible to reduce the duplication of sprites. As soon as a spriteset with a different feature or amount of sprites is added a new collection will be created. """ last_spriteset_collection = None def add_to_action1(spritesets, feature, pos): """ Add a list of spritesets to a spriteset collection. This will try to reuse one collection as long as possible and create a new one when needed. @param spritesets: List of spritesets that will be used by the next action2. @type spritesets: C{list} of L{SpriteSet} @param feature: Feature of the spritesets. @type feature: C{int} @param pos: Position reference to source. @type pos: L{Position} @return: List of collections that needs to be added to the global action list. @rtype: C{list} of L{SpritesetCollection}. """ if not spritesets: return [] setsize = len(real_sprite.parse_sprite_data(spritesets[0])) for spriteset in spritesets: if setsize != len(real_sprite.parse_sprite_data(spriteset)): raise generic.ScriptError("Using spritesets with different sizes in a single sprite group / layout is not possible", pos) global spriteset_stats if spriteset_stats[0] < len(spritesets): spriteset_stats = (len(spritesets), pos) global last_spriteset_collection actions = [] if last_spriteset_collection is None or not last_spriteset_collection.can_add(spritesets, feature): last_spriteset_collection = SpritesetCollection(feature, len(real_sprite.parse_sprite_data(spritesets[0]))) actions.append(last_spriteset_collection) last_spriteset_collection.add(spritesets) return actions def get_action1_index(spriteset): """ Get the index of a spriteset in the action1. The given spriteset must have been added in the last call to #add_to_action1. Any new calls to #add_to_action1 may or may not allocate a new spriteset collection and as such make previous spritesets inaccessible. @param spriteset: The spriteset to get the index of. @type spriteset: L{SpriteSet}. @return: The index in the action1 of the given spriteset. @rtype: C{int} """ assert last_spriteset_collection is not None return last_spriteset_collection.get_index(spriteset) def make_cb_failure_action1(feature): """ Create an action1 that may be used for a callback failure If the last action1 is of the correct feature, no new action1 is needed Else, add a new action1 with 1 spriteset containing 0 sprites @param feature: Feature of the requested action 1 @type feature: C{int} @return: List of actions to append (if any) and action1 index to use @rtype: C{tuple} of (C{list} of L{BaseAction}, C{int}) """ global last_spriteset_collection if last_spriteset_collection is not None and last_spriteset_collection.feature == feature: actions = [] else: last_spriteset_collection = None actions = [Action1(feature, 1, 0)] return (actions, 0) # Index is currently always 0, but will change with ext. A1 nml-0.4.4/nml/actions/action0.py0000644000567200056720000011504612643457545017632 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml.actions.action0properties import BaseAction0Property, Action0Property, properties, two_byte_property from nml import generic, expression, nmlop, grfstrings from nml.actions import base_action, action4, action6, actionD, action7 from nml.ast import general class BlockAllocation(object): """ Administration of allocated blocks of a size, in a range of addresses. Blocks always start at address C{0}, but the first available freely usable address may be further. The allocation information is kept in L{allocated}. It has the following cases - An address does not exist in the dictionary. The address is free for use. - An address exists, with an integer number as size. At this address a block has been allocated with that size. - An address exists, with value C{None}. The address is part of an allocated block, but not the start. @ivar first: First freely usable address. @type first: C{int} @ivar last: Last freely usable address. @type last: C{int} @ivar name: Name for debug output. @type name: C{str} @ivar dynamic_allocation: True, if ids are allocated. False, if they refer to static entities. @type dynamic_allocation: C{bool} @ivar allocated: Mapping of allocated blocks. @type allocated: C{dict} of C{int} to allocation information. @ivar filled: Mapping of block size to smallest address that may contain free space. Serves as a cache to speed up searches. @type filled: C{dict} of C{int} """ def __init__(self, first, last, name, dynamic_allocation=True): self.first = first self.last = last self.name = name self.dynamic_allocation = dynamic_allocation self.allocated = {} self.filled = {} def get_num_allocated(self): """ Return number of allocated ids. """ if self.dynamic_allocation: return len(self.allocated) else: return 0 def get_max_allocated(self): """ Return maximum number of allocateable ids. """ if self.dynamic_allocation: return self.last - self.first + 1 else: return 0 def in_range(self, addr, length): """ Does a block starting at the provided address with the given length in the available address space? @param addr: First address of the block. @type addr: C{int} @param length: Number of addresses in the block. @type length: C{int} @return: Whether the block fits enitrely in the available address space. @rtype: C{bool} """ return addr >= 0 and addr + length - 1 <= self.last def get_size(self, addr): """ Get the size of the block allocated at the given address. @param addr: Address being queried. @type addr: C{int} @return: Size of the allocated block, if one was allocated here, else C{None}. @rtype: C{int} or C{None} """ return self.allocated.get(addr) def is_address_free(self, addr): """ Is the space at the given address still available? ( cheaper variant of C{self.get_last_used(addr, 1) is None} ) @param addr: Address being queried. @type addr: C{int} @return: Whether the space at the provided address is available. @rtype: C{bool} """ return addr not in self.allocated def get_last_used(self, addr, length): """ Check whether a block of addresses is used. @param addr: First address of the block. @type addr: C{int} @param length: Number of addresses in the block. @type length: C{int} @return: The last used address in the block, or C{None} if all addresses are free. @rtype: C{int} or C{None} @precond: Addresses of the range should be within the available address space. """ for idx in range(addr + length - 1, addr - 1, -1): if idx in self.allocated: return idx return None def mark_used(self, addr, length): """ Mark an area as being allocated. @param addr: First address of the block. @type addr: C{int} @param length: Number of addresses in the block. @type length: C{int} @precond: Addresses of the block should be within the freely available address space. @precond: No address in the block may have been allocated. """ self.allocated[addr] = length for idx in range(addr + 1, addr + length): self.allocated[idx] = None def find_unused(self, length): """ Find an area of unused space. @param length: Number of addresses to find. @type length: C{int} @return: Address in the freely available address space for the new block, or C{None} if no space is available. @rtype: C{int} or C{None} """ idx = self.filled.get(length) if idx is None: # Never searched before with this block size. # Start at the biggest offset previously discovered with a smaller block size. smaller_filleds = [min_f for sz, min_f in self.filled.items() if sz < length] idx = self.first if len(smaller_filleds) == 0 else max(smaller_filleds) last_idx = self.last - length + 1 while idx < last_idx: last_used = self.get_last_used(idx, length) if last_used is None: self.filled[length] = idx + length return idx idx = last_used + 1 return None # Available IDs for each feature. # Maximum allowed id (houses and indtiles in principle allow up to 511, but action3 does not accept extended bytes). used_ids = [ BlockAllocation(116, 0xFFFF, "Train"), BlockAllocation( 88, 0xFFFF, "Road Vehicle"), BlockAllocation( 11, 0xFFFF, "Ship"), BlockAllocation( 41, 0xFFFF, "Aircraft"), BlockAllocation( 0, 255, "Station"), BlockAllocation( 0, 8, "Canal", False), BlockAllocation( 0, 15, "Bridge", False), BlockAllocation( 0, 255, "House"), BlockAllocation( 0, -1, "Global", False), BlockAllocation( 0, 255, "Industry Tile"), BlockAllocation( 0, 127, "Industry"), BlockAllocation( 0, 31, "Cargo"), BlockAllocation( 0, -1, "Sound"), BlockAllocation( 0, 127, "Airport"), BlockAllocation( 0, -1, "Signal", False), BlockAllocation( 0, 255, "Object"), BlockAllocation( 0, 15, "Railtype"), BlockAllocation( 0, 255, "Airport Tile"), ] def print_stats(): """ Print statistics about used ids. """ for feature in used_ids: used = feature.get_num_allocated() if used > 0 and feature.dynamic_allocation: generic.print_info("{} items: {}/{}".format(feature.name, used, feature.get_max_allocated())) def mark_id_used(feature, id, num_ids): """ Mark a range of ids as used. @param feature: Feature of the ids. @type feature: C{int} @param id: First id to be marked. @type id: C{int} @param num_ids: Number of ids to mark as used, starting with \a id. @type num_ids: C{int} """ used_ids[feature].mark_used(id, num_ids) def check_id_range(feature, id, num_ids, pos): """ Check that the id is valid, and is either free or points to an already allocated block. @param feature: Feature of the ids. @type feature: C{int} @param id: Base id number. @type id: C{int} @param num_ids: Number of ids to test, starting with \a id. @type num_ids: C{int} @param pos: Source position of the check, for reporting errors. @type pos: L{Position} @return: Whether the block was allocated already. @rtype: C{bool} """ blk_alloc = used_ids[feature] # Check that IDs are valid and in range. if not blk_alloc.in_range(id, num_ids): msg = "Item ID must be in range 0..{:d}, encountered {:d}..{:d}." msg = msg.format(blk_alloc.last, id, id + num_ids - 1) raise generic.ScriptError(msg, pos) # ID already defined, but with the same size: OK if blk_alloc.get_size(id) == num_ids: return True # All IDs free: no problem. if blk_alloc.get_last_used(id, num_ids) is None: return False # No space at the indicated position, report an error. if blk_alloc.get_size(id) is not None: # ID already defined with a different size: error. raise generic.ScriptError("Item with ID {:d} has already been defined, but with a different size.".format(id), pos) if blk_alloc.is_address_free(id): # First item id free -> any of the additional tile ids must be blocked. msg = "This multi-tile house requires that item IDs {:d}..{:d} are free, but they are not." msg = msg.format(id, id + num_ids - 1) raise generic.ScriptError(msg, pos) # ID already defined as part of a multi-tile house. raise generic.ScriptError("Item ID {:d} has already used as part of a multi-tile house.".format(id), pos) def get_free_id(feature, num_ids, pos): """ Find an id to allocate a range of \a num_ids ids in a feature. @param feature: Feature of the ids. @type feature: C{int} @param num_ids: Number of ids to allocate. @type num_ids: C{int} @param pos: Position information. @type pos: L{Position} """ blk_alloc = used_ids[feature] addr = blk_alloc.find_unused(num_ids) if addr is None: msg = "Unable to allocate ID for item, no more free IDs available (maximum is {:d})" msg = msg.format(blk_alloc.last) raise generic.ScriptError(msg, pos) blk_alloc.mark_used(addr, num_ids) return addr # Number of tiles for various house sizes house_sizes = { 0 : 1, # 1x1 2 : 2, # 2x1 3 : 2, # 1x2 4 : 4, # 2x2 } def adjust_value(value, org_value, unit, ottd_convert_func): """ Make sure that the property value written to the NewGRF will match exactly the value as quoted @param value: The value to check, converted to base units @type value: L{Expression} @param org_value: The original value as written in the input file @type org_value: L{Expression} @param unit: The unit of the org_value @type unit: L{Unit} or C{None} @return: The adjusted value @rtype: L{Expression} """ while ottd_convert_func(value, unit) > org_value.value: value = expression.ConstantNumeric(int(value.value - 1), value.pos) lower_value = value while ottd_convert_func(value, unit) < org_value.value: value = expression.ConstantNumeric(int(value.value + 1), value.pos) higher_value = value if abs(ottd_convert_func(lower_value, unit) - org_value.value) < abs(ottd_convert_func(higher_value, unit) - org_value.value): return lower_value return higher_value class Action0(base_action.BaseAction): """ Representation of NFO action 0. Action 0 is used to set properties on all kinds of objects. It can set any number of properties on a list of consecutive IDs. Each property is defined by a unique (per feature) integer. @ivar feature: Feature number to set properties for. @type feature: C{int} @ivar id: First ID to set properties for @type id: C{int} @ivar prop_list: List of all properties that are to be set. @type prop_list: C{list} of L{BaseAction0Property} @ivar num_ids: Number of IDs to set properties for. @type num_ids: C{int} or C{None} """ def __init__(self, feature, id): self.feature = feature self.id = id self.prop_list = [] self.num_ids = None def prepare_output(self, sprite_num): if self.num_ids is None: self.num_ids = 1 def write(self, file): size = 7 for prop in self.prop_list: assert isinstance(prop, BaseAction0Property), type(prop) if isinstance(prop, Action0Property): assert len(prop.values) == self.num_ids size += prop.get_size() file.start_sprite(size) file.print_bytex(0) file.print_bytex(self.feature) file.print_byte(len(self.prop_list)) file.print_bytex(self.num_ids) file.print_bytex(0xFF) file.print_wordx(self.id) file.newline() for prop in self.prop_list: prop.write(file) file.end_sprite() def create_action0(feature, id, act6, action_list): """ Create an action0 with variable id @param feature: Feature of the action0 @type feature: C{int} @param id: ID of the corresponding item @type id: L{Expression} @param act6: Action6 to add any modifications to @type act6: L{Action6} @param action_list: Action list to append any extra actions to @type action_list: C{list} of L{BaseAction} @return: A tuple of (resulting action0, offset to use for action6) @rtype: C{tuple} of (L{Action0}, C{int}) """ id, offset = actionD.write_action_value(id, action_list, act6, 5, 2) action0 = Action0(feature, id.value) return (action0, offset) 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_property_value(prop_info, value, unit = None, size_bit = None): """ Parse a single property value / unit To determine the value that is to be used in nfo @param prop_info: A dictionary with property information @type prop_info: C{dict} @param value: Value of the property @type value: L{Expression} @param unit: Unit of the property value (e.g. km/h) @type unit: L{Unit} or C{None} @param size_bit: Bit that indicates the size of a multitile house Set iff the item is a house @type size_bit: C{int} or C{None} @return: List of values to actually use (in nfo) for the property @rtype: L{Expression} """ # Change value to use, except when the 'nfo' unit is used if unit is None or unit.type != 'nfo': # Save the original value to test conversion against it org_value = value # Multiply by property-specific conversion factor mul, div = 1, 1 if 'unit_conversion' in prop_info: mul = prop_info['unit_conversion'] if isinstance(mul, tuple): mul, div = mul # Divide by conversion factor specified by unit if unit is not None: if not 'unit_type' in prop_info or unit.type != prop_info['unit_type']: raise generic.ScriptError("Invalid unit for property", value.pos) unit_mul, unit_div = unit.convert, 1 if isinstance(unit_mul, tuple): unit_mul, unit_div = unit_mul mul *= unit_div div *= unit_mul # Factor out common factors gcd = generic.greatest_common_divisor(mul, div) mul //= gcd div //= gcd if isinstance(value, (expression.ConstantNumeric, expression.ConstantFloat)): # Even if mul == div == 1, we have to round floats and adjust value value = expression.ConstantNumeric(int(float(value.value) * mul / div + 0.5), value.pos) if unit is not None and 'adjust_value' in prop_info: value = adjust_value(value, org_value, unit, prop_info['adjust_value']) elif mul != div: # Compute (value * mul + div/2) / div value = expression.BinOp(nmlop.MUL, value, expression.ConstantNumeric(mul, value.pos), value.pos) value = expression.BinOp(nmlop.ADD, value, expression.ConstantNumeric(int(div / 2), value.pos), value.pos) value = expression.BinOp(nmlop.DIV, value, expression.ConstantNumeric(div, value.pos), value.pos) elif isinstance(value, expression.ConstantFloat): # Round floats to ints value = expression.ConstantNumeric(int(value.value + 0.5), value.pos) # Apply value_function if it exists if 'value_function' in prop_info: value = prop_info['value_function'](value) # Make multitile houses work if size_bit is not None: num_ids = house_sizes[size_bit] assert 'multitile_function' in prop_info ret = prop_info['multitile_function'](value, num_ids, size_bit) assert len(ret) == num_ids return ret else: return [value] def parse_property(prop_info, value_list, feature, id): """ Parse a single property @param prop_info: A dictionary with property information @type prop_info: C{dict} @param value_list: List of values for the property, with unit conversion applied @type value_list: C{list} of L{Expression} @param feature: Feature of the associated item @type feature: C{int} @param id: ID of the associated item @type id: L{Expression} @return: A tuple containing the following: - List of properties to add to the action 0 - List of actions to prepend - List of modifications to apply via action 6 - List of actions to append @rtype: C{tuple} of (C{list} of L{Action0Property}, C{list} of L{BaseAction}, C{list} of 3-C{tuple}, C{list} of L{BaseAction}) """ action_list = [] action_list_append = [] mods = [] if 'custom_function' in prop_info: props = prop_info['custom_function'](*value_list) else: # First process each element in the value_list final_values = [] for i, value in enumerate(value_list): if 'string_literal' in prop_info and (isinstance(value, expression.StringLiteral) or prop_info['string_literal'] != 4): # Parse non-string exprssions just like integers. User will have to take care of proper value. # This can be used to set a label (=string of length 4) to the value of a parameter. if not isinstance(value, expression.StringLiteral): raise generic.ScriptError("Value for property {:d} must be a string literal".format(prop_info['num']), value.pos) if len(value.value) != prop_info['string_literal']: raise generic.ScriptError("Value for property {:d} must be of length {:d}".format(prop_info['num'], prop_info['string_literal']), value.pos) elif isinstance(value, expression.ConstantNumeric): pass elif isinstance(value, expression.Parameter) and isinstance(value.num, expression.ConstantNumeric): mods.append( (value.num.value, prop_info['size'], i * prop_info['size'] + 1) ) value = expression.ConstantNumeric(0) elif isinstance(value, expression.String): if not 'string' in prop_info: raise generic.ScriptError("String used as value for non-string property: " + str(prop_info['num']), value.pos) string_range = prop_info['string'] stringid, string_actions = action4.get_string_action4s(feature, string_range, value, id) value = expression.ConstantNumeric(stringid) action_list_append.extend(string_actions) else: tmp_param, tmp_param_actions = actionD.get_tmp_parameter(value) mods.append((tmp_param, prop_info['size'], i * prop_info['size'] + 1)) action_list.extend(tmp_param_actions) value = expression.ConstantNumeric(0) final_values.append(value) # Now, write a single Action0 Property with all of these values if prop_info['num'] != -1: props = [Action0Property(prop_info['num'], final_values, prop_info['size'])] else: props = [] return (props, action_list, mods, action_list_append) 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 parse_property_block(prop_list, feature, id, size): """ Parse a property block to an action0 (with possibly various other actions) @param prop_list: List of properties to parse @type prop_list: C{list} of L{Property} @param feature: Feature of the associated item @type feature: C{int} @param id: ID of the associated item @type id: L{Expression} @param size: Size (for houses only) @type size: L{ConstantNumeric} or C{None} @return: List of resulting actions @rtype: C{list} of L{BaseAction} """ action6.free_parameters.save() action_list = [] action_list_append = [] act6 = action6.Action6() action0, offset = create_action0(feature, id, act6, action_list) if feature == 0x07: size_bit = size.value if size is not None else 0 action0.num_ids = house_sizes[size_bit] else: size_bit = None action0.num_ids = 1 prop_info_list = [] value_list_list = [] pos_list = [] for prop in prop_list: new_prop_info_list = get_property_info_list(feature, prop.name) prop_info_list.extend(new_prop_info_list) value_list_list.extend(parse_property_value(prop_info, prop.value, prop.unit, size_bit) for prop_info in new_prop_info_list) pos_list.extend(prop.name.pos for i in prop_info_list) validate_prop_info_list(prop_info_list, pos_list, feature) for prop_info, value_list in zip(prop_info_list, value_list_list): if 'test_function' in prop_info and not prop_info['test_function'](*value_list): continue properties, extra_actions, mods, extra_append_actions = parse_property(prop_info, value_list, feature, id) action_list.extend(extra_actions) action_list_append.extend(extra_append_actions) for mod in mods: act6.modify_bytes(mod[0], mod[1], mod[2] + offset) for p in properties: offset += p.get_size() action0.prop_list.extend(properties) if len(act6.modifications) > 0: action_list.append(act6) if len(action0.prop_list) != 0: action_list.append(action0) action_list.extend(action_list_append) action6.free_parameters.restore() return action_list class IDListProp(BaseAction0Property): def __init__(self, prop_num, id_list): self.prop_num = prop_num self.id_list = id_list def write(self, file): file.print_bytex(self.prop_num) for i, id_val in enumerate(self.id_list): if i > 0 and i % 5 == 0: file.newline() file.print_string(id_val.value, False, True) file.newline() def get_size(self): return len(self.id_list) * 4 + 1 def get_cargolist_action(cargo_list): action0 = Action0(0x08, 0) action0.prop_list.append(IDListProp(0x09, cargo_list)) action0.num_ids = len(cargo_list) return [action0] def get_railtypelist_action(railtype_list): action6.free_parameters.save() act6 = action6.Action6() action_list = [] action0, offset = create_action0(0x08, expression.ConstantNumeric(0), act6, action_list) id_table = [] offset += 1 # Skip property number for railtype in railtype_list: if isinstance(railtype, expression.StringLiteral): id_table.append(railtype) offset+=4 continue param, extra_actions = actionD.get_tmp_parameter(expression.ConstantNumeric(expression.parse_string_to_dword(railtype[-1]))) action_list.extend(extra_actions) for idx in range(len(railtype)-2, -1, -1): val = expression.ConstantNumeric(expression.parse_string_to_dword(railtype[idx])) action_list.append(action7.SkipAction(0x09, 0x00, 4, (0x0D, None), val.value, 1)) action_list.append(actionD.ActionD(expression.ConstantNumeric(param), expression.ConstantNumeric(0xFF), nmlop.ASSIGN, expression.ConstantNumeric(0xFF), val)) act6.modify_bytes(param, 4, offset) id_table.append(expression.StringLiteral(r"\00\00\00\00", None)) offset += 4 action0.prop_list.append(IDListProp(0x12, id_table)) action0.num_ids = len(railtype_list) if len(act6.modifications) > 0: action_list.append(act6) action_list.append(action0) action6.free_parameters.restore() return action_list class ByteListProp(BaseAction0Property): def __init__(self, prop_num, data): self.prop_num = prop_num self.data = data def write(self, file): file.print_bytex(self.prop_num) file.newline() for i, data_val in enumerate(self.data): if i > 0 and i % 8 == 0: file.newline() file.print_bytex(ord(data_val)) file.newline() def get_size(self): return len(self.data) + 1 def get_snowlinetable_action(snowline_table): assert len(snowline_table) == 12*32 action6.free_parameters.save() action_list = [] tmp_param_map = {} #Cache for tmp parameters act6 = action6.Action6() act0, offset = create_action0(0x08, expression.ConstantNumeric(0), act6, action_list) act0.num_ids = 1 offset += 1 # Skip property number data_table = [] idx = 0 while idx < len(snowline_table): val = snowline_table[idx] if isinstance(val, expression.ConstantNumeric): data_table.append(val.value) idx += 1 continue if idx + 3 >= len(snowline_table): tmp_param, tmp_param_actions = actionD.get_tmp_parameter(val) tmp_param_map[val] = tmp_param act6.modify_bytes(tmp_param, 1, offset + idx) action_list.extend(tmp_param_actions) data_table.append(0) idx += 1 continue # Merge the next 4 values together in a single parameter. val2 = expression.BinOp(nmlop.SHIFT_LEFT, snowline_table[idx + 1], expression.ConstantNumeric(8)) val3 = expression.BinOp(nmlop.SHIFT_LEFT, snowline_table[idx + 2], expression.ConstantNumeric(16)) val4 = expression.BinOp(nmlop.SHIFT_LEFT, snowline_table[idx + 3], expression.ConstantNumeric(24)) expr = expression.BinOp(nmlop.OR, val, val2) expr = expression.BinOp(nmlop.OR, expr, val3) expr = expression.BinOp(nmlop.OR, expr, val4) expr = expr.reduce() #Cache lookup, saves some ActionDs if expr in tmp_param_map: tmp_param, tmp_param_actions = tmp_param_map[expr], [] else: tmp_param, tmp_param_actions = actionD.get_tmp_parameter(expr) tmp_param_map[expr] = tmp_param act6.modify_bytes(tmp_param, 4, offset + idx) action_list.extend(tmp_param_actions) data_table.extend([0, 0, 0, 0]) idx += 4 act0.prop_list.append(ByteListProp(0x10, ''.join([chr(x) for x in data_table]))) if len(act6.modifications) > 0: action_list.append(act6) action_list.append(act0) action6.free_parameters.restore() return action_list def get_basecost_action(basecost): action6.free_parameters.save() action_list = [] tmp_param_map = {} #Cache for tmp parameters #We want to avoid writing lots of action0s if possible i = 0 while i < len(basecost.costs): cost = basecost.costs[i] act6 = action6.Action6() act0, offset = create_action0(0x08, cost.name, act6, action_list) first_id = cost.name.value if isinstance(cost.name, expression.ConstantNumeric) else None num_ids = 1 #Number of values that will be written in one go values = [] #try to capture as much values as possible while True: cost = basecost.costs[i] if isinstance(cost.value, expression.ConstantNumeric): values.append(cost.value) else: #Cache lookup, saves some ActionDs if cost.value in tmp_param_map: tmp_param, tmp_param_actions = tmp_param_map[cost.value], [] else: tmp_param, tmp_param_actions = actionD.get_tmp_parameter(cost.value) tmp_param_map[cost.value] = tmp_param act6.modify_bytes(tmp_param, 1, offset + num_ids) action_list.extend(tmp_param_actions) values.append(expression.ConstantNumeric(0)) #check if we can append the next to this one (it has to be consecutively numbered) if first_id is not None and (i + 1) < len(basecost.costs): nextcost = basecost.costs[i+1] if isinstance(nextcost.name, expression.ConstantNumeric) and nextcost.name.value == first_id + num_ids: num_ids += 1 i += 1 #Yes We Can, continue the loop to append this value to the list and try further continue # No match, so stop it and write an action0 break act0.prop_list.append(Action0Property(0x08, values, 1)) act0.num_ids = num_ids if len(act6.modifications) > 0: action_list.append(act6) action_list.append(act0) i += 1 action6.free_parameters.restore() return action_list class LanguageTranslationTable(BaseAction0Property): def __init__(self, num, name_list, extra_names): self.num = num self.mappings = [] for name, idx in list(name_list.items()): self.mappings.append( (idx, name) ) if name in extra_names: for extra_name in extra_names[name]: self.mappings.append( (idx, extra_name) ) # Sort to keep the output deterministic self.mappings.sort() def write(self, file): file.print_bytex(self.num) for mapping in self.mappings: file.print_bytex(mapping[0]) file.print_string(mapping[1]) file.print_bytex(0) file.newline() def get_size(self): size = 2 for mapping in self.mappings: size += 1 + grfstrings.get_string_size(mapping[1]) return size def get_language_translation_tables(lang): action0 = Action0(0x08, lang.langid) if lang.genders is not None: action0.prop_list.append(LanguageTranslationTable(0x13, lang.genders, lang.gender_map)) if lang.cases is not None: action0.prop_list.append(LanguageTranslationTable(0x14, lang.cases, lang.case_map)) if lang.plural is not None: action0.prop_list.append(Action0Property(0x15, expression.ConstantNumeric(lang.plural), 1)) if len(action0.prop_list) > 0: return [action0] return [] disable_info = { # Vehicles: set climates_available to 0 0x00 : {'num': 116, 'props': [{'num': 0x06, 'size': 1, 'value': 0}]}, 0x01 : {'num': 88, 'props': [{'num': 0x06, 'size': 1, 'value': 0}]}, 0x02 : {'num': 11, 'props': [{'num': 0x06, 'size': 1, 'value': 0}]}, 0x03 : {'num': 41, 'props': [{'num': 0x06, 'size': 1, 'value': 0}]}, # Houses / industries / airports: Set substitute_type to FF 0x07 : {'num': 110, 'props': [{'num': 0x08, 'size': 1, 'value': 0xFF}]}, 0x0A : {'num': 37, 'props': [{'num': 0x08, 'size': 1, 'value': 0xFF}]}, 0x0D : {'num': 10, 'props': [{'num': 0x08, 'size': 1, 'value': 0xFF}]}, # Cargos: Set bitnum to FF and label to 0 0x0B : {'num': 27, 'props': [{'num': 0x08, 'size': 1, 'value': 0xFF}, {'num': 0x17, 'size': 4, 'value': 0}]}, } def get_disable_actions(disable): """ Get the action list for a disable_item block @param disable: Disable block @type disable: L{DisableItem} @return: A list of resulting actions @rtype: C{list} of L{BaseAction} """ feature = disable.feature.value if feature not in disable_info: raise generic.ScriptError("disable_item() is not available for feature {:d}.".format(feature), disable.pos) if disable.first_id is None: # No ids set -> disable all assert disable.last_id is None first = 0 num = disable_info[feature]['num'] else: first = disable.first_id.value if disable.last_id is None: num = 1 else: num = disable.last_id.value - first + 1 act0 = Action0(feature, first) act0.num_ids = num for prop in disable_info[feature]['props']: act0.prop_list.append(Action0Property(prop['num'], num * [expression.ConstantNumeric(prop['value'])], prop['size'])) return [act0] class EngineOverrideProp(BaseAction0Property): def __init__(self, source, target): self.source = source self.target = target def write(self, file): file.print_bytex(0x11) file.print_dwordx(self.source) file.print_dwordx(self.target) file.newline() def get_size(self): return 9 def get_engine_override_action(override): act0 = Action0(0x08, 0) act0.num_ids = 1 act0.prop_list.append(EngineOverrideProp(override.source_grfid, override.grfid)) return [act0] def parse_sort_block(feature, vehid_list): prop_num = [0x1A, 0x20, 0x1B, 0x1B] action_list = [] if len(vehid_list) >= 2: last = vehid_list[0] idx = len(vehid_list) - 1 while idx >= 0: cur = vehid_list[idx] prop = Action0Property(prop_num[feature], [last], 3) action_list.append(Action0(feature, cur.value)) action_list[-1].prop_list.append(prop) last = cur idx -= 1 return action_list callback_flag_properties = { 0x00: {'size': 1, 'num': 0x1E}, 0x01: {'size': 1, 'num': 0x17}, 0x02: {'size': 1, 'num': 0x12}, 0x03: {'size': 1, 'num': 0x14}, 0x05: {'size': 1, 'num': 0x08}, 0x07: two_byte_property(0x14, 0x1D), 0x09: {'size': 1, 'num': 0x0E}, 0x0A: two_byte_property(0x21, 0x22), 0x0B: {'size': 1, 'num': 0x1A}, 0x0F: {'size': 2, 'num': 0x15}, 0x11: {'size': 1, 'num': 0x0E}, } def get_callback_flags_actions(feature, id, flags): """ Get a list of actions to set the callback flags of a certain item @param feature: Feature of the item @type feature: C{int} @param id: ID of the item @type id: L{Expression} @param flags: Value of the 'callback_flags' property @type flags: C{int} @return: A list of actions @rtype: C{list} of L{BaseAction} """ assert isinstance(id, expression.ConstantNumeric) act0, offset = create_action0(feature, id, None, None) act0.num_ids = 1 assert feature in callback_flag_properties prop_info_list = callback_flag_properties[feature] if not isinstance(prop_info_list, list): prop_info_list = [prop_info_list] for prop_info in prop_info_list: act0.prop_list.append(Action0Property(prop_info['num'], parse_property_value(prop_info, expression.ConstantNumeric(flags)), prop_info['size'])) return [act0] def get_volume_actions(volume_list): """ Get a list of actions to set sound volumes @param volume_list: List of (sound id, volume) tuples, sorted in ascending order @type volume_list: C{list} of (C{int}, C{int})-tuples @return: A list of actions @rtype: C{list} of L{BaseAction} """ action_list = [] first_id = None # First ID in a series of consecutive IDs value_series = [] # Series of values to write in a single action for id, volume in volume_list: if first_id is None: first_id = id continue_series = first_id + len(value_series) == id if continue_series: value_series.append(volume) if not continue_series or id == volume_list[-1][0]: # Write action for this series act0, offset = create_action0(0x0C, expression.ConstantNumeric(first_id), None, None) act0.num_ids = len(value_series) act0.prop_list.append(Action0Property(0x08, [expression.ConstantNumeric(v) for v in value_series], 1)) action_list.append(act0) # start new series, if needed if not continue_series: first_id = id value_series = [volume] return action_list nml-0.4.4/nml/__init__.py0000644000567200056720000000124312643457545016365 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" nml-0.4.4/nml/version_info.py0000644000567200056720000001053412643457545017331 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" # The following version determination code is a greatly simplified version # of the mercurial repo code. The version is stored in nml/__version__.py # get_numeric_version is used only for the purpose of packet creation, # in all other cases use get_nml_version() import subprocess, os def get_child_output(cmd): """ Run a child process, and collect the generated output. @param cmd: Command to execute. @type cmd: C{list} of C{str} @return: Generated output of the command, split on whitespace. @rtype: C{list} of C{str} """ return subprocess.check_output(cmd, universal_newlines = True).split() def get_hg_version(): path = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) version = '' if os.path.isdir(os.path.join(path,'.hg')): # we want to return to where we were. So save the old path try: version_list = get_child_output(['hg', '-R', path, 'id', '-n', '-t', '-i']) except OSError as e: print("Mercurial checkout found but cannot determine its version. Error({0}): {1}".format(e.errno, e.strerror)) return version if version_list[1].endswith('+'): modified = 'M' else: modified = '' # Test whether we have a tag (=release version) and add it, if found if len(version_list) > 2 and version_list[2] != 'tip' and modified == '': # Tagged release version = version_list[2] else: # Branch or modified version hash = version_list[0].rstrip('+') # Get the date of the commit of the current NML version in days since January 1st 2000 ctimes = get_child_output(["hg", "-R", path, "parent", "--template='{date|hgdate} {date|shortdate}\n'"]) ctime = (int((ctimes[0].split("'"))[1]) - 946684800) // (60 * 60 * 24) cversion = str(ctime) # Combine the version string version = "v{}{}:{} from {}".format(cversion, modified, hash, ctimes[2].split("'", 1)[0]) return version def get_lib_versions(): versions = {} #PIL try: from PIL import Image versions["PIL"] = Image.VERSION except ImportError: try: import Image versions["PIL"] = Image.VERSION except ImportError: versions["PIL"] = "Not found!" #PLY try: from ply import lex versions["PLY"] = lex.__version__ except ImportError: versions["PLY"] = "Not found!" return versions def get_nml_version(): # first try whether we find an nml repository. Use that version, if available version = get_hg_version() if version: return version # no repository was found. Return the version which was saved upon built try: from nml import __version__ version = __version__.version except ImportError: version = 'unknown' return version def get_cli_version(): #version string for usage in command line result = get_nml_version() + "\n" result += "Library versions encountered:\n" for lib, lib_ver in list(get_lib_versions().items()): result += lib + ": " + lib_ver + "\n" return result[0:-1] #strip trailing newline def get_and_write_version(): version = get_nml_version() if version: try: path = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) f = open(os.path.join(path, "nml", "__version__.py"), "w") f.write('# this file is autogenerated by setup.py\n') f.write('version = "{}"\n'.format(version)) f.close() return version.split()[0] except IOError: print("Version file NOT written") nml-0.4.4/nml/output_base.py0000644000567200056720000002240712643457545017165 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" """ Abstract base classes that implements common functionality for output classes """ import array, io class OutputBase(object): """ Base class for output to a data file. The file is opened with L{open}. Once that is done, data can be written using the L{file} data member. When finished writing, the file should be closed with L{close}. Derived classes should implement L{open_file} to perform the actual opening of the file. L{assemble_file} is called to actually compose the output from possible multiple sources. @ivar filename: Name of the data file. @type filename: C{str} @ivar file: Memory output file handle, if opened. @type file: C{file} or C{None} """ def __init__(self, filename): self.filename = filename self.file = None def open(self): """ Open the output file. Data gets stored in-memory. """ raise NotImplementedError("Implement me in {}".format(type(self))) def open_file(self): """ Construct the file handle of the disk output file. @return: File handle of the opened file. @rtype: C{file} """ raise NotImplementedError("Implement me in {}".format(type(self))) def assemble_file(self, real_file): """ File is about to be closed, last chance to append data. @param real_file: Actual output stream. @type real_file: C{io.IOBase} """ real_file.write(self.file.getvalue()) def discard(self): """ Close the memory file, without writing to disk. """ if isinstance(self.file, io.IOBase): self.file.close() self.file = None def close(self): """ Close the memory file, copy collected output to the real file. """ real_file = self.open_file() self.assemble_file(real_file) real_file.close() self.discard() def skip_sprite_checks(self): """ Return whether sprites need detailed parsing. """ return False class SpriteOutputBase(OutputBase): """ Base class for output to a sprite file. @ivar in_sprite: Set to true if we are currently outputting a sprite. Outputting anything when not in a sprite causes an assert. @type in_sprite: C{bool} @ivar byte_count: Number of bytes written in the current sprite. @type byte_count: C{int} @ivar expected_count: Number of bytes expected in the current sprite. @type expected_count: C{int} """ def __init__(self, filename): OutputBase.__init__(self, filename) self.in_sprite = False self.expected_count = 0 self.byte_count = 0 def close(self): assert not self.in_sprite OutputBase.close(self) def prepare_byte(self, value): """ Normalize the provided value to an unsigned byte value. @param value: Value to normalize. @type value: C{int} @return: Normalized value (0..255). @rtype: C{int} @precond: Must be outputting a sprite. """ assert self.in_sprite if -0x80 <= value < 0 : value += 0x100 assert value >= 0 and value <= 0xFF self.byte_count += 1 return value def prepare_word(self, value): """ Normalize the provided value to an unsigned word value. @param value: Value to normalize. @type value: C{int} @return: Normalized value (0..65535). @rtype: C{int} @precond: Must be outputting a sprite. """ assert self.in_sprite if -0x8000 <= value < 0: value += 0x10000 assert value >= 0 and value <= 0xFFFF self.byte_count += 2 return value def prepare_dword(self, value): """ Normalize the provided value to an unsigned double word value. @param value: Value to normalize. @type value: C{int} @return: Normalized value (0..0xFFFFFFFF). @rtype: C{int} @precond: Must be outputting a sprite. """ assert self.in_sprite if -0x80000000 <= value < 0: value += 0x100000000 assert value >= 0 and value <= 0xFFFFFFFF self.byte_count += 4 return value def print_varx(self, value, size): """ Print a variable sized value. @param value: Value to output. @type value: C{int} @param size: Size of the output (1..4), 3 means extended byte. @type size: C{int} """ if size == 1: self.print_bytex(value) elif size == 2: self.print_wordx(value) elif size == 3: self.print_bytex(0xFF) self.print_wordx(value) elif size == 4: self.print_dwordx(value) else: assert False def print_bytex(self, byte, pretty_print = None): """ Output an unsigned byte. @param byte: Value to output. @type byte: C{int} """ raise NotImplementedError("Implement print_bytex() in {}".format(type(self))) def print_wordx(self, byte): """ Output an unsigned word (2 bytes). @param byte: Value to output. @type byte: C{int} """ raise NotImplementedError("Implement print_wordx() in {}".format(type(self))) def print_dwordx(self, byte): """ Output an unsigned double word (4 bytes). @param byte: Value to output. @type byte: C{int} """ raise NotImplementedError("Implement print_dwordx() in {}".format(type(self))) def newline(self, msg = "", prefix = "\t"): """ Output a line separator, prefixed with L{prefix}, C{"// "}, and the L{msg}, if the latter is not empty. @param msg: Optional message to output first. @type msg: C{str} @param prefix: Additional white space in front of the comment. @type prefix: C{str} """ raise NotImplementedError("Implement newline() in {}".format(type(self))) def comment(self, msg): """ Output a textual comment at a line by itself. @param msg: Comment message. @type msg: C{str} @note: Only use if no bytes have been written to the current line. """ raise NotImplementedError("Implement comment() in {}".format(type(self))) def start_sprite(self, expected_size): """ Note to the output stream that a sprite is about to be written. @param expected_size: Expected size of the sprite data. @type expected_size: C{int} """ assert not self.in_sprite self.in_sprite = True self.expected_count = expected_size self.byte_count = 0 def end_sprite(self): """ Note to the output stream that a sprite has been written. The number of bytes denoted as expected size with the L{start_sprite} call, should have been written. """ assert self.in_sprite self.in_sprite = False self.newline() assert self.expected_count == self.byte_count, "Expected {:d} bytes to be written to sprite, got {:d}".format(self.expected_count, self.byte_count) class TextOutputBase(OutputBase): """ Base class for textual output. """ def __init__(self, filename): OutputBase.__init__(self, filename) def open(self): self.file = io.StringIO() class BinaryOutputBase(SpriteOutputBase): """ Class for binary output. """ def __init__(self, filename): SpriteOutputBase.__init__(self, filename) def open(self): self.file = array.array('B') def newline(self, msg = "", prefix = "\t"): pass def print_data(self, data): """ Print a chunck of data in one go @param data: Data to output @type data: C{array} """ self.byte_count += len(data) self.file.extend(data) def wb(self, byte): self.file.append(byte) def print_byte(self, value): value = self.prepare_byte(value) self.wb(value) def print_bytex(self, value, pretty_print = None): self.print_byte(value) def print_word(self, value): value = self.prepare_word(value) self.wb(value & 0xFF) self.wb(value >> 8) def print_wordx(self, value): self.print_word(value) def print_dword(self, value): value = self.prepare_dword(value) self.wb(value & 0xFF) self.wb((value >> 8) & 0xFF) self.wb((value >> 16) & 0xFF) self.wb(value >> 24) def print_dwordx(self, value): self.print_dword(value) nml-0.4.4/nml/nmlop.py0000644000567200056720000002635612643457545015767 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import operator from .expression.base_expression import Type, ConstantNumeric, ConstantFloat from nml import generic class Operator(object): """ Operator in an expression. @ivar act2_supports: Whether the operator is supported by action 2. @type act2_supports: C{bool} @ivar act2_str: NFO text representation of the operator in action 2. @type act2_str: C{str} or C{None} @ivar act2_num: Numeric value of the operator in action 2. @type act2_num: C{int} or C{None} @ivar actd_supports: Whether the operator is supported by action D. @type actd_supports: C{bool} @ivar actd_str: NFO text representation of the operator in action D. @type actd_str: C{str} or C{None} @ivar actd_num: Numeric value of the operator in action D. @type actd_num: C{int} or C{None} @ivar returns_boolean: Whether the operator gives a boolean result. @type returns_boolean: C{bool} @ivar token: Infix text to use for the string representation of the operator. @type token: C{None} or C{str} @ivar prefix_text: Prefix text to use for the string representation of the operator, if available. @type prefix_text: C{None} or C{str} """ def __init__(self, act2_supports = False, act2_str = None, act2_num = None, actd_supports = False, actd_str = None, actd_num = None, returns_boolean = False, token = None, prefix_text = None, compiletime_func = None, validate_func = None): self.act2_supports = act2_supports self.act2_str = act2_str self.act2_num = act2_num self.actd_supports = actd_supports self.actd_str = actd_str self.actd_num = actd_num self.returns_boolean = returns_boolean self.token = token self.prefix_text = prefix_text self.compiletime_func = compiletime_func self.validate_func = validate_func def to_string(self, expr1, expr2): """ Convert expression to readable string form. @param expr1: Left child expression text. @type expr1: C{str} @param expr2: Right child expression text. @type expr2: C{str} @return: Text representation of the operator with its child expressions. @rtype: C{str} """ if self.prefix_text is not None: return '{}({}, {})'.format(self.prefix_text, expr1, expr2) else: # Infix notation. return '({} {} {})'.format(expr1, self.token, expr2) def unsigned_rshift(a, b): if a < 0: a += 0x100000000 return generic.truncate_int32(a >> b) def unsigned_rrotate(a, b): if a < 0: a += 0x100000000 return generic.truncate_int32((a >> b) | (a << (32 - b))) def validate_func_int(expr1, expr2, pos): if expr1.type() != Type.INTEGER or expr2.type() != Type.INTEGER: raise generic.ScriptError("Binary operator requires both operands to be integers.", pos) 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): 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_func_add(expr1, expr2, pos): if (expr1.type() == Type.STRING_LITERAL) ^ (expr2.type() == Type.STRING_LITERAL): raise generic.ScriptError("Concatenating a string literal and a number is not possible.", pos) if expr1.type() != Type.STRING_LITERAL: validate_func_float(expr1, expr2, pos) def validate_func_div_mod(expr1, expr2, pos): validate_func_float(expr1, expr2, pos) if isinstance(expr2, (ConstantNumeric, ConstantFloat)) and expr2.value == 0: raise generic.ScriptError("Division and modulo require the right hand side to be nonzero.", pos) def validate_func_rhs_positive(expr1, expr2, pos): validate_func_int(expr1, expr2, pos) if isinstance(expr2, ConstantNumeric) and expr2.value < 0: raise generic.ScriptError("Right hand side of the operator may not be a negative number.", pos) ADD = Operator( act2_supports = True, act2_str = r'\2+', act2_num = 0, actd_supports = True, actd_str = r'\D+', actd_num = 1, token = '+', compiletime_func = operator.add, validate_func = validate_func_add, ) SUB = Operator( act2_supports = True, act2_str = r'\2-', act2_num = 1, actd_supports = True, actd_str = r'\D-', actd_num = 2, token = '-', compiletime_func = operator.sub, validate_func = validate_func_float, ) DIV = Operator( act2_supports = True, act2_str = r'\2/', act2_num = 6, actd_supports = True, actd_str = r'\D/', actd_num = 10, token = '/', compiletime_func = lambda a, b: a // b if isinstance(a, int) and isinstance(b, int) else a / b, validate_func = validate_func_div_mod, ) MOD = Operator( act2_supports = True, act2_str = r'\2%', act2_num = 7, actd_supports = True, actd_str = r'\D%', actd_num = 12, token = '%', compiletime_func = operator.mod, validate_func = validate_func_div_mod, ) MUL = Operator( act2_supports = True, act2_str = r'\2*', act2_num = 10, actd_supports = True, actd_str = r'\D*', actd_num = 4, token = '*', compiletime_func = operator.mul, validate_func = validate_func_float, ) AND = Operator( act2_supports = True, act2_str = r'\2&', act2_num = 11, actd_supports = True, actd_str = r'\D&', actd_num = 7, token = '&', compiletime_func = operator.and_, validate_func = validate_func_int, ) OR = Operator( act2_supports = True, act2_str = r'\2|', act2_num = 12, actd_supports = True, actd_str = r'\D|', actd_num = 8, token = '|', compiletime_func = operator.or_, validate_func = validate_func_int, ) XOR = Operator( act2_supports = True, act2_str = r'\2^', act2_num = 13, actd_supports = True, token = '^', compiletime_func = operator.xor, validate_func = validate_func_int, ) CMP_EQ = Operator( act2_supports = True, actd_supports = True, returns_boolean = True, token = '==', compiletime_func = operator.eq, validate_func = validate_func_float, ) CMP_NEQ = Operator( act2_supports = True, actd_supports = True, returns_boolean = True, token = '!=', compiletime_func = operator.ne, validate_func = validate_func_float, ) CMP_LE = Operator( act2_supports = True, actd_supports = True, returns_boolean = True, token = '<=', compiletime_func = operator.le, validate_func = validate_func_float, ) CMP_GE = Operator( act2_supports = True, actd_supports = True, returns_boolean = True, token = '>=', compiletime_func = operator.ge, validate_func = validate_func_float, ) CMP_LT = Operator( act2_supports = True, actd_supports = True, returns_boolean = True, token = '<', compiletime_func = operator.lt, validate_func = validate_func_float, ) CMP_GT = Operator( act2_supports = True, actd_supports = True, returns_boolean = True, token = '>', compiletime_func = operator.gt, validate_func = validate_func_float, ) MIN = Operator( act2_supports = True, act2_str = r'\2<', act2_num = 2, actd_supports = True, compiletime_func = min, prefix_text = "min", validate_func = validate_func_float, ) MAX = Operator( act2_supports = True, act2_str = r'\2>', act2_num = 3, actd_supports = True, compiletime_func = max, prefix_text = "max", validate_func = validate_func_float, ) STO_TMP = Operator( act2_supports = True, act2_str = r'\2sto', act2_num = 14, prefix_text = "STORE_TEMP", validate_func = validate_func_rhs_positive, ) STO_PERM = Operator( act2_supports = True, act2_str = r'\2psto', act2_num = 16, prefix_text = "STORE_PERM", validate_func = validate_func_rhs_positive, ) SHIFT_LEFT = Operator( act2_supports = True, act2_str = r'\2<<', act2_num = 20, actd_supports = True, actd_str = r'\D<<', actd_num = 6, token = '<<', compiletime_func = operator.lshift, validate_func = validate_func_rhs_positive, ) SHIFT_RIGHT = Operator( act2_supports = True, act2_str = r'\2>>', act2_num = 22, actd_supports = True, token = '>>', compiletime_func = operator.rshift, validate_func = validate_func_rhs_positive, ) SHIFTU_RIGHT = Operator( act2_supports = True, act2_str = r'\2u>>', act2_num = 21, actd_supports = True, token = '>>>', compiletime_func = unsigned_rshift, validate_func = validate_func_rhs_positive, ) HASBIT = Operator( act2_supports = True, actd_supports = True, returns_boolean = True, prefix_text = "hasbit", compiletime_func = lambda a, b: (a & (1 << b)) != 0, validate_func = validate_func_rhs_positive, ) #A few operators that are generated internally but can't be directly written in nml NOTHASBIT = Operator( act2_supports = True, actd_supports = True, returns_boolean = True, prefix_text = "!hasbit", compiletime_func = lambda a, b: (a & (1 << b)) == 0, ) VAL2 = Operator( act2_supports = True, act2_str = r'\2r', act2_num = 15, compiletime_func = lambda a, b: b, ) ASSIGN = Operator( actd_supports = True, actd_str = r'\D=', actd_num = 0, ) SHIFTU_LEFT = Operator( actd_supports = True, actd_str = r'\Du<<', actd_num = 5, token = '<<', ) VACT2_CMP = Operator( act2_supports = True, act2_str = r'\2cmp', act2_num = 18, prefix_text = "CMP", ) VACT2_UCMP = Operator( act2_supports = True, act2_str = r'\2ucmp', act2_num = 19, prefix_text = "UCMP", ) MINU = Operator( act2_supports = True, act2_str = r'\2u<', act2_num = 4, ) ROT_RIGHT = Operator( act2_supports = True, act2_str = r'\2ror', act2_num = 17, prefix_text = "rotate", compiletime_func = unsigned_rrotate, validate_func = validate_func_int, ) DIVU = Operator( act2_supports = True, act2_str = r'\2u/', act2_num = 8, actd_supports = True, actd_str = r'\Du/', actd_num = 9, ) class GRMOperator(object): def __init__(self, op_str, op_num): self.op_str = op_str self.op_num = op_num self.value = op_num def __str__(self): return self.op_str def write(self, file, size): assert size == 1 file.print_bytex(self.op_num, self.op_str) GRM_RESERVE = GRMOperator(r'\DR', 0) nml-0.4.4/nml/grfstrings.py0000644000567200056720000014202312643457545017020 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import os, codecs, glob, re from nml import generic def utf8_get_size(char): if char < 128: return 1 if char < 2048: return 2 if char < 65536: return 3 return 4 DEFAULT_LANGUAGE = 0x7F DEFAULT_LANGNAME = "english.lng" 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 is_ascii_string(string): """ Check whether a given string can be written using the ASCII codeset or that we need unicode. @param string: The string to check. @type string: C{str} @return: True iff the string is ascii-only. @rtype: C{bool} """ assert isinstance(string, str) i = 0 while i < len(string): if string[i] != '\\': if ord(string[i]) >= 0x7B: return False i += 1 else: if string[i+1] in ('\\', '"'): i += 2 elif string[i+1] == 'U': return False else: i += 3 return True def get_string_size(string, final_zero = True, force_ascii = False): """ Get the size (in bytes) of a given string. @param string: The string to check. @type string: C{str} @param final_zero: Whether or not to account for a zero-byte directly after the string. @type final_zero: C{bool} @param force_ascii: When true, make sure the string is written as ascii as opposed to unicode. @type force_ascii: C{bool} @return: The length (in bytes) of the given string. @rtype: C{int} @raise generic.ScriptError: force_ascii and not is_ascii_string(string). """ size = 0 if final_zero: size += 1 if not is_ascii_string(string): if force_ascii: raise generic.ScriptError("Expected ascii string but got a unicode string") size += 2 i = 0 while i < len(string): if string[i] != '\\': size += utf8_get_size(ord(string[i])) i += 1 else: if string[i+1] in ('\\', '"'): size += 1 i += 2 elif string[i+1] == 'U': size += utf8_get_size(int(string[i+2:i+6], 16)) i += 6 else: size += 1 i += 3 return size def get_translation(string, lang_id = DEFAULT_LANGUAGE): """ Get the translation of a given string in a certain language. If there is no translation available in the given language return the default translation. @param string: the string to get the translation for. @type string: L{expression.String} @param lang_id: The language id of the language to translate the string into. @type lang_id: C{int} @return: Translation of the given string in the given language. @rtype: C{str} """ for lang_pair in langs: langid, lang = lang_pair if langid != lang_id: continue if string.name.value not in lang.strings: break return lang.get_string(string, lang_id) return default_lang.get_string(string, lang_id) def get_translations(string): """ Get a list of language ids that have a translation for the given string. @param string: the string to get the translations for. @type string: L{expression.String} @return: List of languages that translate the given string. @rtype: C{list} of C{int} """ translations = [] for lang_pair in langs: langid, lang = lang_pair assert langid is not None if string.name.value in lang.strings and lang.get_string(string, langid) != default_lang.get_string(string, langid): translations.append(langid) # Also check for translated substrings import nml.expression for param in string.params: if not isinstance(param, nml.expression.String): continue param_translations = get_translations(param) translations.extend([langid for langid in param_translations if not langid in translations]) return translations def com_parse_comma(val, lang_id): val = val.reduce_constant() return str(val) def com_parse_hex(val, lang_id): val = val.reduce_constant() return "0x{:X}".format(val.value) def com_parse_string(val, lang_id): import nml.expression if not isinstance(val, (nml.expression.StringLiteral, nml.expression.String)): raise generic.ScriptError("Expected a (literal) string", val.pos) if isinstance(val, nml.expression.String): # Check that the string exists if val.name.value not in default_lang.strings: raise generic.ScriptError("Substring \"{}\" does not exist".format(val.name.value), val.pos) return get_translation(val, lang_id) return val.value commands = { # Special characters / glyphs '': {'unicode': r'\0D', 'ascii': r'\0D'}, '{': {'unicode': r'{', 'ascii': r'{' }, 'NBSP': {'unicode': r'\U00A0'}, # character A0 is used as up arrow in TTD, so don't use ASCII here. 'COPYRIGHT': {'unicode': r'\U00A9', 'ascii': r'\A9'}, 'TRAIN': {'unicode': r'\UE0B4', 'ascii': r'\B4'}, 'LORRY': {'unicode': r'\UE0B5', 'ascii': r'\B5'}, 'BUS': {'unicode': r'\UE0B6', 'ascii': r'\B6'}, 'PLANE': {'unicode': r'\UE0B7', 'ascii': r'\B7'}, 'SHIP': {'unicode': r'\UE0B8', 'ascii': r'\B8'}, # Change the font size. 'TINYFONT': {'unicode': r'\0E', 'ascii': r'\0E'}, 'BIGFONT': {'unicode': r'\0F', 'ascii': r'\0F'}, 'COMMA': {'unicode': r'\UE07B', 'ascii': r'\7B', 'size': 4, 'parse': com_parse_comma}, 'SIGNED_WORD': {'unicode': r'\UE07C', 'ascii': r'\7C', 'size': 2, 'parse': com_parse_comma}, 'UNSIGNED_WORD': {'unicode': r'\UE07E', 'ascii': r'\7E', 'size': 2, 'parse': com_parse_comma}, 'CURRENCY': {'unicode': r'\UE07F', 'ascii': r'\7F', 'size': 4}, 'STRING': {'unicode': r'\UE080', 'ascii': r'\80', 'allow_case': True, 'size': 2, 'parse': com_parse_string}, 'DATE1920_LONG': {'unicode': r'\UE082', 'ascii': r'\82', 'size': 2}, 'DATE1920_SHORT': {'unicode': r'\UE083', 'ascii': r'\83', 'size': 2}, 'VELOCITY': {'unicode': r'\UE084', 'ascii': r'\84', 'size': 2}, 'SKIP': {'unicode': r'\UE085', 'ascii': r'\85', 'size': 2}, 'VOLUME': {'unicode': r'\UE087', 'ascii': r'\87', 'size': 2}, 'HEX': {'unicode': r'\UE09A\08', 'ascii': r'\9A\08', 'size': 4, 'parse': com_parse_hex}, 'STATION': {'unicode': r'\UE09A\0C', 'ascii': r'\9A\0C', 'size': 2}, 'WEIGHT': {'unicode': r'\UE09A\0D', 'ascii': r'\9A\0D', 'size': 2}, 'DATE_LONG': {'unicode': r'\UE09A\16', 'ascii': r'\9A\16', 'size': 4}, 'DATE_SHORT': {'unicode': r'\UE09A\17', 'ascii': r'\9A\17', 'size': 4}, 'POWER': {'unicode': r'\UE09A\18', 'ascii': r'\9A\18', 'size': 2}, 'VOLUME_SHORT': {'unicode': r'\UE09A\19', 'ascii': r'\9A\19', 'size': 2}, 'WEIGHT_SHORT': {'unicode': r'\UE09A\1A', 'ascii': r'\9A\1A', 'size': 2}, 'CARGO_LONG': {'unicode': r'\UE09A\1B', 'ascii': r'\9A\1B', 'size': 2 * 2}, 'CARGO_SHORT': {'unicode': r'\UE09A\1C', 'ascii': r'\9A\1C', 'size': 2 * 2}, 'CARGO_TINY': {'unicode': r'\UE09A\1D', 'ascii': r'\9A\1D', 'size': 2 * 2}, # Colors 'BLUE': {'unicode': r'\UE088', 'ascii': r'\88'}, 'SILVER': {'unicode': r'\UE089', 'ascii': r'\89'}, 'GOLD': {'unicode': r'\UE08A', 'ascii': r'\8A'}, 'RED': {'unicode': r'\UE08B', 'ascii': r'\8B'}, 'PURPLE': {'unicode': r'\UE08C', 'ascii': r'\8C'}, 'LTBROWN': {'unicode': r'\UE08D', 'ascii': r'\8D'}, 'ORANGE': {'unicode': r'\UE08E', 'ascii': r'\8E'}, 'GREEN': {'unicode': r'\UE08F', 'ascii': r'\8F'}, 'YELLOW': {'unicode': r'\UE090', 'ascii': r'\90'}, 'DKGREEN': {'unicode': r'\UE091', 'ascii': r'\91'}, 'CREAM': {'unicode': r'\UE092', 'ascii': r'\92'}, 'BROWN': {'unicode': r'\UE093', 'ascii': r'\93'}, 'WHITE': {'unicode': r'\UE094', 'ascii': r'\94'}, 'LTBLUE': {'unicode': r'\UE095', 'ascii': r'\95'}, 'GRAY': {'unicode': r'\UE096', 'ascii': r'\96'}, 'DKBLUE': {'unicode': r'\UE097', 'ascii': r'\97'}, 'BLACK': {'unicode': r'\UE098', 'ascii': r'\98'}, # Deprecated string codes 'DWORD_S': {'unicode': r'\UE07B', 'ascii': r'\7B', 'deprecated': True, 'size': 4}, 'PARAM': {'unicode': r'\UE07B', 'ascii': r'\7B', 'deprecated': True, 'size': 4}, 'WORD_S': {'unicode': r'\UE07C', 'ascii': r'\7C', 'deprecated': True, 'size': 2}, 'BYTE_S': {'unicode': r'\UE07D', 'ascii': r'\7D', 'deprecated': True}, 'WORD_U': {'unicode': r'\UE07E', 'ascii': r'\7E', 'deprecated': True, 'size': 2}, 'POP_WORD': {'unicode': r'\UE085', 'ascii': r'\85', 'deprecated': True, 'size': 2}, 'CURRENCY_QWORD': {'unicode': r'\UE09A\01', 'ascii': r'\9A\01', 'deprecated': True}, 'PUSH_WORD': {'unicode': r'\UE09A\03', 'ascii': r'\9A\03', 'deprecated': True}, 'UNPRINT': {'unicode': r'\UE09A\04', 'ascii': r'\9A\04', 'deprecated': True}, 'BYTE_HEX': {'unicode': r'\UE09A\06', 'ascii': r'\9A\06', 'deprecated': True}, 'WORD_HEX': {'unicode': r'\UE09A\07', 'ascii': r'\9A\07', 'deprecated': True, 'size': 2}, 'DWORD_HEX': {'unicode': r'\UE09A\08', 'ascii': r'\9A\08', 'deprecated': True, 'size': 4}, 'QWORD_HEX': {'unicode': r'\UE09A\0B', 'ascii': r'\9A\0B', 'deprecated': True}, 'WORD_S_TONNES': {'unicode': r'\UE09A\0D', 'ascii': r'\9A\0D', 'deprecated': True, 'size': 2}, } special_commands = [ 'P', 'G', 'G=', ] 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 for line in codecs.open(generic.find_file(custom_tags_file), "r", "utf-8"): 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 class StringCommand(object): """ Instantiated string command. @ivar name: Name of the string command. @type name: C{str} @ivar case: ??? @type case: ??? @ivar arguments: Arguments of the instantiated command. @type arguments: C{list} of ??? @ivar offset: Index to an argument of the P or G string command (ie "2" in {P 2 ...}), if available. @type offset: C{int} or C{None} @ivar str_pos: String argument index ("2" in {2:FOO..}, if available. @type str_pos: C{int} or C{None} @ivar pos: Position of the string. @type pos: L{Position} """ def __init__(self, name, str_pos, pos): assert name in commands or name in special_commands self.name = name self.case = None self.arguments = [] self.offset = None self.str_pos = str_pos self.pos = pos def set_arguments(self, arg_string): start = -1 cur = 0 quoted = False whitespace = " \t" while cur < len(arg_string): if start != -1: if (quoted and arg_string[cur] == '"') or (not quoted and arg_string[cur] in whitespace): if not quoted and self.offset is None and len(self.arguments) == 0 and isint(arg_string[start:cur]) and self.name in ('P', 'G'): self.offset = int(arg_string[start:cur]) else: self.arguments.append(arg_string[start:cur]) start = -1 elif arg_string[cur] not in whitespace: quoted = arg_string[cur] == '"' start = cur + 1 if quoted else cur cur += 1 if start != -1 and not quoted: self.arguments.append(arg_string[start:]) start = -1 return start == -1 def validate_arguments(self, lang): if lang.langid == DEFAULT_LANGUAGE: return if self.name == 'P': if not lang.has_plural_pragma(): raise generic.ScriptError("Using {P} without a ##plural pragma", self.pos) if len(self.arguments) != lang.get_num_plurals(): raise generic.ScriptError("Invalid number of arguments to plural command, expected {:d} but got {:d}".format(lang.get_num_plurals(), len(self.arguments)), self.pos) elif self.name == 'G': if not lang.has_gender_pragma(): raise generic.ScriptError("Using {G} without a ##gender pragma", self.pos) if len(self.arguments) != len(lang.genders): raise generic.ScriptError("Invalid number of arguments to gender command, expected {:d} but got {:d}".format(len(lang.genders), len(self.arguments)), self.pos) elif self.name == 'G=': if not lang.has_gender_pragma(): raise generic.ScriptError("Using {G=} without a ##gender pragma", self.pos) if len(self.arguments) != 1: raise generic.ScriptError("Invalid number of arguments to set-gender command, expected {:d} but got {:d}".format(1, len(self.arguments)), self.pos) elif len(self.arguments) != 0: raise generic.ScriptError("Unexpected arguments to command \"{}\"".format(self.name), self.pos) def parse_string(self, str_type, lang, wanted_lang_id, prev_command, stack, static_args): """ Convert the string command to output text. @param str_type: Exptected type of result text, C{"unicode"} or C{"ascii"}. @type str_type: C{str} @param lang: Language of the string. @type lang: L{Language} @param wanted_lang_id: Language-id to use for interpreting the command (this string may be from another language, eg with missing strings). @param prev_command: Argument of previous string command (parameter number, size). @type prev_command: C{tuple} or C{None} @param stack: Stack of available arguments (list of (parameter number, size)). @type stack: C{list} of C{tuple} (C{int}, C{int}) or C{None} @param static_args: Static command arguments. """ if self.name in commands: if not self.is_important_command(): return commands[self.name][str_type] # Compute position of the argument in the stack. stack_pos = 0 for (pos, size) in stack: if pos == self.str_pos: break stack_pos += size self_size = commands[self.name]['size'] stack.remove((self.str_pos, self_size)) if self.str_pos < len(static_args): if 'parse' not in commands[self.name]: raise generic.ScriptError("Provided a static argument for string command '{}' which is invalid".format(self.name), self.pos) # Parse commands using the wanted (not current) lang id, so translations are used if present return commands[self.name]['parse'](static_args[self.str_pos], wanted_lang_id) prefix = '' suffix = '' if self.case: prefix += STRING_SELECT_CASE[str_type] + '\\{:02X}'.format(self.case) if stack_pos + self_size > 8: raise generic.ScriptError("Trying to read an argument from the stack without reading the arguments before", self.pos) if self_size == 4 and stack_pos == 4: prefix += STRING_ROTATE[str_type] + STRING_ROTATE[str_type] elif self_size == 4 and stack_pos == 2: prefix += STRING_PUSH_WORD[str_type] + STRING_ROTATE[str_type] + STRING_ROTATE[str_type] suffix += STRING_SKIP[str_type] elif self_size == 2 and stack_pos == 6: prefix += STRING_ROTATE[str_type] elif self_size == 2 and stack_pos == 4: prefix += STRING_PUSH_WORD[str_type] + STRING_ROTATE[str_type] suffix += STRING_SKIP[str_type] elif self_size == 2 and stack_pos == 2: prefix += STRING_PUSH_WORD[str_type] + STRING_PUSH_WORD[str_type] + STRING_ROTATE[str_type] suffix += STRING_SKIP[str_type] + STRING_SKIP[str_type] else: assert stack_pos == 0 return prefix + commands[self.name][str_type] + suffix assert self.name in special_commands # Create a local copy because we shouldn't modify the original offset = self.offset if offset is None: if self.name == 'P': if not prev_command: raise generic.ScriptError("A plural choice list {P} has to be preceded by another string code or provide an offset", self.pos) offset = prev_command[0] else: if not stack: raise generic.ScriptError("A gender choice list {G} has to be followed by another string code or provide an offset", self.pos) offset = stack[0][0] offset -= len(static_args) if self.name == 'P': if offset < 0: return self.arguments[lang.static_plural_form(static_args[offset]) - 1] ret = BEGIN_PLURAL_CHOICE_LIST[str_type] + '\\{:02X}'.format(0x80 + offset) for idx, arg in enumerate(self.arguments): if idx == len(self.arguments) - 1: ret += CHOICE_LIST_DEFAULT[str_type] else: ret += CHOICE_LIST_ITEM[str_type] + '\\{:02X}'.format(idx + 1) ret += arg ret += CHOICE_LIST_END[str_type] return ret if self.name == 'G': if offset < 0: return self.arguments[lang.static_gender(static_args[offset]) - 1] ret = BEGIN_GENDER_CHOICE_LIST[str_type] + '\\{:02X}'.format(0x80 + offset) for idx, arg in enumerate(self.arguments): if idx == len(self.arguments) - 1: ret += CHOICE_LIST_DEFAULT[str_type] else: ret += CHOICE_LIST_ITEM[str_type] + '\\{:02X}'.format(idx + 1) ret += arg ret += CHOICE_LIST_END[str_type] return ret def get_type(self): if self.name in commands: if 'ascii' in commands[self.name]: return 'ascii' else: return 'unicode' if self.name == 'P' or self.name == 'G': for arg in self.arguments: if not is_ascii_string(arg): return 'unicode' return 'ascii' def is_important_command(self): if self.name in special_commands: return False return 'size' in commands[self.name] def get_arg_size(self): return commands[self.name]['size'] # Characters that are valid in hex numbers VALID_HEX = "0123456789abcdefABCDEF" def is_valid_hex(string): return all(c in VALID_HEX for c in string) def validate_escapes(string, pos): """ Validate that all escapes (starting with a backslash) are correct. When an invalid escape is encountered, an error is thrown @param string: String to validate @type string: C{str} @param pos: Position information @type pos: L{Position} """ i = 0 while i < len(string): # find next '\' i = string.find('\\', i) if i == -1: break if i+1 >= len(string): raise generic.ScriptError("Unexpected end-of-line encountered after '\\'", pos) if string[i+1] in ('\\', '"'): i += 2 elif string[i+1] == 'U': if i+5 >= len(string) or not is_valid_hex(string[i+2:i+6]): raise generic.ScriptError("Expected 4 hexadecimal characters after '\\U'", pos) i += 6 else: if i+2 >= len(string) or not is_valid_hex(string[i+1:i+3]): raise generic.ScriptError("Expected 2 hexadecimal characters after '\\'", pos) i += 3 class NewGRFString(object): """ A string text in a language. @ivar string: String text. @type string: C{str} @ivar cases: Mapping of cases to ... @type cases: ??? @ivar components: Split string text. @type components: C{list} of (C{str} or L{StringCommand}) @ivar pos: Position of the string text. @type pos: L{Position} """ def __init__(self, string, lang, pos): """ Construct a L{NewGRFString}, and break down the string text into text literals and string commands. @param string: String text. @type string: C{str} @param lang: Language containing this string. @type lang: L{Language} @param pos: Position of the string text. @type pos: L{Position} """ validate_escapes(string, pos) self.string = string self.cases = {} self.components = [] self.pos = pos idx = 0 while idx < len(string): if string[idx] != '{': j = string.find('{', idx) if j == -1: self.components.append(string[idx:]) break self.components.append(string[idx:j]) idx = j start = idx + 1 end = start cmd_pos = None if start >= len(string): raise generic.ScriptError("Expected '}' before end-of-line.", pos) if string[start].isdigit(): while end < len(string) and string[end].isdigit(): end += 1 if end == len(string) or string[end] != ':': raise generic.ScriptError("Error while parsing position part of string command", pos) cmd_pos = int(string[start:end]) start = end + 1 end = start #Read the command name while end < len(string) and string[end] not in '} =.': end += 1 command_name = string[start:end] if end < len(string) and string[end] == '=': command_name += '=' if command_name not in commands and command_name not in special_commands: raise generic.ScriptError("Undefined command \"{}\"".format(command_name), pos) if command_name in commands and 'deprecated' in commands[command_name]: generic.print_warning("String code '{}' has been deprecated and will be removed soon".format(command_name), pos) del commands[command_name]['deprecated'] # command = StringCommand(command_name, cmd_pos, pos) if end >= len(string): raise generic.ScriptError("Missing '}' from command \"{}\"".format(string[start:]), pos) if string[end] == '.': if command_name not in commands or 'allow_case' not in commands[command_name]: raise generic.ScriptError("Command \"{}\" can't have a case".format(command_name), pos) case_start = end + 1 end = case_start while end < len(string) and string[end] not in '} ': end += 1 case = string[case_start:end] if lang.cases is None or case not in lang.cases: raise generic.ScriptError("Invalid case-name \"{}\"".format(case), pos) command.case = lang.cases[case] if string[end] != '}': command.argument_is_assigment = string[end] == '=' arg_start = end + 1 end = string.find('}', end + 1) if end == -1 or not command.set_arguments(string[arg_start:end]): raise generic.ScriptError("Missing '}' from command \"{}\"".format(string[start:]), pos) command.validate_arguments(lang) if command_name == 'G=' and self.components: raise generic.ScriptError("Set-gender command {G=} must be at the start of the string", pos) self.components.append(command) idx = end + 1 if len(self.components) > 0 and isinstance(self.components[0], StringCommand) and self.components[0].name == 'G=': self.gender = self.components[0].arguments[0] if self.gender not in lang.genders: raise generic.ScriptError("Invalid gender name '{}'".format(self.gender), pos) self.components.pop(0) else: self.gender = None cmd_pos = 0 for cmd in self.components: if not (isinstance(cmd, StringCommand) and cmd.is_important_command()): continue if cmd.str_pos is None: cmd.str_pos = cmd_pos cmd_pos = cmd.str_pos + 1 def get_type(self): """ Get the type of string text. @return: C{"unicode"} if Unicode is required for expressing the string text, else C{"ascii"}. @rtype: C{str} """ for comp in self.components: if isinstance(comp, StringCommand): if comp.get_type() == 'unicode': return 'unicode' else: if not is_ascii_string(comp): return 'unicode' for case in list(self.cases.values()): if case.get_type() == 'unicode': return 'unicode' return 'ascii' def remove_non_default_commands(self): i = 0 while i < len(self.components): comp = self.components[i] if isinstance(comp, StringCommand): if comp.name == 'P' or comp.name == 'G': self.components[i] = comp.arguments[-1] if comp.arguments else "" i += 1 def parse_string(self, str_type, lang, wanted_lang_id, static_args): """ Convert the string text to output text. """ ret = "" stack = [(idx, size) for idx, size in enumerate(self.get_command_sizes())] prev_command = None for comp in self.components: if isinstance(comp, StringCommand): next_command = stack[0] if stack else None ret += comp.parse_string(str_type, lang, wanted_lang_id, prev_command, stack, static_args) if (comp.name in commands) and comp.is_important_command(): prev_command = next_command else: ret += comp return ret def get_command_sizes(self): """ Get the size of each string parameter. """ sizes = {} for cmd in self.components: if not (isinstance(cmd, StringCommand) and cmd.is_important_command()): continue if cmd.str_pos in sizes: raise generic.ScriptError("Two or more string commands are using the same argument", self.pos) sizes[cmd.str_pos] = cmd.get_arg_size() sizes_list = [] for idx in range(len(sizes)): if idx not in sizes: raise generic.ScriptError("String argument {:d} is not used".format(idx), self.pos) sizes_list.append(sizes[idx]) return sizes_list def match_commands(self, other_string): return self.get_command_sizes() == other_string.get_command_sizes() def isint(x, base = 10): try: int(x, base) return True except ValueError: return False NUM_PLURAL_FORMS = 13 CHOICE_LIST_ITEM = {'unicode': r'\UE09A\10', 'ascii': r'\9A\10'} CHOICE_LIST_DEFAULT = {'unicode': r'\UE09A\11', 'ascii': r'\9A\11'} CHOICE_LIST_END = {'unicode': r'\UE09A\12', 'ascii': r'\9A\12'} BEGIN_GENDER_CHOICE_LIST = {'unicode': r'\UE09A\13', 'ascii': r'\9A\13'} BEGIN_CASE_CHOICE_LIST = {'unicode': r'\UE09A\14', 'ascii': r'\9A\14'} BEGIN_PLURAL_CHOICE_LIST = {'unicode': r'\UE09A\15', 'ascii': r'\9A\15'} SET_STRING_GENDER = {'unicode': r'\UE09A\0E', 'ascii': r'\9A\0E'} STRING_SKIP = {'unicode': r'\UE085', 'ascii': r'\85'} STRING_ROTATE = {'unicode': r'\UE086', 'ascii': r'\86'} STRING_PUSH_WORD = {'unicode': r'\UE09A\03\20\20', 'ascii': r'\9A\03\20\20'} STRING_SELECT_CASE = {'unicode': r'\UE09A\0F', 'ascii': r'\9A\0F'} # Information about languages, borrowed from OpenTTD. # (isocode name, grf langid, plural number) LANG_INFOS = [('af_ZA', 0x1b, 0), ('ar_EG', 0x14, 1), ('be_BY', 0x10, 6), ('bg_BG', 0x18, 0), ('ca_ES', 0x22, 0), ('cs_CZ', 0x15, 10), ('cv_RU', 0x0b, 0), ('cy_GB', 0x0f, 0), ('da_DK', 0x2d, 0), ('de_DE', 0x02, 0), ('el_GR', 0x1e, 2), ('en_AU', 0x3d, 0), ('en_GB', 0x01, 0), ('en_US', 0x00, 0), ('eo_EO', 0x05, 0), ('es_ES', 0x04, 0), ('et_EE', 0x34, 0), ('eu_ES', 0x21, 0), ('fa_IR', 0x62, 0), ('fi_FI', 0x35, 0), ('fo_FO', 0x12, 0), ('fr_FR', 0x03, 2), ('fy_NL', 0x32, 0), ('ga_IE', 0x08, 4), ('gd_GB', 0x13, 13), ('gl_ES', 0x31, 0), ('he_IL', 0x61, 0), ('hr_HR', 0x38, 6), ('hu_HU', 0x24, 2), ('id_ID', 0x5a, 1), ('io_IO', 0x06, 0), ('is_IS', 0x29, 0), ('it_IT', 0x27, 0), ('ja_JP', 0x39, 1), ('ko_KR', 0x3a, 11), ('la_VA', 0x66, 0), ('lb_LU', 0x23, 0), ('lt_LT', 0x2b, 5), ('lv_LV', 0x2a, 3), ('mk_MK', 0x26, 0), ('mr_IN', 0x11, 0), ('ms_MY', 0x3c, 0), ('mt_MT', 0x09, 12), ('nb_NO', 0x2f, 0), ('nl_NL', 0x1f, 0), ('nn_NO', 0x0e, 0), ('pl_PL', 0x30, 7), ('pt_BR', 0x37, 2), ('pt_PT', 0x36, 0), ('ro_RO', 0x28, 0), ('ru_RU', 0x07, 6), ('sk_SK', 0x16, 10), ('sl_SI', 0x2c, 8), ('sr_RS', 0x0d, 6), ('sv_SE', 0x2e, 0), ('ta_IN', 0x0a, 0), ('th_TH', 0x42, 1), ('tr_TR', 0x3e, 1), ('uk_UA', 0x33, 6), ('ur_PK', 0x5c, 0), ('vi_VN', 0x54, 1), ('zh_CN', 0x56, 1), ('zh_TW', 0x0c, 1), ] LANG_NAMES = dict((lng[0], lng[1]) for lng in LANG_INFOS) LANG_PLURALS = dict((lng[1], lng[2]) for lng in LANG_INFOS) class Language(object): """ @ivar default: Whether the language is the default language. @type default: C{bool} @ivar langid: Language id of the language, if known. @type langid: C{None} or C{int} @ivar plural: Plural type. @type plural: C{None} or C{int} @ivar genders: @type genders: @ivar gender_map: @type gender_map: @ivar cases: @type cases: @ivar case_map: @type case_map: @ivar strings: Language strings of the file. @type strings: C{dict} of """ def __init__(self, default): self.default = default self.langid = None self.plural = None self.genders = None self.gender_map = {} self.cases = None self.case_map = {} self.strings = {} def get_num_plurals(self): if self.plural is None: return 0 num_plurals = { 0: 2, 1: 1, 2: 2, 3: 3, 4: 5, 5: 3, 6: 3, 7: 3, 8: 4, 9: 2, 10: 3, 11: 2, 12: 4, 13: 4, } return num_plurals[self.plural] def has_plural_pragma(self): return self.plural is not None def has_gender_pragma(self): return self.genders is not None def static_gender(self, expr): import nml.expression if isinstance(expr, nml.expression.StringLiteral): return len(self.genders) if not isinstance(expr, nml.expression.String): raise generic.ScriptError("{G} can only refer to a string argument") parsed = self.get_string(expr, self.langid) if parsed.find(SET_STRING_GENDER['ascii']) == 0: return int(parsed[len(SET_STRING_GENDER['ascii']) + 1 : len(SET_STRING_GENDER['ascii']) + 3], 16) if parsed.find(SET_STRING_GENDER['unicode']) == 0: return int(parsed[len(SET_STRING_GENDER['unicode']) + 1 : len(SET_STRING_GENDER['unicode']) + 3], 16) return len(self.genders) def static_plural_form(self, expr): #Return values are the same as "Plural index" here: #http://newgrf-specs.tt-wiki.net/wiki/StringCodes#Using_plural_forms val = expr.reduce_constant().value if self.plural == 0: return 1 if val == 1 else 2 if self.plural == 1: return 1 if self.plural == 2: return 1 if val in (0, 1) else 2 if self.plural == 3: if val % 10 == 1 and val % 100 != 11: return 1 return 2 if val == 0 else 3 if self.plural == 4: if val == 1: return 1 if val == 2: return 2 if 3 <= val <= 6: return 3 if 7 <= val <= 10: return 4 return 5 if self.plural == 5: if val % 10 == 1 and val % 100 != 11: return 1 if 2 <= (val % 10) <= 9 and not 12 <= (val % 100) <= 19: return 2 return 3 if self.plural == 6: if val % 10 == 1 and val % 100 != 11: return 1 if 2 <= (val % 10) <= 4 and not 12 <= (val % 100) <= 14: return 2 return 3 if self.plural == 7: if val == 0: return 1 if 2 <= (val % 10) <= 4 and not 12 <= (val % 100) <= 14: return 2 return 3 if self.plural == 8: if val % 100 == 1: return 1 if val % 100 == 2: return 2 if val % 100 in (3, 4): return 3 return 4 if self.plural == 9: if val % 10 == 1 and val % 100 != 11: return 1 return 2 if self.plural == 10: if val == 1: return 1 if 2 <= val <= 4: return 2 return 3 if self.plural == 11: if val % 10 in (0, 1, 3, 6, 7, 8): return 1 return 2 if self.plural == 12: if val == 1: return 1 if val == 0 or 2 <= (val % 100) <= 10: return 2 if 11 <= (val % 100) <= 19: return 3 return 4 if self.plural == 13: if val == 1 or val == 11: return 1 if val == 2 or val == 12: return 2 if (val >= 3 and val <= 10) or (val >= 13 and val <=19): return 3 return 4 assert False, "Unknown plural type" def get_string(self, string, lang_id): """ Lookup up a string by name/params and return the actual created string @param string: String object @type string: L{expression.String} @param lang_id: Language ID we are actually looking for. This may differ from the ID of this language, if the string is missing from the target language. @type lang_id: C{int} @return: The created string @rtype: C{str} """ string_id = string.name.value assert isinstance(string_id, str) assert string_id in self.strings assert lang_id == self.langid or self.langid == DEFAULT_LANGUAGE str_type = self.strings[string_id].get_type() parsed_string = "" if self.strings[string_id].gender is not None: parsed_string += SET_STRING_GENDER[str_type] + '\\{:02X}'.format(self.genders[self.strings[string_id].gender]) if len(self.strings[string_id].cases) > 0: parsed_string += BEGIN_CASE_CHOICE_LIST[str_type] for case_name, case_string in list(self.strings[string_id].cases.items()): case_id = self.cases[case_name] parsed_string += CHOICE_LIST_ITEM[str_type] + '\\{:02X}'.format(case_id) + case_string.parse_string(str_type, self, lang_id, string.params) parsed_string += CHOICE_LIST_DEFAULT[str_type] parsed_string += self.strings[string_id].parse_string(str_type, self, lang_id, string.params) if len(self.strings[string_id].cases) > 0: parsed_string += CHOICE_LIST_END[str_type] return parsed_string 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[1].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 handle_plural(self, data, pos): """ Handle a 'plural' pragma. @param data: Data of the pragma. @type data: C{str} """ if self.plural is not None: raise generic.ScriptError("plural form already set", pos) try: # Explicitly force base 10 like in OpenTTD's lang file value = int(data[1], 10) except ValueError: raise generic.ScriptError("Invalid plural form", pos) if value < 0 or value > NUM_PLURAL_FORMS: raise generic.ScriptError("Invalid plural form", pos) self.plural = value self.check_expected_plural(pos) def check_expected_plural(self, pos): """ Check whether the provided plural form of the language file matches with the expected value for that language. If not, raise an error. @param pos: Position of the ##plural line, if position is available. @type pos: L{Position} """ if self.plural is None or self.langid is None: return expected = LANG_PLURALS.get(self.langid) if expected is not None and self.plural != expected: msg = "Language with language id 0x{:02x} has plural form {:d} while the language uses plural form {:d}." msg = msg.format(self.langid, self.plural, expected) raise generic.ScriptError(msg, pos) def handle_gender(self, data, pos): """ Handle a 'gender' pragma. @param data: Data of the pragma. @type data: C{str} """ if self.genders is not None: raise generic.ScriptError("Genders already defined", pos) self.genders = {} for idx, gender in enumerate(data[1].split()): self.genders[gender] = idx + 1 self.gender_map[gender] = [] def handle_map_gender(self, data, pos): """ Handle a 'map_gender' pragma. @param data: Data of the pragma. @type data: C{str} """ if self.genders is None: raise generic.ScriptError("##map_gender is not allowed before ##gender", pos) genders = data[1].split() if len(genders) != 2: raise generic.ScriptError("Invalid ##map_gender line", pos) if genders[0] not in self.genders: raise generic.ScriptError("Trying to map non-existing gender '{}'".format(genders[0]), pos) self.gender_map[genders[0]].append(genders[1]) def handle_case(self, data, pos): """ Handle a 'case' pragma. @param data: Data of the pragma. @type data: C{str} """ if self.cases is not None: raise generic.ScriptError("Cases already defined", pos) self.cases = {} for idx, case in enumerate(data[1].split()): self.cases[case] = idx + 1 self.case_map[case] = [] def handle_map_case(self, data, pos): """ Handle a 'map_case' pragma. @param data: Data of the pragma. @type data: C{str} """ if self.cases is None: raise generic.ScriptError("##map_case is not allowed before ##case", pos) cases = data[1].split() if len(cases) != 2: raise generic.ScriptError("Invalid ##map_case line", pos) if cases[0] not in self.cases: raise generic.ScriptError("Trying to map non-existing case '{}'".format(cases[0]), pos) self.case_map[cases[0]].append(cases[1]) def handle_text(self, data, pos): """ Handle a text string. @param data: Data of the pragma. @type data: C{str} """ _type, string, case, value = data if not re.match("[A-Za-z_0-9]+$", string): raise generic.ScriptError("Invalid string name \"{}\"".format(string), pos) if string in self.strings and case is None: raise generic.ScriptError("String name \"{}\" is used multiple times".format(string), pos) if self.default: self.strings[string] = NewGRFString(value, self, pos) self.strings[string].remove_non_default_commands() else: if string not in default_lang.strings: generic.print_warning("String name \"{}\" does not exist in master file".format(string), pos) return newgrf_string = NewGRFString(value, self, pos) if not default_lang.strings[string].match_commands(newgrf_string): generic.print_warning("String commands don't match with master file \"{}\"".format(DEFAULT_LANGNAME), pos) return if case is None: self.strings[string] = newgrf_string else: if string not in self.strings: generic.print_warning("String with case used before the base string", pos) return if self.cases is None or case not in self.cases: generic.print_warning("Invalid case name \"{}\"".format(case), pos) return if case in self.strings[string].cases: raise generic.ScriptError("String name \"{}.{}\" is used multiple times".format(string, case), pos) if newgrf_string.gender: generic.print_warning("Case-strings can't set the gender, only the base string can", pos) return self.strings[string].cases[case] = newgrf_string def scan_line(self, line, pos): """ Scan a line of a language file. @param line: Line to scan. @type line: C{str} @param pos: Position information of the line. @type pos: L{Position} @return: Contents of the scanned line: - C{None} Nothing of interest found. - (, ) A pragma line has been found. - ('string', , ) A string with optional case has been found. @rtype: C{None} or a C{tuple} """ if len(line) == 0: return None # Silently ignore empty lines. if line[0] == '#': if len(line) > 2 and line[1] == '#' and line[2] != '#': # "##pragma" line. if self.default: return None # Default language ignores all pragmas. if line[:12] == "##grflangid ": return ('grflangid', line[12:]) if line[:9] == "##plural ": return ('plural', line[9:]) if line[:9] == "##gender ": return ('gender', line[9:]) if line[:13] == "##map_gender ": return ('map_gender', line[13:]) if line[:11] == "##map_case ": return ('map_case', line[11:]) if line[:7] == "##case ": return ('case', line[7:]) raise generic.ScriptError("Invalid pragma", pos) return None # Normal comment # Must be a line defining a string. i = line.find(':') if i == -1: raise generic.ScriptError("Line has no ':' delimiter", pos) name = line[:i].strip() value = line[i + 1:] i = name.find('.') # Find a case. if i > 0: case = name[i + 1:] name = name[:i] else: case = None if self.default and case is not None: return None # Ignore cases for the default language return ('string', name, case, value) def handle_string(self, line, pos): funcs = { 'grflangid' : self.handle_grflangid, 'plural' : self.handle_plural, 'gender' : self.handle_gender, 'map_gender' : self.handle_map_gender, 'map_case' : self.handle_map_case, 'case' : self.handle_case, 'string' : self.handle_text } res = self.scan_line(line, pos) if res is not None: funcs[res[0]](res, pos) default_lang = Language(True) default_lang.langid = DEFAULT_LANGUAGE langs = [] 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 codecs.open(generic.find_file(filename), "r", "utf-8") as f: for idx, line in enumerate(f): 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 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() nml-0.4.4/nml/expression/0000755000567200056720000000000012643457623016450 5ustar jenkinsjenkins00000000000000nml-0.4.4/nml/expression/array.py0000644000567200056720000000242412643457545020145 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from .base_expression import Expression class Array(Expression): def __init__(self, values, pos): Expression.__init__(self, pos) self.values = values def debug_print(self, indentation): generic.print_dbg(indentation, 'Array of values:') for v in self.values: v.debug_print(indentation + 2) def __str__(self): return '[' + ', '.join([str(expr) for expr in self.values]) + ']' def reduce(self, id_dicts = [], unknown_id_fatal = True): return Array([val.reduce(id_dicts, unknown_id_fatal) for val in self.values], self.pos) nml-0.4.4/nml/expression/variable.py0000644000567200056720000000763612643457545020626 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from .base_expression import Type, Expression, ConstantNumeric class Variable(Expression): def __init__(self, num, shift = None, mask = None, param = None, pos = None): Expression.__init__(self, pos) self.num = num self.shift = shift if shift is not None else ConstantNumeric(0) self.mask = mask if mask is not None else ConstantNumeric(0xFFFFFFFF) self.param = param self.add = None self.div = None self.mod = None self.extra_params = [] def debug_print(self, indentation): generic.print_dbg(indentation, 'Action2 variable') self.num.debug_print(indentation + 2) if self.param is not None: generic.print_dbg(indentation + 2, 'Parameter:') if isinstance(self.param, str): generic.print_dbg(indentation + 4, 'Procedure call:', self.param) else: self.param.debug_print(indentation + 4) if len(self.extra_params) > 0: generic.print_dbg(indentation + 2, 'Extra parameters:') for extra_param in self.extra_params: extra_param.debug_print(indentation + 4) def __str__(self): num = "0x{:02X}".format(self.num.value) if isinstance(self.num, ConstantNumeric) else str(self.num) ret = 'var[{}, {}, {}'.format(num, self.shift, self.mask) if self.param is not None: ret += ', {}'.format(self.param) ret += ']' if self.add is not None: ret = '({} + {})'.format(ret, self.add) if self.div is not None: ret = '({} / {})'.format(ret, self.div) if self.mod is not None: ret = '({} % {})'.format(ret, self.mod) return ret def reduce(self, id_dicts = [], unknown_id_fatal = True): num = self.num.reduce(id_dicts) shift = self.shift.reduce(id_dicts) mask = self.mask.reduce(id_dicts) param = self.param.reduce(id_dicts) if self.param is not None else None if num.type() != Type.INTEGER or shift.type() != Type.INTEGER or mask.type() != Type.INTEGER or \ (param is not None and param.type() != Type.INTEGER): raise generic.ScriptError("All parts of a variable access must be integers.", self.pos) var = Variable(num, shift, mask, param, self.pos) var.add = None if self.add is None else self.add.reduce(id_dicts) var.div = None if self.div is None else self.div.reduce(id_dicts) var.mod = None if self.mod is None else self.mod.reduce(id_dicts) var.extra_params = [(extra_param[0], extra_param[1].reduce(id_dicts)) for extra_param in self.extra_params] return var def supported_by_action2(self, raise_error): return True def supported_by_actionD(self, raise_error): if raise_error: if isinstance(self.num, ConstantNumeric): if self.num.value == 0x7C: raise generic.ScriptError("LOAD_PERM is only available in switch-blocks.", self.pos) if self.num.value == 0x7D: raise generic.ScriptError("LOAD_TEMP is only available in switch-blocks.", self.pos) raise generic.ScriptError("Variable accesses are not supported outside of switch-blocks.", self.pos) return False nml-0.4.4/nml/expression/patch_variable.py0000644000567200056720000000304312643457545021771 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from .base_expression import Expression class PatchVariable(Expression): """ Class for reading so-called 'patch variables' via a special ActionD @ivar num: Variable number to read @type num: C{int} """ def __init__(self, num, pos = None): Expression.__init__(self, pos) self.num = num def debug_print(self, indentation): generic.print_dbg(indentation, 'PatchVariable:', self.num) def __str__(self): return "PatchVariable({:d})".format(self.num) def reduce(self, id_dicts = [], unknown_id_fatal = True): return self def supported_by_action2(self, raise_error): if raise_error: raise generic.ScriptError("Reading patch variables is not supported in a switch-block.", self.pos) return False def supported_by_actionD(self, raise_error): return True nml-0.4.4/nml/expression/functioncall.py0000644000567200056720000006555212643457545021523 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import datetime, calendar, math from nml import generic, nmlop from .base_expression import Type, Expression, ConstantNumeric, ConstantFloat from .binop import BinOp from .bitmask import BitMask from .parameter import parse_string_to_dword from .storage_op import StorageOp from .string_literal import StringLiteral from .ternaryop import TernaryOp from . import identifier from functools import reduce class FunctionCall(Expression): def __init__(self, name, params, pos): Expression.__init__(self, pos) self.name = name self.params = params def debug_print(self, indentation): generic.print_dbg(indentation, 'Call function: ' + self.name.value) for param in self.params: generic.print_dbg(indentation + 2, 'Parameter:') param.debug_print(indentation + 4) def __str__(self): ret = '{}({})'.format(self.name, ', '.join(str(param) for param in self.params)) return ret def reduce(self, id_dicts = [], unknown_id_fatal = True): # At this point we don't care about invalid arguments, they'll be handled later. identifier.ignore_all_invalid_ids = True params = [param.reduce(id_dicts, unknown_id_fatal = False) for param in self.params] identifier.ignore_all_invalid_ids = False if self.name.value in function_table: func = function_table[self.name.value] val = func(self.name.value, params, self.pos) return val.reduce(id_dicts) else: #try user-defined functions func_ptr = self.name.reduce(id_dicts, unknown_id_fatal = False, search_func_ptr = True) if func_ptr != self.name: # we found something! if func_ptr.type() == Type.SPRITEGROUP_REF: func_ptr.param_list = params return func_ptr if func_ptr.type() != Type.FUNCTION_PTR: raise generic.ScriptError("'{}' is defined, but it is not a function.".format(self.name.value), self.pos) return func_ptr.call(params) if unknown_id_fatal: raise generic.ScriptError("'{}' is not defined as a function.".format(self.name.value), self.pos) return FunctionCall(self.name, params, self.pos) class SpecialCheck(Expression): """ Action7/9 special check (e.g. to see whether a cargo is defined) @ivar op: Action7/9 operator to use @type op: (C{int}, C{str})-tuple @ivar varnum: Variable number to read @type varnum: C{int} @ivar results: Result of the check when skipping (0) or not skipping (1) @type results: (C{int}, C{int})-tuple @ivar value: Value to test @type value: C{int} @ivar varsize: Varsize for the action7/9 check @type varsize: C{int} @ivar mask: Mask to to test only certain bits of the value @type mask: C{int} @ivar pos: Position information @type pos: L{Position} """ def __init__(self, op, varnum, results, value, to_string, varsize = 4, mask = None, pos = None): Expression.__init__(self, pos) self.op = op self.varnum = varnum self.results = results self.value = value self.to_string = to_string self.varsize = varsize; self.mask = mask def reduce(self, id_dicts = [], unknown_id_fatal = True): return self def __str__(self): return self.to_string def supported_by_actionD(self, raise_error): return True class GRMOp(Expression): def __init__(self, op, feature, count, to_string, pos = None): Expression.__init__(self, pos) self.op = op self.feature = feature self.count = count self.to_string = to_string def reduce(self, id_dicts = [], unknown_id_fatal = True): return self def __str__(self): return self.to_string(self) def supported_by_actionD(self, raise_error): return True #{ Builtin functions def builtin_min(name, args, pos): """ min(...) builtin function. @return Lowest value of the given arguments. """ if len(args) < 2: raise generic.ScriptError("min() requires at least 2 arguments", pos) return reduce(lambda x, y: BinOp(nmlop.MIN, x, y, pos), args) def builtin_max(name, args, pos): """ max(...) builtin function. @return Heighest value of the given arguments. """ if len(args) < 2: raise generic.ScriptError("max() requires at least 2 arguments", pos) return reduce(lambda x, y: BinOp(nmlop.MAX, x, y, pos), args) 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 builtin_day_of_year(name, args, pos): """ day_of_year(month, day) builtin function. @return Day of the year, assuming February has 28 days. """ if len(args) != 2: raise generic.ScriptError(name + "() must have a month and a day parameter", pos) month = args[0].reduce() if not isinstance(month, ConstantNumeric): raise generic.ScriptError('Month should be a compile-time constant.', month.pos) if month.value < 1 or month.value > 12: raise generic.ScriptError('Month should be a value between 1 and 12.', month.pos) day = args[1].reduce() if not isinstance(day, ConstantNumeric): raise generic.ScriptError('Day should be a compile-time constant.', day.pos) # Mapping of month to number of days in that month. number_days = {1: 31, 2: 28, 3: 31, 4: 30, 5: 31, 6: 30, 7: 31, 8: 31, 9: 30, 10: 31, 11: 30, 12: 31} if day.value < 1 or day.value > number_days[month.value]: raise generic.ScriptError('Day should be value between 1 and {:d}.'.format(number_days[month.value]), day.pos) return ConstantNumeric(datetime.date(1, month.value, day.value).toordinal(), pos) def builtin_storage(name, args, pos): """ Accesses to temporary / persistent storage """ return StorageOp(name, args, pos) def builtin_ucmp(name, args, pos): if len(args) != 2: raise generic.ScriptError(name + "() must have exactly two parameters", pos) return BinOp(nmlop.VACT2_UCMP, args[0], args[1], pos) def builtin_cmp(name, args, pos): if len(args) != 2: raise generic.ScriptError(name + "() must have exactly two parameters", pos) return BinOp(nmlop.VACT2_CMP, args[0], args[1], pos) def builtin_rotate(name, args, pos): if len(args) != 2: raise generic.ScriptError(name + "() must have exactly two parameters", pos) return BinOp(nmlop.ROT_RIGHT, args[0], args[1], 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 builtin_getbits(name, args, pos): """ getbits(value, first, amount) builtin function. @return Extract C{amount} bits starting at C{first} from C{value}, that is (C{value} >> C{first}) & (1 << C{amount} - 1) """ if len(args) != 3: raise generic.ScriptError(name + "() must have exactly three parameters", pos) # getbits(value, first, amount) = (value >> first) & ((0xFFFFFFFF << amount) ^ 0xFFFFFFFF) part1 = BinOp(nmlop.SHIFTU_RIGHT, args[0], args[1], pos) part2 = BinOp(nmlop.SHIFT_LEFT, ConstantNumeric(0xFFFFFFFF), args[2], pos) part3 = BinOp(nmlop.XOR, part2, ConstantNumeric(0xFFFFFFFF), pos) return BinOp(nmlop.AND, part1, part3, pos) def builtin_version_openttd(name, args, pos): """ version_openttd(major, minor, revision[, build]) builtin function. @return The version information encoded in a double-word. """ if len(args) > 4 or len(args) < 3: raise generic.ScriptError(name + "() must have 3 or 4 parameters", pos) major = args[0].reduce_constant().value minor = args[1].reduce_constant().value revision = args[2].reduce_constant().value build = args[3].reduce_constant().value if len(args) == 4 else 0x80000 return ConstantNumeric((major << 28) | (minor << 24) | (revision << 20) | build) def builtin_cargotype_available(name, args, pos): """ cargotype_available(cargo_label) builtin function. @return 1 if the cargo 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((0x0B, r'\7c'), 0, (0, 1), parse_string_to_dword(label), "{}({})".format(name, str(label)), pos = args[0].pos) def builtin_railtype_available(name, args, pos): """ railtype_available(cargo_label) builtin function. @return 1 if the railtype 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((0x0D, None), 0, (0, 1), parse_string_to_dword(label), "{}({})".format(name, str(label)), pos = args[0].pos) def builtin_grf_status(name, args, pos): """ grf_(current_status|future_status|order_behind)(grfid[, mask]) builtin function. @return 1 if the grf is, or will be, active, 0 otherwise. """ if len(args) not in (1, 2): raise generic.ScriptError(name + "() must have 1 or 2 parameters", pos) labels = [label.reduce() for label in args] mask = parse_string_to_dword(labels[1]) if len(labels) > 1 else None if name == 'grf_current_status': op = (0x06, r'\7G') results = (1, 0) elif name == 'grf_future_status': op = (0x0A, r'\7gg') results = (0, 1) elif name == 'grf_order_behind': op = (0x08, r'\7gG') results = (0, 1) else: assert False, "Unknown grf status function" if mask is None: string = "{}({})".format(name, str(labels)) varsize = 4 else: string = "{}({}, {})".format(name, str(labels), str(mask)) varsize = 8 return SpecialCheck(op, 0x88, results, parse_string_to_dword(labels[0]), string, varsize, mask, args[0].pos) def builtin_visual_effect_and_powered(name, args, pos): """ Builtin function, used in two forms: visual_effect_and_powered(effect, offset, powered) visual_effect(effect, offset) Use this to set the vehicle property visual_effect[_and_powered] and for the callback VEH_CB_VISUAL_EFFECT[_AND_POWERED] """ arg_len = 2 if name == 'visual_effect' else 3 if len(args) != arg_len: raise generic.ScriptError(name + "() must have {:d} parameters".format(arg_len), pos) from nml import global_constants effect = args[0].reduce_constant(global_constants.const_list).value offset = BinOp(nmlop.ADD, args[1], ConstantNumeric(8), args[1].pos).reduce_constant().value generic.check_range(offset, 0, 0x0F, "offset in function " + name, pos) if arg_len == 3: powered = args[2].reduce_constant(global_constants.const_list).value if powered != 0 and powered != 0x80: raise generic.ScriptError("3rd argument to visual_effect_and_powered (powered) must be either ENABLE_WAGON_POWER or DISABLE_WAGON_POWER", pos) else: powered = 0 return ConstantNumeric(effect | offset | powered) def builtin_create_effect(name, args, pos): """ Builtin function: create_effect(effect_sprite, l_x_offset, t_y_offset, z_offset) Use this to set the values for temporary storages 100+x in the callback create_effect """ if len(args) != 4: raise generic.ScriptError(name + "() must have 4 parameters", pos) from nml import global_constants sprite = args[0].reduce_constant(global_constants.const_list).value offset1 = args[1].reduce_constant().value offset2 = args[2].reduce_constant().value offset3 = args[3].reduce_constant().value generic.check_range(sprite, 0, 255, "effect_sprite in function " + name, args[0].pos) generic.check_range(offset1, -128, 127, "l_x_offset in function " + name, args[1].pos) generic.check_range(offset2, -128, 127, "t_y_offset in function " + name, args[2].pos) generic.check_range(offset3, -128, 127, "z_offset in function " + name, args[3].pos) return ConstantNumeric(sprite | (offset1 & 0xFF) << 8 | (offset2 & 0xFF) << 16 | (offset3 & 0xFF) << 24) def builtin_str2number(name, args, pos): if len(args) != 1: raise generic.ScriptError(name + "() must have 1 parameter", pos) return ConstantNumeric(parse_string_to_dword(args[0])) def builtin_cargotype(name, args, pos): if len(args) != 1: raise generic.ScriptError(name + "() must have 1 parameter", pos) from nml import global_constants if not isinstance(args[0], StringLiteral) or args[0].value not in global_constants.cargo_numbers: raise generic.ScriptError("Parameter for " + name + "() must be a string literal that is also in your cargo table", pos) return ConstantNumeric(global_constants.cargo_numbers[args[0].value]) def builtin_railtype(name, args, pos): if len(args) != 1: raise generic.ScriptError(name + "() must have 1 parameter", pos) from nml import global_constants if not isinstance(args[0], StringLiteral) or args[0].value not in global_constants.railtype_table: raise generic.ScriptError("Parameter for " + name + "() must be a string literal that is also in your railtype table", pos) return ConstantNumeric(global_constants.railtype_table[args[0].value]) def builtin_reserve_sprites(name, args, pos): if len(args) != 1: raise generic.ScriptError(name + "() must have 1 parameter", pos) count = args[0].reduce_constant() func = lambda x: '{}({:d})'.format(name, count.value) return GRMOp(nmlop.GRM_RESERVE, 0x08, count.value, func, pos) 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 builtin_trigonometric(name, args, pos): if len(args) != 1: raise generic.ScriptError(name + "() must have 1 parameter", pos) val = args[0].reduce() if not isinstance(val, (ConstantNumeric, ConstantFloat)): raise generic.ScriptError("Parameter for " + name + "() must be a constant", pos) trigonometric_func_table = { 'acos': math.acos, 'asin': math.asin, 'atan': math.atan, 'cos': math.cos, 'sin': math.sin, 'tan': math.tan, } return ConstantFloat(trigonometric_func_table[name](val.value), val.pos) def builtin_int(name, args, pos): if len(args) != 1: raise generic.ScriptError(name + "() must have 1 parameter", pos) val = args[0].reduce() if not isinstance(val, (ConstantNumeric, ConstantFloat)): raise generic.ScriptError("Parameter for " + name + "() must be a constant", pos) return ConstantNumeric(int(val.value), val.pos) def builtin_abs(name, args, pos): if len(args) != 1: raise generic.ScriptError(name + "() must have 1 parameter", pos) guard = BinOp(nmlop.CMP_LT, args[0], ConstantNumeric(0), args[0].pos) return TernaryOp(guard, BinOp(nmlop.SUB, ConstantNumeric(0), args[0], args[0].pos), args[0], args[0].pos).reduce() def builtin_sound_file(name, args, pos): from nml.actions import action11 if len(args) not in (1, 2): raise generic.ScriptError(name + "() must have 1 or 2 parameters", pos) if not isinstance(args[0], StringLiteral): raise generic.ScriptError("Parameter for " + name + "() must be a string literal", pos) volume = args[1].reduce_constant().value if len(args) >= 2 else 100 generic.check_range(volume, 0, 100, "sound volume", pos) return ConstantNumeric(action11.add_sound( (args[0].value, volume), pos), 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_relative_coord(name, args, pos): """ relative_coord(x, y) builtin function. @return Coordinates in 0xYYXX format. """ if len(args) != 2: raise generic.ScriptError(name + "() must have x and y coordinates as parameters", pos) if isinstance(args[0], ConstantNumeric): generic.check_range(args[0].value, 0, 255, "Argument of '{}'".format(name), args[0].pos) if isinstance(args[1], ConstantNumeric): generic.check_range(args[1].value, 0, 255, "Argument of '{}'".format(name), args[1].pos) x_coord = BinOp(nmlop.AND, args[0], ConstantNumeric(0xFF), args[0].pos) y_coord = BinOp(nmlop.AND, args[1], ConstantNumeric(0xFF), args[1].pos) # Shift Y to its position. y_coord = BinOp(nmlop.SHIFT_LEFT, y_coord, ConstantNumeric(8), y_coord.pos) return BinOp(nmlop.OR, x_coord, y_coord, pos) def builtin_num_corners_raised(name, args, pos): """ num_corners_raised(slope) builtin function. slope is a 5-bit value @return Number of raised corners in a slope (4 for steep slopes) """ if len(args) != 1: raise generic.ScriptError(name + "() must have 1 parameter", pos) slope = args[0] # The returned value is ((slope x 0x8421) & 0x11111) % 0xF # Explanation in steps: (numbers in binary) # - Masking constrains the slope to 5 bits, just to be sure (a|bcde) # - Multiplication creates 4 copies of those bits (abcd|eabc|deab|cdea|bcde) # - And-masking leaves only the lowest bit in each nibble (000d|000c|000b|000a|000e) # - The modulus operation adds one to the output for each set bit # - We now have the count of bits in the slope, which is wat we want. yay! slope = BinOp(nmlop.AND, slope, ConstantNumeric(0x1F), pos) slope = BinOp(nmlop.MUL, slope, ConstantNumeric(0x8421), pos) slope = BinOp(nmlop.AND, slope, ConstantNumeric(0x11111), pos) return BinOp(nmlop.MOD, slope, ConstantNumeric(0xF), pos) def builtin_slope_to_sprite_offset(name, args, pos): """ builtin function slope_to_sprite_offset(slope) @return sprite offset to use """ if len(args) != 1: raise generic.ScriptError(name + "() must have 1 parameter", pos) if isinstance(args[0], ConstantNumeric): generic.check_range(args[0].value, 0, 15, "Argument of '{}'".format(name), args[0].pos) # step 1: ((slope >= 0) & (slope <= 14)) * slope # This handles all non-steep slopes expr = BinOp(nmlop.AND, BinOp(nmlop.CMP_LE, args[0], ConstantNumeric(14), pos), BinOp(nmlop.CMP_GE, args[0], ConstantNumeric(0), pos), pos) expr = BinOp(nmlop.MUL, expr, args[0], pos) # Now handle the steep slopes separately # So add (slope == SLOPE_XX) * offset_of_SLOPE_XX for each steep slope steep_slopes = [(23, 16), (27, 17), (29, 15), (30, 18)] for slope, offset in steep_slopes: to_add = BinOp(nmlop.MUL, BinOp(nmlop.CMP_EQ, args[0], ConstantNumeric(slope), pos), ConstantNumeric(offset), pos) expr = BinOp(nmlop.ADD, expr, to_add, pos) return expr def builtin_palette_1cc(name, args, pos): """ palette_1cc(colour) builtin function. @return Recolour sprite to use """ if len(args) != 1: raise generic.ScriptError(name + "() must have 1 parameter", pos) if isinstance(args[0], ConstantNumeric): generic.check_range(args[0].value, 0, 15, "Argument of '{}'".format(name), args[0].pos) return BinOp(nmlop.ADD, args[0], ConstantNumeric(775), pos) def builtin_palette_2cc(name, args, pos): """ palette_2cc(colour1, colour2) builtin function. @return Recolour sprite to use """ if len(args) != 2: raise generic.ScriptError(name + "() must have 2 parameters", pos) for i in range(0, 2): if isinstance(args[i], ConstantNumeric): generic.check_range(args[i].value, 0, 15, "Argument of '{}'".format(name), args[i].pos) col2 = BinOp(nmlop.MUL, args[1], ConstantNumeric(16), pos) col12 = BinOp(nmlop.ADD, col2, args[0], pos) # Base sprite is not a constant from nml import global_constants base = global_constants.patch_variable(global_constants.patch_variables['base_sprite_2cc'], pos) return BinOp(nmlop.ADD, col12, base, pos) def builtin_vehicle_curv_info(name, args, pos): """ vehicle_curv_info(prev_cur, cur_next) builtin function @return Value to use with vehicle var curv_info """ if len(args) != 2: raise generic.ScriptError(name + "() must have 2 parameters", pos) for arg in args: if isinstance(arg, ConstantNumeric): generic.check_range(arg.value, -2, 2, "Argument of '{}'".format(name), arg.pos) args = [BinOp(nmlop.AND, arg, ConstantNumeric(0xF), pos) for arg in args] cur_next = BinOp(nmlop.SHIFT_LEFT, args[1], ConstantNumeric(8), pos) return BinOp(nmlop.OR, args[0], cur_next, pos) def builtin_format_string(name, args, pos): """ format_string(format, ... args ..) builtin function @return Formatted string """ if len(args) < 1: raise generic.ScriptError(name + "() must have at least one parameter", pos) format = args[0].reduce() if not isinstance(format, StringLiteral): raise generic.ScriptError(name + "() parameter 1 'format' must be a literal string", format.pos) # Validate other args format_args = [] for i, arg in enumerate(args[1:]): arg = arg.reduce() if not isinstance(arg, (StringLiteral, ConstantFloat, ConstantNumeric)): raise generic.ScriptError(name + "() parameter {:d} is not a constant number of literal string".format(i+1), arg.pos) format_args.append(arg.value) try: result = format.value % tuple(format_args) return StringLiteral(result, pos) except Exception as ex: raise generic.ScriptError("Invalid combination of format / arguments for {}: {}".format(name, str(ex)), pos) #} function_table = { 'min' : builtin_min, 'max' : builtin_max, 'date' : builtin_date, 'day_of_year' : builtin_day_of_year, 'bitmask' : lambda name, args, pos: BitMask(args, pos), 'STORE_TEMP' : builtin_storage, 'STORE_PERM' : builtin_storage, 'LOAD_TEMP' : builtin_storage, 'LOAD_PERM' : builtin_storage, 'hasbit' : builtin_hasbit, 'getbits' : builtin_getbits, 'version_openttd' : builtin_version_openttd, 'cargotype_available' : builtin_cargotype_available, 'railtype_available' : builtin_railtype_available, 'grf_current_status' : builtin_grf_status, 'grf_future_status' : builtin_grf_status, 'grf_order_behind' : builtin_grf_status, 'visual_effect' : builtin_visual_effect_and_powered, 'visual_effect_and_powered' : builtin_visual_effect_and_powered, 'create_effect' : builtin_create_effect, 'str2number' : builtin_str2number, 'cargotype' : builtin_cargotype, 'railtype' : builtin_railtype, 'reserve_sprites' : builtin_reserve_sprites, 'industry_type' : builtin_industry_type, 'int' : builtin_int, 'abs' : builtin_abs, 'acos' : builtin_trigonometric, 'asin' : builtin_trigonometric, 'atan' : builtin_trigonometric, 'cos' : builtin_trigonometric, 'sin' : builtin_trigonometric, 'tan' : builtin_trigonometric, 'UCMP' : builtin_ucmp, 'CMP' : builtin_cmp, 'rotate' : builtin_rotate, 'sound' : builtin_sound_file, 'import_sound': builtin_sound_import, 'relative_coord' : builtin_relative_coord, 'num_corners_raised' : builtin_num_corners_raised, 'slope_to_sprite_offset' : builtin_slope_to_sprite_offset, 'palette_1cc' : builtin_palette_1cc, 'palette_2cc' : builtin_palette_2cc, 'vehicle_curv_info' : builtin_vehicle_curv_info, 'format_string' : builtin_format_string, } nml-0.4.4/nml/expression/identifier.py0000644000567200056720000000516612643457545021157 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from .base_expression import Expression, ConstantNumeric from .string_literal import StringLiteral ignore_all_invalid_ids = False def default_id_func(x, pos): """ Default id conversion function. @param x: Value to convert. @type x: C{str}, C{int}, or C{float} @param pos: Position of the id. @type pos: L{Position} @return: Expression of the id. @rtype: L{Expression} """ if isinstance(x, str): return StringLiteral(x, pos) else: return ConstantNumeric(x, pos) class Identifier(Expression): def __init__(self, value, pos = None): Expression.__init__(self, pos) self.value = value def debug_print(self, indentation): generic.print_dbg(indentation, 'ID:', self.value) def __str__(self): return self.value def reduce(self, id_dicts = [], unknown_id_fatal = True, search_func_ptr = False): for id_dict in id_dicts: if isinstance(id_dict, tuple): id_d, func = id_dict else: id_d, func = id_dict, default_id_func if self.value in id_d: if search_func_ptr: # Do not reduce function pointers, since they have no (numerical) value return func(id_d[self.value], self.pos) else: return func(id_d[self.value], self.pos).reduce(id_dicts) if unknown_id_fatal and not ignore_all_invalid_ids: raise generic.ScriptError("Unrecognized identifier '" + self.value + "' encountered", self.pos) return self def supported_by_actionD(self, raise_error): if raise_error: raise generic.ScriptError("Unknown identifier '{}'".format(self.value), self.pos) return False def __eq__(self, other): return other is not None and isinstance(other, Identifier) and self.value == other.value def __hash__(self): return hash(self.value) nml-0.4.4/nml/expression/boolean.py0000644000567200056720000000343312643457545020447 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from .base_expression import Type, Expression class Boolean(Expression): """ Convert to boolean truth value. @ivar expr: (Integer) expression to convert. @type expr: C{Expression} """ def __init__(self, expr, pos = None): Expression.__init__(self, pos) self.expr = expr def debug_print(self, indentation): generic.print_dbg(indentation, 'Force expression to boolean:') self.expr.debug_print(indentation + 2) def reduce(self, id_dicts = [], unknown_id_fatal = True): expr = self.expr.reduce(id_dicts) if expr.type() != Type.INTEGER: raise generic.ScriptError("Only integers can be converted to a boolean value.", self.pos) if expr.is_boolean(): return expr return Boolean(expr) def supported_by_action2(self, raise_error): return self.expr.supported_by_action2(raise_error) def supported_by_actionD(self, raise_error): return self.expr.supported_by_actionD(raise_error) def is_boolean(self): return True def __str__(self): return "!!({})".format(self.expr) nml-0.4.4/nml/expression/storage_op.py0000644000567200056720000001104612643457545021171 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from .base_expression import ConstantNumeric, Expression, Type from .parameter import parse_string_to_dword storage_op_info = { 'STORE_PERM' : {'store': True, 'perm': True, 'grfid': False, 'max': 0x0F}, 'STORE_TEMP' : {'store': True, 'perm': False, 'grfid': False, 'max': 0x10F}, 'LOAD_PERM' : {'store': False, 'perm': True, 'grfid': True, 'max': 0x0F}, 'LOAD_TEMP' : {'store': False, 'perm': False, 'grfid': False, 'max': 0xFF}, } class StorageOp(Expression): """ Class for reading/writing to (temporary or permanent) storage @ivar name: Name of the called storage function @type name: C{str} @ivar info: Dictionary containting information about the operation to perform @type info: C{dict} @ivar value: Value to store, or C{None} for loading operations @type value: L{Expression} @ivar register: Register to access @type register: L{Expression} @ivar grfid: GRFID of the register to access @type grfid: L{Expression} """ def __init__(self, name, args, pos = None): Expression.__init__(self, pos) self.name = name assert name in storage_op_info self.info = storage_op_info[name] arg_len = (2,) if self.info['store'] else (1,) if self.info['grfid']: arg_len += (arg_len[0] + 1,) if len(args) not in arg_len: argstr = "{:d}".format(arg_len[0]) if len(arg_len) == 1 else "{}..{}".format(arg_len[0], arg_len[1]) raise generic.ScriptError("{} requires {} argument(s), encountered {:d}".format(name, argstr, len(args)), pos) i = 0 if self.info['store']: self.value = args[i] i += 1 else: self.value = None self.register = args[i] i += 1 if i < len(args): self.grfid = args[i] assert self.info['grfid'] else: self.grfid = None def debug_print(self, indentation): generic.print_dbg(indentation, self.name) generic.print_dbg(indentation + 2, 'Register:') self.register.debug_print(indentation + 4) if self.value is not None: generic.print_dbg(indentation + 2, 'Value:') self.value.debug_print(indentation + 4) if self.grfid is not None: generic.print_dbg(indentation + 2, 'GRFID:') self.grfid.debug_print(indentation + 4) def __str__(self): args = [] if self.value is not None: args.append(str(self.value)) args.append(str(self.register)) if self.grfid is not None: args.append(str(self.grfid)) return "{}({})".format(self.name, ", ".join(args)) def reduce(self, id_dicts = [], unknown_id_fatal = True): args = [] if self.value is not None: value = self.value.reduce(id_dicts) if value.type() != Type.INTEGER: raise generic.ScriptError("Value to store must be an integer.", value.pos) args.append(value) register = self.register.reduce(id_dicts) if register.type() != Type.INTEGER: raise generic.ScriptError("Register to access must be an integer.", register.pos) if isinstance(register, ConstantNumeric) and register.value > self.info['max']: raise generic.ScriptError("Maximum register for {} is {:d}".format(self.name, self.info['max']), self.pos) args.append(register) if self.grfid is not None: grfid = self.grfid.reduce(id_dicts) # Test validity parse_string_to_dword(grfid) args.append(grfid) return StorageOp(self.name, args, self.pos) def supported_by_action2(self, raise_error): return True def supported_by_actionD(self, raise_error): if raise_error: raise generic.ScriptError("{}() may only be used inside switch-blocks".format(self.name), self.pos) return False nml-0.4.4/nml/expression/ternaryop.py0000644000567200056720000000560412643457545021055 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from .base_expression import Type, Expression, ConstantNumeric class TernaryOp(Expression): def __init__(self, guard, expr1, expr2, pos): Expression.__init__(self, pos) self.guard = guard self.expr1 = expr1 self.expr2 = expr2 def debug_print(self, indentation): generic.print_dbg(indentation, 'Ternary operator') generic.print_dbg(indentation, 'Guard:') self.guard.debug_print(indentation + 2) generic.print_dbg(indentation, 'Expression 1:') self.expr1.debug_print(indentation + 2) generic.print_dbg(indentation, 'Expression 2:') self.expr2.debug_print(indentation + 2) def reduce(self, id_dicts = [], unknown_id_fatal = True): guard = self.guard.reduce(id_dicts) expr1 = self.expr1.reduce(id_dicts) expr2 = self.expr2.reduce(id_dicts) if isinstance(guard, ConstantNumeric): if guard.value != 0: return expr1 else: return expr2 if guard.type() != Type.INTEGER or expr1.type() != Type.INTEGER or expr2.type() != Type.INTEGER: raise generic.ScriptError("All parts of the ternary operator (?:) must be integers.", self.pos) return TernaryOp(guard, expr1, expr2, self.pos) def supported_by_action2(self, raise_error): return self.guard.supported_by_action2(raise_error) and self.expr1.supported_by_action2(raise_error) and self.expr2.supported_by_action2(raise_error) def supported_by_actionD(self, raise_error): return self.guard.supported_by_actionD(raise_error) and self.expr1.supported_by_actionD(raise_error) and self.expr2.supported_by_actionD(raise_error) def is_boolean(self): return self.expr1.is_boolean() and self.expr2.is_boolean() def __eq__(self, other): return other is not None and isinstance(other, TernaryOp) and self.guard == other.guard and self.expr1 == other.expr1 and self.expr2 == other.expr2 def __ne__(self, other): return not self.__eq__(other) def __hash__(self): return hash((self.guard, self.expr1, self.expr2)) def __str__(self): return "({} ? {} : {})".format(str(self.guard), str(self.expr1), str(self.expr2)) nml-0.4.4/nml/expression/special_parameter.py0000644000567200056720000000626512643457545022516 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from .base_expression import Expression class SpecialParameter(Expression): """ Class for handling special grf parameters. These can be assigned special, custom methods for reading / writing to them. @ivar name: Name of the parameter, for debugging purposes. @type name: C{str} @ivar info: Information about the parameter. @type info: C{dict} @ivar write_func: Function that will be called when the parameter is the target of an assignment Arguments: Dictionary with parameter information (self.info) Target expression to assign Position information Return value is a 2-tuple: Left side of the assignment (must be a parameter) Right side of the assignment (may be any expression) @type write_func: C{function} @ivar read_func: Function that will be called to read out the parameter value Arguments: Dictionary with parameter information (self.info) Position information Return value: Expression that should be evaluated to get the parameter value @type read_func: C{function} @ivar is_bool: Does read_func return a boolean value? @type is_bool: C{bool} """ def __init__(self, name, info, write_func, read_func, is_bool, pos = None): Expression.__init__(self, pos) self.name = name self.info = info self.write_func = write_func self.read_func = read_func self.is_bool = is_bool def debug_print(self, indentation): generic.print_dbg(indentation, "Special parameter '{}'".format(self.name)) def __str__(self): return self.name def reduce(self, id_dicts = [], unknown_id_fatal = True): return self def is_boolean(self): return self.is_bool def can_assign(self): return self.write_func is not None def to_assignment(self, expr): param, expr = self.write_func(self.info, expr, self.pos) param = param.reduce() expr = expr.reduce() return (param, expr) def to_reading(self): param = self.read_func(self.info, self.pos) param = param.reduce() return param def supported_by_actionD(self, raise_error): return True def supported_by_action2(self, raise_error): return True nml-0.4.4/nml/expression/__init__.py0000644000567200056720000000344312643457545020570 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import re from .array import Array from .base_expression import Type, Expression, ConstantNumeric, ConstantFloat from .bin_not import BinNot, Not from .binop import BinOp from .bitmask import BitMask from .boolean import Boolean from .functioncall import FunctionCall, SpecialCheck, GRMOp from .functionptr import FunctionPtr from .identifier import Identifier from .patch_variable import PatchVariable from .parameter import Parameter, OtherGRFParameter, parse_string_to_dword from .special_parameter import SpecialParameter from .spritegroup_ref import SpriteGroupRef from .storage_op import StorageOp from .string import String from .string_literal import StringLiteral from .ternaryop import TernaryOp from .variable import Variable is_valid_id = re.compile('[a-zA-Z_][a-zA-Z0-9_]{3}$') def identifier_to_print(name): """ Check whether the given name is a valid 4 letter identifier to print (for cargoes and railtypes). @param name: Name to check. @return The identifier itself, if it is a valid name, else a string literal text with the name. """ if is_valid_id.match(name): return name return '"{}"'.format(name) nml-0.4.4/nml/expression/functionptr.py0000644000567200056720000000427012643457545021403 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from .base_expression import Type, Expression class FunctionPtr(Expression): """ Pointer to a function. If this appears inside an expression, the user has made an error. @ivar name: Identifier that has been resolved to this function pointer. @type name: L{Identifier} @ivar func: Function that will be called to resolve this function call. Arguments: Name of the function (C{str}) List of passed arguments (C{list} of L{Expression}) Position information (L{Position}) Any extra arguments passed to the constructor of this class @type func: C{function} @ivar extra_args List of arguments that should be passed to the function that is to be called. @type extra_args C{list} """ def __init__(self, name, func, *extra_args): self.name = name self.func = func self.extra_args = extra_args def debug_print(self, indentation): assert False, "Function pointers should not appear inside expressions." def __str__(self): assert False, "Function pointers should not appear inside expressions." def reduce(self, id_dicts = [], unknown_id_fatal = True): raise generic.ScriptError("'{}' is a function and should be called using the function call syntax.".format(str(self.name)), self.name.pos) def type(self): return Type.FUNCTION_PTR def call(self, args): return self.func(self.name.value, args, self.name.pos, *self.extra_args) nml-0.4.4/nml/expression/spritegroup_ref.py0000644000567200056720000000571512643457545022254 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from nml.actions import action2 from .base_expression import Type, Expression class SpriteGroupRef(Expression): """ Container for a reference to a sprite group / layout @ivar name: Name of the referenced item @type name: L{Identifier} @ivar param_list: List of parameters to be passed @type param_list: C{list} of L{Expression} @ivar pos: Position of this reference @type pos: L{Position} @ivar act2: Action2 that is the target of this reference To be used for action2s that have no direct equivalent in the AST @type act2: L{Action2} """ def __init__(self, name, param_list, pos, act2 = None): self.name = name self.param_list = param_list self.pos = pos self.act2 = act2 def debug_print(self, indentation): generic.print_dbg(indentation, 'Reference to:', self.name) if len(self.param_list) != 0: generic.print_dbg(indentation, 'Parameters:') for p in self.param_list: p.debug_print(indentation + 2) def __str__(self): if self.param_list: return '{}({})'.format(self.name, ', '.join(str(x) for x in self.param_list)) return str(self.name) def get_action2_id(self, feature): """ Get the action2 set-ID that this reference maps to @param feature: Feature of the action2 @type feature: C{int} @return: The set ID @rtype: C{int} """ if self.act2 is not None: return self.act2.id if self.name.value == 'CB_FAILED': return 0 # 0 serves as a failed CB result because it is never used try: spritegroup = action2.resolve_spritegroup(self.name) except generic.ScriptError: raise AssertionError("Illegal action2 reference '{}' encountered.".format(self.name.value)) return spritegroup.get_action2(feature).id def reduce(self, id_dicts = [], unknown_id_fatal = True): return self def type(self): return Type.SPRITEGROUP_REF def __eq__(self, other): return other is not None and isinstance(other, SpriteGroupRef) and other.name == self.name and other.param_list == self.param_list def __hash__(self): return hash(self.name) ^ hash(tuple(self.param_list)) nml-0.4.4/nml/expression/string_literal.py0000644000567200056720000000270512643457545022053 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic from .base_expression import Type, Expression class StringLiteral(Expression): """ String literal expression. @ivar value: Value of the string literal. @type value: C{str} """ def __init__(self, value, pos): Expression.__init__(self, pos) self.value = value def debug_print(self, indentation): generic.print_dbg(indentation, 'String literal: "{}"'.format(self.value)) def __str__(self): return '"{}"'.format(self.value) def write(self, file, size): assert len(self.value) == size file.print_string(self.value, final_zero = False, force_ascii = True) def reduce(self, id_dicts = [], unknown_id_fatal = True): return self def type(self): return Type.STRING_LITERAL nml-0.4.4/nml/expression/binop.py0000644000567200056720000002372512643457545020145 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, nmlop from .base_expression import Expression, ConstantNumeric, ConstantFloat from .string_literal import StringLiteral from .variable import Variable from .boolean import Boolean class BinOp(Expression): def __init__(self, op, expr1, expr2, pos = None): Expression.__init__(self, pos) self.op = op self.expr1 = expr1 self.expr2 = expr2 def debug_print(self, indentation): generic.print_dbg(indentation, 'Binary operator, op =', self.op.token) self.expr1.debug_print(indentation + 2) self.expr2.debug_print(indentation + 2) def __str__(self): if self.op == nmlop.SUB and isinstance(self.expr1, ConstantNumeric) and self.expr1.value == 0: return '-' + str(self.expr2) return self.op.to_string(self.expr1, self.expr2) def get_priority(self, expr): """ Get the priority of an expression. For optimalization reason we prefer complexer expressions (= low priority) on the left hand side of this expression. The following priorities are used: -1: everything that doesn't fit in one of the other categories. 0: Variables to be parsed in a varaction2 1: Expressions that can be parsed via actionD, with the exception of constant numbers 2: constant numbers @param expr: The expression to get the priority of. @type expr: L{Expression} @return: The priority for the given expression. @rtype: C{int} """ if isinstance(expr, Variable): return 0 if isinstance(expr, ConstantNumeric): return 2 if expr.supported_by_actionD(False): return 1 return -1 def reduce(self, id_dicts = [], unknown_id_fatal = True): # Reducing a BinOp expression is done in several phases: # - Reduce both subexpressions. # - If both subexpressions are constant, compute the result and return it. # - If the operator allows it and the second expression is more complex than # the first one swap them. # - If the operation is a no-op, delete it. # - Variables (as used in action2var) can have some computations attached to # them, do that if possible. # - Try to merge multiple additions/subtractions with constant numbers # - Reduce both subexpressions. expr1 = self.expr1.reduce(id_dicts) expr2 = self.expr2.reduce(id_dicts) # Make sure the combination of operands / operator is valid if self.op.validate_func is not None: self.op.validate_func(expr1, expr2, self.pos) # - If both subexpressions are constant, compute the result and return it. if isinstance(expr1, ConstantNumeric) and isinstance(expr2, ConstantNumeric) and self.op.compiletime_func is not None: return ConstantNumeric(self.op.compiletime_func(expr1.value, expr2.value), self.pos) if isinstance(expr1, StringLiteral) and isinstance(expr2, StringLiteral): assert self.op == nmlop.ADD return StringLiteral(expr1.value + expr2.value, expr1.pos) if isinstance(expr1, (ConstantNumeric, ConstantFloat)) and isinstance(expr2, (ConstantNumeric, ConstantFloat)) and self.op.compiletime_func is not None: return ConstantFloat(self.op.compiletime_func(expr1.value, expr2.value), self.pos) # - If the operator allows it and the second expression is more complex than # the first one swap them. op = self.op if op in commutative_operators or self.op in (nmlop.CMP_LT, nmlop.CMP_GT): prio1 = self.get_priority(expr1) prio2 = self.get_priority(expr2) if prio2 < prio1: expr1, expr2 = expr2, expr1 if op == nmlop.CMP_LT: op = nmlop.CMP_GT elif op == nmlop.CMP_GT: op = nmlop.CMP_LT # - If the operation is a no-op, delete it. if op == nmlop.AND and isinstance(expr2, ConstantNumeric) and (expr2.value == -1 or expr2.value == 0xFFFFFFFF): return expr1 if op in (nmlop.DIV, nmlop.DIVU, nmlop.MUL) and isinstance(expr2, ConstantNumeric) and expr2.value == 1: return expr1 if op in (nmlop.ADD, nmlop.SUB) and isinstance(expr2, ConstantNumeric) and expr2.value == 0: return expr1 # - Variables (as used in action2var) can have some computations attached to # them, do that if possible. if isinstance(expr1, Variable) and expr2.supported_by_actionD(False): # An action2 Variable has some special fields (mask, add, div and mod) that can be used # to perform some operations on the value. These operations are faster than a normal # advanced varaction2 operator so we try to use them whenever we can. if op == nmlop.AND and expr1.add is None: expr1.mask = BinOp(nmlop.AND, expr1.mask, expr2, self.pos).reduce(id_dicts) return expr1 if op == nmlop.ADD and expr1.div is None and expr1.mod is None: if expr1.add is None: expr1.add = expr2 else: expr1.add = BinOp(nmlop.ADD, expr1.add, expr2, self.pos).reduce(id_dicts) return expr1 if op == nmlop.SUB and expr1.div is None and expr1.mod is None: if expr1.add is None: expr1.add = ConstantNumeric(0) expr1.add = BinOp(nmlop.SUB, expr1.add, expr2, self.pos).reduce(id_dicts) return expr1 # The div and mod fields cannot be used at the same time. Also whenever either of those # two are used the add field has to be set, so we change it to zero when it's not yet set. if op == nmlop.DIV and expr1.div is None and expr1.mod is None: if expr1.add is None: expr1.add = ConstantNumeric(0) expr1.div = expr2 return expr1 if op == nmlop.MOD and expr1.div is None and expr1.mod is None: if expr1.add is None: expr1.add = ConstantNumeric(0) expr1.mod = expr2 return expr1 # Since we have a lot of nml-variables that are in fact only the high bits of an nfo # variable it can happen that we want to shift back the variable to the left. # Don't use any extra opcodes but just reduce the shift-right in that case. if op == nmlop.SHIFT_LEFT and isinstance(expr2, ConstantNumeric) and expr1.add is None and expr2.value < expr1.shift.value: expr1.shift.value -= expr2.value expr1.mask = BinOp(nmlop.SHIFT_LEFT, expr1.mask, expr2).reduce() return expr1 # - Try to merge multiple additions/subtractions with constant numbers if op in (nmlop.ADD, nmlop.SUB) and isinstance(expr2, ConstantNumeric) and \ isinstance(expr1, BinOp) and expr1.op in (nmlop.ADD, nmlop.SUB) and isinstance(expr1.expr2, ConstantNumeric): val = expr2.value if op == nmlop.ADD else -expr2.value if expr1.op == nmlop.ADD: return BinOp(nmlop.ADD, expr1.expr1, ConstantNumeric(expr1.expr2.value + val), self.pos).reduce() if expr1.op == nmlop.SUB: return BinOp(nmlop.SUB, expr1.expr1, ConstantNumeric(expr1.expr2.value - val), self.pos).reduce() if op == nmlop.OR and isinstance(expr1, Boolean) and isinstance(expr2, Boolean): return Boolean(BinOp(op, expr1.expr, expr2.expr, self.pos)).reduce(id_dicts) return BinOp(op, expr1, expr2, self.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 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 is_boolean(self): if self.op in (nmlop.AND, nmlop.OR, nmlop.XOR): return self.expr1.is_boolean() and self.expr2.is_boolean() return self.op.returns_boolean def __eq__(self, other): return other is not None and isinstance(other, BinOp) and self.op == other.op and self.expr1 == other.expr1 and self.expr2 == other.expr2 def __ne__(self, other): return not self.__eq__(other) def __hash__(self): return hash((self.op, self.expr1, self.expr2)) commutative_operators = set([ nmlop.ADD, nmlop.MUL, nmlop.AND, nmlop.OR, nmlop.XOR, nmlop.CMP_EQ, nmlop.CMP_NEQ, nmlop.MIN, nmlop.MAX, ]) nml-0.4.4/nml/expression/base_expression.py0000644000567200056720000001263112643457545022221 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic class Type(object): """ Enum-type class of the various value types possible in NML """ INTEGER = 0 FLOAT = 1 STRING_LITERAL = 2 FUNCTION_PTR = 3 SPRITEGROUP_REF = 4 class Expression(object): """ Superclass for all expression classes. @ivar pos: Position of the data in the original file. @type pos: :L{Position} """ def __init__(self, pos): self.pos = pos def debug_print(self, indentation): """ Print all data with explanation of what it is to standard output. @param indentation: Indent all printed lines with at least C{indentation} spaces. """ raise NotImplementedError('debug_print must be implemented in expression-subclass {:r}'.format(type(self))) def __str__(self): """ Convert this expression to a string representing this expression in valid NML-code. @return: A string representation of this expression. """ raise NotImplementedError('__str__ must be implemented in expression-subclass {:r}'.format(type(self))) def reduce(self, id_dicts = [], unknown_id_fatal = True): """ Reduce this expression to the simplest representation possible. @param id_dicts: A list with dicts that are used to map identifiers to another (often numeric) representation. @param unknown_id_fatal: Is encountering an unknown identifier somewhere in this expression a fatal error? @return: A deep copy of this expression simplified as much as possible. """ raise NotImplementedError('reduce must be implemented in expression-subclass {:r}'.format(type(self))) def reduce_constant(self, id_dicts = []): """ Reduce this expression and make sure the result is a constant number. @param id_dicts: A list with dicts that are used to map identifiers to another (often numeric) representation. @return: A constant number that is the result of this expression. """ expr = self.reduce(id_dicts) if not isinstance(expr, ConstantNumeric): raise generic.ConstError(self.pos) return expr def supported_by_action2(self, raise_error): """ Check if this expression can be used inside a switch-block. @param raise_error: If true raise a scripterror instead of returning false. @return: True if this expression can be calculated by advanced varaction2. """ if raise_error: raise generic.ScriptError("This expression is not supported in a switch-block", self.pos) return False def supported_by_actionD(self, raise_error): """ Check if this expression can be used inside a parameter-assignment. @param raise_error: If true raise a scripterror instead of returning false. @return: True if this expression can be calculated by actionD. """ if raise_error: raise generic.ScriptError("This expression can not be assigned to a parameter", self.pos) return False def is_boolean(self): """ Check if this expression is limited to 0 or 1 as value. @return: True if the value of this expression is either 0 or 1. """ return False def type(self): """ Determine the datatype of this expression. @return: A constant from the L{Type} class, representing the data type. """ return Type.INTEGER class ConstantNumeric(Expression): def __init__(self, value, pos = None): Expression.__init__(self, pos) self.value = generic.truncate_int32(value) def debug_print(self, indentation): generic.print_dbg(indentation, 'Int:', self.value) def write(self, file, size): file.print_varx(self.value, size) def __str__(self): return str(self.value) def reduce(self, id_dicts = [], unknown_id_fatal = True): return self def supported_by_action2(self, raise_error): return True def supported_by_actionD(self, raise_error): return True def is_boolean(self): return self.value == 0 or self.value == 1 def __eq__(self, other): return other is not None and isinstance(other, ConstantNumeric) and other.value == self.value def __hash__(self): return self.value class ConstantFloat(Expression): def __init__(self, value, pos): Expression.__init__(self, pos) self.value = float(value) def debug_print(self, indentation): generic.print_dbg(indentation, 'Float:', self.value) def __str__(self): return str(self.value) def reduce(self, id_dicts = [], unknown_id_fatal = True): return self def type(self): return Type.FLOAT nml-0.4.4/nml/expression/bitmask.py0000644000567200056720000000352012643457545020457 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, nmlop from .base_expression import Type, Expression, ConstantNumeric from .binop import BinOp class BitMask(Expression): def __init__(self, values, pos): Expression.__init__(self, pos) self.values = values def debug_print(self, indentation): generic.print_dbg(indentation, 'Get bitmask:') for value in self.values: value.debug_print(indentation + 2) def reduce(self, id_dicts = [], unknown_id_fatal = True): ret = ConstantNumeric(0, self.pos) for orig_expr in self.values: val = orig_expr.reduce(id_dicts) if val.type() != Type.INTEGER: raise generic.ScriptError("Parameters of 'bitmask' must be integers.", orig_expr.pos) if isinstance(val, ConstantNumeric) and val.value >= 32: raise generic.ScriptError("Parameters of 'bitmask' cannot be greater than 31", orig_expr.pos) val = BinOp(nmlop.SHIFT_LEFT, ConstantNumeric(1), val, val.pos) ret = BinOp(nmlop.OR, ret, val, self.pos) return ret.reduce() def __str__(self): return "bitmask(" + ", ".join(str(e) for e in self.values) + ")" nml-0.4.4/nml/expression/string.py0000644000567200056720000000410412643457545020332 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from .base_expression import Expression from .identifier import Identifier from nml import generic from functools import reduce class String(Expression): def __init__(self, params, pos): Expression.__init__(self, pos) if len(params) == 0: raise generic.ScriptError("string() requires at least one parameter.", pos) self.name = params[0] if not isinstance(self.name, Identifier): raise generic.ScriptError("First parameter of string() must be an identifier.", pos) self.params = params[1:] def debug_print(self, indentation): generic.print_dbg(indentation, 'String:') self.name.debug_print(indentation + 2) for param in self.params: generic.print_dbg(indentation + 2, 'Parameter:') param.debug_print(indentation + 4) def __str__(self): ret = 'string(' + self.name.value for p in self.params: ret += ', ' + str(p) ret += ')' return ret def reduce(self, id_dicts = [], unknown_id_fatal = True): params = [p.reduce(id_dicts) for p in self.params] return String([self.name] + params, self.pos) def __eq__(self, other): return other is not None and isinstance(other, String) and self.name == other.name and self.params == other.params def __hash__(self): return hash(self.name) ^ reduce(lambda x, y: x ^ hash(y), self.params, 0) nml-0.4.4/nml/expression/parameter.py0000644000567200056720000001052212643457545021005 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, grfstrings from .base_expression import Type, Expression, ConstantNumeric from .string_literal import StringLiteral class Parameter(Expression): 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 debug_print(self, indentation): generic.print_dbg(indentation, 'Parameter:') self.num.debug_print(indentation + 2) def __str__(self): return 'param[{}]'.format(self.num) def reduce(self, id_dicts = [], unknown_id_fatal = True): num = self.num.reduce(id_dicts) if num.type() != Type.INTEGER: raise generic.ScriptError("Parameter number must be an integer.", num.pos) return Parameter(num, self.pos) def supported_by_action2(self, raise_error): supported = isinstance(self.num, ConstantNumeric) if not supported and raise_error: raise generic.ScriptError("Parameter acessess with non-constant numbers are not supported in a switch-block.", self.pos) return supported def supported_by_actionD(self, raise_error): return True def __eq__(self, other): return other is not None and isinstance(other, Parameter) and self.num == other.num def __ne__(self, other): return not self.__eq__(other) def __hash__(self): return hash((self.num,)) class OtherGRFParameter(Expression): def __init__(self, grfid, num, pos = None): Expression.__init__(self, pos) self.grfid = grfid self.num = num def debug_print(self, indentation): generic.print_dbg(indentation, 'OtherGRFParameter:') self.grfid.debug_print(indentation + 2) self.num.debug_print(indentation + 2) def __str__(self): return 'param[{}, {}]'.format(self.grfid, self.num) def reduce(self, id_dicts = [], unknown_id_fatal = True): grfid = self.grfid.reduce(id_dicts) #Test validity parse_string_to_dword(grfid) num = self.num.reduce(id_dicts) if num.type() != Type.INTEGER: raise generic.ScriptError("Parameter number must be an integer.", num.pos) return OtherGRFParameter(grfid, num, self.pos) def supported_by_action2(self, raise_error): if raise_error: raise generic.ScriptError("Reading parameters from another GRF is not supported in a switch-block.", self.pos) return False def supported_by_actionD(self, raise_error): return True def parse_string_to_dword(string): """ Convert string literal expression of length 4 to it's equivalent 32 bit number. @param string: Expression to convert. @type string: L{Expression} @return: Value of the converted expression (a 32 bit integer number, little endian). @rtype: C{int} """ if not isinstance(string, StringLiteral) or grfstrings.get_string_size(string.value, False, True) != 4: raise generic.ScriptError("Expected a string literal of length 4", string.pos) pos = string.pos string = string.value bytes = [] i = 0 try: while len(bytes) < 4: if string[i] == '\\': bytes.append(int(string[i+1:i+3], 16)) i += 3 else: bytes.append(ord(string[i])) i += 1 except ValueError: raise generic.ScriptError("Cannot convert string to integer id", pos) return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24) nml-0.4.4/nml/expression/bin_not.py0000644000567200056720000000700412643457545020456 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, nmlop from .base_expression import Type, Expression, ConstantNumeric from .binop import BinOp from .boolean import Boolean class BinNot(Expression): def __init__(self, expr, pos = None): Expression.__init__(self, pos) self.expr = expr def debug_print(self, indentation): generic.print_dbg(indentation, 'Binary not:') self.expr.debug_print(indentation + 2) def reduce(self, id_dicts = [], 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 supported_by_action2(self, raise_error): return self.expr.supported_by_action2(raise_error) def supported_by_actionD(self, raise_error): return self.expr.supported_by_actionD(raise_error) def __str__(self): return "~" + str(self.expr) class Not(Expression): def __init__(self, expr, pos = None): Expression.__init__(self, pos) self.expr = expr def debug_print(self, indentation): generic.print_dbg(indentation, 'Logical not:') self.expr.debug_print(indentation + 2) def reduce(self, id_dicts = [], 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(expr.value == 0) if isinstance(expr, Not): return Boolean(expr.expr).reduce() if isinstance(expr, BinOp): if expr.op == nmlop.CMP_EQ: return BinOp(nmlop.CMP_NEQ, expr.expr1, expr.expr2) if expr.op == nmlop.CMP_NEQ: return BinOp(nmlop.CMP_EQ, expr.expr1, expr.expr2) if expr.op == nmlop.CMP_LE: return BinOp(nmlop.CMP_GT, expr.expr1, expr.expr2) if expr.op == nmlop.CMP_GE: return BinOp(nmlop.CMP_LT, expr.expr1, expr.expr2) if expr.op == nmlop.CMP_LT: return BinOp(nmlop.CMP_GE, expr.expr1, expr.expr2) if expr.op == nmlop.CMP_GT: return BinOp(nmlop.CMP_LE, expr.expr1, expr.expr2) if expr.op == nmlop.HASBIT: return BinOp(nmlop.NOTHASBIT, expr.expr1, expr.expr2) if expr.op == nmlop.NOTHASBIT: return BinOp(nmlop.HASBIT, expr.expr1, expr.expr2) return Not(expr) def supported_by_action2(self, raise_error): return self.expr.supported_by_action2(raise_error) def supported_by_actionD(self, raise_error): return self.expr.supported_by_actionD(raise_error) def is_boolean(self): return True def __str__(self): return "!" + str(self.expr) nml-0.4.4/nml/parser.py0000644000567200056720000007023212643457545016126 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic, expression, tokens, nmlop, unit from nml.ast import assignment, basecost, cargotable, conditional, deactivate, disable_item, error, font, general, grf, item, loop, produce, railtypetable, replace, spriteblock, switch, townnames, snowline, skipall, tilelayout, alt_sprites, base_graphics, override, sort_vehicles from nml.actions import actionD, real_sprite import ply.yacc as yacc class NMLParser(object): """ @ivar lexer: Scanner providing tokens. @type lexer: L{NMLLexer} @ivar tokens: Tokens of the scanner (used by PLY). @type tokens: C{List} of C{str} @ivar parser: PLY parser. @type parser: L{ply.yacc} """ def __init__(self): self.lexer = tokens.NMLLexer() self.lexer.build() self.tokens = self.lexer.tokens self.parser = yacc.yacc(debug = False, module = self, write_tables = 0) def parse(self, text, input_filename): self.lexer.setup(text, input_filename) return self.parser.parse(None, lexer = self.lexer.lexer) #operator precedence (lower in the list = higher priority) precedence = ( ('left', 'COMMA'), ('right', 'TERNARY_OPEN','COLON'), ('left', 'LOGICAL_OR'), ('left', 'LOGICAL_AND'), ('left', 'OR'), ('left', 'XOR'), ('left', 'AND'), ('left', 'COMP_EQ', 'COMP_NEQ', 'COMP_LE', 'COMP_GE', 'COMP_LT', 'COMP_GT'), ('left', 'SHIFT_LEFT', 'SHIFT_RIGHT', 'SHIFTU_RIGHT'), ('left', 'PLUS', 'MINUS'), ('left', 'TIMES', 'DIVIDE', 'MODULO'), ('left', 'LOGICAL_NOT', 'BINARY_NOT'), ) def p_error(self, t): if t is None: raise generic.ScriptError('Syntax error, unexpected end-of-file') else: raise generic.ScriptError('Syntax error, unexpected token "{}"'.format(t.value), t.lineno) # # Main script blocks # def p_main_script(self, t): 'main_script : script' t[0] = general.MainScript(t[1]) def p_script(self, t): '''script : | script main_block''' if len(t) == 1: t[0] = [] else: t[0] = t[1] + [t[2]] def p_main_block(self, t): '''main_block : switch | random_switch | produce | spriteset | spritegroup | spritelayout | template_declaration | tilelayout | town_names | cargotable | railtype | grf_block | param_assignment | skip_all | conditional | loop | item | property_block | graphics_block | liveryoverride_block | error_block | disable_item | deactivate | replace | replace_new | base_graphics | font_glyph | alt_sprites | snowline | engine_override | sort_vehicles | basecost''' t[0] = t[1] # # Expressions # def p_expression(self, t): '''expression : NUMBER | FLOAT | param | variable | ID | STRING_LITERAL | string''' t[0] = t[1] def p_parenthesed_expression(self, t): 'expression : LPAREN expression RPAREN' t[0] = t[2] def p_parameter(self, t): 'param : PARAMETER LBRACKET expression RBRACKET' t[0] = expression.Parameter(t[3], t.lineno(1), True) def p_parameter_other_grf(self, t): 'param : PARAMETER LBRACKET expression COMMA expression RBRACKET' t[0] = expression.OtherGRFParameter(t[3], t[5], t.lineno(1)) code_to_op = { '+' : nmlop.ADD, '-' : nmlop.SUB, '*' : nmlop.MUL, '/' : nmlop.DIV, '%' : nmlop.MOD, '&' : nmlop.AND, '|' : nmlop.OR, '^' : nmlop.XOR, '&&' : nmlop.AND, '||' : nmlop.OR, '==' : nmlop.CMP_EQ, '!=' : nmlop.CMP_NEQ, '<=' : nmlop.CMP_LE, '>=' : nmlop.CMP_GE, '<' : nmlop.CMP_LT, '>' : nmlop.CMP_GT, '<<' : nmlop.SHIFT_LEFT, '>>' : nmlop.SHIFT_RIGHT, '>>>': nmlop.SHIFTU_RIGHT, } def p_binop(self, t): '''expression : expression PLUS expression | expression MINUS expression | expression TIMES expression | expression DIVIDE expression | expression MODULO expression | expression AND expression | expression OR expression | expression XOR expression | expression SHIFT_LEFT expression | expression SHIFT_RIGHT expression | expression SHIFTU_RIGHT expression | expression COMP_EQ expression | expression COMP_NEQ expression | expression COMP_LE expression | expression COMP_GE expression | expression COMP_LT expression | expression COMP_GT expression''' t[0] = expression.BinOp(self.code_to_op[t[2]], t[1], t[3], t[1].pos) def p_binop_logical(self, t): '''expression : expression LOGICAL_AND expression | expression LOGICAL_OR expression''' t[0] = expression.BinOp(self.code_to_op[t[2]], expression.Boolean(t[1]), expression.Boolean(t[3]), t[1].pos) def p_logical_not(self, t): 'expression : LOGICAL_NOT expression' t[0] = expression.Not(expression.Boolean(t[2]), t.lineno(1)) def p_binary_not(self, t): 'expression : BINARY_NOT expression' t[0] = expression.BinNot(t[2], t.lineno(1)) def p_ternary_op(self, t): 'expression : expression TERNARY_OPEN expression COLON expression' t[0] = expression.TernaryOp(t[1], t[3], t[5], t[1].pos) def p_unary_minus(self, t): 'expression : MINUS expression' t[0] = expression.BinOp(self.code_to_op[t[1]], expression.ConstantNumeric(0), t[2], t.lineno(1)) def p_variable(self, t): 'variable : VARIABLE LBRACKET expression_list RBRACKET' t[0] = expression.Variable(*t[3]) t[0].pos = t.lineno(1) def p_function(self, t): 'expression : ID LPAREN expression_list RPAREN' t[0] = expression.FunctionCall(t[1], t[3], t[1].pos) def p_array(self, t): 'expression : LBRACKET expression_list RBRACKET' t[0] = expression.Array(t[2], t.lineno(1)) # # Commonly used non-terminals that are not expressions # def p_assignment_list(self, t): '''assignment_list : assignment | param_desc | assignment_list assignment | assignment_list param_desc''' if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[2]] def p_assignment(self, t): 'assignment : ID COLON expression SEMICOLON' t[0] = assignment.Assignment(t[1], t[3], t[1].pos) def p_param_desc(self, t): '''param_desc : PARAMETER expression LBRACE setting_list RBRACE | PARAMETER LBRACE setting_list RBRACE''' if len(t) == 5: t[0] = grf.ParameterDescription(t[3]) else: t[0] = grf.ParameterDescription(t[4], t[2]) def p_setting_list(self, t): '''setting_list : setting | setting_list setting''' if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[2]] def p_setting(self, t): 'setting : ID LBRACE setting_value_list RBRACE' t[0] = grf.ParameterSetting(t[1], t[3]) def p_setting_value_list(self, t): '''setting_value_list : setting_value | setting_value_list setting_value''' if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[2]] def p_setting_value(self, t): 'setting_value : assignment' t[0] = t[1] def p_names_setting_value(self, t): 'setting_value : ID COLON LBRACE name_string_list RBRACE SEMICOLON' t[0] = assignment.Assignment(t[1], t[4], t[1].pos) def p_name_string_list(self, t): '''name_string_list : name_string_item | name_string_list name_string_item''' if len(t) == 2: t[0] = expression.Array([t[1]], t[1].pos) else: t[0] = expression.Array(t[1].values + [t[2]], t[1].pos) def p_name_string_item(self, t): 'name_string_item : expression COLON string SEMICOLON' t[0] = assignment.Assignment(t[1], t[3], t[1].pos) def p_string(self, t): 'string : STRING LPAREN expression_list RPAREN' t[0] = expression.String(t[3], t.lineno(1)) def p_non_empty_expression_list(self, t): '''non_empty_expression_list : expression | non_empty_expression_list COMMA expression''' if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[3]] def p_expression_list(self, t): '''expression_list : | non_empty_expression_list | non_empty_expression_list COMMA''' t[0] = [] if len(t) == 1 else t[1] def p_non_empty_id_list(self, t): '''non_empty_id_list : ID | non_empty_id_list COMMA ID''' if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[3]] def p_id_list(self, t): '''id_list : | non_empty_id_list | non_empty_id_list COMMA''' t[0] = [] if len(t) == 1 else t[1] def p_generic_assignment(self, t): 'generic_assignment : expression COLON expression SEMICOLON' t[0] = assignment.Assignment(t[1], t[3], t.lineno(1)) def p_generic_assignment_list(self, t): '''generic_assignment_list : | generic_assignment_list generic_assignment''' t[0] = [] if len(t) == 1 else t[1] + [t[2]] def p_snowline_assignment(self, t): '''snowline_assignment : expression COLON expression SEMICOLON | expression COLON expression UNIT SEMICOLON''' unit_value = None if len(t) == 5 else unit.get_unit(t[4]) t[0] = assignment.UnitAssignment(t[1], t[3], unit_value, t.lineno(1)) def p_snowline_assignment_list(self, t): '''snowline_assignment_list : | snowline_assignment_list snowline_assignment''' t[0] = [] if len(t) == 1 else t[1] + [t[2]] # # Item blocks # def p_item(self, t): 'item : ITEM LPAREN expression_list RPAREN LBRACE script RBRACE' t[0] = item.Item(t[3], t[6], t.lineno(1)) def p_property_block(self, t): 'property_block : PROPERTY LBRACE property_list RBRACE' t[0] = item.PropertyBlock(t[3], t.lineno(1)) def p_property_list(self, t): '''property_list : property_assignment | property_list property_assignment''' if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[2]] def p_property_assignment(self, t): '''property_assignment : ID COLON expression SEMICOLON | ID COLON expression UNIT SEMICOLON | NUMBER COLON expression SEMICOLON | NUMBER COLON expression UNIT SEMICOLON''' name = t[1] unit_value = None if len(t) == 5 else unit.get_unit(t[4]) t[0] = item.Property(name, t[3], unit_value, t.lineno(1)) def p_graphics_block(self, t): 'graphics_block : GRAPHICS LBRACE graphics_list RBRACE' t[0] = item.GraphicsBlock(t[3][0], t[3][1], t.lineno(1)) def p_liveryoverride_block(self, t): 'liveryoverride_block : LIVERYOVERRIDE LPAREN expression RPAREN LBRACE graphics_list RBRACE' t[0] = item.LiveryOverride(t[3], item.GraphicsBlock(t[6][0], t[6][1], t.lineno(1)), t.lineno(1)) def p_graphics_list(self, t): '''graphics_list : graphics_assignment_list | graphics_assignment_list switch_value | switch_value''' # Save graphics block as a tuple, we need to add position info later if len(t) == 2: if isinstance(t[1], list): t[0] = (t[1], None) else: t[0] = ([], t[1]) else: t[0] = (t[1], t[2]) def p_graphics_assignment(self, t): 'graphics_assignment : expression COLON switch_value' t[0] = item.GraphicsDefinition(t[1], t[3]) def p_graphics_assignment_list(self, t): '''graphics_assignment_list : graphics_assignment | graphics_assignment_list graphics_assignment''' if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[2]] # # Program flow control (if/else/while) # def p_conditional(self, t): '''conditional : if_else_parts | if_else_parts else_block''' parts = t[1] if len(t) > 2: parts.append(t[2]) t[0] = conditional.ConditionalList(parts) def p_else_block(self, t): 'else_block : ELSE LBRACE script RBRACE' t[0] = conditional.Conditional(None, t[3], t.lineno(1)) def p_if_else_parts(self, t): '''if_else_parts : IF LPAREN expression RPAREN LBRACE script RBRACE | if_else_parts ELSE IF LPAREN expression RPAREN LBRACE script RBRACE''' if len(t) == 8: t[0] = [conditional.Conditional(t[3], t[6], t.lineno(1))] else: t[0] = t[1] + [conditional.Conditional(t[5], t[8], t.lineno(2))] def p_loop(self, t): 'loop : WHILE LPAREN expression RPAREN LBRACE script RBRACE' t[0] = loop.Loop(t[3], t[6], t.lineno(1)) # # (Random) Switch block # def p_switch(self, t): 'switch : SWITCH LPAREN expression_list RPAREN LBRACE switch_body RBRACE' t[0] = switch.Switch(t[3], t[6], t.lineno(1)) def p_switch_body(self, t): '''switch_body : switch_ranges switch_value | switch_ranges''' t[0] = switch.SwitchBody(t[1], t[2] if len(t) == 3 else None) def p_switch_ranges(self, t): '''switch_ranges : | switch_ranges expression COLON switch_value | switch_ranges expression UNIT COLON switch_value | switch_ranges expression RANGE expression COLON switch_value | switch_ranges expression RANGE expression UNIT COLON switch_value''' if len(t) == 1: t[0] = [] elif len(t) == 5: t[0] = t[1] + [switch.SwitchRange(t[2], t[2], t[4])] elif len(t) == 6: t[0] = t[1] + [switch.SwitchRange(t[2], t[2], t[5], t[3])] elif len(t) == 7: t[0] = t[1] + [switch.SwitchRange(t[2], t[4], t[6])] else: t[0] = t[1] + [switch.SwitchRange(t[2], t[4], t[7], t[5])] def p_switch_value(self, t): '''switch_value : RETURN expression SEMICOLON | RETURN SEMICOLON | expression SEMICOLON''' if len(t) == 4: t[0] = switch.SwitchValue(t[2], True, t[2].pos) elif t[1] == 'return': t[0] = switch.SwitchValue(None, True, t.lineno(1)) else: t[0] = switch.SwitchValue(t[1], False, t[1].pos) def p_random_switch(self, t): 'random_switch : RANDOMSWITCH LPAREN expression_list RPAREN LBRACE random_body RBRACE' t[0] = switch.RandomSwitch(t[3], t[6], t.lineno(1)) def p_random_body(self, t): '''random_body : | random_body expression COLON switch_value''' if len(t) == 1: t[0] = [] else: t[0] = t[1] + [switch.RandomChoice(t[2], t[4])] def p_produce(self, t): 'produce : PRODUCE LPAREN expression_list RPAREN SEMICOLON' t[0] = produce.Produce(t[3], t.lineno(1)) # # Real sprites and related stuff # def p_real_sprite(self, t): '''real_sprite : LBRACKET expression_list RBRACKET | ID COLON LBRACKET expression_list RBRACKET''' if len(t) == 4: t[0] = real_sprite.RealSprite(param_list = t[2], poslist = [t.lineno(1)]) else: t[0] = real_sprite.RealSprite(param_list = t[4], label = t[1], poslist = [t.lineno(1)]) def p_recolour_assignment_list(self, t): '''recolour_assignment_list : | recolour_assignment_list recolour_assignment''' t[0] = [] if len(t) == 1 else t[1] + [t[2]] def p_recolour_assignment_1(self, t): 'recolour_assignment : expression COLON expression SEMICOLON' t[0] = assignment.Assignment(assignment.Range(t[1], None), assignment.Range(t[3], None), t[1].pos) def p_recolour_assignment_2(self, t): 'recolour_assignment : expression RANGE expression COLON expression RANGE expression SEMICOLON' t[0] = assignment.Assignment(assignment.Range(t[1], t[3]), assignment.Range(t[5], t[7]), t[1].pos) def p_recolour_assignment_3(self, t): 'recolour_assignment : expression RANGE expression COLON expression SEMICOLON' t[0] = assignment.Assignment(assignment.Range(t[1], t[3]), assignment.Range(t[5], None), t[1].pos) def p_recolour_sprite(self, t): '''real_sprite : RECOLOUR_SPRITE LBRACE recolour_assignment_list RBRACE | ID COLON RECOLOUR_SPRITE LBRACE recolour_assignment_list RBRACE''' if len(t) == 5: t[0] = real_sprite.RecolourSprite(mapping = t[3], poslist = [t.lineno(1)]) else: t[0] = real_sprite.RecolourSprite(mapping = t[5], label = t[1], poslist = [t.lineno(1)]) def p_template_declaration(self, t): 'template_declaration : TEMPLATE ID LPAREN id_list RPAREN LBRACE spriteset_contents RBRACE' t[0] = spriteblock.TemplateDeclaration(t[2], t[4], t[7], t.lineno(1)) def p_template_usage(self, t): '''template_usage : ID LPAREN expression_list RPAREN | ID COLON ID LPAREN expression_list RPAREN''' if len(t) == 5: t[0] = real_sprite.TemplateUsage(t[1], t[3], None, t.lineno(1)) else: t[0] = real_sprite.TemplateUsage(t[3], t[5], t[1], t.lineno(1)) def p_spriteset_contents(self, t): '''spriteset_contents : real_sprite | template_usage | spriteset_contents real_sprite | spriteset_contents template_usage''' if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[2]] def p_replace(self, t): '''replace : REPLACESPRITE LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE | REPLACESPRITE ID LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE''' if len(t) == 9: t[0] = replace.ReplaceSprite(t[4], t[7], t[2], t.lineno(1)) else: t[0] = replace.ReplaceSprite(t[3], t[6], None, t.lineno(1)) def p_replace_new(self, t): '''replace_new : REPLACENEWSPRITE LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE | REPLACENEWSPRITE ID LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE''' if len(t) == 9: t[0] = replace.ReplaceNewSprite(t[4], t[7], t[2], t.lineno(1)) else: t[0] = replace.ReplaceNewSprite(t[3], t[6], None, t.lineno(1)) def p_base_graphics(self, t): '''base_graphics : BASE_GRAPHICS LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE | BASE_GRAPHICS ID LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE''' if len(t) == 9: t[0] = base_graphics.BaseGraphics(t[4], t[7], t[2], t.lineno(1)) else: t[0] = base_graphics.BaseGraphics(t[3], t[6], None, t.lineno(1)) def p_font_glyph(self, t): '''font_glyph : FONTGLYPH LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE | FONTGLYPH ID LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE''' if len(t) == 9: t[0] = font.FontGlyphBlock(t[4], t[7], t[2], t.lineno(1)) else: t[0] = font.FontGlyphBlock(t[3], t[6], None, t.lineno(1)) def p_alt_sprites(self, t): 'alt_sprites : ALT_SPRITES LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE' t[0] = alt_sprites.AltSpritesBlock(t[3], t[6], t.lineno(1)) # # Sprite sets/groups and such # def p_spriteset(self, t): 'spriteset : SPRITESET LPAREN expression_list RPAREN LBRACE spriteset_contents RBRACE' t[0] = spriteblock.SpriteSet(t[3], t[6], t.lineno(1)) def p_spritegroup_normal(self, t): 'spritegroup : SPRITEGROUP ID LBRACE spriteview_list RBRACE' t[0] = spriteblock.SpriteGroup(t[2], t[4], t.lineno(1)) def p_spritelayout(self, t): '''spritelayout : SPRITELAYOUT ID LBRACE layout_sprite_list RBRACE | SPRITELAYOUT ID LPAREN id_list RPAREN LBRACE layout_sprite_list RBRACE''' if len(t) == 6: t[0] = spriteblock.SpriteLayout(t[2], [], t[4], t.lineno(1)) else: t[0] = spriteblock.SpriteLayout(t[2], t[4], t[7], t.lineno(1)) def p_spriteview_list(self, t): '''spriteview_list : | spriteview_list spriteview''' if len(t) == 1: t[0] = [] else: t[0] = t[1] + [t[2]] def p_spriteview(self, t): '''spriteview : ID COLON LBRACKET expression_list RBRACKET SEMICOLON | ID COLON expression SEMICOLON''' if len(t) == 7: t[0] = spriteblock.SpriteView(t[1], t[4], t.lineno(1)) else: t[0] = spriteblock.SpriteView(t[1], [t[3]], t.lineno(1)) def p_layout_sprite_list(self, t): '''layout_sprite_list : | layout_sprite_list layout_sprite''' if len(t) == 1: t[0] = [] else: t[0] = t[1] + [t[2]] def p_layout_sprite(self, t): 'layout_sprite : ID LBRACE layout_param_list RBRACE' t[0] = spriteblock.LayoutSprite(t[1], t[3], t.lineno(1)) def p_layout_param_list(self, t): '''layout_param_list : assignment | layout_param_list assignment''' if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[2]] # # Town names # def p_town_names(self, t): '''town_names : TOWN_NAMES LPAREN expression RPAREN LBRACE town_names_param_list RBRACE | TOWN_NAMES LBRACE town_names_param_list RBRACE''' if len(t) == 8: t[0] = townnames.TownNames(t[3], t[6], t.lineno(1)) else: t[0] = townnames.TownNames(None, t[3], t.lineno(1)) def p_town_names_param_list(self, t): '''town_names_param_list : town_names_param | town_names_param_list town_names_param''' if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[2]] def p_town_names_param(self, t): '''town_names_param : ID COLON string SEMICOLON | LBRACE town_names_part_list RBRACE | LBRACE town_names_part_list COMMA RBRACE''' if t[1] != '{': t[0] = townnames.TownNamesParam(t[1], t[3], t.lineno(1)) else: t[0] = townnames.TownNamesPart(t[2], t.lineno(1)) def p_town_names_part_list(self, t): '''town_names_part_list : town_names_part | town_names_part_list COMMA town_names_part''' if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[3]] def p_town_names_part(self, t): '''town_names_part : TOWN_NAMES LPAREN expression COMMA expression RPAREN | ID LPAREN STRING_LITERAL COMMA expression RPAREN''' if t[1] == 'town_names': t[0] = townnames.TownNamesEntryDefinition(t[3], t[5], t.lineno(1)) else: t[0] = townnames.TownNamesEntryText(t[1], t[3], t[5], t.lineno(1)) # # Snow line # def p_snowline(self, t): 'snowline : SNOWLINE LPAREN ID RPAREN LBRACE snowline_assignment_list RBRACE' t[0] = snowline.Snowline(t[3], t[6], t.lineno(1)) # # Various misc. main script blocks that don't belong anywhere else # def p_param_assignment(self, t): 'param_assignment : expression EQ expression SEMICOLON' t[0] = actionD.ParameterAssignment(t[1], t[3]) def p_error_block(self, t): 'error_block : ERROR LPAREN expression_list RPAREN SEMICOLON' t[0] = error.Error(t[3], t.lineno(1)) def p_disable_item(self, t): 'disable_item : DISABLE_ITEM LPAREN expression_list RPAREN SEMICOLON' t[0] = disable_item.DisableItem(t[3], t.lineno(1)) def p_cargotable(self, t): '''cargotable : CARGOTABLE LBRACE cargotable_list RBRACE | CARGOTABLE LBRACE cargotable_list COMMA RBRACE''' t[0] = cargotable.CargoTable(t[3], t.lineno(1)) def p_cargotable_list(self, t): '''cargotable_list : ID | STRING_LITERAL | cargotable_list COMMA ID | cargotable_list COMMA STRING_LITERAL''' if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[3]] def p_railtypetable(self, t): '''railtype : RAILTYPETABLE LBRACE railtypetable_list RBRACE | RAILTYPETABLE LBRACE railtypetable_list COMMA RBRACE''' t[0] = railtypetable.RailtypeTable(t[3], t.lineno(1)) def p_railtypetable_list(self, t): '''railtypetable_list : railtypetable_item | railtypetable_list COMMA railtypetable_item''' if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[3]] def p_railtypetable_item(self, t): '''railtypetable_item : ID | STRING_LITERAL | ID COLON LBRACKET expression_list RBRACKET''' if len(t) == 2: t[0] = t[1] else: t[0] = assignment.Assignment(t[1], t[4], t[1].pos) def p_basecost(self, t): 'basecost : BASECOST LBRACE generic_assignment_list RBRACE' t[0] = basecost.BaseCost(t[3], t.lineno(1)) def p_deactivate(self, t): 'deactivate : DEACTIVATE LPAREN expression_list RPAREN SEMICOLON' t[0] = deactivate.DeactivateBlock(t[3], t.lineno(1)) def p_grf_block(self, t): 'grf_block : GRF LBRACE assignment_list RBRACE' t[0] = grf.GRF(t[3], t.lineno(1)) def p_skip_all(self, t): 'skip_all : SKIP_ALL SEMICOLON' t[0] = skipall.SkipAll(t.lineno(1)) def p_engine_override(self, t): 'engine_override : ENGINE_OVERRIDE LPAREN expression_list RPAREN SEMICOLON' t[0] = override.EngineOverride(t[3], t.lineno(1)) def p_sort_vehicles(self, t): 'sort_vehicles : SORT_VEHICLES LPAREN expression_list RPAREN SEMICOLON' t[0] = sort_vehicles.SortVehicles(t[3], t.lineno(1)) def p_tilelayout(self, t): 'tilelayout : TILELAYOUT ID LBRACE tilelayout_list RBRACE' t[0] = tilelayout.TileLayout(t[2], t[4], t.lineno(1)) def p_tilelayout_list(self, t): '''tilelayout_list : tilelayout_item | tilelayout_list tilelayout_item''' if len(t) == 2: t[0] = [t[1]] else: t[0] = t[1] + [t[2]] def p_tilelayout_item_tile(self, t): 'tilelayout_item : expression COMMA expression COLON expression SEMICOLON' t[0] = tilelayout.LayoutTile(t[1], t[3], t[5]) def p_tilelayout_item_prop(self, t): 'tilelayout_item : assignment' t[0] = t[1] nml-0.4.4/nml/free_number_list.py0000644000567200056720000001115012643457545020150 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" from nml import generic class FreeNumberList(object): """ Contains a list with numbers and functions to pop one number from the list, to save the current state and to restore to the previous state. @ivar total_amount: Amount of numbers in the beginning. @type total_amount: C{int} @ivar stats: Statistics about id usage. @type stats: Tuple of C{int} and L{Position} refering to the amount on location of most concurrently used ids. @ivar free_numbers: The list with currently unused numbers. @type free_numbers: C{list} @ivar states: A list of lists. Each sublist contains all numbers that were L{popped} between the last call to L{save} and the next call to L{save}. Every time L{save} is called one sublist is added, every time L{restore} is called the topmost sublist is removed and it's values are added to the free number list again. @type states: C{list} @ivar used_numbers: A set with all numbers that have been used at some time. This is used by L{pop_unique}. @type used_numbers: C{set} @ivar exception: Exception to be thrown when there is no number available when requested via pop() or pop_global(). @type exception: C{str} @ivar exception_unique: Exception to be thrown when there is no unique number available when it is requested via pop_unique(). @type exception_unique: C{str} """ def __init__(self, free_numbers, exception, exception_unique): self.total_amount = len(free_numbers) self.stats = (0, None) self.free_numbers = free_numbers self.states = [] self.used_numbers = set() self.exception = exception self.exception_unique = exception_unique def pop(self, pos): """ Pop a free number from the list. You have to call L{save} at least once before calling L{pop}. @param pos: Positional context. @type pos: L{Position} @return: Some free number. """ assert len(self.states) > 0 if self.free_numbers == 0: raise generic.ScriptError(self.exception, pos) num = self.free_numbers.pop() self.states[-1].append(num) self.used_numbers.add(num) num_used = len(self.used_numbers) if num_used > self.stats[0]: self.stats = (num_used, pos) return num def pop_global(self, pos): """ Pop a free number from the list. The number may have been used before and already been restored but it'll never be given out again. @param pos: Positional context. @type pos: L{Position} @return: Some free number. """ if self.free_numbers == 0: raise generic.ScriptError(self.exception, pos) return self.free_numbers.pop() def pop_unique(self, pos): """ Pop a free number from the list. The number has not been used before and will not be used again. @param pos: Positional context. @type pos: L{Position} @return: A unique free number. """ for num in reversed(self.free_numbers): if num in self.used_numbers: continue self.free_numbers.remove(num) self.used_numbers.add(num) num_used = len(self.used_numbers) if num_used > self.stats[0]: self.stats = (num_used, pos) return num raise generic.ScriptError(self.exception_unique, pos) def save(self): """ Save the current state. All calls to L{pop} will be saved and can be reverted by calling L{restore} """ self.states.append([]) def restore(self): """ Add all numbers given out via L{pop} since the last L{save} to the free number list. """ assert len(self.states) > 0 self.states[-1].reverse() self.free_numbers.extend(self.states[-1]) self.states.pop() nml-0.4.4/nml/output_nml.py0000644000567200056720000000212312643457545017032 0ustar jenkinsjenkins00000000000000__license__ = """ NML is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. NML is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with NML; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.""" import codecs from nml import output_base class OutputNML(output_base.TextOutputBase): """ Class for outputting NML. """ def __init__(self, filename): output_base.TextOutputBase.__init__(self, filename) def open_file(self): return codecs.open(self.filename, 'w', encoding='utf-8') def write(self, text): self.file.write(text) def newline(self): self.file.write('\n') nml-0.4.4/examples/0000755000567200056720000000000012643457623015301 5ustar jenkinsjenkins00000000000000nml-0.4.4/examples/road_vehicle/0000755000567200056720000000000012643457623017725 5ustar jenkinsjenkins00000000000000nml-0.4.4/examples/road_vehicle/example_road_vehicle.nml0000644000567200056720000001712312643457545024603 0ustar jenkinsjenkins00000000000000/* * This file is aimed to provide an example on how to code a vehicle in NML * In this case a road vehicle is coded, other vehicle types work in a similar fashion. * To keep the code readable, not every property or variable is documented in * detail, refer to the vehicle-specific reference in the documentation. * The coded vehicle is relatively simple. Apart from the minimum required, * it has cargo-specific capacaity and graphics. For a more advanced example, * refer to the train example. * * The vehicle coded here is a first-generation flatbed truck. * It is taken from OpenGFX+ Road Vehicles, with some modifications * to provide a better example. Original graphics are by DanMack and Zephyris, * coding by Terkhen and planetmaker. * * Apart from this file, you will also need the following * - Graphics, found in 'gfx' folder. * - Language files, to be placed in the 'lang' folder. * Currently only english.lng is supplied. */ /* * First, define a grf block. This defines some basic properties of the grf, * which are required for the grf to be valid and loadable. Additionally, * user-configurable parameters are defined here also. */ grf { /* This grf is part of NML, therefore "NML" is chosen as the first three * characters of the GRFID. It is the fourth real grf defined as part of * NML, therefore the last character is set to 3 as numbering is zero-based. * Successive grfs will have 4, 5, etc., to make sure each example grf has * a unique GRFID. */ grfid: "NML\03"; /* GRF name and description strings are defined in the lang files */ name: string(STR_GRF_NAME); desc: string(STR_GRF_DESC); /* This is the first version, start numbering at 0. */ version: 0; min_compatible_version: 0; } /* Check for engine pool */ if (!dynamic_engines) { error(ERROR, USED_WITH, string(STR_ERROR_ENGINE_POOL)); } /* Define a cargo translation table * All cargo types that need any special treatment must be included here * Add cargos used in the refit mask first, as those are limited to 32 bits * so these cargos must be within the first 32 entries */ cargotable { /* Used in refit mask */ LVST, // Livestock (piece goods) WOOL, // Wool (piece goods, covered) SCRP, // Scrap metal (bulk) FICR, // Fibre crops (bulk, piece goods) PETR, // Petrol / fuel oil (liquid) RFPR, // Refined products (liquid) /* Not used in refit mask */ GOOD, // Goods (express) ENSP, // Engineering supplies (express, piece goods) FMSP, // Farm supplies (express, piece goods) MNSP, // Manufacturing supplies (express, piece goods) PAPR, // Paper (piece goods) STEL, // Steel (piece goods) VEHI, // Vehicles (piece goods, oversized) COPR, // Copper (piece goods) WOOD, // Wood (piece goods) } /* Sprite template for a truck */ template tmpl_truck(x) { [ 0 + x, 0, 8, 18, -3, -10] [ 16 + x, 0, 20, 16, -14, -7] [ 48 + x, 0, 28, 12, -14, -6] [ 96 + x, 0, 20, 16, -6, -7] [ 128 + x, 0, 8, 18, -3, -10] [ 144 + x, 0, 20, 16, -14, -7] [ 176 + x, 0, 28, 12, -14, -6] [ 224 + x, 0, 20, 16, -6, -7] } /* Define various cargo-specific graphics */ /* Paper */ spriteset(flatbed_truck_1_paper_empty, "gfx/flatbed_truck_1_paper.png") { tmpl_truck(0) } spriteset(flatbed_truck_1_paper_full, "gfx/flatbed_truck_1_paper.png") { tmpl_truck(260) } spritegroup flatbed_truck_1_paper { loaded: [flatbed_truck_1_paper_empty, flatbed_truck_1_paper_full]; loading: [flatbed_truck_1_paper_empty, flatbed_truck_1_paper_full]; } /* Steel */ spriteset(flatbed_truck_1_steel_empty, "gfx/flatbed_truck_1_steel.png") { tmpl_truck(0) } spriteset(flatbed_truck_1_steel_full, "gfx/flatbed_truck_1_steel.png") { tmpl_truck(260) } spritegroup flatbed_truck_1_steel { loaded: [flatbed_truck_1_steel_empty, flatbed_truck_1_steel_full]; loading: [flatbed_truck_1_steel_empty, flatbed_truck_1_steel_full]; } /* Wood */ spriteset(flatbed_truck_1_wood_empty, "gfx/flatbed_truck_1_wood.png") { tmpl_truck(0) } spriteset(flatbed_truck_1_wood_full, "gfx/flatbed_truck_1_wood.png") { tmpl_truck(260) } spritegroup flatbed_truck_1_wood { loaded: [flatbed_truck_1_wood_empty, flatbed_truck_1_wood_full]; loading: [flatbed_truck_1_wood_empty, flatbed_truck_1_wood_full]; } /* Copper */ spriteset(flatbed_truck_1_copper_empty, "gfx/flatbed_truck_1_copper.png") { tmpl_truck(0) } spriteset(flatbed_truck_1_copper_full, "gfx/flatbed_truck_1_copper.png") { tmpl_truck(260) } spritegroup flatbed_truck_1_copper { loaded: [flatbed_truck_1_copper_empty, flatbed_truck_1_copper_full]; loading: [flatbed_truck_1_copper_empty, flatbed_truck_1_copper_full]; } /* Goods */ spriteset(flatbed_truck_1_goods_empty, "gfx/flatbed_truck_1_goods.png") { tmpl_truck(0) } spriteset(flatbed_truck_1_goods_full, "gfx/flatbed_truck_1_goods.png") { tmpl_truck(260) } spritegroup flatbed_truck_1_goods { loaded: [flatbed_truck_1_goods_empty, flatbed_truck_1_goods_full]; loading: [flatbed_truck_1_goods_empty, flatbed_truck_1_goods_full]; } /* Decide capacity based on cargo type (cargo_capacity callback) * cargo_type_in_veh is available in the purchase list also, * so a separate CB for in the purchase list is not needed */ switch (FEAT_ROADVEHS, SELF, flatbed_truck_1_capacity_switch, cargo_type_in_veh) { GOOD: return 14; ENSP: return 14; FMSP: return 14; MNSP: return 14; PAPR: return 15; PETR: return 10; RFPR: return 10; STEL: return 15; VEHI: return 12; return 20; } /* Define the road vehicle */ item(FEAT_ROADVEHS, flatbed_truck_1) { property { /* Properties common to all vehicle types */ name: string(STR_NAME_FLATBED_TRUCK_1); climates_available: bitmask(CLIMATE_TEMPERATE, CLIMATE_ARCTIC, CLIMATE_TROPICAL); introduction_date: date(1926,01,01); model_life: 65; /* retire_early not set, use default retirement behaviour */ vehicle_life: 15; reliability_decay: 20; refittable_cargo_classes: bitmask(CC_PIECE_GOODS, CC_EXPRESS); non_refittable_cargo_classes: bitmask(CC_PASSENGERS, CC_REFRIGERATED); /* Livestock, wool and fibre crops can be transported acc. cargo classes * But we don't want that, so disable refitting for them. * Scrap metal, petrol and refined products cannot be transported acc. cargo classes * We do want to transport those, so enable refitting for them. */ cargo_allow_refit: [LVST, WOOL, SCRP, FICR, PETR, RFPR]; cargo_disallow_refit: []; // we allow other cargoes, if class matches loading_speed: 5; cost_factor: 108; running_cost_factor: 90; /* cargo_age_period is left at default */ /* RV-specific properties */ sprite_id: SPRITE_ID_NEW_ROADVEH; speed: 48 km/h; misc_flags: bitmask(ROADVEH_FLAG_2CC); refit_cost: 0; // Refitting is free /* callback_flags are not set, no need to manually enable callbacks */ running_cost_base: RUNNING_COST_ROADVEH; power: 120 hp; weight: 9.5 ton; /* TE and air drag coefficient is left at default */ cargo_capacity: 20; // Changed by callback sound_effect: SOUND_BUS_START_PULL_AWAY; /* Visual effect is left at default (no effect) */ } /* Define graphics for various cargo types, as well as the capacity callback */ graphics { cargo_capacity: flatbed_truck_1_capacity_switch; PAPR: flatbed_truck_1_paper; STEL: flatbed_truck_1_steel; COPR: flatbed_truck_1_copper; WOOD: flatbed_truck_1_wood; default: flatbed_truck_1_goods; // Default to Goods. } } nml-0.4.4/examples/road_vehicle/gfx/0000755000567200056720000000000012643457623020511 5ustar jenkinsjenkins00000000000000nml-0.4.4/examples/road_vehicle/gfx/flatbed_truck_1_copper.png0000644000567200056720000000406212643457545025625 0ustar jenkinsjenkins00000000000000‰PNG  IHDRø€^<sRGB®ÎéPLTEÿîîïïððññòòóóôôõõöö¨¨¨¸¸¸ÈÈÈØØØèèèüüü4ý(2ã3E~hÄï@SÁžAXf mU]oUSg4˜¿¸¡Áj!ÏjPh?ò¤qÛ„´ð÷ÃOØL֘ċ-Ò¢’²®‘K¼ÈÄã^盬ClÖŠóº“øÃL™Z.ö5Ãr´í­• –/æ5X/äY v?kÝŤ¥,%ðÓ“l%r[NÖÚæEbâëKhKSA—uÓd#Åç,¾ê|AN<QNƒgª^.ö5à0šOä4€›uû‘jÀø4ÿ–ë…lk0ã󨪅oÒÄ‹)ñ½6qâ!œ~ë`h×m€À¡0¥ï3áý5>û˜(ø[zƃX)>¾Vý”x±™x–8IüTD ®œ©z¾øBãù2\¿N4ðø8ÿ–3þ|±©Á„?% ¸²™¦¥ÄK%éPé†~t×(֣Ϲ°OkhƒjS"£Á­’W˜j/¾Ôàz´[k€¶"ä¶3>]ìhÀøAÕûjŒ¬¾ä¾‹£Tî·‹+¾™ƒBlxà§ÒÎÅU$Þ•Ô€ŠëôÝÆý’9RàbªïuGÄêŸNÇ#Ò„ËŸ¸ü3‡ï§Ÿl¡Ì%~òÉEƒ©eR5¤%þ¨ˆ’ˆïy‰Ùê›Áj—\ðv1lP-r¤"(¡Ã¡è?ùÃÜy(¨zh4®G‚:î?@bf‹q¶a€¶Jø°æ8Q˜a#´Ú>X9@ßw„äÌàOÕhìsÇj;Kƒ«¨Žj@øgiøž—ç»pm_‚žŒi—T퀟îv E<\"NïÀiÆRªAuxh‰«DíÀûÒø6ÅÞ¡­"¾"œ„Dz÷­Õƒð©ß¬Î dµl€sïüÏ4ð¼8Ÿª‹€4z(ç)ç'{vð KO ÑcïÞ®×÷û°D”²†+èT|÷‹Né™ÇdìØ2Á ÿ;  êåõh°²ú¢ÂSñæ±÷Fì(¾'÷+z_ºô·‡×f‘ü|ðËß ¨Ÿ¬Ýà;iP”Pñáãí ja®8”úά’~EœÃ÷ä„H~>™-WÐêq‹ ð­4À×+Ï…Q?PËN;4Á®ú¥ö/Ãx‹šØIø0ïº_ÓK$ÿPôNàEúêá[žþüÆñŽìißIšð½‰? þGí)9[ú[IEND®B`‚nml-0.4.4/examples/road_vehicle/gfx/flatbed_truck_1_steel.png0000644000567200056720000000407512643457545025455 0ustar jenkinsjenkins00000000000000‰PNG  IHDRø€^<sRGB®ÎéPLTEÿîîïïððññòòóóôôõõöö¨¨¨¸¸¸ÈÈÈØØØèèèüüü4ο¦Á„?ݬj ¢²#YÀE™0-ßÍ™@¤}¥kúAÒM tR ˜Îy¬5CÛEen¯¬ÂJÆÖÖà¼HˆãÓMÂïûŽŒ‡ ±f¼\öX/Û̧§7ÞlkÀa˜”i øNÂÇù×4X,d]ÆŸ³¾(«¸ÔBj\ù¿5ö’.U•tâK\µN÷·Gß—’L乺[Tæiáá[ • v<ù)ëZ™p·#VŒ_Sd’´íÁÕ‡š³>Ž)¸NzkȽlS2¡Áé|¹_` wºùRƒûI^Л½ þE^Ö5˜ðéfCÆ÷²ÞecPê÷]BÙ?6Ìx2[ ‰XóÀ«ÒÖ†Yáoµ b}Â~Éœ½G)¨bªmuCĪ_VÆ#ÒˆËW\ þ™Â·UŒOeA¤Œëä¬ÁØ2)›Ò€Œß©á¤⼦R?Ÿª1ÛKNx »6¨ ÙÓ”ÐþPôŸôá>y(Èzh4¶E‚:ì?@¢ë¦Ùãl]m•ðaÍ¡Qh‚ °Z­¬, ÷ˆï:Btfp§ê ÇÊlÜ ”mGip—ç½þQþÈ *_Á¹]x=m/)Û?ýÜmAŠpØH/ÞB¥éE©:Õà¡%Ì µÚ7“Æ·)à Ú*â+‰H¤ 7ßZ=ŸúÍâÌ@¥–  §ÁÔ;˜ÓQ@¸Sý\" =$ˆu”Ó“=;x„¥§„౉woÓêÛ­›"J †3èP|û›Nñ™Ç)zކhl™òòó4€¬Ç32‹Rÿ@Tx*î¡xl½ÛŠozëH‡ýŠÞ—Îýíá´™%?êeïT„O¥=êñï A& ã¡þ‰9èáZ˜ÍŽ¥>‡3«¨_gÿ=9á{’„OÅ–3hñ¸Åð­4À×+%ž ƒ~ ævi‚]ôKí^&úñ4±ƒðaÞe¿¦—Hî¡è4À›ôÕõ<ýùã5ȸ¦}'i^À÷ÿ~üÕ€ê ‹fOIEND®B`‚nml-0.4.4/examples/road_vehicle/gfx/flatbed_truck_1_goods.png0000644000567200056720000000402212643457545025444 0ustar jenkinsjenkins00000000000000‰PNG  IHDRø€^<sRGB®ÎéPLTEÿîîïïððññòòóóôôõõöö¨¨¨¸¸¸ÈÈÈØØØèèèüüü4ß+€¿Ýè} †²Ý*xÈišã;‚I3>ÕŸj¹û/DÎ×µR}78}%ûwÁs`o«Äƒ²Ìk݃{ è…GÏx°Ô'í†Ó·ƒ•Nߣ}§Â챋S< ôG=pØOñ@L豌ܧRÞª/T›ÝÇx@m´É‘.Éà„ ‡¤Oøß<%f|/mª¸ÿ@Ãð*‘#®6 pµ }Øs !ÈH¥åúåÒ‚ú(¯¯ŽïÉx*Ìö³<ôG= l?ËžºÇ²Ç;} ‹Kþw·+âas‚ù* ,+Ùá¥%Î 0µƒÚ7OSÌ®xì$é$ApÒ›O­ž¤Oýfqg˜oés™¯T˜'ìçyPõ 8×BOÝCû[ýœñ0ÁzëCæÛ;Ü (K¿¢ŸŽîôv½z<†ù""¥Ñ.ƒNÕ·ü•@êe©Ç q-°0öç Çî!õ¢Ô?QÐP<¶ŒÅ‡£õAÇýŠž—Îýíé½™-?GêåxEì‰þ|YkDäAq?ÒØq]Ìø<êñOßÂlv¦(õ9\Y&ýŠbŸ““~`ùIúTl]½þ7x€W®ámŠ‚œOÚyA“ì¢_*ÿ@5œÓ¢&v’>¬»ì×<øŸïþ‰~õð-O½}ãød®¦}gÐnß þ÷yðÚh˜õ¿'8˜nÌp°XȽÊtÒ¸áu”´pßûŸ°™ŒÖ+‹\Ò⳪êº)áE"÷:ïÜh"“´’¬ëŠð›9•š/Ö9ðÃR¼¡hï ðÆݘæ`¹{9X~ ¬U&]UeÅÃÿÔä$9áAÛr´†Ø6/ __|[ º¬›&!<ŒŸ?’ø²u½Yx.¢Vê}¾Xç€Â¦â\rp|À˜¾æÏq°\HžƒÈu>?k$¾‰…£ðÒ¡ðN¿µ7”m3I @àP˜Òõÿû%>û˜(ø.^øñZ#VŒÏ#…¯d7 /²Â3'‘ðc%88°Rçéâ0lœ.ÁÁñÏî’+ÃÁ„?]d9ˆ]G0 ›)aZ¾Ÿ+’ö™nè“n;H'Šõè”G¯1]ÙÜ_Z‰NÆÒ6 |$m+Ƨ‹&…? = AeNxÁœøxÕ–à`É÷:ÆUŸæ`ï8 |œ?ÇÁb!yB×)ŠKUÇV ¥ñÉÿm°7Ø0éÊIU G¾Êe׫t» C%HDž«¿F6'‰ ßZp&Øñ¤§h©ÃÝŽX1~C‘ɤíήú8æÂ>é­áèU› ŽH4¬á@?rp8rÕoåñßÄ)ÏÁ„O+®Ãý—)ñ…/¹ïâ(¥ý²aÅ—@³µPˆ ü”ÊÚ°*<ámI­R±­ºš°_rÎÞ£¸˜ì:ÕRbõ‡UZ†ñˆ4âò'®ÿLáÛÞJÆ'[(SÂå2s0¶Lª¦}8 ªßÊáïÃÁä:td­~>UcµW\ðv1lP,r "0¡ü!é?éÃ|s“PõÐhl‡ ª°ÿ@}?Y䀳õ=´U‡5‡B¡2ÀFh™?XY@ßu„èÌÀVKèq0nª¶8ÀªßÊáïÄsjà|®í‹×“QöŠªðÓÏݨ‡Èñâ-8ÍPV²—-ZªR[ð¾9i|›b®ÐV_N”D zõ­Õð©ß,Î dµì€SïÜ—ƒÃ“9˜„çãSõl€Fb]ÊéÉî<ÂÒSBðØÄ»·íÔõÚÏ)æ Úßþ¡SA|f «wèq€-Sœ~Põx$VCTx*À<ÖÞˆmÅ·ƒuI‡ýŠÞ—Îýíæ¸™)ßürÀ÷2Â'kçBøý|Í%T<ø_9Ý\ ³Åž  3˨_QÎþ{rÂ÷(ß ŸÌ–+hñ¸Å¿øz¥ÂsaÐä¼ÓöKš`ýR¹—‰~<„Ml'|˜wÙ¯é%’{(z%ð"}ôp-O}?q¼"{Ú3“æÖ"þn-³·úÏrzîƒ( $)«S5M'…‘ã=—s.ZU¾}ùVeÚWЪ|³6÷î|oŒ'¬Wã¾;ß?ÞÃÉ誖ƒª2Ö.Ëš5výnÐ0ÅòS‰g¬÷‚¶ËÓÄš¥Gøü(̱¬ËÔ<±@;>쉷gAw¥›ó'º\Ð])è5Å简Ï®erÞç@jlê^s •.s õŸ< qŠløtðUk©µ¹ Y×4¸âqeÐã¬öYâÛRÐþD› ºÍ%CXS|îòøÝ‚ßrâßæ@ŽÔôvð’¥4|J°âs@ÚGº.ÏÁ†Ï­~Ê^¢ÂÄ-4.ø%I|ãχ=,¦uYr Ý>¨ËÝȾbhRünGèÂñ¸Öyå&÷Ño¡Ÿ$¾a Ž÷ tžÍmÜ^q !íJ‡ªðQñã˜ÃǴ÷ÄÁ†Ï}3>å rrâgʼ½ÄA÷}Ýs û…œ$ŠZ€ÜÖkHm³ë1ñ"´9*«8¸ÃºZŠÏ]6hsáÄÛrâ;‹üÚÔj™öš…6n¯8€¬SêIõGXñ)´C(q°áóA=>á@Å®ÓJÄG6Ãjó‰¿Ú%N|'æ {jØ[w)ÝTRòP˜Ò×™ˆ”>ûXSõ49¤¾ Ç/ b¥øÜåð­¹>¿¹Î!1\cc6µe8+¬÷½~Á ¾óªÏqpÀ'<¾îàl‰ƒ>×úN—9è×iˆ_Ù «Mô”øiw Z´žpèüàÿör…pr ¨É— ß®×)±¹¾qLð‚Ñ×QÐ+b¥øÌáÏóD‰‡A×9Z=+.ä P›>r [šðýø‚`[·¤ú<|Ïã·£V%ø¬øv,ràñwÕw½ˆ¬–Õ.ñ×N X\tïS%î†f€q×ÉæëÛ}žáœçšn‰Íâ"°9çÀ™`Å#¾h†Á,ñjG¬ ‘Ù Ý  ŒeÕ¬žkìÎT‘ÚH_SE#}q}ÅT ªþ]_•9xàK:ht‘ÆgÕÃ_WµœÆ6¬É5×]\Ƶq\¬øhv„8pÃÞXçbUA;SÃt-„â.ö¶Æõ’c¥ÀÅÌõj/–ñ]L<‘¸ÜãÝà¿9|79ÃøäuºgÈíª¶@mçp€ª—Æ?‡ƒÍu=²~÷]5ª½‡ªÜ£¤\ .ºÉ™6ˆÀ„ ›¡_òpænzÑ@¡qW ÐÆõ‚˜¦Í"gœmš ¬>\«16B›âÆ Äs73âã†Aˆtϰíª«•Jbæj;‹Pý»þYët”{­ÐêEhõ5¦]ô¬vÀÏ¿µr@EÜ\BN0ÞÓ̦6“¹à¦%VzïۃƷ)ë Ê*âÂI‚ÈA?}ku'|¿+Hö ¹]µ„¼ƒÛ³Ú?Ê:™ ^«Áïê‡v·%ƒFúp>äüdßm<ÂÒSBôèÈ«÷rµ·Û´oDŒYVЩøNÒ® Ý3ð®ZAŽÇ”|£þ?hw‚{†ƒÕßžŠg2˜³@ñ­Ô9:®Wô¾t¯owÏÍNù9øPçf|/`|~ÜB…«˜©þO4/SÕ xð¿zt÷%ÌUg‚RÙMR¯(æð=9ᔟ„ÏfkS|*o «ºüUàë•÷…Q=0ûJ;/h‚=ÔKë_¨†ãaXTÄN‡yõzÛÌIõ«8Àƒô§›/yöëƒí7rP±§}2h¾Ï&þ÷qðÑw*jFÌu1IEND®B`‚nml-0.4.4/examples/road_vehicle/lang/0000755000567200056720000000000012643457623020646 5ustar jenkinsjenkins00000000000000nml-0.4.4/examples/road_vehicle/lang/english.lng0000644000567200056720000000124312643457545023004 0ustar jenkinsjenkins00000000000000##grflangid 0x01 STR_GRF_NAME :NML Example NewGRF: Road Vehicle STR_GRF_DESC :{ORANGE}NML Example NewGRF: Road Vehicle{}{BLACK}This NewGRF is intended to provide a coding example for the high-level NewGRF-coding language NML.{}Original graphics by {SILVER}DanMack, Zephyris, {BLACK}coding by {SILVER}Terkhen, planetmaker.{}{BLACK}This NewGRF defines first-generation flatbed truck. STR_ERROR_ENGINE_POOL :enable multiple NewGRF engine sets = on STR_NAME_FLATBED_TRUCK_1 :Flatbed Truck MkI nml-0.4.4/examples/object/0000755000567200056720000000000012643457623016547 5ustar jenkinsjenkins00000000000000nml-0.4.4/examples/object/cc_grid.png0000644000567200056720000000613412643457545020656 0ustar jenkinsjenkins00000000000000‰PNG  IHDRÞ1óüKsRGB®ÎéPLTEÿ 000@@@PPPdddttt„„„”””¨¨¨¸¸¸ÈÈÈØØØèèèüüü4)ß™z &ÀæÁ—(Ú|ø¢½îͧÜàÝK|Wôþø u»Õ º]ßœø¨ÚM´è4ÚÐèzá{˜jC¦ÇH°ÓíüøE›_¤7нùô×àÞmà£C2Ñ1LPªïÔ„g|µÔ£“kC£ëƒï¹ÿúG‹š[_Šhá òº7Wôá½[ÅwFïװ΀@R˜ËÒÄñ]»Á‚Nª ®¾‹è7ÄN§¾éðŽ6 ¾o {sK_€w+øè†¿týIj.ïzÖlø®Ü`C'Ó†Fï´ûV>ÌÔ^‡¾)ñ Œ6,>´7нY¤/À»—ø^é]?¢}T ‰­%Ñw€ñ…ÄÑøÎ|A§ék-mft÷çÞõYÖôɧžú€¨““o±b&|â Œ/ÀÈÑ»0z#¼{ÁthÂ_¸½ Žëv…WŸ‡à=øÎ¼d·^[› ÝãsëIõ úïåÛkÒ71¾¡Ñ†Ãä äÚ»$4B¼{Êô®yƒIè“LÝD>Áûð½{éunaáw }ÚZ쳞TŸ û^ü¿¹½j}“ãm(|qÞÀeΈ"#Ä»'lp§w+áBR0”Hß@}¯öñY¯®mëµû¯n­WºÇ`[‹ž_vÿJôEyã’É-yï~÷Zï (j³B:¨ë³í€|¾±ÑæÇ‡÷†eT ÏÈïnë½²¶L¼» *Lß;Ó• n#÷J6Mñf«Bˆj/ÆËáG›• J(>´7êRþùú*Es¼:Ÿ[%_ÏPljŒO²×cg+Žmé.ø s=ë\ùþ¼Û~-}й<Úm†/ªÇÝ£môl€’¯¶Íq n55hu1û @Ƚ%Ai2Iúî…Äà{~Üo¿¦>àNÚhq¯•£×ò)ù^ù,g(¢r!ÖSï½'>¿š)äá÷øpyV&|Ôª“¨»åÑ¡½Jï–Lt&%ÏtçtL&³ÜzÀ¨GŸŽÎóÏ®PUëÙO÷DLïúÆ’ŸÏM-K‘ð¼gʉ˜«PÀÝí})25¨•I2øÏоì™BÐócѽ¥½ëNx4Y¯Â”ü„çvê’á'õT¨rÿ®<£Xo³žà-²žâÄè_Kü|ÿd¨d˳.ઋ®º(“æùcê.¬ì~Ó_лæ|vS﫱%¿Ÿ%¿째¡E`=Õ}/É¥$¯º(ôQU𺠡ý„lPf½“æv¥7}ÿún¢ä·+ÎHïžÛ3ÛÖÓe¦Ì`½LU%yÕE ­ºÈ^w1áÚ{uy5üîVJ~7úœôþÔFÕVmÓzÊß9¬·«.rT]d¯»˜.s¦6¶5à§9^Ó»3“Y r-‚26öm<&²žbërëíª‹ UÙë.<›æCéý–Æàì-âlð6½{2™= µ ½ ù˜ôÖ#ZÑz»êb|ÕEöº æùȪUõpÙb=²Ò»9“¹©pM‚25—ÔÖ£U­·«.FW]d¯»°nšSz'do&s5èÝ”É Ö73½·÷?BÑ;µûôÜÖ[¾êB#«.²×]¨“¢¼'F¢¹Ù[8.Z\Ê=Ù»ÜþíM‚û=^óLS9Ö•R@JNÈþrLZuQ.\É__h—MXˆì¤¨“ÙO[wQ‰ó»Î{à$È€Ž-[2Í}vÕØ~ª: qæÇÙ£nêÔ^z[?¼6ÃlI¶´µ«.öCžª×ØZ@Vþ¦÷-[º¯Vîª ˜ý°›yƒë.ÐYù›Þ·léNO»êj?Ìf^‚º tVþ¦÷-[ðòwGò®ºˆ¡w[ÕEöº tVþ÷¡]ozß²¥FÚÂ0áï?@:ùûà]u¡¯ºxd8xÝůc8ªµ®¬ühœ6¤Çû‡Và”œ¸>ÒvHz—WIðß'·ó  u¾ÌcTšŸÆ›: ¾[±i?uÝÚ^¯ÑYùxzÿCËÈb+À%Fïœé¸`‚)$»­SÊS>o¬Ñ6åVãQú9Êdl02_´æï~‚r^c¢RøîméÅÝØg¢_ãGdå3:ZѳXz_ˆT:Ž·9€ àî[VõXèÇrp›‚Þ=8ÒvôîËÊç kbgwaôŽïÕƒ ÷2æ®`|ŸâO£wøx3èÉnw߇¯YùŒþïÌDïpuœIM…˜î[bì>ÓÃdôÎ3Ñ»ëíû°šÏÊÿÑ ð¦÷¤ê. N®Bïé'6ùél¼( ÞR? *wV~Äà=:s&=½ãˆ”·2jâ³h¾o„™¢2g´Üzû þãs-s&×m¿~Éâ›FïÃt›Œ·èdõóEW¿ !žÞ‡åFoÙ²e‹˜ÞMYùOï{ºeË–ô•oCpËÿ[Óîêav!­IEND®B`‚nml-0.4.4/examples/object/example_object.nml0000644000567200056720000001275112643457545022251 0ustar jenkinsjenkins00000000000000/* * This file is aimed to provide an example on how to code an Object in NML. * To keep the code readable, not every property or variable is documented in * detail, refer to the object-specific reference in the documentation. * * The NewGRF implements a replacement for the company land as NewObject which does * not show a sign but blends in with the terrain naturally. In transparent view * it shows a company-coloured border around the tiles. * * Apart from this file, you will also need the following * - Graphics, found in cc_grid.png (in the same folder) * - Language files, to be placed in the 'lang' folder. * Currently english.lng is supplied. */ /* * First, define a grf block. This defines some basic properties of the grf, * which are required for the grf to be valid and loadable. */ grf { /* This grf is part of NML, therefore "NML" is chosen as the first three * characters of the GRFID. It is the second real grf defined as part of * NML (the first is the train example), therefore the last character is * set to 1. Successive grfs will have 2, 3, etc. there, to make sure each * example grf has a unique GRFID. */ grfid: "NML\01"; /* GRF name and description strings are defined in the lang files */ name: string(STR_GRF_NAME); desc: string(STR_GRF_DESCRIPTION); /* This is the first version, start numbering at 0. */ version: 0; min_compatible_version: 0; /* This NewGRF has no parameters. See the train example NewGRF for parameter * usage */ } /* Using parametrized sprite layouts are only valid in OpenTTD r22723 or later. * Earlier versions will choke on those and otherwise disable the NewGRF. */ if (version_openttd(1,2,0,22723) > openttd_version) { error(FATAL, REQUIRES_OPENTTD, string(STR_VERSION_22723)); } // Template for 19 sprites: one for each possible tile slope template tmpl_groundsprites(x, y) { [ 0+x, y, 64, 31, -31, 0 ] [ 80+x, y, 64, 31, -31, 0 ] [ 160+x, y, 64, 23, -31, 0 ] [ 240+x, y, 64, 23, -31, 0 ] [ 320+x, y, 64, 31, -31, 0 ] [ 398+x, y, 64, 31, -31, 0 ] [ 478+x, y, 64, 23, -31, 0 ] [ 558+x, y, 64, 23, -31, 0 ] [ 638+x, y, 64, 39, -31, -8 ] [ 718+x, y, 64, 39, -31, -8 ] [ 798+x, y, 64, 31, -31, -8 ] [ 878+x, y, 64, 31, -31, -8 ] [ 958+x, y, 64, 39, -31, -8 ] [1038+x, y, 64, 39, -31, -8 ] [1118+x, y, 64, 31, -31, -8 ] [1196+x, y, 64, 47, -31,-16 ] [1276+x, y, 64, 15, -31, 0 ] [1356+x, y, 64, 31, -31, -8 ] [1436+x, y, 64, 31, -31, -8 ] } /* Spriteset of the 19 possible landslopes with company-coloured grid */ spriteset (cc_frame, "cc_grid.png") { tmpl_groundsprites(1, 1) } spritelayout company_land_layout { ground { /* normal ground sprite - always draw */ sprite: LOAD_TEMP(0) + LOAD_TEMP(1); } childsprite { /* company-coloured border - always draw */ sprite: cc_frame(LOAD_TEMP(0)); always_draw: 1; recolour_mode: RECOLOUR_REMAP; palette: PALETTE_USE_DEFAULT; } childsprite { /* again the normal ground sprite. Thus in non-transparent view * only the normal ground sprite is shown. In transparent view * this acts as sprite which darkens the other two sprites via * a translation to transparency. */ sprite: LOAD_TEMP(0) + LOAD_TEMP(1); } } /* A pseudo-switch which sets the temporary parameters for the sprite layout */ switch (FEAT_OBJECTS, SELF, company_land_terrain_switch, [ /* We store the offset into the spriteset due to the tile slope into the 1st temporary variable * (= storage register 0) */ STORE_TEMP(slope_to_sprite_offset(tile_slope), 0), /* We store the offset to the flat groundsprite we use into the 2nd temporary variable * (= storage register 1) */ STORE_TEMP(GROUNDSPRITE_NORMAL, 1), STORE_TEMP(terrain_type == TILETYPE_DESERT ? GROUNDSPRITE_DESERT : LOAD_TEMP(1), 1), STORE_TEMP(terrain_type == TILETYPE_SNOW ? GROUNDSPRITE_SNOW : LOAD_TEMP(1), 1), ]) { company_land_layout; } /* Pseudo switch for the purchase list branch: we want to display the flat ground tile */ switch (FEAT_OBJECTS, SELF, company_land_purchase_switch, [ STORE_TEMP(0, 0), STORE_TEMP(GROUNDSPRITE_NORMAL, 1), 1 ]) { company_land_layout; } /* Define the object itself */ item(FEAT_OBJECTS, company_land) { property { /* The class allows to sort objects into categories. This is 'infrastructure' */ class: "INFR"; /* If no other NewGRF provides this class before us, we have to name it */ classname: string(STR_NAME_OBJCLASS_INFRASTRUCTURE); /* Name of this particular object */ name: string(STR_NAME_COMPANY_LAND); climates_available: ALL_CLIMATES; size: [1, 1]; build_cost_multiplier: 1; remove_cost_multiplier: 1; introduction_date: date(1,1,1); // available from day 1 end_of_life_date: date(10000,1,1); // available till year 10000 /* Anything can overbuild the object, removing returns the money, we don't want foundations and we want to allow bridges */ object_flags: bitmask(OBJ_FLAG_ANYTHING_REMOVE, OBJ_FLAG_REMOVE_IS_INCOME, OBJ_FLAG_NO_FOUNDATIONS, OBJ_FLAG_ALLOW_BRIDGE); height: 0; // it's only a ground tile num_views: 1; } graphics { purchase: company_land_purchase_switch; tile_check: return 0; additional_text: return string(STR_NAME_COMPANY_LAND); company_land_terrain_switch; } } nml-0.4.4/examples/object/lang/0000755000567200056720000000000012643457623017470 5ustar jenkinsjenkins00000000000000nml-0.4.4/examples/object/lang/english.lng0000644000567200056720000000131412643457545021625 0ustar jenkinsjenkins00000000000000##grflangid 0x01 STR_GRF_NAME :NML Example NewGRF: Object STR_GRF_DESCRIPTION :{ORANGE}NML Example NewGRF: Object{}{BLACK}This NewGRF is intended to provide a coding example for the high-level NewGRF-coding language NML.{}Original graphics by {SILVER}planetmaker, {BLACK}coding by {SILVER}planetmaker.{}{BLACK}This NewGRF defines a tile which can act as company-land replacement. STR_VERSION_22723 :1.2.0 (r22723) STR_NAME_OBJCLASS_INFRASTRUCTURE :Infrastructure STR_NAME_COMPANY_LAND :Company land nml-0.4.4/examples/train/0000755000567200056720000000000012643457623016416 5ustar jenkinsjenkins00000000000000nml-0.4.4/examples/train/example_train.nml0000644000567200056720000003055112643457545021765 0ustar jenkinsjenkins00000000000000/* * This file is aimed to provide an example on how to code a vehicle in NML * In this case a train is coded, other vehicle types work in a similar fashion. * To keep the code readable, not every property or variable is documented in * detail, refer to the vehicle-specific reference in the documentation. * The coded vehicle is quite complex, in order to show the various possibilities. * For a more simple example, refer to the example road vehicle. * * The vehicle coded here is a Dutch EMU, the ICM 'Koploper' * Graphics are by Purno, the original NFO code is written by DJNekkid * As in real life, you can choose between a 3- and 4-part variant, * to be selected via refitting. This adds some complexity, which * provided the needed excuse to implement a lot of callbacks :) * * Apart from this file, you will also need the following * - Graphics, found in icm.png (in the same folder) * - Language files, to be placed in the 'lang' folder. * Currently english.lng and dutch.lng are supplied. */ /* * First, define a grf block. This defines some basic properties of the grf, * which are required for the grf to be valid and loadable. Additionally, * user-configurable parameters are defined here also. */ grf { /* This grf is part of NML, therefore "NML" is chosen as the first three * characters of the GRFID. It is the first real grf defined as part of * NML, therefore the last character is set to 0. Successive grfs will * have 1, 2, etc. there, to make sure each example grf has a unique GRFID. */ grfid: "NML\00"; /* GRF name and description strings are defined in the lang files */ name: string(STR_GRF_NAME); desc: string(STR_GRF_DESC); /* This is the first version, start numbering at 0. */ version: 0; min_compatible_version: 0; /* Define user-configurable parameters */ param { /* There is one parameter, which can be used to alter the colour scheme */ colour_scheme { type: int; name: string(STR_PARAM_COLOUR_SCHEME_NAME); desc: string(STR_PARAM_COLOUR_SCHEME_DESC); /* There are currently three possible values: * - 1cc * - 2cc (default) * - real-world */ min_value: 0; max_value: 2; def_value: 1; names: { 0: string(STR_PARAM_COLOUR_SCHEME_1CC); 1: string(STR_PARAM_COLOUR_SCHEME_2CC); 2: string(STR_PARAM_COLOUR_SCHEME_REAL); }; } } } /* Define a rail type table, * this allows referring to railtypes * irrespective of the grfs loaded. */ railtypetable { RAIL, ELRL, MONO, MGLV, } /* Next: a series of templates for the graphics * Templates allow you to avoid repetitive coding of sprite offsets, * as long as you consistently use the same alignment * Note that layout in png differs slightly from the orignal graphics in the 2cc set */ /* Basic template for 4 vehicle views */ template tmpl_vehicle_basic(x, y) { // parameters x, y: coordinates of top-left corner of first sprite [x, y, 8, 24, -3, -12] //xpos ypos xsize ysize xrel yrel [x + 9, y, 22, 20, -14, -12] [x + 32, y, 32, 16, -16, -12] [x + 65, y, 22, 20, -6, -12] } /* Template for a vehicle with only 4 views (symmetric) */ template tmpl_vehicle_4_views(num) { // parameter num: Index in the graphics file, assuming vertical ordering of vehicles tmpl_vehicle_basic(1, 1 + 32 * num) } /* Template for a vehicle with 8 views (non-symmetric) */ template tmpl_vehicle_8_views(num, reversed) { // parameter num: Index in the graphics file, assuming vertical ordering of vehicles // parameter reversed: Reverse visible orientation of vehicle, if set to 1 tmpl_vehicle_basic(reversed ? 89 : 1, 1 + 32 * num) tmpl_vehicle_basic(reversed ? 1 : 89, 1 + 32 * num) } /* Template for a single vehicle sprite */ template tmpl_vehicle_single(num, xsize, ysize, xrel, yrel) { [1, 1 + 32 * num, xsize, ysize, xrel, yrel] } /* Define the spritesets, these allow referring to these sprites later on */ spriteset (set_icm_front_lighted, "icm.png") { tmpl_vehicle_8_views(0, 0) } spriteset (set_icm_rear_lighted, "icm.png") { tmpl_vehicle_8_views(1, 1) } spriteset (set_icm_front, "icm.png") { tmpl_vehicle_8_views(2, 0) } spriteset (set_icm_rear, "icm.png") { tmpl_vehicle_8_views(3, 1) } spriteset (set_icm_middle, "icm.png") { tmpl_vehicle_4_views(4) } spriteset (set_icm_purchase, "icm.png") { tmpl_vehicle_single(5, 53, 14, -25, -10) } spriteset (set_icm_invisible, "icm.png") { tmpl_vehicle_single(6, 1, 1, 0, 0) } /* --- Graphics callback --- */ /* Only the frontmost vehicle should have its lights on */ switch(FEAT_TRAINS, SELF, sw_icm_graphics_front, position_in_consist) { 0: set_icm_front_lighted; set_icm_front; } /* Only the rearmost vehicle should have red lights */ switch(FEAT_TRAINS, SELF, sw_icm_graphics_rear, position_in_consist_from_end) { 0: set_icm_rear_lighted; set_icm_rear; } /* In the 3-part version, the 3rd car is invisible */ switch(FEAT_TRAINS, SELF, sw_icm_graphics_middle, ((position_in_consist % 4) == 2) && (cargo_subtype == 0)) { 1: set_icm_invisible; set_icm_middle; } /* Choose between front, middle and back parts */ switch(FEAT_TRAINS, SELF, sw_icm_graphics, position_in_consist % 4) { 0: sw_icm_graphics_front; 1 .. 2: sw_icm_graphics_middle; 3: sw_icm_graphics_rear; CB_FAILED; } /* --- Cargo subtype text --- */ switch(FEAT_TRAINS, SELF, sw_icm_cargo_subtype_text, cargo_subtype) { 0: return string(STR_ICM_SUBTYPE_3_PART); 1: return string(STR_ICM_SUBTYPE_4_PART); return CB_RESULT_NO_TEXT; } /* --- Colour mapping callback --- */ switch(FEAT_TRAINS, SELF, sw_icm_colour_mapping, colour_scheme) { /* Emulate 1cc by making the first colour always yellow, this looks much better (and more realistic) */ 0: return palette_2cc(COLOUR_YELLOW, company_colour1); /* Use the default, i.e. 2 company colours */ 1: return base_sprite_2cc + CB_RESULT_COLOUR_MAPPING_ADD_CC; /* Use realistic colours, i.e. yellow + dark blue */ 2: return palette_2cc(COLOUR_YELLOW, COLOUR_DARK_BLUE); CB_FAILED; // should not be reached } /* --- Start/stop callback --- */ switch(FEAT_TRAINS, SELF, sw_icm_start_stop, num_vehs_in_consist) { /* Vehicles may be coupled to a maximum of 4 units (12-16 cars) */ 1 .. 16: return CB_RESULT_NO_TEXT; return string(STR_ICM_CANNOT_START); } /* --- Articulated part callback --- */ switch(FEAT_TRAINS, SELF, sw_icm_articulated_part, extra_callback_info1) { /* Add three articulated parts, for a total of four */ 1 .. 3: return icm; return CB_RESULT_NO_MORE_ARTICULATED_PARTS; } /* --- Wagon attach callback --- */ switch(FEAT_TRAINS, SELF, sw_icm_can_attach_wagon, vehicle_type_id) { /* SELF refers to the wagon here, check that it's an ICM */ icm: return CB_RESULT_ATTACH_ALLOW; return string(STR_ICM_CANNOT_ATTACH_OTHER); } /* --- Shorten vehicle callback --- */ switch(FEAT_TRAINS, SELF, sw_icm_shorten_3_part_vehicle, position_in_consist % 4) { /* In the three part version, shorten the 2nd vehicle to 1/8 and the 3rd to 7/8 * The rear (7/8) part is then made invisisble */ 1: return SHORTEN_TO_1_8; 2: return SHORTEN_TO_7_8; return SHORTEN_TO_8_8; } switch(FEAT_TRAINS, SELF, sw_icm_shorten_vehicle, cargo_subtype) { 0: sw_icm_shorten_3_part_vehicle; return SHORTEN_TO_8_8; // 4-part vehicle needs no shortening } /* Power, weight and TE are all applied to the front vehicle only */ switch(FEAT_TRAINS, SELF, sw_icm_power, cargo_subtype) { 0: return int(1260 / 0.7457); // kW return int(1890 / 0.7457); // kW } switch(FEAT_TRAINS, SELF, sw_icm_weight, cargo_subtype) { 0: return 48 * 3; // 48 ton per wagon return 48 * 4; } switch(FEAT_TRAINS, SELF, sw_icm_te, cargo_subtype) { /* Base TE coefficient = 0.3 * 3 parts: 1/3 of weight on driving wheels * 4 parts: 3/8 of weight on driving wheels */ 0: return int(0.3 * 255 / 3); return int(0.3 * 255 * 3 / 8); } /* Adjust depot view of trains */ traininfo_y_offset = 2; train_width_32_px = 1; /* Increase base cost to provide a greater range for running costs */ basecost { PR_RUNNING_TRAIN_ELECTRIC: 2; } /* Define the actual train */ item(FEAT_TRAINS, icm) { /* Define properties first, make sure to set all of them */ property { name: string(STR_ICM_NAME); climates_available: bitmask(CLIMATE_TEMPERATE, CLIMATE_ARCTIC, CLIMATE_TROPICAL); // not available in toyland introduction_date: date(1983, 1, 1); model_life: VEHICLE_NEVER_EXPIRES; vehicle_life: 30; reliability_decay: 20; refittable_cargo_classes: bitmask(CC_PASSENGERS); non_refittable_cargo_classes: bitmask(); cargo_allow_refit: []; // refitting is done via cargo classes only, no cargoes need explicit enabling/disabling cargo_disallow_refit: []; // refitting is done via cargo classes only, no cargoes need explicit enabling/disabling loading_speed: 6; // It's an intercity train, loading is relatively slow cost_factor: 45; running_cost_factor: 100; // Changed by callback sprite_id: SPRITE_ID_NEW_TRAIN; speed: 141 km/h; // actually 140, but there are rounding errors misc_flags: bitmask(TRAIN_FLAG_2CC, TRAIN_FLAG_MU); refit_cost: 0; // callback flags are not set manually track_type: ELRL; // from rail type table ai_special_flag: AI_FLAG_PASSENGER; power: 1260 kW; // Changed by CB running_cost_base: RUNNING_COST_ELECTRIC; dual_headed: 0; cargo_capacity: 36; // lower than in real world, for gameplay weight: 144 ton; // Total weight, changed by callback ai_engine_rank: 0; // not intended to be used by the ai engine_class: ENGINE_CLASS_ELECTRIC; extra_power_per_wagon: 0 kW; tractive_effort_coefficient: 0.1; // Changed by callback air_drag_coefficient: 0.06; length: 8; /* Overridden by callback to disable for non-powered wagons */ visual_effect_and_powered: visual_effect_and_powered(VISUAL_EFFECT_ELECTRIC, 2, DISABLE_WAGON_POWER); extra_weight_per_wagon: 0 ton; bitmask_vehicle_info: 0; } /* Define graphics and callbacks * Setting all callbacks is not needed, only define what is used */ graphics { default: sw_icm_graphics; purchase: set_icm_purchase; cargo_subtype_text: sw_icm_cargo_subtype_text; additional_text: return string(STR_ICM_ADDITIONAL_TEXT); colour_mapping: sw_icm_colour_mapping; start_stop: sw_icm_start_stop; articulated_part: sw_icm_articulated_part; can_attach_wagon: sw_icm_can_attach_wagon; running_cost_factor: (cargo_subtype == 1) ? 150 : 100; purchase_running_cost_factor: return 100; /* Capacity is per part */ cargo_capacity: return (cargo_subtype == 0) && ((position_in_consist % 4) == 2) ? 0 : 36; /* In the purchase menu, we want to show the capacity for the three-part version, * i.e. divide the capacity of three cars across four */ purchase_cargo_capacity: return 36 * 3 / 4; /* Only the front vehicle has a visual effect */ visual_effect_and_powered: return (position_in_consist % 4 == 0) ? visual_effect_and_powered(VISUAL_EFFECT_ELECTRIC, 2, DISABLE_WAGON_POWER) : visual_effect_and_powered(VISUAL_EFFECT_DISABLE, 0, DISABLE_WAGON_POWER); shorten_vehicle: sw_icm_shorten_vehicle; /* Only the front vehicle has power */ purchase_power: return int(1260 / 0.7457); // kW power: sw_icm_power; /* Only the front vehicle has weight */ purchase_weight: return 144; //tons weight: sw_icm_weight; /* Only the front vehicle has TE */ purchase_tractive_effort_coefficient: return int(0.3 * 255 / 3); // Only 1/3 of the weight is on the driving weels. tractive_effort_coefficient: sw_icm_te; } } /* Define properties valid only in OpenTTD r22713 or later only for those versions. * Earlier versions will choke on those and otherwise disable the NewGRF. */ if (version_openttd(1,2,0,22713) < openttd_version) { item(FEAT_TRAINS, icm) { property { cargo_age_period: 185; // default value } } } nml-0.4.4/examples/train/icm.png0000644000567200056720000000775212643457545017712 0ustar jenkinsjenkins00000000000000‰PNG  IHDRàâ`;;ØsRGB®ÎégAMA± üaPLTEÿîîïïððññòòóóôôõõöö¨¨¨¸¸¸ÈÈÈØØØèèèüüü4I¯=õ#Š0ØÂ‘â™É©¨W% Áó«Ëƒ—Õe%Kó‹:Àÿun+9ÛUY–ç¹ËÀh» ™`•=A)ò©7 Ä9w¦Šý{z2󺪨 ¤ø ,Ëb`kD³®Nå L8À\É|]©,u„6ɲD(醀æ}€šÿÕo²ÂqR¥9Ñdó=fðÃìJÛ,£UÕÓSU•%‡›K–Å~·ÿo¾‘#OO%t½¸2X• ñ²ª¶ÛÝ~W±-x"ÏCŠ>ò¼>]¾«ü]]^Üó@9ãË ÃQvé²üVo.‡aÃìî÷ ˆê¤WÄÞOÖ×5¬‡ü‚åTŸ¸À¡Óéëôõõ·¿"fœ. @ù'Ê1ÀÛï.ʨ4ߨ«pè«€G°‹=[ɱˆ{XÓ•p '1òàÆ®_¶/›Ý–?oüCïÛ÷÷÷Üm"”=©жÙõsÈt‰ÞÇ®×›í¦1/mï¶ïhž¾’]òFMcøh 6“üŠsu Œ2|}Ù½¬÷;øà‘ýî >pèm÷OðøªCü;?"Àvòú‡ö¿tx›ü¯_×ëݚͷ¶Ù¼¨z€‹*Mzg3¹âÀéYMïcK?k*›õËÛÛ ü@‘!Ò-Ä<Ϊ.À*{nÃFv ”õ üèÖÙ<³Ý~ñ;j¶9Ÿå¤çohÔжôU?s+_½™ÿ>@œZêîï[f#ŸFÜ,ÕpòÄ2µ#coà&öãÞžNß\8{ˆg>AÊàÌžÝ|Êàì!žù? ƒÍÙ±(¶ÖVÕ®vÏ6Ïd¬wcØ¥™ãðòƒ=oZjÏu;$ €!ÔÊúzT8Àoñƒ&B ?Ø >ŽTOÕ´òƒMÝÑ÷‘Ý^??¨Ów~P§GØEÀ†wtðƒêd7ä öŽÿ§ÃbïBÔ)6ƒ’qÂGð.~Pž.:~x<Êsòƒ2‰œ‡\Á×'bååØžEM~¯ÎòƒjŒâëñƒ-J~PNc#ªë•šá †i?˜ÛøA-lcùAùå…@„Úõ øÁž1ñƒD«£ÏxÅÔЃòƒ à£òƒrÍ8!?ø)ùÁœùÁÏ{óƒz?Ø›Å:<Þ§üLü`7rÑóƒŸHŸ}&~0ðÂZ}Fú,ñƒ¡ÉU?ñƒ£ÂQ£ðd;¢hÏáJÊàQ½¥Í”Á[F{Žsý€ ªE»°óYŽÃmeg;ÉNl74Ë­~pbG„ú6ÿÄvÇd~pБý «ÒصòƒêTŽv#J~p8Òný l× dõƒ»šÿÃúÁÉ*~Ð0ÈJ1h§¤v}ý ÄoówUúAáÓR»)õƒº#š~PøôƒÐΦ„À°d¨8¥„ ë¡Ý´úAÍ]?(|úADbÑÒa})»ý ðéÅÔúÁ ©8¹Ëß•°ë _'ØnO?(|úA1µ~PŽ¥.?(|úAa×vöôƒÂ§€Óê `_?(|úAa×@Î;Ùíëà ~NªGlúAáÓ »~P(ý صéÁÿaý œR?H׃¾~ë….‚£´?h$§É\À!ý ˜Z?ŽÚôƒÂ§vý \X?ˆ´èiˆ éÅÔúAÌ„…d€úA`Ó ¥”»úAáÓ²Ý õƒÐÆ Ÿ~&‘¾~.¬4¶Ê6áÓjvy­>“^ °«„ó ëåõÓÐâDý òƒ:@MÙF××!ý nwý Øå…·¨[CS?(”~»šE?è5Û,<¦ÒÂdaÓ~ßÙQA ‰·õƒrŒâÔoI‚í^­TŽtôƒ!Ž0DÖbI?ˆ™°èÃír€Âï•~Pê®~0ÌíI†Ðò4$ÔîõúAÿÃj(ý`X+oí+2èµVAéÃZykGPê½.‡U¨ôƒaçñ×nGú«†Ôb=‚º `'«KY q!e0e0¤¿Ü¡nê¢?¯‹&~ÐNŒ6G;äJ/¥>sýÝcÖEç…A¥ s¤¥ö®8¨tØð[ü o]?8à°~p2€~~pX?HŽôõƒÈÒÃÞŽ£ ïèÓR»›ðƒý 8bÓºøAÞ@ Ÿ|;øA¥$»Sî/ªEz ?ØÕ:ùAù?ñƒóðƒ}ý`<ü Ïs᳨A5KVVÓÆÂ^¯d~°§Œ†¼Z?(¿LÐáñÊxøÁkõƒÈZôƒñðƒ×ê™äÇãòƒrIe×>?¨g0ñƒ½ç0íQ•‰䀸ŸWéwhSì/šøẢ ˜¬¾Øn(ìq't¶Bf÷Øl8À‡çCC²°úÁ7 ÃûÉÀj»ÝíwEJ¯¶”%oK)Ók2L êØî·È•%íK ·~Y@ß.‘¶Ý)зK¤mgÊeäg2ŠC:Õ'Y¾¾€Vú‚d}viiw‰´±KËèÙ%ÒÆ.-àÐ.‘6via‡w‰´±K‹èÛ%’¶·¸[@ß.‘¶Ý)—гK¤uwÊùÉSaA¹9·xZ¥ Æ“‹qž¤ Ž‹[<­RãÉÅ8ORÇÅ-žV)ƒñäbœ')ƒãâO«”Áxr1ΓÁ‡ÛPKýRZ=xþ.i£€¥ôD§Ÿ©‹.=…)ƒ)ƒ‘G uÑÈäu/eТÈ+¤ Fž ¯{)ƒÞE^!e0òyÝKô†(ò )ƒ‘'Èë^Ê 7D‘WHŒ ªâ+ÂúWT­ X–‰*#¥JáèVq„TIiªÈ,eðÊ~~²±×õ`AÇézšõ›üfŒ+8h”WXІą2üž¢â8¹ÄI¬5þç¯ô2ƒü÷xXZ›Jk(V‚eÎÉ%Ñg‹KþŠùQ³ [ ¿V«²³Æï9GQå«ßD‘}5?¹6†¯€…wC¥Ü]–_XÌHË\ÖÚKÒ.?°X«d\k~‡æ·¢¦Íßðõµ°*@V¨@¨5`S¡-.3²Ó®R= ëX¨²Ó*IÚËoµH3¢ÐÉÆ‹|(½ ŒÞ뿃ý{ªì«:¾VWãE> Ò` ¾ûÚaµÚE¾ s›g`¥$¡óU2¯ßâ·A?”¤ÚŽø/”ô±§˜i¡€£Â_PÒ…9ýfF”GX ôÇ4µ?ÒºZ'ÀÀ›?Ÿk\yßw¥0ŸÎw ösÚ÷Uyê|>¥î¥‚Ùxè$.Ib*ÅZ¡;Æ=íÒIýVæj*æ™ë–²•רP^O«0zIwYnBGUˆÊ$è³LkVÅ¡Õ.Ô‚^d8ÇÏt ±N«P[[­‚ÛÐe/°”(>kÍrÓQœZí’¨ëMd0æJïÊBó mx‰e«U'qû-ä¢ÏBOÅ> ó,µfƒúõ«Õ®ÖwÅcÚuGiUÜË«jT¤S£ùÛn`ØA‘¡tœ|ÖŠŒ&€;ö}×¹‰ Óíš”Eÿkžû¾ê(Ò}O‘`*Ò6Ïr/þnŠ„âãÆw]’K«]ê* œ…7¤Z­ÂÏ7¾êxÇ!ï'Àú¬jóYª§]#ÃF»0ŒOgº3+hó*ËžV©õ¦Æ•¦iL•˜¦a+øŸ‚fÇRB‚"^H1•£œîŽ(JÓ»<«„ ÀbõèE†½¼«œé–rPì"@¡Ös4È)Pñ ´²Sb¢~µ?R™Y똲ðYQ¨Y˜gú¬U*sÚ·ÚåRÐk´ªï«fêŒ/(è9T„éG¢"à<+QÀF`ÑDúñ|h—c´ eÀšÕë|–KMØQÔ&ò¬Û¹Ðs£]¨(K&¡rÝhÕÙ÷ÔØ“P4f§Æš)2šÓõ”Ù’´yV‚EåÔ´Ö®…=\kÅâ°Xd¢'\@§™b(U±)XB¡~<ÐÃ1‰YøêÐÝeJ̽žvØ, Wöð1Mýô°×<‹§ÄìÔ  ÐcÑAT ›ƒµ÷LöÖZ`Z•Š#æ\)¦Ó«ƒÂ"Ï`X¡‡·ë¿píæ °¬pXV¸¬pXV¸¬py†Ê¡ÜDXVèáwƒ5µŒìYŠø³†à}`áݤ~îR)YíýüÊ;‡Ê<ѪN)È_= JB´=X"¥õÝÏâm§Ù÷ÕÖoQÚ@8nR#O¤ø)P·¢mÁÂ{«€6Aõ ,Æh#d%©Ûczë ªåÛÇó!° v¿U´žé oÉmà ZL®µAZ,oS†èïIzí´‘Ù7 «K’ë„4 ú«õFEñ)¢å#Ï2S‹`á ;]kËÑü€—ÂŒ(øúBŒƒÎ|}˜&WU¨ó9‚3^¦:Ÿ»…βh»¹+'9JÖ0M»nÈÿcïÒ’ÌØ…O¨j˜,ŠL TÞ%ì)(Ø÷]d8"J€Ò@´èü ü49.UÌǯӬJ”U½z-¸ÊJK2ßÖÊ„5LCõ¬ý¨(¤Á‡ž”C Óä1¤Î'€Š7>*ɇ, ©x QÜR®$GÃ|fSˆ±JmÆg¥PT†6`˜ª„ªU-GY‚#‹ÚJkË«¨wRÒ(hoCC «}Xnà YÕÓ(°KòikúM¨0& RÍÕšÂï/T.˜‹ _kà³Ô2Í‚âÄ&ðGæ AÖ0Mö-r Ð"R¨­®4 }˜”¿úŸ£ÏÂÑ¢©CÓwâÔmIRL‘FiMƒ¥±r©ûQáË`¥è¯*öY€>K,ñY6ø"eÒ1Ä5LS¿/Í@;U5 £ÄvîP£h÷ji¢…YШ!BSx>c[(*ÄñmÂg¡ÑBŸ…vРQ*+÷‚©ƒ´ñÆ’5̺q±–V0}êèBÿ#b C÷ž¢Á`\Õaðó°)ü2g2ª6†—”l"ϪšÁ„ò,ÑÛ”4'+¥ˆÝº+ü,kÖ0Òñx1ê¤j`ð@R\û(TR³$„»½´cˆMaŽÙ4Åð¤\ `uX¥?coNe Öæ²†‰B=]f¤¨còt—y}?)PwEaÄEG"-:þô¬­ã2Ô«r¡ŸgÊ­[è€ ™J(e C[±ïã€~‹5j` a*†]€Æ¯‚ ÚÁ^Ü>×-dlÑÓÇKžX¨Àá`0ØG×E…Qaž­Š• ¢UaaÐTF†6´»ÇÛT·Ù7Åk²†M±Ã™bA  5…yBQ!%_Ôi”Ö y–»•àÈ‚]òX[ CËábÒ÷3­ß˜ÂªŽ Ì7™B1Vˆa¹ÉæYÎ÷B§ÂK›Rå’†¡³Š\2š­Û£0êrÞh#ÊõÂ5Eµd¦8=¸‰ánµs·‹,I»H£Îä—ër†¤’æzfP-‰ è̪ŒYQ\6E‹»Ó>û]˧—£Feè°¼š/,7-0é€9ä `.“¸N„ùè¸Ã[¢”K·Q!OrÍ O?7´n0B`‹ E6b?ÍMÐíx­9ðAµ\ΠÓÅ.T5dS85­°!XÒÖØq˜QHFˈRwQ»ÛPâX´“üd yžŒëðƒŒk«+r9ž49ÇV¸,RØ÷.àoFÑ 2I·äöÍ ¯sI.hÖ0˜;ò `¡çRpb:SP(‘Vä9üÃ{It=3reºß¦\6*ä äK¢½çYz/¶n Ž“îŸn³b­“?(7ýñ½PÉõŒ·7€Å‡&³)ü“BîŸß‹xÙRð-À:L£¹X‡|¬{·»X‡Ï‚u°¬ëca…kãƒ%l¥f…°†Wa c 0Ea`;=lÛC-3Å«Eù…»…êцÖþwJ²桉€øm1- ûêiQOÁ’À#{“¡Ò*‚â!L‡WXF züƒ ZÞIì¹ñ'UÛzC_vUš™e€ 2k¦âÆñHÆÖR£YAΈ¯ÄÿÑÔ“uîQÒÚHÒH@ug“¢ê¾2dÆõ¬Bàp"åPì¡~5.Ò±²(FW)k)ðÇØ›ˆ’œ؃ýØÔóP%­X’Ñà×¥Õ§ºa\9àæ¦r€i+=“¢ *QÖÙ+ @Nù€ü †Úä¯Yú›„ò95æqÊãÔØu_)“ºŠò5Sìqz×㤳Çé]“ÎW}el&ì Êã‚ '<.çð¸pÂãrŽ|ÐW]Eã…«›<.Iò¸PÊã’$ ¥}å*Êó"O‹ý<.Aô¸ØÏãÄ|ØW.¢|¯È}ûe´[µ¸¯ÂZ÷‰ °ÞVØõ´£ýY¡ ¬pXV¸¬pXV¸¬pù…õF ~€`XΰÂpè ð7Ýü8u,@Ë;ÇPXÁ‚ß_¸ZñèLqë˜Ö9ãK ¥:Òk€å–Œ‡«chí»(‡g”;ªøŠðŸþU–X&ªŒD^es1r;ÅRš*2 a¼²ŸŸlìõ)X·5™88þ½aÉDfŒ+´à™e™BaQe2ÉÀŠcz]¬uLOy§Wú™Aþ{ü0,P·Ë/èiËÐcÌe^ã’¤¿ dlQñ/#¬ÎòÕo¢È¾šŸ\CGXô4×ÛÓÇEJOÎû X–‘ÌíŠù±» ª$Îks€5mþ†¯‹aim*zjÚ­aÔº27OÎ{WXô$ Ëè’ô´KöµŠÞÒ#f`!¥Ó͵JáH¯üLûw°O•}UÇE°è äP>‡«zÚ+Ø€…1ößµv]kŽêRÝßᤀ#­¥+éý‘¶™•øƒŽ AIæ\ô›¾ó™…ea@Bn>­€œ!Tañ~ÑšX\¤HÈßf2ï´ŠŸ³Ncü äÝéÿþ ÚìÖiù*kóV«Ü´ò K>W†l'DèàvóNÿ åí€zëж˜7ÚE|jcØhÕíþ‚Á*AT`Ý©©è¨†ÛQ”•!3h*è¶Ñ¾yR|²>A>l Q«E%,®`ø‚…þ¨i Œ±†`6Xôi«ìž(ÿÞ°h _l­1Jº·ƶ'¼–åÀgl¤èÿ^}‚fqŵ©V°JE·×yœV›"¡§¢W÷æY·Û²R°O(?ŸéYåâ|†Tu„ž7yUÖÚU¦ëÀ*ЙFµORp[Á I4hMEY½9¬“‚²­ʼV0Ë(Çð½Á5Ü®:* (Ž (X¡˜$=g­Ç"e#(€ôHG¡ÌÑšALÛ<«cU( Aâ„ò,ÑŬo›ËÆöòª¢Ž ©ò¦ÏÁ¢ÓE¨b›¢›³óX¢~µ?ÒÓܵ¦™˜£k€1ôY7s,¥ÍÃÞ× ZT—¶ÆD¢—wµÚå`]ʸô#g3˜rí¯É³nÃAý<«P`V%—V«(é¢Ã–ºªj—£Ïr)ä.ª``h”gú¬Û FŠÆÍ$Z€.nÿù¬¼Ó*ô‡Ó ª‘ËÑ ¦Y+;ŸÕÌkÑ4–®ç¹–ÏgEÀyV4¶ù‡T‰ öú8û'ÅF…N¥lÒ*à0€q-)žšÕ¿2€ÎUw—<ëÃÖ`Ør-ô“5õ@×Y.óX ç³(Ϻ­ RvEyÖ®n*®ÓßÉd³²xË–°¨\èeñ‹Ñ¡¼= %W5SüG‹<ц©8bÎEÇuõƒõ×`¨±0Äè’™ký¹·ŒôW7…åÓV€åÖ›¦+À °Â`}L€Ñí¹ýÈø®’1 4Üÿïõ¾ŸáþŸþ'¦þÏî•~ß}¢¿xzÙ­Sùê `õwŽ\ÄΚ,ƒ5·ÿ§û„ë K÷‰þâé%·n—cïV=ù{ø½ízüa½¼ÚÖüþŸþ'ÜfXúŸx\³ö‡ÌhUv¦:ÞÙ »…ûVŸ³ûÚ5ü’¾ñj_ã֨ŷŸÐcŸ8LÂÂrïúåŸØ(¬zçÈ/Moi~Û}Ä.#¤©­ßzš–ÛþŸ]œaú¬fÅíÖ—b“°­" •îw¤ˆ$•“÷´«û„ëþŸÛfÍͰtŸèOê»Ýz9önaµ¾ª]áD“}XͼqÜjטïêI3/rÿu¬YSŸÑ^cµq\ø‰ÍÁêûªÕX¸Pãºç»^}#«YÖtÐ}­ƒÕi—nÈÉ5a}œÏjµ*jÕDŠÛjòÁõÚé/žvôY½åØ»ƒÕF€‡ÞºÁ»°º\‡‘Èp%3¹›Áh¯f0žðU÷ŠG£¾+^ Ö§äYmxã«æ`õ}×02 >ëO`ÍiÕXw´ëÕ>kqž¥w˜gõ´JŽkÕ¬n_»‚ô~ÒA«f`Mä]¯†eÃX`À²duXs¾ÊÖ­ïZÇg-«ºïÎgÍk•¬íZ'Ïzd>kgyjÃ]­r…Ukjéz¡ûÌgÝ×*GX­v½á¢m%Å÷´j ¬NLè\ï°BXá °¬pXá °¬pXá °¬pXá °¬pXá °¬pXá °¬pmÖ6ÚÚúñ_V€`XV€õ*Qòýawq_Vöó+ÿ–ü5ÑbQF~¬ìûê!Å3Ý¢ D8[Ú,U}Uß#O‘¿' Ì×·‘oKfQ–éC¾ ‡õAÿ¸[Ò¬TvÐKD婾@¾5¬Yi¾E—'€‚•¹”Ú˜k³z 4!•£ _=yc²Uõz|ãNk°²ˆXaÏó»¹niùà¨'bNÀ\š5Ågpt*6' ¾¾€>…·a¾¾Ì»Â’Ù7^嚌!Z7=K÷, v©D ˜6Â]iÔáÐ5u¦Ó@”¢e„Œ×î`1¡¼U0íÔÃ7ÀHÁî»×¬($u ÊM”Ö¤\,ïìj ÷+‹¾ÙÊ+˜vW‡0ͦó)`S¢®4jhúÆ÷•MÂ"jMá÷—‘o # ÈØ÷X¸ÔÑ\kû°ÜþÀIÔÀGfAÍ´Š4Š?‡´"åÚ,"ôCãÙ*˜~(„»Ñ0)õ?gQ¨×ó¦Ïk&{ÆÍÓÚ,Ö&²V0ýxrt la€‘»‚š…EeΤ§6†—oKÖ…5?zy7ì[ Ë”‹'5]Tø5£\»eÝÝKžE™^®Àà¡ÐÜ@9ˆbZthÆYÏ:®½À²&0§(0š`µÏª»¶åd ÷Kf` I]ǃï4ŸÅ՚ ä®aÙ¢E gÉÛžëLᙓ/¹gXÙ7ÂV{†EUG…?æ{ÊnV\;)LoXÁÞsZ¿.gÜ 7K¶9UÎ ¶`iyÅÅG©Õå ¹?X.Qà{ÀbÊîF…Û¾•8³E‹ƒ”ãE‹÷eçJºZá€Ü,É%Àü^"üF°šr/¢ª!¬ûà‡…·b‹º°zXD‹La|í–-R‘›lWssO±FѸ¤Jé–ܬȺ+2-,™î–Õ¨³5…ðíÇ jþ3QcWXužTJï+t×r>b/XMV¬õþò,WøF°jS¸Ër“3«7‚51ͶÃ[yXoµ|:À °Þ–ϯ°¬ëO`ý®ÝŒ•+À ×=Xc·ß±`­ «}´ó*z„Y€µ)Xò ¯žçÜ¡ÊäõŽíkEX¼Læ5.ÙšE‹ŠiœB÷б «y@p£]–VƒŠwÀñÛk šeŸ‰´KöµêöqŽÖj°ºÇo7Úu­U×SjÖšÑ`÷€`‰|ðmÆÛJ§ž`­ «Õ.òUÖ.æãZ`m¡‚aqq,!Û·³çXëÀÒôok s‹KëEU÷б¯‚[«GŒÝ[`mVÜT+X¥"ŽÛeÓXÛÕäUy“Wµ‘á„v…rÓj°Ú Ìk³Œè¾q\Ö:°dcó~µ¢«jäM*°Ö׬F«²—wyW«]A³6a¯´ê¶ªÚÌàf¢A™–ZíÊe06ºßVÖ;í 3Å›KŠG Kzö¸¥k•rSXÝ`…+À °¬+\›º À W€`…+À W€`…+À W€`…+À W€`…+À W€`…kÃ×ÿ×E±PcmŒIEND®B`‚nml-0.4.4/examples/railtype/gfx/tunnel_track.png0000644000567200056720000000315012643457545023117 0ustar jenkinsjenkins00000000000000‰PNG  IHDR–dS…9ÂsRGB®ÎéPLTEÿîîïïððññòòóóôôõõöö¨¨¨¸¸¸ÈÈÈØØØèèèüüü4 ÅXåb¡. Q …a5Õ£AÔdAXA…ëV РËÀjœj•u#a ¬Æ¢Ç¨X½b¿Œ%°#)†³b΃Ža kmL¬¤XŸcºÓX!„£7Q€t.‚ùeŽq ‹ îUu8Mn䬘Uñ½†Ûq¬ª±'ªG$ÅTëUù¼[lcTó (ïOµI1¬ÛT•``Õuu%åíÉ^c¨-@R,åXýL±±4ˆPãÕW÷ýt 4B.sÌMUi¾)öc¬þ§”òÞÚªÉЙ¡£²Œ`­j“b$~#–&a#”³J){2å—‘gŪrRŒš…Ùˆe¨F¨ ¬Çáôf3Í1ºäEU.úX·« ˜”¢‰1ÞI€K®9¦×9{#VR*引"ãÔJ1¹RìÍX+¥(¼˜`G¦>h7)F¿#É;)åþÆ£åxTlg¬¨T_â’ëÒÑC„}Þ¥7bÑ¿^>€œ}t3ò`,i ¿¾2VAXŸEŒÅXŒÅXŒÅXŒÅXŒÅXañ+cýGX‹Ã- IJ°nŒ÷ªå`iÐ Ò:‚F¬6Юù}¬A)¬£RèqܫŠ9°äMw³RÃÆz\öŠå’qV½áš¾ËôþíœS¸ØÕ¯V½{°š;¾ÛŽ%Ì2§|„rN®÷Ï{°ªªj^mÅên £éÓ{Š ¥„ùâ¶íÁºÚÁÀøl#Vï)&¥|r5œƒïŸ2ìÁj'¿:„ën,©úÚ“R≴+ûºvÊ4<ýe]š÷“bÝ×àÖN™9?0Ue—Šq_[ÕN9%¡ƒs¬É澎Jé<ãÕfs_©úD¦Ïu†ÜÊæ¾n|ÝÄî+»¯ì¾ò{"c±ûÊXŒÅXŒÅXŒÅXŒÅXŒõ?.3@lìŠñ…IEND®B`‚nml-0.4.4/examples/railtype/gfx/depot_normal.png0000644000567200056720000000630012643457545023111 0ustar jenkinsjenkins00000000000000‰PNG  IHDR⃧f7sRGB®ÎéPLTEÿîîïïððññòòóóôôõõöö¨¨¨¸¸¸ÈÈÈØØØèèèüüü4‚(€ðÕboÌD&h¢‡!Úãëû¥.v„`l°(ãXE„DáM3I/ ÐÝ,D¼!QFheOuò¼‹Ø÷ŽU G**:jQ±éæ!’‚=™hG„ ?Ï@Ä;’ZÌÐ_Vò¼‹]#*ºšvô·ʨÔQÍ3TQzåsá ?ÖñbƒòùAIh,øýE%K]$€¦uϘ‚nP ¢R8‘IÂ1 çN_‰(€ÝãN²ÜE@Pà‹íørK¼Q h¢ÜabÄ£;2s äÚSS]T¿ÚöôLý,XO€™‚ž2qúÜV§Ä1â!Ic%J‚ObÜ EÈÕg7Ñ %*È=$4TÀç1g²ÁNߓ’2X@y¹2br¹ $oP˜àÁ‡î…ÜV§$(1lŸ^’¹²ss]Q}ð]ÈxDƒ‘÷ao@ïüEDþHG#ã3|GÚ §)dEÄd$8D@Ô4°ÅñO©Û;1RŸ”¤8ÃÆ}ÛØƒ­Ž•\ Q}Ã`¢(^ؑƓ',6È¿fKì’3íuH!hÝHÉ•£†F“‰²K±i2 XG?Èd$_’PÔ¬DÌpä“« ¦(Š_}Ÿ¢¨tŠ\EÄW4Ô}’º""sPÌâ;$oð‚%2‹’òws]1®&d9€¾íÐB}Ìü^'£Î#ß{™éu¬}2„l&@?÷>åIh*®B–G$Ù²+ÍE£ŠE%¦rB!9;Ñû¬AÑf0PrB6x?Š®É'ó«[±9¹¤sìp¬…‰©w†Õ£jÏT ¯“^1{ŸôËíÕɵ<âé_h¢‘‘jW8<Žú¼ö`UkìYƒ¸‘’+ÉaŠÇk0X]µ'×òˆ¸¦¤/þýóâoè‡Ü@3 )© `¾>•Ë´ 2f}º—Šb®©Ù£¡lÅY‹]úZQvxR÷¨#/b åPBŠ‹ ôí\EÓæ"ß8xqlF]¡•5di=ïJ .€H†ÕËK¯ÉV± A’aLö<€4øEDCB ä0?õCr ¡ÁJÇ£! ®½1‘°ÕÇËÂÍÏk2)ÎŒqÁ€­ùºeJ="ÂV÷2›£ò\€ó,¦V¨ãë[‹Û̘«ªÑd\h­êx W8´° ùXŸÝá@*–ÂMK*¾¼˜ÙÇ¥ Ïüdf‡~èÚ×7°ãØT×1ÔaбM7ሑM£Ä>Ä-öÁÃ?îèPE³+ ~ÿŽ{Ôæ`ú—è“CZ¥Üȸª5ÆK;2·Y/ªÖ7 EV`¤ «XgEíxp Õì¿ õéé; öƼ2ŸŒó™5yŽ¥ÆÁÏ9àú«þ¿NNä¼ ó²rÍ|.0T÷ømWBlÍ“!Cƒ5ƒOòZ)Í]‘ÌÓÿ}ëÇ6Š“¤¦˜óDŸû 𑡺 ÚrD5b¨#ŸìSÊ'B´coõdŠçšuôy¢4‹†z$ÄB¸Q¼ô õÔ'Ó¶¶†vÚÞëᛢ]tfÌ|Њ¢¥_Ý÷m‡]Mé‡ó#ûäXÉŽç %ÀÛ=_”¸ÚåyÐEt¯G÷"´“«~©èՎƈOj›ò$NÞðªâ¸:òÁ˜,ìÛëÐé]¸”CeÔ‰„nŠð†O‰•!F•çAñÁ‘“ *ž/ÄP“O*"tíý„óœŽWìƒ.úàåLÒ™O ×4‘1[PŠ-û ùàUO¦¢O¶Ñ' ·è¶¡"OÌ1ѧd‘ûà•PÇ>‰ÓWYnAE‚6†ÅÝU™ú¼¡Á'q¶oÌvTô:ªXòÁ'62Ÿq°6¡"®³ÈsJ>8ëPÊ'QE·µ×b¨®äƒ3ÏݰO`4ÔͨȆ: 8ãhù$*îFm@Ň¯q;À^œuzJÅ=¯ÁUÏÀ)B¼ÔŸ™ÄÒ˜™ ®|ÌO¿ÓŸÙgàòÖ'¿ôU+bE¬ˆ±"Þ4õ×"†Šø5·Pİ*â6ŠVDÜJÃjˆÛ)bX qKE « n«ˆaÄ­1,ŽX‹jC-b¨E µˆá3ˆµˆ¡1œ4¸Õ"¥jÃÕE þú10äùùþ/YÄàÞ+bïÿ­ŠÊçûß+bp7(bгŠÜtCù|ÿÝŠL,b0³‹ìTCù|ÿ½‹ØZç1X[ðEc¦Î÷_(bXÓPGá ÂÍŒ"ûôT¬Ó˜8ß_.b°xb‚Üš8WPQ,¥Ô\’b®-b°O?~#êÄùþbƒ%@²SKag%9¤ràÑW1ØåÊ·tn±p¾ÿ¼ˆÁÄš0±Ô5‰Š³†&M¯-b(ÛEÔ©óýã"c‡tá$/®`¨*¢ñ‹‘ÀsMƒÿÏg†Í…óýã"‹¥è»+6¸ÀbJi–NÔZŒöÝ";YÄ0}¾?/b ËÄHjÝDƒ‹,‰•Žœø¢?[Ä0}¾?1D8>ðjÓɸµ66”Êô³E gçûÛ¬ˆÌ“s+ 8ÝàbÛSŠ£ŽNµŒK1œúdVÄ ÿIÙâ¥ÜdTZYÍ%ŠNÎ÷·YCŒ¡îôtêº[ÅŠ…dk]¦ˆa|¾?+b°)KØÛÖ/ª8`g4‹1 >™18Iöï7¸øc2W >š¿/QÄ >©Íp6œ-ô^õ‹19ê¨â§‹Ø'õÒ¢$|{ÏúŨ¢Y¬ˆ}R?>"¤™Vð¦õ‹¹¡.UÄ wRáî_¿ÈfÑ"DÜmªˆAÑ´uÑ"Aü½‹v›+bPµˆáÎWE¬ˆ±"VÄŠX+bE¬ˆ±"VÄŠX+bEÜîõÏÚg˵ÄIEND®B`‚nml-0.4.4/examples/railtype/gfx/lc_left.png0000644000567200056720000000737212643457545022050 0ustar jenkinsjenkins00000000000000‰PNG  IHDR·aÆþsRGB®ÎéPLTEÿîîïïððññòòóóôôõõöö¨¨¨¸¸¸ÈÈÈØØØèèèüüü4^c >x. ‹M­£õúcuù×Q¼YŒi~½‚s ÿÅ‹íf½~ùõ×Ë}—¿~½¬×›í"þo$È$E6ƒ!+úZqüS|+ú åþS‘|׈ä4KûRqößþývxiƒ¼Ê?Üj’u1îöî–βFl ×êX_ï«_¾ƒTûò/®Ub¦I¹ÏaÌ¥ª_î£m\o£}MRþå73H¢œAN³œF†@®o¿Üï÷‹ET-åo$€LÒã8âEô>dE‹øpxˆD ’2ÀaL9\-¶ÍI½üm´ß;‰$d€ãîƒábÓu¸KÂ5dˆãÞ['6%Ènö õ£Ý0Ç}aÈîïݼA¶‹ÅöúT>ÀñÈ=’Ë+*AŽéÜ+d-6p| Ò'YÝ,¢®BŠ"=Îd[>V¼½gµâi’ÜÞã*\¶ ´(Š9Oê?ŠúÝú=«õÇ0Ȥõ×ÍeVżŸCnÕñýë0‡ äm´ú¾{K5©'æ8óUV³:öñA bê§÷u]%êUÖŠçSÞÕ·÷¬¶ïn|üýÞÃí=®²ÚF,{@Ê\«ã2ÇG:Ë‘ÛC\‘ŒzY%O¼ý~oöûE´øaôsˆùQ¾@ù2# “ß©o·|{{ðK¶Ûxô“új¹š1Híèf9Óæ,ˆ$(‘3 A‰ô„΀|xÝ\“Ü3:Ï äýŸÔÚ¿rl ü å:ÍýåÄLâ£øÚèϸûê¡k:ÄÌ D}®ëüö@7¦i~, ®žÆ\{VêýÙ¡ýËif‹[?,7˜öD7H“ÞÜÞ{Ö8Qž« r&¯7a…˜·ÿw:1¿ué\öKën݆¦éd»8ÛîšÎô'õNõO³˜ª›ÝêŽéýj’ iÝ·ýk£»u› Æ5Ƚ?ñ2鹚oçu/SDs"Óé…/öàÔ Ýÿ¦ÄoI®AŒ/þbbÖÿ§Ç7iG çé§éü‰}1):Osbo¿êDøŸÄü‰Îå ¤±Ö=÷î¤þoäeÌÝeÄ$o‰µþÿçƒôؽò»ì½½ÅÞ~"˜èmÄ~gÚkß1øï/ÖºoV#oÝÏø>joâ¾÷8¨Åï}»ñríE ŸãnâBlCâI4Ð]>»a¤'IÂ#NÂA^siÏëV]_«êó®’C i×ËfÒf¥BÍ9Ä=7Òë+¿V·Ï» ¹|vCØõòˆTجDZ!ŠsH{^·êìöZ)†,#ízÙ,ãzÈÒœÃÃe4·‰¸Y+a×åÆ1ˆæîAä×J3‡ ]wD*žÔà¢j•x$•ß‹ò#’Ô8o•û9D×*qáú¨“p}¢»ÝYªºÝX!;áúDw/J{¾s_!åúý²gBhR¶I¶>Q݋Ҟ—_/{¬Ð´ª¼ƒ¬áúD~Žën»·p]!—õ»t$]Š/ïR±ìM\·ê2K Ï!}úT´êXùV. +Ç­ºî!½{®{^Þ‰i€ ©xô•¦zŠ–Y®ï’sõý$@Äy…TOÑÒâ$ tÈJœƒ$òç?2Ï9DÞó²U~*ä)YrÙÇaé¡Bd=/[åiY²Jœ´PÞó²U>*diVËe` Ëz÷ QÏËVù¨ã|pÐ4ké¥B–Ò‡Oç—jÆÿêÄ: AåË@!€B˜Ô!€@!€B„„ @ @Èã ½X?*ìgG¹nlg•Ÿã˜J>߯>¦¿Vn¶—ð³£\/–³*ÎQN]Ò:$I'¿V½nÛ?*¬ÙQî§p¯Ó©W¸–³Êw”KËV +äz¿¾U‡õã Õ9Æ€TŸŒ· YŠå~Š·ñJ“NíZÎ*ÿLÿÑü,Ra…o‡ì’ôÓScÔeì%ó³£ÜílþÈûãÄç¨>§.©î¬ÚãïÝ#?bQ¹£\ûËÛOâc¿¬ª^“]»eÓ·J8dÕ÷ëeîØ™Ç@3rö|¬BÜïÔ¹í5”­’NêI¯tÝô<Ì ¹lŸô>—¸¨§d%Y™$ü”ÞÏV!¦9—¸¨¤¿œ³TÈIôc¬'©Ûî>+ä6—¸ªÕr)¸ãëŒß 9l»ø®[•¸¨¥‘Ì õN^+$Mìû:øÙQ®3Z÷>Q¶êöĽ{ä×;9È@Fõ¼¾UBØ×¡â`ï“æë7–sÃG,e #ZÕ\Ï„âbï“Ö væ¡'o=o­øÃq± †¹7QMuŽ/uÔ­j?Ïã›$w “4 Þb|® ÖïoxI×Õ £é?ªò[ë–K @ €@ €@!€B„B„  DrI&ü©âª]Ò„GdÂct­’ö<žDy­òAr/ Òžç€ÌDX¸E!>‰xS@ÅUYŠk¥ï¹è¤¤p?dyɽ€8²ù ™ëÑZ1’*†¬ —½^@2Íåu>©gA‚d>–½¹f+=BxÇkÎácÙ›ûñQ!¹pôÕÝ‹îA2Ë^/’‰Aò ‡,E«Š" nw9ˆæ ת–E Hnœƒ”'ÈÅë˃¸®[yϽ9d™ÜH$ˆ $ Ä8);þô??¹¯òòÊz^¶*ó’›ç88øÙùÎv—[QÖó²U¹— ‰£(0(Š=€äž—­òS!Q€sHä¥B"ṯól#‹ßK«ÎÄYTÛÄrÙ@“: @ €@ €@!€B„B„ÈlAîÆ~ˆü$4Ë„pŽv²ÌKÏyìˆþà Z»~§ËC<$‡Š®ƒ[´’Q xžß½öý‰#9B.‡`ý½Â¥q5¨trÆÉ2ì=ÖN­ðç'1ÝqËèáW*Ô€\ìds”V­Ý€^'ˆÔs`/+ˆ‘F tÆÕÃ}wêO¯‘<ˆ!òép×|Áz¥ß5*Ö»¥vó]ȧʰõX;µêl{£dý¦(ÀHý©;¦¤ê¤Tk?€ùÿû Åljg{(y€hF¤ÅïááÞÇ" X>$r¹^é/8k­*ÿú>(=Yœq ºÇJ÷V¦SPÛ@¿P‡ÿµý¡k´Ò½VÒŠ7˜ÿü"ú,ä· œ›ÙŠ„#"7|û6þ6éCyž×õ¼õJáæ!YÉ9VëõZþÛu8¹§Þ©ÚÉ»3N’A÷XéÞJ} Xj!ÔëaOÛÛl›/Ø=ý%”¥Õs"XÅÁºÎM¬ØrGob4t—c‰uuuÿpj5d+êb#¯O~kp°ë{ +'j§îBÎ8MÝcIµê§ÔjOµê†:«ZûCá€6BªÕn Ž´Üѷѳëºão X>vY÷÷q øCäl ŸYpxryO­Þ¿¤ŠÅ¥ïBÎ8UÝc!Ký>ìÕjß|Av)”O‡)°¶ª%›-™Cؽk“/ˆÃ¡+ÑrG#ì¦ðÛq¢Xß÷îâh€PÁsôÅqüý:¬š÷ƒuXÞSƒõ*O:œ‘Šœ+["ãÀ®­ð+­Vœ›½jå•Ãá0Ž]w¹XˆÞF8î8é±`â{ÝWÉÁàÑr¿¸Î~ȯðF‘†ÕŸžtZ˜Aš¯ç0¸²…9»¶Qæ+yÒ•µVn´k-Kaï_ÿ<¢µŽÇw Pªt—ùÞJ~~ÀrŽÔºmXÞM¥§»Ö¼1Z˜QêmÎ6Ãl¥Z3Œ«9Cˆˆ4_‰sàS®¬µr£«Ö ú鋎¡s÷}‘4sÙB<Ó@™sõXù ƒ1JgPŽ)™AZ©Ö £ÇjʸÁ%0 ÍWã9+te­su `mïçÎø@ £¸Ç"°Ê£d阒¤•jÍ0z¬¦Œ¡´1Ê|5dÈEÐóHWÖ2W„]›ÍøÕ’8Ĥ¸Ç"Ô*kŒÒjB=ž’¤•jÏ0y¬F°ä AÒ|ÍgLºÏ#]Y‹Vvíç«t•ë­2Æ(}Ê1µü@+•Ì =V#XÚÆ ÌWcÇ$_C“®¬¥®ý4`Uè±HcÔ¶šS[Wf¶R ®ÊౚÁ Ý%i¾=Ö¤Kº²tå”]û9ÀªÖc‘ÆhK!i¥Ú¯Êä±Á ÑKš¯Ä9¤h®¬¥r®åËÐÍPƨEˆ7µ`¬Ô«Êy¬F°F¡ë’æ+u ]Y{`²k¹Ç"¡MÆhñ#t™÷X”j•¿ªâ X.—#Ò|¥û8Ê•µ7&»–Á2f˜Ñ“€u`¥–¸ª~‰ Ia¾Ú:?³+{lÀ=Vzåù&ËH;¦å®ªD†²1Ìæ«ífWöØ& ½=–Áÿ<&É ÍWûrkre½ªÖ/…½ËÅä#„ùZôH‘we¬6cò‘ £ùZt޼+Ë`1XyJræk‰GŠÊŸ,ކÝè< ƒÅÁ`qp0X ƒÅÁÑ&°›UØßµ•Šê;‘·%šYùùÁ’L£õÑé­¼ykÀjdåçKÈOôH²ÄÑ`U݉¼=`5±òóN8—ÿ»¤8¬Ê;‘·¬FV~Þ —8IµRŸCjo‚+VÕÈÛ£XM¬üÜŠü†¿”^ ¥[Ç(VÅÈ[¤X ¬üÌŠ¥t ¿­\ÈVïã÷mÅÈ[¤X ¬üÌ`%ýU|9½ùÓ÷mµÈÛ¤XÍ«üì=–l¯~KVBÄêã`UÞ‰¼5`5²òóN¸£:wý&Kcu¬bU؉¼eŠÕ°ÊÏ;áÓG'éÝ%VÁÓÕÕ‘ŠUe'òv)VÓ*?ï„¿LZ‰Z]]VåÈÛ¤XÍ«ü¼þú2“h)µpX•w;o“be+¯Å|½h°Â×Ù ªª•Þjà(°ªîÞ°²•×d¾^¶b…³WT­§¹u«’Ó[uôö€•®¼6óõ¢Áz{›…¨ZOsëV妷òì­+UyæëEƒ5›ÎÞPµP±l”›ÞÊ{°·¬}å¢Fóõ¢ÁêtfST­§¹u«’‡ªº{[bW¹#µª6óõÂÁ’h!XÖ­ªLï'4¡“ÕÛÀÍ׋ Ñz²o5À`•™Ä©#ª>óõ¢Áªÿ¾ý¼`½¼LÕX—ùÊ`1X ¬WùF°Fó•Áb°Xáëëì¥Fó•Áb°Xoá+ªV}æ+ƒÅ`i°ÞBT­úÌW‹ÁÒdÍBecÔe¾2X Ö­·ÍW‹ÁJ‘UŸùÊ`1X)´ê3_,+ÓºÌW‹ÁÊFMæ+ƒÅ`åЪÃ|e°8‰;OƒÅÁ`q0X ƒÅÁ`qp0X ƒÅÁÁ`q0X ƒÅÁ`q0X ƒÅÁ`qp0X ƒÅÁÁ`q0X ƒÅÁ`q|Žø?ÛúQ‚Ô4ÅIEND®B`‚nml-0.4.4/examples/railtype/gfx/lc_right.png0000644000567200056720000000767012643457545022234 0ustar jenkinsjenkins00000000000000‰PNG  IHDR·aÆþsRGB®ÎéPLTEÿîîïïððññòòóóôôõõöö¨¨¨¸¸¸ÈÈÈØØØèèèüüü4’¥x¥›ðÞdípÐÀA"ù mþ¿ªTÜÍáÇ€p2ŸÄ«. @ÆX™™‚˜ÎlÖÕ_}¶¶Xoº·×ˆ7ž Èbë`½~ ²Îþ87‹Ñ Õ¿¯àœÈ?áb»Y¯_ÿýãõ>Èëÿ¾®×›í"üg$È$E6ƒ&+ø^pü•þHÛ ÙoþU|kD r˜¥HRpìw?þþ±­ƒ¼î³ßÜíK’u:îöno¶šeôœ«cýs½+¾¼»ìÎUb¦‰¹ÏaL^Å—»`–Û…Û`W’døÃt’({Ã,»‘.3ÇõËÝn·XåvÁb‘ý²âÐAÈ$ -Žý>\·&+X„ûý %È c²æj±­vêÙ/ƒÝÎô“(Ab@:8î>.6M‡»$4Y#Aº8îMô‘(A^fR>ÚusÜé&Ѽüù2oíb±=?•wp<¹G’G%ÈÛjî² ›!AÚ$ëï›E°ÑUHš®Þf ²Í+®sV».Ž.*ÉeŽ+{pÙ*@VišÎ¹Sÿ•–3ºåœÕºƒ£äBR›ãú¥i²Ì2÷sÈ¥:~~ïæè¹¶V?w×9.U§›·™²ªÕ± ÷jS>½¯Ë*Q²–<‡˜ì®¾ÌYíû~ºñøç½ûËWVm#†½1 YÎÕ‘÷ñ$ßr» ’QÏ!Ëø‰é÷¼½ÙíÁâ—Ñ÷!æWö ²o3òÁ0^òó2áv;àÇÛe» G?©/£åŒA<8Žf¢™þ3oA ^‰|â•HKè‡×Í6É=£Ï¹ÜþImÿß[¿C¹NsY1€¸(¾ú‰tœÏ¸ûjÐ5bf¢Þ×¹pS‚T_ («§Ò×~*õvïPÿršÞâr=7˜vG¶›¤Jo. ·3«ìÆ(÷U¹4“•ï7a…˜ëÿ'1¿¶‘i\ú·ÖݺMÓ(ÈzqÖÝ5'ÓîÔÕ?Í`ª<ìÚé˜ÖW“THí¾m_Ý­[1¶AîýŽI÷UÎk^¦&ˆfG¦q®@ê€Sƒ4ÿ›¿Ú$Ù1®@ÚCˆ‰AjÿŸß4¤-|>¤Ÿ¤ñ;ýƒIÑ~ª{ý»N„ÿ5AÌÿ2ѾTƺŸ­;©=ÀÙdsw1É”Xíÿ¿?Hk€Ý*Ÿ±ÃÞË{ý‰`¢iÄöÉÔǾcðo߬vßV¬FÞº_qµÕqß{Ôâ·~Ž]ùvõA _ãnâÌä[.ø&qõ&Ûä š+ÿ¾$?ĈOä ÚFµéµ:¿œ'Ø";¬TxX+1ÈA ’“âÔW¢ ,߇âZß'–l‘¦éJ¶“X ²’‚äï‡ÈO=(ö!¿Vç§,¹VÙQYo²bq“e4§.l²ÄûP\+E“e–Ò–ôà ÉÊŽêÍ2ˆ|Šk%‘º£>D~Tò>D±ØØïCäêú£¹W±¸B¤ûÐUHl¹Bô!ò£ÊG@O+ä`¹B^Œp¯ëCä# O+äÅn…äK,Ȇðåv+$åæc…dÇe»B„Cør• »’€ü«¢)‘VH$ïC„CøüJ­Þd ‘âî•ö!ò}ˆ·È›Y¢˜þ:ᾜŸ±;¹x~‘úÓ¯M‰u•´ã1‡|ÎÁ6H,|q‘¢)‘=‡(N=–v<åüŒý ‰½)šÙsˆ²É’UH1ç`¿B‚ÄÆ8êCdÏ!±™eR\(ë OñRÚ‡¸¨(?0+$_“Âr²\ +ä)vP!ùêK+d¹´Ý‡,£H6 /€dæa…DÑÒ2H$ž\\FN*$ò´‰ì‚¨­WˆŸ¹,âHq\óùV‹G ·ÃÄ#ù@Èäý-— @ €@!€@!€B„ÈȰÐÏ—ù†# €Èï ¢Y%í­ñÉÚ}ÿl÷îçà•ïI?ØL¹Ú›èóö\¬(w8H?ÿϨVIK'/¹¾'ý`3åjo"7+Ê­b!ˆf•´òíëQ’~”ïI?ØL¹Ú[{?‡ÃÃ-VÙ1H÷ñ¡YVíp½ ‡€hVI+ß¾’] ÓÙdéV{k}Âq×›ëoæC¼ÕŠr«¸Xì¥lWz›,Õ 7K3²Éº¼Áó¨ÉÒ­öÖ¬Žî&ËÙŠr¦X쥨ÈM–j…›xt§÷€¨Vê©ä%î[øÎÝzYùò3«¸¨È! º  Ò_!Š•z*ÅD½âf½¬KÁ튟r}OÚN…dÕñÒ?ÊrX!çÊŸr8\ß“¶Q!õÁ¿7R¬5q°P!ç…Ô ùÈüúžôô¯†>‡¸¯|U‡é+$>FTHyûF¶úö£±W²\Æý[H_·_FQó.òRynz@4«½å«ŠžÔ]¬(ר(ê[üC1¹µïÄa ·‘Ïm«É&‹;péÜ‘ý©ÂÚÈÒDÓƒ\G2/2óóªE|UŸA¢!S'úTnø wæ=¿òôû·ÎØɺ„K_Ò ²ºÓ–25ÈíÆïÙÉêÄH9îïÙ‰òØQäUÈtÀÉîå] €× @ €@ €@!€B„B„  @Èhù‹‘&žú÷ÞÚ[ˆO$m£Û‡æµèÄØ9‰A7 GOANÞœl!^L/q’¯!h»ÉRì#Mí7Y©D’:Q¬ëè#ÈÉW£§ 'ï@N€ˆšEûã É2 ÃÞÄx؇ÈJ"ßGâäèåsÈQ1’{åû8ºö&^>‡ˆ*•ƒ¸ª“å I¤ä¨I<­Q“¥˜þJ¤C3y“¥8*1ˆ‹¸9y "¾€ŸZpò䔘eLÝKÔ>H"3§ÔÈÑ$ž‚8èCdçn’ÔI…Ìäé9”µn@Žùyש§iøüdä9 …ò”ß'¶AL~`Þ$& Ÿ-ƒ„A ¬ç0q’˜w G¡e@ü¤G ‡Ï!Iq\VAT„Ö+ÄÏÏö²›×ÑóêqŽ šõWÛÃÄ#ù@Èäý-— @ €@!€@!€B„ÈÈðIŸ_„ÏÂ@ùÍA´k@ÕÒseÍýt€LrT=çæb½¬$~<€8YÀìt¤š$ùŸAòC‚(ÖÝ)Þ¾r~éðˆnµ·fŽ]/z¹[Q®V%ö¡IG‚¤} šµ¬šÕÑÝdåÇ ÜÇGª«ÛJ•ÌäxìëC‚UR ¯ ïvAn·dÇïâ}¼§úž-É9(:ª± Oé{O§®Zí­Y=£,Ș¡Æ@íÚm#@’äúžôCÕZV­»ÄÕzY·V!I€H—l9/ä ÉÇ×÷¤‚¨Ö²jVGˆ«õ².yÏWu˜¾Bž“dD…”·ïõ=é©+¤ýhìO…”«:L_!a$'%ÈmDn£BòUE$Oê®+¤XÕaúÉÅ }'iܾ“N.Kk„ÏF:wäbªðV!YÛ0=ˆi?~iÍêL’¯*â3ÈqÈsȈÜnø wæ=¿òôû·ÎX¹Ýô½ ­êÄ ÈåÆïÙÉêÄHyó÷ìDyl€¨@ò*dºaÀd÷ƒò.‚€@!€B!€B„ @ €@ €@!€B„B„  @ €@!€Â%„ @ @ €@!€B!€B„ @ €@ €@!€B„B„  @ €@!€@!€B„„ @ @ €@!€B„B„  @ €@!€@!€B„„ æ?®¿—rAµ-IEND®B`‚nml-0.4.4/examples/railtype/gfx/depot_electric.png0000644000567200056720000000647012643457545023423 0ustar jenkinsjenkins00000000000000‰PNG  IHDR⃧f7sRGB®ÎéPLTEÿîîïïððññòòóóôôõõöö¨¨¨¸¸¸ÈÈÈØØØèèèüüü4‚«bˆn¬"B¢Šð ûýRD$‚(@/´‹ñ}‰2Â(Ït¡.BžO玦z G*je(YÅýÃ2DR°£m‰áûˆøN¤Q3v—•<ŸâÃ^Tô4´fôwi”QïQG½,PEA˜U*/„±Ž”ßï•„Ábx¾¨äÔ ê̓ހJj€K$žô•ˆØŽ ±ît §§Ú¹hÆ¿!oI€×#Š‚”B4 ‘'®BLxôŽA¸FJí9ȹ)‚Šæô7ÞÙúY°Ž êƱ:? ^#¾$ùZ‰’“Xwâ$äæ«›”ƒ„’ä*†5gvÀVÞ•ÿÌJÊÅÊ3ÈsÊEI yÊ_|˜^ÆêÜ€%ò—®Q.Ùy¸nŠ(>†vÈDƒ+â3¾³ ùWZº2a‘}Nâ€1ãi Ù1) 5qü]žöÄV‚4d%©Îpð¦œÄ1ž!VÇJn†(¾M…¡Q/>÷•&Pç|œÌ‘ØædŒ!ÅkßB0(Ú‘’!¦Œ©Œæå”âÐd02²Ža‘ÉH¾,¡¨ßG‰„+^ÀQNn‚˜«(>».WQ™¥ Šˆ_1PŸc”6ÐN"2Õ,~‡ô Þ°$fQRþÞ>\7@L» ÙN `èD;ŒÐz¿ÖÊUç+ßM v²ÒkYû$dŒƒ•}ß…Ü'a¨´ Y‘d<òZ4©HUTj*7’³½Ïmú¥$䀣êšsrøh×GÜŸ©Fu…ËcäªÏ{VPëÎÄ)C%¹Lñõê6¤`׿ä±>"î)éÉÂü‚äæ!O'ÒŠF Gn*X ÷§ò°¦N©ëÓ;q«(ᑚ3±ÎùÊYØìÒsD¹ÃêCy3 )—šs]Ì Â„þ<°¶8P†±øí‘‹×fÔF9(ÈÑ~¾çˆY£~yéÅ*N!J3LÍžà $lšIÄÝ…È~}úæ¥Bk£Ç£% ñ¶Œ÷Í*ÂlêDhÔñð¢kÔ‘÷RÜÓƒý¦µküD Ú] Ãu r°Fåµ÷Yl­x¡Ž¯?Þ®ÆÆW±›j OÎE¥ºñʇ6´þàë2 ?0…üT¹yÃX//6Cvi«Â+?YÙaVêõ'$#D„s¦¤÷-àx[”?¨Â#‡(V‰ç˜nÁpþñG óÖÞO ~ÿn¬Qö`»—”“}[¥ÞȸÚ@€Z¬£;2Ÿ³_Ô&üøA•iÁ*Ñ9¬¢€v'y¯…”Ö®Hè1<Ãߟý±æ&©¨æ¤>Ñ s°ñ¨þ'ÐŒå@­G9Ùå–O„Ø ÝXÁÏúdŠ×Šu ƒ•ôõHˆåF+ƒåæ€zš“ù¶ކqj¾êÃ7MwÑ™qƒ•ä t0|µÕýø¾mc‹ôñ€šrr¬dËëg¾òóE©«í°zÏ9Øø×ãŸ?A3»ë×Ði_Ýáh-ç$ ú$:7©à§~„ª¹®Žr05‹êçk?ù»ª®Ai TFMHèùòO‰uMŒºÏÁÊKVC fDÈ”“µísRae¾þƒp^ÓQÒH¤iA™7:õà*)³ãœ4’“–Ê5jX¹o· "¬í Ç9èša^õÉTÊI“r -ünCE¼Þ8!ßäf1ÌÁ+?B•œ¬%Pw/m o@E£¢åZîƒþªN}>PŸ“¸•Ä!a}t*E*6Íd.8±Ñ÷I[ITWn7¡"$ÝuðS9¸èPJß'QÅÊ7Us *ª (P!¨¦rpá¹ÎI䨇 ¿¹ÜÌ.8ZD9i)P¿dÀ ˆ,ª´–œŸÏ¢ÓSšnžà}šEnzNâ¥ù,< †Ö.pãc~êù,>‡Ÿ}’ñ—~Ä‚X bA,ˆŸÚú‹‰¡ þˆ·`bØñ6L "ÞŠ‰a3ÄÛ11l„xK&†MoËİâ­™VG,&†bb(&†bb(&† C11œ XL ÅÄPL ïA,&†÷™†ç¯714Þ»fÄÞÄ Ö21 Î/014D¹>bob¨›Üœ‰at¾ÿfL ­KM î|F;˜·3 L Œ¸‰‰AÊ”›&÷ø8åÓØås‹ƒc¶³&:!ŽÉاh%BQJÅ–”úZƒ{Üí¦*ª1çg‰gM Ž­çó=ÛTTñÙÐ?Ãÿ_kbp;7Ù4ÒYâÚžŸï?71Øä £3çÛ w Eš^kb˜Ž+·••s²†ëÈÄ@PüoJqõ²¬nè˜!I'*%Aû¦‰ÁÍšNÎ÷›ÏM21Ðá3ì<žÎ.»éWÙk•8é‹ú¨‰Î-¦³ÄvÜ'ÙÄ€pM…ÑÙ~•›p¥ZŠ õúQCÎÉzœ“lbðä dù¼»0àj·§4W•½Œ71Ðj‰T⹘H=.1ãÙ[ÞdÔJYÍ5L œ“uöÜ L ãKLUùÏô/²­ë˜†ž ÜeƒË]Â}®Q§•'ã:&#9I§¦“‰¡A; ¢¾9àêÛP¸RñQj%CöÁi›Î†Ã6ŒzÅWùSs\ÏÄÀ9©ÓuobðÎ}¥1©¸ž‰sRßÝýF{iÜ6s'²?ëƒpÝêz&}wwW[YÏ|¹‘!×51 â=Õè[11èõM Œø/71ÜC11Ä‚X bA,ˆ± Ä‚X bA,ˆ± Ä_æñs ¼õÍ.fIEND®B`‚nml-0.4.4/examples/railtype/gfx/fences.png0000644000567200056720000000354312643457545021677 0ustar jenkinsjenkins00000000000000‰PNG  IHDRš0(LmsRGB®ÎégAMA± üaPLTEÿîîïïððññòòóóôôõõöö¨¨¨¸¸¸ÈÈÈØØØèèèüüü4ÇðS£sNm4)µðãÿ,ä@=4ΙyHÐ@é„SS ÙsŒ`j°‹Æ9'rh¥UóvbÒD›*çÕ#@ãœY*>‘¦·‚ §&FsÌ14Û¶†Î9‘jйæê†ë{'v ÔwêáÑlÎL‰ÆÐNMˆ&Ì124qΉTƒÎ5w£I!‘Ž×gѼ™ íÔDhpŽ‘O-Ì9šÂæùnÔ š3S£!šMœcähÖõ°çœÚæ¹.šÝ™@C85 •ch‚œóÃhgvMìÔ4tŽÑ¡ÙsNmó\Q53;…&rjY4©£E½ºgk¿‹&tf'Ñ §–E㟕Ýa>לSû\SM5È™E:µ,8§fý'k&ƒÚ„j Ÿk*¡‰œÙi4ScT“?èÐÄÏÜlásM4±3»€æàÔ²hJO ¿¯)ÝßÝíÒ™]A³;µ¦h†ï?r’ÎìšSëh.HisžéM?ßtºÞöL­-šÂOž/D]TÞ˸·™R6š©èÞ~v4"t!ÿ6óv4Ω5EÃýæäBÔªTõo3  §Ö þZ•xÞØÉ•ß_¯öòWcÕüŦ@®qM6UÍ·Ÿkn ²©âþ)q®ùG]ʱÿxñòhè:vaG“AGÓѰxl®šŽE ohì’èªaCÔª@GÓ*òl¿ ¢VZ¡i5ß/ê·£y,¬Ž¦£yl;°®šŽæ±xìÀºj:šÇFà±ëªéhÇì?MW3Üžê—IEND®B`‚nml-0.4.4/examples/railtype/gfx/gui_erail.png0000644000567200056720000000761712643457545022402 0ustar jenkinsjenkins00000000000000‰PNG  IHDRXdmŸ¾sRGB®ÎéPLTEÿîîïïððññòòóóôôõõöö¨¨¨¸¸¸ÈÈÈØØØèèèüüü4d-=…VìF"GK\|š}zœ>Îß{žú)‹`I=¹\ëûëê›%vt²ÇÁ<ò Þ§6¼³XqŠUìÅBúκìÄe(¥.™CWÓÖ’Ãa‚‰”Ç—R´b“±d,[2RX#ð“±Bu0EËD=0Î..g³¹+9)ÄeÊž\ñ¤'׆鞂ʸšÔÚ…ìq0¼Çr Ç‚6(ÏX)V*Å cÁÄ8e|ŮӪØ9ð­:9´ÒK_üyAàêBðcVb-™xtî/g—@žŒd$ÔÕa:9ø¿­à7˜\e&W{êÕ;¾ ÙãPyÕ^,è[ÖÀ2XA²ˆ!c©‡¦~AS€‰ Nþ äcž cAÖ‘Z,øøÿ÷/H>n¸^ –"+G ,”zàáá~„Bv:RX3i&ª’/˜De&‡§Uä¼N*/{Ð#ï±à**QÞc¥ÙJU; O+ÂL5&·†A,“J˜¼éŠ'è±ôsiÖ2`A›ˆ/¬_„zhÀrýØ …+»åËøv|{›¼Ôôa:½˜NCÈWQÔé„S +-…«··7õKU© ãúžz¥b'w!{Ä#ï±:É}¬ ,“­Ì•”B™W#/)€y虤ª>ùæêÏd­¤Fºê¥<7 "…U9K ®i¶îÆ/Π;'˜ÌºÝîýÃ=”Ɖ¶Úmhä³Rø¦MnNžì)ˆœˆÚ…ìq¼Çj'÷±2°²l¥ óÌŸ,…úBÐ× #!ßE2tg–M®É’…ÉÕžZ½þ1QI,ª¼ ÙãPyå¨k¡—¤cʲ•bÁÏoèÚ×JKJ¢”ÙkžLR•ÉZ†Q]´‚ºwøz/C\´])´îÆ£Ø 'y©ùl6½xˆÂ @%¿„ï\7Î'×Í{arµ§&oßê\éà›Âh‚bºÍ£,¥nó(k¬Uâ¨yI•Ý>GQ®%z,G÷X9X­,[)çu°2+Ý.OASe6`¥/e XËåU²Öp¼X/î`<6…m>›^<Ì JN&_­»wwXš\¾A’–«Õ¸ÒÁ<Š2'éA*¦{œƒÔXIR|ÝòS!ªì¶ÈËr-uƒ4y¯¼5°L¶R3zUÁò”5Z­òKmŒ–½Q´ìõn¥}Uh}¥Y"ý4½tߨɥº}+_,Dÿ,,VÑF·y”õ‡ )•ô 5VÊ#$ÅWëO…ª²[â@äZ¢ÇŠ“k¬VK¤s¡`!ʱKO, :LúRë½Ñèýr9z?±ÿAyU•³ ¸üRÀ'O>؛ʴ¨þ‰|$÷@…Q»Çv°Š˜”Jz+á‘ܵAÅWËOE¨²["GäZ¬ÇŠÝ…Ÿh…›`mù ÖÏ {€d­±TYI%'¹V‡§³éåÀÁ2ᦡ£úçæbmzà¨ͣ X›¸”Jz+áWF”øJÎAª²öÈ1¹–(…¾ê|’ ó KïjÔ»º-¤ÎJ£ I¶Ò§O³ò€e޵Ñ?±ÐJ¥=(ÅÔæK©¤©±âB„¤øJÌKL©²öÈ1¹¶îý<0„¤5n¾.Ì%Æf S)±*s ú':)ŒÒ‹E)¦¤)¥’¤ÆŠyøPR|E符¤*k“kO ,…–Þø#ôÈÔÀ¶9PýÓ:*ŒÒ(RŠ)éAJ©°ó&2!¾"ªN§¤*k‹—k7=~4ÄŠ˜ ú§ÅƒFéüCÝ&&=H)Õ¡±b£‘F‹_ËRÎ/æÓ)©ÊZ"'äÚó Õ?IRµB1¥=()Õ¡±¢`EÑhHНhǤnC“ª¬­âríÙ€UÖ?é]H £¶‚€+¦°)•.ž”ÆŠƒµ„¤E‰¯D5¿ UYk)DåÚó Õ?é·F÷X I)Õ¡±¢`-—½+R|%æPI‹Pe­‘£ríõXEý“î(aÔ’ÅÔúS¡RªwBcEÁz¿ìõHñ•šC’ª¬=rL®=£«¨Z=Patû%tËv» "¥ÚÀÂ5V´ciƒ_É9HUvKäˆ\{F`õO›.Œî,LJ­òSUð -J|µu~¸*û³‘7ºÇ*êŸöÅÂ„Ñ Ë[ás”›`Ój?U)IñÕ6®ÊþläÍí±ýsϘœž)¾Ú›L•e°,¥°¨6,R|µÏ©² V“1ÙÅ_·ÍQVe,«ŒVI|Ý>‡¬íq`±ØFç%`c°Ø,6‹Ábc°Ø,6¶&edVa¿×VÉêŸDÞ;ÍÈ–ú€ä:Z».oí“ÈÖIF~l°„Пî9Z;/oݓțÖ)F~Ü áÂïêŸKŠ:ëu’=Qû$òÆ€u’‘wÁN*[éäãÊë°vÖI¶oëžDÞœŒuŠ‘;c¿Á/¯„Î[•ÁÊÎ:É÷m͓Ȕ±N0ò#g,§à+H2—@ÿ}²–«„(}ÖI¶okžDÞ Œu‚‘,Ó_æËõ>ÜTKn>Øá$ò&e¬Ó‹üè=–j¯~3•°Ú¬Ú'‘7¬“Œü¸ îêÎ=¹“•`U¬ô¬,cÕ8‰¼aëÄ"?î‚__¹¦w¬:ÁM·[¬ü¬“rƪsy³2Ö©E~ܺÖh™lÕéT+;ëD'¯µŒUï$ò&e¬Ó‹ü¸ þüÔWhVÁÝn»]©š³NJû¶ÞiçMÊX›‘ïE|ý¥ÁZ>÷Ÿ kA¶JŽèvçGE²dñ¹ºç¼7¬ÍÈ÷$¾þÚkÙ†¬uóÁzÔºIeé)ôDõ³k=ò½‰¯¿4X//ý%d­›Ö£Ð ÃÒ3èìçó±™$òÝÅ×fÕ¿î¿@Ö‚Œe;8 Úò¢g°ŸXyäbgñµa`µZýkÈZ7¬G T|)ì ös+‹ÜU¹jGñµq`)´,ëQu–÷ Eèôÿ1Ð í(¾6,@ëÆ~ÔƒUe¯]• v_›Öþ÷íù‚õôt­KàNâ+ƒÅ`Q`=«;‚»‰¯ ƒEƒµ|~î?í$¾2X –¬—å3d­]ÄW‹Á¢#¿~yYBÖÚI|e°,:òëþRË»ˆ¯ ƒe‰<‘1v_,Ëy¿¿³øÊ`1X¶È÷(¾2X Ö&Z{_,«ˆÖ^ÄW‹Á*¡µñ•Áb;IÜy Ø,6‹Ábcc°Ø,6‹Ábc°Ø,66‹Ábc°ØØ,6‹Ábcc°Ø,6‹Ábc°Ø,66‹Ábc°ØØ,6‹í<ìË -žéyIEND®B`‚nml-0.4.4/examples/railtype/example_railtype.nml0000644000567200056720000002675512643457545023230 0ustar jenkinsjenkins00000000000000/* * This file is aimed to provide an example on how to code a railtype in NML. * To keep the code readable, not every property or variable is documented in * detail, refer to the object-specific reference in the documentation. * * The NewGRF implements a graphical replacement for the normal and electric * rails. Since almost all sprites (except caternary) are supplied, you can * use this grf to see in detail what sprites are needed and in what order. * * Essentially this is a cut-down version of the Swedish Rails grf, drawn by * Irwe and coded by planetmaker. Support for parameters, time-dependent * graphics and snow support has been removed to keep this example within * reasonable size. Due to the large quantity of sprites required for a * railtype grf, the number of lines of code is still relatively high. * * All real sprites have been templated, even if the template is used only * once. This allows adding e.g. snowed graphics fairly easily. * * Apart from this file, you will also need the following * - Graphics, found in in the gfx folder * - Language files, to be placed in the 'lang' folder. * Currently english.lng is supplied. */ /********************************************** * Header, containing some general stuff: **********************************************/ /* * First, define a grf block. This defines some basic properties of the grf, * which are required for the grf to be valid and loadable. */ grf { /* This grf is part of NML, therefore "NML" is chosen as the first three * characters of the GRFID. It is the third real grf defined as part of * NML, therefore the last character is set to 2. Successive grfs will * have 3, 4, etc. there, to make sure each example grf has a unique GRFID. */ grfid : "NML\02"; name : string(STR_GRF_NAME); desc : string(STR_GRF_DESCRIPTION); version : 0; // must be numeric min_compatible_version : 0; } /* Check for NuTracks and disable, if we're not active _after_ NuTracks */ if (!grf_order_behind("DJT\01")) { error(FATAL, MUST_LOAD_AFTER, "NuTracks"); } /* Default ground tile template (re-use as needed) */ template ground_tile(x, y) { [x, y, 64, 31, -31, 0] } /********************************************** * Track underlays (tracks + ballast): **********************************************/ /* Underlays (single track bits with ballast)\ * Used for bridge surfaces also, therefore the template is split */ template tmpl_underlay_straight() { ground_tile(75, 0) ground_tile( 0, 0) } template tmpl_underlay_slope() { [ 75, 40, 64,39, -31, -8] [150, 40, 64,23, -31, 0] [225, 40, 64,23, -31, 0] [300, 40, 64,39, -30, -9] } template tmpl_underlay_diagonal() { ground_tile(150, 0) ground_tile(225, 0) ground_tile( 0, 40) ground_tile(300, 0) } template tmpl_underlay_railtypes() { tmpl_underlay_straight() tmpl_underlay_diagonal() tmpl_underlay_slope() /* X-crossing */ ground_tile(0, 120) /* underlay for crossings w/o tracks */ ground_tile( 0, 80) ground_tile(225, 80) ground_tile(150, 80) ground_tile( 75, 80) ground_tile(300, 80) } /* Spriteset containing all underlays */ spriteset(track_underlays, "gfx/rails_overlays.png") { tmpl_underlay_railtypes() } /********************************************** * Track overlays (tracks without ballast): **********************************************/ /* Template for overlays; 2x straight track, 4x diagonal track, 4x slope */ template tmpl_overlay_railtypes() { [ 0,155, 40,21, -19, 5] [ 50,155, 40,21, -19, 5] [100,155, 40, 7, -19, 4] [150,155, 40, 7, -21, 20] [200,155, 12,19, 11, 6] [250,155, 12,19, -21, 6] [ 0,195, 64,39, -33, -8] [ 75,195, 64,23, -31, 0] [150,195, 64,23, -31, 0] [225,195, 64,39, -32, -9] } /* Spriteset for overlays */ spriteset(track_overlays, "gfx/rails_overlays.png") { tmpl_overlay_railtypes() } /********************************************** * Level crossings: **********************************************/ /* Level crossings require differing sprites depending * on the open/closed state and on the driving side */ /* Template for the track overlays (x/y) */ template tmpl_rails_crossing(x,y) { [x, y, 44, 23, -21, 4] [x+50, y, 44, 23, -21, 4] } template tmpl_level_crossing_railtypes_open(y) { tmpl_rails_crossing(5, 5) [ 0, y, 5,12, -3, -8] [ 50, y, 8,21, -5, -14] [100, y, 6,23, -7, -20] [150, y, 5,12, -5, -8] [200, y, 7,21, 3, -15] [250, y, 5,12, -1, -8] [300, y, 5,12, -3, -10] [350, y, 8,22, -3, -19] } template tmpl_level_crossing_railtypes_closed(y) { tmpl_rails_crossing(5, 5) [ 0, y, 5, 12, -3, -8] [ 50, y, 19, 19, -4, -6] [100, y, 23, 17, -24, -9] [150, y, 5, 12, -5, -8] [200, y, 25, 14, 3, -9] [250, y, 5, 12, -1, -8] [300, y, 5, 12, -3, -10] [350, y, 19, 14, -15, -11] } template tmpl_level_crossing_railtypes_left_open(y) { tmpl_rails_crossing(5, 5) [ 0, y, 7, 21, 0, -14] [ 50, y, 5, 12, -2, -6] [100, y, 5, 12, -3, -9] [150, y, 7, 21, -7, -15] [200, y, 5, 12, 4, -7] [250, y, 7, 22, 0, -17] [300, y, 6, 21, -2, -19] [350, y, 5, 12, -3, -9] } template tmpl_level_crossing_railtypes_left_closed(y) { tmpl_rails_crossing(5, 5) [ 0, y, 21, 19, -14, -6] [ 50, y, 5, 12, -2, -6] [100, y, 5, 12, -3, -9] [150, y, 23, 15, -23, -9] [200, y, 5, 12, 4, -7] [250, y, 23, 17, 0, -7] [300, y, 21, 13, -2, -11] [350, y, 5, 12, -3, -9] } // right hand traffic: spriteset(lc_right_closed, "gfx/lc_right.png") { tmpl_level_crossing_railtypes_closed(100) } spriteset(lc_right_open, "gfx/lc_right.png") { tmpl_level_crossing_railtypes_open(50) } // left hand traffic: spriteset(lc_left_closed, "gfx/lc_left.png") { tmpl_level_crossing_railtypes_left_closed(100) } spriteset(lc_left_open, "gfx/lc_left.png") { tmpl_level_crossing_railtypes_left_open(50) } switch(FEAT_RAILTYPES, SELF, right_level_crossing_state_switch, level_crossing_status) { LEVEL_CROSSING_CLOSED: lc_right_closed; lc_right_open; } switch(FEAT_RAILTYPES, SELF, left_level_crossing_state_switch, level_crossing_status) { LEVEL_CROSSING_CLOSED: lc_left_closed; lc_left_open; } switch(FEAT_RAILTYPES, SELF, level_crossing_switch, traffic_side) { TRAFFIC_SIDE_LEFT: left_level_crossing_state_switch; right_level_crossing_state_switch; } /********************************************** * Tracks in tunnels: **********************************************/ /* Template for tunnel track overlays */ template tmpl_tunnel_tracks() { ground_tile(75, 0) ground_tile( 0, 0) ground_tile(75, 50) ground_tile( 0, 50) } spriteset(tunnel_overlays, "gfx/tunnel_track.png") { tmpl_tunnel_tracks() } /********************************************** * Depots: **********************************************/ /* Template for depot sprites */ template tmpl_depot() { [200, 10, 16, 8, 17, 7] [118, 8, 64, 47, -9, -31] [ 0, 10, 16, 8, -31, 7] [ 37, 8, 64, 47, -53, -31] [ 37, 63, 64, 47, -53, -31] [118, 63, 64, 47, -9, -31] } /* Depots have differing sprites for normal and e-rail */ spriteset(depot_normal_rail, "gfx/depot_normal.png") { tmpl_depot() } spriteset(depot_electric_rail, "gfx/depot_electric.png") { tmpl_depot() } /********************************************** * Bridge surfaces: **********************************************/ /* Bridge surface, uses the same sprites as track underlays, but in a different order */ template tmpl_bridges_underlay() { tmpl_underlay_straight() tmpl_underlay_slope() tmpl_underlay_diagonal() } /* Spriteset for bridge surfaces */ spriteset(bridge_underlay, "gfx/rails_overlays.png") { tmpl_bridges_underlay() } /********************************************** * Fences: **********************************************/ /* Template for fences, parametrized to allow multiple sets of fences (unused) */ template tmpl_fences(y) { [ 0, y, 32,20, -30, -4] [ 48, y, 32,20, 0, -3] [ 96, y, 2,30, 0,-17] [112, y, 64, 5, -30, -4] [192, y, 32,12, -30, -4] [240, y, 32,12, 2, -3] [288, y, 32,28, -31,-12] [350, y, 32,28, 1,-10] } /* Spriteset for (company-coloured) fences */ spriteset(fencesCC, "gfx/fences.png") { tmpl_fences(0) } /********************************************** * GUI sprites: **********************************************/ /* Template for a single icon sprite */ template tmpl_gui_icon(x, y) { [x, y, 20, 20, 0, 0] } /* Template for a single cursor sprite */ template tmpl_gui_cursor(x, y) { [x, y, 32, 32, 0, 0] } /* Template for all the GUI sprites (8 icons + 8 cursors) */ template tmpl_gui() { tmpl_gui_icon( 0, 0) tmpl_gui_icon( 25, 0) tmpl_gui_icon( 50, 0) tmpl_gui_icon( 75, 0) tmpl_gui_icon(100, 0) tmpl_gui_icon(125, 0) tmpl_gui_icon(150, 0) tmpl_gui_icon(175, 0) tmpl_gui_cursor(200, 0) tmpl_gui_cursor(250, 0) tmpl_gui_cursor(300, 0) tmpl_gui_cursor(350, 0) tmpl_gui_cursor(400, 0) tmpl_gui_cursor(450, 0) tmpl_gui_cursor(500, 0) tmpl_gui_cursor(550, 0) } /* Spritesets for the normal and electric GUI */ spriteset(gui_normal, "gfx/gui_rail.png") { tmpl_gui() } spriteset(gui_electric, "gfx/gui_erail.png") { tmpl_gui() } /********************************************** * Railtype definitions: **********************************************/ /* Define the normal rails */ item(FEAT_RAILTYPES, rail) { /* Set only the most essential properties, * Lots of compatible railtypes are defined to allow compatibility with * various other sets out there */ property { label: "RAIL"; // Let this railtype replace the default normal rails compatible_railtype_list: ["RAIL", "ELRL", "_040", "_080", "RLOW", "RMED", "RHIG", "E040", "E080", "ELOW", "EMED", "EHIG", "HSTR", "DBNN", "DBNE", "DBHN", "DBHE"]; powered_railtype_list: ["RAIL", "ELRL", "_040", "_080", "RLOW", "RMED", "RHIG", "E040", "E080", "ELOW", "EMED", "EHIG", "HSTR", "DBNN", "DBNE", "DBHN", "DBHE"]; } /* Associate graphics with this railtype */ graphics { track_overlay: track_overlays; underlay: track_underlays; level_crossings: level_crossing_switch; tunnels: tunnel_overlays; depots: depot_normal_rail; bridge_surfaces: bridge_underlay; fences: fencesCC; gui: gui_normal; /* Caternary is not not implemented here, use the default */ } } /* Define the electric rails */ item(FEAT_RAILTYPES, elrail) { /* Set only the most essential properties, * Lots of compatible railtypes are defined to allow compatibility with * various other sets out there */ property { label: "ELRL"; // Let this railtype replace the default electric rails compatible_railtype_list: ["RAIL", "ELRL", "_040", "_080", "RLOW", "RMED", "RHIG", "E040", "E080", "ELOW", "EMED", "EHIG", "HSTR", "DBNN", "DBNE", "DBHN", "DBHE"]; powered_railtype_list: ["ELRL", "E040", "E080", "ELOW", "EMED", "EHIG", "HSTR", "DBNE", "DBHE"]; } /* Associate graphics with this railtype */ graphics { track_overlay: track_overlays; underlay: track_underlays; level_crossings: level_crossing_switch; tunnels: tunnel_overlays; depots: depot_electric_rail; bridge_surfaces: bridge_underlay; fences: fencesCC; gui: gui_electric; /* Caternary is not not implemented here, use the default */ } } nml-0.4.4/examples/railtype/lang/0000755000567200056720000000000012643457623020053 5ustar jenkinsjenkins00000000000000nml-0.4.4/examples/railtype/lang/english.lng0000644000567200056720000000073412643457545022215 0ustar jenkinsjenkins00000000000000##grflangid 0x01 STR_GRF_NAME :NML Example NewGRF: Railtype STR_GRF_DESCRIPTION :{ORANGE}NML Example NewGRF: Railtype{}{BLACK}This NewGRF is intended to provide a coding example for the high-level NewGRF-coding language NML.{}Original graphics by {SILVER}Irwe, {BLACK}coding by {SILVER}planetmaker.{}{BLACK}This NewGRF defines a graphical replacement for normal and electric rails nml-0.4.4/PKG-INFO0000644000567200056720000000161112643457623014557 0ustar jenkinsjenkins00000000000000Metadata-Version: 1.1 Name: nml Version: 0.4.4 Summary: A tool to compile nml files to grf or nfo files Home-page: http://dev.openttdcoop.org/projects/nml Author: NML Development Team Author-email: nml-team@openttdcoop.org License: GPL-2.0+ Description: A tool to compile nml files to grf and / or nfo files.NML is a meta-language that aims to be a lot simpler to learn and use than nfo. Platform: UNKNOWN Classifier: Development Status :: 2 - Pre-Alpha Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: GNU General Public License (GPL) Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Topic :: Software Development :: Compilers nml-0.4.4/MANIFEST.in0000644000567200056720000000111712643457545015224 0ustar jenkinsjenkins00000000000000global-exclude ._* # Include documentation recursive-include docs *.html *.txt nmlc.1 nml.spec # Include regression tests recursive-include regression *.nml *.lng *.grf *.nfo *.png *.pcx include regression/Makefile include regression/beef.wav # But do not include files generated by regression tests prune regression/output prune regression/output2 prune regression/nml_output # Include (some) examples recursive-include examples *.nml *.lng *.png # include nml itself, including c-modules recursive-include nml *.py *.c # Include build files and main script file include Makefile nmlc nml-0.4.4/regression/0000755000567200056720000000000012643457623015643 5ustar jenkinsjenkins00000000000000nml-0.4.4/regression/017_articulated_tram.nml0000644000567200056720000000456012643457545022276 0ustar jenkinsjenkins00000000000000/* A simple articulated tram, graphics from OpenGFX+rv Code is modified in some places for testing reasons. */ grf { grfid: "NML\17"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } template tmpl_tram(x, y) { [ x, y, 8, 18, -3, -10] [ 16 + x, y, 20, 18, -14, -5] [ 48 + x, y, 28, 15, -14, -8] [ 96 + x, y, 20, 18, -6, -7] [128 + x, y, 8, 18, -3, -10] [144 + x, y, 20, 18, -14, -9] [176 + x, y, 28, 15, -14, -8] [224 + x, y, 20, 18, -6, -7] } spriteset(foster_express_set, "tram_foster_express.png") { tmpl_tram(48,1) } switch(FEAT_ROADVEHS, SELF, foster_express_articulated_parts, extra_callback_info1) { 1..3: return foster_express_tram; return 0xFF; } item(FEAT_ROADVEHS, foster_express_tram, 88) { property { name: string(STR_NAME_FOSTER_TURBO_TRAM); climates_available: ALL_CLIMATES; model_life: 40; // years vehicle_life: 30; // years introduction_date: date(1965,1,1); reliability_decay: 1; running_cost_base: RUNNING_COST_ROADVEH; // Default road vehicle running cost base running_cost_factor: 135; cost_factor: 143; speed: 317 mph; power: 220 hp; weight: 22 ton; sprite_id: SPRITE_ID_NEW_ROADVEH; // We have our own sprites loading_speed: 16; // loading speed tractive_effort_coefficient: 0.3; air_drag_coefficient: 0.5; cargo_capacity: 45; // passengers refittable_cargo_classes: bitmask(CC_PASSENGERS); // Allow passengers (and tourists) non_refittable_cargo_classes: NO_CARGO_CLASS; // Disallow other cargos cargo_allow_refit: []; default_cargo_type: DEFAULT_CARGO_FIRST_REFITTABLE; misc_flags: bitmask(ROADVEH_FLAG_TRAM); // This is a tram } graphics { articulated_part: foster_express_articulated_parts; foster_express_set; } } nml-0.4.4/regression/025_loop.nml0000644000567200056720000000005012643457545017710 0ustar jenkinsjenkins00000000000000i = 0; while (i < 5) { i = i + 1; } nml-0.4.4/regression/008_railtypes.nml0000644000567200056720000000023612643457545020762 0ustar jenkinsjenkins00000000000000 item(FEAT_RAILTYPES, normal_rail, 0) { property { label: "RAIL"; speed_limit: 40km/h; compatible_railtype_list: ["ELRL"]; } }nml-0.4.4/regression/opengfx_generic_trams1.pcx0000644000567200056720000003375112643457545023024 0ustar jenkinsjenkins00000000000000 r•,,sÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿðÿÈÈÿÔÌÿÄjÁØÖÔÿÃjÐÌÿÃjÄÈÿÐjÃÌÿ×ÁØjÃÔÿÔÿÿÐÿðÿÂXÁ؈XÂÈÿÔÌÿÆÂØÔÔÿÄÁØÏÌÿÂXÁ؈XÂÈÿÏÁØÄÌÿÕÂØÅÔÿÈXÃØËÿÿÐÿðÿˆXˆXjÈÿÔÌÿÈÂØÒÔÿÅÁØÎÌÿˆXÁ؈XjÈÿÎÁØÅÌÿÓÂØÇÔÿjXƒÁØÂˆÁØXÂØÉÿÿÐÿðÿňXjXÈÿËÃØXˆÃÌÿÄÕØÃÔÿÈXÁØÂjËÌÿÈÁ؈XjXÈÿËÃØXˆÃÌÿÃÕØÄÔÿˆÃXƒÁØÂˆÂXÂØÇÿÿÐÿðÿÁ؈XÈjXÈÿÉÂØXÁØÂˆÁ؃ÂXÌÿÂÂØˆÁØˆÂØˆÃ؈ÁØÂˆÁØÄˆXˆÃØÔÿÁØXƒÁØÂˆÁØXÂjÉÌÿÁ؈XÁØÂˆjXÈÿÉÂØXÁØÂˆÁ؃ÂXÌÿÂØˆÁØˆÂØˆÃ؈ÁØÂˆÁØÄˆXˆÃØÂÔÿˆÂjÃXƒÁØÂˆÂXÆØjÿÿÐÿðÿÁ؈XÁØÂˆjXÈÿÇÂØÂXˆÁ؃ÃXÂjÌÿÄØˆÁØÂˆÁ؈ÃXˆÃXˆXjXÁØˆÃØÔÿˆÃXƒÁØÂˆÂXÂjÇÌÿÁ؈XÁØÂˆjXÈÿÇÂØÂXˆÁ؃ÃXÂjÌÿÄØˆÁØÂˆÁ؈ÃXˆÃXˆXjXÁØˆÃØÔÿƒˆÂjÃXƒÁØÂˆÂXÂØÃÿÿÐÿðÿÁ؈ÂXˆjXÈÿjÆØÂXˆÁ؃ÃXÂjˆÌÿÁ؈ÁØÁÉ„ƒÁÉ„ƒÁÉ„ƒÁÉ„ƒÁÉ„ƒÁË„ƒÁË„ƒÁˈÁØÔÿˆÁØjÃXƒÁØÂˆÂXÂjÅÌÿÁ؈ÂXˆjXÈÿÅÂØÂXˆÁ؃ÃXÂjˆÌÿÁ؈ÁØÁÉ„ƒÁÉ„ƒÁÉ„ƒÁÉ„ƒÁÉ„ƒÁË„ƒÁË„ƒÁˈÁØÔÿ„ ƒˆÂjÃXƒÁØÂˆÂXÁØÂÿÿÐÿðÿÁ؈XÈjXÈÿÃÂØÂXˆÁ؃ÃXÂjˆ …ÌÿÂØˆÈXjÃXjXjXjXjÂXˆÂØÔÿƒˆÁØjÃXƒÁØÂˆÂXÂjÃÌÿÁ؈XÈjXÈÿÃÂØÂXˆÁ؃ÃXÂjˆ …ÌÿÂØˆÈXjÃXjXjXjXjÂXˆÂØÔÿÂÈ„ ƒˆÂjÃXƒÁ؈ÂXˆÿÿÐÿðÿÁ؈ÂXÁ؈jXÈÿÂÁØÂXˆÁ؃ÃXÂjˆ … ÌÿˆÒj ÃjˆÔÿ„ ƒˆÁØjÃXƒÁØÂˆÂXjÂÌÿÁ؈ÂXˆjXÈÿÂÁØÂXˆÁ؃ÃXÂjˆ … ÌÿˆÒj ÃjˆÔÿÁÈÁ×ÂÉ„ ƒˆÂjÃXňÿÿÐÿðÿÁ؈XˆÁ؈jXÈÿˆÂXˆÁ؃ÃXÂjˆ … ÂËÌÿ€„ƒ …„ƒ …„ƒ …„ƒ …„ƒ …„ƒ „ƒÔÿÂÈ„ ƒˆÁØjÃXƒÁ؈ÂXˆÌÿÁ؈XÈjXÈÿˆÂXˆÁ؃ÃXÂjˆ … ÂËÌÿ€„ƒ …„ƒ …„ƒ …„ƒ …„ƒ …„ƒ „ƒÔÿÁ×XÁ×ÁÉÁÈÁÉ„ ƒĈXÃÊÁËÿÿÐÿðÿÁ؈ÂXÁ؈jXÈÿňÃXÂjˆ … ÂËÁÊÁËÌÿÁÊ…„ …„ …„ …„ …„ …„ …„ÁÌÔÿÁÈÁ×ÂÉ„ ƒˆÁØjÃXňÌÿÁ؈ÂXˆjXÈÿňÃXÂjˆ … ÂËÁÊÁËÌÿÁÊ…„ …„ …„ …„ …„ …„ …„ÁÌÔÿ‚jXÁ×ÁÊÂÉ„ ƒˆÁÊÂ˃ÿÿÐÿðÿÁ؈ÂXÁ؈jXÈÿÁØ€ÁɈÂXÂjˆ … ÂËÁÊ‚XÁÉÌÿÁÉÙËÁÌÔÿÁ×XÁ×ÁÉÁÈÁÉ„ ƒĈÂXˆÁ˃ÁËÌÿÁ؈ÂXˆjXÈÿÁØ€ÁɈÂXÂjˆ … ÂËÁÊ‚XÁÉÌÿÁÉÙËÁÌÔÿÂjÁÇÁÊÁËÂÉ„ ƒƒÂƒ„ÁËÿÿÐÿðÿÁØÂXÁ؈jXÈÿÁØÃ€ÁÉXˆ … ÁËÁÊÁÌ‚X‚jÌÿÁÉÆËÁÉËÊÃËÂÊÃËÔÿ‚jXÁ×ÁÊÂÉ„ ƒÁ؈XÁË€ƒ€ƒÌÿÁØÂXˆjXÈÿÁØÃ€ÁÉXˆ … ÁËÁÊÁÌ‚X‚jÌÿÁÉÆËÁÉËÊÃËÂÊÃËÔÿÆÁÇÁÈÁËÁ×ÂÉ„ „ƒ„ÂÊÁËÿÿÐÿðÿÁØÂˆÁ؈ÂXÈÿÁØÂ€ÂÁÌ … ÂËÁÌÁËÁÈ‚jÃÌÿÂÂË‚ÄXÁ×ÇËÂÌ‚ÄXÁ×ÁÌÁÍÔÿÂjÁÇÁÊÁËÂÉ„ ƒÁ̃€ÁËÌÿÁØÄˆÂXÈÿÁØÂ€ÂÁÌ … ÂËÁÌÁËÁÈ‚jÃÌÿÂË‚ÄXÁ×ÇËÂÌ‚ÄXÁ×ÁÌÁÍÂÔÿÈÁ×XÁ×ÁËÂÉÃʵÁÊÁÈÿÿÐÿðÿÁÉÃjÂËÈÿÁÊÁɃÁË ÁËÁÊÂËÁÉÁÈÆÌÿÄ‚jˆ‚jÉ‚jˆ‚jÃÔÿÆÁÇÁÈÁËÁ×ÂÉ„ Á˃ÁÉÁÊÌÿÁÉÀÂËÈÿÁÊÁɃÁË ÁËÁÊÂËÁÉÁÈÆÌÿÂjˆ‚jÉ‚jˆ‚jÄÔÿÉ‚jXÁ×ÁËÁÊÁËÁÊÁÈÂÿÿÐÿðÿÁÉ€Á؃„ÁËÈÿXÁȵÁÉ„ÁÉÂÊÁË‚XÁÈÈüÿÈÁ×XÁ×ÁËÃÉ„ÁÉ4ÁÈXÌÿÁÉ€ƒ„ÁËÈÿXÁÈCÁÉ„ÁÉÂÊÁË‚XÁÈÈüÿË‚jÁÉÁÊÁÈÄÿÿÐÿðÿÁÊ€ƒÂ„ÁËÈÿÂXjÁÊÁÉÁË‚X‚jÉüÿÉ‚jXÁ×ÁËÁÉÁÊjXÂÌÿÁÊ€ƒÂ„ÁËÈÿÂXjÁÊÁÉÁË‚X‚jÉüÿÔÿÿÐÿðÿÄÉÁÊÂÉÁÊÈÿÄXjÁÉ‚jËüÿË‚jÁÉjXÄÌÿÃÉÂ4ÂÉÁÊÈÿÄXjÁÉ‚jËüÿÔÿÿÐÿðÿÃɵÃÊÿÿùÿÄÉÄÊÿÿÿÿýÿðÿÆÊÿÿùÿÂXj Â(ÁõÿÿÿÿýÿðÿÈÿÿùÿÆXÿÿÿÿýÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÑÿÄ×ÁÿÄ×ÔÿÃ×ÍÿÃ×ÅÿÂ×ÁÿÂ×ÁÿÄ×ãÿÃ×ÒÿÂ×ÁÿÂ×ÄÿÈ×áÿÃ×ÄÿÄׯÿÁ×ÃÿÄ×ÏÿÃ×ÂÿÄ×ÂÿÃ×ËÿÃ×ÒÿÁ×ÃÿÄ×ÿÿÜÿÒÿÂ×ÃÿÂ×ÔÿÂ×ÁÿÂ×ÍÿÂ×ÅÿÂ×ÁÿÂ×ÂÿÂ×ÊÿÁ×ÚÿÂ×ÒÿÂ×ÁÿÂ×ÄÿÁ×ÂÿÂ×ÂÿÁ×âÿÂ×ÃÿÂ×ÂÿÂ×ÄÿÂ×ÂÿÂ×ÂÿÂ×ÍÿÂ×ÃÿÂ×ÂÿÂ×ÂÿÂ×ÌÿÂ×ÑÿÂ×ÂÿÂ×ÂÿÂ×ÿÿÛÿÒÿÂ×ÃÿÂ×ÔÿÂ×ÐÿÂ×ÅÿÂ×ÁÿÂ×ÂÿÂ×ÊÿÁ×ÚÿÂ×ÒÿÂ×ÁÿÂ×ÇÿÂ×åÿÂ×ÃÿÂ×ÂÿÂ×ÃÿÃ×ÂÿÂ×ÂÿÂ×ÌÿÂ×ÄÿÂ×ÂÿÂ×ÂÿÂ×ÌÿÂ×ÐÿÃ×ÂÿÂ×ÂÿÂ×ÿÿÛÿÒÿÂ×ÃÿÂ×ÂÿÃ×ÂÿÃ×ÁÿÂ×ÁÿÃ×ÂÿÄ×ÁÿÃ×ÂÿÃ×ÁÿÂ×ÁÿÄ×ÌÿÂ×ÁÿÃ×ÁÿÂ×ÁÿÄ×ÁÿÃ×ÂÿÃ×ÁÿÅ×ÁÿÂ×ÁÿÃ×ÁÿÆ×ÃÿÃ×ÂÿÃ×ÁÿÂ×ÎÿÂ×ÃÿÃ×ÁÿÂ×ÁÿÃ×ÂÿÃ×ÁÿÂ×ÁÿÂ×ÍÿÂ×ÃÿÂ×ÂÿÂ×ÃÿÃ×ÂÿÂ×ÂÿÂ×ËÿÅ×ÂÿÂ×ÂÿÂ×ÂÿÂ×ÁÿÈ×ÃÿÂ×ÁÿÂ×ÍÿÃ×ÂÿÂ×ÂÿÂ×ÃÿÅ×ÃÿÃ×ÃÿÄ×ÂÿÄ×ÂÿÃ×ÂÿÃ×ÁÿÂ×ÂÿÅ×ÂÿÃ×ÂÿÃ×ÁÿÂ×ÁÿÄ×ÙÿÒÿÇ×ÁÿÂ×ÁÿÂ×ÂÿÃ×ÁÿÃ×ÁÿÂ×ÂÿÂ×ÁÿÂ×ÁÿÂ×ÂÿÃ×ÁÿÃ×ÁÿÂ×ÌÿÂ×ÂÿÅ×ÂÿÂ×ÁÿÂ×ÁÿÂ×ÂÿÃ×ÁÿÁ×ÁÿÂ×ÁÿÂ×ÂÿÃ×ÁÿÃ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÂÿÅ×ÎÿÂ×ÄÿÃ×ÁÿÃ×ÁÿÂ×ÂÿÈ×ÍÿÂ×ÃÿÂ×ÂÿÂ×ÂÿÁ×ÁÿÂ×ÂÿÂ×ÂÿÂ×ËÿÂ×ÂÿÂ×ÁÿÂ×ÂÿÂ×ÂÿÂ×ÁÿÁ×ÃÿÂ×ÁÿÂ×ÂÿÅ×ÌÿÁ×ÁÿÂ×ÂÿÂ×ÂÿÂ×ÄÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÂÿÁ×ÁÿÂ×ÂÿÁ×ÁÿÂ×ÁÿÂ×ÂÿÅ×ÁÿÂ×ÁÿÂ×ÂÿÂ×ÁÿÂ×ÂÿÃ×ÁÿÃ×ÂÿÁ×ÙÿÒÿÂ×ÃÿÂ×ÁÿÅ×ÂÿÂ×ÃÿÅ×ÂÿÂ×ÁÿÂ×ÁÿÂ×ÂÿÂ×ÃÿÂ×ÁÿÂ×ÌÿÂ×ÂÿÂ×ÁÿÂ×ÂÿÂ×ÁÿÅ×ÂÿÂ×ÄÿÂ×ÁÿÂ×ÂÿÂ×ÃÿÂ×ÁÿÂ×ÄÿÂ×ÂÿÂ×ÁÿÂ×ÎÿÂ×ÄÿÂׯÿÂ×ÂÿÂ×ÁÿÂ×ÁÿÂ×ÍÿÂ×ÄÿÅ×ÁÿÁ×ÂÿÂ×ÂÿÂ×ÂÿÂ×ËÿÂ×ÂÿÂ×ÁÿÂ×ÂÿÂ×ÂÿÄ×ÃÿÂ×ÁÿÂ×ÂÿÂ×ÁÿÂ×ËÿÁ×ÂÿÂ×ÂÿÂ×ÂÿÂ×ÄÿÂ×ÁÿÂ×ÄÿÂ×ÁÿÃ×ÃÿÃ×ÃÿÅ×ÂÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÂÿÅ×ÂÿÂ×ÃÿÃ×ÛÿÒÿÂ×ÃÿÂ×ÁÿÂ×ÅÿÂ×ÃÿÂ×ÅÿÂ×ÁÿÂ×ÁÿÂ×ÂÿÂ×ÃÿÂ×ÁÿÂ×ÌÿÂ×ÂÿÂ×ÁÿÂ×ÂÿÂ×ÁÿÂ×ÅÿÂ×ÄÿÂ×ÁÿÂ×ÂÿÂ×ÃÿÂ×ÁÿÂ×ÂÿÄ×ÂÿÂ×ÁÿÂ×ÎÿÂ×ÄÿÂ×ÄÿÄ×ÂÿÂ×ÁÿÂ×ÁÿÂ×ÄÿÃׯÿÂׯÿÂ×ÂÿÆ×ÁÿÂ×ÂÿÂ×ÄÿÃ×ÄÿÂ×ÂÿÂ×ÁÿÂ×ÂÿÂ×ÂÿÂ×ÁÿÂ×ÂÿÂ×ÁÿÂ×ÂÿÂ×ÁÿÂ×ÄÿÃ×ÄÿÆ×ÁÿÂ×ÂÿÂ×ÄÿÂ×ÁÿÂ×ÂÿÄ×ÃÿÃ×ÃÿÃ×ÁÿÂ×ÅÿÂ×ÁÿÂ×ÂÿÃ×ÃÿÂ×ÅÿÂ×ÅÿÃ×ÙÿÒÿÂ×ÃÿÂ×ÁÿÂ×ÂÿÁ×ÂÿÂ×ÃÿÂ×ÂÿÁ×ÂÿÂ×ÁÿÂ×ÁÿÂ×ÂÿÂ×ÃÿÂ×ÁÿÂ×ÌÿÂ×ÂÿÂ×ÁÿÂ×ÂÿÂ×ÁÿÂ×ÂÿÁ×ÂÿÂ×ÄÿÂ×ÁÿÂ×ÂÿÂ×ÃÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÂÿÂ×ÁÿÂ×ÎÿÂ×ÄÿÂ×ÃÿÂ×ÁÿÂ×ÂÿÂ×ÁÿÂ×ÁÿÂ×ÍÿÂ×ÅÿÂׯÿÂ×ÂÿÂ×ÂÿÂ×ËÿÂ×ÂÿÂ×ÁÿÂ×ÂÿÂ×ÂÿÂ×ÂÿÂ×ÁÿÂ×ÁÿÂ×ÂÿÂ×ÁÿÂ×ÎÿÂ×ÂÿÂ×ÂÿÂ×ÄÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÁ×ÂÿÂ×ÁÿÁ×ÂÿÂ×ÁÿÂ×ÂÿÁ×ÂÿÂ×ÁÿÂ×ÁÿÁׯÿÂ×ÂÿÁ×ÂÿÂ×ÃÿÁ×ÂÿÂ×ÙÿÑÿÄ×ÁÿÄ×ÁÿÃ×ÂÿÄ×ÃÿÃ×ÂÿÄ×ÁÿÃ×ÂÿÄ×ÃÿÅ×ÊÿÇ×ÁÿÃ×ÁÿÃ×ÁÿÃ×ÂÿÄ×ÄÿÉ×ÂÿÁ×ÁÿÂ×ÃÿÈ×ÁÿÃ×ÌÿÄ×ÂÿÄ×ÃÿÈ×ÁÿÂ×ÁÿÃ×ËÿÄ×ÂÿÃ×ÇÿÂ×ÃÿÄ×ÍÿÄ×ÃÿÄ×ÂÿÄ×ÁÿÇ×ÂÿÃ×ÁÿÃ×ÍÿÂ×ÃÿÄ×ÅÿÄ×ÃÿÉ×ÂÿÄ×ÃÿÃ×ÂÿÃ×ÁÿÈ×ÃÿÃ×ÂÿÄ×ÂÿÄ×ÚÿÿÿÿÿÿÿøÿÂ׿ÿÂ×ãÿÆ×ëÿÿÿÿÿÿÿøÿÂ׿ÿÂ×ãÿÁ×ÄÿÁ×ëÿÿÿÿÿÿÿ÷ÿÄ×äÿÄ×ãÿÄ×ìÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿðÿÁÉÂÊÂËÁÌÈÿÌÁÉÁÊÁÌÁËÄÌÿjÁØÚÔÿÃjÐÌÿÃjÄÈÿÐjÃÌÿÚÁØjÔÿÄÁËÁÊÂËÌÿÿÐÿðÿÁÇÁÉÁÊÁÉÁÊÁËÁÌÁËÈÿÊÁÇÂÊÃËÁÌÁËÂÌÿÂÂØØÔÿÄÁØÏÌÿÁÇÁÉÁØÁÉÁÊÁÉÈÿÏÁØÄÌÿØÂØÂÔÿÂÁÊÄËÂÌÁËÊÿÿÐÿðÿÁÇÂÉÂÊÂÌÁËÈÿÈÁÇÂÉÁÊÄËÃÌÌÿÄÂØÖÔÿÅÁØÎÌÿÂÇÁÉÁØÁÉÁÊÁËÁÉÈÿÎÁØÅÌÿÖÂØÄÔÿÂÊÄËÁÌÁËÂÌÁËÈÿÿÐÿðÿÁÇÁÉÁÊÁÉÂÊÁÌÁÊÈÿÆÁÇÂÉÂÊÁÌÁËÃÌÄËÌÿÄÁÊÁÉÂØÒÉÂÔÿÅÁÌÁØÁËÌÌÿÁÇÁÉÁÊÁØÁÊÁËÁÌÁÊÈÿÍÁØÁÉÁËÄÌÿÂÒÉÂØÁÉÁÊÄÔÿÁÊÁÉÃÊÁËÁÉÂÊÁËÃÌÁËÆÿÿÐÿðÿÁÇÂÉÂÊÁËÁÌÁËÈÿjÆØÁÊÃËÃÌÁËÁÊÁËÁÊ„ƒÌÿÂÂÉÂËÂÉÂØÂÉÁÊÁÉÂÊÁÉÄÊÁËÂÊÁËÂÊÔÿÂÁÊÁÉÁÊÂËÁØÁÌÁËÊÌÿÁÇÁÉÁÊÁØÂÊÁÌÁÊÈÿÊÁÇÁÉÁØÂÊÃËÂÌÿÂÊÁËÂÊÁËÄÊÁÉÂÊÁÉÁÊÂÉÂØÂÉÄËÂÔÿƒÅÉÁÊÁÉÁÊÂËÂÌÅØjÿÿÐÿðÿÁÇÁÉÁÊÁÉÁÊÁËÁÌÁËÈÿÂÁÇÂÉÁÊÁÉÁÊÁËÃÌÁËÁÊÁÉÁÊ„ƒ…„ÌÿÂÊÂËÁÌÂÊÁÉÁÊÂXÁËÁÊÃËÂÊÁËÁÌÃËÁÌÁËÂÌÔÿÂÉÁÊÂÉÁÊÁËÁØÂÌÁËÈÌÿÁÇÂÉÂÊÁËÁÌÁËÈÿÈÁÇÂÉÁØÁËÁÌÁËÃÌÁËÌÿÂÌÁËÁÌÃËÁÌÁËÂÊÃËÁÊÁËÂXÁÊÁÉÂÊÁÌÃËÁÊÔÿ„ƒÁÉÁÇÃÉÁÊÁËÁÊÁÌÁËÃÌÁËÂÿÿÐÿðÿÁÇÁÉÂÊÁØÂÌÁËÈÿÂÉÁÊÁÉÂÊÃÌÁËÁÊÁÉÁÊ„ƒ…„ƒÁÊÌÿÂÌÁËÁÌÁËÁÌÅËÁÌÃËÁÌÁËÁÌÁËÁÌÁËÃÌÄÊÔÿÅÉÁÊÁÉÂÊÁËÃÌÁËÆÌÿÁÇÁÉÁÊÁÉÁÊÁËÁÌÁËÈÿÆÁÇÂÉÂÊÁÌÁËÃÌÂËÁÌÁËÌÿÄÊÃÌÁËÁÌÁËÁÌÁËÁÌÃËÁÌÅËÁÌÄËÁÌÔÿÁȃ„ƒÁÉÁÇÃÉÁÊÂËÁÌÁËÃÌÿÿÐÿðÿÁÇÁÉÁÊÁËÁØÁÊÁÌÁËÈÿÁÇÁÈÁÊÁÉÁÊÃÌÁËÁÊÁÉÁÊ„ƒ…„ÄÊÌÿÄÊØÌÔÿƒÅÉÁÊÁÉÁÊÂËÃÌÁËÄÌÿÁÇÁÉÁÊÁËÁÌÁÊÁÌÁËÈÿÄÁÇÂÉÁÊÃËÃÌÁËÃÊ„ƒÌÿÚÌÁËÁÌÔÿÁÈÃɄƒÁÉÁÇÃÉÁÊÁËÃÌÁËÁÌÿÿÐÿðÿÁÇÁÉÁÊÁËÁØÂÌÁËÈÿÁÉÂÈÁÇÂÌÁËÁÊÁÉÁÊ„ƒ…„ÄÊÁÈÁÊÌÿÄÌÁËÁÉÅÊÁËÂÊÍË€Ôÿ„ƒÁÉÁÇÃÉÁÊÁËÁÊÁÌÁËÁÌÁËÁÌÁÊÂÌÿÁÇÁÉÃÊÁËÁÌÁËÈÿÂÁÉÁÊÁÌÁÊÁÉÁÊÁËÃÌÁËÁÊÁÉÁÊ„ƒ…„Ìÿ€ÍËÂÊÁËÅÊÁÉÄËÁÌÔÿÁÈÁ×ÄɄƒÁÈÁÇÃÉÁËÁÊÃËÿÿÐÿðÿÁÇÁÉÁËÁÌÁØÂÌÁËÈÿƒÁÉÁÈÁÇÁËÂÊ„ƒ…„ÄÊÁË‚XÁÈÌÿ€Á˃„Á˃„Á˃„Á˃„Á˃„Á˃„€ÂÔÿÁÇÁɄƒÁÉÁÇÃÉÁÊÄËÂÌÁÊÌÿÁÉÁÇÁÉÁÊÁËÂÌÁËÈÿÂÊÃËÄÌÁËÁÊÁÉÁÊ„ƒ…„ÂËÌÿ€„ƒÁË„ƒÁË„ƒÁË„ƒÁË„ƒÁË„ƒÁ˃ÔÿÁ×XÁ×ÁÉÁÈÂÉÁȄƒÂÉÄʃÿÿÐÿðÿÁÇÁÉÂËÁØÂÌÁËÈÿ„ƒÃÉÁÇ„ƒ…„ÄÊÁÌ‚X‚jÌÿ…Á˃„…Á˃„…Á˃„…Á˃„…Á˃„…Á˃„…€ÁÊÔÿÂÈÂɄƒÁÉÁÇÁÉÃÊÃËÂÌÁËÌÿÁÉÁÇÁÉÃÊÁÌÁËÈÿÂÉÄËÃÌÁÊÁÉÁÊ„ƒ…„ÂËÁÊÁËÌÿÁÊ€…„ƒÁË…„ƒÁË…„ƒÁË…„ƒÁË…„ƒÁË…„ƒÁË…Ôÿ‚jXÁ×ÁÊÄɄƒÁʃÁÊ„ƒÿÿÐÿðÿÂÇÁÈÁÉÁØÂËÁÌÈÿÁÇÁÈÁɃÁÇ…„ÄÊÁÌÁËÁÉ‚jÃÌÿÁÉÃËÁÊÁÈÅÊÁËÂÊËËÂÊÁÉÔÿÁÊÁ×ÁÈÃɄƒÂÉÃÊÁËÂÌÁËÌÿÁÉÃÊÁËÂÌÁËÈÿÁÉ€ÂÉÂÌÄË„ƒ…„ÂËÂÊÂÌÌÿÁÉÂÊËËÂÊÁËÅÊÁÈÁÊÃËÁÉÔÿÂjÁÇÁÊÁËÄÉ„ƒÁÊ„ƒÂÊÁËÿÿÐÿðÿÁÇÁÈÁÉÁÊjÁÊÂËÈÿµÂÈ„ƒÁÈÃÊÁÉÁÈÁËÁÉÁÈÆÌÿµÆËÁÉËÊÃËÂÊÄËÔÿÁÇXÂ×ÁÈÂÉÁȄƒÁÉÁÊÁËÁÊÁÇ€ƒÁËÌÿÁÊÀÁËÁÌÈÿÁÉÀÁÉÂËÁÊ„ƒ…„ÁÉÃÊÁË‚XÁÉÌÿÄËÂÊÃËËÊÁÉÆËÁÉÔÿÆÁÇÁÈÁËÁ×ÄÉÅʵÿÿÐÿðÿÁȃÂʃÁËÈÿÁÇÃÈÁÉÁÈÁÉÁÊÁË‚XÁÈÈÌÿÂË‚ÄXÁ×ÇËÂÌ‚ÄXÁ×ÁÌÂÍÔÿ‚jXÁ×ÁÊÄɄƒÁÊÁÇ€„ÁÊÌÿÁÊ€ƒ„ÁÌÈÿÁÈ€ÂÁÉ„ƒ…„ÂËÂÊÁÌ‚X‚jÌÿÂÍÁÌÁ×ÄX‚ÂÌÇËÁ×ÄX‚ÂËÔÿÈÁ×XÁ×ÁÉÁÈÁÉÅÊÁÈÿÿÐÿðÿÁÈ„ƒÂÊ„ƒÁËÈÿÂÁÇÁȵÁÈÁË‚X‚jÉÌÿÂjˆ‚jÉ‚jˆ‚jÄÔÿÂjÁÇÁÊÁËÂÉÁÈÁÉ„ƒÁÊÁǃÂÊÌÿÁÉ€ƒÂ„ÁËÈÿÁÈÁɃÁÉ…„ÄÊÁÌÁËÁÈ‚jÃÌÿÄj‚ˆj‚Éj‚ˆj‚ÃÔÿÉ‚jXÁ×ÁËÁʵÁÊÁÈÂÿÿÐÿðÿÁÇÁÉÅÊÁËÈÿÄÁÇÁÈÁÉ‚jËüÿÆÁÆÁÈÁËÁ×ÁÈÁÉÁÈÁÉÁÊ€ÂÊÁËÁÊÌÿÁÉÇÊÈÿÁÉÁÈÂÉ„ÁÉÁʃÂÊÂËÁÉÁÈÆüÿË‚jÁÉÁÊÁÈÄÿÿÐÿðÿÁǵÁÉÃÊ·ÁËÈÿÔüÿÈÁÈXÂ×ÂÉÃÊ4ÂÊÌÿÁÉÂÊÂ4ÃÊÈÿÂÉCÃÉÂÊÁË‚XÁÈÈüÿÔÿÿÐÿðÿÁÇÁÈÁÉÃÊÈÿÔüÿÉ‚jXÁ×ÁÈÁÊÁËÂÊÁËÌÿÄÉÄÊÈÿÂÄÉÁÊ‚X‚jÉüÿÔÿÿÐÿÿÿÁÿÔüÿË‚jÁÈÂÊÁËÃÌÿXÅÉÂËÈÿÄÃÉ‚jËüÿÔÿÿÐÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÐÿÇ×àÿÂ×ÁÿÂ×ÁÿÇ×åÿÂ×ÁÿÂ×ÄÿÈ×áÿÃ×ÄÿÄ×ÅÿÃ×ÃÿÄ×ÌÿÅ×ÃÿÄ×ÁÿÃ×ËÿÃ×ÒÿÁ×ÄÿÄ×ÿÿúÿÑÿÂ×ÃÿÁ×ÎÿÁ×ÑÿÂ×ÁÿÂ×ÂÿÂ×ÃÿÁ×åÿÂ×ÁÿÂ×ÄÿÁ×ÂÿÂ×ÂÿÁ×âÿÂ×ÃÿÂ×ÂÿÂ×ÃÿÂ×ÄÿÄ×ÌÿÆ×ÂÿÄ×ÃÿÂ×ÌÿÂ×ÑÿÂ×ÃÿÄ×ÿÿûÿÑÿÂ×ÒÿÁ×ÑÿÂ×ÁÿÂ×ÂÿÂ×éÿÂ×ÁÿÂ×ÇÿÂ×åÿÂ×ÃÿÂ×ÂÿÂ×ÂÿÂ×ÅÿÁ×ÏÿÁ×ÃÿÁ×ÃÿÁׯÿÂ×ÌÿÂ×ÐÿÃ×ÃÿÁ×ÿÿþÿÑÿÂ×ÁÿÁ×ÃÿÃ×ÃÿÄ×ÁÿÄ×ÁÿÃ×ÂÿÃ×ÁÿÂ×ËÿÂ×ÁÿÁ×ÂÿÃ×ÁÿÆ×ÂÿÃ×ÁÿÂ×ÁÿÃ×ÃÿÄ×ÂÿÄ×ÎÿÂ×ÃÿÃ×ÁÿÂ×ÁÿÃ×ÂÿÃ×ÁÿÂ×ÁÿÂ×ÍÿÂ×ÃÿÂ×ÂÿÂ×ÁÿÅ×ÃÿÄ×ÐÿÁ×ÃÿÄ×ÃÿÂ×ÁÿÈ×ÃÿÂ×ÁÿÂ×ÍÿÃ×ÃÿÄ×ÄÿÅ×ÃÿÃ×ÃÿÄ×ÂÿÄ×ÂÿÃ×ÂÿÃ×ÁÿÂ×ÂÿÅ×ÂÿÃ×ÂÿÃ×ÁÿÂ×ÁÿÄ×øÿÑÿÄ×ÂÿÂ×ÁÿÂ×ÁÿÂ×ÂÿÁ×ÂÿÂ×ÁÿÂ×ÁÿÂ×ÂÿÃ×ÁÿÁ×ËÿÄ×ÃÿÃ×ÂÿÂ×ÁÿÂ×ÂÿÃ×ÁÿÃ×ÁÿÂ×ÁÿÂ×ÂÿÁ×ÁÿÂ×ÂÿÁ×ÎÿÂ×ÄÿÃ×ÁÿÃ×ÁÿÂ×ÂÿÈ×ÍÿÂ×ÃÿÂ×ÂÿÂ×ÁÿÂ×ÂÿÂ×ÁÿÆ×ÎÿÁ×ÃÿÆ×ÂÿÂ×ÁÿÁ×ÃÿÂ×ÁÿÂ×ÂÿÅ×ÌÿÁ×ÁÿÂ×ÂÿÆ×ÄÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÂÿÁ×ÁÿÂ×ÂÿÁ×ÁÿÂ×ÁÿÂ×ÂÿÅ×ÁÿÂ×ÁÿÂ×ÂÿÂ×ÁÿÂ×ÂÿÃ×ÁÿÃ×ÂÿÁ×øÿÑÿÂ×ÁÿÁ×ÂÿÂ×ÁÿÂ×ÁÿÃ×ÄÿÂ×ÁÿÅ×ÂÿÂ×ÎÿÂ×ÁÿÁ×ÄÿÂ×ÂÿÂ×ÁÿÂ×ÂÿÂ×ÃÿÅ×ÁÿÃ×ÃÿÃ×ÐÿÂ×ÄÿÂׯÿÂ×ÂÿÂ×ÁÿÂ×ÁÿÂ×ÍÿÂ×ÄÿÅ×ÁÿÂ×ÂÿÂ×ÅÿÂ×ÎÿÁ×ÇÿÂ×ÂÿÄ×ÃÿÂ×ÁÿÂ×ÂÿÂ×ÁÿÂ×ËÿÁ×ÂÿÂׯÿÂ×ÄÿÂ×ÁÿÂ×ÄÿÂ×ÁÿÃ×ÃÿÃ×ÃÿÅ×ÂÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÂÿÅ×ÂÿÂ×ÃÿÃ×úÿÑÿÂ×ÄÿÂ×ÁÿÂ×ÃÿÃ×ÂÿÂ×ÁÿÂ×ÅÿÂ×ÎÿÂׯÿÃ×ÁÿÂ×ÁÿÂ×ÂÿÂ×ÃÿÂׯÿÃ×ÃÿÃ×ÎÿÂ×ÄÿÂ×ÄÿÄ×ÂÿÂ×ÁÿÂ×ÁÿÂ×ÄÿÃׯÿÂׯÿÂ×ÂÿÂ×ÂÿÂׯÿÁ×ÄÿÃ×ÇÿÁ×ÈÿÁ×ÂÿÂ×ÁÿÂ×ÂÿÂ×ÁÿÂ×ÂÿÂ×ÁÿÂ×ÄÿÃ×ÄÿÆ×ÆÿÁ×ÄÿÂ×ÁÿÂ×ÂÿÄ×ÃÿÃ×ÃÿÃ×ÁÿÂ×ÅÿÂ×ÁÿÂ×ÂÿÃ×ÃÿÂ×ÅÿÂ×ÅÿÃ×øÿÑÿÂ×ÄÿÂ×ÁÿÂ×ÁÿÁ×ÂÿÂ×ÂÿÂ×ÁÿÂ×ÂÿÁ×ÂÿÂ×ÎÿÂ×ÃÿÁ×ÁÿÁ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÂÿÂ×ÃÿÂ×ÂÿÁ×ÁÿÁ×ÂÿÂ×ÁÿÁ×ÂÿÂ×ÎÿÂ×ÄÿÂ×ÃÿÂ×ÁÿÂ×ÂÿÂ×ÁÿÂ×ÁÿÂ×ÍÿÂ×ÅÿÂ×ÃÿÂ×ÂÿÂ×ÁÿÂ×ÂÿÁ×ÎÿÁ×ÄÿÂ×ÂÿÁ×ÃÿÂ×ÂÿÂ×ÁÿÂ×ÁÿÂ×ÂÿÂ×ÁÿÂ×ÎÿÂ×ÂÿÂ×ÂÿÁ×ÅÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÁ×ÂÿÂ×ÁÿÁ×ÂÿÂ×ÁÿÂ×ÂÿÁ×ÂÿÂ×ÁÿÂ×ÁÿÁׯÿÂ×ÂÿÁ×ÂÿÂ×ÃÿÁ×ÂÿÂ×øÿÐÿÄ×ÄÿÃ×ÂÿÄ×ÃÿÃ×ÁÿÃ×ÂÿÄ×ÌÿÉ×ÁÿÇ×ÂÿÄ×ÃÿÃ×ÂÿÄ×ÂÿÄ×ÎÿÄ×ÂÿÄ×ÃÿÈ×ÁÿÂ×ÁÿÃ×ËÿÄ×ÂÿÃ×ÅÿÄ×ÂÿÄ×ÏÿÁ×ÄÿÄ×ÃÿÄ×ÁÿÇ×ÂÿÃ×ÁÿÃ×ÍÿÂ×ÂÿÄׯÿÄ×ÃÿÉ×ÂÿÄ×ÃÿÃ×ÂÿÃ×ÁÿÈ×ÃÿÃ×ÂÿÄ×ÂÿÄ×ùÿÿÿËÿÂ×ÿÿÿÿÌÿÂ׿ÿÂ×ãÿÆ×ÿÿËÿÿÿËÿÂ×ÿÿÿÿÌÿÂ׿ÿÂ×ãÿÁ×ÄÿÁ×ÿÿËÿÿÿÊÿÄ×ÿÿÿÿÊÿÄ×äÿÄ×ãÿÄ×ÿÿÌÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿðÿÈÈÿÔÌÿÊÁõÑÔÿÆÁõ!ÌÌÿÂj!Áõ„ÂÈÿÌ!jÆÌÿÑÁõÊÔÿÔÿÿÐÿðÿÈÈÿÃj!ÆÁÊÁËÇÌÿÊ!ˆÐÔÿÄj!ÁØjÌÌÿ„ˆjÁõÈÿÌjÁØ!ÁõÄÌÿЈ!ÊÔÿÇÂÌÆ!ÁõÃÿÿÐÿðÿÁÊÄËÁÌÈÿÄÁØ!ÁõÁÇÂÉÂÊÁËÁÌÅÌÿÊjÁ؈ÏÔÿÅÁØjÍÌÿÁÇÁÉÁØjÁÊÁÉÈÿÍjÁØÅÌÿψÁØjÊÔÿÅÂËÁÌÁËÂÌÁË!jÁØÄÿÿÐÿðÿÁÇÁÉÁÊÁÉÂÊÁÌÁÊÈÿÄÁØjÁÇÂÉÂÊÁÌÁËÁÌÁËÂÌÃÌÿÂÊɈjËÉÃÔÿÂÁÊÁÉÁØjÁËÂÌÁËÊÌÿÂÇÁɈÁØÁÊÁËÁÉÈÿÊÁÇÂÉÁÊjÁØÂËÂÌÿÃËÉjˆÊÉÂÔÿÃÂÊÁËÁÉÂÊÁËÃÌÁËjÁØÄÿÿÐÿðÿÁÇÂÉÂÊÁËÁÌÁËÈÿÃÁØjÂÉÁÊÃËÃÌÁËÃÊÂÌÿÂÊÁËÂÊÁËÂÊÁËÃjÁÊÁÉÁÊÃÉÁÊÃÉÁÊÁÉÁÇÂÔÿÂÉÁÊÁÉÁØjÂËÂÌÁËÈÌÿÁÇÁÉÁÊÁØjÁËÁÌÁÊÈÿÈÁÇÂÉÁÊÁËjÁØÃÌÁËÌÿÂÁÇÁÉÁÊÃÉÁÊÃÉÁÊÁÉÁÊÃjÁËÂÊÁËÂÊÁËÂÊÔÿÂÅÉÁÊÁÉÁÊÂËÃÌjÁØÃÿÿÐÿðÿÁÇÁÉj!Áõ„ÁÌÁËÈÿÂÁÇÁÉÁØjÁÉÁÊÁËÃÌÁËÁÊÁÉÂÊ€ÌÿÁÉÁÌÁËÁÌÂËÁÌÃËÁ؈ÃÊÃËÁÊÁÉÂËÁÊÁÉÁÊ„ÁÇÔÿÅÉÁÊÁØÂÊÁËÃÌÁËÆÌÿÁÇÁÉÁÊÁÉÂÊÁÌÁÊÈÿÆÁÇÂÉÂÊÁÌÁËÁØÂÌÂËÁÌÁËÌÿÂÇÁÊÁÉÁÊÂËÁÉÁÊÃËÃʈÁØÃËÁÌÂËÁÌÁËÂÌÔÿ€ÂÉÁÇÃÉÁÊÁËÁÊÁÌÁËjÁØÁÌÁËÂÿÿÐÿðÿÁÇ„ÁʈjÁÌÁõÁËÈÿÂÉÁÊÁÉÁØÁÊÃÌÁËÁÊÁÉÁÊ„ƒÁʃÌÿÂÉÁÌÁËÂÌÁËÂÌÁËÁÌÁËÁÌÃËÁÌÃËÁÌÄ˃„€ÔÿƒÅÉÁÊÁÉÁÊÂËÃÌÁËÄÌÿÁÇÂÉÂÊÁËÁÌÁËÈÿÄÁÇÂÉÁÊÃËÃÌÁËÃÊ„ƒÌÿÀÄËÁÌÃËÁÌÃËÁÌÁËÁÌÁËÂÌÁËÂÌÁËÃÌÔÿ€ÁɃÁÉÁÇÃÉÁÊÂËÁØÁËÃÌÿÿÐÿðÿÁÇÁÉÁÊÁØjÁÊÁÌÁËÈÿÁÇÁÈÁÊÁÉÁÊÃÌÁËÁÊÁÉÁÊ„ƒ…„Áʃ„ÌÿÂÉ×Ì„ƒ„Ôÿ„ƒÁÉÁÇÃÉÁÊÁËÁÊÁÌÁËÃÌÃÌÿÁÇÁÉÁÊÁÉÁÊÁËÁÌÁËÈÿÃÁÊÁÌÁÊÁÉÁÊÁËÃÌÁËÁÊÁÉÁÊ„ƒ…„ÌÿÃÙÌÔÿƒÁɄƒÁÉÁÇÃÉÁÊÁËÃÌÁËÁÌÿÿÐÿðÿÁÇÁÉÁʈÁØÂÌÁËÈÿÁÉÂÈÁÇÂÌÁËÁÊÁÉÁÊ„ƒ…„ÄÊÁËÁÊÌÿƒ„ÌËÂÊÁËÃÊÁËÄʄƒÔÿÁÇÁɄƒÁÉÁÇÃÉÁÊÄËÁÌÃÌÿÁÇÁÉÂÊÁËÂÌÁËÈÿÂÁÊÃËÄÌÁËÁÊÁÉÁÊ„ƒ…„ÂËÌÿƒÄÊÁËÃÊÁËÂÊÌË„ƒÔÿÂÈÄɄƒÁÈÁÇÃÉÁËÁÊÃËÿÿÐÿðÿÁÇÁÉÁËÁØjÂÌÁËÈÿƒÁÉÁÈÁÇÁËÂÊ„ƒ…„ÄÊÁÉÁË‚Á×Ìÿ„…ƒ„…Á˃„…Á̃„…Á˃„Á˃„…„ƒÔÿÂÈÂɄƒÁÉÁÇÁÉÃÊÁÌ€ƒÂÌÿÁÇÁÉÁÊÁËÁÌÁÊÁÌÁËÈÿÂÃÇÁËÃÌÁÊÁÉÁÊ„ƒ…„ÂËÁÊÁËÌÿƒ…„ƒÁË„ƒÁË…„ƒÁÌ…„ƒÁË…„ƒ…„ÔÿÁׂÁÈÁÉÁÈÂÉÁȄƒÂÉÄʃÿÿÐÿðÿÁÇÁÉÂËÃÌÁËÈÿ„ƒÃÉÁÇ„ƒ…„ÄÊÂÌÁÊ‚jÌÿÂɃ„…Á̃„…Á̃„…Á̃„…Á̃„…€ƒ„ÔÿÂÊÁÈÃɄƒÂÉÁÊÂǃ„ÂÌÿÁÇÁÊÄËÁÌÁËÈÿÂÇ€ÂÁËÁÊÁË„ƒ…„ÂËÂÊÂÌÌÿ„ƒ€…„ƒÁÌ…„ƒÁÌ…„ƒÁÌ…„ƒÁÌ…„ƒÂÌÔÿ‚Á×ÁÇÂÊÄɄƒÁʃÁÊ„ƒÿÿÐÿðÿÂÇÁÈÁÉÁÊÂËÁÌÈÿÁÇÁÈÁɃÁÇ…„ÄÊÁÌÁËÁÉ‚jÃÌÿÁÉÄÊÁËÃÊÁËÂÊÍË€„ÔÿÂÇÂÊÁÈÂÉÁÈ„ƒÁÉ€ÂÇ€ƒÃ„ÌÿÁÉÃÊÄËÈÿ€Âƒ„€ÁÊ…„ÁÉÃÊÁËÁÌÁÊÁÉÌÿ„€ÄÊÁËÃÊÁËÂÊÍËÁÌÔÿÂÁ×ÁÇÁÊÁËÄÉ„ƒÁÊ„ƒÂÊÁËÿÿÐÿðÿÁÇÁÈÁÉÃÊÂËÈÿµÂÈ„ƒÁÈÃÊÁÉÂËÁÉÁÈÆÌÿÁËÁÉÐÊÂÌÃÊÂËÂÊ4Ôÿ‚ÁׂÂÊÄÉÁÈ€ÁÇÄ„ÌÿÁÉÂÇ€ƒ„ÁËÈÿ€Âƒ„ƒÁÊÂËÂÊÁÌÁË‚Á×jÌÿCÂÊÁÉÐÊÂÌÃÊÃËÔÿÆÁÇÁÈÁËÁÈÄÉÅʵÿÿÐÿðÿÁȃÂʃÁËÈÿÁÇÃÈÁÉÁÈÂÉÂË‚Á×ÈÌÿÁÊÇÉÅËÆÌÁËÃÌÁÍÂÊÔÿÂÁ×ÁÇÁÊÁËÂÉÁȃ€ƒÅ„ÌÿÂÇ€ƒÃ„ÈÿƒĄƒÃÊÁÌÁËÁÈÁ×jÃÌÿÃÊÇÉÅËÆÌÁËÃÌÁÍÔÿÈÁׂÁÈÁËÂÉÅÊÁÈÿÿÐÿðÿÁÈ„ƒÂÊ„ƒÁËÈÿÂÁÇÁȵÁÈÂËÁÉ‚jÉÌÿ‚jˆ‚jÊÉ‚jˆ‚jÂÉÂÔÿÆÁÆÁÈÂËÂÈÁÉÂÄÁÊ4ÌÿÁÇ€ƒÄ„ÈÿCÁÈÄ„ƒÃÊÂËÁÉÁÈÆÌÿÂÂÉj‚ˆj‚ÊÉj‚ˆj‚ÂÔÿÉ‚Á×ÁÈÂËÁʵÁÊÁÈÂÿÿÐÿðÿÁÇÁÉÅÊÁËÈÿÄÁÇÁÈÁÉ‚ÌüÿÈÁÈÁÉÁÈÁÊÂɃ„ÂÊ jÌÿ€ƒÅ„ÈÿXjÂÉ„ÂÊÁËÁÊÁÉÁÈÈüÿË‚Á×ÁÉÁÊÁÈÄÿÿÐÿðÿÁǵÁÉÃÊ·ÁËÈÿÔüÿÉ‚Á×ÂÊÁÈÁÊ4 jÂÌÿƒÆ„ÈÿÂXjCÁÉÁÊÁË‚Á×jÉüÿÔÿÿÐÿðÿÁÇÁÈÁÉÃÊÈÿÔüÿË‚Á×ÁÈ jÄÌÿÁÉÇÊÈÿÄXjÁÉÁ×jËüÿÔÿÿÐÿÿÿÿÿòÿÁÉCÄÊ4ÁÊÿÿÿÿýÿÿÿÿÿòÿXj Â(ÿÿÿÿýÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÑÿÇ×àÿÂ×ÁÿÂ×ÃÿÃ×ÁÿÁ×õÿÂ×ÁÿÂ×ÌÿÃ×ÄÿÄ×ÃÿÄ×ÅÿÃ×ÌÿÄ×ÃÿÄ×ÂÿÃ×ËÿÃ×ÐÿÄ×ÂÿÄ×ÿÿÿÿÌÿÒÿÂ×ÃÿÁ×ÎÿÁ×ÑÿÂ×ÁÿÂ×ÂÿÂ×ÂÿÂ×ÜÿÁרÿÂ×ÁÿÂ×ÍÿÂ×ÃÿÂ×ÂÿÂ×ÁÿÂ×ÂÿÂ×ÃÿÂ×ÍÿÂ×ÂÿÂ×ÁÿÂ×ÂÿÂ×ÂÿÂ×ÌÿÂ×ÏÿÄ×ÂÿÂ×ÂÿÂ×ÿÿÿÿËÿÒÿÂ×ÒÿÁ×ÑÿÂ×ÁÿÂ×ÂÿÂ×ÃÿÁ×ÜÿÁרÿÂ×ÁÿÂ×ÍÿÂ×ÃÿÂ×ÂÿÂ×ÁÿÂ×ÂÿÂ×ÂÿÂ×ÎÿÂ×ÂÿÂ×ÁÿÂ×ÂÿÂ×ÂÿÂ×ÌÿÂ×ÏÿÁ×ÅÿÂ×ÂÿÂ×ÿÿÿÿËÿÒÿÂ×ÁÿÁ×ÃÿÃ×ÃÿÄ×ÁÿÄ×ÁÿÃ×ÂÿÃ×ÁÿÂ×ËÿÃ×ÄÿÃ×ÁÿÇ×ÃÿÃ×ÂÿÃ×ÁÿÉ×ÁÿÂ×ÁÿÃ×ÂÿÃ×ÁÿÂ×ÁÿÂ×ÔÿÂ×ÃÿÂ×ÂÿÂ×ÁÿÃ×ÁÿÁ×ÂÿÅ×ÌÿÃ×ÁÿÁ×ÂÿÂ×ÂÿÂ×ÂÿÂ×ÁÿÈ×ÃÿÂ×ÁÿÂ×ÌÿÄ×ÂÿÂ×ÂÿÂ×ÃÿÅ×ÃÿÃ×ÃÿÄ×ÂÿÄ×ÂÿÃ×ÂÿÃ×ÁÿÂ×ÂÿÅ×ÂÿÃ×ÂÿÃ×ÁÿÂ×ÁÿÄ×ÿÿÉÿÒÿÄ×ÂÿÂ×ÁÿÂ×ÁÿÂ×ÂÿÁ×ÂÿÂ×ÁÿÂ×ÁÿÂ×ÂÿÃ×ÁÿÁ×ÍÿÃ×ÃÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÂÿÃ×ÁÿÁ×ÁÿÂ×ÂÿÃ×ÁÿÃ×ÁÿÂ×ÂÿÈ×ÔÿÂ×ÃÿÂ×ÂÿÂ×ÂÿÄ×ÂÿÂ×ÂÿÂ×ÌÿÄ×ÂÿÂ×ÂÿÂ×ÂÿÂ×ÁÿÁ×ÃÿÂ×ÁÿÂ×ÂÿÅ×ËÿÆ×ÁÿÂ×ÂÿÂ×ÄÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÂÿÁ×ÁÿÂ×ÂÿÁ×ÁÿÂ×ÁÿÂ×ÂÿÅ×ÁÿÂ×ÁÿÂ×ÂÿÂ×ÁÿÂ×ÂÿÃ×ÁÿÃ×ÂÿÁ×ÿÿÉÿÒÿÂ×ÁÿÁ×ÂÿÂ×ÁÿÂ×ÁÿÃ×ÄÿÂ×ÁÿÅ×ÂÿÂ×ÑÿÃ×ÂÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÅ×ÂÿÂ×ÄÿÂ×ÂÿÂׯÿÂ×ÂÿÂ×ÁÿÂ×ÁÿÂ×ÔÿÂ×ÄÿÅ×ÁÿÂ×ÁÿÃ×ÁÿÂ×ÂÿÂ×ËÿÂ×ÁÿÃ×ÁÿÂ×ÂÿÂ×ÂÿÄ×ÃÿÂ×ÁÿÂ×ÂÿÂ×ÁÿÂ×ÏÿÂ×ÁÿÂ×ÂÿÂ×ÄÿÂ×ÁÿÂ×ÄÿÂ×ÁÿÃ×ÃÿÃ×ÃÿÅ×ÂÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÂÿÅ×ÂÿÂ×ÃÿÃ×ÿÿËÿÒÿÂ×ÄÿÂ×ÁÿÂ×ÃÿÃ×ÂÿÂ×ÁÿÂ×ÅÿÂ×ÎÿÁ×ÃÿÂ×ÂÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÅÿÂ×ÄÿÂ×ÂÿÂ×ÄÿÄ×ÂÿÂ×ÁÿÂ×ÁÿÂ×ËÿÃׯÿÂׯÿÂ×ÂÿÂ×ÂÿÂ×ÁÿÂ×ÂÿÂ×ÄÿÃ×ÄÿÂ×ÂÿÂ×ÁÿÂ×ÂÿÂ×ÂÿÂ×ÁÿÂ×ÂÿÂ×ÁÿÂ×ÂÿÂ×ÁÿÂ×ÄÿÃ×ÉÿÁ×ÁÿÂ×ÂÿÂ×ÄÿÂ×ÁÿÂ×ÂÿÄ×ÃÿÃ×ÃÿÃ×ÁÿÂ×ÅÿÂ×ÁÿÂ×ÂÿÃ×ÃÿÂ×ÅÿÂ×ÅÿÃ×ÿÿÉÿÒÿÂ×ÄÿÂ×ÁÿÂ×ÁÿÁ×ÂÿÂ×ÂÿÂ×ÁÿÂ×ÂÿÁ×ÂÿÂ×ÎÿÂ×ÂÿÂ×ÂÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÂÿÁ×ÂÿÂ×ÄÿÂ×ÂÿÂ×ÃÿÂ×ÁÿÂ×ÂÿÂ×ÁÿÂ×ÁÿÂ×ÔÿÂ×ÅÿÂ×ÃÿÂ×ÂÿÂ×ÁÿÂ×ÂÿÂ×ËÿÂ×ÂÿÂ×ÁÿÂ×ÂÿÂ×ÂÿÂ×ÂÿÂ×ÁÿÂ×ÁÿÂ×ÂÿÂ×ÁÿÂ×ËÿÂ×ÂÿÁ×ÂÿÂ×ÂÿÂ×ÄÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÁ×ÂÿÂ×ÁÿÁ×ÂÿÂ×ÁÿÂ×ÂÿÁ×ÂÿÂ×ÁÿÂ×ÁÿÁׯÿÂ×ÂÿÁ×ÂÿÂ×ÃÿÁ×ÂÿÂ×ÿÿÉÿÑÿÄ×ÄÿÃ×ÂÿÄ×ÃÿÃ×ÁÿÃ×ÂÿÄ×ÍÿÁ×ÁÿÃ×ÄÿÉ×ÃÿÃ×ÂÿÄ×ÃÿÇ×ÃÿÈ×ÁÿÂ×ÁÿÃ×ÒÿÄ×ÂÿÃ×ÅÿÄ×ÃÿÄ×ÍÿÄ×ÃÿÄ×ÂÿÄ×ÁÿÇ×ÂÿÃ×ÁÿÃ×ÊÿÄ×ÄÿÄ×ÅÿÄ×ÃÿÉ×ÂÿÄ×ÃÿÃ×ÂÿÃ×ÁÿÈ×ÃÿÃ×ÂÿÄ×ÂÿÄ×ÿÿÊÿÿÿÎÿÂ×ÿÿøÿÂ׿ÿÂ×ãÿÆ×ÿÿÛÿÿÿÎÿÂ×ÿÿøÿÂ׿ÿÂ×ãÿÁ×ÄÿÁ×ÿÿÛÿÿÿÍÿÄ×ÿÿöÿÄ×äÿÄ×ãÿÄ×ÿÿÜÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿÿÿÿÿÿÿÿÿÿÿøÿ ÿîîïïððññòòóóôôõõöö¨¨¨¸¸¸ÈÈÈØØØèèèüüü4ç¬ó³ÏÀ –H3Q5€ ©BàƒÇÄÆáä.@ $p³d!sý#ø~<<+"À¾xÓ ÀM›À0‡ÿêB™\€„Àt‘8K€@zŽB¦@F€˜&S `ËcbãP-`'æÓ€ø™{[”! ‘ eˆDh;¬ÏVŠEX0fKÄ9Ø-0IWfH°·ÀÎ ² 0Qˆ…){`È##x„™FòW<ñ+®ç*x™²<¹$9E[-qWW.(ÎI+6aaš@.Ây™24àóÌ ‘àƒóýxήÎÎ6޶_-ê¿ÿ"bbãþåÏ«p@át~Ñþ,/³€;€mþ¢%îh^  u÷‹f²@µ éÚWópø~<ß5°j>{‘-¨]cöK'XtÀâ÷ò»oÁÔ(€hƒáÏwÿï?ýG %€fI’q^D$.Tʳ?ÇD *°AôÁ,ÀÁÜÁ ü`6„B$ÄÂBB d€r`)¬‚B(†Í°*`/Ô@4ÀQh†“p.ÂU¸=púažÁ(¼ AÈa!ÚˆbŠX#Ž™…ø!ÁH‹$ ɈQ"K‘5H1RŠT UHò=r9‡\Fº‘;È2‚ü†¼G1”²Q=Ô µC¹¨7„F¢ Ðdt1š ›Ðr´=Œ6¡çЫhÚ>CÇ0Àè3Äl0.ÆÃB±8, “c˱"¬ «Æ°V¬»‰õcϱwEÀ 6wB aAHXLXNØH¨ $4Ú 7 „QÂ'"“¨K´&ºùÄb21‡XH,#Ö/{ˆCÄ7$‰C2'¹I±¤TÒÒFÒnR#é,©›4H#“ÉÚdk²9”, +È…ääÃä3ää!ò[ b@q¤øSâ(RÊjJåå4åe˜2AU£šRݨ¡T5ZB­¡¶R¯Q‡¨4uš9̓IK¥­¢•Óhh÷i¯ètºÝ•N—ÐWÒËéGè—èôw †ƒÇˆg(›gw¯˜L¦Ó‹ÇT071ë˜ç™™oUX*¶*|‘Ê •J•&•*/T©ª¦ªÞª UóUËT©^S}®FU3Sã© Ô–«UªPëSSg©;¨‡ªg¨oT?¤~Yý‰YÃLÃOC¤Q ±_ã¼Æ c³x,!k «†u5Ä&±ÍÙ|v*»˜ý»‹=ª©¡9C3J3W³Ró”f?ã˜qøœtN ç(§—ó~ŠÞï)â)¦4L¹1e\kª–—–X«H«Q«Gë½6®í§¦½E»YûAÇJ'\'GgÎçSÙSݧ §M=:õ®.ªk¥¡»Dw¿n§î˜ž¾^€žLo§Þy½çú}/ýTýmú§õG X³ $Û Î<Å5qo</ÇÛñQC]Ã@C¥a•a—á„‘¹Ñ<£ÕFFŒiÆ\ã$ãmÆmÆ£&&!&KMêMîšRM¹¦)¦;L;LÇÍÌÍ¢ÍÖ™5›=1×2ç›ç›×›ß·`ZxZ,¶¨¶¸eI²äZ¦Yî¶¼n…Z9Y¥XUZ]³F­­%Ö»­»§§¹N“N«žÖgðñ¶É¶©·°åØÛ®¶m¶}agbg·Å®Ã“}º}ý= ‡Ù«Z~s´r:V:ޚΜî?}Åô–é/gXÏÏØ3ã¶Ë)ÄiS›ÓGgg¹sƒóˆ‹‰K‚Ë.—>.›ÆÝȽäJtõq]ázÒõ›³›Âí¨Û¯î6îiî‡ÜŸÌ4Ÿ)žY3sÐÃÈCàQåÑ? Ÿ•0k߬~OCOgµç#/c/‘W­×°·¥wª÷aï>ö>rŸã>ã<7Þ2ÞY_Ì7À·È·ËOÃož_…ßC#ÿdÿzÿѧ€%g‰A[ûøz|!¿Ž?:Ûeö²ÙíAŒ ¹AA‚­‚åÁ­!hÈì­!÷ç˜Î‘Îi…P~èÖÐaæa‹Ã~ '…‡…W†?ŽpˆXÑ1—5wÑÜCsßDúD–DÞ›g1O9¯-J5*>ª.j<Ú7º4º?Æ.fYÌÕXXIlK9.*®6nl¾ßüíó‡ââ ã{˜/È]py¡ÎÂô…§©.,:–@LˆN8”ðA*¨Œ%òw%Ž yÂÂg"/Ñ6шØC\*NòH*Mz’쑼5y$Å3¥,幄'©¼L LÝ›:žšv m2=:½1ƒ’‘qBª!M“¶gêgæfvˬe…²þÅn‹·/•Ék³¬Y- ¶B¦èTZ(×*²geWf¿Í‰Ê9–«ž+Íí̳ÊÛ7œïŸÿíÂá’¶¥†KW-X潬j9²‰Š®Û—Ø(Üxå‡oÊ¿™Ü”´©«Ä¹dÏfÒféæÞ-ž[–ª—æ—n ÙÚ´ ßV´íõöEÛ/—Í(Û»ƒ¶C¹£¿<¸¼e§ÉÎÍ;?T¤TôTúT6îÒݵa×ønÑî{¼ö4ìÕÛ[¼÷ý>ɾÛUUMÕfÕeûIû³÷?®‰ªéø–ûm]­NmqíÇÒý#¶×¹ÔÕÒ=TRÖ+ëGǾþïw- 6 UœÆâ#pDyäé÷ ß÷ :ÚvŒ{¬áÓvg/jBšòšF›Sšû[b[ºOÌ>ÑÖêÞzüGÛœ499â?rýéü§CÏdÏ&žþ¢þË®/~øÕë×Îјѡ—ò—“¿m|¥ýêÀë¯ÛÆÂƾÉx31^ôVûíÁwÜwï£ßOä| (ÿhù±õSЧû“““ÿ˜óüc3-Û cHRMz%€ƒùÿ€éu0ê`:˜o’_ÅFPLTEÿîîïïððññòòóóôôõõöö¨¨¨¸¸¸ÈÈÈØØØèèèüüü4ÈŸ *ηÄBªŽƒ$s ‘"mK[–«Áb¼"D‹Ht$Ñià~²–¢ÌFöÏÒ—áKTnáNˆ¤u’n-a‘¢Lg:²-±ÊÿXÔ4(‹ˆÇlj‰¬E¤èì,ËtÔ²gÙòWÇÂzÃÏ ªªã€Ü–¢,;k­£–XßÐúÙKxn¬Š-"Yk­¥0 ‚ŠˆÈÕd”{yž÷zm“õ´ÖÏNÖscÕ«w7õ…aª9‚Š‘¢LçynÔ,oÙ³¾¡µãeX5‹B̲Tá¸-E:ÏgÒ(¦ÚbY Ã%Ÿ«"·‘…ˆÒ8Õ:ÕÀj®Hç3)¥”¦%–{èþ%±*rG) SÊÒ³, +&@çA@an”T£‘”²–{èþ˰"ª’Ó*¨1 Ýãr"R%€ c8õŒ)i 5’óvXî*eù‹aUTQ…‰ÖItÃqªuz†h­Eª`"`Ð5JJ#U¡F-“¥³4 C[>ÆõÓ±žcó¯"ª‰­¦g®±~cH† fŒ’FÌm±ÎÆahmY–öa®Ü¢©*ªÜCèDŸÖh-"!YW‰ÁŒ”²ìšÕºgY[Úrõ×ΕaUïÅ Ö\®Ý£¡{sTÌHU(¹X0£òÖ+øÕª\­J[–ö®ê¨ÙmÀ‚ ¨©j)KˆT1ÁL. /̨¶ë¬²ÎÕjU–¥µŸÅÙ‘-ª7­‘ˆªŠˆÐ­IÝc´ „Ë–` ¤Q7Ëeû2¬µZ­Ê2 C[~Åõ2’…Ö¶Àª÷ÜçU?Ó¸wÐîEtYÚ2 Ã:[ ¥}ºYæ-—Ö½gtTã,,Ëòž× ÀrgãÚ¼ÊiÔq Ò4@ÄL“Û¸BGe£0ÊÎBk)„¹4£²Ìäm{ýqÒ-Ë?¢pœék]ZŒÂ2}9X„®Í ¢ý–Nn+·«^§i7ëúŒÒ''Öv£pœ¯5"QÁ4Ê^Ûöeh-UÝ“½“(Ìôu¶È0 £ÍÓ¼[Æ¢Šš}ƒ|âÿ ë:éAEé"½NÇZ¢{]_œœüÖÜ$ED§ ¤2j¹XLÚ÷,DªôÉÉÉø:[d˳0 O³t[Xp‹Ðº ‚z*³å7±Éíué2]¤Zkj¬7o2 £ˆ2Rµ\.XA-Ï:DNë·=½È–YžfI¶=¬{+øÊ-‹Y‹fk³ÁŸ&GËäv‘t—ÖÍ#§õÛ‰»É(<ÍÒlóܨO˲oFí°zÑŸˆTé®OYz¶U¬&YYtçô°N×W'AÿŽõŠªî2¹]&·×Éi¤Z׋‹„D•>É–gaxš%‹,­“uCø¦à-±fòœV]€Ë—UÑzâ·¶ÔAPÕù‚'ʽ¢ª—Ö‰kõîp¤«Än“‡e–fÌÌf᛹º6-±äèOD¢¨¿ÉÒ³lÛK·L²Í vku„HA0N¿š7¯Â9#ªºÉÑ2Ñ:éÖ ‰ú )­óp“¥ÙBÂ7xÍÔ¢íæŸTˆÝÊR[þžoKˆ4a"k±´e¢“ ‚qš¥ipë^²ŒáäŠQëäö:©u5ÞåáSF8Ù›‚«…QËÖXWm‚‡¶ÜËÍ6gÃ<ÖAÔ\„X¢µ¶Ô‰NÆi–fiнÇu‹OÄpÈ^Q•è»™±"BŠÂˆ6ò€{‚]µ4ê&¤¶en‚[»'Œy»Íž•WÔ‹}Çe)8ªwq7H»ö!,¬`ÜqmÎŒÞËáž`SfŒ‹~h‡¥FHåž0¦W[mðéiE½ Ñu“FKA0NdiÐíEq÷Ðfã±@°øp¾š£ÍÜË ÀÀ¨¥ðCÛ-9º+@»'ŒRªÑV_X$ÿh¸ôF«êAÐí ¢ÈQ…ú,!`Ê8çCÎ^53cšœ®ó€¿ c„Ìaf´tTù´mƒ¯ÁÑZw%¯F[M»Hþ‘Võܵª8îŃ^÷,fãPŸ?P†ô±Sæšgœ³Ww3cÖ 67 ÌEøó·oßÊöÉú”¥d·Æ)g‹\ÉíbqÎ/’Ê¥K¯[UGq4ˆÇˆãPŸoÀbT}œ2à †qf8[ÏŒgYú)#»'l}“~P¹UJ©Q{¬íž0VÈ\IµÌå–±˜`œ_4ÅèZUƒA<&£>/ØtôàlxAÕÑG¾d`€³)»›ϲ”p/7¦hn’3ø r©T…l½tȰéU³E>S7¹Ì·»(eØà º½~/î;ªPcÁ&…=º(¥jÉ€ƒx/&M«?MÎÝMÌù•ºÉå„Ër˜‚˜Àë…mÞä•ú4#Ó¼PÅp8rm—Â!çuªf3*Ï¥Ùf*Ì@À9[s¹<=/+ SùcX çì¢âB€`¿f`8g¯î•ÎlFÈY1•ª'CnÚaÍG_¥ê¼˜0µÕý¬÷à8ãuºˆÖ©*d¡””cAQ—ð„ªB¸^Ï™á|Ý«®Ô§áyÁ¸œ#8<<<¶Æ’£ùÁÜZ—òí6ø÷büšg†5ŨC}^LXa U•!ˆu 3ª>2àKœqæzU“ªó‚MÈBÃÃÃÃË!¿hÛàçMªÐ6)ß2Ötâ"aÄŒãª{•<(pê±dm–0UGÜõz6•ÍM’=/ؤR*øüo—,Þ6Yr¦nò«Õ©šË¹¥2 VoZG pvQÿ煔à 7a‰ÍfŒ¨bÀAÀTÎÔM>;hnò& UñùóííçÃá¤m²æ#u?URl)YAÜ ó^Üc樎„À9ozÕ„Ã uðÖº„9ãœQÅÅ„œ5½ÊMÀ Pp{{ttttÛ>Yj4›¡=/Œ™¨\J)¥ÚÒãŽqÜãžÊ'f¸)š;u¯’çjx8áæq¬u sfgÕG!„t½Ê•ŽœK`F*8:z÷.Žãø¨-–¼ÚH•,”’W[ZÁLEÜSJJÖ›0Ü|f8M¯2jòD²Þ ¸+á¦ã1¢bFè&¦d.ç ŒäÑ»xP–ï 磺wÊýó¹ûóm T.eΦ½ó©ëY|É€¿M¯2êðpxñtƒ?j°ÖšTÉ) 5’&FÉx0øò¥ßÿò¥ßo‡…Røþ¾t¾Ñ¶°¦BJ%åÄÀ´w~‡BÔ½Êä·Ÿ‡Î†B€hJxºÑñî&%]€U¬©Úþ‡¹¼ºRRI)çç…ÃáÈ-mþ)¥Tƒ¦ GBֽʨ£wñ;PýþÃXB4%<½ëxœsÔTr~Þ”30P°¦j‹5ÍåèÀõ*i$¹)¶´tRI9íïb0Ìd `€aÔ¡K–Š£~ôæòáƒXu »õ©ëxœ›½”*„^p†‚Ë/— z[¬%s—Mãþ|°¥2œÔÉʧñ ¿¿É…™ôΧ`j¬á(£QÿÍã‡Ùq] 6Š˜‰ûy€&ׇóùÄl8—#׫Œ2êöóðbkXƒË ©¦ñàu§Óé\bÁ`ÚëM9L›^eT?~ú˜äàÒ•ð&Ö{!륇ɇ‡Ã 0À@í÷¿çÛ²N§Ó¹’ó«BÈá„)®ŽÞÅïþ~•Ÿô¸3¸œL÷w¿Ýõ®7Ó¦WÅñ·à.Áp„\/=n?O8°Í›l‹¥ÞÊ+#ápx…Pq< ú[;ù×éÄ—÷~{çu „Qq¿¿Q6Oßæà’ ±Ññ@¬ó ŽÞÅï ì÷¿÷«ÅN§Ó)”5<rB ýþÏ”>4â¸F ¾¼~Ýþ£A,Ö„Æ-= ¡âÁ—/ý¸ÝUþö™§”®Wq¡»Ê±êW¯ûß÷9ÊFÇFåz•Pƒyþ|âY/zÕUÝþ–Í‚Õé¼~Ýïÿ‡NMdzž ÌC{,jdT<ˆ¿•‡ïº2jð¦ßßî<7V§ï„Qqüðñ]ejdÔ·®²³X®ã¹ âñ›le”ÚÿæUv«ÓÌ…x*ßS†m®²ÓXÎ ~Óÿï¯ÒéÄ­®²ãX?÷*Ëcí–Ëcy,å±<–Ëcy,å±<–Ëcy,å±<–Ëcy,å±<–Ëcy,õ+ÿ°GÄq=ö(IEND®B`‚nml-0.4.4/regression/opengfx_trains_start.pcx0000644000567200056720000004004712643457545022632 0ustar jenkinsjenkins00000000000000 ˜Ñ,,™ÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿ×ÿÈ×ÏÿÃ××ÿÃ×ÁÿÅ×ÁÿÃ×ÁÿÂ×ÁÿÃ×ÁÿÃ×ÌÿÃ×ÇÿÄ×ÇÿÄ×ÌÿÆ×ÈÿÇÁØÁ×XˆÃÐÿÈÁØXÁ×ÁØÁ×ÂXÂ×ÁØÂÌÿÄÂØÊÐÿÃØÂˆXÈÿʈÄÐÿÂÁØÂ×ÂXÁ×ÁØÁ×XÁØÈÌÿÃÁ×ÂXÁ×ÁØÇøÿ×ÿÁ×ÂÿÂ×ÂÿÁ×ÐÿÂרÿÂ×ÃÿÂ×ÃÿÁ×ÂÿÂ×ÂÿÂ×ÂÿÂ×ËÿÅ×ÅÿÂ×ÂÿÂ×ÅÿÂ×ÂÿÂ×ËÿÁ×ÁØÁ×ÁØXÁ×XÁ×ÈÿÅÁ؈XˆÁ׈ˆÁØÐÿLjÁØÁ×ÁØÁ×ĈÁ×XÁ×ÌÿˆÂXÂ׈ÅÐÿÂØˆÂ×ÃXÈÿňÂ×ÁØXˆÂÐÿÁ×XÁ×ĈÁ×ÁØÁ×Á؈ÇÌÿÁ׈ˆÂ׈XˆÁØÅøÿÚÿÂ×ÓÿÂרÿÂ×ÃÿÂ×ÃÿÁׯÿÂ×ÂÿÂ×ÊÿÁ×ÃÿÂ×ÅÿÂ×ÂÿÂ×ÅÿÂ×ÂÿÂ×ËÿÁ×XÁ×ÁØÃˆÁØÈÿÄÂ×ÂØˆ ˆjÃØˆÐÿXˆÁØÁ׈ÁØÁ׈Á׈Á×XÂ×XÁ׈Â×ÌÿÂ×ÂØÂ׈ˆÁ×ÁØXˆÃÐÿÂØˆÁ؈ÃXÈÿÈXÁØÁ׈ˆÁØÁ×ÂjXˆÐÿÂ׈Á×XÂ×XÁ׈Á׈Á×Á؈Á×Á؈XÌÿˆÁØÂ×jˆ ˆÂØÂ×ÄøÿÚÿÂ×ÄÿÃ×ÂÿÃ×ÁÿÂ×ÂÿÄ×ÂÿÃ×ÂÿÃ×ÁÿÂ×ÌÿÂ×ÂÿÂ×ÃÿÁ×ÁÿÃ×ÂÿÂ×ÂÿÂ×ÂÿÄ×ÈÿÂ×ÅÿÃ×ÁÿÁׯÿÂ×ÂÿÂ×ËÿÂ×XÁ×ÁØÁ×XÁØÈÿˆÂXÂ×ÄØˆXÁË ÐÿˆXˆÁØÂXÁ׈XÁ×XÁ×ÂØÁ×Á؈Â×Ìÿ ÁÉÂ×ÂØÁ×ÂØXˆÃ؈ÐÿÂØˆÁ؈ÃXÈÿÁ×ÃØˆXÂØÂ׈ÂXÁË ÐÿÂ׈ÁØÁ×ÂØÁ×XÁ×XˆÁ×ÂXÁ؈XˆÌÿ!ÁÉÂØÂ×ÂØÂ×ÂXjXÂøÿÚÿÂ×ÃÿÂ×ÁÿÂ×ÂÿÅ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÂÿÃ×ÁÿÁ×ÌÿÂ×ÁÿÁ×ÁÿÂ×ÁÿÁ×ÃÿÂ×ÂÿÂ×ÂÿÂ×ÁÿÂ×ÂÿÁ×ÈÿÁ×ÇÿÄׯÿÂ×ÂÿÂ×ËÿÁ×ÈÁ×ÁØÁ×ÁØÈÿˆÂXˆjˆÂ×Á؈ÂËÁÊ ÐÿˆjˆjXˆÊ×ÂØÌÿ!ÃÉÂ׈XÁØÂÁ×ÂØÁ×ÐÿÈ×ÈÿÁ×ÂØÁ×ÂÁØXˆÂ×ÂËÁÊ(ÐÿÂØÊ׈XjˆjˆÌÿ(ÃÉÂØÂ׈jÂ×ÈXøÿÚÿÂ×ÃÿÅ×ÂÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÅ×ÂÿÂ×ÏÿÂ×ÁÿÁ×ÁÿÂ×ÁÿÁ×ÃÿÂ×ÂÿÂ×ÂÿÂ×ÁÿÃ×ÉÿÁ×ÇÿÂ×ÁÿÃ×ÅÿÂ×ÂÿÂ×ËÿÁ×XÁ×XÁ׈Á×ÁØÈÿÂ׈Xj ÂXÁØXÂËÁÊÂË ÐÿÅXÁ×ËØˆÁØÌÿ(ÂÉÁÊÂÉÃ×ÂØÄ×ÐÿÁ×XÁ×ÁØÁ×XÁØÁ×ÈÿÄ×ÂØÃ×ÅË ÐÿÁØˆËØÁ×ÅXÌÿ(ÂÉÁÊÂÉÂØÂXˆjÂXÁ؈øÿÚÿÂ×ÃÿÂ×ÅÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÅÿÂׯÿÃ×ÇÿÂ×ÃÿÂ×ÄÿÂ×ÂÿÂ×ÂÿÂ×ÃÿÃׯÿÁ×ÄÿÃ×ÁÿÂ×ÂÿÂ×ÁÿÃ×ÁÿÂ×ÂÿÂ×ËÿÂ×ÁØÁ×XÁ×XÁØÈÿÄ×XˆÂØÂËÂÊÂËÁÊ(Ðÿ(ˆÄÊÁÉÂÈÁÉÇʈ(Ìÿ(ÁÉÁÈÃÊÂÉÅ×ÁØÂ×ÐÿÂ×ÁØÁ׈ÁØÁ×ÈÿÂ×ÁØÅׯËÁÊ Ðÿ(ˆÄÊÁÉÂÈÁÉÇʈ(Ìÿ(ÁÉÁÈÁÉÂÊÂÉÂØÂXÃØˆøÿÚÿÂ×ÃÿÂ×ÂÿÁ×ÂÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÂÿÁ×ÂÿÂ×ÐÿÂ×ÃÿÂ×ÄÿÂ×ÂÿÂ×ÂÿÂ×ÁÿÁ×ÂÿÂ×ÅÿÅ×ÅÿÂ×ÂÿÂ×ÅÿÂ×ÂÿÂ×ËÿÁ×ÂØÂˆÃØÈÿÆ×ˆÁËÂÊÂËÁÊÁËÁØÁ×Ðÿ ˆÁÊÁÉÍʈ ÌÿÂØÂÊÁÈÁÉÁÊÂÉÁØÃ×ÁØÂ×ÐÿÁØÁ׈Á×XÁ׈XÈÿÂ×ÁØÃ×ÁØÁËÁÊÃËÁÊÁˈÁØÐÿ ˆÁÊÁÉÃÊÄËÆÊˆ ÌÿÁ×ÁØÂÉÁÈÁÉÁÊÂɈÁ×ˆÃØÁ×øÿÙÿÄ×ÃÿÃ×ÂÿÃ×ÁÿÃ×ÁÿÅ×ÁÿÃ×ÂÿÄ×ÏÿÂ×ÃÿÂ×ÃÿÐ×ÅÿÆ×ÆÿÄ×ÇÿÄ×ÌÿÁ×Á؈Â×ÂXjÈÿÆ× ÃËÁÊÁËÂØXjÐÿÁ× ˆÄÊÂÈÁÉÁÊÁÉÂÊÁÉÂÊÁˈ ÌÿXˆÁØÂÉÁÈÂÉ!Ã×ÁØÂ×ÐÿÁØXÁ×XÁ×ÁØÂXÈÿÂ×ÁØÃ×(ÃËÁÊÁˈÁØjXÐÿ ˆÄÊÂÈÁÉÁÊÁÉÂÊÁÉÂÊÁËÁØ Á×ÌÿjXÂØÂÉÁÈÂÉ!Â×Á؈ÁØÁ×øÿÿÿÿÿÓÿÁ׈XˆÂXÂjÈÿÆ× ÁËÁÊÁËXÁØXjÂÐÿÁ× ˆÏȈ ÌÿÂXˆÁØÂÉÁÈ(Ã×ÁØÂ×ÐÿÁØÆ×XÈÿÂ×ÁØÃ× ÁËÁÊÁˈÁØjXÂÐÿ ˆÏȈ Á×ÌÿÂjXÁØXÂÉÁÈ(Á×ÂØˆÁØÁ×øÿÿÿÿÿÓÿˆÁ×ÁØÅˆÈÿÂØiÃ× ÁˈÁØÆÐÿˆÁØˆÃØˆÃ؈ÁØÂˆÃ؈Á×ÌÿƈÁØÁÉ(Â×ÂØÂ×ÐÿÈ×ÈÿÂ×ÁØÃ× ÁˈÁØÆÐÿÁ×ˆÃØÂˆÁØˆÃØˆÃ؈Á؈ÌÿÆÂØÁÉ(Á×ÁØiÂ×ÁØøÿÿÿÿÿÓÿÃØÅˆÈÿÂÂØÂ×(ˆjXÆÐÿÂXjÂ×XjÅÂjÂ×XjÌÿÆjˆ(Ä×ÂÐÿÂ×ÁØˆÂØÂ×ÈÿÂÄ×(ÁØjXÆÐÿjXÂ×ÂjÅjXÂ×jXÂÌÿÆXjˆ(ÁØÃ×ÂøÿÿÿÿÿÓÿÂØÂˆÂXˆÈÿÄÂØjXÈðÿÈjˆÂ×ÄÐÿÂ×ÁØˆÂØÂ×ÈÿÄÂ×jXÈðÿÈXjÁ×ˆÄøÿÿÿÿÿÓÿÁØÂˆÂXÈÿÿéÿÂ×ÁØˆÂØÂ×ÿÿÿÿÒÿÿÿÿÿÓÿÁ؈ÂXĈÿÿéÿÂ×ÁØˆÂØÂ×ÿÿÿÿÒÿÛÿÁ×ÕÿÂ×ÿÿßÿÁ×ÁØÁ×FiÁ×ÁØÁ×ÿÿéÿÂ×ÁØˆÂØÂ×ÿÿÿÿÒÿÚÿÂ×ÒÿÁ×ÂÿÂ×ÿÿßÿÂ×ÁØÂ×ÁØÂ×ÿÿéÿÈØÿÿÿÿÒÿÚÿÃ×ÑÿÁ×ÿÿÿÿÿÿÿÿÿÿïÿÙÿÁ×ÁÿÂ×ÃÿÃ×ÁÿÂ×ÁÿÄ×ÁÿÇ×ÂÿÄ×ÿÿÿÿÿÿÿÿÿÿåÿÙÿÁ×ÂÿÂ×ÃÿÃ×ÁÿÃ×ÁÿÂ×ÂÿÂ×ÂÿÂ×ÁÿÂ×ÁÿÂ×ÿÿÿÿÿÿÿÿÿÿåÿÙÿÅ×ÃÿÂ×ÃÿÂ×ÅÿÂ×ÂÿÂ×ÁÿÂ×ÿÿÿÿÿÿÿÿÿÿèÿØÿÁ×ÃÿÂ×ÃÿÂ×ÃÿÂ×ÅÿÂ×ÂÿÂ×ÁÿÂ×ÿÿÿÿÿÿÿÿÿÿèÿØÿÁ×ÄÿÂ×ÂÿÂ×ÃÿÂ×ÂÿÁ×ÂÿÂ×ÂÿÂ×ÁÿÂ×ÂÿÁ×ÿÿÿÿÿÿÿÿÿÿåÿ×ÿÃ×ÂÿÈ×ÃÿÃ×ÃÿÇ×ÁÿÃ×ÿÿÿÿÿÿÿÿÿÿæÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÔÿÈÈÿÔÌÿÊLjËÔÿÔÌÿÈÈÿÔÌÿËLjÊÔÿÔÓÿÿÿÿÿÔÿÃ׈XˆXÈÿÉÁ×ÁØÉÌÿÊÂXjXjÂXËÔÿÉÁØÁ×ÉÌÿÃ׈XˆXÈÿÉÁ×ÁØÉÌÿËÂXjXjÂXÊÔÿÉÁØÁ×ÉÓÿÿÿÿÿÔÿÃ×Á؈XˆÈÿÇÁ×ÃØˆÁØÁ׈ÅÌÿÂÁ×ÂØÃˆÁØÁ×Âj j ÂjÆˆÂØÃÔÿňÁ×ÁØˆÃØÁ×ÇÌÿÃ×Á؈XˆÈÿÇÁ×ÃØˆÁØÁ׈ÅÌÿÃÁ×ÂØÃˆÁØÁ×Âj j ÂjÆˆÂØÂÔÿňÁ×ÁØˆÃØÁ×ÇÓÿÿÿÿÿÔÿÂ×Á؈Á×jXˆÈÿÅÁ×ÃØÂˆÁ؈ÁØÃXÃÌÿÂÁ؈ÅXÁØÇ XjXjÃXˆÃÔÿÃÃXÁ؈ÁØÂˆÃØÁ×ÅÌÿÂ×Á؈Á×jXˆÈÿÅÁ×ÃØÂˆÁ؈ÁØÃXÃÌÿÃÁ؈ÅXÁØÇ XjXjÃXˆÂÔÿÃÃXÁ؈ÁØÂˆÃØÁ×ÅÓÿÿÿÿÿÔÿÁØÂˆÂX ÁØÈÿÅÁÇÂˆÂØˆÂXÁØÂˆÂXÌÿˆÂXÃjXˆÁÈÁÉ„ƒÁÉÁÊjXÄjÂXÃÔÿÂØÂˆÁØÂXˆÂØÂˆÁÌÅÌÿÁØÂˆÂX ÁØÈÿÅÁÇÂˆÂØˆÂXÁØÂˆÂXÌÿÈÂXÃjXˆÁÈÁÉ„ƒÁÉÁÊjXÄjÂXÂÔÿÂØÂˆÁØÂXˆÂØÂˆÁÌÅÓÿÿÿÿÿÔÿÁØÂˆÂX XjÈÿÅÁǃÁÈÁɈÂXÂØÁËÂXÂØÌÿˆXÅjˆÁÊÁɃ„ƒÁËÁÊ jà ÂjXÃÔÿÂ×ÂØÁÉÂØÂXˆÂʃÁËÅÌÿÁØÂˆÂX XjÈÿÅÁǃÁÈÁɈÂXÂØÁËÂXÂØÌÿÈXÅjˆÁÊÁɃ„ƒÁËÁÊ jà ÂjXÂÔÿÂ×ÂØÁÉÂØÂXˆÂʃÁËÅÓÿÿÿÿÿÔÿÁØÂˆÂX XÁØÈÿÃÁ؈Á×ÁØÁÇÁÈÁÉÁ˃ÁËÂØÁÊÂÌÿÂ(ÈÊÃËÈÊ(ÃÔÿÂÁÊÂ×ÁɃÁÉÁÊÁÊÁËÁØÁ×ÂØÃÌÿÁØÂˆÂX XÁØÈÿÃÁ؈Á×ÁØÁÇÁÈÁÉÁ˃ÁËÂØÁÊÂÌÿÃ(ÈÊÃËÈÊ(ÂÔÿÂÁÊÂ×ÁɃÁÉÁÊÁÊÁËÁØÁ×ÂØÃÓÿõÿÁ×ÕÿÂ×ÍÿÃ×ÁÿÁׯÿÃ×êÿÁÇÃÈÂÉÂÊÈÿÂØÂˆÁ×ÂXÁ؃ÁÉÁÊ„ƒÁʃÁÊÌÿÂ(XÁÉ„ƒÂÊÇËÂÌ„ƒÁÌj(ÃÔÿ ÁʃÁʃÁÊÁ˃ÁØÂXÁ×ÂˆÂØÌÿÁÇÃÈÂÉÂÊÈÿÂØÂˆÁ×ÂXÁ؃ÁÉÁÊ„ƒÁʃÁÊÌÿÃ(XÁÉ„ƒÂÊÇËÂÌ„ƒÁÌj(ÂÔÿ ÁʃÁʃÁÊÁ˃ÁØÂXÁ×ÂˆÂØÓÿôÿÂ×ÒÿÁ×ÂÿÂ×ÌÿÂ×ÂÿÂ×ÇÿÂ×êÿÁȃÂɃÁÊÈÿÂÁÇÂØXˆÂXÁËÃÊÁË„ƒC(ÌÿÂ(XÁɃ„ƒÁÊÅËÁÌÁËÁÌÂ˃„ƒÁÌj(ÃÔÿ(@ƒÁÉÁÊÁÉÁÊÁÉÂØÂˆXÂØÁÊÂÌÿÁȃÂɃÁÊÈÿÂÁÇÂØXˆÂXÁËÃÊÁË„ƒC(ÌÿÃ(XÁɃ„ƒÁÊÅËÁÌÁËÁÌÂ˃„ƒÁÌj(ÂÔÿ(@ƒÁÉÁÊÁÉÁÊÁÉÂØÂˆXÂØÁÊÂÓÿôÿÃ×ÑÿÁ×ÏÿÂ×ÄÿÁ×ÇÿÂ×êÿÁ×Á؈XÂjXˆÈÿ€ÁÇÁ×ÂØÂXÂØÄÊBCÁÉXÌÿÂ(XÂ@ÂAÅBÆC4CÂ4j(XÂÔÿXÁÉ@AÂÊÁÉÁÊÂ×ÂØÂˆÁ×ÁÊ€ÂÌÿÁ×Á؈XÂjXˆÈÿ€ÁÇÁ×ÂØÂXÂØÄÊBCÁÉXÌÿÂX(XÂ@ÂAÅBÆC4CÂ4j(ÂÔÿXÁÉ@AÂÊÁÉÁÊÂ×ÂØÂˆÁ×ÁÊ€ÂÓÿóÿÁ×ÁÿÂ×ÃÿÃ×ÁÿÂ×ÁÿÄ×ÁÿÇ×ÂÿÄ×ÅÿÂ×ÇÿÃ×ÂÿÄ×ÃÿÃ×ÃÿÃ×ÃÿÄ×ÂÿÃ×ÐÿÂØˆXˆjXˆÈÿÂ(ÁÇÂ×ÁÇÂØƒÂÊÂBÂÉÁ؈ÌÿÂXÓËj XÂÔÿˆÁØÂÉ@AÂʃÂ×ÁÊÂ×ÁÊ ÌÿÂØˆXˆjXˆÈÿÂ(ÁÇÂ×ÁÇÂØƒÂÊÂBÂÉÁ؈ÌÿÂXXÓËj ÂÔÿˆÁØÂÉ@AÂʃÂ×ÁÊÂ×ÁÊ ÓÿóÿÁ×ÂÿÂ×ÃÿÃ×ÁÿÃ×ÁÿÂ×ÂÿÂ×ÂÿÂ×ÁÿÂ×ÁÿÂ×ÅÿÂׯÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÂÿÁ×ÁÿÂ×ÁÿÂ×ÏÿÁ׈XÄjˆÈÿ( Â(Á×€ÁÇÁÊ„ƒÂAÂɈÁØÃÌÿÂ ÕØ ÃÔÿÃÁ؈ÂÉ@AƒÁÉÁÊ€Á× ÌÿÁ׈XÄjˆÈÿ( Â(Á×€ÁÇÁÊ„ƒÂAÂɈÁØÃÌÿÃ ÕØ ÂÔÿÃÁ؈ÂÉ@AƒÁÉÁÊ€Á× ÓÿóÿÅ×ÃÿÂ×ÃÿÂ×ÅÿÂ×ÂÿÂ×ÁÿÂ×ÈÿÂ×ÉÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÃ×ÃÿÅ×ÏÿÁ׈XjˆjXˆÈÿˆ Â(ÁÇÁÊÂAÂÉÁׯÌÿÂÁ×XjÁ×XjÁ×ÉÁØXjÁ×XjÁ×ÃÔÿÆÁ×ÂÉ@AÁÉÁÊ  ˆÌÿÁ׈XjˆjXˆÈÿˆ Â(ÁÇÁÊÂAÂÉÁׯÌÿÃÁ×XjÁ×XjÁ×ÉÁØXjÁ×XjÁ×ÂÔÿÆÁ×ÂÉ@AÁÉÁÊ  ˆÓÿòÿÁ×ÃÿÂ×ÃÿÂ×ÃÿÂ×ÅÿÂ×ÂÿÂ×ÁÿÂ×ÈÿÂ×ÇÿÄ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÃÿÃ×ÁÿÂ×ÒÿXÆ×XÈÿÂØÂˆ (ÁÇ@ÂÉÁ×ÈüÿÈÁ×ÂÉ@ÁÊ ÂˆÂØÌÿXÆ×XÈÿÂØÂˆ (ÁÇ@ÂÉÁ×ÈüÿÈÁ×ÂÉ@ÁÊ ÂˆÂØÓÿòÿÁ×ÄÿÂ×ÂÿÂ×ÃÿÂ×ÂÿÁ×ÂÿÂ×ÂÿÂ×ÁÿÂ×ÂÿÁׯÿÂ×ÃÿÁ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÁ×ÂÿÂ×ÁÿÂ×ÂÿÁ×ÏÿÁÇ€ÁÇÁ×ÁØÁÇÁÉÈÿÃÂØÂˆXÁɈÁØÉüÿÉÁ؈ÁÉXÂˆÂØÃÌÿÁÇ€ÁÇÁ×ÁØÁÇÁÉÈÿÃÂØÂˆXÁɈÁØÉüÿÉÁ؈ÁÉXÂˆÂØÃÓÿñÿÃ×ÂÿÈ×ÃÿÃ×ÃÿÇ×ÁÿÃ×ÈÿÄ×ÃÿÆ×ÁÿÂ×ÃÿÃ×ÃÿÃ×ÂÿÄ×ÃÿÃ×ÐÿÃÇÁ×ÁØÁÈÂÉÈÿÅÂØˆÁØËüÿËÁØˆÂØÅÌÿÃÇÁ×ÁØÁÈÂÉÈÿÅÂØˆÁØËüÿËÁØˆÂØÅÓÿÿÿÿÿÔÿ( (Á×ÁØÂ(ÿÿùÿ( (Á×ÁØÂ(ÿÿÿÿÁÿÿÿÿÿÔÿ ( Âj (ÿÿùÿ ( Âj (ÿÿÿÿÁÿÿÿÿÿÔÿÈØÿÿùÿÈØÿÿÿÿÁÿÿÿÿÿÔÿÃXÂØÃXÿÿùÿÃXÂØÃXÿÿÿÿÁÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÐÿÁÆÄÈÁÇÈÿÍÁÇÂÉÁÈÄÊÿÎÂÇ( ÁÇÁÉÁÇÁÉÁÇÁÉÁÇÁÉÁÇÅÑÿÄÁÉÐËÿÂÈÄÉÈÿÐÁÆÄËÿÅÉÇ (ÂÇÎÐÿÄÁÆÁÇÂÈÍÖÿÿÿÿÿÐÿÁÆÁÈÄÊÁÉÁÈÈÿËÁÇÁÉÂÊÂËÁÊÁÉÂÊÿÍÄÉÂÊÅËÁÊÁÉÄÑÿÂÄÊÁÉÎËÿÁÇÁÉÂÊÁËÂÊÁÉÈÿÎÁÆÁÉÂÊÁÉÂËÿÄÁØÁÇÁÈÁÉÃÊÃËÂÉÂËÍÐÿÂÁÇÁÉÄÊÁÉÁÈËÖÿÿÿÿÿÐÿÁÆÄÊÁËÁÊÁÉÈÿÉÁLjÁÊÄËÂÊÁÌÊÿÃÆÆÇÂÈÁÇÁÉÁË XÅËÂÌÁËÁÊÂËÂÑÿÁÊÄËÂÊÁÉÌËÿÁÇÁÉÁÊÁËÁÌÁËÂÊÈÿÌÁÆÁÉÂÊÂËÂÊËÿÂÂÉÁÈÁÊÄËÃÌ (ÁÌÁÍÂÈÄÆÆÇÐÿÁÆÁÈÁÊÃËÁʈÁÈÉÖÿÍÿÈ×äÿÂ×ÁÿÂ×ÁÿÈ×ÌÿÃ×ËÿÂ×ÁÿÂ×èÿÁÇÁËÂÊÂËÂÊÈÿÇÁÇÂʈÁÌÁËÁÊÁÌÁÌ ÊÿˆÁÊÅËÁÊÁËÁÊÂÉÁÈÁÉÃÊÂËÁÌÁËÁÌÄËÂ̾ÑÿÂÉÂÊÃËÂÊÁÉÁÈÂÉÁÈÇËÿÁÇÁÉÁÊÁËÁÌÁËÁÊÁÉÈÿÇÂÈÁÉÁÈÂÊÃËÃÌÁËÁÊËÿ¾ÂÊÃÉÂÊÇËÁÊÁÈÂÉÆËÁÊÁËÁʈÐÿˆÂÈÂˈÂËÁÈÇÖÿÍÿÁ×ÂÿÂ×ÂÿÁ×äÿÂ×ÁÿÂ×ÁÿÁ×ÂÿÂ×ÂÿÁ×ÍÿÂ×ËÿÂ×ÁÿÂ×èÿÁÈÁËÁÌÁÊÈÿÇÁÇÁÊÃËÁÌÂÍ ÊÿˆÁËÂÌÃÍÁÌÂËÁÊÂÉÁÊÁËÁÌÁÌÁÍÁÍÃÂÍÁõÑÿÃÉÃÊÁÉÁÈÁÉÂÊÁÉÁÈÅËÿÁÈÁÉÁÊÁËÁÌÁËÂÊÈÿÅÁÈÁÉÁËÁÉÂÊÁËÂÌÁÍÁÌÂËÁÊËÿX!ÂÊÂÁÊÁËÁÌÁÌÁÍÁÍÂÉÁÊÃÌÃÍÁÌÁÍÁ̈ÐÿÁØÁÈÂÊÂËÁËÁÊÇÖÿÐÿÂ×çÿÂ×ÁÿÂ×ÄÿÂ×ÐÿÂ×ËÿÂ×ÁÿÂ×èÿÁÉÁÊÁËÁÌÁËÁÉÈÿÅÁÆÃÉÁÈÁÊÁÊÁËÂÍ XÊÿÁØÁÊÃËÁÊÁËÁÊÁËÃÊÁËÁÊÁËÁÊÁÌÁÊÁÌÁÍÁÍÁÍÂÁ̾jÑÿÁõÄÉÁÆÁÈÁÊÂËÂÊÁÉÁÈÃËÿÁÈÁÉÁÊÂÌÂÊÁÉÈÿÃÁÆÂÉÂÊÁËÂÉÁÊÁËÂÌÁËÁÊÂËÿj¾ÂÁÉÁËÁËÁÌÆÍÁËÂÊÈËÁÊÁØÐÿÁØÂ!ÁÉÁÊÂËÁËÁÊÂÉÁÊÁÉÅÖÿÐÿÂ×ÃÿÃ×ÁÿÂ×ÁÿÃ×ÁÿÅ×ÁÿÂ×ÂÿÃ×ÂÿÃ×ÁÿÂ×ÍÿÂ×ÃÿÃ×ÁÿÂ×ÁÿÃ×ÁÿÆ×ÃÿÃ×ðÿÁÉÁÊÄËÁÊÁÌÈÿÃÁÆÁÉÂÊÂËÁÊÂÉÂÌ  ÁØÊÿ ÂÍÂÌÂÍÁÌÅÍÁÌÁÍÂÌÈÍÁÌÂÍ (XÑÿÂ((ÁÉÁÆÂÁÈÁÊÂËÁÌÂËÂÊÁÈÂËÿÁÇÁÉÃÊÁÉÂÊÈÿÃÁÈÁÉÂÊÂËÁÌÁËÁÊÁÌÁÍÂËËÿÁØ!ÂõÃËÇÌÁÍÂÊÁÌÁÊÁËÁÌÁÊÁËÁÌÁÊÁËÁÌÁÊÁËÂÌ ÐÿÁØÂ Â!ÁÈÁÊÁËÁÊÁÉÁÊÂËÂÊÁÉÃÖÿÐÿÂ×ÄÿÂ×ÁÿÂ×ÂÿÃ×ÁÿÁ×ÁÿÅ×ÁÿÂ×ÁÿÂ×ÂÿÃ×ÁÿÁ×ÍÿÂ×ÄÿÂ×ÁÿÂ×ÂÿÃ×ÁÿÃ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ïÿÂÁÊÁÊÈÿÁÆÁÉÂÊÂËÂÌÁÍÂËÁÌ ÁÉÊÿj É ÂÁõˆÑÿXÂ!(ÁÊÁÉÃÊÂËÁÌÁÌÂËÿÁÆÁÈÆÉÈÿÂÁÉÂÊÁÌÁËÁÊÁËÁÍÁÌ (ËÿÁ×(ÂÊ  jÐÿÁÈ Â!ÂÈÃÉÂÊÂËÁÊÂÉÖÿÐÿÂ×ÄÿÂ×ÁÿÂ×ÂÿÂ×ÄÿÂ×ÁÿÂ×ÁÿÅ×ÂÿÂ×ÐÿÂ×ÄÿÂ×ÁÿÂ×ÂÿÂ×ÃÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ïÿÁËÁÈÂÊÁÉÁÌÈÿÂÇÁÈÂÊÁËÁÌÁÍÂÌÁËÁÊ  (Á×ÁÇÊÿX   Ê  (ÁØÑÿÁ×X !(( ÁÉÁÊÃÊÁËÁÌ(ËÿÁÆÁÊÁËÁÉÈÿÂËÁËÂÊÂÍ  !Á×ËÿÁ× ÁõÊ    XÐÿÁÆÁ×X jÂÁÈÃÉÂÊÂÉÃÊÖÿÐÿÂ×ÄÿÂ×ÁÿÂ×ÂÿÂ×ÄÿÂ×ÁÿÂ×ÁÿÂ×ÅÿÂ×ÐÿÂ×ÄÿÂ×ÁÿÂ×ÂÿÂ×ÃÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ïÿÁÆÁÇÁÊÂËÃÊÈÿÂÁØÂÈÂËÁÌÁËÁÊ Áõ (Á×ÃÊÿÁØ Í (!É Â XÁ×ÑÿÂXj !Â(ÂõÁÈÁÊÃÌ¿jËÿÁÇÁÊÁËÁÌÁËÁÊÈÿˆ¿(ÁËÁÌÂÂÍ !jÂËÿÂ×!ÂõÊ !Î ÁØÐÿÃÁ×XÂj !ÁÉÁÈÂÉÁÊÂØÂÊÖÿÐÿÂ×ÄÿÂ×ÁÿÂ×ÂÿÂ×ÄÿÂ×ÁÿÂ×ÁÿÂ×ÂÿÁ×ÂÿÂ×ÐÿÂ×ÄÿÂ×ÁÿÂ×ÂÿÂ×ÃÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ïÿÁÆÁÉÁÊÁËÁÌÁËÁÊÁÉÈÿÂ×ÁØÂÉÁËÁÊ Áõ! ÅÊÿÁØ(Ì (!Ä ÂõÃ(Áõ(ÂÁÆÑÿÄXjÂ! ÁõÁõÂÊÁÌ ¿j(!ËÿÁÈÁÉÁÊÁËÁÌÁËÁÊÁÉÈÿˆÁØX¿ÁõÁÌÁÍÂÁõ (jÄËÿÁÆ! (Ã!Â(Äõ !Íõ(ÁØÐÿÅÂXj Â!(ÂÉÁÊÂØÁÈÖÿÏÿÄ×ÄÿÉ×ÂÿÃ×ÁÿÃ×ÁÿÃ×ÂÿÄ×ÎÿÄ×ÄÿÉ×ÂÿÁ×ÁÿÂ×ÃÿÃ×ðÿÁÆÁÉÁÊÁËÁÌÁËÂÊÈÿÁØÃ×ÁÇ ( ÇÊÿjXÅ È!ÂjÄ!ˆXÃ׈Á×ÁÈÁÆÑÿÆXÂjÂ(Áõ j X jËÿÁÉÂÊÃËÂÊÈÿÁ؈Ãj  Â(jÆËÿÁÆÁÈÁ׈Ã×ÂˆÄ ÂjÍ XjÐÿÇXj !((ÁÊÁÉÂØÁ×ÁØÖÿÿÿÿÿÐÿÁÆÁÉÁÊÂÌÂÊÁÉÈÿÄ×ÁØ Áõ ÉûÿÈX !(Áõ ÂXjXÁØËÿÁÉÅÊÁËÁÉÈÿÁ×ÁØÃˆ  (jÈûÿÉXj !(ÁÊÁØÃ×ÖÿÿÿÿÿÐÿÁÆÁÈÂÊÂËÂÊÈÿÂÃ×ÁÆ ! ËûÿÊÁ؈X(ÂõÂØˆÁØÁ×ËÿÁÊÁËÁÌ ÁËÈÿÂ×ÃØÁõ XÁØÊûÿËXj !ÁÈÁØÂ×ÂÖÿÿÿÿÿÐÿÁÇÂÈÂÊÂÉÁÊÈÿÄÂ׈Á×ÍûÿÌÁ×ÁÆÁȈÄ×ËÿÂÁËÁÌ ÈÿÄ×ÁÉÁÈÁ×ÌûÿÍXˆÂ×ÄÖÿÿÿÿÿÐÿÁÈÂÉÂÊÁËÁÊÁÉÿÿÙÿÏÁÆÅËÿÁÈÁÉÂËÁÌÁËÁÊÁÉÈÿÅÁÈÏÿÿçÿÿÿÿÿÐÿÁÉÁÊÁËÂ×ÁÊÁËÁÊÿÿùÿÁÈÁÉÁËÃÌÁÊÁËÿÿÿÿÅÿÿÿÿÿÐÿÁÊÁÉÁ×ÁØÂ×ÁÊÿÿùÿÁÈ(  ÁËÿÿÿÿÅÿÿÿÿÿÐÿÂÉÁ×ÁØÂ×ÁÈÿÿùÿ j¿X!¿ ÿÿÿÿÅÿÿÿÿÿÐÿÂÇÁ×ÁØÂ×ÁØÁÆÿÿùÿ ˆj j ÿÿÿÿÅÿÿÿÿÿÐÿÈ×ÿÿùÿjÁ؈XÂjˆÿÿÿÿÅÿÿÿÿÿÐÿÈ×ÿÿùÿˆÁ×Á؈XˆÁØjÿÿÿÿÅÿÿÿÿÿÿÿÿÿÒÿÁÇÁ×ÂØˆÁØÁ×ÁÇÿÿÿÿÅÿÿÿÿÿÿÿÿÿÒÿÂÄ×ÂÿÿÿÿÅÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÐÿÂÈÄÉÈÿÏÁÆÄÌÿÂÆËÇÂÆÅÇÁÆÆÇÔÿÄÁÉÏÌÿÂÈÄÉÈÿÏÁÆÄÌÿÂÆËÇÂÆÅÇÁÆÆÇÔÿÄÁÉÏ×ÿÿÿÿÿÐÿÁÇÁÉÂÊÁËÂÊÁÉÈÿÍÁÆÁÉÂÊÁÉÂÌÿÂÊÖËÁÊÁËÁÊÁØÔÿÂÄÊÁÉÍÌÿÁÇÁÉÂÊÁËÂÊÁÉÈÿÍÁÆÁÉÂÊÁÉÂÌÿÂÊÖËÁÊÁËÁÊÁØÔÿÂÄÊÁÉÍ×ÿÿÿÿÿÐÿÁÇÁÉÁÊÁËÁÌÁËÂÊÈÿËÁÆÁÉÂÊÂËÂÊÌÿÁÊÁËÁÌÃÍÃÌÃÍÃÌÁÍÂÌÁÍÂÌÃÍÂÌÁËÁØÔÿÁÊÄËÂÊÁÉËÌÿÁÇÁÉÁÊÁËÁÌÁËÂÊÈÿËÁÆÁÉÂÊÂËÂÊÌÿÁÊÁËÁÌÃÍÃÌÃÍÃÌÁÍÂÌÁÍÂÌÃÍÂÌÁËÁØÔÿÁÊÄËÂÊÁÉË×ÿÿÿÿÿÐÿÁÆÁÉÁÊÁËÁÌÁËÁÊÁÉÈÿÉÁÇÁÉÂÊÂËÃÌÁËÁÊÌÿÁÊÃËÁÊÕËÁʈÔÿÂÉÂÊÄËÂÊÁÉÉÌÿÁÆÁÉÁÊÁËÁÌÁËÁÊÁÉÈÿÉÁÇÁÉÂÊÂËÃÌÁËÁÊÌÿÁÊÃËÁÊÕËÁʈÔÿÂÉÂÊÄËÂÊÁÉÉ×ÿÿÿÿÿÐÿÁÆÁÉÁÊÁËÁÌÁËÂÊÈÿÇÁÇÁÉÂÊÂËÂÌÁÍÁÌÂËÁÊÌÿÂÍÂÌÂÍÁËÁÌÁÊÁËÁÌÁÊÁËÂÊÁËÁÌÁÊÁËÁÌÁÊÁËÁÌÁÊÁËÂÌ ÔÿÃÉÂÊÄËÂÊÁÉÇÌÿÁÆÁÉÁÊÁËÁÌÁËÂÊÈÿÇÁÇÁÉÂÊÂËÂÌÁÍÁÌÂËÁÊÌÿÂÍÂÌÂÍÁËÁÌÁÊÁËÁÌÁÊÁËÂÊÁËÁÌÁÊÁËÁÌÁÊÁËÁÌÁÊÁËÂÌ ÔÿÃÉÂÊÄËÂÊÁÉÇ×ÿÿÿÿÿÐÿÁÆÁÉÁÊÁËÁÌÁËÁÊÁÉÈÿÅÁÆÁÉÂÊÂËÂÌÁÍÂÌÁËÁÊÂÌÿ      !ÔÿÁõÃÉÂÊÄËÂÊÁÉÅÌÿÁÆÁÉÁÊÁËÁÌÁËÁÊÁÉÈÿÅÁÆÁÉÂÊÂËÂÌÁÍÂÌÁËÁÊÂÌÿ      !ÔÿÁõÃÉÂÊÄËÂÊÁÉÅ×ÿÍÿÈ×ÌÿÃ×ÐÿÃ×ÁÿÁ×ÓÿÃ×ÿÿÁÆÁÉÁÊÁËÁÌÁËÂÊÈÿÃÁÆÁÉÂÊÂËÂÌÁÍÂÌÁËÁÊÌÿ        ÔÿÂ((ÃÉÂÊÄËÂÊÁÉÃÌÿÁÆÁÉÁÊÁËÁÌÁËÂÊÈÿÃÁÆÁÉÂÊÂËÂÌÁÍÂÌÁËÁÊÌÿ        ÔÿÂ((ÃÉÂÊÄËÂÊÁÉÃ×ÿÍÿÁ×ÂÿÂ×ÂÿÁ×ÍÿÂ×ÏÿÂ×ÂÿÂ×ÔÿÂ×ÿÿÁÆÁÉÁÊÁËÁÌÁËÁÊÁÉÈÿÁÆÁÉÂÊÂËÂÌÁÍÂÌÁËÁÊ  (ÌÿÄ É (!É Ã ˆÔÿXÂ!((ÁÈÂÉÃÊÃËÁÊÂÉÌÿÁÆÁÉÁÊÁËÁÌÁËÁÊÁÉÈÿÁÆÁÉÂÊÂËÂÌÁÍÂÌÁËÁÊ  (ÌÿÄ É (!É Ã ˆÔÿXÂ!((ÁÈÂÉÃÊÃËÁÊÂÉ×ÿÐÿÂ×ÐÿÂ×ÎÿÂ×ÄÿÁ×ÔÿÂ×ÿÿÁÆÁÉÁÊÁËÁÌÁËÂÊÈÿÂÇÁÈÂÊÁËÁÌÁÍÂÌÁËÁÊ !Á×Ìÿ(!Ëõ! Êõ!(ˆÔÿÁ×X !(!ÄÉÂÊÂÉÃÊÌÿÁÆÁÉÁÊÁËÁÌÁËÂÊÈÿÂÇÁÈÂÊÁËÁÌÁÍÂÌÁËÁÊ !Á×Ìÿ(!Ëõ! Êõ!(ˆÔÿÁ×X !(!ÄÉÂÊÂÉÃÊ×ÿÐÿÂ×ÃÿÃ×ÁÿÂ×ÁÿÃ×ÁÿÆ×ÃÿÃׯÿÂ×ÇÿÃ×ÃÿÃ×ÃÿÄ×ÂÿÂ×ÁÿÂ×üÿÁÆÁÉÁÊÁËÁÌÁËÁÊÁÉÈÿÂÁØÂÈÂËÁÌÁËÁÊ  !jÂÌÿÁØÁ×ÂXÔ XÁ×ÁØjÔÿÂXjà j(ÁÉÁÈÂÉÁÊÂØÂÊÌÿÁÆÁÉÁÊÁËÁÌÁËÁÊÁÉÈÿÂÁØÂÈÂËÁÌÁËÁÊ  !jÂÌÿÁØÁ×ÂXÔ XÁ×ÁØjÔÿÂXjà j(ÁÉÁÈÂÉÁÊÂØÂÊ×ÿÐÿÂ×ÄÿÂ×ÁÿÂ×ÂÿÃ×ÁÿÃ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÅÿÂׯÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÂÿÅ×üÿÁÆÁÉÁÊÁËÁÌÁËÂÊÈÿÂ×ÁØÂÉÁËÁÊ Áõ!jÄüÿÄXÃj!((ÂÉÁÊÂØÁÈÌÿÁÆÁÉÁÊÁËÁÌÁËÂÊÈÿÂ×ÁØÂÉÁËÁÊ Áõ!jÄüÿÄXÃj!((ÂÉÁÊÂØÁÈ×ÿÐÿÂ×ÄÿÂ×ÁÿÂ×ÂÿÂ×ÃÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÅÿÂׯÿÂ×ÁÿÂ×ÄÿÂ×ÁÿÂ×ÅÿÂ×ÁÿÂ×üÿÁÆÁÉÁÊÁËÁÌÁËÁÊÁÉÈÿÁØÃ×ÁÇ jÆüÿÆÂX !Ã(ÁÊÁÉÂØÁ×ÁØÌÿÁÆÁÉÁÊÁËÁÌÁËÁÊÁÉÈÿÁØÃ×ÁÇ jÆüÿÆÂX !Ã(ÁÊÁÉÂØÁ×ÁØ×ÿÐÿÂ×ÄÿÂ×ÁÿÂ×ÂÿÂ×ÃÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÅÿÂׯÿÂ×ÁÿÂ×ÂÿÄ×ÁÿÂ×ÅÿÂ×ÁÿÂ×üÿÁÆÁÉÁÊÁËÁÌÁËÂÊÈÿÄ×ÁØ !jÈüÿÈXj (ÁÊÁØÃ×ÌÿÁÆÁÉÁÊÁËÁÌÁËÂÊÈÿÄ×ÁØ !jÈüÿÈXj (ÁÊÁØÃ××ÿÐÿÂ×ÄÿÂ×ÁÿÂ×ÂÿÂ×ÃÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂׯÿÂ×ÃÿÁ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÂÿÁ×ÂÿÂ×ÁÿÂ×üÿÁÇÂÈÂÊÂÉÁÊÈÿÂÃ×ÁÆ ! ÊüÿÊXj !ÁÆÃ×ÌÿÁÇÂÈÂÊÂÉÁÊÈÿÂÃ×ÁÆ ! ÊüÿÊXj !ÁÆÃ××ÿÏÿÄ×ÄÿÉ×ÂÿÁ×ÁÿÂ×ÃÿÃ×ÈÿÄ×ÃÿÃ×ÃÿÅ×ÁÿÃ×ÂÿÃ×ÁÿÃ×ûÿÁÈÂÉÂÊÁËÁÊÁÉÈÿÄÂ׈Á×ÌüÿÌÁ×ÂØÂ×ÃÌÿÁÈÂÉÂÊÁËÁÊÁÉÈÿÄÂ׈Á×ÌüÿÌÁ×ÂØÂ×Ã×ÿÿÿÿÿÐÿÁÉÁÊÁËÂ×ÁÊÁËÁÊÿÿÙÿÎÁ×ÅÌÿÁÉÁÊÁËÂ×ÁÊÁËÁÊÿÿÙÿÎÁ×Å×ÿÿÿÿÿÐÿÁÊÁÉÁ×ÁØÂ×ÁÊÿÿùÿÁÊÁÉÁ×ÁØÂ×ÁÊÿÿÿÿÅÿÿÿÿÿÐÿÂÉÁ×ÁØÂ×ÁÈÿÿùÿÂÉÁ×ÁØÂ×ÁÈÿÿÿÿÅÿÿÿÿÿÐÿÂÇÁ×ÁØÂ×ÁØÁÆÿÿùÿÂÇÁ×ÁØÂ×ÁØÁÆÿÿÿÿÅÿÿÿÿÿÐÿÈ×ÿÿùÿÈ×ÿÿÿÿÅÿÿÿÿÿÐÿÈ×ÿÿùÿÈ×ÿÿÿÿÅÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÐÿÂÈÄÉÈÿÏÁÆÄÌÿÂÆËÇÂÆÅÇÁÆÆÇÔÿÄÁÉÏÌÿÂÈÄÉÈÿÏÁÆÄÌÿÂÆËÇÂÆÅÇÁÆÆÇÔÿÄÁÉÏ×ÿÿÿÿÿÐÿÁÇÁÉÂÊÁËÂÊÁÉÈÿÍÁÆÁÉÂÊÁÉÂÌÿÂÊÖËÁÊÁËÁÊÁØÔÿÂÄÊÁÉÍÌÿÁÇÁÉÂÊÁËÂÊÁÉÈÿÍÁÆÁÉÂÊÁÉÂÌÿÂÊÖËÁÊÁËÁÊÁØÔÿÂÄÊÁÉÍ×ÿÿÿÿÿÐÿÁÇÁÉÁÊÁËÁÌÁËÂÊÈÿËÁÆÁÉÂÊÂËÂÊÌÿÁÊÁËÁÌÃÍÃÌÃÍÃÌÁÍÂÌÁÍÂÌÃÍÂÌÁËÁØÔÿÁÊÄËÂÊÁÉËÌÿÁÇÁÉÁÊÁËÁÌÁËÂÊÈÿËÁÆÁÉÂÊÂËÂÊÌÿÁÊÁËÁÌÃÍÃÌÃÍÃÌÁÍÂÌÁÍÂÌÃÍÂÌÁËÁØÔÿÁÊÄËÂÊÁÉË×ÿÿÿÿÿÐÿÁÆÁÉÁÊÁËÁÌÁËÁÊÁÉÈÿÉÁÇÁÉÂÊÂËÃÌÁËÁÊÌÿÁÊÃËÁÊÕËÁʈÔÿÂÉÂÊÄËÂÊÁÉÉÌÿÁÆÁÉÁÊÁËÁÌÁËÁÊÁÉÈÿÉÁÇÁÉÂÊÂËÃÌÁËÁÊÌÿÁÊÃËÁÊÕËÁʈÔÿÂÉÂÊÄËÂÊÁÉÉ×ÿÿÿÿÿÐÿÁÆÁÉÁÊÁËÁÌÁËÂÊÈÿÇÁÇÁÉÂÊÂËÂÌÁÍÁÌÂËÁÊÌÿÅÌÁËÁÌÃËÁÌÅÊÁÌÈËÂÌ ÔÿÃÉÂÊÄËÂÊÁÉÇÌÿÁÆÁÉÁÊÁËÁÌÁËÂÊÈÿÇÁÇÁÉÂÊÂËÂÌÁÍÁÌÂËÁÊÌÿÅÌÁËÁÌÃËÁÌÅÊÁÌÈËÂÌ ÔÿÃÉÂÊÄËÂÊÁÉÇ×ÿÿÿÿÿÐÿÁÆÁÉÁÊÁËÁÌÁËÁÊÁÉÈÿÅÁÆÁÉÂÊÂËÂÌÁÍÂÌÁËÁÊÌÿà Šà  ÂÃ!ÔÿÂõÃÉÂÊÄËÂÊÁÉÅÌÿÁÆÁÉÁÊÁËÁÌÁËÁÊÁÉÈÿÅÁÆÁÉÂÊÂËÂÌÁÍÂÌÁËÁÊÌÿà Šà  ÂÃ!ÔÿÂõÃÉÂÊÄËÂÊÁÉÅ×ÿÿÿÿÿÐÿÁÆÁÉÁÊÁËÁÌÁËÂÊÈÿÃÁÆÁÉÂÊÂËÂÌÁÍÂÌÁËÁÊÃÌÿÂ Ä ÁõÅ ÁõÅ Â Ã ÔÿÂ(Áõ(ÃÉÂÊÄËÂÊÁÉÃÌÿÁÆÁÉÁÊÁËÁÌÁËÂÊÈÿÃÁÆÁÉÂÊÂËÂÌÁÍÂÌÁËÁÊÃÌÿÂ Ä ÁõÅ ÁõÅ Â Ã ÔÿÂ(Áõ(ÃÉÂÊÄËÂÊÁÉÃ×ÿÍÿÈ×ÌÿÃ×ÍÿÄ×ÃÿÄ×ÇÿÂ×ÁÿÃ×ÇÿÃ×ÁÿÁ×ùÿÁÆÁÉÁÊÁËÁÌÁËÁÊÁÉÈÿÁÆÁÉÂÊÂËÂÌÁÍÂÌÁËÁÊ  (ÌÿÄ Â Â !Å !Ä Ä ˆÔÿXÂ!Ã(ÁÈÂÉÃÊÃËÁÊÂÉÌÿÁÆÁÉÁÊÁËÁÌÁËÁÊÁÉÈÿÁÆÁÉÂÊÂËÂÌÁÍÂÌÁËÁÊ  (ÌÿÄ Â Â !Å !Ä Ä ˆÔÿXÂ!Ã(ÁÈÂÉÃÊÃËÁÊÂÉ×ÿÍÿÁ×ÂÿÂ×ÂÿÁ×ÍÿÂ×ÎÿÃ×ÃÿÃ×ÈÿÂ×ÂÿÂׯÿÂ×ÂÿÂ×ùÿÁÆÁÉÁÊÁËÁÌÁËÂÊÈÿÂÇÁÈÂÊÁËÁÌÁÍÂÌÁËÁÊ !Á×Ìÿ(!ÄõÁõ Å( ÄõÁõÂ!(ˆÔÿÁ×X Ã!Â(ÄÉÂÊÂÉÃÊÌÿÁÆÁÉÁÊÁËÁÌÁËÂÊÈÿÂÇÁÈÂÊÁËÁÌÁÍÂÌÁËÁÊ !Á×Ìÿ(!ÄõÁõ Å( ÄõÁõÂ!(ˆÔÿÁ×X Ã!Â(ÄÉÂÊÂÉÃÊ×ÿÐÿÂ×ÐÿÂ×ÎÿÃ×ÃÿÃ×ÌÿÂ×ÅÿÂ×ÄÿÁ×ùÿÁÆÁÉÁÊÁËÁÌÁËÁÊÁÉÈÿÂÁØÂÈÂËÁÌÁËÁÊà Áõ !jÂÌÿÁØÁ×ÂXÔ XÁ×ÁØjÔÿÂÁ×X j!(!(ÁÉÁÈÂÉÁÊÂØÂÊÌÿÁÆÁÉÁÊÁËÁÌÁËÁÊÁÉÈÿÂÁØÂÈÂËÁÌÁËÁÊà Áõ !jÂÌÿÁØÁ×ÂXÔ XÁ×ÁØjÔÿÂÁ×X j!(!(ÁÉÁÈÂÉÁÊÂØÂÊ×ÿÐÿÂ×ÃÿÃ×ÁÿÂ×ÁÿÃ×ÁÿÆ×ÃÿÃׯÿÁ×ÁÿÂ×ÁÿÁ×ÁÿÂ×ÂÿÃ×ÂÿÃ×ÂÿÂ×ÅÿÂ×ÇÿÃ×ÂÿÃ×ÁÿÂ×ìÿÁÆÁÉÁÊÁËÁÌÁËÂÊÈÿÂ×ÁØÂÉÁËÁÊ Áõ jÄüÿÄÁ׈j (ÂõÂÉÁÊÂØÁÈÌÿÁÆÁÉÁÊÁËÁÌÁËÂÊÈÿÂ×ÁØÂÉÁËÁÊ Áõ jÄüÿÄÁ׈j (ÂõÂÉÁÊÂØÁÈ×ÿÐÿÂ×ÄÿÂ×ÁÿÂ×ÂÿÃ×ÁÿÃ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÅÿÁ×ÁÿÂ×ÁÿÁ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÂÿÂ×ÂÿÂ×ÅÿÂׯÿÂ×ÁÿÂ×ÂÿÃ×ÁÿÁ×ìÿÁÆÁÉÁÊÁËÁÌÁËÁÊÁÉÈÿÁØÃ×ÁÇ ( jÆüÿÆÁ׈jÂ!(ÂõÁÊÁÉÂØÁ×ÁØÌÿÁÆÁÉÁÊÁËÁÌÁËÁÊÁÉÈÿÁØÃ×ÁÇ ( jÆüÿÆÁ׈jÂ!(ÂõÁÊÁÉÂØÁ×ÁØ×ÿÐÿÂ×ÄÿÂ×ÁÿÂ×ÂÿÂ×ÃÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÅÿÁ×ÁÿÂ×ÁÿÁ×ÁÿÂ×ÄÿÂ×ÂÿÂ×ÂÿÂ×ÅÿÂ×ÉÿÂ×ÂÿÂ×ïÿÁÆÁÉÁÊÁËÁÌÁËÂÊÈÿÄ×ÁØ !jÈüÿÈÁ×X !(ÁõÁÊÁØÃ×ÌÿÁÆÁÉÁÊÁËÁÌÁËÂÊÈÿÄ×ÁØ !jÈüÿÈÁ×X !(ÁõÁÊÁØÃ××ÿÐÿÂ×ÄÿÂ×ÁÿÂ×ÂÿÂ×ÃÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÅÿÁ×ÂÿÂ×ÂÿÂ×ÂÿÄ×ÂÿÂ×ÂÿÂ×ÅÿÂ×ÇÿÄ×ÂÿÂ×ïÿÁÇÂÈÂÊÂÉÁÊÈÿÂÃ×ÁÆ ! ÊüÿÊÁ×X !ÁÆÃ×ÌÿÁÇÂÈÂÊÂÉÁÊÈÿÂÃ×ÁÆ ! ÊüÿÊÁ×X !ÁÆÃ××ÿÐÿÂ×ÄÿÂ×ÁÿÂ×ÂÿÂ×ÃÿÂ×ÁÿÂ×ÁÿÂ×ÁÿÂ×ÅÿÁ×ÂÿÂ×ÂÿÂ×ÁÿÂ×ÁÿÂ×ÂÿÂ×ÂÿÂׯÿÂ×ÃÿÁ×ÁÿÂ×ÁÿÂ×ÂÿÂ×ïÿÁÈÂÉÂÊÁËÁÊÁÉÈÿÄÂ׈Á×ÌüÿÌÁ×ÂØÂ×ÃÌÿÁÈÂÉÂÊÁËÁÊÁÉÈÿÄÂ׈Á×ÌüÿÌÁ×ÂØÂ×Ã×ÿÏÿÄ×ÄÿÉ×ÂÿÁ×ÁÿÂ×ÃÿÃ×ÅÿÃ×ÁÿÂ×ÁÿÄ×ÁÿÍׯÿÄ×ÃÿÉ×îÿÁÉÁÊÁËÂ×ÁÊÁËÁÊÿÿÙÿÎÁ×ÅÌÿÁÉÁÊÁËÂ×ÁÊÁËÁÊÿÿÙÿÎÁ×Å×ÿÿÿÿÿÐÿÁÊÁÉÁ×ÁØÂ×ÁÊÿÿùÿÁÊÁÉÁ×ÁØÂ×ÁÊÿÿÿÿÅÿÿÿÿÿÐÿÂÉÁ×ÁØÂ×ÁÈÿÿùÿÂÉÁ×ÁØÂ×ÁÈÿÿÿÿÅÿÿÿÿÿÐÿÂÇÁ×ÁØÂ×ÁØÁÆÿÿùÿÂÇÁ×ÁØÂ×ÁØÁÆÿÿÿÿÅÿÿÿÿÿÐÿÈ×ÿÿùÿÈ×ÿÿÿÿÅÿÿÿÿÿÐÿÈ×ÿÿùÿÈ×ÿÿÿÿÅÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿÿÿÿÿÿÿÿÿÿÿÿÿßÿ ÿîîïïððññòòóóôôõõöö¨¨¨¸¸¸ÈÈÈØØØèèèüüü4V;™{«äõ½ÆZs$æ˜è6gî¿ÆÚÌçõó:[?07Zf:Vû˜cq}ˆnPê‡Ès°À}Š‘ðÀ½Á©Ï ÇQgís;k×±Î@d:j“3ÏWFk ÄÒ{‡!Å]©"ω¶ÔchS•®}ê³1ÁÏ-ïvòJµí f,Õv`s,€£ óe5ˆ«GKLÖoi}ÂŒ°6øVOχha ®‘ø¯kV«Ö{{¹ø½É„ˆÅâ•÷hϺ\Ûỉ¶&VëÏq¹0—K¨SÿÑ÷kÔOÝÌ…{m‘êyL_B¼‡Ìç 8 êHÆ'ž4ÿ’e¥Ú–÷pªm–i›_ÇÎFØÙˆùo1æª'Ô£©G`nFØü2 ÄS–³Ïb{\ÛùÙÓ¯o3Ÿ÷ï`$Úf‹­À'Ô‹ý~œ9És­ÔÀ‰+H}6AÄ!–÷Öšµ|3扶`Èïh[Öœ•t9Ïž©+oEÃ÷37–2GBj0°7ÝEqŸÛþ*}š¶•秵퓗뮉N˜Ãœâ+qΨm›kßšücú^Zß]œÆ\WÁŠ+}{æ,ϯ=Òâ¶»t5›ó—é¯â§mD=Wj­m˜qš”9m‰g¿A,ÄëyÓâcĤ«/–\Gà‰V½…ù ð€°Õ%>öí07ºKß½=”›\[°ÀÅó±[5ªˆm9óøåƼÑ:¾ÌÄï:Y4j‹ò_êÂûAWxBV p€÷k‹ÒPÃV¿7…eè™je´ÊÏ·èlTôØ¢ï.έµ„†¦çyÛ¢Z´[ÌuÓ4×ã:jci–Õˆz¶©øNV Ê]Ýoïf®°Ú°âȹÕãàu\¼1ø 67gšµñÑ~}U;j{hÚ+$ÌÜ VæªÁA¯9ži[õõe_±‹Õ€->S~Æu=ÅÛ™ûj»öôHÝ” ¾î»úó“üº=Þ¯måk{h{~öļFäW³íھ׋¶©NºW¬»ÏϺۮ—Ç{ÌCì Ü@ãˆÆÅJÔ]ý¢»Ï8ÃXÛ¡|¯À©žÓwÈ@Qñ²+jÛï­?S× qßró^ÿkîÝÛ½Âg…ºôôR改þ'ènIÛ¾Áü0w¹>©ÜOý)æ¡¶U8ëTéÝÍóxoEmû˜ÿiYm+OœÏw W†Õ~ñbm;6ó³ÌOægÛÃÜÅöhövu&#¶ÜŽkòœ]æ ÿðßÄEøœÇÈóè€`§DPêüyæv ™È2"­%~+ùlqÎ7"/1Ÿža>=Ç|Ú=Œ1_’]0þs÷D–ü(sÇ y«ZGÁmÝ*3kªyV-?NÉÄI?ÊœIˆÄ}Ís¢]Ò”t2ib-s×åß}§R$æ«—‹‡9"¼óD†,؆ B²zÌÝrw™ 2ò¥ -ÃT‰Èbt[J­eãéê›Õ“{užÓl%i&$ìÒƒK<ÙѤÄt´òyŒ¥Ðp%s…݆Ë:1‰öt.1ºÈRÞMI<‚oí%=_ŒqÜtQ$&¦ l++P§ÌÓº"Ý“ lÏüPâ8RçræTKø$æ¿’M›IEND®B`‚nml-0.4.4/regression/expected/0000755000567200056720000000000012643457623017444 5ustar jenkinsjenkins00000000000000nml-0.4.4/regression/expected/026_asl.grf0000644000567200056720000000374312643457545021324 0ustar jenkinsjenkins00000000000000GRF‚  1ÿÿA€&ÿÿ‰D`ÿh €ÿÿÿÿÿÿýÿÿÿ  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõÿÿ  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~‚‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõÿÿ&ÿÿB€€€€ƒ‚Pÿÿ‰C ÿ €b)     ‚ ƒÿÿÿÿýÿÿÿ  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõÿÿ  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~‚‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõÿÿ&ÿþB€€€€ƒ‚Pÿþ‰D ÿ €`)     ‚ ƒþþÿþ ÿÿÿþÿþ‰ ÿÿ€þÿÿ‰ ÿÿþ[[ÿÿÿÈòÿûÿèÐÉÊÌË ÇÊÊËËË ÇÉÉàËÌĮ̀è9èàjØØØèØ7èÊËÊ„ƒÐ6è8ØÉè…„ØʨƒÊÇÈè%¨$ÊÊÊÊÉÈÈÇ ÈʃÉÈÇËʸ$ Ë‚XÈ„ƒÉÉÉÇÀ6 Ì‚X‚jÇÈɃÇÈËÉèµÈè+ÈØYËÉÈÈæÈÈÈÉÈèØèH¨üȵÈèZØ6 ÐHÁ8ÈòÿûÿèÐÉÊÌË ÇÊÊËËË ÇÉÉàËÌĮ̀è9èàjØØØèØ7èÊËÊ„ƒÐ6è8ØÉè…„ØʨƒÊÇÈè%¨$ÊÊÊÊÉÈÈÇ ÈʃÉÈÇËʸ$ Ë‚XÈ„ƒÉÉÉÇÀ6 Ì‚X‚jÇÈɃÇÈËÉèµÈè+ÈØYËÉÈÈæÈÈÈÉÈèØèH¨üȵÈèZØ6 ÐHÁ8nml-0.4.4/regression/expected/022_disable_item.nfo0000644000567200056720000000222412643457545023157 0ustar jenkinsjenkins00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d5 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\22" "NML regression test" 00 "A test newgrf testing NML" 00 3 * 124 00 00 \b1 74 FF \wx0000 06 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 4 * 9 00 0A \b1 01 FF \wx000C 08 FF 5 * 24 00 0B \b2 03 FF \wx000A 08 FF FF FF 17 \dx00000000 \dx00000000 \dx00000000 nml-0.4.4/regression/expected/027_airport_layout.nfo0000644000567200056720000000154612643457545023626 0ustar jenkinsjenkins00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 6 01 11 \b1 FF \wx0001 1 * 1 00 // Name: small_airport_tile_layout - feature 11 2 * 25 02 11 FF \b2 \dx80000000 \dxC0008000 \b32 \b16 80 \dx80000000 \b0 \b0 \b0 \b16 \b16 \b16 3 * 16 00 11 \b4 01 FF \wx0000 08 00 0F \wx0103 10 01 11 01 4 * 7 03 11 01 00 \b0 \wx00FF // small_airport_tile_layout; 5 * 36 00 0D \b2 01 FF \wx0000 08 00 0A \b1 \d21 00 00 00 FE \wx0000 01 00 FE \wx0000 02 00 FE \wx0000 03 00 46 00 80 nml-0.4.4/regression/expected/023_engine_override.grf0000644000567200056720000000027612643457545023704 0ustar jenkinsjenkins00000000000000GRF‚  ¬ÿ6ÿCINFOBVRSNBMINVBNPARBPALSABBLTR84ÿNML#NML regression testA test newgrf testing NMLÿÿtestABCDÿÿNML#4Vxnml-0.4.4/regression/expected/007_townnames.nfo0000644000567200056720000000140112643457545022550 0ustar jenkinsjenkins00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 52 0F 00 // A 02 04 00 05 0A "small" 00 0A "medium" 00 02 "big" 00 01 "" 00 03 05 05 0D "village" 00 0A "town" 00 01 "city" 00 1 * 20 0F 01 // 1 01 01 00 01 01 "tiny village" 00 2 * 80 0F 82 00 "US Stations" 00 1F "Test stationnnetjes" 00 7F "ÞTeßt Stätiöµſ" 00 00 01 03 0A 04 0A "MainCapital" 00 85 00 81 01 nml-0.4.4/regression/expected/013_train_callback.nfo0000644000567200056720000001723612643457545023500 0ustar jenkinsjenkins00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d75 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "W" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\13" "NML regression test" 00 "A test newgrf testing NML" 00 3 * 208 00 08 \b1 32 FF \wx0000 09 "WDPR" "SCRP" "CMNT" "WOOD" "LVST" "STEL" "VEHI" "BRCK" "WOOL" "BUBL" "TOYS" "FZDR" "FRUT" "FRVG" "FOOD" "OIL_" "GOOD" "WATR" "MILK" "COAL" "IORE" "AORE" "CLAY" "GRVL" "SAND" "GRAI" "RSGR" "MAIZ" "CORE" "FERT" "CTCD" "SULP" "WHEA" "RFPR" "COLA" "PETR" "PAPR" "TOFF" "SUGR" "PASS" "MAIL" "BATT" "SWET" "RUBR" "FMSP" "ENSP" "MNSP" "FICR" "PLAS" "PLST" 4 * 28 00 08 \b1 05 FF \wx0000 12 "RAIL" "ELRL" "MONO" "MGLV" "TRPD" 5 * 6 01 00 \b8 FF \wx0004 6 temperate_railwagons.png 8bpp 0 25 8 24 -3 -12 normal 7 temperate_railwagons.png 8bpp 16 25 22 17 -14 -9 normal 8 temperate_railwagons.png 8bpp 48 25 32 12 -16 -8 normal 9 temperate_railwagons.png 8bpp 96 25 22 17 -6 -9 normal 10 temperate_railwagons.png 8bpp 0 250 8 24 -3 -12 normal 11 temperate_railwagons.png 8bpp 16 250 22 17 -14 -9 normal 12 temperate_railwagons.png 8bpp 48 250 32 12 -16 -8 normal 13 temperate_railwagons.png 8bpp 96 250 22 17 -6 -9 normal 14 arctic_railwagons.pcx 8bpp 0 25 8 24 -3 -12 normal 15 arctic_railwagons.pcx 8bpp 16 25 22 17 -14 -9 normal 16 arctic_railwagons.pcx 8bpp 48 25 32 12 -16 -8 normal 17 arctic_railwagons.pcx 8bpp 96 25 22 17 -6 -9 normal 18 arctic_railwagons.pcx 8bpp 0 250 8 24 -3 -12 normal 19 arctic_railwagons.pcx 8bpp 16 250 22 17 -14 -9 normal 20 arctic_railwagons.pcx 8bpp 48 250 32 12 -16 -8 normal 21 arctic_railwagons.pcx 8bpp 96 250 22 17 -6 -9 normal 22 temperate_railwagons.png 8bpp 0 225 8 24 -3 -12 normal 23 temperate_railwagons.png 8bpp 16 225 22 17 -14 -9 normal 24 temperate_railwagons.png 8bpp 48 225 32 12 -16 -8 normal 25 temperate_railwagons.png 8bpp 96 225 22 17 -6 -9 normal 26 temperate_railwagons.png 8bpp 0 350 8 24 -3 -12 normal 27 temperate_railwagons.png 8bpp 16 350 22 17 -14 -9 normal 28 temperate_railwagons.png 8bpp 48 350 32 12 -16 -8 normal 29 temperate_railwagons.png 8bpp 96 350 22 17 -6 -9 normal 30 temperate_railwagons.png 8bpp 0 150 8 24 -3 -12 normal 31 temperate_railwagons.png 8bpp 16 150 22 17 -14 -9 normal 32 temperate_railwagons.png 8bpp 48 150 32 12 -16 -8 normal 33 temperate_railwagons.png 8bpp 96 150 22 17 -6 -9 normal 34 temperate_railwagons.png 8bpp 0 275 8 24 -3 -12 normal 35 temperate_railwagons.png 8bpp 16 275 22 17 -14 -9 normal 36 temperate_railwagons.png 8bpp 48 275 32 12 -16 -8 normal 37 temperate_railwagons.png 8bpp 96 275 22 17 -6 -9 normal // Name: bulk_wagon_coal_default_group - feature 00 38 * 13 02 00 FF \b2 \b2 \w0 \w1 \w0 \w1 // Name: bulk_wagon_coal_arctic_group - feature 00 39 * 13 02 00 FE \b2 \b2 \w2 \w3 \w2 \w3 // Name: bulk_wagon_coal2_group - feature 00 40 * 13 02 00 FD \b2 \b2 \w4 \w5 \w4 \w5 // Name: bulk_wagon_grain_group - feature 00 41 * 13 02 00 FC \b2 \b2 \w6 \w7 \w6 \w7 // Name: bulk_wagon_coal_default_switch 42 * 15 02 00 FD 80 02 \b0 04 \wx00FF \wx00FF \wx00FF // (2/3) -> (3/4): bulk_wagon_coal_default_group; \wx00FD // (1/3) -> (1/4): bulk_wagon_coal2_group; // param[127] = (param[131] & 255) 43 * 9 0D 7F \D& 83 FF \dx000000FF 44 * 7 06 7F 04 FF \wx0006 FF // Name: bulk_wagon_coal_climate_switch 45 * 23 02 00 FD 89 1A 00 \dx00000000 // param[127] \b1 \wx00FE \dx00000001 \dx00000001 // 1 .. 1: bulk_wagon_coal_arctic_group; \wx00FD // default: bulk_wagon_coal_default_switch; // Name: bulk_wagon_graphics_switch 46 * 23 02 00 FC 89 47 00 \dx000000FF \b1 \wx00FD \dx00000013 \dx00000013 // 19 .. 19: bulk_wagon_coal_climate_switch; \wx00FC // default: bulk_wagon_grain_group; // Name: @CB_FAILED_REAL00 47 * 9 02 00 FD \b1 \b1 \w0 \w0 // Name: @CB_FAILED00 48 * 23 02 00 FD 89 0C 00 \dx0000FFFF \b1 \wx8000 \dx00000000 \dx00000000 // graphics callback -> return 0 \wx00FD // Non-graphics callback, return graphics result // Name: bulk_wagon_cb_capacity_switch 49 * 83 02 00 FE 89 47 00 \dx000000FF \b7 \wx8019 \dx0000002F \dx0000002F // 47 .. 47: return 25; \wx8014 \dx0000000C \dx0000000C // 12 .. 12: return 20; \wx8014 \dx0000000D \dx0000000D // 13 .. 13: return 20; \wx8019 \dx00000019 \dx00000019 // 25 .. 25: return 25; \wx8019 \dx0000001B \dx0000001B // 27 .. 27: return 25; \wx8014 \dx0000001A \dx0000001A // 26 .. 26: return 20; \wx8019 \dx00000020 \dx00000020 // 32 .. 32: return 25; \wx00FD // No default specified -> fail callback // Name: bulk_wagon_cb_weight_switch 50 * 53 02 00 FD 89 47 00 \dx000000FF \b4 \wx8012 \dx00000013 \dx00000013 // 19 .. 19: return 18; \wx8012 \dx0000000C \dx0000000C // 12 .. 12: return 18; \wx8012 \dx0000000D \dx0000000D // 13 .. 13: return 18; \wx8012 \dx0000001A \dx0000001A // 26 .. 26: return 18; \wx00FD // No default specified -> fail callback // param[125] = (param[1] * 5000) 51 * 9 0D 7D \D* 01 FF \dx00001388 // param[126] = (param[125] + 698) 52 * 9 0D 7E \D+ 7D FF \dx000002BA // param[127] = (param[126] / 1397) 53 * 9 0D 7F \D/ 7E FF \dx00000575 // param[124] = (param[2] * 12500) 54 * 9 0D 7C \D* 02 FF \dx000030D4 // param[125] = (param[124] + 6286) 55 * 9 0D 7D \D+ 7C FF \dx0000188E // param[126] = (param[125] / 12573) 56 * 9 0D 7E \D/ 7D FF \dx0000311D // param[123] = (param[3] * 8) 57 * 9 0D 7B \D* 03 FF \dx00000008 // param[124] = (param[123] + 2) 58 * 9 0D 7C \D+ 7B FF \dx00000002 // param[125] = (param[124] / 5) 59 * 9 0D 7D \D/ 7C FF \dx00000005 // param[122] = (param[4] * 2965) 60 * 9 0D 7A \D* 04 FF \dx00000B95 // param[123] = (param[122] + 1105) 61 * 9 0D 7B \D+ 7A FF \dx00000451 // param[124] = (param[123] / 2211) 62 * 9 0D 7C \D/ 7B FF \dx000008A3 63 * 22 06 7F 02 FF \wx000B 7E 02 FF \wx000E 7D 02 FF \wx0011 7C 02 FF \wx0014 FF 64 * 22 00 00 \b5 01 FF \wx0074 09 \wx0025 09 \wx0000 09 \wx0000 09 \wx0000 0B \wx0000 // param[127] = (param[1] & 255) 65 * 9 0D 7F \D& 01 FF \dx000000FF // param[126] = (param[1] << -8) 66 * 9 0D 7E \D<< 01 FF \dxFFFFFFF8 67 * 12 06 7F 01 FF \wx004D 7E 01 FF \wx004F FF 68 * 82 00 00 \b26 01 FF \wx0074 06 0F 28 \wx0010 1D \dx00000000 29 \wx01CB 1D \dx00000000 2C \b4 00 01 0C 0D 1D \dx00000000 15 13 12 FD 2A \dx000A7A40 04 FF 26 00 03 1E 02 00 07 0A 17 B6 0D 05 09 \wx0000 1C 28 05 00 0B \wx0000 0E \dx00004C30 14 1E 16 00 24 00 25 00 69 * 27 04 00 7F 01 FF \wx0074 "NML Test bulk wagon" 00 70 * 9 00 00 \b1 01 FF \wx0074 1E 08 // Name: @action3_0 71 * 33 02 00 FD 89 10 00 \dx000000FF \b2 \wx00FE \dx00000014 \dx00000014 // bulk_wagon_cb_capacity_switch; \wx00FD \dx00000016 \dx00000016 // bulk_wagon_cb_weight_switch; \wx00FC // bulk_wagon_graphics_switch; // Name: @action3_1 72 * 33 02 00 FF 89 10 00 \dx000000FF \b2 \wx801E \dx00000014 \dx00000014 // return 30; \wx8019 \dx00000016 \dx00000016 // return 25; \wx00FC // bulk_wagon_graphics_switch; // Name: @action3_2 73 * 33 02 00 FD 89 0C 00 \dx0000FFFF \b2 \wx00FE \dx00000015 \dx00000015 // bulk_wagon_cb_capacity_switch; \wx00FD \dx00000036 \dx00000036 // @action3_0; \wx00FC // bulk_wagon_graphics_switch; // Name: @action3_3 74 * 23 02 00 FC 89 0C 00 \dx0000FFFF \b1 \wx00FF \dx00000036 \dx00000036 // @action3_1; \wx00FC // bulk_wagon_graphics_switch; 75 * 12 03 00 01 FF \wx0074 \b1 FF \wx00FC // @action3_3; \wx00FD // @action3_2; nml-0.4.4/regression/expected/001_action8.nfo0000644000567200056720000000122412643457545022077 0ustar jenkinsjenkins00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d2 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\1" "NML regression test" 00 "A test newgrf testing NML" 00 nml-0.4.4/regression/expected/010_liveryoverride.nfo0000644000567200056720000000524612643457545023614 0ustar jenkinsjenkins00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d35 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "W" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\10" "NML regression test" 00 "A test newgrf testing NML" 00 3 * 6 01 00 \b3 FF \wx0008 4 opengfx_trains_start.pcx 8bpp 142 112 8 22 -3 -10 normal 5 opengfx_trains_start.pcx 8bpp 158 112 21 15 -14 -7 normal 6 opengfx_trains_start.pcx 8bpp 190 112 31 12 -16 -8 normal 7 opengfx_trains_start.pcx 8bpp 238 112 21 16 -6 -7 normal 8 opengfx_trains_start.pcx 8bpp 270 112 8 24 -3 -10 normal 9 opengfx_trains_start.pcx 8bpp 286 112 21 16 -15 -6 normal 10 opengfx_trains_start.pcx 8bpp 318 112 32 12 -16 -8 normal 11 opengfx_trains_start.pcx 8bpp 366 112 21 15 -6 -7 normal 12 arctic_railwagons.pcx 8bpp 0 0 8 24 -3 -12 normal 13 arctic_railwagons.pcx 8bpp 16 0 22 17 -14 -9 normal 14 arctic_railwagons.pcx 8bpp 48 0 32 12 -16 -8 normal 15 arctic_railwagons.pcx 8bpp 96 0 22 17 -6 -9 normal 16 arctic_railwagons.pcx 8bpp 0 0 8 24 -3 -12 normal 17 arctic_railwagons.pcx 8bpp 16 0 22 17 -14 -9 normal 18 arctic_railwagons.pcx 8bpp 48 0 32 12 -16 -8 normal 19 arctic_railwagons.pcx 8bpp 96 0 22 17 -6 -9 normal 20 opengfx_trains_start.pcx 8bpp 142 139 8 21 -3 -10 normal 21 opengfx_trains_start.pcx 8bpp 158 139 20 15 -13 -7 normal 22 opengfx_trains_start.pcx 8bpp 190 139 28 10 -12 -6 normal 23 opengfx_trains_start.pcx 8bpp 238 139 20 16 -6 -7 normal 24 opengfx_trains_start.pcx 8bpp 270 139 8 21 -3 -10 normal 25 opengfx_trains_start.pcx 8bpp 286 139 20 15 -15 -6 normal 26 opengfx_trains_start.pcx 8bpp 318 139 28 10 -16 -6 normal 27 opengfx_trains_start.pcx 8bpp 366 139 20 16 -6 -7 normal // Name: turbotrain_engine_group - feature 00 28 * 9 02 00 FF \b1 \b1 \w0 \w0 // Name: normal_passenger_group - feature 00 29 * 9 02 00 FE \b1 \b1 \w1 \w1 // Name: turbotrain_passenger_group - feature 00 30 * 9 02 00 FD \b1 \b1 \w2 \w2 31 * 11 00 00 \b2 01 FF \wx0014 12 FD 27 04 32 * 9 03 00 01 FF \wx0014 \b0 \wx00FF // turbotrain_engine_group; 33 * 9 03 00 81 FF \wx001B \b0 \wx00FD // turbotrain_passenger_group; 34 * 27 00 00 \b6 01 FF \wx001B 12 FD 27 04 28 \wx0001 1D \dx00000000 29 \wx0000 1D \dx00000000 35 * 9 03 00 01 FF \wx001B \b0 \wx00FE // normal_passenger_group; nml-0.4.4/regression/expected/025_loop.grf0000644000567200056720000000015712643457545021511 0ustar jenkinsjenkins00000000000000GRF‚  ] ÿ ÿÿ ÿ ~ÿ ÿ ~~ÿáÿÿÿ ÿ ~ ÿ ÿÿ šnml-0.4.4/regression/expected/006_vehicle.grf0000644000567200056720000001455312643457545022163 0ustar jenkinsjenkins00000000000000GRF‚  Ìÿ6ÿCINFOBVRSNBMINVBNPARBPALSWBBLTR34ÿNMLNML regression testA test newgrf testing NML ÿÿ PASSMAILGOODIOREGOLDFOOD ÿ ÿ ÿ ~ÿ ÿ ~ÿÿÿ,ÿ?ÿÿY(†ó  HL ‡˜XÿM€ÿÿYFoster Express TramÿÿYFoster Sneltramÿÿý ýýýýýýý ÿÿ ÿÿYÿ wýÿöÿÉÊÊËËÌÇÉÊÉÊËÌËÇÉèÌàèÊÌÊØØÀ ÊÊØÐ ËØÊÈØ0ËÌÈÐÇÈÉØèYèÊjèbȃÊʃËÈ„ƒÊÊ„ƒØHÊè{ǵà ·Ëà)ÊÊ Îýÿöÿÿ-~ãÉ>“×ÊØR©ÇËØoÉ·ÌÐ#aóǨ(°2°(Ø7°(ˆUØKˆ(ˆPˆ-Ø‘ˆPˆ¥ˆxˆ ˆ ˆ °ðߨˆ ˆðÑØ(°Íˆðˆ(ˆð±@©Yˆxˆ(¹†ˆ ±hÙ•#qëÈÑÂØÈ‰½ˆ#Èð¯j‰êÐF 4¯ƒ,Ï¡¸¨(;›„Ð-©E¸Š²X²b²gâX ê†µŠ…ÚàꟷÉ|º…ˆÍ©ÇêË wýÿöÿÉÊÊËËÌÇÉÊÉÊËÌËÇÉèÌàèÊÌÊØØÀ ÊÊØÐ ËØÊÈØ0ËÌÈÐÇÈÉØèYèÊjèbȃÊʃËÈ„ƒÊÊ„ƒØHÊè{ǵà ·Ëà)ÊÊÈòÿûÿèÐÉÊÌË ÇÊÊËËË ÇÉÉàËÌĮ̀è9èàjØØØèØ7èÊËÊ„ƒÐ6è8ØÉè…„ØʨƒÊÇÈè%¨$ÊÊÊÊÉÈÈÇ ÈʃÉÈÇËʸ$ Ë‚XÈ„ƒÉÉÉÇÀ6 Ì‚X‚jÇÈɃÇÈËÉèµÈè+ÈØYËÉÈÈæÈÈÈÉÈèØèH¨üȵÈèZØ6 ÐHÁ8kòÿûÿÿذ ˆˆ#¨2-~ãÉ>“×ÊoÉ·ÌR©ÇˈPˆ_ˆnˆ}¨ŒaóÇÐZØ_ØZØ_ØdˆnˆÃˆÒˆáØZع°¾ˆ_°dØ×ØÜ‰'‰6ˆZ±‰ˆUˆ×Ñ@¯jé’ߨذ ¨‰ˆ¯±‹±;Ù¤ ;›„4¯ƒ‰h‰‰‰ ¨Z±þ¨ZB{…Èiº!ˆZ‰‰ˆ‚ˆZ°ÃÚvÚ&#qëȈ¹‰½ˆ´‰¨´²q²{ÚäØdØiÚ™‰Â‰6‰ˆZ²Õ°¹Ù•,Ϩn۱ljˆ´ˆ¹Ó‰0¿‚ëÛ¿XÑ"±þ²ýÛÀÛk‰Â‰ŠË°ZÈdâ²»¶Ùܰ×Ðd‰hˆZ¼BГ×Ê-~ãɰ"ÐØ° ˆˆ#ˆ2ˆA¸P™¨\R©ÇËØ¨pˆ„ؘ°¢Ø¬ˆ°ØÊØUˆ¸7›ˆ*Єoɷ̈\Ñ¿XبW°¶ˆk°Kˆ#°Øiànœ° ˆ9ˆCˆpˆaˆuˆkˆuˆè±N莉KÚˆ9ˆHˆWˆfˆuˆ„ˆ“¢™‰&ŠŠ‰ü‰ª‰¹‰È‰×Úœ%Û€éªàÈ ,Ï4¯ƒ;›„ˆˆˆ(ˆ<ˆPˆP¨dØ}Øxà}ê8B{…Ó;°‰ˆˆˆ(ˆ<ˆPˆPˆdЉáÊ8‹É³~#qëÈ‹¡‹¦‹µ‹Y‹h‹w‹çÜÀœ ÿµ‹¡‹°ŒHŒCŒRŒa‹ûŒuŒš¬à0¿åŽÄM´Wï׌RŒaŒ9ˆKˆKÔÊvÌ›ÍØÀzÞψذ†ˆ ˆ ßòÿøÿjØèР à؈ˆˆ< JÊÉØØÉÉÉèÐÐ àlÉÉËËÉØÊÉÊèÊÊÊËèÊÊè5ÊËËÌàXXËè àÌèÌËÌÌÌèØ àØÐà?àCà"à&À ËØeØfØAÐË€€Ëƒ„àÀÀ€…˃„àÀ¸€ÊèÁËÊȈTà›èϵÐcÐrÐÐÉèÏ ËË‚XXXX×ȇÌÌÐÌÍÍá…‚jˆˆ‚j¹°éúÿùÿjèèÐÀ ؈ˆ°3ÌØË@ÊÉÊËËØÌ ÉÉÊèè°ÉÉÉàÊÊË̸ƒÀ˸„ƒèÇà, ËÊÌËÌËÌÊÇɨËà,ÊÈÈÉÀÊèUàAËÊ×èÐ,ØÌÌËÇX×àÈØXèT Ç€ƒË‚jX×è¢ÐBè„èfèÇÊËè+ènÊǃÊàé/ÆÈËèXÈÉÊ€è½ÈÈèXØf4À(ØBX×Èè¹Ê™#‚jÈДúÿùÿÊ/&-4;Lv¯÷I¥lÒ8™ð8vª¯jèߨ؃oÉ·ÌØR©Çˈ>“×Ê-~ãÉÐ àØÐ%Ø/à*‹Ð%°*ˆˆ4¸9ލ9°CˆH؆°°C¸èÐ4¯ƒ,ψRˆšˆÎˆR°Ó’é";›„ÐWˆ\aóLj¸© Ù4¹±%Ñ^áR“¨Cˆfˆf‰‰†ˆÂ¸a”#qëÈØ©šˆÑˆÑ‰k‰ñ‰zâ èfÚ ï׈kˆÛ‰‰Ö‰ÛÒ†ÙÊÈfÙp¿XÐkˆpÚŸØï‰±² ‰ÙÀ%Û€Ò5âì“0¿‚Û¨kŠÌ‰KŠˆ\ÚŒÚDãF‘ës¸aÚA³RˆÇЍ³ØR¸ãŽS÷ÆÑïËš™Ž²ÛÌØö‹PãåŒÉC‘f‹Ö»‰ùù4³˜Š ‰>ª‹¼H† ©r°*¼héúÿùÿjèèÐÀ ؈ˆ°3ÌØË@ÊÉÊËËØÌ ÉÉÊèè°ÉÉÉàÊÊË̸ƒÀ˸„ƒèÇà, ËÊÌËÌËÌÊÇɨËà,ÊÈÈÉÀÊèUàAËÊ×èÐ,ØÌÌËÇX×àÈØXèT Ç€ƒË‚jX×è¢ÐBè„èfèÇÊËè+ènÊǃÊàé/ÆÈËèXÈÉÊ€è½ÈÈèXØf4À(ØBX×Èè¹Ê™#‚jÈÐyýÿöÿjè ÇÉØÉÊÉÇØ ËÉÇÉÊØÊËÌÊØÊàÉÊèËèèØèØØà1ËÌÐè0ËÐÌËÊ€èIàƒ„ÌÉ€à„Ø è:èMÊ44àÉàaÊÊÉýÿöÿÿØÈ ¯jˆ¨# aóÇ-~ãÉè4ßØÐ >“×Êȸ#ˆ(°(R©ÇËÐA°KØAØP°#oÉ·ÌÐZˆ(°(Øxˆ(Ø›°–ˆPØ}ˆxˆ›ˆ(ˆ¹°–ˆPˆsˆÈˆõ‰Øëˆ(‰ˆð±EˆsˆPÙmÙŸ,ÏØÈ %ۀЉmˆ#Ø7 4¯ƒ;›„Ñ•ÙþÐAˆ#à#Ø(ˆ ‰‰'‘®ùù4؉O©Û‰å‘wyýÿöÿjè ÇÉØÉÊÉÇØ ËÉÇÉÊØÊËÌÊØÊàÉÊèËèèØèØØà1ËÌÐè0ËÐÌËÊ€èIàƒ„ÌÉ€à„Ø è:èMÊ44àÉàaÊÊÞòÿ÷ÿèÐà jˆè ؈$ˆÀ&ÉËLÇÉØÊÊË˨ ÇÉÉØËÌËÌÌÌÀ+èÊÊÐèÀè5à$ ÊÊÊ„ƒÉÊÌÊèØÉè…„ØXèG¸ËËÉÉèkà[¸ÊËÉ€ÉÉàZËËÈÊÌÌÉ€€€è+ØHè| ÊË‚XÉÈ€€É¸$ ‚X‚jÈɃƒÉ…„è…è¡ÈèèŠÈÉÉè5ƒà×ÉÈÑ:ÉÉCÉàÅèHÈÐèÙØH¡`è)˜ƒòÿ÷ÿÀ/&-4;Lv¯÷I¥lÒ8™ð8v¥¯jèߨ؃ Ø-~ãÉR©Çˈ aóÇÐØ>“×ÊØØ Ø%Ø*‹¨*°/Ø%oɷ̰ Ð¸Žˆ9¨cˆ99ˆ\ˆˆ¦ˆ¨•°ÓØÝ ;›„4¯ƒ’¨ØÄØýˆ\ˆÓ°R°Ì¸RB{…Øa“‰JˆÓ‰*ˆWˆ©°W±•”©‹‰‰‹ˆÛˆ³ˆ\±2 f%Û€©û‰©‰„‰ˆ\‰þâ<˜fØk°pˆ•‰kÙÌŠU²½0¿‚ë¿XÚñ“#qëÈ f,ÏØˉlj’¶°\Èf¯j‘ÈaÑ2à\ÒâÓa¹ÇŠ“‹ ا¸WŽÉê°\°®ª„⋽۸<Œª2üÔÿC«Ì‹˜‰>á7‰Ë-ˆ4ÓR‰ ìx…ˆ^±'Þòÿ÷ÿèÐà jˆè ؈$ˆÀ&ÉËLÇÉØÊÊË˨ ÇÉÉØËÌËÌÌÌÀ+èÊÊÐèÀè5à$ ÊÊÊ„ƒÉÊÌÊèØÉè…„ØXèG¸ËËÉÉèkà[¸ÊËÉ€ÉÉàZËËÈÊÌÌÉ€€€è+ØHè| ÊË‚XÉÈ€€É¸$ ‚X‚jÈɃƒÉ…„è…è¡ÈèèŠÈÉÉè5ƒà×ÉÈÑ:ÉÉCÉàÅèHÈÐèÙØH¡`è)˜ÞòÿøÿèР Øjˆ°؈6ˆÉÉÉèÐÐ ØØÉÊØlÊÊËèÊÊÊÊÉèÊØ ÉËËËËÌÌËÌè Ìà!ËËÊËXXà$àÊè7è Ø!À%à4ØèèР Ø/€Ø(Ø-àTÐzÊØqÌ€„ƒËàÀÐ˃ƒÊ€…è àÀ¸蹈TàÏÈàµØÊпØi°nè ÍÍÌ×XXXX‚èêИÐàýé‰j‚ˆˆj‚¹’¸¥òÿøÿ`%*6B²1ºHÖdò€œ ‚ߨè¯j‚À ߨ‚° –-~ãÉØ° ˆˆ#ˆ2ˆA¨P¸rÐd>“×Ê™ØØ R©Çˈ¨°(¸>ˆˆk¨kØÔØ_Ød¸ ›oÉ·ÌØÐ° °/°ˆ¬°R°À¿X؈¬Ðèˆ_¸òœˆê¨/ˆ¢ˆ¬ˆ¶ˆ¶ˆôˆÔ‰¹莈zˆ‰ˆ˜ˆ§ˆ¶ˆÅˆÔˆã‰ÈŽ%Û€‰À‰Ï‰Þ‰íŠ[ŠjŠeŠ.Š àŽ,ÏØИ ;›„4¯ƒÐÒˆˆˆ(ˆ<ˆPPÒeànàsÂ8Ù!ؘB{…ˆ/ˆˆˆ(ˆkˆPˆPˆdêÆ‹œ‹t‹ƒ‹’‹OŒŒ Ü7#qëÈ‹ÔFäÅëT‹óŒaŒŒkŒzŒ‰ŒŽŒ\ŒkåSšvÌ›ÍØÔuï׬/´90¿‚Œ˜Œ½ŒÌˆKˆK´ùÝýÐaψذ†ˆ ˆ ÞòÿøÿèР Øjˆ°؈6ˆÉÉÉèÐÐ ØØÉÊØlÊÊËèÊÊÊÊÉèÊØ ÉËËËËÌÌËÌè Ìà!ËËÊËXXà$àÊè7è Ø!À%à4ØèèР Ø/€Ø(Ø-àTÐzÊØqÌ€„ƒËàÀÐ˃ƒÊ€…è àÀ¸蹈TàÏÈàµØÊпØi°nè ÍÍÌ×XXXX‚èêИÐàýé‰j‚ˆˆj‚¹’¸ÈúÿùÿËÊËËàà ÐèËËÌÌ ÊРÊÉÊèèè+À,ƒÉÉÉÉÉèàAØØØØØj„ƒèÇàËÊàAàXȃ¨ØWÌÈè;¨,àUÌÈ×àPàBÈàBà«Ë×X×Éè,ÈØXà„ Êƒ‚jX×ÊÀ,èÊ„ƒèÜ‚jÇè¢ØXèèÄÐèÇÈËØXà<ʵÀüÐXØȹàXËʵ¨ÐÉÐ$`úÿùÿÿذ R©ÇË>“×ÊР؈(ˆ7ˆFˆU°dˆU°_oÉ·ÌØˆnˆ ˆ¯ˆ¾Ð´ˆ¹ˆdˆnˆÜ‰‰Ù-~ãɨi±'ˆˆ×ˆÜ‰w©†4¯ƒ,ÏÐiØn° °}°‡‰EÙJߨذ À¯j;›„ÐiˆnaóLjxªÚ‰E‰¸²N#qëÈÈdˆnˆnˆÜˆÒ‰³°á¸d‰'ˆÜˆÜ‰T‰©Š…Ú”ÐÈï׉ˆn©¸Øú‰JÚ5‹W«\à_¿X¨iˆÜÑO‰¸’&Š”ÛÀ²NÓè0¿‚ʸn²ÕŠqŠ&Ú”ˆUÐd²NŒL¸nÚX‹*Šß²”ˆU‹ÔŒ°Œ¿ÚÁÚ{±Y‹H‹À“Ê íµ#¥2‰¸‘¸Œ$´œÓx‡–‰¸â½¥Ð´ˆZ׿õ޲&´ìˆ´¾6ÈúÿùÿËÊËËàà ÐèËËÌÌ ÊРÊÉÊèèè+À,ƒÉÉÉÉÉèàAØØØØØj„ƒèÇàËÊàAàXȃ¨ØWÌÈè;¨,àUÌÈ×àPàBÈàBà«Ë×X×Éè,ÈØXà„ Êƒ‚jX×ÊÀ,èÊ„ƒèÜ‚jÇè¢ØXèèÄÐèÇÈËØXà<ʵÀüÐXØȹàXËʵ¨ÐÉÐ$nml-0.4.4/regression/expected/011_snowline.nfo0000644000567200056720000000355312643457545022400 0ustar jenkinsjenkins00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d3 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\11" "NML regression test" 00 "A test newgrf testing NML" 00 3 * 392 00 08 \b1 01 FF \wx0000 10 06 06 06 05 05 05 05 05 04 04 04 04 04 04 03 03 03 03 03 02 02 02 02 02 01 01 01 01 01 00 00 00 00 02 04 05 07 09 0B 0C 0E 10 12 13 15 17 19 1A 1C 1E 20 21 23 25 27 28 2A 2C 2E 2F 2F 2F 2F 2F 31 33 35 37 38 3A 3C 3E 3F 41 43 45 46 48 4A 4C 4D 4F 51 53 54 56 58 5A 5B 5D 5F 61 62 64 66 66 68 6A 6B 6D 6F 71 72 74 76 78 79 7B 7D 7F 80 82 84 86 87 89 8B 8D 8E 90 92 94 95 97 99 9B 9B 9B 9D 9E A0 A2 A4 A5 A7 A9 AB AC AE B0 B2 B3 B5 B7 B9 BA BC BE C0 C1 C3 C5 C7 C8 CA CC CE D0 D1 D1 D3 D5 D7 D8 DA DC DE DF E1 E3 E5 E6 E8 EA EC ED EF F1 F3 F4 F6 F8 FA FB FD FF FE FC FB FA FA FA F8 F7 F6 F5 F3 F2 F1 EF EE ED EB EA E9 E7 E6 E5 E4 E2 E1 E0 DE DD DC DA D9 D8 D6 D5 D4 D2 D1 D1 D0 CF CD CC CB C9 C8 C7 C5 C4 C3 C1 C0 BF BE BC BB BA B8 B7 B6 B4 B3 B2 B0 AF AE AD AB AA A9 A9 A7 A6 A5 A3 A2 A1 9F 9E 9D 9B 9A 99 98 96 95 94 92 91 90 8E 8D 8C 8A 89 88 87 85 84 83 81 81 81 80 7C 79 75 72 6E 6B 67 64 60 5D 59 55 52 4E 4B 47 44 40 3D 39 35 32 2E 2B 27 24 20 1D 19 16 16 12 12 12 11 11 11 11 11 10 10 10 10 10 0F 0F 0F 0F 0F 0E 0E 0E 0E 0E 0E 0D 0D 0D 0D 0D 0C 0C 0C 0C 0C 0C 0C 0B 0B 0B 0B 0B 0A 0A 0A 0A 0A 09 09 09 09 09 08 08 08 08 08 07 07 07 07 07 06 06 06 nml-0.4.4/regression/expected/023_engine_override.nfo0000644000567200056720000000141712643457545023706 0ustar jenkinsjenkins00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d4 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\23" "NML regression test" 00 "A test newgrf testing NML" 00 3 * 16 00 08 \b1 01 FF \wx0000 11 \dx74736574 \dx44434241 4 * 16 00 08 \b1 01 FF \wx0000 11 \dx234C4D4E \dx78563412 nml-0.4.4/regression/expected/007_townnames.grf0000644000567200056720000000027612643457545022555 0ustar jenkinsjenkins00000000000000GRF‚  ¬4ÿ small mediumbig village towncityÿtiny villagePÿ‚US StationsTest stationnnetjesÞTeßt Stätiöµſ  MainCapital…nml-0.4.4/regression/expected/010_liveryoverride.grf0000644000567200056720000001252512643457545023606 0ustar jenkinsjenkins00000000000000GRF‚  éÿ#6ÿCINFOBVRSNBMINVBNPARBPALSWBBLTR84ÿNMLNML regression testA test newgrf testing NMLÿÿýýýýý ý ý ý ý ýýýýýýýýýýýýýýý ÿÿ ÿþ ÿý ÿÿý' ÿÿÿ ÿÿýÿÿý'() ÿÿþýÿöÿÆÈÈÈÈÇèÊÊÊÊÉÈÆàËÊÉÇËèèÈËÌÊÉÊËÌËÉÉèè ÌÊÊËè9ÉÌÆÇØ/ÊÆè!Ìè@ÈàÌÌèYà`àPÇÈà0ÉÊÈèJàhèP××èqÊÉר××èàÈÇÇàØÆ×××èÐà èòÿùÿèÐÇÉÉȘàÊÊËËÊÉ &ˆèèÊ̸9 ÊʈÌËÊÌÌ ÀNÊè)ÌÍÍÐÆÉèbÊè?è XØÐa ÉÌÌ  ØÈÌÌÍè<  ÉÇÇÈè‰ÌÍÌèd  (×ÇØÈÈèa ËÊ õ (×èÏ×רÉÉè& õ!ÐŽØ×××Çè( (À¡èר õÀ´ÈÆ È9 ˆà_±1: ðÿøÿèÐ ÇÇ( ÇÉÇÉàǘà'ÉÉÉÉÊÊËËËËËÊÉà:ÆÆÆÇÇÇèÈÈÇÉË XØ ÌÌËÊËËÈ,è.ÉÈè;è<ÌËÌàAÌ̾è)ÍÍÍèè ÊËÌÌÍÍÍÍõàjè<Ëà8èDÌÊØ Í̾jè:è?àBàØKÈ Í (X èè èè õˆ è   ¸ è>Ø èÐ (!¸ X×(èÐ (! õõ(((õ(ÆX !!!è!!jjà ˆX××׈ˆ×ÈÆúúÿùÿÉàà ÀÊÊÊʈÊËËËˈÉÉÊàè-ÈÉÉÈÈHÉààÊÊÈõèÉÆÈèÐ1è{ (((ÉÆÈè]Ìà^èD X!!(ÊÉè†èÌ×X !(( ÉÊàËÌ(è¼Xjè(õõàWÌÌÌ¿jàÒXj!! õõÊÊÌ ¿j(!ÐâXjjè- j X Ø*Ø.èZ õ XXjXر ؈XèZØØˆØ×¡!×ÆÈˆ××טáFÆÙF ®ýÿöÿÈÈÉÉÉÉÇÉÊÊËÊÊÉèËÌè ÈÉÈÈèÌÌÐ è&ÊÊÆØ7 ÉÉÆÊËÉÇèÌËà(Ø8àOËàQè/èZ ÉÊËÌ ËèÌ ÈÉËÈ`ËÌèY#ËÈ( Ë j¿X!¿ ˆˆj j j؈Xjjˆˆ×è ˆØjÇ×ØØˆØ×Ç×××× öñÿúÿèÐà ƈàÉÊÊɈ&èËËÊÊÀ>ÈÈÉÈàËÌÌÌËÐÈÉËà'ÌÌÍÌè,ØIà7èè)ËÊØ(àKè6ÌÍËËèÉÊÊèIËÍÌè  (ËËèsÍÍ  !׈¿(ËÌè àjˆØX¿õÌÍõ (è؈jjj  (Ð רˆˆˆ èÈ&Ø ×ØØØõ XØ©××××ÉÈ׉/èú‰A 5 ðÿøÿÇÇÇèè (ÇÇØØÀ ØÇÈÉÊÊÊËËËÉÉËˈ!ÉÉÈàËÌÌÌ (ÌÍÈÈÆÆÆÆÐT¾ÊÊÉÉè@à#èCÊÈàFØ ËʈX!ÊÊÊËÌÌÍÍè-èKÍÍÍÌÍ̈j¾ÉËà èè˸LËÊØØ!õõÐ|èÌÍÊÊÌÊËèÐè‘×( èàèè j× õ¨   è X××èa èà !° àØÆ! (!!!((õõõõ èàÐõ(è È׈ˆ××è jjàà ØXj äúÿùÿÆÇÈÈàà ÈÇÉÊÊÊÊɘ ÆÈÊËËËʈ°. ˆˆÈÈËˈèÀEØÈÊè,ËÊÀ!!ÉÊËËËÊÉÉÊÉÐ* !!ÈèÊÉè\èràÈàèYÉè)àÉè. Æ×X jÐàà[ ×Xjj !Éè.ÊØØØXXjàD(èkØØÀßè !((ÊÉØØ×ظôÐè)××ש àDÈè‰XˆÐ& ƒýÿõÿ×××׈×àˆ×ˆXÈjˆØˆàèXjèØàèØè& ÐjȈ èEÐèè8ÇÈ××ËËjèèu ËËÈÉ׃×Êà„ƒèÇÉàŽèÈè‡àÇXèÊˈˆˆèˆˆËòÿ÷ÿèÐè ×׈ˆØˆXXXˆ(ˆXjXj ¨à(Ðè¨(èè(èD ˜jXˆ×èƒàèxàR°ÊËЈ¨(ËÉÊËèŒ×  ÉÉÊÊXXÈÈ××j°Êàj!È×X××XàxÐ( ÉÉj!È׃×ÇØdØÐñÇׄƒ×ÇèxèÀàˆX×Èè9ɰÑ×ÈÉÉXX‘-×××Èàx‰TéUˆí ðÿøÿ×××èР ØØˆ×ˆàØ à àˆˆXèèÐàXXèàèˆXjèjjjà:èèèàØjàjè è\è&è)а  è àoÈmÀG¸ àb×É „ƒ àÀ Ëè …„ƒàÀÀ àË×ÈÈÉÉÉèÊÊÊèи ËË×ÈËèØÌàÌÌèèÍÍèøá ××ÐGÐMÉàöØ!é !jááà Ƚúÿ÷ÿXjjàà Àˆ×ˆXˆØ×à  0×××è׈õ°j¸0(ƒ ¸è_ÈHÇÉõ¸XˆjX Ø`ÈÈÉ XÐ××èɰHè¼jÉjjXØȸ`èÉËjXàFÀÊàèàµØØxÊ׃ȨÊ׃„¸¸0Ê×Xˆ¨Øýàˆé¡Càèn˜Àárƒýÿõÿ×××׈×àˆ×ˆXÈjˆØˆàèXjèØàèØè& ÐjȈ èEÐèè8ÇÈ××ËËjèèu ËËÈÉ׃×Êà„ƒèÇÉàŽèÈè‡àÇXèÊˈˆˆèˆˆËòÿ÷ÿèÐè ×׈ˆØˆXXXˆ(ˆXjXj ¨à(Ðè¨(èè(èD ˜jXˆ×èƒàèxàR°ÊËЈ¨(ËÉÊËèŒ×  ÉÉÊÊXXÈÈ××j°Êàj!È×X××XàxÐ( ÉÉj!È׃×ÇØdØÐñÇׄƒ×ÇèxèÀàˆX×Èè9ɰÑ×ÈÉÉXX‘-×××Èàx‰TéUˆí ðÿøÿ×××èР ØØˆ×ˆàØ à àˆˆXèèÐàXXèàèˆXjèjjjà:èèèàØjàjè è\è&è)а  è àoÈmÀG¸ àb×É „ƒ àÀ Ëè …„ƒàÀÀ àË×ÈÈÉÉÉèÊÊÊèи ËË×ÈËèØÌàÌÌèèÍÍèøá ××ÐGÐMÉàöØ!é !jááà Ƚúÿ÷ÿXjjàà Àˆ×ˆXˆØ×à  0×××è׈õ°j¸0(ƒ ¸è_ÈHÇÉõ¸XˆjX Ø`ÈÈÉ XÐ××èɰHè¼jÉjjXØȸ`èÉËjXàFÀÊàèàµØØxÊ׃ȨÊ׃„¸¸0Ê×Xˆ¨Øýàˆé¡Càèn˜ÀárfýÿöÿÈÈÉÉÉÉÇÉÊÊËÊÊÉèËÌè ÆÐÉȈˆ ˆ0ˆ@ØPÇÈÈè^ÉÊènàiè Ë×רrר××èàÈÇÇàØÆ×××èÐà ·óÿùÿèÐè ƈèÉÊÊɈÊÊËËÊʰ;ÇØÌÌÌËÀÀÍÌè*¨6àè&ˆÊˆ  (ÇÇÈènÀ6  !רÈÈèpËÊ  è j×רÉÉèH õà Ø×××Çà6 ØèרàHÈÈÆ ! ‘×׈ס Å ôÿúÿÆÆÇÇÇèØÈ ÈÊÊËËËèа ÊËÊØÊËÌÍÍÍÌÌÐÌè è ØËèà$ˆ<Ð:ˆà4ÍÍËÌè@èGØÐ ÌÌ    à !  è È  èè(!¸ èˆ(!õõõèØ! ¨ (ˆØ×XX èÐÀ Xרj¸úÿùÿÉàà ÀÊÊÊʈÊËËËËÉÉʈɈèaõˆ(((ˆX!!èÈàXào ÊÉÉ×X !(!àBèoØè*j jàBà-ØØààjjàBè™ØØÈÐ×XØB((ÊÉØØ×ØÀëàB(è'×××°ÿèX!Ƙé!×è9ØÙ6fýÿöÿÈÈÉÉÉÉÇÉÊÊËÊÊÉèËÌè ÆÐÉȈˆ ˆ0ˆ@ØPÇÈÈè^ÉÊènàiè Ë×רrר××èàÈÇÇàØÆ×××èÐà ·ñÿúÿèÐè ƈèÉÊÊɈÊÊËËÊʰ;ÇØÌÌÌËÀÀÍÌè*¨6àè&ˆÊˆ  (ÇÇÈènÀ6  !רÈÈèpËÊ  è j×רÉÉèH õà Ø×××Çà6 ØèרàHÈÈÆ ! ‘×׈ס Å ðÿúÿÆÆÇÇÇèØÈ ÈÊÊËËËèа ÊËÊØÊËÌÍÍÍÌÌÐÌè è ØËèà$ˆ<Ð:ˆà4ÍÍËÌè@èGØÐ ÌÌ    à !  è È  èè(!¸ èˆ(!õõõèØ! ¨ (ˆØ×XX èÐÀ Xרj¸úÿùÿÉàà ÀÊÊÊʈÊËËËËÉÉʈɈèaõˆ(((ˆX!!èÈàXào ÊÉÉ×X !(!àBèoØè*j jàBà-ØØààjjàBè™ØØÈÐ×XØB((ÊÉØØ×ØÀëàB(è'×××°ÿèX!Ƙé!×è9ØÙ6nml-0.4.4/regression/expected/013_train_callback.grf0000644000567200056720000001714012643457545023466 0ustar jenkinsjenkins00000000000000GRF‚  <ÿK6ÿCINFOBVRSNBMINVBNPARBPALSWBBLTR84ÿNMLNML regression testA test newgrf testing NMLÐÿ2ÿ WDPRSCRPCMNTWOODLVSTSTELVEHIBRCKWOOLBUBLTOYSFZDRFRUTFRVGFOODOIL_GOODWATRMILKCOALIOREAORECLAYGRVLSANDGRAIRSGRMAIZCOREFERTCTCDSULPWHEARFPRCOLAPETRPAPRTOFFSUGRPASSMAILBATTSWETRUBRFMSPENSPMNSPFICRPLASPLSTÿÿRAILELRLMONOMGLVTRPDÿÿýýý ý ý ý ý ýýýýýýýýýýýýýýýýýýý ý!ý"ý#ý$ý%ý& ÿÿ ÿþ ÿý ÿüÿý€ÿÿÿý ÿ ƒÿÿÿÿÿÿý‰þýÿü‰Gÿýü ÿýÿý‰ ÿÿ€ýSÿþ‰Gÿ€//€ € €€€€ ý5ÿý‰Gÿ€€ € €ý ÿ }ÿˆ ÿ ~}ÿº ÿ  ~ÿu ÿ |ÿÔ0 ÿ }|ÿŽ ÿ ~ }ÿ1 ÿ {ÿ ÿ |{ÿ ÿ } |ÿ ÿ zÿ• ÿ {zÿQ ÿ | {ÿ£ÿÿ ~ÿ}ÿ|ÿÿÿÿt % ÿ ÿÿ ÿ ~ÿøÿÿÿ ÿÿM~ÿOÿRÿÿt()Ë, ý*@z ÿ& ¶  ( 0L$%ÿÿtNML Test bulk wagon ÿÿt!ÿý‰ÿþýü!ÿÿ‰ÿ€€ü!ÿý‰ ÿÿþý66üÿü‰ ÿÿÿ66ü ÿÿtÿüýsýÿ÷ÿèÀØè ×ר Ø ààˆˆˆ è0ØvèzàÀjàjˆˆˆèÅòÿùÿèÐèàè×°0ØÐÀPØà(ËÐ\Ðà)ÊËËè¸è> ÉÊËÀèèÊj!èàhËÈÉj!àè2àPר×ÐgØPÐjèàiàPÀ×ר-èP°Ðjàd‘*èˆ Ü ðÿùÿèШ èи è  ÐÐØÈ ¨Øàà××à`èhˆ‚È~Ѐ ÉÊËËËèèÈà£ØØ è©àÈèÄØètÈÉÊÊÊà³à¸è!ÐèIè@ ÈÈÉÉÉÈŸèðÐàj Ø×XXˆˆXX×××èØˆˆˆàà Xj!××j!è è Ðà À ¾úÿùÿàà Àèàè>ؘ0ÊˈÉÊ˰°è‡èsèà jXÈÉËÊèŸè××ÐÊÉØ`èèؾ×ÉÐ`èÈèÈÐ`èÀàà`Ðj©×è`Ø-ˆˆ¡jXè,jÐÐ( pýÿ÷ÿè׈X×××׈ˆ×à×XˆØÐXÈèÈØÀÈèHˆˆ( Ø Øv àÀjàjè}è€ ÏòÿùÿèÐèà׈X××°0؈×Xˆè(ËÐ\¸ˆÊËËè,¸<èÉÊËØPàe×èè Êj!×èËÈÉj!è2àPר×èºÐPÐjèèØPÀ×ר-èP°Ðjàd‘*èˆ î ðÿùÿèШ ××××׈Xˆˆˆèàè XXXXà ׈èèà$à(èè,XØ"Ø ØÐ"È%Ø&Xˆ×à`èhˆ‚ÀèÉÊËËËèèÈà£ØØ àÈèÄ ØÈÉÊÊÊèKàÈèIè@ ÈÈÉÉÉè àÈàjØ×XØîÀ¾á ÙÑ j!××j!è è Ðà ÀÃúÿùÿàà Àè×׈ˆè>×××XÊˈˆÉÊ˰°è‡ˆˆ×XXˆà jXÈÉËÊØˆè××ÐÊÉÐ`èؾ×ÉÐ`èÈèÈÐ`èÀàà`Ðj©×è`Ø-ˆˆ¡jXè,jÐÐ(ýÿ÷ÿnnnnmnoonlllllkØklknèèkmmkkkèmm5555iiè5kè nèè Øè 5onØ5oÀˆoèfàg oi5kkllmnii5à mÈiØ ËÈÉÉÊÊÊÊËÈÈÐ ×××è×׈ˆˆèÅòÿøÿèÐmnnoàllk5mmèk55ii°0Ðlèno5n°<làè@5m5 è)mn5lÐØ(à+kØèÉËÈkl°ÊÊÉiè6à,È(làj!i5kmmÀÊÐii55è@ÐÉÉÇØíˆ××ÐlÉÉÀXˆèè.àÀ××àÀÉÇØdˆˆä ðÿùÿnnmmmèШ nnnoonllmlllèØkèàkkkk55à ¨kèØ5è555iÐ@¸Ø6à8ØiiØ èhˆ‚È~Ѐi5kkièWill5àÀimà×ièhàlèilèkÀ m5i׈××àii°$è¥À$×׈ˆÈÈÈèÐÉÉÉèÐÉÉÊÊÊˈ×XXˆˆXX×èà Xj!××j!è‰XˆèèˆX׸Èúÿøÿmmmnàà Àmnlkkkèlài5mnØ055immˆà-ikØ5ØHnoà€ÈÈ5èÐ0è5k5àÇÈÈÈkikÈHèèRnojXÀØHØmmØÉè0ilimnèllÐÓÇÈlèè¿ÈØÉè0là×׈¹àÉk؈X©Øà×ס,jXÈɈÈÐ(|ýÿ÷ÿnn׈X×oon××׈ˆ×Ø×XˆèàˆmmØÐ¨nÈàè@ˆÈ@ˆoÈ0ØàèXÐ nmmnoooi5kkllmnii5à mÈiØ ËÈÉÉÊÊÊÊËÈÈÐ à‡à‹è…èˆÅòÿøÿèÐmnnoàl׈ˆ mmlˆ×ˆX××°0Ȉ×Xno5n¸<؈è(5m5ˆmn5lÐÀ(×ÐèÉËØ<àe׸ÊËÉi5mn×èÈ(làj!i5kmmÀÊÊÊà ii55klkÐÉÉÇØíèUÐlÉÉÀà¤×è.àÀ××à¼ÀÉÇØdˆˆî ðÿùÿnnmmmèШ nnnoonll××××׈Xˆˆˆèàè XXXX55à ׈èèà$à(èè,XØ"Ø ØÐ"È%Ø&Xˆ×à`èhˆ‚È~Ѐ i5kkikklill5àÀimà×i55è5è$è èkÀ m5i×èiiiii°$klèPÐLà¹ÈÈÈèÐÉÉÉèÉÊè ËÊÊÊÊËˈXØí¸ØàÐj!××j!ØXˆèè ˆXظÏúÿøÿmmmnàà Àmn×××kmnˆˆ××è˜i5Ø,Xˆ55immè/i5ik¨noà€ÈÈ5èØ0ˆ×XXˆ×5àÇÈÈÈkikÐH ˆˆXX××inojXÀØHènommØÉè0ilimnèllÐÓÇÈlèllÀØÉè0làèÕ¹àÉk؈X©Øà×ס,jXÈɈÈÐ(fýÿõÿÉÉÊÊËËÌÌÉXXXXXˆÀЈ興ˆèÌÉØØØØ×רè ÐÀØØÀ˜ˆ((ÐwÌÇÈèè‚Øè‹ÀÀÐ èjà)jËЈ¸òÿøÿèÐÊËÌÌàˆˆØØàèØ××°0Øè+àÉÉÐXÉÊÐØØËèÊǰè=ÈËÈà(XXXØXèhÀËÈèˆÐ°ÌÈÈÇàT؈Ë˰j!ÈÈÈè0ʘèÈÇǰ<ØÇÈÉèjèAÉèv ××àDÐjÈd‘@èˆÜ ðÿøÿÊÊÊèР Ð ʈˆˆˆˆXØØ ° רËÐ XXèˆ%à×Ìè@àˆCØØØØØ××ר ËÌÌÌèР àÈÉËÉèÉËÊèÐÉËËèèËà ÇËÊèР ÇËÇè ×è Ɉ#¸ ÊÌ×ÈÈÌÉèÈÌÊèÐÀ ÈÊè@ÇèÀ#ˆ)è› ×ÇÇÇÇÇÈÈÉÉèÉèÐÈ é j!××j!è è Ðà ÀœúÿøÿÉÊÊËàà ÀÉÊˆØØØË˜èˆàḛ̀0ÊÉÉÊØ˜ÊLjÀ˜ÌØHÉà 0×àÉÈ×××èjX°ÊËàÌÌÉè¾àȼǨHË˰ê°ÊÊ¡À0àj©ÐHè-ˆˆ¡,àÊjÐÐ(týÿõÿ'ÉÉÊÊËËÌÌÉØˆXXˆØÌɈFIJHˆÌÉFGGIIHÌÉGFHHJIÈÐHÈè)ˆ((Jà0GFII @Ø7èPÐwÌÇÈèè‚Øè‹ÀÀÐ èjà) jËØØØØ××´òÿøÿèÐÊËÌÌàXˆØØàHIJH××°0ÉÊØIHJÌÌÉÉIHËèÊǘ(IÐËȈØËÈÉʈˆFàeÈ<àÌÈÈÇÉÊGGè<˰j!ÈÈÈè0ʘèÈÇǰ<ØÇÈÉèjèAÉèv ××àDÐjÈd‘@èˆî ðÿøÿÊÊÊèР ÐʈˆFGGHHIJIIIàèàJJJˆØËè Fàè!à$è'àè,JØ"Ìà à>àB¸"Ø*IHJIHà ËÌÌÌèР àÈÉËÉèÉËÊèÐÉËËèèËà ÇËÊèР ÇËÇè ×è Ɉ#¸ ÊÌ×ÈÈÌÉèÈÌÊèÐÀ ÈÊè@ÇèÀ#ˆ)è› ×ÇÇÇÇÇÈÈÉÉèÉèÐÈ é j!××j!è è Ðà À°úÿøÿÉÊÊËàà ÀÉÊˆØØØË˜èˆFGIIÊÉÉÊGHHJØ̸ÇàIˆèÐIÐ0ÌØ0Éà¸JIØ×àÉÈÀÊè0Iè×èjX¨ËàÌÌÉè¾àȼǰHè°ê°ÊÊ¡À0àj±jÐHè-ˆˆ¡,àÊjÐÐ(€ýÿöÿÀØÀèàà àèÀà(èˆ ˆ è(àwèzèXè è\è ˆˆˆèˆˆjàjÐ ÈòÿøÿèÐè °0àèØÀPàààÐØà>à3˸àSÐÊÊÈè?àFÊÊèNè6ÀÈj!Ø2àèWàèè<àP臈Øíè+ØØÐjàÒÉèØ¸××èCàרÐjØdˆˆ!Ë ðÿøÿèШ èÐèèè¸ ˜Øà@ 1ÐØÈ èhˆ‚È~Ѐàs †à’È¢ˆàièˆ%Ð"ˆˆˆÉÉÉèÊÊÊèÐÈ Ëˆà!ˆ^Ø[ÑàȰØñÐüÑØ“èa ×XXˆˆXX×××èØˆˆˆàà Xj!××j!¨"×׸"Ëúÿøÿàà ÀèàØ0ˆÈèVèà€ÈÉàèàÈɨ jX¸ÐØÈ0èÐÓˆèèÍè½ÀéˆÈ衈è)Éèj©ààˆˆ¡,jXè0jÐÐ(#‰ýÿöÿè@CDB@AACCBA@BBDCÀØÐDCCè CCD؈ ˆ Ð8èWDBèàwèzè è ˆˆˆèˆˆààÐ$ÖòÿøÿèÐèèBCDB°0àCBDÀPÐCBàиCè3˨(BÈÊÊà<@àeBàFÊÊèNAAà(Èj!Ø2àèWàèè<àPÇØíè+ØØÐ jɨ××èC褠ÐjØdˆˆ%é ðÿøÿèШ @AABBCDCCCèCè è DDDDà @àè!è$è&BCàà#à"Ø à>àBÐ"èJàNè*DCBà`èhˆ‚È~Ѐ èÐà È¢ˆè$ˆ%È"ˆˆˆÉÉÉèÊÊÊèÐÈ Ëˆà!ˆ^Ø[Ñàà_ÐbЉàÐà† ˆ×XXˆˆXX×××èØˆˆˆàà Xj!××j!è è Ðà À&Ðúÿøÿàà Àè@ACCABBDˆCˆÈèVÈà€ÈÉèà0CBDDÈɸCè jX˜ØÈ0èÐÓÇèèÍÀéÇÈè¡Çè)Éèj©ààˆˆ¡,jXè0jÐÐ(nml-0.4.4/regression/expected/020_recolour.nfo0000644000567200056720000000740212643457545022371 0ustar jenkinsjenkins00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d19 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "W" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\20" "NML regression test" 00 "A test newgrf testing NML" 00 // param[3] = param[\DR] 3 * 9 0D 03 \D= \DR FE \dx000308FF 4 * 7 06 03 02 FF \wx0003 FF 5 * 5 0A \b1 \b3 \w0 6 * 257 00 00 00 00 00 00 00 00 00 00 00 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF C0 C1 C2 C3 C4 C5 B2 D9 0A 0B 0C 0D 0E 0F CE CF D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF F0 F1 F2 F3 F4 F5 00 00 00 00 00 00 00 00 00 FF 7 * 257 00 00 00 00 00 00 00 00 00 00 00 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF C0 C1 C2 C3 C4 C5 3E 3F 40 41 42 43 44 45 CE CF D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF F0 F1 F2 F3 F4 F5 00 00 00 00 00 00 00 00 00 FF 8 * 257 00 00 00 00 00 00 00 00 00 00 00 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF C0 C1 C2 C3 C4 C5 9A 9B 9C 9D 9E 9F A0 A1 CE CF D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF F0 F1 F2 F3 F4 F5 00 00 00 00 00 00 00 00 00 FF 9 * 6 01 01 \b1 FF \wx0008 10 opengfx_generic_trams1.pcx 8bpp 48 56 8 18 -3 -10 normal 11 opengfx_generic_trams1.pcx 8bpp 64 56 20 19 -14 -5 normal 12 opengfx_generic_trams1.pcx 8bpp 96 56 28 15 -14 -8 normal 13 opengfx_generic_trams1.pcx 8bpp 144 56 20 19 -6 -7 normal 14 opengfx_generic_trams1.pcx 8bpp 176 56 8 18 -3 -10 normal 15 opengfx_generic_trams1.pcx 8bpp 192 56 20 19 -14 -9 normal 16 opengfx_generic_trams1.pcx 8bpp 224 56 28 15 -14 -8 normal 17 opengfx_generic_trams1.pcx 8bpp 272 56 20 19 -6 -7 normal // Name: foster_express_group - feature 01 18 * 9 02 01 FF \b1 \b1 \w0 \w0 19 * 9 03 01 01 FF \wx0058 \b0 \wx00FF // foster_express_group; nml-0.4.4/regression/expected/019_switch.grf0000644000567200056720000000071112643457545022040 0ustar jenkinsjenkins00000000000000GRF‚  ·ÿ 6ÿCINFOBVRSNBMINVBNPARBPALSABBLTR84ÿNMLNML regression testA test newgrf testing NML3ÿÿИcoal˜diamonds˜Extra info for coal mine: {&ÿ ÿ‰$`ÿÿÿÿbøÿÿ€€/ÿ ÿ‰  ÿ€ ÿ € ÿ ÿ ÿ ÿ!@" ÿ þ‰€ÿ ýÿ ý‰ ÿÿ€ý+ÿ ý‰ ÿÿÿ77€::þ;;ýÿ ýnml-0.4.4/regression/expected/027_airport_layout.grf0000644000567200056720000000022012643457545023606 0ustar jenkinsjenkins00000000000000GRF‚  ~ÿÿÿÿÿ€€À €€ÿÿÿÿ$ÿ ÿ þþþF€nml-0.4.4/regression/expected/025_loop.nfo0000644000567200056720000000140012643457545021505 0ustar jenkinsjenkins00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags // param[127] = 0 0 * 9 0D 7F \D= FF 00 \dx00000000 1 * 2 10 10 // param[126] = (param[127] - 5) 2 * 9 0D 7E \D- 7F FF \dx00000005 // param[126] = (param[126] << -31) 3 * 9 0D 7E \Du<< 7E FF \dxFFFFFFE1 4 * 9 09 7E 04 \7= \dx00000000 02 // param[127] = (param[127] + 1) 5 * 9 0D 7F \D+ 7F FF \dx00000001 6 * 6 09 9A 01 \71 00 10 nml-0.4.4/regression/expected/014_read_special_param.grf0000644000567200056720000000106012643457545024323 0ustar jenkinsjenkins00000000000000GRF‚  ÿçÿCINFOBVRSNBMINVBNPARCPARACTNAMEName of the int settingTDESCDescription of a settingBMASKBLIMIBDFLTCBTYPEBMASKBDFLTCBTYPEBMASKBDFLTBPALSABBLTR84ÿNMLNML regression testA test newgrf testing NMLÿ  ÿ ÿÿ  ÿ ÿ ÿ ÿÿ  ÿ ÿ ÿ þNML ÿ žžÿ ÿ ÿÿ ž ÿ ÿ ÿ Žÿþÿÿÿÿ ¤ ÿ }þÿÿ ÿ ~}ÿÿ ÿ ~ÿ ÿ ÿnml-0.4.4/regression/expected/029_base_graphics.grf0000644000567200056720000000207612643457545023340 0ustar jenkinsjenkins00000000000000GRF‚  bÿðððïïïðððððððððÿ££££¤¤´´µ³³³²³µýýýýýýý ý ý  Dþÿ  èÐè èèè6 èèà èàèØÐ؈ˆØ:èOè,à/B ÿÿèàèàèèÐ ØØ%è¸/Ð$À'Ð?è-à$àà/à6E èØè èÈ Ø Ðè(È*Ø à*è"Ð*À?Ð ØDà&à?ØDèU : àÐ Ø Ðà è#ØØ#àÐ àÐàBè+è   ) ààà ØØ° Ð'Ð-nml-0.4.4/regression/expected/015_basic_object.nfo0000644000567200056720000000201612643457545023146 0ustar jenkinsjenkins00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d6 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\15" "NML regression test" 00 "A test newgrf testing NML" 00 3 * 33 04 0F FF 02 \wxD000 "Miscellaneous" 00 "Basic object" 00 // Name: obj_basic_tile - feature 0F 4 * 18 02 0F FF \b1 \dx0000058C \dx03078A48 \b0 \b0 \b0 \b16 \b16 \b30 5 * 41 00 0F \b11 01 FF \wx0000 08 "MISC" 09 \wxD000 0A \wxD001 0B 07 0C 11 0E \dx000A96C9 0F \dx000AC196 0D 01 14 01 10 \wx0800 16 04 6 * 7 03 0F 01 00 \b0 \wx00FF // obj_basic_tile; nml-0.4.4/regression/expected/004_deactivate.nfo0000644000567200056720000000075712643457545022660 0ustar jenkinsjenkins00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 6 0E \b1 \dx44434241 1 * 10 0E \b2 \dx04030201 \dx48474645 nml-0.4.4/regression/expected/016_basic_airporttiles.grf0000644000567200056720000000040512643457545024416 0ustar jenkinsjenkins00000000000000GRF‚  NÿÿýÿÿÀ€ÿÿÿÿýÿöÿÆÈÈÈÈÇèÊÊÊÊÉÈÆàËÊÉÇËèèÈËÌÊÉÊËÌËÉÉèè ÌÊÊËè9ÉÌÆÇØ/ÊÆè!Ìè@ÈàÌÌèYà`àPÇÈà0ÉÊÈèJàhèP××èqÊÉר××èàÈÇÇàØÆ×××èÐà nml-0.4.4/regression/expected/028_font.grf0000644000567200056720000000131612643457545021507 0ustar jenkinsjenkins00000000000000GRF‚  ’ÿ{ýýýýÿ{ýýý ý ÿ{ý ý ýý*D þÿèÐààØ ¨Ø°È#D þÿèèèР è)D þÿèàÐØ Ø èØ¨à/D þÿèР ˆà'èèˆ>MD à Ø àD Dèàèà è DèÐè˜ 2DþÿèР ÐÐ °¸¨À#ˆ?ˆ¸ DþÿèèР °2Dþÿèи ÀÈ ˆÈ,À%È-À+˜ ? LÀpÐ;D þÿèР ˆˆ'ˆ6ˆEˆT¸ è ØØ¨ 'ˆ”ˆ£ˆ²ˆÁˆÐÀßnml-0.4.4/regression/expected/030_house.grf0000644000567200056720000003504412643457545021662 0ustar jenkinsjenkins00000000000000GRF‚  0 ÿa6ÿCINFOBVRSNBMINVBNPARBPALSWBBLTR84ÿNML0NML regression testA test newgrf testing NMLÿÿÜBreweryÿÿ PASSMAILGRAIWHEAÿÿýýý ÿÿÿý ýýýýý1ÿÿCÀ€ƒ€€0…†# ˆ‰Šjÿÿ‰C ÿ    €   @   } ÿÿÿÿ ‚}€ ÿÿÿÿ  }‚ ÿÿÿÿ ƒ@       „}‹ ÿÿÿÿ ÿÿÿÿ   }„ ÿÿÿÿ   …C ÿ     }‹ ÿÿÿÿ †F ÿ   ‡}Œ ÿÿÿÿ    }‡ ÿÿÿÿ   ˆF`ÿÿÿÿÿ   ‰F`ÿ6Šÿÿ,ÿþ‰  ‹ Œÿÿ,ÿý‰  ‹ Œÿÿ,ÿü‰ ÿÿÿÿ ‹ Œÿÿ,ÿû‰  ‹ Œÿÿ,ÿû‰}ÿÿþýüû,ÿü‰ ÿÿÿÿ ‹ Œÿÿ,ÿý‰  ‹ Œÿÿ,ÿþ‰  ‹ Œÿÿ,ÿÿ‰  ‹ Œÿÿ,ÿÿ‰}ÿÿüýþÿÿÿ‰_ûÿÿû‰Fÿÿ€þ€)ÿþ‰F ÿ    ÿÿÿý€;ÿþ‰_(   ÿÿÿÿ }ÿ0ÿ þý€ÿý‰Dÿ€€ ÿ ~ÿ ÿ  GRAI ÿ ~ÿ ÿ }ÿ ÿ  WHEA ÿ }ÿÿ ~} ÿ  ÿ {ÿ ÿ zÿ ÿ  GRAI ÿ zÿ ÿ z ÿ {ÿ ÿ |{ÿÿ ÿ }|ÿ ÿ ~}ÿ ÿ zÿ ÿ yÿ ÿ  GRAI ÿ yÿ ÿ y ÿ zÿ ÿ {zÿÿ ÿ |{ÿ ÿ }|ÿ ÿ yÿ ÿ xÿ ÿ  GRAI ÿ xÿ ÿ x ÿ yÿ ÿ zyÿÿ ÿ {zÿ ÿ |{ÿ ÿ xÿ ÿ wÿ ÿ  GRAI ÿ wÿ ÿ w ÿ xÿ ÿ yxÿÿ ÿ zyÿ ÿ {zÿÿ~ÿ9}ÿ=|ÿA{ÿEÿ²ÿÿ()*+ÜÜÜÜ 0  d  ÈÈÈÈúúúúð ‚!”"8””””  ÿÿÿ9ÿý‰  ÿ ÿÿýûþHHÿ ÿÿý ÿÿ/ÿý‰  ÿ ÿÿûþHHÿ ÿÿý ÿÿ/ÿý‰  ÿ ÿÿûþHHÿ ÿÿý ÿÿ/ÿÿ‰  ÿ ÿÿûþHHÿ ÿÿÿ"@áÿèР Ð{{llˆ"ˆ1ˆ@˜Oll{l{{kkˆdˆsˆ‚˜|lièA{5{ˆ¦ˆµˆÄÐ|èx{k|l{ØBkiˆèˆ÷‰ièvi{kè|èÁ5lè 5ˆÆ‰8ˆø{{j{klièºk„5éDié 55‰l‰{¹t|á4k{èvà9è‰èŽèÌéM‰Œ‰¼É²é1èðèølèk5à„éFàMàÔ‰ð‰ÿán(|5é¹(éjØIèûkláèƒ{iˆÆ‘ðésklkèzjá½{{„k|è¸|léŒiilèÙጉιtá&è¸Ùuè2i|kiéèéKêKèèÖ„èJ{‰ÎâÄ5È_iè~évØùà‰à‘êEêéŒÙéÜêYˆ>é¥éïèñá·èAé…éþØÚIèêè[|à¤àª”5à+àúè7àaØÉéé}èÂéÏêOê |êPé”l5liÙ |À||à&kl(â2jâ=ê;áyÚ é<àŠâ ؽè!áéeëØ`ØøØ5â8ÑAà€à¶k{„êKjl؈j|ê’è¤ëWjë\è®Ò®|éoÑàØºÙ¦ØvÛÀêéãŒê lê{(5á$Äâ®Ù.ëìèåÚxiØé>éAéOâÂèùë’ÒØá)¬Záèéáþä4é5lkâ†áˆë~jáÐáƒëlljáÄŠÊÚøã²„Üuê;é¹|ijéÌâ|Ø›è“ãØÙêŒRÃ:Ù>êvëãõìÑj„kjêŒäâ׋Fœ6äxê„ë¾á¹ã‡ê“ãÂÑíŒpäxáŽì@áDã?ÜÀãHÑì²ÁÊ äÀão5êQÚ¿ãíJŒ„þà í;ì…ìÃÙ-|Ú¿íIˆŽ;›NãÊåBáûêÍ㿎y‹ÎëSÚøí éÊëuŽŽ·ŽÆÈÆìsÒË|ŽôœÛ¼º3BœVâæiŽ|rŽˆi5Žº°Ê@áÿèР Ð#$õõˆ"ˆ1ˆ@˜O}#õõ##"ˆBˆrˆ¨|#õ"ès舄ˆ´ˆÃ¸> à|à>àJˆèˆ÷‰à|Ð: sèGÐ ‰*‰9ºè6 $$ è#8#ésõ}#8‰J‰z¹‰$$àºè¾"}}#}$$ØÂ}éJ‰Œ‰º¹6Àô$õ$#èwõ}è| Á‰Î‰þØø õõéyØ„á> }}}ÙFèN銉²É6ؾØ÷èº áÆàÒÙÊéÖ$ŠtÂlèy èéɲ}áøàõê„8áÙáÖˆ„ʪ}Ñð88éôèÂÚ|ê¾ØV ééé„àßàÊŠªàªàü"éu Ø»éx ê}è¾é|é‡ê }éRéÒ$ѳ:Ò""Ù"â.ê2}#á¹èìâÆs"ÚˆâÊ""‰ŒÐøØ+ël¸¶#è)ê s} ê:àŒàKà€õêVºÖëœÁT#á:è-ët$ësê.ê|ã€éÎ }$èɹœõ8sÛVâZéŒÙôÉtéý8Û2""sÈèèNé è‰àÏ8#ê”éEˆÂÉàÚ"â*êûë9Û|}àŒÚ> ÓJ¨ˆªôÓ¦s á| s$ë5ÛºsÑÆáw éù}áÈêÖ Œ˜Ã²à1Û.ë{# ë:ê>ã„#sºÎ‹Œì»ø"}"#"ë6ê;é Üá ÁF6”6éB áÎéì¼€}ÛŒëÆâ‰tpà²õètÛ:Ñ®"äàŒä À̺ÉlÉŒê‰ÑÖ‹|üxãÚBì=ÊJˆŽ:“ŒÑ.ÛåBŽuŽ„µ€ÜÚFêSŽªŽ¹ŽÈ½ÂÒJ}ŽÆŽö–äO&5DŽFBq€Žü­ç¼ @áÿèР Пҟ҈"ˆ1ˆ@˜OÒÒè?ˆBˆpˆ˜>Ôè~ŸÔ蟟ˆ¦ˆµˆÄÈ>ÔÔÔàBÒŸèˆÆˆõ‰àøÔÒÒÔÒèèÀèÅØ ‰*‰9øŸèrè¿é>ÔÔèÅèLá ‰l‰{ÁŠà¶è½à¸àCØ;àèT‰‰¼É²Ð5à„Ñà¿à‚àÇÔÔ‰ð‰ÿè¸ØhÐ÷¸ÿÉ Ôà“Š‘ðàbÙ³ÑÈxØÏÐؘŠt¹ð¸sà÷áÈûØ”àZáXáÍŠÊ.àõÈ&Ð~Ùv٠Рьⓟ‰Á#РÁÁ|áÃØ]áÉÝÔª”ánØú =áÓÙŒÐOЀÀ^ª.Ú'â6Ð)âøÐÀ-ÙNÈYâ âÔÒëœâžÀÈÀÉDâÒÊÁÉÔÉáʢۦѡ⬸<бÑËфЉÊ#ËèâZÀ.ØÈ=Ñ’ËEÉ8ÉÎã"‹® yÚuÈÑھ㹟ÙêŒlòÙíÛý¹¹Ó†Ú¿Éþˆ>ºüÒ2ä9ÁòÒOÚ}²A‹F«ÑÛmãÀ´DÊ`ɶv«üÀÚÊÜÜ?ÑÙS²ÁÒª¹ÎÁDÔD‰6ü‰Œ¹¾ªÑŽ.Ž=­|Í:ÉDãœíÎŽlŽ{¾Ê”°‰ŽªŽ¹ŽÈäNÝ4ÕÇŒ¶Žõ´Û„Šl1@Ô³ds‚€€¯ ìL[@áÿÄÿx¶¸º¼¾ÀÂÄÆÈÊÌÎÐÒÔÚâêôþ (08@HPX`hpx€ˆ˜ ¨°¸ÀÈÕçý2Qs˜ÁìGv¥Ó/]:‹¹çCr¢Ò2b’Àì3JXbhjlnprtv€€àÀ„ {IKt† HרˆXNèàNˆ KKJIKvyyˆ HJèyvèèè,tè&ià I† <<à0è>à7¸>ÀFàˆ{IØ@Àˆ0 8IÈHÀÈXKÀˆX°x؈ˆx€Èæƒ%À ˆ"c\\\\\°Œ èè#\\][À5c[à&è-\\]^àáN’à+èààØØ’•È\è=èTè9¸T˜¨iØ#àZÀAtN›ucØIèc]\§É¶ceˆc`èr]à%dace©ÙßLNè'c`ÐC¸Œ`à+à'eaace«ÐÖt èjèÐpÀ–è=`à)eaà-¬Ò7tce! à-È\ÈŸèZÐ+j ae­Êeabè0Ø] ¦èšàW ! !deedà/áö`À1àÀ¬!èµ!(Ð-cÈŒNXØùKȸ {LXj jIL##¬ è.í•é¸r#"è2èÀ{ièséä{JàˆØ` L #8sª iÈ^ tr (#""sHJ{iàÆé'Ø.à¶ {L(#8 (sè.KLè*$™s( ê˜r!!è‘rÀ°{L#  è(r"(#”б!H{rÀ¡  s#$àÌé»èŠ##õ##ŒëÝIHJè¢(õ$$ˆèºà $#„$$$##޲¸ D[@áÿÄÿèР ˆˆ'ˆ6ˆEˆT˜cË͈rˆˆŸË͈@ˆ¿ˆÎˆÝˆ@ˆû‰ ‰À€‰0‰?‰N‰]‰n‰}‰Œ‰›ˆ@‰¹‰È‰×ˆ€‰õŠŠˆÀŠ1Š@ŠOŠ^‰Š|Š‹ŠšÚ_НоŠÍŠÜ[]^ˆAŠþ‹ ‹[\]^ˆA‹>‹Mˆ~\à@ˆ‚‹‹ŽÐ¶À~]^ˆ‚‹À‹Ïж]Ð~è@] ÃŒŒзèBè½áè3ÁEèLˆ@ŒNÐö\\[\èáèÂÙ†Ø>^^Œ€ŒØ÷à?ØÀéàBà|]^‰ŒÎÐ\\àè0é]èºÐÍáµ\`[è逨>`aè‰èÿ]‰@NØþé8``ØBâ@Ø>aؾèÍᛃŠ4é÷ˆbaØB]Ø|ccea^âK^]ÀŠñàý`àBèºb]]è@édaŽ‹1áõàÀcaZèBˆà|[\èˆ^^afcˆ@‹pê;éA\ˆaZaZc`bcebêPàB]eeaèÉŽ€žèl\éÀ`cê}Z`ee[âJâS_eae‰ŽÍHHØB[[à0\è€câ [_éÑŸIIØBè']]è@è€èþâˆéS[c_éÏ@@<èBã1^àAèÀèþêÈÛeØA€ˆ@JãdáìèccéâëŠà?ØAÀÀØ|ëåêhè€Z`câRèffeà‚ÿ/ê&ê¤ãn][[é‚[áÒà¾efdcccŽÒ/Ø»ì4]â.ÙÎáÏèƒèþd!ttt!!ÿ/è;ãòêèacьȾecè>{Lutÿ­­ÓïÓ&âl{JÒÙ>è?uK{Lë-Ý7кfeèZéªbâü]ȺeaëvZJê{{HHcuáêc`кfåºêÁ [ZIiJIiIIè~K{KLàAˆøà}Z[[à@caÀºÙˆØAè€efÐAI}è@„cèÉ,×ÿc`ˆàBì×a€aaedcïrßo]ìÿîÄded€cèMI,êûc€©€è@€`åpaèBëÚíæØÁcá ec€eeàÅbèÀL{Lƒi @àCîgƒèBì\ßpàAcêdà>ƒe`eÙLƒé€|cßÿab`XƒàCÐBàï·ç°è‚ccÐ>bíõiI}ëû…ƒ„c8Nßÿaè@èC€ã:ï"ç·î%]ì{Ø‚eƒëbe`î5Ѐé@8|HÐ@<èBȃÛ:ZâBÓÂà‚\غè=ƒà@{uu|è|}L€{È@HHàBàÆí§àÆäAäBÌØºè=„ØÀ c8L€räØ}IcnÀ@àBIHGiÈÆ¹Œ ºtì?ïØ=èNnÃÀJJIrÝ_HGÙJÁŒa`Ñ7غìýJKtìÂ{NƒÐ>trÒ@q|Jè@{€LKIäÀØ|MMMÕ€|I|K΀ê@àCè@HIJkJiÁJb`fÀº,è| vL„tKiHKtt|è>GHHI|Âé;à…qHIrIJié‹ÑÎa°øè|vL…uê'í½àFFpGHpÀ€êÂ{èàAáŒØ@êÖrKátuÀ|éå~LIØ×ЀHÃkØÀá¾ÉŒÙ‹IrKØø,àº{{õõ##à»ÀJi{Ië;{ê€é‹HÛÎäê5!rèvêÀ{|¹Kê@è¸LK}m,é6êðàÀLIˆ>( Jê!mrÙµÒ€íRëk{iëªãmJKGpHIKé\LIsõs >ÐBèÀèBë€áÂ@JØøLï±KIFGëpì~tà€>Ø„sIIIr@kë¸Ðø{tGFGpìèÀ"ˆ|¨BéJëMIXÊ€JêíØø{LˆØiéUI<ˆ>ˆ„ãÀ{irIä‰{í–àøiH{KJLéõj{Iˆ|ˆÇÙJJ{iáJà‹êMèøèx|JL áô ˆø‰ÉŒìc!!Ñ {LÐøJL(}#}‰6‰E±ÎáJ!HíMr{tˆØéxIJLˆ|‰‘Î àBáËr{ØøIˆø‰¾‰ÍØB"IÝ {L #‰ð‰üŠ ¡JéLL(Š.Š:ŠIš”}à@ŠªŠyŠˆŠ—( 8ŠèЏŠÇŠÖŠåŠô‹‹‹!‹0‹?‹N‹]‹l›¹L[@áÿÄÿ9¶¸º¼¾ÀÂÄÆÈÊÌÎÐÒÔÖØÚÜÞàâäæèêìîðòõùþ #-9GWi}“¬ÅÝõ %=Vo‡Ÿ·Ïçÿ0I:ay‘©ÁÙñ !9Qi™±ÇÛíý !)/357€€àÀˆˆ. _‚ ^_ƒ ^_„ ^^^_… ]à † ]]† ^èda† afcaˆ eeacea__Š è e]^__Œ fdà èAèŽè, ]^]]^^^__àS]]èà __’ ^è è^è'Øq_” Ø@]]]è^èZ—Ø*]\\\]èdeàCÐè[\\èc€c^è6a–è²c[[èeeà`– _è\c[[[dàƒfbeè0è\\ccèèÒbe`èå– !è·è2àèBeafà0uKàÐcccÈBƒØ— KNèõà…àBè-„à0cèL騅èCè. …eact– Kvà³ÈDè/ct{èN¨D…èD J{{M– LN¨CØ|Kvà0 Bè,JKKLNè`L˜BJKtLKvàè0ØBÐ,uèȸBè,LƒtKKNèÚv¸Bà,uL„tKtØÚN˜-uL…ucLKàªØ†}È.uèmKèÚ}Nˆ-m|ià7¸>ÀFàˆ{IØ@Àˆ0 8IÈHÀÈXKÀˆX°x؈ˆx€Èæƒ% ÔÔÀ ˆ" Õ  è¸Œ ØàèÀ5ÕÖÕÕØ, Ô¡èèNáN’Õ Ôà ÕÔØJàØ’•Ö ØÐ ÕÔÔ È;Ùo˜Öà2è#è8ÈC蛨"›Ðà\赡càiàS Õ§É¶ÕèØKàNàÌc`àda Ô©ÙßLNè'c`ÀCØÔ¡`à+à'eaa Ô«ÐÖt èjèØpé èñà~è-à)eaà-¬Ò7tÖÕ! à-À/éèŸèZÐ+j aÔ­Êeabè0Ø]à¦é!ئèšàW ! ! Ôedà/áö`À1àÀ¬!èµ!(Ð-cÈŒNXØùKȸ {LXj jIL##¬ è.í•é¸r#"è2èÀ{ièséä{JàˆØ` L #8sª iÈ^ tr (#""sHJ{iàÆé'Ø.à¶ {L(#8 (sè.KLè*$™s( ê˜r!!è‘rÀ°{L#  è(r"(#”б!H{rÀ¡  s#$àÌé»èŠ##õ##ŒëÝIHJè¢(õ$$ˆèºà $#„$$$##޲ž D[@áÿÄÿèР ˆˆ'ˆ6ˆEˆT˜c•—ˆrˆˆŸ•—ˆ@ˆ¿ˆÎˆÝˆ@ˆû‰ ‰À€‰0‰?‰N‰]‰n‰}‰Œ‰›ˆ@‰¹‰È‰×ˆ€‰õŠŠˆÀŠ1Š@ŠOŠ^‰Š|Š‹ŠšÚÔŠ¯Š¾ŠÍŠÜÕÔ¡ˆAŠþ‹ ‹Õ  ÔÔˆ‚‹?‹Nˆ?Õ Ô Ô¡‹r‹‹ëŸ  ~àƒˆ‹Â‹Ñè¶Ø´Ø½àÁ ‚ˆMŒ ¸öè÷ÕȾèÁ¹éÒÒŒ@ŒOÖ è÷ è?àüÙéáÇáL¡Ò‰ÑŒŽÑ6á9à?éè=ØÇáŠÒ¡°‹ŒÆ¨à} èÙÔ ¡éCÕà|Ò耉ŽѵÕ `ÕØÂÐ}`aèûèÒŠMÐé``ÕÖÕÕØ| `aaè¼Ø?‰ÀŽØþê8ˆbèCèBàº`acceØ€êÀ‰}Øþè€aèBèºbÔÔè@é{è>daŽ‹1ÙzèÀcaZèBˆà|âQé{è|afcˆ@‰üá÷áÁ ˆaZaZc`bcebè@ÚR¡eeaèÉŽ€žÕÙ¿Õè€ ÕÖÖZ`eeØ?Òeae‰ŽÍHÖÕéúÙDÕÕÖè€cØ¿âMÕÔ‹Ï IÖHHÑû è@è€ÐþÙÔcìN¡‹Îˆ@ÔÛCccéÑÑèececŒNÍIë¶Ðíã=ZZ`á½ê†ÛffeàAÿœyÌ4ãoáé‚âÂäè¾efdcccŽÒ‰þÙhä1ÔÕêƒëãä ed!ttt!!ÿŠ|Éëè>acáŒÛPíIèþè>{Lutÿ¢|ÀþÕ âl{JÐBí†é½ttuK{Læ; ÕÖIiJIiIIè~K{KLàA®ë(årà@caè·ØºÝ¼î»ÕèÃ{È=Ôæ‰é†<{tK|L ƒà@ÊÐxÞhä+ØMÓ=Ó>ëéÔõᆃeÕeÙLƒé€|Õ×Öb`XƒàCÐBccDZÐAÐ>bíõiI}ëû…ƒé8NßÿÕÖè@èC€`bîëØ¿ç;å:íûc[eƒëbe`î5Ѐ„Õ 8|HÐ@<èBȃÛ:ÊNß¾åüàºà=ƒà@{uuèº8}L€{È@HH<îbàÆí§èÆÚCÝúÝüкè=„ØÀè| €räØ}IÕ nÀ@àBIHGiáJ¡Œ ºtì?ïØ=énÃÀJJIrÝ_HGHéŒÕÁŒa`Ñ7èºÕ ìýJKtìÂ{NƒÐ>trÖ,q|Jè@{€LKIäÀ躠tMMMÕ€<ÕÖqè€HéGHHI|ÂHÕHà…qHIrIJié‹ÉÎÕÐ>Ð|vL…uÕê'í½àFFpGHpÀ€êÂ{èàAáŒØ@êÖrKátuàøÔè|éå~LIØ×ЀHÃkØÀá¾ëÌãÍÙ‹IrKØø,à>{{õõ##à»ÀJi{Ië;{ê€é‹HÛÎäê5!rèvêÀ{|¹Kê@è¸LK}m,é6êðàÀLIˆ>( Jê!mrÙµÒ€ëÆëk{iëlãmJKGpHIKé\LIsõs >ÐBèÀèBë€áÂ@JØøLï±KIFGëpì~tà€>Ø„sIIIr@kë¸Ðø{tGFGpìèÀ"ˆ|¨BéJëMIXÒ€JêíØø{LˆØiéUI<ˆ>ˆ„ãÀ{irIä‰{í–àøiH{KJLéõj{Iˆ|ˆÇÙJJ{iáJà‹êMèøèx|JL áô ˆø‰ÉŒìc!!Ñ {LÐøJL(}#}‰6‰E±ÎáJ!HíMr{tˆØéxIJLˆ|‰‘Î àBáËr{ØøIˆø‰¾‰ÍØB"IÝ {L #‰ð‰üŠ ¡JéLL(Š.Š:ŠIš”}à@ŠªŠyŠˆŠ—( 8ŠèЏŠÇŠÖŠåŠô‹‹‹!‹0‹?‹N‹]‹l›¹L[@áÿÄÿ9¶¸º¼¾ÀÂÄÆÈÊÌÎÐÒÔÖØÚÜÞàâäæèêìîðòõùþ #-9GWi}“¬ÅÝõ %=Vo‡Ÿ·Ïçÿ0I:ay‘©ÁÙñ !9Qi™±ÇÛíý !)/357€€àÀˆˆ. ¡‚ Ô¡ƒ ÔÔ¡„è Ô¡… èÔ¡†èÔ † àda† afcaˆ eeacea¡¡Š è eÔ¡¡¡Œ fdè à èŽè,èPÔ¡ÒÐà^ÒÒèàà&’ Ø*à èàÔ¡”àyàÒààYè•—à•ÔØ<àdeèoÈ ÔÕ\[¡¡\è c€c¡Ô ¡a–àÖ[[èeeèÔ– ¡  ÕÖc[[[dàƒfbeè0ÐcèèÒbe`èå– !^Ø2à`eafà0uK]ÐÈBƒØ— KNà0Õè»èBè-„à0àÃKLèzà…àCè. …eaÔ¡t– KvàÈDè/éMt{èN¨D …eÔÔJ{{M– LN¨CÕ¡à|Kvà0°BØ,JKKLNè`LÈB ÔÔØtLKvàè0àBÔÔØ,uèÈ ànÔØ,LƒtKKNèÚvèBÈXè,uL„tKtØÚNÈoÐ-uL…uÔ¡LKàªØ†}È.uuÕ¡mKèÚ}Nˆ-m|i 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 6 12 \b1 00 \b4 \w123 1 font_addl.png 8bpp 10 10 5 13 0 -2 normal nocrop 2 font_addl.png 8bpp 30 10 3 13 0 -2 normal nocrop 3 font_addl.png 8bpp 50 10 5 13 0 -2 normal nocrop 4 font_addl.png 8bpp 70 10 7 13 0 -2 normal nocrop 5 * 6 12 \b1 01 \b4 \w123 6 font_addl.png 8bpp 10 30 3 8 0 0 normal nocrop 7 font_addl.png 8bpp 30 30 1 8 0 0 normal nocrop 8 font_addl.png 8bpp 50 30 3 8 0 0 normal nocrop 9 font_addl.png 8bpp 70 30 4 8 0 0 normal nocrop 10 * 6 12 \b1 02 \b4 \w123 11 font_addl.png 8bpp 10 40 7 21 0 -2 normal nocrop 12 font_addl.png 8bpp 30 40 2 21 0 -2 normal nocrop 13 font_addl.png 8bpp 50 40 7 21 0 -2 normal nocrop 14 font_addl.png 8bpp 70 40 11 21 0 -2 normal nocrop nml-0.4.4/regression/expected/005_error.nfo0000644000567200056720000000151212643457545021667 0ustar jenkinsjenkins00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 9 0B 00 1F 02 "zorg" 00 1 * 9 0B 00 7F 02 "care" 00 // param[127] = 14 2 * 9 0D 7F \D= FF 00 \dx0000000E // param[125] = (param[2] * 12) 3 * 9 0D 7D \D* 02 FF \dx0000000C // param[126] = (param[1] + param[125]) 4 * 5 0D 7E \D+ 01 7D 5 * 66 0B 03 1F FF "De wissels zijn bevroren, onze excuses voor het ongemak." 00 "42" 00 7F 7E 6 * 42 0B 03 7F FF "Something bad (tm) has happened." 00 "42" 00 7F 7E nml-0.4.4/regression/expected/026_asl.nfo0000644000567200056720000001323412643457545021324 0ustar jenkinsjenkins00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags // Name: layout1 - feature 11 0 * 23 02 11 FF \b65 \dx00000000 \wx0000 \dx00000000 \wx0002 \b0 \b0 \b0 \b16 \b16 \b16 80 // Name: layout1@registers - feature 11 1 * 38 02 11 FF 89 44 60 \dx000000FF \dx00000A68 \dx00000001 \2sto 1A 00 \dx00000080 \b1 \wx00FF \dx00000000 \dx00000000 \wx00FF // 2 * 7 03 11 01 00 \b0 \wx00FF // layout1; 3 * 6 01 0F \b2 FF \wx0003 4 opengfx_generic_trams1.pcx 8bpp 64 56 20 19 -14 -5 normal 5 * 1 00 6 * 1 00 7 * 257 00 00 00 00 00 00 00 00 00 00 00 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F 81 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF F0 F1 F2 F3 F4 F5 00 00 00 00 00 00 00 00 00 FF 8 * 257 00 00 00 00 00 00 00 00 00 00 00 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F 82 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF F0 F1 F2 F3 F4 F5 00 00 00 00 00 00 00 00 00 FF 9 * 1 00 // Name: layout2 - feature 0F 10 * 38 02 0F FF \b66 \dx00000000 \wx0000 \dx80000000 \wx0002 \b0 \b0 \b0 \b16 \b16 \b16 80 \dx80018000 \wx000F \b0 \b0 \b0 \b16 \b16 \b16 81 83 82 // Name: layout2@registers - feature 0F 11 * 80 02 0F FF 89 43 20 \dx000000FF \2sto 1A 20 \dx00000080 \2r 62 00 29 \dx00000001 \2^ 1A 20 \dx00000001 \2sto 1A 20 \dx00000081 \2r 1A 20 \dx00000001 \2sto 1A 20 \dx00000082 \2r 1A 20 \dx00000001 \2sto 1A 00 \dx00000083 \b1 \wx00FF \dx00000000 \dx00000000 \wx00FF // 12 * 6 01 11 \b2 FF \wx0003 13 opengfx_generic_trams1.pcx 8bpp 64 56 20 19 -14 -5 normal 14 * 1 00 15 * 1 00 16 * 257 00 00 00 00 00 00 00 00 00 00 00 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F 81 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF F0 F1 F2 F3 F4 F5 00 00 00 00 00 00 00 00 00 FF 17 * 257 00 00 00 00 00 00 00 00 00 00 00 0A 0B 0C 0D 0E 0F 10 11 12 13 14 15 16 17 18 19 1A 1B 1C 1D 1E 1F 20 21 22 23 24 25 26 27 28 29 2A 2B 2C 2D 2E 2F 30 31 32 33 34 35 36 37 38 39 3A 3B 3C 3D 3E 3F 40 41 42 43 44 45 46 47 48 49 4A 4B 4C 4D 4E 4F 50 51 52 53 54 55 56 57 58 59 5A 5B 5C 5D 5E 5F 60 61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 71 72 73 74 75 76 77 78 79 7A 7B 7C 7D 7E 7F 82 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F 90 91 92 93 94 95 96 97 98 99 9A 9B 9C 9D 9E 9F A0 A1 A2 A3 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB BC BD BE BF C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 CA CB CC CD CE CF D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 DA DB DC DD DE DF E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF F0 F1 F2 F3 F4 F5 00 00 00 00 00 00 00 00 00 FF 18 * 1 00 // Name: layout2 - feature 11 19 * 38 02 11 FE \b66 \dx00000000 \wx0000 \dx80000000 \wx0002 \b0 \b0 \b0 \b16 \b16 \b16 80 \dx80018000 \wx000F \b0 \b0 \b0 \b16 \b16 \b16 81 83 82 // Name: layout2@registers - feature 11 20 * 80 02 11 FE 89 44 20 \dx000000FF \2sto 1A 20 \dx00000080 \2r 60 00 29 \dx00000001 \2^ 1A 20 \dx00000001 \2sto 1A 20 \dx00000081 \2r 1A 20 \dx00000001 \2sto 1A 20 \dx00000082 \2r 1A 20 \dx00000001 \2sto 1A 00 \dx00000083 \b1 \wx00FE \dx00000000 \dx00000000 \wx00FE // 21 * 7 03 11 01 01 \b0 \wx00FE // layout2; 22 * 10 00 0F \b1 01 FF \wx0001 15 \wx0008 // Name: @CB_FAILED_LAYOUT0F 23 * 17 02 0F FE \b0 \dx00000000 \dx00000000 \b0 \b0 \b0 \b0 \b0 // Name: @CB_FAILED0F 24 * 23 02 0F FE 89 0C 00 \dx0000FFFF \b1 \wx8000 \dx00000000 \dx00000000 // graphics callback -> return 0 \wx00FE // Non-graphics callback, return graphics result // Name: @action3_0 25 * 23 02 0F FF 89 0C 00 \dx0000FFFF \b1 \wx00FE \dx0000015B \dx0000015B // @CB_FAILED0F; \wx00FF // layout2; 26 * 7 03 0F 01 01 \b0 \wx00FF // @action3_0; nml-0.4.4/regression/expected/031_aircraft.nfo0000644000567200056720000000145712643457545022340 0ustar jenkinsjenkins00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d4 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\30" "NML regression test" 00 "A test newgrf testing NML" 00 3 * 26 00 03 \b7 01 FF \wx0014 1A \dx000B0612 04 1E 03 1E 06 07 0C 3F 1F \wx0400 0F \wx0115 4 * 23 04 03 7F 01 FF \wx0014 "Test plane 0x14" 00 nml-0.4.4/regression/expected/021_grf_parameter.nfo0000644000567200056720000000340112643457545023351 0ustar jenkinsjenkins00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d3 1 * 817 14 "C" "INFO" "B" "VRSN" \w4 \dx00000001 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 03 "C" "PARA" "C" \d0 "T" "NAME" 7F "Disable gridlines" 00 "T" "DESC" 7F "This setting allows to replace all ground sprites such that the landscape is painted (mostly) without grid lines. Note that roads and train tracks don't yet follow this rule" 00 "B" "TYPE" \w1 01 "B" "MASK" \w3 \b0 \b0 \b1 "B" "DFLT" \w4 \dx00000001 00 "C" \d1 "T" "NAME" 7F "Replace the transmitter tower by a rock" 00 "T" "DESC" 7F "Enable to replace the transmitter tower by a rock (useful for early scenarios without telecomunication towers)" 00 "B" "TYPE" \w1 01 "B" "MASK" \w3 \b0 \b1 \b1 "B" "DFLT" \w4 \dx00000000 00 "C" \d2 "T" "NAME" 7F "Landscape type" 00 "T" "DESC" 7F "Select the landscape (ground tile) type" 00 "B" "MASK" \w1 01 "B" "LIMI" \w8 \d0 \d1 "C" "VALU" "T" \d0 7F "normal (according to climate)" 00 "T" \d1 7F "alpine (temperate grass in arctic)" 00 "T" \d2 7F "temperate (not implemented)" 00 "T" \d3 7F "arctic (not implemented)" 00 "T" \d4 7F "tropical (not implemented)" 00 "T" \d5 7F "toyland (not implemented)" 00 00 "B" "DFLT" \w4 \dx00000000 00 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\21" "NML regression test" 00 "A test newgrf testing NML" 00 // param[10] = param[1] 3 * 5 0D 0A \D= 01 00 nml-0.4.4/regression/expected/005_error.grf0000644000567200056720000000031712643457545021665 0ustar jenkinsjenkins00000000000000GRF‚  ½ ÿ zorg ÿ care ÿ ÿ ÿ }ÿ ÿ ~}Bÿ ÿDe wissels zijn bevroren, onze excuses voor het ongemak.42~*ÿ ÿSomething bad (tm) has happened.42~nml-0.4.4/regression/expected/002_sounds.grf0000644000567200056720000000035412643457545022045 0ustar jenkinsjenkins00000000000000GRF‚  ª ÿ ÿI ÿ ÿJ ÿ ÿI ÿ ÿK ÿ ÿL ÿ ÿJÿýÿþ4Vxý ÿþ4Vx ÿ ÿIf ÿ ÿKL3ÿÿbeef.wav¾ï¾ï ÿÿbeef.wav¾ï¾ïnml-0.4.4/regression/expected/011_snowline.grf0000644000567200056720000000104112643457545022362 0ustar jenkinsjenkins00000000000000GRF‚  ÿ6ÿCINFOBVRSNBMINVBNPARBPALSABBLTR84ÿNMLNML regression testA test newgrf testing NMLˆÿÿ  !#%'(*,./////13578:<>?ACEFHJLMOQSTVXZ[]_abdffhjkmoqrtvxy{}€‚„†‡‰‹Ž’”•—™›››ž ¢¤¥§©«¬®°²³µ·¹º¼¾ÀÁÃÅÇÈÊÌÎÐÑÑÓÕרÚÜÞßáãåæèêìíïñóôöøúûýÿþüûúúúø÷öõóòñïîíëêéçæåäâáàÞÝÜÚÙØÖÕÔÒÑÑÐÏÍÌËÉÈÇÅÄÃÁÀ¿¾¼»º¸·¶´³²°¯®­«ª©©§¦¥£¢¡Ÿž›š™˜–•”’‘ŽŒŠ‰ˆ‡…„ƒ€|yurnkgd`]YURNKGD@=952.+'$  nml-0.4.4/regression/expected/003_assignment.nfo0000644000567200056720000000236012643457545022706 0ustar jenkinsjenkins00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags // param[0] = 3 0 * 9 0D 00 \D= FF 00 \dx00000003 // param[1] = 4 1 * 9 0D 01 \D= FF 00 \dx00000004 // param[2] = (param[0] + param[1]) 2 * 5 0D 02 \D+ 00 01 // param[3] = (param[0] * 3) 3 * 9 0D 03 \D* 00 FF \dx00000003 // param[4] = 11 4 * 9 0D 04 \D= FF 00 \dx0000000B // param[5] = (param[4] & 3) 5 * 9 0D 05 \D& 04 FF \dx00000003 // param[127] = (param[1] + param[2]) 6 * 5 0D 7F \D+ 01 02 // param[6] = (param[127] + param[3]) 7 * 5 0D 06 \D+ 7F 03 // param[127] = param[3] 8 * 5 0D 7F \D= 03 00 9 * 7 06 7F 01 FF \wx0003 FF // param[7] = param[0] 10 * 5 0D 07 \D= 00 00 11 * 7 06 00 01 FF \wx0001 FF // param[0] = 5 12 * 9 0D 00 \D= FF 00 \dx00000005 // param[127] = param[3] 13 * 5 0D 7F \D= 03 00 14 * 12 06 00 01 FF \wx0001 7F 01 FF \wx0003 FF // param[0] = param[0] 15 * 5 0D 00 \D= 00 00 nml-0.4.4/regression/expected/012_basecost.nfo0000644000567200056720000000313112643457545022336 0ustar jenkinsjenkins00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d20 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\12" "NML regression test" 00 "A test newgrf testing NML" 00 // param[127] = (param[2] + 8) 3 * 9 0D 7F \D+ 02 FF \dx00000008 4 * 7 06 7F 01 FF \wx0008 FF 5 * 9 00 08 \b1 01 FF \wx0001 08 00 6 * 14 00 08 \b1 06 FF \wx002A 08 06 06 06 06 06 06 7 * 13 00 08 \b1 05 FF \wx0042 08 06 06 06 06 06 // param[126] = (param[1] + 9) 8 * 9 0D 7E \D+ 01 FF \dx00000009 9 * 27 06 7E 01 FF \wx0008 7E 01 FF \wx0009 7E 01 FF \wx000A 7E 01 FF \wx000B 7E 01 FF \wx000C FF 10 * 13 00 08 \b1 05 FF \wx000F 08 00 00 00 00 00 11 * 9 00 08 \b1 01 FF \wx0034 08 15 // param[124] = param[11] 12 * 5 0D 7C \D= 0B 00 13 * 7 06 7C 01 FF \wx0003 FF // param[125] = param[0] 14 * 5 0D 7D \D= 00 00 // param[122] = (param[11] + 1) 15 * 9 0D 7A \D+ 0B FF \dx00000001 16 * 7 06 7A 01 FF \wx0003 FF // param[123] = param[0] 17 * 5 0D 7B \D= 00 00 // param[124] = (param[123] + 8) 18 * 9 0D 7C \D+ 7B FF \dx00000008 19 * 12 06 7D 02 FF \wx0005 7C 01 FF \wx0008 FF 20 * 9 00 08 \b1 01 FF \wx0000 08 00 nml-0.4.4/regression/expected/017_articulated_tram.grf0000644000567200056720000000376012643457545024070 0ustar jenkinsjenkins00000000000000GRF‚  ¤ÿ6ÿCINFOBVRSNBMINVBNPARBPALSWBBLTR84ÿNMLNML regression testA test newgrf testing NMLÿÿ‰ÿÿÿÿX€ÿ€JÿÿX(†ó  HL ‡ÿþXÿM€-$ÿÿÿXFoster Turbo Tramÿÿýý ý ý ý ý ýý ÿþ ÿÿXÿþ‰ ÿÿÿþ ÿÿXþwýÿöÿÉÊÊËËÌÇÉÊÉÊËÌËÇÉèÌàèÊÌÊØØÀ ÊÊØÐ ËØÊÈØ0ËÌÈÐÇÈÉØèYèÊjèbȃÊʃËÈ„ƒÊÊ„ƒØHÊè{ǵà ·Ëà)ÊÊ ÈòÿûÿèÐÉÊÌË ÇÊÊËËË ÇÉÉàËÌĮ̀è9èàjØØØèØ7èÊËÊ„ƒÐ6è8ØÉè…„ØʨƒÊÇÈè%¨$ÊÊÊÊÉÈÈÇ ÈʃÉÈÇËʸ$ Ë‚XÈ„ƒÉÉÉÇÀ6 Ì‚X‚jÇÈɃÇÈËÉèµÈè+ÈØYËÉÈÈæÈÈÈÉÈèØèH¨üȵÈèZØ6 ÐHÁ8 ßòÿøÿjØèР à؈ˆˆ< JÊÉØØÉÉÉèÐÐ àlÉÉËËÉØÊÉÊèÊÊÊËèÊÊè5ÊËËÌàXXËè àÌèÌËÌÌÌèØ àØÐà?àCà"à&À ËØeØfØAÐË€€Ëƒ„àÀÀ€…˃„àÀ¸€ÊèÁËÊȈTà›èϵÐcÐrÐÐÉèÏ ËË‚XXXX×ȇÌÌÐÌÍÍá…‚jˆˆ‚j¹° ãúÿùÿjèèÐÀ ؈ˆ°3ÌØË@ÊÉÊËËØÌ ÉÉÊèè°ÉÉÉàÊÊË̸ƒÀ˸„ƒèÇà, ËÊÌËÌËÌÊÇɨËà,ÊÈÈÉÀÊèUàAËÊ×èÐ,ØÌÌËÇX×àÈØXèT Ç€ƒË‚jX×è¢ÐBè„èfèÇÊËè+ènÊǃÊàé/ÆÈËèXÈÉÊ€è½ÈÈèXØf4À(ØBX×Èè¹ÊË yýÿöÿjè ÇÉØÉÊÉÇØ ËÉÇÉÊØÊËÌÊØÊàÉÊèËèèØèØØà1ËÌÐè0ËÐÌËÊ€èIàƒ„ÌÉ€à„Ø è:èMÊ44àÉàaÊÊ Úòÿ÷ÿèÐà jˆè ؈$ˆÀ&ÉËLÇÉØÊÊË˨ ÇÉÉØËÌËÌÌÌÀ+èÊÊÐèÀè5à$ ÊÊÊ„ƒÉÊÌÊèØÉè…„ØXèG¸ËËÉÉèkà[¸ÊËÉ€ÉÉàZËËÈÊÌÌÉ€€€è+ØHè| ÊË‚XÉÈ€€É¸$ ‚X‚jÈɃƒÉ…„è…è¡ÈèèŠÈÉÉè5ƒà×ÉÈÑ:ÉÉCÉàÅèHÈÐèÙØHÁ`ÞòÿøÿèР Øjˆ°؈6ˆÉÉÉèÐÐ ØØÉÊØlÊÊËèÊÊÊÊÉèÊØ ÉËËËËÌÌËÌè Ìà!ËËÊËXXà$àÊè7è Ø!À%à4ØèèР Ø/€Ø(Ø-àTÐzÊØqÌ€„ƒËàÀÐ˃ƒÊ€…è àÀ¸蹈TàÏÈàµØÊпØi°nè ÍÍÌ×XXXX‚èêИÐàýé‰j‚ˆˆj‚¹’¸ÈúÿùÿËÊËËàà ÐèËËÌÌ ÊРÊÉÊèèè+À,ƒÉÉÉÉÉèàAØØØØØj„ƒèÇàËÊàAàXȃ¨ØWÌÈè;¨,àUÌÈ×àPàBÈàBà«Ë×X×Éè,ÈØXà„ Êƒ‚jX×ÊÀ,èÊ„ƒèÜ‚jÇè¢ØXèèÄÐèÇÈËØXà<ʵÀüÐXØȹàXËʵ¨ÐÉÐ$nml-0.4.4/regression/expected/016_basic_airporttiles.nfo0000644000567200056720000000136412643457545024427 0ustar jenkinsjenkins00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 6 01 11 \b1 FF \wx0001 1 opengfx_trains_start.pcx 8bpp 142 112 8 22 -3 -10 normal // Name: small_airport_tiles_graphics - feature 11 2 * 15 02 11 FF \b1 \dx00000F8D \dxC0000000 \b0 \b0 80 3 * 16 00 11 \b4 01 FF \wx0000 08 00 0F \wx0103 10 01 11 01 4 * 7 03 11 01 00 \b0 \wx00FF // small_airport_tiles_graphics; nml-0.4.4/regression/expected/002_sounds.nfo0000644000567200056720000000165012643457545022051 0ustar jenkinsjenkins00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags // param[0] = 73 0 * 9 0D 00 \D= FF 00 \dx00000049 // param[1] = 74 1 * 9 0D 01 \D= FF 00 \dx0000004A // param[2] = 73 2 * 9 0D 02 \D= FF 00 \dx00000049 // param[3] = 75 3 * 9 0D 03 \D= FF 00 \dx0000004B // param[4] = 76 4 * 9 0D 04 \D= FF 00 \dx0000004C // param[5] = 74 5 * 9 0D 05 \D= FF 00 \dx0000004A 6 * 3 11 \w4 7 ** beef.wav 8 * 8 FE 00 \dx78563412 \wx0003 9 ** beef.wav 10 * 8 FE 00 \dx78563412 \wx0003 11 * 9 00 0C \b1 01 FF \wx0049 08 66 12 * 10 00 0C \b1 02 FF \wx004B 08 4C 33 nml-0.4.4/regression/expected/018_airport_tile.grf0000644000567200056720000000026712643457545023241 0ustar jenkinsjenkins00000000000000GRF‚  ¥ÿ6ÿCINFOBVRSNBMINVBNPARBPALSABBLTR84ÿNMLNML regression testA test newgrf testing NMLÿÿhJ"ÿÿnml-0.4.4/regression/expected/009_replace.grf0000644000567200056720000000165312643457545022157 0ustar jenkinsjenkins00000000000000GRF‚  Þÿ 6ÿCINFOBVRSNBMINVBNPARBPALSWBBLTR84ÿNML NML regression testA test newgrf testing NMLÿ  ýýÿ ÿýý ý ý ý ý wýÿöÿÉÊÊËËÌÇÉÊÉÊËÌËÇÉèÌàèÊÌÊØØÀ ÊÊØÐ ËØÊÈØ0ËÌÈÐÇÈÉØèYèÊjèbȃÊʃËÈ„ƒÊÊ„ƒØHÊè{ǵà ·Ëà)ÊÊwýÿÉÊÊËËÌÇÉÊÉÊËÌËÇÉèÌàèÊÌÊØØÀ ÊÊØÐ ËØÊÈØ0ËÌÈÐÇÈÉØèYèÊjèbȃÊʃËÈ„ƒÊÊ„ƒØHÊè{ǵà ·Ëà)ÊÊ6 ôÿþÿõàà àõ¸˜Ø°- ˆØQˆYˆYˆYÐY 3 üÿùÿèè Ðõ˜èõ° à -˜ ˆˆYˆYˆYÈY ^ òÿùÿèàõõ°°ˆ#µµ¶¶ˆ<¸¨¨¦ØˆZØ7¶¦¨¨µˆxÀ‚èTȈˆ ˆ<¨¾ˆ– Ü°–ˆúˆ–‰ˆ–‰6‰7‰T¹c 4 öÿùÿèè¸ Ðõ˜õˆàˆGˆGˆGˆGЊ 2 þÿþÿèèõ¸ Øõàˆ˜è˜ ˆGˆGˆGˆGÀˆ ` ôÿùÿèÐõõˆˆ¨4¶¶µµˆ;ÀN¶¶¦¨¨˜°hµ¨¨¦¶¨9˜‚µÐTЖˆœ¨°ˆ‚ˆÊˆ‚ˆä ‚ˆþˆ‚‰‰2‰6‰LÑfnml-0.4.4/regression/expected/008_railtypes.grf0000644000567200056720000000006112643457545022547 0ustar jenkinsjenkins00000000000000GRF‚  ÿÿRAIL(ELRLnml-0.4.4/regression/expected/001_action8.grf0000644000567200056720000000022412643457545022072 0ustar jenkinsjenkins00000000000000GRF‚  ‚ÿ6ÿCINFOBVRSNBMINVBNPARBPALSABBLTR84ÿNMLNML regression testA test newgrf testing NMLnml-0.4.4/regression/expected/008_railtypes.nfo0000644000567200056720000000076012643457545022561 0ustar jenkinsjenkins00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 21 00 10 \b3 01 FF \wx0000 08 "RAIL" 14 \wx0028 0E \b1 "ELRL" nml-0.4.4/regression/expected/015_basic_object.grf0000644000567200056720000000041312643457545023141 0ustar jenkinsjenkins00000000000000GRF‚  ùÿ6ÿCINFOBVRSNBMINVBNPARBPALSABBLTR84ÿNMLNML regression testA test newgrf testing NML!ÿÿÐMiscellaneousBasic objectÿÿŒHŠ)ÿ ÿMISC Ð Ð  É– –Á ÿÿnml-0.4.4/regression/expected/009_replace.nfo0000644000567200056720000000211212643457545022152 0ustar jenkinsjenkins00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d12 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "W" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\9" "NML regression test" 00 "A test newgrf testing NML" 00 3 * 5 0A \b1 \b2 \w3092 4 opengfx_generic_trams1.pcx 8bpp 48 56 8 18 -3 -10 normal 5 opengfx_generic_trams1.pcx 8bpp 48 56 8 18 -3 4 normal 6 * 5 05 09 FF \w6 7 oneway.png 8bpp 18 8 24 16 -12 -8 normal 8 oneway.png 8bpp 50 8 24 16 -12 -8 normal 9 oneway.png 8bpp 82 8 28 16 -14 -8 normal 10 oneway.png 8bpp 114 8 24 16 -10 -8 normal 11 oneway.png 8bpp 146 8 24 16 -10 -8 normal 12 oneway.png 8bpp 178 8 28 16 -12 -8 normal nml-0.4.4/regression/expected/018_airport_tile.nfo0000644000567200056720000000150112643457545023235 0ustar jenkinsjenkins00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d4 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\18" "NML regression test" 00 "A test newgrf testing NML" 00 // Name: dirt_runway_sw_snow - feature 11 3 * 18 02 11 FF \b1 \dx00000000 \dx03224A68 \b0 \b15 \b0 \b16 \b1 \b6 4 * 7 03 11 01 00 \b0 \wx00FF // dirt_runway_sw_snow; nml-0.4.4/regression/expected/004_deactivate.grf0000644000567200056720000000006112643457545022640 0ustar jenkinsjenkins00000000000000GRF‚  ÿABCD ÿEFGHnml-0.4.4/regression/expected/019_switch.nfo0000644000567200056720000000411112643457545022042 0ustar jenkinsjenkins00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d12 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\19" "NML regression test" 00 "A test newgrf testing NML" 00 3 * 51 04 00 FF 03 \wxD000 "\98coal" 00 "\98diamonds" 00 "\98Extra info for coal mine: \7B" 00 // Name: return_switch 4 * 38 02 0A FF 89 24 60 \dxFFFFFFFF \dxFFFFF862 \dx00000001 \2psto 1A 00 \dx00000001 \b1 \wx8000 \dx00000001 \dx00000000 // Bogus range to avoid nvar == 0 \wx8000 // default: return 0; // Name: coal_mine_subtype_switch 5 * 47 02 0A FF 89 1A 20 \dx00000004 \2psto 1A 20 \dx00000000 \2r 02 00 \dx000000FF \b2 \wx8000 \dx00000000 \dx0000000A // 0 .. 10: return string(STR_COALMINE_MONTH_0_10); \wx00FF \dx0000000D \dx0000000D // 13 .. 13: return_switch; \wx8001 // default: return string(STR_COALMINE_MONTH_11); 6 * 11 00 0A \b2 01 FF \wx0000 08 00 09 00 7 * 11 00 0A \b2 01 FF \wx0000 21 40 22 03 // Name: @return_action_0 8 * 13 02 0A FE 89 10 00 \dx00000001 \b0 \wx8000 // Return computed value // Name: @CB_FAILED_PROD 9 * 15 02 0A FD 00 \wx0000 \wx0000 \wx0000 \wx0000 \wx0000 00 // Name: @CB_FAILED0A 10 * 23 02 0A FD 89 0C 00 \dx0000FFFF \b1 \wx8000 \dx00000000 \dx00000000 // graphics callback -> return 0 \wx00FD // Non-graphics callback, return graphics result // Name: @action3_0 11 * 43 02 0A FD 89 0C 00 \dx0000FFFF \b3 \wx00FF \dx00000037 \dx00000037 // coal_mine_subtype_switch; \wx8002 \dx0000003A \dx0000003A // return string(STR_COALMINE_EXTRA_TEXT); \wx00FE \dx0000003B \dx0000003B // return var[0x10, 0, 1] \wx00FD // @CB_FAILED0A; 12 * 7 03 0A 01 00 \b0 \wx00FD // @action3_0; nml-0.4.4/regression/expected/021_grf_parameter.grf0000644000567200056720000000163112643457545023350 0ustar jenkinsjenkins00000000000000GRF‚  ‡ÿ1ÿCINFOBVRSNBMINVBNPARCPARACTNAMEDisable gridlinesTDESCThis setting allows to replace all ground sprites such that the landscape is painted (mostly) without grid lines. Note that roads and train tracks don't yet follow this ruleBTYPEBMASKBDFLTCTNAMEReplace the transmitter tower by a rockTDESCEnable to replace the transmitter tower by a rock (useful for early scenarios without telecomunication towers)BTYPEBMASKBDFLTCTNAMELandscape typeTDESCSelect the landscape (ground tile) typeBMASKBLIMICVALUTnormal (according to climate)Talpine (temperate grass in arctic)Ttemperate (not implemented)Tarctic (not implemented)Ttropical (not implemented)Ttoyland (not implemented)BDFLTBPALSABBLTR84ÿNML!NML regression testA test newgrf testing NMLÿ nml-0.4.4/regression/expected/030_house.nfo0000644000567200056720000002573612643457545021675 0ustar jenkinsjenkins00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d97 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "W" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\30" "NML regression test" 00 "A test newgrf testing NML" 00 3 * 14 04 07 FF 01 \wxDC00 "Brewery" 00 4 * 24 00 08 \b1 04 FF \wx0000 09 "PASS" "MAIL" "GRAI" "WHEA" 5 * 6 01 07 \b2 FF \wx0006 6 groundtiles.png 8bpp 10 10 64 31 -31 0 normal 7 groundtiles.png 8bpp 150 10 64 31 -31 0 normal 8 groundtiles.png 8bpp 220 10 64 31 -31 0 normal 9 * 1 00 10 * 1 00 11 * 1 00 12 brewery.png 8bpp 10 60 64 91 -31 -60 normal nocrop 13 brewery.png 8bpp 80 60 64 91 -31 -60 normal nocrop 14 brewery.png 8bpp 150 60 64 91 -31 -60 normal nocrop 15 brewery_snow.png 8bpp 10 60 64 91 -31 -60 normal nocrop 16 brewery_snow.png 8bpp 80 60 64 91 -31 -60 normal nocrop 17 brewery_snow.png 8bpp 150 60 64 91 -31 -60 normal nocrop // Name: brewery_sprite_layout - feature 07 18 * 49 02 07 FF \b67 \dx00000F8D \wx0000 \dxC0000000 \wx0002 \b0 \b0 80 83 \dx80008001 \wx0003 \b0 \b0 \b0 \b16 \b16 \b48 85 86 \dx00000000 \wx0023 \b8 \b0 \b0 \b11 \b16 \b7 88 89 8A // Name: brewery_sprite_layout@registers - feature 07 19 * 362 02 07 FF 89 43 20 \dx000000FF \2cmp 1A 20 \dx00000004 \2& 1A 20 \dx00000001 \2sto 1A 20 \dx00000080 // guard \2^ 1A 20 \dx00000001 \2sto 1A 20 \dx00000081 // !guard \2r 40 20 \dx00000003 \2< 1A 20 \dx00000001 \2* 7D 81 20 \dxFFFFFFFF \2sto 1A 20 \dx00000082 \2r 7D 80 20 \dxFFFFFFFF \2* 1A 20 \dx00000002 \2+ 7D 82 20 \dxFFFFFFFF \2sto 1A 20 \dx00000083 \2r 40 20 \dx00000003 \2cmp 1A 20 \dx00000003 \2& 1A 20 \dx00000001 \2^ 1A 20 \dx00000001 \2sto 1A 20 \dx00000084 \2r 7D 8B 20 \dxFFFFFFFF \2cmp 1A 20 \dxFFFFFFFF \2& 1A 20 \dx00000001 \2| 7D 84 20 \dxFFFFFFFF \2^ 1A 20 \dx00000001 \2sto 1A 20 \dx00000085 \2r 43 20 \dx000000FF \2cmp 1A 20 \dx00000004 \2& 1A 20 \dx00000001 \2* 1A 20 \dx00000003 \2+ 7D 8B 20 \dxFFFFFFFF \2sto 1A 20 \dx00000086 \2r 46 20 \dx000000FF \2cmp 1A 20 \dx00000000 \2& 1A 20 \dx00000001 \2sto 1A 20 \dx00000087 \2r 7D 8C 20 \dxFFFFFFFF \2u< 1A 20 \dx00000001 \2^ 1A 20 \dx00000001 \2| 7D 87 20 \dxFFFFFFFF \2^ 1A 20 \dx00000001 \2sto 1A 20 \dx00000088 \2r 46 60 \dx000000FF \dxFFFFFFFF \dx00000004 \2+ 1A 20 \dx00000C07 \2sto 1A 20 \dx00000089 \2r 46 60 \dx000000FF \dx00000036 \dx00000001 \2sto 1A 00 \dx0000008A \b1 \wx00FF \dx00000000 \dx00000000 \wx00FF // // Name: @return_action_0 20 * 44 02 07 FE 89 1A 20 \dx00000002 \2sto 1A 20 \dx0000008B \2r 1A 20 \dx00000000 \2sto 1A 00 \dx0000008C \b1 \wx00FF \dx00000000 \dx00000000 // brewery_sprite_layout \wx00FF // brewery_sprite_layout // Name: @return_action_1 21 * 44 02 07 FD 89 1A 20 \dx00000001 \2sto 1A 20 \dx0000008B \2r 1A 20 \dx00000000 \2sto 1A 00 \dx0000008C \b1 \wx00FF \dx00000000 \dx00000000 // brewery_sprite_layout \wx00FF // brewery_sprite_layout // Name: @return_action_2 22 * 44 02 07 FC 89 1A 20 \dxFFFFFFFF \2sto 1A 20 \dx0000008B \2r 1A 20 \dx00000000 \2sto 1A 00 \dx0000008C \b1 \wx00FF \dx00000000 \dx00000000 // brewery_sprite_layout \wx00FF // brewery_sprite_layout // Name: @return_action_3 23 * 44 02 07 FB 89 1A 20 \dx00000000 \2sto 1A 20 \dx0000008B \2r 1A 20 \dx00000001 \2sto 1A 00 \dx0000008C \b1 \wx00FF \dx00000000 \dx00000000 // brewery_sprite_layout \wx00FF // brewery_sprite_layout // Name: brewery_layout_1 24 * 44 02 07 FB 89 7D FF 10 \dx000000FF \b3 \wx00FE \dx00000000 \dx00000000 // 0 .. 0: @return_action_0; \wx00FD \dx00000002 \dx00000002 // 2 .. 2: @return_action_1; \wx00FC \dx00000001 \dx00000001 // 1 .. 1: @return_action_2; \wx00FB // default: @return_action_3; // Name: @return_action_0 25 * 44 02 07 FC 89 1A 20 \dxFFFFFFFF \2sto 1A 20 \dx0000008B \2r 1A 20 \dx00000000 \2sto 1A 00 \dx0000008C \b1 \wx00FF \dx00000000 \dx00000000 // brewery_sprite_layout \wx00FF // brewery_sprite_layout // Name: @return_action_1 26 * 44 02 07 FD 89 1A 20 \dx00000000 \2sto 1A 20 \dx0000008B \2r 1A 20 \dx00000001 \2sto 1A 00 \dx0000008C \b1 \wx00FF \dx00000000 \dx00000000 // brewery_sprite_layout \wx00FF // brewery_sprite_layout // Name: @return_action_2 27 * 44 02 07 FE 89 1A 20 \dx00000002 \2sto 1A 20 \dx0000008B \2r 1A 20 \dx00000000 \2sto 1A 00 \dx0000008C \b1 \wx00FF \dx00000000 \dx00000000 // brewery_sprite_layout \wx00FF // brewery_sprite_layout // Name: @return_action_3 28 * 44 02 07 FF 89 1A 20 \dx00000001 \2sto 1A 20 \dx0000008B \2r 1A 20 \dx00000000 \2sto 1A 00 \dx0000008C \b1 \wx00FF \dx00000000 \dx00000000 // brewery_sprite_layout \wx00FF // brewery_sprite_layout // Name: brewery_layout_2 29 * 44 02 07 FF 89 7D FF 10 \dx000000FF \b3 \wx00FC \dx00000000 \dx00000000 // 0 .. 0: @return_action_0; \wx00FD \dx00000002 \dx00000002 // 2 .. 2: @return_action_1; \wx00FE \dx00000001 \dx00000001 // 1 .. 1: @return_action_2; \wx00FF // default: @return_action_3; // Name: brewery_choose_layout 30 * 23 02 07 FF 89 5F 08 \dx00000001 \b1 \wx00FB \dx00000000 \dx00000000 // 0 .. 0: brewery_layout_1; \wx00FF // default: brewery_layout_2; // Name: brewery_next_frame 31 * 23 02 07 FB 89 46 00 \dx000000FF \b1 \wx80FF \dx00000000 \dx00000000 // 0 .. 0: return 255; \wx80FE // default: return 254; // Name: @return_action_0 32 * 41 02 07 FE 89 46 20 \dx000000FF \2cmp 1A 20 \dx00000000 \2& 1A 20 \dx00000001 \2* 1A 20 \dxFFFFFF04 // expr1 - expr2 \2+ 1A 00 \dx000000FD \b0 \wx8000 // Return computed value // Name: brewery_cargo_accepted 33 * 59 02 07 FE 89 5F 28 \dx00000001 \2u< 1A 20 \dx00000001 \2* 1A 20 \dxFFFFFFFF // expr1 - expr2 \2+ 1A 20 \dx00000003 \2cmp 7D FF 30 \dx000000FF \2& 1A 00 \dx00000001 \b1 \wx00FE \dx00000001 \dx00000001 // 1 .. 1: return ((var[0x46, 0, 255] == 0) ? 1 : 253) \wx80FD // default: return 253; // Name: brewery_check_location 34 * 23 02 07 FD 89 44 00 \dx000000FF \b1 \wx8001 \dx00000000 \dx00000000 // 0 .. 0: return 1; \wx8000 // default: return 0; // param[126] = 0 35 * 9 0D 7E \D= FF 00 \dx00000000 36 * 9 09 00 04 \7c \dx49415247 01 // param[126] = 1 37 * 9 0D 7E \D= FF 00 \dx00000001 // param[125] = 0 38 * 9 0D 7D \D= FF 00 \dx00000000 39 * 9 09 00 04 \7c \dx41454857 01 // param[125] = 1 40 * 9 0D 7D \D= FF 00 \dx00000001 // param[127] = (param[126] | param[125]) 41 * 5 0D 7F \D| 7E 7D 42 * 9 09 7F 04 \7= \dx00000000 10 // param[123] = 3 43 * 9 0D 7B \D= FF 00 \dx00000003 // param[122] = 0 44 * 9 0D 7A \D= FF 00 \dx00000000 45 * 9 09 00 04 \7c \dx49415247 01 // param[122] = 1 46 * 9 0D 7A \D= FF 00 \dx00000001 47 * 9 09 7A 04 \7= \dx00000000 01 // param[123] = 2 48 * 9 0D 7B \D= FF 00 \dx00000002 // param[124] = (param[123] & 255) 49 * 9 0D 7C \D& 7B FF \dx000000FF // param[125] = (param[124] | 0) 50 * 9 0D 7D \D| 7C FF \dx00000000 // param[126] = (param[125] | 65536) 51 * 9 0D 7E \D| 7D FF \dx00010000 // param[122] = 3 52 * 9 0D 7A \D= FF 00 \dx00000003 // param[121] = 0 53 * 9 0D 79 \D= FF 00 \dx00000000 54 * 9 09 00 04 \7c \dx49415247 01 // param[121] = 1 55 * 9 0D 79 \D= FF 00 \dx00000001 56 * 9 09 79 04 \7= \dx00000000 01 // param[122] = 2 57 * 9 0D 7A \D= FF 00 \dx00000002 // param[123] = (param[122] & 255) 58 * 9 0D 7B \D& 7A FF \dx000000FF // param[124] = (param[123] | 0) 59 * 9 0D 7C \D| 7B FF \dx00000000 // param[125] = (param[124] | 65536) 60 * 9 0D 7D \D| 7C FF \dx00010000 // param[121] = 3 61 * 9 0D 79 \D= FF 00 \dx00000003 // param[120] = 0 62 * 9 0D 78 \D= FF 00 \dx00000000 63 * 9 09 00 04 \7c \dx49415247 01 // param[120] = 1 64 * 9 0D 78 \D= FF 00 \dx00000001 65 * 9 09 78 04 \7= \dx00000000 01 // param[121] = 2 66 * 9 0D 79 \D= FF 00 \dx00000002 // param[122] = (param[121] & 255) 67 * 9 0D 7A \D& 79 FF \dx000000FF // param[123] = (param[122] | 0) 68 * 9 0D 7B \D| 7A FF \dx00000000 // param[124] = (param[123] | 65536) 69 * 9 0D 7C \D| 7B FF \dx00010000 // param[120] = 3 70 * 9 0D 78 \D= FF 00 \dx00000003 // param[119] = 0 71 * 9 0D 77 \D= FF 00 \dx00000000 72 * 9 09 00 04 \7c \dx49415247 01 // param[119] = 1 73 * 9 0D 77 \D= FF 00 \dx00000001 74 * 9 09 77 04 \7= \dx00000000 01 // param[120] = 2 75 * 9 0D 78 \D= FF 00 \dx00000002 // param[121] = (param[120] & 255) 76 * 9 0D 79 \D& 78 FF \dx000000FF // param[122] = (param[121] | 0) 77 * 9 0D 7A \D| 79 FF \dx00000000 // param[123] = (param[122] | 65536) 78 * 9 0D 7B \D| 7A FF \dx00010000 79 * 22 06 7E 04 FF \wx0039 7D 04 FF \wx003D 7C 04 FF \wx0041 7B 04 FF \wx0045 FF 80 * 178 00 07 \b23 04 FF \wx0000 08 28 29 2A 2B 12 \wxDC00 \wxDC00 \wxDC00 \wxDC00 09 30 20 20 20 19 00 00 00 00 0B 64 00 00 00 0C 19 00 00 00 0D 08 08 08 08 0E 02 02 02 02 0F 01 01 01 01 1E \dx00000000 \dx00000000 \dx00000000 \dx00000000 10 \wx00C8 \wx00C8 \wx00C8 \wx00C8 11 FA FA FA FA 18 F0 00 00 00 0A \wx8214 \wx0000 \wx0000 \wx0000 21 \wx0794 \wx0000 \wx0000 \wx0000 22 \wx0802 \wx0000 \wx0000 \wx0000 1F 14 00 00 00 13 \wx3807 \wx0000 \wx0000 \wx0000 17 \dx03060804 \dx03060804 \dx03060804 \dx03060804 16 00 00 00 00 1A 94 94 94 94 1B 02 02 02 02 20 \b2 02 03 \b2 02 03 \b2 02 03 \b2 02 03 81 * 11 00 07 \b2 01 FF \wx0000 14 03 1D 00 82 * 2 10 10 // Name: @action3_0 83 * 57 02 07 FD 89 1A 20 \dx00000000 \2sto 1A 20 \dx000000FF \2r 0C 00 \dx0000FFFF \b3 \wx00FD \dx00000017 \dx00000017 // brewery_check_location; \wx00FB \dx0000001A \dx0000001A // brewery_next_frame; \wx00FE \dx00000148 \dx00000148 // brewery_cargo_accepted; \wx00FF // brewery_choose_layout; 84 * 9 07 7F 04 \7= \dx00000000 02 85 * 7 03 07 01 00 \b0 \wx00FD // @action3_0; 86 * 11 00 07 \b2 01 FF \wx0001 14 02 1D 00 // Name: @action3_1 87 * 47 02 07 FD 89 1A 20 \dx00010100 \2sto 1A 20 \dx000000FF \2r 0C 00 \dx0000FFFF \b2 \wx00FB \dx0000001A \dx0000001A // brewery_next_frame; \wx00FE \dx00000148 \dx00000148 // brewery_cargo_accepted; \wx00FF // brewery_choose_layout; 88 * 9 07 7F 04 \7= \dx00000000 02 89 * 7 03 07 01 01 \b0 \wx00FD // @action3_1; 90 * 11 00 07 \b2 01 FF \wx0002 14 02 1D 00 // Name: @action3_2 91 * 47 02 07 FD 89 1A 20 \dx00020001 \2sto 1A 20 \dx000000FF \2r 0C 00 \dx0000FFFF \b2 \wx00FB \dx0000001A \dx0000001A // brewery_next_frame; \wx00FE \dx00000148 \dx00000148 // brewery_cargo_accepted; \wx00FF // brewery_choose_layout; 92 * 9 07 7F 04 \7= \dx00000000 02 93 * 7 03 07 01 02 \b0 \wx00FD // @action3_2; 94 * 11 00 07 \b2 01 FF \wx0003 14 02 1D 00 // Name: @action3_3 95 * 47 02 07 FF 89 1A 20 \dx00030101 \2sto 1A 20 \dx000000FF \2r 0C 00 \dx0000FFFF \b2 \wx00FB \dx0000001A \dx0000001A // brewery_next_frame; \wx00FE \dx00000148 \dx00000148 // brewery_cargo_accepted; \wx00FF // brewery_choose_layout; 96 * 9 07 7F 04 \7= \dx00000000 01 97 * 7 03 07 01 03 \b0 \wx00FF // @action3_3; nml-0.4.4/regression/expected/031_aircraft.grf0000644000567200056720000000031712643457545022326 0ustar jenkinsjenkins00000000000000GRF‚  ½ÿ6ÿCINFOBVRSNBMINVBNPARBPALSABBLTR84ÿNML0NML regression testA test newgrf testing NMLÿÿ  ?ÿÿTest plane 0x14nml-0.4.4/regression/expected/020_recolour.grf0000644000567200056720000000517512643457545022372 0ustar jenkinsjenkins00000000000000GRF‚  'ÿ6ÿCINFOBVRSNBMINVBNPARBPALSWBBLTR84ÿNML NML regression testA test newgrf testing NML ÿ þÿÿÿÿÿ ÿ  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄŲ٠ÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõÿÿ  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅ>?@ABCDEÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõÿÿ  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅš›œžŸ ¡ÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõÿÿÿý ý ý ýýýýý ÿÿ ÿÿXÿ wýÿöÿÉÊÊËËÌÇÉÊÉÊËÌËÇÉèÌàèÊÌÊØØÀ ÊÊØÐ ËØÊÈØ0ËÌÈÐÇÈÉØèYèÊjèbȃÊʃËÈ„ƒÊÊ„ƒØHÊè{ǵà ·Ëà)ÊÊ ÈòÿûÿèÐÉÊÌË ÇÊÊËËË ÇÉÉàËÌĮ̀è9èàjØØØèØ7èÊËÊ„ƒÐ6è8ØÉè…„ØʨƒÊÇÈè%¨$ÊÊÊÊÉÈÈÇ ÈʃÉÈÇËʸ$ Ë‚XÈ„ƒÉÉÉÇÀ6 Ì‚X‚jÇÈɃÇÈËÉèµÈè+ÈØYËÉÈÈæÈÈÈÉÈèØèH¨üȵÈèZØ6 ÐHÁ8 ßòÿøÿjØèР à؈ˆˆ< JÊÉØØÉÉÉèÐÐ àlÉÉËËÉØÊÉÊèÊÊÊËèÊÊè5ÊËËÌàXXËè àÌèÌËÌÌÌèØ àØÐà?àCà"à&À ËØeØfØAÐË€€Ëƒ„àÀÀ€…˃„àÀ¸€ÊèÁËÊȈTà›èϵÐcÐrÐÐÉèÏ ËË‚XXXX×ȇÌÌÐÌÍÍá…‚jˆˆ‚j¹°éúÿùÿjèèÐÀ ؈ˆ°3ÌØË@ÊÉÊËËØÌ ÉÉÊèè°ÉÉÉàÊÊË̸ƒÀ˸„ƒèÇà, ËÊÌËÌËÌÊÇɨËà,ÊÈÈÉÀÊèUàAËÊ×èÐ,ØÌÌËÇX×àÈØXèT Ç€ƒË‚jX×è¢ÐBè„èfèÇÊËè+ènÊǃÊàé/ÆÈËèXÈÉÊ€è½ÈÈèXØf4À(ØBX×Èè¹Ê™#‚jÈÐyýÿöÿjè ÇÉØÉÊÉÇØ ËÉÇÉÊØÊËÌÊØÊàÉÊèËèèØèØØà1ËÌÐè0ËÐÌËÊ€èIàƒ„ÌÉ€à„Ø è:èMÊ44àÉàaÊÊÞòÿ÷ÿèÐà jˆè ؈$ˆÀ&ÉËLÇÉØÊÊË˨ ÇÉÉØËÌËÌÌÌÀ+èÊÊÐèÀè5à$ ÊÊÊ„ƒÉÊÌÊèØÉè…„ØXèG¸ËËÉÉèkà[¸ÊËÉ€ÉÉàZËËÈÊÌÌÉ€€€è+ØHè| ÊË‚XÉÈ€€É¸$ ‚X‚jÈɃƒÉ…„è…è¡ÈèèŠÈÉÉè5ƒà×ÉÈÑ:ÉÉCÉàÅèHÈÐèÙØH¡`è)˜ÞòÿøÿèР Øjˆ°؈6ˆÉÉÉèÐÐ ØØÉÊØlÊÊËèÊÊÊÊÉèÊØ ÉËËËËÌÌËÌè Ìà!ËËÊËXXà$àÊè7è Ø!À%à4ØèèР Ø/€Ø(Ø-àTÐzÊØqÌ€„ƒËàÀÐ˃ƒÊ€…è àÀ¸蹈TàÏÈàµØÊпØi°nè ÍÍÌ×XXXX‚èêИÐàýé‰j‚ˆˆj‚¹’¸ÈúÿùÿËÊËËàà ÐèËËÌÌ ÊРÊÉÊèèè+À,ƒÉÉÉÉÉèàAØØØØØj„ƒèÇàËÊàAàXȃ¨ØWÌÈè;¨,àUÌÈ×àPàBÈàBà«Ë×X×Éè,ÈØXà„ Êƒ‚jX×ÊÀ,èÊ„ƒèÜ‚jÇè¢ØXèèÄÐèÇÈËØXà<ʵÀüÐXØȹàXËʵ¨ÐÉÐ$nml-0.4.4/regression/expected/024_conditional.nfo0000644000567200056720000000114612643457545023045 0ustar jenkinsjenkins00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags // param[127] = param[0] 0 * 5 0D 7F \D= 00 00 1 * 9 09 7F 04 \7= \dx00000000 01 // param[1] = 1 2 * 9 0D 01 \D= FF 00 \dx00000001 // param[3] = 1 3 * 9 0D 03 \D= FF 00 \dx00000001 nml-0.4.4/regression/expected/012_basecost.grf0000644000567200056720000000064012643457545022334 0ustar jenkinsjenkins00000000000000GRF‚  Žÿ6ÿCINFOBVRSNBMINVBNPARBPALSABBLTR84ÿNMLNML regression testA test newgrf testing NML ÿ ÿÿÿÿ ÿÿÿÿ* ÿÿB ÿ ~ÿ ÿ~ÿ~ÿ ~ÿ ~ÿ ~ÿ ÿ ÿÿ ÿÿ4ÿ | ÿ|ÿÿÿ } ÿ z ÿÿzÿÿÿ { ÿ |{ÿ ÿ}ÿ|ÿÿ ÿÿnml-0.4.4/regression/expected/032_simple_house.nfo0000644000567200056720000000261312643457545023235 0ustar jenkinsjenkins00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d10 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "D" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\32" "NML regression test" 00 "A test newgrf testing NML" 00 3 * 20 04 07 FF 01 \wxDC00 "Example house" 00 4 * 6 01 07 \b1 FF \wx0001 5 nlhs.png 8bpp 98 8 44 36 -22 0 normal nocrop // Name: spritelayout_townhouse - feature 07 6 * 23 02 07 FF \b65 \dx00000F8D \wx0000 \dx80008000 \wx0001 \b4 \b2 \b0 \b8 \b16 \b27 80 // Name: spritelayout_townhouse@registers - feature 07 7 * 30 02 07 FF 89 1A 20 \dx00000001 \2sto 1A 00 \dx00000080 \b1 \wx00FF \dx00000000 \dx00000000 \wx00FF // 8 * 16 00 07 \b4 01 FF \wx0000 08 02 15 02 18 A0 12 \wxDC00 // Name: @action3_0 9 * 37 02 07 FF 89 1A 20 \dx00000000 \2sto 1A 20 \dx000000FF \2r 0C 00 \dx0000FFFF \b1 \wx00FF \dx00000000 \dx00000000 // spritelayout_townhouse; \wx00FF // spritelayout_townhouse; 10 * 7 03 07 01 00 \b0 \wx00FF // @action3_0; nml-0.4.4/regression/expected/024_conditional.grf0000644000567200056720000000011312643457545023032 0ustar jenkinsjenkins00000000000000GRF‚  9ÿ  ÿ  ÿ ÿ ÿ ÿnml-0.4.4/regression/expected/017_articulated_tram.nfo0000644000567200056720000000370512643457545024073 0ustar jenkinsjenkins00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d18 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "W" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\17" "NML regression test" 00 "A test newgrf testing NML" 00 // Name: foster_express_articulated_parts 3 * 23 02 01 FF 89 10 00 \dxFFFFFFFF \b1 \wx8058 \dx00000001 \dx00000003 // 1 .. 3: return 88; \wx80FF // default: return 255; 4 * 74 00 01 \b25 01 FF \wx0058 06 0F 04 28 03 1E 1F \dx000AF386 02 01 0A \dx00004C48 09 87 11 8F 08 FF 15 FE 13 16 14 58 0E FF 07 10 18 4D 19 80 0F 2D 1D \wx0001 16 \dx00000000 1E \wx0000 16 \dx00000000 24 \b0 16 \dx00000000 10 FF 1C 01 5 * 25 04 01 7F 01 FF \wx0058 "Foster Turbo Tram" 00 6 * 6 01 01 \b1 FF \wx0008 7 tram_foster_express.png 8bpp 48 1 8 18 -3 -10 normal 8 tram_foster_express.png 8bpp 64 1 20 18 -14 -5 normal 9 tram_foster_express.png 8bpp 96 1 28 15 -14 -8 normal 10 tram_foster_express.png 8bpp 144 1 20 18 -6 -7 normal 11 tram_foster_express.png 8bpp 176 1 8 18 -3 -10 normal 12 tram_foster_express.png 8bpp 192 1 20 18 -14 -9 normal 13 tram_foster_express.png 8bpp 224 1 28 15 -14 -8 normal 14 tram_foster_express.png 8bpp 272 1 20 18 -6 -7 normal // Name: foster_express_set - feature 01 15 * 9 02 01 FE \b1 \b1 \w0 \w0 16 * 9 00 01 \b1 01 FF \wx0058 17 10 // Name: @action3_0 17 * 23 02 01 FE 89 0C 00 \dx0000FFFF \b1 \wx00FF \dx00000016 \dx00000016 // foster_express_articulated_parts; \wx00FE // foster_express_set; 18 * 9 03 01 01 FF \wx0058 \b0 \wx00FE // @action3_0; nml-0.4.4/regression/expected/003_assignment.grf0000644000567200056720000000033212643457545022677 0ustar jenkinsjenkins00000000000000GRF‚  È ÿ ÿ ÿ ÿÿ  ÿ ÿ ÿ ÿ ÿ ÿÿ ÿ ÿ ÿÿÿÿ ÿÿÿ ÿ ÿÿ  ÿÿÿÿÿ nml-0.4.4/regression/expected/029_base_graphics.nfo0000644000567200056720000000455112643457545023344 0ustar jenkinsjenkins00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 257 00 00 00 00 00 00 00 00 00 00 00 F0 F0 F0 EF EF EF 0F 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 F0 F0 00 00 00 00 00 00 F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 F0 F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 F0 00 00 00 00 00 00 00 00 00 00 1 * 257 00 00 00 00 00 00 00 00 00 00 00 A3 A3 A3 A3 A4 A4 0F 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 B4 B4 00 00 00 00 00 00 B5 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 B3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 B3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 B3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 B2 B3 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 B5 00 00 00 00 00 00 00 00 00 00 2 fonts.png 8bpp 10 10 2 1 0 -2 normal nocrop 3 fonts.png 8bpp 30 10 3 13 0 -2 normal 4 fonts.png 8bpp 50 10 5 13 0 -2 normal 5 fonts.png 8bpp 70 10 10 13 0 -2 normal 6 fonts.png 8bpp 90 10 9 13 0 -2 normal 7 fonts.png 8bpp 110 10 12 13 0 -2 normal 8 fonts.png 8bpp 130 10 9 13 0 -2 normal 9 fonts.png 8bpp 150 10 3 13 0 -2 normal 10 fonts.png 8bpp 170 10 5 13 0 -2 normal nml-0.4.4/regression/expected/006_vehicle.nfo0000644000567200056720000000630112643457545022157 0ustar jenkinsjenkins00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d21 1 * 54 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 00 "B" "PALS" \w1 "W" "B" "BLTR" \w1 "3" 00 00 2 * 52 08 08 "NML\6" "NML regression test" 00 "A test newgrf testing NML" 00 3 * 32 00 08 \b1 06 FF \wx0000 09 "PASS" "MAIL" "GOOD" "IORE" "GOLD" "FOOD" // param[0] = 9 4 * 9 0D 00 \D= FF 00 \dx00000009 // param[126] = (param[0] * 5) 5 * 9 0D 7E \D* 00 FF \dx00000005 // param[127] = (param[126] + 5) 6 * 9 0D 7F \D+ 7E FF \dx00000005 7 * 7 06 7F 01 FF \wx002C FF 8 * 63 00 01 \b21 01 FF \wx0059 06 03 04 28 03 1E 1F \dx000AF386 02 01 0A \dx00004C48 09 87 11 8F 08 98 13 16 14 58 0E FF 07 10 18 4D 19 80 0F 00 1D \wx0003 16 \dx00000000 1E \wx0000 16 \dx00000000 1C 01 9 * 27 04 01 7F 01 FF \wx0059 "Foster Express Tram" 00 10 * 23 04 01 1F 01 FF \wx0059 "Foster Sneltram" 00 11 * 6 01 01 \b1 FF \wx0008 12 opengfx_generic_trams1.pcx 8bpp 48 56 8 18 -3 -10 normal | opengfx_generic_trams1.png 32bpp 48 56 8 18 -3 -10 normal | opengfx_generic_trams1.pcx mask 48 56 | opengfx_generic_trams1.pcx 8bpp 48 56 8 18 -3 -10 zi2 13 opengfx_generic_trams1.pcx 8bpp 64 56 20 19 -14 -5 normal | opengfx_generic_trams1.png 32bpp 64 56 20 19 -14 -5 normal | opengfx_generic_trams1.pcx mask 64 56 | opengfx_generic_trams1.pcx 8bpp 64 56 20 19 -14 -5 zi2 14 opengfx_generic_trams1.pcx 8bpp 96 56 28 15 -14 -8 normal | opengfx_generic_trams1.png 32bpp 96 56 28 15 -14 -8 normal | opengfx_generic_trams1.pcx mask 96 56 | opengfx_generic_trams1.pcx 8bpp 96 56 28 15 -14 -8 zi2 15 opengfx_generic_trams1.pcx 8bpp 144 56 20 19 -6 -7 normal | opengfx_generic_trams1.png 32bpp 144 56 20 19 -6 -7 normal | opengfx_generic_trams1.pcx mask 144 56 | opengfx_generic_trams1.pcx 8bpp 144 56 20 19 -6 -7 zi2 16 opengfx_generic_trams1.pcx 8bpp 176 56 8 18 -3 -10 normal | opengfx_generic_trams1.png 32bpp 176 56 8 18 -3 -10 normal | opengfx_generic_trams1.pcx mask 176 56 | opengfx_generic_trams1.pcx 8bpp 176 56 8 18 -3 -10 zi2 17 opengfx_generic_trams1.pcx 8bpp 192 56 20 19 -14 -9 normal | opengfx_generic_trams1.png 32bpp 192 56 20 19 -14 -9 normal | opengfx_generic_trams1.pcx mask 192 56 | opengfx_generic_trams1.pcx 8bpp 192 56 20 19 -14 -9 zi2 18 opengfx_generic_trams1.pcx 8bpp 224 56 28 15 -14 -8 normal | opengfx_generic_trams1.png 32bpp 224 56 28 15 -14 -8 normal | opengfx_generic_trams1.pcx mask 224 56 | opengfx_generic_trams1.pcx 8bpp 224 56 28 15 -14 -8 zi2 19 opengfx_generic_trams1.pcx 8bpp 272 56 20 19 -6 -7 normal | opengfx_generic_trams1.png 32bpp 272 56 20 19 -6 -7 normal | opengfx_generic_trams1.pcx mask 272 56 | opengfx_generic_trams1.pcx 8bpp 272 56 20 19 -6 -7 zi2 // Name: foster_express_set - feature 01 20 * 9 02 01 FF \b1 \b1 \w0 \w0 21 * 9 03 01 01 FF \wx0059 \b0 \wx00FF // foster_express_set; nml-0.4.4/regression/expected/014_read_special_param.nfo0000644000567200056720000000371112643457545024334 0ustar jenkinsjenkins00000000000000// Automatically generated by GRFCODEC. Do not modify! // (Info version 32) // Escapes: 2+ 2- 2< 2> 2u< 2u> 2/ 2% 2u/ 2u% 2* 2& 2| 2^ 2sto = 2s 2rst = 2r 2psto 2ror = 2rot 2cmp 2ucmp 2<< 2u>> 2>> // Escapes: 71 70 7= 7! 7< 7> 7G 7g 7gG 7GG 7gg 7c 7C // Escapes: D= = DR D+ = DF D- = DC Du* = DM D* = DnF Du<< = DnC D<< = DO D& D| Du/ D/ Du% D% // Format: spritenum imagefile depth xpos ypos xsize ysize xrel yrel zoom flags 0 * 4 \d20 1 * 231 14 "C" "INFO" "B" "VRSN" \w4 \dx00000000 "B" "MINV" \w4 \dx00000000 "B" "NPAR" \w1 03 "C" "PARA" "C" \d0 "T" "NAME" 7F "Name of the int setting" 00 "T" "DESC" 7F "Description of a setting" 00 "B" "MASK" \w1 00 "B" "LIMI" \w8 \d0 \d2 "B" "DFLT" \w4 \dx00000000 00 "C" \d1 "B" "TYPE" \w1 01 "B" "MASK" \w3 \b1 \b0 \b1 "B" "DFLT" \w4 \dx00000000 00 "C" \d2 "B" "TYPE" \w1 01 "B" "MASK" \w3 \b1 \b2 \b1 "B" "DFLT" \w4 \dx00000001 00 00 "B" "PALS" \w1 "A" "B" "BLTR" \w1 "8" 00 00 2 * 52 08 08 "NML\14" "NML regression test" 00 "A test newgrf testing NML" 00 // param[16] = param[0] 3 * 5 0D 10 \D= 00 00 // param[17] = 0 4 * 9 0D 11 \D= FF 00 \dx00000000 5 * 6 09 01 01 \70 00 01 // param[17] = 1 6 * 9 0D 11 \D= FF 00 \dx00000001 // param[18] = 0 7 * 9 0D 12 \D= FF 00 \dx00000000 8 * 6 09 01 01 \70 02 01 // param[18] = 1 9 * 9 0D 12 \D= FF 00 \dx00000001 // param[19] = param[0] 10 * 9 0D 13 \D= 00 FE \dx014C4D4E // param[158] = (param[158] | 2) 11 * 9 0D 9E \D| 9E FF \dx00000002 // param[20] = 0 12 * 9 0D 14 \D= FF 00 \dx00000000 13 * 6 09 9E 01 \70 03 01 // param[20] = 1 14 * 9 0D 14 \D= FF 00 \dx00000001 // param[142] = -2 15 * 9 0D 8E \D= FF 00 \dxFFFFFFFE // param[21] = param[164] 16 * 5 0D 15 \D= A4 00 // param[125] = param[19] 17 * 9 0D 7D \D= 13 FE \dx0000FFFF // param[126] = (param[125] & 255) 18 * 9 0D 7E \D& 7D FF \dx000000FF // param[127] = (param[126] + 12) 19 * 9 0D 7F \D+ 7E FF \dx0000000C // param[22] = (1 << param[127]) 20 * 9 0D 16 \D<< FF 7F \dx00000001 nml-0.4.4/regression/014_read_special_param.nml0000644000567200056720000000214512643457545022537 0ustar jenkinsjenkins00000000000000grf { grfid: "NML\14"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; param { int_param { type: int; min_value: 0; max_value: 2; def_value: 0; name: string(STR_PARAM_NAME); desc: string(STR_PARAM_DESC); } } param { bool_param_1 { type: bool; def_value: 0; } bool_param_2 { type: bool; def_value: 1; bit: 2; } } } /* Read a user-changeable integer setting */ param[0x10] = int_param; /* Read a user-changeable bool setting */ param[0x11] = bool_param_1; /* Read a user-changeable bool setting */ param[0x12] = bool_param_2; /* Read a setting from another newgrf */ param[0x13] = param["NML\01", 0]; /* Read and write a misc grf bit */ desert_paved_roads = 1; param[0x14] = train_width_32_px; /* Read and write a special grf parameter */ traininfo_y_offset = -2; param[0x15] = year_loaded; /* Read a patch variable */ param[0x16] = map_size; nml-0.4.4/regression/031_aircraft.nml0000644000567200056720000000113612643457545020535 0ustar jenkinsjenkins00000000000000grf { grfid : "NML\30"; name : string(STR_REGRESSION_NAME); desc : string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } item (FEAT_AIRCRAFT, Boeing_2707, 0x14) { property { name: string(STR_NAME_PLANE); introduction_date: date(1978,01,01); // Introduction two years after Concorde model_life: 30; vehicle_life: 30; climates_available: bitmask(CLIMATE_TEMPERATE, CLIMATE_ARCTIC, CLIMATE_TROPICAL); speed: 805 km/h; range: 1024; // same as concorde with typical max payload passenger_capacity: 277; // would have cruised 6480 km at M2.7 with this load } }nml-0.4.4/regression/016_basic_airporttiles.nml0000644000567200056720000000104712643457545022630 0ustar jenkinsjenkins00000000000000spriteset(turbotrain_engine_set, "opengfx_trains_start.pcx") { [142,112, 8,22, -3,-10] } spritelayout small_airport_tiles_graphics { ground {sprite: GROUNDSPRITE_NORMAL; } childsprite { sprite: turbotrain_engine_set; always_draw: 1; } } item(FEAT_AIRPORTTILES, small_airport_tiles) { property { substitute: 0; animation_info: [ANIMATION_LOOPING, 4]; // loop, 4 frames animation_speed: 1; animation_triggers: 1; } graphics { small_airport_tiles_graphics; } } nml-0.4.4/regression/013_train_callback.nml0000644000567200056720000001172712643457545021702 0ustar jenkinsjenkins00000000000000grf { grfid: "NML\13"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } /* * ******************************************** * Define cargo and railtype translation tables * ******************************************** */ cargotable { // cargos needed for special refit orders WDPR, SCRP, CMNT, WOOD, // bulk, bulk+piece+flat, tank, piece LVST, STEL, VEHI, BRCK, // piece+flat, piece, piece, piece WOOL, BUBL, TOYS, FZDR, // flat, flat, flat, tank FRUT, FRVG, FOOD, // bulk, bulk, piece // cargos only referenced OIL_, GOOD, WATR, MILK, COAL, IORE, AORE, CLAY, GRVL, SAND, GRAI, RSGR, MAIZ, CORE, FERT, CTCD, SULP, WHEA, RFPR, COLA, PETR, PAPR, TOFF, SUGR, PASS, MAIL, BATT, SWET, RUBR, FMSP, ENSP, MNSP, FICR, PLAS, PLST } railtypetable { RAIL, ELRL, MONO, MGLV, TRPD } template tmpl_railwagon(x,y) { [ 0+x, y, 8,24, -3,-12] [ 16+x, y, 22,17, -14, -9] [ 48+x, y, 32,12, -16, -8] [ 96+x, y, 22,17, -6, -9] } spriteset(bulk_wagon_empty_set, "temperate_railwagons.png") { tmpl_railwagon(0,25) } spriteset(bulk_wagon_coal_default_set, "temperate_railwagons.png") { tmpl_railwagon(0,250) } spritegroup bulk_wagon_coal_default_group { loaded: [bulk_wagon_empty_set, bulk_wagon_coal_default_set]; loading: [bulk_wagon_empty_set, bulk_wagon_coal_default_set]; } spriteset(bulk_wagon_coal_arctic_empty_set, "arctic_railwagons.pcx") { tmpl_railwagon(0,25) } spriteset(bulk_wagon_coal_arctic_full_set, "arctic_railwagons.pcx") { tmpl_railwagon(0,250) } spritegroup bulk_wagon_coal_arctic_group { loaded: [bulk_wagon_coal_arctic_empty_set, bulk_wagon_coal_arctic_full_set]; loading: [bulk_wagon_coal_arctic_empty_set, bulk_wagon_coal_arctic_full_set]; } spriteset(bulk_wagon_coal2_empty_set, "temperate_railwagons.png") { tmpl_railwagon(0,225) } spriteset(bulk_wagon_coal2_full_set, "temperate_railwagons.png") { tmpl_railwagon(0,350) } spritegroup bulk_wagon_coal2_group { loaded: [bulk_wagon_coal2_empty_set, bulk_wagon_coal2_full_set]; loading: [bulk_wagon_coal2_empty_set, bulk_wagon_coal2_full_set]; } spriteset(bulk_wagon_empty_grain_set, "temperate_railwagons.png") { tmpl_railwagon(0,150) } spriteset(bulk_wagon_grain_set, "temperate_railwagons.png") { tmpl_railwagon(0,275) } spritegroup bulk_wagon_grain_group { loaded: [bulk_wagon_empty_grain_set, bulk_wagon_grain_set]; loading: [bulk_wagon_empty_grain_set, bulk_wagon_grain_set]; } random_switch (FEAT_TRAINS, SELF, bulk_wagon_coal_default_switch, bitmask(TRIGGER_VEHICLE_SERVICE)) { 2: bulk_wagon_coal_default_group; 1: bulk_wagon_coal2_group; } switch (FEAT_TRAINS, SELF, bulk_wagon_coal_climate_switch, climate) { CLIMATE_ARCTIC: bulk_wagon_coal_arctic_group; bulk_wagon_coal_default_switch; } switch(FEAT_TRAINS, SELF, bulk_wagon_graphics_switch, cargo_type_in_veh) { COAL: bulk_wagon_coal_climate_switch; bulk_wagon_grain_group; // default to grain } switch (FEAT_TRAINS, SELF, bulk_wagon_cb_capacity_switch, cargo_type_in_veh) { FICR: return 25; FRUT: return 20; FRVG: return 20; GRAI: return 25; MAIZ: return 25; RSGR: return 20; WHEA: return 25; // no default: instead fail CB and use capacity set in properties (30) } switch (FEAT_TRAINS, SELF, bulk_wagon_cb_weight_switch, cargo_type_in_veh) { COAL: return 18; FRUT: return 18; FRVG: return 18; RSGR: return 18; // no default: instead fail CB and use weight set in properties (25t) } item(FEAT_TRAINS, bulk_wagon) { property { // Some bogus property assignments to test handling of units speed: 10 m/s; speed: param[1] m/s; speed: param[2] km/h; speed: param[3] mph; power: param[4] kW; } property { // We try to simulate the stats of the temperate grain wagon name: string(STR_NAME_BULK_WAGON); climates_available: ALL_CLIMATES; refittable_cargo_classes: bitmask(CC_BULK); non_refittable_cargo_classes: bitmask(CC_PASSENGERS, CC_MAIL, CC_ARMOURED, CC_LIQUID, CC_REFRIGERATED, CC_HAZARDOUS); cargo_allow_refit: [WDPR, SCRP, FRUT, FRVG]; default_cargo_type: COAL; sprite_id: SPRITE_ID_NEW_TRAIN; introduction_date: date(1880,1,1); model_life: VEHICLE_NEVER_EXPIRES; retire_early: 0; vehicle_life: 30; reliability_decay: 0; loading_speed: 10; cost_factor: 182; running_cost_factor: 5; speed: 0; refit_cost: 40; track_type: RAIL; power: 0; running_cost_base: RUNNING_COST_STEAM; cargo_capacity: 30; weight: param[1] ton; bitmask_vehicle_info: 0; } graphics { weight: bulk_wagon_cb_weight_switch; purchase_weight: return 25; cargo_capacity: bulk_wagon_cb_capacity_switch; purchase_cargo_capacity: return 30; default: bulk_wagon_graphics_switch; } } nml-0.4.4/regression/groundtiles.png0000644000567200056720000000573712643457545020727 0ustar jenkinsjenkins00000000000000‰PNG  IHDR@0WSítEXtSoftwareAdobe ImageReadyqÉe<PLTEÿîîïïððññòòóóôôõõöö¨¨¨¸¸¸ÈÈÈØØØèèèüüü4®—®?€€€€Çuxxxx\/øçæÕ¶Msû÷T‰.º×«Tõ<¿^e‡ñž*o°iÚ¦mSzí¦~?@øs•¾šçª*çתŒ#ðû·(ìkl}ûüM»ü‹ž¯RUs‰Çùù*ãþ ~ÀÌkS×´„0…çnÚr‰„#^öYx» `s¾Š,œË窌“\"á/û,| € …6áðùo|zü¦cá\"a<ûB¾UÅ`›+wŽúþñ*Ã4Žþ‰„ñ4í ù ¢÷µ±MM€xÓ§‡”€ôþ±›\à\ÁþWNÞ³>V¥ª›«k‡×êTÍòÿ¾:ÍU\àÜÄþ7M£¼g3üÛNŒ#µà *â rn®:ò^n!¸QŠÐ Æû«Ôµˆ²ï«¾®Éī޼WE„; n”°b't¨ÃÛly¥€Þ×à9ya!ú  )²¾ï¦•y…7jGÊ(÷UYû^=׈7÷xr¹Î÷UQæM"ÜA{ (x œâôVÅ0>:¬>€V$’h©ùK¨˜BÿóMGz®8G¶@k†…Òý\E ró û% 2×÷=¡«Ù/!¼Ve çŠ€s„a ´f8 )Ç·ÎA¼MÂk” Ó¶Ÿ©j]E‡ÐtÊÎööMS©“‹¶Ací V÷UÈß«,ÖA³õA¼®TGÖ@ƒG}» •:1¹h4æQÑ£Á:~òÓ± H—Ö ÈB—za8#hâþú (Û°ÓfºQ¹Ç&hí¬t4ºµ“Ë*Â.WõÂ^^¥ñáç$ø± ”_XxYÅLwPî± Zû#+G š ÝÚÉ“z#XX^<Í/%ƒL$ÁK2 ûúM‹>…_ “Ò²êuäÈöx½ àaî;@= Ç)¤VÑ’¾ÒáÄ¢ðzÓ ¿V} ¿&¥d;Ô7êÈÛãKzd Ùè„'šqHLÔxrÀ¶k™iR¶“m•ì*Ñܳ n…ýÉ´ œ¿WéÏ*^WÂzÕ ¸T ƒy0c@Ù:›ºßØÉ¶Jö •hn‚Ù·ÉþdZÎOèsŠxD'£–Á>°î:üøƒ37¾ $k÷õ¦ gÝo‹^¤w¸Ü÷Oe`†÷kg3¯0Ë飯$¿Tº¹<—tféeyáÈk•i´î·Eo wŒ¹ï)žÊÀ ïSzß L©ùèÅ;ðFì—°‘LÈT­úŤ%&ù\»Þ´«Pì0Àúââ aF6Z+tn­""%VõF¸Â/νÃïΆ¥0p®Ý:àÍk%Ö¤Ø)`€õeV3²ƒµÂq|@2Œ½¨I›SRó¼‹ ™;/²tň'¼Ó*’R2.&a3’ÈТ¤+*:g9Çf­’µûZ½¼/Ƴ„~èúú¬0ëãt&…µV‘”’q1 ›‘ -Jºi¡â8ZαYåa •º$ŒKÄ.[‰=tLE:öF!dó!YÑKŸ+Yè4§¸E³YÆE‘Q#Îl}1 Ý}e  Wœq"Ley¶^8AAPQXxªz™é®2pÔœ2.šÍ2ž¦Œqfë²ÐÇg(ª‘Ò2Z‘rË $€  ~¼Æ?4?ÐOG3®À¯ \"q¹àâ£O¯BüÏeÄûß-Ù¹Òóʈ÷Wƒôð»Êˆß¼œ?¾ÜaF|YØ/dÄPÈÓï##¾Å…]²p9o»1@©Y|ñ—³d_`œ×²ÍžÕ¹¥LçÓÊZÊfÌŸÙEÝMFü¥`˜í³ø-??Ëó3{«ï2?0g¬Öȓψ¿Ïo¹p2âã¬Ñ6-ù׳üzfe ÜH9iQÁeìís‹<åŒøKq~>—nFüóØ ^0Ì”¬è=A ‰Ëçs‹\âáT¡=ñ—‚¿}>‹‚;ñÖ’“oóòš/=B<"ìùü–ærY†Íûfă=¬ À“Ë3¯½‘ |ÑfÄ£*ßnõ ñ  +~ P\ˆIeÄ#qM†+Ðk_ʈ7G·Õ»v½à¡)8zÎ1©Œx´š.Ũñ¥>ƒñ‰Y¸$Ó¶7#¾há°_ãq3âñ¼óŠñà:A…x_F|ùŠñ<´ \Gš;8k!^µg–Å(5-ýõýVg^ç2‡KD.—¼ ñ³·ðOeÄ㇭w—gáÛÀ¼w߀0õ0Óîþ-`ýÈT€)1´¹ŒoåàÁs„ÚoüG)æ[¦™ì†_ÂE¬‘9Ž£„ƒG¥ÜñÅ:Í,ïî7>`®ŸÞxÁ)Çè¯9=Ô˜âë¦ÜÀ=â67¼»ßl<µPÁ )zÁIcàñ£³¦Äb©Íp“7-ìK.¥·üøT|Á)ã^g©ÌDLÛf`iÜîçp1´p©WrôÔ<§i8JЙ0…$Ø îŠ§4€áÍ3ëDÙHgV§–¸,Z†ª°ú e•âã8†¹—Ôf¤³É$ËÀ(ˆ« ËÂeW«…NKô=|—¢‚ ÏÄãBÀðÊSLÌO `*«‚¶HðÛ.€[™B|Õe„}|:°pI¢5&%gèôæ;,0}]'w€9N]L$&Ü—3âõçÿ/eÄã“bZŸ/„NhÝÑ\Ó–ÊMÝšÂ7àŽ’On‘òXlBÊ´œNÃýi)ªþ©µ2]\qGñ¹Ð%órJÀ©Š*Ô¹Ài«Q3⫎¢B:!`œ•LGw$™–1â]`T¥Sا×âáh¦…xú|‹5¹ƒ³âõ§=liª%gËQ-[âåñ‚,¬jZg”‹.SLL—¼Q«?s ¿(Äc² ónéòrxßPm^ý4tigGm\J7fÄë”xªÒtéÞþëÿ<ÀªMžNF<ìãѧLwoî-à6ëÿÊŠ_‡/Ú/ %žK—@o"‡n¿ |¹~•¡(Ì„ˆÍ¥xi P„P×CÔ#®(uÛV—ÞMáÍ”2×¢Z{lè=–þÁ'Ú-C s¹(¦‘ïX¸µ:í üöMIf+¡©$¼Ókà¶P¬›ßp <0@á‘/à ‚` cªzaòrÄëÊo²° i)£+øišo³¥™'#^a•íÙƒ¸te¸td:gn˜Pß WŒL }?²ßÒ ´ ¸²µ²/†qicXÝY˜¬+&äÖn‹¦ÃcTB\6BÂFƒãL5­c·\2ݞÃÛhî‚3 &¸%Äãä­•bp%õæ°‰ÓEI_×^%"ôÑÔ„æ0æÒÖu7w¿5¼v„x¦%wüê†Y_oe^!¾àØB;6—2x¾´5‡kßÎ]Y×·…xªMÿ«ñ5ø‰TL#‹ÐÀö®u‚¸ž»-î" ƒS7|"ÀÆÆ!.¿âŽ¢Z¢ÐVÖ3véÂ<-)°1œPMÜ‘dÚš‰†M #ÞMÇŒxY¿Ž/¸žï$ˆÏ^ˆ_ÄWàø¾ÛZÖŒøùÊ´kFüÜ-¼ ñ?â««0mdáuÿn ñÕô…x­Óz„x‹´-ÂC¹xÅm!¾º'!ÞlÖº1+Óü!žßoÚkãä‹c\›Èw Ä#®-ÄsÉé"ì‹ÄKãä»â5® \K,Æã»ÈÚóÉCÝ#O^ˆïp ˜5œ.Âw³yâB|k&5U/Ä›º6§,Ä›¸¦¾2s~´Hˆ7'*Ä÷¸®_õB<÷ñÕpáa#OAˆ·€[!þŠ[‘m‡^—v?y  aîBüyjŸ`ÝÌÓí]Ú’ºíK£Û½KSj:7­Þ¥`,ò@½˜"·w©ÑŽÖn^ùz—v‡gZ½KÍZ7M4îOõ.¯“I÷.¥­±Æ5;’n¢8N’¶Ù›1Zè¡Kc¯ÓÌEžp¦7RsU4´Um©B×=ÔˆìùÃR0Y·Ó |ïÒöÐ[A¦åÕ¸BäF¯žªŠ®õÂÝ!£¡–ÅŒÇÔ<Òl×XØÌ 2Úèp+Ï„À(kZ¸ò™VÏŠ ¼!áräÁv:["¨ÆÍZ\+#¾Çd,»8²{2â±k‡¬q§Ð»47‚õ.Õ¸àÓÉÏ7kä´E¦Þ¥é”Úba4VŽs,]Ýâ•9~Pš&ŠR@nçBÒº·rß6Nˆ˜p£ÑjÄWq‹ ¸YÈ|ƒ9\‚ûÁž¸ã”¦ÁÞl<Å:z¡›µZsX)*¯eáŽUš&¦"li2½f­°B~!>߬Õóâ×Þ¥s¦÷‹jåIËÄ$Y p—4¹ —hã2^ŽL» ñs·ðOÕˆÿ±Ä~gB¼¸­mTÃ7¶Ü9pên+Ÿ÷ÐÍ-7þ@z'À‰¸-|Ýú« ¹à´'I­_ˆôƨª±Åýi,ÒI§’mÝh·ßãMã¶Ût·ÛnwxÕôz5@ÁW9l·‘må€À ìÍn·O†ÀñfK€›À»DãÁÕžnìi˷쇵l‡”^Èœ!ß-mx›ÀÚê°yK\û}Ôÿ‚ÚncÚÿ]|5ð>ÞÓö»x¿t%àõ“=-êÃWH—>–åÒä†w¿ 3ƒŽC´§Ñ^7áÊÓpù€GH£}çØ!]ZïJbpCÜE=3#.L!^1ï§,¢mú1°pY0qô–T#Çv àÒ;Ë¥)Ti7Ç™¹E\¯oåÃó…xÎXÏù}dÁ_ïÒÚ©d¶ÛÃ7Ç»ãdÄ#rLp·³ƒ` —î£t„¸»-˜cu¸cdÄsZ+[Að—c…xópui°8tŒÎè™ñÓ‰ñááa1ÀÏA"YNÛƒÅ}Ôò}acâWàxÓæ< îÆ–;Î}ÀY›Dn|[ ¿ºñ&ô%v‡üÀ¬IàÝV¿¿j¯Œ›xå†íÙd€ÓŠÇoCà|³#ñswi¦/ž1@É7pôCÓø’Çv«Eô!p¦¡€ØÌˆßír _)r|ÒÃ>x,`24½þ…l"Àz1íow-.PXBünŸ=Ðh¯Œ›påâáuþð0…9¬]ÚšÃy‡Kv±…øêe!Þ“ÏXÖ»ÿƒvìsX»´5‡³nž‚åGâùNÏúxÅ,´K;2m®?Dƒ½Ã9:ž¿ßÑœpƒ`ð÷ÙƅÙ~\!>Ó^ãÁÖsë:çÇcï÷ã ñÚk`š”a-l¹4 ñ°Px!>£¨TˆÏµKQæâk ñ¡3â#—~°¢ô¬…xÒ,•Öì½´\j©ÆËù @†Sc¶ —Þí–$Ä3<-©4Í*ÄÏøV-Å‹cúª%¾ÇV•þðˆ Ü÷fu‡ºH/0þ‰éS¥óÊ ,$¶dEd˜ÊÁ¨ÕÅÓÙqáE.RNqqW•êíÀÔ‹Ö625ÀoÆ{êK‹îqÙL¸.ì*âZEñÑËoOQ|.º&=rH`m#˜‹Îr²qŠâ{kÄSxøç­ïA ¬½±±ƒLç§×­/n\jy«(~ìÁ âÆ 2àä—¢êqM`õBQüZ 󥯅´;deÁ0.mn¥y{Ü[Ÿ‰—Šâkd mMpàËuX}©`N^q­ŒøzP#¾æÒ“ß# y!Ü EñÀXP &®u®qP™q>òVFüqå´€í¢ø£eÄO¥(¾´ì¢øc ñ)Šï­Ò(Š?¶-Š_NÈ¥•êŠâ¿†¯‹ââ]`]#^‡ª×âƒ×ˆoßZ+­µYë¬D¼µ‚ø ì®J<Àˆ‹EÊ.Š·ºV-vQ)_Q|ôˆª²‘CS½d•¹À´£`7Uü»¢ømUüa¥xþDa#†ÝÅ׸XWÚm°È=Ç\á¨/!®ï#´XÈ,«ä€À™®ˆíÅoq«,HbXøÅqJ€X ;Õ%£õýˆ/îˆðÁ¨G__û›U¿ufÀÍÐx±Yæ n§´þ¦šéº ¨¾EÇ7~à„ Ècª5–LŸ‚…s'ÈJn„Þ*bSˆ¿DQ” ÐÇ“H÷ñ#ëûèÜ cë?p†L2~¬¬TØ¢øÚÂW`œgx ²&i„Æûùñ¼€ë¹ O ]ß±pDÕúõÜMÁKÕB¼‚i¯ç2ÅŸpLKôÜÕX"žbz.·a=,0LØê:«Jê1ÑÜUcª–Šæ²&.'da¥ÈÄ0wÕØ2­-pØñ:hÙ5â“8}!> _#Þµðì…xwÏ^ñX‹âÏ8Š“ÀgÆø•K$.—¼ÖˆŸ½…W]úÇÍZßøðñãã§Çv|›Ÿ`Ì´Yë{äb¸6ü#øO„|çÍZ“P>ºÍZɾø£‘÷ƒÿôøÑ߬õý‡ÇÇ 5k}Pþf­h¹Ÿ~¾Y+O6rH`0ØããGO³Vš¥ÇiÖúè ‡|·ôá=îÌ Y«Jã5kÕÈSÐ¥ÀîÇÇq›µ‚»|úô¾Y+¹ô£Õ¬õ=ƪã7kÅSاÇÐÍZ|ÍjÖ !øñušµ"rh]úÃ{\:,¨Y«;‡f­ãdÄO¨Y«;‡íf­ãeÄO§Y«3‡Íf­ãfÄO¥Y«3‡ûf­ãgÄO£Y«vé5kÞO×f­³U<Öf­³^›µ.ÀÂhãåÈ´«?w ¯ñ?>œNœŸœ[ÞÝðö´ÝÞlÖ*N'ø°{·ßÁØá?¼-$Ü;ùG1¡f­[üÎÿÖ VãâäŸNÛä@wx[¢…ßyB§ÉsÁ„ä¾f­G@€~´šµ‚Cì¶BluSl"Ha O³Ö?ïáxšH³V~ûða³Ö¬'íf­‡ôtbü´M;ïN鷜ғ¯Y+‡WAdÓÊ“#r½s›µv¸`—“8YÍZOÉ»wع1¡)Œ—wÉ ¶ÀÕ;O³Öƒ8¦Çéµòšµ˜ÑnÖ*®¸'ÀãG«Yë~…±Ó.>´ö=âöœâ“§Y럴n‹,‚7kÕ6]úH¡ŠBŒøÈÜf­Û-ƒmõØÑÍv‹§F<;ÄG¢…ÿºðÒ¥ÊpéôplC83÷#5k…Øœtø:íO§.­-|=æ0À± UÇkÄspl _Ç=Åã„¢ôxѪÆnÖ á `!bït”6›µ¢‰1T^#ža€Çi´Yë—–ïìf­‡ãk5k=ƒ7kÕKË5kå8‡ù‚šµžŽpöy· f­• jÖzÄ3ïaAÍZdâµYël×f­sþ¾°± ñ+ð ¼Áhù<¼qÀí¾úvЇ›“¤ª’7¹ç†¸ ·ûú°V*QGQú[î¹ámÖj¼Î€»}uµUè!·Yk…©zñoù~xÃÛ¬•þ„må ü¶e<Y} Õn—#^ô¦å„Ý–‡‡L›éäX9,pšD¶KÖM£<4k­"Øý4{xÐ7à€\·øšµVyœ:Vœë½È=s­›T™Oˆ¿Ù¬Õ+ÄgQâX9|Ð2»iMN:R³Vî^wAë?FéB8éxB|åÝë†mÖê­6È€uGoÖzqA›µ¶Aë œ0ô¿$ï­;žV¦´ÏC`¯.=ÿf­ƒ(ýÊB¼-Ä£ô¼…xwi9MˉÒK¶––³î–Á‹^^³V-įÍZg ü}acmÖúošµþñ矟??}y‚ñåzQÝE³Ö ¾w¿ÜhÖZxšµþ¼@ŒÐ툿ò=4k­þxúòÙ߬µ¨å³Û¬•ì‹?Wdº~úüå鳿Yë>=ÙÈ!aÿŸ`O]`løˆ–ûüå盵ÂùBÈ“hÖúç¸;ƒf­÷éó8ÍZŸäà~ršµ¶Ö}¯YëÓ“F߬§¤<ħY+"y ߬-üåscŒUŸÇnÖ*žÆ¾<…nÖê-lÖŠ6f­ˆºY«´æß¬Õ ZóoÖ À_ àù7kýó\ /¨Y«ãү׬µœH³V:+=­ÍZ×f­³ñÖÞ¥+ð¼€×f­³—i×f­s·ðËB<¸»ûnéEÞûnI4rBéôî7 —ñDyÕT-…Äm¾Û=Àô€ƒriI= ¥ðsNøOcÕ—ò…áâõ1klä kéÈà·iàh46°ka¤å8¯jI/ÔH9(°ÞeXã6„[3øâù?5ë2O›+òÔ€ \ûÛ˜éÑn0V³~pîS-E,;äiÛ¸¤8Ûï'QžÄ‹dçóùë™é;€ë‘iñ=oå¦Ež°žÔ®~Ø®¦¥d‚€ÿ:ÿõ7Ów~òP 9úë­lÁàÀ5£}Ò¸ò¯¿]`ú@æ1ÀÕ û þ›é;ôÄ¡…Ÿÿ’-2ž¡ÂÊ´Ú}e¿ƒ¬î¬û×ßÏÜH«F¼ÖŸ[`zßa{jÄ3Áåù[Ç6,L*-Ff`~þ[rá ñL羓Ïp÷)òz+ó ñL0@F­»áeÚ…I—hdˆBü¦_YãGB<ºhQ= `kƒ¹j @ÃÓxB²®ëË4qq ÷m„@`™j K$Ã@ ø_¨âÜHX×·ðçî0æ„@B"ëºÂ¦`Î)Dëºó XsN . „Ϥä“y¬ë>΃@`͉Ìëºó Xsj ÓºÞo®ëOHpKP@ @Á_‰?g¤e¯ŸIEND®B`‚nml-0.4.4/regression/018_airport_tile.nml0000644000567200056720000000100112643457545021433 0ustar jenkinsjenkins00000000000000grf { grfid: "NML\18"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } spritelayout dirt_runway_sw_snow { building { sprite: 0xA68; yoffset: 0x0F; yextent: 1; zextent: 6; recolour_mode: RECOLOUR_TRANSPARENT; palette: PALETTE_TRANSPARENT; } } item(FEAT_AIRPORTTILES, small_airport_tiles) { graphics { dirt_runway_sw_snow; } } nml-0.4.4/regression/027_airport_layout.nml0000644000567200056720000000153612643457545022030 0ustar jenkinsjenkins00000000000000spriteset(small_airport_tile_set) { [] } spritelayout small_airport_tile_layout { ground { sprite: small_airport_tile_set; } childsprite { sprite: small_airport_tile_set; always_draw: 1; xoffset: 32; yoffset: 16; recolour_mode: RECOLOUR_REMAP; palette: PALETTE_USE_DEFAULT; } building { sprite: small_airport_tile_set; } } item(FEAT_AIRPORTTILES, small_airport_tiles) { property { substitute: 0; animation_info: [1, 4]; // loop, 4 frames animation_speed: 1; animation_triggers: 1; } graphics { small_airport_tile_layout; } } tilelayout small_airport_layout_north { rotation: DIRECTION_NORTH; 0, 0: small_airport_tiles; 1, 0: small_airport_tiles; 2, 0: small_airport_tiles; 3, 0: 70; // original airport tile } item(FEAT_AIRPORTS, small_airport) { property { override: 0; layouts: [small_airport_layout_north]; } } nml-0.4.4/regression/010_liveryoverride.nml0000644000567200056720000000471712643457545022021 0ustar jenkinsjenkins00000000000000grf { grfid: "NML\10"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } /* * Turbo train engine (arctic) * Livery override for passenger wagon * Graphics by DanMacK, adopted from OpenGFX+ for testing purposes */ spriteset(turbotrain_engine_set, "opengfx_trains_start.pcx") { [142,112, 8,22, -3,-10] [158,112, 21,15, -14, -7] [190,112, 31,12, -16, -8] [238,112, 21,16, -6, -7] [270,112, 8,24, -3,-10] [286,112, 21,16, -15, -6] [318,112, 32,12, -16, -8] [366,112, 21,15, -6, -7] } spritegroup turbotrain_engine_group { loading: turbotrain_engine_set; loaded: turbotrain_engine_set; } spriteset(normal_passenger_set, "arctic_railwagons.pcx") { [ 0, 0, 8,24, -3,-12] [ 16, 0, 22,17, -14, -9] [ 48, 0, 32,12, -16, -8] [ 96, 0, 22,17, -6, -9] [ 0, 0, 8,24, -3,-12] [ 16, 0, 22,17, -14, -9] [ 48, 0, 32,12, -16, -8] [ 96, 0, 22,17, -6, -9] } spritegroup normal_passenger_group { loading: normal_passenger_set; loaded: normal_passenger_set; } spriteset(turbotrain_passenger_set, "opengfx_trains_start.pcx") { [142,139, 8,21, -3,-10] [158,139, 20,15, -13, -7] [190,139, 28,10, -12, -6] [238,139, 20,16, -6, -7] [270,139, 8,21, -3,-10] [286,139, 20,15, -15, -6] [318,139, 28,10, -16, -6] [366,139, 20,16, -6, -7] } spritegroup turbotrain_passenger_group { loading: turbotrain_passenger_set; loaded: turbotrain_passenger_set; } // Turbotrain engine: item(FEAT_TRAINS, turbotrain, 20) { property { sprite_id: SPRITE_ID_NEW_TRAIN; // We have our own sprites misc_flags: bitmask(TRAIN_FLAG_MU); // We use special sprites for passenger and mail wagons } graphics { turbotrain_engine_group; } livery_override(passenger_wagon) { turbotrain_passenger_group; } } item(FEAT_TRAINS, passenger_wagon, 27) { property { sprite_id: SPRITE_ID_NEW_TRAIN; // We have our own sprites misc_flags: bitmask(TRAIN_FLAG_MU); // We use special sprites for passenger and mail wagons refittable_cargo_classes: bitmask(CC_PASSENGERS); // Allow passengers (and tourists) non_refittable_cargo_classes: NO_CARGO_CLASS; // Disallow other cargos } graphics { normal_passenger_group; } } nml-0.4.4/regression/032_simple_house.nml0000644000567200056720000000133012643457545021433 0ustar jenkinsjenkins00000000000000grf { grfid: "NML\32"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } spriteset (spriteset_townhouse, "nlhs.png") { [98, 8, 44, 36, -22, 0, NOCROP] } spritelayout spritelayout_townhouse { ground { sprite: GROUNDSPRITE_NORMAL; } building { sprite: spriteset_townhouse; recolour_mode: RECOLOUR_REMAP; palette: PALETTE_USE_DEFAULT; xextent: 8; yextent: 16; zextent: 27; xoffset: 4; yoffset: 2; zoffset: 0; hide_sprite: 0; } } item (FEAT_HOUSES, item_townhouse, -1, HOUSE_SIZE_1X1) { property { substitute: 2; override: 2; probability: 10; name: string(STR_032_HOUSE); } graphics { default: spritelayout_townhouse; } } nml-0.4.4/regression/brewery.png0000644000567200056720000001634512643457545020044 0ustar jenkinsjenkins00000000000000‰PNG  IHDR,´ááÝ pHYs  šœ OiCCPPhotoshop ICC profilexÚSgTSé=÷ÞôBKˆ€”KoR RB‹€‘&*! Jˆ!¡ÙQÁEEÈ ˆŽŽ€ŒQ, Š Øä!¢Žƒ£ˆŠÊûá{£kÖ¼÷æÍþµ×>ç¬ó³ÏÀ –H3Q5€ ©BàƒÇÄÆáä.@ $p³d!sý#ø~<<+"À¾xÓ ÀM›À0‡ÿêB™\€„Àt‘8K€@zŽB¦@F€˜&S `ËcbãP-`'æÓ€ø™{[”! ‘ eˆDh;¬ÏVŠEX0fKÄ9Ø-0IWfH°·ÀÎ ² 0Qˆ…){`È##x„™FòW<ñ+®ç*x™²<¹$9E[-qWW.(ÎI+6aaš@.Ây™24àóÌ ‘àƒóýxήÎÎ6޶_-ê¿ÿ"bbãþåÏ«p@át~Ñþ,/³€;€mþ¢%îh^  u÷‹f²@µ éÚWópø~<ß5°j>{‘-¨]cöK'XtÀâ÷ò»oÁÔ(€hƒáÏwÿï?ýG %€fI’q^D$.Tʳ?ÇD *°AôÁ,ÀÁÜÁ ü`6„B$ÄÂBB d€r`)¬‚B(†Í°*`/Ô@4ÀQh†“p.ÂU¸=púažÁ(¼ AÈa!ÚˆbŠX#Ž™…ø!ÁH‹$ ɈQ"K‘5H1RŠT UHò=r9‡\Fº‘;È2‚ü†¼G1”²Q=Ô µC¹¨7„F¢ Ðdt1š ›Ðr´=Œ6¡çЫhÚ>CÇ0Àè3Äl0.ÆÃB±8, “c˱"¬ «Æ°V¬»‰õcϱwEÀ 6wB aAHXLXNØH¨ $4Ú 7 „QÂ'"“¨K´&ºùÄb21‡XH,#Ö/{ˆCÄ7$‰C2'¹I±¤TÒÒFÒnR#é,©›4H#“ÉÚdk²9”, +È…ääÃä3ää!ò[ b@q¤øSâ(RÊjJåå4åe˜2AU£šRݨ¡T5ZB­¡¶R¯Q‡¨4uš9̓IK¥­¢•Óhh÷i¯ètºÝ•N—ÐWÒËéGè—èôw †ƒÇˆg(›gw¯˜L¦Ó‹ÇT071ë˜ç™™oUX*¶*|‘Ê •J•&•*/T©ª¦ªÞª UóUËT©^S}®FU3Sã© Ô–«UªPëSSg©;¨‡ªg¨oT?¤~Yý‰YÃLÃOC¤Q ±_ã¼Æ c³x,!k «†u5Ä&±ÍÙ|v*»˜ý»‹=ª©¡9C3J3W³Ró”f?ã˜qøœtN ç(§—ó~ŠÞï)â)¦4L¹1e\kª–—–X«H«Q«Gë½6®í§¦½E»YûAÇJ'\'GgÎçSÙSݧ §M=:õ®.ªk¥¡»Dw¿n§î˜ž¾^€žLo§Þy½çú}/ýTýmú§õG X³ $Û Î<Å5qo</ÇÛñQC]Ã@C¥a•a—á„‘¹Ñ<£ÕFFŒiÆ\ã$ãmÆmÆ£&&!&KMêMîšRM¹¦)¦;L;LÇÍÌÍ¢ÍÖ™5›=1×2ç›ç›×›ß·`ZxZ,¶¨¶¸eI²äZ¦Yî¶¼n…Z9Y¥XUZ]³F­­%Ö»­»§§¹N“N«žÖgðñ¶É¶©·°åØÛ®¶m¶}agbg·Å®Ã“}º}ý= ‡Ù«Z~s´r:V:ޚΜî?}Åô–é/gXÏÏØ3ã¶Ë)ÄiS›ÓGgg¹sƒóˆ‹‰K‚Ë.—>.›ÆÝȽäJtõq]ázÒõ›³›Âí¨Û¯î6îiî‡ÜŸÌ4Ÿ)žY3sÐÃÈCàQåÑ? Ÿ•0k߬~OCOgµç#/c/‘W­×°·¥wª÷aï>ö>rŸã>ã<7Þ2ÞY_Ì7À·È·ËOÃož_…ßC#ÿdÿzÿѧ€%g‰A[ûøz|!¿Ž?:Ûeö²ÙíAŒ ¹AA‚­‚åÁ­!hÈì­!÷ç˜Î‘Îi…P~èÖÐaæa‹Ã~ '…‡…W†?ŽpˆXÑ1—5wÑÜCsßDúD–DÞ›g1O9¯-J5*>ª.j<Ú7º4º?Æ.fYÌÕXXIlK9.*®6nl¾ßüíó‡ââ ã{˜/È]py¡ÎÂô…§©.,:–@LˆN8”ðA*¨Œ%òw%Ž yÂÂg"/Ñ6шØC\*NòH*Mz’쑼5y$Å3¥,幄'©¼L LÝ›:žšv m2=:½1ƒ’‘qBª!M“¶gêgæfvˬe…²þÅn‹·/•Ék³¬Y- ¶B¦èTZ(×*²geWf¿Í‰Ê9–«ž+Íí̳ÊÛ7œïŸÿíÂá’¶¥†KW-X潬j9²‰Š®Û—Ø(Üxå‡oÊ¿™Ü”´©«Ä¹dÏfÒféæÞ-ž[–ª—æ—n ÙÚ´ ßV´íõöEÛ/—Í(Û»ƒ¶C¹£¿<¸¼e§ÉÎÍ;?T¤TôTúT6îÒݵa×ønÑî{¼ö4ìÕÛ[¼÷ý>ɾÛUUMÕfÕeûIû³÷?®‰ªéø–ûm]­NmqíÇÒý#¶×¹ÔÕÒ=TRÖ+ëGǾþïw- 6 UœÆâ#pDyäé÷ ß÷ :ÚvŒ{¬áÓvg/jBšòšF›Sšû[b[ºOÌ>ÑÖêÞzüGÛœ499â?rýéü§CÏdÏ&žþ¢þË®/~øÕë×Îјѡ—ò—“¿m|¥ýêÀë¯ÛÆÂƾÉx31^ôVûíÁwÜwï£ßOä| (ÿhù±õSЧû“““ÿ˜óüc3-Û cHRMz%€ƒùÿ€éu0ê`:˜o’_ÅFPLTEÿîîïïððññòòóóôôõõöö¨¨¨¸¸¸ÈÈÈØØØèèèüüü4ËÄQQF¥µIb²Ìš=ÄbŒ{¿ išÞ”EA"mL¥sUiµÖJ÷q5ääÏ_ 5Ò£(-(!eQå 1¦*mb3Céi¶X)Å•BÊ»¢tŽhÔÆXSZž¹€µ9„b„w¤œ”Wœ¸ê„ "jcLU•®ÚK,þ+°ш°p”â„©ˆN#¤QWeµŸõK’Å9!QkJqâŠØQ’"h”¦:>¦ÔØýĪ>ç„hJ1ˆRTŽhŠiE©Ž]|l÷rëð‹°O’ÌZc NHŸ¤˜"EÔqåâckÃÞc‰$±ÖkA犊hÄ(¥u\¹ÊÅDZŽÿ°’$³6I²Ìƒ“IáHš""PÇggUåâãnXöøwÇÒÆ&6ˬ±IbÖ8!:¥!¢Ž«êìÔ9wÃʾ£õwoàgci4¶½«IŒ)%D#bD£QÇÕi¿®ë~¿c²ìóZw²~6V{ßœ˜,³ÖPZºve\ aåêºöj^wÃ2ßÑÚó1ÔØ>¾jo—«ªt^´û.JQÇ®®çÒ+¦:b%‰¡ôÅ`ÉŸŠ5ÑQ4Ö¬n—+°Õ¾ uìê¹”RJßËJOËßk2iûÉ´xV•g¥&@¬¶©µWRÇRÊnXíM÷o8†“ÉdRœ¢Ñ¬°*)Õ¨'˜ðžsêæLIߨ±\tÃjÏr|ü›aM&“W“Iá\AHÔà¥h ¢žÀTÀ òJJ/U£Æ“媲¢Ôا¸þv¬Ÿñðo2™L&­ ç W®ÐX›´ZLx&˜÷JzÙ0ßtÅ:;¥ÔXkí\{øˆ¦…Ò‘W¸“vµYfm«%`*˜—R6’]3¯:w–±&36³6{ŒkïÆpõ ‘RÔ„´h¬iÿRh³v™`^ªFÉ›æU÷}–1I–µ÷™ÇÇ{ŽÕ& #Н¢Q£&„P“%‰µ7Ó¨'L0_ËFåËæU×­C–µ›5™1 ã^=¢Ñ£(¢H)"j­#D­[¤ÄÚÄcÖ“È@I¯n—Ëîc˜Xk­5ÆcbJmò×ËHVE°tA4Rš¦ˆš­Ñ˜(Bl_šIljL–™˜ÒU¶@J?þt»Œ]½èž,ƒˆ&‹)=­¨ý*^/Ë!"âS\›g9)Úçí)’²$Ƙʥí»k֌И˜ÆÕY«Ea!ýø6vÌ×]o¤ ê(EŒéiå®ÍLLãòå`9Ä4"ŒÐèïa¹â® DGiZ^—eT:)k­A­£˜žV§×Τ4†é•»þÕÐ÷*Ò©Ži宫›ÊÄ4Ž‹3†®}K1E4V뺈ÑXÞ”×å©s5¢±Æ¡ÖúÕÆEÒRyµ¼¹™vÆ:¶Æ¡ÓZŸ^W7ÕòŒÆô¤*w…ÛXi!Fˆí=žþ.ÖMqw]R.Ë›Ò9‡)ÐØí‹¬h)½Z.oX»nXñ±5ˆîUênªeEéIUT»ÃÚÚÁ¯ªŠ˜¦›%ú9¬ò¤8Zw7EÔr9§‘FˆÆ&Ö8ç^éö"czR•ÕµWŸ–·Ô½wÃêÇ-{´¿©Ê³b­“µQUíÃ46ɾÍÖÆYþ(I´,î–ÅÝuqBHébQD“˜6[ÕòŒÒ“ª¸©ÊU²nc÷¶á±æÒXkœs«\¾ ¬íªr„¬ÞBBžCöGIVár® d‚¸b¶‰5£u–UY1?Ÿ7Ô½]¨kßKŽíj÷Ñ‚ßVåY»Ýb¹ê«ªr!Ú$ä´tÕ“Xœ³?JG˹"*ÑQŠÆ®•®óp[•Õ¨‹ßªk¦nº>ü“ÊÚvlÁ?Ueì²zwXjì*â ·]U…+!䴬ʒlqm%Ëû–kY¸û•1BD“Ù­<|ªb7…ùÛ†«¯–±®L¼ Nc—Ö~—«a;BŠ5׺ª\áŠÓ²*«’D[\[X|*F#öGI ÷°2jlw£y . üøÚ«¥W·4î:†_§Âûw»ì¬ÚUý¼p÷\«ª*\ÑR½Ï#RFÕcX XÃx˵¹2júu›1ïÛXAìþ솥Æ_{ßÈ«|yâª>)\[Òëª"®¨Jõã<Ïû§Uuú(ÖMÙW+c¼•‡Ú3 ¼Zz ±û³ë#9Þ÷^H©Æ;ýƒEñ5—Û¨ª>!$ê㸥¢î,!`Æ8ç#¾¹2–ÅÉC2á½5LÁ—-U=ëZð[(d­äÕx§Ébk.WW·µ¬w»)e Øà"QÐÏTÓFŽŸØ”²‹’,pÄt]õ'ÅÙú"æ7õ•º­å”˺nT#Ρ隬­TÍÕ§yì”Ü%~“kØï÷û÷TÞ3uð8ãœsvQ’f ¦ ðv#Qn]ä•ú4ÌêF5£ÑhÄ9tÝ:ï…\¬R5ŸÇî\ú]Ž¡Á<4œßWý*U'ç cgª~ ‹ãœ].ÖðkžsöÇÖèÌç±ã¬™IÕŒGÓ÷ݰã¯RuÞL™Úéó¬ à8»¯zwŸªF6JIù84«ž^B´]Ï™çü¾«®Vɸœ5c8<<<uÆ’ãÅÁfªï™ÚmÁSà× 8óì~©;o¦¬ñjš§ÆÄý³’|dÀ— 8ã¬íª‡‹d3²QÍèððððbÄ/ºüb3UmÊwŒ5›¶‘ðb ¾åZýŠò QÀ9¨§’µ9Â%9âm׳™ÜºÈi ¤Tðùßm²x×dɹº­¯îSµrGcHZ¬þl /xðÀÙÅêW\4RަÜ?…%6G˜±‹’0à `&çê¶ž¬/ò¦ UóùóÝÝçÃÑ´k²cµ*©v”,’ȺŸ÷™?ZEÂàœ¯»ê@Âᔃ:x ë~„9[­Œ\Ì@È틬A£àîîèèèè®{²ÔxÝUSUK)¥T;ºÝ"ÏyÞWõÔažûf}åbÕUò\§Ü?u?«Æcä£BÎ7FG.$0/½Ÿçy~ÔK^m¤J6JÉ«íàf"ï+%%ëÏxáuã±’|«e¾ñLÉZ.0Ë£÷ùput|?k1^u§|}¾h¾a€ª¥¬Ù¬>k;‹/ðbÝU^Ž.ž/ø£5Ö}ã•ä~²Qc)aê•̇Ã/_ƒ/_ƒŽ›R©Æ-øëײýùƻš)•”S³þù±ê*_ß}=¹ b=³Æ{X ”ló̫枪ëxPË«+%•”rqÞ9]€ÜÑÃ? ¥”j˜ÀŒCÃà¨]Ï䪫¼:zŸ¿5<Ž%Äz„gÇ9÷LI%çëq bÍj9>h»Jz ‡#î›m€TRί‡9x~:ðÀ½ðê°M–ÊãAüöòñ?X sѬF¸ÝŸ¶Ç´«—R£ Ò3PpùårÞë@ɺͦo>ØÑNWɪgùpðúõ%~Ú?Ÿ_a.@y5ŒoŸ~™m˜¯FÄÆ3±h` 5|y<ŸÏ¬† 9n»Ê+¯î>.v†5¼œ‚j–ßôz½Þå0 fýþŒÃlÝU^ òç_“^¶#¼‰õAÈÕÖÃ×£ÃÑx` ^~äÛ²^¯×»’‹«FÈÑ”{)®ŽÞçï¾=Ëßt»3¼œÎ^þõ¶»úÜÏÖ]•çßwx žƒØh<ò~ëq÷yÊ€m^dW,õN^y ‡£ h„Êóáp8ØÙ›½^~¹õ¯÷ÞäÀAx•cóüe/¹â>êè}þÞÃëÁ~µØëõz’c¯F‡#îA¨áp0Øá;¥y\¯†_Þ¼éþÑÀ0÷ ßn=¡òá—/ƒ¼ÛY¾ùÌSʶ«8€POe‡X« Wo?ö9ÊFã /¼j»J¨áb°ø>ñ\mzÕÕª¾Éæ ÁêõÞ¼ ~üC§uã¿_ ÍCw,jìU>Ì¿7Ñ¿aÍε'PI}Újš.C%E A¬¯6EEŒ*¿¦n Qü¿TTD©&ÿ +~Í€ÖB ‡Hoz[~ˆÓ’¢ÂÉ—ã«H¢ Ö|]Ô²w+dtôê ¨¤í#‡ê½:ߨQ‘¢A*èYâ|_ù©¬ÏkM ”Po·ªèœ`çÞrs*UÍzT¶U3A¥®p§1ö*GE€Š•a*/@EˆŠ³¨½0úìÕûkŒ [+Mj·¢êY̽,Ùë}–¯=ü®¿ömýIÔÌ¢™eæ«GMÏ×x„©kמ(Oó7ò3 ü³ÕIEND®B`‚nml-0.4.4/regression/012_basecost.nml0000644000567200056720000000047712643457545020553 0ustar jenkinsjenkins00000000000000grf { grfid: "NML\12"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } basecost { 1: param[2]; PR_RUNNING: -2; PR_BUILD_VEHICLE: param[1] + 1; PR_BUILD_WAYPOINT_RAIL: 14 - 1; param[param[11]]: param[param[11]+1]; } nml-0.4.4/regression/fonts.png0000644000567200056720000000224612643457545017511 0ustar jenkinsjenkins00000000000000‰PNG  IHDRµÆÇí sRGB®ÎéPLTEÿîîïïððññòòóóôôõõöö¨¨¨¸¸¸ÈÈÈØØØèèèüüü4P±òhJudGÝDS]”·†½M]³‚þ—ÎZj“«d†6ÔÔ½#ôº’ 8½9u(•О<‹5Ž8„šÚgX P‡·Ñ7 `èk)¦;Xaã6žO]Š¿z«×5 ¹Ø¦ÿÊIcÔÚ•;½^ ¢| ×ßZG­B‡{W¨¼ùL…OÉh9Ñ!‡{]7‚†®ýs¬ìêåö‹OÒƒ‹'º)dp62?öL™üRMê`Ý8Ùc'[Ù>iIEND®B`‚nml-0.4.4/regression/oneway.png0000644000567200056720000000513712643457545017664 0ustar jenkinsjenkins00000000000000‰PNG  IHDR ÝFìÇPLTEÿîîïïððññòòóóôôõõöö¨¨¨¸¸¸ÈÈÈØØØèèèüüü4x\ òò¶}>kžx›G CÉöèjx¢†„»ší¯†_@µ:>"Òðùýý§m1)Âê¢Ë½…ßÈë! ÝGjlºMH¯ÈÜ?Mg¾Ì«ü".AGß?çmÛ?ßßÏ¢@<•R& 6°büÁ&]Cg‚&¥ÆFv24´øŠ2FþI0€R#´”1YüoàãCòÀêàÇP«4ÞѤ?G´$r_þ!'I±$….¤ ™@WÃldã/²«Yÿj°¤Ë”މHÒ%ó‰ E/“oØ™@jÂEH&ÔzôÈÏ­AÒ8.8iŸëÆ~;?.ÔE¾,z||Ö›×ó^;Â7\¾žl±ÈÙ,?J"M|<è5û ÃŽÃrÍnº(12…°ù¸gí«1ÌeÇN&ô­9e¶>’‘Ûuz[ü»³#Ô!¥¸)€X¸$ºz,!iH³rúTH ä×êëùo"xì ÂÍÎÞüöK±oÄèá ïDå$‘.—lGah”½d#Ïà¨qƒƒrî;RR4ú¯%-°lè*AçµÖˆ@v@JŸ•ÅsëŽ!ÈîŽ}§4 ä2ç~h×ïà²5Ÿ^¡Fõ—LJâ£ã™™8sÚÅȾ#Ñ;1’ŽX¥d;KEwþÕ°›G+qÈ'Ô˜$‚'© í:{#rQ{!5¨æóH|eq|<–õ±„¤qX —-ТGh§=È Çi7{œiyåbIî#ÆßýØŒ{`6þàñ¹è>˜?d?hÓc¥\Uâö0¤¦)ƒÍF‹Û/iÂm¶HÃd Œ ¥67@’ο?[…¤ñžIz¦ísvÊtÓ!@ßšÏOÉV zâJâ£ãn#p9%[Óàé'ô¡ÄⶇÛ2ü©XÓŸkäоH„ÚuÜ=i ¦£dÍ(î†FNvi(² Iã‚@.ÃöÔµë÷#[óÊ Ë(/nŒ{cì¡ íxáe ±uÄ#÷5=$Õvo˜Óœ„O´|îƒÜ”af§X[š†z‘[!_1·!iœÈåA^e’~myd’nh9ÖQÁŽÒÇJo.Æßp¿.‡¾µ¡Ãhÿ§ ¸ÅÁ“tn͹Tó6Õh-·)¹H®ß /2U)@Ò8+ë« ÅIúåU–õ]¯1³8oÞáãÍÙHŒãï ×ì=ck™aÈзæ¾_[wÖ.Æà%HMÀÆÃß ƃÇmHçr}™ðÒ6oSÛõ:°ÍK% mª·]×tD£~! Ê‹J“E*Ï["Ì.¸ÕÊ¥šªæ»†UH/#·Aê§ eHïiÒ?nÝÝä©›‘Ûì†#Q^ˆróŸé*ù˜ÃrH„­¯âæJ ¦¾ÍûRHÃvp ’Æ1üKŸnq/‘ìÿá½D_ÜÕB¿µ¼…¯–Ï8ìÁxaŊч]÷ƒ|$ ȼdólúK\LEÎܾg† Cð!¯ànÜ1û™4T Ó‹’‚ŠÝ1:Žœ×ciä-¿¹@ï ÜŽ•{Ò’ DRnW˜3Ô“ñ£SÔ3?!Âë"€r#!¹TÇ]ùQH*Hõ¼Áö7ìᶇÿ{õG¨è{¨Õv&wµNòT *úWe'>~Ýñ3†8ÔÀ•¨y' ùH*ÈžaÞæ³öÀ©qù‡u[ýJ¾„¤¡Ù4þågÍgM5ßBÒ(Äÿ;§ÜŸJðcßIEND®B`‚nml-0.4.4/regression/lang/0000755000567200056720000000000012643457623016564 5ustar jenkinsjenkins00000000000000nml-0.4.4/regression/lang/us.lng0000644000567200056720000000005412643457545017717 0ustar jenkinsjenkins00000000000000##grflangid en_US STR_STATIONS :US Stations nml-0.4.4/regression/lang/dutch.lng0000644000567200056720000000043012643457545020375 0ustar jenkinsjenkins00000000000000##grflangid nl_NL STR_REGRESSION_ERROR :De wissels zijn bevroren, onze excuses voor het ongemak. STR_REGRESSION_CARE :zorg STR_ANSWER :42 STR_NAME_FOSTER_EXPRESS_TRAM :Foster Sneltram STR_STATIONS :Test stationnnetjes nml-0.4.4/regression/lang/english.lng0000644000567200056720000000365512643457545020733 0ustar jenkinsjenkins00000000000000##grflangid en_GB STR_NULL : STR_REGRESSION_NAME :NML regression test STR_REGRESSION_DESC :A test newgrf testing NML STR_REGRESSION_ERROR :Something bad (tm) has happened. STR_REGRESSION_CARE :care STR_ANSWER :42 STR_NAME_FOSTER_EXPRESS_TRAM :Foster Express Tram STR_NAME_FOSTER_TURBO_TRAM :Foster Turbo Tram STR_NAME_PLANE :Test plane 0x14 STR_NAME_BULK_WAGON :NML Test bulk wagon STR_PARAM_NAME :Name of the int setting STR_PARAM_DESC :Description of a setting STR_PARAM_NOGRID :Disable gridlines STR_PARAM_NOGRID_DESC :This setting allows to replace all ground sprites such that the landscape is painted (mostly) without grid lines. Note that roads and train tracks don't yet follow this rule STR_PARAM_TRANSMITTER :Replace the transmitter tower by a rock STR_PARAM_TRANSMITTER_DESC :Enable to replace the transmitter tower by a rock (useful for early scenarios without telecomunication towers) STR_PARAM_LANDSCAPE :Landscape type STR_PARAM_LANDSCAPE_DESC :Select the landscape (ground tile) type STR_PARAM_LANDSCAPE_NORMAL :normal (according to climate) STR_PARAM_LANDSCAPE_ALPINE :alpine (temperate grass in arctic) STR_PARAM_LANDSCAPE_TEMPERATE :temperate (not implemented) STR_PARAM_LANDSCAPE_ARCTIC :arctic (not implemented) STR_PARAM_LANDSCAPE_TROPICAL :tropical (not implemented) STR_PARAM_LANDSCAPE_TOYLAND :toyland (not implemented) STR_STATIONS :Teßt Stätiöµſ STR_OBJ_MISC_CLASS :Miscellaneous STR_OBJ_BASIC :Basic object STR_COALMINE_EXTRA_TEXT :{BLACK}Extra info for coal mine: {COMMA} STR_COALMINE_MONTH_0_10 :{BLACK}coal STR_COALMINE_MONTH_11 :{BLACK}diamonds STR_BREWERY_NAME :Brewery STR_032_HOUSE :Example house nml-0.4.4/regression/001_action8.nml0000644000567200056720000000023112643457545020277 0ustar jenkinsjenkins00000000000000grf { grfid: "NML\1"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } nml-0.4.4/regression/030_house.nml0000644000567200056720000001526412643457545020073 0ustar jenkinsjenkins00000000000000/* * This NewGRF adds the FIRS Brewery as a 2x2 town building * Original graphics by FooBar and andythenorth * * It accepts grain (as well as some/pax) mail. * Whenever grain is accepted, a smoke plume rises above the building. * The Brewery has a very high probability to appear, but special checks * make sure that only 1 appears per town. */ /* Make our NewGRF visible to OpenTTD */ grf { grfid: "NML\30"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } /* Add a cargo translation table */ cargotable { PASS, MAIL, GRAI, WHEA } /* Sprite templates for both ground and building sprites */ template tmpl_ground_tile(x, y, filename) { [x, y, 64, 31, -31, 0, filename] } template tmpl_building_sprite(x, y, h, dy, filename) { [x, y, 64, h, -31, dy, NOCROP, filename] } /* Spriteset containing all ground sprites */ spriteset(brewery_spriteset_ground) { tmpl_ground_tile( 10, 10, "groundtiles.png") //bare tmpl_ground_tile(150, 10, "groundtiles.png") //stones tmpl_ground_tile(220, 10, "groundtiles.png") //snowed [] /* pad with empty sprites, as all spritesets have to have the same no. of sprites (=6) */ [] [] } spriteset(brewery_spriteset_building) { tmpl_building_sprite( 10, 60, 91, -60, "brewery.png") // tile with chimney tmpl_building_sprite( 80, 60, 91, -60, "brewery.png") // left part of large building tmpl_building_sprite(150, 60, 91, -60, "brewery.png") // right part of large building tmpl_building_sprite( 10, 60, 91, -60, "brewery_snow.png") // idem, with snow tmpl_building_sprite( 80, 60, 91, -60, "brewery_snow.png") tmpl_building_sprite(150, 60, 91, -60, "brewery_snow.png") } /* Generic sprite layout that is used for all tiles of the building. Parmaeters: * - building_sprite: offset in the brewery_spriteset_building spriteset to use. -1 to skip building sprite. * - with_smoke: Show smoke above the tile (also depends on animation state) */ spritelayout brewery_sprite_layout(building_sprite, with_smoke) { /* First: Draw the normal ground sprite as a base layer */ ground { sprite: GROUNDSPRITE_NORMAL; } /* Now draw our own ground sprite * Below the snowline, it is either a bare land sprite (construction state 0) or stones (construction state 1..3) * Above the snowline, it is always a snowed sprite */ childsprite { sprite: brewery_spriteset_ground((terrain_type == TILETYPE_SNOW) ? 2 : min(construction_state, 1)); always_draw: 1; // Draw this sprite even in transparent mode } /* Now draw the building sprite, assuming the building is fully constructed */ building { sprite: brewery_spriteset_building((terrain_type == TILETYPE_SNOW) * 3 + building_sprite); /* Enable recolouring for the flag on top, it will get a random colour */ recolour_mode: RECOLOUR_REMAP; palette: PALETTE_USE_DEFAULT; zextent: 48; hide_sprite: building_sprite == -1 || construction_state != 3; } building { /* 3079 .. 3083 are steam smoke sprites * Draw one of them, assuming the animation frame != 0 */ sprite: 3079 + (animation_frame - 1) / 4; xoffset: 8; yoffset: 0; zoffset: 55 + (animation_frame - 1); xextent: 11; zextent: 7; hide_sprite: !with_smoke || animation_frame == 0; } } switch(FEAT_HOUSES, SELF, brewery_layout_1, house_tile) { HOUSE_TILE_NORTH: brewery_sprite_layout(2, 0); HOUSE_TILE_WEST: brewery_sprite_layout(1, 0); HOUSE_TILE_EAST: brewery_sprite_layout(-1, 0); // empty tile brewery_sprite_layout(0, 1); //south, building with chimney } switch(FEAT_HOUSES, SELF, brewery_layout_2, house_tile) { HOUSE_TILE_NORTH: brewery_sprite_layout(-1, 0); // empty tile HOUSE_TILE_WEST: brewery_sprite_layout(0, 1); // building with chimney HOUSE_TILE_EAST: brewery_sprite_layout(2, 0); brewery_sprite_layout(1, 0); //south } /* Randomly choose a graphics layout */ switch(FEAT_HOUSES, SELF, brewery_choose_layout, random_bits & 1) { 0 : brewery_layout_1; brewery_layout_2; } /* Stop the animation in frame 0, else continue normally * We choose frame 0 as 'stop' frame (no smoke visible) so there is no smoke * during / after construction */ switch(FEAT_HOUSES, SELF, brewery_next_frame, animation_frame) { 0 : return CB_RESULT_STOP_ANIMATION; return CB_RESULT_NEXT_FRAME; } /* When grain / wheat is accepted, start the animation if: * - The animation is currently stopped (frame 0) * - The house tile actually contains smoke. This saves CPU time * Else, do nothing */ switch(FEAT_HOUSES, SELF, brewery_cargo_accepted, house_tile == ((random_bits & 1) ? HOUSE_TILE_WEST : HOUSE_TILE_SOUTH)) { // only animate the tile that has the chimney with smoke 1 : return (animation_frame == 0) ? 1 : CB_RESULT_DO_NOTHING; return CB_RESULT_DO_NOTHING; } /* Only allow construction of a brewery if there isn't already one in the same town */ switch(FEAT_HOUSES, SELF, brewery_check_location, same_house_count_town) { 0 : 1; 0; } /* Only enable the brewery if there is a grain or wheat cargo type */ if (cargotype_available("GRAI") || cargotype_available("WHEA")) { item(FEAT_HOUSES, item_brewery, -1, HOUSE_SIZE_2X2) { property { substitute: 40; // use 2x2 shopping mall as fallback name: string(STR_BREWERY_NAME); /* do not replace (override) any original house type */ building_flags: bitmask(HOUSE_FLAG_ANIMATE); population: 100; mail_multiplier: 25; accepted_cargos: [[cargotype_available("GRAI") ? GRAI : WHEA, 8], [PASS, 2], [MAIL, 1]]; local_authority_impact: 200; removal_cost_multiplier: 250; probability: 15; // high probability for testing purposes years_available: [1940, 2050]; minimum_lifetime: 20; availability_mask: [bitmask(TOWNZONE_EDGE, TOWNZONE_OUTSKIRT, TOWNZONE_OUTER_SUBURB), bitmask(CLIMATE_TEMPERATE, CLIMATE_ARCTIC, ABOVE_SNOWLINE)]; random_colours: [COLOUR_RED, COLOUR_BLUE, COLOUR_GREEN, COLOUR_YELLOW]; refresh_multiplier: 0; animation_info: [ANIMATION_LOOPING, 21]; // 'stop' frame + 20 normal frames. Auto-loop back to frame 0 animation_speed: 2; /* no building class set, it's pointless */ watched_cargo_types: [GRAI, WHEA]; } graphics { default: brewery_choose_layout; anim_next_frame: brewery_next_frame; watched_cargo_accepted: brewery_cargo_accepted; construction_check: brewery_check_location; } } } nml-0.4.4/regression/opengfx_generic_trams1.png0000644000567200056720000002342712643457545023015 0ustar jenkinsjenkins00000000000000‰PNG  IHDRs–,DhsRGB®ÎébKGDÿÿÿ ½§“ pHYs  šœtIMEÜ1(½õ;Ù IDATxÚí}l×uî7™ÝòQɰX ¤MkS4è'®L: -Áz›ØJ¹±ß‹ö²°¬ÄPЊbeÔ-S5ý 1`Y¦bÔH“*@‹BBåUJ,ÀUä0z E³&“¥ Â$¥m—‚rnA–]‚ ¦ÌÜÙ™Ùùýc¹Ë=@hwvçÎ{Îùîwν³âA@ Ê÷Ñ‘9@ ˆÌ @dN "s@ 2'‘9@ ˆÌ @dN Dæ%Ž3ÿóz¾Ó?ïýç.rwÇï~¹íŸ³þs7wü ¯¼ýÕ×ôÛ=öíŽ×þ•zü¶(÷ÇùóÀ]ÿ„'Àêöüv §Ãi|}îŽÔ⃛Ù?çýçîxé³ûñÛ ûÞ«ö÷Ú/}ß Öþܸô"+ÙN}˜~]¤âË,*'bÇ.–÷= ŠÜ|p–K¿‹Ý_Ùþ(žý½MZÁõ‰õ«˜}ã.Š“iW /ý±XL•Ÿ“2ß$e®ÈLQ¡ù£ŒTÉ€ÐéŸ2 Âéó]de6!]õ@pÊ\e^aû2´°¶ÆþzBª+ Œu(ޱ£VJ”ye+s¾Ð™äc>+¤×’HWJT%Ü5Ÿ®‘Õ¹Ÿ¬Îd¥œ´ !±¬Ô׬¦í ¡ž2ë_sTÊ[Àþålû`Œû òé˜R©ü]¡¢•ù…#EÕéWe&AJC<Ð5!9­*ÈÁ¾ Í_¿ زšT•×/4ŸkIU¼–”¿é·Ce–µRê.”y™Û¿x¶„ýWæ_¹ˆšÿ¸ŒÕ[Ç ÆÊ‘R'e^ÙÊšÔש;^4V LI(ÕE—tlL@Q¨}\) ÖW<1tM ÖѼžº²ˆõ펳×3@W ‰ÁœôH †'þY:>¯£ðüFÔ†2ãVê)Ÿ(ªØö/cÛ‡2X½5 t¥PóÈYÅ88TêRæjeÆkÛ¡2s®Ä|xêÛ‚põo<(ó¬ÀÓÞ§RÉéý Eð Ì ”±VnG™(uʼ í_<Û¢Ìw¼¼˜ýðÿÿèúK‡J]‘¤ÌI™9»C5æJ‰žú5÷æ]]ºº][ë4ûƒÁ{å¿32ÐÖPÅë­Ì²ufS©ûf”•ýËÚöfOW!¶o?b}?s¦Ôµ“îfB…’¹["/t.‡Aœ|ûÿú»î®¿V+¥Îð—ÀScÀïSÀÈ¿"t¥°Nù>â-ÝßBËÉËH]s_¹ í_ƶgDL Ù_+ûƒ9©çíˉÁªÛ‰‘= Š”¹L"š}Œ7K§_‹A¼ðm@fGd»;õ³çĆ/æÉ¤æÞé­©÷s¨n­EÛá’ý3¦J,³À£9šÂ™ca`b^ÜN,’ÎÙ¦o6¡®~Åö÷•ê0³À«Ž¥S<нÙÊìû2Q&øôýZ ½™Bõ¿·cèÍIà Ù¿ll¯Å¸8qÞ€£Ë¸ËGðèî =×V˜¹=±ë‰ÇíÕL „ /³ðïm ´‚©¡ôÞÈáÌãúétsTt¸Ëç»LŠ8~ÀSÏ“¯5£õÂ`èœüईH N™ª.¹(?™ÐÞ)„>7‡|ù¥@ƒ˜‘úô§yehI}5ÿq/û6Þý„òd•x¥¡âì_ö¶7À½ŸlKpRæ–ìg÷!úƒb’:7ÜÓv$>0…ß}¼ÃÕdNØ"dë«ÂÔäÅ“õÉ T·Gñì«ö pñ–k15”‘S¾tªA®?2%ÆTïÙŽÕå0fߪ÷õ>†XzùÆ5¨~¨¨# L|„¡ƒvCÿÜ3ÝŠc Ê,ú®<ÞÕ¹(Ú‡e5éÅþX«B+eeÿ-a{™ÛÐs<€Z$§ä5†Ì¤ådÃÇ!:öXò*ûšù®SiL eä@^Ÿ®÷Û«Áqw -'ÛÏÍžŽ‰õÍ!yõ b åÙž•SËÉv« ®´}²Ö¿m¢¨©'§ä*6Ökcýúï'cöô¤UÛÒþ­ˆ¤ìÌþÿït¤ê0Ùßû+!ÕÔc}]¨«_ÁÝÛíxÒ>óJ&󯞽…‘wªä4™2{Á,¿•M|  K‹µhÝ9—O{;Ldíü`³I‹¥<©×Õ¯`¸gJŒ¥EqÏkfaʼnŠpX jôuA¬Yz¯‰W<´5õ›]HöI$~À/û¯õÿ#„ºú¾Kö'2/Ç2‹„sÉþ&8Œ„ßP-nK;°Âþ ÝýAö'™—Sš?{K÷ÃáãPÚ¶ÕÓ|ù§g³c?U+Âi”DæåBæfÉ3‘ù'sÀø·Ä…'Éþ"s@ ” î£! "s@ ™Èœ@ Dæ°UàË ÒoZË¡NW¶ï¦m«óÙçÊ{)Æ}m昘µ©3ûkQ cd66vmÊqÜ’ò3?}A¯Fí»µsPþÔ˜6A™+ Æ^ëó;0œÞiŸ½Þ«ÓÉ.(lfà‚P§›R d3‚ÖöWÏ^ÚcvÏsêÏÚ¶ŒŽ•‚9&„MR楤„Ò°ÁV°O„ä• ·â˜ʀ̭RI³’†Þ÷YŠç¥]¯ ÜîuÜôUÛ³±1SInÇ߯ Ë‹¾¿Õ& /÷å¶ÌµciäÛV¥ ;%'«R”•¯›õÍ+ÇcŒï ÂXìÏNiDk0£Ïõ¾of»íú©JÍ è¦¯v¯¶n©w »×Z¹;µ±á›pƒD?ʬ ½É×NÙ¨XcbÖ?#Ð+ߨ=n××øÅ+ws¢¼/(Ç´Rf†ö›`ìc1K>NÇÀû³ãXÅp–ýO\Af—ãPÅöÏJBÙï3çî?ºŒHsuõ+XZ¬E]ý "Û³hŽf‘NñÂÏÍÉv05]Fëcsª ƒM RwÌÜÉ%õ`ɜ݇û{p5~’$û7ë«’}!ùZs°d^x¯¥Gæj¿ Ìþñ³·TqÁb€žè!2§2‹1‘+È4]FìT‘æl‘+¶wô4û øÇÝÉ`9‚ÝC‘ÐxtY÷xëÎ9ÄN¥ƒ%ÉâdSîûÇúp?ãgo~–Nñr|$.ŒåR™ÅJe>>Egw ™ÞID𳀤úÈ¥åd‘N寑HÎÒÊÂÀȉªR"ÄXŸ?6J¾Ö\4[ôÞ˜ÂîƒóNÓlWˆÈ6 «23/ö·"ÊX_Z/Œøv~M<Éþ ÙoêêW0ðØÇÏÞ’ãŽwd{Ó7›TãßÍʤî@©*‘̕ʜ©g=]˜RÍZƒ{È u gš:²Ú`fÇðùëÀZ8è-˜#;çÄkö5§vÒÒÞ·LnÛ³ªÀÊ,ðˆŸ½…áã%¨ÓÛ³"‘?×€Þhñƒ7³ÀûšASʉäò¸¤6½Ü_üì-@š„´v3òqözi±V×OZwÎùÖ?3 ÿt Õ¹(ÚU?õ^+I]?{›ðºæBd¾Õ”¹³²ÀµW´¥%p%‰³k ½3)’øR¾3» äXŸxážy18Ö’X _Ùû·sô ûißÇ>v*%Ÿ±±I ÎãÌãmè½1¸"[Z¬E|ÀÅdî CKöo 18/±Ò/.Uzïè€NƧÌ™}A+'mæñ†‚R£2k l2e° ù«”Lê™4Hs¶€ÈÙ$›Nñè½1¥Š)RêÞóÅ-µ Ã=aÙQì.€2U¬:»S†$.+ñ/ŒkQ`-‚ø mÂoœ ÷çR­³ò_-´Çµß×#™ñѨjêú¿ÍíÒ)hÇ‘M26‚×ñbP àÚÉݪÌfóz×ÿª¦Þ¬õ9#ù„ÙñæhVxÛßÉœÛñò"f{eÄx H]éJ‘¡K»Ï• ÌóJ¬A®™ÛU^«|<”‘ÉÀŒÄ“W¯¨”xüÀóžú¬$ÛÌ/_Û¨L =®TÞzŸÛ·HþjÌUš„RoÝ9‡æhã£Q×eíZ‰U™AÌÐrŽËlfö1²­‘˜ùŠáû–™ýIWÞ®^Q)õH³Z©mTH§xô"˜Ì‘ȼŒá„À"ÍY¬.‡q÷v«Ù0žúæ¤+¿{;‚Êxîk:Åcúf“n¹H[RqŠKßàíÏJ#ÍfÜ;úÆG£¾”2 ¼j³ÓRQ]ý :¿wã£QÛe¶|†ænÒ|÷ùxöÿf\Ý«Ö'´e'mY&° ½¿±>Ñ_cûöë’z ß„ÕlXEêì>˜½Ò)è&V®x2ϧò ž”å…¿Û‹=û'å±"ñÈö,†ÞÈóÀÉv×Á|éõ0ÖÃ) 4‰Ä (Þg8Ñ.½Ÿ—>o7ø\ÿ}õÂ׀Њøç3>$. ÚTdL!*•bgw pYÓ4g‘Ióª4Þ©*W®Y°õ½IÖlrO{Æåd¸‚K¯‡µZ¬oÿX‹ :ų¯æäõý÷YÅû°éç‰W0ô¦X[ûí?{º À’ýˆõ­`u9Œšm9ÄöíÇêr³£¿À:€Ùßæý‚ÙŠÅXf—RÝn l1eî*•””%AͶj¶å0r¥÷>;oJâÝ)œ9& m†Ébö­zÇ—_o>à™xJh÷÷}hX«Åž¾y ŸÃêrØP‘)I]«(™Íš£"yF.xßÚÉÔ¹ÝìŒ-Þš©X¦ÔÍHÜmF°çX#ïTI“­Ø¦è ¾½z"¡¿wÙÿ ê€ÎkÀÄGHöó:×/©ëùÅžca™À÷ +âà[c”‘JͶœ¼ã@J­|¸û.çÏ•Tzä+;pï_ÿX‹"~ho‰³RHòŸf€‰¬§Ç¹wJ«ˆ$³À–lÜ–YØ9K‹µÂïýÝoÎ}õì- ÷4 åäj¶åTµSí‚Sd…Ùðîwðì«9Ç ˆ»?å mÓÎè®SiL½ŸCÛaq—²Ì¦D~×’¸à8Ñ®[iòpºÀ¨ê¿›•Y˜)Ûœ¾Ùä»ýU È×€‰,_ü:Z›“÷›«2aÉ/â/´É%ªø mì^œÚŸ°Õ”9#c1(¦Yh“Sf‹ܹ{ÿP{¤å¤¨.™:¨á›ìyUýqu9ŒGwÏà׿lÓÇ·ê×0…»·#Å)ó2ºíCCš²ƒã‡F•5ôš¸¥ëVØC†Ê¬îÛÿ‰?þÒŠœf³ò‹R™¼S%/ÊJeÇÊ\[3×Úâ=nש´Ü'–AÄöí·þtJYÏ•K_r™ìïìW¥ .Ö×…ºú=§‡†*\™ŸKö7‰õÍ)ƒ:5ÜÓM ~ì¸1¦ÔøQ¯X,ýì°//˜kê¬v €ùÚ‡=çCß8äõ’köœÇmÕlËm¼ßßTu¸oNQÓâ/´9-³ldpI RæFÊ@©¦•*møø#Áÿç¤Ìl+2VSo9¹!ï´P.ø¹¨éüœ€F¥[*sÖÿ˜N;Ã?ž×ÖÄÉþ?DæäŒ4~ŒÔ[Nî•·^J$îvü8Ž»&î"Ñ@Ú%â¸Ì¦$u*³Qü™¡˜ ÿЙ@ ˆÌ P ð¼›…ã¸%£ÏA¨ÛÌ›c}¡Nïu)öÙÉ}صC©Ü—²Vý×ë¯ÑùvÎuêÃVí›Ý‹Û1ð2®nÆ“@ÊÜÐéA¨cAM~ ˆ>—´÷Qj÷eÔ垣õ½ïEävHÑí¸úm½þ‘“2/Jà–:–3‘oÅûÒy÷§´í•êø•[†I(32׃“ÔXyŒµ£w¾ö˜Q°kUª>µm,f}qZ°£Ý©Ó~Úù~1CëV%§}²ó};í»-s5–NJZfþéÅoÝÄ®YßÜúi¥Lp÷å@zŽÊÞ¥ÌÚ×Fç[))/}Ök[¯V}qzïFǃPîvúiÄA—Àì”´céW‰ÎLˆØUÃVþT_Ý–]ÌüÓ‹ß:õ+£ï8õS½Ï+%K¹/(Âð#}v’NúYÃôÚ–!ØQ™A: ²ŸvœÝÌÀNûÇ-9!8eÿƒê—öÝ’²—,ˬääeLìÞ«]¿õ»ä” Š™En¹2‹AÓ ÞͬùÚ-ÁøÑ¶Ñ½;“ 3ª R{'>áöÞÊb~í 1jŸ÷ê+Nú¬§j„‰SûZ•:œø­“ñPŽ£W¡µ™ñ´%”¹ßŠ<(Ò±jßÏ Ôk{³¹Ÿ¶°[f²/Z%oFhv§žMì´oUNñ{,­Ê9vûì§òö’]ë•5½¶WIŠ\¾g¯ó[-2X-Z˜•%ì,Î5)ØÝîä^̾ï×b›öí,ÞÙYØõ£ovT¨ßcfFævÇÌ¿xK#Ò²;&^üÁ/ÿ·“5lvâgo!qad“:¸ñ2ÿI¯Øû#‰\¿.þ]IšL‚Àâ=v 'ã=.úF ¢wôôŽ~`>1–Ânö*÷‡†8N"ÙÈö¬êƒæhgºŸv®Ì¸;~&*<á‡h<ºŒH³Øv]ý ²=ËÚ/¢2¿(’²€ÐYÚÊœ»`ÂH½ú®Ì™’ý&hÃÒb-’¯5“™1[n®2çÆðNƺ(ög‚§9šE:Å£9*ÆO:Åcèàž|vS8†¤Ì+™Ìï—¹®~K‹µ2ávv§0>~süd>Æ¿^@âÝ)Ù9•Nª u×Á;•¶uB²? …X_—í‹$_k<˜ãgo‰*jb°¡0ˆ óû.‹lsVñ"Û³˜¾Ù$ü>ïžÈT;©÷Lè@bpØýy"s»> úÁ€(b}'>(™ÇÏÞ’ÅÎôͦñÃNñznžÈœÈ\ŸÌõ”s:Å ?7'u0KDΈ²®~E¥øµ™Yàå÷Ãâ¤á*˜•AÀÚÔ¾_ Iy² ŒÁè}&ÍãÞO¶ÌŒ„Øõ”„®7¤îŠÌÙd>ÜVM$®&s ÏÛ¢½7¦Ôupk2oÔˆ+›æ'I 6ú{=|ü‘ÀÈ\o"Rö•Å‘2†Î<žÓ:‘¹{l_M•J~¬÷­&Gç7]F¤/‹d’ýc*å«GâÊ,`ä*T¿7Žõ¤óòGìTßœT)}í5Eˆ%–ø@XE,,HXŸZwΩÞ×Õ¯ yõ .3B÷Ê f¤Èa¸§™…œê~š£YôŽ~àD©;ëK_êêÛYÐ?8÷‡æ, ¬23S¹ø^¼^âÂàâ¾¥ìBü˜ÂÕ–Ù¤>Ü““J-óº„É^7G³èý(, zFõÃÓ˜Bm‡Eß̤yDš³ªIˆù$Ëj•búÂH þ@d^ŽD~õ â{1ÜBï[aœyü2ƒÏÛ'$)hc}µHöw ®~Ý)CO^½¬E¡ ­`=œrÝÿ_ÿ²Oi]ûº÷Æ”tl^&e]’»ö=`-‚{ŸGõÃÓÀg­þŽý¯ÆP‹¢-Q‘z¬OMðA“ºrre÷­G€NI\o-¦÷F¶ 3zgXËabÖµgÖ0ÔÚš½ òhŽN™úŽ×‰ÍB+²?Dš#*RgöÉûn¾DÅüáL ‘y™!¶o?€I¡ã£QôÞx锽à]åsxࡌìp½7¬I_Èg=œBqW…¸%ÎŒ”¹R]¹Ù{„2ÀZëÛ? ˆ3XüUJ&u;ÊLIê~±vÍÄ âgo¡ó{×1>UõÝ,3>½Àœ“ Q6fßÖV¾ªcã 1)ð#RO ÎŒk/‚ɈÌËYèìž²}F¤9‹Õå0îÞŽ`5ÆSßœ´EâñyÕ¡MU›tªlÊ ug»£óôj¢zïEÌÉ Ýo4>|H\|¼zÅV3e¦,Oø¦¤Ý´ÉvD1å«ÌÊìøCâØ3®úÌ2ºê\ã£QµošØÖiÖ!f¼ý‰¿öÿÒ7ÄrfͶˆBX¡Àjø&¬fÃ*`~Ìî%ânbåŠ'sÑ!L».\ø»½Ø³R3gé}ëÎ9 ½‘&€ãΕ-B)\z½+¯ðC)$Ž=ƒK¯‡UÇzߨ‹w¿Ó ›”¿7ò^Nþ^âD»ê=B)¹¹†Ïau9lÄJRWNBÝ)¼ûü ÂOøâÒÂg[aúÓ&L£I*wLª>cJ݌ĕ¤äÃÿX‹kTç¢xöUÈÊ_~¯ùΟþxgþêºæ¼†Þ»¬:†í©@ì?{º è˜@Ë“í¨Ù&úBͶbûöcu9ŒÙÑ_`ÀìoóþÀ&^æ ™Ã?ûyX™”9 ¥naÇdº.Rj¶åP³-‡‘+í¸÷ÙySgd1>•ˆ< ¸ø_Qd”:B)ÔÜSÐõpNuLUÒ1ø^s´©à¼õ#Xÿâ¤xÌg<ðØæÝÛ9ˆHâô§M˜þ´ ÍäïÇEÎÔSRe ™ëâ_ú$  Ͼš“IZ[NQ’¸ÒÜ”©°ÖjQso«’bD_¥Tïµßºçiõ¾±7˜ ê€Îk˜=]…Æy´>6'‹3Øs,,øžcaE\bklMd ±f[N» JxÛ¼ÇÝtY>W^¼ùÊÜû׿Ö¢ˆÚ[@âéé›MHþÓŒDⲪt¼5mשtA×Ïô.}_TCëÛ?5}³ñgAÌH€¬Ì°$.¤h‹IDATAìOº0õ~‘½ 2qL0×_ò<~šñ±µ5µúái‰XWT¥„2¨^øöœØqlßþ‚ë±l`i±Öj_{áõ÷þÕ _3‚úxplÕÖÎÎkÀD_¬Bëcsò~sU&¬Ø8À&Èø m재ìEØêd®$rMP[9ÇO9N8ÒrR$F<-ÿc¯¬8•¥@Ü*¶ºÆì[õº:Å)ýï #ª,ÜB™)h‰M"€µ{ÎþóÂkþ³Äè˜@ü; ò˜)±ºÆ¿}8¬Õ"²·™Of°þÅ1uk.É<“æóÊš-Ɖ%'K2ÿüÃÓº$@.Qì9ÖŽ¥<©‹YY€á€Óþÿá&'TýéÀ”³‹ ±Rf«ËaÙNX«5*û8ÍäÌ@Kä6Ûã>/$q Õÿ.®c0å¨$ñß}¼C¬‹$îÙþJá”üàË/ÿ‡¶¸%¹¦þèîÙô&­»·#ÂŒ¾H"T ™KŠšK߯4Ø~TRçfOGŽ´œÌàÑÝ3²×)§øJæy"ÌB^¬ìÈYÛíË}48¿s«”×·ÿü¥y÷ñy²œIS©Ìã‡öºÉ¸û.¹2ÿÃMN¨ú_'3¢)¡ð'¥ºõ;3Ó>A™ì:x`‚•WR@Øÿ´÷£eFçcüÉ¢Ú:“|ËÉ Ù2 ü¹áž¶#ñ)ܽaãIO€Vz™%¢xJOSstö8¿"=¬Ù– R »¨4g´ æ–—ål‡íåò ¾lMb¸ç:€ˆ 8ûq.¹Lb°j©Ì9îŤwõ ›\dg[MHœìï€Ôc}+¨«_Q’x¥Ž‘¹†ÌÏÝû‡Ú#±¾9%¡§†{Ú¢‰Á“¹GP0›q|`ž¥Óxà¡ÌÚ‡=çC!4ˆ°¬Ç±²•Ë$ú;b¬É\~u @vcߪ:,ùRd{V['ûSü™éL‹ 6jÄ䌛 ÌZNn`öô/”$îºL¡·HÙžó_ÍÌ+õd­]'ûSü™^xשôÆûýMòŽ köœ}cà™DæBÉ(CIM¬Ô’’tÞß{ˆÌ P6 ÿ”@ ˆÌ @dN "s@ ˆðå'p9Ž[2úL\ü,¬`}¡NùZ¯ïì;›ÝçͧvÖž«õ£16º®YÛNýÏεÝ^ÏK?íöÛÎx(s¥³ ‚PÇþ‚š ‚ 8%ÉmÑ–:ŒlªGå1³ÏÌÎwkC»×vëŸ~«‘›˜2/UU8›—1TÚ½“â&”<™kKF©©ž:Qcíè¯WÑ mÖ`Õ_«ö­‚ÒI_­”š^iÄîhÇÃÎ8MÚfcì•ÔõÚ÷3;²“•x¹¦ÛR]tSÖqwf}sëÃ4ù±ÌbæhFÆ6J­µ¯Î×:©CkSY;Á¥×W½{²ÓW½ò”Þ÷ì¶[Še321²ƒßí;õ3ûQóÚ–4Š7;ÇíøœÑwœú°Y¹‰Pd2÷Rót«œ¼(2¿ Á¯¾*¿gÇ¡7SyMˆvIÀï2ÓöÝöÇ‹¿ùµ¶d61:‰7«ã~Ý¿ÓØ¨ä2\I”Yì ¼˜„1µ*Ù/’ ª¯Å(…¸½; ŒnÇØn¿Ü¶ÏÎós'““±ô{­ÆªÔa´Èj¦¬Ž£™¸DèEVæ~+ò I-H’ô«íb9³WµT?ƒR±f“Q}Öf0AL"N•·—ÌX{vleÕ¸ ÛûñC[V V fi²Å?/)ŸQûNœØÉ¨“’€“ÒÓ ìlu-¯{нøŸ“E?;¶°ã›~Æ‹cêdòòc1ߨ ÅÏM"óŠ4R dcêŸmµMqRTÑ¿à&³%2/ õAªƒlLý£ø 2'‚/ _M$"s@ ™Èœ@ Dæ@dN "s@ ™Èœ@ ˆÌ @dN "s@ ™‘9@ ˆÌ þ Ïcnü<]öIEND®B`‚nml-0.4.4/regression/Makefile0000644000567200056720000000141312643457545017305 0ustar jenkinsjenkins00000000000000.PHONY: clean all .DEFAULT: _V ?= @ _E ?= @echo _SE ?= echo TEST_FILES = $(basename $(shell ls *.nml)) NMLC ?= ../nmlc # Note: Manually overriding NML_FLAGS may break the regression test NML_FLAGS ?= -s -c --verbosity=1 .PHONY: $(TEST_FILES) clean all: $(TEST_FILES) $(TEST_FILES): $(_V) echo "Running test $@" $(_V) mkdir -p output nml_output output2 $(_V) $(NMLC) $(NML_FLAGS) --nfo output/$@.nfo --grf output/$@.grf $@.nml --nml nml_output/$@.nml && \ $(NMLC) $(NML_FLAGS) -n --nfo output2/$@.nfo --grf output2/$@.grf nml_output/$@.nml $(_V) diff -u expected/$@.nfo output/$@.nfo && diff -u expected/$@.grf output/$@.grf && \ diff -u expected/$@.nfo output2/$@.nfo && diff -u expected/$@.grf output2/$@.grf clean: $(_V) rm -rf output nml_output output2 parsetab.py nml-0.4.4/regression/020_recolour.nml0000644000567200056720000000232412643457545020572 0ustar jenkinsjenkins00000000000000grf { grfid: "NML\20"; name: string(STR_REGRESSION_NAME); desc: string(STR_REGRESSION_DESC); version: 0; min_compatible_version: 0; } template company_recolour(offset) { recolour_sprite { 0xC6..0xCD: offset..offset+7; } } param[3] = reserve_sprites(3); replace(param[3]) { recolour_sprite { 0xC6: 0x28; 0xC7: 0xF5; 0xC8..0xCD: 0x0A..0x0F; } company_recolour(0x3E) company_recolour(0x9A) } // To write any recolour sprite to the output nmlc needs to know which palette // to use. One option would be to specify the palette via the commandline, but // for now just include some graphics so nmlc can determine the palette from that. // Foster Express tram spriteset(foster_express_set, "opengfx_generic_trams1.pcx") { [ 48,56, 8,18, -3,-10] [ 64,56, 20,19, -14, -5] [ 96,56, 28,15, -14, -8] [144,56, 20,19, -6, -7] [176,56, 8,18, -3,-10] [192,56, 20,19, -14, -9] [224,56, 28,15, -14, -8] [272,56, 20,19, -6, -7] } spritegroup foster_express_group { loading: foster_express_set; loaded: foster_express_set; } item(FEAT_ROADVEHS, foster_express_tram) { graphics { foster_express_group; } }nml-0.4.4/setup.py0000755000567200056720000000336612643457545015213 0ustar jenkinsjenkins00000000000000#! /usr/bin/env python3 try: from cx_Freeze import setup, Executable except ImportError: pass import sys, os, string, subprocess from setuptools import setup, Extension, find_packages version = sys.version_info if version[0] < 3 or (version[0] == 3 and version[1] < 2): sys.exit('ERROR: Sorry, Python 3.2 or later is required for this application.') # Import our version information from nml import version_info version = version_info.get_and_write_version() setup(name='nml', version=version, packages=find_packages(), description='A tool to compile nml files to grf or nfo files', long_description = 'A tool to compile nml files to grf and / or nfo files.' \ 'NML is a meta-language that aims to be a lot simpler to learn and use than nfo.', license='GPL-2.0+', classifiers = ['Development Status :: 2 - Pre-Alpha', 'Environment :: Console', 'Intended Audience :: Developers', 'License :: OSI Approved :: GNU General Public License (GPL)', 'Operating System :: OS Independent', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Topic :: Software Development :: Compilers', ], url='http://dev.openttdcoop.org/projects/nml', author='NML Development Team', author_email='nml-team@openttdcoop.org', entry_points={ 'console_scripts': ['nmlc = nml.main:run'] }, ext_modules = [Extension("nml_lz77", ["nml/_lz77.c"], optional=True)], ) nml-0.4.4/nml.egg-info/0000755000567200056720000000000012643457623015743 5ustar jenkinsjenkins00000000000000nml-0.4.4/nml.egg-info/entry_points.txt0000644000567200056720000000004712643457623021242 0ustar jenkinsjenkins00000000000000[console_scripts] nmlc = nml.main:run nml-0.4.4/nml.egg-info/top_level.txt0000644000567200056720000000001512643457623020471 0ustar jenkinsjenkins00000000000000nml nml_lz77 nml-0.4.4/nml.egg-info/PKG-INFO0000644000567200056720000000161112643457623017037 0ustar jenkinsjenkins00000000000000Metadata-Version: 1.1 Name: nml Version: 0.4.4 Summary: A tool to compile nml files to grf or nfo files Home-page: http://dev.openttdcoop.org/projects/nml Author: NML Development Team Author-email: nml-team@openttdcoop.org License: GPL-2.0+ Description: A tool to compile nml files to grf and / or nfo files.NML is a meta-language that aims to be a lot simpler to learn and use than nfo. Platform: UNKNOWN Classifier: Development Status :: 2 - Pre-Alpha Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: GNU General Public License (GPL) Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Topic :: Software Development :: Compilers nml-0.4.4/nml.egg-info/SOURCES.txt0000644000567200056720000001660512643457623017637 0ustar jenkinsjenkins00000000000000MANIFEST.in Makefile nmlc setup.py docs/changelog.txt docs/index.html docs/license.txt docs/nml.spec docs/nmlc.1 docs/readme.txt examples/object/cc_grid.png examples/object/example_object.nml examples/object/lang/english.lng examples/railtype/example_railtype.nml examples/railtype/gfx/depot_electric.png examples/railtype/gfx/depot_normal.png examples/railtype/gfx/fences.png examples/railtype/gfx/gui_erail.png examples/railtype/gfx/gui_rail.png examples/railtype/gfx/lc_left.png examples/railtype/gfx/lc_right.png examples/railtype/gfx/rails_overlays.png examples/railtype/gfx/tunnel_track.png examples/railtype/lang/english.lng examples/road_vehicle/example_road_vehicle.nml examples/road_vehicle/gfx/flatbed_truck_1_copper.png examples/road_vehicle/gfx/flatbed_truck_1_goods.png examples/road_vehicle/gfx/flatbed_truck_1_paper.png examples/road_vehicle/gfx/flatbed_truck_1_steel.png examples/road_vehicle/gfx/flatbed_truck_1_wood.png examples/road_vehicle/lang/english.lng examples/train/example_train.nml examples/train/icm.png examples/train/lang/dutch.lng examples/train/lang/english.lng nml/__init__.py nml/__version__.py nml/_lz77.c nml/free_number_list.py nml/generic.py nml/global_constants.py nml/grfstrings.py nml/lz77.py nml/main.py nml/nmlop.py nml/output_base.py nml/output_dep.py nml/output_grf.py nml/output_nfo.py nml/output_nml.py nml/palette.py nml/parser.py nml/spritecache.py nml/spriteencoder.py nml/tokens.py nml/unit.py nml/version_info.py nml.egg-info/PKG-INFO nml.egg-info/SOURCES.txt nml.egg-info/dependency_links.txt nml.egg-info/entry_points.txt nml.egg-info/top_level.txt nml/actions/__init__.py nml/actions/action0.py nml/actions/action0properties.py nml/actions/action1.py nml/actions/action10.py nml/actions/action11.py nml/actions/action12.py nml/actions/action14.py nml/actions/action2.py nml/actions/action2layout.py nml/actions/action2production.py nml/actions/action2random.py nml/actions/action2real.py nml/actions/action2var.py nml/actions/action2var_variables.py nml/actions/action3.py nml/actions/action3_callbacks.py nml/actions/action4.py nml/actions/action5.py nml/actions/action6.py nml/actions/action7.py nml/actions/action8.py nml/actions/actionA.py nml/actions/actionB.py nml/actions/actionD.py nml/actions/actionE.py nml/actions/actionF.py nml/actions/base_action.py nml/actions/real_sprite.py nml/actions/sprite_count.py nml/ast/__init__.py nml/ast/alt_sprites.py nml/ast/assignment.py nml/ast/base_graphics.py nml/ast/base_statement.py nml/ast/basecost.py nml/ast/cargotable.py nml/ast/conditional.py nml/ast/deactivate.py nml/ast/disable_item.py nml/ast/error.py nml/ast/font.py nml/ast/general.py nml/ast/grf.py nml/ast/item.py nml/ast/loop.py nml/ast/override.py nml/ast/produce.py nml/ast/railtypetable.py nml/ast/replace.py nml/ast/skipall.py nml/ast/snowline.py nml/ast/sort_vehicles.py nml/ast/sprite_container.py nml/ast/spriteblock.py nml/ast/switch.py nml/ast/tilelayout.py nml/ast/townnames.py nml/editors/__init__.py nml/editors/extract_tables.py nml/editors/kate.py nml/editors/notepadpp.py nml/expression/__init__.py nml/expression/array.py nml/expression/base_expression.py nml/expression/bin_not.py nml/expression/binop.py nml/expression/bitmask.py nml/expression/boolean.py nml/expression/functioncall.py nml/expression/functionptr.py nml/expression/identifier.py nml/expression/parameter.py nml/expression/patch_variable.py nml/expression/special_parameter.py nml/expression/spritegroup_ref.py nml/expression/storage_op.py nml/expression/string.py nml/expression/string_literal.py nml/expression/ternaryop.py nml/expression/variable.py regression/001_action8.nml regression/002_sounds.nml regression/003_assignment.nml regression/004_deactivate.nml regression/005_error.nml regression/006_vehicle.nml regression/007_townnames.nml regression/008_railtypes.nml regression/009_replace.nml regression/010_liveryoverride.nml regression/011_snowline.nml regression/012_basecost.nml regression/013_train_callback.nml regression/014_read_special_param.nml regression/015_basic_object.nml regression/016_basic_airporttiles.nml regression/017_articulated_tram.nml regression/018_airport_tile.nml regression/019_switch.nml regression/020_recolour.nml regression/021_grf_parameter.nml regression/022_disable_item.nml regression/023_engine_override.nml regression/024_conditional.nml regression/025_loop.nml regression/026_asl.nml regression/027_airport_layout.nml regression/028_font.nml regression/029_base_graphics.nml regression/030_house.nml regression/031_aircraft.nml regression/032_simple_house.nml regression/Makefile regression/arctic_railwagons.pcx regression/beef.wav regression/brewery.png regression/brewery_snow.png regression/font_addl.png regression/fonts.png regression/groundtiles.png regression/nlhs.png regression/oneway.png regression/opengfx_generic_trams1.pcx regression/opengfx_generic_trams1.png regression/opengfx_trains_start.pcx regression/temperate_railwagons.png regression/tram_foster_express.png regression/expected/001_action8.grf regression/expected/001_action8.nfo regression/expected/002_sounds.grf regression/expected/002_sounds.nfo regression/expected/003_assignment.grf regression/expected/003_assignment.nfo regression/expected/004_deactivate.grf regression/expected/004_deactivate.nfo regression/expected/005_error.grf regression/expected/005_error.nfo regression/expected/006_vehicle.grf regression/expected/006_vehicle.nfo regression/expected/007_townnames.grf regression/expected/007_townnames.nfo regression/expected/008_railtypes.grf regression/expected/008_railtypes.nfo regression/expected/009_replace.grf regression/expected/009_replace.nfo regression/expected/010_liveryoverride.grf regression/expected/010_liveryoverride.nfo regression/expected/011_snowline.grf regression/expected/011_snowline.nfo regression/expected/012_basecost.grf regression/expected/012_basecost.nfo regression/expected/013_train_callback.grf regression/expected/013_train_callback.nfo regression/expected/014_read_special_param.grf regression/expected/014_read_special_param.nfo regression/expected/015_basic_object.grf regression/expected/015_basic_object.nfo regression/expected/016_basic_airporttiles.grf regression/expected/016_basic_airporttiles.nfo regression/expected/017_articulated_tram.grf regression/expected/017_articulated_tram.nfo regression/expected/018_airport_tile.grf regression/expected/018_airport_tile.nfo regression/expected/019_switch.grf regression/expected/019_switch.nfo regression/expected/020_recolour.grf regression/expected/020_recolour.nfo regression/expected/021_grf_parameter.grf regression/expected/021_grf_parameter.nfo regression/expected/022_disable_item.grf regression/expected/022_disable_item.nfo regression/expected/023_engine_override.grf regression/expected/023_engine_override.nfo regression/expected/024_conditional.grf regression/expected/024_conditional.nfo regression/expected/025_loop.grf regression/expected/025_loop.nfo regression/expected/026_asl.grf regression/expected/026_asl.nfo regression/expected/027_airport_layout.grf regression/expected/027_airport_layout.nfo regression/expected/028_font.grf regression/expected/028_font.nfo regression/expected/029_base_graphics.grf regression/expected/029_base_graphics.nfo regression/expected/030_house.grf regression/expected/030_house.nfo regression/expected/031_aircraft.grf regression/expected/031_aircraft.nfo regression/expected/032_simple_house.grf regression/expected/032_simple_house.nfo regression/lang/dutch.lng regression/lang/english.lng regression/lang/us.lngnml-0.4.4/nml.egg-info/dependency_links.txt0000644000567200056720000000000112643457623022011 0ustar jenkinsjenkins00000000000000 nml-0.4.4/nmlc0000755000567200056720000000013112643457545014340 0ustar jenkinsjenkins00000000000000#! /usr/bin/env python3 from nml import main if __name__ == "__main__": main.run() nml-0.4.4/docs/0000755000567200056720000000000012643457623014413 5ustar jenkinsjenkins00000000000000nml-0.4.4/docs/license.txt0000644000567200056720000004310412643457545016603 0ustar jenkinsjenkins00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. nml-0.4.4/docs/readme.txt0000644000567200056720000001162112643457545016415 0ustar jenkinsjenkins00000000000000NML readme Last updated: 2016-01-07 Release version: 0.4.3 ------------------------------------------------------------------------ Table of Contents: ------------------ 1) About 2) Contact 3) Dependencies 3.1) Required dependencies 3.2) Optional dependencies 4) Installation 5) Usage 6) Known issues 7) Credits 1) About: -- ------ NML is a a python-based compiler, capable of compiling NML files (along with their associated language, sound and graphic files) into grf and / or nfo files. The documentation about the language can be found on http://newgrf-specs.tt-wiki.net/wiki/NML:Main NML is licensed under the GNU General Public License version 2, or at your option, any later version. For more information, see 'license.txt' (GPL version 2), or later versions at . 2) Contact: -- -------- Contact can be made via the issue tracker / source repository at http://dev.openttdcoop.org/projects/nml or via IRC on the #openttdcoop.devzone channel on OFTC. 3) Dependencies: -- ------------- 3.1) Required dependencies: ---- ---------------------- NML requires the following 3rd party packages to run: - python Minimal version is 3.2. Python 2 is not supported. - python image library For install options see https://pillow.readthedocs.org/installation.html#simple-installation - ply downloadable from http://www.dabeaz.com/ply/ 3.2) Optional dependencies: ---- ---------------------- To install NML you'll need these 3rd party packages: - buildout Only necesary if you want to use the installer. You can run NML without installation or manually install it. - gcc (or possibly another c++ compiler) Needed to compile the cython version of the lz77 module for grf encoding 4) Installation: -- ------------- NML uses buildout for packaging / installation. To install NML run: python setup.py install If you want to install the package manually copy 'nmlc' to any directory in your path and the directory 'nml' to any directory in your python path. 5) Usage: -- ------ Usage: nmlc [options] Where is the nml file to parse Options: --version show program's version number and exit -h, --help show this help message and exit -d, --debug write the AST to stdout -s, --stack Dump stack when an error occurs --grf= write the resulting grf to --md5= Write an md5sum of the resulting grf to --nfo= write nfo output to -M output a rule suitable for make describing the graphics dependencies of the main grf file (requires input file or --grf) --MF= When used with -M, specifies a file to write the dependencies to --MT= target of the rule emitted by dependency generation (requires -M) -c crop extraneous transparent blue from real sprites -u save uncompressed data in the grf file --nml= write optimized nml to -o , --output= write output(nfo/grf) to -t , --custom-tags= Load custom tags from [default: custom_tags.txt] -l , --lang-dir= Load language files from directory [default: lang] --default-lang= The default language is stored in [default: english.lng] --start-sprite= Set the first sprite number to write (do not use except when you output nfo that you want to include in other files) -p , --palette= Force nml to use the palette [default: ANY]. Valid values are 'DOS', 'WIN', 'ANY' --quiet Disable all warnings. Errors will be printed normally. -n, --no-cache Disable caching of sprites in .cache[index] files, which may reduce compilation time. --cache-dir= Cache files are stored in directory [default: .nmlcache] --clear-orphaned Remove unused/orphaned items from cache files. --verbosity= Set the verbosity level for informational output. [default: 3, max: 4] 6) Known issues: -- ------------- See the issue tracker at https://dev.openttdcoop.org/projects/nml/issues 7) Credits: -- -------- Active developers (in alphabetical order): Albert Hofkamp (Alberth) Christoph Elsenhans (frosch) Ingo von Borstel (planetmaker) Remko Bijker (Rubidium) Inactive developers: Jasper Reichardt (Hirundo) José Soler (Terkhen) Thijs Marinussen (Yexo) Special thanks to: Richard Barrell For writing the buildout script needed to install NML. nml-0.4.4/docs/nml.spec0000644000567200056720000000433212643457545016062 0ustar jenkinsjenkins00000000000000# # spec file for package nml # # Copyright (c) 2012 SUSE LINUX Products GmbH, Nuernberg, Germany. # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed # upon. The license for this file, and modifications and additions to the # file, is the same license as for the pristine package itself (unless the # license for the pristine package is not an Open Source License, in which # case the license is the MIT License). An "Open Source License" is a # license that conforms to the Open Source Definition (Version 1.9) # published by the Open Source Initiative. # Please submit bugfixes or comments via http://bugs.opensuse.org/ # Name: nml Version: 0.4.3 Release: 0 Summary: NewGRF Meta Language License: GPL-2.0+ Group: Development/Tools/Building Url: http://dev.openttdcoop.org/projects/nml Source0: http://bundles.openttdcoop.org/nml/releases/%{version}/%{name}-%{version}.src.tar.gz BuildRequires: python-devel >= 3.2 BuildRequires: python-setuptools #We need for regression test the required packages also on building: BuildRequires: python-imaging BuildRequires: python-ply Requires: python-imaging Requires: python-ply Provides: nmlc = %{version} BuildRoot: %{_tmppath}/%{name}-%{version}-build %if 0%{?suse_version} && 0%{?suse_version} <= 1110 %{!?python_sitelib: %global python_sitelib %(python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} %else BuildArch: noarch %endif %description A tool to compile nml files to grf or nfo files, making newgrf coding easier. %prep %setup -q %build python setup.py build %install python setup.py install --skip-build --root=%{buildroot} --prefix=%{_prefix} install -D -m0644 docs/nmlc.1 %{buildroot}%{_mandir}/man1/nmlc.1 #setuptools should not be a requirement on running, so we install the nmlc wrapper from source install -m0755 nmlc %{buildroot}%{_bindir}/nmlc %check cd regression PYTHONPATH=%{buildroot}%{python_sitelib} make _V= NMLC=%{buildroot}%{_bindir}/nmlc %files %defattr(-,root,root,-) %doc docs/*.txt %{_bindir}/nmlc %{_mandir}/man1/nmlc.1* %{python_sitelib}/* %changelog nml-0.4.4/docs/nmlc.10000644000567200056720000000536212643457545015437 0ustar jenkinsjenkins00000000000000.Dd January 07, 2016 .Dt NMLC 1 .Sh NAME .Nm NMLC .Nd A compiler from NML code to NFO and/or GRF files. .Sh SYNOPSIS .Nm nmlc .Op options .Op file .Sh OPTIONS .Bl -tag .It Fl c Crop extraneous transparent blue from real sprites. .It Fl u Save real sprites uncompressed to GRF files. This saves a lot of time during encoding but it's not recommended when creating a file for distribution since it makes the output file substantially bigger. .It Fl \-grf Ns = Ns Ar file Write output in GRF format to . .It Fl \-nfo Ns = Ns Ar file Write output in NFO format to . .It Fl \-nml Ns = Ns Ar file Write output in NML format to . .It Fl \-output Ns = Ns Ar file | Fl o Ar file Write output to . The output type is detected from the extension of the filename. It must be one of nfo, nml or grf. .It Fl \-md5 Ns = Ns Ar file Write an md5sum of the resulting grf to . .It Fl \-debug | Fl d Print a dump of the AST to stdout. .It Fl \-version Print programme's version number and exit. .It Fl \-help | Fl h Print usage information. .It Fl \-stack | Fl s Dump stack when an error occurs. .It Fl M Output a rule suitable for make describing the graphics dependencies of the main grf file (requires input file or \-\-grf) .It Fl \-MF Ns = Ns Ar file When used with \-M, specifies a file to write the dependencies to .It Fl \-MT Ns = Ns Ar file Target of the rule emitted by dependency generation (requires \-M) .It Fl \-custom\-tags Ns = Ns Ar file | Fl t Ar file Load custom tags from [default: custom_tags.txt]. .It Fl \-lang-dir Ns = Ns Ar dir | Fl l Ar dir Load language files from directory [default: lang]. .It Fl \-default\-lang Ns = Ns Ar file The default language is stored in [default: english.lng]. .It Fl \-sprites\-dir Ns = Ns Ar dir | Fl a Ar dir Store 32bpp sprites in directory [default: sprites]. .It Fl \-start\-sprite Ns = Ns Ar num Set the first sprite number to write (do not use except when you output nfo that you want to include in other files). .It Fl \-palette Ns = Ns Ar palette | Fl p Ar palette Force nml to use the palette [default: ANY]. Valid values are 'DOS', 'WIN', 'ANY'. .It Fl \-quiet Disable all warnings. Errors will be printed normally. .It Fl \-cache\-dir Ns = Ns Ar dir Cache files are stored in directory [default: .nmlcache]. .It Fl \-clear\-orphaned Remove unused / orphaned items from cache files. .It Fl \-verbosity Ns = Ns Ar level Set the verbosity level for informational output [default: 3, max: 4]. .El .Sh SEE ALSO The language reference at .Pa http://newgrf\-specs.tt\-wiki.net/wiki/NML:Main .Sh AUTHOR NML was written by Albert Hofkamp, Christoph Elsenhans, Jasper Reichardt, Ingo von Borstel, José Soler and Thijs Marinussen. .Pp This manual page was originally written by Thijs Marinussen. nml-0.4.4/docs/index.html0000644000567200056720000000123412643457545016413 0ustar jenkinsjenkins00000000000000 NML documentation

The NML documentation has been moved to the following location: http://newgrf-specs.tt-wiki.net/wiki/NML:Main.

You will be redirected in a few seconds, click the link if this doesn't happen (most likely because meta refresh is disabled in your browser).

nml-0.4.4/docs/changelog.txt0000644000567200056720000004646512643457545017125 0ustar jenkinsjenkins000000000000000.4.3 (2016-01-07) ------------------------------------------------------------------------ - Add: prob_map_gen as alternative name for the mapgen industry probability property - Fix: Compatibility with newer versions of pillow. - Fix: Vehicle 'sort' function caused internal error when an empty list was passed. - Fix: motion_counter is 24 bits, not 4. 0.4.2 (2015-09-13) ------------------------------------------------------------------------ -Add: New industry type limits of OpenTTD 1.6 -Fix: [CF] Build the version which is asked to be built instead of tip -Fix: Mark the cython acceleration module as optional. -Fix #7641: Sort gender and case translation tables deterministically (matthijs) -Fix #7640: Use dashes, not hyphens in manpage (matthijs) -Fix: getbits -Fix #7336: Action 6 offset was off by one for VA2 ranges when using a list of expressions in a switch. -Fix #7185: Incorrect Action6 offsets for Production Action2. -Doc: Be more verbose about MANIFEST.in and remove bootstrap from manifest as well 0.4.1 (2015-04-12) ------------------------------------------------------------------------ - Change: Try to improve packaging by applying some in-built automatisms via find_package() (oberhumer) (issue #7540) - Add: second_rocky_tileset - Add: Build-in function 'getbits' - Fix: Building source bundle was broken - Fix: Version identification for tags - Doc: Update readme with python version info - Cleanup: Remove pre-OpenTTD-1.1 wrappers for SHIFT_LEFT, SHIFT_RIGHT and SHIFTU_RIGHT. - Cleanup: Remove bootstrap 0.4.0 (2015-02-18) ------------------------------------------------------------------------ - Feature: [NewGRF] create_effect and effect_spawn_model - Feature: [NewGRF] EFFECT_SPRITE_NONE constant for create_effect callback - Feature: [NewGRF] support for OTTD_RECOLOUR action5 sprite(s) - Feature: [NewGRF] Support for Latin - Feature: [NewGRF] Variable to test for enabled wagon speed limits. (issue #6474) - Feature: Improve speed by caching position during parsing. - Feature: Warn about usage of animation and semi-transparent colours, and add spriteset flags to enable/disable the checks. (issue #1085) - Feature: Improved error position reporting with templated real sprites. Closes #7001 - Feature: Cython acceleration module for GRF compression. - Change: Convert from Python2 to Python 3.2+ - Change: At least check for isatty when using the funky colour codes in warning output (issue #5411) - Change: Do not store uncompressed sprites in the sprite cache. - Change: Use a separate spritecache file for each source image. - Change: Keep (possibly only temporarily) unused items in the spritecache until they are out-dated for sure. - Change: Reduce load on stdout by limiting incremental progress output to 1 message per second. - Change: Store amount of pure-white pixels in the spritecache instead of plain text warnings. - Change: Rewrite syntax file generators as python scripts - Change: [devzone] Don't run regressions in parallel to allow other tasks to run concurrently - Change: [devzone] Also update DevZone when we build tip but call it by its hash - Change: [devzone] Build the nml_lz77 extension for use by the CF - Add: Print progress information to interactive terminals. - Add: Progress output about lang files. - Add: Print statistics about used Action0 ids. - Add: Print statistics about used ActionF ids. - Add: Print statistics about used Spriteset ids. - Add: Print statistics about used Spritegroup ids. - Add: Print statistics about temporary Action2 registers. - Add: Print statistics about temporary ActionD registers and Action10 labels. - Add: Print statistics about GRF parameters. - Add: Print statistics about string ids. - Add: Print statistics about sound effect ids. - Add: Print statistics about cargo and railtype translation tables. - Add: Command line option to specify a cache directory. - Add: Command line option to set verbosity level of info output. - Add: Vebosity level 4 for printing CPU time on processing. - Add: Real sprites keep list of positions for improved error reporting with templates. - Add: Collect positions for real sprites through template instantiation. - Add: Build a position with an include stack from a list positions. - Fix: [NewGRF] Number of vehicles in var 41 is one-based, only var 40 is zero-based. - Fix: [NewGRF] Patch flags can only be accessed via action 7/9. (issue #6996) - Fix: [NewGRF] Action7/9 bit tests must use varsize 1. - Fix: Don't write parse tables. Closes #4091 - Fix: Printing Unicode characters in NFO was broken. Also added regression check for it. - Fix: CPP output line directive can have several flags, which caused matching failure. - Fix: Validate string names for being proper identifiers - Fix: Add the output palette to the spritecache key. (issue #6496) - Fix: Encode sprites sequentially per source image file. (issue #7004) - Fix: No proper error message was given, if an unreferenced Spritegroup was unable to allocate an id. - Fix: Don't fail if there's a .hg directory but no mercurial - Doc: main function, and instance variables of the parser and scanner. - Doc: RealSprite members - Doc: Unused return values. - Doc: instance variables of SpriteAction. 0.3.1 (2014-05-10) ------------------------------------------------------------------------ - Add: String commands CARGO_LONG, CARGO_SHORT and CARGO_TINY - Add: Object variable 'nearby_tile_object_view' - Add: Vehicle variable 'vehicle_is_unloading' - Add: House callback result 'CB_RESULT_HOUSE_NO_MORE_PRODUCTION' - Fix: Typo-fix in font_glpyh block name. - Fix: Error in raising an error in format_string - Fix: Don't catch more exception than expected, comment typo fixes, removed empty line - Fix: Report error from the spriteview position, instead of using a non-existing variable. - Fix: Do not report a position using a non-existing instance variable. - Fix: Don't crash on raising None after printing a warning. - Fix: Add also rpm spec file and syntax highlighting creation scripts to source distribution - Doc: Additional documentation, small fixes/improvments. - Change: Drop Python 2.5 support. 0.3.0 (2014-01-01) ------------------------------------------------------------------------ - Feature: use grf container format v2, nfo v32 and grf v8 - Feature: Support for alternate sprites, including 32bpp - Feature: Support for houses - Feature: Configurable volume for sound effects - Feature: Support pillow image library as well (Toshio Kuratomi) (issue #4799) - Feature: Remove the 64 kiB file size limit on sound effects - Feature: Vehicles: Deprecate property 'refittable_cargo_types' (issue #3583) - Feature: Vehicles: Add property 'default_cargo_type' for trains, RVs and ships (issue #3571) - Feature: Vehicle properties cargo_allow_refit and cargo_disallow_refit - Feature: Vehicles variables curvature info: 'curv_info', ' curv_info_prev_cur', 'curv_info_cur_next' and 'curv_info_prev_next' (variable 0x62) - Feature: Vehicles variables 'position_in_articulated_veh' and 'position_in_articulated_veh_from_end' (var 0x4D) - Feature: Canals variable 'dike_map' (variable 0x82) - Feature: Base-station variables for airports and stations - Feature: Station variables - Feature: Objects property 'count_per_map256' - Feature: General variables 'base_sprite_foundations' and 'base_sprite_shores' (variables 0x15 and 0x16) - Feature: Cargo property 'capacity multiplier' (property 0x1D) - Feature: Allow outputting multiple sprites per real sprite slot (issue #3712) - Feature: Labels for recolour sprites, as for real sprites - Feature: use language code instead of number as argument for ##grflangid pragma in language files (issue #3960) - Feature: Improved error output (issue #2929, #3814, #4736, #4299, #5411, #6209) - Feature: Unit conversions for non-constant values (issue #3828) - Feature: Builtin function format_string(format, args) (issue #4074) - Feature: Cache real sprites when building a GRF, to reduce compilation time - Feature: Allow using 'default: foo' in switch-blocks, or omitting the default altogether (issue #4186) - Feature: Check for identical language IDs in the different language files (issue #4997) - Feature: Verify whether translations use the plural form expected by their language - Feature: Consider file system paths case-insensitive at all systems (issue #5429) - Change: Vehicles: 'length' property and callback for trains and RVs (replaces 'shorten_vehicle') - Change: Vehicles: No longer set the default cargo automatically (issue #3571) - Change: Rework the RV speed property, so it works with non-constant values (issue #3828) - Change: Merge aircraft properties 'is_helicopter' and 'is_large' into a single common property 'aircraft_type' (issue #3700) - Change: Rename LOADINGSTAGE_XXX to LOADING_STAGE_XXX - Change: Canals: variable 'tile_height' now returns height in tiles - Change: Rename 'availability' callback to 'construction_probability' - Change: snowline_height is in tiles - Change: Output cropping debug output to stderr instead of stdout - Change: Unlink .grf file before write (issue #4165) - Change: Let item ID -1 mean 'use default ID' (as if no ID was specified) - Change: Don't always set the same property value for all tiles, only do so when appropriate - Change: Allocate parameters starting at 127 instead of 64 (issue #4222) - Change: improve optimization for expressions containing comparison operators by marking them as "return bool" - Change: Modify version output to always give the version and revision, for both, releases and nightlies - Change: delay changing not into xor with 1 to the last moment, it's not efficient when doing the computations via actionD, only for action2 - Change: use the location of the version_info.py file instead of the current repo's version (issue #5513) - Feature: Maintenance cost properties for airports and railtypes - Feature: New base costs for property maintenance - Add: max_height_level variable - Add: a few constants for generic groundsprites in the baseset - Add: new constant CB_RESULT_NO_TEXT for use in cargo_subtype_(text/display) - Add: Alternative railtype label list (issue #3459) - Add: New property 'value_function' for action 0 properties - Add: Command-line parameter '--no-cache' (-n) to disable caching of real sprites. Enable this parameter in the regression makefile for the NML-output to NFO/GRF compilation step - Add: Command-line parameter '--quiet' to disable all warnings (issue #3106) - Add: Command-line parameter '--md5' to write md5sum of grf to another file (issue #3732) - Add: NML output for item size and sprite layout parameters - Add: Language files: ##map_case pragma similar to ##map_gender - Add: TILE_CLASS_VOID to allow checking for map border and to have a constant when using nearby_tile_class on it - Add: Scripts to create syntax highlighting file for kate and notepad++ - Add: misc object flag 'OBJ_FLAG_SCALE_BY_WATER' to influence amount of objects placed on maps - Add: Support for Scottish Gaelic - Fix: hardcoded path in .devzone/build/files caused errors with new CF (issue #3267) - Fix: proper error message when input nml is not utf-8 encoded (issue #3233) - Fix: also use the filename in error messages if it isn't preprocessed by gcc but directly supplied as input file - Fix: provide proper error message when running out of parameter or label numbers - Fix: it was not possible to use constants in the -part of an item-block - Fix: Rename all occurences of 'base_sprites' to 'base_graphics', to avoid possible confusion such as wrong NML output - Fix: Real sprite lists may contain unexpanded templates, so comparing their lengths makes no sense - Fix: only reduce start_id argument to replace-block during pre-process stage (issue #3744) - Fix: with statement needs import from future for python 2.5 - Fix: Fix tile compression to remove some useless (for grfv2) code and add a missing check for the chunk length (issue #3785) - Fix: Sprites with long format (!= long chunks) tile compression had incorrect offsets - Fix: some string industry properties didn't accept a string as value - Fix: do palette conversion before putting the sprite data in a tuple - Fix: ind. tile 'autoslope' cb num was set to 0x3B instead of 0x3C - Fix: compatibility with python 2.5 (issue #3998) - Fix: provide file/line information when detecting an error in a string even when in a later stage (issue #3898) - Fix: Backslash-escapes in strings weren't properly validated. Also remove useless \n escape (issue #3636) - Fix: Proper handling of failed callbacks (issue #2933) - Fix: Provide a proper error message if a substring is missing, instead of an assertion error (also issue #3674) (issue #3932) - Fix: Use translations for statically included string parameters if possible, even if the base string is not translated (issue #3642) - Fix: Proper error message when running out of switch registers (issue #3082) - Fix: Don't use a string instead of a position object (issue #4063) - Fix: We should always round floats, but not try to round everything else - Fix: refit_cost callback may also be called from the purchase menu - Fix: Publishing one of the generated grfs is enough. Especially if they would overwrite eachother on publishing - Fix: Make ActionA work for more than 255 consecutive sprites - Fix: applying operator ! to a constant number didn't work (issue #4458) - Fix: Missing range check for callback results could cause assertions (issue #4769) - Fix: Cache white pixel messages (issue #4760) - Fix: Random switch interdependencies were messed up (issue #4742) - Fix: Rail type and snowline table action6 offsets weren't updated when changing ID to an extended byte (issue #4787) - Fix: Incorrect action 6 offset in random action 2 (issue #4730) - Fix: Use the same (decimal) numbering scheme for plurals in language files as OpenTTD (issue #4811) - Fix: cargo_age_period is property 2B, not 28. A typo caused CB36 to fail - Fix: Position information for errors regarding sound file includes was missing / broken (issue #4850) - Fix: Give the expected default lang file name in case it wasn't found - Fix: Road vehicle speed was incorrectly set for vehicles faster than 70mph (issue #5336) - Fix: Object variable 'company colour' returned faulty values (issue #5624) - Fix: Snowline code still generated GRFv7 output (issue #5609) - Fix: Town name parts could end up with more than 255 entries - Fix: Also add offset when skipping fixed rail types - Fix: Don't loop forever on / paths (issue #6209) - Fix: adjust_value had tendency to take the value higher than the wanted - Fix: aircraft speed property conversion didn't do the conversion from (issue #4667) - Fix: For houses without any callbacks a dummy VA2 with zero cases was created, which unintentionally returned a computed value (issue #5294) - Fix: P string command shall default to the previous parameter (issue #6503) - Doc: add abstract base class to document Action0Property and relatives - Doc: RPM build spec - Doc: why the default language is processed twice - Doc: Add some code documentation to the grfstrings.py file - Remove: Ability to save 32bpp sprites as pngs, and lots of other stuff that won't be needed anymore - Remove: Deprecated refittable_cargo_types property. Instead zero it upon writing a different refit property, to avoid possible conflicts with other grfs (issue #3583) - Remove: Unused command-line parameter 'sprites-dir.' - Remove: Cargo properties single_unit_text and multiple_units_text 0.2.5 (2014-01-01) ------------------------------------------------------------------------ - Feature: Support pillow image library as well (Toshio Kuratomi) (issue #4799) - Change: Modify version output to always give the version and revision, for both, releases and nightlies - Add: New GUI sprite in OpenTTD r24749, r25293, r25344 and r25916 - Fix: Use the correct version_info.py file instead of the current repo's version (issue #5513) - Fix: Town name parts could end up with more than 255 entries - Fix: Also add offset when skipping fixed rail types - Fix: adjust_value had tendency to take the value higher than the wanted value - Fix: aircraft speed property conversion didn't do the conversion from (issue #4667) 0.2.4 (2012-10-14) ------------------------------------------------------------------------ - Feature: Report NML line information as well as pixel position for pure white pixels. Also, report number of pixels in the sprite, instead of the whole image (issue #4029) - Feature: 'signals' callback for railtypes - Feature: Allow the 'nfo' unit to be used with non-constant values (issue #3828) - Feature: 'build_prod_change' callback for industries to set industry production level on construction - Feature: Constant CB_RESULT_REFIT_COST_MASK - Feature: Vehicle misc_flag VEHTYPE_FLAG_NO_BREAKDOWN_SMOKE - Feature: 'current_max_speed' variable for vehicles (issue #3979) - Feature: 'vehicle_is_in_depot' variable for aircraft - Feature: 'range' property and callback for aircraft - Feature: 'production_rate_1/2' variables for industries - Feature: 'town_zone' variable for railtypes - Feature: 'other_veh_(curv_info|is_hidden|x_offset|y_offset|z_offset)' variables for vehicles - Fix: Provide a proper error message when running out of action2 IDs - Fix: A '{' at the end of a string could cause a crash - Fix: Backslash-escapes in strings weren't properly validated. Also remove useless \n escape (issue #3636) - Fix: Provide a proper error message if a substring is missing, instead of an assertion error (issue #3932) - Fix: 'refit_cost' callback may also be called from the purchase menu - Fix: Allow using constants > 255 as variable parameter (issue #4086) - Fix: Sprite layout register code contained an unsorted iteration over dictionary keys, resulting in possible regression failures 0.2.3 (2012-02-19) ------------------------------------------------------------------------ - Feature: Action5 for tunnel portals - Fix: Properly catch out-of-bounds image reads (issue #3666) - Fix: Character code 0xA0 (NBSP) is used for an up arrow in TTD, so don't write it as ascii. Force unicode instead (issue #3643) 0.2.2 (2012-01-29) ------------------------------------------------------------------------ - Feature: support for (optional) url-information in the grf-block - Feature: names for newly defined cargo classes: CC_POWDERIZED and CC_NEO_BULK - Feature: clean target to Makefile in main dir and let make clean remove regression/parsetab.py - Fix: don't crash when a line in custom_tags.txt is missing a colon delimiter - Fix: each action4 is limited to 255 strings. Write multiple actions when we have more than that - Fix: groff warning about manpage - Fix: include buildout.cfg in src distribution and prune regression/nml_output/ (issue #3490) - Fix: Add NML output for replacenew-block (issue #3450) - Fix: include nmlc script so regression runs out-of-the-box for source package - Fix: several files from examples/ were missing in source distributions - Fix: 'make install' was broken - Doc: add notice about ZPL in readme.txt 0.2.1 (2011-12-18) ------------------------------------------------------------------------ - Feature: CB_RESULT_NO_MORE_ARTICULATED_PARTS, CB_RESULT_REVERSED_VEHICLE and CB_RESULT_NO_TEXT as constants to make porting projects to NML 0.3 easier. - Fix: Internal error when the ID in a replace-block was not a compile-time constant - Fix: Crash when referencing a SpriteSetCollection in a graphics-block that was inside an if-block - Fix: Text files in docs/ were not included in source package - Doc: Add GPL v2 header to all .py files 0.2.0 (2011-11-20) ------------------------------------------------------------------------ No changelog available for 0.2.0 and earlier nml-0.4.4/Makefile0000644000567200056720000000043412643457545015127 0ustar jenkinsjenkins00000000000000MAKE?=make PYTHON?=/usr/bin/env python3 .PHONY: regression install bundle extensions clean regression: extensions $(MAKE) -C regression test: regression install: $(PYTHON) setup.py install extensions: $(PYTHON) setup.py build_ext --inplace clean: $(MAKE) -C regression clean nml-0.4.4/setup.cfg0000644000567200056720000000007312643457623015304 0ustar jenkinsjenkins00000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0