def get_production_v2_actions(produce): """ Get the action list that implements the given produce-block in nfo. @param produce: Produce-block to parse. @type produce: L{Produce2} """ action_list = [] action6.free_parameters.save() varact2parser = action2var.Varaction2Parser(0x0A, action2var.get_scope(0x0A)) def resolve_cargoitem(item): cargolabel = item.name.value if cargolabel not in global_constants.cargo_numbers: raise generic.ScriptError( "Cargo label {0} not found in your cargo table".format( cargolabel), produce.pos) cargoindex = global_constants.cargo_numbers[cargolabel] valueregister = resolve_prodcb_register(item.value, varact2parser) return (cargoindex, valueregister) sub_in = [resolve_cargoitem(item) for item in produce.subtract_in] add_out = [resolve_cargoitem(item) for item in produce.add_out] again = resolve_prodcb_register(produce.again, varact2parser) prod_action = Action2Production(produce.name.value, produce.pos, 2, sub_in, add_out, again) return finish_production_actions(produce, prod_action, action_list, varact2parser)
def pre_process(self): for graphics_def in self.graphics_list: graphics_def.reduce_expressions(action2var.get_scope(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, action2var.get_scope(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 pre_process(self): # Check parameter names seen_names = set() for param in self.param_list: if not isinstance(param, expression.Identifier): raise generic.ScriptError( "switch 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) feature = next(iter(self.feature_set)) var_scope = action2var.get_scope(feature, self.var_range) if var_scope is None: raise generic.ScriptError( "Requested scope not available for this feature.", self.pos) # Allocate registers param_map = {} param_registers = [] for param in self.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)) self.register_map[feature] = param_registers self.expr = action2var.reduce_varaction2_expr(self.expr, var_scope, [param_map]) self.body.reduce_expressions(var_scope, [param_map]) switch_base_class.pre_process(self)
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, action2var.get_scope(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 param in produce.param_list: result_list.append(resolve_prodcb_register(param, varact2parser)) 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]) return finish_production_actions(produce, prod_action, action_list, varact2parser)
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, action2var.get_scope(feature)) varact2parser.parse_expr(expr) return create_intermediate_varaction2(feature, varact2parser, mapping, default, pos)
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 name, val, pos: expression.ConstantNumeric(val, pos))] offset = action2var.reduce_varaction2_expr( sg_ref.param_list[0], action2var.get_scope(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 pre_process(self): var_scope = action2var.get_scope(0x0A) for i, param in enumerate(self.subtract_in): self.subtract_in[i].value = action2var.reduce_varaction2_expr( param.value, var_scope) for i, param in enumerate(self.add_out): self.add_out[i].value = action2var.reduce_varaction2_expr( param.value, var_scope) self.again = action2var.reduce_varaction2_expr(self.again, var_scope) produce_base_class.pre_process(self)
def pre_process(self): generic.print_warning( generic.Warning.DEPRECATION, "Consider using the new produce() syntax for '{}'".format( self.name), self.name.pos, ) var_scope = action2var.get_scope(0x0A) for i, param in enumerate(self.param_list): self.param_list[i] = action2var.reduce_varaction2_expr( param, var_scope) produce_base_class.pre_process(self)
def resolve_prodcb_register(param, varact2parser): if ( isinstance(param, expression.StorageOp) and param.name == "LOAD_TEMP" and isinstance(param.register, expression.ConstantNumeric) ): # We can load a register directly res = 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, action2var.get_scope(0x0A))) store_tmp = action2var.VarAction2StoreTempVar() res = 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 return res
def pre_process(self): feature = next(iter(self.feature_set)) # var_scope is really weird for type=BACKWARD/FORWARD. # Expressions in cases will still refer to the origin vehicle. var_scope = action2var.get_scope( feature, 0x8A if self.type.value == "PARENT" else 0x89) for choice in self.choices: choice.reduce_expressions(var_scope) 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 create_proc_call_varaction2(feature, proc, ret_value_function, parent_action, 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}) """ proc.is_procedure = True varact2parser = action2var.Varaction2Parser(feature, action2var.get_scope(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) action2.add_ref(result, parent_action) comment = result.name.value + ";" return (action_list, result, comment)
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 __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: # var_scope is really weird for type=BACKWARD/FORWARD. var_scope = action2var.get_scope(feature, 0x89) self.type_count = action2var.reduce_varaction2_expr( self.type.params[0], var_scope) 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 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 = {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, 0x8A if type_byte == 0x83 else 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 to compute parameter for type 0x84 varaction2 = action2var.Action2Var( feature, "{}@registers".format(random_switch.name.value), random_switch.pos, 0x89) varact2parser = action2var.Varaction2Parser( feature, action2var.get_scope(feature, 0x89)) 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