Jump to content

Sync peds?


Recommended Posts

Hi, this script works fine if you're seeing it on your own perspective, you could see the zombies walking smoothly and fine, but when you see others being chased by their own zombies, the zombies is kinda laggy and kinda slow, is there anyway to sync with other players on what you're seeing as well? this is a clientside script so. yeah that might be the problem but im sure there might be something?  the peds are created serverside, but the chase function is on client.

Chase function:

local function trackMe()
	
	local zombies = getElementsByType("ped",resourceRoot,true)
	
	for index,zombie in ipairs(zombies) do
		if math.random(1,60) == 15 and isPedDead(zombie) == false then
			local sound = playSound3D("sounds/mgroan"..tostring(math.random(1,10))..".ogg",zombie.position)
			setSoundMaxDistance(sound, 30)
		end
		local zombieTarget = zombieData[zombie].target
		if zombieTarget and isElement(zombieTarget) then
			local lx,ly,lz = getElementPosition(zombieTarget)
			local lVector = Vector3(lx,ly,lz)
			local hVector = Vector3(getPedBonePosition(zombie,6))
			local zVector = Vector3(getElementPosition(zombie))
			local doesZombieSeePlayer = isLineOfSightClear(hVector,lVector,true,false,false,true,false,true,true,zombie)
			local distanceToPlayer = getDistanceBetweenPoints2D(zVector.x,zVector.y,lVector.x,lVector.y)
			if timesExecuted%5 == 0 and zombieData[zombie].target == localPlayer then
				local newTarget,doesSee,distance = getNewTarget(zombie)
				if newTarget and doesSee and distance < distanceToPlayer and newTarget ~= zombieData[zombie].target then
					triggerServerEvent("Zday:setZombieNewTarget",zombie,newTarget)
				end
			end
			if zombieData[zombie].target == localPlayer and doesZombieSeePlayer == false and isPedDead(zombieTarget)==false then
				local newTarget,doesSee = getNewTarget(zombie,localPlayer)
				if newTarget and doesSee and newTarget ~= zombieData[zombie].target then
					triggerServerEvent("Zday:setZombieNewTarget",zombie,newTarget)
				end
			end
			if zombieData[zombie].hunting then
				if not zombieData[zombie].positions then zombieData[zombie].positions = 0 end
				if not zombieData[zombie].paths then zombieData[zombie].paths = {} end
				local dist = false
				if #zombieData[zombie].paths > 1 then
					local llx,lly,llz = unpack(zombieData[zombie].paths[#zombieData[zombie].paths-1])
					dist = getDistanceBetweenPoints3D(llx,lly,llz,lx,ly,lz)
				end
				if dist and dist > 0.7 then
					table.insert(zombieData[zombie].paths,{lx,ly,lz,getPedControlState(zombieTarget,"jump"),getPedControlState(zombieTarget,"sprint")})
				elseif not dist then
					table.insert(zombieData[zombie].paths,{lx,ly,lz,getPedControlState(zombieTarget,"jump"),getPedControlState(zombieTarget,"sprint")})
				end
				if zombieData[zombie].paths[zombieData[zombie].positions+1] and not zombieData[zombie].eating then
					local nx,ny,nz,jump,sprint = unpack(zombieData[zombie].paths[zombieData[zombie].positions+1])
					local nVector = Vector3(nx,ny,nz)
					local isClear = isLineOfSightClear(zVector,nVector,true,true,false,true,false,true,true,zombie)
					local notMoving = false
					if zombieData[zombie].lastPosition then
						local dist = getDistanceBetweenPoints3D(zombieData[zombie].lastPosition,zVector)
						if dist < 0.01 then
							local action = math.random(1,2)
							if action == 1 then
								setPedControlState(zombie,"fire",true)
							elseif action == 2 then
								notMoving = true
							end
						else
							setPedControlState(zombie,"fire",false)
						end
					end
					local needToJump = (jump or getPedSimplestTask(zombie) == "TASK_SIMPLE_CLIMB" or arePedLegsBlocked(zombie) or notMoving)
					if needToJump then
						if (getPedMoveState(zombie) ~= "jump" or getPedMoveState(zombie) ~= "climb") then setPedControlState(zombie,"forwards",false) end
						if sprint then setPedControlState(zombie,"sprint",true) end
						setPedControlState(zombie,"jump",true)
					else
						setPedControlState(zombie,"sprint",false)
						setPedControlState(zombie,"jump",false)
					end
					local dist = getDistanceBetweenPoints2D(zVector.x,zVector.y,nVector.x,nVector.y)
					local pathDistance = getZombieCalculatedPathDistance(zombie)
					local diff = pathDistance/distanceToPlayer
					if pathDistance > distanceToPlayer and diff > 1.1 and doesZombieSeePlayer then
						zombieData[zombie].paths = {}
						zombieData[zombie].positions = 0
						zombieData[zombie].changingPath = true
						zombieData[zombie].changePathTick = getTickCount()
					end
					if zombieData[zombie].changingPath then
						if (getTickCount() - zombieData[zombie].changePathTick) > 500 then
							zombieData[zombie].changingPath = nil
							zombieData[zombie].changePathTick = nil
						end
					end
					local angle = rot(zVector.x,zVector.y,nVector.x,nVector.y)
					setPedCameraRotation(zombie,-angle)
					setPedControlState(zombie,"forwards",true)
					if zombie.inWater then
						setElementRotation(zombie,0,0,angle)
						setPedControlState(zombie,"sprint",true)
					elseif not sprint then
						setPedControlState(zombie,"sprint",false)
					end
					zombieData[zombie].lastPosition = Vector3(getElementPosition(zombie))
					if isClear and dist < 1 then
						zombieData[zombie].positions = zombieData[zombie].positions + 1
					end
				else
					if isPedDead(zombie) == false and getDistanceBetweenPoints3D(zVector,lVector) < 1 and not playersDoomed[zombieTarget] then
						murderPlayerByZombie(zombieTarget,zombie)
						if isPedDead(zombieTarget) then 
							playersDoomed[zombieTarget] = true
						end 
					elseif getDistanceBetweenPoints3D(zVector,lVector) < 1 and ((isPedDead(zombieTarget) or zombieTarget.health<1) and playersEatable[zombieTarget] and not playerEated[zombieTarget]) then
						playerEated[zombieTarget] = true
						zombieData[zombie].eating = true
						setPedAnimation(zombie,"MEDIC","cpr",-1,false,true,false)
						setTimer(resetZombieAnimation,10000,1,zombie)
					end
					setPedControlState(zombie,"forwards",false)
				end
			else
				if doesZombieSeePlayer and not zombieData[zombie].hunting then
					zombieData[zombie].hunting = true
					zombieData[zombie].positions = 0
				elseif not zombieData[zombie].hunting then
					local newTarget = getNewTarget(zombie,localPlayer),doesSee
					if newTarget and doesSee and not zombieData[zombie].changingTarget then
						triggerServerEvent("Zday:setZombieNewTarget",zombie,newTarget)
						zombieData[zombie].changingTarget = true
					end
				end
				if not zombieData[zombie].changingPath then
					setPedControlState(zombie,"forwards",false)
				end
			end
		else
			setPedControlState(zombie,"forwards",false)
		end
	end
	
	timesExecuted=timesExecuted+1

end

setTimer(trackMe,100,0)	

 

Edited by Bean666
Link to post
  • Moderators
1 hour ago, Bean666 said:

yeah that might be the problem but im sure there might be something?

You can reduce the ped sync interval from 400 to 200 (mtaserver.conf):

https://wiki.multitheftauto.com/wiki/Server_mtaserver.conf#ped_sync_interval

(https://wiki.multitheftauto.com/wiki/SetServerConfigSetting)

The server has to be stopped first in case of editing the config file.

Link to post
Posted (edited)
13 minutes ago, IIYAMA said:

You can reduce the ped sync interval from 400 to 200 (mtaserver.conf):

https://wiki.multitheftauto.com/wiki/Server_mtaserver.conf#ped_sync_interval

(https://wiki.multitheftauto.com/wiki/SetServerConfigSetting)

The server has to be stopped first in case of editing the config file.

will 100 hurt the server?

as when i start the script an info is saying it needs to set ped sync interval to a 100.

Edited by Bean666
Link to post
  • Moderators
17 minutes ago, Bean666 said:

will 100 hurt the server?

as when i start the script an info is saying it needs to set ped sync interval to a 100.

According to wiki the minimum for peds value is 200. If you can set it to 100, it shouldn't be working, that is if wiki is up to date.

Will it hurt the server? That depends on the internet connection of the players. 😭

Link to post
1 minute ago, IIYAMA said:

According to wiki the minimum for peds value is 200. If you can set it to 100, it shouldn't be working, that is if wiki is up to date.

RQZxc2E.png

it says valid 50-4000 so yeah maybe 100 is valid though idk ill try.

  • Like 1
Link to post
  • Moderators
15 minutes ago, Bean666 said:

RQZxc2E.png

it says valid 50-4000 so yeah maybe 100 is valid though idk ill try.

yea give it a try. 👍

Link to post
Posted (edited)

tried 100 ped sync interval, works better than before but other's peds still kinda looking stuttery. 

local function trackMe()
	
	local zombies = getElementsByType("ped",resourceRoot,true)
	
	for index,zombie in ipairs(zombies) do
		if math.random(1,60) == 15 and isPedDead(zombie) == false then
			local sound = playSound3D("sounds/mgroan"..tostring(math.random(1,10))..".ogg",zombie.position)
			setSoundMaxDistance(sound, 50)
		end
		local zombieTarget = zombieData[zombie].target
		if zombieTarget and isElement(zombieTarget) and isPedDead(zombieTarget) == false then
			local lx,ly,lz = getElementPosition(zombieTarget)
			local lVector = Vector3(lx,ly,lz)
			local hVector = Vector3(getPedBonePosition(zombie,6))
			local zVector = Vector3(getElementPosition(zombie))
			local doesZombieSeePlayer = isLineOfSightClear(hVector,lVector,true,false,false,true,false,true,true,zombie)
			local distanceToPlayer = getDistanceBetweenPoints2D(zVector.x,zVector.y,lVector.x,lVector.y)
			if timesExecuted%5 == 0 and zombieData[zombie].target == localPlayer then
				local newTarget,doesSee,distance = getNewTarget(zombie)
				if newTarget and doesSee and distance < distanceToPlayer and newTarget ~= zombieData[zombie].target then
					triggerServerEvent("Zday:setZombieNewTarget",zombie,newTarget)
				end
			end
			if getDistanceBetweenPoints3D(zVector,lVector) > 100 then 
				triggerServerEvent("Zday:destroyZombie",zombie)
			end 
			if zombieData[zombie].hunting then
				if not zombieData[zombie].positions then zombieData[zombie].positions = 0 end
				if not zombieData[zombie].paths then zombieData[zombie].paths = {} end
				local dist = false
				if #zombieData[zombie].paths > 1 then
					local llx,lly,llz = unpack(zombieData[zombie].paths[#zombieData[zombie].paths-1])
					dist = getDistanceBetweenPoints3D(llx,lly,llz,lx,ly,lz)
				end
				if dist and dist > 0.7 then
					table.insert(zombieData[zombie].paths,{lx,ly,lz,getPedControlState(zombieTarget,"jump"),getPedControlState(zombieTarget,"sprint")})
				elseif not dist then
					table.insert(zombieData[zombie].paths,{lx,ly,lz,getPedControlState(zombieTarget,"jump"),getPedControlState(zombieTarget,"sprint")})
				end
				if zombieData[zombie].paths[zombieData[zombie].positions+1] and not zombieData[zombie].eating then
					local nx,ny,nz,jump,sprint = unpack(zombieData[zombie].paths[zombieData[zombie].positions+1])
					local nVector = Vector3(nx,ny,nz)
					local isClear = isLineOfSightClear(zVector,nVector,true,true,false,true,false,true,true,zombie)
					local notMoving = false
					if zombieData[zombie].lastPosition then
						local dist = getDistanceBetweenPoints3D(zombieData[zombie].lastPosition,zVector)
						if dist < 0.01 then
							local action = math.random(1,2)
							if action == 1 then
								setPedControlState(zombie,"fire",true)
							elseif action == 2 then
								notMoving = true
							end
						else
							setPedControlState(zombie,"fire",false)
						end
					end
					local needToJump = (jump or getPedSimplestTask(zombie) == "TASK_SIMPLE_CLIMB" or arePedLegsBlocked(zombie) or notMoving)
					if needToJump then
						if (getPedMoveState(zombie) ~= "jump" or getPedMoveState(zombie) ~= "climb") then setPedControlState(zombie,"forwards",false) end
						if sprint then setPedControlState(zombie,"sprint",true) end
						setPedControlState(zombie,"jump",true)
					else
						setPedControlState(zombie,"sprint",false)
						setPedControlState(zombie,"jump",false)
					end
					local dist = getDistanceBetweenPoints2D(zVector.x,zVector.y,nVector.x,nVector.y)
					local pathDistance = getZombieCalculatedPathDistance(zombie)
					local diff = pathDistance/distanceToPlayer
					if pathDistance > distanceToPlayer and diff > 1.1 and doesZombieSeePlayer then
						zombieData[zombie].paths = {}
						zombieData[zombie].positions = 0
						zombieData[zombie].changingPath = true
						zombieData[zombie].changePathTick = getTickCount()
					end
					if zombieData[zombie].changingPath then
						if (getTickCount() - zombieData[zombie].changePathTick) > 500 then
							zombieData[zombie].changingPath = nil
							zombieData[zombie].changePathTick = nil
						end
					end
					local angle = rot(zVector.x,zVector.y,nVector.x,nVector.y)
					local mutatedZombie = getElementData(zombie, "mutatedZombie")
					setPedCameraRotation(zombie,-angle)
					setPedControlState(zombie,"forwards",true)
					if mutatedZombie == "Runner" then 
						setPedControlState(zombie,"sprint",true)
					end 
					if zombie.inWater then
						setElementRotation(zombie,0,0,angle)
						setPedControlState(zombie,"sprint",true)
					elseif not sprint then
						if mutatedZombie == "Runner" then 
							setPedControlState(zombie,"sprint",true)
						else
							setPedControlState(zombie,"sprint",false)
						end 
					end
					zombieData[zombie].lastPosition = Vector3(getElementPosition(zombie))
					if isClear and dist < 1 then
						zombieData[zombie].positions = zombieData[zombie].positions + 1
					end
					if getDistanceBetweenPoints3D(zVector,lVector) < 4 then 
						if getPedControlState(zombie, "sprint") == true then 
							setPedControlState(zombie, "sprint", false)
						end 
					end 
				else
					if getDistanceBetweenPoints3D(zVector,lVector) > 100 then 
						triggerServerEvent("Zday:destroyZombie",zombie)
					elseif isPedDead(zombie) == false and getDistanceBetweenPoints3D(zVector,lVector) < 2 and not playersDoomed[zombieTarget] then
						murderPlayerByZombie(zombieTarget,zombie)
						if isPedDead(zombieTarget) then 
							playersDoomed[zombieTarget] = true
						end
					elseif getDistanceBetweenPoints3D(zVector,lVector) < 1 and ((isPedDead(zombieTarget) or zombieTarget.health<1) and playersEatable[zombieTarget] and not playerEated[zombieTarget]) then
						playerEated[zombieTarget] = true
						zombieData[zombie].eating = true
						setPedAnimation(zombie,"MEDIC","cpr",-1,false,true,false)
						setTimer(resetZombieAnimation,10000,1,zombie)
					end
					if getDistanceBetweenPoints3D(zVector,lVector) < 4 then 
						if getPedControlState(zombie, "sprint") == true then 
							setPedControlState(zombie, "sprint", false)
						end 
					end 
					setPedControlState(zombie,"forwards",false)
				end
			else
				if doesZombieSeePlayer and not zombieData[zombie].hunting then
					zombieData[zombie].hunting = true
					zombieData[zombie].positions = 0
				elseif not zombieData[zombie].hunting then
					local newTarget = getNewTarget(zombie,localPlayer),doesSee
					if newTarget and doesSee and not zombieData[zombie].changingTarget then
						triggerServerEvent("Zday:setZombieNewTarget",zombie,newTarget)
						zombieData[zombie].changingTarget = true
					end
				end
				if not zombieData[zombie].changingPath then
					setPedControlState(zombie,"forwards",false)
				end
			end
		else
			setPedControlState(zombie,"forwards",false)
			triggerServerEvent("Zday:delayDestroyZombie", zombie)
		end
	end
	
	timesExecuted=timesExecuted+1

end

setTimer(trackMe,100,0)	

client follow func.

 

syncer(Server):

local function setZombieTarget(zombie,target)

	if target and isElement(target) then
		triggerClientEvent(root,"Zday:setZombieTarget",zombie,zombie,target)
		zombieTargets[zombie] = target
		if getElementSyncer(zombie) ~= target then
			setElementSyncer(zombie,target)
		end
	end

end

 

overall nothings wrong, it's just the sync.

Edited by Bean666
Link to post
  • Moderators
4 hours ago, Bean666 said:

tried 100 ped sync interval, works better than before but other's peds still kinda looking stuttery. 

It looks pretty good. I have seen worse.

One of the reasons for the stuttering is because some ped interactions are based on each client individuality. At the moment the peds are resynced by the syncer (position, rotation and velocity are being reset), the interactions that are set by the syncer player does not match up with the non syncer players.

Interactions like:

  • Animations
  • Initial running speed
  • Jumping
  • etc.

 

That being said, there is always some variation of stuttering involved when streaming element orientations.

 


How to solve this interaction de-sync problem?

  • You might want to consider doing some of the ped interactions serverside.
  • Or the same way as the streamer works:
    • You set an <animation/interaction> for your ped
    • Send a message to serverside.
    • Serverside sends a message to all other clients (excluding yourself).
    • And those clients set the <animation/interaction>.
  • Like 1
Link to post
11 minutes ago, IIYAMA said:

It looks pretty good. I have seen worse.

One of the reasons for the stuttering is because some ped interactions are based on each client individuality. At the moment the peds are resynced by the syncer (position, rotation and velocity are being reset), the interactions that are set by the syncer player does not match up with the non syncer players.

Interactions like:

  • Animations
  • Initial running speed
  • Jumping
  • etc.

 

That being said, there is always some variation of stuttering involved when streaming element orientations.

 


How to solve this interaction de-sync problem?

  • You might want to consider doing some of the ped interactions serverside.
  • Or the same way as the streamer works:
    • You set an <animation/interaction> for your ped
    • Send a message to serverside.
    • Serverside sends a message to all other clients (excluding yourself).
    • And those clients set the <animation/interaction>.

i might do the same way as the streamer works, but first thing, wdym by animations/interactions? if u mean by walking anims i can't set it since it does not update, peds wont rotate and just keep going the same direction, even tho updatePosition bool is set to true, thats why i only have the state "forward" set to true instead.

Link to post
  • Moderators
32 minutes ago, Bean666 said:

but first thing, wdym by animations/interactions?

Every function that makes the ped do anything, that also includes the "forward" state.

 

33 minutes ago, Bean666 said:

if u mean by walking anims i can't set it since it does not update

(You are able to do that. Take a closer look at the zombie resource. That is if you want that.)

Link to post
Posted (edited)
4 minutes ago, IIYAMA said:

Every function that makes the ped do anything, that also includes the "forward" state.

 

(You are able to do that. Take a closer look at the zombie resource. That is if you want that.)

Animations is an option for me, i'll take a look on that after i fix the sync.

and the zombie script i'm using isn't slothmans thats why the walking anims kinda aren't suited on it, but i'll try. anyway,

about:

  • Send a message to server-side.
  • Serverside sends a message to all other clients (excluding yourself).

By this how do I do this?

The send message kinda thingy.

is it like a trigger Client/Server Event or some sort?

Can i have a short example, I'm kind of new to sync stuff. I might have an idea once I see how it's done.

Edited by Bean666
Link to post
  • Moderators
19 minutes ago, Bean666 said:

is it like a trigger Client/Server Event or some sort?

Yes

 

 

48 minutes ago, Bean666 said:

Can i have a short example, I'm kind of new to sync stuff. I might have an idea once I see how it's done.

A small one, you have to fill in the blanks: --[[...]]

 

Syncer (clientside):

triggerServerEvent(--[[...]])

 

Server:

addEvent(--[[...]])

function functionName (ped, action, status)
	-- get all players except for the one that triggered the the event --
	local players = getElementsByType("player")
	for i=1, #players do
		if players[i] == client then
			table.remove(players, i)
			break
		end
	end
 	-- --
  
	triggerClientEvent(players, --[[...]])
end
  
addEventHandler(--[[...]])

 

Other players (clientside):

addEvent(--[[...]])

function functionName (ped, action, status)
  
end
  
addEventHandler(--[[...]])

 

 

 

Link to post
Posted (edited)
9 minutes ago, IIYAMA said:

Yes

 

 

A small one, you have to fill in the blanks: --[[...]]

 

Syncer (clientside):



triggerServerEvent(--[[...]])

 

 

 

 

let's say i put this on the trackMe function,

which initiates every 100 ms, would that be fine? or would I have to create it's own timer that's not 100 ms since I feel like that would eat bandwidth or something. What do you suggest?

Edited by Bean666
Link to post
  • Moderators
8 minutes ago, Bean666 said:

which initiates every 100 ms, would that be fine? or would I have to create it's own timer that's not 100 ms since I feel like that would eat bandwidth or something. What do you suggest?

 

That data rate is a bit too high, especially if you are sending the data separately, that is a multiplier after all.

  • All data merged in to 1 packet/message: triggerServerEvent/triggerClientEvent
  • Do not send data double. If you set the ped to sprinting, do not tell the other players that over and over.

 

 

Link to post
2 minutes ago, IIYAMA said:

 

That data rate is a bit too high, especially if you are sending the data separately, that is a multiplier after all.

  • All data merged in to 1 packet/message: triggerServerEvent/triggerClientEvent
  • Do not send data double. If you set the ped to sprinting, do not tell the other players that over and over.

 

 

I can see that as well, so anyway to do this without having to put it on the 100 ms function? 

Link to post
  • Moderators
18 minutes ago, Bean666 said:

I can see that as well, so anyway to do this without having to put it on the 100 ms function? 

The 100ms function is not the issue.

The issue is the synchronization with the server. Which means that the syncer has to keep track of what the ped is actually doing. And only send a message to the server and other players when the ped is doing something else.

Link to post
Posted (edited)
3 minutes ago, IIYAMA said:

The 100ms function is not the issue.

The issue is the synchronization with the server. Which means that the syncer has to keep track of what the ped is actually doing. And only send a message to the server and other players when the ped is doing something else.

so will it be okay if i only send the message while the ped is following? so it would look smoother than it was? forget the attacking, etc etc.

Edited by Bean666
Link to post
  • Moderators
Just now, Bean666 said:

so it would look smoother than it was?

More consistent and at the right timing. Not sure about 'smoother'.

  • Like 1
Link to post
Posted (edited)
37 minutes ago, IIYAMA said:

More consistent and at the right timing. Not sure about 'smoother'.

wdyt about this? tried it on myself, without the sync yet, setelementposition with bool false doesnt look weird. curious what itll look like to other players.

Send zombie element to server:

triggerServerEvent("zombieSyncServer", resourceRoot, zombie)

 

Server, get position of zombie:

addEvent("zombieSyncServer", true)

function syncSendToClient (ped, x, y, z)
	-- get all players except for the one that triggered the the event --
	local players = getElementsByType("player")
	for i=1, #players do
		if players[i] == client then
			table.remove(players, i)
			break
		end
	end
 	-- --
	local Zx, Zy, Zz = getElementPosition(ped)
	triggerClientEvent(players, "zombieSyncClient", resourceRoot, ped, Zx, Zy, Zz)
end
  
addEventHandler("zombieSyncServer", root, syncSendToClient)

 

back to client:

addEvent("zombieSyncClient", true)

function syncReceiveFromServer (ped, x, y, z)
	setElementPosition ( ped, x, y, z, false )  
end
  
addEventHandler("zombieSyncClient", root, syncReceiveFromServer)

I don't know what im doing for now but that's what i came up first with.

Edited by Bean666
Link to post
  • Moderators
28 minutes ago, Bean666 said:

Server, get position of zombie:

Position synchronization is already handled by the server. Better to try something else.

Link to post
3 hours ago, IIYAMA said:

Position synchronization is already handled by the server. Better to try something else.

noticed that. tested already with someone , hell looks terrible hahaha, any ideas u can suggest? or what functions to use or what do i need to get and set.

Link to post
  • Moderators
11 hours ago, Bean666 said:

noticed that. tested already with someone , hell looks terrible hahaha, any ideas u can suggest? or what functions to use or what do i need to get and set.

For example jumping.

  • Like 1
Link to post

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
  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...