@@ -23,8 +23,8 @@ import { WebSocket } from "uWebSockets.js";
2323import Reader from "./Coder/Reader" ;
2424import Writer from "./Coder/Writer" ;
2525import GameServer from "./Game" ;
26- import ClientCamera from "./Native/Camera" ;
27- import { ArenaState } from "./Native/Arena" ;
26+ import { CameraEntity } from "./Native/Camera" ;
27+
2828import ObjectEntity from "./Entity/Object" ;
2929import TankDefinitions , { getTankById , TankCount } from "./Const/TankDefinitions" ;
3030import DevTankDefinitions , { DevTank } from "./Const/DevTankDefinitions" ;
@@ -80,6 +80,7 @@ export interface ClientWrapper {
8080 client : Client | null ;
8181 ipAddress : string ;
8282 gamemode : string ;
83+ reconnectionKey : string | null ;
8384}
8485
8586export default class Client {
@@ -102,7 +103,10 @@ export default class Client {
102103 /** Inner websocket connection. */
103104 public ws : WebSocket < ClientWrapper > | null ;
104105 /** Client's camera entity. */
105- public camera : ClientCamera | null = null ;
106+ public camera : CameraEntity | null = null ;
107+
108+ /** Client's reconnection key for reclaiming cameras. */
109+ public reconnectionKey : string | null = null ;
106110
107111 /** Whether or not the player has used in game dev cheats before (such as level up or godmode). */
108112 private devCheatsUsed : boolean = false ;
@@ -119,14 +123,48 @@ export default class Client {
119123 this . game . clients . add ( this ) ;
120124 this . ws = ws ;
121125 this . lastPingTick = this . connectTick = game . tick ;
126+ this . reconnectionKey = ws . getUserData ( ) . reconnectionKey ;
122127 }
123128
124129 /** Accepts the client and creates a camera for it. */
125130 public acceptClient ( ) {
126131 this . write ( ) . u8 ( ClientBound . ServerInfo ) . stringNT ( this . game . gamemode ) . stringNT ( config . host ) . send ( ) ;
127132 this . write ( ) . u8 ( ClientBound . PlayerCount ) . vu ( GameServer . globalPlayerCount ) . send ( ) ;
128133 this . write ( ) . u8 ( ClientBound . Accept ) . vi ( this . accessLevel ) . send ( ) ;
129- this . camera = new ClientCamera ( this . game , this ) ;
134+
135+ // Check for reconnection
136+ const didRestoreCamera = this . tryReconnection ( ) ;
137+ if ( ! didRestoreCamera ) {
138+ // If reconnection failed, create a new camera
139+ this . camera = new CameraEntity ( this . game ) ;
140+ this . camera . setClient ( this ) ;
141+ }
142+ }
143+
144+ private tryReconnection ( ) : boolean {
145+ if ( ! config . enableReconnection ) return false ;
146+ const reconnectionKey = this . reconnectionKey ;
147+ if ( ! reconnectionKey ) return false ;
148+
149+ for ( const id of this . game . entities . cameras ) {
150+ const camera = this . game . entities . inner [ id ] as CameraEntity ;
151+ if ( ! camera ) continue ;
152+
153+ if ( camera . reconnectionKey === reconnectionKey ) {
154+ if ( camera . getClient ( ) ) {
155+ // Already claimed, so our reconnection key itself is a duplicate
156+ this . reconnectionKey = null ;
157+ return false ;
158+ }
159+
160+ camera . setClient ( this ) ;
161+ this . camera = camera ;
162+
163+ return true ;
164+ }
165+ }
166+
167+ return false ;
130168 }
131169
132170 /** Sends data to client. */
@@ -154,8 +192,14 @@ export default class Client {
154192
155193 this . inputs . deleted = true ;
156194 this . inputs . movement . magnitude = 0 ;
157-
158- if ( Entity . exists ( this . camera ) ) this . camera . delete ( ) ;
195+ if ( Entity . exists ( this . camera ) ) {
196+ if ( config . enableReconnection && this . reconnectionKey ) {
197+ // Mark camera for reconnection instead of deleting
198+ this . camera . markForReconnection ( ) ;
199+ } else {
200+ this . camera . delete ( ) ;
201+ }
202+ }
159203 }
160204
161205 /** Handles to incoming messages. */
0 commit comments