Merge branch 'main' into codebase_todo

This commit is contained in:
Dragorn421
2023-12-26 18:50:33 +01:00
63 changed files with 11195 additions and 1826 deletions
+17 -2
View File
@@ -1,6 +1,6 @@
# Fast64
This requires Blender 3.2+.
This requires Blender 3.2+. Blender 4.0+ is recommended.
Forked from [kurethedead/fast64 on BitBucket](https://bitbucket.org/kurethedead/fast64/src).
@@ -22,7 +22,7 @@ Thanks to InTheBeef for LowPolySkinnedMario.
### Discord Server
We have a Discord server for support as well as development [here](https://discord.gg/ny7PDcN2x8).
### Table of Contents
### Links to Docs / Guides for Each Game
1. [ Super Mario 64 ](/fast64_internal/sm64/README.md)
2. [ Ocarina Of Time ](/fast64_internal/oot/README.md)
@@ -48,6 +48,21 @@ There may occur cases where code is formatted differently based on the code use
### Converting To F3D v5 Materials
A new optimized shader graph was introduced to decrease processing times for material creation and exporting. If you have a project that still uses old materials, you may want to convert them to v5. To convert an old project, click the "Recreate F3D Materials As V5" operator near the top of the Fast64 tab in 3D view. This may take a while depending on the number of materials in the project. Then go to the outliner, change the display mode to "Orphan Data" (broken heart icon), then click "Purge" in the top right corner. Purge multiple times until all of the old node groups are gone.
### F3DEX3 Features
Fast64 supports exporting data for [F3DEX3](https://github.com/HackerN64/F3DEX3), a modded microcode which brings many new features and higher performance. **Preview of these new features in the 3D view is not currently supported**, but they will be exported correctly. To modify the vertex colors of an object with a material which is using packed normals (shading/lighting and vertex colors together), temporarily switch to a vertex colored preset or uncheck `Lighting` in the material geometry mode settings. Once the vertex colors are painted how you want them, re-enable `Lighting` and `Packed Normals`.
Selecting F3DEX3 as your microcode unlocks a large number of additional presets based on F3DEX3 features. For more information on all these features, see the F3DEX3 readme, GBI, and [these videos](https://www.youtube.com/playlist?list=PLU2OUGtyQi6QswDQOXWIMaYFUcgQ9Psvm). The preset names get very long and are abbreviated as follows:
- `Shaded`: Computes lighting, which normally affects shade color.
- `Vcol`: Vertex colors are enabled in addition to lighting; normally these are multiplied together to become shade color.
- `Ao`: Ambient occlusion.
- `Cel`: Cel shading. If followed by a number, this is the number of cel levels.
- `Ltcol`: Cel shading tints are loaded from light colors.
- `Blend` vs. `Mul` for cel shading: Whether to apply the tint in the blender with linear interpolation, or by multiplication in the CC. The latter sometimes looks better, but does not support vertex colors.
- `Lerp` vs. `Mult` for multitexture (water): Whether the two textures are combined by linear interpolation or multiplication.
For cel shading, it is recommended to start with one of the cel shading presets, then modify the settings under the `Use Cel Shading` panel. Hover over each UI control for additional information about how that setting works.
### Updater
Fast64 features an updater ([CGCookie/blender-addon-updater](https://github.com/CGCookie/blender-addon-updater)).
+5 -4
View File
@@ -161,7 +161,6 @@ class F3D_GlobalSettingsPanel(bpy.types.Panel):
col = self.layout.column()
col.scale_y = 1.1 # extra padding
prop_split(col, context.scene, "f3d_type", "F3D Microcode")
col.prop(context.scene, "isHWv1")
col.prop(context.scene, "saveTextures")
col.prop(context.scene, "f3d_simple", text="Simple Material UI")
col.prop(context.scene, "generateF3DNodeGraph", text="Generate F3D Node Graph For Materials")
@@ -427,7 +426,6 @@ def gameEditorUpdate(self, context):
# register operators and panels here
# append menu layout drawing function to an existing window
def register():
if bpy.app.version < (3, 2, 0):
msg = "\n".join(
(
@@ -472,8 +470,11 @@ def register():
bpy.types.Scene.saveTextures = bpy.props.BoolProperty(name="Save Textures As PNGs (Breaks CI Textures)")
bpy.types.Scene.generateF3DNodeGraph = bpy.props.BoolProperty(name="Generate F3D Node Graph", default=True)
bpy.types.Scene.exportHiddenGeometry = bpy.props.BoolProperty(name="Export Hidden Geometry", default=True)
bpy.types.Scene.exportInlineF3D = bpy.props.BoolProperty(name="Bleed and Inline Material Exports", \
description = "Inlines and bleeds materials in a single mesh. GeoLayout + Armature exports bleed over entire model", default=False)
bpy.types.Scene.exportInlineF3D = bpy.props.BoolProperty(
name="Bleed and Inline Material Exports",
description="Inlines and bleeds materials in a single mesh. GeoLayout + Armature exports bleed over entire model",
default=False,
)
bpy.types.Scene.blenderF3DScale = bpy.props.FloatProperty(
name="F3D Blender Scale", default=100, update=on_update_render_settings
)
+55 -12
View File
@@ -171,7 +171,11 @@ enumCombKey = [
enumTextConv = [
("G_TC_CONV", "Convert", "Convert YUV to RGB"),
("G_TC_FILTCONV", "Filter And Convert", "Applies chosen filter on cycle 1 and converts YUB to RGB in the second cycle"),
(
"G_TC_FILTCONV",
"Filter And Convert",
"Applies chosen filter on cycle 1 and converts YUB to RGB in the second cycle",
),
("G_TC_FILT", "Filter", "Applies chosen filter on textures with no color conversion"),
]
@@ -189,11 +193,19 @@ enumTextLUT = [
enumTextLOD = [
("G_TL_TILE", "Tile", "Shows selected color combiner tiles"),
("G_TL_LOD", "LoD", "Enables LoD calculations, LoD tile is base tile + clamp(log2(texel/pixel)), remainder of log2(texel/pixel) ratio gets stored to LoD Fraction in the color combiner"),
(
"G_TL_LOD",
"LoD",
"Enables LoD calculations, LoD tile is base tile + clamp(log2(texel/pixel)), remainder of log2(texel/pixel) ratio gets stored to LoD Fraction in the color combiner",
),
]
enumTextDetail = [
("G_TD_CLAMP", "Clamp", "Shows base tile for texel0 and texel 1 when magnifying (>1 texel/pixel), else shows LoD tiles"),
(
"G_TD_CLAMP",
"Clamp",
"Shows base tile for texel0 and texel 1 when magnifying (>1 texel/pixel), else shows LoD tiles",
),
("G_TD_SHARPEN", "Sharpen", "Sharpens pixel colors when magnifying (<1 texel/pixel), always shows LoD tiles"),
("G_TD_DETAIL", "Detail", "Shows base tile when magnifying (<1 texel/pixel), else shows LoD tiles + 1"),
]
@@ -213,8 +225,16 @@ enumCycleType = [
enumColorDither = [("G_CD_DISABLE", "Disable", "Disable"), ("G_CD_ENABLE", "Enable", "Enable")]
enumPipelineMode = [
("G_PM_1PRIMITIVE", "1 Primitive", "Adds in pipe sync after every tri draw. Adds significant amounts of lag. Only use in vanilla SM64 hacking projects"),
("G_PM_NPRIMITIVE", "N Primitive", "No additional syncs are added after tri draws. Default option for every game but vanilla SM64"),
(
"G_PM_1PRIMITIVE",
"1 Primitive",
"Adds in pipe sync after every tri draw. Adds significant amounts of lag. Only use in vanilla SM64 hacking projects",
),
(
"G_PM_NPRIMITIVE",
"N Primitive",
"No additional syncs are added after tri draws. Default option for every game but vanilla SM64",
),
]
enumAlphaCompare = [
@@ -248,7 +268,7 @@ enumBlendColor = [
"Input (CC/Blender)",
"First cycle: Color Combiner RGB, Second cycle: Blender numerator from first cycle",
),
("G_BL_CLR_MEM", "Framebuffer Color", "Framebuffer Color (Memory)"),
("G_BL_CLR_MEM", "Framebuffer Color", "Framebuffer (Memory) Color"),
("G_BL_CLR_BL", "Blend Color", "Blend Color Register"),
("G_BL_CLR_FOG", "Fog Color", "Fog Color Register"),
]
@@ -256,13 +276,13 @@ enumBlendColor = [
enumBlendAlpha = [
("G_BL_A_IN", "Color Combiner Alpha", "Color Combiner Alpha"),
("G_BL_A_FOG", "Fog Alpha", "Fog Color Register Alpha"),
("G_BL_A_SHADE", "Shade Alpha", "Stepped Shade Alpha"),
("G_BL_A_SHADE", "Shade Alpha", "Stepped Shade Alpha from RSP, often fog"),
("G_BL_0", "0", "0"),
]
enumBlendMix = [
("G_BL_1MA", "1 - A", "1 - A, where A is selected above"),
("G_BL_A_MEM", "Framebuffer Alpha", "Framebuffer (Memory) Alpha"),
("G_BL_A_MEM", "Framebuffer Alpha", "Framebuffer (Memory) Alpha (Coverage)"),
("G_BL_1", "1", "1"),
("G_BL_0", "0", "0"),
]
@@ -357,15 +377,38 @@ maxTexelCount = {
}
enumF3D = [
("F3D", "F3D", "F3D"),
("F3DEX/LX", "F3DEX/LX", "F3DEX/LX"),
("F3D", "F3D", "Original microcode used in SM64"),
("F3DEX/LX", "F3DEX/LX", "F3DEX version 1"),
("F3DLX.Rej", "F3DLX.Rej", "F3DLX.Rej"),
("F3DLP.Rej", "F3DLP.Rej", "F3DLP.Rej"),
("F3DEX2/LX2", "F3DEX2/LX2", "F3DEX2/LX2"),
("F3DEX2.Rej/LX2.Rej", "F3DEX2.Rej/LX2.Rej", "F3DEX2.Rej/LX2.Rej"),
("F3DEX2/LX2", "F3DEX2/LX2/ZEX", "Family of microcodes used in later N64 games including OoT and MM"),
("F3DEX2.Rej/LX2.Rej", "F3DEX2.Rej/LX2.Rej", "Variant of F3DEX2 family using vertex rejection instead of clipping"),
("F3DEX3", "F3DEX3", "Custom microcode by Sauraen"),
]
enumLargeEdges = [
("Clamp", "Clamp", "Clamp outside image bounds"),
("Wrap", "Wrap", "Wrap outside image bounds (more expensive)"),
]
enumCelThreshMode = [
("Lighter", "Lighter", "This cel level is drawn when the lighting level per-pixel is LIGHTER than (>=) the threshold"),
("Darker", "Darker", "This cel level is drawn when the lighting level per-pixel is DARKER than (<) the threshold"),
]
enumCelTintPipeline = [
("CC", "CC (tint in Prim Color)", "Cel shading puts tint color in Prim Color and tint level in Prim Alpha. Set up CC color to LERP between source color and tint color based on tint level, or multiply source color by tint color. Source may be Tex 0 or Env Color"),
("Blender", "Blender (tint in Fog Color)", "Cel shading puts tint color in Fog Color and tint level in Fog Alpha. Set up blender to LERP between CC output and tint color based on tint level. Then set CC to Tex 0 * shade color (vertex colors)"),
]
enumCelCutoutSource = [
("TEXEL0", "Texture 0", "Cel shading material has binary alpha cutout from Texture 0 alpha. Does not work with I4 or I8 formats"),
("TEXEL1", "Texture 1", "Cel shading material has binary alpha cutout from Texture 1 alpha. Does not work with I4 or I8 formats"),
("ENVIRONMENT", "None / Env Alpha", "Make sure your material writes env color, and set env alpha to opaque (255)"),
]
enumCelTintType = [
("Fixed", "Fixed", "Fixed tint color and level stored directly in DL"),
("Segment", "Segment", "Call a segmented DL to set the tint, can change at runtime"),
("Light", "From Light", "Automatically load tint color from selectable light slot. Tint level stored in DL"),
]
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+141 -73
View File
@@ -1,5 +1,6 @@
from typing import Union
import bmesh, bpy, mathutils, re, math, traceback
from mathutils import Vector
from bpy.utils import register_class, unregister_class
from .f3d_gbi import *
from .f3d_material import (
@@ -257,7 +258,7 @@ def interpretLoadVertices(romfile, vertexBuffer, transformMatrix, command, segme
data = romfile.read(dataLength)
for i in range(numVerts):
vert = mathutils.Vector(readVectorFromShorts(data, i * 16))
vert = Vector(readVectorFromShorts(data, i * 16))
vert = transformMatrix @ vert
transformedVert = bytearray(6)
writeVectorToShorts(transformedVert, 0, vert)
@@ -276,9 +277,9 @@ def interpretDrawTriangle(command, vertexBuffer, faceSeq, vertSeq, uv_layer, def
index1 = int(command[6] / 0x0A)
index2 = int(command[7] / 0x0A)
vert0 = mathutils.Vector(getPosition(vertexBuffer, index0))
vert1 = mathutils.Vector(getPosition(vertexBuffer, index1))
vert2 = mathutils.Vector(getPosition(vertexBuffer, index2))
vert0 = Vector(getPosition(vertexBuffer, index0))
vert1 = Vector(getPosition(vertexBuffer, index1))
vert2 = Vector(getPosition(vertexBuffer, index2))
verts[0] = vertSeq.new(vert0)
verts[1] = vertSeq.new(vert1)
@@ -292,7 +293,7 @@ def interpretDrawTriangle(command, vertexBuffer, faceSeq, vertSeq, uv_layer, def
loopIndex = 0
for loop in tri.loops:
loop[uv_layer].uv = mathutils.Vector(getUV(vertexBuffer, int(command[5 + loopIndex] / 0x0A)))
loop[uv_layer].uv = Vector(getUV(vertexBuffer, int(command[5 + loopIndex] / 0x0A)))
loopIndex += 1
return verts
@@ -506,19 +507,19 @@ class F3DContext:
# data for Mesh.from_pydata, list of BufferVertex tuples
# use BufferVertex to also form uvs / normals / colors
self.verts: list[F3DVert] = []
self.limbGroups: dict[str : list[int]] = {} # dict of groupName : vertex indices
self.limbGroups: dict[str, list[int]] = {} # dict of groupName : vertex indices
self.lights: Lights = Lights("lights_context")
self.lights: Lights = Lights("lights_context", self.f3d)
# Here these are ints, but when parsing the values will be normalized.
self.lights.l = [
Light([0, 0, 0], [0x28, 0x28, 0x28]),
Light([0, 0, 0], [0x28, 0x28, 0x28]),
Light([0, 0, 0], [0x28, 0x28, 0x28]),
Light([0, 0, 0], [0x28, 0x28, 0x28]),
Light([0, 0, 0], [0x28, 0x28, 0x28]),
Light([0, 0, 0], [0x28, 0x28, 0x28]),
Light([0, 0, 0], [0x28, 0x28, 0x28]),
Light([0, 0, 0], [0x49, 0x49, 0x49]),
Light([0, 0, 0], [0x49, 0x49, 0x49]),
Light([0, 0, 0], [0x49, 0x49, 0x49]),
Light([0, 0, 0], [0x49, 0x49, 0x49]),
Light([0, 0, 0], [0x49, 0x49, 0x49]),
Light([0, 0, 0], [0x49, 0x49, 0x49]),
Light([0, 0, 0], [0x49, 0x49, 0x49]),
]
self.lights.a = Ambient([0, 0, 0])
self.numLights: int = 0
@@ -576,15 +577,15 @@ class F3DContext:
self.tileSizes = [DPSetTileSize(i, 0, 0, 32, 32) for i in range(8)]
self.lights = Lights("lights_context")
self.lights = Lights("lights_context", self.f3d)
self.lights.l = [
Light([0, 0, 0], [0x28, 0x28, 0x28]),
Light([0, 0, 0], [0x28, 0x28, 0x28]),
Light([0, 0, 0], [0x28, 0x28, 0x28]),
Light([0, 0, 0], [0x28, 0x28, 0x28]),
Light([0, 0, 0], [0x28, 0x28, 0x28]),
Light([0, 0, 0], [0x28, 0x28, 0x28]),
Light([0, 0, 0], [0x28, 0x28, 0x28]),
Light([0, 0, 0], [0x49, 0x49, 0x49]),
Light([0, 0, 0], [0x49, 0x49, 0x49]),
Light([0, 0, 0], [0x49, 0x49, 0x49]),
Light([0, 0, 0], [0x49, 0x49, 0x49]),
Light([0, 0, 0], [0x49, 0x49, 0x49]),
Light([0, 0, 0], [0x49, 0x49, 0x49]),
Light([0, 0, 0], [0x49, 0x49, 0x49]),
]
self.lights.a = Ambient([0, 0, 0])
self.numLights = 0
@@ -600,14 +601,14 @@ class F3DContext:
# decomp format
"\{\s*\{\s*"
+ "\{([^,\}]*),([^,\}]*),([^,\}]*)\}\s*,"
+ "[^,\}]*,\s*"
+ "([^,\}]*),\s*"
+ "\{([^,\}]*),([^,\}]*)\}\s*,\s*"
+ "\{([^,\}]*),([^,\}]*),([^,\}]*),([^,\}]*)\}\s*"
+ "\}\s*\}",
# nusys format
"\{\s*"
+ "([^,\}]*),([^,\}]*),([^,\}]*),"
+ "[^,\}]*,"
+ "([^,\}]*),"
+ "([^,\}]*),([^,\}]*),"
+ "([^,\}]*),([^,\}]*),([^,\}]*),([^,\}]*)\s*"
+ "\}",
@@ -641,7 +642,7 @@ class F3DContext:
mat = self.mat()
f3dVert = bufferVert.f3dVert
position = transform @ mathutils.Vector(f3dVert.position)
position = transform @ Vector(f3dVert.position)
if mat.tex0.tex is not None:
texDimensions = mat.tex0.tex.size
elif mat.tex0.use_tex_reference:
@@ -656,18 +657,19 @@ class F3DContext:
uv = [convertF3DUV(f3dVert.uv[i], texDimensions[i]) for i in range(2)]
uv[1] = 1 - uv[1]
color = [
value / 256
if value > 0
else int.from_bytes(round(value).to_bytes(1, "big", signed=True), "big", signed=False) / 256
for value in f3dVert.getColorOrNormal()
]
normal = bytesToNormal(f3dVert.getColorOrNormal()[:3]) + [0]
normal = (transform.inverted().transposed() @ mathutils.Vector(normal)).normalized()[:3]
has_rgb, has_normal, has_packed_normals = getRgbNormalSettings(self.mat())
rgb = Vector([v / 0xFF for v in f3dVert.rgb]) if has_rgb else Vector([1.0, 1.0, 1.0])
alpha = f3dVert.alpha / 0xFF
normal = Vector([0.0, 0.0, 0.0]) # Zero normal makes normals_split_custom_set use auto
if has_normal:
if has_packed_normals:
normal = f3dVert.normal
else:
normal = Vector([v - 0x100 if v >= 0x80 else v for v in f3dVert.rgb]).normalized()
normal = (transform.inverted().transposed() @ normal).normalized()
# NOTE: The groupIndex here does NOT correspond to a vertex group, but to the name of the limb (c variable)
return BufferVertex(F3DVert(position, uv, color, normal), bufferVert.groupIndex, bufferVert.materialIndex)
return BufferVertex(F3DVert(position, uv, rgb, normal, alpha), bufferVert.groupIndex, bufferVert.materialIndex)
def addVertices(self, num, start, vertexDataName, vertexDataOffset):
vertexData = self.vertexData[vertexDataName]
@@ -879,6 +881,23 @@ class F3DContext:
mat.rdp_settings.g_cull_front = value
if bitFlags & self.f3d.G_CULL_BACK:
mat.rdp_settings.g_cull_back = value
if self.f3d.F3DEX_GBI_3:
if bitFlags & self.f3d.G_AMBOCCLUSION:
mat.rdp_settings.g_ambocclusion = value
if bitFlags & self.f3d.G_ATTROFFSET_Z_ENABLE:
mat.rdp_settings.g_attroffset_z_enable = value
if bitFlags & self.f3d.G_ATTROFFSET_ST_ENABLE:
mat.rdp_settings.g_attroffset_st_enable = value
if bitFlags & self.f3d.G_PACKED_NORMALS:
mat.rdp_settings.g_packed_normals = value
if bitFlags & self.f3d.G_LIGHTTOALPHA:
mat.rdp_settings.g_lighttoalpha = value
if bitFlags & self.f3d.G_LIGHTING_SPECULAR:
mat.rdp_settings.g_lighting_specular = value
if bitFlags & self.f3d.G_FRESNEL_COLOR:
mat.rdp_settings.g_fresnel_color = value
if bitFlags & self.f3d.G_FRESNEL_ALPHA:
mat.rdp_settings.g_fresnel_alpha = value
if bitFlags & self.f3d.G_FOG:
mat.rdp_settings.g_fog = value
if bitFlags & self.f3d.G_LIGHTING:
@@ -887,6 +906,8 @@ class F3DContext:
mat.rdp_settings.g_tex_gen = value
if bitFlags & self.f3d.G_TEXTURE_GEN_LINEAR:
mat.rdp_settings.g_tex_gen_linear = value
if bitFlags & self.f3d.G_LOD:
mat.rdp_settings.g_lod = value
if bitFlags & self.f3d.G_SHADING_SMOOTH:
mat.rdp_settings.g_shade_smooth = value
if bitFlags & self.f3d.G_CLIPPING:
@@ -901,10 +922,29 @@ class F3DContext:
mat.rdp_settings.g_shade = bitFlags & self.f3d.G_SHADE != 0
mat.rdp_settings.g_cull_front = bitFlags & self.f3d.G_CULL_FRONT != 0
mat.rdp_settings.g_cull_back = bitFlags & self.f3d.G_CULL_BACK != 0
if self.f3d.F3DEX_GBI_3:
mat.rdp_settings.g_ambocclusion = bitFlags & self.f3d.G_AMBOCCLUSION != 0
mat.rdp_settings.g_attroffset_z_enable = bitFlags & self.f3d.G_ATTROFFSET_Z_ENABLE != 0
mat.rdp_settings.g_attroffset_st_enable = bitFlags & self.f3d.G_ATTROFFSET_ST_ENABLE != 0
mat.rdp_settings.g_packed_normals = bitFlags & self.f3d.G_PACKED_NORMALS != 0
mat.rdp_settings.g_lighttoalpha = bitFlags & self.f3d.G_LIGHTTOALPHA != 0
mat.rdp_settings.g_lighting_specular = bitFlags & self.f3d.G_LIGHTING_SPECULAR != 0
mat.rdp_settings.g_fresnel_color = bitFlags & self.f3d.G_FRESNEL_COLOR != 0
mat.rdp_settings.g_fresnel_alpha = bitFlags & self.f3d.G_FRESNEL_ALPHA != 0
else:
mat.rdp_settings.g_ambocclusion = False
mat.rdp_settings.g_attroffset_z_enable = False
mat.rdp_settings.g_attroffset_st_enable = False
mat.rdp_settings.g_packed_normals = False
mat.rdp_settings.g_lighttoalpha = False
mat.rdp_settings.g_lighting_specular = False
mat.rdp_settings.g_fresnel_color = False
mat.rdp_settings.g_fresnel_alpha = False
mat.rdp_settings.g_fog = bitFlags & self.f3d.G_FOG != 0
mat.rdp_settings.g_lighting = bitFlags & self.f3d.G_LIGHTING != 0
mat.rdp_settings.g_tex_gen = bitFlags & self.f3d.G_TEXTURE_GEN != 0
mat.rdp_settings.g_tex_gen_linear = bitFlags & self.f3d.G_TEXTURE_GEN_LINEAR != 0
mat.rdp_settings.g_lod = bitFlags & self.f3d.G_LOD != 0
mat.rdp_settings.g_shade_smooth = bitFlags & self.f3d.G_SHADING_SMOOTH != 0
mat.rdp_settings.g_clipping = bitFlags & self.f3d.G_CLIPPING != 0
@@ -1147,9 +1187,9 @@ class F3DContext:
lightObj.rotation_euler = (
mathutils.Euler((0, 0, math.pi)).to_quaternion()
@ (
mathutils.Euler((math.pi / 2, 0, 0)).to_quaternion() @ mathutils.Vector(light.normal)
).rotation_difference(mathutils.Vector((0, 0, 1)))
@ (mathutils.Euler((math.pi / 2, 0, 0)).to_quaternion() @ Vector(light.normal)).rotation_difference(
Vector((0, 0, 1))
)
).to_euler()
# lightObj.rotation_euler[0] *= 1
bLight.color = light.color
@@ -1173,7 +1213,7 @@ class F3DContext:
self.mat().set_lights = True
lightIndex = self.getLightIndex(command.params[0])
colorData = math_eval(command.params[1], self.f3d)
color = mathutils.Vector(
color = Vector(
[((colorData >> 24) & 0xFF) / 0xFF, ((colorData >> 16) & 0xFF) / 0xFF, ((colorData >> 8) & 0xFF) / 0xFF]
)
@@ -1231,21 +1271,21 @@ class F3DContext:
def createLights(self, data, lightsName):
numLights, lightValues = parseLightsData(data, lightsName, self)
ambientColor = mathutils.Vector(gammaInverse([value / 255 for value in lightValues[0:3]]))
ambientColor = Vector(gammaInverse([value / 255 for value in lightValues[0:3]]))
lightList = []
for i in range(numLights):
color = mathutils.Vector(gammaInverse([value / 255 for value in lightValues[3 + 6 * i : 3 + 6 * i + 3]]))
direction = mathutils.Vector(bytesToNormal(lightValues[3 + 6 * i + 3 : 3 + 6 * i + 6]))
color = Vector(gammaInverse([value / 255 for value in lightValues[3 + 6 * i : 3 + 6 * i + 3]]))
direction = Vector(bytesToNormal(lightValues[3 + 6 * i + 3 : 3 + 6 * i + 6]))
lightList.append(Light(color, direction))
while len(lightList) < 7:
lightList.append(Light(mathutils.Vector([0, 0, 0]), mathutils.Vector([0x28, 0x28, 0x28])))
lightList.append(Light(Vector([0, 0, 0]), Vector([0x49, 0x49, 0x49])))
# normally a and l are Ambient and Light objects,
# but here they will be a color and blender light object array.
lights = Lights(lightsName)
lights = Lights(lightsName, self.f3d)
lights.a = Ambient(ambientColor)
lights.l = lightList
@@ -1547,6 +1587,45 @@ class F3DContext:
self.setLightColor(dlData, command)
elif command.name[:13] == "gsSPSetLights":
self.setLights(dlData, command)
elif command.name == "gsSPAmbOcclusionAmb":
mat.ao_ambient = float_from_u16_str(command.params[0])
mat.set_ao = True
elif command.name == "gsSPAmbOcclusionDir":
mat.ao_directional = float_from_u16_str(command.params[0])
mat.set_ao = True
elif command.name == "gsSPAmbOcclusionPoint":
mat.ao_point = float_from_u16_str(command.params[0])
mat.set_ao = True
elif command.name == "gsSPAmbOcclusionAmbDir":
mat.ao_ambient = float_from_u16_str(command.params[0])
mat.ao_directional = float_from_u16_str(command.params[1])
mat.set_ao = True
elif command.name == "gsSPAmbOcclusionDirPoint":
mat.ao_directional = float_from_u16_str(command.params[0])
mat.ao_point = float_from_u16_str(command.params[1])
mat.set_ao = True
elif command.name == "gsSPAmbOcclusion":
mat.ao_ambient = float_from_u16_str(command.params[0])
mat.ao_directional = float_from_u16_str(command.params[1])
mat.ao_point = float_from_u16_str(command.params[2])
mat.set_ao = True
elif command.name == "gsSPFresnel":
scale = int_from_s16_str(command.params[0])
offset = int_from_s16_str(command.params[1])
dotMax = ((0x7F - offset) << 15) // scale
dotMin = ((0x00 - offset) << 15) // scale
mat.fresnel_hi = dotMax / float(0x7FFF)
mat.fresnel_lo = dotMin / float(0x7FFF)
mat.set_fresnel = True
elif command.name == "gsSPAttrOffsetST":
mat.attroffs_st = [
int_from_s16_str(command.params[0]) / 32,
int_from_s16_str(command.params[1]) / 32,
]
mat.set_attroffs_st = True
elif command.name == "gsSPAttrOffsetZ":
mat.attroffs_z = int_from_s16_str(command.params[0])
mat.set_attroffs_z = True
elif command.name == "gsSPFogFactor":
pass
elif command.name == "gsSPFogPosition":
@@ -1776,12 +1855,11 @@ class F3DContext:
color_layer = mesh.vertex_colors.new(name="Col").data
for i in range(len(mesh.loops)):
# if self.materialContext.f3d_mat.rdp_settings.g_lighting:
color_layer[i].color = self.verts[i].color
color_layer[i].color = self.verts[i].rgb.to_4d()
alpha_layer = mesh.vertex_colors.new(name="Alpha").data
for i in range(len(mesh.loops)):
alpha_layer[i].color = [self.verts[i].color[3]] * 3 + [1]
alpha_layer[i].color = [self.verts[i].alpha] * 3 + [1]
if bpy.context.mode != "OBJECT":
bpy.ops.object.mode_set(mode="OBJECT")
@@ -1803,7 +1881,7 @@ class F3DContext:
i = 0
for key, lightObj in self.lightData.items():
lightObj.location = bpy.context.scene.cursor.location + mathutils.Vector((i, 0, 0))
lightObj.location = bpy.context.scene.cursor.location + Vector((i, 0, 0))
i += 1
self.clearGeometry()
@@ -1903,29 +1981,21 @@ def parseVertexData(dlData: str, vertexDataName: str, f3dContext: F3DContext):
patterns = f3dContext.vertexFormatPatterns(data)
vertexData = []
for pattern in patterns:
# Note that color is None here, as we are just parsing vertex data.
# The same values should be used for color and normal, but getColorOrNormal() will be used later
# which returns whichever value is not None between the two.
# When loaded into the vertex buffer and transformed, the actual normal/color will be calculated.
vertexData = [
F3DVert(
mathutils.Vector(
[math_eval(match.group(1), f3d), math_eval(match.group(2), f3d), math_eval(match.group(3), f3d)]
),
mathutils.Vector([math_eval(match.group(4), f3d), math_eval(match.group(5), f3d)]),
None,
mathutils.Vector(
[
math_eval(match.group(6), f3d),
math_eval(match.group(7), f3d),
math_eval(match.group(8), f3d),
math_eval(match.group(9), f3d),
]
),
# For this step, store rgb/normal as rgb and packed normal as normal.
for match in re.finditer(pattern, data, re.DOTALL):
values = [math_eval(g, f3d) for g in match.groups()]
if len(values) == 9:
# A format without the flag / packed normal
values = values[0:3] + [0] + values[3:9]
vertexData.append(
F3DVert(
Vector(values[0:3]),
Vector(values[4:6]),
Vector(values[6:9]),
unpackNormal(values[3]),
values[9],
)
)
for match in re.finditer(pattern, data, re.DOTALL)
]
if len(vertexData) > 0:
break
f3dContext.vertexData[vertexDataName] = vertexData
@@ -2233,8 +2303,6 @@ class F3D_ImportDL(bpy.types.Operator):
removeDoubles = context.scene.DLRemoveDoubles
importNormals = context.scene.DLImportNormals
drawLayer = context.scene.DLImportDrawLayer
f3dType = context.scene.f3d_type
isHWv1 = context.scene.isHWv1
data = getImportData([importPath])
@@ -2245,7 +2313,7 @@ class F3D_ImportDL(bpy.types.Operator):
removeDoubles,
importNormals,
drawLayer,
F3DContext(F3D(f3dType, isHWv1), basePath, createF3DMat(None)),
F3DContext(get_F3D_GBI(), basePath, createF3DMat(None)),
)
self.report({"INFO"}, "Success!")
+7 -29
View File
@@ -1061,35 +1061,13 @@ def savePaletteLoad(
assert 0 <= palAddr < 256 and (palAddr & 0xF) == 0
palFmt = texFormatOf[palFormat]
nocm = ["G_TX_WRAP", "G_TX_NOMIRROR"]
if not f3d._HW_VERSION_1:
gfxOut.commands.extend(
[
DPSetTextureImage(palFmt, "G_IM_SIZ_16b", 1, fPalette),
DPSetTile("0", "0", 0, 256 + palAddr, loadtile, 0, nocm, 0, 0, nocm, 0, 0),
DPLoadTLUTCmd(loadtile, palLen - 1),
]
)
else:
gfxOut.commands.extend(
[
_DPLoadTextureBlock(
fPalette,
256 + palAddr,
palFmt,
"G_IM_SIZ_16b",
4 * palLen,
1,
0,
nocm,
nocm,
0,
0,
0,
0,
)
]
)
gfxOut.commands.extend(
[
DPSetTextureImage(palFmt, "G_IM_SIZ_16b", 1, fPalette),
DPSetTile("0", "0", 0, 256 + palAddr, loadtile, 0, nocm, 0, 0, nocm, 0, 0),
DPLoadTLUTCmd(loadtile, palLen - 1),
]
)
# Functions for converting and writing texture and palette data
File diff suppressed because it is too large Load Diff
+928
View File
@@ -0,0 +1,928 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Documentation on this file's format:
elements:
- <Actor> -> defines an actor
* Category -> the actor's category
* ID -> the actor's ID (written when exporting to C)
* Key -> the actor's identifier (shouldn't be changed ever, saved in the .blend)
* Name -> Display name (seen in the UI)
* ObjectKey -> the actor's tied objects (identified by the never-changing key)
* Index -> used for compatibility with blends
- <List> -> defines a hardcoded list (WIP, can't be used atm)
* Name -> name of the list
* Key -> the list identifier (shouldn't be changed)
sub-elements of <Actor>:
- <Type> -> adds an enum property of the actor's basic parameter (can't use multiple ones atm)
* Index -> defines an order
* Mask -> the mask to apply to the value (as in `value & mask`)
# <Item> -> represents a single element of the enum (Params -> the parameter value, already shifted)
for each sub element (of <Actor>) mentioned below:
* Index -> defines an order (should not change, used for saving in the .blend)
* Mask -> the mask to apply to the value (as in `value & mask`), the amount of shifting is determined from that
* Name -> display name (in the UI)
* TiedActorTypes -> optional, used to use this property for the current actor type (see en_rd for an example)
* Target -> optional, defines which variable should be used to store this parameter (actor.home.rot.X-Y-Z, actor.params), if none then Params is used by default
- <Bool> -> adds a bool property (checkbox)
- <Enum> -> adds an enum property, different from <Type> (use this if you want multiple <Type> enums for now)
* Value -> the not-shifted hex value (0x1, 0x2, etc)
- <Property> -> adds a string property, expects an hex value
- <Flag> -> adds a string property, specific to actor flags (switch flag, chest flag, etc)
* Type -> the type of flag we're dealing with: switch, collectible or chest
- <Message> -> used to draw the navi message id <List>
- <ChestContent> -> used to draw the chest content item <List>
- <Collectible> -> used to draw the collectible drop item <List>
-->
<Table>
<Actor ID="ACTOR_PLAYER" Key="player" ObjectKey="OBJECT_UNSET_0" Name="Player" Category=""></Actor>
<Actor ID="ACTOR_EN_TEST" Key="en_test" ObjectKey="GAMEPLAY_KEEP" Name="En_Test" Category=""></Actor>
<Actor ID="ACTOR_EN_GIRLA" Key="en_girla" ObjectKey="GAMEPLAY_KEEP" Name="En_GirlA" Category=""></Actor>
<Actor ID="ACTOR_EN_PART" Key="en_part" ObjectKey="GAMEPLAY_KEEP" Name="En_Part" Category=""></Actor>
<Actor ID="ACTOR_EN_LIGHT" Key="en_light" ObjectKey="GAMEPLAY_KEEP" Name="Flame" Category="">
<Type Index="1" Mask="0x000F">
<Item Params="0000">Large Orange Flame</Items>
<Item Params="0001">Large Orange Flame</Items>
<Item Params="0002">Large Blue Flame</Items>
<Item Params="0003">Large Green Flame</Items>
<Item Params="0004">Small Orange Flame</Items>
<Item Params="0005">Large Orange Flame</Items>
<Item Params="0006">Large Green Flame</Items>
<Item Params="0007">Large Blue Flame</Items>
<Item Params="0008">Large Magenta Flame</Items>
<Item Params="0009">Large Pale Orange Flame</Items>
<Item Params="000A">Large Pale Yellow Flame</Items>
<Item Params="000B">Large Pale Green Flame</Items>
<Item Params="000C">Large Pale Pink Flame</Items>
<Item Params="000D">Large Pale Purple Flame</Items>
<Item Params="000E">Large Pale Indigo Flame</Items>
<Item Params="000F">Large Pale Blue Flame</Items>
<!--Item Params="83F0">Candle Flame</Items-->
<!--Item Params="FFFF">Faint Blue Aura</Items-->
</Type>
</Actor>
<Actor ID="ACTOR_EN_DOOR" Key="en_door" ObjectKey="GAMEPLAY_KEEP" Name="Wooden Door" Category=""></Actor>
<Actor ID="ACTOR_EN_BOX" Key="en_box" ObjectKey="OBJECT_BOX" Name="Treasure Chest" Category="">
<Type Index="1" Mask="0xF000">
<Item Params="0000">Golden</Item>
<Item Params="1000">Golden - Appears - Clear Flag</Item>
<Item Params="2000">Boss Key Chest</Item>
<Item Params="3000">Golden - Falls - Switch Flag</Item>
<Item Params="4000">Golden - Invisible</Item>
<Item Params="5000">Wooden</Item>
<Item Params="6000">Wooden - Invisible</Item>
<Item Params="7000">Wooden - Clear Flag</Item>
<Item Params="8000">Wooden - Falls - Switch Flag</Item>
<Item Params="9000">Crash</Item>
<Item Params="A000">Crash</Item>
<Item Params="B000">Golden - Appears - Switch Flag</Item>
</Type>
<Flag Index="1" Mask="0x1FF" Target="ZRot" Type="Switch"/>
<Flag Index="2" Mask="0x001F" Target="Params" Type="Chest"/>
<ChestContent Mask="0x0FE0" Target="Params"/>
</Actor>
<!-- this->collectableFlag = (this->dyna.actor.world.rot.x & 0x7F); -->
<Actor ID="ACTOR_EN_PAMETFROG" Key="en_pametfrog" ObjectKey="OBJECT_BIGSLIME" Name="Gekko and Snapper Miniboss" Category=""></Actor>
<Actor ID="ACTOR_EN_OKUTA" Key="en_okuta" ObjectKey="OBJECT_OKUTA" Name="Octorok" Category=""></Actor>
<Actor ID="ACTOR_EN_BOM" Key="en_bom" ObjectKey="GAMEPLAY_KEEP" Name="Powder Keg" Category=""></Actor>
<Actor ID="ACTOR_EN_WALLMAS" Key="en_wallmas" ObjectKey="OBJECT_WALLMASTER" Name="Wallmaster" Category=""></Actor>
<Actor ID="ACTOR_EN_DODONGO" Key="en_dodongo" ObjectKey="OBJECT_DODONGO" Name="Dodongo" Category="">
<Type Index="1">
<Item Params="0000">Regular</Item>
<Item Params="0001">Large</Item>
</Type>
</Actor>
<Actor ID="ACTOR_EN_FIREFLY" Key="en_firefly" ObjectKey="OBJECT_FIREFLY" Name="Keese" Category=""></Actor>
<Actor ID="ACTOR_EN_HORSE" Key="en_horse" ObjectKey="GAMEPLAY_KEEP" Name="Child Epona (Cutscenes)" Category=""></Actor>
<Actor ID="ACTOR_EN_ITEM00" Key="en_item00" ObjectKey="OBJECT_UNSET_0" Name="Collectible Items" Category="">
<Collectible Index="1" Mask="0x00FF" Name="CItem" Target="Params" Type="Drop"/>
<Flag Index="1" Mask="0x7F00" Name="Collectible Flag" Type="Collectible"/>
<Bool Index="1" Mask="0x8000" Name="Obtain on Load"/>
</Actor>
<Actor ID="ACTOR_EN_ARROW" Key="en_arrow" ObjectKey="GAMEPLAY_KEEP" Name="Arrow" Category=""></Actor>
<Actor ID="ACTOR_EN_ELF" Key="en_elf" ObjectKey="GAMEPLAY_KEEP" Name="Healing Fairy and Tatl (Gameplay)" Category=""></Actor>
<Actor ID="ACTOR_EN_NIW" Key="en_niw" ObjectKey="OBJECT_NIW" Name="Cucco" Category=""></Actor>
<Actor ID="ACTOR_EN_TITE" Key="en_tite" ObjectKey="OBJECT_TITE" Name="Tektite" Category=""></Actor>
<Actor ID="ACTOR_EN_PEEHAT" Key="en_peehat" ObjectKey="OBJECT_PH" Name="Peahat" Category=""></Actor>
<Actor ID="ACTOR_EN_BUTTE" Key="en_butte" ObjectKey="GAMEPLAY_FIELD_KEEP" Name="Butterfly" Category=""></Actor>
<Actor ID="ACTOR_EN_INSECT" Key="en_insect" ObjectKey="GAMEPLAY_KEEP" Name="Bug" Category=""></Actor>
<Actor ID="ACTOR_EN_FISH" Key="en_fish" ObjectKey="GAMEPLAY_KEEP" Name="Fish" Category=""></Actor>
<Actor ID="ACTOR_EN_HOLL" Key="en_holl" ObjectKey="GAMEPLAY_KEEP" Name="Black Room Transition Plane" Category=""></Actor>
<Actor ID="ACTOR_EN_DINOFOS" Key="en_dinofos" ObjectKey="OBJECT_DINOFOS" Name="Dinolfos" Category=""></Actor>
<Actor ID="ACTOR_EN_HATA" Key="en_hata" ObjectKey="OBJECT_HATA" Name="Red Flag on Post" Category=""></Actor>
<Actor ID="ACTOR_EN_ZL1" Key="en_zl1" ObjectKey="OBJECT_ZL1" Name="Child Zelda" Category=""></Actor>
<Actor ID="ACTOR_EN_VIEWER" Key="en_viewer" ObjectKey="GAMEPLAY_KEEP" Name="En_Viewer" Category=""></Actor>
<Actor ID="ACTOR_EN_BUBBLE" Key="en_bubble" ObjectKey="OBJECT_BUBBLE" Name="Shabom" Category=""></Actor>
<Actor ID="ACTOR_DOOR_SHUTTER" Key="door_shutter" ObjectKey="GAMEPLAY_KEEP" Name="Dungeon Door" Category=""></Actor>
<Actor ID="ACTOR_EN_BOOM" Key="en_boom" ObjectKey="GAMEPLAY_KEEP" Name="Zora Fins" Category=""></Actor>
<Actor ID="ACTOR_EN_TORCH2" Key="en_torch2" ObjectKey="GAMEPLAY_KEEP" Name="Elegy Statues" Category=""></Actor>
<Actor ID="ACTOR_EN_MINIFROG" Key="en_minifrog" ObjectKey="OBJECT_FR" Name="Frog" Category=""></Actor>
<Actor ID="ACTOR_EN_ST" Key="en_st" ObjectKey="OBJECT_ST" Name="Skulltula" Category="">
<Type Index="1" Mask="0x0040">
<Item Params="0000">Default</Item>
<Item Params="0040">Invisible</Item>
</Type>
<Flag Index="1" Mask="0x003F" Type="Switch"/>
</Actor>
<Actor ID="ACTOR_EN_A_OBJ" Key="en_a_obj" ObjectKey="OBJECT_UNSET_0" Name="Directional Sign and Square Sign [Early]" Category=""></Actor>
<Actor ID="ACTOR_OBJ_WTURN" Key="obj_wturn" ObjectKey="GAMEPLAY_KEEP" Name="Stone Tower Temple Inverter" Category=""></Actor>
<Actor ID="ACTOR_EN_RIVER_SOUND" Key="en_river_sound" ObjectKey="GAMEPLAY_KEEP" Name="Sound Effects I" Category=""></Actor>
<Actor ID="ACTOR_EN_OSSAN" Key="en_ossan" ObjectKey="GAMEPLAY_KEEP" Name="Middle-Aged Man" Category=""></Actor>
<Actor ID="ACTOR_EN_FAMOS" Key="en_famos" ObjectKey="OBJECT_FAMOS" Name="Death Armos" Category=""></Actor>
<Actor ID="ACTOR_EN_BOMBF" Key="en_bombf" ObjectKey="OBJECT_BOMBF" Name="Bomb Flower" Category=""></Actor>
<Actor ID="ACTOR_EN_AM" Key="en_am" ObjectKey="OBJECT_AM" Name="Armos" Category=""></Actor>
<Actor ID="ACTOR_EN_DEKUBABA" Key="en_dekubaba" ObjectKey="OBJECT_DEKUBABA" Name="Deku Baba" Category=""></Actor>
<Actor ID="ACTOR_EN_M_FIRE1" Key="en_m_fire1" ObjectKey="GAMEPLAY_KEEP" Name="Deku Nut Effect" Category=""></Actor>
<Actor ID="ACTOR_EN_M_THUNDER" Key="en_m_thunder" ObjectKey="GAMEPLAY_KEEP" Name="Spin Attack and Sword Beam Effects" Category=""></Actor>
<Actor ID="ACTOR_BG_BREAKWALL" Key="bg_breakwall" ObjectKey="GAMEPLAY_KEEP" Name="Post Office Objects" Category=""></Actor>
<Actor ID="ACTOR_DOOR_WARP1" Key="door_warp1" ObjectKey="OBJECT_WARP1" Name="Blue Warp" Category=""></Actor>
<Actor ID="ACTOR_OBJ_SYOKUDAI" Key="obj_syokudai" ObjectKey="OBJECT_SYOKUDAI" Name="Torch" Category=""></Actor>
<Actor ID="ACTOR_ITEM_B_HEART" Key="item_b_heart" ObjectKey="OBJECT_GI_HEARTS" Name="Heart Container (Boss Lairs)" Category=""></Actor>
<Actor ID="ACTOR_EN_DEKUNUTS" Key="en_dekunuts" ObjectKey="OBJECT_DEKUNUTS" Name="Mad Scrub" Category=""></Actor>
<Actor ID="ACTOR_EN_BBFALL" Key="en_bbfall" ObjectKey="OBJECT_BB" Name="Red Bubble" Category=""></Actor>
<Actor ID="ACTOR_ARMS_HOOK" Key="arms_hook" ObjectKey="GAMEPLAY_KEEP" Name="Hookshot" Category=""></Actor>
<Actor ID="ACTOR_EN_BB" Key="en_bb" ObjectKey="OBJECT_BB" Name="Blue Bubble" Category=""></Actor>
<Actor ID="ACTOR_BG_KEIKOKU_SPR" Key="bg_keikoku_spr" ObjectKey="OBJECT_KEIKOKU_OBJ" Name="Fountain Water" Category=""></Actor>
<Actor ID="ACTOR_EN_WOOD02" Key="en_wood02" ObjectKey="OBJECT_WOOD02" Name="Greenery" Category=""></Actor>
<Actor ID="ACTOR_EN_DEATH" Key="en_death" ObjectKey="OBJECT_DEATH" Name="Gomess" Category=""></Actor>
<Actor ID="ACTOR_EN_MINIDEATH" Key="en_minideath" ObjectKey="OBJECT_DEATH" Name="Gomess' Bats" Category=""></Actor>
<Actor ID="ACTOR_EN_VM" Key="en_vm" ObjectKey="OBJECT_VM" Name="Beamos" Category=""></Actor>
<Actor ID="ACTOR_DEMO_EFFECT" Key="demo_effect" ObjectKey="GAMEPLAY_KEEP" Name="Demo_Effect" Category=""></Actor>
<Actor ID="ACTOR_DEMO_KANKYO" Key="demo_kankyo" ObjectKey="GAMEPLAY_KEEP" Name="Environment Effects" Category=""></Actor>
<Actor ID="ACTOR_EN_FLOORMAS" Key="en_floormas" ObjectKey="OBJECT_WALLMASTER" Name="Floormaster" Category=""></Actor>
<Actor ID="ACTOR_EN_RD" Key="en_rd" ObjectKey="OBJECT_RD" Name="Redead" Category=""></Actor>
<Actor ID="ACTOR_BG_F40_FLIFT" Key="bg_f40_flift" ObjectKey="OBJECT_F40_OBJ" Name="Stone Tower Temple Elevator [Early]" Category=""></Actor>
<Actor ID="ACTOR_UNSET_4E" Key="unset_4e" ObjectKey="OBJECT_UNSET_0" Name="Golden Gauntlets Rock (JP 1.0 Only)" Category=""></Actor>
<Actor ID="ACTOR_OBJ_MURE" Key="obj_mure" ObjectKey="GAMEPLAY_KEEP" Name="Fish, Bugs, Butterflies" Category=""></Actor>
<Actor ID="ACTOR_EN_SW" Key="en_sw" ObjectKey="OBJECT_ST" Name="(Golden) Skulltula" Category=""></Actor>
<Actor ID="ACTOR_OBJECT_KANKYO" Key="object_kankyo" ObjectKey="GAMEPLAY_KEEP" Name="Environment Effects 2?" Category=""></Actor>
<Actor ID="ACTOR_EN_HORSE_LINK_CHILD" Key="en_horse_link_child" ObjectKey="OBJECT_HORSE_LINK_CHILD" Name="Child Epona (Gameplay)" Category=""></Actor>
<Actor ID="ACTOR_DOOR_ANA" Key="door_ana" ObjectKey="GAMEPLAY_FIELD_KEEP" Name="Grotto Hole" Category=""></Actor>
<Actor ID="ACTOR_EN_ENCOUNT1" Key="en_encount1" ObjectKey="GAMEPLAY_KEEP" Name="En_Encount1" Category=""></Actor>
<Actor ID="ACTOR_DEMO_TRE_LGT" Key="demo_tre_lgt" ObjectKey="OBJECT_BOX" Name="Treasure Chest Glow" Category=""></Actor>
<Actor ID="ACTOR_EN_ENCOUNT2" Key="en_encount2" ObjectKey="OBJECT_FUSEN" Name="Majora Balloon" Category=""></Actor>
<Actor ID="ACTOR_EN_FIRE_ROCK" Key="en_fire_rock" ObjectKey="OBJECT_EFC_STAR_FIELD" Name="Rock and Beam of Light [OoT]" Category=""></Actor>
<Actor ID="ACTOR_BG_CTOWER_ROT" Key="bg_ctower_rot" ObjectKey="OBJECT_CTOWER_ROT" Name="Clock Tower Helix Path" Category=""></Actor>
<Actor ID="ACTOR_MIR_RAY" Key="mir_ray" ObjectKey="OBJECT_MIR_RAY" Name="Mirror Shield Light Ray I [?]" Category=""></Actor>
<Actor ID="ACTOR_EN_SB" Key="en_sb" ObjectKey="OBJECT_SB" Name="Shellblade" Category=""></Actor>
<Actor ID="ACTOR_EN_BIGSLIME" Key="en_bigslime" ObjectKey="OBJECT_BIGSLIME" Name="Mad Jelly" Category=""></Actor>
<Actor ID="ACTOR_EN_KAREBABA" Key="en_karebaba" ObjectKey="OBJECT_DEKUBABA" Name="Deku Baba" Category=""></Actor>
<Actor ID="ACTOR_EN_IN" Key="en_in" ObjectKey="OBJECT_IN" Name="Gorman Bros." Category=""></Actor>
<Actor ID="ACTOR_EN_RU" Key="en_ru" ObjectKey="OBJECT_RU2" Name="Adult Ruto [OoT]" Category=""></Actor>
<Actor ID="ACTOR_EN_BOM_CHU" Key="en_bom_chu" ObjectKey="GAMEPLAY_KEEP" Name="Bombchu" Category=""></Actor>
<Actor ID="ACTOR_EN_HORSE_GAME_CHECK" Key="en_horse_game_check" ObjectKey="OBJECT_HORSE_GAME_CHECK" Name="En_Horse_Game_Check" Category=""></Actor>
<Actor ID="ACTOR_EN_RR" Key="en_rr" ObjectKey="OBJECT_RR" Name="Like Like" Category=""></Actor>
<Actor ID="ACTOR_EN_FR" Key="en_fr" ObjectKey="GAMEPLAY_KEEP" Name="En_Fr" Category=""></Actor>
<Actor ID="ACTOR_EN_FISHING" Key="en_fishing" ObjectKey="OBJECT_UNSET_0" Name="Fishing Pond Owner (JP 1.0 Only)" Category=""></Actor>
<Actor ID="ACTOR_OBJ_OSHIHIKI" Key="obj_oshihiki" ObjectKey="GAMEPLAY_DANGEON_KEEP" Name="Pushable Block" Category=""></Actor>
<Actor ID="ACTOR_EFF_DUST" Key="eff_dust" ObjectKey="GAMEPLAY_KEEP" Name="Spin Attack Charge Particles" Category=""></Actor>
<Actor ID="ACTOR_BG_UMAJUMP" Key="bg_umajump" ObjectKey="GAMEPLAY_KEEP" Name="Horse Jumping Fence" Category=""></Actor>
<Actor ID="ACTOR_ARROW_FIRE" Key="arrow_fire" ObjectKey="GAMEPLAY_KEEP" Name="Fire Arrow" Category=""></Actor>
<Actor ID="ACTOR_ARROW_ICE" Key="arrow_ice" ObjectKey="GAMEPLAY_KEEP" Name="Ice Arrow" Category=""></Actor>
<Actor ID="ACTOR_ARROW_LIGHT" Key="arrow_light" ObjectKey="GAMEPLAY_KEEP" Name="Light Arrow" Category=""></Actor>
<Actor ID="ACTOR_ITEM_ETCETERA" Key="item_etcetera" ObjectKey="GAMEPLAY_KEEP" Name="Item_Etcetera" Category=""></Actor>
<Actor ID="ACTOR_OBJ_KIBAKO" Key="obj_kibako" ObjectKey="GAMEPLAY_KEEP" Name="Small Wooden Crate" Category=""></Actor>
<Actor ID="ACTOR_OBJ_TSUBO" Key="obj_tsubo" ObjectKey="GAMEPLAY_KEEP" Name="Pot" Category=""></Actor>
<Actor ID="ACTOR_EN_IK" Key="en_ik" ObjectKey="OBJECT_IK" Name="Iron Knuckle" Category=""></Actor>
<Actor ID="ACTOR_DEMO_SHD" Key="demo_shd" ObjectKey="OBJECT_FWALL" Name="Demo_Shd" Category=""></Actor>
<Actor ID="ACTOR_EN_DNS" Key="en_dns" ObjectKey="OBJECT_DNS" Name="Deku Scrub Guard (Royal Chamber)" Category=""></Actor>
<Actor ID="ACTOR_ELF_MSG" Key="elf_msg" ObjectKey="GAMEPLAY_KEEP" Name="Elf_Msg" Category=""></Actor>
<Actor ID="ACTOR_EN_HONOTRAP" Key="en_honotrap" ObjectKey="GAMEPLAY_DANGEON_KEEP" Name="En_Honotrap" Category=""></Actor>
<Actor ID="ACTOR_EN_TUBO_TRAP" Key="en_tubo_trap" ObjectKey="GAMEPLAY_DANGEON_KEEP" Name="Flying Pot" Category=""></Actor>
<Actor ID="ACTOR_OBJ_ICE_POLY" Key="obj_ice_poly" ObjectKey="GAMEPLAY_KEEP" Name="Ice Sparkle Effect" Category=""></Actor>
<Actor ID="ACTOR_EN_FZ" Key="en_fz" ObjectKey="OBJECT_FZ" Name="Freezard" Category=""></Actor>
<Actor ID="ACTOR_EN_KUSA" Key="en_kusa" ObjectKey="GAMEPLAY_KEEP" Name="Cut-able Grass" Category=""></Actor>
<Actor ID="ACTOR_OBJ_BEAN" Key="obj_bean" ObjectKey="OBJECT_MAMENOKI" Name="Magic Bean Plant" Category=""></Actor>
<Actor ID="ACTOR_OBJ_BOMBIWA" Key="obj_bombiwa" ObjectKey="OBJECT_BOMBIWA" Name="Bombable Rock" Category=""></Actor>
<Actor ID="ACTOR_OBJ_SWITCH" Key="obj_switch" ObjectKey="GAMEPLAY_DANGEON_KEEP" Name="Dungeon Switches" Category=""></Actor>
<Actor ID="ACTOR_OBJ_LIFT" Key="obj_lift" ObjectKey="OBJECT_D_LIFT" Name="Dampé's House Elevator" Category=""></Actor>
<Actor ID="ACTOR_OBJ_HSBLOCK" Key="obj_hsblock" ObjectKey="OBJECT_D_HSBLOCK" Name="Stone Hookshot Pillar" Category=""></Actor>
<Actor ID="ACTOR_EN_OKARINA_TAG" Key="en_okarina_tag" ObjectKey="GAMEPLAY_KEEP" Name="Ocarina Song Spot" Category=""></Actor>
<Actor ID="ACTOR_EN_GOROIWA" Key="en_goroiwa" ObjectKey="OBJECT_GOROIWA" Name="Snowball and Rolling Boulder [?]" Category=""></Actor>
<Actor ID="ACTOR_EN_DAIKU" Key="en_daiku" ObjectKey="OBJECT_DAIKU" Name="Carpenter (Clock Town)" Category=""></Actor>
<Actor ID="ACTOR_EN_NWC" Key="en_nwc" ObjectKey="OBJECT_NWC" Name="Cucco Chick" Category=""></Actor>
<Actor ID="ACTOR_ITEM_INBOX" Key="item_inbox" ObjectKey="GAMEPLAY_KEEP" Name="Item_Inbox" Category=""></Actor>
<Actor ID="ACTOR_EN_GE1" Key="en_ge1" ObjectKey="OBJECT_GE1" Name="Pirate Lieutenant" Category=""></Actor>
<Actor ID="ACTOR_OBJ_BLOCKSTOP" Key="obj_blockstop" ObjectKey="GAMEPLAY_KEEP" Name="Obj_Blockstop" Category=""></Actor>
<Actor ID="ACTOR_EN_SDA" Key="en_sda" ObjectKey="GAMEPLAY_KEEP" Name="Dynamic Shadow (Glitchy)" Category=""></Actor>
<Actor ID="ACTOR_EN_CLEAR_TAG" Key="en_clear_tag" ObjectKey="GAMEPLAY_KEEP" Name="En_Clear_Tag" Category=""></Actor>
<Actor ID="ACTOR_EN_GM" Key="en_gm" ObjectKey="OBJECT_IN2" Name="Gorman" Category=""></Actor>
<Actor ID="ACTOR_EN_MS" Key="en_ms" ObjectKey="OBJECT_MS" Name="Magic Bean Seller" Category=""></Actor>
<Actor ID="ACTOR_EN_HS" Key="en_hs" ObjectKey="OBJECT_HS" Name="Grog" Category=""></Actor>
<Actor ID="ACTOR_BG_INGATE" Key="bg_ingate" ObjectKey="OBJECT_SICHITAI_OBJ" Name="Boat Cruise Canoe" Category=""></Actor>
<Actor ID="ACTOR_EN_KANBAN" Key="en_kanban" ObjectKey="OBJECT_KANBAN" Name="Square Signpost" Category=""></Actor>
<Actor ID="ACTOR_EN_ATTACK_NIW" Key="en_attack_niw" ObjectKey="OBJECT_NIW" Name="Attacking Cucco" Category=""></Actor>
<Actor ID="ACTOR_EN_MK" Key="en_mk" ObjectKey="OBJECT_MK" Name="Marine Scientist" Category=""></Actor>
<Actor ID="ACTOR_EN_OWL" Key="en_owl" ObjectKey="OBJECT_OWL" Name="Owl" Category=""></Actor>
<Actor ID="ACTOR_EN_ISHI" Key="en_ishi" ObjectKey="GAMEPLAY_KEEP" Name="Rock" Category=""></Actor>
<Actor ID="ACTOR_OBJ_HANA" Key="obj_hana" ObjectKey="OBJECT_HANA" Name="Orange Graveyard Flower" Category=""></Actor>
<Actor ID="ACTOR_OBJ_LIGHTSWITCH" Key="obj_lightswitch" ObjectKey="OBJECT_LIGHTSWITCH" Name="Sun Switch" Category=""></Actor>
<Actor ID="ACTOR_OBJ_MURE2" Key="obj_mure2" ObjectKey="GAMEPLAY_KEEP" Name="Grass and Rock Cluster" Category=""></Actor>
<Actor ID="ACTOR_EN_FU" Key="en_fu" ObjectKey="OBJECT_MU" Name="Honey and Darling" Category=""></Actor>
<Actor ID="ACTOR_EN_STREAM" Key="en_stream" ObjectKey="OBJECT_STREAM" Name="Water Spout" Category=""></Actor>
<Actor ID="ACTOR_EN_MM" Key="en_mm" ObjectKey="GAMEPLAY_KEEP" Name="Rock Sirloin" Category=""></Actor>
<Actor ID="ACTOR_EN_WEATHER_TAG" Key="en_weather_tag" ObjectKey="GAMEPLAY_KEEP" Name="En_Weather_Tag" Category=""></Actor>
<Actor ID="ACTOR_EN_ANI" Key="en_ani" ObjectKey="OBJECT_ANI" Name="Part-Timer" Category=""></Actor>
<Actor ID="ACTOR_EN_JS" Key="en_js" ObjectKey="OBJECT_OB" Name="Moon Child" Category=""></Actor>
<Actor ID="ACTOR_EN_OKARINA_EFFECT" Key="en_okarina_effect" ObjectKey="GAMEPLAY_KEEP" Name="Song of Storms Effect I [?]" Category=""></Actor>
<Actor ID="ACTOR_EN_MAG" Key="en_mag" ObjectKey="OBJECT_MAG" Name="Title Logo" Category=""></Actor>
<Actor ID="ACTOR_ELF_MSG2" Key="elf_msg2" ObjectKey="GAMEPLAY_KEEP" Name="Elf_Msg2" Category=""></Actor>
<Actor ID="ACTOR_BG_F40_SWLIFT" Key="bg_f40_swlift" ObjectKey="OBJECT_F40_OBJ" Name="Stone Tower Temple Platform [Early]" Category=""></Actor>
<Actor ID="ACTOR_EN_KAKASI" Key="en_kakasi" ObjectKey="OBJECT_KA" Name="Scarecrow" Category=""></Actor>
<Actor ID="ACTOR_OBJ_MAKEOSHIHIKI" Key="obj_makeoshihiki" ObjectKey="GAMEPLAY_KEEP" Name="Obj_Makeoshihiki" Category=""></Actor>
<Actor ID="ACTOR_OCEFF_SPOT" Key="oceff_spot" ObjectKey="GAMEPLAY_KEEP" Name="Sun's Song Effect" Category=""></Actor>
<Actor ID="ACTOR_EN_TORCH" Key="en_torch" ObjectKey="GAMEPLAY_KEEP" Name="Treasure Chest (Grotto)" Category=""></Actor>
<Actor ID="ACTOR_SHOT_SUN" Key="shot_sun" ObjectKey="GAMEPLAY_KEEP" Name="Shot_Sun" Category=""></Actor>
<Actor ID="ACTOR_OBJ_ROOMTIMER" Key="obj_roomtimer" ObjectKey="GAMEPLAY_KEEP" Name="Obj_Roomtimer" Category=""></Actor>
<Actor ID="ACTOR_EN_SSH" Key="en_ssh" ObjectKey="OBJECT_SSH" Name="Cursed Skulltula Man" Category=""></Actor>
<Actor ID="ACTOR_OCEFF_WIPE" Key="oceff_wipe" ObjectKey="GAMEPLAY_KEEP" Name="Song of Time Effect" Category=""></Actor>
<Actor ID="ACTOR_OCEFF_STORM" Key="oceff_storm" ObjectKey="GAMEPLAY_KEEP" Name="Song of Storms Effect II [?]" Category=""></Actor>
<Actor ID="ACTOR_OBJ_DEMO" Key="obj_demo" ObjectKey="GAMEPLAY_KEEP" Name="Cutscene Trigger" Category=""></Actor>
<Actor ID="ACTOR_EN_MINISLIME" Key="en_minislime" ObjectKey="OBJECT_BIGSLIME" Name="Jelly Droplets" Category=""></Actor>
<Actor ID="ACTOR_EN_NUTSBALL" Key="en_nutsball" ObjectKey="GAMEPLAY_KEEP" Name="Deku Nut Projectile" Category=""></Actor>
<Actor ID="ACTOR_OCEFF_WIPE2" Key="oceff_wipe2" ObjectKey="GAMEPLAY_KEEP" Name="Epona's Song Effect" Category=""></Actor>
<Actor ID="ACTOR_OCEFF_WIPE3" Key="oceff_wipe3" ObjectKey="GAMEPLAY_KEEP" Name="Saria's Song Effect" Category=""></Actor>
<Actor ID="ACTOR_EN_DG" Key="en_dg" ObjectKey="OBJECT_DOG" Name="Dog" Category=""></Actor>
<Actor ID="ACTOR_EN_SI" Key="en_si" ObjectKey="OBJECT_ST" Name="Gold Skulltula Token" Category=""></Actor>
<Actor ID="ACTOR_OBJ_COMB" Key="obj_comb" ObjectKey="OBJECT_COMB" Name="Beehive" Category=""></Actor>
<Actor ID="ACTOR_OBJ_KIBAKO2" Key="obj_kibako2" ObjectKey="OBJECT_KIBAKO2" Name="Wooden Crate" Category=""></Actor>
<Actor ID="ACTOR_EN_HS2" Key="en_hs2" ObjectKey="GAMEPLAY_KEEP" Name="En_Hs2" Category=""></Actor>
<Actor ID="ACTOR_OBJ_MURE3" Key="obj_mure3" ObjectKey="GAMEPLAY_KEEP" Name="Rupee Cluster" Category=""></Actor>
<Actor ID="ACTOR_EN_TG" Key="en_tg" ObjectKey="OBJECT_MU" Name="Honey and Darling (Cutscenes)" Category=""></Actor>
<Actor ID="ACTOR_EN_WF" Key="en_wf" ObjectKey="OBJECT_WF" Name="Wolfos" Category=""></Actor>
<Actor ID="ACTOR_EN_SKB" Key="en_skb" ObjectKey="OBJECT_SKB" Name="Stalchild" Category=""></Actor>
<Actor ID="ACTOR_EN_GS" Key="en_gs" ObjectKey="OBJECT_GS" Name="Gossip Stone" Category=""></Actor>
<Actor ID="ACTOR_OBJ_SOUND" Key="obj_sound" ObjectKey="GAMEPLAY_KEEP" Name="Sound Effects II" Category=""></Actor>
<Actor ID="ACTOR_EN_CROW" Key="en_crow" ObjectKey="OBJECT_CROW" Name="Guay" Category=""></Actor>
<Actor ID="ACTOR_EN_COW" Key="en_cow" ObjectKey="OBJECT_COW" Name="Cow" Category=""></Actor>
<Actor ID="ACTOR_OCEFF_WIPE4" Key="oceff_wipe4" ObjectKey="GAMEPLAY_KEEP" Name="Scarecrow's Song Effect" Category=""></Actor>
<Actor ID="ACTOR_EN_ZO" Key="en_zo" ObjectKey="OBJECT_ZO" Name="Zora [Early]" Category=""></Actor>
<Actor ID="ACTOR_OBJ_MAKEKINSUTA" Key="obj_makekinsuta" ObjectKey="GAMEPLAY_KEEP" Name="Obj_Makekinsuta" Category=""></Actor>
<Actor ID="ACTOR_EN_GE3" Key="en_ge3" ObjectKey="OBJECT_GELDB" Name="Aveil" Category=""></Actor>
<Actor ID="ACTOR_OBJ_HAMISHI" Key="obj_hamishi" ObjectKey="GAMEPLAY_FIELD_KEEP" Name="Bronze Boulder" Category=""></Actor>
<Actor ID="ACTOR_EN_ZL4" Key="en_zl4" ObjectKey="OBJECT_STK" Name="En_Zl4" Category=""></Actor>
<Actor ID="ACTOR_EN_MM2" Key="en_mm2" ObjectKey="GAMEPLAY_KEEP" Name="Postman's Letter to Himself" Category=""></Actor>
<Actor ID="ACTOR_DOOR_SPIRAL" Key="door_spiral" ObjectKey="GAMEPLAY_KEEP" Name="Spiral Staircase" Category=""></Actor>
<Actor ID="ACTOR_OBJ_PZLBLOCK" Key="obj_pzlblock" ObjectKey="GAMEPLAY_KEEP" Name="Majora Pushblock" Category=""></Actor>
<Actor ID="ACTOR_OBJ_TOGE" Key="obj_toge" ObjectKey="OBJECT_TRAP" Name="Blade Trap" Category=""></Actor>
<Actor ID="ACTOR_OBJ_ARMOS" Key="obj_armos" ObjectKey="OBJECT_AM" Name="Armos Statue" Category=""></Actor>
<Actor ID="ACTOR_OBJ_BOYO" Key="obj_boyo" ObjectKey="OBJECT_BOYO" Name="Green Bumper" Category=""></Actor>
<Actor ID="ACTOR_EN_GRASSHOPPER" Key="en_grasshopper" ObjectKey="OBJECT_GRASSHOPPER" Name="Dragonfly" Category=""></Actor>
<Actor ID="ACTOR_OBJ_GRASS" Key="obj_grass" ObjectKey="GAMEPLAY_FIELD_KEEP" Name="Obj_Grass" Category=""></Actor>
<Actor ID="ACTOR_OBJ_GRASS_CARRY" Key="obj_grass_carry" ObjectKey="GAMEPLAY_FIELD_KEEP" Name="Obj_Grass_Carry" Category=""></Actor>
<Actor ID="ACTOR_OBJ_GRASS_UNIT" Key="obj_grass_unit" ObjectKey="GAMEPLAY_FIELD_KEEP" Name="Grass Cluster" Category=""></Actor>
<Actor ID="ACTOR_BG_FIRE_WALL" Key="bg_fire_wall" ObjectKey="OBJECT_FWALL" Name="Proximity-Activated Firewall" Category=""></Actor>
<Actor ID="ACTOR_EN_BU" Key="en_bu" ObjectKey="GAMEPLAY_KEEP" Name="En_Bu" Category=""></Actor>
<Actor ID="ACTOR_EN_ENCOUNT3" Key="en_encount3" ObjectKey="OBJECT_BIG_FWALL" Name="Circle of Light [?]" Category=""></Actor>
<Actor ID="ACTOR_EN_JSO" Key="en_jso" ObjectKey="OBJECT_JSO" Name="Garo Master I [?]" Category=""></Actor>
<Actor ID="ACTOR_OBJ_CHIKUWA" Key="obj_chikuwa" ObjectKey="OBJECT_D_LIFT" Name="Falling Block Row" Category=""></Actor>
<Actor ID="ACTOR_EN_KNIGHT" Key="en_knight" ObjectKey="OBJECT_KNIGHT" Name="Igos du Ikana and Henchmen [?]" Category=""></Actor>
<Actor ID="ACTOR_EN_WARP_TAG" Key="en_warp_tag" ObjectKey="GAMEPLAY_KEEP" Name="Warp to Trial Entrance" Category=""></Actor>
<Actor ID="ACTOR_EN_AOB_01" Key="en_aob_01" ObjectKey="OBJECT_AOB" Name="Mamamu Yan" Category=""></Actor>
<Actor ID="ACTOR_EN_BOJ_01" Key="en_boj_01" ObjectKey="GAMEPLAY_KEEP" Name="En_Boj_01" Category=""></Actor>
<Actor ID="ACTOR_EN_BOJ_02" Key="en_boj_02" ObjectKey="GAMEPLAY_KEEP" Name="En_Boj_02" Category=""></Actor>
<Actor ID="ACTOR_EN_BOJ_03" Key="en_boj_03" ObjectKey="GAMEPLAY_KEEP" Name="En_Boj_03" Category=""></Actor>
<Actor ID="ACTOR_EN_ENCOUNT4" Key="en_encount4" ObjectKey="GAMEPLAY_KEEP" Name="En_Encount4" Category=""></Actor>
<Actor ID="ACTOR_EN_BOM_BOWL_MAN" Key="en_bom_bowl_man" ObjectKey="OBJECT_CS" Name="Bomber I [?]" Category=""></Actor>
<Actor ID="ACTOR_EN_SYATEKI_MAN" Key="en_syateki_man" ObjectKey="OBJECT_SHN" Name="Shooting Gallery Proprietors [?]" Category=""></Actor>
<Actor ID="ACTOR_BG_ICICLE" Key="bg_icicle" ObjectKey="OBJECT_ICICLE" Name="Icicle" Category=""></Actor>
<Actor ID="ACTOR_EN_SYATEKI_CROW" Key="en_syateki_crow" ObjectKey="OBJECT_CROW" Name="Guay (Shooting Gallery)" Category=""></Actor>
<Actor ID="ACTOR_EN_BOJ_04" Key="en_boj_04" ObjectKey="GAMEPLAY_KEEP" Name="En_Boj_04" Category=""></Actor>
<Actor ID="ACTOR_EN_CNE_01" Key="en_cne_01" ObjectKey="GAMEPLAY_KEEP" Name="Thin Woman in Blue Dress [OoT]" Category=""></Actor>
<Actor ID="ACTOR_EN_BBA_01" Key="en_bba_01" ObjectKey="GAMEPLAY_KEEP" Name="Bomb Shop Proprietor's Mother [Early]" Category=""></Actor>
<Actor ID="ACTOR_EN_BJI_01" Key="en_bji_01" ObjectKey="OBJECT_BJI" Name="Shikashi" Category=""></Actor>
<Actor ID="ACTOR_BG_SPDWEB" Key="bg_spdweb" ObjectKey="OBJECT_SPDWEB" Name="Spiderweb" Category=""></Actor>
<Actor ID="ACTOR_EN_MT_TAG" Key="en_mt_tag" ObjectKey="GAMEPLAY_KEEP" Name="En_Mt_tag" Category=""></Actor>
<Actor ID="ACTOR_BOSS_01" Key="boss_01" ObjectKey="OBJECT_BOSS01" Name="Odolwa" Category=""></Actor>
<Actor ID="ACTOR_BOSS_02" Key="boss_02" ObjectKey="OBJECT_BOSS02" Name="Twinmold" Category=""></Actor>
<Actor ID="ACTOR_BOSS_03" Key="boss_03" ObjectKey="OBJECT_BOSS03" Name="Gyorg" Category=""></Actor>
<Actor ID="ACTOR_BOSS_04" Key="boss_04" ObjectKey="OBJECT_BOSS04" Name="Wart" Category=""></Actor>
<Actor ID="ACTOR_BOSS_05" Key="boss_05" ObjectKey="OBJECT_BOSS05" Name="Bio Deku Baba" Category=""></Actor>
<Actor ID="ACTOR_BOSS_06" Key="boss_06" ObjectKey="OBJECT_KNIGHT" Name="Igos du Ikana [?]" Category=""></Actor>
<Actor ID="ACTOR_BOSS_07" Key="boss_07" ObjectKey="OBJECT_BOSS07" Name="Majora" Category=""></Actor>
<Actor ID="ACTOR_BG_DY_YOSEIZO" Key="bg_dy_yoseizo" ObjectKey="OBJECT_DY_OBJ" Name="Great Fairy" Category=""></Actor>
<Actor ID="ACTOR_EN_BOJ_05" Key="en_boj_05" ObjectKey="GAMEPLAY_KEEP" Name="En_Boj_05" Category=""></Actor>
<Actor ID="ACTOR_EN_SOB1" Key="en_sob1" ObjectKey="GAMEPLAY_KEEP" Name="En_Sob1" Category=""></Actor>
<Actor ID="ACTOR_EN_GO" Key="en_go" ObjectKey="OBJECT_OF1D_MAP" Name="Goron" Category=""></Actor>
<Actor ID="ACTOR_EN_RAF" Key="en_raf" ObjectKey="OBJECT_RAF" Name="Carnivorous Lilypad" Category=""></Actor>
<Actor ID="ACTOR_OBJ_FUNEN" Key="obj_funen" ObjectKey="OBJECT_FUNEN" Name="Stone Tower Smoke Plume [Early]" Category=""></Actor>
<Actor ID="ACTOR_OBJ_RAILLIFT" Key="obj_raillift" ObjectKey="OBJECT_RAILLIFT" Name="Elevator (Deku Palace and Woodfall Temple) [?]" Category=""></Actor>
<Actor ID="ACTOR_BG_NUMA_HANA" Key="bg_numa_hana" ObjectKey="OBJECT_NUMA_OBJ" Name="Wooden Flower" Category=""></Actor>
<Actor ID="ACTOR_OBJ_FLOWERPOT" Key="obj_flowerpot" ObjectKey="OBJECT_FLOWERPOT" Name="Potted Plant" Category=""></Actor>
<Actor ID="ACTOR_OBJ_SPINYROLL" Key="obj_spinyroll" ObjectKey="OBJECT_SPINYROLL" Name="Spiked Log (Horizontal)" Category=""></Actor>
<Actor ID="ACTOR_DM_HINA" Key="dm_hina" ObjectKey="OBJECT_BSMASK" Name="Boss Remains (Cutscenes)" Category=""></Actor>
<Actor ID="ACTOR_EN_SYATEKI_WF" Key="en_syateki_wf" ObjectKey="OBJECT_WF" Name="Wolfos (Shooting Gallery)" Category=""></Actor>
<Actor ID="ACTOR_OBJ_SKATEBLOCK" Key="obj_skateblock" ObjectKey="GAMEPLAY_DANGEON_KEEP" Name="Ice Pushblock" Category=""></Actor>
<Actor ID="ACTOR_OBJ_ICEBLOCK" Key="obj_iceblock" ObjectKey="OBJECT_ICE_BLOCK" Name="Frozen Enemy Ice Block" Category=""></Actor>
<Actor ID="ACTOR_EN_BIGPAMET" Key="en_bigpamet" ObjectKey="OBJECT_TL" Name="Snapper (Mini-Boss)" Category=""></Actor>
<Actor ID="ACTOR_EN_SYATEKI_DEKUNUTS" Key="en_syateki_dekunuts" ObjectKey="OBJECT_DEKUNUTS" Name="Mad Scrub (Shooting Gallery)" Category=""></Actor>
<Actor ID="ACTOR_ELF_MSG3" Key="elf_msg3" ObjectKey="GAMEPLAY_KEEP" Name="Elf_Msg3" Category=""></Actor>
<Actor ID="ACTOR_EN_FG" Key="en_fg" ObjectKey="OBJECT_FR" Name="Frog II [?]" Category=""></Actor>
<Actor ID="ACTOR_DM_RAVINE" Key="dm_ravine" ObjectKey="OBJECT_KEIKOKU_DEMO" Name="Tree Trunk" Category=""></Actor>
<Actor ID="ACTOR_DM_SA" Key="dm_sa" ObjectKey="OBJECT_STK" Name="Dm_Sa" Category=""></Actor>
<Actor ID="ACTOR_EN_SLIME" Key="en_slime" ObjectKey="OBJECT_SLIME" Name="Chuchu" Category=""></Actor>
<Actor ID="ACTOR_EN_PR" Key="en_pr" ObjectKey="OBJECT_PR" Name="Desbreko" Category=""></Actor>
<Actor ID="ACTOR_OBJ_TOUDAI" Key="obj_toudai" ObjectKey="OBJECT_F53_OBJ" Name="Clock Tower Spotlight" Category=""></Actor>
<Actor ID="ACTOR_OBJ_ENTOTU" Key="obj_entotu" ObjectKey="OBJECT_F53_OBJ" Name="Clock Town 2D Chimney Backdrop" Category=""></Actor>
<Actor ID="ACTOR_OBJ_BELL" Key="obj_bell" ObjectKey="OBJECT_F52_OBJ" Name="Stock Pot Inn Bell" Category=""></Actor>
<Actor ID="ACTOR_EN_SYATEKI_OKUTA" Key="en_syateki_okuta" ObjectKey="OBJECT_OKUTA" Name="Octorok (Shooting Gallery)" Category=""></Actor>
<Actor ID="ACTOR_OBJ_SHUTTER" Key="obj_shutter" ObjectKey="OBJECT_F53_OBJ" Name="Clock Town Bank Shutter" Category=""></Actor>
<Actor ID="ACTOR_DM_ZL" Key="dm_zl" ObjectKey="OBJECT_ZL4" Name="Child Zelda (Cutscenes)" Category=""></Actor>
<Actor ID="ACTOR_EN_ELFGRP" Key="en_elfgrp" ObjectKey="GAMEPLAY_KEEP" Name="Group of Stray Fairies" Category=""></Actor>
<Actor ID="ACTOR_DM_TSG" Key="dm_tsg" ObjectKey="OBJECT_OPEN_OBJ" Name="Deku Door/Spotlights" Category=""></Actor>
<Actor ID="ACTOR_EN_BAGUO" Key="en_baguo" ObjectKey="OBJECT_GMO" Name="Nejiron" Category=""></Actor>
<Actor ID="ACTOR_OBJ_VSPINYROLL" Key="obj_vspinyroll" ObjectKey="OBJECT_SPINYROLL" Name="Spiked Log (Vertical)" Category=""></Actor>
<Actor ID="ACTOR_OBJ_SMORK" Key="obj_smork" ObjectKey="OBJECT_F53_OBJ" Name="Romani Ranch Chimney Smoke" Category=""></Actor>
<Actor ID="ACTOR_EN_TEST2" Key="en_test2" ObjectKey="GAMEPLAY_KEEP" Name="En_Test2" Category=""></Actor>
<Actor ID="ACTOR_EN_TEST3" Key="en_test3" ObjectKey="OBJECT_TEST3" Name="Kafei" Category=""></Actor>
<Actor ID="ACTOR_EN_TEST4" Key="en_test4" ObjectKey="GAMEPLAY_KEEP" Name="Three-Day Timer" Category=""></Actor>
<Actor ID="ACTOR_EN_BAT" Key="en_bat" ObjectKey="OBJECT_BAT" Name="Bad Bat" Category=""></Actor>
<Actor ID="ACTOR_EN_SEKIHI" Key="en_sekihi" ObjectKey="GAMEPLAY_KEEP" Name="Mikau's Grave and Song Pedestals [Early]" Category=""></Actor>
<Actor ID="ACTOR_EN_WIZ" Key="en_wiz" ObjectKey="OBJECT_WIZ" Name="Wizzrobe" Category=""></Actor>
<Actor ID="ACTOR_EN_WIZ_BROCK" Key="en_wiz_brock" ObjectKey="OBJECT_WIZ" Name="Wizzrobe Warp Platform" Category=""></Actor>
<Actor ID="ACTOR_EN_WIZ_FIRE" Key="en_wiz_fire" ObjectKey="OBJECT_WIZ" Name="Wizzrobe Fire Attack" Category=""></Actor>
<Actor ID="ACTOR_EFF_CHANGE" Key="eff_change" ObjectKey="GAMEPLAY_KEEP" Name="Camera Refocuser" Category=""></Actor>
<Actor ID="ACTOR_DM_STATUE" Key="dm_statue" ObjectKey="OBJECT_SMTOWER" Name="Elegy Statue Light Beam [?]" Category=""></Actor>
<Actor ID="ACTOR_OBJ_FIRESHIELD" Key="obj_fireshield" ObjectKey="GAMEPLAY_KEEP" Name="Circle of Flames" Category=""></Actor>
<Actor ID="ACTOR_BG_LADDER" Key="bg_ladder" ObjectKey="OBJECT_LADDER" Name="Ladder" Category=""></Actor>
<Actor ID="ACTOR_EN_MKK" Key="en_mkk" ObjectKey="OBJECT_MKK" Name="Black and White Boes" Category=""></Actor>
<Actor ID="ACTOR_DEMO_GETITEM" Key="demo_getitem" ObjectKey="GAMEPLAY_KEEP" Name="Great Fairy's Mask and Great Fairy's Sword" Category=""></Actor>
<Actor ID="ACTOR_EN_DNB" Key="en_dnb" ObjectKey="OBJECT_HANAREYAMA_OBJ" Name="En_Dnb" Category=""></Actor>
<Actor ID="ACTOR_EN_DNH" Key="en_dnh" ObjectKey="OBJECT_TRO" Name="Boat Cruise Target Spot" Category=""></Actor>
<Actor ID="ACTOR_EN_DNK" Key="en_dnk" ObjectKey="GAMEPLAY_KEEP" Name="Mad Scrubs (Cutscenes)" Category=""></Actor>
<Actor ID="ACTOR_EN_DNQ" Key="en_dnq" ObjectKey="OBJECT_DNO" Name="Deku King" Category=""></Actor>
<Actor ID="ACTOR_BG_KEIKOKU_SAKU" Key="bg_keikoku_saku" ObjectKey="OBJECT_KEIKOKU_OBJ" Name="Spiked Iron Fence" Category=""></Actor>
<Actor ID="ACTOR_OBJ_HUGEBOMBIWA" Key="obj_hugebombiwa" ObjectKey="OBJECT_BOMBIWA" Name="Powder Keg Boulder" Category=""></Actor>
<Actor ID="ACTOR_EN_FIREFLY2" Key="en_firefly2" ObjectKey="OBJECT_FIREFLY" Name="En_Firefly2" Category=""></Actor>
<Actor ID="ACTOR_EN_RAT" Key="en_rat" ObjectKey="OBJECT_RAT" Name="Real Bombchu" Category=""></Actor>
<Actor ID="ACTOR_EN_WATER_EFFECT" Key="en_water_effect" ObjectKey="OBJECT_WATER_EFFECT" Name="Dripping Water" Category=""></Actor>
<Actor ID="ACTOR_EN_KUSA2" Key="en_kusa2" ObjectKey="GAMEPLAY_FIELD_KEEP" Name="Keaton Grass Cluster" Category=""></Actor>
<Actor ID="ACTOR_BG_SPOUT_FIRE" Key="bg_spout_fire" ObjectKey="OBJECT_FWALL" Name="Proximity-Activated Firewall" Category=""></Actor>
<Actor ID="ACTOR_BG_DBLUE_MOVEBG" Key="bg_dblue_movebg" ObjectKey="OBJECT_DBLUE_OBJECT" Name="Great Bay Temple Gears" Category=""></Actor>
<Actor ID="ACTOR_EN_DY_EXTRA" Key="en_dy_extra" ObjectKey="OBJECT_DY_OBJ" Name="Great Fairy Healing Beam" Category=""></Actor>
<Actor ID="ACTOR_EN_BAL" Key="en_bal" ObjectKey="OBJECT_BAL" Name="Tingle (Gameplay)" Category=""></Actor>
<Actor ID="ACTOR_EN_GINKO_MAN" Key="en_ginko_man" ObjectKey="OBJECT_BOJ" Name="Bank Teller, Sakon, Twin Jugglers" Category=""></Actor>
<Actor ID="ACTOR_EN_WARP_UZU" Key="en_warp_uzu" ObjectKey="OBJECT_WARP_UZU" Name="Pirates' Fortress Telescope" Category=""></Actor>
<Actor ID="ACTOR_OBJ_DRIFTICE" Key="obj_driftice" ObjectKey="OBJECT_DRIFTICE" Name="Drifting Ice Platform" Category=""></Actor>
<Actor ID="ACTOR_EN_LOOK_NUTS" Key="en_look_nuts" ObjectKey="OBJECT_DNK" Name="Deku Scrub Guard (Palace Gardens)" Category=""></Actor>
<Actor ID="ACTOR_EN_MUSHI2" Key="en_mushi2" ObjectKey="GAMEPLAY_KEEP" Name="En_Mushi2" Category=""></Actor>
<Actor ID="ACTOR_EN_FALL" Key="en_fall" ObjectKey="GAMEPLAY_KEEP" Name="The Moon" Category=""></Actor>
<Actor ID="ACTOR_EN_MM3" Key="en_mm3" ObjectKey="OBJECT_MM" Name="Postman (Counting Game)" Category=""></Actor>
<Actor ID="ACTOR_BG_CRACE_MOVEBG" Key="bg_crace_movebg" ObjectKey="OBJECT_CRACE_OBJECT" Name="Deku Shrine Door" Category=""></Actor>
<Actor ID="ACTOR_EN_DNO" Key="en_dno" ObjectKey="OBJECT_DNJ" Name="Deku Butler" Category=""></Actor>
<Actor ID="ACTOR_EN_PR2" Key="en_pr2" ObjectKey="OBJECT_PR" Name="Skullfish" Category=""></Actor>
<Actor ID="ACTOR_EN_PRZ" Key="en_prz" ObjectKey="OBJECT_PR" Name="Skullfish - Defeated" Category=""></Actor>
<Actor ID="ACTOR_EN_JSO2" Key="en_jso2" ObjectKey="OBJECT_JSO" Name="Garo Master II [?]" Category=""></Actor>
<Actor ID="ACTOR_OBJ_ETCETERA" Key="obj_etcetera" ObjectKey="GAMEPLAY_KEEP" Name="Deku Flower" Category=""></Actor>
<Actor ID="ACTOR_EN_EGOL" Key="en_egol" ObjectKey="OBJECT_EG" Name="Eyegore" Category=""></Actor>
<Actor ID="ACTOR_OBJ_MINE" Key="obj_mine" ObjectKey="OBJECT_NY" Name="Spiked Metal Mine" Category=""></Actor>
<Actor ID="ACTOR_OBJ_PURIFY" Key="obj_purify" ObjectKey="GAMEPLAY_KEEP" Name="Poisoned/Purified Water Elements" Category=""></Actor>
<Actor ID="ACTOR_EN_TRU" Key="en_tru" ObjectKey="OBJECT_TRU" Name="Koume (Gameplay) [?]" Category=""></Actor>
<Actor ID="ACTOR_EN_TRT" Key="en_trt" ObjectKey="OBJECT_TRT" Name="Kotake (No Broom) [?]" Category=""></Actor>
<Actor ID="ACTOR_EN_TEST5" Key="en_test5" ObjectKey="GAMEPLAY_KEEP" Name="Spring Water" Category=""></Actor>
<Actor ID="ACTOR_EN_TEST6" Key="en_test6" ObjectKey="GAMEPLAY_KEEP" Name="Song of Time Cutscene Effects" Category=""></Actor>
<Actor ID="ACTOR_EN_AZ" Key="en_az" ObjectKey="OBJECT_AZ" Name="Beaver Bros." Category=""></Actor>
<Actor ID="ACTOR_EN_ESTONE" Key="en_estone" ObjectKey="OBJECT_EG" Name="Eyegore Rubble" Category=""></Actor>
<Actor ID="ACTOR_BG_HAKUGIN_POST" Key="bg_hakugin_post" ObjectKey="OBJECT_HAKUGIN_OBJ" Name="Snowhead Temple Central Pillar" Category=""></Actor>
<Actor ID="ACTOR_DM_OPSTAGE" Key="dm_opstage" ObjectKey="OBJECT_KEIKOKU_DEMO" Name="Opening Cutscene Objects" Category=""></Actor>
<Actor ID="ACTOR_DM_STK" Key="dm_stk" ObjectKey="OBJECT_STK" Name="Skull Kid" Category=""></Actor>
<Actor ID="ACTOR_DM_CHAR00" Key="dm_char00" ObjectKey="OBJECT_DELF" Name="Tatl and Tael (Cutscenes) II [?]" Category=""></Actor>
<Actor ID="ACTOR_DM_CHAR01" Key="dm_char01" ObjectKey="OBJECT_MTORIDE" Name="Woodfall Temple Rises Cutscene Objects" Category=""></Actor>
<Actor ID="ACTOR_DM_CHAR02" Key="dm_char02" ObjectKey="OBJECT_STK2" Name="Clock Tower Roof Cutscene - OoT and Majora's Mask" Category=""></Actor>
<Actor ID="ACTOR_DM_CHAR03" Key="dm_char03" ObjectKey="OBJECT_OSN" Name="Happy Mask Salesman (Cutscenes)" Category=""></Actor>
<Actor ID="ACTOR_DM_CHAR04" Key="dm_char04" ObjectKey="GAMEPLAY_KEEP" Name="Tatl and Tael (Cutscenes) I [?]" Category=""></Actor>
<Actor ID="ACTOR_DM_CHAR05" Key="dm_char05" ObjectKey="OBJECT_DMASK" Name="Masks (Cutscenes)" Category=""></Actor>
<Actor ID="ACTOR_DM_CHAR06" Key="dm_char06" ObjectKey="OBJECT_YUKIYAMA" Name="Mountain Village Cutscene Objects [?]" Category=""></Actor>
<Actor ID="ACTOR_DM_CHAR07" Key="dm_char07" ObjectKey="OBJECT_MILKBAR" Name="Milk Bar Stage (Cutscenes)" Category=""></Actor>
<Actor ID="ACTOR_DM_CHAR08" Key="dm_char08" ObjectKey="OBJECT_KAMEJIMA" Name="Turtle (Cutscenes) [?]" Category=""></Actor>
<Actor ID="ACTOR_DM_CHAR09" Key="dm_char09" ObjectKey="OBJECT_BEE" Name="Giant Bee (Cutscenes)" Category=""></Actor>
<Actor ID="ACTOR_OBJ_TOKEIDAI" Key="obj_tokeidai" ObjectKey="OBJECT_OBJ_TOKEIDAI" Name="Clock Tower and Light Beam" Category=""></Actor>
<Actor ID="ACTOR_EN_MNK" Key="en_mnk" ObjectKey="OBJECT_MNK" Name="Monkey" Category=""></Actor>
<Actor ID="ACTOR_EN_EGBLOCK" Key="en_egblock" ObjectKey="OBJECT_EG" Name="Eyegore Block" Category=""></Actor>
<Actor ID="ACTOR_EN_GUARD_NUTS" Key="en_guard_nuts" ObjectKey="OBJECT_DNK" Name="Deku Scrub Guard (Palace Entrance) [?]" Category=""></Actor>
<Actor ID="ACTOR_BG_HAKUGIN_BOMBWALL" Key="bg_hakugin_bombwall" ObjectKey="OBJECT_HAKUGIN_OBJ" Name="Snowhead Temple Bombable Wall" Category=""></Actor>
<Actor ID="ACTOR_OBJ_TOKEI_TOBIRA" Key="obj_tokei_tobira" ObjectKey="OBJECT_TOKEI_TOBIRA" Name="Clock Tower Doors" Category=""></Actor>
<Actor ID="ACTOR_BG_HAKUGIN_ELVPOLE" Key="bg_hakugin_elvpole" ObjectKey="OBJECT_HAKUGIN_OBJ" Name="Snowhead Temple Punchable Pillar Inserts" Category=""></Actor>
<Actor ID="ACTOR_EN_MA4" Key="en_ma4" ObjectKey="OBJECT_MA1" Name="Romani I [?]" Category=""></Actor>
<Actor ID="ACTOR_EN_TWIG" Key="en_twig" ObjectKey="OBJECT_TWIG" Name="Beaver Race Ring" Category=""></Actor>
<Actor ID="ACTOR_EN_PO_FUSEN" Key="en_po_fusen" ObjectKey="OBJECT_PO_FUSEN" Name="Poe Balloon" Category=""></Actor>
<Actor ID="ACTOR_EN_DOOR_ETC" Key="en_door_etc" ObjectKey="GAMEPLAY_KEEP" Name="En_Door_Etc" Category=""></Actor>
<Actor ID="ACTOR_EN_BIGOKUTA" Key="en_bigokuta" ObjectKey="OBJECT_BIGOKUTA" Name="Big Octo" Category=""></Actor>
<Actor ID="ACTOR_BG_ICEFLOE" Key="bg_icefloe" ObjectKey="OBJECT_ICEFLOE" Name="Ice Arrow Platform" Category=""></Actor>
<Actor ID="ACTOR_OBJ_OCARINALIFT" Key="obj_ocarinalift" ObjectKey="OBJECT_RAILLIFT" Name="Triforce Elevator" Category=""></Actor>
<Actor ID="ACTOR_EN_TIME_TAG" Key="en_time_tag" ObjectKey="GAMEPLAY_KEEP" Name="En_Time_Tag" Category=""></Actor>
<Actor ID="ACTOR_BG_OPEN_SHUTTER" Key="bg_open_shutter" ObjectKey="OBJECT_OPEN_OBJ" Name="Deku Emblem Door" Category=""></Actor>
<Actor ID="ACTOR_BG_OPEN_SPOT" Key="bg_open_spot" ObjectKey="OBJECT_OPEN_OBJ" Name="Skull Kid Spotlights" Category=""></Actor>
<Actor ID="ACTOR_BG_FU_KAITEN" Key="bg_fu_kaiten" ObjectKey="OBJECT_FU_KAITEN" Name="Honey and Darling's Shop Rotating Platform" Category=""></Actor>
<Actor ID="ACTOR_OBJ_AQUA" Key="obj_aqua" ObjectKey="GAMEPLAY_KEEP" Name="Poured Water" Category=""></Actor>
<Actor ID="ACTOR_EN_ELFORG" Key="en_elforg" ObjectKey="GAMEPLAY_KEEP" Name="Stray Fairy" Category=""></Actor>
<Actor ID="ACTOR_EN_ELFBUB" Key="en_elfbub" ObjectKey="OBJECT_BUBBLE" Name="Stray Fairy Bubble" Category=""></Actor>
<Actor ID="ACTOR_EN_FU_MATO" Key="en_fu_mato" ObjectKey="OBJECT_FU_MATO" Name="Honey and Darling's Shop Target" Category=""></Actor>
<Actor ID="ACTOR_EN_FU_KAGO" Key="en_fu_kago" ObjectKey="OBJECT_FU_MATO" Name="Honey and Darling's Shop Basket" Category=""></Actor>
<Actor ID="ACTOR_EN_OSN" Key="en_osn" ObjectKey="OBJECT_OSN" Name="Happy Mask Salesman (Gameplay)" Category=""></Actor>
<Actor ID="ACTOR_BG_CTOWER_GEAR" Key="bg_ctower_gear" ObjectKey="OBJECT_CTOWER_ROT" Name="Clock Tower Gear" Category=""></Actor>
<Actor ID="ACTOR_EN_TRT2" Key="en_trt2" ObjectKey="OBJECT_TRT" Name="Kotake (Broom) [?]" Category=""></Actor>
<Actor ID="ACTOR_OBJ_TOKEI_STEP" Key="obj_tokei_step" ObjectKey="OBJECT_TOKEI_STEP" Name="Clock Tower Roof Door" Category=""></Actor>
<Actor ID="ACTOR_BG_LOTUS" Key="bg_lotus" ObjectKey="OBJECT_LOTUS" Name="Lilypad" Category=""></Actor>
<Actor ID="ACTOR_EN_KAME" Key="en_kame" ObjectKey="OBJECT_TL" Name="Snapper" Category=""></Actor>
<Actor ID="ACTOR_OBJ_TAKARAYA_WALL" Key="obj_takaraya_wall" ObjectKey="OBJECT_TAKARAYA_OBJECTS" Name="Treasure Chest Game Proximity-Activated Wall" Category=""></Actor>
<Actor ID="ACTOR_BG_FU_MIZU" Key="bg_fu_mizu" ObjectKey="OBJECT_FU_KAITEN" Name="Honey and Darling's Shop Moat" Category=""></Actor>
<Actor ID="ACTOR_EN_SELLNUTS" Key="en_sellnuts" ObjectKey="OBJECT_DNT" Name="Business Scrub (Flying) [?]" Category=""></Actor>
<Actor ID="ACTOR_BG_DKJAIL_IVY" Key="bg_dkjail_ivy" ObjectKey="OBJECT_DKJAIL_OBJ" Name="Woodfall Prison Ivy" Category=""></Actor>
<Actor ID="ACTOR_OBJ_VISIBLOCK" Key="obj_visiblock" ObjectKey="OBJECT_VISIBLOCK" Name="Lens of Truth Platform" Category=""></Actor>
<Actor ID="ACTOR_EN_TAKARAYA" Key="en_takaraya" ObjectKey="OBJECT_BG" Name="Treasure Chest Game Employee" Category=""></Actor>
<Actor ID="ACTOR_EN_TSN" Key="en_tsn" ObjectKey="OBJECT_TSN" Name="Fisherman (Great Bay)" Category=""></Actor>
<Actor ID="ACTOR_EN_DS2N" Key="en_ds2n" ObjectKey="OBJECT_DS2N" Name="Potion Shop Proprietor (Updated) [OoT]" Category=""></Actor>
<Actor ID="ACTOR_EN_FSN" Key="en_fsn" ObjectKey="OBJECT_FSN" Name="Curiosity Shop Proprietor" Category=""></Actor>
<Actor ID="ACTOR_EN_SHN" Key="en_shn" ObjectKey="OBJECT_SHN" Name="Swamp Tourist Center Guide" Category=""></Actor>
<Actor ID="ACTOR_EN_STOP_HEISHI" Key="en_stop_heishi" ObjectKey="OBJECT_SDN" Name="Soldier (Gate Guard)" Category=""></Actor>
<Actor ID="ACTOR_OBJ_BIGICICLE" Key="obj_bigicicle" ObjectKey="OBJECT_BIGICICLE" Name="Ice Block" Category=""></Actor>
<Actor ID="ACTOR_EN_LIFT_NUTS" Key="en_lift_nuts" ObjectKey="OBJECT_DNT" Name="Deku Scrub Playground Employee" Category=""></Actor>
<Actor ID="ACTOR_EN_TK" Key="en_tk" ObjectKey="OBJECT_TK" Name="Dampé" Category=""></Actor>
<Actor ID="ACTOR_BG_MARKET_STEP" Key="bg_market_step" ObjectKey="OBJECT_MARKET_OBJ" Name="West Clock Town Steps" Category=""></Actor>
<Actor ID="ACTOR_OBJ_LUPYGAMELIFT" Key="obj_lupygamelift" ObjectKey="OBJECT_RAILLIFT" Name="Deku Scrub Playground Elevator" Category=""></Actor>
<Actor ID="ACTOR_EN_TEST7" Key="en_test7" ObjectKey="GAMEPLAY_KEEP" Name="Song of Soaring Cutscene Activator" Category=""></Actor>
<Actor ID="ACTOR_OBJ_LIGHTBLOCK" Key="obj_lightblock" ObjectKey="OBJECT_LIGHTBLOCK" Name="Dissolvable Light Block" Category=""></Actor>
<Actor ID="ACTOR_MIR_RAY2" Key="mir_ray2" ObjectKey="OBJECT_MIR_RAY" Name="Mirror Shield Reflectable Spotlight [?]" Category=""></Actor>
<Actor ID="ACTOR_EN_WDHAND" Key="en_wdhand" ObjectKey="OBJECT_WDHAND" Name="Dexihand" Category=""></Actor>
<Actor ID="ACTOR_EN_GAMELUPY" Key="en_gamelupy" ObjectKey="GAMEPLAY_KEEP" Name="Deku Scrub Playground Rupee" Category=""></Actor>
<Actor ID="ACTOR_BG_DANPEI_MOVEBG" Key="bg_danpei_movebg" ObjectKey="GAMEPLAY_KEEP" Name="Dampé's House Objects" Category=""></Actor>
<Actor ID="ACTOR_EN_SNOWWD" Key="en_snowwd" ObjectKey="OBJECT_SNOWWD" Name="Snow-Covered Tree" Category=""></Actor>
<Actor ID="ACTOR_EN_PM" Key="en_pm" ObjectKey="OBJECT_MM" Name="Postman (Delivering Letters)" Category=""></Actor>
<Actor ID="ACTOR_EN_GAKUFU" Key="en_gakufu" ObjectKey="GAMEPLAY_KEEP" Name="2D Music Staff" Category=""></Actor>
<Actor ID="ACTOR_ELF_MSG4" Key="elf_msg4" ObjectKey="GAMEPLAY_KEEP" Name="Elf_Msg4" Category=""></Actor>
<Actor ID="ACTOR_ELF_MSG5" Key="elf_msg5" ObjectKey="GAMEPLAY_KEEP" Name="Elf_Msg5" Category=""></Actor>
<Actor ID="ACTOR_EN_COL_MAN" Key="en_col_man" ObjectKey="GAMEPLAY_KEEP" Name="Piece of Heart" Category=""></Actor>
<Actor ID="ACTOR_EN_TALK_GIBUD" Key="en_talk_gibud" ObjectKey="OBJECT_RD" Name="Gibdo (Ikana Well)" Category=""></Actor>
<Actor ID="ACTOR_EN_GIANT" Key="en_giant" ObjectKey="OBJECT_GIANT" Name="Giant" Category=""></Actor>
<Actor ID="ACTOR_OBJ_SNOWBALL" Key="obj_snowball" ObjectKey="OBJECT_GOROIWA" Name="Large Snowball" Category=""></Actor>
<Actor ID="ACTOR_BOSS_HAKUGIN" Key="boss_hakugin" ObjectKey="OBJECT_BOSS_HAKUGIN" Name="Goht" Category=""></Actor>
<Actor ID="ACTOR_EN_GB2" Key="en_gb2" ObjectKey="OBJECT_PS" Name="Ghost Hut Proprietor" Category=""></Actor>
<Actor ID="ACTOR_EN_ONPUMAN" Key="en_onpuman" ObjectKey="GAMEPLAY_KEEP" Name="Monkey Instrument Prompt" Category=""></Actor>
<Actor ID="ACTOR_BG_TOBIRA01" Key="bg_tobira01" ObjectKey="OBJECT_SPOT11_OBJ" Name="Goron Shrine Gate" Category=""></Actor>
<Actor ID="ACTOR_EN_TAG_OBJ" Key="en_tag_obj" ObjectKey="GAMEPLAY_KEEP" Name="En_Tag_Obj" Category=""></Actor>
<Actor ID="ACTOR_OBJ_DHOUSE" Key="obj_dhouse" ObjectKey="OBJECT_DHOUSE" Name="Dampé's House Facade" Category=""></Actor>
<Actor ID="ACTOR_OBJ_HAKAISI" Key="obj_hakaisi" ObjectKey="OBJECT_HAKAISI" Name="Gravestone" Category=""></Actor>
<Actor ID="ACTOR_BG_HAKUGIN_SWITCH" Key="bg_hakugin_switch" ObjectKey="OBJECT_GORONSWITCH" Name="Goron Link Switch" Category=""></Actor>
<Actor ID="ACTOR_EN_SNOWMAN" Key="en_snowman" ObjectKey="OBJECT_SNOWMAN" Name="Big and Small Eeno" Category=""></Actor>
<Actor ID="ACTOR_TG_SW" Key="tg_sw" ObjectKey="GAMEPLAY_KEEP" Name="TG_Sw" Category=""></Actor>
<Actor ID="ACTOR_EN_PO_SISTERS" Key="en_po_sisters" ObjectKey="OBJECT_PO_SISTERS" Name="Poe Sisters" Category=""></Actor>
<Actor ID="ACTOR_EN_PP" Key="en_pp" ObjectKey="OBJECT_PP" Name="Hiploop" Category=""></Actor>
<Actor ID="ACTOR_EN_HAKUROCK" Key="en_hakurock" ObjectKey="OBJECT_BOSS_HAKUGIN" Name="Goht Debris" Category=""></Actor>
<Actor ID="ACTOR_EN_HANABI" Key="en_hanabi" ObjectKey="GAMEPLAY_KEEP" Name="Fireworks" Category=""></Actor>
<Actor ID="ACTOR_OBJ_DOWSING" Key="obj_dowsing" ObjectKey="GAMEPLAY_KEEP" Name="Obj_Dowsing" Category=""></Actor>
<Actor ID="ACTOR_OBJ_WIND" Key="obj_wind" ObjectKey="GAMEPLAY_KEEP" Name="Wind Funnel" Category=""></Actor>
<Actor ID="ACTOR_EN_RACEDOG" Key="en_racedog" ObjectKey="OBJECT_DOG" Name="Dog (Doggie Racetrack)" Category=""></Actor>
<Actor ID="ACTOR_EN_KENDO_JS" Key="en_kendo_js" ObjectKey="OBJECT_JS" Name="Swordsman" Category=""></Actor>
<Actor ID="ACTOR_BG_BOTIHASIRA" Key="bg_botihasira" ObjectKey="OBJECT_BOTIHASIRA" Name="Captain Keeta Race Gatepost" Category=""></Actor>
<Actor ID="ACTOR_EN_FISH2" Key="en_fish2" ObjectKey="OBJECT_FB" Name="Marine Research Lab Fish" Category=""></Actor>
<Actor ID="ACTOR_EN_PST" Key="en_pst" ObjectKey="OBJECT_PST" Name="Postbox" Category=""></Actor>
<Actor ID="ACTOR_EN_POH" Key="en_poh" ObjectKey="OBJECT_PO" Name="Poe" Category=""></Actor>
<Actor ID="ACTOR_OBJ_SPIDERTENT" Key="obj_spidertent" ObjectKey="OBJECT_SPIDERTENT" Name="Tent-Shaped Spider Web" Category=""></Actor>
<Actor ID="ACTOR_EN_ZORAEGG" Key="en_zoraegg" ObjectKey="OBJECT_ZORAEGG" Name="Zora Egg" Category=""></Actor>
<Actor ID="ACTOR_EN_KBT" Key="en_kbt" ObjectKey="OBJECT_KBT" Name="Zubora" Category=""></Actor>
<Actor ID="ACTOR_EN_GG" Key="en_gg" ObjectKey="OBJECT_GG" Name="Darmani's Ghost I [?]" Category=""></Actor>
<Actor ID="ACTOR_EN_MARUTA" Key="en_maruta" ObjectKey="OBJECT_MARUTA" Name="Swordsman's School Practice Log" Category=""></Actor>
<Actor ID="ACTOR_OBJ_SNOWBALL2" Key="obj_snowball2" ObjectKey="OBJECT_GOROIWA" Name="Small Snowball" Category=""></Actor>
<Actor ID="ACTOR_EN_GG2" Key="en_gg2" ObjectKey="OBJECT_GG" Name="Darmani's Ghost II [?]" Category=""></Actor>
<Actor ID="ACTOR_OBJ_GHAKA" Key="obj_ghaka" ObjectKey="OBJECT_GHAKA" Name="Darmani's Gravestone" Category=""></Actor>
<Actor ID="ACTOR_EN_DNP" Key="en_dnp" ObjectKey="OBJECT_DNQ" Name="Deku Princess" Category=""></Actor>
<Actor ID="ACTOR_EN_DAI" Key="en_dai" ObjectKey="OBJECT_DAI" Name="Biggoron" Category=""></Actor>
<Actor ID="ACTOR_BG_GORON_OYU" Key="bg_goron_oyu" ObjectKey="OBJECT_OYU" Name="Hot Spring Water" Category=""></Actor>
<Actor ID="ACTOR_EN_KGY" Key="en_kgy" ObjectKey="OBJECT_KGY" Name="Gabora" Category=""></Actor>
<Actor ID="ACTOR_EN_INVADEPOH" Key="en_invadepoh" ObjectKey="GAMEPLAY_KEEP" Name="En_Invadepoh" Category=""></Actor>
<Actor ID="ACTOR_EN_GK" Key="en_gk" ObjectKey="OBJECT_GK" Name="Goron Elder's Son" Category=""></Actor>
<Actor ID="ACTOR_EN_AN" Key="en_an" ObjectKey="OBJECT_AN1" Name="Anju (Gameplay)" Category=""></Actor>
<Actor ID="ACTOR_EN_BEE" Key="en_bee" ObjectKey="OBJECT_BEE" Name="Giant Bee (Gameplay)" Category=""></Actor>
<Actor ID="ACTOR_EN_OT" Key="en_ot" ObjectKey="OBJECT_OT" Name="Seahorse" Category=""></Actor>
<Actor ID="ACTOR_EN_DRAGON" Key="en_dragon" ObjectKey="OBJECT_UTUBO" Name="Deep Python" Category=""></Actor>
<Actor ID="ACTOR_OBJ_DORA" Key="obj_dora" ObjectKey="OBJECT_DORA" Name="Swordsman's School Gong" Category=""></Actor>
<Actor ID="ACTOR_EN_BIGPO" Key="en_bigpo" ObjectKey="OBJECT_BIGPO" Name="Big Poe" Category=""></Actor>
<Actor ID="ACTOR_OBJ_KENDO_KANBAN" Key="obj_kendo_kanban" ObjectKey="OBJECT_DORA" Name="Swordsman's School Wooden Board" Category=""></Actor>
<Actor ID="ACTOR_OBJ_HARIKO" Key="obj_hariko" ObjectKey="OBJECT_HARIKO" Name="Cow Figurine" Category=""></Actor>
<Actor ID="ACTOR_EN_STH" Key="en_sth" ObjectKey="GAMEPLAY_KEEP" Name="En_Sth" Category=""></Actor>
<Actor ID="ACTOR_BG_SINKAI_KABE" Key="bg_sinkai_kabe" ObjectKey="OBJECT_SINKAI_KABE" Name="Bg_Sinkai_Kabe" Category=""></Actor>
<Actor ID="ACTOR_BG_HAKA_CURTAIN" Key="bg_haka_curtain" ObjectKey="OBJECT_HAKA_OBJ" Name="Beneath the Grave Curtain" Category=""></Actor>
<Actor ID="ACTOR_BG_KIN2_BOMBWALL" Key="bg_kin2_bombwall" ObjectKey="OBJECT_KIN2_OBJ" Name="Oceanside Spider House Bombable Wall" Category=""></Actor>
<Actor ID="ACTOR_BG_KIN2_FENCE" Key="bg_kin2_fence" ObjectKey="OBJECT_KIN2_OBJ" Name="Oceanside Spider House Fireplace Grate" Category=""></Actor>
<Actor ID="ACTOR_BG_KIN2_PICTURE" Key="bg_kin2_picture" ObjectKey="OBJECT_KIN2_OBJ" Name="Oceanside Spider House Skull Kid Painting" Category=""></Actor>
<Actor ID="ACTOR_BG_KIN2_SHELF" Key="bg_kin2_shelf" ObjectKey="OBJECT_KIN2_OBJ" Name="Oceanside Spider House Drawers and Bookshelf" Category=""></Actor>
<Actor ID="ACTOR_EN_RAIL_SKB" Key="en_rail_skb" ObjectKey="OBJECT_SKB" Name="Circle of Stalchildren" Category=""></Actor>
<Actor ID="ACTOR_EN_JG" Key="en_jg" ObjectKey="OBJECT_JG" Name="Goron Elder" Category=""></Actor>
<Actor ID="ACTOR_EN_TRU_MT" Key="en_tru_mt" ObjectKey="OBJECT_TRU" Name="Koume (Boat Cruise) [?]" Category=""></Actor>
<Actor ID="ACTOR_OBJ_UM" Key="obj_um" ObjectKey="OBJECT_UM" Name="Cremia's Cart" Category=""></Actor>
<Actor ID="ACTOR_EN_NEO_REEBA" Key="en_neo_reeba" ObjectKey="OBJECT_RB" Name="Leever" Category=""></Actor>
<Actor ID="ACTOR_BG_MBAR_CHAIR" Key="bg_mbar_chair" ObjectKey="OBJECT_MBAR_OBJ" Name="Milk Bar Chair" Category=""></Actor>
<Actor ID="ACTOR_BG_IKANA_BLOCK" Key="bg_ikana_block" ObjectKey="GAMEPLAY_DANGEON_KEEP" Name="Bg_Ikana_Block" Category=""></Actor>
<Actor ID="ACTOR_BG_IKANA_MIRROR" Key="bg_ikana_mirror" ObjectKey="OBJECT_IKANA_OBJ" Name="Stone Tower Temple Mirror" Category=""></Actor>
<Actor ID="ACTOR_BG_IKANA_ROTARYROOM" Key="bg_ikana_rotaryroom" ObjectKey="OBJECT_IKANA_OBJ" Name="Stone Tower Temple Rotating Room" Category=""></Actor>
<Actor ID="ACTOR_BG_DBLUE_BALANCE" Key="bg_dblue_balance" ObjectKey="OBJECT_DBLUE_OBJECT" Name="Great Bay Temple See-Saw" Category=""></Actor>
<Actor ID="ACTOR_BG_DBLUE_WATERFALL" Key="bg_dblue_waterfall" ObjectKey="OBJECT_DBLUE_OBJECT" Name="Great Bay Temple Water Spout" Category=""></Actor>
<Actor ID="ACTOR_EN_KAIZOKU" Key="en_kaizoku" ObjectKey="OBJECT_KZ" Name="Pirate [?]" Category=""></Actor>
<Actor ID="ACTOR_EN_GE2" Key="en_ge2" ObjectKey="OBJECT_GLA" Name="Patrolling Pirate Guard" Category=""></Actor>
<Actor ID="ACTOR_EN_MA_YTS" Key="en_ma_yts" ObjectKey="OBJECT_MA1" Name="Romani II [?]" Category=""></Actor>
<Actor ID="ACTOR_EN_MA_YTO" Key="en_ma_yto" ObjectKey="OBJECT_MA2" Name="Cremia" Category=""></Actor>
<Actor ID="ACTOR_OBJ_TOKEI_TURRET" Key="obj_tokei_turret" ObjectKey="OBJECT_TOKEI_TURRET" Name="South Clock Town Objects" Category=""></Actor>
<Actor ID="ACTOR_BG_DBLUE_ELEVATOR" Key="bg_dblue_elevator" ObjectKey="OBJECT_DBLUE_OBJECT" Name="Great Bay Temple Elevator" Category=""></Actor>
<Actor ID="ACTOR_OBJ_WARPSTONE" Key="obj_warpstone" ObjectKey="OBJECT_SEK" Name="Owl Statue" Category=""></Actor>
<Actor ID="ACTOR_EN_ZOG" Key="en_zog" ObjectKey="OBJECT_ZOG" Name="Mikau" Category=""></Actor>
<Actor ID="ACTOR_OBJ_ROTLIFT" Key="obj_rotlift" ObjectKey="OBJECT_ROTLIFT" Name="Deku Moon Trial Rotating Platform" Category=""></Actor>
<Actor ID="ACTOR_OBJ_JG_GAKKI" Key="obj_jg_gakki" ObjectKey="OBJECT_JG" Name="Goron Elder's Drum" Category=""></Actor>
<Actor ID="ACTOR_BG_INIBS_MOVEBG" Key="bg_inibs_movebg" ObjectKey="OBJECT_INIBS_OBJECT" Name="Twinmold's Lair Objects [?]" Category=""></Actor>
<Actor ID="ACTOR_EN_ZOT" Key="en_zot" ObjectKey="OBJECT_ZO" Name="Zora (Land)" Category=""></Actor>
<Actor ID="ACTOR_OBJ_TREE" Key="obj_tree" ObjectKey="OBJECT_TREE" Name="Fork-Branched Tree" Category=""></Actor>
<Actor ID="ACTOR_OBJ_Y2LIFT" Key="obj_y2lift" ObjectKey="OBJECT_KAIZOKU_OBJ" Name="Pirates' Fortress Mesh Elevator" Category=""></Actor>
<Actor ID="ACTOR_OBJ_Y2SHUTTER" Key="obj_y2shutter" ObjectKey="OBJECT_KAIZOKU_OBJ" Name="Pirates' Fortress Interior Door" Category=""></Actor>
<Actor ID="ACTOR_OBJ_BOAT" Key="obj_boat" ObjectKey="OBJECT_KAIZOKU_OBJ" Name="Pirates' Fortress Boat" Category=""></Actor>
<Actor ID="ACTOR_OBJ_TARU" Key="obj_taru" ObjectKey="OBJECT_TARU" Name="Barrel" Category=""></Actor>
<Actor ID="ACTOR_OBJ_HUNSUI" Key="obj_hunsui" ObjectKey="OBJECT_HUNSUI" Name="Geyser" Category=""></Actor>
<Actor ID="ACTOR_EN_JC_MATO" Key="en_jc_mato" ObjectKey="OBJECT_TRU" Name="Boat Cruise Target" Category=""></Actor>
<Actor ID="ACTOR_MIR_RAY3" Key="mir_ray3" ObjectKey="OBJECT_MIR_RAY" Name="Mirror Shield Light Ray II [?]" Category=""></Actor>
<Actor ID="ACTOR_EN_ZOB" Key="en_zob" ObjectKey="OBJECT_ZOB" Name="Japas" Category=""></Actor>
<Actor ID="ACTOR_ELF_MSG6" Key="elf_msg6" ObjectKey="GAMEPLAY_KEEP" Name="Elf_Msg6" Category=""></Actor>
<Actor ID="ACTOR_OBJ_NOZOKI" Key="obj_nozoki" ObjectKey="GAMEPLAY_KEEP" Name="Obj_Nozoki" Category=""></Actor>
<Actor ID="ACTOR_EN_TOTO" Key="en_toto" ObjectKey="OBJECT_ZM" Name="Toto" Category=""></Actor>
<Actor ID="ACTOR_EN_RAILGIBUD" Key="en_railgibud" ObjectKey="OBJECT_RD" Name="Gibdo (Ikana Canyon)" Category=""></Actor>
<Actor ID="ACTOR_EN_BABA" Key="en_baba" ObjectKey="OBJECT_BBA" Name="Bomb Shop Proprietor's Mother" Category=""></Actor>
<Actor ID="ACTOR_EN_SUTTARI" Key="en_suttari" ObjectKey="OBJECT_BOJ" Name="Sakon" Category=""></Actor>
<Actor ID="ACTOR_EN_ZOD" Key="en_zod" ObjectKey="OBJECT_ZOD" Name="Tijo" Category=""></Actor>
<Actor ID="ACTOR_EN_KUJIYA" Key="en_kujiya" ObjectKey="OBJECT_KUJIYA" Name="Lottery Shop Kiosk" Category=""></Actor>
<Actor ID="ACTOR_EN_GEG" Key="en_geg" ObjectKey="OBJECT_OF1D_MAP" Name="Don Gero" Category=""></Actor>
<Actor ID="ACTOR_OBJ_KINOKO" Key="obj_kinoko" ObjectKey="GAMEPLAY_KEEP" Name="Mushroom Scent Cloud" Category=""></Actor>
<Actor ID="ACTOR_OBJ_YASI" Key="obj_yasi" ObjectKey="OBJECT_OBJ_YASI" Name="Palm Tree" Category=""></Actor>
<Actor ID="ACTOR_EN_TANRON1" Key="en_tanron1" ObjectKey="GAMEPLAY_KEEP" Name="Swarm of Moths" Category=""></Actor>
<Actor ID="ACTOR_EN_TANRON2" Key="en_tanron2" ObjectKey="OBJECT_BOSS04" Name="Wart's Bubbles" Category=""></Actor>
<Actor ID="ACTOR_EN_TANRON3" Key="en_tanron3" ObjectKey="OBJECT_BOSS03" Name="Gyorg's Fish" Category=""></Actor>
<Actor ID="ACTOR_OBJ_CHAN" Key="obj_chan" ObjectKey="OBJECT_OBJ_CHAN" Name="Goron Village Chandelier" Category=""></Actor>
<Actor ID="ACTOR_EN_ZOS" Key="en_zos" ObjectKey="OBJECT_ZOS" Name="Evan" Category=""></Actor>
<Actor ID="ACTOR_EN_S_GORO" Key="en_s_goro" ObjectKey="OBJECT_OF1D_MAP" Name="Goron (Goron Shrine and Bomb Shop) [?]" Category=""></Actor>
<Actor ID="ACTOR_EN_NB" Key="en_nb" ObjectKey="OBJECT_NB" Name="Anju's Grandmother (Gameplay)" Category=""></Actor>
<Actor ID="ACTOR_EN_JA" Key="en_ja" ObjectKey="OBJECT_BOJ" Name="Jugglers" Category=""></Actor>
<Actor ID="ACTOR_BG_F40_BLOCK" Key="bg_f40_block" ObjectKey="OBJECT_F40_OBJ" Name="Stone Tower Temple Shifting Block" Category=""></Actor>
<Actor ID="ACTOR_BG_F40_SWITCH" Key="bg_f40_switch" ObjectKey="OBJECT_F40_SWITCH" Name="Elegy Statue Switch" Category=""></Actor>
<Actor ID="ACTOR_EN_PO_COMPOSER" Key="en_po_composer" ObjectKey="OBJECT_PO_COMPOSER" Name="Composer Brothers" Category=""></Actor>
<Actor ID="ACTOR_EN_GURUGURU" Key="en_guruguru" ObjectKey="OBJECT_FU" Name="Guru-Guru" Category=""></Actor>
<Actor ID="ACTOR_OCEFF_WIPE5" Key="oceff_wipe5" ObjectKey="GAMEPLAY_KEEP" Name="Sonata of Awakening Effect" Category=""></Actor>
<Actor ID="ACTOR_EN_STONE_HEISHI" Key="en_stone_heishi" ObjectKey="OBJECT_SDN" Name="Shiro" Category=""></Actor>
<Actor ID="ACTOR_OCEFF_WIPE6" Key="oceff_wipe6" ObjectKey="GAMEPLAY_KEEP" Name="Song of Soaring Effect" Category=""></Actor>
<Actor ID="ACTOR_EN_SCOPENUTS" Key="en_scopenuts" ObjectKey="OBJECT_DNT" Name="Business Scrub (Telescope)" Category=""></Actor>
<Actor ID="ACTOR_EN_SCOPECROW" Key="en_scopecrow" ObjectKey="OBJECT_CROW" Name="Guay (Observatory Telescope)" Category=""></Actor>
<Actor ID="ACTOR_OCEFF_WIPE7" Key="oceff_wipe7" ObjectKey="GAMEPLAY_KEEP" Name="Song of Healing Effect" Category=""></Actor>
<Actor ID="ACTOR_EFF_KAMEJIMA_WAVE" Key="eff_kamejima_wave" ObjectKey="OBJECT_KAMEJIMA" Name="Turtle's Tsunami" Category=""></Actor>
<Actor ID="ACTOR_EN_HG" Key="en_hg" ObjectKey="OBJECT_HARFGIBUD" Name="Pamela's Father (Normal)" Category=""></Actor>
<Actor ID="ACTOR_EN_HGO" Key="en_hgo" ObjectKey="OBJECT_HARFGIBUD" Name="Pamela's Father (Cursed)" Category=""></Actor>
<Actor ID="ACTOR_EN_ZOV" Key="en_zov" ObjectKey="OBJECT_ZOV" Name="Lulu" Category=""></Actor>
<Actor ID="ACTOR_EN_AH" Key="en_ah" ObjectKey="OBJECT_AH" Name="Anju's Mother (Gameplay)" Category=""></Actor>
<Actor ID="ACTOR_OBJ_HGDOOR" Key="obj_hgdoor" ObjectKey="OBJECT_HGDOOR" Name="Music Box House Cupboard Doors" Category=""></Actor>
<Actor ID="ACTOR_BG_IKANA_BOMBWALL" Key="bg_ikana_bombwall" ObjectKey="OBJECT_IKANA_OBJ" Name="Stone Tower Temple Bombable Floor Tile and Wall" Category=""></Actor>
<Actor ID="ACTOR_BG_IKANA_RAY" Key="bg_ikana_ray" ObjectKey="OBJECT_IKANA_OBJ" Name="Stone Tower Temple Light Ray [?]" Category=""></Actor>
<Actor ID="ACTOR_BG_IKANA_SHUTTER" Key="bg_ikana_shutter" ObjectKey="OBJECT_IKANA_OBJ" Name="Stone Tower Temple Lattice Door" Category=""></Actor>
<Actor ID="ACTOR_BG_HAKA_BOMBWALL" Key="bg_haka_bombwall" ObjectKey="OBJECT_HAKA_OBJ" Name="Beneath the Grave Bombable Wall" Category=""></Actor>
<Actor ID="ACTOR_BG_HAKA_TOMB" Key="bg_haka_tomb" ObjectKey="OBJECT_HAKA_OBJ" Name="Flat's Tomb" Category=""></Actor>
<Actor ID="ACTOR_EN_SC_RUPPE" Key="en_sc_ruppe" ObjectKey="GAMEPLAY_KEEP" Name="Large Rotating Green Rupee" Category=""></Actor>
<Actor ID="ACTOR_BG_IKNV_DOUKUTU" Key="bg_iknv_doukutu" ObjectKey="OBJECT_IKNV_OBJ" Name="Sharp's Cave" Category=""></Actor>
<Actor ID="ACTOR_BG_IKNV_OBJ" Key="bg_iknv_obj" ObjectKey="OBJECT_IKNV_OBJ" Name="Ikana Canyon Objects" Category=""></Actor>
<Actor ID="ACTOR_EN_PAMERA" Key="en_pamera" ObjectKey="OBJECT_PAMERA" Name="Pamela" Category=""></Actor>
<Actor ID="ACTOR_OBJ_HSSTUMP" Key="obj_hsstump" ObjectKey="OBJECT_HSSTUMP" Name="Hookshot Stump" Category=""></Actor>
<Actor ID="ACTOR_EN_HIDDEN_NUTS" Key="en_hidden_nuts" ObjectKey="OBJECT_HINTNUTS" Name="Mad Scrub (Sleeping)" Category=""></Actor>
<Actor ID="ACTOR_EN_ZOW" Key="en_zow" ObjectKey="OBJECT_ZO" Name="Zora (Water)" Category=""></Actor>
<Actor ID="ACTOR_EN_TALK" Key="en_talk" ObjectKey="GAMEPLAY_KEEP" Name="En_Talk" Category=""></Actor>
<Actor ID="ACTOR_EN_AL" Key="en_al" ObjectKey="OBJECT_AL" Name="Madame Aroma (Gameplay)" Category=""></Actor>
<Actor ID="ACTOR_EN_TAB" Key="en_tab" ObjectKey="OBJECT_TAB" Name="Mr. Barten" Category=""></Actor>
<Actor ID="ACTOR_EN_NIMOTSU" Key="en_nimotsu" ObjectKey="OBJECT_BOJ" Name="Bomb Shop Bag" Category=""></Actor>
<Actor ID="ACTOR_EN_HIT_TAG" Key="en_hit_tag" ObjectKey="GAMEPLAY_KEEP" Name="En_Hit_Tag" Category=""></Actor>
<Actor ID="ACTOR_EN_RUPPECROW" Key="en_ruppecrow" ObjectKey="OBJECT_CROW" Name="Guay (Circling Clock Town)" Category=""></Actor>
<Actor ID="ACTOR_EN_TANRON4" Key="en_tanron4" ObjectKey="OBJECT_TANRON4" Name="Flock of Seagulls" Category=""></Actor>
<Actor ID="ACTOR_EN_TANRON5" Key="en_tanron5" ObjectKey="OBJECT_BOSS02" Name="En_Tanron5" Category=""></Actor>
<Actor ID="ACTOR_EN_TANRON6" Key="en_tanron6" ObjectKey="OBJECT_TANRON5" Name="Swarm of Giant Bees" Category=""></Actor>
<Actor ID="ACTOR_EN_DAIKU2" Key="en_daiku2" ObjectKey="OBJECT_DAIKU" Name="Carpenter (Milk Road)" Category=""></Actor>
<Actor ID="ACTOR_EN_MUTO" Key="en_muto" ObjectKey="OBJECT_TORYO" Name="Mutoh (Gameplay)" Category=""></Actor>
<Actor ID="ACTOR_EN_BAISEN" Key="en_baisen" ObjectKey="OBJECT_BAI" Name="Captain Viscen" Category=""></Actor>
<Actor ID="ACTOR_EN_HEISHI" Key="en_heishi" ObjectKey="OBJECT_SDN" Name="Soldier (Mayor's House)" Category=""></Actor>
<Actor ID="ACTOR_EN_DEMO_HEISHI" Key="en_demo_heishi" ObjectKey="OBJECT_SDN" Name="Soldier (Cutscenes) I [?]" Category=""></Actor>
<Actor ID="ACTOR_EN_DT" Key="en_dt" ObjectKey="OBJECT_DT" Name="Mayor Dotour (Gameplay)" Category=""></Actor>
<Actor ID="ACTOR_EN_CHA" Key="en_cha" ObjectKey="OBJECT_CHA" Name="Laundry Pool Bell [?]" Category=""></Actor>
<Actor ID="ACTOR_OBJ_DINNER" Key="obj_dinner" ObjectKey="OBJECT_OBJ_DINNER" Name="Cremia and Romani's Dinner" Category=""></Actor>
<Actor ID="ACTOR_EFF_LASTDAY" Key="eff_lastday" ObjectKey="OBJECT_LASTDAY" Name="Moon Fall Effects" Category=""></Actor>
<Actor ID="ACTOR_BG_IKANA_DHARMA" Key="bg_ikana_dharma" ObjectKey="OBJECT_IKANA_OBJ" Name="Ancient Castle of Ikana Punchable Pillar Segments" Category=""></Actor>
<Actor ID="ACTOR_EN_AKINDONUTS" Key="en_akindonuts" ObjectKey="OBJECT_DNT" Name="Traveling Business Scrub" Category=""></Actor>
<Actor ID="ACTOR_EFF_STK" Key="eff_stk" ObjectKey="OBJECT_STK2" Name="Skull Kid Moon-Summoning Effects [?]" Category=""></Actor>
<Actor ID="ACTOR_EN_IG" Key="en_ig" ObjectKey="OBJECT_DAI" Name="Link the Goron" Category=""></Actor>
<Actor ID="ACTOR_EN_RG" Key="en_rg" ObjectKey="OBJECT_OF1D_MAP" Name="Goron (Goron Racetrack)" Category=""></Actor>
<Actor ID="ACTOR_EN_OSK" Key="en_osk" ObjectKey="OBJECT_IKN_DEMO" Name="Igos du Ikana and Henchmen's Heads" Category=""></Actor>
<Actor ID="ACTOR_EN_STH2" Key="en_sth2" ObjectKey="GAMEPLAY_KEEP" Name="En_Sth2" Category=""></Actor>
<Actor ID="ACTOR_EN_YB" Key="en_yb" ObjectKey="OBJECT_YB" Name="Kamaro" Category=""></Actor>
<Actor ID="ACTOR_EN_RZ" Key="en_rz" ObjectKey="OBJECT_RZ" Name="Rosa Sister" Category=""></Actor>
<Actor ID="ACTOR_EN_SCOPECOIN" Key="en_scopecoin" ObjectKey="GAMEPLAY_KEEP" Name="En_Scopecoin" Category=""></Actor>
<Actor ID="ACTOR_EN_BJT" Key="en_bjt" ObjectKey="OBJECT_BJT" Name="Hand in Toilet" Category=""></Actor>
<Actor ID="ACTOR_EN_BOMJIMA" Key="en_bomjima" ObjectKey="OBJECT_CS" Name="Jim I [?]" Category=""></Actor>
<Actor ID="ACTOR_EN_BOMJIMB" Key="en_bomjimb" ObjectKey="OBJECT_CS" Name="Jim II [?]" Category=""></Actor>
<Actor ID="ACTOR_EN_BOMBERS" Key="en_bombers" ObjectKey="OBJECT_CS" Name="Bomber II [?]" Category=""></Actor>
<Actor ID="ACTOR_EN_BOMBERS2" Key="en_bombers2" ObjectKey="OBJECT_CS" Name="Bomber (Hideout Guard)" Category=""></Actor>
<Actor ID="ACTOR_EN_BOMBAL" Key="en_bombal" ObjectKey="OBJECT_FUSEN" Name="Majora Balloon (North Clock Town)" Category=""></Actor>
<Actor ID="ACTOR_OBJ_MOON_STONE" Key="obj_moon_stone" ObjectKey="OBJECT_GI_RESERVE00" Name="Moon's Tear" Category=""></Actor>
<Actor ID="ACTOR_OBJ_MU_PICT" Key="obj_mu_pict" ObjectKey="GAMEPLAY_KEEP" Name="Obj_Mu_Pict" Category=""></Actor>
<Actor ID="ACTOR_BG_IKNINSIDE" Key="bg_ikninside" ObjectKey="OBJECT_IKNINSIDE_OBJ" Name="Ancient Castle of Ikana Objects [?]" Category=""></Actor>
<Actor ID="ACTOR_EFF_ZORABAND" Key="eff_zoraband" ObjectKey="OBJECT_ZORABAND" Name="Blue Spotlight Effect" Category=""></Actor>
<Actor ID="ACTOR_OBJ_KEPN_KOYA" Key="obj_kepn_koya" ObjectKey="OBJECT_KEPN_KOYA" Name="Gorman Track Buildings" Category=""></Actor>
<Actor ID="ACTOR_OBJ_USIYANE" Key="obj_usiyane" ObjectKey="OBJECT_OBJ_USIYANE" Name="Cow Barn Roof (Exterior)" Category=""></Actor>
<Actor ID="ACTOR_EN_NNH" Key="en_nnh" ObjectKey="OBJECT_NNH" Name="Deku Butler's Son" Category=""></Actor>
<Actor ID="ACTOR_OBJ_KZSAKU" Key="obj_kzsaku" ObjectKey="OBJECT_KZSAKU" Name="Metal Portcullis" Category=""></Actor>
<Actor ID="ACTOR_OBJ_MILK_BIN" Key="obj_milk_bin" ObjectKey="OBJECT_OBJ_MILK_BIN" Name="Chateau Romani Delivery Bottle" Category=""></Actor>
<Actor ID="ACTOR_EN_KITAN" Key="en_kitan" ObjectKey="OBJECT_KITAN" Name="Keaton" Category=""></Actor>
<Actor ID="ACTOR_BG_ASTR_BOMBWALL" Key="bg_astr_bombwall" ObjectKey="OBJECT_ASTR_OBJ" Name="Astral Observatory Bombable Wall" Category=""></Actor>
<Actor ID="ACTOR_BG_IKNIN_SUSCEIL" Key="bg_iknin_susceil" ObjectKey="OBJECT_IKNINSIDE_OBJ" Name="Hot Checkered Ceiling [?]" Category=""></Actor>
<Actor ID="ACTOR_EN_BSB" Key="en_bsb" ObjectKey="OBJECT_BSB" Name="Captain Keeta" Category=""></Actor>
<Actor ID="ACTOR_EN_RECEPGIRL" Key="en_recepgirl" ObjectKey="OBJECT_BG" Name="Mayor's Receptionist" Category=""></Actor>
<Actor ID="ACTOR_EN_THIEFBIRD" Key="en_thiefbird" ObjectKey="OBJECT_THIEFBIRD" Name="Takkuri" Category=""></Actor>
<Actor ID="ACTOR_EN_JGAME_TSN" Key="en_jgame_tsn" ObjectKey="OBJECT_TSN" Name="Fisherman (Fisherman's Jumping Game)" Category=""></Actor>
<Actor ID="ACTOR_OBJ_JGAME_LIGHT" Key="obj_jgame_light" ObjectKey="OBJECT_SYOKUDAI" Name="Torch Stand (Fisherman's Jumping Game)" Category=""></Actor>
<Actor ID="ACTOR_OBJ_YADO" Key="obj_yado" ObjectKey="OBJECT_YADO_OBJ" Name="Stockpot Inn Window" Category=""></Actor>
<Actor ID="ACTOR_DEMO_SYOTEN" Key="demo_syoten" ObjectKey="OBJECT_SYOTEN" Name="Ikana Canyon Curse Lifted Effects" Category=""></Actor>
<Actor ID="ACTOR_DEMO_MOONEND" Key="demo_moonend" ObjectKey="OBJECT_MOONEND" Name="Moon (Cutscenes)" Category=""></Actor>
<Actor ID="ACTOR_BG_LBFSHOT" Key="bg_lbfshot" ObjectKey="OBJECT_LBFSHOT" Name="Rainbow Hookshot Pillar" Category=""></Actor>
<Actor ID="ACTOR_BG_LAST_BWALL" Key="bg_last_bwall" ObjectKey="OBJECT_LAST_OBJ" Name="Link Moon Trial Bombable and Climbable Walls" Category=""></Actor>
<Actor ID="ACTOR_EN_AND" Key="en_and" ObjectKey="OBJECT_AND" Name="Anju (Wedding Dress)" Category=""></Actor>
<Actor ID="ACTOR_EN_INVADEPOH_DEMO" Key="en_invadepoh_demo" ObjectKey="GAMEPLAY_KEEP" Name="Invader Poe (Cutscenes)" Category=""></Actor>
<Actor ID="ACTOR_OBJ_DANPEILIFT" Key="obj_danpeilift" ObjectKey="OBJECT_OBJ_DANPEILIFT" Name="Deku Shrine and Snowhead Temple Elevator [?]" Category=""></Actor>
<Actor ID="ACTOR_EN_FALL2" Key="en_fall2" ObjectKey="OBJECT_FALL2" Name="Falling Moon" Category=""></Actor>
<Actor ID="ACTOR_DM_AL" Key="dm_al" ObjectKey="OBJECT_AL" Name="Madame Aroma (Cutscenes)" Category=""></Actor>
<Actor ID="ACTOR_DM_AN" Key="dm_an" ObjectKey="OBJECT_AN1" Name="Anju Cutscene Animations" Category=""></Actor>
<Actor ID="ACTOR_DM_AH" Key="dm_ah" ObjectKey="OBJECT_AH" Name="Anju's Mother (Cutscenes)" Category=""></Actor>
<Actor ID="ACTOR_DM_NB" Key="dm_nb" ObjectKey="OBJECT_NB" Name="Anju's Grandmother (Cutscenes)" Category=""></Actor>
<Actor ID="ACTOR_EN_DRS" Key="en_drs" ObjectKey="OBJECT_DRS" Name="Wedding Dress Mannequin" Category=""></Actor>
<Actor ID="ACTOR_EN_ENDING_HERO" Key="en_ending_hero" ObjectKey="OBJECT_DT" Name="Mayor Dotour (Cutscenes)" Category=""></Actor>
<Actor ID="ACTOR_DM_BAL" Key="dm_bal" ObjectKey="OBJECT_BAL" Name="Tingle (Cutscenes)" Category=""></Actor>
<Actor ID="ACTOR_EN_PAPER" Key="en_paper" ObjectKey="OBJECT_BAL" Name="Tingle Confetti" Category=""></Actor>
<Actor ID="ACTOR_EN_HINT_SKB" Key="en_hint_skb" ObjectKey="OBJECT_SKB" Name="Stalchild (Oceanside Spider House)" Category=""></Actor>
<Actor ID="ACTOR_DM_TAG" Key="dm_tag" ObjectKey="GAMEPLAY_KEEP" Name="Dm_Tag" Category=""></Actor>
<Actor ID="ACTOR_EN_BH" Key="en_bh" ObjectKey="OBJECT_BH" Name="Brown Bird" Category=""></Actor>
<Actor ID="ACTOR_EN_ENDING_HERO2" Key="en_ending_hero2" ObjectKey="OBJECT_BAI" Name="Viscen (Cutscenes)" Category=""></Actor>
<Actor ID="ACTOR_EN_ENDING_HERO3" Key="en_ending_hero3" ObjectKey="OBJECT_TORYO" Name="Mutoh (Cutscenes)" Category=""></Actor>
<Actor ID="ACTOR_EN_ENDING_HERO4" Key="en_ending_hero4" ObjectKey="OBJECT_SDN" Name="Soldier (Cutscenes) II [?]" Category=""></Actor>
<Actor ID="ACTOR_EN_ENDING_HERO5" Key="en_ending_hero5" ObjectKey="OBJECT_DAIKU" Name="Carpenter (Cutscenes)" Category=""></Actor>
<Actor ID="ACTOR_EN_ENDING_HERO6" Key="en_ending_hero6" ObjectKey="GAMEPLAY_KEEP" Name="En_Ending_Hero6" Category=""></Actor>
<Actor ID="ACTOR_DM_GM" Key="dm_gm" ObjectKey="OBJECT_AN1" Name="Dm_Gm" Category=""></Actor>
<Actor ID="ACTOR_OBJ_SWPRIZE" Key="obj_swprize" ObjectKey="GAMEPLAY_KEEP" Name="Obj_Swprize" Category=""></Actor>
<Actor ID="ACTOR_EN_INVISIBLE_RUPPE" Key="en_invisible_ruppe" ObjectKey="GAMEPLAY_KEEP" Name="Invisible - Rupee" Category=""></Actor>
<Actor ID="ACTOR_OBJ_ENDING" Key="obj_ending" ObjectKey="OBJECT_ENDING_OBJ" Name="Epilogue Cutscene Objects" Category=""></Actor>
<Actor ID="ACTOR_EN_RSN" Key="en_rsn" ObjectKey="GAMEPLAY_KEEP" Name="Bomb Shop Proprietor" Category=""></Actor>
<!-- =============================================== -->
<!-- ================ MESSAGE TABLE ================ -->
<!-- =============================================== -->
<List Name="Elf_Msg Message ID">
<!-- TODO -->
<!--Item Key="msg_00" Name="What's that?" Value="0x00"/ -->
</List>
<!-- =============================================== -->
<!-- ================ COLLECTIBLES ================= -->
<!-- =============================================== -->
<List Name="Collectibles">
<Item Value="0x00" Key="ITEM00_RUPEE_GREEN" Name="Green Rupee" />
<Item Value="0x01" Key="ITEM00_RUPEE_BLUE" Name="Blue Rupee" />
<Item Value="0x02" Key="ITEM00_RUPEE_RED" Name="Red Rupee" />
<Item Value="0x03" Key="ITEM00_RECOVERY_HEART" Name="Recovery Heart" />
<Item Value="0x04" Key="ITEM00_BOMBS_A" Name="Bomb (5)" />
<Item Value="0x05" Key="ITEM00_ARROWS_10" Name="Arrows (10)" />
<Item Value="0x06" Key="ITEM00_HEART_PIECE" Name="Heart Piece" />
<Item Value="0x07" Key="ITEM00_HEART_CONTAINER" Name="Heart Container" />
<Item Value="0x08" Key="ITEM00_ARROWS_30" Name="Arrows (20)" />
<Item Value="0x09" Key="ITEM00_ARROWS_40" Name="Arrows (30)" />
<Item Value="0x0A" Key="ITEM00_ARROWS_50" Name="Arrows (50)" />
<Item Value="0x0B" Key="ITEM00_BOMBS_B" Name="Bomb (5)" />
<Item Value="0x0C" Key="ITEM00_NUTS_1" Name="Deku Nut (1)" />
<Item Value="0x0D" Key="ITEM00_STICK" Name="Deku Stick (1)" />
<Item Value="0x0E" Key="ITEM00_MAGIC_LARGE" Name="Large Magic Jar" />
<Item Value="0x0F" Key="ITEM00_MAGIC_SMALL" Name="Small Magic Jar" />
<Item Value="0x10" Key="ITEM00_MASK" Name="Link=Arrows, Zora=Heart, Goron=Magic" />
<Item Value="0x11" Key="ITEM00_SMALL_KEY" Name="Small Key" />
<Item Value="0x12" Key="ITEM00_FLEXIBLE" Name="Flexible" />
<Item Value="0x13" Key="ITEM00_RUPEE_HUGE" Name="Orange Rupee" />
<Item Value="0x14" Key="ITEM00_RUPEE_PURPLE" Name="Purple Rupee" />
<Item Value="0x15" Key="ITEM00_3_HEARTS" Name="Recovery Hearts (3)"/>
<Item Value="0x16" Key="ITEM00_SHIELD_HERO" Name="Hero-Shield" />
<Item Value="0x17" Key="ITEM00_NUTS_10" Name="Deku Nuts (10)" />
<Item Value="0x18" Key="ITEM00_NOTHING" Name="Nothing" />
<Item Value="0x19" Key="ITEM00_BOMBS_0" Name="Bomb (None/Fake)"/>
<Item Value="0x1A" Key="ITEM00_BIG_FAIRY" Name="Big Fairy" />
<Item Value="0x1B" Key="ITEM00_MAP" Name="Map" />
<Item Value="0x1C" Key="ITEM00_COMPASS" Name="Compass" />
<Item Value="0x1D" Key="ITEM00_MUSHROOM_CLOUD" Name="Mushroom Cloud" />
</List>
<!-- =============================================== -->
<!-- ================ CHEST CONTENT ================ -->
<!-- =============================================== -->
<List Name="Chest Content">
<Item Key="item_00" Value="0x00" Name="[None]" />
<Item Key="item_01" Value="0x01" Name="Rupee (1)" />
<Item Key="item_02" Value="0x02" Name="Rupees (5)" />
<Item Key="item_03" Value="0x03" Name="Rupees (10)" />
<Item Key="item_04" Value="0x04" Name="Rupees (20)" />
<Item Key="item_05" Value="0x05" Name="Rupees (50)" />
<Item Key="item_06" Value="0x06" Name="Rupees (100)" />
<Item Key="item_07" Value="0x07" Name="Rupees (200)" />
<Item Key="item_08" Value="0x08" Name="Wallet (Adult)" />
<Item Key="item_09" Value="0x09" Name="Wallet (Giant)" />
<Item Key="item_0A" Value="0x0A" Name="Recovery Heart" />
<Item Key="item_0B" Value="0x0B" Name="GI_0B" />
<Item Key="item_0C" Value="0x0C" Name="Heart Piece" />
<Item Key="item_0D" Value="0x0D" Name="Heart Container" />
<Item Key="item_0E" Value="0x0E" Name="Small Magic Jar" />
<Item Key="item_0F" Value="0x0F" Name="Large Magic Jar" />
<Item Key="item_10" Value="0x10" Name="GI_10" />
<Item Key="item_11" Value="0x11" Name="Stray Fairy" />
<Item Key="item_12" Value="0x12" Name="GI_12" />
<Item Key="item_13" Value="0x13" Name="GI_13" />
<Item Key="item_14" Value="0x14" Name="Bombs (1)" />
<Item Key="item_15" Value="0x15" Name="Bombs (5)" />
<Item Key="item_16" Value="0x16" Name="Bombs (10)" />
<Item Key="item_17" Value="0x17" Name="Bombs (20)" />
<Item Key="item_18" Value="0x18" Name="Bombs (30)" />
<Item Key="item_19" Value="0x19" Name="Deku-Stick" />
<Item Key="item_1A" Value="0x1A" Name="Bombchus (10)" />
<Item Key="item_1B" Value="0x1B" Name="Bomb-Bag (20)" />
<Item Key="item_1C" Value="0x1C" Name="Bomb-Bag (30)" />
<Item Key="item_1D" Value="0x1D" Name="Bomb-Bag (40)" />
<Item Key="item_1E" Value="0x1E" Name="Arrows (10)" />
<Item Key="item_1F" Value="0x1F" Name="Arrows (30)" />
<Item Key="item_20" Value="0x20" Name="Arrows (40)" />
<Item Key="item_21" Value="0x21" Name="Arrows (50)" />
<Item Key="item_22" Value="0x22" Name="Quiver (30)" />
<Item Key="item_23" Value="0x23" Name="Quiver (40)" />
<Item Key="item_24" Value="0x24" Name="Quiver (50)" />
<Item Key="item_25" Value="0x25" Name="Fire-Arrows" />
<Item Key="item_26" Value="0x26" Name="Ice-Arrows" />
<Item Key="item_27" Value="0x27" Name="Light-Arrows" />
<Item Key="item_28" Value="0x28" Name="Deku-Nuts (1)" />
<Item Key="item_29" Value="0x29" Name="Deku-Nuts (5)" />
<Item Key="item_2A" Value="0x2A" Name="Deku-Nuts (10)" />
<Item Key="item_2B" Value="0x2B" Name="GI_2B" />
<Item Key="item_2C" Value="0x2C" Name="GI_2C" />
<Item Key="item_2D" Value="0x2D" Name="GI_2D" />
<Item Key="item_2E" Value="0x2E" Name="Bombchus (20)" />
<Item Key="item_2F" Value="0x2F" Name="GI_2F" />
<Item Key="item_30" Value="0x30" Name="GI_30" />
<Item Key="item_31" Value="0x31" Name="GI_31" />
<Item Key="item_32" Value="0x32" Name="Hero-Shield" />
<Item Key="item_33" Value="0x33" Name="Mirror-Shield" />
<Item Key="item_34" Value="0x34" Name="Powder-Keg" />
<Item Key="item_35" Value="0x35" Name="Magic Beans" />
<Item Key="item_36" Value="0x36" Name="Bombchu (1)" />
<Item Key="item_37" Value="0x37" Name="Kokiri-Sword" />
<Item Key="item_38" Value="0x38" Name="Razow-Sword" />
<Item Key="item_39" Value="0x39" Name="Gilded-Sword" />
<Item Key="item_3A" Value="0x3A" Name="Bombchus (5)" />
<Item Key="item_3B" Value="0x3B" Name="Great-Fairy Sword" />
<Item Key="item_3C" Value="0x3C" Name="Small Key" />
<Item Key="item_3D" Value="0x3D" Name="Boss-Key" />
<Item Key="item_3E" Value="0x3E" Name="Map" />
<Item Key="item_3F" Value="0x3F" Name="Compass" />
<Item Key="item_40" Value="0x40" Name="GI_40" />
<Item Key="item_41" Value="0x41" Name="Hookshor" />
<Item Key="item_42" Value="0x42" Name="Lens of Truth" />
<Item Key="item_43" Value="0x43" Name="Pictograph Box" />
<Item Key="item_44" Value="0x44" Name="GI_44" />
<Item Key="item_45" Value="0x45" Name="GI_45" />
<Item Key="item_46" Value="0x46" Name="GI_46" />
<Item Key="item_47" Value="0x47" Name="GI_47" />
<Item Key="item_48" Value="0x48" Name="GI_48" />
<Item Key="item_49" Value="0x49" Name="GI_49" />
<Item Key="item_4A" Value="0x4A" Name="GI_4A" />
<Item Key="item_4B" Value="0x4B" Name="GI_4B" />
<Item Key="item_4C" Value="0x4C" Name="Ocarina of time" />
<Item Key="item_4D" Value="0x4D" Name="GI_4D" />
<Item Key="item_4E" Value="0x4E" Name="GI_4E" />
<Item Key="item_4F" Value="0x4F" Name="GI_4F" />
<Item Key="item_50" Value="0x50" Name="Bomber's Notebook" />
<Item Key="item_51" Value="0x51" Name="GI_51" />
<Item Key="item_52" Value="0x52" Name="Golden-Skulltula Token" />
<Item Key="item_53" Value="0x53" Name="GI_53" />
<Item Key="item_54" Value="0x54" Name="GI_54" />
<Item Key="item_55" Value="0x55" Name="Remains (Odolwa)" />
<Item Key="item_56" Value="0x56" Name="Remains (Goht)" />
<Item Key="item_57" Value="0x57" Name="Remains (Gyorg)" />
<Item Key="item_58" Value="0x58" Name="Remains (Twinmold)" />
<Item Key="item_59" Value="0x59" Name="Red Potion Bottle" />
<Item Key="item_5A" Value="0x5A" Name="Bottle" />
<Item Key="item_5B" Value="0x5B" Name="Red Potion" />
<Item Key="item_5C" Value="0x5C" Name="Green Potion" />
<Item Key="item_5D" Value="0x5D" Name="Blue Potion" />
<Item Key="item_5E" Value="0x5E" Name="Fairy" />
<Item Key="item_5F" Value="0x5F" Name="Deku Princess" />
<Item Key="item_60" Value="0x60" Name="Milk-Bottle (Full)" />
<Item Key="item_61" Value="0x61" Name="Milk-Bottle (Half)" />
<Item Key="item_62" Value="0x62" Name="Fish" />
<Item Key="item_63" Value="0x63" Name="Bug" />
<Item Key="item_64" Value="0x64" Name="Blue-Fire" />
<Item Key="item_65" Value="0x65" Name="Poe" />
<Item Key="item_66" Value="0x66" Name="Big-Poe" />
<Item Key="item_67" Value="0x67" Name="Spring-Water (Cold)" />
<Item Key="item_68" Value="0x68" Name="Spring-Water (Hot)" />
<Item Key="item_69" Value="0x69" Name="Zora-Egg" />
<Item Key="item_6A" Value="0x6A" Name="Gold-Dust" />
<Item Key="item_6B" Value="0x6B" Name="Mushroom" />
<Item Key="item_6C" Value="0x6C" Name="GI_6C" />
<Item Key="item_6D" Value="0x6D" Name="GI_6D" />
<Item Key="item_6E" Value="0x6E" Name="Seahorse" />
<Item Key="item_6F" Value="0x6F" Name="Chateau-Romani Bottle" />
<Item Key="item_70" Value="0x70" Name="Hylian Loach" />
<Item Key="item_71" Value="0x71" Name="GI_71" />
<Item Key="item_72" Value="0x72" Name="GI_72" />
<Item Key="item_73" Value="0x73" Name="GI_73" />
<Item Key="item_74" Value="0x74" Name="GI_74" />
<Item Key="item_75" Value="0x75" Name="GI_75" />
<Item Key="item_76" Value="0x76" Name="Ice-Trap" />
<Item Key="item_77" Value="0x77" Name="GI_77" />
<Item Key="item_78" Value="0x78" Name="Mask (Deku)" />
<Item Key="item_79" Value="0x79" Name="Mask (Goron)" />
<Item Key="item_7A" Value="0x7A" Name="Mask (Zora)" />
<Item Key="item_7B" Value="0x7B" Name="Mask (Fierce-Deity)" />
<Item Key="item_7C" Value="0x7C" Name="Mask (Captain)" />
<Item Key="item_7D" Value="0x7D" Name="Mask (Giant)" />
<Item Key="item_7E" Value="0x7E" Name="Mask (All-Night)" />
<Item Key="item_7F" Value="0x7F" Name="Mask (Bunny)" />
<Item Key="item_80" Value="0x80" Name="Mask (Keaton)" />
<Item Key="item_81" Value="0x81" Name="Mask (Garo)" />
<Item Key="item_82" Value="0x82" Name="Mask (Romani)" />
<Item Key="item_83" Value="0x83" Name="Mask (Circus leader)" />
<Item Key="item_84" Value="0x84" Name="Mask (Postman)" />
<Item Key="item_85" Value="0x85" Name="Mask (Couple)" />
<Item Key="item_86" Value="0x86" Name="Mask (Great-Fairy)" />
<Item Key="item_87" Value="0x87" Name="Mask (Gibdo)" />
<Item Key="item_88" Value="0x88" Name="Mask (Don-Gero)" />
<Item Key="item_89" Value="0x89" Name="Mask (Kamaro)" />
<Item Key="item_8A" Value="0x8A" Name="Mask (Truth)" />
<Item Key="item_8B" Value="0x8B" Name="Mask (Stone)" />
<Item Key="item_8C" Value="0x8C" Name="Mask (Bremen)" />
<Item Key="item_8D" Value="0x8D" Name="Mask (Blast)" />
<Item Key="item_8E" Value="0x8E" Name="Mask (Scents)" />
<Item Key="item_8F" Value="0x8F" Name="Mask (Kafei)" />
<Item Key="item_90" Value="0x90" Name="GI_90" />
<Item Key="item_91" Value="0x91" Name="Chateau-Romani Milk" />
<Item Key="item_92" Value="0x92" Name="Regular Milk" />
<Item Key="item_93" Value="0x93" Name="Gold Dust (0x93)" />
<Item Key="item_94" Value="0x94" Name="Hylian Loach (0x94)" />
<Item Key="item_95" Value="0x95" Name="Seahorse (Caught)" />
<Item Key="item_96" Value="0x96" Name="Moon tear" />
<Item Key="item_97" Value="0x97" Name="Land-Deed (Land)" />
<Item Key="item_98" Value="0x98" Name="Land-Deed (Swamp)" />
<Item Key="item_99" Value="0x99" Name="Land-Deed (Mountain)" />
<Item Key="item_9A" Value="0x9A" Name="Land-Deed (Ocean)" />
<Item Key="item_9B" Value="0x9B" Name="Stolen Sword (Great-Fairy)" />
<Item Key="item_9C" Value="0x9C" Name="Stolen Sword (Kokiri)" />
<Item Key="item_9D" Value="0x9D" Name="Stolen Sword (Razor)" />
<Item Key="item_9E" Value="0x9E" Name="Stolen Sword (Gilded)" />
<Item Key="item_9F" Value="0x9F" Name="Stolen Shield (Hero)" />
<Item Key="item_A0" Value="0xA0" Name="Room-Key" />
<Item Key="item_A1" Value="0xA1" Name="Letter to Mama" />
<Item Key="item_A2" Value="0xA2" Name="GI_A2" />
<Item Key="item_A3" Value="0xA3" Name="GI_A3" />
<Item Key="item_A4" Value="0xA4" Name="GI_A4" />
<Item Key="item_A5" Value="0xA5" Name="GI_A5" />
<Item Key="item_A6" Value="0xA6" Name="GI_A6" />
<Item Key="item_A7" Value="0xA7" Name="GI_A7" />
<Item Key="item_A8" Value="0xA8" Name="GI_A8" />
<Item Key="item_A9" Value="0xA9" Name="Stolen Bottle" />
<Item Key="item_AA" Value="0xAA" Name="Letter to Kafei" />
<Item Key="item_AB" Value="0xAB" Name="Pendant of memories" />
<Item Key="item_AC" Value="0xAC" Name="GI_AC" />
<Item Key="item_AD" Value="0xAD" Name="GI_AD" />
<Item Key="item_AE" Value="0xAE" Name="GI_AE" />
<Item Key="item_AF" Value="0xAF" Name="GI_AF" />
<Item Key="item_B0" Value="0xB0" Name="GI_B0" />
<Item Key="item_B1" Value="0xB1" Name="GI_B1" />
<Item Key="item_B2" Value="0xB2" Name="GI_B2" />
<Item Key="item_B3" Value="0xB3" Name="GI_B3" />
<Item Key="item_B4" Value="0xB4" Name="Tingle-Map (Clock-Town)" />
<Item Key="item_B5" Value="0xB5" Name="Tingle-Map (Woodfall)" />
<Item Key="item_B6" Value="0xB6" Name="Tingle-Map (Snowhead)" />
<Item Key="item_B7" Value="0xB7" Name="Tingle-Map (Romani Ranch)" />
<Item Key="item_B8" Value="0xB8" Name="Tingle-Map (Great Bay)" />
<Item Key="item_B9" Value="0xB9" Name="Tingle-Map (Stone Tower)" />
</List>
</Table>
+35 -21
View File
@@ -1,17 +1,30 @@
# Ocarina Of Time
## Table of Contents
1. [Getting Started](#getting-started)
2. [Scene Overview](#scene-overview)
3. [Actors](#actors)
4. [Exits](#exits)
5. [Draw Config and Dynamic Material Properties](#scene-draw-configuration-and-dynamic-material-properties)
6. [Collision](#collision)
7. [Skeletons and Animations](#skeletons-and-animations)
8. [Flipbook Textures](#flipbook-textures)
9. [Custom Link Process](#custom-link-process)
10. [Custom Skeleton Mesh Process](#custom-skeleton-mesh-process)
11. [Cutscenes](#cutscenes)
### Getting Started
1. In the 3D view properties sidebar, go to the ``Fast64`` tab, then ``Fast64 Global Settings`` and set ``Game`` to ``OOT``.
2. Set ``F3D Microcode`` to ``F3DEX2/LX2``.
3. Switch to the ``OOT`` tab. In ``OOT File Settings``, set your decomp path to the path of your [OoT Decomp](https://github.com/zeldaret/oot/) repository on disk.
4. In ``OOT Tools``, click "Add Scene" to create a basic scene.
1. In the 3D view properties sidebar (default hotkey to show this is `n` in the viewport), go to the ``Fast64`` tab, then ``Fast64 Global Settings`` and set ``Game`` to ``OOT``.
2. Switch to the ``OOT`` tab. In ``OOT File Settings``, set your decomp path to the path of your [HackerOoT (recommended)](https://github.com/HackerN64/HackerOoT) or [OoT Decomp](https://github.com/zeldaret/oot/) repository on disk. Check `Enable HackerOoT Features` if using HackerOoT.
3. In ``OOT Tools``, click `Add Scene` to create a basic scene.
4. Press `a` so that everything is selected, then click `Clear Transform`.
5. In ``OOT Scene Exporter`` you can choose the scene to replace or add. Some scenes have some hardcoded things that will cause them to break, so choose something like ``Market Entrance (Child Day) (Entra)``.
- To add a custom scene choose ``Custom`` in the scene search box, then choose in which folder you want to export the scene and which name you want it to be (note that Fast64 will force the scene name to be lower-case).
- Enable ``Export as Single File`` if you want to have your scene in the same format as the other ones in decomp.
6. Make sure you selected the right scene in ``Scene Object`` then click "Export Scene" to export it. When you click ``Add Scene`` this is set automatically.
7. Compile and run the game. This was tested for commit ef56b01.
7. Compile and run the game.
8. (Optional) In the ``View`` tab you may want to increase the ``Clip End`` value.
9. Note: You can enable ``Export as Single File`` if you want to have your scene in the same format as the other ones in decomp.
10. Note: You can read [this code](https://github.com/Dragorn421/oot/tree/mod_base_for_mods) to take a glance at what you can do for quality of life for testing.
9. Note: You can read [this code](https://github.com/Dragorn421/oot/tree/mod_base_for_mods) to take a glance at what you can do for quality of life for testing.
### Scene Overview
In Blender, the "empty" object type is used to define different types of OOT data, including scenes and rooms.
@@ -124,24 +137,19 @@ Heavy modifications of Links model can cause his matrices array to shift from wh
6. In the actor header file, (in src/overlays/actors/\<name\>/), set the joint/morph table sizes to be (number of bones + 1)
7. In the actor source file, this value should also be used for the limbCount argument in SkelAnime_InitFlex().
### Creating a Cutscene
### Cutscenes
For more informations about cutscenes [click here](cutscene_docs.md)
**Creating the cutscene itself:**
To create custom cutscenes you need to get [zcamedit, made by Sauraen](https://github.com/sauraen/zcamedit).
1. Start with using the ``Add Cutscene`` button from the OOT Panel. Name it ``Cutscene.YOUR_CS_NAME``, ``YOUR_CS_NAME`` being the name of your choice, it can be something like: ``Cutscene.fireTempleIntroCS``. Note that this object can't be parented to any object.
2. Select the cutscene empty object, then in the ``Object Properties`` panel click on ``Init Cutscene Empty``, then ``Create camera shot``. This will initialise the cutscene and add a basic camera shot with 4 bones.
3. Select the scene where you want to add the cutscene, and in the object properties go in the ``Cutscene`` tab then enable ``Write Cutscene``. In ``Data`` select ``Object`` and in ``Cutscene Object`` select the cutscene empty object you just created.
4. Now you need to create the camera shot. The ``Create camera shot`` button from the cutscene object's properties panel will add a basic shot that you can edit. To have a better idea of the position/angle of one point of the shot, you can change the display of the armature in the ``Object Data Properties`` panel (select the shot object first), choose ``Octahedral`` in ``Viewport Display`` then ``Display As``.
5. As mentioned in the zcamedit repository, the first and last bone of the shot won't be actual camera points, it defines the start and the end of a cutscene, this means with the basic shot you'll have only 2 actual camera points. Either duplicate (``SHIFT+D``) or add a new bone to add more camera points.
6. You can edit the position and rotation of each bone in the ``Edit Mode`` after selecting the shot object. The "tail" (less large point) of a bone is the direction, and "head" (larger point) is the origin.
7. When you're done with your cutscene, start with exporting your scene (you don't need to export it everytime). When this is done, select the cutscene object you want to export and use ``Export Cutscene`` from ``OOT Cutscene Exporter`` in the OOT panel. In the ``File`` field, you can choose the scene of your choice (note that it can export into actors too).
1. Start with using the ``Add Cutscene`` button from the OOT Panel. Name it ``Cutscene.YOUR_CS_NAME``, ``YOUR_CS_NAME`` being the name of your choice, it can be something like: ``Cutscene.fireTempleIntroCS``. Note that this object can't be parented to any object, also this will automatically create a new camera and it will also set the Blender scene's active camera to this one.
2. Select the scene where you want to add the cutscene, and in the object properties go in the ``Cutscene`` tab then enable ``Write Cutscene``. In ``Data`` select ``Object`` and in ``Cutscene Object`` select the cutscene empty object you just created.
3. Now you can create the camera shot. The ``Create Camera Shot`` button from the cutscene object's properties panel will add a basic shot with 4 bones that you can edit. To have a better idea of the position/angle of one point of the shot, you can change the display of the armature in the ``Object Data Properties`` panel (select the shot object first), choose ``Octahedral`` in ``Viewport Display`` then ``Display As``.
4. The first and last bone of the shot won't be actual camera points, it defines the start and the end of a camera shot, this means a basic shot will only have 2 actual camera points. Either duplicate (``SHIFT+D``) or add a new bone to add more camera points. Note that these points will be exported in the order you can see in the view layer.
5. You can edit the position and rotation of each bone in the ``Edit Mode`` after selecting the shot object. The "tail" (less large point) of a bone is the direction (the "look-at", or AT), and "head" (larger point) is the origin (the "eye").
7. When you're done with your cutscene, exporting the scene will also export the camera shot and actor cue data. If you don't want to re-export the scene everytime, select the cutscene object you want to export and use ``Export Cutscene`` from ``OOT Cutscene Exporter`` in the OOT panel. In the ``File`` field, you can choose the scene of your choice (note that it can export into actors too). You can toggle the usage of decomp's names and macros with the ``Use Decomp for Export`` checkbox, you can also choose to insert the motion data in an existing cutscene (it will create a new array if it can't find it, it's based on the name of the object you selected). This is done by toggling the ``Export Motion Data Only`` checkbox.
8. Compile the game.
To get more informations about the game's cutscene system/camera system, read [zcamedit's readme](https://github.com/sauraen/zcamedit#setting-up-a-cutscene)
Note: a "cutscene terminator" is a cutscene command that makes a scene transition. For example, this is used in the intro cutscene or in the credits.
<details closed>
<summary>Armature Data Properties Panel</summary>
<img src="/images/oot_armature_data_properties.png" width=500/>
@@ -171,3 +179,9 @@ Note that you can have the actual address of your cutscene if you use ``sym_info
- Result: ``Symbol gHyruleFieldIntroCs (RAM: 0x02013AA0, ROM: 0x27E9AA0, build/assets/scenes/overworld/spot00/spot00_scene.o)``
If you have a softlock in-game then you probably did something wrong when creating the cutscene. Make sure you set up the bones properly. The softlock means the game is playing a cutscene but it's probably reading wrong data. Make sure the cutscene is exported, if it's not export it again.
If the camera preview in Blender isn't following where you have the bones or if the cutscene sort of works in-game but the positions are all wrong:
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.
+18
View File
@@ -37,6 +37,12 @@ from .animation.properties import (
from .cutscene.operators import cutscene_ops_register, cutscene_ops_unregister
from .cutscene.properties import cutscene_props_register, cutscene_props_unregister
from .cutscene.panels import cutscene_panels_register, cutscene_panels_unregister
from .cutscene.preview import cutscene_preview_register, cutscene_preview_unregister
from .cutscene.motion.operators import csMotion_ops_register, csMotion_ops_unregister
from .cutscene.motion.properties import csMotion_props_register, csMotion_props_unregister
from .cutscene.motion.panels import csMotion_panels_register, csMotion_panels_unregister
from .cutscene.motion.preview import csMotion_preview_register, csMotion_preview_unregister
from .skeleton.operators import skeleton_ops_register, skeleton_ops_unregister
from .skeleton.properties import skeleton_props_register, skeleton_props_unregister
@@ -121,6 +127,12 @@ def oot_register(registerPanels):
file_register()
anim_props_register()
csMotion_ops_register()
csMotion_props_register()
csMotion_panels_register()
csMotion_preview_register()
cutscene_preview_register()
for cls in oot_classes:
register_class(cls)
@@ -154,5 +166,11 @@ def oot_unregister(unregisterPanels):
file_unregister()
anim_props_unregister()
cutscene_preview_unregister()
csMotion_preview_unregister()
csMotion_panels_unregister()
csMotion_props_unregister()
csMotion_ops_unregister()
if unregisterPanels:
oot_panel_unregister()
+37 -13
View File
@@ -2,7 +2,8 @@ from bpy.types import Object, PropertyGroup, UILayout
from bpy.utils import register_class, unregister_class
from bpy.props import EnumProperty, StringProperty, IntProperty, BoolProperty, CollectionProperty, PointerProperty
from ...utility import prop_split, label_split
from ..oot_constants import ootData, ootEnumSceneSetupPreset, ootEnumCamTransition
from ..oot_constants import ootData, ootEnumCamTransition
from ..oot_upgrade import upgradeActors
from ..scene.properties import OOTAlternateSceneHeaderProperty
from ..room.properties import OOTAlternateRoomHeaderProperty
from .operators import OOT_SearchActorIDEnumOperator
@@ -15,6 +16,12 @@ from ..oot_utility import (
drawEnumWithCustom,
)
ootEnumSceneSetupPreset = [
("Custom", "Custom", "Custom"),
("All Scene Setups", "All Scene Setups", "All Scene Setups"),
("All Non-Cutscene Scene Setups", "All Non-Cutscene Scene Setups", "All Non-Cutscene Scene Setups"),
]
class OOTActorHeaderItemProperty(PropertyGroup):
headerIndex: IntProperty(name="Scene Setup", min=4, default=4)
@@ -110,6 +117,11 @@ class OOTActorProperty(PropertyGroup):
rotOverrideZ: StringProperty(name="Rot Z", default="0")
headerSettings: PointerProperty(type=OOTActorHeaderProperty)
@staticmethod
def upgrade_object(obj: Object):
print(f"Processing '{obj.name}'...")
upgradeActors(obj)
def draw_props(self, layout: UILayout, altRoomProp: OOTAlternateRoomHeaderProperty, objName: str):
# prop_split(layout, actorProp, 'actorID', 'Actor')
actorIDBox = layout.column()
@@ -145,15 +157,19 @@ class OOTActorProperty(PropertyGroup):
class OOTTransitionActorProperty(PropertyGroup):
roomIndex: IntProperty(min=0)
fromRoom: PointerProperty(type=Object, poll=lambda self, object: self.isRoomEmptyObject(object))
toRoom: PointerProperty(type=Object, poll=lambda self, object: self.isRoomEmptyObject(object))
cameraTransitionFront: EnumProperty(items=ootEnumCamTransition, default="0x00")
cameraTransitionFrontCustom: StringProperty(default="0x00")
cameraTransitionBack: EnumProperty(items=ootEnumCamTransition, default="0x00")
cameraTransitionBackCustom: StringProperty(default="0x00")
dontTransition: BoolProperty(default=False)
isRoomTransition: BoolProperty(name="Is Room Transition", default=True)
actor: PointerProperty(type=OOTActorProperty)
def isRoomEmptyObject(self, obj: Object):
return obj.type == "EMPTY" and obj.ootEmptyType == "Room"
def draw_props(
self, layout: UILayout, altSceneProp: OOTAlternateSceneHeaderProperty, roomObj: Object, objName: str
):
@@ -175,10 +191,12 @@ class OOTTransitionActorProperty(PropertyGroup):
if roomObj is None:
actorIDBox.label(text="This must be part of a Room empty's hierarchy.", icon="OUTLINER")
else:
actorIDBox.prop(self, "dontTransition")
if not self.dontTransition:
label_split(actorIDBox, "Room To Transition From", str(roomObj.ootRoomHeader.roomIndex))
prop_split(actorIDBox, self, "roomIndex", "Room To Transition To")
actorIDBox.prop(self, "isRoomTransition")
if self.isRoomTransition:
prop_split(actorIDBox, self, "fromRoom", "Room To Transition From")
prop_split(actorIDBox, self, "toRoom", "Room To Transition To")
if self.fromRoom == self.toRoom:
actorIDBox.label(text="Warning: You selected the same room!", icon="ERROR")
actorIDBox.label(text='Y+ side of door faces toward the "from" room.', icon="ORIENTATION_NORMAL")
drawEnumWithCustom(actorIDBox, self, "cameraTransitionFront", "Camera Transition Front", "")
drawEnumWithCustom(actorIDBox, self, "cameraTransitionBack", "Camera Transition Back", "")
@@ -188,23 +206,29 @@ class OOTTransitionActorProperty(PropertyGroup):
class OOTEntranceProperty(PropertyGroup):
# This is also used in entrance list, and roomIndex is obtained from the room this empty is parented to.
# This is also used in entrance list.
spawnIndex: IntProperty(min=0)
customActor: BoolProperty(name="Use Custom Actor")
actor: PointerProperty(type=OOTActorProperty)
tiedRoom: PointerProperty(
type=Object,
poll=lambda self, object: self.isRoomEmptyObject(object),
description="Used to set the room index",
)
def isRoomEmptyObject(self, obj: Object):
return obj.type == "EMPTY" and obj.ootEmptyType == "Room"
def draw_props(self, layout: UILayout, obj: Object, altSceneProp: OOTAlternateSceneHeaderProperty, objName: str):
box = layout.column()
# box.box().label(text = "Properties")
roomObj = getRoomObj(obj)
if roomObj is not None:
split = box.split(factor=0.5)
split.label(text="Room Index")
split.label(text=str(roomObj.ootRoomHeader.roomIndex))
else:
if roomObj is None:
box.label(text="This must be part of a Room empty's hierarchy.", icon="OUTLINER")
entranceProp = obj.ootEntranceProperty
prop_split(box, entranceProp, "tiedRoom", "Room")
prop_split(box, entranceProp, "spawnIndex", "Spawn Index")
prop_split(box, entranceProp.actor, "actorParam", "Actor Param")
box.prop(entranceProp, "customActor")
@@ -0,0 +1,92 @@
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
@@ -0,0 +1 @@
from .functions import getCutsceneMotionData
@@ -0,0 +1,272 @@
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
@@ -0,0 +1,46 @@
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,
)
@@ -0,0 +1 @@
from .functions import importCutsceneData
@@ -0,0 +1,559 @@
import bpy
from dataclasses import dataclass
from struct import unpack
from bpy.types import Object, Armature
from .....utility import PluginError
from ....oot_constants import ootData
from ..utility import setupCutscene, getBlenderPosition, getRotation
from ..constants import (
ootCSMotionLegacyToNewCmdNames,
ootCSMotionListCommands,
ootCSMotionCSCommands,
ootCSMotionListEntryCommands,
ootCSMotionSingleCommands,
ootCSMotionListAndSingleCommands,
)
from ..io_classes import (
OOTCSMotionActorCueList,
OOTCSMotionActorCue,
OOTCSMotionCamEyeSpline,
OOTCSMotionCamATSpline,
OOTCSMotionCamEyeSplineRelToPlayer,
OOTCSMotionCamATSplineRelToPlayer,
OOTCSMotionCamEye,
OOTCSMotionCamAT,
OOTCSMotionCamPoint,
OOTCSMotionCutscene,
OOTCSMotionObjectFactory,
OOTCSMotionMisc,
OOTCSMotionMiscList,
OOTCSMotionTransition,
)
@dataclass
class ParsedCutscene:
"""Local class used to order the parsed cutscene properly"""
csName: str
csData: list[str] # contains every command lists or standalone ones like ``CS_TRANSITION()``
class OOTCSMotionImportCommands:
"""This class contains functions to create the cutscene dataclasses"""
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:
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
def getParsedCutscenes(self):
"""Returns the parsed commands read from every cutscene we can find"""
fileData = ""
if self.fileData is not None:
fileData = self.fileData
elif self.filePath is not None:
with open(self.filePath, "r") as inputFile:
fileData = inputFile.read()
else:
raise PluginError("ERROR: File data can't be found!")
# replace old names
oldNames = list(ootCSMotionLegacyToNewCmdNames.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]}(")
# parse cutscenes
fileLines = fileData.split("\n")
csData = []
cutsceneList: list[list[str]] = []
foundCutscene = False
for line in fileLines:
if not line.startswith("//") and not line.startswith("/*"):
if "CutsceneData " in line:
foundCutscene = True
if foundCutscene:
sLine = line.strip()
if not sLine.endswith("),") and sLine.endswith(","):
line += fileLines[fileLines.index(line) + 1].strip()
if len(csData) == 0 or "CS_" in line:
csData.append(line)
if "};" in line:
foundCutscene = False
cutsceneList.append(csData)
csData = []
if len(cutsceneList) == 0:
print("INFO: Found no cutscenes in this file!")
return None
# parse the commands from every cutscene we found
parsedCutscenes: list[ParsedCutscene] = []
for cutscene in cutsceneList:
cmdListFound = False
curCmdPrefix = None
parsedCS = []
parsedData = ""
csName = None
for line in cutscene:
curCmd = line.strip().split("(")[0]
index = cutscene.index(line) + 1
nextCmd = cutscene[index].strip().split("(")[0] if index < len(cutscene) else None
line = line.strip()
if "CutsceneData" in line:
csName = line.split(" ")[1][:-2]
# 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:
line = line.removesuffix(",") + "\n"
if curCmd in ootCSMotionSingleCommands and curCmd != "CS_END":
parsedData += line
if not cmdListFound and curCmd in ootCSMotionListCommands:
cmdListFound = True
parsedData = ""
# camera and lighting have "non-standard" list names
if curCmd.startswith("CS_CAM"):
curCmdPrefix = "CS_CAM"
elif curCmd.startswith("CS_LIGHT"):
curCmdPrefix = "CS_LIGHT"
else:
curCmdPrefix = curCmd[:-5]
if curCmdPrefix is not None:
if curCmdPrefix in curCmd:
parsedData += line
elif not cmdListFound and curCmd in ootCSMotionListEntryCommands:
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:
cmdListFound = False
parsedCS.append(parsedData)
parsedData = ""
elif not "CutsceneData" in curCmd and not "};" in curCmd:
print(f"WARNING: Unknown command found: ``{curCmd}``")
cmdListFound = False
parsedCutscenes.append(ParsedCutscene(csName, parsedCS))
return parsedCutscenes
def getCutsceneList(self):
"""Returns the list of cutscenes with the data processed"""
parsedCutscenes = self.getParsedCutscenes()
if parsedCutscenes is None:
# 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"),
]
# for each cutscene from the list returned by getParsedCutscenes(),
# create classes containing the cutscene's informations
# that will be used later when creating Blender objects to complete the import
for parsedCS in parsedCutscenes:
cutscene = None
for data in parsedCS.csData:
# create a new cutscene data
if "CS_BEGIN_CUTSCENE(" in data:
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)
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)
cmdList.append(commandData)
# 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):
"""Creates the objects from the Actor Cue List data"""
cueObjList = []
cueEndFrames = []
for i, entry in enumerate(actorCueList, 1):
if len(entry.entries) == 0:
raise PluginError("ERROR: Actor Cue List does not have any Actor Cue!")
lastFrame = lastPos = None
actorCueListObj = self.getNewActorCueListObject(
f"CS_{csNbr:02}.{cueName} Cue List {i:02}", entry.commandType, csObj
)
for j, actorCue in enumerate(entry.entries, 1):
if lastFrame is not None and lastFrame != actorCue.startFrame:
raise PluginError("ERROR: Actor Cues are not temporally continuous!")
if lastPos is not None and lastPos != actorCue.startPos:
raise PluginError("ERROR: Actor Cues are not spatially continuous!")
cueObjList.append(
self.getNewActorCueObject(
f"CS_{csNbr:02}.{cueName} Cue {i}.{j:02}",
actorCue.startFrame,
actorCue.actionID,
actorCue.startPos,
actorCue.rot,
actorCueListObj,
)
)
lastFrame = actorCue.endFrame
lastPos = actorCue.endPos
cueEndFrames.append(lastFrame)
# we need a dummy actor cue to get the end position of the last real one
if lastFrame is not None:
cueObjList.append(
self.getNewActorCueObject(
f"CS_{csNbr:02}.{cueName} Cue {i}.999 (D)",
lastFrame,
"DUMMY",
lastPos,
actorCue.rot,
actorCueListObj,
)
)
cueEndFrames.append(lastFrame + 1)
# updating the end frames
if len(cueEndFrames) != len(cueObjList):
raise PluginError("ERROR: Lists lengths do not match!")
for obj, endFrame in zip(cueObjList, cueEndFrames):
# reading this value will trigger the "get" function
getEndFrame = obj.ootCSMotionProperty.actorCueProp.cueEndFrame
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):
"""Safety checks to make sure the camera data is correct"""
camLists: list[tuple[str, list, list]] = [
("Eye and AT Spline", cutscene.camEyeSplineList, cutscene.camATSplineList),
("Eye and AT Spline Rel to Player", cutscene.camEyeSplineRelPlayerList, cutscene.camATSplineRelPlayerList),
("Eye and AT", cutscene.camEyeList, cutscene.camATList),
]
for camType, eyeList, atList in camLists:
for eyeListEntry, atListEntry in zip(eyeList, atList):
eyeTotal = len(eyeListEntry.entries)
atTotal = len(atListEntry.entries)
# Eye -> bone's head, AT -> bone's tail, that's why both lists requires the same length
if eyeTotal != atTotal:
raise PluginError(f"ERROR: Found {eyeTotal} Eye lists but {atTotal} AT lists in {camType}!")
if eyeTotal < 4:
raise PluginError(f"ERROR: Only {eyeTotal} cam point in this command!")
if eyeTotal > 4:
# 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.
del eyeListEntry.entries[-1]
del atListEntry.entries[-1]
def setBoneData(
self, cameraShotObj: Object, boneData: list[tuple[OOTCSMotionCamPoint, OOTCSMotionCamPoint]], csNbr: int
):
"""Creates the bones from the Camera Point data"""
scale = bpy.context.scene.ootBlenderScale
for i, (eyePoint, atPoint) in enumerate(boneData, 1):
# we need the edit mode to be able to change the bone's location
bpy.ops.object.mode_set(mode="EDIT")
armatureData: Armature = cameraShotObj.data
boneName = f"CS_{csNbr:02}.Camera Point {i:02}"
newEditBone = armatureData.edit_bones.new(boneName)
newEditBone.head = getBlenderPosition(eyePoint.pos, scale)
newEditBone.tail = getBlenderPosition(atPoint.pos, scale)
bpy.ops.object.mode_set(mode="OBJECT")
newBone = armatureData.bones[boneName]
if eyePoint.frame != 0:
print("WARNING: Frames must be 0!")
# using the "AT" (look-at) data since this is what determines where the camera is looking
# the "Eye" only sets the location of the camera
newBone.ootCamShotPointProp.shotPointFrame = atPoint.frame
newBone.ootCamShotPointProp.shotPointViewAngle = atPoint.viewAngle
newBone.ootCamShotPointProp.shotPointRoll = atPoint.camRoll
def setCameraShotData(
self, csObj: Object, eyePoints: list, atPoints: list, camMode: str, startIndex: int, csNbr: int
):
"""Creates the armatures from the Camera Shot data"""
endIndex = 0
# this is required to be able to change the object mode
if bpy.context.mode != "OBJECT":
bpy.ops.object.mode_set(mode="OBJECT")
for i, (camEyeSpline, camATSpline) in enumerate(zip(eyePoints, atPoints), startIndex):
cameraShotObj = self.getNewArmatureObject(f"CS_{csNbr:02}.Camera Shot {i:02}", True, csObj)
if camEyeSpline.endFrame < camEyeSpline.startFrame + 2 or camATSpline.endFrame < camATSpline.startFrame + 2:
print("WARNING: Non-standard end frame")
cameraShotObj.data.ootCamShotProp.shotStartFrame = camEyeSpline.startFrame
cameraShotObj.data.ootCamShotProp.shotCamMode = camMode
boneData = [(eyePoint, atPoint) for eyePoint, atPoint in zip(camEyeSpline.entries, camATSpline.entries)]
self.setBoneData(cameraShotObj, boneData, csNbr)
endIndex = i
return endIndex + 1
def setCutsceneData(self, csNumber):
"""Creates the cutscene empty objects from the file data"""
cutsceneList = self.getCutsceneList()
if cutsceneList is None:
# if it's none then there's no cutscene in the file
return csNumber
for i, cutscene in enumerate(cutsceneList, csNumber):
print(f'Found Cutscene "{cutscene.name}"!')
self.validateCameraData(cutscene)
csName = f"Cutscene.{cutscene.name}"
csObj = self.getNewCutsceneObject(csName, cutscene.frameCount, None)
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
)
if len(cutscene.camEyeSplineRelPlayerList) > 0:
lastIndex = self.setCameraShotData(
csObj,
cutscene.camEyeSplineRelPlayerList,
cutscene.camATSplineRelPlayerList,
"splineEyeOrATRelPlayer",
lastIndex,
i,
)
if len(cutscene.camEyeList) > 0:
lastIndex = self.setCameraShotData(
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
# Init camera + preview objects and setup the scene
setupCutscene(csObj)
print("Done!")
bpy.ops.object.select_all(action="DESELECT")
# ``csNumber`` makes sure there's no duplicates
return csNumber + 1
@@ -0,0 +1,10 @@
import bpy
from .classes import OOTCSMotionImport
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)
return csMotionImport.setCutsceneData(bpy.context.scene.ootCSNumber)
@@ -0,0 +1,258 @@
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
@@ -0,0 +1,371 @@
import bpy
from bpy.types import Object, Operator, Context, Armature
from bpy.utils import register_class, unregister_class
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 ..preview import initFirstFrame, setupCompositorNodes
from .utility import (
setupActorCuePreview,
metersToBlend,
getCSMotionValidateObj,
getNameInformations,
createNewBone,
createNewCameraShot,
getCutsceneEndFrame,
)
def getActorCueList(operator: Operator, context: Context) -> Object | None:
cueListObj = activeObj = context.view_layer.objects.active
if activeObj.ootEmptyType == "CS Actor Cue" and activeObj.parent.ootEmptyType == "CS Actor Cue List":
cueListObj = activeObj.parent
if not cueListObj.ootEmptyType == "CS Actor Cue List" and cueListObj.parent.ootEmptyType == "Cutscene":
operator.report({"WARNING"}, "Select an action list or action point.")
return None
return cueListObj
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()
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)
newActorCueListObj.name = f"{csPrefix}.{playerOrActor} Cue List {index:02}"
# add a basic actor cue and the dummy one
for i in range(2):
nameSuffix = f"{i + 1:02}" if i == 0 else "999 (D)"
newActorCueObj = objFactory.getNewActorCueObject(
f"{csPrefix}.{playerOrActor} Cue {index:02}.{nameSuffix}",
i,
"cueid_none" if isPlayer else "0x0000",
[0, 0, 0],
["0x0", "0x0", "0x0"],
newActorCueListObj,
)
# finally, parenting the object to the cutscene
newActorCueListObj.parent = csObj
class OOTCSMotionPlayPreview(Operator):
"""Camera Preview Playback"""
bl_idname = "object.play_preview"
bl_label = "Preview Playback"
def execute(self, context):
try:
csObj = getCSMotionValidateObj(None, None, None)
if csObj is not None:
# get and set the camera
cameraObj = None
for childObj in csObj.children:
if childObj.type == "CAMERA":
cameraObj = childObj
break
# from https://blender.stackexchange.com/a/259103
space = None
for area in context.screen.areas:
if (area != context.area) and (area.type == "VIEW_3D"):
for space in area.spaces:
if space.type == "VIEW_3D":
break
if space is not None:
space.region_3d.view_perspective = "CAMERA"
# setup frame data and play the animation
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
setupCompositorNodes()
initFirstFrame(csObj, bpy.context.scene.ootCSPreviewNodesReady, cameraObj)
bpy.ops.screen.animation_cancel()
bpy.ops.screen.animation_play()
return {"FINISHED"}
except:
return {"CANCELLED"}
class OOTCSMotionAddBone(Operator):
"""Add a bone to an armature"""
bl_idname = "object.add_bone"
bl_label = "Add Bone"
def execute(self, context):
try:
cameraShotObj = getCSMotionValidateObj(None, None, "Camera Shot")
if cameraShotObj is not None:
armatureData: Armature = cameraShotObj.data
cameraShotObj.select_set(True)
lastMode = "EDIT" if "EDIT" in context.mode else context.mode
# create the new bone with standard informations
blendOne = metersToBlend(context, float(1))
boneName = f"CS_??.Camera Point ??"
createNewBone(cameraShotObj, boneName, [blendOne, blendOne, 0.0], [blendOne, 0.0, 0.0])
# update the name of the new bone and move it to the position of the previous last one
bpy.ops.object.mode_set(mode="EDIT")
if len(armatureData.edit_bones) > 0 and "CS_" in armatureData.edit_bones[-2].name:
secondToLastBone = armatureData.edit_bones[-2]
newBone = armatureData.edit_bones[-1]
splitName = secondToLastBone.name.split(" ")
try:
newBone.name = f"{splitName[0]} Point {int(splitName[2]) + 1:02}"
except ValueError:
newBone.name = f"Camera Point 500"
newBone.head = secondToLastBone.head
newBone.tail = secondToLastBone.tail
bpy.ops.object.mode_set(mode="OBJECT") # going back to object mode to update the bones properly
if armatureData.bones[-1].name == boneName:
raise PluginError("ERROR: Something went wrong...")
bpy.ops.object.mode_set(mode=lastMode)
self.report({"INFO"}, "Success!")
return {"FINISHED"}
else:
raise PluginError("You must select an armature object parented to a cutscene empty object!")
except:
return {"CANCELLED"}
class OOTCSMotionAddActorCue(Operator):
"""Add an entry to a player or actor cue list"""
bl_idname = "object.add_actor_cue_point"
bl_label = "Add Actor Cue"
isPlayer: BoolProperty(default=False)
def execute(self, context):
actorCueListObj = getCSMotionValidateObj(None, None, None)
try:
if actorCueListObj is not None:
if actorCueListObj.parent is not None and actorCueListObj.parent.ootEmptyType != "Cutscene":
actorCueListObj = actorCueListObj.parent
# start by creating the new object with basic values
objFactory = OOTCSMotionObjectFactory()
newActorCueObj = objFactory.getNewActorCueObject(
f"New {'Player' if self.isPlayer else 'Actor'} Cue",
0,
"cueid_none" if self.isPlayer else "0x0000",
[0, 0, 0],
["0x0", "0x0", "0x0"],
None,
)
# if there's other actor cues, take the information from the last non-dummy one
if len(actorCueListObj.children) > 0:
dummyCue = None
for i, obj in enumerate(actorCueListObj.children):
if obj.ootEmptyType == "CS Dummy Cue":
dummyCue = obj
if dummyCue is not None:
lastCueObj = actorCueListObj.children[i - 1]
nameSplit = lastCueObj.name.split(".")
try:
index = int(nameSplit[-1]) + 1
except ValueError:
index = i + 500 # huge number to make sure it's going at the end of the list
startFrame = dummyCue.ootCSMotionProperty.actorCueProp.cueStartFrame
newActorCueObj.name = f"{nameSplit[0]}.{nameSplit[1]}.{index:02}"
newActorCueObj.ootCSMotionProperty.actorCueProp.cueStartFrame = startFrame
dummyCue.ootCSMotionProperty.actorCueProp.cueStartFrame += 1
newActorCueObj.location = lastCueObj.location
newActorCueObj.rotation_euler = lastCueObj.rotation_euler
else:
# else create the name from the actor cue list object
nameSplit = actorCueListObj.name.split(".")
try:
index = int(nameSplit[1].split(" ")[3])
except ValueError:
index = 500
newActorCueObj.name = f"{nameSplit[0]}.{nameSplit[1][:-8]} {index}.01"
# add the dummy cue since the list is empty
nameSplit = newActorCueObj.name.split(".")
newDummyCueObj = objFactory.getNewActorCueObject(
f"{nameSplit[0]}.{nameSplit[1]}.999 (D)",
1,
"0x0000",
[0, 0, 0],
["0x0", "0x0", "0x0"],
actorCueListObj,
)
# update the end frame of the real cue
getEndFrame = newActorCueObj.ootCSMotionProperty.actorCueProp.cueEndFrame
newActorCueObj.parent = actorCueListObj
else:
raise PluginError("ERROR: Select the Actor or Player Cue List!")
self.report({"INFO"}, "Success!")
return {"FINISHED"}
except Exception as e:
# print(e)
raise PluginError("ERROR: Something went wrong.")
return {"CANCELLED"}
class OOTCSMotionCreateActorCuePreview(Operator):
"""Create a preview empty object for a player or an actor cue list"""
bl_idname = "object.create_actor_cue_preview"
bl_label = "Create Preview Object"
def execute(self, context):
cueList = getCSMotionValidateObj(None, None, None)
if cueList is not None and cueList.ootEmptyType in ["CS Actor Cue List", "CS Player Cue List"]:
isCueMoving = setupActorCuePreview(
cueList.parent, "Actor" if "Actor" in cueList.ootEmptyType else "Player", True, cueList
)
self.report({"INFO"}, "Success!" if isCueMoving else "Actor Cue don't move, ignored preview.")
return {"FINISHED"}
else:
return {"CANCELLED"}
class OOTCSMotionCreateCameraShot(Operator):
"""Create and initialize a camera shot armature"""
bl_idname = "object.create_camera_shot"
bl_label = "Create Camera Shot"
def execute(self, context):
csObj = getCSMotionValidateObj(None, None, None)
if csObj is not None:
createNewCameraShot(csObj)
self.report({"INFO"}, "Success!")
return {"FINISHED"}
else:
return {"CANCELLED"}
class OOTCSMotionCreatePlayerCueList(Operator):
"""Create a cutscene player cue list"""
bl_idname = "object.create_player_cue_list"
bl_label = "Create Player Cue List"
def execute(self, context):
csObj = getCSMotionValidateObj(None, None, None)
try:
if csObj is not None:
createNewActorCueList(csObj, True)
else:
raise PluginError("ERROR: You must select the cutscene object!")
self.report({"INFO"}, "Success!")
return {"FINISHED"}
except:
return {"CANCELLED"}
class OOTCSMotionCreateActorCueList(Operator):
"""Create a cutscene actor cue list"""
bl_idname = "object.create_actor_cue_list"
bl_label = "Create Actor Cue list"
def execute(self, context):
csObj = getCSMotionValidateObj(None, None, None)
try:
if csObj is not None:
createNewActorCueList(csObj, False)
else:
raise PluginError("ERROR: You must select the cutscene object!")
self.report({"INFO"}, "Success!")
return {"FINISHED"}
except:
return {"CANCELLED"}
class OOT_SearchActorCueCmdTypeEnumOperator(Operator):
bl_idname = "object.oot_search_actorcue_cmdtype_enum_operator"
bl_label = "Select Command Type"
bl_property = "commandType"
bl_options = {"REGISTER", "UNDO"}
commandType: EnumProperty(items=ootEnumCSActorCueListCommandType, default="actor_cue_0_0")
objName: StringProperty()
def execute(self, context):
obj = bpy.data.objects[self.objName]
obj.ootCSMotionProperty.actorCueListProp.commandType = self.commandType
context.region.tag_redraw()
self.report({"INFO"}, "Selected: " + self.commandType)
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.invoke_search_popup(self)
return {"RUNNING_MODAL"}
class OOT_SearchPlayerCueIdEnumOperator(Operator):
bl_idname = "object.oot_search_playercueid_enum_operator"
bl_label = "Select Player Cue ID"
bl_property = "playerCueID"
bl_options = {"REGISTER", "UNDO"}
playerCueID: EnumProperty(items=ootData.enumData.ootEnumCsPlayerCueId, default="cueid_none")
objName: StringProperty()
def execute(self, context):
obj = bpy.data.objects[self.objName]
obj.ootCSMotionProperty.actorCueProp.playerCueID = self.playerCueID
context.region.tag_redraw()
self.report({"INFO"}, "Selected: " + self.playerCueID)
return {"FINISHED"}
def invoke(self, context, event):
context.window_manager.invoke_search_popup(self)
return {"RUNNING_MODAL"}
classes = (
OOTCSMotionPlayPreview,
OOTCSMotionAddBone,
OOTCSMotionAddActorCue,
OOTCSMotionCreateActorCuePreview,
OOTCSMotionCreateCameraShot,
OOTCSMotionCreatePlayerCueList,
OOTCSMotionCreateActorCueList,
OOT_SearchActorCueCmdTypeEnumOperator,
OOT_SearchPlayerCueIdEnumOperator,
)
def csMotion_ops_register():
for cls in classes:
register_class(cls)
def csMotion_ops_unregister():
for cls in reversed(classes):
unregister_class(cls)
@@ -0,0 +1,50 @@
from bpy.types import Panel
from bpy.utils import register_class, unregister_class
from .properties import OOTCSMotionCameraShotProperty, OOTCSMotionCameraShotPointProperty
class OOT_CSMotionCameraShotPanel(Panel):
bl_label = "Cutscene Motion Camera Shot Controls"
bl_idname = "OOT_PT_camera_shot_panel"
bl_space_type = "PROPERTIES"
bl_region_type = "WINDOW"
bl_context = "object"
bl_options = {"HIDE_HEADER"}
def draw(self, context):
obj = context.view_layer.objects.active
layout = self.layout
if obj.type == "ARMATURE":
camShotProp: OOTCSMotionCameraShotProperty = obj.data.ootCamShotProp
camShotPointProp: OOTCSMotionCameraShotPointProperty = None
activeBone = editBone = None
box = layout.box()
camShotProp.draw_props(box, self.bl_label)
if obj.mode == "POSE":
box.label(text="Warning: You can't be in 'Pose' mode to edit camera bones!")
elif obj.mode == "OBJECT":
activeBone = obj.data.bones.active
if activeBone is not None:
camShotPointProp = activeBone.ootCamShotPointProp
camShotPointProp.draw_props(box)
elif obj.mode == "EDIT":
editBone = obj.data.edit_bones.active
if editBone is not None:
camShotPointProp = editBone.ootCamShotPointProp
camShotPointProp.draw_props(box)
classes = (OOT_CSMotionCameraShotPanel,)
def csMotion_panels_register():
for cls in classes:
register_class(cls)
def csMotion_panels_unregister():
for cls in reversed(classes):
unregister_class(cls)
@@ -0,0 +1,220 @@
import bpy
import math
from mathutils import Vector, Quaternion
from bpy.app.handlers import persistent
from bpy.types import Object, Scene
from ....utility import PluginError
from .utility import (
BoneData,
getCameraShotBoneData,
getCSMotionValidateObj,
)
# Eye -> Position, AT -> look-at, where the camera is looking
def getUndefinedCamPosEye():
return (Vector((0.0, 0.0, 0.0)), Quaternion(), 45.0)
def getUndefinedCamPosAT():
return (Vector((0.0, 0.0, 0.0)), Vector((0.0, 0.0, -1.0)), 0.0, 45.0)
def getSplineCoeffs(t: float):
# Reverse engineered from func_800BB0A0 in Debug ROM
t = min(t, 1.0) # no check for t < 0
oneminust = 1.0 - t
tsq = t * t
oneminustcube6 = (oneminust * oneminust * oneminust) / 6.0
tcube = tsq * t
spline2 = ((tcube * 0.5) - tsq) + 0.6666667
spline3 = ((tsq + t - tcube) * 0.5) + 0.16666667
tcube6 = tcube / 6.0
return oneminustcube6, spline2, spline3, tcube6
def getZ64SplineInterpolate(bones: list[BoneData], frame: int):
# Reverse engineered from func_800BB2B4 in Debug ROM
p = 0 # keyframe
t = 0.0 # ratioBetweenPoints
# Simulate cutscene for all frames up to present
for _ in range(frame):
if p + 2 >= len(bones) - 1:
# Camera position is uninitialized
return getUndefinedCamPosAT()
framesPoint1 = bones[p + 1].frame
denomPoint1 = 1.0 / framesPoint1 if framesPoint1 != 0 else 0.0
framesPoint2 = bones[p + 2].frame
denomPoint2 = 1.0 / framesPoint2 if framesPoint2 != 0 else 0.0
dt = max(t * (denomPoint2 - denomPoint1) + denomPoint1, 0.0)
# Different from in game; we remove the extra dummy point at import
# and add it at export.
if t + dt >= 1.0:
if p + 3 == len(bones) - 1:
break
t -= 1.0
p += 1
t += dt
# Spline interpolate for current situation
if p + 3 > len(bones) - 1:
if frame > 0:
print("Internal error in spline algorithm")
return getUndefinedCamPosAT()
s1, s2, s3, s4 = getSplineCoeffs(t)
eye = s1 * bones[p].head + s2 * bones[p + 1].head + s3 * bones[p + 2].head + s4 * bones[p + 3].head
at = s1 * bones[p].tail + s2 * bones[p + 1].tail + s3 * bones[p + 2].tail + s4 * bones[p + 3].tail
roll = s1 * bones[p].roll + s2 * bones[p + 1].roll + s3 * bones[p + 2].roll + s4 * bones[p + 3].roll
viewAngle = (
s1 * bones[p].viewAngle
+ s2 * bones[p + 1].viewAngle
+ s3 * bones[p + 2].viewAngle
+ s4 * bones[p + 3].viewAngle
)
return (eye, at, roll, viewAngle)
def getCmdCamState(shotObj: Object, frame: int):
frame -= shotObj.data.ootCamShotProp.shotStartFrame + 1
if frame < 0:
print(f"Warning, camera command evaluated for frame {frame}")
return getUndefinedCamPosEye()
bones = getCameraShotBoneData(shotObj, False)
if bones is None:
return getUndefinedCamPosEye()
eye, at, roll, viewAngle = getZ64SplineInterpolate(bones, frame)
# TODO handle cam_mode (relativeToLink)
lookvec = at - eye
if lookvec.length < 1e-6:
return getUndefinedCamPosEye()
lookvec.normalize()
ux = Vector((1.0, 0.0, 0.0))
uy = Vector((0.0, 1.0, 0.0))
uz = Vector((0.0, 0.0, 1.0))
qroll = Quaternion(uz, roll * math.pi / 128.0)
qpitch = Quaternion(-ux, math.pi + math.acos(lookvec.dot(uz)))
qyaw = Quaternion(-uz, math.atan2(lookvec.dot(ux), lookvec.dot(uy)))
return (eye, qyaw @ qpitch @ qroll, viewAngle)
def getCutsceneCamState(csObj: Object, frame: int):
"""Returns (pos, rot_quat, viewAngle)"""
shotObjects: list[Object] = []
for childObj in csObj.children:
obj = getCSMotionValidateObj(csObj, childObj, "Camera Shot")
if obj is not None:
shotObjects.append(obj)
shotObjects.sort(key=lambda obj: obj.name)
shotObj = None
if len(shotObjects) > 0:
startFrame = -1
for obj in shotObjects:
if obj.data.ootCamShotProp.shotStartFrame < frame and obj.data.ootCamShotProp.shotStartFrame > startFrame:
shotObj = obj
startFrame = obj.data.ootCamShotProp.shotStartFrame
if shotObj is None or len(shotObjects) == 0:
return getUndefinedCamPosEye()
return getCmdCamState(shotObj, frame)
def getActorCueState(cueListObj: Object, frame: int):
pos = Vector((0.0, 0.0, 0.0))
rot = Vector((0.0, 0.0, 0.0))
cueList: list[Object] = []
for cueObj in cueListObj.children:
obj = getCSMotionValidateObj(None, cueObj, None)
if obj is not None:
cueList.append(obj)
cueList.sort(key=lambda o: o.ootCSMotionProperty.actorCueProp.cueStartFrame)
if len(cueList) >= 2:
for i in range(len(cueList) - 1):
startFrame = cueList[i].ootCSMotionProperty.actorCueProp.cueStartFrame
endFrame = cueList[i + 1].ootCSMotionProperty.actorCueProp.cueStartFrame
if endFrame > startFrame and frame > startFrame:
if frame <= endFrame:
pos = cueList[i].location * (endFrame - frame) + cueList[i + 1].location * (frame - startFrame)
pos /= endFrame - startFrame
rot = cueList[i].rotation_euler
return pos, rot
elif i == len(cueList) - 2:
# If went off the end, use last position
pos = cueList[i + 1].location
rot = cueList[i].rotation_euler
return pos, rot
@persistent
def previewFrameHandler(scene: Scene):
for obj in bpy.data.objects:
parentObj = obj.parent
if (
parentObj is not None
and parentObj.type == "EMPTY"
and parentObj.name.startswith("Cutscene.")
and parentObj.ootEmptyType == "Cutscene"
):
if obj.type == "CAMERA":
pos, rot_quat, viewAngle = getCutsceneCamState(parentObj, scene.frame_current)
if parentObj.ootCutsceneProperty.preview.useWidescreen:
viewAngle *= 4 / 3
if pos is not None:
obj.location = pos
obj.rotation_mode = "QUATERNION"
obj.rotation_quaternion = rot_quat
obj.data.angle = math.pi * viewAngle / 180.0
elif obj.ootEmptyType in ["CS Actor Cue Preview", "CS Player Cue Preview"]:
cueListToPreview = None
if "Actor" in obj.ootEmptyType:
cueListToPreview = obj.ootCSMotionProperty.actorCueListProp.actorCueListToPreview
elif "Player" in obj.ootEmptyType:
cueListToPreview = obj.ootCSMotionProperty.actorCueListProp.playerCueListToPreview
else:
raise PluginError("Unknown Empty Type!")
if cueListToPreview is not None:
pos, rot = getActorCueState(cueListToPreview, scene.frame_current)
if pos is not None:
obj.location = pos
obj.rotation_mode = "XYZ"
obj.rotation_euler = rot
def csMotion_preview_register():
bpy.app.handlers.frame_change_pre.append(previewFrameHandler)
def csMotion_preview_unregister():
if previewFrameHandler in bpy.app.handlers.frame_change_pre:
bpy.app.handlers.frame_change_pre.remove(previewFrameHandler)
@@ -0,0 +1,277 @@
import bpy
from bpy.types import PropertyGroup, Object, UILayout, Armature, Bone, Scene, EditBone
from bpy.props import IntProperty, StringProperty, PointerProperty, EnumProperty, FloatProperty
from bpy.utils import register_class, unregister_class
from ...oot_upgrade import upgradeCutsceneMotion
from ...oot_utility import getEnumName
from ...oot_constants import ootData
from .constants import ootEnumCSMotionCamMode, ootEnumCSActorCueListCommandType
from .operators import (
OOTCSMotionAddActorCue,
OOTCSMotionCreateActorCuePreview,
OOT_SearchActorCueCmdTypeEnumOperator,
OOTCSMotionAddBone,
OOT_SearchPlayerCueIdEnumOperator,
)
def getNextCuesStartFrame(self):
curCueObj = bpy.context.view_layer.objects.active
parentObj = curCueObj.parent
if parentObj is not None and parentObj.ootEmptyType in ["CS Actor Cue List", "CS Player Cue List"]:
if curCueObj.ootEmptyType in ["CS Actor Cue", "CS Player Cue"]:
actorCueObjList = parentObj.children
for i, obj in enumerate(actorCueObjList):
if obj == curCueObj:
return actorCueObjList[i + 1].ootCSMotionProperty.actorCueProp.cueStartFrame
return -1
class OOTCSMotionActorCueListProperty(PropertyGroup):
commandType: EnumProperty(
items=ootEnumCSActorCueListCommandType, name="CS Actor Cue Command Type", default="actor_cue_0_0"
)
commandTypeCustom: StringProperty(name="CS Actor Cue Command Type Custom")
actorCueListToPreview: PointerProperty(
type=Object, name="", poll=lambda self, object: self.isActorCueListObj(object, "CS Actor Cue List")
)
playerCueListToPreview: PointerProperty(
type=Object, name="", poll=lambda self, object: self.isActorCueListObj(object, "CS Player Cue List")
)
def isActorCueListObj(self, obj: Object, emptyType: str):
return obj.type == "EMPTY" and obj.ootEmptyType == emptyType
def draw_props(self, layout: UILayout, isPreview: bool, labelPrefix: str, objName: str):
box = layout.box()
box.box().label(text=f"{labelPrefix} Cue List")
isPlayer = labelPrefix == "Player"
# Player Cue has only one command type
if not isPlayer and not isPreview:
searchBox = box.row()
searchOp = searchBox.operator(
OOT_SearchActorCueCmdTypeEnumOperator.bl_idname, icon="VIEWZOOM", text="Command Type:"
)
searchOp.objName = objName
searchBox.label(text=getEnumName(ootEnumCSActorCueListCommandType, self.commandType))
if self.commandType == "Custom":
split = box.split(factor=0.5)
split.label(text="Command Type Custom:")
split.prop(self, "commandTypeCustom", text="")
if not isPreview:
split = box.split(factor=0.5)
addCueOp = split.operator(OOTCSMotionAddActorCue.bl_idname, text=f"Add {labelPrefix} Cue")
addCueOp.isPlayer = isPlayer
split.operator(OOTCSMotionCreateActorCuePreview.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):
cueStartFrame: IntProperty(name="Start Frame", description="Start frame of the Actor Cue", default=0, min=0)
cueEndFrame: IntProperty(
name="End Frame",
description="End Frame of the Actor Cue, corresponds to the Start Frame of the next Actor Cue",
default=0,
min=-1,
get=lambda self: getNextCuesStartFrame(self),
)
playerCueID: EnumProperty(items=ootData.enumData.ootEnumCsPlayerCueId, default="cueid_none")
cueActionID: StringProperty(
name="Action ID", default="0x0001", description="Actor action. Meaning is unique for each different actor."
)
def draw_props(self, layout: UILayout, labelPrefix: str, isDummy: bool, objName: str):
box = layout.box()
dummyExtra = "(Sets previous Actor Cue's end frame/pos.)" if isDummy else ""
boxBox = box.box()
boxBox.label(text=f"{labelPrefix if not isDummy else 'Dummy'} Cue")
if len(dummyExtra) > 0:
boxBox.label(text=dummyExtra)
if not isDummy and self.cueStartFrame > self.cueEndFrame:
box.label(
text="Warning: Start Frame > End Frame!",
icon="ERROR",
)
split = box.split(factor=0.5)
split.prop(self, "cueStartFrame")
if not isDummy:
label = f"{labelPrefix} Cue (Action) ID:"
isPlayer = labelPrefix == "Player"
split.prop(self, "cueEndFrame")
if isPlayer:
split = box.split(factor=0.5)
searchOp = split.operator(OOT_SearchPlayerCueIdEnumOperator.bl_idname, icon="VIEWZOOM", text=label)
searchOp.objName = objName
split.label(text=getEnumName(ootData.enumData.ootEnumCsPlayerCueId, self.playerCueID))
if not isPlayer or self.playerCueID == "Custom":
split = box.split(factor=0.5)
split.label(text=label)
split.prop(self, "cueActionID", text="")
addCueOp = box.operator(OOTCSMotionAddActorCue.bl_idname, text=f"Add {labelPrefix} Cue")
addCueOp.isPlayer = isPlayer
class OOTCSMotionCameraShotProperty(PropertyGroup):
shotStartFrame: IntProperty(name="Start Frame", description="Shot start frame", default=0, min=0)
# only used as a hint for users
shotEndFrame: IntProperty(
name="End Frame",
description="Shot end frame",
default=0,
min=-1,
get=lambda self: self.getEndFrame(),
)
shotCamMode: EnumProperty(
items=ootEnumCSMotionCamMode,
name="Mode",
description="Camera command mode",
default="splineEyeOrAT",
)
def getEndFrame(self, camShotObj: Object = None):
if camShotObj is None:
camShotObj = bpy.context.view_layer.objects.active
if camShotObj.type == "ARMATURE":
boneFrameList: list[int] = [bone.ootCamShotPointProp.shotPointFrame for bone in camShotObj.data.bones]
# "fake" eye end frame
return self.shotStartFrame + max(2, sum(frame for frame in boneFrameList)) + 1
return -1
def draw_props(self, layout: UILayout, label: str):
box = layout.box()
box.label(text=label)
split = box.split(factor=0.5)
split.prop(self, "shotStartFrame")
split.prop(self, "shotEndFrame")
box.row().prop(self, "shotCamMode", expand=True)
box.operator(OOTCSMotionAddBone.bl_idname)
class OOTCSMotionCameraShotPointProperty(PropertyGroup):
shotPointFrame: IntProperty(
name="Frame",
description="Key point frames value",
default=30,
min=0,
get=lambda self: self.getValue("frame"),
set=lambda self, value: self.setValue(value, "frame"),
)
shotPointViewAngle: FloatProperty(
name="FoV",
description="Field of view (degrees)",
default=60.0,
min=0.01,
max=179.99,
get=lambda self: self.getValue("viewAngle"),
set=lambda self, value: self.setValue(value, "viewAngle"),
)
shotPointRoll: IntProperty(
name="Roll",
description="Camera roll (degrees), positive turns image clockwise",
default=0,
min=-128,
max=127,
get=lambda self: self.getValue("roll"),
set=lambda self, value: self.setValue(value, "roll"),
)
# internal usage only
frame: IntProperty(default=30, min=0)
viewAngle: FloatProperty(default=60.0, min=0.01, max=179.99)
roll: IntProperty(min=-128, max=127, default=0)
def getBoneFromEditBone(self, shotObject: Object, editBone: EditBone) -> Bone | None:
for bone in shotObject.data.bones:
if bone.name == editBone.name:
return bone
else:
print("Could not find corresponding bone")
return None
def getValue(self, propName: str):
return getattr(self, propName)
def setValue(self, value, propName: str):
setattr(self, propName, value)
activeObj = bpy.context.view_layer.objects.active
if activeObj.mode == "EDIT":
bone = self.getBoneFromEditBone(activeObj, activeObj.data.edit_bones.active)
if bone is not None:
setattr(bone.ootCamShotPointProp, propName, value)
activeObj.data.bones.active = bone
def draw_props(self, layout: UILayout):
box = layout.box()
box.label(text="Bone / Key point:")
row = box.row()
for propName in ["shotPointFrame", "shotPointViewAngle", "shotPointRoll"]:
row.prop(self, propName)
class OOTCutsceneMotionProperty(PropertyGroup):
actorCueListProp: PointerProperty(type=OOTCSMotionActorCueListProperty)
actorCueProp: PointerProperty(type=OOTCSMotionActorCueProperty)
@staticmethod
def upgrade_object(csObj: Object):
print(f"Processing '{csObj.name}'...")
upgradeCutsceneMotion(csObj)
print("Done!")
classes = (
OOTCSMotionActorCueListProperty,
OOTCSMotionActorCueProperty,
OOTCSMotionCameraShotProperty,
OOTCSMotionCameraShotPointProperty,
OOTCutsceneMotionProperty,
)
def csMotion_props_register():
for cls in classes:
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",
)
def csMotion_props_unregister():
del Scene.previewPlayerAge
del EditBone.ootCamShotPointProp
del Bone.ootCamShotPointProp
del Armature.ootCamShotProp
del Object.ootCSMotionProperty
for cls in classes:
unregister_class(cls)
@@ -0,0 +1,275 @@
import bpy
from bpy.types import Object, Bone, Context, EditBone, Armature
from mathutils import Vector
from ....utility import yUpToZUp
from ...oot_utility import ootParseRotation
class BoneData:
def __init__(self, shotObj: Object, bone: Bone):
editBone = self.getEditBoneFromBone(shotObj, bone) if shotObj.mode == "EDIT" else None
self.name = bone.name
self.head = editBone.head if editBone is not None else bone.head
self.tail = editBone.tail if editBone is not None else bone.tail
self.frame = (
editBone["shotPointFrame"]
if editBone is not None and "shotPointFrame" in editBone
else bone.ootCamShotPointProp.shotPointFrame
)
self.viewAngle = (
editBone["shotPointViewAngle"]
if editBone is not None and "shotPointViewAngle" in editBone
else bone.ootCamShotPointProp.shotPointViewAngle
)
self.roll = (
editBone["shotPointRoll"]
if editBone is not None and "shotPointRoll" in editBone
else bone.ootCamShotPointProp.shotPointRoll
)
def getEditBoneFromBone(self, shotObj: Object, bone: Bone) -> EditBone | Bone:
for editBone in shotObj.data.edit_bones:
if editBone.name == bone.name:
return editBone
else:
print("Could not find corresponding bone")
return bone
def createNewBone(cameraShotObj: Object, name: str, headPos: list[float], tailPos: list[float]):
if bpy.context.mode != "OBJECT":
bpy.ops.object.mode_set(mode="OBJECT")
bpy.ops.object.mode_set(mode="EDIT")
armatureData: Armature = cameraShotObj.data
newEditBone = armatureData.edit_bones.new(name)
newEditBone.head = headPos
newEditBone.tail = tailPos
bpy.ops.object.mode_set(mode="OBJECT")
newBone = armatureData.bones[name]
newBone.ootCamShotPointProp.shotPointFrame = 30
newBone.ootCamShotPointProp.shotPointViewAngle = 60.0
newBone.ootCamShotPointProp.shotPointRoll = 0
def createNewCameraShot(csObj: Object):
from .io_classes import OOTCSMotionObjectFactory # 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)
# add 4 bones since it's the minimum required
for i in range(1, 5):
posX = metersToBlend(bpy.context, float(i))
createNewBone(
newCameraShotObj,
f"{csPrefix}.Camera Point {i:02}",
[posX, 0.0, 0.0],
[posX, metersToBlend(bpy.context, 1.0), 0.0],
)
def getBlenderPosition(pos: list[int], scale: int):
"""Returns the converted OoT position"""
# OoT: +X right, +Y up, -Z forward
# Blender: +X right, +Z up, +Y forward
return [float(pos[0]) / scale, -float(pos[2]) / scale, float(pos[1]) / scale]
def getRotation(data: str):
"""Returns the rotation converted to hexadecimal"""
if "DEG_TO_BINANG" in data or not "0x" in data:
angle = float(data.split("(")[1].removesuffix(")") if "DEG_TO_BINANG" in data else data)
binang = int(angle * (0x8000 / 180.0)) # from ``DEG_TO_BINANG()`` in decomp
# if the angle value is higher than 0xFFFF it means we're at 360 degrees
return f"0x{0xFFFF if binang > 0xFFFF else binang:04X}"
else:
return data
def getBlenderRotation(rotation: list[str]):
"""Returns the converted OoT rotation"""
rot = [int(getRotation(r), base=16) for r in rotation]
return yUpToZUp @ Vector(ootParseRotation(rot))
def getCSMotionValidateObj(csObj: Object, obj: Object, target: str):
"""Checks if the given object belong to CS Motion and return it if true"""
if csObj is not None and obj.parent != csObj:
return None
if obj is None:
obj = bpy.context.view_layer.objects.active
if target is None or target != "Camera Shot":
nonListTypes = ["CS Player Cue", "CS Actor Cue", "CS Dummy Cue", "Cutscene"]
listTypes = ["CS Player Cue List", "CS Actor Cue List"]
if obj.type == "EMPTY" and obj.ootEmptyType in nonListTypes or obj.ootEmptyType in listTypes:
return obj
elif target is not None and target == "Camera Shot":
# get the camera shot
parentObj = obj.parent
if obj.type == "ARMATURE" and parentObj is not None and parentObj.type == "EMPTY":
return obj
return None
def getNameInformations(csObj: Object, target: str, index: int):
idx = csPrefix = None
csNbr = 0
# get the last target objects names
if csObj.children is not None:
for i, obj in enumerate(csObj.children):
if obj.type == "EMPTY" and "Cue List" in obj.name or "Camera Shot" in obj.name or target in obj.name:
csPrefix = obj.name.split(".")[0]
if target in obj.name:
try:
idx = int(obj.name.split(" ")[-1]) + 1
except ValueError:
pass # idx will stay None
# saving the cutscene number if the target objects can't be found
if csPrefix is None:
for obj in bpy.data.objects:
if obj.type == "EMPTY" and obj.ootEmptyType == "Cutscene":
csNbr += 1
if obj.name == csObj.name:
break
csPrefix = f"CS_{csNbr:02}"
if idx is None:
idx = 1
if index is not None:
idx = index
return idx, csPrefix
def metersToBlend(context: Context, value: float):
return value * 56.0 / context.scene.ootBlenderScale
def setupActorCuePreview(csObj: Object, actorOrPlayer: str, selectObject: bool, cueList: Object):
from .io_classes import OOTCSMotionObjectFactory # circular import fix
# check if the cue actually moves, if not it's not necessary to create a preview object
isCueMoving = False
for i in range(len(cueList.children) - 1):
actorCue = cueList.children[i]
nextCue = cueList.children[i + 1]
curPos = [round(pos) for pos in actorCue.location]
nextPos = [round(pos) for pos in nextCue.location]
if curPos != nextPos:
isCueMoving = True
break
if isCueMoving:
try:
index, csPrefix = getNameInformations(csObj, "Preview", int(cueList.name.split(" ")[-1]))
name = f"{csPrefix}.{actorOrPlayer} Cue Preview {index:02}"
except ValueError:
name = f"{cueList.name} - Preview"
for obj in csObj.children:
if obj.name == name:
previewObj = obj
break
else:
previewObj = OOTCSMotionObjectFactory().getNewActorCuePreviewObject(name, selectObject, csObj)
actorHeight = 1.5
if actorOrPlayer == "Player":
actorHeight = 1.7 if bpy.context.scene.previewPlayerAge == "link_adult" else 1.3
previewObj.empty_display_type = "SINGLE_ARROW"
previewObj.empty_display_size = metersToBlend(bpy.context, actorHeight)
setattr(previewObj.ootCSMotionProperty.actorCueListProp, f"{actorOrPlayer.lower()}CueListToPreview", cueList)
return isCueMoving
def getCameraShotBoneData(shotObj: Object, runChecks: bool):
boneDataList: list[BoneData] = []
for bone in shotObj.data.bones:
if bone.parent is not None:
print("Camera armature bones are not allowed to have parent bones")
return None
boneDataList.append(BoneData(shotObj, bone))
boneDataList.sort(key=lambda b: b.name)
if runChecks:
if boneDataList is None:
raise RuntimeError("Error in bone properties")
if len(boneDataList) < 4:
raise RuntimeError(f"Only {len(boneDataList)} bones in `{shotObj.name}`")
return boneDataList
def getCutsceneEndFrame(csObj: Object):
shotObjects: list[Object] = []
for childObj in csObj.children:
obj = getCSMotionValidateObj(csObj, childObj, "Camera Shot")
if obj is not None:
shotObjects.append(obj)
shotObjects.sort(key=lambda obj: obj.name)
csEndFrame = -1
for shotObj in shotObjects:
# Seems to be the algorithm which was used in the canon tool: the at list
# counts the extra point (same frames as the last real point), and the pos
# list doesn't count the extra point but adds 1. Of course, neither of these
# values is actually the number of frames the camera motion lasts for.
boneDataList = getCameraShotBoneData(shotObj, True)
endFrame = shotObj.data.ootCamShotProp.shotStartFrame + max(2, sum(b.frame for b in boneDataList)) + 2
csEndFrame = max(csEndFrame, endFrame)
return csEndFrame
def setupCutscene(csObj: Object):
from .io_classes import OOTCSMotionObjectFactory # circular import fix
objFactory = OOTCSMotionObjectFactory()
context = bpy.context
bpy.context.scene.ootCSPreviewCSObj = csObj
camObj = objFactory.getNewCameraObject(
f"{csObj.name}.Camera",
metersToBlend(context, 0.25),
metersToBlend(context, 1e-3),
metersToBlend(context, 200.0),
0.95,
csObj,
)
print("Created New Camera!")
# Preview setup, used when importing cutscenes
for obj in csObj.children:
if obj.ootEmptyType in ["CS Actor Cue List", "CS Player Cue List"]:
setupActorCuePreview(csObj, "Actor" if "Actor" in obj.ootEmptyType else "Player", False, obj)
# Other setup
context.scene.frame_start = 0
context.scene.frame_end = max(getCutsceneEndFrame(csObj), context.scene.frame_end)
context.scene.render.fps = 20
context.scene.render.resolution_x = 320
context.scene.render.resolution_y = 240
context.scene.frame_set(context.scene.frame_start)
context.scene.camera = camObj
+112 -9
View File
@@ -1,4 +1,6 @@
import os
import re
from bpy.path import abspath
from bpy.ops import object
from bpy.props import StringProperty, EnumProperty, IntProperty
@@ -9,6 +11,8 @@ 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
def checkGetFilePaths(context: Context):
@@ -56,7 +60,69 @@ def drawCSListAddOp(layout: UILayout, objName: str, collectionType):
row = box.row(align=True)
for _ in range(3):
addButton(row)
box.label(text="Install zcamedit for camera/actor motion.")
def insertCutsceneData(filePath: str, csName: str):
"""Inserts the motion data in the cutscene and returns the new data"""
fileLines = []
includes = ootCutsceneIncludes("").source.split("\n")
# if the file is not found then it's likely a new file that needs to be created
try:
with open(filePath, "r") as inputFile:
fileLines = inputFile.readlines()
fileLines = fileLines[len(includes) - 1 :]
except FileNotFoundError:
fileLines = []
foundCutscene = False
motionExporter = getCutsceneMotionData(csName, False)
beginIndex = 0
for i, line in enumerate(fileLines):
# skip commented lines
if not line.startswith("//") and not line.startswith("/*"):
if f"CutsceneData {csName}" in line:
foundCutscene = True
if foundCutscene:
if "CS_BEGIN_CUTSCENE" in line:
# save the index of the line that contains the entry total and the framecount for later use
beginIndex = i
# looking at next line to see if we reached the end of the cs script
index = i + 1
if index < len(fileLines) and "CS_END" in fileLines[index]:
# exporting first to get the new framecount and the total of entries values
fileLines.insert(index, motionExporter.getExportData())
# update framecount and entry total values
beginLine = fileLines[beginIndex]
reMatch = re.search(r"\b\(([0-9a-fA-F, ]*)\b", beginLine)
if reMatch is not None:
params = reMatch[1].split(", ")
entryTotal = int(params[0], base=0)
frameCount = int(params[1], base=0)
entries = re.sub(
r"\b\(([0-9a-fA-F]*)\b", f"({entryTotal + motionExporter.entryTotal}", beginLine
)
frames = re.sub(r"\b([0-9a-fA-F]*)\)", f"{frameCount + motionExporter.frameCount})", beginLine)
fileLines[beginIndex] = f"{entries.split(', ')[0]}, {frames.split(', ')[1]}"
else:
raise PluginError("ERROR: Can't find `CS_BEGIN_CUTSCENE()` parameters!")
break
fileData = CData()
if not foundCutscene:
print(f"WARNING: Can't find Cutscene ``{csName}``, inserting data at the end of the file.")
motionExporter.addBeginEndCmds = True
csArrayName = f"CutsceneData {csName}[]"
fileLines.append("\n" + csArrayName + " = {\n" + motionExporter.getExportData() + "};\n")
fileData.header = f"{csArrayName};\n"
fileData.source = "".join(line for line in fileLines)
return fileData
class OOTCSTextboxAdd(Operator):
@@ -92,6 +158,26 @@ class OOTCSListAdd(Operator):
return {"FINISHED"}
class OOT_ImportCutscene(Operator):
bl_idname = "object.oot_import_cutscenes"
bl_label = "Import All Cutscenes"
bl_options = {"REGISTER", "UNDO", "PRESET"}
def execute(self, context):
try:
if context.mode != "OBJECT":
object.mode_set(mode="OBJECT")
path = abspath(context.scene.ootCutsceneImportPath)
context.scene.ootCSNumber = importCutsceneData(path, None)
self.report({"INFO"}, "Successfully imported cutscenes")
return {"FINISHED"}
except Exception as e:
raisePluginError(self, e)
return {"CANCELLED"}
class OOT_ExportCutscene(Operator):
bl_idname = "object.oot_export_cutscene"
bl_label = "Export Cutscene"
@@ -113,7 +199,11 @@ class OOT_ExportCutscene(Operator):
cpath, hpath, headerfilename = checkGetFilePaths(context)
csdata = ootCutsceneIncludes(headerfilename)
converted = convertCutsceneObject(activeObj)
csdata.append(ootCutsceneDataToC(converted, converted.name))
if context.scene.exportMotionOnly:
csdata.append(insertCutsceneData(cpath, activeObj.name.removeprefix("Cutscene.")))
else:
csdata.append(ootCutsceneDataToC(converted, converted.name))
writeCData(csdata, hpath, cpath)
self.report({"INFO"}, "Successfully exported cutscene")
@@ -132,19 +222,27 @@ class OOT_ExportAllCutscenes(Operator):
try:
if context.mode != "OBJECT":
object.mode_set(mode="OBJECT")
cpath, hpath, headerfilename = checkGetFilePaths(context)
csdata = ootCutsceneIncludes(headerfilename)
count = 0
for obj in context.view_layer.objects:
if obj.type != "EMPTY" or obj.ootEmptyType != "Cutscene":
continue
if obj.parent is not None:
raise PluginError("Cutscene object must not be parented to anything")
converted = convertCutsceneObject(obj)
csdata.append(ootCutsceneDataToC(converted, converted.name))
count += 1
if obj.type == "EMPTY" and obj.ootEmptyType == "Cutscene":
if obj.parent is not None:
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))
count += 1
if count == 0:
raise PluginError("Could not find any cutscenes to export")
writeCData(csdata, hpath, cpath)
self.report({"INFO"}, "Successfully exported " + str(count) + " cutscenes")
return {"FINISHED"}
@@ -156,6 +254,7 @@ class OOT_ExportAllCutscenes(Operator):
oot_cutscene_classes = (
OOTCSTextboxAdd,
OOTCSListAdd,
OOT_ImportCutscene,
OOT_ExportCutscene,
OOT_ExportAllCutscenes,
)
@@ -166,10 +265,14 @@ def cutscene_ops_register():
register_class(cls)
Scene.ootCutsceneExportPath = StringProperty(name="File", subtype="FILE_PATH")
Scene.ootCutsceneImportPath = StringProperty(name="File", subtype="FILE_PATH")
Scene.ootCSNumber = IntProperty(default=1, min=0)
def cutscene_ops_unregister():
for cls in reversed(oot_cutscene_classes):
unregister_class(cls)
del Scene.ootCSNumber
del Scene.ootCutsceneImportPath
del Scene.ootCutsceneExportPath
+51 -4
View File
@@ -1,7 +1,9 @@
from bpy.utils import register_class, unregister_class
from bpy.types import Scene
from bpy.props import BoolProperty
from ...utility import prop_split
from ...panels import OOT_Panel
from .operators import OOT_ExportCutscene, OOT_ExportAllCutscenes
from .operators import OOT_ExportCutscene, OOT_ExportAllCutscenes, OOT_ImportCutscene
class OOT_CutscenePanel(OOT_Panel):
@@ -12,20 +14,65 @@ class OOT_CutscenePanel(OOT_Panel):
bl_category = "OOT"
def draw(self, context):
col = self.layout.column()
col.operator(OOT_ExportCutscene.bl_idname)
layout = self.layout
exportBox = layout.box()
exportBox.label(text="Cutscene Exporter")
col = exportBox.column()
if not context.scene.fast64.oot.hackerFeaturesEnabled:
col.prop(context.scene, "useDecompFeatures")
col.prop(context.scene, "exportMotionOnly")
prop_split(exportBox, context.scene, "ootCutsceneExportPath", "Export To")
activeObj = context.view_layer.objects.active
label = None
col = exportBox.column()
colcol = col.column()
if activeObj is None or activeObj.type != "EMPTY" or activeObj.ootEmptyType != "Cutscene":
label = "Select a cutscene object"
if activeObj is not None and activeObj.parent is not None:
label = "Cutscene object must not be parented to anything"
if label is not None:
col.label(text=label)
colcol.enabled = False
colcol.operator(OOT_ExportCutscene.bl_idname)
col.operator(OOT_ExportAllCutscenes.bl_idname)
prop_split(col, context.scene, "ootCutsceneExportPath", "File")
importBox = layout.box()
importBox.label(text="Cutscene Importer")
prop_split(importBox, context.scene, "ootCutsceneImportPath", "Import From")
col = importBox.column()
col.operator(OOT_ImportCutscene.bl_idname)
oot_cutscene_panel_classes = (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=(
"Export everything or only the camera and actor motion data.\n"
+ "This will insert the data into the cutscene."
),
)
for cls in oot_cutscene_panel_classes:
register_class(cls)
def cutscene_panels_unregister():
del Scene.exportMotionOnly
del Scene.useDecompFeatures
for cls in oot_cutscene_panel_classes:
unregister_class(cls)
+278
View File
@@ -0,0 +1,278 @@
import bpy
from math import isclose
from bpy.types import Scene, Object, Node
from bpy.app.handlers import persistent
from ...utility import gammaInverse, hexOrDecInt
def getLerp(max: float, min: float, val: float):
# from ``Environment_LerpWeight()`` in decomp
diff = max - min
ret = None
if diff != 0.0:
ret = 1.0 - (max - val) / diff
if not ret >= 1.0:
return ret
return 1.0
def getColor(value: float) -> float:
"""Returns the value converted in the linear color space"""
return gammaInverse([value / 0xFF, 0.0, 0.0])[0]
def getNode(node: Node, type: str, name: str, location: tuple[float, float]):
"""Returns a compositor node"""
if node is None:
node = bpy.context.scene.node_tree.nodes.new(type)
node.select = False
node.name = node.label = name
node.location = location
return node
def setupCompositorNodes():
"""Creates or re-setups compositor nodes"""
if bpy.app.version < (3, 6, 0):
# Blender 3.6+ is required in order to use Compositor Nodes
return
if not bpy.context.scene.use_nodes:
bpy.context.scene.use_nodes = True
# sets the compositor render mode to "Camera"
space = None
for area in bpy.context.screen.areas:
if (area != bpy.context.area) and (area.type == "VIEW_3D"):
for space in area.spaces:
if space.type == "VIEW_3D":
break
if space is not None and space.shading.use_compositor != "CAMERA":
space.shading.use_compositor = "CAMERA"
# if everything's fine and nodes are already ready to use stop there
if bpy.context.scene.ootCSPreviewNodesReady:
return
# get the existing nodes
nodeTree = bpy.context.scene.node_tree
nodeRenderLayer = nodeComposite = nodeRGBTrans = nodeAlphaOver = nodeRGBMisc = nodeMixRGBMisc = None
for node in nodeTree.nodes.values():
if node.type == "R_LAYERS":
nodeRenderLayer = node
if node.type == "COMPOSITE":
nodeComposite = node
if node.label == "CSTrans_RGB":
nodeRGBTrans = node
if node.label == "CSMisc_RGB":
nodeRGBMisc = node
if node.label == "CSPreview_AlphaOver":
nodeAlphaOver = node
if node.label == "CSMisc_MixRGB":
nodeMixRGBMisc = node
# create or set the data of each nodes
nodeRenderLayer = getNode(nodeRenderLayer, "CompositorNodeRLayers", "CSPreview_RenderLayer", (-500, 0))
nodeRGBMisc = getNode(nodeRGBMisc, "CompositorNodeRGB", "CSMisc_RGB", (-200, -200))
nodeRGBTrans = getNode(nodeRGBTrans, "CompositorNodeRGB", "CSTrans_RGB", (0, -200))
nodeMixRGBMisc = getNode(nodeMixRGBMisc, "CompositorNodeMixRGB", "CSMisc_MixRGB", (0, 0))
nodeAlphaOver = getNode(nodeAlphaOver, "CompositorNodeAlphaOver", "CSPreview_AlphaOver", (200, 0))
nodeComposite = getNode(nodeComposite, "CompositorNodeComposite", "CSPreview_Composite", (400, 0))
# link the nodes together
nodeTree.links.new(nodeMixRGBMisc.inputs[1], nodeRenderLayer.outputs[0])
nodeTree.links.new(nodeMixRGBMisc.inputs[2], nodeRGBMisc.outputs[0])
nodeTree.links.new(nodeAlphaOver.inputs[1], nodeMixRGBMisc.outputs[0])
nodeTree.links.new(nodeAlphaOver.inputs[2], nodeRGBTrans.outputs[0])
nodeTree.links.new(nodeComposite.inputs[0], nodeAlphaOver.outputs[0])
# misc settings
nodeMixRGBMisc.use_alpha = True
nodeMixRGBMisc.blend_type = "COLOR"
bpy.context.scene.ootCSPreviewNodesReady = True
def initFirstFrame(csObj: Object, useNodeFeatures: bool, defaultCam: Object):
# set default values for frame 0
if useNodeFeatures:
color = [0.0, 0.0, 0.0, 0.0]
bpy.context.scene.node_tree.nodes["CSTrans_RGB"].outputs[0].default_value = color
bpy.context.scene.node_tree.nodes["CSMisc_RGB"].outputs[0].default_value = color
csObj.ootCutsceneProperty.preview.trigger = False
csObj.ootCutsceneProperty.preview.isFixedCamSet = False
if defaultCam is not None:
bpy.context.scene.camera = defaultCam
def processCurrentFrame(csObj: Object, curFrame: float, useNodeFeatures: bool, cameraObjects: Object):
"""Execute the actions of each command to create the preview for the current frame"""
# this function was partially adapted from ``z_demo.c``
if curFrame == 0:
initFirstFrame(csObj, useNodeFeatures, cameraObjects[1])
if useNodeFeatures:
previewProp = csObj.ootCutsceneProperty.preview
for transitionCmd in csObj.ootCutsceneProperty.preview.transitionList:
startFrame = transitionCmd.startFrame
endFrame = transitionCmd.endFrame
frameCur = curFrame
isTriggerInstance = transitionCmd.type == "CS_TRANS_TRIGGER_INSTANCE"
linear160 = getColor(160.0)
if transitionCmd.type == "Unknown":
print("ERROR: Unknown command!")
if curFrame == 0:
# makes transitions appear a frame earlier if frame 0
frameCur += 1
if isTriggerInstance and not previewProp.trigger:
color = [linear160, linear160, linear160, 1.0]
bpy.context.scene.node_tree.nodes["CSTrans_RGB"].outputs[0].default_value = color
if frameCur >= startFrame and frameCur <= endFrame:
color = [0.0, 0.0, 0.0, 0.0]
lerp = getLerp(endFrame, startFrame, frameCur)
linear255 = getColor(255.0)
linear155 = getColor(155.0)
if isTriggerInstance:
previewProp.trigger = True
if transitionCmd.type.endswith("IN"):
alpha = linear255 * lerp
else:
alpha = (1.0 - lerp) * linear255
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:
color[0] = color[1] = color[2] = linear160 * alpha
elif "_RED_" in transitionCmd.type:
color[0] = linear255 * alpha
elif "_GREEN_" in transitionCmd.type:
color[1] = linear255 * alpha
elif "_BLUE_" in transitionCmd.type:
color[2] = linear255 * alpha
color[3] = alpha
bpy.context.scene.node_tree.nodes["CSTrans_RGB"].outputs[0].default_value = color
for miscCmd in csObj.ootCutsceneProperty.preview.miscList:
startFrame = miscCmd.startFrame
endFrame = miscCmd.endFrame
if miscCmd.type == "Unknown":
print("ERROR: Unknown command!")
if curFrame == startFrame:
if miscCmd.type == "CS_MISC_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":
# stop the playback and set the frame to 0
bpy.ops.screen.animation_cancel()
bpy.context.scene.frame_set(bpy.context.scene.frame_start)
if curFrame >= startFrame and (curFrame < endFrame or endFrame == startFrame):
if useNodeFeatures:
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":
col = [255.0, 180.0, 100.0]
else:
col = [255.0, 255.0, 254.0]
for i in range(3):
color[i] = getColor(col[i])
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":
color = bpy.context.scene.node_tree.nodes["CSMisc_RGB"].outputs[0].default_value
color[0] = getColor(255.0)
color[1] = color[2] = 0.0
step = 0.05
if curFrame & 8:
if color[3] < 0.20:
color[3] += step
else:
if color[3] > 0.05:
color[3] -= step
bpy.context.scene.node_tree.nodes["CSMisc_RGB"].outputs[0].default_value = color
@persistent
def cutscenePreviewFrameHandler(scene: Scene):
"""Preview frame handler, executes each frame when the cutscene is played"""
csObj: Object = bpy.context.scene.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
foundObj = None
for obj in bpy.data.objects:
if obj.type == "CAMERA" and obj.parent is not None and obj.parent.ootEmptyType in ["Scene", "Room"]:
camPosProp = obj.ootCameraPositionProperty
camTypes = ["CAM_SET_PREREND0", "CAM_SET_PREREND_FIXED"]
if camPosProp.camSType != "Custom" and camPosProp.camSType in camTypes:
foundObj = obj
break
elif camPosProp.camSType == "Custom":
if camPosProp.camSTypeCustom.startswith("0x"):
if hexOrDecInt(camPosProp.camSTypeCustom) == 25:
foundObj = obj
break
elif camPosProp.camSTypeCustom in camTypes:
foundObj = obj
break
if foundObj is not None:
cameraObjects[0] = foundObj
# setup nodes
bpy.context.scene.ootCSPreviewNodesReady = False
setupCompositorNodes()
# 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)
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)
# 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
previewProp.prevFrame = curFrame
def cutscene_preview_register():
bpy.app.handlers.frame_change_pre.append(cutscenePreviewFrameHandler)
def cutscene_preview_unregister():
if cutscenePreviewFrameHandler in bpy.app.handlers.frame_change_pre:
bpy.app.handlers.frame_change_pre.remove(cutscenePreviewFrameHandler)
+70 -1
View File
@@ -1,10 +1,19 @@
from bpy.types import PropertyGroup, Object, UILayout
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 .motion.preview import previewFrameHandler
from .motion.operators import (
OOTCSMotionPlayPreview,
OOTCSMotionCreateCameraShot,
OOTCSMotionCreatePlayerCueList,
OOTCSMotionCreateActorCueList,
)
# Perhaps this should have been called something like OOTCSParentPropertyType,
@@ -219,6 +228,43 @@ class OOTCSListProperty(PropertyGroup):
addOp.objName = objName
class OOTCutsceneCommandBase:
startFrame: IntProperty(min=0)
endFrame: IntProperty(min=0)
class OOTCutsceneTransitionProperty(OOTCutsceneCommandBase, PropertyGroup):
type: StringProperty(default="Unknown")
class OOTCutsceneMiscProperty(OOTCutsceneCommandBase, PropertyGroup):
type: StringProperty(default="Unknown")
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)
trigger: BoolProperty(default=False) # for ``CS_TRANS_TRIGGER_INSTANCE``
isFixedCamSet: BoolProperty(default=False)
prevFrame: IntProperty(default=-1)
nextFrame: IntProperty(default=1)
def updateWidescreen(self, context: Context):
if self.useWidescreen:
context.scene.render.resolution_x = 426
else:
context.scene.render.resolution_x = 320
context.scene.render.resolution_y = 240
# force a refresh of the current frame
previewFrameHandler(context.scene)
class OOTCutsceneProperty(PropertyGroup):
csEndFrame: IntProperty(name="End Frame", min=0, default=100)
csWriteTerminator: BoolProperty(name="Write Terminator (Code Execution)")
@@ -227,7 +273,23 @@ class OOTCutsceneProperty(PropertyGroup):
csTermEnd: IntProperty(name="End Frm", min=0, default=100)
csLists: CollectionProperty(type=OOTCSListProperty, name="Cutscene Lists")
preview: PointerProperty(type=OOTCutscenePreviewProperty)
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 = layout.split(factor=0.5)
split.operator(OOTCSMotionCreateCameraShot.bl_idname, icon="VIEW_CAMERA")
split.operator(OOTCSMotionPlayPreview.bl_idname, icon="RESTRICT_VIEW_OFF")
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:
@@ -250,6 +312,9 @@ classes = (
OOTCS0x09Property,
OOTCSUnkProperty,
OOTCSListProperty,
OOTCutsceneTransitionProperty,
OOTCutsceneMiscProperty,
OOTCutscenePreviewProperty,
OOTCutsceneProperty,
)
@@ -259,9 +324,13 @@ def cutscene_props_register():
register_class(cls)
Object.ootCutsceneProperty = PointerProperty(type=OOTCutsceneProperty)
Scene.ootCSPreviewNodesReady = BoolProperty(default=False)
Scene.ootCSPreviewCSObj = PointerProperty(type=Object)
def cutscene_props_unregister():
del Scene.ootCSPreviewCSObj
del Scene.ootCSPreviewNodesReady
del Object.ootCutsceneProperty
for cls in reversed(classes):
+106
View File
@@ -0,0 +1,106 @@
# Cutscene Documentation
### Dictionnary
- Cutscene "Command/Commands": Cutscene "Commands" are macros that will tell the game what to do in your cutscene. It can be anything from stopping the cutscene at a certain frame, doing scene transitions or even manipulating Actors during the cutscene. The array containing the commands is called a cutscene "script". You can find more informations about each commands below.
- Camera "AT/Eye": AT means "look-at", it defines where the camera is looking, "Eye" is the position of the camera. For a person, "Eye" would be where they are and "AT" the object they are currently looking at. On Blender, the AT is a bone's tail and the Eye is a bone's head.
- "Spline": it's the movement the camera will follow.
- Actor "Cue": This is a system that triggers an Actor's code to make some action. Every Actor that has an implementation for this listens to the channel (defined by the "Actor Cue List" command) and will execute the action depending on the cue's action ID. This system is far from perfect, for instance two actors using the same channel will execute their actions at the same time.
- "Seq": Sequence, it refers to the audio (most of the time it's the background music)
### Commands
More detailed informations and commands' parameters can be found [here](https://github.com/zeldaret/oot/blob/master/include/z64cutscene_commands.h)
- ``CS_BEGIN_CUTSCENE``: defines the beginning of a cutscene script
- ``CS_END``: defines the end of a cutscene script
- ``CS_CAM_POINT``: defines a single camera point, it can be used with any of the "eye" or "at" camera commands
- ``CS_CAM_EYE``: defines a single eye point, this feature is not used in the final game and lacks polish
- ``CS_CAM_EYE_SPLINE``: declares a list of "eye" camera points that forms a spline
- ``CS_CAM_AT_SPLINE``: declares a list of "at" camera points that forms a spline
- ``CS_CAM_EYE_SPLINE_REL_TO_PLAYER`` and ``CS_CAM_AT_SPLINE_REL_TO_PLAYER``: same as the 2 above except these are relative to the player's position and yaw.
- ``CS_MISC_LIST``: declares a list of various miscellaneous commands, they're all self-explanatory, you can find the list [here](https://github.com/zeldaret/oot/blob/master/include/z64cutscene.h#L167-L204)
- ``CS_MISC``: defines a single misc command
- ``CS_LIGHT_SETTING_LIST``: declares a list of light settings commands
- ``CS_LIGHT_SETTING``: changes the light settings to the specified index, the lighting is defined in the scene
- ``CS_RUMBLE_CONTROLLER_LIST``: declares a list of controller rumble settings
- ``CS_RUMBLE_CONTROLLER``: makes the controller rumble, you can control the duration and the strength
- ``CS_ACTOR_CUE_LIST`` and ``CS_PLAYER_CUE_LIST``: declares an actor cue list, this also defines the channel to use (except for player cues)
- ``CS_ACTOR_CUE`` and ``CS_PLAYER_CUE``: defines the action to execute at the desired frame, also defines the position/rotation
- ``CS_TEXT_LIST``: declares a list of textboxes to display on-screen, when a textbox is present the cutscene won't continue until it's closed if the textbox isn't closing automatically
- ``CS_TEXT``: starts a textbox at the desired frame
- ``CS_TEXT_NONE``:
- ``CS_TEXT_OCARINA_ACTION``: defines an ocarina action, this is used when learning new songs during a cutscene
- ``CS_TRANSITION``: fills the screen with a single color to create transitions, "half" types are used in the intro cutscene to make that "night" effect in Link's house, "trigger instance" is used in the intro part where the Deku Tree talks to Navi to keep the screen white for the duration of the command
- ``CS_START_SEQ_LIST``: declares a list of ``CS_START_SEQ`` commands
- ``CS_START_SEQ``: starts a sequence
- ``CS_STOP_SEQ_LIST``: declares a list of ``CS_STOP_SEQ`` commands
- ``CS_STOP_SEQ``: stops a sequence
- ``CS_FADE_OUT_SEQ_LIST``: declares a list of ``CS_FADE_OUT_SEQ`` commands
- ``CS_FADE_OUT_SEQ``: fades out a sequence player on the specified frame range
- ``CS_TIME_LIST``: declares a list of ``CS_TIME`` commands
- ``CS_TIME``: changes the time of day to the specified hour and minute
- ``CS_DESTINATION``: defines a scene change (a new destination), specific destination types are doing other actions like changing the player's age or setting flags
### Cutscene Motion with Fast64
Fast64 has an implementation for creating camera shots and actor cues.
- A camera shot is defined by an Armature object and the camera motion is defined by the armature's bones. Due to the game's spline algorithm for camera motion, you always need one more key point at each end, to define how the camera is moving when it approaches the last normal point. So, the minimum number of bones in the armature is 4, if you want the camera to move between the positions indicated by bones 2 to 3.
When the shot / armature is selected, in the Object Properties pane there are controls for the start frame of that shot and whether it's normal, relative to Link, or the single Eye/AT point mode. When a particular key point / bone is selected, you have controls for the number of frames, view angle (FoV), and roll of the camera at that position.
At export, camera shots are sorted by name, so you should name them with something they will be in the correct order with at export (e.g. Shot01, Shot02, etc.). The bones / key frames are also sorted by name, so their names must be in the order you want the motion to have. These should both be previewed correctly (i.e. if it looks right in Blender, it should work right in game)
When you add a new bone by duplicating the last bone in the sequence, you must switch out of edit mode and back in for the previewer to properly handle that new bone. This only needs to be done after adding bones; you can preview while editing bones normally. This is due to how Blender represents bones differently in edit mode vs. object mode.
- An Actor Cue list is defined by an empty object and child objects. The list object lets you control which the "command type" (defines the channel to use) and each child object defines the action ID. Those child objects are called "Actor Cue Point". Each cue's ending frame is defined by the next cue's starting frame. The next one also defines the end position of the previous cue, this is the design made by the original OoT devs that's why it can look a bit weird at first glance.
### Details about the camera
Additional informations about the camera, you can skip reading this. This is ported from [zcamedit's readme](https://github.com/sauraen/zcamedit#details).
The camera system in game is weird, this is partly why the previewer exists. If the previewer is not behaving as you expect, it's probably working correctly! (Of course if the behavior differs between Blender and in-game, please report a bug.)
First of all, the system is based on four-point spline interpolation. This means, in the simplest case you have four values A-B-C-D, and the output changes from B to C over the duration, except with the initial trajectory based on A-B and with the final trajectory based on C-D so you get a nice curve. This is used separately to interpolate eye (camera position) and at (target look-at position) as well as camera roll and view angle. If you have more values (with the caveats below), the system will move through all the values except the start and end values. So basically you need an extra camera point at the beginning and at the end to set how the camera is starting and stopping.
Now, the game's version of this is weird for two reasons:
<details closed>
<summary>1. Continue flag checking bug</summary>
If you don't care about the coding and just want to make cutscenes, you don't have to worry about this, Fast64 takes care of it at import/export. Just make sure every cutscene command has at least 4 key points (bones).
There is a bug (in the actual game) where when incrementing to the next set of key points, the key point which is checked for whether it's the last point or not is the last point of the new set, not the last point of the old set. This means that you always need an additional extra point at the end (except for the case of exactly four points, see below). This is in addition to the extra point at the end (and the one at the beginning) which are used for the spline interpolation to set how the camera behaves at the start or the end. No data whatsoever is read from this second extra point (except for the flag that it's the last point, which is set up automatically on export).
For the case of 4 points, the camera motion from B to C works correctly, but when it gets to C, it reads the continue flag out of bounds (which will be an unspecified value). In most cases that byte won't be 0xFF, which means that on the following frame it will take the case for 1/2/3 points, and not initialize the camera position values at all, potentially leading to garbage values being used for them.
So in summary:
- Command has 0 points: Will fail to export, but probably crash in game
- Command has 1/2/3 points: Command will immediately end; the position and look will be uninitialized values, whatever was last on the stack (may cause a floating-point exception)
- Command has 4 points: Works, but don't let the cutscene get to the end of this command
- Command has 5 points: Works as if it had 4 points
- Command has 6 points: Works as if it had 5 points
- Etc.
Fast64 will automatically add this second extra point at the end on export, and also automatically remove the extra point at the end on import unless the command has only four points.
</details>
<details closed>
<summary>2. Frames interpolation</summary>
The number of frames actually spent between key points is interpolated in reciprocals and in a varying way between the key points. This makes predicting the number of frames actually spent extremely difficult unless the frames values are the same. In fact it's so difficult that this plugin actually just simulates the cutscene frame-by-frame up to the current frame every time the Blender frame changes, because solving for the position at some future time without actually stepping through all the frames up to there is too hard.
Note: It's a discretized differential equation, if time was continuous, i.e. the frame rate was infinite, it could be solved with calculus, but since it moves in discrete steps at the frames, even the calculus solution would be only approximate. On top of that, when it changes from going between B-C and going between C-D, the initial position near C depends on what happened at B, and so on.
You can think of it as it will spend *about* ``N frames`` around each key point. So, if the camera moves from point B to C but B has a larger ``frames`` value than C, the camera will move more slowly near B and more quickly near C. Also, a value of 0 means infinity, not zero, if C has ``frames=0`` the camera will approach C but never reach it.
Only the ``frames`` values of points B and C affect the result when the camera is between B and C. So, the ``frames`` values of the one extra points at the beginning and the end (in this case A and D) can be arbitrary.
The actual algorithm is:
- Compute the increment in ``t`` value (percentage of the way from point B to C) at point B by 1 / ``B.frames``, or 0 if ``B.frames`` is 0
- Compute the increment in ``t`` value at point C by 1 / ``C.frames`` or 0.
- Linearly interpolate between these based on the current ``t`` value.
- Add this increment to ``t``.
So you can think of it like, if ``B.frames`` is 10 and ``C.frames`` is 30, the camera moves 1/10th of the way from B to C per frame when it's at B, and 1/30th of the way from B to C per frame when it's nearly at C. But, when it's halfway between B and C, it doesn't move 1/20th of the way per frame, it moves (1/10)/2 + (1/30)/2 = 1/15th of the way. And on top of that, it will cross that positional halfway point less than half the total number of frames it actually takes to get from B to C.
</details>
###
There is also an ``endFrame`` parameter in the cutscene data, however it is almost useless, so it is not included as a custom property in the armature. The ``endFrame`` parameter does not end or stop the camera command, running out of key points or another camera command starting does. It's only checked when the camera command starts. In a normal cutscene where time starts from 0 and advances one frame at a time, as long as ``endFrame`` >= start_frame + 2, the command will work the same.
So, this plugin just sets it to a "reasonable value" on export, and just asserted for validity on import. It seems the original developers' tool used the sum of all the points, including the second extra point, as the ``endFrame`` parameter for ``CS_CAM_AT_SPLINE``, and used the sum of all the points without the second extra point, plus 1, for the ``CS_CAM_EYE_SPLINE`` (oh yeah, did I mention they're different?), so Fast64 replicates this behavior.
+2
View File
@@ -14,8 +14,10 @@ class OoT_Data:
"""Contains data related to OoT, like actors or objects"""
def __init__(self):
from .oot_enum_data import OoT_EnumData
from .oot_object_data import OoT_ObjectData
from .oot_actor_data import OoT_ActorData
self.enumData = OoT_EnumData()
self.objectData = OoT_ObjectData()
self.actorData = OoT_ActorData()
+92
View File
@@ -0,0 +1,92 @@
from dataclasses import dataclass, field
from os import path
from .oot_getters import getXMLRoot
from .oot_data import OoT_BaseElement
# Note: "enumData" in this context refers to an OoT Object file (like ``gameplay_keep``)
@dataclass
class OoT_ItemElement(OoT_BaseElement):
pass
@dataclass
class OoT_EnumElement(OoT_BaseElement):
items: list[OoT_ItemElement]
itemByKey: dict[str, OoT_ItemElement] = field(default_factory=dict)
itemByIndex: dict[int, OoT_ItemElement] = field(default_factory=dict)
itemById: dict[int, OoT_ItemElement] = field(default_factory=dict)
def __post_init__(self):
self.itemByKey = {item.key: item for item in self.items}
self.itemByIndex = {item.index: item for item in self.items}
self.itemById = {item.id: item for item in self.items}
class OoT_EnumData:
"""Cutscene and misc enum data"""
def __init__(self):
# general enumData list
self.enumDataList: list[OoT_EnumElement] = []
# Path to the ``EnumData.xml`` file
enumDataXML = path.dirname(path.abspath(__file__)) + "/xml/EnumData.xml"
enumDataRoot = getXMLRoot(enumDataXML)
for enum in enumDataRoot.iterfind("Enum"):
self.enumDataList.append(
OoT_EnumElement(
enum.attrib["ID"],
enum.attrib["Key"],
None,
None,
[
OoT_ItemElement(
item.attrib["ID"],
item.attrib["Key"],
item.attrib["ID"],
int(item.attrib["Index"]),
)
for item in enum
],
)
)
# create list of tuples used by Blender's enum properties
self.deletedEntry = ("None", "(Deleted from the XML)", "None")
self.ootEnumCsCmd: list[tuple[str, str, str]] = []
self.ootEnumCsMiscType: list[tuple[str, str, str]] = []
self.ootEnumCsTextType: list[tuple[str, str, str]] = []
self.ootEnumCsFadeOutSeqPlayer: list[tuple[str, str, str]] = []
self.ootEnumCsTransitionType: list[tuple[str, str, str]] = []
self.ootEnumCsDestination: list[tuple[str, str, str]] = []
self.ootEnumCsPlayerCueId: list[tuple[str, str, str]] = []
self.ootEnumNaviQuestHintType: 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}
for key in self.enumByKey.keys():
setattr(self, "ootEnum" + key[0].upper() + key[1:], self.getOoTEnumData(key))
def getOoTEnumData(self, enumKey: str):
enum = self.enumByKey[enumKey]
firstIndex = min(1, *(item.index for item in enum.items))
lastIndex = max(1, *(item.index for item in enum.items)) + 1
enumData = [self.deletedEntry] * lastIndex
custom = ("Custom", "Custom", "Custom")
for item in enum.items:
if item.index < lastIndex:
identifier = item.key
enumData[item.index] = (identifier, item.name, item.id)
if firstIndex > 0:
enumData[0] = custom
else:
enumData.insert(0, custom)
return enumData
+96 -94
View File
@@ -129,7 +129,7 @@ for each sub element (of <Actor>) mentioned below:
<Type Index="1" Mask="0xF000">
<Item Params="0000"> Large</Item>
<Item Params="1000"> Large, Appears, Clear Flag</Item>
<Item Params="2000"> Boss Keys Chest</Item>
<Item Params="2000"> Boss Key's Chest</Item>
<Item Params="3000"> Large, Falling, Switch Flag</Item>
<Item Params="4000"> Large, Invisible</Item>
<Item Params="5000"> Small</Item>
@@ -489,16 +489,16 @@ Depending on the scene ID, they need certain objects loaded as the first one in
<Item Params="0003"> Waterfall</Item>
<Item Params="0004"> Small Stream</Item>
<Item Params="0005"> Stream</Item>
<Item Params="0006"> Fire Temples Lower Ambient Noise</Item>
<Item Params="0007"> Fire Temples Higher Ambient Noise</Item>
<Item Params="0006"> Fire Temple's Lower Ambient Noise</Item>
<Item Params="0007"> Fire Temple's Higher Ambient Noise</Item>
<Item Params="0008"> Water Dripping (Well)</Item>
<Item Params="0009"> River</Item>
<Item Params="000A"> Market gibberish</Item>
<Item Params="000B"> Decrease current BGM volume</Item>
<Item Params="000D"> Proximity Sarias Song</Item>
<Item Params="000D"> Proximity Saria's Song</Item>
<Item Params="000E"> Howling wind</Item>
<Item Params="000F"> Gurgling</Item>
<Item Params="0010"> Temple of Lights dripping sounds</Item>
<Item Params="0010"> Temple of Light's dripping sounds</Item>
<Item Params="0011"> Low booming-likish sound</Item>
<Item Params="0012"> Quake/Collapse</Item>
<Item Params="0013"> Fairy Fountain</Item>
@@ -911,7 +911,7 @@ ZROT 0x00FF = Gold Skulltula spawn var high byte (See Gold Skulltula Actor 0095)
<Actor Category="ACTORCAT_PROP" ID="ACTOR_EN_LIGHTBOX" Key="en_lightbox" Name="Alpha Large Noisy Stone" ObjectKey="obj_lightbox" Index="106">
<Type Index="1">
<Item Params="0000"> Normal, flashes blue when struck with sword</Item>
<Item Params="0001"> Frickin huge, doesnt flash when struck</Item>
<Item Params="0001"> Frickin' huge, doesn't flash when struck</Item>
<Item Params="0002"> Even bigger?</Item>
<Item Params="0003"> Ridiculously huge?</Item>
<Item Params="0004"> Small size, flashes blue when struck</Item>
@@ -931,12 +931,12 @@ ZROT 0x00FF = Gold Skulltula spawn var high byte (See Gold Skulltula Actor 0095)
<Item Params="0010"> Line Loop</Item>
<Item Params="0020"> Circle Loop</Item>
</Type>
<Property Index="1" Mask="0xF000" Name="Radius" TiedActorTypes="0000,0020"/>
<Property Index="2" Mask="0x0001" Name="Move North"/>
<Property Index="3" Mask="0x0002" Name="Move South"/>
<Property Index="4" Mask="0x0004" Name="Move West"/>
<Property Index="5" Mask="0x0008" Name="Move East"/>
<Property Index="6" Mask="0x0F00" Name="Attack Speed"/>
<Property Index="1" Mask="0xF000" Name="Speed" TiedActorTypes="0000,0020"/>
<Property Index="2" Mask="0x0F00" Name="Radius"/>
<Bool Index="1" Mask="0x0001" Name="Move North"/>
<Bool Index="2" Mask="0x0002" Name="Move South"/>
<Bool Index="3" Mask="0x0004" Name="Move West"/>
<Bool Index="4" Mask="0x0008" Name="Move East"/>
</Actor>
<Actor Category="ACTORCAT_PROP" ID="ACTOR_EN_AROW_TRAP" Key="en_arow_trap" Name="Beta Arrow Trap" Index="109">
</Actor>
@@ -1216,7 +1216,7 @@ ZROT 0x00FF = Gold Skulltula spawn var high byte (See Gold Skulltula Actor 0095)
<Type Index="1">
<Item Params="0000"> Blue Warp and Ruto</Item>
<Item Params="0001"> Leaning Ruto //no collision data, targetable</Item>
<Item Params="0002"> Ruto, First Encounter //Plays cutscene from Jabu-Jabus Belly when you first meet her</Item>
<Item Params="0002"> Ruto, First Encounter //Plays cutscene from Jabu-Jabu's Belly when you first meet her</Item>
<Item Params="0003"> Ruto, after falling down the hole in Jabu-Jabu</Item>
</Type>
</Actor>
@@ -1224,8 +1224,8 @@ ZROT 0x00FF = Gold Skulltula spawn var high byte (See Gold Skulltula Actor 0095)
</Actor>
<Actor Category="ACTORCAT_ENEMY" ID="ACTOR_EN_FD_FIRE" Key="en_fd_fire" Name="Flare Dancer Fire Attack" ObjectKey="obj_gameplay_dangeon_keep" Index="141">
<Type Index="1">
<Item Params="0000"> Flies, lands, dies in a bit //try not to run into it, Linkll catch on fire</Item>
<Item Params="8000"> Flies, lands, creeps towards Link, dies in a bit //its blue, but its still fire</Item>
<Item Params="0000"> Flies, lands, dies in a bit //try not to run into it, Link'll catch on fire</Item>
<Item Params="8000"> Flies, lands, creeps towards Link, dies in a bit //it's blue, but it's still fire</Item>
</Type>
</Actor>
<Actor Category="ACTORCAT_ENEMY" ID="ACTOR_EN_DH" Key="en_dh" Name="Deadhand" ObjectKey="obj_dh" Index="142">
@@ -1732,7 +1732,7 @@ ZROT 0x00FF = Gold Skulltula spawn var high byte (See Gold Skulltula Actor 0095)
<Item Params="0001"> Circular Trap Door Platform //Opens when wrong skull is chosen</Item>
<Item Params="0002"> Gate //Only blocks one-way</Item>
<Item Params="0003"> Skull Top</Item>
<Item Params="0004"> Glitchy graphics, cant see object, behaves like the gate</Item>
<Item Params="0004"> Glitchy graphics, can't see object, behaves like the gate</Item>
</Type>
<Flag Index="1" Mask="0x3F00" Type="Switch"/>
<Notes>+0x3F00 = Switch Flag</Notes>
@@ -1753,7 +1753,7 @@ ZROT 0x00FF = Gold Skulltula spawn var high byte (See Gold Skulltula Actor 0095)
</Actor>
<Actor Category="ACTORCAT_PROP" ID="ACTOR_BG_JYA_COBRA" Key="bg_jya_cobra" Name="Rotating Cobra Mirror" ObjectKey="obj_jya_obj" Index="222">
<Type Index="1" Mask="0x00FF">
<Item Params="0000"> Large shadow //looks like its reproduced, well-done though</Item>
<Item Params="0000"> Large shadow //looks like it's reproduced, well-done though</Item>
<Item Params="0001"> Small Shadow //CRASH?</Item>
<Item Params="0002"> No shadow //CRASH?</Item>
</Type>
@@ -2241,19 +2241,19 @@ ROTZ 0x0001 = Behavior after colliding with Link
<Item Params="0007"> Dark! Narrow! Scary! / Well of Three Features</Item>
<Item Params="0008"> Death Mountain / No passage without a / Royal Decree!</Item>
<Item Params="0009"> Death Mountain Trail</Item>
<Item Params="000A"> Dodongos Cavern / Dont enter without permission!</Item>
<Item Params="000A"> Dodongo's Cavern / Don't enter without permission!</Item>
<Item Params="000B"> Land of the Gorons / Goron City</Item>
<Item Params="000C"> Zoras River / Watch out for swift current / and strong undertow.</Item>
<Item Params="000C"> Zora's River / Watch out for swift current / and strong undertow.</Item>
<Item Params="000D"> The Shadow will yield only to one / with the eye of truth, handed / down in Kakariko Village.</Item>
<Item Params="000E"> Zoras Domain</Item>
<Item Params="000F"> Zoras Fountain / Dont disturb Lord Jabu-Jabu! / King Zora XVI</Item>
<Item Params="0010"> Forest Training Center / Dont recklessly cut signs / read them carefully!</Item>
<Item Params="000E"> Zora's Domain</Item>
<Item Params="000F"> Zora's Fountain / Don't disturb Lord Jabu-Jabu! / King Zora XVI</Item>
<Item Params="0010"> Forest Training Center / Don't recklessly cut signs / read them carefully!</Item>
<Item Params="0011"> All those reckless enough to / venture into the desertplease drop by our shop. / Carpet Merchant</Item>
<Item Params="0012"> Just ahead: / Great Deku Trees Meadow</Item>
<Item Params="0012"> Just ahead: / Great Deku Tree's Meadow</Item>
<Item Params="0013"> Forest Temple</Item>
<Item Params="0014"> The Lost Woods</Item>
<Item Params="0015"> Talon and Malons / Lon Lon Ranch</Item>
<Item Params="0016"> The Great Ingos / Ingo Ranch</Item>
<Item Params="0015"> Talon and Malon's / Lon Lon Ranch</Item>
<Item Params="0016"> The Great Ingo's / Ingo Ranch</Item>
<Item Params="0017"> Lake Hylia</Item>
<Item Params="0018"> Lakeside Laboratory / Daily trying to get to the bottom / of the mysteries of Lake Hylia! / Lake Scientist</Item>
<Item Params="0019"> Gerudo Valley</Item>
@@ -2262,18 +2262,18 @@ ROTZ 0x0001 = Behavior after colliding with Link
<Item Params="001C"> Haunted Wasteland / If you chase a mirage, the / desert will swallow you. / Only one path is true!</Item>
<Item Params="001D"> Spirit Temple</Item>
<Item Params="001E"> Kokiri Shop / We have original forest goods!</Item>
<Item Params="001F"> LINKs House</Item>
<Item Params="001F"> LINK's House</Item>
<Item Params="0020"> Forest folk shall not leave these woods.</Item>
<Item Params="0021"> Follow the trail along the edge of / the cliff and you will reach / Goron City, home of the Gorons.</Item>
<Item Params="0022"> Natural Wonder / Bomb Flower / Danger! Do not uproot!</Item>
<Item Params="0023"> Death Mountain Summit / Entrance to the crater ahead / Beware of intense heat!</Item>
<Item Params="0024"> King Zoras Throne Room / To hear the Kings royal / proclamations, stand on the / platform and speak to him.</Item>
<Item Params="0024"> King Zora's Throne Room / To hear the King's royal / proclamations, stand on the / platform and speak to him.</Item>
<Item Params="0025"> If you can stop my wild rolling, you might get something great. / Hot Rodder Goron</Item>
<Item Params="0026"> Only one with the eye of truth / will find the stone umbrella / that protects against the / rain of blades.</Item>
<Item Params="0027"> Only one who has sacred feet / can cross the valley of the dead.</Item>
<Item Params="0028"> The record time of those / who raced against me was: / ##&quot;##&quot; / Dampé the Gravekeeper</Item>
<Item Params="0029"> Shooting Gallery / etc.</Item>
<Item Params="002A"> Treasure Chest Shop / We dont necessarily sell them...</Item>
<Item Params="002A"> Treasure Chest Shop / We don't necessarily sell them...</Item>
<Item Params="002B"> High Dive Practice Spot / Are you confident / in your diving skill?</Item>
<Item Params="002C"> 032c</Item>
<Item Params="002D"> Mountain Summit / Danger Ahead - Keep Out</Item>
@@ -2286,18 +2286,18 @@ ROTZ 0x0001 = Behavior after colliding with Link
<Item Params="0034"> Heart-Pounding Gravedigging Tour! / From 18:00 to 21:00 Hyrule Time / Dampé the Gravekeeper</Item>
<Item Params="0035"> Heart-Pounding Gravedigging Tour! / Tours are cancelled until a new / gravekeeper is found. We / apologize for any inconvenience.</Item>
<Item Params="0036"> Thrust Attack Signs! / To thrust with your sword, press / CS toward your target while / Z Targeting, then press B.</Item>
<Item Params="0037"> Hole of “Z” / Lets go through this small / hole! / Stand in front of the hole and / push CS towards it. When the / Action Icon shows “Enter,” press / A to crawl into the hole. / Pay attention to what the Action / Icon says!</Item>
<Item Params="0038"> Cut Grass With Your Sword / If you just swing with B, youll / cut horizontally. If you hold Z as / you swing, youll cut vertically.</Item>
<Item Params="0037"> Hole of “Z” / Let's go through this small / hole! / Stand in front of the hole and / push CS towards it. When the / Action Icon shows “Enter,” press / A to crawl into the hole. / Pay attention to what the Action / Icon says!</Item>
<Item Params="0038"> Cut Grass With Your Sword / If you just swing with B, you'll / cut horizontally. If you hold Z as / you swing, you'll cut vertically.</Item>
<Item Params="0039"> Hyrule Castle / Lon Lon Ranch</Item>
<Item Params="003A"> You are here: Hyrule Castle / This way to Lon Lon Ranch</Item>
<Item Params="003B"> Just Ahead / King Zoras Chamber / Show the proper respect!</Item>
<Item Params="003B"> Just Ahead / King Zora's Chamber / Show the proper respect!</Item>
<Item Params="003C"> House of the Great Mido / Boss of the Kokiri</Item>
<Item Params="003D"> House of the Know-It-All Brothers</Item>
<Item Params="003E"> House of Twins</Item>
<Item Params="003F"> Sarias House</Item>
<Item Params="0040"> View Point with Z Targeting / When you have no object to look / at, you can just look forward / with Z. / Stop moving and then change the / direction you are facing, or hold / down Z for a little while. / This can help you get oriented in / the direction you want to face. / Its quite convenient! / If you hold down Z, you can / walk sideways while facing / straight ahead. / Walking sideways can be a very / important technique in dungeon / corridors. Turn around and try doing this right now.</Item>
<Item Params="0041"> Stepping Stones in the Pond / If you boldly go in the direction / you want to jump, you will leap / automatically. / If you hop around on the stones, / youll become happier!</Item>
<Item Params="0042"> No Diving Allowed / It wont do you any good!</Item>
<Item Params="003F"> Saria's House</Item>
<Item Params="0040"> View Point with Z Targeting / When you have no object to look / at, you can just look forward / with Z. / Stop moving and then change the / direction you are facing, or hold / down Z for a little while. / This can help you get oriented in / the direction you want to face. / It's quite convenient! / If you hold down Z, you can / walk sideways while facing / straight ahead. / Walking sideways can be a very / important technique in dungeon / corridors. Turn around and try doing this right now.</Item>
<Item Params="0041"> Stepping Stones in the Pond / If you boldly go in the direction / you want to jump, you will leap / automatically. / If you hop around on the stones, / you'll become happier!</Item>
<Item Params="0042"> No Diving Allowed / It won't do you any good!</Item>
<Item Params="0043"> Switch Targeting / If you see a \/ icon above an / object, you can target it with Z. / ... / You can target the stones next to this sign for practice!</Item>
<Item Params="0044"> Forest Stage / We are waiting to see your / beautiful face! / Win fabulous prizes!</Item>
<Item Params="0045"> Visit the / House of the Know-It-All Brothers / to get answers to all your / item-related questions!</Item>
@@ -2314,7 +2314,7 @@ ROTZ 0x0001 = Behavior after colliding with Link
<Actor Category="ACTORCAT_PROP" ID="ACTOR_EN_SYATEKI_NIW" Key="en_syateki_niw" Name="Hopping Cucco, Not Solid" ObjectKey="obj_niw" Index="287">
<Type Index="1">
<Item Params="0000"> Non-solid cucco, hops oddly every once in awhile and only goes in one direction</Item>
<Item Params="0001"> Invisible, solid cucco, doesnt move, can be attacked but will only smoke and molt</Item>
<Item Params="0001"> Invisible, solid cucco, doesn't move, can be attacked but will only smoke and molt</Item>
<Item Params="0002"> Invisible cucco, cannot be attacked it seems, no idea what it does, but you can hear it</Item>
</Type>
</Actor>
@@ -2752,16 +2752,16 @@ ROTZ 0x0001 = Behavior after colliding with Link
<Flag Index="1" Mask="0x003F" Type="Switch"/>
<Property Index="1" Mask="0x3FC0" Name="Message ID"/>
<Notes>+0x3FC0 = Message ID //0x0200 + Value
-0000 Hi! Im a talking door! //Unused
-0000 Hi! I'm a talking door! //Unused
-0040 Strange... this door doesn't open...
-0080 Strong iron bars are blocking the door. You cant open them with your hands!
-0080 Strong iron bars are blocking the door. You can't open them with your hands!
-00C0 You need a Key to open a door that is locked or chained.
-0100 You need a special key to open this door.
-0140 Be quiet! Its only time! I, Dampé the Gravekeeper, am in bed now! Go away and play! Maybe you can find a ghost in the daytime?
-0180 Its time now. The Gravedigging Tour is over now! I, Dampé the gravekeeper, am in bed! Go away and play! Maybe youll find a ghost!
-0140 Be quiet! It's only time! I, Dampé the Gravekeeper, am in bed now! Go away and play! Maybe you can find a ghost in the daytime?
-0180 It's time now. The Gravedigging Tour is over now! I, Dampé the gravekeeper, am in bed! Go away and play! Maybe you'll find a ghost!
-01C0 Happy Mask Shop / Please read this sign before you use this shop...
-0200 Shadow Temple... here is gathered Hyrule's bloody history of greed and hatred...
-0240 What is hidden in the darkness... Tricks full of ill will... You cant see the way forward...
-0240 What is hidden in the darkness... Tricks full of ill will... You can't see the way forward...
-0280 One who gains the eye of truth will be able to see what is hidden in the darkness.
-02C0 Something strange is covering the entrance. You must solve the puzzle in this room to make the entrance open.
-0300 Giant dead Dodongo... when it sees red, a new way to go will be open.
@@ -2774,14 +2774,14 @@ ROTZ 0x0001 = Behavior after colliding with Link
-04C0 One with the eye of truth shall be guided to the Spirit Temple by an inviting ghost.
-0500 Those who wish to open the path sleeping at the bottom of the lake must play the song passed down by the Royal Family.
-0540 Those who wish to open the gate on the far heights, play the song passed down by the Royal Family.
-0580 Those who find a Small Key can advance to the next room. Those who dont can go home! //Unused
-0580 Those who find a Small Key can advance to the next room. Those who don't can go home! //Unused
-05C0 If you wish to speak to me, do so from the platform.
-0600 Hi, LINK! Look this way! Look over here with Z, and talk to me with A.
-0640 The current time is: time.
-0680 Shine light on the living dead...
-06C0 Those who break into the Royal Tomb will be obstructed by the lurkers in the dark.
-0700 Hey, you! Young man, over there! Look over here, inside the cell!
-0740 My little boy isnt here right now... I think he went to play in the graveyard...
-0740 My little boy isn't here right now... I think he went to play in the graveyard...
-0780 Oh, my boy is asleep right now. Please come back some other time to play with him!
-07C0 When water fills the lake, shoot for the morning light.
-0800 If you want to travel to the future, you should return here with the power of silver from the past.
@@ -2789,17 +2789,17 @@ ROTZ 0x0001 = Behavior after colliding with Link
-0880 This door is currently being refurbished. //Unused
-08C0 It looks like something used to be set in this stand...
-0900 Make my beak face the skull of truth. The alternative is descent into the deep darkness.
-0940 This is not the correct key... the door wont open!
-0980 Grannys Potion Shop / Closed / Gone for Field Study / Please come again! Granny
-09C0 Whos there? What a bad kid, trying to enter from the rear door! Such a bad kid... I have to tell you some juicy gossip! The boss carpenter has a son... Hes the guy who sits under the tree every night... Dont tell the boss I told you that!
-0940 This is not the correct key... the door won't open!
-0980 Granny's Potion Shop / Closed / Gone for Field Study / Please come again! Granny
-09C0 Who's there? What a bad kid, trying to enter from the rear door! Such a bad kid... I have to tell you some juicy gossip! The boss carpenter has a son... He's the guy who sits under the tree every night... Don't tell the boss I told you that!
-0A00 Look at this!
-0A40 Malons gone to sleep! Im goin to sleep now, too. Come back again when its light out!
-0A80 LINKs Records! / Spiders squished: 0 / Largest fish caught: 0 pounds / Marathon time: 00&quot;00&quot; / Horse race time: 00&quot;00&quot; / Horseback archery: 0 points
-0A40 Malon's gone to sleep! I'm goin' to sleep now, too. Come back again when it's light out!
-0A80 LINK's Records! / Spiders squished: 0 / Largest fish caught: 0 pounds / Marathon time: 00&quot;00&quot; / Horse race time: 00&quot;00&quot; / Horseback archery: 0 points
-0AC0 The crest of the Royal Family is inscribed here.
-0B00 R.I.P. / Here lie the souls of those who swore fealty to the Royal Family of Hyrule / The Sheikah, guardians of the Royal Family and founders of Kakariko, watch over these spirits in their eternal slumber.
-0B40 Sleepless Waterfall / The flow of this waterfall serves the King of Hyrule. When the king slumbers, so too do these falls.
-0B80 Some frogs are looking at you from underwater...
-0BC0 Youre standing on a soft carpet for guests... it feels so plush under your feet!
-0BC0 You're standing on a soft carpet for guests... it feels so plush under your feet!
-0C00 If you can overcome the trials in the chambers ahead, then and only then will you be qualified to hold our secret treasure!
-0C40 If you desire to acquire our hidden treasure, you must strive to obtain the keys hidden in each chamber!
-0C80 Defeat all the enemies in a limited time!
@@ -2818,8 +2818,8 @@ ROTZ 0x0001 = Behavior after colliding with Link
</Actor>
<Actor Category="ACTORCAT_NPC" ID="ACTOR_EN_GE2" Key="en_ge2" Name="Patrolling Gerudo" ObjectKey="obj_gla" Index="350">
<Type Index="1">
<Item Params="0000"> Turns around, cant move, whistle-blower</Item>
<Item Params="0001"> Wont turn around, cant move, whistle-blower</Item>
<Item Params="0000"> Turns around, can't move, whistle-blower</Item>
<Item Params="0001"> Won't turn around, can't move, whistle-blower</Item>
<Item Params="0002"> Purple Gerudo, acts like the one that gives you the membership card</Item>
</Type>
<Property Index="1" Mask="0x0F00" Name="Range of movements (type 0)"/>
@@ -3035,7 +3035,7 @@ ROTZ 0x0001 = Behavior after colliding with Link
<Flag Index="1" Mask="0x003F" Type="Switch"/>
</Actor>
<Actor Category="ACTORCAT_BG" ID="ACTOR_OBJ_KIBAKO2" Key="obj_kibako2" Name="Crate" ObjectKey="obj_kibako2" Index="376">
<Property Index="1" Mask="0xFFFF" Name="Skulltula Variable (Nullable)" Target="Params"/>
<Property Index="1" Mask="0xFFFF" Name="Skulltula Var. (Nullable)" Target="Params"/>
<Collectible Index="1" Mask="0x00FF" Target="XRot" Type="Drop"/>
<Flag Index="1" Mask="0x003F" Target="ZRot" Type="Collectible"/>
<Notes>0xFFFF = Spawned Skulltula Variable (see actor 0095)
@@ -3141,7 +3141,7 @@ Z ROTATION +0x3F = Collectible Flag</Notes>
<Property Index="1" Mask="0x040E" Name="Size"/>
<Notes>//0000 to 1098 controls the size, where 0000 is normal size and 1098 is as large as is possible for the machine to handle (may vary depending on the level).
//From F02F to FFFF, the Stalchild will appear but will somehow invert everything. In other words, it walks upside-down underneath the ground.
//Really weird. FFFF is smallest, F02F is supposedly largest (I havent been able to see it, though).</Notes>
//Really weird. FFFF is smallest, F02F is supposedly largest (I haven't been able to see it, though).</Notes>
</Actor>
<Actor Category="ACTORCAT_PROP" ID="ACTOR_DEMO_GJ" Key="demo_gj" Name="Ganon Battle Rubble" ObjectKey="obj_gj" Index="392">
<Type Index="1" Mask="0x007F">
@@ -3224,15 +3224,15 @@ Z ROTATION +0x3F = Collectible Flag</Notes>
<Item Params="0001"> They say you can swim faster by continuously pressing B.</Item>
<Item Params="0002"> They say there is a secret near the lone tree which is not far from the river in the northwest part of Hyrule Field.</Item>
<Item Params="0003"> They say that there is a secret on the road that leads to Lake Hylia.</Item>
<Item Params="0004"> They say that Biggorons Sword is super sharp and will never break.</Item>
<Item Params="0005"> They say that Medigoron didnt really think about his own size, so his store is really cramped.</Item>
<Item Params="0004"> They say that Biggoron's Sword is super sharp and will never break.</Item>
<Item Params="0005"> They say that Medigoron didn't really think about his own size, so his store is really cramped.</Item>
<Item Params="0006"> They say that Malon set the original record in the obstacle course of Lon Lon Ranch.</Item>
<Item Params="0007"> They say that Malon of Lon Lon Ranch hopes a knight in shining armor will come and sweep her off her feet someday.</Item>
<Item Params="0008"> They say that Ruto, the Zora princess who is known for her selfish nature, likes a certain boy...</Item>
<Item Params="0009"> They say that players who select the “HOLD” option for “Z TARGETING” are the real “Zelda players!”</Item>
<Item Params="000A"> They say that there is a secret near a tree in Kakariko Village.</Item>
<Item Params="000B"> They say that, contrary to her elegant image, Princess Zelda of Hyrule Castle is, in fact, a tomboy!</Item>
<Item Params="000C"> They say that Princess Zeldas nanny is actually one of the Sheikah, who many thought had died out.</Item>
<Item Params="000C"> They say that Princess Zelda's nanny is actually one of the Sheikah, who many thought had died out.</Item>
<Item Params="000D"> They say there is a man who can always be found running around in Hyrule Field.</Item>
<Item Params="000E"> They say that it is against the rules to use glasses at the Treasure Chest Shop in Hyrule Castle Town Market.</Item>
<Item Params="000F"> They say that the chicken lady goes to the Lakeside Laboratory to study how to breed pocket-sized Cuccos.</Item>
@@ -3246,9 +3246,9 @@ Z ROTATION +0x3F = Collectible Flag</Notes>
<Item Params="0017"> They say that strange owl, Kaepora Gaebora, may look big and heavy, but its character is rather lighthearted.</Item>
<Item Params="0018"> They say that the horse Ganondorf rides is a solid black Gerudo stallion.</Item>
<Item Params="0019"> They say that Ganondorf is not satisfied with ruling only the Gerudo and aims to conquer all of Hyrule!</Item>
<Item Params="001A"> They say that the treasure you can earn in the Gerudos Training Ground is not as great as you would expect, given its difficulty!</Item>
<Item Params="001A"> They say that the treasure you can earn in the Gerudo's Training Ground is not as great as you would expect, given its difficulty!</Item>
<Item Params="001B"> They say that there is a switch that you can activate only by using the Spin Attack.</Item>
<Item Params="001C"> They say that its possible to find a total of 100 Gold Skulltulas throughout Hyrule.</Item>
<Item Params="001C"> They say that it's possible to find a total of 100 Gold Skulltulas throughout Hyrule.</Item>
<Item Params="001D"> They say that when non-fairy folk enter the Lost Woods, they become monsters!</Item>
<Item Params="001E"> They say that the small holes in the ground that you can find all over Hyrule make perfect breeding ground for bugs.</Item>
<Item Params="001F"> They say that the Kokiri are always followed by small fairies.</Item>
@@ -3433,56 +3433,56 @@ Z ROTATION +0x3F = Collectible Flag</Notes>
<Flag Index="1" Mask="0x003F" Type="Switch"/>
</Actor>
<List Name="Elf_Msg Message ID">
<Item Key="msg_00" Name="Whats that?" Value="0x00"/>
<Item Key="msg_00" Name="What's that?" Value="0x00"/>
<Item Key="msg_01" Name="Look, look, LINK! You can see down below this web using C-Up!" Value="0x01"/>
<Item Key="msg_02" Name="Look at this wall! The vines growing on it give it a rough surface... Maybe you can climb it, LINK!" Value="0x02"/>
<Item Key="msg_03" Name="You can open a door by standing in front of it and pressing A. Pay attention to what the Action Icon says. Thats the blue icon at the top of the screen!" Value="0x03"/>
<Item Key="msg_03" Name="You can open a door by standing in front of it and pressing A. Pay attention to what the Action Icon says. That's the blue icon at the top of the screen!" Value="0x03"/>
<Item Key="msg_04" Name="Look! Something is hanging up there! It looks like an old ladder!" Value="0x04"/>
<Item Key="msg_05" Name="Hey... isnt that the same design thats on the Door of Time?" Value="0x05"/>
<Item Key="msg_05" Name="Hey... isn't that the same design that's on the Door of Time?" Value="0x05"/>
<Item Key="msg_06" Name="It looks like that torch was burning not too long ago..." Value="0x06"/>
<Item Key="msg_07" Name="From here on, well be going through some narrow passages! If you take it slow, maybe you can sneak up on some enemies." Value="0x07"/>
<Item Key="msg_07" Name="From here on, we'll be going through some narrow passages! If you take it slow, maybe you can sneak up on some enemies." Value="0x07"/>
<Item Key="msg_08" Name="Stand next to this block and grab hold of it with A. While holding A, you can push or pull it. If you stand next to the block and press A while pressing the control stick towards the block, you can climb on top of it. Pay attention to what the Action Icon says!" Value="0x08"/>
<Item Key="msg_0C" Name="After you get into the water, if you hold down A, you can dive! I bet there are some interesting things underwater!" Value="0x0C"/>
<Item Key="msg_14" Name="Wow! Look at all those Bomb Flowers! Is there any way you can set them all off at once?" Value="0x14"/>
<Item Key="msg_15" Name="It looks like there are many lava pits around here, so watch your step!" Value="0x15"/>
<Item Key="msg_16" Name="With that switch on, the moving platform goes even higher. Now you can quickly reach the second floor!" Value="0x16"/>
<Item Key="msg_19" Name="You never know what will be around the corner in these narrow paths... Use Z Targeting to always look in the proper direction. This is a useful technique, isnt it?" Value="0x19"/>
<Item Key="msg_19" Name="You never know what will be around the corner in these narrow paths... Use Z Targeting to always look in the proper direction. This is a useful technique, isn't it?" Value="0x19"/>
<Item Key="msg_1F" Name="LINK, what are you looking at?" Value="0x1F"/>
<Item Key="msg_24" Name="The Desert Colossuss face... it sure looks evil!" Value="0x24"/>
<Item Key="msg_26" Name="I can hear the spirits whispering in this room... “Look for the eye of truth...” Thats what theyre saying!" Value="0x26"/>
<Item Key="msg_28" Name="Here... I can hear the spirits whispering in this room... “Those who have sacred feet should let the wind guide them. Then, they will be lead to the hidden path.” Thats what they are saying!" Value="0x28"/>
<Item Key="msg_29" Name="This wall... it says something here... “Danger above...” Thats what it says." Value="0x29"/>
<Item Key="msg_2A" Name="This wall... it says something here... “Danger below...” Thats what it says." Value="0x2A"/>
<Item Key="msg_24" Name="The Desert Colossus's face... it sure looks evil!" Value="0x24"/>
<Item Key="msg_26" Name="I can hear the spirits whispering in this room... “Look for the eye of truth...” That's what they're saying!" Value="0x26"/>
<Item Key="msg_28" Name="Here... I can hear the spirits whispering in this room... “Those who have sacred feet should let the wind guide them. Then, they will be lead to the hidden path.” That's what they are saying!" Value="0x28"/>
<Item Key="msg_29" Name="This wall... it says something here... “Danger above...” That's what it says." Value="0x29"/>
<Item Key="msg_2A" Name="This wall... it says something here... “Danger below...” That's what it says." Value="0x2A"/>
<Item Key="msg_2B" Name="The water flowing out of this statue is flooding the entire floor." Value="0x2B"/>
<Item Key="msg_2F" Name="Watch out, LINK! Electricity is running through this green slimy thing!" Value="0x2F"/>
<Item Key="msg_31" Name="Watch out, LINK! Electricity is running through this red slimy thing!" Value="0x31"/>
<Item Key="msg_32" Name="Watch out, LINK! Electricity is running through this blue slimy thing!" Value="0x32"/>
<Item Key="msg_33" Name="This switch... It doesn't look like you can press it down with your weight alone, LINK..." Value="0x33"/>
<Item Key="msg_37" Name="The red slimy thing is gone! That must be because you cut the red tail! Will that work with the other ones too?" Value="0x37"/>
<Item Key="msg_39" Name="Theres a switch beyond this wall!" Value="0x39"/>
<Item Key="msg_39" Name="There's a switch beyond this wall!" Value="0x39"/>
<Item Key="msg_3A" Name="It looks like there is something up there on top of the platform!" Value="0x3A"/>
<Item Key="msg_3D" Name="WHAAAT!? Look at all those flags! Can you figure out which ones are real? //Unused?" Value="0x3D"/>
<Item Key="msg_40" Name="The Great Deku Tree has summoned you! Please come with me!" Value="0x40"/>
<Item Key="msg_41" Name="C'mon! Be brave! Let's go into the Deku Tree!" Value="0x41"/>
<Item Key="msg_42" Name="The Great Deku Tree wanted us to go visit the princess at Hyrule Castle... Shouldnt we get going?" Value="0x42"/>
<Item Key="msg_42" Name="The Great Deku Tree wanted us to go visit the princess at Hyrule Castle... Shouldn't we get going?" Value="0x42"/>
<Item Key="msg_43" Name="The girl from the ranch asked us to find her father... I wonder where he is?" Value="0x43"/>
<Item Key="msg_44" Name="I wonder where we'll find the princess in this big old castle?" Value="0x44"/>
<Item Key="msg_45" Name="What would Saria say if we told her were going to save Hyrule?" Value="0x45"/>
<Item Key="msg_45" Name="What would Saria say if we told her we're going to save Hyrule?" Value="0x45"/>
<Item Key="msg_46" Name="Impa said that the Spiritual Stone of Fire is somewhere on Death Mountain." Value="0x46"/>
<Item Key="msg_47" Name="Lets go inside the Dodongo's Cavern using a Bomb Flower!" Value="0x47"/>
<Item Key="msg_48" Name="Darunia said that a fairy lives on top of Death Mountain, didnt he?" Value="0x48"/>
<Item Key="msg_47" Name="Let's go inside the Dodongo's Cavern using a Bomb Flower!" Value="0x47"/>
<Item Key="msg_48" Name="Darunia said that a fairy lives on top of Death Mountain, didn't he?" Value="0x48"/>
<Item Key="msg_49" Name="I wonder if Saria knows anything about the other Spiritual Stone?" Value="0x49"/>
<Item Key="msg_4A" Name="It seems Princess Ruto somehow got inside Jabu-Jabu's belly..." Value="0x4A"/>
<Item Key="msg_4B" Name="You collected three Spiritual Stones! Lets go back to Hyrule Castle!" Value="0x4B"/>
<Item Key="msg_4B" Name="You collected three Spiritual Stones! Let's go back to Hyrule Castle!" Value="0x4B"/>
<Item Key="msg_4C" Name="Those people on the white horse... they were Zelda and Impa, weren't they? It looked like they threw something into the moat!" Value="0x4C"/>
<Item Key="msg_4D" Name="Lets go check inside the Temple of Time." Value="0x4D"/>
<Item Key="msg_4D" Name="Let's go check inside the Temple of Time." Value="0x4D"/>
<Item Key="msg_4E" Name="Should we believe what Sheik said and go to Kakariko Village?" Value="0x4E"/>
<Item Key="msg_50" Name="I wonder whats going on in the forest right now... Im worried about Saria, too!" Value="0x50"/>
<Item Key="msg_50" Name="I wonder what's going on in the forest right now... I'm worried about Saria, too!" Value="0x50"/>
<Item Key="msg_51" Name="That cloud over Death Mountain... there is something strange about it..." Value="0x51"/>
<Item Key="msg_52" Name="An arctic wind is blowing from Zora's River... do you feel it?" Value="0x52"/>
<Item Key="msg_53" Name="Those Iron Boots look like they weigh a ton! If you wear those boots, you may be able to walk at the bottom of the lake." Value="0x53"/>
<Item Key="msg_54" Name="Lets look for someone who might know about the other Sages!" Value="0x54"/>
<Item Key="msg_55" Name="That monster! It came out of the well in the village! Lets go check out the well!" Value="0x55"/>
<Item Key="msg_54" Name="Let's look for someone who might know about the other Sages!" Value="0x54"/>
<Item Key="msg_55" Name="That monster! It came out of the well in the village! Let's go check out the well!" Value="0x55"/>
<Item Key="msg_56" Name="I wonder who built the Spirit Temple, and for what purpose?" Value="0x56"/>
<Item Key="msg_57" Name="Have you ever played the Nocturne of Shadow that Sheik taught you?" Value="0x57"/>
<Item Key="msg_58" Name="The desert... that is where Ganondorf the Evil King was born. If we go there, we might find something..." Value="0x58"/>
@@ -3490,18 +3490,18 @@ Z ROTATION +0x3F = Collectible Flag</Notes>
<Item Key="msg_5B" Name="The one who is waiting for us at the Temple of Time...it could be..." Value="0x5B"/>
<Item Key="msg_5C" Name="We have to save Princess Zelda from her imprisonment in Ganon's Castle!" Value="0x5C"/>
<Item Key="msg_5F" Name="LINK, try to keep moving!!" Value="0x5F"/>
<Item Key="msg_60" Name="I dont mind talking to you using the Ocarinas magic, but Id really like to talk to you face-to-face!" Value="0x60"/>
<Item Key="msg_60" Name="I don't mind talking to you using the Ocarina's magic, but I'd really like to talk to you face-to-face!" Value="0x60"/>
<Item Key="msg_61" Name="The forest is connected to many different places! If you can hear my song, you must be near somewhere that is connected to the forest!" Value="0x61"/>
<Item Key="msg_62" Name="I was so happy to hear that Mr. Darunia loved my song so much! I was even happier to find out I helped out on your quest, LINK! Tee hee hee!" Value="0x62"/>
<Item Key="msg_63" Name="Are you collecting Spiritual Stones? You have one more to find? You mean the Spiritual Stone of Water, dont you? The Great Deku Tree once told me that King Zora, ruler of Zoras Domain, has it..." Value="0x63"/>
<Item Key="msg_64" Name="Are you collecting Spiritual Stones? You have one more to find? You mean the Spiritual Stone of Fire, dont you? The Great Deku Tree once told me that Mr. Darunia of the Gorons has it..." Value="0x64"/>
<Item Key="msg_65" Name="LINK... I dont know what it is... I have this feeling of dread... The Castle... Yes, something bad is happening at the Castle!" Value="0x65"/>
<Item Key="msg_63" Name="Are you collecting Spiritual Stones? You have one more to find? You mean the Spiritual Stone of Water, don't you? The Great Deku Tree once told me that King Zora, ruler of Zora's Domain, has it..." Value="0x63"/>
<Item Key="msg_64" Name="Are you collecting Spiritual Stones? You have one more to find? You mean the Spiritual Stone of Fire, don't you? The Great Deku Tree once told me that Mr. Darunia of the Gorons has it..." Value="0x64"/>
<Item Key="msg_65" Name="LINK... I don't know what it is... I have this feeling of dread... The Castle... Yes, something bad is happening at the Castle!" Value="0x65"/>
<Item Key="msg_66" Name="What? Your ocarina sounds... different somehow... Have you been practicing a lot, LINK?" Value="0x66"/>
<Item Key="msg_67" Name="Are you looking for a temple? A mysterious bird once told me... “Eyes that can see through darkness will open in a storm.” Do you have any idea what he meant by this?" Value="0x67"/>
<Item Key="msg_68" Name="Where are you, LINK? Are you looking for a temple? I once heard a mysterious bird say... “Go, young man. Go to the Desert Goddess with an ocarina.” Do you have any idea what he meant by this?" Value="0x68"/>
<Item Key="msg_69" Name="Did you find all the temples yet?" Value="0x69"/>
<Item Key="msg_6A" Name="Great! Youre safe! I knew I would hear from you again! Im in the Forest Temple! The forest spirits were calling for help, so I went to check it out... But its full of evil monsters! Help me, LINK!" Value="0x6A"/>
<Item Key="msg_6B" Name="LINK... At first, I didnt want to become the Sage of the Forest... But Im glad now. Because Im helping you save Hyrule, LINK! Yes, I am!" Value="0x6B"/>
<Item Key="msg_6A" Name="Great! You're safe! I knew I would hear from you again! I'm in the Forest Temple! The forest spirits were calling for help, so I went to check it out... But it's full of evil monsters! Help me, LINK!" Value="0x6A"/>
<Item Key="msg_6B" Name="LINK... At first, I didn't want to become the Sage of the Forest... But I'm glad now. Because I'm helping you save Hyrule, LINK! Yes, I am!" Value="0x6B"/>
<Item Key="msg_6C" Name="If all six Sages come together, we can imprison Ganondorf, the King of Evil, in the Sacred Realm. But in order to make a perfect seal, we need the seventh Sage. Someone you know must be that Sage, LINK... From now on, you must travel between past and future to awaken the remaining Sages! Keep up the good work, LINK!" Value="0x6C"/>
<Item Key="msg_6D" Name="We, the Six Sages, are channeling our power to you! The destiny of Hyrule depends upon you!" Value="0x6D"/>
<Item Key="msg_80" Name="I can hear a voice from / somewhere... / It's saying: / &amp;quot;Collect five silver Rupees...&amp;quot;" Value="0x80"/>
@@ -3538,6 +3538,7 @@ Z ROTATION +0x3F = Collectible Flag</Notes>
<Item Key="item_arrow_5" Name="Arrows (5)" Value="0x08"/>
<Item Key="item_arrows_10" Name="Arrows (10)" Value="0x09"/>
<Item Key="item_arrows_30" Name="Arrows (30)" Value="0x0A"/>
<Item Key="item_bombs_5_copy" Name="Bombs (5)" Value="0x0B"/>
<Item Key="item_deku_nuts" Name="Deku Nuts" Value="0x0C"/>
<Item Key="item_deku_sticks" Name="Deku Sticks" Value="0x0D"/>
<Item Key="item_magic_large" Name="Large Magic Jar" Value="0x0E"/>
@@ -3551,6 +3552,7 @@ Z ROTATION +0x3F = Collectible Flag</Notes>
<Item Key="item_hylian_shield" Name="Hylian Shield" Value="0x16"/>
<Item Key="item_zora_tunic" Name="Zora Tunic" Value="0x17"/>
<Item Key="item_goron_tunic" Name="Goron Tunic" Value="0x18"/>
<Item Key="item_bombs_0" Name="Bombs (None/Fake)" Value="0x19"/>
</List>
<List Name="Chest Content">
<Item Key="item_heart" Name="Recovery Heart" Value="0x48"/>
@@ -3586,9 +3588,9 @@ Z ROTATION +0x3F = Collectible Flag</Notes>
<Item Key="item_zora_tunic" Name="Zora Tunic" Value="0x2D"/>
<Item Key="item_iron_boots" Name="Iron Boots" Value="0x2E"/>
<Item Key="item_hover_boots" Name="Hover Boots" Value="0x2F"/>
<Item Key="item_kokiris_sword" Name="Kokiris Sword" Value="0x27"/>
<Item Key="item_giants_knife" Name="Giants Knife" Value="0x28"/>
<Item Key="item_biggorons_sword" Name="Biggorons Sword" Value="0x57"/>
<Item Key="item_kokiris_sword" Name="Kokiri's Sword" Value="0x27"/>
<Item Key="item_giants_knife" Name="Giant's Knife" Value="0x28"/>
<Item Key="item_biggorons_sword" Name="Biggoron's Sword" Value="0x57"/>
<Item Key="item_goron_bracelet" Name="Goron Bracelet" Value="0x54"/>
<Item Key="item_silver_gauntlets" Name="Silver Gauntlets" Value="0x35"/>
<Item Key="item_golden_gauntlets" Name="Golden Gauntlets" Value="0x36"/>
@@ -3607,9 +3609,9 @@ Z ROTATION +0x3F = Collectible Flag</Notes>
<Item Key="item_fire_arrow" Name="Fire Arrow" Value="0x58"/>
<Item Key="item_ice_arrow" Name="Ice Arrow" Value="0x59"/>
<Item Key="item_light_arrow" Name="Light Arrow" Value="0x5A"/>
<Item Key="item_dins_fire" Name="Dins Fire" Value="0x5C"/>
<Item Key="item_farores_wind" Name="Farores Wind" Value="0x5D"/>
<Item Key="item_nayrus_love" Name="Nayrus Love" Value="0x5E"/>
<Item Key="item_dins_fire" Name="Din's Fire" Value="0x5C"/>
<Item Key="item_farores_wind" Name="Farore's Wind" Value="0x5D"/>
<Item Key="item_nayrus_love" Name="Nayru's Love" Value="0x5E"/>
<Item Key="item_stone_of_agony" Name="Stone of Agony" Value="0x39"/>
<Item Key="item_gerudo_membership_card" Name="Gerudo Membership Card" Value="0x3A"/>
<Item Key="item_gold_skulltula_token" Name="Gold Skulltula Token" Value="0x5B"/>
@@ -3628,7 +3630,7 @@ Z ROTATION +0x3F = Collectible Flag</Notes>
<Item Key="item_milk_refill" Name="Milk Refill" Value="0x50"/>
<Item Key="item_weird_egg" Name="Weird Egg" Value="0x47"/>
<Item Key="item_chicken" Name="Chicken" Value="0x19"/>
<Item Key="item_zeldas_letter" Name="Zeldas Letter" Value="0x0B"/>
<Item Key="item_zeldas_letter" Name="Zelda's Letter" Value="0x0B"/>
<Item Key="item_keaton_mask" Name="Keaton Mask" Value="0x1A"/>
<Item Key="item_skull_mask" Name="Skull Mask" Value="0x17"/>
<Item Key="item_spooky_mask" Name="Spooky Mask" Value="0x18"/>
@@ -3642,11 +3644,11 @@ Z ROTATION +0x3F = Collectible Flag</Notes>
<Item Key="item_cojiro" Name="Cojiro" Value="0x0E"/>
<Item Key="item_odd_mushroom" Name="Odd Mushroom" Value="0x1F"/>
<Item Key="item_odd_potion" Name="Odd Potion" Value="0x20"/>
<Item Key="item_saw" Name="Poachers Saw" Value="0x21"/>
<Item Key="item_broken_sword" Name="Broken Gorons Sword" Value="0x22"/>
<Item Key="item_saw" Name="Poacher's Saw" Value="0x21"/>
<Item Key="item_broken_sword" Name="Broken Goron's Sword" Value="0x22"/>
<Item Key="item_prescription" Name="Prescription" Value="0x23"/>
<Item Key="item_eyeball_frog" Name="Eyeball Frog" Value="0x24"/>
<Item Key="item_eye_drops" Name="Worlds Finest Eye Drops" Value="0x25"/>
<Item Key="item_eye_drops" Name="World's Finest Eye Drops" Value="0x25"/>
<Item Key="item_claim_check" Name="Claim Check" Value="0x26"/>
<Item Key="item_big_quiver" Name="Big Quiver" Value="0x30"/>
<Item Key="item_biggest_quiver" Name="Biggest Quiver" Value="0x31"/>
+432
View File
@@ -0,0 +1,432 @@
<?xml version="1.0" encoding="UTF-8"?>
<Table KeyType="System.UInt16" ValueType="System.String">
<!--
Description:
This file hosts various enum data from decomp.
Format:
- <Enum>: a list of <Item> with an unique identifier (key), the decomp ID and the index
Parameters:
* Games: list of OoT versions that shares this data
* Key: identifier, the name is the same as the enum from decomp
- <Item>: description of an element of an <Enum>
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
* Index: the location of the item in the enum (should match decomp)
Notes:
- Player Cue Ids got their own enum but not regular Actor Cues.
This is because Actor Cues are among cutscene commands (``csCmd``)
-->
<Enum Games="OoTMqDbg" Key="csCmd" ID="CutsceneCmd">
<!-- https://github.com/zeldaret/oot/blob/7235af2249843fb68740111b70089bad827a4730/include/z64cutscene.h#L35-L165 -->
<Item Key="cam_eye_spline" ID="CS_CMD_CAM_EYE_SPLINE" Index="1"/>
<Item Key="cam_at_spline" ID="CS_CMD_CAM_AT_SPLINE" Index="2"/>
<Item Key="misc" ID="CS_CMD_MISC" Index="3"/>
<Item Key="light_setting" ID="CS_CMD_LIGHT_SETTING" Index="4"/>
<Item Key="cam_eye_spline_rel_to_player" ID="CS_CMD_CAM_EYE_SPLINE_REL_TO_PLAYER" Index="5"/>
<Item Key="cam_at_spline_rel_to_player" ID="CS_CMD_CAM_AT_SPLINE_REL_TO_PLAYER" Index="6"/>
<Item Key="cam_eye" ID="CS_CMD_CAM_EYE" Index="7"/>
<Item Key="cam_at" ID="CS_CMD_CAM_AT" Index="8"/>
<Item Key="rumble_controller" ID="CS_CMD_RUMBLE_CONTROLLER" Index="9"/>
<Item Key="player_cue" ID="CS_CMD_PLAYER_CUE" Index="10"/>
<Item Key="unimplemented_b" ID="CS_CMD_UNIMPLEMENTED_B" Index="11"/>
<Item Key="unimplemented_d" ID="CS_CMD_UNIMPLEMENTED_D" Index="13"/>
<Item Key="actor_cue_1_0" ID="CS_CMD_ACTOR_CUE_1_0" Index="14"/>
<Item Key="actor_cue_0_0" ID="CS_CMD_ACTOR_CUE_0_0" Index="15"/>
<Item Key="actor_cue_1_1" ID="CS_CMD_ACTOR_CUE_1_1" Index="16"/>
<Item Key="actor_cue_0_1" ID="CS_CMD_ACTOR_CUE_0_1" Index="17"/>
<Item Key="actor_cue_0_2" ID="CS_CMD_ACTOR_CUE_0_2" Index="18"/>
<Item Key="text" ID="CS_CMD_TEXT" Index="19"/>
<Item Key="unimplemented_15" ID="CS_CMD_UNIMPLEMENTED_15" Index="21"/>
<Item Key="unimplemented_16" ID="CS_CMD_UNIMPLEMENTED_16" Index="22"/>
<Item Key="actor_cue_0_3" ID="CS_CMD_ACTOR_CUE_0_3" Index="23"/>
<Item Key="actor_cue_1_2" ID="CS_CMD_ACTOR_CUE_1_2" Index="24"/>
<Item Key="actor_cue_2_0" ID="CS_CMD_ACTOR_CUE_2_0" Index="25"/>
<Item Key="unimplemented_1b" ID="CS_CMD_UNIMPLEMENTED_1B" Index="27"/>
<Item Key="unimplemented_1c" ID="CS_CMD_UNIMPLEMENTED_1C" Index="28"/>
<Item Key="actor_cue_3_0" ID="CS_CMD_ACTOR_CUE_3_0" Index="29"/>
<Item Key="actor_cue_4_0" ID="CS_CMD_ACTOR_CUE_4_0" Index="30"/>
<Item Key="actor_cue_6_0" ID="CS_CMD_ACTOR_CUE_6_0" Index="31"/>
<Item Key="unimplemented_20" ID="CS_CMD_UNIMPLEMENTED_20" Index="32"/>
<Item Key="unimplemented_21" ID="CS_CMD_UNIMPLEMENTED_21" Index="33"/>
<Item Key="actor_cue_0_4" ID="CS_CMD_ACTOR_CUE_0_4" Index="34"/>
<Item Key="actor_cue_1_3" ID="CS_CMD_ACTOR_CUE_1_3" Index="35"/>
<Item Key="actor_cue_2_1" ID="CS_CMD_ACTOR_CUE_2_1" Index="36"/>
<Item Key="actor_cue_3_1" ID="CS_CMD_ACTOR_CUE_3_1" Index="37"/>
<Item Key="actor_cue_4_1" ID="CS_CMD_ACTOR_CUE_4_1" Index="38"/>
<Item Key="actor_cue_0_5" ID="CS_CMD_ACTOR_CUE_0_5" Index="39"/>
<Item Key="actor_cue_1_4" ID="CS_CMD_ACTOR_CUE_1_4" Index="40"/>
<Item Key="actor_cue_2_2" ID="CS_CMD_ACTOR_CUE_2_2" Index="41"/>
<Item Key="actor_cue_3_2" ID="CS_CMD_ACTOR_CUE_3_2" Index="42"/>
<Item Key="actor_cue_4_2" ID="CS_CMD_ACTOR_CUE_4_2" Index="43"/>
<Item Key="actor_cue_5_0" ID="CS_CMD_ACTOR_CUE_5_0" Index="44"/>
<Item Key="transition" ID="CS_CMD_TRANSITION" Index="45"/>
<Item Key="actor_cue_0_6" ID="CS_CMD_ACTOR_CUE_0_6" Index="46"/>
<Item Key="actor_cue_4_3" ID="CS_CMD_ACTOR_CUE_4_3" Index="47"/>
<Item Key="actor_cue_1_5" ID="CS_CMD_ACTOR_CUE_1_5" Index="48"/>
<Item Key="actor_cue_7_0" ID="CS_CMD_ACTOR_CUE_7_0" Index="49"/>
<Item Key="actor_cue_2_3" ID="CS_CMD_ACTOR_CUE_2_3" Index="50"/>
<Item Key="actor_cue_3_3" ID="CS_CMD_ACTOR_CUE_3_3" Index="51"/>
<Item Key="actor_cue_6_1" ID="CS_CMD_ACTOR_CUE_6_1" Index="52"/>
<Item Key="actor_cue_3_4" ID="CS_CMD_ACTOR_CUE_3_4" Index="53"/>
<Item Key="actor_cue_4_4" ID="CS_CMD_ACTOR_CUE_4_4" Index="54"/>
<Item Key="actor_cue_5_1" ID="CS_CMD_ACTOR_CUE_5_1" Index="55"/>
<Item Key="actor_cue_6_2" ID="CS_CMD_ACTOR_CUE_6_2" Index="57"/>
<Item Key="actor_cue_6_3" ID="CS_CMD_ACTOR_CUE_6_3" Index="58"/>
<Item Key="unimplemented_3b" ID="CS_CMD_UNIMPLEMENTED_3B" Index="59"/>
<Item Key="actor_cue_7_1" ID="CS_CMD_ACTOR_CUE_7_1" Index="60"/>
<Item Key="unimplemented_3d" ID="CS_CMD_UNIMPLEMENTED_3D" Index="61"/>
<Item Key="actor_cue_8_0" ID="CS_CMD_ACTOR_CUE_8_0" Index="62"/>
<Item Key="actor_cue_3_5" ID="CS_CMD_ACTOR_CUE_3_5" Index="63"/>
<Item Key="actor_cue_1_6" ID="CS_CMD_ACTOR_CUE_1_6" Index="64"/>
<Item Key="actor_cue_3_6" ID="CS_CMD_ACTOR_CUE_3_6" Index="65"/>
<Item Key="actor_cue_3_7" ID="CS_CMD_ACTOR_CUE_3_7" Index="66"/>
<Item Key="actor_cue_2_4" ID="CS_CMD_ACTOR_CUE_2_4" Index="67"/>
<Item Key="actor_cue_1_7" ID="CS_CMD_ACTOR_CUE_1_7" Index="68"/>
<Item Key="actor_cue_2_5" ID="CS_CMD_ACTOR_CUE_2_5" Index="69"/>
<Item Key="actor_cue_1_8" ID="CS_CMD_ACTOR_CUE_1_8" Index="70"/>
<Item Key="unimplemented_47" ID="CS_CMD_UNIMPLEMENTED_47" Index="71"/>
<Item Key="actor_cue_2_6" ID="CS_CMD_ACTOR_CUE_2_6" Index="72"/>
<Item Key="unimplemented_49" ID="CS_CMD_UNIMPLEMENTED_49" Index="73"/>
<Item Key="actor_cue_2_7" ID="CS_CMD_ACTOR_CUE_2_7" Index="74"/>
<Item Key="actor_cue_3_8" ID="CS_CMD_ACTOR_CUE_3_8" Index="75"/>
<Item Key="actor_cue_0_7" ID="CS_CMD_ACTOR_CUE_0_7" Index="76"/>
<Item Key="actor_cue_5_2" ID="CS_CMD_ACTOR_CUE_5_2" Index="77"/>
<Item Key="actor_cue_1_9" ID="CS_CMD_ACTOR_CUE_1_9" Index="78"/>
<Item Key="actor_cue_4_5" ID="CS_CMD_ACTOR_CUE_4_5" Index="79"/>
<Item Key="actor_cue_1_10" ID="CS_CMD_ACTOR_CUE_1_10" Index="80"/>
<Item Key="actor_cue_2_8" ID="CS_CMD_ACTOR_CUE_2_8" Index="81"/>
<Item Key="actor_cue_3_9" ID="CS_CMD_ACTOR_CUE_3_9" Index="82"/>
<Item Key="actor_cue_4_6" ID="CS_CMD_ACTOR_CUE_4_6" Index="83"/>
<Item Key="actor_cue_5_3" ID="CS_CMD_ACTOR_CUE_5_3" Index="84"/>
<Item Key="actor_cue_0_8" ID="CS_CMD_ACTOR_CUE_0_8" Index="85"/>
<Item Key="start_seq" ID="CS_CMD_START_SEQ" Index="86"/>
<Item Key="stop_seq" ID="CS_CMD_STOP_SEQ" Index="87"/>
<Item Key="actor_cue_6_4" ID="CS_CMD_ACTOR_CUE_6_4" Index="88"/>
<Item Key="actor_cue_7_2" ID="CS_CMD_ACTOR_CUE_7_2" Index="89"/>
<Item Key="actor_cue_5_4" ID="CS_CMD_ACTOR_CUE_5_4" Index="90"/>
<Item Key="actor_cue_0_9" ID="CS_CMD_ACTOR_CUE_0_9" Index="93"/>
<Item Key="actor_cue_1_11" ID="CS_CMD_ACTOR_CUE_1_11" Index="94"/>
<Item Key="actor_cue_0_10" ID="CS_CMD_ACTOR_CUE_0_10" Index="105"/>
<Item Key="actor_cue_2_9" ID="CS_CMD_ACTOR_CUE_2_9" Index="106"/>
<Item Key="actor_cue_0_11" ID="CS_CMD_ACTOR_CUE_0_11" Index="107"/>
<Item Key="actor_cue_3_10" ID="CS_CMD_ACTOR_CUE_3_10" Index="108"/>
<Item Key="unimplemented_6d" ID="CS_CMD_UNIMPLEMENTED_6D" Index="109"/>
<Item Key="actor_cue_0_12" ID="CS_CMD_ACTOR_CUE_0_12" Index="110"/>
<Item Key="actor_cue_7_3" ID="CS_CMD_ACTOR_CUE_7_3" Index="111"/>
<Item Key="unimplemented_70" ID="CS_CMD_UNIMPLEMENTED_70" Index="112"/>
<Item Key="unimplemented_71" ID="CS_CMD_UNIMPLEMENTED_71" Index="113"/>
<Item Key="actor_cue_7_4" ID="CS_CMD_ACTOR_CUE_7_4" Index="114"/>
<Item Key="actor_cue_6_5" ID="CS_CMD_ACTOR_CUE_6_5" Index="115"/>
<Item Key="actor_cue_1_12" ID="CS_CMD_ACTOR_CUE_1_12" Index="116"/>
<Item Key="actor_cue_2_10" ID="CS_CMD_ACTOR_CUE_2_10" Index="117"/>
<Item Key="actor_cue_1_13" ID="CS_CMD_ACTOR_CUE_1_13" Index="118"/>
<Item Key="actor_cue_0_13" ID="CS_CMD_ACTOR_CUE_0_13" Index="119"/>
<Item Key="actor_cue_1_14" ID="CS_CMD_ACTOR_CUE_1_14" Index="120"/>
<Item Key="actor_cue_2_11" ID="CS_CMD_ACTOR_CUE_2_11" Index="121"/>
<Item Key="actor_cue_0_14" ID="CS_CMD_ACTOR_CUE_0_14" Index="123"/>
<Item Key="fade_out_seq" ID="CS_CMD_FADE_OUT_SEQ" Index="124"/>
<Item Key="actor_cue_1_15" ID="CS_CMD_ACTOR_CUE_1_15" Index="125"/>
<Item Key="actor_cue_2_12" ID="CS_CMD_ACTOR_CUE_2_12" Index="126"/>
<Item Key="actor_cue_3_11" ID="CS_CMD_ACTOR_CUE_3_11" Index="127"/>
<Item Key="actor_cue_4_7" ID="CS_CMD_ACTOR_CUE_4_7" Index="128"/>
<Item Key="actor_cue_5_5" ID="CS_CMD_ACTOR_CUE_5_5" Index="129"/>
<Item Key="actor_cue_6_6" ID="CS_CMD_ACTOR_CUE_6_6" Index="130"/>
<Item Key="actor_cue_1_16" ID="CS_CMD_ACTOR_CUE_1_16" Index="131"/>
<Item Key="actor_cue_2_13" ID="CS_CMD_ACTOR_CUE_2_13" Index="132"/>
<Item Key="actor_cue_3_12" ID="CS_CMD_ACTOR_CUE_3_12" Index="133"/>
<Item Key="actor_cue_7_5" ID="CS_CMD_ACTOR_CUE_7_5" Index="134"/>
<Item Key="actor_cue_4_8" ID="CS_CMD_ACTOR_CUE_4_8" Index="135"/>
<Item Key="actor_cue_5_6" ID="CS_CMD_ACTOR_CUE_5_6" Index="136"/>
<Item Key="actor_cue_6_7" ID="CS_CMD_ACTOR_CUE_6_7" Index="137"/>
<Item Key="actor_cue_0_15" ID="CS_CMD_ACTOR_CUE_0_15" Index="138"/>
<Item Key="actor_cue_0_16" ID="CS_CMD_ACTOR_CUE_0_16" Index="139"/>
<Item Key="time" ID="CS_CMD_TIME" Index="140"/>
<Item Key="actor_cue_1_17" ID="CS_CMD_ACTOR_CUE_1_17" Index="141"/>
<Item Key="actor_cue_7_6" ID="CS_CMD_ACTOR_CUE_7_6" Index="142"/>
<Item Key="actor_cue_9_0" ID="CS_CMD_ACTOR_CUE_9_0" Index="143"/>
<Item Key="actor_cue_0_17" ID="CS_CMD_ACTOR_CUE_0_17" Index="144"/>
<Item Key="destination" ID="CS_CMD_DESTINATION" Index="1000"/>
<Item Key="end" ID="CS_CMD_END" Index="65535"/>
</Enum>
<Enum Games="OoTMqDbg" Key="csMiscType" ID="CutsceneMiscType">
<!-- https://github.com/zeldaret/oot/blob/823e47a0f8e9fb9fab89bc61b57f45488ff6debe/include/z64cutscene.h#L167-L204 -->
<Item Key="unimplemented_0" ID="CS_MISC_UNIMPLEMENTED_0" Index="0"/>
<Item Key="rain" ID="CS_MISC_RAIN" Index="1"/>
<Item Key="lightning" ID="CS_MISC_LIGHTNING" Index="2"/>
<Item Key="set_csflag_0" ID="CS_MISC_SET_CSFLAG_0" Index="3"/>
<Item Key="unimplemented_4" ID="CS_MISC_UNIMPLEMENTED_4" Index="4"/>
<Item Key="unimplemented_5" ID="CS_MISC_UNIMPLEMENTED_5" Index="5"/>
<Item Key="lift_fog" ID="CS_MISC_LIFT_FOG" Index="6"/>
<Item Key="cloudy_sky" ID="CS_MISC_CLOUDY_SKY" Index="7"/>
<Item Key="fade_kokiri_grass_env_alpha" ID="CS_MISC_FADE_KOKIRI_GRASS_ENV_ALPHA" Index="8"/>
<Item Key="snow" ID="CS_MISC_SNOW" Index="9"/>
<Item Key="set_csflag_1" ID="CS_MISC_SET_CSFLAG_1" Index="10"/>
<Item Key="deku_tree_death" ID="CS_MISC_DEKU_TREE_DEATH" Index="11"/>
<Item Key="stop_cutscene" ID="CS_MISC_STOP_CUTSCENE" Index="12"/>
<Item Key="triforce_flash" ID="CS_MISC_TRIFORCE_FLASH" Index="13"/>
<Item Key="set_locked_viewpoint" ID="CS_MISC_SET_LOCKED_VIEWPOINT" Index="14"/>
<Item Key="show_title_card" ID="CS_MISC_SHOW_TITLE_CARD" Index="15"/>
<Item Key="quake_start" ID="CS_MISC_QUAKE_START" Index="16"/>
<Item Key="quake_stop" ID="CS_MISC_QUAKE_STOP" Index="17"/>
<Item Key="stop_storm_and_advance_to_day" ID="CS_MISC_STOP_STORM_AND_ADVANCE_TO_DAY" Index="18"/>
<Item Key="set_flag_fast_windmill" ID="CS_MISC_SET_FLAG_FAST_WINDMILL" Index="19"/>
<Item Key="set_flag_well_drained" ID="CS_MISC_SET_FLAG_WELL_DRAINED" Index="20"/>
<Item Key="set_flag_lake_hylia_restored" ID="CS_MISC_SET_FLAG_LAKE_HYLIA_RESTORED" Index="21"/>
<Item Key="vismono_black_and_white" ID="CS_MISC_VISMONO_BLACK_AND_WHITE" Index="22"/>
<Item Key="vismono_sepia" ID="CS_MISC_VISMONO_SEPIA" Index="23"/>
<Item Key="hide_room" ID="CS_MISC_HIDE_ROOM" Index="24"/>
<Item Key="time_advance_to_night" ID="CS_MISC_TIME_ADVANCE_TO_NIGHT" Index="25"/>
<Item Key="set_time_based_light_setting" ID="CS_MISC_SET_TIME_BASED_LIGHT_SETTING" Index="26"/>
<Item Key="red_pulsating_lights" ID="CS_MISC_RED_PULSATING_LIGHTS" Index="27"/>
<Item Key="halt_all_actors" ID="CS_MISC_HALT_ALL_ACTORS" Index="28"/>
<Item Key="resume_all_actors" ID="CS_MISC_RESUME_ALL_ACTORS" Index="29"/>
<Item Key="set_csflag_3" ID="CS_MISC_SET_CSFLAG_3" Index="30"/>
<Item Key="set_csflag_4" ID="CS_MISC_SET_CSFLAG_4" Index="31"/>
<Item Key="sandstorm_fill" ID="CS_MISC_SANDSTORM_FILL" Index="32"/>
<Item Key="sunssong_start" ID="CS_MISC_SUNSSONG_START" Index="33"/>
<Item Key="freeze_time" ID="CS_MISC_FREEZE_TIME" Index="34"/>
<Item Key="long_scarecrow_song" ID="CS_MISC_LONG_SCARECROW_SONG" Index="35"/>
</Enum>
<Enum Games="OoTMqDbg" Key="csTextType" ID="CutsceneTextType">
<!-- https://github.com/zeldaret/oot/blob/823e47a0f8e9fb9fab89bc61b57f45488ff6debe/include/z64cutscene.h#L206-L212 -->
<Item Key="normal" ID="CS_TEXT_NORMAL" Index="0"/>
<Item Key="choice" ID="CS_TEXT_CHOICE" Index="1"/>
<Item Key="ocarina_action" ID="CS_TEXT_OCARINA_ACTION" Index="2"/>
<Item Key="goron_ruby" ID="CS_TEXT_GORON_RUBY" Index="3"/>
<Item Key="zora_sapphire" ID="CS_TEXT_ZORA_SAPPHIRE" Index="4"/>
</Enum>
<Enum Games="OoTMqDbg" Key="csFadeOutSeqPlayer" ID="CutsceneFadeOutSeqPlayer">
<!-- https://github.com/zeldaret/oot/blob/7235af2249843fb68740111b70089bad827a4730/include/z64cutscene.h#L214-L217 -->
<Item Key="fade_out_fanfare" ID="CS_FADE_OUT_FANFARE" Index="3"/>
<Item Key="fade_out_bgm_main" ID="CS_FADE_OUT_BGM_MAIN" Index="4"/>
</Enum>
<Enum Games="OoTMqDbg" Key="csTransitionType" ID="CutsceneTransitionType">
<!-- https://github.com/zeldaret/oot/blob/823e47a0f8e9fb9fab89bc61b57f45488ff6debe/include/z64cutscene.h#L219-233 -->
<Item Key="gray_fill_in" ID="CS_TRANS_GRAY_FILL_IN" Index="1"/>
<Item Key="blue_fill_in" ID="CS_TRANS_BLUE_FILL_IN" Index="2"/>
<Item Key="red_fill_out" ID="CS_TRANS_RED_FILL_OUT" Index="3"/>
<Item Key="green_fill_out" ID="CS_TRANS_GREEN_FILL_OUT" Index="4"/>
<Item Key="gray_fill_out" ID="CS_TRANS_GRAY_FILL_OUT" Index="5"/>
<Item Key="blue_fill_out" ID="CS_TRANS_BLUE_FILL_OUT" Index="6"/>
<Item Key="red_fill_in" ID="CS_TRANS_RED_FILL_IN" Index="7"/>
<Item Key="green_fill_in" ID="CS_TRANS_GREEN_FILL_IN" Index="8"/>
<Item Key="trigger_instance" ID="CS_TRANS_TRIGGER_INSTANCE" Index="9"/>
<Item Key="black_fill_out" ID="CS_TRANS_BLACK_FILL_OUT" Index="10"/>
<Item Key="black_fill_in" ID="CS_TRANS_BLACK_FILL_IN" Index="11"/>
<Item Key="black_fill_out_to_half" ID="CS_TRANS_BLACK_FILL_OUT_TO_HALF" Index="12"/>
<Item Key="black_fill_in_from_half" ID="CS_TRANS_BLACK_FILL_IN_FROM_HALF" Index="13"/>
</Enum>
<Enum Games="OoTMqDbg" Key="csDestination" ID="CutsceneDestination">
<!-- https://github.com/zeldaret/oot/blob/823e47a0f8e9fb9fab89bc61b57f45488ff6debe/include/z64cutscene.h#L235-356 -->
<Item Key="unimplemented_0" ID="CS_DEST_UNIMPLEMENTED_0" Index="0"/>
<Item Key="cutscene_map_ganon_horse" ID="CS_DEST_CUTSCENE_MAP_GANON_HORSE" Index="1"/>
<Item Key="cutscene_map_three_goddesses" ID="CS_DEST_CUTSCENE_MAP_THREE_GODDESSES" Index="2"/>
<Item Key="gerudo_valley_din_part_1" ID="CS_DEST_GERUDO_VALLEY_DIN_PART_1" Index="3"/>
<Item Key="death_mountain_trail_nayru" ID="CS_DEST_DEATH_MOUNTAIN_TRAIL_NAYRU" Index="4"/>
<Item Key="kokiri_forest_farore" ID="CS_DEST_KOKIRI_FOREST_FARORE" Index="5"/>
<Item Key="cutscene_map_triforce_creation" ID="CS_DEST_CUTSCENE_MAP_TRIFORCE_CREATION" Index="6"/>
<Item Key="kokiri_forest_receive_kokiri_emerald" ID="CS_DEST_KOKIRI_FOREST_RECEIVE_KOKIRI_EMERALD" Index="7"/>
<Item Key="temple_of_time_from_master_sword" ID="CS_DEST_TEMPLE_OF_TIME_FROM_MASTER_SWORD" Index="8"/>
<Item Key="gerudo_valley_din_part_2" ID="CS_DEST_GERUDO_VALLEY_DIN_PART_2" Index="9"/>
<Item Key="links_house_intro" ID="CS_DEST_LINKS_HOUSE_INTRO" Index="10"/>
<Item Key="kokiri_forest_intro" ID="CS_DEST_KOKIRI_FOREST_INTRO" Index="11"/>
<Item Key="death_mountain_trail_from_goron_ruby" ID="CS_DEST_DEATH_MOUNTAIN_TRAIL_FROM_GORON_RUBY" Index="12"/>
<Item Key="zoras_fountain_from_zoras_sapphire" ID="CS_DEST_ZORAS_FOUNTAIN_FROM_ZORAS_SAPPHIRE" Index="13"/>
<Item Key="kokiri_forest_from_kokiri_emerald" ID="CS_DEST_KOKIRI_FOREST_FROM_KOKIRI_EMERALD" Index="14"/>
<Item Key="temple_of_time_kokiri_emerald_restored" ID="CS_DEST_TEMPLE_OF_TIME_KOKIRI_EMERALD_RESTORED" Index="15"/>
<Item Key="temple_of_time_goron_ruby_restored" ID="CS_DEST_TEMPLE_OF_TIME_GORON_RUBY_RESTORED" Index="16"/>
<Item Key="temple_of_time_zoras_sapphire_restored" ID="CS_DEST_TEMPLE_OF_TIME_ZORAS_SAPPHIRE_RESTORED" Index="17"/>
<Item Key="temple_of_time_after_light_medallion" ID="CS_DEST_TEMPLE_OF_TIME_AFTER_LIGHT_MEDALLION" Index="18"/>
<Item Key="death_mountain_trail" ID="CS_DEST_DEATH_MOUNTAIN_TRAIL" Index="19"/>
<Item Key="unimplemented_14" ID="CS_DEST_UNIMPLEMENTED_14" Index="20"/>
<Item Key="lake_hylia_water_restored" ID="CS_DEST_LAKE_HYLIA_WATER_RESTORED" Index="21"/>
<Item Key="desert_colossus_requiem" ID="CS_DEST_DESERT_COLOSSUS_REQUIEM" Index="22"/>
<Item Key="cutscene_map_ganondorf_defeated_credits" ID="CS_DEST_CUTSCENE_MAP_GANONDORF_DEFEATED_CREDITS" Index="23"/>
<Item Key="jabu_jabu" ID="CS_DEST_JABU_JABU" Index="24"/>
<Item Key="chamber_of_sages_light_medallion" ID="CS_DEST_CHAMBER_OF_SAGES_LIGHT_MEDALLION" Index="25"/>
<Item Key="temple_of_time_kokiri_emerald_restored_2" ID="CS_DEST_TEMPLE_OF_TIME_KOKIRI_EMERALD_RESTORED_2" Index="26"/>
<Item Key="temple_of_time_goron_ruby_restored_2" ID="CS_DEST_TEMPLE_OF_TIME_GORON_RUBY_RESTORED_2" Index="27"/>
<Item Key="temple_of_time_zoras_sapphire_restored_2" ID="CS_DEST_TEMPLE_OF_TIME_ZORAS_SAPPHIRE_RESTORED_2" Index="28"/>
<Item Key="chamber_of_sages_forest_medallion" ID="CS_DEST_CHAMBER_OF_SAGES_FOREST_MEDALLION" Index="29"/>
<Item Key="chamber_of_sages_fire_medallion" ID="CS_DEST_CHAMBER_OF_SAGES_FIRE_MEDALLION" Index="30"/>
<Item Key="chamber_of_sages_water_medallion" ID="CS_DEST_CHAMBER_OF_SAGES_WATER_MEDALLION" Index="31"/>
<Item Key="hyrule_field_flashback" ID="CS_DEST_HYRULE_FIELD_FLASHBACK" Index="32"/>
<Item Key="hyrule_field_from_zelda_escape" ID="CS_DEST_HYRULE_FIELD_FROM_ZELDA_ESCAPE" Index="33"/>
<Item Key="cutscene_map_ganondorf_from_master_sword" ID="CS_DEST_CUTSCENE_MAP_GANONDORF_FROM_MASTER_SWORD" Index="34"/>
<Item Key="hyrule_field_intro_dream" ID="CS_DEST_HYRULE_FIELD_INTRO_DREAM" Index="35"/>
<Item Key="unimplemented_24" ID="CS_DEST_UNIMPLEMENTED_24" Index="36"/>
<Item Key="unimplemented_25" ID="CS_DEST_UNIMPLEMENTED_25" Index="37"/>
<Item Key="cutscene_map_sheikah_legend" ID="CS_DEST_CUTSCENE_MAP_SHEIKAH_LEGEND" Index="38"/>
<Item Key="temple_of_time_zelda_reveal" ID="CS_DEST_TEMPLE_OF_TIME_ZELDA_REVEAL" Index="39"/>
<Item Key="temple_of_time_get_light_arrows" ID="CS_DEST_TEMPLE_OF_TIME_GET_LIGHT_ARROWS" Index="40"/>
<Item Key="lake_hylia_from_lake_restored" ID="CS_DEST_LAKE_HYLIA_FROM_LAKE_RESTORED" Index="41"/>
<Item Key="kakariko_village_drain_well" ID="CS_DEST_KAKARIKO_VILLAGE_DRAIN_WELL" Index="42"/>
<Item Key="windmill_from_well_drained" ID="CS_DEST_WINDMILL_FROM_WELL_DRAINED" Index="43"/>
<Item Key="temple_of_time_from_all_stones_restored" ID="CS_DEST_TEMPLE_OF_TIME_FROM_ALL_STONES_RESTORED" Index="44"/>
<Item Key="unimplemented_2d" ID="CS_DEST_UNIMPLEMENTED_2D" Index="45"/>
<Item Key="temple_of_time_after_light_medallion_alt" ID="CS_DEST_TEMPLE_OF_TIME_AFTER_LIGHT_MEDALLION_ALT" Index="46"/>
<Item Key="kakariko_village_nocturne_part_2" ID="CS_DEST_KAKARIKO_VILLAGE_NOCTURNE_PART_2" Index="47"/>
<Item Key="desert_colossus_from_requiem" ID="CS_DEST_DESERT_COLOSSUS_FROM_REQUIEM" Index="48"/>
<Item Key="temple_of_time_from_light_arrows" ID="CS_DEST_TEMPLE_OF_TIME_FROM_LIGHT_ARROWS" Index="49"/>
<Item Key="kakariko_village_from_nocturne" ID="CS_DEST_KAKARIKO_VILLAGE_FROM_NOCTURNE" Index="50"/>
<Item Key="hyrule_field_from_zeldas_courtyard" ID="CS_DEST_HYRULE_FIELD_FROM_ZELDAS_COURTYARD" Index="51"/>
<Item Key="temple_of_time_song_of_time" ID="CS_DEST_TEMPLE_OF_TIME_SONG_OF_TIME" Index="52"/>
<Item Key="hyrule_field_from_song_of_time" ID="CS_DEST_HYRULE_FIELD_FROM_SONG_OF_TIME" Index="53"/>
<Item Key="gerudo_valley_credits" ID="CS_DEST_GERUDO_VALLEY_CREDITS" Index="54"/>
<Item Key="gerudo_fortress_credits" ID="CS_DEST_GERUDO_FORTRESS_CREDITS" Index="55"/>
<Item Key="kakariko_village_credits" ID="CS_DEST_KAKARIKO_VILLAGE_CREDITS" Index="56"/>
<Item Key="death_mountain_trail_credits_part_1" ID="CS_DEST_DEATH_MOUNTAIN_TRAIL_CREDITS_PART_1" Index="57"/>
<Item Key="goron_city_credits" ID="CS_DEST_GORON_CITY_CREDITS" Index="58"/>
<Item Key="lake_hylia_credits" ID="CS_DEST_LAKE_HYLIA_CREDITS" Index="59"/>
<Item Key="zoras_fountain_credits crashes" ID="CS_DEST_ZORAS_FOUNTAIN_CREDITS crashes" Index="60"/>
<Item Key="zoras_domain_credits" ID="CS_DEST_ZORAS_DOMAIN_CREDITS" Index="61"/>
<Item Key="kokiri_forest_credits_part_1" ID="CS_DEST_KOKIRI_FOREST_CREDITS_PART_1" Index="62"/>
<Item Key="kokiri_forest_credits_part_2" ID="CS_DEST_KOKIRI_FOREST_CREDITS_PART_2" Index="63"/>
<Item Key="hyrule_field_credits" ID="CS_DEST_HYRULE_FIELD_CREDITS" Index="64"/>
<Item Key="lon_lon_ranch_credits_part_1_alt" ID="CS_DEST_LON_LON_RANCH_CREDITS_PART_1_ALT" Index="65"/>
<Item Key="kakariko_village_from_trail_owl" ID="CS_DEST_KAKARIKO_VILLAGE_FROM_TRAIL_OWL" Index="66"/>
<Item Key="hyrule_field_from_lake_hylia_owl" ID="CS_DEST_HYRULE_FIELD_FROM_LAKE_HYLIA_OWL" Index="67"/>
<Item Key="cutscene_map_deku_sprout_part_2" ID="CS_DEST_CUTSCENE_MAP_DEKU_SPROUT_PART_2" Index="68"/>
<Item Key="kokiri_forest_deku_sprout_part_3" ID="CS_DEST_KOKIRI_FOREST_DEKU_SPROUT_PART_3" Index="69"/>
<Item Key="death_mountain_trail_credits_part_2" ID="CS_DEST_DEATH_MOUNTAIN_TRAIL_CREDITS_PART_2" Index="70"/>
<Item Key="temple_of_time_credits" ID="CS_DEST_TEMPLE_OF_TIME_CREDITS" Index="71"/>
<Item Key="zeldas_courtyard_credits" ID="CS_DEST_ZELDAS_COURTYARD_CREDITS" Index="72"/>
<Item Key="lon_lon_ranch_credits_part_1" ID="CS_DEST_LON_LON_RANCH_CREDITS_PART_1" Index="73"/>
<Item Key="lon_lon_ranch_credits_part_2" ID="CS_DEST_LON_LON_RANCH_CREDITS_PART_2" Index="74"/>
<Item Key="lon_lon_ranch_credits_part_3" ID="CS_DEST_LON_LON_RANCH_CREDITS_PART_3" Index="75"/>
<Item Key="lon_lon_ranch_credits_part_4" ID="CS_DEST_LON_LON_RANCH_CREDITS_PART_4" Index="76"/>
<Item Key="lon_lon_ranch_credits_part_5" ID="CS_DEST_LON_LON_RANCH_CREDITS_PART_5" Index="77"/>
<Item Key="lon_lon_ranch_credits_part_6" ID="CS_DEST_LON_LON_RANCH_CREDITS_PART_6" Index="78"/>
<Item Key="lon_lon_ranch_1" ID="CS_DEST_LON_LON_RANCH_1" Index="79"/>
<Item Key="lon_lon_ranch_2" ID="CS_DEST_LON_LON_RANCH_2" Index="80"/>
<Item Key="lon_lon_ranch_3" ID="CS_DEST_LON_LON_RANCH_3" Index="81"/>
<Item Key="lon_lon_ranch_4" ID="CS_DEST_LON_LON_RANCH_4" Index="82"/>
<Item Key="lon_lon_ranch_5" ID="CS_DEST_LON_LON_RANCH_5" Index="83"/>
<Item Key="lon_lon_ranch_6" ID="CS_DEST_LON_LON_RANCH_6" Index="84"/>
<Item Key="lon_lon_ranch_7" ID="CS_DEST_LON_LON_RANCH_7" Index="85"/>
<Item Key="lon_lon_ranch_8" ID="CS_DEST_LON_LON_RANCH_8" Index="86"/>
<Item Key="lon_lon_ranch_9" ID="CS_DEST_LON_LON_RANCH_9" Index="87"/>
<Item Key="lon_lon_ranch_10" ID="CS_DEST_LON_LON_RANCH_10" Index="88"/>
<Item Key="lon_lon_ranch_11" ID="CS_DEST_LON_LON_RANCH_11" Index="89"/>
<Item Key="lon_lon_ranch_12" ID="CS_DEST_LON_LON_RANCH_12" Index="90"/>
<Item Key="lon_lon_ranch_13" ID="CS_DEST_LON_LON_RANCH_13" Index="91"/>
<Item Key="lon_lon_ranch_14" ID="CS_DEST_LON_LON_RANCH_14" Index="92"/>
<Item Key="lon_lon_ranch_15" ID="CS_DEST_LON_LON_RANCH_15" Index="93"/>
<Item Key="lon_lon_ranch_from_eponas_song" ID="CS_DEST_LON_LON_RANCH_FROM_EPONAS_SONG" Index="94"/>
<Item Key="stones_restored_conditional" ID="CS_DEST_STONES_RESTORED_CONDITIONAL" Index="95"/>
<Item Key="desert_colossus_from_chamber_of_sages" ID="CS_DEST_DESERT_COLOSSUS_FROM_CHAMBER_OF_SAGES" Index="96"/>
<Item Key="graveyard_from_chamber_of_sages" ID="CS_DEST_GRAVEYARD_FROM_CHAMBER_OF_SAGES" Index="97"/>
<Item Key="death_mountain_crater_from_chamber_of_sages" ID="CS_DEST_DEATH_MOUNTAIN_CRATER_FROM_CHAMBER_OF_SAGES" Index="98"/>
<Item Key="sacred_forest_meadow_warp_pad" ID="CS_DEST_SACRED_FOREST_MEADOW_WARP_PAD" Index="99"/>
<Item Key="kokiri_forest_from_chamber_of_sages" ID="CS_DEST_KOKIRI_FOREST_FROM_CHAMBER_OF_SAGES" Index="100"/>
<Item Key="desert_colossus_from_nabooru_capture" ID="CS_DEST_DESERT_COLOSSUS_FROM_NABOORU_CAPTURE" Index="101"/>
<Item Key="temple_of_time_front_of_pedestal" ID="CS_DEST_TEMPLE_OF_TIME_FRONT_OF_PEDESTAL" Index="102"/>
<Item Key="hyrule_field_title_screen" ID="CS_DEST_HYRULE_FIELD_TITLE_SCREEN" Index="103"/>
<Item Key="title_screen_demo" ID="CS_DEST_TITLE_SCREEN_DEMO" Index="104"/>
<Item Key="graveyard_suns_song_part_2" ID="CS_DEST_GRAVEYARD_SUNS_SONG_PART_2" Index="105"/>
<Item Key="royal_familys_tomb_suns_song_part_3" ID="CS_DEST_ROYAL_FAMILYS_TOMB_SUNS_SONG_PART_3" Index="106"/>
<Item Key="ganons_castle_dispel_forest_beam" ID="CS_DEST_GANONS_CASTLE_DISPEL_FOREST_BEAM" Index="107"/>
<Item Key="ganons_castle_dispel_water_beam" ID="CS_DEST_GANONS_CASTLE_DISPEL_WATER_BEAM" Index="108"/>
<Item Key="ganons_castle_dispel_shadow_beam" ID="CS_DEST_GANONS_CASTLE_DISPEL_SHADOW_BEAM" Index="109"/>
<Item Key="ganons_castle_dispel_fire_beam" ID="CS_DEST_GANONS_CASTLE_DISPEL_FIRE_BEAM" Index="110"/>
<Item Key="ganons_castle_dispel_light_beam" ID="CS_DEST_GANONS_CASTLE_DISPEL_LIGHT_BEAM" Index="111"/>
<Item Key="ganons_castle_dispel_spirit_beam" ID="CS_DEST_GANONS_CASTLE_DISPEL_SPIRIT_BEAM" Index="112"/>
<Item Key="ganons_castle_dispel_barrier_conditonal" ID="CS_DEST_GANONS_CASTLE_DISPEL_BARRIER_CONDITONAL" Index="113"/>
<Item Key="hyrule_field_from_fairy_ocarina" ID="CS_DEST_HYRULE_FIELD_FROM_FAIRY_OCARINA" Index="114"/>
<Item Key="hyrule_field_from_impa_escort" ID="CS_DEST_HYRULE_FIELD_FROM_IMPA_ESCORT" Index="115"/>
<Item Key="from_rauru_final_message_conditional" ID="CS_DEST_FROM_RAURU_FINAL_MESSAGE_CONDITIONAL" Index="116"/>
<Item Key="hyrule_field_credits_sky" ID="CS_DEST_HYRULE_FIELD_CREDITS_SKY" Index="117"/>
<Item Key="ganon_battle_tower_collapse" ID="CS_DEST_GANON_BATTLE_TOWER_COLLAPSE" Index="118"/>
<Item Key="zeldas_courtyard_receive_letter" ID="CS_DEST_ZELDAS_COURTYARD_RECEIVE_LETTER" Index="119"/>
</Enum>
<Enum Games="OoTMqDbg" Key="csPlayerCueId" ID="PlayerCueId">
<!-- https://github.com/zeldaret/oot/blob/b3486b57ef41a284a366eb40ca07bf82bf4750d6/include/z64player.h#L462-L542 -->
<Item Key="cueid_none" ID="PLAYER_CUEID_NONE" Index="0"/>
<Item Key="cueid_1" ID="PLAYER_CUEID_1" Index="1"/>
<Item Key="cueid_2" ID="PLAYER_CUEID_2" Index="2"/>
<Item Key="cueid_3" ID="PLAYER_CUEID_3" Index="3"/>
<Item Key="cueid_4" ID="PLAYER_CUEID_4" Index="4"/>
<Item Key="cueid_5" ID="PLAYER_CUEID_5" Index="5"/>
<Item Key="cueid_6" ID="PLAYER_CUEID_6" Index="6"/>
<Item Key="cueid_7" ID="PLAYER_CUEID_7" Index="7"/>
<Item Key="cueid_8" ID="PLAYER_CUEID_8" Index="8"/>
<Item Key="cueid_9" ID="PLAYER_CUEID_9" Index="9"/>
<Item Key="cueid_10" ID="PLAYER_CUEID_10" Index="10"/>
<Item Key="cueid_11" ID="PLAYER_CUEID_11" Index="11"/>
<Item Key="cueid_12" ID="PLAYER_CUEID_12" Index="12"/>
<Item Key="cueid_13" ID="PLAYER_CUEID_13" Index="13"/>
<Item Key="cueid_14" ID="PLAYER_CUEID_14" Index="14"/>
<Item Key="cueid_15" ID="PLAYER_CUEID_15" Index="15"/>
<Item Key="cueid_16" ID="PLAYER_CUEID_16" Index="16"/>
<Item Key="cueid_17" ID="PLAYER_CUEID_17" Index="17"/>
<Item Key="cueid_18" ID="PLAYER_CUEID_18" Index="18"/>
<Item Key="cueid_19" ID="PLAYER_CUEID_19" Index="19"/>
<Item Key="cueid_20" ID="PLAYER_CUEID_20" Index="20"/>
<Item Key="cueid_21" ID="PLAYER_CUEID_21" Index="21"/>
<Item Key="cueid_22" ID="PLAYER_CUEID_22" Index="22"/>
<Item Key="cueid_23" ID="PLAYER_CUEID_23" Index="23"/>
<Item Key="cueid_24" ID="PLAYER_CUEID_24" Index="24"/>
<Item Key="cueid_25" ID="PLAYER_CUEID_25" Index="25"/>
<Item Key="cueid_26" ID="PLAYER_CUEID_26" Index="26"/>
<Item Key="cueid_27" ID="PLAYER_CUEID_27" Index="27"/>
<Item Key="cueid_28" ID="PLAYER_CUEID_28" Index="28"/>
<Item Key="cueid_29" ID="PLAYER_CUEID_29" Index="29"/>
<Item Key="cueid_30" ID="PLAYER_CUEID_30" Index="30"/>
<Item Key="cueid_31" ID="PLAYER_CUEID_31" Index="31"/>
<Item Key="cueid_32" ID="PLAYER_CUEID_32" Index="32"/>
<Item Key="cueid_33" ID="PLAYER_CUEID_33" Index="33"/>
<Item Key="cueid_34" ID="PLAYER_CUEID_34" Index="34"/>
<Item Key="cueid_35" ID="PLAYER_CUEID_35" Index="35"/>
<Item Key="cueid_36" ID="PLAYER_CUEID_36" Index="36"/>
<Item Key="cueid_37" ID="PLAYER_CUEID_37" Index="37"/>
<Item Key="cueid_38" ID="PLAYER_CUEID_38" Index="38"/>
<Item Key="cueid_39" ID="PLAYER_CUEID_39" Index="39"/>
<Item Key="cueid_40" ID="PLAYER_CUEID_40" Index="40"/>
<Item Key="cueid_41" ID="PLAYER_CUEID_41" Index="41"/>
<Item Key="cueid_42" ID="PLAYER_CUEID_42" Index="42"/>
<Item Key="cueid_43" ID="PLAYER_CUEID_43" Index="43"/>
<Item Key="cueid_44" ID="PLAYER_CUEID_44" Index="44"/>
<Item Key="cueid_45" ID="PLAYER_CUEID_45" Index="45"/>
<Item Key="cueid_46" ID="PLAYER_CUEID_46" Index="46"/>
<Item Key="cueid_47" ID="PLAYER_CUEID_47" Index="47"/>
<Item Key="cueid_48" ID="PLAYER_CUEID_48" Index="48"/>
<Item Key="cueid_49" ID="PLAYER_CUEID_49" Index="49"/>
<Item Key="cueid_50" ID="PLAYER_CUEID_50" Index="50"/>
<Item Key="cueid_51" ID="PLAYER_CUEID_51" Index="51"/>
<Item Key="cueid_52" ID="PLAYER_CUEID_52" Index="52"/>
<Item Key="cueid_53" ID="PLAYER_CUEID_53" Index="53"/>
<Item Key="cueid_54" ID="PLAYER_CUEID_54" Index="54"/>
<Item Key="cueid_55" ID="PLAYER_CUEID_55" Index="55"/>
<Item Key="cueid_56" ID="PLAYER_CUEID_56" Index="56"/>
<Item Key="cueid_57" ID="PLAYER_CUEID_57" Index="57"/>
<Item Key="cueid_58" ID="PLAYER_CUEID_58" Index="58"/>
<Item Key="cueid_59" ID="PLAYER_CUEID_59" Index="59"/>
<Item Key="cueid_60" ID="PLAYER_CUEID_60" Index="60"/>
<Item Key="cueid_61" ID="PLAYER_CUEID_61" Index="61"/>
<Item Key="cueid_62" ID="PLAYER_CUEID_62" Index="62"/>
<Item Key="cueid_63" ID="PLAYER_CUEID_63" Index="63"/>
<Item Key="cueid_64" ID="PLAYER_CUEID_64" Index="64"/>
<Item Key="cueid_65" ID="PLAYER_CUEID_65" Index="65"/>
<Item Key="cueid_66" ID="PLAYER_CUEID_66" Index="66"/>
<Item Key="cueid_67" ID="PLAYER_CUEID_67" Index="67"/>
<Item Key="cueid_68" ID="PLAYER_CUEID_68" Index="68"/>
<Item Key="cueid_69" ID="PLAYER_CUEID_69" Index="69"/>
<Item Key="cueid_70" ID="PLAYER_CUEID_70" Index="70"/>
<Item Key="cueid_71" ID="PLAYER_CUEID_71" Index="71"/>
<Item Key="cueid_72" ID="PLAYER_CUEID_72" Index="72"/>
<Item Key="cueid_73" ID="PLAYER_CUEID_73" Index="73"/>
<Item Key="cueid_74" ID="PLAYER_CUEID_74" Index="74"/>
<Item Key="cueid_75" ID="PLAYER_CUEID_75" Index="75"/>
<Item Key="cueid_76" ID="PLAYER_CUEID_76" Index="76"/>
<Item Key="cueid_77" ID="PLAYER_CUEID_77" Index="77"/>
</Enum>
<Enum Games="OoTMqDbg" Key="naviQuestHintType" ID="NaviQuestHintFileId">
<!-- https://github.com/zeldaret/oot/blob/107c0288cc46e378c67dc4ae2e39cfd2e5fcbc25/include/z64scene.h#L463-L467 -->
<Item Key="hints_none" ID="NAVI_QUEST_HINTS_NONE" Index="0"/>
<Item Key="hints_overworld" ID="NAVI_QUEST_HINTS_OVERWORLD" Index="1"/>
<Item Key="hints_dungeon" ID="NAVI_QUEST_HINTS_DUNGEON" Index="2"/>
</Enum>
</Table>
+3 -9
View File
@@ -6,7 +6,7 @@ from bpy.utils import register_class, unregister_class
from mathutils import Matrix
from ...utility import CData, PluginError, raisePluginError, writeCData, toAlnum
from ...f3d.f3d_parser import importMeshC, getImportData
from ...f3d.f3d_gbi import DLFormat, F3D, TextureExportSettings, ScrollMethod
from ...f3d.f3d_gbi import DLFormat, F3D, TextureExportSettings, ScrollMethod, get_F3D_GBI
from ...f3d.f3d_writer import TriangleConverterInfo, removeDL, saveStaticModel, getInfoDict
from ..oot_utility import ootGetObjectPath, getOOTScale
from ..oot_model_classes import OOTF3DContext, ootGetIncludedAssetData
@@ -28,8 +28,6 @@ from ..oot_utility import (
def ootConvertMeshToC(
originalObj: bpy.types.Object,
finalTransform: mathutils.Matrix,
f3dType: str,
isHWv1: bool,
DLFormat: DLFormat,
saveTextures: bool,
settings: OOTDLExportSettings,
@@ -47,7 +45,7 @@ def ootConvertMeshToC(
try:
obj, allObjs = ootDuplicateHierarchy(originalObj, None, False, OOTObjectCategorizer())
fModel = OOTModel(f3dType, isHWv1, name, DLFormat, drawLayer)
fModel = OOTModel(name, DLFormat, drawLayer)
triConverterInfo = TriangleConverterInfo(obj, None, fModel.f3d, finalTransform, getInfoDict(obj))
fMeshes = saveStaticModel(
triConverterInfo, fModel, obj, finalTransform, fModel.name, not saveTextures, False, "oot"
@@ -123,7 +121,7 @@ class OOT_ImportDL(Operator):
paths = [ootGetObjectPath(isCustomImport, importPath, folderName)]
data = getImportData(paths)
f3dContext = OOTF3DContext(F3D("F3DEX2/LX2", False), [name], basePath)
f3dContext = OOTF3DContext(get_F3D_GBI(), [name], basePath)
scale = getOOTScale(settings.actorScale)
if not isCustomImport:
@@ -181,15 +179,11 @@ class OOT_ExportDL(Operator):
# context.scene.geoLevelOption)
saveTextures = context.scene.saveTextures
isHWv1 = context.scene.isHWv1
f3dType = context.scene.f3d_type
exportSettings = context.scene.fast64.oot.DLExportSettings
ootConvertMeshToC(
obj,
finalTransform,
f3dType,
isHWv1,
DLFormat.Static,
saveTextures,
exportSettings,
+3 -102
View File
@@ -9,71 +9,15 @@ ootEnumRoomShapeType = [
("ROOM_SHAPE_TYPE_CULLABLE", "Cullable", "Cullable"),
]
ootRoomShapeStructs = [
"RoomShapeNormal",
"RoomShapeImage",
"RoomShapeCullable",
]
ootRoomShapeEntryStructs = [
"RoomShapeDListsEntry",
"RoomShapeDListsEntry",
"RoomShapeCullableEntry",
]
ootEnumSceneMenu = [
("General", "General", "General"),
("Lighting", "Lighting", "Lighting"),
("Cutscene", "Cutscene", "Cutscene"),
("Exits", "Exits", "Exits"),
("Alternate", "Alternate", "Alternate"),
]
ootEnumRenderScene = [
("General", "General", "General"),
("Alternate", "Alternate", "Alternate"),
]
ootEnumSceneMenuAlternate = [
("General", "General", "General"),
("Lighting", "Lighting", "Lighting"),
("Cutscene", "Cutscene", "Cutscene"),
("Exits", "Exits", "Exits"),
]
ootEnumRoomMenu = [
("General", "General", "General"),
("Objects", "Objects", "Objects"),
("Alternate", "Alternate", "Alternate"),
]
ootEnumRoomMenuAlternate = [
("General", "General", "General"),
("Objects", "Objects", "Objects"),
]
ootEnumHeaderMenu = [
("Child Night", "Child Night", "Child Night"),
("Adult Day", "Adult Day", "Adult Day"),
("Adult Night", "Adult Night", "Adult Night"),
("Cutscene", "Cutscene", "Cutscene"),
]
ootEnumHeaderMenuComplete = [
("Child Day", "Child Day", "Child Day"),
("Child Night", "Child Night", "Child Night"),
("Adult Day", "Adult Day", "Adult Day"),
("Adult Night", "Adult Night", "Adult Night"),
("Cutscene", "Cutscene", "Cutscene"),
]
ootEnumLightGroupMenu = [
("Dawn", "Dawn", "Dawn"),
("Day", "Day", "Day"),
("Dusk", "Dusk", "Dusk"),
("Night", "Night", "Night"),
]
] + ootEnumHeaderMenu
ootEnumLinkIdle = [
("Custom", "Custom", "Custom"),
@@ -87,21 +31,6 @@ ootEnumLinkIdle = [
("0xFF", "Hops On Epona", "Hops On Epona"),
]
# Make sure to add exceptions in utility.py - selectMeshChildrenOnly
ootEnumEmptyType = [
("None", "None", "None"),
("Scene", "Scene", "Scene"),
("Room", "Room", "Room"),
("Actor", "Actor", "Actor"),
("Transition Actor", "Transition Actor", "Transition Actor"),
("Entrance", "Entrance", "Entrance"),
("Water Box", "Water Box", "Water Box"),
("Cull Group", "Custom Cull Group", "Cull Group"),
("LOD", "LOD Group", "LOD Group"),
("Cutscene", "Cutscene", "Cutscene"),
# ('Camera Volume', 'Camera Volume', 'Camera Volume'),
]
ootEnumCloudiness = [
("Custom", "Custom", "Custom"),
("0x00", "Sunny", "Sunny"),
@@ -348,22 +277,6 @@ ootEnumNaviHints = [
("0x02", "Dungeon", "elf_message_ydan"),
]
ootEnumTransitionAnims = [
("Custom", "Custom", "Custom"),
("0x00", "Spiky", "Spiky"),
("0x01", "Triforce", "Triforce"),
("0x02", "Slow Black Fade", "Slow Black Fade"),
("0x03", "Slow Day/White, Slow Night/Black Fade", "Slow Day/White, Slow Night/Black Fade"),
("0x04", "Fast Day/Black, Slow Night/Black Fade", "Fast Day/Black, Slow Night/Black Fade"),
("0x05", "Fast Day/White, Slow Night/Black Fade", "Fast Day/White, Slow Night/Black Fade"),
("0x06", "Very Slow Day/White, Slow Night/Black Fade", "Very Slow Day/White, Slow Night/Black Fade"),
("0x07", "Very Slow Day/White, Slow Night/Black Fade", "Very Slow Day/White, Slow Night/Black Fade"),
("0x0E", "Slow Sandstorm Fade", "Slow Sandstorm Fade"),
("0x0F", "Fast Sandstorm Fade", "Fast Sandstorm Fade"),
("0x20", "Iris Fade", "Iris Fade"),
("0x2C", "Shortcut Transition", "Shortcut Transition"),
]
# The order of this list matters (normal OoT scene order as defined by ``scene_table.h``)
ootEnumSceneID = [
("Custom", "Custom", "Custom"),
@@ -599,6 +512,7 @@ ootSceneIDToName = {
"SCENE_SASATEST": "sasatest",
"SCENE_TESTROOM": "testroom",
}
ootSceneNameToID = {val: key for key, val in ootSceneIDToName.items()}
ootEnumCamTransition = [
("Custom", "Custom", "Custom"),
@@ -607,7 +521,7 @@ ootEnumCamTransition = [
# ("0xFF", "0xFF", "0xFF"),
]
# see curRoom.unk_03
# see curRoom.behaviorType1
ootEnumRoomBehaviour = [
("Custom", "Custom", "Custom"),
("0x00", "Default", "Default"),
@@ -618,17 +532,6 @@ ootEnumRoomBehaviour = [
("0x05", "Disable Darker Screen Effect (NL/Spins)", "Disable Darker Screen Effect (NL/Spins)"),
]
ootEnumExitIndex = [
("Custom", "Custom", "Custom"),
("Default", "Default", "Default"),
]
ootEnumSceneSetupPreset = [
("Custom", "Custom", "Custom"),
("All Scene Setups", "All Scene Setups", "All Scene Setups"),
("All Non-Cutscene Scene Setups", "All Non-Cutscene Scene Setups", "All Non-Cutscene Scene Setups"),
]
ootEnumDrawConfig = [
("Custom", "Custom", "Custom"),
("SDC_DEFAULT", "Default", "Default"),
@@ -689,5 +592,3 @@ ootEnumDrawConfig = [
("SDC_GANONS_TOWER_COLLAPSE_INTERIOR", "Ganon's Tower (Collapsing) (Ganon Sonogo)", "Ganon Sonogo"),
("SDC_INSIDE_GANONS_CASTLE_COLLAPSE", "Inside Ganon's Castle (Collapsing) (Ganontika Sonogo)", "Ganontika Sonogo"),
]
ootSceneNameToID = {val: key for key, val in ootSceneIDToName.items()}
+1 -3
View File
@@ -210,9 +210,7 @@ class OOTScene(OOTCommonCommands):
count = count + 1
def addRoom(self, roomIndex, roomName, roomShape):
roomModel = self.model.addSubModel(
OOTModel(self.model.f3d.F3D_VER, self.model.f3d._HW_VERSION_1, roomName + "_dl", self.model.DLFormat, None)
)
roomModel = self.model.addSubModel(OOTModel(roomName + "_dl", self.model.DLFormat, None))
room = OOTRoom(roomIndex, roomName, roomModel, roomShape)
if roomIndex in self.rooms:
raise PluginError("Repeat room index " + str(roomIndex) + " for " + str(roomName))
+30 -13
View File
@@ -3,7 +3,7 @@ from random import random
from collections import OrderedDict
from ..utility import PluginError, readFile, parentObject, hexOrDecInt, gammaInverse, yUpToZUp
from ..f3d.f3d_parser import parseMatrices, importMeshC
from ..f3d.f3d_gbi import F3D
from ..f3d.f3d_gbi import F3D, get_F3D_GBI
from ..f3d.flipbook import TextureFlipbook
from .collision.properties import OOTMaterialCollisionProperty
from .oot_model_classes import OOTF3DContext
@@ -12,6 +12,7 @@ from .scene.exporter.to_c import getDrawConfig
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 .oot_utility import (
getHeaderSettings,
@@ -147,6 +148,7 @@ class SharedSceneData:
includeCameras: bool,
includePaths: bool,
includeWaterBoxes: bool,
includeCutscenes: bool,
):
self.actorDict = {} # actor hash : blender object
self.entranceDict = {} # actor hash : blender object
@@ -162,6 +164,7 @@ class SharedSceneData:
self.includeCameras = includeCameras
self.includePaths = includePaths
self.includeWaterBoxes = includeWaterBoxes
self.includeCutscenes = includeCutscenes
def addHeaderIfItemExists(self, hash, itemType: str, headerIndex: int):
if itemType == "Actor":
@@ -192,8 +195,6 @@ class SharedSceneData:
def parseScene(
f3dType: str,
isHWv1: bool,
settings: OOTImportSceneSettingsProperty,
option: str,
):
@@ -216,7 +217,8 @@ def parseScene(
importSubdir = os.path.dirname(getSceneDirFromLevelName(sceneName)) + "/"
sceneFolderPath = ootGetPath(importPath, settings.isCustomDest, importSubdir, sceneName, False, True)
sceneData = readFile(os.path.join(sceneFolderPath, f"{sceneName}_scene.c"))
filePath = os.path.join(sceneFolderPath, f"{sceneName}_scene.c")
sceneData = readFile(filePath)
# roomData = ""
# sceneFolderFiles = [f for f in listdir(sceneFolderPath) if isfile(join(sceneFolderPath, f))]
@@ -231,7 +233,7 @@ def parseScene(
bpy.context.mode = "OBJECT"
# set scene default registers (see sDefaultDisplayList)
f3dContext = OOTF3DContext(F3D(f3dType, isHWv1), [], bpy.path.abspath(bpy.context.scene.ootDecompPath))
f3dContext = OOTF3DContext(get_F3D_GBI(), [], bpy.path.abspath(bpy.context.scene.ootDecompPath))
f3dContext.mat().prim_color = (0.5, 0.5, 0.5, 0.5)
f3dContext.mat().env_color = (0.5, 0.5, 0.5, 0.5)
@@ -260,7 +262,12 @@ def parseScene(
settings.includeCameras,
settings.includePaths,
settings.includeWaterBoxes,
settings.includeCutscenes,
)
if settings.includeCutscenes:
bpy.context.scene.ootCSNumber = importCutsceneData(None, sceneData)
sceneObj = parseSceneCommands(sceneName, None, None, sceneCommandsName, sceneData, f3dContext, 0, sharedSceneData)
bpy.context.scene.ootSceneExportObj = sceneObj
@@ -363,9 +370,14 @@ def parseSceneCommands(
if not (args[1] == "NULL" or args[1] == "0" or args[1] == "0x00"):
lightsListName = stripName(args[1])
parseLightList(sceneObj, sceneHeader, sceneData, lightsListName, headerIndex)
elif command == "SCENE_CMD_CUTSCENE_DATA":
cutsceneName = args[0]
print("Cutscene command parsing not implemented.")
elif command == "SCENE_CMD_CUTSCENE_DATA" and sharedSceneData.includeCutscenes:
sceneHeader.writeCutscene = True
sceneHeader.csWriteType = "Object"
csObjName = f"Cutscene.{args[0]}"
try:
sceneHeader.csWriteObject = bpy.data.objects[csObjName]
except:
print(f"ERROR: Cutscene ``{csObjName}`` do not exist!")
elif command == "SCENE_CMD_ALTERNATE_HEADER_LIST":
# Delay until after rooms are parsed
altHeadersListName = stripName(args[0])
@@ -715,12 +727,16 @@ def parseTransActorList(
sharedSceneData.transDict[actorHash] = actorObj
if roomIndexFront != 255:
parentObject(roomObjs[roomIndexFront], actorObj)
transActorProp.roomIndex = roomIndexBack
fromRoom = roomObjs[roomIndexFront]
toRoom = roomObjs[roomIndexBack]
if roomIndexFront != roomIndexBack:
parentObject(fromRoom, actorObj)
transActorProp.fromRoom = fromRoom
transActorProp.toRoom = toRoom
transActorProp.isRoomTransition = True
else:
parentObject(roomObjs[roomIndexBack], actorObj)
transActorProp.dontTransition = True
transActorProp.isRoomTransition = False
parentObject(toRoom, actorObj)
setCustomProperty(transActorProp, "cameraTransitionFront", camFront, ootEnumCamTransition)
setCustomProperty(transActorProp, "cameraTransitionBack", camBack, ootEnumCamTransition)
@@ -808,6 +824,7 @@ def parseSpawnList(
spawnObj.ootEmptyType = "Entrance"
spawnObj.name = "Entrance"
spawnProp = spawnObj.ootEntranceProperty
spawnProp.tiedRoom = roomObjs[roomIndex]
spawnProp.spawnIndex = spawnIndex
spawnProp.customActor = actorID != "ACTOR_PLAYER"
actorProp = spawnProp.actor
+20 -13
View File
@@ -104,14 +104,12 @@ def ootCombineSceneFiles(levelC):
return sceneC
def ootExportSceneToC(
originalSceneObj, transformMatrix, f3dType, isHWv1, sceneName, DLFormat, savePNG, exportInfo, bootToSceneOptions
):
def ootExportSceneToC(originalSceneObj, transformMatrix, sceneName, DLFormat, savePNG, exportInfo, bootToSceneOptions):
checkObjectReference(originalSceneObj, "Scene object")
isCustomExport = exportInfo.isCustomExportPath
exportPath = exportInfo.exportPath
scene = ootConvertScene(originalSceneObj, transformMatrix, f3dType, isHWv1, sceneName, DLFormat, not savePNG)
scene = ootConvertScene(originalSceneObj, transformMatrix, sceneName, DLFormat, not savePNG)
exportSubdir = ""
if exportInfo.customSubPath is not None:
@@ -492,7 +490,7 @@ def readPathProp(pathProp, obj, scene, sceneObj, sceneName, transformMatrix):
addActor(scene, ootConvertPath(sceneName, obj, relativeTransform), obj.ootSplineProperty, "pathList", obj.name)
def ootConvertScene(originalSceneObj, transformMatrix, f3dType, isHWv1, sceneName, DLFormat, convertTextureData):
def ootConvertScene(originalSceneObj, transformMatrix, sceneName, DLFormat, convertTextureData):
if originalSceneObj.type != "EMPTY" or originalSceneObj.ootEmptyType != "Scene":
raise PluginError(originalSceneObj.name + ' is not an empty with the "Scene" empty type.')
@@ -510,7 +508,7 @@ def ootConvertScene(originalSceneObj, transformMatrix, f3dType, isHWv1, sceneNam
raise PluginError("The scene has no child empties with the 'Room' empty type.")
try:
scene = OOTScene(sceneName, OOTModel(f3dType, isHWv1, sceneName + "_dl", DLFormat, None))
scene = OOTScene(sceneName, OOTModel(sceneName + "_dl", DLFormat, None))
readSceneData(scene, sceneObj.fast64.oot.scene, sceneObj.ootSceneHeader, sceneObj.ootAlternateSceneHeaders)
processedRooms = set()
@@ -784,12 +782,15 @@ def ootProcessEmpties(scene, room, sceneObj, obj, transformMatrix):
elif obj.ootEmptyType == "Transition Actor":
transActorProp = obj.ootTransitionActorProperty
if transActorProp.actor.actorID != "None":
if transActorProp.dontTransition:
front = (255, getCustomProperty(transActorProp, "cameraTransitionBack"))
back = (room.roomIndex, getCustomProperty(transActorProp, "cameraTransitionFront"))
if transActorProp.isRoomTransition:
if transActorProp.fromRoom is None or transActorProp.toRoom is None:
raise PluginError("ERROR: Missing room empty object assigned to transition.")
fromIndex = transActorProp.fromRoom.ootRoomHeader.roomIndex
toIndex = transActorProp.toRoom.ootRoomHeader.roomIndex
else:
front = (room.roomIndex, getCustomProperty(transActorProp, "cameraTransitionFront"))
back = (transActorProp.roomIndex, getCustomProperty(transActorProp, "cameraTransitionBack"))
fromIndex = toIndex = room.roomIndex
front = (fromIndex, getCustomProperty(transActorProp, "cameraTransitionFront"))
back = (toIndex, getCustomProperty(transActorProp, "cameraTransitionBack"))
transActorName = (
ootData.actorData.actorsByID[transActorProp.actor.actorID].name.replace(
@@ -818,8 +819,14 @@ def ootProcessEmpties(scene, room, sceneObj, obj, transformMatrix):
)
elif obj.ootEmptyType == "Entrance":
entranceProp = obj.ootEntranceProperty
spawnIndex = obj.ootEntranceProperty.spawnIndex
addActor(scene, OOTEntrance(room.roomIndex, spawnIndex), entranceProp.actor, "entranceList", obj.name)
spawnIndex = entranceProp.spawnIndex
if entranceProp.tiedRoom is not None:
roomIndex = entranceProp.tiedRoom.ootRoomHeader.roomIndex
else:
raise PluginError("ERROR: Missing room empty object assigned to the entrance.")
addActor(scene, OOTEntrance(roomIndex, spawnIndex), entranceProp.actor, "entranceList", obj.name)
addStartPosition(
scene,
spawnIndex,
+2 -2
View File
@@ -95,11 +95,11 @@ def ootGetLinkData(basePath: str) -> str:
class OOTModel(FModel):
def __init__(self, f3dType, isHWv1, name, DLFormat, drawLayerOverride):
def __init__(self, name, DLFormat, drawLayerOverride):
self.drawLayerOverride = drawLayerOverride
self.flipbooks: list[TextureFlipbook] = []
FModel.__init__(self, f3dType, isHWv1, name, DLFormat, GfxMatWriteMethod.WriteAll)
FModel.__init__(self, name, DLFormat, GfxMatWriteMethod.WriteAll)
# Since dynamic textures are handled by scene draw config, flipbooks should only belong to scene model.
# Thus we have this function.
+106
View File
@@ -1,5 +1,9 @@
import bpy
from bpy.types import Object, CollectionProperty
from .data import OoT_ObjectData
from .cutscene.motion.constants import ootEnumCSMotionCamMode
from .oot_constants import ootData
def upgradeObjectList(objList: CollectionProperty, objData: OoT_ObjectData):
@@ -34,3 +38,105 @@ def upgradeRoomHeaders(roomObj: Object, objData: OoT_ObjectData):
upgradeObjectList(sceneLayer.objectList, objData)
for i in range(len(altHeaders.cutsceneHeaders)):
upgradeObjectList(altHeaders.cutsceneHeaders[i].objectList, objData)
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"]
def upgradeCutsceneMotion(csMotionObj: Object):
"""Main upgrade logic for Cutscene Motion data from zcamedit"""
objName = csMotionObj.name
if csMotionObj.type == "EMPTY":
csMotionProp = csMotionObj.ootCSMotionProperty
if "zc_alist" in csMotionObj and ("Preview." in objName or "ActionList." in objName):
legacyData = csMotionObj["zc_alist"]
emptyTypeSuffix = "List" if "ActionList." in objName else "Preview"
csMotionObj.ootEmptyType = f"CS {'Player' if 'Link' in objName else 'Actor'} Cue {emptyTypeSuffix}"
if "actor_id" in legacyData:
index = legacyData["actor_id"]
if index >= 0:
cmdEnum = ootData.enumData.enumByKey["csCmd"]
cmdType = cmdEnum.itemByIndex.get(index)
if cmdType is not None:
csMotionProp.actorCueListProp.commandType = cmdType.key
else:
csMotionProp.actorCueListProp.commandType = "Custom"
csMotionProp.actorCueListProp.commandTypeCustom = f"0x{index:04X}"
del legacyData["actor_id"]
del csMotionObj["zc_alist"]
if "zc_apoint" in csMotionObj and "Point." in objName:
isPlayer = "Link" in csMotionObj.parent.name
legacyData = csMotionObj["zc_apoint"]
csMotionObj.ootEmptyType = f"CS {'Player' if isPlayer else 'Actor'} Cue"
if "start_frame" in legacyData:
csMotionProp.actorCueProp.cueStartFrame = legacyData["start_frame"]
del legacyData["start_frame"]
if "action_id" in legacyData:
playerEnum = ootData.enumData.enumByKey["csPlayerCueId"]
item = None
if isPlayer:
item = playerEnum.itemByIndex.get(int(legacyData["action_id"], base=16))
if isPlayer and item is not None:
csMotionProp.actorCueProp.playerCueID = item.key
else:
csMotionProp.actorCueProp.cueActionID = legacyData["action_id"]
del legacyData["action_id"]
del csMotionObj["zc_apoint"]
if csMotionObj.type == "ARMATURE":
camShotProp = csMotionObj.data.ootCamShotProp
if "start_frame" in csMotionObj.data:
camShotProp.shotStartFrame = csMotionObj.data["start_frame"]
del csMotionObj.data["start_frame"]
if "cam_mode" in csMotionObj.data:
camShotProp.shotCamMode = ootEnumCSMotionCamMode[csMotionObj.data["cam_mode"]][0]
del csMotionObj.data["cam_mode"]
for bone in csMotionObj.data.bones:
camShotPointProp = bone.ootCamShotPointProp
if "frames" in bone:
camShotPointProp.shotPointFrame = bone["frames"]
del bone["frames"]
if "fov" in bone:
camShotPointProp.shotPointViewAngle = bone["fov"]
del bone["fov"]
if "camroll" in bone:
camShotPointProp.shotPointRoll = bone["camroll"]
del bone["camroll"]
+82 -2
View File
@@ -1,5 +1,11 @@
import bpy, math, os, re
import bpy
import math
import os
import re
from ast import parse, Expression, Num, UnaryOp, USub, Invert, BinOp
from mathutils import Vector
from bpy.types import Object
from bpy.utils import register_class, unregister_class
from typing import Callable
from .oot_constants import ootSceneIDToName
@@ -491,6 +497,13 @@ def getEnumName(enumItems, value):
raise PluginError("Could not find enum value " + str(value))
def getEnumIndex(enumItems, value):
for i, enumTuple in enumerate(enumItems):
if enumTuple[0] == value or enumTuple[1] == value:
return i
return None
def ootConvertTranslation(translation):
return [int(round(value)) for value in translation]
@@ -501,7 +514,7 @@ def ootConvertRotation(rotation):
# parse rotaion in Vec3s format
def ootParseRotation(values):
def ootParseRotation(values: list[int]):
return [
math.radians(
(int.from_bytes(value.to_bytes(2, "big", signed=value < 0x8000), "big", signed=False) / 2**16) * 360
@@ -861,3 +874,70 @@ def getEvalParams(input: str):
raise ValueError(f"Unsupported AST node {node}")
return f"0x{_eval(node.body):X}"
def getNewPath(type: str, isClosedShape: bool):
"""
Returns a new Curve Object with the selected spline shape
Parameters:
- ``type``: the path's type (square, line, etc)
- ``isClosedShape``: choose if the spline should have an extra point to make a closed shape
"""
# create a new curve
newCurve = bpy.data.curves.new("New Path", "CURVE")
newCurve.dimensions = "3D"
# add a new spline to the curve
newSpline = newCurve.splines.new("NURBS") # comes with 1 point
# generate shape based on 'type' parameter
scaleDivBy2 = bpy.context.scene.ootBlenderScale / 2
match type:
case "Line":
newSpline.points.add(1)
for i, point in enumerate(newSpline.points):
point.co.x = i * bpy.context.scene.ootBlenderScale
point.co.w = 1
case "Triangle":
newSpline.points.add(2)
for i, point in enumerate(newSpline.points):
point.co.x = i * scaleDivBy2
if i == 1:
point.co.y = (len(newSpline.points) * scaleDivBy2) / 2
point.co.w = 1
case "Square" | "Trapezium":
newSpline.points.add(3)
for i, point in enumerate(newSpline.points):
point.co.x = i * scaleDivBy2
if i in [1, 2]:
if type == "Square":
point.co.y = (len(newSpline.points) - 1) * scaleDivBy2
if i == 1:
point.co.x = newSpline.points[0].co.x
else:
point.co.x = point.co.y
else:
point.co.y = 1 * scaleDivBy2
point.co.w = 1
case _:
raise PluginError("ERROR: Invalid Path Type!")
if isClosedShape and type != "Line":
newSpline.points.add(1)
newSpline.points[-1].co = newSpline.points[0].co
# make the curve's display accurate to the point's shape
newSpline.use_cyclic_u = True
newSpline.use_endpoint_u = False
newSpline.resolution_u = 64
newSpline.order_u = 2
# create a new object and add the curve as data
newPath = bpy.data.objects.new("New Path", newCurve)
newPath.show_name = True
newPath.location = Vector(bpy.context.scene.cursor.location)
bpy.context.view_layer.active_layer_collection.collection.objects.link(newPath)
return newPath
+57 -1
View File
@@ -1,12 +1,16 @@
import bpy
from bpy.utils import register_class, unregister_class
from ..utility import prop_split, gammaInverse
from .oot_constants import ootEnumEmptyType
from .oot_utility import getSceneObj, getRoomObj
from .scene.properties import OOTSceneProperties
from .room.properties import OOTObjectProperty, OOTRoomHeaderProperty, OOTAlternateRoomHeaderProperty
from .collision.properties import OOTWaterBoxProperty
from .cutscene.properties import OOTCutsceneProperty
from .cutscene.motion.properties import (
OOTCutsceneMotionProperty,
OOTCSMotionActorCueListProperty,
OOTCSMotionActorCueProperty,
)
from .actor.properties import (
OOTActorProperty,
@@ -14,6 +18,28 @@ from .actor.properties import (
OOTEntranceProperty,
)
# Make sure to add exceptions in utility.py - selectMeshChildrenOnly
ootEnumEmptyType = [
("None", "None", "None"),
("Scene", "Scene", "Scene"),
("Room", "Room", "Room"),
("Actor", "Actor", "Actor"),
("Transition Actor", "Transition Actor", "Transition Actor"),
("Entrance", "Entrance", "Entrance"),
("Water Box", "Water Box", "Water Box"),
("Cull Group", "Custom Cull Group", "Cull Group"),
("LOD", "LOD Group", "LOD Group"),
("Cutscene", "Cutscene Main", "Cutscene"),
("CS Actor Cue List", "CS Actor Cue List", "CS Actor Cue List"),
("CS Actor Cue", "CS Actor Cue", "CS Actor Cue"),
("CS Player Cue List", "CS Player Cue List", "CS Player Cue List"),
("CS Player Cue", "CS Player Cue", "CS Player Cue"),
("CS Actor Cue Preview", "CS Actor Cue Preview", "CS Actor Cue Preview"),
("CS Player Cue Preview", "CS Player Cue Preview", "CS Player Cue Preview"),
("CS Dummy Cue", "CS Dummy Cue", "CS Dummy Cue"),
# ('Camera Volume', 'Camera Volume', 'Camera Volume'),
]
def drawSceneHeader(box: bpy.types.UILayout, obj: bpy.types.Object):
objName = obj.name
@@ -144,6 +170,21 @@ class OOTObjectPanel(bpy.types.Panel):
csProp: OOTCutsceneProperty = obj.ootCutsceneProperty
csProp.draw_props(box, obj)
elif obj.ootEmptyType in [
"CS Actor Cue List",
"CS Player Cue List",
"CS Actor Cue Preview",
"CS Player Cue Preview",
]:
labelPrefix = "Player" if "Player" in obj.ootEmptyType else "Actor"
actorCueListProp: OOTCSMotionActorCueListProperty = 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.draw_props(box, labelPrefix, obj.ootEmptyType == "CS Dummy Cue", obj.name)
elif obj.ootEmptyType == "None":
box.label(text="Geometry can be parented to this.")
@@ -157,6 +198,21 @@ class OOT_ObjectProperties(bpy.types.PropertyGroup):
if obj.type == "EMPTY":
if obj.ootEmptyType == "Room":
OOTObjectProperty.upgrade_object(obj)
if obj.ootEmptyType in {"Entrance", "Transition Actor"}:
OOTActorProperty.upgrade_object(obj)
if any(obj.name.startswith(elem) for elem in ["ActionList.", "Point.", "Preview."]):
OOTCutsceneMotionProperty.upgrade_object(obj)
if "Point." in obj.name:
parentObj = obj.parent
if parentObj is not None:
if parentObj.children[-1] == obj:
obj.ootEmptyType = "CS Dummy Cue"
else:
print("WARNING: An Actor Cue has been detected outside an Actor Cue List: " + obj.name)
elif obj.type == "ARMATURE":
if obj.parent.name.startswith("Cutscene.") or obj.parent.ootEmptyType == "Cutscene":
OOTCutsceneMotionProperty.upgrade_object(obj)
class OOTCullGroupProperty(bpy.types.PropertyGroup):
+9 -3
View File
@@ -19,14 +19,20 @@ from bpy.props import (
from ..oot_constants import (
ootData,
ootEnumRoomMenu,
ootEnumRoomMenuAlternate,
ootEnumRoomBehaviour,
ootEnumLinkIdle,
ootEnumRoomShapeType,
ootEnumHeaderMenu,
)
ootEnumRoomMenuAlternate = [
("General", "General", "General"),
("Objects", "Objects", "Objects"),
]
ootEnumRoomMenu = ootEnumRoomMenuAlternate + [
("Alternate", "Alternate", "Alternate"),
]
class OOTObjectProperty(PropertyGroup):
expandTab: BoolProperty(name="Expand Tab")
@@ -34,7 +40,7 @@ class OOTObjectProperty(PropertyGroup):
objectIDCustom: StringProperty(default="OBJECT_CUSTOM")
@staticmethod
def upgrade_object(obj):
def upgrade_object(obj: Object):
print(f"Processing '{obj.name}'...")
upgradeRoomHeaders(obj, ootData.objectData)
@@ -1,9 +1,21 @@
from .....utility import CData, indent
from .....f3d.f3d_gbi import ScrollMethod, TextureExportSettings
from ....oot_model_classes import OOTGfxFormatter
from ....oot_constants import ootRoomShapeStructs, ootRoomShapeEntryStructs, ootEnumRoomShapeType
from ....oot_constants import ootEnumRoomShapeType
from ....oot_level_classes import OOTRoom, OOTRoomMeshGroup, OOTRoomMesh
ootRoomShapeStructs = [
"RoomShapeNormal",
"RoomShapeImage",
"RoomShapeCullable",
]
ootRoomShapeEntryStructs = [
"RoomShapeDListsEntry",
"RoomShapeDListsEntry",
"RoomShapeCullableEntry",
]
def getRoomShapeDLEntry(meshEntry: OOTRoomMeshGroup, roomShape: str):
opaqueName = meshEntry.DLGroup.opaque.name if meshEntry.DLGroup.opaque is not None else "NULL"
@@ -72,7 +72,7 @@ def getCutsceneDataCmd(outScene: OOTScene, headerIndex: int):
case _:
csDataName = outScene.csWriteCustom
return f"SCENE_CMD_CUTSCENE_DATA({csDataName})"
return indent + f"SCENE_CMD_CUTSCENE_DATA({csDataName})"
def getSceneCommandList(outScene: OOTScene, headerIndex: int):
@@ -1,15 +1,23 @@
from .....utility import CData, PluginError
from .....utility import CData, PluginError, indent
from ....oot_level_classes import OOTScene
from ....cutscene.constants import ootEnumCSTextboxTypeEntryC, ootEnumCSListTypeListC, ootEnumCSListTypeEntryC
from ....cutscene.motion.exporter import getCutsceneMotionData
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)
data.source += "\tCS_BEGIN_CUTSCENE(" + str(nentries) + ", " + str(csParent.csEndFrame) + "),\n"
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("
@@ -136,6 +144,7 @@ def ootCutsceneDataToC(csParent, csName):
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
@@ -143,8 +152,14 @@ def ootCutsceneDataToC(csParent, csName):
def getSceneCutscenes(outScene: OOTScene):
cutscenes: list[CData] = []
altHeaders = [outScene, outScene.childNightHeader, outScene.adultDayHeader, outScene.adultNightHeader]
altHeaders: list[OOTScene] = [
outScene,
outScene.childNightHeader,
outScene.adultDayHeader,
outScene.adultNightHeader,
]
altHeaders.extend(outScene.cutsceneHeaders)
csObjects = []
for i, curHeader in enumerate(altHeaders):
# curHeader is either None or an OOTScene. This can either be the main scene itself,
@@ -152,10 +167,13 @@ def getSceneCutscenes(outScene: OOTScene):
if curHeader is not None and curHeader.writeCutscene:
if curHeader.csWriteType == "Embedded":
cutscenes.append(ootCutsceneDataToC(curHeader, curHeader.cutsceneDataName(i)))
elif curHeader.csWriteType == "Object":
elif curHeader.csWriteType == "Object" and curHeader.csWriteObject.name not in csObjects:
cutscenes.append(ootCutsceneDataToC(curHeader.csWriteObject, curHeader.csWriteObject.name))
csObjects.append(curHeader.csWriteObject.name)
for extraCs in outScene.extraCutscenes:
cutscenes.append(ootCutsceneDataToC(extraCs, extraCs.name))
if not extraCs.name in csObjects:
cutscenes.append(ootCutsceneDataToC(extraCs, extraCs.name))
csObjects.append(extraCs.name)
return cutscenes
+2 -10
View File
@@ -37,14 +37,8 @@ def run_ops_without_view_layer_update(func):
def parseSceneFunc():
context = bpy.context
settings = context.scene.ootSceneImportSettings
parseScene(
context.scene.f3d_type,
context.scene.isHWv1,
settings,
settings.option,
)
settings = bpy.context.scene.ootSceneImportSettings
parseScene(settings, settings.option)
class OOT_SearchSceneEnumOperator(Operator):
@@ -178,8 +172,6 @@ class OOT_ExportScene(Operator):
ootExportSceneToC(
obj,
finalTransform,
context.scene.f3d_type,
context.scene.isHWv1,
levelName,
DLFormat.Static,
context.scene.saveTextures,
+53 -11
View File
@@ -28,9 +28,6 @@ from ..oot_utility import (
from ..oot_constants import (
ootEnumMusicSeq,
ootEnumSceneID,
ootEnumExitIndex,
ootEnumTransitionAnims,
ootEnumLightGroupMenu,
ootEnumGlobalObject,
ootEnumNaviHints,
ootEnumSkybox,
@@ -40,13 +37,49 @@ from ..oot_constants import (
ootEnumCameraMode,
ootEnumNightSeq,
ootEnumAudioSessionPreset,
ootEnumSceneMenu,
ootEnumSceneMenuAlternate,
ootEnumHeaderMenu,
ootEnumDrawConfig,
ootEnumHeaderMenuComplete,
)
ootEnumSceneMenuAlternate = [
("General", "General", "General"),
("Lighting", "Lighting", "Lighting"),
("Cutscene", "Cutscene", "Cutscene"),
("Exits", "Exits", "Exits"),
]
ootEnumSceneMenu = ootEnumSceneMenuAlternate + [
("Alternate", "Alternate", "Alternate"),
]
ootEnumLightGroupMenu = [
("Dawn", "Dawn", "Dawn"),
("Day", "Day", "Day"),
("Dusk", "Dusk", "Dusk"),
("Night", "Night", "Night"),
]
ootEnumTransitionAnims = [
("Custom", "Custom", "Custom"),
("0x00", "Spiky", "Spiky"),
("0x01", "Triforce", "Triforce"),
("0x02", "Slow Black Fade", "Slow Black Fade"),
("0x03", "Slow Day/White, Slow Night/Black Fade", "Slow Day/White, Slow Night/Black Fade"),
("0x04", "Fast Day/Black, Slow Night/Black Fade", "Fast Day/Black, Slow Night/Black Fade"),
("0x05", "Fast Day/White, Slow Night/Black Fade", "Fast Day/White, Slow Night/Black Fade"),
("0x06", "Very Slow Day/White, Slow Night/Black Fade", "Very Slow Day/White, Slow Night/Black Fade"),
("0x07", "Very Slow Day/White, Slow Night/Black Fade", "Very Slow Day/White, Slow Night/Black Fade"),
("0x0E", "Slow Sandstorm Fade", "Slow Sandstorm Fade"),
("0x0F", "Fast Sandstorm Fade", "Fast Sandstorm Fade"),
("0x20", "Iris Fade", "Iris Fade"),
("0x2C", "Shortcut Transition", "Shortcut Transition"),
]
ootEnumExitIndex = [
("Custom", "Custom", "Custom"),
("Default", "Default", "Default"),
]
class OOTSceneCommon:
ootEnumBootMode = [
@@ -219,7 +252,11 @@ class OOTSceneTableEntryProperty(PropertyGroup):
class OOTExtraCutsceneProperty(PropertyGroup):
csObject: PointerProperty(name="Cutscene Object", type=Object)
csObject: PointerProperty(
name="Cutscene Object",
type=Object,
poll=lambda self, object: object.type == "EMPTY" and object.ootEmptyType == "Cutscene",
)
class OOTSceneHeaderProperty(PropertyGroup):
@@ -264,7 +301,11 @@ class OOTSceneHeaderProperty(PropertyGroup):
writeCutscene: BoolProperty(name="Write Cutscene")
csWriteType: EnumProperty(name="Cutscene Data Type", items=ootEnumCSWriteType, default="Embedded")
csWriteCustom: StringProperty(name="CS hdr var:", default="")
csWriteObject: PointerProperty(name="Cutscene Object", type=Object)
csWriteObject: PointerProperty(
name="Cutscene Object",
type=Object,
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
@@ -508,6 +549,7 @@ class OOTImportSceneSettingsProperty(PropertyGroup):
includeCameras: BoolProperty(name="Cameras", default=True)
includePaths: BoolProperty(name="Paths", default=True)
includeWaterBoxes: BoolProperty(name="Water Boxes", default=True)
includeCutscenes: BoolProperty(name="Cutscenes", default=False)
option: EnumProperty(items=ootEnumSceneID, default="SCENE_DEKU_TREE")
def draw_props(self, layout: UILayout, sceneOption: str):
@@ -515,17 +557,19 @@ class OOTImportSceneSettingsProperty(PropertyGroup):
includeButtons1 = col.row(align=True)
includeButtons1.prop(self, "includeMesh", toggle=1)
includeButtons1.prop(self, "includeCollision", toggle=1)
includeButtons1.prop(self, "includeActors", toggle=1)
includeButtons2 = col.row(align=True)
includeButtons2.prop(self, "includeActors", toggle=1)
includeButtons2.prop(self, "includeCullGroups", toggle=1)
includeButtons2.prop(self, "includeLights", toggle=1)
includeButtons2.prop(self, "includeCameras", toggle=1)
includeButtons3 = col.row(align=True)
includeButtons3.prop(self, "includeCameras", toggle=1)
includeButtons3.prop(self, "includePaths", toggle=1)
includeButtons3.prop(self, "includeWaterBoxes", toggle=1)
includeButtons3.prop(self, "includeCutscenes", toggle=1)
col.prop(self, "isCustomDest")
if self.isCustomDest:
prop_split(col, self, "destPath", "Directory")
prop_split(col, self, "name", "Name")
@@ -534,8 +578,6 @@ class OOTImportSceneSettingsProperty(PropertyGroup):
prop_split(col, self, "subFolder", "Subfolder")
prop_split(col, self, "name", "Name")
col.label(text="Cutscenes won't be imported.")
if "SCENE_JABU_JABU" in sceneOption:
col.label(text="Pulsing wall effect won't be imported.", icon="ERROR")
@@ -210,8 +210,6 @@ def ootConvertArmatureToSkeletonWithMesh(
def ootConvertArmatureToC(
originalArmatureObj: bpy.types.Object,
convertTransformMatrix: mathutils.Matrix,
f3dType: str,
isHWv1: bool,
DLFormat: DLFormat,
savePNG: bool,
drawLayer: str,
@@ -240,7 +238,7 @@ def ootConvertArmatureToC(
removeVanillaData = settings.removeVanillaData
optimize = settings.optimize
fModel = OOTModel(f3dType, isHWv1, skeletonName, DLFormat, drawLayer)
fModel = OOTModel(skeletonName, DLFormat, drawLayer)
skeleton, fModel = ootConvertArmatureToSkeletonWithMesh(
originalArmatureObj, convertTransformMatrix, fModel, skeletonName, not savePNG, drawLayer, optimize
)
@@ -1,5 +1,5 @@
import mathutils, bpy, math
from ....f3d.f3d_gbi import F3D
from ....f3d.f3d_gbi import F3D, get_F3D_GBI
from ....f3d.f3d_parser import getImportData, parseF3D
from ....utility import hexOrDecInt, applyRotation
from ...oot_f3d_writer import ootReadActorScale
@@ -233,7 +233,7 @@ def ootImportSkeletonC(basePath: str, importSettings: OOTSkeletonImportSettings)
limbsData = matchResult.group(2)
limbList = [entry.strip()[1:] for entry in limbsData.split(",") if entry.strip() != ""]
f3dContext = OOTF3DContext(F3D("F3DEX2/LX2", False), limbList, basePath)
f3dContext = OOTF3DContext(get_F3D_GBI(), limbList, basePath)
f3dContext.mat().draw_layer.oot = drawLayer
if overlayName is not None and importSettings.autoDetectActorScale:
+1 -5
View File
@@ -120,13 +120,9 @@ class OOT_ExportSkeleton(Operator):
exportSettings: OOTSkeletonExportSettings = context.scene.fast64.oot.skeletonExportSettings
saveTextures = context.scene.saveTextures
isHWv1 = context.scene.isHWv1
f3dType = context.scene.f3d_type
drawLayer = armatureObj.ootDrawLayer
ootConvertArmatureToC(
armatureObj, finalTransform, f3dType, isHWv1, DLFormat.Static, saveTextures, drawLayer, exportSettings
)
ootConvertArmatureToC(armatureObj, finalTransform, DLFormat.Static, saveTextures, drawLayer, exportSettings)
self.report({"INFO"}, "Success!")
return {"FINISHED"}
+84 -9
View File
@@ -1,9 +1,13 @@
import bpy
from mathutils import Vector
from bpy.ops import mesh, object, curve
from bpy.types import Operator
from bpy.props import FloatProperty, StringProperty
from bpy.types import Operator, Object, Context
from bpy.props import FloatProperty, StringProperty, EnumProperty, BoolProperty
from ...operators import AddWaterBox, addMaterialByName
from ...utility import parentObject, setOrigin
from ..cutscene.motion.utility import setupCutscene, createNewCameraShot
from ..oot_utility import getNewPath
class OOT_AddWaterBox(AddWaterBox):
@@ -92,6 +96,7 @@ class OOT_AddScene(Operator):
roomObj = context.view_layer.objects.active
roomObj.ootEmptyType = "Room"
roomObj.name = "Room"
entranceObj.ootEntranceProperty.tiedRoom = roomObj
parentObject(roomObj, planeObj)
location += Vector([0, 0, 2])
@@ -143,7 +148,9 @@ class OOT_AddRoom(Operator):
class OOT_AddCutscene(Operator):
bl_idname = "object.oot_add_cutscene"
bl_label = "Add Cutscene"
bl_options = {"REGISTER", "UNDO", "PRESET"}
bl_options = {"REGISTER", "UNDO"}
csName: StringProperty(name="", default="Something", description="The Cutscene's Name without `Cutscene.`")
def execute(self, context):
if context.mode != "OBJECT":
@@ -153,30 +160,98 @@ class OOT_AddCutscene(Operator):
object.empty_add(type="ARROWS", radius=1, align="WORLD")
csObj = context.view_layer.objects.active
csObj.ootEmptyType = "Cutscene"
csObj.name = "Cutscene.Something"
csObj.name = f"Cutscene.{self.csName}"
createNewCameraShot(csObj)
setupCutscene(csObj)
object.select_all(action="DESELECT")
csObj.select_set(True)
context.view_layer.objects.active = csObj
return {"FINISHED"}
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self, width=200)
def draw(self, context):
layout = self.layout
layout.label(text="Set the Cutscene's Name")
split = layout.split(factor=0.30)
split.label(text="Cutscene.")
split.prop(self, "csName")
class OOT_AddPath(Operator):
bl_idname = "object.oot_add_path"
bl_label = "Add Path"
bl_options = {"REGISTER", "UNDO", "PRESET"}
bl_options = {"REGISTER", "UNDO"}
isClosedShape: BoolProperty(name="", default=True)
pathType: EnumProperty(
name="",
items=[
("Line", "Line", "Line"),
("Square", "Square", "Square"),
("Triangle", "Triangle", "Triangle"),
("Trapezium", "Trapezium", "Trapezium"),
],
default="Line",
)
def execute(self, context):
if context.mode != "OBJECT":
object.mode_set(mode="OBJECT")
object.select_all(action="DESELECT")
location = Vector(context.scene.cursor.location)
curve.primitive_nurbs_path_add(radius=1, align="WORLD", location=location[:])
pathObj = context.view_layer.objects.active
pathObj.name = "New Path"
pathObj = getNewPath(self.pathType, self.isClosedShape)
activeObj = context.view_layer.objects.active
if activeObj.type == "EMPTY" and activeObj.ootEmptyType == "Scene":
pathObj.parent = activeObj
object.select_all(action="DESELECT")
pathObj.select_set(True)
context.view_layer.objects.active = pathObj
return {"FINISHED"}
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self, width=320)
def draw(self, context):
layout = self.layout
layout.label(text="Path Settings")
props = [("Path Type", "pathType"), ("Closed Shape", "isClosedShape")]
for desc, propName in props:
split = layout.split(factor=0.30)
split.label(text=desc)
split.prop(self, propName)
class OOTClearTransformAndLock(Operator):
bl_idname = "object.oot_clear_transform"
bl_label = "Clear Transform (Scenes & Cutscenes)"
bl_options = {"REGISTER", "UNDO"}
def clearTransform(self, obj: Object):
print(obj.name)
prevSelect = obj.select_get()
obj.select_set(True)
object.location_clear()
object.rotation_clear()
object.scale_clear()
object.origin_clear()
if obj.type != "EMPTY":
object.transform_apply(location=True, rotation=True, scale=True)
obj.select_set(prevSelect)
def execute(self, context: Context):
try:
for obj in bpy.data.objects:
if obj.type == "EMPTY":
if obj.ootEmptyType in ["Scene", "Cutscene"]:
self.clearTransform(obj)
for childObj in obj.children_recursive:
self.clearTransform(childObj)
self.report({"INFO"}, "Success!")
return {"FINISHED"}
except:
return {"CANCELLED"}
+3
View File
@@ -7,6 +7,7 @@ from .operators import (
OOT_AddRoom,
OOT_AddCutscene,
OOT_AddPath,
OOTClearTransformAndLock,
)
@@ -22,6 +23,7 @@ class OoT_ToolsPanel(OOT_Panel):
col.operator(OOT_AddRoom.bl_idname)
col.operator(OOT_AddCutscene.bl_idname)
col.operator(OOT_AddPath.bl_idname)
col.operator(OOTClearTransformAndLock.bl_idname)
oot_operator_panel_classes = [
@@ -35,6 +37,7 @@ toolOpsToRegister = [
OOT_AddRoom,
OOT_AddCutscene,
OOT_AddPath,
OOTClearTransformAndLock,
]
+14 -27
View File
@@ -16,6 +16,7 @@ from typing import Tuple, Union, Iterable
from ..f3d.f3d_bleed import BleedGraphics
from ..f3d.f3d_gbi import (
get_F3D_GBI,
GbiMacro,
GfxTag,
FMaterial,
@@ -98,8 +99,8 @@ enumHUDPaths = {
class SM64Model(FModel):
def __init__(self, f3dType, isHWv1, name, DLFormat, matWriteMethod):
FModel.__init__(self, f3dType, isHWv1, name, DLFormat, matWriteMethod)
def __init__(self, name, DLFormat, matWriteMethod):
FModel.__init__(self, name, DLFormat, matWriteMethod)
def getDrawLayerV3(self, obj):
return int(obj.draw_layer_static)
@@ -153,8 +154,8 @@ class SM64GfxFormatter(GfxFormatter):
return data
def exportTexRectToC(dirPath, texProp, f3dType, isHWv1, texDir, savePNG, name, exportToProject, projectExportData):
fTexRect = exportTexRectCommon(texProp, f3dType, isHWv1, name, not savePNG)
def exportTexRectToC(dirPath, texProp, texDir, savePNG, name, exportToProject, projectExportData):
fTexRect = exportTexRectCommon(texProp, name, not savePNG)
if name is None or name == "":
raise PluginError("Name cannot be empty.")
@@ -270,7 +271,7 @@ def modifyDLForHUD(data):
return data
def exportTexRectCommon(texProp, f3dType, isHWv1, name, convertTextureData):
def exportTexRectCommon(texProp, name, convertTextureData):
tex = texProp.tex
if tex is None:
raise PluginError("No texture is selected.")
@@ -285,7 +286,7 @@ def exportTexRectCommon(texProp, f3dType, isHWv1, name, convertTextureData):
texProp.T.mask = ceil(log(texProp.tex.size[1], 2) - 0.001)
texProp.T.shift = 0
fTexRect = FTexRect(f3dType, isHWv1, toAlnum(name), GfxMatWriteMethod.WriteDifferingAndRevert)
fTexRect = FTexRect(toAlnum(name), GfxMatWriteMethod.WriteDifferingAndRevert)
fMaterial = FMaterial(toAlnum(name) + "_mat", DLFormat.Dynamic)
# dl_hud_img_begin
@@ -343,8 +344,6 @@ def sm64ExportF3DtoC(
obj,
DLFormat,
transformMatrix,
f3dType,
isHWv1,
texDir,
savePNG,
texSeparate,
@@ -359,8 +358,6 @@ def sm64ExportF3DtoC(
inline = bpy.context.scene.exportInlineF3D
fModel = SM64Model(
f3dType,
isHWv1,
name,
DLFormat,
GfxMatWriteMethod.WriteDifferingAndRevert if not inline else GfxMatWriteMethod.WriteAll,
@@ -481,8 +478,8 @@ def sm64ExportF3DtoC(
return fileStatus
def exportF3DtoBinary(romfile, exportRange, transformMatrix, obj, f3dType, isHWv1, segmentData, includeChildren):
fModel = SM64Model(f3dType, isHWv1, obj.name, DLFormat, GfxMatWriteMethod.WriteDifferingAndRevert)
def exportF3DtoBinary(romfile, exportRange, transformMatrix, obj, segmentData, includeChildren):
fModel = SM64Model(obj.name, DLFormat, GfxMatWriteMethod.WriteDifferingAndRevert)
fMeshes = exportF3DCommon(obj, fModel, transformMatrix, includeChildren, obj.name, DLFormat.Static, True)
fMesh = fMeshes[fModel.getDrawLayerV3(obj)]
fModel.freePalettes()
@@ -501,8 +498,8 @@ def exportF3DtoBinary(romfile, exportRange, transformMatrix, obj, f3dType, isHWv
return fMesh.draw.startAddress, addrRange, segPointerData
def exportF3DtoBinaryBank0(romfile, exportRange, transformMatrix, obj, f3dType, isHWv1, RAMAddr, includeChildren):
fModel = SM64Model(f3dType, isHWv1, obj.name, DLFormat, GfxMatWriteMethod.WriteDifferingAndRevert)
def exportF3DtoBinaryBank0(romfile, exportRange, transformMatrix, obj, RAMAddr, includeChildren):
fModel = SM64Model(obj.name, DLFormat, GfxMatWriteMethod.WriteDifferingAndRevert)
fMeshes = exportF3DCommon(obj, fModel, transformMatrix, includeChildren, obj.name, DLFormat.Static, True)
fMesh = fMeshes[fModel.getDrawLayerV3(obj)]
segmentData = copy.copy(bank0Segment)
@@ -521,14 +518,14 @@ def exportF3DtoBinaryBank0(romfile, exportRange, transformMatrix, obj, f3dType,
return (fMesh.draw.startAddress, (startAddress, startAddress + len(data)), segPointerData)
def exportF3DtoInsertableBinary(filepath, transformMatrix, obj, f3dType, isHWv1, includeChildren):
fModel = SM64Model(f3dType, isHWv1, obj.name, DLFormat, GfxMatWriteMethod.WriteDifferingAndRevert)
def exportF3DtoInsertableBinary(filepath, transformMatrix, obj, includeChildren):
fModel = SM64Model(obj.name, DLFormat, GfxMatWriteMethod.WriteDifferingAndRevert)
fMeshes = exportF3DCommon(obj, fModel, transformMatrix, includeChildren, obj.name, DLFormat.Static, True)
fMesh = fMeshes[fModel.getDrawLayerV3(obj)]
data, startRAM = getBinaryBank0F3DData(fModel, 0, [0, 0xFFFFFF])
# must happen after getBinaryBank0F3DData
address_ptrs = fModel.get_ptr_addresses(f3dType)
address_ptrs = fModel.get_ptr_addresses(get_F3D_GBI())
writeInsertableFile(filepath, insertableBinaryTypes["Display List"], address_ptrs, fMesh.draw.startAddress, data)
@@ -603,8 +600,6 @@ class SM64_ExportDL(bpy.types.Operator):
obj,
DLFormat.Static if context.scene.DLExportisStatic else DLFormat.Dynamic,
finalTransform,
context.scene.f3d_type,
context.scene.isHWv1,
bpy.context.scene.DLTexDir,
bpy.context.scene.saveTextures,
bpy.context.scene.DLSeparateTextureDef,
@@ -624,8 +619,6 @@ class SM64_ExportDL(bpy.types.Operator):
bpy.path.abspath(context.scene.DLInsertableBinaryPath),
finalTransform,
obj,
context.scene.f3d_type,
context.scene.isHWv1,
bpy.context.scene.DLincludeChildren,
)
self.report({"INFO"}, "Success! DL at " + context.scene.DLInsertableBinaryPath + ".")
@@ -648,8 +641,6 @@ class SM64_ExportDL(bpy.types.Operator):
[int(context.scene.DLExportStart, 16), int(context.scene.DLExportEnd, 16)],
finalTransform,
obj,
context.scene.f3d_type,
context.scene.isHWv1,
getAddressFromRAMAddress(int(context.scene.DLRAMAddr, 16)),
bpy.context.scene.DLincludeChildren,
)
@@ -659,8 +650,6 @@ class SM64_ExportDL(bpy.types.Operator):
[int(context.scene.DLExportStart, 16), int(context.scene.DLExportEnd, 16)],
finalTransform,
obj,
context.scene.f3d_type,
context.scene.isHWv1,
segmentData,
bpy.context.scene.DLincludeChildren,
)
@@ -796,8 +785,6 @@ class ExportTexRectDraw(bpy.types.Operator):
exportTexRectToC(
bpy.path.abspath(exportPath),
context.scene.texrect,
context.scene.f3d_type,
context.scene.isHWv1,
"textures/segment2",
context.scene.saveTextures,
context.scene.TexRectName,
+13 -11
View File
@@ -1,3 +1,4 @@
import bpy
from bpy.ops import object
from bpy.types import Bone, Object, Panel, Operator, Armature, Mesh, Material, PropertyGroup
from bpy.utils import register_class, unregister_class
@@ -81,7 +82,6 @@ enumMatOverrideOptions = [
def drawGeoInfo(panel: Panel, bone: Bone):
panel.layout.box().label(text="Geolayout Inspector")
if bone is None:
panel.layout.label(text="Edit geolayout properties in Pose mode.")
@@ -427,9 +427,14 @@ class GeolayoutBoneSidePanel(Panel):
def getSwitchOptionBone(switchArmature):
optionBones = []
for poseBone in switchArmature.pose.bones:
if poseBone.bone_group is not None and poseBone.bone_group.name == "SwitchOption":
optionBones.append(poseBone.name)
if bpy.app.version >= (4, 0, 0):
for bone in switchArmature.data.bones:
if "SwitchOption" in bone.collections:
optionBones.append(bone.name)
else:
for poseBone in switchArmature.pose.bones:
if poseBone.bone_group is not None and poseBone.bone_group.name == "SwitchOption":
optionBones.append(poseBone.name)
if len(optionBones) > 1:
raise PluginError("There should only be one switch option bone in " + switchArmature.name + ".")
elif len(optionBones) < 1:
@@ -441,18 +446,15 @@ def getSwitchOptionBone(switchArmature):
return optionBones[0]
def updateBone(self, context):
if not hasattr(context, "bone"):
print("No bone in context.")
return
def updateBone(bone, context):
armatureObj = context.object
createBoneGroups(armatureObj)
if context.bone.geo_cmd not in animatableBoneTypes:
addBoneToGroup(armatureObj, context.bone.name, context.bone.geo_cmd)
if bone.geo_cmd not in animatableBoneTypes:
addBoneToGroup(armatureObj, bone.name, bone.geo_cmd)
object.mode_set(mode="POSE")
else:
addBoneToGroup(armatureObj, context.bone.name, None)
addBoneToGroup(armatureObj, bone.name, None)
object.mode_set(mode="POSE")
+69 -73
View File
@@ -4,7 +4,7 @@ from ..f3d.f3d_parser import createBlankMaterial, parseF3DBinary
from ..panels import SM64_Panel, sm64GoalImport
from .sm64_level_parser import parseLevelAtPointer
from .sm64_constants import level_pointers, level_enums
from .sm64_geolayout_bone import enumShadowType
from .sm64_geolayout_bone import enumShadowType, animatableBoneTypes, enumBoneType
from .sm64_geolayout_constants import getGeoLayoutCmdLength, nodeGroupCmds, GEO_BRANCH_STORE
from ..utility import (
@@ -70,8 +70,6 @@ def parseGeoLayout(
useArmature,
ignoreSwitch,
shadeSmooth,
f3dType,
isHWv1,
):
currentAddress = startAddress
romfile.seek(currentAddress)
@@ -118,8 +116,6 @@ def parseGeoLayout(
0,
0,
[None] * 16 * 16,
f3dType,
isHWv1,
segmentData=segmentData,
)
@@ -169,7 +165,7 @@ def parseGeoLayout(
# bpy.ops.transform.rotate(value = math.radians(-90), orient_axis = 'X')
# bpy.ops.object.transform_apply()
if useArmature:
if bpy.app.version < (4, 0, 0) and useArmature:
armatureObj.data.layers[1] = True
"""
@@ -201,8 +197,6 @@ def parseNode(
switchLevel,
switchCount,
vertexBuffer,
f3dType,
isHWv1,
singleChild=False,
endCmd=GEO_NODE_CLOSE,
segmentData=None,
@@ -296,8 +290,6 @@ def parseNode(
switchLevel,
switchCount,
vertexBuffer,
f3dType,
isHWv1,
singleChild=switchActive,
segmentData=segmentData,
)
@@ -346,8 +338,6 @@ def parseNode(
nodeIndex[-1],
segmentData,
vertexBuffer,
f3dType,
isHWv1,
)
elif currentCmd[0] == GEO_TRANSLATE: # 0x11
@@ -364,8 +354,6 @@ def parseNode(
nodeIndex[-1],
segmentData,
vertexBuffer,
f3dType,
isHWv1,
)
elif currentCmd[0] == GEO_ROTATE: # 0x12
@@ -382,8 +370,6 @@ def parseNode(
nodeIndex[-1],
segmentData,
vertexBuffer,
f3dType,
isHWv1,
)
elif currentCmd[0] == GEO_LOAD_DL_W_OFFSET: # 0x13
@@ -400,8 +386,6 @@ def parseNode(
currentCmd,
segmentData,
vertexBuffer,
f3dType,
isHWv1,
)
elif currentCmd[0] == GEO_BILLBOARD: # 0x14
@@ -418,8 +402,6 @@ def parseNode(
nodeIndex[-1],
segmentData,
vertexBuffer,
f3dType,
isHWv1,
)
elif currentCmd[0] == GEO_LOAD_DL: # 0x15
@@ -436,8 +418,6 @@ def parseNode(
nodeIndex[-1],
segmentData,
vertexBuffer,
f3dType,
isHWv1,
)
elif currentCmd[0] == GEO_START_W_SHADOW: # 0x16
@@ -490,8 +470,6 @@ def parseNode(
nodeIndex[-1],
segmentData,
vertexBuffer,
f3dType,
isHWv1,
)
elif currentCmd[0] == GEO_START_W_RENDERAREA: # 0x20
@@ -530,11 +508,18 @@ def parseNode(
def generateMetarig(armatureObj):
armature = armatureObj.data
startBones = findStartBones(armatureObj)
createBoneGroups(armatureObj)
for boneName in startBones:
traverseArmatureForMetarig(armatureObj, boneName, None)
armatureObj.data.layers = createBoneLayerMask([boneLayers["visual"]])
if bpy.app.version >= (4, 0, 0):
if not "visual" in armature.collections:
armature.collections.new(name="visual")
armature.collections["visual"].assign(armature.bones[boneName])
else:
armatureObj.data.layers = createBoneLayerMask([boneLayers["visual"]])
if bpy.context.mode != "OBJECT":
bpy.ops.object.mode_set(mode="OBJECT")
@@ -542,15 +527,32 @@ def generateMetarig(armatureObj):
def traverseArmatureForMetarig(armatureObj, boneName, parentName):
if bpy.context.mode != "OBJECT":
bpy.ops.object.mode_set(mode="OBJECT")
poseBone = armatureObj.pose.bones[boneName]
if poseBone.bone_group is None:
processBoneMeta(armatureObj, boneName, parentName)
elif poseBone.bone_group.name == "Ignore":
return
armature = armatureObj.data
bone = armature.bones[boneName]
poseBone = armatureObj.pose.bones[boneName]
nextParentName = boneName if poseBone.bone_group is None else parentName
childrenNames = [child.name for child in poseBone.children]
if bpy.app.version >= (4, 0, 0):
if "Ignore" in bone.collections:
return
nonAnimatableBoneTypes = set([item[0] for item in enumBoneType]) - animatableBoneTypes
isAnimatableBone = not any([item in bone.collections for item in nonAnimatableBoneTypes])
if isAnimatableBone:
processBoneMeta(armatureObj, boneName, parentName)
nextParentName = boneName if isAnimatableBone else parentName
bone = armature.bones[boneName] # re-obtain reference after edit mode changes
childrenNames = [child.name for child in bone.children]
else:
if poseBone.bone_group is None:
processBoneMeta(armatureObj, boneName, parentName)
elif poseBone.bone_group.name == "Ignore":
return
poseBone = armatureObj.pose.bones[boneName] # re-obtain reference after edit mode changes
nextParentName = boneName if poseBone.bone_group is None else parentName
childrenNames = [child.name for child in poseBone.children]
for childName in childrenNames:
traverseArmatureForMetarig(armatureObj, childName, nextParentName)
@@ -558,6 +560,7 @@ def traverseArmatureForMetarig(armatureObj, boneName, parentName):
def processBoneMeta(armatureObj, boneName, parentName):
bpy.ops.object.mode_set(mode="EDIT")
bone = armatureObj.data.edit_bones[boneName]
armature = armatureObj.data
# create meta bone, which the actual bone copies the rotation of
metabone = armatureObj.data.edit_bones.new("meta_" + boneName)
@@ -596,16 +599,33 @@ def processBoneMeta(armatureObj, boneName, parentName):
translateConstraint.target = armatureObj
translateConstraint.subtarget = metaboneName
metabone.layers = createBoneLayerMask([boneLayers["meta"]])
if bpy.app.version >= (4, 0, 0):
if "meta" not in armature.collections:
armature.collections.new(name="meta")
armature.collections["meta"].assign(metabone)
if not "visual" in armature.collections:
armature.collections.new(name="visual")
armature.collections["visual"].assign(visualBone)
# Ignore collection should always be created, but check just in case
# (generateMetarig() calls createBoneGroups() before traverseArmatureForMetarig())
if not "Ignore" in armature.collections:
armature.collections.new(name="Ignore")
armature.collections["Ignore"].assign(visualBone)
armature.collections["Ignore"].assign(metabone)
else:
metabone.layers = createBoneLayerMask([boneLayers["meta"]])
visualBone.layers = createBoneLayerMask([boneLayers["visual"]])
metabonePose.bone_group_index = getBoneGroupIndex(armatureObj, "Ignore")
visualBonePose.bone_group_index = getBoneGroupIndex(armatureObj, "Ignore")
metabone.use_deform = False
metabonePose.lock_rotation = (True, True, True)
visualBone.layers = createBoneLayerMask([boneLayers["visual"]])
visualBone.use_deform = False
metabonePose.bone_group_index = getBoneGroupIndex(armatureObj, "Ignore")
visualBonePose.bone_group_index = getBoneGroupIndex(armatureObj, "Ignore")
bpy.ops.object.mode_set(mode="EDIT")
metabone = armatureObj.data.edit_bones[metaboneName]
visualBone = armatureObj.data.edit_bones[visualBoneName]
@@ -644,6 +664,7 @@ def processBoneMeta(armatureObj, boneName, parentName):
def createConnectBone(armatureObj, childName, parentName):
armature = armatureObj.data
child = armatureObj.data.edit_bones[childName]
parent = armatureObj.data.edit_bones[parentName]
@@ -665,8 +686,16 @@ def createConnectBone(armatureObj, childName, parentName):
bpy.ops.object.mode_set(mode="OBJECT")
connectPoseBone = armatureObj.pose.bones[connectBoneName]
connectBone = armatureObj.data.bones[connectBoneName]
connectBone.layers = createBoneLayerMask([boneLayers["visual"]])
connectPoseBone.bone_group_index = getBoneGroupIndex(armatureObj, "Ignore")
if bpy.app.version > (4, 0, 0):
if not "visual" in armature.collections:
armature.collections.new(name="visual")
armature.collections["visual"].assign(connectBone)
armature.collections["Ignore"].assign(connectBone)
else:
connectBone.layers = createBoneLayerMask([boneLayers["visual"]])
connectPoseBone.bone_group_index = getBoneGroupIndex(armatureObj, "Ignore")
connectPoseBone.lock_rotation = (True, True, True)
bpy.ops.object.mode_set(mode="EDIT")
@@ -817,10 +846,7 @@ def parseDL(
nodeIndex,
segmentData,
vertexBuffer,
f3dType,
isHWv1,
):
drawLayer = bitMask(currentCmd[1], 0, 4)
romfile.seek(currentAddress)
@@ -841,8 +867,6 @@ def parseDL(
nodeIndex,
"DisplayList",
vertexBuffer,
f3dType,
isHWv1,
)
if armatureObj is not None:
bone = armatureObj.data.bones[boneName]
@@ -865,8 +889,6 @@ def parseDLWithOffset(
currentCmd,
segmentData,
vertexBuffer,
f3dType,
isHWv1,
):
print("DL_OFFSET " + hex(currentAddress))
romfile.seek(currentAddress)
@@ -968,10 +990,7 @@ def handleNodeCommon(
nodeIndex,
boneGroupName,
vertexBuffer,
f3dType,
isHWv1,
):
boneName = format(nodeIndex, "03") + "-" + boneGroupName.lower()
if armatureObj is not None:
@@ -1012,8 +1031,6 @@ def parseScale(
nodeIndex,
segmentData,
vertexBuffer,
f3dType,
isHWv1,
):
print("SCALE " + hex(currentAddress))
@@ -1042,8 +1059,6 @@ def parseScale(
nodeIndex,
"Scale",
vertexBuffer,
f3dType,
isHWv1,
)
if armatureObj is not None:
bone = armatureObj.data.bones[boneName]
@@ -1069,8 +1084,6 @@ def parseTranslateRotate(
nodeIndex,
segmentData,
vertexBuffer,
f3dType,
isHWv1,
):
print("TRANSLATE_ROTATE " + hex(currentAddress))
@@ -1127,8 +1140,6 @@ def parseTranslateRotate(
nodeIndex,
"TranslateRotate",
vertexBuffer,
f3dType,
isHWv1,
)
if armatureObj is not None:
bone = armatureObj.data.bones[boneName]
@@ -1159,8 +1170,6 @@ def parseTranslate(
nodeIndex,
segmentData,
vertexBuffer,
f3dType,
isHWv1,
):
print("TRANSLATE " + hex(currentAddress))
@@ -1193,8 +1202,6 @@ def parseTranslate(
nodeIndex,
"Translate",
vertexBuffer,
f3dType,
isHWv1,
)
if armatureObj is not None:
bone = armatureObj.data.bones[boneName]
@@ -1220,8 +1227,6 @@ def parseRotate(
nodeIndex,
segmentData,
vertexBuffer,
f3dType,
isHWv1,
):
print("ROTATE " + hex(currentAddress))
@@ -1254,8 +1259,6 @@ def parseRotate(
nodeIndex,
"Rotate",
vertexBuffer,
f3dType,
isHWv1,
)
if armatureObj is not None:
bone = armatureObj.data.bones[boneName]
@@ -1280,8 +1283,6 @@ def parseBillboard(
nodeIndex,
segmentData,
vertexBuffer,
f3dType,
isHWv1,
):
print("BILLBOARD " + hex(currentAddress))
@@ -1314,8 +1315,6 @@ def parseBillboard(
nodeIndex,
"Billboard",
vertexBuffer,
f3dType,
isHWv1,
)
if armatureObj is not None:
bone = armatureObj.data.bones[boneName]
@@ -1461,7 +1460,6 @@ def getMarioBoneName(startRelativeAddr, armatureData, default="sm64_mesh"):
def assignMarioGeoMetadata(obj, commandAddress, geoStartAddress, cmdType, armatureData, lastTransRotAddr=None):
# for geo_pointer reading offsets:
# cmd = 0
# draw layer = 1
@@ -1558,8 +1556,6 @@ class SM64_ImportGeolayout(bpy.types.Operator):
generateArmature,
ignoreSwitch,
True,
context.scene.f3d_type,
context.scene.isHWv1,
)
romfileSrc.close()
+32 -9
View File
@@ -55,21 +55,33 @@ def createBoneLayerMask(values):
def createBoneGroups(armatureObj):
for (groupName, properties) in boneNodeProperties.items():
if getBoneGroupByName(armatureObj, groupName) is None:
boneGroup = armatureObj.pose.bone_groups.new(name=groupName)
boneGroup.color_set = properties.theme
armature = armatureObj.data
for groupName, properties in boneNodeProperties.items():
if bpy.app.version >= (4, 0, 0):
if groupName not in armature.collections:
boneGroup = armature.collections.new(name=groupName)
else:
if getBoneGroupByName(armatureObj, groupName) is None:
boneGroup = armatureObj.pose.bone_groups.new(name=groupName)
boneGroup.color_set = properties.theme
def addBoneToGroup(armatureObj, boneName, groupName):
armature = armatureObj.data
if groupName is None:
if bpy.context.mode != "OBJECT":
bpy.ops.object.mode_set(mode="OBJECT")
posebone = armatureObj.pose.bones[boneName]
bone = armatureObj.data.bones[boneName]
posebone.bone_group = None
bone = armature.bones[boneName]
bone.use_deform = True
bone.layers = createBoneLayerMask([boneLayers["anim"]])
if bpy.app.version >= (4, 0, 0):
if not "anim" in armature.collections:
armature.collections.new(name="anim")
armature.collections["anim"].assign(bone)
else:
posebone.bone_group = None
bone.layers = createBoneLayerMask([boneLayers["anim"]])
posebone.lock_location = (False, False, False)
posebone.lock_rotation = (False, False, False)
posebone.lock_scale = (False, False, False)
@@ -80,13 +92,24 @@ def addBoneToGroup(armatureObj, boneName, groupName):
if bpy.context.mode != "OBJECT":
bpy.ops.object.mode_set(mode="OBJECT")
posebone = armatureObj.pose.bones[boneName]
bone = armatureObj.data.bones[boneName]
posebone.bone_group_index = getBoneGroupIndex(armatureObj, groupName)
if bpy.app.version >= (4, 0, 0):
armature.collections[groupName].assign(bone)
else:
posebone.bone_group_index = getBoneGroupIndex(armatureObj, groupName)
if groupName != "Ignore":
bone.use_deform = boneNodeProperties[groupName].deform
if groupName != "DisplayList":
bone.layers = createBoneLayerMask([boneLayers["other"]])
if bpy.app.version >= (4, 0, 0):
if not "other" in armature.collections:
armature.collections.new(name="other")
armature.collections["other"].assign(bone)
else:
bone.layers = createBoneLayerMask([boneLayers["other"]])
if groupName != "SwitchOption":
posebone.lock_location = (True, True, True)
posebone.lock_rotation = (True, True, True)
+28 -104
View File
@@ -62,7 +62,6 @@ from ..utility import (
from ..f3d.f3d_material import (
isTexturePointSampled,
isLightingDisabled,
)
from ..f3d.f3d_writer import (
@@ -78,10 +77,10 @@ from ..f3d.f3d_writer import (
saveMeshWithLargeTexturesByFaces,
saveMeshByFaces,
getF3DVert,
convertVertexData,
)
from ..f3d.f3d_gbi import (
get_F3D_GBI,
GfxList,
GfxListTag,
GfxMatWriteMethod,
@@ -355,14 +354,9 @@ def appendRevertToGeolayout(geolayoutGraph, fModel):
# Convert to Geolayout
def convertArmatureToGeolayout(
armatureObj, obj, convertTransformMatrix, f3dType, isHWv1, camera, name, DLFormat, convertTextureData
):
def convertArmatureToGeolayout(armatureObj, obj, convertTransformMatrix, camera, name, DLFormat, convertTextureData):
inline = bpy.context.scene.exportInlineF3D
fModel = SM64Model(
f3dType,
isHWv1,
name,
DLFormat,
GfxMatWriteMethod.WriteDifferingAndRevert if not inline else GfxMatWriteMethod.WriteAll,
@@ -426,14 +420,11 @@ def convertArmatureToGeolayout(
# Camera is unused here
def convertObjectToGeolayout(
obj, convertTransformMatrix, f3dType, isHWv1, camera, name, fModel: FModel, areaObj, DLFormat, convertTextureData
obj, convertTransformMatrix, camera, name, fModel: FModel, areaObj, DLFormat, convertTextureData
):
inline = bpy.context.scene.exportInlineF3D
if fModel is None:
fModel = SM64Model(
f3dType,
isHWv1,
name,
DLFormat,
GfxMatWriteMethod.WriteDifferingAndRevert if not inline else GfxMatWriteMethod.WriteAll,
@@ -503,8 +494,6 @@ def exportGeolayoutArmatureC(
armatureObj,
obj,
convertTransformMatrix,
f3dType,
isHWv1,
dirPath,
texDir,
savePNG,
@@ -519,7 +508,7 @@ def exportGeolayoutArmatureC(
DLFormat,
):
geolayoutGraph, fModel = convertArmatureToGeolayout(
armatureObj, obj, convertTransformMatrix, f3dType, isHWv1, camera, dirName, DLFormat, not savePNG
armatureObj, obj, convertTransformMatrix, camera, dirName, DLFormat, not savePNG
)
return saveGeolayoutC(
@@ -542,8 +531,6 @@ def exportGeolayoutArmatureC(
def exportGeolayoutObjectC(
obj,
convertTransformMatrix,
f3dType,
isHWv1,
dirPath,
texDir,
savePNG,
@@ -558,7 +545,7 @@ def exportGeolayoutObjectC(
DLFormat,
):
geolayoutGraph, fModel = convertObjectToGeolayout(
obj, convertTransformMatrix, f3dType, isHWv1, camera, dirName, None, None, DLFormat, not savePNG
obj, convertTransformMatrix, camera, dirName, None, None, DLFormat, not savePNG
)
return saveGeolayoutC(
@@ -811,29 +798,27 @@ def saveGeolayoutC(
# Insertable Binary
def exportGeolayoutArmatureInsertableBinary(
armatureObj, obj, convertTransformMatrix, f3dType, isHWv1, filepath, camera
):
def exportGeolayoutArmatureInsertableBinary(armatureObj, obj, convertTransformMatrix, filepath, camera):
geolayoutGraph, fModel = convertArmatureToGeolayout(
armatureObj, obj, convertTransformMatrix, f3dType, isHWv1, camera, armatureObj.name, DLFormat.Static, True
armatureObj, obj, convertTransformMatrix, camera, armatureObj.name, DLFormat.Static, True
)
saveGeolayoutInsertableBinary(geolayoutGraph, fModel, filepath, f3dType)
saveGeolayoutInsertableBinary(geolayoutGraph, fModel, filepath)
def exportGeolayoutObjectInsertableBinary(obj, convertTransformMatrix, f3dType, isHWv1, filepath, camera):
def exportGeolayoutObjectInsertableBinary(obj, convertTransformMatrix, filepath, camera):
geolayoutGraph, fModel = convertObjectToGeolayout(
obj, convertTransformMatrix, f3dType, isHWv1, camera, obj.name, None, None, DLFormat.Static, True
obj, convertTransformMatrix, camera, obj.name, None, None, DLFormat.Static, True
)
saveGeolayoutInsertableBinary(geolayoutGraph, fModel, filepath, f3dType)
saveGeolayoutInsertableBinary(geolayoutGraph, fModel, filepath)
def saveGeolayoutInsertableBinary(geolayoutGraph, fModel, filepath, f3d):
def saveGeolayoutInsertableBinary(geolayoutGraph, fModel, filepath):
data, startRAM = getBinaryBank0GeolayoutData(fModel, geolayoutGraph, 0, [0, 0xFFFFFF])
address_ptrs = geolayoutGraph.get_ptr_addresses()
address_ptrs.extend(fModel.get_ptr_addresses(f3d))
address_ptrs.extend(fModel.get_ptr_addresses(get_F3D_GBI()))
writeInsertableFile(
filepath, insertableBinaryTypes["Geolayout"], address_ptrs, geolayoutGraph.startGeolayout.startAddress, data
@@ -850,14 +835,11 @@ def exportGeolayoutArmatureBinaryBank0(
levelCommandPos,
modelID,
textDumpFilePath,
f3dType,
isHWv1,
RAMAddr,
camera,
):
geolayoutGraph, fModel = convertArmatureToGeolayout(
armatureObj, obj, convertTransformMatrix, f3dType, isHWv1, camera, armatureObj.name, DLFormat.Static, True
armatureObj, obj, convertTransformMatrix, camera, armatureObj.name, DLFormat.Static, True
)
return saveGeolayoutBinaryBank0(
@@ -873,14 +855,11 @@ def exportGeolayoutObjectBinaryBank0(
levelCommandPos,
modelID,
textDumpFilePath,
f3dType,
isHWv1,
RAMAddr,
camera,
):
geolayoutGraph, fModel = convertObjectToGeolayout(
obj, convertTransformMatrix, f3dType, isHWv1, camera, obj.name, None, None, DLFormat.Static, True
obj, convertTransformMatrix, camera, obj.name, None, None, DLFormat.Static, True
)
return saveGeolayoutBinaryBank0(
@@ -940,13 +919,10 @@ def exportGeolayoutArmatureBinary(
levelCommandPos,
modelID,
textDumpFilePath,
f3dType,
isHWv1,
camera,
):
geolayoutGraph, fModel = convertArmatureToGeolayout(
armatureObj, obj, convertTransformMatrix, f3dType, isHWv1, camera, armatureObj.name, DLFormat.Static, True
armatureObj, obj, convertTransformMatrix, camera, armatureObj.name, DLFormat.Static, True
)
return saveGeolayoutBinary(
@@ -963,13 +939,10 @@ def exportGeolayoutObjectBinary(
levelCommandPos,
modelID,
textDumpFilePath,
f3dType,
isHWv1,
camera,
):
geolayoutGraph, fModel = convertObjectToGeolayout(
obj, convertTransformMatrix, f3dType, isHWv1, camera, obj.name, None, None, DLFormat.Static, True
obj, convertTransformMatrix, camera, obj.name, None, None, DLFormat.Static, True
)
return saveGeolayoutBinary(
@@ -1025,6 +998,7 @@ def geoWriteTextDump(textDumpFilePath, geolayoutGraph, levelData):
# are converted to SwitchOverrideNodes. During this process, any material
# override geometry will be generated as well.
# Afterward, the node hierarchy is traversed again, and any SwitchOverride
# nodes are converted to actual geolayout node hierarchies.
def generateSwitchOptions(transformNode, geolayout, geolayoutGraph, prefix):
@@ -1489,7 +1463,6 @@ def processMesh(
transformNode = TransformNode(node)
if obj.type != "EMPTY" and (obj.use_render_range or obj.add_shadow or obj.add_func):
parentTransformNode.children.append(transformNode)
transformNode.parent = parentTransformNode
transformNode.node.hasDL = False
@@ -1631,7 +1604,6 @@ def processBone(
):
bone = armatureObj.data.bones[boneName]
poseBone = armatureObj.pose.bones[boneName]
boneGroup = poseBone.bone_group
finalTransform = copy.deepcopy(transformMatrix)
materialOverrides = copy.copy(materialOverrides)
@@ -2139,6 +2111,7 @@ def checkIfFirstNonASMNode(childNode):
# parent connects child node to itself
# skinned node handled by child
# A skinned mesh node should be before a mesh node.
# However, other transform nodes may exist in between two mesh nodes,
# So the skinned mesh node must be inserted before any of those transforms.
@@ -2218,7 +2191,6 @@ def addSkinnedMeshNode(armatureObj, boneName, skinnedMesh, transformNode, parent
highestChildIndex > 0
and type(highestChildNode.parent.children[highestChildIndex - 1].node) is FunctionNode
):
precedingFunctionCmds.insert(0, copy.deepcopy(highestChildNode.parent.children[highestChildIndex - 1]))
highestChildIndex -= 1
# _____________
@@ -2390,7 +2362,6 @@ def saveModelGivenVertexGroup(
fMeshes = {}
fSkinnedMeshes = {}
for drawLayer, materialFaces in skinnedFaces.items():
meshName = getFMeshName(vertexGroup, namePrefix, drawLayer, False)
checkUniqueBoneNames(fModel, meshName, vertexGroup)
skinnedMeshName = getFMeshName(vertexGroup, namePrefix, drawLayer, True)
@@ -2471,7 +2442,7 @@ def saveModelGivenVertexGroup(
)
# Must be done after all geometry saved
for (material, specificMat, overrideType) in materialOverrides:
for material, specificMat, overrideType in materialOverrides:
for drawLayer, fMesh in fMeshes.items():
saveOverrideDraw(obj, fModel, material, specificMat, overrideType, fMesh, drawLayer, convertTextureData)
for drawLayer, fMesh in fSkinnedMeshes.items():
@@ -2503,7 +2474,10 @@ def saveOverrideDraw(
last_replaced = None
command_index = 0
def find_material_from_jump_cmd(material_list: tuple[tuple[bpy.types.Material, str, FAreaData], tuple[FMaterial, tuple[int, int]]], dl_jump: SPDisplayList):
def find_material_from_jump_cmd(
material_list: tuple[tuple[bpy.types.Material, str, FAreaData], tuple[FMaterial, tuple[int, int]]],
dl_jump: SPDisplayList,
):
if dl_jump.displayList.tag == GfxListTag.Geometry:
return None, None
for mat in material_list:
@@ -2635,16 +2609,15 @@ def splitSkinnedFacesIntoTwoGroups(skinnedFaces, fModel, obj, uv_data, drawLayer
material = obj.material_slots[material_index].material
fMaterial, texDimensions = saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData)
exportVertexColors = isLightingDisabled(material)
convertInfo = LoopConvertInfo(uv_data, obj, exportVertexColors)
convertInfo = LoopConvertInfo(uv_data, obj, material)
for skinnedFace in skinnedFaceArray:
for (face, loop) in skinnedFace.loopsInGroup:
for face, loop in skinnedFace.loopsInGroup:
f3dVert = getF3DVert(loop, face, convertInfo, obj.data)
bufferVert = BufferVertex(f3dVert, None, material_index)
if bufferVert not in inGroupVerts:
inGroupVerts.append(bufferVert)
loopDict[loop] = f3dVert
for (face, loop) in skinnedFace.loopsNotInGroup:
for face, loop in skinnedFace.loopsNotInGroup:
vert = obj.data.vertices[loop.vertex_index]
if vert not in notInGroupBlenderVerts:
notInGroupBlenderVerts.append(vert)
@@ -2723,7 +2696,6 @@ def saveSkinnedMeshByMaterial(
materialKey = (material, drawLayerKey, fModel.global_data.getCurrentAreaKey(material))
fMaterial, texDimensions = fModel.getMaterialAndHandleShared(materialKey)
isPointSampled = isTexturePointSampled(material)
exportVertexColors = isLightingDisabled(material)
skinnedTriGroup = fSkinnedMesh.tri_group_new(fMaterial)
fSkinnedMesh.draw.commands.append(SPDisplayList(fMaterial.material))
@@ -2735,16 +2707,11 @@ def saveSkinnedMeshByMaterial(
for bufferVert in vertData:
skinnedTriGroup.vertexList.vertices.append(
convertVertexData(
bufferVert.f3dVert.toVtx(
obj.data,
bufferVert.f3dVert.position,
bufferVert.f3dVert.uv,
bufferVert.f3dVert.stOffset,
bufferVert.f3dVert.getColorOrNormal(),
texDimensions,
parentMatrix,
isPointSampled,
exportVertexColors,
)
)
@@ -2796,33 +2763,6 @@ def saveSkinnedMeshByMaterial(
)
return fMesh, fSkinnedMesh
# for material_index, skinnedFaceArray in skinnedFaces.items():
#
# # We've already saved all materials, this just returns the existing ones.
# material = obj.material_slots[material_index].material
# fMaterial, texDimensions = \
# saveOrGetF3DMaterial(material, fModel, obj, drawLayer, convertTextureData)
# isPointSampled = isTexturePointSampled(material)
# exportVertexColors = isLightingDisabled(material)
#
# triGroup = fMesh.tri_group_new(fMaterial)
# fMesh.draw.commands.append(SPDisplayList(fMaterial.material))
# fMesh.draw.commands.append(SPDisplayList(triGroup.triList))
# if fMaterial.revert is not None:
# fMesh.draw.commands.append(SPDisplayList(fMaterial.revert))
#
# convertInfo = LoopConvertInfo(uv_data, obj, exportVertexColors)
# triConverter = TriangleConverter(triConverterInfo, texDimensions, material,
# None, triGroup.triList, triGroup.vertexList, copy.deepcopy(existingVertData), copy.deepcopy(matRegionDict))
# saveTriangleStrip(triConverter, [skinnedFace.bFace for skinnedFace in skinnedFaceArray], None, obj.data, True)
# saveTriangleStrip(triConverterClass,
# [skinnedFace.bFace for skinnedFace in skinnedFaceArray], None,
# convertInfo, triGroup.triList, triGroup.vertexList, fModel.f3d,
# texDimensions, currentMatrix, isPointSampled, exportVertexColors,
# copy.deepcopy(existingVertData), copy.deepcopy(matRegionDict),
# infoDict, obj.data, None, True)
return fMesh, fSkinnedMesh
def writeDynamicMeshFunction(name, displayList):
@@ -2897,8 +2837,6 @@ class SM64_ExportGeolayoutObject(ObjectDataExporter):
exportGeolayoutObjectC(
obj,
finalTransform,
context.scene.f3d_type,
context.scene.isHWv1,
exportPath,
bpy.context.scene.geoTexDir,
saveTextures,
@@ -2917,8 +2855,6 @@ class SM64_ExportGeolayoutObject(ObjectDataExporter):
exportGeolayoutObjectInsertableBinary(
obj,
finalTransform,
context.scene.f3d_type,
context.scene.isHWv1,
bpy.path.abspath(bpy.context.scene.geoInsertableBinaryPath),
None,
)
@@ -2954,8 +2890,6 @@ class SM64_ExportGeolayoutObject(ObjectDataExporter):
finalTransform,
*modelLoadInfo,
textDumpFilePath,
context.scene.f3d_type,
context.scene.isHWv1,
getAddressFromRAMAddress(int(context.scene.geoRAMAddr, 16)),
None,
)
@@ -2968,8 +2902,6 @@ class SM64_ExportGeolayoutObject(ObjectDataExporter):
segmentData,
*modelLoadInfo,
textDumpFilePath,
context.scene.f3d_type,
context.scene.isHWv1,
None,
)
@@ -3108,8 +3040,6 @@ class SM64_ExportGeolayoutArmature(bpy.types.Operator):
armatureObj,
obj,
finalTransform,
context.scene.f3d_type,
context.scene.isHWv1,
exportPath,
bpy.context.scene.geoTexDir,
saveTextures,
@@ -3130,8 +3060,6 @@ class SM64_ExportGeolayoutArmature(bpy.types.Operator):
armatureObj,
obj,
finalTransform,
context.scene.f3d_type,
context.scene.isHWv1,
bpy.path.abspath(bpy.context.scene.geoInsertableBinaryPath),
None,
)
@@ -3168,8 +3096,6 @@ class SM64_ExportGeolayoutArmature(bpy.types.Operator):
finalTransform,
*modelLoadInfo,
textDumpFilePath,
context.scene.f3d_type,
context.scene.isHWv1,
getAddressFromRAMAddress(int(context.scene.geoRAMAddr, 16)),
None,
)
@@ -3183,8 +3109,6 @@ class SM64_ExportGeolayoutArmature(bpy.types.Operator):
segmentData,
*modelLoadInfo,
textDumpFilePath,
context.scene.f3d_type,
context.scene.isHWv1,
None,
)
+6 -9
View File
@@ -697,10 +697,7 @@ class SM64OptionalFileStatus:
self.starSelectC = False
def exportLevelC(
obj, transformMatrix, f3dType, isHWv1, levelName, exportDir, savePNG, customExport, levelCameraVolumeName, DLFormat
):
def exportLevelC(obj, transformMatrix, levelName, exportDir, savePNG, customExport, levelCameraVolumeName, DLFormat):
fileStatus = SM64OptionalFileStatus()
if customExport:
@@ -725,7 +722,11 @@ def exportLevelC(
puppycamVolumeString = ""
inline = bpy.context.scene.exportInlineF3D
fModel = SM64Model(f3dType, isHWv1, levelName + "_dl", DLFormat, GfxMatWriteMethod.WriteDifferingAndRevert if not inline else GfxMatWriteMethod.WriteAll)
fModel = SM64Model(
levelName + "_dl",
DLFormat,
GfxMatWriteMethod.WriteDifferingAndRevert if not inline else GfxMatWriteMethod.WriteAll,
)
childAreas = [child for child in obj.children if child.type == "EMPTY" and child.sm64_obj_type == "Area Root"]
if len(childAreas) == 0:
raise PluginError("The level root has no child empties with the 'Area Root' object type.")
@@ -767,8 +768,6 @@ def exportLevelC(
geolayoutGraph, fModel = convertObjectToGeolayout(
obj,
transformMatrix,
f3dType,
isHWv1,
child.areaCamera,
levelName + "_" + areaName,
fModel,
@@ -1181,8 +1180,6 @@ class SM64_ExportLevel(ObjectDataExporter):
fileStatus = exportLevelC(
obj,
finalTransform,
context.scene.f3d_type,
context.scene.isHWv1,
levelName,
exportPath,
context.scene.saveTextures,
+76 -9
View File
@@ -1,8 +1,9 @@
import bpy, random, string, os, math, traceback, re, os, mathutils, ast, operator
from math import pi, ceil, degrees, radians
from math import pi, ceil, degrees, radians, copysign
from mathutils import *
from .utility_anim import *
from typing import Callable, Iterable, Any
from typing import Callable, Iterable, Any, Tuple, Optional
from bpy.types import UILayout
CollectionProperty = Any # collection prop as defined by using bpy.props.CollectionProperty
@@ -600,6 +601,21 @@ to_s16 = lambda x: cast_integer(round(x), 16, True)
radians_to_s16 = lambda d: to_s16(d * 0x10000 / (2 * math.pi))
def int_from_s16(value: int) -> int:
value &= 0xFFFF
if value >= 0x8000:
value -= 0x10000
return value
def int_from_s16_str(value: str) -> int:
return int_from_s16(int(value, 0))
def float_from_u16_str(value: str) -> float:
return float(int(value, 0)) / (2**16)
def decompFolderMessage(layout):
layout.box().label(text="This will export to your decomp folder.")
@@ -1184,6 +1200,14 @@ def prop_split(layout, data, field, name, **prop_kwargs):
split.prop(data, field, text="", **prop_kwargs)
def multilineLabel(layout: UILayout, text: str, icon: str = "NONE"):
layout = layout.column()
for i, line in enumerate(text.split("\n")):
r = layout.row()
r.label(text = line, icon = icon if i == 0 else "NONE")
r.scale_y = 0.75
def toAlnum(name, exceptions=[]):
if name is None or name == "":
return None
@@ -1425,13 +1449,56 @@ def normToSigned8Vector(normal):
return [int.from_bytes(int(value * 127).to_bytes(1, "big", signed=True), "big") for value in normal]
# Normal values are signed bytes (-128 to 127)
# Normalized magnitude = 127
def convertNormal(normal):
F3DNormal = bytearray(0)
for axis in normal:
F3DNormal.extend(int(axis * 127).to_bytes(1, "big", signed=True))
return F3DNormal
def unpackNormalS8(packedNormal: int) -> Tuple[int, int, int]:
assert isinstance(packedNormal, int) and packedNormal >= 0 and packedNormal <= 0xFFFF
xo, yo = packedNormal >> 8, packedNormal & 0xFF
# This is following the instructions in F3DEX3
x, y = xo & 0x7F, yo & 0x7F
z = x + y
zNeg = bool(z & 0x80)
x2, y2 = x ^ 0x7F, y ^ 0x7F # this is actually producing 7F - x, 7F - y
z = z ^ 0x7F # 7F - x - y; using xor saves an instruction and a register on the RSP
if zNeg:
x, y = x2, y2
x, y = -x if xo & 0x80 else x, -y if yo & 0x80 else y
z = z - 0x100 if z & 0x80 else z
assert abs(x) + abs(y) + abs(z) == 127
return x, y, z
def unpackNormal(packedNormal: int) -> Vector:
# Convert constant-L1 norm to standard L2 norm
return Vector(unpackNormalS8(packedNormal)).normalized()
def packNormal(normal: Vector) -> int:
# Convert standard normal to constant-L1 normal
assert len(normal) == 3
l1norm = abs(normal[0]) + abs(normal[1]) + abs(normal[2])
xo, yo, zo = tuple([int(round(a * 127.0 / l1norm)) for a in normal])
if abs(xo) + abs(yo) > 127:
yo = int(math.copysign(127 - abs(xo), yo))
zo = int(math.copysign(127 - abs(xo) - abs(yo), zo))
assert abs(xo) + abs(yo) + abs(zo) == 127
# Pack normals
xsign, ysign = xo & 0x80, yo & 0x80
x, y = abs(xo), abs(yo)
if zo < 0:
x, y = 0x7F - x, 0x7F - y
x, y = x | xsign, y | ysign
packedNormal = x << 8 | y
# The only error is in the float to int rounding above. The packing and unpacking
# will precisely restore the original int values.
assert (xo, yo, zo) == unpackNormalS8(packedNormal)
return packedNormal
def getRgbNormalSettings(f3d_mat: "F3DMaterialProperty") -> Tuple[bool, bool, bool]:
rdp_settings = f3d_mat.rdp_settings
has_packed_normals = bpy.context.scene.f3d_type == "F3DEX3" and rdp_settings.g_packed_normals
has_rgb = not rdp_settings.g_lighting or has_packed_normals
has_normal = rdp_settings.g_lighting
return has_rgb, has_normal, has_packed_normals
def byteMask(data, offset, amount):
BIN
View File
Binary file not shown.