Jump to content

Recommended Posts

I know there are ways to measure the distance between the player and objects, including tiles on the ground.  I’m trying to use my distance to the shore to control the player’s speed.  Does anyone know how to do this?  I’m almost certain I’ve seen code to measure distances, but didn’t understand how it worked.

Link to comment
https://forums.kleientertainment.com/forums/topic/125557-measuring-distances/
Share on other sites

This goes into vector math, but the simple form is a 2D case with pythagoras' theorem since the game uses Euclidean geometry.

The easy thing to do is to get the origins of the two, subtract one from the other, and use that for the theorem.

local x1,y1,z1 = ent1.Transform:GetWorldPosition()
local x2,y2,z2 = ent2.Transform:GetWorldPosition()
local dx,dy,dz = x2-x1,y2-y1,z2-z1
local dist2d = math.sqrt(dx*dx + dz*dz)
local dist3d = math.sqrt(dx*dx + dy*dy + dz*dz)

And for tiles you can get the tile's "origin" from any coordinate via:

local tx,ty,tz = TheWorld.Map:GetTileCenterPoint(x, 0, z)

 

You could use Klei's vector wrappers and make the code shorter:

local dist3d = (ent1.Transform:GetPosition() - ent2.Transform:GetPosition()):Length()


local tilevec = ToVector3(TheWorld.Map:GetTileCenterPoint(x, 0, z))

 

  • Like 1
  • Sanity 1

@CarlZalph

I think I understood most of that, but I don’t know how to use that code to check the player’s distance to the shoreline.  (This is supposed to lower the player’s sleep the further they are from shore, since they can swim.

Ah, I assumed you'd already got that bit calculated with the 'my distance to the shore' as in 'my <function that calculates> distance to the shore'.  My mistake.

 

Many ways to go about doing that.

The most straight forward way I can think of is to create a periodic timer on the player instance which polls nearby tiles and checks if it's an ocean.

You can check if a point is ocean via:

TheWorld.Map:IsOceanAtPoint(x, 0, z)

Since turfs tiles are fixed 4.0 units away from one another you can simplify the grid aspect by adding 4*tileoffset to the player's position.

To get the tileoffsets required to find the shortest distance, either create some sort of mathematical spiral for doing so or pick out the offsets manually and iterate over the ipairs table, bailing on a successful test.

After getting the tile location you can then get the tile's center point from:

TheWorld.Map:GetTileCenterPoint(x, 0, z)

And then pipe that into a function to give you the nearest point to the square if that tile found isn't the player's tile to give you the nearest distance.

@CarlZalph

awesome!  Thank you!  It hat may be enough for me to begin testing, but there’s on weird factor here.  The character is literally swimming, so their distance to the nearest ocean tile is, well, small, haha.  What I need to know is the distance to the nearest land tile, or maybe it would be simpler to test the nearest non-sea tile?  My original plan was to make an invisible wall near the shoreline, so the player could only swim in ‘shallow water’, but it might make more sense if they simply swim slower the further they get from shore, discouraging ocean swimming, but not making it impossible.  If they swim Too far from shore, maybe I remove their ‘drown resistance’.

Ah, well for the land tile the check could use:

TheWorld.Map:IsAboveGroundAtPoint(x, 0, z)

These functions from the Map are mainly wrapper functions to convert a coordinate into a tile coordinate and checking the map's tile ID.

 

Since your character is based solely on water depth and not a distance from the shore itself, might I suggest getting the ocean tile depth directly?

To get a tile ID from a point:

local map = TheWorld.Map
local tcx, _, tcz = map:GetTileCenterPoint(x, 0, z)
local tx, tz = map:GetTileCoordsAtPoint(tcx, 0, tcz)
local tileid = map:GetTile(tx, tz)

You can get some tile meta information through another Klei wrapper:

local tileinfo = GetTileInfo(tileid)

The tileinfo table is defined in worldtiledefs.lua, but the important parts for your use case is to test:

tileinfo.is_shoreline == true
tileinfo.ocean_depth == "SHALLOW"

The first one will be true only for the tile adjacent to land, and the second is true for the lighter blue tint water.

Other values for ocean_depth are: "NORMAL", "DEEP", and "VERY_DEEP".

Edited by CarlZalph

@CarlZalph
I'm testing the code now.  Correct me if I'm wrong, but I need to define the player's position first, right?  Those coordinates are used to test the tiles near me?  I used this code:

--Checking water depth.  --Test code
print("Testing Water Depth/Distance")
local map = TheWorld.Map
print("Map Test")
local tcx, _, tcz = map:GetTileCenterPoint(x, 0, z)
print("txc Test")
local tx, tz = map:GetTileCoordsAtPoint(tcx, 0, tcz)
print("tx tz Test")
local tileid = map:GetTile(tx, tz)
print("tileid Test")

local tileinfo = GetTileInfo(tileid)
print("tileinfo Test")

print("Testing Tile info")
if tileinfo.is_shoreline == true then
	print("Tile is shoreline")
	if tileinfo.ocean_depth == "SHALLOW" then
		print("SHALLOW WATER")
	end
end

The error I received said 'X' was not defined, which it isn't.  From what I can tell, this code Can test a tile's properties, but I'm still figuring out how to tell it to test the tiles near the player.  I put it in the character's file, and it runs about once every two seconds.

@CarlZalph
Great!  I got the code working, but it's a little odd sometimes.
I typed:

--Checking water depth.  --Test code
print("Testing Water Depth/Distance")
local x, y, z = inst.Transform:GetWorldPosition()
print("Player's X, Y, and Y position Test")
local map = TheWorld.Map
print("Map Test")
local tcx, _, tcz = map:GetTileCenterPoint(x, 0, z)
print("txc Test")
local tx, tz = map:GetTileCoordsAtPoint(tcx, 0, tcz)
print("tx tz Test")
local tileid = map:GetTile(tx, tz)
print("tileid Test")

local tileinfo = GetTileInfo(tileid)
print("tileinfo Test")

print("Testing Tile info")
--if tileinfo.is_shoreline == false then
--	print("Tile is shoreline")
	if tileinfo.ocean_depth == "SHALLOW" then
		print("SHALLOW WATER")
	elseif tileinfo.ocean_depth == "NORMAL" then
		print("NORMAL WATER")
	elseif tileinfo.ocean_depth == "DEEP" then
		print("DEEP WATER")
	elseif tileinfo.ocean_depth == "VERY_DEEP" then
		print("VERY_DEEP WATER")
	else
		print("Water's Depth Could Not Be Tested.")
	end
--end
if tileinfo.is_shoreline == true then
	print("Tile is shoreline")
elseif tileinfo.is_shoreline == false then
	print("Tile is Not shoreline")
end

And the result were often similar to:

[00:01:07]: Testing Water Depth/Distance	
[00:01:07]: Player's X, Y, and Y position Test	
[00:01:07]: Map Test	
[00:01:07]: txc Test	
[00:01:07]: tx tz Test	
[00:01:07]: tileid Test	
[00:01:07]: tileinfo Test	
[00:01:07]: Testing Tile info	
[00:01:07]: SHALLOW WATER	
[00:01:07]: Tile is shoreline

This is great, but every so often it it says:

[00:01:04]: Testing Water Depth/Distance	
[00:01:04]: Player's X, Y, and Y position Test	
[00:01:04]: Map Test	
[00:01:04]: txc Test	
[00:01:04]: tx tz Test	
[00:01:04]: tileid Test	
[00:01:04]: tileinfo Test	
[00:01:04]: Testing Tile info	
[00:01:04]: Water's Depth Could Not Be Tested.	

"Tile is Not shoreline" is never used.  It can detect the 'shoreline', which I assume are ocean tiles, but non-ocean tiles aren't registered as non-shoreline?  Perhaps the value is 'nil' I suppose.

This may be all I need, as I can make the player's speed drop depending on the depth of the water, and possibly remove their drowning resistance in very deep water.  I may even be able to use this to limit the player's ability to jump into the water, so they won't jump in when on a boat.  That would be a shame.
But I can sort of see how this can be used to compare distances.  If I wanted to find the nearest land tile (within 30ft for example), would I need to use the player's position and then repeat the testing of tiles all around me until one registered as land?  Or was there a way to automatically tell the game to test all the tiles around the player?  As I understand it, my code is currently testing the tile I'm standing on (which may actually be all I need, considering it's automatically has a depth rating!  Wait, is the depth rating already defining the tiles distance from land?  This may be really easy, wow!)

Edit:  I looked up the difference between the different depths, and they almost seem like biomes.  Most of the ocean is shallow water, so I could restrict the player to shallow water, but it won’t be very noticeable.  Slowing down as the player gets further from shore would still help convey the sense that swimming is useful, but not heavily encouraged.

Edit 2:  Ok, after further testing I figured out how to test the player's position and the position of the tiles near them.  I can probably figure out a way to manually tell the game to check all the tiles around me, but it won't exactly be fun to code.
What took me a little time to figure out was the shoreline statistic.  Ocean tiles Very close to the shore are shoreline tiles, but nothing else is.  I swam in a tiny river and some of the tiles were ocean, but not shoreline, which was confusing me.
I'll probably use the 'tcx, __, tcz test' to get a basic coordinate to test from, then test 3-4 tiles in every direction, covering 9-16 tiles...  Lots of testing.  If none register as shoreline, maybe I make the player drown or something, idk.  I could use the closest tile that lacks a depth statistic to control the player's speed by comparing its coordinates to the player's.  There may be multiple close tiles that aren't ocean, so I'd need to test each, compare the distances, and then use the shortest distance..  (sounds like work,,)

Edited by FurryEskimo
18 hours ago, FurryEskimo said:

"Tile is Not shoreline" is never used.  It can detect the 'shoreline', which I assume are ocean tiles, but non-ocean tiles aren't registered as non-shoreline?  Perhaps the value is 'nil' I suppose.

This may be all I need, as I can make the player's speed drop depending on the depth of the water, and possibly remove their drowning resistance in very deep water.  I may even be able to use this to limit the player's ability to jump into the water, so they won't jump in when on a boat.  That would be a shame.
But I can sort of see how this can be used to compare distances.  If I wanted to find the nearest land tile (within 30ft for example), would I need to use the player's position and then repeat the testing of tiles all around me until one registered as land?  Or was there a way to automatically tell the game to test all the tiles around the player?  As I understand it, my code is currently testing the tile I'm standing on (which may actually be all I need, considering it's automatically has a depth rating!  Wait, is the depth rating already defining the tiles distance from land?  This may be really easy, wow!)

Edit:  I looked up the difference between the different depths, and they almost seem like biomes.  Most of the ocean is shallow water, so I could restrict the player to shallow water, but it won’t be very noticeable.  Slowing down as the player gets rather from shore would still help conver the sense that swimming is useful, but not heavily encouraged.

Yeah if the field is available then it's there, otherwise it's nil or false.

So to check you'd:

if field == true
then
  -- Only true
else
  -- false, nil, number, string, etc
end

This is a map rendered out showing the different ocean tile types:

image.png.c4220c121d6d62f0c9fa5dfbc27d1f4a.png

There's the light blue that surrounds the adjacent land tiles, then there's the shallow that is a fairly big area around the land, and then the other depths.

The depth rating is based on the tile type, and the tile type is picked by worldgen to have that 'gradient' going from shore to deep land.

If the shallow water is too loose for your purposes, then you'll need to add another restriction.

For example you say you want to slow them down, well you can have an internal variable keep track how long ago it was that the player was on land or shoreline and scale down the locomotor's speed by that.

@CarlZalph

That could work.  I’d make a variable that counts up the longer the player is in the water.

I was also thinking, I could simply check for shoreline tiles.  If the player is in shallow water they’re fine, but if a shoreline tile isn’t nearby you’re slower, and if you’re in deeper water you have a limited amount of time to turn back or you drown, plus a heavier speed penalty would be applied.  That’s much easier to code (I think)

Edit:  I've been told that a 'raster' may be what I need to effectively check the tiles near the player, but I've yet to find an effective guide on such a technique, but I'll keep looking.  For some reason formulas to systematically check a grid aren't that easy to find.
I may also just be able to use a repeat function.  I used to use those a lot in C++, but I've never coded one in Lua yet.

Edit 2:  Figured it out.  I can use code like this to search the grid around my character.  If any tiles test positive, I'll know.

--[ local variable definition --]
local a = 1
local b = 1

--[ repeat loop execution --]
repeat
	b = 1
	repeat
		print("value of a:", a)
		print("value of b:", b)
		b =  b + 1
	until( b > 15 )
	a = a + 1
until( a > 15 )

Edit 3:  Took some doing, but I think I got it!!  This checks around the player, four tiles in every direction, totally 64 tiles checked, wow!  I haven't noticed a spike in lag of any kind, so that's good!  If I wanted, I could check each tile's distance to the player, and then compare that to the shortest known distance, and then use the shortest distance to shore, if I wanted to apply a speed penalty that changes smoothly in relation to the player's distance from shore.

--Checking water depth.  --Test code
print("Testing Water Depth/Distance")
local x, y, z = inst.Transform:GetWorldPosition()
print("Player's X, Y, and Z Position Test")
print (x, y, z)
local map = TheWorld.Map
print("Map Test")


--[ local variable definition --]
local a = 1
local b = 1

--[ repeat loop execution --]
repeat
	b = 1
	repeat
		print("value of a:", a)
		print("value of b:", b)

		local tcx, _, tcz = map:GetTileCenterPoint(x + 16 - (4 * b), 0, z + 16 - (4 * a))
		print("tcx, __, tcz Test")
		print(map:GetTileCenterPoint(x + 16 - (4 * b), 0, z + 16 - (4 * a)))
		local tx, tz = map:GetTileCoordsAtPoint(tcx, 0, tcz)
		print("tx tz Test")
		print(map:GetTileCoordsAtPoint(tcx, 0, tcz))
		local tileid = map:GetTile(tx, tz)
		print("Tile ID Test")
		print(map:GetTile(tx, tz))

		local tileinfo = GetTileInfo(tileid)
		print("tileinfo Test")
		print(GetTileInfo(tileid))

		print("Testing Tile info")
	--	if tileinfo.is_shoreline == false then
	--		print("Tile is shoreline")
			if tileinfo.ocean_depth == "SHALLOW" then
				print("SHALLOW WATER")
			elseif tileinfo.ocean_depth == "NORMAL" then
				print("NORMAL WATER")
			elseif tileinfo.ocean_depth == "DEEP" then
				print("DEEP WATER")
			elseif tileinfo.ocean_depth == "VERY_DEEP" then
				print("VERY_DEEP WATER")
			else
				print("Water's Depth Could Not Be Tested.")
			end
	--	end
		if tileinfo.is_shoreline == true then
			print("Tile is shoreline")
		elseif tileinfo.is_shoreline == false then  --Note: This never happens!  Non-shoreline tiles probably have a 'ni' value, not 'false'.
			print("Tile is Not shoreline")
		elseif tileinfo.is_shoreline == nil then
			print("Tile is nil")
		end

		b =  b + 1
	until( b > 8 )
	a = a + 1
until( a > 8 )

 

Edited by FurryEskimo

@CarlZalph
Quick question, what's the difference between "local tcx, _, tcz = map:GetTileCenterPoint(" and "local tx, tz = map:GetTileCoordsAtPoint("?
I expected the values to be the same, but they often seem to differ by around a hundred, and I'm not sure why.  I've coded the character so their speed drops significantly once no shorelines are detected.  I tried testing for shorelines or the lack of an ocean depth (implying land) but for some reason I was detecting tiles that had no depth rating, while out in the ocean. idk.  To the point though, the drop in speed is sort of, awkward and sudden, so measuring the distance to shore may help.

local dist2d = math.sqrt( (tcx - x)^2 + (tcz - z)^2 )  --Test code.
print("Distance between player and tile: ", dist2d)

This seems to work, but I wanted your opinion.  I know I've messed something up somewhere, because when I measure to the right I'm getting a distance twice as far as when I measure tiles to the character's left,

14 hours ago, FurryEskimo said:

Quick question, what's the difference between "local tcx, _, tcz = map:GetTileCenterPoint(" and "local tx, tz = map:GetTileCoordsAtPoint("?
I expected the values to be the same, but they often seem to differ by around a hundred, and I'm not sure why.  I've coded the character so their speed drops significantly once no shorelines are detected.  I tried testing for shorelines or the lack of an ocean depth (implying land) but for some reason I was detecting tiles that had no depth rating, while out in the ocean. idk.  To the point though, the drop in speed is sort of, awkward and sudden, so measuring the distance to shore may help.


local dist2d = math.sqrt( (tcx - x)^2 + (tcz - z)^2 )  --Test code.
print("Distance between player and tile: ", dist2d)

This seems to work, but I wanted your opinion.  I know I've messed something up somewhere, because when I measure to the right I'm getting a distance twice as far as when I measure tiles to the character's left,

First function returns a coordinate in in-game 3D worldspace coordinate system, and the second function returns a tile index relative to the map object as a whole.

The second function is used to get the tile coordinates for use in the function:

map:GetTile(tx, tz)

So for your case you can use the first function alone to get the distance, and yes your dist2d is fine.

local dx = 16 - (4 * b)
local dz = 16 - (4 * a)
local dist2d = math.sqrt(dx*dx + dz*dz)
local tcx, _, tcz = map:GetTileCenterPoint(x + dx, 0, z + dz)

This is a simplified one since you're already calculating out the deltas by getting the tile spots nearby.  While not as accurate as getting the distance to the tile's centers it'll be 'smoother' in that the tile edges will be the transition points on the speed.

@CarlZalph
I did some more testing and decided to graph out the points being tested.  Something is definitely wrong.  For some reason it's testing too far to the left and down, and the points being tested north to south are super close together.  I thought each tile was separated by four units, right?  I've reviewed the math a few times now and just can't tell what's wrong.  I thought I'd done it perfectly.
image.png.5161d6d2d7a822268b4a5a096c64e25d.png
(Player is at 389, 75)

--Checking water depth.  --Test code
print("Testing Water Depth/Distance")
local x, y, z = inst.Transform:GetWorldPosition()
print("Player's X, Y, and Z Position Test")
print (x, y, z)
local map = TheWorld.Map
print("Map Test")


--[ local variable definition --]
local a = 1
local b = 1
local byland = 0

--[ repeat loop execution --]
repeat
	b = 1
	repeat
		print("value of a:", a)
		print("value of b:", b)

		local tcx, _, tcz = map:GetTileCenterPoint(x + 12 - (4 * b), 0, z + 12 - (4 * a))
		print("tcx, __, tcz Test")
		print(map:GetTileCenterPoint(x + 12 - (4 * b), 0, z + 12 - (4 * a)))
		local tx, tz = map:GetTileCoordsAtPoint(tcx, 0, tcz)
		print("tx tz Test")

		local dist2d = math.sqrt( (tcx - x)^2 + (tcz - z)^2 )  --Test code.
		print("Distance between player and tile: ", dist2d)

		print(map:GetTileCoordsAtPoint(tcx, 0, tcz))
		local tileid = map:GetTile(tx, tz)
		print("Tile ID Test")
		print(map:GetTile(tx, tz))

		local tileinfo = GetTileInfo(tileid)
		print("tileinfo Test")
		print(GetTileInfo(tileid))

		print("Testing Tile info")
	--	if tileinfo.is_shoreline == false then
	--		print("Tile is shoreline")
			if tileinfo.ocean_depth == "SHALLOW" then
				print("SHALLOW WATER")
			elseif tileinfo.ocean_depth == "NORMAL" then
				print("NORMAL WATER")
			elseif tileinfo.ocean_depth == "DEEP" then
				print("DEEP WATER")
			elseif tileinfo.ocean_depth == "VERY_DEEP" then
				print("VERY_DEEP WATER")
			else
				print("Water's Depth Could Not Be Tested.")
		--		byland = 1
			end
	--	end
		if tileinfo.is_shoreline == true then
			print("Tile is shoreline.")
			byland = 1
		elseif tileinfo.is_shoreline == nil then
			print("Tile is not shoreline.")
		end

		b =  b + 1
	until( b > 7 or byland == 1)
	a = a + 1
until( a > 7 or byland == 1)

if byland == 0 then
	print("Not by land.")
	inst.components.locomotor.walkspeed = inst.components.locomotor.walkspeed / 3
	inst.components.locomotor.runspeed = inst.components.locomotor.runspeed / 3
else
	print("By land.")
end

 

Edited by FurryEskimo
2 hours ago, FurryEskimo said:

@CarlZalph
I did some more testing and decided to graph out the points being tested.  Something is definitely wrong.  For some reason it's testing too far to the left and down, and the points being tested north to south are super close together.  I thought each tile was separated by four units, right?  I've reviewed the math a few times now and just can't tell what's wrong.  I thought I'd done it perfectly.

If I had to guess, then it's something with the loops or other.  I didn't look into too much what you've put together.

I do see the value in having such a function existing, so I created one that uses a lookup table of offsets for fast checks.

local GenerateGridRadiusLookup = function(r)
    local rsq = r*r
    local data = {}
    local datasorted = {}
    for x = -r, r
    do
        local xsq = x*x
        for z = -r, r
        do
            local zsq = z*z
            local dsq = xsq + zsq
            if dsq > 0 and dsq <= rsq
            then
                local d = math.sqrt(dsq)
                local entry = data[dsq]
                if entry == nil
                then
                    entry = {}
                    table.insert(datasorted, {d, entry})
                end
                table.insert(entry, {x, z})
                data[dsq] = entry
            end
        end
    end
    table.sort(datasorted, function(a, b) return a[1] < b[1] end)
    datasorted.maxr = r
    return datasorted
end
local GetMinTileDistanceByLookup = function(ent, lookup, testfn)
    local map = TheWorld.Map
    local ex, _, ez = ent.Transform:GetWorldPosition()
    for _,v in ipairs(lookup)
    do
        local entries = v[2]
        for _,v2 in ipairs(entries)
        do
            local xo = ex + v2[1]*4
            local zo = ez + v2[2]*4
            local tx, tz = map:GetTileCoordsAtPoint(xo, 0, zo)
            local tid = map:GetTile(tx, tz)
            if testfn(tid)
            then
                local tcx, _, tcz = map:GetTileCenterPoint(xo, 0, zo)
                return v[1]*4, tid, tcx, tcz
            end
        end
    end
    return nil
end
local GetMinUnitDistanceByLookup = function(ent, lookup, testfn)
    local map = TheWorld.Map
    local ex, _, ez = ent.Transform:GetWorldPosition()
    for _,v in ipairs(lookup)
    do
        local entries = v[2]
        local valids = {}
        for _,v2 in ipairs(entries)
        do
            local xo = ex + v2[1]*4
            local zo = ez + v2[2]*4
            local tx, tz = map:GetTileCoordsAtPoint(xo, 0, zo)
            local tid = map:GetTile(tx, tz)
            if testfn(tid)
            then
                local tcx, _, tcz = map:GetTileCenterPoint(xo, 0, zo)
                table.insert(valids, {tcx, tcz, tid})
            end
        end
        local mindsq = lookup.maxr * lookup.maxr * 16 + 1
        local mindtid = nil
        local mindx, mindz = nil, nil
        for _,v in ipairs(valids)
        do
            local tcx, tcz = v[1], v[2]
            local ctx = ex < tcx - 2 and tcx - 2 or ex > tcx + 2 and tcx + 2 or ex
            local ctz = ez < tcz - 2 and tcz - 2 or ez > tcz + 2 and tcz + 2 or ez
            local dx = ex - ctx
            local dz = ez - ctz
            local dsq = dx*dx + dz*dz
            if dsq < mindsq
            then
                mindsq = dsq
                mindtid = v[3]
                mindx = ctx
                mindz = ctz
            end
        end
        if mindtid
        then
            return math.sqrt(mindsq), mindtid, mindx, mindz
        end
    end
    return nil
end
local GetTileIDOn = function(ent)
    local map = TheWorld.Map
    local ex, _, ez = ent.Transform:GetWorldPosition()
    local tx, tz = map:GetTileCoordsAtPoint(ex, 0, ez)
    local tid = map:GetTile(tx, tz)
    return tid
end
local GetOceanDepthOf = function(tid)
    local tinfo = GetTileInfo(tid)
    return tinfo.ocean_depth, tinfo.is_shoreline
end


local GridRadiusLookup = GenerateGridRadiusLookup(5)

Where '5' is the biggest tile distance away the lookup will generate for faster lookups.

 

Example test code using these:

local ptid = GetTileIDOn(ThePlayer)
if IsOceanTile(ptid)
then
    local d, tid, nx, nz = GetMinUnitDistanceByLookup(ThePlayer, GridRadiusLookup, IsLandTile)
    if d
    then
        print(string.format("ThePlayer is in the ocean, and the nearest land is %.1f units away and is of type %d.  It is at %.1f %.1f.", d, tid, nx, nz))
    else
        print("ThePlayer is in the ocean, and there are no nearby land tiles found.")
    end
    local depth, isshore = GetOceanDepthOf(ptid)
    print(string.format("    The ocean tile ThePlayer is in has depth of {%s}%s.", tostring(depth), isshore and " and is a shoreline" or ""))
elseif IsLandTile(ptid)
then
    local d, tid, nx, nz = GetMinUnitDistanceByLookup(ThePlayer, GridRadiusLookup, IsOceanTile)
    if d
    then
        print(string.format("ThePlayer is on land, and the nearest ocean is %.1f units away and is of type %d.  It is at %.1f %.1f.", d, tid, nx, nz))
    else
        print("ThePlayer is on land, and there are no nearby ocean tiles found.")
    end
else
    print("ThePlayer is neither on land nor in the ocean.")
    -- Could be another terrain type by mods or something else by Klei in the future.
end

Using the Klei-provided wrapper functions IsLandTile and IsOceanTile defined in ocean_util.lua.

 

I have two functions in here, one for calculating based on tiles so the distance function is more jagged and the other calculates it as a linear unit distance.

GetMinUnitDistanceByLookup = Linear, GetMinTileDistanceByLookup = Jagged

Both return nil if nothing is found, or 4 return results: MinDistance, TileID, MinDistanceX, MinDistanceZ

You could use these coords to do something at the nearest point found, and other stuff depending on its tileID and whatnot.

Edited by CarlZalph

@CarlZalph
Haha, you'll excuse me if I'm just a little boggled.
I can sort-of tell what you've done, and I think I may know how to get the information out of your equation.  I'm not a professional coder, but it looks like you made some sort of array of coordinates, then ran the data through some test, comparing it to the existing information, and then that data can be called, yes?  I did something similar, just a lot more, noob-ish.

I've tried implementing your code a few times now and get an error each time.  The first time it was because "GetTileIDOn" wasn't defined (I tried using just the second half of your post.)  I tried installing all of your function, just to see what would happen, and it does run, but it claims the "ent" in "local ex, _, ez = ent.Transform:GetWorldPosition()" isn't defined.

I'm still trying to figure out what's wrong with my equation, since while noob-sh, I still think it should be able to work, possibly.  The main reason I want to get my version working is because I better understand it, and can better diagnose and fix errors.  I'm going to retest the equation that generates the scan locations and see if I can't figure out what's wrong with it!

Edit:  Ha, I think I figured it out, and it's silly and simple.  The graph I had didn't have an even scaling, so the values appeared stretched out.  I also began testing at a=1 and b=1, so that might have thrown off the results.  idk for sure if this will fix it, but I hope so!  My evening will be a lot less stressful if this issue was just a silly fluke.

Edited by FurryEskimo
44 minutes ago, FurryEskimo said:

@CarlZalph
Haha, you'll excuse me if I'm just a little boggled.
I can sort-of tell what you've done, and I think I may know how to get the information out of your equation.  I'm not a professional coder, but it looks like you made some sort of array of coordinates, then ran the data through some test, comparing it to the existing information, and then that data can be called, yes?  I did something similar, just a lot more, noob-ish.

I've tried implementing your code a few times now and get an error each time.  The first time it was because "GetTileIDOn" wasn't defined (I tried using just the second half of your post.)  I tried installing all of your function, just to see what would happen, and it does run, but it claims the "ent" in "local ex, _, ez = ent.Transform:GetWorldPosition()" isn't defined.

I'm still trying to figure out what's wrong with my equation, since while noob-sh, I still think it should be able to work, possibly.  The main reason I want to get my version working is because I better understand it, and can better diagnose and fix errors.  I'm going to retest the equation that generates the scan locations and see if I can't figure out what's wrong with it!

Aye the second part requires the first part, and the code is just code ran on a client not the server.

To have it on the server, replace ThePlayer with the character prefab's inst on the server-side.

Edited by CarlZalph

@CarlZalph
Thanks, I'll give that a try.  In the meantime I have good news!  My code works!  It's correctly generating the coordinates of all the tiles around the player!  If I tell it to check each tile's distance to the player and remember the shortest distance, this will be a piece of cake!  I do worry though that it may generate lag, and the affect on speed will only feel smooth if the equation is run frequently.

The easiest solution I can think of is to make a basic test run occasionally, and if the player is standing on ground, do nothing.  If the player's in the water, run this equation more often.  With time I may learn how to make this test activate and deactivate as the player jumps into and out of the water, but that's a project for a different day.

I am curious though, your code seems so much more professional, but more complex too; what's the benefit of using your code vs what I wrote?

image.thumb.png.cc6c0b060f6bbe2762284a8d4b7104b4.png

Edit:  This is the formula I'm currently using, and it seems to work.  I can edit this pretty easily.

		--Checking water depth.  --Test code
		print("Testing Water Depth/Distance")
		local x, y, z = inst.Transform:GetWorldPosition()
		print("Player's Position: ", x, y, z)

		local map = TheWorld.Map
		print("Map Test")


		--[ local variable definition --]
		local a = 0
		local b = 0
		local byland = 0
		local shortestdistance = nil

		--[ repeat loop execution --]
		repeat
			b = 0
			repeat
				print("Value of a:", a)
				print("Value of b:", b)

				local tcx, _, tcz = map:GetTileCenterPoint(x + 12 - (4 * b), 0, z + 12 - (4 * a))
				print("TCX: ", tcx)
				print("TCZ: ", tcz)

				local tx, tz = map:GetTileCoordsAtPoint(tcx, 0, tcz)
				print("TX: ", tx)
				print("TZ: ", tz)

				local tileid = map:GetTile(tx, tz)
				print("Tile ID: ", tileid)

				local tileinfo = GetTileInfo(tileid)
				print("Tile Info: ", tileinfo)

				print("Testing Tile Info.")
				print("Water Type: ", tileinfo.ocean_depth)
--[[
				if tileinfo.ocean_depth == "SHALLOW" then
					print("SHALLOW WATER")
				elseif tileinfo.ocean_depth == "NORMAL" then
					print("NORMAL WATER")
				elseif tileinfo.ocean_depth == "DEEP" then
					print("DEEP WATER")
				elseif tileinfo.ocean_depth == "VERY_DEEP" then
					print("VERY_DEEP WATER")
				else
					print("Water's Depth Could Not Be Tested.")
			--		byland = 1
				end
--]]

				local dist2d = math.sqrt( (tcx - x)^2 + (tcz - z)^2 )  --Test code.
				print("Distance between player and tile: ", dist2d)

				if tileinfo.is_shoreline == true then
					print("Tile is shoreline.")
					byland = 1

					if shortestdistance == nil then
						shortestdistance = dist2d
					elseif dist2d < shortestdistance then
						shortestdistance = dist2d
					end
					print("Shortest distance to a shoreline tile as of this test: ", shortestdistance)
				elseif tileinfo.is_shoreline == nil then
					print("Tile is not shoreline.")
				end

				b =  b + 1
			until( b > 6 )  --or byland == 1)
			a = a + 1
		until( a > 6 )  --or byland == 1)

		if byland == 0 then
			print("Not by land.")
			inst.components.locomotor.walkspeed = inst.components.locomotor.walkspeed / 3
			inst.components.locomotor.runspeed = inst.components.locomotor.runspeed / 3
		else
			print("By land.")
		end

 

Edited by FurryEskimo
1 hour ago, FurryEskimo said:

@CarlZalph
Thanks, I'll give that a try.  In the meantime I have good news!  My code works!  It's correctly generating the coordinates of all the tiles around the player!  If I tell it to check each tile's distance to the player and remember the shortest distance, this will be a piece of cake!  I do worry though that it may generate lag, and the affect on speed will only feel smooth if the equation is run frequently.

The easiest solution I can think of is to make a basic test run occasionally, and if the player is standing on ground, do nothing.  If the player's in the water, run this equation more often.  With time I may learn how to make this test activate and deactivate as the player jumps into and out of the water, but that's a project for a different day.

I am curious though, your code seems so much more professional, but more complex too; what's the benefit of using your code vs what I wrote?

 

Mine'll be faster in the general case as it precalculates out a table of table sorted by distances from the center tile.  It does this in a lazy fashion of masking the circle out of a square when it really just needs 1/8th of a circle and reflect the other 8 nodes without having to do calculations, but this is just a one off and not done when the game is simulating.

It'll then check out the tiles nearest to the player outward to calculate the minimum distance up to the precalculated maximum, returning the real closest distance the entity has to move to be 'on the tile' via `GetMinUnitDistanceByLookup` or an approximate distance based on tiles via `GetMinTileDistanceByLookup`.  The return values also tell you the spot that the entity must be on to make this distance 0 and the tile ID for further parsing and use elsewhere.

 

As for professional this isn't so much professional as I didn't use varnames for the lookup table and chose indexes instead for more performance over code readabilty (indexing versus hashmap lookup).  Professionals would say that's bad form as it also makes maintaining the code harder.

@CarlZalph
So I've been testing the code and it works pretty well!  There appears to be one weird issue though.  The scanning itself seems to cause no lag, and the player's speed seems fine as long as they're by shore, but when the player moves just far enough into the water so that their speed drops to a crawl, on that line they appear to flicker, allowed to move, but then snapping back into place.  If I click the player moves the way you'd expect, but trying to control the player yourself causes weird snapping issues.  Do you have any clue what might be happening?  I change the player's speed constantly, but this is different and weird, and I have no clue why it's happening.

Edited by FurryEskimo

That sounds like a lag-compensation prediction error where the server thinks that the player is in one spot and the client thinks they're way far away at another spot and then teleports back to the last well known good spot.

Perhaps a floating point precision loss is happening where the client still thinks it's moving at some rate while on the server it's actually a lot smaller.

I do see you're touching the locomotor's base speed directly instead of using an external speed modifier.  Not sure how these two would interfere with each other, will have to test.

inst.components.locomotor:SetExternalSpeedMultiplier(inst, "someuniquestringhere", value)

This is generally what I see Klei use for speed modifications, perhaps this is the appropriate function that replicates better with clients?

 

I'd also re-check your equation used to calculate out the new speed and note that my functions can return nil.

@CarlZalph
Aha!  So there was a default way to change speed!  I just made up my own method and it seemed to work just fine.  Currently my player's speed is:

Default Speed Option: (55/(0.0012*player's temperature^2+10)+2) + 0.5 * √Number of hound followers
  •When Snowing:  + 0.5
  •One to three hound followers is recommended.

It's honestly kinda silly, but I like it and it works.  The player becomes faster as they become colder, faster as they train hounds, and faster in the snow.  It's subtle and somehow doesn't seem to cause lag, which was a major concern at first.  I've tried a few different methods to change the player's speed in the water, some being Very simple, but it still seems to cause jittering from time to time, but because there's so much math happening, it's hard to look at the server log and figure out when the jittering is happening.

if byland == 1 then
	waterdebuff = 0.6 * (shortestdistance^0.5)
--	if waterdebuff < (inst.components.locomotor.walkspeed - 1) then
		print("By land.")
--		inst.components.locomotor.walkspeed = (inst.components.locomotor.walkspeed * 0.75) - waterdebuff
--		inst.components.locomotor.runspeed = (inst.components.locomotor.runspeed * 0.75) - waterdebuff

		inst.components.locomotor.walkspeed = inst.components.locomotor.walkspeed / 2
		inst.components.locomotor.runspeed = inst.components.locomotor.runspeed / 2
--	else
--		print("By land, but debuff was excessive.")
--		inst.components.locomotor.walkspeed = inst.components.locomotor.walkspeed / 8
--		inst.components.locomotor.runspeed = inst.components.locomotor.runspeed / 8
--	end
else
	print("Not by land.")
	inst.components.locomotor.walkspeed = inst.components.locomotor.walkspeed / 6
	inst.components.locomotor.runspeed = inst.components.locomotor.runspeed / 6
end

 

Edited by FurryEskimo

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
  • Create New...