public void SaveProperties(LevelscriptCommand Command) { // Acts var acts = new[] { false, false, false, false, false, false, false, false }; acts[7] = Act1; acts[6] = Act2; acts[5] = Act3; acts[4] = Act4; acts[3] = Act5; acts[2] = Act6; clNormal3DObject.SetActs(Command, Bits.ArrayToByte(acts)); // Position clNormal3DObject.SetPosition(Command, Position); // Rotation clNormal3DObject.SetRotation(Command, Rotation); // Model-ID clNormal3DObject.SetModelID(Command, ModelID); // Behavior-ID clNormal3DObject.SetSegBehaviorAddr(Command, BehaviorID); // B. Params var bParams = new ObjBParams(); bParams.BParam1 = BParam1; bParams.BParam2 = BParam2; bParams.BParam3 = BParam3; bParams.BParam4 = BParam4; clNormal3DObject.SetParams(Command, bParams); }
public void SaveProperties(LevelscriptCommand Command, ScrollTexConfig config) { clScrollingTexture.SetCycleDuration(Command, CycleDuration); clScrollingTexture.SetVertexPointer(Command, Conversions.ToUInteger(VertexPointer)); clScrollingTexture.SetCountOfFaces(Command, FacesCount); clScrollingTexture.SetScrollSpeed(Command, ScrollingSpeed); clScrollingTexture.SetScrollType(Command, (byte)Type); clScrollingTexture.SetScrollBehavior(Command, (byte)Behavior); clScrollingTexture.SetGroupID(Command, GroupID); clNormal3DObject.SetSegBehaviorAddr(Command, config is object && config.UseCustomBehavior && config.CustomBehaviorAddress > -1 ? (uint)config.CustomBehaviorAddress : 0x401700); var acts = new[] { false, false, false, false, false, false, false, false }; acts[7] = Act1; acts[6] = Act2; acts[5] = Act3; acts[4] = Act4; acts[3] = Act5; acts[2] = Act6; clNormal3DObject.SetActs(Command, Bits.ArrayToByte(acts)); }
/// <summary> /// Saves a ROM Manager Level to ROM. /// </summary> /// <param name="lvl"></param> /// <param name="rommgr"></param> /// <param name="output"></param> /// <param name="curOff"></param> /// <returns></returns> public virtual LevelSaveResult SaveLevel(Level lvl, RomManager rommgr, BinaryData output, ref uint curOff) { var saveres = new LevelSaveResult(); BinaryData data0x19; var lid = rommgr.LevelInfoData.GetByLevelID(lvl.LevelID); // Write Area Model & Update Scrolling Texture Vertex Pointers & Write Custom Object Bank var CollisionBoxTableIndex = new[] { 0, 0x32, 0x33 }; foreach (LevelArea a in lvl.Areas) { a.SpecialBoxes.SortByHeight(); a.Bank0x0EOffset = curOff; int oldModelStart = a.AreaModel.Fast3DBuffer.Fast3DBankStart; int newModelStart; int modelOffset; // Add the new water boxes a.AreaModel.Collision.SpecialBoxes.Clear(); foreach (SpecialBox sp in a.SpecialBoxes) { var boxdata = new Model.Collision.BoxData { X1 = sp.X1, X2 = sp.X2, Z1 = sp.Z1, Z2 = sp.Z2, Y = sp.Y, Index = (short)CollisionBoxTableIndex[(int)sp.Type] }; switch (sp.Type) { case SpecialBoxType.Water: boxdata.Type = Model.Collision.BoxDataType.Water; break; case SpecialBoxType.Mist: boxdata.Type = Model.Collision.BoxDataType.Mist; break; case SpecialBoxType.ToxicHaze: boxdata.Type = Model.Collision.BoxDataType.ToxicHaze; break; } a.AreaModel.Collision.SpecialBoxes.Add(boxdata); CollisionBoxTableIndex[(int)sp.Type] += 1; } // Write Area Model ObjectModel.SaveResult res; res = a.AreaModel.ToBinaryData(output, (int)curOff, (int)curOff, 0xE000000); // Calculate Model Offset & Update Scrolling Texture Vertex Pointers newModelStart = a.AreaModel.Fast3DBuffer.Fast3DBankStart; modelOffset = newModelStart - oldModelStart; if (modelOffset != 0) { a.UpdateScrollingTextureVertexPointer(modelOffset); } a.CollisionPointer = res.CollisionPointer; a.Geolayout.Geopointers.Clear(); a.Geolayout.Geopointers.AddRange(res.GeoPointers.ToArray()); curOff += (uint)(res.Length + 0x20); General.HexRoundUp2(ref curOff); a.Bank0xELength = (int)(curOff - a.Bank0x0EOffset); } // Write Background Image output.RoundUpPosition(); int customBGStart = Conversions.ToInteger(curOff); int customBGEnd = 0; if (lvl.Background.IsCustom) // .ID = Geolayout.BackgroundIDs.Custom Then { // Write Custom Background lvl.Background.WriteImage(output.BaseStream, customBGStart); // Write Pointer Table var bgPtrTable = LevelBG.GetBackgroundPointerTable(); output.Write(bgPtrTable, 0, bgPtrTable.Length); customBGEnd = customBGStart + lvl.Background.ImageLength + bgPtrTable.Length; curOff += (uint)lvl.Background.ImageLength + (uint)bgPtrTable.Length; General.HexRoundUp2(ref curOff); } // Generate & Write Local Object Bank uint localObjectBankRomStart = 0; uint localObjectBankRomEnd = 0; bool writeLocalObjectBank = lvl.LocalObjectBank.Models.Count > 0 && lvl.EnableLocalObjectBank; if (writeLocalObjectBank) { localObjectBankRomStart = curOff; curOff += (uint)lvl.LocalObjectBank.WriteToSeg(output, (int)curOff, 0x9); localObjectBankRomEnd = curOff; ((RMLevel)lvl).Config.LocalObjectBank = lvl.LocalObjectBank.Config; lvl.LocalObjectBank.WriteCollisionPointers(output); } ((RMLevel)lvl).Config.EnableLocalObjectBank = writeLocalObjectBank; // Get Bank 0x19 if (lvl.Bank0x19 is null) { lvl.Bank0x19 = rommgr.SetSegBank(0x19, Conversions.ToInteger(curOff), (int)RomManagerSettings.DefaultLevelscriptSize); lvl.Bank0x19.Data = new MemoryStream(); lvl.Bank0x19.Length = (int)RomManagerSettings.DefaultLevelscriptSize; } else { var oldData = lvl.Bank0x19.Data; lvl.Bank0x19 = rommgr.SetSegBank(0x19, Conversions.ToInteger(curOff), (int)(curOff + lvl.Bank0x19.Length)); lvl.Bank0x19.Data = oldData; } data0x19 = new BinaryStreamData(lvl.Bank0x19.Data); saveres.Bank0x19 = lvl.Bank0x19; curOff += (uint)lvl.Bank0x19.Data.Length; General.HexRoundUp2(ref curOff); // Update Geolayouts foreach (LevelArea a in lvl.Areas) { // Update Backcolor Command var cmd = a.Geolayout.Geolayoutscript.GetFirst(GeolayoutCommandTypes.Background); if (a.Background.Type == AreaBGs.Levelbackground && lvl.Background.Enabled) { cgBackground.SetBackgroundPointer(cmd, unchecked ((int)0x802763D4)); cgBackground.SetBackgroundID(cmd, (short)lvl.Background.ID); } else { cgBackground.SetBackgroundPointer(cmd, 0); cgBackground.SetRgbaColor(cmd, a.Background.Color); } } // Write Geolayouts int geoOffset = 0x5F00; foreach (LevelArea a in lvl.Areas) { geoOffset -= General.HexRoundUp1(a.Geolayout.Length) + 0x50; a.GeolayoutOffset = (uint)(lvl.Bank0x19.BankAddress + geoOffset); a.Geolayout.Write(lvl.Bank0x19.Data, geoOffset); a.Geolayout.NewGeoOffset = lvl.Bank0x19.RomStart + geoOffset; } // Füge Show-Dialog-Command & 2D-Camera-Object ein foreach (LevelArea a in lvl.Areas) { // Show-Dialog-Command if (a.ShowMessage.Enabled) { var cmdShowMsg = new LevelscriptCommand($"30 04 00 {a.ShowMessage.DialogID.ToString("X2")}"); int indexOf1E = a.Levelscript.IndexOfFirst(LevelscriptCommandTypes.EndOfArea); a.Levelscript.Insert(indexOf1E, cmdShowMsg); } // 2D-Camera-Object var cmds2d = new List <LevelscriptCommand>(); foreach (LevelscriptCommand obj in a.Objects) { if (obj.CommandType == LevelscriptCommandTypes.Normal3DObject) { if (clNormal3DObject.GetSegBehaviorAddr(obj) == (long)0x130053C4) // Behav-ID: '0x130053C4 { cmds2d.Add(obj); } } } if (a.Enable2DCamera) { if (cmds2d.Count == 0) { var cmd = new LevelscriptCommand("24 18 1F 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 13 00 53 C4"); a.Objects.Add(cmd); } } else if (cmds2d.Count > 0) { foreach (LevelscriptCommand cmd in cmds2d) { a.Objects.Remove(cmd); } } } Levelscript lvlScript0E = null; Dictionary <byte, uint> areaobjwarpoffsetdic = null; SegmentedBank firstBank0xE = null; uint curFirstBank0xEOffset = 0; // Add Objects and Warps to new Levelscript lvlScript0E = new Levelscript(); firstBank0xE = rommgr.SetSegBank(0xE, Conversions.ToInteger(curOff), 0); areaobjwarpoffsetdic = new Dictionary <byte, uint>(); foreach (LevelArea a in lvl.Areas) { areaobjwarpoffsetdic.Add(a.AreaID, (uint)(firstBank0xE.BankAddress + curFirstBank0xEOffset)); foreach (LevelscriptCommand c in a.Objects) { lvlScript0E.Add(c); curFirstBank0xEOffset += (uint)c.Length; } foreach (ManagedScrollingTexture c in a.ScrollingTextures) { c.SaveProperties(rommgr.RomConfig.ScrollTexConfig); lvlScript0E.Add(c.Command); curFirstBank0xEOffset += (uint)c.Command.Length; } foreach (LevelscriptCommand c in a.Warps) { lvlScript0E.Add(c); curFirstBank0xEOffset += (uint)c.Length; } foreach (LevelscriptCommand c in a.WarpsForGame) { lvlScript0E.Add(c); curFirstBank0xEOffset += (uint)c.Length; } lvlScript0E.Add(new LevelscriptCommand("07 04 00 00")); curFirstBank0xEOffset += 4; } firstBank0xE.Length = (int)General.HexRoundUp1(curFirstBank0xEOffset); lvlScript0E.Write(output, firstBank0xE.RomStart); curOff += (uint)firstBank0xE.Length; // Füge Area dem Levelscript hinzu int cIndex2 = lvl.Levelscript.IndexOfFirst(LevelscriptCommandTypes.x1E); foreach (var a in lvl.Areas) { foreach (var c in a.Levelscript) { lvl.Levelscript.Insert(cIndex2, c); cIndex2 += 1; } } // Übernehme Level- und Areaeinstellungen int CurrentAreaIndex = 0; var areaobjwarpindextoinsertdic = new Dictionary <byte, int>(); var areaidindex = new Dictionary <byte, byte>(); LevelArea tArea = null; bool foundCmdShowMsg = false; LevelscriptCommand cmdBgSegLoad = null; LevelscriptCommand cmdGobSegLoad = null; LevelscriptCommand cmdGobJump = null; LevelscriptCommand cmdLobSegLoad = null; LevelscriptCommand cmdLobJump = null; var cmdsToInsertAt = new Dictionary <LevelscriptCommand, LevelscriptCommand>(); var cmdsToRemove = new List <LevelscriptCommand>(); foreach (var c in lvl.Levelscript) { var switchExpr1 = c.CommandType; switch (switchExpr1) { case LevelscriptCommandTypes.StartArea: { tArea = lvl.Areas[CurrentAreaIndex]; byte areaid = tArea.AreaID; areaidindex.Add(areaid, Conversions.ToByte(areaidindex.Count)); clStartArea.SetSegGeolayoutAddr((LevelscriptCommand)c, (uint)(lvl.Areas[CurrentAreaIndex].Geolayout.NewGeoOffset - lvl.Bank0x19.RomStart + lvl.Bank0x19.BankAddress)); areaobjwarpindextoinsertdic.Add(areaid, lvl.Levelscript.IndexOf(c) + 1); break; } case LevelscriptCommandTypes.EndOfArea: { if (!foundCmdShowMsg && tArea.ShowMessage.Enabled) { var cmdShowMsg = new LevelscriptCommand($"30 04 00 {tArea.ShowMessage.DialogID.ToString("X2")}"); cmdsToInsertAt.Add((LevelscriptCommand)c, cmdShowMsg); } foundCmdShowMsg = false; CurrentAreaIndex += 1; tArea = null; break; } case LevelscriptCommandTypes.AreaMusic: { clAreaMusic.SetMusicID((LevelscriptCommand)c, lvl.Areas[CurrentAreaIndex].BGMusic); break; } case LevelscriptCommandTypes.AreaMusicSimple: { clAreaMusicSimple.SetMusicID((LevelscriptCommand)c, lvl.Areas[CurrentAreaIndex].BGMusic); break; } case LevelscriptCommandTypes.Tarrain: { clTerrian.SetTerrainType((LevelscriptCommand)c, (byte)lvl.Areas[CurrentAreaIndex].TerrainType); break; } case LevelscriptCommandTypes.LoadRomToRam: { var switchExpr2 = clLoadRomToRam.GetSegmentedID((LevelscriptCommand)c); switch (switchExpr2) { case 0xE: // Bank 0xE clLoadRomToRam.SetRomStart((LevelscriptCommand)c, firstBank0xE.RomStart); clLoadRomToRam.SetRomEnd((LevelscriptCommand)c, firstBank0xE.RomEnd); break; case 0xA: cmdBgSegLoad = (LevelscriptCommand)c; break; case 0x7: if (lvl.LastGobCmdSegLoad == c) { cmdGobSegLoad = (LevelscriptCommand)c; } break; case 0x9: if (lvl.LastLobCmdSegLoad == c) { cmdLobSegLoad = (LevelscriptCommand)c; } break; } break; } case LevelscriptCommandTypes.ShowDialog: { if ((bool)tArea?.ShowMessage.Enabled && !foundCmdShowMsg) { clShowDialog.SetDialogID((LevelscriptCommand)c, tArea.ShowMessage.DialogID); foundCmdShowMsg = true; } else { cmdsToRemove.Add((LevelscriptCommand)c); } break; } case LevelscriptCommandTypes.JumpToSegAddr: { int bankID = clJumpToSegAddr.GetSegJumpAddr((LevelscriptCommand)c) >> 24; switch (bankID) { case 0x7: cmdGobJump = (LevelscriptCommand)c; break; case 0x9: cmdLobJump = (LevelscriptCommand)c; break; } break; } } } // Füge Jump Commands zur ersten 0xE-Bank hinzu foreach (var e in areaobjwarpindextoinsertdic.OrderByDescending(n => n.Value)) { uint segStartAddr = areaobjwarpoffsetdic[e.Key]; lvl.Levelscript.Insert(e.Value, new LevelscriptCommand(new byte[] { 0x6, 8, 0, 0, Conversions.ToByte((long)(segStartAddr >> 24) & (long)0xFF), Conversions.ToByte((long)(segStartAddr >> 16) & (long)0xFF), Conversions.ToByte((long)(segStartAddr >> 8) & (long)0xFF), Conversions.ToByte((long)segStartAddr & (long)0xFF) })); } // Lösche Commands foreach (LevelscriptCommand cmd in cmdsToRemove) { lvl.Levelscript.Remove(cmd); } // Füge neue Commands ein foreach (KeyValuePair <LevelscriptCommand, LevelscriptCommand> kvp in cmdsToInsertAt) { int index = lvl.Levelscript.IndexOf(kvp.Key); lvl.Levelscript.Insert(index, kvp.Value); } // Füge Background-Command ein if (lvl.Background.Enabled) { var newbgcmd = cmdBgSegLoad ?? new LevelscriptCommand(new byte[] { 0x17, 0xC, 0, 0xA, 0, 0, 0, 0, 0, 0, 0, 0 }); if (lvl.Background.IsCustom && lvl.Background.HasImage) // .ID = Geolayout.BackgroundIDs.Custom Then { clLoadRomToRam.SetRomStart(newbgcmd, customBGStart); clLoadRomToRam.SetRomEnd(newbgcmd, customBGEnd); } else { clLoadRomToRam.SetRomStart(newbgcmd, (int)General.GetBackgroundAddressOfID(lvl.Background.ID, false)); clLoadRomToRam.SetRomEnd(newbgcmd, (int)General.GetBackgroundAddressOfID(lvl.Background.ID, true)); } if (!lvl.Levelscript.Contains(newbgcmd)) { int indexoffirstx1e = lvl.Levelscript.IndexOfFirst(LevelscriptCommandTypes.x1D); lvl.Levelscript.Insert(indexoffirstx1e, newbgcmd); } } else if (cmdBgSegLoad is object) { lvl.Levelscript.Remove(cmdBgSegLoad); } // Füge Global Object Bank Command ein if (lvl.EnableGlobalObjectBank) { var newgobjumpcmd = cmdGobJump ?? new LevelscriptCommand("06 08 00 00 07 00 00 00"); var newgobcmd = cmdGobSegLoad ?? new LevelscriptCommand("17 0C 00 07 00 00 00 00 00 00 00 00"); clLoadRomToRam.SetRomStart(newgobcmd, rommgr.GlobalModelBank.CurSeg.RomStart); clLoadRomToRam.SetRomEnd(newgobcmd, rommgr.GlobalModelBank.CurSeg.RomEnd); if (!lvl.Levelscript.Contains(newgobcmd)) { int indexoffirstx1d = lvl.Levelscript.IndexOfFirst(LevelscriptCommandTypes.x1D); lvl.Levelscript.Insert(indexoffirstx1d, newgobcmd); lvl.LastGobCmdSegLoad = newgobcmd; } if (!lvl.Levelscript.Contains(newgobjumpcmd)) { int indexoffirstx1d = lvl.Levelscript.IndexOfFirst(LevelscriptCommandTypes.x1D); lvl.Levelscript.Insert(indexoffirstx1d + 1, newgobjumpcmd); } } else { if (cmdGobJump is object) { lvl.Levelscript.Remove(cmdGobJump); } if (cmdGobSegLoad is object) { lvl.Levelscript.Remove(cmdGobSegLoad); } } // Füge Local Object Bank Command ein if (writeLocalObjectBank) { var newlobjumpcmd = cmdLobJump ?? new LevelscriptCommand("06 08 00 00 09 00 00 00"); var newlobcmd = cmdLobSegLoad ?? new LevelscriptCommand("17 0C 00 09 00 00 00 00 00 00 00 00"); clLoadRomToRam.SetRomStart(newlobcmd, (int)localObjectBankRomStart); clLoadRomToRam.SetRomEnd(newlobcmd, (int)localObjectBankRomEnd); if (!lvl.Levelscript.Contains(newlobcmd)) { int indexoffirstx1d = lvl.Levelscript.IndexOfFirst(LevelscriptCommandTypes.x1D); lvl.Levelscript.Insert(indexoffirstx1d, newlobcmd); lvl.LastLobCmdSegLoad = newlobcmd; } if (!lvl.Levelscript.Contains(newlobjumpcmd)) { int indexoffirstx1d = lvl.Levelscript.IndexOfFirst(LevelscriptCommandTypes.x1D); lvl.Levelscript.Insert(indexoffirstx1d + 1, newlobjumpcmd); } } else { if (cmdLobJump is object) { lvl.Levelscript.Remove(cmdLobJump); } if (cmdLobSegLoad is object) { lvl.Levelscript.Remove(cmdLobSegLoad); } } // Write Level Start (Start of Bank 0x19) lvl.Bank0x19.Data.Position = 0; foreach (byte b in Level.LevelscriptStart) { data0x19.Write(b); } // Write Levelscript lvl.Levelscript.Write(lvl.Bank0x19.Data, Conversions.ToInteger(data0x19.Position)); // Parse Levelscript again! bool AreaOnFly = false; foreach (var c in lvl.Levelscript.ToArray()) { var switchExpr3 = c.CommandType; switch (switchExpr3) { case LevelscriptCommandTypes.StartArea: { AreaOnFly = true; break; } case LevelscriptCommandTypes.EndOfArea: { AreaOnFly = false; lvl.Levelscript.Remove(c); break; } } if (AreaOnFly) { lvl.Levelscript.Remove(c); } } var bwToUse = data0x19 ?? output; // Write 4 checkbytes for the One-0xE-Bank-Per-Area-Code lvl.Bank0x19.Data.Position = 0x5FFC; bwToUse.Write(Conversions.ToInteger(0x4BC9189A)); // Write Area Table foreach (LevelArea a in lvl.Areas) { uint off = (uint)(0x5F00 + a.AreaID * 0x10); lvl.Bank0x19.Data.Position = off; bwToUse.Write(Conversions.ToUInteger(a.Bank0x0EOffset)); bwToUse.Write(Conversions.ToUInteger(a.Bank0x0EOffset + a.Bank0xELength)); bwToUse.Write(Conversions.ToUInteger(0)); bwToUse.Write(Conversions.ToByte(0x0)); bwToUse.Write(Conversions.ToByte(0x0)); bwToUse.Write(Conversions.ToByte(0x0)); bwToUse.Write(Bits.ArrayToByte(new[] { false, false, false, false, false, false, false, a.Enable2DCamera })); } // Write SpecialBoxes int CurrentBoxOffset = 0x6A00; foreach (LevelArea a in lvl.Areas) { var TableIndex = new[] { 0, 0x32, 0x33 }; var TableOffset = new[] { 0x6000 + 0x50 * a.AreaID, 0x6280 + 0x50 * a.AreaID, 0x6500 + 0x50 * a.AreaID }; foreach (SpecialBoxType t in Enum.GetValues(typeof(SpecialBoxType))) { foreach (SpecialBox w in a.SpecialBoxes.Where(n => n.Type == t)) { // Write Table Entry bwToUse.Position = TableOffset[(int)w.Type]; bwToUse.Write(Conversions.ToShort(TableIndex[(int)w.Type])); bwToUse.Write(Conversions.ToShort(0x0)); bwToUse.Write(Conversions.ToInteger(CurrentBoxOffset + lvl.Bank0x19.BankAddress)); TableOffset[(int)w.Type] = Conversions.ToInteger(bwToUse.Position); // Write Box Data bwToUse.Position = CurrentBoxOffset; foreach (byte b in w.ToArrayBoxData()) { bwToUse.Write(b); } TableIndex[(int)w.Type] += 1; CurrentBoxOffset += 0x20; } } foreach (int i in TableOffset) { bwToUse.Position = i; bwToUse.Write(Conversions.ToUShort(0xFFFF)); } } // Write Bank0x19 lvl.Bank0x19.WriteData(output); // Hardcoded Camera Settings & Act Selector General.PatchClass.Open(output); General.PatchClass.set_HardcodedCamera_Enabled(lvl.LevelID, lvl.HardcodedCameraSettings); General.PatchClass.set_ActSelector_Enabled(lvl.LevelID, lvl.ActSelector); // Write Pointer to Levelscript output.Position = lid.Pointer; output.Write(Conversions.ToInteger(0x100019)); output.Write(Conversions.ToUInteger(lvl.Bank0x19.RomStart)); output.Write(Conversions.ToUInteger(lvl.Bank0x19.RomEnd)); output.Write(Conversions.ToUInteger(0x1900001C)); output.Write(Conversions.ToUInteger(0x7040000)); // Write Area Reverb if (EnableLoadingAreaReverb) { foreach (var area in lvl.Areas) { if (area is RMLevelArea && area.AreaID >= 1 && area.AreaID <= 3) { output.Position = 0xEE0C0 + lvl.LevelID * 3 + area.AreaID - 1; output.Write((byte)((RMLevelArea)area).ReverbLevel); } } } return(saveres); }