This file is indexed.

/usr/src/castle-game-engine-6.4/services/castlegameservice.pas is in castle-game-engine-src 6.4+dfsg1-2.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
{
  Copyright 2015-2017 Michalis Kamburelis.

  This file is part of "Castle Game Engine".

  "Castle Game Engine" is free software; see the file COPYING.txt,
  included in this distribution, for details about the copyright.

  "Castle Game Engine" is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

  ----------------------------------------------------------------------------
}

{ Integration with game service (Google Play Games or Apple Game Center) (TGameService). }
unit CastleGameService;

{$I castleconf.inc}

interface

uses Classes,
  CastleStringUtils, CastleTimeUtils;

type
  { Event for @link(TGameService.OnPlayerBestScoreReceived). }
  TPlayerBestScoreEvent = procedure (Sender: TObject; const LeaderboardId: string; const Score: Int64) of object;

  { User choice at "save game" dialog displayed by @link(TGameService.ShowSaveGames).
    Used as a parameter for @link(TGameService.OnSaveGameChosen) event. }
  TSaveGameChoice = (sgCancel, sgNew, sgExisting);

  { Event for @link(TGameService.OnSaveGameChosen). }
  TSaveGameChosenEvent = procedure (Sender: TObject; const Choice: TSaveGameChoice; const SaveGameName: string) of object;

  { Event for @link(TGameService.OnSaveGameLoaded).
    @param Success Whether we loaded the savegame successfully.
    @param Content The savegame content, if Success. If not Success, this is the error message. }
  TSaveGameLoadedEvent = procedure (Sender: TObject; const Success: boolean; const Content: string) of object;

  { Status of TGameService sign-in. }
  TGameServiceStatus = (
    // Not signed-in to the service.
    gsSignedOut,
    // Not signed-in to the service, but during signing-in.
    gsSigningIn,
    // Signed-in to the service. All game service methods work now.
    gsSignedIn,
    // During signing-out from the service. You should not call other methods on the service now.
    gsSigningOut
  );

  { Integration with a game service,
    that can be used to show achievements, leaderboards, and store save games "in the cloud".
    This integrates with

    @unorderedList(
      @itemSpacing Compact
      @item Google Play Games on Android
      @item Apple Game Center on iOS.
    )

    Usage:

    @orderedList(
      @item(Include the necessary integration code in your Android / iOS project.

        For Android, declare your project type as "integrated" and add
        the "google_play_games" service inside CastleEngineManifest.xml.
        See https://github.com/castle-engine/castle-engine/wiki/Android-Project-Services-Integrated-with-Castle-Game-Engine .

        For iOS, add the "apple_game_center" service inside CastleEngineManifest.xml.
        See https://github.com/castle-engine/castle-engine/wiki/iOS-Project-Services-Integrated-with-Castle-Game-Engine .

        Build your project with the Castle Game Engine build tool:
        https://github.com/castle-engine/castle-engine/wiki/Build-Tool .)

      @item(Create an instance of this class. Only a single instance of this class is allowed.)

      @item(Call @link(TGameService.Initialize).
        You usually do it from @link(TCastleApplication.OnInitialize).
        The @link(TGameService.Initialize) must be called before calling any other method
        of this class.)

      @item(Use this to manage achievements, leaderboards and so on.)

      @item(The user must be "signed in" to the game service.

        The methods that display some user-interface will automatically attempt
        to sign-in the user if needed. These include @link(ShowAchievements),
        @link(ShowLeaderboard), @link(ShowSaveGames).
        So you don't have to do anything before you call them.

        All other methods will @italic(not sign-in the user automatically).
        For example, @link(Achievement) or @link(SubmitScore) or
        @link(SaveGameLoad) or @link(SaveGameSave).
        You should always make sure that the user is signed-in before calling them.
        To do this, pass AutoStartSignInFlow parameter as @true to @link(Initialize)
        or call the @link(RequestSignedIn RequestSignedIn(true)).
        And then wait for the @link(Status) property to change to gsSignedIn
        (you can register @link(OnStatusChanged) to be notified about changes).
      )
    )
  }
  TGameService = class(TComponent)
  private
    FOnPlayerBestScoreReceived: TPlayerBestScoreEvent;
    FInitialized,
      FInitializedAutoStartSignInFlow,
      FInitializedSaveGames: boolean;
    FOnSaveGameChosen: TSaveGameChosenEvent;
    FOnSaveGameLoaded: TSaveGameLoadedEvent;
    FOnStatusChanged: TNotifyEvent;
    FStatus: TGameServiceStatus;
    function MessageReceived(const Received: TCastleStringList): boolean;
    procedure ReinitializeJavaActivity(Sender: TObject);
  protected
    procedure DoSignedInChanged; virtual; deprecated 'use DoStatusChanged';
    procedure DoStatusChanged; virtual;
    procedure DoPlayerBestScoreReceived(const LeaderboardId: string; const Score: Int64); virtual;
    procedure DoSaveGameChosen(const Choice: TSaveGameChoice; const SaveGameName: string); virtual;
    procedure DoSaveGameLoaded(const Success: boolean; const Content: string); virtual;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    { Connect to the service, optionally try to sign-in player.

      If the player already connected this application with
      Google Play Games (Android) or Apple Game Center (iOS),
      (s)he will be signed-in automatically.
      If not, player will be asked to sign-in (which means asking to accept permissions
      on Android) only if AutoStartSignInFlow.
      So if you want to avoid the initial Google Play Games dialog on Android,
      just always pass AutoStartSignInFlow=false.

      Calling this when already initialized is harmless (will not do anything).

      In theory, you can call this at any point in your application.
      But you probably want to call it early, e.g. from Application.OnInitialize,
      otherwise user will not be signed-in automatically.
      Most calls (like sending the leaderboars or achievements) will be ignored until
      you call this.

      @param(SaveGames Indicates whether you want to use save games feature.
        You can then use @link(ShowSaveGames), @link(SaveGameSave), @link(SaveGameLoad)
        methods. See also the description of this feature in Google Play Games:
        https://developers.google.com/games/services/common/concepts/savedgames.) }
    procedure Initialize(const AutoStartSignInFlow: boolean = true;
      const SaveGames: boolean = false);

    { Was the @link(Initialize) called. }
    property Initialized: boolean read FInitialized;

    { Current status of signing-in. }
    property Status: TGameServiceStatus read FStatus;

    { Is user currently signed-in. Just a shortcut for @code(Status = gsSignedIn) check. }
    function SignedIn: boolean;

    { Report the given achievement as achieved.

      For Google Play Games (Android):
      Use Google Developer Console to define achievements for your game,
      and use their ids with this method. The achievement ids are autogenerated by Google,
      they look like @code(CgkIvYuzpZsIEAIQAQ).

      For Apple Game Center (iOS):
      Use iTunes Connect to define achievements for your game,
      and use their ids with this method. The achievement ids are chosen by you,
      so you can choose something readable like 'boss_defeated'.
    }
    procedure Achievement(const AchievementId: string);

    { Report a score in given leaderboard.
      Use Google Developer Console (Android) or iTunes Connect (iOS)
      to create leaderboards for your game, use their ids here.

      TODO: Not implemented for Apple Game Center (iOS) yet. }
    procedure SubmitScore(const LeaderboardId: string; const Score: Int64);

    { Get the best score, if available, for given leaderboard.

      This may (after some time, asynchronously) call
      OnPlayerBestScoreReceived with this score.

      Note that no error is signalled if loading the score fails for any reason.
      This includes failing to load because user is not connected
      to the game service (this method doesn't connect automatically;
      wait for OnStatusChanged before calling this, if you need).

      TODO: Not implemented for Apple Game Center (iOS) yet. }
    procedure RequestPlayerBestScore(const LeaderboardId: string);

    { Request sign-in or sign-out.
      The operation will be done asynchronously (it will in most cases require
      some network communication).

      Watch for changes to the @link(Status) property.
      You can register an event on @link(OnStatusChanged) to be notified about this. }
    procedure RequestSignedIn(const Value: boolean);

    { Show the user achievements, using the default UI.
      Automatically connects (signs-in) player to game services,
      if not connected yet. }
    procedure ShowAchievements;

    { Show the given leaderboard, using the default UI.
      The leaderboard should be created in the Games configuration
      of the Google Developer Console (Android) or iTunes Connect (iOS).
      Automatically connects (signs-in) player to game services,
      if not connected yet. }
    procedure ShowLeaderboard(const LeaderboardId: string);

    { Show the existing @italic(saved games) stored in the cloud
      for this user. This can be used to offer user a choice in which slot
      to save the game, or from which slot to load the game.

      Note that it's not necessary to use this method to manage savegames.
      E.g. if you want, you can just choose a constant savegame name for your game,
      and use SaveGameSave and SaveGameLoad with this name. This would imply
      that each game service user (Google Play Games user or Apple Game Center user)
      has only one savegame in the cloud for your game.

      The user may choose an existing savegame, or indicate creation
      of a new savegame (if parameter AllowAddButton is @true). In response,
      the callback OnSaveGameChosen will be called.
      It will either

      @orderedList(
        @item(indicate the name of an existing savegame user has chosen,)
        @item(or that user wants to create a new savegame,)
        @item(or that user cancelled the dialog.)
      )

      Using this requires initializing the game service first,
      by calling the @link(Initialize) with @code(SaveGames) parameter set to @true.
      Just like @link(ShowAchievements) and @link(ShowLeaderboard),
      this method automatically signs-in player to game service,
      if not signed-in yet.

      Note that @italic(the OnSaveGameChosen callback
      is not called if the user cancels the sign-in operation), and therefore
      cancels the savegame choice this way. The same remark applies to all
      reasons why sign-in may fail (network problems etc.).
      If this is a problem for your logic (e.g. if you want to "wait"
      until OnSaveGameChosen is called), then never call ShowSaveGames
      when @link(SignedIn) is @false. Instead, call RequestSignedIn, wait until
      SignedIn changed to @true (which may be "never", in case of network problems
      or user cancelling!), and only then call ShowSaveGames.

      TODO: Not implemented for Apple Game Center (iOS) yet.

      @param(Title Dialog title to display.)
      @param(AllowAddButton Enable user to choose "new save game".)
      @param(AllowDelete Enable user to delete savegames from the dialog.)
      @param(MaxNumberOfSaveGamesToShow Maximum number of savegames to show.
        Use -1 to just show all savegames.)
    }
    procedure ShowSaveGames(const Title: string; const AllowAddButton, AllowDelete: boolean;
      const MaxNumberOfSaveGamesToShow: Integer);

    { Save a savegame identified by the given name.
      See the SaveGameLoad documentation about the conflict resolution
      and valid savegame names.

      The Contents should be a valid UTF-8 string. For implementation
      reasons, you should not save arbitrary binary data this way, for now
      (or it could result in exceptions about being unable to encode/decode UTF-8
      sequences).

      Description and PlayedTime are shown to player in ShowSaveGames.
      PlayedTime may also be used for conflict resolution (if the savegame
      on the server was modified in the meantime, without loading it in this game).

      No callback is called in response, the game is saved in the background.

      This does not connect player to game service, if it's not connected
      already. An error when saving is not reported back to Pascal, for now.
      (The assumption here is that you will keep a local savegame anyway,
      in case user does not connect to game service. So inability to save
      the savegame to the cloud is not alarming, and does not require any special
      reaction. Please submit a request if you'd like to have a callback
      about it.) }
    procedure SaveGameSave(const SaveGameName, Contents, Description: string;
      const PlayedTime: TFloatTime);

    { Load a savegame identified by the given name.
      If the savegame does not exist, it will be automatically created.

      If the server requires conflict resolution,
      the savegame with longest playtime is used (internally: we use
      RESOLUTION_POLICY_LONGEST_PLAYTIME flag with Google Play Games).
      For this to work, you must provide a proper PlayedTime parameter
      when saving all your savegames through @link(SaveGameSave).

      Valid savegame names are defined by Google Play Games:
      Must be between 1 and 100 non-URL-reserved characters (a-z, A-Z, 0-9,
      or the symbols "-", ".", "_", or "~").

      In response, the callback OnSaveGameLoaded will be @italic(always) called,
      with the loaded savegame contents (as a string),
      or the error message.

      This does not connect player to game service (like Google Play Games),
      if it's not connected already.

      An error will be reported to OnSaveGameLoaded callback
      if trying to load fails for any reason.
      This includes the case when loading fails because user is not connected
      to game service yet (this method @italic(does not) connect user
      automatically; wait for OnStatusChanged before calling this method,
      if you need it). }
    procedure SaveGameLoad(const SaveGameName: string);
  published
    { Event called when @link(Status) changed, for example because
      @link(RequestSignedIn) was called, or because user signs-in automatically
      (which may happen if you used AutoStartSignInFlow with @link(Initialize),
      or if user was signed-in in this application previously). }
    property OnStatusChanged: TNotifyEvent read FOnStatusChanged write FOnStatusChanged;
    property OnSignedInChanged: TNotifyEvent read FOnStatusChanged write FOnStatusChanged stored false;
      deprecated 'use OnStatusChanged';
    { Event received in response to @link(RequestPlayerBestScore). }
    property OnPlayerBestScoreReceived: TPlayerBestScoreEvent read FOnPlayerBestScoreReceived write FOnPlayerBestScoreReceived;
    { Event received in response to @link(ShowSaveGames). }
    property OnSaveGameChosen: TSaveGameChosenEvent read FOnSaveGameChosen write FOnSaveGameChosen;
    { Event received in response to @link(SaveGameLoad).
      See TSaveGameLoadedEvent for documentation of parameters. }
    property OnSaveGameLoaded: TSaveGameLoadedEvent read FOnSaveGameLoaded write FOnSaveGameLoaded;
  end;

implementation

{$warnings off}
{ use deprecated units below, only to have them compiled together with Lazarus
  castle_base.lpk package }
uses SysUtils,
  CastleUtils, CastleMessaging, CastleApplicationProperties, CastleLog,
  // this is deprecated
  CastleGooglePlayGames, CastleShaders, CastleGenericLists, CastleWarnings;
{$warnings on}

constructor TGameService.Create(AOwner: TComponent);
begin
  inherited;
  Messaging.OnReceive.Add(@MessageReceived);
  ApplicationProperties.OnInitializeJavaActivity.Add(@ReinitializeJavaActivity);
end;

destructor TGameService.Destroy;
begin
  if Messaging <> nil then
    Messaging.OnReceive.Remove(@MessageReceived);
  if ApplicationProperties(false) <> nil then
    ApplicationProperties(false).OnInitializeJavaActivity.Remove(@ReinitializeJavaActivity);
  inherited;
end;

procedure TGameService.ReinitializeJavaActivity(Sender: TObject);
begin
  { in case Java activity got killed and is created again, reinitialize services }
  if FInitialized then
  begin
    FStatus := gsSignedOut;
    Initialize(FInitializedAutoStartSignInFlow, FInitializedSaveGames);
  end;
end;

procedure TGameService.DoStatusChanged;
begin
  if Assigned(OnStatusChanged) then
    OnStatusChanged(Self);
  {$warnings off} // calling deprecated to keep it working
  DoSignedInChanged;
  {$warnings on}
end;

procedure TGameService.DoSignedInChanged;
begin
end;

function TGameService.SignedIn: boolean;
begin
  Result := FStatus = gsSignedIn;
end;

procedure TGameService.DoPlayerBestScoreReceived(const LeaderboardId: string; const Score: Int64);
begin
  if Assigned(OnPlayerBestScoreReceived) then
    OnPlayerBestScoreReceived(Self, LeaderboardId, Score);
end;

procedure TGameService.DoSaveGameChosen(const Choice: TSaveGameChoice; const SaveGameName: string);
begin
  if Assigned(OnSaveGameChosen) then
    OnSaveGameChosen(Self, Choice, SaveGameName);
end;

procedure TGameService.DoSaveGameLoaded(const Success: boolean; const Content: string);
begin
  if Assigned(OnSaveGameLoaded) then
    OnSaveGameLoaded(Self, Success, Content);
end;

function TGameService.MessageReceived(const Received: TCastleStringList): boolean;
var
  StatusInt: Int64;
begin
  Result := false;

  if (Received.Count = 3) and
     (Received[0] = 'best-score') then
  begin
    DoPlayerBestScoreReceived(Received[1], StrToInt64(Received[2]));
    Result := true;
  end else

  if (Received.Count = 2) and
     (Received[0] = 'game-service-status') then
  begin
    StatusInt := StrToIntDef(Received[1], -1);
    if (StatusInt >= Ord(Low(TGameServiceStatus))) and
       (StatusInt <= Ord(High(TGameServiceStatus))) then
    begin
      FStatus := TGameServiceStatus(StatusInt);
      DoStatusChanged;
    end else
      WritelnWarning('Invalid game-service-status parameter "%s"', [Received[1]]);
    Result := true;
  end else

  if (Received.Count = 2) and
     (Received[0] = 'chosen-save-game') then
  begin
    DoSaveGameChosen(sgExisting, Received[1]);
    Result := true;
  end;

  if (Received.Count = 1) and
     (Received[0] = 'chosen-save-game-new') then
  begin
    DoSaveGameChosen(sgNew, '');
    Result := true;
  end;

  if (Received.Count = 1) and
     (Received[0] = 'chosen-save-game-cancel') then
  begin
    DoSaveGameChosen(sgCancel, '');
    Result := true;
  end;

  if (Received.Count = 3) and
     (Received[0] = 'save-game-loaded') then
  begin
    DoSaveGameLoaded(StrToBool(Received[1]), Received[2]);
    Result := true;
  end;
end;

procedure TGameService.Initialize(const AutoStartSignInFlow: boolean;
  const SaveGames: boolean);
begin
  { at first Initialize call, remember AutoStartSignInFlow }
  if not FInitialized then
  begin
    FInitializedAutoStartSignInFlow := AutoStartSignInFlow;
    FInitializedSaveGames := SaveGames;
  end;
  FInitialized := true;
  Messaging.Send(['game-service-initialize',
    TMessaging.BoolToStr(AutoStartSignInFlow),
    TMessaging.BoolToStr(SaveGames)
  ]);
end;

procedure TGameService.Achievement(const AchievementId: string);
begin
  Messaging.Send(['achievement', AchievementId]);
end;

procedure TGameService.SubmitScore(const LeaderboardId: string; const Score: Int64);
begin
  Messaging.Send(['submit-score', LeaderboardId, IntToStr(Score)]);
end;

procedure TGameService.RequestPlayerBestScore(const LeaderboardId: string);
begin
  Messaging.Send(['request-player-best-score', LeaderboardId]);
end;

procedure TGameService.RequestSignedIn(const Value: boolean);
begin
  Messaging.Send(['game-service-sign-in', TMessaging.BoolToStr(Value)]);
end;

procedure TGameService.ShowAchievements;
begin
  Messaging.Send(['show', 'achievements']);
end;

procedure TGameService.ShowLeaderboard(const LeaderboardId: string);
begin
  Messaging.Send(['show', 'leaderboard', LeaderboardId]);
end;

procedure TGameService.ShowSaveGames(const Title: string; const AllowAddButton, AllowDelete: boolean;
  const MaxNumberOfSaveGamesToShow: Integer);
begin
  Messaging.Send(['show', 'save-games', Title,
    TMessaging.BoolToStr(AllowAddButton),
    TMessaging.BoolToStr(AllowDelete),
    IntToStr(MaxNumberOfSaveGamesToShow)
  ]);
end;

procedure TGameService.SaveGameLoad(const SaveGameName: string);
begin
  Messaging.Send(['save-game-load', SaveGameName]);
end;

procedure TGameService.SaveGameSave(const SaveGameName, Contents, Description: string;
  const PlayedTime: TFloatTime);
begin
  Messaging.Send(['save-game-save', SaveGameName, Contents, Description, TMessaging.TimeToStr(PlayedTime)]);
end;

end.