Skip to content

Instantly share code, notes, and snippets.

@Dragorn421
Last active July 14, 2022 14:23
Show Gist options
  • Save Dragorn421/b91a3a9367454de46859705bd80ebdac to your computer and use it in GitHub Desktop.
Save Dragorn421/b91a3a9367454de46859705bd80ebdac to your computer and use it in GitHub Desktop.
OoT64 decomp: (wip) tie cutscene cmd ids for npc actions to appropriate actors
import os
import re
def failimpl(*args):
print("! fail() vvvvv")
print(*args)
print("! fail() ^^^^^")
def fail(*args):
failimpl(*args)
exit()
def raisefail(*args):
failimpl(*args)
raise Exception()
pattern_actor_entry = re.compile(
r"(ACTOR_[^,]*),\s*"
+ r"\{[^}]*\},\s*"
+ r"\{[^}]*\},\s*"
+ r"(0x[0-9A-Fa-f]+|[0-9]+)"
)
def get_actors_used_in_scene(assets_scene_dir):
"""Returns a list of tuples ("ACTOR_...", params)"""
actors = []
for root, dirs, files in os.walk(assets_scene_dir):
for name in files:
if "room" in name and name.endswith(".c"):
file = os.path.join(root, name)
with open(file) as f:
contents = f.read()
for m in pattern_actor_entry.finditer(contents):
actor_id = m.group(1)
params_str = m.group(2)
params_int = int(params_str, 0)
actors.append((actor_id, params_int))
return actors
# eg: ACTOR_DEMO_KANKYO -> DEMO_KANKYO
def get_actor_name(actor_id):
if not actor_id.startswith("ACTOR_"):
raisefail('not actor_id.startswith("ACTOR_")', "actor_id =", actor_id)
actor_name = actor_id[len("ACTOR_") :]
return actor_name
# eg: ACTOR_DEMO_KANKYO -> ovl_Demo_Kankyo
def get_actor_ovl_name(actor_id):
actor_name = get_actor_name(actor_id)
actor_name_parts = actor_name.split("_")
for i in range(len(actor_name_parts)):
actor_name_part = actor_name_parts[i]
upper_part_length = 1
# a guess at why ovl_Demo_6K is not ovl_Demo_6k
while actor_name_part[upper_part_length - 1] in "0123456789":
upper_part_length += 1
actor_name_part = (
actor_name_part[:upper_part_length].upper()
+ actor_name_part[upper_part_length:].lower()
)
actor_name_parts[i] = actor_name_part
actor_ovl_name = "ovl_" + "_".join(actor_name_parts)
return actor_ovl_name
# eg: ACTOR_DEMO_KANKYO -> z_demo_kankyo
def get_actor_z_name(actor_id):
actor_name = get_actor_name(actor_id)
actor_z_name = "z_" + actor_name.lower()
return actor_z_name
# eg: ACTOR_DEMO_KANKYO -> src/overlays/actors/ovl_Demo_Kankyo/z_demo_kankyo.c
def get_actor_source_path(actor_id):
if actor_id == "ACTOR_EN_A_OBJ":
return "src/code/z_en_a_keep.c"
elif actor_id == "ACTOR_EN_ITEM00":
return "src/code/z_en_item00.c"
else:
actor_ovl_name = get_actor_ovl_name(actor_id)
actor_z_name = get_actor_z_name(actor_id)
return f"src/overlays/actors/{actor_ovl_name}/{actor_z_name}.c"
pattern_npc_actions_indexing = re.compile(r"npcActions\[([^\]]*)\]")
pattern_npc_actions_used_no_indexing = re.compile(r"npcActions[^\[]")
def get_actor_action_indices_used(actor_id, params):
actor_source_path = get_actor_source_path(actor_id)
with open(actor_source_path) as f:
contents = f.read()
npc_actions_used_no_indexing_lines = []
for m in pattern_npc_actions_used_no_indexing.finditer(contents):
line_start_index = contents.rfind("\n", 0, m.start()) + 1
line_end_index = contents.find("\n", m.start())
if line_end_index == -1:
line_end_index = len(contents)
line = contents[line_start_index:line_end_index]
npc_actions_used_no_indexing_lines.append(line)
if npc_actions_used_no_indexing_lines:
fail(
"The actor uses npcActions without indexing into it",
"\nactor_id =",
actor_id,
"\nactor_source_path =",
actor_source_path,
"\nnpc_actions_used_no_indexing_lines =",
npc_actions_used_no_indexing_lines,
)
if actor_id == "ACTOR_DEMO_KANKYO":
# DEMOKANKYO_ROCK_1 - DEMOKANKYO_ROCK_5
if params >= 2 and params <= 6:
return {params - 2}
else:
return set()
if actor_id == "ACTOR_DEMO_EFFECT":
effect_type_to_action_index = {
0x13: 1, # DEMO_EFFECT_JEWEL_KOKIRI
0x14: 1, # DEMO_EFFECT_JEWEL_GORON
0x15: 1, # DEMO_EFFECT_JEWEL_ZORA
0x09: 6, # DEMO_EFFECT_MEDAL_FIRE
0x0A: 6, # DEMO_EFFECT_MEDAL_WATER
0x0B: 6, # DEMO_EFFECT_MEDAL_FOREST
0x0C: 6, # DEMO_EFFECT_MEDAL_SPIRIT
0x0D: 6, # DEMO_EFFECT_MEDAL_SHADOW
0x0E: 6, # DEMO_EFFECT_MEDAL_LIGHT
0x17: 6, # DEMO_EFFECT_LIGHTARROW
0x12: 7, # DEMO_EFFECT_LIGHT
0x04: 0, # DEMO_EFFECT_GOD_LGT_DIN
0x05: 1, # DEMO_EFFECT_GOD_LGT_NAYRU
0x06: 2, # DEMO_EFFECT_GOD_LGT_FARORE
0x11: 4, # DEMO_EFFECT_LIGHTRING_TRIFORCE
0x08: 3, # DEMO_EFFECT_TRIFORCE_SPOT
0x16: 2, # DEMO_EFFECT_DUST
}
effect_type = params & 0xFF
if effect_type not in effect_type_to_action_index:
return set()
else:
return {effect_type_to_action_index[effect_type]}
if actor_id == "ACTOR_EN_ZL2":
return {0}
if actor_id == "ACTOR_EN_XC":
# FIXME not sure if the actor uses both at the same time, EnXc is complicated
return {0, 4}
if actor_id == "ACTOR_DEMO_6K":
if params == 0:
return {1}
elif params == 1 or params in {3, 4, 5, 6, 7, 8}:
return {6}
elif params in {14, 15, 16, 17, 18, 19}:
return {params - 14}
else:
return set()
if actor_id == "ACTOR_EN_OWL":
# FIXME EnOwl only uses 7 but I'm not sure it always uses npcActions regardless of params
# (this is also true for any npcActions found with pattern_npc_actions_indexing)
return {7}
if actor_id == "ACTOR_DEMO_IM":
# FIXME same as EnXc. code also looks somewhat copypasted
return {5, 6}
if actor_id == "ACTOR_EN_RU2":
# FIXME same as EnXc.
return {3, 6}
if actor_id == "ACTOR_EN_IK":
# FIXME same as EnOwl.
return {4}
if actor_id == "ACTOR_EN_NB":
# FIXME same as EnXc.
return {1, 6}
if actor_id == "ACTOR_EN_RL":
# FIXME same as EnXc.
return {0, 6}
if actor_id == "ACTOR_DEMO_SA":
# FIXME same as EnXc.
return {1, 4, 6}
if actor_id == "ACTOR_DEMO_DU":
# FIXME same as EnXc.
return {2, 6}
if actor_id == "ACTOR_DEMO_GO":
if params == 0:
return {3}
elif params == 1:
return {4}
else:
return {5}
if actor_id == "ACTOR_EN_RU1":
# FIXME same as EnOwl.
return {3}
if actor_id == "ACTOR_DEMO_EC":
# FIXME same as EnXc.
return {6, 7}
if actor_id == "ACTOR_BG_SPOT01_IDOHASHIRA":
# FIXME same as EnOwl.
return {2}
if actor_id == "ACTOR_OBJECT_KANKYO":
# FIXME same as EnXc.
return {0, 1, 2, 3, 4, 5, 6}
if actor_id == "ACTOR_EN_TR":
if params == 0: # Koume
return {3}
elif params == 1: # Kotake
return {2}
else: # crash
return set()
if actor_id == "ACTOR_DEMO_EXT":
return {5}
if actor_id == "ACTOR_DEMO_GT":
# FIXME same as EnXc.
return {1, 2, 3, 4, 5, 6, 7, 9}
if actor_id == "ACTOR_DEMO_IK":
# FIXME same as EnXc.
return {4, 5, 6, 7}
actor_action_indices = set()
for m in pattern_npc_actions_indexing.finditer(contents):
actor_action_index_str = m.group(1)
try:
actor_action_index_int = int(actor_action_index_str, 0)
except ValueError:
fail(
"npcActions isn't indexed with an integer",
"\nactor_id =",
actor_id,
"\nactor_source_path =",
actor_source_path,
"\nactor_action_index_str =",
actor_action_index_str,
)
actor_action_indices.add(actor_action_index_int)
return actor_action_indices
# from the switch in Cutscene_ProcessCommands
# key is npcActions index, values are cases in the switch that use that npcActionsIndex
cutscene_npc_action_command_ids_by_actor_action_index = {
0: [
# 15, # apparently unused
# 17, # apparently unused
18,
23,
34,
39,
46,
76,
85,
93,
105,
107,
110,
119,
123,
138,
139,
144,
],
}
if __name__ == "__main__":
for (
actor_action_index,
cutscene_npc_action_command_ids,
) in cutscene_npc_action_command_ids_by_actor_action_index.items():
for cutscene_npc_action_command_id in cutscene_npc_action_command_ids:
cutscene_command_searches = [
f"CS_NPC_ACTION_LIST({cutscene_npc_action_command_id},",
f"CS_NPC_ACTION_LIST(0x{cutscene_npc_action_command_id:03X},",
]
relevant_actor_ids = None
for top in ("assets", "src"):
for root, dirs, files in os.walk(top):
for name in files:
if name.endswith(".c"):
file = os.path.join(root, name)
with open(file) as f:
lines = f.readlines()
found_cutscene_command_searches = [
cutscene_command_search
for cutscene_command_search in cutscene_command_searches
if any(
cutscene_command_search in line for line in lines
)
]
has_cs_command = len(found_cutscene_command_searches) > 0
if has_cs_command:
if file.startswith("assets/scenes/"):
actors_used_in_scene = get_actors_used_in_scene(
root
)
if relevant_actor_ids is None:
print(
"Initializing relevant_actor_ids =",
relevant_actor_ids,
"from actors_used_in_scene =",
actors_used_in_scene,
)
relevant_actor_ids = set()
for actor_id, params in actors_used_in_scene:
actor_action_indices = (
get_actor_action_indices_used(
actor_id, params
)
)
print(
f"{actor_id}:0x{params:04X} {actor_action_indices}",
end="",
)
if (
actor_action_index
in actor_action_indices
):
relevant_actor_ids.add(actor_id)
print(" x")
else:
print("")
print(
"relevant_actor_ids =", relevant_actor_ids
)
else:
# TODO intersect relevant_actor_ids and actors_used_in_scene but do so in a readable way to make debugging easier
print("xxx")
elif file in {
"src/overlays/actors/ovl_En_Zl1/z_en_zl1_cutscene_data.c",
}:
print(
"Found found_cutscene_command_searches =",
found_cutscene_command_searches,
"\nin file =",
file,
"\nignoring!",
)
else:
fail(
"Found found_cutscene_command_searches =",
found_cutscene_command_searches,
"\nin file =",
file,
"\nbut what to do with it?",
)
if relevant_actor_ids is None:
fail(
"relevant_actor_ids is None",
"\nactor_action_index =",
actor_action_index,
"\ncutscene_npc_action_command_id =",
cutscene_npc_action_command_id,
)
if len(relevant_actor_ids) == 0:
fail(
"len(relevant_actor_ids) == 0",
"\nactor_action_index =",
actor_action_index,
"\ncutscene_npc_action_command_id =",
cutscene_npc_action_command_id,
)
if len(relevant_actor_ids) > 1:
fail(
"len(relevant_actor_ids) > 1",
"\nactor_action_index =",
actor_action_index,
"\ncutscene_npc_action_command_id =",
cutscene_npc_action_command_id,
"\nrelevant_actor_ids =",
relevant_actor_ids,
)
print(
"actor_action_index =",
actor_action_index,
"\ncutscene_npc_action_command_id =",
cutscene_npc_action_command_id,
"\nrelevant_actor_ids = ",
relevant_actor_ids,
)
# TODO this doesn't handle cutscene data in actor overlays
import os
from pprint import pprint
#
#
#
rooms_by_scene = {
"assets/scenes/overworld/spot00/spot00_scene.c": [
"assets/scenes/overworld/spot00/spot00_room_0.c",
],
}
def make_rooms_by_scene():
rooms_by_scene = dict()
for root, dirs, files in os.walk("assets/scenes"):
if not files:
continue
if root == "assets/scenes/test_levels/syotes":
# early map, the room has no header, there's no cutscene data there anyway
continue
scene_c_files = [file for file in files if file.endswith("_scene.c")]
assert (
len(scene_c_files) == 1
), f"Expected exactly one scene .c file, found {scene_c_files}"
scene_c_file = os.path.join(root, scene_c_files[0])
rooms_c_files = [
os.path.join(root, file)
for file in files
if file.endswith(".c") and "_room_" in file
]
assert len(rooms_c_files) > 0, "Expected at least one room .c file, found none"
# Note: correct order isn't guaranteed, but doesn't matter here
rooms_by_scene[scene_c_file] = rooms_c_files
return rooms_by_scene
rooms_by_scene = make_rooms_by_scene()
pprint(rooms_by_scene)
#
#
#
layers_by_scene = {
"assets/scenes/overworld/spot00/spot00_scene.c": [
"spot00_sceneCommands", # main header
"spot00_sceneSet_011690", # first alternate header
"spot00_sceneSet_0112D0",
None, # NULL
"spot00_sceneSet_011AD0",
"spot00_sceneSet_011B70",
"spot00_sceneSet_011CC0",
"spot00_sceneSet_011D60",
"spot00_sceneSet_011E60",
"spot00_sceneSet_011F20",
"spot00_sceneSet_012020",
"spot00_sceneSet_0120E0",
"spot00_sceneSet_012180",
],
}
cutscene_by_layer_by_scene = {
"assets/scenes/overworld/spot00/spot00_scene.c": {
"spot00_sceneSet_011B70": "spot00_sceneCutsceneData_008494",
},
}
cutscene_npc_action_list_command_ids_by_cutscene = {
"spot00_sceneCutsceneData_008494": {
0x010,
0x012,
},
}
def make_scene_data():
layers_by_scene = dict()
cutscene_by_layer_by_scene = dict()
cutscene_npc_action_list_command_ids_by_cutscene = dict()
for scene in rooms_by_scene.keys():
with open(scene) as f:
lines = f.readlines()
# layers_by_scene
alternate_headers_start_line_i = None
alternate_headers_end_line_i = None
"""
SceneCmd* hakaana_ouke_sceneAlternateHeaders0x000060[] = {
NULL,
NULL,
NULL,
hakaana_ouke_sceneSet_002280,
hakaana_ouke_sceneSet_002390,
};
"""
for i, line in enumerate(lines):
if "SceneCmd*" in line and "_sceneAlternateHeaders" in line:
alternate_headers_start_line_i = i
if alternate_headers_start_line_i is not None and ";" in line:
alternate_headers_end_line_i = i
break
if alternate_headers_start_line_i is None:
alternate_headers = []
else:
assert alternate_headers_end_line_i is not None
alternate_headers = [
None if header == "NULL" else header
for header in (
line.strip().removesuffix(",")
for line in lines[
alternate_headers_start_line_i
+ 1 : alternate_headers_end_line_i
]
)
]
assert len(alternate_headers) < 100 # sanity check
headers = []
"""
SceneCmd hakaana_ouke_sceneCommands[] = {
SCENE_CMD_ALTERNATE_HEADER_LIST(hakaana_ouke_sceneAlternateHeaders0x000060),
SCENE_CMD_SOUND_SETTINGS(3, 19, 24),
SCENE_CMD_ROOM_LIST(3, hakaana_ouke_sceneRoomList0x0000B4),
SCENE_CMD_TRANSITION_ACTOR_LIST(2, hakaana_ouke_sceneTransitionActorList_000094),
SCENE_CMD_MISC_SETTINGS(0x00, 0x00000002),
SCENE_CMD_COL_HEADER(&hakaana_ouke_sceneCollisionHeader_002250),
SCENE_CMD_ENTRANCE_LIST(hakaana_ouke_sceneEntranceList0x0000CC),
SCENE_CMD_SPAWN_LIST(2, hakaana_ouke_sceneStartPositionList0x000074),
SCENE_CMD_SKYBOX_SETTINGS(0, 0, true),
SCENE_CMD_EXIT_LIST(hakaana_ouke_sceneExitList_0000D0),
SCENE_CMD_ENV_LIGHT_SETTINGS(4, hakaana_ouke_sceneLightSettings0x0000D4),
SCENE_CMD_END(),
};
"""
for line in lines:
if "SceneCmd " in line:
header = line.split(" ")[1].removesuffix("[]")
if header not in alternate_headers:
headers.append(header)
assert (
len(headers) == 1
), f"Expected exactly one non-alternate header, found {headers}"
layers = headers + alternate_headers
layers_by_scene[scene] = layers
# cutscene_by_layer_by_scene
cutscene_by_layer_by_scene[scene] = dict()
for layer in layers:
if not layer:
continue
"""
SceneCmd hakaana_ouke_sceneSet_002280[] = {
SCENE_CMD_SOUND_SETTINGS(3, 19, 24),
SCENE_CMD_ROOM_LIST(3, hakaana_ouke_sceneRoomList0x002310),
SCENE_CMD_TRANSITION_ACTOR_LIST(2, hakaana_ouke_sceneTransitionActorList_0022F0),
SCENE_CMD_MISC_SETTINGS(0x00, 0x00000000),
SCENE_CMD_COL_HEADER(&hakaana_ouke_sceneCollisionHeader_002250),
SCENE_CMD_ENTRANCE_LIST(hakaana_ouke_sceneEntranceList0x002328),
SCENE_CMD_SPAWN_LIST(1, hakaana_ouke_sceneStartPositionList0x0022E0),
SCENE_CMD_SKYBOX_SETTINGS(0, 0, false),
SCENE_CMD_EXIT_LIST(hakaana_ouke_sceneExitList_00232C),
SCENE_CMD_ENV_LIGHT_SETTINGS(4, hakaana_ouke_sceneLightSettings0x002330),
SCENE_CMD_CUTSCENE_DATA(gSunSongGraveSunSongTeachCs),
SCENE_CMD_END(),
};
"""
found_layer_header = False
cutscene = None
for i, line in enumerate(lines):
if "SceneCmd " in line and layer in line:
found_layer_header = True
if found_layer_header:
if "SCENE_CMD_CUTSCENE_DATA" in line:
assert cutscene is None, "Duplicate SCENE_CMD_CUTSCENE_DATA"
cutscene = (
line.strip()
.removeprefix("SCENE_CMD_CUTSCENE_DATA(")
.removesuffix("),")
)
if ";" in line:
break
assert found_layer_header
if cutscene:
cutscene_by_layer_by_scene[scene][layer] = cutscene
# cutscene_npc_action_list_command_ids_by_cutscene
"""
CutsceneData bdan_sceneCutsceneData_0130A0[] = {
CS_BEGIN_CUTSCENE(14, 1299),
CS_PLAYER_ACTION_LIST(3),
CS_PLAYER_ACTION(5, 0, 272, 0x0000, 0xC000, 0x0000, -1085, -1025, -3347, -1085, -1025, -3347,
1.13930371975e-29f, 0.00000000000e+00f, 1.40129846432e-45f),
CS_PLAYER_ACTION(3, 272, 292, 0x0000, 0xC000, 0x0000, -1085, -1025, -3347, -1085, -1025, -3347,
1.13930371975e-29f, 0.00000000000e+00f, 1.40129846432e-45f),
CS_PLAYER_ACTION(5, 292, 777, 0x0000, 0xC000, 0x0000, -1085, -1025, -3347, -1085, -1025, -3347,
1.13930371975e-29f, 0.00000000000e+00f, 1.40129846432e-45f),
CS_MISC_LIST(1),
CS_MISC(0x000C, 330, 627, 0, 0, 0, -64, 50, 0, -64, 50, 0, 0, 0),
CS_NPC_ACTION_LIST(0x042, 3),
CS_NPC_ACTION(1, 0, 40, 0x0000, 0x4000, 0x0000, -1352, -969, -3341, -1352, -969, -3341, 0.00000000000e+00f,
0.00000000000e+00f, 1.40129846432e-45f),
CS_NPC_ACTION(2, 40, 213, 0x0000, 0x4000, 0x0000, -1352, -969, -3341, -1360, -969, -3343, 0.00000000000e+00f,
0.00000000000e+00f, 1.40129846432e-45f),
CS_NPC_ACTION(3, 213, 1000, 0x0000, 0x4000, 0x0000, -1360, -969, -3343, -1360, -969, -3343, 0.00000000000e+00f,
0.00000000000e+00f, 1.40129846432e-45f),
CS_NPC_ACTION_LIST(0x030, 1),
CS_NPC_ACTION(2, 0, 90, 0x0000, 0x0000, 0x0000, -1360, -963, -3343, -1360, -963, -3343, 0.00000000000e+00f,
0.00000000000e+00f, 0.00000000000e+00f),
CS_NPC_ACTION_LIST(0x030, 2),
CS_NPC_ACTION(2, 90, 211, 0x0000, 0x0000, 0x0000, -1352, -922, -3341, -1352, -922, -3341, 0.00000000000e+00f,
0.00000000000e+00f, 0.00000000000e+00f),
CS_NPC_ACTION(6, 211, 311, 0x0000, 0x0000, 0x0000, -1352, -922, -3341, -1352, -922, -3341, 0.00000000000e+00f,
0.00000000000e+00f, 0.00000000000e+00f),
CS_NPC_ACTION_LIST(0x03E, 3),
CS_NPC_ACTION(4, 0, 210, 0x0000, 0x0000, 0x0000, -1065, -972, -3305, -1065, -978, -3305, 0.00000000000e+00f,
-2.85714287311e-02f, 0.00000000000e+00f),
CS_NPC_ACTION(4, 210, 220, 0x8000, 0x0000, 0x0000, -1065, -978, -3305, -1065, -973, -3344, 0.00000000000e+00f,
5.00000000000e-01f, 0.00000000000e+00f),
CS_NPC_ACTION(4, 220, 410, 0x0000, 0x0000, 0x0000, -1065, -973, -3344, -1065, -976, -3344, 0.00000000000e+00f,
-1.57894734293e-02f, 0.00000000000e+00f),
CS_TEXT_LIST(6),
CS_TEXT_NONE(0, 162),
CS_TEXT_DISPLAY_TEXTBOX(0x4050, 162, 211, 0, 0xFFFF, 0xFFFF),
CS_TEXT_NONE(211, 232),
CS_TEXT_DISPLAY_TEXTBOX(0x4051, 232, 241, 0, 0xFFFF, 0xFFFF),
CS_TEXT_NONE(241, 247),
CS_TEXT_DISPLAY_TEXTBOX(0x4052, 247, 299, 0, 0xFFFF, 0xFFFF),
CS_PLAY_BGM_LIST(1),
CS_PLAY_BGM(35, 112, 113, 0, 0, 0, -57, 177, 0, -57, 177),
CS_CAM_POS_LIST(0, 1176),
CS_CAM_POS(CS_CMD_CONTINUE, 0x00, 0, 41.906601f, -1390, -948, -3339, 0x00C6),
CS_CAM_POS(CS_CMD_CONTINUE, 0x00, 0, 40.706596f, -1390, -948, -3339, 0x00C8),
CS_CAM_POS(CS_CMD_CONTINUE, 0x00, 0, 40.706596f, -1390, -948, -3339, 0x00D7),
CS_CAM_POS(CS_CMD_CONTINUE, 0x00, 0, 40.706596f, -1418, -938, -3337, 0x00E8),
CS_CAM_POS(CS_CMD_CONTINUE, 0x00, 0, 45.106609f, -1418, -938, -3337, 0x00EA),
CS_CAM_POS(CS_CMD_CONTINUE, 0x00, 0, 45.106609f, -1418, -938, -3337, 0x013D),
CS_CAM_POS(CS_CMD_CONTINUE, 0x00, 0, 45.106609f, -1418, -938, -3337, 0x013F),
CS_CAM_POS(CS_CMD_STOP, 0x00, 0, 45.106609f, -1418, -938, -3337, 0x006D),
CS_CAM_POS_LIST(91, 1270),
CS_CAM_POS(CS_CMD_CONTINUE, 0x00, 0, 44.906612f, -1319, -934, -3343, 0x00C6),
CS_CAM_POS(CS_CMD_CONTINUE, 0x00, 0, 44.706612f, -1319, -936, -3344, 0x00C8),
CS_CAM_POS(CS_CMD_CONTINUE, 0x00, 0, 44.706612f, -1319, -936, -3344, 0x00D7),
CS_CAM_POS(CS_CMD_CONTINUE, 0x00, 0, 44.706612f, -1319, -936, -3344, 0x00E8),
CS_CAM_POS(CS_CMD_CONTINUE, 0x00, 0, 44.706612f, -1326, -904, -3342, 0x00EA),
CS_CAM_POS(CS_CMD_CONTINUE, 0x00, 0, 60.906673f, -1326, -904, -3342, 0x013D),
CS_CAM_POS(CS_CMD_CONTINUE, 0x00, 0, 60.906673f, -1326, -904, -3342, 0x013F),
CS_CAM_POS(CS_CMD_CONTINUE, 0x00, 0, 60.906673f, -1326, -904, -3342, 0x014E),
CS_CAM_POS(CS_CMD_CONTINUE, 0x00, 0, 60.906673f, -1326, -904, -3342, 0x015F),
CS_CAM_POS(CS_CMD_CONTINUE, 0x00, 0, 60.906673f, -1326, -904, -3342, 0x0161),
CS_CAM_POS(CS_CMD_STOP, 0x00, 0, 60.906673f, -1326, -1024, -3342, 0x652E),
CS_CAM_POS_LIST(211, 332),
CS_CAM_POS(CS_CMD_CONTINUE, 0x00, 0, 30.306555f, -1471, -819, -3149, 0x00C6),
CS_CAM_POS(CS_CMD_CONTINUE, 0x00, 0, 30.306555f, -1471, -819, -3149, 0x00C8),
CS_CAM_POS(CS_CMD_CONTINUE, 0x00, 0, 30.306555f, -1471, -819, -3149, 0x00D7),
CS_CAM_POS(CS_CMD_CONTINUE, 0x00, 0, 30.306555f, -1471, -819, -3149, 0x00E8),
CS_CAM_POS(CS_CMD_STOP, 0x00, 0, 30.306555f, -1471, -819, -3149, 0x00EA),
CS_CAM_FOCUS_POINT_LIST(0, 1205),
CS_CAM_FOCUS_POINT(CS_CMD_CONTINUE, 0x00, 30, 40.706596f, -1295, -1003, -3352, 0x00C6),
CS_CAM_FOCUS_POINT(CS_CMD_CONTINUE, 0x00, 50, 40.706596f, -1296, -1003, -3352, 0x00C8),
CS_CAM_FOCUS_POINT(CS_CMD_CONTINUE, 0x00, 20, 40.706596f, -1296, -1003, -3352, 0x00D7),
CS_CAM_FOCUS_POINT(CS_CMD_CONTINUE, 0x00, 15, 45.106609f, -1314, -969, -3346, 0x00E8),
CS_CAM_FOCUS_POINT(CS_CMD_CONTINUE, 0x00, 30, 45.106609f, -1313, -970, -3346, 0x00EA),
CS_CAM_FOCUS_POINT(CS_CMD_CONTINUE, 0x00, 1000, 45.106609f, -1313, -969, -3346, 0x013D),
CS_CAM_FOCUS_POINT(CS_CMD_CONTINUE, 0x00, 30, 45.106609f, -1313, -970, -3346, 0x013F),
CS_CAM_FOCUS_POINT(CS_CMD_STOP, 0x00, 30, 45.106609f, -1313, -970, -3346, 0x006D),
CS_CAM_FOCUS_POINT_LIST(91, 1299),
CS_CAM_FOCUS_POINT(CS_CMD_CONTINUE, 0x00, 30, 44.706612f, -1405, -988, -3343, 0x00C6),
CS_CAM_FOCUS_POINT(CS_CMD_CONTINUE, 0x00, 7, 44.706612f, -1406, -989, -3344, 0x00C8),
CS_CAM_FOCUS_POINT(CS_CMD_CONTINUE, 0x00, 7, 44.706612f, -1406, -989, -3344, 0x00D7),
CS_CAM_FOCUS_POINT(CS_CMD_CONTINUE, 0x00, 7, 44.706612f, -1406, -989, -3344, 0x00E8),
CS_CAM_FOCUS_POINT(CS_CMD_CONTINUE, 0x00, 7, 60.906673f, -1393, -978, -3342, 0x00EA),
CS_CAM_FOCUS_POINT(CS_CMD_CONTINUE, 0x00, 30, 60.906673f, -1393, -977, -3342, 0x013D),
CS_CAM_FOCUS_POINT(CS_CMD_CONTINUE, 0x00, 30, 60.906673f, -1393, -977, -3342, 0x013F),
CS_CAM_FOCUS_POINT(CS_CMD_CONTINUE, 0x00, 1000, 60.906673f, -1393, -977, -3342, 0x014E),
CS_CAM_FOCUS_POINT(CS_CMD_CONTINUE, 0x00, 30, 60.906673f, -1393, -977, -3342, 0x015F),
CS_CAM_FOCUS_POINT(CS_CMD_CONTINUE, 0x00, 30, 60.906673f, -1393, -977, -3342, 0x0161),
CS_CAM_FOCUS_POINT(CS_CMD_STOP, 0x00, 30, 60.906673f, -1401, -1094, -3347, 0x652E),
CS_CAM_FOCUS_POINT_LIST(211, 361),
CS_CAM_FOCUS_POINT(CS_CMD_CONTINUE, 0x00, 30, 30.306555f, -1426, -857, -3190, 0x00C6),
CS_CAM_FOCUS_POINT(CS_CMD_CONTINUE, 0x00, 30, 30.306555f, -1426, -857, -3190, 0x00C8),
CS_CAM_FOCUS_POINT(CS_CMD_CONTINUE, 0x00, 30, 30.306555f, -1426, -857, -3190, 0x00D7),
CS_CAM_FOCUS_POINT(CS_CMD_CONTINUE, 0x00, 30, 30.306555f, -1426, -857, -3190, 0x00E8),
CS_CAM_FOCUS_POINT(CS_CMD_STOP, 0x00, 30, 30.306555f, -1426, -857, -3190, 0x00EA),
CS_END(),
};
"""
cutscene_npc_action_list_command_ids_by_cutscene[cutscene] = set()
found_cutscene = False
for i, line in enumerate(lines):
if "CutsceneData " in line and cutscene in line:
found_cutscene = True
if found_cutscene:
if "CS_NPC_ACTION_LIST" in line:
cmd_id_str = (
line.strip()
.removeprefix("CS_NPC_ACTION_LIST(")
.split(",")[0]
)
cmd_id = int(cmd_id_str, 0)
cutscene_npc_action_list_command_ids_by_cutscene[
cutscene
].add(cmd_id)
if ";" in line:
break
assert found_cutscene
return (
layers_by_scene,
cutscene_by_layer_by_scene,
cutscene_npc_action_list_command_ids_by_cutscene,
)
(
layers_by_scene,
cutscene_by_layer_by_scene,
cutscene_npc_action_list_command_ids_by_cutscene,
) = make_scene_data()
pprint(layers_by_scene)
pprint(cutscene_by_layer_by_scene)
pprint(cutscene_npc_action_list_command_ids_by_cutscene)
#
#
#
layers_by_room = {
"assets/scenes/overworld/spot00/spot00_room_0.c": [
"spot00_room_0Commands",
"spot00_room_0Set_000C50",
"spot00_room_0Set_000770",
None,
"spot00_room_0Set_001120",
"spot00_room_0Set_0011F0",
"spot00_room_0Set_0012C0",
"spot00_room_0Set_001360",
"spot00_room_0Set_001570",
"spot00_room_0Set_0018D0",
"spot00_room_0Set_001920",
"spot00_room_0Set_001C80",
"spot00_room_0Set_001CF0",
]
}
actors_by_layer_by_room = {
"assets/scenes/overworld/spot00/spot00_room_0.c": {
"spot00_room_0Set_0011F0": [
("ACTOR_BG_SPOT00_HANEBASI", 0xFFFF),
("ACTOR_EN_VIEWER", 0x0000),
("ACTOR_EN_VIEWER", 0x0101),
("ACTOR_EN_VIEWER", 0x0202),
("ACTOR_EN_VIEWER", 0x0303),
("ACTOR_EN_VIEWER", 0x0404),
("ACTOR_EN_RIVER_SOUND", 0x0001),
],
},
}
def make_room_data():
layers_by_room = dict()
actors_by_layer_by_room = dict()
for room in (room for rooms in rooms_by_scene.values() for room in rooms):
with open(room) as f:
lines = f.readlines()
# layers_by_room
alternate_headers_start_line_i = None
alternate_headers_end_line_i = None
"""
SceneCmd* hakaana_ouke_room_0AlternateHeaders0x000048[] = {
NULL,
NULL,
NULL,
hakaana_ouke_room_0Set_000130,
hakaana_ouke_room_0Set_0001B0,
};
"""
for i, line in enumerate(lines):
if "SceneCmd*" in line and "AlternateHeaders" in line:
alternate_headers_start_line_i = i
if alternate_headers_start_line_i is not None and ";" in line:
alternate_headers_end_line_i = i
break
if alternate_headers_start_line_i is None:
alternate_headers = []
else:
assert alternate_headers_end_line_i is not None
alternate_headers = [
None if header == "NULL" else header
for header in (
line.strip().removesuffix(",")
for line in lines[
alternate_headers_start_line_i
+ 1 : alternate_headers_end_line_i
]
)
]
assert len(alternate_headers) < 100 # sanity check
headers = []
"""
SceneCmd hakaana_ouke_room_0Commands[] = {
SCENE_CMD_ALTERNATE_HEADER_LIST(hakaana_ouke_room_0AlternateHeaders0x000048),
SCENE_CMD_ECHO_SETTINGS(4),
SCENE_CMD_ROOM_BEHAVIOR(0x00, 0x01, false, false),
SCENE_CMD_SKYBOX_DISABLES(true, true),
SCENE_CMD_TIME_SETTINGS(255, 255, 0),
SCENE_CMD_MESH(&hakaana_ouke_room_0PolygonType0_000110),
SCENE_CMD_OBJECT_LIST(7, hakaana_ouke_room_0ObjectList_00005C),
SCENE_CMD_ACTOR_LIST(10, hakaana_ouke_room_0ActorList_00006C),
SCENE_CMD_END(),
};
"""
for line in lines:
if "SceneCmd " in line:
header = line.split(" ")[1].removesuffix("[]")
if header not in alternate_headers:
headers.append(header)
assert (
len(headers) == 1
), f"Expected exactly one non-alternate header, found {headers}"
layers = headers + alternate_headers
layers_by_room[room] = layers
# actors_by_layer_by_room
actors_by_layer_by_room[room] = dict()
for layer in layers:
if not layer:
continue
"""
SceneCmd hakaana_ouke_room_0Set_0001B0[] = {
SCENE_CMD_ECHO_SETTINGS(0),
SCENE_CMD_ROOM_BEHAVIOR(0x00, 0x01, false, false),
SCENE_CMD_SKYBOX_DISABLES(false, false),
SCENE_CMD_TIME_SETTINGS(255, 255, 0),
SCENE_CMD_MESH(&hakaana_ouke_room_0PolygonType0_000110),
SCENE_CMD_OBJECT_LIST(4, hakaana_ouke_room_0ObjectList_0001F0),
SCENE_CMD_ACTOR_LIST(3, hakaana_ouke_room_0ActorList_0001F8),
SCENE_CMD_END(),
};
"""
found_layer_header = False
actor_list_length = None
actor_list = None
for i, line in enumerate(lines):
if "SceneCmd " in line and layer in line:
found_layer_header = True
if found_layer_header:
if "SCENE_CMD_ACTOR_LIST" in line:
assert actor_list is None, "Duplicate SCENE_CMD_ACTOR_LIST"
parts = (
line.strip()
.removeprefix("SCENE_CMD_ACTOR_LIST(")
.removesuffix("),")
.split(",")
)
actor_list_length_str = parts[0].strip().removesuffix(",")
actor_list_length = int(actor_list_length_str)
actor_list = parts[1].strip()
if ";" in line:
break
assert found_layer_header
if actor_list:
actors_by_layer_by_room[room][layer] = []
"""
ActorEntry spot00_room_0ActorList_001178[] = {
{ ACTOR_BG_SPOT00_HANEBASI, { 0, -10, 670 }, { 0, 0, 0 }, 0xFFFF },
{ ACTOR_EN_VIEWER, { 2313, -11, 2013 }, { 0, 0, 0 }, 0x0000 },
{ ACTOR_EN_VIEWER, { 2398, -12, 2017 }, { 0, 0, 0 }, 0x0101 },
{ ACTOR_EN_VIEWER, { 2313, -7, 2098 }, { 0, 0, 0 }, 0x0202 },
{ ACTOR_EN_VIEWER, { 2395, -2, 2098 }, { 0, 0, 0 }, 0x0303 },
{ ACTOR_EN_VIEWER, { 2361, -9, 2062 }, { 0, 0, 0 }, 0x0404 },
{ ACTOR_EN_RIVER_SOUND, { -2862, -315, -500 }, { 0, 0, 0 }, 0x0001 },
};
"""
found_actor_list = False
count = None
for i, line in enumerate(lines):
if "ActorEntry " in line and actor_list in line:
assert not found_actor_list
found_actor_list = True
count = 0
elif found_actor_list:
if ";" in line:
break
count += 1
if count <= actor_list_length:
parts = (
line.strip()
.removeprefix("{")
.removesuffix("},")
.strip()
.split()
)
actor_id = parts[0].removesuffix(",")
actor_params_str = parts[-1]
actor_params = int(actor_params_str, 0)
actors_by_layer_by_room[room][layer].append(
(actor_id, actor_params)
)
assert found_actor_list
return (layers_by_room, actors_by_layer_by_room)
(layers_by_room, actors_by_layer_by_room) = make_room_data()
pprint(layers_by_room)
pprint(actors_by_layer_by_room)
#
#
#
# from the switch in Cutscene_ProcessCommands
# key is npcActions index, values are cases in the switch that use that npcActionsIndex
cutscene_npc_action_list_command_ids_by_npc_actions_index = {
0: {15, 17, 18, 23, 34, 39, 46, 76, 85, 93, 105, 107, 110, 119, 123, 138, 139, 144},
1: {14, 16, 24, 35, 40, 48, 64, 68, 70, 78, 80, 94, 116, 118, 120, 125, 131, 141},
2: {25, 36, 41, 50, 67, 69, 72, 74, 81, 106, 117, 121, 126, 132},
3: {29, 37, 42, 51, 53, 63, 65, 66, 75, 82, 108, 127, 133},
4: {30, 38, 43, 47, 54, 79, 83, 128, 135},
5: {44, 55, 77, 84, 90, 129, 136},
6: {31, 52, 57, 58, 88, 115, 130, 137},
7: {49, 60, 89, 111, 114, 134, 142},
8: {62},
9: {143},
}
npc_actions_index_by_cutscene_npc_action_list_command_id = {
cutscene_npc_action_list_command_id: npc_actions_index
for (
npc_actions_index,
cutscene_npc_action_list_command_ids,
) in cutscene_npc_action_list_command_ids_by_npc_actions_index.items()
for cutscene_npc_action_list_command_id in cutscene_npc_action_list_command_ids
}
"""
npc_actions_indices_by_actor_id = {
"ACTOR_EN_ZL2": {0},
"ACTOR_EN_VIEWER": {0, 1},
}
def make_npc_actions_indices_by_actor_id():
npc_actions_indices_by_actor_id = dict()
from . import cs_actor_action_doc_v2
cs_actor_action_doc_v2.
return get_actor_action_indices_used
npc_actions_indices_by_actor_id = make_npc_actions_indices_by_actor_id()
pprint(npc_actions_indices_by_actor_id)
"""
# TODO obviously...
def get_actor_action_indices_used(actor_id, params):
import cs_actor_action_doc_v2
return cs_actor_action_doc_v2.get_actor_action_indices_used(actor_id, params)
"""
actor_ids_by_npc_actions_index = dict()
for actor, npc_actions_indices in npc_actions_indices_by_actor_id.items():
for npc_actions_index in npc_actions_indices:
if npc_actions_index not in actor_ids_by_npc_actions_index:
actor_ids_by_npc_actions_index[npc_actions_index] = set()
actor_ids_by_npc_actions_index[npc_actions_index].add(actor)
"""
actors_by_cutscene_npc_action_list_command_id = {
cutscene_npc_action_list_command_id: list()
for cutscene_npc_action_list_command_id in npc_actions_index_by_cutscene_npc_action_list_command_id.keys()
}
print()
# Tie the data together to fill actors_by_cutscene_npc_action_list_command_id
for scene, rooms in rooms_by_scene.items():
"""
if "spot00" not in scene:
continue
"""
for layer_index, scene_layer in enumerate(layers_by_scene[scene]):
if scene_layer is None:
# scene has no layer, nothing to do for this layer index
continue
cutscene_by_layer = cutscene_by_layer_by_scene[scene]
if scene_layer not in cutscene_by_layer:
# scene layer has no cutscene, nothing to do for this layer
continue
cutscene = cutscene_by_layer[scene_layer]
cutscene_npc_action_list_command_ids = (
cutscene_npc_action_list_command_ids_by_cutscene[cutscene]
)
for room in rooms:
room_layer = layers_by_room[room][layer_index]
assert (
room_layer is not None
), f"Scene {scene} has layer at index {layer_index} but room {room} doesn't"
actors_by_layer = actors_by_layer_by_room[room]
if room_layer not in actors_by_layer:
# room layer has no actors, nothing to do for this room (for this layer)
continue
actors = actors_by_layer[room_layer]
for actor_id, actor_params in actors:
npc_actions_indices = get_actor_action_indices_used(
actor_id, actor_params
)
for npc_action_index in npc_actions_indices:
assert (
npc_action_index
in cutscene_npc_action_list_command_ids_by_npc_actions_index
), f"Unknown npcActions index {npc_action_index}"
common_cutscene_npc_action_list_command_ids = (
cutscene_npc_action_list_command_ids
& cutscene_npc_action_list_command_ids_by_npc_actions_index[
npc_action_index
]
)
if common_cutscene_npc_action_list_command_ids:
print(
scene,
layer_index,
scene_layer,
room,
room_layer,
actor_id,
npc_action_index,
common_cutscene_npc_action_list_command_ids,
)
for (
common_cutscene_npc_action_list_command_id
) in common_cutscene_npc_action_list_command_ids:
# TODO don't stringify actor_params, temporary for debugging
actors_by_cutscene_npc_action_list_command_id[
common_cutscene_npc_action_list_command_id
].append(
(
actor_id,
f"0x{actor_params:04X}",
os.path.join(*scene.split(os.path.sep)[2:4]),
npc_action_index,
)
)
pprint(actors_by_cutscene_npc_action_list_command_id)
import csv
with open("cs_npc_action_cmd_ids.csv", "w") as f:
csvw = csv.writer(f, dialect="unix")
csvw.writerow(["Cs Cmd ID", "Actor ID", "Params", "Scene", "npcActions index"])
for cmd_id, actors in sorted(
actors_by_cutscene_npc_action_list_command_id.items(),
key=lambda item: item[0],
):
if not actors:
csvw.writerow([cmd_id, "(unused)"])
header = cmd_id
for (actor_id, actor_params, scene, npc_action_index,) in sorted(
set(actors),
key=lambda actor: (actor[2], actor[0], actor[3], actor[1]),
):
csvw.writerow([header, actor_id, actor_params, scene, npc_action_index])
header = ""
csvw.writerow([])
...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment