mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-14 19:29:36 +01:00
RSI support (#552)
* RSI WiP * More work but we're doing bsdiff now * RSI loading seems to mostly work. * Vector2u deserialization test. * Add in packages again. * This is the part where I realize I need a path manipulation library. * The start of a path class but it's late so I'm going to bed. * HIGHLY theoretical ResourcePath code. Partially tested but not really. * Allow x86 for unit tests I guess jesus christ. Thanks Microsoft for still not shipping x64 VS in 2018. * Resource paths work & are tested. * I missed a doc spot. * ResourcePaths implemented on the server. * Client works with resource paths. TIME FOR A REFACTOR. * Some work but this might be a stupid idea so I migh throw it in the trash. * Resources refactored completely. They now only get the requested resourcepath. They're in charge of opening files to load. * RSI Loader WORKS. * Update AudioResource for new loading support. * Fix package references. * Fix more references. * Gonna work now?
This commit is contained in:
committed by
GitHub
parent
796555fad5
commit
d7414930ff
@@ -7,6 +7,7 @@
|
||||
<Copy SourceFiles="@(_ResourceFiles)" DestinationFolder="$(OutputPath)Resources\%(RecursiveDir)" />
|
||||
</Target>
|
||||
<Target Name="CopyBsdiffWrap">
|
||||
<Exec Command="$(Python) ../Tools/download_bsdiffwrap.py $(Platform) $(TargetOS) $(OutputPath)" CustomErrorRegularExpression="^Error" />
|
||||
<Exec Condition="'$(Platform)' == 'x64'" Command="$(Python) ../Tools/download_bsdiffwrap.py $(Platform) $(TargetOS) $(OutputPath)" CustomErrorRegularExpression="^Error" />
|
||||
<Warning Condition="'$(Platform)' != 'x64'" Text="Did not download bsdiff because the platform is not set to x64. Only use this build for unit testing!" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
||||
@@ -25,6 +25,7 @@ using SS14.Shared.IoC;
|
||||
using SS14.Shared.Log;
|
||||
using SS14.Shared.Network.Messages;
|
||||
using SS14.Shared.Prototypes;
|
||||
using SS14.Shared.Utility;
|
||||
using System;
|
||||
|
||||
namespace SS14.Client
|
||||
@@ -77,6 +78,10 @@ namespace SS14.Client
|
||||
|
||||
public override void Main(Godot.SceneTree tree)
|
||||
{
|
||||
#if !X64
|
||||
throw new InvalidOperationException("The client cannot start outside x64.");
|
||||
#endif
|
||||
|
||||
PreInitIoC();
|
||||
IoCManager.Resolve<ISceneTreeHolder>().Initialize(tree);
|
||||
InitIoC();
|
||||
@@ -113,7 +118,7 @@ namespace SS14.Client
|
||||
_tileDefinitionManager.Initialize();
|
||||
_networkManager.Initialize(false);
|
||||
_console.Initialize();
|
||||
_prototypeManager.LoadDirectory(@"./Prototypes/");
|
||||
_prototypeManager.LoadDirectory(new ResourcePath(@"/Prototypes/"));
|
||||
_prototypeManager.Resync();
|
||||
_mapManager.Initialize();
|
||||
placementManager.Initialize();
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace SS14.Client.GameObjects
|
||||
|
||||
public void SetIcon(string name)
|
||||
{
|
||||
Icon = IoCManager.Resolve<IResourceCache>().GetResource<TextureResource>($@"./Textures/{name}");
|
||||
Icon = IoCManager.Resolve<IResourceCache>().GetResource<TextureResource>($@"/Textures/{name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ namespace SS14.Client.GameObjects
|
||||
{
|
||||
radius = FloatMath.Clamp(value, 2, 10);
|
||||
var mgr = IoCManager.Resolve<IResourceCache>();
|
||||
var tex = mgr.GetResource<TextureResource>($"Textures/Effects/Light/lighting_falloff_{(int)radius}.png");
|
||||
var tex = mgr.GetResource<TextureResource>($"/Textures/Effects/Light/lighting_falloff_{(int)radius}.png");
|
||||
// TODO: Maybe editing the global texture resource is not a good idea.
|
||||
tex.Texture.GodotTexture.SetFlags(tex.Texture.GodotTexture.GetFlags() | (int)Godot.Texture.FlagsEnum.Filter);
|
||||
Light.Texture = tex.Texture;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using SS14.Client.Graphics;
|
||||
using SS14.Client.Graphics.ClientEye;
|
||||
@@ -20,6 +19,7 @@ using SS14.Shared.Map;
|
||||
using SS14.Shared.Maths;
|
||||
using SS14.Shared.Utility;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
using Path = System.IO.Path;
|
||||
|
||||
// Warning: Shitcode ahead!
|
||||
namespace SS14.Client.GameObjects
|
||||
@@ -147,7 +147,7 @@ namespace SS14.Client.GameObjects
|
||||
}
|
||||
|
||||
var manager = IoCManager.Resolve<IResourceCache>();
|
||||
if (manager.TryGetResource<TextureResource>($@"./Textures/{spriteKey}", out var sprite))
|
||||
if (manager.TryGetResource<TextureResource>($@"/Textures/{spriteKey}", out var sprite))
|
||||
{
|
||||
AddSprite(spriteKey, sprite.Texture);
|
||||
}
|
||||
@@ -189,7 +189,7 @@ namespace SS14.Client.GameObjects
|
||||
var ext = Path.GetExtension(curr.Key);
|
||||
var withoutExt = Path.ChangeExtension(curr.Key, null);
|
||||
string name = $"{withoutExt}_{dir.ToLowerInvariant()}{ext}";
|
||||
if (resMgr.TryGetResource<TextureResource>(@"./Textures/" + name, out var res))
|
||||
if (resMgr.TryGetResource<TextureResource>(@"/Textures/" + name, out var res))
|
||||
dirSprites.Add(name, res.Texture);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -267,7 +267,7 @@ namespace SS14.Client.GameObjects
|
||||
|
||||
public Effect(EffectSystemMessage effectcreation, IResourceCache resourceCache)
|
||||
{
|
||||
EffectSprite = resourceCache.GetResource<TextureResource>("Textures/" + effectcreation.EffectSprite).Texture;
|
||||
EffectSprite = resourceCache.GetResource<TextureResource>("/Textures/" + effectcreation.EffectSprite).Texture;
|
||||
Coordinates = effectcreation.Coordinates;
|
||||
EmitterCoordinates = effectcreation.EmitterCoordinates;
|
||||
Velocity = effectcreation.Velocity;
|
||||
|
||||
@@ -88,7 +88,7 @@ namespace SS14.Client.Graphics.Lighting
|
||||
deferredViewport.AddChild(canvasModulate);
|
||||
rootViewport.AddChild(deferredViewport);
|
||||
|
||||
var whiteTex = resourceCache.GetResource<TextureResource>(@"./Textures/Effects/Light/white.png");
|
||||
var whiteTex = resourceCache.GetResource<TextureResource>(@"/Textures/Effects/Light/white.png");
|
||||
deferredMaskBackground = new Godot.Sprite()
|
||||
{
|
||||
Name = "DeferredMaskBackground",
|
||||
|
||||
63
SS14.Client/Graphics/RSI/RSI.State.cs
Normal file
63
SS14.Client/Graphics/RSI/RSI.State.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using SS14.Shared.Maths;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
|
||||
namespace SS14.Client.Graphics
|
||||
{
|
||||
public sealed partial class RSI
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a single icon state inside an RSI.
|
||||
/// </summary>
|
||||
public sealed class State
|
||||
{
|
||||
public Vector2u Size { get; }
|
||||
public StateId StateId { get; }
|
||||
public DirectionType Directions { get; }
|
||||
public int DirectionsCount
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (Directions)
|
||||
{
|
||||
case DirectionType.Dir1:
|
||||
return 1;
|
||||
case DirectionType.Dir4:
|
||||
return 4;
|
||||
case DirectionType.Dir8:
|
||||
return 8;
|
||||
default:
|
||||
throw new InvalidOperationException("Unknown direction");
|
||||
}
|
||||
}
|
||||
}
|
||||
private (Texture icon, float delay)[][] Icons;
|
||||
|
||||
public State(Vector2u size, StateId stateId, DirectionType direction, (Texture icon, float delay)[][] icons)
|
||||
{
|
||||
Size = size;
|
||||
StateId = stateId;
|
||||
Directions = direction;
|
||||
Icons = icons;
|
||||
}
|
||||
|
||||
public enum DirectionType : byte
|
||||
{
|
||||
Dir1,
|
||||
Dir4,
|
||||
Dir8,
|
||||
}
|
||||
|
||||
public (Texture icon, float delay) GetFrame(int direction, int frame)
|
||||
{
|
||||
return Icons[direction][frame];
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<(Texture icon, float delay)> GetDirectionFrames(int direction)
|
||||
{
|
||||
return Icons[direction];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
119
SS14.Client/Graphics/RSI/RSI.cs
Normal file
119
SS14.Client/Graphics/RSI/RSI.cs
Normal file
@@ -0,0 +1,119 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NJsonSchema;
|
||||
using SS14.Shared.Log;
|
||||
using SS14.Shared.Maths;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Collections;
|
||||
using SS14.Client.Interfaces.ResourceManagement;
|
||||
using SS14.Shared.Utility;
|
||||
|
||||
namespace SS14.Client.Graphics
|
||||
{
|
||||
/// <summary>
|
||||
/// Type to handle Robust Station Image (RSI) files.
|
||||
/// </summary>
|
||||
public sealed partial class RSI : IEnumerable<RSI.State>
|
||||
{
|
||||
/// <summary>
|
||||
/// The size of this RSI, width x height.
|
||||
/// </summary>
|
||||
public Vector2u Size { get; private set; }
|
||||
private Dictionary<StateId, State> States = new Dictionary<StateId, State>();
|
||||
|
||||
public State this[StateId key]
|
||||
{
|
||||
get => States[key];
|
||||
}
|
||||
|
||||
public void AddState(State state)
|
||||
{
|
||||
States[state.StateId] = state;
|
||||
}
|
||||
|
||||
public void RemoveState(StateId stateId)
|
||||
{
|
||||
States.Remove(stateId);
|
||||
}
|
||||
|
||||
public bool TryGetState(StateId stateId, out State state)
|
||||
{
|
||||
return States.TryGetValue(stateId, out state);
|
||||
}
|
||||
|
||||
public RSI(Vector2u size)
|
||||
{
|
||||
Size = size;
|
||||
}
|
||||
|
||||
public IEnumerator<State> GetEnumerator()
|
||||
{
|
||||
return States.Values.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum Selectors
|
||||
{
|
||||
None = 0,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a name+selector pair used to reference states in an RSI.
|
||||
/// </summary>
|
||||
public struct StateId
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly Selectors Selectors;
|
||||
|
||||
public StateId(string name, Selectors selectors)
|
||||
{
|
||||
Name = name;
|
||||
Selectors = selectors;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
|
||||
public static implicit operator StateId(string key)
|
||||
{
|
||||
return new StateId(key, Selectors.None);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is StateId id && Equals(id);
|
||||
}
|
||||
|
||||
public bool Equals(StateId id)
|
||||
{
|
||||
return id.Name == Name && id.Selectors == Selectors;
|
||||
}
|
||||
|
||||
public static bool operator ==(StateId a, StateId b)
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator !=(StateId a, StateId b)
|
||||
{
|
||||
return !a.Equals(b);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Name.GetHashCode() ^ Selectors.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
51
SS14.Client/Graphics/RSI/RSISchema.json
Normal file
51
SS14.Client/Graphics/RSI/RSISchema.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "RSI Image Format Validation Schema V1",
|
||||
"description": "Robust Station Image",
|
||||
"type": "object",
|
||||
"definitions": {
|
||||
"size": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"x": {"type": "integer", "minimum": 1},
|
||||
"y": {"type": "integer", "minimum": 1}
|
||||
},
|
||||
"required": ["x","y"]
|
||||
},
|
||||
"directions": {
|
||||
"type": "integer",
|
||||
"enum": [1,4,8]
|
||||
},
|
||||
"state": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string"},
|
||||
"select": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"}
|
||||
},
|
||||
"flags": {"type": "object"}, //To be de-serialized as a Dictionary
|
||||
"directions": {"$ref": "#/definitions/directions"},
|
||||
"delays": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {"type": "number", "minimum": 0, "exclusiveMinimum": true} //number == float
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["name","select","flags","directions"] //'delays' is marked as optional in the spec
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"version": {"type": "integer", "minimum": 1, "maximum": 1},
|
||||
"size": {"$ref": "#/definitions/size"},
|
||||
"states": {
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/definitions/state"},
|
||||
"minItems": 1
|
||||
}
|
||||
},
|
||||
"required": ["version","size","states"]
|
||||
}
|
||||
@@ -1,28 +1,39 @@
|
||||
using Godot;
|
||||
using SS14.Client.ResourceManagement;
|
||||
using SS14.Shared.GameObjects;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SS14.Client.ResourceManagement;
|
||||
using SS14.Shared.Interfaces;
|
||||
using SS14.Shared.Utility;
|
||||
|
||||
namespace SS14.Client.Interfaces.ResourceManagement
|
||||
{
|
||||
public interface IResourceCache
|
||||
public interface IResourceCache : IResourceManager
|
||||
{
|
||||
// For convenience.
|
||||
|
||||
void LoadLocalResources();
|
||||
void LoadBaseResources();
|
||||
|
||||
/// <summary>
|
||||
/// TEMPORARY: We need this because Godot can't load most resources without the disk easily.
|
||||
/// </summary>
|
||||
bool TryGetDiskFilePath(ResourcePath path, out string diskPath);
|
||||
|
||||
T GetResource<T>(string path, bool useFallback = true)
|
||||
where T : BaseResource, new();
|
||||
|
||||
T GetResource<T>(ResourcePath path, bool useFallback = true)
|
||||
where T : BaseResource, new();
|
||||
|
||||
bool TryGetResource<T>(string path, out T resource)
|
||||
where T : BaseResource, new();
|
||||
|
||||
bool TryGetResource<T>(ResourcePath path, out T resource)
|
||||
where T : BaseResource, new();
|
||||
|
||||
void CacheResource<T>(string path, T resource)
|
||||
where T : BaseResource, new();
|
||||
|
||||
void CacheResource<T>(ResourcePath path, T resource)
|
||||
where T : BaseResource, new();
|
||||
|
||||
T GetFallback<T>()
|
||||
where T : BaseResource, new();
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace SS14.Client.Map
|
||||
TileSet.CreateTile(ret);
|
||||
if (!string.IsNullOrEmpty(tileDef.SpriteName))
|
||||
{
|
||||
var texture = resourceCache.GetResource<TextureResource>($@"./Textures/Tiles/{tileDef.SpriteName}.png");
|
||||
var texture = resourceCache.GetResource<TextureResource>($@"/Textures/Tiles/{tileDef.SpriteName}.png");
|
||||
TileSet.TileSetTexture(ret, texture.Texture.GodotTexture);
|
||||
Textures[ret] = texture;
|
||||
}
|
||||
|
||||
@@ -333,7 +333,7 @@ namespace SS14.Client.Placement
|
||||
//Will break if states not ordered correctly.
|
||||
|
||||
var spriteName = spriteParam == null ? "" : spriteParam.GetValue<string>();
|
||||
var sprite = ResourceCache.GetResource<TextureResource>("Textures/" + spriteName);
|
||||
var sprite = ResourceCache.GetResource<TextureResource>("/Textures/" + spriteName);
|
||||
|
||||
CurrentBaseSprite = sprite;
|
||||
CurrentBaseSpriteKey = spriteName;
|
||||
@@ -346,7 +346,7 @@ namespace SS14.Client.Placement
|
||||
{
|
||||
var tileDefs = _tileDefManager;
|
||||
|
||||
CurrentBaseSprite = ResourceCache.GetResource<TextureResource>("Textures/UserInterface/tilebuildoverlay.png");
|
||||
CurrentBaseSprite = ResourceCache.GetResource<TextureResource>("/Textures/UserInterface/tilebuildoverlay.png");
|
||||
CurrentBaseSpriteKey = "UserInterface/tilebuildoverlay.png";
|
||||
|
||||
IsActive = true;
|
||||
|
||||
@@ -48,12 +48,12 @@ namespace SS14.Client.Placement
|
||||
|
||||
public TextureResource GetSprite(string key)
|
||||
{
|
||||
return pManager.ResourceCache.GetResource<TextureResource>("Textures/" + key);
|
||||
return pManager.ResourceCache.GetResource<TextureResource>("/Textures/" + key);
|
||||
}
|
||||
|
||||
public bool TryGetSprite(string key, out TextureResource sprite)
|
||||
{
|
||||
return pManager.ResourceCache.TryGetResource<TextureResource>("Textures/" + key, out sprite);
|
||||
return pManager.ResourceCache.TryGetResource<TextureResource>("/Textures/" + key, out sprite);
|
||||
}
|
||||
|
||||
public void SetSprite()
|
||||
@@ -112,4 +112,3 @@ namespace SS14.Client.Placement
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using SS14.Shared.IoC;
|
||||
using SS14.Shared.Reflection;
|
||||
using SS14.Shared.Reflection;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SS14.Client.Reflection
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using SS14.Client.Interfaces.ResourceManagement;
|
||||
using SS14.Shared.Utility;
|
||||
|
||||
namespace SS14.Client.ResourceManagement
|
||||
{
|
||||
@@ -25,7 +25,7 @@ namespace SS14.Client.ResourceManagement
|
||||
/// Deserializes the resource from the VFS.
|
||||
/// </summary>
|
||||
/// <param name="cache">ResourceCache this resource is being loaded into.</param>
|
||||
/// <param name="path">Path of the resource relative to the root of the ResourceCache.</param>
|
||||
public abstract void Load(IResourceCache cache, string path);
|
||||
/// <param name="path">Path of the resource requested on the VFS.</param>
|
||||
public abstract void Load(IResourceCache cache, ResourcePath path);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,16 @@
|
||||
using SS14.Client.Interfaces.ResourceManagement;
|
||||
using SS14.Shared.ContentPack;
|
||||
using SS14.Shared.Interfaces;
|
||||
using SS14.Shared.IoC;
|
||||
using SS14.Shared.Log;
|
||||
using SS14.Shared.Utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using SS14.Client.ResourceManagement;
|
||||
using SS14.Shared.Configuration;
|
||||
|
||||
namespace SS14.Client.ResourceManagement
|
||||
{
|
||||
public class ResourceCache : ResourceManager, IResourceCache, IDisposable
|
||||
public partial class ResourceCache : ResourceManager, IResourceCache, IDisposable
|
||||
{
|
||||
private Dictionary<(string, Type), BaseResource> CachedResources = new Dictionary<(string, Type), BaseResource>();
|
||||
private Dictionary<(ResourcePath, Type), BaseResource> CachedResources = new Dictionary<(ResourcePath, Type), BaseResource>();
|
||||
|
||||
public void LoadBaseResources()
|
||||
{
|
||||
@@ -30,7 +24,7 @@ namespace SS14.Client.ResourceManagement
|
||||
MountContentDirectory(@"Resources/");
|
||||
#else
|
||||
MountContentDirectory(@"../../Resources/");
|
||||
MountContentDirectory(@"Resources/Assemblies", "Assemblies/");
|
||||
MountContentDirectory(@"Resources/Assemblies", new ResourcePath("/Assemblies/"));
|
||||
#endif
|
||||
//_resources.MountContentPack(@"./EngineContentPack.zip");
|
||||
}
|
||||
@@ -41,6 +35,11 @@ namespace SS14.Client.ResourceManagement
|
||||
}
|
||||
|
||||
public T GetResource<T>(string path, bool useFallback = true) where T : BaseResource, new()
|
||||
{
|
||||
return GetResource<T>(new ResourcePath(path), useFallback);
|
||||
}
|
||||
|
||||
public T GetResource<T>(ResourcePath path, bool useFallback = true) where T : BaseResource, new()
|
||||
{
|
||||
if (CachedResources.TryGetValue((path, typeof(T)), out var cached))
|
||||
{
|
||||
@@ -50,11 +49,7 @@ namespace SS14.Client.ResourceManagement
|
||||
var _resource = new T();
|
||||
try
|
||||
{
|
||||
if (!TryGetDiskFilePath(path, out var diskPath))
|
||||
{
|
||||
throw new FileNotFoundException(path);
|
||||
}
|
||||
_resource.Load(this, diskPath);
|
||||
_resource.Load(this, path);
|
||||
CachedResources[(path, typeof(T))] = _resource;
|
||||
return _resource;
|
||||
}
|
||||
@@ -74,6 +69,11 @@ namespace SS14.Client.ResourceManagement
|
||||
}
|
||||
|
||||
public bool TryGetResource<T>(string path, out T resource) where T : BaseResource, new()
|
||||
{
|
||||
return TryGetResource(new ResourcePath(path), out resource);
|
||||
}
|
||||
|
||||
public bool TryGetResource<T>(ResourcePath path, out T resource) where T : BaseResource, new()
|
||||
{
|
||||
if (CachedResources.TryGetValue((path, typeof(T)), out var cached))
|
||||
{
|
||||
@@ -83,11 +83,7 @@ namespace SS14.Client.ResourceManagement
|
||||
var _resource = new T();
|
||||
try
|
||||
{
|
||||
if (!TryGetDiskFilePath(path, out var diskPath))
|
||||
{
|
||||
throw new FileNotFoundException(path);
|
||||
}
|
||||
_resource.Load(this, diskPath);
|
||||
_resource.Load(this, path);
|
||||
resource = _resource;
|
||||
CachedResources[(path, typeof(T))] = resource;
|
||||
return true;
|
||||
@@ -100,11 +96,21 @@ namespace SS14.Client.ResourceManagement
|
||||
}
|
||||
|
||||
public bool HasResource<T>(string path) where T : BaseResource, new()
|
||||
{
|
||||
return HasResource<T>(new ResourcePath(path));
|
||||
}
|
||||
|
||||
public bool HasResource<T>(ResourcePath path) where T : BaseResource, new()
|
||||
{
|
||||
return TryGetResource<T>(path, out var _);
|
||||
}
|
||||
|
||||
public void CacheResource<T>(string path, T resource) where T : BaseResource, new()
|
||||
{
|
||||
CacheResource(new ResourcePath(path), resource);
|
||||
}
|
||||
|
||||
public void CacheResource<T>(ResourcePath path, T resource) where T : BaseResource, new()
|
||||
{
|
||||
CachedResources[(path, typeof(T))] = resource;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using SS14.Client.Audio;
|
||||
using SS14.Client.Interfaces.ResourceManagement;
|
||||
using SS14.Shared.Utility;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace SS14.Client.ResourceManagement
|
||||
@@ -8,24 +10,26 @@ namespace SS14.Client.ResourceManagement
|
||||
{
|
||||
public AudioStream AudioStream { get; private set; }
|
||||
|
||||
public override void Load(IResourceCache cache, string diskPath)
|
||||
public override void Load(IResourceCache cache, ResourcePath path)
|
||||
{
|
||||
if (!File.Exists(diskPath))
|
||||
if (!cache.ContentFileExists(path))
|
||||
{
|
||||
throw new FileNotFoundException(diskPath);
|
||||
throw new FileNotFoundException("Content file does not exist for audio sample.");
|
||||
}
|
||||
|
||||
var data = File.ReadAllBytes(diskPath);
|
||||
var stream = new Godot.AudioStreamOGGVorbis()
|
||||
using (var fileStream = cache.ContentFileRead(path))
|
||||
{
|
||||
Data = data
|
||||
};
|
||||
if (stream.GetLength() == 0)
|
||||
{
|
||||
throw new InvalidDataException();
|
||||
var stream = new Godot.AudioStreamOGGVorbis()
|
||||
{
|
||||
Data = fileStream.ToArray(),
|
||||
};
|
||||
if (stream.GetLength() == 0)
|
||||
{
|
||||
throw new InvalidDataException();
|
||||
}
|
||||
AudioStream = new GodotAudioStreamSource(stream);
|
||||
Shared.Log.Logger.Debug($"{stream.GetLength()}");
|
||||
}
|
||||
AudioStream = new GodotAudioStreamSource(stream);
|
||||
Shared.Log.Logger.Debug($"{stream.GetLength()}");
|
||||
}
|
||||
|
||||
public static implicit operator AudioStream(AudioResource res)
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using SS14.Client.Interfaces.ResourceManagement;
|
||||
using System.IO;
|
||||
using SS14.Shared.Utility;
|
||||
|
||||
namespace SS14.Client.ResourceManagement
|
||||
{
|
||||
@@ -14,14 +15,18 @@ namespace SS14.Client.ResourceManagement
|
||||
public DynamicFont Font { get; private set; }
|
||||
public DynamicFontData FontData { get; private set; }
|
||||
|
||||
public override void Load(IResourceCache cache, string path)
|
||||
public override void Load(IResourceCache cache, ResourcePath path)
|
||||
{
|
||||
if (!System.IO.File.Exists(path))
|
||||
if (!cache.ContentFileExists(path))
|
||||
{
|
||||
throw new FileNotFoundException(path);
|
||||
throw new FileNotFoundException("Content file does not exist for texture");
|
||||
}
|
||||
if (!cache.TryGetDiskFilePath(path, out string diskPath))
|
||||
{
|
||||
throw new InvalidOperationException("Textures can only be loaded from disk.");
|
||||
}
|
||||
|
||||
var res = ResourceLoader.Load(path);
|
||||
var res = ResourceLoader.Load(diskPath);
|
||||
if (!(res is DynamicFontData fontData))
|
||||
{
|
||||
throw new InvalidDataException("Path does not point to a font.");
|
||||
|
||||
190
SS14.Client/ResourceManagement/ResourceTypes/RSIResource.cs
Normal file
190
SS14.Client/ResourceManagement/ResourceTypes/RSIResource.cs
Normal file
@@ -0,0 +1,190 @@
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NJsonSchema;
|
||||
using SS14.Client.Graphics;
|
||||
using SS14.Client.Interfaces.ResourceManagement;
|
||||
using SS14.Shared.Log;
|
||||
using SS14.Shared.Maths;
|
||||
using SS14.Shared.Utility;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace SS14.Client.ResourceManagement
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles the loading code for RSI files.
|
||||
/// See <see cref="RSI"/> for the RSI API itself.
|
||||
/// </summary>
|
||||
public sealed class RSIResource : BaseResource
|
||||
{
|
||||
public RSI RSI { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The minimum version of RSI we can load.
|
||||
/// </summary>
|
||||
public const uint MINIMUM_RSI_VERSION = 1;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum version of RSI we can load.
|
||||
/// </summary>
|
||||
public const uint MAXIMUM_RSI_VERSION = 1;
|
||||
|
||||
public override void Load(IResourceCache cache, ResourcePath path)
|
||||
{
|
||||
var manifestPath = path / "meta.json";
|
||||
string manifestContents;
|
||||
|
||||
using (var manifestFile = cache.ContentFileRead(manifestPath))
|
||||
using (var reader = new StreamReader(manifestFile))
|
||||
{
|
||||
manifestContents = reader.ReadToEnd();
|
||||
}
|
||||
|
||||
var errors = RSISchema.Validate(manifestContents);
|
||||
if (errors.Count != 0)
|
||||
{
|
||||
Logger.Error($"Unable to load RSI from '{path}', {errors.Count} errors:");
|
||||
|
||||
foreach (var error in errors)
|
||||
{
|
||||
Logger.Error(error.ToString());
|
||||
}
|
||||
|
||||
throw new RSILoadException($"{errors.Count} errors while loading RSI. See console.");
|
||||
}
|
||||
|
||||
// Ok schema validated just fine.
|
||||
var manifestJson = JObject.Parse(manifestContents);
|
||||
var size = manifestJson["size"].ToObject<Vector2u>();
|
||||
|
||||
var rsi = new RSI(size);
|
||||
|
||||
// Do every state.
|
||||
foreach (var stateObject in manifestJson["states"].Cast<JObject>())
|
||||
{
|
||||
var stateName = stateObject["name"].ToObject<string>();
|
||||
var dirValue = stateObject["directions"].ToObject<int>();
|
||||
RSI.State.DirectionType directions;
|
||||
|
||||
switch (dirValue)
|
||||
{
|
||||
case 1:
|
||||
directions = RSI.State.DirectionType.Dir1;
|
||||
break;
|
||||
case 4:
|
||||
directions = RSI.State.DirectionType.Dir4;
|
||||
break;
|
||||
case 8:
|
||||
directions = RSI.State.DirectionType.Dir8;
|
||||
break;
|
||||
default:
|
||||
throw new RSILoadException($"Invalid direction: {dirValue}");
|
||||
}
|
||||
|
||||
// We can ignore selectors and flags for now,
|
||||
// because they're not used yet!
|
||||
|
||||
// Get the lists of delays.
|
||||
float[][] delays;
|
||||
if (stateObject.TryGetValue("delays", out var delayToken))
|
||||
{
|
||||
delays = delayToken.ToObject<float[][]>();
|
||||
|
||||
if (delays.Length != dirValue)
|
||||
{
|
||||
throw new RSILoadException($"Directions count does not match amount of delays specified.");
|
||||
}
|
||||
|
||||
for (var i = 0; i < delays.Length; i++)
|
||||
{
|
||||
var delayList = delays[i];
|
||||
if (delayList.Length == 0)
|
||||
{
|
||||
delays[i] = new float[] { 1 };
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
delays = new float[dirValue][];
|
||||
// No delays specified, default to 1 frame per dir.
|
||||
for (var i = 0; i < dirValue; i++)
|
||||
{
|
||||
delays[i] = new float[] { 1 };
|
||||
}
|
||||
}
|
||||
|
||||
var texPath = path / (stateName + ".png");
|
||||
var texture = cache.GetResource<TextureResource>(texPath).Texture;
|
||||
|
||||
if (texture.Width % size.X != 0 || texture.Height % size.Y != 0)
|
||||
{
|
||||
throw new RSILoadException("State image size is not a multiple of the icon size.");
|
||||
}
|
||||
|
||||
// Amount of icons per row of the sprite sheet.
|
||||
var sheetWidth = texture.Width / size.X;
|
||||
|
||||
var iconFrames = new(Texture, float)[dirValue][];
|
||||
var counter = 0;
|
||||
for (var j = 0; j < iconFrames.Length; j++)
|
||||
{
|
||||
var delayList = delays[j];
|
||||
var directionFrames = new(Texture, float)[delayList.Length];
|
||||
for (var i = 0; i < delayList.Length; i++)
|
||||
{
|
||||
var PosX = (counter % sheetWidth) * size.X;
|
||||
var PosY = (counter / sheetWidth) * size.Y;
|
||||
|
||||
var atlasTexture = new Godot.AtlasTexture()
|
||||
{
|
||||
Atlas = texture,
|
||||
Region = new Godot.Rect2(PosX, PosY, size.X, size.Y)
|
||||
};
|
||||
|
||||
directionFrames[i] = (new GodotTextureSource(atlasTexture), delayList[i]);
|
||||
counter++;
|
||||
}
|
||||
iconFrames[j] = directionFrames;
|
||||
}
|
||||
|
||||
var state = new RSI.State(size, stateName, directions, iconFrames);
|
||||
rsi.AddState(state);
|
||||
}
|
||||
|
||||
RSI = rsi;
|
||||
}
|
||||
|
||||
private static readonly JsonSchema4 RSISchema = GetSchema();
|
||||
|
||||
private static JsonSchema4 GetSchema()
|
||||
{
|
||||
string schema;
|
||||
using (var schemaStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("SS14.Client.Graphics.RSI.RSISchema.json"))
|
||||
using (var schemaReader = new StreamReader(schemaStream))
|
||||
{
|
||||
schema = schemaReader.ReadToEnd();
|
||||
}
|
||||
|
||||
return JsonSchema4.FromJsonAsync(schema).Result;
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class RSILoadException : Exception
|
||||
{
|
||||
public RSILoadException()
|
||||
{
|
||||
}
|
||||
public RSILoadException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
public RSILoadException(string message, Exception inner) : base(message, inner)
|
||||
{
|
||||
}
|
||||
protected RSILoadException(
|
||||
System.Runtime.Serialization.SerializationInfo info,
|
||||
System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,27 @@
|
||||
using SS14.Client.Graphics;
|
||||
using SS14.Client.Interfaces.ResourceManagement;
|
||||
using SS14.Shared.Log;
|
||||
using SS14.Shared.Utility;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace SS14.Client.ResourceManagement
|
||||
{
|
||||
public class TextureResource : BaseResource
|
||||
{
|
||||
public override string Fallback => "Textures/noSprite.png";
|
||||
public override string Fallback => "/Textures/noSprite.png";
|
||||
public Texture Texture { get; private set; }
|
||||
private Godot.ImageTexture godotTexture;
|
||||
|
||||
public override void Load(IResourceCache cache, string diskPath)
|
||||
public override void Load(IResourceCache cache, ResourcePath path)
|
||||
{
|
||||
if (!File.Exists(diskPath))
|
||||
if (!cache.ContentFileExists(path))
|
||||
{
|
||||
throw new FileNotFoundException(diskPath);
|
||||
throw new FileNotFoundException("Content file does not exist for texture");
|
||||
}
|
||||
if (!cache.TryGetDiskFilePath(path, out string diskPath))
|
||||
{
|
||||
throw new InvalidOperationException("Textures can only be loaded from disk.");
|
||||
}
|
||||
godotTexture = new Godot.ImageTexture();
|
||||
godotTexture.Load(diskPath);
|
||||
|
||||
@@ -33,12 +33,12 @@
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<DefineConstants>DEBUG;TRACE;X64</DefineConstants>
|
||||
<DebugType>portable</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<DefineConstants>TRACE;RELEASE</DefineConstants>
|
||||
<DefineConstants>TRACE;RELEASE;X64</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
@@ -57,8 +57,16 @@
|
||||
</PropertyGroup>
|
||||
<Import Project="..\MSBuild\SS14.DefineConstants.targets" />
|
||||
<ItemGroup>
|
||||
<Reference Include="Newtonsoft.Json, Version=11.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>$(SolutionDir)packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="NJsonSchema, Version=9.10.42.0, Culture=neutral, PublicKeyToken=c2f9c3bdfae56102, processorArchitecture=MSIL">
|
||||
<HintPath>$(SolutionDir)packages\NJsonSchema.9.10.42\lib\net45\NJsonSchema.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Net" />
|
||||
<Reference Include="System.Runtime.Serialization" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
@@ -155,6 +163,9 @@
|
||||
<Compile Include="ResourceManagement\ResourceCache.cs" />
|
||||
<Compile Include="ResourceManagement\ResourceTypes\AudioResource.cs" />
|
||||
<Compile Include="ResourceManagement\ResourceTypes\FontResource.cs" />
|
||||
<Compile Include="Graphics\RSI\RSI.cs" />
|
||||
<Compile Include="Graphics\RSI\RSI.State.cs" />
|
||||
<Compile Include="ResourceManagement\ResourceTypes\RSIResource.cs" />
|
||||
<Compile Include="ResourceManagement\ResourceTypes\TextureResource.cs" />
|
||||
<Compile Include="SceneTreeHolder.cs" />
|
||||
<Compile Include="UserInterface\Controls\BaseButton.cs" />
|
||||
@@ -258,7 +269,12 @@
|
||||
<Name>SS14.Shared</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.config" />
|
||||
<EmbeddedResource Include="Graphics\RSI\RSISchema.json" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Import Project="..\MSBuild\SS14.Engine.targets" />
|
||||
<Target Name="AfterBuild" DependsOnTargets="CopyBsdiffWrap" />
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -161,7 +161,7 @@ namespace SS14.Client.UserInterface.CustomControls
|
||||
if (spriteNameParam != null)
|
||||
spriteName = spriteNameParam.GetValue<string>();
|
||||
|
||||
var tex = resourceCache.GetResource<TextureResource>("Textures/" + spriteName);
|
||||
var tex = resourceCache.GetResource<TextureResource>("/Textures/" + spriteName);
|
||||
var rect = container.GetChild("TextureWrap").GetChild<TextureRect>("TextureRect");
|
||||
if (tex != null)
|
||||
{
|
||||
|
||||
11
SS14.Client/app.config
Normal file
11
SS14.Client/app.config
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-11.0.0.0" newVersion="11.0.0.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
||||
@@ -1,6 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Newtonsoft.Json" version="11.0.2" targetFramework="net451" />
|
||||
<package id="NJsonSchema" version="9.10.42" targetFramework="net451" />
|
||||
<package id="SharpZipLib" version="0.86.0" targetFramework="net451" />
|
||||
<package id="YamlDotNet" version="4.3.0" targetFramework="net451" />
|
||||
<package id="System.ValueTuple" version="4.4.0" targetFramework="net451" />
|
||||
</packages>
|
||||
<package id="YamlDotNet" version="4.3.0" targetFramework="net451" />
|
||||
</packages>
|
||||
@@ -37,6 +37,7 @@ using SS14.Server.Player;
|
||||
using SS14.Shared.Enums;
|
||||
using SS14.Shared.Reflection;
|
||||
using SS14.Shared.Timing;
|
||||
using SS14.Shared.Utility;
|
||||
|
||||
namespace SS14.Server
|
||||
{
|
||||
@@ -180,7 +181,7 @@ namespace SS14.Server
|
||||
// Load from the resources dir in the repo root instead.
|
||||
// It's a debug build so this is fine.
|
||||
_resources.MountContentDirectory(@"../../Resources/");
|
||||
_resources.MountContentDirectory(@"Resources/Assemblies", "Assemblies/");
|
||||
_resources.MountContentDirectory(@"Resources/Assemblies", new ResourcePath("/Assemblies/"));
|
||||
#endif
|
||||
|
||||
//mount the engine content pack
|
||||
@@ -219,7 +220,7 @@ namespace SS14.Server
|
||||
// because of 'reasons' this has to be called after the last assembly is loaded
|
||||
// otherwise the prototypes will be cleared
|
||||
var prototypeManager = IoCManager.Resolve<IPrototypeManager>();
|
||||
prototypeManager.LoadDirectory(@"Prototypes");
|
||||
prototypeManager.LoadDirectory(new ResourcePath(@"/Prototypes"));
|
||||
prototypeManager.Resync();
|
||||
|
||||
var clientConsole = IoCManager.Resolve<IClientConsoleHost>();
|
||||
@@ -325,7 +326,7 @@ namespace SS14.Server
|
||||
//TODO: This should prob shutdown all managers in a loop.
|
||||
|
||||
// remove all maps
|
||||
if(_runLevel == ServerRunLevel.Game)
|
||||
if (_runLevel == ServerRunLevel.Game)
|
||||
{
|
||||
var mapMgr = IoCManager.Resolve<IMapManager>();
|
||||
|
||||
|
||||
@@ -87,11 +87,11 @@ namespace SS14.Server.Chat
|
||||
|
||||
private void LoadEmotes()
|
||||
{
|
||||
if (!_resources.TryContentFileRead(@"emotes.xml", out var emoteFileStream))
|
||||
if (!_resources.TryContentFileRead(@"/emotes.xml", out var emoteFileStream))
|
||||
return;
|
||||
|
||||
var serializer = new XmlSerializer(typeof(List<Emote>));
|
||||
var emotes = (List<Emote>) serializer.Deserialize(emoteFileStream);
|
||||
var emotes = (List<Emote>)serializer.Deserialize(emoteFileStream);
|
||||
emoteFileStream.Close();
|
||||
|
||||
foreach (var emote in emotes)
|
||||
|
||||
@@ -10,6 +10,7 @@ using SS14.Shared.Log;
|
||||
using SS14.Shared.Map;
|
||||
using SS14.Shared.Prototypes;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
using SS14.Shared.Utility;
|
||||
|
||||
namespace SS14.Server.Maps
|
||||
{
|
||||
@@ -42,7 +43,7 @@ namespace SS14.Server.Maps
|
||||
var document = new YamlDocument(root);
|
||||
|
||||
var rootPath = _resMan.ConfigDirectory;
|
||||
var path = Path.Combine(rootPath, "./", yamlPath);
|
||||
var path = Path.Combine(rootPath.ToString(), "./", yamlPath);
|
||||
var fullPath = Path.GetFullPath(path);
|
||||
|
||||
var dir = Path.GetDirectoryName(fullPath);
|
||||
@@ -72,7 +73,7 @@ namespace SS14.Server.Maps
|
||||
Logger.Info($"[MAP] No user blueprint path: {fullPath}");
|
||||
|
||||
// fallback to content
|
||||
if (_resMan.TryContentFileRead(path, out var contentReader))
|
||||
if (_resMan.TryContentFileRead(ResourcePath.Root / path, out var contentReader))
|
||||
{
|
||||
reader = new StreamReader(contentReader);
|
||||
}
|
||||
|
||||
@@ -50,6 +50,9 @@ namespace SS14.Server
|
||||
{
|
||||
private static void Main(string[] args)
|
||||
{
|
||||
#if !X64
|
||||
throw new InvalidOperationException("The server cannot start outside x64.");
|
||||
#endif
|
||||
//Register minidump dumper only if the app isn't being debugged. No use filling up hard drives with shite
|
||||
RegisterIoC();
|
||||
LoadContentAssemblies();
|
||||
|
||||
@@ -50,13 +50,13 @@
|
||||
<AllowedReferenceRelatedFileExtensions>.dll.config</AllowedReferenceRelatedFileExtensions>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x64' ">
|
||||
<DefineConstants>TRACE;DEBUG</DefineConstants>
|
||||
<DefineConstants>TRACE;DEBUG;X64</DefineConstants>
|
||||
<DebugSymbols>True</DebugSymbols>
|
||||
<Optimize>False</Optimize>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|x64' ">
|
||||
<DefineConstants>TRACE;RELEASE</DefineConstants>
|
||||
<DefineConstants>TRACE;RELEASE;X64</DefineConstants>
|
||||
<Optimize>True</Optimize>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<AllowedReferenceRelatedFileExtensions>.dll.config</AllowedReferenceRelatedFileExtensions>
|
||||
|
||||
@@ -28,6 +28,10 @@
|
||||
<assemblyIdentity name="ICSharpCode.SharpZipLib" publicKeyToken="1b03e6acf1164f73" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-0.86.0.518" newVersion="0.86.0.518" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-11.0.0.0" newVersion="11.0.0.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
||||
|
||||
@@ -177,17 +177,17 @@ namespace SS14.Shared.ContentPack
|
||||
public static bool TryLoadAssembly<T>(IResourceManager resMan, string assemblyName) where T : GameShared
|
||||
{
|
||||
// get the assembly from the file system
|
||||
if (resMan.TryContentFileRead($@"Assemblies/{assemblyName}.dll", out MemoryStream gameDll))
|
||||
if (resMan.TryContentFileRead($@"/Assemblies/{assemblyName}.dll", out MemoryStream gameDll))
|
||||
{
|
||||
Logger.Debug($"[SRV] Loading {assemblyName} DLL");
|
||||
|
||||
// see if debug info is present
|
||||
if (resMan.TryContentFileRead($@"Assemblies/{assemblyName}.pdb", out MemoryStream gamePdb))
|
||||
if (resMan.TryContentFileRead($@"/Assemblies/{assemblyName}.pdb", out MemoryStream gamePdb))
|
||||
{
|
||||
try
|
||||
{
|
||||
// load the assembly into the process, and bootstrap the GameServer entry point.
|
||||
AssemblyLoader.LoadGameAssembly<T>(gameDll.ToArray(), gamePdb.ToArray());
|
||||
LoadGameAssembly<T>(gameDll.ToArray(), gamePdb.ToArray());
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -201,7 +201,7 @@ namespace SS14.Shared.ContentPack
|
||||
try
|
||||
{
|
||||
// load the assembly into the process, and bootstrap the GameServer entry point.
|
||||
AssemblyLoader.LoadGameAssembly<T>(gameDll.ToArray());
|
||||
LoadGameAssembly<T>(gameDll.ToArray());
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
|
||||
@@ -1,65 +1,71 @@
|
||||
using SS14.Shared.Log;
|
||||
using SS14.Shared.Utility;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace SS14.Shared.ContentPack
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds info about a directory that is mounted in the VFS.
|
||||
/// </summary>
|
||||
internal class DirLoader : IContentRoot
|
||||
public partial class ResourceManager
|
||||
{
|
||||
private readonly DirectoryInfo _directory;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// Holds info about a directory that is mounted in the VFS.
|
||||
/// </summary>
|
||||
/// <param name="directory">Directory to mount.</param>
|
||||
public DirLoader(DirectoryInfo directory)
|
||||
class DirLoader : IContentRoot
|
||||
{
|
||||
_directory = directory;
|
||||
}
|
||||
private readonly DirectoryInfo _directory;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Mount()
|
||||
{
|
||||
// Exists returns false if it actually exists, but no perms to read it
|
||||
return _directory.Exists;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MemoryStream GetFile(string relPath)
|
||||
{
|
||||
var path = GetPath(relPath);
|
||||
if (path == null)
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="directory">Directory to mount.</param>
|
||||
public DirLoader(DirectoryInfo directory)
|
||||
{
|
||||
return null;
|
||||
_directory = directory;
|
||||
}
|
||||
|
||||
var bytes = File.ReadAllBytes(path);
|
||||
return new MemoryStream(bytes, false);
|
||||
}
|
||||
|
||||
internal string GetPath(string relPath)
|
||||
{
|
||||
var fullPath = Path.GetFullPath(Path.Combine(_directory.FullName, relPath));
|
||||
|
||||
if (!File.Exists(fullPath))
|
||||
return null;
|
||||
|
||||
return fullPath;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<string> FindFiles(string path)
|
||||
{
|
||||
var fullPath = Path.GetFullPath(Path.Combine(_directory.FullName, path));
|
||||
var paths = PathHelpers.GetFiles(fullPath);
|
||||
|
||||
// GetFiles returns full paths, we want them relative to root
|
||||
foreach (var filePath in paths)
|
||||
/// <inheritdoc />
|
||||
public void Mount()
|
||||
{
|
||||
yield return filePath.Substring(_directory.FullName.Length);
|
||||
// Looks good to me
|
||||
// Nothing to check here since the ResourceManager handles checking permissions.
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetFile(ResourcePath relPath, out MemoryStream fileStream)
|
||||
{
|
||||
var path = GetPath(relPath);
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
fileStream = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var bytes = File.ReadAllBytes(path);
|
||||
fileStream = new MemoryStream(bytes, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
internal string GetPath(ResourcePath relPath)
|
||||
{
|
||||
return Path.GetFullPath(Path.Combine(_directory.FullName, relPath.ToRelativeSystemPath()));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<ResourcePath> FindFiles(ResourcePath path)
|
||||
{
|
||||
var fullPath = GetPath(path);
|
||||
if (!Directory.Exists(fullPath))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
var paths = PathHelpers.GetFiles(fullPath);
|
||||
|
||||
// GetFiles returns full paths, we want them relative to root
|
||||
foreach (var filePath in paths)
|
||||
{
|
||||
var relpath = filePath.Substring(_directory.FullName.Length);
|
||||
yield return ResourcePath.FromRelativeSystemPath(relpath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,31 +1,35 @@
|
||||
using System.Collections.Generic;
|
||||
using SS14.Shared.Utility;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace SS14.Shared.ContentPack
|
||||
{
|
||||
/// <summary>
|
||||
/// Common interface for mounting various things in the VFS
|
||||
/// </summary>
|
||||
internal interface IContentRoot
|
||||
public partial class ResourceManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the content root.
|
||||
/// Common interface for mounting various things in the VFS.
|
||||
/// </summary>
|
||||
/// <returns>If the content was mounted properly.</returns>
|
||||
bool Mount();
|
||||
protected interface IContentRoot
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes the content root.
|
||||
/// Throws an exception if the content root failed to mount.
|
||||
/// </summary>
|
||||
void Mount();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a file from the content root using the relative path.
|
||||
/// </summary>
|
||||
/// <param name="relPath">Relative path from the root directory.</param>
|
||||
/// <returns>A stream of the file loaded into memory.</returns>
|
||||
MemoryStream GetFile(string relPath);
|
||||
/// <summary>
|
||||
/// Gets a file from the content root using the relative path.
|
||||
/// </summary>
|
||||
/// <param name="relPath">Relative path from the root directory.</param>
|
||||
/// <returns>A stream of the file loaded into memory.</returns>
|
||||
bool TryGetFile(ResourcePath relPath, out MemoryStream stream);
|
||||
|
||||
/// <summary>
|
||||
/// Recursively finds all files in a directory and all sub directories.
|
||||
/// </summary>
|
||||
/// <param name="path">Directory to search inside of.</param>
|
||||
/// <returns>Enumeration of all relative file paths of the files found.</returns>
|
||||
IEnumerable<string> FindFiles(string path);
|
||||
/// <summary>
|
||||
/// Recursively finds all files in a directory and all sub directories.
|
||||
/// </summary>
|
||||
/// <param name="path">Directory to search inside of.</param>
|
||||
/// <returns>Enumeration of all relative file paths of the files found.</returns>
|
||||
IEnumerable<ResourcePath> FindFiles(ResourcePath path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,65 +2,70 @@
|
||||
using System.IO;
|
||||
using ICSharpCode.SharpZipLib.Zip;
|
||||
using SS14.Shared.Log;
|
||||
using SS14.Shared.Utility;
|
||||
|
||||
namespace SS14.Shared.ContentPack
|
||||
{
|
||||
/// <summary>
|
||||
/// Loads a zipped content pack into the VFS.
|
||||
/// </summary>
|
||||
internal class PackLoader : IContentRoot
|
||||
public partial class ResourceManager
|
||||
{
|
||||
private readonly FileInfo _pack;
|
||||
private ZipFile _zip;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// Loads a zipped content pack into the VFS.
|
||||
/// </summary>
|
||||
/// <param name="pack">The zip file to mount in the VFS.</param>
|
||||
public PackLoader(FileInfo pack)
|
||||
class PackLoader : IContentRoot
|
||||
{
|
||||
_pack = pack;
|
||||
}
|
||||
private readonly FileInfo _pack;
|
||||
private ZipFile _zip;
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Mount()
|
||||
{
|
||||
Logger.Info($"[RES] Loading ContentPack: {_pack.FullName}...");
|
||||
|
||||
var zipFileStream = File.OpenRead(_pack.FullName);
|
||||
_zip = new ZipFile(zipFileStream);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MemoryStream GetFile(string relPath)
|
||||
{
|
||||
var entry = _zip.GetEntry(relPath);
|
||||
|
||||
if (entry == null)
|
||||
return null;
|
||||
|
||||
// this caches the deflated entry stream in memory
|
||||
// this way people can read the stream however many times they want to,
|
||||
// without the performance hit of deflating it every time.
|
||||
var memStream = new MemoryStream();
|
||||
using (var zipStream = _zip.GetInputStream(entry))
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
/// <param name="pack">The zip file to mount in the VFS.</param>
|
||||
public PackLoader(FileInfo pack)
|
||||
{
|
||||
zipStream.CopyTo(memStream);
|
||||
memStream.Position = 0;
|
||||
_pack = pack;
|
||||
}
|
||||
|
||||
return memStream;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<string> FindFiles(string path)
|
||||
{
|
||||
foreach (ZipEntry zipEntry in _zip)
|
||||
/// <inheritdoc />
|
||||
public void Mount()
|
||||
{
|
||||
if (zipEntry.IsFile && zipEntry.Name.StartsWith(path))
|
||||
yield return zipEntry.Name;
|
||||
Logger.Info($"[RES] Loading ContentPack: {_pack.FullName}...");
|
||||
|
||||
var zipFileStream = File.OpenRead(_pack.FullName);
|
||||
_zip = new ZipFile(zipFileStream);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryGetFile(ResourcePath relPath, out MemoryStream fileStream)
|
||||
{
|
||||
var entry = _zip.GetEntry(relPath.ToRootedPath().ToString());
|
||||
|
||||
if (entry == null)
|
||||
{
|
||||
fileStream = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// this caches the deflated entry stream in memory
|
||||
// this way people can read the stream however many times they want to,
|
||||
// without the performance hit of deflating it every time.
|
||||
fileStream = new MemoryStream();
|
||||
using (var zipStream = _zip.GetInputStream(entry))
|
||||
{
|
||||
zipStream.CopyTo(fileStream);
|
||||
fileStream.Position = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<ResourcePath> FindFiles(ResourcePath path)
|
||||
{
|
||||
foreach (ZipEntry zipEntry in _zip)
|
||||
{
|
||||
if (zipEntry.IsFile && zipEntry.Name.StartsWith(path.ToRootedPath().ToString()))
|
||||
yield return new ResourcePath(zipEntry.Name).ToRelativePath();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,13 +7,14 @@ using SS14.Shared.Interfaces;
|
||||
using SS14.Shared.Interfaces.Configuration;
|
||||
using SS14.Shared.IoC;
|
||||
using SS14.Shared.Log;
|
||||
using SS14.Shared.Utility;
|
||||
|
||||
namespace SS14.Shared.ContentPack
|
||||
{
|
||||
/// <summary>
|
||||
/// Virtual file system for all disk resources.
|
||||
/// </summary>
|
||||
public class ResourceManager : IResourceManager
|
||||
public partial class ResourceManager : IResourceManager
|
||||
{
|
||||
private const string DataFolderName = "Space Station 14";
|
||||
|
||||
@@ -21,7 +22,7 @@ namespace SS14.Shared.ContentPack
|
||||
private readonly IConfigurationManager _config;
|
||||
|
||||
private DirectoryInfo _configRoot;
|
||||
private readonly List<(string prefix, IContentRoot root)> _contentRoots = new List<(string, IContentRoot)>();
|
||||
private readonly List<(ResourcePath prefix, IContentRoot root)> _contentRoots = new List<(ResourcePath, IContentRoot)>();
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ConfigDirectory => _configRoot.FullName;
|
||||
@@ -55,107 +56,142 @@ namespace SS14.Shared.ContentPack
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void MountContentPack(string pack, string prefix=null)
|
||||
public void MountContentPack(string pack, ResourcePath prefix = null)
|
||||
{
|
||||
if (prefix == null)
|
||||
{
|
||||
prefix = ResourcePath.Root;
|
||||
}
|
||||
if (!prefix.IsRooted)
|
||||
{
|
||||
throw new ArgumentException("Prefix must be rooted.", nameof(prefix));
|
||||
}
|
||||
pack = PathHelpers.ExecutableRelativeFile(pack);
|
||||
|
||||
var packInfo = new FileInfo(pack);
|
||||
|
||||
if (!packInfo.Exists)
|
||||
{
|
||||
throw new FileNotFoundException("Specified ContentPack does not exist: " + packInfo.FullName);
|
||||
}
|
||||
|
||||
//create new PackLoader
|
||||
var loader = new PackLoader(packInfo);
|
||||
|
||||
if (loader.Mount())
|
||||
_contentRoots.Add((prefix, loader));
|
||||
loader.Mount();
|
||||
_contentRoots.Add((prefix, loader));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void MountContentDirectory(string path, string prefix=null)
|
||||
public void MountContentDirectory(string path, ResourcePath prefix = null)
|
||||
{
|
||||
if (prefix == null)
|
||||
{
|
||||
prefix = ResourcePath.Root;
|
||||
}
|
||||
if (!prefix.IsRooted)
|
||||
{
|
||||
throw new ArgumentException("Prefix must be rooted.", nameof(prefix));
|
||||
}
|
||||
path = PathHelpers.ExecutableRelativeFile(path);
|
||||
var pathInfo = new DirectoryInfo(path);
|
||||
if (!pathInfo.Exists)
|
||||
{
|
||||
throw new DirectoryNotFoundException("Specified directory does not exist: " + pathInfo.FullName);
|
||||
}
|
||||
|
||||
var loader = new DirLoader(pathInfo);
|
||||
if (loader.Mount())
|
||||
{
|
||||
_contentRoots.Add((prefix, loader));
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Error($"Unable to mount content directory: {path}");
|
||||
}
|
||||
loader.Mount();
|
||||
_contentRoots.Add((prefix, loader));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public MemoryStream ContentFileRead(string path)
|
||||
{
|
||||
// loop over each root trying to get the file
|
||||
foreach ((var prefix, var root) in _contentRoots)
|
||||
{
|
||||
if (!TryHandlePrefix(path, prefix, out var tempPath))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var file = root.GetFile(tempPath);
|
||||
if (file != null)
|
||||
return file;
|
||||
}
|
||||
return null;
|
||||
return ContentFileRead(new ResourcePath(path));
|
||||
}
|
||||
|
||||
// TODO: Remove this when/if we can get Godot to load from not-the filesystem.
|
||||
protected bool TryGetDiskFilePath(string path, out string diskPath)
|
||||
/// <inheritdoc />
|
||||
public MemoryStream ContentFileRead(ResourcePath path)
|
||||
{
|
||||
// loop over each root trying to get the file
|
||||
foreach ((var prefix, var root) in _contentRoots)
|
||||
if (TryContentFileRead(path, out var fileStream))
|
||||
{
|
||||
if (!(root is DirLoader dirLoader) || !TryHandlePrefix(path, prefix, out var tempPath))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
diskPath = dirLoader.GetPath(tempPath);
|
||||
if (diskPath != null)
|
||||
return true;
|
||||
return fileStream;
|
||||
}
|
||||
diskPath = null;
|
||||
return false;
|
||||
throw new FileNotFoundException($"Path does not exist in the VFS: '{path}'");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryContentFileRead(string path, out MemoryStream fileStream)
|
||||
{
|
||||
var file = ContentFileRead(path);
|
||||
if (file != null)
|
||||
return TryContentFileRead(new ResourcePath(path), out fileStream);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryContentFileRead(ResourcePath path, out MemoryStream fileStream)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
fileStream = file;
|
||||
return true;
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
fileStream = default(MemoryStream);
|
||||
if (!path.IsRooted)
|
||||
{
|
||||
throw new ArgumentException("Path must be rooted", nameof(path));
|
||||
}
|
||||
foreach ((var prefix, var root) in _contentRoots)
|
||||
{
|
||||
if (!path.TryRelativeTo(prefix, out var relative))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (root.TryGetFile(relative, out fileStream))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
fileStream = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool ContentFileExists(string path)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
return ContentFileExists(new ResourcePath(path));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<string> ContentFindFiles(string path)
|
||||
public bool ContentFileExists(ResourcePath path)
|
||||
{
|
||||
var alreadyReturnedFiles = new HashSet<string>();
|
||||
return TryContentFileRead(path, out var _);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<ResourcePath> ContentFindFiles(string path)
|
||||
{
|
||||
return ContentFindFiles(new ResourcePath(path));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<ResourcePath> ContentFindFiles(ResourcePath path)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
if (!path.IsRooted)
|
||||
{
|
||||
throw new ArgumentException("Path is not rooted", nameof(path));
|
||||
}
|
||||
var alreadyReturnedFiles = new HashSet<ResourcePath>();
|
||||
foreach ((var prefix, var root) in _contentRoots)
|
||||
{
|
||||
if (!TryHandlePrefix(path, prefix, out var tempPath))
|
||||
if (!path.TryRelativeTo(prefix, out var relative))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var filename in root.FindFiles(tempPath))
|
||||
foreach (var filename in root.FindFiles(relative))
|
||||
{
|
||||
var newpath = prefix + filename;
|
||||
var newpath = prefix / filename;
|
||||
if (!alreadyReturnedFiles.Contains(newpath))
|
||||
{
|
||||
alreadyReturnedFiles.Add(newpath);
|
||||
@@ -165,24 +201,22 @@ namespace SS14.Shared.ContentPack
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryHandlePrefix(string path, string prefix, out string actualPath)
|
||||
// TODO: Remove this when/if we can get Godot to load from not-the-filesystem.
|
||||
public bool TryGetDiskFilePath(ResourcePath path, out string diskPath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(prefix))
|
||||
// loop over each root trying to get the file
|
||||
foreach ((var prefix, var root) in _contentRoots)
|
||||
{
|
||||
actualPath = path;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (path.StartsWith(prefix))
|
||||
{
|
||||
actualPath = path.Substring(prefix.Length);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
actualPath = null;
|
||||
return false;
|
||||
if (!(root is DirLoader dirLoader) || !path.TryRelativeTo(prefix, out var tempPath))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
diskPath = dirLoader.GetPath(tempPath);
|
||||
if (File.Exists(diskPath))
|
||||
return true;
|
||||
}
|
||||
diskPath = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using SS14.Shared.Utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace SS14.Shared.Interfaces
|
||||
@@ -22,47 +24,108 @@ namespace SS14.Shared.Interfaces
|
||||
/// Loads a content pack from disk into the VFS. The path is relative to
|
||||
/// the executable location on disk.
|
||||
/// </summary>
|
||||
/// <param name="pack"></param>
|
||||
void MountContentPack(string pack, string prefix=null);
|
||||
/// <param name="pack">The path of the pack to load on disk.</param>
|
||||
/// <param name="prefix">The resource path to which all files in the pack will be relative to in the VFS.</param>
|
||||
/// <exception cref="FileNotFoundException">Thrown if <paramref name="pack"/> does not exist on disk.</exception>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="prefix"/> is not rooted.</exception>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="pack"/> is null.</exception>
|
||||
void MountContentPack(string pack, ResourcePath prefix = null);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a directory to search inside of to the VFS. The directory is relative to
|
||||
/// the executable location on disk.
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
void MountContentDirectory(string path, string prefix=null);
|
||||
/// <param name="path">The path of the directory to add to the VFS on disk.</param>
|
||||
/// <param name="prefix">The resource path to which all files in the directory will be relative to in the VFS.</param>
|
||||
/// <exception cref="DirectoryNotFoundException">Thrown if <paramref name="path"/> does not exist on disk.</exception>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="prefix"/> passed is not rooted.</exception>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="path"/> is null.</exception>
|
||||
void MountContentDirectory(string path, ResourcePath prefix = null);
|
||||
|
||||
/// <summary>
|
||||
/// Read a file from the mounted content roots.
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
/// <param name="path">The path to the file in the VFS. Must be rooted.</param>
|
||||
/// <returns>The memory stream of the file.</returns>
|
||||
/// <exception cref="FileNotFoundException">Thrown if <paramref name="path"/> does not exist in the VFS.</exception>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="path"/> is not rooted.</exception>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="path"/> is null.</exception>
|
||||
MemoryStream ContentFileRead(ResourcePath path);
|
||||
|
||||
/// <summary>
|
||||
/// Read a file from the mounted content roots.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to the file in the VFS. Must be rooted.</param>
|
||||
/// <returns>The memory stream of the file.</returns>
|
||||
/// <exception cref="FileNotFoundException">Thrown if <paramref name="path"/> does not exist in the VFS.</exception>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="path"/> is not rooted.</exception>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="path"/> is null.</exception>
|
||||
MemoryStream ContentFileRead(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Check if a file exists in any of the mounted content roots.
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
/// <param name="path">The path of the file to check.</param>
|
||||
/// <returns>True if the file exists, false otherwise.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="path"/> is not rooted.</exception>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="path"/> is null.</exception>
|
||||
bool ContentFileExists(ResourcePath path);
|
||||
|
||||
/// <summary>
|
||||
/// Check if a file exists in any of the mounted content roots.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the file to check.</param>
|
||||
/// <returns>True if the file exists, false otherwise.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="path"/> is not rooted.</exception>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="path"/> is null.</exception>
|
||||
bool ContentFileExists(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Try to read a file from the mounted content roots.
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <param name="fileStream"></param>
|
||||
/// <returns></returns>
|
||||
/// <param name="path">The path of the file to try to read.</param>
|
||||
/// <param name="fileStream">The memory stream of the file's contents. Null if the file could not be loaded.</param>
|
||||
/// <returns>True if the file could be loaded, false otherwise.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="path"/> is not rooted.</exception>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="path"/> is null.</exception>
|
||||
bool TryContentFileRead(ResourcePath path, out MemoryStream fileStream);
|
||||
|
||||
/// <summary>
|
||||
/// Try to read a file from the mounted content roots.
|
||||
/// </summary>
|
||||
/// <param name="path">The path of the file to try to read.</param>
|
||||
/// <param name="fileStream">The memory stream of the file's contents. Null if the file could not be loaded.</param>
|
||||
/// <returns>True if the file could be loaded, false otherwise.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="path"/> is not rooted.</exception>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="path"/> is null.</exception>
|
||||
bool TryContentFileRead(string path, out MemoryStream fileStream);
|
||||
|
||||
/// <summary>
|
||||
/// Recursively finds all files in a directory and all sub directories.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the directory does not exist, an empty enumerable is returned.
|
||||
/// </remarks>
|
||||
/// <param name="path">Directory to search inside of.</param>
|
||||
/// <returns>Enumeration of all relative file paths of the files found.</returns>
|
||||
IEnumerable<string> ContentFindFiles(string path);
|
||||
/// <returns>Enumeration of all relative file paths of the files found, that is they are relative to <paramref name="path"/>.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="path"/> is not rooted.</exception>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="path"/> is null.</exception>
|
||||
IEnumerable<ResourcePath> ContentFindFiles(ResourcePath path);
|
||||
|
||||
/// <summary>
|
||||
/// Absolute path to the configuration directory for the game. If you are writing any files,
|
||||
/// Recursively finds all files in a directory and all sub directories.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If the directory does not exist, an empty enumerable is returned.
|
||||
/// </remarks>
|
||||
/// <param name="path">Directory to search inside of.</param>
|
||||
/// <returns>Enumeration of all relative file paths of the files found, that is they are relative to <paramref name="path"/>.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown if <paramref name="path"/> is not rooted.</exception>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="path"/> is null.</exception>
|
||||
IEnumerable<ResourcePath> ContentFindFiles(string path);
|
||||
|
||||
/// <summary>
|
||||
/// Absolute disk path to the configuration directory for the game. If you are writing any files,
|
||||
/// they need to be inside of this directory.
|
||||
/// </summary>
|
||||
string ConfigDirectory { get; }
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace SS14.Shared.Maths
|
||||
{
|
||||
[JsonObject(memberSerialization: MemberSerialization.Fields)]
|
||||
[Serializable]
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct Vector2u : IEquatable<Vector2u>
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace SS14.Shared.Prototypes
|
||||
/// <summary>
|
||||
/// Load prototypes from files in a directory, recursively.
|
||||
/// </summary>
|
||||
void LoadDirectory(string path);
|
||||
void LoadDirectory(ResourcePath path);
|
||||
void LoadFromStream(TextReader stream);
|
||||
/// <summary>
|
||||
/// Clear out all prototypes and reset to a blank slate.
|
||||
@@ -181,7 +181,7 @@ namespace SS14.Shared.Prototypes
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void LoadDirectory(string path)
|
||||
public void LoadDirectory(ResourcePath path)
|
||||
{
|
||||
foreach (var filePath in _resources.ContentFindFiles(path))
|
||||
{
|
||||
|
||||
@@ -84,6 +84,9 @@
|
||||
<Reference Include="Nett, Version=0.7.0.0, Culture=neutral, processorArchitecture=MSIL">
|
||||
<HintPath>$(SolutionDir)packages\Nett.0.7.0\lib\Net40\Nett.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=11.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>$(SolutionDir)packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System">
|
||||
<Name>System</Name>
|
||||
</Reference>
|
||||
@@ -97,6 +100,8 @@
|
||||
<Name>System.Data.DataSetExtensions</Name>
|
||||
</Reference>
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Net" />
|
||||
<Reference Include="System.Runtime.Serialization" />
|
||||
<Reference Include="System.Windows.Forms">
|
||||
<Name>System.Windows.Forms</Name>
|
||||
</Reference>
|
||||
@@ -262,6 +267,7 @@
|
||||
<Compile Include="Timing\GameLoop.cs" />
|
||||
<Compile Include="Timing\IStopwatch.cs" />
|
||||
<Compile Include="Timing\Stopwatch.cs" />
|
||||
<Compile Include="Utility\ResourcePath.cs" />
|
||||
<Compile Include="Utility\QuadTree.cs" />
|
||||
<Compile Include="Reflection\ReflectAttribute.cs" />
|
||||
<Compile Include="Serialization\NetSerializableAttribute.cs" />
|
||||
@@ -394,4 +400,4 @@
|
||||
<Visible>False</Visible>
|
||||
</Resources>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace SS14.Shared.Utility
|
||||
@@ -13,12 +13,11 @@ namespace SS14.Shared.Utility
|
||||
// they are the same. Mono/linux has both as '/', for example.
|
||||
// Hardcode the only platforms we care about.
|
||||
|
||||
var separators = new char [] { '/', '\\' };
|
||||
var separators = new char[] { '/', '\\' };
|
||||
string newpath = "";
|
||||
foreach (string tmp in pathname.Split(separators))
|
||||
newpath = Path.Combine (newpath, tmp);
|
||||
newpath = System.IO.Path.Combine(newpath, tmp);
|
||||
return newpath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
543
SS14.Shared/Utility/ResourcePath.cs
Normal file
543
SS14.Shared/Utility/ResourcePath.cs
Normal file
@@ -0,0 +1,543 @@
|
||||
// Because System.IO.Path sucks.
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace SS14.Shared.Utility
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides object-oriented path manipulation for resource paths.
|
||||
/// ResourcePaths are immutable.
|
||||
/// </summary>
|
||||
public class ResourcePath
|
||||
{
|
||||
/// <summary>
|
||||
/// The separator for the file system of the system we are compiling to.
|
||||
/// Backslash on Windows, forward slash on sane systems.
|
||||
/// </summary>
|
||||
#if WINDOWS
|
||||
public const string SYSTEM_SEPARATOR = "\\";
|
||||
#else
|
||||
public const string SYSTEM_SEPARATOR = "/";
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// "." as a static. Separator used is <c>/</c>.
|
||||
/// </summary>
|
||||
public static readonly ResourcePath Self = new ResourcePath(".");
|
||||
|
||||
/// <summary>
|
||||
/// "/" (root) as a static. Separator used is <c>/</c>.
|
||||
/// </summary>
|
||||
public static readonly ResourcePath Root = new ResourcePath("/");
|
||||
|
||||
/// <summary>
|
||||
/// List of the segments of the path.
|
||||
/// This is pretty much a split of the input string path by separator,
|
||||
/// except for the root, which is represented as the separator in position #0.
|
||||
/// </summary>
|
||||
private readonly string[] Segments;
|
||||
|
||||
/// <summary>
|
||||
/// The separator between "segments"/"directories" for this path.
|
||||
/// </summary>
|
||||
public string Separator { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a new path from a string, splitting it by the separator provided.
|
||||
/// </summary>
|
||||
/// <param name="path">The string path to turn into a resource path.</param>
|
||||
/// <param name="separator">The separator for the resource path.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if you try to use "." as separator.</exception>
|
||||
/// <exception cref="ArgumentNullException">Thrown if either argument is null.</exception>
|
||||
public ResourcePath(string path, string separator = "/")
|
||||
{
|
||||
if (separator == ".")
|
||||
{
|
||||
throw new ArgumentException("Yeah no.", nameof(separator));
|
||||
}
|
||||
Separator = separator;
|
||||
|
||||
if (path == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
}
|
||||
if (separator == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(separator));
|
||||
}
|
||||
if (path == "")
|
||||
{
|
||||
Segments = new string[] { "." };
|
||||
return;
|
||||
}
|
||||
|
||||
var segments = new List<string>();
|
||||
var splitsegments = path.Split(new string[] { separator }, StringSplitOptions.None);
|
||||
var i = 0;
|
||||
if (splitsegments[0] == "")
|
||||
{
|
||||
i = 1;
|
||||
segments.Add(separator);
|
||||
}
|
||||
for (; i < splitsegments.Length; i++)
|
||||
{
|
||||
var segment = splitsegments[i];
|
||||
if (segment == "" || (segment == "." && segments.Count != 0))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (i == 1 && segments[0] == ".")
|
||||
{
|
||||
segments[0] = segment;
|
||||
}
|
||||
else
|
||||
{
|
||||
segments.Add(segment);
|
||||
}
|
||||
}
|
||||
Segments = segments.ToArray();
|
||||
}
|
||||
|
||||
private ResourcePath(string[] segments, string separator)
|
||||
{
|
||||
Segments = segments;
|
||||
Separator = separator;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string ToString()
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
var i = 0;
|
||||
if (IsRooted)
|
||||
{
|
||||
i = 1;
|
||||
builder.Append(Separator);
|
||||
}
|
||||
for (; i < Segments.Length; i++)
|
||||
{
|
||||
builder.Append(Segments[i]);
|
||||
if (i + 1 < Segments.Length)
|
||||
{
|
||||
builder.Append(Separator);
|
||||
}
|
||||
}
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the path is rooted (starts with the separator).
|
||||
/// </summary>
|
||||
/// <seealso cref="IsRelative" />
|
||||
/// <seealso cref="ToRootedPath"/>
|
||||
public bool IsRooted => Segments[0] == Separator;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the path is not rooted.
|
||||
/// </summary>
|
||||
/// <seealso cref="IsRooted" />
|
||||
/// <seealso cref="ToRelativePath"/>
|
||||
public bool IsRelative => !IsRooted;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the path is equal to "."
|
||||
/// </summary>
|
||||
public bool IsSelf => Segments.Length == 1 && Segments[0] == ".";
|
||||
|
||||
/// <summary>
|
||||
/// Returns the file extension of file path, if any.
|
||||
/// Returns "" if there is no file extension.
|
||||
/// The extension returned does NOT include a period.
|
||||
/// </summary>
|
||||
public string Extension
|
||||
{
|
||||
get
|
||||
{
|
||||
var filename = Filename;
|
||||
if (string.IsNullOrWhiteSpace(filename))
|
||||
{
|
||||
return "";
|
||||
}
|
||||
var index = filename.LastIndexOf('.');
|
||||
if (index == 0 || index == -1 || index == filename.Length - 1)
|
||||
{
|
||||
// The path is a dotfile (like .bashrc),
|
||||
// or there's no period at all,
|
||||
// or the period is at the very end.
|
||||
// Non of these cases are truly an extension.
|
||||
return "";
|
||||
}
|
||||
return filename.Substring(index + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the file name.
|
||||
/// </summary>
|
||||
public string Filename
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Segments.Length == 1 && IsRooted)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
|
||||
return Segments[Segments.Length - 1];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the file name, without extension.
|
||||
/// </summary>
|
||||
public string FilenameWithoutExtension
|
||||
{
|
||||
get
|
||||
{
|
||||
var filename = Filename;
|
||||
if (string.IsNullOrWhiteSpace(filename))
|
||||
{
|
||||
return filename;
|
||||
}
|
||||
var index = filename.LastIndexOf('.');
|
||||
if (index == 0 || index == -1 || index == filename.Length - 1)
|
||||
{
|
||||
return filename;
|
||||
}
|
||||
return filename.Substring(0, index);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new instance with a different separator set.
|
||||
/// </summary>
|
||||
/// <param name="newSeparator">The new separator to use.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if the new separator is "."</exception>
|
||||
/// <exception cref="ArgumentNullException">Thrown if <paramref name="newSeparator"/> is null.</exception>
|
||||
public ResourcePath ChangeSeparator(string newSeparator)
|
||||
{
|
||||
if (newSeparator == ".")
|
||||
{
|
||||
throw new ArgumentException("Yeah no.", nameof(newSeparator));
|
||||
}
|
||||
if (newSeparator == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(newSeparator));
|
||||
}
|
||||
// Convert the segments into a string path, then re-parse it.
|
||||
// Solves the edge case of the segments containing the new separator.
|
||||
var path = new ResourcePath(Segments, newSeparator).ToString();
|
||||
return new ResourcePath(path, newSeparator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Joins two resource paths together, with separator in between.
|
||||
/// If the second path is absolute, the first path is completely ignored.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentException">Thrown if the separators of the two paths do not match.</exception>
|
||||
// "Why use / instead of +" you may think:
|
||||
// * It's clever, although I got the idea from Python's pathlib.
|
||||
// * It avoids confusing operator precedence causing you to join two strings,
|
||||
// because path + string + string != path + (string + string),
|
||||
// whereas path / (string / string) doesn't compile.
|
||||
public static ResourcePath operator /(ResourcePath a, ResourcePath b)
|
||||
{
|
||||
if (a.Separator != b.Separator)
|
||||
{
|
||||
throw new ArgumentException("Both separators must be the same.");
|
||||
}
|
||||
if (b.IsRooted)
|
||||
{
|
||||
return b;
|
||||
}
|
||||
if (b.IsSelf)
|
||||
{
|
||||
return a;
|
||||
}
|
||||
string[] segments = new string[a.Segments.Length + b.Segments.Length];
|
||||
a.Segments.CopyTo(segments, 0);
|
||||
b.Segments.CopyTo(segments, a.Segments.Length);
|
||||
return new ResourcePath(segments, a.Separator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new segment to the path as string.
|
||||
/// </summary>
|
||||
public static ResourcePath operator /(ResourcePath path, string b)
|
||||
{
|
||||
return path / new ResourcePath(b, path.Separator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// "Cleans" the resource path, removing <c>..</c>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If .. appears at the base of a path, it is left alone. If it appears at root level (like /..) it is removed entirely.
|
||||
/// </remarks>
|
||||
public ResourcePath Clean()
|
||||
{
|
||||
var segments = new List<string>();
|
||||
|
||||
foreach (var segment in Segments)
|
||||
{
|
||||
// If you have ".." cleaning that up doesn't remove that.
|
||||
if (segment == ".." && segments.Count != 0)
|
||||
{
|
||||
// Trying to do /.. results in /
|
||||
if (segments.Count == 1 && segments[0] == Separator)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var pos = segments.Count - 1;
|
||||
if (segments[pos] != "..")
|
||||
{
|
||||
segments.RemoveAt(pos);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
segments.Add(segment);
|
||||
}
|
||||
|
||||
if (segments.Count == 0)
|
||||
{
|
||||
return new ResourcePath(".", Separator);
|
||||
}
|
||||
|
||||
return new ResourcePath(segments.ToArray(), Separator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check whether a path is clean, i.e. <see cref="Clean"/> would not modify it.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public bool IsClean()
|
||||
{
|
||||
for (var i = 0; i < Segments.Length; i++)
|
||||
{
|
||||
if (Segments[i] == "..")
|
||||
{
|
||||
if (IsRooted)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (i > 0 && Segments[i - 1] != "..")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Turns the path into a rooted path by prepending it with the separator.
|
||||
/// Does nothing if the path is already rooted.
|
||||
/// </summary>
|
||||
/// <seealso cref="IsRooted" />
|
||||
/// <seealso cref="ToRelativePath" />
|
||||
public ResourcePath ToRootedPath()
|
||||
{
|
||||
if (IsRooted)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
var segments = new string[Segments.Length + 1];
|
||||
Segments.CopyTo(segments, 1);
|
||||
segments[0] = Separator;
|
||||
return new ResourcePath(segments, Separator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Turns the path into a relative path by removing the root separator, if any.
|
||||
/// Does nothing if the path is already relative.
|
||||
/// </summary>
|
||||
/// <seealso cref="IsRelative"/>
|
||||
/// <seealso cref="ToRootedPath" />
|
||||
public ResourcePath ToRelativePath()
|
||||
{
|
||||
if (IsRelative)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
var segments = new string[Segments.Length - 1];
|
||||
Array.Copy(Segments, 1, segments, 0, Segments.Length - 1);
|
||||
return new ResourcePath(segments, Separator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Turns the path into a relative path with system-specific separator.
|
||||
/// For usage in disk I/O.
|
||||
/// </summary>
|
||||
public string ToRelativeSystemPath()
|
||||
{
|
||||
return ChangeSeparator(SYSTEM_SEPARATOR).ToRelativePath().ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a relative disk path back into a resource path.
|
||||
/// </summary>
|
||||
/// <exception cref="ArgumentNullException">Thrown if either argument is null.</exception>
|
||||
public static ResourcePath FromRelativeSystemPath(string path, string newseparator = "/")
|
||||
{
|
||||
return new ResourcePath(path, SYSTEM_SEPARATOR).ChangeSeparator(newseparator);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the path of how this instance is "relative" to <paramref name="basePath"/>,
|
||||
/// such that <c>basePath/result == this</c>.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var path1 = new ResourcePath("/a/b/c");
|
||||
/// var path2 = new ResourcePath("/a");
|
||||
/// Console.WriteLine(path1.RelativeTo(path2)); // prints "b/c".
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <exception cref="ArgumentException">Thrown if we are not relative to the base path or the separators are not the same.</exception>
|
||||
public ResourcePath RelativeTo(ResourcePath basePath)
|
||||
{
|
||||
if (TryRelativeTo(basePath, out var relative))
|
||||
{
|
||||
return relative;
|
||||
}
|
||||
|
||||
throw new ArgumentException($"{this} does not start with {basePath}.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try pattern version of <see cref="RelativeTo(ResourcePath)"/>
|
||||
/// </summary>
|
||||
/// <param name="relative">The path of how we are relative to <paramref name="basePath"/>, if at all.</param>
|
||||
/// <returns>True if we are relative to <paramref name="basePath"/>, false otherwise.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown if the separators are not the same.</exception>
|
||||
public bool TryRelativeTo(ResourcePath basePath, out ResourcePath relative)
|
||||
{
|
||||
if (basePath.Separator != Separator)
|
||||
{
|
||||
throw new ArgumentException("Separators must be the same.", nameof(basePath));
|
||||
}
|
||||
if (Segments.Length < basePath.Segments.Length)
|
||||
{
|
||||
relative = null;
|
||||
return false;
|
||||
}
|
||||
if (Segments.Length == basePath.Segments.Length)
|
||||
{
|
||||
if (this == basePath)
|
||||
{
|
||||
relative = new ResourcePath(".", Separator);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
relative = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
var i = 0;
|
||||
for (; i < basePath.Segments.Length; i++)
|
||||
{
|
||||
if (Segments[i] != basePath.Segments[i])
|
||||
{
|
||||
relative = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var segments = new string[Segments.Length - basePath.Segments.Length];
|
||||
Array.Copy(Segments, basePath.Segments.Length, segments, 0, segments.Length);
|
||||
relative = new ResourcePath(segments, Separator);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the common base of two paths.
|
||||
/// </summary>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var path1 = new ResourcePath("/a/b/c");
|
||||
/// var path2 = new ResourcePath("/a/e/d");
|
||||
/// Console.WriteLine(path1.RelativeTo(path2)); // prints "/a".
|
||||
/// </code>
|
||||
/// </example>
|
||||
/// <param name="other">The other path.</param>
|
||||
/// <exception cref="ArgumentException">Thrown if there is no common base between the two paths.</exception>
|
||||
public ResourcePath CommonBase(ResourcePath other)
|
||||
{
|
||||
if (other.Separator != Separator)
|
||||
{
|
||||
throw new ArgumentException("Separators must match.");
|
||||
}
|
||||
|
||||
var i = 0;
|
||||
for (; i < Segments.Length && i < other.Segments.Length; i++)
|
||||
{
|
||||
if (Segments[i] != other.Segments[i])
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == 0)
|
||||
{
|
||||
throw new ArgumentException($"{this} and {other} have no common base.");
|
||||
}
|
||||
var segments = new string[i];
|
||||
Array.Copy(Segments, segments, i);
|
||||
return new ResourcePath(segments, Separator);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var code = Separator.GetHashCode();
|
||||
foreach (var segment in Segments)
|
||||
{
|
||||
code |= segment.GetHashCode();
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is ResourcePath path && Equals(path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks that we are equal with <paramref name="path"/>.
|
||||
/// This method does NOT clean the paths beforehand, so paths that point to the same location may fail if they are not cleaned beforehand.
|
||||
/// Paths are never equal if they do not have the same separator.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to check equality with.</param>
|
||||
/// <returns>True if the paths are equal, false otherwise.</returns>
|
||||
public bool Equals(ResourcePath path)
|
||||
{
|
||||
if (path == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return path.Separator == Separator && Segments.SequenceEqual(path.Segments);
|
||||
}
|
||||
|
||||
public static bool operator ==(ResourcePath a, ResourcePath b)
|
||||
{
|
||||
if ((object)a == null)
|
||||
{
|
||||
return (object)b == null;
|
||||
}
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator !=(ResourcePath a, ResourcePath b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,10 @@
|
||||
<assemblyIdentity name="ICSharpCode.SharpZipLib" publicKeyToken="1b03e6acf1164f73" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-0.86.0.518" newVersion="0.86.0.518" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-11.0.0.0" newVersion="11.0.0.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
||||
@@ -3,7 +3,8 @@
|
||||
<package id="Mono.Cecil" version="0.9.6.4" targetFramework="net451" />
|
||||
<package id="NetSerializer" version="4.1.0" targetFramework="net451" />
|
||||
<package id="Nett" version="0.7.0" targetFramework="net451" />
|
||||
<package id="Newtonsoft.Json" version="11.0.2" targetFramework="net451" />
|
||||
<package id="SharpZipLib" version="0.86.0" targetFramework="net451" />
|
||||
<package id="System.ValueTuple" version="4.4.0" targetFramework="net451" />
|
||||
<package id="YamlDotNet" version="4.3.0" targetFramework="net451" />
|
||||
</packages>
|
||||
</packages>
|
||||
@@ -1,5 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(SolutionDir)packages\Microsoft.NET.Test.Sdk.15.7.0\build\net45\Microsoft.Net.Test.Sdk.props" Condition="Exists('$(SolutionDir)packages\Microsoft.NET.Test.Sdk.15.7.0\build\net45\Microsoft.Net.Test.Sdk.props')" />
|
||||
<Import Project="$(SolutionDir)packages\Microsoft.NET.Test.Sdk.15.7.0\build\net45\Microsoft.Net.Test.Sdk.props" Condition="Exists('$(SolutionDir)packages\Microsoft.NET.Test.Sdk.15.7.0\build\net45\Microsoft.Net.Test.Sdk.props')" />
|
||||
<Import Project="$(SolutionDir)packages\NUnit.3.10.1\build\NUnit.props" Condition="Exists('$(SolutionDir)packages\NUnit.3.10.1\build\NUnit.props')" />
|
||||
<Import Project="$(SolutionDir)packages\NUnit3TestAdapter.3.10.0\build\net35\NUnit3TestAdapter.props" Condition="Exists('$(SolutionDir)packages\NUnit3TestAdapter.3.10.0\build\net35\NUnit3TestAdapter.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
@@ -22,6 +26,8 @@
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<Optimize>false</Optimize>
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|x86' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
@@ -45,17 +51,22 @@
|
||||
<Reference Include="Castle.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL">
|
||||
<HintPath>$(SolutionDir)packages\Castle.Core.4.2.1\lib\net45\Castle.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.VisualStudio.CodeCoverage.Shim, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>$(SolutionDir)packages\Microsoft.CodeCoverage.1.0.3\lib\netstandard1.0\Microsoft.VisualStudio.CodeCoverage.Shim.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Moq, Version=4.8.0.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
|
||||
<HintPath>$(SolutionDir)packages\Moq.4.8.2\lib\net45\Moq.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="nunit.framework">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
<HintPath>$(SolutionDir)packages\NUnit.3.7.1\lib\net45\nunit.framework.dll</HintPath>
|
||||
<Reference Include="Newtonsoft.Json, Version=11.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>$(SolutionDir)packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="nunit.framework, Version=3.10.1.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
|
||||
<HintPath>$(SolutionDir)packages\NUnit.3.10.1\lib\net45\nunit.framework.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Configuration" />
|
||||
<Reference Include="System.Threading.Tasks.Extensions, Version=4.1.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>$(SolutionDir)packages\System.Threading.Tasks.Extensions.4.3.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll</HintPath>
|
||||
<Reference Include="System.Threading.Tasks.Extensions, Version=4.1.1.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
|
||||
<HintPath>$(SolutionDir)packages\System.Threading.Tasks.Extensions.4.4.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.ValueTuple">
|
||||
<HintPath>$(SolutionDir)packages\System.ValueTuple.4.4.0\lib\netstandard1.0\System.ValueTuple.dll</HintPath>
|
||||
@@ -89,6 +100,7 @@
|
||||
<Compile Include="Shared\Maths\Angle_Test.cs" />
|
||||
<Compile Include="Shared\Maths\Matrix3_Test.cs" />
|
||||
<Compile Include="Shared\Maths\Ray_Test.cs" />
|
||||
<Compile Include="Shared\Maths\Vector2u_Test.cs" />
|
||||
<Compile Include="Shared\Maths\Vector2_Test.cs" />
|
||||
<Compile Include="Shared\Maths\Direction_Test.cs" />
|
||||
<Compile Include="Shared\Physics\CollisionManager_Test.cs" />
|
||||
@@ -98,6 +110,7 @@
|
||||
<Compile Include="Shared\Timing\GameLoop_Test.cs" />
|
||||
<Compile Include="Shared\Timing\GameTiming_Test.cs" />
|
||||
<Compile Include="Shared\Utility\CollectionExtensions_Test.cs" />
|
||||
<Compile Include="Shared\Utility\ResourcePath_Test.cs" />
|
||||
<Compile Include="Shared\Utility\YamlHelpers_Test.cs" />
|
||||
<Compile Include="Shared\ColorUtils_Test.cs" />
|
||||
<Compile Include="SS14UnitTest.cs" />
|
||||
@@ -147,4 +160,15 @@
|
||||
<DefineConstants Condition="'$(HEADLESS)'!=''">$(DefineConstants);HEADLESS</DefineConstants>
|
||||
</PropertyGroup>
|
||||
<Target Name="AfterBuild" DependsOnTargets="CopyResourcesFromShared" />
|
||||
</Project>
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('$(SolutionDir)packages\NUnit3TestAdapter.3.10.0\build\net35\NUnit3TestAdapter.props')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)packages\NUnit3TestAdapter.3.10.0\build\net35\NUnit3TestAdapter.props'))" />
|
||||
<Error Condition="!Exists('$(SolutionDir)packages\NUnit.3.10.1\build\NUnit.props')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)packages\NUnit.3.10.1\build\NUnit.props'))" />
|
||||
<Error Condition="!Exists('$(SolutionDir)packages\Microsoft.NET.Test.Sdk.15.7.0\build\net45\Microsoft.Net.Test.Sdk.props')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)packages\Microsoft.NET.Test.Sdk.15.7.0\build\net45\Microsoft.Net.Test.Sdk.props'))" />
|
||||
<Error Condition="!Exists('$(SolutionDir)packages\Microsoft.NET.Test.Sdk.15.7.0\build\net45\Microsoft.Net.Test.Sdk.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)packages\Microsoft.NET.Test.Sdk.15.7.0\build\net45\Microsoft.Net.Test.Sdk.targets'))" />
|
||||
</Target>
|
||||
<Import Project="$(SolutionDir)packages\Microsoft.NET.Test.Sdk.15.7.0\build\net45\Microsoft.Net.Test.Sdk.targets" Condition="Exists('$(SolutionDir)packages\Microsoft.NET.Test.Sdk.15.7.0\build\net45\Microsoft.Net.Test.Sdk.targets')" />
|
||||
<Import Project="$(SolutionDir)packages\Microsoft.NET.Test.Sdk.15.7.0\build\net45\Microsoft.Net.Test.Sdk.targets" Condition="Exists('$(SolutionDir)packages\Microsoft.NET.Test.Sdk.15.7.0\build\net45\Microsoft.Net.Test.Sdk.targets')" />
|
||||
</Project>
|
||||
|
||||
18
SS14.UnitTesting/Shared/Maths/Vector2u_Test.cs
Normal file
18
SS14.UnitTesting/Shared/Maths/Vector2u_Test.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using Newtonsoft.Json;
|
||||
using NUnit.Framework;
|
||||
using SS14.Shared.Maths;
|
||||
|
||||
namespace SS14.UnitTesting.Shared.Maths
|
||||
{
|
||||
[TestFixture]
|
||||
[Parallelizable]
|
||||
[TestOf(typeof(Vector2u))]
|
||||
public class Vector2u_Test
|
||||
{
|
||||
[Test]
|
||||
public void TestJsonDeserialization()
|
||||
{
|
||||
Assert.That(JsonConvert.DeserializeObject<Vector2u>("{\"x\": 10, \"y\": 10}"), Is.EqualTo(new Vector2u(10, 10)));
|
||||
}
|
||||
}
|
||||
}
|
||||
211
SS14.UnitTesting/Shared/Utility/ResourcePath_Test.cs
Normal file
211
SS14.UnitTesting/Shared/Utility/ResourcePath_Test.cs
Normal file
@@ -0,0 +1,211 @@
|
||||
using NUnit.Framework;
|
||||
using SS14.Shared.Utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace SS14.UnitTesting.Shared.Utility
|
||||
{
|
||||
[TestFixture]
|
||||
[Parallelizable(ParallelScope.Fixtures | ParallelScope.All)]
|
||||
[TestOf(typeof(ResourcePath))]
|
||||
public class ResourcePath_Test
|
||||
{
|
||||
public static List<(string, string)> InputClean_Values = new List<(string, string)>
|
||||
{
|
||||
("/Textures", "/Textures"),
|
||||
("Textures", "Textures"),
|
||||
("Textures/", "Textures"),
|
||||
("Textures/Laser.png", "Textures/Laser.png"),
|
||||
("Textures//Laser.png", "Textures/Laser.png"),
|
||||
("Textures/..//Radio.png/", "Textures/../Radio.png"),
|
||||
("", "."),
|
||||
(".", "."),
|
||||
("./foo", "foo"),
|
||||
("foo/.", "foo"),
|
||||
("foo/./bar", "foo/bar"),
|
||||
("./", "."),
|
||||
("/.", "/"),
|
||||
("/", "/"),
|
||||
(" ", " "), // Note the spaces here.
|
||||
(" / ", " / "),
|
||||
(". ", ". ")
|
||||
};
|
||||
|
||||
// Tests whether input and output remains unchanged.
|
||||
[Test]
|
||||
public void InputClean_Test([ValueSource(nameof(InputClean_Values))] (string input, string expected) path)
|
||||
{
|
||||
var respath = new ResourcePath(path.input);
|
||||
Assert.That(respath.ToString(), Is.EqualTo(path.expected));
|
||||
}
|
||||
|
||||
public static List<(string, string)> Extension_Values = new List<(string, string)>
|
||||
{
|
||||
("foo", ""),
|
||||
("foo.png", "png"),
|
||||
("test/foo.png", "png"),
|
||||
(".bashrc", ""),
|
||||
("..png", "png"),
|
||||
("x.y.z", "z")
|
||||
};
|
||||
|
||||
[Test]
|
||||
public void Extension_Test([ValueSource(nameof(Extension_Values))] (string path, string expected) data)
|
||||
{
|
||||
var respath = new ResourcePath(data.path);
|
||||
Assert.That(respath.Extension, Is.EqualTo(data.expected));
|
||||
}
|
||||
|
||||
public static List<(string, string)> Filename_Values = new List<(string, string)>
|
||||
{
|
||||
("foo", "foo"),
|
||||
("foo.png", "foo.png"),
|
||||
("x/y/z", "z"),
|
||||
("/bar", "bar"),
|
||||
("foo/", "foo") // Trailing / gets trimmed.
|
||||
};
|
||||
|
||||
[Test]
|
||||
public void Filename_Test([ValueSource(nameof(Filename_Values))] (string path, string expected) data)
|
||||
{
|
||||
var respath = new ResourcePath(data.path);
|
||||
Assert.That(respath.Filename, Is.EqualTo(data.expected));
|
||||
}
|
||||
|
||||
public static List<(string, string)> FilenameWithoutExtension_Values = new List<(string, string)>
|
||||
{
|
||||
("foo", "foo"),
|
||||
("foo.png", "foo"),
|
||||
("test/foo.png", "foo"),
|
||||
("derp/.bashrc", ".bashrc"),
|
||||
("..png", "."),
|
||||
("x.y.z", "x.y")
|
||||
};
|
||||
|
||||
[Test]
|
||||
public void FilenameWithoutExtension_Test([ValueSource(nameof(FilenameWithoutExtension_Values))] (string path, string expected) data)
|
||||
{
|
||||
var respath = new ResourcePath(data.path);
|
||||
Assert.That(respath.FilenameWithoutExtension, Is.EqualTo(data.expected));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ChangeSeparator_Test()
|
||||
{
|
||||
var respath = new ResourcePath("a/b/c").ChangeSeparator("👏");
|
||||
Assert.That(respath.ToString(), Is.EqualTo("a👏b👏c"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Combine_Test()
|
||||
{
|
||||
var path1 = new ResourcePath("/a/b");
|
||||
var path2 = new ResourcePath("c/d.png");
|
||||
Assert.That((path1 / path2).ToString(), Is.EqualTo("/a/b/c/d.png"));
|
||||
Assert.That((path1 / "z").ToString(), Is.EqualTo("/a/b/z"));
|
||||
}
|
||||
|
||||
public static List<(string, string)> Clean_Values = new List<(string, string)>
|
||||
{
|
||||
("//a/b/../c/./ss14.png", "/a/c/ss14.png"),
|
||||
("../a", "../a"),
|
||||
("../a/..", ".."),
|
||||
("../..", "../.."),
|
||||
("a/..", "."),
|
||||
("/../a", "/a"),
|
||||
("/..", "/"),
|
||||
};
|
||||
|
||||
[Test]
|
||||
public void Clean_Test([ValueSource(nameof(Clean_Values))] (string path, string expected) data)
|
||||
{
|
||||
var path = new ResourcePath(data.path);
|
||||
var cleaned = path.Clean();
|
||||
Assert.Multiple(() =>
|
||||
{
|
||||
if (path == cleaned)
|
||||
{
|
||||
Assert.That(path.IsClean());
|
||||
}
|
||||
Assert.That(path.Clean(), Is.EqualTo(new ResourcePath(data.expected)));
|
||||
Assert.That(cleaned.IsClean());
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RootedConversions_Test()
|
||||
{
|
||||
var path = new ResourcePath("/a/b");
|
||||
Assert.That(path.IsRooted);
|
||||
Assert.That(path.ToRootedPath(), Is.EqualTo(path));
|
||||
|
||||
var relative = path.ToRelativePath();
|
||||
Assert.That(relative, Is.EqualTo(new ResourcePath("a/b")));
|
||||
Assert.That(relative.IsRelative);
|
||||
|
||||
Assert.That(relative.ToRelativePath(), Is.EqualTo(relative));
|
||||
Assert.That(relative.ToRootedPath(), Is.EqualTo(path));
|
||||
}
|
||||
|
||||
public static List<(string, string, string)> RelativeTo_Values = new List<(string, string, string)>
|
||||
{
|
||||
("/a/b", "/a", "b"),
|
||||
("/a", "/", "a"),
|
||||
("/a/b/c", "/", "a/b/c"),
|
||||
("/a", "/a", "."),
|
||||
("a/b", "a", "b"),
|
||||
("/Textures/Weapons/laser.png", "/Textures/", "Weapons/laser.png")
|
||||
};
|
||||
|
||||
[Test]
|
||||
public void RelativeTo_Test([ValueSource(nameof(RelativeTo_Values))] (string source, string basePath, string expected) value)
|
||||
{
|
||||
var path = new ResourcePath(value.source);
|
||||
var basePath = new ResourcePath(value.basePath);
|
||||
Assert.That(path.RelativeTo(basePath), Is.EqualTo(new ResourcePath(value.expected)));
|
||||
}
|
||||
|
||||
public static List<(string, string)> RelativeToFail_Values = new List<(string, string)>
|
||||
{
|
||||
("/a/b", "/b"),
|
||||
("/a", "/c/d"),
|
||||
("/a/b", "/a/d"),
|
||||
(".", "/"),
|
||||
("/", ".")
|
||||
};
|
||||
|
||||
[Test]
|
||||
public void RelativeToFail_Test([ValueSource(nameof(RelativeToFail_Values))] (string source, string basePath) value)
|
||||
{
|
||||
var path = new ResourcePath(value.source);
|
||||
var basePath = new ResourcePath(value.basePath);
|
||||
Assert.That(() => path.RelativeTo(basePath), Throws.ArgumentException);
|
||||
}
|
||||
|
||||
public static List<(string, string, string)> CommonBase_Values = new List<(string, string, string)>
|
||||
{
|
||||
("/a/b", "/a/c", "/a"),
|
||||
("a/b", "a/c", "a"),
|
||||
("/usr", "/bin", "/")
|
||||
};
|
||||
|
||||
[Test]
|
||||
public void CommonBase_Test([ValueSource(nameof(CommonBase_Values))] (string a, string b, string expected) value)
|
||||
{
|
||||
var path = new ResourcePath(value.a);
|
||||
var basePath = new ResourcePath(value.b);
|
||||
Assert.That(path.CommonBase(basePath), Is.EqualTo(new ResourcePath(value.expected)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CommonBaseFail_Test()
|
||||
{
|
||||
var path = new ResourcePath("a/b");
|
||||
var basePath = new ResourcePath("b/a");
|
||||
Assert.That(() => path.CommonBase(basePath), Throws.ArgumentException);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,14 @@
|
||||
<assemblyIdentity name="ICSharpCode.SharpZipLib" publicKeyToken="1b03e6acf1164f73" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-0.86.0.518" newVersion="0.86.0.518" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-11.0.0.0" newVersion="11.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Threading.Tasks.Extensions" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.1.1.0" newVersion="4.1.1.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
||||
@@ -1,11 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Castle.Core" version="4.2.1" targetFramework="net451" />
|
||||
<package id="Microsoft.CodeCoverage" version="1.0.3" targetFramework="net451" />
|
||||
<package id="Microsoft.NET.Test.Sdk" version="15.7.0" targetFramework="net451" />
|
||||
<package id="Moq" version="4.8.2" targetFramework="net451" />
|
||||
<package id="NUnit" version="3.7.1" targetFramework="net451" />
|
||||
<package id="NUnit.ConsoleRunner" version="3.7.0" targetFramework="net451" />
|
||||
<package id="NUnit3TestAdapter" version="3.8.0" targetFramework="net451" />
|
||||
<package id="System.Threading.Tasks.Extensions" version="4.3.0" targetFramework="net451" />
|
||||
<package id="Newtonsoft.Json" version="11.0.2" targetFramework="net451" />
|
||||
<package id="NUnit" version="3.10.1" targetFramework="net451" />
|
||||
<package id="NUnit.ConsoleRunner" version="3.8.0" targetFramework="net451" />
|
||||
<package id="NUnit3TestAdapter" version="3.10.0" targetFramework="net451" />
|
||||
<package id="System.Threading.Tasks.Extensions" version="4.4.0" targetFramework="net451" />
|
||||
<package id="System.ValueTuple" version="4.4.0" targetFramework="net451" />
|
||||
<package id="YamlDotNet" version="4.3.0" targetFramework="net451" />
|
||||
</packages>
|
||||
Reference in New Issue
Block a user