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 = nmlop.SHIFT_LEFT(house_north_tile_id, 24) register_FF = nmlop.OR(lowbytes, highbyte).reduce() register_FF = nmlop.STO_TMP(register_FF, 0xFF) expr = nmlop.VAL2(register_FF, expr) 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) # lgtm[py/uninitialized-local-variable] action6.free_parameters.restore() return prepend_action_list + action_list
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_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): if global_constants.identifier_refcount[assignment.param.value] == 0: # Named parameter is not referenced, ignoring return [] 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 = nmlop.SUB(1, assignment.value.expr) assignment = ParameterAssignment(assignment.param, expr) if isinstance(assignment.value, expression.BinNot): expr = nmlop.SUB(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 parse_property_block(prop_list, feature, id, size): """ Parse a property block to an action0 (with possibly various other actions) @param prop_list: List of properties to parse @type prop_list: C{list} of L{Property} @param feature: Feature of the associated item @type feature: C{int} @param id: ID of the associated item @type id: L{Expression} @param size: Size (for houses only) @type size: L{ConstantNumeric} or C{None} @return: List of resulting actions @rtype: C{list} of L{BaseAction} """ action6.free_parameters.save() action_list = [] action_list_append = [] act6 = action6.Action6() action0, offset = create_action0(feature, id, act6, action_list) if feature == 0x07: size_bit = size.value if size is not None else 0 action0.num_ids = house_sizes[size_bit] else: size_bit = None action0.num_ids = 1 prop_info_list = [] value_list_list = [] pos_list = [] for prop in prop_list: new_prop_info_list = get_property_info_list(feature, prop.name) prop_info_list.extend(new_prop_info_list) value_list_list.extend( parse_property_value(prop_info, prop.value, prop.unit, size_bit) for prop_info in new_prop_info_list ) pos_list.extend(prop.name.pos for i in prop_info_list) validate_prop_info_list(prop_info_list, pos_list, feature) for prop_info, value_list in zip(prop_info_list, value_list_list): if "test_function" in prop_info and not prop_info["test_function"](*value_list): continue props, extra_actions, mods, extra_append_actions = parse_property(prop_info, value_list, feature, id) action_list.extend(extra_actions) action_list_append.extend(extra_append_actions) for mod in mods: act6.modify_bytes(mod[0], mod[1], mod[2] + offset) for p in props: offset += p.get_size() action0.prop_list.extend(props) if len(act6.modifications) > 0: action_list.append(act6) if len(action0.prop_list) != 0: action_list.append(action0) action_list.extend(action_list_append) action6.free_parameters.restore() return action_list
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_randomswitch(random_switch): """ Parse a randomswitch block into actions @param random_switch: RandomSwitch block to parse @type random_switch: L{RandomSwitch} @return: List of actions @rtype: C{list} of L{BaseAction} """ action_list = action2real.create_spriteset_actions(random_switch) feature = next(iter(random_switch.feature_set)) type_byte, count, count_expr, start_bit, bits_available = parse_randomswitch_type( random_switch) total_prob = sum( [choice.probability.value for choice in random_switch.choices]) assert total_prob > 0 nrand = 1 while nrand < total_prob: nrand <<= 1 # Verify that enough random data is available if min(1 << bits_available, 0x80) < nrand: msg = "The maximum sum of all random_switch probabilities is {:d}, encountered {:d}." msg = msg.format(min(1 << bits_available, 0x80), total_prob) raise generic.ScriptError(msg, random_switch.pos) randbit, nrand = parse_randomswitch_dependencies(random_switch, start_bit, bits_available, nrand) random_action2 = Action2Random(feature, random_switch.name.value, random_switch.pos, type_byte, count, random_switch.triggers.value, randbit, nrand) random_switch.random_act2 = random_action2 action6.free_parameters.save() act6 = action6.Action6() offset = 8 if count is not None else 7 #divide the 'extra' probabilities in an even manner i = 0 resulting_prob = dict( (c, c.probability.value) for c in random_switch.choices) while i < (nrand - total_prob): best_choice = None best_ratio = 0 for choice in random_switch.choices: #float division, so 9 / 10 = 0.9 ratio = choice.probability.value / float(resulting_prob[choice] + 1) if ratio > best_ratio: best_ratio = ratio best_choice = choice assert best_choice is not None resulting_prob[best_choice] += 1 i += 1 for choice in random_switch.choices: res_prob = resulting_prob[choice] result, comment = action2var.parse_result(choice.result.value, action_list, act6, offset, random_action2, None, 0x89, res_prob) offset += res_prob * 2 comment = "({:d}/{:d}) -> ({:d}/{:d}): ".format( choice.probability.value, total_prob, res_prob, nrand) + comment random_action2.choices.append( RandomAction2Choice(result, res_prob, comment)) if len(act6.modifications) > 0: action_list.append(act6) action_list.append(random_action2) if count_expr is None: random_switch.set_action2(random_action2, feature) else: # Create intermediate varaction2 varaction2 = action2var.Action2Var( feature, '{}@registers'.format(random_switch.name.value), random_switch.pos, 0x89) varact2parser = action2var.Varaction2Parser(feature) varact2parser.parse_expr(count_expr) varaction2.var_list = varact2parser.var_list action_list.extend(varact2parser.extra_actions) extra_act6 = action6.Action6() for mod in varact2parser.mods: extra_act6.modify_bytes(mod.param, mod.size, mod.offset + 4) if len(extra_act6.modifications) > 0: action_list.append(extra_act6) ref = expression.SpriteGroupRef(random_switch.name, [], None, random_action2) varaction2.ranges.append( action2var.VarAction2Range(expression.ConstantNumeric(0), expression.ConstantNumeric(0), ref, '')) varaction2.default_result = ref varaction2.default_comment = '' # Add two references (default + range) action2.add_ref(ref, varaction2) action2.add_ref(ref, varaction2) random_switch.set_action2(varaction2, feature) action_list.append(varaction2) action6.free_parameters.restore() return action_list
def parse_sg_ref_result(result, action_list, parent_action, var_range): """ Parse a result that is a sprite group reference. @param result: Result to parse @type result: L{SpriteGroupRef} @param action_list: List to append any extra actions to @type action_list: C{list} of L{BaseAction} @param parent_action: Reference to the action of which this is a result @type parent_action: L{BaseAction} @param var_range: Variable range to use for variables in the expression @type var_range: C{int} @return: Result to use in the calling varaction2 @rtype: L{SpriteGroupRef} """ if result.name.value == "CB_FAILED": return get_failed_cb_result(parent_action.feature, action_list, parent_action, result.pos) if len(result.param_list) == 0: action2.add_ref(result, parent_action) return result # Result is parametrized # Insert an intermediate varaction2 to store expressions in registers var_feature = parent_action.feature if var_range == 0x89 else action2var_variables.varact2parent_scope[ parent_action.feature] varact2parser = Varaction2Parser(var_feature) layout = action2.resolve_spritegroup(result.name) for i, param in enumerate(result.param_list): if i > 0: varact2parser.var_list.append(nmlop.VAL2) varact2parser.var_list_size += 1 varact2parser.parse_expr(reduce_varaction2_expr(param, var_feature)) varact2parser.var_list.append(nmlop.STO_TMP) store_tmp = VarAction2StoreLayoutParam( layout.register_map[parent_action.feature][i]) varact2parser.var_list.append(store_tmp) varact2parser.var_list_size += store_tmp.get_size( ) + 1 # Add 1 for operator action_list.extend(varact2parser.extra_actions) extra_act6 = action6.Action6() for mod in varact2parser.mods: extra_act6.modify_bytes(mod.param, mod.size, mod.offset + 4) if len(extra_act6.modifications) > 0: action_list.append(extra_act6) global return_action_id name = "@return_action_{:d}".format(return_action_id) varaction2 = Action2Var(parent_action.feature, name, result.pos, var_range) return_action_id += 1 varaction2.var_list = varact2parser.var_list ref = expression.SpriteGroupRef(result.name, [], result.pos) varaction2.ranges.append( VarAction2Range(expression.ConstantNumeric(0), expression.ConstantNumeric(0), ref, result.name.value)) varaction2.default_result = ref varaction2.default_comment = result.name.value # Add the references as procs, to make sure, that any intermediate registers # are freed at the spritelayout and thus not selected to pass parameters # Reference is used twice (range + default) so call add_ref twice action2.add_ref(ref, varaction2, True) action2.add_ref(ref, varaction2, True) ref = expression.SpriteGroupRef(expression.Identifier(name), [], None, varaction2) action_list.append(varaction2) action2.add_ref(ref, parent_action) return ref
def parse_varaction2(switch_block): global return_action_id return_action_id = 0 action6.free_parameters.save() act6 = action6.Action6() action_list = action2real.create_spriteset_actions(switch_block) feature = next(iter(switch_block.feature_set)) varaction2 = Action2Var(feature, switch_block.name.value, switch_block.pos, switch_block.var_range) expr = reduce_varaction2_expr(switch_block.expr, get_feature(switch_block)) offset = 4 #first var parser = Varaction2Parser(get_feature(switch_block)) parser.parse_expr(expr) action_list.extend(parser.extra_actions) for mod in parser.mods: act6.modify_bytes(mod.param, mod.size, mod.offset + offset) varaction2.var_list = parser.var_list offset += parser.var_list_size + 1 # +1 for the byte num-ranges 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( "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 get_layout_action2s(spritelayout, feature, spr_pos): """ @param spr_pos: Position information of the sprite view. @type spr_pos: L{Position} """ ground_sprite = None building_sprites = [] actions = [] if feature not in action2.features_sprite_layout: raise generic.ScriptError( "Sprite layouts are not supported for feature '{}'.".format( general.feature_name(feature))) # Allocate registers param_map = {} param_registers = [] for i, param in enumerate(spritelayout.param_list): reg = action2var.VarAction2LayoutParam() param_registers.append(reg) param_map[param.value] = reg param_map = ( param_map, lambda value, pos: action2var.VarAction2LoadLayoutParam(value)) spritelayout.register_map[feature] = param_registers # Reduce all expressions, can't do that earlier as feature is not known all_sprite_sets = [] layout_sprite_list = [] # Create a new structure for layout_sprite in spritelayout.layout_sprite_list: param_list = [] layout_sprite_list.append( (layout_sprite.type, layout_sprite.pos, param_list)) for param in layout_sprite.param_list: param_val = action2var.reduce_varaction2_expr( param.value, feature, [param_map]) param_list.append((param.name, param_val)) if isinstance(param_val, expression.SpriteGroupRef): spriteset = action2.resolve_spritegroup(param_val.name) if not spriteset.is_spriteset(): raise generic.ScriptError( "Expected a reference to a spriteset.", param_val.pos) all_sprite_sets.append(spriteset) actions.extend( action1.add_to_action1(all_sprite_sets, feature, spritelayout.pos)) temp_registers = [] for type, pos, param_list in layout_sprite_list: if type.value not in layout_sprite_types: raise generic.ScriptError( "Invalid sprite type '{}' encountered. Expected 'ground', 'building', or 'childsprite'." .format(type.value), type.pos) sprite = Action2LayoutSprite(feature, layout_sprite_types[type.value], pos, [param_map]) for name, value in param_list: sprite.set_param(name, value) temp_registers.extend(sprite.get_all_registers()) if sprite.type == Action2LayoutSpriteType.GROUND: if ground_sprite is not None: raise generic.ScriptError( "Sprite layout can have no more than one ground sprite", spritelayout.pos) ground_sprite = sprite else: building_sprites.append(sprite) if ground_sprite is None: if len(building_sprites) == 0: #no sprites defined at all, that's not very much. raise generic.ScriptError( "Sprite layout requires at least one sprite", spr_pos) #set to 0 for no ground sprite ground_sprite = Action2LayoutSprite(feature, Action2LayoutSpriteType.GROUND) ground_sprite.set_param(expression.Identifier('sprite'), expression.ConstantNumeric(0)) action6.free_parameters.save() act6 = action6.Action6() offset = 4 sprite_num = ground_sprite.get_sprite_number() sprite_num, offset = actionD.write_action_value(sprite_num, actions, act6, offset, 4) offset += ground_sprite.get_registers_size() for sprite in building_sprites: sprite_num = sprite.get_sprite_number() sprite_num, offset = actionD.write_action_value( sprite_num, actions, act6, offset, 4) offset += sprite.get_registers_size() offset += 3 if sprite.type == Action2LayoutSpriteType.CHILD else 6 if len(act6.modifications) > 0: actions.append(act6) layout_action = Action2Layout( feature, spritelayout.name.value + " - feature {:02X}".format(feature), spritelayout.pos, ground_sprite, building_sprites, param_registers) actions.append(layout_action) if temp_registers: varact2parser = action2var.Varaction2Parser(feature) for register_info in temp_registers: reg, expr = register_info[1], register_info[2] if reg is None: continue varact2parser.parse_expr(expr) varact2parser.var_list.append(nmlop.STO_TMP) varact2parser.var_list.append(reg) varact2parser.var_list.append(nmlop.VAL2) varact2parser.var_list_size += reg.get_size() + 2 # Only continue if we actually needed any new registers if temp_registers and varact2parser.var_list: #Remove the last VAL2 operator varact2parser.var_list.pop() varact2parser.var_list_size -= 1 actions.extend(varact2parser.extra_actions) extra_act6 = action6.Action6() for mod in varact2parser.mods: extra_act6.modify_bytes(mod.param, mod.size, mod.offset + 4) if len(extra_act6.modifications) > 0: actions.append(extra_act6) varaction2 = action2var.Action2Var( feature, "{}@registers - feature {:02X}".format(spritelayout.name.value, feature), spritelayout.pos, 0x89) varaction2.var_list = varact2parser.var_list ref = expression.SpriteGroupRef(spritelayout.name, [], None, layout_action) varaction2.ranges.append( action2var.VarAction2Range(expression.ConstantNumeric(0), expression.ConstantNumeric(0), ref, '')) varaction2.default_result = ref varaction2.default_comment = '' # Add two references (default + range) # Make sure that registers allocated here are not used by the spritelayout action2.add_ref(ref, varaction2, True) action2.add_ref(ref, varaction2, True) spritelayout.set_action2(varaction2, feature) actions.append(varaction2) else: spritelayout.set_action2(layout_action, feature) action6.free_parameters.restore() return actions
def get_production_actions(produce): """ Get the action list that implements the given produce-block in nfo. @param produce: Produce-block to parse. @type produce: L{Produce} """ action_list = [] act6 = action6.Action6() action6.free_parameters.save() result_list = [] varact2parser = action2var.Varaction2Parser(0x0A) if all(x.supported_by_actionD(False) for x in produce.param_list): version = 0 offset = 4 for i, param in enumerate(produce.param_list): result, offset = actionD.write_action_value( param, action_list, act6, offset, 2 if i < 5 else 1) result_list.append(result.value) else: version = 1 for i, param in enumerate(produce.param_list): if isinstance(param, expression.StorageOp) and param.name == 'LOAD_TEMP' and \ isinstance(param.register, expression.ConstantNumeric): # We can load a register directly result_list.append( action2var.VarAction2Var(0x7D, 0, 0xFFFFFFFF, param.register.value)) else: if len(varact2parser.var_list) != 0: varact2parser.var_list.append(nmlop.VAL2) varact2parser.var_list_size += 1 varact2parser.parse_expr( action2var.reduce_varaction2_expr(param, 0x0A)) store_tmp = action2var.VarAction2StoreTempVar() result_list.append(action2var.VarAction2LoadTempVar(store_tmp)) varact2parser.var_list.append(nmlop.STO_TMP) varact2parser.var_list.append(store_tmp) varact2parser.var_list_size += store_tmp.get_size( ) + 1 # Add 1 for operator if len(act6.modifications) > 0: action_list.append(act6) prod_action = Action2Production(produce.name.value, produce.pos, version, result_list[0:3], result_list[3:5], result_list[5]) action_list.append(prod_action) if len(varact2parser.var_list) == 0: produce.set_action2(prod_action, 0x0A) else: # Create intermediate varaction2 varaction2 = action2var.Action2Var( 0x0A, '{}@registers'.format(produce.name.value), produce.pos, 0x89) varaction2.var_list = varact2parser.var_list action_list.extend(varact2parser.extra_actions) extra_act6 = action6.Action6() for mod in varact2parser.mods: extra_act6.modify_bytes(mod.param, mod.size, mod.offset + 4) if len(extra_act6.modifications) > 0: action_list.append(extra_act6) ref = expression.SpriteGroupRef(produce.name, [], None, prod_action) varaction2.ranges.append( action2var.VarAction2Range(expression.ConstantNumeric(0), expression.ConstantNumeric(0), ref, '')) varaction2.default_result = ref varaction2.default_comment = '' # Add two references (default + range) action2.add_ref(ref, varaction2) action2.add_ref(ref, varaction2) produce.set_action2(varaction2, 0x0A) action_list.append(varaction2) action6.free_parameters.restore() return action_list
def get_string_action4s(feature, string_range, string, id=None): """ Let a string from the lang files be used in the rest of NML. This may involve adding actions directly, but otherwise an ID is allocated and the string will be written later @param feature: Feature that uses the string @type feature: C{int} @param string_range: String range to use, either a value from L{string_ranges} or C{None} if N/A (item names) @type string_range: C{int} or C{None} @param string: String to parse @type string: L{expression.String} @param id: ID to use for this string, or C{None} if it will be allocated dynamically (random_id is true for the string range) @type id: L{Expression} or C{None} @return: A tuple of two values: - ID of the string (useful if allocated dynamically) - Resulting action list to be appended @rtype: C{tuple} of (C{int}, C{list} of L{BaseAction}) """ grfstrings.validate_string(string) write_action4s = True action6.free_parameters.save() actions = [] mod = None if string_range is not None: size = 2 if string_ranges[string_range]["random_id"]: # ID is allocated randomly, we will output the actions later write_action4s = False if (feature, string) in used_strings[string_range]: id_val = used_strings[string_range][(feature, string)] else: id_val = string_ranges[string_range]["ids"].pop() used_strings[string_range][(feature, string)] = id_val else: # ID must be supplied assert id is not None assert isinstance(id, expression.ConstantNumeric) id_val = id.value | (string_range << 8) else: # Not a string range, so we must have an id assert id is not None size = 3 if feature <= 3 else 1 if isinstance(id, expression.ConstantNumeric): id_val = id.value else: id_val = 0 tmp_param, tmp_param_actions = actionD.get_tmp_parameter(id) actions.extend(tmp_param_actions) # Apply ID via action4 later mod = (tmp_param, 2 if feature <= 3 else 1, 5 if feature <= 3 else 4) if write_action4s: strings = [(lang_id, grfstrings.get_translation(string, lang_id)) for lang_id in grfstrings.get_translations(string)] # Sort the strings for deterministic ordering and prepend the default language strings = [(0x7F, grfstrings.get_translation(string))] + sorted( strings, key=lambda lang_text: lang_text[0]) for lang_id, text in strings: if mod is not None: act6 = action6.Action6() act6.modify_bytes(*mod) actions.append(act6) actions.append(Action4(feature, lang_id, size, id_val, [text])) action6.free_parameters.restore() return (id_val, actions)