private void picSlider_MouseDoubleClick(object sender, MouseEventArgs e) { int targetFrame = XtoRealFrameNumber(e.X); if (targetFrame < 0) { targetFrame = 0; } // Try to find animcommand under cursor foreach (WadAnimCommand ac in Animation.WadAnimation.AnimCommands) { if (ac.FrameBased && ac.Parameter1 == targetFrame) { AnimCommandDoubleClick?.Invoke(this, ac); return; } } // No animcommand found, try to create new one WadAnimCommand newCommand = new WadAnimCommand() { Type = WadAnimCommandType.PlaySound, Parameter1 = (short)targetFrame }; AnimCommandDoubleClick?.Invoke(this, newCommand); }
private void MoveCommand(bool down) { if (down) { foreach (DataGridViewRow row in gridViewCommands.SelectedRows) { int index = row.Index; if (index + 1 > _animCommands.Count - 1) { continue; } WadAnimCommand cmd = _animCommands[index]; _animCommands.RemoveAt(index); _animCommands.Insert(index + 1, cmd); gridViewCommands.Rows[index].Selected = false; gridViewCommands.Rows[index + 1].Selected = true; } } else { foreach (DataGridViewRow row in gridViewCommands.SelectedRows) { int index = row.Index; if (index - 1 < 0) { continue; } WadAnimCommand cmd = _animCommands[index]; _animCommands.RemoveAt(index); _animCommands.Insert(index - 1, cmd); gridViewCommands.Rows[index].Selected = false; gridViewCommands.Rows[index - 1].Selected = true; } } }
private void butCopy_Click(object sender, EventArgs e) { foreach (DataGridViewRow row in gridViewCommands.SelectedRows) { int index = row.Index; WadAnimCommand cmdCopy = _animCommands[index].Clone(); _animCommands.Insert(index + 1, cmdCopy); } }
private void GridViewCommands_SelectionChanged(object sender, EventArgs e) { if (gridViewCommands.SelectedRows.Count == 1) { int index = gridViewCommands.SelectedRows[0].Index; WadAnimCommand cmd = _animCommands[index]; animCommandEditor.Command = cmd; } else { return; } }
internal void SelectCommand(WadAnimCommand cmd) { foreach (DataGridViewRow row in gridViewCommands.Rows) { if (_animCommands[row.Index].Equals(cmd)) { row.Selected = true; } else { row.Selected = false; } } }
private void butAddEffect_Click(object sender, EventArgs e) { WadAnimCommand newCmd = new WadAnimCommand() { Type = WadAnimCommandType.SetPosition }; _animCommands.Add(newCmd); for (int i = 0; i < _animCommands.Count - 1; i++) { gridViewCommands.Rows[i].Selected = false; } gridViewCommands.Rows[_animCommands.Count - 1].Selected = true; }
public FormReplaceAnimCommands(AnimationEditor editor, WadAnimCommand refCommand = null) { InitializeComponent(); _editor = editor; aceFind.Initialize(_editor, true); aceReplace.Initialize(_editor, true); aceFind.Command = refCommand == null ? new WadAnimCommand() { Type = WadAnimCommandType.SetPosition } : refCommand; aceReplace.Command = new WadAnimCommand() { Type = WadAnimCommandType.SetPosition }; // Set window property handlers Configuration.LoadWindowProperties(this, _editor.Tool.Configuration); FormClosing += new FormClosingEventHandler((s, e) => Configuration.SaveWindowProperties(this, _editor.Tool.Configuration)); UpdateUI(); }
private void Search(bool newSearch = true) { // Reset previous search's state... dgvResults.Rows.Clear(); // Store flipeffect settings in case user later changes them if (newSearch) { _backupCommand = aceFind.Command.Clone(); } var collectedAnims = new List <int>(); for (int i = 0; i < _editor.Animations.Count; i++) { var anim = _editor.Animations[i]; foreach (var ac in anim.WadAnimation.AnimCommands) { if (WadAnimCommand.DistinctiveEquals(ac, _backupCommand, false)) { if (!collectedAnims.Contains(i)) { collectedAnims.Add(i); } string result = "Animation: " + anim.WadAnimation.Name + ", command: " + ac; dgvResults.Rows.Add(false, anim.Index, result); } } } UpdateUI(); if (newSearch) { statusLabel.Text = "Search finished. Found " + dgvResults.Rows.Count + " match" + (dgvResults.Rows.Count == 1 ? "" : "es") + " in " + collectedAnims.Count + " animation" + (collectedAnims.Count <= 1 ? "" : "s") + "."; } }
public void UpdateUI(WadAnimCommand cmd) { if (_currentlyDoingCommandSelection) { return; } if (cmd == null) { comboCommandType.Enabled = false; commandControls.Visible = false; return; } else { comboCommandType.Enabled = true; commandControls.Visible = true; } try { _currentlyDoingCommandSelection = true; comboCommandType.SelectedIndex = (int)(cmd.Type) - 1; switch (cmd.Type) { case WadAnimCommandType.SetPosition: commandControls.Visible = true; commandControls.SelectedTab = tabSetPosition; tbPosX.Value = cmd.Parameter1; tbPosY.Value = cmd.Parameter2; tbPosZ.Value = cmd.Parameter3; break; case WadAnimCommandType.SetJumpDistance: commandControls.Visible = true; commandControls.SelectedTab = tabSetJumpDistance; tbHorizontal.Value = cmd.Parameter1; tbVertical.Value = cmd.Parameter2; break; case WadAnimCommandType.EmptyHands: case WadAnimCommandType.KillEntity: commandControls.Visible = false; break; case WadAnimCommandType.PlaySound: commandControls.Visible = true; commandControls.SelectedTab = tabPlaySound; tbPlaySoundFrame.Value = cmd.Parameter1; nudSoundId.Value = cmd.Parameter2 & 0x3FFF; switch (cmd.Parameter2 & 0xC000) { default: comboPlaySoundConditions.SelectedIndex = 0; break; case 0x4000: comboPlaySoundConditions.SelectedIndex = 1; break; case 0x8000: comboPlaySoundConditions.SelectedIndex = 2; break; } break; case WadAnimCommandType.FlipEffect: commandControls.Visible = true; commandControls.SelectedTab = tabFlipeffect; tbFlipEffectFrame.Value = cmd.Parameter1; tbFlipEffect.Value = cmd.Parameter2 & 0x3FFF; switch (cmd.Parameter2 & 0xC000) { default: comboFlipeffectConditions.SelectedIndex = 0; break; case 0x4000: comboFlipeffectConditions.SelectedIndex = 1; break; case 0x8000: comboFlipeffectConditions.SelectedIndex = 2; break; } break; } } finally { _currentlyDoingCommandSelection = false; } }
private void ReplaceOrDelete(bool delete = false) { // Enlist all animations which are pending for replacement var animsToUndo = new List <AnimationNode>(); for (int i = 0; i < dgvResults.Rows.Count; i++) { var process = (bool)dgvResults.Rows[i].Cells[0].Value; var number = (int)dgvResults.Rows[i].Cells[1].Value; if (process && !animsToUndo.Any(anim => anim.Index == number)) { animsToUndo.Add(_editor.Animations[number]); } } // Undo _editor.Tool.UndoManager.PushAnimationChanged(_editor, animsToUndo); int count = 0; int animCount = 0; int actionCount = 0; bool alreadyFound = false; for (int i = 0; i < _editor.Animations.Count; i++) { // Collect animcommand indices to remove later in reverse order // with RemoveAt function. List <int> indicesToDelete = new List <int>(); for (int j = 0; j < _editor.Animations[i].WadAnimation.AnimCommands.Count; j++) { alreadyFound = false; var ac = _editor.Animations[i].WadAnimation.AnimCommands[j]; if (WadAnimCommand.DistinctiveEquals(ac, _backupCommand, false)) { if ((bool)dgvResults.Rows[count].Cells[0].Value == true) { if (!alreadyFound) // Increase counter for statistics { animCount++; alreadyFound = true; } if (delete) { indicesToDelete.Add(j); } else { var preparedCommand = aceReplace.Command.Clone(); // Preserve frame number in frame-based animcommands if (preparedCommand.FrameBased && ac.FrameBased) { preparedCommand.Parameter1 = ac.Parameter1; } _editor.Animations[i].WadAnimation.AnimCommands[j] = preparedCommand; } actionCount++; // Increase counter for statistics } count++; } } // Remove previously collected indices in reverse order. if (indicesToDelete.Count > 0) { indicesToDelete .OrderByDescending(a => a) .ToList() .ForEach(item => _editor.Animations[i].WadAnimation.AnimCommands.RemoveAt(item)); } } UpdateUI(); EditingWasDone = true; animsToUndo.ForEach(anim => _editor.Tool.AnimationEditorAnimationChanged(anim, false)); if (delete) { statusLabel.Text = "Deleted " + actionCount + " animcommand" + (actionCount == 1 ? "" : "s") + " in " + animCount + " animation" + (animCount == 1 ? "" : "s") + "."; } else { statusLabel.Text = "Replacement finished. Made " + actionCount + " replacement" + (actionCount == 1 ? "" : "s") + " in " + animCount + " animation" + (animCount == 1 ? "" : "s") + "."; } // Run one more extra pass to show deselected results Search(false); }
public void ReplaceAnimCommands(WadAnimCommand oldCommand, WadAnimCommand newCommand) => Animations.ForEach(a => a.WadAnimation.AnimCommands.ForEach(ac => { if (ac == oldCommand) { ac = newCommand.Clone(); } }));
internal static WadMoveable ConvertTr4MoveableToWadMoveable(Wad2 wad, Tr4Wad oldWad, int moveableIndex, Dictionary <int, WadTexture> textures) { wad_moveable oldMoveable = oldWad.Moveables[moveableIndex]; var newId = new WadMoveableId(oldMoveable.ObjectID); // A workaround to find out duplicated item IDs produced by StrPix unpatched for v130 wads. // In such case, a legacy name is used to guess real item ID, if this fails - broken item is filtered out. if (wad.Moveables.ContainsKey(newId)) { var message = "Duplicated moveable ID " + oldMoveable.ObjectID + " was identified while loading " + oldWad.BaseName + ". "; if (oldWad.LegacyNames.Count - oldWad.Statics.Count < moveableIndex) { logger.Warn(message + "Can't restore real ID by name, name table is too short. Ignoring moveable."); return(null); } bool isMoveable; var guessedId = TrCatalog.GetItemIndex(TRVersion.Game.TR4, oldWad.LegacyNames[moveableIndex], out isMoveable); if (isMoveable && guessedId.HasValue) { newId = new WadMoveableId(guessedId.Value); if (wad.Moveables.ContainsKey(newId)) { logger.Warn(message + "Can't restore real ID by name, name table is inconsistent. Ignoring moveable."); return(null); } else { logger.Warn(message + "Successfully restored real ID by name."); } } else { logger.Warn(message + "Can't find provided name in catalog. Ignoring moveable."); return(null); } } WadMoveable newMoveable = new WadMoveable(newId); var frameBases = new Dictionary <WadAnimation, ushort[]>(); // Load meshes var meshes = new List <WadMesh>(); for (int j = 0; j < oldMoveable.NumPointers; j++) { meshes.Add(ConvertTr4MeshToWadMesh(wad, oldWad, textures, oldWad.Meshes[(int)oldWad.RealPointers[oldMoveable.PointerIndex + j]], (int)oldMoveable.ObjectID)); } // Build the skeleton var root = new WadBone(); root.Name = "bone_0_root"; root.Parent = null; root.Translation = Vector3.Zero; root.Mesh = meshes[0]; newMoveable.Bones.Add(root); for (int j = 0; j < oldMoveable.NumPointers - 1; j++) { WadBone bone = new WadBone(); bone.Name = "bone_" + (j + 1).ToString(); bone.Parent = null; bone.Translation = Vector3.Zero; bone.Mesh = meshes[j + 1]; newMoveable.Bones.Add(bone); } for (int mi = 0; mi < (oldMoveable.NumPointers - 1); mi++) { int j = mi + 1; var opcode = (WadLinkOpcode)oldWad.Links[(int)(oldMoveable.LinksIndex + mi * 4)]; int linkX = oldWad.Links[(int)(oldMoveable.LinksIndex + mi * 4) + 1]; int linkY = -oldWad.Links[(int)(oldMoveable.LinksIndex + mi * 4) + 2]; int linkZ = oldWad.Links[(int)(oldMoveable.LinksIndex + mi * 4) + 3]; newMoveable.Bones[j].OpCode = opcode; newMoveable.Bones[j].Translation = new Vector3(linkX, linkY, linkZ); } // Convert animations int numAnimations = 0; int nextMoveable = oldWad.GetNextMoveableWithAnimations(moveableIndex); if (nextMoveable == -1) { numAnimations = oldWad.Animations.Count - oldMoveable.AnimationIndex; } else { numAnimations = oldWad.Moveables[nextMoveable].AnimationIndex - oldMoveable.AnimationIndex; } for (int j = 0; j < numAnimations; j++) { if (oldMoveable.AnimationIndex == -1) { break; } wad_animation oldAnimation = oldWad.Animations[j + oldMoveable.AnimationIndex]; WadAnimation newAnimation = new WadAnimation(); newAnimation.StateId = oldAnimation.StateId; newAnimation.FrameRate = oldAnimation.FrameDuration; newAnimation.NextAnimation = (ushort)(oldAnimation.NextAnimation - oldMoveable.AnimationIndex); newAnimation.NextFrame = oldAnimation.NextFrame; newAnimation.Name = TrCatalog.GetAnimationName(TRVersion.Game.TR4, oldMoveable.ObjectID, (uint)j); // Fix wadmerger/wad format bug with inverted frame start/end on single-frame anims ushort newFrameStart = oldAnimation.FrameStart < oldAnimation.FrameEnd ? oldAnimation.FrameStart : oldAnimation.FrameEnd; ushort newFrameEnd = oldAnimation.FrameStart < oldAnimation.FrameEnd ? oldAnimation.FrameEnd : newFrameStart; newAnimation.EndFrame = (ushort)(newFrameEnd - newFrameStart); for (int k = 0; k < oldAnimation.NumStateChanges; k++) { WadStateChange sc = new WadStateChange(); wad_state_change wadSc = oldWad.Changes[(int)oldAnimation.ChangesIndex + k]; sc.StateId = (ushort)wadSc.StateId; for (int n = 0; n < wadSc.NumDispatches; n++) { WadAnimDispatch ad = new WadAnimDispatch(); wad_anim_dispatch wadAd = oldWad.Dispatches[(int)wadSc.DispatchesIndex + n]; ad.InFrame = (ushort)(wadAd.Low - newFrameStart); ad.OutFrame = (ushort)(wadAd.High - newFrameStart); ad.NextAnimation = (ushort)((wadAd.NextAnimation - oldMoveable.AnimationIndex) % numAnimations); ad.NextFrame = (ushort)wadAd.NextFrame; sc.Dispatches.Add(ad); } newAnimation.StateChanges.Add(sc); } if (oldAnimation.NumCommands < oldWad.Commands.Count) { int lastCommand = oldAnimation.CommandOffset; for (int k = 0; k < oldAnimation.NumCommands; k++) { short commandType = oldWad.Commands[lastCommand]; WadAnimCommand command = new WadAnimCommand(); command.Type = (WadAnimCommandType)commandType; switch (command.Type) { case WadAnimCommandType.SetPosition: command.Parameter1 = (short)oldWad.Commands[lastCommand + 1]; command.Parameter2 = (short)oldWad.Commands[lastCommand + 2]; command.Parameter3 = (short)oldWad.Commands[lastCommand + 3]; lastCommand += 4; break; case WadAnimCommandType.SetJumpDistance: command.Parameter1 = (short)oldWad.Commands[lastCommand + 1]; command.Parameter2 = (short)oldWad.Commands[lastCommand + 2]; lastCommand += 3; break; case WadAnimCommandType.EmptyHands: case WadAnimCommandType.KillEntity: lastCommand += 1; break; case WadAnimCommandType.PlaySound: case WadAnimCommandType.FlipEffect: command.Parameter1 = (short)(oldWad.Commands[lastCommand + 1] - newFrameStart); command.Parameter2 = (short)oldWad.Commands[lastCommand + 2]; // For single-frame anims, clamp frame number to first frame (another fix for WM/wad format range inversion bug) if (newAnimation.EndFrame == 0 && command.Parameter1 > 0) { command.Parameter1 = 0; } lastCommand += 3; break; default: // Ignore invalid anim commands (see for example karnak.wad) logger.Warn("Invalid anim command " + commandType); goto ExitForLoop; } newAnimation.AnimCommands.Add(command); } ExitForLoop: ; } int frames = (int)oldAnimation.KeyFrameOffset / 2; uint numFrames = 0; if (oldAnimation.KeyFrameSize != 0) { if ((j + oldMoveable.AnimationIndex) == (oldWad.Animations.Count - 1)) { numFrames = ((uint)(2 * oldWad.KeyFrames.Count) - oldAnimation.KeyFrameOffset) / (uint)(2 * oldAnimation.KeyFrameSize); } else { numFrames = (oldWad.Animations[oldMoveable.AnimationIndex + j + 1].KeyFrameOffset - oldAnimation.KeyFrameOffset) / (uint)(2 * oldAnimation.KeyFrameSize); } } for (int f = 0; f < numFrames; f++) { WadKeyFrame frame = new WadKeyFrame(); int startOfFrame = frames; frame.BoundingBox = new BoundingBox(new Vector3(oldWad.KeyFrames[frames], -oldWad.KeyFrames[frames + 2], oldWad.KeyFrames[frames + 4]), new Vector3(oldWad.KeyFrames[frames + 1], -oldWad.KeyFrames[frames + 3], oldWad.KeyFrames[frames + 5])); frames += 6; frame.Offset = new Vector3(oldWad.KeyFrames[frames], (short)(-oldWad.KeyFrames[frames + 1]), oldWad.KeyFrames[frames + 2]); frames += 3; for (int n = 0; n < oldMoveable.NumPointers; n++) { frame.Angles.Add(WadKeyFrameRotation.FromTrAngle(ref frames, oldWad.KeyFrames, false, true)); } if ((frames - startOfFrame) < oldAnimation.KeyFrameSize) { frames += ((int)oldAnimation.KeyFrameSize - (frames - startOfFrame)); } newAnimation.KeyFrames.Add(frame); } // New velocities float acceleration = oldAnimation.Accel / 65536.0f; newAnimation.StartVelocity = oldAnimation.Speed / 65536.0f; float lateralAcceleration = oldAnimation.AccelLateral / 65536.0f; newAnimation.StartLateralVelocity = oldAnimation.SpeedLateral / 65536.0f; if (newAnimation.KeyFrames.Count != 0 && newAnimation.FrameRate != 0) { newAnimation.EndVelocity = newAnimation.StartVelocity + acceleration * (newAnimation.KeyFrames.Count - 1) * newAnimation.FrameRate; newAnimation.EndLateralVelocity = newAnimation.StartLateralVelocity + lateralAcceleration * (newAnimation.KeyFrames.Count - 1) * newAnimation.FrameRate; } else { // Basic foolproofness for potentially broken animations newAnimation.EndVelocity = newAnimation.StartVelocity; newAnimation.EndLateralVelocity = newAnimation.StartLateralVelocity; } // Deduce real maximum frame number, based on interpolation and keyframes. // We need to refer this value in NextFrame-related fixes (below) because of epic WadMerger bug, // which incorrectly calculates NextFrame and "steals" last frame from every custom animation. ushort maxFrameCount = (ushort)((newAnimation.FrameRate == 1 || numFrames <= 2) ? numFrames : ((numFrames - 1) * newAnimation.FrameRate) + 1); // Also correct animation out-point if (newAnimation.EndFrame >= maxFrameCount) { newAnimation.EndFrame = (ushort)(maxFrameCount - 1); } frameBases.Add(newAnimation, new ushort[] { newFrameStart, (ushort)(maxFrameCount - 1) }); newMoveable.Animations.Add(newAnimation); } for (int i = 0; i < newMoveable.Animations.Count; i++) { var animation = newMoveable.Animations[i]; if (animation.KeyFrames.Count == 0) { animation.EndFrame = 0; } // HACK: this fixes some invalid NextAnimations values animation.NextAnimation %= (ushort)newMoveable.Animations.Count; newMoveable.Animations[i] = animation; } for (int i = 0; i < newMoveable.Animations.Count; i++) { var animation = newMoveable.Animations[i]; // HACK: this fixes some invalid NextFrame values if (frameBases[newMoveable.Animations[animation.NextAnimation]][0] != 0) { animation.NextFrame -= frameBases[newMoveable.Animations[animation.NextAnimation]][0]; if (animation.NextFrame > frameBases[newMoveable.Animations[animation.NextAnimation]][1]) { animation.NextFrame = frameBases[newMoveable.Animations[animation.NextAnimation]][1]; } } foreach (var stateChange in animation.StateChanges) { for (int j = 0; j < stateChange.Dispatches.Count; ++j) { WadAnimDispatch animDispatch = stateChange.Dispatches[j]; // HACK: Probably WadMerger's bug if (animDispatch.NextAnimation > 32767) { animDispatch.NextAnimation = 0; animDispatch.NextFrame = 0; continue; } if (frameBases[newMoveable.Animations[animDispatch.NextAnimation]][0] != 0) { // HACK: In some cases dispatches have invalid NextFrame. // From tests it seems that's ok to make NextFrame equal to max frame number. animDispatch.NextFrame -= frameBases[newMoveable.Animations[animDispatch.NextAnimation]][0]; if (animDispatch.NextFrame > frameBases[newMoveable.Animations[animDispatch.NextAnimation]][1]) { animDispatch.NextFrame = frameBases[newMoveable.Animations[animDispatch.NextAnimation]][1]; } } stateChange.Dispatches[j] = animDispatch; } } } wad.Moveables.Add(newMoveable.Id, newMoveable); return(newMoveable); }
public static WadMoveable ConvertTrLevelMoveableToWadMoveable(Wad2 wad, TrLevel oldLevel, int moveableIndex, TextureArea[] objectTextures) { Console.WriteLine("Converting Moveable " + moveableIndex); var oldMoveable = oldLevel.Moveables[moveableIndex]; WadMoveable newMoveable = new WadMoveable(new WadMoveableId(oldMoveable.ObjectID)); // First a list of meshes for this moveable is built var oldMeshes = new List <tr_mesh>(); for (int j = 0; j < oldMoveable.NumMeshes; j++) { oldMeshes.Add(oldLevel.Meshes[(int)oldLevel.RealPointers[(int)(oldMoveable.StartingMesh + j)]]); } // Convert the WadMesh var newMeshes = new List <WadMesh>(); foreach (var oldMesh in oldMeshes) { newMeshes.Add(ConvertTrLevelMeshToWadMesh(wad, oldLevel, oldMesh, objectTextures)); } // Build the skeleton var root = new WadBone(); root.Name = "bone_0_root"; root.Parent = null; root.Translation = Vector3.Zero; root.Mesh = newMeshes[0]; newMoveable.Bones.Add(root); for (int j = 0; j < oldMoveable.NumMeshes - 1; j++) { var bone = new WadBone(); bone.Name = "bone_" + (j + 1).ToString(); bone.Parent = null; bone.Translation = Vector3.Zero; bone.Mesh = newMeshes[j + 1]; newMoveable.Bones.Add(bone); } for (int mi = 0; mi < (oldMeshes.Count - 1); mi++) { int j = mi + 1; var opcode = (WadLinkOpcode)oldLevel.MeshTrees[(int)(oldMoveable.MeshTree + mi * 4)]; int linkX = oldLevel.MeshTrees[(int)(oldMoveable.MeshTree + mi * 4) + 1]; int linkY = -oldLevel.MeshTrees[(int)(oldMoveable.MeshTree + mi * 4) + 2]; int linkZ = oldLevel.MeshTrees[(int)(oldMoveable.MeshTree + mi * 4) + 3]; newMoveable.Bones[j].OpCode = opcode; newMoveable.Bones[j].Translation = new Vector3(linkX, linkY, linkZ); } // Convert animations int numAnimations = 0; int nextMoveable = oldLevel.GetNextMoveableWithAnimations(moveableIndex); if (nextMoveable == -1) { numAnimations = oldLevel.Animations.Count - oldMoveable.Animation; } else { numAnimations = oldLevel.Moveables[nextMoveable].Animation - oldMoveable.Animation; } var frameBases = new Dictionary <WadAnimation, ushort>(); for (int j = 0; j < numAnimations; j++) { if (oldMoveable.Animation == -1) { break; } WadAnimation newAnimation = new WadAnimation(); var oldAnimation = oldLevel.Animations[j + oldMoveable.Animation]; newAnimation.FrameRate = oldAnimation.FrameRate; newAnimation.NextAnimation = (ushort)(oldAnimation.NextAnimation - oldMoveable.Animation); newAnimation.NextFrame = oldAnimation.NextFrame; newAnimation.StateId = oldAnimation.StateID; newAnimation.EndFrame = (ushort)(oldAnimation.FrameEnd - oldAnimation.FrameStart); newAnimation.Name = TrCatalog.GetAnimationName(oldLevel.Version, oldMoveable.ObjectID, (uint)j); for (int k = 0; k < oldAnimation.NumStateChanges; k++) { WadStateChange sc = new WadStateChange(); var wadSc = oldLevel.StateChanges[(int)oldAnimation.StateChangeOffset + k]; sc.StateId = wadSc.StateID; for (int n = 0; n < wadSc.NumAnimDispatches; n++) { WadAnimDispatch ad = new WadAnimDispatch(); var wadAd = oldLevel.AnimDispatches[(int)wadSc.AnimDispatch + n]; ad.InFrame = (ushort)(wadAd.Low - oldAnimation.FrameStart); ad.OutFrame = (ushort)(wadAd.High - oldAnimation.FrameStart); ad.NextAnimation = (ushort)((wadAd.NextAnimation - oldMoveable.Animation) % numAnimations); ad.NextFrame = (ushort)wadAd.NextFrame; sc.Dispatches.Add(ad); } newAnimation.StateChanges.Add(sc); } if (oldAnimation.NumAnimCommands < oldLevel.AnimCommands.Count) { int lastCommand = oldAnimation.AnimCommand; for (int k = 0; k < oldAnimation.NumAnimCommands; k++) { short commandType = oldLevel.AnimCommands[lastCommand + 0]; WadAnimCommand command = new WadAnimCommand { Type = (WadAnimCommandType)commandType }; switch (commandType) { case 1: command.Parameter1 = (short)oldLevel.AnimCommands[lastCommand + 1]; command.Parameter2 = (short)oldLevel.AnimCommands[lastCommand + 2]; command.Parameter3 = (short)oldLevel.AnimCommands[lastCommand + 3]; lastCommand += 4; break; case 2: command.Parameter1 = (short)oldLevel.AnimCommands[lastCommand + 1]; command.Parameter2 = (short)oldLevel.AnimCommands[lastCommand + 2]; lastCommand += 3; break; case 3: lastCommand += 1; break; case 4: lastCommand += 1; break; case 5: command.Parameter1 = (short)(oldLevel.AnimCommands[lastCommand + 1] - oldAnimation.FrameStart); command.Parameter2 = (short)oldLevel.AnimCommands[lastCommand + 2]; lastCommand += 3; break; case 6: command.Parameter1 = (short)(oldLevel.AnimCommands[lastCommand + 1] - oldAnimation.FrameStart); command.Parameter2 = (short)oldLevel.AnimCommands[lastCommand + 2]; lastCommand += 3; break; default: // Ignore invalid anim commands (see for example karnak.wad) logger.Warn("Unknown anim command " + commandType); goto ExitForLoop; } newAnimation.AnimCommands.Add(command); } ExitForLoop: ; } int frames = (int)oldAnimation.FrameOffset / 2; uint numFrames; if (j + oldMoveable.Animation == oldLevel.Animations.Count - 1) { if (oldAnimation.FrameSize == 0) { numFrames = oldLevel.Version == TRVersion.Game.TR1 ? (uint)((newAnimation.EndFrame + 1) / newAnimation.FrameRate) : 0; } else { numFrames = ((uint)(2 * oldLevel.Frames.Count) - oldAnimation.FrameOffset) / (uint)(2 * oldAnimation.FrameSize); } } else { if (oldAnimation.FrameSize == 0) { numFrames = oldLevel.Version == TRVersion.Game.TR1 ? (uint)((newAnimation.EndFrame + 1) / newAnimation.FrameRate) : 0; } else { numFrames = (oldLevel.Animations[oldMoveable.Animation + j + 1].FrameOffset - oldAnimation.FrameOffset) / (uint)(2 * oldAnimation.FrameSize); } } for (int f = 0; f < numFrames; f++) { WadKeyFrame frame = new WadKeyFrame(); int startOfFrame = frames; frame.BoundingBox = new BoundingBox(new Vector3(oldLevel.Frames[frames], -oldLevel.Frames[frames + 2], oldLevel.Frames[frames + 4]), new Vector3(oldLevel.Frames[frames + 1], -oldLevel.Frames[frames + 3], oldLevel.Frames[frames + 5])); frames += 6; frame.Offset = new Vector3(oldLevel.Frames[frames], (short)(-oldLevel.Frames[frames + 1]), oldLevel.Frames[frames + 2]); frames += 3; // TR1 has also the number of angles to follow if (oldLevel.Version == TRVersion.Game.TR1) { frames++; } for (int n = 0; n < oldMoveable.NumMeshes; n++) { frame.Angles.Add( WadKeyFrameRotation.FromTrAngle(ref frames, oldLevel.Frames, oldLevel.Version == TRVersion.Game.TR1, oldLevel.Version == TRVersion.Game.TR4 || oldLevel.Version == TRVersion.Game.TR5)); } if ((frames - startOfFrame) < oldAnimation.FrameSize) { frames += ((int)oldAnimation.FrameSize - (frames - startOfFrame)); } newAnimation.KeyFrames.Add(frame); } frameBases.Add(newAnimation, oldAnimation.FrameStart); // New velocities float acceleration = oldAnimation.Accel / 65536.0f; newAnimation.StartVelocity = oldAnimation.Speed / 65536.0f; float lateralAcceleration = oldAnimation.AccelLateral / 65536.0f; newAnimation.StartLateralVelocity = oldAnimation.SpeedLateral / 65536.0f; if (newAnimation.KeyFrames.Count != 0 && newAnimation.FrameRate != 0) { newAnimation.EndVelocity = newAnimation.StartVelocity + acceleration * (newAnimation.KeyFrames.Count - 1) * newAnimation.FrameRate; newAnimation.EndLateralVelocity = newAnimation.StartLateralVelocity + lateralAcceleration * (newAnimation.KeyFrames.Count - 1) * newAnimation.FrameRate; } newMoveable.Animations.Add(newAnimation); } for (int i = 0; i < newMoveable.Animations.Count; i++) { var animation = newMoveable.Animations[i]; if (animation.KeyFrames.Count == 0) { animation.EndFrame = 0; } // HACK: this fixes some invalid NextAnimations values animation.NextAnimation %= (ushort)newMoveable.Animations.Count; newMoveable.Animations[i] = animation; } for (int i = 0; i < newMoveable.Animations.Count; i++) { var animation = newMoveable.Animations[i]; // HACK: this fixes some invalid NextFrame values if (frameBases[newMoveable.Animations[animation.NextAnimation]] != 0) { animation.NextFrame %= frameBases[newMoveable.Animations[animation.NextAnimation]]; } foreach (var stateChange in animation.StateChanges) { for (int J = 0; J < stateChange.Dispatches.Count; ++J) { WadAnimDispatch animDispatch = stateChange.Dispatches[J]; if (frameBases[newMoveable.Animations[animDispatch.NextAnimation]] != 0) { ushort newFrame = (ushort)(animDispatch.NextFrame % frameBases[newMoveable.Animations[animDispatch.NextAnimation]]); // In some cases dispatches have invalid NextFrame. // From tests it seems that's ok to delete the dispatch or put the NextFrame equal to zero. if (newFrame > newMoveable.Animations[animDispatch.NextAnimation].EndFrame) { newFrame = 0; } animDispatch.NextFrame = newFrame; } stateChange.Dispatches[J] = animDispatch; } } newMoveable.Animations[i] = animation; } return(newMoveable); }