def parse_graphics_block(graphics_block, feature, id, size, is_livery_override = False): """ Parse a graphics block (or livery override) into a list of actions, mainly action3 @param graphics_block: Graphics-block to parse @type graphics_block: L{GraphicsBlock} @param feature: Feature of the associated item @type feature: C{int} @param id: ID of the associated item @type id: L{Expression} @param size: Size of the associated item (relevant for houses only) @type size: L{ConstantNumeric} or C{None} @param is_livery_override: Whether this is a livery override instead of a normal graphics block @type is_livery_override: C{bool} @return: The resulting list of actions @rtype: L{BaseAction} """ action_list = action2real.create_spriteset_actions(graphics_block) if feature == 0x07: # Multi-tile houses need more work size_bit = size.value if size is not None else 0 for i, tile in enumerate(house_tiles[size_bit]): tile_id = id if i == 0 else expression.BinOp(nmlop.ADD, id, expression.ConstantNumeric(i, id.pos), id.pos).reduce() action_list.extend(parse_graphics_block_single_id(graphics_block, feature, tile_id, is_livery_override, tile, id)) else: action_list.extend(parse_graphics_block_single_id(graphics_block, feature, id, is_livery_override)) return action_list
def 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_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