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); } } } } }