LoadBalancingClient.cs 209 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431
  1. // -----------------------------------------------------------------------
  2. // <copyright file="LoadBalancingClient.cs" company="Exit Games GmbH">
  3. // Loadbalancing Framework for Photon - Copyright (C) 2018 Exit Games GmbH
  4. // </copyright>
  5. // <summary>
  6. // Provides the operations and a state for games using the
  7. // Photon LoadBalancing server.
  8. // </summary>
  9. // <author>developer@photonengine.com</author>
  10. // ----------------------------------------------------------------------------
  11. #if UNITY_4_7 || UNITY_5 || UNITY_5_3_OR_NEWER
  12. #define SUPPORTED_UNITY
  13. #endif
  14. namespace Photon.Realtime
  15. {
  16. using System;
  17. using System.Collections;
  18. using System.Collections.Generic;
  19. using System.Diagnostics;
  20. using ExitGames.Client.Photon;
  21. #if SUPPORTED_UNITY
  22. using UnityEngine;
  23. using Debug = UnityEngine.Debug;
  24. #endif
  25. #if SUPPORTED_UNITY || NETFX_CORE
  26. using Hashtable = ExitGames.Client.Photon.Hashtable;
  27. using SupportClass = ExitGames.Client.Photon.SupportClass;
  28. #endif
  29. #region Enums
  30. /// <summary>
  31. /// State values for a client, which handles switching Photon server types, some operations, etc.
  32. /// </summary>
  33. /// \ingroup publicApi
  34. public enum ClientState
  35. {
  36. /// <summary>Peer is created but not used yet.</summary>
  37. PeerCreated,
  38. /// <summary>Transition state while connecting to a server. On the Photon Cloud this sends the AppId and AuthenticationValues (UserID).</summary>
  39. Authenticating,
  40. /// <summary>Not Used.</summary>
  41. Authenticated,
  42. /// <summary>The client sent an OpJoinLobby and if this was done on the Master Server, it will result in. Depending on the lobby, it gets room listings.</summary>
  43. JoiningLobby,
  44. /// <summary>The client is in a lobby, connected to the MasterServer. Depending on the lobby, it gets room listings.</summary>
  45. JoinedLobby,
  46. /// <summary>Transition from MasterServer to GameServer.</summary>
  47. DisconnectingFromMasterServer,
  48. [Obsolete("Renamed to DisconnectingFromMasterServer")]
  49. DisconnectingFromMasterserver = DisconnectingFromMasterServer,
  50. /// <summary>Transition to GameServer (client authenticates and joins/creates a room).</summary>
  51. ConnectingToGameServer,
  52. [Obsolete("Renamed to ConnectingToGameServer")]
  53. ConnectingToGameserver = ConnectingToGameServer,
  54. /// <summary>Connected to GameServer (going to auth and join game).</summary>
  55. ConnectedToGameServer,
  56. [Obsolete("Renamed to ConnectedToGameServer")]
  57. ConnectedToGameserver = ConnectedToGameServer,
  58. /// <summary>Transition state while joining or creating a room on GameServer.</summary>
  59. Joining,
  60. /// <summary>The client entered a room. The CurrentRoom and Players are known and you can now raise events.</summary>
  61. Joined,
  62. /// <summary>Transition state when leaving a room.</summary>
  63. Leaving,
  64. /// <summary>Transition from GameServer to MasterServer (after leaving a room/game).</summary>
  65. DisconnectingFromGameServer,
  66. [Obsolete("Renamed to DisconnectingFromGameServer")]
  67. DisconnectingFromGameserver = DisconnectingFromGameServer,
  68. /// <summary>Connecting to MasterServer (includes sending authentication values).</summary>
  69. ConnectingToMasterServer,
  70. [Obsolete("Renamed to ConnectingToMasterServer.")]
  71. ConnectingToMasterserver = ConnectingToMasterServer,
  72. /// <summary>The client disconnects (from any server). This leads to state Disconnected.</summary>
  73. Disconnecting,
  74. /// <summary>The client is no longer connected (to any server). Connect to MasterServer to go on.</summary>
  75. Disconnected,
  76. /// <summary>Connected to MasterServer. You might use matchmaking or join a lobby now.</summary>
  77. ConnectedToMasterServer,
  78. [Obsolete("Renamed to ConnectedToMasterServer.")]
  79. ConnectedToMasterserver = ConnectedToMasterServer,
  80. [Obsolete("Renamed to ConnectedToMasterServer.")]
  81. ConnectedToMaster = ConnectedToMasterServer,
  82. /// <summary>Client connects to the NameServer. This process includes low level connecting and setting up encryption. When done, state becomes ConnectedToNameServer.</summary>
  83. ConnectingToNameServer,
  84. /// <summary>Client is connected to the NameServer and established encryption already. You should call OpGetRegions or ConnectToRegionMaster.</summary>
  85. ConnectedToNameServer,
  86. /// <summary>Clients disconnects (specifically) from the NameServer (usually to connect to the MasterServer).</summary>
  87. DisconnectingFromNameServer,
  88. /// <summary>Client was unable to connect to Name Server and will attempt to connect with an alternative network protocol (TCP).</summary>
  89. ConnectWithFallbackProtocol
  90. }
  91. /// <summary>
  92. /// Internal state, how this peer gets into a particular room (joining it or creating it).
  93. /// </summary>
  94. internal enum JoinType
  95. {
  96. /// <summary>This client creates a room, gets into it (no need to join) and can set room properties.</summary>
  97. CreateRoom,
  98. /// <summary>The room existed already and we join into it (not setting room properties).</summary>
  99. JoinRoom,
  100. /// <summary>Done on Master Server and (if successful) followed by a Join on Game Server.</summary>
  101. JoinRandomRoom,
  102. /// <summary>Done on Master Server and (if successful) followed by a Join or Create on Game Server.</summary>
  103. JoinRandomOrCreateRoom,
  104. /// <summary>Client is either joining or creating a room. On Master- and Game-Server.</summary>
  105. JoinOrCreateRoom
  106. }
  107. /// <summary>Enumeration of causes for Disconnects (used in LoadBalancingClient.DisconnectedCause).</summary>
  108. /// <remarks>Read the individual descriptions to find out what to do about this type of disconnect.</remarks>
  109. public enum DisconnectCause
  110. {
  111. /// <summary>No error was tracked.</summary>
  112. None,
  113. /// <summary>OnStatusChanged: The server is not available or the address is wrong. Make sure the port is provided and the server is up.</summary>
  114. ExceptionOnConnect,
  115. /// <summary>OnStatusChanged: Dns resolution for a hostname failed. The exception for this is being catched and logged with error level.</summary>
  116. DnsExceptionOnConnect,
  117. /// <summary>OnStatusChanged: The server address was parsed as IPv4 illegally. An illegal address would be e.g. 192.168.1.300. IPAddress.TryParse() will let this pass but our check won't.</summary>
  118. ServerAddressInvalid,
  119. /// <summary>OnStatusChanged: Some internal exception caused the socket code to fail. This may happen if you attempt to connect locally but the server is not available. In doubt: Contact Exit Games.</summary>
  120. Exception,
  121. /// <summary>OnStatusChanged: The server disconnected this client due to timing out (missing acknowledgement from the client).</summary>
  122. ServerTimeout,
  123. /// <summary>OnStatusChanged: This client detected that the server's responses are not received in due time.</summary>
  124. ClientTimeout,
  125. /// <summary>OnStatusChanged: The server disconnected this client from within the room's logic (the C# code).</summary>
  126. DisconnectByServerLogic,
  127. /// <summary>OnStatusChanged: The server disconnected this client for unknown reasons.</summary>
  128. DisconnectByServerReasonUnknown,
  129. /// <summary>OnOperationResponse: Authenticate in the Photon Cloud with invalid AppId. Update your subscription or contact Exit Games.</summary>
  130. InvalidAuthentication,
  131. /// <summary>OnOperationResponse: Authenticate in the Photon Cloud with invalid client values or custom authentication setup in Cloud Dashboard.</summary>
  132. CustomAuthenticationFailed,
  133. /// <summary>The authentication ticket should provide access to any Photon Cloud server without doing another authentication-service call. However, the ticket expired.</summary>
  134. AuthenticationTicketExpired,
  135. /// <summary>OnOperationResponse: Authenticate (temporarily) failed when using a Photon Cloud subscription without CCU Burst. Update your subscription.</summary>
  136. MaxCcuReached,
  137. /// <summary>OnOperationResponse: Authenticate when the app's Photon Cloud subscription is locked to some (other) region(s). Update your subscription or master server address.</summary>
  138. InvalidRegion,
  139. /// <summary>OnOperationResponse: Operation that's (currently) not available for this client (not authorized usually). Only tracked for op Authenticate.</summary>
  140. OperationNotAllowedInCurrentState,
  141. /// <summary>OnStatusChanged: The client disconnected from within the logic (the C# code).</summary>
  142. DisconnectByClientLogic,
  143. /// <summary>The client called an operation too frequently and got disconnected due to hitting the OperationLimit. This triggers a client-side disconnect, too.</summary>
  144. /// <remarks>To protect the server, some operations have a limit. When an OperationResponse fails with ErrorCode.OperationLimitReached, the client disconnects.</remarks>
  145. DisconnectByOperationLimit,
  146. /// <summary>The client received a "Disconnect Message" from the server. Check the debug logs for details.</summary>
  147. DisconnectByDisconnectMessage
  148. }
  149. /// <summary>Available server (types) for internally used field: server.</summary>
  150. /// <remarks>Photon uses 3 different roles of servers: Name Server, Master Server and Game Server.</remarks>
  151. public enum ServerConnection
  152. {
  153. /// <summary>This server is where matchmaking gets done and where clients can get lists of rooms in lobbies.</summary>
  154. MasterServer,
  155. /// <summary>This server handles a number of rooms to execute and relay the messages between players (in a room).</summary>
  156. GameServer,
  157. /// <summary>This server is used initially to get the address (IP) of a Master Server for a specific region. Not used for Photon OnPremise (self hosted).</summary>
  158. NameServer
  159. }
  160. /// <summary>Defines which sort of app the LoadBalancingClient is used for: Realtime or Voice.</summary>
  161. public enum ClientAppType
  162. {
  163. /// <summary>Realtime apps are for gaming / interaction. Also used by PUN 2.</summary>
  164. Realtime,
  165. /// <summary>Voice apps stream audio.</summary>
  166. Voice,
  167. /// <summary>Fusion clients are for matchmaking and relay in Photon Fusion.</summary>
  168. Fusion
  169. }
  170. /// <summary>
  171. /// Defines how the communication gets encrypted.
  172. /// </summary>
  173. public enum EncryptionMode
  174. {
  175. /// <summary>
  176. /// This is the default encryption mode: Messages get encrypted only on demand (when you send operations with the "encrypt" parameter set to true).
  177. /// </summary>
  178. PayloadEncryption,
  179. /// <summary>
  180. /// With this encryption mode for UDP, the connection gets setup and all further datagrams get encrypted almost entirely. On-demand message encryption (like in PayloadEncryption) is unavailable.
  181. /// </summary>
  182. DatagramEncryption = 10,
  183. /// <summary>
  184. /// With this encryption mode for UDP, the connection gets setup with random sequence numbers and all further datagrams get encrypted almost entirely. On-demand message encryption (like in PayloadEncryption) is unavailable.
  185. /// </summary>
  186. DatagramEncryptionRandomSequence = 11,
  187. ///// <summary>
  188. ///// Same as above except that GCM mode is used to encrypt data.
  189. ///// </summary>
  190. //DatagramEncryptionGCMRandomSequence = 12,
  191. /// <summary>
  192. /// Datagram Encryption with GCM.
  193. /// </summary>
  194. DatagramEncryptionGCM = 13,
  195. }
  196. /// <summary>Container for port definitions.</summary>
  197. public struct PhotonPortDefinition
  198. {
  199. public static readonly PhotonPortDefinition AlternativeUdpPorts = new PhotonPortDefinition() { NameServerPort = 27000, MasterServerPort = 27001, GameServerPort = 27002};
  200. /// <summary>Typical ports: UDP: 5058 or 27000, TCP: 4533, WSS: 19093 or 443.</summary>
  201. public ushort NameServerPort;
  202. /// <summary>Typical ports: UDP: 5056 or 27002, TCP: 4530, WSS: 19090 or 443.</summary>
  203. public ushort MasterServerPort;
  204. /// <summary>Typical ports: UDP: 5055 or 27001, TCP: 4531, WSS: 19091 or 443.</summary>
  205. public ushort GameServerPort;
  206. }
  207. #endregion
  208. /// <summary>
  209. /// This class implements the Photon LoadBalancing workflow by using a LoadBalancingPeer.
  210. /// It keeps a state and will automatically execute transitions between the Master and Game Servers.
  211. /// </summary>
  212. /// <remarks>
  213. /// This class (and the Player class) should be extended to implement your own game logic.
  214. /// You can override CreatePlayer as "factory" method for Players and return your own Player instances.
  215. /// The State of this class is essential to know when a client is in a lobby (or just on the master)
  216. /// and when in a game where the actual gameplay should take place.
  217. /// Extension notes:
  218. /// An extension of this class should override the methods of the IPhotonPeerListener, as they
  219. /// are called when the state changes. Call base.method first, then pick the operation or state you
  220. /// want to react to and put it in a switch-case.
  221. /// We try to provide demo to each platform where this api can be used, so lookout for those.
  222. /// </remarks>
  223. public class LoadBalancingClient : IPhotonPeerListener
  224. {
  225. /// <summary>
  226. /// The client uses a LoadBalancingPeer as API to communicate with the server.
  227. /// This is public for ease-of-use: Some methods like OpRaiseEvent are not relevant for the connection state and don't need a override.
  228. /// </summary>
  229. public LoadBalancingPeer LoadBalancingPeer { get; private set; }
  230. /// <summary>
  231. /// Gets or sets the binary protocol version used by this client
  232. /// </summary>
  233. /// <remarks>
  234. /// Use this always instead of setting it via <see cref="LoadBalancingClient.LoadBalancingPeer"/>
  235. /// (<see cref="PhotonPeer.SerializationProtocolType"/>) directly, especially when WSS protocol is used.
  236. /// </remarks>
  237. public SerializationProtocol SerializationProtocol
  238. {
  239. get
  240. {
  241. return this.LoadBalancingPeer.SerializationProtocolType;
  242. }
  243. set
  244. {
  245. this.LoadBalancingPeer.SerializationProtocolType = value;
  246. }
  247. }
  248. /// <summary>The version of your client. A new version also creates a new "virtual app" to separate players from older client versions.</summary>
  249. public string AppVersion { get; set; }
  250. /// <summary>The AppID as assigned from the Photon Cloud. If you host yourself, this is the "regular" Photon Server Application Name (most likely: "LoadBalancing").</summary>
  251. public string AppId { get; set; }
  252. /// <summary>The ClientAppType defines which sort of AppId should be expected. The LoadBalancingClient supports Realtime and Voice app types. Default: Realtime.</summary>
  253. public ClientAppType ClientType { get; set; }
  254. /// <summary>User authentication values to be sent to the Photon server right after connecting.</summary>
  255. /// <remarks>Set this property or pass AuthenticationValues by Connect(..., authValues).</remarks>
  256. public AuthenticationValues AuthValues { get; set; }
  257. /// <summary>Enables the new Authentication workflow.</summary>
  258. public AuthModeOption AuthMode = AuthModeOption.Auth;
  259. /// <summary>Defines how the communication gets encrypted.</summary>
  260. public EncryptionMode EncryptionMode = EncryptionMode.PayloadEncryption;
  261. /// <summary>Optionally contains a protocol which will be used on Master- and GameServer. </summary>
  262. /// <remarks>
  263. /// When using AuthMode = AuthModeOption.AuthOnceWss, the client uses a wss-connection on the NameServer but another protocol on the other servers.
  264. /// As the NameServer sends an address, which is different per protocol, it needs to know the expected protocol.
  265. ///
  266. /// This is nullable by design. In many cases, the protocol on the NameServer is not different from the other servers.
  267. /// If set, the operation AuthOnce will contain this value and the OpAuth response on the NameServer will execute a protocol switch.
  268. /// </remarks>
  269. public ConnectionProtocol? ExpectedProtocol { get; set; }
  270. ///<summary>Simplifies getting the token for connect/init requests, if this feature is enabled.</summary>
  271. private object TokenForInit
  272. {
  273. get
  274. {
  275. if (this.AuthMode == AuthModeOption.Auth)
  276. {
  277. return null;
  278. }
  279. return (this.AuthValues != null) ? this.AuthValues.Token : null;
  280. }
  281. }
  282. /// <summary>Internally used cache for the server's token. Identifies a user/session and can be used to rejoin.</summary>
  283. private object tokenCache;
  284. /// <summary>True if this client uses a NameServer to get the Master Server address.</summary>
  285. /// <remarks>This value is public, despite being an internal value, which should only be set by this client.</remarks>
  286. public bool IsUsingNameServer { get; set; }
  287. /// <summary>Name Server Host Name for Photon Cloud. Without port and without any prefix.</summary>
  288. public string NameServerHost = "ns.photonengine.io";
  289. /// <summary>Name Server Address for Photon Cloud (based on current protocol). You can use the default values and usually won't have to set this value.</summary>
  290. public string NameServerAddress { get { return this.GetNameServerAddress(); } }
  291. /// <summary>Name Server port per protocol (the UDP port is different than TCP, etc).</summary>
  292. private static readonly Dictionary<ConnectionProtocol, int> ProtocolToNameServerPort = new Dictionary<ConnectionProtocol, int>() { { ConnectionProtocol.Udp, 5058 }, { ConnectionProtocol.Tcp, 4533 }, { ConnectionProtocol.WebSocket, 9093 }, { ConnectionProtocol.WebSocketSecure, 19093 } }; //, { ConnectionProtocol.RHttp, 6063 } };
  293. /// <summary>Replaced by ServerPortOverrides.</summary>
  294. [Obsolete("Set port overrides in ServerPortOverrides. Not used anymore!")]
  295. public bool UseAlternativeUdpPorts { get; set; }
  296. /// <summary>Defines overrides for server ports. Used per server-type if > 0. Important: You must change these when the protocol changes!</summary>
  297. /// <remarks>
  298. /// Typical ports are listed in PhotonPortDefinition.
  299. ///
  300. /// Instead of using the port provided from the servers, the specified port is used (independent of the protocol).
  301. /// If a value is 0 (default), the port is not being replaced.
  302. ///
  303. /// Different protocols have different typical ports per server-type.
  304. /// https://doc.photonengine.com/en-us/pun/current/reference/tcp-and-udp-port-numbers
  305. ///
  306. /// In case of using the AuthMode AutOnceWss, the name server's protocol is wss, while udp or tcp will be used on the master server and game server.
  307. /// Set the ports accordingly per protocol and server.
  308. /// </remarks>
  309. public PhotonPortDefinition ServerPortOverrides;
  310. /// <summary>Enables a fallback to another protocol in case a connect to the Name Server fails.</summary>
  311. /// <remarks>
  312. /// When connecting to the Name Server fails for a first time, the client will select an alternative
  313. /// network protocol and re-try to connect.
  314. ///
  315. /// The fallback will use the default Name Server port as defined by ProtocolToNameServerPort.
  316. ///
  317. /// The fallback for TCP is UDP. All other protocols fallback to TCP.
  318. /// </remarks>
  319. public bool EnableProtocolFallback { get; set; }
  320. /// <summary>The currently used server address (if any). The type of server is define by Server property.</summary>
  321. public string CurrentServerAddress { get { return this.LoadBalancingPeer.ServerAddress; } }
  322. /// <summary>Your Master Server address. In PhotonCloud, call ConnectToRegionMaster() to find your Master Server.</summary>
  323. /// <remarks>
  324. /// In the Photon Cloud, explicit definition of a Master Server Address is not best practice.
  325. /// The Photon Cloud has a "Name Server" which redirects clients to a specific Master Server (per Region and AppId).
  326. /// </remarks>
  327. public string MasterServerAddress { get; set; }
  328. /// <summary>The game server's address for a particular room. In use temporarily, as assigned by master.</summary>
  329. public string GameServerAddress { get; protected internal set; }
  330. /// <summary>The server this client is currently connected or connecting to.</summary>
  331. /// <remarks>
  332. /// Each server (NameServer, MasterServer, GameServer) allow some operations and reject others.
  333. /// </remarks>
  334. public ServerConnection Server { get; private set; }
  335. /// <summary>
  336. /// Defines a proxy URL for WebSocket connections. Can be the proxy or point to a .pac file.
  337. /// </summary>
  338. /// <remarks>
  339. /// This URL supports various definitions:
  340. ///
  341. /// "user:pass@proxyaddress:port"<br/>
  342. /// "proxyaddress:port"<br/>
  343. /// "system:"<br/>
  344. /// "pac:"<br/>
  345. /// "pac:http://host/path/pacfile.pac"<br/>
  346. ///
  347. /// Important: Don't define a protocol, except to point to a pac file. the proxy address should not begin with http:// or https://.
  348. /// </remarks>
  349. public string ProxyServerAddress;
  350. /// <summary>Backing field for property.</summary>
  351. private ClientState state = ClientState.PeerCreated;
  352. /// <summary>Current state this client is in. Careful: several states are "transitions" that lead to other states.</summary>
  353. public ClientState State
  354. {
  355. get
  356. {
  357. return this.state;
  358. }
  359. set
  360. {
  361. if (this.state == value)
  362. {
  363. return;
  364. }
  365. ClientState previousState = this.state;
  366. this.state = value;
  367. if (StateChanged != null) StateChanged(previousState, this.state);
  368. }
  369. }
  370. /// <summary>Returns if this client is currently connected or connecting to some type of server.</summary>
  371. /// <remarks>This is even true while switching servers. Use IsConnectedAndReady to check only for those states that enable you to send Operations.</remarks>
  372. public bool IsConnected { get { return this.LoadBalancingPeer != null && this.State != ClientState.PeerCreated && this.State != ClientState.Disconnected; } }
  373. /// <summary>
  374. /// A refined version of IsConnected which is true only if your connection is ready to send operations.
  375. /// </summary>
  376. /// <remarks>
  377. /// Not all operations can be called on all types of servers. If an operation is unavailable on the currently connected server,
  378. /// this will result in a OperationResponse with ErrorCode != 0.
  379. ///
  380. /// Examples: The NameServer allows OpGetRegions which is not available anywhere else.
  381. /// The MasterServer does not allow you to send events (OpRaiseEvent) and on the GameServer you are unable to join a lobby (OpJoinLobby).
  382. ///
  383. /// To check which server you are on, use: <see cref="Server"/>.
  384. /// </remarks>
  385. public bool IsConnectedAndReady
  386. {
  387. get
  388. {
  389. if (this.LoadBalancingPeer == null)
  390. {
  391. return false;
  392. }
  393. switch (this.State)
  394. {
  395. case ClientState.PeerCreated:
  396. case ClientState.Disconnected:
  397. case ClientState.Disconnecting:
  398. case ClientState.DisconnectingFromGameServer:
  399. case ClientState.DisconnectingFromMasterServer:
  400. case ClientState.DisconnectingFromNameServer:
  401. case ClientState.Authenticating:
  402. case ClientState.ConnectingToGameServer:
  403. case ClientState.ConnectingToMasterServer:
  404. case ClientState.ConnectingToNameServer:
  405. case ClientState.Joining:
  406. case ClientState.Leaving:
  407. return false; // we are not ready to execute any operations
  408. }
  409. return true;
  410. }
  411. }
  412. /// <summary>Register a method to be called when this client's ClientState gets set.</summary>
  413. /// <remarks>This can be useful to react to being connected, joined into a room, etc.</remarks>
  414. public event Action<ClientState, ClientState> StateChanged;
  415. /// <summary>Register a method to be called when an event got dispatched. Gets called after the LoadBalancingClient handled the internal events first.</summary>
  416. /// <remarks>
  417. /// This is an alternative to extending LoadBalancingClient to override OnEvent().
  418. ///
  419. /// Note that OnEvent is calling EventReceived after it handled internal events first.
  420. /// That means for example: Joining players will already be in the player list but leaving
  421. /// players will already be removed from the room.
  422. /// </remarks>
  423. public event Action<EventData> EventReceived;
  424. /// <summary>Register a method to be called when an operation response is received.</summary>
  425. /// <remarks>
  426. /// This is an alternative to extending LoadBalancingClient to override OnOperationResponse().
  427. ///
  428. /// Note that OnOperationResponse gets executed before your Action is called.
  429. /// That means for example: The OpJoinLobby response already set the state to "JoinedLobby"
  430. /// and the response to OpLeave already triggered the Disconnect before this is called.
  431. /// </remarks>
  432. public event Action<OperationResponse> OpResponseReceived;
  433. /// <summary>Wraps up the target objects for a group of callbacks, so they can be called conveniently.</summary>
  434. /// <remarks>By using Add or Remove, objects can "subscribe" or "unsubscribe" for this group of callbacks.</remarks>
  435. public ConnectionCallbacksContainer ConnectionCallbackTargets;
  436. /// <summary>Wraps up the target objects for a group of callbacks, so they can be called conveniently.</summary>
  437. /// <remarks>By using Add or Remove, objects can "subscribe" or "unsubscribe" for this group of callbacks.</remarks>
  438. public MatchMakingCallbacksContainer MatchMakingCallbackTargets;
  439. /// <summary>Wraps up the target objects for a group of callbacks, so they can be called conveniently.</summary>
  440. /// <remarks>By using Add or Remove, objects can "subscribe" or "unsubscribe" for this group of callbacks.</remarks>
  441. internal InRoomCallbacksContainer InRoomCallbackTargets;
  442. /// <summary>Wraps up the target objects for a group of callbacks, so they can be called conveniently.</summary>
  443. /// <remarks>By using Add or Remove, objects can "subscribe" or "unsubscribe" for this group of callbacks.</remarks>
  444. internal LobbyCallbacksContainer LobbyCallbackTargets;
  445. /// <summary>Wraps up the target objects for a group of callbacks, so they can be called conveniently.</summary>
  446. /// <remarks>By using Add or Remove, objects can "subscribe" or "unsubscribe" for this group of callbacks.</remarks>
  447. internal WebRpcCallbacksContainer WebRpcCallbackTargets;
  448. /// <summary>Wraps up the target objects for a group of callbacks, so they can be called conveniently.</summary>
  449. /// <remarks>By using Add or Remove, objects can "subscribe" or "unsubscribe" for this group of callbacks.</remarks>
  450. internal ErrorInfoCallbacksContainer ErrorInfoCallbackTargets;
  451. /// <summary>Summarizes (aggregates) the different causes for disconnects of a client.</summary>
  452. /// <remarks>
  453. /// A disconnect can be caused by: errors in the network connection or some vital operation failing
  454. /// (which is considered "high level"). While operations always trigger a call to OnOperationResponse,
  455. /// connection related changes are treated in OnStatusChanged.
  456. /// The DisconnectCause is set in either case and summarizes the causes for any disconnect in a single
  457. /// state value which can be used to display (or debug) the cause for disconnection.
  458. /// </remarks>
  459. public DisconnectCause DisconnectedCause { get; protected set; }
  460. /// <summary>Internal value if the client is in a lobby.</summary>
  461. /// <remarks>This is used to re-set this.State, when joining/creating a room fails.</remarks>
  462. public bool InLobby
  463. {
  464. get { return this.State == ClientState.JoinedLobby; }
  465. }
  466. /// <summary>The lobby this client currently uses. Defined when joining a lobby or creating rooms</summary>
  467. public TypedLobby CurrentLobby { get; internal set; }
  468. /// <summary>
  469. /// If enabled, the client will get a list of available lobbies from the Master Server.
  470. /// </summary>
  471. /// <remarks>
  472. /// Set this value before the client connects to the Master Server. While connected to the Master
  473. /// Server, a change has no effect.
  474. ///
  475. /// Implement OptionalInfoCallbacks.OnLobbyStatisticsUpdate, to get the list of used lobbies.
  476. ///
  477. /// The lobby statistics can be useful if your title dynamically uses lobbies, depending (e.g.)
  478. /// on current player activity or such.
  479. /// In this case, getting a list of available lobbies, their room-count and player-count can
  480. /// be useful info.
  481. ///
  482. /// ConnectUsingSettings sets this to the PhotonServerSettings value.
  483. /// </remarks>
  484. public bool EnableLobbyStatistics;
  485. /// <summary>Internal lobby stats cache, used by LobbyStatistics.</summary>
  486. private readonly List<TypedLobbyInfo> lobbyStatistics = new List<TypedLobbyInfo>();
  487. /// <summary>The local player is never null but not valid unless the client is in a room, too. The ID will be -1 outside of rooms.</summary>
  488. public Player LocalPlayer { get; internal set; }
  489. /// <summary>
  490. /// The nickname of the player (synced with others). Same as client.LocalPlayer.NickName.
  491. /// </summary>
  492. public string NickName
  493. {
  494. get
  495. {
  496. return this.LocalPlayer.NickName;
  497. }
  498. set
  499. {
  500. if (this.LocalPlayer == null)
  501. {
  502. return;
  503. }
  504. this.LocalPlayer.NickName = value;
  505. }
  506. }
  507. /// <summary>An ID for this user. Sent in OpAuthenticate when you connect. If not set, the PlayerName is applied during connect.</summary>
  508. /// <remarks>
  509. /// On connect, if the UserId is null or empty, the client will copy the PlayName to UserId. If PlayerName is not set either
  510. /// (before connect), the server applies a temporary ID which stays unknown to this client and other clients.
  511. ///
  512. /// The UserId is what's used in FindFriends and for fetching data for your account (with WebHooks e.g.).
  513. ///
  514. /// By convention, set this ID before you connect, not while being connected.
  515. /// There is no error but the ID won't change while being connected.
  516. /// </remarks>
  517. public string UserId
  518. {
  519. get
  520. {
  521. if (this.AuthValues != null)
  522. {
  523. return this.AuthValues.UserId;
  524. }
  525. return null;
  526. }
  527. set
  528. {
  529. if (this.AuthValues == null)
  530. {
  531. this.AuthValues = new AuthenticationValues();
  532. }
  533. this.AuthValues.UserId = value;
  534. }
  535. }
  536. /// <summary>The current room this client is connected to (null if none available).</summary>
  537. public Room CurrentRoom { get; set; }
  538. /// <summary>Is true while being in a room (this.state == ClientState.Joined).</summary>
  539. /// <remarks>
  540. /// Aside from polling this value, game logic should implement IMatchmakingCallbacks in some class
  541. /// and react when that gets called.<br/>
  542. /// OpRaiseEvent, OpLeave and some other operations can only be used (successfully) when the client is in a room..
  543. /// </remarks>
  544. public bool InRoom
  545. {
  546. get
  547. {
  548. return this.state == ClientState.Joined && this.CurrentRoom != null;
  549. }
  550. }
  551. /// <summary>Statistic value available on master server: Players on master (looking for games).</summary>
  552. public int PlayersOnMasterCount { get; internal set; }
  553. /// <summary>Statistic value available on master server: Players in rooms (playing).</summary>
  554. public int PlayersInRoomsCount { get; internal set; }
  555. /// <summary>Statistic value available on master server: Rooms currently created.</summary>
  556. public int RoomsCount { get; internal set; }
  557. /// <summary>Internally used to decide if a room must be created or joined on game server.</summary>
  558. private JoinType lastJoinType;
  559. /// <summary>Used when the client arrives on the GS, to join the room with the correct values.</summary>
  560. private EnterRoomParams enterRoomParamsCache;
  561. /// <summary>Used to cache a failed "enter room" operation on the Game Server, to return to the Master Server before calling a fail-callback.</summary>
  562. private OperationResponse failedRoomEntryOperation;
  563. /// <summary>Maximum of userIDs that can be sent in one friend list request.</summary>
  564. private const int FriendRequestListMax = 512;
  565. /// <summary>Contains the list of names of friends to look up their state on the server.</summary>
  566. private string[] friendListRequested;
  567. /// <summary>Internal flag to know if the client currently fetches a friend list.</summary>
  568. public bool IsFetchingFriendList { get { return this.friendListRequested != null; } }
  569. /// <summary>The cloud region this client connects to. Set by ConnectToRegionMaster(). Not set if you don't use a NameServer!</summary>
  570. public string CloudRegion { get; private set; }
  571. /// <summary>The cluster name provided by the Name Server.</summary>
  572. /// <remarks>
  573. /// The value is provided by the OpResponse for OpAuthenticate/OpAuthenticateOnce.
  574. /// Default: null. This value only ever updates from the Name Server authenticate response.
  575. /// </remarks>
  576. public string CurrentCluster { get; private set; }
  577. /// <summary>Contains the list if enabled regions this client may use. Null, unless the client got a response to OpGetRegions.</summary>
  578. public RegionHandler RegionHandler;
  579. /// <summary>Stores the best region summary of a previous session to speed up connecting.</summary>
  580. private string bestRegionSummaryFromStorage;
  581. /// <summary>Set when the best region pinging is done.</summary>
  582. public string SummaryToCache;
  583. /// <summary>Internal connection setting/flag. If the client should connect to the best region or not.</summary>
  584. /// <remarks>
  585. /// It's set in the Connect...() methods. Only ConnectUsingSettings() sets it to true.
  586. /// If true, client will ping available regions and select the best.
  587. /// A bestRegionSummaryFromStorage can be used to cut the ping time short.
  588. /// </remarks>
  589. private bool connectToBestRegion = true;
  590. /// <summary>Definition of parameters for encryption data (included in Authenticate operation response).</summary>
  591. private class EncryptionDataParameters
  592. {
  593. /// <summary>
  594. /// Key for encryption mode
  595. /// </summary>
  596. public const byte Mode = 0;
  597. /// <summary>
  598. /// Key for first secret
  599. /// </summary>
  600. public const byte Secret1 = 1;
  601. /// <summary>
  602. /// Key for second secret
  603. /// </summary>
  604. public const byte Secret2 = 2;
  605. }
  606. private class CallbackTargetChange
  607. {
  608. public readonly object Target;
  609. /// <summary>Add if true, remove if false.</summary>
  610. public readonly bool AddTarget;
  611. public CallbackTargetChange(object target, bool addTarget)
  612. {
  613. this.Target = target;
  614. this.AddTarget = addTarget;
  615. }
  616. }
  617. private readonly Queue<CallbackTargetChange> callbackTargetChanges = new Queue<CallbackTargetChange>();
  618. private readonly HashSet<object> callbackTargets = new HashSet<object>();
  619. /// <summary>Creates a LoadBalancingClient with UDP protocol or the one specified.</summary>
  620. /// <param name="protocol">Specifies the network protocol to use for connections.</param>
  621. public LoadBalancingClient(ConnectionProtocol protocol = ConnectionProtocol.Udp)
  622. {
  623. this.ConnectionCallbackTargets = new ConnectionCallbacksContainer(this);
  624. this.MatchMakingCallbackTargets = new MatchMakingCallbacksContainer(this);
  625. this.InRoomCallbackTargets = new InRoomCallbacksContainer(this);
  626. this.LobbyCallbackTargets = new LobbyCallbacksContainer(this);
  627. this.WebRpcCallbackTargets = new WebRpcCallbacksContainer(this);
  628. this.ErrorInfoCallbackTargets = new ErrorInfoCallbacksContainer(this);
  629. this.LoadBalancingPeer = new LoadBalancingPeer(this, protocol);
  630. this.LoadBalancingPeer.OnDisconnectMessage += this.OnDisconnectMessageReceived;
  631. this.SerializationProtocol = SerializationProtocol.GpBinaryV18;
  632. this.LocalPlayer = this.CreatePlayer(string.Empty, -1, true, null); //TODO: Check if we can do this later
  633. #if SUPPORTED_UNITY
  634. CustomTypesUnity.Register();
  635. #endif
  636. #if UNITY_WEBGL
  637. if (this.LoadBalancingPeer.TransportProtocol == ConnectionProtocol.Tcp || this.LoadBalancingPeer.TransportProtocol == ConnectionProtocol.Udp)
  638. {
  639. this.LoadBalancingPeer.Listener.DebugReturn(DebugLevel.WARNING, "WebGL requires WebSockets. Switching TransportProtocol to WebSocketSecure.");
  640. this.LoadBalancingPeer.TransportProtocol = ConnectionProtocol.WebSocketSecure;
  641. }
  642. #endif
  643. this.State = ClientState.PeerCreated;
  644. }
  645. /// <summary>Creates a LoadBalancingClient, setting various values needed before connecting.</summary>
  646. /// <param name="masterAddress">The Master Server's address to connect to. Used in Connect.</param>
  647. /// <param name="appId">The AppId of this title. Needed for the Photon Cloud. Find it in the Dashboard.</param>
  648. /// <param name="gameVersion">A version for this client/build. In the Photon Cloud, players are separated by AppId, GameVersion and Region.</param>
  649. /// <param name="protocol">Specifies the network protocol to use for connections.</param>
  650. public LoadBalancingClient(string masterAddress, string appId, string gameVersion, ConnectionProtocol protocol = ConnectionProtocol.Udp) : this(protocol)
  651. {
  652. this.MasterServerAddress = masterAddress;
  653. this.AppId = appId;
  654. this.AppVersion = gameVersion;
  655. }
  656. public int NameServerPortInAppSettings;
  657. /// <summary>
  658. /// Gets the NameServer Address (with prefix and port), based on the set protocol (this.LoadBalancingPeer.UsedProtocol).
  659. /// </summary>
  660. /// <returns>NameServer Address (with prefix and port).</returns>
  661. private string GetNameServerAddress()
  662. {
  663. var protocolPort = 0;
  664. ProtocolToNameServerPort.TryGetValue(this.LoadBalancingPeer.TransportProtocol, out protocolPort);
  665. if (this.NameServerPortInAppSettings != 0)
  666. {
  667. this.DebugReturn(DebugLevel.INFO, string.Format("Using NameServerPortInAppSettings: {0}", this.NameServerPortInAppSettings));
  668. protocolPort = this.NameServerPortInAppSettings;
  669. }
  670. if (this.ServerPortOverrides.NameServerPort > 0)
  671. {
  672. protocolPort = this.ServerPortOverrides.NameServerPort;
  673. }
  674. switch (this.LoadBalancingPeer.TransportProtocol)
  675. {
  676. case ConnectionProtocol.Udp:
  677. case ConnectionProtocol.Tcp:
  678. return string.Format("{0}:{1}", NameServerHost, protocolPort);
  679. case ConnectionProtocol.WebSocket:
  680. return string.Format("ws://{0}:{1}", NameServerHost, protocolPort);
  681. case ConnectionProtocol.WebSocketSecure:
  682. return string.Format("wss://{0}:{1}", NameServerHost, protocolPort);
  683. default:
  684. throw new ArgumentOutOfRangeException();
  685. }
  686. }
  687. #region Operations and Commands
  688. // needed connect variants:
  689. // connect to Name Server only (could include getregions) -> end after getregions
  690. // connect to Region Master via Name Server (specific region/cluster) -> no getregions! authenticates and ends after on connected to master
  691. // connect to Best Region via Name Server
  692. // connect to Master Server (no Name Server, no appid)
  693. public virtual bool ConnectUsingSettings(AppSettings appSettings)
  694. {
  695. if (this.LoadBalancingPeer.PeerState != PeerStateValue.Disconnected)
  696. {
  697. this.DebugReturn(DebugLevel.WARNING, "ConnectUsingSettings() failed. Can only connect while in state 'Disconnected'. Current state: " + this.LoadBalancingPeer.PeerState);
  698. return false;
  699. }
  700. if (appSettings == null)
  701. {
  702. this.DebugReturn(DebugLevel.ERROR, "ConnectUsingSettings failed. The appSettings can't be null.'");
  703. return false;
  704. }
  705. switch (this.ClientType)
  706. {
  707. case ClientAppType.Realtime:
  708. this.AppId = appSettings.AppIdRealtime;
  709. break;
  710. case ClientAppType.Voice:
  711. this.AppId = appSettings.AppIdVoice;
  712. break;
  713. case ClientAppType.Fusion:
  714. this.AppId = appSettings.AppIdFusion;
  715. break;
  716. }
  717. this.AppVersion = appSettings.AppVersion;
  718. this.IsUsingNameServer = appSettings.UseNameServer;
  719. this.CloudRegion = appSettings.FixedRegion;
  720. this.connectToBestRegion = string.IsNullOrEmpty(this.CloudRegion);
  721. this.EnableLobbyStatistics = appSettings.EnableLobbyStatistics;
  722. this.LoadBalancingPeer.DebugOut = appSettings.NetworkLogging;
  723. this.AuthMode = appSettings.AuthMode;
  724. if (appSettings.AuthMode == AuthModeOption.AuthOnceWss)
  725. {
  726. this.LoadBalancingPeer.TransportProtocol = ConnectionProtocol.WebSocketSecure;
  727. this.ExpectedProtocol = appSettings.Protocol;
  728. }
  729. else
  730. {
  731. this.LoadBalancingPeer.TransportProtocol = appSettings.Protocol;
  732. this.ExpectedProtocol = null;
  733. }
  734. this.EnableProtocolFallback = appSettings.EnableProtocolFallback;
  735. this.bestRegionSummaryFromStorage = appSettings.BestRegionSummaryFromStorage;
  736. this.DisconnectedCause = DisconnectCause.None;
  737. this.CheckConnectSetupWebGl();
  738. if (this.IsUsingNameServer)
  739. {
  740. this.Server = ServerConnection.NameServer;
  741. if (!appSettings.IsDefaultNameServer)
  742. {
  743. this.NameServerHost = appSettings.Server;
  744. }
  745. this.ProxyServerAddress = appSettings.ProxyServer;
  746. this.NameServerPortInAppSettings = appSettings.Port;
  747. if (!this.LoadBalancingPeer.Connect(this.NameServerAddress, this.ProxyServerAddress, this.AppId, this.TokenForInit))
  748. {
  749. return false;
  750. }
  751. this.State = ClientState.ConnectingToNameServer;
  752. }
  753. else
  754. {
  755. this.Server = ServerConnection.MasterServer;
  756. int portToUse = appSettings.IsDefaultPort ? 5055 : appSettings.Port; // TODO: setup new (default) port config
  757. this.MasterServerAddress = string.Format("{0}:{1}", appSettings.Server, portToUse);
  758. if (!this.LoadBalancingPeer.Connect(this.MasterServerAddress, this.ProxyServerAddress, this.AppId, this.TokenForInit))
  759. {
  760. return false;
  761. }
  762. this.State = ClientState.ConnectingToMasterServer;
  763. }
  764. return true;
  765. }
  766. [Obsolete("Use ConnectToMasterServer() instead.")]
  767. public bool Connect()
  768. {
  769. return this.ConnectToMasterServer();
  770. }
  771. /// <summary>
  772. /// Starts the "process" to connect to a Master Server, using MasterServerAddress and AppId properties.
  773. /// </summary>
  774. /// <remarks>
  775. /// To connect to the Photon Cloud, use ConnectUsingSettings() or ConnectToRegionMaster().
  776. ///
  777. /// The process to connect includes several steps: the actual connecting, establishing encryption, authentification
  778. /// (of app and optionally the user) and connecting to the MasterServer
  779. ///
  780. /// Users can connect either anonymously or use "Custom Authentication" to verify each individual player's login.
  781. /// Custom Authentication in Photon uses external services and communities to verify users. While the client provides a user's info,
  782. /// the service setup is done in the Photon Cloud Dashboard.
  783. /// The parameter authValues will set this.AuthValues and use them in the connect process.
  784. ///
  785. /// Connecting to the Photon Cloud might fail due to:
  786. /// - Network issues (OnStatusChanged() StatusCode.ExceptionOnConnect)
  787. /// - Region not available (OnOperationResponse() for OpAuthenticate with ReturnCode == ErrorCode.InvalidRegion)
  788. /// - Subscription CCU limit reached (OnOperationResponse() for OpAuthenticate with ReturnCode == ErrorCode.MaxCcuReached)
  789. /// </remarks>
  790. public virtual bool ConnectToMasterServer()
  791. {
  792. if (this.LoadBalancingPeer.PeerState != PeerStateValue.Disconnected)
  793. {
  794. this.DebugReturn(DebugLevel.WARNING, "ConnectToMasterServer() failed. Can only connect while in state 'Disconnected'. Current state: " + this.LoadBalancingPeer.PeerState);
  795. return false;
  796. }
  797. // when using authMode AuthOnce or AuthOnceWSS, the token must be available for the init request. if it's null in that case, don't connect
  798. if (this.AuthMode != AuthModeOption.Auth && this.TokenForInit == null)
  799. {
  800. this.DebugReturn(DebugLevel.ERROR, "Connect() failed. Can't connect to MasterServer with Token == null in AuthMode: " + this.AuthMode);
  801. return false;
  802. }
  803. this.CheckConnectSetupWebGl();
  804. if (this.LoadBalancingPeer.Connect(this.MasterServerAddress, this.ProxyServerAddress, this.AppId, this.TokenForInit))
  805. {
  806. this.DisconnectedCause = DisconnectCause.None;
  807. this.connectToBestRegion = false;
  808. this.State = ClientState.ConnectingToMasterServer;
  809. this.Server = ServerConnection.MasterServer;
  810. return true;
  811. }
  812. return false;
  813. }
  814. /// <summary>
  815. /// Connects to the NameServer for Photon Cloud, where a region and server list can be obtained.
  816. /// </summary>
  817. /// <see cref="OpGetRegions"/>
  818. /// <returns>If the workflow was started or failed right away.</returns>
  819. public bool ConnectToNameServer()
  820. {
  821. if (this.LoadBalancingPeer.PeerState != PeerStateValue.Disconnected)
  822. {
  823. this.DebugReturn(DebugLevel.WARNING, "ConnectToNameServer() failed. Can only connect while in state 'Disconnected'. Current state: " + this.LoadBalancingPeer.PeerState);
  824. return false;
  825. }
  826. this.IsUsingNameServer = true;
  827. this.CloudRegion = null;
  828. this.CheckConnectSetupWebGl();
  829. if (this.AuthMode == AuthModeOption.AuthOnceWss)
  830. {
  831. if (this.ExpectedProtocol == null)
  832. {
  833. this.ExpectedProtocol = this.LoadBalancingPeer.TransportProtocol;
  834. }
  835. this.LoadBalancingPeer.TransportProtocol = ConnectionProtocol.WebSocketSecure;
  836. }
  837. if (this.LoadBalancingPeer.Connect(this.NameServerAddress, this.ProxyServerAddress, "NameServer", this.TokenForInit))
  838. {
  839. this.DisconnectedCause = DisconnectCause.None;
  840. this.connectToBestRegion = false;
  841. this.State = ClientState.ConnectingToNameServer;
  842. this.Server = ServerConnection.NameServer;
  843. return true;
  844. }
  845. return false;
  846. }
  847. /// <summary>
  848. /// Connects you to a specific region's Master Server, using the Name Server to find the IP.
  849. /// </summary>
  850. /// <remarks>
  851. /// If the region is null or empty, no connection will be made.
  852. /// If the region (code) provided is not available, the connection process will fail on the Name Server.
  853. /// This method connects only to the region defined. No "Best Region" pinging will be done.
  854. ///
  855. /// If the region string does not contain a "/", this means no specific cluster is requested.
  856. /// To support "Sharding", the region gets a "/*" postfix in this case, to select a random cluster.
  857. /// </remarks>
  858. /// <returns>If the operation could be sent. If false, no operation was sent.</returns>
  859. public bool ConnectToRegionMaster(string region)
  860. {
  861. if (string.IsNullOrEmpty(region))
  862. {
  863. this.DebugReturn(DebugLevel.ERROR, "ConnectToRegionMaster() failed. The region can not be null or empty.");
  864. return false;
  865. }
  866. this.IsUsingNameServer = true;
  867. if (this.State == ClientState.Authenticating)
  868. {
  869. if (this.LoadBalancingPeer.DebugOut >= DebugLevel.INFO)
  870. {
  871. this.DebugReturn(DebugLevel.INFO, "ConnectToRegionMaster() will skip calling authenticate, as the current state is 'Authenticating'. Just wait for the result.");
  872. }
  873. return true;
  874. }
  875. if (this.State == ClientState.ConnectedToNameServer)
  876. {
  877. this.CloudRegion = region;
  878. bool authenticating = this.CallAuthenticate();
  879. if (authenticating)
  880. {
  881. this.State = ClientState.Authenticating;
  882. }
  883. return authenticating;
  884. }
  885. this.LoadBalancingPeer.Disconnect();
  886. if (!string.IsNullOrEmpty(region) && !region.Contains("/"))
  887. {
  888. region = region + "/*";
  889. }
  890. this.CloudRegion = region;
  891. this.CheckConnectSetupWebGl();
  892. if (this.AuthMode == AuthModeOption.AuthOnceWss)
  893. {
  894. if (this.ExpectedProtocol == null)
  895. {
  896. this.ExpectedProtocol = this.LoadBalancingPeer.TransportProtocol;
  897. }
  898. this.LoadBalancingPeer.TransportProtocol = ConnectionProtocol.WebSocketSecure;
  899. }
  900. this.connectToBestRegion = false;
  901. this.DisconnectedCause = DisconnectCause.None;
  902. if (!this.LoadBalancingPeer.Connect(this.NameServerAddress, this.ProxyServerAddress, "NameServer", null))
  903. {
  904. return false;
  905. }
  906. this.State = ClientState.ConnectingToNameServer;
  907. this.Server = ServerConnection.NameServer;
  908. return true;
  909. }
  910. [Conditional("UNITY_WEBGL")]
  911. private void CheckConnectSetupWebGl()
  912. {
  913. #if UNITY_WEBGL
  914. if (this.LoadBalancingPeer.TransportProtocol != ConnectionProtocol.WebSocket && this.LoadBalancingPeer.TransportProtocol != ConnectionProtocol.WebSocketSecure)
  915. {
  916. this.DebugReturn(DebugLevel.WARNING, "WebGL requires WebSockets. Switching TransportProtocol to WebSocketSecure.");
  917. this.LoadBalancingPeer.TransportProtocol = ConnectionProtocol.WebSocketSecure;
  918. }
  919. this.EnableProtocolFallback = false; // no fallback on WebGL
  920. #endif
  921. }
  922. /// <summary>
  923. /// Privately used only for reconnecting.
  924. /// </summary>
  925. private bool Connect(string serverAddress, string proxyServerAddress, ServerConnection serverType)
  926. {
  927. // TODO: Make sure app doesn't quit right now
  928. if (this.State == ClientState.Disconnecting)
  929. {
  930. this.DebugReturn(DebugLevel.ERROR, "Connect() failed. Can't connect while disconnecting (still). Current state: " + this.State);
  931. return false;
  932. }
  933. // when using authMode AuthOnce or AuthOnceWSS, the token must be available for the init request. if it's null in that case, don't connect
  934. if (this.AuthMode != AuthModeOption.Auth && serverType != ServerConnection.NameServer && this.TokenForInit == null)
  935. {
  936. this.DebugReturn(DebugLevel.ERROR, "Connect() failed. Can't connect to " + serverType + " with Token == null in AuthMode: " + this.AuthMode);
  937. return false;
  938. }
  939. // connect might fail, if the DNS name can't be resolved or if no network connection is available, etc.
  940. bool connecting = this.LoadBalancingPeer.Connect(serverAddress, proxyServerAddress, this.AppId, this.TokenForInit);
  941. if (connecting)
  942. {
  943. this.DisconnectedCause = DisconnectCause.None;
  944. this.Server = serverType;
  945. switch (serverType)
  946. {
  947. case ServerConnection.NameServer:
  948. State = ClientState.ConnectingToNameServer;
  949. break;
  950. case ServerConnection.MasterServer:
  951. State = ClientState.ConnectingToMasterServer;
  952. break;
  953. case ServerConnection.GameServer:
  954. State = ClientState.ConnectingToGameServer;
  955. break;
  956. }
  957. }
  958. return connecting;
  959. }
  960. /// <summary>Can be used to reconnect to the master server after a disconnect.</summary>
  961. /// <remarks>Common use case: Press the Lock Button on a iOS device and you get disconnected immediately.</remarks>
  962. public bool ReconnectToMaster()
  963. {
  964. if (this.LoadBalancingPeer.PeerState != PeerStateValue.Disconnected)
  965. {
  966. this.DebugReturn(DebugLevel.WARNING, "ReconnectToMaster() failed. Can only connect while in state 'Disconnected'. Current state: " + this.LoadBalancingPeer.PeerState);
  967. return false;
  968. }
  969. if (string.IsNullOrEmpty(this.MasterServerAddress))
  970. {
  971. this.DebugReturn(DebugLevel.WARNING, "ReconnectToMaster() failed. MasterServerAddress is null or empty.");
  972. return false;
  973. }
  974. if (this.tokenCache == null)
  975. {
  976. this.DebugReturn(DebugLevel.WARNING, "ReconnectToMaster() failed. It seems the client doesn't have any previous authentication token to re-connect.");
  977. return false;
  978. }
  979. if (this.AuthValues == null)
  980. {
  981. this.DebugReturn(DebugLevel.WARNING, "ReconnectToMaster() with AuthValues == null is not correct!");
  982. this.AuthValues = new AuthenticationValues();
  983. }
  984. this.AuthValues.Token = this.tokenCache;
  985. return this.Connect(this.MasterServerAddress, this.ProxyServerAddress, ServerConnection.MasterServer);
  986. }
  987. /// <summary>
  988. /// Can be used to return to a room quickly by directly reconnecting to a game server to rejoin a room.
  989. /// </summary>
  990. /// <remarks>
  991. /// Rejoining room will not send any player properties. Instead client will receive up-to-date ones from server.
  992. /// If you want to set new player properties, do it once rejoined.
  993. /// </remarks>
  994. /// <returns>False, if the conditions are not met. Then, this client does not attempt the ReconnectAndRejoin.</returns>
  995. public bool ReconnectAndRejoin()
  996. {
  997. if (this.LoadBalancingPeer.PeerState != PeerStateValue.Disconnected)
  998. {
  999. this.DebugReturn(DebugLevel.WARNING, "ReconnectAndRejoin() failed. Can only connect while in state 'Disconnected'. Current state: " + this.LoadBalancingPeer.PeerState);
  1000. return false;
  1001. }
  1002. if (string.IsNullOrEmpty(this.GameServerAddress))
  1003. {
  1004. this.DebugReturn(DebugLevel.WARNING, "ReconnectAndRejoin() failed. It seems the client wasn't connected to a game server before (no address).");
  1005. return false;
  1006. }
  1007. if (this.enterRoomParamsCache == null)
  1008. {
  1009. this.DebugReturn(DebugLevel.WARNING, "ReconnectAndRejoin() failed. It seems the client doesn't have any previous room to re-join.");
  1010. return false;
  1011. }
  1012. if (this.tokenCache == null)
  1013. {
  1014. this.DebugReturn(DebugLevel.WARNING, "ReconnectAndRejoin() failed. It seems the client doesn't have any previous authentication token to re-connect.");
  1015. return false;
  1016. }
  1017. if (this.AuthValues == null)
  1018. {
  1019. this.AuthValues = new AuthenticationValues();
  1020. }
  1021. this.AuthValues.Token = this.tokenCache;
  1022. if (!string.IsNullOrEmpty(this.GameServerAddress) && this.enterRoomParamsCache != null)
  1023. {
  1024. this.lastJoinType = JoinType.JoinRoom;
  1025. this.enterRoomParamsCache.JoinMode = JoinMode.RejoinOnly;
  1026. return this.Connect(this.GameServerAddress, this.ProxyServerAddress, ServerConnection.GameServer);
  1027. }
  1028. return false;
  1029. }
  1030. /// <summary>Disconnects the peer from a server or stays disconnected. If the client / peer was connected, a callback will be triggered.</summary>
  1031. /// <remarks>
  1032. /// Disconnect will attempt to notify the server of the client closing the connection.
  1033. ///
  1034. /// Clients that are in a room, will leave the room. If the room's playerTTL &gt; 0, the player will just become inactive (and may rejoin).
  1035. ///
  1036. /// This method will not change the current State, if this client State is PeerCreated, Disconnecting or Disconnected.
  1037. /// In those cases, there is also no callback for the disconnect. The DisconnectedCause will only change if the client was connected.
  1038. /// </remarks>
  1039. public void Disconnect(DisconnectCause cause = DisconnectCause.DisconnectByClientLogic)
  1040. {
  1041. if (this.State == ClientState.Disconnecting || this.State == ClientState.PeerCreated)
  1042. {
  1043. this.DebugReturn(DebugLevel.INFO, "Disconnect() call gets skipped due to State " + this.State + ". DisconnectedCause: " + this.DisconnectedCause + " Parameter cause: " + cause);
  1044. return;
  1045. }
  1046. if (this.State != ClientState.Disconnected)
  1047. {
  1048. this.State = ClientState.Disconnecting;
  1049. this.DisconnectedCause = cause;
  1050. this.LoadBalancingPeer.Disconnect();
  1051. }
  1052. }
  1053. /// <summary>
  1054. /// Private Disconnect variant that sets the state, too.
  1055. /// </summary>
  1056. private void DisconnectToReconnect()
  1057. {
  1058. switch (this.Server)
  1059. {
  1060. case ServerConnection.NameServer:
  1061. this.State = ClientState.DisconnectingFromNameServer;
  1062. break;
  1063. case ServerConnection.MasterServer:
  1064. this.State = ClientState.DisconnectingFromMasterServer;
  1065. break;
  1066. case ServerConnection.GameServer:
  1067. this.State = ClientState.DisconnectingFromGameServer;
  1068. break;
  1069. }
  1070. this.LoadBalancingPeer.Disconnect();
  1071. }
  1072. /// <summary>
  1073. /// Useful to test loss of connection which will end in a client timeout. This modifies LoadBalancingPeer.NetworkSimulationSettings. Read remarks.
  1074. /// </summary>
  1075. /// <remarks>
  1076. /// Use with care as this sets LoadBalancingPeer.IsSimulationEnabled.<br/>
  1077. /// Read LoadBalancingPeer.IsSimulationEnabled to check if this is on or off, if needed.<br/>
  1078. ///
  1079. /// If simulateTimeout is true, LoadBalancingPeer.NetworkSimulationSettings.IncomingLossPercentage and
  1080. /// LoadBalancingPeer.NetworkSimulationSettings.OutgoingLossPercentage will be set to 100.<br/>
  1081. /// Obviously, this overrides any network simulation settings done before.<br/>
  1082. ///
  1083. /// If you want fine-grained network simulation control, use the NetworkSimulationSettings.<br/>
  1084. ///
  1085. /// The timeout will lead to a call to <see cref="IConnectionCallbacks.OnDisconnected"/>, as usual in a client timeout.
  1086. ///
  1087. /// You could modify this method (or use NetworkSimulationSettings) to deliberately run into a server timeout by
  1088. /// just setting the OutgoingLossPercentage = 100 and the IncomingLossPercentage = 0.
  1089. /// </remarks>
  1090. /// <param name="simulateTimeout">If true, a connection loss is simulated. If false, the simulation ends.</param>
  1091. public void SimulateConnectionLoss(bool simulateTimeout)
  1092. {
  1093. this.DebugReturn(DebugLevel.WARNING, "SimulateConnectionLoss() set to: "+simulateTimeout);
  1094. if (simulateTimeout)
  1095. {
  1096. this.LoadBalancingPeer.NetworkSimulationSettings.IncomingLossPercentage = 100;
  1097. this.LoadBalancingPeer.NetworkSimulationSettings.OutgoingLossPercentage = 100;
  1098. }
  1099. this.LoadBalancingPeer.IsSimulationEnabled = simulateTimeout;
  1100. }
  1101. private bool CallAuthenticate()
  1102. {
  1103. if (this.IsUsingNameServer && this.Server != ServerConnection.NameServer && (this.AuthValues == null || this.AuthValues.Token == null))
  1104. {
  1105. this.DebugReturn(DebugLevel.ERROR, "Authenticate without Token is only allowed on Name Server. Connecting to: " + this.Server + " on: " + this.CurrentServerAddress + ". State: " + this.State);
  1106. return false;
  1107. }
  1108. if (this.AuthMode == AuthModeOption.Auth)
  1109. {
  1110. if (!this.CheckIfOpCanBeSent(OperationCode.Authenticate, this.Server, "Authenticate"))
  1111. {
  1112. return false;
  1113. }
  1114. return this.LoadBalancingPeer.OpAuthenticate(this.AppId, this.AppVersion, this.AuthValues, this.CloudRegion, (this.EnableLobbyStatistics && this.Server == ServerConnection.MasterServer));
  1115. }
  1116. else
  1117. {
  1118. if (!this.CheckIfOpCanBeSent(OperationCode.AuthenticateOnce, this.Server, "AuthenticateOnce"))
  1119. {
  1120. return false;
  1121. }
  1122. ConnectionProtocol targetProtocolPastNameServer = this.ExpectedProtocol != null ? (ConnectionProtocol) this.ExpectedProtocol : this.LoadBalancingPeer.TransportProtocol;
  1123. return this.LoadBalancingPeer.OpAuthenticateOnce(this.AppId, this.AppVersion, this.AuthValues, this.CloudRegion, this.EncryptionMode, targetProtocolPastNameServer);
  1124. }
  1125. }
  1126. /// <summary>
  1127. /// This method dispatches all available incoming commands and then sends this client's outgoing commands.
  1128. /// It uses DispatchIncomingCommands and SendOutgoingCommands to do that.
  1129. /// </summary>
  1130. /// <remarks>
  1131. /// The Photon client libraries are designed to fit easily into a game or application. The application
  1132. /// is in control of the context (thread) in which incoming events and responses are executed and has
  1133. /// full control of the creation of UDP/TCP packages.
  1134. ///
  1135. /// Sending packages and dispatching received messages are two separate tasks. Service combines them
  1136. /// into one method at the cost of control. It calls DispatchIncomingCommands and SendOutgoingCommands.
  1137. ///
  1138. /// Call this method regularly (10..50 times a second).
  1139. ///
  1140. /// This will Dispatch ANY received commands (unless a reliable command in-order is still missing) and
  1141. /// events AND will send queued outgoing commands. Fewer calls might be more effective if a device
  1142. /// cannot send many packets per second, as multiple operations might be combined into one package.
  1143. /// </remarks>
  1144. /// <example>
  1145. /// You could replace Service by:
  1146. ///
  1147. /// while (DispatchIncomingCommands()); //Dispatch until everything is Dispatched...
  1148. /// SendOutgoingCommands(); //Send a UDP/TCP package with outgoing messages
  1149. /// </example>
  1150. /// <seealso cref="PhotonPeer.DispatchIncomingCommands"/>
  1151. /// <seealso cref="PhotonPeer.SendOutgoingCommands"/>
  1152. public void Service()
  1153. {
  1154. if (this.LoadBalancingPeer != null)
  1155. {
  1156. this.LoadBalancingPeer.Service();
  1157. }
  1158. }
  1159. /// <summary>
  1160. /// While on the NameServer, this gets you the list of regional servers (short names and their IPs to ping them).
  1161. /// </summary>
  1162. /// <returns>If the operation could be sent. If false, no operation was sent (e.g. while not connected to the NameServer).</returns>
  1163. private bool OpGetRegions()
  1164. {
  1165. if (!this.CheckIfOpCanBeSent(OperationCode.GetRegions, this.Server, "GetRegions"))
  1166. {
  1167. return false;
  1168. }
  1169. bool sent = this.LoadBalancingPeer.OpGetRegions(this.AppId);
  1170. return sent;
  1171. }
  1172. /// <summary>
  1173. /// Request the rooms and online status for a list of friends. All clients should set a unique UserId before connecting. The result is available in this.FriendList.
  1174. /// </summary>
  1175. /// <remarks>
  1176. /// Used on Master Server to find the rooms played by a selected list of users.
  1177. /// The result will be stored in LoadBalancingClient.FriendList, which is null before the first server response.
  1178. ///
  1179. /// Users identify themselves by setting a UserId in the LoadBalancingClient instance.
  1180. /// This will send the ID in OpAuthenticate during connect (to master and game servers).
  1181. /// Note: Changing a player's name doesn't make sense when using a friend list.
  1182. ///
  1183. /// The list of usernames must be fetched from some other source (not provided by Photon).
  1184. ///
  1185. ///
  1186. /// Internal:<br/>
  1187. /// The server response includes 2 arrays of info (each index matching a friend from the request):<br/>
  1188. /// ParameterCode.FindFriendsResponseOnlineList = bool[] of online states<br/>
  1189. /// ParameterCode.FindFriendsResponseRoomIdList = string[] of room names (empty string if not in a room)<br/>
  1190. /// <br/>
  1191. /// The options may be used to define which state a room must match to be returned.
  1192. /// </remarks>
  1193. /// <param name="friendsToFind">Array of friend's names (make sure they are unique).</param>
  1194. /// <param name="options">Options that affect the result of the FindFriends operation.</param>
  1195. /// <returns>If the operation could be sent (requires connection).</returns>
  1196. public bool OpFindFriends(string[] friendsToFind, FindFriendsOptions options = null)
  1197. {
  1198. if (!this.CheckIfOpCanBeSent(OperationCode.FindFriends, this.Server, "FindFriends"))
  1199. {
  1200. return false;
  1201. }
  1202. if (this.IsFetchingFriendList)
  1203. {
  1204. this.DebugReturn(DebugLevel.WARNING, "OpFindFriends skipped: already fetching friends list.");
  1205. return false; // fetching friends currently, so don't do it again (avoid changing the list while fetching friends)
  1206. }
  1207. if (friendsToFind == null || friendsToFind.Length == 0)
  1208. {
  1209. this.DebugReturn(DebugLevel.ERROR, "OpFindFriends skipped: friendsToFind array is null or empty.");
  1210. return false;
  1211. }
  1212. if (friendsToFind.Length > FriendRequestListMax)
  1213. {
  1214. this.DebugReturn(DebugLevel.ERROR, string.Format("OpFindFriends skipped: friendsToFind array exceeds allowed length of {0}.", FriendRequestListMax));
  1215. return false;
  1216. }
  1217. List<string> friendsList = new List<string>(friendsToFind.Length);
  1218. for (int i = 0; i < friendsToFind.Length; i++)
  1219. {
  1220. string friendUserId = friendsToFind[i];
  1221. if (string.IsNullOrEmpty(friendUserId))
  1222. {
  1223. this.DebugReturn(DebugLevel.WARNING,
  1224. string.Format(
  1225. "friendsToFind array contains a null or empty UserId, element at position {0} skipped.",
  1226. i));
  1227. }
  1228. else if (friendUserId.Equals(UserId))
  1229. {
  1230. this.DebugReturn(DebugLevel.WARNING,
  1231. string.Format(
  1232. "friendsToFind array contains local player's UserId \"{0}\", element at position {1} skipped.",
  1233. friendUserId,
  1234. i));
  1235. }
  1236. else if (friendsList.Contains(friendUserId))
  1237. {
  1238. this.DebugReturn(DebugLevel.WARNING,
  1239. string.Format(
  1240. "friendsToFind array contains duplicate UserId \"{0}\", element at position {1} skipped.",
  1241. friendUserId,
  1242. i));
  1243. }
  1244. else
  1245. {
  1246. friendsList.Add(friendUserId);
  1247. }
  1248. }
  1249. if (friendsList.Count == 0)
  1250. {
  1251. this.DebugReturn(DebugLevel.ERROR, "OpFindFriends skipped: friends list to find is empty.");
  1252. return false;
  1253. }
  1254. string[] filteredArray = friendsList.ToArray();
  1255. bool sent = this.LoadBalancingPeer.OpFindFriends(filteredArray, options);
  1256. this.friendListRequested = sent ? filteredArray : null;
  1257. return sent;
  1258. }
  1259. /// <summary>If already connected to a Master Server, this joins the specified lobby. This request triggers an OnOperationResponse() call and the callback OnJoinedLobby().</summary>
  1260. /// <param name="lobby">The lobby to join. Use null for default lobby.</param>
  1261. /// <returns>If the operation could be sent. False, if the client is not IsConnectedAndReady or when it's not connected to a Master Server.</returns>
  1262. public bool OpJoinLobby(TypedLobby lobby)
  1263. {
  1264. if (!this.CheckIfOpCanBeSent(OperationCode.JoinLobby, this.Server, "JoinLobby"))
  1265. {
  1266. return false;
  1267. }
  1268. if (lobby == null)
  1269. {
  1270. lobby = TypedLobby.Default;
  1271. }
  1272. bool sent = this.LoadBalancingPeer.OpJoinLobby(lobby);
  1273. if (sent)
  1274. {
  1275. this.CurrentLobby = lobby;
  1276. this.State = ClientState.JoiningLobby;
  1277. }
  1278. return sent;
  1279. }
  1280. /// <summary>Opposite of joining a lobby. You don't have to explicitly leave a lobby to join another (client can be in one max, at any time).</summary>
  1281. /// <returns>If the operation could be sent (has to be connected).</returns>
  1282. public bool OpLeaveLobby()
  1283. {
  1284. if (!this.CheckIfOpCanBeSent(OperationCode.LeaveLobby, this.Server, "LeaveLobby"))
  1285. {
  1286. return false;
  1287. }
  1288. return this.LoadBalancingPeer.OpLeaveLobby();
  1289. }
  1290. /// <summary>
  1291. /// Joins a random room that matches the filter. Will callback: OnJoinedRoom or OnJoinRandomFailed.
  1292. /// </summary>
  1293. /// <remarks>
  1294. /// Used for random matchmaking. You can join any room or one with specific properties defined in opJoinRandomRoomParams.
  1295. ///
  1296. /// You can use expectedCustomRoomProperties and expectedMaxPlayers as filters for accepting rooms.
  1297. /// If you set expectedCustomRoomProperties, a room must have the exact same key values set at Custom Properties.
  1298. /// You need to define which Custom Room Properties will be available for matchmaking when you create a room.
  1299. /// See: OpCreateRoom(string roomName, RoomOptions roomOptions, TypedLobby lobby)
  1300. ///
  1301. /// This operation fails if no rooms are fitting or available (all full, closed or not visible).
  1302. /// It may also fail when actually joining the room which was found. Rooms may close, become full or empty anytime.
  1303. ///
  1304. /// This method can only be called while the client is connected to a Master Server so you should
  1305. /// implement the callback OnConnectedToMaster.
  1306. /// Check the return value to make sure the operation will be called on the server.
  1307. /// Note: There will be no callbacks if this method returned false.
  1308. ///
  1309. ///
  1310. /// This client's State is set to ClientState.Joining immediately, when the operation could
  1311. /// be called. In the background, the client will switch servers and call various related operations.
  1312. ///
  1313. /// When you're in the room, this client's State will become ClientState.Joined.
  1314. ///
  1315. ///
  1316. /// When entering a room, this client's Player Custom Properties will be sent to the room.
  1317. /// Use LocalPlayer.SetCustomProperties to set them, even while not yet in the room.
  1318. /// Note that the player properties will be cached locally and are not wiped when leaving a room.
  1319. ///
  1320. /// More about matchmaking:
  1321. /// https://doc.photonengine.com/en-us/realtime/current/reference/matchmaking-and-lobby
  1322. ///
  1323. /// You can define an array of expectedUsers, to block player slots in the room for these users.
  1324. /// The corresponding feature in Photon is called "Slot Reservation" and can be found in the doc pages.
  1325. /// </remarks>
  1326. /// <param name="opJoinRandomRoomParams">Optional definition of properties to filter rooms in random matchmaking.</param>
  1327. /// <returns>If the operation could be sent currently (requires connection to Master Server).</returns>
  1328. public bool OpJoinRandomRoom(OpJoinRandomRoomParams opJoinRandomRoomParams = null)
  1329. {
  1330. if (!this.CheckIfOpCanBeSent(OperationCode.JoinRandomGame, this.Server, "JoinRandomGame"))
  1331. {
  1332. return false;
  1333. }
  1334. if (opJoinRandomRoomParams == null)
  1335. {
  1336. opJoinRandomRoomParams = new OpJoinRandomRoomParams();
  1337. }
  1338. this.enterRoomParamsCache = new EnterRoomParams();
  1339. this.enterRoomParamsCache.Lobby = opJoinRandomRoomParams.TypedLobby;
  1340. this.enterRoomParamsCache.ExpectedUsers = opJoinRandomRoomParams.ExpectedUsers;
  1341. bool sending = this.LoadBalancingPeer.OpJoinRandomRoom(opJoinRandomRoomParams);
  1342. if (sending)
  1343. {
  1344. this.lastJoinType = JoinType.JoinRandomRoom;
  1345. this.State = ClientState.Joining;
  1346. }
  1347. return sending;
  1348. }
  1349. /// <summary>
  1350. /// Attempts to join a room that matches the specified filter and creates a room if none found.
  1351. /// </summary>
  1352. /// <remarks>
  1353. /// This operation is a combination of filter-based random matchmaking with the option to create a new room,
  1354. /// if no fitting room exists.
  1355. /// The benefit of that is that the room creation is done by the same operation and the room can be found
  1356. /// by the very next client, looking for similar rooms.
  1357. ///
  1358. /// There are separate parameters for joining and creating a room.
  1359. ///
  1360. /// This method can only be called while connected to a Master Server.
  1361. /// This client's State is set to ClientState.Joining immediately.
  1362. ///
  1363. /// Either IMatchmakingCallbacks.OnJoinedRoom or IMatchmakingCallbacks.OnCreatedRoom get called.
  1364. ///
  1365. /// More about matchmaking:
  1366. /// https://doc.photonengine.com/en-us/realtime/current/reference/matchmaking-and-lobby
  1367. ///
  1368. /// Check the return value to make sure the operation will be called on the server.
  1369. /// Note: There will be no callbacks if this method returned false.
  1370. /// </remarks>
  1371. /// <returns>If the operation will be sent (requires connection to Master Server).</returns>
  1372. public bool OpJoinRandomOrCreateRoom(OpJoinRandomRoomParams opJoinRandomRoomParams, EnterRoomParams createRoomParams)
  1373. {
  1374. if (!this.CheckIfOpCanBeSent(OperationCode.JoinRandomGame, this.Server, "OpJoinRandomOrCreateRoom"))
  1375. {
  1376. return false;
  1377. }
  1378. if (opJoinRandomRoomParams == null)
  1379. {
  1380. opJoinRandomRoomParams = new OpJoinRandomRoomParams();
  1381. }
  1382. if (createRoomParams == null)
  1383. {
  1384. createRoomParams = new EnterRoomParams();
  1385. }
  1386. createRoomParams.JoinMode = JoinMode.CreateIfNotExists;
  1387. this.enterRoomParamsCache = createRoomParams;
  1388. this.enterRoomParamsCache.Lobby = opJoinRandomRoomParams.TypedLobby;
  1389. this.enterRoomParamsCache.ExpectedUsers = opJoinRandomRoomParams.ExpectedUsers;
  1390. bool sending = this.LoadBalancingPeer.OpJoinRandomOrCreateRoom(opJoinRandomRoomParams, createRoomParams);
  1391. if (sending)
  1392. {
  1393. this.lastJoinType = JoinType.JoinRandomOrCreateRoom;
  1394. this.State = ClientState.Joining;
  1395. }
  1396. return sending;
  1397. }
  1398. /// <summary>
  1399. /// Creates a new room. Will callback: OnCreatedRoom and OnJoinedRoom or OnCreateRoomFailed.
  1400. /// </summary>
  1401. /// <remarks>
  1402. /// When successful, the client will enter the specified room and callback both OnCreatedRoom and OnJoinedRoom.
  1403. /// In all error cases, OnCreateRoomFailed gets called.
  1404. ///
  1405. /// Creating a room will fail if the room name is already in use or when the RoomOptions clashing
  1406. /// with one another. Check the EnterRoomParams reference for the various room creation options.
  1407. ///
  1408. ///
  1409. /// This method can only be called while the client is connected to a Master Server so you should
  1410. /// implement the callback OnConnectedToMaster.
  1411. /// Check the return value to make sure the operation will be called on the server.
  1412. /// Note: There will be no callbacks if this method returned false.
  1413. ///
  1414. ///
  1415. /// When you're in the room, this client's State will become ClientState.Joined.
  1416. ///
  1417. ///
  1418. /// When entering a room, this client's Player Custom Properties will be sent to the room.
  1419. /// Use LocalPlayer.SetCustomProperties to set them, even while not yet in the room.
  1420. /// Note that the player properties will be cached locally and are not wiped when leaving a room.
  1421. ///
  1422. /// You can define an array of expectedUsers, to block player slots in the room for these users.
  1423. /// The corresponding feature in Photon is called "Slot Reservation" and can be found in the doc pages.
  1424. /// </remarks>
  1425. /// <param name="enterRoomParams">Definition of properties for the room to create.</param>
  1426. /// <returns>If the operation could be sent currently (requires connection to Master Server).</returns>
  1427. public bool OpCreateRoom(EnterRoomParams enterRoomParams)
  1428. {
  1429. if (!this.CheckIfOpCanBeSent(OperationCode.CreateGame, this.Server, "CreateGame"))
  1430. {
  1431. return false;
  1432. }
  1433. bool onGameServer = this.Server == ServerConnection.GameServer;
  1434. enterRoomParams.OnGameServer = onGameServer;
  1435. if (!onGameServer)
  1436. {
  1437. this.enterRoomParamsCache = enterRoomParams;
  1438. }
  1439. bool sending = this.LoadBalancingPeer.OpCreateRoom(enterRoomParams);
  1440. if (sending)
  1441. {
  1442. this.lastJoinType = JoinType.CreateRoom;
  1443. this.State = ClientState.Joining;
  1444. }
  1445. return sending;
  1446. }
  1447. /// <summary>
  1448. /// Joins a specific room by name and creates it on demand. Will callback: OnJoinedRoom or OnJoinRoomFailed.
  1449. /// </summary>
  1450. /// <remarks>
  1451. /// Useful when players make up a room name to meet in:
  1452. /// All involved clients call the same method and whoever is first, also creates the room.
  1453. ///
  1454. /// When successful, the client will enter the specified room.
  1455. /// The client which creates the room, will callback both OnCreatedRoom and OnJoinedRoom.
  1456. /// Clients that join an existing room will only callback OnJoinedRoom.
  1457. /// In all error cases, OnJoinRoomFailed gets called.
  1458. ///
  1459. /// Joining a room will fail, if the room is full, closed or when the user
  1460. /// already is present in the room (checked by userId).
  1461. ///
  1462. /// To return to a room, use OpRejoinRoom.
  1463. ///
  1464. /// This method can only be called while the client is connected to a Master Server so you should
  1465. /// implement the callback OnConnectedToMaster.
  1466. /// Check the return value to make sure the operation will be called on the server.
  1467. /// Note: There will be no callbacks if this method returned false.
  1468. ///
  1469. /// This client's State is set to ClientState.Joining immediately, when the operation could
  1470. /// be called. In the background, the client will switch servers and call various related operations.
  1471. ///
  1472. /// When you're in the room, this client's State will become ClientState.Joined.
  1473. ///
  1474. ///
  1475. /// If you set room properties in roomOptions, they get ignored when the room is existing already.
  1476. /// This avoids changing the room properties by late joining players.
  1477. ///
  1478. /// When entering a room, this client's Player Custom Properties will be sent to the room.
  1479. /// Use LocalPlayer.SetCustomProperties to set them, even while not yet in the room.
  1480. /// Note that the player properties will be cached locally and are not wiped when leaving a room.
  1481. ///
  1482. /// You can define an array of expectedUsers, to block player slots in the room for these users.
  1483. /// The corresponding feature in Photon is called "Slot Reservation" and can be found in the doc pages.
  1484. /// </remarks>
  1485. /// <param name="enterRoomParams">Definition of properties for the room to create or join.</param>
  1486. /// <returns>If the operation could be sent currently (requires connection to Master Server).</returns>
  1487. public bool OpJoinOrCreateRoom(EnterRoomParams enterRoomParams)
  1488. {
  1489. if (!this.CheckIfOpCanBeSent(OperationCode.JoinGame, this.Server, "JoinOrCreateRoom"))
  1490. {
  1491. return false;
  1492. }
  1493. bool onGameServer = this.Server == ServerConnection.GameServer;
  1494. enterRoomParams.JoinMode = JoinMode.CreateIfNotExists;
  1495. enterRoomParams.OnGameServer = onGameServer;
  1496. if (!onGameServer)
  1497. {
  1498. this.enterRoomParamsCache = enterRoomParams;
  1499. }
  1500. bool sending = this.LoadBalancingPeer.OpJoinRoom(enterRoomParams);
  1501. if (sending)
  1502. {
  1503. this.lastJoinType = JoinType.JoinOrCreateRoom;
  1504. this.State = ClientState.Joining;
  1505. }
  1506. return sending;
  1507. }
  1508. /// <summary>
  1509. /// Joins a room by name. Will callback: OnJoinedRoom or OnJoinRoomFailed.
  1510. /// </summary>
  1511. /// <remarks>
  1512. /// Useful when using lobbies or when players follow friends or invite each other.
  1513. ///
  1514. /// When successful, the client will enter the specified room and callback via OnJoinedRoom.
  1515. /// In all error cases, OnJoinRoomFailed gets called.
  1516. ///
  1517. /// Joining a room will fail if the room is full, closed, not existing or when the user
  1518. /// already is present in the room (checked by userId).
  1519. ///
  1520. /// To return to a room, use OpRejoinRoom.
  1521. /// When players invite each other and it's unclear who's first to respond, use OpJoinOrCreateRoom instead.
  1522. ///
  1523. /// This method can only be called while the client is connected to a Master Server so you should
  1524. /// implement the callback OnConnectedToMaster.
  1525. /// Check the return value to make sure the operation will be called on the server.
  1526. /// Note: There will be no callbacks if this method returned false.
  1527. ///
  1528. /// A room's name has to be unique (per region, appid and gameversion).
  1529. /// When your title uses a global matchmaking or invitations (e.g. an external solution),
  1530. /// keep regions and the game versions in mind to join a room.
  1531. ///
  1532. ///
  1533. /// This client's State is set to ClientState.Joining immediately, when the operation could
  1534. /// be called. In the background, the client will switch servers and call various related operations.
  1535. ///
  1536. /// When you're in the room, this client's State will become ClientState.Joined.
  1537. ///
  1538. ///
  1539. /// When entering a room, this client's Player Custom Properties will be sent to the room.
  1540. /// Use LocalPlayer.SetCustomProperties to set them, even while not yet in the room.
  1541. /// Note that the player properties will be cached locally and are not wiped when leaving a room.
  1542. ///
  1543. /// You can define an array of expectedUsers, to reserve player slots in the room for friends or party members.
  1544. /// The corresponding feature in Photon is called "Slot Reservation" and can be found in the doc pages.
  1545. /// </remarks>
  1546. /// <param name="enterRoomParams">Definition of properties for the room to join.</param>
  1547. /// <returns>If the operation could be sent currently (requires connection to Master Server).</returns>
  1548. public bool OpJoinRoom(EnterRoomParams enterRoomParams)
  1549. {
  1550. if (!this.CheckIfOpCanBeSent(OperationCode.JoinGame, this.Server, "JoinRoom"))
  1551. {
  1552. return false;
  1553. }
  1554. bool onGameServer = this.Server == ServerConnection.GameServer;
  1555. enterRoomParams.OnGameServer = onGameServer;
  1556. if (!onGameServer)
  1557. {
  1558. this.enterRoomParamsCache = enterRoomParams;
  1559. }
  1560. bool sending = this.LoadBalancingPeer.OpJoinRoom(enterRoomParams);
  1561. if (sending)
  1562. {
  1563. this.lastJoinType = enterRoomParams.JoinMode == JoinMode.CreateIfNotExists ? JoinType.JoinOrCreateRoom : JoinType.JoinRoom;
  1564. this.State = ClientState.Joining;
  1565. }
  1566. return sending;
  1567. }
  1568. /// <summary>
  1569. /// Rejoins a room by roomName (using the userID internally to return). Will callback: OnJoinedRoom or OnJoinRoomFailed.
  1570. /// </summary>
  1571. /// <remarks>
  1572. /// Used to return to a room, before this user was removed from the players list.
  1573. /// Internally, the userID will be checked by the server, to make sure this user is in the room (active or inactice).
  1574. ///
  1575. /// In contrast to join, this operation never adds a players to a room. It will attempt to retake an existing
  1576. /// spot in the playerlist or fail. This makes sure the client doean't accidentally join a room when the
  1577. /// game logic meant to re-activate an existing actor in an existing room.
  1578. ///
  1579. /// This method will fail on the server, when the room does not exist, can't be loaded (persistent rooms) or
  1580. /// when the userId is not in the player list of this room. This will lead to a callback OnJoinRoomFailed.
  1581. ///
  1582. /// Rejoining room will not send any player properties. Instead client will receive up-to-date ones from server.
  1583. /// If you want to set new player properties, do it once rejoined.
  1584. /// </remarks>
  1585. public bool OpRejoinRoom(string roomName)
  1586. {
  1587. if (!this.CheckIfOpCanBeSent(OperationCode.JoinGame, this.Server, "RejoinRoom"))
  1588. {
  1589. return false;
  1590. }
  1591. bool onGameServer = this.Server == ServerConnection.GameServer;
  1592. EnterRoomParams opParams = new EnterRoomParams();
  1593. this.enterRoomParamsCache = opParams;
  1594. opParams.RoomName = roomName;
  1595. opParams.OnGameServer = onGameServer;
  1596. opParams.JoinMode = JoinMode.RejoinOnly;
  1597. bool sending = this.LoadBalancingPeer.OpJoinRoom(opParams);
  1598. if (sending)
  1599. {
  1600. this.lastJoinType = JoinType.JoinRoom;
  1601. this.State = ClientState.Joining;
  1602. }
  1603. return sending;
  1604. }
  1605. /// <summary>
  1606. /// Leaves the current room, optionally telling the server that the user is just becoming inactive. Will callback: OnLeftRoom.
  1607. /// </summary>
  1608. ///
  1609. /// <remarks>
  1610. /// OpLeaveRoom skips execution when the room is null or the server is not GameServer or the client is disconnecting from GS already.
  1611. /// OpLeaveRoom returns false in those cases and won't change the state, so check return of this method.
  1612. ///
  1613. /// In some cases, this method will skip the OpLeave call and just call Disconnect(),
  1614. /// which not only leaves the room but also the server. Disconnect also triggers a leave and so that workflow is is quicker.
  1615. /// </remarks>
  1616. /// <param name="becomeInactive">If true, this player becomes inactive in the game and can return later (if PlayerTTL of the room is != 0).</param>
  1617. /// <param name="sendAuthCookie">WebFlag: Securely transmit the encrypted object AuthCookie to the web service in PathLeave webhook when available</param>
  1618. /// <returns>If the current room could be left (impossible while not in a room).</returns>
  1619. public bool OpLeaveRoom(bool becomeInactive, bool sendAuthCookie = false)
  1620. {
  1621. if (!this.CheckIfOpCanBeSent(OperationCode.Leave, this.Server, "LeaveRoom"))
  1622. {
  1623. return false;
  1624. }
  1625. this.State = ClientState.Leaving;
  1626. this.GameServerAddress = String.Empty;
  1627. this.enterRoomParamsCache = null;
  1628. return this.LoadBalancingPeer.OpLeaveRoom(becomeInactive, sendAuthCookie);
  1629. }
  1630. /// <summary>Gets a list of rooms matching the (non empty) SQL filter for the given SQL-typed lobby.</summary>
  1631. /// <remarks>
  1632. /// Operation is only available for lobbies of type SqlLobby and the filter can not be empty.
  1633. /// It will check those conditions and fail locally, returning false.
  1634. ///
  1635. /// This is an async request which triggers a OnOperationResponse() call.
  1636. /// </remarks>
  1637. /// <see cref="https://doc.photonengine.com/en-us/realtime/current/reference/matchmaking-and-lobby#sql_lobby_type"/>
  1638. /// <param name="typedLobby">The lobby to query. Has to be of type SqlLobby.</param>
  1639. /// <param name="sqlLobbyFilter">The sql query statement.</param>
  1640. /// <returns>If the operation could be sent (has to be connected).</returns>
  1641. public bool OpGetGameList(TypedLobby typedLobby, string sqlLobbyFilter)
  1642. {
  1643. if (!this.CheckIfOpCanBeSent(OperationCode.GetGameList, this.Server, "GetGameList"))
  1644. {
  1645. return false;
  1646. }
  1647. if (string.IsNullOrEmpty(sqlLobbyFilter))
  1648. {
  1649. this.DebugReturn(DebugLevel.ERROR, "Operation GetGameList requires a filter.");
  1650. return false;
  1651. }
  1652. if (typedLobby.Type != LobbyType.SqlLobby)
  1653. {
  1654. this.DebugReturn(DebugLevel.ERROR, "Operation GetGameList can only be used for lobbies of type SqlLobby.");
  1655. return false;
  1656. }
  1657. return this.LoadBalancingPeer.OpGetGameList(typedLobby, sqlLobbyFilter);
  1658. }
  1659. /// <summary>
  1660. /// Updates and synchronizes a Player's Custom Properties. Optionally, expectedProperties can be provided as condition.
  1661. /// </summary>
  1662. /// <remarks>
  1663. /// Custom Properties are a set of string keys and arbitrary values which is synchronized
  1664. /// for the players in a Room. They are available when the client enters the room, as
  1665. /// they are in the response of OpJoin and OpCreate.
  1666. ///
  1667. /// Custom Properties either relate to the (current) Room or a Player (in that Room).
  1668. ///
  1669. /// Both classes locally cache the current key/values and make them available as
  1670. /// property: CustomProperties. This is provided only to read them.
  1671. /// You must use the method SetCustomProperties to set/modify them.
  1672. ///
  1673. /// Any client can set any Custom Properties anytime (when in a room).
  1674. /// It's up to the game logic to organize how they are best used.
  1675. ///
  1676. /// You should call SetCustomProperties only with key/values that are new or changed. This reduces
  1677. /// traffic and performance.
  1678. ///
  1679. /// Unless you define some expectedProperties, setting key/values is always permitted.
  1680. /// In this case, the property-setting client will not receive the new values from the server but
  1681. /// instead update its local cache in SetCustomProperties.
  1682. ///
  1683. /// If you define expectedProperties, the server will skip updates if the server property-cache
  1684. /// does not contain all expectedProperties with the same values.
  1685. /// In this case, the property-setting client will get an update from the server and update it's
  1686. /// cached key/values at about the same time as everyone else.
  1687. ///
  1688. /// The benefit of using expectedProperties can be only one client successfully sets a key from
  1689. /// one known value to another.
  1690. /// As example: Store who owns an item in a Custom Property "ownedBy". It's 0 initally.
  1691. /// When multiple players reach the item, they all attempt to change "ownedBy" from 0 to their
  1692. /// actorNumber. If you use expectedProperties {"ownedBy", 0} as condition, the first player to
  1693. /// take the item will have it (and the others fail to set the ownership).
  1694. ///
  1695. /// Properties get saved with the game state for Turnbased games (which use IsPersistent = true).
  1696. /// </remarks>
  1697. /// <param name="actorNr">Defines which player the Custom Properties belong to. ActorID of a player.</param>
  1698. /// <param name="propertiesToSet">Hashtable of Custom Properties that changes.</param>
  1699. /// <param name="expectedProperties">Provide some keys/values to use as condition for setting the new values. Client must be in room.</param>
  1700. /// <param name="webFlags">Defines if the set properties should be forwarded to a WebHook. Client must be in room.</param>
  1701. /// <returns>
  1702. /// False if propertiesToSet is null or empty or have zero string keys.
  1703. /// If not in a room, returns true if local player and expectedProperties and webFlags are null.
  1704. /// False if actorNr is lower than or equal to zero.
  1705. /// Otherwise, returns if the operation could be sent to the server.
  1706. /// </returns>
  1707. public bool OpSetCustomPropertiesOfActor(int actorNr, Hashtable propertiesToSet, Hashtable expectedProperties = null, WebFlags webFlags = null)
  1708. {
  1709. if (propertiesToSet == null || propertiesToSet.Count == 0)
  1710. {
  1711. this.DebugReturn(DebugLevel.ERROR, "OpSetCustomPropertiesOfActor() failed. propertiesToSet must not be null nor empty.");
  1712. return false;
  1713. }
  1714. if (this.CurrentRoom == null)
  1715. {
  1716. // if you attempt to set this player's values without conditions, then fine:
  1717. if (expectedProperties == null && webFlags == null && this.LocalPlayer != null && this.LocalPlayer.ActorNumber == actorNr)
  1718. {
  1719. return this.LocalPlayer.SetCustomProperties(propertiesToSet);
  1720. }
  1721. if (this.LoadBalancingPeer.DebugOut >= DebugLevel.ERROR)
  1722. {
  1723. this.DebugReturn(DebugLevel.ERROR, "OpSetCustomPropertiesOfActor() failed. To use expectedProperties or webForward, you have to be in a room. State: " + this.State);
  1724. }
  1725. return false;
  1726. }
  1727. Hashtable customActorProperties = new Hashtable();
  1728. customActorProperties.MergeStringKeys(propertiesToSet);
  1729. if (customActorProperties.Count == 0)
  1730. {
  1731. this.DebugReturn(DebugLevel.ERROR, "OpSetCustomPropertiesOfActor() failed. Only string keys allowed for custom properties.");
  1732. return false;
  1733. }
  1734. return this.OpSetPropertiesOfActor(actorNr, customActorProperties, expectedProperties, webFlags);
  1735. }
  1736. /// <summary>Internally used to cache and set properties (including well known properties).</summary>
  1737. /// <remarks>Requires being in a room (because this attempts to send an operation which will fail otherwise).</remarks>
  1738. protected internal bool OpSetPropertiesOfActor(int actorNr, Hashtable actorProperties, Hashtable expectedProperties = null, WebFlags webFlags = null)
  1739. {
  1740. if (!this.CheckIfOpCanBeSent(OperationCode.SetProperties, this.Server, "SetProperties"))
  1741. {
  1742. return false;
  1743. }
  1744. if (actorProperties == null || actorProperties.Count == 0)
  1745. {
  1746. this.DebugReturn(DebugLevel.ERROR, "OpSetPropertiesOfActor() failed. actorProperties must not be null nor empty.");
  1747. return false;
  1748. }
  1749. bool res = this.LoadBalancingPeer.OpSetPropertiesOfActor(actorNr, actorProperties, expectedProperties, webFlags);
  1750. if (res && !this.CurrentRoom.BroadcastPropertiesChangeToAll && (expectedProperties == null || expectedProperties.Count == 0))
  1751. {
  1752. Player target = this.CurrentRoom.GetPlayer(actorNr);
  1753. if (target != null)
  1754. {
  1755. target.InternalCacheProperties(actorProperties);
  1756. this.InRoomCallbackTargets.OnPlayerPropertiesUpdate(target, actorProperties);
  1757. }
  1758. }
  1759. return res;
  1760. }
  1761. /// <summary>
  1762. /// Updates and synchronizes this Room's Custom Properties. Optionally, expectedProperties can be provided as condition.
  1763. /// </summary>
  1764. /// <remarks>
  1765. /// Custom Properties are a set of string keys and arbitrary values which is synchronized
  1766. /// for the players in a Room. They are available when the client enters the room, as
  1767. /// they are in the response of OpJoin and OpCreate.
  1768. ///
  1769. /// Custom Properties either relate to the (current) Room or a Player (in that Room).
  1770. ///
  1771. /// Both classes locally cache the current key/values and make them available as
  1772. /// property: CustomProperties. This is provided only to read them.
  1773. /// You must use the method SetCustomProperties to set/modify them.
  1774. ///
  1775. /// Any client can set any Custom Properties anytime (when in a room).
  1776. /// It's up to the game logic to organize how they are best used.
  1777. ///
  1778. /// You should call SetCustomProperties only with key/values that are new or changed. This reduces
  1779. /// traffic and performance.
  1780. ///
  1781. /// Unless you define some expectedProperties, setting key/values is always permitted.
  1782. /// In this case, the property-setting client will not receive the new values from the server but
  1783. /// instead update its local cache in SetCustomProperties.
  1784. ///
  1785. /// If you define expectedProperties, the server will skip updates if the server property-cache
  1786. /// does not contain all expectedProperties with the same values.
  1787. /// In this case, the property-setting client will get an update from the server and update it's
  1788. /// cached key/values at about the same time as everyone else.
  1789. ///
  1790. /// The benefit of using expectedProperties can be only one client successfully sets a key from
  1791. /// one known value to another.
  1792. /// As example: Store who owns an item in a Custom Property "ownedBy". It's 0 initally.
  1793. /// When multiple players reach the item, they all attempt to change "ownedBy" from 0 to their
  1794. /// actorNumber. If you use expectedProperties {"ownedBy", 0} as condition, the first player to
  1795. /// take the item will have it (and the others fail to set the ownership).
  1796. ///
  1797. /// Properties get saved with the game state for Turnbased games (which use IsPersistent = true).
  1798. /// </remarks>
  1799. /// <param name="propertiesToSet">Hashtable of Custom Properties that changes.</param>
  1800. /// <param name="expectedProperties">Provide some keys/values to use as condition for setting the new values.</param>
  1801. /// <param name="webFlags">Defines web flags for an optional PathProperties webhook.</param>
  1802. /// <returns>
  1803. /// False if propertiesToSet is null or empty or have zero string keys.
  1804. /// Otherwise, returns if the operation could be sent to the server.
  1805. /// </returns>
  1806. public bool OpSetCustomPropertiesOfRoom(Hashtable propertiesToSet, Hashtable expectedProperties = null, WebFlags webFlags = null)
  1807. {
  1808. if (propertiesToSet == null || propertiesToSet.Count == 0)
  1809. {
  1810. this.DebugReturn(DebugLevel.ERROR, "OpSetCustomPropertiesOfRoom() failed. propertiesToSet must not be null nor empty.");
  1811. return false;
  1812. }
  1813. Hashtable customGameProps = new Hashtable();
  1814. customGameProps.MergeStringKeys(propertiesToSet);
  1815. if (customGameProps.Count == 0)
  1816. {
  1817. this.DebugReturn(DebugLevel.ERROR, "OpSetCustomPropertiesOfRoom() failed. Only string keys are allowed for custom properties.");
  1818. return false;
  1819. }
  1820. return this.OpSetPropertiesOfRoom(customGameProps, expectedProperties, webFlags);
  1821. }
  1822. protected internal bool OpSetPropertyOfRoom(byte propCode, object value)
  1823. {
  1824. Hashtable properties = new Hashtable();
  1825. properties[propCode] = value;
  1826. return this.OpSetPropertiesOfRoom(properties);
  1827. }
  1828. /// <summary>Internally used to cache and set properties (including well known properties).</summary>
  1829. /// <remarks>Requires being in a room (because this attempts to send an operation which will fail otherwise).</remarks>
  1830. protected internal bool OpSetPropertiesOfRoom(Hashtable gameProperties, Hashtable expectedProperties = null, WebFlags webFlags = null)
  1831. {
  1832. if (!this.CheckIfOpCanBeSent(OperationCode.SetProperties, this.Server, "SetProperties"))
  1833. {
  1834. return false;
  1835. }
  1836. if (gameProperties == null || gameProperties.Count == 0)
  1837. {
  1838. this.DebugReturn(DebugLevel.ERROR, "OpSetPropertiesOfRoom() failed. gameProperties must not be null nor empty.");
  1839. return false;
  1840. }
  1841. bool res = this.LoadBalancingPeer.OpSetPropertiesOfRoom(gameProperties, expectedProperties, webFlags);
  1842. if (res && !this.CurrentRoom.BroadcastPropertiesChangeToAll && (expectedProperties == null || expectedProperties.Count == 0))
  1843. {
  1844. this.CurrentRoom.InternalCacheProperties(gameProperties);
  1845. this.InRoomCallbackTargets.OnRoomPropertiesUpdate(gameProperties);
  1846. }
  1847. return res;
  1848. }
  1849. /// <summary>
  1850. /// Send an event with custom code/type and any content to the other players in the same room.
  1851. /// </summary>
  1852. /// <param name="eventCode">Identifies this type of event (and the content). Your game's event codes can start with 0.</param>
  1853. /// <param name="customEventContent">Any serializable datatype (including Hashtable like the other OpRaiseEvent overloads).</param>
  1854. /// <param name="raiseEventOptions">Contains used send options. If you pass null, the default options will be used.</param>
  1855. /// <param name="sendOptions">Send options for reliable, encryption etc</param>
  1856. /// <returns>If operation could be enqueued for sending. Sent when calling: Service or SendOutgoingCommands.</returns>
  1857. public virtual bool OpRaiseEvent(byte eventCode, object customEventContent, RaiseEventOptions raiseEventOptions, SendOptions sendOptions)
  1858. {
  1859. if (!this.CheckIfOpCanBeSent(OperationCode.RaiseEvent, this.Server, "RaiseEvent"))
  1860. {
  1861. return false;
  1862. }
  1863. return this.LoadBalancingPeer.OpRaiseEvent(eventCode, customEventContent, raiseEventOptions, sendOptions);
  1864. }
  1865. /// <summary>
  1866. /// Operation to handle this client's interest groups (for events in room).
  1867. /// </summary>
  1868. /// <remarks>
  1869. /// Note the difference between passing null and byte[0]:
  1870. /// null won't add/remove any groups.
  1871. /// byte[0] will add/remove all (existing) groups.
  1872. /// First, removing groups is executed. This way, you could leave all groups and join only the ones provided.
  1873. ///
  1874. /// Changes become active not immediately but when the server executes this operation (approximately RTT/2).
  1875. /// </remarks>
  1876. /// <param name="groupsToRemove">Groups to remove from interest. Null will not remove any. A byte[0] will remove all.</param>
  1877. /// <param name="groupsToAdd">Groups to add to interest. Null will not add any. A byte[0] will add all current.</param>
  1878. /// <returns>If operation could be enqueued for sending. Sent when calling: Service or SendOutgoingCommands.</returns>
  1879. public virtual bool OpChangeGroups(byte[] groupsToRemove, byte[] groupsToAdd)
  1880. {
  1881. if (!this.CheckIfOpCanBeSent(OperationCode.ChangeGroups, this.Server, "ChangeGroups"))
  1882. {
  1883. return false;
  1884. }
  1885. return this.LoadBalancingPeer.OpChangeGroups(groupsToRemove, groupsToAdd);
  1886. }
  1887. #endregion
  1888. #region Helpers
  1889. /// <summary>
  1890. /// Privately used to read-out properties coming from the server in events and operation responses (which might be a bit tricky).
  1891. /// </summary>
  1892. private void ReadoutProperties(Hashtable gameProperties, Hashtable actorProperties, int targetActorNr)
  1893. {
  1894. // read game properties and cache them locally
  1895. if (this.CurrentRoom != null && gameProperties != null)
  1896. {
  1897. this.CurrentRoom.InternalCacheProperties(gameProperties);
  1898. if (this.InRoom)
  1899. {
  1900. this.InRoomCallbackTargets.OnRoomPropertiesUpdate(gameProperties);
  1901. }
  1902. }
  1903. if (actorProperties != null && actorProperties.Count > 0)
  1904. {
  1905. if (targetActorNr > 0)
  1906. {
  1907. // we have a single entry in the actorProperties with one user's name
  1908. // targets MUST exist before you set properties
  1909. Player target = this.CurrentRoom.GetPlayer(targetActorNr);
  1910. if (target != null)
  1911. {
  1912. Hashtable props = this.ReadoutPropertiesForActorNr(actorProperties, targetActorNr);
  1913. target.InternalCacheProperties(props);
  1914. this.InRoomCallbackTargets.OnPlayerPropertiesUpdate(target, props);
  1915. }
  1916. }
  1917. else
  1918. {
  1919. // in this case, we've got a key-value pair per actor (each
  1920. // value is a hashtable with the actor's properties then)
  1921. int actorNr;
  1922. Hashtable props;
  1923. string newName;
  1924. Player target;
  1925. foreach (object key in actorProperties.Keys)
  1926. {
  1927. actorNr = (int)key;
  1928. if (actorNr == 0)
  1929. {
  1930. continue;
  1931. }
  1932. props = (Hashtable)actorProperties[key];
  1933. newName = (string)props[ActorProperties.PlayerName];
  1934. target = this.CurrentRoom.GetPlayer(actorNr);
  1935. if (target == null)
  1936. {
  1937. target = this.CreatePlayer(newName, actorNr, false, props);
  1938. this.CurrentRoom.StorePlayer(target);
  1939. }
  1940. target.InternalCacheProperties(props);
  1941. }
  1942. }
  1943. }
  1944. }
  1945. /// <summary>
  1946. /// Privately used only to read properties for a distinct actor (which might be the hashtable OR a key-pair value IN the actorProperties).
  1947. /// </summary>
  1948. private Hashtable ReadoutPropertiesForActorNr(Hashtable actorProperties, int actorNr)
  1949. {
  1950. if (actorProperties.ContainsKey(actorNr))
  1951. {
  1952. return (Hashtable)actorProperties[actorNr];
  1953. }
  1954. return actorProperties;
  1955. }
  1956. /// <summary>
  1957. /// Internally used to set the LocalPlayer's ID (from -1 to the actual in-room ID).
  1958. /// </summary>
  1959. /// <param name="newID">New actor ID (a.k.a actorNr) assigned when joining a room.</param>
  1960. public void ChangeLocalID(int newID)
  1961. {
  1962. if (this.LocalPlayer == null)
  1963. {
  1964. this.DebugReturn(DebugLevel.WARNING, string.Format("Local actor is null or not in mActors! mLocalActor: {0} mActors==null: {1} newID: {2}", this.LocalPlayer, this.CurrentRoom.Players == null, newID));
  1965. }
  1966. if (this.CurrentRoom == null)
  1967. {
  1968. // change to new actor/player ID and make sure the player does not have a room reference left
  1969. this.LocalPlayer.ChangeLocalID(newID);
  1970. this.LocalPlayer.RoomReference = null;
  1971. }
  1972. else
  1973. {
  1974. // remove old actorId from actor list
  1975. this.CurrentRoom.RemovePlayer(this.LocalPlayer);
  1976. // change to new actor/player ID
  1977. this.LocalPlayer.ChangeLocalID(newID);
  1978. // update the room's list with the new reference
  1979. this.CurrentRoom.StorePlayer(this.LocalPlayer);
  1980. }
  1981. }
  1982. /// <summary>
  1983. /// Called internally, when a game was joined or created on the game server successfully.
  1984. /// </summary>
  1985. /// <remarks>
  1986. /// This reads the response, finds out the local player's actorNumber (a.k.a. Player.ID) and applies properties of the room and players.
  1987. /// Errors for these operations are to be handled before this method is called.
  1988. /// </remarks>
  1989. /// <param name="operationResponse">Contains the server's response for an operation called by this peer.</param>
  1990. private void GameEnteredOnGameServer(OperationResponse operationResponse)
  1991. {
  1992. this.CurrentRoom = this.CreateRoom(this.enterRoomParamsCache.RoomName, this.enterRoomParamsCache.RoomOptions);
  1993. this.CurrentRoom.LoadBalancingClient = this;
  1994. // first change the local id, instead of first updating the actorList since actorList uses ID to update itself
  1995. // the local player's actor-properties are not returned in join-result. add this player to the list
  1996. int localActorNr = (int)operationResponse[ParameterCode.ActorNr];
  1997. this.ChangeLocalID(localActorNr);
  1998. if (operationResponse.Parameters.ContainsKey(ParameterCode.ActorList))
  1999. {
  2000. int[] actorsInRoom = (int[])operationResponse.Parameters[ParameterCode.ActorList];
  2001. this.UpdatedActorList(actorsInRoom);
  2002. }
  2003. Hashtable actorProperties = (Hashtable)operationResponse[ParameterCode.PlayerProperties];
  2004. Hashtable gameProperties = (Hashtable)operationResponse[ParameterCode.GameProperties];
  2005. this.ReadoutProperties(gameProperties, actorProperties, 0);
  2006. object temp;
  2007. if (operationResponse.Parameters.TryGetValue(ParameterCode.RoomOptionFlags, out temp))
  2008. {
  2009. this.CurrentRoom.InternalCacheRoomFlags((int)temp);
  2010. }
  2011. this.State = ClientState.Joined;
  2012. // the callbacks OnCreatedRoom and OnJoinedRoom are called in the event join. it contains important info about the room and players.
  2013. // unless there will be no room events (RoomOptions.SuppressRoomEvents = true)
  2014. if (this.CurrentRoom.SuppressRoomEvents)
  2015. {
  2016. if (this.lastJoinType == JoinType.CreateRoom || (this.lastJoinType == JoinType.JoinOrCreateRoom && this.LocalPlayer.ActorNumber == 1))
  2017. {
  2018. this.MatchMakingCallbackTargets.OnCreatedRoom();
  2019. }
  2020. this.MatchMakingCallbackTargets.OnJoinedRoom();
  2021. }
  2022. }
  2023. private void UpdatedActorList(int[] actorsInGame)
  2024. {
  2025. if (actorsInGame != null)
  2026. {
  2027. foreach (int actorNumber in actorsInGame)
  2028. {
  2029. if (actorNumber == 0)
  2030. {
  2031. continue;
  2032. }
  2033. Player target = this.CurrentRoom.GetPlayer(actorNumber);
  2034. if (target == null)
  2035. {
  2036. this.CurrentRoom.StorePlayer(this.CreatePlayer(string.Empty, actorNumber, false, null));
  2037. }
  2038. }
  2039. }
  2040. }
  2041. /// <summary>
  2042. /// Factory method to create a player instance - override to get your own player-type with custom features.
  2043. /// </summary>
  2044. /// <param name="actorName">The name of the player to be created. </param>
  2045. /// <param name="actorNumber">The player ID (a.k.a. actorNumber) of the player to be created.</param>
  2046. /// <param name="isLocal">Sets the distinction if the player to be created is your player or if its assigned to someone else.</param>
  2047. /// <param name="actorProperties">The custom properties for this new player</param>
  2048. /// <returns>The newly created player</returns>
  2049. protected internal virtual Player CreatePlayer(string actorName, int actorNumber, bool isLocal, Hashtable actorProperties)
  2050. {
  2051. Player newPlayer = new Player(actorName, actorNumber, isLocal, actorProperties);
  2052. return newPlayer;
  2053. }
  2054. /// <summary>Internal "factory" method to create a room-instance.</summary>
  2055. protected internal virtual Room CreateRoom(string roomName, RoomOptions opt)
  2056. {
  2057. Room r = new Room(roomName, opt);
  2058. return r;
  2059. }
  2060. private bool CheckIfOpAllowedOnServer(byte opCode, ServerConnection serverConnection)
  2061. {
  2062. switch (serverConnection)
  2063. {
  2064. case ServerConnection.MasterServer:
  2065. switch (opCode)
  2066. {
  2067. case OperationCode.CreateGame:
  2068. case OperationCode.Authenticate:
  2069. case OperationCode.AuthenticateOnce:
  2070. case OperationCode.FindFriends:
  2071. case OperationCode.GetGameList:
  2072. case OperationCode.GetLobbyStats:
  2073. case OperationCode.JoinGame:
  2074. case OperationCode.JoinLobby:
  2075. case OperationCode.LeaveLobby:
  2076. case OperationCode.WebRpc:
  2077. case OperationCode.ServerSettings:
  2078. case OperationCode.JoinRandomGame:
  2079. return true;
  2080. }
  2081. break;
  2082. case ServerConnection.GameServer:
  2083. switch (opCode)
  2084. {
  2085. case OperationCode.CreateGame:
  2086. case OperationCode.Authenticate:
  2087. case OperationCode.AuthenticateOnce:
  2088. case OperationCode.ChangeGroups:
  2089. case OperationCode.GetProperties:
  2090. case OperationCode.JoinGame:
  2091. case OperationCode.Leave:
  2092. case OperationCode.WebRpc:
  2093. case OperationCode.ServerSettings:
  2094. case OperationCode.SetProperties:
  2095. case OperationCode.RaiseEvent:
  2096. return true;
  2097. }
  2098. break;
  2099. case ServerConnection.NameServer:
  2100. switch (opCode)
  2101. {
  2102. case OperationCode.Authenticate:
  2103. case OperationCode.AuthenticateOnce:
  2104. case OperationCode.GetRegions:
  2105. case OperationCode.ServerSettings:
  2106. return true;
  2107. }
  2108. break;
  2109. default:
  2110. throw new ArgumentOutOfRangeException("serverConnection", serverConnection, null);
  2111. }
  2112. return false;
  2113. }
  2114. private bool CheckIfOpCanBeSent(byte opCode, ServerConnection serverConnection, string opName)
  2115. {
  2116. if (this.LoadBalancingPeer == null)
  2117. {
  2118. this.DebugReturn(DebugLevel.ERROR, string.Format("Operation {0} ({1}) can't be sent because peer is null", opName, opCode));
  2119. return false;
  2120. }
  2121. if (!this.CheckIfOpAllowedOnServer(opCode, serverConnection))
  2122. {
  2123. if (this.LoadBalancingPeer.DebugOut >= DebugLevel.ERROR)
  2124. {
  2125. this.DebugReturn(DebugLevel.ERROR, string.Format("Operation {0} ({1}) not allowed on current server ({2})", opName, opCode, serverConnection));
  2126. }
  2127. return false;
  2128. }
  2129. if (!this.CheckIfClientIsReadyToCallOperation(opCode))
  2130. {
  2131. DebugLevel levelToReport = DebugLevel.ERROR;
  2132. if (opCode == OperationCode.RaiseEvent && (this.State == ClientState.Leaving || this.State == ClientState.Disconnecting || this.State == ClientState.DisconnectingFromGameServer))
  2133. {
  2134. levelToReport = DebugLevel.INFO;
  2135. }
  2136. if (this.LoadBalancingPeer.DebugOut >= levelToReport)
  2137. {
  2138. this.DebugReturn(levelToReport, string.Format("Operation {0} ({1}) not called because client is not connected or not ready yet, client state: {2}", opName, opCode, Enum.GetName(typeof(ClientState), this.State)));
  2139. }
  2140. return false;
  2141. }
  2142. if (this.LoadBalancingPeer.PeerState != PeerStateValue.Connected)
  2143. {
  2144. this.DebugReturn(DebugLevel.ERROR, string.Format("Operation {0} ({1}) can't be sent because peer is not connected, peer state: {2}", opName, opCode, this.LoadBalancingPeer.PeerState));
  2145. return false;
  2146. }
  2147. return true;
  2148. }
  2149. private bool CheckIfClientIsReadyToCallOperation(byte opCode)
  2150. {
  2151. switch (opCode)
  2152. {
  2153. //case OperationCode.ServerSettings: // ??
  2154. //case OperationCode.WebRpc: // WebRPC works on MS and GS and I think it does not need the client to be ready
  2155. case OperationCode.Authenticate:
  2156. case OperationCode.AuthenticateOnce:
  2157. return this.IsConnectedAndReady ||
  2158. this.State == ClientState.ConnectingToNameServer || // this is required since we do not set state to ConnectedToNameServer before authentication
  2159. this.State == ClientState.ConnectingToMasterServer || // this is required since we do not set state to ConnectedToMasterServer before authentication
  2160. this.State == ClientState.ConnectingToGameServer; // this is required since we do not set state to ConnectedToGameServer before authentication
  2161. case OperationCode.ChangeGroups:
  2162. case OperationCode.GetProperties:
  2163. case OperationCode.SetProperties:
  2164. case OperationCode.RaiseEvent:
  2165. case OperationCode.Leave:
  2166. return this.InRoom;
  2167. case OperationCode.JoinGame:
  2168. case OperationCode.CreateGame:
  2169. return this.State == ClientState.ConnectedToMasterServer || this.InLobby || this.State == ClientState.ConnectedToGameServer; // CurrentRoom can be not null in case of quick rejoin
  2170. case OperationCode.LeaveLobby:
  2171. return this.InLobby;
  2172. case OperationCode.JoinRandomGame:
  2173. case OperationCode.FindFriends:
  2174. case OperationCode.GetGameList:
  2175. case OperationCode.GetLobbyStats: // do we need to be inside lobby to call this?
  2176. case OperationCode.JoinLobby: // You don't have to explicitly leave a lobby to join another (client can be in one max, at any time)
  2177. return this.State == ClientState.ConnectedToMasterServer || this.InLobby;
  2178. case OperationCode.GetRegions:
  2179. return this.State == ClientState.ConnectedToNameServer;
  2180. }
  2181. return this.IsConnected;
  2182. }
  2183. #endregion
  2184. #region Implementation of IPhotonPeerListener
  2185. /// <summary>Debug output of low level api (and this client).</summary>
  2186. /// <remarks>This method is not responsible to keep up the state of a LoadBalancingClient. Calling base.DebugReturn on overrides is optional.</remarks>
  2187. public virtual void DebugReturn(DebugLevel level, string message)
  2188. {
  2189. if (this.LoadBalancingPeer.DebugOut != DebugLevel.ALL && level > this.LoadBalancingPeer.DebugOut)
  2190. {
  2191. return;
  2192. }
  2193. #if !SUPPORTED_UNITY
  2194. Debug.WriteLine(message);
  2195. #else
  2196. if (level == DebugLevel.ERROR)
  2197. {
  2198. Debug.LogError(message);
  2199. }
  2200. else if (level == DebugLevel.WARNING)
  2201. {
  2202. Debug.LogWarning(message);
  2203. }
  2204. else if (level == DebugLevel.INFO)
  2205. {
  2206. Debug.Log(message);
  2207. }
  2208. else if (level == DebugLevel.ALL)
  2209. {
  2210. Debug.Log(message);
  2211. }
  2212. #endif
  2213. }
  2214. private void CallbackRoomEnterFailed(OperationResponse operationResponse)
  2215. {
  2216. if (operationResponse.ReturnCode != 0)
  2217. {
  2218. if (operationResponse.OperationCode == OperationCode.JoinGame)
  2219. {
  2220. this.MatchMakingCallbackTargets.OnJoinRoomFailed(operationResponse.ReturnCode, operationResponse.DebugMessage);
  2221. }
  2222. else if (operationResponse.OperationCode == OperationCode.CreateGame)
  2223. {
  2224. this.MatchMakingCallbackTargets.OnCreateRoomFailed(operationResponse.ReturnCode, operationResponse.DebugMessage);
  2225. }
  2226. else if (operationResponse.OperationCode == OperationCode.JoinRandomGame)
  2227. {
  2228. this.MatchMakingCallbackTargets.OnJoinRandomFailed(operationResponse.ReturnCode, operationResponse.DebugMessage);
  2229. }
  2230. }
  2231. }
  2232. /// <summary>
  2233. /// Uses the OperationResponses provided by the server to advance the internal state and call ops as needed.
  2234. /// </summary>
  2235. /// <remarks>
  2236. /// When this method finishes, it will call your OnOpResponseAction (if any). This way, you can get any
  2237. /// operation response without overriding this class.
  2238. ///
  2239. /// To implement a more complex game/app logic, you should implement your own class that inherits the
  2240. /// LoadBalancingClient. Override this method to use your own operation-responses easily.
  2241. ///
  2242. /// This method is essential to update the internal state of a LoadBalancingClient, so overriding methods
  2243. /// must call base.OnOperationResponse().
  2244. /// </remarks>
  2245. /// <param name="operationResponse">Contains the server's response for an operation called by this peer.</param>
  2246. public virtual void OnOperationResponse(OperationResponse operationResponse)
  2247. {
  2248. // if (operationResponse.ReturnCode != 0) this.DebugReturn(DebugLevel.ERROR, operationResponse.ToStringFull());
  2249. // use the "secret" or "token" whenever we get it. doesn't really matter if it's in AuthResponse.
  2250. if (operationResponse.Parameters.ContainsKey(ParameterCode.Token))
  2251. {
  2252. if (this.AuthValues == null)
  2253. {
  2254. this.AuthValues = new AuthenticationValues();
  2255. //this.DebugReturn(DebugLevel.ERROR, "Server returned secret. Created AuthValues.");
  2256. }
  2257. this.AuthValues.Token = operationResponse[ParameterCode.Token] as string;
  2258. this.tokenCache = this.AuthValues.Token;
  2259. }
  2260. // if the operation limit was reached, disconnect (but still execute the operation response).
  2261. if (operationResponse.ReturnCode == ErrorCode.OperationLimitReached)
  2262. {
  2263. this.Disconnect(DisconnectCause.DisconnectByOperationLimit);
  2264. }
  2265. switch (operationResponse.OperationCode)
  2266. {
  2267. case OperationCode.Authenticate:
  2268. case OperationCode.AuthenticateOnce:
  2269. {
  2270. if (operationResponse.ReturnCode != 0)
  2271. {
  2272. this.DebugReturn(DebugLevel.ERROR, operationResponse.ToStringFull() + " Server: " + this.Server + " Address: " + this.LoadBalancingPeer.ServerAddress);
  2273. switch (operationResponse.ReturnCode)
  2274. {
  2275. case ErrorCode.InvalidAuthentication:
  2276. this.DisconnectedCause = DisconnectCause.InvalidAuthentication;
  2277. break;
  2278. case ErrorCode.CustomAuthenticationFailed:
  2279. this.DisconnectedCause = DisconnectCause.CustomAuthenticationFailed;
  2280. this.ConnectionCallbackTargets.OnCustomAuthenticationFailed(operationResponse.DebugMessage);
  2281. break;
  2282. case ErrorCode.InvalidRegion:
  2283. this.DisconnectedCause = DisconnectCause.InvalidRegion;
  2284. break;
  2285. case ErrorCode.MaxCcuReached:
  2286. this.DisconnectedCause = DisconnectCause.MaxCcuReached;
  2287. break;
  2288. case ErrorCode.InvalidOperation:
  2289. case ErrorCode.OperationNotAllowedInCurrentState:
  2290. this.DisconnectedCause = DisconnectCause.OperationNotAllowedInCurrentState;
  2291. break;
  2292. case ErrorCode.AuthenticationTicketExpired:
  2293. this.DisconnectedCause = DisconnectCause.AuthenticationTicketExpired;
  2294. break;
  2295. }
  2296. this.Disconnect(this.DisconnectedCause);
  2297. break; // if auth didn't succeed, we disconnect (above) and exit this operation's handling
  2298. }
  2299. if (this.Server == ServerConnection.NameServer || this.Server == ServerConnection.MasterServer)
  2300. {
  2301. if (operationResponse.Parameters.ContainsKey(ParameterCode.UserId))
  2302. {
  2303. string incomingId = (string)operationResponse.Parameters[ParameterCode.UserId];
  2304. if (!string.IsNullOrEmpty(incomingId))
  2305. {
  2306. this.UserId = incomingId;
  2307. this.LocalPlayer.UserId = incomingId;
  2308. this.DebugReturn(DebugLevel.INFO, string.Format("Received your UserID from server. Updating local value to: {0}", this.UserId));
  2309. }
  2310. }
  2311. if (operationResponse.Parameters.ContainsKey(ParameterCode.NickName))
  2312. {
  2313. this.NickName = (string)operationResponse.Parameters[ParameterCode.NickName];
  2314. this.DebugReturn(DebugLevel.INFO, string.Format("Received your NickName from server. Updating local value to: {0}", this.NickName));
  2315. }
  2316. if (operationResponse.Parameters.ContainsKey(ParameterCode.EncryptionData))
  2317. {
  2318. this.SetupEncryption((Dictionary<byte, object>)operationResponse.Parameters[ParameterCode.EncryptionData]);
  2319. }
  2320. }
  2321. if (this.Server == ServerConnection.NameServer)
  2322. {
  2323. string receivedCluster = operationResponse[ParameterCode.Cluster] as string;
  2324. if (!string.IsNullOrEmpty(receivedCluster))
  2325. {
  2326. this.CurrentCluster = receivedCluster;
  2327. }
  2328. // on the NameServer, authenticate returns the MasterServer address for a region and we hop off to there
  2329. this.MasterServerAddress = operationResponse[ParameterCode.Address] as string;
  2330. if (this.ServerPortOverrides.MasterServerPort != 0)
  2331. {
  2332. //Debug.LogWarning("Incoming MasterServer Address: "+this.MasterServerAddress);
  2333. this.MasterServerAddress = ReplacePortWithAlternative(this.MasterServerAddress, this.ServerPortOverrides.MasterServerPort);
  2334. //Debug.LogWarning("New MasterServer Address: "+this.MasterServerAddress);
  2335. }
  2336. if (this.AuthMode == AuthModeOption.AuthOnceWss && this.ExpectedProtocol != null)
  2337. {
  2338. this.DebugReturn(DebugLevel.INFO, string.Format("AuthOnceWss mode. Auth response switches TransportProtocol to ExpectedProtocol: {0}.", this.ExpectedProtocol));
  2339. this.LoadBalancingPeer.TransportProtocol = (ConnectionProtocol)this.ExpectedProtocol;
  2340. this.ExpectedProtocol = null;
  2341. }
  2342. this.DisconnectToReconnect();
  2343. }
  2344. else if (this.Server == ServerConnection.MasterServer)
  2345. {
  2346. this.State = ClientState.ConnectedToMasterServer;
  2347. if (this.failedRoomEntryOperation == null)
  2348. {
  2349. this.ConnectionCallbackTargets.OnConnectedToMaster();
  2350. }
  2351. else
  2352. {
  2353. this.CallbackRoomEnterFailed(this.failedRoomEntryOperation);
  2354. this.failedRoomEntryOperation = null;
  2355. }
  2356. if (this.AuthMode != AuthModeOption.Auth)
  2357. {
  2358. this.LoadBalancingPeer.OpSettings(this.EnableLobbyStatistics);
  2359. }
  2360. }
  2361. else if (this.Server == ServerConnection.GameServer)
  2362. {
  2363. this.State = ClientState.Joining;
  2364. if (this.enterRoomParamsCache.JoinMode == JoinMode.RejoinOnly)
  2365. {
  2366. this.enterRoomParamsCache.PlayerProperties = null;
  2367. }
  2368. else
  2369. {
  2370. Hashtable allProps = new Hashtable();
  2371. allProps.Merge(this.LocalPlayer.CustomProperties);
  2372. if (!string.IsNullOrEmpty(this.LocalPlayer.NickName))
  2373. {
  2374. allProps[ActorProperties.PlayerName] = this.LocalPlayer.NickName;
  2375. }
  2376. this.enterRoomParamsCache.PlayerProperties = allProps;
  2377. }
  2378. this.enterRoomParamsCache.OnGameServer = true;
  2379. if (this.lastJoinType == JoinType.JoinRoom || this.lastJoinType == JoinType.JoinRandomRoom || this.lastJoinType == JoinType.JoinRandomOrCreateRoom || this.lastJoinType == JoinType.JoinOrCreateRoom)
  2380. {
  2381. this.LoadBalancingPeer.OpJoinRoom(this.enterRoomParamsCache);
  2382. }
  2383. else if (this.lastJoinType == JoinType.CreateRoom)
  2384. {
  2385. this.LoadBalancingPeer.OpCreateRoom(this.enterRoomParamsCache);
  2386. }
  2387. break;
  2388. }
  2389. // optionally, OpAuth may return some data for the client to use. if it's available, call OnCustomAuthenticationResponse
  2390. Dictionary<string, object> data = (Dictionary<string, object>)operationResponse[ParameterCode.Data];
  2391. if (data != null)
  2392. {
  2393. this.ConnectionCallbackTargets.OnCustomAuthenticationResponse(data);
  2394. }
  2395. break;
  2396. }
  2397. case OperationCode.GetRegions:
  2398. // Debug.Log("GetRegions returned: " + operationResponse.ToStringFull());
  2399. if (operationResponse.ReturnCode == ErrorCode.InvalidAuthentication)
  2400. {
  2401. this.DebugReturn(DebugLevel.ERROR, string.Format("GetRegions failed. AppId is unknown on the (cloud) server. "+operationResponse.DebugMessage));
  2402. this.Disconnect(DisconnectCause.InvalidAuthentication);
  2403. break;
  2404. }
  2405. if (operationResponse.ReturnCode != ErrorCode.Ok)
  2406. {
  2407. this.DebugReturn(DebugLevel.ERROR, "GetRegions failed. Can't provide regions list. ReturnCode: " + operationResponse.ReturnCode + ": " + operationResponse.DebugMessage);
  2408. this.Disconnect(DisconnectCause.InvalidAuthentication);
  2409. break;
  2410. }
  2411. if (this.RegionHandler == null)
  2412. {
  2413. this.RegionHandler = new RegionHandler(this.ServerPortOverrides.MasterServerPort);
  2414. }
  2415. if (this.RegionHandler.IsPinging)
  2416. {
  2417. this.DebugReturn(DebugLevel.WARNING, "Received an response for OpGetRegions while the RegionHandler is pinging regions already. Skipping this response in favor of completing the current region-pinging.");
  2418. return; // in this particular case, we suppress the duplicate GetRegion response. we don't want a callback for this, cause there is a warning already.
  2419. }
  2420. this.RegionHandler.SetRegions(operationResponse);
  2421. this.ConnectionCallbackTargets.OnRegionListReceived(this.RegionHandler);
  2422. if (this.connectToBestRegion)
  2423. {
  2424. // ping minimal regions (if one is known) and connect
  2425. this.RegionHandler.PingMinimumOfRegions(this.OnRegionPingCompleted, this.bestRegionSummaryFromStorage);
  2426. }
  2427. break;
  2428. case OperationCode.JoinRandomGame: // this happens only on the master server. on gameserver this is a "regular" join
  2429. case OperationCode.CreateGame:
  2430. case OperationCode.JoinGame:
  2431. if (operationResponse.ReturnCode != 0)
  2432. {
  2433. if (this.Server == ServerConnection.GameServer)
  2434. {
  2435. this.failedRoomEntryOperation = operationResponse;
  2436. this.DisconnectToReconnect();
  2437. }
  2438. else
  2439. {
  2440. this.State = (this.InLobby) ? ClientState.JoinedLobby : ClientState.ConnectedToMasterServer;
  2441. this.CallbackRoomEnterFailed(operationResponse);
  2442. }
  2443. }
  2444. else
  2445. {
  2446. if (this.Server == ServerConnection.GameServer)
  2447. {
  2448. this.GameEnteredOnGameServer(operationResponse);
  2449. }
  2450. else
  2451. {
  2452. this.GameServerAddress = (string)operationResponse[ParameterCode.Address];
  2453. if (this.ServerPortOverrides.GameServerPort != 0)
  2454. {
  2455. //Debug.LogWarning("Incoming GameServer Address: " + this.GameServerAddress);
  2456. this.GameServerAddress = ReplacePortWithAlternative(this.GameServerAddress, this.ServerPortOverrides.GameServerPort);
  2457. //Debug.LogWarning("New GameServer Address: " + this.GameServerAddress);
  2458. }
  2459. string roomName = operationResponse[ParameterCode.RoomName] as string;
  2460. if (!string.IsNullOrEmpty(roomName))
  2461. {
  2462. this.enterRoomParamsCache.RoomName = roomName;
  2463. }
  2464. this.DisconnectToReconnect();
  2465. }
  2466. }
  2467. break;
  2468. case OperationCode.GetGameList:
  2469. if (operationResponse.ReturnCode != 0)
  2470. {
  2471. this.DebugReturn(DebugLevel.ERROR, "GetGameList failed: " + operationResponse.ToStringFull());
  2472. break;
  2473. }
  2474. List<RoomInfo> _RoomInfoList = new List<RoomInfo>();
  2475. Hashtable games = (Hashtable)operationResponse[ParameterCode.GameList];
  2476. foreach (string gameName in games.Keys)
  2477. {
  2478. _RoomInfoList.Add(new RoomInfo(gameName, (Hashtable)games[gameName]));
  2479. }
  2480. this.LobbyCallbackTargets.OnRoomListUpdate(_RoomInfoList);
  2481. break;
  2482. case OperationCode.JoinLobby:
  2483. this.State = ClientState.JoinedLobby;
  2484. this.LobbyCallbackTargets.OnJoinedLobby();
  2485. break;
  2486. case OperationCode.LeaveLobby:
  2487. this.State = ClientState.ConnectedToMasterServer;
  2488. this.LobbyCallbackTargets.OnLeftLobby();
  2489. break;
  2490. case OperationCode.Leave:
  2491. this.DisconnectToReconnect();
  2492. break;
  2493. case OperationCode.FindFriends:
  2494. if (operationResponse.ReturnCode != 0)
  2495. {
  2496. this.DebugReturn(DebugLevel.ERROR, "OpFindFriends failed: " + operationResponse.ToStringFull());
  2497. this.friendListRequested = null;
  2498. break;
  2499. }
  2500. bool[] onlineList = operationResponse[ParameterCode.FindFriendsResponseOnlineList] as bool[];
  2501. string[] roomList = operationResponse[ParameterCode.FindFriendsResponseRoomIdList] as string[];
  2502. //if (onlineList == null || roomList == null || this.friendListRequested == null || onlineList.Length != this.friendListRequested.Length)
  2503. //{
  2504. // // TODO: Check if we should handle this case better / more extensively
  2505. // this.DebugReturn(DebugLevel.ERROR, "OpFindFriends failed. Some list is not set. OpResponse: " + operationResponse.ToStringFull());
  2506. // this.friendListRequested = null;
  2507. // this.isFetchingFriendList = false;
  2508. // break;
  2509. //}
  2510. List<FriendInfo> friendList = new List<FriendInfo>(this.friendListRequested.Length);
  2511. for (int index = 0; index < this.friendListRequested.Length; index++)
  2512. {
  2513. FriendInfo friend = new FriendInfo();
  2514. friend.UserId = this.friendListRequested[index];
  2515. friend.Room = roomList[index];
  2516. friend.IsOnline = onlineList[index];
  2517. friendList.Insert(index, friend);
  2518. }
  2519. this.friendListRequested = null;
  2520. this.MatchMakingCallbackTargets.OnFriendListUpdate(friendList);
  2521. break;
  2522. case OperationCode.WebRpc:
  2523. this.WebRpcCallbackTargets.OnWebRpcResponse(operationResponse);
  2524. break;
  2525. }
  2526. if (this.OpResponseReceived != null) this.OpResponseReceived(operationResponse);
  2527. }
  2528. /// <summary>
  2529. /// Uses the connection's statusCodes to advance the internal state and call operations as needed.
  2530. /// </summary>
  2531. /// <remarks>This method is essential to update the internal state of a LoadBalancingClient. Overriding methods must call base.OnStatusChanged.</remarks>
  2532. public virtual void OnStatusChanged(StatusCode statusCode)
  2533. {
  2534. switch (statusCode)
  2535. {
  2536. case StatusCode.Connect:
  2537. if (this.State == ClientState.ConnectingToNameServer)
  2538. {
  2539. if (this.LoadBalancingPeer.DebugOut >= DebugLevel.ALL)
  2540. {
  2541. this.DebugReturn(DebugLevel.ALL, "Connected to nameserver.");
  2542. }
  2543. this.Server = ServerConnection.NameServer;
  2544. if (this.AuthValues != null)
  2545. {
  2546. this.AuthValues.Token = null; // when connecting to NameServer, invalidate the secret (only)
  2547. }
  2548. }
  2549. if (this.State == ClientState.ConnectingToGameServer)
  2550. {
  2551. if (this.LoadBalancingPeer.DebugOut >= DebugLevel.ALL)
  2552. {
  2553. this.DebugReturn(DebugLevel.ALL, "Connected to gameserver.");
  2554. }
  2555. this.Server = ServerConnection.GameServer;
  2556. }
  2557. if (this.State == ClientState.ConnectingToMasterServer)
  2558. {
  2559. if (this.LoadBalancingPeer.DebugOut >= DebugLevel.ALL)
  2560. {
  2561. this.DebugReturn(DebugLevel.ALL, "Connected to masterserver.");
  2562. }
  2563. this.Server = ServerConnection.MasterServer;
  2564. this.ConnectionCallbackTargets.OnConnected(); // if initial connect
  2565. }
  2566. if (this.LoadBalancingPeer.TransportProtocol != ConnectionProtocol.WebSocketSecure)
  2567. {
  2568. if (this.Server == ServerConnection.NameServer || this.AuthMode == AuthModeOption.Auth)
  2569. {
  2570. this.LoadBalancingPeer.EstablishEncryption();
  2571. }
  2572. }
  2573. else
  2574. {
  2575. goto case StatusCode.EncryptionEstablished;
  2576. }
  2577. break;
  2578. case StatusCode.EncryptionEstablished:
  2579. if (this.Server == ServerConnection.NameServer)
  2580. {
  2581. this.State = ClientState.ConnectedToNameServer;
  2582. // if there is no specific region to connect to, get available regions from the Name Server. the result triggers next actions in workflow
  2583. if (string.IsNullOrEmpty(this.CloudRegion))
  2584. {
  2585. this.OpGetRegions();
  2586. break;
  2587. }
  2588. }
  2589. else
  2590. {
  2591. // auth AuthOnce, no explicit authentication is needed on Master Server and Game Server. this is done via token, so: break
  2592. if (this.AuthMode == AuthModeOption.AuthOnce || this.AuthMode == AuthModeOption.AuthOnceWss)
  2593. {
  2594. break;
  2595. }
  2596. }
  2597. // authenticate in all other cases (using the CloudRegion, if available)
  2598. bool authenticating = this.CallAuthenticate();
  2599. if (authenticating)
  2600. {
  2601. this.State = ClientState.Authenticating;
  2602. }
  2603. else
  2604. {
  2605. this.DebugReturn(DebugLevel.ERROR, "OpAuthenticate failed. Check log output and AuthValues. State: " + this.State);
  2606. }
  2607. break;
  2608. case StatusCode.Disconnect:
  2609. // disconnect due to connection exception is handled below (don't connect to GS or master in that case)
  2610. this.friendListRequested = null;
  2611. bool wasInRoom = this.CurrentRoom != null;
  2612. this.CurrentRoom = null; // players get cleaned up inside this, too, except LocalPlayer (which we keep)
  2613. this.ChangeLocalID(-1); // depends on this.CurrentRoom, so it must be called after updating that
  2614. if (this.Server == ServerConnection.GameServer && wasInRoom)
  2615. {
  2616. this.MatchMakingCallbackTargets.OnLeftRoom();
  2617. }
  2618. if (this.ExpectedProtocol != null && this.LoadBalancingPeer.TransportProtocol != this.ExpectedProtocol)
  2619. {
  2620. this.DebugReturn(DebugLevel.INFO, string.Format("On disconnect switches TransportProtocol to ExpectedProtocol: {0}.", this.ExpectedProtocol));
  2621. this.LoadBalancingPeer.TransportProtocol = (ConnectionProtocol)this.ExpectedProtocol;
  2622. this.ExpectedProtocol = null;
  2623. }
  2624. switch (this.State)
  2625. {
  2626. case ClientState.ConnectWithFallbackProtocol:
  2627. this.EnableProtocolFallback = false; // the client does a fallback only one time
  2628. this.LoadBalancingPeer.TransportProtocol = (this.LoadBalancingPeer.TransportProtocol == ConnectionProtocol.Tcp) ? ConnectionProtocol.Udp : ConnectionProtocol.Tcp;
  2629. this.NameServerPortInAppSettings = 0; // this does not affect the ServerSettings file, just a variable at runtime
  2630. this.ServerPortOverrides = new PhotonPortDefinition(); // use default ports for the fallback
  2631. if (!this.LoadBalancingPeer.Connect(this.NameServerAddress, this.ProxyServerAddress, this.AppId, this.TokenForInit))
  2632. {
  2633. return;
  2634. }
  2635. this.State = ClientState.ConnectingToNameServer;
  2636. break;
  2637. case ClientState.PeerCreated:
  2638. case ClientState.Disconnecting:
  2639. if (this.AuthValues != null)
  2640. {
  2641. this.AuthValues.Token = null; // when leaving the server, invalidate the secret (but not the auth values)
  2642. }
  2643. this.State = ClientState.Disconnected;
  2644. this.ConnectionCallbackTargets.OnDisconnected(this.DisconnectedCause);
  2645. break;
  2646. case ClientState.DisconnectingFromGameServer:
  2647. case ClientState.DisconnectingFromNameServer:
  2648. this.ConnectToMasterServer(); // this gets the client back to the Master Server
  2649. break;
  2650. case ClientState.DisconnectingFromMasterServer:
  2651. this.Connect(this.GameServerAddress, this.ProxyServerAddress, ServerConnection.GameServer); // this connects the client with the Game Server (when joining/creating a room)
  2652. break;
  2653. case ClientState.Disconnected:
  2654. // this client is already Disconnected, so no further action is needed.
  2655. // this.DebugReturn(DebugLevel.INFO, "LBC.OnStatusChanged(Disconnect) this.State: " + this.State + ". Server: " + this.Server);
  2656. break;
  2657. default:
  2658. string stacktrace = "";
  2659. #if DEBUG && !NETFX_CORE
  2660. stacktrace = new System.Diagnostics.StackTrace(true).ToString();
  2661. #endif
  2662. this.DebugReturn(DebugLevel.WARNING, "Got a unexpected Disconnect in LoadBalancingClient State: " + this.State + ". Server: " + this.Server + " Trace: " + stacktrace);
  2663. if (this.AuthValues != null)
  2664. {
  2665. this.AuthValues.Token = null; // when leaving the server, invalidate the secret (but not the auth values)
  2666. }
  2667. this.State = ClientState.Disconnected;
  2668. this.ConnectionCallbackTargets.OnDisconnected(this.DisconnectedCause);
  2669. break;
  2670. }
  2671. break;
  2672. case StatusCode.DisconnectByServerUserLimit:
  2673. this.DebugReturn(DebugLevel.ERROR, "This connection was rejected due to the apps CCU limit.");
  2674. this.DisconnectedCause = DisconnectCause.MaxCcuReached;
  2675. this.State = ClientState.Disconnecting;
  2676. break;
  2677. case StatusCode.DnsExceptionOnConnect:
  2678. this.DisconnectedCause = DisconnectCause.DnsExceptionOnConnect;
  2679. this.State = ClientState.Disconnecting;
  2680. break;
  2681. case StatusCode.ServerAddressInvalid:
  2682. this.DisconnectedCause = DisconnectCause.ServerAddressInvalid;
  2683. this.State = ClientState.Disconnecting;
  2684. break;
  2685. case StatusCode.ExceptionOnConnect:
  2686. case StatusCode.SecurityExceptionOnConnect:
  2687. case StatusCode.EncryptionFailedToEstablish:
  2688. this.DisconnectedCause = DisconnectCause.ExceptionOnConnect;
  2689. // if enabled, the client can attempt to connect with another networking-protocol to check if that connects
  2690. if (this.EnableProtocolFallback && this.State == ClientState.ConnectingToNameServer)
  2691. {
  2692. this.State = ClientState.ConnectWithFallbackProtocol;
  2693. }
  2694. else
  2695. {
  2696. this.State = ClientState.Disconnecting;
  2697. }
  2698. break;
  2699. case StatusCode.Exception:
  2700. case StatusCode.ExceptionOnReceive:
  2701. case StatusCode.SendError:
  2702. this.DisconnectedCause = DisconnectCause.Exception;
  2703. this.State = ClientState.Disconnecting;
  2704. break;
  2705. case StatusCode.DisconnectByServerTimeout:
  2706. this.DisconnectedCause = DisconnectCause.ServerTimeout;
  2707. this.State = ClientState.Disconnecting;
  2708. break;
  2709. case StatusCode.DisconnectByServerLogic:
  2710. this.DisconnectedCause = DisconnectCause.DisconnectByServerLogic;
  2711. this.State = ClientState.Disconnecting;
  2712. break;
  2713. case StatusCode.DisconnectByServerReasonUnknown:
  2714. this.DisconnectedCause = DisconnectCause.DisconnectByServerReasonUnknown;
  2715. this.State = ClientState.Disconnecting;
  2716. break;
  2717. case StatusCode.TimeoutDisconnect:
  2718. this.DisconnectedCause = DisconnectCause.ClientTimeout;
  2719. // if enabled, the client can attempt to connect with another networking-protocol to check if that connects
  2720. if (this.EnableProtocolFallback && this.State == ClientState.ConnectingToNameServer)
  2721. {
  2722. this.State = ClientState.ConnectWithFallbackProtocol;
  2723. }
  2724. else
  2725. {
  2726. this.State = ClientState.Disconnecting;
  2727. }
  2728. break;
  2729. }
  2730. }
  2731. /// <summary>
  2732. /// Uses the photonEvent's provided by the server to advance the internal state and call ops as needed.
  2733. /// </summary>
  2734. /// <remarks>This method is essential to update the internal state of a LoadBalancingClient. Overriding methods must call base.OnEvent.</remarks>
  2735. public virtual void OnEvent(EventData photonEvent)
  2736. {
  2737. int actorNr = photonEvent.Sender;
  2738. Player originatingPlayer = (this.CurrentRoom != null) ? this.CurrentRoom.GetPlayer(actorNr) : null;
  2739. switch (photonEvent.Code)
  2740. {
  2741. case EventCode.GameList:
  2742. case EventCode.GameListUpdate:
  2743. List<RoomInfo> _RoomInfoList = new List<RoomInfo>();
  2744. Hashtable games = (Hashtable)photonEvent[ParameterCode.GameList];
  2745. foreach (string gameName in games.Keys)
  2746. {
  2747. _RoomInfoList.Add(new RoomInfo(gameName, (Hashtable)games[gameName]));
  2748. }
  2749. this.LobbyCallbackTargets.OnRoomListUpdate(_RoomInfoList);
  2750. break;
  2751. case EventCode.Join:
  2752. Hashtable actorProperties = (Hashtable)photonEvent[ParameterCode.PlayerProperties];
  2753. if (originatingPlayer == null)
  2754. {
  2755. if (actorNr > 0)
  2756. {
  2757. originatingPlayer = this.CreatePlayer(string.Empty, actorNr, false, actorProperties);
  2758. this.CurrentRoom.StorePlayer(originatingPlayer);
  2759. }
  2760. }
  2761. else
  2762. {
  2763. originatingPlayer.InternalCacheProperties(actorProperties);
  2764. originatingPlayer.IsInactive = false;
  2765. originatingPlayer.HasRejoined = actorNr != this.LocalPlayer.ActorNumber; // event is for non-local player, who is known (by ActorNumber), so it's a returning player
  2766. }
  2767. if (actorNr == this.LocalPlayer.ActorNumber)
  2768. {
  2769. // in this player's own join event, we get a complete list of players in the room, so check if we know each of the
  2770. int[] actorsInRoom = (int[])photonEvent[ParameterCode.ActorList];
  2771. this.UpdatedActorList(actorsInRoom);
  2772. // any operation that does a "rejoin" will set this value to true. this can indicate if the local player returns to a room.
  2773. originatingPlayer.HasRejoined = this.enterRoomParamsCache.JoinMode == JoinMode.RejoinOnly;
  2774. // joinWithCreateOnDemand can turn an OpJoin into creating the room. Then actorNumber is 1 and callback: OnCreatedRoom()
  2775. if (this.lastJoinType == JoinType.CreateRoom || (this.lastJoinType == JoinType.JoinOrCreateRoom && this.LocalPlayer.ActorNumber == 1))
  2776. {
  2777. this.MatchMakingCallbackTargets.OnCreatedRoom();
  2778. }
  2779. this.MatchMakingCallbackTargets.OnJoinedRoom();
  2780. }
  2781. else
  2782. {
  2783. this.InRoomCallbackTargets.OnPlayerEnteredRoom(originatingPlayer);
  2784. }
  2785. break;
  2786. case EventCode.Leave:
  2787. if (originatingPlayer != null)
  2788. {
  2789. bool isInactive = false;
  2790. if (photonEvent.Parameters.ContainsKey(ParameterCode.IsInactive))
  2791. {
  2792. isInactive = (bool)photonEvent.Parameters[ParameterCode.IsInactive];
  2793. }
  2794. if (isInactive)
  2795. {
  2796. originatingPlayer.IsInactive = true;
  2797. }
  2798. else
  2799. {
  2800. originatingPlayer.IsInactive = false;
  2801. this.CurrentRoom.RemovePlayer(actorNr);
  2802. }
  2803. }
  2804. if (photonEvent.Parameters.ContainsKey(ParameterCode.MasterClientId))
  2805. {
  2806. int newMaster = (int)photonEvent[ParameterCode.MasterClientId];
  2807. if (newMaster != 0)
  2808. {
  2809. this.CurrentRoom.masterClientId = newMaster;
  2810. this.InRoomCallbackTargets.OnMasterClientSwitched(this.CurrentRoom.GetPlayer(newMaster));
  2811. }
  2812. }
  2813. // finally, send notification that a player left
  2814. this.InRoomCallbackTargets.OnPlayerLeftRoom(originatingPlayer);
  2815. break;
  2816. case EventCode.PropertiesChanged:
  2817. // whenever properties are sent in-room, they can be broadcasted as event (which we handle here)
  2818. // we get PLAYERproperties if actorNr > 0 or ROOMproperties if actorNumber is not set or 0
  2819. int targetActorNr = 0;
  2820. if (photonEvent.Parameters.ContainsKey(ParameterCode.TargetActorNr))
  2821. {
  2822. targetActorNr = (int)photonEvent[ParameterCode.TargetActorNr];
  2823. }
  2824. Hashtable gameProperties = null;
  2825. Hashtable actorProps = null;
  2826. if (targetActorNr == 0)
  2827. {
  2828. gameProperties = (Hashtable)photonEvent[ParameterCode.Properties];
  2829. }
  2830. else
  2831. {
  2832. actorProps = (Hashtable)photonEvent[ParameterCode.Properties];
  2833. }
  2834. this.ReadoutProperties(gameProperties, actorProps, targetActorNr);
  2835. break;
  2836. case EventCode.AppStats:
  2837. // only the master server sends these in (1 minute) intervals
  2838. this.PlayersInRoomsCount = (int)photonEvent[ParameterCode.PeerCount];
  2839. this.RoomsCount = (int)photonEvent[ParameterCode.GameCount];
  2840. this.PlayersOnMasterCount = (int)photonEvent[ParameterCode.MasterPeerCount];
  2841. break;
  2842. case EventCode.LobbyStats:
  2843. string[] names = photonEvent[ParameterCode.LobbyName] as string[];
  2844. int[] peers = photonEvent[ParameterCode.PeerCount] as int[];
  2845. int[] rooms = photonEvent[ParameterCode.GameCount] as int[];
  2846. byte[] types;
  2847. ByteArraySlice slice = photonEvent[ParameterCode.LobbyType] as ByteArraySlice;
  2848. bool useByteArraySlice = slice != null;
  2849. if (useByteArraySlice)
  2850. {
  2851. types = slice.Buffer;
  2852. }
  2853. else
  2854. {
  2855. types = photonEvent[ParameterCode.LobbyType] as byte[];
  2856. }
  2857. this.lobbyStatistics.Clear();
  2858. for (int i = 0; i < names.Length; i++)
  2859. {
  2860. TypedLobbyInfo info = new TypedLobbyInfo();
  2861. info.Name = names[i];
  2862. info.Type = (LobbyType)types[i];
  2863. info.PlayerCount = peers[i];
  2864. info.RoomCount = rooms[i];
  2865. this.lobbyStatistics.Add(info);
  2866. }
  2867. if (useByteArraySlice)
  2868. {
  2869. slice.Release();
  2870. }
  2871. this.LobbyCallbackTargets.OnLobbyStatisticsUpdate(this.lobbyStatistics);
  2872. break;
  2873. case EventCode.ErrorInfo:
  2874. this.ErrorInfoCallbackTargets.OnErrorInfo(new ErrorInfo(photonEvent));
  2875. break;
  2876. case EventCode.AuthEvent:
  2877. if (this.AuthValues == null)
  2878. {
  2879. this.AuthValues = new AuthenticationValues();
  2880. }
  2881. this.AuthValues.Token = photonEvent[ParameterCode.Token] as string;
  2882. this.tokenCache = this.AuthValues.Token;
  2883. break;
  2884. }
  2885. this.UpdateCallbackTargets();
  2886. if (this.EventReceived != null) this.EventReceived(photonEvent);
  2887. }
  2888. /// <summary>In Photon 4, "raw messages" will get their own callback method in the interface. Not used yet.</summary>
  2889. public virtual void OnMessage(object message)
  2890. {
  2891. this.DebugReturn(DebugLevel.ALL, string.Format("got OnMessage {0}", message));
  2892. }
  2893. #endregion
  2894. private void OnDisconnectMessageReceived(DisconnectMessage obj)
  2895. {
  2896. this.DebugReturn(DebugLevel.ERROR, string.Format("Got DisconnectMessage. Code: {0} Msg: \"{1}\". Debug Info: {2}", obj.Code, obj.DebugMessage, obj.Parameters.ToStringFull()));
  2897. this.Disconnect(DisconnectCause.DisconnectByDisconnectMessage);
  2898. }
  2899. /// <summary>A callback of the RegionHandler, provided in OnRegionListReceived.</summary>
  2900. /// <param name="regionHandler">The regionHandler wraps up best region and other region relevant info.</param>
  2901. private void OnRegionPingCompleted(RegionHandler regionHandler)
  2902. {
  2903. //Debug.Log("OnRegionPingCompleted " + regionHandler.BestRegion);
  2904. //Debug.Log("RegionPingSummary: " + regionHandler.SummaryToCache);
  2905. this.SummaryToCache = regionHandler.SummaryToCache;
  2906. this.ConnectToRegionMaster(regionHandler.BestRegion.Code);
  2907. }
  2908. protected internal static string ReplacePortWithAlternative(string address, ushort replacementPort)
  2909. {
  2910. bool webSocket = address.StartsWith("ws");
  2911. if (webSocket)
  2912. {
  2913. UriBuilder urib = new UriBuilder(address);
  2914. urib.Port = replacementPort;
  2915. return urib.ToString();
  2916. }
  2917. else
  2918. {
  2919. UriBuilder urib = new UriBuilder(string.Format("scheme://{0}", address));
  2920. return string.Format("{0}:{1}", urib.Host, replacementPort);
  2921. }
  2922. }
  2923. private void SetupEncryption(Dictionary<byte, object> encryptionData)
  2924. {
  2925. var mode = (EncryptionMode)(byte)encryptionData[EncryptionDataParameters.Mode];
  2926. switch (mode)
  2927. {
  2928. case EncryptionMode.PayloadEncryption:
  2929. byte[] encryptionSecret = (byte[])encryptionData[EncryptionDataParameters.Secret1];
  2930. this.LoadBalancingPeer.InitPayloadEncryption(encryptionSecret);
  2931. break;
  2932. case EncryptionMode.DatagramEncryption:
  2933. case EncryptionMode.DatagramEncryptionRandomSequence:
  2934. {
  2935. byte[] secret1 = (byte[])encryptionData[EncryptionDataParameters.Secret1];
  2936. byte[] secret2 = (byte[])encryptionData[EncryptionDataParameters.Secret2];
  2937. this.LoadBalancingPeer.InitDatagramEncryption(secret1, secret2, mode == EncryptionMode.DatagramEncryptionRandomSequence);
  2938. }
  2939. break;
  2940. case EncryptionMode.DatagramEncryptionGCM:
  2941. {
  2942. byte[] secret1 = (byte[])encryptionData[EncryptionDataParameters.Secret1];
  2943. this.LoadBalancingPeer.InitDatagramEncryption(secret1, null, true, true);
  2944. }
  2945. break;
  2946. default:
  2947. throw new ArgumentOutOfRangeException();
  2948. }
  2949. }
  2950. /// <summary>
  2951. /// This operation makes Photon call your custom web-service by path/name with the given parameters (converted into Json).
  2952. /// Use <see cref="IWebRpcCallback.OnWebRpcResponse"/> as a callback.
  2953. /// </summary>
  2954. /// <remarks>
  2955. /// A WebRPC calls a custom, http-based function on a server you provide. The uriPath is relative to a "base path"
  2956. /// which is configured server-side. The sent parameters get converted from C# types to Json. Vice versa, the response
  2957. /// of the web-service will be converted to C# types and sent back as normal operation response.
  2958. ///
  2959. /// To use this feature, you have to setup your server:
  2960. ///
  2961. /// For a Photon Cloud application, <a href="https://doc.photonengine.com/en-us/realtime/current/reference/webhooks">
  2962. /// visit the Dashboard </a> and setup "WebHooks". The BaseUrl is used for WebRPCs as well.
  2963. ///
  2964. /// The class <see cref="WebRpcResponse"/> is a helper-class that extracts the most valuable content from the WebRPC
  2965. /// response.
  2966. /// </remarks>
  2967. /// <param name="uriPath">The url path to call, relative to the baseUrl configured on Photon's server-side.</param>
  2968. /// <param name="parameters">The parameters to send to the web-service method.</param>
  2969. /// <param name="sendAuthCookie">Defines if the authentication cookie gets sent to a WebHook (if setup).</param>
  2970. public bool OpWebRpc(string uriPath, object parameters, bool sendAuthCookie = false)
  2971. {
  2972. if (string.IsNullOrEmpty(uriPath))
  2973. {
  2974. this.DebugReturn(DebugLevel.ERROR, "WebRPC method name must not be null nor empty.");
  2975. return false;
  2976. }
  2977. if (!this.CheckIfOpCanBeSent(OperationCode.WebRpc, this.Server, "WebRpc"))
  2978. {
  2979. return false;
  2980. }
  2981. Dictionary<byte, object> opParameters = new Dictionary<byte, object>();
  2982. opParameters.Add(ParameterCode.UriPath, uriPath);
  2983. if (parameters != null)
  2984. {
  2985. opParameters.Add(ParameterCode.WebRpcParameters, parameters);
  2986. }
  2987. if (sendAuthCookie)
  2988. {
  2989. opParameters.Add(ParameterCode.EventForward, WebFlags.SendAuthCookieConst);
  2990. }
  2991. //return this.LoadBalancingPeer.OpCustom(OperationCode.WebRpc, opParameters, true);
  2992. return this.LoadBalancingPeer.SendOperation(OperationCode.WebRpc, opParameters, SendOptions.SendReliable);
  2993. }
  2994. /// <summary>
  2995. /// Registers an object for callbacks for the implemented callback-interfaces.
  2996. /// </summary>
  2997. /// <remarks>
  2998. /// Adding and removing callback targets is queued to not mess with callbacks in execution.
  2999. /// Internally, this means that the addition/removal is done before the LoadBalancingClient
  3000. /// calls the next callbacks. This detail should not affect a game's workflow.
  3001. ///
  3002. /// The covered callback interfaces are: IConnectionCallbacks, IMatchmakingCallbacks,
  3003. /// ILobbyCallbacks, IInRoomCallbacks, IOnEventCallback and IWebRpcCallback.
  3004. ///
  3005. /// See: <a href="https://doc.photonengine.com/en-us/realtime/current/reference/dotnet-callbacks"/>
  3006. /// </remarks>
  3007. /// <param name="target">The object that registers to get callbacks from this client.</param>
  3008. public void AddCallbackTarget(object target)
  3009. {
  3010. this.callbackTargetChanges.Enqueue(new CallbackTargetChange(target, true));
  3011. }
  3012. /// <summary>
  3013. /// Unregisters an object from callbacks for the implemented callback-interfaces.
  3014. /// </summary>
  3015. /// <remarks>
  3016. /// Adding and removing callback targets is queued to not mess with callbacks in execution.
  3017. /// Internally, this means that the addition/removal is done before the LoadBalancingClient
  3018. /// calls the next callbacks. This detail should not affect a game's workflow.
  3019. ///
  3020. /// The covered callback interfaces are: IConnectionCallbacks, IMatchmakingCallbacks,
  3021. /// ILobbyCallbacks, IInRoomCallbacks, IOnEventCallback and IWebRpcCallback.
  3022. ///
  3023. /// See: <a href="https://doc.photonengine.com/en-us/realtime/current/reference/dotnet-callbacks"></a>
  3024. /// </remarks>
  3025. /// <param name="target">The object that unregisters from getting callbacks.</param>
  3026. public void RemoveCallbackTarget(object target)
  3027. {
  3028. this.callbackTargetChanges.Enqueue(new CallbackTargetChange(target, false));
  3029. }
  3030. /// <summary>
  3031. /// Applies queued callback cahnges from a queue to the actual containers. Will cause exceptions if used while callbacks execute.
  3032. /// </summary>
  3033. /// <remarks>
  3034. /// There is no explicit check that this is not called during callbacks, however the implemented, private logic takes care of this.
  3035. /// </remarks>
  3036. protected internal void UpdateCallbackTargets()
  3037. {
  3038. while (this.callbackTargetChanges.Count > 0)
  3039. {
  3040. CallbackTargetChange change = this.callbackTargetChanges.Dequeue();
  3041. if (change.AddTarget)
  3042. {
  3043. if (this.callbackTargets.Contains(change.Target))
  3044. {
  3045. //Debug.Log("UpdateCallbackTargets skipped adding a target, as the object is already registered. Target: " + change.Target);
  3046. continue;
  3047. }
  3048. this.callbackTargets.Add(change.Target);
  3049. }
  3050. else
  3051. {
  3052. if (!this.callbackTargets.Contains(change.Target))
  3053. {
  3054. //Debug.Log("UpdateCallbackTargets skipped removing a target, as the object is not registered. Target: " + change.Target);
  3055. continue;
  3056. }
  3057. this.callbackTargets.Remove(change.Target);
  3058. }
  3059. this.UpdateCallbackTarget<IInRoomCallbacks>(change, this.InRoomCallbackTargets);
  3060. this.UpdateCallbackTarget<IConnectionCallbacks>(change, this.ConnectionCallbackTargets);
  3061. this.UpdateCallbackTarget<IMatchmakingCallbacks>(change, this.MatchMakingCallbackTargets);
  3062. this.UpdateCallbackTarget<ILobbyCallbacks>(change, this.LobbyCallbackTargets);
  3063. this.UpdateCallbackTarget<IWebRpcCallback>(change, this.WebRpcCallbackTargets);
  3064. this.UpdateCallbackTarget<IErrorInfoCallback>(change, this.ErrorInfoCallbackTargets);
  3065. IOnEventCallback onEventCallback = change.Target as IOnEventCallback;
  3066. if (onEventCallback != null)
  3067. {
  3068. if (change.AddTarget)
  3069. {
  3070. EventReceived += onEventCallback.OnEvent;
  3071. }
  3072. else
  3073. {
  3074. EventReceived -= onEventCallback.OnEvent;
  3075. }
  3076. }
  3077. }
  3078. }
  3079. /// <summary>Helper method to cast and apply a target per (interface) type.</summary>
  3080. /// <typeparam name="T">Either of the interfaces for callbacks.</typeparam>
  3081. /// <param name="change">The queued change to apply (add or remove) some target.</param>
  3082. /// <param name="container">The container that calls callbacks on it's list of targets.</param>
  3083. private void UpdateCallbackTarget<T>(CallbackTargetChange change, List<T> container) where T : class
  3084. {
  3085. T target = change.Target as T;
  3086. if (target != null)
  3087. {
  3088. if (change.AddTarget)
  3089. {
  3090. container.Add(target);
  3091. }
  3092. else
  3093. {
  3094. container.Remove(target);
  3095. }
  3096. }
  3097. }
  3098. }
  3099. /// <summary>
  3100. /// Collection of "organizational" callbacks for the Realtime Api to cover: Connection and Regions.
  3101. /// </summary>
  3102. /// <remarks>
  3103. /// Classes that implement this interface must be registered to get callbacks for various situations.
  3104. ///
  3105. /// To register for callbacks, call <see cref="LoadBalancingClient.AddCallbackTarget"/> and pass the class implementing this interface
  3106. /// To stop getting callbacks, call <see cref="LoadBalancingClient.RemoveCallbackTarget"/> and pass the class implementing this interface
  3107. ///
  3108. /// </remarks>
  3109. /// \ingroup callbacks
  3110. public interface IConnectionCallbacks
  3111. {
  3112. /// <summary>
  3113. /// Called to signal that the "low level connection" got established but before the client can call operation on the server.
  3114. /// </summary>
  3115. /// <remarks>
  3116. /// After the (low level transport) connection is established, the client will automatically send
  3117. /// the Authentication operation, which needs to get a response before the client can call other operations.
  3118. ///
  3119. /// Your logic should wait for either: OnRegionListReceived or OnConnectedToMaster.
  3120. ///
  3121. /// This callback is useful to detect if the server can be reached at all (technically).
  3122. /// Most often, it's enough to implement OnDisconnected(DisconnectCause cause) and check for the cause.
  3123. ///
  3124. /// This is not called for transitions from the masterserver to game servers.
  3125. /// </remarks>
  3126. void OnConnected();
  3127. /// <summary>
  3128. /// Called when the client is connected to the Master Server and ready for matchmaking and other tasks.
  3129. /// </summary>
  3130. /// <remarks>
  3131. /// The list of available rooms won't become available unless you join a lobby via LoadBalancingClient.OpJoinLobby.
  3132. /// You can join rooms and create them even without being in a lobby. The default lobby is used in that case.
  3133. /// </remarks>
  3134. void OnConnectedToMaster();
  3135. /// <summary>
  3136. /// Called after disconnecting from the Photon server. It could be a failure or an explicit disconnect call
  3137. /// </summary>
  3138. /// <remarks>
  3139. /// The reason for this disconnect is provided as DisconnectCause.
  3140. /// </remarks>
  3141. void OnDisconnected(DisconnectCause cause);
  3142. /// <summary>
  3143. /// Called when the Name Server provided a list of regions for your title.
  3144. /// </summary>
  3145. /// <remarks>Check the RegionHandler class description, to make use of the provided values.</remarks>
  3146. /// <param name="regionHandler">The currently used RegionHandler.</param>
  3147. void OnRegionListReceived(RegionHandler regionHandler);
  3148. /// <summary>
  3149. /// Called when your Custom Authentication service responds with additional data.
  3150. /// </summary>
  3151. /// <remarks>
  3152. /// Custom Authentication services can include some custom data in their response.
  3153. /// When present, that data is made available in this callback as Dictionary.
  3154. /// While the keys of your data have to be strings, the values can be either string or a number (in Json).
  3155. /// You need to make extra sure, that the value type is the one you expect. Numbers become (currently) int64.
  3156. ///
  3157. /// Example: void OnCustomAuthenticationResponse(Dictionary&lt;string, object&gt; data) { ... }
  3158. /// </remarks>
  3159. /// <see cref="https://doc.photonengine.com/en-us/realtime/current/reference/custom-authentication"/>
  3160. void OnCustomAuthenticationResponse(Dictionary<string, object> data);
  3161. /// <summary>
  3162. /// Called when the custom authentication failed. Followed by disconnect!
  3163. /// </summary>
  3164. /// <remarks>
  3165. /// Custom Authentication can fail due to user-input, bad tokens/secrets.
  3166. /// If authentication is successful, this method is not called. Implement OnJoinedLobby() or OnConnectedToMaster() (as usual).
  3167. ///
  3168. /// During development of a game, it might also fail due to wrong configuration on the server side.
  3169. /// In those cases, logging the debugMessage is very important.
  3170. ///
  3171. /// Unless you setup a custom authentication service for your app (in the [Dashboard](https://dashboard.photonengine.com)),
  3172. /// this won't be called!
  3173. /// </remarks>
  3174. /// <param name="debugMessage">Contains a debug message why authentication failed. This has to be fixed during development.</param>
  3175. void OnCustomAuthenticationFailed(string debugMessage);
  3176. }
  3177. /// <summary>
  3178. /// Collection of "organizational" callbacks for the Realtime Api to cover the Lobby.
  3179. /// </summary>
  3180. /// <remarks>
  3181. /// Classes that implement this interface must be registered to get callbacks for various situations.
  3182. ///
  3183. /// To register for callbacks, call <see cref="LoadBalancingClient.AddCallbackTarget"/> and pass the class implementing this interface
  3184. /// To stop getting callbacks, call <see cref="LoadBalancingClient.RemoveCallbackTarget"/> and pass the class implementing this interface
  3185. ///
  3186. /// </remarks>
  3187. /// \ingroup callbacks
  3188. public interface ILobbyCallbacks
  3189. {
  3190. /// <summary>
  3191. /// Called on entering a lobby on the Master Server. The actual room-list updates will call OnRoomListUpdate.
  3192. /// </summary>
  3193. /// <remarks>
  3194. /// While in the lobby, the roomlist is automatically updated in fixed intervals (which you can't modify in the public cloud).
  3195. /// The room list gets available via OnRoomListUpdate.
  3196. /// </remarks>
  3197. void OnJoinedLobby();
  3198. /// <summary>
  3199. /// Called after leaving a lobby.
  3200. /// </summary>
  3201. /// <remarks>
  3202. /// When you leave a lobby, [OpCreateRoom](@ref OpCreateRoom) and [OpJoinRandomRoom](@ref OpJoinRandomRoom)
  3203. /// automatically refer to the default lobby.
  3204. /// </remarks>
  3205. void OnLeftLobby();
  3206. /// <summary>
  3207. /// Called for any update of the room-listing while in a lobby (InLobby) on the Master Server.
  3208. /// </summary>
  3209. /// <remarks>
  3210. /// Each item is a RoomInfo which might include custom properties (provided you defined those as lobby-listed when creating a room).
  3211. /// Not all types of lobbies provide a listing of rooms to the client. Some are silent and specialized for server-side matchmaking.
  3212. /// </remarks>
  3213. void OnRoomListUpdate(List<RoomInfo> roomList);
  3214. /// <summary>
  3215. /// Called when the Master Server sent an update for the Lobby Statistics.
  3216. /// </summary>
  3217. /// <remarks>
  3218. /// This callback has two preconditions:
  3219. /// EnableLobbyStatistics must be set to true, before this client connects.
  3220. /// And the client has to be connected to the Master Server, which is providing the info about lobbies.
  3221. /// </remarks>
  3222. void OnLobbyStatisticsUpdate(List<TypedLobbyInfo> lobbyStatistics);
  3223. }
  3224. /// <summary>
  3225. /// Collection of "organizational" callbacks for the Realtime Api to cover Matchmaking.
  3226. /// </summary>
  3227. /// <remarks>
  3228. /// Classes that implement this interface must be registered to get callbacks for various situations.
  3229. ///
  3230. /// To register for callbacks, call <see cref="LoadBalancingClient.AddCallbackTarget"/> and pass the class implementing this interface
  3231. /// To stop getting callbacks, call <see cref="LoadBalancingClient.RemoveCallbackTarget"/> and pass the class implementing this interface
  3232. ///
  3233. /// </remarks>
  3234. /// \ingroup callbacks
  3235. public interface IMatchmakingCallbacks
  3236. {
  3237. /// <summary>
  3238. /// Called when the server sent the response to a FindFriends request.
  3239. /// </summary>
  3240. /// <remarks>
  3241. /// After calling OpFindFriends, the Master Server will cache the friend list and send updates to the friend
  3242. /// list. The friends includes the name, userId, online state and the room (if any) for each requested user/friend.
  3243. ///
  3244. /// Use the friendList to update your UI and store it, if the UI should highlight changes.
  3245. /// </remarks>
  3246. void OnFriendListUpdate(List<FriendInfo> friendList);
  3247. /// <summary>
  3248. /// Called when this client created a room and entered it. OnJoinedRoom() will be called as well.
  3249. /// </summary>
  3250. /// <remarks>
  3251. /// This callback is only called on the client which created a room (see OpCreateRoom).
  3252. ///
  3253. /// As any client might close (or drop connection) anytime, there is a chance that the
  3254. /// creator of a room does not execute OnCreatedRoom.
  3255. ///
  3256. /// If you need specific room properties or a "start signal", implement OnMasterClientSwitched()
  3257. /// and make each new MasterClient check the room's state.
  3258. /// </remarks>
  3259. void OnCreatedRoom();
  3260. /// <summary>
  3261. /// Called when the server couldn't create a room (OpCreateRoom failed).
  3262. /// </summary>
  3263. /// <remarks>
  3264. /// Creating a room may fail for various reasons. Most often, the room already exists (roomname in use) or
  3265. /// the RoomOptions clash and it's impossible to create the room.
  3266. ///
  3267. /// When creating a room fails on a Game Server:
  3268. /// The client will cache the failure internally and returns to the Master Server before it calls the fail-callback.
  3269. /// This way, the client is ready to find/create a room at the moment of the callback.
  3270. /// In this case, the client skips calling OnConnectedToMaster but returning to the Master Server will still call OnConnected.
  3271. /// Treat callbacks of OnConnected as pure information that the client could connect.
  3272. /// </remarks>
  3273. /// <param name="returnCode">Operation ReturnCode from the server.</param>
  3274. /// <param name="message">Debug message for the error.</param>
  3275. void OnCreateRoomFailed(short returnCode, string message);
  3276. /// <summary>
  3277. /// Called when the LoadBalancingClient entered a room, no matter if this client created it or simply joined.
  3278. /// </summary>
  3279. /// <remarks>
  3280. /// When this is called, you can access the existing players in Room.Players, their custom properties and Room.CustomProperties.
  3281. ///
  3282. /// In this callback, you could create player objects. For example in Unity, instantiate a prefab for the player.
  3283. ///
  3284. /// If you want a match to be started "actively", enable the user to signal "ready" (using OpRaiseEvent or a Custom Property).
  3285. /// </remarks>
  3286. void OnJoinedRoom();
  3287. /// <summary>
  3288. /// Called when a previous OpJoinRoom call failed on the server.
  3289. /// </summary>
  3290. /// <remarks>
  3291. /// Joining a room may fail for various reasons. Most often, the room is full or does not exist anymore
  3292. /// (due to someone else being faster or closing the room).
  3293. ///
  3294. /// When joining a room fails on a Game Server:
  3295. /// The client will cache the failure internally and returns to the Master Server before it calls the fail-callback.
  3296. /// This way, the client is ready to find/create a room at the moment of the callback.
  3297. /// In this case, the client skips calling OnConnectedToMaster but returning to the Master Server will still call OnConnected.
  3298. /// Treat callbacks of OnConnected as pure information that the client could connect.
  3299. /// </remarks>
  3300. /// <param name="returnCode">Operation ReturnCode from the server.</param>
  3301. /// <param name="message">Debug message for the error.</param>
  3302. void OnJoinRoomFailed(short returnCode, string message);
  3303. /// <summary>
  3304. /// Called when a previous OpJoinRandom call failed on the server.
  3305. /// </summary>
  3306. /// <remarks>
  3307. /// The most common causes are that a room is full or does not exist (due to someone else being faster or closing the room).
  3308. ///
  3309. /// This operation is only ever sent to the Master Server. Once a room is found by the Master Server, the client will
  3310. /// head off to the designated Game Server and use the operation Join on the Game Server.
  3311. ///
  3312. /// When using multiple lobbies (via OpJoinLobby or a TypedLobby parameter), another lobby might have more/fitting rooms.<br/>
  3313. /// </remarks>
  3314. /// <param name="returnCode">Operation ReturnCode from the server.</param>
  3315. /// <param name="message">Debug message for the error.</param>
  3316. void OnJoinRandomFailed(short returnCode, string message);
  3317. /// <summary>
  3318. /// Called when the local user/client left a room, so the game's logic can clean up it's internal state.
  3319. /// </summary>
  3320. /// <remarks>
  3321. /// When leaving a room, the LoadBalancingClient will disconnect the Game Server and connect to the Master Server.
  3322. /// This wraps up multiple internal actions.
  3323. ///
  3324. /// Wait for the callback OnConnectedToMaster, before you use lobbies and join or create rooms.
  3325. /// </remarks>
  3326. void OnLeftRoom();
  3327. }
  3328. /// <summary>
  3329. /// Collection of "in room" callbacks for the Realtime Api to cover: Players entering or leaving, property updates and Master Client switching.
  3330. /// </summary>
  3331. /// <remarks>
  3332. /// Classes that implement this interface must be registered to get callbacks for various situations.
  3333. ///
  3334. /// To register for callbacks, call <see cref="LoadBalancingClient.AddCallbackTarget"/> and pass the class implementing this interface
  3335. /// To stop getting callbacks, call <see cref="LoadBalancingClient.RemoveCallbackTarget"/> and pass the class implementing this interface
  3336. ///
  3337. /// </remarks>
  3338. /// \ingroup callbacks
  3339. public interface IInRoomCallbacks
  3340. {
  3341. /// <summary>
  3342. /// Called when a remote player entered the room. This Player is already added to the playerlist.
  3343. /// </summary>
  3344. /// <remarks>
  3345. /// If your game starts with a certain number of players, this callback can be useful to check the
  3346. /// Room.playerCount and find out if you can start.
  3347. /// </remarks>
  3348. void OnPlayerEnteredRoom(Player newPlayer);
  3349. /// <summary>
  3350. /// Called when a remote player left the room or became inactive. Check otherPlayer.IsInactive.
  3351. /// </summary>
  3352. /// <remarks>
  3353. /// If another player leaves the room or if the server detects a lost connection, this callback will
  3354. /// be used to notify your game logic.
  3355. ///
  3356. /// Depending on the room's setup, players may become inactive, which means they may return and retake
  3357. /// their spot in the room. In such cases, the Player stays in the Room.Players dictionary.
  3358. ///
  3359. /// If the player is not just inactive, it gets removed from the Room.Players dictionary, before
  3360. /// the callback is called.
  3361. /// </remarks>
  3362. void OnPlayerLeftRoom(Player otherPlayer);
  3363. /// <summary>
  3364. /// Called when a room's custom properties changed. The propertiesThatChanged contains all that was set via Room.SetCustomProperties.
  3365. /// </summary>
  3366. /// <remarks>
  3367. /// Since v1.25 this method has one parameter: Hashtable propertiesThatChanged.<br/>
  3368. /// Changing properties must be done by Room.SetCustomProperties, which causes this callback locally, too.
  3369. /// </remarks>
  3370. /// <param name="propertiesThatChanged"></param>
  3371. void OnRoomPropertiesUpdate(Hashtable propertiesThatChanged);
  3372. /// <summary>
  3373. /// Called when custom player-properties are changed. Player and the changed properties are passed as object[].
  3374. /// </summary>
  3375. /// <remarks>
  3376. /// Changing properties must be done by Player.SetCustomProperties, which causes this callback locally, too.
  3377. /// </remarks>
  3378. /// <param name="targetPlayer">Contains Player that changed.</param>
  3379. /// <param name="changedProps">Contains the properties that changed.</param>
  3380. void OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps);
  3381. /// <summary>
  3382. /// Called after switching to a new MasterClient when the current one leaves.
  3383. /// </summary>
  3384. /// <remarks>
  3385. /// This is not called when this client enters a room.
  3386. /// The former MasterClient is still in the player list when this method get called.
  3387. /// </remarks>
  3388. void OnMasterClientSwitched(Player newMasterClient);
  3389. }
  3390. /// <summary>
  3391. /// Event callback for the Realtime Api. Covers events from the server and those sent by clients via OpRaiseEvent.
  3392. /// </summary>
  3393. /// <remarks>
  3394. /// Classes that implement this interface must be registered to get callbacks for various situations.
  3395. ///
  3396. /// To register for callbacks, call <see cref="LoadBalancingClient.AddCallbackTarget"/> and pass the class implementing this interface
  3397. /// To stop getting callbacks, call <see cref="LoadBalancingClient.RemoveCallbackTarget"/> and pass the class implementing this interface
  3398. ///
  3399. /// </remarks>
  3400. /// \ingroup callbacks
  3401. public interface IOnEventCallback
  3402. {
  3403. /// <summary>Called for any incoming events.</summary>
  3404. /// <remarks>
  3405. /// To receive events, implement IOnEventCallback in any class and register it via AddCallbackTarget
  3406. /// (either in LoadBalancingClient or PhotonNetwork).
  3407. ///
  3408. /// With the EventData.Sender you can look up the Player who sent the event.
  3409. ///
  3410. /// It is best practice to assign an eventCode for each different type of content and action, so the Code
  3411. /// will be essential to read the incoming events.
  3412. /// </remarks>
  3413. void OnEvent(EventData photonEvent);
  3414. }
  3415. /// <summary>
  3416. /// Interface for "WebRpc" callbacks for the Realtime Api. Currently includes only responses for Web RPCs.
  3417. /// </summary>
  3418. /// <remarks>
  3419. /// Classes that implement this interface must be registered to get callbacks for various situations.
  3420. ///
  3421. /// To register for callbacks, call <see cref="LoadBalancingClient.AddCallbackTarget"/> and pass the class implementing this interface
  3422. /// To stop getting callbacks, call <see cref="LoadBalancingClient.RemoveCallbackTarget"/> and pass the class implementing this interface
  3423. ///
  3424. /// </remarks>
  3425. /// \ingroup callbacks
  3426. public interface IWebRpcCallback
  3427. {
  3428. /// <summary>
  3429. /// Called when the response to a WebRPC is available. See <see cref="LoadBalancingClient.OpWebRpc"/>.
  3430. /// </summary>
  3431. /// <remarks>
  3432. /// Important: The response.ReturnCode is 0 if Photon was able to reach your web-service.<br/>
  3433. /// The content of the response is what your web-service sent. You can create a WebRpcResponse from it.<br/>
  3434. /// Example: WebRpcResponse webResponse = new WebRpcResponse(operationResponse);<br/>
  3435. ///
  3436. /// Please note: Class OperationResponse is in a namespace which needs to be "used":<br/>
  3437. /// using ExitGames.Client.Photon; // includes OperationResponse (and other classes)
  3438. /// </remarks>
  3439. /// <example>
  3440. /// public void OnWebRpcResponse(OperationResponse response)
  3441. /// {
  3442. /// Debug.LogFormat("WebRPC operation response {0}", response.ToStringFull());
  3443. /// switch (response.ReturnCode)
  3444. /// {
  3445. /// case ErrorCode.Ok:
  3446. /// WebRpcResponse webRpcResponse = new WebRpcResponse(response);
  3447. /// Debug.LogFormat("Parsed WebRPC response {0}", response.ToStringFull());
  3448. /// if (string.IsNullOrEmpty(webRpcResponse.Name))
  3449. /// {
  3450. /// Debug.LogError("Unexpected: WebRPC response did not contain WebRPC method name");
  3451. /// }
  3452. /// if (webRpcResponse.ResultCode == 0) // success
  3453. /// {
  3454. /// switch (webRpcResponse.Name)
  3455. /// {
  3456. /// // todo: add your code here
  3457. /// case GetGameListWebRpcMethodName: // example
  3458. /// // ...
  3459. /// break;
  3460. /// }
  3461. /// }
  3462. /// else if (webRpcResponse.ResultCode == -1)
  3463. /// {
  3464. /// Debug.LogErrorFormat("Web server did not return ResultCode for WebRPC method=\"{0}\", Message={1}", webRpcResponse.Name, webRpcResponse.Message);
  3465. /// }
  3466. /// else
  3467. /// {
  3468. /// Debug.LogErrorFormat("Web server returned ResultCode={0} for WebRPC method=\"{1}\", Message={2}", webRpcResponse.ResultCode, webRpcResponse.Name, webRpcResponse.Message);
  3469. /// }
  3470. /// break;
  3471. /// case ErrorCode.ExternalHttpCallFailed: // web service unreachable
  3472. /// Debug.LogErrorFormat("WebRPC call failed as request could not be sent to the server. {0}", response.DebugMessage);
  3473. /// break;
  3474. /// case ErrorCode.HttpLimitReached: // too many WebRPCs in a short period of time
  3475. /// // the debug message should contain the limit exceeded
  3476. /// Debug.LogErrorFormat("WebRPCs rate limit exceeded: {0}", response.DebugMessage);
  3477. /// break;
  3478. /// case ErrorCode.InvalidOperation: // WebRPC not configured at all OR not configured properly OR trying to send on name server
  3479. /// if (PhotonNetwork.Server == ServerConnection.NameServer)
  3480. /// {
  3481. /// Debug.LogErrorFormat("WebRPC not supported on NameServer. {0}", response.DebugMessage);
  3482. /// }
  3483. /// else
  3484. /// {
  3485. /// Debug.LogErrorFormat("WebRPC not properly configured or not configured at all. {0}", response.DebugMessage);
  3486. /// }
  3487. /// break;
  3488. /// default:
  3489. /// // other unknown error, unexpected
  3490. /// Debug.LogErrorFormat("Unexpected error, {0} {1}", response.ReturnCode, response.DebugMessage);
  3491. /// break;
  3492. /// }
  3493. /// }
  3494. ///
  3495. /// </example>
  3496. void OnWebRpcResponse(OperationResponse response);
  3497. }
  3498. /// <summary>
  3499. /// Interface for <see cref="EventCode.ErrorInfo"/> event callback for the Realtime Api.
  3500. /// </summary>
  3501. /// <remarks>
  3502. /// Classes that implement this interface must be registered to get callbacks for various situations.
  3503. ///
  3504. /// To register for callbacks, call <see cref="LoadBalancingClient.AddCallbackTarget"/> and pass the class implementing this interface
  3505. /// To stop getting callbacks, call <see cref="LoadBalancingClient.RemoveCallbackTarget"/> and pass the class implementing this interface
  3506. ///
  3507. /// </remarks>
  3508. /// \ingroup callbacks
  3509. public interface IErrorInfoCallback
  3510. {
  3511. /// <summary>
  3512. /// Called when the client receives an event from the server indicating that an error happened there.
  3513. /// </summary>
  3514. /// <remarks>
  3515. /// In most cases this could be either:
  3516. /// 1. an error from webhooks plugin (if HasErrorInfo is enabled), read more here:
  3517. /// https://doc.photonengine.com/en-us/realtime/current/gameplay/web-extensions/webhooks#options
  3518. /// 2. an error sent from a custom server plugin via PluginHost.BroadcastErrorInfoEvent, see example here:
  3519. /// https://doc.photonengine.com/en-us/server/current/plugins/manual#handling_http_response
  3520. /// 3. an error sent from the server, for example, when the limit of cached events has been exceeded in the room
  3521. /// (all clients will be disconnected and the room will be closed in this case)
  3522. /// read more here: https://doc.photonengine.com/en-us/realtime/current/gameplay/cached-events#special_considerations
  3523. ///
  3524. /// If you implement <see cref="IOnEventCallback.OnEvent"/> or <see cref="LoadBalancingClient.EventReceived"/> you will also get this event.
  3525. /// </remarks>
  3526. /// <param name="errorInfo">Object containing information about the error</param>
  3527. void OnErrorInfo(ErrorInfo errorInfo);
  3528. }
  3529. /// <summary>
  3530. /// Container type for callbacks defined by IConnectionCallbacks. See LoadBalancingCallbackTargets.
  3531. /// </summary>
  3532. /// <remarks>
  3533. /// While the interfaces of callbacks wrap up the methods that will be called,
  3534. /// the container classes implement a simple way to call a method on all registered objects.
  3535. /// </remarks>
  3536. public class ConnectionCallbacksContainer : List<IConnectionCallbacks>, IConnectionCallbacks
  3537. {
  3538. private readonly LoadBalancingClient client;
  3539. public ConnectionCallbacksContainer(LoadBalancingClient client)
  3540. {
  3541. this.client = client;
  3542. }
  3543. public void OnConnected()
  3544. {
  3545. this.client.UpdateCallbackTargets();
  3546. foreach (IConnectionCallbacks target in this)
  3547. {
  3548. target.OnConnected();
  3549. }
  3550. }
  3551. public void OnConnectedToMaster()
  3552. {
  3553. this.client.UpdateCallbackTargets();
  3554. foreach (IConnectionCallbacks target in this)
  3555. {
  3556. target.OnConnectedToMaster();
  3557. }
  3558. }
  3559. public void OnRegionListReceived(RegionHandler regionHandler)
  3560. {
  3561. this.client.UpdateCallbackTargets();
  3562. foreach (IConnectionCallbacks target in this)
  3563. {
  3564. target.OnRegionListReceived(regionHandler);
  3565. }
  3566. }
  3567. public void OnDisconnected(DisconnectCause cause)
  3568. {
  3569. this.client.UpdateCallbackTargets();
  3570. foreach (IConnectionCallbacks target in this)
  3571. {
  3572. target.OnDisconnected(cause);
  3573. }
  3574. }
  3575. public void OnCustomAuthenticationResponse(Dictionary<string, object> data)
  3576. {
  3577. this.client.UpdateCallbackTargets();
  3578. foreach (IConnectionCallbacks target in this)
  3579. {
  3580. target.OnCustomAuthenticationResponse(data);
  3581. }
  3582. }
  3583. public void OnCustomAuthenticationFailed(string debugMessage)
  3584. {
  3585. this.client.UpdateCallbackTargets();
  3586. foreach (IConnectionCallbacks target in this)
  3587. {
  3588. target.OnCustomAuthenticationFailed(debugMessage);
  3589. }
  3590. }
  3591. }
  3592. /// <summary>
  3593. /// Container type for callbacks defined by IMatchmakingCallbacks. See MatchMakingCallbackTargets.
  3594. /// </summary>
  3595. /// <remarks>
  3596. /// While the interfaces of callbacks wrap up the methods that will be called,
  3597. /// the container classes implement a simple way to call a method on all registered objects.
  3598. /// </remarks>
  3599. public class MatchMakingCallbacksContainer : List<IMatchmakingCallbacks>, IMatchmakingCallbacks
  3600. {
  3601. private readonly LoadBalancingClient client;
  3602. public MatchMakingCallbacksContainer(LoadBalancingClient client)
  3603. {
  3604. this.client = client;
  3605. }
  3606. public void OnCreatedRoom()
  3607. {
  3608. this.client.UpdateCallbackTargets();
  3609. foreach (IMatchmakingCallbacks target in this)
  3610. {
  3611. target.OnCreatedRoom();
  3612. }
  3613. }
  3614. public void OnJoinedRoom()
  3615. {
  3616. this.client.UpdateCallbackTargets();
  3617. foreach (IMatchmakingCallbacks target in this)
  3618. {
  3619. target.OnJoinedRoom();
  3620. }
  3621. }
  3622. public void OnCreateRoomFailed(short returnCode, string message)
  3623. {
  3624. this.client.UpdateCallbackTargets();
  3625. foreach (IMatchmakingCallbacks target in this)
  3626. {
  3627. target.OnCreateRoomFailed(returnCode, message);
  3628. }
  3629. }
  3630. public void OnJoinRandomFailed(short returnCode, string message)
  3631. {
  3632. this.client.UpdateCallbackTargets();
  3633. foreach (IMatchmakingCallbacks target in this)
  3634. {
  3635. target.OnJoinRandomFailed(returnCode, message);
  3636. }
  3637. }
  3638. public void OnJoinRoomFailed(short returnCode, string message)
  3639. {
  3640. this.client.UpdateCallbackTargets();
  3641. foreach (IMatchmakingCallbacks target in this)
  3642. {
  3643. target.OnJoinRoomFailed(returnCode, message);
  3644. }
  3645. }
  3646. public void OnLeftRoom()
  3647. {
  3648. this.client.UpdateCallbackTargets();
  3649. foreach (IMatchmakingCallbacks target in this)
  3650. {
  3651. target.OnLeftRoom();
  3652. }
  3653. }
  3654. public void OnFriendListUpdate(List<FriendInfo> friendList)
  3655. {
  3656. this.client.UpdateCallbackTargets();
  3657. foreach (IMatchmakingCallbacks target in this)
  3658. {
  3659. target.OnFriendListUpdate(friendList);
  3660. }
  3661. }
  3662. }
  3663. /// <summary>
  3664. /// Container type for callbacks defined by IInRoomCallbacks. See InRoomCallbackTargets.
  3665. /// </summary>
  3666. /// <remarks>
  3667. /// While the interfaces of callbacks wrap up the methods that will be called,
  3668. /// the container classes implement a simple way to call a method on all registered objects.
  3669. /// </remarks>
  3670. internal class InRoomCallbacksContainer : List<IInRoomCallbacks>, IInRoomCallbacks
  3671. {
  3672. private readonly LoadBalancingClient client;
  3673. public InRoomCallbacksContainer(LoadBalancingClient client)
  3674. {
  3675. this.client = client;
  3676. }
  3677. public void OnPlayerEnteredRoom(Player newPlayer)
  3678. {
  3679. this.client.UpdateCallbackTargets();
  3680. foreach (IInRoomCallbacks target in this)
  3681. {
  3682. target.OnPlayerEnteredRoom(newPlayer);
  3683. }
  3684. }
  3685. public void OnPlayerLeftRoom(Player otherPlayer)
  3686. {
  3687. this.client.UpdateCallbackTargets();
  3688. foreach (IInRoomCallbacks target in this)
  3689. {
  3690. target.OnPlayerLeftRoom(otherPlayer);
  3691. }
  3692. }
  3693. public void OnRoomPropertiesUpdate(Hashtable propertiesThatChanged)
  3694. {
  3695. this.client.UpdateCallbackTargets();
  3696. foreach (IInRoomCallbacks target in this)
  3697. {
  3698. target.OnRoomPropertiesUpdate(propertiesThatChanged);
  3699. }
  3700. }
  3701. public void OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProp)
  3702. {
  3703. this.client.UpdateCallbackTargets();
  3704. foreach (IInRoomCallbacks target in this)
  3705. {
  3706. target.OnPlayerPropertiesUpdate(targetPlayer, changedProp);
  3707. }
  3708. }
  3709. public void OnMasterClientSwitched(Player newMasterClient)
  3710. {
  3711. this.client.UpdateCallbackTargets();
  3712. foreach (IInRoomCallbacks target in this)
  3713. {
  3714. target.OnMasterClientSwitched(newMasterClient);
  3715. }
  3716. }
  3717. }
  3718. /// <summary>
  3719. /// Container type for callbacks defined by ILobbyCallbacks. See LobbyCallbackTargets.
  3720. /// </summary>
  3721. /// <remarks>
  3722. /// While the interfaces of callbacks wrap up the methods that will be called,
  3723. /// the container classes implement a simple way to call a method on all registered objects.
  3724. /// </remarks>
  3725. internal class LobbyCallbacksContainer : List<ILobbyCallbacks>, ILobbyCallbacks
  3726. {
  3727. private readonly LoadBalancingClient client;
  3728. public LobbyCallbacksContainer(LoadBalancingClient client)
  3729. {
  3730. this.client = client;
  3731. }
  3732. public void OnJoinedLobby()
  3733. {
  3734. this.client.UpdateCallbackTargets();
  3735. foreach (ILobbyCallbacks target in this)
  3736. {
  3737. target.OnJoinedLobby();
  3738. }
  3739. }
  3740. public void OnLeftLobby()
  3741. {
  3742. this.client.UpdateCallbackTargets();
  3743. foreach (ILobbyCallbacks target in this)
  3744. {
  3745. target.OnLeftLobby();
  3746. }
  3747. }
  3748. public void OnRoomListUpdate(List<RoomInfo> roomList)
  3749. {
  3750. this.client.UpdateCallbackTargets();
  3751. foreach (ILobbyCallbacks target in this)
  3752. {
  3753. target.OnRoomListUpdate(roomList);
  3754. }
  3755. }
  3756. public void OnLobbyStatisticsUpdate(List<TypedLobbyInfo> lobbyStatistics)
  3757. {
  3758. this.client.UpdateCallbackTargets();
  3759. foreach (ILobbyCallbacks target in this)
  3760. {
  3761. target.OnLobbyStatisticsUpdate(lobbyStatistics);
  3762. }
  3763. }
  3764. }
  3765. /// <summary>
  3766. /// Container type for callbacks defined by IWebRpcCallback. See WebRpcCallbackTargets.
  3767. /// </summary>
  3768. /// <remarks>
  3769. /// While the interfaces of callbacks wrap up the methods that will be called,
  3770. /// the container classes implement a simple way to call a method on all registered objects.
  3771. /// </remarks>
  3772. internal class WebRpcCallbacksContainer : List<IWebRpcCallback>, IWebRpcCallback
  3773. {
  3774. private LoadBalancingClient client;
  3775. public WebRpcCallbacksContainer(LoadBalancingClient client)
  3776. {
  3777. this.client = client;
  3778. }
  3779. public void OnWebRpcResponse(OperationResponse response)
  3780. {
  3781. this.client.UpdateCallbackTargets();
  3782. foreach (IWebRpcCallback target in this)
  3783. {
  3784. target.OnWebRpcResponse(response);
  3785. }
  3786. }
  3787. }
  3788. /// <summary>
  3789. /// Container type for callbacks defined by <see cref="IErrorInfoCallback"/>. See <see cref="LoadBalancingClient.ErrorInfoCallbackTargets"/>.
  3790. /// </summary>
  3791. /// <remarks>
  3792. /// While the interfaces of callbacks wrap up the methods that will be called,
  3793. /// the container classes implement a simple way to call a method on all registered objects.
  3794. /// </remarks>
  3795. internal class ErrorInfoCallbacksContainer : List<IErrorInfoCallback>, IErrorInfoCallback
  3796. {
  3797. private LoadBalancingClient client;
  3798. public ErrorInfoCallbacksContainer(LoadBalancingClient client)
  3799. {
  3800. this.client = client;
  3801. }
  3802. public void OnErrorInfo(ErrorInfo errorInfo)
  3803. {
  3804. this.client.UpdateCallbackTargets();
  3805. foreach (IErrorInfoCallback target in this)
  3806. {
  3807. target.OnErrorInfo(errorInfo);
  3808. }
  3809. }
  3810. }
  3811. /// <summary>
  3812. /// Class wrapping the received <see cref="EventCode.ErrorInfo"/> event.
  3813. /// </summary>
  3814. /// <remarks>
  3815. /// This is passed inside <see cref="IErrorInfoCallback.OnErrorInfo"/> callback.
  3816. /// If you implement <see cref="IOnEventCallback.OnEvent"/> or <see cref="LoadBalancingClient.EventReceived"/> you will also get <see cref="EventCode.ErrorInfo"/> but not parsed.
  3817. ///
  3818. /// In most cases this could be either:
  3819. /// 1. an error from webhooks plugin (if HasErrorInfo is enabled), read more here:
  3820. /// https://doc.photonengine.com/en-us/realtime/current/gameplay/web-extensions/webhooks#options
  3821. /// 2. an error sent from a custom server plugin via PluginHost.BroadcastErrorInfoEvent, see example here:
  3822. /// https://doc.photonengine.com/en-us/server/current/plugins/manual#handling_http_response
  3823. /// 3. an error sent from the server, for example, when the limit of cached events has been exceeded in the room
  3824. /// (all clients will be disconnected and the room will be closed in this case)
  3825. /// read more here: https://doc.photonengine.com/en-us/realtime/current/gameplay/cached-events#special_considerations
  3826. /// </remarks>
  3827. public class ErrorInfo
  3828. {
  3829. /// <summary>
  3830. /// String containing information about the error.
  3831. /// </summary>
  3832. public readonly string Info;
  3833. public ErrorInfo(EventData eventData)
  3834. {
  3835. this.Info = eventData[ParameterCode.Info] as string;
  3836. }
  3837. public override string ToString()
  3838. {
  3839. return string.Format("ErrorInfo: {0}", this.Info);
  3840. }
  3841. }
  3842. }