diff --git a/Content.IntegrationTests/DMProject/Tests/change_area_appearance.dm b/Content.IntegrationTests/DMProject/Tests/change_area_appearance.dm
index f1e805efb0..2940477c6f 100644
--- a/Content.IntegrationTests/DMProject/Tests/change_area_appearance.dm
+++ b/Content.IntegrationTests/DMProject/Tests/change_area_appearance.dm
@@ -2,7 +2,7 @@
color = rgb(255,0,0)
/datum/unit_test/test_change_area_appearance/RunTest()
- var/area/subtype/S = new()
+ var/area/subtype/S = areas_by_type[/area/subtype]
var/list/block_turfs = block(locate(1,1,1), locate(2,2,2))
for(var/turf/T in block_turfs)
S.contents += T
diff --git a/Content.IntegrationTests/DMProject/Tests/range.dm b/Content.IntegrationTests/DMProject/Tests/range.dm
index b4e92d4086..7718f5b9e9 100644
--- a/Content.IntegrationTests/DMProject/Tests/range.dm
+++ b/Content.IntegrationTests/DMProject/Tests/range.dm
@@ -1,40 +1,179 @@
-//Tests that /proc/range() is iterating along the correct, wonky path
+// Tests the implementation of range() and orange()
+
+#define LOC(x, y) locate(x, y, 3)
+
+/obj/contained/one
+/obj/contained/two
+
+/datum/unit_test/range/proc/run_case(atom/center, list/expected, identifier, isorange, view_size = 1)
+ var/error_index = 0
+ var/list/result = isorange ? orange(center, view_size) : range(center, view_size)
+ try
+ if(result.len != expected.len)
+ error_index = expected.len
+ CRASH("result is [result.len > expected.len ? "longer" : "shorter"] than expected")
+ for(var/index in 1 to result.len)
+ if(result[index] != expected[index])
+ error_index = index
+ CRASH("result does not match expected")
+ catch(var/exception/exc)
+ var/list/error_output = list()
+ error_output += "[identifier]: [exc]"
+ error_output += "expected:"
+ for(var/i in 1 to expected.len)
+ var/atom/A = expected[i]
+ error_output += ("\t([A.x], [A.y]) [A.type] [i == error_index ? "<-- here" : null]")
+ error_output += "got:"
+ for(var/i in 1 to result.len)
+ var/atom/A = result[i]
+ error_output += ("\t([A.x], [A.y]) [A.type] [i == error_index ? "<-- here" : null]")
+
+ CRASH(error_output.Join("\n"))
+
/datum/unit_test/range/RunTest()
world.maxx = world.maxy = 5
- //Test that it goes in the right order
- var/list/correctCoordinates = list(
- list(3,3),
- list(2,2),
- list(2,3),
- list(2,4),
- list(3,2),
- list(3,4),
- list(4,2),
- list(4,3),
- list(4,4)
- )
- var/i = 1
- var/turf/centre = locate(3,3,1)
- for(var/x in range(1,centre))
- var/turf/T = x
- ASSERT(!isnull(T))
- var/list/coords = correctCoordinates[i]
- ASSERT(coords[1] == T.x)
- ASSERT(coords[2] == T.y)
- i += 1
- if(i != 10)
- CRASH("range(1,centre) iterated over [i - 1] tiles, expected 9")
- //Test that arguments are parsed correctly
- var/std = range(1,centre)
- if(std ~! range(centre,1))
- CRASH("range(1,centre) and range(centre,1) do not return the same result.")
- if(std ~! range("3x3",centre))
- CRASH("ViewRange argument parsing for range() isn't working correctly.")
- //Test that getting the range from a mob includes the mob's loc.
- var/list/mob_seen_turfs = list()
- var/mob/test/timmy = new(centre)
- for(var/turf/x in range(1,timmy))
- mob_seen_turfs += list(x)
- if(std ~! mob_seen_turfs)
- CRASH("Using a non-/turf Center for range() did not work correctly.")
- del(timmy)
\ No newline at end of file
+
+ var/turf/center = LOC(3, 3)
+ var/area/outer_area = areas_by_type[/area]
+ var/obj/container = new /obj(center)
+ var/obj/contained = new /obj/contained/one(container)
+ var/obj/contained_trash_1 = new /obj/contained/two(contained)
+ var/obj/contained_trash_2 = new /obj/contained/two(contained)
+ var/obj/contained_trash_3 = new /obj/contained/two(contained)
+
+ var/list/turf_range_case = list(
+ LOC(3, 3),
+ outer_area,
+ container,
+ LOC(2, 2),
+ LOC(2, 3),
+ LOC(2, 4),
+ LOC(3, 2),
+ LOC(3, 4),
+ LOC(4, 2),
+ LOC(4, 3),
+ LOC(4, 4),
+ )
+
+ var/list/turf_orange_case = list(
+ LOC(2, 2),
+ outer_area,
+ LOC(2, 3),
+ LOC(2, 4),
+ LOC(3, 2),
+ LOC(3, 4),
+ LOC(4, 2),
+ LOC(4, 3),
+ LOC(4, 4),
+ )
+
+ var/list/container_range_case = list(
+ contained,
+ LOC(3, 3),
+ outer_area,
+ container,
+ LOC(2, 2),
+ LOC(2, 3),
+ LOC(2, 4),
+ LOC(3, 2),
+ LOC(3, 4),
+ LOC(4, 2),
+ LOC(4, 3),
+ LOC(4, 4),
+ )
+
+ var/list/container_orange_case = list(
+ LOC(3, 3),
+ outer_area,
+ LOC(2, 2),
+ LOC(2, 3),
+ LOC(2, 4),
+ LOC(3, 2),
+ LOC(3, 4),
+ LOC(4, 2),
+ LOC(4, 3),
+ LOC(4, 4),
+ )
+
+ var/list/contained_range_case = list(
+ contained_trash_1,
+ contained_trash_2,
+ contained_trash_3,
+ container,
+ contained,
+ )
+
+ var/list/contained_orange_case = list(
+ container,
+ )
+
+
+ run_case(center, turf_range_case, nameof(turf_range_case), FALSE)
+ run_case(center, turf_orange_case, nameof(turf_orange_case), TRUE)
+ run_case(container, container_range_case, nameof(container_range_case), FALSE)
+ run_case(container, container_orange_case, nameof(container_orange_case), TRUE)
+ run_case(contained, contained_range_case, nameof(contained_range_case), FALSE)
+ run_case(contained, contained_orange_case, nameof(contained_orange_case), TRUE)
+
+ // unusual cases
+
+ var/list/turf_range_2x2 = list(
+ center,
+ outer_area,
+ container,
+ LOC(2, 2),
+ LOC(2, 3),
+ LOC(3, 2),
+ )
+
+ var/list/turf_orange_2x2 = list(
+ LOC(2, 2),
+ outer_area,
+ LOC(2, 3),
+ LOC(3, 2),
+ )
+
+ var/list/turf_range_1x3 = list(
+ center,
+ outer_area,
+ container,
+ LOC(3, 2),
+ LOC(3, 4),
+ )
+
+ var/list/turf_orange_1x3 = list(
+ LOC(3, 2),
+ outer_area,
+ LOC(3, 4)
+ )
+
+ var/list/turf_range_3x1 = list(
+ center,
+ outer_area,
+ container,
+ LOC(2, 3),
+ LOC(4, 3),
+ )
+
+ var/list/turf_orange_3x1 = list(
+ LOC(2, 3),
+ outer_area,
+ LOC(4, 3),
+ )
+
+ run_case(center, turf_range_2x2, nameof(turf_range_2x2), FALSE, "2x2")
+ run_case(center, turf_orange_2x2, nameof(turf_orange_2x2), TRUE, "2x2")
+ run_case(center, turf_range_1x3, nameof(turf_range_1x3), FALSE, "1x3")
+ run_case(center, turf_orange_1x3, nameof(turf_orange_1x3), TRUE, "1x3")
+ run_case(center, turf_range_3x1, nameof(turf_range_3x1), FALSE, "3x1")
+ run_case(center, turf_orange_3x1, nameof(turf_orange_3x1), TRUE, "3x1")
+
+ // FIXME: these pass in BYOND, but the way we iterate over area turfs diverges
+ // var/list/area_range_case = list(outer_area) + outer_area.contents
+ // var/list/area_orange_case = area_range_case.Copy()
+ // run_case(outer_area, area_range_case, nameof(area_range_case), FALSE)
+ // run_case(outer_area, area_orange_case, nameof(area_orange_case), TRUE)
+
+ del(container)
+
+#undef LOC
diff --git a/Content.IntegrationTests/DMProject/code.dm b/Content.IntegrationTests/DMProject/code.dm
index fdf82c2fb0..52cf462802 100644
--- a/Content.IntegrationTests/DMProject/code.dm
+++ b/Content.IntegrationTests/DMProject/code.dm
@@ -3,6 +3,11 @@
/turf/border
/mob/test
+var/global/list/areas_by_type = list()
+/area/New()
+ areas_by_type[type] = src
+
+
//The actual tests
//NOTE: Tests placed in the IntegrationTests suite
// should actually require a normal server in order to work.
@@ -26,6 +31,9 @@
throw EXCEPTION("You must override RunTest()")
/world/New()
+ // prepare areas
+ for(var/area_subtype in typesof(/area) - /area)
+ new area_subtype()
for(var/subtype in typesof(/datum/unit_test))
if(subtype == /datum/unit_test) //skip the base class
continue
diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectClient.cs b/OpenDreamRuntime/Objects/Types/DreamObjectClient.cs
index 209d6c870b..683c4a1417 100644
--- a/OpenDreamRuntime/Objects/Types/DreamObjectClient.cs
+++ b/OpenDreamRuntime/Objects/Types/DreamObjectClient.cs
@@ -60,8 +60,8 @@ protected override bool TryGetVar(string varName, out DreamValue value) {
return true;
case "view":
// Number if square & centerable, string representation otherwise
- if (View is { IsSquare: true, IsCenterable: true }) {
- value = new DreamValue(View.Range);
+ if (View.CanSquareRange) {
+ value = new DreamValue(View.SquareRange.Value);
} else {
value = new DreamValue(View.ToString());
}
diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectWorld.cs b/OpenDreamRuntime/Objects/Types/DreamObjectWorld.cs
index 434781f3a7..dba2a799ce 100644
--- a/OpenDreamRuntime/Objects/Types/DreamObjectWorld.cs
+++ b/OpenDreamRuntime/Objects/Types/DreamObjectWorld.cs
@@ -232,8 +232,8 @@ protected override bool TryGetVar(string varName, out DreamValue value) {
case "view":
// Number if square & centerable, string representation otherwise
- if (DefaultView.IsSquare && DefaultView.IsCenterable) {
- value = new DreamValue(DefaultView.Range);
+ if (DefaultView.CanSquareRange) {
+ value = new DreamValue(DefaultView.SquareRange.Value);
} else {
value = new DreamValue(DefaultView.ToString());
}
diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs
index f599f2b3e4..16f9b3a7d2 100644
--- a/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs
+++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeHelpers.cs
@@ -25,74 +25,77 @@ internal static partial class DreamProcNativeHelpers {
];
///
- /// This is a helper proc for oview, view, orange, and range to do their strange iteration with.
- /// BYOND has a very strange, kinda-spiralling iteration pattern for the above procs,
- /// pretty much like this:
- /// 13 15 17 19 24
- /// 12 03 05 08 23
- /// 11 02 00 07 22
- /// 10 01 04 06 21
- /// 09 14 16 18 20
- ///
- /// This helper attempts to mimic this iteration pattern.
- /// NOTE: THIS DOES NOT ITERATE OVER THE CENTRE. THAT'S THE PROC'S JOB.
+ /// This is a helper method for oview, view, orange, and range to do their strange iteration with.
+ /// BYOND has a very strange, kinda-spiralling iteration pattern for these procs,
+ /// which looks like this in a 3x3 case:
+ ///
+ /// 13 15 17 19 24
+ /// 12 03 05 08 23
+ /// 11 02 00 07 22
+ /// 10 01 04 06 21
+ /// 09 14 16 18 20
+ ///
///
///
- /// This proc tries to handle the rectangular case, but I am not totally confident that it's up to parity.
+ /// Does not iterate over the center.
///
- /// Turfs, in the correct, parity order for the above procs.
- public static IEnumerable MakeViewSpiral(DreamObjectAtom center, ViewRange distance) {
- var mapMgr = IoCManager.Resolve();
- var atomMgr = IoCManager.Resolve();
- var centerPos = atomMgr.GetAtomPosition(center);
-
- int widthRange = (distance.Width - 1) >> 1; // TODO: Make rectangles work.
- int heightRange = (distance.Height - 1) >> 1;
- int donutCount = Math.Max(widthRange, heightRange);
- for(int d = 1; d <= donutCount; d++) { // for each donut
- int sideLength = d + d + 1;
- //The left column
- {
- int leftColumnX = centerPos.X - d;
- int startingLeftColumnY = centerPos.Y - d;
- for (int i = 0; i < sideLength; ++i) {
- if (mapMgr.TryGetTurfAt((leftColumnX, startingLeftColumnY + i), centerPos.Z, out var turf)) {
- yield return turf;
- }
+ /// Tuple representing X/Y coordinates.
+ private static IEnumerable<(int X, int Y)> DantomSpiral((int X, int Y) center, (int Width, int Height) range) {
+ int bottomRange = range.Height / 2;
+ int topRange = (range.Height - 1) / 2;
+ int leftRange = range.Width / 2;
+ int rightRange = (range.Width - 1) / 2;
+
+ int donutCount = Math.Max(bottomRange, leftRange);
+ for(int donut = 1; donut <= donutCount; donut++) {
+ int columnBottom = center.Y - Math.Min(donut, bottomRange);
+ int columnTop = center.Y + Math.Min(donut, topRange);
+
+ // left column
+ if(donut <= leftRange) {
+ int posX = center.X - donut;
+ for(int posY = columnBottom; posY <= columnTop; posY++) {
+ yield return (posX, posY);
}
}
- //The criss-cross-apple-sauce
- {
- int crissCrossLength = sideLength - 2;
- int startingCrossX = centerPos.X - d + 1;
- for(int i = 0; i < crissCrossLength; ++i) {
- //the criss
- if (mapMgr.TryGetTurfAt((startingCrossX+i, centerPos.Y - d), centerPos.Z, out var crissTurf)) {
- yield return crissTurf;
- }
- //the cross
- if (mapMgr.TryGetTurfAt((startingCrossX + i, centerPos.Y + d), centerPos.Z, out var crossTurf)) {
- yield return crossTurf;
- }
+
+ // the criss-cross-apple-sauce
+ if(donut <= bottomRange) {
+ int startingPosX = center.X - donut + 1;
+ int endingPosX = center.X + donut - 1;
+ for(int posX = startingPosX; posX <= endingPosX; posX++) {
+ yield return (posX, center.Y - donut); // the criss
+
+ if(donut > topRange) continue;
+
+ yield return (posX, center.Y + donut); // the cross
}
}
- //The right column
- {
- int rightColumnX = centerPos.X + d;
- int startingRightColumnY = centerPos.Y - d;
- for (int i = 0; i < sideLength; ++i) {
- if (mapMgr.TryGetTurfAt((rightColumnX, startingRightColumnY + i), centerPos.Z, out var turf)) {
- yield return turf;
- }
+
+ // right column
+ if(donut <= rightRange) {
+ int posX = center.X + donut; // this is the only difference
+ for(int posY = columnBottom; posY <= columnTop; posY++) {
+ yield return (posX, posY);
}
}
}
}
- ///
- /// A variation of
- /// that works on the view algorithm's collection of tiles
- ///
+ ///
+ public static IEnumerable MakeViewSpiral(DreamObjectAtom center, ViewRange distance) {
+ var mapMgr = IoCManager.Resolve();
+ var atomMgr = IoCManager.Resolve();
+ var centerPos = atomMgr.GetAtomPosition(center);
+
+ foreach((int posX, int posY) in DantomSpiral((centerPos.X, centerPos.Y), (distance.Width, distance.Height))) {
+ if(mapMgr.TryGetTurfAt((posX, posY), centerPos.Z, out var turf)) {
+ yield return turf;
+ }
+ }
+ }
+
+ ///
public static IEnumerable MakeViewSpiral(ViewAlgorithm.Tile?[,] tiles, bool includeCenter) {
var width = tiles.GetLength(0);
var height = tiles.GetLength(1);
@@ -101,36 +104,8 @@ public static IEnumerable MakeViewSpiral(DreamObjectAtom center
if (includeCenter)
yield return tiles[centerPos.X, centerPos.Y];
- int widthRange = (width - 1) >> 1; // TODO: Make rectangles work.
- int heightRange = (height - 1) >> 1;
- int donutCount = Math.Max(widthRange, heightRange);
- for(int d = 1; d <= donutCount; d++) { // for each donut
- int sideLength = d + d + 1;
-
- //The left column
- int leftColumnX = centerPos.X - d;
- int startingLeftColumnY = centerPos.Y - d;
- for (int i = 0; i < sideLength; ++i) {
- yield return tiles[leftColumnX, startingLeftColumnY + i];
- }
-
- //The criss-cross-apple-sauce
- int crissCrossLength = sideLength - 2;
- int startingCrossX = centerPos.X - d + 1;
- for(int i = 0; i < crissCrossLength; ++i) {
- //the criss
- yield return tiles[startingCrossX + i, centerPos.Y - d];
-
- //the cross
- yield return tiles[startingCrossX + i, centerPos.Y + d];
- }
-
- //The right column
- int rightColumnX = centerPos.X + d;
- int startingRightColumnY = centerPos.Y - d;
- for (int i = 0; i < sideLength; ++i) {
- yield return tiles[rightColumnX, startingRightColumnY + i];
- }
+ foreach((int posX, int posY) in DantomSpiral((centerPos.X, centerPos.Y), (width, height))) {
+ yield return tiles[posX, posY];
}
}
@@ -199,6 +174,88 @@ public static (DreamObjectAtom?, ViewRange) ResolveViewArguments(DreamManager dr
return tiles;
}
+ public static DreamValue HandleRange(NativeProc.Bundle bundle, DreamObject? usr, bool includeCenter) {
+ (DreamObjectAtom? center, ViewRange range) = DreamProcNativeHelpers.ResolveViewArguments(bundle.DreamManager, usr as DreamObjectAtom, bundle.Arguments);
+ if (center is null)
+ return new DreamValue(bundle.ObjectTree.CreateList());
+
+ HashSet seenAreas = [];
+ DreamList rangeList = bundle.ObjectTree.CreateList(range.Height * range.Width);
+
+ void AddToList(DreamValue value) {
+ rangeList.AddValue(value);
+ if(value.TryGetValueAsDreamObject(out var turfValue) && !seenAreas.Contains(turfValue.Cell.Area)) {
+ var area = turfValue.Cell.Area;
+ rangeList.AddValue(new(area));
+ seenAreas.Add(area);
+ }
+ }
+
+ if(center is DreamObjectArea areaCenter) { // yeah you can do this
+ // setting rangeList directly cause we'll never hit the area case
+ rangeList.AddValue(new(center));
+ foreach(var turf in areaCenter.Turfs) {
+ rangeList.AddValue(new(turf));
+ foreach(var content in turf.Contents.EnumerateValues()) {
+ rangeList.AddValue(content);
+ }
+ }
+
+ return new(rangeList);
+ }
+ else if(center is DreamObjectTurf turfCenter) {
+ if(includeCenter) { // if we're orange, we want to skip the else block too
+ AddToList(new(center));
+ foreach(DreamValue content in turfCenter.Contents.EnumerateValues()) {
+ AddToList(content);
+ }
+ }
+ }
+ else { // we're getting the range of a container
+ // add our contents first
+ if(includeCenter) {
+ if(center.TryGetVariable("contents", out var centerContents) && centerContents.TryGetValueAsDreamList(out var centerContentsList)) {
+ foreach(DreamValue content in centerContentsList.EnumerateValues()) {
+ AddToList(content);
+ }
+ }
+
+ centerContents.Dispose();
+ }
+
+ // the loc's contents will include us
+ if (center.TryGetVariable("loc", out DreamValue centerLoc)) {
+ if (centerLoc.TryGetValueAsDreamObject(out var centerLocObject)) {
+ AddToList(centerLoc);
+
+ using var contents = centerLocObject.GetVariable("contents");
+ if (contents.TryGetValueAsDreamList(out var locContentsList)) {
+ foreach (DreamValue content in locContentsList.EnumerateValues()) {
+ if(!includeCenter && content.TryGetValueAsDreamObject(out var dreamObject) && dreamObject == center)
+ continue;
+ AddToList(content);
+ }
+ }
+ }
+
+ centerLoc.Dispose();
+ if(centerLocObject is not DreamObjectTurf) {
+ return new(rangeList);
+ }
+ }
+ }
+
+ // finally, add the surrounding turfs
+ foreach (var turf in DreamProcNativeHelpers.MakeViewSpiral(center, range)) {
+ AddToList(new DreamValue(turf));
+ foreach (DreamValue content in turf.Contents.EnumerateValues()) {
+ AddToList(content);
+ }
+ }
+
+ return new(rangeList);
+ }
+
public static DreamValue HandleViewersHearers(NativeProc.Bundle bundle, DreamObject? usr, bool ignoreLight) {
DreamValue? depthValue = null;
DreamObjectAtom? center = null;
@@ -228,7 +285,7 @@ public static DreamValue HandleViewersHearers(NativeProc.Bundle bundle, DreamObj
var centerPos = bundle.AtomManager.GetAtomPosition(center);
if (depthValue is null || !depthValue.Value.TryGetValueAsInteger(out var depth))
- depth = bundle.DreamManager.WorldInstance.DefaultView.Range;
+ depth = bundle.DreamManager.WorldInstance.DefaultView.BiggestAxis;
foreach (var mob in bundle.MapManager.GetMobsInRange(centerPos, depth)) {
var (_, range) = ResolveViewArguments(bundle.DreamManager, mob, bundle.Arguments);
@@ -283,7 +340,7 @@ public static DreamValue HandleOviewersOhearers(NativeProc.Bundle bundle, DreamO
var centerPos = bundle.AtomManager.GetAtomPosition(center);
if (depthValue is null || !depthValue.Value.TryGetValueAsInteger(out var depth))
- depth = bundle.DreamManager.WorldInstance.DefaultView.Range;
+ depth = bundle.DreamManager.WorldInstance.DefaultView.BiggestAxis;
foreach (var atom in bundle.AtomManager.EnumerateAtoms(bundle.ObjectTree.Mob)) {
var mob = (DreamObjectMob)atom;
diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs
index 5cbcf2c020..1eacef5227 100644
--- a/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs
+++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs
@@ -1813,18 +1813,7 @@ public static DreamValue NativeProc_ohearers(NativeProc.Bundle bundle, DreamObje
[DreamProcParameter("Dist", Type = DreamValueTypeFlag.Float, DefaultValue = 5)]
[DreamProcParameter("Center", Type = DreamValueTypeFlag.DreamObject)]
public static DreamValue NativeProc_orange(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) {
- (DreamObjectAtom? center, ViewRange range) = DreamProcNativeHelpers.ResolveViewArguments(bundle.DreamManager, usr as DreamObjectAtom, bundle.Arguments);
- if (center is null)
- return new DreamValue(bundle.ObjectTree.CreateList());
- DreamList rangeList = bundle.ObjectTree.CreateList(range.Height * range.Width);
- foreach (var turf in DreamProcNativeHelpers.MakeViewSpiral(center, range)) {
- rangeList.AddValue(new DreamValue(turf));
- foreach (DreamValue content in turf.Contents.EnumerateValues()) {
- rangeList.AddValue(content);
- }
- }
-
- return new DreamValue(rangeList);
+ return DreamProcNativeHelpers.HandleRange(bundle, usr, false);
}
[DreamProc("oview")]
@@ -1970,48 +1959,7 @@ public static DreamValue NativeProc_rand_seed(NativeProc.Bundle bundle, DreamObj
[DreamProcParameter("Dist", Type = DreamValueTypeFlag.Float, DefaultValue = 5)]
[DreamProcParameter("Center", Type = DreamValueTypeFlag.DreamObject)]
public static DreamValue NativeProc_range(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) {
- (DreamObjectAtom? center, ViewRange range) = DreamProcNativeHelpers.ResolveViewArguments(bundle.DreamManager, usr as DreamObjectAtom, bundle.Arguments);
- if (center is null)
- return new DreamValue(bundle.ObjectTree.CreateList());
-
- DreamList rangeList = bundle.ObjectTree.CreateList(range.Height * range.Width);
-
- //Have to include centre
- rangeList.AddValue(new DreamValue(center));
-
- if(center.TryGetVariable("contents", out var centerContents) && centerContents.TryGetValueAsDreamList(out var centerContentsList)) {
- foreach(DreamValue content in centerContentsList.EnumerateValues()) {
- rangeList.AddValue(content);
- }
- }
-
- centerContents.Dispose();
-
- // If it's not a /turf, we have to include its loc and the loc's contents
- if (center is not DreamObjectTurf && center.TryGetVariable("loc",out DreamValue centerLoc)) {
- if (centerLoc.TryGetValueAsDreamObject(out var centerLocObject)) {
- rangeList.AddValue(centerLoc);
-
- using var contents = centerLocObject.GetVariable("contents");
- if (contents.TryGetValueAsDreamList(out var locContentsList)) {
- foreach (DreamValue content in locContentsList.EnumerateValues()) {
- rangeList.AddValue(content);
- }
- }
- }
-
- centerLoc.Dispose();
- }
-
- //And then everything else
- foreach (var turf in DreamProcNativeHelpers.MakeViewSpiral(center, range)) {
- rangeList.AddValue(new DreamValue(turf));
- foreach (DreamValue content in turf.Contents.EnumerateValues()) {
- rangeList.AddValue(content);
- }
- }
-
- return new DreamValue(rangeList);
+ return DreamProcNativeHelpers.HandleRange(bundle, usr, true);
}
[DreamProc("ref")]
diff --git a/OpenDreamShared/Dream/ViewRange.cs b/OpenDreamShared/Dream/ViewRange.cs
index cc216c210e..5bfe7ad040 100644
--- a/OpenDreamShared/Dream/ViewRange.cs
+++ b/OpenDreamShared/Dream/ViewRange.cs
@@ -1,4 +1,5 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using Robust.Shared.Maths;
namespace OpenDreamShared.Dream;
@@ -9,20 +10,23 @@ namespace OpenDreamShared.Dream;
///
public readonly struct ViewRange {
public readonly int Width, Height;
- public bool IsSquare => (Width == Height);
+ public int BiggestAxis => Math.Max(Width, Height);
public int CenterX => Width / 2;
public int CenterY => Height / 2;
public Vector2i Center => (CenterX, CenterY);
- //View can be centered in both directions?
+ public bool IsSquare => Width == Height;
public bool IsCenterable => (Width % 2 == 1) && (Height % 2 == 1);
+ [MemberNotNullWhen(true, nameof(SquareRange))]
+ public bool CanSquareRange => IsSquare && IsCenterable;
+
///
- /// The distance this ViewRange covers in every direction if and
- /// are true
+ /// The distance this ViewRange covers in every direction
+ /// if is true
///
- public int Range => (IsSquare && IsCenterable) ? (Width - 1) / 2 : 0;
+ public int? SquareRange => CanSquareRange ? (Width - 1) / 2 : null;
public ViewRange(int range) {
// A square covering "range" cells in each direction