feat: update nakama, and patch, checkpoint for restore 2.3.1
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 7.5 KiB |
+130
@@ -0,0 +1,130 @@
|
|||||||
|
# Change Log
|
||||||
|
All notable changes to this project are documented below.
|
||||||
|
|
||||||
|
The format is based on [keep a changelog](http://keepachangelog.com/) and this project uses [semantic versioning](http://semver.org/).
|
||||||
|
|
||||||
|
## [3.3.1] - 2023-04-17
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix arguments for HTTPRequest.request() for beta17
|
||||||
|
- Fix typehints for enums in Godot 4.0-rc1
|
||||||
|
- Fix type check and typehint for Godot 4.0-rc3
|
||||||
|
- Fix null byte array error in GodotHttpAdapter for C#
|
||||||
|
|
||||||
|
## [3.3.0] - 2023-01-30
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add support for subscription validation APIs that were added in Nakama v3.13.0
|
||||||
|
- Add support for sending events
|
||||||
|
- Allow disabling threads for making HTTP requests
|
||||||
|
- Add support for delete_account_sync() and other API changes that were added in Nakama v3.15.0
|
||||||
|
|
||||||
|
## [3.2.0] - 2022-08-30
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix NakamaSocket.add_matchmaker_party_async() and the tests for it
|
||||||
|
- Fix MatchData.op_code type in schema to TYPE_INT
|
||||||
|
- Fix circular reference in Nakama singleton to itself
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add support for receiving binary data in "NakamaRTAPI.MatchState"
|
||||||
|
- Add support for sending and receiving binary data in "NakamaRTAPI.PartyData"
|
||||||
|
- Add NakamaMultiplayerBridge to integrate with Godot's High-Level Multiplayer API
|
||||||
|
|
||||||
|
## [3.1.0] - 2022-04-28
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Expose the "seen_before" property on "NakamaAPI.ApiValidatedPurchase"
|
||||||
|
- Add support for creating match by name
|
||||||
|
- Add support for "count_multple" on "NakamaSocket.add_matchmaker_async()" and "NakamaSocket.add_matchmaker_party_async()"
|
||||||
|
- Add C# support classes to better integrate the .NET client with the Mono version of Godot, allowing HTML5 exports to work
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix receiving "NakamaRTAPI.PartyClose" message
|
||||||
|
- Fix sending and receiving of PartyData
|
||||||
|
|
||||||
|
## [3.0.0] - 2022-03-28
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add realtime party support.
|
||||||
|
- Add purchase validation functions.
|
||||||
|
- Add Apple authentication functions.
|
||||||
|
- Add "demote_group_users_async" function.
|
||||||
|
- A session can be refreshed on demand with "session_refresh_async" method.
|
||||||
|
- Session and/or refresh tokens can now be invalidated with a client logout.
|
||||||
|
- The client now supports session auto-refresh using refresh tokens. This is enabled by default.
|
||||||
|
- The client now supports auto-retrying failed request due to network error. This is enabled by defulut.
|
||||||
|
- The client now support cancelling requests in-flight via "client.cancel_request".
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix Dictionary serialization (e.g. "NakamaSocket.add_matchmaker_async" "p_string_props" and "p_numeric_props").
|
||||||
|
- Pass join metadata onwards into match join message.
|
||||||
|
- Don't stop processing messages when the game is paused.
|
||||||
|
- Fix "rpc_async", "rpc_async_with_key". Now uses GET request only if no payload is passed.
|
||||||
|
- Fix client errors parsing in Nakama 3.x
|
||||||
|
- Make it possible to omit the label and query on NakamaClient.list_matches_async().
|
||||||
|
|
||||||
|
### Backwards incompatible changes
|
||||||
|
|
||||||
|
- The "received_error" signal on "NakamaSocket" is now emited with an "NakamaRTAPI.Error" object received from the server.
|
||||||
|
Previously, it was emitted with an integer error code when the socket failed to connect.
|
||||||
|
If you have old code using the "received_error" signal, you can switch to the new "connection_error" signal, which was added to replace it.
|
||||||
|
|
||||||
|
## [2.1.0] - 2020-08-01
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add an optional log level parameter to "Nakama.create_client".
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Update variable definitions to new gdscript variable controls.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix "add_friends_async" should have its "id" field input as optional.
|
||||||
|
- Fix "add_matchmaker_async" and "MatchmakerAdd" parameter assignment.
|
||||||
|
- Fix missing "presence" property in NakamaRTAPI.MatchData.
|
||||||
|
- Fix NakamaSocket not emitting "received_error" correctly.
|
||||||
|
- Fix "DEFAULT_LOG_LEVEL" in Nakama.gd not doing anything.
|
||||||
|
|
||||||
|
## [2.0.0] - 2020-04-02
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Decode base64 data in "MatchData". (breaks compat)
|
||||||
|
- Add "FacebookInstantGame" endpoints (link/unlink/authenticate).
|
||||||
|
- GDScript-style comments (removing all XML tags).
|
||||||
|
- Add "list_storage_objects_async" "p_user_id" parameter to allow listing user(s) objects.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fix encoding of "op_code" in "MatchDataSend" and marshalling of "NakamaSocket.send_match_state_[raw_]async".
|
||||||
|
- Fix parsing of "MatchmakerMatched" messages when no token is specified.
|
||||||
|
- Disable "HTTPRequest.use_threads" in HTML5 exports.
|
||||||
|
- "NakamaSession.is_expired" returned reversed result.
|
||||||
|
- Fix "NakamaClient.update_account_async" to allow updating account without username change.
|
||||||
|
- Fix "NakamaClient.update_group_async" to allow updating group without name change.
|
||||||
|
- Fix "HTTPAdapter._send_async" error catching for some edge cases.
|
||||||
|
- Fix "NakamaClient.send_rpc_async" with empty payload (will send empty string now).
|
||||||
|
- Fix "NakamaRTAPI.Status" parsing.
|
||||||
|
- Fix "NakamaClient" "list_leaderboard_records_around_owner_async" and "list_leaderboard_records_async" parameter order. (breaks compat)
|
||||||
|
- Rename "NakamaClient.JoinTournamentAsync" to "join_tournament_async" for consistent naming.
|
||||||
|
- Update all "p_limit" parameters default in "NakamaClient" to "10".
|
||||||
|
- Fix "NakamaRTAPI.Stream" parsing.
|
||||||
|
|
||||||
|
## [1.0.0] - 2020-01-28
|
||||||
|
### Added
|
||||||
|
- Initial public release.
|
||||||
|
- Client API implementation.
|
||||||
|
- Realtime Socket implementation.
|
||||||
|
- Helper singleton.
|
||||||
|
- Setup instructions.
|
||||||
@@ -0,0 +1,203 @@
|
|||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
codegen
|
||||||
|
=======
|
||||||
|
|
||||||
|
> A util tool to generate a client from the Swagger spec of Nakama's server API.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```shell
|
||||||
|
go run main.go "$GOPATH/src/github.com/heroiclabs/nakama/apigrpc/apigrpc.swagger.json" > ../addons/com.heroiclabs.nakama/api/NakamaAPI.gd
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rationale
|
||||||
|
|
||||||
|
We want to maintain a simple lean low level client within our GDScript client which has minimal dependencies so we built our own. This gives us complete control over the dependencies required and structure of the code generated.
|
||||||
|
|
||||||
|
The generated code is designed to be supported Godot Engine `3.1+`.
|
||||||
|
|
||||||
|
### Limitations
|
||||||
|
|
||||||
|
The code generator has __only__ been checked against the Swagger specification generated for Nakama server. YMMV.
|
||||||
|
|
||||||
+639
@@ -0,0 +1,639 @@
|
|||||||
|
// Copyright 2018 The Nakama Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
)
|
||||||
|
|
||||||
|
var utilities = map[string]string {
|
||||||
|
"ApiAccount":
|
||||||
|
`
|
||||||
|
|
||||||
|
var _wallet_dict = null
|
||||||
|
var wallet_dict : Dictionary:
|
||||||
|
get:
|
||||||
|
if _wallet_dict == null:
|
||||||
|
if _wallet == null:
|
||||||
|
return {}
|
||||||
|
var json = JSON.new()
|
||||||
|
if json.parse(_wallet) != OK:
|
||||||
|
return {}
|
||||||
|
_wallet_dict = json.get_data()
|
||||||
|
return _wallet_dict as Dictionary
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
const codeTemplate string = `### Code generated by codegen/main.go. DO NOT EDIT. ###
|
||||||
|
|
||||||
|
extends RefCounted
|
||||||
|
class_name NakamaAPI
|
||||||
|
{{- range $defname, $definition := .Definitions }}
|
||||||
|
{{- $classname := $defname | title }}
|
||||||
|
{{- if isRefToEnum $classname }}
|
||||||
|
|
||||||
|
# {{ enumSummary $definition | stripNewlines }}
|
||||||
|
{{- range $idx, $val := ($definition | enumDescriptions) }}
|
||||||
|
# {{ $val }}
|
||||||
|
{{- end -}}
|
||||||
|
# {{ $definition | enumDescriptions }}
|
||||||
|
enum {{ $classname | title }} { {{- range $idx, $enum := $definition.Enum }}{{ $enum }} = {{ $idx }},{{- end -}} }
|
||||||
|
{{- else }}
|
||||||
|
|
||||||
|
# {{ $definition.Description | stripNewlines }}
|
||||||
|
class {{ $classname }} extends NakamaAsyncResult:
|
||||||
|
|
||||||
|
const _SCHEMA = {
|
||||||
|
{{- range $propname, $property := $definition.Properties }}
|
||||||
|
{{- $fieldname := $propname | pascalToSnake }}
|
||||||
|
{{- $_field := printf "_%s" $fieldname }}
|
||||||
|
{{- $gdType := godotType $property.Type $property.Ref $property.Items.Type $property.Items.Ref (isRefToEnum (cleanRef $property.Ref)) }}
|
||||||
|
"{{ $fieldname }}": {"name": "{{ $_field }}", "type": {{ $gdType | godotSchemaType }}, "required": false
|
||||||
|
{{- if eq $property.Type "array" -}}
|
||||||
|
, "content": {{ (godotType $property.Items.Type $property.Items.Ref "" "" false) | godotSchemaType }}
|
||||||
|
{{- else if eq $property.Type "object" -}}
|
||||||
|
, "content": {{ (godotType $property.AdditionalProperties.Type "" "" "" false) | godotSchemaType }}
|
||||||
|
{{- end -}}
|
||||||
|
},
|
||||||
|
{{- end }}
|
||||||
|
}
|
||||||
|
|
||||||
|
{{- range $propname, $property := $definition.Properties }}
|
||||||
|
{{- $fieldname := $propname | pascalToSnake }}
|
||||||
|
{{- $_field := printf "_%s" $fieldname }}
|
||||||
|
{{- $gdType := godotType $property.Type $property.Ref $property.Items.Type $property.Items.Ref (isRefToEnum (cleanRef $property.Ref)) }}
|
||||||
|
{{- $gdDef := $gdType | godotDef }}
|
||||||
|
|
||||||
|
# {{ $property.Description }}
|
||||||
|
var {{ $_field }}
|
||||||
|
var {{ $fieldname }} : {{ $gdType }}:
|
||||||
|
get:
|
||||||
|
{{- if $property.Ref }}
|
||||||
|
{{- if isRefToEnum (cleanRef $property.Ref) }}{{/* Enums */}}
|
||||||
|
return {{ cleanRef $property.Ref }}.values()[0] if not {{ cleanRef $property.Ref }}.values().has({{ $_field }}) else {{ $_field }}
|
||||||
|
{{- else }}{{/* Object reference */}}
|
||||||
|
return _{{ $fieldname }} as {{ $gdType }}
|
||||||
|
{{- end }}
|
||||||
|
{{- else if eq $property.Type "object"}}{{/* Dictionaries */}}
|
||||||
|
return Dictionary() if not {{ $_field }} is Dictionary else {{ $_field }}.duplicate()
|
||||||
|
{{- else }}{{/* Simple type */}}
|
||||||
|
return {{ $gdDef }} if not {{ $_field }} is {{ $gdType }} else {{ $gdType }}({{ $_field }})
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- godotClassUtils $classname }}
|
||||||
|
|
||||||
|
func _init(p_exception = null):
|
||||||
|
super(p_exception)
|
||||||
|
|
||||||
|
static func create(p_ns : GDScript, p_dict : Dictionary) -> {{ $classname }}:
|
||||||
|
return _safe_ret(NakamaSerializer.deserialize(p_ns, "{{ $classname }}", p_dict), {{ $classname }}) as {{ $classname }}
|
||||||
|
|
||||||
|
func serialize() -> Dictionary:
|
||||||
|
return NakamaSerializer.serialize(self)
|
||||||
|
|
||||||
|
func _to_string() -> String:
|
||||||
|
if is_exception():
|
||||||
|
return get_exception()._to_string()
|
||||||
|
var output : String = ""
|
||||||
|
{{- range $propname, $property := $definition.Properties }}
|
||||||
|
{{- $fieldname := $propname | pascalToSnake }}
|
||||||
|
{{- $_field := printf "_%s" $fieldname }}
|
||||||
|
{{- if eq $property.Type "array" }}
|
||||||
|
output += "{{ $fieldname }}: %s, " % [{{ $_field }}]
|
||||||
|
{{- else if eq $property.Type "object" }}
|
||||||
|
var map_string : String = ""
|
||||||
|
if typeof({{ $_field }}) == TYPE_DICTIONARY:
|
||||||
|
for k in {{ $_field }}:
|
||||||
|
map_string += "{%s=%s}, " % [k, {{ $_field }}[k]]
|
||||||
|
output += "{{ $fieldname }}: [%s], " % map_string
|
||||||
|
{{- else }}
|
||||||
|
output += "{{ $fieldname }}: %s, " % {{ $_field }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
return output
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
# The low level client for the Nakama API.
|
||||||
|
class ApiClient extends RefCounted:
|
||||||
|
|
||||||
|
var _base_uri : String
|
||||||
|
|
||||||
|
var _http_adapter
|
||||||
|
var _namespace : GDScript
|
||||||
|
var _server_key : String
|
||||||
|
var auto_refresh := true
|
||||||
|
var auto_refresh_time := 300
|
||||||
|
|
||||||
|
var auto_retry : bool:
|
||||||
|
set(p_value):
|
||||||
|
_http_adapter.auto_retry = p_value
|
||||||
|
get:
|
||||||
|
return _http_adapter.auto_retry
|
||||||
|
|
||||||
|
var auto_retry_count : int:
|
||||||
|
set(p_value):
|
||||||
|
_http_adapter.auto_retry_count = p_value
|
||||||
|
get:
|
||||||
|
return _http_adapter.auto_retry_count
|
||||||
|
|
||||||
|
var auto_retry_backoff_base : int:
|
||||||
|
set(p_value):
|
||||||
|
_http_adapter.auto_retry_backoff_base = p_value
|
||||||
|
get:
|
||||||
|
return _http_adapter.auto_retry_backoff_base
|
||||||
|
|
||||||
|
var last_cancel_token:
|
||||||
|
get:
|
||||||
|
return _http_adapter.get_last_token()
|
||||||
|
|
||||||
|
func _init(p_base_uri : String, p_http_adapter, p_namespace : GDScript, p_server_key : String, p_timeout : int = 10):
|
||||||
|
_base_uri = p_base_uri
|
||||||
|
_http_adapter = p_http_adapter
|
||||||
|
_http_adapter.timeout = p_timeout
|
||||||
|
_namespace = p_namespace
|
||||||
|
_server_key = p_server_key
|
||||||
|
|
||||||
|
func _refresh_session(p_session : NakamaSession):
|
||||||
|
if auto_refresh and p_session.is_valid() and p_session.refresh_token and not p_session.is_refresh_expired() and p_session.would_expire_in(auto_refresh_time):
|
||||||
|
var request = ApiSessionRefreshRequest.new()
|
||||||
|
request._token = p_session.refresh_token
|
||||||
|
return await session_refresh_async(_server_key, "", request)
|
||||||
|
return null
|
||||||
|
|
||||||
|
func cancel_request(p_token):
|
||||||
|
if p_token:
|
||||||
|
_http_adapter.cancel_request(p_token)
|
||||||
|
|
||||||
|
{{- range $url, $path := .Paths }}
|
||||||
|
{{- range $method, $operation := $path}}
|
||||||
|
|
||||||
|
# {{ $operation.Summary | stripNewlines }}
|
||||||
|
{{- if $operation.Responses.Ok.Schema.Ref }}
|
||||||
|
func {{ $operation.OperationId | apiFuncName }}_async(
|
||||||
|
{{- else }}
|
||||||
|
func {{ $operation.OperationId | apiFuncName }}_async(
|
||||||
|
{{- end}}
|
||||||
|
|
||||||
|
{{- if $operation.Security }}
|
||||||
|
{{- with (index $operation.Security 0) }}
|
||||||
|
{{- range $key, $value := . }}
|
||||||
|
{{- if eq $key "BasicAuth" }}
|
||||||
|
p_basic_auth_username : String
|
||||||
|
, p_basic_auth_password : String
|
||||||
|
{{- else if eq $key "HttpKeyAuth" }}
|
||||||
|
p_bearer_token : String
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- else }}
|
||||||
|
p_session : NakamaSession
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
{{- range $parameter := $operation.Parameters }}
|
||||||
|
{{- $argument := $parameter.Name | prependParameter }}
|
||||||
|
{{- if not $parameter.Required }}{{/* Godot does not support typed optional parameters yet. */}}
|
||||||
|
, {{ $argument }} = null # : {{ $parameter.Type }}
|
||||||
|
{{- else if eq $parameter.In "body" }}
|
||||||
|
{{- if eq $parameter.Schema.Type "string" }}
|
||||||
|
, {{ $argument }} : String
|
||||||
|
{{- else }}
|
||||||
|
, {{ $argument }} : {{ $parameter.Schema.Ref | cleanRef }}
|
||||||
|
{{- end }}
|
||||||
|
{{- else }}
|
||||||
|
, {{ $argument }} : {{ godotType $parameter.Type $parameter.Schema.Ref $parameter.Items.Type "" (isRefToEnum (cleanRef $parameter.Schema.Ref)) }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
)
|
||||||
|
{{- if $operation.Responses.Ok.Schema.Ref }} -> {{ $operation.Responses.Ok.Schema.Ref | cleanRef }}
|
||||||
|
{{- else }} -> NakamaAsyncResult
|
||||||
|
{{- end }}:
|
||||||
|
{{- $classname := "NakamaAsyncResult" }}
|
||||||
|
{{- if $operation.Responses.Ok.Schema.Ref }}
|
||||||
|
{{- $classname = $operation.Responses.Ok.Schema.Ref | cleanRef }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if not $operation.Security }}
|
||||||
|
var try_refresh = await _refresh_session(p_session)
|
||||||
|
if try_refresh != null:
|
||||||
|
if try_refresh.is_exception():
|
||||||
|
return {{ $classname }}.new(try_refresh.get_exception())
|
||||||
|
await p_session.refresh(try_refresh)
|
||||||
|
{{- end }}
|
||||||
|
var urlpath : String = "{{- $url }}"
|
||||||
|
{{- range $parameter := $operation.Parameters }}
|
||||||
|
{{- $argument := $parameter.Name | prependParameter }}
|
||||||
|
{{- if eq $parameter.In "path" }}
|
||||||
|
urlpath = urlpath.replace("{{- print "{" $parameter.Name "}"}}", NakamaSerializer.escape_http({{ $argument }}))
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
var query_params = ""
|
||||||
|
{{- range $parameter := $operation.Parameters }}
|
||||||
|
{{- $argument := $parameter.Name | prependParameter }}
|
||||||
|
{{- $snakecase := $parameter.Name | pascalToSnake }}
|
||||||
|
{{- if eq $parameter.In "query"}}
|
||||||
|
{{- if $parameter.Required }}
|
||||||
|
if true: # Hack for static checks
|
||||||
|
{{- else }}
|
||||||
|
if {{ $argument }} != null:
|
||||||
|
{{- end }}
|
||||||
|
{{- if eq $parameter.Type "integer" }}
|
||||||
|
query_params += "{{- $snakecase }}=%d&" % {{ $argument }}
|
||||||
|
{{- else if eq $parameter.Type "string" }}
|
||||||
|
query_params += "{{- $snakecase }}=%s&" % NakamaSerializer.escape_http({{ $argument }})
|
||||||
|
{{- else if eq $parameter.Type "boolean" }}
|
||||||
|
query_params += "{{- $snakecase }}=%s&" % str(bool({{ $argument }})).to_lower()
|
||||||
|
{{- else if eq $parameter.Type "array" }}
|
||||||
|
for elem in {{ $argument }}:
|
||||||
|
query_params += "{{- $snakecase }}=%s&" % elem
|
||||||
|
{{- else }}
|
||||||
|
{{ $parameter }} // ERROR
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
var uri = "%s%s%s" % [_base_uri, urlpath, "?" + query_params if query_params else ""]
|
||||||
|
var method = "{{- $method | uppercase }}"
|
||||||
|
var headers = {}
|
||||||
|
{{- if $operation.Security }}
|
||||||
|
{{- with (index $operation.Security 0) }}
|
||||||
|
{{- range $key, $value := . }}
|
||||||
|
{{- if eq $key "BasicAuth" }}
|
||||||
|
var credentials = Marshalls.utf8_to_base64(p_basic_auth_username + ":" + p_basic_auth_password)
|
||||||
|
var header = "Basic %s" % credentials
|
||||||
|
headers["Authorization"] = header
|
||||||
|
{{- else if eq $key "HttpKeyAuth" }}
|
||||||
|
if (p_bearer_token):
|
||||||
|
var header = "Bearer %s" % p_bearer_token
|
||||||
|
headers["Authorization"] = header
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- else }}
|
||||||
|
var header = "Bearer %s" % p_session.token
|
||||||
|
headers["Authorization"] = header
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
var content : PackedByteArray
|
||||||
|
{{- range $parameter := $operation.Parameters }}
|
||||||
|
{{- $argument := $parameter.Name | prependParameter }}
|
||||||
|
{{- if eq $parameter.In "body" }}
|
||||||
|
{{- if eq $parameter.Schema.Type "string" }}
|
||||||
|
content = JSON.stringify({{ $argument }}).to_utf8_buffer()
|
||||||
|
{{- else }}
|
||||||
|
content = JSON.stringify({{ $argument }}.serialize()).to_utf8_buffer()
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
var result = await _http_adapter.send_async(method, uri, headers, content)
|
||||||
|
if result is NakamaException:
|
||||||
|
return {{ $classname }}.new(result)
|
||||||
|
|
||||||
|
{{- if $operation.Responses.Ok.Schema.Ref }}
|
||||||
|
var out : {{ $classname }} = NakamaSerializer.deserialize(_namespace, "{{ $classname }}", result)
|
||||||
|
return out
|
||||||
|
{{- else }}
|
||||||
|
return NakamaAsyncResult.new()
|
||||||
|
{{- end}}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
`
|
||||||
|
|
||||||
|
func convertRefToClassName(input string) (className string) {
|
||||||
|
cleanRef := strings.TrimPrefix(input, "#/definitions/")
|
||||||
|
className = strings.Title(cleanRef)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func stripNewlines(input string) (output string) {
|
||||||
|
output = strings.Replace(input, "\n", " ", -1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func prependParameter(input string) (output string) {
|
||||||
|
output = "p_" + pascalToSnake(input)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func pascalToSnake(input string) (output string) {
|
||||||
|
output = ""
|
||||||
|
prev_low := false
|
||||||
|
for _, v := range input {
|
||||||
|
is_cap := v >= 'A' && v <= 'Z'
|
||||||
|
is_low := v >= 'a' && v <= 'z'
|
||||||
|
if is_cap && prev_low {
|
||||||
|
output = output + "_"
|
||||||
|
}
|
||||||
|
output += strings.ToLower(string(v))
|
||||||
|
prev_low = is_low
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func apiFuncName(input string) (output string) {
|
||||||
|
output = pascalToSnake(input[7:])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func godotType(p_type string, p_ref string, p_item_type string, p_extra string, p_is_enum bool) (out string) {
|
||||||
|
|
||||||
|
is_array := false
|
||||||
|
is_dict := false
|
||||||
|
switch p_type {
|
||||||
|
case "integer":
|
||||||
|
out = "int"
|
||||||
|
case "string":
|
||||||
|
out = "String"
|
||||||
|
case "boolean":
|
||||||
|
out = "bool"
|
||||||
|
case "array":
|
||||||
|
is_array = true
|
||||||
|
case "object":
|
||||||
|
is_dict = true
|
||||||
|
default:
|
||||||
|
if p_is_enum {
|
||||||
|
out = "int"
|
||||||
|
} else {
|
||||||
|
out = convertRefToClassName(p_ref)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_array {
|
||||||
|
switch p_item_type {
|
||||||
|
case "integer":
|
||||||
|
out = "PackedIntArray"
|
||||||
|
return
|
||||||
|
case "string":
|
||||||
|
out = "PackedStringArray"
|
||||||
|
return
|
||||||
|
case "boolean":
|
||||||
|
out = "PackedIntArray"
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
out = "Array"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if is_dict {
|
||||||
|
out = "Dictionary"
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func godotDef(p_type string) (out string) {
|
||||||
|
switch(p_type) {
|
||||||
|
case "bool": out = "false"
|
||||||
|
case "int": out = "0"
|
||||||
|
case "String": out = "\"\""
|
||||||
|
case "PackedIntArray": out = "PackedIntArray()"
|
||||||
|
case "PackedStringArray": out = "PackedStringArray()"
|
||||||
|
case "Array": out = "Array()"
|
||||||
|
case "Dictionary": out = "Dictionary()"
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func godotLooseType(p_type string) (out string) {
|
||||||
|
switch(p_type) {
|
||||||
|
case "PackedStringArray", "PackedIntArray":
|
||||||
|
out = "Array"
|
||||||
|
default:
|
||||||
|
out = p_type
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func godotSchemaType(p_type string) (out string) {
|
||||||
|
out = "TYPE_"
|
||||||
|
switch(p_type) {
|
||||||
|
case "bool": out += "BOOL"
|
||||||
|
case "int": out += "INT"
|
||||||
|
case "String": out += "STRING"
|
||||||
|
case "PackedIntArray": out += "ARRAY"
|
||||||
|
case "PackedStringArray": out += "ARRAY"
|
||||||
|
case "Array": out += "ARRAY"
|
||||||
|
case "Dictionary": out += "DICTIONARY"
|
||||||
|
default: out = "\"" + p_type + "\""
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func pascalToCamel(input string) (camelCase string) {
|
||||||
|
if input == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
camelCase = strings.ToLower(string(input[0]))
|
||||||
|
camelCase += string(input[1:])
|
||||||
|
return camelCase
|
||||||
|
}
|
||||||
|
|
||||||
|
func camelToPascal(camelCase string) (pascalCase string) {
|
||||||
|
|
||||||
|
if len(camelCase) <= 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
pascalCase = strings.ToUpper(string(camelCase[0])) + camelCase[1:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func enumSummary(def Definition) string {
|
||||||
|
// quirk of swagger generation: if enum doesn't have a title
|
||||||
|
// then the title can be found as the first entry in the split description.
|
||||||
|
if def.Title != "" {
|
||||||
|
return def.Title
|
||||||
|
}
|
||||||
|
|
||||||
|
split := strings.Split(def.Description, "\n")
|
||||||
|
|
||||||
|
if len(split) <= 0 {
|
||||||
|
panic("No newlines in enum description found.")
|
||||||
|
}
|
||||||
|
|
||||||
|
return split[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func enumDescriptions(def Definition) (output []string) {
|
||||||
|
|
||||||
|
split := strings.Split(def.Description, "\n")
|
||||||
|
|
||||||
|
if len(split) <= 0 {
|
||||||
|
panic("No newlines in enum description found.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if def.Title != "" {
|
||||||
|
return split
|
||||||
|
}
|
||||||
|
|
||||||
|
// quirk of swagger generation: if enum doesn't have a title
|
||||||
|
// then the title can be found as the first entry in the split description.
|
||||||
|
// so ignore for individual enum descriptions.
|
||||||
|
return split[2:]
|
||||||
|
}
|
||||||
|
|
||||||
|
type Definition struct {
|
||||||
|
Properties map[string]struct {
|
||||||
|
Type string
|
||||||
|
Ref string `json:"$ref"` // used with object
|
||||||
|
Items struct { // used with type "array"
|
||||||
|
Type string
|
||||||
|
Ref string `json:"$ref"`
|
||||||
|
}
|
||||||
|
AdditionalProperties struct {
|
||||||
|
Type string // used with type "map"
|
||||||
|
}
|
||||||
|
Format string // used with type "boolean"
|
||||||
|
Description string
|
||||||
|
}
|
||||||
|
Enum []string
|
||||||
|
Description string
|
||||||
|
// used only by enums
|
||||||
|
Title string
|
||||||
|
}
|
||||||
|
|
||||||
|
func godotClassUtils(p_name string) string {
|
||||||
|
if val, ok := utilities[p_name]; ok {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Argument flags
|
||||||
|
var output = flag.String("output", "", "The output for generated code.")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
inputs := flag.Args()
|
||||||
|
if len(inputs) < 1 {
|
||||||
|
fmt.Printf("No input file found: %s\n\n", inputs)
|
||||||
|
fmt.Println("openapi-gen [flags] inputs...")
|
||||||
|
flag.PrintDefaults()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
input := inputs[0]
|
||||||
|
content, err := ioutil.ReadFile(input)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Unable to read file: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var schema struct {
|
||||||
|
Paths map[string]map[string]struct {
|
||||||
|
Summary string
|
||||||
|
OperationId string
|
||||||
|
Responses struct {
|
||||||
|
Ok struct {
|
||||||
|
Schema struct {
|
||||||
|
Ref string `json:"$ref"`
|
||||||
|
}
|
||||||
|
} `json:"200"`
|
||||||
|
}
|
||||||
|
Parameters []struct {
|
||||||
|
Name string
|
||||||
|
In string
|
||||||
|
Required bool
|
||||||
|
Type string // used with primitives
|
||||||
|
Items struct { // used with type "array"
|
||||||
|
Type string
|
||||||
|
}
|
||||||
|
Schema struct { // used with http body
|
||||||
|
Type string
|
||||||
|
Ref string `json:"$ref"`
|
||||||
|
}
|
||||||
|
Format string // used with type "boolean"
|
||||||
|
}
|
||||||
|
Security []map[string][]struct {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Definitions map[string]Definition
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(content, &schema); err != nil {
|
||||||
|
fmt.Printf("Unable to decode input %s : %s\n", input, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmap := template.FuncMap{
|
||||||
|
"cleanRef": convertRefToClassName,
|
||||||
|
"stripNewlines": stripNewlines,
|
||||||
|
"title": strings.Title,
|
||||||
|
"uppercase": strings.ToUpper,
|
||||||
|
"prependParameter": prependParameter,
|
||||||
|
"pascalToSnake": pascalToSnake,
|
||||||
|
"apiFuncName": apiFuncName,
|
||||||
|
"godotType": godotType,
|
||||||
|
"godotLooseType": godotLooseType,
|
||||||
|
"godotSchemaType": godotSchemaType,
|
||||||
|
"godotDef": godotDef,
|
||||||
|
"isRefToEnum": func(ref string) bool {
|
||||||
|
if len(ref) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// swagger schema definition keys have inconsistent casing
|
||||||
|
var camelOk bool
|
||||||
|
var pascalOk bool
|
||||||
|
var enums []string
|
||||||
|
|
||||||
|
asCamel := pascalToCamel(ref)
|
||||||
|
if _, camelOk = schema.Definitions[asCamel]; camelOk {
|
||||||
|
enums = schema.Definitions[asCamel].Enum
|
||||||
|
}
|
||||||
|
|
||||||
|
asPascal := camelToPascal(ref)
|
||||||
|
if _, pascalOk = schema.Definitions[asPascal]; pascalOk {
|
||||||
|
enums = schema.Definitions[asPascal].Enum
|
||||||
|
}
|
||||||
|
|
||||||
|
if !pascalOk && !camelOk {
|
||||||
|
fmt.Printf("no definition found: %v", ref)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(enums) > 0
|
||||||
|
},
|
||||||
|
"enumDescriptions": enumDescriptions,
|
||||||
|
"enumSummary": enumSummary,
|
||||||
|
"godotClassUtils": godotClassUtils,
|
||||||
|
}
|
||||||
|
tmpl, err := template.New(input).Funcs(fmap).Parse(codeTemplate)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Template parse error: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(*output) < 1 {
|
||||||
|
tmpl.Execute(os.Stdout, schema)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Create(*output)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Unable to create file: %s\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
writer := bufio.NewWriter(f)
|
||||||
|
tmpl.Execute(writer, schema)
|
||||||
|
writer.Flush()
|
||||||
|
}
|
||||||
@@ -219,6 +219,7 @@ custom_minimum_size = Vector2(0, 20)
|
|||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
theme_override_styles/background = SubResource("StyleBoxFlat_wusgu")
|
theme_override_styles/background = SubResource("StyleBoxFlat_wusgu")
|
||||||
theme_override_styles/fill = SubResource("StyleBoxFlat_2h0ma")
|
theme_override_styles/fill = SubResource("StyleBoxFlat_2h0ma")
|
||||||
|
show_percentage = false
|
||||||
|
|
||||||
[node name="ProgressLabel" type="Label" parent="CenterContainer/MainVBox/ProgressBar" unique_id=1293848486]
|
[node name="ProgressLabel" type="Label" parent="CenterContainer/MainVBox/ProgressBar" unique_id=1293848486]
|
||||||
unique_name_in_owner = true
|
unique_name_in_owner = true
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ func _try_restore_session() -> void:
|
|||||||
|
|
||||||
var file := FileAccess.open_encrypted_with_pass(SESSION_FILE, FileAccess.READ, ENCRYPTION_KEY)
|
var file := FileAccess.open_encrypted_with_pass(SESSION_FILE, FileAccess.READ, ENCRYPTION_KEY)
|
||||||
if not file:
|
if not file:
|
||||||
print("[AuthManager] Could not open session file")
|
print("[AuthManager] Could not open session file. Corrupt or wrong key. Deleting.")
|
||||||
|
DirAccess.remove_absolute(SESSION_FILE)
|
||||||
return
|
return
|
||||||
|
|
||||||
var session_data = file.get_var()
|
var session_data = file.get_var()
|
||||||
|
|||||||
@@ -184,10 +184,8 @@ func host_game(room_meta: Dictionary = {}):
|
|||||||
printerr("Cannot host: Bridge not initialized")
|
printerr("Cannot host: Bridge not initialized")
|
||||||
return
|
return
|
||||||
print("Hosting match via Nakama Bridge...")
|
print("Hosting match via Nakama Bridge...")
|
||||||
var result = await bridge.create_match()
|
await bridge.create_match()
|
||||||
if result and result.is_exception():
|
# Errors are emitted via bridge's match_join_error signal
|
||||||
emit_signal("match_join_error", result.get_exception().message)
|
|
||||||
return
|
|
||||||
# Store room metadata in Nakama storage so other players can see it in listings
|
# Store room metadata in Nakama storage so other players can see it in listings
|
||||||
if session and current_match_id and room_meta.size() > 0:
|
if session and current_match_id and room_meta.size() > 0:
|
||||||
var meta_json = JSON.stringify(room_meta)
|
var meta_json = JSON.stringify(room_meta)
|
||||||
@@ -213,10 +211,13 @@ func join_game(match_id: String):
|
|||||||
# Wait a bit for cleanup
|
# Wait a bit for cleanup
|
||||||
await get_tree().create_timer(0.2).timeout
|
await get_tree().create_timer(0.2).timeout
|
||||||
|
|
||||||
|
if not bridge:
|
||||||
|
printerr("Cannot join: Bridge became null while waiting")
|
||||||
|
return
|
||||||
|
|
||||||
print("Joining match: ", match_id)
|
print("Joining match: ", match_id)
|
||||||
var result = await bridge.join_match(match_id)
|
await bridge.join_match(match_id)
|
||||||
if result and result.is_exception():
|
# Errors are emitted via bridge's match_join_error signal
|
||||||
emit_signal("match_join_error", result.get_exception().message)
|
|
||||||
|
|
||||||
# --- Callbacks ---
|
# --- Callbacks ---
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
class Log:
|
||||||
|
|
||||||
|
enum {ERROR, INFO, DEBUG}
|
||||||
|
|
||||||
|
static func _s(lvl):
|
||||||
|
if lvl == ERROR:
|
||||||
|
return "ERROR"
|
||||||
|
elif lvl == INFO:
|
||||||
|
return "INFO"
|
||||||
|
elif lvl == DEBUG:
|
||||||
|
return "DEBUG"
|
||||||
|
return "WTF"
|
||||||
|
|
||||||
|
static func __log(lvl, msg, data):
|
||||||
|
print("======= %s: %s" % [_s(lvl), msg])
|
||||||
|
if not data.is_empty():
|
||||||
|
var json = JSON.new()
|
||||||
|
print(json.stringify(data, " ", true))
|
||||||
|
|
||||||
|
static func error(msg, data={}):
|
||||||
|
__log(ERROR, msg, data)
|
||||||
|
|
||||||
|
static func info(msg, data={}):
|
||||||
|
__log(INFO, msg, data)
|
||||||
|
|
||||||
|
static func debug(msg, data={}):
|
||||||
|
__log(DEBUG, msg, data)
|
||||||
|
|
||||||
|
const REAL_ASSERT = false
|
||||||
|
|
||||||
|
var _success = false
|
||||||
|
var _start_time = 0
|
||||||
|
var _quit = false
|
||||||
|
var _disabled = false
|
||||||
|
var __me = ""
|
||||||
|
|
||||||
|
# Override this to specify test initialization (called on _ready if not disabled)
|
||||||
|
# You can run your whole test here, and call done() when finished
|
||||||
|
func setup():
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Override this to do cleanup, make assertions at the end of the test (called on _exit_tree)
|
||||||
|
# NOTE: You can still fail here if you like with fail() or by failing an assertion
|
||||||
|
func teardown():
|
||||||
|
pass
|
||||||
|
|
||||||
|
func _init():
|
||||||
|
__me = get_script()
|
||||||
|
while __me.get_base_script() != null:
|
||||||
|
__me = __me.get_base_script()
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
if _disabled:
|
||||||
|
set_process(false)
|
||||||
|
set_physics_process(false)
|
||||||
|
set_process_input(false)
|
||||||
|
set_process_unhandled_input(false)
|
||||||
|
set_process_unhandled_key_input(false)
|
||||||
|
done()
|
||||||
|
Log.info("SKIP: %s" % __get_source(self))
|
||||||
|
return
|
||||||
|
_start_time = Time.get_ticks_usec()
|
||||||
|
Log.info("RUNNING: %s" % __get_source(self))
|
||||||
|
setup()
|
||||||
|
|
||||||
|
func _exit_tree():
|
||||||
|
if _disabled:
|
||||||
|
return
|
||||||
|
|
||||||
|
teardown()
|
||||||
|
Log.info("%s: %s" % ["SUCCESS" if _success else "!!!!!!!!!!!!!!!!!!! FAILURE", __get_source(self)])
|
||||||
|
|
||||||
|
func __get_source(who):
|
||||||
|
if who == null or who.get_script() == null:
|
||||||
|
return "Unknown source: %s" % str(who)
|
||||||
|
return who.get_script().resource_path
|
||||||
|
|
||||||
|
func __get_caller():
|
||||||
|
for s in get_stack():
|
||||||
|
if __me.resource_path != s.source:
|
||||||
|
return s
|
||||||
|
return null
|
||||||
|
|
||||||
|
func __get_assertion():
|
||||||
|
var stack = get_stack()
|
||||||
|
stack.reverse()
|
||||||
|
for s in stack:
|
||||||
|
if __me.resource_path == s.source:
|
||||||
|
return s
|
||||||
|
return null
|
||||||
|
|
||||||
|
func __assert(v1, v2):
|
||||||
|
if not (v1 == v2):
|
||||||
|
Log.error("Assert Failed", {"caller": __get_caller(), "assertion": __get_assertion(), "full_stack": get_stack()})
|
||||||
|
print_tree_pretty()
|
||||||
|
_quit = true
|
||||||
|
if REAL_ASSERT:
|
||||||
|
assert(v1 == v2)
|
||||||
|
if v1 == v2:
|
||||||
|
return false
|
||||||
|
return true
|
||||||
|
|
||||||
|
func assert_time(max_time):
|
||||||
|
__assert(max_time > float(Time.get_ticks_usec() - _start_time) / 1000.0 / 1000.0, true)
|
||||||
|
|
||||||
|
### Returns true if the assertion failed, so you can do:
|
||||||
|
### if assert_cond(cond):
|
||||||
|
### return # Assertion failed!
|
||||||
|
func assert_cond(cond):
|
||||||
|
return __assert(cond, true)
|
||||||
|
|
||||||
|
func assert_false(cond):
|
||||||
|
return __assert(cond, false)
|
||||||
|
|
||||||
|
func assert_equal(v1, v2):
|
||||||
|
return __assert(v1, v2)
|
||||||
|
|
||||||
|
func done():
|
||||||
|
_success = true
|
||||||
|
_quit = true
|
||||||
|
|
||||||
|
func fail():
|
||||||
|
_success = false
|
||||||
|
_quit = true
|
||||||
|
__assert(false, true)
|
||||||
|
|
||||||
|
func disable():
|
||||||
|
_disabled = true
|
||||||
|
|
||||||
|
func is_disabled():
|
||||||
|
return _disabled
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
; Engine configuration file.
|
||||||
|
; It's best edited using the editor UI and not directly,
|
||||||
|
; since the parameters that go here are not all obvious.
|
||||||
|
;
|
||||||
|
; Format:
|
||||||
|
; [section] ; section goes between []
|
||||||
|
; param=value ; assign values to parameters
|
||||||
|
|
||||||
|
config_version=5
|
||||||
|
|
||||||
|
_global_script_classes=[{
|
||||||
|
"base": "RefCounted",
|
||||||
|
"class": &"NakamaAPI",
|
||||||
|
"language": &"GDScript",
|
||||||
|
"path": "res://addons/com.heroiclabs.nakama/api/NakamaAPI.gd"
|
||||||
|
}, {
|
||||||
|
"base": "RefCounted",
|
||||||
|
"class": &"NakamaAsyncResult",
|
||||||
|
"language": &"GDScript",
|
||||||
|
"path": "res://addons/com.heroiclabs.nakama/utils/NakamaAsyncResult.gd"
|
||||||
|
}, {
|
||||||
|
"base": "RefCounted",
|
||||||
|
"class": &"NakamaClient",
|
||||||
|
"language": &"GDScript",
|
||||||
|
"path": "res://addons/com.heroiclabs.nakama/client/NakamaClient.gd"
|
||||||
|
}, {
|
||||||
|
"base": "RefCounted",
|
||||||
|
"class": &"NakamaException",
|
||||||
|
"language": &"GDScript",
|
||||||
|
"path": "res://addons/com.heroiclabs.nakama/utils/NakamaException.gd"
|
||||||
|
}, {
|
||||||
|
"base": "Node",
|
||||||
|
"class": &"NakamaHTTPAdapter",
|
||||||
|
"language": &"GDScript",
|
||||||
|
"path": "res://addons/com.heroiclabs.nakama/client/NakamaHTTPAdapter.gd"
|
||||||
|
}, {
|
||||||
|
"base": "RefCounted",
|
||||||
|
"class": &"NakamaLogger",
|
||||||
|
"language": &"GDScript",
|
||||||
|
"path": "res://addons/com.heroiclabs.nakama/utils/NakamaLogger.gd"
|
||||||
|
}, {
|
||||||
|
"base": "RefCounted",
|
||||||
|
"class": &"NakamaMultiplayerBridge",
|
||||||
|
"language": &"GDScript",
|
||||||
|
"path": "res://addons/com.heroiclabs.nakama/utils/NakamaMultiplayerBridge.gd"
|
||||||
|
}, {
|
||||||
|
"base": "MultiplayerPeerExtension",
|
||||||
|
"class": &"NakamaMultiplayerPeer",
|
||||||
|
"language": &"GDScript",
|
||||||
|
"path": "res://addons/com.heroiclabs.nakama/utils/NakamaMultiplayerPeer.gd"
|
||||||
|
}, {
|
||||||
|
"base": "NakamaAsyncResult",
|
||||||
|
"class": &"NakamaRTAPI",
|
||||||
|
"language": &"GDScript",
|
||||||
|
"path": "res://addons/com.heroiclabs.nakama/api/NakamaRTAPI.gd"
|
||||||
|
}, {
|
||||||
|
"base": "RefCounted",
|
||||||
|
"class": &"NakamaRTMessage",
|
||||||
|
"language": &"GDScript",
|
||||||
|
"path": "res://addons/com.heroiclabs.nakama/api/NakamaRTMessage.gd"
|
||||||
|
}, {
|
||||||
|
"base": "RefCounted",
|
||||||
|
"class": &"NakamaSerializer",
|
||||||
|
"language": &"GDScript",
|
||||||
|
"path": "res://addons/com.heroiclabs.nakama/utils/NakamaSerializer.gd"
|
||||||
|
}, {
|
||||||
|
"base": "NakamaAsyncResult",
|
||||||
|
"class": &"NakamaSession",
|
||||||
|
"language": &"GDScript",
|
||||||
|
"path": "res://addons/com.heroiclabs.nakama/api/NakamaSession.gd"
|
||||||
|
}, {
|
||||||
|
"base": "RefCounted",
|
||||||
|
"class": &"NakamaSocket",
|
||||||
|
"language": &"GDScript",
|
||||||
|
"path": "res://addons/com.heroiclabs.nakama/socket/NakamaSocket.gd"
|
||||||
|
}, {
|
||||||
|
"base": "Node",
|
||||||
|
"class": &"NakamaSocketAdapter",
|
||||||
|
"language": &"GDScript",
|
||||||
|
"path": "res://addons/com.heroiclabs.nakama/socket/NakamaSocketAdapter.gd"
|
||||||
|
}, {
|
||||||
|
"base": "RefCounted",
|
||||||
|
"class": &"NakamaStorageObjectId",
|
||||||
|
"language": &"GDScript",
|
||||||
|
"path": "res://addons/com.heroiclabs.nakama/api/NakamaStorageObjectId.gd"
|
||||||
|
}, {
|
||||||
|
"base": "RefCounted",
|
||||||
|
"class": &"NakamaWriteStorageObject",
|
||||||
|
"language": &"GDScript",
|
||||||
|
"path": "res://addons/com.heroiclabs.nakama/api/NakamaWriteStorageObject.gd"
|
||||||
|
}]
|
||||||
|
_global_script_class_icons={
|
||||||
|
"NakamaAPI": "",
|
||||||
|
"NakamaAsyncResult": "",
|
||||||
|
"NakamaClient": "",
|
||||||
|
"NakamaException": "",
|
||||||
|
"NakamaHTTPAdapter": "",
|
||||||
|
"NakamaLogger": "",
|
||||||
|
"NakamaMultiplayerBridge": "",
|
||||||
|
"NakamaMultiplayerPeer": "",
|
||||||
|
"NakamaRTAPI": "",
|
||||||
|
"NakamaRTMessage": "",
|
||||||
|
"NakamaSerializer": "",
|
||||||
|
"NakamaSession": "",
|
||||||
|
"NakamaSocket": "",
|
||||||
|
"NakamaSocketAdapter": "",
|
||||||
|
"NakamaStorageObjectId": "",
|
||||||
|
"NakamaWriteStorageObject": ""
|
||||||
|
}
|
||||||
|
|
||||||
|
[application]
|
||||||
|
|
||||||
|
run/main_scene="res://tester.tscn"
|
||||||
|
config/features=PackedStringArray("4.0")
|
||||||
|
|
||||||
|
[autoload]
|
||||||
|
|
||||||
|
Nakama="*res://addons/com.heroiclabs.nakama/Nakama.gd"
|
||||||
|
Config="*res://utils/config.gd"
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
END_STRING="======= TESTS END"
|
||||||
|
PROJECT_PATH="test_suite/"
|
||||||
|
GODOT_BIN="test_suite/bin/godot.elf"
|
||||||
|
OUT=`$GODOT_BIN --headless --debug --path test_suite/ -s res://runner.gd`
|
||||||
|
RUN=`echo $OUT | grep "$END_STRING"`
|
||||||
|
RES=`echo $OUT | grep FAILURE`
|
||||||
|
|
||||||
|
echo "$OUT"
|
||||||
|
|
||||||
|
if [ -z "$RUN" ]; then
|
||||||
|
echo "Run failed!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$RES" ]; then
|
||||||
|
echo "Tests passed!"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "Tests failed!"
|
||||||
|
exit 1
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
extends SceneTree
|
||||||
|
|
||||||
|
func _init():
|
||||||
|
var me = ProjectSettings.localize_path(ProjectSettings.globalize_path(get_script().resource_path))
|
||||||
|
var dir = ProjectSettings.localize_path(me.get_base_dir())
|
||||||
|
|
||||||
|
if dir != "res://":
|
||||||
|
print("Must be run with the script dir as the res:// path")
|
||||||
|
print("Current path: ", ProjectSettings.globalize_path(dir))
|
||||||
|
print("RES:// path: ", ProjectSettings.globalize_path("res://"))
|
||||||
|
quit()
|
||||||
|
return
|
||||||
|
var n = load("res://tester.gd").new()
|
||||||
|
root.add_child(n)
|
||||||
|
current_scene = n
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
{
|
||||||
|
"HOST": "127.0.0.1",
|
||||||
|
"PORT": 7350,
|
||||||
|
"SCHEME": "http",
|
||||||
|
"SERVER_KEY": "defaultkey"
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
var current = null
|
||||||
|
var tests = []
|
||||||
|
var frame_time = 0.0
|
||||||
|
var fixed_time = 0.0
|
||||||
|
var dirs = ["tests"]
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
while not dirs.is_empty():
|
||||||
|
var d = dirs.pop_front()
|
||||||
|
_expand(d, dirs)
|
||||||
|
print("Running %d tests:\n%s" % [tests.size(), tests])
|
||||||
|
|
||||||
|
func _process(delta):
|
||||||
|
frame_time += delta
|
||||||
|
_check_end()
|
||||||
|
|
||||||
|
func _physics_process(delta):
|
||||||
|
fixed_time += delta
|
||||||
|
_check_end()
|
||||||
|
|
||||||
|
func _check_end():
|
||||||
|
if current == null:
|
||||||
|
if tests.size() == 0:
|
||||||
|
_end_tests()
|
||||||
|
return
|
||||||
|
current = load(tests.pop_front()).new()
|
||||||
|
add_child(current)
|
||||||
|
elif current._quit:
|
||||||
|
if not current._success:
|
||||||
|
_end_tests()
|
||||||
|
current.queue_free()
|
||||||
|
current = null
|
||||||
|
|
||||||
|
func _end_tests():
|
||||||
|
print("======= TESTS END")
|
||||||
|
set_process(false)
|
||||||
|
set_physics_process(false)
|
||||||
|
await get_tree().create_timer(1.0).timeout
|
||||||
|
get_tree().call_deferred("quit")
|
||||||
|
|
||||||
|
func _expand(p_name, r_dirs):
|
||||||
|
var dir = DirAccess.open("res://")
|
||||||
|
if dir.change_dir(p_name) != OK:
|
||||||
|
print("Unable to chdir into: %s" % p_name)
|
||||||
|
return
|
||||||
|
dir.list_dir_begin()
|
||||||
|
var f = dir.get_next()
|
||||||
|
while f != "":
|
||||||
|
if dir.current_is_dir():
|
||||||
|
r_dirs.append("%s/%s" % [p_name, f])
|
||||||
|
if f.ends_with("_test.gd"):
|
||||||
|
tests.append("%s/%s" % [p_name, f])
|
||||||
|
f = dir.get_next()
|
||||||
|
dir.list_dir_end()
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
[gd_scene load_steps=2 format=3 uid="uid://bglgahpqv1ojs"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://tester.gd" id="1"]
|
||||||
|
|
||||||
|
[node name="Control" type="Control"]
|
||||||
|
layout_mode = 3
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
grow_horizontal = 2
|
||||||
|
grow_vertical = 2
|
||||||
|
script = ExtResource("1")
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
extends "res://base_test.gd"
|
||||||
|
|
||||||
|
func setup():
|
||||||
|
var username = str(randi_range(1000, 100000))
|
||||||
|
var client = Nakama.create_client(Config.SERVER_KEY, Config.HOST, Config.PORT, Config.SCHEME)
|
||||||
|
# POST
|
||||||
|
var session = await client.authenticate_custom_async("MyIdentifier")
|
||||||
|
if assert_cond(session.is_valid()) or assert_cond(!session.is_expired()):
|
||||||
|
return
|
||||||
|
# PUT
|
||||||
|
var update = await client.update_account_async(session, username)
|
||||||
|
if assert_false(update.is_exception()):
|
||||||
|
return
|
||||||
|
# GET
|
||||||
|
var account : NakamaAPI.ApiAccount = await client.get_account_async(session)
|
||||||
|
if assert_false(account.is_exception()):
|
||||||
|
return
|
||||||
|
if assert_cond(account.user.username == username):
|
||||||
|
return
|
||||||
|
# POST - DELETE
|
||||||
|
var group : NakamaAPI.ApiGroup = await client.create_group_async(session, "MyGroupName3")
|
||||||
|
if assert_false(group.is_exception()):
|
||||||
|
return
|
||||||
|
var delete = await client.delete_group_async(session, group.id)
|
||||||
|
if assert_false(delete.is_exception()):
|
||||||
|
return
|
||||||
|
# All good
|
||||||
|
done()
|
||||||
|
return
|
||||||
|
|
||||||
|
func _process(_delta):
|
||||||
|
assert_time(3)
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
extends "res://base_test.gd"
|
||||||
|
|
||||||
|
var content = {"My": "message"}
|
||||||
|
var match_string_props = {"region": "europe"}
|
||||||
|
var match_numeric_props = {"rank": 8}
|
||||||
|
var got_msg = false
|
||||||
|
var got_match = false
|
||||||
|
var socket1 = null
|
||||||
|
var socket2 = null
|
||||||
|
|
||||||
|
func setup():
|
||||||
|
var client = Nakama.create_client(Config.SERVER_KEY, Config.HOST, Config.PORT, Config.SCHEME)
|
||||||
|
|
||||||
|
var session1 = await client.authenticate_custom_async("MyIdentifier")
|
||||||
|
if assert_cond(session1.is_valid()):
|
||||||
|
return
|
||||||
|
|
||||||
|
var session2 = await client.authenticate_custom_async("MyIdentifier2")
|
||||||
|
if assert_cond(session1.is_valid()):
|
||||||
|
return
|
||||||
|
|
||||||
|
socket1 = Nakama.create_socket_from(client)
|
||||||
|
socket1.received_channel_message.connect(self._on_socket1_message)
|
||||||
|
socket1.received_matchmaker_matched.connect(self._on_socket1_matchmaker_matched)
|
||||||
|
var done = await socket1.connect_async(session1)
|
||||||
|
# Check that connection succeded
|
||||||
|
if assert_false(done.is_exception()):
|
||||||
|
return
|
||||||
|
|
||||||
|
socket2 = Nakama.create_socket_from(client)
|
||||||
|
done = await socket2.connect_async(session2)
|
||||||
|
# Check that connection succeded
|
||||||
|
if assert_false(done.is_exception()):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Join room
|
||||||
|
var room1 = await socket1.join_chat_async("MyRoom", NakamaSocket.ChannelType.Room)
|
||||||
|
if assert_false(room1.is_exception()):
|
||||||
|
return
|
||||||
|
var room2 = await socket2.join_chat_async("MyRoom", NakamaSocket.ChannelType.Room)
|
||||||
|
if assert_false(room2.is_exception()):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Socket 2 send message to socket 1
|
||||||
|
var msg_ack = await socket2.write_chat_message_async(room2.id, content)
|
||||||
|
if assert_false(msg_ack.is_exception()):
|
||||||
|
return
|
||||||
|
|
||||||
|
var ticket1 = await socket1.add_matchmaker_async("+properties.region:europe +properties.rank:>=7 +properties.rank:<=9", 2, 8, match_string_props, match_numeric_props)
|
||||||
|
if assert_false(ticket1.is_exception()):
|
||||||
|
return
|
||||||
|
var ticket2 = await socket2.add_matchmaker_async("+properties.region:europe +properties.rank:>=7 +properties.rank:<=9", 2, 8, match_string_props, match_numeric_props)
|
||||||
|
if assert_false(ticket2.is_exception()):
|
||||||
|
return
|
||||||
|
|
||||||
|
func _on_socket1_message(msg):
|
||||||
|
if assert_equal(msg.content, JSON.stringify(content)):
|
||||||
|
return
|
||||||
|
got_msg = true
|
||||||
|
check_end()
|
||||||
|
|
||||||
|
func _on_socket1_matchmaker_matched(p_matchmaker_matched):
|
||||||
|
if assert_equal(JSON.stringify(p_matchmaker_matched.users[0].string_properties), JSON.stringify(match_string_props)):
|
||||||
|
return
|
||||||
|
if assert_equal(JSON.stringify(p_matchmaker_matched.users[0].numeric_properties), JSON.stringify(match_numeric_props)):
|
||||||
|
return
|
||||||
|
if assert_equal(JSON.stringify(p_matchmaker_matched.users[1].string_properties), JSON.stringify(match_string_props)):
|
||||||
|
return
|
||||||
|
if assert_equal(JSON.stringify(p_matchmaker_matched.users[1].numeric_properties), JSON.stringify(match_numeric_props)):
|
||||||
|
return
|
||||||
|
got_match = true
|
||||||
|
check_end()
|
||||||
|
|
||||||
|
func _process(_delta):
|
||||||
|
assert_time(60)
|
||||||
|
|
||||||
|
func check_end():
|
||||||
|
if got_match and got_msg:
|
||||||
|
done()
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
extends "res://base_test.gd"
|
||||||
|
|
||||||
|
var content = {"My": "message"}
|
||||||
|
var match_props = {"region": "europe"}
|
||||||
|
var got_msg = false
|
||||||
|
var got_match = false
|
||||||
|
var socket1 : NakamaSocket = null
|
||||||
|
var socket2 : NakamaSocket = null
|
||||||
|
|
||||||
|
func setup():
|
||||||
|
var client = Nakama.create_client(Config.SERVER_KEY, Config.HOST, Config.PORT, Config.SCHEME)
|
||||||
|
|
||||||
|
var session1 = await client.authenticate_custom_async("MyIdentifier")
|
||||||
|
if assert_cond(session1.is_valid()):
|
||||||
|
return
|
||||||
|
|
||||||
|
var session2 = await client.authenticate_custom_async("MyIdentifier2")
|
||||||
|
if assert_cond(session2.is_valid()):
|
||||||
|
return
|
||||||
|
|
||||||
|
socket1 = Nakama.create_socket_from(client)
|
||||||
|
socket1.received_party_close.connect(self._on_party_close)
|
||||||
|
socket1.received_party_data.connect(self._on_party_data)
|
||||||
|
socket1.received_party_join_request.connect(self._on_party_join_request)
|
||||||
|
|
||||||
|
var conn = await socket1.connect_async(session1)
|
||||||
|
# Check that connection succeded
|
||||||
|
if assert_false(conn.is_exception()):
|
||||||
|
return
|
||||||
|
|
||||||
|
var party = await socket1.create_party_async(false, 2)
|
||||||
|
if assert_false(party.is_exception()):
|
||||||
|
return
|
||||||
|
#done()
|
||||||
|
|
||||||
|
socket2 = Nakama.create_socket_from(client)
|
||||||
|
socket2.received_party.connect(self._on_party)
|
||||||
|
socket2.received_party_close.connect(self._on_party_close)
|
||||||
|
socket2.received_party_join_request.connect(self._on_party_join_request)
|
||||||
|
socket2.received_party_leader.connect(self._on_party_leader)
|
||||||
|
socket2.received_party_presence.connect(self._on_party_presence)
|
||||||
|
|
||||||
|
var conn2 = await socket2.connect_async(session2)
|
||||||
|
# Check that connection succeded
|
||||||
|
if assert_false(conn2.is_exception()):
|
||||||
|
return
|
||||||
|
|
||||||
|
var join = await socket2.join_party_async(party.party_id)
|
||||||
|
if assert_false(join.is_exception()):
|
||||||
|
return
|
||||||
|
|
||||||
|
func _on_party_join_request(party_join_request : NakamaRTAPI.PartyJoinRequest):
|
||||||
|
prints("_on_party_join_request", party_join_request)
|
||||||
|
var requests : NakamaRTAPI.PartyJoinRequest = await socket1.list_party_join_requests_async(party_join_request.party_id)
|
||||||
|
if assert_false(requests.is_exception()):
|
||||||
|
return
|
||||||
|
if assert_cond(requests.presences.size() == 1):
|
||||||
|
return
|
||||||
|
|
||||||
|
await socket1.accept_party_member_async(party_join_request.party_id, party_join_request.presences[0])
|
||||||
|
await socket1.promote_party_member(party_join_request.party_id, party_join_request.presences[0])
|
||||||
|
|
||||||
|
func _on_party(party):
|
||||||
|
prints("_on_party", party)
|
||||||
|
|
||||||
|
func _on_party_close(party_close):
|
||||||
|
prints("_on_party_close", party_close)
|
||||||
|
|
||||||
|
func _on_party_data(data : NakamaRTAPI.PartyData):
|
||||||
|
prints("_on_party_data", data)
|
||||||
|
var left = await socket1.leave_party_async(data.party_id)
|
||||||
|
if assert_false(left.is_exception()):
|
||||||
|
return
|
||||||
|
|
||||||
|
func _on_party_leader(party_leader : NakamaRTAPI.PartyLeader):
|
||||||
|
prints("_on_party_leader", party_leader)
|
||||||
|
var ticket = await socket2.add_matchmaker_party_async(party_leader.party_id)
|
||||||
|
if assert_false(ticket.is_exception()):
|
||||||
|
return
|
||||||
|
_on_party_ticket(ticket)
|
||||||
|
|
||||||
|
func _on_party_ticket(ticket : NakamaRTAPI.PartyMatchmakerTicket):
|
||||||
|
prints("_on_party_ticket", ticket)
|
||||||
|
var removed = await socket2.remove_matchmaker_party_async(ticket.party_id, ticket.ticket)
|
||||||
|
if assert_false(removed.is_exception()):
|
||||||
|
return
|
||||||
|
var sent = await socket2.send_party_data_async(ticket.party_id, 1, "asd")
|
||||||
|
if assert_false(sent.is_exception()):
|
||||||
|
return
|
||||||
|
|
||||||
|
func _on_party_presence(party_presence : NakamaRTAPI.PartyPresenceEvent):
|
||||||
|
prints("_on_party_presence", party_presence)
|
||||||
|
var left = party_presence.leaves.size() == 1
|
||||||
|
if left:
|
||||||
|
var closed = await socket2.close_party_async(party_presence.party_id)
|
||||||
|
if assert_false(closed.is_exception()):
|
||||||
|
return
|
||||||
|
done()
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
extends "res://base_test.gd"
|
||||||
|
|
||||||
|
var _connected = false
|
||||||
|
|
||||||
|
func setup():
|
||||||
|
var client = Nakama.create_client(Config.SERVER_KEY, Config.HOST, Config.PORT, Config.SCHEME)
|
||||||
|
|
||||||
|
var session = await client.authenticate_custom_async("MyIdentifier")
|
||||||
|
if assert_cond(session.is_valid()):
|
||||||
|
return
|
||||||
|
|
||||||
|
var socket = Nakama.create_socket_from(client)
|
||||||
|
socket.connected.connect(self._on_socket_connected)
|
||||||
|
var conn = await socket.connect_async(session)
|
||||||
|
# Check that connection succeded
|
||||||
|
if assert_false(conn.is_exception()):
|
||||||
|
return
|
||||||
|
# Check that signal has been called
|
||||||
|
if assert_cond(_connected):
|
||||||
|
return
|
||||||
|
done()
|
||||||
|
|
||||||
|
func _on_socket_connected():
|
||||||
|
_connected = true
|
||||||
|
|
||||||
|
func _process(_delta):
|
||||||
|
assert_time(3)
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
extends "res://base_test.gd"
|
||||||
|
|
||||||
|
const COLLECTION = "ACollection"
|
||||||
|
const K1 = "Question"
|
||||||
|
const K2 = "Answer"
|
||||||
|
const V1 = {"question": "Ultimate question"}
|
||||||
|
const V2 = {"answer": "42"}
|
||||||
|
|
||||||
|
func setup():
|
||||||
|
var username = str(randi_range(1000, 100000))
|
||||||
|
var client = Nakama.create_client(Config.SERVER_KEY, Config.HOST, Config.PORT, Config.SCHEME)
|
||||||
|
|
||||||
|
var session = await client.authenticate_custom_async("MyIdentifier")
|
||||||
|
if assert_cond(session.is_valid()):
|
||||||
|
return
|
||||||
|
|
||||||
|
var objs = [
|
||||||
|
NakamaWriteStorageObject.new(COLLECTION, K1, 1, 1, to_json(V1), ""),
|
||||||
|
NakamaWriteStorageObject.new(COLLECTION, K2, 1, 1, to_json(V2), "")
|
||||||
|
]
|
||||||
|
var write : NakamaAPI.ApiStorageObjectAcks = await client.write_storage_objects_async(session, objs)
|
||||||
|
if assert_false(write.is_exception()):
|
||||||
|
return
|
||||||
|
|
||||||
|
var objs_ids = []
|
||||||
|
for a in write.acks:
|
||||||
|
#var obj : NakamaAPI.ApiStorageObjectAck = a
|
||||||
|
objs_ids.append(NakamaStorageObjectId.new(a.collection, a.key, a.user_id, a.version))
|
||||||
|
|
||||||
|
var read : NakamaAPI.ApiStorageObjects = await client.read_storage_objects_async(session, objs_ids)
|
||||||
|
if assert_false(read.is_exception()):
|
||||||
|
return
|
||||||
|
if assert_equal(read.objects.size(), 2):
|
||||||
|
return
|
||||||
|
if assert_equal(read.objects[0].collection, COLLECTION):
|
||||||
|
return
|
||||||
|
if assert_cond(read.objects[0].key in [K1, K2]):
|
||||||
|
return
|
||||||
|
if assert_cond(to_json(parse_json(read.objects[0].value)) in [to_json(V1), to_json(V2)]):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Delete one
|
||||||
|
var del = await client.delete_storage_objects_async(session, [objs_ids[0]])
|
||||||
|
if assert_false(del.is_exception()):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Confirm that one was deleted
|
||||||
|
var read2 : NakamaAPI.ApiStorageObjects = await client.read_storage_objects_async(session, objs_ids)
|
||||||
|
if assert_false(read2.is_exception()):
|
||||||
|
return
|
||||||
|
if assert_equal(read2.objects.size(), 1):
|
||||||
|
return
|
||||||
|
if assert_equal(read2.objects[0].collection, COLLECTION):
|
||||||
|
return
|
||||||
|
if assert_equal(read2.objects[0].key, objs_ids[1].key):
|
||||||
|
return
|
||||||
|
if assert_cond(to_json(parse_json(read2.objects[0].value)) in [to_json(V1), to_json(V2)]):
|
||||||
|
return
|
||||||
|
done()
|
||||||
|
return
|
||||||
|
|
||||||
|
func _process(_delta):
|
||||||
|
assert_time(3)
|
||||||
|
|
||||||
|
func to_json(value) -> String:
|
||||||
|
return JSON.stringify(value)
|
||||||
|
|
||||||
|
func parse_json(value):
|
||||||
|
var json = JSON.new()
|
||||||
|
if json.parse(value) != OK:
|
||||||
|
return null
|
||||||
|
return json.get_data()
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
extends Node
|
||||||
|
|
||||||
|
var HOST = "127.0.0.1"
|
||||||
|
var PORT = 7350
|
||||||
|
var SCHEME = "http"
|
||||||
|
var SERVER_KEY = "defaultkey"
|
||||||
|
|
||||||
|
func _ready():
|
||||||
|
var f = FileAccess.open("res://settings.json", FileAccess.READ)
|
||||||
|
if not f:
|
||||||
|
return
|
||||||
|
var json = JSON.new()
|
||||||
|
var error = json.parse(f.get_as_text())
|
||||||
|
var parsed = json.get_data()
|
||||||
|
if error != OK or typeof(parsed) != TYPE_DICTIONARY:
|
||||||
|
return
|
||||||
|
for k in parsed:
|
||||||
|
match k:
|
||||||
|
"HOST": HOST = parsed[k]
|
||||||
|
"PORT": PORT = parsed[k]
|
||||||
|
"SCHEME": SCHEME = parsed[k]
|
||||||
|
"SERVER_KEY": SERVER_KEY = parsed[k]
|
||||||
Reference in New Issue
Block a user