mysimulation/server/tso.files/Formats/IFF/Chunks/NBRS.cs
Tony Bark 8fec258215 Added FSO.Files for use with the API server
Don't ask me. FreeSO is the prime example of dependency hell.
2024-05-01 04:38:12 -04:00

224 lines
7.6 KiB
C#
Executable file

using FSO.Files.Utils;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace FSO.Files.Formats.IFF.Chunks
{
/// <summary>
/// This chunk defines all neighbours in a neighbourhood.
/// A neighbour is a specific version of a sim object with associated relationships and person data. (skills, person type)
///
/// These can be read within SimAntics without the avatar actually present. This is used to find and spawn suitable sims on
/// ped portals as visitors, and also drive phone calls to other sims in the neighbourhood.
/// When neighbours are spawned, they assume the attributes saved here. A TS1 global call allows the game to save these attributes.
/// </summary>
public class NBRS : IffChunk
{
public List<Neighbour> Entries = new List<Neighbour>();
public Dictionary<short, Neighbour> NeighbourByID = new Dictionary<short, Neighbour>();
public Dictionary<uint, short> DefaultNeighbourByGUID = new Dictionary<uint, short>();
public uint Version;
/// <summary>
/// Reads a NBRS chunk from a stream.
/// </summary>
/// <param name="iff">An Iff instance.</param>
/// <param name="stream">A Stream object holding a NBRS chunk.</param>
public override void Read(IffFile iff, Stream stream)
{
using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
{
io.ReadUInt32(); //pad
Version = io.ReadUInt32(); //0x49 for latest game
string magic = io.ReadCString(4); //SRBN
var count = io.ReadUInt32();
for (int i=0; i<count; i++)
{
if (!io.HasMore) break;
var neigh = new Neighbour(io);
Entries.Add(neigh);
if (neigh.Unknown1 > 0)
{
NeighbourByID.Add(neigh.NeighbourID, neigh);
DefaultNeighbourByGUID[neigh.GUID] = neigh.NeighbourID;
}
}
}
Entries = Entries.OrderBy(x => x.NeighbourID).ToList();
foreach (var entry in Entries)
entry.RuntimeIndex = Entries.IndexOf(entry);
}
/// <summary>
/// Writes a NBRS chunk to a stream.
/// </summary>
/// <param name="iff">An Iff instance.</param>
/// <param name="stream">A destination stream.</param>
public override bool Write(IffFile iff, Stream stream)
{
using (var io = IoWriter.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
{
io.WriteUInt32(0);
io.WriteUInt32(0x49);
io.WriteCString("SRBN", 4);
io.WriteInt32(Entries.Count);
foreach (var n in NeighbourByID.Values)
{
n.Save(io);
}
}
return true;
}
public void AddNeighbor(Neighbour nb) {
Entries.Add(nb);
Entries = Entries.OrderBy(x => x.NeighbourID).ToList();
foreach (var entry in Entries)
entry.RuntimeIndex = Entries.IndexOf(entry);
NeighbourByID.Add(nb.NeighbourID, nb);
DefaultNeighbourByGUID[nb.GUID] = nb.NeighbourID;
}
public short GetFreeID()
{
//find the lowest id that is free
short newID = 1;
for (int i = 0; i < Entries.Count; i++)
{
if (Entries[i].NeighbourID == newID) newID++;
else if (Entries[i].NeighbourID < newID) continue;
else break;
}
return newID;
}
}
public class Neighbour
{
public int Unknown1 = 1; //1
public int Version = 0xA; //0x4, 0xA
//if 0xA, unknown3 follows
//0x4 indicates person data size of 0xa0.. (160 bytes, or 80 entries)
public int Unknown3 = 9; //9
public string Name;
public int MysteryZero = 0;
public int PersonMode; //0/5/9
public short[] PersonData; //can be null
public short NeighbourID;
public uint GUID;
public int UnknownNegOne = -1; //negative 1 usually
public Dictionary<int, List<short>> Relationships;
public int RuntimeIndex; //used for fast continuation of Set to Next
public Neighbour() { }
public Neighbour(IoBuffer io)
{
Unknown1 = io.ReadInt32();
if (Unknown1 != 1) { return; }
Version = io.ReadInt32();
if (Version == 0xA)
{
//TODO: what version does this truly start?
Unknown3 = io.ReadInt32();
if (Unknown3 != 9) { }
}
Name = io.ReadNullTerminatedString();
if (Name.Length % 2 == 0) io.ReadByte();
MysteryZero = io.ReadInt32();
if (MysteryZero != 0) { }
PersonMode = io.ReadInt32();
if (PersonMode > 0)
{
var size = (Version == 0x4) ? 0xa0 : 0x200;
PersonData = new short[88];
int pdi = 0;
for (int i=0; i<size; i+=2)
{
if (pdi >= 88)
{
io.ReadBytes(size - i);
break;
}
PersonData[pdi++] = io.ReadInt16();
}
}
NeighbourID = io.ReadInt16();
GUID = io.ReadUInt32();
UnknownNegOne = io.ReadInt32();
if (UnknownNegOne != -1) { }
var entries = io.ReadInt32();
Relationships = new Dictionary<int, List<short>>();
for (int i=0; i<entries; i++)
{
var keyCount = io.ReadInt32();
if (keyCount != 1) { }
var key = io.ReadInt32();
var values = new List<short>();
var valueCount = io.ReadInt32();
for (int j=0; j<valueCount; j++)
{
values.Add((short)io.ReadInt32());
}
Relationships.Add(key, values);
}
}
public override string ToString()
{
return Name;
}
public void Save(IoWriter io)
{
io.WriteInt32(Unknown1);
io.WriteInt32(Version);
if (Version == 0xA) io.WriteInt32(Unknown3);
io.WriteNullTerminatedString(Name);
if (Name.Length % 2 == 0) io.WriteByte(0);
io.WriteInt32(MysteryZero);
io.WriteInt32(PersonMode);
if (PersonMode > 0)
{
var size = (Version == 0x4) ? 0xa0 : 0x200;
int pdi = 0;
for (int i = 0; i < size; i += 2)
{
if (pdi >= 88)
{
io.WriteInt16(0);
}
else
{
io.WriteInt16(PersonData[pdi++]);
}
}
}
io.WriteInt16(NeighbourID);
io.WriteUInt32(GUID);
io.WriteInt32(UnknownNegOne);
io.WriteInt32(Relationships.Count);
foreach (var rel in Relationships)
{
io.WriteInt32(1); //keycount (1)
io.WriteInt32(rel.Key);
io.WriteInt32(rel.Value.Count);
foreach (var val in rel.Value)
{
io.WriteInt32(val);
}
}
}
}
}