def preprocess_storageop(self, expr): assert isinstance(expr, expression.StorageOp) 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(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() elif isinstance(expr, expression.SpriteGroupRef): self.parse_proc_call(expr) else: expr.supported_by_action2(True) assert False #supported_by_action2 should have raised the correct error already
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)
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 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 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, [])
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 preprocess_storageop(self, expr): assert isinstance(expr, expression.StorageOp) if expr.info["perm"] and not self.var_scope.has_persistent_storage: raise generic.ScriptError( "Persistent storage is not supported for feature '{}'".format( self.var_scope.name), 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.var_scope is action2var_variables.scope_towns: # 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 = nmlop.STO_TMP(grfid, 0x100, expr.pos) ret = nmlop.VAL2(store_op, ret) 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_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 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 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)
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, [])
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 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 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 <count>, 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 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 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 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 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 <num-sets> sprite_offset = 0 # Number of sprites already covered by previous [<num-sprites> <first-sprite>]-pairs while total_sprites > 0: this_block = min(total_sprites, 255) # number of sprites in this block total_sprites -= this_block offset += 1 # Skip <num-sprites> 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
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 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
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 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_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
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
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 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 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 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 parse_not(self, expr): self.parse_binop( expression.BinOp(nmlop.XOR, expr.expr, expression.ConstantNumeric(1)))