Exemple #1
0
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
Exemple #2
0
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
Exemple #3
0
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