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_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_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