Release 0.1.0

This commit is contained in:
xeon
2026-02-02 20:53:22 +03:00
commit 25660300dd
152 changed files with 882089 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
.direnv/
.zig-cache/
zig-out/
store/

235
LICENSE Normal file
View File

@@ -0,0 +1,235 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for software and other kinds of works, specifically designed to ensure cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users.
When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License which gives you legal permission to copy, distribute and/or modify the software.
A secondary benefit of defending all users' freedom is that improvements made in alternate versions of the program, if they receive widespread use, become available for other developers to incorporate. Many developers of free software are heartened and encouraged by the resulting cooperation. However, in the case of software used on network servers, this result may fail to come about. The GNU General Public License permits making a modified version and letting the public access it on a server without ever releasing its source code to the public.
The GNU Affero General Public License is designed specifically to ensure that, in such cases, the modified source code becomes available to the community. It requires the operator of a network server to provide the source code of the modified version running there to the users of that server. Therefore, public use of a modified version, on a publicly accessible server, gives the public access to the source code of the modified version.
An older license, called the Affero General Public License and published by Affero, was designed to accomplish similar goals. This is a different license, not a version of the Affero GPL, but Affero has released a new version of the Affero GPL which permits relicensing under this license.
The precise terms and conditions for copying, distribution and modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based on the Program.
To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work.
A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source.
The Corresponding Source for a work in source code form is that same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures.
When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified it, and giving a relevant date.
b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices".
c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so.
A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways:
a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b.
d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d.
A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product.
"Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made.
If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM).
The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or authors of the material; or
e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors.
All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11).
However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice.
Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party.
If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it.
A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software. This Corresponding Source shall include the Corresponding Source for any work covered by version 3 of the GNU General Public License that is incorporated pursuant to the following paragraph.
Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the work with which it is combined will remain governed by version 3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of the GNU Affero General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU Affero General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation.
If the Program specifies that a proxy can decide which future versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program.
Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.
LR-S
Copyright (C) 2026 ReversedRooms
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program 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. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer network, you should also make sure that it provides a way for users to get its source. For example, if your program is a web application, its interface could display a "Source" link that leads users to an archive of the code. There are many ways you could offer source, and different solutions will be better for different programs; see section 13 for the specific requirements.
You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see <http://www.gnu.org/licenses/>.

41
README.md Normal file
View File

@@ -0,0 +1,41 @@
# LR-S
#### Server Emulator for the game Arknights: Endfield.
![title](assets/img/title.png)
# Getting Started
## Requirements
- Zig 0.16.0-dev.2368: [Linux](https://ziglang.org/builds/zig-x86_64-linux-0.16.0-dev.2368+380ea6fb5.tar.xz)/[Windows](https://ziglang.org/builds/zig-x86_64-windows-0.16.0-dev.2368+380ea6fb5.zip)
#### For additional help, you can join our [discord server](https://discord.xeondev.com)
## Setup
### Building from sources
#### Linux
```sh
git clone https://git.xeondev.com/LR/S.git
cd S
. ./envrc # In case you don't have zig installed, `envrc` can do this for you.
zig build run-confsv &
zig build run-gamesv
```
#### Windows
```bat
# Assuming you have git and zig installed.
git clone https://git.xeondev.com/LR/S.git
cd S
zig build run-confsv -Doptimize=ReleaseSmall
# Open another instance of cmd.exe in this directory, then run:
zig build run-gamesv -Doptimize=ReleaseSmall
```
### Logging in
Currently supported client version is `1.0.14`, you can get it from 3rd party sources.
Next, you have to apply the necessary [client patch](https://git.xeondev.com/LR/C). It allows you to connect to the local server.
## Community
- [Our Discord Server](https://discord.xeondev.com)
- [Our Telegram Channel](https://t.me/reversedrooms)
## Donations
Continuing to produce open source software requires contribution of time, code and -especially for the distribution- money. If you are able to make a contribution, it will go towards ensuring that we are able to continue to write, support and host the high quality software that makes all of our lives easier. Feel free to make a contribution [via Boosty](https://boosty.to/xeondev/donate)!

View File

@@ -0,0 +1,42 @@
{
"config": {
"Character": {
"skillConfigs": [
{
"skillId": "common_character_ai_dash",
"skillType": 3
},
{
"skillId": "common_character_battle_step_left",
"skillType": 0
},
{
"skillId": "common_character_battle_step_right",
"skillType": 0
},
{
"skillId": "common_character_farming_start",
"skillType": 3
},
{
"skillId": "common_character_farming_end",
"skillType": 3
},
{
"skillId": "common_character_healtap",
"skillType": 3
},
{
"skillId": "common_character_healtap_left",
"skillType": 3
},
{
"skillId": "common_character_battle_walk_retreat",
"skillType": 0
}
]
}
},
"name": "CommonSkillConfig",
"hideFlags": 0
}

File diff suppressed because it is too large Load Diff

BIN
assets/img/title.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 MiB

View File

@@ -0,0 +1,374 @@
[
{
"Key": "chr_0011_seraph",
"Value": {
"charId": "chr_0011_seraph",
"weaponIds1": [
"wpn_funnel_0010",
"wpn_funnel_0012"
],
"weaponIds2": [
"wpn_funnel_0011",
"wpn_funnel_0007"
],
"weaponIds3": []
}
},
{
"Key": "chr_0023_antal",
"Value": {
"charId": "chr_0023_antal",
"weaponIds1": [
"wpn_funnel_0008",
"wpn_funnel_0014"
],
"weaponIds2": [
"wpn_funnel_0009",
"wpn_funnel_0005"
],
"weaponIds3": []
}
},
{
"Key": "chr_0020_meurs",
"Value": {
"charId": "chr_0020_meurs",
"weaponIds1": [
"wpn_claym_0006",
"wpn_claym_0015"
],
"weaponIds2": [
"wpn_claym_0008",
"wpn_claym_0011"
],
"weaponIds3": []
}
},
{
"Key": "chr_0024_deepfin",
"Value": {
"charId": "chr_0024_deepfin",
"weaponIds1": [
"wpn_sword_0020",
"wpn_sword_0019",
"wpn_sword_0012"
],
"weaponIds2": [
"wpn_sword_0010"
],
"weaponIds3": []
}
},
{
"Key": "chr_0017_yvonne",
"Value": {
"charId": "chr_0017_yvonne",
"weaponIds1": [
"wpn_pistol_0010",
"wpn_pistol_0006"
],
"weaponIds2": [
"wpn_pistol_0008",
"wpn_pistol_0005"
],
"weaponIds3": []
}
},
{
"Key": "chr_0025_ardelia",
"Value": {
"charId": "chr_0025_ardelia",
"weaponIds1": [
"wpn_funnel_0013",
"wpn_funnel_0010",
"wpn_funnel_0006"
],
"weaponIds2": [
"wpn_funnel_0005",
"wpn_funnel_0014"
],
"weaponIds3": []
}
},
{
"Key": "chr_0029_pograni",
"Value": {
"charId": "chr_0029_pograni",
"weaponIds1": [
"wpn_sword_0016",
"wpn_sword_0012"
],
"weaponIds2": [
"wpn_sword_0005"
],
"weaponIds3": []
}
},
{
"Key": "chr_0018_dapan",
"Value": {
"charId": "chr_0018_dapan",
"weaponIds1": [
"wpn_claym_0004",
"wpn_claym_0008"
],
"weaponIds2": [
"wpn_claym_0015",
"wpn_claym_0011"
],
"weaponIds3": []
}
},
{
"Key": "chr_0019_karin",
"Value": {
"charId": "chr_0019_karin",
"weaponIds1": [
"wpn_sword_0012",
"wpn_sword_0019"
],
"weaponIds2": [
"wpn_sword_0013",
"wpn_sword_0021"
],
"weaponIds3": []
}
},
{
"Key": "chr_0026_lastrite",
"Value": {
"charId": "chr_0026_lastrite",
"weaponIds1": [
"wpn_claym_0013",
"wpn_claym_0011"
],
"weaponIds2": [
"wpn_claym_0008"
],
"weaponIds3": []
}
},
{
"Key": "chr_0021_whiten",
"Value": {
"charId": "chr_0021_whiten",
"weaponIds1": [
"wpn_lance_0011",
"wpn_lance_0013"
],
"weaponIds2": [
"wpn_lance_0006"
],
"weaponIds3": []
}
},
{
"Key": "chr_0013_aglina",
"Value": {
"charId": "chr_0013_aglina",
"weaponIds1": [
"wpn_funnel_0011",
"wpn_funnel_0006"
],
"weaponIds2": [
"wpn_funnel_0010",
"wpn_funnel_0007"
],
"weaponIds3": []
}
},
{
"Key": "chr_0015_lifeng",
"Value": {
"charId": "chr_0015_lifeng",
"weaponIds1": [
"wpn_lance_0012",
"wpn_lance_0004",
"wpn_lance_0010"
],
"weaponIds2": [
"wpn_lance_0003"
],
"weaponIds3": []
}
},
{
"Key": "chr_9000_endmin",
"Value": {
"charId": "chr_9000_endmin",
"weaponIds1": [
"wpn_sword_0021",
"wpn_sword_0013"
],
"weaponIds2": [
"wpn_sword_0011",
"wpn_sword_0005"
],
"weaponIds3": []
}
},
{
"Key": "chr_0022_bounda",
"Value": {
"charId": "chr_0022_bounda",
"weaponIds1": [
"wpn_pistol_0005",
"wpn_pistol_0006"
],
"weaponIds2": [
"wpn_pistol_0012"
],
"weaponIds3": []
}
},
{
"Key": "chr_0005_chen",
"Value": {
"charId": "chr_0005_chen",
"weaponIds1": [
"wpn_sword_0011",
"wpn_sword_0015"
],
"weaponIds2": [
"wpn_sword_0021",
"wpn_sword_0013"
],
"weaponIds3": []
}
},
{
"Key": "chr_0002_endminm",
"Value": {
"charId": "chr_0002_endminm",
"weaponIds1": [
"wpn_sword_0021",
"wpn_sword_0013"
],
"weaponIds2": [
"wpn_sword_0011",
"wpn_sword_0005"
],
"weaponIds3": []
}
},
{
"Key": "chr_0006_wolfgd",
"Value": {
"charId": "chr_0006_wolfgd",
"weaponIds1": [
"wpn_pistol_0009",
"wpn_pistol_0008"
],
"weaponIds2": [
"wpn_pistol_0004",
"wpn_pistol_0003"
],
"weaponIds3": []
}
},
{
"Key": "chr_0007_ikut",
"Value": {
"charId": "chr_0007_ikut",
"weaponIds1": [
"wpn_sword_0014",
"wpn_sword_0012"
],
"weaponIds2": [
"wpn_sword_0018",
"wpn_sword_0019"
],
"weaponIds3": []
}
},
{
"Key": "chr_0014_aurora",
"Value": {
"charId": "chr_0014_aurora",
"weaponIds1": [
"wpn_claym_0006",
"wpn_claym_0013"
],
"weaponIds2": [
"wpn_claym_0008",
"wpn_claym_0004"
],
"weaponIds3": []
}
},
{
"Key": "chr_0016_laevat",
"Value": {
"charId": "chr_0016_laevat",
"weaponIds1": [
"wpn_sword_0006",
"wpn_sword_0010",
"wpn_sword_0014"
],
"weaponIds2": [
"wpn_sword_0007"
],
"weaponIds3": []
}
},
{
"Key": "chr_0009_azrila",
"Value": {
"charId": "chr_0009_azrila",
"weaponIds1": [
"wpn_claym_0007",
"wpn_claym_0008",
"wpn_claym_0012"
],
"weaponIds2": [
"wpn_claym_0004",
"wpn_claym_0014"
],
"weaponIds3": []
}
},
{
"Key": "chr_0003_endminf",
"Value": {
"charId": "chr_0003_endminf",
"weaponIds1": [
"wpn_sword_0021",
"wpn_sword_0013"
],
"weaponIds2": [
"wpn_sword_0011",
"wpn_sword_0005"
],
"weaponIds3": []
}
},
{
"Key": "chr_0004_pelica",
"Value": {
"charId": "chr_0004_pelica",
"weaponIds1": [
"wpn_funnel_0009",
"wpn_funnel_0008"
],
"weaponIds2": [
"wpn_funnel_0004",
"wpn_funnel_0005"
],
"weaponIds3": []
}
},
{
"Key": "chr_0012_avywen",
"Value": {
"charId": "chr_0012_avywen",
"weaponIds1": [
"wpn_lance_0011",
"wpn_lance_0006"
],
"weaponIds2": [
"wpn_lance_0008"
],
"weaponIds3": []
}
}
]

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

149
build.zig Normal file
View File

@@ -0,0 +1,149 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const common = b.createModule(.{
.root_source_file = b.path("common/src/root.zig"),
});
const lr_proto_gen = b.addExecutable(.{
.name = "lr_proto_gen",
.root_module = b.createModule(.{
.root_source_file = b.path("proto/gen/src/main.zig"),
.optimize = optimize,
.target = b.graph.host,
}),
});
const compile_proto = b.addRunArtifact(lr_proto_gen);
compile_proto.expectExitCode(0);
const pb_generated = compile_proto.captureStdOut(.{ .basename = "beyond_generated.zig" });
for (proto_files) |file| {
compile_proto.addFileArg(b.path(file));
}
const proto = b.createModule(.{
.root_source_file = b.path("proto/src/root.zig"),
.optimize = optimize,
.target = target,
});
proto.addAnonymousImport("beyond_generated", .{ .root_source_file = pb_generated });
const confsv = b.addExecutable(.{
.name = "lr-confsv",
.root_module = b.createModule(.{
.root_source_file = b.path("confsv/src/main.zig"),
.imports = &.{.{ .name = "common", .module = common }},
.target = target,
.optimize = optimize,
}),
});
const gamesv = b.addExecutable(.{
.name = "lr-gamesv",
.root_module = b.createModule(.{
.root_source_file = b.path("gamesv/src/main.zig"),
.imports = &.{
.{ .name = "common", .module = common },
.{ .name = "proto", .module = proto },
},
.target = target,
.optimize = optimize,
}),
});
gamesv.step.dependOn(&compile_proto.step);
b.step(
"run-confsv",
"run the config server",
).dependOn(&b.addRunArtifact(confsv).step);
b.step(
"run-gamesv",
"run the game server",
).dependOn(&b.addRunArtifact(gamesv).step);
b.installArtifact(confsv);
b.installArtifact(gamesv);
}
const proto_files: []const []const u8 = &.{
"proto/pb/battle.proto",
"proto/pb/common.proto",
"proto/pb/cs_achieve.proto",
"proto/pb/cs_activity.proto",
"proto/pb/cs_adventure_book.proto",
"proto/pb/cs_adventure.proto",
"proto/pb/cs_anti_cheat.proto",
"proto/pb/cs_battle.proto",
"proto/pb/cs_bitset.proto",
"proto/pb/cs_bp.proto",
"proto/pb/cs_character.proto",
"proto/pb/cs_char_bag.proto",
"proto/pb/cs_collection.proto",
"proto/pb/cs_dialog.proto",
"proto/pb/cs_domain_depot.proto",
"proto/pb/cs_domain_development.proto",
"proto/pb/cs_doodad_group.proto",
"proto/pb/cs_dungeon.proto",
"proto/pb/cs_energy_point.proto",
"proto/pb/cs_equip.proto",
"proto/pb/cs_factory_blue_print.proto",
"proto/pb/cs_factory_chapter.proto",
"proto/pb/cs_factory_op.proto",
"proto/pb/cs_factory.proto",
"proto/pb/cs_focus_mode.proto",
"proto/pb/cs_friend_chat.proto",
"proto/pb/cs_friend.proto",
"proto/pb/cs_gacha.proto",
"proto/pb/cs_game_mechanics.proto",
"proto/pb/cs_game_mode.proto",
"proto/pb/cs_game_var.proto",
"proto/pb/cs_gem.proto",
"proto/pb/cs_global_effect.proto",
"proto/pb/cs_guide.proto",
"proto/pb/cs_item_bag.proto",
"proto/pb/cs_kite_station.proto",
"proto/pb/cs_login.proto",
"proto/pb/cs_mail.proto",
"proto/pb/cs_map_mark.proto",
"proto/pb/cs_mini_game.proto",
"proto/pb/cs_misc.proto",
"proto/pb/cs_mission.proto",
"proto/pb/cs_monster_spawner.proto",
"proto/pb/cs_monthlycard.proto",
"proto/pb/cs_msgid.proto",
"proto/pb/cs_npc.proto",
"proto/pb/cs_pay.proto",
"proto/pb/cs_proto.proto",
"proto/pb/cs_prts.proto",
"proto/pb/cs_punish.proto",
"proto/pb/cs_racing_dungeon.proto",
"proto/pb/cs_recycle_bin.proto",
"proto/pb/cs_red_dot.proto",
"proto/pb/cs_scene.proto",
"proto/pb/cs_sensitive.proto",
"proto/pb/cs_settlement.proto",
"proto/pb/cs_shop.proto",
"proto/pb/cs_sns.proto",
"proto/pb/cs_spaceship.proto",
"proto/pb/cs_statistic.proto",
"proto/pb/cs_submit_item.proto",
"proto/pb/cs_td.proto",
"proto/pb/cs_time_freeze.proto",
"proto/pb/cs_tools.proto",
"proto/pb/cs_unlock.proto",
"proto/pb/cs_wallet.proto",
"proto/pb/cs_weapon.proto",
"proto/pb/cs_week_raid.proto",
"proto/pb/cs_wiki.proto",
"proto/pb/errorcode.proto",
"proto/pb/factory_core.proto",
"proto/pb/options.proto",
"proto/pb/ss_common.proto",
};

7
build.zig.zon Normal file
View File

@@ -0,0 +1,7 @@
.{
.name = .LR_S,
.version = "0.1.0",
.minimum_zig_version = "0.16.0-dev.2368+380ea6fb5",
.paths = .{""},
.fingerprint = 0x50ff8392fab61337,
}

90
common/src/args.zig Normal file
View File

@@ -0,0 +1,90 @@
const std = @import("std");
const meta = std.meta;
const enums = std.enums;
const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
pub fn Parsed(comptime Args: type) type {
return struct {
const P = @This();
arena: ArenaAllocator,
options: Args,
pub fn deinit(p: P) void {
p.arena.deinit();
}
};
}
pub fn parseOrPrintUsageAlloc(comptime Args: type, gpa: std.mem.Allocator, args: std.process.Args) ?Parsed(Args) {
var arena: ArenaAllocator = .init(gpa);
const args_slice = args.toSlice(arena.allocator()) catch return null;
const parsed_args = parse(Args, args_slice[1..]) orelse {
printUsage(Args, args_slice[0]);
arena.deinit();
return null;
};
return .{
.arena = arena,
.options = parsed_args,
};
}
pub fn parse(comptime Args: type, args: []const [:0]const u8) ?Args {
const fields = @typeInfo(Args).@"struct".fields;
const ArgField = comptime meta.FieldEnum(Args);
const Flag = comptime blk: {
const field_names = meta.fieldNames(ArgField);
var flags: [field_names.len]u8 = undefined;
for (field_names, 0..) |name, i| {
flags[i] = name[0];
}
break :blk @Enum(u8, .exhaustive, field_names, &flags);
};
var result: Args = .{};
var arg_stack_buffer: [fields.len]Flag = undefined;
var arg_stack = std.ArrayList(Flag).initBuffer(arg_stack_buffer[0..]);
for (args) |arg| {
if (arg[0] == '-') {
for (arg[1..]) |flag| {
if (arg_stack.items.len == fields.len) return null;
arg_stack.appendAssumeCapacity(std.enums.fromInt(Flag, flag) orelse return null);
}
} else {
if (arg_stack.items.len == 0) return null;
switch (arg_stack.swapRemove(0)) {
inline else => |flag| @field(result, @tagName(flag)) = arg,
}
}
}
return if (arg_stack.items.len == 0) result else null;
}
pub fn printUsage(comptime Args: type, program_name: []const u8) void {
const usage_string = comptime blk: {
var fmt: []const u8 = "Usage: {s} [-";
for (@typeInfo(Args).@"struct".fields) |field| {
fmt = fmt ++ .{field.name[0]};
}
fmt = fmt ++ "] ";
for (@typeInfo(Args).@"struct".fields) |field| {
fmt = fmt ++ "[" ++ field.name ++ "] ";
}
break :blk fmt ++ "\n";
};
std.debug.print(usage_string, .{program_name});
}

1
common/src/io.zig Normal file
View File

@@ -0,0 +1 @@
pub const Poll = @import("io/poll.zig").Poll;

2605
common/src/io/poll.zig Normal file

File diff suppressed because it is too large Load Diff

47
common/src/mem.zig Normal file
View File

@@ -0,0 +1,47 @@
const std = @import("std");
pub fn LimitedString(comptime limit: usize) type {
return struct {
const String = @This();
pub const max_length = limit;
pub const empty: String = .{};
bytes: [max_length + 1]u8 = @splat(0),
pub fn init(value: []const u8) error{TooLongString}!String {
var string: String = .{};
try string.set(value);
return string;
}
pub fn constant(comptime value: []const u8) String {
errdefer comptime unreachable; // Constant string literal is too long.
return try comptime String.init(value);
}
pub fn view(string: *const String) [:0]const u8 {
std.debug.assert(string.bytes[max_length] == 0);
return std.mem.span(@as([*:0]const u8, @ptrCast(&string.bytes)));
}
pub fn set(string: *String, value: []const u8) error{TooLongString}!void {
if (value.len > max_length) return error.TooLongString;
@memcpy(string.bytes[0..value.len], value);
string.bytes[value.len] = 0;
}
pub fn jsonStringify(string: *const String, jws: anytype) !void {
try jws.write(string.view());
}
pub fn jsonParse(a: std.mem.Allocator, source: anytype, options: std.json.ParseOptions) !String {
return switch (try source.nextAlloc(a, options.allocate.?)) {
inline .string, .allocated_string => |string| String.init(string) catch error.LengthMismatch,
else => return error.UnexpectedToken,
};
}
};
}

3
common/src/root.zig Normal file
View File

@@ -0,0 +1,3 @@
pub const io = @import("io.zig");
pub const args = @import("args.zig");
pub const mem = @import("mem.zig");

59
confsv/src/encryption.zig Normal file
View File

@@ -0,0 +1,59 @@
const std = @import("std");
const aes = std.crypto.core.aes;
const Random = std.Random;
const Allocator = std.mem.Allocator;
pub fn CBC(comptime BlockCipher: anytype) type {
const EncryptCtx = aes.AesEncryptCtx(BlockCipher);
return struct {
const Self = @This();
const block_length = EncryptCtx.block_length;
ctx: EncryptCtx,
pub fn init(key: [BlockCipher.key_bits / 8]u8) Self {
return .{ .ctx = BlockCipher.initEnc(key) };
}
pub fn paddedLength(length: usize) usize {
return (std.math.divCeil(usize, length + 1, block_length) catch unreachable) * EncryptCtx.block_length;
}
pub fn encrypt(self: Self, dst: []u8, src: []const u8, iv: *const [block_length]u8) void {
const padded_length = paddedLength(src.len);
std.debug.assert(dst.len == padded_length); // destination buffer must hold the padded plaintext
var cv = iv.*;
var i: usize = 0;
while (i + block_length <= src.len) : (i += block_length) {
const in = src[i..][0..block_length];
for (cv[0..], in) |*x, y| x.* ^= y;
self.ctx.encrypt(&cv, &cv);
@memcpy(dst[i..][0..block_length], &cv);
}
// Last block
var in: [block_length]u8 = @splat(0);
const padding_length: u8 = @intCast(padded_length - src.len);
@memset(&in, padding_length);
@memcpy(in[0 .. src.len - i], src[i..]);
for (cv[0..], in) |*x, y| x.* ^= y;
self.ctx.encrypt(&cv, &cv);
@memcpy(dst[i..], cv[0 .. dst.len - i]);
}
};
}
// Caller owns the returned buffer.
pub fn encryptAlloc(gpa: Allocator, random: Random, key: [16]u8, data: []const u8) Allocator.Error![]u8 {
const Cipher = CBC(aes.Aes128);
const result = try gpa.alloc(u8, Cipher.block_length + Cipher.paddedLength(data.len));
random.bytes(result[0..Cipher.block_length]); // IV
const cipher: Cipher = .init(key);
cipher.encrypt(result[Cipher.block_length..], data, result[0..Cipher.block_length]);
return result;
}

95
confsv/src/http.zig Normal file
View File

@@ -0,0 +1,95 @@
const std = @import("std");
const routes = @import("routes.zig");
const Io = std.Io;
const Allocator = std.mem.Allocator;
const Server = std.http.Server;
const net = Io.net;
const request_timeout: Io.Duration = .fromSeconds(5);
pub const ConcurrencyAvailability = enum {
undetermined,
unavailable,
available,
};
pub const IoOptions = struct {
// Indicates whether Io.concurrent() should be considered.
concurrency: ConcurrencyAvailability,
// Specifies the preferred system clock.
preferred_clock: Io.Clock,
};
pub fn processClient(
io: Io,
stream: net.Stream,
gpa: Allocator,
options: IoOptions,
) Io.Cancelable!void {
const log = std.log.scoped(.http);
defer stream.close(io);
log.debug("new connection from '{f}'", .{stream.socket.address});
defer log.debug("client from '{f}' disconnected", .{stream.socket.address});
var recv_buffer: [8192]u8 = undefined;
var send_buffer: [8192]u8 = undefined;
var reader = stream.reader(io, &recv_buffer);
var writer = stream.writer(io, &send_buffer);
var server: Server = .init(&reader.interface, &writer.interface);
var request = receiveRequest(io, options, &server) catch |err| switch (err) {
error.Canceled, error.ConcurrencyUnavailable => return,
else => |e| {
log.err("failed to receive request from '{f}': {t}", .{ stream.socket.address, e });
return;
},
};
log.info(
"received request from '{f}': {s} ({t})",
.{ stream.socket.address, request.head.target, request.head.method },
);
routes.process(io, gpa, &request) catch |err| switch (err) {
error.Canceled => return,
error.RouteNotFound => {
log.warn(
"route '{s}' not found, requested by: '{f}'",
.{ request.head.target, stream.socket.address },
);
request.respond("Not Found", .{ .status = .not_found }) catch return;
},
error.MethodNotAllowed => request.respond("Method Not Allowed", .{ .status = .method_not_allowed }) catch
return,
else => |e| log.err(
"failed to process request from '{f}': {t}",
.{ stream.socket.address, e },
),
};
}
fn receiveRequest(io: Io, options: IoOptions, server: *Server) !Server.Request {
return switch (options.concurrency) {
.undetermined => unreachable,
.unavailable => try server.receiveHead(),
.available => {
var receive = try io.concurrent(Server.receiveHead, .{server});
errdefer _ = receive.cancel(io) catch {};
var sleep = try io.concurrent(Io.sleep, .{ io, request_timeout, options.preferred_clock });
defer sleep.cancel(io) catch {};
return switch (try io.select(.{
.receive = &receive,
.sleep = &sleep,
})) {
.sleep => try receive.cancel(io),
.receive => |request| request,
};
},
};
}

122
confsv/src/main.zig Normal file
View File

@@ -0,0 +1,122 @@
const std = @import("std");
const common = @import("common");
const http = @import("http.zig");
const Io = std.Io;
const Init = std.process.Init;
const Allocator = std.mem.Allocator;
const ConcurrencyAvailability = http.ConcurrencyAvailability;
const net = Io.net;
const assert = std.debug.assert;
const log = std.log.scoped(.confsv);
const Options = struct {
listen_address: []const u8 = "127.0.0.1:10001",
};
fn start(init: Init.Minimal, io: Io, gpa: Allocator) u8 {
const args = common.args.parseOrPrintUsageAlloc(Options, gpa, init.args) orelse return 1;
defer args.deinit();
std.debug.print(
\\ __ ____ _____
\\ / / / __ \ / ___/
\\ / / / /_/ /_____\__ \
\\ / /___/ _, _/_____/__/ /
\\/_____/_/ |_| /____/
\\
, .{});
const listen_address = net.IpAddress.parseLiteral(args.options.listen_address) catch {
log.err("Invalid listen address specified.", .{});
return 1;
};
var server = listen_address.listen(io, .{ .reuse_address = true }) catch |err| switch (err) {
error.AddressInUse => {
log.err(
"Address '{f}' is in use. Another instance of this server might be already running.",
.{listen_address},
);
return 1;
},
else => |e| {
log.err("Failed to listen at '{f}': {t}", .{ listen_address, e });
return 1;
},
};
defer server.deinit(io);
var http_processors: Io.Group = .init;
defer http_processors.cancel(io);
var preferred_clock: Io.Clock = .awake; // Prefer monotonic clock by default. Fallback to realtime.
var concurrency_availability: ConcurrencyAvailability = .undetermined;
log.info("listening at {f}", .{listen_address});
defer log.info("shutting down...", .{});
accept_loop: while (true) {
const stream = server.accept(io) catch |err| switch (err) {
error.Canceled => break, // Shutdown requested
error.ProcessFdQuotaExceeded, error.SystemFdQuotaExceeded, error.SystemResources => {
// System is overloaded. Stop accepting new connections for now.
while (true) {
if (io.sleep(.fromSeconds(1), preferred_clock)) break else |sleep_err| switch (sleep_err) {
error.Canceled => break :accept_loop, // Shutdown requested
error.UnsupportedClock => preferred_clock = if (preferred_clock == .awake)
.real
else
continue :accept_loop, // No clock available.
error.Unexpected => continue :accept_loop, // Sleep is unimportant then.
}
}
continue;
},
else => |e| { // Something else happened. We probably want to report this and continue.
log.err("TCP accept failed: {t}", .{e});
continue;
},
};
var io_options: http.IoOptions = .{
.preferred_clock = preferred_clock,
.concurrency = .available,
};
if (http_processors.concurrent(io, http.processClient, .{ io, stream, gpa, io_options })) {
concurrency_availability = .available;
} else |err| switch (err) {
error.ConcurrencyUnavailable => switch (concurrency_availability) {
.available => stream.close(io), // Can't process more connections atm.
.unavailable, .undetermined => {
// The environment doesn't support concurrency.
if (concurrency_availability != .unavailable)
log.warn("Environment doesn't support concurrency. One request at a time will be processed.", .{});
concurrency_availability = .unavailable;
io_options.concurrency = .unavailable;
http_processors.async(io, http.processClient, .{ io, stream, gpa, io_options });
},
},
}
}
return 0;
}
pub fn main(init: Init.Minimal) u8 {
var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
defer assert(.ok == debug_allocator.deinit());
const gpa = debug_allocator.allocator();
var poll: common.io.Poll(.{}) = .init(gpa);
defer poll.deinit();
const io = poll.io();
return start(init, io, gpa);
}

66
confsv/src/routes.zig Normal file
View File

@@ -0,0 +1,66 @@
const std = @import("std");
const Io = std.Io;
const Allocator = std.mem.Allocator;
const Request = std.http.Server.Request;
const version = @import("routes/version.zig");
const config = @import("routes/config.zig");
const server_list = @import("routes/server_list.zig");
const routes = .{
.{ "/api/game/get_latest", version.getOnlineAppVersion },
.{ "/api/remote_config/v2/1003/prod-obt/default/Windows/game_config", config.getRemoteGameConfig },
.{ "/get_server_list", server_list.get },
.{ "/api/game/get_latest_resources", version.getLatestResources },
};
const Route = blk: {
var field_names: [routes.len][:0]const u8 = undefined;
var field_values: [routes.len]usize = undefined;
for (routes, 0..) |route, i| {
const path, _ = route;
field_names[i] = path;
field_values[i] = i;
}
break :blk @Enum(usize, .exhaustive, &field_names, &field_values);
};
pub const ProcessError = error{ RouteNotFound, MethodNotAllowed } || Error;
pub const Error = Io.Cancelable || Request.ExpectContinueError || Allocator.Error;
pub fn process(
io: Io,
gpa: Allocator,
request: *Request,
) ProcessError!void {
const log = std.log.scoped(.routing);
switch (request.head.method) {
.GET, .POST => {},
else => |method| {
log.debug("method not allowed: {t}", .{method});
return error.MethodNotAllowed;
},
}
const path = if (std.mem.findScalar(u8, request.head.target, '?')) |query_i|
request.head.target[0..query_i]
else
request.head.target;
const route = std.meta.stringToEnum(Route, path) orelse
return error.RouteNotFound;
switch (route) {
inline else => |tag| inline for (routes) |pair| {
const name, const processFn = pair;
if (comptime std.mem.eql(u8, name, @tagName(tag))) {
try processFn(io, gpa, request);
break;
}
} else comptime unreachable,
}
}

View File

@@ -0,0 +1,35 @@
const std = @import("std");
const routes = @import("../routes.zig");
const encryption = @import("../encryption.zig");
const Io = std.Io;
const Allocator = std.mem.Allocator;
const Request = std.http.Server.Request;
const Base64Encoder = std.base64.standard.Encoder;
const config_key = [16]u8{ 0x71, 0x99, 0xBC, 0xE9, 0x47, 0xC3, 0xA7, 0xF9, 0x20, 0x27, 0x76, 0xA0, 0x2B, 0x1F, 0x87, 0x64 };
pub fn getRemoteGameConfig(io: Io, gpa: Allocator, request: *Request) routes.Error!void {
var response_buffer: [1024]u8 = undefined;
var body = try request.respondStreaming(&response_buffer, .{});
const response: RemoteGameCfg = .{
.enableHotUpdate = false,
.mockLogin = true,
};
const content = try std.fmt.allocPrint(gpa, "{f}", .{std.json.fmt(response, .{})});
defer gpa.free(content);
const io_source: std.Random.IoSource = .{ .io = io };
const ciphertext = try encryption.encryptAlloc(gpa, io_source.interface(), config_key, content);
defer gpa.free(ciphertext);
try body.writer.print("{b64}", .{ciphertext});
try body.end();
}
const RemoteGameCfg = struct {
enableHotUpdate: bool,
mockLogin: bool,
};

View File

@@ -0,0 +1,31 @@
const std = @import("std");
const routes = @import("../routes.zig");
const Io = std.Io;
const Allocator = std.mem.Allocator;
const Request = std.http.Server.Request;
pub fn get(io: Io, gpa: Allocator, request: *Request) routes.Error!void {
_ = io;
_ = gpa;
var response_buffer: [1024]u8 = undefined;
var body = try request.respondStreaming(&response_buffer, .{});
const response: ServerList = .{
.servers = &.{.{ .name = "LR", .addr = "127.0.0.1", .port = 30000 }},
};
try body.writer.print("{f}", .{std.json.fmt(response, .{})});
try body.end();
}
const ServerDesc = struct {
name: []const u8,
addr: []const u8,
port: i32,
};
const ServerList = struct {
servers: []const ServerDesc,
};

View File

@@ -0,0 +1,99 @@
const std = @import("std");
const routes = @import("../routes.zig");
const Io = std.Io;
const Allocator = std.mem.Allocator;
const Request = std.http.Server.Request;
pub fn getOnlineAppVersion(io: Io, gpa: Allocator, request: *Request) routes.Error!void {
_ = io;
_ = gpa;
var response_buffer: [1024]u8 = undefined;
var body = try request.respondStreaming(&response_buffer, .{});
const response: OnlineAppVersionResponse = .{
.action = 0,
.version = "1.0.14",
.request_version = "1.0.14",
.pkg = .{
.packs = &.{},
.total_size = 0,
.file_path = "https://beyond.hg-cdn.com/YDUTE5gscDZ229CW/1.0/update/6/6/Windows/1.0.14_Qk2mXHuAH1JWKF37/files",
.url = "",
.md5 = "",
.package_size = "0",
.file_id = "0",
.sub_channel = "6",
.game_files_md5 = "c36ad08e5d4a7cfd580228971d7a4563",
},
.patch = null,
.state = 0,
.launcher_action = 0,
};
try body.writer.print("{f}", .{std.json.fmt(response, .{})});
try body.end();
}
pub fn getLatestResources(io: Io, gpa: Allocator, request: *Request) routes.Error!void {
_ = io;
_ = gpa;
var response_buffer: [1024]u8 = undefined;
var body = try request.respondStreaming(&response_buffer, .{});
const response: ResVersionData = .{
.resources = &.{
.{
.name = "main",
.version = "5439650-20",
.path = "https://beyond.hg-cdn.com/YDUTE5gscDZ229CW/1.0/resource/Windows/main/5439650-20_PEuAF7OENsVNjc1L/files",
},
.{
.name = "initial",
.version = "5439650-20",
.path = "https://beyond.hg-cdn.com/YDUTE5gscDZ229CW/1.0/resource/Windows/initial/5439650-20_2HA0Xw0M0B0XWdBV/files",
},
},
.configs = "{\"kick_flag\":false}",
.res_version = "initial_5439650-20_main_5439650-20",
.patch_index_path = "",
.domain = "https://beyond.hg-cdn.com",
};
try body.writer.print("{f}", .{std.json.fmt(response, .{})});
try body.end();
}
const OnlineAppVersionResponse = struct {
action: i32,
version: []const u8,
request_version: []const u8,
pkg: struct {
packs: []const struct {},
total_size: u64,
file_path: []const u8,
url: []const u8,
md5: []const u8,
package_size: []const u8,
file_id: []const u8,
sub_channel: []const u8,
game_files_md5: []const u8,
},
patch: ?struct {},
state: u32,
launcher_action: u32,
};
const ResVersionData = struct {
resources: []const struct {
name: []const u8,
version: []const u8,
path: []const u8,
},
configs: []const u8,
res_version: []const u8,
patch_index_path: []const u8,
domain: []const u8,
};

16
envrc Executable file
View File

@@ -0,0 +1,16 @@
#!/bin/sh
# DEPENDS: curl, tar, xz, realpath
ZIG_VERSION="0.16.0-dev.2368+380ea6fb5"
ZIG_PLATFORM="x86_64-linux"
ZIG_DIST="zig-${ZIG_PLATFORM}-${ZIG_VERSION}"
ZIG_DIR="./.direnv/${ZIG_DIST}/"
if [ ! -d $ZIG_DIR ]; then
mkdir -p ./.direnv
curl -L "https://ziglang.org/builds/{$ZIG_DIST}.tar.xz" |
tar xfJ - -C ./.direnv/ &&
export PATH=$(realpath $ZIG_DIR):$PATH
else
export PATH=$(realpath $ZIG_DIR):$PATH
fi

112
gamesv/src/Assets.zig Normal file
View File

@@ -0,0 +1,112 @@
const Assets = @This();
const std = @import("std");
pub const configs = @import("Assets/configs.zig");
pub const Tables = @import("Assets/Tables.zig");
pub const CharacterSkillMap = @import("Assets/CharacterSkillMap.zig");
const Io = std.Io;
const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
const meta = std.meta;
const log = std.log.scoped(.assets);
arena: ArenaAllocator,
owned_tables: Tables.Owned,
char_skill_map: CharacterSkillMap,
str_to_num_dicts: IndexDictionaries.StrToNum,
num_to_str_dicts: IndexDictionaries.NumToStr,
common_skill_config: configs.CommonSkillConfig,
level_config_table: std.StringArrayHashMapUnmanaged(configs.LevelConfig),
pub const IdGroup = enum {
char_id,
item_id,
};
const IndexDictionaries = blk: {
const names = meta.fieldNames(IdGroup);
break :blk .{
.StrToNum = @Struct(.auto, null, names, &@splat(*const Tables.StrToNum), &@splat(.{})),
.NumToStr = @Struct(.auto, null, names, &@splat(*const Tables.NumToStr), &@splat(.{})),
};
};
pub fn load(io: Io, gpa: Allocator) !Assets {
const owned_tables = try Tables.load(io, gpa);
errdefer owned_tables.deinit();
var arena: ArenaAllocator = .init(gpa);
errdefer arena.deinit();
const char_skill_map = try CharacterSkillMap.init(arena.allocator(), &owned_tables.tables);
var str_to_num_dicts: IndexDictionaries.StrToNum = undefined;
var num_to_str_dicts: IndexDictionaries.NumToStr = undefined;
inline for (@typeInfo(IdGroup).@"enum".fields) |field| {
@field(str_to_num_dicts, field.name) = owned_tables.tables.str_to_num.getPtr(field.name) orelse {
log.err("missing str-to-num dictionary: " ++ field.name, .{});
return error.MissingData;
};
@field(num_to_str_dicts, field.name) = owned_tables.tables.num_to_str.getPtr(field.name) orelse {
log.err("missing num-to-str dictionary: " ++ field.name, .{});
return error.MissingData;
};
}
const common_skill_config = try configs.loadJsonConfig(
configs.CommonSkillConfig,
io,
arena.allocator(),
configs.CommonSkillConfig.file,
);
const level_config_table = try configs.loadJsonConfig(
std.json.ArrayHashMap(configs.LevelConfig),
io,
arena.allocator(),
"LevelConfigTable.json",
);
return .{
.arena = arena,
.owned_tables = owned_tables,
.char_skill_map = char_skill_map,
.str_to_num_dicts = str_to_num_dicts,
.num_to_str_dicts = num_to_str_dicts,
.common_skill_config = common_skill_config,
.level_config_table = level_config_table.map,
};
}
pub fn deinit(assets: *Assets) void {
assets.owned_tables.deinit();
assets.arena.deinit();
}
pub inline fn table(
assets: *const Assets,
comptime t: std.meta.FieldEnum(Tables),
) *const @FieldType(Tables, @tagName(t)) {
return &@field(assets.owned_tables.tables, @tagName(t));
}
pub fn strToNum(
assets: *const Assets,
comptime group: IdGroup,
str: []const u8,
) ?i32 {
const str_to_num = @field(assets.str_to_num_dicts, @tagName(group));
return str_to_num.dic.map.get(str);
}
pub fn numToStr(
assets: *const Assets,
comptime group: IdGroup,
num: i32,
) ?[]const u8 {
const num_to_str = @field(assets.num_to_str_dicts, @tagName(group));
return num_to_str.dic.map.get(num);
}

View File

@@ -0,0 +1,79 @@
// Maps character ids to list of their skills.
const CharacterSkillMap = @This();
const std = @import("std");
const Tables = @import("Tables.zig");
const Allocator = std.mem.Allocator;
const log = std.log.scoped(.skill_map);
map: std.StringArrayHashMapUnmanaged(CharacterSkills),
const CharacterSkills = struct {
all_skills: []const []const u8,
combo_skill: []const u8,
normal_skill: []const u8,
attack_skill: []const u8,
ultimate_skill: []const u8,
};
pub fn init(arena: Allocator, tables: *const Tables) !CharacterSkillMap {
var result: CharacterSkillMap = .{ .map = .empty };
for (tables.character.keys()) |char_id| {
var skill_ids: std.ArrayList([]const u8) = .empty;
var combo_skill: ?[]const u8 = null;
var normal_skill: ?[]const u8 = null;
var attack_skill: ?[]const u8 = null;
var ultimate_skill: ?[]const u8 = null;
for (tables.skill_patch.keys()) |skill_id| {
if (std.mem.startsWith(u8, skill_id, char_id)) {
try skill_ids.append(arena, skill_id);
if (std.mem.find(u8, skill_id, "normal_skill") != null) {
normal_skill = skill_id;
} else if (std.mem.find(u8, skill_id, "combo_skill") != null) {
combo_skill = skill_id;
} else if (std.mem.find(u8, skill_id, "ultimate_skill") != null) {
ultimate_skill = skill_id;
} else if (std.mem.find(u8, skill_id, "_attack1") != null) {
attack_skill = skill_id;
}
}
}
if (skill_ids.items.len == 0) // Dummy Character
continue;
if (combo_skill == null) {
log.err("no combo_skill for {s}", .{char_id});
return error.MalformedData;
}
if (normal_skill == null) {
log.err("no normal_skill for {s}", .{char_id});
return error.MalformedData;
}
if (attack_skill == null) {
log.err("no attack_skill for {s}", .{char_id});
return error.MalformedData;
}
if (ultimate_skill == null) {
log.err("no ultimate_skill for {s}", .{char_id});
return error.MalformedData;
}
try result.map.put(arena, char_id, .{
.combo_skill = combo_skill.?,
.normal_skill = normal_skill.?,
.attack_skill = attack_skill.?,
.ultimate_skill = ultimate_skill.?,
.all_skills = skill_ids.items,
});
}
return result;
}

View File

@@ -0,0 +1,223 @@
const Tables = @This();
const std = @import("std");
const json = std.json;
const Io = std.Io;
const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
const StringArrayHashMap = std.StringArrayHashMapUnmanaged;
const log = std.log.scoped(.tables);
pub const CharacterData = @import("Tables/CharacterData.zig");
pub const SkillPatchDataBundleList = @import("Tables/SkillPatchDataBundleList.zig");
pub const WeaponBasicData = @import("Tables/WeaponBasicData.zig");
pub const CharWpnRecommendData = @import("Tables/CharWpnRecommendData.zig");
pub const DomainData = @import("Tables/DomainData.zig");
pub const StrToNum = struct {
pub const file = "StrIdNumTable.json";
dic: StringTable(i32),
};
pub const NumToStr = struct {
pub const file = "NumIdStrTable.json";
dic: IntTable(i32, []const u8),
};
character: StringArrayHashMap(CharacterData),
skill_patch: StringArrayHashMap(SkillPatchDataBundleList),
weapon_basic: StringArrayHashMap(WeaponBasicData),
str_to_num: StringArrayHashMap(StrToNum),
num_to_str: StringArrayHashMap(NumToStr),
char_wpn_recommend: StringArrayHashMap(CharWpnRecommendData),
domain_data: StringArrayHashMap(DomainData),
pub const LoadError = error{
NotStarted,
ReadFail,
ParseFail,
} || Io.Cancelable || Allocator.Error;
const LoadResults = blk: {
var field_names: []const []const u8 = &.{};
for (@typeInfo(Tables).@"struct".fields) |field| {
field_names = field_names ++ .{field.name};
}
var field_types: [field_names.len]type = undefined;
var field_attrs: [field_names.len]std.builtin.Type.StructField.Attributes = undefined;
for (field_names, 0..) |name, i| {
field_types[i] = LoadError!@FieldType(Tables, name);
field_attrs[i] = .{
.default_value_ptr = &@as(LoadError!@FieldType(Tables, name), LoadError.NotStarted),
};
}
break :blk @Struct(.auto, null, field_names, &field_types, &field_attrs);
};
pub const Owned = struct {
tables: Tables,
arenas: [@typeInfo(Tables).@"struct".fields.len]?ArenaAllocator,
pub fn deinit(owned: Owned) void {
for (owned.arenas) |maybe_arena| if (maybe_arena) |arena| {
arena.deinit();
};
}
};
pub fn load(io: Io, gpa: Allocator) (error{LoadFailed} || Io.Cancelable)!Owned {
var owned: Owned = .{
.tables = undefined,
.arenas = @splat(null),
};
errdefer owned.deinit();
var loaders: Io.Group = .init;
defer loaders.cancel(io);
var results: LoadResults = .{};
inline for (@typeInfo(Tables).@"struct".fields, 0..) |field, i| {
owned.arenas[i] = .init(gpa);
loaders.async(
io,
Loader(field.type).startLoading,
.{ &@field(results, field.name), io, owned.arenas[i].?.allocator() },
);
}
try loaders.await(io);
var has_errors = false;
inline for (@typeInfo(Tables).@"struct".fields) |field| {
if (@field(results, field.name)) |table| {
@field(owned.tables, field.name) = table;
} else |err| switch (err) {
error.Canceled => return error.Canceled,
else => |e| {
has_errors = true;
log.err("failed to load table '{s}': {t}", .{ field.name, e });
},
}
}
return if (!has_errors) owned else error.LoadFailed;
}
fn Loader(comptime Table: type) type {
return struct {
pub fn startLoading(
result: *LoadError!Table,
io: Io,
arena: Allocator,
) Io.Cancelable!void {
const Value = @FieldType(Table.KV, "value");
const file = Io.Dir.cwd().openFile(io, "assets/tables/" ++ Value.file, .{}) catch |err| switch (err) {
error.Canceled => return error.Canceled,
else => {
result.* = LoadError.ReadFail;
return;
},
};
defer file.close(io);
var buffer: [16384]u8 = undefined;
var file_reader = file.reader(io, &buffer);
var json_reader: json.Reader = .init(arena, &file_reader.interface);
defer json_reader.deinit();
if (json.parseFromTokenSourceLeaky(
StringTable(Value),
arena,
&json_reader,
.{ .ignore_unknown_fields = true },
)) |st| {
result.* = st.map;
} else |_| {
result.* = LoadError.ParseFail;
}
}
};
}
// HashMap wrapper to deserialize from an array of ["Key": "String", "Value": {...}]
fn StringTable(comptime V: type) type {
return struct {
const ST = @This();
map: StringArrayHashMap(V) = .empty,
const IntermediateKV = struct {
Key: []const u8,
Value: V,
};
pub fn jsonParse(a: std.mem.Allocator, source: anytype, options: json.ParseOptions) !ST {
if (try source.nextAlloc(a, options.allocate.?) != .array_begin)
return error.UnexpectedToken;
var map: StringArrayHashMap(V) = .empty;
errdefer map.deinit(a);
while (source.peekNextTokenType()) |t| switch (t) {
.object_begin => {
const kv = json.innerParse(IntermediateKV, a, source, options) catch unreachable;
try map.put(a, kv.Key, kv.Value);
},
.array_end => {
_ = try source.next();
break;
},
else => return error.UnexpectedToken,
} else |err| return err;
return .{ .map = map };
}
};
}
// HashMap wrapper to deserialize from an array of ["Key": Int, "Value": {...}]
fn IntTable(comptime K: type, comptime V: type) type {
return struct {
const ST = @This();
map: std.AutoArrayHashMapUnmanaged(K, V) = .empty,
const IntermediateKV = struct {
Key: K,
Value: V,
};
pub fn jsonParse(a: std.mem.Allocator, source: anytype, options: json.ParseOptions) !ST {
if (try source.nextAlloc(a, options.allocate.?) != .array_begin)
return error.UnexpectedToken;
var map: std.AutoArrayHashMapUnmanaged(K, V) = .empty;
errdefer map.deinit(a);
while (source.peekNextTokenType()) |t| switch (t) {
.object_begin => {
const kv = json.innerParse(IntermediateKV, a, source, options) catch unreachable;
try map.put(a, kv.Key, kv.Value);
},
.array_end => {
_ = try source.next();
break;
},
else => return error.UnexpectedToken,
} else |err| return err;
return .{ .map = map };
}
};
}

View File

@@ -0,0 +1,6 @@
pub const file = "CharWpnRecommendTable.json";
charId: []const u8,
weaponIds1: []const []const u8,
weaponIds2: []const []const u8,
weaponIds3: []const []const u8,

View File

@@ -0,0 +1,120 @@
pub const file = "CharacterTable.json";
pub const AttributeDataPack = struct {
Attribute: AttributeData,
breakStage: i32,
};
pub const AttributeData = struct {
attrs: []const AttributePair,
};
pub const AttributePair = struct {
attrType: AttributeType,
attrValue: f64,
};
pub const AttributeType = enum(i32) {
level = 0,
max_hp = 1,
atk = 2,
def = 3,
physical_damage_taken_scalar = 4,
fire_damage_taken_scalar = 5,
pulse_damage_taken_scalar = 6,
cryst_damage_taken_scalar = 7,
weight = 8,
critical_rate = 9,
critical_damage_increase = 10,
hatred = 11,
normal_attack_range = 12,
move_speed_scalar = 13,
turn_rate_scalar = 14,
attack_rate = 15,
skill_cooldown_scalar = 16,
normal_attack_damage_increase = 17,
hp_recovery_per_sec = 18,
hp_recovery_per_sec_by_max_hp_ratio = 19,
max_poise = 20,
poise_rec_time = 21,
max_ultimate_sp = 22,
damage_taken_scalar_with_poise = 23,
poise_damage_taken_scalar = 24,
physical_infliction_damage_scalar = 25,
poise_damage_output_scalar = 26,
breaking_attack_damage_taken_scalar = 27,
ultimate_skill_damage_increase = 28,
heal_output_increase = 29,
heal_taken_increase = 30,
poise_rec_time_scalar = 31,
normal_skill_damage_increase = 32,
combo_skill_damage_increase = 33,
knock_down_time_addition = 34,
fire_burst_damage_increase = 35,
pulse_burst_damage_increase = 36,
cryst_burst_damage_increase = 37,
natural_burst_damage_increase = 38,
str = 39,
agi = 40,
wisd = 41,
will = 42,
life_steal = 43,
ultimate_sp_gain_scalar = 44,
atb_cost_addition = 45,
skill_cooldown_addition = 46,
combo_skill_cooldown_scalar = 47,
natural_damage_taken_scalar = 48,
ignite_damage_scalar = 49,
physical_damage_increase = 50,
fire_damage_increase = 51,
pulse_damage_increase = 52,
cryst_damage_increase = 53,
natural_damage_increase = 54,
ether_damage_increase = 55,
fire_abnormal_damage_increase = 56,
pulse_abnormal_damage_increase = 57,
cryst_abnormal_damage_increase = 58,
natural_abnormal_damage_increase = 59,
ether_damage_taken_scalar = 60,
damage_to_broken_unit_increase = 61,
weakness_dmg_scalar = 62,
shelter_dmg_scalar = 63,
physical_enhanced_dmg_increase = 64,
fire_enhanced_dmg_increase = 65,
pulse_enhanced_dmg_increase = 66,
cryst_enhanced_dmg_increase = 67,
natural_enhanced_dmg_increase = 68,
ether_enhanced_dmg_increase = 69,
physical_vulnerable_dmg_increase = 70,
fire_vulnerable_dmg_increase = 71,
pulse_vulnerable_dmg_increase = 72,
cryst_vulnerable_dmg_increase = 73,
natural_vulnerable_dmg_increase = 74,
ether_vulnerable_dmg_increase = 75,
atk_increase_factor_from_str = 76,
atk_increase_factor_from_agi = 77,
atk_increase_factor_from_wisd = 78,
atk_increase_factor_from_will = 79,
physical_dmg_resist_scalar = 80,
natural_dmg_resist_scalar = 81,
cryst_dmg_resist_scalar = 82,
pulse_dmg_resist_scalar = 83,
fire_dmg_resist_scalar = 84,
ether_dmg_resist_scalar = 85,
slow_action_speed_scalar = 86,
physical_and_spell_infliction_enhance = 87,
shield_output_increase = 88,
shield_taken_increase = 89,
};
attributes: []const AttributeDataPack,
charBattleTagIds: []const []const u8,
charId: []const u8,
mainAttrType: i32,
profession: u32,
rarity: u32,
resilienceDeductionFactor: f32,
sortOrder: u32,
subAttrType: i32,
superArmor: u32,
weaponType: u32,

View File

@@ -0,0 +1,4 @@
pub const file = "DomainDataTable.json";
domainId: []const u8,
levelGroup: []const []const u8,

View File

@@ -0,0 +1,12 @@
pub const file = "SkillPatchTable.json";
SkillPatchDataBundle: []const SkillPatchData,
pub const SkillPatchData = struct {
coolDown: f32,
costType: u32,
costValue: f32,
level: u32,
maxChargeTime: u32,
skillId: []const u8,
};

View File

@@ -0,0 +1,8 @@
pub const file = "WeaponBasicTable.json";
weaponId: []const u8,
weaponPotentialSkill: []const u8,
weaponSkillList: []const []const u8,
maxLv: u32,
rarity: u32,
weaponType: u32,

View File

@@ -0,0 +1,34 @@
const std = @import("std");
const json = std.json;
pub const CommonSkillConfig = @import("configs/CommonSkillConfig.zig");
pub const LevelConfig = @import("configs/LevelConfig.zig");
const Io = std.Io;
const Allocator = std.mem.Allocator;
pub fn loadJsonConfig(
comptime T: type,
io: Io,
arena: Allocator,
filename: []const u8,
) !T {
const config_dir = try Io.Dir.cwd().openDir(io, "assets/configs/", .{});
defer config_dir.close(io);
const file = try config_dir.openFile(io, filename, .{});
defer file.close(io);
var buffer: [16384]u8 = undefined;
var file_reader = file.reader(io, &buffer);
var json_reader: json.Reader = .init(arena, &file_reader.interface);
defer json_reader.deinit();
return try json.parseFromTokenSourceLeaky(
T,
arena,
&json_reader,
.{ .ignore_unknown_fields = true },
);
}

View File

@@ -0,0 +1,14 @@
pub const file = "CommonSkillConfig.json";
config: struct {
Character: SkillConfigList,
},
pub const SkillConfigList = struct {
skillConfigs: []const SkillConfig,
};
pub const SkillConfig = struct {
skillId: []const u8,
skillType: u32,
};

View File

@@ -0,0 +1,16 @@
id: []const u8,
idNum: i32,
scope: u8,
isSeamless: bool,
mapIdStr: []const u8,
isDimensionLevel: bool,
dimensionSourceLevelId: []const u8,
startPos: Vector,
playerInitPos: Vector,
playerInitRot: Vector,
pub const Vector = struct {
x: f32,
y: f32,
z: f32,
};

217
gamesv/src/Session.zig Normal file
View File

@@ -0,0 +1,217 @@
const Session = @This();
const std = @import("std");
const proto = @import("proto");
const logic = @import("logic.zig");
const network = @import("network.zig");
const auth = @import("Session/auth.zig");
const fs = @import("fs.zig");
const Assets = @import("Assets.zig");
const Io = std.Io;
const Allocator = std.mem.Allocator;
const Crc32 = std.hash.Crc32;
const pb = proto.pb;
const net = Io.net;
const first_request_timeout: Io.Duration = .fromSeconds(5);
const subsequent_request_timeout: Io.Duration = .fromSeconds(30);
pub const ConcurrencyAvailability = enum {
undetermined,
unavailable,
available,
};
pub const IoOptions = struct {
// Indicates whether Io.concurrent() should be considered.
concurrency: ConcurrencyAvailability,
// Specifies the preferred system clock.
preferred_clock: Io.Clock,
};
writer: *Io.Writer,
client_seq_id: u64 = 0,
server_seq_id: u64 = 0,
pub fn process(
io: Io,
gpa: Allocator,
assets: *const Assets,
stream: net.Stream,
options: IoOptions,
) Io.Cancelable!void {
const log = std.log.scoped(.net);
defer stream.close(io);
log.debug("new connection from '{f}'", .{stream.socket.address});
defer log.debug("client from '{f}' disconnected", .{stream.socket.address});
var recv_buffer: [64 * 1024]u8 = undefined;
var send_buffer: [4 * 1024]u8 = undefined;
var reader = stream.reader(io, &recv_buffer);
var writer = stream.writer(io, &send_buffer);
var session: Session = .{
.writer = &writer.interface,
};
var world: ?logic.World = null;
defer if (world) |*w| w.deinit(gpa);
var receive_timeout = first_request_timeout;
while (receiveNetRequest(io, &reader.interface, receive_timeout, options)) |request| {
session.client_seq_id = request.head.up_seqid;
log.debug("received header: {any}", .{request.head});
log.debug("received body: {X}", .{request.body});
if (world) |*w| {
logic.messaging.process(gpa, w, &request) catch |err| switch (err) {
error.MissingHandler => log.warn("no handler for {t}", .{request.msgId()}),
error.DecodeFailed => {
log.err(
"received malformed message of type '{t}' from '{f}', disconnecting",
.{ request.msgId(), stream.socket.address },
);
return;
},
error.Canceled, error.WriteFailed, error.OutOfMemory => return,
};
} else {
const result = processFirstRequest(io, gpa, &session, &request) catch |err| switch (err) {
error.UnexpectedMessage => {
log.err(
"received unexpected first message '{t}' from '{f}', disconnecting",
.{ request.msgId(), stream.socket.address },
);
return;
},
error.DecodeFailed => {
log.err(
"received malformed login request from '{t}', disconnecting",
.{stream.socket.address},
);
return;
},
error.LoginFailed => {
log.err(
"session from '{f}' has failed to login, disconnecting",
.{stream.socket.address},
);
return;
},
// Regardless which one, the session is invalidated by now.
error.WriteFailed, error.OutOfMemory => return,
error.Canceled => |e| return e,
};
const player = fs.persistence.loadPlayer(io, gpa, assets, result.uid) catch |err| switch (err) {
error.Canceled => |e| return e,
else => |e| {
log.err("failed to load data for player with uid {d}: {t}, disconnecting", .{ result.uid, e });
return;
},
};
log.info(
"client from '{f}' has successfully logged into account with uid: {d}",
.{ stream.socket.address, result.uid },
);
world = logic.World.init(&session, assets, result.uid, player, gpa, io);
receive_timeout = subsequent_request_timeout;
logic.systems.triggerEvent(.{ .login = .{} }, &world.?, gpa) catch |err| switch (err) {
error.Canceled, error.OutOfMemory, error.WriteFailed => return,
};
}
} else |err| switch (err) {
error.Canceled,
error.ConcurrencyUnavailable,
error.ReadFailed,
error.EndOfStream,
=> {},
error.HeadDecodeError,
error.ChecksumMismatch,
error.InvalidMessageId,
=> |e| log.err(
"failed to receive request from '{f}': {t}",
.{ stream.socket.address, e },
),
}
}
pub const SendError = Io.Writer.Error;
pub fn send(session: *Session, message: anytype) SendError!void {
var buffer: [128]u8 = undefined;
var discarding: Io.Writer.Discarding = .init("");
var hashed: Io.Writer.Hashed(Crc32) = .initHasher(&discarding.writer, .init(), &buffer);
proto.encodeMessage(&hashed.writer, message) catch unreachable; // Discarding + Hashed can't fail.
hashed.writer.flush() catch unreachable;
const head: pb.CSHead = .{
.msgid = @intFromEnum(proto.messageId(@TypeOf(message))),
.up_seqid = session.client_seq_id, // Why? No idea. But FlushSync kills itself if it's not like that
.down_seqid = 0,
.total_pack_count = 0,
.checksum = hashed.hasher.final(),
};
const head_size = proto.encodingLength(head);
const body_size = discarding.fullCount();
try session.writer.writeInt(u8, @intCast(head_size), .little);
try session.writer.writeInt(u16, @intCast(body_size), .little);
try proto.encodeMessage(session.writer, head);
try proto.encodeMessage(session.writer, message);
try session.writer.flush();
session.server_seq_id += 1;
}
fn processFirstRequest(io: Io, gpa: Allocator, session: *Session, request: *const network.Request) !auth.Result {
if (request.msgId() != .cs_login)
return error.UnexpectedMessage;
var reader: Io.Reader = .fixed(request.body);
var arena: std.heap.ArenaAllocator = .init(gpa);
defer arena.deinit();
const cs_login = proto.decodeMessage(&reader, arena.allocator(), pb.CS_LOGIN) catch
return error.DecodeFailed;
return try auth.processLoginRequest(io, session, &cs_login);
}
const ReceiveError = Io.Cancelable || Io.ConcurrentError || network.Request.ReadError;
fn receiveNetRequest(
io: Io,
reader: *Io.Reader,
timeout: Io.Duration,
options: IoOptions,
) ReceiveError!network.Request {
return switch (options.concurrency) {
.undetermined => unreachable,
.unavailable => try network.Request.read(reader),
.available => {
var receive = try io.concurrent(network.Request.read, .{reader});
errdefer _ = receive.cancel(io) catch {};
var sleep = try io.concurrent(Io.sleep, .{ io, timeout, options.preferred_clock });
defer sleep.cancel(io) catch {};
return switch (try io.select(.{
.receive = &receive,
.sleep = &sleep,
})) {
.sleep => try receive.cancel(io),
.receive => |request| request,
};
},
};
}

View File

@@ -0,0 +1,29 @@
const std = @import("std");
const pb = @import("proto").pb;
const Session = @import("../Session.zig");
const Io = std.Io;
const Allocator = std.mem.Allocator;
const log = std.log.scoped(.auth);
pub const Error = error{LoginFailed} || Session.SendError || Allocator.Error || Io.Cancelable;
pub const Result = struct {
uid: u64, // It's a string in SC_LOGIN tho
};
pub fn processLoginRequest(io: Io, session: *Session, request: *const pb.CS_LOGIN) Error!Result {
log.info("login request received: {any}", .{request});
const uid = std.fmt.parseInt(u64, request.uid, 10) catch
return error.LoginFailed;
try session.send(pb.SC_LOGIN{
.uid = request.uid,
.server_time = @intCast((Io.Clock.real.now(io) catch Io.Timestamp.zero).toSeconds()),
.server_time_zone = 3,
});
return .{ .uid = uid };
}

344
gamesv/src/fs.zig Normal file
View File

@@ -0,0 +1,344 @@
const std = @import("std");
pub const persistence = @import("fs/persistence.zig");
const Io = std.Io;
const Dir = Io.Dir;
const File = Io.File;
const Allocator = std.mem.Allocator;
const Crc32 = std.hash.Crc32;
const MultiArrayList = std.MultiArrayList;
const log = std.log.scoped(.fs);
pub const RepresentationError = error{ReprSizeMismatch};
pub const LoadStructError = error{
SystemResources,
FileNotFound,
InputOutput,
ChecksumMismatch,
} || RepresentationError || Io.Cancelable;
pub const LoadDynamicArrayError = Allocator.Error || LoadStructError;
pub const SaveStructError = error{
SystemResources,
InputOutput,
} || Io.Cancelable;
const struct_header_size: usize = checksum_size;
const checksum_size: usize = 4;
const ArrayHeader = struct {
checksum: u32,
item_count: u32,
};
pub fn loadStruct(comptime T: type, io: Io, dir: Dir, sub_path: []const u8) LoadStructError!T {
const repr_size = @sizeOf(T);
var result: T = undefined;
const file = dir.openFile(io, sub_path, .{}) catch |err| switch (err) {
error.FileNotFound, error.SystemResources, error.Canceled => |e| return e,
else => |e| {
log.debug("fs.loadStruct('{s}') openFile failed: {t}", .{ sub_path, e });
return error.SystemResources;
},
};
defer file.close(io);
const length = file.length(io) catch |err| switch (err) {
error.Streaming => unreachable,
error.Canceled, error.SystemResources => |e| return e,
else => |e| {
log.debug("fs.loadStruct('{s}') File.length() failed: {t}", .{ sub_path, e });
return error.SystemResources;
},
};
if (length != repr_size + struct_header_size)
return RepresentationError.ReprSizeMismatch;
var file_reader = file.reader(io, "");
const reader = &file_reader.interface;
var checksum: [4]u8 = undefined;
reader.readSliceAll(&checksum) catch |err| switch (err) {
error.ReadFailed => switch (file_reader.err.?) {
error.Canceled, error.SystemResources => |e| return e,
else => return error.InputOutput,
},
else => return error.InputOutput,
};
const bytes: [*]u8 = @ptrCast(&result);
var bytes_writer: Io.Writer = .fixed(bytes[0..repr_size]);
var writer_buf: [128]u8 = undefined; // Just to amortize vtable calls.
var hashed: Io.Writer.Hashed(Crc32) = .initHasher(&bytes_writer, .init(), &writer_buf);
reader.streamExact(&hashed.writer, repr_size) catch |err| switch (err) {
error.ReadFailed => switch (file_reader.err.?) {
error.Canceled, error.SystemResources => |e| return e,
else => return error.InputOutput,
},
else => return error.InputOutput,
};
hashed.writer.flush() catch unreachable;
if (hashed.hasher.final() != std.mem.readInt(u32, &checksum, .native))
return error.ChecksumMismatch;
return result;
}
pub fn saveStruct(comptime T: type, data: *const T, io: Io, dir: Dir, sub_path: []const u8) !void {
const repr_size = @sizeOf(T);
const file = dir.createFile(io, sub_path, .{}) catch |err| switch (err) {
error.Canceled, error.SystemResources => |e| return e,
else => |e| {
log.debug("saveStruct('{s}'): createFile failed: {t}", .{ sub_path, e });
return error.InputOutput;
},
};
defer file.close(io);
var file_writer_buf: [1024]u8 = undefined;
var file_writer = file.writer(io, &file_writer_buf);
// Checksum placeholder.
file_writer.interface.writeInt(u32, 0, .native) catch return error.InputOutput;
var hashed_writer_buf: [128]u8 = undefined; // Just to amortize vtable calls.
var hashed: Io.Writer.Hashed(Crc32) = .initHasher(&file_writer.interface, .init(), &hashed_writer_buf);
const bytes: [*]const u8 = @ptrCast(data);
hashed.writer.writeAll(bytes[0..repr_size]) catch return error.InputOutput;
hashed.writer.flush() catch return error.InputOutput;
file_writer.seekTo(0) catch return error.InputOutput;
file_writer.interface.writeInt(u32, hashed.hasher.final(), .native) catch return error.InputOutput;
file_writer.interface.flush() catch return error.InputOutput;
}
pub fn loadDynamicArray(
comptime Elem: type,
io: Io,
dir: Dir,
gpa: Allocator,
sub_path: []const u8,
) LoadDynamicArrayError![]Elem {
const elem_size = @sizeOf(Elem);
const file = dir.openFile(io, sub_path, .{}) catch |err| switch (err) {
error.FileNotFound, error.SystemResources, error.Canceled => |e| return e,
else => |e| {
log.debug("fs.loadDynamicArray('{s}') openFile failed: {t}", .{ sub_path, e });
return error.SystemResources;
},
};
defer file.close(io);
const length = file.length(io) catch |err| switch (err) {
error.Streaming => unreachable,
error.Canceled, error.SystemResources => |e| return e,
else => |e| {
log.debug("fs.loadDynamicArray('{s}') File.length() failed: {t}", .{ sub_path, e });
return error.SystemResources;
},
};
if (length < @sizeOf(ArrayHeader))
return RepresentationError.ReprSizeMismatch;
var file_reader = file.reader(io, "");
const reader = &file_reader.interface;
var header: ArrayHeader = undefined;
reader.readSliceAll(@ptrCast(&header)) catch |err| switch (err) {
error.ReadFailed => switch (file_reader.err.?) {
error.Canceled, error.SystemResources => |e| return e,
else => return error.InputOutput,
},
else => return error.InputOutput,
};
if (length < (elem_size * header.item_count) + @sizeOf(ArrayHeader))
return RepresentationError.ReprSizeMismatch;
const result = try gpa.alloc(Elem, header.item_count);
errdefer gpa.free(result);
const bytes: [*]u8 = @ptrCast(result);
var bytes_writer: Io.Writer = .fixed(bytes[0 .. elem_size * header.item_count]);
var writer_buf: [128]u8 = undefined; // Just to amortize vtable calls.
var hashed: Io.Writer.Hashed(Crc32) = .initHasher(&bytes_writer, .init(), &writer_buf);
reader.streamExact(&hashed.writer, elem_size * header.item_count) catch |err| switch (err) {
error.ReadFailed => switch (file_reader.err.?) {
error.Canceled, error.SystemResources => |e| return e,
else => return error.InputOutput,
},
else => return error.InputOutput,
};
hashed.writer.flush() catch unreachable;
if (hashed.hasher.final() != header.checksum)
return error.ChecksumMismatch;
return result;
}
pub fn saveDynamicArray(comptime Elem: type, array: []const Elem, io: Io, dir: Dir, sub_path: []const u8) SaveStructError!void {
std.debug.assert(array.len <= std.math.maxInt(u32));
const file = dir.createFile(io, sub_path, .{}) catch |err| switch (err) {
error.Canceled, error.SystemResources => |e| return e,
else => |e| {
log.debug("saveDynamicArray('{s}'): createFile failed: {t}", .{ sub_path, e });
return error.InputOutput;
},
};
defer file.close(io);
var file_writer_buf: [1024]u8 = undefined;
var file_writer = file.writer(io, &file_writer_buf);
// Checksum placeholder.
file_writer.interface.writeInt(u32, 0, .native) catch return error.InputOutput;
file_writer.interface.writeInt(u32, @truncate(array.len), .native) catch return error.InputOutput;
var hashed_writer_buf: [128]u8 = undefined; // Just to amortize vtable calls.
var hashed: Io.Writer.Hashed(Crc32) = .initHasher(&file_writer.interface, .init(), &hashed_writer_buf);
hashed.writer.writeAll(@ptrCast(array)) catch return error.InputOutput;
hashed.writer.flush() catch return error.InputOutput;
file_writer.seekTo(0) catch return error.InputOutput;
file_writer.interface.writeInt(u32, hashed.hasher.final(), .native) catch return error.InputOutput;
file_writer.interface.flush() catch return error.InputOutput;
}
pub fn loadMultiArrayList(
comptime Elem: type,
io: Io,
dir: Dir,
gpa: Allocator,
sub_path: []const u8,
) LoadDynamicArrayError!MultiArrayList(Elem) {
const file = dir.openFile(io, sub_path, .{}) catch |err| switch (err) {
error.FileNotFound, error.SystemResources, error.Canceled => |e| return e,
else => |e| {
log.debug("fs.loadMultiArrayList('{s}') openFile failed: {t}", .{ sub_path, e });
return error.SystemResources;
},
};
defer file.close(io);
const length = file.length(io) catch |err| switch (err) {
error.Streaming => unreachable,
error.Canceled, error.SystemResources => |e| return e,
else => |e| {
log.debug("fs.loadMultiArrayList('{s}') File.length() failed: {t}", .{ sub_path, e });
return error.SystemResources;
},
};
if (length < @sizeOf(ArrayHeader))
return RepresentationError.ReprSizeMismatch;
var file_reader = file.reader(io, "");
var header: ArrayHeader = undefined;
file_reader.interface.readSliceAll(@ptrCast(&header)) catch |err| switch (err) {
error.ReadFailed => switch (file_reader.err.?) {
error.Canceled, error.SystemResources => |e| return e,
else => return error.InputOutput,
},
else => return error.InputOutput,
};
const bytes_length = MultiArrayList(Elem).capacityInBytes(header.item_count);
if (length < bytes_length + @sizeOf(ArrayHeader))
return RepresentationError.ReprSizeMismatch;
var result = try MultiArrayList(Elem).initCapacity(gpa, header.item_count);
errdefer result.deinit(gpa);
result.len = header.item_count;
const fields = comptime std.enums.values(MultiArrayList(Elem).Field);
var vecs: [fields.len][]u8 = undefined;
var slice = result.slice();
inline for (fields) |field| {
vecs[@intFromEnum(field)] = std.mem.sliceAsBytes(slice.items(field));
}
var hashed = file_reader.interface.hashed(Crc32.init(), "");
hashed.reader.readVecAll(&vecs) catch |err| switch (err) {
error.ReadFailed => switch (file_reader.err.?) {
error.Canceled, error.SystemResources => |e| return e,
else => return error.InputOutput,
},
else => return error.InputOutput,
};
if (hashed.hasher.final() != header.checksum)
return error.ChecksumMismatch;
return result;
}
pub fn saveMultiArrayList(
comptime Elem: type,
list: *const MultiArrayList(Elem),
io: Io,
dir: Dir,
sub_path: []const u8,
) SaveStructError!void {
std.debug.assert(list.len <= std.math.maxInt(u32));
const file = dir.createFile(io, sub_path, .{}) catch |err| switch (err) {
error.Canceled, error.SystemResources => |e| return e,
else => |e| {
log.debug("saveMultiArrayList('{s}'): createFile failed: {t}", .{ sub_path, e });
return error.InputOutput;
},
};
defer file.close(io);
var file_writer_buf: [1024]u8 = undefined;
var file_writer = file.writer(io, &file_writer_buf);
// Checksum placeholder.
file_writer.interface.writeInt(u32, 0, .native) catch return error.InputOutput;
file_writer.interface.writeInt(u32, @truncate(list.len), .native) catch return error.InputOutput;
var hashed_writer_buf: [128]u8 = undefined; // Just to amortize vtable calls.
var hashed: Io.Writer.Hashed(Crc32) = .initHasher(&file_writer.interface, .init(), &hashed_writer_buf);
const fields = comptime std.enums.values(MultiArrayList(Elem).Field);
var vecs: [fields.len][]const u8 = undefined;
var slice = list.slice();
inline for (fields) |field| {
vecs[@intFromEnum(field)] = std.mem.sliceAsBytes(slice.items(field));
}
hashed.writer.writeVecAll(&vecs) catch return error.InputOutput;
hashed.writer.flush() catch return error.InputOutput;
file_writer.seekTo(0) catch return error.InputOutput;
file_writer.interface.writeInt(u32, hashed.hasher.final(), .native) catch return error.InputOutput;
file_writer.interface.flush() catch return error.InputOutput;
}

View File

@@ -0,0 +1,477 @@
const std = @import("std");
const fs = @import("../fs.zig");
const logic = @import("../logic.zig");
const Assets = @import("../Assets.zig");
const Io = std.Io;
const Allocator = std.mem.Allocator;
const Player = logic.Player;
const player_data_dir = "store/player/";
const base_component_file = "base_data";
const server_game_vars_file = "server_game_vars";
const client_game_vars_file = "client_game_vars";
const unlocked_systems_file = "unlocked_systems";
const bitset_file = "bitset";
const char_bag_path = "char_bag";
const char_bag_chars_file = "chars";
const char_bag_teams_file = "teams";
const char_bag_meta_file = "meta";
const item_bag_path = "item_bag";
const item_bag_weapon_depot_file = "weapon_depot";
const default_team: []const []const u8 = &.{
"chr_0026_lastrite",
"chr_0009_azrila",
"chr_0016_laevat",
"chr_0022_bounda",
};
const LoadPlayerError = error{
InputOutput,
SystemResources,
} || Allocator.Error || Io.Cancelable;
const log = std.log.scoped(.persistence);
// Opens or creates data directory for the player with specified uid.
pub fn openPlayerDataDir(io: Io, uid: u64) !Io.Dir {
var dir_path_buf: [player_data_dir.len + 20]u8 = undefined;
const dir_path = std.fmt.bufPrint(&dir_path_buf, player_data_dir ++ "{d}", .{uid}) catch
unreachable; // Since we're printing a u64, it shouldn't exceed the buffer.
const cwd: Io.Dir = .cwd();
return cwd.openDir(io, dir_path, .{}) catch |open_err| switch (open_err) {
error.Canceled => |e| return e,
error.FileNotFound => cwd.createDirPathOpen(io, dir_path, .{}) catch |create_err| switch (create_err) {
error.Canceled => |e| return e,
else => return error.InputOutput,
},
else => return error.InputOutput,
};
}
// Loads player data. Creates components that do not exist.
// Resets component to default if its data is corrupted.
pub fn loadPlayer(io: Io, gpa: Allocator, assets: *const Assets, uid: u64) !Player {
const data_dir = try openPlayerDataDir(io, uid);
defer data_dir.close(io);
var result: Player = undefined;
result.base = try loadBaseComponent(io, data_dir, uid);
result.game_vars = try loadGameVarsComponent(io, gpa, data_dir, uid);
errdefer result.game_vars.deinit(gpa);
result.unlock = try loadUnlockComponent(io, gpa, data_dir, uid);
errdefer result.unlock.deinit(gpa);
result.item_bag = try loadItemBagComponent(io, gpa, data_dir);
errdefer result.item_bag.deinit(gpa);
result.char_bag = loadCharBagComponent(io, gpa, data_dir, uid) catch |err| switch (err) {
error.NeedsReset => try createDefaultCharBagComponent(io, gpa, assets, &result.item_bag, data_dir),
else => |e| return e,
};
errdefer result.char_bag.deinit(gpa);
result.bitset = loadBitsetComponent(io, data_dir, uid) catch |err| switch (err) {
error.NeedsReset => try createDefaultBitsetComponent(io, data_dir, assets),
else => |e| return e,
};
return result;
}
fn loadBaseComponent(
io: Io,
data_dir: Io.Dir,
uid: u64,
) !Player.Base {
return fs.loadStruct(Player.Base, io, data_dir, base_component_file) catch |err| switch (err) {
inline error.FileNotFound, error.ChecksumMismatch, error.ReprSizeMismatch => |e| reset: {
if (e == error.ChecksumMismatch) {
log.err(
"checksum mismatched for base_data of player {d}, resetting to defaults.",
.{uid},
);
}
if (e == error.ReprSizeMismatch) {
log.err(
"struct layout mismatched for base_data of player {d}, resetting to defaults.",
.{uid},
);
}
var defaults: Player.Base = .init;
try fs.saveStruct(Player.Base, &defaults, io, data_dir, base_component_file);
break :reset defaults;
},
error.Canceled => |e| return e,
else => return error.InputOutput,
};
}
fn loadGameVarsComponent(io: Io, gpa: Allocator, data_dir: Io.Dir, uid: u64) !Player.GameVars {
var game_vars: Player.GameVars = undefined;
game_vars.server_vars = try loadArray(
Player.GameVars.ServerVar,
io,
gpa,
data_dir,
uid,
server_game_vars_file,
Player.GameVars.default_server_vars,
);
errdefer gpa.free(game_vars.server_vars);
game_vars.client_vars = try loadArray(
Player.GameVars.ClientVar,
io,
gpa,
data_dir,
uid,
client_game_vars_file,
Player.GameVars.default_client_vars,
);
errdefer gpa.free(game_vars.client_vars);
return game_vars;
}
fn loadUnlockComponent(io: Io, gpa: Allocator, data_dir: Io.Dir, uid: u64) !Player.Unlock {
var unlock: Player.Unlock = undefined;
unlock.unlocked_systems = try loadArray(
Player.Unlock.SystemType,
io,
gpa,
data_dir,
uid,
unlocked_systems_file,
Player.Unlock.default_unlocked_systems,
);
errdefer gpa.free(unlock.unlocked_systems);
return unlock;
}
fn loadCharBagComponent(io: Io, gpa: Allocator, data_dir: Io.Dir, uid: u64) !Player.CharBag {
const char_bag_dir = data_dir.openDir(io, char_bag_path, .{}) catch |err| switch (err) {
error.FileNotFound => return error.NeedsReset,
error.Canceled => |e| return e,
else => return error.InputOutput,
};
defer char_bag_dir.close(io);
var chars = fs.loadMultiArrayList(Player.CharBag.Char, io, char_bag_dir, gpa, char_bag_chars_file) catch |err| switch (err) {
error.FileNotFound, error.ChecksumMismatch, error.ReprSizeMismatch => return error.NeedsReset,
error.Canceled, error.OutOfMemory => |e| return e,
else => return error.InputOutput,
};
errdefer chars.deinit(gpa);
var teams = fs.loadMultiArrayList(Player.CharBag.Team, io, char_bag_dir, gpa, char_bag_teams_file) catch |err| switch (err) {
error.FileNotFound, error.ChecksumMismatch, error.ReprSizeMismatch => return error.NeedsReset,
error.Canceled, error.OutOfMemory => |e| return e,
else => return error.InputOutput,
};
errdefer teams.deinit(gpa);
if (teams.len == 0) return error.NeedsReset;
const meta = fs.loadStruct(Player.CharBag.Meta, io, char_bag_dir, char_bag_meta_file) catch |err| switch (err) {
inline error.FileNotFound, error.ChecksumMismatch, error.ReprSizeMismatch => |e| reset: {
if (e == error.ChecksumMismatch) {
log.err(
"checksum mismatched for char bag metadata of player {d}, resetting to defaults.",
.{uid},
);
}
if (e == error.ReprSizeMismatch) {
log.err(
"struct layout mismatched for char bag metadata of player {d}, resetting to defaults.",
.{uid},
);
}
const defaults: Player.CharBag.Meta = .{ .curr_team_index = 0 };
try fs.saveStruct(Player.CharBag.Meta, &defaults, io, data_dir, base_component_file);
break :reset defaults;
},
error.Canceled => |e| return e,
else => return error.InputOutput,
};
if (meta.curr_team_index >= teams.len)
return error.NeedsReset;
return .{
.chars = chars,
.teams = teams,
.meta = meta,
};
}
fn createDefaultCharBagComponent(
io: Io,
gpa: Allocator,
assets: *const Assets,
// Depends on ItemBag because it has to create weapons for the new characters.
item_bag: *Player.ItemBag,
data_dir: Io.Dir,
) !Player.CharBag {
const char_bag_dir = data_dir.createDirPathOpen(io, char_bag_path, .{}) catch |err| switch (err) {
error.Canceled => |e| return e,
else => return error.InputOutput,
};
defer char_bag_dir.close(io);
var chars = try std.MultiArrayList(
Player.CharBag.Char,
).initCapacity(gpa, assets.table(.character).count());
errdefer chars.deinit(gpa);
for (assets.table(.character).keys(), assets.table(.character).values()) |id, char_data| {
const char_id_num = assets.strToNum(.char_id, id) orelse continue;
if (!assets.char_skill_map.map.contains(id))
continue; // Dummy Character
const weapon_template_id: i32 = blk: {
if (assets.table(.char_wpn_recommend).getPtr(id)) |recommend| {
if (recommend.weaponIds1.len > 0)
break :blk assets.strToNum(.item_id, recommend.weaponIds1[0]).?;
}
for (assets.table(.weapon_basic).values()) |weapon| {
if (weapon.weaponType == char_data.weaponType)
break :blk assets.strToNum(.item_id, weapon.weaponId).?;
} else continue; // No suitable weapon, don't create this character because it'll be broken in-game.
};
try item_bag.weapon_depot.append(gpa, .{
.template_id = weapon_template_id,
.exp = 0,
.weapon_lv = 1,
.refine_lv = 0,
.breakthrough_lv = 0,
.attach_gem_id = 0,
});
const weapon_id: Player.ItemBag.WeaponIndex = @enumFromInt(
@as(u64, @intCast(item_bag.weapon_depot.len - 1)),
);
const hp = for (char_data.attributes[0].Attribute.attrs) |attr| {
if (attr.attrType == .max_hp)
break attr.attrValue;
} else 100;
const sp = for (char_data.attributes[0].Attribute.attrs) |attr| {
if (attr.attrType == .max_ultimate_sp)
break attr.attrValue;
} else 100;
chars.appendAssumeCapacity(.{
.template_id = char_id_num,
.level = 1,
.exp = 0,
.is_dead = false,
.hp = hp,
.ultimate_sp = @floatCast(sp),
.weapon_id = weapon_id,
.own_time = 0,
.equip_medicine_id = 0,
.potential_level = 5,
});
}
var teams = try std.MultiArrayList(Player.CharBag.Team).initCapacity(gpa, 1);
errdefer teams.deinit(gpa);
var result: Player.CharBag = .{
.chars = chars,
.teams = teams,
.meta = .{ .curr_team_index = 0 },
};
var team: Player.CharBag.Team.SlotArray = @splat(.empty);
for (default_team, 0..) |char_template_id, i| {
const id_num = assets.strToNum(.char_id, char_template_id).?;
const char_index = result.charIndexById(id_num).?;
team[i] = .fromCharIndex(char_index);
}
result.teams.appendAssumeCapacity(.{
.name = .constant("reversedrooms"),
.char_team = team,
.leader_index = team[0].charIndex().?,
});
try saveItemBagComponent(io, data_dir, item_bag);
try fs.saveMultiArrayList(Player.CharBag.Char, &result.chars, io, char_bag_dir, char_bag_chars_file);
try fs.saveMultiArrayList(Player.CharBag.Team, &result.teams, io, char_bag_dir, char_bag_teams_file);
try fs.saveStruct(Player.CharBag.Meta, &result.meta, io, char_bag_dir, char_bag_meta_file);
return result;
}
fn loadItemBagComponent(io: Io, gpa: Allocator, data_dir: Io.Dir) !Player.ItemBag {
const item_bag_dir = data_dir.openDir(io, "item_bag", .{}) catch |open_err| switch (open_err) {
error.FileNotFound => data_dir.createDirPathOpen(io, "item_bag", .{}) catch |create_err| switch (create_err) {
error.Canceled => |e| return e,
else => return error.InputOutput,
},
error.Canceled => |e| return e,
else => return error.InputOutput,
};
defer item_bag_dir.close(io);
var weapon_depot = fs.loadMultiArrayList(Player.ItemBag.Weapon, io, item_bag_dir, gpa, item_bag_weapon_depot_file) catch |err| switch (err) {
error.FileNotFound,
error.ChecksumMismatch,
error.ReprSizeMismatch,
=> std.MultiArrayList(Player.ItemBag.Weapon).empty,
error.Canceled, error.OutOfMemory => |e| return e,
else => return error.InputOutput,
};
errdefer weapon_depot.deinit(gpa);
return .{ .weapon_depot = weapon_depot };
}
pub fn saveItemBagComponent(io: Io, data_dir: Io.Dir, component: *const Player.ItemBag) !void {
const item_bag_dir = data_dir.createDirPathOpen(io, item_bag_path, .{}) catch |err| switch (err) {
error.Canceled => |e| return e,
else => return error.InputOutput,
};
defer item_bag_dir.close(io);
try fs.saveMultiArrayList(Player.ItemBag.Weapon, &component.weapon_depot, io, item_bag_dir, item_bag_weapon_depot_file);
}
pub fn saveCharBagComponent(
io: Io,
data_dir: Io.Dir,
component: *const Player.CharBag,
comptime what: union(enum) { all, chars, teams, meta },
) !void {
const char_bag_dir = data_dir.createDirPathOpen(io, char_bag_path, .{}) catch |err| switch (err) {
error.Canceled => |e| return e,
else => return error.InputOutput,
};
defer char_bag_dir.close(io);
if (what == .all or what == .chars) {
try fs.saveMultiArrayList(Player.CharBag.Char, &component.chars, io, char_bag_dir, char_bag_chars_file);
}
if (what == .all or what == .teams) {
try fs.saveMultiArrayList(Player.CharBag.Team, &component.teams, io, char_bag_dir, char_bag_teams_file);
}
if (what == .all or what == .meta) {
try fs.saveStruct(Player.CharBag.Meta, &component.meta, io, char_bag_dir, char_bag_meta_file);
}
}
fn loadBitsetComponent(io: Io, data_dir: Io.Dir, uid: u64) !Player.Bitset {
return fs.loadStruct(Player.Bitset, io, data_dir, bitset_file) catch |err| switch (err) {
inline error.FileNotFound, error.ChecksumMismatch, error.ReprSizeMismatch => |e| {
if (e == error.ChecksumMismatch) {
log.err(
"checksum mismatched for bitset of player {d}, resetting to defaults.",
.{uid},
);
}
if (e == error.ReprSizeMismatch) {
log.err(
"struct layout mismatched for bitset of player {d}, resetting to defaults.",
.{uid},
);
}
return error.NeedsReset;
},
error.Canceled => |e| return e,
else => return error.InputOutput,
};
}
fn createDefaultBitsetComponent(io: Io, data_dir: Io.Dir, assets: *const Assets) !Player.Bitset {
var bitset: Player.Bitset = .init;
for (assets.level_config_table.values()) |config| {
bitset.set(.level_have_been, @intCast(config.idNum)) catch |err| switch (err) {
error.ValueOutOfRange => { // This means we have to increase Bitset.max_value
std.debug.panic(
"createDefaultBitsetComponent: value is out of range! ({d}/{d})",
.{ config.idNum, Player.Bitset.max_value },
);
},
};
bitset.set(.level_map_first_view, @intCast(config.idNum)) catch unreachable;
bitset.set(.read_level, @intCast(config.idNum)) catch unreachable;
}
try fs.saveStruct(Player.Bitset, &bitset, io, data_dir, bitset_file);
return bitset;
}
fn loadArray(
comptime T: type,
io: Io,
gpa: Allocator,
data_dir: Io.Dir,
uid: u64,
sub_path: []const u8,
defaults: []const T,
) ![]T {
return fs.loadDynamicArray(T, io, data_dir, gpa, sub_path) catch |err| switch (err) {
inline error.FileNotFound, error.ChecksumMismatch, error.ReprSizeMismatch => |e| reset: {
if (e == error.ChecksumMismatch) {
log.err(
"checksum mismatched for '{s}' of player {d}, resetting to defaults.",
.{ sub_path, uid },
);
}
if (e == error.ReprSizeMismatch) {
log.err(
"struct layout mismatched for '{s}' of player {d}, resetting to defaults.",
.{ sub_path, uid },
);
}
try fs.saveDynamicArray(T, defaults, io, data_dir, sub_path);
break :reset try gpa.dupe(T, defaults);
},
error.Canceled, error.OutOfMemory => |e| return e,
else => return error.InputOutput,
};
}

7
gamesv/src/logic.zig Normal file
View File

@@ -0,0 +1,7 @@
pub const World = @import("logic/World.zig");
pub const Resource = @import("logic/Resource.zig");
pub const Player = @import("logic/Player.zig");
pub const messaging = @import("logic/messaging.zig");
pub const event = @import("logic/event.zig");
pub const systems = @import("logic/systems.zig");
pub const queries = @import("logic/queries.zig");

View File

@@ -0,0 +1,45 @@
const Player = @This();
const std = @import("std");
const meta = std.meta;
const Allocator = std.mem.Allocator;
pub const Base = @import("Player/Base.zig");
pub const GameVars = @import("Player/GameVars.zig");
pub const Unlock = @import("Player/Unlock.zig");
pub const CharBag = @import("Player/CharBag.zig");
pub const ItemBag = @import("Player/ItemBag.zig");
pub const Bitset = @import("Player/Bitset.zig");
base: Base,
game_vars: GameVars,
unlock: Unlock,
char_bag: CharBag,
item_bag: ItemBag,
bitset: Bitset,
pub fn deinit(player: *Player, gpa: Allocator) void {
player.game_vars.deinit(gpa);
player.unlock.deinit(gpa);
player.char_bag.deinit(gpa);
player.item_bag.deinit(gpa);
}
// Describes the dependency on an individual player component.
pub fn Component(comptime tag: meta.FieldEnum(Player)) type {
return struct {
pub const player_component_tag = tag;
data: *@FieldType(Player, @tagName(tag)),
};
}
pub fn isComponent(comptime T: type) bool {
if (!@hasDecl(T, "player_component_tag")) return false;
return T == Component(T.player_component_tag);
}
pub fn getComponentByType(player: *Player, comptime T: type) T {
return .{ .data = &@field(player, @tagName(T.player_component_tag)) };
}

View File

@@ -0,0 +1,37 @@
const Base = @This();
const common = @import("common");
const mem = common.mem;
pub const max_role_name_length: usize = 15;
pub const init: Base = .{
.create_ts = 0,
.role_name = .constant("xeondev"),
.role_id = 1,
.level = .first,
.exp = 0,
.create_ts_display = 0,
.gender = .default,
};
pub const Gender = enum(u8) {
pub const default: Gender = .male;
invalid = 0,
male = 1,
female = 2,
};
pub const Level = enum(u8) {
first = 1,
last = 60,
_,
};
create_ts: i64,
role_name: mem.LimitedString(max_role_name_length),
role_id: u64,
level: Level,
exp: u32,
create_ts_display: i64,
gender: Gender,

View File

@@ -0,0 +1,113 @@
const Bitset = @This();
const std = @import("std");
const Allocator = std.mem.Allocator;
pub const max_value = 512;
const Set = std.bit_set.ArrayBitSet(u64, max_value);
pub const init: Bitset = .{};
sets: [Type.count]Set = @splat(.initEmpty()),
pub fn set(b: *Bitset, t: Type, value: u64) error{ValueOutOfRange}!void {
if (value > max_value) return error.ValueOutOfRange;
b.sets[@intFromEnum(t) - 1].set(@intCast(value));
}
pub const Type = enum(u32) {
pub const count: usize = blk: {
const values = std.enums.values(Type);
break :blk @as(usize, @intFromEnum(values[values.len - 1])) + 1;
};
found_item = 1,
wiki = 2,
unread_wiki = 3,
monster_drop = 4,
got_item = 5,
area_first_view = 6,
unread_got_item = 7,
prts = 8,
unread_prts = 9,
prts_first_lv = 10,
prts_terminal_content = 11,
level_have_been = 12,
level_map_first_view = 13,
unread_formula = 14,
new_char = 15,
elog_channel = 16,
fmv_watched = 17,
time_line_watched = 18,
map_filter = 19,
friend_has_request = 20,
equip_tech_formula = 21,
radio_trigger = 22,
remote_communication_finish = 23,
unlock_server_dungeon_series = 24,
chapter_first_view = 25,
adventure_level_reward_done = 26,
dungeon_entrance_touched = 27,
equip_tech_tier = 28,
char_doc = 30,
char_voice = 31,
reading_pop = 32,
reward_id_done = 33,
prts_investigate = 34,
racing_received_bp_node = 35,
racing_complete_achievement = 36,
racing_received_achievement = 37,
interactive_active = 39,
mine_point_first_time_collect = 40,
unread_char_doc = 41,
unread_char_voice = 42,
area_toast_once = 44,
unread_equip_tech_formula = 45,
prts_investigate_unread_note = 46,
prts_investigate_note = 47,
game_mechanic_read = 48,
read_active_blackbox = 49,
read_level = 50,
factroy_placed_building = 51,
interactive_two_state = 52,
unread_unlock_spaceship_room_type = 53,
unlock_spaceship_room_type = 54,
unlock_user_avatar = 55,
unlock_user_avatar_frame = 56,
unlock_business_card_topic = 57,
special_game_event = 58,
radio_id = 59,
got_weapon = 60,
read_new_version_equip_tech_formula = 61,
mist_map_unlocked = 62,
read_achive = 63,
camera_volume = 64,
read_fac_tech_tree_unhidden_tech = 65,
read_fac_tech_tree_unhidden_category = 66,
mist_map_mv_watched = 67,
remote_communication_wait_for_play = 68,
mission_completed_once = 69,
psn_cup_unlocked = 70,
unread_week_raid_mission = 71,
unlock_game_entrance_activity_series = 72,
unlock_domain_depot = 73,
unlock_recycle_bin = 74,
manual_crafted_item = 75,
un_read_new_activity_notify = 76,
read_picture_ids = 77,
read_shop_id = 78,
read_shop_goods_id = 79,
read_bp_season_id = 80,
read_bp_task_id = 81,
read_cash_shop_goods_id = 82,
new_avatar_unlock = 83,
new_avatar_frame_unlock = 84,
new_theme_unlock = 85,
read_char_potential_pic_ids = 86,
read_high_difficulty_dungeon_series = 87,
reported_client_log_types = 88,
activated_factory_inst = 89,
read_max_world_level = 90,
got_formula_unlock_item = 91,
};

View File

@@ -0,0 +1,102 @@
const CharBag = @This();
const std = @import("std");
const common = @import("common");
const Player = @import("../Player.zig");
const Allocator = std.mem.Allocator;
teams: std.MultiArrayList(Team),
chars: std.MultiArrayList(Char),
meta: Meta,
pub const CharIndex = enum(u64) {
_,
// Returns an 'objectId' for network serialization.
pub fn objectId(i: CharIndex) u64 {
return @intFromEnum(i) + 1;
}
pub fn fromObjectId(id: u64) CharIndex {
return @enumFromInt(id - 1);
}
};
pub fn deinit(bag: *CharBag, gpa: Allocator) void {
bag.teams.deinit(gpa);
bag.chars.deinit(gpa);
}
pub fn charIndexById(bag: *const CharBag, template_id: i32) ?CharIndex {
const idx: u64 = @intCast(
std.mem.findScalar(i32, bag.chars.items(.template_id), template_id) orelse
return null,
);
return @enumFromInt(idx);
}
pub fn charIndexWithWeapon(bag: *const CharBag, weapon: Player.ItemBag.WeaponIndex) ?CharIndex {
const idx: u64 = @intCast(
std.mem.findScalar(Player.ItemBag.WeaponIndex, bag.chars.items(.weapon_id), weapon) orelse
return null,
);
return @enumFromInt(idx);
}
// Checks:
// 1. Existence of the team.
// 2. Existence of the specified character index in the team.
pub fn ensureTeamMember(bag: *const CharBag, team_index: usize, char_index: CharIndex) error{
InvalidTeamIndex,
NotTeamMember,
}!void {
if (team_index < 0 or team_index >= bag.teams.len) {
return error.InvalidTeamIndex;
}
const char_team = &bag.teams.items(.char_team)[team_index];
_ = std.mem.findScalar(u64, @ptrCast(char_team), @intFromEnum(char_index)) orelse
return error.NotTeamMember;
}
pub const Meta = struct {
curr_team_index: u32,
};
pub const Team = struct {
pub const slots_count: usize = 4;
pub const SlotArray = [Team.slots_count]Team.Slot;
pub const Slot = enum(u64) {
empty = std.math.maxInt(u64),
_,
pub fn charIndex(s: Slot) ?CharIndex {
return if (s != .empty) @enumFromInt(@intFromEnum(s)) else null;
}
pub fn fromCharIndex(i: CharIndex) Slot {
return @enumFromInt(@intFromEnum(i));
}
};
name: common.mem.LimitedString(15) = .empty,
char_team: [slots_count]Slot = @splat(Slot.empty),
leader_index: CharIndex,
};
pub const Char = struct {
template_id: i32,
level: i32,
exp: i32,
is_dead: bool,
hp: f64,
ultimate_sp: f32,
weapon_id: Player.ItemBag.WeaponIndex,
own_time: i64,
equip_medicine_id: i32,
potential_level: u32,
};

View File

@@ -0,0 +1,71 @@
const GameVars = @This();
const std = @import("std");
const Allocator = std.mem.Allocator;
pub const default_server_vars: []const ServerVar = &.{
.{ .key = .already_set_gender, .value = 1 },
.{ .key = .dash_energy_limit, .value = 100 },
.{ .key = .already_set_name, .value = 1 },
};
pub const default_client_vars: []const ClientVar = &.{
.{ .key = 43, .value = 1 },
.{ .key = 78, .value = 1 },
.{ .key = 82, .value = 1 },
.{ .key = 125, .value = 1 },
.{ .key = 126, .value = 1 },
};
pub const ClientVar = packed struct {
key: i32,
value: i64,
};
pub const ServerVar = packed struct {
key: ServerVarType,
value: i64,
};
client_vars: []ClientVar,
server_vars: []ServerVar,
pub fn deinit(gv: *GameVars, gpa: Allocator) void {
gpa.free(gv.client_vars);
gpa.free(gv.server_vars);
}
pub const ServerVarType = enum(i32) {
const common_begin: i32 = 100000;
const common_end: i32 = 109999;
const daily_refresh_begin: i32 = 110000;
const daily_refresh_end: i32 = 119999;
pub const Kind = enum(i32) {
common = 10,
daily_refresh = 11,
weekly_refresh = 12,
monthly_refresh = 13,
};
server_test_1 = 100001,
server_test_2 = 100002,
already_set_gender = 100003,
enhance_bean = 100004,
enhance_bean_last_replenish_time = 100005,
dash_energy_limit = 100006,
already_set_name = 100007,
social_share_control = 100008,
db_config_version = 100009,
client_debug_mode_end_time = 100010,
recover_ap_by_money_count = 110001,
poop_cow_interact_count = 110002,
stamina_reduce_used_count = 110003,
space_ship_daily_credit_reward = 110004,
daily_enemy_drop_mod_reward_count = 110005,
daily_enemy_exp_count = 110006,
pub inline fn kind(vt: ServerVarType) Kind {
return @enumFromInt(@intFromEnum(vt) / 10_000);
}
};

View File

@@ -0,0 +1,28 @@
const ItemBag = @This();
const std = @import("std");
const common = @import("common");
const Allocator = std.mem.Allocator;
weapon_depot: std.MultiArrayList(Weapon),
pub fn deinit(bag: *ItemBag, gpa: Allocator) void {
bag.weapon_depot.deinit(gpa);
}
pub const WeaponIndex = enum(u64) {
_,
pub fn instId(i: WeaponIndex) u64 {
return @intFromEnum(i) + 1;
}
};
pub const Weapon = struct {
template_id: i32,
exp: u32,
weapon_lv: u32,
refine_lv: u32,
breakthrough_lv: u32,
attach_gem_id: u64,
};

View File

@@ -0,0 +1,126 @@
const Unlock = @This();
const std = @import("std");
const Allocator = std.mem.Allocator;
pub const default_unlocked_systems: []const SystemType = blk: {
const fields = @typeInfo(SystemType).@"enum".fields;
var list: []const SystemType = &.{};
for (fields) |field| {
if (field.value != @intFromEnum(SystemType.none)) {
list = list ++ .{@field(SystemType, field.name)};
}
}
break :blk list;
};
unlocked_systems: []SystemType,
pub fn deinit(unlock: *Unlock, gpa: Allocator) void {
gpa.free(unlock.unlocked_systems);
}
pub const SystemType = enum(i32) {
none = 10000000,
map = 0,
inventory = 1,
watch = 2,
valuable_depot = 3,
shop = 4,
char_team = 5,
gacha = 51,
dungeon = 52,
bloc_mission = 53,
mail = 54,
wiki = 55,
prts = 56,
submit_ether = 57,
scan = 58,
char_ui = 59,
friend = 60,
daily_mission = 61,
general_ability_bomb = 62,
general_ability_fluid_interact = 63,
general_ability = 64,
sns = 65,
equip_tech = 66,
equip_produce = 67,
dungeon_factory = 69,
enemy_spawner = 70,
general_ability_water_gun = 71,
general_ability_snapshot = 72,
fac_building_pin = 101,
fac_craft_pin = 102,
fac_mode = 103,
fac_tech_tree = 104,
fac_overview = 105,
fac_yield_stats = 106,
fac_conveyor = 107,
fac_transfer_port = 108,
fac_bridge = 109,
fac_splitter = 110,
fac_merger = 111,
fac_bus = 112,
fac_zone = 113,
fac_system = 114,
fac_pipe = 115,
fac_pipe_splitter = 116,
fac_pipe_connector = 117,
fac_pipe_converger = 118,
fac_hub = 119,
fac_bus_free = 120,
fac_top_view = 121,
fac_blueprint = 122,
fac_underground_pipe = 123,
fac_social = 124,
fac_valve = 125,
fac_pipe_valve = 126,
fac_panel_store = 127,
fac_fertilize = 128,
manual_craft = 201,
item_use = 202,
item_quick_bar = 203,
product_manual = 204,
manual_craft_soil = 205,
weapon = 251,
equip = 252,
equip_enhance = 253,
gem_enhance = 254,
normal_attack = 301,
normal_skill = 302,
ultimate_skill = 303,
team_skill = 304,
combo_skill = 305,
team_switch = 306,
dash = 307,
jump = 308,
lock_target = 309,
spaceship_present_gift = 401,
spaceship_manufacturing_station = 402,
spaceship_control_center = 403,
spaceship_system = 404,
spaceship_grow_cabin = 405,
spaceship_shop = 406,
spaceship_guest_room = 407,
settlement = 501,
domain_development = 502,
domain_development_domain_depot = 503,
settlement_defense = 504,
kite_station = 511,
domain_shop = 512,
racing_dungeon = 601,
battle_training = 602,
week_raid = 603,
week_raid_intro = 604,
water_drone_can_use_xiranite = 605,
adventure_exp_and_lv = 651,
adventure_book = 652,
guidance_manul = 661,
ai_bark = 670,
achievement = 701,
minigame_puzzle = 801,
bp_system = 802,
activity = 1100,
check_in = 1113,
};

View File

@@ -0,0 +1,46 @@
const Resource = @This();
const std = @import("std");
const mem = std.mem;
const Assets = @import("../Assets.zig");
const Io = std.Io;
pub const AllocatorKind = enum {
gpa,
arena,
};
pub const PingTimer = struct {
io: Io,
last_client_ts: u64 = 0,
pub fn serverTime(pt: PingTimer) u64 {
return if (Io.Clock.real.now(pt.io)) |ts|
@intCast(ts.toMilliseconds())
else |_|
pt.last_client_ts;
}
};
assets: *const Assets,
ping_timer: PingTimer,
pub fn init(assets: *const Assets, io_impl: Io) Resource {
return .{
.assets = assets,
.ping_timer = .{ .io = io_impl },
};
}
pub fn io(res: *const Resource) Io {
return res.ping_timer.io; // TODO: move to the root of resources.
}
// Describes the dependency on an allocator.
pub fn Allocator(comptime kind: AllocatorKind) type {
return struct {
pub const allocator_kind = kind;
interface: mem.Allocator,
};
}

View File

@@ -0,0 +1,58 @@
// Describes player-local state of the world.
const World = @This();
const std = @import("std");
const logic = @import("../logic.zig");
const Session = @import("../Session.zig");
const Assets = @import("../Assets.zig");
const Io = std.Io;
const Allocator = std.mem.Allocator;
pub const PlayerId = struct { uid: u64 };
player_id: PlayerId,
session: *Session, // TODO: should it be here this way? Do we need an abstraction?
res: logic.Resource,
player: logic.Player,
pub fn init(
session: *Session,
assets: *const Assets,
uid: u64,
player: logic.Player,
gpa: Allocator,
io: Io,
) World {
_ = gpa;
return .{
.player_id = .{ .uid = uid },
.session = session,
.player = player,
.res = .init(assets, io),
};
}
pub fn deinit(world: *World, gpa: Allocator) void {
world.player.deinit(gpa);
}
pub const GetComponentError = error{
ComponentUnavailable,
};
pub fn getComponentByType(world: *World, comptime T: type) GetComponentError!T {
switch (T) {
PlayerId => return world.player_id,
*Session => return world.session,
*logic.Resource.PingTimer => return &world.res.ping_timer,
*const Assets => return world.res.assets,
Io => return world.res.io(),
else => {
if (comptime logic.Player.isComponent(T)) {
return world.player.getComponentByType(T);
}
@compileError("World.getComponentByType(" ++ @typeName(T) ++ ") is unsupported");
},
}
}

View File

@@ -0,0 +1,80 @@
const std = @import("std");
pub const kinds = @import("event/kinds.zig");
const Allocator = std.mem.Allocator;
const meta = std.meta;
// Describes the event receiver
pub fn Receiver(comptime kind: meta.Tag(Kind)) type {
return struct {
pub const rx_event_kind = kind;
pub const Event = @field(
kinds,
@typeInfo(kinds).@"struct".decls[@intFromEnum(kind)].name,
);
payload: Event,
};
}
// Describes the event sender
pub fn Sender(comptime kind: meta.Tag(Kind)) type {
return struct {
pub const tx_event_kind = kind;
pub const Event = @field(
kinds,
@typeInfo(kinds).@"struct".decls[@intFromEnum(kind)].name,
);
event_queue: *Queue,
pub fn send(s: @This(), event: Event) Allocator.Error!void {
try s.event_queue.push(@unionInit(Kind, @tagName(kind), event));
}
};
}
pub const Queue = struct {
arena: Allocator,
deque: std.Deque(Kind),
pub fn init(arena: Allocator) Queue {
return .{ .arena = arena, .deque = .empty };
}
pub fn push(queue: *Queue, event: Kind) Allocator.Error!void {
try queue.deque.pushBack(queue.arena, event);
}
};
pub const Kind = blk: {
var types: []const type = &.{};
var indices: []const u16 = &.{};
var names: []const []const u8 = &.{};
for (@typeInfo(kinds).@"struct".decls, 0..) |decl, i| {
const declaration = @field(kinds, decl.name);
if (@TypeOf(declaration) != type) continue;
if (meta.activeTag(@typeInfo(declaration)) == .@"struct") {
indices = indices ++ .{@as(u16, @intCast(i))};
types = types ++ .{@field(kinds, decl.name)};
names = names ++ .{toSnakeCase(decl.name)};
}
}
const EventTag = @Enum(u16, .exhaustive, names, indices[0..names.len]);
break :blk @Union(.auto, EventTag, names, types[0..names.len], &@splat(.{}));
};
inline fn toSnakeCase(comptime name: []const u8) []const u8 {
var result: []const u8 = "";
for (name, 0..) |c, i| {
if (std.ascii.isUpper(c)) {
if (i != 0) result = result ++ "_";
result = result ++ .{std.ascii.toLower(c)};
} else result = result ++ .{c};
}
return result;
}

View File

@@ -0,0 +1,16 @@
pub const Login = struct {};
pub const CharBagTeamModified = struct {
team_index: usize,
modification: enum {
set_leader,
set_char_team,
},
};
pub const SyncSelfScene = struct {
reason: enum {
entrance,
team_modified,
},
};

View File

@@ -0,0 +1,116 @@
const std = @import("std");
const proto = @import("proto");
const logic = @import("../logic.zig");
const network = @import("../network.zig");
const Session = @import("../Session.zig");
const Io = std.Io;
const Allocator = std.mem.Allocator;
const log = std.log.scoped(.messaging);
const meta = std.meta;
const namespaces = &.{
@import("messaging/player.zig"),
@import("messaging/scene.zig"),
@import("messaging/char_bag.zig"),
@import("messaging/friend_chat.zig"),
};
pub fn Request(comptime CSType: type) type {
return struct {
pub const CSMessage = CSType;
message: *const CSMessage,
session: *Session,
};
}
const MsgID = blk: {
var msg_types: []const type = &.{};
for (namespaces) |namespace| {
for (@typeInfo(namespace).@"struct".decls) |decl_info| {
const decl = @field(namespace, decl_info.name);
const fn_info = switch (@typeInfo(@TypeOf(decl))) {
.@"fn" => |info| info,
else => continue,
};
if (fn_info.params.len == 0) continue;
const Param = fn_info.params[0].type.?;
if (!@hasDecl(Param, "CSMessage")) continue;
msg_types = msg_types ++ .{Param.CSMessage};
}
}
var msg_names: [msg_types.len][]const u8 = @splat("");
var msg_ids: [msg_types.len]i32 = @splat(0);
for (msg_types, 0..) |CSMsg, i| {
// Proven to exist by the code above.
msg_names[i] = CSMsg.message_name;
msg_ids[i] = @intFromEnum(proto.messageId(CSMsg));
}
break :blk @Enum(i32, .exhaustive, &msg_names, &msg_ids);
};
pub fn process(
gpa: Allocator,
world: *logic.World,
request: *const network.Request,
) !void {
const recv_msg_id = std.enums.fromInt(MsgID, request.head.msgid) orelse {
return error.MissingHandler;
};
switch (recv_msg_id) {
inline else => |msg_id| {
handler_lookup: inline for (namespaces) |namespace| {
inline for (@typeInfo(namespace).@"struct".decls) |decl_info| {
const decl = @field(namespace, decl_info.name);
const fn_info = switch (@typeInfo(@TypeOf(decl))) {
.@"fn" => |info| info,
else => continue,
};
if (fn_info.params.len == 0) continue;
const Param = fn_info.params[0].type.?;
if (!@hasDecl(Param, "CSMessage")) continue;
if (comptime !std.mem.eql(u8, @tagName(msg_id), Param.CSMessage.message_name))
continue;
var arena: std.heap.ArenaAllocator = .init(gpa);
defer arena.deinit();
var queue: logic.event.Queue = .init(arena.allocator());
var reader: Io.Reader = .fixed(request.body);
var message = proto.decodeMessage(&reader, arena.allocator(), Param.CSMessage) catch
return error.DecodeFailed;
var handler_args: meta.ArgsTuple(@TypeOf(decl)) = undefined;
handler_args[0] = .{
.message = &message,
.session = world.session,
};
inline for (fn_info.params[1..], 1..) |param, i| {
handler_args[i] = logic.queries.resolve(param.type.?, world, &queue, gpa, arena.allocator()) catch {
log.err("message handler for '{s}' requires an optional component", .{@typeName(Param.CSMessage)});
return;
};
}
try @call(.auto, decl, handler_args);
try logic.systems.run(world, &queue, gpa, arena.allocator());
break :handler_lookup;
}
} else comptime unreachable;
},
}
}

View File

@@ -0,0 +1,119 @@
const std = @import("std");
const pb = @import("proto").pb;
const logic = @import("../../logic.zig");
const Assets = @import("../../Assets.zig");
const event = logic.event;
const messaging = logic.messaging;
const Player = logic.Player;
pub fn onCharBagSetTeamLeader(
request: messaging.Request(pb.CS_CHAR_BAG_SET_TEAM_LEADER),
char_bag: Player.Component(.char_bag),
team_modified_tx: event.Sender(.char_bag_team_modified),
) !void {
const log = std.log.scoped(.char_bag_set_team_leader);
if ((request.message.team_type orelse .CHAR_BAG_TEAM_TYPE_MAIN) != .CHAR_BAG_TEAM_TYPE_MAIN)
return; // 'TEMP' teams are not supported.
const team_index = std.math.cast(usize, request.message.team_index) orelse {
log.err("invalid team index: {d}", .{request.message.team_index});
return;
};
const char_index: Player.CharBag.CharIndex = .fromObjectId(
request.message.leaderid,
);
char_bag.data.ensureTeamMember(team_index, char_index) catch |err| switch (err) {
error.InvalidTeamIndex => {
log.err(
"team index is out of range! {d}/{d}",
.{ team_index, char_bag.data.teams.len },
);
return;
},
error.NotTeamMember => {
log.err(
"character with index {d} is not a member of team {d}",
.{ @intFromEnum(char_index), team_index },
);
return;
},
};
const leader_index = &char_bag.data.teams.items(.leader_index)[team_index];
log.info(
"switching leader for team {d} ({d} -> {d})",
.{ team_index, leader_index.*, char_index },
);
leader_index.* = char_index;
try team_modified_tx.send(.{
.team_index = team_index,
.modification = .set_leader,
});
}
pub fn onCharBagSetTeam(
request: messaging.Request(pb.CS_CHAR_BAG_SET_TEAM),
char_bag: Player.Component(.char_bag),
team_modified_tx: event.Sender(.char_bag_team_modified),
) !void {
const log = std.log.scoped(.char_bag_set_team);
const team_index = std.math.cast(usize, request.message.team_index) orelse {
log.err("invalid team index: {d}", .{request.message.team_index});
return;
};
if (request.message.char_team.items.len > Player.CharBag.Team.slots_count) {
log.err(
"char_team exceeds slots count! {d}/{d}",
.{ request.message.char_team.items.len, Player.CharBag.Team.slots_count },
);
return;
}
if (std.mem.findScalar(u64, request.message.char_team.items, request.message.leader_id) == null) {
log.err("leader_id doesn't present in char_team", .{});
return;
}
var new_char_team: Player.CharBag.Team.SlotArray = @splat(.empty);
for (request.message.char_team.items, 0..) |char_id, i| {
if (std.mem.countScalar(u64, request.message.char_team.items, char_id) > 1) {
log.err("duplicated character id: {d}", .{char_id});
return;
}
const char_index: Player.CharBag.CharIndex = .fromObjectId(char_id);
if (@intFromEnum(char_index) >= char_bag.data.chars.len) {
log.err("invalid character object id: {d}", .{char_id});
return;
}
new_char_team[i] = .fromCharIndex(char_index);
}
const teams_slice = char_bag.data.teams.slice();
teams_slice.items(.char_team)[team_index] = new_char_team;
teams_slice.items(.leader_index)[team_index] = .fromObjectId(request.message.leader_id);
try team_modified_tx.send(.{
.team_index = team_index,
.modification = .set_char_team,
});
try request.session.send(pb.SC_CHAR_BAG_SET_TEAM{
.team_type = .CHAR_BAG_TEAM_TYPE_MAIN,
.team_index = request.message.team_index,
.char_team = request.message.char_team,
.scope_name = 1,
.leader_id = request.message.leader_id,
});
}

View File

@@ -0,0 +1,9 @@
const pb = @import("proto").pb;
const logic = @import("../../logic.zig");
const messaging = logic.messaging;
pub fn onCsFriendChatListSimpleSync(
request: messaging.Request(pb.CS_FRIEND_CHAT_LIST_SIMPLE_SYNC),
) !void {
try request.session.send(pb.SC_FRIEND_CHAT_LIST_SIMPLE_SYNC{});
}

View File

@@ -0,0 +1,27 @@
const pb = @import("proto").pb;
const logic = @import("../../logic.zig");
const messaging = logic.messaging;
pub fn onCsPing(
request: messaging.Request(pb.CS_PING),
timer: *logic.Resource.PingTimer,
) !void {
timer.last_client_ts = request.message.client_ts;
try request.session.send(pb.SC_PING{
.client_ts = request.message.client_ts,
.server_ts = timer.serverTime(),
});
}
pub fn onCsFlushSync(
request: messaging.Request(pb.CS_FLUSH_SYNC),
timer: *logic.Resource.PingTimer,
) !void {
timer.last_client_ts = request.message.client_ts;
try request.session.send(pb.SC_FLUSH_SYNC{
.client_ts = request.message.client_ts,
.server_ts = timer.serverTime(),
});
}

View File

@@ -0,0 +1,10 @@
const pb = @import("proto").pb;
const logic = @import("../../logic.zig");
const messaging = logic.messaging;
pub fn onSceneLoadFinish(
_: messaging.Request(pb.CS_SCENE_LOAD_FINISH),
sync_self_scene_tx: logic.event.Sender(.sync_self_scene),
) !void {
try sync_self_scene_tx.send(.{ .reason = .entrance });
}

View File

@@ -0,0 +1,27 @@
const std = @import("std");
const logic = @import("../logic.zig");
const meta = std.meta;
const Allocator = std.mem.Allocator;
pub fn resolve(
comptime Query: type,
world: *logic.World,
event_queue: *logic.event.Queue,
gpa: Allocator,
arena: Allocator,
) !Query {
if (comptime meta.activeTag(@typeInfo(Query)) == .@"struct") {
if (@hasDecl(Query, "allocator_kind")) {
switch (Query.allocator_kind) {
.gpa => return .{ .interface = gpa },
.arena => return .{ .interface = arena },
}
} else if (@hasDecl(Query, "tx_event_kind")) {
return .{ .event_queue = event_queue };
}
}
return world.getComponentByType(Query);
}

View File

@@ -0,0 +1,94 @@
const std = @import("std");
const logic = @import("../logic.zig");
const Session = @import("../Session.zig");
const meta = std.meta;
const event = logic.event;
const Io = std.Io;
const Allocator = std.mem.Allocator;
const namespaces = &.{
@import("systems/base.zig"),
@import("systems/game_vars.zig"),
@import("systems/unlock.zig"),
@import("systems/item_bag.zig"),
@import("systems/char_bag.zig"),
@import("systems/bitset.zig"),
@import("systems/dungeon.zig"),
@import("systems/domain_dev.zig"),
@import("systems/factory.zig"),
@import("systems/stubs.zig"),
@import("systems/friend.zig"),
@import("systems/scene.zig"),
@import("systems/player_saves.zig"),
};
pub const RunSystemsError = Io.Cancelable || Session.SendError || Allocator.Error;
// Initiate an event frame by triggering one.
pub fn triggerEvent(kind: event.Kind, world: *logic.World, gpa: Allocator) RunSystemsError!void {
var arena: std.heap.ArenaAllocator = .init(gpa); // Arena for the event frame.
defer arena.deinit();
var queue: event.Queue = .init(arena.allocator());
try queue.push(kind);
try run(world, &queue, gpa, arena.allocator());
}
// Execute the event frame.
pub fn run(world: *logic.World, queue: *event.Queue, gpa: Allocator, arena: Allocator) RunSystemsError!void {
while (queue.deque.popFront()) |event_kind| {
try dispatchEvent(event_kind, world, queue, gpa, arena);
}
}
// Process single event of the frame.
fn dispatchEvent(
kind: event.Kind,
world: *logic.World,
queue: *event.Queue,
gpa: Allocator,
arena: Allocator,
) RunSystemsError!void {
switch (kind) {
inline else => |payload, tag| inline for (namespaces) |namespace| {
inline for (@typeInfo(namespace).@"struct".decls) |decl_info| {
const decl = @field(namespace, decl_info.name);
const fn_info = switch (@typeInfo(@TypeOf(decl))) {
.@"fn" => |info| info,
else => continue,
};
if (fn_info.params.len == 0) continue;
const Param = fn_info.params[0].type.?;
if (!@hasDecl(Param, "rx_event_kind")) continue;
if (Param.rx_event_kind != tag) continue;
try invoke(payload, decl, world, queue, gpa, arena);
}
},
}
}
fn invoke(
payload: anytype,
decl: anytype,
world: *logic.World,
queue: *event.Queue,
gpa: Allocator,
arena: Allocator,
) !void {
var handler_args: meta.ArgsTuple(@TypeOf(decl)) = undefined;
handler_args[0] = .{ .payload = payload };
inline for (@typeInfo(@TypeOf(decl)).@"fn".params[1..], 1..) |param, i| {
handler_args[i] = logic.queries.resolve(param.type.?, world, queue, gpa, arena) catch {
return;
};
}
try @call(.auto, decl, handler_args);
}

View File

@@ -0,0 +1,21 @@
const pb = @import("proto").pb;
const logic = @import("../../logic.zig");
const Session = @import("../../Session.zig");
const Player = logic.Player;
pub fn syncBaseDataOnLogin(
rx: logic.event.Receiver(.login),
session: *Session,
base_comp: Player.Component(.base),
) !void {
_ = rx;
try session.send(pb.SC_SYNC_BASE_DATA{
.roleid = base_comp.data.role_id,
.role_name = base_comp.data.role_name.view(),
.level = @intFromEnum(base_comp.data.level),
.gender = @enumFromInt(@intFromEnum(base_comp.data.gender)),
.short_id = "1",
});
}

View File

@@ -0,0 +1,27 @@
const std = @import("std");
const pb = @import("proto").pb;
const logic = @import("../../logic.zig");
const Session = @import("../../Session.zig");
const Player = logic.Player;
pub fn syncAllBitset(
rx: logic.event.Receiver(.login),
session: *Session,
bitset: Player.Component(.bitset),
) !void {
_ = rx;
var sync_all_bitset: pb.SC_SYNC_ALL_BITSET = .init;
var sets_buf: [Player.Bitset.Type.count]pb.BITSET_DATA = undefined;
sync_all_bitset.bitset = .initBuffer(&sets_buf);
for (&bitset.data.sets, 1..) |*set, i| {
sync_all_bitset.bitset.appendAssumeCapacity(.{
.type = @intCast(i),
.value = .{ .items = @constCast(&set.masks) },
});
}
try session.send(sync_all_bitset);
}

View File

@@ -0,0 +1,100 @@
const std = @import("std");
const pb = @import("proto").pb;
const logic = @import("../../logic.zig");
const Assets = @import("../../Assets.zig");
const Session = @import("../../Session.zig");
const Player = logic.Player;
pub fn syncCharBag(
rx: logic.event.Receiver(.login),
assets: *const Assets,
session: *Session,
char_bag: Player.Component(.char_bag),
arena: logic.Resource.Allocator(.arena),
) !void {
_ = rx;
var sync_char_bag: pb.SC_SYNC_CHAR_BAG_INFO = .{
.curr_team_index = @intCast(char_bag.data.meta.curr_team_index),
.temp_team_info = .init,
.scope_name = 1,
.max_char_team_member_count = comptime @intCast(Player.CharBag.Team.slots_count),
};
const teams = char_bag.data.teams.slice();
try sync_char_bag.team_info.ensureTotalCapacity(arena.interface, teams.len);
const all_team_slots = try arena.interface.alloc([4]u64, teams.len);
for (
0..,
teams.items(.name),
teams.items(.char_team),
teams.items(.leader_index),
) |i, name, slots, leader_index| {
var char_team: std.ArrayList(u64) = .initBuffer(&all_team_slots[i]);
for (slots) |slot| if (slot != .empty) {
char_team.appendAssumeCapacity(@intFromEnum(slot) + 1);
};
sync_char_bag.team_info.appendAssumeCapacity(.{
.team_name = name.view(),
.char_team = .{ .items = char_team.items },
.leaderid = leader_index.objectId(),
});
}
const chars = char_bag.data.chars.slice();
try sync_char_bag.char_info.ensureTotalCapacity(arena.interface, chars.len);
for (0..chars.len) |i| {
const index: Player.CharBag.CharIndex = @enumFromInt(i);
const template_id = assets.numToStr(.char_id, chars.items(.template_id)[i]) orelse continue;
const skills = assets.char_skill_map.map.getPtr(template_id).?;
var char_info: pb.CHAR_INFO = .{
.objid = index.objectId(),
.templateid = template_id,
.char_type = .default_type,
.level = chars.items(.level)[i],
.exp = chars.items(.exp)[i],
.is_dead = chars.items(.is_dead)[i],
.weapon_id = chars.items(.weapon_id)[i].instId(),
.own_time = chars.items(.own_time)[i],
.equip_medicine_id = chars.items(.equip_medicine_id)[i],
.potential_level = chars.items(.potential_level)[i],
.normal_skill = skills.normal_skill,
.battle_info = .{ .hp = chars.items(.hp)[i], .ultimatesp = chars.items(.ultimate_sp)[i] },
.skill_info = .{
.normal_skill = skills.normal_skill,
.combo_skill = skills.combo_skill,
.ultimate_skill = skills.ultimate_skill,
.disp_normal_attack_skill = skills.attack_skill,
},
.talent = .{},
.battle_mgr_info = .{
.msg_generation = @truncate(index.objectId()),
.battle_inst_id = @truncate(index.objectId()),
.part_inst_info = .{},
},
.trial_data = .{},
};
try char_info.skill_info.?.level_info.ensureTotalCapacity(arena.interface, skills.all_skills.len);
for (skills.all_skills) |name| {
char_info.skill_info.?.level_info.appendAssumeCapacity(.{
.skill_id = name,
.skill_level = 1,
.skill_max_level = 1,
.skill_enhanced_level = 1,
});
}
try sync_char_bag.char_info.append(arena.interface, char_info);
}
try session.send(sync_char_bag);
}

View File

@@ -0,0 +1,23 @@
const pb = @import("proto").pb;
const logic = @import("../../logic.zig");
const Assets = @import("../../Assets.zig");
const Session = @import("../../Session.zig");
pub fn syncDomainDevSystem(
rx: logic.event.Receiver(.login),
session: *Session,
assets: *const Assets,
arena: logic.Resource.Allocator(.arena),
) !void {
_ = rx;
var domain_dev_sync: pb.SC_DOMAIN_DEVELOPMENT_SYSTEM_SYNC = .init;
for (assets.table(.domain_data).keys()) |chapter_id| {
try domain_dev_sync.domains.append(arena.interface, .{
.chapter_id = chapter_id,
.dev_degree = .{ .level = 1 },
});
}
try session.send(domain_dev_sync);
}

View File

@@ -0,0 +1,16 @@
const pb = @import("proto").pb;
const logic = @import("../../logic.zig");
const Session = @import("../../Session.zig");
pub fn syncFullDungeonStatus(
rx: logic.event.Receiver(.login),
session: *Session,
) !void {
_ = rx;
// TODO
try session.send(pb.SC_SYNC_FULL_DUNGEON_STATUS{
.cur_stamina = 200,
.max_stamina = 200,
});
}

View File

@@ -0,0 +1,53 @@
const pb = @import("proto").pb;
const logic = @import("../../logic.zig");
const Assets = @import("../../Assets.zig");
const Session = @import("../../Session.zig");
const default_chapter = "domain_2";
pub fn syncFactoryData(
rx: logic.event.Receiver(.login),
session: *Session,
assets: *const Assets,
arena: logic.Resource.Allocator(.arena),
) !void {
_ = rx;
try session.send(pb.SC_FACTORY_SYNC{
.stt = .init,
.formula_man = .init,
.progress_status = .init,
});
var factory_sync_scope: pb.SC_FACTORY_SYNC_SCOPE = .{
.scope_name = 1,
.current_chapter_id = default_chapter,
.transport_route = .init,
.book_mark = .init,
.sign_mgr = .init,
.shared_mgr = .init,
};
for (assets.table(.domain_data).keys()) |chapter_id| {
try factory_sync_scope.active_chapter_ids.append(arena.interface, chapter_id);
}
try session.send(factory_sync_scope);
for (assets.table(.domain_data).keys()) |chapter_id| {
try session.send(pb.SC_FACTORY_SYNC_CHAPTER{
.chapter_id = chapter_id,
.blackboard = .{ .power = .{ .is_stop_by_power = true } },
.pin_board = .{},
.statistic = .{},
.pending_place = .{},
});
try session.send(pb.SC_FACTORY_HS{
.blackboard = .{
.power = .{ .is_stop_by_power = true },
},
.chapter_id = chapter_id,
});
}
}

View File

@@ -0,0 +1,19 @@
const pb = @import("proto").pb;
const logic = @import("../../logic.zig");
const Session = @import("../../Session.zig");
pub fn syncPersonalData(
rx: logic.event.Receiver(.login),
session: *Session,
) !void {
_ = rx;
// TODO
try session.send(pb.SC_FRIEND_PERSONAL_DATA_SYNC{
.data = .{
.user_avatar_id = 7,
.User_avatar_frame_id = 3,
.business_card_topic_id = 11,
},
});
}

View File

@@ -0,0 +1,32 @@
const pb = @import("proto").pb;
const logic = @import("../../logic.zig");
const Session = @import("../../Session.zig");
const Player = logic.Player;
pub fn syncAllGameVars(
rx: logic.event.Receiver(.login),
session: *Session,
game_vars: Player.Component(.game_vars),
arena: logic.Resource.Allocator(.arena),
) !void {
_ = rx;
var sync_all_game_var: pb.SC_SYNC_ALL_GAME_VAR = .init;
try sync_all_game_var.server_vars.ensureTotalCapacity(arena.interface, game_vars.data.server_vars.len);
try sync_all_game_var.client_vars.ensureTotalCapacity(arena.interface, game_vars.data.client_vars.len);
for (game_vars.data.server_vars) |sv| {
sync_all_game_var.server_vars.appendAssumeCapacity(
.{ .key = @intFromEnum(sv.key), .value = sv.value },
);
}
for (game_vars.data.client_vars) |cv| {
sync_all_game_var.client_vars.appendAssumeCapacity(
.{ .key = cv.key, .value = cv.value },
);
}
try session.send(sync_all_game_var);
}

View File

@@ -0,0 +1,55 @@
const pb = @import("proto").pb;
const logic = @import("../../logic.zig");
const Session = @import("../../Session.zig");
const Player = logic.Player;
pub fn syncItemBagScopes(
rx: logic.event.Receiver(.login),
session: *Session,
item_bag: Player.Component(.item_bag),
char_bag: Player.Component(.char_bag),
arena: logic.Resource.Allocator(.arena),
) !void {
_ = rx;
var item_bag_scope_sync: pb.SC_ITEM_BAG_SCOPE_SYNC = .{
.bag = .init,
.quick_bar = .init,
.assistant = .init,
.scope_name = 1,
};
var weapon_depot: pb.SCD_ITEM_DEPOT = .init;
try weapon_depot.inst_list.ensureTotalCapacity(arena.interface, item_bag.data.weapon_depot.len);
const weapon_slice = item_bag.data.weapon_depot.slice();
for (0..weapon_slice.len) |i| {
const weapon_index: Player.ItemBag.WeaponIndex = @enumFromInt(i);
const weapon = weapon_slice.get(i);
weapon_depot.inst_list.appendAssumeCapacity(.{
.count = 1,
.inst = .{
.inst_id = weapon_index.instId(),
.inst_impl = .{ .weapon = .{
.inst_id = weapon_index.instId(),
.template_id = weapon.template_id,
.exp = weapon.exp,
.weapon_lv = weapon.weapon_lv,
.refine_lv = weapon.refine_lv,
.breakthrough_lv = weapon.breakthrough_lv,
.attach_gem_id = weapon.attach_gem_id,
.equip_char_id = if (char_bag.data.charIndexWithWeapon(weapon_index)) |char_index|
char_index.objectId()
else
0,
} },
},
});
}
try item_bag_scope_sync.depot.append(arena.interface, .{ .key = 1, .value = weapon_depot });
try session.send(item_bag_scope_sync);
}

View File

@@ -0,0 +1,38 @@
const std = @import("std");
const pb = @import("proto").pb;
const logic = @import("../../logic.zig");
const Session = @import("../../Session.zig");
const fs = @import("../../fs.zig");
const Io = std.Io;
const Player = logic.Player;
const log = std.log.scoped(.player_saves);
pub fn saveCharBagTeams(
_: logic.event.Receiver(.char_bag_team_modified),
char_bag: Player.Component(.char_bag),
player_id: logic.World.PlayerId,
io: Io,
) !void {
const data_dir = fs.persistence.openPlayerDataDir(io, player_id.uid) catch |err| switch (err) {
error.Canceled => |e| return e,
else => |e| {
log.err(
"failed to open data dir for player with uid {d}: {t}",
.{ player_id.uid, e },
);
return;
},
};
defer data_dir.close(io);
fs.persistence.saveCharBagComponent(io, data_dir, char_bag.data, .teams) catch |err| switch (err) {
error.Canceled => |e| return e,
else => |e| {
log.err("save failed: {t}", .{e});
return;
},
};
}

View File

@@ -0,0 +1,180 @@
const std = @import("std");
const pb = @import("proto").pb;
const logic = @import("../../logic.zig");
const Session = @import("../../Session.zig");
const Assets = @import("../../Assets.zig");
const Player = logic.Player;
const ArrayList = std.ArrayList;
const Allocator = std.mem.Allocator;
const default_level = "map02_lv001";
pub fn enterSceneOnLogin(
rx: logic.event.Receiver(.login),
session: *Session,
assets: *const Assets,
base_comp: Player.Component(.base),
) !void {
_ = rx;
const level_config = assets.level_config_table.getPtr(default_level).?;
const position: pb.VECTOR = .{
.X = level_config.playerInitPos.x,
.Y = level_config.playerInitPos.y,
.Z = level_config.playerInitPos.z,
};
try session.send(pb.SC_CHANGE_SCENE_BEGIN_NOTIFY{
.scene_num_id = level_config.idNum,
.position = position,
.pass_through_data = .init,
});
try session.send(pb.SC_ENTER_SCENE_NOTIFY{
.role_id = base_comp.data.role_id,
.scene_num_id = level_config.idNum,
.position = position,
.pass_through_data = .init,
});
}
pub fn refreshCharTeam(
rx: logic.event.Receiver(.char_bag_team_modified),
char_bag: Player.Component(.char_bag),
sync_tx: logic.event.Sender(.sync_self_scene),
) !void {
switch (rx.payload.modification) {
.set_leader => return, // Doesn't require any action from server.
.set_char_team => if (rx.payload.team_index == char_bag.data.meta.curr_team_index) {
// If the current active team has been modified, it has to be re-spawned.
try sync_tx.send(.{ .reason = .team_modified });
},
}
}
pub fn syncSelfScene(
rx: logic.event.Receiver(.sync_self_scene),
session: *Session,
arena: logic.Resource.Allocator(.arena),
char_bag: logic.Player.Component(.char_bag),
assets: *const Assets,
) !void {
const reason: pb.SELF_INFO_REASON_TYPE = switch (rx.payload.reason) {
.entrance => .SLR_ENTER_SCENE,
.team_modified => .SLR_CHANGE_TEAM,
};
const level_config = assets.level_config_table.getPtr(default_level).?;
const position: pb.VECTOR = .{
.X = level_config.playerInitPos.x,
.Y = level_config.playerInitPos.y,
.Z = level_config.playerInitPos.z,
};
const team_index = char_bag.data.meta.curr_team_index;
const leader_index = char_bag.data.teams.items(.leader_index)[team_index];
var self_scene_info: pb.SC_SELF_SCENE_INFO = .{
.scene_num_id = level_config.idNum,
.self_info_reason = @intFromEnum(reason),
.teamInfo = .{
.team_type = .CHAR_BAG_TEAM_TYPE_MAIN,
.team_index = @intCast(team_index),
.cur_leader_id = leader_index.objectId(),
.team_change_token = 0,
},
.scene_impl = .{ .empty = .{} },
.detail = .{},
};
for (char_bag.data.teams.items(.char_team)[team_index]) |slot| {
const char_index = slot.charIndex() orelse continue;
const char_template_id_num = char_bag.data.chars.items(.template_id)[@intFromEnum(char_index)];
const char_template_id = assets.numToStr(.char_id, char_template_id_num).?;
const char_data = assets.table(.character).getPtr(char_template_id).?;
var scene_char: pb.SCENE_CHARACTER = .{
.level = 1,
.battle_info = .{
.msg_generation = @intCast(char_index.objectId()),
.battle_inst_id = @intCast(char_index.objectId()),
.part_inst_info = .{},
},
.common_info = .{
.id = char_index.objectId(),
.templateid = char_template_id,
.position = position,
.rotation = .{},
.scene_num_id = level_config.idNum,
},
};
for (char_data.attributes[0].Attribute.attrs) |attr| {
if (attr.attrType == .max_hp)
scene_char.common_info.?.hp = attr.attrValue;
try scene_char.attrs.append(arena.interface, .{
.attr_type = @intFromEnum(attr.attrType),
.basic_value = attr.attrValue,
.value = attr.attrValue,
});
}
scene_char.battle_info.?.skill_list = try packCharacterSkills(
arena.interface,
assets,
char_template_id,
);
try self_scene_info.detail.?.char_list.append(arena.interface, scene_char);
}
try session.send(self_scene_info);
}
fn packCharacterSkills(
arena: Allocator,
assets: *const Assets,
template_id: []const u8,
) Allocator.Error!ArrayList(pb.SERVER_SKILL) {
const char_skills = assets.char_skill_map.map.getPtr(template_id).?.all_skills;
var list: ArrayList(pb.SERVER_SKILL) = try .initCapacity(
arena,
char_skills.len + assets.common_skill_config.config.Character.skillConfigs.len,
);
errdefer comptime unreachable;
for (char_skills, 1..) |name, i| {
list.appendAssumeCapacity(.{
.skill_id = .{
.id_impl = .{ .str_id = name },
.type = .BATTLE_ACTION_OWNER_TYPE_SKILL,
},
.blackboard = .{},
.inst_id = (100 + i),
.level = 1,
.source = .BATTLE_SKILL_SOURCE_DEFAULT,
.potential_lv = 1,
.is_enable = true,
});
}
for (assets.common_skill_config.config.Character.skillConfigs, char_skills.len + 1..) |config, i| {
list.appendAssumeCapacity(.{
.skill_id = .{
.id_impl = .{ .str_id = config.skillId },
.type = .BATTLE_ACTION_OWNER_TYPE_SKILL,
},
.blackboard = .{},
.inst_id = (100 + i),
.level = 1,
.source = .BATTLE_SKILL_SOURCE_DEFAULT,
.potential_lv = 1,
.is_enable = true,
});
}
return list;
}

View File

@@ -0,0 +1,50 @@
const pb = @import("proto").pb;
const logic = @import("../../logic.zig");
const Session = @import("../../Session.zig");
// Sends the dummy 'SYNC' messages for the components that aren't implemented yet.
pub fn loginSyncStub(
rx: logic.event.Receiver(.login),
session: *Session,
) !void {
_ = rx;
try session.send(pb.SC_ADVENTURE_SYNC_ALL{
.level = 1,
.world_level = 1,
.unlock_world_level = 1,
});
try session.send(pb.SC_ADVENTURE_BOOK_SYNC{
.adventure_book_stage = 1,
});
try session.send(pb.SC_SYNC_ALL_MINI_GAME.init);
try session.send(pb.SC_SYNC_ALL_MAIL.init);
try session.send(pb.SC_KITE_STATION_SYNC_ALL.init);
try session.send(pb.SC_SYNC_ALL_GUIDE.init);
try session.send(pb.SC_GLOBAL_EFFECT_SYNC_ALL.init);
try session.send(pb.SC_SYNC_ALL_DOODAD_GROUP.init);
try session.send(pb.SC_SETTLEMENT_SYNC_ALL.init);
try session.send(pb.SC_DOMAIN_DEPOT_SYNC_ALL_INFO.init);
try session.send(pb.SC_SYNC_ALL_DIALOG.init);
try session.send(pb.SC_SYNC_ALL_ROLE_SCENE.init);
try session.send(pb.SC_SYNC_ALL_WIKI.init);
try session.send(pb.SC_RECYCLE_BIN_SYSTEM_SYNC_ALL.init);
try session.send(pb.SC_SYNC_ALL_STAT.init);
try session.send(pb.SC_BP_SYNC_ALL{
.season_data = .init,
.level_data = .init,
.bp_track_mgr = .init,
.bp_task_mgr = .init,
});
try session.send(pb.SC_SYNC_ALL_MISSION.init);
try session.send(pb.SC_SPACESHIP_SYNC{
.assist_data = .init,
.expedition_data = .init,
});
try session.send(pb.SC_ACHIEVE_SYNC{
.achieve_display_info = .init,
});
}

View File

@@ -0,0 +1,22 @@
const pb = @import("proto").pb;
const logic = @import("../../logic.zig");
const Session = @import("../../Session.zig");
const Player = logic.Player;
pub fn syncAllUnlock(
rx: logic.event.Receiver(.login),
session: *Session,
unlock: Player.Component(.unlock),
arena: logic.Resource.Allocator(.arena),
) !void {
_ = rx;
var sync_all_unlock: pb.SC_SYNC_ALL_UNLOCK = .init;
try sync_all_unlock.unlock_systems.appendSlice(
arena.interface,
@ptrCast(unlock.data.unlocked_systems),
);
try session.send(sync_all_unlock);
}

140
gamesv/src/main.zig Normal file
View File

@@ -0,0 +1,140 @@
const std = @import("std");
const common = @import("common");
const Session = @import("Session.zig");
const Assets = @import("Assets.zig");
const Io = std.Io;
const Init = std.process.Init;
const Allocator = std.mem.Allocator;
const net = Io.net;
const assert = std.debug.assert;
const log = std.log.scoped(.gamesv);
const Options = struct {
listen_address: []const u8 = "127.0.0.1:30000",
};
fn start(init: Init.Minimal, io: Io, gpa: Allocator) u8 {
const args = common.args.parseOrPrintUsageAlloc(Options, gpa, init.args) orelse return 1;
defer args.deinit();
std.debug.print(
\\ __ ____ _____
\\ / / / __ \ / ___/
\\ / / / /_/ /_____\__ \
\\ / /___/ _, _/_____/__/ /
\\/_____/_/ |_| /____/
\\
, .{});
var assets: Assets = loadAssetsMultithreaded(gpa) catch |err| switch (err) {
error.Canceled => return 0, // got interrupted early
else => |e| {
log.err("failed to load assets: {t}", .{e});
return 1;
},
};
defer assets.deinit();
const listen_address = net.IpAddress.parseLiteral(args.options.listen_address) catch {
log.err("Invalid listen address specified.", .{});
return 1;
};
var server = listen_address.listen(io, .{ .reuse_address = true }) catch |err| switch (err) {
error.AddressInUse => {
log.err(
"Address '{f}' is in use. Another instance of this server might be already running.",
.{listen_address},
);
return 1;
},
else => |e| {
log.err("Failed to listen at '{f}': {t}", .{ listen_address, e });
return 1;
},
};
defer server.deinit(io);
var sessions: Io.Group = .init;
defer sessions.cancel(io);
var preferred_clock: Io.Clock = .awake;
var concurrency_availability: Session.ConcurrencyAvailability = .undetermined;
log.info("listening at {f}", .{listen_address});
defer log.info("shutting down...", .{});
accept_loop: while (true) {
const stream = server.accept(io) catch |err| switch (err) {
error.Canceled => break, // Shutdown requested
error.ProcessFdQuotaExceeded, error.SystemFdQuotaExceeded, error.SystemResources => {
// System is overloaded. Stop accepting new connections for now.
while (true) {
if (io.sleep(.fromSeconds(1), preferred_clock)) break else |sleep_err| switch (sleep_err) {
error.Canceled => break :accept_loop, // Shutdown requested
error.UnsupportedClock => preferred_clock = if (preferred_clock == .awake)
.real
else
continue :accept_loop, // No clock available.
error.Unexpected => continue :accept_loop, // Sleep is unimportant then.
}
}
continue;
},
else => |e| { // Something else happened. We probably want to report this and continue.
log.err("TCP accept failed: {t}", .{e});
continue;
},
};
var io_options: Session.IoOptions = .{
.preferred_clock = preferred_clock,
.concurrency = .available,
};
if (sessions.concurrent(io, Session.process, .{ io, gpa, &assets, stream, io_options })) {
concurrency_availability = .available;
} else |err| switch (err) {
error.ConcurrencyUnavailable => switch (concurrency_availability) {
.available => stream.close(io), // Can't process more connections atm.
.unavailable, .undetermined => {
// The environment doesn't support concurrency.
if (concurrency_availability != .unavailable)
log.warn("Environment doesn't support concurrency. One session at a time will be processed.", .{});
concurrency_availability = .unavailable;
io_options.concurrency = .unavailable;
sessions.async(io, Session.process, .{ io, gpa, &assets, stream, io_options });
},
},
}
}
return 0;
}
// Loads assets using Io.Threaded under the hood
fn loadAssetsMultithreaded(gpa: Allocator) !Assets {
var threaded: Io.Threaded = .init(gpa, .{ .environ = .empty });
defer threaded.deinit();
return try Assets.load(threaded.io(), gpa);
}
pub fn main(init: Init.Minimal) u8 {
var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
defer assert(.ok == debug_allocator.deinit());
const gpa = debug_allocator.allocator();
var poll: common.io.Poll(.{}) = .init(gpa);
defer poll.deinit();
const io = poll.io();
return start(init, io, gpa);
}

45
gamesv/src/network.zig Normal file
View File

@@ -0,0 +1,45 @@
const std = @import("std");
const proto = @import("proto");
const pb = proto.pb;
const Io = std.Io;
const Crc32 = std.hash.Crc32;
pub const Request = struct {
head: pb.CSHead,
body: []u8,
pub const ReadError = error{
HeadDecodeError,
ChecksumMismatch,
InvalidMessageId,
} || Io.Reader.Error;
pub fn read(reader: *Io.Reader) ReadError!Request {
const header_length = try reader.takeInt(u8, .little);
const body_length = try reader.takeInt(u16, .little);
const payload = try reader.take(header_length + body_length);
var head_reader: Io.Reader = .fixed(payload[0..header_length]);
const head = proto.decodeMessage(
&head_reader,
.failing, // CSHead contains only scalar fields. No allocation needed.
pb.CSHead,
) catch return error.HeadDecodeError;
if (std.enums.fromInt(pb.CSMessageID, head.msgid) == null)
return error.InvalidMessageId;
const body = payload[header_length..];
const checksum = Crc32.hash(body);
if (checksum != head.checksum)
return error.ChecksumMismatch;
return .{ .head = head, .body = body };
}
pub fn msgId(request: *const Request) pb.CSMessageID {
return @enumFromInt(request.head.msgid);
}
};

644
proto/gen/src/main.zig Normal file
View File

@@ -0,0 +1,644 @@
const std = @import("std");
const Io = std.Io;
const Allocator = std.mem.Allocator;
const CompilationMode = enum {
full,
structures,
descriptors,
pub fn outputStructures(cm: CompilationMode) bool {
return switch (cm) {
.full, .structures => true,
.descriptors => false,
};
}
pub fn outputDescriptors(cm: CompilationMode) bool {
return switch (cm) {
.full, .descriptors => true,
.structures => false,
};
}
};
pub fn main(init: std.process.Init) u8 {
const args = init.minimal.args.toSlice(init.arena.allocator()) catch {
std.log.err("couldn't obtain cli arguments", .{});
return 1;
};
if (args.len <= 1) {
std.log.err("no input files", .{});
return 1;
}
var mode: CompilationMode = .full;
var stdout_buffer: [1024]u8 = undefined;
var stdout = Io.File.stdout().writer(init.io, stdout_buffer[0..]);
writeOutputFileHeader(&stdout.interface) catch |err| {
std.log.err("couldn't write output: {t}", .{err});
return 1;
};
const cwd = Io.Dir.cwd();
for (args[1..]) |path| {
if (path[0] == '-') {
// Compilation mode specifier.
mode = std.meta.stringToEnum(CompilationMode, path[1..]) orelse {
std.log.err("invalid parameter: {s}", .{path});
return 1;
};
continue;
}
const content = cwd.readFileAlloc(init.io, path, init.gpa, .unlimited) catch |err| {
std.log.err("couldn't read input file '{s}': {t}", .{ path, err });
return 1;
};
defer init.gpa.free(content);
if (content[content.len - 1] != '\n') {
std.log.err("input file '{s}' doesn't have a terminating newline", .{path});
return 1;
}
var arena: std.heap.ArenaAllocator = .init(init.gpa);
defer arena.deinit();
writeGeneratedCodeForProto(arena.allocator(), &stdout.interface, content, path, mode) catch |err| {
std.log.err("failed to generate code for '{s}': {t}", .{ path, err });
return 1;
};
}
stdout.interface.flush() catch @panic("stdout.flush");
return 0;
}
fn writeOutputFileHeader(out: *Io.Writer) !void {
try out.writeAll(
\\const std = @import("std");
\\fn MapEntry(comptime K: type, comptime V: type) type {
\\ return struct {
\\ pub const map_entry: void = {};
\\ pub const init: @This() = .{
\\ .key = switch (@typeInfo(K)) {
\\ .int => 0,
\\ .bool => false,
\\ else => if (K == []const u8) "" else .init,
\\ },
\\ .value = switch (@typeInfo(V)) {
\\ .int => 0,
\\ .bool => false,
\\ else => if (V == []const u8) "" else .init,
\\ },
\\ };
\\ pub const key_field_desc: FieldDesc = .{ .number = 1 };
\\ pub const value_field_desc: FieldDesc = .{ .number = 2 };
\\
\\ key: K,
\\ value: V,
\\ };
\\}
\\
\\pub const FieldDesc = struct {
\\ number: u32,
\\};
\\
);
}
fn writeGeneratedCodeForProto(
arena: Allocator,
out: *Io.Writer,
input_proto: []const u8,
input_path: []const u8,
mode: CompilationMode,
) !void {
try out.print("\n// {s}\n\n", .{input_path});
var l: Lexer = .init(input_path, input_proto);
while (!l.isAtEnd()) {
const keyword = try l.expect(.keyword);
switch (keyword.keyword) {
.syntax => {
try l.expectPunct(.equal_sign);
const syntax = try l.expect(.quoted);
if (!std.mem.eql(u8, syntax.quoted, "proto3")) {
std.log.err("{f}", .{Diagnostic.custom(&l, syntax, "unsupported syntax; this compiler only supports \"proto3\"")});
return error.UnsupportedSyntax;
}
try l.expectPunct(.semicolon);
},
.package => {
while (true) {
_ = try l.expect(.name);
const p = try l.expect(.punct);
switch (p.punct) {
.semicolon => break,
.dot => continue,
else => {
std.log.err("{f}", .{Diagnostic.unexpected(&l, p)});
return error.UnexpectedToken;
},
}
}
},
.option => {
_ = try l.expect(.name);
try l.expectPunct(.equal_sign);
_ = try l.expect(.quoted);
try l.expectPunct(.semicolon);
},
.import => {
_ = try l.expect(.quoted);
try l.expectPunct(.semicolon);
},
.@"enum" => try compileEnum(mode, &l, out),
.message => try compileMessage(mode, arena, &l, out),
else => {
std.log.err("{f}", .{Diagnostic.unexpected(&l, keyword)});
return error.UnexpectedToken;
},
}
}
}
fn compileEnum(mode: CompilationMode, l: *Lexer, out: *Io.Writer) !void {
const name = try l.expect(.name);
const output_structures = mode.outputStructures();
if (output_structures)
try out.print("pub const {s} = enum(i32) {{\n", .{name.name});
try l.expectPunct(.open_curly);
while (true) {
const token = try l.next() orelse return error.EndOfFile;
if (std.meta.activeTag(token) == .punct and token.punct == .close_curly) break;
if (std.meta.activeTag(token) != .name) {
std.log.err("{f}", .{Diagnostic.unexpected(l, token)});
return error.UnexpectedToken;
}
try l.expectPunct(.equal_sign);
const discriminant = try l.expect(.number);
try l.expectPunct(.semicolon);
if (output_structures)
try out.print(" {s} = {s},\n", .{ token.name, discriminant.number });
}
if (output_structures)
try out.writeAll("};\n\n");
}
const FieldType = union(enum) {
regular: []const u8,
repeated: []const u8,
map: struct {
key: []const u8,
value: []const u8,
},
pub fn parse(l: *Lexer, token: Lexer.Token) !FieldType {
if (std.meta.activeTag(token) == .keyword and token.keyword == .repeated) {
return .{ .repeated = (try l.expect(.name)).name };
} else if (std.meta.activeTag(token) == .keyword and token.keyword == .map) {
try l.expectPunct(.open_triangle);
const key = (try l.expect(.name)).name;
try l.expectPunct(.comma);
const value = (try l.expect(.name)).name;
try l.expectPunct(.close_triangle);
return .{ .map = .{
.key = key,
.value = value,
} };
} else if (std.meta.activeTag(token) == .name) {
return .{ .regular = token.name };
} else {
std.log.err("{f}", .{Diagnostic.unexpected(l, token)});
return error.UnexpectedToken;
}
}
pub fn format(ft: FieldType, w: *Io.Writer) !void {
switch (ft) {
.regular => |name| try w.print("{f}", .{TypeName{ .name = name, .standalone = true }}),
.repeated => |name| try w.print("std.ArrayList({f})", .{TypeName{ .name = name }}),
.map => |kv| try w.print("std.ArrayList(MapEntry({f}, {f}))", .{
TypeName{ .name = kv.key },
TypeName{ .name = kv.value },
}),
}
}
const TypeName = struct {
name: []const u8,
standalone: bool = false,
pub fn format(tn: TypeName, w: *Io.Writer) !void {
if (std.meta.stringToEnum(Primitive, tn.name)) |primitive| {
try w.print("{f}", .{primitive});
} else {
if (tn.standalone) try w.writeByte('?');
try w.print("{s}", .{tn.name});
}
}
};
const Primitive = enum {
bool,
int32,
int64,
uint32,
uint64,
string,
bytes,
float,
double,
pub fn format(p: Primitive, w: *Io.Writer) !void {
try w.writeAll(switch (p) {
.bool => "bool",
.int32 => "i32",
.int64 => "i64",
.uint32 => "u32",
.uint64 => "u64",
.string, .bytes => "[]const u8",
.float => "f32",
.double => "f64",
});
}
};
};
const FieldDesc = struct {
name: []const u8,
number: []const u8,
};
fn compileMessage(mode: CompilationMode, arena: Allocator, l: *Lexer, out: *Io.Writer) !void {
const name = try l.expect(.name);
const output_structures = mode.outputStructures();
const output_descriptors = mode.outputDescriptors();
try out.print("pub const {s} = struct {{\n", .{name.name});
if (output_structures) {
try out.writeAll(" pub const init: @This() = .{};\n");
try out.print(" pub const message_name = \"{s}\";\n", .{name.name});
}
try l.expectPunct(.open_curly);
var field_descs: std.ArrayList(FieldDesc) = .empty;
while (true) {
const token = try l.next() orelse return error.EndOfFile;
if (std.meta.activeTag(token) == .punct and token.punct == .close_curly) break;
if (std.meta.activeTag(token) == .keyword and token.keyword == .oneof) {
const oneof_name = try l.expect(.name);
try l.expectPunct(.open_curly);
if (output_structures)
try out.print(" {s}: ?union(enum) {{\n", .{oneof_name.name});
while (true) {
const t = try l.next() orelse return error.EndOfFile;
if (std.meta.activeTag(t) == .punct and t.punct == .close_curly) break;
if (std.meta.activeTag(t) != .name) {
std.log.err("{f}", .{Diagnostic.unexpected(l, t)});
return error.UnexpectedToken;
}
const field_type = try FieldType.parse(l, t);
const field_name = try l.expectNameOrKeyword();
try l.expectPunct(.equal_sign);
const field_number = (try l.expect(.number)).number;
try l.expectPunct(.semicolon);
if (output_structures)
try out.print(" {s}: {f},\n", .{ field_name, field_type });
try field_descs.append(arena, .{
.name = field_name,
.number = field_number,
});
}
if (output_structures)
try out.writeAll(" },\n");
continue;
}
const field_type = try FieldType.parse(l, token);
const field_name = try l.expectNameOrKeyword();
try l.expectPunct(.equal_sign);
const field_number = (try l.expect(.number)).number;
try l.expectPunct(.semicolon);
try field_descs.append(arena, .{
.name = field_name,
.number = field_number,
});
if (output_structures)
try out.print(" {s}: {f} = {s},\n", .{ field_name, field_type, switch (field_type) {
.regular => |regular| if (std.meta.stringToEnum(FieldType.Primitive, regular)) |p| switch (p) {
.int32, .int64, .uint32, .uint64, .float, .double => "0",
.string, .bytes => "\"\"",
.bool => "false",
} else "null",
.repeated, .map => ".empty",
} });
}
if (output_descriptors) {
for (field_descs.items) |desc| {
try out.print(
" pub const {s}_field_desc: FieldDesc = .{{ .number = {s} }};\n",
.{ desc.name, desc.number },
);
}
}
try out.writeAll("};\n\n");
}
const Diagnostic = struct {
where: Placement,
kind: union(enum) {
unexpected: Lexer.Token,
custom: []const u8,
},
pub fn unexpected(l: *const Lexer, t: Lexer.Token) Diagnostic {
return .{
.where = .extract(l, t.size()),
.kind = .{ .unexpected = t },
};
}
pub fn custom(l: *const Lexer, t: Lexer.Token, message: []const u8) Diagnostic {
return .{
.where = .extract(l, t.size()),
.kind = .{ .custom = message },
};
}
pub fn customNoToken(l: *const Lexer, width: usize, message: []const u8) Diagnostic {
return .{
.where = .extract(l, width),
.kind = .{ .custom = message },
};
}
pub fn format(d: Diagnostic, w: *Io.Writer) !void {
try w.print("{s}:{d}:{d}: ", .{ d.where.file, d.where.line, d.where.pos });
switch (d.kind) {
.unexpected => |token| {
try w.print("unexpected {f}\n", .{token});
},
.custom => |message| {
try w.print("{s}\n", .{message});
},
}
try w.print("{s}\n", .{d.where.region});
try w.splatByteAll(' ', d.where.pos);
try w.splatByteAll('^', d.where.width);
try w.writeByte('\n');
}
const Placement = struct {
region: []const u8,
file: []const u8,
line: usize,
pos: usize,
width: usize,
fn extract(l: *const Lexer, unit_width: usize) Placement {
const cur_pos = l.start.len - l.content.len - unit_width;
const line_end = cur_pos + unit_width + (std.mem.findScalar(u8, l.content, '\n') orelse l.content.len);
const line_beginning = if (std.mem.findScalarLast(u8, l.start[0..cur_pos], '\n')) |nl| nl + 1 else 0;
return .{
.file = l.filename,
.region = l.start[line_beginning..line_end],
.line = l.line,
.pos = cur_pos - line_beginning,
.width = unit_width,
};
}
};
};
const Lexer = struct {
filename: []const u8,
start: []const u8,
content: []const u8,
line: usize = 1,
pub fn init(filename: []const u8, content: []const u8) Lexer {
return .{ .filename = filename, .start = content, .content = content };
}
pub fn isAtEnd(l: *Lexer) bool {
l.skipWhitespaces();
return l.content.len == 0;
}
pub fn next(l: *Lexer) !?Token {
if (l.isAtEnd()) return null;
if (std.enums.fromInt(Token.Punct, l.content[0])) |punct| {
l.content = l.content[1..];
return .{ .punct = punct };
}
switch (l.content[0]) {
'-', '0'...'9' => return .{ .number = l.consumeNumber() },
'a'...'z', 'A'...'Z', '_' => {
const name = l.consumeName();
return if (std.meta.stringToEnum(Token.Keyword, name)) |keyword|
.{ .keyword = keyword }
else
.{ .name = name };
},
'"' => return .{ .quoted = try l.consumeEnclosed('"') },
else => {
l.content = l.content[1..];
std.log.err("{f}", .{Diagnostic.customNoToken(l, 1, "unexpected character")});
return error.UnexpectedCharacter;
},
}
}
pub fn expectPunct(l: *Lexer, expected: Token.Punct) !void {
const p = try l.expect(.punct);
if (p.punct != expected) {
std.log.err("{f}", .{Diagnostic.unexpected(l, p)});
return error.UnexpectedToken;
}
}
pub fn expect(l: *Lexer, expected_kind: std.meta.Tag(Token)) !Token {
const t = try l.next() orelse return error.EndOfFile;
if (std.meta.activeTag(t) != expected_kind) {
std.log.err("{f}", .{Diagnostic.unexpected(l, t)});
return error.UnexpectedToken;
}
return t;
}
// apparently, field names can consist of keywords...
pub fn expectNameOrKeyword(l: *Lexer) ![]const u8 {
switch (try l.next() orelse return error.EndOfFile) {
.name => |name| return name,
.keyword => |keyword| return @tagName(keyword),
else => |t| {
std.log.err("{f}", .{Diagnostic.unexpected(l, t)});
return error.UnexpectedToken;
},
}
}
fn consumeNumber(l: *Lexer) []const u8 {
const start = l.content;
l.content = l.content[1..]; // checked in main.zig:0/fn(.)next
while (l.content.len > 0) : (l.content = l.content[1..]) {
switch (l.content[0]) {
'0'...'9' => continue,
else => break,
}
}
return start[0 .. start.len - l.content.len];
}
fn consumeName(l: *Lexer) []const u8 {
const start = l.content;
while (l.content.len > 0) : (l.content = l.content[1..]) {
switch (l.content[0]) {
'a'...'z', 'A'...'Z', '0'...'9', '_' => continue,
else => break,
}
}
return start[0 .. start.len - l.content.len];
}
fn consumeEnclosed(l: *Lexer, c: u8) ![]const u8 {
l.content = l.content[1..]; // checked in main.zig:0/fn(.)next
const start = l.content;
while (l.content.len > 0) : (l.content = l.content[1..]) {
if (l.content[0] == c) break;
} else return error.UnclosedLiteral;
l.content = l.content[1..];
return start[0 .. start.len - l.content.len - 1];
}
fn skipWhitespaces(l: *Lexer) void {
var saw_comment_slash: bool = false; // indicates whether we saw the first '/'
var skipping_line: bool = false; // indicates if we're skipping the rest of line (due to '//')
while (l.content.len > 0) : (l.content = l.content[1..]) {
switch (l.content[0]) {
' ', '\r', '\t' => continue,
'\n' => {
l.line += 1;
skipping_line = false;
},
'/' => if (saw_comment_slash) {
saw_comment_slash = false;
skipping_line = true;
} else if (!skipping_line) {
saw_comment_slash = true;
},
else => if (!skipping_line) break,
}
}
}
pub const Token = union(enum) {
number: []const u8,
name: []const u8,
quoted: []const u8,
keyword: Keyword,
punct: Punct,
pub const Keyword = enum {
syntax,
package,
import,
option,
message,
@"enum",
repeated,
map,
oneof,
};
pub const Punct = enum(u8) {
semicolon = ';',
open_curly = '{',
close_curly = '}',
equal_sign = '=',
open_triangle = '<',
close_triangle = '>',
comma = ',',
dot = '.',
open_paren = '(',
close_paren = ')',
open_square = '[',
close_square = ']',
pub fn Restricted(comptime variants: []const Punct) type {
var field_names: []const []const u8 = &.{};
inline for (variants) |variant|
field_names = field_names ++ .{@tagName(variant)};
var field_values: [field_names.len]u8 = undefined;
inline for (variants, 0..) |variant, i|
field_values[i] = @intFromEnum(variant);
return @Enum(u8, .exhaustive, field_names, &field_values);
}
};
pub fn size(t: Token) usize {
return switch (t) {
.punct => 1,
.quoted => |string| string.len + 2,
.number, .name => |content| content.len,
.keyword => |keyword| switch (keyword) {
inline else => |tag| @tagName(tag).len,
},
};
}
pub fn format(t: Token, w: *Io.Writer) !void {
switch (t) {
.punct => |p| try w.print("'{c}'", .{@intFromEnum(p)}),
.quoted => |s| try w.print("\"{s}\"", .{s}),
.name, .number => |n| try w.print("'{s}'", .{n}),
.keyword => |kw| try w.print("'{s}'", .{@tagName(kw)}),
}
}
};
};

1077
proto/pb/battle.proto Normal file

File diff suppressed because it is too large Load Diff

669
proto/pb/common.proto Normal file
View File

@@ -0,0 +1,669 @@
syntax = "proto3";
package proto;
option go_package = "beyond-go/proto/pbcommon;pbcommon";
import "options.proto";
enum CLIENT_PLATFORM_TYPE {
DEFAULT = 0;
ANDRIOD = 1;
IOS = 2;
WINDOWS = 3;
PLAYSTATION = 4;
HARMONYOS = 5;
MAX = 6;
}
enum MOVE_OBJECT_TYPE {
INVALID = 0;
ROLE = 1;
NPC = 2;
MONSTER = 3;
CHARACTER = 4;
INTERACTIVE = 5;
SUMMON = 6;
}
enum ITEM_FLAG {
ItemNormal = 0;
ItemLock = 1;
ItemTrash = 2;
}
enum ITEM_MOVE_MODE {
Normal = 0;
BatchItemId = 1;
Grid = 2;
HalfGrid = 3;
AutoBatch = 4;
}
enum INTERACTIVE_PROPERTY_VALUE_TYPE {
Bool = 0;
Int = 1;
Float = 2;
String = 3;
}
enum UID_TYPE {
Common = 0;
Char = 2;
Monster = 3;
Interactive = 4;
Npc = 5;
Summon = 6;
PayOrder = 7;
}
enum UID_TYPE_EXTEND {
InvalidPlaceholder = 0;
TriggerVolume = 8;
}
enum CHAR_TYPE {
default_type = 0;
trial_type = 1;
}
enum TEAM_CHAR_ID_TYPE {
obj_id = 0;
preset_char_id = 1;
}
enum GENDER {
GEN_INVALID = 0;
GEN_MALE = 1;
GEN_FEMALE = 2;
GEN_TOTAL = 3;
}
enum FRIEND_SPACESHIP_HELP_STATUS {
FRIEND_SPACESHIP_HELP_STATUS_INVALID = 0;
FRIEND_SPACESHIP_HELP_STATUS_CAN_HELP = 1;
FRIEND_SPACESHIP_HELP_STATUS_HELPED = 2;
}
enum FRIEND_USER_INFO_TYPE {
FRIEND_USER_INFO_TYPE_SPACESHIP_DEFAULT = 0;
FRIEND_USER_INFO_TYPE_NONE = 1;
}
enum PSN_AUTHCODE_QUERY_TYPE {
PSN_BLACKLIST_UPDATE = 0;
PSN_FRIENDLIST_UPDATE = 1;
}
enum RegionStatus {
RegionStatus_UnLocked = 0;
RegionStatus_Explored = 1;
RegionStatus_ExpeditionInProgress = 2;
}
enum ExpeditionStatus {
ExpeditionStatus_Available = 0;
ExpeditionStatus_InProgress = 1;
ExpeditionStatus_Completed = 2;
}
enum SpaceshipExtraReportType {
SpaceshipExtraReportType_None = 0;
SpaceshipExtraReportType_Expedition = 1;
}
enum BRIEF_BP_STATUS {
BRIEF_BP_STATUS_NONE = 0;
BRIEF_BP_STATUS_EXIST = 1;
BRIEF_BP_STATUS_INVALID = 2;
}
enum FRIEND_CHAT_MSG_TYPE {
FRIEND_CHAT_MSG_TYPE_PRESET_TEXT = 0;
FRIEND_CHAT_MSG_TYPE_EMOJI = 1;
FRIEND_CHAT_MSG_TYPE_SOCIAL_BUILDING = 2;
FRIEND_CHAT_MSG_TYPE_BLUE_PRINT = 3;
}
enum HG_THIRD_ACCOUNT_TYPE {
ACCOUNT_TYPE_DEFAULT = 0;
ACCOUNT_TYPE_PSN = 1;
}
message VECTOR {
float X = 1;
float Y = 2;
float Z = 3;
}
message DYNAMIC_PARAMETER {
int32 value_type = 1;
int32 real_type = 2;
repeated bool value_bool_list = 3;
repeated int64 value_int_list = 4;
repeated float value_float_list = 5;
repeated string value_string_list = 6;
}
message SKILL_INFO {
repeated SKILL_LEVEL_INFO level_info = 1;
string normal_skill = 2;
string ultimate_skill = 3;
string combo_skill = 4;
string disp_normal_attack_skill = 5;
}
message EQUIP_ATTR {
int32 attr_type = 1;
int32 modifier_type = 2;
double modifier_value = 3;
int32 modify_attribute_type = 4;
}
message ATTR_ENHANCE_FAILED_DATA {
map<int32, int32> enhance_failed_times_by_level = 1;
}
message EQUIP_ENHANCE_DATA {
map<int32, ATTR_ENHANCE_FAILED_DATA> enhance_failed_data_by_attr_index = 1;
}
message EQUIP_DATA {
uint64 equipid = 1;
int32 templateid = 2;
uint64 equip_char_id = 3;
map<int32, int32> enhance = 8;
EQUIP_ENHANCE_DATA equip_enhance_data = 9;
}
message GEM_DATA {
uint64 gemId = 1;
int32 template_id = 2;
int32 total_cost = 3;
repeated GEM_TERM terms = 4;
uint64 weapon_id = 5;
int32 domain_id = 6;
}
message GEM_TERM {
int32 term_num_id = 1;
int32 cost = 2;
}
message ITEM_BUNDLE {
string id = 1;
int32 count = 2;
}
message INST_ITEM_BUNDLE {
string id = 1;
int32 count = 2;
uint64 inst_id = 3;
}
message WEAPON_DATA {
uint64 inst_id = 1;
int32 template_id = 2;
uint64 exp = 3;
uint32 weapon_lv = 4;
uint32 refine_lv = 5;
uint32 breakthrough_lv = 6;
uint64 equip_char_id = 7;
uint64 attach_gem_id = 8;
}
message ITEM_INST {
uint64 inst_id = 1;
bool is_lock = 101;
bool is_new = 102;
ITEM_FLAG item_flag = 103;
int64 expire_ts = 104;
oneof inst_impl {
EQUIP_DATA equip = 2;
GEM_DATA gem = 3;
WEAPON_DATA weapon = 4;
ITEM_INST_EMPTY empty = 31;
}
}
message ITEM_INST_EMPTY {
}
message SCD_ITEM_GRID {
int32 grid_index = 1;
string id = 2;
int32 count = 3;
ITEM_INST inst = 4;
}
message ITEM_GRID {
int32 id = 1;
int32 count = 2;
ITEM_INST inst = 3;
int32 index = 4;
}
message SUBMIT_ITEM {
string item_id = 1;
uint32 count = 2;
bool is_inst = 3;
uint64 inst_id = 4;
}
message BATTLE_INFO {
double hp = 1;
float ultimatesp = 2;
}
message SEND_MAIL_DEF {
MAIL_CONTENT mail_content = 1;
repeated REWARD_ITEM item_list = 2;
int64 send_time = 3;
int64 expire_time = 4;
int32 mail_type = 5;
int32 mail_sub_type = 6;
bool is_global = 7;
bool is_star = 8;
bool ignore_cap_check = 9;
string gm_unique_task_id = 10;
}
message GAME_CONDITION {
string condition_id = 1;
int32 condition_type = 2;
repeated string params = 3;
int32 compare_operator = 4;
int32 progress_to_compare = 5;
repeated int32 sub_condition_id_logics = 6;
repeated GAME_CONDITION sub_conditions = 7;
int32 scope_mask = 8;
bool use_current_scope = 9;
}
message SEND_SYSTEM_MAIL_DEF {
uint64 mail_id = 1;
GAME_CONDITION condition = 2;
repeated REWARD_ITEM completed_item_list = 3;
int64 expire_time = 4;
bool is_star = 5;
string unique_id = 6;
int64 mail_expire_in = 7;
int64 mail_expire_at = 8;
bool has_attachment = 9;
int32 mail_sub_type = 10;
int64 task_expire_at = 11;
int32 channel_id = 12;
}
message COST_ITEM_LIST_DEF {
repeated SUBMIT_ITEM itemList = 1;
}
message MAIL_CONTENT {
string template_id = 1;
string title = 2;
string content = 3;
string senderName = 4;
string senderIcon = 5;
map<string, string> params = 6;
}
message REWARD_ITEM {
string id = 1;
int64 count = 2;
ITEM_INST inst = 3;
int32 item_opt = 4;
}
message SKILL_LEVEL_INFO {
string skill_id = 1;
int32 skill_level = 2;
int32 skill_max_level = 3;
int32 skill_enhanced_level = 4;
}
message TALENT {
string latest_break_node = 1;
repeated string attr_nodes = 2;
repeated string latest_passive_skill_nodes = 3;
repeated string latest_factory_skill_nodes = 4;
}
message ROLE_BASE_INFO {
uint64 leader_char_id = 1;
VECTOR leader_position = 2;
VECTOR leader_rotation = 3;
string sceneName = 4;
uint64 server_ts = 11;
}
message TEMP_TEAM_CHAR_SAVE_DATA {
TRIAL_CHAR_DATA trial_char_data = 1;
int32 hp = 2;
int32 ultimate_sp = 3;
}
message TRIAL_CHAR_DATA {
uint32 trial_char_type = 1;
int32 equip_medicine_num = 2;
}
message TEAM_CHAR_ID {
TEAM_CHAR_ID_TYPE id_type = 1;
oneof char_id {
uint64 obj_id = 2;
string preset_char_id = 3;
}
}
message PASS_THROUGH_DATA {
repeated string str_args = 1;
repeated int64 int_args = 2;
}
message SCENE_TRACK_POINT {
string inst_id = 1;
}
message SCENE_STATIC_MAP_MARK {
int32 index = 1;
}
message WALLET_MONEY_RECORD {
int32 record_type = 1;
string money_id = 2;
oneof record_data {
WALLET_DAILY_SOURCE_MONEY_RECORD daily_source_data = 5;
}
}
message WALLET_DAILY_SOURCE_MONEY_RECORD {
repeated WALLET_DAILY_SOURCE_MONEY_BOOK books = 1;
}
message WALLET_DAILY_SOURCE_MONEY_BOOK {
int64 ts = 1;
repeated int64 record = 2;
}
message SCENE_DYNAMIC_MAP_MARK {
uint32 id = 1;
int32 scene_num_id = 2;
VECTOR pos = 3;
string note = 4;
uint32 typ = 5;
int32 tier_index = 6;
int32 tier_id = 7;
}
message ATTACH_USER_INFO {
uint64 original_role_id = 1;
string original_user_id = 2;
uint64 target_role_id = 3;
string target_user_id = 4;
}
message REMOTE_ROLE_BASE {
string userid = 1;
string name = 2;
uint32 create_time = 3;
}
message DOMAIN_DEVELOPMENT {
string chapter_id = 1;
string version = 3;
DOMAIN_DEVELOPMENT_DEGREE dev_degree = 2;
}
message DOMAIN_DEVELOPMENT_DEGREE {
uint64 exp = 1;
uint32 level = 2;
uint64 rewarded_level = 3;
repeated uint64 source = 4;
}
message SPACESHIP_PUBLIC_DATA {
SPACESHIP_HELP_RECORD help_record = 1;
bool unlock_guest_room = 3;
SPACESHIP_GUEST_ROOM_CLUE_LOGIC_EXTRA_RECORD clue_logic_record = 4;
}
message SPACESHIP_HELP_RECORD {
map<string, ROOM_HELP_RECORD> room_help_record = 1;
int64 ttl = 2;
}
message ROOM_HELP_RECORD {
int32 control_center = 1;
int32 manufacturing_station = 2;
map<int32, int32> grow_cabin = 3;
int32 command_center = 4;
int32 control_center_left = 6;
int32 manufacturing_station_left = 7;
map<int32, int32> grow_cabin_left = 8;
int32 command_center_left = 9;
int32 can_be_helped_flag = 5;
repeated int32 can_be_helped_grow_cabin_box = 10;
}
message SPACESHIP_GUEST_ROOM_CLUE_LOGIC_EXTRA_RECORD {
uint64 info_exchange_inst_id = 1;
int64 info_exchange_expire_ts = 2;
map<uint64, bool> role_map = 3;
map<int32, int64> host_clue_status = 4;
int32 clue_extension_room_level = 5;
int64 last_owner_update_ts = 7;
}
message SPACESHIP_GUEST_ROOM_CLUE {
string clue_id = 1;
uint64 inst_id = 2;
uint64 from_role_id = 3;
int64 expire_ts = 4;
map<int32, bool> char_num_id_to_prob_acc = 5;
int64 generate_ts = 6;
bool is_read = 7;
}
message SPACESHIP_GUEST_ROOM_VISITOR_RECORD {
int32 day_count = 1;
repeated VISIT_OP_RECORD visit_op_record = 2;
int32 recved_credit_cnt = 3;
int32 total_credit_cnt = 4;
}
message VISIT_OP_RECORD {
uint64 role_id = 1;
int64 start_ts = 2;
int64 last_ts = 3;
bool joined_info_exchange = 4;
map<int32, int64> money_id_to_sold_price = 5;
repeated VISIT_RECORD_PROD_SUPPORT_INFO prod_support_list = 6;
}
message VISIT_RECORD_PROD_SUPPORT_INFO {
int32 room_type = 1;
int32 room_number = 2;
}
message VALUE_DATA {
oneof val {
bool v_bool = 1;
int64 v_int = 2;
float v_float = 3;
string v_string = 4;
}
}
message DYNAMIC_PARAMETER_DATA {
int32 value_type = 1;
int32 real_type = 2;
repeated VALUE_DATA details = 3;
}
message PROPERTY_RECORD_DATA {
map<int32, DYNAMIC_PARAMETER_DATA> common_properties = 1;
bytes bool_properties = 2;
}
message FRIEND_PERSONAL_DATA {
int32 user_avatar_id = 1;
int32 User_avatar_frame_id = 2;
int32 business_card_topic_id = 3;
string signature = 4;
repeated uint64 char_list = 5;
bool business_card_expand_flag = 20;
bool psn_only = 30;
}
message SROLE_DATA_DOMAIN_DEV {
string domain_id = 1;
uint32 level = 2;
}
message SROLE_DATA_DOMAIN_DEV_SYSTEM {
repeated SROLE_DATA_DOMAIN_DEV domains = 1;
}
message SROLE_DATA_ACHIEVEMENT {
map<int32, int32> display = 1;
repeated SROLE_DATA_ACHIEVEMENT_INFO info_list = 2;
}
message SROLE_DATA_ACHIEVEMENT_INFO {
int32 achieve_num_id = 1;
int32 level = 2;
bool is_plated = 3;
}
message SROLE_DATA_STATISTIC {
uint32 char_num = 1;
uint32 weapon_num = 2;
uint32 doc_num = 3;
}
message SROLE_DATA_CHAR {
string templateId = 1;
uint32 level = 2;
uint32 exp = 3;
uint32 potential_level = 4;
map<int32, EQUIP_DATA> equip = 5;
map<uint64, GEM_DATA> gem = 6;
WEAPON_DATA weapon = 7;
SKILL_INFO skill_info = 8;
int32 equip_medicine_id = 9;
TALENT talent = 10;
map<int32, string> potential_cg = 11;
}
message SROLED_DOMAIN_SHOP_GOODS {
string shop_id = 1;
int64 unlock_time = 2;
repeated string shop_goods_id = 3;
}
message SROLE_DATA_DOMAIN_SHOP {
repeated SROLED_DOMAIN_SHOP_GOODS shop_goods = 1;
}
message SROLE_DATA_DOMAIN_DEPOT {
map<uint64, int64> delegate_inst_id_to_timeout_ts = 1;
}
message FRIEND_BASE_USER_INFO {
uint64 role_id = 1;
string name = 3;
string short_id = 4;
int64 last_login_time = 5;
int64 last_logout_time = 6;
bool online = 7;
uint32 adventure_level = 8;
string signature = 9;
GENDER gender = 10;
int32 business_card_topic_id = 11;
int32 user_avatar_id = 12;
int32 user_avatar_frame_id = 13;
repeated FRIEND_CHAR_INFO char_data = 14;
HG_THIRD_ACCOUNT_TYPE last_login_type = 31;
THIRD_ACCOUNT_DATA third_account_data = 32;
}
message FRIEND_CHAR_INFO {
string templateId = 1;
uint32 level = 2;
uint32 potential_level = 3;
}
message FRIEND_SPACESHIP_DEFAULT_DATA {
bool help_flag = 1;
bool clue_flag = 2;
bool unlock_guest_room = 3;
FRIEND_SPACESHIP_HELP_STATUS help_status = 4;
bool unlock_clue_room = 5;
}
message FRIEND_STRANGER_USER_INFO {
FRIEND_BASE_USER_INFO base_data = 1;
}
message FRIEND_FRIEND_USER_INFO {
FRIEND_USER_INFO_TYPE data_type = 1;
FRIEND_BASE_USER_INFO base_data = 2;
oneof extra_data {
FRIEND_SPACESHIP_DEFAULT_DATA spaceship_default = 3;
}
}
message FRIEND_FRIEND_INFO {
FRIEND_FRIEND_USER_INFO friend_user_info = 1;
int64 create_time = 2;
string remark_name = 3;
}
message FRIEND_BLACK_LIST_INFO {
uint64 role_id = 1;
string name = 3;
string short_id = 4;
bool online = 5;
int64 last_logout_time = 6;
uint32 adventure_level = 7;
int32 business_card_topic_id = 8;
int32 user_avatar_id = 9;
int32 user_avatar_frame_id = 10;
HG_THIRD_ACCOUNT_TYPE last_login_type = 31;
THIRD_ACCOUNT_DATA third_account_data = 32;
}
message FRIEND_REQUEST_INFO {
FRIEND_STRANGER_USER_INFO stranger_user_info = 1;
int64 create_time = 2;
}
message FRIEND_FRIEND_S_INFO {
uint64 role_id = 1;
int64 create_time = 2;
string remark_name = 3;
}
message FRIEND_REQUEST_S_INFO {
uint64 role_id = 1;
int64 create_time = 2;
}
message FRIEND_BLACK_S_INFO {
uint64 role_id = 1;
int64 create_time = 2;
}
message DOMAIN_DEPOT_DELIVER_PEER_ROLE_INFO {
uint64 role_id = 1;
string name = 2;
uint32 user_avatar_id = 3;
uint32 user_avatar_frame_id = 4;
int32 adventure_level = 5;
string short_id = 6;
bool is_delete = 7;
int32 business_card_topic_id = 8;
}
message DOMAIN_DEPOT_BUYER_INFO {
int32 deliver_num_id = 1;
int32 buyer_num_id = 2;
int64 reward_value = 3;
bool is_critical = 4;
int32 price_cfg_idx = 5;
}
message DOMAIN_DEPOT_DELIVER_DATA {
int32 package_type = 1;
int32 package_progress = 2;
uint64 original_role_id = 3;
uint64 inst_id = 4;
int32 cargo_integrity = 5;
int32 domain_depot_num_id = 6;
int64 expire_ts = 7;
int64 original_price = 8;
int32 deliver_pack_type = 9;
int32 deliver_item_type = 10;
repeated DOMAIN_DEPOT_BUYER_INFO buyer_list = 11;
DOMAIN_DEPOT_DELIVER_PEER_ROLE_INFO peer_role_info = 12;
uint64 delegate_role_id = 13;
string deliver_redis_key = 14;
int64 final_payment_value = 15;
}
message BRIEF_BP_DATA {
string share_code = 1;
string name = 2;
repeated int32 tags = 3;
string icon = 4;
int32 base_color = 5;
BRIEF_BP_STATUS status = 6;
int32 share_idx = 7;
uint64 bp_uid = 8;
}
message FRIEND_CHAT_MSG_DATA_PRESET_TEXT {
int32 num_id = 1;
}
message FRIEND_CHAT_MSG_DATA_EMOJI {
int32 num_id = 1;
}
message FRIEND_CHAT_MSG_DATA_SOCIAL_BUILDING {
uint64 creator_id = 1;
int32 scene_num_id = 2;
uint32 node_id = 3;
int32 template_id = 4;
VECTOR pos = 5;
int32 dir_y = 6;
VECTOR world_pos = 7;
VECTOR world_rot = 8;
uint64 like = 9;
oneof extra_data {
FRIEND_CHAT_MSG_DATA_SOCIAL_BUILDING_SIGN sign_data = 20;
}
}
message FRIEND_CHAT_MSG_DATA_SOCIAL_BUILDING_SIGN {
repeated int32 signs = 1;
}
message FRIEND_CHAT_MSG_DATA_BLUE_PRINT {
string shared_code = 1;
}
message FRIEND_CHAT_MSG_DATA {
int64 ts = 1;
uint64 owner_id = 2;
FRIEND_CHAT_MSG_TYPE type = 3;
oneof extra_data {
FRIEND_CHAT_MSG_DATA_PRESET_TEXT preset_text_data = 4;
FRIEND_CHAT_MSG_DATA_EMOJI emoji_data = 5;
FRIEND_CHAT_MSG_DATA_SOCIAL_BUILDING social_building_data = 6;
FRIEND_CHAT_MSG_DATA_BLUE_PRINT blue_print_data = 7;
}
}
message THIRD_ACCOUNT_DATA_PSN {
string account_id = 1;
string online_id = 2;
}
message THIRD_ACCOUNT_DATA {
HG_THIRD_ACCOUNT_TYPE third_account_data_type = 1;
oneof data {
THIRD_ACCOUNT_DATA_PSN third_account_psn = 2;
}
}
message DEVICE_INFO {
string device_id = 1;
string os = 2;
string os_ver = 3;
string brand = 4;
string model = 5;
string simulator = 6;
string network = 7;
string carrier = 8;
string language = 9;
string country_iso_code = 10;
int64 ipv4 = 11;
string client_res_version = 12;
}

41
proto/pb/cs_achieve.proto Normal file
View File

@@ -0,0 +1,41 @@
syntax = "proto3";
package proto;
option go_package = "/csproto";
message SCD_ACHIEVE_CONDITIONS {
map<string, int32> condition_values = 1;
}
message SCD_ACHIEVE_PUBLIC_INFO {
int32 achieve_num_id = 1;
int64 open_time = 2;
int64 close_time = 3;
}
message SCD_ACHIEVE_PERSONAL_INFO {
int32 achieve_num_id = 1;
int32 level = 2;
bool is_plated = 3;
int64 obtain_time_ts = 4;
SCD_ACHIEVE_CONDITIONS achieve_conditions = 5;
}
message SCD_ACHIEVE_DISPLAY_INFO {
map<int32, int32> display = 1;
repeated int32 display_depot = 2;
}
message SC_ACHIEVE_SYNC {
repeated SCD_ACHIEVE_PUBLIC_INFO achieve_public_infos = 1;
repeated SCD_ACHIEVE_PERSONAL_INFO achieve_personal_infos = 2;
SCD_ACHIEVE_DISPLAY_INFO achieve_display_info = 3;
}
message SC_ACHIEVE_SYNC_PUBLIC_INFO {
repeated SCD_ACHIEVE_PUBLIC_INFO achieve_public_infos = 1;
}
message SC_ACHIEVE_SYNC_PERSONAL_INFO {
repeated SCD_ACHIEVE_PERSONAL_INFO achieve_personal_infos = 1;
}
message CS_ACHIEVE_DISPLAY_INFO_CHANGE {
map<int32, int32> display = 1;
repeated int32 display_depot = 2;
}
message SC_ACHIEVE_DISPLAY_INFO_CHANGE {
}

155
proto/pb/cs_activity.proto Normal file
View File

@@ -0,0 +1,155 @@
syntax = "proto3";
package proto;
option go_package = "/csproto";
import "options.proto";
message SC_ACTIVITY_SYNC {
repeated ACTIVITY_INFO info = 1;
}
message SC_ACTIVITY_MODIFY {
repeated ACTIVITY_INFO info = 1;
}
message SC_ACTIVITY_PROGRESS_CHANGE {
string id = 1;
SCD_ACTIVITY_CONDITIONS conditions = 2;
}
message SCD_ACTIVITY_CONDITIONS {
map<string, int32> values = 1;
map<string, bool> flags = 2;
}
message ACTIVITY_INFO {
bool is_enable = 1;
string id = 2;
int64 start_time = 3;
int64 end_time = 4;
int32 typ = 5;
ACTIVITY_INFO_SPECIFIED data = 6;
bool is_completed = 7;
bool is_unlocked = 8;
int32 status = 9;
bool reward_all = 11;
SCD_ACTIVITY_CONDITIONS conditions = 10;
}
message ACTIVITY_INFO_SPECIFIED {
oneof data {
ACTIVITY_INFO_SPECIFIED_CHECKIN checkin = 15;
ACTIVITY_INFO_SPECIFIED_BASE_MULTI_STAGE base_multi_stage = 16;
ACTIVITY_INFO_SPECIFIED_CONDITIONAL_MULTI_STAGE conditional_multi_stage = 17;
ACTIVITY_INFO_SPECIFIED_GAME_ENTRANCE game_entrance = 18;
ACTIVITY_INFO_SPECIFIED_CHAR_TRIAL char_trial = 19;
ACTIVITY_INFO_SPECIFIED_REFLOW reflow = 20;
ACTIVITY_INFO_SPECIFIED_WEEKLY_TASK weekly_task = 21;
}
}
message ACTIVITY_INFO_SPECIFIED_CHECKIN {
int32 login_days = 1;
repeated uint64 reward_days = 2;
}
message CS_DAILY_CHECKIN {
string id = 1;
repeated uint32 days = 2;
}
message SC_DAILY_CHECKIN {
string id = 1;
repeated uint32 days = 2;
}
message ACTIVITY_INFO_SPECIFIED_BASE_MULTI_STAGE {
repeated int32 receive_stage_list = 1;
repeated int32 complete_stage_list = 2;
}
message CS_BASE_MULTI_STAGE_ACTIVITY_RECEIVE_REWARD {
string id = 1;
int32 stage = 2;
repeated int32 stage_list = 3;
}
message SC_BASE_MULTI_STAGE_ACTIVITY_RECEIVE_REWARD {
string id = 1;
repeated int32 stage_list = 2;
string reward_id = 3;
}
message ACTIVITY_INFO_SPECIFIED_GAME_ENTRANCE {
bool complete_intro_mission = 2;
repeated GAME_ENTRANCE_ACTIVITY_SERIES_INFO series_infos = 1;
}
message GAME_ENTRANCE_ACTIVITY_SERIES_INFO {
string series_id = 1;
int64 open_time = 2;
}
message ACTIVITY_INFO_SPECIFIED_CHAR_TRIAL {
map<string, bool> dungeon_status = 1;
}
message ACTIVITY_INFO_SPECIFIED_REFLOW {
int64 start_time = 1;
int64 end_time = 2;
}
message ACTIVITY_INFO_SPECIFIED_WEEKLY_TASK {
int32 cur_week_score = 1;
repeated ACTIVITY_CONDITIONAL_STAGE_INFO task_info = 2;
repeated ACTIVITY_WEEKLY_TASK_MILESTONE milestone_info = 3;
}
message ACTIVITY_INFO_SPECIFIED_CONDITIONAL_MULTI_STAGE {
bool complete_intro_mission = 1;
bool complete_finale_mission = 2;
repeated ACTIVITY_CONDITIONAL_STAGE_INFO stage_list = 3;
repeated string show_preview_stage_list = 4;
oneof extra_data {
ACTIVITY_INFO_CONDITIONAL_MULTI_STAGE_EXTRA_DATA_PHOTO_TAKING photo_taking = 15;
}
}
message ACTIVITY_CONDITIONAL_STAGE_INFO {
string id = 1;
int32 status = 2;
SCD_ACTIVITY_CONDITIONS conditions = 3;
}
message ACTIVITY_INFO_CONDITIONAL_MULTI_STAGE_EXTRA_DATA_PHOTO_TAKING {
}
message SC_ACTIVITY_CONDITIONAL_MULTI_STAGE_BASE_CHANGE {
repeated SCD_ACTIVITY_CONDITIONAL_MULTI_STAGE_BASE changes = 1;
}
message SCD_ACTIVITY_CONDITIONAL_MULTI_STAGE_BASE {
string id = 1;
bool complete_intro_mission = 2;
bool complete_finale_mission = 3;
repeated ACTIVITY_CONDITIONAL_STAGE_INFO stage_list = 4;
}
message CS_CONDITIONAL_MULTI_STAGE_ACTIVITY_GAIN_REWARD {
string activity_id = 1;
repeated string stage_id_list = 2;
}
message SC_CONDITIONAL_MULTI_STAGE_ACTIVITY_GAIN_REWARD {
string activity_id = 1;
repeated string stage_id_list = 2;
}
message SC_ACTIVITY_CONDITIONAL_STAGE_PROGRESS_CHANGE {
string stage_id = 1;
SCD_ACTIVITY_CONDITIONS conditions = 2;
}
message CS_ACTIVITY_CHAR_TRIAL_CLAIM_REWARD {
string activity_id = 1;
repeated string dungeon_id_list = 2;
}
message CS_ACTIVITY_READ_NOTIFY {
string activity_id = 1;
}
message ACTIVITY_WEEKLY_TASK_MILESTONE {
string id = 1;
bool rewarded = 2;
}
message CS_ACTIVITY_WEEKLY_TASK_TASK_GAIN_REWARD {
string activity_id = 1;
repeated string task_id_list = 2;
}
message SC_ACTIVITY_WEEKLY_TASK_TASK_GAIN_REWARD {
string activity_id = 1;
repeated string task_id_list = 2;
}
message CS_ACTIVITY_WEEKLY_TASK_MILESTONE_GAIN_REWARD {
string activity_id = 1;
repeated string milestone_id_list = 2;
}
message SC_ACTIVITY_WEEKLY_TASK_MILESTONE_GAIN_REWARD {
string activity_id = 1;
repeated string milestone_id_list = 2;
}

View File

@@ -0,0 +1,26 @@
syntax = "proto3";
package proto;
option go_package = "/csproto";
message CS_ADVENTURE_TAKE_REWARD_ALL {
}
message SC_ADVENTURE_LEVEL_MODIFY {
int32 level = 1;
int64 exp = 2;
}
message SC_ADVENTURE_SYNC_ALL {
int32 level = 1;
int64 exp = 2;
int32 world_level = 3;
int32 unlock_world_level = 4;
int64 last_set_world_level_ts = 5;
}
message CS_ADVENTURE_SET_WORLD_LEVEL {
int32 world_level = 1;
}
message SC_ADVENTURE_WORLD_LEVEL_MODIFY {
int32 world_level = 1;
int32 unlock_world_level = 2;
int64 last_set_world_level_ts = 3;
}

View File

@@ -0,0 +1,39 @@
syntax = "proto3";
package proto;
option go_package = "/csproto";
message ADVENTURE_TASK {
string task_id = 1;
int32 progress = 2;
int32 state = 3;
}
message SC_ADVENTURE_BOOK_SYNC {
int32 adventure_book_stage = 1;
repeated ADVENTURE_TASK tasks = 2;
int32 daily_activation = 3;
repeated int32 daily_activation_gathered_reward_ids = 4;
}
message SC_DAILY_ACTIVATION_MODIFY {
int32 daily_activation = 1;
repeated int32 daily_activation_gathered_reward_ids = 2;
}
message CS_TAKE_ADVENTURE_TASK_REWARD {
string task_id = 1;
}
message CS_TAKE_ALL_ACTIVATION_REWARD {
}
message CS_TAKE_ALL_ADVENTURE_TASK_REWARD {
int32 task_type = 1;
}
message SC_ADVENTURE_TASK_MODIFY {
repeated ADVENTURE_TASK tasks = 1;
}
message CS_TAKE_ADVENTURE_BOOK_STAGE_REWARD {
int32 adventure_book_stage = 1;
}
message SC_ADVENTURE_BOOK_STAGE_MODIFY {
int32 adventure_book_stage = 1;
}
message SC_RESET_DAILY_ADVENTURE_TASK {
}

View File

@@ -0,0 +1,48 @@
syntax = "proto3";
package proto;
option go_package = "/csproto";
import "options.proto";
enum ANTI_CHEAT_SDK_TYPE {
ANTI_CHEAT_SDK_TYPE_NONE = 0;
ANTI_CHEAT_SDK_TYPE_PC = 1;
ANTI_CHEAT_SDK_TYPE_MOBILE = 2;
}
message CS_ANTI_CHEAT_UPLOAD_DATA {
bytes data = 1;
}
message SC_ANTI_CHEAT_SYNC_DATA {
bytes data = 1;
}
message SC_ANTI_CHEAT_TICKET {
string ticket = 1;
int32 zone_id = 2;
}
message CS_ANTI_CHEAT_SEND_ANTI_DATA1 {
bytes data = 1;
}
message CS_ANTI_CHEAT_SEND_ANTI_DATA2 {
bytes data = 1;
}
message CS_ANTI_CHEAT_SEND_ANTI_DATA4 {
bytes data = 1;
}
message SC_ANTI_CHEAT_SEND_ANTI_DATA4 {
string result = 1;
}
message CS_ANTI_CHEAT_GET_LIGHT_FEATURE {
int32 cpu_arch = 1;
}
message SC_ANTI_CHEAT_GET_LIGHT_FEATURE {
bytes name_buf = 1;
bytes sig_buf = 2;
uint32 sig_crc = 3;
}
message SC_ANTI_CHEAT_CREDIT_WARNING {
int32 record_duration = 1;
}
message SC_ANTI_CHEAT_CREDIT_ZERO {
int64 last_credit_zero_ts = 1;
}

80
proto/pb/cs_battle.proto Normal file
View File

@@ -0,0 +1,80 @@
syntax = "proto3";
package proto;
option go_package = "/csproto";
import "battle.proto";
import "common.proto";
message CS_DEV_CLEAR_BATTLE_INFO {
}
message CS_BATTLE_OP {
BATTLE_CLIENT_DATA client_data = 1;
}
message SC_SPAWN_ENEMY {
uint32 client_key = 1;
repeated uint64 enemy_inst_ids = 2;
}
message SPAWN_SERVER_SUMMON_DATA {
uint64 owner_inst_id = 1;
uint64 summon_inst_id = 2;
}
message SC_ENTITY_PROPERTY_CHANGE {
uint64 inst_id = 1;
BATTLE_INFO info = 2;
}
message SC_BATTLE_DEBUG_INFO {
string msg = 1;
int32 reason = 2;
}
message SC_BATTLE_GENERATION_CHANGE {
uint64 inst_id = 1;
uint32 generation = 2;
}
message SC_RESET_BATTLE_STATUS {
uint64 inst_id = 1;
uint64 battle_inst_id = 2;
BATTLE_MGR_INFO battle_info = 3;
BATTLE_RESET_REASON reason = 4;
}
message SC_ATTACH_SERVER_SKILL {
uint64 inst_id = 1;
repeated SERVER_SKILL skills = 2;
uint32 generation = 3;
}
message SC_UPDATE_SERVER_SKILL {
uint64 inst_id = 1;
repeated SERVER_SKILL skills = 2;
uint32 generation = 3;
}
message SC_DETACH_SERVER_SKILL {
uint64 inst_id = 1;
repeated uint64 del_inst_ids = 2;
uint32 generation = 3;
}
message SC_ADD_SERVER_BUFF {
uint64 inst_id = 1;
repeated SERVER_BUFF buffs = 2;
uint32 generation = 3;
}
message SC_REMOVE_SERVER_BUFF {
uint64 inst_id = 1;
repeated uint64 del_inst_ids = 2;
uint32 generation = 3;
}
message SC_BATTLE_SYNC_SEQ_ID {
int32 last_max_seq_id = 1;
}
message GLOBAL_BUFF_INFO {
int32 global_buff_num_id = 1;
BATTLE_BLACKBOARD assigned_items = 2;
}
message SC_BATTLE_SYNC_GLOBAL_BUFF_INFO {
repeated GLOBAL_BUFF_INFO global_buff_info = 1;
}
message SC_BATTLE_ADD_GLOBAL_BUFF {
GLOBAL_BUFF_INFO global_buff_info = 1;
}
message SC_BATTLE_REMOVE_GLOBAL_BUFF {
int32 global_buff_num_id = 1;
}

39
proto/pb/cs_bitset.proto Normal file
View File

@@ -0,0 +1,39 @@
syntax = "proto3";
package proto;
option go_package = "/csproto";
import "options.proto";
message SC_SYNC_ALL_BITSET {
repeated BITSET_DATA bitset = 1;
}
message BITSET_DATA {
int32 type = 1;
repeated uint64 value = 2;
}
message CS_BITSET_ADD {
int32 type = 1;
repeated uint32 value = 2;
}
message CS_BITSET_REMOVE_ALL {
int32 type = 1;
}
message CS_BITSET_REMOVE {
int32 type = 1;
repeated uint32 value = 2;
}
message SC_BITSET_ADD {
int32 type = 1;
repeated uint32 value = 2;
int32 source = 3;
}
message SC_BITSET_REMOVE {
int32 type = 1;
repeated uint32 value = 2;
int32 source = 3;
}
message SC_BITSET_REMOVE_ALL {
int32 type = 1;
int32 source = 2;
}

132
proto/pb/cs_bp.proto Normal file
View File

@@ -0,0 +1,132 @@
syntax = "proto3";
package proto;
option go_package = "/csproto";
import "common.proto";
enum BP_TASK_STATE {
NOT_ACCEPT = 0;
IN_PROCESSING = 1;
HAS_COMPLETED = 2;
TAKE_REWARD = 3;
DISABLE = 4;
}
enum BP_VISIBLE_CONDS_OBJ_TYPE {
TASK = 0;
TASK_GROUP = 1;
TASK_LABEL = 2;
}
enum BP_TRACK_ACTIVE_TYPE {
BTAT_FREE = 0;
BTAT_PAY = 1;
BTAT_TICKET = 2;
}
message CSD_BP_TRACK_REWARD {
string bp_track_id = 1;
repeated int32 level = 2;
}
message CS_BP_TAKE_TRACK_REWARD {
repeated CSD_BP_TRACK_REWARD take_track_reward = 1;
}
message SC_BP_TAKE_TRACK_REWARD {
repeated CSD_BP_TRACK_REWARD failed_reward = 1;
repeated ITEM_BUNDLE items = 2;
}
message CS_BP_TAKE_TASK_REWARD {
repeated string task_ids = 1;
}
message SC_BP_TASK_STATE_MODIFY {
map<string, BP_TASK_STATE> task_state = 1;
}
message CS_BP_BUY_LEVEL {
int32 target_level = 1;
}
message CS_BP_BUY_ORIGINIUM_TRACK {
}
message SCD_BP_CLOSE_STATUS {
bool is_close = 1;
int32 close_reason = 2;
}
message SCD_BP_SEASON_DATA {
string season_id = 1;
int32 abscent_cnt = 2;
int64 open_time = 3;
int64 close_time = 4;
SCD_BP_CLOSE_STATUS close_status = 5;
int64 can_buy_level_tms = 6;
}
message SC_BP_SYNC_ALL {
SCD_BP_SEASON_DATA season_data = 1;
SCD_BP_LEVEL_DATA level_data = 2;
SCD_BP_TRACK_MGR bp_track_mgr = 3;
SCD_BP_TASK_MGR bp_task_mgr = 4;
}
message SCD_BP_LEVEL_DATA {
int32 cur_level = 1;
int32 cur_exp = 2;
}
message SC_BP_LEVEL_MODIFY {
SCD_BP_LEVEL_DATA level_data = 1;
}
message SCD_BP_TRACK_REWARD_DATA {
string bp_track_id = 1;
repeated uint64 reward_state = 2;
int32 recurring_times = 3;
BP_TRACK_ACTIVE_TYPE active_type = 4;
}
message SCD_BP_TRACK_MGR {
repeated SCD_BP_TRACK_REWARD_DATA track_data = 1;
}
message SC_BP_TRACK_MGR_MODIFY {
SCD_BP_TRACK_MGR bp_track_mgr = 1;
}
message SCD_BP_TASK {
string task_id = 1;
repeated SCD_BP_CONDITION_DATA visible_conditions = 2;
repeated SCD_BP_CONDITION_DATA complete_conditions = 3;
int64 visible_tms = 4;
BP_TASK_STATE task_state = 5;
SCD_BP_CLOSE_STATUS close_status = 6;
}
message SCD_BP_TASK_GROUP {
string task_group_id = 1;
repeated SCD_BP_CONDITION_DATA visible_conditions = 2;
int64 visible_tms = 3;
int64 disable_tms = 4;
repeated SCD_BP_TASK tasks = 5;
SCD_BP_CLOSE_STATUS close_status = 6;
}
message SCD_BP_TASK_LABEL {
string task_label_id = 1;
repeated SCD_BP_CONDITION_DATA visible_conditions = 2;
int64 visible_tms = 3;
repeated SCD_BP_TASK_GROUP groups = 4;
SCD_BP_CLOSE_STATUS close_status = 5;
}
message SCD_BP_TASK_MGR {
repeated SCD_BP_TASK_LABEL labels = 1;
}
message SCD_BP_CONDITION_DATA {
string cond_id = 1;
int32 value = 2;
bool flag = 3;
}
message SC_BP_SYNC_VISIBLE_CONDITION {
BP_VISIBLE_CONDS_OBJ_TYPE obj_type = 1;
string id = 2;
repeated SCD_BP_CONDITION_DATA conditions = 3;
}
message SC_BP_SYNC_TASK_COMPLETE_CONDITION {
string task_id = 1;
repeated SCD_BP_CONDITION_DATA conditions = 2;
}
message SC_BP_ABSCENT_COUNT_MODIFY {
int32 abscent_cnt = 1;
}
message SC_BP_SYNC_TASK_LABEL_MODIFY {
repeated SCD_BP_TASK_LABEL labels = 1;
}
message SC_BP_BUY_LEVEL {
int32 target_level = 1;
}

162
proto/pb/cs_char_bag.proto Normal file
View File

@@ -0,0 +1,162 @@
syntax = "proto3";
package proto;
option go_package = "/csproto";
import "common.proto";
import "battle.proto";
import "options.proto";
enum CHAR_BAG_TEAM_TYPE {
CHAR_BAG_TEAM_TYPE_MAIN = 0;
CHAR_BAG_TEAM_TYPE_TEMP = 1;
}
message CHAR_INFO {
uint64 objid = 1;
string templateid = 2;
int32 level = 3;
int32 exp = 4;
map<int32, uint64> equip_col = 6;
string normal_skill = 7;
bool is_dead = 8;
BATTLE_INFO battle_info = 9;
SKILL_INFO skill_info = 10;
map<string, int32> equip_suit = 11;
uint64 weapon_id = 12;
int64 own_time = 13;
int32 equip_medicine_id = 14;
uint32 potential_level = 15;
TALENT talent = 16;
BATTLE_MGR_INFO battle_mgr_info = 17;
CHAR_TYPE char_type = 18;
TRIAL_CHAR_DATA trial_data = 19;
map<int32, string> potential_cg = 20;
}
message CS_CHAR_BAG_SET_TEAM {
int32 team_index = 1;
repeated uint64 char_team = 2;
uint64 leader_id = 3;
}
message CS_CHAR_BAG_SET_CURR_TEAM_INDEX {
int32 team_index = 1;
uint64 leader_id = 2;
}
message CS_CHAR_BAG_SET_TEAM_NAME {
int32 team_index = 1;
string team_name = 2;
}
message CS_CHAR_BAG_TEAM_CHANGE_FINISH {
uint64 team_change_token = 1;
}
message CS_CHAR_BAG_SET_TEAM_LEADER {
CHAR_BAG_TEAM_TYPE team_type = 1;
int32 team_index = 2;
uint64 leaderid = 3;
}
message CS_CHAR_BAG_CLIENT_LOCAL_TEAM_REPORT {
string reason = 1;
repeated string team_member_template_ids = 2;
string meta = 3;
bool enter_local_team = 4;
}
message CS_CHAR_BAG_RELEASE_NPC_TEAMMATES_FROM_AI_CONTROL {
string dungeon_id = 1;
string npc_teammate_enter_action_id = 2;
string new_leader_preset_id = 3;
int32 env_type = 4;
string env_id = 5;
string new_leader_template_id = 6;
}
message SC_CHAR_BAG_REMOVE_TRIAL_CHARACTER {
int32 scope_name = 1;
}
message SCD_CHAR_BAG_NPC_TEAMMATE {
uint64 obj_id = 1;
int32 team_index = 2;
}
message SC_CHAR_BAG_SET_TEAM {
CHAR_BAG_TEAM_TYPE team_type = 1;
int32 team_index = 2;
repeated uint64 char_team = 3;
int32 scope_name = 4;
uint64 leader_id = 5;
}
message SC_CHAR_BAG_SET_NPC_TEAMMATES {
CHAR_BAG_TEAM_TYPE team_type = 1;
int32 team_index = 2;
int32 scope_name = 3;
repeated uint64 enter_npc_teammate_ids = 4;
repeated uint64 leave_npc_teammate_ids = 5;
}
message CHAR_TEAM_MEMBER_INFO {
string normal_skillid = 1;
}
message CHAR_TEAM_INFO {
string team_name = 1;
repeated uint64 char_team = 2;
uint64 leaderid = 3;
}
message SC_SYNC_CHAR_BAG_INFO {
repeated CHAR_INFO char_info = 1;
repeated CHAR_TEAM_INFO team_info = 2;
int32 curr_team_index = 3;
uint32 max_char_team_member_count = 4;
CHAR_TEAM_INFO temp_team_info = 5;
CHAR_BAG_TEAM_TYPE team_type = 6;
int32 scope_name = 10;
bool is_scope_delete = 11;
}
message SC_CHAR_BAG_SET_CURR_TEAM_INDEX {
int32 curr_team_index = 1;
int32 scope_name = 2;
}
message SC_CHAR_BAG_SET_CURR_TEAM_TYPE {
CHAR_BAG_TEAM_TYPE team_type = 1;
int32 scope_name = 2;
}
message SC_CHAR_BAG_ADD_CHAR {
CHAR_INFO char = 1;
int32 scope_name = 2;
}
message SC_CHAR_BAG_SET_TEAM_NAME {
int32 team_index = 1;
string team_name = 2;
int32 scope_name = 3;
}
message SC_CHAR_BAG_SET_TEAM_LEADER {
CHAR_BAG_TEAM_TYPE team_type = 1;
int32 team_index = 2;
uint64 leaderid = 3;
int32 scope_name = 4;
}
message SC_CHAR_BAG_SET_MAX_TEAM_MEMBER_COUNT {
uint32 max_char_team_member_count = 1;
int32 scope_name = 2;
}
message SC_CHAR_BAG_TEAM_LEADER_NOT_MATCH_NTF {
CHAR_BAG_TEAM_TYPE team_type = 1;
int32 team_index = 2;
uint64 leaderid = 3;
int32 scope_name = 4;
}
message SC_CHAR_BAG_DEL_CHAR {
uint64 char_inst_id = 1;
int32 scope_name = 2;
}
message SC_CHAR_BAG_ADD_CHAR_WITH_CONVERSION_NOTIFY {
string char_template_id = 1;
int32 scope_name = 2;
bool is_converted = 3;
string converted_item_id = 4;
string converted_reward_id = 5;
int32 source_reason = 6;
}
message PERSISTENT_BUFF_INFO {
int32 item_num_id = 1;
float duration = 2;
repeated uint64 char_inst_ids = 3;
string stacking_key = 4;
}
message SC_CHAR_BAG_SYNC_PERSISTENT_BUFFS {
repeated PERSISTENT_BUFF_INFO buffs = 1;
}

View File

@@ -0,0 +1,72 @@
syntax = "proto3";
package proto;
option go_package = "/csproto";
import "common.proto";
message ITEM_INFO {
string res_id = 1;
int32 res_count = 2;
}
message CS_CHAR_LEVEL_UP {
uint64 charObjID = 1;
repeated ITEM_INFO items = 2;
}
message SC_CHAR_LEVEL_UP {
uint64 charObjID = 1;
}
message SC_CHAR_SYNC_LEVEL_EXP {
uint64 charObjID = 1;
int32 level = 2;
int32 exp = 3;
}
message SC_CHAR_GAIN_EXP_TOAST {
int32 exp = 1;
int32 expType = 2;
}
message SC_CHAR_SYNC_STATUS {
uint64 objid = 1;
bool is_dead = 2;
BATTLE_INFO battle_info = 3;
}
message CS_CHAR_POTENTIAL_UNLOCK {
uint64 char_obj_id = 1;
uint32 level = 2;
int32 itemId = 3;
}
message SC_CHAR_POTENTIAL_UNLOCK {
uint64 char_obj_id = 1;
uint32 level = 2;
}
message CS_CHAR_SET_POTENTIAL_CG {
uint64 char_obj_id = 1;
int32 potential_level = 2;
string cg_id = 3;
}
message SC_CHAR_SET_POTENTIAL_CG {
uint64 char_obj_id = 1;
int32 potential_level = 2;
string cg_id = 3;
}
message SC_CHAR_SKILL_INFOS {
uint64 char_obj_id = 1;
repeated SKILL_LEVEL_INFO level_infos = 2;
}
message CS_CHAR_SKILL_LEVEL_UP {
uint64 char_obj_id = 1;
string skill_id = 2;
int32 skill_type = 3;
}
message SC_CHAR_SKILL_LEVEL_UP {
uint64 char_obj_id = 1;
SKILL_LEVEL_INFO level_info = 2;
}
message CS_CHAR_UNLOCK_TALENT_NODE {
uint64 char_obj_id = 1;
string node_id = 2;
}
message SC_CHAR_UNLOCK_TALENT_NODE {
uint64 char_obj_id = 1;
string node_id = 2;
}

View File

@@ -0,0 +1,20 @@
syntax = "proto3";
package proto;
option go_package = "/csproto";
message SCENE_COLLECTION {
string scene_name = 1;
string prefab_id = 2;
int32 count = 3;
repeated uint64 detail_obj_id_list = 4;
}
message SC_SCENE_COLLECTION_SYNC {
repeated SCENE_COLLECTION collection_list = 1;
}
message SC_SCENE_COLLECTION_MODIFY {
string scene_name = 1;
string prefab_id = 2;
int32 count = 3;
uint64 new_detail_obj_id = 4;
}

34
proto/pb/cs_dialog.proto Normal file
View File

@@ -0,0 +1,34 @@
syntax = "proto3";
package proto;
option go_package = "/csproto";
import "options.proto";
import "cs_submit_item.proto";
enum DialogExtraInfoType {
DialogExtraInfoType_NONE = 0;
DialogExtraInfoType_SUBMIT_ITEM = 1;
}
message DIALOG {
int32 dialog_id = 1;
repeated int32 option_ids = 2;
repeated int32 finish_nums = 3;
}
message CS_FINISH_DIALOG {
int32 dialog_id = 1;
repeated int32 option_ids = 2;
repeated int32 finish_nums = 3;
DialogExtraInfoType dialog_extra_info_type = 4;
oneof dialog_extra_info {
CS_SCENE_SUBMIT_ITEM submit_info = 5;
}
}
message SC_FINISH_DIALOG {
int32 dialog_id = 1;
repeated int32 option_ids = 2;
repeated int32 finish_nums = 3;
}
message SC_SYNC_ALL_DIALOG {
repeated DIALOG dialog_list = 1;
}

View File

@@ -0,0 +1,123 @@
syntax = "proto3";
package proto;
option go_package = "/csproto";
import "common.proto";
import "options.proto";
message CS_DOMAIN_DEPOT_UNLOCK_REQ {
string domain_depot_id = 1;
}
message SC_DOMAIN_DEPOT_UNLOCK_RSP {
string domain_depot_id = 1;
}
message CS_DOMAIN_DEPOT_LEVEL_UPGRADE_REQ {
string domain_depot_id = 1;
}
message SC_DOMAIN_DEPOT_LEVEL_UPGRADE_RSP {
string domain_depot_id = 1;
uint32 level = 2;
}
message CS_REDUCE_CARGO_INTEGRITY_REQ {
uint64 deliver_inst_id = 1;
int32 reduce_type = 2;
}
message SC_REDUCE_CARGO_INTEGRITY_RSP {
uint64 deliver_inst_id = 1;
int32 current_cargo_integrity = 2;
int64 reward_value = 3;
}
message SCD_DELEGATE_INST_STATUS_CHANGE {
uint64 deliver_inst_id = 1;
bool is_delete = 2;
int32 state = 3;
int64 expire_ts = 4;
}
message SC_DOMAIN_DEPOT_DAILY_REFRESH_TRIGGER {
repeated int32 can_package_domain_depot_num_id_list = 1;
repeated SCD_DELEGATE_INST_STATUS_CHANGE status_change_list = 2;
}
message SC_DOMAIN_DEPOT_SYNC_ALL_INFO {
repeated DOMAIN_DEPOT_INFO domain_depot_info_list = 1;
repeated DOMAIN_DEPOT_DELIVER_DATA domain_depot_deliver_info_list = 2;
int32 daily_take_delegate_count = 3;
uint64 cur_deliver_inst_id = 4;
map<int32, string> version_map = 5;
}
message DOMAIN_DEPOT_INFO {
string domain_depot_id = 1;
int32 level = 2;
bool can_package = 3;
uint64 domain_depot_deliver_inst_id = 4;
}
message CS_DOMAIN_DEPOT_PACK_ITEM_REQ {
string domain_depot_id = 1;
int32 deliver_pack_type = 2;
int32 deliver_item_type = 3;
map<string, int64> items = 4;
}
message SC_DOMAIN_DEPOT_PACK_ITEM_RSP {
string domain_depot_id = 1;
uint64 deliver_inst_id = 2;
repeated DOMAIN_DEPOT_BUYER_INFO buyer_candidate_list = 3;
int32 pack_type = 4;
int32 item_type = 5;
}
message CS_DOMAIN_DEPOT_SELECT_BUYER_REQ {
uint64 deliver_inst_id = 1;
string buyer_id = 2;
}
message SC_DOMAIN_DEPOT_SELECT_BUYER_RSP {
uint64 deliver_inst_id = 1;
DOMAIN_DEPOT_BUYER_INFO buyer_info = 2;
int64 reward_value = 3;
}
message CS_DOMAIN_DEPOT_FETCH_DELEGATE_REQ {
}
message SC_DOMAIN_DEPOT_FETCH_DELEGATE_RSP {
repeated DOMAIN_DEPOT_DELIVER_DATA delegate_info_list = 1;
}
message CS_DOMAIN_DEPOT_DELEGATE_REQ {
uint64 deliver_inst_id = 1;
}
message SC_DOMAIN_DEPOT_DELEGATE_RSP {
uint64 deliver_inst_id = 1;
int64 expire_ts = 2;
}
message CS_DOMAIN_DEPOT_TAKE_DELEGATE_REQ {
uint64 deliver_inst_id = 1;
}
message SC_DOMAIN_DEPOT_TAKE_DELEGATE_RSP {
uint64 deliver_inst_id = 1;
int32 daily_take_delegate_count = 3;
int64 expire_ts = 4;
}
message CS_DOMAIN_DEPOT_RECV_PACKAGE_FOR_DELIVER_REQ {
uint64 deliver_inst_id = 1;
}
message SC_DOMAIN_DEPOT_RECV_PACKAGE_FOR_DELIVER_RSP {
uint64 deliver_inst_id = 1;
}
message CS_DOMAIN_DEPOT_SEND_PACKAGE_FOR_DELIVER_REQ {
uint64 deliver_inst_id = 1;
}
message SC_DOMAIN_DEPOT_SEND_PACKAGE_FOR_DELIVER_RSP {
uint64 deliver_inst_id = 1;
int64 reward_value = 2;
int64 extra_credit_count = 3;
}
message SC_DOMAIN_DEPOT_UPDATE_DELEGATE_INST_STATE {
uint64 deliver_inst_id = 1;
int32 state = 2;
DOMAIN_DEPOT_DELIVER_PEER_ROLE_INFO peer_role_info = 3;
int64 expire_ts = 4;
int64 final_payment_value = 5;
}
message CS_DOMAIN_DEPOT_COLLECT_DELEGATE_REWARD_REQ {
repeated uint64 deliver_inst_id = 2;
}
message SC_DOMAIN_DEPOT_COLLECT_DELEGATE_REWARD_RSP {
repeated uint64 deliver_inst_id = 2;
map<string, int64> money_id_to_add_value = 3;
}

View File

@@ -0,0 +1,43 @@
syntax = "proto3";
package proto;
option go_package = "/csproto";
import "common.proto";
enum DOMAIN_DEVELOPMENT_READ_VERSION_TYPE {
DOMAIN_DEV_SYSTEM = 0;
DOMAIN_DEV_KITE_STATION = 1;
DOMAIN_DEV_SETTLEMENT = 2;
DOMAIN_DEV_DOMAIN_SHOP = 3;
DOMAIN_DEV_DOMAIN_DEPOT = 4;
}
message CS_DOMAIN_DEVELOPMENT_READ_VERSION_INFO {
string chapter_id = 1;
}
message CS_DOMAIN_DEVELOPMENT_TAKE_REWARD {
string chapter_id = 1;
uint64 rewarded_level = 2;
}
message SC_DOMAIN_DEVELOPMENT_SYSTEM_SYNC {
repeated DOMAIN_DEVELOPMENT domains = 1;
}
message SC_DOMAIN_DEVELOPMENT_SYNC {
DOMAIN_DEVELOPMENT domain = 1;
}
message SC_DOMAIN_DEVELOPMENT_DEGREE_MODIFY {
string chapter_id = 1;
DOMAIN_DEVELOPMENT_DEGREE degree = 2;
}
message SC_DOMAIN_DEVELOPMENT_REWARDED_LEVEL_MODIFY {
string chapter_id = 1;
uint64 rewarded_level = 2;
}
message SCD_DOMAIN_DEVELOPMENT_READ_VERSION_INFO_MODIFY {
DOMAIN_DEVELOPMENT_READ_VERSION_TYPE data_type = 1;
string chapter_id = 2;
string version = 3;
}
message SC_DOMAIN_DEVELOPMENT_READ_VERSION_INFO_MODIFY {
repeated SCD_DOMAIN_DEVELOPMENT_READ_VERSION_INFO_MODIFY data = 1;
}

View File

@@ -0,0 +1,19 @@
syntax = "proto3";
package proto;
option go_package = "/csproto";
message SC_SYNC_ALL_DOODAD_GROUP {
repeated DOODAD_GROUP_INFO doodad_group_info_list = 1;
}
message DOODAD_GROUP_INFO {
int32 scene_num_id = 1;
uint64 group_id = 2;
int64 next_refresh_time = 3;
int32 next_refresh_count = 4;
int32 max_count = 5;
int32 cur_count = 8;
}
message SC_DOODAD_GROUP_MODIFY {
repeated DOODAD_GROUP_INFO doodad_group_info_list = 1;
}

85
proto/pb/cs_dungeon.proto Normal file
View File

@@ -0,0 +1,85 @@
syntax = "proto3";
package proto;
option go_package = "/csproto";
import "common.proto";
import "options.proto";
enum DUNGEON_COMPLETION_STATUS {
DC_STATUS_NONE = 0;
DC_STATUS_BEFORE_COMPLETION = 1;
DC_STATUS_BEFORE_REWARD = 2;
DC_STATUS_AFTER_REWARD = 3;
}
message CS_ENTER_DUNGEON {
repeated TEAM_CHAR_ID char_team = 1;
string dungeon_id = 2;
oneof enter_param {
ENTER_RACING_DUNGEON_PARAM racing_param = 21;
ENTER_WEEK_RAID_PARAM week_raid_param = 22;
}
}
message CS_ENTER_TRAIN_DUNGEON {
repeated TEAM_CHAR_ID char_team = 1;
string dungeon_id = 2;
ENTER_RACING_DUNGEON_PARAM racing_param = 10;
}
message ENTER_RACING_DUNGEON_PARAM {
int32 level = 1;
string tactics_id = 2;
}
message ENTER_WEEK_RAID_PARAM {
string item_id = 1;
int32 count = 2;
}
message CS_RESTART_DUNGEON {
string dungeon_id = 1;
DUNGEON_COMPLETION_STATUS completion_status = 2;
}
message CS_LEAVE_DUNGEON {
string dungeon_id = 1;
}
message CS_DUNGEON_RECOVER_AP {
repeated ITEM_BUNDLE items = 1;
bool use_money = 2;
repeated INST_ITEM_BUNDLE inst_items = 3;
int32 expect_money_buy_count = 4;
}
message CS_DUNGEON_RESTORE_AP {
string lunch_box_item_id = 1;
int32 count = 2;
}
message CS_DUNGEON_TOUCH_ENTRANCE {
string dungeon_series_id = 1;
}
message SC_ENTER_DUNGEON {
string dungeon_id = 3;
string scene_id = 4;
}
message SC_RESTART_DUNGEON {
string dungeon_id = 1;
}
message SC_LEAVE_DUNGEON {
string dungeon_id = 1;
}
message SC_SYNC_STAMINA {
uint32 cur_stamina = 1;
uint32 max_stamina = 2;
int64 next_recover_time = 3;
int32 delta = 4;
}
message SC_SYNC_FULL_DUNGEON_STATUS {
uint32 cur_stamina = 1;
uint32 max_stamina = 2;
int64 next_recover_time = 3;
}
message SC_NTF_AP_ITEMS_EXPIRE_AUTO_USE {
uint32 cur_stamina = 1;
int32 delta = 2;
}
message SC_DUNGEON_RESTORE_AP_RSP {
ITEM_BUNDLE cost_empty_lunch_box_item = 1;
int32 cost_stamina = 2;
ITEM_BUNDLE reward_full_lunch_box_item = 3;
}

View File

@@ -0,0 +1,25 @@
syntax = "proto3";
package proto;
option go_package = "/csproto";
message CS_WORLD_ENERGY_POINT_REQ_GROUP_REWARD {
string game_group_id = 1;
int32 reward_multiplier = 2;
bool without_stamina_reward = 3;
bool use_stamina_reduce = 4;
bool use_terms = 5;
}
message SC_WORLD_ENERGY_POINT_REQ_GROUP_REWARD {
string game_id = 1;
int32 reward_multiplier = 2;
bool without_stamina_reward = 3;
bool use_stamina_reduce = 4;
}
message CS_WORLD_ENERGY_POINT_SET_TERMS {
string game_group_id = 1;
repeated string terms = 2;
}
message CS_WORLD_ENERGY_POINT_ABANDON_GROUP_REWARD {
string game_group_id = 1;
}

64
proto/pb/cs_equip.proto Normal file
View File

@@ -0,0 +1,64 @@
syntax = "proto3";
package proto;
option go_package = "/csproto";
import "common.proto";
import "options.proto";
message CS_EQUIP_PUTON {
uint64 charid = 1;
int32 slotid = 2;
uint64 equipid = 3;
}
message SC_EQUIP_PUTON {
uint64 charid = 1;
int32 slotid = 2;
uint64 equipid = 3;
map<string, int32> suitinfo = 4;
uint64 put_off_charid = 5;
map<string, int32> old_owner_suitinfo = 6;
}
message CS_EQUIP_PUTOFF {
uint64 charid = 1;
int32 slotid = 2;
}
message SC_EQUIP_PUTOFF {
uint64 charid = 1;
int32 slotid = 2;
map<string, int32> suitinfo = 3;
}
message CS_EQUIP_MEDICINE_MODIFY {
uint64 char_id = 1;
int32 equip_medicine_id = 2;
}
message SC_EQUIP_MEDICINE_MODIFY {
uint64 char_id = 1;
int32 equip_medicine_id = 2;
}
message CS_EQUIP_RECYCLE {
repeated uint64 equip_instid_list = 1;
int32 scope_name = 2;
}
message SC_EQUIP_RECYCLE {
repeated uint64 equip_instid_list = 1;
repeated string item_id = 2;
repeated int32 item_count = 3;
int32 scope_name = 4;
}
message CS_EQUIP_ENHANCE {
uint64 equip_inst_id = 1;
uint64 ingredient_inst_id = 2;
int32 attr_index = 3;
}
message SC_EQUIP_ENHANCE {
uint64 equip_inst_id = 1;
EQUIP_DATA equip_data = 2;
}
message CS_EQUIP_PRODUCE {
string formula_id = 1;
}
message SC_EQUIP_PRODUCE {
string formula_id = 1;
uint64 equip_instid = 2;
}

300
proto/pb/cs_factory.proto Normal file
View File

@@ -0,0 +1,300 @@
syntax = "proto3";
package proto;
option go_package = "/csproto";
import "options.proto";
enum FACTORY_FORMULA_VISIBLE_REASON {
VISIBLE_REASON_GET_NEW_ITEM = 0;
VISIBLE_REASON_UNLOCK = 1;
VISIBLE_REASON_ITEM_VISIBLE = 2;
VISIBLE_REASON_NEW_BUILDING = 3;
VISIBLE_REASON_NEW_BUILDING_MODE = 4;
VISIBLE_REASON_NEW_FORMULA_DONE = 5;
}
message SCD_FACTORY_STT_NODE {
string id = 1;
int32 state = 2;
map<string, int32> values = 3;
map<string, bool> flags = 4;
bool hidden = 5;
}
message SCD_FACTORY_STT_PACKAGE {
string id = 1;
int32 state = 2;
map<string, int32> values = 3;
map<string, bool> flags = 4;
bool hidden = 5;
}
message SCD_FACTORY_STT_LAYER {
string id = 1;
int32 state = 2;
}
message SCD_FACTORY_STT_CATEGORY {
string id = 1;
bool hidden = 2;
}
message SCD_FACTORY_SYNC_STT {
repeated SCD_FACTORY_STT_NODE nodes = 1;
repeated SCD_FACTORY_STT_PACKAGE packages = 2;
repeated SCD_FACTORY_STT_LAYER layers = 3;
repeated SCD_FACTORY_STT_CATEGORY categories = 4;
}
message SCD_FACTORY_SYNC_FORMULA_MAN {
repeated string unlocked = 1;
repeated string visible = 2;
map<string, SCD_FACTORY_SYNC_FORMULA_MODE> modes = 3;
repeated string level_up = 4;
repeated string product_manual = 5;
}
message SCD_FACTORY_SYNC_FORMULA_MODE {
repeated string building_ids = 1;
}
message SCD_FACTORY_BUS_FREE_CNT_LIMIT {
int32 bus_start_limit = 1;
int32 bus_free_limit = 2;
}
message SCD_FACTORY_PANEL_BUS_FREE_LIMIT {
map<int32, SCD_FACTORY_BUS_FREE_CNT_LIMIT> bus_free_panel_limit = 1;
}
message SCD_FACTORY_SYNC_PROGRESS_STATUS {
repeated string building_power_diffuser_enable = 1;
repeated string building_power_can_be_wire_start = 2;
map<string, SCD_FACTORY_PANEL_BUS_FREE_LIMIT> bus_free_limit = 3;
map<string, SCD_FACTORY_BUILDING_DOMAIN_PLACE_LIMIT> domain_place_limit = 4;
}
message SC_FACTORY_SYNC {
SCD_FACTORY_SYNC_FORMULA_MAN formula_man = 1;
SCD_FACTORY_SYNC_STT stt = 2;
SCD_FACTORY_SYNC_PROGRESS_STATUS progress_status = 3;
}
message SC_FACTORY_MODIFY_FORMULA_MAN {
repeated string new_unlocked = 1;
repeated string new_product_manual = 2;
repeated string new_level_up = 3;
}
message SC_FACTORY_MODIFY_STT {
repeated SCD_FACTORY_STT_NODE nodes = 1;
repeated SCD_FACTORY_STT_PACKAGE packages = 2;
repeated SCD_FACTORY_STT_LAYER layers = 3;
repeated SCD_FACTORY_STT_CATEGORY categories = 4;
}
message FACTORY_FORMULA_VISIABLE {
repeated string formula_list = 1;
repeated string output_list = 2;
repeated string source = 3;
FACTORY_FORMULA_VISIBLE_REASON reason = 4;
}
message SC_FACTORY_MODIFY_VISIBLE_FORMULA {
FACTORY_FORMULA_VISIABLE new_visible = 1;
}
message SC_FACTORY_MODIFY_FORMULA_MODE {
string mode = 1;
repeated string new_building_ids = 2;
}
message SCD_FACTORY_BUILDING_DOMAIN_PLACE_LIMIT {
map<string, int32> building_limit = 1;
}
message SC_FACTORY_MODIFY_PROGRESS_STATUS {
repeated string add_building_power_diffuser_enable = 1;
repeated string add_building_power_can_be_wire_start = 3;
map<string, SCD_FACTORY_PANEL_BUS_FREE_LIMIT> bus_free_limit = 4;
map<string, SCD_FACTORY_BUILDING_DOMAIN_PLACE_LIMIT> domain_place_limit = 5;
}
message SC_FACTORY_SYNC_SCOPE {
int32 scope_name = 1;
string current_chapter_id = 2;
repeated string active_chapter_ids = 3;
SCD_FACTORY_SYNC_HUB_TRANSPORT_ROUTE_MANAGER transport_route = 5;
SCD_FACTORY_STATISTIC_BOOKMARK book_mark = 6;
SCD_FACTORY_SYNC_PANEL_STORE panel_store = 7;
SCD_FACTORY_SYNC_SIGN_MGR sign_mgr = 8;
SCD_FACTORY_SYNC_SHARED_MGR shared_mgr = 9;
}
message CS_FACTORY_STATISTIC_SET_BOOKMARK_ITEM_IDS {
repeated string item_ids = 1;
bool is_remove = 2;
int32 scope_name = 11;
}
message SC_FACTORY_MODIFY_STATISTIC_BOOKMARK {
SCD_FACTORY_STATISTIC_BOOKMARK book_mark = 1;
int32 scope_name = 11;
}
message SCD_FACTORY_STATISTIC_BOOKMARK {
repeated string bookmark_item_ids = 1;
}
message SCD_FACTORY_SYNC_HUB_TRANSPORT_ROUTE_MANAGER {
int64 update_ts = 1;
repeated SCD_FACTORY_HUB_TRANSPORT_ROUTE routes = 2;
}
message SCD_FACTORY_HUB_TRANSPORT_ROUTE {
string chapter_id = 1;
int32 index = 2;
string target_chapter_id = 3;
string item_id = 4;
int32 item_num = 5;
int32 item_num_max = 6;
int32 status = 7;
int64 progress = 8;
bool lossless = 9;
}
message CS_FACTORY_HUB_TRANSPORT_ROUTE_SET {
int32 scope_name = 1;
string chapter_id = 2;
int32 index = 3;
string target_chapter_id = 4;
string item_id = 5;
int32 item_num_max = 6;
bool lossless = 7;
}
message CS_FACTORY_HUB_TRANSPORT_ROUTE_RESET {
int32 scope_name = 1;
string chapter_id = 2;
int32 index = 3;
}
message CS_FACTORY_HUB_TRANSPORT_ROUTE_RESTART {
int32 scope_name = 1;
string chapter_id = 2;
int32 index = 3;
}
message SC_FACTORY_HUB_TRANSPORT_ROUTE_MODIFY {
SCD_FACTORY_SYNC_HUB_TRANSPORT_ROUTE_MANAGER transport_route_modified = 1;
}
message SC_FACTORY_RELEASE_SCOPE {
int32 scope_name = 1;
repeated string delete_chapter_id_list = 11;
}
message SC_FACTORY_MODIFY_SCOPE {
int32 scope_name = 1;
string current_chapter_id = 2;
repeated string delete_chapter_id_list = 11;
}
message SC_FACTORY_SOIL_RECLAIM {
int32 scope_name = 1;
string chapter_id = 2;
uint32 node_id = 3;
bool result = 4;
}
message SC_FACTORY_SOIL_WATER {
int32 scope_name = 1;
string chapter_id = 2;
uint32 node_id = 3;
bool result = 4;
}
message SC_FACTORY_SOIL_HARVEST {
int32 scope_name = 1;
string chapter_id = 2;
uint32 node_id = 3;
bool result = 4;
}
message CS_FACTORY_SOIL_FERTILIZE {
int32 scope_name = 1;
string chapter_id = 2;
uint32 node_id = 3;
string item_id = 4;
}
message SC_FACTORY_SOIL_FERTILIZE {
int32 scope_name = 1;
string chapter_id = 2;
uint32 node_id = 3;
string item_id = 4;
bool result = 5;
}
message CS_FACTORY_STT_UNLOCK_LAYER {
string layer_id = 1;
}
message CS_FACTORY_STT_UNLOCK_NODE {
string node_id = 1;
}
message CS_FACTORY_SOIL_RECLAIM {
int32 scope_name = 1;
string chapter_id = 2;
uint32 node_id = 3;
uint64 char_object_id = 4;
}
message CS_FACTORY_SOIL_WATER {
int32 scope_name = 1;
string chapter_id = 2;
uint32 node_id = 3;
uint64 char_object_id = 4;
}
message CS_FACTORY_SOIL_HARVEST {
int32 scope_name = 1;
string chapter_id = 2;
uint32 node_id = 3;
uint64 char_object_id = 4;
}
message SC_FACTORY_SYNC_OFFLINE_INFO {
int64 max_offline_calc_sec = 1;
int64 end_offline_calc_ts = 2;
int64 offline_sec = 3;
int64 miss_offline_sec = 4;
}
message CS_FACTORY_HUB_WORKSHOP_MAKE {
int32 scope_name = 1;
uint32 node_id = 2;
string formula_id = 3;
int32 multi = 4;
}
message SC_FACTORY_HUB_WORKSHOP_MAKE {
bool success = 1;
}
message CS_FACTORY_PRODUCT_MANUAL_UNLOCK {
int32 scope_name = 1;
repeated string id_list = 2;
}
message SC_FACTORY_PRODUCT_MANUAL_UNLOCK {
int32 scope_name = 1;
repeated string id_list = 2;
}
message CS_FACTORY_MANUALLY_WORK_EXEC {
int32 scope_name = 1;
string formula_id = 2;
int32 count = 3;
}
message SC_FACTORY_MANUALLY_WORK_EXEC {
int32 scope_name = 1;
string formula_id = 2;
int32 count = 3;
}
message CS_FACTORY_PANEL_STORE_BUY {
string id = 1;
}
message SC_FACTORY_MODIFY_PANEL_STORE {
repeated SCD_FACTORY_PANEL_STORE_GOOD goods = 1;
}
message SCD_FACTORY_SYNC_PANEL_STORE {
repeated SCD_FACTORY_PANEL_STORE_GOOD goods = 1;
}
message SCD_FACTORY_PANEL_STORE_GOOD {
string id = 1;
int32 state = 2;
}
message SCD_FACTORY_SYNC_SIGN_MGR {
repeated SCD_FACTORY_SIGN_NODE_INFO sign_node = 1;
}
message SCD_FACTORY_SIGN_NODE_INFO {
string chapter_id = 1;
uint32 node_id = 2;
repeated int32 sign_id = 3;
}
message SC_FACTORY_MODIFY_SIGN_MGR {
int32 scope_name = 1;
SCD_FACTORY_SYNC_SIGN_MGR sign_mgr = 2;
}
message SCD_FACTORY_SYNC_SHARED_MGR {
repeated SCD_FACTORY_SHARED_NODE_INFO recevie_shared_nodes = 1;
}
message SCD_FACTORY_SHARED_NODE_INFO {
int32 scene_num_id = 1;
uint32 node_id = 2;
uint64 owner_id = 3;
uint64 shared_role_id = 4;
int64 create_timestamp = 5;
}
message SC_FACTORY_MODIFY_SHARED_MGR {
int32 scope_name = 1;
SCD_FACTORY_SYNC_SHARED_MGR shared_mgr = 2;
bool is_add = 3;
}

View File

@@ -0,0 +1,224 @@
syntax = "proto3";
package proto;
option go_package = "/csproto";
import "cs_factory_op.proto";
import "cs_factory_chapter.proto";
import "options.proto";
enum FACTORY_BP_REVIEW_STATUS {
PENDING = 0;
IN_PROGRESS = 1;
APPROVED = 2;
}
enum FACTORY_BP_SOURCE_TYPE {
FBST_MINE = 0;
FBST_SYS = 1;
FBST_GIFT = 2;
FBST_PRESET = 3;
}
enum FACTORY_BP_MODIFY_TYPE {
FBMT_NONE = 0;
FBMT_NAME = 1;
FBMT_DESC = 2;
FBMT_ICON = 3;
FBMT_PROD_ICON = 4;
FBMT_BP_TAGS = 5;
}
message CSD_FACTORY_BLUE_PRINT_SIZE {
int32 x_len = 1;
int32 z_len = 2;
}
message CSD_FACTORY_BLUE_PRINT_ICON {
string icon = 1;
int32 base_color = 2;
}
message CSD_BLUE_PRINT_TRANSFORM {
SCD_VEC3_INT position = 1;
SCD_VEC3_INT direction = 2;
CSD_FACTORY_INTERACTIVE_PARAM interactive_param = 3;
SCD_VEC3_INT direction_in = 6;
SCD_VEC3_INT direction_out = 7;
repeated SCD_VEC3_INT points = 8;
}
message CSD_BLUE_PRINT_NODE {
string template_id = 1;
string product_icon = 2;
int32 node_id = 3;
CSD_BLUE_PRINT_TRANSFORM transform = 4;
repeated CSD_BATCH_PLACE_COM coms = 5;
}
message CSD_FACTORY_BLUE_PRINT_DATA {
string name = 3;
string desc = 4;
CSD_FACTORY_BLUE_PRINT_SIZE bp_size = 5;
CSD_FACTORY_BLUE_PRINT_ICON bp_icon = 7;
repeated int32 bp_tags = 8;
FACTORY_BP_REVIEW_STATUS review_status = 9;
CSD_FACTORY_BLUE_PRINT_PARAM bp_param = 10;
int32 use_count = 11;
uint64 creator_role_id = 12;
string creator_user_id = 13;
repeated CSD_BLUE_PRINT_NODE nodes = 14;
bool is_new = 15;
int64 fetch_time = 16;
}
message CS_FACTORY_SAVE_BLUE_PRINT {
string index = 1;
CSD_FACTORY_BLUE_PRINT_DATA blue_print_data = 2;
}
message SC_FACTORY_SAVE_BLUE_PRINT {
string index = 1;
}
message CSD_FACTORY_BLUE_PRINT_NODE_PRODUCT_ICON {
repeated int32 node_id = 1;
repeated string prod_icon = 2;
}
message CSD_FACTORY_BLUE_PRINT_TAGS {
repeated int32 tags = 1;
}
message FACTORY_BLUE_PRINT_MODIFY_DATA {
FACTORY_BP_MODIFY_TYPE modify_type = 1;
oneof op_payload {
string modify_name = 11;
string modify_desc = 12;
CSD_FACTORY_BLUE_PRINT_ICON modify_bp_icon = 14;
CSD_FACTORY_BLUE_PRINT_NODE_PRODUCT_ICON nodes_prod_icon = 15;
CSD_FACTORY_BLUE_PRINT_TAGS modify_tags = 16;
}
}
message CS_FACTORY_MODIFY_BLUE_PRINT {
string index = 1;
repeated FACTORY_BLUE_PRINT_MODIFY_DATA modify_data = 3;
CSD_FACTORY_BLUE_PRINT_PARAM bp_param = 4;
}
message SC_FACTORY_MODIFY_BLUE_PRINT {
string index = 1;
repeated FACTORY_BLUE_PRINT_MODIFY_DATA modify_data = 3;
CSD_FACTORY_BLUE_PRINT_PARAM bp_param = 4;
}
message CS_FACTORY_DEL_BLUE_PRINT {
string index = 1;
repeated CSD_FACTORY_BLUE_PRINT_PARAM bp_param = 3;
}
message SC_FACTORY_DEL_BLUE_PRINT {
string index = 1;
repeated CSD_FACTORY_BLUE_PRINT_PARAM bp_param = 3;
}
message SC_FACTORY_SYNC_BLUE_PRINT_DATA {
CSD_FACTORY_BLUE_PRINT_DATA blue_print_data = 1;
}
message SCD_FACTORY_SYS_BLUE_PRINT_DATA {
CSD_FACTORY_BLUE_PRINT_PARAM sys_bp_param = 1;
uint64 bp_uid = 2;
int32 use_count = 3;
int64 fetch_time = 4;
bool is_new = 5;
}
message SCD_FACTORY_GIFT_BLUE_PRINT_DATA {
CSD_FACTORY_BLUE_PRINT_PARAM gift_bp_param = 1;
int32 use_count = 2;
int64 fetch_time = 3;
bool is_new = 4;
}
message SC_FACTORY_SYNC_BLUE_PRINT_MGR {
repeated CSD_FACTORY_BLUE_PRINT_DATA blue_print_info = 1;
repeated SCD_FACTORY_SYS_BLUE_PRINT_DATA sys_blue_print = 2;
repeated SCD_FACTORY_GIFT_BLUE_PRINT_DATA gift_blue_print = 3;
SCD_FACTORY_BP_PUBLIC_STATUS fac_bp_pub_status = 4;
}
message CSD_FACTORY_GIFT_BLUE_PRINT {
uint64 bp_uid = 1;
uint64 target_role_id = 2;
int32 share_idx = 3;
}
message CSD_FACTORY_BLUE_PRINT_PARAM {
FACTORY_BP_SOURCE_TYPE source_type = 1;
oneof op_payload {
uint64 my_bp_uid = 11;
string sys_bp_key = 12;
CSD_FACTORY_GIFT_BLUE_PRINT gift_bp_key = 13;
string preset_bp_key = 14;
}
}
message CS_FACTORY_USE_BLUE_PRINT {
string index = 1;
int32 scope_name = 3;
int32 map_id = 4;
CSD_FACTORY_AREA_RELATED_POS related_pos = 5;
CSD_FACTORY_BLUE_PRINT_PARAM bp_param = 6;
}
message SC_FACTORY_USE_BLUE_PRINT {
string index = 1;
CSD_FACTORY_BLUE_PRINT_PARAM bp_param = 2;
int32 use_count = 3;
}
message CS_FACTORY_REVIEW_BLUE_PRINT {
string index = 1;
CSD_FACTORY_BLUE_PRINT_PARAM bp_param = 2;
}
message SC_FACTORY_REVIEW_BLUE_PRINT {
string index = 1;
CSD_FACTORY_BLUE_PRINT_PARAM bp_param = 2;
FACTORY_BP_REVIEW_STATUS status = 3;
}
message CS_FACTORY_SHARE_BLUE_PRINT {
string index = 1;
CSD_FACTORY_BLUE_PRINT_PARAM bp_param = 2;
}
message SC_FACTORY_SHARE_BLUE_PRINT {
string index = 1;
string share_code = 2;
}
message CS_FACTORY_QUERY_SHARED_BLUE_PRINT {
string index = 1;
string share_code = 2;
}
message SC_FACTORY_QUERY_SHARED_BLUE_PRINT {
string index = 1;
CSD_FACTORY_BLUE_PRINT_DATA blue_print_data = 2;
}
message CS_FACTORY_FETCH_GIFT_BLUE_PRINT {
string index = 1;
string share_code = 2;
}
message SC_FACTORY_FETCH_GIFT_BLUE_PRINT {
string index = 1;
}
message CS_FACTORY_REPORT_BLUE_PRINT {
string index = 1;
CSD_FACTORY_BLUE_PRINT_PARAM bp_param = 2;
repeated string reason = 3;
string content = 4;
}
message SC_FACTORY_REPORT_BLUE_PRINT {
string index = 1;
}
message CS_FACTORY_QUERY_GIFT_BLUE_PRINT {
string index = 1;
repeated CSD_FACTORY_BLUE_PRINT_PARAM bp_params = 2;
}
message SC_FACTORY_QUERY_GIFT_BLUE_PRINT {
string index = 1;
repeated CSD_FACTORY_BLUE_PRINT_DATA blue_print_data = 2;
}
message CSD_FACTORY_SET_BP_FLAG {
CSD_FACTORY_BLUE_PRINT_PARAM bp_param = 1;
bool is_new = 2;
}
message CS_FACTORY_SET_BLUE_PRINT_FLAG {
string index = 1;
repeated CSD_FACTORY_SET_BP_FLAG set_bp_flag = 2;
}
message SC_FACTORY_SET_BLUE_PRINT_FLAG {
string index = 1;
repeated CSD_FACTORY_SET_BP_FLAG success_set = 2;
}
message SCD_FACTORY_BP_PUBLIC_STATUS {
bool forbid_share = 1;
bool forbid_fetch = 2;
}
message SC_FACTORY_BP_PUBLIC_STATUS {
SCD_FACTORY_BP_PUBLIC_STATUS public_status = 1;
}

Some files were not shown because too many files have changed in this diff Show More