LoadBalancingClient.cs 216 KB

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