M2: Difference between revisions
m (→Header) |
m (→Header) |
||
Line 52: | Line 52: | ||
{{Template:Type/M2Array|M2Texture|section=Textures}} textures; | {{Template:Type/M2Array|M2Texture|section=Textures}} textures; | ||
{{Template:Type/M2Array|M2TextureWeight|section=Transparency}} texture_weights; // Transparency of textures. | {{Template:Type/M2Array|M2TextureWeight|section=Transparency}} texture_weights; // Transparency of textures. | ||
#if {{Template:Sandbox/VersionRange|max_expansionlevel=2}} | |||
{{Template:Type/M2Array|?}} unknown; | |||
#endif | |||
{{Template:Type/M2Array|M2TextureTransform|section=Texture_Transforms}} texture_transforms; | {{Template:Type/M2Array|M2TextureTransform|section=Texture_Transforms}} texture_transforms; | ||
{{Template:Type/M2Array|uint16_t|section=Replacable_texture_lookup}} replacable_texture_lookup; | {{Template:Type/M2Array|uint16_t|section=Replacable_texture_lookup}} replacable_texture_lookup; |
Revision as of 16:47, 24 April 2016
M2 files (also called MDX) contain model objects. Each M2 file describes the vertices, faces, materials, texture names, animations and properties of one model. M2 files don't have a chunked format like most other WoW formats.
Models are used for doodads (decoration objects), players, monsters and really everything in the game except for Terrain and WMOs.
This file describes their structure beginning with the second expansion "Wrath of the Lich King".
Header
The header has mostly the layout of number-offset pairs, containing the number of a particular record in the file, and the offset. These appear at fixed places in the header. Record sizes are not specified in the file.
struct { uint32_t magic; // "MD20" uint32_t version; M2Array<char> name; // should be globally unique, used to reload by name in internal clients struct { uint32_t flag_tilt_x : 1; uint32_t flag_tilt_y : 1; uint32_t : 1; #if ≥ // TODO: verify version uint32_t flag_has_blend_maps : 1; // add BlendMaps fields in header uint32_t : 1; #if ≥ uint32_t flag_load_phys_data : 1; uint32_t : 1; #if ≥ uint32_t flag_has_lod_skin_files : 1; uint32_t flag_camera_related : 1; // TODO: verify version #endif #endif #endif } global_flags; M2Array<M2Loopⁱ> global_loops; // Timestamps used in global looping animations. M2Array<M2Sequenceⁱ> sequences; // Information about the animations in the model. M2Array<uint16_tⁱ> sequence_lookups; // Mapping of sequence IDs to the entries in the Animation sequences block. #if ≤ M2Array<?> playable_animation_lookup; #endif M2Array<M2CompBoneⁱ> bones; // MAX_BONES = 0x100 M2Array<uint16_tⁱ> key_bone_lookup; // Lookup table for key skeletal bones. M2Array<M2Vertexⁱ> vertices; #if ≤ M2Array<M2SkinProfileⁱ> skin_profiles; #else uint32_t num_skin_profiles; // Views (LOD) are now in .skins. #endif M2Array<M2Colorⁱ> colors; // Color and alpha animations definitions. M2Array<M2Textureⁱ> textures; M2Array<M2TextureWeightⁱ> texture_weights; // Transparency of textures. #if ≤ M2Array<?> unknown; #endif M2Array<M2TextureTransformⁱ> texture_transforms; M2Array<uint16_tⁱ> replacable_texture_lookup; M2Array<M2Materialⁱ> materials; // Blending modes / render flags. M2Array<uint16_tⁱ> bone_lookup_table; M2Array<uint16_tⁱ> texture_lookup_table; M2Array<uint16_tⁱ> tex_unit_lookup_table; // ≥ : unused M2Array<uint16_tⁱ> transparency_lookup_table; M2Array<uint16_tⁱ> texture_transforms_lookup_table; CAaBoxⁱ bounding_box; // min/max( [1].z, 2.0277779f ) - 0.16f seems to be the maximum camera height float bounding_sphere_radius; CAaBoxⁱ collision_box; float collision_sphere_radius; M2Array<uint16_tⁱ> bounding_triangles; M2Array<C3Vectorⁱ> bounding_vertices; M2Array<C3Vectorⁱ> bounding_normals; M2Array<M2Attachmentⁱ> attachments; // position of equipped weapons or effects M2Array<uint16_tⁱ> attachment_lookup_table; M2Array<M2Eventⁱ> events; // Used for playing sounds when dying and a lot else. M2Array<M2Lightⁱ> lights; // Lights are mainly used in loginscreens but in wands and some doodads too. M2Array<M2Cameraⁱ> cameras; // The cameras are present in most models for having a model in the character tab. M2Array<uint16_tⁱ> camera_lookup_table; M2Array<M2Ribbonⁱ> ribbon_emitters; // Things swirling around. See the CoT-entrance for light-trails. M2Array<M2Particleⁱ> particle_emitters; #if ≥ // TODO: verify version if (flag_has_blend_maps) { M2Array<uint16_tⁱ> blend_map_overrides; // When set, textures blending is overriden by the associated array. } #endif } header;
Versions
Files get handled differently depending on this! Ranges are inclusive.
Version | Extension |
---|---|
274-? | Legion |
?-? | Warlords of Draenor |
272 | Mists of Pandaria |
265-272 | Cataclysm |
264 | Wrath of the Lich King |
260-263 | The Burning Crusade |
?-256 | Classic |
Legion
The file might be chunked instead. If this is the case, the magic will be anything but 'MD20' and the m2 data will be in the 'MD21' chunk. If the first magic is 'MD20', it will be loaded just fine like it did previously. Note that the chunks can be in any order with MD21 often being first.
PFID (Legion+)
uint32_t phys_file_id;
SFID (Legion+)
uint32_t skinFileDataIDs[header.nViews]; uint32_t lod_skinFileDataIDs[lodBands /* 2? */];
AFID (Legion+)
struct { uint16_t anim_id; uint16_t sub_anim_id; uint32_t file_id; // might be 0 for "none" (so this is probably not sparse, even if it could be) } anim_file_ids[];
BFID (Legion+)
uint32_t boneFileDataIDs[];
MD21 (Legion+)
M2Data pre_legion_style_data;
Skeleton and animation
Global sequences
- nGlobalSequences 32-bit unsigned integers starting at ofsGlobalSequences.
A list of timestamps that act as upper limits for global sequence ranges.
struct M2Loop { uint32_t timestamp; } loops[];
Standard animation block
- < uses a single-timeline approach, chaining all animations into one long piece and separating them via begin and end given in animation data. ≥ , each animation has an own timeline.
- Animation blocks contain a list of lists of timestamps and a list of lists of values, where the first list is by animation and the second one by timestamp-entry.
- Many values that change with time are specified using blocks like the following.
template<typename T> struct M2Array { uint32_t number; uint32_t offset_elements; };
struct M2TrackBase { uint16_t interpolation_type; uint16_t global_sequence; #if < M2Array<pair<uint32_t>> interpolation_ranges; // no longer required ≥ , as implicit by minimum and maximum timestamp per sequence. M2Array<uint32_t> timestamps; #else M2Array<M2Array<uint32_t>> timestamps; #endif }; template<typename T> struct M2Track : M2TrackBase { #if < M2Array<T> values; #else M2Array<M2Array<T>> values; #endif };
- Thus, as example, with
M2CompBoneⁱ b;
one may get the number of animations having translation information with
b.translation.timestamps.number
and the number of timestamps in the first animation using
b.translation.timestamps.elements[0].number
and the first timestamp value of the first animation via
b.translation.timestamps.elements[0].elements[0]
The actual translation vector for animation 0 at timestamp 0 is at
b.translation.values.elements[0].elements[0]
- Some timestamps/values.elements entries may have number/elements = 0, if for that animation id no animation is given.
- .anim files are just a blob of data which may as well be in the main model file, that is pointed to by the first array_ref layer.
- [model file name][animation id]-[animation sub-id].anim
- it seems like it is possible to detect if animation data is stored in-m2 or externally via
- All animations which have flags & 0x20 are stored internally.
- Animations which do not have flags & 0x20 are not stored internally.
- Animations which do not have flags & 0x20 AND do not have flags & 0x40 are in .anim files
- Animations which do not have flags & 0x20 AND DO have flags 0x40 are stored... somewhere. I have no clue.
Global Sequences
If a block has a sequence >= 0, the block has a completely separate max timestamp. This is the value in the model's ofsGlobalSequences table; index into that table with this sequence value and use that as the block's max timestamp. Blocks that use these global sequences also only have one track, so at the same time as clipping the current timestamp to the max time above, interpolated value should always be taken from track 0 in the block.
A global sequence is completely unrelated to animations. It just always loops. This way, the sequence is not interrupted when an animation is launched.
This appears to be frequently used by models that don't have more conventional animations (login screen animations, items/weapons with animated effects, etc).
-- Rour, additionally, these sequences can be longer or shorter than whatever animation is running for a given model, so I recommend taking a global scene timestamp and then clipping that value into the given max timestamp range. Otherwise animations will appear to reset when the regular animation loops, which is not good.
Interpolation
- If the interpolation type is 0, then values will change instantly at the timestamp, with no interpolation whatsoever.
- If the interpolation type is 1, then the block linearly interpolates between keyframe values (lerp for vectors/colours, slerp for quaternions).
- If the interpolation type is 2, then hermite interpolation is used (instead of linear).
- If the interpolation type is 3, then bezier interpolation is used (instead of linear).
Animation sequences
- nAnimations 0x40-byte records starting at ofsAnimations.
List of animations present in the model.
struct M2Sequence { uint16_t animation_id; // Animation id in AnimationData.dbc uint16_t sub_animation_id; // Sub-animation id: Which number in a row of animations this one is. uint32_t length; // The length (timestamps) of the animation. I believe this actually the length of the animation in milliseconds. float moving_speed; // This is the speed the character moves with in this animation. uint32_t flags; // See below. int16_t probability; // This is used to determine how often the animation is played. For all animations of the same type, this adds up to 0x7FFF (32767). uint16_t _padding; uint32_t minimum_repetitions; // May both be 0 to not repeat. Client will pick a random number of repetitions within bounds if given. uint32_t maximum_repetitions; uint32_t blend_time; // The client blends (lerp) animation states between animations where the end and start values differ. This specifies how long that blending takes. Values: 0, 50, 100, 150, 200, 250, 300, 350, 500. CAaBoxⁱ bounds; float bound_radius; int16_t next_animation; // id of the following animation of this AnimationID, points to an Index or is -1 if none. uint16_t aliasNext; // id in the list of animations. Used to find actual animation if this sequence is an alias (flags & 0x40) } sequences[];
--Koward (talk) 09:50, 18 December 2015 (UTC) In M2 v274 (Legion), it looks like blend_time has been divided in two uint16_t, and for standard animations the old blend_time is duplicated in both fields (ex : uint32 150 becomes two uint16 150). Maybe start and end blend_time values ? See Creature/GennGreymane/GennGreymane.m2 .
Flags
One thing I saw in the source is that "-1 animationblocks" in bones wont get parsed if 0x20 is not set.
The client loads .anim files if (flags & 0x130 ) == 0. The .anim file to use is "%s%04d-%02d.anim" % (model_filename_without_extension, anim.id, anim.sub_anim_id).
Flag | Description |
---|---|
0x01 | Sets 0x80 when loaded. (M2Init) |
0x02 | |
0x04 | |
0x08 | |
0x10 | apparently set during runtime in CM2Shared::LoadLowPrioritySequence for all entries of a loaded sequence (including aliases) |
0x20 | Looped animation. // not set → lowPriorityKeyFrameData |
0x40 | has next / is alias (client skips these following next, until an animation without 0x40 is found. |
0x80 | Blended animation (if either side of a transition has 0x80, lerp between end->start states, unless end==start by comparing bone values) |
0x100 | sequence stored in model ? |
-- Rour, some animations rely on blending to look right. The MoP mage CM shoulders only animate half of their movement and rely on lerping back to the start position to look correct.
Animation Lookup
- nAnimationLookup in 16-bit shorts starting at ofsAnimationLookup.
Lookup table for Animations in AnimationData.dbc.
struct { uint16_t animation_id; // Index at ofsAnimations which represents the animation in AnimationData.dbc. -1 if none. } animation_lookups[];
Bones
- nBones records of 0x58 bytes starting at ofsBones. (M2Array<M2CompBone>)
struct M2CompBone // probably M2Bone ≤ { int32_t key_bone_id; // Back-reference to the key bone lookup table. -1 if this is no key bone. enum { spherical_billboard = 0x8, cylindrical_billboard_lock_x = 0x10, cylindrical_billboard_lock_y = 0x20, cylindrical_billboard_lock_z = 0x40, transformed = 0x200, kinematic_bone = 0x400, // MoP+: allow physics to influence this bone helmet_anim_scaled = 0x1000, // set blend_modificator to helmetAnimScalingRec.m_amount for this bone }; uint32_t flags; int16_t parent_bone; // Parent bone ID or -1 if there is none. uint16_t submesh_id; // Mesh part ID uint16_t _unknown[2]; // only ≥ ? M2Track<C3Vectorⁱ> translation; #if ≤ M2Track<C4Quaternionⁱ> rotation; #else M2Track<M2CompQuatⁱ> rotation; // compressed values, default is (32767,32767,32767,65535) == (0,0,0,1) == identity #endif M2Track<C3Vectorⁱ> scale; C3Vectorⁱ pivot; // The pivot point of that bone. } bones[];
The bone indices in the vertex definitions seem to index into this data.
Billboards
The billboarding bits are used for various things:
- Light halos around lamps must always face the viewer
- The cannonball stack model (in the Deadmines or Booty Bay), where each cannonball is a crude hemisphere, they always face the viewer to create the illusion of actual cannonballs.
Spherical and cylindrical billboard bits are mutually exclusive. Only one of them can be used for the bone.
Bone Lookup Table
- nBoneLookupTable 16-bit integers starting at ofsBoneLookupTable. (values: 0 to nBones-1)
Lookup table for bones referenced from M2SkinSection.
struct { uint16_t bone; } bone_lookup[];
Key-Bone Lookup
- nKeyBoneLookup 16-bit shorts starting at ofsKeyBoneLookup.
Its a lookup table for key skeletal bones like hands, arms, legs, etc. nKeyBoneLookup is 27 for the most models. At static models it is mostly 1.
struct { uint16_t bone; // -1 if none } key_bone_lookup[];
Official list:
- 00 "ArmL"
- 01 "ArmR"
- 02 "ShoulderL"
- 03 "ShoulderR"
- 04 "SpineLow"
- 05 "Waist"
- 06 "Head"
- 07 "Jaw"
- 08 "IndexFingerR"
- 09 "MiddleFingerR"
- 10 "PinkyFingerR"
- 11 "RingFingerR"
- 12 "ThumbR"
- 13 "IndexFingerL"
- 14 "MiddleFingerL"
- 15 "PinkyFingerL"
- 16 "RingFingerL"
- 17 "ThumbL"
- 18 "$BTH"
- 19 "$CSR"
- 20 "$CSL"
- 21 "_Breath"
- 22 "_Name"
- 23 "_NameMount"
- 24 "$CHD"
- 25 "$CCH"
- 26 "Root"
- 27 "Wheel1" ≥
- 28 "Wheel2" ≥
- 29 "Wheel3" ≥
- 30 "Wheel4" ≥
- 31 "Wheel5" ≥
- 32 "Wheel6" ≥
- 33 "Wheel7" ≥
- 34 "Wheel8" ≥
Geometry and rendering
Vertices
- nVertices entries of 48 bytes per vertex (at ofsVertices)
struct M2Vertex { C3Vectorⁱ pos; uint8 bone_weights[4]; uint8 bone_indices[4]; C3Vectorⁱ normal; C2Vectorⁱ tex_coords[2]; // two textures, depending on shader used };
Models, too, use a Z-up coordinate systems, so in order to convert to Y-up, the X, Y, Z values become (X, -Z, Y).
-- Rour, the WoW vertex shaders all follow the same pattern, "Diffuse_XX_YY" (or sometimes XX, YY and Env). The particular vertex shader that is used chooses which set of texture coordinates to use. So Diffuse_T1 sends T1 texcoords to the fragment shader. Where Diffuse_T1_T2 sends both (for textures 0 and 1) but Diffuse_T1_T1 sends the same coords for both textures. Etc.
Views (LOD)
Since there is no ofsViews anymore, this data is now stored in .skin files. More information about them here: M2/WotLK/.skin.
Render flags
- nRenderFlags (uint16, uint16) pairs starting at ofsRenderFlags
struct M2Material { uint16_t flags; uint16_ blending_mode; // apparently a bitfield } materials[];
- Flags:
Flag | Meaning |
---|---|
0x01 | Unlit |
0x02 | Unfogged? |
0x04 | Two-sided (no backface culling if set) |
0x08 | ? (probably billboarded) |
0x10 | Disable z-buffer? |
0x40 | shadow batch related ??? (seen in WoD) |
0x80 | shadow batch related ??? (seen in WoD) |
0x400 | ??? (seen in WoD) |
0x800 | prevent alpha for custom elements. if set, use (fully) opaque or transparent. (litSphere, shadowMonk) (MoP+) |
- Blending mode
Value | source | dest | notes |
---|---|---|---|
0 | GL_ONE | GL_ZERO | blending disabled (GxBlendStateDesc: 0) |
1 | GL_ONE | GL_ZERO | Mod (1) |
2 | GL_SRC_ALPHA | GL_ONE_MINUS_SRC_ALPHA | Decal (2) |
3 | GL_ONE | GL_ONE | Add (10) |
4 | GL_SRC_ALPHA | GL_ONE | Mod2x (3) |
5 | GL_DST_COLOR | GL_ZERO | Fade (4) |
6 | GL_DST_COLOR | GL_SRC_COLOR | Deeprun Tram glass (5) |
7 | GL_ONE | GL_ONE_MINUS_SRC_ALPHA | WoD+ (13) |
(blend equation is always GL_FUNC_ADD. Values are retrieved via GxBlendStateDesc's lower 5 bits. no separate blend func for alpha.)
- Blending mode
Value | Mapped to | Meaning |
---|---|---|
0 | 0 | Combiners_Opaque (Blend disabled) |
1 | 1 | Combiners_Mod (Blend enabled, Src = ONE, Dest = ZERO, SrcAlpha = ONE, DestAlpha = ZERO) |
2 | 1 | Combiners_Decal (Blend enabled, Src = SRC_ALPHA, Dest = INV_SRC_ALPHA, SrcAlpha = SRC_ALPHA, DestAlpha = INV_SRC_ALPHA ) |
3 | 1 | Combiners_Add (Blend enabled, Src = SRC_COLOR, Dest = DEST_COLOR, SrcAlpha = SRC_ALPHA, DestAlpha = DEST_ALPHA ) |
4 | 1 | Combiners_Mod2x (Blend enabled, Src = SRC_ALPHA, Dest = ONE, SrcAlpha = SRC_ALPHA, DestAlpha = ONE ) |
5 | 4 | Combiners_Fade (Blend enabled, Src = SRC_ALPHA, Dest = INV_SRC_ALPHA, SrcAlpha = SRC_ALPHA, DestAlpha = INV_SRC_ALPHA ) |
6 | 4 | Used in the Deeprun Tram subway glass, supposedly (Blend enabled, Src = DEST_COLOR, Dest = SRC_COLOR, SrcAlpha = DEST_ALPHA, DestAlpha = SRC_ALPHA ) |
7 | ? | New in WoD, needs research! Example model: World\Expansion05\Doodads\Shadowmoon\Doodads\6FX_Fire_Grassline_Doodad_blue_LARGE.m2 |
*Blend values are taken from D3D11 debugging of the client
Blend mode overrides
If this block is present (globalflags&8) and the "shading" flags of a textureunit wont be &0x8000, blending modes wont get mapped to the values above but to the ones in this block.
Instead of Mapping[renderflags->blendingmode] it will be UnknownBlock[textureunit->Shading].
As shading is not &0x8000 and (in their code) needs to be above 0, this may only touch Diffuse_T1.
According to wod, if the M2 Header has flag 0x08, instead of reading blend mode from M2 RenderFlags, blendMode is read from the raw blend maps referenced in header.
var flags = renderFlags[texUnit.renderFlags]; var blendMode = flags >> 16; if ((header.GlobalModelFlags & 0x08) != 0 && texUnit.shader_id < mBlendMap.Length) blendMode = mBlendMap[texUnit.shader_id];
Texture unit lookup table
No longer used in Cataclysm+.
- nTexUnits 16-bit integers starting at ofsTexUnits.
struct { uint16_t unit; // -1, 0, or 1. see below } texture_units[];
For models that use multitexturing, this maps given texture unit numbers into actual texture unit numbers (0 or 1).
Values of -1 seem to mean environment mapping.
One model is of special interest, Creature/KelThuzad/KelThuzad.m2, which is the only one that has an nTexUnits of 3, and has three texture units specified for some of its submeshes. Sure enough, two of those map to 0 and 1, and one maps to -1.
More confusion thanks to my favorite "weird" model, World/Generic/Gnome/Passive Doodads/GnomeMachine/GnomeSubwayGlass.m2, which is the translucent, environment mapped glass tunnel in the Deeprun Tram. It only has a single value in this block, -1, which is used for the single texture layer in both render operations in the model. This and the magic with rendering flags/blend modes make up the neat transparent-reflective glass effect, but confuse me even more about how envmapping and such is handled. (and where it seems to get the bluish color from - is it in the model (no color blocks in this particular model), the wmo, a solid background color, or simply the result of the blending used?)
As a side note, on my (dated) system WoW does every texture unit in a single pass.
Colors and transparency
Colors
- nColors records starting at ofsColors, followed by data referenced in these records.
struct M2Color { M2Track<C3Vectorⁱ> color; // vertex colors M2Track<fixed16ⁱ> alpha; // 0 - transparent, 0x7FFF - opaque. Normaly NonInterp } colors[];
This block is the M2 equivalent to the GEOA chunk in MDX files, it represents the vertex color and visibility animations for meshes. Referenced from the Texture Unit blocks in the *.skin. If a texunit belonging to a submesh has a value of -1 then the submesh doesnot use this block. Contains a separate timeline for transparency values. If no animation is used, the given value is constant.
Transparency
- nTransparency AnimationBlocks at ofsTransparency, followed by data referenced in these records.
struct M2TextureWeight { M2Track<fixed16ⁱ> weight; } textureWeights[];
Specifies global transparency values in addition to the values given in the Color block. I assume these are multiplied together eventually.
Transparency lookup table
- nTransLookup 16-bit integers starting at ofsTransLookup.
struct { uint16_t transparency; } transparency_lookup[];
Contains indices into the Transparency block. Used by the texture unit definitions in the LOD block.
Textures
- Textures are defined globally in a list, additionally, a lookup table is given, referenced during rendering, to select textures.
- First is a list of nTextures texture definition records, 16 bytes per record, starting at ofsTextures.
After it comes a string block with the texture filenames, which seems to have the same structure like the MOTX-Chunk in WMOs(see WMO), the "empty aligtments" are just the zeros were non "0 - hardcoded" type textures point too.
struct M2Texture { uint32_t type; // see below uint32_t flags; // see below uint32_t lenFilename; // for non-hardcoded textures (type != 0), this still points to a zero-sized string uint32_t ofsFilename; } textures[];
Texture Types
Texture type is 0 for regular textures, nonzero for skinned textures (filename not referenced in the M2 file!) For instance, in the NightElfFemale model, her eye glow is a type 0 texture and has a file name, the other 3 textures have types of 1, 2 and 6. The texture filenames for these come from client database files:
- DBFilesClient\CharSections.dbc
- DBFilesClient\CreatureDisplayInfo.dbc
- DBFilesClient\ItemDisplayInfo.dbc
- (possibly more)
Flags
Value | Meaning |
---|---|
1 | Texture wrap X |
2 | Texture wrap Y |
Texture lookup table
- nTexLookup items starting at ofsTexLookup.
struct { uint16_t texture; } texture_lookup[];
Replacable texture lookup
- nTexReplace 16-bit integers starting at ofsTexReplace.
struct { uint16_t replacement; } texture_replacements[];
A reverse lookup table for 'replaced' textures, mapping replacable ids to texture indices or -1. Only goes up to the maximum id used in the model.
Its strange, that HARDCODED is in the list, as a model can have more than one of course. Its just the last one written to the file.
Effects
Texture Transforms
- This block contains definitions for texture animations, for example, flowing water or lava in some models. The keyframe values are used in the texture transform matrix.
nTextureTransforms records of 0x3C bytes starting at ofsTextureTransforms, followed by data referenced in these records.
struct M2TextureTransform { M2Track<C3Vectorⁱ> translation; M2Track<C4Quaternionⁱ> rotation; // rotation center is texture center (0.5, 0.5) M2Track<C3Vectorⁱ> scaling; } textureTransforms[];
Seems like UV rotation in some models are made against (0.5, 0.5) point instead of (0, 0). At least it's the case for world\goober\g_scourgerunecirclecrystal.m2
So to get the proper UV rotation it would be necessary apply rotation this way:
- Translate UV anim matrix to point (0.5, 0.5)
- Apply rotation mat from quaternion
- UV anim matrix to point (-0.5, -0.5)
Texture Transforms lookup table
- nTextureTransformsLookup items starting at ofsTextureTransformsLookup.
struct { uint16_t anim_texture_id; // -1 for static } anim_texture_lookup[];
Ribbon emitters
- nRibbonEmitters records of 0xB0 bytes starting at ofsRibbonEmitters, followed by data referenced in these records.
struct M2Ribbon { uint32_t _unknown0; // Always (as I have seen): -1. uint32_t bone; // A bone to attach to. C3Vectorⁱ position; // And a position, relative to that bone. M2Array<uint16_t> texture_refs; // into textures M2Array<uint16_t> blend_refs; // into materials M2Track<C3Vectorⁱ> color; M2Track<fixed16ⁱ> opacity; // And an alpha value in a short, where: 0 - transparent, 0x7FFF - opaque. M2Track<float> height_above; M2Track<float> height_below; // do not set to same! float edgesPerSec; // this defines how smooth the ribbon is. A low value may produce a lot of edges. float edgeLifeSpanInSec; // the length aka Lifespan. float gravity; // use arcsin(val) to get the emission angle in degree uint16_t m_rows; // tiles in texture uint16_t m_cols; M2Track<uint16_t> texSlot; M2Track<uchar> dataEnabled; uint32_t _unknown3; // padding? } ribbons[];
Some models that contain ribbon emitters and are viewable in the game world are: Wisps in BFD, Al'ar the Phoenix in Tempest Keep and any other phoenix models and the energy trails in the COT (not the actual instance, but the entrance cave in Tanaris Desert). Other models with ribbon emitters are spells and effects.
Parameters from the MDL format that are probably in here somewhere: emission rate, rows, cols ...?
Particle emitters
This is partly wrong as hell! Do not rely on this block, at all. It might even be wrong for WotLK.
- nParticleEmitters records starting at ofsParticleEmitters, followed by data referenced in these records.
M2ParticleOld
Offset | Type | Name | Description |
---|---|---|---|
0x000 | uint32 | Unknown | Always (as I have seen): -1. |
0x004 | uint32 | Flags | See Below |
0x008 | C3Vectorⁱ | Position | The position. Relative to the following bone. |
0x014 | uint16 | Bone | The bone its attached to. |
0x016 | uint16 | Texture | And the textures that are used. For multi-textured particles actually three ids compressed:
uint16_t texture_0 : 5; uint16_t texture_1 : 5; uint16_t texture_2 : 5; uint16_t : 1; |
0x018 | M2Array<uchar> | model_filename | if given, this emitter spawns models |
0x020 | M2Array<uchar> | child_emitter_filename | if given, this emitter is an alias for the (maximum 4) emitters of the given model |
0x028 | uint8 | BlendingType | A blending type for the particle. See Below |
0x029 | uint8 | EmitterType | 1 - Plane (rectangle), 2 - Sphere, 3 - Spline, 4 - Bone |
0x02A | uint16 | ParticleColorIndex | This one is used for ParticleColor.dbc. See below. |
0x02C | uint8 | ParticleType? | Found below. |
0x02D | uint8 | HeadorTail | 0 - Head, 1 - Tail, 2 - Both |
0x02E | uint16 | TextureTileRotation | Rotation for the texture tile. (Values: -1,0,1) |
0x030 | uint16 | TextureDimensions_rows | for tiled textures |
0x032 | uint16 | TextureDimensions_columns | |
0x034 | ABlock<float> | EmissionSpeed | Base velocity at which particles are emitted. |
0x048 | ABlock<float> | SpeedVariation | Random variation in particle velocity. (range: 0 to 1) |
0x05C | ABlock<float> | VerticalRange | Drifting away vertically. (range: 0 to pi) For plane generators, this is the maximum polar angle of the initial velocity; 0 makes the velocity straight up (+z). For sphere generators, this is the maximum elevation of the initial position; 0 makes the initial position entirely in the x-y plane (z=0). |
0x070 | ABlock<float> | HorizontalRange | They can do it horizontally too! (range: 0 to 2*pi) For plane generators, this is the maximum azimuth angle of the initial velocity; 0 makes the velocity have no sideways (y-axis) component. For sphere generators, this is the maximum azimuth angle of the initial position. |
0x084 | ABlock<float> | Gravity | Not necessarily a float; see below. |
0x098 | ABlock<float> | Lifespan | |
0x0AC | float | LifespanVary | An individual particle's lifespan is added to by LifespanVary * random(-1, 1)
|
0x0B0 | ABlock<float> | EmissionRate | |
0x0C4 | float | EmissionRateVary | This adds to the base EmissionRate value the same way as LifespanVary. The random value is different every update. |
0x0C8 | ABlock<float> | EmissionAreaLength | For plane generators, this is the width of the plane in the x-axis. For sphere generators, this is the minimum radius. |
0x0DC | ABlock<float> | EmissionAreaWidth | For plane generators, this is the width of the plane in the y-axis. For sphere generators, this is the maximum radius. |
0x0F0 | ABlock<float> | ZSource | When greater than 0, the initial velocity of the particle is (particle.position - C3Vector(0, 0, zSource)).Normalize()
|
0x104 | FBlock<C3Vectorⁱ> | colorTrack | (short, C3Vectorⁱ) This one points to 3 floats defining red, green and blue. |
0x114 | FBlock<fixed16ⁱ> | alphaTrack | (short, short) Looks like opacity (short) --Igor; Most likely they all have 3 timestamps for {start, middle, end}. |
0x124 | FBlock<C2Vectorⁱ> | scaleTrack | (short, vec2f) It carries two floats per key. (x and y scale) |
0x134 | C2Vectorⁱ | ScaleVary | A percentage amount to randomly vary the scale of each particle |
0x13C | FBlock<uint16> | headCellTrack | Some kind of intensity values seen: 0,16,17,32(if set to different it will have high intensity) (short, short) |
0x14C | FBlock<uint16> | tailCellTrack | (short, short) |
0x15C | float | something_particle_style | |
0x160 | float | has something to do with the spread | |
0x164 | float | has something to do with the spread | |
0x168 | CRangeⁱ | twinkleScale | {2 float: min, max} |
0x170 | float | ||
0x174 | float | Drag | Air resistance, see below. |
0x178 | float | BaseSpin | Initial rotation of the particle quad |
0x17C | float | BaseSpinVary | |
0x180 | float | Spin | Rotation of the particle quad per second |
0x184 | float | SpinVary | |
0x188 | float | ||
0x18C | C3Vectorⁱ | model_1_rotation | |
0x198 | C3Vectorⁱ | "model_2_rotation" | what model2?! [last float is sometimes -0, so not sure about this --Schlumpf (talk) 19:07, 26 January 2016 (UTC)] |
0x1A4 | C3Vectorⁱ | model_translation | |
0x1B0 | float[4] | followParams | |
0x1C0 | uint32 | nUnknownReference | |
0x1C4 | uint32 | ofsUnknownReference | C3Vectorⁱ array |
0x1C8 | ABlock<uchar> | EnabledIn | (boolean) Has been in the earlier documentations. Enabled Anim Block. Appears to be used sparely now, probably there's a flag that links particles to animation sets where they are enabled. |
M2Particle (Cata+)
- Cata+ has multi texture support
using fp_6_9 = fixed_pointⁱ<uint16_t, 6, 9>; using fp_2_5 = fixed_pointⁱ<uint8_t, 2, 5>; struct vector_2fp_6_9 { fp_6_9 x; fp_6_9 y; }; struct M2Particle { M2ParticleOld old; vector_2fp_6_9 multiTextureParam0[2]; vector_2fp_6_9 multiTextureParam1[2]; };
In addition to these two parameters, "ParticleType" and "HeadOrTail" got reused (as in replaced at their current position) as
fp_2_5 multiTextureParamX[2];
where all arrays are one entry per additional texture.
I don't know if the previous meaning of the two parameters still exists, got moved, or was just never used to begin with. ParticleType appears to be implicit by having flags & 0x10100000
(→ multi texture), a model (→ model) or neither (→ default).--Schlumpf (talk) 23:47, 29 October 2015 (UTC)
remarks
About Drag: For a non-zero values, instead of travelling linearly the particles seem to slow down sooner. I can't work out the exact function but for a value of, say, 10, the particles pretty much stay in place. Not the same effect as gravity, though. Speed is multiplied by exp( -drag * t ). This value is also present in M3 format.
About particle rotation: 0 for none, 1 to rotate the particle 360 degrees throughout its lifetime.
Rotation can be a float value greater or less one. Results look better if use it as a "phase shift": particle_rotate = randfloat(-sys->rotation * pi, sys->rotation * pi); --Igor
Particle Flags
Value | Description |
---|---|
0x1 | Particles are affected by lighting |
0x2 | |
0x4 | |
0x8 | Particles travel "up" in world space, rather than model. |
0x10 | Do not Trail |
0x20 | Unlightning |
0x40 | |
0x80 | Particles in Model Space |
0x100 | |
0x200 | spawn position randomized in some way? |
0x400 | STYLE: Pinned Particles, their quad enlarges from their creation position to where they expand. |
0x800 | |
0x1000 | XYQuad Particles. They align to XY axis facing Z axis direction. |
0x2000 | clamp to ground |
0x4000 | |
0x8000 | |
0x10000 | ChooseRandomTexture |
0x20000 | STYLE: "Outward" particles, most emitters have this and their particles move away from the origin, when they don't the particles start at origin+(speed*life) and move towards the origin. |
0x40000 | STYLE: unknown. In a large proportion of particles this seems to be simply the opposite of the above flag, but in some (e.g. voidgod.m2 or wingedlionmount.m2) both flags are true. |
0x80000 | If set, ScaleVary affects x and y independently; if not set, ScaleVary.x affects x and y uniformly, and ScaleVary.y is not used. |
0x200000 | Random FlipBookStart |
0x800000 | gravity values are compressed vectors instead of z-axis values (see Compressed Particle Gravity below) |
0x1000000 | bone generator = bone, not joint |
0x4000000 | do not throttle emission rate based on distance |
0x10000000 | Particle uses multi-texturing (could be one of the other WoD-specific flags), see multi-textured section. |
ParticleColorIndex
This is used in conjunction with ParticleColor.dbc to alter the default colour of particles. If the particle colour is not meant to be changed then its ParticleColorIndex will have a value of zero. If the particle colour may be changed then the value will be 11, 12 or 13, indicating whether the first, second or third Start, Mid and End colours are to be used, respectively. The row of ParticleColor.dbc to be used is determined its ID value, which should correspond to the ParticleColorID value supplied by CreatureDisplayInfo.dbc or ItemDisplayInfo.dbc for creatures or items.
Multi-textured particles
A bunch of 6.0.2 particle emitters now have many high bits set in flags (0x10000000, maybe) and this changes the texture field of the emitter structure. Blizzard has packed three texture indices into the one 16-bit field. The following appears to produce the correct three textures:
int16_t texIndex1 = _def.texture & 0x1f;
int16_t texIndex2 = (_def.texture >> 5) & 0x1f;
int16_t texIndex3 = (_def.texture >> 10) & 0x1f;
-- Rour, I haven't found the BLS file with the particle shaders in it, but dumping the ARB shaders from the mac client show that it just multiplies all three texture samples together, then multiplies by 4.0 (ala. mod2x style)
Particle types
Value | Description |
---|---|
0 | "normal" particle |
1 | large quad from the particle's origin to its position (used in Moonwell water effects) |
2 | seems to be the same as 0 (found some in the Deeprun Tram blinky-lights-sign thing) |
ParticleType is always 0 and, maybe, now (Flags & 0x40000) != 0 means "particles from origin to position". --Igor Checked and verified --BlinkHawk
Particle Blendings
Value | Description |
---|---|
0 | glDisable(GL_BLEND); glDisable(GL_ALPHA_TEST); |
1 | glBlendFunc(GL_SRC_COLOR, GL_ONE); |
2 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); |
3 | glDisable(GL_BLEND); glEnable(GL_ALPHA_TEST); |
4 | glBlendFunc(GL_SRC_ALPHA, GL_ONE); |
from Modelviewer source -- Rour, some WoD particle effects are using blend mode 0x7 here.
The Fake-AnimationBlock
- Its pretty much like the real one but without the "header".
Offset | Type | Name | Description |
---|---|---|---|
0x000 | uint32 | nTimestamps | The number of timestamps. |
0x004 | uint32 | ofsTimestamps | And the offset to them. The timestamps are shorts! (?) |
0x008 | uint32 | nKeys | The same number again. This time its the number of Keys / Values. |
0x00C | uint32 | ofsKeys | And their offset. |
But they're unable to change between different animations, so they directly point to the data.
Compressed Particle Gravity
Key values in the gravity track are decompressed at load time from a 4-byte value to a C3Vector.
struct CompressedParticleGravity { int8_t x, y; int16_t z; }; for (/* each 4-byte value in the particle gravity track */) { float *pValue; C3Vector *pDst; if (particle.flags & 0x800000) { // interpret the 4 bytes at pValue as CompressedParticleGravity: CompressedParticleGravity v = new (pValue) CompressedParticleGravity(); C3Vector dir = C3Vector(v.x, v.y, 0) * (1.0f / 128.0f); float z = sqrtf(1.0f - dir.Dot(dir)); float mag = v.z * 0.04238648f; if (mag < 0) { z = -z; mag = -mag; } dir.z = z; dir *= mag; *pDst = dir; } else { *pDst = C3Vector(0, 0, -(*pValue)); } }
Miscellaneous
Bounding volumes
These blocks give a simplified bounding volume for the model. Characters and creatures have just a simple box.
Vertices
- nBoundingVertices vertices at ofsBoundingVertices.
This block defines the possible points used for the model. They are referenced in the triangles block later.
struct { C3Vectorⁱ position; } bounding_vertices[];
Triangles
- nBoundingTriangles/3 triples of uint16s at ofsBoundingTriangles.
The number in the header tells us how many uint16s there are, not how many triangles. To use this better, you should group three of them into an array. The nBoundingTriangles/3 indices will tell you which vertices are used for the triangle then.
struct { uint16_t index; // three entries pointing to vertices per triangle } bounding_triangles[];
The number nBoundingTriangles once again contains the number of indices used, so divide by 3 to get the number of triangles.
Normals
- nBoundingNormals normals at ofsBoundingNormals.
This one defines a normal per triangle. The vectors are normalized, but Blizzard seems to have some problems getting a simple vector normalized leading in several 0,0,0.999999999 ones. Whatever.
As each vertex has a corresponding normal vector, it should be true that nBoundingNormals = nBoundingTriangles / 3.
struct { C3Vectorⁱ normal; } bounding_normals[];
Lights
- nLights records of 0x9C bytes starting at ofsLights, followed by data referenced in these records.
struct M2Light { uint16_t type; // Types are listed below. int16_t bone; // -1 if not attached to a bone C3Vectorⁱ position; // relative to bone, if given M2Track<C3Vectorⁱ> ambient_color; M2Track<float> ambient_intensity; // defaults to 1.0 M2Track<C3Vectorⁱ> diffuse_color; M2Track<float> diffuse_intensity; // defaults to 1.0 M2Track<float> attenuation_start; M2Track<float> attenuation_end; M2Track<uchar> unknown; // enabled? } lights[];
Two light types:
Value | Description |
---|---|
0 | Directional |
1 | Point light |
Cameras
- nCameras records of 0x64 bytes starting at ofsCameras.
These blocks are present in the "flyby" camera models which completely lack geometry and the main menu backdrop models which are supposed to have a fixed camera. Additionally, characters and monsters also have this block. The reason that non-mainmenu and non-geometry M2s have cameras was is you can see the unit's portrait and the character info tab.
struct M2Camera { uint32_t type; // 0: portrait, 1: characterinfo; -1: else (flyby etc.); referenced backwards in the lookup table. #if < float fov; // No radians, no degrees. Multiply by 35 to get degrees. #endif float far_clip; float near_clip; M2Track<M2SplineKey<C3Vectorⁱ>> positions; // How the camera's position moves. Should be 3*3 floats. C3Vectorⁱ position_base; M2Track<M2SplineKey<C3Vectorⁱ>> target_position; // How the target moves. Should be 3*3 floats. C3Vectorⁱ target_position_base; M2Track<M2SplineKey<float>> roll; // The camera can have some roll-effect. Its 0 to 2*Pi. #if ≥ M2Track<M2SplineKey<float>> FoV; // Units are not radians. Multiplying by 35 seems to be the correct # of degrees. This is incredibly important, as otherwise e.g. loading screen models will look totally wrong. #endif } cameras[];
Camera lookup table
- nCameraLookup 16-bit integers starting at ofsCameraLookup.
This block lists the different cameras existing in the model. The index in the array is also the type. CameraLookupTable[1] is always the character tab camera.
"-1" type cameras are not referenced.
If nCameras >= 1, then nCameraLookup will be >= 1 regardless of whether any camera will be actually referenced in it. See interface/glues/models/ui_mainmenu_warlords/ui_mainmenu_warlords.m2. A valid block thus may be -1s only. This appears to be an exporter-quirk rather than a requirement.
struct { uint16_t camera; } camera_lookup[];
Attachments
- nAttachments records of the following structure starting at ofsAttachments, followed by data referenced in these records.
This block specifies a bunch of locations on the body - hands, shoulders, head, back, knees etc. It is used to put items on a character. This seems very likely as this block also contains positions for sheathed weapons, a shield, etc.
struct M2Attachment { uint32_t id; // Referenced in the lookup-block below. uint32_t bone; // attachment base -- possibly just uint16_t and padding C3Vectorⁱ position; // relative to bone; M2Track<uchar> animate_attached; // whether or not the attached model is animated when this model is. only a bool is used. default is true. } attachments[];
Meaning depends on type of model. The following are for creatures/characters mainly:
For weapons, usually 5 of these points are present, which correspond to the 5 columns in ItemVisuals.dbc, which in turn has 5 models from ItemVisualEffects.dbc. This is for the weapon glowy effects and such. The effect ID is the last column in ItemDisplayInfo.dbc. They take the ids 0 to 4. Mounts take the id 0 for their rider. Breath (17) is used by CGCamera::FinishLoadingTarget() aswell as some other one. The name above the head of a Unit (CGUnit_C::GetNamePosition) looks for PlayerNameMounted (29), then PlayerName (18).
Attachment Lookup
- nAttachLookup 16-bit integers starting at ofsAttachLookup.
The index of the array defines, which type that attachment is of. Its the same as the list above. The lookups and the id of the animations point in a circle.
struct { uint16_t attachment; } attachment_lookup[];
Events
- nEvents records of 0x24 bytes starting at ofsEvents, followed by timestamps referenced in these records.
These events are used for timing sounds for example. You can find the $DTH (death) event on nearly every model. It will play the death sound for the unit.
The events you can use depend on the way, the model is used. Dynamic objects can shake the camera, doodads shouldn't. Units can do a lot more than other objects.
Somehow there are some entries, that don't use the $... names but identifiers like "DEST" (destination), "POIN" (point) or "WHEE" (wheel). How they are used? Idk.
struct M2Event { uint32_t identifier; // mostly a 3 character name prefixed with '$'. uint32_t data; // This data is passed when the event is fired. uint32_t bone; // Somewhere it has to be attached. C3Vectorⁱ position; // Relative to that bone of course, animated. Pivot without animating. M2TrackBase enabled; // This is a timestamp-only animation block. It is built up the same as a normal AnimationBlocks, but is missing values, as every timestamp is an implicit "fire now". } events[];
Possible Events
There are a lot more of them. I did not list all up to now.
ID | data | what | Type | seen to be fired on | Description |
---|---|---|---|---|---|
$AH[0-3] | — | PlaySoundKit (customAttack[x]) | soundEffect ID is defined by CreatureSoundDataRec::m_customAttack[x] | ||
$AIM | Vehicles | CGUnit_C::ComputeMissileTrajectory | Position used as MissileFirePos. | ||
$ALT | anim_swap_event / DisplayTransition | Unit | CUnitDisplayTransition_C::UpdateState(1) or CUnitDisplayTransition_C::HandleAnimSwapEvent | ||
$BL[0-3] | — | FootstepAnimEventHit (left) | Unit | Backwards | |
$BR[0-3] | — | FootstepAnimEventHit (right) | Unit | Backwards | |
$BRT | — | PlaySoundKit (birth) | soundEffect ID is defined by CreatureSoundDatarec::m_birthSoundID | ||
$BTH | — | Breath | Unit | All situations, where nothing happens or breathing. | Adds Special Unit Effect based on unit state (under water, in-snow, …) |
$BWP | — | PlayRangedItemPull (Bow Pull) | Unit | LoadRifle, LoadBow | |
$BWR | — | BowRelease | Unit | AttackRifle, AttackBow, AttackThrown | |
$CAH | — | Unit | Attack*, *Unarmed, ShieldBash, Special* | attack hold? CGUnit_C::HandleCombatAnimEvent | |
$CCH | — | Unit | mostly not fired, AttackThrown | CEffect::DrawFishingString needs this on the model for getting the string attachments. | |
$CFM | — | Unit | CGCamera::UpdateMountHeightOrOffset | CGCamera::UpdateMountHeightOrOffset: Only z is used. Non-animated. Not used if $CMA | |
$CHD | Unit | not fired | probably does not exist?! | ||
$CMA | — | Unit | CGCamera::UpdateMountHeightOrOffset: Position for camera | ||
$CPP | PlayCombatActionAnimKit | parry, anims, depending on some state, also some callback which might do more | |||
$CSD | soundEntryId | PlayEmoteSound | Unit | Emote* | |
$CSL | — | release_missiles_on_next_update if has_pending_missiles (left) | Unit | AttackRifle, SpellCast*, ChannelCast* | "x is {L or R} (""Left/right hand"") (?)" |
$CSR | — | release_missiles_on_next_update if has_pending_missiles (right) | Unit | AttackBow, AttackRifle, AttackThrown, SpellCast*, ChannelCast* | "x is {L or R} (""Left/right hand"") (?)" |
$CSS | — | PlayWeaponSwooshSound | sound played depends on CGUnit_C::GetWeaponSwingType | ||
$CST | — | release_missiles_on_next_update if has_pending_missiles | Unit | Attack*, *Unarmed, ShieldBash, Special*, SpellCast, Parry*, EmoteEat, EmoteRoar, Kick, ... | $CSL/R/T are also used in CGUnit_C::ComputeDefaultMissileFirePos. |
$CVS | ? | Data: SoundEntriesAdvanced.dbc, Sound — Not present in 6.0.1.18179 | |||
$DSE | — | DestroyEmitter | MapObj | ||
$DSL | soundEntryId | PlaySoundKit (custom), low priority | GO | ||
$DSO | soundEntryId | PlaySoundKit (custom) | GO | ||
$DTH | — | DeathThud + LootEffect | Unit | Death, Drown, Knockdown | """I'm dead now!"", UnitCombat_C, this plays death sounds and more." Note that this is NOT triggering CreatureSoundDataRec::m_soundDeathID, but that is just always triggered as soon as the death animation plays. |
$EAC | — | object package state enter 3, exit 2, 4, 5 | |||
$EDC | — | object package state enter 5, exit 3, 4, 2 | |||
$EMV | — | object package state enter 4, exit 3, 2, 5 | |||
$ESD | — | PlayEmoteStateSound | Unit | soundEffect ID is implicit by currently played emote | |
$EWT | — | object package state enter 2, exit 3, 4, 5 | |||
$FD[1-9] | — | PlayFidgetSound | CreatureSoundDataRec::m_soundFidget (only has 5 entries, so don’t use 6-9) | ||
$FDX | — | PlayUnitSound (stand) | soundEffect ID is defined by CreatureSoundDataRec::m_soundStandID | ||
$FL[0-3] | — | FootstepAnimEventHit (left) | Forward | ||
$FR[0-3] | — | FootstepAnimEventHit (right) | Forward | ||
$FSD | — | HandleFootfallAnimEvent | Unit | Walk, Run (multiple times), ... | Plays some sound. Footstep? Also seen at several emotes etc. where feet are moved. CGUnit_C::HandleFootfallAnimEvent |
$GC[0-3] | — | GameObject_C_PlayAnimatedSound | soundEffect ID is defined by GameObjectDisplayInfoRec::m_Sound[x + 6] ({Custom0, Custom1, Custom2, Custom3}) | ||
$GO[0-5] | — | GameObject_C_PlayAnimatedSound | soundEffect ID is defined by GameObjectDisplayInfoRec::m_Sound[x] ({Stand, Open, Loop, Close, Destroy, Opened}) | ||
$HIT | — | PlayWoundAnimKit | Unit | Attack*, *Unarmed, ShieldBash, Special* | soundEntryId depends on SpellVisualKit |
$KVS | ? | MapLoad.cpp -- not found in 6.0.1.18179 | |||
$RL[0-3] | — | FootstepAnimEventHit (left) | Running | ||
$RR[0-3] | — | FootstepAnimEventHit (right) | Running | ||
$SCD | — | PlaySoundKit (spellCastDirectedSound) | soundEffect ID is defined by CreatureSoundDataRec::m_spellCastDirectedSoundID | ||
$SHK | spellEffectCameraShakesID | AddShake | GO | ||
$SHL | — | ExchangeSheathedWeapon (left) | Sheath, HipSheath | ||
$SHR | — | ExchangeSheathedWeapon (right) | Sheath, HipSheath | ||
$SL[0-3] | — | FootstepAnimEventHit (left) | Stop, (JumpEnd), (Shuffle*) | Stop | |
$SMD | — | PlaySoundKit (submerged) | soundEffect ID is defined by CreatureSoundDatarec::m_submergedSoundID | ||
$SMG | — | PlaySoundKit (submerge) | soundEffect ID is defined by CreatureSoundDatarec::m_submergeSoundID | ||
$SND | soundEntryId | PlaySoundKit (custom) | GO | ||
$SR[0-3] | — | FootstepAnimEventHit (right) | Stop, (JumpEnd), (Shuffle*) | Stop | |
$STx | Mounts | MountTransitionObject::UpdateCharacterData | Not seen in 6.0.1.18179 -- x is {E and B} , sequence time is taken of both, pivot of $STB. (Also, attachment info for attachment 0) | ||
$TRD | — | HandleSpellEventSound | Unit | EmoteWork*, UseStanding* | soundEffect ID is implicit by SpellRec |
$VG[0-8] | — | HandleBoneAnimGrabEvent | |||
$VT[0-8] | — | HandleBoneAnimThrowEvent | |||
$WGG | — | PlayUnitSound (wingGlide) | soundEffect ID is defined by CreatureSoundDataRec::m_soundWingGlideID | ||
$WL[0-3] | — | FootstepAnimEventHit (left) | |||
$WNG | — | PlayUnitSound (wingFlap) | soundEffect ID is defined by CreatureSoundDataRec::m_soundWingFlapID | ||
$WR[0-3] | — | FootstepAnimEventHit (right) | |||
$WTB | — | Weapons | Weapon Trail Bottom position, also used for Bow String | ||
$WTT | — | Weapons | Weapon Trail Top position | ||
$WWG | ? | Calls some function in the Object VMT. -- Not seen in 6.0.1.18179 | |||
DEST | ? | exploding ballista, that one has a really fucked up block. Oo | |||
POIN | Unit | not fired | Data: ?, seen on multiple models. Basilisk for example. (6801) | ||
WHEE | ? | Data: 601+, Used on wheels at vehicles. |