Removed NioTSO client and server

- NioTSO client isn't needed because we're using RayLib
- Added FreeSO's API server to handle most backend operations
This commit is contained in:
Tony Bark 2024-05-01 02:55:43 -04:00
parent f12ba1502b
commit 22191ce648
591 changed files with 53264 additions and 3362 deletions

View file

@ -0,0 +1,288 @@
using Mp3Sharp;
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework.Audio;
using System.Threading;
using System.Threading.Tasks;
namespace FSO.Common.Audio
{
public class MP3Player : ISFXInstanceLike
{
public static bool NewMode = true;
private Mp3Stream Stream;
public DynamicSoundEffectInstance Inst;
private int LastChunkSize = 1; //don't die immediately..
private Thread DecoderThread;
private List<byte[]> NextBuffers = new List<byte[]>();
private List<int> NextSizes = new List<int>();
private int Requests;
private AutoResetEvent DecodeNext;
private AutoResetEvent BufferDone;
private bool EndOfStream;
private bool Active = true;
private Thread MainThread; //keep track of this, terminate when it closes.
private SoundState _State = SoundState.Stopped;
private bool Disposed = false;
private float _Volume = 1f;
private float _Pan;
private object ControlLock = new object();
private string Path;
public int SendExtra = 2;
private static byte[] Blank = new byte[65536];
public MP3Player(string path)
{
Path = path;
// //let's get started...
DecodeNext = new AutoResetEvent(true);
BufferDone = new AutoResetEvent(false);
MainThread = Thread.CurrentThread;
Task.Run((Action)Start);
}
public void Start()
{
Stream = new Mp3Stream(Path);
Stream.DecodeFrames(1);
var freq = Stream.Frequency;
lock (ControlLock)
{
if (Disposed) return;
Inst = new DynamicSoundEffectInstance(freq, AudioChannels.Stereo);
Inst.IsLooped = false;
Inst.BufferNeeded += SubmitBufferAsync;
if (_State == SoundState.Playing) Inst.Play();
else if (_State == SoundState.Paused)
{
Inst.Play();
Inst.Pause();
}
Inst.Volume = _Volume;
Inst.Pan = _Pan;
Requests = 2;
}
//SubmitBuffer(null, null);
//SubmitBuffer(null, null);
DecoderThread = new Thread(() =>
{
try
{
while (Active && MainThread.IsAlive)
{
DecodeNext.WaitOne(128);
bool go;
lock (this) go = Requests > 0;
while (go)
{
var buf = new byte[262144];// 524288];
var read = Stream.Read(buf, 0, buf.Length);
lock (this)
{
Requests--;
NextBuffers.Add(buf);
NextSizes.Add(read);
if (read == 0)
{
EndOfStream = true;
BufferDone.Set();
return;
}
BufferDone.Set();
}
lock (this) go = Requests > 0;
}
}
}
catch (Exception e) { }
});
DecoderThread.Start();
DecodeNext.Set();
}
public void Play()
{
lock (ControlLock)
{
_State = SoundState.Playing;
Inst?.Play();
}
}
public void Stop()
{
lock (ControlLock)
{
_State = SoundState.Stopped;
Inst?.Stop();
}
}
public void Pause()
{
lock (ControlLock)
{
_State = SoundState.Paused;
Inst?.Pause();
}
}
public void Resume()
{
lock (ControlLock)
{
_State = SoundState.Playing;
Inst?.Resume();
}
}
public void Dispose()
{
lock (ControlLock)
{
Disposed = true;
Inst?.Dispose();
Stream?.Dispose();
Active = false;
DecodeNext.Set(); //end the mp3 thread immediately
EndOfStream = true;
}
}
public bool IsEnded()
{
return EndOfStream && Inst.PendingBufferCount == 0;
}
public float Volume
{
get
{
lock (ControlLock)
{
if (Inst != null) return Inst.Volume;
else return _Volume;
}
}
set
{
lock (ControlLock)
{
_Volume = value;
if (Inst != null) Inst.Volume = value;
}
}
}
public float Pan
{
get
{
lock (ControlLock)
{
if (Inst != null) return Inst.Pan;
else return _Pan;
}
}
set
{
lock (ControlLock)
{
_Pan = value;
if (Inst != null) Inst.Pan = value;
}
}
}
public SoundState State
{
get
{
lock (ControlLock)
{
if (Inst != null) return Inst.State;
else return _State;
}
}
}
public bool IsLooped { get; set; }
private void SubmitBuffer(object sender, EventArgs e)
{
byte[] buffer = new byte[524288];
lock (this)
{
var read = Stream.Read(buffer, 0, buffer.Length);
LastChunkSize = read;
if (read == 0)
{
return;
}
Inst.SubmitBuffer(buffer, 0, read);
}
}
private void SubmitBufferAsync(object sender, EventArgs e)
{
while (true)
{
if (EndOfStream) return;
var gotData = false;
lock (this)
{
if (NextBuffers.Count > 0)
{
if (NextSizes[0] > 0) Inst.SubmitBuffer(NextBuffers[0], 0, NextSizes[0]);
gotData = true;
NextBuffers.RemoveAt(0);
NextSizes.RemoveAt(0);
Requests++;
DecodeNext.Set();
if (SendExtra > 0)
{
SendExtra--;
continue;
}
return;
}
if (EndOfStream) return;
}
if (!gotData)
{
Inst.SubmitBuffer(Blank, 0, Blank.Length);
Requests++;
DecodeNext.Set();
return;
//if (NewMode) BufferDone.WaitOne(128);
}
}
}
}
public interface ISFXInstanceLike
{
float Volume { get; set; }
float Pan { get; set; }
SoundState State { get; }
bool IsLooped { get; set; }
void Play();
void Stop();
void Pause();
void Resume();
void Dispose();
}
}

View file

@ -0,0 +1,50 @@
using System;
namespace FSO.Common
{
/// <summary>
/// TODO: apply a time delta to sync with server time. right now we assume client time is correct.
/// </summary>
public class ClientEpoch
{
public static uint Now
{
get
{
uint epoch = (uint)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds;
return epoch;
}
}
public static uint FromDate(DateTime time)
{
return (uint)(time.ToUniversalTime() - new DateTime(1970, 1, 1)).TotalSeconds;
}
public static DateTime ToDate(uint time)
{
var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
return epoch.AddSeconds(time);
}
public static string HMSRemaining(uint date)
{
TimeSpan span = (ToDate(date) - ToDate(ClientEpoch.Now));
return String.Format("{0} hours, {1} minutes and {2} seconds", (int)span.TotalHours, span.Minutes, span.Seconds);
}
public static string DHMRemaining(uint date)
{
if (date == uint.MaxValue) return "Permanent";
TimeSpan span = (ToDate(date) - ToDate(ClientEpoch.Now));
return String.Format("{0} days, {1} hours and {2} minutes", (int)span.TotalDays, span.Hours, span.Minutes);
}
public static uint Default
{
get { return 0; }
}
}
}

View file

@ -0,0 +1,43 @@
namespace FSO.Common.Content
{
/// <summary>
/// Represents the ID of a content resource.
/// Consists of two parts: TypeID (uint) and FileID (uint).
/// </summary>
public class ContentID
{
public uint TypeID;
public uint FileID;
public string FileName;
private long v;
/// <summary>
/// Creates a new ContentID instance.
/// </summary>
/// <param name="typeID">The TypeID of the content resource.</param>
/// <param name="fileID">The FileID of the content resource.</param>
public ContentID(uint typeID, uint fileID)
{
this.TypeID = typeID;
this.FileID = fileID;
}
public ContentID(string name)
{
this.FileName = name;
}
public ContentID(long v)
{
this.TypeID = (uint)v;
this.FileID = (uint)(v >> 32);
}
public ulong Shift()
{
var fileIDLong = ((ulong)FileID) << 32;
return fileIDLong | TypeID;
}
}
}

View file

@ -0,0 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace TSO.Common.content
{
public interface IContent
{
}
}

View file

@ -0,0 +1,13 @@
using System.Collections.Generic;
namespace FSO.Common.Content
{
public interface IContentProvider <T>
{
T Get(ulong id);
T Get(string name);
T Get(uint type, uint fileID);
T Get(ContentID id);
List<IContentReference<T>> List();
}
}

View file

@ -0,0 +1,13 @@
namespace FSO.Common.Content
{
public interface IContentReference <T> : IContentReference
{
T Get();
}
public interface IContentReference
{
object GetGeneric();
object GetThrowawayGeneric();
}
}

Binary file not shown.

View file

@ -0,0 +1,20 @@
namespace FSO.Common.Enum
{
public enum LotCategory
{
none = 0,
money = 1,
offbeat = 2,
romance = 3,
services = 4,
shopping = 5,
skills = 6,
welcome = 7,
games = 8,
entertainment = 9,
residence = 10,
community = 11, //cannot be set by users
recent = 255 //for filter searches
}
}

View file

@ -0,0 +1,114 @@
using System;
namespace FSO.Common.Enum
{
public enum Top100Category
{
lot_money = 1,
lot_offbeat = 2,
lot_romance = 3,
lot_services = 4,
lot_shopping = 5,
lot_skills = 6,
lot_welcome = 7,
lot_games = 8,
lot_entertainment = 9,
lot_residence = 10,
avatar_most_famous = 11,
avatar_best_karma = 12,
avatar_friendliest = 13,
avatar_most_infamous = 14,
avatar_meanest = 15
}
public static class Top100CategoryUtils
{
public static bool IsAvatarCategory(this Top100Category category)
{
return !category.IsLotCategory();
}
public static bool IsLotCategory(this Top100Category category)
{
switch (category)
{
case Top100Category.lot_money:
case Top100Category.lot_offbeat:
case Top100Category.lot_romance:
case Top100Category.lot_services:
case Top100Category.lot_shopping:
case Top100Category.lot_skills:
case Top100Category.lot_welcome:
case Top100Category.lot_games:
case Top100Category.lot_entertainment:
case Top100Category.lot_residence:
return true;
default:
return false;
}
}
public static Top100Category FromLotCategory(LotCategory category)
{
switch (category)
{
case LotCategory.money:
return Top100Category.lot_money;
case LotCategory.offbeat:
return Top100Category.lot_offbeat;
case LotCategory.romance:
return Top100Category.lot_romance;
case LotCategory.services:
return Top100Category.lot_services;
case LotCategory.shopping:
return Top100Category.lot_shopping;
case LotCategory.skills:
return Top100Category.lot_skills;
case LotCategory.welcome:
return Top100Category.lot_welcome;
case LotCategory.games:
return Top100Category.lot_games;
case LotCategory.entertainment:
return Top100Category.lot_entertainment;
case LotCategory.residence:
return Top100Category.lot_residence;
}
throw new Exception("Unknown lot category");
}
public static LotCategory ToLotCategory(this Top100Category category)
{
switch (category)
{
case Top100Category.lot_money:
return LotCategory.money;
case Top100Category.lot_offbeat:
return LotCategory.offbeat;
case Top100Category.lot_romance:
return LotCategory.romance;
case Top100Category.lot_services:
return LotCategory.services;
case Top100Category.lot_shopping:
return LotCategory.shopping;
case Top100Category.lot_skills:
return LotCategory.skills;
case Top100Category.lot_welcome:
return LotCategory.welcome;
case Top100Category.lot_games:
return LotCategory.games;
case Top100Category.lot_entertainment:
return LotCategory.entertainment;
case Top100Category.lot_residence:
return LotCategory.residence;
}
return LotCategory.none;
}
}
public enum Top100CategoryType
{
AVATAR = 1,
LOT = 2
}
}

View file

@ -0,0 +1,11 @@
namespace FSO.Common.Enum
{
public enum UserReferenceType
{
EA = 1,
MAXIS = 2,
MOMI = 3,
TSO = 4,
AVATAR = 5
}
}

View file

@ -0,0 +1,307 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>9.0.30729</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{C42962A1-8796-4F47-9DCD-79ED5904D8CA}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>FSO.Common</RootNamespace>
<AssemblyName>FSO.Common</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<FileUpgradeFlags>
</FileUpgradeFlags>
<UpgradeBackupLocation>
</UpgradeBackupLocation>
<OldToolsVersion>3.5</OldToolsVersion>
<PublishUrl>publish\</PublishUrl>
<Install>true</Install>
<InstallFrom>Disk</InstallFrom>
<UpdateEnabled>false</UpdateEnabled>
<UpdateMode>Foreground</UpdateMode>
<UpdateInterval>7</UpdateInterval>
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
<UpdatePeriodically>false</UpdatePeriodically>
<UpdateRequired>false</UpdateRequired>
<MapFileExtensions>true</MapFileExtensions>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>1.0.0.%2a</ApplicationVersion>
<IsWebBootstrapper>false</IsWebBootstrapper>
<UseApplicationTrust>false</UseApplicationTrust>
<BootstrapperEnabled>true</BootstrapperEnabled>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Prefer32Bit>false</Prefer32Bit>
<UseVSHostingProcess>true</UseVSHostingProcess>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
<UseVSHostingProcess>true</UseVSHostingProcess>
</PropertyGroup>
<ItemGroup>
<Reference Include="Common.Logging, Version=3.4.1.0, Culture=neutral, PublicKeyToken=af08829b84f0328e, processorArchitecture=MSIL">
<HintPath>..\packages\Common.Logging.3.4.1\lib\net40\Common.Logging.dll</HintPath>
</Reference>
<Reference Include="Common.Logging.Core, Version=3.4.1.0, Culture=neutral, PublicKeyToken=af08829b84f0328e, processorArchitecture=MSIL">
<HintPath>..\packages\Common.Logging.Core.3.4.1\lib\net40\Common.Logging.Core.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x86\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DebugType>full</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'ServerRelease|AnyCPU'">
<OutputPath>bin\ServerRelease\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>AnyCPU</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'ServerRelease|x86'">
<OutputPath>bin\x86\ServerRelease\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="Mina.NET, Version=2.0.11.0, Culture=neutral, PublicKeyToken=bc4dde96e5154fe5, processorArchitecture=MSIL">
<HintPath>..\packages\Mina.2.0.11\lib\net40\Mina.NET.dll</HintPath>
</Reference>
<Reference Include="MonoGame.Framework, Version=3.6.0.1625, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\MonoGame.Framework.Portable.3.6.0.1625\lib\portable-net45+win8+wpa81\MonoGame.Framework.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.12.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="Ninject, Version=3.0.0.0, Culture=neutral, PublicKeyToken=c7192dc5380945e7, processorArchitecture=MSIL">
<HintPath>..\packages\Portable.Ninject.3.3.1\lib\net40-client\Ninject.dll</HintPath>
</Reference>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.5.7\lib\net45\NLog.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Collections.Immutable, Version=1.2.3.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Collections.Immutable.1.5.0\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll</HintPath>
</Reference>
<Reference Include="System.Configuration" />
<Reference Include="System.Core">
</Reference>
<Reference Include="System.IO.Compression" />
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.ServiceModel" />
<Reference Include="System.Transactions" />
<Reference Include="System.Web" />
<Reference Include="System.Xml.Linq">
</Reference>
<Reference Include="System.Data.DataSetExtensions">
</Reference>
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Audio\MP3Player.cs" />
<Compile Include="ClientEpoch.cs" />
<Compile Include="Content\ContentID.cs" />
<Compile Include="Content\IContentProvider.cs" />
<Compile Include="Content\IContentReference.cs" />
<Compile Include="Enum\LotCategory.cs" />
<Compile Include="Enum\Top100Category.cs" />
<Compile Include="FSOEnvironment.cs" />
<Compile Include="IniConfig.cs" />
<Compile Include="Enum\UserReferenceType.cs" />
<Compile Include="MeshSimplify\MSRef.cs" />
<Compile Include="MeshSimplify\MSTriangle.cs" />
<Compile Include="MeshSimplify\Simplify.cs" />
<Compile Include="MeshSimplify\SymmetricMatrix.cs" />
<Compile Include="MeshSimplify\MSVertex.cs" />
<Compile Include="Model\DynamicTuning.cs" />
<Compile Include="Model\DynTuningEntry.cs" />
<Compile Include="Model\IntersectRectSet.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Rendering\CachableTexture2D.cs" />
<Compile Include="Rendering\Emoji\EmojiCache.cs" />
<Compile Include="Rendering\Emoji\EmojiDictionary.cs" />
<Compile Include="Rendering\Emoji\EmojiProvider.cs" />
<Compile Include="Rendering\Framework\3DComponent.cs" />
<Compile Include="Rendering\Framework\3DLayer.cs" />
<Compile Include="Rendering\Framework\3DScene.cs" />
<Compile Include="Rendering\Framework\3DTargetScene.cs" />
<Compile Include="Rendering\Framework\Camera\BasicCamera.cs" />
<Compile Include="Rendering\Framework\Camera\ICamera.cs" />
<Compile Include="Rendering\Framework\Camera\ManualCamera.cs" />
<Compile Include="Rendering\Framework\Camera\OrthographicCamera.cs" />
<Compile Include="Rendering\Framework\CursorManager.cs" />
<Compile Include="Rendering\Framework\Game.cs" />
<Compile Include="Rendering\Framework\GameScreen.cs" />
<Compile Include="Rendering\Framework\I3DGeometry.cs" />
<Compile Include="Rendering\Framework\IGraphicsLayer.cs" />
<Compile Include="Rendering\Framework\IO\ClipboardHandler.cs" />
<Compile Include="Rendering\Framework\IO\IDepthProvider.cs" />
<Compile Include="Rendering\Framework\IO\IFocusableUI.cs" />
<Compile Include="Rendering\Framework\IO\InputManager.cs" />
<Compile Include="Rendering\Framework\IO\MouseEvent.cs" />
<Compile Include="Rendering\Framework\Model\UIState.cs" />
<Compile Include="Rendering\Framework\Model\UpdateState.cs" />
<Compile Include="Rendering\Framework\Shapes\3DCube.cs" />
<Compile Include="Rendering\Framework\UpdateHookDelegate.cs" />
<Compile Include="Rendering\Framework\3DAbstract.cs" />
<Compile Include="Rendering\TextureInfo.cs" />
<Compile Include="Rendering\TimeOfDayConfig.cs" />
<Compile Include="Security\AvatarPermissions.cs" />
<Compile Include="Security\ISecurityContext.cs" />
<Compile Include="Security\NullSecurityContext.cs" />
<Compile Include="Serialization\ISerializationContext.cs" />
<Compile Include="Serialization\Primitives\cTSOGenericData.cs" />
<Compile Include="Serialization\Primitives\cTSONetMessageStandard.cs" />
<Compile Include="Serialization\Primitives\cTSOProperty.cs" />
<Compile Include="Serialization\Primitives\cTSOTopicUpdateMessage.cs" />
<Compile Include="Serialization\SerializationContext.cs" />
<Compile Include="Serialization\ModelSerializer.cs" />
<Compile Include="Serialization\IModelSerializer.cs" />
<Compile Include="Serialization\IoBufferDeserializable.cs" />
<Compile Include="Serialization\IoBufferSerializable.cs" />
<Compile Include="Serialization\IoBufferUtils.cs" />
<Compile Include="Serialization\TypeSerializers\cTSOValueBooleanMap.cs" />
<Compile Include="Serialization\TypeSerializers\cTSOValueBooleanVector.cs" />
<Compile Include="Serialization\TypeSerializers\cTSOValueGenericData.cs" />
<Compile Include="Serialization\TypeSerializers\cTSOValueUInt32Vector.cs" />
<Compile Include="Serialization\TypeSerializers\cTSOValueBoolean.cs" />
<Compile Include="Serialization\TypeSerializers\cTSOValueDecorated.cs" />
<Compile Include="Serialization\TypeSerializers\cTSOValueSByte.cs" />
<Compile Include="Serialization\TypeSerializers\cTSOValueByte.cs" />
<Compile Include="Serialization\TypeSerializers\cTSOValueSByteVector.cs" />
<Compile Include="Serialization\TypeSerializers\cTSOValueByteVector.cs" />
<Compile Include="Serialization\TypeSerializers\cTSOValueString.cs" />
<Compile Include="Serialization\TypeSerializers\cTSOValueStringVector.cs" />
<Compile Include="Serialization\TypeSerializers\cTSOValueUInt16.cs" />
<Compile Include="Serialization\TypeSerializers\cTSOValueUInt32.cs" />
<Compile Include="Serialization\TypeSerializers\cTSOValueUInt64.cs" />
<Compile Include="TS1\TS1Curve.cs" />
<Compile Include="Utils\AssemblyUtils.cs" />
<Compile Include="Utils\Base64JsonConverter.cs" />
<Compile Include="Utils\BBCodeParser.cs" />
<Compile Include="Utils\Binding.cs" />
<Compile Include="Utils\Cache\CacheKey.cs" />
<Compile Include="Utils\Cache\FileSystemCache.cs" />
<Compile Include="Utils\Cache\ICache.cs" />
<Compile Include="Utils\Callback.cs" />
<Compile Include="Utils\CollectionUtils.cs" />
<Compile Include="Utils\CurLoader.cs" />
<Compile Include="Utils\DebugUtils.cs" />
<Compile Include="Utils\DirectionUtils.cs" />
<Compile Include="Utils\DotPath.cs" />
<Compile Include="Utils\FeatureLevelTest.cs" />
<Compile Include="Utils\FileUtils.cs" />
<Compile Include="Utils\GameThread.cs" />
<Compile Include="Utils\HTMLPrinter.cs" />
<Compile Include="Utils\IffPrinter.cs" />
<Compile Include="Utils\ITimedCachable.cs" />
<Compile Include="Utils\IXMLEntity.cs" />
<Compile Include="Utils\LinqUtils.cs" />
<Compile Include="Utils\MoneyFormatter.cs" />
<Compile Include="Utils\PathCaseTools.cs" />
<Compile Include="Utils\Promise.cs" />
<Compile Include="Utils\PPXDepthEngine.cs" />
<Compile Include="Utils\StateMachine.cs" />
<Compile Include="Utils\TextureGenerator.cs" />
<Compile Include="Utils\TextureUtils.cs" />
<Compile Include="Utils\TimedReferenceCache.cs" />
<Compile Include="Utils\TSOTime.cs" />
<Compile Include="Utils\ValuePointer.cs" />
<Compile Include="Utils\XMLList.cs" />
<Compile Include="WorldGeometry\MeshProjector.cs" />
<Compile Include="WorldGeometry\Paths\LinePath.cs" />
<Compile Include="WorldGeometry\Paths\SVGParser.cs" />
<Compile Include="WorldGeometry\RoadGeometry.cs" />
<Compile Include="WorldGeometry\SimplifiedHeightmap.cs" />
<Compile Include="WorldGeometry\TS1RoadTemplates.cs" />
<Compile Include="WorldGeometry\Utils\TriangleSet.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Other\libs\mp3sharp\mp3sharp\Mp3Sharp.csproj">
<Project>{834cab58-648d-47cc-ac6f-d01c08c809a4}</Project>
<Name>Mp3Sharp</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Content Include="Content\SDL2.dll" />
<Content Include="version.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<BootstrapperPackage Include=".NETFramework,Version=v4.0">
<Visible>False</Visible>
<ProductName>Microsoft .NET Framework 4 %28x86 and x64%29</ProductName>
<Install>true</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Client.3.5">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName>
<Install>false</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
<Visible>False</Visible>
<ProductName>.NET Framework 3.5 SP1</ProductName>
<Install>false</Install>
</BootstrapperPackage>
<BootstrapperPackage Include="Microsoft.Windows.Installer.3.1">
<Visible>False</Visible>
<ProductName>Windows Installer 3.1</ProductName>
<Install>true</Install>
</BootstrapperPackage>
</ItemGroup>
<ItemGroup>
<None Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View file

@ -0,0 +1,38 @@
using System.Threading;
namespace FSO.Common
{
public static class FSOEnvironment
{
public static Thread GameThread;
public static string ContentDir = "Content/";
public static string UserDir = "Content/";
public static string GFXContentDir = "Content/OGL";
public static bool DirectX = false;
public static bool Linux = false;
public static bool UseMRT = true;
/// <summary>
/// True if system does not support gl_FragDepth (eg. iOS). Uses alternate pipeline that abuses stencil buffer.
/// </summary>
public static bool SoftwareDepth = false;
public static int GLVer = 3;
public static float UIZoomFactor = 1f;
public static float DPIScaleFactor = 1;
public static bool SoftwareKeyboard = false;
public static bool NoSound = false;
public static int RefreshRate = 60;
/// <summary>
/// True if 3D features are enabled (like smooth rotation + zoom). Loads some content with mipmaps and other things.
/// Used to mean "3d camera" as well, though that has been moved to configuration and world state.
/// </summary>
public static bool Enable3D;
public static bool EnableNPOTMip = true;
public static bool TexCompress = true;
public static bool TexCompressSupport = true;
public static bool MSAASupport = true;
public static string Args = "";
}
}

84
server/tso.common/IniConfig.cs Executable file
View file

@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
namespace FSO.Common
{
public abstract class IniConfig
{
private string ActivePath;
public abstract Dictionary<string, string> DefaultValues
{
get; set;
}
private void SetValue(string key, string value)
{
var prop = this.GetType().GetProperty(key);
if (prop != null)
{
try
{
if (prop.PropertyType != typeof(string))
prop.SetValue(this, Convert.ChangeType(value, prop.PropertyType, CultureInfo.InvariantCulture));
else prop.SetValue(this, value);
}
catch (Exception) { }
}
}
public IniConfig(string path)
{
ActivePath = path;
Load();
}
public void Load()
{
//assume default values for all unset properties
foreach (var pair in DefaultValues)
{
SetValue(pair.Key, pair.Value);
}
if (!File.Exists(ActivePath))
{
Save();
} else
{
var lines = File.ReadAllLines(ActivePath);
foreach (var line in lines)
{
var clean = line.Trim();
if (clean.Length == 0 || clean[0] == '#' || clean[0] == '[') continue;
var split = clean.IndexOf('=');
if (split == -1) continue; //?
var prop = clean.Substring(0, split).Trim();
var value = clean.Substring(split+1).Trim();
SetValue(prop, value);
}
}
}
public void Save()
{
try
{
using (var stream = new StreamWriter(File.Open(ActivePath, FileMode.Create, FileAccess.Write)))
{
stream.WriteLine("# FreeSO Settings File. Properties are self explanatory.");
var props = this.GetType().GetProperties();
foreach (var prop in props)
{
if (prop.Name == "Default" || prop.Name == "DefaultValues") continue;
stream.WriteLine(prop.Name + "=" + Convert.ToString(prop.GetValue(this), CultureInfo.InvariantCulture));
}
}
}
catch (Exception) { }
}
}
}

View file

@ -0,0 +1,7 @@
namespace FSO.Common.MeshSimplify
{
public struct MSRef
{
public int tid, tvertex;
}
}

View file

@ -0,0 +1,12 @@
using Microsoft.Xna.Framework;
namespace FSO.Common.MeshSimplify
{
public class MSTriangle
{
public int[] v = new int[3];
public double[] err = new double[4];
public bool deleted, dirty;
public Vector3 n;
}
}

View file

@ -0,0 +1,13 @@
using Microsoft.Xna.Framework;
namespace FSO.Common.MeshSimplify
{
public class MSVertex
{
public Vector3 p;
public Vector2 t; //texcoord
public int tstart, tcount;
public SymmetricMatrix q;
public bool border;
}
}

View file

@ -0,0 +1,403 @@
/*
* ==== Fast Quadratic Mesh Simplification ====
* Ported and extended from https://github.com/sp4cerat/Fast-Quadric-Mesh-Simplification/
*
* Typically used for simplifying meshes the 3D reconstruction generates.
*
*/
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
namespace FSO.Common.MeshSimplify
{
public class Simplify
{
public List<MSTriangle> triangles = new List<MSTriangle>();
public List<MSVertex> vertices = new List<MSVertex>();
public List<MSRef> refs = new List<MSRef>();
public void simplify_mesh(int target_count, double agressiveness = 7, int iterations = 100)
{
//for (int i=0; i<triangles.Count; i++) triangles[i].deleted = false;
// main iteration loop
int deleted_triangles = 0;
var deleted0 = new List<int>();
var deleted1 = new List<int>();
int triangle_count = triangles.Count;
for (int iteration=0; iteration<iterations; iteration++)
{
// target number of triangles reached ? Then break
if (triangle_count - deleted_triangles <= target_count) break;
// update mesh once in a while
if (iteration % 5 == 0)
{
update_mesh(iteration);
}
// clear dirty flag
for (var i=0; i < triangles.Count; i++)
triangles[i].dirty = false;
//
// All triangles with edges below the threshold will be removed
//
// The following numbers works well for most models.
// If it does not, try to adjust the 3 parameters
//
double threshold = 0.000000001 * Math.Pow((double)iteration + 3, agressiveness);
// remove vertices & mark deleted triangles
for (var i = 0; i < triangles.Count; i++)
{
var t = triangles[i];
if (t.err[3] > threshold) continue;
if (t.deleted) continue;
if (t.dirty) continue;
for (int j = 0; j < 3; j++)
{
if (t.err[j] < threshold)
{
int i0 = t.v[j]; var v0 = vertices[i0];
int i1 = t.v[(j + 1) % 3]; var v1 = vertices[i1];
// Border check
if (v0.border != v1.border) continue;
// Compute vertex to collapse to
Vector3 p = Vector3.Zero;
calculate_error(i0, i1, ref p);
deleted0.Clear(); // normals temporarily
for (int n = 0; n < v0.tcount; n++) deleted0.Add(0);
deleted1.Clear(); // normals temporarily
for (int n = 0; n < v1.tcount; n++) deleted1.Add(0);
// dont remove if flipped
if (flipped(p, i0, i1, v0, v1, deleted0)) continue;
if (flipped(p, i1, i0, v1, v0, deleted1)) continue;
// not flipped, so remove edge
var vec = v1.p - v0.p;
var vec2 = p - v0.p;
vec2 /= vec.Length();
vec /= vec.Length();
var lp = Vector3.Dot(vec, vec2);
v0.p = p;
v0.t = Vector2.Lerp(v0.t, v1.t, lp);
v0.q = v1.q + v0.q;
int tstart = refs.Count;
update_triangles(i0, v0, deleted0, ref deleted_triangles);
update_triangles(i0, v1, deleted1, ref deleted_triangles);
int tcount = refs.Count - tstart;
if (tcount <= v0.tcount)
{
// save ram
for (int tc=0; tc<tcount; tc++)
{
refs[v0.tstart + tc] = refs[tstart + tc];
}
}
else
// append
v0.tstart = tstart;
v0.tcount = tcount;
break;
}
}
// done?
if (triangle_count - deleted_triangles <= target_count) break;
}
}
// clean up mesh
compact_mesh();
}
// Check if a triangle flips when this edge is removed
bool flipped(Vector3 p, int i0, int i1, MSVertex v0, MSVertex v1, List<int> deleted)
{
int bordercount = 0;
for (int k=0; k<v0.tcount; k++)
{
var t = triangles[refs[v0.tstart + k].tid];
if (t.deleted) continue;
int s = refs[v0.tstart + k].tvertex;
int id1 = t.v[(s + 1) % 3];
int id2 = t.v[(s + 2) % 3];
if (id1 == i1 || id2 == i1) // delete ?
{
bordercount++;
deleted[k]=1;
continue;
}
Vector3 d1 = vertices[id1].p - p; d1.Normalize();
Vector3 d2 = vertices[id2].p - p; d2.Normalize();
if (Math.Abs(Vector3.Dot(d1, d2)) > 0.999) return true;
Vector3 n;
n = Vector3.Cross(d1, d2);
n.Normalize();
deleted[k] = 0;
if (Vector3.Dot(n, t.n) < 0.2) return true;
}
return false;
}
// Update triangle connections and edge error after a edge is collapsed
void update_triangles(int i0, MSVertex v, List<int> deleted, ref int deleted_triangles)
{
Vector3 p = Vector3.Zero;
for (int k = 0; k < v.tcount; k++)
{
var r = refs[v.tstart + k];
var t = triangles[r.tid];
if (t.deleted) continue;
if (k < deleted.Count && deleted[k] > 0)
{
t.deleted = true;
deleted_triangles++;
continue;
}
t.v[r.tvertex] = i0;
t.dirty = true;
t.err[0] = calculate_error(t.v[0], t.v[1], ref p);
t.err[1] = calculate_error(t.v[1], t.v[2], ref p);
t.err[2] = calculate_error(t.v[2], t.v[0], ref p);
t.err[3] = Math.Min(t.err[0], Math.Min(t.err[1], t.err[2]));
refs.Add(r);
}
}
// compact triangles, compute edge error and build reference list
void update_mesh(int iteration)
{
if (iteration > 0) // compact triangles
{
int dst = 0;
for (int i = 0; i<triangles.Count; i++) {
if (!triangles[i].deleted)
{
triangles[dst++] = triangles[i];
}
}
var c = triangles.Count;
triangles.RemoveRange(dst, c - dst);
}
//
// Init Quadrics by Plane & Edge Errors
//
// required at the beginning ( iteration == 0 )
// recomputing during the simplification is not required,
// but mostly improves the result for closed meshes
//
if (iteration == 0)
{
for (int i=0; i<vertices.Count; i++)
vertices[i].q = new SymmetricMatrix(0.0);
for (int i=0; i<triangles.Count; i++)
{
var t = triangles[i];
Vector3 n;
Vector3[] p = new Vector3[3];
for (int j=0; j<3; j++) p[j] = vertices[t.v[j]].p;
n = Vector3.Cross(p[1] - p[0], p[2] - p[0]);
n.Normalize();
t.n = n;
for (int j = 0; j < 3; j++) vertices[t.v[j]].q =
vertices[t.v[j]].q + new SymmetricMatrix(n.X, n.Y, n.Z, -Vector3.Dot(n,p[0]));
}
for (int i = 0; i < triangles.Count; i++)
{
// Calc Edge Error
var t = triangles[i]; Vector3 p = Vector3.Zero;
for (int j = 0; j < 3; j++) t.err[j] = calculate_error(t.v[j], t.v[(j + 1) % 3], ref p);
t.err[3] = Math.Min(t.err[0], Math.Min(t.err[1], t.err[2]));
}
}
// Init Reference ID list
for (int i = 0; i < vertices.Count; i++)
{
vertices[i].tstart = 0;
vertices[i].tcount = 0;
}
for (int i = 0; i < triangles.Count; i++)
{
var t = triangles[i];
for (int j = 0; j < 3; j++) vertices[t.v[j]].tcount++;
}
int tstart = 0;
for (int i = 0; i < vertices.Count; i++)
{
var v = vertices[i];
v.tstart = tstart;
tstart += v.tcount;
v.tcount = 0;
}
// Write References
refs.Clear();
for (int i = 0; i < triangles.Count * 3; i++)
refs.Add(new MSRef());
for (int i = 0; i < triangles.Count; i++)
{
var t = triangles[i];
for (int j = 0; j < 3; j++)
{
var v = vertices[t.v[j]];
refs[v.tstart + v.tcount] = new MSRef()
{
tid = i,
tvertex = j
};
v.tcount++;
}
}
// Identify boundary : vertices[].border=0,1
if (iteration == 0)
{
List<int> vcount = new List<int>();
List<int> vids = new List<int>();
for (int i = 0; i < vertices.Count; i++)
vertices[i].border = false;
for (int i = 0; i < vertices.Count; i++)
{
var v = vertices[i];
vcount.Clear();
vids.Clear();
for (int j = 0; j < v.tcount; j++)
{
int k = refs[v.tstart + j].tid;
var t = triangles[k];
for (k = 0; k < 3; k++)
{
int ofs = 0, id = t.v[k];
while (ofs < vcount.Count)
{
if (vids[ofs] == id) break;
ofs++;
}
if (ofs == vcount.Count)
{
vcount.Add(1);
vids.Add(id);
}
else
vcount[ofs]++;
}
}
for (int j = 0; j < vcount.Count; j++)
{
if (vcount[j] == 1)
vertices[vids[j]].border = true;
}
}
}
}
// Finally compact mesh before exiting
void compact_mesh()
{
int dst = 0;
for (int i = 0; i < vertices.Count; i++)
{
vertices[i].tcount = 0;
}
for (int i = 0; i < triangles.Count; i++)
{
if (!triangles[i].deleted)
{
var t = triangles[i];
triangles[dst++] = t;
for (int j = 0; j < 3; j++) vertices[t.v[j]].tcount = 1;
}
}
triangles.RemoveRange(dst, triangles.Count - dst);
dst = 0;
for (int i = 0; i < vertices.Count; i++)
{
if (vertices[i].tcount > 0)
{
vertices[i].tstart = dst;
vertices[dst].p = vertices[i].p;
vertices[dst].t = vertices[i].t;
dst++;
}
}
for (int i = 0; i < triangles.Count; i++)
{
var t = triangles[i];
for (int j = 0; j < 3; j++) t.v[j] = vertices[t.v[j]].tstart;
}
vertices.RemoveRange(dst, vertices.Count - dst);
}
// Error between vertex and Quadric
double vertex_error(SymmetricMatrix q, double x, double y, double z)
{
return q[0] * x * x + 2 * q[1] * x * y + 2 * q[2] * x * z + 2 * q[3] * x + q[4] * y * y
+ 2 * q[5] * y * z + 2 * q[6] * y + q[7] * z * z + 2 * q[8] * z + q[9];
}
// Error for one edge
double calculate_error(int id_v1, int id_v2, ref Vector3 p_result)
{
// compute interpolated vertex
SymmetricMatrix q = vertices[id_v1].q + vertices[id_v2].q;
bool border = vertices[id_v1].border && vertices[id_v2].border;
double error = 0;
double det = q.det(0, 1, 2, 1, 4, 5, 2, 5, 7);
if (det != 0 && !border)
{
// q_delta is invertible
p_result.X = (float)(-1 / det * (q.det(1, 2, 3, 4, 5, 6, 5, 7, 8))); // vx = A41/det(q_delta)
p_result.Y = (float)(1 / det * (q.det(0, 2, 3, 1, 5, 6, 2, 7, 8))); // vy = A42/det(q_delta)
p_result.Z = (float)(-1 / det * (q.det(0, 1, 3, 1, 4, 6, 2, 5, 8))); // vz = A43/det(q_delta)
error = vertex_error(q, p_result.X, p_result.Y, p_result.Z);
}
else
{
// det = 0 -> try to find best result
Vector3 p1 = vertices[id_v1].p;
Vector3 p2 = vertices[id_v2].p;
Vector3 p3 = (p1 + p2) / 2;
double error1 = vertex_error(q, p1.X, p1.Y, p1.Z);
double error2 = vertex_error(q, p2.X, p2.Y, p2.Z);
double error3 = vertex_error(q, p3.X, p3.Y, p3.Z);
error = Math.Min(error1, Math.Min(error2, error3));
if (error1 == error) p_result = p1;
if (error2 == error) p_result = p2;
if (error3 == error) p_result = p3;
}
return error;
}
}
}

View file

@ -0,0 +1,56 @@
namespace FSO.Common.MeshSimplify
{
public class SymmetricMatrix
{
public SymmetricMatrix(double c) {
for (int i=0; i<10; i++) m[i] = c;
}
public SymmetricMatrix(double m11, double m12, double m13, double m14,
double m22, double m23, double m24,
double m33, double m34,
double m44)
{
m[0] = m11; m[1] = m12; m[2] = m13; m[3] = m14;
m[4] = m22; m[5] = m23; m[6] = m24;
m[7] = m33; m[8] = m34;
m[9] = m44;
}
// Make plane
public SymmetricMatrix(double a, double b, double c, double d)
{
m[0] = a * a; m[1] = a * b; m[2] = a * c; m[3] = a * d;
m[4] = b * b; m[5] = b * c; m[6] = b * d;
m[7] = c * c; m[8] = c * d;
m[9] = d * d;
}
public double this[int c] {
get { return m[c]; }
set { m[c] = value; }
}
public double[] m = new double[10];
//determinant
public double det(int a11, int a12, int a13,
int a21, int a22, int a23,
int a31, int a32, int a33)
{
double det = m[a11] * m[a22] * m[a33] + m[a13] * m[a21] * m[a32] + m[a12] * m[a23] * m[a31]
- m[a13] * m[a22] * m[a31] - m[a11] * m[a23] * m[a32] - m[a12] * m[a21] * m[a33];
return det;
}
public static SymmetricMatrix operator +(SymmetricMatrix m, SymmetricMatrix n)
{
return new SymmetricMatrix(m[0] + n[0], m[1] + n[1], m[2] + n[2], m[3] + n[3],
m[4] + n[4], m[5] + n[5], m[6] + n[6],
m[7] + n[7], m[8] + n[8],
m[9] + n[9]);
}
}
}

View file

@ -0,0 +1,10 @@
namespace FSO.Common.Model
{
public class DynTuningEntry
{
public string tuning_type { get; set; }
public int tuning_table { get; set; }
public int tuning_index { get; set; }
public float value { get; set; }
}
}

View file

@ -0,0 +1,148 @@
using System.Collections.Generic;
using System.IO;
namespace FSO.Common.Model
{
public class DynamicTuning
{
//global tuning:
// city -
// 0: terrain
// 0: forceSnow (0/1/null)
public static DynamicTuning Global;
//string type/iff, int table, int index.
public Dictionary<string, Dictionary<int, Dictionary<int, float>>> Tuning = new Dictionary<string, Dictionary<int, Dictionary<int, float>>>();
public const int CURRENT_VERSION = 0;
public int Version = CURRENT_VERSION;
public void SerializeInto(BinaryWriter writer)
{
writer.Write(CURRENT_VERSION);
writer.Write(Tuning.Count);
foreach (var type in Tuning)
{
writer.Write(type.Key);
writer.Write(type.Value.Count);
foreach (var table in type.Value)
{
writer.Write(table.Key);
writer.Write(table.Value.Count);
foreach (var value in table.Value)
{
writer.Write(value.Key);
writer.Write(value.Value);
}
}
}
}
public DynamicTuning(IEnumerable<DynTuningEntry> entries)
{
foreach (var entry in entries)
{
AddTuning(entry);
}
}
public void AddTuning(DynTuningEntry entry)
{
Dictionary<int, Dictionary<int, float>> tables;
if (!Tuning.TryGetValue(entry.tuning_type, out tables))
{
tables = new Dictionary<int, Dictionary<int, float>>();
Tuning[entry.tuning_type] = tables;
}
Dictionary<int, float> data;
if (!tables.TryGetValue(entry.tuning_table, out data))
{
data = new Dictionary<int, float>();
tables[entry.tuning_table] = data;
}
data[entry.tuning_index] = entry.value;
}
public DynamicTuning(DynamicTuning old)
{
foreach (var type in Tuning)
{
var newType = new Dictionary<int, Dictionary<int, float>>();
foreach (var table in type.Value)
{
var newTable = new Dictionary<int, float>();
foreach (var value in table.Value)
{
newTable[value.Key] = value.Value;
}
newType[table.Key] = newTable;
}
Tuning[type.Key] = newType;
}
}
public DynamicTuning(BinaryReader reader)
{
Version = reader.ReadInt32();
var count = reader.ReadInt32();
for (int i=0; i<count; i++)
{
var key = reader.ReadString();
var count2 = reader.ReadInt32();
var newType = new Dictionary<int, Dictionary<int, float>>();
for (int j = 0; j < count2; j++)
{
var key2 = reader.ReadInt32();
var count3 = reader.ReadInt32();
var newTable = new Dictionary<int, float>();
for (int k=0; k<count3; k++)
{
var key3 = reader.ReadInt32();
newTable[key3] = reader.ReadSingle();
}
newType[key2] = newTable;
}
Tuning[key] = newType;
}
}
public Dictionary<int, float> GetTable(string type, int table)
{
Dictionary<int, Dictionary<int, float>> tables;
if (Tuning.TryGetValue(type, out tables))
{
Dictionary<int, float> data;
if (tables.TryGetValue(table, out data))
{
return data;
}
}
return null;
}
public Dictionary<int, Dictionary<int, float>> GetTables(string type)
{
Dictionary<int, Dictionary<int, float>> tables;
if (Tuning.TryGetValue(type, out tables))
{
return tables;
}
return null;
}
public float? GetTuning(string type, int table, int index)
{
Dictionary<int, Dictionary<int, float>> tables;
if (Tuning.TryGetValue(type, out tables))
{
Dictionary<int, float> data;
if (tables.TryGetValue(table, out data))
{
float result;
if (data.TryGetValue(index, out result))
return result;
}
}
return null;
}
}
}

View file

@ -0,0 +1,115 @@
using Microsoft.Xna.Framework;
namespace FSO.Common.Model
{
/// <summary>
/// A k-d Tree for looking up rectangle intersections
/// TODO: balancing? could make performance gains more stable at the cost of some of the worst case.
/// </summary>
public class IntersectRectTree
{
public IntersectRectNode Root;
public void Add(Rectangle rect)
{
if (Root == null)
{
Root = new IntersectRectNode
{
Dimension = IntersectRectDimension.Left,
Rect = rect
};
} else
{
Root.AddAsChild(rect);
}
}
public bool SearchForIntersect(Rectangle rect)
{
if (Root == null) return false;
else
{
return Root.SearchForIntersect(rect);
}
}
}
public class IntersectRectNode
{
public IntersectRectNode LeftChild;
public IntersectRectNode RightChild;
public IntersectRectDimension Dimension;
public Rectangle Rect;
public void AddAsChild(Rectangle rect)
{
bool rightSide = false;
switch (Dimension)
{
case IntersectRectDimension.Top:
rightSide = rect.Top > Rect.Top; break;
case IntersectRectDimension.Left:
rightSide = rect.Left > Rect.Left; break;
case IntersectRectDimension.Bottom:
rightSide = rect.Bottom > Rect.Bottom; break;
case IntersectRectDimension.Right:
rightSide = rect.Right > Rect.Right; break;
}
if (rightSide)
{
if (RightChild != null) RightChild.AddAsChild(rect);
else
{
RightChild = new IntersectRectNode
{
Dimension = (IntersectRectDimension)(((int)Dimension + 1) % 4),
Rect = rect
};
}
}
else
{
if (LeftChild != null) LeftChild.AddAsChild(rect);
else
{
LeftChild = new IntersectRectNode
{
Dimension = (IntersectRectDimension)(((int)Dimension + 1) % 4),
Rect = rect
};
}
}
}
public bool SearchForIntersect(Rectangle rect)
{
if (rect.Intersects(Rect)) return true;
//search in child nodes.
int dontSearch = 0;
switch (Dimension)
{
case IntersectRectDimension.Top:
dontSearch = (rect.Bottom < Rect.Top)?2:0; break; //if true, do not have to search right (where top greater)
case IntersectRectDimension.Left:
dontSearch = (rect.Right < Rect.Left)?2:0; break; //if true, do not have to search right (where left greater)
case IntersectRectDimension.Bottom:
dontSearch = (rect.Top > Rect.Bottom)?1:0; break; //if true, do not have to search left (where bottom less)
case IntersectRectDimension.Right:
dontSearch = (rect.Left > Rect.Right)?1:0; break; //if true, do not have to search left (where right less)
}
//may need to search both :'( won't happen often with our small rectangles over large space though.
return ((dontSearch != 1 && LeftChild != null && LeftChild.SearchForIntersect(rect))
|| (dontSearch != 2 && RightChild != null && RightChild.SearchForIntersect(rect)));
}
}
public enum IntersectRectDimension : byte
{
Top,
Left,
Bottom,
Right
}
}

View file

@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("TSO.Common")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("TSO.Common")]
[assembly: AssemblyCopyright("Copyright © 2013")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("f1dd298b-4150-4948-8cb2-dbba73e35dac")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View file

@ -0,0 +1,88 @@
using FSO.Common.Utils;
using Microsoft.Xna.Framework.Graphics;
using System;
namespace FSO.Common.Rendering
{
public class CachableTexture2D : Texture2D, ITimedCachable
{
/// <summary>
/// Creates a new texture of the given size
/// </summary>
/// <param name="graphicsDevice"></param>
/// <param name="width"></param>
/// <param name="height"></param>
public CachableTexture2D(GraphicsDevice graphicsDevice, int width, int height)
: base(graphicsDevice, width, height)
{
}
/// <summary>
/// Creates a new texture of a given size with a surface format and optional mipmaps
/// </summary>
/// <param name="graphicsDevice"></param>
/// <param name="width"></param>
/// <param name="height"></param>
/// <param name="mipmap"></param>
/// <param name="format"></param>
public CachableTexture2D(GraphicsDevice graphicsDevice, int width, int height, bool mipmap, SurfaceFormat format)
: base(graphicsDevice, width, height, mipmap, format)
{
}
/// <summary>
/// Creates a new texture array of a given size with a surface format and optional mipmaps.
/// Throws ArgumentException if the current GraphicsDevice can't work with texture arrays
/// </summary>
/// <param name="graphicsDevice"></param>
/// <param name="width"></param>
/// <param name="height"></param>
/// <param name="mipmap"></param>
/// <param name="format"></param>
/// <param name="arraySize"></param>
public CachableTexture2D(GraphicsDevice graphicsDevice, int width, int height, bool mipmap, SurfaceFormat format, int arraySize)
: base(graphicsDevice, width, height, mipmap, format, arraySize)
{
}
private bool WasReferenced = true;
public bool BeingDisposed = false;
private bool Resurrect = true;
public object Parent; //set if you want a parent object to be tied to this object (don't kill parent til we die)
~CachableTexture2D() {
if (!IsDisposed && !BeingDisposed)
{
//if we are disposed, there's no need to do anything.
if (WasReferenced)
{
TimedReferenceController.KeepAlive(this, KeepAliveType.DEREFERENCED);
WasReferenced = false;
GC.ReRegisterForFinalize(this);
Resurrect = true;
}
else
{
BeingDisposed = true;
GameThread.NextUpdate(x => this.Dispose());
GC.ReRegisterForFinalize(this);
Resurrect = true; //one more final
}
}
else { Resurrect = false; }
}
public void Rereferenced(bool saved)
{
WasReferenced = saved;
}
protected override void Dispose(bool disposing)
{
if (disposing) base.Dispose(disposing);
else if (Resurrect)
{
//in finalizer
GC.ReRegisterForFinalize(this);
}
}
}
}

View file

@ -0,0 +1,103 @@
using FSO.Common.Utils;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
namespace FSO.Common.Rendering.Emoji
{
public class EmojiCache
{
public string Source = "https://cdnjs.cloudflare.com/ajax/libs/twemoji/14.0.2/72x72/";
public int DefaultRes = 24;
public int Width = 32;
public int NextIndex = 0;
public List<string> Emojis = new List<string>();
public Dictionary<string, int> EmojiToIndex = new Dictionary<string, int>();
public RenderTarget2D EmojiTex;
public SpriteBatch EmojiBatch;
public HashSet<int> IncompleteSpaces = new HashSet<int>();
public HashSet<int> ErrorSpaces = new HashSet<int>();
private GraphicsDevice GD;
private bool needClear = true;
public EmojiCache(GraphicsDevice gd)
{
GD = gd;
EmojiBatch = new SpriteBatch(gd);
EmojiTex = new RenderTarget2D(gd, Width * DefaultRes, Width * DefaultRes, false, SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.PreserveContents);
ServicePointManager.Expect100Continue = true;
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11;
}
public void ExpandIfNeeded()
{
//todo
}
public Rectangle GetEmoji(string emojiID) {
int index;
if (EmojiToIndex.TryGetValue(emojiID, out index))
{
return RectForIndex(index);
} else
{
index = NextIndex++;
ExpandIfNeeded();
lock (IncompleteSpaces) IncompleteSpaces.Add(index);
var client = new WebClient();
client.DownloadDataCompleted += (object sender, DownloadDataCompletedEventArgs e) =>
{
if (e.Cancelled || e.Error != null || e.Result == null)
{
lock (ErrorSpaces) ErrorSpaces.Add(index);
} else
{
GameThread.NextUpdate(x =>
{
try
{
using (var mem = new MemoryStream(e.Result))
{
var tex = Texture2D.FromStream(GD, mem);
var decimated = TextureUtils.Decimate(tex, GD, 72 / DefaultRes, true);
//blit this into our emoji buffer
GD.SetRenderTarget(EmojiTex);
if (needClear)
{
GD.Clear(Color.TransparentBlack);
needClear = false;
}
EmojiBatch.Begin(blendState: BlendState.NonPremultiplied, sortMode: SpriteSortMode.Immediate);
EmojiBatch.Draw(decimated, RectForIndex(index), Color.White);
EmojiBatch.End();
GD.SetRenderTarget(null);
}
}
catch (Exception)
{
lock (ErrorSpaces) ErrorSpaces.Add(index);
}
});
}
lock (IncompleteSpaces) IncompleteSpaces.Remove(index);
};
client.DownloadDataAsync(new Uri((emojiID[0] == '!')?(emojiID.Substring(1)):(Source + emojiID + ".png")));
Emojis.Add(emojiID);
EmojiToIndex[emojiID] = index;
return RectForIndex(index);
}
}
private Rectangle RectForIndex(int index)
{
return new Rectangle((index % Width) * DefaultRes, (index / Width) * DefaultRes, DefaultRes, DefaultRes);
}
}
}

View file

@ -0,0 +1,95 @@
using Newtonsoft.Json.Linq;
using System.Collections.Generic;
using System.IO;
using System.Linq;
namespace FSO.Common.Rendering.Emoji
{
public class EmojiDictionary
{
public Dictionary<string, string> NameToEmojis = new Dictionary<string, string>();
public Dictionary<string, List<string>> KeywordToCandidates = new Dictionary<string, List<string>>();
public Dictionary<string, List<string>> CandidatesToKeywords = new Dictionary<string, List<string>>();
public EmojiDictionary()
{
JObject emojis;
using (var emojiDict = new StreamReader(
new FileStream(Path.Combine(FSOEnvironment.ContentDir, "UI/emojis.json"), FileMode.Open, FileAccess.Read, FileShare.Read)))
{
emojis = (JObject)Newtonsoft.Json.JsonConvert.DeserializeObject(emojiDict.ReadToEnd());
}
foreach (var token in emojis)
{
var charValue = token.Value.Value<string>("char");
var twemojiID = ToCodePoint(charValue);
NameToEmojis[token.Key] = twemojiID;
var keys = token.Value.Value<JArray>("keywords");
foreach (var key in keys)
AddKeyword(key.Value<string>(), token.Key);
}
using (var emojiDict = new StreamReader(
new FileStream(Path.Combine(FSOEnvironment.ContentDir, "UI/customemojis.json"), FileMode.Open, FileAccess.Read, FileShare.Read)))
{
emojis = (JObject)Newtonsoft.Json.JsonConvert.DeserializeObject(emojiDict.ReadToEnd());
}
foreach (var token in emojis)
{
NameToEmojis[token.Key] = "!" + token.Value.Value<string>("url");
var keys = token.Value.Value<JArray>("keywords");
foreach (var key in keys)
AddKeyword(key.Value<string>(), token.Key);
}
}
public void AddKeyword(string keyword, string candidate)
{
List<string> cand;
if (!KeywordToCandidates.TryGetValue(keyword, out cand))
{
cand = new List<string>();
KeywordToCandidates[keyword] = cand;
}
cand.Add(candidate);
if (!CandidatesToKeywords.TryGetValue(candidate, out cand))
{
cand = new List<string>();
CandidatesToKeywords[candidate] = cand;
}
cand.Add(keyword);
}
public string ToCodePoint(string str)
{
var cs = str.ToCharArray();
var i = 0;
var c = 0;
var p = 0;
var r = new List<string>();
var zeroWidth = str.Any(x => x == '\x200D');
while (i < cs.Length)
{
c = cs[i++];
if (c == 0xfe0f && !zeroWidth) continue; //"as image", just ignore this
if (p > 0)
{
r.Add((0x10000 + ((p - 0xD800) << 10) + (c - 0xDC00)).ToString("x"));
p = 0;
}
else if (0xD800 <= c && c <= 0xDBFF)
{
p = c;
}
else
{
r.Add(c.ToString("x"));
}
}
return string.Join("-", r);
}
}
}

View file

@ -0,0 +1,232 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Text;
namespace FSO.Common.Rendering.Emoji
{
public class EmojiProvider
{
public EmojiDictionary Dict;
public EmojiCache Cache;
public Random DictRand = new Random();
public Dictionary<string, string> TranslateShortcuts = new Dictionary<string, string>()
{
{ "i", "eye" },
{ "you", "point_right" },
{ "your", "point_right" },
{ "you're", "point_right" },
{ "me", "relieved: :point_left" },
{ "i'm", "relieved: :point_left" },
{ "us", "couple" },
{ "we", "couple" },
{ "our", "couple" },
{ "what", "woman_shrugging" },
{ "be", "b" },
{ "to", "two" },
{ "and", "symbols" },
{ "that", "point_up" },
{ "for", "four" },
{ "not", "exclamation" },
{ "this", "point_up" },
{ "but", "exclamation" },
{ "his", "man" },
{ "her", "woman" },
{ "him", "man" },
{ "he", "man" },
{ "she", "woman" },
{ "from", "postbox" },
{ "they", "family_man_woman_girl_boy" },
{ "them", "family_man_woman_girl_boy" },
{ "or", "thinking" },
{ "an", "a" },
{ "will", "thinking" },
{ "my", "relieved: :point_left" },
{ "all", "rainbow_flag" },
{ "would", "tree" },
{ "so", "woman_shrugging" },
{ "out", "outbox_tray" },
{ "if", "thinking" },
{ "about", "arrows_counterclockwise" },
{ "who", "thinking: :family_man_woman_girl_boy" },
{ "get", "gift" },
{ "which", "woman_shrugging" },
{ "go", "door" },
{ "when", "watch" },
{ "make", "toolbox" },
{ "know", "brain" },
{ "take", "takeout_box" },
{ "into", "arrow_heading_down" },
{ "year", "calendar" },
{ "because", "woman_shrugging" },
{ "hmm", "thinking" },
{ "yo", "wave" },
{ "hey", "wave" },
{ "sup", "wave" },
};
public EmojiProvider(GraphicsDevice gd)
{
Dict = new EmojiDictionary();
Cache = new EmojiCache(gd);
}
public string EmojiFromName(string name)
{
string result;
if (Dict.NameToEmojis.TryGetValue(name, out result))
return result;
return null;
}
private string StripPunctuation(string word, out string punctuation)
{
for (int i=word.Length-1; i>=0; i--)
{
if (!char.IsPunctuation(word[i]))
{
punctuation = word.Substring(i + 1);
return word.Substring(0, i + 1);
}
}
punctuation = "";
return word;
}
public string SearchForWordHit(string word)
{
string direct;
if (TranslateShortcuts.TryGetValue(word, out direct))
{
return ":" + direct + ":";
}
if (Dict.NameToEmojis.TryGetValue(word, out direct))
{
return ":" + word + ":";
}
List<string> options;
if (Dict.KeywordToCandidates.TryGetValue(word, out options))
{
return ":" + options[DictRand.Next(options.Count)] + ":";
}
return null;
}
public string TranslateWordToEmoji(string word)
{
string punctuation;
var lower = StripPunctuation(word.ToLowerInvariant(), out punctuation);
string result;
result = SearchForWordHit(lower);
if (result != null) return result + ' ' + punctuation;
if (lower.EndsWith("s"))
{
result = SearchForWordHit(lower.Substring(0, lower.Length-1));
if (result != null) return result + ' ' + punctuation;
}
if (lower.EndsWith("ing"))
{
result = SearchForWordHit(lower.Substring(0, lower.Length - 3));
if (result != null) return result + ' ' + punctuation;
}
return word.Substring(0, lower.Length) + punctuation;
}
public Tuple<Texture2D, Rectangle> GetEmoji(string id)
{
var rect = Cache.GetEmoji(id);
return new Tuple<Texture2D, Rectangle>(Cache.EmojiTex, rect);
}
public string EmojiToBB(string input)
{
//search through the string for emojis to turn to BBcode
int index = 0;
int lastColon = -1;
var result = new StringBuilder();
while (true)
{
var nColon = input.IndexOf(':', index);
if (nColon == -1) break;
if (lastColon == -1) result.Append(input.Substring(index, nColon - index)); //add up to the colon
else
{
//is the string between the two colons an emoji?
var emoji = EmojiFromName(input.Substring(lastColon + 1, nColon - (lastColon + 1)));
if (emoji == null)
{
result.Append(":"+input.Substring(index, nColon - index)); //add up to the colon (include the last colon we skipped)
} else
{
result.Append("[emoji=" + emoji + "] ");
lastColon = -1;
index = nColon + 1;
continue;
}
}
index = nColon + 1;
lastColon = nColon;
}
result.Append(((lastColon == -1) ? "" : ":") + input.Substring(index));
return result.ToString();
}
public string EmojiTranslate(string input)
{
//search for replacement candidates for each word in input
var words = input.Split(' ');
for (int i=0; i<words.Length; i++)
{
var word = words[i];
if (word == "") continue;
if (word.Length > 2 && word.StartsWith(":") && word.EndsWith(":"))
{
//is this already an emoji? if so, skip it
var existing = EmojiFromName(word.Substring(1, word.Length-2));
if (existing == null) continue;
}
words[i] = TranslateWordToEmoji(word);
}
return String.Join(" ", words);
}
public string EmojiOnly(string input, int mode)
{
if (mode == 2) return EmojiTranslate(input);
//search through the string for emojis to keep
int index = 0;
int lastColon = -1;
var result = new StringBuilder();
while (true)
{
var nColon = input.IndexOf(':', index);
if (nColon == -1) break;
else
{
//is the string between the two colons an emoji?
var name = input.Substring(lastColon + 1, nColon - (lastColon + 1));
var emoji = EmojiFromName(name);
if (emoji == null)
{
}
else
{
result.Append(":" + name + ": ");
lastColon = -1;
index = nColon + 1;
continue;
}
}
index = nColon + 1;
lastColon = nColon;
}
//result.Append(((lastColon == -1) ? "" : ":"));
return result.ToString();
}
}
}

View file

@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework.Graphics;
using FSO.Common.Rendering.Framework.Camera;
using FSO.Common.Rendering.Framework.Model;
namespace FSO.Common.Rendering.Framework
{
/// <summary>
/// Base class for scenes with 3D elements.
/// </summary>
public abstract class _3DAbstract : IDisposable
{
public ICamera Camera;
public string ID;
public bool Visible = true;
public abstract List<_3DComponent> GetElements();
public abstract void Add(_3DComponent item);
public abstract void Update(UpdateState Time);
public abstract void Draw(GraphicsDevice device);
protected _3DLayer Parent;
private EventHandler<EventArgs> ResetEvent;
public virtual void PreDraw(GraphicsDevice device)
{
}
public virtual void Initialize(_3DLayer layer)
{
Parent = layer;
}
/// <summary>
/// Creates a new _3DAbstract instance.
/// </summary>
/// <param name="Device">A GraphicsDevice instance.</param>
public _3DAbstract(GraphicsDevice Device)
{
m_Device = Device;
ResetEvent = new EventHandler<EventArgs>(m_Device_DeviceReset);
m_Device.DeviceReset += ResetEvent;
}
/// <summary>
/// Called when m_Device is reset.
/// </summary>
private void m_Device_DeviceReset(object sender, EventArgs e)
{
DeviceReset(m_Device);
}
protected GraphicsDevice m_Device;
public abstract void DeviceReset(GraphicsDevice Device);
public static bool IsInvalidated;
public object Controller { get; internal set; }
public void SetController(object controller)
{
this.Controller = controller;
}
public T FindController<T>()
{
if(Controller is T)
{
return (T)Controller;
}
return default(T);
}
public virtual void Dispose()
{
if (m_Device != null) m_Device.DeviceReset -= ResetEvent;
}
}
}

View file

@ -0,0 +1,161 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using FSO.Common.Rendering.Framework.Model;
namespace FSO.Common.Rendering.Framework
{
public abstract class _3DComponent
{
public _3DScene Scene;
public _3DComponent()
{
}
private Vector3 m_Position = Vector3.Zero;
private Vector3 m_Scale = Vector3.One;
private float m_RotateX = 0.0f;
private float m_RotateY = 0.0f;
private float m_RotateZ = 0.0f;
/// <summary>
/// Gets or the GraphicsDevice instance for this component.
/// </summary>
public GraphicsDevice Device
{
get
{
return Scene.Parent.Device;
}
}
/// <summary>
/// The X component of this 3DComponent's rotation.
/// </summary>
public float RotationX
{
get { return m_RotateX; }
set
{
m_RotateX = value;
m_WorldDirty = true;
}
}
/// <summary>
/// The Y component of this 3DComponent's rotation.
/// </summary>
public float RotationY
{
get { return m_RotateY; }
set
{
m_RotateY = value;
m_WorldDirty = true;
}
}
/// <summary>
/// The Z component of this 3DComponent's rotation.
/// </summary>
public float RotationZ
{
get { return m_RotateZ; }
set
{
m_RotateZ = value;
m_WorldDirty = true;
}
}
/// <summary>
/// This 3DComponent's position.
/// </summary>
public Vector3 Position
{
get { return m_Position; }
set
{
m_Position = value;
m_WorldDirty = true;
}
}
/// <summary>
/// This 3DComponent's scale.
/// </summary>
public Vector3 Scale
{
get { return m_Scale; }
set
{
m_Scale = value;
m_WorldDirty = true;
}
}
private Matrix m_World = Matrix.Identity;
private bool m_WorldDirty = false;
public Matrix World
{
get
{
if (m_WorldDirty)
{
m_World = Matrix.CreateRotationX(m_RotateX) * Matrix.CreateRotationY(m_RotateY) * Matrix.CreateRotationZ(m_RotateZ) * Matrix.CreateScale(m_Scale) * Matrix.CreateTranslation(m_Position);
m_WorldDirty = false;
}
return m_World;
}
}
private string m_StringID;
public string ID
{
get { return m_StringID; }
set { m_StringID = value; }
}
public virtual void Initialize()
{
}
public abstract void Update(UpdateState state);
public abstract void Draw(GraphicsDevice device);
/// <summary>
/// GraphicsDevice was reset.
/// </summary>
public abstract void DeviceReset(GraphicsDevice Device);
public override string ToString()
{
if (m_StringID != null)
{
return m_StringID;
}
return base.ToString();
}
/// <summary>
/// This 3DComponent's camera's view.
/// </summary>
protected Matrix View
{
get
{
return Scene.Camera.View;
}
}
/// <summary>
/// This 3DComponent's camera's projection.
/// </summary>
protected Matrix Projection
{
get
{
return Scene.Camera.Projection;
}
}
}
}

View file

@ -0,0 +1,99 @@
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework.Graphics;
namespace FSO.Common.Rendering.Framework
{
public class _3DLayer : IGraphicsLayer
{
public GraphicsDevice Device;
public List<_3DAbstract> Scenes = new List<_3DAbstract>();
public List<_3DAbstract> External = new List<_3DAbstract>();
#region IGraphicsLayer Members
public void Update(FSO.Common.Rendering.Framework.Model.UpdateState state)
{
foreach (var scene in Scenes)
{
scene.Update(state);
}
foreach (var scene in External)
{
scene.Update(state);
}
}
public void PreDraw(Microsoft.Xna.Framework.Graphics.GraphicsDevice device)
{
foreach (var scene in Scenes)
{
if (scene.Visible) scene.PreDraw(device);
}
}
public void Draw(Microsoft.Xna.Framework.Graphics.GraphicsDevice device)
{
foreach (var scene in Scenes)
{
if (scene.Visible) scene.Draw(device);
}
}
public void Initialize(Microsoft.Xna.Framework.Graphics.GraphicsDevice device)
{
this.Device = device;
foreach (var scene in Scenes)
{
scene.Initialize(this);
}
foreach (var scene in External)
{
scene.Initialize(this);
}
}
public void Add(_3DAbstract scene)
{
Scenes.Add(scene);
if (this.Device != null)
{
scene.Initialize(this);
}
}
public void Remove(_3DAbstract scene)
{
Scenes.Remove(scene);
}
public void Clear()
{
foreach (var scene in Scenes)
{
if (scene is IDisposable) ((IDisposable)scene).Dispose();
}
Scenes.Clear();
}
/// <summary>
/// Adds a scene to the draw stack. The system will not call
/// Draw on the scene but it will be initialized and given updates
/// </summary>
/// <param name="scene"></param>
public void AddExternal(_3DAbstract scene){
External.Add(scene);
if (this.Device != null)
{
scene.Initialize(this);
}
}
public void RemoveExternal(_3DAbstract scene)
{
External.Remove(scene);
}
#endregion
}
}

View file

@ -0,0 +1,125 @@
using System;
using System.Collections.Generic;
using FSO.Common.Rendering.Framework.Camera;
using Microsoft.Xna.Framework.Graphics;
using FSO.Common.Rendering.Framework.Model;
namespace FSO.Common.Rendering.Framework
{
/// <summary>
/// A scene capable of rendering 3D elements.
/// </summary>
public class _3DScene : _3DAbstract
{
private List<_3DComponent> m_Elements = new List<_3DComponent>();
public new _3DLayer Parent;
/// <summary>
/// Creates a new _3DScene instance.
/// </summary>
/// <param name="Device">A GraphicsDevice instance used for rendering.</param>
/// <param name="camera">A camera inheriting from ICamera used for rendering.</param>
public _3DScene(GraphicsDevice Device, ICamera camera) : base(Device)
{
this.Camera = camera;
}
/// <summary>
/// Creates a new _3DScene instance.
/// </summary>
/// <param name="Device">A GraphicsDevice instance used for rendering.</param>
public _3DScene(GraphicsDevice Device) : base(Device)
{
}
/// <summary>
/// Graphics device was reset (happens when scene is updated or minimized.)
/// </summary>
void m_Device_DeviceReset(object sender, EventArgs e)
{
throw new NotImplementedException();
}
/// <summary>
/// Returns the _3DComponents that make up this scene.
/// </summary>
/// <returns>A List of _3DComponent instances.</returns>
public override List<_3DComponent> GetElements()
{
return m_Elements;
}
public override void Initialize(_3DLayer layer)
{
this.Parent = layer;
foreach (var element in m_Elements)
{
element.Initialize();
}
}
public override void Update(UpdateState state)
{
for (int i = 0; i < m_Elements.Count; i++)
{
m_Elements[i].Update(state);
}
}
/// <summary>
/// Removes a 3D element from this 3DScene.
/// </summary>
/// <param name="item">The _3DComponent instance to remove.</param>
public void Remove(_3DComponent item)
{
m_Elements.Remove(item);
}
/// <summary>
/// Adds a 3D element to this 3DScene.
/// </summary>
/// <param name="item">The _3DComponent instance to add.</param>
public override void Add(_3DComponent item)
{
m_Elements.Add(item);
item.Scene = this;
if (this.Parent != null)
{
item.Initialize();
}
}
public override void PreDraw(GraphicsDevice device){
}
public override void Draw(GraphicsDevice device)
{
for (int i = 0; i < m_Elements.Count; i++)
{
m_Elements[i].Draw(device);
}
}
public override string ToString()
{
if (ID != null)
{
return ID;
}
return base.ToString();
}
/// <summary>
/// GraphicsDevice was reset.
/// </summary>
/// <param name="Device">The GraphicsDevice instance.</param>
public override void DeviceReset(GraphicsDevice Device)
{
for (int i = 0; i < m_Elements.Count; i++)
m_Elements[i].DeviceReset(Device);
}
}
}

View file

@ -0,0 +1,44 @@
using FSO.Common.Rendering.Framework.Camera;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace FSO.Common.Rendering.Framework
{
public class _3DTargetScene : _3DScene
{
public RenderTarget2D Target;
private GraphicsDevice Device;
private int Multisample = 0;
public Color ClearColor = Color.Transparent;
public _3DTargetScene(GraphicsDevice device, ICamera camera, Point size, int multisample) : this(device, size, multisample) { Camera = camera; }
public _3DTargetScene(GraphicsDevice device, Point size, int multisample) : base(device)
{
Device = device;
Multisample = multisample;
SetSize(size);
}
public void SetSize(Point size)
{
if (Target != null) Target.Dispose();
Target = new RenderTarget2D(Device, size.X, size.Y, false, SurfaceFormat.Color, DepthFormat.Depth24Stencil8, Multisample, RenderTargetUsage.PreserveContents);
}
public override void Draw(GraphicsDevice device)
{
var oldTargets = device.GetRenderTargets();
device.SetRenderTarget(Target);
device.Clear(ClearColor);
device.DepthStencilState = DepthStencilState.Default;
Camera.ProjectionDirty();
base.Draw(device);
device.SetRenderTargets(oldTargets);
}
public override void Dispose()
{
base.Dispose();
Target.Dispose();
}
}
}

View file

@ -0,0 +1,273 @@
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.ComponentModel;
namespace FSO.Common.Rendering.Framework.Camera
{
/// <summary>
/// A basic camera for the game.
/// </summary>
[DisplayName("BasicCamera")]
public class BasicCamera : ICamera
{
public float NearPlane { get; set; }
public float FarPlane { get; set; }
public float AspectRatioMultiplier { get; set; }
protected Vector3 m_Position;
protected Vector3 m_Target;
protected Vector3 m_Up;
protected GraphicsDevice m_Device;
/// <summary>
/// Creates a new BasicCamera instance. Assumes projection is full screen!
/// </summary>
/// <param name="device">A GraphicsDevice instance used for rendering.</param>
/// <param name="Position">Camera's initial position.</param>
/// <param name="Target">Camera's initial target.</param>
/// <param name="Up">Camera's initial up vector.</param>
public BasicCamera(GraphicsDevice device, Vector3 Position, Vector3 Target, Vector3 Up)
{
m_Device = device;
AspectRatioMultiplier = 1.0f;
NearPlane = 1.0f;
FarPlane = 800.0f;
m_Position = Position;
m_Target = Target;
m_Up = Up;
m_ViewDirty = true;
/**
* Assume the projection is full screen, center origin
*/
ProjectionOrigin = new Vector2(
m_Device.Viewport.Width / 2.0f,
m_Device.Viewport.Height / 2.0f
);
}
protected Vector2 m_ProjectionOrigin = Vector2.Zero;
/// <summary>
/// Gets or sets this BasicCamera's projection origin.
/// </summary>
public Vector2 ProjectionOrigin
{
get
{
return m_ProjectionOrigin;
}
set
{
m_ProjectionOrigin = value;
m_ProjectionDirty = true;
}
}
protected Matrix m_Projection;
protected bool m_ProjectionDirty;
public void ProjectionDirty()
{
m_ProjectionDirty = true;
}
/// <summary>
/// Gets this camera's projection.
/// </summary>
[Browsable(false)]
public Matrix Projection
{
get
{
if (m_ProjectionDirty)
{
CalculateProjection();
m_ProjectionDirty = false;
}
return m_Projection;
}
}
private float _FOV = (float)Math.PI / 4f;
public float FOV
{
get
{
return _FOV;
}
set
{
_FOV = value;
ProjectionDirty();
}
}
protected virtual void CalculateProjection()
{
var device = m_Device;
var aspect = device.Viewport.AspectRatio * AspectRatioMultiplier;
var ratioX = m_ProjectionOrigin.X / device.Viewport.Width;
var ratioY = m_ProjectionOrigin.Y / device.Viewport.Height;
var projectionX = 0.0f - (1.0f * ratioX);
var projectionY = (1.0f * ratioY);
m_Projection = Matrix.CreatePerspectiveFieldOfView(FOV, aspect, NearPlane, FarPlane);
/*m_Projection = Matrix.CreatePerspectiveOffCenter(
projectionX, projectionX + 1.0f,
((projectionY-1.0f) / aspect), (projectionY) / aspect,
NearPlane, FarPlane
);*/
m_Projection = Matrix.CreateScale(Zoom, Zoom, 1.0f) * m_Projection;
}
protected virtual void CalculateView()
{
var translate = Matrix.CreateTranslation(m_Translation);
var position = Vector3.Transform(m_Position, translate);
var target = Vector3.Transform(m_Target, translate);
m_View = Matrix.CreateLookAt(position, target, m_Up);
}
protected bool m_ViewDirty = false;
protected Matrix m_View = Matrix.Identity;
[Browsable(false)]
public Matrix View
{
get
{
if (m_ViewDirty)
{
m_ViewDirty = false;
CalculateView();
}
return m_View;
}
}
protected float m_Zoom = 1.0f;
/// <summary>
/// Gets or sets this BasicCamera's zoom level.
/// </summary>
public float Zoom
{
get { return m_Zoom; }
set
{
m_Zoom = value;
m_ViewDirty = true;
m_ProjectionDirty = true;
}
}
protected Vector3 m_Translation;
/// <summary>
/// Gets or sets this BasicCamera's translation.
/// </summary>
public Vector3 Translation
{
get
{
return m_Translation;
}
set
{
m_Translation = value;
m_ViewDirty = true;
}
}
/// <summary>
/// Gets or sets this BasicCamera's position.
/// </summary>
public Vector3 Position
{
get
{
return m_Position;
}
set
{
m_Position = value;
m_ViewDirty = true;
}
}
/// <summary>
/// Gets or sets this BasicCamera's target.
/// </summary>
public Vector3 Target
{
get
{
return m_Target;
}
set
{
m_Target = value;
m_ViewDirty = true;
}
}
/// <summary>
/// Gets or sets this BasicCamera's up vector.
/// </summary>
public Vector3 Up
{
get
{
return m_Up;
}
set
{
m_Up = value;
m_ViewDirty = true;
}
}
public bool DrawCamera = false;
public void Draw(GraphicsDevice device)
{
/*
device.RasterizerState.PointSize = 30.0f;
device.VertexDeclaration = new VertexDeclaration(device, VertexPositionColor.VertexElements);
var effect = new BasicEffect(device);
effect.World = Matrix.Identity;
effect.View = View;
effect.Projection = Projection;
effect.VertexColorEnabled = true;
foreach (var pass in effect.Techniques[0].Passes)
{
pass.Apply();
var vertex = new VertexPositionColor(Position, Color.Green);
var vertexList = new VertexPositionColor[1] { vertex };
device.DrawUserPrimitives(PrimitiveType.PointList, vertexList, 0, 1);
vertex.Color = Color.Red;
vertex.Position = Target;
device.DrawUserPrimitives(PrimitiveType.PointList, vertexList, 0, 1);
}
* XNA4 no longer has support for point primitives.
*/
}
}
}

View file

@ -0,0 +1,24 @@
using Microsoft.Xna.Framework;
namespace FSO.Common.Rendering.Framework.Camera
{
public interface ICamera
{
Matrix View { get; }
Matrix Projection { get; }
Vector3 Position { get; set; }
Vector3 Target { get; set; }
Vector3 Up { get; set; }
Vector3 Translation { get; set; }
Vector2 ProjectionOrigin { get; set; }
float NearPlane { get; set; }
float FarPlane { get; set; }
float Zoom { get; set; }
float AspectRatioMultiplier { get; set; }
void ProjectionDirty();
}
}

View file

@ -0,0 +1,35 @@
namespace FSO.Common.Rendering.Framework.Camera
{
public class ManualCamera : ICamera
{
#region ICamera Members
public Microsoft.Xna.Framework.Matrix View { get; set; }
public Microsoft.Xna.Framework.Matrix Projection { get; set; }
public Microsoft.Xna.Framework.Vector3 Position { get; set; }
public Microsoft.Xna.Framework.Vector3 Target { get; set; }
public Microsoft.Xna.Framework.Vector3 Up { get; set; }
public Microsoft.Xna.Framework.Vector3 Translation { get; set; }
public Microsoft.Xna.Framework.Vector2 ProjectionOrigin { get; set; }
public float NearPlane { get; set; }
public float FarPlane { get; set; }
public float Zoom { get; set; }
public float AspectRatioMultiplier { get; set; }
public void ProjectionDirty()
{
}
#endregion
}
}

View file

@ -0,0 +1,37 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace FSO.Common.Rendering.Framework.Camera
{
/// <summary>
/// Orthographic camera for the game. Used for rendering lots.
/// </summary>
public class OrthographicCamera : BasicCamera
{
public OrthographicCamera(GraphicsDevice device, Vector3 Position, Vector3 Target, Vector3 Up)
: base(device, Position, Target, Up)
{
}
protected override void CalculateProjection()
{
var device = m_Device;
var aspect = device.Viewport.AspectRatio * AspectRatioMultiplier;
var ratioX = m_ProjectionOrigin.X / device.Viewport.Width;
var ratioY = m_ProjectionOrigin.Y / device.Viewport.Height;
var projectionX = 0.0f - (1.0f * ratioX);
var projectionY = (1.0f * ratioY);
m_Projection = Matrix.CreateOrthographicOffCenter(
projectionX, projectionX + 1.0f,
((projectionY - 1.0f) / aspect), (projectionY) / aspect,
NearPlane, FarPlane
);
var zoom = 1 / m_Zoom;
m_Projection = m_Projection * Matrix.CreateScale(zoom);
}
}
}

View file

@ -0,0 +1,137 @@
using System.Collections.Generic;
using System.IO;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics;
using FSO.Common.Utils;
namespace FSO.Common.Rendering.Framework
{
public enum CursorType
{
Normal,
ArrowUp,
ArrowUpLeft,
ArrowUpRight,
ArrowDown,
ArrowDownLeft,
ArrowDownRight,
ArrowLeft,
ArrowRight,
LiveNothing,
LiveObjectUnavail,
LivePerson,
IBeam,
SimsRotate,
SimsRotateNE,
SimsRotateSE,
SimsRotateSW,
SimsRotateNW,
SimsMove,
SimsPlace,
Hourglass,
LiveObjectAvail,
LiveObject1Star,
LiveObject2Star,
LiveObject3Star,
LiveObject4Star,
LiveObject5Star,
LiveObjectSpecial,
}
/// <summary>
/// Manages cursors in the game.
/// </summary>
public class CursorManager
{
public static CursorManager INSTANCE;
private Dictionary<CursorType, MouseCursor> m_CursorMap;
private GraphicsDevice GD;
public CursorType CurrentCursor { get; internal set;} = CursorType.Normal;
public CursorManager(GraphicsDevice gd)
{
INSTANCE = this;
m_CursorMap = new Dictionary<CursorType, MouseCursor>();
this.GD = gd;
}
public void SetCursor(CursorType type)
{
if (m_CursorMap.ContainsKey(type))
{
CurrentCursor = type;
Mouse.SetCursor(m_CursorMap[type]);
}
}
public Dictionary<CursorType, string> GenMap()
{
return new Dictionary< CursorType, string> (){
//{CursorType.Normal, "arrow.cur"},
{ CursorType.ArrowUp, "up.cur"},
{ CursorType.ArrowUpLeft, "upleft.cur"},
{ CursorType.ArrowUpRight, "upright.cur"},
{ CursorType.ArrowDown, "down.cur"},
{ CursorType.ArrowDownLeft, "downleft.cur"},
{ CursorType.ArrowDownRight, "downright.cur"},
{ CursorType.ArrowLeft, "left.cur"},
{ CursorType.ArrowRight, "right.cur"},
{ CursorType.LiveNothing, "livenothing.cur"},
{ CursorType.LiveObjectAvail, "liveobjectavail.cur"},
{ CursorType.LiveObjectUnavail, "liveobjectunavail.cur"},
{ CursorType.LivePerson, "liveperson.cur"},
{ CursorType.SimsRotate, "simsrotate.cur" },
{ CursorType.SimsRotateNE, "simsrotatene.cur" },
{ CursorType.SimsRotateNW, "simsrotatenw.cur" },
{ CursorType.SimsRotateSE, "simsrotatese.cur" },
{ CursorType.SimsRotateSW, "simsrotatesw.cur" },
{ CursorType.SimsMove, "simsmove.cur" },
{ CursorType.SimsPlace, "simsplace.cur" },
{ CursorType.Hourglass, "hourglass.cur" }
};
}
public void Init(string basepath, bool ts1)
{
var map = GenMap();
var curPath = "UIGraphics/Shared/cursors/";
if (!ts1) curPath = curPath.ToLowerInvariant();
foreach (var item in map)
{
m_CursorMap.Add(item.Key,
LoadCustomCursor(
Path.Combine(basepath, curPath, item.Value)
));
}
var starMax = 5;
var stars = LoadUpgradeCursors(Path.Combine(basepath, curPath, "liveobjectavail.cur"), starMax);
for (int i=0; i<starMax; i++)
{
m_CursorMap.Add(CursorType.LiveObject1Star + i, stars[i]);
}
m_CursorMap.Add(CursorType.IBeam, MouseCursor.IBeam);
//m_CursorMap.Add(CursorType.Hourglass, MouseCursor.Wait);
m_CursorMap.Add(CursorType.Normal, MouseCursor.Arrow);
}
private MouseCursor[] LoadUpgradeCursors(string path, int maxStars)
{
return CurLoader.LoadUpgradeCursors(GD, File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read), maxStars);
}
private MouseCursor LoadCustomCursor(string path)
{
return CurLoader.LoadMonoCursor(GD, File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read));
}
}
}

View file

@ -0,0 +1,30 @@
using Microsoft.Xna.Framework;
namespace FSO.Common.Rendering.Framework
{
public abstract class Game : Microsoft.Xna.Framework.Game
{
protected GraphicsDeviceManager Graphics;
protected GameScreen Screen;
public Game() : base()
{
Graphics = new GraphicsDeviceManager(this);
}
protected override void Initialize(){
base.Initialize();
Screen = new GameScreen(GraphicsDevice);
}
protected override void Update(GameTime gameTime){
Screen.Update(gameTime, IsActive);
}
protected override void Draw(GameTime gameTime){
base.Draw(gameTime);
Screen.Draw(gameTime);
}
}
}

View file

@ -0,0 +1,225 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using FSO.Common.Rendering.Framework.Model;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
namespace FSO.Common.Rendering.Framework
{
/// <summary>
/// A screen used for drawing.
/// </summary>
public class GameScreen
{
public List<IGraphicsLayer> Layers = new List<IGraphicsLayer>();
public GraphicsDevice Device;
public UpdateState State;
public static Color ClearColor = new Color(0x72, 0x72, 0x72);
private int touchedFrames;
private int lastTouchCount;
private MouseState lastMouseState;
private Vector2? prevTouchAvg;
private const int TOUCH_ACCEPT_TIME = 5;
public GameScreen(GraphicsDevice device)
{
this.Device = device;
State = new UpdateState();
}
private static List<char> TextCharacters = new List<char>();
public static void TextInput(object sender, TextInputEventArgs e)
{
TextCharacters.Add(e.Character);
}
/// <summary>
/// Adds a graphical element to this scene.
/// </summary>
/// <param name="layer">Element inheriting from IGraphicsLayer.</param>
public void Add(IGraphicsLayer layer)
{
layer.Initialize(Device);
Layers.Add(layer);
}
public void Update(GameTime time, bool hasFocus)
{
State.Time = time;
State.PreviousKeyboardState = State.KeyboardState;
State.FrameTextInput = TextCharacters;
var touchMode = FSOEnvironment.SoftwareKeyboard;
if (touchMode)
{
if (FSOEnvironment.SoftwareDepth) State.KeyboardState = new KeyboardState();
TouchCollection touches = TouchPanel.GetState();
var missing = new HashSet<MultiMouse>(State.MouseStates);
//relate touches to their last virtual mouse
foreach (var touch in touches)
{
var mouse = State.MouseStates.FirstOrDefault(x => x.ID == touch.Id);
if (mouse == null)
{
mouse = new MultiMouse { ID = touch.Id };
State.MouseStates.Add(mouse);
}
missing.Remove(mouse);
mouse.MouseState = new MouseState(
(int)touch.Position.X, (int)touch.Position.Y, 0,
ButtonState.Pressed,
ButtonState.Released,
ButtonState.Released,
ButtonState.Released,
ButtonState.Released
);
}
//if virtual mouses no longer have their touch, they are performing a "mouse up"
//if the state has mouseovers, we should record the mouse state as being lifted.
foreach (var miss in missing)
{
if (miss.LastMouseOver == null && miss.LastMouseDown == null)
{
State.MouseStates.Remove(miss);
} else
{
miss.MouseState = new MouseState(miss.MouseState.X, miss.MouseState.Y, 0, ButtonState.Released, ButtonState.Released, ButtonState.Released, ButtonState.Released, ButtonState.Released);
miss.Dead = true;
}
}
}
else
{
//single mouse state
if (hasFocus)
{
State.MouseState = Mouse.GetState();
State.KeyboardState = Keyboard.GetState();
}
else
{
State.MouseState = new MouseState();
State.KeyboardState = new KeyboardState();
}
if (State.KeyboardState.IsKeyDown(Keys.LeftAlt) && State.MouseState.LeftButton == ButtonState.Pressed)
{
//emulated middle click with alt
var ms = State.MouseState;
State.MouseState = new MouseState(ms.X, ms.Y, ms.ScrollWheelValue, ButtonState.Released, ButtonState.Pressed, ms.RightButton, ms.XButton1, ms.XButton2);
}
if (State.MouseStates.Count == 0)
{
State.MouseStates.Add(new MultiMouse { ID = 1 });
}
State.MouseStates[0].MouseState = State.MouseState;
}
State.SharedData.Clear();
State.Update();
foreach (var layer in Layers){
layer.Update(State);
}
TextCharacters.Clear();
}
private void TouchStub(UpdateState state)
{
var test = TouchPanel.EnableMouseTouchPoint;
TouchCollection touches = TouchPanel.GetState();
if (touches.Count != lastTouchCount) touchedFrames = 0;
lastTouchCount = touches.Count;
if (touches.Count > 0)
{
Vector2 avg = new Vector2();
for (int i = 0; i < touches.Count; i++)
{
avg += touches[i].Position;
}
avg /= touches.Count;
if (touchedFrames < TOUCH_ACCEPT_TIME)
{
avg = prevTouchAvg ?? avg;
state.MouseState = new MouseState(
(int)avg.X, (int)avg.Y, state.MouseState.ScrollWheelValue,
ButtonState.Released,
ButtonState.Released,
ButtonState.Released,
ButtonState.Released,
ButtonState.Released
);
touchedFrames++;
}
else
{
state.MouseState = new MouseState(
(int)avg.X, (int)avg.Y, state.MouseState.ScrollWheelValue,
(touches.Count > 1) ? ButtonState.Released : ButtonState.Pressed,
(touches.Count > 1) ? ButtonState.Pressed : ButtonState.Released,
(touches.Count > 1) ? ButtonState.Pressed : ButtonState.Released,
ButtonState.Released,
ButtonState.Released
);
prevTouchAvg = avg;
state.TouchMode = true;
}
}
else
{
prevTouchAvg = null;
touchedFrames = 0;
if (state.TouchMode) state.MouseState = new MouseState(
lastMouseState.X, lastMouseState.Y, state.MouseState.ScrollWheelValue,
ButtonState.Released,
ButtonState.Released,
ButtonState.Released,
ButtonState.Released,
ButtonState.Released
);
//state.TouchMode = false;
}
lastMouseState = state.MouseState;
}
public void Draw(GameTime time)
{
lock (Device)
{
foreach (var layer in Layers.Reverse<IGraphicsLayer>())
{
layer.PreDraw(Device);
}
}
Device.SetRenderTarget(null);
Device.BlendState = BlendState.AlphaBlend;
Device.Clear(ClearColor);
//Device.RasterizerState.AlphaBlendEnable = true;
//Device.DepthStencilState.DepthBufferEnable = true;
lock (Device)
{
foreach (var layer in Layers)
{
layer.Draw(Device);
}
}
}
}
}

View file

@ -0,0 +1,8 @@
using Microsoft.Xna.Framework.Graphics;
namespace FSO.Common.Rendering.Framework
{
public interface I3DGeometry {
void DrawGeometry(GraphicsDevice gd);
}
}

View file

@ -0,0 +1,13 @@
using FSO.Common.Rendering.Framework.Model;
using Microsoft.Xna.Framework.Graphics;
namespace FSO.Common.Rendering.Framework
{
public interface IGraphicsLayer
{
void Initialize(GraphicsDevice device);
void Update(UpdateState state);
void PreDraw(GraphicsDevice device);
void Draw(GraphicsDevice device);
}
}

View file

@ -0,0 +1,10 @@
namespace FSO.Common.Rendering.Framework.IO
{
public class ClipboardHandler
{
public static ClipboardHandler Default = new ClipboardHandler();
public virtual string Get() { return ""; }
public virtual void Set(string text) { }
}
}

View file

@ -0,0 +1,10 @@
namespace FSO.Common.Rendering.Framework.IO
{
/// <summary>
/// Represents an object that has depth
/// </summary>
public interface IDepthProvider
{
float Depth { get; }
}
}

View file

@ -0,0 +1,13 @@
namespace FSO.Common.Rendering.Framework.IO
{
public interface IFocusableUI
{
void OnFocusChanged(FocusEvent newFocus);
}
public enum FocusEvent
{
FocusIn,
FocusOut
}
}

View file

@ -0,0 +1,571 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Input;
using System.Runtime.InteropServices;
using FSO.Common.Rendering.Framework.Model;
namespace FSO.Common.Rendering.Framework.IO
{
/// <summary>
/// Manages input for the game.
/// </summary>
public class InputManager
{
private IFocusableUI LastFocus;
public bool RequireWindowFocus = false;
public void SetFocus(IFocusableUI ui)
{
/** No change **/
if (ui == LastFocus) { return; }
if (LastFocus != null)
{
LastFocus.OnFocusChanged(FocusEvent.FocusOut);
}
LastFocus = ui;
if (ui != null)
{
LastFocus.OnFocusChanged(FocusEvent.FocusIn);
}
}
public IFocusableUI GetFocus()
{
return LastFocus;
}
[DllImport("user32.dll")]
static extern int MapVirtualKey(uint uCode, uint uMapType);
/// <summary>
/// Utility to apply the result of pressing keys against a buffer
/// </summary>
/// <param name="buffer"></param>
/// <param name="keys"></param>
public KeyboardInputResult ApplyKeyboardInput(StringBuilder m_SBuilder, UpdateState state, int cursorIndex, int cursorEndIndex, bool allowInput)
{
if (!state.WindowFocused && RequireWindowFocus) { return null; }
var PressedKeys = state.KeyboardState.GetPressedKeys();
int charCount = 0;
if (state.FrameTextInput == null) charCount = 0;
else charCount = state.FrameTextInput.Count;
if (PressedKeys.Length + charCount == 0) { return null; }
//bit of a legacy thing going on here
//we support both "pressed keys" and the keyboard event system.
//todo: clean up a bit
var didChange = false;
var result = new KeyboardInputResult();
var m_CurrentKeyState = state.KeyboardState;
var m_OldKeyState = state.PreviousKeyboardState;
result.ShiftDown = PressedKeys.Contains(Keys.LeftShift) || PressedKeys.Contains(Keys.RightShift);
result.CapsDown = state.KeyboardState.CapsLock;
result.NumLockDown = state.KeyboardState.NumLock;
// Right alt aka AltGr is treated as Ctrl+Alt. It is used to type accented letters and other unusual characters so pressing that key cannot cause special actions.
result.CtrlDown = (PressedKeys.Contains(Keys.LeftControl) && !PressedKeys.Contains(Keys.RightAlt)) || PressedKeys.Contains(Keys.RightControl);
for (int j = 0; j < state.NewKeys.Count + charCount; j++)
{
var key = (j<state.NewKeys.Count)?state.NewKeys[j]:Keys.None;
bool processChar = true;
if (key != Keys.None)
{
processChar = false;
if (key == Keys.Back || key == Keys.Delete)
{
if (m_SBuilder.Length > 0)
{
/**
* Delete previous character or delete selection
*/
if (cursorEndIndex == -1 && result.CtrlDown)
{
/** Delete up until the previous whitespace char **/
int newEndIndex = cursorIndex;
if (newEndIndex == -1)
{
newEndIndex = m_SBuilder.Length;
cursorIndex = m_SBuilder.Length;
}
int dir = (key == Keys.Delete) ? 1 : -1;
int ws = (key == Keys.Delete) ? 0 : -1;
while (newEndIndex+ws >= 0 && newEndIndex+ws < m_SBuilder.Length)
{
if (Char.IsWhiteSpace(m_SBuilder[newEndIndex+ws]))
{
/** Keep the whitespace char **/
break;
}
newEndIndex += dir;
}
if (cursorIndex > newEndIndex)
{
cursorEndIndex = cursorIndex;
cursorIndex = newEndIndex;
}
else
cursorEndIndex = newEndIndex;
if (cursorEndIndex == cursorIndex)
cursorIndex = cursorEndIndex = -1;
}
if (cursorEndIndex == -1)
{
/** Previous character **/
var index = cursorIndex == -1 ? m_SBuilder.Length : cursorIndex;
if ((key == Keys.Back) && (index > 0))
{
var numToDelete = 1;
if (index > 1 && m_SBuilder[index - 1] == '\n' && m_SBuilder[index - 2] == '\r')
{
numToDelete = 2;
}
m_SBuilder.Remove(index - numToDelete, numToDelete);
result.NumDeletes += numToDelete;
if (cursorIndex != -1)
{
cursorIndex -= numToDelete;
}
}
else if ((key == Keys.Delete) && (index < m_SBuilder.Length))
{
/** Guys, delete removes the next character, not the last!! **/
var numToDelete = 1;
if ((index < m_SBuilder.Length - 1) && m_SBuilder[index] == '\r' && m_SBuilder[index + 1] == '\n')
{
numToDelete = 2;
}
m_SBuilder.Remove(index, numToDelete);
result.NumDeletes += numToDelete;
}
}
else
{
DeleteSelectedText(m_SBuilder, ref cursorIndex, ref cursorEndIndex, ref didChange, result);
}
result.SelectionChanged = true;
didChange = true;
}
}
else if (key == Keys.Enter)
{
if (allowInput)
{
/** Line break **/
if (cursorEndIndex != -1)
{
/** Delete selected text **/
DeleteSelectedText(m_SBuilder, ref cursorIndex, ref cursorEndIndex, ref didChange, result);
}
if (cursorIndex == -1)
{
m_SBuilder.Append("\n");
}
else
{
cursorIndex = Math.Min(m_SBuilder.Length, cursorIndex);
m_SBuilder.Insert(cursorIndex, "\n");
cursorIndex += 1;
}
result.NumInsertions += 1;
didChange = true;
result.EnterPressed = true;
}
}
else if (key == Keys.Tab)
{
result.TabPressed = true;
}
else if (result.CtrlDown)
{
switch (key)
{
case Keys.A:
/** Select all **/
cursorIndex = 0;
cursorEndIndex = m_SBuilder.Length;
result.SelectionChanged = true;
break;
case Keys.C:
case Keys.X:
/** Copy text to clipboard **/
if (cursorEndIndex > 0)
{
var selectionStart = Math.Max(0, cursorIndex);
var selectionEnd = cursorEndIndex;
GetSelectionRange(ref selectionStart, ref selectionEnd);
var str = m_SBuilder.ToString().Substring(selectionStart, selectionEnd - selectionStart);
ClipboardHandler.Default.Set(str);
if (key == Keys.X)
{
DeleteSelectedText(m_SBuilder, ref cursorIndex, ref cursorEndIndex, ref didChange, result);
}
}
break;
case Keys.V:
/** Paste text in **/
var clipboardText = ClipboardHandler.Default.Get();
if (clipboardText != null)
{
/** TODO: Cleanup the clipboard text to make sure its valid **/
/** If i have selection, delete it **/
if (cursorEndIndex != -1)
{
DeleteSelectedText(m_SBuilder, ref cursorIndex, ref cursorEndIndex, ref didChange, result);
}
/** Paste **/
if (cursorIndex == -1)
{
m_SBuilder.Append(clipboardText);
}
else
{
m_SBuilder.Insert(Math.Min(cursorIndex, m_SBuilder.Length), clipboardText);
cursorIndex += clipboardText.Length;
}
result.NumInsertions += clipboardText.Length;
didChange = true;
}
break;
}
} else {
result.UnhandledKeys.Add(key);
processChar = true;
}
}
if (processChar)
{
char value;
if (j >= state.NewKeys.Count) value = state.FrameTextInput[j - state.NewKeys.Count];
else if (state.FrameTextInput != null) continue;
else value = TranslateChar(key, result.ShiftDown, result.CapsDown, result.NumLockDown);
/** For now we dont support tabs in text **/
if (!char.IsControl(value) && value != '\0' && value != '\t' && value != '\b' && value != '\r')
{
if (allowInput)
{
if (cursorEndIndex != -1)
{
/** Delete selected text **/
DeleteSelectedText(m_SBuilder, ref cursorIndex, ref cursorEndIndex, ref didChange, result);
}
if (cursorIndex == -1)
{
m_SBuilder.Append(value);
}
else
{
m_SBuilder.Insert(cursorIndex, value);
cursorIndex++;
}
result.NumInsertions++;
didChange = true;
}
}
else
{
result.UnhandledKeys.Add(key);
}
}
}
result.SelectionStart = cursorIndex;
result.SelectionEnd = cursorEndIndex;
result.ContentChanged = didChange;
return result;
}
private void DeleteSelectedText(StringBuilder m_SBuilder, ref int cursorIndex, ref int cursorEndIndex, ref bool didChange, KeyboardInputResult result)
{
/** Remove selected text **/
var index = cursorIndex == -1 ? m_SBuilder.Length : cursorIndex;
var end = cursorEndIndex;
if (end < index)
{
var temp = index;
index = end;
end = temp;
}
m_SBuilder.Remove(index, end - index);
cursorIndex = index;
if (cursorIndex >= m_SBuilder.Length)
{
cursorIndex = -1;
}
cursorEndIndex = -1;
result.SelectionChanged = true;
didChange = true;
}
public void GetSelectionRange(ref int start, ref int end)
{
if (end < start)
{
var temp = start;
start = end;
end = temp;
}
}
public static char TranslateChar(Keys key, bool shift, bool capsLock, bool numLock)
{
switch (key)
{
case Keys.A: return TranslateAlphabetic('a', shift, capsLock);
case Keys.B: return TranslateAlphabetic('b', shift, capsLock);
case Keys.C: return TranslateAlphabetic('c', shift, capsLock);
case Keys.D: return TranslateAlphabetic('d', shift, capsLock);
case Keys.E: return TranslateAlphabetic('e', shift, capsLock);
case Keys.F: return TranslateAlphabetic('f', shift, capsLock);
case Keys.G: return TranslateAlphabetic('g', shift, capsLock);
case Keys.H: return TranslateAlphabetic('h', shift, capsLock);
case Keys.I: return TranslateAlphabetic('i', shift, capsLock);
case Keys.J: return TranslateAlphabetic('j', shift, capsLock);
case Keys.K: return TranslateAlphabetic('k', shift, capsLock);
case Keys.L: return TranslateAlphabetic('l', shift, capsLock);
case Keys.M: return TranslateAlphabetic('m', shift, capsLock);
case Keys.N: return TranslateAlphabetic('n', shift, capsLock);
case Keys.O: return TranslateAlphabetic('o', shift, capsLock);
case Keys.P: return TranslateAlphabetic('p', shift, capsLock);
case Keys.Q: return TranslateAlphabetic('q', shift, capsLock);
case Keys.R: return TranslateAlphabetic('r', shift, capsLock);
case Keys.S: return TranslateAlphabetic('s', shift, capsLock);
case Keys.T: return TranslateAlphabetic('t', shift, capsLock);
case Keys.U: return TranslateAlphabetic('u', shift, capsLock);
case Keys.V: return TranslateAlphabetic('v', shift, capsLock);
case Keys.W: return TranslateAlphabetic('w', shift, capsLock);
case Keys.X: return TranslateAlphabetic('x', shift, capsLock);
case Keys.Y: return TranslateAlphabetic('y', shift, capsLock);
case Keys.Z: return TranslateAlphabetic('z', shift, capsLock);
case Keys.D0: return (shift) ? ')' : '0';
case Keys.D1: return (shift) ? '!' : '1';
case Keys.D2: return (shift) ? '@' : '2';
case Keys.D3: return (shift) ? '#' : '3';
case Keys.D4: return (shift) ? '$' : '4';
case Keys.D5: return (shift) ? '%' : '5';
case Keys.D6: return (shift) ? '^' : '6';
case Keys.D7: return (shift) ? '&' : '7';
case Keys.D8: return (shift) ? '*' : '8';
case Keys.D9: return (shift) ? '(' : '9';
case Keys.Add: return '+';
case Keys.Divide: return '/';
case Keys.Multiply: return '*';
case Keys.Subtract: return '-';
case Keys.Space: return ' ';
case Keys.Tab: return '\t';
case Keys.Decimal: if (numLock && !shift) return '.'; break;
case Keys.NumPad0: if (numLock && !shift) return '0'; break;
case Keys.NumPad1: if (numLock && !shift) return '1'; break;
case Keys.NumPad2: if (numLock && !shift) return '2'; break;
case Keys.NumPad3: if (numLock && !shift) return '3'; break;
case Keys.NumPad4: if (numLock && !shift) return '4'; break;
case Keys.NumPad5: if (numLock && !shift) return '5'; break;
case Keys.NumPad6: if (numLock && !shift) return '6'; break;
case Keys.NumPad7: if (numLock && !shift) return '7'; break;
case Keys.NumPad8: if (numLock && !shift) return '8'; break;
case Keys.NumPad9: if (numLock && !shift) return '9'; break;
case Keys.OemBackslash: return shift ? '|' : '\\';
case Keys.OemCloseBrackets: return shift ? '}' : ']';
case Keys.OemComma: return shift ? '<' : ',';
case Keys.OemMinus: return shift ? '_' : '-';
case Keys.OemOpenBrackets: return shift ? '{' : '[';
case Keys.OemPeriod: return shift ? '>' : '.';
case Keys.OemPipe: return shift ? '|' : '\\';
case Keys.OemPlus: return shift ? '+' : '=';
case Keys.OemQuestion: return shift ? '?' : '/';
case Keys.OemQuotes: return shift ? '"' : '\'';
case Keys.OemSemicolon: return shift ? ':' : ';';
case Keys.OemTilde: return shift ? '~' : '`';
}
return (char)0;
}
public static char TranslateAlphabetic(char baseChar, bool shift, bool capsLock)
{
return (capsLock ^ shift) ? char.ToUpper(baseChar) : baseChar;
}
public void HandleMouseEvents(UpdateState state)
{
foreach (var mouse in state.MouseStates) {
var mouseBtnDown = mouse.MouseState.LeftButton == Microsoft.Xna.Framework.Input.ButtonState.Pressed;
var mouseDif = mouseBtnDown != mouse.LastMouseDownState;
if (mouse.NewMultiMouse) mouse.NewMultiMouse = false;
else
mouse.LastMouseDownState = mouseBtnDown;
state.MouseState = mouse.MouseState; //make sure each event uses the mouse state for this mouse.
state.CurrentMouseID = mouse.ID;
//if anyone accesses vanilla mouse state during the update loop, it will be the last mouse that was present.
var topMost =
state.MouseEvents.Where(x => x.Item1 == mouse.ID).OrderByDescending(x => x.Item2.Element.Depth).FirstOrDefault();
if (topMost != null && !mouse.Dead)
{
/** different element? **/
if (mouse.LastMouseOver != topMost.Item2)
{
if (mouse.LastMouseOver != null)
{
mouse.LastMouseOver.Callback(UIMouseEventType.MouseOut, state);
}
topMost.Item2.Callback(UIMouseEventType.MouseOver, state);
mouse.LastMouseOver = topMost.Item2;
}
}
else
{
if (mouse.LastMouseOver != null)
{
mouse.LastMouseOver.Callback(UIMouseEventType.MouseOut, state);
mouse.LastMouseOver = null;
}
}
if (mouseDif)
{
if (mouseBtnDown)
{
if (mouse.LastMouseDown != null)
{
/** We already have mouse down on an object **/
return;
}
if (mouse.LastMouseOver != null)
{
mouse.LastMouseDown = mouse.LastMouseOver;
mouse.LastMouseDown.Callback(UIMouseEventType.MouseDown, state);
}
}
else
{
if (mouse.LastMouseDown != null)
{
mouse.LastMouseDown.Callback(UIMouseEventType.MouseUp, state);
mouse.LastMouseDown = null;
}
}
}
}
}
}
public class KeyboardInputResult
{
public List<Keys> UnhandledKeys = new List<Keys>();
public bool ContentChanged;
public bool ShiftDown;
public bool CapsDown;
public bool NumLockDown;
public bool CtrlDown;
public bool EnterPressed;
public bool TabPressed;
public int NumDeletes;
public int NumInsertions;
public int SelectionStart;
public int SelectionEnd;
public bool SelectionChanged;
}
}

View file

@ -0,0 +1,25 @@
using FSO.Common.Rendering.Framework.Model;
using Microsoft.Xna.Framework;
namespace FSO.Common.Rendering.Framework.IO
{
public enum UIMouseEventType
{
MouseOver,
MouseOut,
MouseDown,
MouseUp
}
public delegate void UIMouseEvent(UIMouseEventType type, UpdateState state);
public class UIMouseEventRef
{
public UIMouseEvent Callback;
public Rectangle Region;
//public UIElement Element;
public UIMouseEventType LastState;
public IDepthProvider Element;
}
}

View file

@ -0,0 +1,21 @@
using Microsoft.Xna.Framework;
namespace FSO.Common.Rendering.Framework.Model
{
public class UIState
{
public int Width;
public int Height;
public UITooltipProperties TooltipProperties = new UITooltipProperties();
public string Tooltip;
}
public class UITooltipProperties
{
public float Opacity;
public Vector2 Position;
public bool Show;
public Color Color = Color.Black;
public bool UpdateDead;
}
}

View file

@ -0,0 +1,131 @@
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using FSO.Common.Rendering.Framework.IO;
namespace FSO.Common.Rendering.Framework.Model
{
public class MultiMouse
{
public int ID;
public MouseState MouseState;
public UIMouseEventRef LastMouseDown;
public UIMouseEventRef LastMouseOver;
public bool LastMouseDownState = false;
public bool NewMultiMouse = true;
public bool Dead = false;
}
/// <summary>
/// Contains common information used in the update loop
/// </summary>
public class UpdateState
{
public GameTime Time;
public List<MultiMouse> MouseStates = new List<MultiMouse>();
public MouseState MouseState;
public int CurrentMouseID;
public KeyboardState KeyboardState;
public bool ShiftDown
{
get { return KeyboardState.IsKeyDown(Keys.LeftShift) || KeyboardState.IsKeyDown(Keys.RightShift); }
}
/// <summary>
/// Right alt is treated as LeftCtrl+RightAlt so while right Alt is down, you cannot predict if left Ctrl is also down.
/// For that reason, this variable is false when left Ctrl and right Alt are down, and right Ctrl is not down.
/// </summary>
public bool CtrlDown
{
get { return (KeyboardState.IsKeyDown(Keys.LeftControl) && !KeyboardState.IsKeyDown(Keys.RightAlt)) || KeyboardState.IsKeyDown(Keys.RightControl); }
}
public bool AltDown
{
get { return KeyboardState.IsKeyDown(Keys.LeftAlt) || KeyboardState.IsKeyDown(Keys.RightAlt); }
}
public UIState UIState = new UIState();
public InputManager InputManager;
public bool TouchMode;
public KeyboardState PreviousKeyboardState;
public List<char> FrameTextInput;
/** A Place to keep shared variables, clears every update cycle **/
public Dictionary<string, object> SharedData = new Dictionary<string, object>();
public List<Tuple<int, UIMouseEventRef>> MouseEvents = new List<Tuple<int, UIMouseEventRef>>();
private Dictionary<Keys, long> KeyDownTime = new Dictionary<Keys, long>();
private List<Keys> KeyInRepeatMode = new List<Keys>();
public List<Keys> NewKeys = new List<Keys>();
public int Depth;
public bool WindowFocused;
public bool MouseOverWindow;
public bool ProcessMouseEvents
{
get
{
return WindowFocused && MouseOverWindow;
}
}
public void Update()
{
NewKeys.Clear();
Depth = 0;
/**
* If a key has been held down for X duration, treat it as if it is newly
* pressed
*/
for(var i=0; i < KeyInRepeatMode.Count; i++){
if (!KeyboardState.IsKeyDown(KeyInRepeatMode[i]))
{
KeyInRepeatMode.RemoveAt(i);
i--;
}
}
var now = Time.TotalGameTime.Ticks;
var keys = KeyboardState.GetPressedKeys();
foreach (var key in keys)
{
var newPress = PreviousKeyboardState.IsKeyUp(key);
if (newPress)
{
KeyDownTime[key] = now;
NewKeys.Add(key);
}
else
{
if (KeyInRepeatMode.Contains(key))
{
/** How long has it been down? **/
if (now - KeyDownTime[key] > 400000)
{
/** Its been down long enough, consider it a new key **/
KeyDownTime[key] = now;
NewKeys.Add(key);
}
}
else
{
/** How long has it been down? **/
if (now - KeyDownTime[key] > 9000000)
{
/** Its been down long enough, consider it in repeat mode **/
KeyDownTime[key] = now;
KeyInRepeatMode.Add(key);
}
}
}
}
}
}
}

View file

@ -0,0 +1,86 @@
using System.Collections.Generic;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
namespace FSO.Common.Rendering.Framework.Shapes
{
public class _3DCube : _3DComponent
{
private BasicEffect Effect;
private VertexPositionColor[] Geom;
private List<VertexPositionColor> GeomList;
private Color Color;
private Vector3 Size;
public _3DCube(Color color, Vector3 size)
{
this.Color = color;
this.Size = size;
}
public override void Initialize(){
Effect = new BasicEffect(Device);
/** Bottom Face **/
var btmTL = new Vector3(0.0f, 0.0f, 0.0f);
var btmTR = new Vector3(Size.X, 0.0f, 0.0f);
var btmBR = new Vector3(Size.X, 0.0f, Size.Z);
var btmBL = new Vector3(0.0f, 0.0f, Size.Z);
/** Top face **/
var topTL = new Vector3(0.0f, Size.Y, 0.0f);
var topTR = new Vector3(Size.X, Size.Y, 0.0f);
var topBR = new Vector3(Size.X, Size.Y, Size.Z);
var topBL = new Vector3(0.0f, Size.Y, Size.Z);
GeomList = new List<VertexPositionColor>();
AddQuad(Color, topTL, topTR, topBR, topBL);
AddQuad(Color.Yellow, btmTL, btmTR, btmBR, btmBL);
AddQuad(Color.Green, topTL, topTR, btmTR, btmTL);
AddQuad(Color.Blue, topBL, topTL, btmTL, btmBL);
AddQuad(Color.Orange, topBR, topTR, btmTR, btmBR);
AddQuad(Color.White, topBL, topBR, btmBR, btmBL);
Geom = GeomList.ToArray();
}
private void AddQuad(Color color, Vector3 tl, Vector3 tr, Vector3 br, Vector3 bl)
{
GeomList.Add(new VertexPositionColor(tl, color));
GeomList.Add(new VertexPositionColor(tr, color));
GeomList.Add(new VertexPositionColor(br, color));
GeomList.Add(new VertexPositionColor(br, color));
GeomList.Add(new VertexPositionColor(bl, color));
GeomList.Add(new VertexPositionColor(tl, color));
}
public override void Draw(GraphicsDevice device)
{
Effect.World = World;
Effect.View = View;
Effect.Projection = Projection;
Effect.VertexColorEnabled = true;
//Effect.EnableDefaultLighting();
foreach (var pass in Effect.CurrentTechnique.Passes)
{
pass.Apply();
device.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.TriangleList, Geom, 0, Geom.Length / 3);
}
}
public override void Update(FSO.Common.Rendering.Framework.Model.UpdateState state)
{
}
public override void DeviceReset(GraphicsDevice Device)
{
}
}
}

View file

@ -0,0 +1,6 @@
using FSO.Common.Rendering.Framework.Model;
namespace FSO.Common.Rendering.Framework
{
public delegate void UpdateHookDelegate(UpdateState state);
}

View file

@ -0,0 +1,21 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace FSO.Common.Rendering
{
public class TextureInfo
{
public Vector2 UVScale;
public Point Size;
public Point Diff;
public TextureInfo() { }
public TextureInfo(Texture2D tex, int width, int height)
{
Size = new Point(width, height);
Diff = new Point(tex.Width, tex.Height) - Size;
UVScale = Size.ToVector2() / new Vector2(tex.Width, tex.Height);
}
}
}

View file

@ -0,0 +1,69 @@
using Microsoft.Xna.Framework;
using System;
namespace FSO.Common.Rendering
{
public static class TimeOfDayConfig
{
public static float DarknessMultiplier = 0.8f;
private static Color[] m_TimeColors = new Color[]
{
new Color(50, 70, 122)*1.25f,
new Color(50, 70, 122)*1.25f,
new Color(55, 75, 111)*1.25f,
new Color(70, 70, 70)*1.25f,
new Color(217, 109, 50), //sunrise
new Color(255, 255, 255),
new Color(255, 255, 255), //peak
new Color(255, 255, 255), //peak
new Color(255, 255, 255),
new Color(255, 255, 255),
new Color(217, 109, 50), //sunset
new Color(70, 70, 70)*1.25f,
new Color(55, 75, 111)*1.25f,
new Color(50, 70, 122)*1.25f,
};
private static float[] m_SkyColors = new float[]
{
4/8f,
4/8f,
4/8f,
5/8f,
6/8f, //sunrise
7/8f,
8/8f, //peak
0/8f, //peak
0/8f,
0/8f,
1/8f, //sunset
2/8f,
3/8f,
4/8f,
};
private static Color PowColor(Color col, float pow)
{
var vec = col.ToVector4();
vec.X = (float)Math.Pow(vec.X, pow);
vec.Y = (float)Math.Pow(vec.Y, pow);
vec.Z = (float)Math.Pow(vec.Z, pow);
return new Color(vec);
}
public static Color ColorFromTime(double time)
{
Color col1 = m_TimeColors[(int)Math.Floor(time * (m_TimeColors.Length - 1))]; //first colour
Color col2 = m_TimeColors[(int)Math.Floor(time * (m_TimeColors.Length - 1)) + 1]; //second colour
if (DarknessMultiplier != 1.0)
{
col1 = Color.Lerp(Color.White, col1, DarknessMultiplier);
col2 = Color.Lerp(Color.White, col2, DarknessMultiplier);
}
double Progress = (time * (m_TimeColors.Length - 1)) % 1; //interpolation progress (mod 1)
return PowColor(Color.Lerp(col1, col2, (float)Progress), 2.2f); //linearly interpolate between the two colours for this specific time.
}
}
}

View file

@ -0,0 +1,7 @@
namespace FSO.Common.Security
{
public enum AvatarPermissions
{
WRITE
}
}

View file

@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace FSO.Common.Security
{
public interface ISecurityContext
{
void DemandAvatar(uint id, AvatarPermissions permission);
void DemandAvatars(IEnumerable<uint> id, AvatarPermissions permission);
void DemandInternalSystem();
}
}

View file

@ -0,0 +1,22 @@
using System.Collections.Generic;
namespace FSO.Common.Security
{
public class NullSecurityContext : ISecurityContext
{
public static NullSecurityContext INSTANCE = new NullSecurityContext();
public void DemandAvatar(uint id, AvatarPermissions permission)
{
}
public void DemandAvatars(IEnumerable<uint> id, AvatarPermissions permission)
{
}
public void DemandInternalSystem()
{
}
}
}

View file

@ -0,0 +1,27 @@
using Mina.Core.Buffer;
using System;
namespace FSO.Common.Serialization
{
public interface IModelSerializer
{
object Deserialize(uint clsid, IoBuffer input, ISerializationContext context);
void Serialize(IoBuffer output, object obj, ISerializationContext context);
void Serialize(IoBuffer output, object value, ISerializationContext context, bool clsIdPrefix);
IoBuffer SerializeBuffer(object value, ISerializationContext context, bool clsIdPrefix);
uint? GetClsid(object value);
void AddTypeSerializer(ITypeSerializer serializer);
}
public interface ITypeSerializer
{
object Deserialize(uint clsid, IoBuffer input, ISerializationContext serializer);
void Serialize(IoBuffer output, object value, ISerializationContext serializer);
uint? GetClsid(object value);
bool CanSerialize(Type type);
bool CanDeserialize(uint clsid);
}
}

View file

@ -0,0 +1,10 @@
using Ninject;
namespace FSO.Common.Serialization
{
public interface ISerializationContext
{
IKernel Kernel { get; }
IModelSerializer ModelSerializer { get; }
}
}

View file

@ -0,0 +1,9 @@
using Mina.Core.Buffer;
namespace FSO.Common.Serialization
{
public interface IoBufferDeserializable
{
void Deserialize(IoBuffer input, ISerializationContext context);
}
}

View file

@ -0,0 +1,9 @@
using Mina.Core.Buffer;
namespace FSO.Common.Serialization
{
public interface IoBufferSerializable
{
void Serialize(IoBuffer output, ISerializationContext context);
}
}

View file

@ -0,0 +1,304 @@
using Mina.Core.Buffer;
using System;
using System.Collections.Generic;
using System.Text;
namespace FSO.Common.Serialization
{
public static class IoBufferUtils
{
public static void PutSerializable(this IoBuffer buffer, object obj, ISerializationContext context)
{
buffer.PutSerializable(context, obj, false);
}
public static byte[] GetBytes(this IoBuffer buffer)
{
var result = new byte[buffer.Limit];
buffer.Get(result, 0, buffer.Limit);
return result;
}
public static T Deserialize<T>(byte[] bytes, ISerializationContext context) where T : IoBufferDeserializable
{
var buffer = IoBuffer.Wrap(bytes);
return Deserialize<T>(buffer, context);
}
public static T Deserialize<T>(IoBuffer buffer, ISerializationContext context) where T : IoBufferDeserializable
{
var model = Activator.CreateInstance<T>();
model.Deserialize(buffer, context);
return (T)model;
}
public static IoBuffer SerializableToIoBuffer(object obj, ISerializationContext context)
{
if (obj is IoBuffer)
{
var ioBuffer = (IoBuffer)obj;
return (IoBuffer)ioBuffer;
}
else if (obj is byte[])
{
var byteArray = (byte[])obj;
return IoBuffer.Wrap(byteArray);
}
else if (obj is IoBufferSerializable)
{
var ioBuffer = IoBuffer.Allocate(0);
ioBuffer.AutoExpand = true;
ioBuffer.Order = ByteOrder.BigEndian;
var serializable = (IoBufferSerializable)obj;
serializable.Serialize(ioBuffer, context);
ioBuffer.Flip();
return ioBuffer;
}
throw new Exception("Unknown serializable type: " + obj);
}
public static void PutSerializable(this IoBuffer buffer, ISerializationContext context, object obj, bool writeLength)
{
if(obj is IoBuffer)
{
var ioBuffer = (IoBuffer)obj;
if (writeLength){
buffer.PutUInt32((uint)ioBuffer.Remaining);
}
buffer.Put(ioBuffer);
}else if(obj is byte[])
{
var byteArray = (byte[])obj;
if (writeLength)
{
buffer.PutUInt32((uint)byteArray.Length);
}
buffer.Put(byteArray);
}else if(obj is IoBufferSerializable)
{
var ioBuffer = IoBuffer.Allocate(0);
ioBuffer.AutoExpand = true;
ioBuffer.Order = ByteOrder.BigEndian;
var serializable = (IoBufferSerializable)obj;
serializable.Serialize(ioBuffer, context);
ioBuffer.Flip();
if (writeLength)
{
buffer.PutUInt32((uint)ioBuffer.Remaining);
}
buffer.Put(ioBuffer);
}
}
public static void PutBool(this IoBuffer buffer, bool value)
{
buffer.Put(value ? (byte)0x01 : (byte)0x00);
}
public static bool GetBool(this IoBuffer buffer)
{
return buffer.Get() == 1 ? true : false;
}
public static void PutUInt32(this IoBuffer buffer, uint value)
{
int converted = unchecked((int)value);
buffer.PutInt32(converted);
}
public static uint GetUInt32(this IoBuffer buffer)
{
return (uint)buffer.GetInt32();
}
public static void PutUInt16(this IoBuffer buffer, ushort value)
{
buffer.PutInt16((short)value);
}
public static void PutUInt64(this IoBuffer buffer, ulong value)
{
buffer.PutInt64((long)value);
}
public static void PutEnum<T>(this IoBuffer buffer, T enumValue)
{
ushort value = Convert.ToUInt16((object)enumValue);
buffer.PutUInt16(value);
}
public static void PutUTF8(this IoBuffer buffer, string value)
{
if (value == null)
{
buffer.PutInt16(-1);
}
else
{
buffer.PutInt16((short)value.Length);
buffer.PutString(value, Encoding.UTF8);
}
}
public static string GetUTF8(this IoBuffer buffer)
{
short len = buffer.GetInt16();
if (len == -1)
{
return null;
}
return buffer.GetString(len, Encoding.UTF8);
}
public static ushort GetUInt16(this IoBuffer buffer)
{
return (ushort)buffer.GetInt16();
}
public static ulong GetUInt64(this IoBuffer buffer)
{
return (ulong)buffer.GetInt64();
}
public static T GetEnum<T>(this IoBuffer buffer)
{
return (T)System.Enum.Parse(typeof(T), buffer.GetUInt16().ToString());
}
public static String GetPascalVLCString(this IoBuffer buffer)
{
byte lengthByte = 0;
uint length = 0;
int shift = 0;
do
{
lengthByte = buffer.Get();
length |= (uint)((lengthByte & (uint)0x7F) << shift);
shift += 7;
} while (
(lengthByte >> 7) == 1
);
if (length > 0)
{
var data = new List<byte>();
for (int i = 0; i < length; i++)
{
data.Add(buffer.Get());
}
return Encoding.UTF8.GetString(data.ToArray());
}
else
{
return "";
}
}
public static byte[] GetPascalVLCString(String value)
{
if(value == null)
{
return new byte[] { 0x00 };
}
//TODO: Support strings bigger than 128 chars
var buffer = new byte[1 + value.Length];
buffer[0] = (byte)value.Length;
var chars = value.ToCharArray();
for(int i=0; i < chars.Length; i++){
buffer[i + 1] = (byte)chars[i];
}
return buffer;
}
public static void PutPascalVLCString(this IoBuffer buffer, String value)
{
byte[] encode = null;
long strlen = 0;
if (value != null)
{
encode = Encoding.UTF8.GetBytes(value);
strlen = encode.Length;
}
bool write = strlen > 0;
bool first = true;
while (strlen > 0 || first)
{
buffer.Put((byte)(((strlen > 127) ? (uint)128 : 0) | (strlen & 127)));
strlen >>= 7;
first = false;
}
if (write)
{
buffer.Put(encode);
}
}
public static String GetPascalString(this IoBuffer buffer)
{
byte len1 = buffer.Get();
byte len2 = buffer.Get();
byte len3 = buffer.Get();
byte len4 = buffer.Get();
len1 &= 0x7F;
long len = len1 << 24 | len2 << 16 | len3 << 8 | len4;
if (len > 0)
{
StringBuilder str = new StringBuilder();
for (int i = 0; i < len; i++)
{
str.Append((char)buffer.Get());
}
return str.ToString();
}
else
{
return "";
}
}
public static void PutPascalString(this IoBuffer buffer, String value)
{
long strlen = 0;
if (value != null)
{
strlen = value.Length;
}
byte len1 = (byte)((strlen >> 24) | 0x80);
byte len2 = (byte)((strlen >> 16) & 0xFF);
byte len3 = (byte)((strlen >> 8) & 0xFF);
byte len4 = (byte)(strlen & 0xFF);
buffer.Put(len1);
buffer.Put(len2);
buffer.Put(len3);
buffer.Put(len4);
if (strlen > 0)
{
foreach (char ch in value.ToCharArray())
{
buffer.Put((byte)ch);
}
}
}
}
}

View file

@ -0,0 +1,106 @@
using FSO.Common.Serialization.TypeSerializers;
using Mina.Core.Buffer;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
namespace FSO.Common.Serialization
{
public class ModelSerializer : IModelSerializer
{
private List<ITypeSerializer> TypeSerializers = new List<ITypeSerializer>();
private ConcurrentDictionary<Type, ITypeSerializer> SerialCache = new ConcurrentDictionary<Type, ITypeSerializer>();
private ConcurrentDictionary<uint, ITypeSerializer> DeserialCache = new ConcurrentDictionary<uint, ITypeSerializer>();
public ModelSerializer(){
//Built-in
AddTypeSerializer(new cTSOValueBoolean());
AddTypeSerializer(new cTSOValueBooleanVector());
AddTypeSerializer(new cTSOValueBooleanMap());
AddTypeSerializer(new cTSOValueString());
AddTypeSerializer(new cTSOValueStringVector());
AddTypeSerializer(new cTSOValueByte());
AddTypeSerializer(new cTSOValueByteVector());
AddTypeSerializer(new cTSOValueSByte());
AddTypeSerializer(new cTSOValueSByteVector());
AddTypeSerializer(new cTSOValueUInt32());
AddTypeSerializer(new cTSOValueUInt32Vector());
AddTypeSerializer(new cTSOValueUInt16());
AddTypeSerializer(new cTSOValueDecorated());
AddTypeSerializer(new cTSOValueUInt64());
AddTypeSerializer(new cTSOValueGenericData());
}
public uint? GetClsid(object value)
{
if (value == null) { return null; }
var serializer = GetSerializer(value.GetType());
if (serializer == null) { return null; }
return serializer.GetClsid(value);
}
public void Serialize(IoBuffer output, object obj, ISerializationContext context)
{
if (obj == null) { return; }
var serializer = GetSerializer(obj.GetType());
if (serializer == null) { return; }
serializer.Serialize(output, obj, context);
}
public void Serialize(IoBuffer output, object value, ISerializationContext context, bool clsIdPrefix)
{
if (value == null) { return; }
var serializer = GetSerializer(value.GetType());
if (serializer == null) { return; }
if (clsIdPrefix){
output.PutUInt32(serializer.GetClsid(value).Value);
}
serializer.Serialize(output, value, context);
}
public IoBuffer SerializeBuffer(object value, ISerializationContext context, bool clsIdPrefix)
{
var buffer = IoBuffer.Allocate(256);
buffer.AutoExpand = true;
buffer.Order = ByteOrder.BigEndian;
Serialize(buffer, value, context, clsIdPrefix);
buffer.Flip();
return buffer;
}
public object Deserialize(uint clsid, IoBuffer input, ISerializationContext context)
{
var serializer = GetSerializer(clsid);
if (serializer == null) { return null; }
return serializer.Deserialize(clsid, input, context);
}
public void AddTypeSerializer(ITypeSerializer serializer)
{
TypeSerializers.Add(serializer);
}
private ITypeSerializer GetSerializer(uint clsid){
return DeserialCache.GetOrAdd(clsid, (t) =>
{
return TypeSerializers.FirstOrDefault(x => x.CanDeserialize(clsid));
});
}
private ITypeSerializer GetSerializer(Type type){
return SerialCache.GetOrAdd(type, (t) =>
{
return TypeSerializers.FirstOrDefault(x => x.CanSerialize(type));
});
}
}
}

View file

@ -0,0 +1,13 @@
namespace FSO.Common.Serialization.Primitives
{
public class cTSOGenericData
{
public byte[] Data;
public cTSOGenericData() { }
public cTSOGenericData(byte[] data)
{
Data = data;
}
}
}

View file

@ -0,0 +1,114 @@
using System;
using Mina.Core.Buffer;
using System.ComponentModel;
using FSO.Common.Serialization.TypeSerializers;
namespace FSO.Common.Serialization.Primitives
{
[cTSOValue(0x125194E5)]
public class cTSONetMessageStandard : IoBufferSerializable, IoBufferDeserializable
{
public uint Unknown_1 { get; set; }
public uint SendingAvatarID { get; set; }
public cTSOParameterizedEntityFlags Flags { get; set; }
public uint MessageID { get; set; }
public uint? DatabaseType { get; set; }
public uint? DataServiceType { get; set; }
public uint? Parameter { get; set; }
public uint RequestResponseID { get; set; }
[TypeConverter(typeof(ExpandableObjectConverter))]
public object ComplexParameter { get; set; }
public uint Unknown_2 { get; set; }
public cTSONetMessageStandard(){
}
public void Deserialize(IoBuffer input, ISerializationContext context)
{
this.Unknown_1 = input.GetUInt32();
this.SendingAvatarID = input.GetUInt32();
var flagsByte = input.Get();
this.Flags = (cTSOParameterizedEntityFlags)flagsByte;
this.MessageID = input.GetUInt32();
if ((this.Flags & cTSOParameterizedEntityFlags.HAS_DS_TYPE) == cTSOParameterizedEntityFlags.HAS_DS_TYPE)
{
this.DataServiceType = input.GetUInt32();
}else if ((this.Flags & cTSOParameterizedEntityFlags.HAS_DB_TYPE) == cTSOParameterizedEntityFlags.HAS_DB_TYPE){
this.DatabaseType = input.GetUInt32();
}
if ((this.Flags & cTSOParameterizedEntityFlags.HAS_BASIC_PARAMETER) == cTSOParameterizedEntityFlags.HAS_BASIC_PARAMETER)
{
this.Parameter = input.GetUInt32();
}
if ((this.Flags & cTSOParameterizedEntityFlags.UNKNOWN) == cTSOParameterizedEntityFlags.UNKNOWN)
{
this.Unknown_2 = input.GetUInt32();
}
if ((this.Flags & cTSOParameterizedEntityFlags.HAS_COMPLEX_PARAMETER) == cTSOParameterizedEntityFlags.HAS_COMPLEX_PARAMETER)
{
uint typeId = DatabaseType.HasValue ? DatabaseType.Value : DataServiceType.Value;
this.ComplexParameter = context.ModelSerializer.Deserialize(typeId, input, context);
}
}
public void Serialize(IoBuffer output, ISerializationContext context)
{
output.PutUInt32(Unknown_1);
output.PutUInt32(SendingAvatarID);
byte flags = 0;
if (this.DatabaseType.HasValue){
flags |= (byte)cTSOParameterizedEntityFlags.HAS_DB_TYPE;
}
if (this.DataServiceType.HasValue){
flags |= (byte)cTSOParameterizedEntityFlags.HAS_DB_TYPE;
flags |= (byte)cTSOParameterizedEntityFlags.HAS_DS_TYPE;
}
if (this.Parameter != null){
flags |= (byte)cTSOParameterizedEntityFlags.HAS_BASIC_PARAMETER;
}
if(this.ComplexParameter != null){
flags |= (byte)cTSOParameterizedEntityFlags.HAS_COMPLEX_PARAMETER;
}
output.Put(flags);
output.PutUInt32(MessageID);
if (this.DataServiceType.HasValue)
{
output.PutUInt32(this.DataServiceType.Value);
}else if (this.DatabaseType.HasValue){
output.PutUInt32(this.DatabaseType.Value);
}
if (this.Parameter.HasValue){
output.PutUInt32(this.Parameter.Value);
}
if (this.ComplexParameter != null){
context.ModelSerializer.Serialize(output, ComplexParameter, context, false);
}
}
}
[Flags]
public enum cTSOParameterizedEntityFlags
{
HAS_DB_TYPE = 1,
HAS_DS_TYPE = 2,
HAS_BASIC_PARAMETER = 4,
UNKNOWN = 8,
HAS_COMPLEX_PARAMETER = 32
}
}

View file

@ -0,0 +1,53 @@
using System.Collections.Generic;
using Mina.Core.Buffer;
namespace FSO.Common.Serialization.Primitives
{
public class cTSOProperty : IoBufferSerializable, IoBufferDeserializable
{
public uint StructType;
public List<cTSOPropertyField> StructFields;
public void Serialize(IoBuffer output, ISerializationContext context)
{
output.PutUInt32(0x89739A79);
output.PutUInt32(StructType);
output.PutUInt32((uint)StructFields.Count);
foreach (var item in StructFields)
{
output.PutUInt32(item.StructFieldID);
context.ModelSerializer.Serialize(output, item.Value, context, true);
}
}
public void Deserialize(IoBuffer input, ISerializationContext context)
{
//Unknown
input.GetUInt32();
StructType = input.GetUInt32();
StructFields = new List<cTSOPropertyField>();
var numFields = input.GetUInt32();
for(int i=0; i < numFields; i++){
var fieldId = input.GetUInt32();
var typeId = input.GetUInt32();
var value = context.ModelSerializer.Deserialize(typeId, input, context);
StructFields.Add(new cTSOPropertyField
{
StructFieldID = fieldId,
Value = value
});
}
}
}
public class cTSOPropertyField
{
public uint StructFieldID;
public object Value;
}
}

View file

@ -0,0 +1,70 @@
using Mina.Core.Buffer;
using FSO.Common.Serialization.TypeSerializers;
namespace FSO.Common.Serialization.Primitives
{
[cTSOValue(0x9736027)]
public class cTSOTopicUpdateMessage : IoBufferSerializable, IoBufferDeserializable
{
public uint MessageId { get; set; } = 0xA97360C5;
public uint StructType { get; set; }
public uint StructId { get; set; }
public uint StructField { get; set; }
public uint[] DotPath { get; set; }
public uint Unknown1 { get; set; }
public uint Unknown2 { get; set; }
public object Value { get; set; }
public string ReasonText { get; set; }
public void Serialize(IoBuffer output, ISerializationContext context)
{
output.PutUInt32(Unknown1); //Update counter
output.PutUInt32(MessageId); //Message id
output.PutUInt32(Unknown2); //Unknown
if (DotPath != null)
{
output.PutUInt32((uint)DotPath.Length);
foreach(var component in DotPath){
output.PutUInt32(component);
}
}
else
{
//Vector size
output.PutUInt32(3);
output.PutUInt32(StructType);
output.PutUInt32(StructId);
output.PutUInt32(StructField);
}
//Write value
context.ModelSerializer.Serialize(output, Value, context, true);
output.PutPascalVLCString(ReasonText);
}
public void Deserialize(IoBuffer input, ISerializationContext context)
{
Unknown1 = input.GetUInt32();
MessageId = input.GetUInt32();
Unknown2 = input.GetUInt32();
var vectorSize = input.GetUInt32();
DotPath = new uint[vectorSize];
for(int i=0; i < vectorSize; i++){
DotPath[i] = input.GetUInt32();
}
var valueType = input.GetUInt32();
this.Value = context.ModelSerializer.Deserialize(valueType, input, context);
//this.ReasonText = input.GetPascalVLCString();
}
}
}

View file

@ -0,0 +1,16 @@
using Ninject;
namespace FSO.Common.Serialization
{
public class SerializationContext : ISerializationContext
{
public IKernel Kernel { get; internal set; }
public IModelSerializer ModelSerializer { get; internal set; }
public SerializationContext(IKernel Kernel, IModelSerializer ModelSerializer)
{
this.Kernel = Kernel;
this.ModelSerializer = ModelSerializer;
}
}
}

View file

@ -0,0 +1,37 @@
using Mina.Core.Buffer;
using System;
namespace FSO.Common.Serialization.TypeSerializers
{
public class cTSOValueBoolean : ITypeSerializer
{
private readonly uint CLSID = 0x696D1183;
public bool CanDeserialize(uint clsid)
{
return clsid == CLSID;
}
public bool CanSerialize(Type type)
{
return type.IsAssignableFrom(typeof(bool));
}
public object Deserialize(uint clsid, IoBuffer input, ISerializationContext serializer)
{
var byteValue = input.Get();
return byteValue == 0x01 ? true : false;
}
public void Serialize(IoBuffer output, object value, ISerializationContext serializer)
{
bool boolValue = (bool)value;
output.Put(boolValue ? (byte)0x01 : (byte)0x00);
}
public uint? GetClsid(object value)
{
return CLSID;
}
}
}

View file

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using Mina.Core.Buffer;
using System.Collections.Immutable;
namespace FSO.Common.Serialization.TypeSerializers
{
public class cTSOValueBooleanMap : ITypeSerializer
{
private readonly uint CLSID = 0xC97757F5;
public bool CanDeserialize(uint clsid)
{
return clsid == CLSID;
}
public bool CanSerialize(Type type)
{
return typeof(ImmutableDictionary<uint, bool>).IsAssignableFrom(type);
}
public object Deserialize(uint clsid, IoBuffer buffer, ISerializationContext serializer)
{
var result = new Dictionary<uint, bool>();
var count = buffer.GetUInt32();
for(int i=0; i < count; i++){
var key = buffer.GetUInt32();
result.Add(key, buffer.Get() > 0);
}
return ImmutableDictionary.ToImmutableDictionary(result);
}
public void Serialize(IoBuffer output, object value, ISerializationContext serializer)
{
var dict = (ImmutableDictionary<uint, bool>)value;
output.PutUInt32((uint)dict.Count);
foreach (var elem in dict)
{
output.PutUInt32(elem.Key);
output.Put((byte)(elem.Value ? 1 : 0));
}
}
public uint? GetClsid(object value)
{
return CLSID;
}
}
}

View file

@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using Mina.Core.Buffer;
using System.Collections.Immutable;
namespace FSO.Common.Serialization.TypeSerializers
{
public class cTSOValueBooleanVector : ITypeSerializer
{
private readonly uint CLSID = 0x89738492;
public bool CanDeserialize(uint clsid)
{
return clsid == CLSID;
}
public bool CanSerialize(Type type)
{
return typeof(IList<bool>).IsAssignableFrom(type);
}
public object Deserialize(uint clsid, IoBuffer buffer, ISerializationContext serializer)
{
var result = new List<bool>();
var count = buffer.GetUInt32();
for(int i=0; i < count; i++){
result.Add(buffer.Get() > 0);
}
return ImmutableList.ToImmutableList(result);
}
public void Serialize(IoBuffer output, object value, ISerializationContext serializer)
{
IList<bool> list = (IList<bool>)value;
output.PutUInt32((uint)list.Count);
byte last = 0;
int pos = 0;
for (int i = 0; i < list.Count; i++)
{
output.Put((byte)(list[i]?1:0));
//output.Put((byte)(1));
/*last |= (byte)((list[i] ? 1 : 0) << pos++);
if (pos >= 8)
{
output.Put(last);
pos = 0;
last = 0;
}*/
}
}
public uint? GetClsid(object value)
{
return CLSID;
}
}
}

View file

@ -0,0 +1,35 @@
using System;
using Mina.Core.Buffer;
namespace FSO.Common.Serialization.TypeSerializers
{
public class cTSOValueByte : ITypeSerializer
{
private readonly uint CLSID = 0xC976087C;
public bool CanDeserialize(uint clsid)
{
return clsid == CLSID;
}
public bool CanSerialize(Type type)
{
return type.IsAssignableFrom(typeof(byte));
}
public object Deserialize(uint clsid, IoBuffer input, ISerializationContext serializer)
{
return input.Get();
}
public void Serialize(IoBuffer output, object value, ISerializationContext serializer)
{
output.Put((byte)value);
}
public uint? GetClsid(object value)
{
return CLSID;
}
}
}

View file

@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using Mina.Core.Buffer;
using System.Collections.Immutable;
namespace FSO.Common.Serialization.TypeSerializers
{
public class cTSOValueByteVector : ITypeSerializer
{
private readonly uint CLSID = 0x097608AB;
public bool CanDeserialize(uint clsid)
{
return clsid == CLSID;
}
public bool CanSerialize(Type type)
{
return typeof(IList<byte>).IsAssignableFrom(type);
}
public object Deserialize(uint clsid, IoBuffer buffer, ISerializationContext serializer)
{
var result = new List<byte>();
var count = buffer.GetUInt32();
for(int i=0; i < count; i++){
result.Add(buffer.Get());
}
return ImmutableList.ToImmutableList(result);
}
public void Serialize(IoBuffer output, object value, ISerializationContext serializer)
{
IList<byte> list = (IList<byte>)value;
output.PutUInt32((uint)list.Count);
for (int i = 0; i < list.Count; i++)
{
output.Put(list[i]);
}
}
public uint? GetClsid(object value)
{
return CLSID;
}
}
}

View file

@ -0,0 +1,97 @@
using System;
using System.Collections.Generic;
using Mina.Core.Buffer;
using System.Reflection;
using FSO.Common.Utils;
namespace FSO.Common.Serialization.TypeSerializers
{
/// <summary>
/// Serializes / deserializes anything that implements IoBufferSerializable & IoBufferDeserializable and has a cTSOValue decoration
/// </summary>
public class cTSOValueDecorated : ITypeSerializer
{
protected Dictionary<uint, Type> ClsIdToType = new Dictionary<uint, Type>();
protected Dictionary<Type, uint> TypeToClsId = new Dictionary<Type, uint>();
public cTSOValueDecorated(){
//
//var assembly = Assembly.GetAssembly(typeof(cTSOSerializer));
var assemblies = AssemblyUtils.GetFreeSOLibs();
foreach(var asm in assemblies)
{
ScanAssembly(asm);
}
}
protected virtual void ScanAssembly(Assembly assembly)
{
try
{
foreach (Type type in assembly.GetTypes())
{
System.Attribute[] attributes = System.Attribute.GetCustomAttributes(type);
foreach (Attribute attribute in attributes)
{
if (attribute is cTSOValue)
{
foreach (uint clsid in ((cTSOValue)attribute).ClsId)
{
ClsIdToType.Add(clsid, type);
TypeToClsId.Add(type, clsid);
}
}
}
}
} catch (Exception)
{
}
}
public bool CanDeserialize(uint clsid)
{
return ClsIdToType.ContainsKey(clsid);
}
public bool CanSerialize(Type type)
{
return TypeToClsId.ContainsKey(type);
}
public object Deserialize(uint clsid, IoBuffer input, ISerializationContext serializer)
{
var instance = Activator.CreateInstance(ClsIdToType[clsid]);
((IoBufferDeserializable)instance).Deserialize(input, serializer);
return instance;
}
public uint? GetClsid(object value)
{
Type type = value.GetType();
if (TypeToClsId.ContainsKey(type))
{
return TypeToClsId[type];
}
return null;
}
public void Serialize(IoBuffer output, object value, ISerializationContext serializer)
{
((IoBufferSerializable)value).Serialize(output, serializer);
}
}
[System.AttributeUsage(System.AttributeTargets.Class)]
public class cTSOValue : System.Attribute
{
public uint[] ClsId;
public cTSOValue(params uint[] ClsId)
{
this.ClsId = ClsId;
}
}
}

View file

@ -0,0 +1,50 @@
using FSO.Common.Serialization.Primitives;
using Mina.Core.Buffer;
using System;
using System.Collections.Generic;
namespace FSO.Common.Serialization.TypeSerializers
{
class cTSOValueGenericData : ITypeSerializer
{
private readonly uint CLSID = 0xA99AF3B7;
public bool CanDeserialize(uint clsid)
{
return clsid == CLSID;
}
public bool CanSerialize(Type type)
{
return type.IsAssignableFrom(typeof(cTSOGenericData));
}
public object Deserialize(uint clsid, IoBuffer input, ISerializationContext serializer)
{
var result = new List<byte>();
var iclsid = input.GetUInt32();
var count = input.GetUInt32();
for (int i = 0; i < count; i++)
{
result.Add(input.Get());
}
return new cTSOGenericData(result.ToArray());
}
public void Serialize(IoBuffer output, object value, ISerializationContext serializer)
{
var dat = (cTSOGenericData)value;
output.PutUInt32(0x0A2C6585);
output.PutUInt32((uint)dat.Data.Length);
for (int i = 0; i < dat.Data.Length; i++)
{
output.Put(dat.Data[i]);
}
}
public uint? GetClsid(object value)
{
return CLSID;
}
}
}

View file

@ -0,0 +1,35 @@
using System;
using Mina.Core.Buffer;
namespace FSO.Common.Serialization.TypeSerializers
{
public class cTSOValueSByte : ITypeSerializer
{
private readonly uint CLSID = 0xE976088A;
public bool CanDeserialize(uint clsid)
{
return clsid == CLSID;
}
public bool CanSerialize(Type type)
{
return type.IsAssignableFrom(typeof(sbyte));
}
public object Deserialize(uint clsid, IoBuffer input, ISerializationContext serializer)
{
return (sbyte)input.Get();
}
public void Serialize(IoBuffer output, object value, ISerializationContext serializer)
{
output.Put((byte)(sbyte)value);
}
public uint? GetClsid(object value)
{
return CLSID;
}
}
}

View file

@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using Mina.Core.Buffer;
using System.Collections.Immutable;
namespace FSO.Common.Serialization.TypeSerializers
{
public class cTSOValueSByteVector : ITypeSerializer
{
private readonly uint CLSID = 0x097608AF;
public bool CanDeserialize(uint clsid)
{
return clsid == CLSID;
}
public bool CanSerialize(Type type)
{
return typeof(IList<sbyte>).IsAssignableFrom(type);
}
public object Deserialize(uint clsid, IoBuffer input, ISerializationContext serializer)
{
var result = new List<sbyte>();
var count = input.GetUInt32();
for(int i=0; i < count; i++){
result.Add((sbyte)input.Get());
}
return ImmutableList.ToImmutableList(result);
}
public void Serialize(IoBuffer output, object value, ISerializationContext serializer)
{
IList<sbyte> list = (IList<sbyte>)value;
output.PutUInt32((uint)list.Count);
for (int i = 0; i < list.Count; i++)
{
output.Put((byte)list[i]);
}
}
public uint? GetClsid(object value)
{
return CLSID;
}
}
}

View file

@ -0,0 +1,35 @@
using System;
using Mina.Core.Buffer;
namespace FSO.Common.Serialization.TypeSerializers
{
public class cTSOValueString : ITypeSerializer
{
private readonly uint CLSID = 0x896D1688;
public bool CanDeserialize(uint clsid)
{
return clsid == CLSID;
}
public bool CanSerialize(Type type)
{
return type.IsAssignableFrom(typeof(string));
}
public object Deserialize(uint clsid, IoBuffer input, ISerializationContext serializer)
{
return IoBufferUtils.GetPascalVLCString(input);
}
public void Serialize(IoBuffer output, object value, ISerializationContext serializer)
{
output.PutPascalVLCString((string)value);
}
public uint? GetClsid(object value)
{
return CLSID;
}
}
}

View file

@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using Mina.Core.Buffer;
using System.Collections.Immutable;
namespace FSO.Common.Serialization.TypeSerializers
{
public class cTSOValueStringVector : ITypeSerializer
{
private readonly uint CLSID = 0x8973849E;
public bool CanDeserialize(uint clsid)
{
return clsid == CLSID;
}
public bool CanSerialize(Type type)
{
return typeof(IList<string>).IsAssignableFrom(type);
}
public object Deserialize(uint clsid, IoBuffer input, ISerializationContext serializer)
{
var result = new List<String>();
var count = input.GetUInt32();
for(int i=0; i < count; i++){
result.Add(IoBufferUtils.GetPascalVLCString(input));
}
return ImmutableList.ToImmutableList(result);
}
public void Serialize(IoBuffer output, object value, ISerializationContext serializer)
{
IList<String> list = (IList<String>)value;
output.PutUInt32((uint)list.Count);
for(int i=0; i < list.Count; i++){
output.PutPascalVLCString(list[i]);
}
}
public uint? GetClsid(object value)
{
return CLSID;
}
}
}

View file

@ -0,0 +1,36 @@
using Mina.Core.Buffer;
using System;
namespace FSO.Common.Serialization.TypeSerializers
{
public class cTSOValueUInt16 : ITypeSerializer
{
//0xE9760891: cTSOValue<unsigned short>
private readonly uint CLSID = 0xE9760891;
public bool CanDeserialize(uint clsid)
{
return clsid == CLSID;
}
public bool CanSerialize(Type type)
{
return type.IsAssignableFrom(typeof(ushort));
}
public object Deserialize(uint clsid, IoBuffer input, ISerializationContext serializer)
{
return input.GetUInt16();
}
public void Serialize(IoBuffer output, object value, ISerializationContext serializer)
{
output.PutUInt16((ushort)value);
}
public uint? GetClsid(object value)
{
return CLSID;
}
}
}

View file

@ -0,0 +1,36 @@
using Mina.Core.Buffer;
using System;
namespace FSO.Common.Serialization.TypeSerializers
{
public class cTSOValueUInt32 : ITypeSerializer
{
//0x696D1189: cTSOValue<unsigned long>
private readonly uint CLSID = 0x696D1189;
public bool CanDeserialize(uint clsid)
{
return clsid == CLSID;
}
public bool CanSerialize(Type type)
{
return type.IsAssignableFrom(typeof(uint));
}
public object Deserialize(uint clsid, IoBuffer input, ISerializationContext serializer)
{
return input.GetUInt32();
}
public void Serialize(IoBuffer output, object value, ISerializationContext serializer)
{
output.PutUInt32((uint)value);
}
public uint? GetClsid(object value)
{
return CLSID;
}
}
}

View file

@ -0,0 +1,49 @@
using Mina.Core.Buffer;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
namespace FSO.Common.Serialization.TypeSerializers
{
class cTSOValueUInt32Vector : ITypeSerializer
{
//0x89738496: cTSOValueVector<unsigned long>
private readonly uint CLSID = 0x89738496;
public bool CanDeserialize(uint clsid)
{
return clsid == CLSID;
}
public bool CanSerialize(Type type)
{
return typeof(IList<uint>).IsAssignableFrom(type);
}
public object Deserialize(uint clsid, IoBuffer input, ISerializationContext serializer)
{
var result = new List<uint>();
var count = input.GetUInt32();
for (int i = 0; i < count; i++)
{
result.Add((uint)input.GetUInt32());
}
return ImmutableList.ToImmutableList(result);
}
public void Serialize(IoBuffer output, object value, ISerializationContext serializer)
{
IList<uint> list = (IList<uint>)value;
output.PutUInt32((uint)list.Count);
for (int i = 0; i < list.Count; i++)
{
output.PutUInt32((uint)list[i]);
}
}
public uint? GetClsid(object value)
{
return CLSID;
}
}
}

View file

@ -0,0 +1,36 @@
using Mina.Core.Buffer;
using System;
namespace FSO.Common.Serialization.TypeSerializers
{
public class cTSOValueUInt64 : ITypeSerializer
{
//0x69D3E3DB: cTSOValue<unsigned __int64>
private readonly uint CLSID = 0x69D3E3DB;
public bool CanDeserialize(uint clsid)
{
return clsid == CLSID;
}
public bool CanSerialize(Type type)
{
return type.IsAssignableFrom(typeof(ulong));
}
public object Deserialize(uint clsid, IoBuffer input, ISerializationContext serializer)
{
return input.GetUInt64();
}
public void Serialize(IoBuffer output, object value, ISerializationContext serializer)
{
output.PutUInt64((ulong)value);
}
public uint? GetClsid(object value)
{
return CLSID;
}
}
}

109
server/tso.common/TS1/TS1Curve.cs Executable file
View file

@ -0,0 +1,109 @@
using Microsoft.Xna.Framework;
using System;
using System.Linq;
namespace FSO.Common.TS1
{
/// <summary>
///
/// </summary>
public class TS1Curve
{
public Point[] Points; // ordered by x
public int CacheStart;
public float[] Cache;
public int[] CacheFixed;
public TS1Curve(string input)
{
input = input.Replace(");(", ") (");
var points = input.Split(' ');
var pointParse = points.Select(pointStr =>
{
var pointClean = pointStr.Substring(1, pointStr.Length - 2);
var split = pointClean.Split(';');
return new Point(int.Parse(split[0]), int.Parse(split[1]));
});
Points = pointParse.OrderBy(pt => pt.X).ToArray();
}
public float GetPoint(float input)
{
if (input < Points[0].X) return Points[0].Y;
// find the first point we're ahead of
// we want to interpolate between that point and the one after it
int i = 0;
for (i = Points.Length-1; i >= 0; i--)
{
var point = Points[i];
if (input >= point.X)
{
break;
}
}
if (i == Points.Length-1)
{
return Points[i].Y;
}
var start = Points[i];
var end = Points[i + 1];
var iF = (input - start.X) / (float)(end.X - start.X);
return (1 - iF) * start.Y + iF * end.Y;
}
public int GetPointFixed(int input)
{
if (input < Points[0].X) return Points[0].Y;
// find the first point we're ahead of
// we want to interpolate between that point and the one after it
int i = 0;
for (i = 0; i < Points.Length; i++)
{
var point = Points[i];
if (input >= point.X)
{
break;
}
}
if (i == Points.Length - 1)
{
return Points[i].Y;
}
var start = Points[i];
var end = Points[i + 1];
var iF = ((input - start.X) * 65536) / (end.X - start.X);
return (65536 - iF) * start.Y + iF * end.Y;
}
// CACHED ACCESS
public void BuildCache(int min, int max)
{
Cache = new float[(max - min) + 1];
CacheFixed = new int[(max - min) + 1];
CacheStart = min;
for (int i=min; i<=max; i++)
{
Cache[i - min] = GetPoint(i);
CacheFixed[i - min] = GetPointFixed(i);
}
}
public float GetCachedPoint(int point)
{
point = Math.Max(CacheStart, Math.Min(CacheStart + Cache.Length - 1, point));
return Cache[point];
}
public int GetCachedPointFixed(int point)
{
point = Math.Max(CacheStart, Math.Min(CacheStart + CacheFixed.Length - 1, point));
return CacheFixed[point];
}
}
}

View file

@ -0,0 +1,32 @@
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace FSO.Common.Utils
{
public class AssemblyUtils
{
public static Assembly Entry;
public static List<Assembly> GetFreeSOLibs()
{
var map = new Dictionary<string, Assembly>();
if (Entry == null) Entry = Assembly.GetEntryAssembly();
RecurseAssembly(Entry, map);
return map.Values.ToList();
}
private static void RecurseAssembly(Assembly assembly, Dictionary<string, Assembly> map)
{
var refs = assembly.GetReferencedAssemblies();
foreach (var refAsm in refs)
{
if ((refAsm.Name.StartsWith("FSO.") || refAsm.Name.Equals("FreeSO") || refAsm.Name.Equals("server")) && !map.ContainsKey(refAsm.Name))
{
var loadedAssembly = Assembly.Load(refAsm);
map.Add(refAsm.Name, loadedAssembly);
RecurseAssembly(loadedAssembly, map);
}
};
}
}
}

View file

@ -0,0 +1,126 @@
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
namespace FSO.Common.Utils
{
/// <summary>
/// In:
/// - String Containing BBCode
///
/// [color=red]Hello![/color]
/// [color=#FF0000]Also [size=20]red[/size][/color]
/// \[ escaped \] square brackets.
///
/// Out:
/// - String Without BBCode
/// - Ordered list of BBCodeCommands, and the index in the converted string they occur at.
/// </summary>
public class BBCodeParser
{
public string Stripped;
public List<BBCodeCommand> Commands = new List<BBCodeCommand>();
public BBCodeParser(string input)
{
var stripped = new StringBuilder();
int index = 0;
while (index < input.Length)
{
var newIndex = input.IndexOf('[', index);
if (newIndex == -1)
{
newIndex = input.Length;
//no more commands.
//render the rest of the string and break out.
stripped.Append(input.Substring(index, newIndex - index));
break;
}
stripped.Append(input.Substring(index, newIndex - index));
//we found the start of a bbcode. is it escaped?
if (newIndex > 0 && input[newIndex - 1] == '\\')
{
//draw the bracket.
stripped[stripped.Length - 1] = '['; //replace the backslash with the leftbracket
index = newIndex + 1; //continue after the bracket
} else
{
//find our right bracket
var endIndex = input.IndexOf(']', newIndex);
if (endIndex == -1)
{
//fail safe.
stripped.Append('[');
index = newIndex + 1; //continue after the bracket
} else
{
Commands.Add(new BBCodeCommand(input.Substring(newIndex + 1, (endIndex - newIndex) - 1), stripped.Length));
index = endIndex + 1;
}
}
}
Stripped = stripped.ToString();
}
public static string SanitizeBB(string input)
{
if (input.LastOrDefault() == '\\') input += ' ';
return input.Replace("[", "\\[");
}
}
public class BBCodeCommand
{
public BBCodeCommandType Type;
public string Parameter;
public int Index;
public bool Close;
public BBCodeCommand(string cmd, int index)
{
Index = index;
if (cmd[0] == '/')
{
Close = true;
cmd = cmd.Substring(1);
}
var split = cmd.Split('=');
if (split.Length > 1) Parameter = split[1];
System.Enum.TryParse(split[0], out Type);
}
public Color ParseColor()
{
if (Parameter == null || Parameter.Length == 0) return Color.White;
//todo: search color static members for named colours
//for now we only support hex
if (Parameter.Length == 7 && Parameter[0] == '#')
{
uint rgb;
if (uint.TryParse(Parameter.Substring(1), NumberStyles.HexNumber, CultureInfo.CurrentCulture, out rgb)) {
return new Color((byte)(rgb >> 16), (byte)(rgb >> 8), (byte)(rgb), (byte)255);
}
} else
{
var color = typeof(Color).GetProperties().Where(x => x.PropertyType == typeof(Color) && x.Name.ToLowerInvariant() == Parameter.ToLowerInvariant()).FirstOrDefault();
if (color != null)
{
return (Color)color.GetValue(null);
}
}
return Color.White;
}
}
public enum BBCodeCommandType
{
unknown,
color,
size,
emoji,
s
}
}

View file

@ -0,0 +1,29 @@
using System;
using Newtonsoft.Json;
namespace FSO.Common.Utils
{
public class Base64JsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
if (typeof(byte[]).IsAssignableFrom(objectType))
{
return true;
}
return false;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
string data = (string)reader.Value;
return Convert.FromBase64String(data);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
byte[] bytes = (byte[])value;
writer.WriteValue(Convert.ToBase64String(bytes));
}
}
}

View file

@ -0,0 +1,301 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
namespace FSO.Common.Utils
{
public class Binding <T> : IDisposable where T : INotifyPropertyChanged
{
private List<IBinding> Bindings = new List<IBinding>();
private HashSet<INotifyPropertyChanged> Watching = new HashSet<INotifyPropertyChanged>();
private T _Value;
public event Callback<T> ValueChanged;
public Binding()
{
lock(Binding.All)
Binding.All.Add(this);
}
~Binding()
{
//i don't think the garbage collector will remove this if there is still a reference to its callback function.
ClearWatching();
}
public void Dispose()
{
ClearWatching();
Bindings = null;
}
public T Value
{
get
{
return _Value;
}
set
{
ClearWatching();
_Value = value;
if(_Value != null){
Watch(_Value, null);
}
Digest();
if(ValueChanged != null)
{
ValueChanged(_Value);
}
}
}
private void ClearWatching()
{
lock (Watching)
{
var clone = new List<INotifyPropertyChanged>(Watching);
foreach (var item in clone)
{
item.PropertyChanged -= OnPropertyChanged;
Watching.Remove(item);
}
}
}
private void Watch(INotifyPropertyChanged source, HashSet<INotifyPropertyChanged> toWatch)
{
lock (Watching)
{
if (!Watching.Contains(source))
{
source.PropertyChanged += OnPropertyChanged;
Watching.Add(source);
}
toWatch?.Add(source);
var properties = source.GetType().GetProperties();
foreach (var property in properties)
{
if (typeof(INotifyPropertyChanged).IsAssignableFrom(property.PropertyType))
{
var value = (INotifyPropertyChanged)property.GetValue(source, null);
if (value != null)
{
Watch(value, toWatch);
}
}
}
}
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
lock (Watching)
{
var property = sender.GetType().GetProperty(e.PropertyName);
if (typeof(INotifyPropertyChanged).IsAssignableFrom(property.PropertyType))
{
//We may have a new nested object to watch
//ClearWatching();
if (_Value != null)
{
var toWatch = new HashSet<INotifyPropertyChanged>();
Watch(_Value, toWatch);
var toRemove = Watching.Except(toWatch).ToList();
foreach (var rem in toRemove)
{
Watching.Remove(rem);
rem.PropertyChanged -= OnPropertyChanged;
}
}
}
}
Digest();
}
//Using a dumb digest system for now, not very efficient but works
private void Digest()
{
GameThread.InUpdate(() => _Digest());
}
private void _Digest()
{
lock (this)
{
if (Bindings == null) return;
foreach (var binding in Bindings)
{
binding.Digest(_Value);
}
}
}
public Binding<T> WithBinding(object target, string targetProperty, string sourcePath)
{
//If top level changes, we need to update children
//If member of top level changes, we need to update children
var binding = new DotPathBinding(target, targetProperty, DotPath.CompileDotPath(typeof(T), sourcePath));
Bindings.Add(binding);
return this;
}
public Binding<T> WithBinding(object target, string targetProperty, string sourcePath, Func<object, object> valueConverter)
{
//If top level changes, we need to update children
//If member of top level changes, we need to update children
var binding = new DotPathBinding(target, targetProperty, DotPath.CompileDotPath(typeof(T), sourcePath), valueConverter);
Bindings.Add(binding);
return this;
}
public Binding<T> WithMultiBinding(Callback<BindingChange[]> callback, params string[] paths)
{
var compiledPaths = new PropertyInfo[paths.Length][];
for(int i=0; i < paths.Length; i++){
compiledPaths[i] = DotPath.CompileDotPath(typeof(T), paths[i]);
}
var binding = new MultiDotPathBinding(callback, paths, compiledPaths);
Bindings.Add(binding);
return this;
}
}
public class BindingChange
{
public string Path;
public object PreviousValue;
public object Value;
}
class MultiDotPathBinding : IBinding
{
private Callback<BindingChange[]> Callback;
private PropertyInfo[][] Paths;
private string[] PathStrings;
private object[] Values;
public MultiDotPathBinding(Callback<BindingChange[]> callback, string[] pathStrings, PropertyInfo[][] paths)
{
Callback = callback;
Paths = paths;
PathStrings = pathStrings;
Values = new object[Paths.Length];
}
public void Digest(object source)
{
List<BindingChange> changes = null;
for(int i=0; i < Paths.Length; i++){
var path = Paths[i];
var value = DotPath.GetDotPathValue(source, path);
if(value != Values[i]){
//Changed
if (changes == null) { changes = new List<BindingChange>(); }
changes.Add(new BindingChange()
{
Path = PathStrings[i],
PreviousValue = Values[i],
Value = value
});
Values[i] = value;
}
}
if(changes != null)
{
Callback(changes.ToArray());
}
}
}
class DotPathBinding : Binding
{
private PropertyInfo[] Path;
private object LastValue;
private Func<object, object> Converter;
public DotPathBinding(object target, string targetProperty, PropertyInfo[] path, Func<object, object> converter) : this(target, targetProperty, path)
{
this.Converter = converter;
}
public DotPathBinding(object target, string targetProperty, PropertyInfo[] path) : base(target, targetProperty)
{
this.Path = path;
}
public override void Digest(object source){
var value = GetValue(source);
if(value != LastValue){
LastValue = value;
if (Converter != null)
{
SetValue(Converter(value));
}
else
{
SetValue(value);
}
}
}
private object GetValue(object source)
{
return DotPath.GetDotPathValue(source, Path);
}
}
public abstract class Binding : IBinding
{
private object Target;
private string TargetProperty;
private PropertyInfo[] TargetPropertyPath;
public Binding(object target, string targetProperty)
{
Target = target;
TargetProperty = targetProperty;
TargetPropertyPath = DotPath.CompileDotPath(target.GetType(), targetProperty);
}
protected void SetValue(object value)
{
DotPath.SetDotPathValue(Target, TargetPropertyPath, value);
}
public abstract void Digest(object source);
public virtual void Dispose() { Target = null; }
public static List<IDisposable> All = new List<IDisposable>();
public static void DisposeAll()
{
//dispose of all bindings that are left over (city leave return)
lock (All)
{
foreach (var item in All)
{
item.Dispose();
}
All.Clear();
}
}
}
interface IBinding
{
void Digest(object source);
//object GetValue(object source);
//void SetValue(object value);
}
}

View file

@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace FSO.Common.Utils.Cache
{
public class CacheKey : IEqualityComparer<CacheKey>, IEquatable<CacheKey>
{
public static CacheKey Root = CacheKey.For();
public string[] Components { get; internal set; }
protected CacheKey(string[] components)
{
this.Components = components;
}
public override int GetHashCode()
{
return GetHashCode(this);
}
public bool Equals(CacheKey x, CacheKey y)
{
return x.Components.SequenceEqual(y.Components);
}
public bool Equals(CacheKey other)
{
return Equals(this, other);
}
public int GetHashCode(CacheKey obj)
{
int hashcode = 0;
foreach (string value in obj.Components)
{
if (value != null)
hashcode += value.GetHashCode();
}
return hashcode;
}
public static CacheKey For(params object[] components)
{
return new CacheKey(components.Select(x => "" + x).ToArray());
}
public static CacheKey Combine(CacheKey domain, params object[] components)
{
var newComponents = new string[domain.Components.Length+components.Length];
Array.Copy(domain.Components, newComponents, domain.Components.Length);
Array.Copy(components.Select(x => "" + x).ToArray(), 0, newComponents, domain.Components.Length, components.Length);
return new CacheKey(newComponents);
}
}
}

View file

@ -0,0 +1,340 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace FSO.Common.Utils.Cache
{
public class FileSystemCache : ICache
{
private const int DIGEST_DELAY = 10000;
private string _Directory { get; set; }
private Queue<FileSystemCacheMutation> _Mutations;
private Thread _DigestThread;
private bool _Active;
private long _CacheSize;
private long _MaxCacheSize;
private Thread _MainThread;
private LinkedList<FileSystemCacheEntry> _Cache;
private Dictionary<CacheKey, FileSystemCacheEntry> _Index;
public FileSystemCache(string directory, long maxSize)
{
_MainThread = Thread.CurrentThread;
_Directory = directory;
_Mutations = new Queue<FileSystemCacheMutation>();
_CacheSize = 0;
_MaxCacheSize = maxSize;
_Cache = new LinkedList<FileSystemCacheEntry>();
_Index = new Dictionary<CacheKey, FileSystemCacheEntry>();
}
public void Init()
{
var tempList = new List<FileSystemCacheEntry>();
_ScanDirectory(CacheKey.Root, tempList);
foreach(var item in tempList.OrderByDescending(x => x.LastRead)){
_Cache.AddLast(item);
_Index.Add(item.Key, item);
_CacheSize += item.Length;
}
_Active = true;
_DigestThread = new Thread(DigestLoop);
_DigestThread.Priority = ThreadPriority.BelowNormal;
_DigestThread.Start();
}
private void DigestLoop()
{
while (_Active && _MainThread?.IsAlive != false)
{
Digest();
Thread.Sleep(DIGEST_DELAY);
}
}
private void Digest()
{
lock (_Mutations)
{
/**
* We can avoid some work & disk hits by removing tasks for keys modified in later tasks
*/
List<FileSystemCacheMutation> tasks = new List<FileSystemCacheMutation>();
Dictionary<CacheKey, FileSystemCacheMutation> taskIndex = new Dictionary<CacheKey, FileSystemCacheMutation>();
FileSystemCacheMutation mutation;
while (_Mutations.Count > 0 && (mutation = _Mutations.Dequeue()) != null)
{
tasks.Add(mutation);
FileSystemCacheMutation previousTask = null;
taskIndex.TryGetValue(mutation.Key, out previousTask);
if (previousTask == null) {
taskIndex[mutation.Key] = mutation;
continue;
}
tasks.Remove(previousTask);
taskIndex[mutation.Key] = mutation;
}
int bytesRequired = 0;
foreach(var task in tasks){
bytesRequired += task.GetBytesRequired(this);
}
if(bytesRequired > 0 && (_CacheSize + bytesRequired) >= _MaxCacheSize)
{
//We need to evict some entries to make room
while((_CacheSize + bytesRequired) >= _MaxCacheSize && _Cache.Count > 0)
{
var last = _Cache.Last;
try {
new FileSystemCacheRemoveMutation() {
Key = last.Value.Key
}.Execute(this);
}catch(Exception ex){
}
CalculateCacheSize();
}
}
foreach (var task in tasks)
{
try
{
task.Execute(this);
}
catch (Exception ex)
{
}
}
//Recalculate cache size if we made changes
if(tasks.Count > 0){
CalculateCacheSize();
}
}
}
private void CalculateCacheSize()
{
long size = 0;
foreach(var item in _Cache){
size += item.Length;
}
_CacheSize = size;
}
private void _ScanDirectory(CacheKey parent, List<FileSystemCacheEntry> tempList)
{
var dir = GetFilePath(parent);
if (!Directory.Exists(dir)) { return; }
var info = new DirectoryInfo(dir);
foreach(FileInfo file in info.GetFiles())
{
var key = CacheKey.Combine(parent, file.Name);
tempList.Add(new FileSystemCacheEntry {
Key = key,
LastRead = file.LastAccessTime,
LastWrite = file.LastWriteTime,
Length = file.Length
});
}
foreach(var subDir in info.GetDirectories())
{
var key = CacheKey.Combine(parent, subDir.Name);
_ScanDirectory(key, tempList);
}
}
public bool ContainsKey(CacheKey key)
{
return _Index.ContainsKey(key);
}
public void Add(CacheKey key, byte[] bytes)
{
var clone = new byte[bytes.Length];
Buffer.BlockCopy(bytes, 0, clone, 0, bytes.Length);
lock (_Mutations)
{
_Mutations.Enqueue(new FileSystemCacheAddMutation
{
Key = key,
Data = clone
});
}
}
public void Remove(CacheKey key)
{
lock (_Mutations)
{
_Mutations.Enqueue(new FileSystemCacheRemoveMutation
{
Key = key
});
}
}
public Task<T> Get<T>(CacheKey key)
{
if(typeof(T) == typeof(byte[]))
{
return Task.Factory.StartNew(() =>
{
var file = GetFilePath(key);
byte[] result = null;
if (File.Exists(file)){
result = File.ReadAllBytes(file);
}else{
throw new Exception("File not found");
}
TouchEntry(key);
return (T)(object)result;
});
}
throw new Exception("Not implemented yet");
}
public void Dispose()
{
_Active = false;
}
internal string GetFilePath(CacheKey key)
{
return Path.Combine(_Directory, Path.Combine(key.Components));
}
internal FileSystemCacheEntry GetEntry(CacheKey key)
{
FileSystemCacheEntry entry = null;
_Index.TryGetValue(key, out entry);
return entry;
}
internal void AddEntry(FileSystemCacheEntry entry)
{
var existing = GetEntry(entry.Key);
if(existing != null){
_Cache.Remove(existing);
}
_Cache.AddLast(entry);
_Index[entry.Key] = entry;
}
internal void RemoveEntry(CacheKey key)
{
var existing = GetEntry(key);
if (existing != null)
{
_Cache.Remove(existing);
_Index.Remove(key);
}
}
internal void TouchEntry(CacheKey key)
{
var existing = GetEntry(key);
if (existing != null)
{
_Cache.AddFirst(existing);
}
}
}
public interface FileSystemCacheMutation
{
CacheKey Key { get; }
int GetBytesRequired(FileSystemCache cache);
void Execute(FileSystemCache cache);
}
public class FileSystemCacheAddMutation : FileSystemCacheMutation
{
public CacheKey Key { get; set; }
public byte[] Data { get; set; }
public void Execute(FileSystemCache cache)
{
var path = cache.GetFilePath(Key);
var finalPart = path.LastIndexOf('/');
Directory.CreateDirectory((finalPart == -1)?path:path.Substring(0, finalPart));
File.WriteAllBytes(path, Data);
var entry = cache.GetEntry(Key);
if (entry == null)
{
entry = new FileSystemCacheEntry();
entry.Key = Key;
entry.LastRead = DateTime.MinValue;
entry.LastWrite = DateTime.Now;
entry.Length = Data.Length;
cache.AddEntry(entry);
}
else
{
entry.Length = Data.Length;
}
}
public int GetBytesRequired(FileSystemCache cache)
{
var existingFile = cache.GetEntry(Key);
if(existingFile != null)
{
return Data.Length - (int)existingFile.Length;
}
return Data.Length;
}
}
public class FileSystemCacheRemoveMutation : FileSystemCacheMutation
{
public CacheKey Key { get; set; }
public void Execute(FileSystemCache cache)
{
File.Delete(cache.GetFilePath(Key));
cache.RemoveEntry(Key);
}
public int GetBytesRequired(FileSystemCache cache)
{
var existingFile = cache.GetEntry(Key);
if (existingFile != null)
{
return -((int)existingFile.Length);
}
return 0;
}
}
public class FileSystemCacheEntry
{
public CacheKey Key;
public DateTime LastWrite;
public DateTime LastRead;
public long Length;
}
}

View file

@ -0,0 +1,20 @@
using System;
using System.Threading.Tasks;
namespace FSO.Common.Utils.Cache
{
public interface ICache : IDisposable
{
bool ContainsKey(CacheKey key);
void Add(CacheKey key, byte[] bytes);
void Remove(CacheKey key);
Task<T> Get<T>(CacheKey key);
//bool IsReady { get; }
//bool Contains(string type, string key);
//Task<byte[]> GetBytes(string type, string key);
//Task PutBytes(string type, string key, byte[] bytes);
//Task Init();
}
}

View file

@ -0,0 +1,8 @@
namespace FSO.Common.Utils
{
public delegate void Callback();
public delegate void Callback<T>(T data);
public delegate void Callback<T, T2>(T data, T2 data2);
public delegate void Callback<T, T2, T3>(T data, T2 data2, T3 data3);
}

View file

@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
namespace FSO.Client.Utils
{
public static class CollectionUtils
{
public static void Shuffle<T>(this IList<T> list)
{
Random rng = new Random();
int n = list.Count;
while (n > 1)
{
n--;
int k = rng.Next(n + 1);
T value = list[k];
list[k] = list[n];
list[n] = value;
}
}
public static IEnumerable<TResult> Select<TSource, TResult>(this Array items, Func<TSource, TResult> converter)
{
var result = new List<TResult>();
foreach (var item in items)
{
result.Add(converter((TSource)item));
}
return result;
}
public static Dictionary<TKey, TValue> Clone<TKey, TValue>(Dictionary<TKey, TValue> input)
{
var result = new Dictionary<TKey, TValue>();
foreach (var val in input)
{
result.Add(val.Key, val.Value);
}
return result;
}
private static Random RAND = new Random();
public static T RandomItem<T>(this T[] items)
{
var index = RAND.Next(items.Length);
return items[index];
}
}
}

View file

@ -0,0 +1,110 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System;
using System.IO;
namespace FSO.Common.Utils
{
public static class CurLoader
{
public static MouseCursor LoadMonoCursor(GraphicsDevice gd, Stream stream)
{
var cur = LoadCursor(gd, stream);
return MouseCursor.FromTexture2D(cur.Item1, cur.Item2.X, cur.Item2.Y);
}
public static MouseCursor[] LoadUpgradeCursors(GraphicsDevice gd, Stream stream, int maxStars)
{
var cur = LoadCursor(gd, stream);
Texture2D starTex;
using (var str = File.Open("Content/uigraphics/upgrade_star.png", FileMode.Open, FileAccess.Read, FileShare.Read))
{
starTex = Texture2D.FromStream(gd, str);
}
var batch = new SpriteBatch(gd);
var results = new MouseCursor[maxStars];
for (int i = 0; i < maxStars; i++) {
var starPos = cur.Item1.Width - 12;
var width = Math.Max(starPos + 8 * i + 9, cur.Item1.Width);
var tex = new RenderTarget2D(gd, width, Math.Max(width, cur.Item1.Height));
gd.SetRenderTarget(tex);
gd.Clear(Color.Transparent);
batch.Begin(SpriteSortMode.Immediate);
batch.Draw(cur.Item1, Vector2.Zero, Color.White);
for (int j=0; j<=i; j++)
{
batch.Draw(starTex, new Vector2(starPos, 5), Color.White);
starPos += 8;
}
batch.End();
gd.SetRenderTarget(null);
results[i] = MouseCursor.FromTexture2D(tex, cur.Item2.X, cur.Item2.Y);
}
starTex.Dispose();
return results;
}
public static Func<GraphicsDevice, Stream, Texture2D> BmpLoaderFunc;
public static Tuple<Texture2D, Point> LoadCursor(GraphicsDevice gd, Stream stream)
{
using (var io = new BinaryReader(stream))
{
//little endian
var tempbmp = new MemoryStream();
var outIO = new BinaryWriter(tempbmp);
var reserved = io.ReadInt16();
var type = io.ReadInt16();
if (type != 2) throw new Exception("Not a cursor!");
var images = io.ReadInt16(); //currently discard extra images...
//read first image
var width = io.ReadByte();
var height = io.ReadByte();
var colors = io.ReadByte();
var reserved2 = io.ReadByte();
var xOffset = io.ReadInt16();
var yOffset = io.ReadInt16();
var size = io.ReadInt32();
var offset = io.ReadInt32();
stream.Seek(offset - 22, SeekOrigin.Current);
//ok... now write the bitmap data to a fake bitmap
outIO.Write(new char[] { 'B', 'M' });
outIO.Write(size + 14); //size, plus header
outIO.Write(0);
outIO.Write(14);
var data = new byte[size];
stream.Read(data, 0, size);
outIO.Write(data);
tempbmp.Seek(0, SeekOrigin.Begin);
var tex = BmpLoaderFunc(gd, tempbmp);
//our mask is on top. the image is on bottom.
var odata = new byte[tex.Width * tex.Height * 4];
tex.GetData(odata);
var ndata = new byte[tex.Width * tex.Height * 2];
var limit = ndata.Length;
for (int i=0; i< limit; i+=4)
{
var j = i + limit;
ndata[i] = (byte)((~odata[i]) & odata[j]);
ndata[i+1] = ndata[i];
ndata[i+2] = ndata[i];
ndata[i+3] = (byte)(~odata[i]);
}
var oTex = new Texture2D(gd, width, height);
oTex.SetData(ndata);
tex.Dispose();
return new Tuple<Texture2D, Point>(oTex, new Point(xOffset, yOffset));
}
}
}
}

View file

@ -0,0 +1,12 @@
using Newtonsoft.Json;
namespace FSO.Common.Utils
{
public static class DebugUtils
{
public static string LogObject(object obj)
{
return JsonConvert.SerializeObject(obj);
}
}
}

View file

@ -0,0 +1,75 @@
using Microsoft.Xna.Framework;
using System;
namespace FSO.Common.Utils
{
public static class DirectionUtils
{
/// <summary>
/// Finds the difference between two radian directions.
/// </summary>
/// <param name="a">The direction to subtract from.</param>
/// <param name="b">The direction to subtract.</param>
public static double Difference(double a, double b) {
double value = PosMod(b-a, Math.PI*2);
if (value > Math.PI) value -= Math.PI * 2;
return value;
}
/// <summary>
/// Normalizes a direction to the range -PI through PI.
/// </summary>
/// <param name="dir">The direction to normalize.</param>
public static double Normalize(double dir)
{
dir = PosMod(dir, Math.PI * 2);
if (dir > Math.PI) dir -= Math.PI * 2;
return dir;
}
/// <summary>
/// Normalizes a direction in degrees to the range -180 through 180.
/// </summary>
/// <param name="dir">The direction to normalize.</param>
public static double NormalizeDegrees(double dir)
{
dir = PosMod(dir, 360);
if (dir > 180) dir -= 360;
return dir;
}
/// <summary>
/// Calculates the mathematical modulus of a value.
/// </summary>
/// <param name="x">The number to mod.</param>
/// <param name="x">The factor to use.</param>
public static double PosMod(double x, double m)
{
return (x % m + m) % m;
}
private static int[] tab32 = new int[] {
0, 9, 1, 10, 13, 21, 2, 29,
11, 14, 16, 18, 22, 25, 3, 30,
8, 12, 20, 28, 15, 17, 24, 7,
19, 27, 23, 6, 26, 5, 4, 31
};
public static int Log2Int(uint value)
{
value |= value >> 1;
value |= value >> 2;
value |= value >> 4;
value |= value >> 8;
value |= value >> 16;
return tab32[(uint)(value * 0x07C4ACDD) >> 27];
}
public static float QuaternionDistance(Quaternion q1, Quaternion q2)
{
var inner = q1.X * q2.X + q1.Y * q2.Y + q1.Z * q2.Z + q1.W * q2.W;
return (float)Math.Acos(2 * (inner * inner) - 1);
}
}
}

View file

@ -0,0 +1,54 @@
using System;
using System.Reflection;
namespace FSO.Common.Utils
{
public static class DotPath
{
public static PropertyInfo[] CompileDotPath(Type sourceType, string sourcePath)
{
//Dot path
var path = sourcePath.Split(new char[] { '.' });
var properties = new PropertyInfo[path.Length];
var currentType = sourceType;
for (int i = 0; i < path.Length; i++)
{
var property = currentType.GetProperty(path[i]);
properties[i] = property;
currentType = property.PropertyType;
}
return properties;
}
public static object GetDotPathValue(object source, PropertyInfo[] path)
{
if (source == null) { return null; }
var currentValue = source;
for (var i = 0; i < path.Length; i++)
{
currentValue = path[i].GetValue(currentValue, null);
if (currentValue == null) { return null; }
}
return currentValue;
}
public static void SetDotPathValue(object source, PropertyInfo[] path, object value)
{
if (source == null) { return; }
var currentValue = source;
for (var i = 0; i < path.Length - 1; i++)
{
currentValue = path[i].GetValue(currentValue, null);
if (currentValue == null) { return; }
}
var member = path[path.Length - 1];
member.SetValue(currentValue, value, null);
}
}
}

View file

@ -0,0 +1,68 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
namespace FSO.Common.Utils
{
public class FeatureLevelTest
{
public static bool UpdateFeatureLevel(GraphicsDevice gd)
{
//if 3d is enabled, check if we support non-power-of-two mipmaps
if (FSOEnvironment.SoftwareKeyboard && FSOEnvironment.SoftwareDepth)
{
FSOEnvironment.EnableNPOTMip = false;
return true;
}
try
{
using (var mipTest = new Texture2D(gd, 11, 11, true, SurfaceFormat.Color))
{
var data = new Color[11 * 11];
TextureUtils.UploadWithMips(mipTest, gd, data);
}
}
catch (Exception e)
{
FSOEnvironment.EnableNPOTMip = false;
}
try
{
using (var mipTest = new Texture2D(gd, 4, 4, true, SurfaceFormat.Dxt5))
{
var data = new byte[16];
mipTest.SetData(data);
}
}
catch (Exception e)
{
FSOEnvironment.TexCompressSupport = false;
}
//msaa test
try
{
var msaaTarg = new RenderTarget2D(gd, 1, 1, false, SurfaceFormat.Color, DepthFormat.None, 8, RenderTargetUsage.PreserveContents);
gd.SetRenderTarget(msaaTarg);
gd.Clear(Color.Red);
var tex = TextureUtils.CopyAccelerated(gd, msaaTarg);
var result = new Color[1];
tex.GetData(result);
FSOEnvironment.MSAASupport = result[0] == Color.Red;
gd.SetRenderTarget(null);
msaaTarg.Dispose();
tex.Dispose();
}
catch
{
gd.SetRenderTarget(null);
FSOEnvironment.MSAASupport = false;
}
return true;
}
}
}

View file

@ -0,0 +1,25 @@
using System;
using System.IO;
using System.Security.Cryptography;
namespace FSO.Common.Utils
{
public class FileUtils
{
public static string ComputeMD5(string filePath){
var bytes = ComputeMD5Bytes(filePath);
return BitConverter.ToString(bytes).Replace("-", "").ToLowerInvariant();
}
public static byte[] ComputeMD5Bytes(string filePath)
{
using (var md5 = MD5.Create())
{
using (var stream = File.OpenRead(filePath))
{
return md5.ComputeHash(stream);
}
}
}
}
}

View file

@ -0,0 +1,274 @@
using FSO.Common.Rendering.Framework.Model;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace FSO.Common.Utils
{
public class GameThreadInterval
{
private Callback Callback;
private long Interval;
private double EndTime = -1;
private UpdateHook _TickHook;
private bool _Clear;
public GameThreadInterval(Callback callback, long interval)
{
Callback = callback;
Interval = interval;
_TickHook = GameThread.EveryUpdate(Tick);
}
public void Clear()
{
_Clear = true;
}
private void Tick(UpdateState state)
{
if (_Clear)
{
_TickHook.Remove();
return;
}
var now = state.Time.TotalGameTime.TotalMilliseconds;
if (EndTime == -1)
{
EndTime = now + Interval;
}
if (EndTime <= now)
{
Callback();
EndTime = now + Interval;
}
}
}
public class GameThreadTimeout
{
private Callback Callback;
private long Delay;
private double EndTime = -1;
private UpdateHook _TickHook;
private bool _Clear;
public GameThreadTimeout(Callback callback, long delay)
{
Callback = callback;
Delay = delay;
_TickHook = GameThread.EveryUpdate(Tick);
}
public void Clear(){
_Clear = true;
}
private void Tick(UpdateState state)
{
if (_Clear){
_TickHook.Remove();
return;
}
var now = state.Time.TotalGameTime.TotalMilliseconds;
if (EndTime == -1){
EndTime = now + Delay;
}
if(EndTime <= now){
_TickHook.Remove();
Callback();
}
}
}
public class UpdateHook
{
public bool RemoveNext = false;
public Callback<UpdateState> Callback;
public void Remove(){
RemoveNext = true;
}
}
public class GameThread
{
public static bool Killed;
public static bool NoGame;
public static EventWaitHandle OnKilled = new EventWaitHandle(false, EventResetMode.ManualReset);
public static event Action KilledEvent;
public static Thread Game;
public static bool UpdateExecuting;
private static List<UpdateHook> _UpdateHooks = new List<UpdateHook>();
private static UpdateHook[] _UpdateHooksCopy = new UpdateHook[0];
private static List<UpdateHook> _UpdateHooksRemove = new List<UpdateHook>();
private static Queue<Callback<UpdateState>> _UpdateCallbacks = new Queue<Callback<UpdateState>>();
private static Queue<Callback<UpdateState>> _UpdateCallbacksSwap = new Queue<Callback<UpdateState>>();
public static AutoResetEvent OnWork = new AutoResetEvent(false);
public static void SetKilled()
{
Killed = true;
OnKilled.Set();
KilledEvent?.Invoke();
}
public static GameThreadTimeout SetTimeout(Callback callback, long delay)
{
var result = new GameThreadTimeout(callback, delay);
return result;
}
public static GameThreadInterval SetInterval(Callback callback, long delay)
{
var result = new GameThreadInterval(callback, delay);
return result;
}
//I know we already have a way to do this with IUIProcess but we need a way for other libs that dont
//have the UI code for reference
public static UpdateHook EveryUpdate(Callback<UpdateState> callback)
{
var newHook = new UpdateHook()
{
Callback = callback
};
lock (_UpdateHooks)
{
_UpdateHooks.Add(newHook);
}
return newHook;
}
public static void NextUpdate(Callback<UpdateState> callback)
{
lock (_UpdateCallbacks)
{
_UpdateCallbacks.Enqueue(callback);
}
OnWork.Set();
}
public static void InUpdate(Callback callback)
{
if (IsInGameThread() && UpdateExecuting){
callback();
}else{
NextUpdate(x => callback());
}
}
public static bool IsInGameThread()
{
var thread = Thread.CurrentThread;
if (thread == Game || NoGame)
{
return true;
}
return false;
}
public static Task<T> NextUpdate<T>(Func<UpdateState, T> callback)
{
TaskCompletionSource<T> task = new TaskCompletionSource<T>();
lock (_UpdateCallbacks)
{
_UpdateCallbacks.Enqueue(x =>
{
task.SetResult(callback(x));
});
}
return TimeoutAfter(task.Task, new TimeSpan(0, 0, 5));
}
public static async Task<TResult> TimeoutAfter<TResult>(Task<TResult> task, TimeSpan timeout)
{
using (var timeoutCancellationTokenSource = new CancellationTokenSource())
{
var completedTask = await Task.WhenAny(task, Task.Delay(timeout, timeoutCancellationTokenSource.Token));
if (completedTask == task)
{
timeoutCancellationTokenSource.Cancel();
return await task; // Very important in order to propagate exceptions
}
else
{
return default(TResult);
}
}
}
public static void DigestUpdate(UpdateState state)
{
Queue<Callback<UpdateState>> _callbacks;
lock (_UpdateCallbacks)
{
// Swap the active callbacks queue with the second one, so we can
// process entries without fear of more being added.
_callbacks = _UpdateCallbacks;
_UpdateCallbacks = _UpdateCallbacksSwap;
_UpdateCallbacksSwap = _callbacks;
}
while (_callbacks.Count > 0)
{
_callbacks.Dequeue()(state);
}
int hookCount;
UpdateHook[] _hooks;
List<UpdateHook> toRemove = _UpdateHooksRemove;
lock (_UpdateHooks)
{
hookCount = _UpdateHooks.Count;
if (_UpdateHooksCopy.Length < _UpdateHooks.Count)
{
_UpdateHooksCopy = _UpdateHooks.ToArray();
}
else
{
_UpdateHooks.CopyTo(_UpdateHooksCopy);
}
_hooks = _UpdateHooksCopy;
}
for (int i = 0; i < hookCount; i++)
{
var item = _hooks[i];
item.Callback(state);
if (item.RemoveNext)
{
toRemove.Add(item);
}
}
if (toRemove.Count > 0)
{
lock (_UpdateHooks)
{
foreach (var rem in toRemove) _UpdateHooks.Remove(rem);
}
toRemove.Clear();
}
//finally, check cache controller
TimedReferenceController.Tick();
}
}
}

View file

@ -0,0 +1,348 @@
//disabled for now so FSO.Files can reference this project.
/*
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Security.Cryptography;
using FSO.Files.Formats.IFF.Chunks;
using Microsoft.Xna.Framework.Graphics;
namespace FSO.Common.Utils
{
/// <summary>
/// This tool helps export internal structures such as floor catalogs,
/// wall catalogs etc to a html file to check that file parsing is working correctly
/// </summary>
public class HTMLPrinter : IDisposable
{
private List<object> sections = new List<object>();
private Dictionary<string, string> patterns = new Dictionary<string, string>();
private string dir;
private string id;
private GraphicsDevice Gd;
public HTMLPrinter(GraphicsDevice gd, string directory, string id)
{
this.Gd = gd;
this.dir = directory;
this.id = id;
sections.Add("<link rel=\"stylesheet\" type=\"text/css\" href=\"main.css\"></link>");
//Add the default patterns
patterns.Add("h1", "<h1>{0}</h1>");
patterns.Add("h2", "<h2>{0}</h2>");
patterns.Add("h3", "<h3>{0}</h3>");
}
public void H1(string text){
Print("h1", text);
}
public void H2(string text){
Print("h2", text);
}
public void H3(string text){
Print("h3", text);
}
public Table CreateTable()
{
return new Table();
}
public void Add(object item){
this.sections.Add(item);
}
public Table CreateTable(params string[] columns)
{
var t = new Table();
foreach (var col in columns)
{
t.WithColumn(new TableColumn(col));
}
return t;
}
public DataTable<T> AddDataTable<T>(IEnumerable<T> values)
{
var table = CreateDataTable<T>(values);
sections.Add(table);
return table;
}
public DataTable<T> CreateDataTable<T>(IEnumerable<T> values)
{
return new DataTable<T>(values);
}
private string ObjectToString(object obj)
{
if (obj != null)
{
return obj.ToString();
}
return string.Empty;
}
public void Print(string pattern, params object[] args)
{
var value = string.Format(patterns[pattern], args);
sections.Add(value);
}
#region IDisposable Members
public void Dispose()
{
Directory.CreateDirectory(Path.Combine(dir, id + "_files"));
var sb = new StringBuilder();
foreach (var item in sections)
{
AppendItem(sb, item);
}
File.WriteAllText(Path.Combine(dir, id + ".html"), sb.ToString());
}
#endregion
public void AppendItem(StringBuilder sb, object item)
{
if (item is IHTMLAppender)
{
((IHTMLAppender)item).Append(this, sb);
}else if(item is SPR2Frame){
try
{
var path = ExportSpriteFrame((SPR2Frame)item);
sb.Append("<img src='" + path + "'></img>");
}
catch (Exception)
{
sb.Append("Failed to export");
}
}
else if (item is SPRFrame)
{
try
{
var path = ExportSpriteFrame((SPRFrame)item);
sb.Append("<img src='" + path + "'></img>");
}
catch (Exception)
{
sb.Append("Failed to export");
}
}
else if (item != null)
{
sb.Append(item.ToString());
}
/**else if(item is SPR){
var t = CreateTable()
.WithColumn(new TableColumn("Sprite", 2));
var sprP = (SPR)item;
for (var i = 0; i < sprP.FrameCount; i++){
try
{
var frame = sprP.GetFrame(i);
t.AddRow((i + 1), frame);
}
catch (Exception ex)
{
t.AddRow((i + 1), "Failed to export frame");
}
}
AppendItem(sb, t);
}**/
/**
}
private string ExportSpriteFrame(SPRFrame frame)
{
var texture = frame.GetTexture(this.Gd);
var temp = Path.GetTempFileName();
texture.SaveAsPng(new FileStream(temp, FileMode.OpenOrCreate), texture.Width, texture.Height);
var hash = FileUtils.ComputeMD5(temp);
var filename = id + "_files/" + hash + ".png";
var newDest = Path.Combine(dir, filename);
if (File.Exists(newDest))
{
File.Delete(temp);
}
else
{
File.Move(temp, newDest);
}
return filename;
}
private string ExportSpriteFrame(SPR2Frame frame)
{
var texture = frame.GetTexture(this.Gd);
var temp = Path.GetTempFileName();
texture.SaveAsPng(new FileStream(temp, FileMode.OpenOrCreate), texture.Width, texture.Height);
var hash = FileUtils.ComputeMD5(temp);
var filename = id + "_files/" + hash + ".png";
var newDest = Path.Combine(dir, filename);
if (File.Exists(newDest))
{
File.Delete(temp);
}
else
{
File.Move(temp, newDest);
}
return filename;
}
}
public class TableColumn
{
public TableColumn(string header)
{
this.Header = header;
}
public TableColumn(string header, int span)
{
this.Header = header;
this.Span = span;
}
public string Header;
public int Span;
}
public class Table : IHTMLAppender
{
private List<TableColumn> Columns = new List<TableColumn>();
private List<object[]> Rows = new List<object[]>();
public Table WithColumn(TableColumn col)
{
Columns.Add(col);
return this;
}
public Table AddRow(params object[] values)
{
Rows.Add(values);
return this;
}
#region IHTMLAppender Members
public void Append(HTMLPrinter printer, StringBuilder sb)
{
sb.Append("<table border='1'>");
if (Columns.Count > 0)
{
sb.Append("<tr>");
foreach (var col in Columns)
{
sb.Append("<th colspan='" + col.Span + "'>" + col.Header + "</th>");
}
sb.Append("</tr>");
}
foreach (var item in Rows)
{
sb.Append("<tr>");
foreach(var col in item){
sb.Append("<td>");
printer.AppendItem(sb, col);
sb.Append("</td>");
}
sb.Append("</tr>");
}
sb.Append("</table>");
}
#endregion
}
public class DataTable<T> : IHTMLAppender
{
private IEnumerable<T> Items;
private List<DataTableColumn<T>> Columns;
public DataTable(IEnumerable<T> items)
{
this.Items = items;
this.Columns = new List<DataTableColumn<T>>();
}
public DataTable<T> WithColumn(string label, Func<T, object> value)
{
Columns.Add(new DataTableColumn<T> { Heading = label, Value = value });
return this;
}
#region IHTMLAppender Members
public void Append(HTMLPrinter printer, StringBuilder sb)
{
sb.Append("<table border='1'>");
sb.Append("<tr>");
foreach (var col in Columns)
{
sb.Append("<td>" + col.Heading + "</td>");
}
sb.Append("</tr>");
foreach (var item in Items)
{
sb.Append("<tr>");
foreach (var col in Columns)
{
sb.Append("<th>");
printer.AppendItem(sb, col.Value(item));
sb.Append("</th>");
}
sb.Append("</tr>");
}
sb.Append("</table>");
}
#endregion
}
public interface IHTMLAppender
{
void Append(HTMLPrinter printer, StringBuilder sb);
}
public class DataTableColumn<T> {
public string Heading;
public Func<T, object> Value;
}
}
*/

View file

@ -0,0 +1,7 @@
namespace FSO.Common.Utils
{
public interface ITimedCachable
{
void Rereferenced(bool save);
}
}

View file

@ -0,0 +1,43 @@
using System;
using System.Xml;
namespace FSO.Common.Utils
{
public interface IXMLEntity
{
XmlElement Serialize(XmlDocument doc);
void Parse(XmlElement element);
}
public static class XMLUtils
{
public static T Parse<T>(string data) where T : IXMLEntity
{
var doc = new XmlDocument();
doc.LoadXml(data);
T result = (T)Activator.CreateInstance(typeof(T));
result.Parse((XmlElement)doc.FirstChild);
return result;
}
public static void AppendTextNode(this XmlElement e, string nodeName, string value)
{
var node = e.OwnerDocument.CreateElement(nodeName);
node.AppendChild(e.OwnerDocument.CreateTextNode(value));
e.AppendChild(node);
}
public static string ReadTextNode(this XmlElement e, string nodeName)
{
foreach (XmlElement child in e.ChildNodes)
{
if (child.Name == nodeName && child.FirstChild != null)
{
return child.FirstChild?.Value;
}
}
return null;
}
}
}

Some files were not shown because too many files have changed in this diff Show more