Medieval Dreams

Started by William, July 12, 2023, 20:29:04

Previous topic - Next topic

William

#15
Well, my next step is to setup player networking movement. only 32 slots have wondered i may need more thankfully can compact information in one message or slot. i shall need to augment the present code of which code that pertains to network or default spawn local to a default and as well saved spawn location.
im still interested in oldschool app/gamedev

Midimaster

#16
I had a look on your current version at GitHub.

And I do not understand, how you plan to position the players.

I would expect, that GNet knows the positions and the game only needs to walk through all players GObj's to copy these values into the pivots.

Each Player (you and also the others clients) need a PIVOT (for terrain height control and X/Z-movement) and a ENTITY for turning, looking, shooting, etc...

If somebody moves, he reports the coordinates to GNet. GNet distributes the values to all clients, and the there app use it to position the PIVOTs:

' load player mesh
Global Playermodel:TMesh=LoadAnimMesh("Media/models/Player/player.b3d")
HideEntity Playermodel

Type TPlayer
    Global All:TList=New TList

    Field Username:String
    Field Entity  :TEntity
    Field Pivot   :TEntity
    Field GObj    :TGNetObject


    Function UpdateAll()
        For Local loc:TPlayer = EachIn All
            loc.UpdateOne()
        Next
    End Function


    Method UpdateOne()
        PositionEntity Pivot, X(), Y(), Z()
    End Method


    Method X:Float()
        Return GetGNetFloat(GObj,1)
    End Method


    Method Y:Float()
        Return GetGNetFloat(GObj,2)
    End Method


    Method Z:Float()
        Return GetGNetFloat(GObj,3)
    End Method
   
   
    Method StartUp()
        Pivot  = CreatePivot()
        Entity = CopyEntity(Playermodel)
        EntityParent Entity, Pivot
        RotateEntity Entity, 180,0,180   ' ???????????????
        EntityType   Pivot, GroupCharacters
    End Method



    Function AddMe:TPlayer(Name:String)
        Local loc:TPlayer = New TPlayer
        loc.GObj     = CreateGNetObject(Host)
        loc.Username = Name
        loc.StartUp
        All.AddLast loc
        SetGNetFloat GObj,1, 14
        SetGNetFloat GObj,2, 0.2
        SetGNetFloat GObj,3,-15
        Return loc
    End Function


    Function AddPlayer(Obj:TGNetObject)
        Local loc:TPlayer = New TPlayer
        loc.GObj = Obj
        loc.StartUp
        PositionEntity loc.Pivot, X() , Y() , Z()
        All.AddLast loc
    End Function


   Function ClientHasClosed(Obj:TGnetObject)
      For Local loc:TPlayer = EachIn All
         If loc.GObj = Obj
            FreeEntity(loc.Entity)
            All.Remove loc
         EndIf
      Next
   End Function

End Type

...back from Egypt

Midimaster

Here is a summary of our Discord-talk over the last days:

Server-Hang:

A possible reason for a hanging server can be, that the ESC-KEYs did not reach the server. A second reason could be that the GNetSync() failt.  I also removed the Frameworks to be sure, to have all modules in the app in the case of a crash. And I added a DrawText to see (in the case of a problem) if the Server crashed or still runs, but cannot exit:

SuperStrict
' This is the main server API for the game client

AppTitle="GNet Server Example"
Graphics 640,480

Global Host:TGNetHost=CreateGNetHost()


If Host
   Print "Host created."
Else
   Print "Couldnt create host."
   End
EndIf

Global listen:Int = GNetListen(Host,12345)

If listen
   Print "Server listening on Port 12345"
Else
   Print "Could not bind socket."
   CloseGNetHost Host
   End
EndIf

Repeat
    Cls
    GNetSync Host
    DrawText MilliSecs(), 100,100
    Flip 1
Until AppTerminate() Or KeyDown(KEY_ESCAPE)

CloseGNetHost Host
Print "Server Shut down"



Send the Pivot's X, not the Entity's X!

I found a bug in thee logic of your code:

If KeyDown( KEY_D )=True Then MoveEntity Pivot,0.1,0,0
If KeyDown( KEY_S )=True Then MoveEntity Pivot,0,0,-0.1
If KeyDown( KEY_W )=True
    MoveEntity Pivot,0,0,0.1
    SetGNetFloat(LocalPlayer.GObj,1,EntityX(Pivot))
EndIf   
...

 You need to send the position of the Pivot !!!



Why does NewX print Zero?


I think it will print 0 most of the time. You calculate the difference between last X-value and new X-value. this only shows for a short moment a value>0, when one of the player moves. After the reported move, the function sets the new x-pos and from this moment the difference is 0, until the player is moved again, or?


MoveEntity or PositionEntity?



I thought about your using of MoveEntity() and PositionEntity(). And I think you use it wrong! If you want to place the other player correct you need to use PositionEntity(). Only PositionEntity() set the actors at a correct global position, because PositionEntity() is always oriented global. Means: X ist always WEST-EAST and Z is always NORTH-SOUTH.

MoveEntity() uses the subjectiv orientation of a player. Means: Z+ is always AHEAD and Z- is BACKWARDS. And X- is always TO-THE-LEFT and X+ is always TO-THE-RIGHT. It all depends on the current direction the actor looks at the moment. This direction is his Z. Example: If the actor looks in direction EAST (what is global X+) and you let him move Z+1, he makes a step in direction X+1.

So you cannot mix up EntityX(), which always returns a global position and MoveEntity(), which makes a subjectiv player's movement.
...back from Egypt

William

#18
 im open, i think i will need to write a lot of network code.

i feel your right positionentity would be how to do it correctly.
 120 milliseconds is a long time i will have to learn a network model.

edit: when i get time i may work on that. i do not know the cause of the unhandled gnet exception error.
im still interested in oldschool app/gamedev

Midimaster

play around with this:

pure Gnet still without 3D-entities:



SuperStrict
Graphics 400,300
Global Host:TGNetHost=CreateGNetHost()
Local Name:String

Const GNET_SERVER_SLOT:Int =31
Const GNET_NAME:Int = 1
Const GNET_X:Int    = 2
Const GNET_Y:Int    = 3
Const GNET_ID:Int   = 4

Global I_Am_Server:Int=False
Global IsAliveText:String, TimeRemain:String

If GNetListen(Host,12345) = True
    Name="SERVER"
    I_Am_Server = True
Else
    If GNetConnect(Host,"127.0.0.1",12345)=False
        Name ="Error"
    Else
        Name="Client"
    EndIf
EndIf

Global Me:TPlayer = TPlayer.AddMe(Name)

Global    TimeOut:Int = MilliSecs()+5000
Repeat
    Cls
SetColor 255,255,255
    DrawText IsAliveText + TimeRemain  ,  30,160
    ScanGNet
    MoveMe
    TPlayer.DrawAll
ChangeColor Me.Id()
    DrawText "HERE= " + Name, 150, 10
    Flip 1
Until AppTerminate() Or TimeOut<MilliSecs()

CloseGNetObject(Me.GObj)
Delay 500
CloseGNetHost(Host)
End


Function ScanGnet()
    ' find new
    GNetSync(Host)
    For Local obj:TGNetObject=EachIn GNetObjects( host, GNET_CREATED )
        TPlayer.AddOthers obj
    Next
    For Local obj:TGNetObject=EachIn GNetObjects( host, GNET_CLOSED )
        TPlayer.Remove obj
    Next
    DrawText "GNetObjects:" + GNetObjects(Host,GNET_ALL ).Count() , 30,100
    DrawText "TPlayers:" + TPlayer.All.Count() , 30,130
IsAliveText = "Server is dead"
TimeRemain  = "  (timeout in:" + Int((timeOut-MilliSecs())/1000) + "sec)"
    For Local loc:TGnetObject=EachIn GNetObjects(Host,GNET_ALL )

        If GetGNetInt(loc, GNET_SERVER_SLOT)=True
IsAliveText="Server is Alive"
            TimeOut=MilliSecs()+5000
            Return
        EndIf
    Next
End Function


Function MoveMe()
    If MouseDown(1)
            Me.Move MouseX(), MouseY()
    EndIf
End Function



Type TPlayer
    Global All:TList = New TList
    Field GObj:TGNetObject

    Method Move(X:Int, Y:Int)
        SetGNetInt GObj, GNET_X , X
        SetGNetInt GObj, GNET_Y , Y   
    End Method

    Function DrawAll()
        For Local loc:TPlayer = EachIn All
            loc.Draw
        Next
    End Function
   
    Method Draw()
ChangeColor ID()
        DrawText "Player:" + Name() , X() , Y()       
    End Method
   
    Method Name:String()
        Return GetGNetString(GObj,GNET_NAME)
    End Method
   
    Method X:Int()
        Return GetGNetInt(GObj,GNET_X)
    End Method

    Method Y:Int()
        Return GetGNetInt(GObj,GNET_Y)
    End Method

    Method ID:Int()
        Return GetGNetInt(GObj,GNET_ID)
    End Method
 

Method Define(Nr:Int, Nam:String)
Nam = Nam +" (ID=" +Nr + ")"
SetGNetInt    GObj, GNET_ID         , Nr
        SetGNetString GObj, GNET_NAME       , Nam
End Method


    Function AddMe:TPlayer(Name:String)
        Local loc:TPlayer = New TPlayer

' first fast setup
        loc.GObj = CreateGNetObject(Host)
        SetGNetInt(loc.GObj, GNET_SERVER_SLOT, I_Am_Server )
loc.Define 0,""
loc.move Rand(300),Rand(250)
All.AddLast loc

' second exact setup
ScanGnet()
Delay 500
ScanGnet()
loc.Define GNetObjects(Host,GNET_ALL ).Count() , Name
Return loc
    End Function
 
 
    Function AddOthers(Obj:TGNetObject)
        Local loc:TPlayer = New TPlayer
        loc.GObj = Obj
        All.AddLast loc
    End Function


    Function Remove(obj:TGNetObject)
        For Local loc:TPlayer = EachIn All
            If loc.GObj=obj
                All.Remove loc
            EndIf
        Next
    End Function
End Type


Function ChangeColor(ID:Int)
Select ID
Case 0
SetColor 77,77,77
Case 1
SetColor 255,255,0
Case 2
SetColor 0,255,255
Case 3
SetColor 255,0,255
End Select
End Function
...back from Egypt

William

#20
@Midimaster so i do not remember what i changed, removed newx and instead, positionentity, there is not  gnet exception error memory leaks now.
im still interested in oldschool app/gamedev

William

#21
eventually i will have to compress data per slot to include multiple data so the full player location and rotation  coordinates  are stored in slot 1 i think, there is some example code i have for this but it relies on knowing how many characters are stored per variable
im still interested in oldschool app/gamedev

Midimaster

I would suggest first to use all 32 slot before thinking about an extended system. At the moment you would need 4 slots X,Y, Z and orientation, plus name, plus ID, plus Server_Alive-Slot. This are 7. Make a list of all parameter you will need in next future. It will have below 32 entries!

...back from Egypt

William

#23
alright so current code, no gnet errors but that the player gets positioned at 0,0,0 and from there doesnt move again from there. i do not know why this is again code can be seen from github.com/zarosath/medievaldreams.io, client and gamenet .bmx

i suspect it may be occuring because of collisions? entitytype has not been set except for terrain and pivot. with present code there seems to be an issue. occasionally and not always, when walking near 0,0,0 before the edge of the terrain the player falls through terrain. it may not be collisions but the gravity code with collision, collision is what stops player from falling below. therefor i shall add a condition the player isnt at 0,0,0 or below.

there are so many basic groundwork that needs to be done before gameplay.

edit: no luck, it appears that it is not affiliated with collisions and that for some reason the other players that join are moved to 0,0,0 and i do not know why, i guess that it is related to gnet but i do not know. for whatever reason, the getgnet floats return 0 even though they are set with entityXYZ.

can someone tell me why? @Midimaster ?

i am using a relatively recent bmx weekly release.
im still interested in oldschool app/gamedev

William

#24
okay, found the issue player movement wasnt working because it was not using a pivot and playerentity is at 0,0,0 for whatever reason. updated github. Thanks to midimaster, i had not read or understood it were necessary. so.. i think the server randomly closes after running and closing clients after some time, i am not sure though thought maybe pressing escape one too many times.

next up i am going to try networking with 70 and above latency on a vps to test networking.
im still interested in oldschool app/gamedev

Midimaster

I had a look on the new GitHub code and this are my adwises:

Missing Server Hang Detection

You need to give the server a client too, because only a GNetObject can set the I_Am_Server flag. This is neccessary to inform the clients via GNET_SERVER_SLOT that the server is still activ or dead.

See lesson VI:
Function ScanGnet()
    ...

    For Local loc:TGnetObject=EachIn GNetObjects(Host,GNET_ALL )
        If GetGNetInt(loc, GNET_SERVER_SLOT)=TRUE
            DrawText "Server is Alive",30,160
            TimeOut = MilliSecs()+5000
            Return
        EndIf
    Next
End Function

You will need this "Server-Client" anyway in future also for communication things between server and clients.


Reason for  "unhandled exception assert failed"

I found the reason, why this message appears sometimes and I found a solution.

All the slots, that are used in your game need to be filled with values in the moment when you create a new client. So i suggest to set them immediately after CreateGNetObject():

Const GnetplayerX:Int = 1
Const GnetplayerY:Int = 2
Const GnetplayerZ:Int = 3
Const GnetplayerPitch:Int = 4
Const GnetplayerYaw:Int = 5
Const GnetplayerRoll:Int = 6
...

Type TPlayer
    Function Addme:TPlayer(Name:String)
        loc.GObj = CreateGNetObject(Host)
    ' first fast setup
        For local i:Int= 1 to 6
        SetGNetFloat loc.GObj,i,0
        Next
    ' second exact setup
    ScanGnet()
    Delay 500
 ...          

The reason is, that immediately the other computers will find the new player and try to position him in the 3D-world. Therefore they ask GNet for the coordinates, which maybe undefined. This result in the error message.



Use METHODs instead of long terms

In your new ScanGnet() you write:
Function ScanGnet()
...
    For Local obj:tgnetobject=EachIn GNetObjects(Host, GNET_MODIFIED)
          For Local loc:TPlayer = EachIn TPlayer.All
                If loc.GObj=obj
                    PositionEntity(loc.pivot,loc.X(),GetGNetFloat(loc.GObj,2),GetGNetFloat(loc.GObj,3))
                    RotateEntity(loc.pivot,loc.Pitch(),loc.Yaw(),loc.Roll())
            EndIf
        Next
    Next
End Function

But you already have this nice METHODs like X(). which is a replacement for GetGNetFloat(loc.GObj,1) 

So why not use:

    PositionEntity(loc.pivot,loc.X(),loc.Y(),loc.Z())
    RotateEntity(loc.pivot,loc.Pitch(),loc.Yaw(),loc.Roll())


or even better:

Function ScanGnet()
...
For Local obj:TGnetObject=EachIn GNetObjects(Host, GNET_MODIFIED)
      For Local loc:TPlayer = EachIn TPlayer.All
        If loc.GObj=obj
            loc.Set
        EndIf
    Next
Next
End Function
....
Type TPlayer
     Method Set()
           PositionEntity Pivot, X(), Y(), Z()
           RotateEntity   Pivot, Pitch(), Yaw(), Roll()
     End Method



The same in the main loop. You better use a method to send the Gnet values:

Instead of your code:
Repeat
    .....
    SetGNetFloat(LocalPlayer.GObj,1,EntityX(localplayer.pivot))
    SetGNetFloat(LocalPlayer.GObj,2,EntityY(localplayer.pivot))
    SetGNetFloat(LocalPlayer.GObj,3,EntityZ(localplayer.pivot))
    SetGNetFloat(localplayer.GObj,4,EntityPitch(localplayer.pivot))
    SetGNetFloat(localplayer.GObj,5,EntityYaw(localplayer.pivot))
    SetGNetFloat(localplayer.GObj,6,EntityRoll(localplayer.pivot))
....

You can use a METHOD:

Repeat
    .....
    LocalPlayer.Send
    ....
Until....

Type TPlayer
      Method Send()
             SetGNetFloat GObj, 1, EntityX(pivot)
             SetGNetFloat GObj, 2, EntityY(pivot)
             ...and so on
             SetGNetFloat GObj, 6, EntityRoll(pivot)
      End Method
[/code]
...back from Egypt

William

#26
Sometimes upon closing a client but now it is when a client closes the other client(S) unhandled exception gnet error too via remote server.

i believe before that it were a enet 'errror' but server is in debug mode now and all slots are/were filled with floats of 0. At one point i were closing and running multiple clients and ran them for 4 minutes just fine and then, enet error or something.

@Midimaster both client and server gnet error, prior to that it was enet. is there something wrong with the code? for i believe it may be time of execution of each network event but it is when closegnetobject the gnet error occurs on line gnetsync(host)

~>Unhandled Exception:ENet errror
~>
exit
Segmentation fault (core dumped)



edit: okay, only setting floats if coordinates of client and server dont match then send coordinates. server appears to not crash/ not Gnet Enet error now. much more stable. I suspect Enet has its own thread.
im still interested in oldschool app/gamedev

William

I believe Gnet/Enet is insecure, if somebody connects to the server and sends bad data, the server may crash. is this true?
im still interested in oldschool app/gamedev

Midimaster

#28
SERVER.BMX

I had a look on the server.bmx and I think this is crazy too often:

Repeat
    GNetSync Host
    Print MilliSecs()
Until shutdown=True

If you write a loop like this, this eats all performance. I guess in this loop the GnetSync() happens 100000 times a second!!! I'm not sure, but I gues you should add something to give the OS a change to react: add DELAY or a use a TIMER.

And is it really a good idea to run the server without a possibility to stop it? How ca shutdown become TRUE? Never in your app. This means the app run and run and run.


Why do you run SERVER.BMX without any window? Is there a useful reason?

PLAYER.BMX

When you are already inside a METHOD you should not use long terms to handle the field:

WRONG:
Method SendX()
    SetGNetFloat(LocalPlayer.GObj,GnetplayerX,EntityX(localplayer.pivot))
End Method

CORRECT:
Method SendX()
    SetGNetFloat GObj , GnetplayerX , EntityX(pivot)
End Method

of course the same with all your other Send...()-functions


All 32 slots?

I think it is no good idea to fill all 32 slots as long as you need only 6. And If you study my example in lesson VII you can see, that it is helpful to insert a GnetSync() after this pre-fill. I also suggest to wait sfor some msecs between
CloseGNetObject() and CloseGNetHost()

CloseGNetObject(localplayer.GObj)
Delay 100
Print"Player object closed"
CloseGNetHost(Host)
delay 100

...back from Egypt

William

#29
true about the server but i mean with 70ms latency it prints milliseconds like maybe 10 times a seconds. so, this would not subtract from performance?
Quote from: Midimaster on August 15, 2023, 02:52:21SERVER.BMX

I had a look on the server.bmx and I think this is crazy too often:

Repeat
    GNetSync Host
    Print MilliSecs()
Until shutdown=True

If you write a loop like this, this eats all performance. I guess in this loop the GnetSync() happens 100000 times a second!!! I'm not sure, but I gues you should add something to give the OS a change to react: add DELAY or a use a TIMER.

And is it really a good idea to run the server without a possibility to stop it? How ca shutdown become TRUE? Never in your app. This means the app run and run and run.


Why do you run SERVER.BMX without any window? Is there a useful reason?

PLAYER.BMX

When you are already inside a METHOD you should not use long terms to handle the field:

WRONG:
Method SendX()
    SetGNetFloat(LocalPlayer.GObj,GnetplayerX,EntityX(localplayer.pivot))
End Method

CORRECT:
Method SendX()
    SetGNetFloat GObj , GnetplayerX , EntityX(pivot)
End Method

of course the same with all your other Send...()-functions


All 32 slots?

I think it is no good idea to fill all 32 slots as long as you need only 6. And If you study my example in lesson VII you can see, that it is helpful to insert a GnetSync() after this pre-fill. I also suggest to wait sfor some msecs between
CloseGNetObject() and CloseGNetHost()

CloseGNetObject(localplayer.GObj)
Delay 100
Print"Player object closed"
CloseGNetHost(Host)
delay 100


would delay stall the thread delaying? perhaps soon may be the time to discuss multithreading. i am wondering when to incorporate that, can gnet be multithreaded, eNet/GNet already? maybe later when game logic is incorporated.

also server seems stable  more the changes  like this, thanks.
im still interested in oldschool app/gamedev