Skip to content

Instantly share code, notes, and snippets.

@slackydev
Created April 1, 2024 17:57
Solving doors in RS by using Anchor and Door distance
program new;
{$DEFINE SRL_USE_REMOTEINPUT}
{$I SRL/osr.simba}
(*
This solution assumes that you have an anchor that is either perpendicular or parallel to the door
(it's either in the angle of facing the closed door, or it's facing the open door)
//
//|
//||
//_||______
//| __ __ |
##| | || | |
##| | || | | _
##| |__||__| | (_)
##| __ __()| Parallel example <--|-->
##| | || | | <-- Door --- Anchor --> _ | _
##| | || | | `\__/ \__/`
##| | || | | `-. .-´
##| |__||__| | '
##|__________|
//
Concept:
Find the door and find some object which we call anchor, in this example I'm using a black ledger.
Now we can use the distance from the ledger to the door to determine if the door is open.
The door state can be defined as
If the distance between Door and Anchor is less than a set value, the state of the door is
> Open if: The anchor is parallel to the closed door (parallel meaning to the direction you enter)
> Closed if: The anchor is perpendicular to the closed door (reverse)
Written example, assuming the anchor's location is parallel to the direction you enter through the door.
An open door could have a distance between 18..19,
while a closed door could have a distance between 21..22.
We can therefor state the cutoff / middle between the two states as approximately 20.
How you choose to attack it is up to you. So long as you can generate varying distance-value
between states to something else (an anchor), you can use this concept.
*)
{$DEFINE DOOR_DEBUG_DISTANCE}
type
EDoorState = (dsUndefined, dsOpen, dsClosed);
var
RSW: TRSWalker;
(*
Input:
Door: The TPA of the door
Anchor: The TPA of the Anchor
Split: This is the cutoffpoint between a closed and an open door
Inverse: If the door is not parallel then this should be True (auto False)
UseMean: Mean may give less accurate distance value, so it may result in (more) false-positives
But it may be less prone to noisy pixels from color-finding.
Return `dsOpen` if the door is open based on given input data
if the method fails it will return `dsUndefined`, otherwise dsFalse; `ds` is short for doorstate
*)
function IsDoorOpen(Door, Anchor: TPointArray; Split: Double; Inverse: Boolean = False; UseMean: Boolean = False): EDoorState;
var
p,q: Vector3;
me,ptAnchor,ptDoor: TPoint;
ATPA: T2DPointArray;
begin
// ensure no failure
if (Length(Anchor) = 0) or (length(Door) = 0) then Exit(dsUndefined);
// find the point in the ledger that's closest to the door and vice versa
// alternatively we could just use the mean and skip this step in many cases.
if not UseMean then begin
anchor.Sort(door.Mean());
ptAnchor := anchor[0];
door.Sort(ptAnchor);
ptDoor := door[0];
end else begin
ptAnchor := anchor.Mean();
ptDoor := door.Mean();
end;
// convert to minimap to work in 2d space, so distance is always the same no matter position,
// camera angle and zoom.
p := MainScreen.PointToMM(ptDoor);
q := MainScreen.PointToMM(ptAnchor);
{$IFDEF DOOR_DEBUG_DISTANCE}
// write the distance output for debugging
// this is to get the distance (number) between the anchor and door (in both states, open and closed)
WriteLn p.Distance(q);
{$ENDIF}
if (p.Distance(q) <= Split) xor inverse then
Exit(dsOpen)
else
Exit(dsClosed);
end;
(*
You can use your own methods to find doors and anchors. This is just as an example.
*)
procedure FindDoorAndAnchor(WorldPos: TPoint; out Door, Anchor: TPointArray);
var
ledger: TPointArray;
ATPA: T2DPointArray;
AnchorSearchBox, DoorSearchBox: TBox;
begin
// rough area of where the anchor and door is (world map coordinates)
AnchorSearchBox := RSW.GetTileMSEx(WorldPos, [2656, 2734]).Expand(10).Bounds();
DoorSearchBox := RSW.GetTileMSEx(WorldPos, [2646, 2754]).Expand(30).Bounds();
DoorSearchBox.LimitTo(MainScreen.Bounds);
AnchorSearchBox.LimitTo(MainScreen.Bounds);
// find the anchor, and the door tpas (regular color-data from ACA)
srl.FindColors(anchor, CTS2(1776416, 1, 0.01, 0.01), AnchorSearchBox);
srl.FindColors(door, CTS2(1993096, 13, 0.08, 1.02), DoorSearchBox);
// basic noise filtering
ATPA := door.Erode(1).Grow(1).Cluster(2);
door := ATPA.Biggest();
// basic noise filtering, small object, so I dont erode noise.
ATPA := anchor.Cluster(2);
anchor := ATPA.Biggest();
end;
(*
Test run:
*)
var
door,anchor: TPointArray;
begin
RSW.Setup([[2656-500, 2734-500, 2656+500, 2734+500]]);
RSClient.Image.SetFontSize(15);
RSClient.Image.SetFontAntialiasing(False);
WriteLn RSW.GetMyPos();
while True do
begin
MM2MS.ZoomLevel := Options.GetZoomLevel();
FindDoorAndAnchor(RSW.GetMyPos(), door, anchor);
RSClient.Image.Clear();
case IsDoorOpen(Door, Anchor, 19.4, False, False) of
dsUndefined: ;
dsOpen: RSClient.Image.DrawText('Open', Door.Mean(), 1);
dsClosed: RSClient.Image.DrawText('Closed', Door.Mean(), 1);
end;
end;
end.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment