mirror of
https://github.com/MaSzyna-EU07/StarterNG.git
synced 2026-11-14 04:05:50 +01:00
Compare commits
1 Commits
fix/linux-
...
feat/terra
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dee54a34bb |
@@ -273,7 +273,7 @@ public class ScenerySyntaxTests
|
||||
include koniec.scm
|
||||
""";
|
||||
|
||||
var scenery = TestScenery.Parser().Parse("scenery/x.scn", scn, _ => true);
|
||||
var scenery = TestScenery.Parser().Parse("scenery/x.scn", scn, new SceneryFileLookup(_ => true));
|
||||
|
||||
Assert.Empty(scenery.Faults);
|
||||
Assert.Equal("td/rozklad", Assert.Single(scenery.Trainsets).Name);
|
||||
@@ -326,7 +326,7 @@ public class ScenerySyntaxTests
|
||||
endoptional
|
||||
""";
|
||||
|
||||
var faults = TestScenery.Parser().Parse("scenery/x.scn", scn, name => name is "tory.scm" or "budynek.inc").Faults;
|
||||
var faults = TestScenery.Parser().Parse("scenery/x.scn", scn, new SceneryFileLookup(name => name is "tory.scm" or "budynek.inc")).Faults;
|
||||
|
||||
Assert.Equal((SceneryFaultKind.MissingInclude, "x.scn", 2, null, "brak.scm"), Seen(Assert.Single(faults)));
|
||||
}
|
||||
@@ -378,4 +378,58 @@ public class ScenerySyntaxTests
|
||||
Assert.Equal(sbt, scenery.HasCompanionTimetable);
|
||||
Assert.Equal(sbt ? 0 : 1, scenery.Faults.Count);
|
||||
}
|
||||
|
||||
private static IReadOnlyList<SceneryFault> TerrainFaults(string scn, bool sbt, params string[] onDisk)
|
||||
{
|
||||
var files = new InMemoryFileSystem(caseSensitive: true).WithLegacyFile("game/scenery/Mapa.scn", scn);
|
||||
if (sbt)
|
||||
files.WithFile("game/scenery/mapa.sbt", "EU07");
|
||||
foreach (string file in onDisk)
|
||||
files.WithFile("game/scenery/" + file, "");
|
||||
return TestScenery.Repository(files).Load("game/scenery/Mapa.scn")!.Faults;
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Teren/Mapa.txtf")]
|
||||
[InlineData("teren/mapa.btf")]
|
||||
[InlineData("TEREN/MAPA.BTF")]
|
||||
public void A_terrain_file_found_as_text_or_binary_is_no_fault(string onDisk) =>
|
||||
Assert.Empty(TerrainFaults("terrain teren/mapa.txtf endterrain\nFirstInit\n", sbt: false, onDisk));
|
||||
|
||||
[Fact]
|
||||
public void A_terrain_file_missing_both_ways_is_reported_but_an_sbt_named_with_it_is_not()
|
||||
{
|
||||
var faults = TerrainFaults("// teren\nterrain teren/mapa.btf teren/stara.sbt\nendterrain\nFirstInit\n", sbt: false);
|
||||
|
||||
Assert.Equal((SceneryFaultKind.MissingTerrainFile, "Mapa.scn", 2, null, "teren/mapa.btf"),
|
||||
Seen(Assert.Single(faults)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_terrain_file_takes_the_place_of_the_sbt_so_terrain_includes_are_read_after_all()
|
||||
{
|
||||
var faults = TerrainFaults("terrain teren/mapa.txtf endterrain\ninclude nmt/brak_ter.scm end\nFirstInit\n",
|
||||
sbt: true, "teren/mapa.txtf");
|
||||
|
||||
Assert.Equal((SceneryFaultKind.MissingInclude, "Mapa.scn", 2, null, "nmt/brak_ter.scm"),
|
||||
Seen(Assert.Single(faults)));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("include nmt/brak_ter.scm end\nterrain teren/mapa.txtf endterrain\nFirstInit\n", 2)]
|
||||
[InlineData("node -1 0 t1 triangles trawa\n0 0 0 0 0 0 0 0 end\nendtri\nterrain teren/mapa.txtf endterrain\nFirstInit\n", 4)]
|
||||
[InlineData("node -1 -1 t1 model 0 0 0 0 teren.t3d none endmodel\nterrain teren/mapa.txtf endterrain\nFirstInit\n", 2)]
|
||||
[InlineData("FirstInit\nterrain teren/mapa.txtf endterrain\n", 2)]
|
||||
public void A_terrain_file_behind_what_the_sbt_already_gave_is_passed_over(string scn, int line)
|
||||
{
|
||||
var faults = TerrainFaults(scn, sbt: true, "teren/mapa.txtf");
|
||||
|
||||
Assert.Equal((SceneryFaultKind.TerrainFileBehindSbt, "Mapa.scn", line, null, "teren/mapa.txtf"),
|
||||
Seen(Assert.Single(faults)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void A_model_not_of_the_terrain_leaves_the_sbt_free_to_give_way() =>
|
||||
Assert.Empty(TerrainFaults("node 500 0 m1 model 0 0 0 0 dom.t3d none endmodel\nterrain teren/mapa.txtf endterrain\nFirstInit\n",
|
||||
sbt: true, "teren/mapa.txtf"));
|
||||
}
|
||||
|
||||
@@ -61,6 +61,19 @@ public enum SceneryFaultKind
|
||||
/// <summary>An include whose file is not there; <see cref="SceneryFault.Token"/> names it.</summary>
|
||||
MissingInclude,
|
||||
|
||||
/// <summary>
|
||||
/// A terrain file named by a terrain directive whose text (.txtf) and binary
|
||||
/// file (.btf) are both missing; <see cref="SceneryFault.Token"/> names it.
|
||||
/// </summary>
|
||||
MissingTerrainFile,
|
||||
|
||||
/// <summary>
|
||||
/// A terrain file the simulator passes over: the scenery's terrain comes from its
|
||||
/// .sbt, which something before the directive was already taken from;
|
||||
/// <see cref="SceneryFault.Token"/> names it.
|
||||
/// </summary>
|
||||
TerrainFileBehindSbt,
|
||||
|
||||
/// <summary>An include with no <c>end</c>, which swallows what follows it.</summary>
|
||||
IncludeNotClosed,
|
||||
|
||||
|
||||
@@ -5,6 +5,14 @@ using System.Linq;
|
||||
|
||||
namespace StarterNG.Domain.Sceneries;
|
||||
|
||||
/// <summary>What the simulator finds of the files a scenery names.</summary>
|
||||
/// <param name="Opens">Whether it opens a file of the scenery folder, named as the scenery names it.</param>
|
||||
/// <param name="HasSbt">
|
||||
/// Whether the scenery has its own .sbt: while that is in use, its terrain includes
|
||||
/// (<c>_ter.scm</c>) and terrain geometry are passed over, the .sbt holding them.
|
||||
/// </param>
|
||||
public sealed record SceneryFileLookup(Func<string, bool> Opens, bool HasSbt = false);
|
||||
|
||||
/// <summary>
|
||||
/// Goes through a scenery's text for the parts the starter reads - trainsets,
|
||||
/// vehicles, includes - finding where each trainset and loose vehicle is, and
|
||||
@@ -29,17 +37,17 @@ public static class ScenerySyntax
|
||||
|
||||
/// <param name="text">The scenery's text, its includes put in place.</param>
|
||||
/// <param name="file">The scenery's file name, for faults in its own text.</param>
|
||||
/// <param name="opensInclude">
|
||||
/// Whether the simulator opens an include, named as the include names it; null
|
||||
/// leaves includes unchecked.
|
||||
/// <param name="files">
|
||||
/// What the simulator finds of the files the scenery names - includes and terrain
|
||||
/// files; null leaves them unchecked.
|
||||
/// </param>
|
||||
public static Result Scan(string text, string file, Func<string, bool>? opensInclude = null)
|
||||
public static Result Scan(string text, string file, SceneryFileLookup? files = null)
|
||||
{
|
||||
string code = SceneryCode.Mask(text, out int unclosed);
|
||||
var tokens = SceneryCode.Tokens(code);
|
||||
var lines = new SceneryLines(text, file);
|
||||
|
||||
var simulator = new Reading(tokens, lines, opensInclude, asSimulator: true);
|
||||
var simulator = new Reading(tokens, lines, files, asSimulator: true);
|
||||
simulator.Run();
|
||||
var author = new Reading(tokens, lines, null, asSimulator: false);
|
||||
author.Run();
|
||||
@@ -110,8 +118,14 @@ public static class ScenerySyntax
|
||||
}
|
||||
|
||||
private sealed class Reading(
|
||||
List<SceneryCode.Token> tokens, SceneryLines lines, Func<string, bool>? opensInclude, bool asSimulator)
|
||||
List<SceneryCode.Token> tokens, SceneryLines lines, SceneryFileLookup? files, bool asSimulator)
|
||||
{
|
||||
// Whether the scenery's .sbt is in use, and whether the simulator passed over
|
||||
// something of the text on its account already - or got past FirstInit - after
|
||||
// which a terrain file can no longer take its place.
|
||||
private bool _sbt = files?.HasSbt ?? false;
|
||||
private bool _sbtTaken;
|
||||
|
||||
public readonly List<(int Start, int End, string Name)> Trainsets = new();
|
||||
public readonly List<(int, int)> Loose = new();
|
||||
public readonly List<SceneryFault> Faults = new();
|
||||
@@ -156,12 +170,20 @@ public static class ScenerySyntax
|
||||
{
|
||||
// Not a vehicle: only its kind is stepped over, what follows is its own.
|
||||
if (i + 4 >= tokens.Count || !tokens[i + 4].Is("dynamic"))
|
||||
{
|
||||
if (_sbt && TerrainGeometry(i))
|
||||
_sbtTaken = true;
|
||||
i += 4;
|
||||
}
|
||||
else
|
||||
i = Vehicle(i);
|
||||
}
|
||||
else if (token.Is("include"))
|
||||
i = Include(i);
|
||||
else if (token.Is("terrain") && asSimulator)
|
||||
i = Terrain(i);
|
||||
else if (token.Is("firstinit"))
|
||||
_sbtTaken |= _sbt;
|
||||
}
|
||||
|
||||
if (_open is { } last)
|
||||
@@ -369,8 +391,13 @@ public static class ScenerySyntax
|
||||
return stop < tokens.Count && tokens[stop].Is("end") ? stop : stop - 1;
|
||||
}
|
||||
|
||||
if (opensInclude is not null && Plain(name) && !opensInclude(name))
|
||||
Fault(SceneryFaultKind.MissingInclude, token.Index, token: name);
|
||||
if (files is not null && Plain(name))
|
||||
{
|
||||
if (_sbt && SimulatorNames.Lower(name).Contains("_ter.scm"))
|
||||
_sbtTaken = true;
|
||||
else if (!files.Opens(name))
|
||||
Fault(SceneryFaultKind.MissingInclude, token.Index, token: name);
|
||||
}
|
||||
|
||||
// The simulator takes its parameters up to the word "end", written just so,
|
||||
// whatever comes before it.
|
||||
@@ -383,6 +410,52 @@ public static class ScenerySyntax
|
||||
return last;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A terrain directive: the files it names up to <c>endterrain</c>. Of a terrain
|
||||
/// file (.txtf, .btf) the simulator takes the text or the binary file of the same
|
||||
/// name, whichever is there; an .sbt it passes over quietly when it cannot use it.
|
||||
/// </summary>
|
||||
private int Terrain(int at)
|
||||
{
|
||||
int end = at + 1;
|
||||
while (end < tokens.Count && !tokens[end].Is("endterrain"))
|
||||
end++;
|
||||
|
||||
for (int named = at + 1; named < end && files is not null; named++)
|
||||
{
|
||||
string name = tokens[named].Text;
|
||||
string lower = SimulatorNames.Lower(name);
|
||||
if (!Plain(name) || !(lower.EndsWith(".txtf") || lower.EndsWith(".btf")))
|
||||
continue;
|
||||
|
||||
string stem = name[..name.LastIndexOf('.')];
|
||||
if (!files.Opens(stem + ".txtf") && !files.Opens(stem + ".btf"))
|
||||
Fault(SceneryFaultKind.MissingTerrainFile, tokens[named].Index, token: name);
|
||||
else if (_sbtTaken)
|
||||
Fault(SceneryFaultKind.TerrainFileBehindSbt, tokens[named].Index, token: name);
|
||||
else
|
||||
// The .sbt gives way: the terrain includes from here on are read after all.
|
||||
_sbt = false;
|
||||
}
|
||||
return Math.Min(end, tokens.Count - 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether the node starting at the given word is terrain geometry, which an .sbt
|
||||
/// in use holds in its place: triangles or lines, or a model shown from any distance.
|
||||
/// </summary>
|
||||
private bool TerrainGeometry(int at)
|
||||
{
|
||||
if (at + 4 >= tokens.Count)
|
||||
return false;
|
||||
var kind = tokens[at + 4];
|
||||
if (kind.Is("model"))
|
||||
return double.TryParse(tokens[at + 2].Text, NumberStyles.Float, CultureInfo.InvariantCulture,
|
||||
out double nearest) && nearest < 0;
|
||||
return kind.Is("triangles") || kind.Is("triangle_strip") || kind.Is("triangle_fan") ||
|
||||
kind.Is("lines") || kind.Is("line_strip") || kind.Is("line_loop");
|
||||
}
|
||||
|
||||
// A vehicle with no trainset around it, written as within one - offset, crew,
|
||||
// coupling - where its track, offset and crew belong.
|
||||
private bool TrainsetStyle(int at) =>
|
||||
|
||||
@@ -20,11 +20,11 @@ public sealed class SceneryParser
|
||||
_random = random;
|
||||
}
|
||||
|
||||
/// <param name="opensInclude">
|
||||
/// Whether the simulator opens an include, as the include names it; null leaves
|
||||
/// includes unchecked.
|
||||
/// <param name="files">
|
||||
/// What the simulator finds of the files the scenery names; null leaves them
|
||||
/// unchecked.
|
||||
/// </param>
|
||||
public Scenery Parse(string path, string content, Func<string, bool>? opensInclude = null)
|
||||
public Scenery Parse(string path, string content, SceneryFileLookup? files = null)
|
||||
{
|
||||
string original = content;
|
||||
|
||||
@@ -38,7 +38,7 @@ public sealed class SceneryParser
|
||||
|
||||
// Read from the text with its optional includes blanked, which keeps every
|
||||
// place where it was, so a fault's line is the line in the file.
|
||||
var syntax = ScenerySyntax.Scan(content, System.IO.Path.GetFileName(path), opensInclude);
|
||||
var syntax = ScenerySyntax.Scan(content, System.IO.Path.GetFileName(path), files);
|
||||
var faults = syntax.Faults.ToList();
|
||||
var inlined = SceneryIncludes.Ranges(content);
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using StarterNG.Application.Abstractions;
|
||||
using StarterNG.Domain;
|
||||
using StarterNG.Domain.Sceneries;
|
||||
using StarterNG.Infrastructure.Adapters;
|
||||
|
||||
@@ -78,10 +77,9 @@ public sealed class SceneryRepository : ISceneryRepository
|
||||
var encodings = new Dictionary<string, Encoding>(StringComparer.Ordinal);
|
||||
|
||||
// With its own .sbt the scenery's terrain comes from it, and the simulator
|
||||
// passes its terrain includes over without opening them.
|
||||
// passes its terrain includes over without opening them - unless a terrain
|
||||
// file takes its place.
|
||||
bool binaryTerrain = Opens(Path.GetFileNameWithoutExtension(path) + ".sbt");
|
||||
bool OpensInclude(string name) =>
|
||||
Opens(name) || (binaryTerrain && SimulatorNames.Lower(name).Contains("_ter.scm"));
|
||||
|
||||
(string File, string Text)? ReadInclude(string name)
|
||||
{
|
||||
@@ -98,7 +96,7 @@ public sealed class SceneryRepository : ISceneryRepository
|
||||
|
||||
string own = LegacyText.Decode(_files.ReadAllBytes(path), out var ownEncoding);
|
||||
string text = SceneryIncludes.Inline(own, ReadInclude);
|
||||
var scenery = _parser.Parse(path, text, OpensInclude);
|
||||
var scenery = _parser.Parse(path, text, new SceneryFileLookup(Opens, binaryTerrain));
|
||||
scenery.HasCompanionTimetable = binaryTerrain;
|
||||
scenery.Sources = sources;
|
||||
scenery.TextEncoding = ownEncoding;
|
||||
|
||||
@@ -742,6 +742,8 @@
|
||||
<String key="SyntaxVehicleOutsideTrainset">Vozidlo {0} stojí mimo soupravu: chybí záhlaví trainset?</String>
|
||||
<String key="SyntaxNotANumber">{0}: „{1}“ místo čísla</String>
|
||||
<String key="SyntaxMissingInclude">Soubor {1} nenalezen (include)</String>
|
||||
<String key="SyntaxMissingTerrainFile">Soubor terénu {1} nenalezen, ani .txtf, ani .btf (terrain)</String>
|
||||
<String key="SyntaxTerrainFileBehindSbt">Soubor terénu {1} ignorován: terén už pochází ze souboru .sbt scenérie</String>
|
||||
<String key="SyntaxIncludeNotClosed">include {1} bez end</String>
|
||||
<String key="SyntaxTrainsetUnreadable">Soupravu {0} se nepodařilo načíst</String>
|
||||
<String key="SyntaxVehicleUnreadable">Vozidlo {0} se nepodařilo načíst</String>
|
||||
|
||||
@@ -772,6 +772,8 @@
|
||||
<String key="SyntaxVehicleOutsideTrainset">Vehicle {0} stands outside a trainset: header missing?</String>
|
||||
<String key="SyntaxNotANumber">{0}: “{1}” instead of a number</String>
|
||||
<String key="SyntaxMissingInclude">File {1} not found (include)</String>
|
||||
<String key="SyntaxMissingTerrainFile">Terrain file {1} not found, neither .txtf nor .btf (terrain)</String>
|
||||
<String key="SyntaxTerrainFileBehindSbt">Terrain file {1} ignored: the terrain already comes from the .sbt of the scenery</String>
|
||||
<String key="SyntaxIncludeNotClosed">include {1} without end</String>
|
||||
<String key="SyntaxTrainsetUnreadable">Trainset {0} could not be read</String>
|
||||
<String key="SyntaxVehicleUnreadable">Vehicle {0} could not be read</String>
|
||||
|
||||
@@ -742,6 +742,8 @@
|
||||
<String key="SyntaxVehicleOutsideTrainset">A(z) {0} jármű szerelvényen kívül áll: hiányzik a trainset fejléc?</String>
|
||||
<String key="SyntaxNotANumber">{0}: „{1}” szám helyett</String>
|
||||
<String key="SyntaxMissingInclude">A(z) {1} fájl nem található (include)</String>
|
||||
<String key="SyntaxMissingTerrainFile">A(z) {1} terepfájl nem található, sem .txtf, sem .btf (terrain)</String>
|
||||
<String key="SyntaxTerrainFileBehindSbt">A(z) {1} terepfájl kimarad: a terep már a díszlet .sbt fájljából jön</String>
|
||||
<String key="SyntaxIncludeNotClosed">include {1} end nélkül</String>
|
||||
<String key="SyntaxTrainsetUnreadable">A(z) {0} szerelvényt nem sikerült beolvasni</String>
|
||||
<String key="SyntaxVehicleUnreadable">A(z) {0} járművet nem sikerült beolvasni</String>
|
||||
|
||||
@@ -769,6 +769,8 @@
|
||||
<String key="SyntaxVehicleOutsideTrainset">Pojazd {0} stoi poza składem: brak nagłówka trainset?</String>
|
||||
<String key="SyntaxNotANumber">{0}: „{1}” zamiast liczby</String>
|
||||
<String key="SyntaxMissingInclude">Brak pliku {1} (include)</String>
|
||||
<String key="SyntaxMissingTerrainFile">Brak pliku terenu {1}, ani .txtf, ani .btf (terrain)</String>
|
||||
<String key="SyntaxTerrainFileBehindSbt">Plik terenu {1} pominięty: teren pochodzi już z pliku .sbt scenerii</String>
|
||||
<String key="SyntaxIncludeNotClosed">include {1} bez end</String>
|
||||
<String key="SyntaxTrainsetUnreadable">Nie udało się odczytać składu {0}</String>
|
||||
<String key="SyntaxVehicleUnreadable">Nie udało się odczytać pojazdu {0}</String>
|
||||
|
||||
@@ -742,6 +742,8 @@
|
||||
<String key="SyntaxVehicleOutsideTrainset">Транспорт {0} стоит вне состава: нет заголовка trainset?</String>
|
||||
<String key="SyntaxNotANumber">{0}: «{1}» вместо числа</String>
|
||||
<String key="SyntaxMissingInclude">Файл {1} не найден (include)</String>
|
||||
<String key="SyntaxMissingTerrainFile">Файл рельефа {1} не найден, ни .txtf, ни .btf (terrain)</String>
|
||||
<String key="SyntaxTerrainFileBehindSbt">Файл рельефа {1} пропущен: рельеф уже берётся из файла .sbt сценария</String>
|
||||
<String key="SyntaxIncludeNotClosed">include {1} без end</String>
|
||||
<String key="SyntaxTrainsetUnreadable">Не удалось прочитать состав {0}</String>
|
||||
<String key="SyntaxVehicleUnreadable">Не удалось прочитать единицу {0}</String>
|
||||
|
||||
Reference in New Issue
Block a user