Author Topic: Some basic p2p stuff  (Read 265 times)

Marcus

  • Administrator
  • Hero Member
  • *****
  • Posts: 542
    • View Profile
Some basic p2p stuff
« on: February 26, 2017 »
I started to write on a 2d multiplayer shooter thingy, might be interesting if you're experimenting with the p2p library but don't find the tick-tack-toe example funny enough :)

The "game" is pointless. Start the first instance and choose to create a lobby (Y), then start up to 3 other instances where you chose not to create a lobby (N). Each player can shoot bullets (left mouse button) and throw grenades (right mouse button). Move around with AWSD or arrow keys and aim with the mouse cursor. Everything one player does is transmitted to the others.

Code: [Select]
' ------------------------------------------------------------------------------
' 1234.
'
' chose to create a lobby (Y) for the first running instance, then start up to
' three more instances that connect to the first (N).
' ------------------------------------------------------------------------------

import "Keycodes.lib"
import "Speed.lib"
import "p2p.lib"

constant:
' transmit player position four (60/15) times every second.
TSMT_PLY_POS_TICKS 15

' players.
PLY_MAX_BULLETS 8
PLY_MAX_GRENADES 2

' p2p messages.
MSG_PLAYER_POS   16
MSG_PLAYER_SHOOT 17
MSG_PLAYER_BULLET_DIED 18
MSG_PLAYER_GRENADE 19

' player types. PLY_BOT only exists for server, will be PLY_REMOTE
' for client.
PLY_NONE   0
PLY_THIS   1
PLY_REMOTE 2
PLY_BOT    3

' player status.
PLY_STATE_NONE       0
PLY_STATE_CONNECTING 1
PLY_STATE_INGAME     2

' log.
MAX_LOG_MESSAGES 6
LOG_CHAT 1
LOG_INFO 2
LOG_GOOD 3
LOG_BAD  4

visible:
' server or client.
isServer = false

' players, bullets and grenades.
players?[4]
playerBullets?[4][PLY_MAX_BULLETS]
playerGrenades?[4][PLY_MAX_GRENADES]

' log.
vLog?[MAX_LOG_MESSAGES]

hidden:

set window 16, 16, 480, 270

if not P2P_Init() then _Error "Could not initialize the p2p library"

_ClearLog
_ClearPlayers

' setup this player.
randomize time()
players[0].type = PLY_THIS
players[0].state = PLY_STATE_INGAME
players[0].x# = float(rnd(width(primary)))
players[0].y# = float(rnd(height(primary)))
players[0].dx# = 0.0
players[0].dy# = 0.0
players[0].spd# = 2.0
players[0].aimX# = 0.0
players[0].aimY# = 1.0
players[0].a# = atan2(1.0, 0.0)
players[0].ticks = 0
players[0].id = -1

' create lobby or connect to localhost.
write "Create lobby? (Y/N) "
result$ = upper(rln$(1))
if result = "Y"
isServer = true
players[0].name$ = "Serverman"
if not P2P_CreateLobby("1234", players[0].name$, 0, false)
_Error "Could not create lobby"
endif
if not P2P_SearchClient()
_Error "Could not search for client"
endif
_Log "Lobby created", LOG_INFO
else
isServer = false
players[0].name$ = "Clientman"
if not P2P_Connect("localhost", players[0].name$)
_Error "Could not connect to lobby"
endif
_Log "Joined lobby", LOG_INFO
endif

set redraw off

do
_P2P_Update
_HandleMessages

for i = 0 to 3
_UpdatePlayer players[i]
next
_UpdatePlayerBullets
_UpdatePlayerGrenades
_UpdateLog

' draw.
set color 0, 0, 0
cls
for i = 0 to 3
if players[i].type and players[i].state = PLY_STATE_INGAME then _DrawPlayer players[i]
next
for i = 0 to 3
' bullets.
for j = 0 to PLY_MAX_BULLETS - 1
if playerBullets[i][j].type
draw rect int(playerBullets[i][j].x#) - 1, int(playerBullets[i][j].y#) - 1, 2, 2, true
endif
next
' grenades.
for j = 0 to PLY_MAX_GRENADES - 1
if playerGrenades[i][j].type
s = 4 + int(sin(playerGrenades[i][j].p#*180.0)*8.0)
x# = playerGrenades[i][j].sx#*(1.0 - playerGrenades[i][j].p#) + playerGrenades[i][j].dx#*playerGrenades[i][j].p#
y# = playerGrenades[i][j].sy#*(1.0 - playerGrenades[i][j].p#) + playerGrenades[i][j].dy#*playerGrenades[i][j].p#
draw rect int(x) - s/2, int(y) - s/2, s, s, true
endif
next
next

_DrawLog

redraw
_SPD_HoldFrame 60
until keydown(VK_ESC, true)

' ------------------------------------------------------------------------------
' handle p2p messages.
' ------------------------------------------------------------------------------
procedure HandleMessages()
pkg?
while P2P_Receive(pkg)
' connections.
if pkg.id = P2P_USER_CONNECTED
_AddPlayer pkg.usr, pkg.msg$
_Log pkg.msg$ + " joined the lobby", LOG_INFO
if isServer
if ServerFull()
_Log "Server is now full", LOG_INFO
elseif not P2P_SearchClient()
_Error "Could not search for another client"
endif
endif
elseif pkg.id = P2P_USER_DISCONNECTED
index = PlayerIndex(pkg.usr)
players[index].type = PLY_NONE
_Log pkg.msg$ + " left the lobby", LOG_INFO
elseif pkg.id = P2P_CONNECTION_LOST
_Error "Lost connection to lobby"
' player status.
elseif pkg.id = MSG_PLAYER_POS
index = PlayerIndex(pkg.usr)
_P2P_Decompose pkg.msg$
x = P2P_GetInt()
y = P2P_GetInt()
a = P2P_GetInt()
moving = P2P_GetInt()
players[index].wx# = float(x)
players[index].wy# = float(y)
players[index].a# = float(a)
players[index].aimX# = cos(float(a))
players[index].aimY# = sin(float(a))
players[index].mov = moving
' first update?
if players[index].state = PLY_STATE_CONNECTING
players[index].state = PLY_STATE_INGAME
players[index].x# = players[index].wx#
players[index].y# = players[index].wy#
endif
dx# = players[index].wx# - players[index].x#
dy# = players[index].wy# - players[index].y#
d# = sqr(dx*dx + dy*dy)
if d >= 32.0
players[index].x# = 0.5*(players[index].x# + players[index].wx#)
players[index].y# = 0.5*(players[index].y# + players[index].wy#)
endif
elseif pkg.id = MSG_PLAYER_SHOOT
' index, type, angle
index = PlayerIndex(pkg.usr)
_P2P_Decompose pkg.msg$
bulletIndex = P2P_GetInt()
playerBullets[index][bulletIndex].type = P2P_GetInt()
a = P2P_GetInt()
dx# = cos(float(a))
dy# = sin(float(a))
playerBullets[index][bulletIndex].x# = players[index].wx# + dx*16.0
playerBullets[index][bulletIndex].y# = players[index].wy# + dy*16.0
playerBullets[index][bulletIndex].dx# = dx
playerBullets[index][bulletIndex].dy# = dy
elseif pkg.id = MSG_PLAYER_BULLET_DIED
index = PlayerIndex(pkg.usr)
_P2P_Decompose pkg.msg$
bulletIndex = P2P_GetInt()
playerBullets[index][bulletIndex].type = 0
elseif pkg.id = MSG_PLAYER_GRENADE
index = PlayerIndex(pkg.usr)
_P2P_Decompose pkg.msg$
grenadeIndex = P2P_GetInt()
playerGrenades[index][grenadeIndex].type = P2P_GetInt()
dstX# = P2P_GetFloat()
dstY# = P2P_GetFloat()
playerGrenades[index][grenadeIndex].sx# = players[index].x#
playerGrenades[index][grenadeIndex].sy# = players[index].y#
playerGrenades[index][grenadeIndex].dx# = dstX
playerGrenades[index][grenadeIndex].dy# = dstY
playerGrenades[index][grenadeIndex].p# = 0.0
dx# = playerGrenades[index][grenadeIndex].dx# - playerGrenades[index][grenadeIndex].sx#
dy# = playerGrenades[index][grenadeIndex].dy# - playerGrenades[index][grenadeIndex].sy#
d# = sqr(dx*dx + dy*dy)
playerGrenades[index][grenadeIndex].spd# = 4.0/d
endif
wend
endproc

' ------------------------------------------------------------------------------
' clear players.
' ------------------------------------------------------------------------------
procedure ClearPlayers()
for i = 0 to 3
players[i].type = PLY_NONE
for j = 0 to PLY_MAX_BULLETS - 1
playerBullets[i][j].type = 0
next
for j = 0 to PLY_MAX_GRENADES - 1
playerGrenades[i][j].type = 0
next
next
endproc

' ------------------------------------------------------------------------------
' add player.
' ------------------------------------------------------------------------------
procedure AddPlayer(id, name$)
for i = 0 to 3
if players[i].type = PLY_NONE then break
next
if i > 3
_Error "No player slot available"
endif
players[i].type = PLY_REMOTE
players[i].state = PLY_STATE_CONNECTING
players[i].id = id
players[i].name$ = name
endproc

' ------------------------------------------------------------------------------
' server full.
' ------------------------------------------------------------------------------
function ServerFull()
for i = 0 to 3
if players[i].type = PLY_NONE then return false
next
return true
endfunc

' ------------------------------------------------------------------------------
' get index in 'players' array for player id.
' ------------------------------------------------------------------------------
function PlayerIndex(id)
for i = 0 to 3
if players[i].type and players[i].id = id then return i
next
_Error "Player not found"
endfunc

' ------------------------------------------------------------------------------
' update player.
' ------------------------------------------------------------------------------
procedure UpdatePlayer(&ply?)
if ply.type = PLY_THIS
_UpdateThisPlayer ply
elseif ply.type = PLY_REMOTE
_UpdateRemotePlayer ply
elseif ply.type = PLY_BOT
_UpdateBotPlayer ply
endif
endproc

' ------------------------------------------------------------------------------
' update this player.
' ------------------------------------------------------------------------------
procedure UpdateThisPlayer(&ply?)
' aim.
mx# = float(mousex())
my# = float(mousey())
dx# = mx - ply.x#
dy# = my - ply.y#
k# = 1.0/sqr(dx*dx + dy*dy)
ply.aimX# = k*dx
ply.aimY# = k*dy
ply.a# = atan2(ply.aimY#, ply.aimX#)

' movement.
dxi = 0
dyi = 0
moving = false
if keydown(VK_LEFT) or keydown("A") then dxi = dxi - 1
if keydown(VK_RIGHT) or keydown("D") then dxi = dxi + 1
if keydown(VK_UP) or keydown("W") then dyi = dyi - 1
if keydown(VK_DOWN) or keydown("S") then dyi = dyi + 1
if dxi or dyi
dx# = float(dxi)
dy# = float(dyi)
k# = 1.0/sqr(dx*dx + dy*dy)
ply.dx# = k*dx
ply.dy# = k*dy
moving = true
else
ply.dx# = 0.0
ply.dy# = 0.0
endif
ply.x# = ply.x# + ply.dx#*ply.spd#
ply.y# = ply.y# + ply.dy#*ply.spd#

' environment collision (will use Tilemap lib later on).
if ply.x# < 0.0 then ply.x# = 0.0
if ply.x# > float(width(primary)) then ply.x# = float(width(primary))
if ply.y# < 0.0 then ply.y# = 0.0
if ply.y# > float(height(primary)) then ply.y# = float(height(primary))

if ply.ticks = 0
_TransmitPlayerPosition ply, moving
endif
ply.ticks = (ply.ticks + 1)%TSMT_PLY_POS_TICKS

' shooting.
if mousebutton(0, true)
for i = 0 to PLY_MAX_BULLETS - 1
if playerBullets[0][i].type = 0 then break
next
if i < PLY_MAX_BULLETS
playerBullets[0][i].type = 1
playerBullets[0][i].x# = ply.x# + ply.aimX#*16.0
playerBullets[0][i].y# = ply.y# + ply.aimY#*16.0
playerBullets[0][i].dx# = ply.aimX#
playerBullets[0][i].dy# = ply.aimY#

_P2P_ComposePackage MSG_PLAYER_SHOOT
_P2P_AddInt i
_P2P_AddInt playerBullets[0][i].type
_P2P_AddInt int(players[0].a#)
_P2P_SendComposed
endif
endif
' throw grenade.
if mousebutton(1, true)
for i = 0 to PLY_MAX_GRENADES - 1
if playerGrenades[0][i].type = 0 then break
next
if i < PLY_MAX_GRENADES
playerGrenades[0][i].type = 1
playerGrenades[0][i].sx# = ply.x#
playerGrenades[0][i].sy# = ply.y#
playerGrenades[0][i].dx# = float(mousex())
playerGrenades[0][i].dy# = float(mousey())
playerGrenades[0][i].p# = 0.0

dx# = playerGrenades[0][i].dx# - playerGrenades[0][i].sx#
dy# = playerGrenades[0][i].dy# - playerGrenades[0][i].sy#
d# = sqr(dx*dx + dy*dy)
playerGrenades[0][i].spd# = 4.0/d

_P2P_ComposePackage MSG_PLAYER_GRENADE
_P2P_AddInt i
_P2P_AddInt playerGrenades[0][i].type
_P2P_AddInt int(playerGrenades[0][i].dx#)
_P2P_AddInt int(playerGrenades[0][i].dy#)
_P2P_SendComposed
endif
endif
endproc

' ------------------------------------------------------------------------------
' update remote player.
' ------------------------------------------------------------------------------
procedure UpdateRemotePlayer(&ply?)
' must be ingame for updating.
if ply.state <> PLY_STATE_INGAME then return

dx# = ply.wx# - ply.x#
dy# = ply.wy# - ply.y#
d# = sqr(dx*dx + dy*dy)
if d > 0.0
k# = 1.0/d
ply.x# = ply.x# + dx*k*min#(d, 2.0)
ply.y# = ply.y# + dy*k*min#(d, 2.0)
if ply.mov
ply.wx# = ply.wx# + dx*k*2.0
ply.wy# = ply.wy# + dy*k*2.0
endif
endif

' environment collision (will use Tilemap lib later on).
if ply.x# < 0.0 then ply.x# = 0.0
if ply.x# > float(width(primary)) then ply.x# = float(width(primary))
if ply.y# < 0.0 then ply.y# = 0.0
if ply.y# > float(height(primary)) then ply.y# = float(height(primary))
endproc

' ------------------------------------------------------------------------------
' update bot player.
' ------------------------------------------------------------------------------
procedure UpdateBotPlayer(&ply?)
endproc

' ------------------------------------------------------------------------------
' transmit player position.
' ------------------------------------------------------------------------------
procedure TransmitPlayerPosition(&ply?, moving)
_P2P_ComposePackage MSG_PLAYER_POS
_P2P_AddInt int(ply.x#)
_P2P_AddInt int(ply.y#)
_P2P_AddInt int(ply.a#)
_P2P_AddInt moving
_P2P_SendComposed
endproc

' ------------------------------------------------------------------------------
' update player bullets.
' ------------------------------------------------------------------------------
procedure UpdatePlayerBullets()
for i = 0 to 3
' this player's bullets.
if i = 0
for j = 0 to PLY_MAX_BULLETS - 1
_UpdatePlayerBullet playerBullets[i][j], j
next
' remote bullets.
else
for j = 0 to PLY_MAX_BULLETS - 1
_UpdateRemotePlayerBullet playerBullets[i][j]
next
endif
next
endproc

' ------------------------------------------------------------------------------
' update grenades.
' ------------------------------------------------------------------------------
procedure UpdatePlayerGrenades()
for i = 0 to 3
for j = 0 to PLY_MAX_GRENADES - 1
if playerGrenades[i][j].type
_UpdateGrenade playerGrenades[i][j]
if i = 0 and playerGrenades[i][j].type = 0
' handle enemy collisions.
endif
endif
next
next
endproc

' ------------------------------------------------------------------------------
' update grenade.
' ------------------------------------------------------------------------------
procedure UpdateGrenade(&g?)
g.p# = g.p# + g.spd#
if g.p# >= 1.0
g.type = 0
endif
endproc

' ------------------------------------------------------------------------------
' update player bullet.
' ------------------------------------------------------------------------------
procedure UpdatePlayerBullet(&b?, index)
if b.type = 0 then return

b.x# = b.x# + b.dx#*8.0
b.y# = b.y# + b.dy#*8.0
if b.x# < 0.0 or b.x# >= float(width(primary)) or b.y# < 0.0 or b.y# >= float(height(primary))
b.type = 0
_P2P_ComposePackage MSG_PLAYER_BULLET_DIED
_P2P_AddInt index
_P2P_SendComposed
endif
endproc

' ------------------------------------------------------------------------------
' update remote player bullet.
' ------------------------------------------------------------------------------
procedure UpdateRemotePlayerBullet(&b?)
if b.type = 0 then return

b.x# = b.x# + b.dx#*8.0
b.y# = b.y# + b.dy#*8.0
endproc

' ------------------------------------------------------------------------------
' draw player.
' ------------------------------------------------------------------------------
procedure DrawPlayer(&ply?)
set color 255, 255, 255
x = int(ply.x#)
y = int(ply.y#)
draw rect x - 8, y - 8, 16, 16, true
draw line x, y, x + int(32.0*ply.aimX#), y + int(32.0*ply.aimY#)

set caret x, y - 32
center ply.name$
endproc

' ------------------------------------------------------------------------------
' clear log.
' ------------------------------------------------------------------------------
procedure ClearLog()
for i = 0 to MAX_LOG_MESSAGES - 1
vLog[i].type = 0
next
endproc

' ------------------------------------------------------------------------------
' add log message.
' ------------------------------------------------------------------------------
procedure Log(msg$, type)
for i = 0 to MAX_LOG_MESSAGES - 2
vLog[i] = vLog[i + 1]
next
vLog[MAX_LOG_MESSAGES - 1].txt$ = msg
vLog[MAX_LOG_MESSAGES - 1].type = type
vLog[MAX_LOG_MESSAGES - 1].a = 1024
endproc

' ------------------------------------------------------------------------------
' update log.
' ------------------------------------------------------------------------------
procedure UpdateLog()
for i = 0 to MAX_LOG_MESSAGES - 1
if vLog[i].type
vLog[i].a = vLog[i].a - 4
if vLog[i].a <= 0 then vLog[i].type = 0
endif
next

endproc

' ------------------------------------------------------------------------------
' draw log.
' ------------------------------------------------------------------------------
procedure DrawLog()
set caret 4, height(primary) - 4 - fheight(0)*MAX_LOG_MESSAGES
for i = 0 to MAX_LOG_MESSAGES - 1
if vLog[i].type
if vLog[i].type = LOG_CHAT;     set color 255, 255, 255, vLog[i].a
elseif vLog[i].type = LOG_INFO; set color 200, 200, 200, vLog[i].a
elseif vLog[i].type = LOG_GOOD; set color 0, 255, 0, vLog[i].a
elseif vLog[i].type = LOG_BAD;  set color 255, 0, 0, vLog[i].a
endif
wln vLog[i].txt$
else
wln
endif
next
endproc

' ------------------------------------------------------------------------------
' show error message and quit.
' ------------------------------------------------------------------------------
procedure Error(msg$)
set font 0
set color 0, 0, 0
cls
set color 255, 255, 255
set caret 0, 0
wln "ERROR: ", msg
redraw
wait 500
wait keydown
end
endproc
.\\\opz

Mr Nutz

  • Newbie
  • *
  • Posts: 10
    • View Profile
Re: Some basic p2p stuff
« Reply #1 on: March 02, 2017 »
Very good example for multiplayer games.
Just a question : the p2p library manage the lag compensation?

Marcus

  • Administrator
  • Hero Member
  • *****
  • Posts: 542
    • View Profile
Re: Some basic p2p stuff
« Reply #2 on: March 06, 2017 »
Nope, since any needed compensation depends on what type of game you're making the library can't make any assumptions on its own.

Still working on this game, but currently there's not much to see.
.\\\opz