1 #include "player_remote.h"
3 #include "player_render.h"
4 #include "network_common.h"
8 #include "ent_miniworld.h"
10 static i32 k_show_own_name
= 0;
12 static void player_remote_clear( struct network_player
*player
){
13 addon_cache_unwatch( k_addon_type_player
, player
->playermodel_view_slot
);
14 addon_cache_unwatch( k_addon_type_board
, player
->board_view_slot
);
16 memset( player
, 0, sizeof(*player
) );
17 strcpy( player
->username
, "unknown" );
18 player
->subsystem
= k_player_subsystem_invalid
;
22 * re-attatches addon_reg pointers on the remote client if we have the same
25 static void relink_remote_player_worlds( u32 client_id
){
26 struct network_player
*player
= &netplayers
.list
[client_id
];
29 addon_uid_to_alias( player
->items
[k_netmsg_playeritem_world0
], &q
[0] );
30 addon_uid_to_alias( player
->items
[k_netmsg_playeritem_world1
], &q
[1] );
33 * currently in 10.23, the hub world will always be the same.
34 * this might but probably wont change in the future
36 for( u32 i
=0; i
<k_world_max
; i
++ ){
37 addon_reg
*reg
= world_static
.instance_addons
[ i
];
39 player
->world_match
[i
] = 0;
42 if( addon_alias_eq( &q
[i
], &world_static
.instance_addons
[i
]->alias
) )
43 player
->world_match
[i
] = 1;
49 * re-attatches addon_reg pointers on the remote client if we have the mod
52 * Run if local worlds change
54 static void relink_all_remote_player_worlds(void){
55 for( u32 i
=0; i
<vg_list_size(netplayers
.list
); i
++ ){
56 struct network_player
*player
= &netplayers
.list
[i
];
58 relink_remote_player_worlds(i
);
62 static void player_remote_update_friendflags( struct network_player
*remote
){
63 ISteamFriends
*hSteamFriends
= SteamAPI_SteamFriends();
64 remote
->isfriend
= SteamAPI_ISteamFriends_HasFriend( hSteamFriends
,
65 remote
->steamid
, k_EFriendFlagImmediate
);
66 remote
->isblocked
= SteamAPI_ISteamFriends_HasFriend( hSteamFriends
,
67 remote
->steamid
, k_EFriendFlagBlocked
);
70 static void player_remote_rx_200_300( SteamNetworkingMessage_t
*msg
){
71 netmsg_blank
*tmp
= msg
->m_pData
;
73 if( tmp
->inetmsg_id
== k_inetmsg_playerjoin
){
74 netmsg_playerjoin
*playerjoin
= msg
->m_pData
;
75 if( !packet_minsize( msg
, sizeof(*playerjoin
) )) return;
77 if( playerjoin
->index
< vg_list_size(netplayers
.list
) ){
78 struct network_player
*player
= &netplayers
.list
[ playerjoin
->index
];
79 player_remote_clear( player
);
81 player
->steamid
= playerjoin
->steamid
;
82 player_remote_update_friendflags( player
);
84 /* TODO: interpret the uids */
85 player
->board_view_slot
= 0;
86 player
->playermodel_view_slot
= 0;
88 struct interp_buffer
*buf
= &netplayers
.interp_data
[playerjoin
->index
];
90 for( u32 i
=0; i
<vg_list_size(buf
->frames
); i
++ ){
91 buf
->frames
[i
].active
= 0;
94 vg_info( "#%u joined friend: %d, blocked: %d\n",
95 playerjoin
->index
, player
->isfriend
, player
->isblocked
);
98 vg_error( "inetmsg_playerjoin: player index out of range\n" );
101 else if( tmp
->inetmsg_id
== k_inetmsg_playerleave
){
102 netmsg_playerleave
*playerleave
= msg
->m_pData
;
103 if( !packet_minsize( msg
, sizeof(*playerleave
) )) return;
105 if( playerleave
->index
< vg_list_size(netplayers
.list
) ){
106 struct network_player
*player
= &netplayers
.list
[ playerleave
->index
];
107 player_remote_clear( player
);
109 vg_info( "player leave (%d)\n", playerleave
->index
);
112 vg_error( "inetmsg_playerleave: player index out of range\n" );
115 else if( tmp
->inetmsg_id
== k_inetmsg_playerusername
){
116 netmsg_playerusername
*update
= msg
->m_pData
;
117 if( !packet_minsize( msg
, sizeof(*update
) )) return;
119 if( update
->index
< vg_list_size(netplayers
.list
) ){
120 struct network_player
*player
= &netplayers
.list
[ update
->index
];
122 network_msgstring( update
->name
, msg
->m_cbSize
, sizeof(*update
),
123 player
->username
, sizeof(player
->username
) );
125 vg_info( "#%u changed username to: %s\n",
126 update
->index
, player
->username
);
129 vg_error( "inetmsg_playerleave: player index out of range\n" );
132 else if( tmp
->inetmsg_id
== k_inetmsg_playerframe
){
133 u32 datasize
= msg
->m_cbSize
- sizeof(netmsg_playerframe
);
135 if( datasize
> sizeof(union interp_animdata
) ){
136 vg_error( "Player frame data exceeds animdata size\n" );
140 netmsg_playerframe
*frame
= msg
->m_pData
;
141 if( frame
->client
>= vg_list_size(netplayers
.list
) ){
142 vg_error( "inetmsg_playerframe: player index out of range\n" );
146 if( frame
->subsystem
>= k_player_subsystem_max
){
147 vg_error( "inetmsg_playerframe: subsystem out of range\n" );
151 struct interp_buffer
*ib
= &netplayers
.interp_data
[ frame
->client
];
152 struct interp_frame
*dest
= NULL
;
154 f64 min_time
= INFINITY
;
155 for( u32 i
=0; i
<vg_list_size(ib
->frames
); i
++ ){
156 struct interp_frame
*ifr
= &ib
->frames
[i
];
163 if( ifr
->timestamp
< min_time
){
164 min_time
= ifr
->timestamp
;
170 dest
->subsystem
= frame
->subsystem
;
171 dest
->instance_id
= frame
->instance_id
;
174 .mode
= k_bitpack_decompress
,
175 .buffer
= frame
->animdata
,
176 .buffer_len
= datasize
,
181 * -------------------------------------------------------------*/
183 dest
->timestamp
= frame
->timestamp
;
184 dest
->boundary_hash
= frame
->boundary_hash
;
186 struct network_player
*player
= &netplayers
.list
[ frame
->client
];
187 struct player_subsystem_interface
*sys
=
188 player_subsystems
[ frame
->subsystem
];
190 if( sys
->network_animator_exchange
){
191 memset( &dest
->data
, 0, sys
->animator_size
);
192 sys
->network_animator_exchange( &ctx
, &dest
->data
);
195 bitpack_bytes( &ctx
, sys
->animator_size
, sys
->animator_data
);
199 * -------------------------------------------------------------*/
201 for( u32 i
=0; i
<frame
->sound_effects
; i
++ ){
203 net_sfx_exchange( &ctx
, &sfx
);
205 f64 t
= (frame
->timestamp
- NETWORK_FRAMERATE
) +
206 (sfx
.subframe
*NETWORK_FRAMERATE
);
208 f32 remaining
= t
- ib
->t
;
210 if( remaining
<= 0.0f
)
211 net_sfx_play( &sfx
);
213 struct net_sfx
*dst
= NULL
;
215 for( u32 j
=0; j
<NETWORK_SFX_QUEUE_LENGTH
; j
++ ){
216 struct net_sfx
*sj
= &netplayers
.sfx_queue
[j
];
217 if( sj
->system
== k_player_subsystem_invalid
){
222 if( sj
->priority
< sfx
.priority
)
227 dst
->subframe
= remaining
;
231 player
->subsystem
= frame
->subsystem
;
232 player
->down_bytes
+= msg
->m_cbSize
;
234 else if( tmp
->inetmsg_id
== k_inetmsg_playeritem
){
235 netmsg_playeritem
*item
= msg
->m_pData
;
236 if( !packet_minsize( msg
, sizeof(*item
)+1 )) return;
238 if( item
->client
>= vg_list_size(netplayers
.list
) ){
239 vg_error( "inetmsg_playerframe: player index out of range\n" );
243 if( item
->type_index
>= k_netmsg_playeritem_max
){
244 vg_warn( "Client #%d invalid equip type %u\n",
245 (i32
)item
->client
, (u32
)item
->type_index
);
249 vg_info( "Client #%d equiped: [%s] %s\n",
251 (const char *[]){[k_netmsg_playeritem_board
]="board",
252 [k_netmsg_playeritem_player
]="player",
253 [k_netmsg_playeritem_world0
]="world0",
254 [k_netmsg_playeritem_world1
]="world1"
255 }[item
->type_index
], item
->uid
);
257 struct network_player
*player
= &netplayers
.list
[ item
->client
];
258 char *uid
= player
->items
[ item
->type_index
];
260 network_msgstring( item
->uid
, msg
->m_cbSize
, sizeof(*item
),
261 uid
, ADDON_UID_MAX
);
263 if( item
->type_index
== k_netmsg_playeritem_board
){
264 addon_cache_unwatch( k_addon_type_board
, player
->board_view_slot
);
265 player
->board_view_slot
=
266 addon_cache_create_viewer_from_uid( k_addon_type_board
, uid
);
268 else if( item
->type_index
== k_netmsg_playeritem_player
){
269 addon_cache_unwatch( k_addon_type_player
,
270 player
->playermodel_view_slot
);
271 player
->playermodel_view_slot
=
272 addon_cache_create_viewer_from_uid( k_addon_type_player
, uid
);
274 else if( (item
->type_index
== k_netmsg_playeritem_world0
) ||
275 (item
->type_index
== k_netmsg_playeritem_world1
) ){
276 relink_remote_player_worlds( item
->client
);
279 else if( tmp
->inetmsg_id
== k_inetmsg_chat
){
280 netmsg_chat
*chat
= msg
->m_pData
;
282 struct network_player
*player
= &netplayers
.list
[ chat
->client
];
283 network_msgstring( chat
->msg
, msg
->m_cbSize
, sizeof(netmsg_chat
),
284 player
->chat
, NETWORK_MAX_CHAT
);
285 player
->chat_time
= vg
.time_real
;
286 vg_info( "[%d]: %s\n", chat
->client
, player
->chat
);
291 * Write localplayer pose to network
293 static void remote_player_send_playerframe(void){
294 u8 sysid
= localplayer
.subsystem
;
295 if( sysid
>= k_player_subsystem_max
) return;
297 struct player_subsystem_interface
*sys
= player_subsystems
[sysid
];
299 if( sys
->animator_size
){
300 u32 max_buf_size
= sys
->animator_size
+ sizeof(localplayer
.sfx_buffer
),
301 base_size
= sizeof(struct netmsg_playerframe
),
302 max_packet
= base_size
+ max_buf_size
;
304 netmsg_playerframe
*frame
= alloca( max_packet
);
305 frame
->inetmsg_id
= k_inetmsg_playerframe
;
306 frame
->client
= 0xff;
307 frame
->subsystem
= localplayer
.subsystem
;
308 frame
->instance_id
= world_static
.active_instance
;
311 .mode
= k_bitpack_compress
,
312 .buffer
= frame
->animdata
,
313 .buffer_len
= max_buf_size
,
318 * -----------------------------------------------*/
320 frame
->timestamp
= vg
.time_real
;
321 frame
->boundary_hash
= localplayer
.boundary_hash
;
322 if( sys
->network_animator_exchange
)
323 sys
->network_animator_exchange( &ctx
, sys
->animator_data
);
325 bitpack_bytes( &ctx
, sys
->animator_size
, sys
->animator_data
);
328 * ---------------------------------------------*/
330 frame
->sound_effects
= localplayer
.sfx_buffer_count
;
331 for( u32 i
=0; i
<localplayer
.sfx_buffer_count
; i
++ )
332 net_sfx_exchange( &ctx
, &localplayer
.sfx_buffer
[i
] );
334 u32 wire_size
= base_size
+ ctx
.bytes
;
335 netplayers
.up_bytes
+= wire_size
;
337 SteamAPI_ISteamNetworkingSockets_SendMessageToConnection(
338 hSteamNetworkingSockets
, network_client
.remote
,
340 k_nSteamNetworkingSend_Unreliable
, NULL
);
345 * Updates network traffic stats
347 static void remote_player_debug_update(void){
348 if( (vg
.time_real
- netplayers
.last_data_measurement
) > 1.0 ){
349 netplayers
.last_data_measurement
= vg
.time_real
;
352 for( u32 i
=0; i
<vg_list_size(netplayers
.list
); i
++ ){
353 struct network_player
*player
= &netplayers
.list
[i
];
354 if( player
->active
){
355 total_down
+= player
->down_bytes
;
356 player
->down_kbs
= ((f32
)player
->down_bytes
)/1024.0f
;
357 player
->down_bytes
= 0;
361 netplayers
.down_kbs
= ((f32
)total_down
)/1024.0f
;
362 netplayers
.up_kbs
= ((f32
)netplayers
.up_bytes
)/1024.0f
;
363 netplayers
.up_bytes
= 0;
368 * Debugging information
370 static void remote_player_network_imgui( m4x4f pv
){
371 if( network_client
.user_intent
== k_server_intent_online
){
373 (network_client
.state
== k_ESteamNetworkingConnectionState_Connected
)))
377 vg_strnull( &str
, buf
, sizeof(buf
) );
379 network_status_string( &str
, &fg
);
380 ui_text( (ui_rect
){ vg
.window_x
- 200, 0, 200, 48 }, buf
, 1,
381 k_ui_align_middle_center
, fg
);
385 if( !network_client
.network_info
)
388 ui_rect panel
= { (vg
.window_x
/ 2) - 200, 0, 400, 600 };
389 ui_fill( panel
, (ui_colour(k_ui_bg
)&0x00ffffff)|0x50000000 );
392 const char *netstatus
= "PROGRAMMING ERROR";
394 struct { enum ESteamNetworkingConnectionState state
; const char *str
; }
396 { k_ESteamNetworkingConnectionState_None
, "None" },
397 { k_ESteamNetworkingConnectionState_Connecting
,
398 (const char *[]){"Connecting -",
402 }[(u32
)(vg
.time_real
/0.25) & 0x3 ] },
403 { k_ESteamNetworkingConnectionState_FindingRoute
, "Finding Route" },
404 { k_ESteamNetworkingConnectionState_Connected
, "Connected" },
405 { k_ESteamNetworkingConnectionState_ClosedByPeer
, "Closed by peer" },
406 { k_ESteamNetworkingConnectionState_ProblemDetectedLocally
,
407 "Problem Detected Locally" },
408 { k_ESteamNetworkingConnectionState_FinWait
, "Fin Wait" },
409 { k_ESteamNetworkingConnectionState_Linger
, "Linger" },
410 { k_ESteamNetworkingConnectionState_Dead
, "Dead" }
412 for( u32 i
=0; i
<vg_list_size(states
); i
++ ){
413 if( states
[i
].state
== network_client
.state
){
414 netstatus
= states
[i
].str
;
418 snprintf( buf
, 512, "Network: %s", netstatus
);
419 ui_info( panel
, buf
);
420 ui_info( panel
, "---------------------" );
422 if( network_client
.state
== k_ESteamNetworkingConnectionState_Connected
){
423 ui_info( panel
, "#-1: localplayer" );
425 snprintf( buf
, 512, "U%.3f/D%.3fkbs",
426 netplayers
.up_kbs
, netplayers
.down_kbs
);
427 ui_info( panel
, buf
);
429 for( u32 i
=0; i
<vg_list_size(netplayers
.list
); i
++ ){
430 struct network_player
*player
= &netplayers
.list
[i
];
431 if( player
->active
){
432 const char *sysname
= "invalid";
434 if( player
->subsystem
< k_player_subsystem_max
){
435 sysname
= player_subsystems
[ player
->subsystem
]->name
;
437 snprintf( buf
, 512, "#%u: %s [%s] D%.1fkbs",
438 i
, player
->username
, sysname
, player
->down_kbs
);
439 ui_info( panel
, buf
);
444 ui_info( panel
, "offline" );
449 * write the remote players final_mtx
451 static void pose_remote_player( u32 index
,
452 struct interp_frame
*f0
,
453 struct interp_frame
*f1
){
455 struct network_player
*player
= &netplayers
.list
[ index
];
457 struct interp_buffer
*buf
= &netplayers
.interp_data
[ index
];
458 struct skeleton
*sk
= &localplayer
.skeleton
;
459 m4x3f
*final_mtx
= &netplayers
.final_mtx
[ sk
->bone_count
*index
];
460 struct player_board_pose
*board_pose
= &netplayers
.board_poses
[index
];
462 struct player_subsystem_interface
*sys0
= player_subsystems
[f0
->subsystem
],
465 player_pose pose0
, pose1
, posed
;
466 sys0
->pose( &f0
->data
, &pose0
);
471 f32 t
= (buf
->t
- f0
->timestamp
) / (f1
->timestamp
- f0
->timestamp
);
472 t
= vg_clampf( t
, 0.0f
, 1.0f
);
474 sys1
= player_subsystems
[f1
->subsystem
];
475 sys1
->pose( &f1
->data
, &pose1
);
477 u16 bounds
= f0
->boundary_hash
^f1
->boundary_hash
;
479 if( bounds
& NETMSG_BOUNDARY_BIT
)
482 if( bounds
& NETMSG_GATE_BOUNDARY_BIT
){
483 /* TODO: Extra work retransforming the root_co, instance_id.. etc */
487 instance_id
= f1
->instance_id
;
489 lerp_player_pose( &pose0
, &pose1
, t
, &posed
);
490 apply_full_skeleton_pose( sk
, &posed
, final_mtx
);
491 memcpy( board_pose
, &posed
.board
, sizeof(*board_pose
) );
494 instance_id
= f0
->instance_id
;
496 apply_full_skeleton_pose( sk
, &pose0
, final_mtx
);
497 memcpy( board_pose
, &pose0
.board
, sizeof(*board_pose
) );
500 if( player
->world_match
[ instance_id
] )
501 player
->active_world
= &world_static
.instances
[ instance_id
];
505 * animate remote player and store in final_mtx
507 static void animate_remote_player( u32 index
){
510 * Trys to keep the cursor inside the buffer
512 f64 min_time
= -999999999.9,
513 max_time
= 999999999.9,
514 abs_max_time
= -999999999.9;
516 struct interp_frame
*minframe
= NULL
,
518 *abs_max_frame
= NULL
;
520 struct interp_buffer
*buf
= &netplayers
.interp_data
[index
];
521 for( u32 i
=0; i
<vg_list_size(buf
->frames
); i
++ ){
522 struct interp_frame
*ifr
= &buf
->frames
[i
];
525 if( (ifr
->timestamp
> min_time
) && (ifr
->timestamp
< buf
->t
) ){
526 min_time
= ifr
->timestamp
;
530 if( (ifr
->timestamp
< max_time
) && (ifr
->timestamp
> buf
->t
) ){
531 max_time
= ifr
->timestamp
;
535 if( ifr
->timestamp
> abs_max_time
){
536 abs_max_time
= ifr
->timestamp
;
542 struct network_player
*player
= &netplayers
.list
[ index
];
543 if( player
->active
!= 2 )
544 player
->active_world
= NULL
;
546 if( minframe
&& maxframe
){
547 pose_remote_player( index
, minframe
, maxframe
);
548 buf
->t
+= vg
.time_frame_delta
;
551 buf
->t
= abs_max_time
- 0.25;
554 pose_remote_player( index
, abs_max_frame
, NULL
);
561 * Update full final_mtx for all remote players
563 static void animate_remote_players(void){
564 for( u32 i
=0; i
<vg_list_size(netplayers
.list
); i
++ ){
565 struct network_player
*player
= &netplayers
.list
[i
];
566 if( !player
->active
) continue;
568 animate_remote_player( i
);
573 * Draw remote players
575 static void render_remote_players( world_instance
*world
, camera
*cam
){
576 SDL_AtomicLock( &addon_system
.sl_cache_using_resources
);
577 struct skeleton
*sk
= &localplayer
.skeleton
;
579 for( u32 i
=0; i
<NETWORK_MAX_PLAYERS
; i
++ ){
580 struct network_player
*player
= &netplayers
.list
[i
];
581 if( !player
->active
|| player
->isblocked
) continue;
582 if( player
->active_world
!= world
) continue;
584 m4x3f
*final_mtx
= &netplayers
.final_mtx
[ sk
->bone_count
*i
];
586 struct player_model
*model
=
587 addon_cache_item_if_loaded( k_addon_type_player
,
588 player
->playermodel_view_slot
);
590 if( !model
) model
= &localplayer
.fallback_model
;
591 render_playermodel( cam
, world
, 0, model
, sk
, final_mtx
);
593 struct player_board
*board
=
594 addon_cache_item_if_loaded( k_addon_type_board
,
595 player
->board_view_slot
);
596 render_board( cam
, world
, board
, final_mtx
[localplayer
.id_board
],
597 &netplayers
.board_poses
[ i
], k_board_shader_player
);
600 SDL_AtomicUnlock( &addon_system
.sl_cache_using_resources
);
603 static int remote_players_randomize( int argc
, const char *argv
[] ){
604 for( int i
=0; i
<NETWORK_MAX_PLAYERS
; i
++ ){
605 struct network_player
*player
= &netplayers
.list
[i
];
607 player
->active
= (vg_randu32() & 0x1)? 2: 0;
608 player
->isfriend
= vg_randu32() & vg_randu32() & 0x1;
609 player
->isblocked
= vg_randu32() & vg_randu32() & vg_randu32() & 0x1;
610 player
->world_match
[ 0 ] = vg_randu32() & 0x1;
611 player
->world_match
[ 1 ] = 0;
613 if( player
->world_match
[0] )
614 player
->active_world
= &world_static
.instances
[0];
616 player
->active_world
= NULL
;
618 for( int i
=0; i
<sizeof(player
->username
)-1; i
++ ){
619 player
->username
[i
] = 'a' + (vg_randu32() % 30);
620 player
->username
[i
+1] = '\0';
622 if( (vg_randu32() % 8) == 3 )
626 for( int i
=0; i
<3; i
++ ){
627 player
->medals
[i
] = vg_randu32() % 3;
632 vg_rand_sphere( pos
);
633 v3_muladds( localplayer
.rb
.co
, pos
, 100.0f
,
634 netplayers
.final_mtx
[ i
*localplayer
.skeleton
.bone_count
][3] );
640 static void remote_players_init(void){
641 vg_console_reg_cmd( "add_test_players", remote_players_randomize
, NULL
);
642 vg_console_reg_var( "k_show_own_name", &k_show_own_name
,
643 k_var_dtype_i32
, 0 );
644 for( u32 i
=0; i
<NETWORK_SFX_QUEUE_LENGTH
; i
++ ){
645 netplayers
.sfx_queue
[i
].system
= k_player_subsystem_invalid
;
649 static void remote_sfx_pre_update(void){
650 for( u32 i
=0; i
<NETWORK_SFX_QUEUE_LENGTH
; i
++ ){
651 struct net_sfx
*si
= &netplayers
.sfx_queue
[i
];
653 if( si
->system
!= k_player_subsystem_invalid
){
654 si
->subframe
-= vg
.time_frame_delta
;
655 if( si
->subframe
<= 0.0f
){
657 si
->system
= k_player_subsystem_invalid
;
664 * animator->root_co of remote player
666 static void remote_player_position( int id
, v3f out_co
){
667 struct skeleton
*sk
= &localplayer
.skeleton
;
668 m4x3f
*final_mtx
= &netplayers
.final_mtx
[ sk
->bone_count
*id
];
669 v3_copy( final_mtx
[0][3], out_co
);
672 enum remote_player_gui_type
{
673 k_remote_player_gui_type_stranger
,
674 k_remote_player_gui_type_friend
,
675 k_remote_player_gui_type_you
,
679 * Given players' root_co, get the screen point where we should draw tag info.
681 static int player_tag_position( m4x4f pv
, v3f root_co
, ui_point out_point
){
683 v3_copy( root_co
, wpos
);
687 m4x4_mulv( pv
, wpos
, wpos
);
689 if( wpos
[3] > 0.0f
){
690 v2_muls( wpos
, (1.0f
/wpos
[3]) * 0.5f
, wpos
);
691 v2_add( wpos
, (v2f
){ 0.5f
, 0.5f
}, wpos
);
693 float k_max
= 32000.0f
;
695 out_point
[0] = vg_clampf(wpos
[0] * vg
.window_x
, -k_max
, k_max
);
696 out_point
[1] = vg_clampf((1.0f
-wpos
[1]) * vg
.window_y
, -k_max
, k_max
);
704 * Draw chat box relative to the root tag position on the screen
706 static void chat_box( ui_point tag_root
, f64 time
, const char *message
){
707 if( (vg
.time_real
- time
) > 15.0 )
711 wr
[2] = ui_text_line_width( message
) + 8;
712 wr
[3] = vg_ui
.font
->glyph_height
+ 2;
713 wr
[0] = tag_root
[0]-(wr
[2]/2);
714 wr
[1] = tag_root
[1] - wr
[3] - 8;
716 ui_fill( wr
, ui_opacity( ui_colour(k_ui_bg
), 0.23f
) );
717 ui_text( wr
, message
, 1, k_ui_align_middle_center
, 0 );
721 * Draw full imgui for remote player
723 static void remote_player_nametag( ui_point tag_root
,
724 struct network_player
*player
){
725 vg_ui
.font
= &vg_ui_font_big
;
728 wr
[2] = VG_MAX( ui_text_line_width( player
->username
), 140 ) + 8;
730 wr
[0] = tag_root
[0]-(wr
[2]/2);
731 wr
[1] = tag_root
[1]-(wr
[3]/2);
733 ui_fill( wr
, ui_opacity( ui_colour(k_ui_bg
), 0.23f
) );
734 ui_text( wr
, player
->username
, 1, k_ui_align_middle_center
, 0 );
736 vg_ui
.font
= &vg_ui_font_small
;
740 for( int i
=0; i
<3; i
++ )
741 if( player
->medals
[i
] )
748 f32 w
= (f32
)wr
[2] / (f32
)cols
;
751 for( int i
=0; i
<3; i
++ ){
752 if( player
->medals
[i
] ){
753 ui_rect col
= { wr
[0] + (f32
)cols
*w
, wr
[1] + wr
[3], w
,
754 vg_ui
.font
->glyph_height
};
756 vg_strnull( &str
, buf
, 32 );
757 vg_strcatch( &str
, (char)k_SRglyph_vg_circle
);
758 vg_strcati32( &str
, player
->medals
[i
] );
760 ui_text( col
, buf
, 1, k_ui_align_middle_center
,
761 ui_colour( (enum ui_scheme_colour
[]){
762 k_ui_yellow
, k_ui_gray
, k_ui_orange
}[i
] ) );
770 static void remote_player_world_gui( m4x4f pv
, v3f root_co
,
771 struct network_player
*player
){
773 if( !player_tag_position( pv
, root_co
, tag_root
) )
777 remote_player_nametag( tag_root
, player
);
778 chat_box( tag_root
, player
->chat_time
, player
->chat
);
781 if( netplayers
.chatting
)
782 chat_box( tag_root
, vg
.time_real
, netplayers
.chat_buffer
);
784 chat_box( tag_root
, netplayers
.chat_time
, netplayers
.chat_message
);
788 static void remote_player_gui_info( ui_rect box
,
789 const char *username
,
790 const char *activity
,
791 enum remote_player_gui_type type
,
794 f32 opacity
= in_world
? 0.6f
: 0.3f
;
796 if( type
== k_remote_player_gui_type_you
)
797 ui_fill( box
, ui_opacity( 0xff555555, opacity
) );
799 ui_fill( box
, ui_opacity( 0xff000000, opacity
) );
801 if( type
== k_remote_player_gui_type_friend
)
802 ui_outline( box
, -1, ui_opacity( 0xff00c4f0, opacity
), 0 );
805 ui_split_ratio( box
, k_ui_axis_h
, 0.6666f
, 1, top
, bottom
);
809 if( type
== k_remote_player_gui_type_friend
)
810 fg
= ui_colour( k_ui_yellow
+ (in_world
? k_ui_brighter
: 0) );
812 fg
= ui_colour( in_world
? k_ui_fg
: k_ui_fg
+4 );
814 vg_ui
.font
= &vg_ui_font_big
;
815 ui_text( top
, username
, 1, k_ui_align_middle_center
, fg
);
816 vg_ui
.font
= &vg_ui_font_small
;
818 ui_text( bottom
, activity
, 1, k_ui_align_middle_center
, fg
);
821 static void remote_players_imgui_lobby(void){
823 * TODO: send location string over the network */
825 ui_px y
= 50, width
= 200, height
= 42, gap
= 2,
826 x
= vg
.window_x
- width
;
828 vg_ui
.font
= &vg_ui_font_big
;
829 ui_text( (ui_rect
){ x
, 0, width
, height
},
830 "Online Players", 1, k_ui_align_middle_center
, 0 );
831 vg_ui
.font
= &vg_ui_font_small
;
834 ui_rect us
= { x
, y
, width
, height
};
835 /* FIXME: your location */
836 remote_player_gui_info( us
, steam_username_at_startup
, "you",
837 k_remote_player_gui_type_you
, 1 );
840 for( u32 i
=0; i
<NETWORK_MAX_PLAYERS
; i
++ ){
841 struct network_player
*player
= &netplayers
.list
[i
];
842 if( !player
->active
|| player
->isblocked
) continue;
844 int in_same_world
= player
->active_world
!= world_current_instance();
846 if( !player
->isfriend
&& !in_same_world
)
849 ui_rect box
= { x
, y
, width
, height
};
850 remote_player_gui_info( box
, player
->username
,
851 in_same_world
? "": "another world",
852 player
->isfriend
, in_same_world
);
857 static void remote_players_imgui_world( world_instance
*world
, m4x4f pv
,
858 f32 max_dist
, int geo_cull
){
859 ui_flush( k_ui_shader_colour
, vg
.window_x
, vg
.window_y
);
861 for( u32 i
=0; i
<NETWORK_MAX_PLAYERS
; i
++ ){
862 struct network_player
*player
= &netplayers
.list
[i
];
863 if( player
->active
){
865 remote_player_position( i
, co
);
867 if( !player
->active_world
)
870 /* their in our active subworld */
871 if( player
->active_world
!= world
){
872 m4x3_mulv( global_miniworld
.mmdl
, co
, co
);
873 co
[1] -= 2.0f
; /* HACK lol */
876 f32 d2
= v3_dist2( co
, localplayer
.rb
.co
);
878 if( d2
> (max_dist
*max_dist
) )
881 f32 dist
= sqrtf(d2
);
882 f32 opacity
= 0.95f
* sqrtf(((max_dist
-dist
)/max_dist
));
889 v3_sub( co
, skaterift
.cam
.pos
, dir
);
892 if( ray_world( world
, skaterift
.cam
.pos
, dir
, &hit
,
893 k_material_flag_ghosts
) ){
898 player
->opacity
= vg_lerpf( player
->opacity
, opacity
,
899 vg
.time_frame_delta
* 2.0f
);
901 remote_player_world_gui( pv
, co
, player
);
903 vg_ui
.colour
[3] = player
->opacity
;
904 ui_flush( k_ui_shader_colour
, vg
.window_x
, vg
.window_y
);
908 vg_ui
.colour
[3] = 1.0f
;
910 remote_player_world_gui( pv
, localplayer
.rb
.co
, NULL
);
911 ui_flush( k_ui_shader_colour
, vg
.window_x
, vg
.window_y
);
914 static void chat_escape(void){
915 netplayers
.chatting
= -1;
918 static void chat_enter( char *buf
, u32 len
){
919 vg_strncpy( buf
, netplayers
.chat_message
, NETWORK_MAX_CHAT
,
920 k_strncpy_always_add_null
);
921 netplayers
.chatting
= -1;
922 netplayers
.chat_time
= vg
.time_real
;
923 chat_send_message( buf
);
926 static void remote_players_chat_imgui(void){
927 if( netplayers
.chatting
== 1 ){
928 ui_rect box
= { 0, 0, 400, 40 },
929 window
= { 0, 0, vg
.window_x
, vg
.window_y
};
930 ui_rect_center( window
, box
);
932 struct ui_textbox_callbacks callbacks
= {
934 .escape
= chat_escape
937 ui_textbox( box
, NULL
,
938 netplayers
.chat_buffer
, NETWORK_MAX_CHAT
, 1,
939 UI_TEXTBOX_AUTOFOCUS
, &callbacks
);
942 if( netplayers
.chatting
== -1 ){
943 netplayers
.chatting
= 0;
944 srinput
.state
= k_input_state_resume
;
947 if( (skaterift
.activity
== k_skaterift_default
) &&
948 button_down( k_srbind_chat
) ){
949 netplayers
.chatting
= 1;
950 netplayers
.chat_buffer
[0] = '\0';
951 srinput
.state
= k_input_state_pause
;