diff --git a/fast64_internal/oot/README.md b/fast64_internal/oot/README.md index e336c3d8f..99d2fc4b3 100644 --- a/fast64_internal/oot/README.md +++ b/fast64_internal/oot/README.md @@ -45,9 +45,9 @@ Read the "Getting Started" section for information on scene exporting. To add an actor you need to create a new empty object in Blender, the shape doesn't matter. When the empty object is created you can set the ``Actor`` object type in the ``Object Properties`` panel. -To add actors to a scene, create a new Empty and parent it to a Room, otherwise they will not be exported in the room C code. Then in the Object Properties panel select ``Actor`` as the Object Type. Use the ``Select Actor ID`` button to choose an actor, and then set the Actor Parameter value as desired (see the list of Actor Parameters below). +To add actors to a scene, create a new Empty and parent it to a Room, otherwise they will not be exported in the room C code. Then in the Object Properties panel select ``Actor`` as the Object Type. Use the ``Select Actor ID`` button to choose an actor, and then set the Actor Parameter value as desired (see the list of Actor Parameters below). -Finally, every actors you are using needs their assets. In OoT they're called "Objects", if an actor is missing an object the code will not spawn the actor. To do this select the Room that your actor is parented to, select the "Objects" tab in its Object Properties window, and click "Add Item". +Finally, every actors you are using needs their assets. In OoT they're called "Objects", if an actor is missing an object the code will not spawn the actor. To do this select the Room that your actor is parented to, select the "Objects" tab in its Object Properties window, and click "Add Item". Then "Search Object ID" to find the actor object you need. For example, if adding a Deku Baba actor (EN_DEKUBABA) you need to add the "Dekubaba" object to the Room's object dependencies. Note that the object list must not contain more than 15 items. @@ -95,7 +95,7 @@ To import an animation, select the armature the animation belongs to then click To export an animation, select an armature and click "Export", which will export the active animation of the armature. ### Flipbook Textures -Many actors in OOT will animate textures through code using a flipbook method, like with Link's eyes/mouth. A flipbook material will use a texture reference pointing to an address formatted as 0x0?000000. You can find the flipbook texture frames in the material properties tab underneath the dynamic material section. +Many actors in OOT will animate textures through code using a flipbook method, like with Link's eyes/mouth. A flipbook material will use a texture reference pointing to an address formatted as 0x0?000000. You can find the flipbook texture frames in the material properties tab underneath the dynamic material section. ![](/images/oot_flipbook.png) On import, Fast64 will try to read the provided actors code for flipbook textures. On export, Fast64 will try to modify texture arrays used for flipbook textures. @@ -120,7 +120,7 @@ For Link, the eyes/mouth materials use flipbook textures. For Link animations yo 11. Common Issues: - Corrupted mesh: Make sure the root, upper control, and lower control bones are the only bones set to non-deform. - Incorrect waist DL: Go to src/code/z_player_lib.c and modify sPlayerWaistDLs to include your own waist DL. - + Note on Link's bone-weighting requirements in depth: Heavy modifications of Links model can cause his matrices array to shift from what many display lists in the game expect. Changing the amount of display lists Link's skeleton has can cause some references to matrices in segment 0xD to break, and those display lists must be updated to reflect your changes. @@ -174,6 +174,13 @@ To be able to actually watch your cutscene you need to have a way to trigger it, - ``gHyruleFieldIntroCs`` is the name of the array with the cutscene commands, as defined in ``assets/scenes/overworld/spot00_scene.c``, ``CutsceneData gHyruleFieldIntroCs[]`` 4. Compile the game again and use the entrance you chose for ``sEntranceCutsceneTable`` and your cutscene should play. +Alternatively, you can use the map select to watch your cutscene, though note that this won't make it watchable during normal gameplay: + +1. Open ``src/overlays/gamestates/ovl_select/z_select.c`` +2. Either edit or add an entry inside ``SceneSelectEntry sScenes[]``, for instance: ``{ "My Scene", MapSelect_LoadGame, ENTR_MYSCENE_0 },`` (note that the entrance used is the first of the block you need to have for the scene) +3. Compile the game, you may or may not need to run ``make clean`` first if you edited the entrance table +4. Get on the map select then scroll until you see your new entry (in the previous example is will be called "My Scene") then press R to change the header, on the vanilla map select the first cutscene header will be called ``デモ00``, on HackerOoT it will be ``Cutscene 0`` then press A to start the cutscene. + Note that you can have the actual address of your cutscene if you use ``sym_info.py`` from decomp. Example with ``gHyruleFieldIntroCs``: - Command: ``./sym_info.py gHyruleFieldIntroCs`` - Result: ``Symbol gHyruleFieldIntroCs (RAM: 0x02013AA0, ROM: 0x27E9AA0, build/assets/scenes/overworld/spot00/spot00_scene.o)`` @@ -185,3 +192,5 @@ If the camera preview in Blender isn't following where you have the bones or if 1. Make sure your scene empty object, room empty object, and cutscene empty object are all at the Blender origin. You can usually do this with a combination of Object > Clear > Origin and Alt+G. Maybe Object > Apply > All Transforms if that doesn't work. If your room empty object is 1 meter below your scene empty object, as fast64 does by default, that offset will be applied to everything in game and then the zcamedit stuff will not be at the correct relative position. 2. If you moved / rotated / etc. one of the camera shots / armatures in object mode, this transformation will be ignored. You can fix this by selecting the shot / armature in object mode and clicking Object > Apply > All Transforms. That will convert the transform to actual changed positions for each bone. + +If the game crashes check the transitions if you use the transition command (check both the ones from the entrance table and your cutscene script), also it will crash if you try to use the map select without having a 5th entrance (or more depending on the number of cutscenes you have) in the group for your scene. diff --git a/fast64_internal/oot/__init__.py b/fast64_internal/oot/__init__.py index ecd7b5fa9..22a324464 100644 --- a/fast64_internal/oot/__init__.py +++ b/fast64_internal/oot/__init__.py @@ -82,18 +82,19 @@ class OOT_Properties(bpy.types.PropertyGroup): def oot_panel_register(): oot_operator_panel_register() + cutscene_panels_register() + scene_panels_register() f3d_panels_register() collision_panels_register() oot_obj_panel_register() - scene_panels_register() spline_panels_register() anim_panels_register() skeleton_panels_register() - cutscene_panels_register() def oot_panel_unregister(): oot_operator_panel_unregister() + cutscene_panels_unregister() collision_panels_unregister() oot_obj_panel_unregister() scene_panels_unregister() @@ -101,7 +102,6 @@ def oot_panel_unregister(): f3d_panels_unregister() anim_panels_unregister() skeleton_panels_unregister() - cutscene_panels_unregister() def oot_register(registerPanels): diff --git a/fast64_internal/oot/cutscene/classes.py b/fast64_internal/oot/cutscene/classes.py new file mode 100644 index 000000000..bdfc5ebb3 --- /dev/null +++ b/fast64_internal/oot/cutscene/classes.py @@ -0,0 +1,597 @@ +import bpy + +from dataclasses import dataclass, field +from bpy.types import Object +from typing import Optional +from ..oot_constants import ootData +from .motion.utility import getBlenderPosition, getBlenderRotation, getRotation, getInteger + + +# NOTE: ``paramNumber`` is the expected number of parameters inside the parsed commands, +# this account for the unused parameters. Every classes are based on the commands arguments from ``z64cutscene_commands.h`` + + +@dataclass +class CutsceneCmdBase: + """This class contains common Cutscene data""" + + params: list[str] + + startFrame: Optional[int] = None + endFrame: Optional[int] = None + + def getEnumValue(self, enumKey: str, index: int, isSeqLegacy: bool = False): + enum = ootData.enumData.enumByKey[enumKey] + item = enum.itemById.get(self.params[index]) + if item is None: + setting = getInteger(self.params[index]) + if isSeqLegacy: + setting -= 1 + item = enum.itemByIndex.get(setting) + return item.key if item is not None else self.params[index] + + +@dataclass +class CutsceneCmdCamPoint(CutsceneCmdBase): + """This class contains a single Camera Point command data""" + + continueFlag: Optional[str] = None + camRoll: Optional[int] = None + frame: Optional[int] = None + viewAngle: Optional[float] = None + pos: list[int] = field(default_factory=list) + paramNumber: int = 8 + + def __post_init__(self): + if self.params is not None: + self.continueFlag = self.params[0] + self.camRoll = getInteger(self.params[1]) + self.frame = getInteger(self.params[2]) + self.viewAngle = float(self.params[3][:-1]) + self.pos = [getInteger(self.params[4]), getInteger(self.params[5]), getInteger(self.params[6])] + + +@dataclass +class CutsceneCmdActorCue(CutsceneCmdBase): + """This class contains a single Actor Cue command data""" + + actionID: Optional[int] = None + rot: list[str] = field(default_factory=list) + startPos: list[int] = field(default_factory=list) + endPos: list[int] = field(default_factory=list) + paramNumber: int = 15 + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[1]) + self.endFrame = getInteger(self.params[2]) + self.actionID = getInteger(self.params[0]) + self.rot = [getRotation(self.params[3]), getRotation(self.params[4]), getRotation(self.params[5])] + self.startPos = [getInteger(self.params[6]), getInteger(self.params[7]), getInteger(self.params[8])] + self.endPos = [getInteger(self.params[9]), getInteger(self.params[10]), getInteger(self.params[11])] + + +@dataclass +class CutsceneCmdActorCueList(CutsceneCmdBase): + """This class contains the Actor Cue List command data""" + + isPlayer: bool = False + commandType: Optional[str] = None + entryTotal: Optional[int] = None + entries: list[CutsceneCmdActorCue] = field(default_factory=list) + paramNumber: int = 2 + listName: str = "actorCueList" + + def __post_init__(self): + if self.params is not None: + if self.isPlayer: + self.commandType = "Player" + self.entryTotal = getInteger(self.params[0]) + else: + self.commandType = self.params[0] + if self.commandType.startswith("0x"): + # make it a 4 digit hex + self.commandType = self.commandType.removeprefix("0x") + self.commandType = "0x" + "0" * (4 - len(self.commandType)) + self.commandType + else: + self.commandType = ootData.enumData.enumByKey["csCmd"].itemById[self.commandType].key + self.entryTotal = getInteger(self.params[1].strip()) + + +@dataclass +class CutsceneCmdCamEyeSpline(CutsceneCmdBase): + """This class contains the Camera Eye Spline data""" + + entries: list[CutsceneCmdCamPoint] = field(default_factory=list) + paramNumber: int = 2 + listName: str = "camEyeSplineList" + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[0]) + self.endFrame = getInteger(self.params[1]) + + +@dataclass +class CutsceneCmdCamATSpline(CutsceneCmdBase): + """This class contains the Camera AT (look-at) Spline data""" + + entries: list[CutsceneCmdCamPoint] = field(default_factory=list) + paramNumber: int = 2 + listName: str = "camATSplineList" + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[0]) + self.endFrame = getInteger(self.params[1]) + + +@dataclass +class CutsceneCmdCamEyeSplineRelToPlayer(CutsceneCmdBase): + """This class contains the Camera Eye Spline Relative to the Player data""" + + entries: list[CutsceneCmdCamPoint] = field(default_factory=list) + paramNumber: int = 2 + listName: str = "camEyeSplineRelPlayerList" + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[0]) + self.endFrame = getInteger(self.params[1]) + + +@dataclass +class CutsceneCmdCamATSplineRelToPlayer(CutsceneCmdBase): + """This class contains the Camera AT Spline Relative to the Player data""" + + entries: list[CutsceneCmdCamPoint] = field(default_factory=list) + paramNumber: int = 2 + listName: str = "camATSplineRelPlayerList" + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[0]) + self.endFrame = getInteger(self.params[1]) + + +@dataclass +class CutsceneCmdCamEye(CutsceneCmdBase): + """This class contains a single Camera Eye point""" + + # This feature is not used in the final game and lacks polish, it is recommended to use splines in all cases. + entries: list = field(default_factory=list) + paramNumber: int = 2 + listName: str = "camEyeList" + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[0]) + self.endFrame = getInteger(self.params[1]) + + +@dataclass +class CutsceneCmdCamAT(CutsceneCmdBase): + """This class contains a single Camera AT point""" + + # This feature is not used in the final game and lacks polish, it is recommended to use splines in all cases. + entries: list = field(default_factory=list) + paramNumber: int = 2 + listName: str = "camATList" + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[0]) + self.endFrame = getInteger(self.params[1]) + + +@dataclass +class CutsceneCmdMisc(CutsceneCmdBase): + """This class contains a single misc command entry""" + + type: Optional[str] = None # see ``CutsceneMiscType`` in decomp + paramNumber: int = 14 + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[1]) + self.endFrame = getInteger(self.params[2]) + self.type = self.getEnumValue("csMiscType", 0) + + +@dataclass +class CutsceneCmdMiscList(CutsceneCmdBase): + """This class contains Misc command data""" + + entryTotal: Optional[int] = None + entries: list[CutsceneCmdMisc] = field(default_factory=list) + paramNumber: int = 1 + listName: str = "miscList" + + def __post_init__(self): + if self.params is not None: + self.entryTotal = getInteger(self.params[0]) + + +@dataclass +class CutsceneCmdTransition(CutsceneCmdBase): + """This class contains Transition command data""" + + type: Optional[str] = None + paramNumber: int = 3 + listName: str = "transitionList" + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[1]) + self.endFrame = getInteger(self.params[2]) + self.type = self.getEnumValue("csTransitionType", 0) + + +@dataclass +class CutsceneCmdText(CutsceneCmdBase): + """This class contains Text command data""" + + textId: Optional[int] = None + type: Optional[str] = None + altTextId1: Optional[int] = None + altTextId2: Optional[int] = None + paramNumber: int = 6 + id: str = "Text" + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[1]) + self.endFrame = getInteger(self.params[2]) + self.textId = getInteger(self.params[0]) + self.type = self.getEnumValue("csTextType", 3) + self.altTextId1 = (getInteger(self.params[4]),) + self.altTextId2 = (getInteger(self.params[5]),) + + +@dataclass +class CutsceneCmdTextNone(CutsceneCmdBase): + """This class contains Text None command data""" + + paramNumber: int = 2 + id: str = "None" + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[0]) + self.endFrame = getInteger(self.params[1]) + + +@dataclass +class CutsceneCmdTextOcarinaAction(CutsceneCmdBase): + """This class contains Text Ocarina Action command data""" + + ocarinaActionId: Optional[str] = None + messageId: Optional[int] = None + paramNumber: int = 4 + id: str = "OcarinaAction" + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[1]) + self.endFrame = getInteger(self.params[2]) + self.ocarinaActionId = self.getEnumValue("ocarinaSongActionId", 0) + self.messageId = getInteger(self.params[3]) + + +@dataclass +class CutsceneCmdTextList(CutsceneCmdBase): + """This class contains Text List command data""" + + entryTotal: Optional[int] = None + entries: list[CutsceneCmdText | CutsceneCmdTextNone | CutsceneCmdTextOcarinaAction] = field(default_factory=list) + paramNumber: int = 1 + listName: str = "textList" + + def __post_init__(self): + if self.params is not None: + self.entryTotal = getInteger(self.params[0]) + + +@dataclass +class CutsceneCmdLightSetting(CutsceneCmdBase): + """This class contains Light Setting command data""" + + isLegacy: Optional[bool] = None + lightSetting: Optional[int] = None + paramNumber: int = 11 + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[1]) + self.endFrame = getInteger(self.params[2]) + self.lightSetting = getInteger(self.params[0]) + if self.isLegacy: + self.lightSetting -= 1 + + +@dataclass +class CutsceneCmdLightSettingList(CutsceneCmdBase): + """This class contains Light Setting List command data""" + + entryTotal: Optional[int] = None + entries: list[CutsceneCmdLightSetting] = field(default_factory=list) + paramNumber: int = 1 + listName: str = "lightSettingsList" + + def __post_init__(self): + if self.params is not None: + self.entryTotal = getInteger(self.params[0]) + + +@dataclass +class CutsceneCmdTime(CutsceneCmdBase): + """This class contains Time Ocarina Action command data""" + + hour: Optional[int] = None + minute: Optional[int] = None + paramNumber: int = 5 + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[1]) + self.endFrame = getInteger(self.params[2]) + self.hour = getInteger(self.params[3]) + self.minute = getInteger(self.params[4]) + + +@dataclass +class CutsceneCmdTimeList(CutsceneCmdBase): + """This class contains Time List command data""" + + entryTotal: Optional[int] = None + entries: list[CutsceneCmdTime] = field(default_factory=list) + paramNumber: int = 1 + listName: str = "timeList" + + def __post_init__(self): + if self.params is not None: + self.entryTotal = getInteger(self.params[0]) + + +@dataclass +class CutsceneCmdStartStopSeq(CutsceneCmdBase): + """This class contains Start/Stop Seq command data""" + + isLegacy: Optional[bool] = None + seqId: Optional[str] = None + paramNumber: int = 11 + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[1]) + self.endFrame = getInteger(self.params[2]) + self.seqId = self.getEnumValue("seqId", 0, self.isLegacy) + + +@dataclass +class CutsceneCmdStartStopSeqList(CutsceneCmdBase): + """This class contains Start/Stop Seq List command data""" + + entryTotal: Optional[int] = None + type: Optional[str] = None + entries: list[CutsceneCmdStartStopSeq] = field(default_factory=list) + paramNumber: int = 1 + listName: str = "seqList" + + def __post_init__(self): + if self.params is not None: + self.entryTotal = getInteger(self.params[0]) + + +@dataclass +class CutsceneCmdFadeSeq(CutsceneCmdBase): + """This class contains Fade Seq command data""" + + seqPlayer: Optional[str] = None + paramNumber: int = 11 + enumKey: str = "csFadeOutSeqPlayer" + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[1]) + self.endFrame = getInteger(self.params[2]) + self.seqPlayer = self.getEnumValue("csFadeOutSeqPlayer", 0) + + +@dataclass +class CutsceneCmdFadeSeqList(CutsceneCmdBase): + """This class contains Fade Seq List command data""" + + entryTotal: Optional[int] = None + entries: list[CutsceneCmdFadeSeq] = field(default_factory=list) + paramNumber: int = 1 + listName: str = "fadeSeqList" + + def __post_init__(self): + if self.params is not None: + self.entryTotal = getInteger(self.params[0]) + + +@dataclass +class CutsceneCmdRumbleController(CutsceneCmdBase): + """This class contains Rumble Controller command data""" + + sourceStrength: Optional[int] = None + duration: Optional[int] = None + decreaseRate: Optional[int] = None + paramNumber: int = 8 + + def __post_init__(self): + if self.params is not None: + self.startFrame = getInteger(self.params[1]) + self.endFrame = getInteger(self.params[2]) + self.sourceStrength = getInteger(self.params[3]) + self.duration = getInteger(self.params[4]) + self.decreaseRate = getInteger(self.params[5]) + + +@dataclass +class CutsceneCmdRumbleControllerList(CutsceneCmdBase): + """This class contains Rumble Controller List command data""" + + entryTotal: Optional[int] = None + entries: list[CutsceneCmdRumbleController] = field(default_factory=list) + paramNumber: int = 1 + listName: str = "rumbleList" + + def __post_init__(self): + if self.params is not None: + self.entryTotal = getInteger(self.params[0]) + + +@dataclass +class CutsceneCmdDestination(CutsceneCmdBase): + """This class contains Destination command data""" + + id: Optional[str] = None + paramNumber: int = 3 + listName: str = "destination" + + def __post_init__(self): + if self.params is not None: + self.id = self.getEnumValue("csDestination", 0) + self.startFrame = getInteger(self.params[1]) + + +@dataclass +class Cutscene: + """This class contains a Cutscene's data, including every commands' data""" + + name: str + totalEntries: int + frameCount: int + paramNumber: int = 2 + + destination: CutsceneCmdDestination = None + actorCueList: list[CutsceneCmdActorCueList] = field(default_factory=list) + playerCueList: list[CutsceneCmdActorCueList] = field(default_factory=list) + camEyeSplineList: list[CutsceneCmdCamEyeSpline] = field(default_factory=list) + camATSplineList: list[CutsceneCmdCamATSpline] = field(default_factory=list) + camEyeSplineRelPlayerList: list[CutsceneCmdCamEyeSplineRelToPlayer] = field(default_factory=list) + camATSplineRelPlayerList: list[CutsceneCmdCamATSplineRelToPlayer] = field(default_factory=list) + camEyeList: list[CutsceneCmdCamEye] = field(default_factory=list) + camATList: list[CutsceneCmdCamAT] = field(default_factory=list) + textList: list[CutsceneCmdTextList] = field(default_factory=list) + miscList: list[CutsceneCmdMiscList] = field(default_factory=list) + rumbleList: list[CutsceneCmdRumbleControllerList] = field(default_factory=list) + transitionList: list[CutsceneCmdTransition] = field(default_factory=list) + lightSettingsList: list[CutsceneCmdLightSettingList] = field(default_factory=list) + timeList: list[CutsceneCmdTimeList] = field(default_factory=list) + seqList: list[CutsceneCmdStartStopSeqList] = field(default_factory=list) + fadeSeqList: list[CutsceneCmdFadeSeqList] = field(default_factory=list) + + +class CutsceneObjectFactory: + """This class contains functions to create new Blender objects""" + + def getNewObject(self, name: str, data, selectObject: bool, parentObj: Object) -> Object: + newObj = bpy.data.objects.new(name=name, object_data=data) + bpy.context.view_layer.active_layer_collection.collection.objects.link(newObj) + if selectObject: + newObj.select_set(True) + bpy.context.view_layer.objects.active = newObj + newObj.parent = parentObj + newObj.location = [0.0, 0.0, 0.0] + newObj.rotation_euler = [0.0, 0.0, 0.0] + newObj.scale = [1.0, 1.0, 1.0] + return newObj + + def getNewEmptyObject(self, name: str, selectObject: bool, parentObj: Object): + return self.getNewObject(name, None, selectObject, parentObj) + + def getNewArmatureObject(self, name: str, selectObject: bool, parentObj: Object): + newArmatureData = bpy.data.armatures.new(name) + newArmatureData.display_type = "STICK" + newArmatureData.show_names = True + newArmatureObject = self.getNewObject(name, newArmatureData, selectObject, parentObj) + return newArmatureObject + + def getNewCutsceneObject(self, name: str, frameCount: int, parentObj: Object): + newCSObj = self.getNewEmptyObject(name, True, parentObj) + newCSObj.ootEmptyType = "Cutscene" + newCSObj.ootCutsceneProperty.csEndFrame = frameCount + return newCSObj + + def getNewActorCueListObject(self, name: str, commandType: str, parentObj: Object): + newActorCueListObj = self.getNewEmptyObject(name, False, parentObj) + newActorCueListObj.ootEmptyType = f"CS {'Player' if 'Player' in name else 'Actor'} Cue List" + cmdEnum = ootData.enumData.enumByKey["csCmd"] + + if commandType == "Player": + commandType = "player_cue" + + index = cmdEnum.itemByKey[commandType].index if commandType in cmdEnum.itemByKey else int(commandType, base=16) + item = cmdEnum.itemByIndex.get(index) + + if item is not None: + newActorCueListObj.ootCSMotionProperty.actorCueListProp.commandType = item.key + else: + newActorCueListObj.ootCSMotionProperty.actorCueListProp.commandType = "Custom" + newActorCueListObj.ootCSMotionProperty.actorCueListProp.commandTypeCustom = commandType + + return newActorCueListObj + + def getNewActorCueObject( + self, + name: str, + startFrame: int, + actionID: int | str, + location: list[int], + rot: list[str], + parentObj: Object, + ): + isDummy = "(D)" in name + isPlayer = not isDummy and not "Actor" in name + + newActorCueObj = self.getNewEmptyObject(name, False, parentObj) + newActorCueObj.location = getBlenderPosition(location, bpy.context.scene.ootBlenderScale) + newActorCueObj.empty_display_type = "ARROWS" + newActorCueObj.rotation_mode = "XZY" + newActorCueObj.rotation_euler = getBlenderRotation(rot) + emptyType = "Dummy" if isDummy else "Player" if isPlayer else "Actor" + newActorCueObj.ootEmptyType = f"CS {emptyType} Cue" + newActorCueObj.ootCSMotionProperty.actorCueProp.cueStartFrame = startFrame + + item = None + if isPlayer: + playerEnum = ootData.enumData.enumByKey["csPlayerCueId"] + if isinstance(actionID, int): + item = playerEnum.itemByIndex.get(actionID) + else: + item = playerEnum.itemByKey.get(actionID) + + if item is not None: + newActorCueObj.ootCSMotionProperty.actorCueProp.playerCueID = item.key + elif not isDummy: + if isPlayer: + newActorCueObj.ootCSMotionProperty.actorCueProp.playerCueID = "Custom" + + if isinstance(actionID, int): + cueActionID = f"0x{actionID:04X}" + else: + cueActionID = actionID + + newActorCueObj.ootCSMotionProperty.actorCueProp.cueActionID = cueActionID + + return newActorCueObj + + def getNewCameraObject( + self, name: str, displaySize: float, clipStart: float, clipEnd: float, alpha: float, parentObj: Object + ): + newCamera = bpy.data.cameras.new(name) + newCameraObj = self.getNewObject(name, newCamera, False, parentObj) + newCameraObj.data.display_size = displaySize + newCameraObj.data.clip_start = clipStart + newCameraObj.data.clip_end = clipEnd + newCameraObj.data.passepartout_alpha = alpha + return newCameraObj + + def getNewActorCuePreviewObject(self, name: str, selectObject, parentObj: Object): + newPreviewObj = self.getNewEmptyObject(name, selectObject, parentObj) + newPreviewObj.ootEmptyType = f"CS {'Actor' if 'Actor' in name else 'Player'} Cue Preview" + return newPreviewObj diff --git a/fast64_internal/oot/cutscene/constants.py b/fast64_internal/oot/cutscene/constants.py index 81a9c2412..b3525246a 100644 --- a/fast64_internal/oot/cutscene/constants.py +++ b/fast64_internal/oot/cutscene/constants.py @@ -1,83 +1,252 @@ -ootEnumCSTextboxTypeEntryC = { - "Text": "CS_TEXT_DISPLAY_TEXTBOX", - "None": "CS_TEXT_NONE", - "LearnSong": "CS_TEXT_LEARN_SONG", -} +from ..oot_constants import ootData +from .classes import ( + CutsceneCmdActorCueList, + CutsceneCmdActorCue, + CutsceneCmdCamEyeSpline, + CutsceneCmdCamATSpline, + CutsceneCmdCamEyeSplineRelToPlayer, + CutsceneCmdCamATSplineRelToPlayer, + CutsceneCmdCamEye, + CutsceneCmdCamAT, + CutsceneCmdCamPoint, + CutsceneCmdMisc, + CutsceneCmdMiscList, + CutsceneCmdTransition, + CutsceneCmdText, + CutsceneCmdTextNone, + CutsceneCmdTextOcarinaAction, + CutsceneCmdTextList, + CutsceneCmdLightSetting, + CutsceneCmdLightSettingList, + CutsceneCmdTime, + CutsceneCmdTimeList, + CutsceneCmdStartStopSeq, + CutsceneCmdStartStopSeqList, + CutsceneCmdFadeSeq, + CutsceneCmdFadeSeqList, + CutsceneCmdRumbleController, + CutsceneCmdRumbleControllerList, + CutsceneCmdDestination, +) -ootEnumCSListTypeListC = { - "Textbox": "CS_TEXT_LIST", - "FX": "CS_SCENE_TRANS_FX", - "Lighting": "CS_LIGHTING_LIST", - "Time": "CS_TIME_LIST", - "PlayBGM": "CS_PLAY_BGM_LIST", - "StopBGM": "CS_STOP_BGM_LIST", - "FadeBGM": "CS_FADE_BGM_LIST", - "Misc": "CS_MISC_LIST", - "0x09": "CS_CMD_09_LIST", - "Unk": "CS_UNK_DATA_LIST", -} -ootEnumCSListTypeEntryC = { - "Textbox": None, # special case - "FX": None, # no list entries - "Lighting": "CS_LIGHTING", - "Time": "CS_TIME", - "PlayBGM": "CS_PLAY_BGM", - "StopBGM": "CS_STOP_BGM", - "FadeBGM": "CS_FADE_BGM", - "Misc": "CS_MISC", - "0x09": "CS_CMD_09", - "Unk": "CS_UNK_DATA", +ootEnumCSListTypeListC = { + "TextList": "CS_TEXT_LIST", + "Transition": "CS_TRANSITION", + "LightSettingsList": "CS_LIGHT_SETTING_LIST", + "TimeList": "CS_TIME_LIST", + "StartSeqList": "CS_START_SEQ_LIST", + "StopSeqList": "CS_STOP_SEQ_LIST", + "FadeOutSeqList": "CS_FADE_OUT_SEQ_LIST", + "MiscList": "CS_MISC_LIST", + "RumbleList": "CS_RUMBLE_CONTROLLER_LIST", } ootEnumCSWriteType = [ - ("Custom", "Custom", "Provide the name of a cutscene header variable"), - ("Embedded", "Embedded", "Cutscene data is within scene header (deprecated)"), - ("Object", "Object", "Reference to Blender object representing cutscene"), + ("Custom", "Custom", "Provide the name of a cutscene header variable", "", 0), + ("Object", "Object", "Reference to Blender object representing cutscene", "", 2), ] +# order here sets order on the UI ootEnumCSListType = [ - ("Textbox", "Textbox", "Textbox"), - ("FX", "Scene Trans FX", "Scene Trans FX"), - ("Lighting", "Lighting", "Lighting"), - ("Time", "Time", "Time"), - ("PlayBGM", "Play BGM", "Play BGM"), - ("StopBGM", "Stop BGM", "Stop BGM"), - ("FadeBGM", "Fade BGM", "Fade BGM"), - ("Misc", "Misc", "Misc"), - ("0x09", "Cmd 09", "Cmd 09"), - ("Unk", "Unknown Data", "Unknown Data"), + ("TextList", "Text List", "Textbox", "ALIGN_BOTTOM", 0), + ("MiscList", "Misc List", "Misc", "OPTIONS", 7), + ("RumbleList", "Rumble List", "Rumble Controller", "OUTLINER_OB_FORCE_FIELD", 8), + ("Transition", "Transition", "Transition", "COLORSET_10_VEC", 1), + ("LightSettingsList", "Light Settings List", "Lighting", "LIGHT_SUN", 2), + ("TimeList", "Time List", "Time", "TIME", 3), + ("StartSeqList", "Start Seq List", "Play BGM", "PLAY", 4), + ("StopSeqList", "Stop Seq List", "Stop BGM", "SNAP_FACE", 5), + ("FadeOutSeqList", "Fade-Out Seq List", "Fade BGM", "IPO_EASE_IN_OUT", 6), ] -ootEnumCSListTypeIcons = [ - "ALIGN_BOTTOM", - "COLORSET_10_VEC", - "LIGHT_SUN", - "TIME", - "PLAY", - "SNAP_FACE", - "IPO_EASE_IN_OUT", - "OPTIONS", - "EVENT_F9", - "QUESTION", -] +csListTypeToIcon = { + "TextList": "ALIGN_BOTTOM", + "Transition": "COLORSET_10_VEC", + "LightSettingsList": "LIGHT_SUN", + "TimeList": "TIME", + "StartSeqList": "PLAY", + "StopSeqList": "SNAP_FACE", + "FadeOutSeqList": "IPO_EASE_IN_OUT", + "MiscList": "OPTIONS", + "RumbleList": "OUTLINER_OB_FORCE_FIELD", +} -ootEnumCSTextboxType = [("Text", "Text", "Text"), ("None", "None", "None"), ("LearnSong", "Learn Song", "Learn Song")] +ootEnumCSTextboxType = [ + ("Text", "Text", "Text"), + ("None", "None", "None"), + ("OcarinaAction", "Ocarina Action", "Learn Song"), +] ootEnumCSTextboxTypeIcons = ["FILE_TEXT", "HIDE_ON", "FILE_SOUND"] -ootEnumCSTransitionType = [ - ("1", "To White +", "Also plays whiteout sound for certain scenes/entrances"), - ("2", "To Blue", "To Blue"), - ("3", "From Red", "From Red"), - ("4", "From Green", "From Green"), - ("5", "From White", "From White"), - ("6", "From Blue", "From Blue"), - ("7", "To Red", "To Red"), - ("8", "To Green", "To Green"), - ("9", "Set Unk", "gSaveContext.unk_1410 = 1, works with scene xn 11/17"), - ("10", "From Black", "From Black"), - ("11", "To Black", "To Black"), - ("12", "To Dim Unk", "Fade gSaveContext.unk_1410 255>100, works with scene xn 11/17"), - ("13", "From Dim", "Alpha 100>255"), +ootCSSubPropToName = { + "startFrame": "Start Frame", + "endFrame": "End Frame", + # TextBox + "textID": "Text ID", + "ocarinaAction": "Ocarina Action", + "csTextType": "Text Type", + "topOptionTextID": "Text ID for Top Option", + "bottomOptionTextID": "Text ID for Bottom Option", + "ocarinaMessageId": "Ocarina Message ID", + # Lighting + "lightSettingsIndex": "Light Settings Index", + # Time + "hour": "Hour", + "minute": "Minute", + # Seq + "csSeqID": "Seq ID", + "csSeqPlayer": "Seq Player Type", + # Misc + "csMiscType": "Misc Type", + # Rumble + "rumbleSourceStrength": "Source Strength", + "rumbleDuration": "Duration", + "rumbleDecreaseRate": "Decrease Rate", + # Lists + "TextList": "Text List", + "TimeList": "Time List", + "FadeOutSeqList": "Fade-Out Seq List", + "Transition": "Transition", + "StartSeqList": "Start Seq List", + "MiscList": "Misc List", + "LightSettingsList": "Light Settings List", + "StopSeqList": "Stop Seq List", + "RumbleList": "Rumble List", +} + +ootEnumCSMotionCamMode = [ + ("splineEyeOrAT", "Eye/AT Spline", "Eye/AT Spline"), + ("splineEyeOrATRelPlayer", "Spline Rel. Player", "Relative to Player's location/yaw"), + ("eyeOrAT", "Eye/AT Point", "Single Eye/AT point (not recommended)"), +] + +ootEnumCSActorCueListCommandType = [ + item for item in ootData.enumData.ootEnumCsCmd if "actor_cue" in item[0] or "player_cue" in item[0] ] +ootEnumCSActorCueListCommandType.sort() +ootEnumCSActorCueListCommandType.insert(0, ("Custom", "Custom", "Custom")) + +ootCSLegacyToNewCmdNames = { + "CS_CAM_POS_LIST": "CS_CAM_EYE_SPLINE", + "CS_CAM_FOCUS_POINT_LIST": "CS_CAM_AT_SPLINE", + "CS_CAM_POS_PLAYER_LIST": "CS_CAM_EYE_SPLINE_REL_TO_PLAYER", + "CS_CAM_FOCUS_POINT_PLAYER_LIST": "CS_CAM_AT_SPLINE_REL_TO_PLAYER", + "CS_NPC_ACTION_LIST": "CS_ACTOR_CUE_LIST", + "CS_PLAYER_ACTION_LIST": "CS_PLAYER_CUE_LIST", + "CS_CMD_07": "CS_CAM_EYE", + "CS_CMD_08": "CS_CAM_AT", + "CS_CAM_POS": "CS_CAM_POINT", + "CS_CAM_FOCUS_POINT": "CS_CAM_POINT", + "CS_CAM_POS_PLAYER": "CS_CAM_POINT", + "CS_CAM_FOCUS_POINT_PLAYER": "CS_CAM_POINT", + "CS_NPC_ACTION": "CS_ACTOR_CUE", + "CS_PLAYER_ACTION": "CS_PLAYER_CUE", + "CS_CMD_09_LIST": "CS_RUMBLE_CONTROLLER_LIST", + "CS_CMD_09": "CS_RUMBLE_CONTROLLER", + "CS_TEXT_DISPLAY_TEXTBOX": "CS_TEXT", + "CS_TEXT_LEARN_SONG": "CS_TEXT_OCARINA_ACTION", + "CS_SCENE_TRANS_FX": "CS_TRANSITION", + "CS_FADE_BGM_LIST": "CS_FADE_OUT_SEQ_LIST", + "CS_FADE_BGM": "CS_FADE_OUT_SEQ", + "CS_TERMINATOR": "CS_DESTINATION", + "CS_LIGHTING_LIST": "CS_LIGHT_SETTING_LIST", + "CS_LIGHTING": "L_CS_LIGHT_SETTING", + "CS_PLAY_BGM_LIST": "CS_START_SEQ_LIST", + "CS_PLAY_BGM": "L_CS_START_SEQ", + "CS_STOP_BGM_LIST": "CS_STOP_SEQ_LIST", + "CS_STOP_BGM": "L_CS_STOP_SEQ", +} + +ootCSListCommands = [ + "CS_ACTOR_CUE_LIST", + "CS_PLAYER_CUE_LIST", + "CS_CAM_EYE_SPLINE", + "CS_CAM_AT_SPLINE", + "CS_CAM_EYE_SPLINE_REL_TO_PLAYER", + "CS_CAM_AT_SPLINE_REL_TO_PLAYER", + "CS_CAM_EYE", + "CS_CAM_AT", + "CS_MISC_LIST", + "CS_LIGHT_SETTING_LIST", + "CS_RUMBLE_CONTROLLER_LIST", + "CS_TEXT_LIST", + "CS_START_SEQ_LIST", + "CS_STOP_SEQ_LIST", + "CS_FADE_OUT_SEQ_LIST", + "CS_TIME_LIST", + "CS_UNK_DATA_LIST", + "CS_PLAY_BGM_LIST", + "CS_STOP_BGM_LIST", + "CS_LIGHTING_LIST", +] + +ootCSListEntryCommands = [ + "CS_ACTOR_CUE", + "CS_PLAYER_CUE", + "CS_CAM_POINT", + "CS_MISC", + "CS_LIGHT_SETTING", + "CS_RUMBLE_CONTROLLER", + "CS_TEXT", + "CS_TEXT_NONE", + "CS_TEXT_OCARINA_ACTION", + "CS_START_SEQ", + "CS_STOP_SEQ", + "CS_FADE_OUT_SEQ", + "CS_TIME", + "CS_UNK_DATA", + "CS_PLAY_BGM", + "CS_STOP_BGM", + "CS_LIGHTING", + # some old commands need to remove 1 to the first argument to stay accurate + "L_CS_LIGHT_SETTING", + "L_CS_START_SEQ", + "L_CS_STOP_SEQ", +] + +ootCSSingleCommands = [ + "CS_BEGIN_CUTSCENE", + "CS_END", + "CS_TRANSITION", + "CS_DESTINATION", +] + +ootCSListAndSingleCommands = ootCSSingleCommands + ootCSListCommands +ootCSListAndSingleCommands.remove("CS_BEGIN_CUTSCENE") +ootCutsceneCommandsC = ootCSSingleCommands + ootCSListCommands + ootCSListEntryCommands + +cmdToClass = { + "CS_CAM_POINT": CutsceneCmdCamPoint, + "CS_MISC": CutsceneCmdMisc, + "CS_LIGHT_SETTING": CutsceneCmdLightSetting, + "CS_TIME": CutsceneCmdTime, + "CS_FADE_OUT_SEQ": CutsceneCmdFadeSeq, + "CS_RUMBLE_CONTROLLER": CutsceneCmdRumbleController, + "CS_TEXT": CutsceneCmdText, + "CS_TEXT_NONE": CutsceneCmdTextNone, + "CS_TEXT_OCARINA_ACTION": CutsceneCmdTextOcarinaAction, + "CS_START_SEQ": CutsceneCmdStartStopSeq, + "CS_STOP_SEQ": CutsceneCmdStartStopSeq, + "CS_ACTOR_CUE": CutsceneCmdActorCue, + "CS_PLAYER_CUE": CutsceneCmdActorCue, + "CS_CAM_EYE_SPLINE": CutsceneCmdCamEyeSpline, + "CS_CAM_AT_SPLINE": CutsceneCmdCamATSpline, + "CS_CAM_EYE_SPLINE_REL_TO_PLAYER": CutsceneCmdCamEyeSplineRelToPlayer, + "CS_CAM_AT_SPLINE_REL_TO_PLAYER": CutsceneCmdCamATSplineRelToPlayer, + "CS_CAM_EYE": CutsceneCmdCamEye, + "CS_CAM_AT": CutsceneCmdCamAT, + "CS_MISC_LIST": CutsceneCmdMiscList, + "CS_TRANSITION": CutsceneCmdTransition, + "CS_TEXT_LIST": CutsceneCmdTextList, + "CS_LIGHT_SETTING_LIST": CutsceneCmdLightSettingList, + "CS_TIME_LIST": CutsceneCmdTimeList, + "CS_FADE_OUT_SEQ_LIST": CutsceneCmdFadeSeqList, + "CS_RUMBLE_CONTROLLER_LIST": CutsceneCmdRumbleControllerList, + "CS_START_SEQ_LIST": CutsceneCmdStartStopSeqList, + "CS_STOP_SEQ_LIST": CutsceneCmdStartStopSeqList, + "CS_ACTOR_CUE_LIST": CutsceneCmdActorCueList, + "CS_PLAYER_CUE_LIST": CutsceneCmdActorCueList, + "CS_DESTINATION": CutsceneCmdDestination, +} diff --git a/fast64_internal/oot/cutscene/exporter/__init__.py b/fast64_internal/oot/cutscene/exporter/__init__.py index 39486d43c..6a6aed48e 100644 --- a/fast64_internal/oot/cutscene/exporter/__init__.py +++ b/fast64_internal/oot/cutscene/exporter/__init__.py @@ -1,13 +1 @@ -from .functions import convertCutsceneObject, readCutsceneData - -from .classes import ( - OOTCSList, - OOTCSTextbox, - OOTCSLighting, - OOTCSTime, - OOTCSBGM, - OOTCSMisc, - OOTCS0x09, - OOTCSUnk, - OOTCutscene, -) +from .functions import getNewCutsceneExport diff --git a/fast64_internal/oot/cutscene/exporter/classes.py b/fast64_internal/oot/cutscene/exporter/classes.py index d5e637713..c038de11b 100644 --- a/fast64_internal/oot/cutscene/exporter/classes.py +++ b/fast64_internal/oot/cutscene/exporter/classes.py @@ -1,83 +1,458 @@ -class OOTCSTextbox: - def __init__(self): - self.textboxType = None - self.messageId = "0x0000" - self.ocarinaSongAction = "0x0000" - self.startFrame = 0 - self.endFrame = 1 - self.type = "0x0000" - self.topOptionBranch = "0x0000" - self.bottomOptionBranch = "0x0000" - self.ocarinaMessageId = "0x0000" - - -class OOTCSLighting: - def __init__(self): - self.index = 1 - self.startFrame = 0 - - -class OOTCSTime: - def __init__(self): - self.startFrame = 0 - self.hour = 23 - self.minute = 59 - - -class OOTCSBGM: - def __init__(self): - self.value = "0x0000" - self.startFrame = 0 - self.endFrame = 1 - - -class OOTCSMisc: - def __init__(self): - self.operation = 1 - self.startFrame = 0 - self.endFrame = 1 - - -class OOTCS0x09: - def __init__(self): - self.startFrame = 0 - self.unk2 = "0x00" - self.unk3 = "0x00" - self.unk4 = "0x00" - - -class OOTCSUnk: - def __unk__(self): - self.unk1 = "0x00000000" - self.unk2 = "0x00000000" - self.unk3 = "0x00000000" - self.unk4 = "0x00000000" - self.unk5 = "0x00000000" - self.unk6 = "0x00000000" - self.unk7 = "0x00000000" - self.unk8 = "0x00000000" - self.unk9 = "0x00000000" - self.unk10 = "0x00000000" - self.unk11 = "0x00000000" - self.unk12 = "0x00000000" - - -class OOTCSList: - def __init__(self): - self.listType = None - self.entries = [] - self.unkType = "0x0001" - self.fxType = "1" - self.fxStartFrame = 0 - self.fxEndFrame = 0 - - -class OOTCutscene: - def __init__(self): - self.name = "" - self.csEndFrame = 100 - self.csWriteTerminator = False - self.csTermIdx = 0 - self.csTermStart = 99 - self.csTermEnd = 100 - self.csLists = [] +import math +import bpy + +from dataclasses import dataclass +from typing import TYPE_CHECKING +from bpy.types import Object +from ....utility import PluginError, indent +from ...oot_constants import ootData +from ..constants import ootEnumCSListTypeListC + +if TYPE_CHECKING: + from ..properties import OOTCutsceneProperty, OOTCSTextProperty + +from ..classes import ( + CutsceneCmdTransition, + CutsceneCmdRumbleController, + CutsceneCmdMisc, + CutsceneCmdTime, + CutsceneCmdLightSetting, + CutsceneCmdText, + CutsceneCmdTextNone, + CutsceneCmdTextOcarinaAction, + CutsceneCmdActorCueList, + CutsceneCmdActorCue, + CutsceneCmdCamEyeSpline, + CutsceneCmdCamATSpline, + CutsceneCmdCamEyeSplineRelToPlayer, + CutsceneCmdCamATSplineRelToPlayer, + CutsceneCmdCamEye, + CutsceneCmdCamAT, + CutsceneCmdCamPoint, +) + + +class CutsceneCmdToC: + """This class contains functions to create the cutscene commands""" + + def getEnumValue(self, enumKey: str, owner, propName: str): + item = ootData.enumData.enumByKey[enumKey].itemByKey.get(getattr(owner, propName)) + return item.id if item is not None else getattr(owner, f"{propName}Custom") + + def getGenericListCmd(self, cmdName: str, entryTotal: int): + return indent * 2 + f"{cmdName}({entryTotal}),\n" + + def getGenericSeqCmd(self, cmdName: str, type: str, startFrame: int, endFrame: int): + return indent * 3 + f"{cmdName}({type}, {startFrame}, {endFrame}" + ", 0" * 8 + "),\n" + + def getTransitionCmd(self, transition: CutsceneCmdTransition): + return indent * 2 + f"CS_TRANSITION({transition.type}, {transition.startFrame}, {transition.endFrame}),\n" + + def getRumbleControllerCmd(self, rumble: CutsceneCmdRumbleController): + return indent * 3 + ( + f"CS_RUMBLE_CONTROLLER(" + + f"0, {rumble.startFrame}, 0, " + + f"{rumble.sourceStrength}, {rumble.duration}, {rumble.decreaseRate}, 0, 0),\n" + ) + + def getMiscCmd(self, misc: CutsceneCmdMisc): + return indent * 3 + (f"CS_MISC(" + f"{misc.type}, {misc.startFrame}, {misc.endFrame}" + ", 0" * 11 + "),\n") + + def getTimeCmd(self, time: CutsceneCmdTime): + return indent * 3 + (f"CS_TIME(" + f"0, {time.startFrame}, 0, {time.hour}, {time.minute}" + "),\n") + + def getLightSettingCmd(self, lightSetting: CutsceneCmdLightSetting): + return indent * 3 + ( + f"CS_LIGHT_SETTING(" + f"{lightSetting.lightSetting}, {lightSetting.startFrame}" + ", 0" * 9 + "),\n" + ) + + def getTextCmd(self, text: CutsceneCmdText): + return indent * 3 + ( + f"CS_TEXT(" + + f"{text.textId}, {text.startFrame}, {text.endFrame}, {text.type}, {text.altTextId1}, {text.altTextId2}" + + "),\n" + ) + + def getTextNoneCmd(self, textNone: CutsceneCmdTextNone): + return indent * 3 + f"CS_TEXT_NONE({textNone.startFrame}, {textNone.endFrame}),\n" + + def getTextOcarinaActionCmd(self, ocarinaAction: CutsceneCmdTextOcarinaAction): + return indent * 3 + ( + f"CS_TEXT_OCARINA_ACTION(" + + f"{ocarinaAction.ocarinaActionId}, {ocarinaAction.startFrame}, " + + f"{ocarinaAction.endFrame}, {ocarinaAction.messageId}" + + "),\n" + ) + + def getDestinationCmd(self, csProp: "OOTCutsceneProperty"): + dest = self.getEnumValue("csDestination", csProp, "csDestination") + return indent * 2 + f"CS_DESTINATION({dest}, {csProp.csDestinationStartFrame}, 0),\n" + + def getActorCueListCmd(self, actorCueList: CutsceneCmdActorCueList, isPlayerActor: bool): + return indent * 2 + ( + f"CS_{'PLAYER' if isPlayerActor else 'ACTOR'}_CUE_LIST(" + + f"{actorCueList.commandType + ', ' if not isPlayerActor else ''}" + + f"{actorCueList.entryTotal}),\n" + ) + + def getActorCueCmd(self, actorCue: CutsceneCmdActorCue, isPlayerActor: bool): + return indent * 3 + ( + f"CS_{'PLAYER' if isPlayerActor else 'ACTOR'}_CUE(" + + f"{actorCue.actionID}, {actorCue.startFrame}, {actorCue.endFrame}, " + + "".join(f"{rot}, " for rot in actorCue.rot) + + "".join(f"{pos}, " for pos in actorCue.startPos) + + "".join(f"{pos}, " for pos in actorCue.endPos) + + "0.0f, 0.0f, 0.0f),\n" + ) + + def getCamListCmd(self, cmdName: str, startFrame: int, endFrame: int): + return indent * 2 + f"{cmdName}({startFrame}, {endFrame}),\n" + + def getCamEyeSplineCmd(self, camEyeSpline: CutsceneCmdCamEyeSpline): + return self.getCamListCmd("CS_CAM_EYE_SPLINE", camEyeSpline.startFrame, camEyeSpline.endFrame) + + def getCamATSplineCmd(self, camATSpline: CutsceneCmdCamATSpline): + return self.getCamListCmd("CS_CAM_AT_SPLINE", camATSpline.startFrame, camATSpline.endFrame) + + def getCamEyeSplineRelToPlayerCmd(self, camEyeSplinePlayer: CutsceneCmdCamEyeSplineRelToPlayer): + return self.getCamListCmd( + "CS_CAM_EYE_SPLINE_REL_TO_PLAYER", camEyeSplinePlayer.startFrame, camEyeSplinePlayer.endFrame + ) + + def getCamATSplineRelToPlayerCmd(self, camATSplinePlayer: CutsceneCmdCamATSplineRelToPlayer): + return self.getCamListCmd( + "CS_CAM_AT_SPLINE_REL_TO_PLAYER", camATSplinePlayer.startFrame, camATSplinePlayer.endFrame + ) + + def getCamEyeCmd(self, camEye: CutsceneCmdCamEye): + return self.getCamListCmd("CS_CAM_EYE", camEye.startFrame, camEye.endFrame) + + def getCamATCmd(self, camAT: CutsceneCmdCamAT): + return self.getCamListCmd("CS_CAM_AT", camAT.startFrame, camAT.endFrame) + + def getCamPointCmd(self, camPoint: CutsceneCmdCamPoint): + return indent * 3 + ( + f"CS_CAM_POINT(" + + f"{camPoint.continueFlag}, {camPoint.camRoll}, {camPoint.frame}, {camPoint.viewAngle}f, " + + "".join(f"{pos}, " for pos in camPoint.pos) + + "0),\n" + ) + + +@dataclass +class CutsceneExport(CutsceneCmdToC): + """This class contains functions to create the new cutscene data""" + + csObjects: dict[str, list[Object]] + useDecomp: bool + motionOnly: bool + entryTotal: int = 0 + frameCount: int = 0 + motionFrameCount: int = 0 + camEndFrame: int = 0 + + def getOoTRotation(self, obj: Object): + """Returns the converted Blender rotation""" + + def conv(r): + r /= 2.0 * math.pi + r -= math.floor(r) + r = round(r * 0x10000) + + if r >= 0x8000: + r += 0xFFFF0000 + + assert r >= 0 and r <= 0xFFFFFFFF and (r <= 0x7FFF or r >= 0xFFFF8000) + + return hex(r & 0xFFFF) + + rotXYZ = [conv(obj.rotation_euler[0]), conv(obj.rotation_euler[2]), conv(obj.rotation_euler[1])] + return [f"DEG_TO_BINANG({(int(rot, base=16) * (180 / 0x8000)):.3f})" for rot in rotXYZ] + + def getOoTPosition(self, pos): + """Returns the converted Blender position""" + + scale = bpy.context.scene.ootBlenderScale + + x = round(pos[0] * scale) + y = round(pos[2] * scale) + z = round(-pos[1] * scale) + + if any(v < -0x8000 or v >= 0x8000 for v in (x, y, z)): + raise RuntimeError(f"Position(s) too large, out of range: {x}, {y}, {z}") + + return [x, y, z] + + def getActorCueListData(self, isPlayer: bool): + """Returns the Actor Cue List commands from the corresponding objects""" + + playerOrActor = f"{'Player' if isPlayer else 'Actor'}" + actorCueListObjects = self.csObjects[f"CS {playerOrActor} Cue List"] + actorCueListObjects.sort(key=lambda o: o.ootCSMotionProperty.actorCueProp.cueStartFrame) + actorCueData = "" + + self.entryTotal += len(actorCueListObjects) + for obj in actorCueListObjects: + entryTotal = len(obj.children) + + if entryTotal == 0: + raise PluginError("ERROR: The Actor Cue List does not contain any child Actor Cue objects") + + if obj.children[-1].ootEmptyType != "CS Dummy Cue": + # we need an extra point that won't be exported to get the real last cue's + # end frame and end position + raise PluginError("ERROR: The Actor Cue List is missing the extra dummy point!") + + commandType = obj.ootCSMotionProperty.actorCueListProp.commandType + + if commandType == "Custom": + commandType = obj.ootCSMotionProperty.actorCueListProp.commandTypeCustom + elif self.useDecomp: + commandType = ootData.enumData.enumByKey["csCmd"].itemByKey[commandType].id + + # ignoring dummy cue + actorCueList = CutsceneCmdActorCueList(None, entryTotal=entryTotal - 1, commandType=commandType) + actorCueData += self.getActorCueListCmd(actorCueList, isPlayer) + + for i, childObj in enumerate(obj.children, 1): + startFrame = childObj.ootCSMotionProperty.actorCueProp.cueStartFrame + if i < len(obj.children) and childObj.ootEmptyType != "CS Dummy Cue": + endFrame = obj.children[i].ootCSMotionProperty.actorCueProp.cueStartFrame + actionID = None + + if isPlayer: + cueID = childObj.ootCSMotionProperty.actorCueProp.playerCueID + if cueID != "Custom": + actionID = ootData.enumData.enumByKey["csPlayerCueId"].itemByKey[cueID].id + + if actionID is None: + actionID = childObj.ootCSMotionProperty.actorCueProp.cueActionID + + actorCue = CutsceneCmdActorCue( + None, + startFrame, + endFrame, + actionID, + self.getOoTRotation(childObj), + self.getOoTPosition(childObj.location), + self.getOoTPosition(obj.children[i].location), + ) + actorCueData += self.getActorCueCmd(actorCue, isPlayer) + + return actorCueData + + def getCameraShotPointData(self, bones, useAT: bool): + """Returns the Camera Point data from the bone data""" + + shotPoints: list[CutsceneCmdCamPoint] = [] + + if len(bones) < 4: + raise RuntimeError("Camera Armature needs at least 4 bones!") + + for bone in bones: + if bone.parent is not None: + raise RuntimeError("Camera Armature bones are not allowed to have parent bones!") + + shotPoints.append( + CutsceneCmdCamPoint( + None, + None, + None, + ("CS_CAM_CONTINUE" if self.useDecomp else "0"), + bone.ootCamShotPointProp.shotPointRoll if useAT else 0, + bone.ootCamShotPointProp.shotPointFrame, + bone.ootCamShotPointProp.shotPointViewAngle, + self.getOoTPosition(bone.head if not useAT else bone.tail), + ) + ) + + # NOTE: because of the game's bug explained in the importer we need to add an extra dummy point when exporting + shotPoints.append( + CutsceneCmdCamPoint(None, None, None, "CS_CAM_STOP" if self.useDecomp else "-1", 0, 0, 0.0, [0, 0, 0]) + ) + return shotPoints + + def getCamCmdFunc(self, camMode: str, useAT: bool): + """Returns the camera get function depending on the camera mode""" + + camCmdFuncMap = { + "splineEyeOrAT": self.getCamATSplineCmd if useAT else self.getCamEyeSplineCmd, + "splineEyeOrATRelPlayer": self.getCamATSplineRelToPlayerCmd + if useAT + else self.getCamEyeSplineRelToPlayerCmd, + "eyeOrAT": self.getCamATCmd if useAT else self.getCamEyeCmd, + } + + return camCmdFuncMap[camMode] + + def getCamClass(self, camMode: str, useAT: bool): + """Returns the camera dataclass depending on the camera mode""" + + camCmdClassMap = { + "splineEyeOrAT": CutsceneCmdCamATSpline if useAT else CutsceneCmdCamEyeSpline, + "splineEyeOrATRelPlayer": CutsceneCmdCamATSplineRelToPlayer + if useAT + else CutsceneCmdCamEyeSplineRelToPlayer, + "eyeOrAT": CutsceneCmdCamAT if useAT else CutsceneCmdCamEye, + } + + return camCmdClassMap[camMode] + + def getCamListData(self, shotObj: Object, useAT: bool): + """Returns the Camera Shot data from the corresponding Armatures""" + + camPointList = self.getCameraShotPointData(shotObj.data.bones, useAT) + startFrame = shotObj.data.ootCamShotProp.shotStartFrame + + # "fake" end frame + endFrame = ( + startFrame + max(2, sum(point.frame for point in camPointList)) + (camPointList[-2].frame if useAT else 1) + ) + + if not useAT: + for pointData in camPointList: + pointData.frame = 0 + self.camEndFrame = endFrame + + camData = self.getCamClass(shotObj.data.ootCamShotProp.shotCamMode, useAT)(None, startFrame, endFrame) + return self.getCamCmdFunc(shotObj.data.ootCamShotProp.shotCamMode, useAT)(camData) + "".join( + self.getCamPointCmd(pointData) for pointData in camPointList + ) + + def getCameraShotData(self): + """Returns every Camera Shot commands""" + + shotObjects = self.csObjects["camShot"] + cameraShotData = "" + + if len(shotObjects) > 0: + motionFrameCount = -1 + for shotObj in shotObjects: + cameraShotData += self.getCamListData(shotObj, False) + self.getCamListData(shotObj, True) + motionFrameCount = max(motionFrameCount, self.camEndFrame + 1) + self.motionFrameCount += motionFrameCount + self.entryTotal += len(shotObjects) * 2 + + return cameraShotData + + def getTextListData(self, textEntry: "OOTCSTextProperty"): + match textEntry.textboxType: + case "Text": + return self.getTextCmd( + CutsceneCmdText( + None, + textEntry.startFrame, + textEntry.endFrame, + textEntry.textID, + self.getEnumValue("csTextType", textEntry, "csTextType"), + textEntry.topOptionTextID, + textEntry.bottomOptionTextID, + ) + ) + case "None": + return self.getTextNoneCmd(CutsceneCmdTextNone(None, textEntry.startFrame, textEntry.endFrame)) + case "OcarinaAction": + return self.getTextOcarinaActionCmd( + CutsceneCmdTextOcarinaAction( + None, + textEntry.startFrame, + textEntry.endFrame, + self.getEnumValue("ocarinaSongActionId", textEntry, "ocarinaAction"), + textEntry.ocarinaMessageId, + ) + ) + + def getCutsceneData(self): + csProp: "OOTCutsceneProperty" = self.csObjects["Cutscene"][0].ootCutsceneProperty + self.frameCount = csProp.csEndFrame + data = "" + + if csProp.csUseDestination: + data += self.getDestinationCmd(csProp) + self.entryTotal += 1 + + for entry in csProp.csLists: + subData = "" + listCmd = "" + entryTotal = 0 + match entry.listType: + case "StartSeqList" | "StopSeqList" | "FadeOutSeqList": + entryTotal = len(entry.seqList) + for elem in entry.seqList: + enumKey = "csFadeOutSeqPlayer" if entry.listType == "FadeOutSeqList" else "seqId" + propName = "csSeqPlayer" if entry.listType == "FadeOutSeqList" else "csSeqID" + subData += self.getGenericSeqCmd( + ootEnumCSListTypeListC[entry.listType].removesuffix("_LIST"), + self.getEnumValue(enumKey, elem, propName), + elem.startFrame, + elem.endFrame, + ) + case "Transition": + subData += self.getTransitionCmd( + CutsceneCmdTransition( + None, + entry.transitionStartFrame, + entry.transitionEndFrame, + self.getEnumValue("csTransitionType", entry, "transitionType"), + ) + ) + case _: + curList = getattr(entry, (entry.listType[0].lower() + entry.listType[1:])) + entryTotal = len(curList) + for elem in curList: + match entry.listType: + case "TextList": + subData += self.getTextListData(elem) + case "LightSettingsList": + subData += self.getLightSettingCmd( + CutsceneCmdLightSetting( + None, elem.startFrame, elem.endFrame, None, elem.lightSettingsIndex + ) + ) + case "TimeList": + subData += self.getTimeCmd( + CutsceneCmdTime(None, elem.startFrame, elem.endFrame, elem.hour, elem.minute) + ) + case "MiscList": + subData += self.getMiscCmd( + CutsceneCmdMisc( + None, + elem.startFrame, + elem.endFrame, + self.getEnumValue("csMiscType", elem, "csMiscType"), + ) + ) + case "RumbleList": + subData += self.getRumbleControllerCmd( + CutsceneCmdRumbleController( + None, + elem.startFrame, + elem.endFrame, + elem.rumbleSourceStrength, + elem.rumbleDuration, + elem.rumbleDecreaseRate, + ) + ) + case _: + raise PluginError("ERROR: Unknown Cutscene List Type!") + if entry.listType != "Transition": + listCmd = self.getGenericListCmd(ootEnumCSListTypeListC[entry.listType], entryTotal) + self.entryTotal += 1 + data += listCmd + subData + + return data + + def getExportData(self): + """Returns the cutscene data""" + + csData = "" + if not self.motionOnly: + csData = self.getCutsceneData() + csData += self.getActorCueListData(False) + self.getActorCueListData(True) + self.getCameraShotData() + + if self.motionFrameCount > self.frameCount: + self.frameCount += self.motionFrameCount - self.frameCount + + return ( + (indent + f"CS_BEGIN_CUTSCENE({self.entryTotal}, {self.frameCount}),\n") + csData + (indent + "CS_END(),\n") + ) diff --git a/fast64_internal/oot/cutscene/exporter/functions.py b/fast64_internal/oot/cutscene/exporter/functions.py index 9ac6204f5..4512f1512 100644 --- a/fast64_internal/oot/cutscene/exporter/functions.py +++ b/fast64_internal/oot/cutscene/exporter/functions.py @@ -1,104 +1,49 @@ -from ...oot_utility import getCutsceneName, getCustomProperty +import bpy -from .classes import ( - OOTCSList, - OOTCSTextbox, - OOTCSLighting, - OOTCSTime, - OOTCSBGM, - OOTCSMisc, - OOTCS0x09, - OOTCSUnk, - OOTCutscene, -) +from bpy.types import Object +from ....utility import PluginError +from .classes import CutsceneExport -def readCutsceneData(csParentOut, csParentIn): - for listIn in csParentIn.csLists: - listOut = OOTCSList() - listOut.listType = listIn.listType - listOut.unkType, listOut.fxType, listOut.fxStartFrame, listOut.fxEndFrame = ( - listIn.unkType, - listIn.fxType, - listIn.fxStartFrame, - listIn.fxEndFrame, - ) - listData = [] - if listOut.listType == "Textbox": - for entryIn in listIn.textbox: - entryOut = OOTCSTextbox() - entryOut.textboxType = entryIn.textboxType - entryOut.messageId = entryIn.messageId - entryOut.ocarinaSongAction = entryIn.ocarinaSongAction - entryOut.startFrame = entryIn.startFrame - entryOut.endFrame = entryIn.endFrame - entryOut.type = entryIn.type - entryOut.topOptionBranch = entryIn.topOptionBranch - entryOut.bottomOptionBranch = entryIn.bottomOptionBranch - entryOut.ocarinaMessageId = entryIn.ocarinaMessageId - listOut.entries.append(entryOut) - elif listOut.listType == "Lighting": - for entryIn in listIn.lighting: - entryOut = OOTCSLighting() - entryOut.index = entryIn.index - entryOut.startFrame = entryIn.startFrame - listOut.entries.append(entryOut) - elif listOut.listType == "Time": - for entryIn in listIn.time: - entryOut = OOTCSTime() - entryOut.startFrame = entryIn.startFrame - entryOut.hour = entryIn.hour - entryOut.minute = entryIn.minute - listOut.entries.append(entryOut) - elif listOut.listType in {"PlayBGM", "StopBGM", "FadeBGM"}: - for entryIn in listIn.bgm: - entryOut = OOTCSBGM() - entryOut.value = entryIn.value - entryOut.startFrame = entryIn.startFrame - entryOut.endFrame = entryIn.endFrame - listOut.entries.append(entryOut) - elif listOut.listType == "Misc": - for entryIn in listIn.misc: - entryOut = OOTCSMisc() - entryOut.operation = entryIn.operation - entryOut.startFrame = entryIn.startFrame - entryOut.endFrame = entryIn.endFrame - listOut.entries.append(entryOut) - elif listOut.listType == "0x09": - for entryIn in listIn.nine: - entryOut = OOTCS0x09() - entryOut.startFrame = entryIn.startFrame - entryOut.unk2 = entryIn.unk2 - entryOut.unk3 = entryIn.unk3 - entryOut.unk4 = entryIn.unk4 - listOut.entries.append(entryOut) - elif listOut.listType == "Unk": - for entryIn in listIn.unk: - entryOut = OOTCSUnk() - entryOut.unk1 = entryIn.unk1 - entryOut.unk2 = entryIn.unk2 - entryOut.unk3 = entryIn.unk3 - entryOut.unk4 = entryIn.unk4 - entryOut.unk5 = entryIn.unk5 - entryOut.unk6 = entryIn.unk6 - entryOut.unk7 = entryIn.unk7 - entryOut.unk8 = entryIn.unk8 - entryOut.unk9 = entryIn.unk9 - entryOut.unk10 = entryIn.unk10 - entryOut.unk11 = entryIn.unk11 - entryOut.unk12 = entryIn.unk12 - listOut.entries.append(entryOut) - csParentOut.csLists.append(listOut) +def getCutsceneObjects(csName: str): + """Returns the object list containing every object from the cutscene to export""" + csObjects: dict[str, list[Object]] = { + "Cutscene": [], + "CS Actor Cue List": [], + "CS Player Cue List": [], + "camShot": [], + } -def convertCutsceneObject(obj): - cs = OOTCutscene() - cs.name = getCutsceneName(obj) - csprop = obj.ootCutsceneProperty - cs.csEndFrame = getCustomProperty(csprop, "csEndFrame") - cs.csWriteTerminator = getCustomProperty(csprop, "csWriteTerminator") - cs.csTermIdx = getCustomProperty(csprop, "csTermIdx") - cs.csTermStart = getCustomProperty(csprop, "csTermStart") - cs.csTermEnd = getCustomProperty(csprop, "csTermEnd") - readCutsceneData(cs, csprop) - return cs + if csName is None: + raise PluginError("ERROR: The cutscene name is None!") + + for obj in bpy.data.objects: + isEmptyObj = obj.type == "EMPTY" + + # look for the cutscene object based on the cutscene name + parentCheck = obj.parent is not None and obj.parent.name == f"Cutscene.{csName}" + csObjCheck = isEmptyObj and obj.ootEmptyType == "Cutscene" and obj.name == f"Cutscene.{csName}" + if parentCheck or csObjCheck: + # add the relevant objects based on the empty type or if it's an armature + if isEmptyObj and obj.ootEmptyType in csObjects.keys(): + csObjects[obj.ootEmptyType].append(obj) + + if obj.type == "ARMATURE" and obj.parent.ootEmptyType == "Cutscene": + csObjects["camShot"].append(obj) + + if len(csObjects["Cutscene"]) != 1: + raise PluginError(f"ERROR: Expected 1 Cutscene Object, found {len(csObjects['Cutscene'])} ({csName}).") + + return csObjects + + +def getNewCutsceneExport(csName: str, motionOnly: bool): + """Returns the initialised cutscene exporter""" + + # this allows us to change the exporter's variables to get what we need + return CutsceneExport( + getCutsceneObjects(csName), + bpy.context.scene.fast64.oot.hackerFeaturesEnabled or bpy.context.scene.useDecompFeatures, + motionOnly, + ) diff --git a/fast64_internal/oot/cutscene/motion/importer/__init__.py b/fast64_internal/oot/cutscene/importer/__init__.py similarity index 100% rename from fast64_internal/oot/cutscene/motion/importer/__init__.py rename to fast64_internal/oot/cutscene/importer/__init__.py diff --git a/fast64_internal/oot/cutscene/motion/importer/classes.py b/fast64_internal/oot/cutscene/importer/classes.py similarity index 56% rename from fast64_internal/oot/cutscene/motion/importer/classes.py rename to fast64_internal/oot/cutscene/importer/classes.py index 4d04af3d4..066494895 100644 --- a/fast64_internal/oot/cutscene/motion/importer/classes.py +++ b/fast64_internal/oot/cutscene/importer/classes.py @@ -1,36 +1,29 @@ import bpy from dataclasses import dataclass -from struct import unpack +from typing import TYPE_CHECKING from bpy.types import Object, Armature -from .....utility import PluginError -from ....oot_constants import ootData -from ..utility import setupCutscene, getBlenderPosition, getRotation +from ....utility import PluginError +from ..motion.utility import setupCutscene, getBlenderPosition, getInteger + +if TYPE_CHECKING: + from ..properties import OOTCSListProperty, OOTCutsceneProperty from ..constants import ( - ootCSMotionLegacyToNewCmdNames, - ootCSMotionListCommands, - ootCSMotionCSCommands, - ootCSMotionListEntryCommands, - ootCSMotionSingleCommands, - ootCSMotionListAndSingleCommands, + ootCSLegacyToNewCmdNames, + ootCSListCommands, + ootCutsceneCommandsC, + ootCSListEntryCommands, + ootCSSingleCommands, + ootCSListAndSingleCommands, + cmdToClass, ) -from ..io_classes import ( - OOTCSMotionActorCueList, - OOTCSMotionActorCue, - OOTCSMotionCamEyeSpline, - OOTCSMotionCamATSpline, - OOTCSMotionCamEyeSplineRelToPlayer, - OOTCSMotionCamATSplineRelToPlayer, - OOTCSMotionCamEye, - OOTCSMotionCamAT, - OOTCSMotionCamPoint, - OOTCSMotionCutscene, - OOTCSMotionObjectFactory, - OOTCSMotionMisc, - OOTCSMotionMiscList, - OOTCSMotionTransition, +from ..classes import ( + CutsceneCmdActorCueList, + CutsceneCmdCamPoint, + Cutscene, + CutsceneObjectFactory, ) @@ -42,134 +35,36 @@ class ParsedCutscene: csData: list[str] # contains every command lists or standalone ones like ``CS_TRANSITION()`` -class OOTCSMotionImportCommands: - """This class contains functions to create the cutscene dataclasses""" +@dataclass +class PropertyData: + listType: str + subPropsData: dict[str, str] + useEndFrame: bool + + +@dataclass +class CutsceneImport(CutsceneObjectFactory): + """This class contains functions to create the new cutscene Blender data""" + + filePath: str # used when importing from the panel + fileData: str # used when importing the cutscenes when importing a scene def getCmdParams(self, data: str, cmdName: str, paramNumber: int): """Returns the list of every parameter of the given command""" - params = data.strip().removeprefix(f"{cmdName}(").replace(" ", "").removesuffix(")").split(",") - if len(params) != paramNumber: + parenthesis = "(" if not cmdName.endswith("(") else "" + params = data.strip().removeprefix(f"{cmdName}{parenthesis}").replace(" ", "").removesuffix(")").split(",") + validTimeCmd = cmdName == "CS_TIME" and len(params) == 6 and paramNumber == 5 + if len(params) != paramNumber and not validTimeCmd: raise PluginError( f"ERROR: The number of expected parameters for `{cmdName}` " + "and the number of found ones is not the same!" ) return params - def getInteger(self, number: str): - """Returns an int number (handles properly negative hex numbers)""" - - if number.startswith("0x"): - number = number.removeprefix("0x") - - # ``"0" * (8 - len(number)`` adds the missing zeroes (if necessary) to have a 8 digit hex number - return unpack("!i", bytes.fromhex("0" * (8 - len(number)) + number))[0] - else: - return int(number) - def getNewCutscene(self, csData: str, name: str): - params = self.getCmdParams(csData, "CS_BEGIN_CUTSCENE", OOTCSMotionCutscene.paramNumber) - return OOTCSMotionCutscene(name, self.getInteger(params[0]), self.getInteger(params[1])) - - def getNewActorCueList(self, cmdData: str, isPlayer: bool): - paramNumber = OOTCSMotionActorCueList.paramNumber - paramNumber = paramNumber - 1 if isPlayer else paramNumber - params = self.getCmdParams(cmdData, f"CS_{'PLAYER' if isPlayer else 'ACTOR'}_CUE_LIST", paramNumber) - - if isPlayer: - actorCueList = OOTCSMotionActorCueList("Player", params[0]) - else: - commandType = params[0] - if commandType.startswith("0x"): - # make it a 4 digit hex - commandType = commandType.removeprefix("0x") - commandType = "0x" + "0" * (4 - len(commandType)) + commandType - actorCueList = OOTCSMotionActorCueList(commandType, self.getInteger(params[1].strip())) - - return actorCueList - - def getNewCamEyeSpline(self, cmdData: str): - params = self.getCmdParams(cmdData, "CS_CAM_EYE_SPLINE", OOTCSMotionCamEyeSpline.paramNumber) - return OOTCSMotionCamEyeSpline(self.getInteger(params[0]), self.getInteger(params[1])) - - def getNewCamATSpline(self, cmdData: str): - params = self.getCmdParams(cmdData, "CS_CAM_AT_SPLINE", OOTCSMotionCamATSpline.paramNumber) - return OOTCSMotionCamATSpline(self.getInteger(params[0]), self.getInteger(params[1])) - - def getNewCamEyeSplineRelToPlayer(self, cmdData: str): - params = self.getCmdParams( - cmdData, "CS_CAM_EYE_SPLINE_REL_TO_PLAYER", OOTCSMotionCamEyeSplineRelToPlayer.paramNumber - ) - return OOTCSMotionCamEyeSplineRelToPlayer(self.getInteger(params[0]), self.getInteger(params[1])) - - def getNewCamATSplineRelToPlayer(self, cmdData: str): - params = self.getCmdParams( - cmdData, "CS_CAM_AT_SPLINE_REL_TO_PLAYER", OOTCSMotionCamATSplineRelToPlayer.paramNumber - ) - return OOTCSMotionCamATSplineRelToPlayer(self.getInteger(params[0]), self.getInteger(params[1])) - - def getNewCamEye(self, cmdData: str): - params = self.getCmdParams(cmdData, "CS_CAM_EYE", OOTCSMotionCamEye.paramNumber) - return OOTCSMotionCamEye(self.getInteger(params[0]), self.getInteger(params[1])) - - def getNewCamAT(self, cmdData: str): - params = self.getCmdParams(cmdData, "CS_CAM_AT", OOTCSMotionCamAT.paramNumber) - return OOTCSMotionCamAT(self.getInteger(params[0]), self.getInteger(params[1])) - - def getNewActorCue(self, cmdData: str, isPlayer: bool): - params = self.getCmdParams( - cmdData, f"CS_{'PLAYER' if isPlayer else 'ACTOR'}_CUE", OOTCSMotionActorCue.paramNumber - ) - - return OOTCSMotionActorCue( - self.getInteger(params[1]), - self.getInteger(params[2]), - self.getInteger(params[0]), - [getRotation(params[3]), getRotation(params[4]), getRotation(params[5])], - [self.getInteger(params[6]), self.getInteger(params[7]), self.getInteger(params[8])], - [self.getInteger(params[9]), self.getInteger(params[10]), self.getInteger(params[11])], - ) - - def getNewCamPoint(self, cmdData: str): - params = self.getCmdParams(cmdData, "CS_CAM_POINT", OOTCSMotionCamPoint.paramNumber) - - return OOTCSMotionCamPoint( - params[0], - self.getInteger(params[1]), - self.getInteger(params[2]), - float(params[3][:-1]), - [self.getInteger(params[4]), self.getInteger(params[5]), self.getInteger(params[6])], - ) - - def getNewMisc(self, cmdData: str): - params = self.getCmdParams(cmdData, "CS_MISC", OOTCSMotionMisc.paramNumber) - miscEnum = ootData.enumData.enumByKey["csMiscType"] - item = miscEnum.itemById.get(params[0]) - if item is None: - item = miscEnum.itemByIndex[self.getInteger(params[0])] - miscType = item.id - return OOTCSMotionMisc(self.getInteger(params[1]), self.getInteger(params[2]), miscType) - - def getNewMiscList(self, cmdData: str): - params = self.getCmdParams(cmdData, "CS_MISC_LIST", OOTCSMotionMiscList.paramNumber) - return OOTCSMotionMiscList(params[0]) - - def getNewTransition(self, cmdData: str): - params = self.getCmdParams(cmdData, "CS_TRANSITION", OOTCSMotionTransition.paramNumber) - transitionEnum = ootData.enumData.enumByKey["csTransitionType"] - item = transitionEnum.itemById.get(params[0]) - if item is None: - item = transitionEnum.itemByIndex[self.getInteger(params[0])] - transType = item.id - return OOTCSMotionTransition(self.getInteger(params[1]), self.getInteger(params[2]), transType) - - -@dataclass -class OOTCSMotionImport(OOTCSMotionImportCommands, OOTCSMotionObjectFactory): - """This class contains functions to create the new cutscene Blender data""" - - filePath: str # used when importing from the panel - fileData: str # used when importing the cutscenes when importing a scene + params = self.getCmdParams(csData, "CS_BEGIN_CUTSCENE", Cutscene.paramNumber) + return Cutscene(name, getInteger(params[0]), getInteger(params[1])) def getParsedCutscenes(self): """Returns the parsed commands read from every cutscene we can find""" @@ -185,11 +80,11 @@ def getParsedCutscenes(self): raise PluginError("ERROR: File data can't be found!") # replace old names - oldNames = list(ootCSMotionLegacyToNewCmdNames.keys()) + oldNames = list(ootCSLegacyToNewCmdNames.keys()) fileData = fileData.replace("CS_CMD_CONTINUE", "CS_CAM_CONTINUE") fileData = fileData.replace("CS_CMD_STOP", "CS_CAM_STOP") for oldName in oldNames: - fileData = fileData.replace(f"{oldName}(", f"{ootCSMotionLegacyToNewCmdNames[oldName]}(") + fileData = fileData.replace(f"{oldName}(", f"{ootCSLegacyToNewCmdNames[oldName]}(") # parse cutscenes fileLines = fileData.split("\n") @@ -237,20 +132,20 @@ def getParsedCutscenes(self): # NOTE: ``CS_UNK_DATA()`` are commands that are completely useless, so we're ignoring those if csName is not None and not "CS_UNK_DATA" in curCmd: - if curCmd in ootCSMotionCSCommands: + if curCmd in ootCutsceneCommandsC: line = line.removesuffix(",") + "\n" - if curCmd in ootCSMotionSingleCommands and curCmd != "CS_END": + if curCmd in ootCSSingleCommands and curCmd != "CS_END": parsedData += line - if not cmdListFound and curCmd in ootCSMotionListCommands: + if not cmdListFound and curCmd in ootCSListCommands: cmdListFound = True parsedData = "" # camera and lighting have "non-standard" list names if curCmd.startswith("CS_CAM"): curCmdPrefix = "CS_CAM" - elif curCmd.startswith("CS_LIGHT"): + elif curCmd.startswith("CS_LIGHT") or curCmd.startswith("L_CS_LIGHT"): curCmdPrefix = "CS_LIGHT" else: curCmdPrefix = curCmd[:-5] @@ -258,11 +153,11 @@ def getParsedCutscenes(self): if curCmdPrefix is not None: if curCmdPrefix in curCmd: parsedData += line - elif not cmdListFound and curCmd in ootCSMotionListEntryCommands: + elif not cmdListFound and curCmd in ootCSListEntryCommands: print(f"{csName}, command:\n{line}") raise PluginError(f"ERROR: Found a list entry outside a list inside ``{csName}``!") - if cmdListFound and nextCmd == "CS_END" or nextCmd in ootCSMotionListAndSingleCommands: + if cmdListFound and nextCmd == "CS_END" or nextCmd in ootCSListAndSingleCommands: cmdListFound = False parsedCS.append(parsedData) parsedData = "" @@ -282,29 +177,7 @@ def getCutsceneList(self): # if it's none then there's no cutscene in the file return None - cutsceneList: list[OOTCSMotionCutscene] = [] - cmdDataList = [ - ("ACTOR_CUE_LIST", self.getNewActorCueList, self.getNewActorCue, "actorCue"), - ("PLAYER_CUE_LIST", self.getNewActorCueList, self.getNewActorCue, "playerCue"), - ("CAM_EYE_SPLINE", self.getNewCamEyeSpline, self.getNewCamPoint, "camEyeSpline"), - ("CAM_AT_SPLINE", self.getNewCamATSpline, self.getNewCamPoint, "camATSpline"), - ( - "CAM_EYE_SPLINE_REL_TO_PLAYER", - self.getNewCamEyeSplineRelToPlayer, - self.getNewCamPoint, - "camEyeSplineRelPlayer", - ), - ( - "CAM_AT_SPLINE_REL_TO_PLAYER", - self.getNewCamATSplineRelToPlayer, - self.getNewCamPoint, - "camATSplineRelPlayer", - ), - ("CAM_EYE", self.getNewCamEye, self.getNewCamPoint, "camEye"), - ("CAM_AT", self.getNewCamAT, self.getNewCamPoint, "camAT"), - ("MISC_LIST", self.getNewMiscList, self.getNewMisc, "misc"), - ("TRANSITION", self.getNewTransition, None, "transition"), - ] + cutsceneList: list[Cutscene] = [] # for each cutscene from the list returned by getParsedCutscenes(), # create classes containing the cutscene's informations @@ -312,48 +185,69 @@ def getCutsceneList(self): for parsedCS in parsedCutscenes: cutscene = None for data in parsedCS.csData: + cmdData = data.removesuffix("\n").split("\n") + cmdListData = cmdData.pop(0) + cmdListName = cmdListData.strip().split("(")[0] + # create a new cutscene data - if "CS_BEGIN_CUTSCENE(" in data: + if cmdListName == "CS_BEGIN_CUTSCENE": cutscene = self.getNewCutscene(data, parsedCS.csName) # if we have a cutscene, create and add the commands data in it - if cutscene is not None: - cmdData = data.removesuffix("\n").split("\n") - cmdListData = cmdData.pop(0) - for cmd, getListFunc, getFunc, listName in cmdDataList: - isPlayer = cmd == "PLAYER_CUE_LIST" - - if f"CS_{cmd}(" in data: - cmdList = getattr(cutscene, f"{listName}List") - - if getListFunc is not None: - if not isPlayer and not cmd == "ACTOR_CUE_LIST": - commandData = getListFunc(cmdListData) + elif cutscene is not None and data.startswith(f"{cmdListName}("): + isPlayer = cmdListData.startswith("CS_PLAYER_CUE_LIST(") + isStartSeq = cmdListData.startswith("CS_START_SEQ_LIST(") + isStopSeq = cmdListData.startswith("CS_STOP_SEQ_LIST(") + + cmd = cmdToClass.get(cmdListName) + if cmd is not None: + cmdList = getattr(cutscene, "playerCueList" if isPlayer else cmd.listName) + + paramNumber = cmd.paramNumber - 1 if isPlayer else cmd.paramNumber + params = self.getCmdParams(cmdListData, cmdListName, paramNumber) + if isStartSeq or isStopSeq: + commandData = cmd(params, type="start" if isStartSeq else "stop") + elif cmdListData.startswith("CS_ACTOR_CUE_LIST(") or isPlayer: + commandData = cmd(params, isPlayer=isPlayer) + else: + commandData = cmd(params) + + if cmdListName != "CS_TRANSITION" and cmdListName != "CS_DESTINATION": + foundEndCmd = False + for d in cmdData: + cmdEntryName = d.strip().split("(")[0] + isLegacy = d.startswith("L_") + if isLegacy: + cmdEntryName = cmdEntryName.removeprefix("L_") + d = d.removeprefix("L_") + + if "CAM" in cmdListName: + flag = d.removeprefix("CS_CAM_POINT(").split(",")[0] + if foundEndCmd: + raise PluginError("ERROR: More camera commands after last one!") + foundEndCmd = "CS_CAM_STOP" in flag or "-1" in flag + + entryCmd = cmdToClass[cmdEntryName] + params = self.getCmdParams(d, cmdEntryName, entryCmd.paramNumber) + + if "CS_LIGHT_SETTING(" in d or isStartSeq or isStopSeq: + listEntry = entryCmd(params, isLegacy=isLegacy) else: - commandData = getListFunc(cmdListData, isPlayer) - - if getFunc is not None: - foundEndCmd = False - for d in cmdData: - if not isPlayer and not cmd == "ACTOR_CUE_LIST": - listEntry = getFunc(d) - if "CAM" in cmd: - flag = d.removeprefix("CS_CAM_POINT(").split(",")[0] - if foundEndCmd: - raise PluginError("ERROR: More camera commands after last one!") - foundEndCmd = "CS_CAM_STOP" in flag or "-1" in flag - else: - listEntry = getFunc(d, isPlayer) - commandData.entries.append(listEntry) - + listEntry = entryCmd(params) + commandData.entries.append(listEntry) + if cmdListName == "CS_DESTINATION": + cutscene.destination = commandData + else: cmdList.append(commandData) + else: + print(f"WARNING: `{cmdListName}` is not implemented yet!") # after processing the commands we can add the cutscene to the cutscene list if cutscene is not None: cutsceneList.append(cutscene) return cutsceneList - def setActorCueData(self, csObj: Object, actorCueList: list[OOTCSMotionActorCueList], cueName: str, csNbr: int): + def setActorCueData(self, csObj: Object, actorCueList: list[CutsceneCmdActorCueList], cueName: str, csNbr: int): """Creates the objects from the Actor Cue List data""" cueObjList = [] @@ -413,7 +307,7 @@ def setActorCueData(self, csObj: Object, actorCueList: list[OOTCSMotionActorCueL if endFrame != getEndFrame and obj.ootEmptyType != "CS Dummy Cue": print(f"WARNING: `{obj.name}`'s end frame do not match the one from the script!") - def validateCameraData(self, cutscene: OOTCSMotionCutscene): + def validateCameraData(self, cutscene: Cutscene): """Safety checks to make sure the camera data is correct""" camLists: list[tuple[str, list, list]] = [ @@ -438,12 +332,12 @@ def validateCameraData(self, cutscene: OOTCSMotionCutscene): # NOTE: There is a bug in the game where when incrementing to the next set of key points, # the key point which checked for whether it's the last point or not is the last point # of the next set, not the last point of the old set. This means we need to remove - # the extra point at the end that will only tell the game that this camera shot stops. + # the extra point at the end that will only tell the game that this camera shot stops. del eyeListEntry.entries[-1] del atListEntry.entries[-1] def setBoneData( - self, cameraShotObj: Object, boneData: list[tuple[OOTCSMotionCamPoint, OOTCSMotionCamPoint]], csNbr: int + self, cameraShotObj: Object, boneData: list[tuple[CutsceneCmdCamPoint, CutsceneCmdCamPoint]], csNbr: int ): """Creates the bones from the Camera Point data""" @@ -493,6 +387,73 @@ def setCameraShotData( return endIndex + 1 + def setPropOrCustom(self, prop, propName: str, value): + try: + setattr(prop, propName, value) + except TypeError: + setattr(prop, propName, "Custom") + setattr(prop, f"{propName}Custom", value) + + def setSubPropertyData(self, subPropsData: dict[str, str], newSubElem, entry): + customNames = [ + "csMiscType", + "csTextType", + "ocarinaAction", + "transitionType", + "csSeqID", + "csSeqPlayer", + "transition", + ] + + for key, value in subPropsData.items(): + if value is not None: + if key in customNames: + valueToSet = getattr(entry, value) + self.setPropOrCustom(newSubElem, key, valueToSet) + else: + setattr(newSubElem, key, getattr(entry, value)) + + def setPropertyData(self, csProp: "OOTCutsceneProperty", cutscene: Cutscene, propDataList: list[PropertyData]): + for data in propDataList: + listName = f"{data.listType[0].lower() + data.listType[1:]}List" + dataList = getattr(cutscene, (listName if data.listType != "FadeOutSeq" else "fadeSeqList")) + for list in dataList: + newElem: "OOTCSListProperty" = csProp.csLists.add() + + if data.listType == "Seq": + type = "StartSeqList" if list.type == "start" else "StopSeqList" + else: + type = f"{data.listType}List" if data.listType != "Transition" else data.listType + newElem.listType = type + + if data.listType == "Transition": + newElem.transitionStartFrame = list.startFrame + newElem.transitionEndFrame = list.endFrame + self.setSubPropertyData(data.subPropsData, newElem, list) + else: + list.entries.sort(key=lambda elem: elem.startFrame) + for entry in list.entries: + newSubElem = getattr(newElem, "seqList" if "fadeOut" in listName else listName).add() + newSubElem.startFrame = entry.startFrame + + if data.useEndFrame: + newSubElem.endFrame = entry.endFrame + + if data.listType == "Text": + self.setPropOrCustom(newSubElem, "textboxType", entry.id) + match entry.id: + case "Text": + newSubElem.textID = f"0x{entry.textId:04X}" + self.setPropOrCustom(newSubElem, "csTextType", entry.type) + case "None": + pass + case "OcarinaAction": + newSubElem.ocarinaMessageId = f"0x{entry.messageId:04X}" + self.setPropOrCustom(newSubElem, "ocarinaAction", entry.ocarinaActionId) + case _: + raise PluginError("ERROR: Unknown text type!") + self.setSubPropertyData(data.subPropsData, newSubElem, entry) + def setCutsceneData(self, csNumber): """Creates the cutscene empty objects from the file data""" @@ -503,18 +464,16 @@ def setCutsceneData(self, csNumber): return csNumber for i, cutscene in enumerate(cutsceneList, csNumber): - print(f'Found Cutscene "{cutscene.name}"!') + print(f'Found Cutscene "{cutscene.name}"! Importing...') self.validateCameraData(cutscene) csName = f"Cutscene.{cutscene.name}" csObj = self.getNewCutsceneObject(csName, cutscene.frameCount, None) + csProp = csObj.ootCutsceneProperty csNumber = i - print("Importing Actor Cues...") self.setActorCueData(csObj, cutscene.actorCueList, "Actor", i) self.setActorCueData(csObj, cutscene.playerCueList, "Player", i) - print("Done!") - print("Importing Camera Shots...") if len(cutscene.camEyeSplineList) > 0: lastIndex = self.setCameraShotData( csObj, cutscene.camEyeSplineList, cutscene.camATSplineList, "splineEyeOrAT", 1, i @@ -535,25 +494,35 @@ def setCutsceneData(self, csNumber): csObj, cutscene.camEyeList, cutscene.camATList, "eyeOrAT", lastIndex, i ) - if len(cutscene.miscList) > 0: - for miscList in cutscene.miscList: - for miscCmd in miscList.entries: - miscProp = csObj.ootCutsceneProperty.preview.miscList.add() - miscProp.startFrame = miscCmd.startFrame - miscProp.endFrame = miscCmd.endFrame - miscProp.type = miscCmd.type - - if len(cutscene.transitionList) > 0: - for transition in cutscene.transitionList: - transitionProp = csObj.ootCutsceneProperty.preview.transitionList.add() - transitionProp.startFrame = transition.startFrame - transitionProp.endFrame = transition.endFrame - transitionProp.type = transition.type + if cutscene.destination is not None: + csProp.csUseDestination = True + csProp.csDestinationStartFrame = cutscene.destination.startFrame + self.setPropOrCustom(csProp, "csDestination", cutscene.destination.id) + + propDataList = [ + PropertyData("Text", {"textboxType": "id"}, True), + PropertyData("Misc", {"csMiscType": "type"}, True), + PropertyData("Transition", {"transitionType": "type"}, True), + PropertyData("LightSettings", {"lightSettingsIndex": "lightSetting"}, False), + PropertyData("Time", {"hour": "hour", "minute": "minute"}, False), + PropertyData("Seq", {"csSeqID": "seqId"}, False), + PropertyData("FadeOutSeq", {"csSeqPlayer": "seqPlayer"}, True), + PropertyData( + "Rumble", + { + "rumbleSourceStrength": "sourceStrength", + "rumbleDuration": "duration", + "rumbleDecreaseRate": "decreaseRate", + }, + False, + ), + ] + self.setPropertyData(csProp, cutscene, propDataList) # Init camera + preview objects and setup the scene setupCutscene(csObj) - print("Done!") bpy.ops.object.select_all(action="DESELECT") + print("Success!") # ``csNumber`` makes sure there's no duplicates return csNumber + 1 diff --git a/fast64_internal/oot/cutscene/motion/importer/functions.py b/fast64_internal/oot/cutscene/importer/functions.py similarity index 75% rename from fast64_internal/oot/cutscene/motion/importer/functions.py rename to fast64_internal/oot/cutscene/importer/functions.py index 16a5fbcca..402176e5d 100644 --- a/fast64_internal/oot/cutscene/motion/importer/functions.py +++ b/fast64_internal/oot/cutscene/importer/functions.py @@ -1,10 +1,10 @@ import bpy -from .classes import OOTCSMotionImport +from .classes import CutsceneImport def importCutsceneData(filePath: str, sceneData: str): """Initialises and imports the cutscene data from either a file or the scene data""" # NOTE: ``sceneData`` is the data read when importing a scene - csMotionImport = OOTCSMotionImport(filePath, sceneData) + csMotionImport = CutsceneImport(filePath, sceneData) return csMotionImport.setCutsceneData(bpy.context.scene.ootCSNumber) diff --git a/fast64_internal/oot/cutscene/motion/constants.py b/fast64_internal/oot/cutscene/motion/constants.py deleted file mode 100644 index 236da1915..000000000 --- a/fast64_internal/oot/cutscene/motion/constants.py +++ /dev/null @@ -1,92 +0,0 @@ -from ...oot_constants import ootData - -ootEnumCSMotionCamMode = [ - ("splineEyeOrAT", "Eye/AT Spline", "Eye/AT Spline"), - ("splineEyeOrATRelPlayer", "Spline Rel. Player", "Relative to Player's location/yaw"), - ("eyeOrAT", "Eye/AT Point", "Single Eye/AT point (not recommended)"), -] - -ootEnumCSActorCueListCommandType = [ - item for item in ootData.enumData.ootEnumCsCmd if "actor_cue" in item[0] or "player_cue" in item[0] -] -ootEnumCSActorCueListCommandType.sort() -ootEnumCSActorCueListCommandType.insert(0, ("Custom", "Custom", "Custom")) - -ootCSMotionLegacyToNewCmdNames = { - "CS_CAM_POS_LIST": "CS_CAM_EYE_SPLINE", - "CS_CAM_FOCUS_POINT_LIST": "CS_CAM_AT_SPLINE", - "CS_CAM_POS_PLAYER_LIST": "CS_CAM_EYE_SPLINE_REL_TO_PLAYER", - "CS_CAM_FOCUS_POINT_PLAYER_LIST": "CS_CAM_AT_SPLINE_REL_TO_PLAYER", - "CS_NPC_ACTION_LIST": "CS_ACTOR_CUE_LIST", - "CS_PLAYER_ACTION_LIST": "CS_PLAYER_CUE_LIST", - "CS_CMD_07": "CS_CAM_EYE", - "CS_CMD_08": "CS_CAM_AT", - "CS_CAM_POS": "CS_CAM_POINT", - "CS_CAM_FOCUS_POINT": "CS_CAM_POINT", - "CS_CAM_POS_PLAYER": "CS_CAM_POINT", - "CS_CAM_FOCUS_POINT_PLAYER": "CS_CAM_POINT", - "CS_NPC_ACTION": "CS_ACTOR_CUE", - "CS_PLAYER_ACTION": "CS_PLAYER_CUE", - "CS_CMD_09_LIST": "CS_RUMBLE_CONTROLLER_LIST", - "CS_CMD_09": "CS_RUMBLE_CONTROLLER", - "CS_TEXT_DISPLAY_TEXTBOX": "CS_TEXT", - "CS_TEXT_LEARN_SONG": "CS_TEXT_OCARINA_ACTION", - "CS_SCENE_TRANS_FX": "CS_TRANSITION", - "CS_FADE_BGM_LIST": "CS_FADE_OUT_SEQ_LIST", - "CS_FADE_BGM": "CS_FADE_OUT_SEQ", - "CS_TERMINATOR": "CS_DESTINATION", -} - -ootCSMotionListCommands = [ - "CS_ACTOR_CUE_LIST", - "CS_PLAYER_CUE_LIST", - "CS_CAM_EYE_SPLINE", - "CS_CAM_AT_SPLINE", - "CS_CAM_EYE_SPLINE_REL_TO_PLAYER", - "CS_CAM_AT_SPLINE_REL_TO_PLAYER", - "CS_CAM_EYE", - "CS_CAM_AT", - "CS_MISC_LIST", - "CS_LIGHT_SETTING_LIST", - "CS_RUMBLE_CONTROLLER_LIST", - "CS_TEXT_LIST", - "CS_START_SEQ_LIST", - "CS_STOP_SEQ_LIST", - "CS_FADE_OUT_SEQ_LIST", - "CS_TIME_LIST", - "CS_UNK_DATA_LIST", - "CS_PLAY_BGM_LIST", - "CS_STOP_BGM_LIST", - "CS_LIGHTING_LIST", -] - -ootCSMotionListEntryCommands = [ - "CS_ACTOR_CUE", - "CS_PLAYER_CUE", - "CS_CAM_POINT", - "CS_MISC", - "CS_LIGHT_SETTING", - "CS_RUMBLE_CONTROLLER", - "CS_TEXT", - "CS_TEXT_NONE", - "CS_TEXT_OCARINA_ACTION", - "CS_START_SEQ", - "CS_STOP_SEQ", - "CS_FADE_OUT_SEQ", - "CS_TIME", - "CS_UNK_DATA", - "CS_PLAY_BGM", - "CS_STOP_BGM", - "CS_LIGHTING", -] - -ootCSMotionSingleCommands = [ - "CS_BEGIN_CUTSCENE", - "CS_END", - "CS_TRANSITION", - "CS_DESTINATION", -] - -ootCSMotionListAndSingleCommands = ootCSMotionSingleCommands + ootCSMotionListCommands -ootCSMotionListAndSingleCommands.remove("CS_BEGIN_CUTSCENE") -ootCSMotionCSCommands = ootCSMotionSingleCommands + ootCSMotionListCommands + ootCSMotionListEntryCommands diff --git a/fast64_internal/oot/cutscene/motion/exporter/__init__.py b/fast64_internal/oot/cutscene/motion/exporter/__init__.py deleted file mode 100644 index b4ff461e0..000000000 --- a/fast64_internal/oot/cutscene/motion/exporter/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .functions import getCutsceneMotionData diff --git a/fast64_internal/oot/cutscene/motion/exporter/classes.py b/fast64_internal/oot/cutscene/motion/exporter/classes.py deleted file mode 100644 index 67a3feaf9..000000000 --- a/fast64_internal/oot/cutscene/motion/exporter/classes.py +++ /dev/null @@ -1,272 +0,0 @@ -import math -import bpy - -from dataclasses import dataclass -from bpy.types import Object -from .....utility import PluginError, indent -from ....oot_constants import ootData - -from ..io_classes import ( - OOTCSMotionActorCueList, - OOTCSMotionActorCue, - OOTCSMotionCamEyeSpline, - OOTCSMotionCamATSpline, - OOTCSMotionCamEyeSplineRelToPlayer, - OOTCSMotionCamATSplineRelToPlayer, - OOTCSMotionCamEye, - OOTCSMotionCamAT, - OOTCSMotionCamPoint, -) - - -class OOTCSMotionExportCommands: - """This class contains functions to create the cutscene commands""" - - def getActorCueListCmd(self, actorCueList: OOTCSMotionActorCueList, isPlayerActor: bool): - return indent + ( - f"CS_{'PLAYER' if isPlayerActor else 'ACTOR'}_CUE_LIST(" - + f"{actorCueList.commandType + ', ' if not isPlayerActor else ''}" - + f"{actorCueList.entryTotal}),\n" - ) - - def getActorCueCmd(self, actorCue: OOTCSMotionActorCue, isPlayerActor: bool): - return indent * 2 + ( - f"CS_{'PLAYER' if isPlayerActor else 'ACTOR'}_CUE(" - + f"{actorCue.actionID}, {actorCue.startFrame}, {actorCue.endFrame}, " - + "".join(f"{rot}, " for rot in actorCue.rot) - + "".join(f"{pos}, " for pos in actorCue.startPos) - + "".join(f"{pos}, " for pos in actorCue.endPos) - + "0.0f, 0.0f, 0.0f),\n" - ) - - def getCamListCmd(self, cmdName: str, startFrame: int, endFrame: int): - return indent + f"{cmdName}({startFrame}, {endFrame}),\n" - - def getCamEyeSplineCmd(self, camEyeSpline: OOTCSMotionCamEyeSpline): - return self.getCamListCmd("CS_CAM_EYE_SPLINE", camEyeSpline.startFrame, camEyeSpline.endFrame) - - def getCamATSplineCmd(self, camATSpline: OOTCSMotionCamATSpline): - return self.getCamListCmd("CS_CAM_AT_SPLINE", camATSpline.startFrame, camATSpline.endFrame) - - def getCamEyeSplineRelToPlayerCmd(self, camEyeSplinePlayer: OOTCSMotionCamEyeSplineRelToPlayer): - return self.getCamListCmd( - "CS_CAM_EYE_SPLINE_REL_TO_PLAYER", camEyeSplinePlayer.startFrame, camEyeSplinePlayer.endFrame - ) - - def getCamATSplineRelToPlayerCmd(self, camATSplinePlayer: OOTCSMotionCamATSplineRelToPlayer): - return self.getCamListCmd( - "CS_CAM_AT_SPLINE_REL_TO_PLAYER", camATSplinePlayer.startFrame, camATSplinePlayer.endFrame - ) - - def getCamEyeCmd(self, camEye: OOTCSMotionCamEye): - return self.getCamListCmd("CS_CAM_EYE", camEye.startFrame, camEye.endFrame) - - def getCamATCmd(self, camAT: OOTCSMotionCamAT): - return self.getCamListCmd("CS_CAM_AT", camAT.startFrame, camAT.endFrame) - - def getCamPointCmd(self, camPoint: OOTCSMotionCamPoint): - return indent * 2 + ( - f"CS_CAM_POINT(" - + f"{camPoint.continueFlag}, {camPoint.camRoll}, {camPoint.frame}, {camPoint.viewAngle}f, " - + "".join(f"{pos}, " for pos in camPoint.pos) - + "0),\n" - ) - - -@dataclass -class OOTCSMotionExport(OOTCSMotionExportCommands): - """This class contains functions to create the new cutscene data""" - - csMotionObjects: dict[str, list[Object]] - useDecomp: bool - addBeginEndCmds: bool - entryTotal: int = 0 - frameCount: int = 0 - camEndFrame: int = 0 - - def getOoTRotation(self, obj: Object): - """Returns the converted Blender rotation""" - - def conv(r): - r /= 2.0 * math.pi - r -= math.floor(r) - r = round(r * 0x10000) - - if r >= 0x8000: - r += 0xFFFF0000 - - assert r >= 0 and r <= 0xFFFFFFFF and (r <= 0x7FFF or r >= 0xFFFF8000) - - return hex(r & 0xFFFF) - - rotXYZ = [conv(obj.rotation_euler[0]), conv(obj.rotation_euler[2]), conv(obj.rotation_euler[1])] - return [f"DEG_TO_BINANG({(int(rot, base=16) * (180 / 0x8000)):.3f})" for rot in rotXYZ] - - def getOoTPosition(self, pos): - """Returns the converted Blender position""" - - scale = bpy.context.scene.ootBlenderScale - - x = round(pos[0] * scale) - y = round(pos[2] * scale) - z = round(-pos[1] * scale) - - if any(v < -0x8000 or v >= 0x8000 for v in (x, y, z)): - raise RuntimeError(f"Position(s) too large, out of range: {x}, {y}, {z}") - - return [x, y, z] - - def getActorCueListData(self, isPlayer: bool): - """Returns the Actor Cue List commands from the corresponding objects""" - - playerOrActor = f"{'Player' if isPlayer else 'Actor'}" - actorCueListObjects = self.csMotionObjects[f"CS {playerOrActor} Cue List"] - actorCueListObjects.sort(key=lambda o: o.ootCSMotionProperty.actorCueProp.cueStartFrame) - actorCueData = "" - - self.entryTotal += len(actorCueListObjects) - for obj in actorCueListObjects: - entryTotal = len(obj.children) - - if entryTotal == 0: - raise PluginError("ERROR: The Actor Cue List does not contain any child Actor Cue objects") - - if obj.children[-1].ootEmptyType != "CS Dummy Cue": - # we need an extra point that won't be exported to get the real last cue's - # end frame and end position - raise PluginError("ERROR: The Actor Cue List is missing the extra dummy point!") - - commandType = obj.ootCSMotionProperty.actorCueListProp.commandType - - if commandType == "Custom": - commandType = obj.ootCSMotionProperty.actorCueListProp.commandTypeCustom - elif self.useDecomp: - commandType = ootData.enumData.enumByKey["csCmd"].itemByKey[commandType].id - - actorCueList = OOTCSMotionActorCueList(commandType, entryTotal - 1) # ignoring dummy cue - actorCueData += self.getActorCueListCmd(actorCueList, isPlayer) - - for i, childObj in enumerate(obj.children, 1): - startFrame = childObj.ootCSMotionProperty.actorCueProp.cueStartFrame - if i < len(obj.children) and childObj.ootEmptyType != "CS Dummy Cue": - endFrame = obj.children[i].ootCSMotionProperty.actorCueProp.cueStartFrame - actionID = None - - if isPlayer: - cueID = childObj.ootCSMotionProperty.actorCueProp.playerCueID - if cueID != "Custom": - actionID = ootData.enumData.enumByKey["csPlayerCueId"].itemByKey[cueID].id - - if actionID is None: - actionID = childObj.ootCSMotionProperty.actorCueProp.cueActionID - - actorCue = OOTCSMotionActorCue( - startFrame, - endFrame, - actionID, - self.getOoTRotation(childObj), - self.getOoTPosition(childObj.location), - self.getOoTPosition(obj.children[i].location), - ) - actorCueData += self.getActorCueCmd(actorCue, isPlayer) - - return actorCueData - - def getCameraShotPointData(self, bones, useAT: bool): - """Returns the Camera Point data from the bone data""" - - shotPoints: list[OOTCSMotionCamPoint] = [] - - if len(bones) < 4: - raise RuntimeError("Camera Armature needs at least 4 bones!") - - for bone in bones: - if bone.parent is not None: - raise RuntimeError("Camera Armature bones are not allowed to have parent bones!") - - shotPoints.append( - OOTCSMotionCamPoint( - ("CS_CAM_CONTINUE" if self.useDecomp else "0"), - bone.ootCamShotPointProp.shotPointRoll if useAT else 0, - bone.ootCamShotPointProp.shotPointFrame, - bone.ootCamShotPointProp.shotPointViewAngle, - self.getOoTPosition(bone.head if not useAT else bone.tail), - ) - ) - - # NOTE: because of the game's bug explained in the importer we need to add an extra dummy point when exporting - shotPoints.append(OOTCSMotionCamPoint("CS_CAM_STOP" if self.useDecomp else "-1", 0, 0, 0.0, [0, 0, 0])) - return shotPoints - - def getCamCmdFunc(self, camMode: str, useAT: bool): - """Returns the camera get function depending on the camera mode""" - - camCmdFuncMap = { - "splineEyeOrAT": self.getCamATSplineCmd if useAT else self.getCamEyeSplineCmd, - "splineEyeOrATRelPlayer": self.getCamATSplineRelToPlayerCmd - if useAT - else self.getCamEyeSplineRelToPlayerCmd, - "eyeOrAT": self.getCamATCmd if useAT else self.getCamEyeCmd, - } - - return camCmdFuncMap[camMode] - - def getCamClass(self, camMode: str, useAT: bool): - """Returns the camera dataclass depending on the camera mode""" - - camCmdClassMap = { - "splineEyeOrAT": OOTCSMotionCamATSpline if useAT else OOTCSMotionCamEyeSpline, - "splineEyeOrATRelPlayer": OOTCSMotionCamATSplineRelToPlayer - if useAT - else OOTCSMotionCamEyeSplineRelToPlayer, - "eyeOrAT": OOTCSMotionCamAT if useAT else OOTCSMotionCamEye, - } - - return camCmdClassMap[camMode] - - def getCamListData(self, shotObj: Object, useAT: bool): - """Returns the Camera Shot data from the corresponding Armatures""" - - camPointList = self.getCameraShotPointData(shotObj.data.bones, useAT) - startFrame = shotObj.data.ootCamShotProp.shotStartFrame - - # "fake" end frame - endFrame = ( - startFrame + max(2, sum(point.frame for point in camPointList)) + (camPointList[-2].frame if useAT else 1) - ) - - if not useAT: - for pointData in camPointList: - pointData.frame = 0 - self.camEndFrame = endFrame - - camData = self.getCamClass(shotObj.data.ootCamShotProp.shotCamMode, useAT)(startFrame, endFrame) - return self.getCamCmdFunc(shotObj.data.ootCamShotProp.shotCamMode, useAT)(camData) + "".join( - self.getCamPointCmd(pointData) for pointData in camPointList - ) - - def getCameraShotData(self): - """Returns every Camera Shot commands""" - - shotObjects = self.csMotionObjects["camShot"] - cameraShotData = "" - - if len(shotObjects) > 0: - frameCount = -1 - for shotObj in shotObjects: - cameraShotData += self.getCamListData(shotObj, False) + self.getCamListData(shotObj, True) - frameCount = max(frameCount, self.camEndFrame + 1) - self.frameCount += frameCount - self.entryTotal += len(shotObjects) * 2 - - return cameraShotData - - def getExportData(self): - """Returns the cutscene data""" - - data = self.getActorCueListData(False) + self.getActorCueListData(True) + self.getCameraShotData() - if self.addBeginEndCmds: - data = ( - indent + f"CS_BEGIN_CUTSCENE({self.entryTotal}, {self.frameCount}),\n" + data + indent + "CS_END(),\n" - ) - return data diff --git a/fast64_internal/oot/cutscene/motion/exporter/functions.py b/fast64_internal/oot/cutscene/motion/exporter/functions.py deleted file mode 100644 index 8996254a7..000000000 --- a/fast64_internal/oot/cutscene/motion/exporter/functions.py +++ /dev/null @@ -1,46 +0,0 @@ -import bpy - -from bpy.types import Object -from .....utility import PluginError -from .classes import OOTCSMotionExport - - -def getCSMotionObjects(csName: str): - """Returns the object list containing every object from the cutscene to export""" - - csMotionObjects: dict[str, list[Object]] = { - "Cutscene": [], - "CS Actor Cue List": [], - "CS Player Cue List": [], - "camShot": [], - } - - if csName is None: - raise PluginError("ERROR: The cutscene name is None!") - - for obj in bpy.data.objects: - isEmptyObj = obj.type == "EMPTY" - - # look for the cutscene object based on the cutscene name - parentCheck = obj.parent is not None and obj.parent.name == f"Cutscene.{csName}" - csObjCheck = isEmptyObj and obj.ootEmptyType == "Cutscene" and obj.name == f"Cutscene.{csName}" - if parentCheck or csObjCheck: - # add the relevant objects based on the empty type or if it's an armature - if isEmptyObj and obj.ootEmptyType in csMotionObjects.keys(): - csMotionObjects[obj.ootEmptyType].append(obj) - - if obj.type == "ARMATURE" and obj.parent.ootEmptyType == "Cutscene": - csMotionObjects["camShot"].append(obj) - - return csMotionObjects - - -def getCutsceneMotionData(csName: str, addBeginEndCmds: bool): - """Returns the initialised cutscene exporter""" - - # this allows us to change the exporter's variables to get what we need - return OOTCSMotionExport( - getCSMotionObjects(csName), - bpy.context.scene.fast64.oot.hackerFeaturesEnabled or bpy.context.scene.useDecompFeatures, - addBeginEndCmds, - ) diff --git a/fast64_internal/oot/cutscene/motion/io_classes.py b/fast64_internal/oot/cutscene/motion/io_classes.py deleted file mode 100644 index 462eeb2f6..000000000 --- a/fast64_internal/oot/cutscene/motion/io_classes.py +++ /dev/null @@ -1,258 +0,0 @@ -import bpy - -from dataclasses import dataclass, field -from bpy.types import Object -from ...oot_constants import ootData -from .utility import getBlenderPosition, getBlenderRotation - - -# NOTE: ``paramNumber`` is the expected number of parameters inside the parsed commands, -# this account for the unused parameters. Every classes are based on the commands arguments from ``z64cutscene_commands.h`` - - -@dataclass -class OOTCSMotionBase: - """This class contains common Cutscene data""" - - startFrame: int - endFrame: int - - -@dataclass -class OOTCSMotionCamPoint: - """This class contains a single Camera Point command data""" - - continueFlag: str - camRoll: int - frame: int - viewAngle: float - pos: list[int, int, int] - paramNumber: int = 8 - - -@dataclass -class OOTCSMotionActorCue(OOTCSMotionBase): - """This class contains a single Actor Cue command data""" - - actionID: int - rot: list[str, str, str] - startPos: list[int, int, int] - endPos: list[int, int, int] - paramNumber: int = 15 - - -@dataclass -class OOTCSMotionActorCueList: - """This class contains the Actor Cue List command data""" - - commandType: str - entryTotal: int - entries: list[OOTCSMotionActorCue] = field(default_factory=list) - paramNumber: int = 2 - - -@dataclass -class OOTCSMotionCamEyeSpline(OOTCSMotionBase): - """This class contains the Camera Eye Spline data""" - - entries: list[OOTCSMotionCamPoint] = field(default_factory=list) - paramNumber: int = 2 - - -@dataclass -class OOTCSMotionCamATSpline(OOTCSMotionBase): - """This class contains the Camera AT (look-at) Spline data""" - - entries: list[OOTCSMotionCamPoint] = field(default_factory=list) - paramNumber: int = 2 - - -@dataclass -class OOTCSMotionCamEyeSplineRelToPlayer(OOTCSMotionBase): - """This class contains the Camera Eye Spline Relative to the Player data""" - - entries: list[OOTCSMotionCamPoint] = field(default_factory=list) - paramNumber: int = 2 - - -@dataclass -class OOTCSMotionCamATSplineRelToPlayer(OOTCSMotionBase): - """This class contains the Camera AT Spline Relative to the Player data""" - - entries: list[OOTCSMotionCamPoint] = field(default_factory=list) - paramNumber: int = 2 - - -@dataclass -class OOTCSMotionCamEye(OOTCSMotionBase): - """This class contains a single Camera Eye point""" - - # This feature is not used in the final game and lacks polish, it is recommended to use splines in all cases. - entries: list = field(default_factory=list) - paramNumber: int = 2 - - -@dataclass -class OOTCSMotionCamAT(OOTCSMotionBase): - """This class contains a single Camera AT point""" - - # This feature is not used in the final game and lacks polish, it is recommended to use splines in all cases. - entries: list = field(default_factory=list) - paramNumber: int = 2 - - -@dataclass -class OOTCSMotionMisc(OOTCSMotionBase): - """This class contains a single misc command entry""" - - type: str # see ``CutsceneMiscType`` in decomp - paramNumber: int = 14 - - -@dataclass -class OOTCSMotionMiscList: - """This class contains Misc command data""" - - entryTotal: int - entries: list[OOTCSMotionMisc] = field(default_factory=list) - paramNumber: int = 1 - - -@dataclass -class OOTCSMotionTransition(OOTCSMotionBase): - """This class contains Transition command data""" - - type: str - paramNumber: int = 3 - - -@dataclass -class OOTCSMotionCutscene: - """This class contains a Cutscene's data, including every commands' data""" - - name: str - totalEntries: int - frameCount: int - paramNumber: int = 2 - - actorCueList: list[OOTCSMotionActorCueList] = field(default_factory=list) - playerCueList: list[OOTCSMotionActorCueList] = field(default_factory=list) - camEyeSplineList: list[OOTCSMotionCamEyeSpline] = field(default_factory=list) - camATSplineList: list[OOTCSMotionCamATSpline] = field(default_factory=list) - camEyeSplineRelPlayerList: list[OOTCSMotionCamEyeSplineRelToPlayer] = field(default_factory=list) - camATSplineRelPlayerList: list[OOTCSMotionCamATSplineRelToPlayer] = field(default_factory=list) - camEyeList: list[OOTCSMotionCamEye] = field(default_factory=list) - camATList: list[OOTCSMotionCamAT] = field(default_factory=list) - miscList: list[OOTCSMotionMiscList] = field(default_factory=list) - transitionList: list[OOTCSMotionTransition] = field(default_factory=list) - - -class OOTCSMotionObjectFactory: - """This class contains functions to create new Blender objects""" - - def getNewObject(self, name: str, data, selectObject: bool, parentObj: Object) -> Object: - newObj = bpy.data.objects.new(name=name, object_data=data) - bpy.context.view_layer.active_layer_collection.collection.objects.link(newObj) - if selectObject: - newObj.select_set(True) - bpy.context.view_layer.objects.active = newObj - newObj.parent = parentObj - newObj.location = [0.0, 0.0, 0.0] - newObj.rotation_euler = [0.0, 0.0, 0.0] - newObj.scale = [1.0, 1.0, 1.0] - return newObj - - def getNewEmptyObject(self, name: str, selectObject: bool, parentObj: Object): - return self.getNewObject(name, None, selectObject, parentObj) - - def getNewArmatureObject(self, name: str, selectObject: bool, parentObj: Object): - newArmatureData = bpy.data.armatures.new(name) - newArmatureData.display_type = "STICK" - newArmatureData.show_names = True - newArmatureObject = self.getNewObject(name, newArmatureData, selectObject, parentObj) - return newArmatureObject - - def getNewCutsceneObject(self, name: str, frameCount: int, parentObj: Object): - newCSObj = self.getNewEmptyObject(name, True, parentObj) - newCSObj.ootEmptyType = "Cutscene" - newCSObj.ootCutsceneProperty.csEndFrame = frameCount - return newCSObj - - def getNewActorCueListObject(self, name: str, commandType: str, parentObj: Object): - newActorCueListObj = self.getNewEmptyObject(name, False, parentObj) - newActorCueListObj.ootEmptyType = f"CS {'Player' if 'Player' in name else 'Actor'} Cue List" - cmdEnum = ootData.enumData.enumByKey["csCmd"] - - if commandType == "Player": - commandType = "player_cue" - - index = cmdEnum.itemByKey[commandType].index if commandType in cmdEnum.itemByKey else int(commandType, base=16) - item = cmdEnum.itemByIndex.get(index) - - if item is not None: - newActorCueListObj.ootCSMotionProperty.actorCueListProp.commandType = item.key - else: - newActorCueListObj.ootCSMotionProperty.actorCueListProp.commandType = "Custom" - newActorCueListObj.ootCSMotionProperty.actorCueListProp.commandTypeCustom = commandType - - return newActorCueListObj - - def getNewActorCueObject( - self, - name: str, - startFrame: int, - actionID: int | str, - location: list[int], - rot: list[str], - parentObj: Object, - ): - isDummy = "(D)" in name - isPlayer = not isDummy and not "Actor" in name - - newActorCueObj = self.getNewEmptyObject(name, False, parentObj) - newActorCueObj.location = getBlenderPosition(location, bpy.context.scene.ootBlenderScale) - newActorCueObj.empty_display_type = "ARROWS" - newActorCueObj.rotation_mode = "XZY" - newActorCueObj.rotation_euler = getBlenderRotation(rot) - emptyType = "Dummy" if isDummy else "Player" if isPlayer else "Actor" - newActorCueObj.ootEmptyType = f"CS {emptyType} Cue" - newActorCueObj.ootCSMotionProperty.actorCueProp.cueStartFrame = startFrame - - item = None - if isPlayer: - playerEnum = ootData.enumData.enumByKey["csPlayerCueId"] - if isinstance(actionID, int): - item = playerEnum.itemByIndex.get(actionID) - else: - item = playerEnum.itemByKey.get(actionID) - - if item is not None: - newActorCueObj.ootCSMotionProperty.actorCueProp.playerCueID = item.key - elif not isDummy: - if isPlayer: - newActorCueObj.ootCSMotionProperty.actorCueProp.playerCueID = "Custom" - - if isinstance(actionID, int): - cueActionID = f"0x{actionID:04X}" - else: - cueActionID = actionID - - newActorCueObj.ootCSMotionProperty.actorCueProp.cueActionID = cueActionID - - return newActorCueObj - - def getNewCameraObject( - self, name: str, displaySize: float, clipStart: float, clipEnd: float, alpha: float, parentObj: Object - ): - newCamera = bpy.data.cameras.new(name) - newCameraObj = self.getNewObject(name, newCamera, False, parentObj) - newCameraObj.data.display_size = displaySize - newCameraObj.data.clip_start = clipStart - newCameraObj.data.clip_end = clipEnd - newCameraObj.data.passepartout_alpha = alpha - return newCameraObj - - def getNewActorCuePreviewObject(self, name: str, selectObject, parentObj: Object): - newPreviewObj = self.getNewEmptyObject(name, selectObject, parentObj) - newPreviewObj.ootEmptyType = f"CS {'Actor' if 'Actor' in name else 'Player'} Cue Preview" - return newPreviewObj diff --git a/fast64_internal/oot/cutscene/motion/operators.py b/fast64_internal/oot/cutscene/motion/operators.py index e028b6976..ad9205a5f 100644 --- a/fast64_internal/oot/cutscene/motion/operators.py +++ b/fast64_internal/oot/cutscene/motion/operators.py @@ -5,8 +5,8 @@ from bpy.props import StringProperty, EnumProperty, BoolProperty from ....utility import PluginError from ...oot_constants import ootData -from .io_classes import OOTCSMotionObjectFactory -from .constants import ootEnumCSActorCueListCommandType +from ..classes import CutsceneObjectFactory +from ..constants import ootEnumCSActorCueListCommandType from ..preview import initFirstFrame, setupCompositorNodes from .utility import ( setupActorCuePreview, @@ -16,6 +16,7 @@ createNewBone, createNewCameraShot, getCutsceneEndFrame, + getCutsceneCamera, ) @@ -34,7 +35,7 @@ def getActorCueList(operator: Operator, context: Context) -> Object | None: def createNewActorCueList(csObj: Object, isPlayer: bool): """Creates a new Actor or Player Cue List and adds one basic cue and the dummy one""" - objFactory = OOTCSMotionObjectFactory() + objFactory = CutsceneObjectFactory() playerOrActor = "Player" if isPlayer else "Actor" newActorCueListObj = objFactory.getNewActorCueListObject(f"New {playerOrActor} Cue List", "actor_cue_0_0", None) index, csPrefix = getNameInformations(csObj, f"{playerOrActor} Cue List", None) @@ -56,7 +57,7 @@ def createNewActorCueList(csObj: Object, isPlayer: bool): newActorCueListObj.parent = csObj -class OOTCSMotionPlayPreview(Operator): +class CutsceneCmdPlayPreview(Operator): """Camera Preview Playback""" bl_idname = "object.play_preview" @@ -68,11 +69,9 @@ def execute(self, context): if csObj is not None: # get and set the camera - cameraObj = None - for childObj in csObj.children: - if childObj.type == "CAMERA": - cameraObj = childObj - break + previewSettings = context.scene.ootPreviewSettingsProperty + cameraObj = getCutsceneCamera(csObj) + cameraObj.data.passepartout_alpha = 1.0 if previewSettings.useOpaqueCamBg else 0.95 # from https://blender.stackexchange.com/a/259103 space = None @@ -88,10 +87,10 @@ def execute(self, context): endFrame = getCutsceneEndFrame(csObj) context.scene.frame_end = endFrame if endFrame > -1 else context.scene.frame_end context.scene.frame_set(context.scene.frame_start) - bpy.context.scene.ootCSPreviewCSObj = csObj - bpy.context.scene.ootCSPreviewNodesReady = False + previewSettings.ootCSPreviewCSObj = csObj + previewSettings.ootCSPreviewNodesReady = False setupCompositorNodes() - initFirstFrame(csObj, bpy.context.scene.ootCSPreviewNodesReady, cameraObj) + initFirstFrame(csObj, previewSettings.ootCSPreviewNodesReady, cameraObj) bpy.ops.screen.animation_cancel() bpy.ops.screen.animation_play() return {"FINISHED"} @@ -99,7 +98,7 @@ def execute(self, context): return {"CANCELLED"} -class OOTCSMotionAddBone(Operator): +class CutsceneCmdAddBone(Operator): """Add a bone to an armature""" bl_idname = "object.add_bone" @@ -145,7 +144,7 @@ def execute(self, context): return {"CANCELLED"} -class OOTCSMotionAddActorCue(Operator): +class CutsceneCmdAddActorCue(Operator): """Add an entry to a player or actor cue list""" bl_idname = "object.add_actor_cue_point" @@ -162,7 +161,7 @@ def execute(self, context): actorCueListObj = actorCueListObj.parent # start by creating the new object with basic values - objFactory = OOTCSMotionObjectFactory() + objFactory = CutsceneObjectFactory() newActorCueObj = objFactory.getNewActorCueObject( f"New {'Player' if self.isPlayer else 'Actor'} Cue", 0, @@ -226,7 +225,7 @@ def execute(self, context): return {"CANCELLED"} -class OOTCSMotionCreateActorCuePreview(Operator): +class CutsceneCmdCreateActorCuePreview(Operator): """Create a preview empty object for a player or an actor cue list""" bl_idname = "object.create_actor_cue_preview" @@ -245,7 +244,7 @@ def execute(self, context): return {"CANCELLED"} -class OOTCSMotionCreateCameraShot(Operator): +class CutsceneCmdCreateCameraShot(Operator): """Create and initialize a camera shot armature""" bl_idname = "object.create_camera_shot" @@ -262,7 +261,7 @@ def execute(self, context): return {"CANCELLED"} -class OOTCSMotionCreatePlayerCueList(Operator): +class CutsceneCmdCreatePlayerCueList(Operator): """Create a cutscene player cue list""" bl_idname = "object.create_player_cue_list" @@ -283,7 +282,7 @@ def execute(self, context): return {"CANCELLED"} -class OOTCSMotionCreateActorCueList(Operator): +class CutsceneCmdCreateActorCueList(Operator): """Create a cutscene actor cue list""" bl_idname = "object.create_actor_cue_list" @@ -349,13 +348,13 @@ def invoke(self, context, event): classes = ( - OOTCSMotionPlayPreview, - OOTCSMotionAddBone, - OOTCSMotionAddActorCue, - OOTCSMotionCreateActorCuePreview, - OOTCSMotionCreateCameraShot, - OOTCSMotionCreatePlayerCueList, - OOTCSMotionCreateActorCueList, + CutsceneCmdPlayPreview, + CutsceneCmdAddBone, + CutsceneCmdAddActorCue, + CutsceneCmdCreateActorCuePreview, + CutsceneCmdCreateCameraShot, + CutsceneCmdCreatePlayerCueList, + CutsceneCmdCreateActorCueList, OOT_SearchActorCueCmdTypeEnumOperator, OOT_SearchPlayerCueIdEnumOperator, ) diff --git a/fast64_internal/oot/cutscene/motion/panels.py b/fast64_internal/oot/cutscene/motion/panels.py index 8b5a975ae..660187319 100644 --- a/fast64_internal/oot/cutscene/motion/panels.py +++ b/fast64_internal/oot/cutscene/motion/panels.py @@ -1,6 +1,6 @@ from bpy.types import Panel from bpy.utils import register_class, unregister_class -from .properties import OOTCSMotionCameraShotProperty, OOTCSMotionCameraShotPointProperty +from .properties import CutsceneCmdCameraShotProperty, CutsceneCmdCameraShotPointProperty class OOT_CSMotionCameraShotPanel(Panel): @@ -16,8 +16,8 @@ def draw(self, context): layout = self.layout if obj.type == "ARMATURE": - camShotProp: OOTCSMotionCameraShotProperty = obj.data.ootCamShotProp - camShotPointProp: OOTCSMotionCameraShotPointProperty = None + camShotProp: CutsceneCmdCameraShotProperty = obj.data.ootCamShotProp + camShotPointProp: CutsceneCmdCameraShotPointProperty = None activeBone = editBone = None box = layout.box() diff --git a/fast64_internal/oot/cutscene/motion/preview.py b/fast64_internal/oot/cutscene/motion/preview.py index 9c9007d73..95d5818a8 100644 --- a/fast64_internal/oot/cutscene/motion/preview.py +++ b/fast64_internal/oot/cutscene/motion/preview.py @@ -185,7 +185,7 @@ def previewFrameHandler(scene: Scene): if obj.type == "CAMERA": pos, rot_quat, viewAngle = getCutsceneCamState(parentObj, scene.frame_current) - if parentObj.ootCutsceneProperty.preview.useWidescreen: + if scene.ootPreviewSettingsProperty.useWidescreen: viewAngle *= 4 / 3 if pos is not None: diff --git a/fast64_internal/oot/cutscene/motion/properties.py b/fast64_internal/oot/cutscene/motion/properties.py index 0138cdafd..d18a48282 100644 --- a/fast64_internal/oot/cutscene/motion/properties.py +++ b/fast64_internal/oot/cutscene/motion/properties.py @@ -6,13 +6,13 @@ from ...oot_upgrade import upgradeCutsceneMotion from ...oot_utility import getEnumName from ...oot_constants import ootData -from .constants import ootEnumCSMotionCamMode, ootEnumCSActorCueListCommandType +from ..constants import ootEnumCSMotionCamMode, ootEnumCSActorCueListCommandType from .operators import ( - OOTCSMotionAddActorCue, - OOTCSMotionCreateActorCuePreview, + CutsceneCmdAddActorCue, + CutsceneCmdCreateActorCuePreview, OOT_SearchActorCueCmdTypeEnumOperator, - OOTCSMotionAddBone, + CutsceneCmdAddBone, OOT_SearchPlayerCueIdEnumOperator, ) @@ -30,7 +30,7 @@ def getNextCuesStartFrame(self): return -1 -class OOTCSMotionActorCueListProperty(PropertyGroup): +class CutsceneCmdActorCueListProperty(PropertyGroup): commandType: EnumProperty( items=ootEnumCSActorCueListCommandType, name="CS Actor Cue Command Type", default="actor_cue_0_0" ) @@ -66,16 +66,16 @@ def draw_props(self, layout: UILayout, isPreview: bool, labelPrefix: str, objNam if not isPreview: split = box.split(factor=0.5) - addCueOp = split.operator(OOTCSMotionAddActorCue.bl_idname, text=f"Add {labelPrefix} Cue") + addCueOp = split.operator(CutsceneCmdAddActorCue.bl_idname, text=f"Add {labelPrefix} Cue") addCueOp.isPlayer = isPlayer - split.operator(OOTCSMotionCreateActorCuePreview.bl_idname) + split.operator(CutsceneCmdCreateActorCuePreview.bl_idname) else: split = box.split(factor=0.5) split.label(text=f"{labelPrefix} Cue List to Preview") split.prop(self, f"{labelPrefix.lower()}CueListToPreview") -class OOTCSMotionActorCueProperty(PropertyGroup): +class CutsceneCmdActorCueProperty(PropertyGroup): cueStartFrame: IntProperty(name="Start Frame", description="Start frame of the Actor Cue", default=0, min=0) cueEndFrame: IntProperty( @@ -123,11 +123,11 @@ def draw_props(self, layout: UILayout, labelPrefix: str, isDummy: bool, objName: split.label(text=label) split.prop(self, "cueActionID", text="") - addCueOp = box.operator(OOTCSMotionAddActorCue.bl_idname, text=f"Add {labelPrefix} Cue") + addCueOp = box.operator(CutsceneCmdAddActorCue.bl_idname, text=f"Add {labelPrefix} Cue") addCueOp.isPlayer = isPlayer -class OOTCSMotionCameraShotProperty(PropertyGroup): +class CutsceneCmdCameraShotProperty(PropertyGroup): shotStartFrame: IntProperty(name="Start Frame", description="Shot start frame", default=0, min=0) # only used as a hint for users @@ -163,10 +163,10 @@ def draw_props(self, layout: UILayout, label: str): split.prop(self, "shotStartFrame") split.prop(self, "shotEndFrame") box.row().prop(self, "shotCamMode", expand=True) - box.operator(OOTCSMotionAddBone.bl_idname) + box.operator(CutsceneCmdAddBone.bl_idname) -class OOTCSMotionCameraShotPointProperty(PropertyGroup): +class CutsceneCmdCameraShotPointProperty(PropertyGroup): shotPointFrame: IntProperty( name="Frame", description="Key point frames value", @@ -231,8 +231,8 @@ def draw_props(self, layout: UILayout): class OOTCutsceneMotionProperty(PropertyGroup): - actorCueListProp: PointerProperty(type=OOTCSMotionActorCueListProperty) - actorCueProp: PointerProperty(type=OOTCSMotionActorCueProperty) + actorCueListProp: PointerProperty(type=CutsceneCmdActorCueListProperty) + actorCueProp: PointerProperty(type=CutsceneCmdActorCueProperty) @staticmethod def upgrade_object(csObj: Object): @@ -242,10 +242,10 @@ def upgrade_object(csObj: Object): classes = ( - OOTCSMotionActorCueListProperty, - OOTCSMotionActorCueProperty, - OOTCSMotionCameraShotProperty, - OOTCSMotionCameraShotPointProperty, + CutsceneCmdActorCueListProperty, + CutsceneCmdActorCueProperty, + CutsceneCmdCameraShotProperty, + CutsceneCmdCameraShotPointProperty, OOTCutsceneMotionProperty, ) @@ -255,19 +255,12 @@ def csMotion_props_register(): register_class(cls) Object.ootCSMotionProperty = PointerProperty(type=OOTCutsceneMotionProperty) - Armature.ootCamShotProp = PointerProperty(type=OOTCSMotionCameraShotProperty) - Bone.ootCamShotPointProp = PointerProperty(type=OOTCSMotionCameraShotPointProperty) - EditBone.ootCamShotPointProp = PointerProperty(type=OOTCSMotionCameraShotPointProperty) - Scene.previewPlayerAge = EnumProperty( - items=[("link_adult", "Adult", "Adult Link (170 cm)", 0), ("link_child", "Child", "Child Link (130 cm)", 1)], - name="Player Age for Preview", - description="For setting Link's height for preview", - default="link_adult", - ) + Armature.ootCamShotProp = PointerProperty(type=CutsceneCmdCameraShotProperty) + Bone.ootCamShotPointProp = PointerProperty(type=CutsceneCmdCameraShotPointProperty) + EditBone.ootCamShotPointProp = PointerProperty(type=CutsceneCmdCameraShotPointProperty) def csMotion_props_unregister(): - del Scene.previewPlayerAge del EditBone.ootCamShotPointProp del Bone.ootCamShotPointProp del Armature.ootCamShotProp diff --git a/fast64_internal/oot/cutscene/motion/utility.py b/fast64_internal/oot/cutscene/motion/utility.py index a7c4c8954..fa8b7c06d 100644 --- a/fast64_internal/oot/cutscene/motion/utility.py +++ b/fast64_internal/oot/cutscene/motion/utility.py @@ -1,5 +1,6 @@ import bpy +from struct import unpack from bpy.types import Object, Bone, Context, EditBone, Armature from mathutils import Vector from ....utility import yUpToZUp @@ -56,13 +57,13 @@ def createNewBone(cameraShotObj: Object, name: str, headPos: list[float], tailPo def createNewCameraShot(csObj: Object): - from .io_classes import OOTCSMotionObjectFactory # circular import fix + from ..classes import CutsceneObjectFactory # circular import fix index, csPrefix = getNameInformations(csObj, "Camera Shot", None) # create a basic armature name = f"{csPrefix}.Camera Shot {index:02}" - newCameraShotObj = OOTCSMotionObjectFactory().getNewArmatureObject(name, True, csObj) + newCameraShotObj = CutsceneObjectFactory().getNewArmatureObject(name, True, csObj) # add 4 bones since it's the minimum required for i in range(1, 5): @@ -83,6 +84,18 @@ def getBlenderPosition(pos: list[int], scale: int): return [float(pos[0]) / scale, -float(pos[2]) / scale, float(pos[1]) / scale] +def getInteger(number: str): + """Returns an int number (handles properly negative hex numbers)""" + + if number.startswith("0x"): + number = number.removeprefix("0x") + + # ``"0" * (8 - len(number)`` adds the missing zeroes (if necessary) to have a 8 digit hex number + return unpack("!i", bytes.fromhex("0" * (8 - len(number)) + number))[0] + else: + return int(number) + + def getRotation(data: str): """Returns the rotation converted to hexadecimal""" @@ -167,7 +180,7 @@ def metersToBlend(context: Context, value: float): def setupActorCuePreview(csObj: Object, actorOrPlayer: str, selectObject: bool, cueList: Object): - from .io_classes import OOTCSMotionObjectFactory # circular import fix + from ..classes import CutsceneObjectFactory # circular import fix # check if the cue actually moves, if not it's not necessary to create a preview object isCueMoving = False @@ -192,11 +205,11 @@ def setupActorCuePreview(csObj: Object, actorOrPlayer: str, selectObject: bool, previewObj = obj break else: - previewObj = OOTCSMotionObjectFactory().getNewActorCuePreviewObject(name, selectObject, csObj) + previewObj = CutsceneObjectFactory().getNewActorCuePreviewObject(name, selectObject, csObj) actorHeight = 1.5 if actorOrPlayer == "Player": - actorHeight = 1.7 if bpy.context.scene.previewPlayerAge == "link_adult" else 1.3 + actorHeight = 1.7 if bpy.context.scene.ootPreviewSettingsProperty.previewPlayerAge == "link_adult" else 1.3 previewObj.empty_display_type = "SINGLE_ARROW" previewObj.empty_display_size = metersToBlend(bpy.context, actorHeight) @@ -245,11 +258,11 @@ def getCutsceneEndFrame(csObj: Object): def setupCutscene(csObj: Object): - from .io_classes import OOTCSMotionObjectFactory # circular import fix + from ..classes import CutsceneObjectFactory # circular import fix - objFactory = OOTCSMotionObjectFactory() + objFactory = CutsceneObjectFactory() context = bpy.context - bpy.context.scene.ootCSPreviewCSObj = csObj + bpy.context.scene.ootPreviewSettingsProperty.ootCSPreviewCSObj = csObj camObj = objFactory.getNewCameraObject( f"{csObj.name}.Camera", metersToBlend(context, 0.25), @@ -273,3 +286,12 @@ def setupCutscene(csObj: Object): context.scene.render.resolution_y = 240 context.scene.frame_set(context.scene.frame_start) context.scene.camera = camObj + + +def getCutsceneCamera(csObj: Object) -> Object | None: + cameraObj = None + for childObj in csObj.children: + if childObj.type == "CAMERA": + cameraObj = childObj + break + return cameraObj diff --git a/fast64_internal/oot/cutscene/operators.py b/fast64_internal/oot/cutscene/operators.py index 97426e9c7..bf02ff845 100644 --- a/fast64_internal/oot/cutscene/operators.py +++ b/fast64_internal/oot/cutscene/operators.py @@ -1,18 +1,19 @@ import os import re +import bpy from bpy.path import abspath from bpy.ops import object from bpy.props import StringProperty, EnumProperty, IntProperty -from bpy.types import Scene, Operator, Context, UILayout +from bpy.types import Scene, Operator, Context from bpy.utils import register_class, unregister_class from ...utility import CData, PluginError, writeCData, raisePluginError -from ..oot_utility import getCollection -from ..scene.exporter.to_c import ootCutsceneDataToC -from .exporter import convertCutsceneObject -from .constants import ootEnumCSTextboxType, ootEnumCSListType, ootEnumCSListTypeIcons -from .motion.importer import importCutsceneData -from .motion.exporter import getCutsceneMotionData +from ..oot_utility import getCollection, getCutsceneName +from ..oot_constants import ootData +from ..scene.exporter.to_c import getCutsceneC +from .constants import ootEnumCSTextboxType, ootEnumCSListType +from .importer import importCutsceneData +from .exporter import getNewCutsceneExport def checkGetFilePaths(context: Context): @@ -42,26 +43,6 @@ def ootCutsceneIncludes(headerfilename): return ret -def drawCSListAddOp(layout: UILayout, objName: str, collectionType): - def addButton(row): - nonlocal l - op = row.operator(OOTCSListAdd.bl_idname, text=ootEnumCSListType[l][1], icon=ootEnumCSListTypeIcons[l]) - op.collectionType = collectionType - op.listType = ootEnumCSListType[l][0] - op.objName = objName - l += 1 - - box = layout.column(align=True) - l = 0 - row = box.row(align=True) - row.label(text="Add:") - addButton(row) - for _ in range(3): - row = box.row(align=True) - for _ in range(3): - addButton(row) - - def insertCutsceneData(filePath: str, csName: str): """Inserts the motion data in the cutscene and returns the new data""" fileLines = [] @@ -76,7 +57,7 @@ def insertCutsceneData(filePath: str, csName: str): fileLines = [] foundCutscene = False - motionExporter = getCutsceneMotionData(csName, False) + motionExporter = getNewCutsceneExport(csName) beginIndex = 0 for i, line in enumerate(fileLines): @@ -125,7 +106,7 @@ def insertCutsceneData(filePath: str, csName: str): return fileData -class OOTCSTextboxAdd(Operator): +class OOTCSTextAdd(Operator): bl_idname = "object.oot_cstextbox_add" bl_label = "Add CS Textbox" bl_options = {"REGISTER", "UNDO"} @@ -198,12 +179,11 @@ def execute(self, context): cpath, hpath, headerfilename = checkGetFilePaths(context) csdata = ootCutsceneIncludes(headerfilename) - converted = convertCutsceneObject(activeObj) if context.scene.exportMotionOnly: csdata.append(insertCutsceneData(cpath, activeObj.name.removeprefix("Cutscene."))) else: - csdata.append(ootCutsceneDataToC(converted, converted.name)) + csdata.append(getCutsceneC(getCutsceneName(activeObj))) writeCData(csdata, hpath, cpath) self.report({"INFO"}, "Successfully exported cutscene") @@ -233,11 +213,10 @@ def execute(self, context): print(f"Parent: {obj.parent.name}, Object: {obj.name}") raise PluginError("Cutscene object must not be parented to anything") - converted = convertCutsceneObject(obj) if context.scene.exportMotionOnly: raise PluginError("ERROR: Not implemented yet.") else: - csdata.append(ootCutsceneDataToC(converted, converted.name)) + csdata.append(getCutsceneC(getCutsceneName(obj))) count += 1 if count == 0: @@ -251,12 +230,61 @@ def execute(self, context): return {"CANCELLED"} +class OOT_SearchCSDestinationEnumOperator(Operator): + bl_idname = "object.oot_search_cs_dest_enum_operator" + bl_label = "Choose Destination" + bl_property = "csDestination" + bl_options = {"REGISTER", "UNDO"} + + csDestination: EnumProperty(items=ootData.enumData.ootEnumCsDestination, default="cutscene_map_ganon_horse") + objName: StringProperty() + + def execute(self, context): + obj = bpy.data.objects[self.objName] + obj.ootCutsceneProperty.csDestination = self.csDestination + + context.region.tag_redraw() + self.report({"INFO"}, "Selected: " + self.csDestination) + return {"FINISHED"} + + def invoke(self, context, event): + context.window_manager.invoke_search_popup(self) + return {"RUNNING_MODAL"} + + +class OOT_SearchCSSeqOperator(Operator): + bl_idname = "object.oot_search_cs_seq_enum_operator" + bl_label = "Search Music Sequence" + bl_property = "seqId" + bl_options = {"REGISTER", "UNDO"} + + seqId: EnumProperty(items=ootData.enumData.ootEnumSeqId, default="general_sfx") + itemIndex: IntProperty() + listType: StringProperty() + + def execute(self, context): + csProp = context.view_layer.objects.active.ootCutsceneProperty + for elem in csProp.csLists: + if elem.listType == self.listType: + elem.seqList[self.itemIndex].csSeqID = self.seqId + break + context.region.tag_redraw() + self.report({"INFO"}, "Selected: " + self.seqId) + return {"FINISHED"} + + def invoke(self, context, event): + context.window_manager.invoke_search_popup(self) + return {"RUNNING_MODAL"} + + oot_cutscene_classes = ( - OOTCSTextboxAdd, + OOTCSTextAdd, OOTCSListAdd, OOT_ImportCutscene, OOT_ExportCutscene, OOT_ExportAllCutscenes, + OOT_SearchCSDestinationEnumOperator, + OOT_SearchCSSeqOperator, ) diff --git a/fast64_internal/oot/cutscene/panels.py b/fast64_internal/oot/cutscene/panels.py index 4a0dddbd9..76685144a 100644 --- a/fast64_internal/oot/cutscene/panels.py +++ b/fast64_internal/oot/cutscene/panels.py @@ -6,6 +6,14 @@ from .operators import OOT_ExportCutscene, OOT_ExportAllCutscenes, OOT_ImportCutscene +class OoT_PreviewSettingsPanel(OOT_Panel): + bl_idname = "OOT_PT_preview_settings" + bl_label = "OOT CS Preview Settings" + + def draw(self, context): + context.scene.ootPreviewSettingsProperty.draw_props(self.layout) + + class OOT_CutscenePanel(OOT_Panel): bl_idname = "OOT_PT_export_cutscene" bl_label = "OOT Cutscene Exporter" @@ -51,13 +59,17 @@ def draw(self, context): col.operator(OOT_ImportCutscene.bl_idname) -oot_cutscene_panel_classes = (OOT_CutscenePanel,) +oot_cutscene_panel_classes = ( + OoT_PreviewSettingsPanel, + OOT_CutscenePanel, +) def cutscene_panels_register(): Scene.useDecompFeatures = BoolProperty( name="Use Decomp for Export", description="Use names and macros from decomp when exporting", default=True ) + Scene.exportMotionOnly = BoolProperty( name="Export Motion Data Only", description=( diff --git a/fast64_internal/oot/cutscene/preview.py b/fast64_internal/oot/cutscene/preview.py index 7693099df..9d3a28830 100644 --- a/fast64_internal/oot/cutscene/preview.py +++ b/fast64_internal/oot/cutscene/preview.py @@ -4,6 +4,7 @@ from bpy.types import Scene, Object, Node from bpy.app.handlers import persistent from ...utility import gammaInverse, hexOrDecInt +from .motion.utility import getCutsceneCamera def getLerp(max: float, min: float, val: float): @@ -55,7 +56,7 @@ def setupCompositorNodes(): space.shading.use_compositor = "CAMERA" # if everything's fine and nodes are already ready to use stop there - if bpy.context.scene.ootCSPreviewNodesReady: + if bpy.context.scene.ootPreviewSettingsProperty.ootCSPreviewNodesReady: return # get the existing nodes @@ -93,7 +94,7 @@ def setupCompositorNodes(): # misc settings nodeMixRGBMisc.use_alpha = True nodeMixRGBMisc.blend_type = "COLOR" - bpy.context.scene.ootCSPreviewNodesReady = True + bpy.context.scene.ootPreviewSettingsProperty.ootCSPreviewNodesReady = True def initFirstFrame(csObj: Object, useNodeFeatures: bool, defaultCam: Object): @@ -121,7 +122,7 @@ def processCurrentFrame(csObj: Object, curFrame: float, useNodeFeatures: bool, c startFrame = transitionCmd.startFrame endFrame = transitionCmd.endFrame frameCur = curFrame - isTriggerInstance = transitionCmd.type == "CS_TRANS_TRIGGER_INSTANCE" + isTriggerInstance = transitionCmd.type == "trigger_instance" linear160 = getColor(160.0) if transitionCmd.type == "Unknown": @@ -144,24 +145,24 @@ def processCurrentFrame(csObj: Object, curFrame: float, useNodeFeatures: bool, c if isTriggerInstance: previewProp.trigger = True - if transitionCmd.type.endswith("IN"): + if transitionCmd.type.endswith("in"): alpha = linear255 * lerp else: alpha = (1.0 - lerp) * linear255 - if "HALF" in transitionCmd.type: - if "_IN_" in transitionCmd.type: + if "half" in transitionCmd.type: + if "_in_" in transitionCmd.type: alpha = linear255 - ((1.0 - lerp) * linear155) else: alpha = linear255 - (linear155 * lerp) - if "_GRAY_" in transitionCmd.type or previewProp.trigger: + if "gray_" in transitionCmd.type or previewProp.trigger: color[0] = color[1] = color[2] = linear160 * alpha - elif "_RED_" in transitionCmd.type: + elif "red_" in transitionCmd.type: color[0] = linear255 * alpha - elif "_GREEN_" in transitionCmd.type: + elif "green_" in transitionCmd.type: color[1] = linear255 * alpha - elif "_BLUE_" in transitionCmd.type: + elif "blue_" in transitionCmd.type: color[2] = linear255 * alpha color[3] = alpha @@ -175,11 +176,11 @@ def processCurrentFrame(csObj: Object, curFrame: float, useNodeFeatures: bool, c print("ERROR: Unknown command!") if curFrame == startFrame: - if miscCmd.type == "CS_MISC_SET_LOCKED_VIEWPOINT" and not None in cameraObjects: + if miscCmd.type == "set_locked_viewpoint" and not None in cameraObjects: bpy.context.scene.camera = cameraObjects[int(csObj.ootCutsceneProperty.preview.isFixedCamSet)] csObj.ootCutsceneProperty.preview.isFixedCamSet ^= True - if miscCmd.type == "CS_MISC_STOP_CUTSCENE": + elif miscCmd.type == "stop_cutscene": # stop the playback and set the frame to 0 bpy.ops.screen.animation_cancel() bpy.context.scene.frame_set(bpy.context.scene.frame_start) @@ -189,8 +190,8 @@ def processCurrentFrame(csObj: Object, curFrame: float, useNodeFeatures: bool, c color = [0.0, 0.0, 0.0, 0.0] lerp = getLerp(endFrame - 1, startFrame, curFrame) - if miscCmd.type in ["CS_MISC_VISMONO_SEPIA", "CS_MISC_VISMONO_BLACK_AND_WHITE"]: - if miscCmd.type == "CS_MISC_VISMONO_SEPIA": + if miscCmd.type in ["vismono_sepia", "vismono_black_and_white"]: + if miscCmd.type == "vismono_sepia": col = [255.0, 180.0, 100.0] else: col = [255.0, 255.0, 254.0] @@ -201,7 +202,7 @@ def processCurrentFrame(csObj: Object, curFrame: float, useNodeFeatures: bool, c color[3] = getColor(255.0) * lerp bpy.context.scene.node_tree.nodes["CSMisc_RGB"].outputs[0].default_value = color - if miscCmd.type == "CS_MISC_RED_PULSATING_LIGHTS": + elif miscCmd.type == "red_pulsating_lights": color = bpy.context.scene.node_tree.nodes["CSMisc_RGB"].outputs[0].default_value color[0] = getColor(255.0) color[1] = color[2] = 0.0 @@ -218,17 +219,14 @@ def processCurrentFrame(csObj: Object, curFrame: float, useNodeFeatures: bool, c @persistent def cutscenePreviewFrameHandler(scene: Scene): """Preview frame handler, executes each frame when the cutscene is played""" - csObj: Object = bpy.context.scene.ootCSPreviewCSObj + previewSettings = scene.ootPreviewSettingsProperty + csObj: Object = previewSettings.ootCSPreviewCSObj if csObj is None or not csObj.type == "EMPTY" and not csObj.ootEmptyType == "Cutscene": return # populate ``cameraObjects`` with the cutscene camera and the first found prerend fixed camera - cameraObjects = [None, None] - for obj in csObj.children: - if obj.type == "CAMERA": - cameraObjects[1] = obj - break + cameraObjects = [None, getCutsceneCamera(csObj)] foundObj = None for obj in bpy.data.objects: @@ -251,18 +249,34 @@ def cutscenePreviewFrameHandler(scene: Scene): cameraObjects[0] = foundObj # setup nodes - bpy.context.scene.ootCSPreviewNodesReady = False + previewSettings.ootCSPreviewNodesReady = False setupCompositorNodes() + previewProp = csObj.ootCutsceneProperty.preview + + # set preview properties + previewProp.miscList.clear() + previewProp.transitionList.clear() + for item in csObj.ootCutsceneProperty.csLists: + if item.listType == "Transition": + newProp = previewProp.transitionList.add() + newProp.startFrame = item.transitionStartFrame + newProp.endFrame = item.transitionEndFrame + newProp.type = item.transitionType + elif item.listType == "MiscList": + for miscEntry in item.miscList: + newProp = previewProp.miscList.add() + newProp.startFrame = miscEntry.startFrame + newProp.endFrame = miscEntry.endFrame + newProp.type = miscEntry.csMiscType # execute the main preview logic - previewProp = csObj.ootCutsceneProperty.preview curFrame = bpy.context.scene.frame_current if isclose(curFrame, previewProp.prevFrame, abs_tol=1) and isclose(curFrame, previewProp.nextFrame, abs_tol=1): - processCurrentFrame(csObj, curFrame, bpy.context.scene.ootCSPreviewNodesReady, cameraObjects) + processCurrentFrame(csObj, curFrame, previewSettings.ootCSPreviewNodesReady, cameraObjects) else: # Simulate cutscene for all frames up to present for i in range(bpy.context.scene.frame_current): - processCurrentFrame(csObj, i, bpy.context.scene.ootCSPreviewNodesReady, cameraObjects) + processCurrentFrame(csObj, i, previewSettings.ootCSPreviewNodesReady, cameraObjects) # since we reached the end of the function, the current frame becomes the previous one previewProp.nextFrame = curFrame + 2 if curFrame > previewProp.prevFrame else curFrame - 2 diff --git a/fast64_internal/oot/cutscene/properties.py b/fast64_internal/oot/cutscene/properties.py index f6f56250f..f2f6c3c58 100644 --- a/fast64_internal/oot/cutscene/properties.py +++ b/fast64_internal/oot/cutscene/properties.py @@ -1,34 +1,39 @@ -import bpy from bpy.types import PropertyGroup, Object, UILayout, Scene, Context from bpy.props import StringProperty, EnumProperty, IntProperty, BoolProperty, CollectionProperty, PointerProperty from bpy.utils import register_class, unregister_class from ...utility import PluginError, prop_split -from ..oot_utility import OOTCollectionAdd, drawCollectionOps -from .operators import OOTCSTextboxAdd, drawCSListAddOp -from .constants import ootEnumCSTextboxType, ootEnumCSListType, ootEnumCSTransitionType, ootEnumCSTextboxTypeIcons +from ..oot_utility import OOTCollectionAdd, drawCollectionOps, getEnumName +from ..oot_constants import ootData +from ..oot_upgrade import upgradeCutsceneSubProps, upgradeCSListProps, upgradeCutsceneProperty +from .operators import OOTCSTextAdd, OOT_SearchCSDestinationEnumOperator, OOTCSListAdd, OOT_SearchCSSeqOperator from .motion.preview import previewFrameHandler +from .motion.utility import getCutsceneCamera from .motion.operators import ( - OOTCSMotionPlayPreview, - OOTCSMotionCreateCameraShot, - OOTCSMotionCreatePlayerCueList, - OOTCSMotionCreateActorCueList, + CutsceneCmdPlayPreview, + CutsceneCmdCreateCameraShot, + CutsceneCmdCreatePlayerCueList, + CutsceneCmdCreateActorCueList, +) + +from .constants import ( + ootEnumCSTextboxType, + ootEnumCSListType, + ootEnumCSTextboxTypeIcons, + ootCSSubPropToName, + csListTypeToIcon, ) -# Perhaps this should have been called something like OOTCSParentPropertyType, -# but now it needs to keep the same name to not break existing scenes which use -# the cutscene system. -class OOTCSProperty: - propName = None +class OOTCutsceneCommon: attrName = None subprops = ["startFrame", "endFrame"] expandTab: BoolProperty(default=True) startFrame: IntProperty(name="", default=0, min=0) - endFrame: IntProperty(name="", default=1, min=0) + endFrame: IntProperty(name="", default=0, min=0) def getName(self): - return self.propName + pass def filterProp(self, name, listProp): return True @@ -36,197 +41,244 @@ def filterProp(self, name, listProp): def filterName(self, name, listProp): return name - def draw(self, layout, listProp, listIndex, cmdIndex, objName, collectionType): - layout.prop( + def draw_props( + self, + layout: UILayout, + listProp: "OOTCSListProperty", + listIndex: int, + cmdIndex: int, + objName: str, + collectionType: str, + tabName: str, + ): + # Draws list elements + box = layout.box().column() + + box.prop( self, "expandTab", - text=self.getName() + " " + str(cmdIndex), + text=f"{tabName if tabName != 'Text' else self.getName()} No. {cmdIndex}", icon="TRIA_DOWN" if self.expandTab else "TRIA_RIGHT", ) if not self.expandTab: return - box = layout.box().column() + drawCollectionOps(box, cmdIndex, collectionType + "." + self.attrName, listIndex, objName) + for p in self.subprops: if self.filterProp(p, listProp): - prop_split(box, self, p, self.filterName(p, listProp)) + name = self.filterName(p, listProp) + displayName = ootCSSubPropToName[name] + if name == "csSeqPlayer": + # change the property name to draw the other enum for fade seq command + p = name -class OOTCSTextboxProperty(OOTCSProperty, PropertyGroup): - propName = "Textbox" - attrName = "textbox" + prop_split(box, self, p, displayName) + + if name == "csSeqID": + seqOp = box.operator(OOT_SearchCSSeqOperator.bl_idname) + seqOp.itemIndex = cmdIndex + seqOp.listType = listProp.listType + + customValues = [ + "csMiscType", + "csTextType", + "ocarinaAction", + "csSeqID", + "csSeqPlayer", + ] + value = getattr(self, p) + if name in customValues and value == "Custom": + prop_split(box, self, f"{name}Custom", f"{displayName} Custom") + + if name == "csTextType" and value != "choice": + break + + +class OOTCSTextProperty(OOTCutsceneCommon, PropertyGroup): + attrName = "textList" subprops = [ - "messageId", - "ocarinaSongAction", + "textID", + "ocarinaAction", "startFrame", "endFrame", - "type", - "topOptionBranch", - "bottomOptionBranch", + "csTextType", + "topOptionTextID", + "bottomOptionTextID", "ocarinaMessageId", ] textboxType: EnumProperty(items=ootEnumCSTextboxType) - messageId: StringProperty(name="", default="0x0000") - ocarinaSongAction: StringProperty(name="", default="0x0000") - type: StringProperty(name="", default="0x0000") - topOptionBranch: StringProperty(name="", default="0x0000") - bottomOptionBranch: StringProperty(name="", default="0x0000") + + # subprops + textID: StringProperty(name="", default="0x0000") + ocarinaAction: EnumProperty( + name="Ocarina Action", items=ootData.enumData.ootEnumOcarinaSongActionId, default="teach_minuet" + ) + ocarinaActionCustom: StringProperty(default="OCARINA_ACTION_CUSTOM") + topOptionTextID: StringProperty(name="", default="0x0000") + bottomOptionTextID: StringProperty(name="", default="0x0000") ocarinaMessageId: StringProperty(name="", default="0x0000") + csTextType: EnumProperty(name="Text Type", items=ootData.enumData.ootEnumCsTextType, default="normal") + csTextTypeCustom: StringProperty(default="CS_TEXT_CUSTOM") def getName(self): - return self.textboxType + return getEnumName(ootEnumCSTextboxType, self.textboxType) def filterProp(self, name, listProp): if self.textboxType == "Text": - return name not in ["ocarinaSongAction", "ocarinaMessageId"] + return name not in ["ocarinaAction", "ocarinaMessageId"] elif self.textboxType == "None": return name in ["startFrame", "endFrame"] - elif self.textboxType == "LearnSong": - return name in ["ocarinaSongAction", "startFrame", "endFrame", "ocarinaMessageId"] + elif self.textboxType == "OcarinaAction": + return name in ["ocarinaAction", "startFrame", "endFrame", "ocarinaMessageId"] else: - raise PluginError("Invalid property name for OOTCSTextboxProperty") + raise PluginError("Invalid property name for OOTCSTextProperty") -class OOTCSLightingProperty(OOTCSProperty, PropertyGroup): - propName = "Lighting" - attrName = "lighting" - subprops = ["index", "startFrame"] - index: IntProperty(name="", default=1, min=1) +class OOTCSLightSettingsProperty(OOTCutsceneCommon, PropertyGroup): + attrName = "lightSettingsList" + subprops = ["lightSettingsIndex", "startFrame"] + lightSettingsIndex: IntProperty(name="", default=0, min=0) -class OOTCSTimeProperty(OOTCSProperty, PropertyGroup): - propName = "Time" - attrName = "time" +class OOTCSTimeProperty(OOTCutsceneCommon, PropertyGroup): + attrName = "timeList" subprops = ["startFrame", "hour", "minute"] hour: IntProperty(name="", default=23, min=0, max=23) minute: IntProperty(name="", default=59, min=0, max=59) -class OOTCSBGMProperty(OOTCSProperty, PropertyGroup): - propName = "BGM" - attrName = "bgm" - subprops = ["value", "startFrame", "endFrame"] - value: StringProperty(name="", default="0x0000") +class OOTCSSeqProperty(OOTCutsceneCommon, PropertyGroup): + attrName = "seqList" + subprops = ["csSeqID", "startFrame", "endFrame"] + csSeqID: EnumProperty(name="Seq ID", items=ootData.enumData.ootEnumSeqId, default="general_sfx") + csSeqIDCustom: StringProperty(default="NA_BGM_CUSTOM") + csSeqPlayer: EnumProperty( + name="Seq Player", items=ootData.enumData.ootEnumCsFadeOutSeqPlayer, default="fade_out_fanfare" + ) + csSeqPlayerCustom: StringProperty(default="CS_FADE_OUT_CUSTOM") def filterProp(self, name, listProp): - return name != "endFrame" or listProp.listType == "FadeBGM" + return name != "endFrame" or listProp.listType == "FadeOutSeqList" def filterName(self, name, listProp): - if name == "value": - return "Fade Type" if listProp.listType == "FadeBGM" else "Sequence" + if name == "csSeqID" and listProp.listType == "FadeOutSeqList": + return "csSeqPlayer" return name -class OOTCSMiscProperty(OOTCSProperty, PropertyGroup): - propName = "Misc" - attrName = "misc" - subprops = ["operation", "startFrame", "endFrame"] - operation: IntProperty(name="", default=1, min=1, max=35) - - -class OOTCS0x09Property(OOTCSProperty, PropertyGroup): - propName = "0x09" - attrName = "nine" - subprops = ["startFrame", "unk2", "unk3", "unk4"] - unk2: StringProperty(name="", default="0x00") - unk3: StringProperty(name="", default="0x00") - unk4: StringProperty(name="", default="0x00") - - -class OOTCSUnkProperty(OOTCSProperty, PropertyGroup): - propName = "Unk" - attrName = "unk" - subprops = ["unk1", "unk2", "unk3", "unk4", "unk5", "unk6", "unk7", "unk8", "unk9", "unk10", "unk11", "unk12"] - unk1: StringProperty(name="", default="0x00000000") - unk2: StringProperty(name="", default="0x00000000") - unk3: StringProperty(name="", default="0x00000000") - unk4: StringProperty(name="", default="0x00000000") - unk5: StringProperty(name="", default="0x00000000") - unk6: StringProperty(name="", default="0x00000000") - unk7: StringProperty(name="", default="0x00000000") - unk8: StringProperty(name="", default="0x00000000") - unk9: StringProperty(name="", default="0x00000000") - unk10: StringProperty(name="", default="0x00000000") - unk11: StringProperty(name="", default="0x00000000") - unk12: StringProperty(name="", default="0x00000000") +class OOTCSMiscProperty(OOTCutsceneCommon, PropertyGroup): + attrName = "miscList" + subprops = ["csMiscType", "startFrame", "endFrame"] + csMiscType: EnumProperty(name="Type", items=ootData.enumData.ootEnumCsMiscType, default="rain") + csMiscTypeCustom: StringProperty(default="CS_MISC_CUSTOM") + + +class OOTCSRumbleProperty(OOTCutsceneCommon, PropertyGroup): + attrName = "rumbleList" + subprops = ["startFrame", "rumbleSourceStrength", "rumbleDuration", "rumbleDecreaseRate"] + + # those variables are unsigned chars in decomp + # see https://github.com/zeldaret/oot/blob/542012efa68d110d6b631f9d149f6e5f4e68cc8e/src/code/z_rumble.c#L58-L77 + rumbleSourceStrength: IntProperty(name="", default=0, min=0, max=255) + rumbleDuration: IntProperty(name="", default=0, min=0, max=255) + rumbleDecreaseRate: IntProperty(name="", default=0, min=0, max=255) class OOTCSListProperty(PropertyGroup): expandTab: BoolProperty(default=True) listType: EnumProperty(items=ootEnumCSListType) - textbox: CollectionProperty(type=OOTCSTextboxProperty) - lighting: CollectionProperty(type=OOTCSLightingProperty) - time: CollectionProperty(type=OOTCSTimeProperty) - bgm: CollectionProperty(type=OOTCSBGMProperty) - misc: CollectionProperty(type=OOTCSMiscProperty) - nine: CollectionProperty(type=OOTCS0x09Property) - unk: CollectionProperty(type=OOTCSUnkProperty) - - unkType: StringProperty(name="", default="0x0001") - fxType: EnumProperty(items=ootEnumCSTransitionType) - fxStartFrame: IntProperty(name="", default=0, min=0) - fxEndFrame: IntProperty(name="", default=1, min=0) + textList: CollectionProperty(type=OOTCSTextProperty) + lightSettingsList: CollectionProperty(type=OOTCSLightSettingsProperty) + timeList: CollectionProperty(type=OOTCSTimeProperty) + seqList: CollectionProperty(type=OOTCSSeqProperty) + miscList: CollectionProperty(type=OOTCSMiscProperty) + rumbleList: CollectionProperty(type=OOTCSRumbleProperty) + + transitionType: EnumProperty(items=ootData.enumData.ootEnumCsTransitionType, default="gray_fill_in") + transitionTypeCustom: StringProperty(default="CS_TRANS_CUSTOM") + transitionStartFrame: IntProperty(name="", default=0, min=0) + transitionEndFrame: IntProperty(name="", default=1, min=0) def draw_props(self, layout: UILayout, listIndex: int, objName: str, collectionType: str): - layout.prop( + box = layout.box().column() + enumName = getEnumName(ootEnumCSListType, self.listType) + + # Draw current command tab + box.prop( self, "expandTab", - text=self.listType + " List" if self.listType != "FX" else "Scene Trans FX", + text=enumName, icon="TRIA_DOWN" if self.expandTab else "TRIA_RIGHT", ) + if not self.expandTab: return - box = layout.box().column() + drawCollectionOps(box, listIndex, collectionType, None, objName, False) - if self.listType == "Textbox": - attrName = "textbox" - elif self.listType == "FX": - prop_split(box, self, "fxType", "Transition") - prop_split(box, self, "fxStartFrame", "Start Frame") - prop_split(box, self, "fxEndFrame", "End Frame") + # Draw current command content + if self.listType == "TextList": + attrName = "textList" + elif self.listType == "Transition": + prop_split(box, self, "transitionType", "Transition Type") + if self.transitionType == "Custom": + prop_split(box, self, "transitionTypeCustom", "Transition Type Custom") + + prop_split(box, self, "transitionStartFrame", "Start Frame") + prop_split(box, self, "transitionEndFrame", "End Frame") return - elif self.listType == "Lighting": - attrName = "lighting" - elif self.listType == "Time": - attrName = "time" - elif self.listType in ["PlayBGM", "StopBGM", "FadeBGM"]: - attrName = "bgm" - elif self.listType == "Misc": - attrName = "misc" - elif self.listType == "0x09": - attrName = "nine" - elif self.listType == "Unk": - prop_split(box, self, "unkType", "Unk List Type") - attrName = "unk" + elif self.listType == "LightSettingsList": + attrName = "lightSettingsList" + elif self.listType == "TimeList": + attrName = "timeList" + elif self.listType in ["StartSeqList", "StopSeqList", "FadeOutSeqList"]: + attrName = "seqList" + elif self.listType == "MiscList": + attrName = "miscList" + elif self.listType == "RumbleList": + attrName = "rumbleList" else: raise PluginError("Internal error: invalid listType " + self.listType) - dat = getattr(self, attrName) - for i, p in enumerate(dat): - p.draw(box, self, listIndex, i, objName, collectionType) - if len(dat) == 0: - box.label(text="No items in " + self.listType + " List.") - if self.listType == "Textbox": - row = box.row(align=True) + data = getattr(self, attrName) + + if self.listType == "TextList": + subBox = box.box() + subBox.label(text="TextBox Commands") + row = subBox.row(align=True) + for l in range(3): addOp = row.operator( - OOTCSTextboxAdd.bl_idname, + OOTCSTextAdd.bl_idname, text="Add " + ootEnumCSTextboxType[l][1], icon=ootEnumCSTextboxTypeIcons[l], ) - addOp.collectionType = collectionType + ".textbox" + + addOp.collectionType = collectionType + ".textList" addOp.textboxType = ootEnumCSTextboxType[l][0] addOp.listIndex = listIndex addOp.objName = objName else: - addOp = box.operator(OOTCollectionAdd.bl_idname, text="Add item to " + self.listType + " List") - addOp.option = len(dat) + addOp = box.operator( + OOTCollectionAdd.bl_idname, text="Add item to " + getEnumName(ootEnumCSListType, self.listType) + ) + addOp.option = len(data) addOp.collectionType = collectionType + "." + attrName addOp.subIndex = listIndex addOp.objName = objName + for i, p in enumerate(data): + # ``p`` type: + # OOTCSTextProperty | OOTCSLightSettingsProperty | OOTCSTimeProperty | + # OOTCSSeqProperty | OOTCSMiscProperty | OOTCSRumbleProperty + p.draw_props(box, self, listIndex, i, objName, collectionType, enumName.removesuffix(" List")) + + if len(data) == 0: + box.label(text="No items in " + getEnumName(ootEnumCSListType, self.listType)) + class OOTCutsceneCommandBase: startFrame: IntProperty(min=0) @@ -242,10 +294,6 @@ class OOTCutsceneMiscProperty(OOTCutsceneCommandBase, PropertyGroup): class OOTCutscenePreviewProperty(PropertyGroup): - useWidescreen: BoolProperty( - name="Use Widescreen Camera", default=False, update=lambda self, context: self.updateWidescreen(context) - ) - transitionList: CollectionProperty(type=OOTCutsceneTransitionProperty) miscList: CollectionProperty(type=OOTCutsceneMiscProperty) @@ -254,6 +302,30 @@ class OOTCutscenePreviewProperty(PropertyGroup): prevFrame: IntProperty(default=-1) nextFrame: IntProperty(default=1) + +class OOTCutscenePreviewSettingsProperty(PropertyGroup): + useWidescreen: BoolProperty( + name="Use Widescreen Camera", default=False, update=lambda self, context: self.updateWidescreen(context) + ) + + useOpaqueCamBg: BoolProperty( + name="Use Opaque Camera Background", + description="Can be used to simulate the letterbox with widescreen mode enabled", + default=False, + update=lambda self, context: self.updateCamBackground(context), + ) + + previewPlayerAge: EnumProperty( + items=[("link_adult", "Adult", "Adult Link (170 cm)", 0), ("link_child", "Child", "Child Link (130 cm)", 1)], + name="Player Age for Preview", + description="For setting Link's height for preview", + default="link_adult", + ) + + # internal only + ootCSPreviewNodesReady: BoolProperty(default=False) + ootCSPreviewCSObj: PointerProperty(type=Object) + def updateWidescreen(self, context: Context): if self.useWidescreen: context.scene.render.resolution_x = 426 @@ -264,57 +336,106 @@ def updateWidescreen(self, context: Context): # force a refresh of the current frame previewFrameHandler(context.scene) + def updateCamBackground(self, context: Context): + camObj = getCutsceneCamera(context.view_layer.objects.active) + if camObj is not None: + if self.useOpaqueCamBg: + camObj.data.passepartout_alpha = 1.0 + else: + camObj.data.passepartout_alpha = 0.95 + + def draw_props(self, layout: UILayout): + previewBox = layout.box() + previewBox.box().label(text="Preview Settings") + prop_split(previewBox, self, "previewPlayerAge", "Player Age for Preview") + previewBox.prop(self, "useWidescreen") + previewBox.prop(self, "useOpaqueCamBg") + class OOTCutsceneProperty(PropertyGroup): csEndFrame: IntProperty(name="End Frame", min=0, default=100) - csWriteTerminator: BoolProperty(name="Write Terminator (Code Execution)") - csTermIdx: IntProperty(name="Index", min=0) - csTermStart: IntProperty(name="Start Frm", min=0, default=99) - csTermEnd: IntProperty(name="End Frm", min=0, default=100) + csUseDestination: BoolProperty(name="Cutscene Destination (Scene Change)") + csDestination: EnumProperty( + name="Destination", items=ootData.enumData.ootEnumCsDestination, default="cutscene_map_ganon_horse" + ) + csDestinationCustom: StringProperty(default="CS_DEST_CUSTOM") + csDestinationStartFrame: IntProperty(name="Start Frame", min=0, default=99) csLists: CollectionProperty(type=OOTCSListProperty, name="Cutscene Lists") + menuTab: EnumProperty(items=ootEnumCSListType) preview: PointerProperty(type=OOTCutscenePreviewProperty) + @staticmethod + def upgrade_object(obj): + print(f"Processing '{obj.name}'...") + + # using the new names since the old ones will be deleted before this is used + csListsNames = ["textList", "lightSettingsList", "timeList", "seqList", "miscList", "rumbleList"] + + csProp: "OOTCutsceneProperty" = obj.ootCutsceneProperty + upgradeCutsceneProperty(csProp) + + for csListProp in csProp.csLists: + upgradeCSListProps(csListProp) + + for listName in csListsNames: + for csListSubProp in getattr(csListProp, listName): + upgradeCutsceneSubProps(csListSubProp) + def draw_props(self, layout: UILayout, obj: Object): split = layout.split(factor=0.5) - split.label(text="Player Age for Preview") - split.prop(bpy.context.scene, "previewPlayerAge", text="") + split.operator(CutsceneCmdCreateCameraShot.bl_idname, icon="VIEW_CAMERA") + split.operator(CutsceneCmdPlayPreview.bl_idname, icon="RESTRICT_VIEW_OFF") split = layout.split(factor=0.5) - split.operator(OOTCSMotionCreateCameraShot.bl_idname, icon="VIEW_CAMERA") - split.operator(OOTCSMotionPlayPreview.bl_idname, icon="RESTRICT_VIEW_OFF") + split.operator(CutsceneCmdCreatePlayerCueList.bl_idname) + split.operator(CutsceneCmdCreateActorCueList.bl_idname) split = layout.split(factor=0.5) - split.operator(OOTCSMotionCreatePlayerCueList.bl_idname) - split.operator(OOTCSMotionCreateActorCueList.bl_idname) - - layout.prop(self.preview, "useWidescreen") - - layout.prop(self, "csEndFrame") - layout.prop(self, "csWriteTerminator") - if self.csWriteTerminator: - r = layout.row() - r.prop(self, "csTermIdx") - r.prop(self, "csTermStart") - r.prop(self, "csTermEnd") - for i, p in enumerate(self.csLists): - p.draw_props(layout, i, obj.name, "Cutscene") - - drawCSListAddOp(layout, obj.name, "Cutscene") + split.label(text="Cutscene End Frame") + split.prop(self, "csEndFrame") + + commandsBox = layout.box() + commandsBox.box().label(text="Cutscene Commands") + + b = commandsBox.box() + b.prop(self, "csUseDestination") + if self.csUseDestination: + b.prop(self, "csDestinationStartFrame") + + searchBox = b.box() + boxRow = searchBox.row() + searchOp = boxRow.operator(OOT_SearchCSDestinationEnumOperator.bl_idname, icon="VIEWZOOM", text="") + searchOp.objName = obj.name + boxRow.label(text=getEnumName(ootData.enumData.ootEnumCsDestination, self.csDestination)) + if self.csDestination == "Custom": + prop_split(searchBox.column(), self, "csDestinationCustom", "Cutscene Destination Custom") + + commandsBox.column_flow(columns=3, align=True).prop(self, "menuTab", expand=True) + label = f"Add New {ootCSSubPropToName[self.menuTab]}" + op = commandsBox.operator(OOTCSListAdd.bl_idname, text=label, icon=csListTypeToIcon[self.menuTab]) + op.collectionType = "Cutscene" + op.listType = self.menuTab + op.objName = obj.name + + for i, csListProp in enumerate(self.csLists): + # ``csListProp`` type: OOTCSListProperty + if csListProp.listType == self.menuTab: + csListProp.draw_props(commandsBox, i, obj.name, "Cutscene") classes = ( - OOTCSTextboxProperty, - OOTCSLightingProperty, + OOTCSTextProperty, + OOTCSLightSettingsProperty, OOTCSTimeProperty, - OOTCSBGMProperty, + OOTCSSeqProperty, OOTCSMiscProperty, - OOTCS0x09Property, - OOTCSUnkProperty, + OOTCSRumbleProperty, OOTCSListProperty, OOTCutsceneTransitionProperty, OOTCutsceneMiscProperty, OOTCutscenePreviewProperty, + OOTCutscenePreviewSettingsProperty, OOTCutsceneProperty, ) @@ -324,13 +445,11 @@ def cutscene_props_register(): register_class(cls) Object.ootCutsceneProperty = PointerProperty(type=OOTCutsceneProperty) - Scene.ootCSPreviewNodesReady = BoolProperty(default=False) - Scene.ootCSPreviewCSObj = PointerProperty(type=Object) + Scene.ootPreviewSettingsProperty = PointerProperty(type=OOTCutscenePreviewSettingsProperty) def cutscene_props_unregister(): - del Scene.ootCSPreviewCSObj - del Scene.ootCSPreviewNodesReady + del Scene.ootPreviewSettingsProperty del Object.ootCutsceneProperty for cls in reversed(classes): diff --git a/fast64_internal/oot/data/oot_enum_data.py b/fast64_internal/oot/data/oot_enum_data.py index df5cf7e5f..0644c6f4b 100644 --- a/fast64_internal/oot/data/oot_enum_data.py +++ b/fast64_internal/oot/data/oot_enum_data.py @@ -8,7 +8,34 @@ @dataclass class OoT_ItemElement(OoT_BaseElement): - pass + parentKey: str + + def __post_init__(self): + # generate the name from the id + + if self.name is None: + keyToPrefix = { + "csCmd": "CS_CMD", + "csMiscType": "CS_MISC", + "csTextType": "CS_TEXT", + "csFadeOutSeqPlayer": "CS_FADE_OUT", + "csTransitionType": "CS_TRANS", + "csDestination": "CS_DEST", + "csPlayerCueId": "PLAYER_CUEID", + "naviQuestHintType": "NAVI_QUEST_HINTS", + "ocarinaSongActionId": "OCARINA_ACTION", + } + + self.name = self.id.removeprefix(f"{keyToPrefix[self.parentKey]}_") + + if self.parentKey in ["csCmd", "csPlayerCueId"]: + split = self.name.split("_") + if self.parentKey == "csCmd" and "ACTOR_CUE" in self.id: + self.name = f"Actor Cue {split[-2]}_{split[-1]}" + else: + self.name = f"Player Cue Id {split[-1]}" + else: + self.name = self.name.replace("_", " ").title() @dataclass @@ -46,8 +73,10 @@ def __init__(self): OoT_ItemElement( item.attrib["ID"], item.attrib["Key"], - item.attrib["ID"], + # note: the name sets automatically after the init if None + item.attrib["Name"] if enum.attrib["Key"] == "seqId" else None, int(item.attrib["Index"]), + enum.attrib["Key"], ) for item in enum ], @@ -65,6 +94,8 @@ def __init__(self): self.ootEnumCsDestination: list[tuple[str, str, str]] = [] self.ootEnumCsPlayerCueId: list[tuple[str, str, str]] = [] self.ootEnumNaviQuestHintType: list[tuple[str, str, str]] = [] + self.ootEnumOcarinaSongActionId: list[tuple[str, str, str]] = [] + self.ootEnumSeqId: list[tuple[str, str, str]] = [] self.enumByID = {enum.id: enum for enum in self.enumDataList} self.enumByKey = {enum.key: enum for enum in self.enumDataList} diff --git a/fast64_internal/oot/data/xml/EnumData.xml b/fast64_internal/oot/data/xml/EnumData.xml index 96277683e..c379af7a4 100644 --- a/fast64_internal/oot/data/xml/EnumData.xml +++ b/fast64_internal/oot/data/xml/EnumData.xml @@ -13,6 +13,7 @@ Parameters: * Key: identifier that should never be changed * ID: the actual enum name from decomp, this may change hence the need of the Key parameter + * Name: a descriptive name about the current item * Index: the location of the item in the enum (should match decomp) Notes: @@ -429,4 +430,172 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fast64_internal/oot/oot_constants.py b/fast64_internal/oot/oot_constants.py index a3feafe52..6c1fbcc66 100644 --- a/fast64_internal/oot/oot_constants.py +++ b/fast64_internal/oot/oot_constants.py @@ -118,114 +118,124 @@ ] ootEnumMusicSeq = [ + # see https://github.com/zeldaret/oot/blob/9f09505d34619883748a7dab05071883281c14fd/include/sequence.h#L4-L118 ("Custom", "Custom", "Custom"), - ("0x02", "Hyrule Field", "Hyrule Field"), - ("0x03", "Hyrule Field (Initial Segment From Loading Area)", "Hyrule Field (Initial Segment From Loading Area)"), - ("0x04", "Hyrule Field (Moving Segment 1)", "Hyrule Field (Moving Segment 1)"), - ("0x05", "Hyrule Field (Moving Segment 2)", "Hyrule Field (Moving Segment 2)"), - ("0x06", "Hyrule Field (Moving Segment 3)", "Hyrule Field (Moving Segment 3)"), - ("0x07", "Hyrule Field (Moving Segment 4)", "Hyrule Field (Moving Segment 4)"), - ("0x08", "Hyrule Field (Moving Segment 5)", "Hyrule Field (Moving Segment 5)"), - ("0x09", "Hyrule Field (Moving Segment 6)", "Hyrule Field (Moving Segment 6)"), - ("0x0A", "Hyrule Field (Moving Segment 7)", "Hyrule Field (Moving Segment 7)"), - ("0x0B", "Hyrule Field (Moving Segment 8)", "Hyrule Field (Moving Segment 8)"), - ("0x0C", "Hyrule Field (Moving Segment 9)", "Hyrule Field (Moving Segment 9)"), - ("0x0D", "Hyrule Field (Moving Segment 10)", "Hyrule Field (Moving Segment 10)"), - ("0x0E", "Hyrule Field (Moving Segment 11)", "Hyrule Field (Moving Segment 11)"), - ("0x0F", "Hyrule Field (Enemy Approaches)", "Hyrule Field (Enemy Approaches)"), - ("0x10", "Hyrule Field (Enemy Near Segment 1)", "Hyrule Field (Enemy Near Segment 1)"), - ("0x11", "Hyrule Field (Enemy Near Segment 2)", "Hyrule Field (Enemy Near Segment 2)"), - ("0x12", "Hyrule Field (Enemy Near Segment 3)", "Hyrule Field (Enemy Near Segment 3)"), - ("0x13", "Hyrule Field (Enemy Near Segment 4)", "Hyrule Field (Enemy Near Segment 4)"), - ("0x14", "Hyrule Field (Standing Still Segment 1)", "Hyrule Field (Standing Still Segment 1)"), - ("0x15", "Hyrule Field (Standing Still Segment 2)", "Hyrule Field (Standing Still Segment 2)"), - ("0x16", "Hyrule Field (Standing Still Segment 3)", "Hyrule Field (Standing Still Segment 3)"), - ("0x17", "Hyrule Field (Standing Still Segment 4)", "Hyrule Field (Standing Still Segment 4)"), - ("0x18", "Dodongo's Cavern", "Dodongo's Cavern"), - ("0x19", "Kakariko Village (Adult)", "Kakariko Village (Adult)"), - ("0x1A", "Enemy Battle", "Enemy Battle"), - ("0x1B", "Boss Battle 00", "Boss Battle 00"), - ("0x1C", "Inside the Deku Tree", "Inside the Deku Tree"), - ("0x1D", "Market", "Market"), - ("0x1E", "Title Theme", "Title Theme"), - ("0x1F", "Link's House", "Link's House"), - ("0x20", "Game Over", "Game Over"), - ("0x21", "Boss Clear", "Boss Clear"), - ("0x22", "Item Get", "Item Get"), - ("0x23", "Opening Ganon", "Opening Ganon"), - ("0x24", "Heart Get", "Heart Get"), - ("0x25", "Prelude Of Light", "Prelude Of Light"), - ("0x26", "Inside Jabu-Jabu's Belly", "Inside Jabu-Jabu's Belly"), - ("0x27", "Kakariko Village (Child)", "Kakariko Village (Child)"), - ("0x28", "Great Fairy's Fountain", "Great Fairy's Fountain"), - ("0x29", "Zelda's Theme", "Zelda's Theme"), - ("0x2A", "Fire Temple", "Fire Temple"), - ("0x2B", "Open Treasure Chest", "Open Treasure Chest"), - ("0x2C", "Forest Temple", "Forest Temple"), - ("0x2D", "Hyrule Castle Courtyard", "Hyrule Castle Courtyard"), - ("0x2E", "Ganondorf's Theme", "Ganondorf's Theme"), - ("0x2F", "Lon Lon Ranch", "Lon Lon Ranch"), - ("0x30", "Goron City", "Goron City "), - ("0x31", "Hyrule Field Morning Theme", "Hyrule Field Morning Theme"), - ("0x32", "Spiritual Stone Get", "Spiritual Stone Get"), - ("0x33", "Bolero of Fire", "Bolero of Fire"), - ("0x34", "Minuet of Woods", "Minuet of Woods"), - ("0x35", "Serenade of Water", "Serenade of Water"), - ("0x36", "Requiem of Spirit", "Requiem of Spirit"), - ("0x37", "Nocturne of Shadow", "Nocturne of Shadow"), - ("0x38", "Mini-Boss Battle", "Mini-Boss Battle"), - ("0x39", "Obtain Small Item", "Obtain Small Item"), - ("0x3A", "Temple of Time", "Temple of Time"), - ("0x3B", "Escape from Lon Lon Ranch", "Escape from Lon Lon Ranch"), - ("0x3C", "Kokiri Forest", "Kokiri Forest"), - ("0x3D", "Obtain Fairy Ocarina", "Obtain Fairy Ocarina"), - ("0x3E", "Lost Woods", "Lost Woods"), - ("0x3F", "Spirit Temple", "Spirit Temple"), - ("0x40", "Horse Race", "Horse Race"), - ("0x41", "Horse Race Goal", "Horse Race Goal"), - ("0x42", "Ingo's Theme", "Ingo's Theme"), - ("0x43", "Obtain Medallion", "Obtain Medallion"), - ("0x44", "Ocarina Saria's Song", "Ocarina Saria's Song"), - ("0x45", "Ocarina Epona's Song", "Ocarina Epona's Song"), - ("0x46", "Ocarina Zelda's Lullaby", "Ocarina Zelda's Lullaby"), - ("0x47", "Sun's Song", "Sun's Song"), - ("0x48", "Song of Time", "Song of Time"), - ("0x49", "Song of Storms", "Song of Storms"), - ("0x4A", "Fairy Flying", "Fairy Flying"), - ("0x4B", "Deku Tree", "Deku Tree"), - ("0x4C", "Windmill Hut", "Windmill Hut"), - ("0x4D", "Legend of Hyrule", "Legend of Hyrule"), - ("0x4E", "Shooting Gallery", "Shooting Gallery"), - ("0x4F", "Sheik's Theme", "Sheik's Theme"), - ("0x50", "Zora's Domain", "Zora's Domain"), - ("0x51", "Enter Zelda", "Enter Zelda"), - ("0x52", "Goodbye to Zelda", "Goodbye to Zelda"), - ("0x53", "Master Sword", "Master Sword"), - ("0x54", "Ganon Intro", "Ganon Intro"), - ("0x55", "Shop", "Shop"), - ("0x56", "Chamber of the Sages", "Chamber of the Sages"), - ("0x57", "File Select", "File Select"), - ("0x58", "Ice Cavern", "Ice Cavern"), - ("0x59", "Open Door of Temple of Time", "Open Door of Temple of Time"), - ("0x5A", "Kaepora Gaebora's Theme", "Kaepora Gaebora's Theme"), - ("0x5B", "Shadow Temple", "Shadow Temple"), - ("0x5C", "Water Temple", "Water Temple"), - ("0x5D", "Ganon's Castle Bridge", "Ganon's Castle Bridge"), - ("0x5E", "Ocarina of Time", "Ocarina of Time"), - ("0x5F", "Gerudo Valley", "Gerudo Valley"), - ("0x60", "Potion Shop", "Potion Shop"), - ("0x61", "Kotake & Koume's Theme", "Kotake & Koume's Theme"), - ("0x62", "Escape from Ganon's Castle", "Escape from Ganon's Castle"), - ("0x63", "Ganon's Castle Under Ground", "Ganon's Castle Under Ground"), - ("0x64", "Ganondorf Battle", "Ganondorf Battle"), - ("0x65", "Ganon Battle", "Ganon Battle"), - ("0x66", "Seal of Six Sages", "Seal of Six Sages"), - ("0x67", "End Credits I", "End Credits I"), - ("0x68", "End Credits II", "End Credits II"), - ("0x69", "End Credits III", "End Credits III"), - ("0x6A", "End Credits IV", "End Credits IV"), - ("0x6B", "King Dodongo & Volvagia Boss Battle", "King Dodongo & Volvagia Boss Battle"), - ("0x6C", "Mini-Game", "Mini-Game"), + ("NA_BGM_GENERAL_SFX", "General Sound Effects", "General Sound Effects"), + ("NA_BGM_NATURE_AMBIENCE", "Nature Ambiance", "Nature Ambiance"), + ("NA_BGM_FIELD_LOGIC", "Hyrule Field", "Hyrule Field"), + ( + "NA_BGM_FIELD_INIT", + "Hyrule Field (Initial Segment From Loading Area)", + "Hyrule Field (Initial Segment From Loading Area)", + ), + ("NA_BGM_FIELD_DEFAULT_1", "Hyrule Field (Moving Segment 1)", "Hyrule Field (Moving Segment 1)"), + ("NA_BGM_FIELD_DEFAULT_2", "Hyrule Field (Moving Segment 2)", "Hyrule Field (Moving Segment 2)"), + ("NA_BGM_FIELD_DEFAULT_3", "Hyrule Field (Moving Segment 3)", "Hyrule Field (Moving Segment 3)"), + ("NA_BGM_FIELD_DEFAULT_4", "Hyrule Field (Moving Segment 4)", "Hyrule Field (Moving Segment 4)"), + ("NA_BGM_FIELD_DEFAULT_5", "Hyrule Field (Moving Segment 5)", "Hyrule Field (Moving Segment 5)"), + ("NA_BGM_FIELD_DEFAULT_6", "Hyrule Field (Moving Segment 6)", "Hyrule Field (Moving Segment 6)"), + ("NA_BGM_FIELD_DEFAULT_7", "Hyrule Field (Moving Segment 7)", "Hyrule Field (Moving Segment 7)"), + ("NA_BGM_FIELD_DEFAULT_8", "Hyrule Field (Moving Segment 8)", "Hyrule Field (Moving Segment 8)"), + ("NA_BGM_FIELD_DEFAULT_9", "Hyrule Field (Moving Segment 9)", "Hyrule Field (Moving Segment 9)"), + ("NA_BGM_FIELD_DEFAULT_A", "Hyrule Field (Moving Segment 10)", "Hyrule Field (Moving Segment 10)"), + ("NA_BGM_FIELD_DEFAULT_B", "Hyrule Field (Moving Segment 11)", "Hyrule Field (Moving Segment 11)"), + ("NA_BGM_FIELD_ENEMY_INIT", "Hyrule Field (Enemy Approaches)", "Hyrule Field (Enemy Approaches)"), + ("NA_BGM_FIELD_ENEMY_1", "Hyrule Field (Enemy Near Segment 1)", "Hyrule Field (Enemy Near Segment 1)"), + ("NA_BGM_FIELD_ENEMY_2", "Hyrule Field (Enemy Near Segment 2)", "Hyrule Field (Enemy Near Segment 2)"), + ("NA_BGM_FIELD_ENEMY_3", "Hyrule Field (Enemy Near Segment 3)", "Hyrule Field (Enemy Near Segment 3)"), + ("NA_BGM_FIELD_ENEMY_4", "Hyrule Field (Enemy Near Segment 4)", "Hyrule Field (Enemy Near Segment 4)"), + ("NA_BGM_FIELD_STILL_1", "Hyrule Field (Standing Still Segment 1)", "Hyrule Field (Standing Still Segment 1)"), + ("NA_BGM_FIELD_STILL_2", "Hyrule Field (Standing Still Segment 2)", "Hyrule Field (Standing Still Segment 2)"), + ("NA_BGM_FIELD_STILL_3", "Hyrule Field (Standing Still Segment 3)", "Hyrule Field (Standing Still Segment 3)"), + ("NA_BGM_FIELD_STILL_4", "Hyrule Field (Standing Still Segment 4)", "Hyrule Field (Standing Still Segment 4)"), + ("NA_BGM_DUNGEON", "Dodongo's Cavern", "Dodongo's Cavern"), + ("NA_BGM_KAKARIKO_ADULT", "Kakariko Village (Adult)", "Kakariko Village (Adult)"), + ("NA_BGM_ENEMY", "Enemy Battle", "Enemy Battle"), + ("NA_BGM_BOSS", "Boss Battle 00", "Boss Battle 00"), + ("NA_BGM_INSIDE_DEKU_TREE", "Inside the Deku Tree", "Inside the Deku Tree"), + ("NA_BGM_MARKET", "Market", "Market"), + ("NA_BGM_TITLE", "Title Theme", "Title Theme"), + ("NA_BGM_LINK_HOUSE", "Link's House", "Link's House"), + ("NA_BGM_GAME_OVER", "Game Over", "Game Over"), + ("NA_BGM_BOSS_CLEAR", "Boss Clear", "Boss Clear"), + ("NA_BGM_ITEM_GET", "Item Get", "Item Get"), + ("NA_BGM_OPENING_GANON", "Opening Ganon", "Opening Ganon"), + ("NA_BGM_HEART_GET", "Heart Get", "Heart Get"), + ("NA_BGM_OCA_LIGHT", "Prelude Of Light", "Prelude Of Light"), + ("NA_BGM_JABU_JABU", "Inside Jabu-Jabu's Belly", "Inside Jabu-Jabu's Belly"), + ("NA_BGM_KAKARIKO_KID", "Kakariko Village (Child)", "Kakariko Village (Child)"), + ("NA_BGM_GREAT_FAIRY", "Great Fairy's Fountain", "Great Fairy's Fountain"), + ("NA_BGM_ZELDA_THEME", "Zelda's Theme", "Zelda's Theme"), + ("NA_BGM_FIRE_TEMPLE", "Fire Temple", "Fire Temple"), + ("NA_BGM_OPEN_TRE_BOX", "Open Treasure Chest", "Open Treasure Chest"), + ("NA_BGM_FOREST_TEMPLE", "Forest Temple", "Forest Temple"), + ("NA_BGM_COURTYARD", "Hyrule Castle Courtyard", "Hyrule Castle Courtyard"), + ("NA_BGM_GANON_TOWER", "Ganondorf's Theme", "Ganondorf's Theme"), + ("NA_BGM_LONLON", "Lon Lon Ranch", "Lon Lon Ranch"), + ("NA_BGM_GORON_CITY", "Goron City", "Goron City"), + ("NA_BGM_FIELD_MORNING", "Hyrule Field Morning Theme", "Hyrule Field Morning Theme"), + ("NA_BGM_SPIRITUAL_STONE", "Spiritual Stone Get", "Spiritual Stone Get"), + ("NA_BGM_OCA_BOLERO", "Bolero of Fire", "Bolero of Fire"), + ("NA_BGM_OCA_MINUET", "Minuet of Woods", "Minuet of Woods"), + ("NA_BGM_OCA_SERENADE", "Serenade of Water", "Serenade of Water"), + ("NA_BGM_OCA_REQUIEM", "Requiem of Spirit", "Requiem of Spirit"), + ("NA_BGM_OCA_NOCTURNE", "Nocturne of Shadow", "Nocturne of Shadow"), + ("NA_BGM_MINI_BOSS", "Mini-Boss Battle", "Mini-Boss Battle"), + ("NA_BGM_SMALL_ITEM_GET", "Obtain Small Item", "Obtain Small Item"), + ("NA_BGM_TEMPLE_OF_TIME", "Temple of Time", "Temple of Time"), + ("NA_BGM_EVENT_CLEAR", "Escape from Lon Lon Ranch", "Escape from Lon Lon Ranch"), + ("NA_BGM_KOKIRI", "Kokiri Forest", "Kokiri Forest"), + ("NA_BGM_OCA_FAIRY_GET", "Obtain Fairy Ocarina", "Obtain Fairy Ocarina"), + ("NA_BGM_SARIA_THEME", "Lost Woods", "Lost Woods"), + ("NA_BGM_SPIRIT_TEMPLE", "Spirit Temple", "Spirit Temple"), + ("NA_BGM_HORSE", "Horse Race", "Horse Race"), + ("NA_BGM_HORSE_GOAL", "Horse Race Goal", "Horse Race Goal"), + ("NA_BGM_INGO", "Ingo's Theme", "Ingo's Theme"), + ("NA_BGM_MEDALLION_GET", "Obtain Medallion", "Obtain Medallion"), + ("NA_BGM_OCA_SARIA", "Ocarina Saria's Song", "Ocarina Saria's Song"), + ("NA_BGM_OCA_EPONA", "Ocarina Epona's Song", "Ocarina Epona's Song"), + ("NA_BGM_OCA_ZELDA", "Ocarina Zelda's Lullaby", "Ocarina Zelda's Lullaby"), + ("NA_BGM_OCA_SUNS", "Sun's Song", "Sun's Song"), + ("NA_BGM_OCA_TIME", "Song of Time", "Song of Time"), + ("NA_BGM_OCA_STORM", "Song of Storms", "Song of Storms"), + ("NA_BGM_NAVI_OPENING", "Fairy Flying", "Fairy Flying"), + ("NA_BGM_DEKU_TREE_CS", "Deku Tree", "Deku Tree"), + ("NA_BGM_WINDMILL", "Windmill Hut", "Windmill Hut"), + ("NA_BGM_HYRULE_CS", "Legend of Hyrule", "Legend of Hyrule"), + ("NA_BGM_MINI_GAME", "Shooting Gallery", "Shooting Gallery"), + ("NA_BGM_SHEIK", "Sheik's Theme", "Sheik's Theme"), + ("NA_BGM_ZORA_DOMAIN", "Zora's Domain", "Zora's Domain"), + ("NA_BGM_APPEAR", "Enter Zelda", "Enter Zelda"), + ("NA_BGM_ADULT_LINK", "Goodbye to Zelda", "Goodbye to Zelda"), + ("NA_BGM_MASTER_SWORD", "Master Sword", "Master Sword"), + ("NA_BGM_INTRO_GANON", "Ganon Intro", "Ganon Intro"), + ("NA_BGM_SHOP", "Shop", "Shop"), + ("NA_BGM_CHAMBER_OF_SAGES", "Chamber of the Sages", "Chamber of the Sages"), + ("NA_BGM_FILE_SELECT", "File Select", "File Select"), + ("NA_BGM_ICE_CAVERN", "Ice Cavern", "Ice Cavern"), + ("NA_BGM_DOOR_OF_TIME", "Open Door of Temple of Time", "Open Door of Temple of Time"), + ("NA_BGM_OWL", "Kaepora Gaebora's Theme", "Kaepora Gaebora's Theme"), + ("NA_BGM_SHADOW_TEMPLE", "Shadow Temple", "Shadow Temple"), + ("NA_BGM_WATER_TEMPLE", "Water Temple", "Water Temple"), + ("NA_BGM_BRIDGE_TO_GANONS", "Ganon's Castle Bridge", "Ganon's Castle Bridge"), + ("NA_BGM_OCARINA_OF_TIME", "Ocarina of Time", "Ocarina of Time"), + ("NA_BGM_GERUDO_VALLEY", "Gerudo Valley", "Gerudo Valley"), + ("NA_BGM_POTION_SHOP", "Potion Shop", "Potion Shop"), + ("NA_BGM_KOTAKE_KOUME", "Kotake & Koume's Theme", "Kotake & Koume's Theme"), + ("NA_BGM_ESCAPE", "Escape from Ganon's Castle", "Escape from Ganon's Castle"), + ("NA_BGM_UNDERGROUND", "Ganon's Castle Under Ground", "Ganon's Castle Under Ground"), + ("NA_BGM_GANONDORF_BOSS", "Ganondorf Battle", "Ganondorf Battle"), + ("NA_BGM_GANON_BOSS", "Ganon Battle", "Ganon Battle"), + ("NA_BGM_END_DEMO", "Seal of Six Sages", "Seal of Six Sages"), + ("NA_BGM_STAFF_1", "End Credits I", "End Credits I"), + ("NA_BGM_STAFF_2", "End Credits II", "End Credits II"), + ("NA_BGM_STAFF_3", "End Credits III", "End Credits III"), + ("NA_BGM_STAFF_4", "End Credits IV", "End Credits IV"), + ("NA_BGM_FIRE_BOSS", "King Dodongo & Volvagia Boss Battle", "King Dodongo & Volvagia Boss Battle"), + ("NA_BGM_TIMED_MINI_GAME", "Mini-Game", "Mini-Game"), + ("NA_BGM_CUTSCENE_EFFECTS", "Various Cutscene Sounds", "Various Cutscene Sounds"), + ("NA_BGM_NO_MUSIC", "No Music", "No Music"), + ("NA_BGM_NATURE_SFX_RAIN", "Nature Ambiance: Rain", "Nature Ambiance: Rain"), ] ootEnumNightSeq = [ diff --git a/fast64_internal/oot/oot_level_classes.py b/fast64_internal/oot/oot_level_classes.py index b26695cf0..c6714fbab 100644 --- a/fast64_internal/oot/oot_level_classes.py +++ b/fast64_internal/oot/oot_level_classes.py @@ -1,5 +1,9 @@ -import bpy, os, shutil +import bpy +import os +import shutil + from typing import Optional +from bpy.types import Object from ..utility import PluginError, toAlnum, indent from .oot_collision_classes import OOTCollision from .oot_model_classes import OOTModel @@ -111,16 +115,10 @@ def __init__(self, name, model): self.cameraList = [] self.writeCutscene = False - self.csWriteType = "Embedded" + self.csWriteType = "Object" + self.csName = "" self.csWriteCustom = "" - self.csWriteObject = None - self.csEndFrame = 100 - self.csWriteTerminator = False - self.csTermIdx = 0 - self.csTermStart = 99 - self.csTermEnd = 100 - self.csLists = [] - self.extraCutscenes = [] + self.extraCutscenes: list[Object] = [] self.sceneTableEntry = OOTSceneTableEntry() @@ -161,9 +159,6 @@ def pathListName(self, headerIndex: int): def cameraListName(self): return self.sceneName() + "_cameraList" - def cutsceneDataName(self, headerIndex): - return self.sceneName() + "_header" + format(headerIndex, "02") + "_cutscene" - def alternateHeadersName(self): return self.sceneName() + "_alternateHeaders" diff --git a/fast64_internal/oot/oot_level_parser.py b/fast64_internal/oot/oot_level_parser.py index d932485e8..d43e2ffed 100644 --- a/fast64_internal/oot/oot_level_parser.py +++ b/fast64_internal/oot/oot_level_parser.py @@ -12,7 +12,7 @@ from .scene.properties import OOTSceneHeaderProperty, OOTLightProperty, OOTImportSceneSettingsProperty from .room.properties import OOTRoomHeaderProperty from .actor.properties import OOTActorProperty, OOTActorHeaderProperty -from .cutscene.motion.importer import importCutsceneData +from .cutscene.importer import importCutsceneData from .oot_utility import ( getHeaderSettings, diff --git a/fast64_internal/oot/oot_level_writer.py b/fast64_internal/oot/oot_level_writer.py index a637b8bf6..35da2d588 100644 --- a/fast64_internal/oot/oot_level_writer.py +++ b/fast64_internal/oot/oot_level_writer.py @@ -1,9 +1,9 @@ import bpy, os, math, mathutils from ..f3d.f3d_gbi import TextureExportSettings from ..f3d.f3d_writer import TriangleConverterInfo, saveStaticModel, getInfoDict +from .scene.properties import OOTSceneProperties, OOTSceneHeaderProperty, OOTAlternateSceneHeaderProperty from .room.properties import OOTRoomHeaderProperty, OOTAlternateRoomHeaderProperty from .oot_constants import ootData -from .cutscene.exporter import convertCutsceneObject, readCutsceneData from .oot_spline import assertCurveValid, ootConvertPath from .oot_model_classes import OOTModel from .oot_collision import OOTCameraData, exportCollisionCommon @@ -227,7 +227,12 @@ def writeOtherSceneProperties(scene, exportInfo, levelC): modifySceneFiles(scene, exportInfo) -def readSceneData(scene, scene_properties, sceneHeader, alternateSceneHeaders): +def readSceneData( + scene: OOTScene, + scene_properties: OOTSceneProperties, + sceneHeader: OOTSceneHeaderProperty, + alternateSceneHeaders: OOTAlternateSceneHeaderProperty, +): scene.write_dummy_room_list = scene_properties.write_dummy_room_list scene.sceneTableEntry.drawConfig = getCustomProperty(sceneHeader.sceneTableEntry, "drawConfig") scene.globalObject = getCustomProperty(sceneHeader, "globalObject") @@ -262,16 +267,10 @@ def readSceneData(scene, scene_properties, sceneHeader, alternateSceneHeaders): scene.writeCutscene = getCustomProperty(sceneHeader, "writeCutscene") if scene.writeCutscene: scene.csWriteType = getattr(sceneHeader, "csWriteType") - if scene.csWriteType == "Embedded": - scene.csEndFrame = getCustomProperty(sceneHeader, "csEndFrame") - scene.csWriteTerminator = getCustomProperty(sceneHeader, "csWriteTerminator") - scene.csTermIdx = getCustomProperty(sceneHeader, "csTermIdx") - scene.csTermStart = getCustomProperty(sceneHeader, "csTermStart") - scene.csTermEnd = getCustomProperty(sceneHeader, "csTermEnd") - readCutsceneData(scene, sceneHeader) - elif scene.csWriteType == "Custom": + + if scene.csWriteType == "Custom": scene.csWriteCustom = getCustomProperty(sceneHeader, "csWriteCustom") - elif scene.csWriteType == "Object": + else: if sceneHeader.csWriteObject is None: raise PluginError("No object selected for cutscene reference") elif sceneHeader.csWriteObject.ootEmptyType != "Cutscene": @@ -279,12 +278,9 @@ def readSceneData(scene, scene_properties, sceneHeader, alternateSceneHeaders): elif sceneHeader.csWriteObject.parent is not None: raise PluginError("Cutscene empty object should not be parented to anything") else: - scene.csWriteObject = convertCutsceneObject(sceneHeader.csWriteObject) + scene.csName = sceneHeader.csWriteObject.name.removeprefix("Cutscene.") if alternateSceneHeaders is not None: - for ec in sceneHeader.extraCutscenes: - scene.extraCutscenes.append(convertCutsceneObject(ec.csObject)) - scene.collision.cameraData = OOTCameraData(scene.name) if not alternateSceneHeaders.childNightHeader.usePreviousHeader: @@ -304,6 +300,9 @@ def readSceneData(scene, scene_properties, sceneHeader, alternateSceneHeaders): cutsceneHeader = scene.getAlternateHeaderScene(scene.name) readSceneData(cutsceneHeader, scene_properties, cutsceneHeaderProp, None) scene.cutsceneHeaders.append(cutsceneHeader) + + for extraCS in sceneHeader.extraCutscenes: + scene.extraCutscenes.append(extraCS.csObject) else: if len(sceneHeader.extraCutscenes) > 0: raise PluginError( @@ -503,7 +502,9 @@ def ootConvertScene(originalSceneObj, transformMatrix, sceneName, DLFormat, conv if bpy.context.scene.exportHiddenGeometry: restoreHiddenState(hiddenState) - roomObjs = [child for child in sceneObj.children_recursive if child.type == "EMPTY" and child.ootEmptyType == "Room"] + roomObjs = [ + child for child in sceneObj.children_recursive if child.type == "EMPTY" and child.ootEmptyType == "Room" + ] if len(roomObjs) == 0: raise PluginError("The scene has no child empties with the 'Room' empty type.") diff --git a/fast64_internal/oot/oot_upgrade.py b/fast64_internal/oot/oot_upgrade.py index 1b34eda37..b3dfd90de 100644 --- a/fast64_internal/oot/oot_upgrade.py +++ b/fast64_internal/oot/oot_upgrade.py @@ -1,11 +1,18 @@ -import bpy - +from dataclasses import dataclass +from typing import TYPE_CHECKING from bpy.types import Object, CollectionProperty from .data import OoT_ObjectData -from .cutscene.motion.constants import ootEnumCSMotionCamMode +from .oot_utility import getEvalParams from .oot_constants import ootData +from .cutscene.constants import ootEnumCSMotionCamMode + +if TYPE_CHECKING: + from .cutscene.properties import OOTCutsceneProperty +##################################### +# Room Header +##################################### def upgradeObjectList(objList: CollectionProperty, objData: OoT_ObjectData): """Transition to the XML object system""" for obj in objList: @@ -40,30 +47,180 @@ def upgradeRoomHeaders(roomObj: Object, objData: OoT_ObjectData): upgradeObjectList(altHeaders.cutsceneHeaders[i].objectList, objData) -def upgradeActors(actorObj: Object): - if actorObj.ootEmptyType == "Entrance": - entranceProp = actorObj.ootEntranceProperty +##################################### +# Cutscene +##################################### +@dataclass +class Cutscene_UpgradeData: + oldPropName: str + newPropName: str + enumData: list[tuple[str, str, str]] # this is the list used for enum properties - for obj in bpy.data.objects: - if obj.type == "EMPTY" and obj.ootEmptyType == "Room": - if actorObj in obj.children_recursive: - entranceProp.tiedRoom = obj - break - elif actorObj.ootEmptyType == "Transition Actor": - transActorProp = actorObj.ootTransitionActorProperty - transActorProp.isRoomTransition = actorObj["ootTransitionActorProperty"]["dontTransition"] == False - del actorObj["ootTransitionActorProperty"]["dontTransition"] - if transActorProp.isRoomTransition: - for obj in bpy.data.objects: - if obj.type == "EMPTY": - if obj.ootEmptyType == "Room": - if actorObj in obj.children_recursive: - transActorProp.fromRoom = obj +def transferOldDataToNew(data, oldDataToNewData: dict[str, str]): + # conversion to the same prop type + # simply transfer the old data to the new one + for oldName, newName in oldDataToNewData.items(): + if oldName in data: + if newName is not None: + value = data[oldName] - if obj.ootRoomHeader.roomIndex == actorObj["ootTransitionActorProperty"]["roomIndex"]: - transActorProp.toRoom = obj - del actorObj["ootTransitionActorProperty"]["roomIndex"] + # special case for rumble subprops where it's a string to int conversion + # another special case for light setting index where the value need to be minus one + if newName in ["rumbleSourceStrength", "rumbleDuration", "rumbleDecreaseRate"]: + value = int(getEvalParams(data[oldName]), base=16) + elif newName == "lightSettingsIndex": + value -= 1 + + data[newName] = value + + del data[oldName] + + +def convertOldDataToEnumData(data, oldDataToEnumData: list[Cutscene_UpgradeData]): + # conversion to another prop type + for csUpgradeData in oldDataToEnumData: + if csUpgradeData.oldPropName in data: + # get the old data + oldData = data[csUpgradeData.oldPropName] + + # if anything goes wrong there set the value to custom to avoid any data loss + try: + if isinstance(oldData, str): + # get the value, doing an eval for strings + # account for custom elements in the enums by adding 1 + value = int(getEvalParams(oldData), base=16) + 1 + + # special cases for ocarina action enum + # since we don't have everything the value need to be shifted + if csUpgradeData.newPropName == "ocarinaAction": + if value in [0x00, 0x01, 0x0E] or value > 0x1A: + raise IndexError + + if value > 0x0E: + value -= 1 + + value -= 2 + + if csUpgradeData.newPropName == "csSeqID": + # the old fade out value is wrong, it assumes it's a seq id + # but it's not, it's a seq player id, + # hence why we raise an error so it defaults to "custom" to avoid any data loss + # @TODO: find a way to check properly which seq command it is + raise NotImplementedError + elif isinstance(oldData, int): + # account for custom elements in the enums by adding 1 + value = oldData + 1 + + # another special case, this time for the misc enum + if csUpgradeData.newPropName == "csMiscType": + if value in [0x00, 0x04, 0x05]: + raise IndexError + + if value > 0x05: + value -= 2 + + value -= 1 + else: + raise NotImplementedError + + # if the value is in the list find the identifier + if value < len(csUpgradeData.enumData): + setattr(data, csUpgradeData.newPropName, csUpgradeData.enumData[value][0]) + else: + # else raise an error to default to custom + raise IndexError + except: + setattr(data, csUpgradeData.newPropName, "Custom") + setattr(data, f"{csUpgradeData.newPropName}Custom", str(oldData)) + + # @TODO: find a way to check properly which seq command it is + if csUpgradeData.newPropName == "csSeqID": + setattr(data, "csSeqPlayer", "Custom") + setattr(data, "csSeqPlayerCustom", str(oldData)) + + del data[csUpgradeData.oldPropName] + + +def upgradeCutsceneSubProps(csListSubProp): + # ``csListSubProp`` types: OOTCSTextProperty | OOTCSSeqProperty | OOTCSMiscProperty | OOTCSRumbleProperty + # based on ``upgradeObjectList`` + + subPropsOldToNew = { + # TextBox + "messageId": "textID", + "topOptionBranch": "topOptionTextID", + "bottomOptionBranch": "bottomOptionTextID", + # Lighting + "index": "lightSettingsIndex", + # Rumble + "unk2": "rumbleSourceStrength", + "unk3": "rumbleDuration", + "unk4": "rumbleDecreaseRate", + # Unk (Deprecated) + "unk": None, + "unkType": None, + "unk1": None, + "unk2": None, + "unk3": None, + "unk4": None, + "unk5": None, + "unk6": None, + "unk7": None, + "unk8": None, + "unk9": None, + "unk10": None, + "unk11": None, + "unk12": None, + } + + subPropsToEnum = [ + # TextBox + Cutscene_UpgradeData("ocarinaSongAction", "ocarinaAction", ootData.enumData.ootEnumOcarinaSongActionId), + Cutscene_UpgradeData("type", "csTextType", ootData.enumData.ootEnumCsTextType), + # Seq + Cutscene_UpgradeData("value", "csSeqID", ootData.enumData.ootEnumSeqId), + # Misc + Cutscene_UpgradeData("operation", "csMiscType", ootData.enumData.ootEnumCsMiscType), + ] + + transferOldDataToNew(csListSubProp, subPropsOldToNew) + convertOldDataToEnumData(csListSubProp, subPropsToEnum) + + +def upgradeCSListProps(csListProp): + # ``csListProp`` type: ``OOTCSListProperty`` + + csListPropOldToNew = { + "textbox": "textList", + "lighting": "lightSettingsList", + "time": "timeList", + "bgm": "seqList", + "misc": "miscList", + "nine": "rumbleList", + "fxStartFrame": "transitionStartFrame", + "fxEndFrame": "transitionEndFrame", + } + + transferOldDataToNew(csListProp, csListPropOldToNew) + + # both are enums but the item list is different (the old one doesn't have a "custom" entry) + convertOldDataToEnumData( + csListProp, [Cutscene_UpgradeData("fxType", "transitionType", ootData.enumData.ootEnumCsTransitionType)] + ) + + +def upgradeCutsceneProperty(csProp: "OOTCutsceneProperty"): + csPropOldToNew = { + "csWriteTerminator": "csUseDestination", + "csTermStart": "csDestinationStartFrame", + "csTermEnd": None, + } + + transferOldDataToNew(csProp, csPropOldToNew) + convertOldDataToEnumData( + csProp, [Cutscene_UpgradeData("csTermIdx", "csDestination", ootData.enumData.ootEnumCsDestination)] + ) def upgradeCutsceneMotion(csMotionObj: Object): @@ -140,3 +297,32 @@ def upgradeCutsceneMotion(csMotionObj: Object): if "camroll" in bone: camShotPointProp.shotPointRoll = bone["camroll"] del bone["camroll"] + + +##################################### +# Actors +##################################### +def upgradeActors(actorObj: Object): + if actorObj.ootEmptyType == "Entrance": + entranceProp = actorObj.ootEntranceProperty + + for obj in bpy.data.objects: + if obj.type == "EMPTY" and obj.ootEmptyType == "Room": + if actorObj in obj.children_recursive: + entranceProp.tiedRoom = obj + break + elif actorObj.ootEmptyType == "Transition Actor": + transActorProp = actorObj.ootTransitionActorProperty + transActorProp.isRoomTransition = actorObj["ootTransitionActorProperty"]["dontTransition"] == False + del actorObj["ootTransitionActorProperty"]["dontTransition"] + + if transActorProp.isRoomTransition: + for obj in bpy.data.objects: + if obj.type == "EMPTY": + if obj.ootEmptyType == "Room": + if actorObj in obj.children_recursive: + transActorProp.fromRoom = obj + + if obj.ootRoomHeader.roomIndex == actorObj["ootTransitionActorProperty"]["roomIndex"]: + transActorProp.toRoom = obj + del actorObj["ootTransitionActorProperty"]["roomIndex"] diff --git a/fast64_internal/oot/oot_utility.py b/fast64_internal/oot/oot_utility.py index 2d22bf642..96c50f21b 100644 --- a/fast64_internal/oot/oot_utility.py +++ b/fast64_internal/oot/oot_utility.py @@ -333,9 +333,7 @@ def ootDuplicateHierarchy(obj, ignoreAttr, includeEmpties, objectCategorizer): def ootSelectMeshChildrenOnly(obj, includeEmpties): isMesh = obj.type == "MESH" - isEmpty = ( - obj.type == "EMPTY" or obj.type == "CAMERA" or obj.type == "CURVE" - ) and includeEmpties + isEmpty = (obj.type == "EMPTY" or obj.type == "CAMERA" or obj.type == "CURVE") and includeEmpties if isMesh or isEmpty: obj.select_set(True) obj.original_name = obj.name diff --git a/fast64_internal/oot/props_panel_main.py b/fast64_internal/oot/props_panel_main.py index 97e31b5c0..2ef8f3872 100644 --- a/fast64_internal/oot/props_panel_main.py +++ b/fast64_internal/oot/props_panel_main.py @@ -8,8 +8,8 @@ from .cutscene.properties import OOTCutsceneProperty from .cutscene.motion.properties import ( OOTCutsceneMotionProperty, - OOTCSMotionActorCueListProperty, - OOTCSMotionActorCueProperty, + CutsceneCmdActorCueListProperty, + CutsceneCmdActorCueProperty, ) from .actor.properties import ( @@ -177,12 +177,12 @@ def draw(self, context): "CS Player Cue Preview", ]: labelPrefix = "Player" if "Player" in obj.ootEmptyType else "Actor" - actorCueListProp: OOTCSMotionActorCueListProperty = obj.ootCSMotionProperty.actorCueListProp + actorCueListProp: CutsceneCmdActorCueListProperty = obj.ootCSMotionProperty.actorCueListProp actorCueListProp.draw_props(box, obj.ootEmptyType == f"CS {labelPrefix} Cue Preview", labelPrefix, obj.name) elif obj.ootEmptyType in ["CS Actor Cue", "CS Player Cue", "CS Dummy Cue"]: labelPrefix = "Player" if obj.parent.ootEmptyType == "CS Player Cue List" else "Actor" - actorCueProp: OOTCSMotionActorCueProperty = obj.ootCSMotionProperty.actorCueProp + actorCueProp: CutsceneCmdActorCueProperty = obj.ootCSMotionProperty.actorCueProp actorCueProp.draw_props(box, labelPrefix, obj.ootEmptyType == "CS Dummy Cue", obj.name) elif obj.ootEmptyType == "None": @@ -214,6 +214,9 @@ def upgrade_changed_props(): if obj.parent.name.startswith("Cutscene.") or obj.parent.ootEmptyType == "Cutscene": OOTCutsceneMotionProperty.upgrade_object(obj) + if obj.ootEmptyType == "Cutscene": + OOTCutsceneProperty.upgrade_object(obj) + class OOTCullGroupProperty(bpy.types.PropertyGroup): sizeControlsCull: bpy.props.BoolProperty(default=True, name="Empty Size Controls Cull Depth") diff --git a/fast64_internal/oot/scene/exporter/to_c/__init__.py b/fast64_internal/oot/scene/exporter/to_c/__init__.py index e9f7106ad..61a8fed1f 100644 --- a/fast64_internal/oot/scene/exporter/to_c/__init__.py +++ b/fast64_internal/oot/scene/exporter/to_c/__init__.py @@ -3,4 +3,4 @@ from .spec import editSpecFile from .scene_folder import modifySceneFiles, deleteSceneFiles from .scene_bootup import setBootupScene, clearBootupScene -from .scene_cutscene import ootCutsceneDataToC +from .scene_cutscene import getCutsceneC diff --git a/fast64_internal/oot/scene/exporter/to_c/actor.py b/fast64_internal/oot/scene/exporter/to_c/actor.py index 653a3f075..446efc331 100644 --- a/fast64_internal/oot/scene/exporter/to_c/actor.py +++ b/fast64_internal/oot/scene/exporter/to_c/actor.py @@ -29,14 +29,14 @@ def getActorEntry(actor: OOTActor): def getActorList(outRoom: OOTRoom, headerIndex: int): """Returns the actor list for the current header""" actorList = CData() - listName = f"ActorEntry {outRoom.actorListName(headerIndex)}" + declarationBase = f"ActorEntry {outRoom.actorListName(headerIndex)}" # .h - actorList.header = f"extern {listName}[];\n" + actorList.header = f"extern {declarationBase}[];\n" # .c actorList.source = ( - (f"{listName}[{outRoom.getActorLengthDefineName(headerIndex)}]" + " = {\n") + (f"{declarationBase}[{outRoom.getActorLengthDefineName(headerIndex)}]" + " = {\n") + "\n".join(getActorEntry(actor) for actor in outRoom.actorList) + "};\n\n" ) @@ -72,14 +72,14 @@ def getTransitionActorEntry(transActor: OOTTransitionActor): def getTransitionActorList(outScene: OOTScene, headerIndex: int): """Returns the transition actor list for the current header""" transActorList = CData() - listName = f"TransitionActorEntry {outScene.transitionActorListName(headerIndex)}" + declarationBase = f"TransitionActorEntry {outScene.transitionActorListName(headerIndex)}" # .h - transActorList.header = f"extern {listName}[];\n" + transActorList.header = f"extern {declarationBase}[];\n" # .c transActorList.source = ( - (f"{listName}[]" + " = {\n") + (f"{declarationBase}[]" + " = {\n") + "\n".join(getTransitionActorEntry(transActor) for transActor in outScene.transitionActorList) + "};\n\n" ) @@ -93,14 +93,14 @@ def getTransitionActorList(outScene: OOTScene, headerIndex: int): def getSpawnActorList(outScene: OOTScene, headerIndex: int): """Returns the spawn actor list for the current header""" spawnActorList = CData() - listName = f"ActorEntry {outScene.startPositionsName(headerIndex)}" + declarationBase = f"ActorEntry {outScene.startPositionsName(headerIndex)}" # .h - spawnActorList.header = f"extern {listName}[];\n" + spawnActorList.header = f"extern {declarationBase}[];\n" # .c spawnActorList.source = ( - (f"{listName}[]" + " = {\n") + (f"{declarationBase}[]" + " = {\n") + "".join(getActorEntry(spawnActor) for spawnActor in outScene.startPositions.values()) + "};\n\n" ) @@ -116,14 +116,14 @@ def getSpawnEntry(entrance: OOTEntrance): def getSpawnList(outScene: OOTScene, headerIndex: int): """Returns the spawn list for the current header""" spawnList = CData() - listName = f"Spawn {outScene.entranceListName(headerIndex)}" + declarationBase = f"Spawn {outScene.entranceListName(headerIndex)}" # .h - spawnList.header = f"extern {listName}[];\n" + spawnList.header = f"extern {declarationBase}[];\n" # .c spawnList.source = ( - (f"{listName}[]" + " = {\n") + (f"{declarationBase}[]" + " = {\n") + (indent + "// { Spawn Actor List Index, Room Index }\n") + "".join(getSpawnEntry(entrance) for entrance in outScene.entranceList) + "};\n\n" diff --git a/fast64_internal/oot/scene/exporter/to_c/room_commands.py b/fast64_internal/oot/scene/exporter/to_c/room_commands.py index 7ef545997..1df1e6c8c 100644 --- a/fast64_internal/oot/scene/exporter/to_c/room_commands.py +++ b/fast64_internal/oot/scene/exporter/to_c/room_commands.py @@ -53,7 +53,7 @@ def getActorListCmd(outRoom: OOTRoom, headerIndex: int): def getRoomCommandList(outRoom: OOTRoom, headerIndex: int): cmdListData = CData() - listName = f"SceneCmd {outRoom.roomName()}_header{headerIndex:02}" + declarationBase = f"SceneCmd {outRoom.roomName()}_header{headerIndex:02}" getCmdFuncList = [ getEchoSettingsCmd, @@ -73,9 +73,9 @@ def getRoomCommandList(outRoom: OOTRoom, headerIndex: int): ) # .h - cmdListData.header = f"extern {listName}[];\n" + cmdListData.header = f"extern {declarationBase}[];\n" # .c - cmdListData.source = f"{listName}[]" + " = {\n" + roomCmdData + "};\n\n" + cmdListData.source = f"{declarationBase}[]" + " = {\n" + roomCmdData + "};\n\n" return cmdListData diff --git a/fast64_internal/oot/scene/exporter/to_c/room_header.py b/fast64_internal/oot/scene/exporter/to_c/room_header.py index 333e764f2..ac93c14ce 100644 --- a/fast64_internal/oot/scene/exporter/to_c/room_header.py +++ b/fast64_internal/oot/scene/exporter/to_c/room_header.py @@ -20,14 +20,14 @@ def getHeaderDefines(outRoom: OOTRoom, headerIndex: int): # Object List def getObjectList(outRoom: OOTRoom, headerIndex: int): objectList = CData() - listName = f"s16 {outRoom.objectListName(headerIndex)}" + declarationBase = f"s16 {outRoom.objectListName(headerIndex)}" # .h - objectList.header = f"extern {listName}[];\n" + objectList.header = f"extern {declarationBase}[];\n" # .c objectList.source = ( - (f"{listName}[{outRoom.getObjectLengthDefineName(headerIndex)}]" + " = {\n") + (f"{declarationBase}[{outRoom.getObjectLengthDefineName(headerIndex)}]" + " = {\n") + ",\n".join(indent + objectID for objectID in outRoom.objectIDList) + ",\n};\n\n" ) @@ -48,14 +48,14 @@ def getRoomData(outRoom: OOTRoom): for i, csHeader in enumerate(outRoom.cutsceneHeaders): roomHeaders.append((csHeader, f"Cutscene No. {i + 1}")) - altHeaderPtrListName = f"SceneCmd* {outRoom.alternateHeadersName()}" + declarationBase = f"SceneCmd* {outRoom.alternateHeadersName()}" # .h - roomC.header = f"extern {altHeaderPtrListName}[];\n" + roomC.header = f"extern {declarationBase}[];\n" # .c altHeaderPtrList = ( - f"{altHeaderPtrListName}[]" + f"{declarationBase}[]" + " = {\n" + "\n".join( indent + f"{curHeader.roomName()}_header{i:02}," if curHeader is not None else indent + "NULL," diff --git a/fast64_internal/oot/scene/exporter/to_c/room_shape.py b/fast64_internal/oot/scene/exporter/to_c/room_shape.py index fd7dd9020..c4029d05f 100644 --- a/fast64_internal/oot/scene/exporter/to_c/room_shape.py +++ b/fast64_internal/oot/scene/exporter/to_c/room_shape.py @@ -36,13 +36,13 @@ def getRoomShapeImageData(roomMesh: OOTRoomMesh, textureSettings: TextureExportS code = CData() if len(roomMesh.bgImages) > 1: - multiBgImageName = f"RoomShapeImageMultiBgEntry {roomMesh.getMultiBgStructName()}" + declarationBase = f"RoomShapeImageMultiBgEntry {roomMesh.getMultiBgStructName()}" # .h - code.header += f"extern {multiBgImageName}[{len(roomMesh.bgImages)}];\n" + code.header += f"extern {declarationBase}[{len(roomMesh.bgImages)}];\n" # .c - code.source += f"{multiBgImageName}[{len(roomMesh.bgImages)}] = {{\n" + code.source += f"{declarationBase}[{len(roomMesh.bgImages)}] = {{\n" for i in range(len(roomMesh.bgImages)): bgImage = roomMesh.bgImages[i] code.source += indent + "{\n" + bgImage.multiPropertiesC(2, i) + indent + "},\n" @@ -82,7 +82,7 @@ def getRoomShape(outRoom: OOTRoom): if mesh.roomShape != "ROOM_SHAPE_TYPE_IMAGE": entryName = mesh.entriesName() - dlEntryArrayName = f"{dlEntryType} {mesh.entriesName()}[{len(mesh.meshEntries)}]" + dlEntryDeclarationBase = f"{dlEntryType} {mesh.entriesName()}[{len(mesh.meshEntries)}]" roomShapeInfo.source = ( "\n".join( @@ -98,8 +98,8 @@ def getRoomShape(outRoom: OOTRoom): + "\n\n" ) - roomShapeDLArray.header = f"extern {dlEntryArrayName};\n" - roomShapeDLArray.source = dlEntryArrayName + " = {\n" + roomShapeDLArray.header = f"extern {dlEntryDeclarationBase};\n" + roomShapeDLArray.source = dlEntryDeclarationBase + " = {\n" for entry in mesh.meshEntries: roomShapeDLArray.source += indent + getRoomShapeDLEntry(entry, mesh.roomShape) diff --git a/fast64_internal/oot/scene/exporter/to_c/scene_commands.py b/fast64_internal/oot/scene/exporter/to_c/scene_commands.py index 0df9e912c..1082bb0ff 100644 --- a/fast64_internal/oot/scene/exporter/to_c/scene_commands.py +++ b/fast64_internal/oot/scene/exporter/to_c/scene_commands.py @@ -65,10 +65,8 @@ def getLightSettingsCmd(outScene: OOTScene, headerIndex: int): def getCutsceneDataCmd(outScene: OOTScene, headerIndex: int): match outScene.csWriteType: - case "Embedded": - csDataName = outScene.cutsceneDataName(headerIndex) case "Object": - csDataName = outScene.csWriteObject.name + csDataName = outScene.csName case _: csDataName = outScene.csWriteCustom @@ -77,7 +75,7 @@ def getCutsceneDataCmd(outScene: OOTScene, headerIndex: int): def getSceneCommandList(outScene: OOTScene, headerIndex: int): cmdListData = CData() - listName = f"SceneCmd {outScene.sceneName()}_header{headerIndex:02}" + declarationBase = f"SceneCmd {outScene.sceneName()}_header{headerIndex:02}" getCmdFunc1ArgList = [ getSoundSettingsCmd, @@ -110,9 +108,9 @@ def getSceneCommandList(outScene: OOTScene, headerIndex: int): ) # .h - cmdListData.header = f"extern {listName}[]" + ";\n" + cmdListData.header = f"extern {declarationBase}[]" + ";\n" # .c - cmdListData.source = f"{listName}[]" + " = {\n" + sceneCmdData + "};\n\n" + cmdListData.source = f"{declarationBase}[]" + " = {\n" + sceneCmdData + "};\n\n" return cmdListData diff --git a/fast64_internal/oot/scene/exporter/to_c/scene_cutscene.py b/fast64_internal/oot/scene/exporter/to_c/scene_cutscene.py index 59a0dca0e..8f0ab5c09 100644 --- a/fast64_internal/oot/scene/exporter/to_c/scene_cutscene.py +++ b/fast64_internal/oot/scene/exporter/to_c/scene_cutscene.py @@ -1,153 +1,26 @@ -from .....utility import CData, PluginError, indent +import bpy + +from .....utility import CData from ....oot_level_classes import OOTScene -from ....cutscene.constants import ootEnumCSTextboxTypeEntryC, ootEnumCSListTypeListC, ootEnumCSListTypeEntryC -from ....cutscene.motion.exporter import getCutsceneMotionData +from ....cutscene.exporter import getNewCutsceneExport + + +def getCutsceneC(csName: str): + csData = CData() + declarationBase = f"CutsceneData {csName}[]" + + # .h + csData.header = f"extern {declarationBase};\n" + # .c + csData.source = ( + declarationBase + + " = {\n" + + getNewCutsceneExport(csName, bpy.context.scene.exportMotionOnly).getExportData() + + "};\n\n" + ) -def ootCutsceneDataToC(csParent, csName): - # csParent can be OOTCutscene or OOTScene - motionExporter = getCutsceneMotionData(csName, False) - motionData = motionExporter.getExportData() - data = CData() - data.header = "extern CutsceneData " + csName + "[];\n" - data.source = "CutsceneData " + csName + "[] = {\n" - nentries = len(csParent.csLists) + (1 if csParent.csWriteTerminator else 0) - frameDiff = 0 - if motionExporter.frameCount > csParent.csEndFrame: - frameDiff = motionExporter.frameCount - csParent.csEndFrame - data.source += ( - indent + f"CS_BEGIN_CUTSCENE({nentries + motionExporter.entryTotal}, " - ) + f"{csParent.csEndFrame + frameDiff}),\n" - if csParent.csWriteTerminator: - data.source += ( - "\tCS_TERMINATOR(" - + str(csParent.csTermIdx) - + ", " - + str(csParent.csTermStart) - + ", " - + str(csParent.csTermEnd) - + "),\n" - ) - for list in csParent.csLists: - data.source += "\t" + ootEnumCSListTypeListC[list.listType] + "(" - if list.listType == "Unk": - data.source += list.unkType + ", " - if list.listType == "FX": - data.source += list.fxType + ", " + str(list.fxStartFrame) + ", " + str(list.fxEndFrame) - else: - data.source += str(len(list.entries)) - data.source += "),\n" - for e in list.entries: - data.source += "\t\t" - if list.listType == "Textbox": - data.source += ootEnumCSTextboxTypeEntryC[e.textboxType] - else: - data.source += ootEnumCSListTypeEntryC[list.listType] - data.source += "(" - if list.listType == "Textbox": - if e.textboxType == "Text": - data.source += ( - e.messageId - + ", " - + str(e.startFrame) - + ", " - + str(e.endFrame) - + ", " - + e.type - + ", " - + e.topOptionBranch - + ", " - + e.bottomOptionBranch - ) - elif e.textboxType == "None": - data.source += str(e.startFrame) + ", " + str(e.endFrame) - elif e.textboxType == "LearnSong": - data.source += ( - e.ocarinaSongAction - + ", " - + str(e.startFrame) - + ", " - + str(e.endFrame) - + ", " - + e.ocarinaMessageId - ) - elif list.listType == "Lighting": - data.source += ( - str(e.index) + ", " + str(e.startFrame) + ", " + str(e.startFrame + 1) + ", 0, 0, 0, 0, 0, 0, 0, 0" - ) - elif list.listType == "Time": - data.source += ( - "1, " - + str(e.startFrame) - + ", " - + str(e.startFrame + 1) - + ", " - + str(e.hour) - + ", " - + str(e.minute) - + ", 0" - ) - elif list.listType in ["PlayBGM", "StopBGM", "FadeBGM"]: - data.source += e.value - if list.listType != "FadeBGM": - data.source += " + 1" # Game subtracts 1 to get actual seq - data.source += ", " + str(e.startFrame) + ", " + str(e.endFrame) + ", 0, 0, 0, 0, 0, 0, 0, 0" - elif list.listType == "Misc": - data.source += ( - str(e.operation) - + ", " - + str(e.startFrame) - + ", " - + str(e.endFrame) - + ", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0" - ) - elif list.listType == "0x09": - data.source += ( - "0, " - + str(e.startFrame) - + ", " - + str(e.startFrame + 1) - + ", " - + e.unk2 - + ", " - + e.unk3 - + ", " - + e.unk4 - + ", 0, 0" - ) - elif list.listType == "Unk": - data.source += ( - e.unk1 - + ", " - + e.unk2 - + ", " - + e.unk3 - + ", " - + e.unk4 - + ", " - + e.unk5 - + ", " - + e.unk6 - + ", " - + e.unk7 - + ", " - + e.unk8 - + ", " - + e.unk9 - + ", " - + e.unk10 - + ", " - + e.unk11 - + ", " - + e.unk12 - ) - else: - raise PluginError("Internal error: invalid cutscene list type " + list.listType) - data.source += "),\n" - data.source += motionData - data.source += "\tCS_END(),\n" - data.source += "};\n\n" - return data + return csData def getSceneCutscenes(outScene: OOTScene): @@ -161,19 +34,18 @@ def getSceneCutscenes(outScene: OOTScene): altHeaders.extend(outScene.cutsceneHeaders) csObjects = [] - for i, curHeader in enumerate(altHeaders): + for curHeader in altHeaders: # curHeader is either None or an OOTScene. This can either be the main scene itself, # or one of the alternate / cutscene headers. if curHeader is not None and curHeader.writeCutscene: - if curHeader.csWriteType == "Embedded": - cutscenes.append(ootCutsceneDataToC(curHeader, curHeader.cutsceneDataName(i))) - elif curHeader.csWriteType == "Object" and curHeader.csWriteObject.name not in csObjects: - cutscenes.append(ootCutsceneDataToC(curHeader.csWriteObject, curHeader.csWriteObject.name)) - csObjects.append(curHeader.csWriteObject.name) + if curHeader.csWriteType == "Object" and curHeader.csName not in csObjects: + cutscenes.append(getCutsceneC(curHeader.csName)) + csObjects.append(curHeader.csName) - for extraCs in outScene.extraCutscenes: - if not extraCs.name in csObjects: - cutscenes.append(ootCutsceneDataToC(extraCs, extraCs.name)) - csObjects.append(extraCs.name) + for csObj in outScene.extraCutscenes: + name = csObj.name.removeprefix("Cutscene.") + if not name in csObjects: + cutscenes.append(getCutsceneC(name)) + csObjects.append(name) return cutscenes diff --git a/fast64_internal/oot/scene/exporter/to_c/scene_header.py b/fast64_internal/oot/scene/exporter/to_c/scene_header.py index 37a7c8dc9..61b3c1621 100644 --- a/fast64_internal/oot/scene/exporter/to_c/scene_header.py +++ b/fast64_internal/oot/scene/exporter/to_c/scene_header.py @@ -59,14 +59,14 @@ def getLightSettingsEntry(light: OOTLight, lightMode: str, isLightingCustom: boo def getLightSettings(outScene: OOTScene, headerIndex: int): lightSettingsData = CData() - lightName = f"EnvLightSettings {outScene.lightListName(headerIndex)}[{len(outScene.lights)}]" + declarationBase = f"EnvLightSettings {outScene.lightListName(headerIndex)}[{len(outScene.lights)}]" # .h - lightSettingsData.header = f"extern {lightName};\n" + lightSettingsData.header = f"extern {declarationBase};\n" # .c lightSettingsData.source = ( - (lightName + " = {\n") + (declarationBase + " = {\n") + "".join( getLightSettingsEntry(light, outScene.skyboxLighting, outScene.isSkyboxLightingCustom, i) for i, light in enumerate(outScene.lights) @@ -90,14 +90,14 @@ def getSceneModel(outScene: OOTScene, textureExportSettings: TextureExportSettin ############# def getExitList(outScene: OOTScene, headerIndex: int): exitList = CData() - listName = f"u16 {outScene.exitListName(headerIndex)}[{len(outScene.exitList)}]" + declarationBase = f"u16 {outScene.exitListName(headerIndex)}[{len(outScene.exitList)}]" # .h - exitList.header = f"extern {listName};\n" + exitList.header = f"extern {declarationBase};\n" # .c exitList.source = ( - (listName + " = {\n") + (declarationBase + " = {\n") # @TODO: use the enum name instead of the raw index + "\n".join(indent + f"{exitEntry.index}," for exitEntry in outScene.exitList) + "\n};\n\n" @@ -111,7 +111,7 @@ def getExitList(outScene: OOTScene, headerIndex: int): ############# def getRoomList(outScene: OOTScene): roomList = CData() - listName = f"RomFile {outScene.roomListName()}[]" + declarationBase = f"RomFile {outScene.roomListName()}[]" # generating segment rom names for every room segNames = [] @@ -120,7 +120,7 @@ def getRoomList(outScene: OOTScene): segNames.append((f"_{roomName}SegmentRomStart", f"_{roomName}SegmentRomEnd")) # .h - roomList.header += f"extern {listName};\n" + roomList.header += f"extern {declarationBase};\n" if not outScene.write_dummy_room_list: # Write externs for rom segments @@ -129,7 +129,7 @@ def getRoomList(outScene: OOTScene): ) # .c - roomList.source = listName + " = {\n" + roomList.source = declarationBase + " = {\n" if outScene.write_dummy_room_list: roomList.source = ( @@ -207,9 +207,9 @@ def getSceneData(outScene: OOTScene): if i == 0: if outScene.hasAlternateHeaders(): - altHeaderListName = f"SceneCmd* {outScene.alternateHeadersName()}[]" - sceneC.header += f"extern {altHeaderListName};\n" - sceneC.source += altHeaderListName + " = {\n" + altHeaderPtrs + "\n};\n\n" + declarationBase = f"SceneCmd* {outScene.alternateHeadersName()}[]" + sceneC.header += f"extern {declarationBase};\n" + sceneC.source += declarationBase + " = {\n" + altHeaderPtrs + "\n};\n\n" # Write the room segment list sceneC.append(getRoomList(outScene)) diff --git a/fast64_internal/oot/scene/exporter/to_c/scene_pathways.py b/fast64_internal/oot/scene/exporter/to_c/scene_pathways.py index 8b6292ef2..48aad26bc 100644 --- a/fast64_internal/oot/scene/exporter/to_c/scene_pathways.py +++ b/fast64_internal/oot/scene/exporter/to_c/scene_pathways.py @@ -5,14 +5,14 @@ def getPathPointData(path: OOTPath, headerIndex: int, pathIndex: int): pathData = CData() - pathName = f"Vec3s {path.pathName(headerIndex, pathIndex)}" + declarationBase = f"Vec3s {path.pathName(headerIndex, pathIndex)}" # .h - pathData.header = f"extern {pathName}[];\n" + pathData.header = f"extern {declarationBase}[];\n" # .c pathData.source = ( - f"{pathName}[]" + f"{declarationBase}[]" + " = {\n" + "\n".join( indent + "{ " + ", ".join(f"{round(curPoint):5}" for curPoint in point) + " }," for point in path.points @@ -26,13 +26,13 @@ def getPathPointData(path: OOTPath, headerIndex: int, pathIndex: int): def getPathData(outScene: OOTScene, headerIndex: int): pathData = CData() pathListData = CData() - listName = f"Path {outScene.pathListName(headerIndex)}[{len(outScene.pathList)}]" + declarationBase = f"Path {outScene.pathListName(headerIndex)}[{len(outScene.pathList)}]" # .h - pathListData.header = f"extern {listName};\n" + pathListData.header = f"extern {declarationBase};\n" # .c - pathListData.source = listName + " = {\n" + pathListData.source = declarationBase + " = {\n" # Parse in alphabetical order of names sortedPathList = sorted(outScene.pathList, key=lambda x: x.objName.lower()) diff --git a/fast64_internal/oot/scene/operators.py b/fast64_internal/oot/scene/operators.py index 675fcdb08..44f1da117 100644 --- a/fast64_internal/oot/scene/operators.py +++ b/fast64_internal/oot/scene/operators.py @@ -1,13 +1,15 @@ -import bpy, os +import bpy +import os + from bpy.path import abspath -from bpy.types import Operator, UILayout +from bpy.types import Operator from bpy.props import EnumProperty, IntProperty, StringProperty from bpy.utils import register_class, unregister_class from bpy.ops import object from mathutils import Matrix, Vector from ...f3d.f3d_gbi import DLFormat from ...utility import PluginError, raisePluginError, ootGetSceneOrRoomHeader -from ..oot_utility import ExportInfo, sceneNameFromID, getEnumName +from ..oot_utility import ExportInfo, sceneNameFromID from ..oot_level_writer import ootExportSceneToC from ..oot_constants import ootEnumMusicSeq, ootEnumSceneID from ..oot_level_parser import parseScene @@ -75,7 +77,7 @@ class OOT_SearchMusicSeqEnumOperator(Operator): bl_property = "ootMusicSeq" bl_options = {"REGISTER", "UNDO"} - ootMusicSeq: EnumProperty(items=ootEnumMusicSeq, default="0x02") + ootMusicSeq: EnumProperty(items=ootEnumMusicSeq, default="NA_BGM_FIELD_LOGIC") headerIndex: IntProperty(default=0, min=0) objName: StringProperty() diff --git a/fast64_internal/oot/scene/properties.py b/fast64_internal/oot/scene/properties.py index 281009098..7c8e2daa3 100644 --- a/fast64_internal/oot/scene/properties.py +++ b/fast64_internal/oot/scene/properties.py @@ -1,4 +1,4 @@ -import os, bpy +import bpy from bpy.types import PropertyGroup, Object, Light, UILayout, Scene from bpy.props import ( EnumProperty, @@ -12,8 +12,6 @@ from bpy.utils import register_class, unregister_class from ...render_settings import on_update_oot_render_settings from ...utility import prop_split, customExportWarning -from ..cutscene.properties import OOTCSListProperty -from ..cutscene.operators import drawCSListAddOp from ..cutscene.constants import ootEnumCSWriteType from ..oot_utility import ( @@ -22,7 +20,6 @@ drawCollectionOps, drawEnumWithCustom, drawAddButton, - getEnumName, ) from ..oot_constants import ( @@ -287,7 +284,7 @@ class OOTSceneHeaderProperty(PropertyGroup): cameraMode: EnumProperty(name="Camera Mode", items=ootEnumCameraMode, default="0x00") cameraModeCustom: StringProperty(name="Camera Mode Custom", default="0x00") - musicSeq: EnumProperty(name="Music Sequence", items=ootEnumMusicSeq, default="0x02") + musicSeq: EnumProperty(name="Music Sequence", items=ootEnumMusicSeq, default="NA_BGM_FIELD_LOGIC") musicSeqCustom: StringProperty(name="Music Sequence ID", default="0x00") nightSeq: EnumProperty(name="Nighttime SFX", items=ootEnumNightSeq, default="0x00") nightSeqCustom: StringProperty(name="Nighttime SFX ID", default="0x00") @@ -299,7 +296,7 @@ class OOTSceneHeaderProperty(PropertyGroup): exitList: CollectionProperty(type=OOTExitProperty, name="Exit List") writeCutscene: BoolProperty(name="Write Cutscene") - csWriteType: EnumProperty(name="Cutscene Data Type", items=ootEnumCSWriteType, default="Embedded") + csWriteType: EnumProperty(name="Cutscene Data Type", items=ootEnumCSWriteType, default="Object") csWriteCustom: StringProperty(name="CS hdr var:", default="") csWriteObject: PointerProperty( name="Cutscene Object", @@ -307,20 +304,8 @@ class OOTSceneHeaderProperty(PropertyGroup): poll=lambda self, object: object.type == "EMPTY" and object.ootEmptyType == "Cutscene", ) - # These properties are for the deprecated "Embedded" cutscene type. They have - # not been removed as doing so would break any existing scenes made with this - # type of cutscene data. - csEndFrame: IntProperty(name="End Frame", min=0, default=100) - csWriteTerminator: BoolProperty(name="Write Terminator (Code Execution)") - csTermIdx: IntProperty(name="Index", min=0) - csTermStart: IntProperty(name="Start Frm", min=0, default=99) - csTermEnd: IntProperty(name="End Frm", min=0, default=100) - csLists: CollectionProperty(type=OOTCSListProperty, name="Cutscene Lists") - extraCutscenes: CollectionProperty(type=OOTExtraCutsceneProperty, name="Extra Cutscenes") - sceneTableEntry: PointerProperty(type=OOTSceneTableEntryProperty) - menuTab: EnumProperty(name="Menu", items=ootEnumSceneMenu, update=onMenuTabChange) altMenuTab: EnumProperty(name="Menu", items=ootEnumSceneMenuAlternate) @@ -396,25 +381,9 @@ def draw_props(self, layout: UILayout, dropdownLabel: str, headerIndex: int, obj r.prop(self, "csWriteType", text="Data") if self.csWriteType == "Custom": cutscene.prop(self, "csWriteCustom") - elif self.csWriteType == "Object": - cutscene.prop(self, "csWriteObject") else: - # This is the GUI setup / drawing for the properties for the - # deprecated "Embedded" cutscene type. They have not been removed - # as doing so would break any existing scenes made with this type - # of cutscene data. - cutscene.label(text='Embedded cutscenes are deprecated. Please use "Object" instead.') - cutscene.prop(self, "csEndFrame", text="End Frame") - cutscene.prop(self, "csWriteTerminator", text="Write Terminator (Code Execution)") - if self.csWriteTerminator: - r = cutscene.row() - r.prop(self, "csTermIdx", text="Index") - r.prop(self, "csTermStart", text="Start Frm") - r.prop(self, "csTermEnd", text="End Frm") - collectionType = "CSHdr." + str(0 if headerIndex is None else headerIndex) - for i, p in enumerate(self.csLists): - p.draw_props(cutscene, i, objName, collectionType) - drawCSListAddOp(cutscene, objName, collectionType) + cutscene.prop(self, "csWriteObject") + if headerIndex is None or headerIndex == 0: cutscene.label(text="Extra cutscenes (not in any header):") for i in range(len(self.extraCutscenes)):