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 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 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 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 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, [], [])
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" for proc in varact2parser.proc_call_list: action2.add_ref(proc, varaction2, True) ref = expression.SpriteGroupRef(expression.Identifier(name), [], None, varaction2) action_list.append(varaction2) return (action_list, ref)
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 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 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 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 param in spritelayout.param_list: reg = action2var.VarAction2CallParam(param.value) param_registers.append(reg) param_map[param.value] = reg param_map = (param_map, lambda name, value, pos: action2var. VarAction2LoadCallParam(value, name)) 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, action2var.get_scope(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() advanced = any(x.is_advanced_sprite() for x in building_sprites + [ground_sprite]) offset = 4 sprite_num = ground_sprite.get_sprite_number() sprite_num, offset = actionD.write_action_value(sprite_num, actions, act6, offset, 4) if advanced: 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) if advanced: 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, action2var.get_scope(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: # lgtm[py/uninitialized-local-variable] # 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 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_scope = get_scope(parent_action.feature, var_range) varact2parser = Varaction2Parser(parent_action.feature, var_scope) 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_scope)) varact2parser.var_list.append(nmlop.STO_TMP) store_tmp = VarAction2StoreCallParam( 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 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, get_scope(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_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)) var_scope = get_scope(feature, switch_block.var_range) varaction2 = Action2Var( feature, switch_block.name.value, switch_block.pos, switch_block.var_range, switch_block.register_map[feature], ) expr = reduce_varaction2_expr(switch_block.expr, var_scope) offset = 4 # first var parser = Varaction2Parser(feature, var_scope) 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 for proc in parser.proc_call_list: action2.add_ref(proc, varaction2, True) 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( generic.Warning.GENERIC, "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
def func60x(name, value, pos): return expression.FunctionPtr(expression.Identifier(name, pos), parse_60x_var, value)