private void ReloadSounds() { comboSound.Items.Clear(); var defaultSoundList = TrCatalog.GetAllSounds(_editor.Tool.DestinationWad.GameVersion); var soundCatalogPresent = _editor.Tool.ReferenceLevel != null && _editor.Tool.ReferenceLevel.Settings.GlobalSoundMap.Count > 0; var maxKnownSound = -1; foreach (var sound in defaultSoundList) { if (sound.Key > maxKnownSound) { maxKnownSound = (int)sound.Key; } } if (soundCatalogPresent) { foreach (var sound in _editor.Tool.ReferenceLevel.Settings.GlobalSoundMap) { if (sound.Id > maxKnownSound) { maxKnownSound = sound.Id; } } } for (int i = 0; i <= maxKnownSound; i++) { var lbl = i.ToString().PadLeft(4, '0') + ": "; if (soundCatalogPresent && _editor.Tool.ReferenceLevel.Settings.GlobalSoundMap.Any(item => item.Id == i)) { lbl += _editor.Tool.ReferenceLevel.Settings.GlobalSoundMap.First(item => item.Id == i).Name; } else if (defaultSoundList.Any(item => item.Key == i)) { lbl += defaultSoundList.First(item => item.Key == i).Value; } else { lbl += "Unknown sound"; } comboSound.Items.Add(lbl); } comboSound.Items.Add("Custom sound ID"); comboSound.SelectedIndex = 0; }
public FormSkeletonEditor(WadToolClass tool, DeviceManager manager, Wad2 wad, WadMoveableId moveableId) { InitializeComponent(); _wad = wad; _moveable = _wad.Moveables[moveableId].Clone(); _tool = tool; panelRendering.Configuration = _tool.Configuration; panelRendering.InitializeRendering(tool, manager); _tool.EditorEventRaised += Tool_EditorEventRaised; WadMoveable skin; var skinId = new WadMoveableId(TrCatalog.GetMoveableSkin(_tool.DestinationWad.GameVersion, _moveable.Id.TypeId)); if (_tool.DestinationWad.Moveables.ContainsKey(skinId)) { skin = _tool.DestinationWad.Moveables[skinId]; } else { skin = _tool.DestinationWad.Moveables[_moveable.Id]; } // Clone the skeleton and load it _bones = new List <WadMeshBoneNode>(); for (int i = 0; i < _moveable.Bones.Count; i++) { var boneNode = new WadMeshBoneNode(null, skin.Bones[i].Mesh, _moveable.Bones[i]); boneNode.Bone.Translation = _moveable.Bones[i].Translation; boneNode.GlobalTransform = Matrix4x4.Identity; _bones.Add(boneNode); } treeSkeleton.Nodes.AddRange(LoadSkeleton()); ExpandSkeleton(); panelRendering.Skeleton = _bones; if (treeSkeleton.SelectedNodes.Count <= 0) { treeSkeleton.SelectNode(treeSkeleton.Nodes[0]); panelRendering.SelectedNode = (WadMeshBoneNode)treeSkeleton.SelectedNodes[0].Tag; } panelRendering.Invalidate(); }
private void dgvStateChanges_CellFormattingSafe(object sender, DarkUI.Controls.DarkDataGridViewSafeCellFormattingEventArgs e) { if (!(e.Row.DataBoundItem is WadStateChangeRow)) { return; } if (e.ColumnIndex == 0) { e.CellStyle.ForeColor = Colors.DisabledText.Multiply(0.8f); } else if (e.ColumnIndex == 4) { var item = (WadStateChangeRow)e.Row.DataBoundItem; var cell = dgvStateChanges.Rows[e.RowIndex].Cells[e.ColumnIndex]; cell.ToolTipText = TrCatalog.GetAnimationName(_editor.Tool.DestinationWad.GameVersion, _editor.Moveable.Id.TypeId, item.NextAnimation); } }
private void Initialize(AnimationNode animation, WadStateChange newStateChange) { if (_initializing) { return; } _initializing = true; _animation = animation; _backupStates = new List <WadStateChange>(); foreach (var sc in animation.WadAnimation.StateChanges) { _backupStates.Add(sc.Clone()); } lblStateChangeAnnouncement.Text = string.Empty; dgvStateChanges.Rows.Clear(); var rows = new List <WadStateChangeRow>(); foreach (var sc in _animation.WadAnimation.StateChanges) { foreach (var d in sc.Dispatches) { rows.Add(new WadStateChangeRow(TrCatalog.GetStateName(_editor.Tool.DestinationWad.GameVersion, _editor.Moveable.Id.TypeId, sc.StateId), sc.StateId, d.InFrame, d.OutFrame, d.NextAnimation, d.NextFrame)); } } if (newStateChange != null && newStateChange.Dispatches.Count == 1) { rows.Add(new WadStateChangeRow(TrCatalog.GetStateName(_editor.Tool.DestinationWad.GameVersion, _editor.Moveable.Id.TypeId, newStateChange.StateId), newStateChange.StateId, newStateChange.Dispatches[0].InFrame, newStateChange.Dispatches[0].OutFrame, newStateChange.Dispatches[0].NextAnimation, newStateChange.Dispatches[0].NextFrame)); _createdNew = true; } dgvStateChanges.DataSource = new BindingList <WadStateChangeRow>(new List <WadStateChangeRow>(rows)); _initializing = false; }
public FormStateChangesEditor(AnimationEditor editor, AnimationNode animation, WadStateChange newStateChange = null) { InitializeComponent(); _editor = editor; dgvControls.CreateNewRow = () => new WadStateChangeRow() { StateName = TrCatalog.GetStateName(_editor.Tool.DestinationWad.GameVersion, _editor.Moveable.Id.TypeId, 0) }; dgvControls.DataGridView = dgvStateChanges; dgvControls.Enabled = true; Initialize(animation, newStateChange); _editor.Tool.EditorEventRaised += Tool_EditorEventRaised; // Set window property handlers Configuration.LoadWindowProperties(this, _editor.Tool.Configuration); FormClosing += new FormClosingEventHandler((s, e) => Configuration.SaveWindowProperties(this, _editor.Tool.Configuration)); }
private void FindLaraSkin() { if (comboItems.Items.Count == 0 || comboItems.SelectedIndex < 0 || !(comboItems.SelectedItem is WadMoveable)) { return; } var item = comboItems.SelectedItem as WadMoveable; if (item.Id == WadMoveableId.Lara) // Show Lara's skin { var skinId = new WadMoveableId(TrCatalog.GetMoveableSkin(_editor.Level.Settings.GameVersion, item.Id.TypeId)); var moveableSkin = _editor.Level.Settings.WadTryGetMoveable(skinId); if (moveableSkin != null) { panelItem.CurrentObject = moveableSkin; } panelItem.Invalidate(); } }
private void ReloadSlots() { // Decide on ID type if (TypeClass == typeof(WadMoveableId)) { PopulateSlots(TrCatalog.GetAllMoveables(GameVersion).Where(item => !_wad.Moveables.Any(moveable => moveable.Key.TypeId == item.Key) && !(_additionalObjectsToHide?.Any(add => add == item.Key) ?? false)).ToList()); } else if (TypeClass == typeof(WadStaticId)) { PopulateSlots(TrCatalog.GetAllStatics(GameVersion).Where(item => !_wad.Statics.Any(stat => stat.Key.TypeId == item.Key) && !(_additionalObjectsToHide?.Any(add => add == item.Key) ?? false)).ToList()); } else if (TypeClass == typeof(WadSpriteSequenceId)) { PopulateSlots(TrCatalog.GetAllSpriteSequences(GameVersion).Where(item => !_wad.SpriteSequences.Any(sprite => sprite.Key.TypeId == item.Key) && !(_additionalObjectsToHide?.Any(add => add == item.Key) ?? false)).ToList()); } else { throw new NotImplementedException("The " + TypeClass + " is not implemented yet."); } // Make sure it redraws lstSlots.Invalidate(); }
private void dgvStateChanges_CellValidating(object sender, DataGridViewCellValidatingEventArgs e) { if (e.ColumnIndex == 0) { return; } try { var cell = dgvStateChanges.Rows[e.RowIndex].Cells[e.ColumnIndex]; var name = dgvStateChanges.Columns[e.ColumnIndex].Name; // For some reason, validating against UInt16 type results in unrecoverable DGV exception on // wrong incoming values, so we're validating against Int16 and filtering out negative values afterwards. Int16 parsedValue = 0; if (e.FormattedValue == null || !Int16.TryParse(e.FormattedValue.ToString(), out parsedValue)) { if (!Int16.TryParse(cell.Value.ToString(), out parsedValue)) { parsedValue = 0; } } var limit = Int16.MaxValue; if (name == columnNextAnimation.Name) { limit = (Int16)(_editor.Animations.Count - 1); } else if (name == columnNextFrame.Name) { Int16 limitNew = 0; if (Int16.TryParse(dgvStateChanges.Rows[e.RowIndex].Cells[4].Value.ToString(), out limitNew)) { limit = (Int16)(_editor.GetRealNumberOfFrames(limitNew)); } } else if (name == columnLowFrame.Name) { Int16 limitNew = 0; if (Int16.TryParse(dgvStateChanges.Rows[e.RowIndex].Cells[3].Value.ToString(), out limitNew)) { limit = limitNew; } } else if (name == columnHighFrame.Name) { limit = (Int16)(_editor.GetRealNumberOfFrames() - 1); } if (parsedValue > limit) { cell.Value = limit; } else if (parsedValue < 0) { cell.Value = (Int16)0; } else { cell.Value = parsedValue; } if (name == columnStateId.Name) { dgvStateChanges.Rows[e.RowIndex].Cells[0].Value = TrCatalog.GetStateName(_editor.Tool.DestinationWad.GameVersion, _editor.Moveable.Id.TypeId, (uint)parsedValue); } } catch (Exception ex) { } }
public static bool ConvertWad2ToNewSoundFormat(string src, string dest) { /* PROCEDURE: * 1. Collect all sounds from Wad2 * 2. Initialise the list of SoundInfoConversionRow, getting ID from TrCatalog * 3. Show the dialog to the user. Here he can load an additional catalog if he changed sounds via TRLE tools. * He can also choose which sounds to export to Xml and if export also samples. * 4. Assign new IDs to sound infos * 5. Remap sounds in animcommands * 6. Optionally export samples if needed and bind them to sound infos * 7. Save Wad2 + Xml (if sounds are present) */ try { // Load Wad2 Wad2 wad = Wad2Loader.LoadFromFile(src, false); // Check if the Wad2 needs to be converted if (wad.SoundSystem != SoundSystem.Dynamic) { return(true); } // Now collect all sound infos from obsolete lists and build a new list var soundInfos = wad.AllLoadedSoundInfos.Values.ToList(); // Loop through each sound info and try to get the classic Id from TrCatalog.xml var conversionList = new List <SoundInfoConversionRow>(); foreach (var soundInfo in soundInfos) { var row = new SoundInfoConversionRow(soundInfo, soundInfo.Name); // If user has changed name, result will be -1 and the user will need to manually set the new sound id row.NewId = TrCatalog.TryGetSoundInfoIdByDescription(wad.GameVersion, soundInfo.Name); if (row.NewId != -1) { row.NewName = TrCatalog.GetOriginalSoundName(wad.GameVersion, (uint)row.NewId); } conversionList.Add(row); } // Now we'll show a dialog with all conversion rows and the user will need to make some choices WadSounds sounds = null; using (var form = new Wad2SoundsConversionDialog(wad.GameVersion, conversionList)) { if (form.ShowDialog() == DialogResult.Cancel) { return(false); } // If the user has loaded a custom catalog, let's get a pointer to it if (form.Sounds != null) { sounds = form.Sounds; } } // Assign new Id and name foreach (var row in conversionList) { row.SoundInfo.Id = row.NewId; row.SoundInfo.Name = row.NewName; } // Remap all sounds in animcommands foreach (var row in conversionList) { if (row.NewId != -1) { foreach (var moveable in wad.Moveables) { foreach (var animation in moveable.Value.Animations) { foreach (var cmd in animation.AnimCommands) { if (cmd.SoundInfoObsolete != null && cmd.SoundInfoObsolete == row.SoundInfo) { cmd.Parameter2 = (short)((cmd.Parameter2 & 0xc000) | row.NewId); cmd.SoundInfoObsolete = null; } } } } } } // Bind samples (only if additional catalog was loaded, TrCatalog has not samples names) if (sounds != null) { foreach (var row in conversionList) { if (row.SaveToXml) { if (row.ExportSamples) { // We export samples only if user has marked both Export to Xml and Export samples checkboxes var samples = new List <string>(); foreach (var sample in row.SoundInfo.Samples) { if (sample.IsLoaded) { // If sample is valid, export the .WAV file to the same directory of Wad2 string sampleName = row.NewName.ToLower() + "_" + row.SoundInfo.Samples.IndexOf(sample) + ".wav"; samples.Add(sampleName); File.WriteAllBytes(Path.GetDirectoryName(dest) + "\\" + sampleName, sample.Data); } } // Assign new samples names row.SoundInfo.Samples.Clear(); foreach (var sample in samples) { row.SoundInfo.Samples.Add(new WadSample(sample)); } } else { // If samples count is the same then use samples from catalog var refInfo = sounds.TryGetSoundInfo(row.NewId); if (refInfo != null && row.SoundInfo.Samples.Count == refInfo.Samples.Count) { row.SoundInfo.Samples.Clear(); row.SoundInfo.Samples.AddRange(refInfo.Samples); } else { row.SoundInfo.Samples.Clear(); } } } } } // Create the new sounds archive foreach (var row in conversionList) { if (row.SaveToXml) { wad.Sounds.SoundInfos.Add(row.SoundInfo); } } // Sort sound infos wad.Sounds.SoundInfos.Sort((a, b) => a.Id.CompareTo(b.Id)); // Make a backup copy if (src == dest) { int index = 0; string backupFilename = ""; while (true) { backupFilename = dest + "." + index + ".bak"; if (!File.Exists(backupFilename)) { break; } index++; } File.Copy(src, backupFilename, true); } // Save Wad2 with Xml sounds wad.SoundSystem = SoundSystem.Xml; Wad2Writer.SaveToFile(wad, dest); // Finished! return(true); } catch (Exception e) { return(false); } }
private void Tool_EditorEventRaised(IEditorEvent obj) { if (obj is InitEvent) { // At startup initialise a new Wad2 if (_tool.Configuration.Tool_MakeEmptyWadAtStartup) { _tool.DestinationWad = new Wad2 { GameVersion = TRVersion.Game.TR4 }; _tool.RaiseEvent(new WadToolClass.DestinationWadChangedEvent()); } } if (obj is WadToolClass.MessageEvent) { var msg = (WadToolClass.MessageEvent)obj; PopUpInfo.Show(popup, this, panel3D, msg.Message, msg.Type); } if (obj is WadToolClass.SelectedObjectEditedEvent || obj is InitEvent) { } if (obj is WadToolClass.DestinationWadChangedEvent || obj is InitEvent) { treeDestWad.Wad = _tool.DestinationWad; treeDestWad.UpdateContent(); panel3D.UpdateAnimationScrollbar(); panel3D.Invalidate(); if (_tool.DestinationWad != null) { labelStatistics.Text = "Moveables: " + _tool.DestinationWad.Moveables.Count + " | " + "Statics: " + _tool.DestinationWad.Statics.Count + " | " + "Sprites sequences: " + _tool.DestinationWad.SpriteSequences.Count + " | " + "Textures: " + _tool.DestinationWad.MeshTexturesUnique.Count; } else { labelStatistics.Text = ""; } } if (obj is WadToolClass.SourceWadChangedEvent || obj is InitEvent) { panelSource.SectionHeader = "Source"; if (_tool?.SourceWad != null) { panelSource.SectionHeader += (String.IsNullOrEmpty(_tool.SourceWad.FileName) ? " (Imported)" : " (" + Path.GetFileName(_tool.SourceWad.FileName) + ")"); } treeSourceWad.Wad = _tool.SourceWad; treeSourceWad.UpdateContent(); panel3D.UpdateAnimationScrollbar(); panel3D.Invalidate(); } if (obj is WadToolClass.MainSelectionChangedEvent || obj is WadToolClass.DestinationWadChangedEvent || obj is WadToolClass.SourceWadChangedEvent || obj is InitEvent) { var mainSelection = _tool.MainSelection; if (mainSelection == null) { panel3D.CurrentObject = null; butEditAnimations.Visible = false; butEditSkeleton.Visible = false; butEditStaticModel.Visible = false; butEditSpriteSequence.Visible = false; } else { Wad2 wad = _tool.GetWad(mainSelection.Value.WadArea); // Display the object (or set it to Lara's skin instead if it's Lara) if (mainSelection.Value.Id is WadMoveableId) { panel3D.CurrentObject = wad.TryGet(new WadMoveableId(TrCatalog.GetMoveableSkin(wad.GameVersion, ((WadMoveableId)mainSelection.Value.Id).TypeId))); } else { panel3D.CurrentObject = wad.TryGet(mainSelection.Value.Id); } panel3D.AnimationIndex = 0; panel3D.KeyFrameIndex = 0; // Update the toolbar below the rendering area butEditAnimations.Visible = (mainSelection.Value.Id is WadMoveableId); butEditSkeleton.Visible = (mainSelection.Value.Id is WadMoveableId); butEditStaticModel.Visible = (mainSelection.Value.Id is WadStaticId); butEditSpriteSequence.Visible = (mainSelection.Value.Id is WadSpriteSequenceId); panel3D.ResetCamera(); panel3D.Invalidate(); } panel3D.UpdateAnimationScrollbar(); panel3D.Invalidate(); } if (obj is WadToolClass.ReferenceLevelChangedEvent) { if (_tool.ReferenceLevel != null) { butCloseRefLevel.Enabled = true; lblRefLevel.Enabled = true; closeReferenceLevelToolStripMenuItem.Enabled = true; lblRefLevel.Text = Path.GetFileNameWithoutExtension(_tool.ReferenceLevel.Settings.LevelFilePath); } else { butCloseRefLevel.Enabled = false; lblRefLevel.Enabled = false; closeReferenceLevelToolStripMenuItem.Enabled = false; lblRefLevel.Text = "(project not loaded)"; } } if (obj is WadToolClass.UnsavedChangesEvent) { UpdateSaveUI(((WadToolClass.UnsavedChangesEvent)obj).UnsavedChanges); } }
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); }
public string ToString(TRVersion.Game gameVersion) { return("(" + TypeId + ") " + TrCatalog.GetOriginalSoundName(gameVersion, TypeId)); }
public static Wad2 ConvertWad2ToTR5Main(WadToolClass tool, IWin32Window owner, Wad2 src) { Wad2 dest = new Wad2(); dest.GameVersion = TRVersion.Game.TR5Main; dest.SoundSystem = SoundSystem.Xml; foreach (var moveable in src.Moveables) { var compatibleSlot = TrCatalog.GetMoveableTR5MainSlot(src.GameVersion, moveable.Key.TypeId); if (compatibleSlot == "") { continue; } bool isMoveable = false; var destId = TrCatalog.GetItemIndex(TRVersion.Game.TR5Main, compatibleSlot, out isMoveable); if (!destId.HasValue) { continue; } var newId = new WadMoveableId(destId.Value); foreach (var animation in moveable.Value.Animations) { foreach (var command in animation.AnimCommands) { if (command.Type == WadAnimCommandType.PlaySound) { int id = command.Parameter2 & 0x3FFF; id += TrCatalog.GetTR5MainSoundMapStart(src.GameVersion); command.Parameter2 = (short)((command.Parameter2 & 0xC000) | (id & 0x3FFF)); } } } dest.Add(newId, moveable.Value); } foreach (var sequence in src.SpriteSequences) { var compatibleSlot = TrCatalog.GetSpriteSequenceTR5MainSlot(src.GameVersion, sequence.Key.TypeId); if (compatibleSlot == "") { continue; } bool isMoveable = false; var destId = TrCatalog.GetItemIndex(TRVersion.Game.TR5Main, compatibleSlot, out isMoveable); if (!destId.HasValue) { continue; } var newId = new WadSpriteSequenceId(destId.Value); dest.Add(newId, sequence.Value); } foreach (var staticObject in src.Statics) { dest.Add(staticObject.Key, staticObject.Value); } return(dest); }
public static void Main(string[] args) { string startFile = null; string batchFile = null; bool doBatchCompile = false; BatchCompileList batchList = null; if (args.Length >= 1) { // Open files on start if (args[0].EndsWith(".prj2", StringComparison.InvariantCultureIgnoreCase)) { startFile = args[0]; } // Batch-compile levels if (args[0].EndsWith(".xml", StringComparison.InvariantCultureIgnoreCase)) { batchFile = args[0]; batchList = BatchCompileList.ReadFromXml(batchFile); doBatchCompile = batchList?.Files.Count > 0; } } // Load configuration var initialEvents = new List <LogEventInfo>(); var configuration = new Configuration().LoadOrUseDefault <Configuration>(initialEvents); // Update DarkUI configuration Colors.Brightness = configuration.UI_FormColor_Brightness / 100.0f; if (configuration.Editor_AllowMultipleInstances || doBatchCompile || mutex.WaitOne(TimeSpan.Zero, true)) { // Setup logging using (var log = new Logging(configuration.Log_MinLevel, configuration.Log_WriteToFile, configuration.Log_ArchiveN, initialEvents)) { // Create configuration file configuration.SaveTry(); // Setup application Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); Application.ThreadException += (sender, e) => { log.HandleException(e.Exception); using (var dialog = new ThreadExceptionDialog(e.Exception)) if (dialog.ShowDialog() == DialogResult.Abort) { Environment.Exit(1); } }; Application.AddMessageFilter(new ControlScrollFilter()); SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext()); if (!File.Exists(Application.StartupPath + "\\Catalogs\\TrCatalog.xml") || !File.Exists(Application.StartupPath + "\\Catalogs\\NgCatalog.xml")) { MessageBox.Show("One of the catalog files is missing.\nMake sure you have TrCatalog.xml and NgCatalog.xml in /Catalogs/ subfolder."); Environment.Exit(1); } // Load catalogs TrCatalog.LoadCatalog(Application.StartupPath + "\\Catalogs\\TrCatalog.xml"); NgCatalog.LoadCatalog(Application.StartupPath + "\\Catalogs\\NgCatalog.xml"); // Run Editor editor = new Editor(SynchronizationContext.Current, configuration); Editor.Instance = editor; // Run editor normally if no batch compile is pending. // Otherwise, don't load main form and jump straight to batch-compiling levels. if (!doBatchCompile) { using (FormMain form = new FormMain(editor)) { form.Show(); if (!string.IsNullOrEmpty(startFile)) // Open files on start { if (startFile.EndsWith(".prj", StringComparison.InvariantCultureIgnoreCase)) { EditorActions.OpenLevelPrj(form, startFile); } else { EditorActions.OpenLevel(form, startFile); } } else if (editor.Configuration.Editor_OpenLastProjectOnStartup) { if (Properties.Settings.Default.RecentProjects != null && Properties.Settings.Default.RecentProjects.Count > 0 && File.Exists(Properties.Settings.Default.RecentProjects[0])) { EditorActions.OpenLevel(form, Properties.Settings.Default.RecentProjects[0]); } } Application.Run(form); } } else { EditorActions.BuildInBatch(editor, batchList, batchFile); } } } else if (startFile != null) // Send opening file to existing editor instance { SingleInstanceManagement.Send(Process.GetCurrentProcess(), new List <string>() { ".prj2" }, startFile); } else // Just bring editor to top, if user tries to launch another copy { SingleInstanceManagement.Bump(Process.GetCurrentProcess()); } }
private static Wad2 LoadWad2(ChunkReader chunkIO, bool obsolete) { if (obsolete) { LEB128.ReadUInt(chunkIO.Raw); } var wad = new Wad2(); Dictionary <long, WadTexture> textures = null; Dictionary <long, WadSample> samples = null; Dictionary <long, WadSoundInfo> soundInfos = null; Dictionary <long, WadSprite> sprites = null; wad.SoundSystem = SoundSystem.Dynamic; chunkIO.ReadChunks((id, chunkSize) => { if (id == Wad2Chunks.GameVersion) { wad.GameVersion = (TRVersion.Game)chunkIO.ReadChunkLong(chunkSize); return(true); } else if (id == Wad2Chunks.SoundSystem) { wad.SoundSystem = (SoundSystem)chunkIO.ReadChunkLong(chunkSize); return(true); } else if (LoadTextures(chunkIO, id, wad, ref textures)) { return(true); } else if (LoadSamples(chunkIO, id, wad, ref samples, obsolete)) { return(true); } else if (LoadSoundInfos(chunkIO, id, wad, ref soundInfos, samples)) { return(true); } else if (LoadFixedSoundInfos(chunkIO, id, wad, soundInfos)) { return(true); } else if (LoadAdditionalSoundInfos(chunkIO, id, wad, soundInfos, samples)) { return(true); } else if (LoadSprites(chunkIO, id, wad, ref sprites)) { return(true); } else if (LoadSpriteSequences(chunkIO, id, wad, sprites)) { return(true); } else if (LoadMoveables(chunkIO, id, wad, soundInfos, textures)) { return(true); } else if (LoadStatics(chunkIO, id, wad, textures)) { return(true); } return(false); }); if (obsolete) { foreach (KeyValuePair <long, WadSoundInfo> soundInfo in soundInfos) { if (TrCatalog.IsSoundFixedByDefault(TRVersion.Game.TR4, checked ((uint)soundInfo.Key))) { var Id = new WadFixedSoundInfoId(checked ((uint)soundInfo.Key)); wad.FixedSoundInfosObsolete.Add(Id, new WadFixedSoundInfo(Id) { SoundInfo = soundInfo.Value }); } } } // XML_SOUND_SYSTEM: Used for conversion of Wad2 to new sound system wad.AllLoadedSoundInfos = soundInfos; // Force wad to be xml wad in case there's no sound infos at all if (wad.SoundSystem != SoundSystem.Xml && wad.AllLoadedSoundInfos?.Count == 0) { wad.SoundSystem = SoundSystem.Xml; } return(wad); }
private static bool LoadSoundInfo(ChunkReader chunkIO, Wad2 wad, Dictionary <long, WadSample> samples, out WadSoundInfo soundInfo, out long index) { var tempSoundInfo = new WadSoundInfo(0); long tempIndex = 0; float volume = 0; float chance = 0; float pitch = 0; float range = 0; chunkIO.ReadChunks((id2, chunkSize2) => { // XML_SOUND_SYSTEM if (id2 == Wad2Chunks.SoundInfoIndex) { tempIndex = chunkIO.ReadChunkLong(chunkSize2); } else if (id2 == Wad2Chunks.SoundInfoVolume) { volume = chunkIO.ReadChunkFloat(chunkSize2); } else if (id2 == Wad2Chunks.SoundInfoRange) { range = chunkIO.ReadChunkFloat(chunkSize2); } else if (id2 == Wad2Chunks.SoundInfoPitch) { pitch = chunkIO.ReadChunkFloat(chunkSize2); } else if (id2 == Wad2Chunks.SoundInfoChance) { chance = chunkIO.ReadChunkFloat(chunkSize2); } else if (id2 == Wad2Chunks.SoundInfoDisablePanning) { tempSoundInfo.DisablePanning = chunkIO.ReadChunkBool(chunkSize2); } else if (id2 == Wad2Chunks.SoundInfoRandomizePitch) { tempSoundInfo.RandomizePitch = chunkIO.ReadChunkBool(chunkSize2); } else if (id2 == Wad2Chunks.SoundInfoRandomizeVolume) { tempSoundInfo.RandomizeVolume = chunkIO.ReadChunkBool(chunkSize2); } else if (id2 == Wad2Chunks.SoundInfoLoopBehaviour) { tempSoundInfo.LoopBehaviour = (WadSoundLoopBehaviour)(3 & chunkIO.ReadChunkByte(chunkSize2)); } else if (id2 == Wad2Chunks.SoundInfoName || id2 == Wad2Chunks.SoundInfoNameObsolete) { tempSoundInfo.Name = chunkIO.ReadChunkString(chunkSize2); } else if (id2 == Wad2Chunks.SoundInfoSampleIndex) { tempSoundInfo.Samples.Add(samples[chunkIO.ReadChunkInt(chunkSize2)]); // Legacy } else { return(false); } return(true); }); // Convert from floats to ints tempSoundInfo.Volume = (int)Math.Round(volume * 100.0f); tempSoundInfo.RangeInSectors = (int)range; tempSoundInfo.Chance = (int)Math.Round(chance * 100.0f); tempSoundInfo.PitchFactor = (int)Math.Round((pitch - 1.0f) * 100.0f); // Try to get the old ID tempSoundInfo.Id = TrCatalog.TryGetSoundInfoIdByDescription(wad.GameVersion, tempSoundInfo.Name); if (string.IsNullOrWhiteSpace(tempSoundInfo.Name)) { tempSoundInfo.Name = TrCatalog.GetOriginalSoundName(wad.GameVersion, unchecked ((uint)tempIndex)); } index = tempIndex; soundInfo = tempSoundInfo; return(true); }
internal static WadStatic ConvertTr4StaticMeshToWadStatic(Wad2 wad, Tr4Wad oldWad, int staticIndex, /*List<WadMesh> meshes*/ Dictionary <int, WadTexture> textures) { var oldStaticMesh = oldWad.Statics[staticIndex]; var newId = new WadStaticId(oldStaticMesh.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.Statics.ContainsKey(newId)) { var message = "Duplicated static ID " + oldStaticMesh.ObjectId + " was identified while loading " + oldWad.BaseName + ". "; if (oldWad.LegacyNames.Count - oldWad.Moveables.Count < staticIndex) { logger.Warn(message + "Can't restore real ID by name, name table is too short. Ignoring static."); return(null); } bool isMoveable; var guessedId = TrCatalog.GetItemIndex(TRVersion.Game.TR4, oldWad.LegacyNames[oldWad.Moveables.Count + staticIndex], out isMoveable); if (!isMoveable && guessedId.HasValue) { newId = new WadStaticId(guessedId.Value); if (wad.Statics.ContainsKey(newId)) { logger.Warn(message + "Can't restore real ID by name, name table is inconsistent. Ignoring static."); return(null); } else { logger.Warn(message + "Successfully restored real ID by name."); } } else { logger.Warn(message + "Can't find provided name in catalog. Ignoring static."); return(null); } } var staticMesh = new WadStatic(newId); //staticMesh.Name = TrCatalog.GetStaticName(WadTombRaiderVersion.TR4, oldStaticMesh.ObjectId); // First setup collisional and visibility bounding boxes staticMesh.CollisionBox = new BoundingBox(new Vector3(oldStaticMesh.CollisionX1, -oldStaticMesh.CollisionY1, oldStaticMesh.CollisionZ1), new Vector3(oldStaticMesh.CollisionX2, -oldStaticMesh.CollisionY2, oldStaticMesh.CollisionZ2)); staticMesh.VisibilityBox = new BoundingBox(new Vector3(oldStaticMesh.VisibilityX1, -oldStaticMesh.VisibilityY1, oldStaticMesh.VisibilityZ1), new Vector3(oldStaticMesh.VisibilityX2, -oldStaticMesh.VisibilityY2, oldStaticMesh.VisibilityZ2)); staticMesh.Mesh = ConvertTr4MeshToWadMesh(wad, oldWad, textures, oldWad.Meshes[(int)oldWad.RealPointers[oldStaticMesh.PointersIndex]], (int)oldStaticMesh.ObjectId); wad.Statics.Add(staticMesh.Id, staticMesh); return(staticMesh); }
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 string ToString(TRVersion.Game gameVersion) { return("(" + TypeId + ") " + TrCatalog.GetSpriteSequenceName(gameVersion, TypeId)); }
public static void Main(string[] args) { var extensions = new List <string>() { ".xml", ".txt" }; string startFile = null; string refLevel = null; if (args.Length > 0) // Open files on start { bool loadAsRefLevel = false; foreach (var arg in args) { if (arg.Equals("-r", StringComparison.InvariantCultureIgnoreCase)) { loadAsRefLevel = true; } else { if (!File.Exists(arg)) { continue; // No file and no valid argument, don't even try to load anything } if (loadAsRefLevel) { if (arg.EndsWith("prj2", StringComparison.InvariantCultureIgnoreCase)) { refLevel = arg; } } else { foreach (var ext in extensions) { if (arg.EndsWith(ext, StringComparison.InvariantCultureIgnoreCase)) { startFile = arg; } } } loadAsRefLevel = false; // Reset arg mode if no expected path was found next to it } } } // Load configuration var configuration = new Configuration().LoadOrUseDefault <Configuration>(); // Update DarkUI configuration Colors.Brightness = configuration.UI_FormColor_Brightness / 100.0f; if (configuration.SoundTool_AllowMultipleInstances || mutex.WaitOne(TimeSpan.Zero, true)) { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.AddMessageFilter(new ControlScrollFilter()); if (!File.Exists(Application.StartupPath + "\\Catalogs\\TrCatalog.xml")) { MessageBox.Show("TrCatalog.xml is missing.\nMake sure you have TrCatalog.xml in /Catalogs/ subfolder."); Environment.Exit(1); } TrCatalog.LoadCatalog(Application.StartupPath + "\\Catalogs\\TRCatalog.xml"); using (FormMain form = new FormMain(configuration, startFile, refLevel)) { form.Show(); Application.Run(form); } } else if (startFile != null) { SingleInstanceManagement.Send(Process.GetCurrentProcess(), extensions, startFile); } else { SingleInstanceManagement.Bump(Process.GetCurrentProcess()); } }
public static List <IWadObjectId> CopyObject(WadToolClass tool, IWin32Window owner, List <IWadObjectId> objectIdsToMove, bool alwaysChooseId) { Wad2 sourceWad = tool.SourceWad; Wad2 destinationWad = tool.DestinationWad; if (destinationWad == null || sourceWad == null || objectIdsToMove.Count == 0) { tool.SendMessage("You must have two wads loaded and at least one source object selected.", PopupType.Error); return(null); } var listInProgress = new List <uint>(); // Figure out the new ids if there are any id collisions IWadObjectId[] newIds = objectIdsToMove.ToArray(); // If destination is TR5Main, try to remap object IDs if (destinationWad.GameVersion == TRVersion.Game.TR5Main) { for (int i = 0; i < objectIdsToMove.Count; ++i) { var objectId = objectIdsToMove[i]; if (objectId is WadMoveableId) { var moveableId = (WadMoveableId)objectId; // Try to get a compatible slot var newSlot = TrCatalog.GetMoveableTR5MainSlot(sourceWad.GameVersion, moveableId.TypeId); if (newSlot == "") { continue; } // Get the new ID bool isMoveable; var newId = TrCatalog.GetItemIndex(destinationWad.GameVersion, newSlot, out isMoveable); if (!newId.HasValue) { continue; } // Save the new ID newIds[i] = new WadMoveableId(newId.Value); } } } for (int i = 0; i < objectIdsToMove.Count; ++i) { if (!sourceWad.Contains(objectIdsToMove[i])) { continue; } if (!alwaysChooseId) { if (!destinationWad.Contains(newIds[i])) { if (!newIds.Take(i).Contains(newIds[i])) // There also must not be collisions with the other custom assigned ids. { continue; } } } bool askConfirm = !alwaysChooseId; // Ask for the new slot do { DialogResult dialogResult; if (askConfirm) { dialogResult = DarkMessageBox.Show(owner, "The id " + newIds[i].ToString(destinationWad.GameVersion) + " is already occupied in the destination wad.\n" + "Do you want to replace it (Yes) or to select another Id (No)?", "Occupied slot", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question); } else { dialogResult = DialogResult.No; } // From this time, always ask for confirm askConfirm = true; if (dialogResult == DialogResult.Cancel) { return(null); } else if (dialogResult == DialogResult.No) { using (var form = new FormSelectSlot(destinationWad, newIds[i], listInProgress)) { if (form.ShowDialog(owner) != DialogResult.OK) { return(null); } if (destinationWad.Contains(form.NewId) || newIds.Take(i).Contains(form.NewId)) { destinationWad.Remove(form.NewId); tool.WadChanged(WadArea.Destination); } newIds[i] = form.NewId; if (form.NewId is WadStaticId) { listInProgress.Add(((WadStaticId)form.NewId).TypeId); } if (form.NewId is WadMoveableId) { listInProgress.Add(((WadMoveableId)form.NewId).TypeId); } if (form.NewId is WadSpriteSequenceId) { listInProgress.Add(((WadSpriteSequenceId)form.NewId).TypeId); } break; } } else { destinationWad.Remove(objectIdsToMove[i]); tool.WadChanged(WadArea.Destination); break; } } while (destinationWad.Contains(newIds[i]) || newIds.Take(i).Contains(newIds[i])); // There also must not be collisions with the other custom assigned ids. } // HACK: Until this is fixed... https://github.com/MontyTRC89/Tomb-Editor/issues/413 // We just block copying of same object to another slot and warn user it's in another slot. var failedIdList = new List <IWadObjectId>(); // Move objects for (int i = 0; i < objectIdsToMove.Count; ++i) { IWadObject obj = sourceWad.TryGet(objectIdsToMove[i]); if (obj == null) { continue; } if (destinationWad.Contains(obj)) // Aforementioned HACK continues here - just add object which failed to copy to failed list { failedIdList.Add(newIds[i]); } else { destinationWad.Add(newIds[i], obj); if (destinationWad.GameVersion == TRVersion.Game.TR5Main && obj is WadMoveable) { var moveable = obj as WadMoveable; foreach (var animation in moveable.Animations) { foreach (var command in animation.AnimCommands) { if (command.Type == WadAnimCommandType.PlaySound) { int id = command.Parameter2 & 0x3FFF; id += TrCatalog.GetTR5MainSoundMapStart(sourceWad.GameVersion); command.Parameter2 = (short)((command.Parameter2 & 0xC000) | (id & 0x3FFF)); } } } } } } // Aforementioned HACK continues here - count amount of actually copied objects int actualAmountOfCopiedObjects = objectIdsToMove.Count - failedIdList.Count; if (actualAmountOfCopiedObjects > 0) { // Update the situation tool.WadChanged(WadArea.Destination); string infoString = (objectIdsToMove.Count == 1 ? "Object" : "Objects") + " successfully copied."; // Aforementioned HACK continues here - additionally inform user that some objects are in different slots. if (failedIdList.Count > 0) { infoString += "\n" + failedIdList.Count + " object" + (failedIdList.Count > 1 ? "s" : "") + " weren't copied because " + (failedIdList.Count > 1 ? "they are" : "it's") + " already in different slot" + (failedIdList.Count > 1 ? "s." : "."); } // Indicate that object is copied tool.SendMessage(infoString, PopupType.Info); } return(newIds.Where(item => !failedIdList.Any(failed => failed == item)).ToList()); }
public string ShortName(TRVersion.Game gameVersion) => TrCatalog.GetStaticName(gameVersion, TypeId);
public string ShortName(TRVersion.Game gameVersion) => TrCatalog.GetMoveableName(gameVersion, TypeId);
private void PrepareItems() { bool isNewTR = _level.Settings.GameVersion > TRVersion.Game.TR3; ReportProgress(42, "Building items table"); _moveablesTable = new Dictionary <MoveableInstance, int>(new ReferenceEqualityComparer <MoveableInstance>()); _aiObjectsTable = new Dictionary <MoveableInstance, int>(new ReferenceEqualityComparer <MoveableInstance>()); _luaIdToItems = new Dictionary <int, int>(); foreach (Room room in _level.Rooms.Where(room => room != null)) { foreach (var instance in room.Objects.OfType <MoveableInstance>()) { WadMoveable wadMoveable = _level.Settings.WadTryGetMoveable(instance.WadObjectId); if (wadMoveable == null) { _progressReporter.ReportWarn("Moveable '" + instance + "' was not included in the level because it is missing the *.wad file."); continue; } Vector3 position = instance.Room.WorldPos + instance.Position; double angle = Math.Round(instance.RotationY * (65536.0 / 360.0)); ushort angleInt = unchecked ((ushort)Math.Max(0, Math.Min(ushort.MaxValue, angle))); // Split AI objects and normal objects (only for TR4+) if (isNewTR && TrCatalog.IsMoveableAI(_level.Settings.GameVersion, wadMoveable.Id.TypeId)) { _aiItems.Add(new tr_ai_item { X = (int)Math.Round(position.X), Y = (int)-Math.Round(position.Y), Z = (int)Math.Round(position.Z), ObjectID = checked ((ushort)instance.WadObjectId.TypeId), Room = (ushort)_roomsRemappingDictionary[instance.Room], Angle = angleInt, OCB = instance.Ocb, Flags = (ushort)(instance.CodeBits << 1) }); _aiObjectsTable.Add(instance, _aiObjectsTable.Count); } else { int flags = (instance.CodeBits << 9) | (instance.ClearBody ? 0x80 : 0) | (instance.Invisible ? 0x100 : 0); ushort color = instance.Color.Equals(Vector3.One) ? (ushort)0xFFFF : PackColorTo16Bit(instance.Color); // Substitute ID is needed to convert visible menu items to pick-up sprites in TR1-2 var realID = TrCatalog.GetSubstituteID(_level.Settings.GameVersion, instance.WadObjectId.TypeId); _items.Add(new tr_item { X = (int)Math.Round(position.X), Y = (int)-Math.Round(position.Y), Z = (int)Math.Round(position.Z), ObjectID = checked ((ushort)realID), Room = (short)_roomsRemappingDictionary[instance.Room], Angle = angleInt, Intensity1 = color, Ocb = isNewTR ? instance.Ocb : unchecked ((short)color), Flags = unchecked ((ushort)flags) }); _moveablesTable.Add(instance, _moveablesTable.Count); if (_level.Settings.GameVersion == TRVersion.Game.TR5Main) { if (!_luaIdToItems.ContainsKey(instance.LuaId)) { _luaIdToItems.Add(instance.LuaId, _items.Count - 1); } } } } } // Sort AI objects and put all LARA_START_POS objects (last AI object by ID) in front if (_level.Settings.GameVersion > TRVersion.Game.TR3) { _aiItems = _aiItems.OrderByDescending(item => item.ObjectID).ThenBy(item => item.OCB).ToList(); ReportProgress(45, " Number of AI objects: " + _aiItems.Count); } ReportProgress(45, " Number of items: " + _items.Count); int maxSafeItemCount, maxItemCount; switch (_level.Settings.GameVersion) { case TRVersion.Game.TRNG: maxSafeItemCount = 255; maxItemCount = 1023; break; case TRVersion.Game.TR5Main: maxSafeItemCount = 1023; maxItemCount = 32767; break; default: maxSafeItemCount = 255; maxItemCount = 255; break; } if (_items.Count > maxItemCount) { var warnString = "Level has more than " + maxItemCount + " moveables. This will lead to crash" + (_level.Settings.GameVersion == TRVersion.Game.TR4 ? ", unless you're using TREP." : "."); _progressReporter.ReportWarn(warnString); } if (_items.Count > maxSafeItemCount) { _progressReporter.ReportWarn("Moveable count is beyond " + maxSafeItemCount + ", which may lead to savegame handling issues."); } }
public string ToString(TRVersion.Game gameVersion) { return("(" + TypeId + ") " + TrCatalog.GetStaticName(gameVersion, TypeId)); }
public static void Main(string[] args) { // Load configuration var initialEvents = new List <LogEventInfo>(); var configuration = new Configuration().LoadOrUseDefault <Configuration>(initialEvents); // Update DarkUI configuration Colors.Brightness = configuration.UI_FormColor_Brightness / 100.0f; // Setup logging using (var log = new Logging(configuration.Log_MinLevel, configuration.Log_WriteToFile, configuration.Log_ArchiveN, initialEvents)) { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException); Application.ThreadException += (sender, e) => { log.HandleException(e.Exception); using (var dialog = new ThreadExceptionDialog(e.Exception)) if (dialog.ShowDialog() == DialogResult.Abort) { Environment.Exit(1); } }; // Show startup help if (configuration.StartUpHelp_Show) { var help = new FormStartupHelp(); Application.Run(help); switch (help.DialogResult) { case DialogResult.Cancel: return; case DialogResult.OK: configuration.StartUpHelp_Show = false; break; } } configuration.SaveTry(); // Run if (!File.Exists(Application.StartupPath + "\\Catalogs\\TrCatalog.xml")) { MessageBox.Show("TrCatalog.xml is missing.\nMake sure you have TrCatalog.xml in /Catalogs/ subfolder."); Environment.Exit(1); } TrCatalog.LoadCatalog(Application.StartupPath + "\\Catalogs\\TRCatalog.xml"); Application.AddMessageFilter(new ControlScrollFilter()); SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext()); using (WadToolClass tool = new WadToolClass(configuration)) { string startWad = null; string refLevel = null; if (args.Length > 0) { bool loadAsRefLevel = false; foreach (var arg in args) { if (arg.Equals("-r", StringComparison.InvariantCultureIgnoreCase)) { loadAsRefLevel = true; } else { if (!File.Exists(arg)) { continue; // No file and no valid argument, don't even try to load anything } if (loadAsRefLevel) { if (arg.EndsWith("prj2", StringComparison.InvariantCultureIgnoreCase)) { refLevel = arg; } } else { startWad = arg; } loadAsRefLevel = false; // Reset arg mode if no expected path was found next to it } } } using (FormMain form = new FormMain(tool)) { form.Show(); if (!string.IsNullOrEmpty(refLevel)) { WadActions.LoadReferenceLevel(tool, form, refLevel); } if (!string.IsNullOrEmpty(startWad)) { WadActions.LoadWad(tool, form, true, startWad); } Application.Run(form); } } } }
private void debugAction5ToolStripMenuItem_Click(object sender, EventArgs e) { TrCatalog.LoadCatalog("Editor\\TRCatalog.xml"); }
private static WadSounds ReadFromSfx(string filename) { var samples = new List <string>(); // Read version int version = 129; using (var readerVersion = new BinaryReaderEx(new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read))) version = readerVersion.ReadInt32(); // Read samples var samPath = Path.GetDirectoryName(filename) + "\\" + Path.GetFileNameWithoutExtension(filename) + ".sam"; using (var readerSounds = new StreamReader(new FileStream(samPath, FileMode.Open, FileAccess.Read, FileShare.Read))) while (!readerSounds.EndOfStream) { samples.Add(readerSounds.ReadLine()); } // Read sounds int soundMapSize = 0; short[] soundMap; var wadSoundInfos = new List <wad_sound_info>(); var soundInfos = new List <WadSoundInfo>(); var sfxPath = Path.GetDirectoryName(filename) + "\\" + Path.GetFileNameWithoutExtension(filename) + ".sfx"; using (var readerSfx = new BinaryReaderEx(new FileStream(sfxPath, FileMode.Open, FileAccess.Read, FileShare.Read))) { // Try to guess the WAD version readerSfx.BaseStream.Seek(740, SeekOrigin.Begin); short first = readerSfx.ReadInt16(); short second = readerSfx.ReadInt16(); version = ((first == -1 || second == (first + 1)) ? 130 : 129); readerSfx.BaseStream.Seek(0, SeekOrigin.Begin); soundMapSize = (version == 130 ? 2048 : 370); soundMap = new short[soundMapSize]; for (var i = 0; i < soundMapSize; i++) { soundMap[i] = readerSfx.ReadInt16(); } var numSounds = readerSfx.ReadUInt32(); for (var i = 0; i < numSounds; i++) { var info = new wad_sound_info(); info.Sample = readerSfx.ReadUInt16(); info.Volume = readerSfx.ReadByte(); info.Range = readerSfx.ReadByte(); info.Chance = readerSfx.ReadByte(); info.Pitch = readerSfx.ReadByte(); info.Characteristics = readerSfx.ReadUInt16(); wadSoundInfos.Add(info); } } // Convert old data to new format for (int i = 0; i < soundMapSize; i++) { // Check if sound is defined at all if (soundMap[i] == -1 || soundMap[i] >= wadSoundInfos.Count) { continue; } // Fill the new sound info var oldInfo = wadSoundInfos[soundMap[i]]; var newInfo = new WadSoundInfo(i); newInfo.Name = TrCatalog.GetOriginalSoundName(TRVersion.Game.TR4, (uint)i); newInfo.Volume = (int)Math.Round(oldInfo.Volume * 100.0f / 255.0f); newInfo.RangeInSectors = oldInfo.Range; newInfo.Chance = (int)Math.Round(oldInfo.Chance * 100.0f / 255.0f); if (newInfo.Chance == 0) { newInfo.Chance = 100; // Convert legacy chance value } newInfo.PitchFactor = (int)Math.Round((oldInfo.Pitch > 127 ? oldInfo.Pitch - 256 : oldInfo.Pitch) * 100.0f / 128.0f); newInfo.RandomizePitch = ((oldInfo.Characteristics & 0x2000) != 0); newInfo.RandomizeVolume = ((oldInfo.Characteristics & 0x4000) != 0); newInfo.DisablePanning = ((oldInfo.Characteristics & 0x1000) != 0); newInfo.LoopBehaviour = (WadSoundLoopBehaviour)(oldInfo.Characteristics & 0x03); newInfo.SoundCatalog = sfxPath; // Read all samples linked to this sound info (for example footstep has 4 samples) int numSamplesInGroup = (oldInfo.Characteristics & 0x00fc) >> 2; for (int j = oldInfo.Sample; j < oldInfo.Sample + numSamplesInGroup; j++) { newInfo.Samples.Add(new WadSample(samples[j])); } soundInfos.Add(newInfo); } var sounds = new WadSounds(soundInfos); return(sounds); }
private void DgvSoundInfos_CellValidated(object sender, DataGridViewCellEventArgs e) { if (e.ColumnIndex != 1) { return; } DataGridViewRow row = dgvSoundInfos.Rows[e.RowIndex]; int id; if (!int.TryParse(row.Cells[1].Value.ToString(), out id)) { row.DefaultCellStyle.BackColor = dgvSoundInfos.BackColor; row.Cells[1].Value = ""; row.Cells[2].Value = ""; } else { // Search if this Id was already assigned foreach (DataGridViewRow row2 in dgvSoundInfos.Rows) { // Ignore the same row if (row2.Index == row.Index) { continue; } // Ignore empty values int id2; if (!int.TryParse(row2.Cells[1].Value.ToString(), out id2)) { continue; } // If is the same then warn the user if (id == id2) { row.DefaultCellStyle.BackColor = dgvSoundInfos.BackColor; row.Cells[1].Value = ""; row.Cells[2].Value = ""; DarkMessageBox.Show(this, "The selected Id " + id + " was already assigned to sound '" + row2.Cells[0].Value + "'", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } } // If additional catalog is loaded, this has the priority string name = ""; if (Sounds != null) { var info = Sounds.TryGetSoundInfo(id); if (info != null) { name = info.Name; } else { name = TrCatalog.GetOriginalSoundName(_version, (uint)id); } } else { name = TrCatalog.GetOriginalSoundName(_version, (uint)id); } if (name == null || name == "") { row.DefaultCellStyle.BackColor = dgvSoundInfos.BackColor; row.Cells[1].Value = ""; row.Cells[2].Value = ""; } else { row.DefaultCellStyle.BackColor = Color.DarkGreen; row.Cells[2].Value = name; } } dgvSoundInfos.InvalidateRow(e.RowIndex); UpdateStatus(); }
public static bool ConvertPrj2ToNewSoundFormat(Level level, string src, string dest, string soundsCatalog, bool save) { /* PROCEDURE: * 1. Collect all sound sources of level: if embedded sound info is null, then it's a sound source * that is referencing a Wad file and we should just remap it, otherwise it's a custom sound source * created inside Tomb Editor and we must export it to Xml and we must export samples too * 2. Try to guess the ID for Wad sounds and generate instead a new ID above 602 for custom sounds * (ID = 602 is the start of TR1 area of extended soundmap of TRNG and it should be rarely used) * 3. Show the dialog to the user. Here he can load an additional catalog if he changed sounds via TRLE tools * 4. Assign new IDs and new names to sound infos * 5. Remap sound sources * 6. Export samples if needed * 7. Save Prj2 + Xml if custom sounds are present */ try { // Check for sound system if (level.Settings.SoundSystem == SoundSystem.Xml) { return(true); } // Infer the wad version from level version TRVersion.Game version = level.Settings.GameVersion.Native(); // Collect all sounds to remap var conversionList = new List <SoundInfoConversionRow>(); // We start from sound id = 602 which is the TR1 sound area of TRNG extended soundmap. // This area is reserved for TR1 enemies and so it *** should *** be used rarely int lastSoundId = 602; foreach (var room in level.Rooms) { if (room != null) { foreach (var obj in room.Objects) { if (obj is SoundSourceInstance) { SoundSourceInstance soundSource = obj as SoundSourceInstance; if (soundSource.WadReferencedSoundName != null && soundSource.WadReferencedSoundName != "") { if (!conversionList.Select(f => f.OldName).Contains(soundSource.WadReferencedSoundName)) { // First try to get sound name from TrCatalog int newId = TrCatalog.TryGetSoundInfoIdByDescription(version, soundSource.WadReferencedSoundName); var row = new SoundInfoConversionRow(null, soundSource.WadReferencedSoundName); if (newId == -1) { // If sound was not found in catalog, then assign a generic Id and ask to the user row.NewName = Regex.Replace(soundSource.WadReferencedSoundName, "[^A-Za-z0-9 _]", "").ToUpper(); row.NewId = lastSoundId++; } else { // Otherwise, we are lucky, and we can just assign the correct Id row.NewName = TrCatalog.GetOriginalSoundName(version, (uint)newId); row.NewId = newId; } conversionList.Add(row); } } else if (soundSource.EmbeddedSoundInfo != null) { bool found = false; foreach (var r in conversionList) { if (r.SoundInfo != null && r.SoundInfo == soundSource.EmbeddedSoundInfo) { found = true; break; } } if (found) { continue; } // Let's first try a search in TrCatalog, maybe we are lucky // First try to get sound name from TrCatalog /*int newId = TrCatalog.TryGetSoundInfoIdByDescription(version, soundSource.EmbeddedSoundInfo.Name); * * var row = new SoundInfoConversionRow(soundSource.EmbeddedSoundInfo, soundSource.EmbeddedSoundInfo.Name); * if (newId == -1) * { * // If sound was not found in catalog, then assign a generic Id and ask to the user * row.NewName = Regex.Replace(soundSource.EmbeddedSoundInfo.Name, "[^A-Za-z0-9 _]", "").ToUpper(); * row.NewId = lastSoundId++; * } * else * { * // Otherwise, we are lucky, and we can just assign the correct Id * row.NewName = TrCatalog.GetOriginalSoundName(version, (uint)newId); * row.NewId = newId; * }*/ // TODO: Lwmte proposed to also there check in TrCatalog, but we should assume that // embedded sound sources are custom sound sources, created by thhe user with custom samples // and we should think carrefully about this var row = new SoundInfoConversionRow(soundSource.EmbeddedSoundInfo, soundSource.EmbeddedSoundInfo.Name); row.NewName = Regex.Replace(soundSource.EmbeddedSoundInfo.Name, "[^A-Za-z0-9 _]", "").ToUpper(); row.NewId = lastSoundId++; // These flags are handle by Tomb Editor and set only for embedded sound sources row.SaveToXml = true; row.ExportSamples = true; conversionList.Add(row); } } } } } WadSounds sounds = null; // Now we'll show a dialog with all conversion rows and the user will need to make some choices if (conversionList.Count != 0) { using (var form = new Prj2SoundsConversionDialog(version, conversionList)) { if (form.ShowDialog() == DialogResult.Cancel) { return(false); } // If the user has loaded an additional catalog, let's get a pointer to it if (form.Sounds != null) { sounds = form.Sounds; } } } // Assign new Id and name foreach (var row in conversionList) { if (row.SoundInfo != null) { row.SoundInfo.Id = row.NewId; row.SoundInfo.Name = row.NewName; } } // We'll export only embedded sound sources var newSounds = new WadSounds(); // Remap sound sources foreach (var room in level.Rooms) { if (room != null) { foreach (var obj in room.Objects) { if (obj is SoundSourceInstance) { SoundSourceInstance soundSource = obj as SoundSourceInstance; if (soundSource.WadReferencedSoundName != null && soundSource.WadReferencedSoundName != "") { soundSource.SoundId = -1; foreach (var row in conversionList) { if (row.OldName == soundSource.WadReferencedSoundName && row.NewId != -1) { soundSource.SoundId = row.NewId; break; } } soundSource.WadReferencedSoundName = ""; soundSource.EmbeddedSoundInfo = null; } else if (soundSource.EmbeddedSoundInfo != null) { // We export embedded sound infos if (!newSounds.SoundInfos.Contains(soundSource.EmbeddedSoundInfo)) { newSounds.SoundInfos.Add(soundSource.EmbeddedSoundInfo); } soundSource.SoundId = -1; foreach (var row in conversionList) { if (row.SoundInfo == soundSource.EmbeddedSoundInfo && row.NewId != -1) { soundSource.SoundId = row.NewId; // Try to bind samples from additional catalog, if loaded /*if (sounds != null) * { * WadSoundInfo catalogInfo = sounds.TryGetSoundInfo(row.NewId); * if (catalogInfo != null && catalogInfo.Samples.Count > 0) * { * soundSource.EmbeddedSoundInfo.Samples.Clear(); * soundSource.EmbeddedSoundInfo.Samples.AddRange(catalogInfo.Samples); * // TODO: in theory if valid samples are found in catalog, we shouldn't need to * // export them * row.ExportSamples = false; * } * }*/ break; } } soundSource.WadReferencedSoundName = ""; soundSource.EmbeddedSoundInfo = null; } } } } } // Export samples foreach (var row in conversionList) { if (row.SoundInfo != null && row.ExportSamples) { var samples = new List <string>(); foreach (var sample in row.SoundInfo.Samples) { if (sample.IsLoaded) { string sampleName = Path.GetFileNameWithoutExtension(dest) + "_" + row.NewName.ToLower() + "_" + row.SoundInfo.Samples.IndexOf(sample) + ".wav"; samples.Add(sampleName); File.WriteAllBytes(Path.GetDirectoryName(dest) + "\\" + sampleName, sample.Data); } } row.SoundInfo.Samples.Clear(); foreach (var sample in samples) { row.SoundInfo.Samples.Add(new WadSample(sample)); } } } // Sort sound infos newSounds.SoundInfos.Sort((a, b) => a.Id.CompareTo(b.Id)); // Make a backup copy if (save && src == dest) { int index = 0; string backupFilename = ""; while (true) { backupFilename = dest + "." + index + ".bak"; if (!File.Exists(backupFilename)) { break; } index++; } File.Copy(src, backupFilename, true); } // Save Xml to file if (newSounds.SoundInfos.Count != 0) { string xmlFileName = Path.GetDirectoryName(dest) + "\\" + Path.GetFileNameWithoutExtension(dest) + ".xml"; WadSounds.SaveToXml(xmlFileName, newSounds); // Assign Xml to level settings level.Settings.SoundsCatalogs.Add(new ReferencedSoundsCatalog(level.Settings, level.Settings.MakeRelative(xmlFileName, VariableType.LevelDirectory))); } level.Settings.SoundSystem = SoundSystem.Xml; // Try to get Xml and SFX files foreach (var wadRef in level.Settings.Wads) { if (wadRef != null && wadRef.LoadException == null) { string wadPath = level.Settings.MakeAbsolute(wadRef.Path); string extension = Path.GetExtension(wadPath).ToLower(); if (extension == ".wad") { string sfxPath = Path.GetDirectoryName(wadPath) + "\\" + Path.GetFileNameWithoutExtension(wadPath) + ".sfx"; if (File.Exists(sfxPath)) { sounds = WadSounds.ReadFromFile(sfxPath); if (sounds != null) { level.Settings.SoundsCatalogs.Add(new ReferencedSoundsCatalog(level.Settings, level.Settings.MakeRelative(sfxPath, VariableType.LevelDirectory))); } } } else if (extension == ".wad2") { string xmlPath = Path.GetDirectoryName(wadPath) + "\\" + Path.GetFileNameWithoutExtension(wadPath) + ".xml"; if (File.Exists(xmlPath)) { sounds = WadSounds.ReadFromFile(xmlPath); if (sounds != null) { level.Settings.SoundsCatalogs.Add(new ReferencedSoundsCatalog(level.Settings, level.Settings.MakeRelative(xmlPath, VariableType.LevelDirectory))); } } } } } // Assign sounds if possible foreach (var soundRef in level.Settings.SoundsCatalogs) { if (soundRef.LoadException == null) { foreach (var sound in soundRef.Sounds.SoundInfos) { if (!level.Settings.SelectedSounds.Contains(sound.Id)) { level.Settings.SelectedSounds.Add(sound.Id); } } } } // Save Prj2 with Xml sounds if (save) { using (var stream = File.OpenWrite(dest)) Prj2Writer.SaveToPrj2(stream, level); } return(true); } catch (Exception ex) { return(false); } }