get path-finding.sml to use function in build-graph.sml to find reachable platforms, and remove now-dead code from path-finding.sml
This commit is contained in:
@@ -1,64 +1,60 @@
|
|||||||
structure BuildGraph =
|
structure BuildGraph =
|
||||||
struct
|
struct
|
||||||
fun insertIfNotExistsOrShorter
|
fun insertIfNotExistsOrShorter (dist, eKeys, eVals, foldPlatID, q, fromPlatID) =
|
||||||
(dist, eKeys, eVals, foldPlatID, q, fromPlatID) =
|
let
|
||||||
let
|
val pos = IntSet.findInsPos (foldPlatID, eKeys)
|
||||||
val pos = IntSet.findInsPos (foldPlatID, eKeys)
|
in
|
||||||
in
|
if pos <> ~1 andalso pos <> Vector.length eKeys then
|
||||||
if pos <> ~1 andalso pos <> Vector.length eKeys then
|
let
|
||||||
let
|
val key = IntSet.sub (eKeys, pos)
|
||||||
val key = IntSet.sub (eKeys, pos)
|
in
|
||||||
in
|
if pos = key then
|
||||||
if pos = key then
|
(* may need to update record in eVals if it is shorter *)
|
||||||
(* may need to update record in eVals if it is shorter *)
|
let
|
||||||
let
|
val {distance = oldDist, ...} = ValSet.sub (eVals, pos)
|
||||||
val {distance = oldDist, ...} = ValSet.sub (eVals, pos)
|
in
|
||||||
in
|
if dist < oldDist then
|
||||||
if dist < oldDist then
|
(* update values as we found a shorter path *)
|
||||||
(* update values as we found a shorter path *)
|
let
|
||||||
let
|
val eVals =
|
||||||
val eVals =
|
ValSet.updateAtIdx
|
||||||
ValSet.updateAtIdx
|
(eVals, {distance = dist, from = fromPlatID}, pos)
|
||||||
(eVals, {distance = dist, from = fromPlatID}, pos)
|
in
|
||||||
val _ = print "UPDATE foldPlatID\n"
|
|
||||||
in
|
|
||||||
(eVals, q)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
(* return existing *)
|
|
||||||
(eVals, q)
|
(eVals, q)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
(* key not explored, so add to queue *)
|
(* return existing *)
|
||||||
let
|
|
||||||
val insRecord =
|
|
||||||
{distance = dist, id = foldPlatID, comesFrom = fromPlatID}
|
|
||||||
val insPos = DistVec.findInsPos (insRecord, q)
|
|
||||||
val q = DistVec.insert (q, insRecord, insPos)
|
|
||||||
val _ = print "INSERT NEW PLAT\n"
|
|
||||||
in
|
|
||||||
(eVals, q)
|
(eVals, q)
|
||||||
end
|
end
|
||||||
end
|
else
|
||||||
else
|
(* key not explored, so add to queue *)
|
||||||
(* key not explored, so add to queue *)
|
let
|
||||||
let
|
val insRecord =
|
||||||
val insRecord =
|
{distance = dist, id = foldPlatID, comesFrom = fromPlatID}
|
||||||
{distance = dist, id = foldPlatID, comesFrom = fromPlatID}
|
val insPos = DistVec.findInsPos (insRecord, q)
|
||||||
val insPos = DistVec.findInsPos (insRecord, q)
|
val q = DistVec.insert (q, insRecord, insPos)
|
||||||
val q = DistVec.insert (q, insRecord, insPos)
|
in
|
||||||
val _ = print "UPDATE foldPlatID\n"
|
(eVals, q)
|
||||||
in
|
end
|
||||||
(eVals, q)
|
end
|
||||||
end
|
else
|
||||||
end
|
(* key not explored, so add to queue *)
|
||||||
|
let
|
||||||
|
val insRecord =
|
||||||
|
{distance = dist, id = foldPlatID, comesFrom = fromPlatID}
|
||||||
|
val insPos = DistVec.findInsPos (insRecord, q)
|
||||||
|
val q = DistVec.insert (q, insRecord, insPos)
|
||||||
|
in
|
||||||
|
(eVals, q)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
type env =
|
type env =
|
||||||
{ platforms: GameType.platform vector
|
{ platforms: GameType.platform vector
|
||||||
, currentPlat: GameType.platform
|
, currentPlat: GameType.platform
|
||||||
, eKeys: IntSet.elem vector
|
, eKeys: IntSet.elem vector
|
||||||
, distSoFar: int
|
, distSoFar: int
|
||||||
}
|
}
|
||||||
|
|
||||||
(* adds platforms to queue if they have not been explored
|
(* adds platforms to queue if they have not been explored
|
||||||
* or, if they have been explored and distance is smaller than previous,
|
* or, if they have been explored and distance is smaller than previous,
|
||||||
@@ -69,7 +65,7 @@ struct
|
|||||||
structure Vertical =
|
structure Vertical =
|
||||||
MakeQuadTreeFold
|
MakeQuadTreeFold
|
||||||
(struct
|
(struct
|
||||||
type env = env
|
type env = env
|
||||||
|
|
||||||
type state = ValSet.elem vector * DistVec.elem vector
|
type state = ValSet.elem vector * DistVec.elem vector
|
||||||
|
|
||||||
@@ -77,28 +73,15 @@ struct
|
|||||||
let
|
let
|
||||||
val {platforms, currentPlat, eKeys, distSoFar} = env
|
val {platforms, currentPlat, eKeys, distSoFar} = env
|
||||||
|
|
||||||
val _ = print ("foldPlatID = " ^ Int.toString foldPlatID ^ "\n")
|
|
||||||
|
|
||||||
val {y = foldPlatY, ...} = Platform.find (foldPlatID, platforms)
|
val {y = foldPlatY, ...} = Platform.find (foldPlatID, platforms)
|
||||||
val {y = currentPlatY, id = fromPlatID, ...} = currentPlat
|
val {y = currentPlatY, id = fromPlatID, ...} = currentPlat
|
||||||
val newDist = abs (foldPlatY - currentPlatY) + distSoFar
|
val newDist = abs (foldPlatY - currentPlatY) + distSoFar
|
||||||
in
|
in
|
||||||
insertIfNotExistsOrShorter
|
insertIfNotExistsOrShorter
|
||||||
(newDist, eKeys, eVals, foldPlatID, q, fromPlatID)
|
(newDist, eKeys, eVals, foldPlatID, q, fromPlatID)
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
fun helpPrint (pos, vec) =
|
|
||||||
if pos = Vector.length vec then
|
|
||||||
()
|
|
||||||
else
|
|
||||||
let
|
|
||||||
val {id, distance, comesFrom} = DistVec.sub (vec, pos)
|
|
||||||
val _ = print ("contains (id = " ^ Int.toString id ^")\n")
|
|
||||||
in
|
|
||||||
helpPrint (pos + 1, vec)
|
|
||||||
end
|
|
||||||
|
|
||||||
fun start (currentPlat: GameType.platform, env: env, state, platformTree) =
|
fun start (currentPlat: GameType.platform, env: env, state, platformTree) =
|
||||||
let
|
let
|
||||||
val {x, y, width, ...} = currentPlat
|
val {x, y, width, ...} = currentPlat
|
||||||
@@ -107,14 +90,8 @@ struct
|
|||||||
val searchY = y - Constants.jumpLimit
|
val searchY = y - Constants.jumpLimit
|
||||||
val height = Constants.worldHeight - searchY
|
val height = Constants.worldHeight - searchY
|
||||||
|
|
||||||
val _ = print "start fold\n"
|
val (eVals, q) = Vertical.foldRegion
|
||||||
val (eVals, q) =
|
(x, searchY, width, height, env, state, platformTree)
|
||||||
Vertical.foldRegion (x, searchY, width, height, env, state, platformTree)
|
|
||||||
val _ = print "finish fold\n"
|
|
||||||
|
|
||||||
val _ = print "BuildGraph q contains IDs:\n"
|
|
||||||
val _ = helpPrint (0, q)
|
|
||||||
val _ = print "\n"
|
|
||||||
in
|
in
|
||||||
(eVals, q)
|
(eVals, q)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,235 +1,5 @@
|
|||||||
structure PathFinding =
|
structure PathFinding =
|
||||||
struct
|
struct
|
||||||
(* functor for adding reachable platforms to queue *)
|
|
||||||
structure FindReachable =
|
|
||||||
struct
|
|
||||||
open GameType
|
|
||||||
|
|
||||||
type env =
|
|
||||||
{ platforms: GameType.platform vector
|
|
||||||
, currentPlat: GameType.platform
|
|
||||||
, eKeys: IntSet.elem vector
|
|
||||||
, distSoFar: int
|
|
||||||
}
|
|
||||||
|
|
||||||
type state = ValSet.elem vector * DistVec.elem vector
|
|
||||||
|
|
||||||
fun isBetween (p1, check, p2) = check >= p1 andalso check <= p2
|
|
||||||
|
|
||||||
fun canJumpUpTo (prevPlat: platform, currentPlat: platform) =
|
|
||||||
let
|
|
||||||
val {x = prevX, y = prevY, width = prevWidth, ...} = prevPlat
|
|
||||||
val {x = curX, y = curY, width = curWidth, ...} = currentPlat
|
|
||||||
|
|
||||||
val prevFinishX = prevX + prevWidth
|
|
||||||
val curFinishX = curX + curWidth
|
|
||||||
in
|
|
||||||
(isBetween (prevX, curX, prevFinishX)
|
|
||||||
orelse
|
|
||||||
isBetween (prevX, curFinishX, prevFinishX) andalso prevY + 155 >= curY)
|
|
||||||
end
|
|
||||||
|
|
||||||
fun canDropDownTo (prevPlat: platform, currentPlat: platform) =
|
|
||||||
let
|
|
||||||
val {x = prevX, y = prevY, width = prevWidth, ...} = prevPlat
|
|
||||||
val {x = curX, y = curY, width = curWidth, ...} = currentPlat
|
|
||||||
|
|
||||||
val prevFinishX = prevX + prevWidth
|
|
||||||
val curFinishX = curX + curWidth
|
|
||||||
in
|
|
||||||
(isBetween (prevX, curX, prevFinishX)
|
|
||||||
orelse isBetween (prevX, curFinishX, prevFinishX) andalso prevY <= curY)
|
|
||||||
end
|
|
||||||
|
|
||||||
fun isReachableFromLeft (prevPlat, currentPlat) =
|
|
||||||
(* prev = right/from, current = left/to *)
|
|
||||||
let
|
|
||||||
val {x = prevX, y = prevY, width = prevWidth, ...} = prevPlat
|
|
||||||
val {x = curX, y = curY, width = curWidth, ...} = currentPlat
|
|
||||||
|
|
||||||
val enemyX = prevX
|
|
||||||
val xDiff = prevX - curX
|
|
||||||
in
|
|
||||||
if xDiff <= Constants.jumpLimit then
|
|
||||||
true
|
|
||||||
else
|
|
||||||
let
|
|
||||||
val enemyApexX = enemyX - Constants.jumpLimit
|
|
||||||
val enemyApexY = prevY + Constants.jumpLimit
|
|
||||||
|
|
||||||
val curFinishX = curX + curWidth
|
|
||||||
|
|
||||||
val diffApexY = enemyApexY - curY
|
|
||||||
val diffApexX = enemyApexX - curFinishX
|
|
||||||
in
|
|
||||||
diffApexX <= diffApexY orelse diffApexY <= 0
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
fun isReachableFromRight (prevPlat, currentPlat) =
|
|
||||||
(* prev = left/from, current = right/to *)
|
|
||||||
let
|
|
||||||
val {x = prevX, y = prevY, width = prevWidth, ...} = prevPlat
|
|
||||||
val {x = curX, y = curY, width = curWidth, ...} = currentPlat
|
|
||||||
|
|
||||||
(* last x coordinate where enemy can fully fit on prevPlat *)
|
|
||||||
val enemyX = prevX + prevWidth - Constants.enemySize
|
|
||||||
|
|
||||||
val xDiff = curX - prevX
|
|
||||||
in
|
|
||||||
if xDiff <= Constants.jumpLimit then
|
|
||||||
(* platform is possible to jump to without falling *)
|
|
||||||
true
|
|
||||||
else
|
|
||||||
let
|
|
||||||
val enemyApexX = enemyX + Constants.jumpLimit
|
|
||||||
val enemyApexY = prevY + Constants.jumpLimit
|
|
||||||
|
|
||||||
val diffApexY = enemyApexY - curY
|
|
||||||
val diffApexX = enemyApexX - curX
|
|
||||||
in
|
|
||||||
diffApexY <= 0 orelse diffApexX <= diffApexY
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
fun insertIfNotExistsOrShorter
|
|
||||||
(dist, eKeys, eVals, foldPlatID, q, fromPlatID) =
|
|
||||||
let
|
|
||||||
val pos = IntSet.findInsPos (foldPlatID, eKeys)
|
|
||||||
in
|
|
||||||
if pos <> ~1 andalso pos <> Vector.length eKeys then
|
|
||||||
let
|
|
||||||
val key = IntSet.sub (eKeys, pos)
|
|
||||||
in
|
|
||||||
if pos = key then
|
|
||||||
(* may need to update record in eVals if it is shorter *)
|
|
||||||
let
|
|
||||||
val {distance = oldDist, ...} = ValSet.sub (eVals, pos)
|
|
||||||
in
|
|
||||||
if dist < oldDist then
|
|
||||||
(* update values as we found a shorter path *)
|
|
||||||
let
|
|
||||||
val eVals =
|
|
||||||
ValSet.updateAtIdx
|
|
||||||
(eVals, {distance = dist, from = fromPlatID}, pos)
|
|
||||||
in
|
|
||||||
(eVals, q)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
(* return existing *)
|
|
||||||
(eVals, q)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
(* key not explored, so add to queue *)
|
|
||||||
let
|
|
||||||
val insRecord =
|
|
||||||
{distance = dist, id = foldPlatID, comesFrom = fromPlatID}
|
|
||||||
val insPos = DistVec.findInsPos (insRecord, q)
|
|
||||||
val q = DistVec.insert (q, insRecord, insPos)
|
|
||||||
in
|
|
||||||
(eVals, q)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
(* key not explored, so add to queue *)
|
|
||||||
let
|
|
||||||
val insRecord =
|
|
||||||
{distance = dist, id = foldPlatID, comesFrom = fromPlatID}
|
|
||||||
val insPos = DistVec.findInsPos (insRecord, q)
|
|
||||||
val q = DistVec.insert (q, insRecord, insPos)
|
|
||||||
in
|
|
||||||
(eVals, q)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
fun fState ((eVals, q), env, foldPlatID) =
|
|
||||||
let
|
|
||||||
val {platforms, currentPlat, eKeys, distSoFar} = env
|
|
||||||
val curPlatID = #id currentPlat
|
|
||||||
val foldPlat = Platform.find (foldPlatID, platforms)
|
|
||||||
in
|
|
||||||
if
|
|
||||||
canJumpUpTo (currentPlat, foldPlat)
|
|
||||||
orelse canDropDownTo (currentPlat, foldPlat)
|
|
||||||
then
|
|
||||||
let
|
|
||||||
(* only need to calculate vertical distance *)
|
|
||||||
val {y = py, ...} = currentPlat
|
|
||||||
val {y = cy, ...} = foldPlat
|
|
||||||
val dist = abs (py - cy)
|
|
||||||
|
|
||||||
val dist = dist + distSoFar
|
|
||||||
in
|
|
||||||
insertIfNotExistsOrShorter
|
|
||||||
(dist, eKeys, eVals, foldPlatID, q, curPlatID)
|
|
||||||
end
|
|
||||||
else if
|
|
||||||
isReachableFromLeft (currentPlat, foldPlat)
|
|
||||||
orelse isReachableFromRight (currentPlat, foldPlat)
|
|
||||||
then
|
|
||||||
let
|
|
||||||
val {x = px, y = py, width = pw, ...} = currentPlat
|
|
||||||
val {x = cx, y = cy, width = cw, ...} = foldPlat
|
|
||||||
|
|
||||||
val pFinishX = px + pw
|
|
||||||
val cFinishX = cx + cw
|
|
||||||
|
|
||||||
val dist =
|
|
||||||
if py = cy then
|
|
||||||
let
|
|
||||||
(* if on same y coordinate,
|
|
||||||
* only need to calculate horizontal distance *)
|
|
||||||
val d1 = abs (px - cx)
|
|
||||||
val d2 = abs (px - cFinishX)
|
|
||||||
val d3 = abs (pFinishX - cx)
|
|
||||||
val d4 = abs (pFinishX - cFinishX)
|
|
||||||
|
|
||||||
val min = Int.min (d1, d2)
|
|
||||||
val min = Int.min (min, d3)
|
|
||||||
in
|
|
||||||
(* divide by 2 so we prefer straight horizontal paths over
|
|
||||||
* diagonal ones by giving horizontal ones a lower cost *)
|
|
||||||
Int.min (min, d4) + distSoFar
|
|
||||||
end
|
|
||||||
else
|
|
||||||
let
|
|
||||||
(* if they have different y coordinate,
|
|
||||||
* need to calculate diagonal length/hypotenuse by pythagoras
|
|
||||||
* *)
|
|
||||||
val x1 = abs (px - cx)
|
|
||||||
val x2 = abs (px - cFinishX)
|
|
||||||
val x3 = abs (pFinishX - cx)
|
|
||||||
val x4 = abs (pFinishX - cFinishX)
|
|
||||||
|
|
||||||
val x = Int.min (x1, x2)
|
|
||||||
val x = Int.min (x, x3)
|
|
||||||
val x = Int.min (x, x4)
|
|
||||||
|
|
||||||
(* there is only one y coordinate for platform
|
|
||||||
* so don't need to 'minimise' it
|
|
||||||
* *)
|
|
||||||
val y = abs (py - cy)
|
|
||||||
|
|
||||||
(* pythagoras *)
|
|
||||||
val xsq = x * x
|
|
||||||
val ysq = y * y
|
|
||||||
val hypsq = xsq + ysq
|
|
||||||
|
|
||||||
(* square root to find diagonal length *)
|
|
||||||
val dg = Real.fromInt hypsq
|
|
||||||
val dg = Math.sqrt dg
|
|
||||||
in
|
|
||||||
Real.toInt IEEEReal.TO_NEAREST dg + distSoFar
|
|
||||||
end
|
|
||||||
in
|
|
||||||
insertIfNotExistsOrShorter
|
|
||||||
(dist, eKeys, eVals, foldPlatID, q, curPlatID)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
(eVals, q)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
fun filterMinDuplicates (q, eKeys) =
|
fun filterMinDuplicates (q, eKeys) =
|
||||||
if DistVec.isEmpty q then
|
if DistVec.isEmpty q then
|
||||||
q
|
q
|
||||||
@@ -265,73 +35,51 @@ struct
|
|||||||
fun getPathList (pID, eID, eKeys, eVals) =
|
fun getPathList (pID, eID, eKeys, eVals) =
|
||||||
helpGetPathList (pID, eID, eKeys, eVals, [])
|
helpGetPathList (pID, eID, eKeys, eVals, [])
|
||||||
|
|
||||||
fun addPlatforms (pos, (eVals, q), env) =
|
|
||||||
let
|
|
||||||
val {platforms, ...} = env
|
|
||||||
in
|
|
||||||
if pos = Vector.length platforms then
|
|
||||||
(eVals, q)
|
|
||||||
else
|
|
||||||
let
|
|
||||||
val foldPlat = Vector.sub (platforms, pos)
|
|
||||||
val foldPlatID = #id foldPlat
|
|
||||||
val (eVals, q) = FindReachable.fState ((eVals, q), env, foldPlatID)
|
|
||||||
in
|
|
||||||
addPlatforms (pos + 1, (eVals, q), env)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
fun loop (pID, eID, platforms, platformTree, q, eKeys, eVals) =
|
fun loop (pID, eID, platforms, platformTree, q, eKeys, eVals) =
|
||||||
let
|
if IntSet.contains (pID, eKeys) then
|
||||||
(* filtering duplicates because we have no decrease-key operation *)
|
(* return path if we explored pid *)
|
||||||
val q = filterMinDuplicates (q, eKeys)
|
getPathList (pID, eID, eKeys, eVals)
|
||||||
in
|
else
|
||||||
if IntSet.contains (pID, eKeys) then
|
(* continue dijkstra's algorithm *)
|
||||||
(* return path if we explored pid *)
|
let
|
||||||
getPathList (pID, eID, eKeys, eVals)
|
(* filtering duplicates because we have no decrease-key operation *)
|
||||||
else (* continue dijkstra's algorithm *) if DistVec.isEmpty q then
|
val q = filterMinDuplicates (q, eKeys)
|
||||||
(* return empty list to signify that there is no path *)
|
in
|
||||||
[]
|
if DistVec.isEmpty q then
|
||||||
else
|
(* return empty list to signify that there is no path *)
|
||||||
(* find reachable values from min in quad tree *)
|
[]
|
||||||
let
|
else
|
||||||
val {distance = distSoFar, id = minID, comesFrom} = DistVec.findMin q
|
(* find reachable values from min in quad tree *)
|
||||||
val plat = Platform.find (minID, platforms)
|
let
|
||||||
|
val {distance = distSoFar, id = minID, comesFrom} =
|
||||||
|
DistVec.findMin q
|
||||||
|
val plat = Platform.find (minID, platforms)
|
||||||
|
|
||||||
(* add explored *)
|
(* add explored *)
|
||||||
val insPos = IntSet.findInsPos (minID, eKeys)
|
val insPos = IntSet.findInsPos (minID, eKeys)
|
||||||
val eKeys = IntSet.insert (eKeys, minID, insPos)
|
val eKeys = IntSet.insert (eKeys, minID, insPos)
|
||||||
val eVals =
|
val eVals =
|
||||||
ValSet.insert
|
ValSet.insert
|
||||||
(eVals, {distance = distSoFar, from = comesFrom}, insPos)
|
(eVals, {distance = distSoFar, from = comesFrom}, insPos)
|
||||||
|
|
||||||
(* on each loop, increment distSoFar by 15.
|
(* on each loop, increment distSoFar by 15.
|
||||||
* Result: paths that require jumps to fewer platforms are
|
* Result: paths that require jumps to fewer platforms are
|
||||||
* incentivised a little bit. *)
|
* incentivised a little bit. *)
|
||||||
val env =
|
val env =
|
||||||
{ platforms = platforms
|
{ platforms = platforms
|
||||||
, currentPlat = plat
|
, currentPlat = plat
|
||||||
, eKeys = eKeys
|
, eKeys = eKeys
|
||||||
, distSoFar = distSoFar + 15
|
, distSoFar = distSoFar + 15
|
||||||
}
|
}
|
||||||
|
|
||||||
val state = (eVals, q)
|
(* fold over quad tree, updating any distances
|
||||||
|
* we find the shortest path for *)
|
||||||
(* calculate area to fold over quad tree *)
|
val (eVals, q) =
|
||||||
val ww = Constants.worldWidth
|
BuildGraph.start (plat, env, (eVals, q), platformTree)
|
||||||
val wh = Constants.worldHeight
|
in
|
||||||
|
loop (pID, eID, platforms, platformTree, q, eKeys, eVals)
|
||||||
val {x, y, width, ...} = plat
|
end
|
||||||
val y = y - Constants.jumpLimit
|
end
|
||||||
val height = wh - y
|
|
||||||
|
|
||||||
(* fold over quad tree, updating any distances
|
|
||||||
* we find the shortest path for *)
|
|
||||||
val (eVals, q) = addPlatforms (0, (eVals, q), env)
|
|
||||||
in
|
|
||||||
loop (pID, eID, platforms, platformTree, q, eKeys, eVals)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
fun start (pID, eID, platforms, platformTree) =
|
fun start (pID, eID, platforms, platformTree) =
|
||||||
let
|
let
|
||||||
|
|||||||
Reference in New Issue
Block a user