Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/retro_data_structures/formats/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
from retro_data_structures.formats.msbt import Msbt
from retro_data_structures.formats.pak import Pak
from retro_data_structures.formats.room import Room
from retro_data_structures.formats.rule import RULE, RuleSet
from retro_data_structures.formats.sand import SAND, Sand
from retro_data_structures.formats.savw import SAVW, Savw
from retro_data_structures.formats.scan import SCAN, Scan
Expand Down Expand Up @@ -79,6 +80,7 @@
"SWHC": SWHC,
"AGSC": AGSC,
"ATBL": ATBL,
"RULE": RULE,
}

ALL_RESOURCE_TYPES = {
Expand Down Expand Up @@ -114,6 +116,7 @@
"SWHC": Swhc,
"AGSC": Agsc,
"ATBL": Atbl,
"RULE": RuleSet,
}


Expand Down
80 changes: 80 additions & 0 deletions src/retro_data_structures/formats/rule.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
from __future__ import annotations

import enum
import typing

import construct

from retro_data_structures.adapters.enum_adapter import EnumAdapter
from retro_data_structures.base_resource import AssetType, BaseResource, Dependency
from retro_data_structures.common_types import AssetId32, FourCC
from retro_data_structures.construct_extensions.alignment import AlignTo
from retro_data_structures.construct_extensions.misc import ErrorWithMessage

if typing.TYPE_CHECKING:
from retro_data_structures.game_check import Game

Check warning on line 15 in src/retro_data_structures/formats/rule.py

View check run for this annotation

Codecov / codecov/patch

src/retro_data_structures/formats/rule.py#L15

Added line #L15 was not covered by tests


class ComparisonOperator(enum.IntEnum):
LessThan = 0
LessThanOrEqualTo = 1
EqualTo = 2
GreaterThanOrEqualTo = 3
GreaterThan = 4


class ValueType(enum.IntEnum):
Bool = 0
Float = 1
Int32 = 2


RuleCondition = construct.Struct(
id=FourCC,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this one too. yonder posted this list which should correspond with one of these:

typedef enum ERuleItem {
    kRI_SZ_MIN = INT_MIN,
    
    kRI_CapacityDarkAmmo = 0x4344414D,
    kRI_RemainingMissile = 0x3F4D534C,
    kRI_PercentPowerBomb = 0x2550424D,
    kRI_PercentLightAmmo = 0x254C414D,
    kRI_PercentDarkAmmo = 0x2544414D,
    kRI_PercentMissile = 0x254D534C,
    kRI_RemainingHealth = 0x3F484C54,
    kRI_RemainingDarkBeam = 0x3F44424D, // Dark Ammo; checks if you have Dark Beam
    kRI_RemainingLightBeam = 0x3F4C424D, // Light Ammo; checks if you have Light Beam
    kRI_AmountLightAmmo = 0x414C414D,
    kRI_AmountDarkAmmo = 0x4144414D,
    kRI_RemainingPowerBomb = 0x3F50424D,
    kRI_AmountHealth = 0x41484C54,
    kRI_AmountMissile = 0x414D534C,
    kRI_Always = 0x414C5753,
    kRI_AmountPowerBomb = 0x4150424D,
    kRI_KonstWhenAnnihilatorBeam = 0x4B574142,
    kRI_HasDarkBeam = 0x4844424D,
    kRI_CapacityMissile = 0x434D534C,
    kRI_CapacityLightAmmo = 0x434C414D,
    kRI_CapacityPowerBomb = 0x4350424D,
    kRI_Unk_KFCH = 0x4B464348,
    kRI_HasLightBeam = 0x484C424D,
    kRI_KonstWhenAnyBeam = 0x4B4C4442,
    kRI_KonstWhenLightBeam = 0x4B574C42,
    kRI_KonstWhenDarkBeam = 0x4B574442,
    kRI_Unk_KWCB = 0x4B574342,
    kRI_Unk_KWKB = 0x4B574B42,
    kRI_Unk_KWPB = 0x4B575042,
    kRI_Unk_KWMS = 0x4B574D53,
    kRI_PercentHealth = 0x50484C54,
    
    kRI_SZ_MAX = INT_MAX
} PACK ERuleItem_t;

static_assert(alignof(ERuleItem_t) == 4);
static_assert(sizeof(ERuleItem_t) == sizeof(int32_t));

typedef enum ERuleType {
    kRT_SZ_MIN = INT_MIN,
    
    kRT_Bool = 0,
    kRT_Float,
    kRT_Int32,
    
    kRT_SZ_MAX = INT_MAX
} PACK ERuleType_t;

static_assert(alignof(ERuleType_t) == 4);
static_assert(sizeof(ERuleType_t) == sizeof(int32_t));

operator=EnumAdapter(ComparisonOperator, construct.Int8ub),
value_type=EnumAdapter(ValueType, construct.Int8ub),
value=construct.Switch(
construct.this.value_type,
{
ValueType.Bool: construct.Int32ub,
ValueType.Float: construct.Float32b,
ValueType.Int32: construct.Int32ub,
},
default=ErrorWithMessage(lambda ctx: f"Unknown type: {ctx.value_type}"),
),
)

Action = construct.Struct(
id=FourCC,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we really oughta make an enum out of this

properties=construct.PrefixedArray(construct.Int8ub, construct.Int32ub),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screenshot_20250410_205841_Discord
might be more like...

def rebuild_action_count(ctx):
    if ctx.prop3 != 0:
        return 3
    if ctx.prop2 != 1:
        return 2
    return 1
count=construct.Rebuild(construct.Int8ub, rebuild_action_count),
prop1=construct.Float32b,
prop2=construct.If(construct.this.count >= 2, construct.Int32ub),
prop3=construct.If(construct.this.count >= 3, construct.Int32ub),

?

)

RuleSetRule = construct.Struct(
conditions=construct.PrefixedArray(construct.Int16ub, RuleCondition),
actions=construct.PrefixedArray(construct.Int16ub, Action),
)

RULE = construct.Struct(
magic=construct.Const(b"RULE"),
version=construct.Const(1, construct.Int8ub),
parent_rule=AssetId32,
rules=construct.PrefixedArray(
construct.Int16ub,
RuleSetRule,
),
_align=AlignTo(32),
end=construct.Terminated,
)


class RuleSet(BaseResource):
@classmethod
def resource_type(cls) -> AssetType:
return "SAND"

Check warning on line 73 in src/retro_data_structures/formats/rule.py

View check run for this annotation

Codecov / codecov/patch

src/retro_data_structures/formats/rule.py#L73

Added line #L73 was not covered by tests
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this right?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question. Don't remember.


@classmethod
def construct_class(cls, target_game: Game) -> construct.Construct:
return RULE

def dependencies_for(self) -> typing.Iterator[Dependency]:
yield Dependency("RULE", self._raw.parent_rule)

Check warning on line 80 in src/retro_data_structures/formats/rule.py

View check run for this annotation

Codecov / codecov/patch

src/retro_data_structures/formats/rule.py#L80

Added line #L80 was not covered by tests
21 changes: 21 additions & 0 deletions tests/formats/test_rule.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from __future__ import annotations

from typing import TYPE_CHECKING

from tests import test_lib

from retro_data_structures.formats import RuleSet

if TYPE_CHECKING:
from retro_data_structures.asset_manager import AssetManager
from retro_data_structures.base_resource import AssetId


def test_compare_p2(prime2_asset_manager: AssetManager, rule_asset_id: AssetId) -> None:
resource = prime2_asset_manager.get_raw_asset(rule_asset_id)
decoded = RuleSet.parse(resource.data, target_game=prime2_asset_manager.target_game)
encoded = decoded.build()

decoded2 = RuleSet.parse(encoded, target_game=prime2_asset_manager.target_game)

assert test_lib.purge_hidden(decoded2.raw) == test_lib.purge_hidden(decoded.raw)
Loading