mysimulation/server/tso.files/Formats/IFF/Chunks/TTAB.cs

434 lines
17 KiB
C#
Raw Permalink Normal View History

using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using FSO.Files.Utils;
namespace FSO.Files.Formats.IFF.Chunks
{
/// <summary>
/// This chunk type defines a list of interactions for an object and assigns a BHAV subroutine
/// for each interaction. The pie menu labels shown to the user are stored in a TTAs chunk with
/// the same ID.
/// </summary>
public class TTAB : IffChunk
{
public TTABInteraction[] Interactions = new TTABInteraction[0];
public Dictionary<uint, TTABInteraction> InteractionByIndex = new Dictionary<uint, TTABInteraction>();
public TTABInteraction[] AutoInteractions = new TTABInteraction[0];
public static float[] AttenuationValues = {
0, //custom
0, //none
0.1f, //low
0.3f, //medium
0.6f, //high
};
public static float[] VisitorAttenuationValues = {
0, //custom
0, //none
0.01f, //low
0.02f, //medium
0.03f, //high
};
/// <summary>
/// Reads a TTAB chunk from a stream.
/// </summary>
/// <param name="iff">An Iff instance.</param>
/// <param name="stream">A Stream object holding a TTAB chunk.</param>
public override void Read(IffFile iff, Stream stream)
{
using (var io = IoBuffer.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
{
InteractionByIndex.Clear();
Interactions = new TTABInteraction[io.ReadUInt16()];
if (Interactions.Length == 0) return; //no interactions, don't bother reading remainder.
var version = io.ReadUInt16();
IOProxy iop;
if (version <= 3)
{
// DO NOT LOAD THIS TTAB TYPE
Interactions = new TTABInteraction[0];
return;
}
if (version < 9 || (version > 10 && !iff.TSBO)) iop = new TTABNormal(io);
else
{
var compressionCode = io.ReadByte();
if (compressionCode != 1) iop = new TTABNormal(io);
else iop = new IffFieldEncode(io);
}
for (int i = 0; i < Interactions.Length; i++)
{
var result = new TTABInteraction();
result.ActionFunction = iop.ReadUInt16();
result.TestFunction = iop.ReadUInt16();
result.MotiveEntries = new TTABMotiveEntry[iop.ReadUInt32()];
result.Flags = (TTABFlags)iop.ReadUInt32();
result.TTAIndex = iop.ReadUInt32();
if (version > 6) result.AttenuationCode = iop.ReadUInt32();
result.AttenuationValue = iop.ReadFloat();
result.AutonomyThreshold = iop.ReadUInt32();
result.JoiningIndex = iop.ReadInt32();
for (int j = 0; j < result.MotiveEntries.Length; j++)
{
var motive = new TTABMotiveEntry();
motive.MotiveIndex = j;
if (version > 6) motive.EffectRangeMinimum = iop.ReadInt16();
motive.EffectRangeDelta = iop.ReadInt16();
if (version > 6) motive.PersonalityModifier = iop.ReadUInt16();
result.MotiveEntries[j] = motive;
}
if (version > 9 && !iff.TSBO)
{
result.Flags2 = (TSOFlags)iop.ReadUInt32();
}
Interactions[i] = result;
InteractionByIndex.Add(result.TTAIndex, result);
}
}
InitAutoInteractions();
}
public override bool Write(IffFile iff, Stream stream)
{
using (var io = IoWriter.FromStream(stream, ByteOrder.LITTLE_ENDIAN))
{
io.WriteUInt16((ushort)Interactions.Length);
io.WriteUInt16((ushort)((IffFile.TargetTS1) ? 8 : 10)); //version. we save version 10 which uses the IO proxy
//...but we can't write out to that yet so write with compression code 0
if (!IffFile.TargetTS1) io.WriteByte(0);
for (int i = 0; i < Interactions.Length; i++)
{
var action = Interactions[i];
io.WriteUInt16(action.ActionFunction);
io.WriteUInt16(action.TestFunction);
io.WriteUInt32((uint)action.MotiveEntries.Length);
io.WriteUInt32((uint)action.Flags);
io.WriteUInt32(action.TTAIndex);
io.WriteUInt32(action.AttenuationCode);
io.WriteFloat(action.AttenuationValue);
io.WriteUInt32(action.AutonomyThreshold);
io.WriteInt32(action.JoiningIndex);
for (int j=0; j < action.MotiveEntries.Length; j++)
{
var mot = action.MotiveEntries[j];
io.WriteInt16(mot.EffectRangeMinimum);
io.WriteInt16(mot.EffectRangeDelta);
io.WriteUInt16(mot.PersonalityModifier);
}
if (!IffFile.TargetTS1) io.WriteUInt32((uint)action.Flags2);
}
}
return true;
}
public void InsertInteraction(TTABInteraction action, int index)
{
var newInt = new TTABInteraction[Interactions.Length + 1];
if (index == -1) index = 0;
Array.Copy(Interactions, newInt, index); //copy before strings
newInt[index] = action;
Array.Copy(Interactions, index, newInt, index + 1, (Interactions.Length - index));
Interactions = newInt;
if (!InteractionByIndex.ContainsKey(action.TTAIndex)) InteractionByIndex.Add(action.TTAIndex, action);
InitAutoInteractions();
}
public void DeleteInteraction(int index)
{
var action = Interactions[index];
var newInt = new TTABInteraction[Interactions.Length - 1];
if (index == -1) index = 0;
Array.Copy(Interactions, newInt, index); //copy before strings
Array.Copy(Interactions, index + 1, newInt, index, (Interactions.Length - (index + 1)));
Interactions = newInt;
if (InteractionByIndex.ContainsKey(action.TTAIndex)) InteractionByIndex.Remove(action.TTAIndex);
InitAutoInteractions();
}
public void InitAutoInteractions()
{
foreach (var interaction in Interactions)
{
interaction.ActiveMotiveEntries = interaction.MotiveEntries.Where(x => x.EffectRangeDelta != 0).ToArray();
}
AutoInteractions = Interactions.Where(interaction => interaction.ActiveMotiveEntries.Length > 0).ToArray();
}
}
public abstract class IOProxy
{
public abstract ushort ReadUInt16();
public abstract short ReadInt16();
public abstract int ReadInt32();
public abstract uint ReadUInt32();
public abstract float ReadFloat();
public IoBuffer io;
public IOProxy(IoBuffer io)
{
this.io = io;
}
}
class TTABNormal : IOProxy
{
public override ushort ReadUInt16() { return io.ReadUInt16(); }
public override short ReadInt16() { return io.ReadInt16(); }
public override int ReadInt32() { return io.ReadInt32(); }
public override uint ReadUInt32() { return io.ReadUInt32(); }
public override float ReadFloat() { return io.ReadFloat(); }
public TTABNormal(IoBuffer io) : base(io) { }
}
/// <summary>
/// Represents an interaction in a TTAB chunk.
/// </summary>
public class TTABInteraction
{
public ushort ActionFunction;
public ushort TestFunction;
public TTABMotiveEntry[] MotiveEntries;
public TTABFlags Flags;
public uint TTAIndex;
public uint AttenuationCode;
public float AttenuationValue;
public uint AutonomyThreshold;
public int JoiningIndex;
public TSOFlags Flags2 = (TSOFlags)0x1e; //allow a lot of things
public TTABMotiveEntry[] ActiveMotiveEntries; //populated when asking for auto interactions.
public InteractionMaskFlags MaskFlags {
get {
return (InteractionMaskFlags)(((int)Flags >> 16) & 0xF);
}
set
{
Flags = (TTABFlags)(((int)Flags & 0xFFFF) | ((int)value << 16));
}
}
//ALLOW
public bool AllowVisitors
{
get { return (Flags & TTABFlags.AllowVisitors) > 0 || (Flags2 & TSOFlags.AllowVisitors) > 0; }
set {
Flags &= ~(TTABFlags.AllowVisitors); if (value) Flags |= TTABFlags.AllowVisitors;
Flags2 &= ~(TSOFlags.AllowVisitors); if (value) Flags2 |= TSOFlags.AllowVisitors;
}
}
public bool AllowFriends
{
get { return (Flags2 & TSOFlags.AllowFriends) > 0; }
set { Flags2 &= ~(TSOFlags.AllowFriends); if (value) Flags2 |= TSOFlags.AllowFriends; }
}
public bool AllowRoommates
{
get { return (Flags2 & TSOFlags.AllowRoommates) > 0; }
set { Flags2 &= ~(TSOFlags.AllowRoommates); if (value) Flags2 |= TSOFlags.AllowRoommates; }
}
public bool AllowObjectOwner
{
get { return (Flags2 & TSOFlags.AllowObjectOwner) > 0; }
set { Flags2 &= ~(TSOFlags.AllowObjectOwner); if (value) Flags2 |= TSOFlags.AllowObjectOwner; }
}
public bool UnderParentalControl
{
get { return (Flags2 & TSOFlags.UnderParentalControl) > 0; }
set { Flags2 &= ~(TSOFlags.UnderParentalControl); if (value) Flags2 |= TSOFlags.UnderParentalControl; }
}
public bool AllowCSRs
{
get { return (Flags2 & TSOFlags.AllowCSRs) > 0; }
set { Flags2 &= ~(TSOFlags.AllowCSRs); if (value) Flags2 |= TSOFlags.AllowCSRs; }
}
public bool AllowGhosts
{
get { return (Flags2 & TSOFlags.AllowGhost) > 0; }
set { Flags2 &= ~(TSOFlags.AllowGhost); if (value) Flags2 |= TSOFlags.AllowGhost; }
}
public bool AllowCats
{
get { return (Flags & TTABFlags.AllowCats) > 0; }
set { Flags &= ~(TTABFlags.AllowCats); if (value) Flags |= TTABFlags.AllowCats; }
}
public bool AllowDogs
{
get { return (Flags & TTABFlags.AllowDogs) > 0; }
set { Flags &= ~(TTABFlags.AllowDogs); if (value) Flags |= TTABFlags.AllowDogs; }
}
//TS1
public bool TS1AllowCats
{
get { return (Flags & TTABFlags.TS1AllowCats) > 0; }
set { Flags &= ~(TTABFlags.TS1AllowCats); if (value) Flags |= TTABFlags.TS1AllowCats; }
}
public bool TS1AllowDogs
{
get { return (Flags & TTABFlags.TS1AllowDogs) > 0; }
set { Flags &= ~(TTABFlags.TS1AllowDogs); if (value) Flags |= TTABFlags.TS1AllowDogs; }
}
public bool TS1AllowAdults
{
get { return (Flags & TTABFlags.TS1NoAdult) == 0; }
set { Flags &= ~(TTABFlags.TS1NoAdult); if (!value) Flags |= TTABFlags.TS1NoAdult; }
}
public bool TS1AllowChild
{
get { return (Flags & TTABFlags.TS1NoChild) == 0; }
set { Flags &= ~(TTABFlags.TS1NoChild); if (!value) Flags |= TTABFlags.TS1NoChild; }
}
public bool TS1AllowDemoChild
{
get { return (Flags & TTABFlags.TS1NoDemoChild) == 0; }
set { Flags &= ~(TTABFlags.TS1NoDemoChild); if (!value) Flags |= TTABFlags.TS1NoDemoChild; }
}
public bool Joinable
{
get { return (Flags & TTABFlags.Joinable) > 0; }
set { Flags &= ~(TTABFlags.Joinable); if (value) Flags |= TTABFlags.Joinable; }
}
//FLAGS
public bool Debug
{
get { return (Flags & TTABFlags.Debug) > 0; }
set { Flags &= ~(TTABFlags.Debug); if (value) Flags |= TTABFlags.Debug; }
}
public bool Leapfrog {
get { return (Flags & TTABFlags.Leapfrog) > 0; }
set { Flags &= ~(TTABFlags.Leapfrog); if (value) Flags |= TTABFlags.Leapfrog; }
}
public bool MustRun
{
get { return (Flags & TTABFlags.MustRun) > 0; }
set { Flags &= ~(TTABFlags.MustRun); if (value) Flags |= TTABFlags.MustRun; }
}
public bool AutoFirst
{
get { return (Flags & TTABFlags.AutoFirstSelect) > 0; }
set { Flags &= ~(TTABFlags.AutoFirstSelect); if (value) Flags |= TTABFlags.AutoFirstSelect; }
}
public bool RunImmediately
{
get { return (Flags & TTABFlags.RunImmediately) > 0; }
set { Flags &= ~(TTABFlags.RunImmediately); if (value) Flags |= TTABFlags.RunImmediately; }
}
public bool AllowConsecutive
{
get { return (Flags & TTABFlags.AllowConsecutive) > 0; }
set { Flags &= ~(TTABFlags.AllowConsecutive); if (value) Flags |= TTABFlags.AllowConsecutive; }
}
public bool Carrying
{
get { return (MaskFlags & InteractionMaskFlags.AvailableWhenCarrying) > 0; }
set { MaskFlags &= ~(InteractionMaskFlags.AvailableWhenCarrying); if (value) MaskFlags |= InteractionMaskFlags.AvailableWhenCarrying; }
}
public bool Repair
{
get { return (MaskFlags & InteractionMaskFlags.IsRepair) > 0; }
set { MaskFlags &= ~(InteractionMaskFlags.IsRepair); if (value) MaskFlags |= InteractionMaskFlags.IsRepair; }
}
public bool AlwaysCheck
{
get { return (MaskFlags & InteractionMaskFlags.RunCheckAlways) > 0; }
set { MaskFlags &= ~(InteractionMaskFlags.RunCheckAlways); if (value) MaskFlags |= InteractionMaskFlags.RunCheckAlways; }
}
public bool WhenDead
{
get { return (MaskFlags & InteractionMaskFlags.AvailableWhenDead) > 0; }
set { MaskFlags &= ~(InteractionMaskFlags.AvailableWhenDead); if (value) MaskFlags |= InteractionMaskFlags.AvailableWhenDead; }
}
public void InitMotiveEntries()
{
for (int i=0; i<MotiveEntries.Length; i++)
{
MotiveEntries[i].MotiveIndex = i;
}
}
}
/// <summary>
/// Represents a motive entry in a TTAB chunk.
/// </summary>
public struct TTABMotiveEntry
{
public int MotiveIndex; // don't save this
public short EffectRangeMinimum;
public short EffectRangeDelta;
public ushort PersonalityModifier;
}
public enum TTABFlags
{
AllowVisitors = 1, //COVERED, TODO for no TSOFlags? (default to only roomies, unless this flag set)
Joinable = 1 << 1, //TODO
RunImmediately = 1 << 2, //COVERED
AllowConsecutive = 1 << 3, //TODO
TS1NoChild = 1 << 4,
TS1NoDemoChild = 1 << 5,
TS1NoAdult = 1 << 6,
Debug = 1 << 7, //COVERED: only available to roomies for now
AutoFirstSelect = 1 << 8, //COVERED
TS1AllowCats = 1 << 9,
TS1AllowDogs = 1 << 10,
Leapfrog = 1 << 9, //COVERED
MustRun = 1 << 10, //COVERED
AllowDogs = 1 << 11, //COVERED
AllowCats = 1 << 12, //COVERED
TSOAvailableCarrying = 1 << 16, //COVERED
TSOIsRepair = 1 << 17, //TODO (only available when wear = 0)
TSORunCheckAlways = 1 << 18, //TODO
TSOAvailableWhenDead = 1<<19, //COVERED
FSOPushTail = 1<<30,
FSOPushHead = 1<<29,
FSOSkipPermissions = 1<<28,
FSODirectControl = 1<<27
}
public enum TSOFlags
{
NonEmpty = 1, //if this is the only flag set, flags aren't empty intentionally. force Owner, Roommates, Friends to on
AllowObjectOwner = 1 << 1, //COVERED
AllowRoommates = 1 << 2, //COVERED
AllowFriends = 1 << 3, //TODO
AllowVisitors = 1 << 4, //COVERED
AllowGhost = 1 << 5, //COVERED
UnderParentalControl = 1 << 6, //TODO: interactions always available
AllowCSRs = 1 << 7 //COVERED: only available to admins
}
public enum InteractionMaskFlags
{
AvailableWhenCarrying = 1,
IsRepair = 1<<1,
RunCheckAlways = 1 << 2,
AvailableWhenDead = 1 << 3,
}
}