freeso/TSOClient/tso.content/WorldObjectProvider.cs
riperiperi b57ebbd141 Cleanup: Remove most unused usings and MPL per-file licenses
MPL per-file was used in older project files, but should apply to the whole project, so having it there was redundant.
2023-11-25 03:45:29 +00:00

449 lines
14 KiB
C#

using System;
using System.Collections.Generic;
using FSO.Content.Framework;
using FSO.Common.Content;
using System.Xml;
using FSO.Content.Codecs;
using System.Text.RegularExpressions;
using FSO.Files.Formats.IFF;
using FSO.Files.Formats.IFF.Chunks;
using FSO.Files.Formats.OTF;
using System.IO;
using FSO.Common;
using FSO.Common.Utils;
using FSO.Content.Interfaces;
namespace FSO.Content
{
/// <summary>
/// Provides access to binding (*.iff, *.spf, *.otf) data in FAR3 archives.
/// </summary>
public class WorldObjectProvider : AbstractObjectProvider
{
private FAR1Provider<IffFile> Iffs;
private FAR1Provider<IffFile> Sprites;
private FAR1Provider<OTFFile> TuningTables;
public WorldObjectProvider(Content contentManager) : base(contentManager)
{
}
private bool WithSprites;
/// <summary>
/// Initiates loading of world objects.
/// </summary>
public void Init(bool withSprites)
{
WithSprites = withSprites;
Iffs = new FAR1Provider<IffFile>(ContentManager, new IffCodec(), "objectdata/objects/objiff.far");
TuningTables = new FAR1Provider<OTFFile>(ContentManager, new OTFCodec(), new Regex(".*/objotf.*\\.far"));
Iffs.Init();
TuningTables.Init();
if (withSprites)
{
Sprites = new FAR1Provider<IffFile>(ContentManager, new IffCodec(), new Regex(".*/objspf.*\\.far"));
Sprites.Init();
}
/** Load packingslip **/
Entries = new Dictionary<ulong, GameObjectReference>();
Cache = new TimedReferenceCache<ulong, GameObject>();
var packingslip = new XmlDocument();
packingslip.Load(ContentManager.GetPath("packingslips/objecttable.xml"));
var objectInfos = packingslip.GetElementsByTagName("I");
foreach (XmlNode objectInfo in objectInfos)
{
ulong FileID = Convert.ToUInt32(objectInfo.Attributes["g"].Value, 16);
Entries.Add(FileID, new GameObjectReference(this)
{
ID = FileID,
FileName = objectInfo.Attributes["n"].Value,
Source = GameObjectSource.Far,
Name = objectInfo.Attributes["o"].Value,
Group = Convert.ToInt16(objectInfo.Attributes["m"].Value),
SubIndex = Convert.ToInt16(objectInfo.Attributes["i"].Value)
});
}
//init local objects, piff clones
//Directory.CreateDirectory(Path.Combine(FSOEnvironment.ContentDir, "Objects"));
string[] paths = Directory.GetFiles(Path.Combine(FSOEnvironment.ContentDir, "Objects"), "*.iff", SearchOption.AllDirectories);
for (int i = 0; i < paths.Length; i++)
{
string entry = paths[i];
string filename = Path.GetFileName(entry);
IffFile iffFile = new IffFile(entry);
var objs = iffFile.List<OBJD>();
if (objs == null) continue;
foreach (var obj in objs)
{
Entries.Add(obj.GUID, new GameObjectReference(this)
{
ID = obj.GUID,
FileName = entry,
Source = GameObjectSource.Standalone,
Name = obj.ChunkLabel,
Group = (short)obj.MasterID,
SubIndex = obj.SubIndex
});
// Try get catalog strings for the object with the current locale.
// If they're present, record them so they can be sent to the catalog provider for search.
CTSS catString = iffFile.Get<CTSS>(obj.CatalogStringsID);
if (catString != null)
{
CatalogEnrich.Add(obj.GUID, new GameObjectCatalogEnrich()
{
CatalogName = catString.GetString(0)
});
}
}
}
var piffModified = PIFFRegistry.GetOBJDRewriteNames();
foreach (var name in piffModified)
{
ProcessedFiles.GetOrAdd(name, GenerateResource(new GameObjectReference(this) { FileName = name.Substring(0, name.Length-4), Source = GameObjectSource.Far }));
}
}
protected override Func<string, GameObjectResource> GenerateResource(GameObjectReference reference)
{
return (fname) =>
{
/** Better set this up! **/
IffFile sprites = null, iff = null;
OTFFile tuning = null;
if (reference.Source == GameObjectSource.Far)
{
iff = this.Iffs.Get(reference.FileName + ".iff");
iff.InitHash();
iff.RuntimeInfo.Path = reference.FileName;
if (WithSprites) sprites = this.Sprites.Get(reference.FileName + ".spf");
var rewrite = PIFFRegistry.GetOTFRewrite(reference.FileName + ".otf");
try
{
tuning = (rewrite != null) ? new OTFFile(rewrite) : this.TuningTables.Get(reference.FileName + ".otf");
}
catch (Exception)
{
//if any issues occur loading an otf, just silently ignore it.
}
}
else
{
iff = new IffFile(reference.FileName);
iff.InitHash();
iff.RuntimeInfo.Path = reference.FileName;
iff.RuntimeInfo.State = IffRuntimeState.Standalone;
}
if (iff.RuntimeInfo.State == IffRuntimeState.PIFFPatch)
{
//OBJDs may have changed due to patch. Remove all file references
ResetFile(iff);
}
iff.RuntimeInfo.UseCase = IffUseCase.Object;
if (sprites != null) sprites.RuntimeInfo.UseCase = IffUseCase.ObjectSprites;
return new GameObjectResource(iff, sprites, tuning, reference.FileName, ContentManager);
};
}
}
public class GameObjectReference : IContentReference<GameObject>
{
public ulong ID;
public string FileName;
public GameObjectSource Source;
public string Name;
public short Group;
public short SubIndex;
public bool GlobalSimObject;
private AbstractObjectProvider Provider;
public GameObjectReference(AbstractObjectProvider provider)
{
this.Provider = provider;
}
public override string ToString()
{
return Name;
}
#region IContentReference<GameObject> Members
public GameObject Get()
{
return Provider.Get(ID);
}
public object GetThrowawayGeneric()
{
throw new NotImplementedException();
}
public object GetGeneric()
{
return Get();
}
#endregion
}
public enum GameObjectSource
{
Far,
PIFFClone,
Standalone,
User
}
public class GameObjectCatalogEnrich
{
public string CatalogName;
}
/// <summary>
/// An object in the game world.
/// </summary>
public class GameObject
{
public ulong GUID;
public OBJD OBJ;
public GameObjectResource Resource;
}
public abstract class GameIffResource
{
public abstract IffFile MainIff { get; }
public abstract T Get<T>(ushort id);
public abstract List<T> List<T>();
public Dictionary<uint, short> TuningCache;
public Dictionary<ushort, object> RoutineCache;
public T[] ListArray<T>()
{
List<T> result = List<T>();
if (result == null) result = new List<T>();
return result.ToArray();
}
public GameGlobalResource SemiGlobal;
public static Func<BHAV, GameIffResource, object> BHAVAssembler;
public virtual void Recache()
{
RoutineCache = new Dictionary<ushort, object>();
var bhavs = MainIff.List<BHAV>();
if (bhavs != null)
{
foreach (var bhav in bhavs)
{
RoutineCache[bhav.ChunkID] = BHAVAssembler(bhav, this);
}
}
TuningCache = new Dictionary<uint, short>();
var bcons = MainIff.List<BCON>();
if (bcons != null)
{
foreach (var table in bcons)
{
uint i = ((uint)table.ChunkID << 16);
foreach (var item in table.Constants)
{
TuningCache[i++] = (short)item;
}
}
}
}
public object GetRoutine(ushort id)
{
object result;
if (RoutineCache.TryGetValue(id, out result))
return result;
else
return null;
}
}
/// <summary>
/// The resource for an object in the game world.
/// </summary>
public class GameObjectResource : GameIffResource
{
//DO NOT USE THESE, THEY ARE ONLY PUBLIC FOR DEBUG UTILITIES
public IffFile Iff;
public IffFile Sprites;
public OTFFile Tuning;
//use this tho
public string Name;
public Dictionary<string, VMTreeByNameTableEntry> TreeByName;
public override IffFile MainIff
{
get { return Iff; }
}
public GameObjectResource(IffFile iff, IffFile sprites, OTFFile tuning, string iname, Content content)
{
this.Iff = iff;
this.Sprites = sprites;
this.Tuning = tuning;
this.Name = iname;
if (iff == null) return;
var GLOBChunks = iff.List<GLOB>();
if (GLOBChunks != null && GLOBChunks[0].Name != "")
{
GameGlobal sg = null;
try
{
sg = content.WorldObjectGlobals.Get(GLOBChunks[0].Name);
} catch (Exception)
{
}
if (sg != null) SemiGlobal = sg.Resource; //used for tuning constant fetching.
}
Recache();
}
public override void Recache()
{
base.Recache();
TreeByName = new Dictionary<string, VMTreeByNameTableEntry>();
var bhavs = List<BHAV>();
if (bhavs != null)
{
foreach (var bhav in bhavs)
{
string name = bhav.ChunkLabel;
for (var i = 0; i < name.Length; i++)
{
if (name[i] == 0)
{
name = name.Substring(0, i);
break;
}
}
if (!TreeByName.ContainsKey(name)) TreeByName.Add(name, new VMTreeByNameTableEntry(GetRoutine(bhav.ChunkID)));
}
}
//also add semiglobals
if (SemiGlobal != null)
{
bhavs = SemiGlobal.List<BHAV>();
if (bhavs != null)
{
foreach (var bhav in bhavs)
{
string name = bhav.ChunkLabel;
for (var i = 0; i < name.Length; i++)
{
if (name[i] == 0)
{
name = name.Substring(0, i);
break;
}
}
if (!TreeByName.ContainsKey(name)) TreeByName.Add(name, new VMTreeByNameTableEntry(SemiGlobal.GetRoutine(bhav.ChunkID)));
}
}
}
if (Tuning != null)
{
foreach (var table in Tuning.Tables)
{
if (table == null) continue;
foreach (var item in table.Keys)
{
if (item == null) continue;
TuningCache[((uint)table.ID << 16) | (uint)(item.ID)] = (short)item.Value;
}
}
}
}
/// <summary>
/// Gets a game object's resource based on the ID found in the object's OTF.
/// </summary>
/// <typeparam name="T">Type of object reource to load (IFF, SPF).</typeparam>
/// <param name="id">ID of the resource to load.</param>
/// <returns>An object's resource of the specified type.</returns>
public override T Get<T>(ushort id)
{
var type = typeof(T);
if (type == typeof(OTFTable))
{
if (Tuning != null)
{
return (T)(object)Tuning.GetTable(id);
}
else
{
return default(T);
}
}
T item1 = this.Iff.Get<T>(id);
if (item1 != null)
{
return item1;
}
if (this.Sprites != null)
{
T item2 = this.Sprites.Get<T>(id);
if (item2 != null)
{
return item2;
}
}
return default(T);
}
public override List<T> List<T>()
{
var type = typeof(T);
if ((type == typeof(SPR2) || type == typeof(SPR) || type == typeof(DGRP)) && this.Sprites != null)
{
return this.Sprites.List<T>();
}
return this.Iff.List<T>();
}
}
public class VMTreeByNameTableEntry
{
public object bhav;
public VMTreeByNameTableEntry(object bhav)
{
this.bhav = bhav;
}
}
}